From b7bed6a59c9741995237c262601db78f3737d6d6 Mon Sep 17 00:00:00 2001 From: Pidgey <35320204+PidgeyThePirate@users.noreply.github.com> Date: Wed, 2 Oct 2019 16:53:46 +1000 Subject: [PATCH 001/238] NO_DNA humans can no longer husk. --- code/modules/mob/living/carbon/human/death.dm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 4ceedc5b49c..d95badbbb01 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -158,10 +158,14 @@ return /mob/living/carbon/human/proc/ChangeToHusk() - var/obj/item/organ/external/head/H = bodyparts_by_name["head"] + + // If the target has no DNA to begin with, its DNA can't be damaged beyond repair. + if(NO_DNA in dna.species.species_traits) + return if(HUSK in mutations) return + var/obj/item/organ/external/head/H = bodyparts_by_name["head"] if(istype(H)) H.disfigured = TRUE //makes them unknown without fucking up other stuff like admintools if(H.f_style) From 202091090f45186822d666cc6038c376b165d84a Mon Sep 17 00:00:00 2001 From: SteelSlayer Date: Sun, 20 Oct 2019 00:34:19 -0500 Subject: [PATCH 002/238] Gives players an option to hear AI voice announcements as text announcements --- code/__DEFINES/preferences.dm | 5 +++-- .../client/preference/preferences_toggles.dm | 10 ++++++++++ code/modules/mob/living/silicon/ai/say.dm | 18 +++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index 54fd59307f4..eb5fad0ff27 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -7,9 +7,10 @@ #define SOUND_BUZZ 64 #define SOUND_INSTRUMENTS 128 #define SOUND_MENTORHELP 256 -#define SOUND_DISCO 512 +#define SOUND_DISCO 512 +#define SOUND_AI_VOICE 1024 -#define SOUND_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|SOUND_HEARTBEAT|SOUND_BUZZ|SOUND_INSTRUMENTS|SOUND_MENTORHELP|SOUND_DISCO) +#define SOUND_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|SOUND_HEARTBEAT|SOUND_BUZZ|SOUND_INSTRUMENTS|SOUND_MENTORHELP|SOUND_DISCO|SOUND_AI_VOICE) #define CHAT_OOC 1 #define CHAT_DEAD 2 diff --git a/code/modules/client/preference/preferences_toggles.dm b/code/modules/client/preference/preferences_toggles.dm index d5e4b2bf3fe..3914b43b8c7 100644 --- a/code/modules/client/preference/preferences_toggles.dm +++ b/code/modules/client/preference/preferences_toggles.dm @@ -36,6 +36,16 @@ to_chat(usr, "You will [(prefs.toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from radios or speakers") feedback_add_details("admin_verb","THR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/verb/toggle_ai_voice_annoucements() + set name = "Hear/Silence AI Voice Announcements" + set category = "Preferences" + set desc = "Toggle hearing AI annoucements in voice form or in text form" + if(!holder) return + prefs.sound ^= SOUND_AI_VOICE + prefs.save_preferences(src) + to_chat(usr, "[(prefs.sound & SOUND_AI_VOICE) ? "You will now hear AI announcements." : "AI annoucements will now be converted to text."] ") + feedback_add_details("admin_verb","TAIvoice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + /client/proc/toggleadminhelpsound() set name = "Hear/Silence Admin Bwoinks" set category = "Preferences" diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm index 36f248cd2c2..24cf464234b 100644 --- a/code/modules/mob/living/silicon/ai/say.dm +++ b/code/modules/mob/living/silicon/ai/say.dm @@ -134,6 +134,22 @@ var/const/VOX_PATH = "sound/vox_fem/" for(var/word in words) play_vox_word(word, src.z, null) + ai_voice_announcement_to_text(words, src) + + +/proc/ai_voice_announcement_to_text(words, ai) + var/mob/living/silicon/ai/AI = ai + var/words_string = jointext(words, " ") + var/formatted_message = "

A.I. Announcement

" + formatted_message += "
[words_string]" + formatted_message += "
-[AI]" + + for(var/mob/M in GLOB.player_list) + if(M.client && !(M.client.prefs.sound & SOUND_AI_VOICE)) + var/turf/T = get_turf(M) + if(T && T.z == AI.z && M.can_hear()) + M << 'sound/misc/notice2.ogg' + to_chat(M, formatted_message) /proc/play_vox_word(word, z_level, mob/only_listener) @@ -149,7 +165,7 @@ var/const/VOX_PATH = "sound/vox_fem/" if(!only_listener) // Play voice for all mobs in the z level for(var/mob/M in GLOB.player_list) - if(M.client) + if(M.client && M.client.prefs.sound & SOUND_AI_VOICE) var/turf/T = get_turf(M) if(T && T.z == z_level && M.can_hear()) M << voice From 93b4cb9efedd6fc90824f2e28c2a59f3c162ef4b Mon Sep 17 00:00:00 2001 From: Kyep Date: Sat, 19 Oct 2019 23:56:29 -0700 Subject: [PATCH 003/238] Option to activate forum account when clicking 'forum' button --- interface/interface.dm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/interface.dm b/interface/interface.dm index 8edded38e93..a733b8fed78 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -47,9 +47,11 @@ set desc = "Visit the forum." set hidden = 1 if(config.forumurl) - if(alert("This will open the forum in your browser. Are you sure?",,"Yes","No")=="No") - return - src << link(config.forumurl) + if(config.forum_link_url && prefs && !prefs.fuid) + if(alert("You need to activate your forum account. Activate now? (takes 2 seconds)",,"Yes","No")=="Yes") + link_forum_account() + if(alert("Open the forum in your browser?",,"Yes","No")=="Yes") + src << link(config.forumurl) else to_chat(src, "The forum URL is not set in the server configuration.") @@ -85,7 +87,7 @@ src << link(config.discordurl) else to_chat(src, "The Discord URL is not set in the server configuration.") - + /client/verb/donate() set name = "Donate" set desc = "Donate to help with hosting costs." @@ -96,7 +98,7 @@ src << link(config.donationsurl) else to_chat(src, "The rules URL is not set in the server configuration.") - + /client/verb/hotkeys_help() set name = "Hotkey Help" set category = "OOC" From 4ccf3f392484fcae0eef72135ee2e4876621c986 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 20 Oct 2019 00:02:28 -0700 Subject: [PATCH 004/238] Better tracking for admin online status --- code/game/jobs/job_exp.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm index 359981ded4f..1b64b3e8aee 100644 --- a/code/game/jobs/job_exp.dm +++ b/code/game/jobs/job_exp.dm @@ -286,9 +286,9 @@ var/global/list/role_playtime_requirements = list( var/new_exp = list2params(play_records) prefs.exp = new_exp new_exp = sanitizeSQL(new_exp) - var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("player")] SET exp = '[new_exp]' WHERE ckey='[ckey]'") + var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("player")] SET exp = '[new_exp]',lastseen = Now() WHERE ckey='[ckey]'") if(!update_query.Execute()) var/err = update_query.ErrorMsg() log_game("SQL ERROR during exp_update_client write. Error : \[[err]\]\n") message_admins("SQL ERROR during exp_update_client write. Error : \[[err]\]\n") - return + return \ No newline at end of file From adbe7f1b4324019616b17cc633fc9a0db4c348d6 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 20 Oct 2019 02:19:53 -0700 Subject: [PATCH 005/238] Hide forum link for new players --- code/modules/client/client procs.dm | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 6f971d308fa..410109151ba 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -413,7 +413,7 @@ for(var/mob/M in GLOB.player_list) if(M.client) playercount += 1 - + if(playercount >= 150 && GLOB.panic_bunker_enabled == 0) GLOB.panic_bunker_enabled = 1 message_admins("Panic bunker has been automatically enabled due to playercount surpassing 150") @@ -553,7 +553,7 @@ src << "Sorry but the server is currently not accepting connections from never before seen players. Please try again later." del(src) return // Dont insert or they can just go in again - + var/DBQuery/query_insert = dbcon.NewQuery("INSERT INTO [format_table_name("player")] (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, '[ckey]', Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]')") if(!query_insert.Execute()) var/err = query_insert.ErrorMsg() @@ -601,8 +601,13 @@ /client/proc/check_forum_link() - if(config.forum_link_url && prefs && !prefs.fuid) - to_chat(src, "You do not have your forum account linked. LINK FORUM ACCOUNT") + if(!config.forum_link_url || !prefs || prefs.fuid) + return + if(config.use_exp_tracking) + var/living_hours = get_exp_type_num(EXP_TYPE_LIVING) / 60 + if(living_hours < 20) + return + to_chat(src, "You have no verified forum account. VERIFY FORUM ACCOUNT") /client/proc/create_oauth_token() var/DBQuery/query_find_token = dbcon.NewQuery("SELECT token FROM [format_table_name("oauth_tokens")] WHERE ckey = '[ckey]' limit 1") @@ -774,7 +779,7 @@ // Change the way they should download resources. if(config.resource_urls) preload_rsc = pick(config.resource_urls) - else + else preload_rsc = 1 // If config.resource_urls is not set, preload like normal. // Most assets are now handled through global_cache.dm getFiles( From 3e797180fce006b098c3e907db0b1902d0e6a2f8 Mon Sep 17 00:00:00 2001 From: SteelSlayer Date: Sun, 20 Oct 2019 11:54:57 -0500 Subject: [PATCH 006/238] removes if(!holder) check --- code/modules/client/preference/preferences_toggles.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/modules/client/preference/preferences_toggles.dm b/code/modules/client/preference/preferences_toggles.dm index 3914b43b8c7..5534e18e253 100644 --- a/code/modules/client/preference/preferences_toggles.dm +++ b/code/modules/client/preference/preferences_toggles.dm @@ -40,7 +40,6 @@ set name = "Hear/Silence AI Voice Announcements" set category = "Preferences" set desc = "Toggle hearing AI annoucements in voice form or in text form" - if(!holder) return prefs.sound ^= SOUND_AI_VOICE prefs.save_preferences(src) to_chat(usr, "[(prefs.sound & SOUND_AI_VOICE) ? "You will now hear AI announcements." : "AI annoucements will now be converted to text."] ") From e2fdc0472a37d34d50e66da06088aca9e36ca209 Mon Sep 17 00:00:00 2001 From: Emmett Gaines Date: Mon, 21 Oct 2019 14:29:10 -0400 Subject: [PATCH 007/238] Adds vscode extension recommendations for highlighting and langserv --- .gitignore | 3 ++- .vscode/extensions.json | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .vscode/extensions.json diff --git a/.gitignore b/.gitignore index a4634f3fc84..d9f4a187400 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ data/ *.db stddef.dm .atom-build.json -*.vscode/* \ No newline at end of file +*.vscode/* +!/.vscode/extensions.json \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000000..9099488143b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "gbasood.byond-dm-language-support", + "platymuus.dm-langclient" + ] +} From 902b270ab17d02b6c5f25d473f2652dace274770 Mon Sep 17 00:00:00 2001 From: datlo Date: Thu, 24 Oct 2019 20:31:54 +0200 Subject: [PATCH 008/238] His grace action button now links the box --- .../items/weapons/storage/artistic_toolbox.dm | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/code/game/objects/items/weapons/storage/artistic_toolbox.dm b/code/game/objects/items/weapons/storage/artistic_toolbox.dm index 0d8613cce4a..dfdf57f4b47 100644 --- a/code/game/objects/items/weapons/storage/artistic_toolbox.dm +++ b/code/game/objects/items/weapons/storage/artistic_toolbox.dm @@ -26,41 +26,45 @@ if(istype(M)) to_chat(user, "His Grace [flags & NODROP ? "releases from" : "binds to"] your hand!") flags ^= NODROP + else if(!activated && loc == user) + link_user(user) else to_chat(user, "You can't seem to understand what this does.") /obj/item/storage/toolbox/green/memetic/attack_hand(mob/living/carbon/user) - if(loc == user) - if(!activated) - if(ishuman(user) && !user.HasDisease(new /datum/disease/memetic_madness(0))) - activated = TRUE - user.ForceContractDisease(new /datum/disease/memetic_madness(0)) - for(var/datum/disease/memetic_madness/DD in user.viruses) - DD.progenitor = src - servantlinks.Add(DD) - break - force += 4 - throwforce += 4 - SEND_SOUND(user, 'sound/goonstation/effects/screech.ogg') - shake_camera(user, 20, 1) - var/acount = 0 - var/amax = rand(10, 15) - var/up_and_down - var/asize = 1 - while(acount <= amax) - up_and_down += "a" - if(acount > (amax * 0.5)) - asize-- - else - asize++ - acount++ - to_chat(user, "[up_and_down]") - to_chat(user, "His Grace accepts thee, spread His will! All who look close to the Enlightened may share His gifts.") - original_owner = user - return + if(!activated && loc == user) + link_user(user) + return ..() +/obj/item/storage/toolbox/green/memetic/proc/link_user(mob/living/carbon/user) + if(ishuman(user) && !user.HasDisease(new /datum/disease/memetic_madness(0))) + activated = TRUE + user.ForceContractDisease(new /datum/disease/memetic_madness(0)) + for(var/datum/disease/memetic_madness/DD in user.viruses) + DD.progenitor = src + servantlinks.Add(DD) + break + force += 4 + throwforce += 4 + SEND_SOUND(user, 'sound/goonstation/effects/screech.ogg') + shake_camera(user, 20, 1) + var/acount = 0 + var/amax = rand(10, 15) + var/up_and_down + var/asize = 1 + while(acount <= amax) + up_and_down += "a" + if(acount > (amax * 0.5)) + asize-- + else + asize++ + acount++ + to_chat(user, "[up_and_down]") + to_chat(user, "His Grace accepts thee, spread His will! All who look close to the Enlightened may share His gifts.") + original_owner = user + /obj/item/storage/toolbox/green/memetic/attackby(obj/item/I, mob/user) if(activated) if(istype(I, /obj/item/grab)) From 6368447f83ffd2d7a9249904366e559734ce035c Mon Sep 17 00:00:00 2001 From: datlo Date: Thu, 24 Oct 2019 20:41:36 +0200 Subject: [PATCH 009/238] Add nodrop notification --- code/game/objects/items/weapons/storage/artistic_toolbox.dm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/game/objects/items/weapons/storage/artistic_toolbox.dm b/code/game/objects/items/weapons/storage/artistic_toolbox.dm index dfdf57f4b47..48d59ad8830 100644 --- a/code/game/objects/items/weapons/storage/artistic_toolbox.dm +++ b/code/game/objects/items/weapons/storage/artistic_toolbox.dm @@ -27,7 +27,8 @@ to_chat(user, "His Grace [flags & NODROP ? "releases from" : "binds to"] your hand!") flags ^= NODROP else if(!activated && loc == user) - link_user(user) + if(link_user(user)) + to_chat(user, "Call to His Grace again if you wish it bound to your hand!") else to_chat(user, "You can't seem to understand what this does.") @@ -64,6 +65,8 @@ to_chat(user, "[up_and_down]") to_chat(user, "His Grace accepts thee, spread His will! All who look close to the Enlightened may share His gifts.") original_owner = user + return TRUE + return FALSE /obj/item/storage/toolbox/green/memetic/attackby(obj/item/I, mob/user) if(activated) From a0f7c9cacb4dfe1a59078f71196ea3bd3c448ea6 Mon Sep 17 00:00:00 2001 From: Citinited Date: Fri, 25 Oct 2019 00:50:42 +0100 Subject: [PATCH 010/238] Particle accelerator will irradiate mobs again; adds COMSIG_CROSSED_MOVABLE signal --- code/__DEFINES/components.dm | 1 + code/game/atoms_movable.dm | 1 + code/modules/mob/living/carbon/human/human.dm | 6 -- .../mob/living/simple_animal/bot/mulebot.dm | 6 ++ .../particle_accelerator/particle.dm | 57 ++++++++----------- 5 files changed, 31 insertions(+), 40 deletions(-) diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index df957eaa11e..c94dff5ca6c 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -107,6 +107,7 @@ #define COMSIG_MOVABLE_MOVED "movable_moved" //from base of atom/movable/Moved(): (/atom, dir) #define COMSIG_MOVABLE_CROSS "movable_cross" //from base of atom/movable/Cross(): (/atom/movable) #define COMSIG_MOVABLE_CROSSED "movable_crossed" //from base of atom/movable/Crossed(): (/atom/movable) +#define COMSIG_CROSSED_MOVABLE "crossed_movable" //when we cross over something (calling Crossed() on that atom) #define COMSIG_MOVABLE_UNCROSSED "movable_uncrossed" //from base of atom/movable/Uncrossed(): (/atom/movable) #define COMSIG_MOVABLE_UNCROSS "movable_uncross" //from base of atom/movable/Uncross(): (/atom/movable) #define COMPONENT_MOVABLE_BLOCK_UNCROSS 1 diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index cf7f931ff32..2fb8796dc10 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -220,6 +220,7 @@ // This is automatically called when something enters your square /atom/movable/Crossed(atom/movable/AM, oldloc) SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM) + SEND_SIGNAL(AM, COMSIG_CROSSED_MOVABLE, src) /atom/movable/Bump(atom/A, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump(). if(A && yes) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index fd4c9f0116e..c41a11079d5 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -457,12 +457,6 @@ popup.set_content(dat) popup.open() - -/mob/living/carbon/human/Crossed(atom/movable/AM, oldloc) - var/mob/living/simple_animal/bot/mulebot/MB = AM - if(istype(MB)) - MB.RunOver(src) - // Get rank from ID, ID inside PDA, PDA, ID in wallet, etc. /mob/living/carbon/human/proc/get_authentification_rank(var/if_no_id = "No id", var/if_no_job = "No job") var/obj/item/pda/pda = wear_id diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index d0271feeb87..75418b6f373 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -65,6 +65,7 @@ mulebot_count++ set_suffix(suffix ? suffix : "#[mulebot_count]") + RegisterSignal(src, COMSIG_CROSSED_MOVABLE, .proc/human_squish_check) /mob/living/simple_animal/bot/mulebot/Destroy() unload(0) @@ -868,6 +869,11 @@ else ..() +/mob/living/simple_animal/bot/mulebot/proc/human_squish_check(src, atom/movable/AM) + if(!ishuman(AM)) + return + RunOver(AM) + #undef SIGH #undef ANNOYED #undef DELIGHT diff --git a/code/modules/power/singularity/particle_accelerator/particle.dm b/code/modules/power/singularity/particle_accelerator/particle.dm index 327c29cebda..c7fcfcda5e7 100644 --- a/code/modules/power/singularity/particle_accelerator/particle.dm +++ b/code/modules/power/singularity/particle_accelerator/particle.dm @@ -22,30 +22,29 @@ energy = 50 -/obj/effect/accelerated_particle/New(loc) - ..() +/obj/effect/accelerated_particle/Initialize(loc) + . = ..() + addtimer(CALLBACK(src, .proc/propagate), 1) + RegisterSignal(src, COMSIG_CROSSED_MOVABLE, .proc/try_irradiate) + RegisterSignal(src, COMSIG_MOVABLE_CROSSED, .proc/try_irradiate) + QDEL_IN(src, movement_range) - addtimer(CALLBACK(src, .proc/move), 1) - - -/obj/effect/accelerated_particle/Bump(atom/A) - if(A) - if(isliving(A)) - toxmob(A) - else if(istype(A, /obj/machinery/the_singularitygen)) - var/obj/machinery/the_singularitygen/S = A - S.energy += energy - else if(istype(A, /obj/singularity)) - var/obj/singularity/S = A - S.energy += energy - else if(istype(A, /obj/structure/blob)) - var/obj/structure/blob/B = A - B.take_damage(energy * 0.6) - movement_range = 0 - -/obj/effect/accelerated_particle/Crossed(atom/A, oldloc) +/obj/effect/accelerated_particle/proc/try_irradiate(src, atom/A) if(isliving(A)) - toxmob(A) + var/mob/living/L = A + L.apply_effect((energy * 6), IRRADIATE, 0) + else if(istype(A, /obj/machinery/the_singularitygen)) + var/obj/machinery/the_singularitygen/S = A + S.energy += energy + else if(istype(A, /obj/structure/blob)) + var/obj/structure/blob/B = A + B.take_damage(energy * 0.6) + movement_range = 0 + +/obj/effect/accelerated_particle/Bump(obj/singularity/S) + if(!istype(S)) + return ..() + S.energy += energy /obj/effect/accelerated_particle/ex_act(severity) @@ -54,17 +53,7 @@ /obj/effect/accelerated_particle/singularity_pull() return - - -/obj/effect/accelerated_particle/proc/toxmob(mob/living/M) - M.apply_effect((energy * 6), IRRADIATE, 0) - -/obj/effect/accelerated_particle/proc/move() +/obj/effect/accelerated_particle/proc/propagate() + addtimer(CALLBACK(src, .proc/propagate), 1) if(!step(src,dir)) forceMove(get_step(src, dir)) - movement_range-- - if(movement_range == 0) - qdel(src) - else - sleep(speed) - move() \ No newline at end of file From a54a58a2862d8ed0cc72798a3c3bfac4ec4e316c Mon Sep 17 00:00:00 2001 From: TheSardele Date: Sun, 27 Oct 2019 18:51:59 +0100 Subject: [PATCH 011/238] Fixes sunglasses and sunglass HUD sprites for Vox, Drask and Greys --- code/modules/clothing/glasses/glasses.dm | 5 +++++ icons/mob/species/drask/eyes.dmi | Bin 9266 -> 9408 bytes icons/mob/species/grey/eyes.dmi | Bin 12190 -> 12420 bytes icons/mob/species/vox/eyes.dmi | Bin 11637 -> 11566 bytes 4 files changed, 5 insertions(+) diff --git a/code/modules/clothing/glasses/glasses.dm b/code/modules/clothing/glasses/glasses.dm index ce29fa8f7b1..e80f461246e 100644 --- a/code/modules/clothing/glasses/glasses.dm +++ b/code/modules/clothing/glasses/glasses.dm @@ -274,6 +274,11 @@ see_in_dark = 0 flash_protect = 0 tint = 0 + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) /obj/item/clothing/glasses/sunglasses/noir name = "noir sunglasses" diff --git a/icons/mob/species/drask/eyes.dmi b/icons/mob/species/drask/eyes.dmi index 818c719a541af1a409772eea7c98d7571a8fd309..92b8d7c219066de0f86a5e594e529f043771aeed 100644 GIT binary patch literal 9408 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 literal 9266 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 literal 12190 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 OSG5h`1($CB@&5o_C-^S_ diff --git a/icons/mob/species/vox/eyes.dmi b/icons/mob/species/vox/eyes.dmi index 3e3705d2a90dcea9eff369a0ac7aaa07f9b07ab9..dfa565dd2a712bacf6bb74dfa8e8a8389799c53e 100644 GIT binary patch literal 11566 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�q`e>3dvuWo#5|4c-0X^J59f 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 GkNyW*hXQ>7 literal 11637 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 L1+@0k?ce_omQ5NP From 9061742db56fe277816e2588aa05640a4d69219c Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 27 Oct 2019 23:16:42 -0700 Subject: [PATCH 012/238] Dead mice squeak no tails --- code/datums/components/squeak.dm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm index e5b948f4f26..aca505e6a03 100644 --- a/code/datums/components/squeak.dm +++ b/code/datums/components/squeak.dm @@ -41,6 +41,10 @@ use_delay = use_delay_override /datum/component/squeak/proc/play_squeak() + if(ismob(parent)) + var/mob/M = parent + if(M.stat == DEAD) + return if(prob(squeak_chance)) if(!override_squeak_sounds) playsound(parent, pickweight(default_squeak_sounds), volume, 1, -1) From 3d638b8aec2834e8d2b93905208ec0f9e104c6a2 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 27 Oct 2019 23:40:29 -0700 Subject: [PATCH 013/238] Fixes held/carried mice eating cables --- code/modules/mob/living/simple_animal/friendly/mouse.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index 3ca0c840efd..d6f38d33268 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -41,7 +41,7 @@ AddComponent(/datum/component/squeak, list('sound/creatures/mousesqueak.ogg' = 1), 100) /mob/living/simple_animal/mouse/handle_automated_action() - if(prob(chew_probability)) + if(prob(chew_probability) && isturf(loc)) var/turf/simulated/floor/F = get_turf(src) if(istype(F) && !F.intact) var/obj/structure/cable/C = locate() in F From a24013be5b9cea17bcad4cb904cfdfdafe3d934d Mon Sep 17 00:00:00 2001 From: moxian Date: Mon, 28 Oct 2019 10:19:52 +0000 Subject: [PATCH 014/238] Fix stacks being consumed by redspace. Fixes #12606 --- code/game/objects/items/stacks/stack.dm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index a4141a7a58a..793eb1062d5 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -296,6 +296,8 @@ else return ..() +// Returns TRUE if the stack amount is zero. +// Also qdels the stack gracefully if it is. /obj/item/stack/proc/zero_amount() if(amount < 1) if(isrobot(loc)) @@ -305,8 +307,9 @@ if(ismob(loc)) var/mob/living/L = loc // At this stage, stack code is so horrible and atrocious, I wouldn't be all surprised ghosts can somehow have stacks. If this happens, then the world deserves to burn. L.unEquip(src, TRUE) - qdel(src) - return TRUE + if(amount < 1) // check again. Dropping the stuff could have incresed the amount in src (somehow...) + qdel(src) + return TRUE return FALSE /obj/item/stack/proc/merge(obj/item/stack/S) //Merge src into S, as much as possible From 4fab6f14e669f358c5a7ce91680f99c9f4446d1b Mon Sep 17 00:00:00 2001 From: moxian Date: Mon, 28 Oct 2019 10:34:47 +0000 Subject: [PATCH 015/238] Add comment on why (I think) the stack fix happens to work. --- code/game/objects/items/stacks/stack.dm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 793eb1062d5..0d864492266 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -307,7 +307,10 @@ if(ismob(loc)) var/mob/living/L = loc // At this stage, stack code is so horrible and atrocious, I wouldn't be all surprised ghosts can somehow have stacks. If this happens, then the world deserves to burn. L.unEquip(src, TRUE) - if(amount < 1) // check again. Dropping the stuff could have incresed the amount in src (somehow...) + if(amount < 1) + // If you stand on top of a stack, and drop a - different - 0-amount stack on the floor, + // the two get merged, so the amount of items in the stack can increase from the 0 that it had before. + // Check the amount again, to be sure we're not qdeling healthy stacks. qdel(src) return TRUE return FALSE From 8576aa80ff2df94e0aa9d08bc10e5a18dce13c4b Mon Sep 17 00:00:00 2001 From: Citinited Date: Wed, 30 Oct 2019 21:58:18 +0000 Subject: [PATCH 016/238] Snow crate is available for purchase on both consoles, not just the supply console --- code/modules/holiday/christmas.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/holiday/christmas.dm b/code/modules/holiday/christmas.dm index 37a482231c2..ffbfa412e53 100644 --- a/code/modules/holiday/christmas.dm +++ b/code/modules/holiday/christmas.dm @@ -11,8 +11,8 @@ break //The following spawn is necessary as both the timer and the shuttle systems initialise after the events system does, so we can't add stuff to the shuttle system as it doesn't exist yet and we can't use a timer spawn(60 SECONDS) - var/datum/supply_packs/xmas = SSshuttle.supply_packs["[/datum/supply_packs/misc/snow_machine]"] - xmas.special_enabled = TRUE + var/datum/supply_packs/misc/snow_machine/xmas = SSshuttle.supply_packs["[/datum/supply_packs/misc/snow_machine]"] + xmas.special = FALSE /datum/holiday/xmas/handle_event() spawnTree() From 6520b32e56d6a5707a422b3be16a118154a3a107 Mon Sep 17 00:00:00 2001 From: moxian Date: Thu, 31 Oct 2019 03:42:04 +0000 Subject: [PATCH 017/238] Fix a potential minor logic error. If the stack has no items left, we drop it, it gets merged with another stack on the ground, then we should still report the stack as having zero items. Otherwise there's a potential to accidentally use items on the ground once you run out of items in hand, which is slightly surprising. --- code/game/objects/items/stacks/stack.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 0d864492266..1db390a2c91 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -312,7 +312,7 @@ // the two get merged, so the amount of items in the stack can increase from the 0 that it had before. // Check the amount again, to be sure we're not qdeling healthy stacks. qdel(src) - return TRUE + return TRUE return FALSE /obj/item/stack/proc/merge(obj/item/stack/S) //Merge src into S, as much as possible From 88d83b3dedf22295ade58fbcb5fa69bf545a8365 Mon Sep 17 00:00:00 2001 From: Kyep Date: Wed, 30 Oct 2019 21:27:21 -0700 Subject: [PATCH 018/238] Fixes 12677 - depot self-destruct leaves loot behind --- code/game/objects/structures/depot.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/game/objects/structures/depot.dm b/code/game/objects/structures/depot.dm index 5ea249b5ed2..9f3999ded7d 100644 --- a/code/game/objects/structures/depot.dm +++ b/code/game/objects/structures/depot.dm @@ -113,6 +113,8 @@ depotarea.updateicon() for(var/obj/structure/closet/L in range(30, T)) + for(var/obj/O in L) + qdel(O) L.open() for(var/mob/living/M in range(30, T)) M.gib() From 00e0791ef3e58242dfcd8491ca97dbd859b49738 Mon Sep 17 00:00:00 2001 From: Kyep Date: Wed, 30 Oct 2019 21:52:52 -0700 Subject: [PATCH 019/238] Fixes depot mobs reacting sluggishly --- .../modules/mob/living/simple_animal/hostile/syndicate.dm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm index 02d5ee5d14b..513ab39b180 100644 --- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm +++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm @@ -148,8 +148,10 @@ // This prevents someone from aggroing a depot mob, then hiding in a locker, perfectly safe, while the mob stands there getting killed by their friends. LoseTarget() -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/handle_automated_movement() +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/handle_automated_action() . = ..() + if(!.) + return if(!istype(depotarea)) return if(seen_enemy) @@ -184,6 +186,10 @@ else scan_cycles++ +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/AIShouldSleep(var/list/possible_targets) + FindTarget(possible_targets, 1) + return FALSE + /mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/proc/raise_alert(var/reason) if(istype(depotarea) && (!raised_alert || seen_revived_enemy) && !depotarea.used_self_destruct) raised_alert = TRUE From 43183d7f159851a54cffb3ef51204fabdf21991d Mon Sep 17 00:00:00 2001 From: moxian Date: Thu, 31 Oct 2019 07:32:45 +0000 Subject: [PATCH 020/238] Add missing linebreaks to abductor endscreen report. --- code/game/gamemodes/miniantags/abduction/abduction.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/game/gamemodes/miniantags/abduction/abduction.dm b/code/game/gamemodes/miniantags/abduction/abduction.dm index 908eaa5360f..3d0a74bad71 100644 --- a/code/game/gamemodes/miniantags/abduction/abduction.dm +++ b/code/game/gamemodes/miniantags/abduction/abduction.dm @@ -208,12 +208,14 @@ text += "
The abductors were:
" for(var/datum/mind/abductor_mind in abductors) text += printplayer(abductor_mind) + text += "
" text += printobjectives(abductor_mind) text += "
" if(abductees.len) text += "
The abductees were:
" for(var/datum/mind/abductee_mind in abductees) text += printplayer(abductee_mind) + text += "
" text += printobjectives(abductee_mind) text += "
" to_chat(world, text) From f2f7ec956a50c94ac1e3f14f447060e2b8e099e8 Mon Sep 17 00:00:00 2001 From: Mars Date: Thu, 31 Oct 2019 15:43:35 +0100 Subject: [PATCH 021/238] Orderable conscription kit only without ID --- code/game/objects/items/weapons/storage/backpack.dm | 4 ++++ code/modules/mining/machine_vending.dm | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm index 57f4e999ac8..a394d517235 100644 --- a/code/game/objects/items/weapons/storage/backpack.dm +++ b/code/game/objects/items/weapons/storage/backpack.dm @@ -373,6 +373,10 @@ new /obj/item/clothing/under/rank/miner/lavaland(src) new /obj/item/encryptionkey/headset_cargo(src) new /obj/item/clothing/mask/gas/explorer(src) + new /obj/item/gun/energy/kinetic_accelerator(src) + new /obj/item/kitchen/knife/combat/survival(src) + new /obj/item/flashlight/seclite(src) + new /obj/item/clothing/suit/hooded/explorer(src) /obj/item/storage/backpack/duffel/syndie/ammo/smg diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm index c8fbba6fe51..20d1578524e 100644 --- a/code/modules/mining/machine_vending.dm +++ b/code/modules/mining/machine_vending.dm @@ -299,8 +299,4 @@ /obj/item/storage/backpack/duffel/mining_conscript/full/New() ..() - new /obj/item/clothing/suit/hooded/explorer(src) new /obj/item/card/id/mining_access_card(src) - new /obj/item/gun/energy/kinetic_accelerator(src) - new /obj/item/kitchen/knife/combat/survival(src) - new /obj/item/flashlight/seclite(src) From a0081713f33fc2d20018860abb00c42fd42973ae Mon Sep 17 00:00:00 2001 From: Kyep Date: Sat, 2 Nov 2019 18:51:59 -0700 Subject: [PATCH 022/238] Adds web playerinfo link --- code/controllers/configuration.dm | 8 ++++++-- code/modules/admin/admin.dm | 2 ++ code/modules/admin/topic.dm | 7 +++++++ config/example/config.txt | 3 +++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 8ae35d52179..d24e4dc94f5 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -79,7 +79,7 @@ var/auto_cryo_ssd_mins = 0 var/ssd_warning = 0 - + var/list_afk_minimum = 5 // How long people have to be AFK before it's listed on the "List AFK players" verb var/traitor_objectives_amount = 2 @@ -144,6 +144,7 @@ var/ipintel_detailsurl = "https://iphub.info/?ip=" var/forum_link_url + var/forum_playerinfo_url var/admin_legacy_system = 0 //Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt var/ban_legacy_system = 0 //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt @@ -244,7 +245,7 @@ //cube monkey limit var/cubemonkeycap = 20 - + // Makes gamemodes respect player limits var/enable_gamemode_player_limit = 0 @@ -354,6 +355,9 @@ if("forum_link_url") config.forum_link_url = value + if("forum_playerinfo_url") + config.forum_playerinfo_url = value + if("log_ooc") config.log_ooc = 1 diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 6967a374b51..1801324e72d 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -100,6 +100,8 @@ var/global/nologevent = 0 body += "
Jobban | " body += "Appearance Ban | " body += "Notes | " + if(config.forum_playerinfo_url) + body += "WebInfo | " if(M.client) if(M.client.check_watchlist(M.client.ckey)) body += "Remove from Watchlist | " diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index a318c316532..8db44a1ce53 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -936,6 +936,13 @@ target = text2num(target) show_note(index = target) + else if(href_list["webtools"]) + var/target_ckey = href_list["webtools"] + if(config.forum_playerinfo_url) + var/url_to_open = config.forum_playerinfo_url + target_ckey + if(alert("Open [url_to_open]",,"Yes","No")=="Yes") + usr.client << link(url_to_open) + else if(href_list["shownoteckey"]) var/target_ckey = href_list["shownoteckey"] show_note(target_ckey) diff --git a/config/example/config.txt b/config/example/config.txt index 7dfdc1e413a..27da54922bb 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -213,6 +213,9 @@ GUEST_BAN ## URL to use to link forum accounts. If not set, no link option will be offered. #FORUM_LINK_URL https://example.com/link.php?token= +## URL to use for admins accessing the web-based tools menu +#FORUM_PLAYERINFO_URL https://example.com/info.php?ckey= + ## Comment to disable checking for the cid randomizer dll. (disabled if database isn't enabled or connected) CHECK_RANDOMIZER From de7b8c52e7ee88755a0a7f17d9f865a3d41a686f Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 3 Nov 2019 01:00:01 -0800 Subject: [PATCH 023/238] Add link for newly banned people verifying their forum accounts --- code/modules/admin/topic.dm | 4 ++++ code/modules/client/client procs.dm | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 8db44a1ce53..4abdc89677c 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1002,6 +1002,8 @@ feedback_inc("ban_tmp",1) DB_ban_record(BANTYPE_TEMP, M, mins, reason) feedback_inc("ban_tmp_mins",mins) + if(M.client) + M.client.link_forum_account(TRUE) if(config.banappeals) to_chat(M, "To try to resolve this matter head to [config.banappeals]") else @@ -1018,6 +1020,8 @@ AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP) to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") to_chat(M, "This ban does not expire automatically and must be appealed.") + if(M.client) + M.client.link_forum_account(TRUE) if(config.banappeals) to_chat(M, "To try to resolve this matter head to [config.banappeals]") else diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 410109151ba..644ac6aa123 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -622,12 +622,15 @@ return return tokenstr -/client/proc/link_forum_account() +/client/proc/link_forum_account(fromban) + if(!config.forum_link_url) + return if(IsGuestKey(key)) to_chat(src, "Guest keys cannot be linked.") return if(prefs && prefs.fuid) - to_chat(src, "Your forum account is already set.") + if(!fromban) + to_chat(src, "Your forum account is already set.") return var/DBQuery/query_find_link = dbcon.NewQuery("SELECT fuid FROM [format_table_name("player")] WHERE ckey = '[ckey]' limit 1") if(!query_find_link.Execute()) @@ -635,15 +638,19 @@ return if(query_find_link.NextRow()) if(query_find_link.item[1]) - to_chat(src, "Your forum account is already set. (" + query_find_link.item[1] + ")") + if(!fromban) + to_chat(src, "Your forum account is already set. (" + query_find_link.item[1] + ")") return var/tokenid = create_oauth_token() if(!tokenid) to_chat(src, "link_forum_account: unable to create token") return var/url = "[config.forum_link_url][tokenid]" - to_chat(src, {"Now opening a windows to verify your information with the forums. If the window does not load, please go to: [url]."}) - src << link(url) + if(fromban) + to_chat(src, {"Verify your forum account before you appeal: [url] (this link expires in a few minutes)"}) + else + to_chat(src, {"Now opening a window to verify your information with the forums. If the window does not load, please go to: [url]"}) + src << link(url) return #undef TOPIC_SPAM_DELAY From 1119658377989a4a907c5ae61593a40d94b45d9b Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Sun, 3 Nov 2019 09:11:04 -0500 Subject: [PATCH 024/238] Navy Uniform Fix --- _maps/map_files/cyberiad/z2.dmm | 4 -- .../crates_lockers/closets/secure/security.dm | 3 -- code/modules/clothing/under/miscellaneous.dm | 39 ------------------- 3 files changed, 46 deletions(-) diff --git a/_maps/map_files/cyberiad/z2.dmm b/_maps/map_files/cyberiad/z2.dmm index 684365ada39..757f86db381 100644 --- a/_maps/map_files/cyberiad/z2.dmm +++ b/_maps/map_files/cyberiad/z2.dmm @@ -8862,10 +8862,6 @@ pixel_y = 3 }, /obj/item/clothing/under/rank/centcom/officer, -/obj/item/clothing/under/rank/centcom/representative{ - pixel_x = 3; - pixel_y = -3 - }, /turf/unsimulated/floor{ tag = "icon-floor"; icon_state = "floor" diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index f66987d544b..ace06e8eeed 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -260,7 +260,6 @@ new /obj/item/clothing/accessory/holster(src) new /obj/item/clothing/accessory/blue(src) new /obj/item/clothing/shoes/jackboots/jacksandals(src) - new /obj/item/clothing/under/rank/centcom/blueshield(src) /obj/structure/closet/secure_closet/ntrep @@ -285,7 +284,6 @@ new /obj/item/clothing/under/lawyer/oldman(src) new /obj/item/clothing/under/lawyer/black(src) new /obj/item/clothing/under/lawyer/female(src) - new /obj/item/clothing/under/rank/centcom/representative(src) new /obj/item/clothing/head/ntrep(src) new /obj/item/clothing/shoes/sandal/fancy(src) new /obj/item/storage/box/tapes(src) @@ -459,7 +457,6 @@ new /obj/item/clothing/gloves/color/white(src) new /obj/item/clothing/shoes/centcom(src) new /obj/item/clothing/under/suit_jacket/really_black(src) - new /obj/item/clothing/under/rank/centcom/magistrate(src) new /obj/item/clothing/suit/judgerobe(src) new /obj/item/clothing/head/powdered_wig(src) new /obj/item/gavelblock(src) diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 9e009014deb..a164215ed6d 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -112,45 +112,6 @@ item_color = "centcom" displays_id = 0 -/obj/item/clothing/under/rank/centcom/blueshield - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant\" and bears \"N.S.S. Cyberiad\" on the left shoulder." - name = "\improper Nanotrasen Navy Uniform" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" - displays_id = 0 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/centcom/blueshield/New() - ..() - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant\" and bears [station_name()] on the left shoulder." - -/obj/item/clothing/under/rank/centcom/representative - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Ensign\" and bears \"N.S.S. Cyberiad\" on the left shoulder." - name = "\improper Nanotrasen Navy Uniform" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" - displays_id = 0 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/centcom/representative/New() - ..() - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Ensign\" and bears [station_name()] on the left shoulder." - -/obj/item/clothing/under/rank/centcom/magistrate - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears \"N.S.S. Cyberiad\" on the left shoulder." - name = "\improper Nanotrasen Navy Uniform" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" - displays_id = 0 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/centcom/magistrate/New() - ..() - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears [station_name()] on the left shoulder." - /obj/item/clothing/under/rank/centcom/diplomatic desc = "A very gaudy and official looking uniform of the Nanotrasen Diplomatic Corps." name = "\improper Nanotrasen Diplomatic Uniform" From 80bd63d313aa2fd336b59aa6766c217dec569266 Mon Sep 17 00:00:00 2001 From: TheSardele Date: Mon, 4 Nov 2019 20:05:37 +0100 Subject: [PATCH 025/238] Logs slime attacks correctly Fixes slime attacks being logged in reverse. I.e., a slime attacking a human would be logged as the human sliming the slime. --- code/modules/mob/living/carbon/alien/alien_defense.dm | 2 +- code/modules/mob/living/living_defense.dm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index f4832e20384..04924d4b310 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -79,5 +79,5 @@ In all, this is a lot like the monkey code. /N if(M.is_adult) damage = rand(10, 40) adjustBruteLoss(damage) - add_attack_logs(src, M, "Slime'd for [damage] damage") + add_attack_logs(M, src, "Slime'd for [damage] damage") updatehealth("slime attack") \ No newline at end of file diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 67ea6e7f887..2b148a6a0a2 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -296,7 +296,7 @@ return FALSE if(stat != DEAD) - add_attack_logs(src, M, "Slime'd") + add_attack_logs(M, src, "Slime'd") M.do_attack_animation(src) visible_message("\The [M.name] glomps [src]!", "\The [M.name] glomps you!") return TRUE From c37ed36b590e6f0669164b020d77749b6d3a4087 Mon Sep 17 00:00:00 2001 From: moxian Date: Tue, 5 Nov 2019 07:27:04 +0000 Subject: [PATCH 026/238] Make photos look the same as reality to colorblind people. --- code/modules/paperwork/photography.dm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 11beb1a7cb6..0daf692a60b 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -81,7 +81,16 @@ . += "It is too far away." /obj/item/photo/proc/show(mob/user as mob) - usr << browse_rsc(img, "tmp_photo.png") + var/icon/img_shown = new/icon(img) + var/colormatrix = user.get_screen_colour() + // Apply colorblindness effects, if any. + if(islist(colormatrix)) + img_shown.MapColors( + colormatrix[1], colormatrix[2], colormatrix[3], + colormatrix[4], colormatrix[5], colormatrix[6], + colormatrix[7], colormatrix[8], colormatrix[9], + ) + usr << browse_rsc(img_shown, "tmp_photo.png") usr << browse("[name]" \ + "" \ + "" \ From 3118b776067629d87993a3dd67c3f4ffa9cc4d42 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Tue, 5 Nov 2019 19:31:28 -0500 Subject: [PATCH 027/238] Revert "Navy Uniform Fix" This reverts commit 1119658377989a4a907c5ae61593a40d94b45d9b. --- _maps/map_files/cyberiad/z2.dmm | 4 ++ .../crates_lockers/closets/secure/security.dm | 3 ++ code/modules/clothing/under/miscellaneous.dm | 39 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/_maps/map_files/cyberiad/z2.dmm b/_maps/map_files/cyberiad/z2.dmm index 757f86db381..684365ada39 100644 --- a/_maps/map_files/cyberiad/z2.dmm +++ b/_maps/map_files/cyberiad/z2.dmm @@ -8862,6 +8862,10 @@ pixel_y = 3 }, /obj/item/clothing/under/rank/centcom/officer, +/obj/item/clothing/under/rank/centcom/representative{ + pixel_x = 3; + pixel_y = -3 + }, /turf/unsimulated/floor{ tag = "icon-floor"; icon_state = "floor" diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index ace06e8eeed..f66987d544b 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -260,6 +260,7 @@ new /obj/item/clothing/accessory/holster(src) new /obj/item/clothing/accessory/blue(src) new /obj/item/clothing/shoes/jackboots/jacksandals(src) + new /obj/item/clothing/under/rank/centcom/blueshield(src) /obj/structure/closet/secure_closet/ntrep @@ -284,6 +285,7 @@ new /obj/item/clothing/under/lawyer/oldman(src) new /obj/item/clothing/under/lawyer/black(src) new /obj/item/clothing/under/lawyer/female(src) + new /obj/item/clothing/under/rank/centcom/representative(src) new /obj/item/clothing/head/ntrep(src) new /obj/item/clothing/shoes/sandal/fancy(src) new /obj/item/storage/box/tapes(src) @@ -457,6 +459,7 @@ new /obj/item/clothing/gloves/color/white(src) new /obj/item/clothing/shoes/centcom(src) new /obj/item/clothing/under/suit_jacket/really_black(src) + new /obj/item/clothing/under/rank/centcom/magistrate(src) new /obj/item/clothing/suit/judgerobe(src) new /obj/item/clothing/head/powdered_wig(src) new /obj/item/gavelblock(src) diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index a164215ed6d..9e009014deb 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -112,6 +112,45 @@ item_color = "centcom" displays_id = 0 +/obj/item/clothing/under/rank/centcom/blueshield + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant\" and bears \"N.S.S. Cyberiad\" on the left shoulder." + name = "\improper Nanotrasen Navy Uniform" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" + displays_id = 0 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/centcom/blueshield/New() + ..() + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant\" and bears [station_name()] on the left shoulder." + +/obj/item/clothing/under/rank/centcom/representative + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Ensign\" and bears \"N.S.S. Cyberiad\" on the left shoulder." + name = "\improper Nanotrasen Navy Uniform" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" + displays_id = 0 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/centcom/representative/New() + ..() + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Ensign\" and bears [station_name()] on the left shoulder." + +/obj/item/clothing/under/rank/centcom/magistrate + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears \"N.S.S. Cyberiad\" on the left shoulder." + name = "\improper Nanotrasen Navy Uniform" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" + displays_id = 0 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/centcom/magistrate/New() + ..() + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears [station_name()] on the left shoulder." + /obj/item/clothing/under/rank/centcom/diplomatic desc = "A very gaudy and official looking uniform of the Nanotrasen Diplomatic Corps." name = "\improper Nanotrasen Diplomatic Uniform" From 332dd778967f77f97e5bf9c13791379e2b220105 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Tue, 5 Nov 2019 19:41:54 -0500 Subject: [PATCH 028/238] updates description --- code/modules/clothing/under/miscellaneous.dm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 9e009014deb..01264fc07f8 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -113,8 +113,8 @@ displays_id = 0 /obj/item/clothing/under/rank/centcom/blueshield - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant\" and bears \"N.S.S. Cyberiad\" on the left shoulder." - name = "\improper Nanotrasen Navy Uniform" + desc = "Gold trim on space-black cloth, this uniform bears \"Close Protection\" on the left shoulder." + name = "\improper Formal Nanotrasen Uniform" icon_state = "officer" item_state = "g_suit" item_color = "officer" @@ -123,11 +123,11 @@ /obj/item/clothing/under/rank/centcom/blueshield/New() ..() - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant\" and bears [station_name()] on the left shoulder." + desc = "Gold trim on space-black cloth, this uniform bears \"Close Protection\" on the left shoulder." /obj/item/clothing/under/rank/centcom/representative - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Ensign\" and bears \"N.S.S. Cyberiad\" on the left shoulder." - name = "\improper Nanotrasen Navy Uniform" + desc = "Gold trim on space-black cloth, this uniform bears \"N.S.S. Cyberiad\" on the left shoulder." + name = "\improper Formal Nanotrasen Uniform" icon_state = "officer" item_state = "g_suit" item_color = "officer" @@ -136,11 +136,11 @@ /obj/item/clothing/under/rank/centcom/representative/New() ..() - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Ensign\" and bears [station_name()] on the left shoulder." + desc = "Gold trim on space-black cloth, this uniform bears [station_name()] on the left shoulder." /obj/item/clothing/under/rank/centcom/magistrate desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears \"N.S.S. Cyberiad\" on the left shoulder." - name = "\improper Nanotrasen Navy Uniform" + name = "\improper Formal Nanotrasen Uniform" icon_state = "officer" item_state = "g_suit" item_color = "officer" @@ -161,7 +161,7 @@ /obj/item/clothing/under/rank/blueshield name = "blueshield uniform" - desc = "A short-sleeved black uniform, paired with grey digital-camo cargo pants. Standard issue to Blueshield officers." + desc = "A short-sleeved black uniform, paired with grey digital-camo cargo pants. Standard issue to Blueshields." icon_state = "ert_uniform" item_state = "bl_suit" item_color = "ert_uniform" @@ -169,7 +169,7 @@ /obj/item/clothing/under/rank/blueshield/skirt name = "blueshield skirt" - desc = "A short, black and grey with blue markings skirted uniform. For the feminine Blueshield officers." + desc = "A short, black and grey with blue markings skirted uniform. For the feminine Blueshield." icon_state = "blueshieldf" item_state = "blueshieldf" item_color = "blueshieldf" From a9c19ebb6bda240f296f103d19fabc29cc7b7582 Mon Sep 17 00:00:00 2001 From: moxian Date: Wed, 6 Nov 2019 04:40:16 +0000 Subject: [PATCH 029/238] Make paperwork popups use UID as a window id. --- code/game/objects/structures/noticeboard.dm | 7 +------ code/modules/admin/topic.dm | 2 +- code/modules/paperwork/paper.dm | 2 +- code/modules/paperwork/paper_bundle.dm | 8 ++++---- code/modules/paperwork/photography.dm | 4 ++-- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm index aa6528ee118..5938a52c1d5 100644 --- a/code/game/objects/structures/noticeboard.dm +++ b/code/game/objects/structures/noticeboard.dm @@ -78,10 +78,5 @@ if(href_list["read"]) var/obj/item/paper/P = locate(href_list["read"]) if((P && P.loc == src)) - if(!( istype(usr, /mob/living/carbon/human) )) - usr << browse("[P.name][stars(P.info)]", "window=[P.name]") - onclose(usr, "[P.name]") - else - usr << browse("[P.name][P.info]", "window=[P.name]") - onclose(usr, "[P.name]") + P.show_content(usr) return diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index a318c316532..1114e4eb298 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -2153,7 +2153,7 @@ var/obj/pageobj = B.contents[page] data += "Page [page] - [pageobj.name]
" - usr << browse(data, "window=[B.name]") + usr << browse(data, "window=PaperBundle[B.UID()]") else to_chat(usr, "The faxed item is not viewable. This is probably a bug, and should be reported on the tracker: [fax.type]") diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index a88565d3818..f8f46909f9f 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -80,7 +80,7 @@ else data = "
[infolinks ? info_links : info]
[stamps]" if(view) - var/datum/browser/popup = new(user, name, , paper_width, paper_height) + var/datum/browser/popup = new(user, "Paper[UID()]", , paper_width, paper_height) popup.stylesheets = list() popup.set_content(data) if(!stars) diff --git a/code/modules/paperwork/paper_bundle.dm b/code/modules/paperwork/paper_bundle.dm index ecc26ba0cff..06bd0ec25c6 100644 --- a/code/modules/paperwork/paper_bundle.dm +++ b/code/modules/paperwork/paper_bundle.dm @@ -63,13 +63,13 @@ qdel(W) else if(istype(W, /obj/item/pen) || istype(W, /obj/item/toy/crayon)) - usr << browse("", "window=[name]") //Closes the dialog + usr << browse("", "window=PaperBundle[UID()]") //Closes the dialog P = src[page] P.attackby(W, user, params) update_icon() - if(winget(usr, "[name]", "is-visible") == "true") // NOT MY FAULT IT IS A BUILT IN PROC PLEASE DO NOT HIT ME + if(winget(usr, "PaperBundle[UID()]", "is-visible") == "true") // NOT MY FAULT IT IS A BUILT IN PROC PLEASE DO NOT HIT ME attack_self(usr) //Update the browsed page. add_fingerprint(usr) return @@ -125,7 +125,7 @@ if(istype(src[page], /obj/item/paper)) var/obj/item/paper/P = W dat += P.show_content(usr, view = 0) - usr << browse(dat, "window=[name]") + usr << browse(dat, "window=PaperBundle[UID()]") else if(istype(src[page], /obj/item/photo)) var/obj/item/photo/P = W usr << browse_rsc(P.img, "tmp_photo.png") @@ -133,7 +133,7 @@ + "" \ + "

Written on the back:
[P.scribble]" : ""]"\ - + "", "window=[name]") + + "", "window=PaperBundle[UID()]") /obj/item/paper_bundle/attack_self(mob/user as mob) src.show_content(user) diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 11beb1a7cb6..cffc98c5945 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -86,8 +86,8 @@ + "" \ + "" \ + "[scribble ? "
Written on the back:
[scribble]" : ""]"\ - + "", "window=book;size=[64*photo_size]x[scribble ? 400 : 64*photo_size]") - onclose(usr, "[name]") + + "", "window=Photo[UID()];size=[64*photo_size]x[scribble ? 400 : 64*photo_size]") + onclose(usr, "Photo[UID()]") return /obj/item/photo/verb/rename() From e6997dfdbe8812f8d6e04d4eaa2877f224929130 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Wed, 6 Nov 2019 07:42:33 -0500 Subject: [PATCH 030/238] Updates Naval Officer Outfit --- code/game/jobs/job/central.dm | 2 +- code/modules/clothing/under/miscellaneous.dm | 23 ++++++++++--------- icons/mob/inhands/clothing_lefthand.dmi | Bin 92390 -> 91512 bytes icons/mob/inhands/clothing_righthand.dmi | Bin 89497 -> 88812 bytes icons/mob/species/drask/uniform.dmi | Bin 346838 -> 345141 bytes icons/mob/species/grey/uniform.dmi | Bin 297266 -> 298477 bytes icons/mob/species/vox/uniform.dmi | Bin 316925 -> 318344 bytes icons/mob/uniform.dmi | Bin 377385 -> 378630 bytes icons/obj/clothing/uniforms.dmi | Bin 92825 -> 93213 bytes 9 files changed, 13 insertions(+), 12 deletions(-) diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm index 1d6ae0162a2..695e4a1ec39 100644 --- a/code/game/jobs/job/central.dm +++ b/code/game/jobs/job/central.dm @@ -66,7 +66,7 @@ /datum/outfit/job/ntspecops name = "Special Operations Officer" jobtype = /datum/job/ntspecops - uniform = /obj/item/clothing/under/rank/centcom_commander + uniform = /obj/item/clothing/under/rank/centcom/officer/captain suit = /obj/item/clothing/suit/space/deathsquad/officer back = /obj/item/storage/backpack/security belt = /obj/item/storage/belt/military/assault diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 9e009014deb..b82a04ad263 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -96,20 +96,21 @@ item_color = "centcom" /obj/item/clothing/under/rank/centcom/officer - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.C.V. Fearless CV-286\" on the left shoulder." - name = "\improper Nanotrasen Officers Uniform" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." + name = "\improper Nanotrasen Naval Officers Uniform" + icon_state = "navy_gold" + item_state = "navy_gold" + item_color = "navy_gold" displays_id = 0 flags_size = ONESIZEFITSALL + armor = list("melee" = 60, "bullet" = 60, "laser" = 60,"energy" = 60, "bomb" = 60, "bio" = 100, "rad" = 30, "fire" = 60, "acid" = 100) -/obj/item/clothing/under/rank/centcom/captain - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.C.V. Fearless CV-286\" on the left shoulder." - name = "\improper Nanotrasen Captains Uniform" - icon_state = "centcom" - item_state = "dg_suit" - item_color = "centcom" +/obj/item/clothing/under/rank/centcom/officer/captain + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." + name = "\improper Nanotrasen Naval Captains Uniform" + icon_state = "navy_gold" + item_state = "navy_gold" + item_color = "navy_gold" displays_id = 0 /obj/item/clothing/under/rank/centcom/blueshield diff --git a/icons/mob/inhands/clothing_lefthand.dmi b/icons/mob/inhands/clothing_lefthand.dmi index 745dd19aac7104409f45a1586aff46c65fd802c1..69e602fc6529749c36d0be46e5eddb55940022f1 100644 GIT binary patch delta 70351 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 AGynhq delta 71961 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%0DwxuQ}C$@1$sK)wXZb2)RWpBwJsfWw0uKOEYNP zJCpokFmtJhoC@bccT(odVF*_*YW7;|n0+hQjBVLc zbGh?R`zbiAt^gr6%8U^}w0Rsz4nBo}C%u(SwC(G8W8BD9J-<0~ zee+|~`T)Y9PQk1U^A@)LKnTSF;OybrEOKei6XqKcHKh65^;Z#>w7g*%pKh#jnnT3H4KWE+&q)tvx9}L!@SY>=n zYO$CZ+7-O(fBU%R<~C@``-ABfV}0e0ZXOQhq4Y?i=H}5|^J5{u$FN+@w#@@6qj$nq ze&17V71Q~x9P2hEk}vXUGPyBk1ed=b>9Gg`SFkv+w6wZD=g<5esNv4{Ws`4sv2x~< zF2m{`;`AR}WU{snPx@yE5BDbhLTGFno6F!lDXBM)Y=; z2gu^%KZ0xYi^Cm-nOWa=V}m|FV<+JovIm+R#KAKZwL;=*C2-o6+^)?7Y#o`>~b<^4cUZfR*T0p&m6 z7Z$cQ1`ttDQqKNX$N)j6(bk<7@Sm^0J}F-4quMPIs-_P7uMMs;nyu3XZOy2LRhzMb z%`r=TFvtR1P%J-qW|+S6T8<4?r%(Qi)d?EX4xN2wxRg`WB`W%6z4Q|m02 z_m6gE?}PfpJK_Fme2)#u_%aaQ4r@Qw{L{hADdP7ZB&6F`%5ql-VQYrAJ9y!oS$gV! zc+pv}+QeB;{R5ifWguw{`G|L|Lt}Ewhf)&<{$_ z0>Vo;_mz8?UQg)nX}FXQ!-B3Z##d<&kx){Cu(r%K``kr~T6@DkBw*)w?{!EYErqp{ zaqT*Z*?OW@d#r9KLik~JLwTUDyZGD=k&zm7_g_W6>l@-Euz59A{Pc_bw4wgqmppkn!`nM^WumC-+Uj$*SYrEqnjyKHqd1O)%WnstdXKN^ zN=3+T{&w5`vs|yaOZY%rn*d7lTE3L$1{>nsbj=;-+`ipg@?j{HXT%jbV!H6^nKAT}vTuubj9qJj&;OyN7LO^gwLyY)DhXbzoCizdql-bEZvP8gg!_SH1mqa0Kv*M?`zN68oE>SSXk*Cns>9(iWqsR?F1*0dy%?LR{SJ z`r_&hFcE4S-aG|Wyg_<}aqUHN5PhI`T=LLw%MvqxByG2V9_4*wUw+s;i1flt@8V)x zXZp~c`|e}kQI)Xf)k#%4VN>D+K|?;yrKS&gPQo=VOIneK9Fr3zN%5SgfZvjuLpe<2 zeLGgI!-t=Zn_D?{9|xq#Uw5jM(u4&agk z?$&ZGl@os6npJ(CI2Hb{qvOAVNgnmW_Cluz%Z#rMa){3bpR$i2bgYBTQOBve zv;;OEEtRz$xxk#}f4>mVor>ZrL_}NdqO*Ed9k*)w|-d)ct& z0a#d_PFK^1Ae@hTsO(Qy$4W<%p#X3vEk#A*djqMBFX+!p?3$ajm_!=m@|N#G{`Wry zP`v2*@#Ec}N7_@m4Fk5|C)^u%DYR`aIc*@~BXMtVAPauLd2g_PL?@p#)i`=jIG5ku z>_iL_XApS_^7H(3VySTcp$5?zCi2j%r+LN2<&5y8XTP6xVe7_NXg|uG!+x9Rp&U-K zN1mNA`tdZK%ar$#oBC|Ok+&?qlfVsTx6D*zLuSv{CBu+~tu-OgVJhEsPss(@SSfc$ zIRHIDdTp>5#%{CV5qp5QYi*AjP&+eJ&`CN0@0o}>xTXh(?No(@{B6jfE6d8J05u$r z!t_VhM$4SYUeEAVnz_>FwH`|Z+y$JIQCN5uP+=;@`gO(Ve&0k^kb-d~Y<7tn_{QGY z8)Nn|9bO83iJ9BRMe4+HT>a*`djL2@>o~6VWv$W&Y2o?NCC$aGhA)yjGhHGn7sC?rVgoxNXg7X=oViaQoOf?S=0gel_@i$TcIMCISqd7v-&Yl9 zW!9`ZD0=2at!-kU=86VxdbO2&^s*R zFOm=QFcxYNS@GxFjuz~CJ~nV4cLnZuMKd#wn3$MVq%qj8X*uPh+!<>IkBqEqKEe0~ z=y>5cE4~xZnAQmww{Hg7GOBpgxT>fgMsIMb_c!T&H zFmY!zG%$lgxS$-K^6lHFWF%Q`fB4-td;rS)yn9v&igS5Ig;l2RWJbmda)zVe_F07D zTLtqn8PqObxxuf+j~N<8#D<68DnPWu%bN~oPM>Y%DJfx&L|y<5HcM0PAI!S7AR_>V z_?ScUSqZ1PlYD%9S4xbLt0+AD1 zX+U%tV|+elj_-vEGEcemNEw?p9R9M3IQCS(cZc#gi=Bd=CgRZ}q1&Ac@-UAT!?I&d zuFjxLy@=i0lLuX0_b9I&b2Rnrv|fw6cSnOSqQ_H)02<;ZxLA-(&cG7*vw|DXBY0!r z#H)98WoI&a*nZTY6J1?MwCpFs;zEA+e+04uaKS%)=0y}KY^Vw6KlleN=9W?&;n|k^ zgJF#?=4l&}G2=t5b)PD~tUT`$syvhwxIoSpc!ep2Q(F!usqPP_%SsA#$)PyZAqi}uIzsT_Cx^6p z`39;r)mB)T4goc-Bp^a}qE=JOzapEc^EPQiq)Mk{b9wg`PM z2P?k$Qm}DJpI9JlAgQ7a=jB(}@}^^%bLgj2X9`MD6CaA0Z~z3a)RUc-Yx9BS$X*yOjDMLFcz zI#E~w*Ge_oZ5;RPzHHG3(+4jc{?6m`#%p`_NenL#`S-4m^{q6u@lT6V2n%pBR(&FI zqBsHd?mQa}(YALzGG~EPM|4N=jnHCsb-uFIRtf3CBNchy&$XqfEz6VcVX7~He}ks0 zECU$ux~Bq65wMlfm16oNnhRm#Da&jL-*>6}OX#$Ox#~bvKAl)0#L@w}_e8 zXE{$@Z=D>Ou0$x6$Cu^Y(hOeG`aI!OUwcE*?lk&7^u#mV^&UJ)PC2>M(EsD4+ToM9 zz49O0STRfZ5n0qvm+6gO@-Y)x+!30_q}tY)8Txqc^I2!H)s*jR1-Uy--9=jnZFz$u zR(aCYw3LFIHcG0OMByy=qx>Fk%(#uQ-|@VFJkr_qWHG&hj3_;6{itsE#H@ODRCbAT zHp{tRA@5tL>T69c+`AsFR;pa4c%#5j5{? z+MMy4oJ3J>?rUWTM{RoKAP`ErY0P%-cw}24$L(OS);InPjP-)t#wxX)zLoo8#FB6& zFqvXqGuuxl8B)W_`J4&})dV{)#qR2-~2iWHG1Y07H~jix$Z}L zOP66>^8PgPhSX#p2WW5S*^PFS^ER2_nt%Wh)8KR|c;Z6|q@sLm%n#t?0tgzR??@I)qMJzQ%WWp7CH)83_HHovAKMR(5nY_e z)5N3-wUO)VS!=g}ld43B*0-mB>u?ks8MG#Nh;t+!{dn;H(l?ko{nxkc(7~xK-3p6| zXT3v`T7VzRx{}Y=Z_jy&-cWm7qjOFx?T}MBhFExlDM5l9)9_%x%^*sk8iu37lp=o5Rn`5ouHX>6tLG|dM|DJD> zHQQCwZiQYtFxl`dNg5yALkmszjj{&i2=dww)SyGIlPWXJpW3;Gw8ajXKrAp+Z zQ@70)ZrwI3x9Q0(Ky`neIAxNz>4F(7N`ucqjPUsn2ik9$qT89FU*;1pFC-2e9*A&z zi|$3l6b0|Dph#=r$ygxEbk7Prl2Dhaf4AM7Su={@%G=O;y;i%#goLYVkG=N0C+VkA zIhb9Y>8h={A<{wYe7ODRi1xChsY1YNi+nh}igfV?61wxZ`chKcj6W`u*feayzVmvK z$h0-<&%CpfnnzwpEMRjoy$B=-MT4k<5l=oUn7tg6@M{zrisz8h-*vAfcL*niR1$n& zJuXG;9|RE!;JF^6pHDqX zKQQ~Ry$D>{Kkr^?yn#KC=8$Os;(2dd-4NuUBI{5BdmDv6LnhX&GYT-);|-$q)#K

3}_U!1PVEDzLwTa$gWz>?3XjP9RCKVl;|JQDwBe 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 diff --git a/icons/mob/inhands/clothing_righthand.dmi b/icons/mob/inhands/clothing_righthand.dmi index e4e9fc0f859e9a0edc21975315990a3ffe51685f..3b62b05b314f086978e1e43b3709c788e09d64ad 100644 GIT binary patch delta 70018 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 S*~JBc!l4vAJ*fm(dT~)(T z#>m0Y-ptm~%*GmmT;t-SzlWfN&%W!5$lx2XFzFe-Jg;YeL+hryss8sQcCAU73lxt- zV%_#78u=d=GtRq&tN4UokX5-RTe~6&A7N3Ut8G{tB-!8pLleUHioEA9&XG63tcsgKP;ltNy->N5>}y z>FdrhK6K)i1+MpmI32E&{yNR|D`aSvpN5wq=NaMU0bz#9lKax6JE|}`b%F>|Wa{qj zR^_;}EF$yMZl3%~4AUqPHZ|Nkz z9VYOGhwQ8`$NWcSrPEbDu(|~$^%DYPN@~5^OHf?P9R!*_IQ%2xraZdko_+`6DO^S~15dBL`t|GO@W+UY z%OSS*Ka|)0ObdQ(r(ybS_^J9eL(^XCiaP4cf!rO^d)jM_@LM^>w->j_I-IC_%{qNh$BZGgL_J8J?q~T?3n(GfTNXzD&>t zOW3EM^Vz()BB~SRGZsXqVtq!HRrZNR({JmI(I+JxaCp5c7v~fzLFCqkc?wRUy5p$j zo^yLm#w!$>3!Ct5g_YKE=0h|`gzWvtzC2cn^|s+8H;?bQa<_VY+0!|bUY~Laa{g9 zeNj7^msjLlbIgcH;Z0Q!s=I})4@!A`(7eRX!+MgV5^5gkHLUV^ZR8U6aF)P5YLLR6nc&-DEtP%I z@M<|)Zlm+jyQ5`>IlT+fT+L;YG|}i(W|wlJgzRy>u*q<;{N5MY4=(nQpAq>*A~)LI z@#)s9*3DhIj!-)uCXrE?RcSX$f12#3&|P7PGBJgR4)-5w$B?V%coY={tOV1%XkCc5 zWM(%>a+tldX?B*M?|b|5^{w#dB%*}FZ<0<0<&hcakv~vf|>doq9;}@S6eKy;N12$1R87uOlo!7=KzEF>TOzJn`tb=`<(NG^b#~ zH7MJiLhYl+(fY;${LN6|s*9dQbcr*V!@WpuCM!!Nw74 zRZ9BNM6JimS*l`Fk{9xDyuTbG0Yg-$bo$y&G6ME z@k+t7i*ZpP#(3z1gHT(R08-85a~IE=m)mQ{U+hs6`fJrP6S1c|lVf^P<>*y16sh^^ zmO~OP5efjo_5&tKB*DlcOGjeQ%BoIQ&oeWRBKd>JEvWA zAgzdt0n+*8bRK%4l5yYkc#nIi&+MF!y6v+8*#Lc~&f(V!p9SC}HXmGrtR~I(nl`J0 zy|$^^a<}f-wO=E*GCkPcnfI8etmQ@;Pj{t`9Nq89MP#etg{M_&P6CaGI?)nd~9%Li8}{kEGz`Fva(OakC^$S@_Pp{TYCgbDmAu$9uzvv zDZVl?dbz#W?@f`<)lv6KeH<;>^OZ5ibAZM&*Mef=U>FU#>vaZu+OL zgq7`Dce;G3UG@;ifSX6>nN`CNd<}rYNK`ZH0|nY$$Vg;(sv@8!P6XMOan#NU#)fp{ zF-Fgbe1tq{3oul%f;Tq3Ppft3elKMM`tU7F#9{UdcHASsrKJUeE>Tmnu(OlgxN$?+ z6<)il=e3Ranuk77klwjLt5Q^Fb-X#xsrNqAUDZBqu>*}W0!SPC{A<~u%Pf`7R=pddnir9o}q9VsU2m-ky|Z&H5QNxlt~v~j2U0!9!Wil7TPhfu2|^6BC^Y`3jh zzTit*zAYI2yNwqn#g6S9pR34g5dD;Qon&^%ZJ@2qk7jf}Z$XBbxA(ysM% z`&#yGJ|oflLAF}%)2=1_Of>BGfOdZu4)@>Y4>$ka+goTc$P*_zfp+@p(~ zu)I>8QQ=rkH=HKacQ~p-RM{ag$ll#ve3VFH94D&_G+b@No2n@agxZ5}cN4p1A9xKgeS8P+qpR76&)MVHT> ztA{1Jr3Om}t7OvLbRBdFVG)mmv5WQb7y`^V!D#gSW+0ACvM!{kxR?Zr6L-77TX$H* zVk6IQ(y6*bm)n}WgwEYkSUn!IswkYMw?gZ8+A(ew1 zx-OyThc%Y6YC-)L>uu)p$W=4yujGIc0xqjM1W^J3dqsM*#zhWAAhlp#I&8aNaPu)qQISr5c^FlDr-%!+Je9Z7E_YuR!8=n zY89xebt~Mn6dW{<;$zE4ucKVc#x`dqTsIShB!s4&n--rVioWHlWKd2{O@&8A`O^uQ z*U#7!K($9Z+KtW4Q)_=3pgv1?wA&mb!Ivn+*O-=@YVp#NEfPI@hDrBHw>&urptT)s zCj#gvu7>FDyidmX#$h*{J?X30{L_W3zB-4iU&gp_k)h~YaCW#)#V(z&-I_XdFNkuC z074DGzTgUwA@RMAbeHxwyu>f`)FRDUi#41mho@(ccj?wRC-O=q2%w9cip0=e42p2~ zVmE9d1(^%heEar|kdHU}i9on!j;O!Iadf&M**z~vTO5Uf6UXVGwI97p;wTrlG9*R~ zV-hbL6?)0XO*204aVCo&!YL}0*XhF9zFeaT+(06s?M~rpq9H6jrgduP@@E3&gLQNc zXnEuNX1I>v-rc*%;$|G;OSC2>SoUYC7@-OEi_qFwzJWs417N+88?AvDqzg2LB=S8q z=Bus;yIqw3?Al41`7G4d$iyUwl2LTx08=OD=_!7)$_E4mNo#6qb~{5Fg$8f!rG4I# z)0`-P;=t9DJ#>a{%_s~(pHIE-M44FPtOnS^1M`2LzrI> z_Olf_{IL~-Wf0K3Te;|NBkf@qG=MLPhWA&V%)EmPFMG1Iw`M(2AoM?nb2Il_5J(hr z$#dQ6k*lFtNf#5^U!f{J8a7FkO_cQdRE1MfQF*JWiTL(-;@o7=QU^|?K>*FZnm9Uw z>K>P>5MUDDVM@q#AjKbfV%)-AkXt{7oLVUe?(p;f>$x9e)x^>+Er-Oqazme6+3JcX+rQc7JZRj@8J>h^0Iqw`dp};n%gC<2)ck z$?|FworU1OsZ-GnuMZ|P;f|CPb^Nlfx8-(L2OTyi!Qxb!W+BMDUxLzX)^=`=1Imb|OemBAg##@oyVQu*S| zBs4mxPKV2d#mBEW#yu&% zlbt6()?9;}n{1X!^j|kCp8&_XzwU4ekO_G5#pZ=Nc^>A+gmpmy>Bd68+hKYfue9Vg zye?)#yb^^<`LlrgXq`RgJne;%fTK)HO3v%KCSTm&T6q2WmUfC#B9rT%w~FBMGu30d zdgG3mWV@-KrUN;&dDRd?z!Zu+(FAMy*ABPq_o%Vg8} zKI;kzx8^9LPD&iIG*WW%ECsyT%C?&<3aKdR@4wD15y(j2ud^4C19vVuDomgIK?zKH zV~JTWpTq1^OLs4}HT|SLq0&0Ihf05M*kFvX7>u*2KK=QIv3oLM;kp;$Bh|;_PrJF zf<+b8T8+~oT81%I%!yKmBA5I1{N#ir-^-TL{Mq!EYCg_8Snm>;jc+5n(I0Bq^qyMS zdHKo{dHLJ+(~laP_PR)GC@|{GQ)}O}T5FEcQM8(f+{eYn9Z#iZcYoJ@0R}+4w3 za=eec|4&nXwMX-JNX?(`S{i&toCKL3*sYbi(~mDZ-P0976D!|g&rT7jJj|6&XNET; z5_|a;k`0B`SZ${(>Z>#v-$=>{R>Sk*ez80w8Cz3z_`U^3hvKM#HYnQ=W-i9!9w(0z zcYkOX9Zbu@W1-{C#D}Ki)gp_7`JvhQwRC9DO^P#T&J>vSavaX^9z-4p_&85H1>2GJ zk#s}%?)aoa8w3mlqauraY_LwK6rb`_cWdkJ{0j0<&yR#TB!tv|Em5>5fb%m?F5I>l z$SK$m)s1LozH*&1aijXz2Tx`nL93A@ldgpH#O$`>&88w!jNo)^=O+_SGyR($Yu4-- zT>Wymu~QX?ZbCfo&*nNx$Qwhn*0fr#AT`J+Z9|Qz*}9K^pXxr0!mW+VGhcVZks62I z1qxGXIG@W}yW<2+U?m}u8nGDfypU9#$)E+`(=F(@*~6X``cKt2*LnCn((p@E^D`d& zV2nGoUx65BdPw+Ff^( z_a6AN1&@w8NDg>yIK&i5;Yj{!g4^Ni*OTc%I|TTWU$%F<2<}mbWLqd?wF>-Xx4q$0 z2knM-QM!4=YEk4{yCFDm4dgC$Pug3-OnN}UlH;EK+S0XQEdN4r5fGcQ^yzFhx>pVK ztKN}cBEe+xqkEvS{gh~U?Hy8|-553kQc8Yz?(-2>JwJs_#R0-VO)bOXg8Au}{rnVh z$C}Mgdv-PxsY7wkgdoVL(6IST{UL$Rp#~FoUzuq#%Df!3U}u+zs4d<(>o~E_#+e^K zA;p+ok>X-?qZWvoJ#2@yF{sjDZek`Rx%{(7Zk7cKq`w zDR18JjDGfiTv5d3TqHX_R2P_`gr=KCg^;4g9IR)WvPgw~`yv`B{%?P%rEqWmaU7mG zlI;ul{OvDX^7;?o0{%bgT>LkD3;6$y(*o-Cc|rvlcgEewIcvayELv2JcvMp~8TvQ4 zf1KvHuQi9Q+dD0vrch59&6~mZKO; z>{drB384^XsgUJmOKCmRT&+?FA|j={YuT>Begc^h+aoctcsS0&m)U@$E$Yb)pRa86 z9`sE67h;a=)L%M04%GpGdd}i#`f>H)M%%mUTODyCD*wXdT&M$kB8eC28|t}VSxF7p939^Y|r!I~DLTDx7*bFt9Vg()0603XwHUbV0hi(AApLZ!^Oj@GQ_3OU|~QmCcsQ##PrVKOI#*2^HVCFr)1 zlhTN0yI^u4E=#(B?1m7dVk(%>)LT<{~_W&8$kb*8OM$F0X1f+2_mI%j~KLuU>V$DDvw!DO}T zSS@l_oER`WtMHx%C;iAXdK+TfRG0t6w0zy|sfiN)8H4M0OXxkUh&zuprt09Mvxt8h z+j#F{9rVNdV*O>DrM1@S?BPf}I^=yYeAq=-{Yz}d{`sKw%75Zcyi5S@e2hg4e_uEq zX{@fs-N{ymGulLFtMh8t;bK|0>R%q^XgbRVd>PhU$vJ_cb@(`9g>AAGwhq==pj6e- zu~hd9h>q{?jA`Le6@+bsY*uw=%6!4SOKsS?cmccpV_qs|+=@u3=V4R_E;Cq&Ykdt8PSwCQ zhPjn5%PzSc9-{G?_He?g-S59Hzt&%>I~c>ng{a;DnDetye<{bG3Jl1>tu;^?uH9m^ z^V~EBpmoHUym(P7!nXQ&f9W{XDOON~@!NASLa#LOD3?+f%-=c@LguBjcWp(Ve|=?Q zqWu{UW!WkQ?L?o=xb90?gydQ%a6SLSk2hd&pjmJFOAwbB+Qll^GLycm$=PYG!vn|1 zc6NnxL8GTad9q%g&ySkykrlA@z$`t}xmK{%fGCR-(@`@4YZvIuhYXLnk{O*^{Uz*} z3-kN;?+ZY@RJ)Ur=L8#yfk6GaUD|!8rUBhQw989 z0_o+ueV-lt+ZOQ)d}lL^(=%aP%J=eaI-cMwsA3v~(^9icrym547^H2?8VbFwy zTdAa8{0EqY0h(?4v+}Z`d!=0k1wd_%rm$xd^^;n%Bw)S%#$s>J%jY6Ipc|zy({GT~x+`f2B zDJp&(i!Z0KzKheEv@mgx2N*X09*Sz@lvhakpCc3Uvt0CPTYtB9_6UR3hIBT?&S1y% zuWTrgwr+wYVAKUpo{ne!BJukKPt~Q)yQI`%88*Bi5c_VI8l0#8AyMkff_BUkcc2M= z=V4CzZ!nnG!`%p^M^KOPg3NO~`nDV-oZv3Y^~P9OY;JHLFU84@x*PvMLAS+(VOG!w zXt##ShZ>CwMd}!QL@{o0uM&PeTqEMB9D6d3dp)e;y-PrHOY6lzj^@i5ppl^1ME8zbiokz)l|mqQ+{s%{Sgnnn_hoO9tcBZuqC+;iZAxpctT_Uci7w5D=XT zV3P}he9%kv4K)c4@j=)+DLi&M!fYAF)28gLqp@Dl&AmOiR=xt`te!IHMzM-!Y9INZkg@vg6~3es zWV!h_vNV}m*(!bb2H=J`@0=iFW*pmGjQ+7<{J$Y&_Petzq)-}1_B;AC(!c(ASFlKE z2*9IPB{cd+KQg)Engu0~n+3Vw(fDT9d^nONf+ z_YX#1T}o0YzO=Q!89#B(=enqs&hiCyGtqx z`i_7#6#yAupz5=$HvGTz?a%7Xlx$H&)|4X1{u@Nc%_*|rR&CZGLq5qwoP zEb7Ue`+NmBksYx@yz@&|zHRK|wv{rOUWze7HVLi`GZ*M}zT*Z#Fe`!07WsZVpbs=N z=$(SafmLjc)0fOKE;5SP0m0I5g1&IOHWG1x&>u3M>CfeeMErp!_U004k|oum`=gz5 z9wTH><}PVOK{F2i0fMw78pw>=^6#+wD7vZrTWH{^Id&o+7yZ}|gjS38Jg*0n+nh9` zwFhHVBSBy8*c_{WPwr?zTECG$yw$(Cj%XUi)fj3Y{B*?S&rukr;+76{43arEXFD#^($Zd8>0Pt7 zt!xy9=%H7yUR~wnlnY$?tSJO(&=>%B-K*=57%^T7S^aF!Ydaj1Cfi)@m%bYmvRR)& zzS)TlS1*rIl?4f_xkMP&`@kUbJFHwTguyIxd`&A}vpGf;aW~hw?bNN&oID|_pkYQq zB8Z7Ws3~*aff*c53*7DL*{GDp>xnmo^@J7muhG}#9W7u{|M3-Nj>Uu$g5k2+v9NR` z(`c_3<5KwQ~G|j}Dw82-WfpFj)%)N}moNy`4@Y^mpt3An^@wgJiphwJ{khFz z6YFMquP#RQEl{V|ZZqeSem;!idu*2qx8{T7H9|q&@r}pJOx~cW{_bui>b6t8xAf`6LH^5HmD2htL3>T9omU%NU zN(s&3vg%{vju+ULb@H4QvdkMaqFQPRu4se>m6Y7I>E(_c1IG^v8t<3sH%&RD z4l7XEFX4y`clgU!MT0v#!T5B4iir&)$16ae@+4fm#vw+_8(=dIRQKi1z{>}Mi+@c} z>bubHYAAeZAuw>}*mtW@#uM8t#kKv`+R2#F;yI$HYJ|~jKuPV$uhe64swx}h>d#8i zDgX2P8$(lDy*nITU076p13w$r#k zm>M#-lRMVKrT#&FHKSFB6m2+`uSJIT7V&5f^ghBSK^) zPtI+wcuEt(O9tvLx}SyK0s2|K-*@aP_b0!$z?0h56&7b# zSM7p&&vUCjTWv~&snt^RJQT8?;PWY)9HUTDb9D0?Cc6aGvn=f?X^l;UQi>AAP?XkJSFLmk1zBD>H}Ap(JoL&ROZ zBl^14S1R*o_pMZd89}mAE-i#v{B04*age4F`Qp^>cNI$ZtS=`N3L|2tQX;h1;1GQ1 zT8Vps3A}h^cNO{MaE+6~6RT?oe$T9;!95wgF&B2!UZHgIE-sc7wtn9O)JTvH*6kU` zuS8$OrI;5e=u#f)D5MFQ2K?IGT<^hVbkD!vdDq!dm(PfVzmJzAQ@S?Buhg=VdVR+J z1V0U$SM}pU)L6J3z$n_zmyKA-jqhPy&f7$mG|)?(ZSWIHl*!kt;rr9Ux&D)?VNLC^ zSbJTae4hmQ^wij#o0Ro%8XS&Q$~@vKqMcX z+0bZjco3XYTJBLy*>oT#O(3z2MYV{m)0H_Avs0mMHDT{}M1H}Gc%}@Oqs~h0>Z0I% zR8G75W@Nt|_!bJ`e6XRTmnrbJzaqC6V`qS~PeYepvT@Y&p%MG=oj#uC>QUZT;X=6b zFV#Sa9!cip6O8?9_8iRl^}DXWjtX;k6c^-iX4#&1t=_Gd-*xz5?x2(wR>|x>?gft@ zZL8JQ%{U`8BYRifKe}EzWF#!%e6DG94Yq z2IV>{ASDta@^-UsB8!jxnnz&OYWrm^{b56=i?kGJF!eL|3c8fL%DIN@?c2AzNzUp} zf|jt?nDeXtZ%oFtutlET;lvFLJ&a-;+MtKJV#gV!T%S}v91N+h>Q5^Ip|BOlQ_sT= zDIlN~?&T|B{zG3R9rB9wmp9DD)m}x`vD&rsTZ;olS_h^?rc;{drstBbBa^V)Flfr^ zCvVo&mkQ9md?|F`TbmkkXviVy$`P}N!thebopO8%=kNd-!2A);lZ_R$3X6$36V*mi zjtZ`>7SGW|URJ;46^zu^NN*QFMYB^YL_EsbjDD#qCze(ubLAo)Zrjo^EtPK{$ zimw!Ff3)r$k9o8Z8|4oQMYzWoxn%YoA%t!|dML`hG;(-oA$du2CU4 zrWK|m)k=%MXgy~sq3hT7_V+_;r4IHlSN_hmFXkqtQG2z0R2tB0(kVb_6YIo8jbF{C z^|7~X*?g2Dcr_8kUER##i72Z3g4BJPo0w{pPMt zZSrXm6o$GFGQhZ;(k%y31h8%}XjWa?$hDafLuU^ykKh*(nZ?!_p4$!zPSt~J%Qm7_ zQ}-l`@;EJM;i+wpo;=w=?Q~011%CQ;=^8tG^oWM`ubiBo=b^2({dRC*Z-Zjixa{{ILgQ(G(8GTNJ`Dby#e!5+z|9yJRlnAN zWl%aS&wpf*@B#dabvk2Yr8B1a>p&umM+EfJam{ELBikIp80l@fGE|tlTeOTVGww^~ zp(Yc7;IIzsCr_Sq*P8cd-OorL{E*Y58tSm1+RQGaeecq)jZhh_uva;B#-~@EO|s7% zc~P|AIbPCBU^4hYQb{@Fedn+?J@|prxZ?SLwN86>tbD<=Tatf6avfcpqKP>R-5V2h zs__jgnf;ZC`l*s(T*Ln`d9jv~?Z@awK$>MTl4~_I=aR$&MFs z`W8s*ST*oXCF2Y*-KrNeKG;|O$a+?R;~9uXl>0vnzED)@W!iUr{YO^hm#w|W{FGMF zj}sFc$+Oialb@yfF`N>=YyRn*zMSa5SJoLxqKU62wX&t={d`8Pmuv`(h;(wb1mNne z_Hx0-?5f8R9B!M6(RTpeMGU(8eTDeWHtNEA!-$pM;U6|qXJyBwe%y<*OTQh;@ALbh zYxdI~1wXz~7s0ZpJNN!ont;-d#SarJCZ6_ok5?<_p#+xt#5>G?f&R9tEAwO0W9csV zzPeB;DIz;f<_f-()l$HUPwa~}D7yN-smOvev=w(Ji_}0eM|k(T)?D@0$BIf1m(kH0 zF%0aV8{03CG9t5&St+wtQ~xtP@gv7w0(8*Tc;8W4u)OPOqV>%~X@My!JAGPX1~eBO zaL}r;>u>&(zc+*4{>obW*1|hYX-IeJ`T2$wXHqK$pqv%(^hfyuuLEa5tt5Z|`NMCs zQ;Z5K0A$OSBZt}cXN!{=Por7eJ4Ii$oC}YPd>b$3@>!dzQoqz)6_H^Obsc$XsOXaE zZ|ZZ~%9nAsl?NE@QlKGsV~2Xcx|NCQ6uf26Jakcjc5%2l>v#H>sM{T|nPbr@TB}nN>G%Dq}x)n~C1%!gj%r zpZ)Po^&a9KtDTJX8~KynDP#6Vc?iL&qP@g}^wPcQf@EU@+GA|-I2tn(E$Wno7vC$i z*N&zD{~YI3D!V)%V4}|C4dYk2YZt2Yvf-uok%UkcR`+cz(gj4Be4g<{%@1au($jXm z&^z`Ahcg|pTfWmeb3t+XZn0v~F9GgYF4men(_HyV?$2&+Zq&yO4lC+`w3<|mqL+HS zbx%V->5}&D*Yk;IkF#zs4?ZsTO?aqIeaEJl-;Lm28mlA^<@!5-f*rGwJNxbc0uSrY#xfH2MM?CfQJ56X{P(_rD zOxqAprhVJ|bn^s)KH97O74KQ)@yEsA4t`+~eg^<-&SRANUCWPt?p7+*ZZmBJ?JZ)< z%o4Kkhe3Az2z6l19?p-Ha)0L0eSdU-okH2vH5Hx`<6HXU@+n*XnxomV*Pf&Vx;2V$ z$cc$lAzl<+gCPE%mX>xE(3?rAm>K~YB5RrE3y@8o9D?zY13hU7VLVL* z@uC~uxVa7Cb>h!(xg-4}2hn7y=<*{iAV7{D4gvKS3&O+|_LOyM(_S7@H)^ z2Xk3$&G-Byq7uKlO3w%35`>iYjGIqp|MMzcz(R+evIFB48S-9Nzah5akI|BhURwq5TK|+P?mZ=B_!;*TY$xY)g0qY4R}h;?lJ~$@`*Pl z-w~VI1Yn<@3xy$0W?CyDGHpi7gYWXO^2ir#ng$F2`+m_l&$-m9Z8Qn}I4nuCk zV3eC&_{&nAed!)O4QUQc{km)Y(>YGqUUX3n$Ick5g}j3ju(bURM$AMU4BmsN^pp1c zweUz;O9i&^uHQ}HaIR=JQ|2Y$q#V3we)O>*)R0D`poO<2e~+|1Tc|1h3@Nm*urO3= zL8BPIaX4H2@#6&s5qn~Atqk@fZ~pE}N}v$|`f={9G%!YD8xJial>P(_{6t4t2lJ@E z4vcdsBMZ)Z_fN`g0Z2F8@1>e9sXF^RS(uC016#f3ugL{&(5S|5Lno zQt$tY6b;K06Rhg#TYKoP(4%qj zEi1dO{w`Wc4NRbeZjnMsZ0|j009>C8_!v)sJLNg@6_Y5fPF5=()Czaoi2?Jc;p1a%zL%u>vq}#^m>DyQTI^%LJwvHgB}X7usTdnPY3)A zrYE&RBVBmugsd6th40Dqy!`XyZHm*<#N#4$ev5tG%pIEL`pSo%*L?bRTXy9P_h zDJq6MQc{w8`0$-(#?|LVA&l)nu+Vkh z;OROJY2xX*-LvFvdh8`o(F2Z_F8G6@o@bCd(@#{1~9E)iLe6 zX1^P4P@z)C0iZWY0P?+rh5FUP-9UHVFvEvEHu}PD@wMI=T~yF&x^;tB`J7VAPjy)W zg}Qc(wRxw-R%uS)V@ES*v^urLiJ9Ax}1QzN&#b|}yWS-Z7vW7;$dA5hHynJ9c4hmq6moHi9?wa}l8srVTbr4y& zNI`e^9=KR$HT=fUj~G)6N(cP-@nE#nM*r(yTScTUp??mdAZ`x6L`?;_a;`TCxGuTF zD@CT6nFFOavYf2WaKD3e^e0mMv(kmZw+U&JNu?DehQ(d(q=6j|3T{TvmR%PQ3E;TyPCR+kQ<-k>wFio{uFELeGs;Z8axFDrU^zREt`^=AF+@JmRh7tOeqA&E8goF zS!o4Z&1$p9d@?G>fa&m5M-sf~&q7?`H~ug+XrZnrq7gPHYC8eZ30lre2tw?OFVk;C za0dD7xe{#02yR<*j~0=1jlxn)^kQniEVdaxwB7g7Jkj#KJm;uSpI)GAy@pC*izq-y zz5(LC)#NuD{Hf$XypW;J81U5lz1;GEx8aV1X(c^{q3eL4j^8_G03N$iY)s)z%x8Ra zEwrk)z`UQU#0Jeo#q35b5+RX2Au_1r3nK3L#WF-Mzdt%Y?-$5?G-e^P@#D76CO%i` zMYblZ;6W2>jf7VOdoq-mq57;y?)A;ic#Ue;!aMp)_A?-7VpF~T?(3coz(H#P9mre zGio~vjh|A<6PC9>_(045X_qxjgpl4KTH88m(1{1^b?lwv{Qb+MyxNojbwp0-y%vn^ z;>BtH+1ri4*jZwE({qmH#V|3jZ&8h`@bQe|CC_RyBafhh%{&of#&J8tpC4|kHJ zUNkz(EXe?NR#H~NU_)DL&N0!>OhH|puVf{)SRF3PKytp5hjdMG|8ZMY50_RLVh=y12v-b`y5RM$! zlW^1H+Z)u1;6N^5)f9ZEWI+=bD0ajo#M{}is3*;MBm7z&uJcma+1bsk=C`lMRe>c& ziFm$u*&B9l3wkIEw~KZadFNlj&tLpJw);f}CZX~w`+4JTt5WUR@V#r%=nvI^8F2}0 z%X_#oQu^}Bsr~IG(gr?dq_Y)+q~{fI@5B33_3FLI4EkG2-fwSh%8@}sci>N^ZX4Wp zYdor+W9xXiix)LQPXhHO<3Ow=M}aU$a!dybULR%hFs>^m^VN*kd>a!`Sl^5z%B$I= zJ2Y&%o2^88FL74C$l>%_ClbSIgY!O1FZguHgrG61oUPE^3Hr|6T@_Y2|2@eB6f!gy*fKzfJRyxpOw_hy@== zm4=6f`N;Fu^dU!T92cI~^{;}YvtBgaP&gK3ErlM!Aq+P@-_(3FJ^iXLNAn_}6loN_ zB6}Ycl>A&GCrPE)LQXAy@jTC%T6u1}hq2GqbcI+B8)-^L&mD{0YU9l%T8kI1-YAw) zA$Q5HZ!W1+ubhlIE$qSWIM=%LR}I4}qDf2xU)*BOEd1U*DB>=zR+?^hj_b7qWDxMG zfKV*FykR9t&}Gq$u((G~PF~~o6|@9%Mj5DTWXRmswwRYU>y43E-7^Y*c?KR4O4IV- zG<#FeW2N+6S93a}ItjHv>{y-{Qlf88tZA`5t6yf2gFj}$JCXQ^?%p>S%s5MiW?*38 z7r;%hr;&J357|vN28JD6zNw)+1LLmsYk$jOACRjUGNMoxdPWqU>t$!2mAe49k zz;8NQaT|YuAwX6}@QSEoX2rxC@;7C;{iS^T8K2|4vNuky(Cy+uK;Gb~t~PZm(GPip zKMltlC983PDLSAES=sW`KugDLU9GkQmr{8P5ytEX&aZBbIi4k}WBR^e)m#I&y@rXx z6}~=+>yeg`!N$hckfU;4dXQA~uZ*Lg zxhbb&+;vqeM?kRU;!upcf_E!ONJ!Qsni~Tz${HB3f^8V>qHpg!5Gc_`cV5mCtlH$* zIS)=L@s$Vh_ren%A-OgtLCH0DZ0(F=G&7XdHb$i08x1or0VM<$ z@6T@7)iL^#t`F6{`@ENh1eeL6me$q{$_HRmb9f{iNdi?AzEql7ypRVnQ12TXW$LWJ zWSK{m=l>owImyLSp12iYXa(?Y0luprBO*XVh=M`P`7GFOcnSXn@qOuFIss*H8Eneu zR*$khaVGLc9EI^u%_%Iiz1!J1>!w}y^v6JXaTEB;wtapKk9X0Ia7$NK$xNm`77CQW zBw7i>gS)~p{oi~;ImXha1-$PraP;ITG9Kow4>Rv$tI-I**ujM*myN?J!78=c&&@nH zZ^{4ziFE~uMy);$22sH(=U3Z|cjoV0`F5rpb&JV+_hp?GmDC~MsCD^o{VBECfl`jS z=d1j9>A;;WE%)5rtI+RZYwJ8deoSY)7=i0M2rw=;Pxvx;LEy>e&!1m7b@s|ct`Rr^ zJ3u3?T)iCRiB6q8XFLt63kS<2GARiHlDi+>(W;%H$Ot0pq?}3#GWYKjIAg|xBRG{N zr+-C^d-ccwFBFVCUy1rdWn;K_!+J>BkAnU^1m4Q8&fzyoLm?UC7*TONxv9J*|52Y! z>$B%st2-9lLd3hKzZFsxjIBPgGZWdCt@G})lMU*7>P4QoJPG=P4+n+HKgGq}Y;Rxj zSO&rL)0)h{gOF(k*}I%t=~KF=*dk|~k+QhsBYY^mz5OW$3qJ);;XW3VZ`rUEtKACV z$w_}A&)aYM%7td4ori+S-@L31olAXjR44Xqvnh#>Qa)p4-{IBN6 z$yavaC4*Y5E~=yw9}i&^JKR}%n&(JV5A2_^s;b}at}FNl3xB};hmxk>m!C| zIy^R}A1{yB&;tXJJ{#HLHBmUd0-Ks1f35GgM*mcA>R+vYnk~rq&hogU6)<1iaw4;9 z-(glj2_moEc?w0jfJbS2C*gs7i&P6|>%HYShlid}J=nHqi>bjHjFgxGrqiM4*F?ti zJdE_Zl0;WM?Z47P$Lpam1U*+#IkL{Kn6$#r>F1jFi6Ahof&Oc&sTu!#`ROwtFSqg9 z%$Kihw9&gWLkOdjatPo8gA{mo(*1GVgi?ZdAV7UnK#y6lOE>$>T@Ge%{9m-acT^MK zzcxC6T@Vo|N{NCZO;CCVrHP0L(xf9&q$9l!7OE5x0qIplTIfhe0qIpjfY4hg(o3kx z-SPW7=RNEEanC*Ho|m<>`!@~XT~EmL4?4yR-dl zYv1F8A9u(H3Je&4i~5V;l^LK;88Y<|X+2SQ9E4k30o0PAy5lpXMyAM1|Cn!Jplo67 zanPa+Fm(f_L=#Qu+1c4;0Kc+Jq=pZy`DRY}OI8&I;HT08K3DRQfca@WmxHWD5S&@| zD4bl%!4QN#81`&Qoz{aXYK>@Kd%Q4=tIr`0y<0uRyJWU{@;oj5tM3=x89l^7pmv^? z`^gePzWCRz9&h9C2yeJ?{rE7-LMkZaP>PG|JYZ^roK5Y-?(PbP6;Nu1zt-se1vDyD z@{&N?Mk_-61Tg9DJ$!f$#Jxe$T-wuZw^Ewgqq+JDO=Lmld-6Bg+GkguuS9l0fVWKb z?8hqK!`T&(93k#`cx>^HNhiXqR@(LS_2E!r^9^e~*?|=$dYJXTK9}j}5**#lh>|ZG zDBfgL6tGUcbsixVarmWJ-FD)DsA!o}jP&t2Ta4u0INZz1TkxRm?TUb(pD5rX;9A(+ zH}kKuA2jnX996E<+xZ?*-^AB%b?I#@UrP4QJ%vBaAc+6azDd+qfoJz!Y@JtHZxdEP z8tG4k!MIjFn|W_hABrQ_3WUCQO@5vL!LerU(QX@tn(dKa$DDdj!Cw;|P4fyOjt3?W zL|biF(8Q~gz>y_|vY(Z(LB?$#Z-PDkuX;kx#M->5uo0alel-|zMBqd4J78Y4us(Cq z+HfeaS;R?M?Y5>c*@6TVTMZwIQK#qz{*>e<;wzDG(_Oj_%lQSrj4Kvf^LvT7DqgO?O*z|bbu<_R5(3#IT9N)Dk+NX-toX;ZSz^}pKl#}I*~Pxb65^r1A!Yq*_Lc*@y`ToksH=a( zRYiUL_}2o0>@yI>G6A15c0BMrcNGG^YiqWu6!kR(pu^UR@Xjp;3la--cVBL5Y%JzQ zkU*dg-#(bd>1?pLy15;2&Z#={d?KJPXyzYH=9gQP>fY}j<0(BscTOhYWTkH z`#q~byI4pw4PJf-Tm-tEKOFHfpWGt^+4#@%#42+y4L|MNTe=9PzElb>F8jv$*8>Mm z&}op^U#kiBY3en+0{O+4H1MDJ05jM}|IiU^EJ5gczt@Nl=NhXM?ZV?> zSh{;_;tQj|sG(MX8G3NbE`klX97c(80U#uL!HIGxX#H_!5ziDCyZc%TklZ zQ;w$C?snlC*ZF}jY>_%nN8cAlYpZdlfioH*ox=chq0Fb}<2`oUYj`fd?Zb81pE*61 zS~VDE%iOMz3Y%8eHeIBlBv5T(fPpclA0_aF`m9Sm@{nLKMbuEfJ)AfRxjT$rW*umd z(=M(=y*%^8P>Y{WPm>Y=SG??7qgQv9>&PZwy2lc{DaDMo*`$(o?uK@p&@HojKQN#n zA>~?1)fU|KLeY+xq^6R-;@fgERYT?Hb?qv7(;hSWT=P_$wg*ivFtZuVkH0XJ&)2ka ziT%)wZ8SrV!zsVu+p_$q|1f(8w{%63p3I-*v$%|@IaihAd-Nh<1G+vAlWse&TPTb_ z$}|$R{c2|HAxH`_rW+UkPPV{VIM}EZh&a)&vbk)#QY=pt{4!fQf6$Y}BQS^1}BK(!hfOUfA&l3t;44G{$0@j-0 zhh)6HYt(V~Kv?#Hj2!yB{zQmq9%N7@2Ny2RmLN!JXlXrhfle1_GzW(89&2t74O61y z^2JWGE~Ys8{!5N09D2hXUy>&fQ&}%BrK+rU zN#6311|q9%6ysatYxyem*wIY$c~|>Yd*7qM^HN9_P31t~Pd`lr9ZU0Qq-(T&P0R#iN$AVaJ1}AmsaJ!ee(W z*f_W&fQ<(io9Jc8XKpU!G(qFb?`vlhl7%!k8XLd+TdREG$GHeIz2nn)ZTa;$>CfD% zl@DcqI72d>BeD=wYkXk(Cz-{dq*~%jZNOOgJfWuYe3p24&$Yu%2E!u+$NcL4mY|QI zm-R~yNM_cOhPk8sfKn$)4_<={J!bdeh%!yR5U!dOUw{KA*%Go}4+FLiVc)GQkr+Rmz9x*vg_tj(?mTssnk!tS0s=P6Aw&xrg*9_}5W4u<4 zk~bsyms^Vn8(g{LqZL+tjI1sA7Hdg`@MB8dGRq4M8(I({yj7Qww4S$TwXYg}T6r{C zpR{RCl4<=>G)*k76x|7}qc&5#tp>UZ0$ch&=WvagV_ga4CveBPn^(@#Wh_0c@Z!OZ zuC{1umMhUrEHBo<7%uC#@bpnGSYBN1a6K{(6ZM=akx#CGfpMDCzI%~Y=N3MoHf-(Q zt5n?n8w(b7@?fhcOy#bfjFN~}70N50U(9U}Pr+`R`0kkEsto4jpX%oqa^@l$ z>H;YIo}yBjhREU6wGbg z*13j#?P^5ac9}rE%?Zx6+qZG7T8AVo8yzxNJH3SKg12)t;CEm1Y=-JmOQL8 ze)m^3b_ z;^MkSD_UqEa6PA4D!if)+$!~4pfzQ0^{!v7XaqO#$?b_qJ&YjfA%lRQh$hq2A)4gD zwsq*55z~{6qk0FY-Xs_tPZo)(ZKEvDnuxXn6spz^wv_w9ODa6KNCvW&LU75)@F7Is z(ed!$-@pVZsIm(OBDgV^^N{goDJ2C{hZTCv6&bU?ax0v&zWPcxuHGxbA<`dQ--w&P zG+B1Za62L9>zExnF~VBmC%zt;DM?bUw|bG?@hBasZZ|x-J)pl~(Zzl(?^KNHLfUY$ z5sN^gziTmlhhdc4mumQM!Q;N1+`Uw>KfI%PW1p)uQvZuMkWOK$PxNcI$2QdjrvON7;GM81-yD5u$%-sZE=zXFDflN;Cl{uQ5G_7)-eSFCcG z^uM{o22V9opg)?!zN{|RD+ERTE zV)X7A!geFa^w-+3JFa-8Ss{~=ec+WQO?%%Mh{p7znzetFj;z=Zs!AuaeAI231XsTvn{^t3#0R;lQ+!3_ z^?%nfL5c>xBbYNv`KIfOfloid-@5$0C4CCN_nJP7i_=2^?##o7A-xHDnhE;oBLybe zI|E<%i0yg){JBJV;7S=Q8{02z)(E%fcwwz;1lAisD>n25>pX0RrJ8a90&vn?kXMK4AYzr$d1ZtKy5n=; zoboHulXP-DO?LWzgqekcVXoDyz256o3kSKSBl+0PnBg<=StcOd?-l5p$+D+BMQpJI zt#q*=Xj*{u0R)OL3lcx-krP4IBNdHn%fe!WF_4Nan~IQveAH#k4m6p)2--z$k;hAv zIB!p1nDtdIU;sW;QlbKcKV5)S$nuP`o-I1@)PQHcyea;SbRpGLTtx7;n%fy@Xm`=U zalTb8lal?1ZLnZ|xDN3xQY!U?ML1(~xoY5f=mEkd@Jx^Qqj!iQow*t2yhIeN>m#hkp zHD+-8>}ix%_1KnY#~*uuU^{IMK)a{?&dn?cLkko2(nMLJZEl%6c|ia6VCr&_X&2vJ z*FQQY8iP#LyCS!!0*+U>p=)eacl8kwOyXL9hRRyjjUWu4YbX zlU^fNQM1#1ilpx_7H<43w8=-F^6~AITg&8ox>eeQ^2^Wn#=|lkBAk zi;6yD5VD~Mc(+=ql(@KHlg5DSqN0`U$n)xtAt>B@tAn?$!ORvTW`AG`5FFY8@uMDS z1IvNz$Zv!#U}BhpYCN6RY{}7BOd_alnTb^eqvTS>` zUA3Qmnql z4_0+*cp-OO3%l;$$F(!Qlxk(mV?Gy5J;d*bEIC`tT}AuRO8|oM#vCqL3n!L}n8+8z zfjtdoePxMVgydkaqRpmY&Xt~}nTl6O{O}ixB#O0K>iT9%@$Sj-;0ZB?G5#+WDh!x2 zfBUKKuW>TRq7n{kYHH45c3SEL2+)(UdY$M6@dmDCqCV>F{o^Wu#GbT<9p2^OhF@O`6?vIxiefTBC*k$!CSm9lNjIDG z2r8BY(xQ9W4OB{ZOWI;cREz!GUjFp+BwUAf zZNemsy*hTVvo!78c4gp9Y=!MzF{e8NnVjAEuIHie1U!5wncUOaDQ9k;)kA^oBaIf8 zk}?E|2{Ly3gB>Jr&47)h1OCDfr;~n<^)g9LV(U03b8-t_`3mOVtte)D{n_COsMB5a z04&B3kh%3>G6l*emLovo=`|RPwP2VbF`?ZfRi{T`t_>NN?rCbBh)u=y@b@haDNjZSX!B!m+@pYQ-a# z$H^PN&Wa#9`j3|Nw=RGg+)^d{7C*G(VYGYt1Rkt<(iN!}D^L1Ee#G`W^ciAR?45<@ zWofC-8FzAt-l7V1-m96=L}e`l$5CrDi}7T~ga^$b(q zI$Yj0F!Rc-4p-gL{-dW*u%EEtC zF&!of`{)(+E^i+mgze1_HGb~jHurY=V}ixKLt&4+J3I5OV3ngUo*C8(c;nCXm7j@` zhVKXyN$PXzgYja?p+XPgafUJNoEg~O*{H~5%dV!vCPHzG@O$-4r~AP!=iJ}beF8ZB zP-56hTz^TnL2lNTV}86K=aKl8D@mgCR_dD3&sNIW9s;vz(N{Gwl^K=H&F*uyiNL(KQl_kVbctXeI=XLtb%)0b1D((uUKV=(w z!fh*0dPROb2)T?s{@Y_Jt?uTvWio8F@rWjddchOW(eWDG0#HR4B08qx}hDe5sz=-jR`e}TG1Cx-Cq-FNBx*bMmr z+rS`|9K8((pm&O*Jvci+>0t0(C|ze)z1^=y(q-Y~CcHt)RNLcQeRT#W0#u8{Q2QPo z2}J`#$0s-U^6Bf1>V3QGZ6^+6n6tHUfX?9hbKeDrI5eribWuzNejl=II1Bq(E(-EQw}bwNE;sKxP4RP?GLLKIei}aQodX*YlHa-mgrseL@|Rm zz;3It)>LhM#4WCMeOeu@_L{8o{khPY17t#d_vh%axRgVy3TsyG^Qg@wVwljszYRBX zkU~GvJ55PXdC+x?m71T}L;;8#?kRcr3~)DJ5IxMI=gsHp?5d1aTFHSM{ZIUHj-;PJ z^RHNhWfO7h-w;vvq5t0IU;Y6oi~M)b`xo@-|L18pV&I*0mGAbs_yQGo%wxG9b0t$g zl6YxeFW7IrJSm&+pJ3|7w>&!AbmctkZkfDHtj<~#ncQVYDTZ>_Ch3EgA&`-3<%kl}?_yP+d)-Bh%UutA%AGaV^n_K7DfF}Z#iBTk5zr=9ONo- z%5SQ6F895(4Z#;Zqf=uW6+9fLnHS8fp|>))uEj}V_SnYV$kC$zCe(h5WHf;w z9>o(`z@zZcOC~JE_I39nyymX13USA4_&Su)W^YhT#{U+qw=!)t22wXaF+Oo8SxQ1& zHvB_cUcNBTjhI;4kUhrM|6St1rMcCeTDWLXbQTG^k3J69FRV8WUeEQX!bE&Ja_P0* zGo1A96xVJith*YSn_FlVvDJZ&IRtH#h(9hfU8Pc(hsQp6{SqBaQi>I^PP3;JdPzAX zH$o<~fwq&C4^-GxCC!vHre?ZI0&vnA3&UMkTe7P(g!w*4(y9~(r;U*unN!1mNM04h zg^A?KYToY_EO8trO|a}IrypT*_xh-^?Kx8LO(+1hy(Rtp6@ml;I1nwxoKHjnu+7;v zQYZ^{)eEY)^<&+MI_PfRye}Tftrx_FEY$;!%x1kW<9phjjl)qLpQGYXB;km_!mvP7 z*yn?j7jlRtkxl?JF!z_6(Td?tflA)(R|4pQ+_B$=rNK?uJVG2cnJ)KrnV}Z;xGiBX z(I8U4{a2Yrmw$(XSsM(EB81Kgq#;7PwUP_#+2s?1X&^5|w<(zE7xjn@Dq zG@9{cy?n$6S9&rAuCK<*7hqrb*L%1jB*S>oqN_#}J!k#FA*}}5A!CZFhwhg*b#)w0 zCa)Eq)DFkfTc>z5EQ!#$%q>K0cn6*?ESx;I9gKNCBBFqjU*dv*+|5x-^D{gw?PUfO z=w6DVJAWa(2AE}OO)*3`6$qmF^1{_QKv)V2yT68n&PV_ASyQyx`6AK02$UM9nUfAe z|5!n>>Ad9i5Ed^95!?s_IV1CVTlgiDfQ`=BGJd;- zf8@^XP5G8r?WKEH}IB!tYw0VI!{K*9v>_p~n_=I7(PV>Q68Vzd*30=SmuY_*J1kazg( zVn=!xj2o0T#nH>yPb^=p*qg4`fis?4MAIo1)?wAg#9y8 zYVW6JyIHsuEWurED(bpAy0~{B$E;A+7G+f?{=@_5#y4jRd3<&;NOqcohnYI=!pmYU zMDJIfV3SwO?@y!sowu^HNadf|qB*OxBJ~>FW5bjlN!<`$bkYa&GQF}IPNn^!f_zEn zW;~oEb+&L9demX_!ax9Eh@WuJd>7)+5hN6lIx_xH=h7YzSF$h2Q`QtM5bY8Zeof!_ zuz(_J+&kMHU(2LursD%2@r5H=R z_gux>M735AYWVTiVhW#3ps2L?;8i+MdDSjqJ#MarF$k`)$IsCANfgr#${G3(l^jYW z^X4(X4ObL4!S(%ipUxmWGo)4z{6q~Ab35_HV0PtZysmdS6)-0ez8G8UkI*7x2$_9$ z>}KXVu9{pL%*Bv&DDm39y<#~+7$MX92!gN#XRkO0gA)E+ov57Y6Mk+5DO{5WLeR0} zYM(mXx*m5XllE`cE%Gw3oEJ3yeoeNn%x^K7W8H?S-woq4G8>DNR(K9)VtChDlR~k^ zbrp3%@r8(kQkO#_J4bV$`eXx#5Mh?#|9p=YcqB_F_&(x$_}{C)K>_B1W{2_({(s(2 zIFS1N!@mn+zwuz-0{W{65fs#_^B-2(|J$1|%Q=$&<==SH`bsaxVWPZ8%YeA1{{5Q` zp@u?q>^d&3^fj9vn&-)TId6!Gf+Qj=w^Z`QqwW_*Du<`W_7)3E)y0>$Akc4y;NU|W zlw|RhIGmPqz%qS@?X&5J&JFnsHUk&sm>(}!JfjR}&cKhvf7PT3orD3?97~n-gZ;V)krf6BEcAI^ipMV13uz7XA)0WD*1# z|NedCMwN;_|DqzmH)@IyP^TU>3r6|Mg`wd&$w5KMtn|I|M#yg(bHP+4;+v&9gL{51 zjD0W{zb98_uDwj&D(ZIpV&jWkwwq*%U%EA7)s~ zKi0H*su>}%8MO8NR|Eptbhm2WxeGZ$&vymrJ!BhHmv2WwuRC83==7^V+2!!aO0ss! zi6LS`+r2U*)s!1c&uP>D8Lj;70rL;CQ2cfPJGWfOhn>S$LOW$M)IOB!eKPwLY-9@G zpHJ&sVB}il^~0KIj9;K&QkhFgsJia+plZ)^8=dYmb0hQ-bMT^Q#>RY>T|g-7`D8-K z{kbDvdJ}4c^w-CPN)k5$EJ11%f!A&NDjtu)@GBY|2N;vxl6%Cuy^edAQ5d6|9U323Og6Aga+mkXxI`7O?9^QcGw)?n8jiN^Xke&#u>or zDg{{9M1dlz#Lf8^E4@>uSt~{g2Ua-owHnqr-0SfmG(dp0l*qy9S>+_$40RVsyZJ1UF(twJwnu zALCt!orTd}lPMAFIzq@@k{~4CpE@GwoZ)Ugy?Gi_hLV>VcND2V?aFleoGNMVdp|I+ zimpv;teKT$wP;94F2BUug2^K?z)ypewxOBRFbnqAnbTJ~J!=300Gga9J>M9P}ebnPDHSXjc zk;2;g7CZ5jJ&|A@3zd|UxDT;awTP9QA=-cO%F_@Gx?iI|I6>djlp0tK!98N_HIv<+ zDP&Q63txcpmq?}YB+fZM@tk^5XoPFtQ3qJ4i@)BmDlg@JU6R|b-B0o|0oc@jgx~rm z8m@gNEP!0EgD%=6doFQ&>n=ieii8_~>)P-9d{_2&f;lQ4HnDx3msi(EWf_Dfis$Dc_ zrntZsYxvEE%B^dnqE}`T9yiRCN&j$BoS(}2I{$Eg>q9FYzj(EhVjOa))I22gRenFgFQ}XC8e*dzXJ(@S0?t(zxoDRwF7161^crS91 z{`B)N^iSefuEeUoL{3?b_&%#0MCQ$;s8Sh-xyd+gsBOazA9fEKh2SZ}6vq{Mev#*(H+t`*@%z}yEAiRD`An#=9RRI+8?hE+mmXR8J5?uQlC4Ia8Z7i#95yecOd z(BoDcxO*x*O&6%X{k4RWIO@dnF!1YNNO9bPpdF%f%vBPzK_lHKakpqXki9I_-u%H+ zGeC67d}*(mz`eUBy@2-Rbjn*5RKL;#%cr^GNhY@p#!`iQ%!PiNf4*b!<`j=rZ`8kG z2}JNOX8h*49I}T#ALr;t<*4TmYu@IiqVg94E+)G`7<^qN0TwiAUt(Er;ec>dvkam4@K17H9*;Fzqyc%H-Ae(~`FUM`42 z*hLtJ)-zj{k0R>5uLG|<=WlD2R>5H*-BTva7<6n*nv?rybQ?)@t)NS0%SO{>%)5#e zK7Ky?!hv_*rpg_gB0c31Ip@LGv5WlK^GqGu*pVrktq5oC(VRFw8*?<+w{OoKWDx>` z>ZBKA2hdCcHGV#N3HL&JAgrVal4kL0)S%-cox#eEUnx&cFSX~5uu+(=&3Y#IJ$wUW zD=B}ten45XPL|v4;&DB@5X!MXDhqHYvIV#Avh`NFhPu4hE}cYp3V3REPn84 zjjZTvCbbnx{9Q_k+e~ebDl0u32`Hg*12DRt2?kucyoSbQ@Q=0SQ`-}g_TZns}Am9X`>?GF?`uT{(@I#C(EZIC6GX?n-VA)8v=j)d% z=V6BqhsCAo^R<1p!%bV2x;tXBKs!39<_ACuJjtJOnZK>Syq`(p(Q=zKyas*6%Vv(6 zd;(M4lqk>SFk9PAU(uRU+kt%>YeTG{o0;2b0#EfvIlu*sgSJsXdp@$PjN%5N(mm!= z4dGwm=Bd%kR1@Z0c!>o6nVX7{W#82NVI(Koe8fOUhyF#v35PBQ+WCF)Tq=*Qyx;+9 zsDQ#w<3;8+g*~Y028D#?tq+CX-9F?5l{<{h9Ir!?jaAK1<@~6*`n*i2i697dkuS9< zR>tCw`|E1oMS8&FTeqN=8-Abf^bvy@_^o^gTllYXz0RjUT^OE>ueawY=Wh}a)y)z& zL)&v-sd%U9Hs)fJO0vo7Dx!rCy=+h*2UZVUojwr0eP=?OM&wuRjmO;NQ9L~wzDxpT z+^zZfo%3+=WeDmQsbFqjP*W>7Z;Tlgn1En92Of>%Kk;0MMc^DxA^ZN(^Jxa~B zi-R^e=`}8ZnY{X*Z=g@tc)Y@5eM9)>XPhm^lGc003~A+!_CO~=)9?mQPyXZ04&L)< zTN#f=E^74rgdnDax1Rg&EuTHIy&A4i;f6FELb>@_T2w1kGiBXS&okAmZ7H{< zy;W71k103#raYcdTtWdOWXt0BmYd3L_nzX&VpE+J ztR$RnU1GH`l6Q4ba+9>Go8HKz+NQG`vxsRPv9H*U1Aa>jkHxT)?{|@Q8tGB(5pT)WK#Q}6kefde;R_Torn`2z=hYK9G>`_E%43^O4h_Pl(hyp>DT8)i9C3IM za0bcXJ3h*#jc77P;PS0hym!D@Et5w`^QM+H)YwOeRaAUKL{wq5s#C6r$h0d(cud5I ztyO%RfJt5C;wC&Qu!!cpDW;9tWeX~UTj#gxY-ha-l+|fnW6GKsSoKW3@O|h1$TNXQ zWBW=XEMK=;57KWQv-vy*SRp`~em@l<9S(GdO!sCY0CAG(fs#_>raaHhoAMYypY*Q* zM4W(m&3v{xPImT-V-@x-@BvOcXCIwp4W2)lfoc!^YrkqK_ z{o>7=uMAgcgKEq}ukX-692^`Y_4plLEa3%_@-#3p`nok&7u<^9s?t;aLbji*@h;d` z?^ApalotQCvLe|H1+VhyPt+W}HQvU}GelOPkkT_-7m_|Ce0rCXkr8QAV)DvafcQim zyD_mFM+YVKvJa*}^}j!6)mO=RxsIaYPNLMbrNa(#tcvni|ClWh>zh*dpC+!X3sm|&pI{NquZuYGG?loi* z!*tF&_0q#*O#yq_w<)lGP=WCFk5$RT!nCv|(4MfB@0$B_;RrUuF`S}9Dgxu?o3MRp z38F{wF)n(18G1LlFLZ{I60OCpOh=aTO5$;nkD$5a+}`CQnv&n3GoEHj>F{snfI{rI z@%5|G@ak#d;4sC4#u(+O!lMYw8@FK0ogd6$=|L=c;>8!9Ax3YUk~pZ{oFHS4t*&4U z)x0yW?#cfTE)GTZmd*K z=^$ErtJlEm95RfzdtkUS#LW230MY9=2Fs;+L_KGe%qLNFJuJ~%%=`*}wFb=eLhzVd zGKfg0S5s393JLiMYW~D0aenjdKFNZ7X8J%HRBO}Bw2*poyC=1(g?FmtB}y0I{+cr< zSk`3U*rNT%l7M>}^u;>UMuq*I%2jh$uzE6#?$ z6>)a#)=N^qP3eN=4mxDC*m?_3-7yzqjHqzOpXH?K!q4)r{P0?%txfSc!t$3@%D;0M#k(=;H1U$R z&OO(CeO$a~S6CDou&(I-edcZS`}gmYj55;G6ESAgFmd+$0MO2l;eP?b40$Sb!Vwg4 zp;wH?$RT&{rpvbb&szdhC52uTf2rg#-$%~RX_m3ofu&23f6Y&^9zE~t5cR+(om?dW z+F2$EjDWtWLZ>MM?`5@Jgcm7Re4$*lH?OYS@|Q8(n&J1JX?;3VqEj@@6|G&pQv)kL z77K4=XK?%T^A}S!r%l3fTP)-!PQf650gxGGeSM`KDl;K+bkx<~0EL^>XC>yex3+jL zdUVmtmS9#+HM6qt{mL_}2s$<0b1rwxA3pa|`)`iFx)O|~tAog)_0?n9@c`|z8oAc*0E>wW)47O#{i@U_!;4qc$(Kb=SVMyKt>9ojW=WpopKH zY_zJd^5EEBKlks*-zO{6gn!fkfq-<&|3L&d{0}yP{>a7u$$9@@PJ1%81fsjYe@?bS zX%h(J3TrL%Z|C@(7;5di>?#hcDOvu-e@y8r8*Y+`Z9&`*Q-b)N5{m5WPWlBsAdzK; zAGu@ZZN)(pbTaTuTN^WS3uRkz#vY@YC0w=4%337FcL}<4kw&}TY<47dc{r@yR!fCg zguhVsZ~_ ztPl`=Hmc^_WO`W9b9CPAG7~6Kq!#Z)0)E+c_Ldan92)w`^^$Qx7n@je$2Y72ac><} zXZBf8tp5wpP2o+T@(840V2d}p8aPp+H8qEBhljmzD{<+MkE=x7zn|&i4LcvK89c=Y6fSsG9@Bz$ zyT1St)>y&qqP^<1#6!KKF}HNL6sc@X*%vk02Wfqu?|x?$$L!Ov+q<70S^+8_AD6s< z`kykn2u{mW@~bso!SvUjWWX~)HFHv#L8GIwlYH=3PQklxI9uAc)}(=RNxb-No7DoP znsH=JMoRp(9RK6sG4b=gSuSp~*wqGLx0^q-AgK4Noj#;gliBLJwX7zc%%0)P4m=BPH9-XqmKqrYs7fxy&4do>N#BDCv3WR2h}J?MKvGiFOfk7 zk00K@fB%gRX#evjYSmQ0-iI9E+yf#68a&xzAnS2bNV24t)fTp=g=u)WTMrzJ+NgQ> z;6e5WC;ZVqO?czPae^lGw_6ab&ycvQl%Hwu9w34LY?(5U^V5|7SMORb;MHWi=1CCn zfM=oC3RV*txT5jSK>}cR#v9DXKgug21rpHfM4eZU*_i4Se5Q8f^Lq z8mcqB0`^{)!rNQ+XXaA{ntgluH0w<2^TynlSF=>&SVl_iYz+#}!dtGZ4v&dxk?3^npYhyS4q|_i+pYGI^+-HD?WVc>XLK-Kp0WE= z;=TKr6QiRUf&Pm;HkCec4z+|%ei!n#zrhpmrNDUUt5!usbSlhC?m01!)JKMlj_QZH zPoD+K9&q|G!#9yVr(_e;*8BGH)YQ~v`&-z?otV#`*-?QchU^(spvoN@|3I9?!7F8) zl)b*x^|)&E9L6jIt6R@g@pG<0f#}-dzt^hT-xbi71rK4C1Fxp{)LAB$1#BxtOCCmv zce>(+7iw})nK>Yu-;EK5-7XNjn_k=%&b!01wxD)g)|YW$ZNz(|K*p$jPe00C5{SY( zs7&v4=U%8Y+zZPh=)I-nPbGF#dV;_`kZ_~&>RE1WZM_Hp3hCf+jzNyqC$xm_78O~d za-#X2{zxJ-fV^msU4Ii&35vFU{?6<@?bJxnnVWS;WLQvd%x&GwKX|i^KKRe@1Iq+I zuW<|~aq>Otc^UGq{Oq3Pu1#)+LGN~A*5{o*ZpR)kJ^wxrO~As)29b1p1sHIw$?l}@ z>kl8_xN##WH|is_mr|jq5Fm65cc<=s0d^F4#mhAW182$3Hpx)tD_0&Kf#(H+%vslt zxeK#BX;NSIMh?T3_6KmRf$Po3Nj$q%1`2=O#UsaIGwtbe)%o{%0qL* z<)xd%U>fDC_kyV{43J-x8tM58o~E)dPPZRp$A>6dV6yu`bhW3|e7qN8iOHki?6TwhI(P>z-t-A;?qdi$4gormPa zQeyk|a$^%dYJ-yD&&m-dTpCEL%AQ4%-VwKt2qm|D`KtJPt_~Tq&%oRj3S6Ss_}V@8 z*#}-Hf&SFWRR-RFVCbA@8Dg(04S=6RZDLgv84QmV} z#?+4@)_|*^nHwy<(1yNJr9@ODIj(7L zf6up1dx(IsSa@nGx=Yx-QfIVHll)y`V#$*3#ozYuf*MECFaRxgG&4&3G6D{B_S3Hy z>cW@bvBm9wuT-QIXzOf2J0?8=iaJ2bb9!%MMoV$0J+y`h1>82@FL+K7A$5?w<%)(Y z&#FEn7ECtT@{X^TPFkb85|m8ZsRZ9yZ&J0EY#4q zew18x4qEpb+O$B#s z^?m*Iijkf3e#+I3jixzxfA1xVlN=$c-oEDc54e3%r2x2so`xmy&cqvKp`QF>W2PlC zD(daf*IU=U^n{+LB)oJ0iR{xWesuC9h;BKv_3-`sf{pw zx+{b&&%HE46#M)vih(;<54^5nh9eCGW0yXcj1GcGWJ@78NNgb{$N~f1%=I|A)zLRPY1l&hZ(SJ3Mhb3B8XV&r2@z`Y;C&! z`s`mkovdAbQOizJ#A}0XHwd(6Yl}|$fgb(C!7c+vOy5?FVL!7QP(j7n?!>v#wI|S+clr;ms`xrV!2y5b?$Z!GA>CYGdZ)bIEAS9fnfbwkv>ASjL*JR)0^AQKF`E-4WnF^ZLQ>>GtS(K4Z#g@6`9# zE@RUaoIE7F1g$Q@VMl>qjW|wR)>(M*SY3b@-#%sMn z8>&d+zSpPC0C9gmewiT21D6vQ++Rbb_6kP-G zC*Y57>ff{Y{*dASlu9S_kE{FNW8lI607k*7|HXOgdq-P6{-`UV#7Ku}d$8s^ zVU&YvLV$15QoQpnvW1FU@OpYq8(8;=#J-_Az$ydUyW`8VGG+ks*K65HKN^{|a&+!f z{Uk*p2B@6fM4dO((u$!st{`LYS&Xj-KC*8Fs6id20YY|#cLnU4a3i#PMVG>reA-j%ENUV(?CgYk;s1iQ=Wi~J z{@!j5R6f%!dXja)MSRC5dvQt#!+7r-Y-|s=NJQw`vtdLa)l9Yq0E&0LT3e0U7z_C- zXx{@2x{_NWw!_uBLTNg|EMsz|GUrbt>e}!3D>S-fDXer&Y4+L)IUCTA1$JySp&4 zvPQz|DD8emr+ z_a0DDeBGXC0V*OQA_|g3K@lZ`1c@R^MFd32LCKPl91BEp5>Y^Mkeoqc1C0bhkR&-a zIp>^vPQmy8?!0;P=H7X0-g>Kp`B+ zD-D~5?YAlbmbL^Cv`mvtt*vCo(=FibcBabWLqKxH=jB}n)i0_55W*)gPy(3bpa#r~ zD07|fr6vvpId(=zM5MLEiO}Zo_EQcM>OIKVvl|Y-08AP`rr{FXsL8GQvq{z~BQzLh zs;LT_m674B1m;eY(VHOzqn-q8{0lIj={(gq*!25j)utA{T$3W>Qv0o?v0^6ksk1X- zgbJz4(C#i-1kV=HnxUJKh;jSGco{zJ`=D;aKa3$(^Jnyl9MuP`i22^n@5DK+-g>L6 z$z+@uyyWQY{t1v)wW0Az>4>heHC~LoXXLKYYgamYd`vhEUoZnVl{0xxhH}c9+Ic6n9C|tP3@!b7vd)n)k z;xi~Q!rTaW2Pio54`WeF1P!_)xD0~Y4!-Pz3ioHvpPzw%e*0ukGtFtatl=9I z_}tWK(hSXbuQ7M5-_N+YEZpC}i^{ep{H9pm(n>={mI5%5|Ll24&^8zC5vO!x)y53@ zHa6_W5DK3onjdXXm}qRnc&e#5uhfzrdIJD4TfK%c6^cD|u+3s= zDc{W6HyLwVC9R_r!+>9W1eN_RxJ;y4Bn3J4v^174qebsMlOTX@UK~rS+IIZHQ1zi^ zOoC25)0gtR)MVHBxOMV7ERS@etG^aqap$j|##3Q@rV73B5Al9W(c8V812j#SA}3I0 z2srpG^gK?O25>Mbr~28^F!D8cZLP7b&AW=9_}`_65s=%#3K}!92LKv%^+9u`ukJsT zpwhoeC8SrbWCgt!b*y@n^(*rw@M!|%2t~8rX-$}^dM{R~KM4=t_YzHx_;D3|>o2RN zffhjODf^0*^ZeGOSKL*7bMAAktG*812sE}`+Pb#%?T!IE4Cw1R^wrx}ev6}U^10`X zo~|OobizC#{UA~LGC8?7SVV5`Eck*i5Ew~_kFN%5D1g}$m?1(y;|=<%va@_$5zIcq zzSp2WvV#GkF)ty8R$~a@&Dcg5NinA&xUdNv0z{b@l;?`~N1#?EI!WE76Yy|JX z5%~kA422u>EiRgxnx0GhI$C$hzeGS#lTx>y#@waNdGvyE0ca`Pnr$sWyU;IEX zFxf~lbKA|;dGcKr&lRU*=IV@2rY3XzRu>jEcOjT;w_=|dLIj@3w$8>Jvx)F0&!fKJ zSsRx?!86h^YD0RSWWF^`L$L1QxUf~>a;I|wEQOZVrTy?T6-!#%=udJgv_R|^+hk6>FP>@&&6a@Q+#Z=tKc%}5 zvC=vP?8Zfd%mCuWp!eS4&QEx7ZEX}SV2*WJwf)m?(=_99p9{w#=+)g3LZz%=a9w1z|CIpKaa zKb5kE)!!p8vjpX9bC2$A+;Y20dB5;4%fxF)f94V)VJFa+K319U5TE;!&v2ZfFmuIZ zxcn^WKOj)WV@4gQa3%K`mt83LHMb>g?{l_R+r#wqd}86-43gaq7@n+pVkNukzVX`D z8C|{8`B@yX@6|Qq)|CR=hmBazhnFq{>*(mMSIAHvA zo5ZDqDZD7(Z`wLN`kh?6VsYnTK8zB)z4x++BZ7a*g$R-iI82T3+c_G@4VYf!_e{h$ zm$DXj@KCc}zZgc%DE9pMchBDyTmmZ}2}pn367%v=NTtp^>dsVZ=r1&|++I|f*{E7n zDq?~dKc*qVX*n;CT?XyoL~Rm8E8;$|6*cZbjOjM&HJ`5-($LqRDS|T?mM6~D_!kd|olJUlkm2Juo%>iQ9l*8N7n)t!fWc(UubQ2*3`Kkdx!zI%7LItu1AivmuN5 zZ3waw2LNs&&@#b-HPmpa>ozo6aiw~~mC-5&B7lGWzFY`Qm_$`gC6I?dSIXO*@t_S1 zV&<3-|a{qTwngV%!>GN!sg)0p9T1pg5E=yJNALlip_5|~8({OFm}?f*YE zk^Cp{`DYLzx=0h1KZiR=vB$rP@4ihm1S^%N)AE`{^SK_D_J@E3rSnMb1SplbeVp|N zWB`m+9W0aX4BN%^y3Do#bg=y!pFq<8g_9z>#+N@G%TOXr8wf)6FZaO#tCy8(SCRt!uJv@np7_PATiVv1B7%vY7Hi6J_#$)qm*taFd zU(3uj{`wy@>XTvx?XWIUGR~ha#p_534*X{L({vot!XUhxO`oSvbi;;9q+R7OL2LOE zgx!I$Ga<5Y&WjlSG$1hJCp5ao>;3DI=0n40&qCBp8vrf*BPF%Akf4@I%U1FYpt?_)Y>02yw&fKzpU-cX3{l*C|%G}7E$mOcWZ?Qv+QHl$&z0*5I=O(FiCauegd)orVrbVA>$*@34$1`4djJ<1<^CFmTF9zd zHFWwa*;St~SDyQoV2@m>CHkR)_|}5KY`!EwPtk9Up6dz7>5+&$=f>5OGN ziVc8Q_SfDM6}%6+m2&tb$&{uZFr{ZwoK-OO+VgnDw6e(N?6dtw;}>lbP))ofvSwO6 zh80RNe3z0~>`lqBU~6Qy_-+XsX{Ix&Mk|w=%s?hRvu4!BujJhE;?4-p*XW7o-v%r$ z4(d4G_s`U=8gl97#l)AVoRbb?{`ncSqlDl6y&c|qti~MXm3zkkQTG5$$yl)fXf`)w z+d$J-Y)UmgKK|z3L%jGg)2lQ;*Qc86L27;$9LA=_T5lByrzvMwJ%!ch(0ck{LAu2KEmE*8;o$J_*N96=s!oyN;;bk!DJvAJKzOL#BUFWC*@2t`8jQ7`NE+k(d?>@=@pu*S)a zG{P5U4=z@NW1?QuPt>wi^B+TjJ6|pzvA{!CBr&Uiz>&-JcX zQ@QX_W&r>UuC3H-=>d4qUqLELZWH)L4^SDww{BKYxRxCWId;|OKQkfx(LxqA3$3HvS;iSRB|!Tc#~sPI?o1pA zlcmW5-Nbc^5T;2sXmjM$^sjM*cO=Cf`KzAFl0)rMm2da0v%qWoW5511bs3Mdw~hk; z5H)Hptii#ULK_PoeX^g7dUd!pt;@WIz5Gz>h4v?aACw#$^sbxHypDFQkv`ZTJrW&o z$HU7oOsP2XN5j4ibS3zf^wYY18;S_dqP2e5W-XF?YsZI_LOBjc*O3v<%nlO{9VL2D zp#k7M=`?4dPtL;+!|_cF*jpwM`1tYUjs0y}1nRYA*l;rafBi-UwnrqIkLD zW@fD5=s7JBk!ZM3d30hTY3#|+3P44;2CA-WK?Q1_(~g<*R=3jihwpELP78V~jAZ%}|z;_>Woy8P|>ic7jli1)~9!lzA29$87lFo73 z!s79!N+l)v!n`0&TY|rh4a>Cc*8pzIu=nNE&?kXXGQ!+F%6#{q%Jwb2jWp_mv$jbV zu#{%M(cc>iDYyz>r2RWf`LZzN20|XD8Wq3l-*R)ZOSs?4f$SS}uXKj^%vl~Rno-@M zq$JB7GXKf6!^?b_(y&x(eS3a=@2pH=T7w3<^+-kiR`b*{GRXkk0{Y35CwZB1VFuGm z*{aDOy}jwdLVb<9dv>H!003Hjii(2$nc{Eq@{)6Ka3BZo8)cB(xDf<`$J(IR*xxD( zo~Rb&B`}i_N@|oxJ9eeX=P5HIM_dorq=@fYKW`fX)@h{)TY?DaMV4V?EU*6ZL&ktA zK&61(IM^I|1xm7PEWMcgb?L2a)E&F)^OPWUDx&y=y%mdm)bAsJzw&h*vvU6{^b(rv zcXlCi3uBYtrafEmLd%_ovDQGX7=rxTzUWNMFB#T^QMI$l(D<~FK1M$ znB#O<5ko7Vf|9qTH`6N3`kFDQ_T`HW4gq^lHE6VjXj*aXCckDgm?p4ll7#zotI%ip zcX?&vy2(@I!De`XoQ+M@Mxi*u^}?(qmKGM=HOa5qeAFSLmcUZxn@p@EjZN)r(mlx& zH(D#Bbuv7d+aLOqs-lLDoMpHjy|yiOR|JbjZLAfsCMxN*TpO(~8@7S3+8+cI1dn$o z8h`)(2~?!^bcMTM?o5Dg4-N669(lUIugqx)qT$_f|I&pVl*M=*0iyDp=38odf0hoRlO)Tt|w9`lB8Oa7DW#phO zitHdfpH)}g3wXM*@^1juHhxyg@QN$_;*dbOBisys`^a7P7A{6uEU`UlzE$;_iiWl#$p9p*8mo~yK@ zkurJ1?zqAqr16k@c5cRd@xD&XlI;mQv&xX=wo$j5Dwk@iR;!jIyj}Av6(>GhY4W*0 zE31{#{hkUv^+@d32%jtx*tb#ciY5IOjkGlb8F4P z`#OF;0vD)7Yr8Ky^jb$dBC=l(S-@Wz-4D7U;h0903$);vY1;mGas8-*!vMJ9h1bI$v*~4-oyGw&QmBXDXI6DaEFLC@fQB;hQ zJP(DFRQ@r^p1|^|~@4fW_t!QRUKJ>12m$9>y7M>@V!8 zFG+1Z=CvD|pEuHI{iDTg&&cCa-oFp16!jIvGW`qIt@<|S^wqiT;j~O|cBBVEU0vO% zA5)Io<%4)er8l4el=1%k^?x|;hm?4na1-#6xI=DCxfNG{MPzQxAY_aY)?FI(_7Qfu zxOJFpXQs07_?JN+6XXU2y0Ok>UPCD8Ohf!a(MaX4l4-eq)n_}U+Bg{+!?oPVwaL(? zWplIyEdTb_{KA6#D`m#|$4aNdtjp6xUyco`#{cO;<2M-S%=;zO$=VvE_GtUv%ixCTb|7|H~3L*kcYUY)mpaC>Fpo({5Ks5+< zTY`2duHS-HseDDfBxp(ud04IF7PpPzj?{llHbbRc{RAK|6lAD~IFa3ylySPfSxZkl2YNeNeqEf-V$0qjLd6x)=+S>T?{OpsJDfb{lF_!_l?V-z0g25 zJ(B8#>`Ske|Y07u{X%>Pb@9HzEX9NL)V$OFAML7MzFtq z>@COzyf8F)R3P7Q(vs%w+OwRE;^tvc$}|*6^bblA+nd?{Xz4KU1`lx{AovT7gn9pj zd?(NL{R42cj%)HSBvuj!9G-vRjVd7I7AGG~xBs|u^}oC+Bq8Mgy6%7Vvj4(A`p>V+ z4mu3!auS68zAYG2OR?Oz8+3A{O}vNosf&x`e=A27=eZ<=e)-a6s4grl%s$}%XpkHRTY+W@8>qk~A<-D~!Ia>Px$)jvRZ0)8 z4PCXU6s6im#i(M7P3`-9eAla|$OW%myB6y+{mE35`z{33T098I@Duz0e?r1`BIr>S zdGRu!u*QI~hBmb}t@`&@`ueyK8H+0Xad9U!S^07gq88+to-x#GZTfoN7X+$8jChim z1*n=0TDuPUByUc*LpPygRIu)0c2o3C&91dJik_EK(4~kJ-}0hLI(6hsMaV zQu?g;fS(pSP{zbdF81DPjnIvTcjygoPlwa?_4(M52j-o7y>z*;+#Jq$ZWRiP9_i0I z&%)r#OKYXylBtpiRbd!_D>EgM|YHtQK38Vp3@`#`E*@5cK->>)D=c zs*KD`lFOGN|N6hPws4XKqOy%gZa|T=nT{ySQuauFz|IbKaUv6Ime51vV}s;*;RI$JdS}0^j#?| zK-+R_Z(7BM*epVwrjnR2-uOPXo9^gxe2d&mX-w&YS!JyR%_)1Pr zp6yBx9n)MbKY4yn6P>9;W9fecg@bS={{8#ypby+fFE5_*;qxcY z4pzNtG&g zr>RjK*~7;L$U39xY!#%8I?_X#s7?c|J10VLi3G(V#BE-V{K_EfBrXWH|MkBJ0Xc8L zVmwf+y;=JCL(&X1Etgu`KgKGC?OT5>Qpl2_x5r?o_{nR_H_Mp67U(So*i*g(ASNX8%T@gNoApL&0lq>a&Y2V zt!teQ1A0Hta8oLKn)3w|j}~bhD(!mlmM;}eI?z=#w6V@04=qPtp6?`J_~E)@Zyluz^&L_|&C)KF!>Eu&XXrBK(3# zI6`9TiR&=pqmhvs?BWKWMue;mHv7Oj+cop3?AdboDc=Iu)6kRYP+9eS5hS{jw^Kef;@@{IBs&yb=}0 z-M?;}ZyZ%(kZQhKXNB>HOVcLKm&R+HFj$6;P2<7%?ZljbpFOfXewAX|qx)KBkA9c( zPtpi~NMq6wq{$~4toRvsW}^Ot9kOR8l!V zs-MSkWMX78?K8Vw?4YOrjPajb0?U0mWHKNtBOJ+R4Ei?GF*2S9!G6fia^jZ?w$?u| zupXr03x#EK8<`FMc_g;{t~13K0D&{M91Vsws)ueD&=^}-aQ+JPc%Wm-kNx@ck!^AD z1{?7IOHU(`E2ogg_`L=D%?{{e&+9n9{My_9^^v@m zGo>6W#_Z~HBSHYns+_UUDK(+Ot0<=HZ;KN6$f*KqNCEoszfZ`oB7@{!zxFmeDo-$8 zI=gFo>0>>t{mvRCe=5TzY;Ae8wHK*W0VR;OtN2%nmV33}ZtfDqi5P&aNK(I$^@4r^ zQC<68DlXy4gf5jtVZ=>({ck+!DhE@x`14@V(ZXwF%+Tm#n>+J#IpXqb1Yekn7_Y?0 zldn^$Gai{{lw(j`B)eF&$58VgmUn=9isD|avHgOsY(zM(UNaKVa&P^q6hE&U^B-49eG!8Tb#--BV=$Q7cLOg@og@W7jN}rj0Flz` zm4%ExX|*922YQ{{aQQla_33%Abu&|Z{^)_ z@8H<$!%tXH2s~Y78weRCEU&E0iA-m@1=a!U5%_ttee9YL&v}Ii<`L7de zUYcPtCMzL$SW$5 zUj-d!q3|U&spv`DkmJ&z1(DnSi!tScb;0B+1-|`lU-6Q+Ym9979)Yjf!Mg<=OR(hV z>5E9jNE|w>zn9oV`GASWzSuef9;ZG}z=?joDMGS@%3S~v+Dg0DWw_=!dzs^%Qx`sd z^rCZqbpAf`>+`giKYVI$|BusvwIlhvt*xOkgdR!A&d9i5_v;q|^^PQ46|N+8odm9Y zxw_!|{k=O0RmWzpUK5GC91X8-NONu~Z~b90mWjT#fJh|MG##1sjoRU@ttV+2HOSZc z2G>8QAgy7iKmt;Pg~C-czxj_K6?W)qHvy@cUkw1Z_{Uu(w38jtqHloBHoKv<8x9nb^`l#!!4%U$|00TimA&dK>!AA-)2K7Eh)EN(RufXq~uaq;|i_~s9yYMHK z=AA#j6|?3zujWOOc}mEK`;)p9nzQg}OhK#%nOC|f1?GR3#Wv`3$In{pdRskT}om45Wj!f{+wSHW8RIYY88+OGRm)6 zhxHt8N*9|Qw2pjZXtLrw6Lsnp3k$E!&~HS0Q%L?>>p(lei-Tc!foB zak2lIYo+kP-IXN3Cb9;nr+aBQ6eBHfm z8q1v;8$Q2b3VlEOPwKkYr%EoF#`k-$LWuCUa%VI5a!gI)vl28n-QT+klf4bn4{4Vb zV?%G{P+oxvlY0G?PCOkHT>?74eC&P&Z7W^P6h1TeYYVFhSR6t z`e83liW{@TV@37>|IYnb!+`JTf^O3WqkSq`-6$-$4YI#l){kh&VtZ@vLWkk#D*xv- z9k%qlU~g4JH%_J*s2d-D$Zs!NX~RuRc4OZb@CSx%O+_EZmbud znSkcA`5?(b-55CjtzgcwQm&TAJq^4!AVtwD+F5p*(@18MUFX+Dv7qiyxGX@GR47Xg z_3M+)mM&`&2s4NXn_lVld@wSNYP{|0RP@>~nn(|v{oE_P z3ZZp_8fSXoVgjtvwEx3U(yW6>u7{!~>6!J|uY2SE(PH)}|7vjB_w+#eX0jGz4%oQF z$?sHlPRqu?j4yjk<-lK_+N>FufoNq*ntwHTI%!v@AQM*#9@scG+c0a?l_j)m9(Fi?iwoOWww6|;T+b;NT1%6ecVr{k;tqRi`C|@t{7;-Rt0R zQbUkXW1Gnp2Y4$7Swlo2w%hyGJymaq_U~#5vKIe)5 z-Zq!UYOd7hl_RVNjyck#bgaF6X*aq=B+@s`Fmafv#98{;PP=BI<=9DS2J?x?4PVK!nGPz6{Us_q1W6~m3x)iXco=2wsko_Fkwc6cPYM8h{VLYCN(aav5n8YOU z7)vCXLRo~G^!XZxgZh~*s_wyXD4hEgQ9c7w3{WLq-P)~_XV%6(U(MfXBx$rLJ^%zo z4sjT~|B%;0`OqS-^PnQt{H8w3ll-VrojdhCD+Vl2YX~3a#32(wT5E_;bBub9Zh->$0l;Yh%v2%-k8RIU zB=D(&v!#_xusNAJ8z(0zn4t+5LS8(mOB5V%=ZYn;E|NxlyI==07bnRaWzkF*u z3a`!D3^L2OmR0qOH2Pv_U>7`2Xp=ef=eMT&2=xN|iDbO6ABG5FHLXeN8gtfTf|xj# z8e#$B@p3h<1l!*1UF}tjAWgQ!3B`clwEg?{<-AxNS(R|ZjYs+r2}t23Lz z-AvGC1mczebpj&87#91EJR4 zq7nrulQm9G9hhpl>mKahdDp$+2JQ&1MZ!hKKLWlDtaTxJdVYGgm{@I~W zM23#8q-L-a5E-x3X`Qfhl?yU!BggkK9YFj<7^V@rmiC`X`S|Zsjh5_Zf#zA2Yb_Q{E zd})Cp;-HH=on0>qrfnMb}tYq!;#hCk-IM2iE71r~ZrmqVom2)U!fM$zR*q zq5Aza@Omqx7J2&D$uk`SX>nTzVE;ef$N5r`Yp8WfvT{$}+aP*gD$-r}Q#IlAA8DK1 zM%=4sqTI#~HY_!s)b_UVa>FLrgg>1GR8}V%Y`{}3m*g~q{5CKhDL9l83@mz^4Ny8c z1+JH4moQtpBPoNMe+G0KZd6cBcNz|&-U|1WT(`a&f`>2m(3&j=rFR`nsxiPQq7SWe zrQ?!DFP(77_rNgWGp)lEoBaBnT2=-JnlOFbR!O(|NKGRA3}|%42b`5)>6BM~l|Fb# zwaf*%dF@nStUG1+95)$3A$mKAu!&1a=>Wjl7wF&-no~YN+#Dnn6uv;`j~0P%Xp~!v zK6!E;1mt2|X)6Uz^=f*sJZMkK@J;}*Dn7NBA8%CFkkHTsuGqRvY#z)Gi?+ch=_7ui zPjjDO;meAy+xy$Io>7jyZ>E&4MLbMlvW?kpNH4r2XHFbtu)TX4e>EKIme}xgEFM(E z99L4i-i(#+bLB+!sLVWnae3Z=?shV>;@=tc&HEXd7AY0D%^2s}Jnnb7h?t6j?U-6$ zX;!^Y1oFqI#GGpr9;7~b8lQp3M;53)HGR9jgZJC54@gLEbWOL(Su_~1UI)rQ-|?g~ z1zGv&oU_>cy~oLHeQG3)&4zC1DbLO$2@*e))?SA8!DmV*yfiPLy|bHev6S_U-2P@w zn9!Y4o^$MfANL}md z)7<=*6Z?+=YOBe}$;N#E$~#iYoVZ?JAQ27|MLZ|eKvFhAJ>Lz)uZ_5gb%h666cp*` z)?auXUVa2zlk1uWO-!nX{f)X!z$h8yTCXSe=C3IuNK^Y zUuL=3V&*V>M)yHR6tm4FaFfvgL1dD2D2hdJ?@+cHq{J>QO9Q7+O~-bSxJy3hqJRgT zZ{netuaA!uYe}B&T9AnESV$;p_Mc;h!W)dg)aRZa&m?%W>1ZgUv8b=HuR@b6Plh(S z#2p@~kjvnPoS)KW2P0nIu8kOQ{?zgetTdkx(K4-w{fk&0X??Ejz}6TQ^?l|UQX*!>LY$aUfPwC$nj5Fv@7phfhbw)Wa~G}L>nD<`Qk!D z53%zcQLX0RWm6&|R{5!i^{*Chm>tT^2amwTi+h*Zl&Wbseu! zBtkk@A;V%Q0$4Hj*5j~^?UuP&eipY~EB0`*!@`<*aL=ymCg+Eg4O)^LA2Yq`%Dc|U zkyo{}pkr+;b|z>IkE_V$Lhr3FOkA@Yo{4!YfbDnke_PH)sYK*!lTMHp)T=g4YpA@+ zqtUK!U_h9}Y8|B(FF@&Ccpxh)3lgAGY_y_y&B;Knh!jXb`3!&ld{Kow8^i*9(ZYM7 z3FN3^88<0K_2fB1bc$}8ci8ik9nTJO6hN19)c=xjL)8)Ug*l+A&vf8SJ6F=ht@f8M zUw#IYl6`VqOKsJY80IUm>rUR50X-|C zXy0DPG)e&c-$y_z&;G4a7)Dm_Sg7G#YJ41}Hrn5cCYiZmD2N5R`je-_?nLH-%j>x8 zdc)_E%g#jzkhw2MV#9JExm<{Ieh6}etE*~Z*CiLSwYhGYQpN)Ye#eBjYir5uhr(^RiE~tw<95B2b4VinnIoaHPl|Pss7q-sM z0DUW~p$YguikH(Ro63p&_N727T*f8;Jl5mSv1LTnQydRbZD1aOSUXMQ2&@(WJSaU! zT`q-BO3PPxXa-w0u)yB0(c7z>U0Gtm>np+;nT{=@!Q@23wW4SrxmFBIP^86cJ z!0~un*a%o>y)zN!eeSPO3F1h^N|ol%$T(SSw?OboNd3;>MhtTsb_w2Rfk;U;OIT~v zg)F4IBsziMlUR)xmpDqQy39S_F9J2@1}h=1p5m3)w%q36jXnc{+twgXe0xM2OmsEh z^TZ#PI5xG_to~80Nv1^rqfP+}y!40c)&)1q!tnb+(-;jxZ9Bo^W-HE*NqZ$4hs``# z;J|`eu{;oc3tAxP&S1Y;CR5aX4=YuBw-xnJ9{X0l=@84F7V*8^pUsrB$#^{HmqpGLrIw*A+O z(*DiyaEEglNJ07nk{%WZikiokP_|2)fBNBS7YA}?<(n^#*Uh1D@}v3QQ1l@cC`@BF zQ3wDsA|P1<)bTEyExjwe_<~I}pmtlR4i3`%h|&?&bvjHzCuuOr>=+9uCTZ$g4z9y!D238~5<;q9<~+c>-N-}*iR zL3aTm?WS7pG1n@D(*8IIRZvc;Uk0gAjnY5nxS$~!FRh*48#%6*+{ohSuI!R zJt5PT>0lz#(Fvxtgv&dv+o4GvnvlGj5+>HsB3?`hYAO9Uam~8%U1rrEjqJe$c9R-; zZ`?tW;^Rk;Adp=F65#P*7~b96ODHd=92*<+%0PU)6ydq4cn?yO9^iB}M|$8p@cY_s zUYC`MSm^m-7eKUf7Y^=ax)ky@-2g>nN=!_=^C&TFF2=4SQKE@dR`3?c)WCtNs;Yom zN5`sJVOTjlIc43(s!dFSfW7_r{g-50aO!&$ijs;6GvQw)KU^Qq_s7wlM^xqaR87PA^?&kZy@km2>O`ZEW2+aodv97H~!N&oGX{y&rC z^}kTsU^=${Yudnf60o`f_2^@&)enZhcRN=pz^el)ph?4)lZKr*JKUe zwE!*2iT?v@0gx2HzWsR)|3s;bb{3*>OJmVC|7N=DDbjAS4XerD`GC-VyI_Zm-gevM z4tk{X>B*?zMhd@vlWpsBiI`0sqr z_JscT*Padt8n*^HmN`q&kR$&hFU8Pu4Vu?30r8}T-dx^Aik^D_@(eHq^vKb%-0UGx z88g992~=3I=J$aoy0eYN2j2XBb#H6!Mz$w^3h>~)8wnFV2>}p&FU5VNu^1K}J`qGO zb@_|wl}EMOgN45-(;j{(kQoQZ5Qck%=GrXwhv|F7q>KHZtF-dNpLt}z%;@@s!p9rch9Asp-Ru<`E*yZX-@D+9wY*5G+8eIzQ-hd-!ZI?NaR z^$WGqn9|eH8TpP;LB;;VsErMdcQmUF=(;yeU+&c~8dUY>YUKi5SmJPvjU(d6rS?LC zcI!3sI>*^NXkpEe1cEm}9Racp@lTq>#eHufO%X`q9*pDq)uP$tiN^mEAt?1{_K0)& zd_@DiT+D|Su#g%Ris(4d1HUw^1{2X4p^>qeYVn&v<_GVYqCn1J7ps>!^gT6|38Z8C zq0wlYe8Osr@XAbwE(Xw!0L_gGYY0ic;RwXSy7foJYhGe_ zjgBd(vOlUbK`7UcHn3!_FW29{f5lZ*C$PrTtue+WfvPiUVX8|~xevzB|s*KDTU$hr<-~ypRQ&dC@heOb( zIg=eV1vHte?$KDMCm&jev2|+?cV^!i2kJW-OC&QF1?kz8Z}Sz?SFHwP0hFAK;W=f zwv5yF(HlFeXHMLgKQ51?27 zr~qU42W7k3PIa7Z-DsZlOa6}@S>DR1rL*Y%q%5=Tka_PCP~6fE^g|kAp~4+9-tr5N z7MNw)Yz?|ie||^}_u~z7X3PmJyTJ)ZFS+Xc_I}*VwUKHE*_|2jP$7KzC1udGMfFqS z5Q%;8uq{Qpk_@xGWBPyR*7exuy82HtK@Uji{KYj~{Lpm6c&rD5ITxQm5bydr=W5Rq z&CkXD!mp%vU&NP}k74(`Z#Xup)`HzP+a^_Kv+sbFDG%;32EF4G%gYC*%FC=?@L_Yt z4xLK2ThR}Qpl>4+Ij#(Du??TnZ{%F(bxKb>rmXf^y%_)^E~`3KMo+SA)^fX3w`Y1A znC4xDSey(Z5doDrArraW+tI&_rLf_;^gY*$q|LT%k<@(WdN2P%kpgL6^85qeZ9Ul2nX$Wg7BWfS7s!c;9Gx@_c#BNckArvV$9F9b^D)kK z-Ufbg;Y@U3_j##H!CUf*;xbq>$7gr-`C5bPMuG$=4*08C5IbiU>9qD)Z`_XHZvC*` zFd@r(^y*>ZGiBvkwcj}uH}Ih}%)UiZ99&ydvs$y!MtO|=Reb;6Jsb!)_#OFtodJ6@ zfvU}WZp}7b9|UGTZmbBlqV0^^4y8MxPp0{qcAEY3V}6LHx8KmIGC&7rJ^}FC8Xhqn zbR8L$wQVB=`&?OaJ-_6SB3+Hg17ou^U4g-+*m1BT@QDCs{MG zW!e`;;YeMWSP$2*xU~^&@-w-@=q&OzM%=^$>Y?&$iWE?-dvkc0GLewe?;^_-qI*gpo z$u-wMmrAu69(s5;8t%0`u2fT7m|h5C7Z=aYhlQM6zKa4k;dqaorsI z)oN{SYK^-4zDROqNCqR^ZjD7mha?aXAc^t9*RR{?H3r5fD6Pyv`6Ub*V-`hbV>J_P zaU9dIw_~;xgE){HKT)=t?feW$tn?vR@t~JD>od19GpEcl0E?ruJdcP+Cq@-~E?##t zQ26{sZ=@`Z*CztER_JliSf8h-A6O^dPYv3rNO*9EiK0_Sk~YSzAZk~ z)!ST0er!tcrKxAKsfHriR$x`g+{!-Fdoj4Sn5{%LmsQRj22WHZ5bwQKUuCtK z6?MY0)CkL(5AOoihBJCYz>5x$u19*PyKs13Z%Z=8uEzyd;NjUH2myv>?K9Ekr?0wS z#aC6`u(2VR)6xo#Bz(=-DD=*UNrYP7s#iVD=nE<%rDp0Rk`vTs%?qRCXJZCI2J~Rn zkRL6v?pOn!3VT$G-~LfC4W`C~$1dW)EvXP@F-;A7C(mowN-(3P77efTyd1t-*uPkH zTU^`}X4hG#?w~KdTYkp6D0WF$OT)cz${=sPWy;GnKT}C@Yo`ip@Q09Hq)UUN((Z-& zQMWe4?kp3{Vb#RdBhlHkmHUKC_@w^2nDg87aYX)YDQv-lpSovVHo>rr8tZei$^wl* zK~3UZe-<$7g13x4mvUwBC^ZkwM=D^I&{MhBdMfusfa<5qXM{I8L!q`-;{L6vvTW_~ zk(&^(`ShBe={{fx{k>233p{d9oB)39VXo}O1AERRc;bvvGfv~zD4=H&i-37`aSn*H zFV1L+>wPqLFy>X8nsyVR_n1OD9tbJ0Hh z+mNuZ9>aVNyJdBhscOiQ$qu;L_;i$cadXP+5m*U;g;f-}CfmT?BHX^_ek<>+ousbN z(6Ee8Oq?Z;1Mb^)6BT#;|ZeJ=rKq=-s<_HQM1UARBT#$FQ9x>=)r1-VH_5)Rk_YL0SD6=jNvPIvw5 z5C9aNuYHz|o?eKi{@pUa7#k|8?XI6%4VoNOh+hHm&Aj7x`#WaL)bwUm<80-0NyXyDDF#>Z3vNRNk!Tvom!R1$!Fd3~os-j$(yl6M?z@ zl_-%={b(mUm_c}Cw|p7+2mG0eE8wFXWwc4zSVOsM|NH!@B|Dqndq4fGQAhj(JtQs< z-+r(kYipLMnJB#YaO7iyfiL-Sg}O61Y_>-osFCeocwnM{_hx&szsZ;MuAGJj5o8ZA z;fYN)f0~SC)5K%ypA_yu72uKk^LJLKtD!mf{sUdU>BzNcg{_~%6*I8%XQ|?`t zwRG*tOlBtUZqM7E*L7;xFUnS#j9}a7dRRn>fd_lnn_?nx!8?^S`=&H)L|XHww#qL1 zVZ1j1_j-2t=1ou%Fzjl)F)($W-gdV^5D9pDz^PZaHa!IBECOKe7@^KqWCR|9#%sJZ z0lC0=-Pevvg#iM0UeN|!A}d9%-Lr}q?;6VK>gw82ey771JVQ2{#5Ea?wxtyD3B$_(4&C6!h8)$GUKY@Q>RGV`HxAdl-qTs=*9tkE+6|oc)omyz1(@ z{s$mE%j*d^-O`zynE|o`)3dGksT--t`W&pCk$e@FjdK+Z9 zG`pmt%AE{b?!+0Q6l^IA?~XdX{`IMnC))Pr`4=|0p`4ZF<}V{vKS(nLnY>@LhWP>F zO{OF3cU00gdvz}=1sq>E_mnsuJQID45(+3?-8FQQC--;pTh=%aCdTj8X%wnQ;=CeVIL-p1AC*Pyyt$v8IriEQ>ih6D*K;NAK58E>wXd&L$_V3;ot*EyA&x(K z_wrklii(&>-N9Vqydb!u^t_+f_;E6^q5F-e_UD8%??1fVF7x_%Wx;Ru(cvFbV+Ft5 zKUyXUkaBB|D(3TaR6*QnzkDU|3&S~f1X&^BAt^1XHJ0Lp7qB@XS~Bmo#M+m7pl39cA;M$C}kvD9V}*t za=)0MF35r@HNKeTEsRxqy^sZJGglnGWZhq{xBdXqmXDhb2cmhXR@ch*)VUNh<58u&!Z?F4+3<`*zKI-jtICc&s<$WkFrhtC2TsbuTvBh3Oq=q&( zH~l*ubh>A5c3bPI0A``LW=}s6*9Rhh9u>74a$mk4%TP7+dwb~ziCN2}^Z?&TAp_KHcmK2mIMkp8@T-3h0hi_Sbw}S|oY;w)rx(pD4OyX|ST(1*28G zzf!ZBRtbrHq=Z<~Ozf@JIZaFkT_Y6*&&b^;=Gh@^1T~m*5rl}$P*CY`KP|FH2g87sMky&S0~pp>2CR@!)Y%| zb|+w#hXQbnx&0s}X1wb3_$cfdw6Mm_nb6UpmB=AQULS;Qi(@(isgCLVYKZ=>OBeJ_ zn6h*^FeD{n{-v3PmoZ5C;we8En_j^80Lp~{Mg0L=3Bj$SE|su4vfQB#CMCCb1O?OAMijph!7IK<*@)fi4JV48LrQGpjf=r>J zf~Gv^Tqp``oGsJ6`T6-TRc?jc+Co%SRegJNPI4Cbo$WiG34pLyO+XeBL&y1GY|Nr% z7|rU1AQa`g$ptL4 zh=_`=RUf0GlJ}cnv%Wem9{E_svu^1=v^=`*^?9*=E#}T)@kB}xrtPUck#JNCs4NXl zOdbI?Q3sH+E+iq*MmWOztWU`MOh@c-7D?P#^DRf9VfE_JwmU~-+hIy0$k@prfM2^& zy&e15J#^PLnM;1msrYHi{mtrFIuH;NI^RvFb^D?mryS#H;N#M_Y2s%4)BP2XY%k!% zPwpv|I)1vdkhUk>Wy0>%`FW$Jz-bDIrJ%Z+?e5*X$7yM=dw7)C#7eW|OB{N9J(C|w zSHdL-ul=Eel-Tgy094dWg#c~Pd#!4CZtQN#e&Dcjc;yx?$RmiJp1RZ=s*v*O`P5qS zc5E_cHS~07U`xcE2>q{r0t{~Ww7fddfooRl%3erAIT3}nr8&DtU4H zyDykGbXG5HXhV{a>8p94b4Be&>5rn(zJ*@M+kY2TuvQA#Eb{^0CNU;dyN}u{jYn+E zTmvDbAnvcTrpHLN zgNwaZo;SoIcXX9yNmz~Zk^Pm-@xye$pDy{@hH7If4C_Le>v6x5FgItvGU^%7gdfk; zup>5wKYIL_)3r$ySYh+w(7Qv9{R0omtHvj@004S9>g=aZ0>LG2W)zKCY^d!wJD+I~4v3{^%-V zX_K#`qmx>cT`_-7nrqZ%HO?z~e(iBPqX1}xO9y@uSeX+W zWvd4d5~)C$yQS+EBML7N&;NNGP%|D`vCjhQ^$%wn%X=p+P!^;@x1B-8rEr@| zxp^yC00Uge<;LFET;k5gpEmw(e#F?49MHB5$1N&%T!O>(&ZGi!&VsmNWXC$lYGdP5 zp&VD&xVgJ~KUq`;{6g2MRP^Gi=MNEVv&uV+BTjzg3MX>x^Uu(U$FNq=&dC^oaeK4w z@d=D=F=A?Sf7q?OnEkTXwISd;RKwm_QY9fX=8)OtF!yGg(hvKb;ZTvzjra9s9(g5&wk&2E&M7?ZB@iqn}Hh?xpvERM@=9ufHJiM>Q2YO0(AR}`Ynp>AIe37~kVXz%qKHI^RTQ_EGg8BoyzB}_Lunf8U zq_U1n&bO5r#j`>`blk9FG?xvqfK$K!@#OVut~Uxn4BS`T|!ArNP*#X?Q8)87Jny8^lcY?O3|pmfAnN1 zezGyRK!#w1lhj1$s1P`%y^XtcTlLBg-d&%GExQW2^CdM}> zQ!|+8I-E5D;*G!@>A2+EA|xX`*8C7K)s`%+xo7O%<^)wEhG&XglB4ItY{E8Zc(g3( z&dvwh)J&AtC2KxXb3>Ot$7$13L&0~uf(*WF*3*5O zf;1=^F^zwY8%k%1I~421jz6E_d>?QM>g_eu=-q;zIoI+XEQ%TX#IJVWA)(*#c7T zeWMI&&>qgIPgMCiwN_igOTh(|ao5E|YI!p62Csizq$F89!`iiUb*Ui4<~Pxp#`$~G zc8_nOCAgHf9y6&>iKtK^h1;_SQ!l#cnzXxho*~0K358;iaUM?ZgHtu_dnF|t2AY+! z?XBZgT7wIUkAGk4?8s8=HQ3-Mp48%7;>2cd?#4td`t<8e%TmG;Wvy34Z;Ax|7`R1} z?1mS!8!ooxj-2oB(=mkwcDM$^eHHXlFm9?CNI9>fkTrx@Ct*L%DK<54DUn~-^O)Q7 zkc^Mhw*P%F#ol57D&)GNG?E-O%9@Zm@AND^^+}FyAcirv@*>u0>oSU{gI@pr-8|fC zwQbugs-nCRE<};bcv5Nb?y%Gc=_jH;%6PuG4Si&e7p%_GZWpyyf0lB$xuBZ5r7!42 z>+e`42i`KuQtp1_dd=jAY-`8kR>%tuHy+pFQgZjyWJh*aBJ{Q&Jw}4+0BR^v&^s}V zW{TpgfeK=5I`3MRhGIptV_1scsE5obj;sYj&m!KyT2FkAgPY!E&+2n-j$z>5_ zO`r3<_Z+p4Bb{+Y-H&IGzO&1>aPc*b@@ML_$37chHU1Lc7^GS@FFbdg$}v%gfZz{%pFf{Q%)8!@~GsriGc%}cN=}j-J(i5eE%h#|7euirpb=5b@xY$$J zcXxUZ=HaDxY9l3>t2VY4Gb=TogX1R$MROQJ7mo<1yk5IR*%BO((1w}5ADJ`pV0d>i zA}y6*t^YT%4pV-Wy{r|E8jw0i%~DYBAFX3#-@X@V58B8yN0~+-k7Blu5kEQtx}h4AIc-TUkQ|*Jhsjn5chFO=^|3G#jz6`6V zBP9s7!#I@)3B^q;M%L%~BapTao%QtF2>J81_uP~A%JZuYQ=JI)f)P_n8+_2f;^Y}# z+tb2RjbTs+YuVH;v5+WoGXH6|%nCTQB^qr^-#A0HJWhO>G`#0_lC-BG8z@1}%Z74k zk#EdTy1T$N0BSZ~bMpd&scg)pWCR|xK)AlII=bnGE6OrLml}OxE0g>yCYhwX_($#i z3)95FzmgF7|10wl??27_TF3rLO7UO06#jp6+yCgUs@Ip_nBAE119*yQHDnoHhTX#y z=H0TH4!XKp4RmbYFBY#|kb-|ckDZs4H2&*4bZNal0x$`toKLlg-HBf#w(E`D_!%Xdl?{6-PCxq9+9Vg)T=*?SKyb(C z9)&8{>si23Q~ATK0c+TzKhuB#hwCdRi{4h*o5j}@ zV^BP}*Y;x5p@*_)^FLP~xjL7OCAi2Yq(G13xaI2 z@0k3~N-haQ-O!FV40xJi0H)c?xvC7!gDhN-Z7I8NtL27OxvD)|_8~?bo(c)MZ}>Jy znRK9PpXT&6@^A60g@2mnM7@~^2e{veVs>Vj8jA=w%ACzgerU^mnD*l;Kz>FDYhEIr z3k!NU?o0HY{nh#Jx$VSWo1PnEjbekBHAsDK7B4IBb1}Tkfa8AWDc{!mg=nR@u_mhE zgWY7rmTxJ{I(0y5d#}wrh)ji_ctzK{{5bzG?SxIi+~DS$?X*GH9nbN2R?$o4SxBEh zZIZ`|2Ss+V(~>KHOot)TBFX_oM}wadJ_&%i86scCA$k_zC$gC=Mmp5gR;UV?BeEB# z-li!kR(>$h@*@0-AD(xO!pcF13oY}m@Uweig1T;KPdfzLme609ph5rYaqOLc_TuR% zar5|;LTy`ELG+|y%pOB&(<&T7sr4<*EmctauP@Qy%J<)0xvqsqS>YINa>WVUq-elE z8$Gex7VzIsZp467L)M?bBPmed_A-;}5?FmbjBbElp-+^BfpB`N)WG+7a8ngRG#6gS zZxlx)H=RtMX&A`Gg)`In(CiN)+r9HX9ZKr61*$S5oNI_) zXqRhMyaW76-P$^9q~cz}MXtesfih8zd?w3kcP5bbX20mHON5O)ly@Q?r~j&B1dg}G6u99BSTyvx?; zTHuJV$VxTnj*OHB^;7zP{-lh(Zf$ntkDm2zocDbT*!qA$Fg;)r&@Z6@wXyeNRuGA2 z^ux!GO-(pmcptW9Xq@=!cir_02PkyzNM9lO&OXThzL>H^ASRoM{jLmM>)#mJ7p?AI z!V_12p>6Ibr>wSU)K409CVY-9&>)JBgXO;V=a8MyzM^5AWn^4(!laxHjg>5UZJ50n z9iDhhUFE9RaVwjz;$|@>t94fNLXu^wdU|YDa4GerOP89RZ)s@9_v6F0v!vCR;Tf0oVhaJ#8rNNM=Nb}NTg*CdN2 zXZf1RRhuP|2aop6UyN(hM`Nf;WxYer)mVd$W{q3*pDDJm!f6k%#*o^D3upjJ2hWjGw36zcl9MOawwj!krQ$x~{0spuhT`*SMmvm#J* zXGmQK!>f5M@cN&RyY@lAu}tz_fU#nD)<(SNrq`X4+vPL+J}kX&o&bASK8Q30v|ykp z+N_M>&AxX*De&<%;o5D5IN|KWP1=>BUEFPoh4mj?BIMls^a))+u==*&c z9)@E>aGCA&icg*zy|yR7BtAQJO5XP~3?_%{YMoT`PNtm&np#N17xI(LsdjRBMwTaA zF~528Yp-@}F-2>!)X&D%6n%RFfG{;A2H&HvwmDGN%fh``Qn6V&2w6_#wk!+hS7pi; z4(EVAd-tzs$(8n4s+XPzaW3I>%U{O4`5Auq$x}78aAe$#a_*$y zOv{O2_n|CE@qpNv_Jq;B@!{9*se6P7{S#3v6Wa~i6DmhnpvIUWl@uSHmNNk3iPcD4 zt2@UiT+SJDq@Jy_lI0NyW2FZ$xUG0PpxpTs9GxLgxk?b__4W#TREw*u)W+Hs9bKJjSzV8hVv$k8=QzPQ&M>^H$w(BZJx^vf{6Gir5+$vvlc4$Dd7Yi$KO=`5n( ztK=@c9Z|k_qOgIe49$$Z5N1j$46>472PNB*K=L~v{`*$Zb517>f=E>G@_q}A0n-uY zwIMQ)-Ogi}_M+F+YQu?KSzTx_oKjUJut0Fgc(qQzny_xWO5V$&ocO=#S!58|d%PxGt!*JMuvCU`}{d>_GoTIF5y3@bw`d zUgE-$f_O^2^~boQ#QE&kjGDI1ENu87 zTAUd)FijIO8`dOE_0($cn00w^H3l_-qvnH&Uc$jNDp|X7p28bEe&qaa2m6w&^#cUT z;CO4unysET`wi=M4}O+}B|&>$ypC)JEHeW}M$a-a@|G9SjKRA^D*YZcT@T&lc4rsw zz(06fKpP4*-uB$Hx;G;e^X1Jp_7BC!#zF13By80Jik8u9w-r~()1beoz(+J_8K=2o zTfzYLQ_XGn@oIX=F=2AnJ6MH!)?4oLMF|G+bCuq2-%-(G=4QYLZo{QEWoDP@A%Op8 zwTLu;mg&t9 zp-P0}2GD>Jp`Bn;p0vXia;17#AjqqIdreOp9U{ASu1qzC|LM7=pLSs&Z_T*SZVrz< z6NWx9+LIUiBSXpoQXL)f3hu(b6)4@4)i&wYgrK#fa#|-uK#%C$)}3i}V5_(;O<+G( zoo?6R!TkB_w zMg>WpF!_AbOlqfx{xiR8%zRtw$eqocHS~I`UYDkt9Hz>Y?_>h@fS%9yMU%bFQx(~@ zR$f_sbk>5j`FB(_(4_u)jv^swjcZE_XNQ*+Sk0Jiw%wKgY^QPJwI$`L3g*5ESlY-L z_Z=mB;9H{s6u;2N`+6gGLd*Nu({ffiR zW7@dnt`n)Kq)7xTmT0LTOd-pq^X1G3g#Sjnq9i;&Yh!n8V)UF`x0|$Qke( zkT*@!zq*Or2v((fhNov|CjlOIk`F(kF2$x6pj4^ARdbMuo)3K5f%Of&KKLn0@6BJG z&{faFMSPf$ESbr@zf26kcCd}!b6fjjaH#VFKqgxp`#1A{5*jZ>xqAL1)DZ9=ef!%g z0lxCzv?2UQ%09`~@qfB2OiH-ARq=vw97zic3l>Y%n2_PB$P$YCM=K1j{mXVxIBdggYi4#VVr^yFHNz%M{GHD~ z3@UkX1Fq%VV#u^KHpQ#&{qAy{sKGy&uFI~FxV;npx0`4-E@10vw^n9L#!FQ zL#?!k)(xSY1F#>T*IiP{LZaXYGKLZA_ zkf3(w#4Qg88R8`DK$z!FJ-{{|f}e=`BSqYN+IbiRs<`cm+vMljBYJy7 z+@mjYAesHY?oAzK3676*BXMWX7v`6guqG=Z4UYS;f|xgkIxBsQ-mP0$LpmjEJ=)vZ zwPIHKpU9^~+$96O*ku{H9yQwU^*!mb@2@gs>V@qPEisJkUk|p>(eTi1N5UVOJ99^% z^Ej@A%Vmp(GuNC~$Lza}M>`eH@mb21F7hhv>wF6^PIH zWLd*-tX)=Mea#U@l;2Nshu!O%4}+2q(iyj~$p>aC3%1JNQQ-fh3}T;i70{C}oWK9f zeI?Rw&9~)xB_BJs0k6uIf_g{<@9$^2Yt4X54eMr`#M>6h%pi+}2p2)dgb& z#dL7X=YVyqSyxXquB;c|5O}z<<$bxv9t_uv+Q^79cx&2&LUoz&s{4NJdOc%rI~wrI zA8r4bN*Z*yU2G~Q7s-QmXctrl#&uoY_@@PrUc6udp^`1mm*zlGh}Es=QP-*yAcF=( z6+xkb&q5HAQ;Ew`KdX7bhek*zu{}u53QjJC zMylM{&+BI=E@I~TjHGF)580e0815nX#{VrEi%&Wyo8I*pRNKw}@S!8DZ@u{6VlnvN zf-w>nO9Z(Lh-0i=O7YR$fuBvc<1@`CySST@slE zuM;Pidk;_s1Cf4Lki-=uxChqh6q_kIV<3nJbG3@A#)32j(Xvz1IaeQtX(Ve5$my^8 zwy$`)U3X7}7tcuh1!80BOG@-tR{C`zdLQK+RNZWeGX04055o2ZlB!b{d7$&|eep^~ zPBy6^7u3O4-!i*lW3vG8$mgT(VDLP##Ds?}vyC9_(UDX1nHPaxlX4?%Gl$%fa75J& z-n@UnSK!k@JF{#sf!}~*3tLuvL{z&;y9dLp%Vl`f?30$plnX4J?d)4vNpphai!YBN zi0r|q=BVeVVHqOf!8-9=WuNrQ*aqPUH7njL6{U3-i7w$mmkvyLcw|(D)l55leUT%7 z0cuma+2~kYh0#8M0n&U3U?jI=JtzIF!TX2Hac?3=Ns8 zedfJEQ9zET8lzVmK~W%lz5hiUIRQT0h8205{S^wivt8y$Fk2s;A>x>))Omm&WEzKu zr=^Iz8SsMbT-f2UxeS=~gbo3NIT^bjnF;3s`$=9iZ1_}c%K)UJ0hhL?Fsql|!o}+c zul(|w4c8({n+@rgeDoGRKa=Q^W?^OBac*kF5=}Ne^9O?25~x7%d9DA_DNrP(Xg|Xj z>@N0T>cu4`X@E5U)xfYEOxglD1f~aFXPpg9PhyF5)&flewXklSx=P(+8(~j`2l3 z>>uFSq1@sR&$9k6w3rNkeXRS}=&RrT!;r|wTsZgifB;D8CH?J$DG~meggYSdZ@eJ? zthDeyL<)lc&NcEE{`j8)kbi$$5~}%6-PQlEEB|xX{x>e&CtlAjC?iJlQbXYHrn=sZ JQZ>5={|h|YH9`OY diff --git a/icons/mob/species/drask/uniform.dmi b/icons/mob/species/drask/uniform.dmi index 0828c290e05950fc2e881d5b2fab3b486c0bb7ac..25cfb51013c9f8b067fd8f3b32ee92ddb9589254 100644 GIT binary patch delta 245020 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�yRv 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

F%$vhZcKkPdIr< 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 z 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 LD@zqinE3x6oisem delta 246513 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

h_+KT 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 DeR^X$ diff --git a/icons/mob/species/grey/uniform.dmi b/icons/mob/species/grey/uniform.dmi index 1636097f72e57e42751cad3d98459b37b11a087d..c956187ffa10889df17b2ea8cc7b364d40316a35 100644 GIT binary patch delta 23680 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 afNO$?7~)33zqVZf^m(GHtx~CE8T3E?i@`Pk delta 22444 zcmXtg1ymeO7vv(5i~u=0huYZ8wUp6^mB* zoVQ5dK-I8$LWrjIi*-!)S7Lnsp{Lgy5B_GU=v{*nm`saU8m0@mHGS{bt#S4>wybvV zA1yVF7On4fy`Rqu`-4=CRFVpZ*`d%6bcur3&rz-io?%q6 zl$7+jUEbip1JVz|>9Wf|^!sS*5}P!u==vY=L`QWR73{wr*tGoQ;4R^%nf7DNjr!5q zS;I-Iw>tT)DiM2QCQOJX(a%8=UX3-rSJ>A}57{Sz=Z+@0gY!$nMgy++GkxGsLW-sD z`QpLaomfPdT}6L@rD7Tw;nWIQQG{yZ!BSB^>)mK9mU)U=Q7Ipirc&y2|M`TRi#It4 zajBfH($%C5hcQQiZ*{lFa)!xc-}U8`!h9SfYm&nfXW0@`km3>WF(^~C($V!bt`Ha! zZtJu#6tUMJp#O$7)Zb!1u!KwdkEXa^{`Pq0&6a?Lw5O|2Tn-EYAbvG8n{(V;?XVcA zddcG{ewO;~tBhT2Oz=Rhtqq*k@ap=!V*(=hrAGY0Q_{RFYJno_Ei*8;xlFd6!v~+_6IMrZ79$Jf$Dhx7>`# zI&Qj?9ajUtNQ67QhqV#pqr_xl z;-X7cMyziq!YS}z3PVyjJLbM`IXfeHkj9wzZ`@PeSG|0rG|%-?t)E)yq;_)Lk3;@m zRK?O8%wKv70P2V?#w1+E-Tdqvbpg5q#A{F5MdTEjaa44y;~lou;u}M8uhuU>@Y!=- zXH3RBN#cl}XakX9=U!v92SiTMumErPesa->$!NEEAsEHTs+-ZB8o!%o+B5ny(k{{5vg%PZf|VjNy7q1+`L=-njU|#%`Y;5tb_Vf zfbV@mb8nBlWa=i3rqE&_#kBBBn1#J_IF_^2kSP!s8i64E&yq(1Z{AZ(czlLsqInGaa)f5rCN@u|v^7D;{J=F8 zXCFA#Dab$`6@SN*nHlWB-jkz4JB@qR8f5LS9VAKJ*NR1= zqM}Fy4IF7P#)0ukaaE-GM)1c}?o(A{O{hQ`1}?XE_gAPIzrUMhZ{Lu9nVw(L%L~rx zx6>f;ByRL<@_XmMO{=*Bm*cD*lx8F9`GjMrWYLlGzIbuy>% zz_M&9&)>-vYWn?`Wm)FFaN=&BKu+Vn=w=DU2H_s~>8Le1ejz@`ZP}eigNXp0ma8cR zcu1z(Cd$QLZd9x}l9XXyi0-t$xpeBA^P_CW_YZry+aU~@RFJbN1@;&*LdTSYDHhx$ zCHqpAks5=&GU}rn&j`3uc{%B-Aftj9}3Z5fiDp`nz3=MlaiEm! z@JJ7EkdlK`)#-{7Yw4&x4zOdZ_^}|mAW?1BC_5ioVV{{1oX}PpSg%&9ithzEySkE8 zR77)da`H<`N{WZcr8hJ*{H?)qc5y-DoVxdi?>oc#6>NK59ll6EA~jVuerIQAbwBxG z)t-`LBY+6U{9GCl#Cy<79js)R7ID00+Zh{g-f-}#kS#ud#s0ny!WmA zMnH1Aj`4;0?q%P8-mQDeN_wT_8ae7{^21gXk&;slHq6gXgYyhwhfC?HKc3`e-^zoD z0MsLfF~goc%O0rg69rA5*#c&Uubn8?GG%6HIM6sPyK}nBL?YYQ&m4kAXFmSp7UV>|03L4GK8Y8TP7|$`Q<^*UoFQC7jW;?vFY2<3{ls z2aQBnsoopf+L58|FJXlG-eDiTCgE{z0f{nOWJ6xkFJ-nIKg<{VAs>A1)XAFd#_X!B zuC+A&j{Mdf4Guk4csHcX8PfUpFR5z~PuJQSWkf_rt`H^t zt~Rva&S5?cgWHRr*N=rbxA>ZYR`#XYkuK+6#PF;rLU}aa*hTxhcna*!He`%5)n1>4 z25yC9A+`*oU*sDI}@^wn@` zZnVew0F9?uWP{U;V*uD0J$pJv(-=?as5NI7f3YW&bs_(=%IP=wO2z9;@AZt1-Q_pg z;7$%}wu%yQh zLS_$;k7{~>cJtqG)I6^!m>36SmO5Y&&$a>OtZcPVO`4erIKCsIK-GQZR4~u@?0DR4NX#0lekQc z8D7jsuQDho2uWN*LRIiq!0jwMd)7+M)APNSwsuTbR#Z_D3XMMm#*uI z)WzjxOnf{%008L%4zNg=6ec9y%nG?!JRK^jH^f+lfP3T{bg8cvjFDpZmRbK=UK|l@ zW#wb5_4G7J0@M?wJB2^PogzmF;Ksm?0_=TvOIn6^q2B&hu%gc)1jseFx zIooPb=uA(KvmkE5FSKN-CLVo8m(fjkhB-#c{_MB#bGV|q+ny4I}p9v=B^0+<%>zY^2WOlNUvs-5OVrD*rZDNKQwDZez-z>Y` z9Sf`M?>iASe4~h;Nf&3vB}ZS_PfkuOtgYkFkgNioWiW|&3p3j^&{(+jw;Q;$XL$4* zI;TaR8f=QsdfGC2Z0igk_s|SiRfI$8gM?Qpfe+GmE0`%gN?RfS_Un-^zuQnV+hvq3 zB&|iMKpB>sc9@OceF*x%pVzsC$zAn4H`ryY*&MPm6^PGojtmI=4oWWPJ8G~@=XmW` z+}zoSY>y%U>_a%& z1SD}{AXVSwnxAfPShipjlKM?mJBCy6zF|=Q%}X^TKRhX1%pXZHEavQthlY=Tc6~0L zk^|cvh%Rl2{fM{TKy*G|=>>hpeAQ}IZUDvKfum0P80k=xMXzOmY3fiee(JV=Uovrt?9jC+V7Z z4}XwCA`-X`jDEphiD?zv1R$!PYVuBgHrlJ>_FMbxIpV?3jNV>Dle?R>;KH#-~R zadU(XMlKS+ehKmWn+4R@^TWcze*E-_dOyhDzvHjA$W{og=L(FlR}mm;A6jV zF$PDAg^TO=^8Adw#qP%F3d2zdo3? z%!p%9q2cDnMU8sLwuap=e7^VB_07}sv%|C!KMchCVV{9az#f81SUv3$X?SM@35AXVcH+hF?E_aP z0z&V|0QW83jb8It(W?ier%QeP4a_L0?}rdM#t-Uf0Q82?5WE})l0$9ugN#@7rOrLW z>NKoA$!j}X&N!#4>enxp+vA(&oMV6z_Du89Yv=wI-?VWYE*OIKun%=4zgp9BZ@4MX zFEYw!9d`q*(L#$U&}-jLmcU+ryF^CZfh1c(V6QQ&>=dSc?|Yy&a6-c9%927=yzv&3O+R!(B`1)9B$0{?aP_-MP*l`LYZTQ zbJ&}oPr9O+Z@360I*lEA5fybMw(uireEHg*1+&cn7G2ZSqJ*EDC6lS3X+N0edL_zUc`C)?LFYM%=T|ks z-8OvSpDW3iQuvnUYo>^Z2n(C9x2d_M0xc~qO~PmxL6@l8p_OE)!$O5bjU-?EPDZ?A z`t7pK2*OJ#$gIqMHcTBvwoyTo^ypSneuB&Go#8AqC!_9Jdl8nU#FDO$P!Ud5x1UHP zFE1|?F*`q>+m`w5{_nU{mg8m*it2S!mjOqI0Y_1RI`FG_I@$A`#au(G%YcsMK94Yt zb(6$2{t7QDZk(Z9R-6G65#W@Myh08M0zXF}soVHgi8}6!pCLi`Z&W^rPrV4%T9$3c zg=*UUE%E=Rkd3(n-XCek@>_8srb9!HtC)fvZy^vg0RYFAWR(5nX*{KhdeyS&b=p~0A`xg^Iy?wHZ4zMNtaC2K9=58Yj?S8t` zFT9h!qPg;xnv}DA|E{gwxxKL@nez28-Bf!#w{8|*`?2|Fw0z#%0Np3KH`VJKK_LkX z)R4Qs=2@e^GX0O-~R~Xz|DSrI;p=hkxLS%BjSpn2G&p5ulqD;>YIEj(-0i64NrQxo! zY(HUBH3je8p{wKY;f3WT5eC$s__5N z3N<|vp1Rx7Ma_R?xfWa*{&NN&%k4-rb>kIIu#9(vxZFe^zDJf3+nZ=AAc{b{#9$!gGi6d=v zip$QzYbi%zNoLAa`PaO6Muos# zJKy=7_MrcUx7$txdI`m=DbV6)BRt`M>Ux9)F*s?UrekA7v#a>qM;BRJ%M%+N?X%S% zwYA*nl)B6q+9?O-Y>Xj=SI=$`qx4Be{l>hxifeRSZGbAX-0e9O zB2P2uQBP$zW0I%3?K(9GwsTHSis}}1*wn?LGb<~GWpie5@^O3ZL63E^m8f84RRzV^RvZ!S|1s z0sp)UO^lHRTZ|~Crl(^vGb6?P5Myl6s4v=`7dP-Pqa&z8`(k2&SaFO>tagio!$WL5 zJUU{wi${pPTkre4ygUwJ0#NeB!!V3pGAY$|?+uq595ga^=LfM2Qm#~;L z?7bBZNTnos<#;mNMC0M(k4X|Y%GWk_!o45uA*KYd3K z0T31@lMECyo=1yC=;d`J^OIl9#K?z>w=ChaC`ku z>iXZ2NDAC#v~!DqL<=(CS5<16)1dlHii)Lk;BX(tow6@ zo~*r^T6kKDq?;e(*c5K|z64=}ei0vd^_NIslz!*Jh$YqYh7NmqeAffbFI6hbMLXWE zL;iJ!@g%%NQwSn5j3^Ba4d_$8qn{_f#{`tCI3`{Bpe=(YNaXihRmS~7M0hM$#Tc!E zQQ%gQh3Np76#=m*T%Z{rsVlx0clr0|lfcIdwX)3&h1pdx(suLp8v~6x*Lq8G48_3- zMsx2k3o;}ccZJF|{D0?`u`fS^S4Sc{!6`WZPJU{rs6a$Ts~lTpkbI&Yy63IUX@8|` zVrt4Mh&+Rw3U+}b81!h&N`j;iu7C9|ZvPCL;=SM?`K$`bd!t!UeJ<$K)D#vr_D{?Y zcaW@6%1F65`i+|V?($Hd#42>hbrNXlRIVT-@CG z4iVB@XM|WQc7B~c=9xbNQ&6}W8d2)bCH_;D5joo~(CZnlKJw2h5?Qq+-@c%OHl!zg zo1RRM6BC@Zo}Q2kKeiLp2SxjI{(SCUeusK>SobzK^b`}=b&2PK(@9}Z@~uOWsN$LH zSQ%DdqO?lpU=QfQX&PT?^(5!|so{{w8H^?aoQcBZk%m3EGqNSeyoIo zbJ8ClP~93m-c|6sUs+5|Osq#ckdTsw_4G)_CMHT3fvS);YR1FE!)Vs^@$#*(Fe!Z; z%Ixec#OLe}Zu0aQMb}?t2&AO6)Q~GBIwNBgnDKWg$jg&#Dh?<~eKx~FOGEmal^Ez; z$&LVefjxddgCVY89$uFH53_|o4KL%l#Y`SR#gRN-s+nkQ!{g-EcXVS}{v-Lri(yrh zpSTJCPMJyFQAp|&>uW1bkG zka0mDUUM&AqtikwY&*7fv0K$K-M%$v*!6opp`DLcf4hpKWAQ1wz>hygaZf2TKy3cg z5SqPdo6eg#`f%pypc6k2<`dJm%&mO9dTa!7pOeQkT?XDWMX5-{HzQ$4UeT*aOGd+C zw+`M_bH~DnbAK8!AAeW48$+_TZ0=6eK^IZkp;^(|Ql-}3XTyFV+G z_v18TUtK@vPd$?h!zLemE@-6)ntW{_bgi7t)o=I4_*;7Qp^yj-kwyCt8i45MHVJAy z20Q`+43+}${b+%@{Be|jP4du(#d$bAg=Zwn@-S& z7*uH_AHe$b8vWP!Z8}Uak}f!Hs;KBpYHd)H3<*Y*5jBB@P*HJ-ap~I-;BrfdR;=N( zT7T--fGP;!3L((Uovt#R;>jbsIx!EiLVw5arIvDd~d! z!buc3hrB#73m!h|Z|kB{OG}}sF4v6j-gS*O<+9|%K==5AoUAF}r(s=mM=23d*L3p6%H7?k+7jXliO2x_5Z( zo~*d(Aq=kn(1XRB25eL?*k@XohHOTOUo}sda0xyM^}=bn^O@OM-G`8Q@ncPh&r3p*7$0lbrY0PCz^VCYGMsYA{)h$!Me&s$ zUuN`gQiL9q@3Ph+rPtgz3)J-pZWRcckA!3EzjOc4`8ciFoS3mnW8W5L7t zu3tGE?ynhkF$UBO8gUD=6-H$!5vc*SyrDqaxNtHD_-(1o)i~nbnyY13S5HlPT7Ebs z!lm7Q3$4gT?8u)p&Ij@jnL5F!DDSv{UU5alFL{47%RRLS9H*2ZBglXC zfyf92jB=L4Aa2c#ay!V7^u0w&L#Ry;=00)g-aM}Gg|w2irZ%b7H@>gHLRsu!T#IX1 ztzN)rs8$*}coTuin)Yt4w~d0#xS#{UK>S$LENm3&xUNIvBB(RtlVi%(*w{EQI@*g` zrI~zFDe#|f*xR$fqe$5?K;FA{x75v0b{!?A*3{ILwB_2neo&?lfB*O|V1Wg)8KTwh z{hVx)_wkv~sOIcg3y{_1s9&}Esx|c1m7F9vo^qFZ%j>%`v+2vzJss#6RF8{K);upG zoPmq&K@6a@q~z>B<+z=mnyL^)4K!>2s;VkS$pbE4QDtS)2u4`1@56)pc^JxOkddk? z{xk~_WMFXr4k_rGVCMetC6Aba=@VH~QFL^43)>Sn zfb8yqy87nTw!PrF9Xv1~wRy_AM}SgWn>uO_W~9=nvPZ4_R&0an%E3#}EjV3T_;0)f zlK{`VmW%%O;#uEWg}@m8S7+hOmeDs6(tg?5qCNfn5*ca`*v+jiFg8R6I~nY1HbMs=g&CdeUYb+v9UN{;Kjr``&Ld) z4r7sBIu{!}$&Ludm&knn>_K3`BVdxoqUW?s%^bj{&Vu?6=K*vBMx_A4o}T`@z65Vd zZglGB4{|d27M3yj6EmFr_O}ccf`D8ENPYrqk=V~ z9@O8Ivf$P6ZYvhC;r;0^==eK|-k7N7|NI*p6kHAUhs?BUU~dNY$z2 zew3czB&c)4BD=r8FRrFWkdx0p%?tFv$95{ph%!&F0Ie53@1&5 z0gd~hlK}EuJZasr^SQaUE02dff7kH|m*p1SnXXq%Zc0EJvPalobRmNTDmX@H9B8hlhiH_@uOx{$s;@)~&&N}X zuOH_vq0PC86%MqeN>jM8%7FH;uTL5t38531dI<|(-$5)_3+pSOZ)gr_XZY_Eev0|n8PqiT>G?^@+`@JUz;QPbW+NfP*zl3sNG@Y46$s&}*XuqFs>^wM zx6Qcm^YP7fVqMJ^Q(+*bG*PUrZDU0pv zm%3t7yk_s*458fYMA_VR0FeL|d?2ygBNPw$oMHD?8wQeP$6duFY_)sXTMvQ!%FX38 z8zFLNMr>C$DDL*UT?xvS`OE!c0iY;*;4X-G++d{!y!W^nDxLD)&2UAA^z`&l@$%yH zFWdVSt-T7KFDz+MKj)h%Hot(u!odD?;U_%`_;vDX`wela$U))L772^@Ak{T-c-^pn z`-(rxpg=CK&>Mc&&>--3TZp>#rQz-8f=QLE6)-6pl$uHs{5QR9;btfYK-Vi~qDke5 zOkuxp(Kr_jnWu`R&K?e~M-y&g}D=U1{nZAZu*T85z94&+VTd}Byka>O3ok*@0 z4Th$$=^oskM10#op_`w2Gf-^{I+g7qrL5If-L}Zj1EU%Ljer;(RI1Hr6z4{rYIr$s zoVd(PHk&(Ec&jepKj)#+c854!pcDto9mQV?{o9|9?J$Trly^|UH0bcI z9@CY}cDjg%+Q8so%t#RE9FY8^U*4C_x+^{C#v}j<0a4Hw4R@At=})MQJN=YEco_@d zPS*QF1W5SXMp$0;J+8D~cw@N=`>I`C*0HxzqpH2WHI20VK$ClBtaIHr`EQrwQfUh? zMhs`QfwIQ^j*k9?=3&#`X{_*IaN>3`_LVGd)N_m40tSX^e$NjFOVk1ofmD7FB75=n z_5r9mZ0r57eE9>Mo`D_yWz#^rB5G=wbbPk#DDv|NKo zoinK`qQogKp3B)_j{Yn67nzZLq!N>rwKX;=DYMASc|RqBD33lpJ+FKUyLP>8LL^{g z!vYTDPgYh`eCMcde>@S+X#!OXhTrHL@FeqJzyu-$$4dmEjL2d+vEeF+fXTM9it(iv ztxN^=d}4ndUYNNTL*kw1)Ti&QZk+Hd+?XN696_+XXz}J0j>$h|UI*uHaPm)+52i_( zz0_IK8xgiIHL21FHnxXWG@hQfjMRoguaSxFNPDl-QNY8VT3OJ^bv`%*YzZ_v@(&b9 zQDu#Hb)^jH-$}{HfPvj_a? z)!qqnQ&9LSC79yh&gnX%Adq-2nv$&6;ddZ9$T36OXu&o zXKz@0A5!3@q@)zDn!E0^o+ImyxY-y%Q{P|WZS>$&sn?ne^ilKkrr#fwR+7Ewom|FG znV+`|cN$d*C|;)|B_Tm1!Kj6#RWB+%oy_xUQU>EMF(qZ?-g)~>ZJ%z!3L`K1#cUhK zuw@Z~s|pYC8!|npfCD2;(<-Gr&3ut?(lJ($zB8p%RD0hjQbfdPart!y>E^Te&V|J$n~wGu4cAUw(Cgo_t+GQp!Wq=wthI?Gc%Egs zH=y`l8Oj`eAITzkW4$E^=~&M92snklp(WwZvYv7!j!Pq5MyNuwKOxnGg^aYX1iioM zgV`9EM@<1R-B5nI?To7U{{zUB+cdnylRaPsZn6N|#=fGsdw*zPfC^;cCamf{Ywp!yQBbVP=~vFzzjpjWcvnL{ zmvmTjhb>(>FHty6&BTNP;)c&mQ|m?U|J}laYn7_3Aa@MBgA>UMa_cxNqxpOK85rU^2Szl(gLq-TntVGm zS&hyIc#1o{>u?4CLfEt{Z0G8=r}#_+%bTOcj9X(5&G9&u5Bte5aqpuh(BeVvPxtcW zlq<|H5x>zx&jIWu)`#DqE!A^)c;(KLgquDsJ>_B zW=6V7{OHan&}ye$W$}4+E1~T$C0EvQa9NRGcW z2r|e0m?=VJ!rn*ZYdm0v0;A9WdRP*tctVJXB^@I*3yFwT7|3IG(*45r>?c9CC3 z{Tqx~k$Lr3$#sH%{i1R6@F?Yi`=mZlW1u0y9hEtDGHbaQ*D0KGczgUeNrQ!esXM?d z_3C8Q_JCdRD+8E0@W0Mk%^8Sp1HQEGHjMx*o zS??b8D^xyxS(|3~4NNDG8aeKXb>l1{^{a*?l`d2S$gB!$+-Q`wFt#;GNuWv01K!T=FAFwI!6^g(T0irK0mI0*`4|OHr*3q7a|Xnl(f3 zO&Q*zg7*X@qzc=9j(qoDv#|Pddya#@77M z8*g_?wQ?;4@nE;y2y@Lbh@}3SaCPoea4P#BwLc>I>Bf7oe^!T0EjU)a8}B#LU2>$y z@zozL+IXx|04yu>zYUw8iDk zD-le+XqdM*(9qGrQ%0}^Vc~Y`Xxm;PGrDN(Y~=z^>}?f9&T3D+kL*$%m*<-rwrg!A z79MVH1|2Ef8GR~wc}2gVwMEEM%Eqlx6utVPK6h?6x&oIM7yiF!qIb7ob_LaSdJA|Q z<2qM{3CI4)mN`t!Td2<4SzGS$nAL}ls%V<6eoQS54XEJK1t-$gV}?)%VXAK|1aK&( zZ~*}gby;W?%S*xYX1!Z=!bq{|?IL3~lr7(PMY+tb3|g8BkY8Smre%bwInvUq?A1t^ z7|Z$q%%mC-ME~`e{6aFG0>xCxmV?W4-8n(kHV?BrhS<4mI-A6V=l9iD@e`GH`cf+5 zAmUe~$F0>W|9bKtULu2flgYgE{fPStV2`Ow!Z!H)ghSd~7UYDJE1m5!;SwvHoK4MiVjL!L^9xpJ6R>C5BF?IvmEy^tcn0 zTya}|Kjjc36Oo12+g5G$uKeWqQ`}J`$p0i)nQl%Ac!8>d4H*b36 zdQIuAQsgADmax)p|h)4x9;G6Hn;-0?E@17{kCR)Jo&Z$Jg^)0s<_)9nYW!2DGK6 zrRA+HExlo9|D^x7Ej|AOgiJ-b@5x~cne}ylzLIj#*0NLAH2_Wk82GHUSEg-z zaDNxQtaGmZa(}rSmm%y5i^eYgA2@Mk*H1DF0<#zYH4^FlFUw?4NXqdP9&HpAuVH)^ zmax(V;wLUB^M}1F;M?~;_$NvrFkrkbD_pO*I6^6HBYC+Yp_7u#Em;aj0))v(`47)~ z>1l1r4nm->KpWs5s7gkIbThl8$! zSS&Tiqv}C~*%l zZ|UiA9>^Y^tS85ZhjE>SGOn}+2M6guGPf25+H1l^&CgE=c1Tjxw0!-_1dj8p)PRM5 zLq1@Qjr}T~PfLr$NF)H_9V{%4aCmsxDB<1$v#kx=+=p7@IR)NFTstI_e`=rQzPeHY z%*|cF{V1uLuTM0It7w!T$lwssqTMAxLM5l6fhd>4j31$1Qu{Fq4U|Y@KBxW$l7XQN zjyo6MolDVGdE~BHUa~eEIzNUV6ddaazPJEEUa~jBz;IN0+D3_~GL|^F;W0^+jR#V> zbX$E9xZ9edV-CMpUyjN#vDwSrT7lyn6j3;zByU<0us%7EDFZFzin!)@eD&Q}aPc)^ zVS}A@c~GG`*85i939AyZo4KIm1Grx>SBOG?xIGRfdiXhDM$rQ_uAdVaa&;UK!=JW; zUSOpRze%=Wr3H9DKjaihw@gHvcL?wt7zIZ~MVWlhD=3ivTbYO=VV?U-X$;UC;EGO6 zBv7q|SsxQVjW76>sPc&%8=G25Vl);kx~5`PI^z4rL6m1^2jcA#FUTnfuEDkM0mBs# zvPgi5>hC8e`Y%$J78V_Oa&dbsAA79oN(xRZCgp3+YWco)GINM)709DR_R=YNt;|hP zt*E=tS#1CypdyEOI`w{M{(-Nn6{h96Jrnl>vp_k#jkmr;|7>@fsp_$HFeH(L+LJKu}a~uf)oy|a)QOhMT{M|QVN#i zK#a+KXJjz@Y@EB0)nmXx#l$3+pcuctZUE-#XrWL_aJ?W9u=KDP2@Q36-(2cgld9I$ zd^-Q8>=3xt6!X|{=Kqq2UH9j?ufzE6;gIsT+qsb?3&{ReRaG%EGfTkxybFB5b|L+U z?-^wAr-4I<=75i5)|Ki9#ZP)7iw{xkuXa4{*G-uGE6#E6h&jOG3*6AKFo)ADZ#*R; z#4jIR14?o7Du!Y!%+n(XMO#;il1o_sUo8RfmHXnu`;|$cF9F8L>gYYIFsd&NV)u+2 z1w|@^haayC6fm_gY%y%VPx>yB2}C{^TfNhVOYVT4>Uh|0<$KU^OM&CGngySppG`7ebG<^X?N&$D>~m!td( zb=HJn8*kqo5Cq8_@P5XN)uwklX?DMD7D4{heK6h-U8Iwo-)d0^NN0{Ni-1?Xd$`7&+NOfHn*FDuE?nkXykzZW+;;nI_Rd>R` zs9Dj=-EU%fGpMleoi^sv)eQU;*Z|ANv1n-h{+r^c1gku|`1@8|AklQL+u~aCHCyfI z=}fHpV)ActM!ZQlIR-#!FxJy1ps5#Dr@0$2=m(n(tp!lW+uKv_J=X7U`B5JvnmK-a zgv^;`5Gp6m8-bJ0Zn2HOgn?BK$)jd?1Oz1oD8WiUT!hH0Yih_xlFrWTRj_!^KmJF@*~T?T^?_X z4Y-gdTf;#v)ZWA<=3{^|W`EjxYcx~zNmj{pEsPOFmbuaqU9GpP%(hO=_fu_9Z{F9} z6;;1#1tNK0uf0PA z>@fQ;vvv$tp?sW=Iu9036z^=P@2VDX`L75(Y(;KYr48qft`y#X5i}cB?sq! zT>bFh4E#S>p0z38Fyj{%G?=zH>FE)9d3mS4R)R~7F|AtT|8f?Sk#T&cp;Uw-$u6rP zFP}F>mk1_Xa`N)veKY`HNpwbxAl}KzNm*rOX-P?VP!k}c!NI{;9vyTHxpj5dpV=Lh zkbQl?9o(lcEGdyspl|pZMgqMM7;D&5D&ldBsM!37%BvKVmHSeOU)meI6DSTkk(7z$ zkBkXa#l@J;$<$VdUlb#D-_jmfHI~q~r0eq&g#G&!Rq5)50qoBA)L=$Y2faBuv$XrZ zG&AEL7>Ee2wnd#556@SlF$IsjxEy@z@%6BzsljFbnu>e){r^rw>mnKcpra>z-kJ?W zHvXGh(}1Antl480u*DF2=hht>Cy*DGZz@_jS>8+`d^FfOFNZ-p~( z>wqWw>f!f3YK`$20$e#DH(kufJI1HC@<|PFCRK{E3fBE ziG$O%?4Psbs0%5va1to!yR_gHq!$)|wq;&tG^$+&k#dk>1a4K%JdagK>IIt@O2)-rJNoQNuJ zZth^D8`|k7L;_z!#lwRKv46UA0Ilc7-T78om5^@9iW{)D#h{h^W z)lK=z(pF#6uQV<698f;1GYDH{z`M-asF%~Xw0{Rn6u{yg@;>k_A9wOlSAT3g=SuFF zsOcU5K_nMlr2FzC$jCt{Fu>C49=`i3-Y!~P&-INB7*~FNjisgES21*V_ipQ}(3gQD)J5BR+KDwvCOJ8` zbv6Exr|$aLxuh)f&7Xru3icGqWMS;k@py`Hu)hngzKQxR6Uxt*m*0Pn34h$wZRi+@ zDi}6tjltgk2n1T->xC7Gmi*@}cuMqqO?XJijlkkwaC0w*H$u4$bgn%=)nJB~E(SIk zKkT1mo}AzOwIiI#geCPEPKlhc@t<9Jz~eO+X(G}Gp6g27E6jvG8{g1|mKG;|D<3WC zql)V89+pwn6{OJnK=p?&>Cqpdwf|L)akVknv%vuQ1kk^LgMEIUT-eiiIqQ+=HsqV# zdYQ*$H1c15-J#y7Q4hqm`-XM%?2yK@x}XsO@)gNi+UHS8(&AX~S0wfR`<2u+{Rmyt z^+S8Om`(On&pqz1FStTs2bgE*PYY7qrs~exfvZlXm*;uXqJ;w zWu^GxA{^IX+y4CB2z&};cb8$*M;09`LzyAM!^2~%{q?zWlhXvOF%{)8*#5%y#Y+g| zcUoYoIB#ofYyOBr9U9SpHBiV@eA2vWqn-qGpu&RHykch*|I^(@8?sRh7vSI|!;(vz z8d#f5B*i-=_o35$#@@Y0aAx94!WWT2(>0}cFt92{7EoM8sH^?wEG|67fFDbANR zS0GV(;98y@%*rQ;C~t4^fUxm{{W*?BhH{}6JJAm^@M$DM%{X+B#cO#AjytD|1cJdz z;SP563o`0f3_F?g=yO>n0fvaI{W#=}O4zlmc|ns9Bp9E){ceMW0zxG15-brfqIZZU z=UYIA@z^*+Dy0H>A4~35_I(weZB+Qb{#POM?ea@m_2HXijpCtmOiauqqoRt6leydF zaVCN~0YTF@Fh1a~AV5TIqA2#-fgd$if0eIiXIhA$^=|*ENcVlC)2M!OepNBuQBcGk zn1aVGL9IMwH&|Iv+$4|o;f}+Jsh*wmlw%SI0?(#)gTiIpO|fq(<@e^i zK5Vv$$Kn%5=>^qGv@>rteRu)C#!nUabxKwY9uml_MV@j*$9|-8qdmc{xIl!`BV<4xY?d-dg-=eLINEJ7)LF}o3 zLeXSQeU2+b#^g*hhWMB*i8+UoY<-7A&Aiqx(iB+1`#(io2UHWm)?SMAYNU6h zSZJa5ZfF7uC>^912~B~3gao7tNRuWY1dyVDAVmRb0s;a`mEJ{a=)L7_-aF_2b2jH} zHnYjh-1+Wz=iKjZ!a?qC zNj>SFg7O80(L}<7(u;_s2+f@G6Tdi+N!b_`!{!fu-04C4a5}(WY367PMj{y>ws6$O zGw=D|*AhUZB=fc0Gu|2wMJQWk(SYjH=#ad}lD0?Lj3`pJ`yBr$xmhI2^y%F!r)q_p zYIY2CjSf%roNsV%kyc8*{QIx#MQrX?;r_wmFYc$z=1tzykAxG9^nb_vpagn?F;E{!B;N02>n-`OJ(5p>q-xT1 z9^O-`A_DA{l;}a}ADD@=E^@J@9J$c`B=kK~Cq_F1(V-@2w@B~nY{Uz2%eGQGUVA7> z11{dR!$#iAVqP6v8R?Xski04cDIYg4ikA!kCzspj@*&@xPZ}t6?Fq={kJ(80?p#U$ zfRS690(aFtPiR~NbZ!d|5N%SILqNY7C3BqnX zi+if@;^kMX#sv{z%?-4aj+jg&~UO} zk8eEFH-+BM4ua5ZDWUi~eU?g^`|J_rhFoi5EkjRQ;k^N}tJ8JCq@|M4&dHW)N-rNJhF{E5fK`!=+3D(oPh3O)D1`ozCyY}`v6 zQ&y;X=bC+t;>Pk6ziqqMs7Oz6sU)*W488(GHrrO-)C1KDDmIfI1Oy-&V+^VN0!Z;? z4@MK+lRUra!AutjjrlbWGzg#Txs~*Mr?0F3P9LZx6D0c?D;m|kBMLNNfe3(C3r{L` zhc?Dou*t94!GbNu}`XyUP^o{Cf5H1XW=%xY*{?Bgna6Bba(99Ln?W zJ3W21NvSh;&B#jJTE8lTq5WsvsxA|-iOQ7gOF#+6AI0TW91Yg0iykdMXb+tQ0~uYM zQKhuz-y&KdxMf!m6oEFVG|!z3-*Yf}m110!xR07aUkX6pSv3PnOAsKss2j9GcU>qE zIdBkX*qYMIq|L&j%R=z$jDQn8>KqZ3z=eliD5vTp>zlur7{mwDt2YN*P%EHDF`0g#R~OYj{KB&7xac;x+(x(JfL? zngZr$j0fj|ys-srcf;-3w)s4!^97=GV6|JB0Nrt@`B&%!5z4kMaQ1Z|zIS7yH$mCCk6;yzX8H=E{U9QkedLQOxGkwx;JRE{`wb7H3{-B%R*VMyp;O% zue3Yq=5j)beZ~IP9|Eh8zU8jIBHAb?5hL<}nl|p(KS1at&A!ubH#emfSiNbi({n2q zWT2HngR7X|w2N9EEyzP=L2ev=Zh<$R{wVZ+V=a}Tx&Pku9O}BQ*B-VlbM^PZk%m5f zy__$mq9O&_s{3DAqFSvd`>3g(wM9sW#h0=|;#3l%y8ahTAldMd1nCN(?t=ic$4gC4 zh`|f?Ej{7z{n>l(+wwBNj?)5dAIMn$>t#S#n!7ZGWwWse8Owc2KMj zCcv+NC`O^KEr&q7N2RwY*Dz##yeX3@rv5Qiw}M94Or*0Ko0{O$ke3)pg(vuhi=q0( zyZ-KVpr!~#uNH>a%&qO>b#$a9bE!NBXUG0Eneq8;YW)d1F(t~)-ECwgA3j7C2cYw* z?G78oiL0yVoE1Q%piI-iM5CM!3Q3XPP7mY z$aKXj6SGN&JiS-IfL*X8D?05C^B*qzsl+;@WsW{=nt-+`E9iE+Is5oTz?kFU?ip6Ad3$YEM>b6Ps zt8^x90FFqir<2s_v7PxheoZiv9>4Q7qi8HN;ea3X97x3^DC?$XnJn zef$2I@PMySAZ>r1I@EXEu4N@`zmXduQM|@c< zLtq9QWvV&Fd?onbH?eq~8QwD@p_0;|XyNMnn;*4K*@)`k6p9?G)=n4xBr!So@p)r= zkWf;VLPJ$1-+R^U7@c?I9Te`(g*UQaPMD2X^<%3l2j29>-7}9?PBNgv3gE$aCauEn z5%Vja>5mmQ4YR1=xB)|K0GT&SJ{6AFMsxR7STzYx(9GnHxogT)~^_F#PvMmHe zylu+912WYXL+tnzCB2svG<64zvggyYf^1DgCJG|I)uX^`3 z(Ixt*x@cte$(ui{KJ(9cC@yMVZ7AS<=6pFyQ8bkx=iRXg7_{6f@jlgrX%%=m0tQUL z9!qAL(izi}{ePY@__9urbjg)*0Z0 z8EYBMA~bKgt1$pZfqOP$?(2>D{T`aSZE}xv69Mk`^X!v-utzo7rOb)^c{9ZHSAUp_ zZw_~4Mwz@NG&gf($Tx5$!|G^AsE&x+TG1=1k+z%4X*UT{pKw?TpT3!jJX~j1_MPTr zxjU{hac@Avqz}!KLec-B=>sa03&cg(wCfZ2l@+Ruv1qH&CeyTA&!y$b}xl|t)GF#sN=M35A~{TnGF<>w?SeFr?m>h^NymU;(i&# zrSFIO4O$e-9Q^EtJ6T{5DfeCRtMwIzmiwQ1my+xiyMiazdP5ccD89>7Gi)6D)`PA` z{oue(%!6pa6>W!P*uR`*_|E%L)jL|#kc*@pcH}eQ!S&DSF;hX)7@2tuiTdvcX0WbO zE)9*?ajt(|LtBGhUD%|mFl2wokejYY+JIXzOBTxD{By{+4`W~@EFA>}Mab?MlZNLB zJ~*=*B_3li(c>ig==;&sI)k}a@R$IQ^jHt|J3Td1uBVl@UX(M9y$f-f;lCgGR4Syw z9`qZ#`EwEg+HPA}0KQUTjg8#lWc-j5h>R8mo@|=!;&8Rf$ZpKF-iILJgRKW`b5LUc z+w*n$g7XEgQSI5rYYbFpO;$9Z3n)Z%BS7eD9PZv*g|6%2y@02Oz{7UB3^B!2P4_D@ z2fe#RT!0b5H0nDgB(gXePHGU4#RC$3KAwZX_D{Whrk82$MW@HT&+a zBCiZ_g(=d(T0tW1yq7CH_`No?)iN(DyoSAr!;32>Y)IC?ZzwLkLEmGF7S6*oQ@caF5L+DjGad!fYmBkgfv zd8yVl2RMLm@R9#kW4AN#d}P*A7zL|&>s|@JelAb~!L>A<$G|_wEdf_>d($zsDd<$C zs^g^E=Od zW(1BXcbYP;`SP=+&=??T7~nNpSB(8jDmchN!F!!%p;~5pY53EbBTlBxAE&b0sl~?m z!fUat>KJ!QMEd3!w+nwUc~)=?MIuSITlRUCFD@Lc8vUtlLoPQWsRR9zdSioV2!9&- zWhT1~n~D-Dk;)KD$d{o;y)iy9qXi}(9|dx-vT2O^yyYC$r?%d55+82~5am=vpkHC6 ziMxJydlt&MR>;4;;DEtu#+VLGR-9dkjE#*&7&i}zczN^7^UGDnJ#Cb+N1CJ$?-Y7( zc0`~NROoMGJaW%^<30CDny(1xfE$lvnUyb+|C|Bd&vOGPbn8$VTD;eZfQySLnVk`H zTlmY8lO3cLJH zs8s0!Le~Gn4$R`ZESRONlI-kxhO!a0qwZ1 zadO$E_Qgc3k=@CAMFiL&%G2bF9I>C&#ul8@^jJhZ+D>R0^(EIj=16`i2I=6|rUHh| ztLGGB#*jX@H{MpvG$CoxhX5b*E6itcuR?zzX!y^citjdF<(X(>1m&Y6jWva!d>YIU zO)?ZOA@}e&ZUP5CKplQ{!VR(cSk-vC>-)gnA=A@FBtD3?*cRa;>?$@Do6#IJ*e%4} z<>NTM*XtD^R@unqeyhN;U{Dntye%aqfSza!pik)9H7vk19c2z0hwjGc1OPTW_Um#g zy5gD7JI~v8BBG;RpH0e3uZ+h~h6V|vXj|E$W6a3~nnSs!>nSJfIl*Ji17;JemPtPK zUCDPR^_TP8=J+xF;CA*I#-x{@zNU3CO4?J!$80}ev ziwi{WTmy6?Oolz!XEeDe{aU!&(}RS)JdR>riOyBdRh~5Ch-}7r47je3fsa9~f36X! zv|dYI{d1O!HyA2+er?B@C1$0JM77%RbR_q&)mX%mY;@Psk9H=E(Q%bIv6pB5V7fZ+(%pSTGMS@8q|--@=S9ZH*;pO+GFsXJ5Vh<^16s}{s?sm~;<-`m1w8^gZp_wqR zlRfDlnv!jor#mhm{hgdRYkwLIJ6xUPo*`8%7ZS=R>&|nrXCc2WaK(Po?MzwllCcd? z6{Uat+Xx9%GF+6f#*>7#nVb`9)4Q&kGHpqif5RZI%f?thu)!A=feV|LH__vf{lNP4fh z_rp&sRGe)d5-0SUSOc;!aNKptf)-(52OZV7es4puT&R=uttV$6jx)B55gub+kA_{X zBV^-s;8C8>C|RgBx4bAgMxH_lqFrB>EVq1VgTZ&dfq%N%vLxvX<}}w4OOO=bF>@cG z>L9P;N6ztv_ZxUG=oVtvRzz$9YB_%Y+%VMCJSaTj62NzvaX<%SELaOd;QwCCWWbIn YNwP>;ttPP(ICenio`GhC`jfE#0El*#tpET3 diff --git a/icons/mob/species/vox/uniform.dmi b/icons/mob/species/vox/uniform.dmi index 6f050370f7cde8d0b2277a6c0689abadbce1a42d..3eb4fec6f6f1e6e7b6c81ebed244ce9288f34f53 100644 GIT binary patch delta 255191 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⋼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≪kJf+K|?7zLoW8~*f@?!-#y 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 SD?c6Y{vRkGm)ZKCQvR#O{G*b!H8eYVIZ;cCu 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

K$`hq(7oc?x#N5@&NndY-q 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

sj)+!3lSnJgl?{SqBDY#ji;; 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

}jv(RJU12w-gk;o)0JUd37d!v- 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 WJ?M>FT&;(I59KEsk1OTPg8v6?z-^fT diff --git a/icons/mob/uniform.dmi b/icons/mob/uniform.dmi index b67023a99357383f448812f43932563607529572..1e185efd9c5f712653b017a6248ead69986936ce 100644 GIT binary patch delta 25636 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 NHI>K8Ws0W3{|iBx#T5Vm delta 24367 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

3}_U!1PVEDzLwTa$gWz>?3XjP9RCKVl;|JQDwBe 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 delta 70351 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 AGynhq diff --git a/icons/mob/inhands/clothing_righthand.dmi b/icons/mob/inhands/clothing_righthand.dmi index 3b62b05b314f086978e1e43b3709c788e09d64ad..e4e9fc0f859e9a0edc21975315990a3ffe51685f 100644 GIT binary patch delta 70731 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 JQZ>5={|h|YH9`OY delta 70018 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 S*~JBc(9D|0=elS2x^ht!S`0+m-_H-% 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

h_+KT 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 DeR^X$ delta 245020 zcmY(qbyyXR7cD$Uw@7yi2uMjucPJ&GgfxhxbVv=-ogyXO-Q5k+-Q9WUj&I)I{q7(4 zc{nvQXXY?_&Dwjfy=FQVzd8^i!;jr^di#|#*` zQsr_gtrQa^rmMesHAoZRdHfn%S^s0vtEK2X>d!hQwIk^T%|{58Pg7P%HyR3Zc+dYt zQlBmlT3a7I@9q|Adzq6$rYFw#2K1ipZpvg2_b%M;7Iu0Ax;V329(x5Gg@wRW&%3=- z>fqbG+lz}Eb`t#Mr$;2^Y=aWcg4p)wwAKf&C*g^gpI){tb_+}ihno9w(u{}j=SZ;a z1I{_^-}hc>5_cR9`gr zFpxwn7?u!F)1pi*A4JTVBzO*@C!M2p-254G*Y7ov9%`pM^C}spJ4=&x>^SB5I>7e> zBRNHG(?D)~jm?c3PDBXvZSkmd5-UDu?Nqj6AK|BAhkO*?1+JO8jALr$Vf*&Ij66hS z^JIjDVp0K{eqH40ZuIbYu{Kgg)mA$A;wqi;u-$Z znUQ&7|L7~yej7~4k+E8mVWWi3j=h$q`68>`b+7!h-RvuGD*V1Jn>PwdlO)`Xsg~Ze zrBAD%u;g%6N{W(FYKpLsM?>6jd;}_DlPY4vC#~U0(ct$jHh>AGN`WW2d zC*R8%3G3~5?fkf2Zj9o~ftr~vnOOBjL!S}N*#&~d_DLGCV>oCDYR2SjxmW4Fbtb8G z5&tL?`(FG0=f8qJG~(NzB{6D)PR@8haVvhrXaMXcVvj@BLYbVqz}68pTqo?4-dG!l z?G|ghm=f4&EAy=TV)dOA^wJd$S#~EFX}i!lf6K|k!|(?KPaDQfViqODw}!DQ`3Uw@U1wy-8=B=O*nv&1Q5KUxl2U2a35u7*&YkzRNX3e(L!k zwS`jXUvZU)*pdTkR4x)2qe+86sJCqJpGko!_>W0pI^WSp>x;&!%woN&7mjZu%<_|q zRdQ_UI3(Bon6O#hhC#~mi?imJUFaGIt;(?_NJm3qh_X3oM@XuURakBY1r(h1A4N|& z-5BzcPdd~*rs@?lx6yOl9T~%$%4{_Qq|W&Sshe`Na2On!!^GY-&^4fR$VnHmOjJ~0 z?w&TUDZekDu;QUZI^^0;sF2U^d_79Z^{<1xT!75?lxznJc@1p-hzgP&o0}x4$oPzr zhNfw{@<#>}k{~AR;F0+8Qwyk@;x$yzUT2Ro`kM6l(Ge>9xkd#q9*E}zCya!@pbC)0 zx9juEMEExggRzKzkW6Hfs&J^lKPtfMPh_O?_h*$tb0|@wrisamv%Tg_}+$cj8{fx=JLBTp00m&agIDfQ(Xf6coVZ;GO!>Y={k zMWxqt{trvP9?*qhFb$b=f%UM^tQ7n`N)pxK1DoAXNQsmry}wGevlTmizYX`Cw!K3> zv?Gh3C+=}#Ecn+=5w-~MC9dhA&Cu9+~8c87#GS`JEt;~|aFZab~ zXObe?Jf#cd)W%u~qAIgFx+$9W`L*#Dn z!Z5u@H)-o|DKo#Slc!F5MaLR1a!Ep{P2)7#mTdHaE51M89(@>XN&ldJ!jXNq$o#A% z=5n(XnrAyNQ6p0bo(tY#2woM(b_gE(%6YXk>Z}#GkJk`d5IE%Q{oOQ~iDZ(01KX-W z*$=b$!14vL){J^SvVYdKpy*c1GeV7+#;_0MVV2S3YKD!QO7AYwJ~xU#{W z+}(YoN`3R}?XU&2q4TLXtUY$6`dLp=ndX-z5!)+HP)~uD+lW%pLGEX?Ih?s#{xnK& zUnW!qqLS}pS$+5Y<5?Hh`MuRP9@LI@xFNo(P!TzM`nL7g@mH;3Z#r@}X|VUr;bzKn ze2(s9dTyX`49)7bce5oP1&)pVElT~j7LL}!q$T%ohzs0gF zZF+2b?*rDeCn@)|qMLic6q+T$6rONRw;Ec)ooZQTa)! z2ccO#WrWfMCX(v;_;|2)V7)@|jJ|3z*vC1xKJ4>=RQrGgM*)Hn))|A(Hw;4K7ay?a zvx-Lv81e9wk4g8tD}osV`@Fr$_O+UT($Aoljw7PfqSOJJZeCfi3Z8A8)TgwnMLZnS zkEKK>hf>*&v%B4OA}VX1M-cluP=U{?R;yOo{$hbC*ih`r|~ zYs%rR{_D;W^J>uh{(<|qe1!BoV1@yjh;r~teGjPV=RgQQ>22JaIFq!(EL$Ugx|L}; zJG}4so_>?}ZFmC{cEZ8#>i=QlwfnshB5VIU_$u@`zSRXvX5)8leWNv*G7%Zh)-xf? z%gt^ZyJMNG2V>$zttLYxevK{KzyJI(yg6Cx>+gpH3M#>s`aPz;vfjUc$uKB|wWrq? z$djK}8id8%pX7wt->>m0UY)b?xXRXq<<_he2)8d?a3;uC=mo^f(@QBG3S{EPEd3g^ zzcoFJdj75!6M@Y+vUQyY*RvYX(*OP;+~#Vj677A!634uXb!Gn_$0O829gW-_0H6Zo zB*oOGf?dG`|K$>`>H<~R>2Qh6?ioXFtrnr(RNJnZhY}YT*8?jTYBs~IjA7mH6w6|C zI%Hc8_ia_BlRb;o7ufLcRuIkKx_`AIFS|gz9W2Xc6aBQnIiEM((fi6JJ|e89ruLj= z=62+YvopQcDN$19j55{n%p|?hG@5ABdvXO%dx##iZPdO?Hp`W=v}AdBc!-RQ1Vltc zG%b{3|NLRdc;7tm^70N8U;;x!LlvpdDZ`@s(9*_JTw>xkjqt?8 z#Qm3DEirdW$yRaNtsQgO&=HZ&981aKHb=ba6X}rZPk0VLmaORu6|YPr3Xx0X$F0mQ zgY%%pouRiDXN23?H5e&BH)7aV_R)}s7z2q+*!`wLw{QSHfcMAZZiTjv&fBV?x}uj2 zxhtciRD~@Jn=GRA(|^Yl#T4^3MQ|3YuWLy#W4EVeR02-s?|)qa*Rp>uOa||QR!v$K z8niVh?}&Ku8B15gh*)wnVPTDgXO4NBpfv8A=TQmZCSpNS0{?Tdl^q7s{k8B+ZPPN! zZkT)0-*071NI+8STN!=egy3jyN3hJ38E<3>`Nw(fRvX}Tr8L^rCCN%hhd{`xZK`UK zUsFQ>ogWlbR53$Cn%s#!IuQz?Vwj?i4tY3*Q1Do$j6~*GT7A7HfsU650X!0j0Z{3k z9;FCBL0b*OjvnBJQh!ZO+#xt`q$#O#4Y<^eHj;U|qIbqfwG;q|Z+DzwQ5f$z*19lU1Xhc5wL9Zz=3)jK{3occ!x!v!7=AGp629=#1RrC3j`R zM6yUmG8PQ$ThtK^Pmk2oM{~rA+r8!nu(fe|z2Xg%S|$caM&iH&>s6r5yr`|BHKmy* z9MR|WE8VxxFLD*IkO22v(SzstCDM{@pe@q6bGbTu@(Gzic-g$EcTf%e2w#Au%Z*On0(M=Bl z_&cX1_ZBGw29|6TCsUKRwxq4T`VM#N*H7fAuA5}!epo1`ZKopau<$FL%|1HSM^8`L z2LAXSUXgNUZ5tWGhL21<^+R?CHfa?RCnpYK>O)*`3z0>*_qh|9>kGR>iGYbEYa}RM zuiw563keHD1O#tZeLB7m-NRZ(;JkE8Wwf-k;)H!>LFDP@7ZD{pY-LChTXFW-op<{l z;?BGFsdX1{k<))q(^BVuGdtwoW9AgHb^Px%6Jyu#Y*K7j6Fm5JZnyTId(vw`-}ik) zTCF_yV4|PgwU{IWSnemNv_&QH)h z{S%#ev-IbhDr!~_fQ~yu<;!cEup!J*3>7$lgw|bOScC0CCKTJd_4XCe7Qf@wS{j{* zBR$CQg2OXMa4IQgAd*IF*)U2gf=CeLp!NP_+u^_UhOOOHzI$B+VSVFyK7A}}W2u=9 z?a`}BI#OfB%L{(azsjVQx3tkcZHVN!U;!64=Y12rNqRX(^6!4L2*$tL@ zW8gzjD{P4b5MhjWQ%^ILVgm#rh~|!kIB$>RRP;i*W!m>@;JZQ_Z{gs_Cnh-BV6W#e z@{RN`*U=JnCC$y5JZIAoIi6m9|COMOfh3_aK0eL|$SNu#0mddKhL`(O;?EOGB4Pb8 z4Di6&`FTH2@*SBWLCD<1oh(j&tGPf0p{$WMvPX4*;9%Y94k=j=Aau4;Nk zZM<#+?B=FqfW9&e(9Wwd`uFf$Ho}D@LGvFj9*=`<%_@^CI-o1q*(}%g>-!nN}9mlUgPgQ%>B;oV-xHL%8HD=f6mof5s7#!}ej>8G&KeQG}dVHhxe3GI5 z@pz!WulY$eWq-)y8LY2+8l!Uwk@|qUpIASpJs7BOP`~bri7YY>2Q0h3BVtZn#Y{c@ z_-d^s1&=PpW zCOU4K_X@%V+x~@J1Hiifd2en_9bC=4w4703VBqScIX4*=G6@Mun%dcYi^JZl!Nf9> zh04^P{V9u=`1r3c3e}i2SP0^Wwtw`l$?0XQIM%Vmwz#n@-)(ZHcWuY;W-b4Z9q8VI zZ^J%9FzI)HaU;)QD2{X9-wag;t*Z3Dv6IV)RWc{`(kAu*0761%YmdxYPSOuMj?_N> zF1%>1R%6#H{fD~F$Ddpua%9js#(nS4O)Zz3hzH`>dVNuGBYCI*=&g*o##_gu$uh0~ zz+ndeg5974;lH;@yw+E)+F--L)T8(5%|8)+H9Lgy=j`6wkU0IKCSUmbOP90kM`gaZ zqL0o|5Xr5oz#d>EjM~_3H^1PZT813#48z-mlIFn z40<(0oAnEi?>Y4K^@&PL`;T&4FAEmwHqXC42ZM^B7%Nv8N*|2Y|JaY28UFXr-1)Mo zh=p2O?qXr>VsApZa7v?0A5Pc(RBEu{(TT37cOn{BHZ-EQXu-NSgsmkI5uHG^_{#>g zDY>-^ab&u4g5IMqw>4#FoJic-0Oi1%jh)Lnte)?yWlZySbUY}?9{}$~Tb~^aJ;2~p zCAEwaEXKZfRIK~}>63L?*3OEyMd#kcR-pWDh_@Z{Fe!OnNkv-2KX0wCmA_SVHEqrr zzW^zxjiP@a)gC@0TILps>p?+YBsnh&xiuE}CUvTg4|sVxccVyLH)N&?Mm)>2-4&2k zzn?lP(MwVd%P9Q^2lSt>Hx{o@fZyf_BG;@x!N0%}5eqw!yb}RBabp8h)1)W8L;+Fd zDv^H`dqkA$hnc~+H9QG2l+U%7$j-A=h=2dk8=(Am)yK`tb6qS&X|)Q;`zay>+)Ci@ z2S5NA^!yP$aGKWL`&?~wWgqi00T1|`N9o=9V8HbI$ppLY`$OblhCdRxtZij#>h*M^ z0rdjR5B*rEOon8!H9W&2nR z3xWcI{$>uM$()>t1U^9cC`p=3Ta=OPiG$B++MD(#{n`yBpKkW3V1Nw?@7N!>-g{Jhks;M@>^Ty0pCVtlq)FBMDtce^}ZcaRT@8 zQ;#N@(Sj4%EleooE(K4Yn2qXqTW2$O^EZa|`+~Rwp8vkS{N2Y1m{!j#7SE8&_Z9$E z!#`%!mO@YJ*a0s+cJTHU1)+b=zDe9=Xh1ec`(i#*StF59+`e1MVz9=7QwG*�yRv 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

F%$vhZcKkPdIr< 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 z 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 LD@zqinE3x6oisem diff --git a/icons/mob/species/grey/uniform.dmi b/icons/mob/species/grey/uniform.dmi index c956187ffa10889df17b2ea8cc7b364d40316a35..1636097f72e57e42751cad3d98459b37b11a087d 100644 GIT binary patch delta 22444 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 YNwP>;ttPP(ICenio`GhC`jfE#0El*#tpET3 delta 23680 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 afNO$?7~)33zqVZf^m(GHtx~CE8T3E?i@`Pk diff --git a/icons/mob/species/vox/uniform.dmi b/icons/mob/species/vox/uniform.dmi index 3eb4fec6f6f1e6e7b6c81ebed244ce9288f34f53..6f050370f7cde8d0b2277a6c0689abadbce1a42d 100644 GIT binary patch delta 253859 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⋼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≪kJf+K|?7zLoW8~*f@?!-#y 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 SD?c6Y{vRkGm)ZKCQvR#O{G*b!H8eYVIZ;cCu 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

K$`hq(7oc?x#N5@&NndY-q zoenBUY^7o;U^LVlp)_uXsD{|L1GvqzhDo&l{@uO`xjL3ZD!PvvzJ6>IQhK%OIe8*} zf>Z?WDcxssw5H*f<#dx18a#6#yD`m67ycc)%`<46Z$~A*k{Vg3tl;>?tW=y$655P? z`X2_AwIAXG)NC)H2q;&5%_df_j_l#QN_d(cjWP5a?0miSgq3e)P*rd~Kw=MG;QaJj z+cp{Gnxh<7W6Z-6VNO&wl65n+JFcoT1<5&hd10ZCy#&{Fb*gs(J2ci^EKz;1PE1Mu zko6=r{sP8LEB`;1?Z4PIE6JrVJrDw!xy6PFfr*HmD_s&&Qs>jwzh)I=K(QA_OhUgx~8u03$RXNu=t_YftN8t9qvX1{-2*xikF>%5duKdVH z8ZYYmh`Yc~+u9O}NDJw3ObYVwutXH_fin(&4-0^Qqb78$?r;)yYFt=j_=%-ITz*y( zq(1DkXe1_CE!tSy(~loOdUpyNrkdK=EDkp{eEcm=0)gt8m{7o>9HD2klo6tl{=71* zEH`2uSc0j!9{N(rmepqBA{y%c^<|q76jU2!(L38LdZfJhy5wU1j!z^)q=JHAVDt_I z6{K2Su7c74RCdEWSP0cX3|fLPiHbg`-lQ4VV?z4qnb*7dWBP@L8Oi#&r7X;9Nrzg` z&gRNK3Q_c-+@XIfRvE--^n{+N^FlKS z_13iAG(Y?{Hly7;l4vw6iI&I{Y&t{vh%75L))vRt*FMwVKL%anfv18nOVrs9nQfkt zqlh%|HEx}M<(`69nP=GuWhrsl`L0=P+A_*U(T)T;T&4=DJfODF&hR~l@x1dL=C6Oc zLhco2gs?7oaujgyZ-4yK=>qtZ!{Ql2Cd2woj$j|gxbl0@rKc^34y^g26H_@uQ7wbi zD-^h-WKj$|=JuJjz;Qo;IS$&{?zrtjCF2vtREg`AIs~tnSY`2v&Gza28n{5%U9^Js zbiY}|j)FcD^3wvhZza%{a;Vc+lLTrk2uFV?L3E99sf>UDki^K=EONW_B@-81s9J|i z+BGN_Wh35#F<-qNN)BB537c-nXjj^xfeF@>@#;?|4vhilK2TqXw(9|bGkC$qo_Xxs zvSWQmO>qWMj#js;N^gAuEDqLpK!$kO9CAwbA8|t5V08S0gVXEZPjHh(3Zlqu}t*nd3hUtS{2*9;rEooAeBw}_;|}NhF<&&n`sDwlwpQn zJjgl>%GV2*iMTr#@ z={Lx2a=!%H+&=Vn>R)Jn{s?WowpBqgbN^$S23-gtxi|;Ej_&=h2H!KZ>*h;x2R2+S z2;ppOAg=eYI9r$!GX$$r+U+&u+Pl|`4E?{a5McAue?};ISY)o?#gI4-1W$x}q;1~d z)XIvO99%=`L_hTVHFC=+U~Ze(cw$9+!Jl-0PF#CgHFiCDP$sf}8)f~aV94`&_B1b3 z0X-nBfAZ?p$3M4NvSf@Hpxl7`MURo7KP0(wTBe5L57_Wd%X9jLD=Kpt3o%uJIi~E* z=z5bLiH>@uy*^{3}}bq@5euNy}Knzgg}k}^o{O2)h?jqd9tydS=P z-jewEg$M2*=nxIC7gVGW)Yv4y(y|@r7;`d{7!@j;ipL32KQ9J z{00gF;falRyNS($WKG>D_2SQ0Kt$ zI(ayXRvsk!VAMbYjGsldWcNe%r7v$r!ZE45VNC=V4c3+(cTYByFX=0K>yUxCrb0=t zj-`E1(Zs-St^gGa%*NvbgwSGm!7IupMX)$=;99q4S4=$-(c@VR4?&^69KBhb_+$f0 zD=x8KF<PB*-pypftW({wAwuFnd^5x*Kk zVDVR+L@Ke^=n9P|pd#T^Oy?NuC&01$Iet;=b7g0h<^S9tbU)PM;foA=YL#h^cbnaP zNQZoYs00N`AOeTA8NB+#HF)h&G6Io1XlNw$)gW4S;VC5&z?=jzIC@o5=n-j&g*;e+ z=R1HDin~G`P)3$6q-*w-(c?c-DgYbjWp-{!`X`+ADlFi$BUYD`Z+wd0;m}q}KgFd zLAkHjYZMe6sdOTwspQ^g_?bW8J6;vSNv2+jd=ffZ@E(LB_{y4`m_Z&B; zD5 zRUrY-R@l(<)3DWC_iO>wCm*)S(=qY@dk{bz34sb#PdI*<^>3#+DBM(VvJmPu=T;hB zBNPC*awPGE|Mktp$d!*k!nfv*UC-fLQc~jemN1s{P@NeO;6HNSiZ0-U7VL#Z=>u7h zmyEVT{QJ__7RfV zuT4FRrw7>Qt}lM&9P|kQs>dY1)EZ4v_I*4I=lHNHtn44f!EFRg+{ckpipQR>L-lmz< z*J2*0t1*PHnzo8XVDaifKH;1v?8gTPGd4QsXIijkOjf2-)mLrW?KBll$hnpGz;74Z z@$t22$F@kd*_|Sn#QW~wONC}jzZsowFKyMiBf2){A4ZbfOf0+LA)<;P0lKM!{ z6H9ekg_^S&d2N6Q$MZFCfOH-GgXzC?_TJuJuZ>W`>MqXYPoluIun^C>XnkgA096v( z8nd#$l3&T+HD3VAi`!G-sk7#KTK*DFmbGDpiO z^Zo|0#EN&;!zt7lQ>F!!$ON|!HBd@pefJ$3a(U@!Tv3zX3&Dr~c z&J14$eoGGkFFu;_r5E&C)hXQFV8u4;KDefe1=;`;55dq&`9wD~h=>VBlM88cN$GA@ zNrC43M_0|d`y7O>;X)?h`ywU%?dSVU3r^Lluizv`2)&hQaV0rvs1v_hWC`*hlDw(T zdHpcTr_}N2s1#oRot&*cJ>CD2P4_nG^l-GfYH1_>8Z9Y+>iF!2CX*SZ*A>AS=DIp=7?rv%6F6rFg-LKB z5K!nD@1O;wu@hUx>^urnBVYLcdt0C-h$F7Z{<;aBz}o!panMA`R$J|$&f<9`$v5X- zBASu9WM5C&`HhCNMCOlRHAZ=396ur?xs9^=hZ5)J=I8N&v;b1|94dEOaO_+8_I5l# z=CyI^3)gUD=;+0&QjLq|MI1%=Y*$*S<#2@Vs;sG7|%lwnZ7r2 zl>Vu^#UM3r5l|!O79(KP9<_fWv<^h*e2$5gRmm3j;sa&FLl{2250~Lx)vP@TO&JSO zTEidiMd#&H*nB0bjkRG%Hhf)kBwKMcq#r9H3B-9&bcIAdxOuD2Z$7a_?Y-Vs*WSLZ zZvNh3rH3&g9i68)_z4I1{adIGN3FxR>-YNe!C|9+sfC6%J?^(CdSl0=#-4Liy%-~t z`hF3eP>^Hk-+NB~boKE8oZTCY?QBhiD0|N=sz&Ob0;gn@g*5&0S4!PKp`woVfD(yn zK^R$WV&oW8URl$xvDZB9w6)o8JZQW~L83E#YI<)aNgpq?^G{$5Iyu$lCNL`M+Wfy) zN-2P6H*?2Clq|vFd%bM8IY?5pu?%vK<~BvCxvXh6J3|%U^FPh=GS_+!{SyjG}UXYsP9j>s1mHdX2P$urP1Zs%MvS^E#rD!O32bRL{7S_(zwa1{@CQHW5 zZ3fbyV8toauSKkO)1DGS&1Qdyu(zj2{#!~!BrZyx@}^IdJ+(`#i6Qk^$(Is>=Bo zPP}XRqwP~NgG5B+;8a8~W`eXSZ-(@DHJtnWBUDZ!h%P1thPM|va~x;n9tl4{{=0W9 zfRLgXUFM7WZP)`F9ZQ-zEw%_7Hrj4%QpPTg zl5g!c3gfTq&s+CTq8)SLAo|L|#bNS2m1z*>6rlFn6ivrcz2 z2!5|yn%8hXt;ZUPYx~5v=J4qgrn>u@3uw&oSYuD@Rpi0b7t`Ojrvg|BWO7x*bV$9L zvr?}m&JR*KTvFJl=O1Xj@+V|zR9_H!umV&B8qUOK0Y;r-thkd_)m_2&pI=h1O;oa< zg;{-qQ=v2( z%UKxo|8w5MtbsE8_NWA_J+c;;Lk#Y0)$^oIkrb-MZ(X8@`oUA=^(p<~zIHAzfeg-R zS7XiA992`M-$Nob_4dWy{osXXi)O^3E^9c#Lg70dQkGCV+rLNn;qXflUh9(M`>u#> zoE*|tewV*f@XMyPl`Loh8cZYG5KV3< znLXBWUfh|<95Ez=#)EmNBdlj;@BH}Wq)j3d$7&=S&G?nX4fmKyQzZ^EYW2=j>xa`k z3%QToc3180BGqCc>^?J?V2v2C8Y9_AQmtP@#TUiACrSnHrm5jgtDzXUB1@5w@BAi%g>~9Vkhm3|KCe`X05=vuvo+6&z z{q(d&u2WQ*!-S`d2G;=sx3-vmj?nZhTivGM6J1jmhKE@3?79imn(>d@F0!I=$w~Ec z=MH5|Q&x#Yl}^i081XHeL3i48jm};n82*|W3?MQbyhh(|Kp`(*auuJ^J{_x+>0;!} z&6Ps!Mr&3B{`SEW_Frs+6k~O@z6}&Bh?N{tE^Z$c%~JUIm0~Z_;?luD2idjQ=nlGH zUYgj#};yTqKGOvzAqHAFdm*+}U2H-K{l0|@ox%#2x4Vc}In zNy!uTOkcy;@}n2N!Pm`YFt!Q!<#$?x#psVQ~oY+g@>+jy=CmUpH% z@X5r%|E^R(t|+7|F7^8WRa6Z|WkpnX3ro!b?<4D7O={}uHfm*Eos~fdLsZ}fx}5AP zF2?vuofO-m=}GWoLj{FowyJm%Ui&VTUVg&*>Z&Q986D7^@=NyZ`CbpyO~7%dS5`yM zk)VLvc+p48HlR0(C)zsyH6L5Hq&DA5V$*stw<*}+NG}=AgR^{WT+rbR74{>DOA>DI zs2BRUoEiM_)Icy2-CJ%A{qi4}m)xfh=D*ae_vJ-UocfN~ZZz$GP*RGppPZg{{|ft< znouL$KXx-+)%kMr%_KDJJ_wRDI~|IRT0q88^E+Jh4ACFURhYQy{_XY$R4iW67d%MP zY^TyCL@yfdE=d~0QUyyf(9v6WCJN)O4>>YKeaR6J5Poj!?in?G8hUv%tx9jcIhgA4 za+i`jGx^)F30NUc8*UGiUvSQ0nsCV;4r2`rB&T!W#rTA*(E-)0i^z~N8HhU=qxNlB ztK<{-uJmDJ*uToL)tftoozS*XV8Ulb0QbLN=Q>#R3Euu&4st+q;|c5Zv8QMlmCuRK!8*} z|0%spWb0=nG?4C9)zCmL(~KCKpPkAR(rXdnR7&T=E`G4L{aHv}5En(kFXRBNb=v6^ zE%v z&9`eRx?o8S=?)N&o5UWp{Y37Wy{W4tN}%jet)BiV?JYxWsuQqg@2mGG{B5*Jk&fLg z^qUAhW)({tc(ttdT?{=OlclxxEBm(BqP{ocd1JKCp0%%vfPWt-q`QY0x=Hh~ zcI;a-`q)&zr_0M^;lI8ml5!YB5GQ^~svXORuI{K}yaR7}!MBNSZPoRgoC=BhtFPJt zZqI1Me?{BZ-DmX5QYmgmw2Y!Wuo*x_*x)W z$)-GiutI0Z&JfhO^=-b4#lMSQ73LAx`yN6dd1DI%Z9;sqQt&pp zIAy1W`nbfJB6y5+M5m3CA@#Fo^j$l@5)x8(=gks58A|Cxam@}F8p4cvEX|=`8(gRergH) z*d_Skh&rkgGGSq7M=x3RtyT^C{r;_^hOZqCgTOG|7RYP?4_R%O~B$iQ>k8oQYpe2BoosY^Z?yVaC-r5(}wN90#qWewwM|H1@hQR#{nD?g=MM z!P}g^MOSPN3!^h!yF<|NW0n^7;=W^HJ~7{=`DW4w!GHILaSUQoyiAB$uU;z2~`R(iX6m&+!e6O?sBXVBV2Hrt* zw>3|Ao~T~v|7Pc92=?Mc9dd&yo@C=l_d>T&1mOKXEIct+zklW4;5t++q7F)S@&5Gj zB-{*+;#9hP&6c%&D^FB#Z6f0D6CxF`x%?#Py!Ce_XMIJ@_1_6?a1@}{R?{x3?>Pgf}6be=(N z7e9TSTiPkRI}7-JUKHE=_02{ESuV?+=bvr_;7BNiwj`zB{X{*f9}uDZWafZmJHXtP zyx^-{dY;)vQho8I=Qk|kE>-KD64ZvpMRnf1|15{S;kHtH?={j|CR(T2Qhu&fFd>da z$;&8dobU8BVt#V3a4OhJj{=c|swODkpHz58FL@09JukeC-^W`{=F0QA( zKF|>qETSrg!_qn2mH6H%<32sS^W^B0GlD=sb+><}ol!SF_^P_(MV|1hUG&uqi3H1PfeXQcf@9gHXB>+`;V z#RKl}T#?(B5At|#b>dkn=b%q`4U^amo+QuWfuC38oG!7uEoy)2o$39qH%RISLyia7 zTa3XV4DPZy;MdZp2}3z8!u4Go{&jwR`7@mZz!RlbI)4Owq}QHiVN)WuF2FpveB>7D z%J2J`2_H6xsb=6OaEJJ2eex+OdO4LmY^7uca3jNEePkV?xV|pFr-I4b3R~4OX%@S{{t9m?d3<)ec7dLkeUFYw@IR>g36pn=>rER3cvDS%#ib$K_rI^J<(9wMwsNj+@f~n9!v~ zUpmG|?&^9Tot%Qu1;(!$r)90uZ`e+Y%UDPns*N-H*3cNyGuy(hEjx+-U46#jAKKQ+ z432{?zMy+cg1=4#1_j?o6VTn6po3n1f7abOZ>HeSq?eUGxLte+uJgRjGrDIFQSu|v zfSRIXCeonlOkJo(m{d*$%s3l^F2Q=)+f1`%4+H;XYZdnt{R5Kilv!)hq%7S&d zOfEDRn~{g}HhmxGVO3e;#<4kr=<(e@{!Q)+xLk2)mTG_g{ae_6_Gj=;(VLUK8w|ZV zdkI5BYOsSYjpNcH>e(Y1(YrIRW`P^!N6FT=pdEilpLYL@>Rhgzt~ehG;_>1$1|?Ykb)Gj9Rnu3lu{`aP1pKK& zm26_!HPck0uivyV-PYM{Xh;Q>Qu>9|H&KQ}Q-M1MruO)JE z?&l_#cTg8`tF1M?c)KuL6;Q63#YJm#L{JPX+hEzb*n{4GyB^PPBTBpBx@z#u`3+05 z(NqNoN_zUT0?Q$%n4MrCHqxD%cG0idNRuGMGNU)uQ&K7PlHIVAnM9HP9_C__bo|g2 zK|G+tbq`f}^62t$3@+G}!(h|^SSgbSqMk&L7Q|q555+Z<0Kf)~dB_1Gx9gs&K$QRr zdJ!JX1$Z9Jee#~kyIWap211pwDU=qOqbUvrQ1YIRU$d%fN8>`6@}oPhujEmT zaSFb$_O%pRyHlg$G5W&mckfJE777Z=F$9I`n#GIyI4*+OSuc{6RnpTLd*6q1gJ?_e z!H;ahsF>Q*4ZDXW`?I@9u+A{u6G#Le#-`LkJb`51g~irU+Y0J4y7L&9X-GRB$4Y4_ z_mP9l@xQ6dizOFvv23lG-pj8mrZ!YER6H+kFv&cTC|m=+`nqkq0=MyR6&!-qQ5fA% zs$kI7kQvD`Q8S_?HGQdk+&0dlxztJpIOe~!H(xBZc;M@79{pzd5lZRibpy{LTqU$^ zmt0Q6YSz7iYUDDKSss`?;fBI`-@BeFyKt^<1avL5}Rf4}O3KS~jVmR=8IQsK=s_DuH zM^s?WWo|l^b9cE^6AlcuPj|9rZl<^wXgd*XMU{ZOXQuXDcr1x#GH<|V)jUc^S?*lDc|@bUT8b zB8Ks9!bDp$DR@PV;ufPXEW*qdiagovCs`L763|_F(~596M~~>PKm8Zw3kGLh4rtOc z;o9ou(qj=NaBmlbPFY_Xw;0s$O*H|3^FF!Tb2$EkW$}bd5a84C|Wu9ap6z?oj`{OkLWv%x(`BtyU zh9qYzdA>hOuvTFhY&k+r2st*ZKk~G>?Q7IQ(;b6xCC zq>UhEMTDRSjJ8AQI?tG)sTmnq$+I6kSca#6fj5bah9*@w(dUO>r<%(M0vW)sSrtyY zRgQ{D<+d^fCgG?^Inl?j%SF94HWSAuJv2zhWJ3^`eWc? zOCRJbE}R^jL&Srt5Jh34t5!-6?=5epoxypCU@^T%3a5_IkuFA9KtA&GIV?ugM^+SS z!-0s1$g#3W!|He;%M7E}BNh$=v~$AueItsI5Zhx_DhTKgRe2>04lx;_bs>ymme6=b zgeuyC3S|=Yek9~Dn_BL3Y>xz&tNTIo89ea=Jfa|wY-h;NZa{nI7peliC6KSr-Z?hk z$|uj*s`1Sne#@=xY{OiUHdR{ngn0X+YcrBdG{UnBpA;ylKP<>ALiJDUWI~(UaEsn4i{@}C)>{bY^ZdrG6 zzdWUg7oobVYaBUIFd*zdyS$Kz*2RWGdj4t?yeU~Wd+^Z+3R&$>WM2ITD0{C63IG0! zf3A4;a?*zra*Xzu+n$um>!P#ua{&|GA^eUhd_;KQ?9TnDDQWWl{s`hvz@%&p{nHgu z)Z2?^W@^)3?;xZY?eL@(itFoO5|HsjAf)FoHK-|}KZTsuF&&3sx#?y#zbOd`@`T+; zMlOaL;;-_+PttRd@%1@o(7#A$RE(W5smQg2NF9zWSnEFNedX8W z@oIG)@MS@AotRaZVYeEFp4Zc!#0M#wvJEu~ayK|q_}+~MvFAx&gm5^_@kChAK<}!g zl<@R!#1)W25=Ha9bf`SHGTl3OfCN9IrG-og=JXxtwTiI27=r)8f<91c zxuvCwJXw&xL;TBb^<-`zRt>rAM-4{Tjk+R9ED<1v-kyxlyn-ZL1!*`9c~x;paikjX z@lAfwu3frI?T!eomdpG&^3N+;9)y%L zg-{?Kt6!M@3homrM)T2%^w98<(X9TcfFVN8j+SXa`1oTHhU7WI-^pTiAng#y*Th5} zpTlJ^g$U?p|GRFDG6#g`t7qXv%&J;ifH{Xj(c@AUDomZeZ3C2J{UMkpQnWXsK`0j;53sLeOA`NdU#1U3NmY7Us z8&ETPw2CvyBj(|6#WAYUqqzJGXUA`vLh0yZ{x^&~jaMgJdC2}TrebuffmW zshs_`Zs2Qq@)nHGL2UE)#$9Qe?TbYDL5XUKc0^kUP_pJz8rAVt;VQ-$q`LfitSbks6UyDAWm zOf5@#Vo(7{;I@B-Lg{6rX+a80Dg+B3`zbL)R#P?>85gpRjg5fIIyy5MX3>tSreQQQ z3W|1%7tzu~4Uq)aOgzt`DO`C^%^r8N$6aD#~Cn|z^lf!~wtiQnPn zU0`EMVjVVQ)b2`KU!dMVzpz~!q19Xz(kexN)B#Kai`P)NIujfPsPiYyAu8>z=j>b) zzU2?n<91gJogO>6r;$&$Cf1Z=V(4=>Lp6VNl^X!i7bs0@O+vvn9smCJ&(FUS{~=>4 z?Pk~j-`MYmo4r2*y_{P{B`t0{&AH4(#L~Xr+UET4X!jT#i_$wf-;uwYBvSXpa+k|PGiNTi$fj9=XDe8suIFR* zwdA=&*R*my!YPx<1QPz$V84e4Fz6y|^54BoUgO32QEg`Y%6nG5fLhU{DL(~eEiR6XKp@O zai0@7IR|S-QqSGDHg(NgAgzhaT9vQ)Ntnz`KSb*wCsCrQ>g%&8tEBO~<;mBUz^-_k zkTzf#9ueW3k!s2vN^H+q-Sa>chqQRSyPqmhW6YgiA z#0U-8>Qao{8c4zN!r@W7{2XJ$bE^;gqk`*684~N_{NShUN>D0wCn}qJQZmA}`VAiP z35qxm^?1m4F^s>{Ksr_45wuRPT34+u3dWC#Q*HYs~vm;?BNA zNqAVlU|cR}-@C-=T$PnE{^!8To$!F3;G}Lc_0`$qe(5tb#V7qqhztEApA6)z4+LXyI)ej^@@9_TlJae{0h{C*wYyiOcFvS7 zt30eHl8ip-0cc$l5bs~*%2eJuZjrF_Epf0*3x<7U@qan-y}TTUhdv}t)kVB^b0j;a zu~7)hqVu!!8DFU65H7!wdO@h*-WpIb(rRjIt{A0)w~{=P*4N-M0=84`+G6>GXKTuGc+{@FSxsLt0i)8-?sh`w z8Vs{$k-eYvLiYzO&k1~AI%gTmy?<3@84vCIVCvviBUAG3rKR!rSBYV#XQt(#1X%^> zOzlAhIXP=*=R<2R1yMaXLA>%Ms@@&K=zMh3k>hN{8%JxbT#l~pcwaGlh%pznArP)p z;jLH+DJKFYNMlCoLn^@R^>aS#+&Y__OK`1j8?28844L0eT+;Wee(|+>lDex0bp>L^ zAR>g(0xu-gskN%)J)m_RDzib0d+&|a(k7OJ!_BVPv)DCX_ozs37?G|LGwyWOM`cu;rEx^6Lm`$pyNwd`pBcL3u-77-SRgvH`?0{ zigu_M56>&mY`q|iYk|)I6m&Sbd<1dlB0rm*@{XJtCStNDOW7Xoq>?jpycx=VvHF2$ zdPWZ@Dm(=!uuXMSa9JInQWdoTEk#H~^kqX9B%ndx_ovlRSM6=R9!YphB(;bx%RqdZ zYH6-+p>k&X_E;W54Noy;$oWvS;J|8}EOiynec6D^_K2Wv_Jx5>X{}G;DOK4r_24bc zxDByj1}T8nxGeyMIBf>Asi%uF6yzSo;iVntwsg!F1c=pbRh!cNkd99pw| z4w$mi)ZPP?nfj4m+734B>%jJ6EVm<1_iw5Qw03Ql0q`@F2NzUXWyPM8ju!eu5*72Q zEQwLL65-*s80u;~=`{NvMN38U$gpDw76mtI;2B)zYwlC{oX?vi{MZ*HnlucLq6Lj3 zXo_`jM_F5@h&EDr=&r#y5jj#O{5O7)@EY~3?U3J1z{Qj#o;ou~=|>A=sn@dbu_E0k zxZd*%;U&eBx%SXv%lZ2}@W<#&$j9dp$cUch=UC`wK`Ze9laOJuG zef|AUokU;0I|X4$G`7er#QmjaiiU=QP{ZlxazLZ@_V=59v*RFFbL26-f^NpOKYHy4 z`OjXUVfFZ&zt;ZdM-bwY_Xl$Ex3jr(WQLO;?c%Jsjo|MV+J^s1fg}BUMS>dU+BbP) zHNA-O7wawz>64cT9I5-EH&l4UOLh#9<1ty9w&i6S-A82EX=^1pf?PjQ zaM~#2ubL68J;wTX4ttEP;-`2EW%Am_@?`phWhQj&9{bkAvxdhgpcn&ERb9yLzAXTjZ4HvTybWzFNr?+ ze!_d+7956->)+FHZ$7^T;sv1zH%i-po$xtf-k4C5-))E zZ@Lq#4a^x9WA5|95tAzV&mnc`1W3kw&k}1|BFjJ_%^z)$MbhOl22=cJ0E^Hi}*x-^r8G@;?YCf5+s(R+z3B`b1 zUEP|^HXRu+1@Nnxa4g=bh#r%kuCY%Q_7*>e6LRUP$FxyBlK-0L_mLXhpS+^1_hnBI zkjbEWYnUSVqsymp89Yjrv+Ao>m{uWI!>I>*wKkyx+T*Di9Pxbc~^IZi!;-||kJbbcv_hwjX#Tv(#mpvC@Wd%pV z)d)|Q&!N48gFI!{yjcIVhde+w5Euu}BB)^ozPg5!+VlG13^u>_Uij)gTH2t>0}7B6 z#@m>iSqhFY@ePaE)9gO3Ck1%7KCU*z{vAJhK6g)!9c6)Z)LSKDAtfWKSscs@WdU)v z;XX6!o-RR8^P?anP@G&(OjiR@pdwM`%U@pLBm`+O!>hS8@i)!dXb^Ccx z?4(CoSh#>@+X0q$TOVd6}Yu<>Ed$c#J4@4_K zmLz~6byG?F$xcOpq_}T*T0&3c&Toa6!%{oMT9?NydkdT*YDGTbk*I45gzrK_(|?du zlO_d2Wp*h_@b^DaKyvUAvzmov>h66(_z1L`yjao^MR2L}^ENY0MOYiWAEHc>Bs54b z?C>FvGa6gC(ltWqv|=$Qj;TI($n~U^c57`!MWq!T#JwJv6-K~Khc?BTZzkuJ)Xcg? z{dvCpK%G&a3<5Om%OfNRetx50r)ZzGs@n%B48&^h{;I`Mc*I0~d5`5{JNwK*PH1O5 zAJdVaV(DlL5y~wVC_+IJT2R2KD9DwnBMkDRZJ zl|-j<7|%S^Z~8o-_bnW`{+)lB%&pSpBfiQ%cN3xlq(GnqJJ^0~ zWa*$PkKz0Qhbj=~(jP+|wbr^_{-sv^=eP7&82!gF7`#xC5b4tACFSQ3|Ll_AKyZxj z`>?gKcnJ>9Px*~^02;=nLPv|DD#_eBi^$sEi?N?E3(x1lh(`PlCRZ2Th9KnCDJN%^81A`*F-KlPP8RaQm%tpNVHYuPjbaT}@)7v~ zr)a9z-B5ioK0eLTw!)}C+gqn)K7!TXj9UEALI2aeTwE%lAkj!&glu8YrC81NV&z<<&}_Q5uOfEN-h2%P>94e?eifk*wQOE1J!>BFh{6Xe(JTK37;mAO zDTh^bF{eN9h)h>ygolc7T}@313yKTBK$S+dKz7{E{`-FX*RMfsfoOS7%dq+l;)$b` zsUKQ#$;nn+sM_jI%B`_!9G0ijIPsIWdP%%fiHNgy9QJ?6A;vfN?2@71WL*1BB|r9r zE`92KAy*f|Xem(|!R+Pc+))`;yMB@a#ZR<)4iSI(QpBj#+#P{ok#9=FQ*#yUfX12N z+r5opa}4`vGBI=I9-Q_DAr>ZFR**NiS2a5FuIMehb{fujg%GR1;pVya_Iw~toi%c+ z<8g=Ot3lm&4uX#v7gGnAHg3bG%SHFK9%t_qN;pl=0bNy96*1!g0NZtrY>i~gf|iWj zzTT>*8-J?Iev&?W{`|SXr+v~#?+lNSaPk6I0Nqv;`R1ckvpC+z6IP9iAzhPykcf%t=aesEb|~7F)C{E z@Z-@@;%pH3s9{#vh}p%Za7+HJmwb@7?qxi#n6aRCorkV%*DDfqL~czmaW&$7zWBr+#UN4B(bhq zMacxt7z!dRVOP;_mA7*MLT9-R*w3q2zC z{O+LjS2{S>bpxQ>=y=h6836*jJ$%i1+U@1`BuO<-R$0leh|g}qrcv*iPJtcV_{a!=S%X^W-{DPa zsMPT1>hpX#?`G9~GKtcfvSo*^c5)IDlKT33U^j+kZv@z?5d^>n05f-K#4j;Y|Ak&6 z1^?`9k5eZO@;K)^CK`7qoF-75 z95FPz?0QEdUQL{qj*sO3^Gr`MQ-}ySdQwMAks+-}qfPEkSQLC%z>Eq8JDp*q5e%@4 z@fEBT9|Md!&zrCi%*<(MJ9a9sJvQEAVru(FftS8@2|-lMVPY2tkEU5h1#=S4u}KLo zL;>x}IHRKPc*oGDAK{_6ADbj?_d`Q5Oj?ChRtnl|XG~7q*ljCn*M4sM+0q}2uTYPI z&IxIb8R%>7+wZIzvCBb%es=-|)JjjIX~ftHS*REIPwC?(AH}LQkva z2Z9oAM@t47b<|H)alWU4!fq?ucKf1bCWh#&0C>7lR3oFv%_5&7&;N|S5qZak+N<0i zYS!!hLJWxizTG^>Z3Kr;<~5}y6hGvkqO6X1o7ZbwGZ=*qp z)S8V4Of$B7jAbRx| z3D`e6IoTWS03lH6x~1&l>1px^4@*ua`Eywe z=<+SVw}ZzE2D2=FRP;VCaiB@1eIV^(opNCy59-Z*1;`<}pvll5#v&x>s3Wj0fNI#D zyKzYv%YV}Ix!;sfgE-IIg42azz=f1Te41R4gG?MHu}eTzXK=x11%hpP+V#GIn0&Ax z)F+u{NOcG`I1O6AI~CA>H;a+mE2Ae9N-2ZRD|C3}4sAPjL~+(UN(BP~B7|5W?##VZ z@I!VvGcyEU+NJADai5C`^! zXZZs~PnRcTAWvh}O4iObEw_6BK!`rSFIJb0z3?@GzW3|2VdOO-I5|7(q!)|Cw{Cnr z)E-Ara=j?^y7zH|u!zXDQVLdv2U~;(S~155rHs+D5f{YPmt+DO)jQc6=6}-!+Fr{A z6O34#oQ|Xbj~%pzf6UT@Bt1H$FcMjoH*vWr?9jO^7EJKniN>EE-oGL;H8suAc-2J$ zZC)v?NJ$~J)KMLtMx!OC0JG+!jOgxylwFr6i+APNK~HzLqzyI7B>+&OXEH@qHS242 z!~-}>tmyTngM$U?I;0o*ub%p6BHn-ICP8~s?N6ANu8<1$ljjF-z0wU?@5)sPD13{l zhEX8Snox)14j1w0f%8y;vdFBukaVOL=<&XNlx*Xxv`kC1^{xgAtw)834C+LQOAzsv z!wt62aY-wqq&{sh1P47)b~_q@VV}sg(&*aeYnI#)*&IVwtmz*pb`lDEsHdrp(NvY}7 z@Ue}V0e|d3mr^(K(G`^xvXr({Mg6=?7fpEa9MVG%poLyN9R(Cxrp4nTE4+YQLfqR;zN>D1cR; zc~BT|Doq~yM`8sGb4pL^1Ip7==$aI_l06w!8SB~t>8K)!p31zQUiZoeAtowNIN98N zQK`TLX&vSwCTw*%Ju`K_vw$WY4>l#eKo{^4X(APyQ6RN+7IkkH;G3B;X zbGRVAIm=(h>#@PV#4lQwY3BFfMgU1 z8Gecu+R<=w0!ID5EePX930qy$a3BW3@me|S4$C0A)t-VH>iL_ta;cqZ9RYxTZ0#5^ z)fN0pO{l+v6@&7>=)cU=Psy~PjSr5jU+Hc5A1?B_>f!A7Exc5E3t@Br4;~O85g8p@ z|1Z9w6^>2ZsDQ^7aQ{Edpp95E!akyQ0yK>{!PxZ~^g6JV{8-TW6V})Nuz|Z*fTfG` zNU>@`ktO1$b*HD9yR#Ks!6jA@_Qkre2k+pF^xbRs*egL-pM-rlT4wGn8EE_9$S8VH z9a0)$v~2TmINepjlzpKyVYAvW=DdDQ^6qRG=f1TBAv-~bDVuoi_43%`Ep>RHqRMyB z`=iz0p8=pJKAGh+C1Cy8sre78H+8ZcN5Vy8lWXP#MxMRv=*PU=H9^bkjfL0DAr@*h zv7QYTwWHPi*};C|;t)t{M+Z`EZ7swfK&5(foraR#q0(8x7eVTNXc@&r01GL~)x#fm zRc4@jH&Vq9m+48p^{u9MjXs55)r^q;TXh<#y~_OWYQDa{5!ax|BvvZB`|su^Dg*?F z+Dcv7g|;3lE(<#+CI}#^Mn>!d53g_UDk87{l%H_E_TJ1sylbj+m#!G`?WDq$*NTtt z6Os^OH7LB%&x!XcN0Ymq&sq^6;^rDqLJfTUum{eNb>S4& zJsOj<7*1qJ4LSBd7gq#`p6Ifq_TlTEc>HFgPQJm0VpCwi9FF?Pu3;to4_B0(B%(J` zFKnkqke}qN`?7A{(ayR0-X@!ufs5;%erQ)zrN*)~Py5V_mcC4(&aV37D1mG?k#l?a7#KP?uW_F|G4;B$2~m-gLkIQm;!#Yk<^gU8 zbMN&-^YPEX9Xh9`-v3^1SUO!U$07y)cyC4nc;u2W^gjdQV`+gpRXz;>xn$>yi#yR) zQC3!UH7IXH-UE0}@W_ZzASt}AjC@YZI8aEhU@{8iO6!2DfIqClYmgp-xA+m_l!4*hGw^BfR^#`x?&I{4(17zAeP=h+y5R_hMaew;`HmS=zfq%yfpvkecUznh zg<@Om6wYO|XluzEiyA%6)gLySH{Ot~AWDZ}HZHC{8|Lr)-MQ{_Wd(hf1-H|p$Yr+= zQl1EdISnRm+}VNeNIF*RntHa zi!p$I@QdS(AN<}ubSY?l98Gb?V0@(X>?Qb;rP|xuRn^oG(t-^D+KO4LytS+A44(aw z7@e04&?n+kQ{waF9V2;VQzp+uxXX-O%i8>AGFk`R#YM!K5;rKCY61f)wz zX{1v+r3DEI>F(Y8^8MAl_Yc_5%khl8X8bD@KiMP6E_X<8$U=A zV9t7J@aFgS_UB>L$iKCSNMLz^27wt0ly}h%zJ;^L5eNkI$I1Vm7&0Uzjj|(A910E&%`VTB z^s~xy#J6D!5N{6OeHd1emj_AG^V@NRAJQj&(5j%CLql6yUx_@tmqUilAEJG<`mqPD z5J6C4GZ^cy-BS~`KnbMKF)bIMf1Kov@HBOQ|K9hFw&j0oT`z}!4Sthp zN%~m3*!e`|rByyFBY*|y=;$_neM<@<=khyu(ywzup{Az(-Z?r0!m7?`>8;SiATA~m zHC&%1I%d6fnS_okE18Zg0aG5i1%F+hvrmS{yDWSlVsQc`!m;a2U-w2SI8WI(kWnG#3fn@_1A zVh&TWp~@vLpFeZCg>6;awG?l`NpM?n6x3=TR$>s2#PtJr^{zFgbd$MUtoz#fl4Ir1 zc7KB*BNPZ8foZTQvF{~#Ab=rYTI#3-a34QE zzmbszMTASw~Y0_e?$8E6o5=Q#KhYN27k~7cux^(M|tJts(fhxT3Mu|q;u|N60{bj=@wCTngv3t() zT%Rqm{}b->Q5%Y^D5nZ8X?Wgr+Inlh?|FcU9S0L+A;B()-S4UE_k;BGZ;VE|o6U0L(*e@>kaM<)aoH3v+4U`*WYg&Osu5P z8Zg}7y;^RxpZN}VE#1)CZO*+5Y5oYB5rh#61gu;=+_3-MX*F+mJKAscfGWvs9Px4P zpFrcn(~bU5nSjh!)r7_^p}BU!=Lv&5aq1~YYkpa+9vMn%^TLqEILTxRlM97p$wBSZ zdL~s1@7!qw?B8cT_pr^H54e;?8u{G|5miB0W8H^;jRS><46xzI?rGK0(A10|rFN0T z#e~>-R3R=ww3w0we2D!eem@SG$K~WSUoNf69UR)&O^fJ><8lNW=7d`u1!vWLS%*~t z-+3Y<5+Lc2Bx+F07XO)gGUIQnxyW%1m!UOl%sgMJ*x|9sfj02!fj)VE&YSr~F-cD2 z0#l%*#|NuV?R&TpAmY{E*5I1@~jAMNH zoR3bi-i=!b(A6NU%69}dk$T|aPpnjC%D>LdLHtT(Za`(wH#$7%ioff8Kgz#-Z6T#dfet5~B% zX6ab$9y68G_9pxj0;5>58SK%O_LL-K6u2~h)9&xJ-D|kvUv}?Id;+a$@AXmzv+vR_ z>F$T>YuI|LQO?EgB;fk2ANzbWtbZAhzS9={#P8?oB5w{+LHlziT2|!mQMzKJcYg$R zX!x5}lcCa!f@}Bu7n7f$OB{UtlX(8U>#0Gv8q$(Ez?L+t{Fv&P@y_URT!3Y|KT%6{ zBmwx#oQ-4mVv}$-9@2cxaQa7~ zhKpYNg{z4d7|a(60)(3(6a$EKEu#s3l{W+ETv*8{oK;Hd z+d~7PGNLU(b}=orwSsTnypi_bVspB;b7cyaDC+7A{h(NlmRn zQE$x+?tS31VH5CgGMG$A&XtGFx?EU3$1@Zlo~##OukdKaeF{=p2{QA{wt6a zO~pw@TNkhX8R)$cerp)&TJG9oRHt4DLM zhnw##$XWBW|J=g+`Kuuu({z-M$5$crgKvTGdE4lg#Z#o(2dU3NOK_AOjunG_yJD8k zuWDBUj0bl}|C`LA5I;e+JCVw}cT`&HXGJ%c-R{ zu$srhRC~*aVuQ-R)jYJEPh135aeO_5cX;-<0QhMxLebX&DyT;nVA%>zP{MO?fOfGA-8&)OxEnNukN3Zhs+9x1uF+f zNIabZUwHFg<34(-TH#qzX*pMOz_dF6byo;mLf=z`jic&W2)ZP;%U!jyG|fMnjgv8o zVCRi^jUOVk&So{vG*(LkUmTL0=!R1opFYTIYXWP7c04@oAD_ zje?uDab!(@!>?u?Lr(6#4E%dg&@Bsg4R-4^*1MHbxGb7^T9eu|i=RJJL{756tJj`C z19hIBKY32M3q**-!B(TfV_$1^CSxY=kK##*5~=WUNs9ZS%~DRl)?Mg6=@zZORS~+EywUsny80Jm zxY4-!o5F(>84F`f?|>qy=3Qa`=vLH4s5km(tcs)*VK>S3QJ#o>~SKQnu|s2#@DS;_;dU8o z`a!f15Kzd*DeGK;=Z=#K{rWZ0`g?r*xjF|}-|OmIyE$|Jf~V^nymda(yyiH1v~cu> z3Lz^Q)_~yINhM)aRReJNn7yFDgz;KRkDgsiRQ9tVy|@S z{jOR(!859Q-?eRkQGoG|YFs>u`Lwxj08Ew9kzyh_>W#PHw@^0KD6}pbc62f1Z7ICY zy9lperCo*@ld|FYt34l1J7Bn^H8R4;q-ds)UETWjXG#Wx8B-6ZMKG?&LtE9#Z9{UT zEQ#{DuluK(g7lWc@Mgw#--aw(L8Iw8cPV9rh0sLB74_jO{?@LFduL)}H2l>ybzXEC zDVmD=2*JOaSIE#7c8SRI2(OFt*1>T!I$QMnbn%*NUN1 z<4%vZj!v4l@SS}uUHFLY6{P(8*JhJP@sscOTA;8WKD9hXW8WRujjHWC`L3vKl`#UZ zj6dE#3?sT^KNyHFKxZ6CIvvY#+kC)mxA^f_@jlz{d};Osj;3XF?*l#EaLry3mNl=J z^BgQD%~eU%Aq)GdsSn)@u%t5k)ywXY%i6}fKj-uNt~^1%VqLO4!|C#o1DZF)2;0PM zsh@c^^@$6;amwnR&3f#g&Lt|S^?iphEcXA;*Ust1eBW%tGZGD6$Yf~r?9m42>7HpXT zOna(qTt%A~&IE}4>n}`ut+Qmjfiua8yXh$FQA(J{p73x zP!AthA z%iaIk3ln?)PPuG&Bh+Nb;McTm->%luTuM}CF8ZX3Z--YAKKkoJK@(ccT+(G}Kg)pj zj;MO?6`e`vnRN4lVa0q=cz)P!%&(Sl8^`gSrjtFl*|}qD$D60=vjZXRj-KU+^cNrw z$A*lgUkq6Uqr1Iuwqd*jBkRije~J(hjgXTIC#7tqa;p2c_AUn$3kf-3JC{2)xQnMa z6TZs1q+5RrQ_J`;@cwSNyc2wK`b<&~mxu_K4Rln0nD}m`CJxg>yltDe;(G%g9ZGth zMdrl?KGw#-_Vwb>&ED@W%u^5djqS~eHAKs%PTa1B_2};;6&2!|sPiBp;ipnb{)Pua zZZqpki?NdH?Bpyqy(W_De%LIajy*k`UUrFMN> z;7t4Y;AZHlS&&x_zE?xhNJrVzhSFx0>7zd{mcM>a`u-ehtzq~v6S&$F1Hxklh7H@_ zV+tmWw!9?d7`pyMMO7PeKPjS@a7De?mu7lly`||yZwvWFtTFdyb`~j^UMXcospc|C z=N)gLc&TuI$^YV*FNlIV&^mbfsZjeu8{ywP#^hA~$e9c9Uy+5AJ^r}xKq{Iv&q{jv zfAbKqLE&gzysJ(_8x5Snqm&5-Du%Zh%HP|OF8+!;la_vcd~H)dY`jLhQd$(qr)}1; zRn=`H6A~UrAuR4yf;29;TLp!$wFY5&{r^PeVYA1w$z99wf8ugq?&g3&pt{1xJQGLwb=S7@Q$to-BO z<;{OT{q81E9kF3*>fo=Xz2ua95knnv8ObtXgbwNRg`YKogP-h=&8VduUZu$FJin1K zU)^x#0b8!;zUj!hXA)IODv>vPy9=k=Wt0NOhC-Jgjf)GRl3JG>`^n`OvEQn&q>|uu z%RwS-%a*F#j=}$FSiCSZ`|g65#0jxh)gtJu7hWWl+mQ#XqLi_DB zl_7<%ZXSJ*`m*E1(*$u|bEjT3O=H6YB&$r=(M#q&D4^e)kx2PwAWuQj(0&Oq26T5L zA0^OohNG&9y^0lW4RmuDi=CXRr200w^xwaK9mW^I9*lNCev5Z~77M;2H`U(AQ7icL z&_wg9o9Ihv%-x;8Xd;i8{I;ZnNKn?wL%x_DA%-~{>$jQzuG#-zD^m?2(dWX78NN6? zUezCwGQ>tBaD48{v9Yw7pAhj0@#~nqD<>Epiq4c~+wn)t=`E^>P71}87fRbkul#SQ z0EchR)+O1Ncn5(UJ9{>~YPs~fPZN{;a$E0O;Q2Q>bhCO!Lv7A_cWEb4Q36+2boSlU zgB;9+acr;R+#k6Z$7uxC)>Wa#qrHP9XS+=w|2q{&?v!x$%-#(DM|uPQ|CJr<-lvmG z8evB9hLdA<9zy>c58y3niC8MSDmYztAW7w8{bx2eDuyJ$^TNxER#&gM4q=4x|4qG( zqf#!oJyN7k;-C8>;E`|oA~}=&`SvgR&C1_u~%8~EclJbzcf(my7q@@Ei)SN9--l8F_a^-FeI;CP|F8H zvvfF3ZaU(C$o9XlZ=k6?QrjibXNrBbHhL5FhF2x@QIiSOV$kB_KZw11afziLW}o|B zq0M=IsQJv*o=vo)gn8_|$KScIKn-OAhQCesGgQdXmYP6!4geRWS;T*K>(PE+KX#&1 z34s%q!w!0a>?1bD$)G<$SRNZ|T0yy)ggb|S1x0OX>f;L9+w7~MkHW~br>Pi6Dj#Gb z_iRo@Y;B%m`yX$!TSpKSv0oI;Y!&ALHe zR^Jn!DEKba`UNk;)VhKbM!F{N_x7V+aCl(y@ba*K#*wE9i{ndmD3_nZBw-o=ElMO# zOBJIW0|f%Fx(}xS_=Y53TRaFI?lV3WzZ-6(EGjBSKtKRNWEFm7lQLcMTSYzUDal+J zjcjq{2oorGDTu1bkZpq4>+FSJ@ilE6`PU-V-Ahmn{2|A+$M$!i@yd9E(csICY!mF3`i&ukl%Tf5$wGAqPVf)rB8a(?52D6 z7eNeQAwWpGX@vjcl4M0r91D`8lE^VwK<3nx5o(zql>6*r*GR~7Wf6^RE`9K&;-$8> z=4ll;rFDpvcsB;mekw=u-`k6dseFTj?XgZIN`hrBit;*4u~792ZDA`lDhc5m^pITg z+_&6D^v=lw&VavYSg-fF)8K10DkrQj!iKaANL+}^uU5&K*s^U*ymO@j3xZ7c<}$gP zcyfr5*X!C3(|=H+1k&Q5Ld2CFLpOA&kd6>4w9m;}eF@vDuJ67mJf&PUbWT;q1OkXq z>s34V2$}YVdf^36EDk!Na{57%Q+OgXN$;ENmvr2k$obb@z@X=ic-GZ4fO6!<#e)&9{D?+i15 z-F@ID>T$C`LK;UC({|M(ivg$fDtpfaiG0S++dTWC_>bs%6b3;f(e>v1pT0MH|uy^d~1BSz~5reI|ZQt$c3_*CK`A5;I%z4u)rubE372xA$F)kIqQp z&c!@b8bbNDK2+~&V;S%$qmy&gon`b>+wa8(_}z2ujlVy?XvIzCj5^kf%E_e?f>UuN z2SR&WxG)(TcycBB)@~NBuKYW}yPJP&%IR=!j1xO~I?TRrbA@3&D^oY37Gq4W*zRB1 z1m-{B_y5#?1!hn<#Ag%g>tc8r{A|nP)YSDC8YvZw{V-7d)H%;uf-mJa3ZOnBMII*|XDwT!&lbOVksC=%Mg z&?|CPP6noQHq`OF)`*ea0q0Qz2{rZ$jpTq*9(L;(u!WQeXIw~l#erSFutnjh%pIKL|*ia=r9h~8`r>rm~Tk9%99~NRCwdRA0 z^HjLAx-3sx|f#^*RWUCaat+k$%+8|pE=gCno9hpFz`t>V!&ra`3;Q* z1vc#oQfb1>hCNhy)JU>yih)Os%ENL2=^xh0xQd9sT{-NgM`m`j;H;)^9qU*Se0urxj)^&R`+}FM0M=zZYv=Q+YHU zG$j5?Yks>L^RWF_dxzi1uc6N5@AW1b!6_lfYEdaNR`C72-@)c`X#X9CfB&{pHl2l- z@}=bK<>&C_NtHbFcj8UPxE>~64jN}JKAsvNJ>Fy`rym@8{~)Ue@A{9@s8@VXYn&jH zK8X{2!R>W?jcU>HMe=0jM)hG8P9arr(1(S0t_VjK_Tp!l1vhmc148B56Dt(&+{-({ z&pEi_)9*XSQkH5!VHV5mqqtMHO6A44o=JZZNWX?1-E*|)nDdMuvwfk~{xMxyUxO1^rTYu3Go7~}-AR;1)OGu!17~dSRv1GbUh${2) zGOjQ%GGbt1VI^lAg@5EWWB2#Dp3K~toDdAdyFigX`U{G7wCkMcG)r_t4r0Y01KSmr zv@+%u8vpy>G)vk+uXaA)H?do2oH4bdimBGdDT(u8qQd&YcdO75kPhQ8XySaJhF$3; z!mQE~@Ap_^fPC^dN*eVf4Xl9X0J2FNTwuCIILk{8@(8ySU?rtb4t>NPHn$(M*Ilop z-b{X!#aOl5XKEc17zk~r{-9wHG4D`wHnuH)SR;*fygZaff|_nI)O6= z(O$mdNXO6r+DG-MOMVAU0tVmtnKRHj_x$1_>%5nOPFR>ctLaAR5}tM*_3QP#zcMd4 zuM~0RQ`H-2V{HvVLQf-Gyfn46axya^QVx(|6A}@r`Rin#T~k9Xo^GI2<9_swOXqBJ zJ7QK>AXL)cUaaBGysDnw;>&jqIXi4wJ!QpT+brG@BAU}ptfpSUJSGFf@A)^^wCGYe zJv<&R?ah%V;l9w&{FU6u;JSpuSAI`pu`0 zAr{rcnbai{in%@&X!!t?c^T_JF7-#9lyy zeRMF)(1L~S^XJcjvz@?pVdv+P6m2r~2rzzmb=#CUgj)U!RqT(oWF&&Z&uAW7c5(^4 zaB*>=g{RD-*QZV&y}2e6J(`fS!*OdP5t6qDBMLS!Q{*wqZxO;!#ycU|>d<}%7DT}# z{sHEfk?LT269j8s2Edyho%)+wBdLng^*1avNP-SzbJ zAW3g;;jiDm&H2<(zFnC1EZ;PC`M^1RS_qev$YQ4AFJ?xbN~M4h?;XASVu7$!m_aE=QC<^SuXke7Tfya}TJyegC*mF$nq8lTcH5 zbZaS+8E)gIy_5|J`twhk)Qw7n*a;buo_WQ&A>_tXs_gbGBnV<2?;;bT3QbO>2*amw zaQ{8a6n=5A+7ZK60z9<7J8AG3`JBS$#}CKv_y9ST6fPa1*@Y!1gelqMY?H$9?!J00 z2-@^Uc1kSRHgs3K_m-)k5i`vYxA{&z%d2y#SZOE@Plbam%?I*?VxVde^Bzm;J!o6i zcfk)p7G*l&_~~1ts^*FbqR@mlWC~v^4u88rlKO!ZU0n)AwaV6xFespaE0Rx8p$s_g zeU)1}h|iKyCbaIwMI#E!)xUo+ppAJQKi-c?NjV@*#vb;A?jA=H-H9EZ+VGm>(E-5+ z!BoF)H#m044b;wc?h`R3)zH~VJ%M{j_F0Q?my3-@wl9xP? z)vcoU=2z9HpXG^zER5e_h_AVZ%Fy$@6spK+9e&RdXXXHDczCIrG);O9imjGZ-s8X> zVO)+26vFhKKl%@HqY(Tgex=NYtfY_7q(6;@H&njJbRl>;+6O-?d}o!S#(>AuDirz3#J48un&JmZKZ2euu)RZ1neA-z`WnPWF zt)HAYGb|?5z5FB$$c77%{F+mO1?U(oULtpsG=I=O{jtgGx%Wj(yTKf5oPrBnZy#e+ zEcYCyw$bx|Jp?#PJf5|UKVx}@^2BE7p@N^ED5xa84S=^vJ~?%TgdF0&<=aPjBU@c; z#7CD;*k&$g1pH2ZqsdL@J!|KvSory^-(Y`kRF}=GNO0^sLws5q+poJ7cib+mAg=qA zaX$LRVV;*0HqsWz=W9}m+3uL(w0BuzOn7l!ouf2iIR%Aw;*Kl2OLf}ESENKv_W!mf zmz!SaA~=h>-_|eQ-hea8vRJ8Mne2kBI@e7X>y&VW`+EwBJ6Lm`3?^3&Q0rv#)Fr%o zCZ{-6U4%^4in9+H7#TyX?p*A+z#GK?qIP4>636Lsl$;mQ385%JB_)ai=D$ZN zU>7)OM_b%!2nFh1cS>ix(bc_ET*hf-T~X#VcJsJi_b`VbRM{zNi?wMD0CUmtIkGG1 zo(pt%Wpmu#H?KLrF&-hSN6htrHX4W+17`_q<{WuW7nq;&=fE(EiIiRJ}5w~ z-}*@zB*gB`O)?sDSGD`GBz>xYPb%$`&)d=Ui$v~8zWe0bMc-Cr)wx_e$@fg$TZa}< z$j(I8miHlDx(V1P{sI1F>eXj%^iP ze?h%hqKs*HZLXw_gdcLABDGA@r<9C#FrqNX{xNb$98LVxi+{rlW()YwXaOp>`#(}` z;Q$ICerz{&0?>Td)>F4Gmb?7ebr&q z=75-#FN}qwqhm&lRe!gR+jOsdR-2uJ(%i~SsC(qkV2XsR?hp^(W)Kr^V`F1`1pu!! zih^IEg6!VCd!NNVw)-_~W@l%66t#W#KC=9vhAw$DEktGeiYn)UsgZr$q}EUq{jWz^ zYyGpFcKrSrwU4Ot_&j4!y)7%^M_zypD?`(a}9RL(h}) zZa;NGAca$svLI`LZ{x}G+FB^+@2poAYc!mXPqP%QNPPAp_fo5yPUdr<>CccLPi6cV zQ?MxZ9fC>??Jg$(hnYI;PqXb7OdlD*5>CXQ}O-b26CO4DN#HJ`FRw>#7`-{&gs4b1Q+>xcs1`&u*j0ZWq> z<4xY2_CLM$F+0WLC<4Z6wpUHXiQ9`UG=Sq%SX%l5e9iXG&eESh5GE$(ZEa+6zzs&A zy4h5GGm27*ij6SiU0f%}H^Z#5iw5KOcaXDQO?pVKZKt)%BYx;){#ZvoqPf z*mw7Rn-KePa8y*5yRbucnC8qth|6-f=~__fMV35MnE z?b$1bTmM;9cMc6Hi!7d|i?rDXMVbzKzTRN&933T$iHUJ?a%wg3{2d~JIKL%86@LBt z{PpVxh6<@eY}Bs)5o(u-)1qG3^)7$!S?fDCUog2K0=~%Oph%q3D<-nVfB6;}iOob4 z@VsK3vcZ-=izzY#&Y%GVX}~RsOG?6QGK{rrA$*x{oc}25{0kHqHf1p{@tr7-G1|#> zFHp)i-FV!K6#9cG+pGL4KFD%$I%Ds(ZnV2`HuIaQN6XQT@m7Hg$n8nqZF`3=9{#=e z&bN$rf#-kC=XT=(-onAbu`yT68KvHbe&pYd8EQ6mi3Lh+aq52IkhwlTyO7U70#5Gv zMP9sDA!g1rv;RHTh*jPOB5>}9I&6a zn22jvO=-@qftUx&_v^)SylH1YY`->3(fy)|t)GPp23cj?*%>X?4QYL>Nr~{(Cu5Dh zQaP$0F)TLi6Kjtc9{)sJ%=dim?#>UoP8m`~5KFA4q=Y>i^@;PNC**-V)s9pv6P?yD z-}4d@7`od|3xe8hEGg$cKM3%beWS(|SNOr7u+ShNarsO-`0u7i!ON6(KN6}7)xrx0 z?DrfBo+XSZtgf{Ts4FQrFbY(ELG%DrTkLxtF-Iyc0?~_?!DU&a)?C1(*+z_!vCCWx z5MEy!YlS%gW5L1WFdTAvFd4(;K`9=J$243JsG+|DrHMMD>XEk~sL2KKOwc-0CIuW9 zqNvJKz5A%1K7HEjgc)eo6Vuk)i-m`WXKiOEEw&<0yS=@gn_~Fw+qY3b+{Sy@`3xO& zlz|iWrE#nKcD!dDZEYx^t7tcRaWf%ru$XisNjXEolK?UQXJZ2nA8PAJ-l*!{ErWs4 zA`M~a;_3UD9-EnXFWw7-@T8>{?s3B(V|OAo^K#(ebhjg1$n!*yetn+qrT;XRjd47!9cVqzPeXusoj4SJ&C9ZBV_t% zWRTDDZ}y0*Fe#Ve&WR0V_SF#eBhUGa?NC~8Pr`8t?8tjg)fE*b$bj)%EE8v!=I%vD zLFJQy+TU`CeS`n5t~?wB7>M!!PyjanDVvL%U|fe&l0S2P8Ug9u)d*{$x*tN%R@Q>e zMkRQKks8S1NJb~uR~>^gxc)YIRoClXMERHOOItfwf~5S)xqlz$d(*R%I&i!>_j#gq zWZJdS(Yf=*q}+8h6U4vIL@*iz{wXpA&xv)EgBgHn9lm`_kuM7fc;of#p!CRO4_G1T ziR*YWC2h$wGczS$v0*T(%ilB@=RQPS#1ZGqxV1~rxU|2G_aHel3+gtE-eHxQj>|=Z z?BvEwd=&`!PPan?YPvIN@gYfxB-3N(Z-EVozn$*7+oN38z0S-~tax9OkUO};lEP`o z+s_T0p1lUyWOrZwbqrt?NiWr5hY3iIcYjj?_E+*=O`NE|#pA5cZY0!#UO6?Z%$95F z_Pr`^DI1me+(jOKY3BJf;Hf8viiS4&LoijR0(78TgK{8bl|&6Td`w%eYXc^hsQiBD zx6b7EACM^c0JQ|A{*aJl5%W$Ch6I%S z+Zu;Hg@ems5a2))C<7P^_g)lS0-Ceh9b+Axlq!8g!y%0pMyl5w3C*HU!XCJ=%YMRk z6;s$K{q^Wg@%cLyU5`gVnIktP^tmsSnfxjbBstbY_i_lC%=`3xP7VVFL-UKFdY{d; zA62=87NIYww%{vXYd^MINP_BIDkS^4GZ_hc;E&TE>Rxs$9E~a0A`q=$(x88!-@nbf z1y8iEezSfMf+S1ScvG`Q+%jl@(QLZO&vD{98HhQ&-rb--*X_pFJD5& z$i1k!?u}QEN$M}?WgXhfUx1(_wy958dY1HQ=+0;tb%Xt7OOvThVPjiSR!O+}nd-+c z3$bJ4S16LY`T6Ki(0-=XP3<_OBD5YShG9l53Z{@o|7^6D)(m>3n^;>Z-Lg$bm-+4d zMwS;gKy8?P4vuBihyT7%*9;;DiA|IUeZUPryk?1(K~q8Bs-IA$jf4hHtV+TepHnn0S?fK?~Apunsv;z2_5 z7yzIE8c7aZFWc4zQy*}*uoyHp!4+Ut*?6`41I&Y7K#1k`(Cy$w9bqEdKY_b4#VM*tH;CNCm|HhNb4`LkE7 z9RC!T;OaG$B>-B03)T~-uNp~)c7n&aHx-{aJ68o!JWPoPpK6)Y%L_w!;uxwR|%CBeLL|U+$<#oWTC1bn9lINbo z0^E-DzTb=5zp;#vjD{N08Q*V`qCmo=Tksz{X2k4<-FjeJr29bi-=Da#3_5qvFIY1C ziC1&Fd5;w1mtKA2Wd*vdGS0Z9-X4?8czX8Ng+-V z;dDhLjqfQ<3ZEQ#1+>2|bPFSNFnT*iWbq<6*MUlosU!oE9C#rQz=?^9FLE#6w8xMmBI1Sue}FvDIOwh_edj*#FQ1v z$X>m2e|;eI_-kqQaRgyk88!2SpW1A2y}I*1>WE~E>h=Q8kAzUflxe(ez~yuD_d2lV z6Bc2%O}RuA#s{FLHz7iom?4l+t-JrU_j&7S?_grlvm6aJyfS;{dYQlHYsPpDo)w6? zy~K@$F7idHfoBe{gsBYYO0@?mfv}AIszma~oHnu9(~G}?j~^@W=z+O{kc_M_{Nj00 z1ou544-O>s+rb8-+^h$$DEjqjEKF734FtWotcIwhWn}2s*`rljN}yl;Z*F%33D3{& zjmpG+xE)M3TT!}JE?Vk6nj_-*FLP3I;rriaP2 zBNmz>+bp zGR;#W1(&pshjYOi9?__c3f3_i!O4FwGRb8%w!reH5H4(p0LvX>V3cGp46H!ZPJ7eD z-eKg0()ZdH|2$!@u3YUJ)@M8lc{4W4#>VSxeHyDzOrS(INiON{sYERBXw&IYe_->E ze5~XA3=W!J9%HG5m>D){K^J{*eYVXXwx9Q<&hH%#w&n9}&u6==l6+c;pAa56rjbu) zP<_}Z2xGe5zG01NAbMfGk`Dz0jMAjaZ)EC-?o(Tj?dc%1+-hc-9X#-B@S(#oVB`hG)tIX@i=aZWb40BWepoyVCp0IGFs?zJ`` zLZgk`)0m!RN_!0ZoiBGYC&AY+5y(Avv4I@pqP|f6YxLMN6rrYotNT!O4 zQu#Dd-=^QN#AwVBBKjaK0RaGa+~4Re$jc0Dh+`iuYdl8|U)?i|mS|;KdyvmPvQmHt zYbKKx5V_ThutgWqg7IjepjAM&Y-+{v+v)PKMCzONkrp;C&f-)7dKFsVs^_Q7%PW23 zjDqgFb9~rDFjV*BX|^gSV$5??6|X?qW6Z~zlV(-l_{?_uB$T{@I#gQej%($!W`m+E zA&tm4PsGsWO22))Fg3jT`(mybgP&|5jB?cdO%xZtbiRz48qbxq!|_iuVyo6ZDn6m>rlNfhUER<0?#6|irs9K~2DK@;&%ke)2+qpy znY4hhBK#5RIXx&IaXqaRN3)!QvzF|IGp6G6;ibc%>5DfjHRA&^nFcx*Pe}q7B5+Yq zjZKg3sQn^X_#e2}`C~1-40s4~?zc_gfp%99^Uo|n@~r4n>CWqXIkqlt-8Otl?ka_! zIul7{MmIgn6k-Loy>-N?p((+ukR+IbYi&-49Q-RpF|+wmeZsnK)L(I~=;=$w zRVzhxOVuQ_!AD#3SdQT{W+UhMU+(6AQ5NF`eg$T|h%872On+=uechlytq=06t+XGq z|NrJ;Ob(CHLjTWS?3M)Tx>99BMDgZ-*r5OQiOO?H&$3Z%;h5UP;iZ;P0^qy~{UrC) zDOZrTZ}ong2nBw75_eXw>3nm#Hk_;?A6rK{_t*P&0t#KW^`{Z5H83swLMr?GiDG$~ zZpf(!zUO1c$kI4a0ty8p)fPR3&l=w0SZZz!Ig|Kr>ZO$EWf^nV)b362{AoRc z`8S+I4DBE92QJlGU7TdR?Zj841r^=U{`~u*9hcn}vhpC1EGT(#j3`9*B3c;Hx{bMR zwTkp-u(ETpR{ld`W84PAzxK|xc}+A&Ww)xGq*M6x8MXYD z{68lP2?))SgL&Duj5jXjJr!fuTngNN;bUs^~D-N?uY$|lUN!pccaQYi#5 znfUxtcCP4u-F-~9P;haL*Ns*>w|a$bx#>F;I`B()8aSC1Qn_-kTkS>GG{ZLnKC2lkK1>w0VpWX{r4hq!8+IQtekluF8{BJ&VBK-mBsW#$Nwx zMdF7IJ6R5Y+5R%3f}bac$}?q7f1RZ9A61Y5;rfM(ibRjrfxnM{>fsL~a%OW&%ZRxL zBl+Z{f^%@dx7^`A`jDd&bKqB!%x6P%dAjpNj7jP9L^ijX{l?M2@Nn3m-QBjfwvoYp z=Hd*AUz6Y!9D-?&PC{bw5mOA$YRhYUs?QIIYuwx2LzE}nurxY$e zh7d*#*%Hm1ii%g4gvs5V31*(yC2#c0e*f24hUWYwld-f^l19(@W(2mN1-J4aL!#?z zHT1q(9at_-X2qf$dw%}upNTBMp+sbySuA&A4CpJrwL(Wb-81q0lVwR^CO$9U9v?B7 z@@BQTrkQg4A}SHcU~~ZZ7y^7+lQC){B2)EtK8V3a_RO(NUnmeXB|s&rZw1OqQG@!D4Lcay>cR0ybU+}7e26fnPi`_}1n zTSq38?r+O$Nx#J-oBZ)H50XnLmy=qOuN{IVvHZPp?&rBVh6#~0R)1vkaWv#jPpqjN zaV%28@`tnukS6LlPrp`JujuWJA+hrtW%tY9RW-9*etNnYrmFmOe@2+yo38w#+Fo%^ zXJd*Ag~@y%uEri)2kq)vAkigM?zbTE{wNZtvHZ(v-R|Aty+-B?>Mj$|aU{XBD6Vi4 z;++oFVN~RR*kD=NJK4QQ%|%6W8sAcIdbt}G&re$N&WkZXTzL8L8SK%hD1}^ze6u5a z(+Dk}z!hz@Hr~fZD%$u`mrO2CY}or(eRgWU;z9t^ft*w(h0z-*%iEgqA-So=_(dEO zAH3T*QIAnh3nts@q( z$*+aJTnK#qcGr(|AigSrMul$j|Do$EprUTSZifcxknU9JM!KXC1f`@!x}>CkNFyN9 z-6`FONGL7c-AG7xGxy1prfX>YMo#&i=_SxGP|I3vl%L9jV#Ij@7 zpR3dWvI0ELqqZal7`VfdD7DnXmyFs z$jXWd!=s=G54hNEu+}d$x(%3joiZSW1Z)8xd(NRXNG_Lq29$F^PB0=&JSc@T02Mo? zp6a<1GJwTmFmeMC-r2Y4_jCGjHGSH)k@Br+b=%WpduDa*{dT#W>ERe}VT*t{uLY%W zB-B^&(H}28qs7O^AH4>0tI0O?L};+2?%&UFdM`;gVMGE+^Shj(@ULxi5bE?(U-3#V zY2XVNgzeX$IwmaR^AzV=|1WoK00u)cr+JklVQ{-Y+@7F9Y_G!Stasqc&|sJyzkJ78 zVrJES-2o(Mg)uSMtbO}rc-sYFgkTOJ!K^e%X_Bj!y`j7>$sG*rQdTLZ_}%R?s+s%I zl!4%HHJMJcC(sYM`2c-N#vl~+Wa;hcm+h>}#sM>AW@h=rt%4o+5$t$iwk#VL)-uJX z!N+a{uNf{>KVecSjIB>Uy^sgdm&~Q~@Q#NQ*hWmNj9c@A9cgx)vl;PYGz}x8p*&&? zexssZ4ILfyc%`878F|O5!prOj(H$5%7M58;AdHWU3kr+6lLl_G52%BNhCdFK@N#nT zn9f4mq+haCY=G*Qi#Cu_+}zx;5;Q201VA6Aq zTW0g~w;<3(M9%`H58!o)!n-L@b#-WD_AO? zOaZ#e!#Q9}9g9ks2!Rq!6n+G8j>pyMVao+){$}<`;Ggqc3^Y> zX_A&G{z#bf*qUc04}JgIlP6EOxc&PhpAz%&HhKH3tgSJW)wu6tF6t?%MBqyDP^Tor zV3jqq#mu2Ee8>YD1(1B6etCL;-%+gMqCQNU5kNK7PtzsW@Bj25?4$4NvF@(%IK8(c z_GHh~TAASsP#%*!4Sq@gtM=SJc|^pP&ys_h@~`-sdhrnaLTM07t^IXI3q3b>*iN7J zZQjLBpW6ibYH1?QCmQ*N?e5wFw;lwT_x(iBwpYb+Iz1u`Og(#VN{s){lwS&PC5}$V z)RgT~qEN=~>@ zYDgFij$%2o$Z4-Vk&JRp!eH{()^bxYxsONJM)_I|$}0R2RAn_51C5j^ZPg52RZ*H;}U{>=P!;Vta%KJtGpF3ADkX_(%QhMcv)b5p|grqdX z)5pb&_=puGA_^mWnzBjli&6XUmL41bZQ2gSy1~8Q+KhNZ{`h(t@!}(@S@KaoIxcQV zVF8O*whd^B6~qX?1R9V-lpw=_D9&rid?xmp+9lw>aa67PEFOUx@X2D{R!HHiJ(R~}PRVga3Z zqKF|97}tO=x1bR{J^e%e2dsvz=?lQOwvvRD&`%dPtzOCV_urC7PUkU z)h?u5ZwU4HqFo;MaXv7K+4R5Tm1tLVy<5v=3_9dYG!taJD9whru3_$X2hsDl*ED#l ziMNN2y|gBs3Ln|&rXC&av*TC5A++F-)TB?P;NXx~aI7?)f*;S)gMfw^$N2E|pu_+@ zsQoVh2L|L}NzD%i;4OXq`ZcgQl*!kit5K!_ciI5i9Ok$^$^>pN&o??x#tDAH5k&NR zwIucgrn=b)+=7xhf~?gCVqyrwt8~M=;cFPMO5A2x^FJqmv#~~k>Pw@ z%61G4qaMyAff~A=$oS0+FC4&`jJYBJvGvePeG`x12dzQ2V@n1|rmG-S9o^jImRfv{ z=ADNWZ@@kRI{3-c>*qc~p_%ncYGY*7QT~mV)D){Lk zwXk`8b3mU+ePAc8+GJgyl*9ieDM}Bg0a-yfgY(oV=t&-b{_36`J<@W82 zC;54Kfio^WQ7mdik1#R+i%8TPh=GL_cb=_FySTV`2v%(X6^Bebt<; z#g)xbFZ(j zCna&ku`9MJZ=frKvFZcm&Pog#c~nsG3E&A2v|PYJLx$lj));*)FieyhgX=I%WpePC=6=N-13YZ?q9$>kJgq;?*YiWVEeS4%^Idx$~-EU*(>fcX5D* zk4vix*|j$TOGMdI;kwMnY($f`ceWHQ*ZT;!x~}9HO&xIx z!s}bxBcBxdzQ{T`RrAS>lS4g)Qv&~C@3ING{9pEN2I(TMKW&y!u)*~^z~22Y@~(+^ zE#m`umuz%=U@Znmk##bK;I{1(l91yv>`!0c$RR!>;Rf)Ve!|L%G~k;}<6nAoe%_U* z80&brEJZ2mj;CF&53b7KC`s__{G_rV@6XUCh_K;rtw1tqrDh=!ndzK4ee!t>kS;sj znHsYGu>ro|0(L>=$KAlM!;4%0b^_+T7hK=9yg5EOiO$T#UApiNyuH0O?+U1>s8DSB z$?>`FEQ(rew%_(E7QWndf~ggThYzvM*lbm@*nz)IouQk4Nr_XA7LQOKR?=@&Xj!&w z+SksANT(N>d&&WY`FcY~_-T?4(VQdX7{DJu&&XH7gk7pKncr+~Z2VqW zFu3N9z4q~EbULD&8#5pR zx4rzD_5ldk_Wasx6QaGH!iz~v3>jX$pFSQVbqNDKR8iw1odX6-9WFWfF}I^69x4vH z^fQ%0Gge)fpxb6%M@#>N`9HQQPP}yDFVj%%^+)MO9WD*od<|J|h1oybzD@K4_2KFO zeEY4t7BscF8Si=flVCdKJ2s(FlAkaz3g=6b(BexqOhXD3Ln%q2Mc_>g_mOaZ&Yfu} zB5Kt+8SEsN1X5zmmA>j<3RekF+& z#CbaUD9PeF z<>B0xiy{weSn^F38rCP&45;3Vc2{)Teq`-o5eKA*2c=oOaa-ETY4~K zm1B#|-Yf+8%k)@-W|4CP)adIsSHA@LAA>`mv3NoOugQ%p?M*phAo`bju6ufetd4f*ef{h)fI2^T+~^W{3cT+GYW)g#74`nq>DO1`2o&f8-w`_K z+cPhW)V}B=&^FzAu^pM1-@$cO+x_Qto;#uV#ajJ(2l%|PNe=n6m7>v%_dD(c0AxOB zB~Z;&g^(_CMni!<)cjh`6QB=8_9^diTsT{{lkQ^_2aoJE{;}7!;RQwSGxu#X@6c=E2}D7H{UA_!8$2t1i}w+TXRNqYwwf!a0;#mp<|EI~ zbhw+E)I7ULLpPnEAPMNfEa%G8CwNX`cpeIJ-XZ(fuWO#@bC!mw$P8tB9bKbW;n}@> ziSv>XN1o_(U4mw}b012zhs!C$l?XOo0COHC*}r~ILMIPRlQPOTj@ zr!{{OhHxksX3cN>^OZ&ee6AivJ@HP%Ge2rU=OA(UO*-q~Sw^YZf*WxRs>{*_=|3=_`j=u~%4JchjV4d_TW zA2G*bEJ@GfDy4Ed?JnNb*@aJ*z0C#7d(3C%g7w=##JZV{L?U_Q}L@*Dt#mfOuFGlr`+DW4oFUepsz_t|Dk_!htMmBN{*y+C3M zXG_Z=Df!PE8o%OHv4QCbg^+PY9!V@UOHb#kf4eI;KbT7rI^~+V?uq&g;rA4qGiIp) zY3Tr$u^aFtE#dwBeZZqp(0P`!V(ZRUo})fO${bi9!op!JZRtgs`hk)#p$;t}>|{Kr ze@FNSns82sH|7sXc}gxr`4Z{#wWSlet+0cPAB4AX;Yhrff%XuiB(L|$vr54WCB(pB7aaaSLvfz(%LpcPJCz(@F*GQ8tF z)yAtRV~>su+aX|UZYw*H3|BeJqJV;wH1uR=P+1SqowWLvybi>+fA%1*-G+BS9wH@s zssovI1Cu#wG#}>JuFz6b+kzZ+6f?!!W^-+hMub0vR@v1@HIv4#9{VF!ljY1TgC&Tk zkY<4~@MO}!G=~NL#Y>~fRHgM;;AjT|_e)Dl|7L*s7{CJ8zolmvQ{l$15NDtIU;+bS zX>04^X9jpAoQ7<64v}(54JhTRF?(-B-MMHfHh6K`8tT%&>R64g2>7!R2L1c?-cQFY z^Jx4FzH~*EL^KeY1|>EvNGr_2bI|fi)9alPE-JJgZXi` zTuT3wlnoy({y=EW3AG0;y8M+-PP!*7D66;I{J1k2X@!RF<+3w5u+Is652PD{CcUB? zG73OyI)G+fLrq0)H^EByVbG8EPnO#B#TBS76rruR#fhJQaVodnEMJMo@isQaG%2pr zT`Om?@-IPhti!~|%ia!0rXo;h#+#}tnBRCDQGdD!N%JIBO%^QtabI6nPR_Kg4GeoR z*1CB|szS`uS*vu|G*;Thef)Ce=?vV@wvoIEQ;-(sgw0WrVOW$x!~ZC0E&h#v>u*}_ z36Xx$$Vnf}74V&wI?2n0%1fGsU{32A4Sz&?@-S(;pKKpdb+)U>6e7^ zPJJy$8?{U<2J9ZX(P}ha+RGRu=nr^)x@6V7sHC&^;P`fNUpt94AUtASXY7h1H&h3X zE`aOE0KCtSrEC(sCf6BZ1UNGo7#kWIk`hA_vRUSSGq$L7$N)z9{$VXRMnh}1Fp)8Ikq2im%= z%iWW7NBycjl^=3pFc~q^VO3t@H`|ix>bzi25dpRu?Wle5QCsG#ZH=C#$>L=_2IT*f zHRitEVg+-DyGQRWXGl54(~oXRS7-(rb>i-eem1}oWa z<<#rgy)3+z(;Yy&!cH79HZESmTe6)4!w)0domZ7@fUv;N)T#7Vbt5C4l?;KG%EEN< zr$^GZ5TTFS;A=>X`13Dg_4jk2LSx|K!h5wJ-V;}C6=?mZ=`bR_XV5#QRd9lj6e~$X zAW{~@I3Umi!w~bFGTlujVs^biAc(L%(&FP)vJJ2%bLQcJcv@hSBy%5+s5oTCqgZiL zL1a8zYgJ71z=#LD=Qsah#A8$A=owT@b5MPOWc#wdlo$Z<$IapNypI)zTTHC(&n&)& zWJFZzByAtS&A$$px@Sw)ye*npv!28R5bg^-J#s$s0Wk~2d2GP6baZvC`-7PO{l^be zxL^R01e)D0g*-3>a6J3>#@55T!#`^jVi9(tI zlZykx^FFv>w0|_Q>_9YiFr`%5y1Ks3IFMVX1Ab9u?tn4KpTBJd+DBehrG4=PN(|sm zZfz}*$ShVuutD$6Z=vVUO?ERzq+7NASbv<^lrOr9$uNPz+yj%2jylh`2q9?(4%?OZK?EB6}U^*nI=6=nl^-&XxH?Qmx zWGdD{hhIN$Q2~oyl#Y+&9T+frf33M z$$Qp$wW+?%HSpa8P3k^nXRotH7~sKFxL)^S}Er-URU{fOq9*s zG2;w*D5$7(XfplX+Hgsg_=)={ydt;qQkX*sLE_K;iLS@YZ^NbogAjUdZhX41o!*Q%uAgJk2IOhbCS4WgXn6;W@lhDSGhwv*@Vs=Ss$m&5 zC+0=3sa%ol>gv(rb8tb40&wKd9{@md0aCEg^o<`Nkw$}@$VaiUnF+}~`1smA2PGvs zRk$@D`3Eq}OiGLc=p4<4U6GHsx3Qsy94IO1-qZfw+|0ew!$$DC+4FRPM2e0~=iK!8 z2^m)lBzcYU_9Z+#)a%ZbOOdRK2j$N##|k2?nX&i{b8>T6xG1DuTx!iaihgZqQ3PUB z2+*>#W8>7&fzHAN6miq@)&bTxMi(;j$D6&e6ERKfP==`P8<{8Tya*)@rNLl+RjbDi zni4b^X?jIBIGs(4p>!ZfLxy{dMitXe0H%K@NO(KLW@cu_zx+}9&z@ouvq{Ry1;0iL zNs$DSM%n{@Jjp6WSPkgb#K1XwEfYs1XCW9Bn>_E|kwfZx=Tp*~Y~1>3>*Dw;O;LBp zy>bF@I^{_Ua}EmrlqAN)n^p|nL!;Z4NDa^ zsEG@~^gR6bG79bnJ}zfY=6)(NE+@_0v&2Ehbz?k)4z}2@0aA=0Zc>^Za#INp+gWVY zkbxCu&z(N!<@IOgh^0PH=^6uR98`R|+CeI<(Fkt4s(X16k9hK(!fnDxB%DU2b`T4v!@Vw zDR4t_Np47^SPIk$5MvD!ICjCYTAaig_s22(E3hQ5tmJyAN4vjJ61oe7<@FwrwJqY|oce$6Yt2?S38&*T-k zw~G`g(_)>F0NFG?!g_dQmXkTQw-_hV<<2M^8ZE2*8ywszqbBMVJ_YaWF2Kxa?%aWc za~(jXMI-*gc5{UOFAI^H?=NY6)eZ59ZD(Og7))%+HyRAWQh<;l?6O@ z7}+a|iu^__WsO_x-V?lcr}OY1Zd~Sj^oSRb8^4UTy8PZ|KoEkeYs?0R&*H}8Nucq& zoFP={kei4FUcF2E4jfZ~RKS7xROKaySvr7QoK*81@&AwsMBIifKTR%OfoDm({ym!M z@@<`_hC6sKqJSaM<-c@#*0sO(2k?WZNOFZ^bSx}rWvwpEV6O0jQwyUpa|dN|Bb;j| zL0zDe4JNt2>G_FqE9fUAIzsekVBq}{s>h23<=Ec9q5D0*?+$2+WL&sc1{Tj88pX+K zc!?h+w7v}PHIoLTOBH6qQHhkN9Ut9ew(A{Q_gxXx5Pu}P_Kf_^IujP-t`*F(j5XZ4 zyUO(^-w02u%^nNNQT6;~%$0Sf>{)WRyq-rX)S3B0_|jaQ|acV!@h0`41}%L7z-K|A^F?E6ePxQoy%MMX z_Q^QiG;hPfegl6Pf(xj?tR%F9-#+jPigJ`~AaN=oM;OTAFu!(Ng==e~m5F{E4$BRG zl4rRkyqCUo&HCJ%%*dKZ}UqjwPxp9W9tnB$eXhpq6#oa>O#SV#`JpbY(8Yz zguJQhJ1g}gqP$H5G+f&U(+Eym7k9d|n3zwThgwWy7%s5(vh-ts^Gl z2Kr5U_x`g1G5&R%A+th@ zIP^eb7CmH1oULwQ&`U(EZkMoegdyq^e*=YihbL*wZ9z%)m396qi`xt&h~9t0kWr1Y8+rRi60hm}p%6%l;Jw#L=Bg zD6}nh{#=DKeaH>_w_+e21;0q7V=^Yw9${|&nwA!^zHatynT-h$W)pUx*eZbQeM(_$ z)z)z#{b7_ah=0*N*;*OS?*4|w1ke_y8yt+pPFA)?auD`2(fP$i0F*- z7#-)Tn2i^`(wKa#3Mmc_48%{8ff&Ly1x{jYf6@;zmz9KM%~TUuj8 zO*G!`()d=V>pU@cEO0{5l`C~1=1Te%!_8l*6}L{v4%^o!$!cl8S(kW`-LtBA|9*-2 zi1FdQIreh)4P-KJCOG;a1oI0Ez6*?3Y(H*}_n*wO{3oO{=3N#Hwm2hnHx)oC_22U* zPI(<3gbJAQ(=jyfi!E#~*(M&HW05R`_NH^SK2c-5%ag@n>f zBAk>mW_i-qmP-jyOJIOqi-NO;ZB{)uDYt6sV`uu#r zkeSuq$G4l?>3=Rda9_*^r~HrI2aCi?T4e7;byWlcNJzjHb|KUAi&jHl9}CvwQLnE4 z7OwCSpiY1(EzJ1%TlvucqaBU2+3-t@jH^t*3lC%Vp!{_`bmiSi>qvXznK^Q$v8a;N=(MR(}k1~PzQ6r%t- zGyFc2#^iAkyZjH#^^%XD4-Iu5;ZVPn2j`oMNOP!tA;}o&7Z;MyvMWRw$p-nGn_Wud z7d;S7(%v0y=drxW-MdukB)fas&fhDy_&uJyo1dO9aY(P&a5;5gG%O(EC;{MKq?+KW5_ne&^ zALm^Gs+zh?W_9U;6Hi4oL8^$0Bp?VaHVU;Wp2Mr%`of~3a0|>V81!(NY0+d@W89ytKTr@x%boC8_`cyNF=QHVsSfy$jUx{H9GSclYlTjhWS)Y+i;|Lmh3*pL z=7K1$*mFTMbijad1l?N%+=@*CC`9Lggz&7L{j9|Td%z-lX_$xq6v3vQ0kAj4mFrZU z<^g!y$hzO-_cWDu@lcKh3l-J@HDLk>Qfp^@V*^@UOB@>kh~S_B z2M@nVbx18UM?uD@OXE2*K)jg_2z_aal`@S7QD07FAKL__% z;g;`~!h_PIhVkCkZmMr0%JPIn@9>GM}O!GX!a%BcmX!pZ9DYEpXO+}%*-DK2hz z1f2}m6EUg*UJE7R38|9aPaFn^iW&qs)(VSw-< z1GEBQuqe@E^hE`w-*4ou7fuoCzDmCUZefC+|I9;(Xb5aSu!7qLedh zPL{bo-3|Z?7=Roy{3mcm%$#$3sa3+;$LGW8)+=UnS#S`VT^_DHdHPfejMr6|V{27P zCV=-Y37A7oH+|&Pwpu`-Z!+Y$0T(><-7tpCz<^Qc+5`_9iI(LHT0W6f4V-y z9a|;wK?L|VR+-GPZe6duE&e_ThEXs-Hv`XQ)9a^cE^cysejYIDvf`KxBuLWMwls%D zs?j%7u*Jr)IhQ8teU4h=`HJiD{mp^$82HI)BVN)Ce>SXn>L3po0jW`Fkmr^npI;oe z^2*8rzM2pdgMfgRUW@TBMA6$xna^F7KXwWO$*JNn%z1g<)=H|sKxsKTtkly20w2j1 zhT3ewXHj*pU|i`wJ4X>k zo!;dF&)lu~bgQmTYxU#%_hkv@SRz6}6Cowdk-vna8w(#1U;`=q0bqgSR{$C}6AN0Z ztMKW;63v6>t>4=Q`;}ufnyeSsSe-2auO$GL8DuJ$Iw#VX!4v50?2UeA%$YgP(M&Ui zjfJh0d*=lV2AUpmpKBP5`g&91)#Xl!N|ac|zD>_r>m4@C5riQ?!eDJbh=-Qr)tKp^ zb%jpVq<>ixvJ{VlhG%N>J-Dm?MpsW^uxkIVb@>)AH6%?_DPXMgPqS!5cBXXVw;7L# zLKy~EZ!C*U1_~iQ{)ic%v@cPA ze2r9a6Rq^%NB7Y8fzA-1uYTaIk|}{Ez$6*_O2Q3+HiZM^pq#UxBkm@;Hs(^0ca_+E zvk+2%7kj|a2~GCa_A5XRdXVS=y*sag5;zw1Y$l#Iu?kAiGj#Sv|8JRI-S2LFIvHw&Hq$ELn2b`GDyY5dJonCZO09Q$V_;=<6*0V%e!vKiDDX^}%5!tQ zGG^me>f6>ahOWpv2=f8Q0G}w>{=2-n$%WwemYZ8ZG6zsXT5pdZzFXUirPLg1Gf&kEq3#vmM@O&Ptbd5H#sC5Z}dxG?o~K$xoK z{@Mm+x-kUaaB!6${$YuI_?4afe>dcRLx197vTF+!gGS(sztQGzF|g=a&UoG zPy&F02;*cl03>@zMoLPbCyoJj@}dH6cShkKbe|4%M7cbU%>WTt2^9kqDnj4`_rA@e zgg8sgf52Zf_=Fb7adX=fF;z%^KWFd|C|OywCH;xTi{U2w*UyLto(DYCO4rI3ow&gy z33XjF&aSO311a}NdiB<*&J7Z*>=RGO$jFM#TGc@-K9U_^c`bT&4PVh1rY)UrBuw;2 zUHLWz$Ms9XNzcrLhF-(8w&A1{cKw|p=9Of1 zFjIqulPoW+3Z4&_-(vuwI6|Fwq zQP3)QKPC#CpVxoLD&PtLQ{%&;0mPiSKoq3(bg0U8M@#fZ5Q?ZyA(}sA9^gc2n}r2INwhLi3EiyI8n-( z)b?<8fLT}NsG6eUFDQlIrZk~Ux2DVbx$>69m(Wm2eSJ#Pckj53+h8StE~?%PCJ=xo zp*VF{e-qH&NOj>_H_9XgYePvI6CL(kv|Oh~6^Bph=2?&uVU*K3+4@@f>M(#a3V}=% zrAPPo>zmVWL@qnrc=`DuBcS?vmAggB&;LN48tA29_krMng@$m6+sp0NqwyVhNXQ?a zhi?GNwAK!587_>!V2rD&At>add=h4L-X{5&6^tr*tB(xiya(zz2!jcU#4I~I`oN3I zi{XvNi&xIqtx3G8kz@_lYtYd)x4b*9ms(20^(H~T_dFFi&K}a`=-AlfiQDA>+&sv{ z_n_s511Nk-@@i-l7Z3kCG@%st*5P>pgfH{XIDb!1zg@xy>4qy=W!Sn-N6!mM@{FH6 za)K{1vAiO%g&lZINFapjS^}&+V8%9~pG0z=IqF%t-o>I*O&=JKfrNB(z8AYu!?z*? ziT*g44WhNM(Es@XDMVi%#1n+}bSGf66@rpfn1P>T4t%j0x>Di5wl)%wxo=PWaU9DS!lECqp*I6#AtItm-lyrkR zEac=SFopc?`mBchd<|>@z%+VD;$DoHKQe2T>0GuEda-=o%0cUNCHyI$@ zoK0$zUo|hS_d>DxCOWDJRQ|F%!_$k2PC^Z98CXdm!QjF7%4fg5bacNWqZIK*!rZ*a z-6lf?n+!2qZl2L;7MLioCmbAd4b-cO%j3PV?slV554oyl^O)=1k(P`5NKn{Q4S+)U zabnAAi|pTb(?GI=`V3a^Xh^J?VF4vamTnnbdeGClA&YNXZ3Lj7+!!U!=2Q)ct*1!y zh6mbMd}P?%W~1YO_U4c9@u&JvADeOShK4O4pKcXdzBsc}R8vdjF@h;D;{h=Ylg~DP zmfjj-ybwq38yHaiaDoEU3l7OemsE!F4tSb?qX>gv93-hV_p&1mUEMHH2$AS{1`_^& z#P=)PXZB8!U`Yrn6%!7Y3)`(f8Ea3`SzLUWPXB@|o%7xuO#abf)()}*=Dhj_1_+fy ze%Z`%-sl>yF7{W?QSqAWsR+UTN=n1k*Vjh&LOc6zea~CP62$MHQ8r-yG?>hVOG1{B!5P&3I8-OwNOm{#+k zvW#A{-z))!zw~tVm3-}Edy%a;U zt>S-~q$2d{=Enh1jbA=C9L||)N=#sOt+a`hks&tstUZt#EwxV1<%2I+a_xgE(aS4z zIVpD8yec8YkrxA-AoK;eOx2fk`mr!8?` zq#+Or*b`;kIMWvR+;ac!g5JYmqhAUboKW@AuTL;J91Oz$6eEgIHRHmbi^Dz$# z1T}^^^NW@KNH@lw#g8UhtPEReT|04zTM+$#$&MD4@fyb?XlixwAq7va9f1>ThRyc` zXS{0+%qn!;{H&GsJ8rWp8Zq)HP2a9D&TqH=YchcQE)Hd^WY@Orc~$6O@L0ITz=Y^` zRPj7ajO#%DsoJtIta3ts^#j}W9g2Y!YJYk;#CckG;(JV|FuJ2B^Lz1HX~kv|(}UVm zT7sdBmtn|HF@{|`1vJLVIyXNAt$B^r8wO5Dtp)i%n%)mj%_D1kK|8vZU}Ac4UG)J} z122I;U|r@bNZsfqq~HNY=$TtIpWWNbsb>N>n>gRs$hqo8FE%B& z5UHw6b|^FiQ~o(2&DN@zdehBwA<#ts2FJSHTwRTx zIT)zUFD#6_yLL6do>Jn-?s6d?I=2k~$41EyHfv*rFYzkq0O!E!t!lW!{99?^2n`Qa zbh%loC|jNUb*t<@|4udTV)DkhfNqyebQ~RQLA}pFh)qqk$@gfQjteHI!WJvZX3s3Z zot$lWXQV?gVt-~r9Rk|!8IpwY^-K~N*2xBcvH(Ag?ar@CIX@^W7)EoZwNRV0>II(zKji>;xLo z>6b%-gYVr4vaVlGE%GY+c-Ri3AT#q0~5z~x>Blc&kmDc_;@9-gZ3zKGhdfk)^$9`VkpCL=~%*DKck&7 zW5Z6Ik$gz2h^?HpTC{6J2kLRM3OJ?EPJZ8(G*?PnpQ*M(!AcG1R>mhM{~Q>IoW#7r zJ2GVtY&npkfsTx(VPJd$;7ghUuulOq5*2JEI{IFke0%IUQD^g@Nc`eGQ|$Qx_O|Bu zXlrZ|7@rRBN&HeV6}p?k%Go%tf1e$a@x7@@dGkl>{aMB35Cq*bq(>MUW8EypF0!O;AtN)V398VbC zg+pRbqw{8-9rkjI!~xYNMgssurLWz7#YAO5G6%l6iW``)+U+<$?6*)`Z5T~EWZ`A% zeeVD7rEzPE7{J}a<|>NtKCBrWYKvfhw4~Uj*L1rJy1=-LdA?AJnwFNAP2w^eZGwhg zyi}^oTVg^g=&%@9pu02I-rKAAOkDrfD>^DtwnGv<$=DRmoO{*E z)eOcD)BJ!L1O8m-moEwZiJ)Bo2&%073tlQzZon=>ZMsuU7k&&lOQm@TI=Dffos49$ zOn}29z{wUN2<}rb3u8P9syD}R#HCeMCdLHr!Lw`KQHkZQ2+M%2v^8DDAnZn@j0nOp ziy$HRULW7*x)DL=F*0h0Pn*UPHK0k29_E4*URGYd-QwGEoyEFdIB{6}R{)9}5l*`r z)V-f9)g^sQ{-|R1K+;6&)5MMe1_swEqO*@u7c^6YzCW*7A zS78H#AxZw}c{CJMRMYjIXi^DZ(MOn%zJ54oi-~qOI`9CDO+Iq$1oruWv=c$F0}4p( z`JD}it?&%_>F=*BkGPsBjOR*;7hZBA!j9|+kv(jz??QGTRUx)%9}xX2aUhcs<(h{9 z4~YLr|Lgs@((42$ma$2U4JvQvXTa{bza6 zQsiURzav=XTuK6p6wa4Jn~gxJdz+q)K{&Jc;Od8>Aom~asl16Z6-Kbi#tGDH+d+L+ zRhrF5D!aE1JP*+get;HDfcbQv+bgW>%G3sS0haihx}n|oPPcp1HHlydqnixC_1l_L zVKC_ek`w)>6#glkAvjbTop=up_h5S+^n0jkYHAyvaVONCIY_jS(45MS_0+rU%>8VZ zbYnYV{e1%QZU_VIB%uEx8+Sv`^Dv@GIh9v~^;a9&E~Q$NW1*@%O_j@;Cy54nbWeHZtoU~H z6#?ptibMJ2;o}Jj3gYJdRMhelR(T6oQ&M^aPN%P_fM^p07NInL8Spo8ULDw~*$yPG zv8d%#y3x3Mc)fdV`Q-Lvy+t*2clWWL1>ls^@n2ye^1$~1(O#+IBNJoW(o$x!uM8Ho zD5>G%;+b!Rd2fMg>hj;cNB|@)U3e{2A;>`6|3K4oRx(I`qJj+wPPF{lgp(D*m@pmB zGhTC+*4q2zv&GcOz%c78as-45`eC2n5*Vx!y%vMEe_&gp?F=V8M`8U9I6+#tifjwfK$K z3nKKq_22p(NlBLJj5_GQcnBMnLK*PoLw+%`vh@L~p4R{3>#gIW3jTlprAxX&LJ$nP zkuE_br9(QT1nK6Wf^-Rj(%s!%N=SDj(hbth{$@X)`@4_(eLU{t{;>-?XV320Gw+#s z&&=!ne19`u!gZ^mDCB5k>IK@O@%8U%;$Xh%2#solhvk}T;`}=(bLqT){lu=sMP_!; z_WSoE7U#hb%C^kC4L(p7cRHC1oB}X)1S!mdPxWxO_z*Y)1WOcVI&YBZ$#|&TLuGTC zUto))Z2Wk4@fO5&IQg5kS=cUtM;r`yBNsEg8*J(Rpz(+--!Xy4#@ZU$xw89oIu$6KGR=j6@5`{K_7o}IYLMI;bPj*#YHUHi8y(Xr9I$>l zU232{2=iR`s7L62mxSzw&K$Z%%x!5~`gR%CtQHy$?cQv<3q*x^dnM zD;X78bPkA|G^USQse)B|#EcZ6T_m$cjW>QwYtF;zFiMz}VtmH{7mc>?2Pdf}6UEAA zRA?d5sgqmBw?ZNfjSy*Asm8Z<47)ZAa;2`BgFOaHb{8Q!WkJKPa{(*hMMZlTp>Nt7 zBtYT#Wua1Wi_KrSN+V~e7E%htV0O3%kzA#j%d47iFeP|D4J|fCF6i@}*>sk#^u{oL zcl!5o8T_^Bl@DJKd;64?MEn9VqlKyJ=mf{i2Nk*99)kYF!_!lK91My!^7E6Y$H!{m zw^_=O`mWT|N>6U1BFTkVN`Q6iZ9*w&izyLc;-Qu}72b8S{uNYy-GYZJGh~ zBJ_Qlx;hi1EUr`P5D|zaicU$1KnU5uAD~iN6G_RLJA#{cV`f5#dyhcf?0iv`ltc<2 zHYAu}{~Wy_`sUvSc~y#a0U=0B`q*7X<2`)dau#e36)`QQZ?st6FQ}7e11p002Y);Q z3sVTGmo;S=L-YStrWhpd`cXMgy2`bU^Ns-#_adzp19H^*|M~^VnB!Y3S=B0lnQkRV z`rA3e;=%9Y3`sYTJW#N4c}#mI38a(fsM=Q z{q38Kz0Xc#8a3~@d3fIF>4jF}_sJg)(~+)KILfyWF5gHBx&h@LF!U%paNkpulY10!ROVsoxB z@V0})!(~7TST6Luc~1(q>|;O}kFoV{(?@;2gh=$bSZc(Dlcm#B$8k01<#fhlklFAQ;}IVSmIgug+~{zjICBwCz33h{NMNA?$xL5I z0O%NiT86~#9*VRC6k1+Wr&icA(T?LpyY@gsAk0AKM z`@%GHSsMiO^F}QO)Os<5y0A!T-j}A>3XtX%>7VIha`11ArY5^h6TY-pvTsh&HzK`$cKYFTU zRApHIw#XIFm03}~1Z%Zd_5Y#igSA)R#5{zr@1yGNxdXYDk|k_Zs^6woEFQnEGOw)r zLowL=4*(BT8#l0ij!1l2IN0grjga6}Pbq?EIrGjfKWOWf)!X9g^@rk~YK?RPV&0O1 ze#hBVm1;k63t6CAgCW*0?>I2K!Q)ht-ylQzs68MOX`P4msEVh9M71yunHQ2sCar8_#EP;f1%iUj4Aq@N$D(5?~iO z@kQJGu_fuB5wuT*^!RqPhk|=x*=tX%+1b@t(=I1 zaC6HQ|4`=qhYhotmNZ|Wcul6lB(^vge9?@#i33e<%m5z>^oS19J)G!2L*^#$J5@h- zocqY-O+Nf06C@P@Y#_ZmSWh)ej`lNEW1VofZW>DC1g)8YU1ZIdjjCTQTK$W~r^ki`yZgQ`1p4DXQ9Y>r8L?HJxEK`ydUEbm5jNFT#?{~B$OF(a7CMP!*=}@3 zL7P=Og-pK@IB^D-v$}ZKA$nqu2{g=>{KQqy8ZwA(Eh%94U5Y0DpMS^KZpiG2z5nZ8GX#7EiX=pBe(!zc7-;WRN0N=eyyVdwc zWk_*SV799*83O=UH=`s@l)TjL#y&d^h`y@G?^vg_4KC_Nl1QN(-GE}XHE(XoJH>s| zE0wK8_NQC!gD&v|S?6S4v+<2pClwLfv-`>Oo%FjIs{in>t;lbnx!(%E4NKj-U+ECs z6nj;1`{sjD?h~d%5sLX+$EF`z=mPjcaH%HN`0kgNT4g5jAX{)wQJ`?h>%{`KaMFB>d_ z!2uV+D%!?o^KA-x>KrX{$HOphDR^n0*asu#R5&BAL>%FVNbAHCNHaT?^$f$5rOTSn zzEg?k6q31-_e}wfc?7aG;qj@deQTWfox|F??ivAA&IcUIFLh4x&hOsA{F9QXY5jIHyrh9gYvpQ*K5v{2xin(SsX2=P8`ke9Zio7jNvO>(g`V7wtmcs;z*!N@Q z^!l0XMl3`^deNGZ>vL3?dGafN6E8YuE|FFPmDFA&PPyixuSSQ}Z%dz4ZKCKP?M>?% zQ9mz%cq&Wn)_*WcOp8yJtP()ZVL!yWe1D`4B&!Rs6%#6b~NE@S6mKm0H<*;yH2 z1U0YQ)gb?7y*j%yY&U`BTuQI8iWZN%#gTiXhKIe4FlJfI$taJ!U2U2ZC`5dC!et|f3kXNf4tY1g-9T#VhJG#H)wjO&7fx#H8FZ+iOB2;|9 z*(=$ge1Hx@Rdfc!LPAVzeo_4CW3m;8zfA<2;fQMe1Jn1v3M(*=-y)0(J7jVeE9IR(L#&IgSj{g>!h(2(l zzPY>`G5YoQBe>6mkNoQel*ZJrY2wCrZ;p2hV!uM?JZ45xeXVRyYbM$UC$Uh1lsYe# z-hYjEKRkL$%$PYGDUx}%9}-TC_0M(*1s3hSO!69gXvI{L6juAwe@(g!*vZeurYg;c z9iq}EeZn8JvV2J=>8{8{J@;^6EU*D2zdv{nhfyt6=yk3l{0$}BSIu_f;m70>zaMcq z`8GgEW&_B#!c!%i1kPb0VqNd6QwONnTbav{pDm#@;US9|t|(nj&MA^;q5E5Z@i=fO zC>Vb@Nk?%RZiR}}Uf3eBjc0L2Ub-XMTg++Q*a6~o%AeCJSf;44qBqsFOndQuzO2MeypD>)dbg7zgGwFayb`80I3QDpHD4)ui zUm2x;6Z_L7V&PWjr1VlTB1%pqb95QYQLoi!V6Qs}d$G($)pyb5 z{!|hwktBRc*y_LZoF3h(Z)dnYp(UUF$LKw-gtSO@KJY9Dbkq#0G+I=(r?W9~jTasy zMh@X$l8R#EOiaP(HUeSLTej3U03l7psE6N<2zXcCtMF6b)HES{`4DeT^DWFZL&3n0 zKbetW=y*v13pjbj0||dqGeWofGk!*X^8Uhb(fJIvGD0Wukc zbnZr&h-}UW>XIi>qS(b$xY-YhH$}zJw@6Xr+}Z0IuasXfmqHgaSu0BwV~Bg!qYIt6GSKtSf zZa~`!){bp5!3l8!UN;dml73`Qh8Qe>eyRor9d7 z{;-0QQca>?wrdLI&({$AyfH&I?puFJ0#p^y3h!GtJ@4PU%A~a5APyqca5(qjLT?n+ zf!v1Z687#)rve|&uP)uh2qB<}=l6(H_gP93s$}f>@2d5A4TMBY#1ZEmPU8QfSnK{y zWo2eWI9KLv;8o5`{DppI zt?ZCq)*OBWmJ$8!o(HBgneC`kEq@dSU3sgO6rZNWfIj1P?n0t#l0}Q_`O6FKuHsPL zpC4B@8n2?FASAJ?HR7UypjAu97ESN2Hg&bMR`^lcN0x-*=>{kHWR+EQwI#CS!b>aS znTN1)^MdamrRd&8*tsAbwXEwY#D|{Dqw*)Ls3i)NHX}VWM1xWI?3r2LubTN6sORbJ zL)KTznk!3OBMx?e4qdmtiH%hE1ykq4rHamnGu(k>NWD&c;?vGxG9wi`4jvo}98}Ya z{5mo~{JDeP;P6e>Y8w3Zi3^qx%pcS@$YKi?h>P0P^&o)l1@@uU2%_hj-1bc~9iqUV z1Bd`aK$a1#?c3#y49>NUKa<{$C|{iQw<1~&8K-{wfjvN&MBk?jG?Q=q$ZLo1Aoh|X z6lZRApHxw!WCsV|9Ik|^7I>DVlC9U?5_`9VQGXp$P!`E)cWX-7Svj58?Ktaqx7Mlu zsG%%28XKRyQw|+AvkW`gFDc`Frwq=GWt`*@-HT~43jf%eJO7y!xRmU1+JB0*qt>M^ z^9pf;JV#7^q>Pa)CT!t~(m*>-73tXQR2VM1Tc5^t*AeDG9`RWNwGLD(80$3$Fr#-j zK@R|`_a7KAtTx%mB<+A57^$SlI96aakSjkc<&d-A(TFpUG*oV!JO&>!P-$#+9@ad= zNl%7U`n1VZKsM)lP1X>qd@8zQ{%EKkO6$4nF|XrQVap11{d(8PTT$_<-tMv0%{MFA zM$h2>c|TX2gou?4$Nh@P(A|FB7O96`r)ixw;nX7!JE?R;wa}}^4JpIDyjIb9dm!wh znBx|Tq%lPwD*9`Iaw$dtecLs-hNp=z(HF>yoRy6T$v8tv)ogyx=Z0Q?Kwr*H3SIU5z zAqNmLaPz@NeZGr$a>M@ambJ+$Tj9Z84f|WgtH#P&`%2vY`R%%W5gpjdRz0NT=Eg7N zvPEyvvB2U!`Q_Rz#eQNZ;OCntmY40{P*!XQom!v~*R0fDmCnaQVKRB0AV>=5KJ+v=X2Cl@dxc58SKQcAOmBbtAM= zupa;PKK1}d>Eh(Z%!xUc7Y*Ba><``MiHK+75BZIoq2?6xA%Vjea@;5~jP#e5>ujgP zw~l(DABTJ2H;WN3_^Do^d&Ul2Xw;0Wo{!o)LnejN(7YN24h6h8ahlS9^RGKicy zyN@1%_*^Q#J7^tlO0WEMS0%AAN_$Qsp2^gf0z24qu=j_%AwMl8?4fn^WsG5cOJ9pB zN|KMAdrixdhg}2nQknkREz`dgLA@!|{?3N@o-@L?a>NBcKcJQGDsbiVv33oM{RwAX zXTq?_#Z{Y6q?S(g)a~85c1=1% zyu!BK)*CGs1y&?Jg&nqO^E=5g*V#}vU^+5*oW7zD0xDpQE28?hY}u@@p+^(`Nd7<) zY~J(EY_d=j^KB`a7MmW^9fbbJ_k`I);Tmim_eKtX0r6KrZ5H$OYp2YTmPjok=h>h2 zeyjb7i4tz(xv#ri(3Z13^0T0xzNwV{sn&bhb2*GQv|k4T)z|@G91ca(>sWz*$h#DO za_Hw0l7Y2dqo%;Dg9{-mu4c}|m+lXZqOPQunWIgsr&Lwv{L5>^8y3{-}}dv zj1kGN;vZcnxJ%B}tJWHr*x~-?ynR0E*k;EiGPCxXc^LP=+=QJEdBd#KFvhO)Bk{wD zxC+uDB0Us00|uM%_R_8{^!zE2XK_C)|Hf#Ws-C>8*^tkGJo+7xgIBXJ4~1UZTT1BY zU%Sxj6<*|7KcEu2EctrvZ)F}Jb1K_Wh_c2yK+1f7Z?j$htQd~b|D5_UZsBg0)0eik zIbP;Lx~ETzKk4g{VNr^psMEQm1WG-uaO=0R}))S+Z+nTe9c-mSia>` ztz?rp)iQ>6M{T9!sn4DfHwljhi8lS2;TBw5?JVE0S#6o=^2v~uQ-Jm}*o<jEo^)vbm78EtB1sR==RbKH)JbhV4bz!4KH~-3`WDxkYJ}1q&ME!!dIjU|0CjE2&q?c^22~HU-WJ#t#|=d#k_Dy zP4U>7+xOb#u@^M-_)Ee1_-~+Fx34={d=NWo@{|+m`Gx1)xh%bbggF^zXhA%jTPOc2 zAH~Ue0Of6@Z};R1)23f3c^yxoKNl+$#w#o~`{Ktr)JR?1`sU}}LyGvemyaWqnX!t@ zYACa${_QTgDc1F!IT*NR8eYUnD8aD%9gX|QX&DR{ct^xty?Jm4b8#LsD&QZI{v?@f zo12(F-gnXgsBg>R$?nC5S?j{>xPI;D*dKD5VK*21ToDdq{{7cf1vi8+YgURmHmkLK zuC2d|5Ef0d(Bi-9-l=Ps^7+CY*`#q1VfHJjK531MWq}eEta+zoE99TX#Y7HXJeAm- zHUlzm3!X-IXJx2L2XyhyxIDMtEle_OxuJiYDInzG+Sw{9Ji;Wk7rJ&ZOL*;*lIi$w z7`}1qvt&3{p#B5_%gKDU{twxNNO~LtIb(bg+Bx$AQkK7^DUNQ!hNrp}_3SumN2bRbAQP4m;$U9ys2 zVuOtU7ZCP~b+GmpF>d=tU@-s7n~yMPem-{)<_7h=)|L4)xZ2^uhQ;|qLOv~|8xyk4 zzmgiv>vUnea<6W{dCe!i50XMxY#pG`aB@G8_+IZJgqtrRqJB4vGvNDg4Fws+)D=^~ zSHIYU`DN(xd`bI|N_9Ij8*xcWT0oZj6XQ?T1Y;<|A3vW9{ZVp=4Sp(TZV?($5@h7k zReNE}bh5roZV=;LOc+E-1%q)~74ty1(stwr){QUpD0mg4T?0dB+Cl>q-H>2=@IpNO zrF@ej+*cd@uVU>F?lm9VuK7rYMm)k7`D!}4xQXpA$vD{mP6egr0i2G`<1yOBAioj( z80jNYg)kAg#O7Ltm!T%_kpC^Cv-xb;{;=|`fAla>xqXjm<+ux_9ZQ(D3^IoBC*lFR zx2TXBNu4o`-^ZY($#8!^)VSX#W{P8i!pRn&ZMIlx)rwHBx0`^lHy;v4S2H!qee4fpU?vz)$KuEmIp`#F>o)UFF2U2xPd zSLhUF3M7i=Zwnq+UKOix1BQZ%3T^pk8^-*Zv6JZ>A+oL2^MCxj=eH=|_8K@T#Llg(f~8pOzk?U>E*nz`>U>Fq8&& z5OsS+MZvdkMewz?|1)*9V)agCg7MUy=gZIZ%}HnSU}ER^SPMi>qhYZT&t zM6f~10siiAN=Aqc{_iKg;u#1Q-~wHX^$V~!3Y2W$;(_+ZIrqBuyP)+~5@|0zsz@5~ zNV~UitKO1>Pa3L^6WINal?+ohG*DuO*%XNIpbJgY2Z|FxoJWF3GH7GqDvaoB0sk##p5UvNuXxjGn z7V?DC7eYF-$ZWoQ3N{PNhus8y{A0{+o;FuH#W(6FF zUW{pH(B=wK?KRsY0i$A!ZVSmMu?_3`{TFAhSDn(?P}=5woBZ!9o2A z?dc(L;BjEk_hhKBY}Tb?z=~y*)(*6ST`(F98@njm*2V}#SKmW)G zxDIKalqCsago*DBo(#jH2A^~L-Fg7omm#S7eU?vr&{t8YlsMMj}Y2GO!n zJk%U4T6%6T)e?adEBWUxIFUWDOJdrp;@INC{hY>MV4LSCrhKeYpUf6JR7R=L!2xfN z7e7EDZC*buGVp78<@?-Ew5?Tl;XlpFbga%4lmefU3lGQ_UVlGtOoCo&Kko9}A(b>- zlu}AqSzx{l68yWRawd4K-eQ39cM+~ha}H?+rSVVbx%8VxWcdHh!>#$++S1_BE<(WW zYwzG|Bekb!l~j;^10VG(g}i&luK00*qa=?{$3b>7$d^}GNd!y$!CI`CRy0bM$kxF? zrf;CV*2X_J>#@Z<0Zq-9)uvHTUZ{Cy${l{UTulsk|3ca8H1cCY-}p6Ks9Q$?=ugr~ z1*Z(6ikmVj0wO<4XH)<`x}g5ZbnwG?lo*D)-aU|Ws7Gw2nBhVt%`FqEnfnm0xX@YLj>jkI)~8E zF6?OHo~Sw-sHD^%X<(9LGcuHUKqMB6$LnDe_Jh8ILz(f9RP0S{#MH}Es~zS3i4wf% z62*<_&}!REm^F{g;B4-R@^DW>)*eE$bL-Q~yMOYiiqH%n?@u17a$ZP}QH2Bx2}#H` z#$i?35x-AfeD%vSd4=%$;npt%C2qg_>}2*hpNQOM3X&;+e=JEU;3u-~bKsY_0a<&vJFW!{JUId8hZfoCwC7mGEJ#W3#Kc4}y9b%Orz(P>%B)1rLY z1Wa6nmsD!2Of1B%sQa?Ue5Cw~)@n7mju!3jf_xGd2^lc_7e zZsm6~*1{GjFEFu*&WhiOW%j!WD-gAalY@pHT5)u=^`}L+ySvA1Yb0Z!d$Eey z&NVpQ`$gT4t4xp=L*p$gCGWICQEtY880L4;*Tw!XJiDDF-g zh&mqW_|PPqRP=G~lW8ngZ2XYhKcmPWwE8vKsO?TxfiDTDMXME}MX%h71WErgSp5cN zv=(|}W<~MIDcuJUJwqR%S(>Ai5}M>eg=_&scdBaEI%v+ASfqDmN&+``*TKLB%D3Nh zJ5}N=G^3hBw|(V1n^fkvbN;|4GIKu#e<4WoOL|diWc6D&_{*?#>&lr*k>lR)A=HZg zN$FO-$`%cl`qU|+>2W=^8;bcu|H8i4=H}ZO2l<~e@`-jLpMw{pOvwK<3Upp)6=zbV zIE?@OwLM^YeRm_Xd~D=pIk%33PY9f5RGZefKG{$#!Bq`S{~ta2<6UH@qZnl=$Uix{ zEC1B^m%q+OZkH`p1cKm0Y`1Siaal~Ek(B@qnMZ-h(VYs$-y$a|Fs#^YL=h}#(a@7~ zBuiUcpd3&NfBNBfFi!o-BU%{NuDu>0&ioR`L_d;P&s7?iFun(DJ>!>n9I1IS*!4Re zLp`e~s7q_+QcdqHHYo`!k0@WBIB+6+6a5NeTiZ2=BjI<cp+;){nZqf$Ajd3UE!OlnArQXcVDj(O3;#cXok`%i0^*jNjfv$>1ORq z3!5f0bRj0W3oQRtqm#=ftFr=v_PL?=6`V@DDj(1*&!A^z?>}42t+>U)E%ZvtFVQ#$ zA<*ma+b?(+l)1h;#)8=`)Ds?XG`(`HZ`)%?*7uG4o@(Uo^ZKtKb!Wb_V>V{yl~Wd} z#Ps`i4b~Ct8Tb9LWD7Le3(5HYAUW$m9bLsm45XlvrC-r249-m9Ee{ByzUe_GMb=b1 z*~5G4SL13A$Bcv{n!c73wSS5~+vmdNn

sj)+!3lSnJgl?{SqBDY#ji;; zH$jH`6jx`U)0{^7OTkJ|q9H7ORzsd$TO$iNHOa>2t*{O31poF|Z=s>K;C|*d7JAMx z_Q@wpa2MFJDXE{(YeTF`Nf@6?|)+|h$(k|UU;LW8#UI4 z-Ze-rQGBcFl~$5rmjLO)y;7?JQo|P0zE7vTCRBP}Bdpr}N_(H(rf~6P$zt3070+N_ zKhQbNYgYt33r(K-Dn2(?wO3&YR7~?+k(mRiS!K_u^ zHsA&?8Es45WW~~_VSu%73=gbA9^T$r&CL%%Ky%5QcF}X=r;tm#zD?hZEy3GRNbV+y zjil_G9+9xJk8;$H^@-`)e>w%ETgFwqUq)B`ohvC5bv~5X%N9318a1mDlfClt#_jZW zOVsioxDo`xD07rnQ~iXTL>@Htr)vkz3k+rCZVUI)BO=)TE`-+ zm6lK5@_d9cDj&Kc0xm4;>;om%dMyFQsm190-^TCxd#C4Z3I5FWRCf-cj$Zyl`nUW1 z#sEICRe&a8R-V9*p+<_A18TC#75Zwc2hyc;e9JSh))|mBPh{#GJry)=}R+n7j6_a7VEYzV}9r_Etd$lE@$A_o|K`V|4MV!CX9&YtsF zO>S>LK6DY z5fq^udhc3cB@`&RU?s!_kEp2$wt?inj&kURM`Sd;^R!gm=+ES7UX*G%UWJ+F53DR5@)fdNxV|a+cR@l6jx)arO$7db~5t+TI@W1M-+p3Q=zjn0%te zPjawr&+K}%Xg?rUP9Bnxjo-Hyqtx|+k_A)1?fT{2#rcR)VJUM|vPrh0R3al+4h-b5 zs;f>teOeSz{0WzgigFP+--#S1r7jv+Ym}0eZS1NI4|j3&jXzeWQI8SWJ6E; z24Hvyc3a+c9xm>}%V`{%WczQt-#{!GMakaanAjSr`7w4))m#qQsEqoEgdPcT@p zkS9RUH~_9W`8=w;SZ02T>%)HpTh^QPr7x6hHqWi6MjXK9#m#ey*p;k4RX_|t{!u(4 zqREMxi6clhV1h40{6&%!dy<2dID*U;NVbtpa#UVGM}LLJ*cPP6Dhw@iSb|c+PG%>! z;D@@O&MHMs|AH?W8bqkN{;LR6=CN``gN5xxO|@=7nr%!pg-k}RqQ4fMd>*0|B;Qar zMBCCr-Fivuj?UofiiPKEmsfR3&VN!(8#Hxpm2X~b?mvsyM@tKtb8{Y=S7)um#**LPOs>n*u_x{O zQ*0-&*(}=SRHQ2W&bR4*y*KJgczzJ^VYg+ESXoB@{7Z@`+Im8AKuXVS_WZNHKQGH+u*BG_F)!zg^%Q>- z#d`G6 z9xneLNP{Zq)T)3l)CAh!Gl5iX&}SA+p@7B%+0SmYCxV&8wb7@uQ3Bx3wya#+E-}(b zqwc{=mb3;Rt47T{cm4F{jsn)c$wzu3 z=tF)l@mf3F0da3ZAXKSR{f7!QJ$LVHY~>of@*pweAxL#r%zV3O#2VVodjA#v$=c{E zX2g?YA6cYwm3x(!w(?7-8%yqNCEAH>pIhlc?(SGWUx~46%TwqTfLhavU+WZ|>A!Dh zJvNYn{jHfcMEowmU4HW3+ot=0WyA+yS(zXMlSLni&)KAQZ!?R*SjhE@M8#AQN)So` zR-P5Z2@o@Qcj(}4m@8n_06jk0E(p{6IT1YClB~e*dtFd1Vmrlpd3pJFP+$oa#>!#P zcbZmSa$I(D!UP*ZtAH7sxYYwoghhY+3G*-is@7#Ta&<*i*y8^yc-Q>O=vnrs?slm( zslkLRa{?F_9BsGd2v--W-R~;xF{@18k9DcDtIouGP5}l~z@(;H~Ki!~vhi5j+vUjX9NY4Fo06y@8=jcreelPRQhLMNO zqg3Z-KLLjW2_`l6CYL#U8udu~d`7}i*WT^dFi9QF5P1SitLA;!jP0j*)&1{K2;Bbo zHt!xXh!aU3mPK5qWG;o}P%>6p7M)4g8^>A0DI60_->*|&6eP3;o_SATK1dhA!2 ztW4bOn(6I}2*vM@I?ejh;P|a0M1?8D<09~kf=u&46aH%wo7S}l+?#OQv?$f)&ep37 zqo;pkxSj8%qp$%Q8w20w_Z(cgvskyP0y=L&4QRFHmvGuhdE{)E7~#50#JPFk@b7TB zFAcK2t9u81zLlcqgn2NT5b5lyBTV^8nqzFdwtecC?%Sw}y;x*e`_0)Q_aHoM=mZnS zdN21x5M3+`O~vk8t#56{mmgT;-(h1k`XkQ%)XKS3F=u1MUzLA(C&{-_xZiq0&FGu| z1RuVWKB8E=jtf6Ub_qFn%vLp#7uTXI52(A@>pV+sFllQPu}j(8V|~wcHsF+vENdde zI25r1=BBY)4z48TKv-H|G2tKZJr#d3WZX5E_ZpluT7Ap1v%Dp#P(-N;~ebk1bn?)Z1+$%d^MRoyDkF7Bp`(XM|wyH zWA%R3Kpg{bt_--n(WHUV0}eLGK?1=2Qkt5iBBG+B6=rg<4eH8|a2TzJ{uT8ju*Ox% zaH`Zh+y$yL`z=gD=M|<+T7iiZ;afKNX*0*b8!@<$xKZWP8!=e)@oikR{{>6hudu@5 z@{j>CqdW+JB;$h)H6~Cia#YTW6MzsA&!t(WGx6G#(bG^bAucXpU|@j%H>9CKlB@WG z;XR~xNgx!Y9-0K2&Hdw>9BAAUy)bo_T z)#8--UD^KE$&m7fjy}t3AMzQpNS7N^f-zuWr+4bK-_jCMLe7_wNrefw6N|llC*Bb1 z;R#EO#Cz)GhaPc7w>lX}3xg>+J^J_H-%1?bKe(#yT{mhpP$8}Dr#z>mf1(~y@6eGp z(@^>3x+^%MOgw6u{wX^lAR%g26WS@z!S4Hb{?^jhR1dsP%S?hoK(wPiR6O6}Er7i1 z)&aKQ3#pJ*1U~A%9LTZ83b4udO^H5q;;`$=a(PK63eO`l`uZTcYO~MrQ5jJkM!`r{ zlK^ZKwk>=nYI&8giy~ndLnukJm}8-J9T$JRMDrvjr%cLP>5_DR?~cAJ=Fy_twmCZ{ZM;09ft^ zT_bJ(AMBPW@%A9KM$>`KcdaNSp5^h@imOH_mDhUJ;X zMn8yrExp!j6uoJ<`4vh@cFLW$`sGx+XwSx*24^C$jjooImA#KKqn1%{-Y+b7J- zbKy$Q@~I!Hw=%tsmuwb%sJh@FGF7}&2Hs8 zs8lG{y?;{P22LC~K07SEU61OTuR$G#by}up;0b#1lAfL63L2i#^TVNWtezs~jMi2p z5TGL|O~bj{^`o69_kwG}==A8bPNN?cneQ$A&Mx(bQ>*7xJHMu%6)c70uH5te4iv1c zLxQOqW_~u??84k>LT!y1=8i#wE%m-5oN&i_D9j=28G;A-}**m9o!WwMSq4(qtc}j9ke&%PQRBkf@Xlj> zFOGjhhA@D=0prI&JoOmo>8iQ*{t6)`9fe;M3H?5O1vqg4!3V&BtS+uXG=A6HPY^IU zS=5s6Z@xAOwSF;6gN4w1{bzMb)qF8e5{8{!f9bJ-`(gLC4ZIYgYC#7_KkN~6$!eKf zS|Srbfp6>VY0w`~_`B{xQ}S(6WBaT#6=p%^f+N&^p~`u&1JQt+&lv_16jF1W2UoKX z^+i~Id}{{wc?K0J;;V!HW6uNP?;p&vz%^7-;ao>0O>gQ6*Qj{mWXunw5#x%)032BU zC;+P!8?_nsi7yU)f(PFA^i;InuTzP@8TOgsW-~@`CkWSj4es;gRj#zJI!5#9%4%># z#LKC+H~Oyrp}UDps2uJPA)}&O5tbaye7*U zDce){T_N7=k&v~4v2l8!J$PcJ<UIOzjbrLM+{X`b;VK;ybSo3_w^z(gA$0yQ-?FL%rOXFzef z{2r}Ls;b7HPgT0OGuEeFVd_HZeXgwhzLnTdqVBLI>|nSL1*3nQgCyHIvQxj!+Dylv z6sfKYk#Q(*`vVh99<(veWO10Fy=l?&`Xf3pxZ#yU-En+Jd5UTSjT)SAlg<|133sGF z;WsMdg}hn}ZG3tH_PR*37>H;85NLUKGtbXFy&yZ3f(gUi*^2CbezVz@u=y-bV}fhEk&KDdb!*)-)bsw?ySt{$j0~iNPP6hw zm52D2O-2vc1AEdz5jesJ?jW6z3TQmBoCP$RaS;9>17 z%yyz8{W3~Jv}QWGG-~W@CLp@crY>$@w|Qjo20O+UfHxiqi%|o}G|{Sl_3K6=rNSWZrlTNHeH&dtCQ!m=^3RcR2C~n72tG z!#;B%xn908@vR~76I-yf*7fiZtUMgjc)OHHwD!nh$C%_VE9VIV5Lv;D4-XavJ&&^u zj+a0^VQpw~RrIgDsNGf@5H@Qr+*pW*~BOP7i@^f9Ih=6qw6Q3E&3c ztaeEWC*V&Vg@u8 zF*$K5muThRD}v z{czfwxA4Pak}^o|Ki5>|E7plWN|zDS(F5;fBPO_Xre}uMHW_!u4W)*fdCYsGO%Rw! zKlq*d?+=2hjpT1^y|HlK@y4{Lp@6jRQTIQ~Do&_JhLfGY{K5}Yw?qE^ zC3vVzc=lw#ilIuGCC?$EH6QDm;mEQL^tK4xC&yeZ{-8{lXRY_+>P$#^AU56w~p;bl-kjL1!Rl0gC_BC`}qw_dFQFvEo1G4 zshrOwD6s6jJfr;*f$**?BT+*ukNYKl!2b%Yz*8=wB~dOPfbSf2PUgO4H{ddK^tD~P z5?7xjZ!L6&`6K^C+nesmnh)A5h1=7)5w<2CD$ClqAv)-Z3BDCYO(WRe@lw`|$h_5( zOG&xy9)wlmf%I$VdvN~~$7dqwh?#40s8;Q6Ud?^mm-n&fWfAo>JW9h@k34RnEwhp- zhe`V8tFQ+$f7ZLarZR3{qIXszz2e&#PQ!=k)Yy=&4eQ$K@Ag;Z{eNt|1zc3o*Dic$ zl$Ml6q@-KAyG&H1K}x#&ptN*IhqQozNOzaCfONNXH*+`dec${2zu&zx!^AmfW|*_r zUVH7epY=TAV^M?Ha*6yONy?s1$=p&9N&-NSFQ5lzL4Z(XqRbR;WVnmPDismqU6HL9 z{4wOhlp9l^qq;VHXtENl@Wyv_et9p32w5MG{!rL=@uqN$1V%C7(5}X(KAK$l=Kh-f zU8$jec}hY8CaA?&1=XXhD;Ih-_6&fu%vNQ!Nmy4!)cND|2M{2aE&&XIM`8I&&czNd z;&9?^6z?9kxdk$MN9Q7-H|wHunfa6zc-Gp2M8Zp7`p9A6;e!VcK2bNID{c*>35Czh zu!ZF3zk7ZSUq6w?sOe91k*}Sk;XO6*1bU1crv{|C+$pfQ?+lE%nK&8|FsBmR1K&YV zd&vtdBdD7DNs21JvQhK7hse9W(DpArg7fsc)X0zO5_34xn3$WSpOpvQ@Qv19#AEVL zzEW|Cn!72c{h8&ay!~O1l!w<~K%zd5&6`_NYXIi7Z>C`q`DP#dls-ySK-0BvUg=4w z6B$WXXVpU;?hF-j+#K?StxXGVUVw0WW2%6PTBE#m|H$I``}v@Mug@mM5RFerLq7Qk z<~bXlKD52Pz&?}>t?A^Nd#lp@%cC z4g+!Tp^1s0W-ONOCNE6D?)DrI#Pnd$SXA8NL`=w1=-F@Ep&(0=Z^$=VYHSxVbzM@9 z6ifUiX8B=Wcs-JABLtY5$aqb#y*sG?9zs(Bh`;Cx9=|0%9-%Ek6RI7uFg&7nSmk!v+Q07 z!OY@!mTU(Hj_sxMg;C!5nxiKsg8M)C{h$x8QM)3(%)N=}xw8HFQiFC2eHZqpq=#Iv z2s^1)NQDy0Mas!2SnFqW+KUy7mn;<<(~8HvV?K}m6gSlXG1Dt5@X4ST8R66w zeDmg<`D?_Yvub9Hs~{&iK>bv($M6%=+PBZbNPPEBd$Hu==m)+!6m-O*mV zsdXF;9@&t(-X=S+uEM?FI2Gabfz-PgH7E#E^st-${Bo-#>g!j3fCL6iy%)84i28Ro zKq2Ff!5T=Afjrc&kVf`X^$7)_h=ET?A0BMG*KoEppH!0AzW?o(;Md1-4oK3%;t62q zAUNA$uA|zEINN3f8b?EBeEr2B+YH~}Am>3~Uo z1O`)HB>2WVK?gvKb%#UNLLwk0_Fq~uyko8b1(|J4=znVDR+&5*1MF-Dz|x_YPh#9llG z5OEBpk+r0nN zpbh<;{nRA00z3(&CJy=eQ=LrEnQI`GQxbiEclei_C+sN-2;%{h&V6X236>bO;0Lz= zJemPZ%KN{;`?AD$>~Xom#KB$`SRlj8}mUlg+bfRg-vy}8zh{ekvM)1pMkirAZgRbuGnIKbl z2WkQ{V_Gq=8Yk=M$X9kP$mkPFDf9rOm4Ir+{>cfz!POjV=ZzQuQo_Dt76#4ngc(ny zW>0d%8j>+<76FFE3(}g`M?h7Cb!m4O*VNS1d`Jh}Yu}HMYF~{3Fx-H;I+5rA&yL3d z?7na#EjYnIQ(b<+p9ob~v8C(r3wK=|9UMSAW%=+}uJHK%hvpP6Ay2uE4idfd=W* zo*Ek_vRbg9EsB5!CH}uF%w|}saBHMirbi{vkFJF1{B6=vv;-bXOs?2yv=-( z4?%#{lLX#$|9kvz3=8OsHxHzWAVDH7yX~J)Fop`>aMo>%?7}b@q_eN%zkXF3*Oik) z%C7)9rBx zI&{5B8s?~`0XT>fS&g)$0K*&j5F%nykagt0dhi#lXtk1^VPyI44JUIbO_y7WJ*C0= zFDM)Qe_`1ak8fu9(f(G7+)e+ZL7}E#(V3h$WfnpX+NlESycetl>oS-KW0g6Px5hrhJ%XNpp z^KCerWwmZnHFc*oo1 zr~@z?GTy}?qFQdzIilp`E$Jxc!#{uAJ>&*vJ7TmLV9Y2S{L4uK^t#FNd zU{Q(8i6fjys-EuGIH%8ln_cq}-haE;w4-a@6}b5}9SK5Qy61IV&ZxNc|BXoVieI{% zoO}mZq*~|GsQqc+YfeaoSGG|cIfH#qk*?NjLe6$}MQT8X4@muZCVYGW?o}DF7n0Vb z?)G&jpOcPUuYibQfp^e1?BSy0zXkXbE0*pyh= z4M!~TQKI8(&)Mhn8bD&rxvbXnUqU21(p5$+nCv0m;k6;L018f<|lYM-mh#jmn#CwYW{BvVaHM3P%(S9Ng z9%PGlIv&#`It<_!(3O-M=v=gxhSd*m0Y{THjOpUyVlslXwWjyzm5i==OYPw&Bk)NG zFRc&F{~NsVf0W#`266g7w}Y2B|ACdL@s2-WLb8abl|=mrr%VY${|&;x4IH@@B0Lq( z81&fGC9)nPdz$h-%5THzPlcd}mu9lI#|~XU@@fF9zQD<}YVpmJtD!W*MO$mMdcvFT z(ZfwE?^!kWban7*N;Ld^aEG^+{l86KkO0g}6PF>mGM^TWdmnsyoI3qqcndEV^mkre zbFhHKUg6$^ispRk@ETCuswudr6-NothrJe>2t3Kb~e- zig9mvE9fMPeF@M-dZ2!u_D6e|Zkcp`{(Uj+0P@`%8c>wiS@Vj>T8PWKdSm8Mq^(dlu< zp_IX1GigDs|GN7MPt zA3(IiZt3OXUK`9%v*5gE+bcclwpf;VS{CyMp70HE2I+da(yhaz@#sIpSvhaF?XHtA ztr#mCO|<}+f(8&sP%;;ZIB%0dS6+yB6x3EH4bvocKN>}R>#yfI`W>~jN~&+EJnnhQ zB-t?ccl0)OO)}u!op(6A-iZK3Q1o8cHj3*5;be;xw}~s-c&z%0{(k_o8^C^YWS_I@C6c+ zMyqaZ|B~xDzEqR9QFfUc`On$1aK+A!Bb(m;bDd&sQ@%UiZSY47)WgG&HL~D$AcY zfdPCXd-cng`%tsKNjWU2!@(vR>L8PlatdtS+~U0nUGPCDj-$F)WCJr zSw{ocX>VW@^ZQP1nkNkrDtTMi)!DMFBJ<_?+QPfUdv+SS)Gv7%f9;w4kq}hS5+``@ z<)%i+*~U-ra3ne^cR9Yx{(z|N`fO(!CM^N%DEvMh2fAQa4K(L{ZD%6yGjn|Iitq*t z$#~Gc~M_YuK_lL=h*%SL;1iLSfH5l`JG zgQQYt51yynfxR}Qgob?+oJtr{^@u;tyI~jc1NtNW(sjvPLQx-kURGhi`mb#{%{UQA zP_;Db_QmFNM!TWT+t_~=_3-L_-G+L}KW>Fb%7=!5xQd7SP;)2Xt`KuShM*KN56vl) zhVUGMQg9akRD65`0xM*wprC+jq5WR0!2 zx`t~^qBw)ajysR}5BkQ7e1Ztm2Hf>l&seq#Y=x~DF5`1%Hcl&Vhl0l;p7D{fzE@n` zZuhU_#Dgz=BrStoQTTHEge-TxDm$)2AW1=A>h&>l(UeOUDY!AL7(>4_%wL>e|5a|& zS2eXEL4NMBs&E-<6;mct%927WXWh4(F1#Du%^k0K^fv5JEW^DH+5$0wY)ydptAaJo zXq(=?S2MA1ulMN=>d&?UKa@C|%|V`9_gi{mpypBhcVGwdyDK~?DJ#QP#WfY(G`Aex zLAG$LT$%}Q7#p*>x>b6HqpquVA&GHxN-iE$kgr)tCn5NEE$G2rR~Zc#5m(8QW3!zmc}u;C1D$^#d3#kYmOwcYPc^np^S#yfYhk&Um3-6^x8=X8 zL_pUrbD4JMCGUUGyF@}ldLj1(23mHLWv0aZ$Ujcdq#!y}5LYeL#eZ#NR~PdNfFz$k zClGet4%(Ra{%E3F>XW3i=`zP+jv_f9){wAS4E(F7=eX{5!?lBJE>$#kAa57kR*Bf| z(y+;Ev)PRK{!bViTJSo@Vt$8HcDalF!r==4Dd}34I4%Bw|AlRzI4x{tgH1zMH2>6< zKJ7+G{khj~nJWo86v3ce4Cu~o2X83@<`Mvl>m|o%;l~Y`yYX z8r4J*4hG_sy}yVhPOZ2RkGyrox&DqlVta!T=8F>Ui2JE7gf-Nug3MEh6!_r{=~f8p zB+tr}_hI+?>WfkjQ&B#jdwjc}I5`CAD3Zx+`(()S6RPrgo?m7>iS|GY<^h{NdHG36JP%z5m@7K+(}8co6FF1Auy zDqy%GPo$n^NsZ-8#&K@0rn@yOJa=t5~VIEG0kQlulgI?)h3-z&IavIcYVOz`g6@+i@h*l z+MgcgP;x|FK1Rh{b=9$u5&a5U`$r18NdALWTepvAt~Wzoy(ebaz{a)6WR#yzJBcan z-IvV^4if>ojOV{IdL6G``L>t{W>Adf;q>KcHvsRUssikmHjxO1%SPWo3WU@TQ1Nx6CNyvP+wKt?HJ%x?7G`Z zH4m_zciUNeV~B+C-o!B}t@Rf6Oy0Tq=wa!MEnPJdhr=^Pn~vkvr)v8@YsB}yVjttZ zGEk2X-zZJjx%Jo{j~3Dq&V;|+cPk+&j^d)C!NWQ`7s)pFi`FAA2#y?-2ga! zS$TP62vD_g=hSLVb+%qdZoNd6@&ThOY75&3&Lgy+R<77I;whW1Y4zu!VOAUps^|6CBCR&k;Q1 zX~#xbvIhhI48n^@>QSFNdu|dB|6b!2WO!+hUPAf7!1;KKUXv~!_;|cy`;IEsPWsh_ z#=@PYTt%U3y!m3X2n_d{OyeF?sQn)e7!06D!

fDiT`I`Z(ct>`S$g4+j{ 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 X>j6$;O8o!;5CZDT+Dac3tzZ2Y`6P}_ delta 25636 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 NHI>K8Ws0W3{|iBx#T5Vm diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi index bb7a1ce9188484dbe1f6f677e740cbec0b82dcbd..15a4adb9f416b181bfea225866b636e022f8099c 100644 GIT binary patch delta 19257 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 SZ2lU64ofpz(|Ti%oBsh)G5U)D delta 19613 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 Date: Wed, 6 Nov 2019 07:43:54 -0500 Subject: [PATCH 032/238] Revert "Revert "Updates Naval Officer Outfit"" This reverts commit 24cdaeb41369766594855953821c895747f9ee62. --- code/game/jobs/job/central.dm | 2 +- code/modules/clothing/under/miscellaneous.dm | 23 ++++++++++--------- icons/mob/inhands/clothing_lefthand.dmi | Bin 92390 -> 91512 bytes icons/mob/inhands/clothing_righthand.dmi | Bin 89497 -> 88812 bytes icons/mob/species/drask/uniform.dmi | Bin 346838 -> 345141 bytes icons/mob/species/grey/uniform.dmi | Bin 297266 -> 298477 bytes icons/mob/species/vox/uniform.dmi | Bin 316925 -> 318344 bytes icons/mob/uniform.dmi | Bin 377385 -> 378630 bytes icons/obj/clothing/uniforms.dmi | Bin 92825 -> 93213 bytes 9 files changed, 13 insertions(+), 12 deletions(-) diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm index 1d6ae0162a2..695e4a1ec39 100644 --- a/code/game/jobs/job/central.dm +++ b/code/game/jobs/job/central.dm @@ -66,7 +66,7 @@ /datum/outfit/job/ntspecops name = "Special Operations Officer" jobtype = /datum/job/ntspecops - uniform = /obj/item/clothing/under/rank/centcom_commander + uniform = /obj/item/clothing/under/rank/centcom/officer/captain suit = /obj/item/clothing/suit/space/deathsquad/officer back = /obj/item/storage/backpack/security belt = /obj/item/storage/belt/military/assault diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 9e009014deb..b82a04ad263 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -96,20 +96,21 @@ item_color = "centcom" /obj/item/clothing/under/rank/centcom/officer - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.C.V. Fearless CV-286\" on the left shoulder." - name = "\improper Nanotrasen Officers Uniform" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." + name = "\improper Nanotrasen Naval Officers Uniform" + icon_state = "navy_gold" + item_state = "navy_gold" + item_color = "navy_gold" displays_id = 0 flags_size = ONESIZEFITSALL + armor = list("melee" = 60, "bullet" = 60, "laser" = 60,"energy" = 60, "bomb" = 60, "bio" = 100, "rad" = 30, "fire" = 60, "acid" = 100) -/obj/item/clothing/under/rank/centcom/captain - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.C.V. Fearless CV-286\" on the left shoulder." - name = "\improper Nanotrasen Captains Uniform" - icon_state = "centcom" - item_state = "dg_suit" - item_color = "centcom" +/obj/item/clothing/under/rank/centcom/officer/captain + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." + name = "\improper Nanotrasen Naval Captains Uniform" + icon_state = "navy_gold" + item_state = "navy_gold" + item_color = "navy_gold" displays_id = 0 /obj/item/clothing/under/rank/centcom/blueshield diff --git a/icons/mob/inhands/clothing_lefthand.dmi b/icons/mob/inhands/clothing_lefthand.dmi index 745dd19aac7104409f45a1586aff46c65fd802c1..69e602fc6529749c36d0be46e5eddb55940022f1 100644 GIT binary patch delta 70351 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 AGynhq delta 71961 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%0DwxuQ}C$@1$sK)wXZb2)RWpBwJsfWw0uKOEYNP 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

3}_U!1PVEDzLwTa$gWz>?3XjP9RCKVl;|JQDwBe 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 diff --git a/icons/mob/inhands/clothing_righthand.dmi b/icons/mob/inhands/clothing_righthand.dmi index e4e9fc0f859e9a0edc21975315990a3ffe51685f..3b62b05b314f086978e1e43b3709c788e09d64ad 100644 GIT binary patch delta 70018 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 S*~JBc!l4vAJ*fm(dT~)(T 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 JQZ>5={|h|YH9`OY diff --git a/icons/mob/species/drask/uniform.dmi b/icons/mob/species/drask/uniform.dmi index 0828c290e05950fc2e881d5b2fab3b486c0bb7ac..25cfb51013c9f8b067fd8f3b32ee92ddb9589254 100644 GIT binary patch delta 245020 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�yRv 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

F%$vhZcKkPdIr< zVj_urB9|P`qg)IQHkqMTnGHy`(FgTtG{p;B|`Q04_}!xqy@Y*1y2#|7ioiZ3adT&2z=ta+@Ka~K&J+ma48 znR~9c8yuv>#6YCeVF#%qPXBue(af*A_C?r~GF1!2OLLB2Ox9LG}0suilYx)L;m zepT0f`hpvbmy#m*(=GzDmh`<{mOhW4uP+rHog{R8;IAUXD_mR-r$dcd%d=%KT%*c4 zcIX2%o9)>9eT|f*=J1n1kfV0RvvGaFx^=7Ra>ArHoE#eewB?k2x>>!&OyjvvjvoS5 z!3#vbiw1(Qx0)KA{+OAp1EwFFhmZ=V=8f*Y6yjfUdICbJR}F?^-U9Wt(_o&<>m%ah z+o~CSW=hR}|E=-9n3!as577m2p6|>9*7fv`+WyJ>vD?}r9G#u@3Zt~nDNw?ov}HLy zva@~NO@pjyeSpvVwDOre(Sj?WEnghefyEu(9!SN-FW#FO@vFWyrua;jm#g(0WOJyR zX^3~juiWIsElcg--!wajwP~E<1)TooLl0WZ` z9J%Jg^<5wLfI0R9VduwuwSO5(kXu3-ssgq@LrUS{3Iwj7XvJR!qKG!x0Z<6P@sB-0 zP0BPw-}}Ym4?)yILONpROHNf3#YE!b;@W>^iXhMMets^W^B z5gaWpK7h~jq3&=yX;OPSR@KljTos8(hk#N)>!U;S}1~+E^Ko{^c|3%TOS|w3!owrfm)P`cqLz>R1$pS zJlY^?6JsiRVTzBB_W_gOzGdU2i(BfYB=y}Yis!C^kHpjAy9_x)FcSUyw-{yTpw&Ec z-^z+T=X530JWKz~hMfvZl<<9NF13HxPq>8l+kD~hRDp~E6U53EGbLMH3BrDUejOXoxl5neW4D}TFM=f5n9P^&so%cE zgv?BK{ncZCVxWPkPgF@2ZyOjCO)s^RMbVVrQn&^?Z{}t#`T3RI6d*4%0 zZTtFTf1Aa$tW`486CjaGzn!$9)`Ch5Pb;&`6It^!(AV_wKT#yppy#EBx5jh99!K6# zeog@l6DlUeg&6C{7O7xT8Xli%KNOhrTW0z}C3c9+zJO(nPlYA=KbsexzuL+pw%l5A z6s@?Kq~A{41$eGnd_s^Kh(T1ZX)I7kdOt${748JW*YD#W+=5#yqO8+oPM~!OeJKU^ z&oi*)Ip{P)nGx^TuU~%`r9&H27VomI(AxEP6V_ayNrbcJ@bK_)6dzHmhb)|pAy=gL z=C^O(#Nqa5XEh)%mGAv-W(3`{pmv2ZY8*7`V^RP788rgZxn(>&grZ|&^7q_m-@n)V zaz3Q=X4Bg3ptf1WotTm;f)<61^X7xi5WZPml zeP^DYGqkf*t`vr5ebmbB;a$r@``%=w@zuuvv1*+zyyoVv9=eovb!{;0kA7`6Z$_+~ z;;=c+;4;8@cOvmqCPVv;Z5%Z%TvE)*2^|=^f~6#UOE(-P<**;abHnUKT%)b(J&DAHuk}TaTq+~OVjg7Tc&5?%#plZdOG~=v1G{~4T3{+h% zbd7Z%sB1yn*d4l&wCNzbEW$u*;FzuztC}eA>ES(dn~t;3XbImfZjF00R@LRwMqTu9 zE7^U@bj8Zn-kz+20)7?b^Qo_!RNLl^?2rTwCYVpY!cRDvq zAGX}={pG_4wCld*=J;sJpj~6!YND>Y?(S}Jw^q+bssLeXM#ez5iWZRKelc7^DKa{` zLt&|r&>8_Ty`?2Zi>1|*&!3dj9eAp~1mv4jCC$v0Q_rIp2qdMy#y_r~) z<4accO8*!kOA(+UlKYc2R627s7t&1^oe#e4Ih%JwY7v`w!r=c|N-1o3Cce_+6w z0Gx;@YSpCMaR%N>f>b>t!T8L4^%!q5w%!uFH`IUdbh-AyuBY}vmcS*)H2Ri(!sob- z(A`pGIo$l{@S`NlB#_s`Izk0<6WVqDe&fBYbbsA=F<|Bi=jF!2bum+-GXU2X{rV%j z#Waii-Ck}W=)zsLO0Iw2$>_FY#-d&Ka-r6`SBBC90nn&62V;$G>kd|&7OXVt?XpLo zr@vaSHNsjlb6{`&OcknUDy0hF+#QsTL4p8cukBy)^vJEsIMdkQzilRRl>+80dmsZ9 zrq{CPJI_`@Qt~0Vw)lS^7CCl^)5cH_cGxx=N0k>%s%adCnMCnJty^>gXvf9?s(j-0 z3`nf>I%ZJ@i>-Ci)mb84xLa;FHJR>?%R}>vy2$NQ5&t@+-Qb-ZP#{XL46p6ho;|>D ziqm=R0uNwzhh~S)ByDhQjsQI{DtIMaR60m_Cy~1HVq=9;lE*8ictMsRfZ;JM@lwcUSQJX#im%(71mzHdx9foZ#qJp6+qd2AKFGF8a;Ue-u+nQOFtS-mw?nU$VrJ^X1DbL@`5pc zDOz22J$TFVwFYC^(10FKg6RhZ$;Vy{3%j?Ew|-$M?Bvwp2P@ced;jicZRS`H z1J?r}60kE4xC@A&LZn_KVN&pU?LX@t*S}D-tog}*rIPr+yl66#5(fF~ zAd1q5Z0#o$&`qv{(yUEv{YexVt=vf`sLh6B~%pjE#+D4gFol)$`S?rtSI2 z)GnI4#kp!g%ejsuWtmsMhOprUmb35Y1nl1jAi~m&L$M%$_rhZf0@v~JsNIpl@B%XS zfHL<6qxDlfGu$(fHWs7~0ea{81V}?eBIBXr!C7%XW65v|M*`#%ZL1bwojiAjzkiAP zEM?1?dt7U1ujf?)VTur{b#EQ9Qz$(I@{*LnOzR!p_jhkoC|zj-Z+%gg9cb@a_dJ!p z4k9Wh9F04;Y+DgsQCo}q3AZBV{t*r=7ih89>}?T|c!f)4I*b->ED$RUlD^V4n>wxi zlF3mOSH^Oz{`Ud(QtzYN+hY+Aj$Gwy@0CpfH~?I{r*AnwLLXKMD~hA+$l|T_u0hQP z|K1}wuLjk}-*mVs=ee!bY`=P7*g^$FRkwL;{+P>}`_?6c_fHrawU6*X84nj;VbK>r zbT>Jo!$U|;o+`>yeYn;5j^-hk4YC72Pa^|B*orj(Jh)A{yI<`fpO(U@W(!^E8s4b3 z3oqV}T-y%JzdgZ=_|4JP+iU;y^aM634$v9|P6SQB!LeF}g@tt(X=-ZLZk*)69Qbiw zzN8WyA19O;%zbkkHJds}Dh#|NB@J1NV_jQ8f}dicd)4_?>#d!;d)mx7ufd{GG^hV} zLqk3w(Ctby{Ro__LWCSnmuoQv*3f4B@-MEqXG{zysDtxPL-U`{uiXjX@CLeJ0{TEXk)zNFS#8QJ=QwANuCQv3g(3fTxBGmv8!uE! zL`6nYvAiQvQHi;vSKL5#phw?>P;FkZ{9Bt6!18GwJ;OsLe!*B>Be0(C?_2&>Pm(7o zte{4t)1>les2@@nxJYHWP=g8SU1E~99MWO@blLy}>9m|2BuL~?yM&|S<0IlpQgF%2 zU%{zeav~kIX5tEH@h;z(YD65X+ndv|`q9Tur1EZ$&0K#otj(kH#y`-s-Pevc6*$=* zoB_gNr+0}<7&#Vhm#<*0u^JXwmc(6}r8D=>t2JvMV?{b&DY9qMh>o{w(q zZWHXn@D`h4fY#L`+c>ONz{uGhdDDdI;wMlaFdwf}^)d+?Sj9~pv}k`y<>cN9=jq3gCQAa>+V6};kMQj-hxCk~spJ1Yv-=_ExOesYSMhlc~jg@?bKR-DKc%`9ONB3Q+BNpYkYU?et!L3Ip#9w7wxO#cYok>00o}5Iq37Lh(&xuLowo>%F5W|zS%xQqJ`g@dDZ_Me6(Lm zbuodmg(D)BCZ)Q2>6o4#=?jrc^k&yfyP#j~k$<7A+_l(tD%5m}a6+$^83VF%rs|hi za(hZI_gS-Lx9HA1-MJ=L-5uGwW0#KYb{)PQ0UrI+DY?tP+(AySR)hFvGeq!EW?8n| z5`Hz>!*C@P&h1~?g(JN8c2SzsQ_@+4So*ez58+$s8u7| z<7UiHw$jA%3JZNAA|gV>c*Z15S|;Vy@_jdsU?2<|%Jq{|Q)>faJ||~dm{503H=$K! ziZkO5Yrf&KW`f3@SmgY^KFHXH2h+vS?7gjcfq|Nu8V+(S{x8Yzj#3kaNxLWV;tyRf zGh-yjsH9GM(ny^2W!AMa-yM-CBN&!#?(AgN)e%)!SF=7sPmHXrPMQ$}P>KgT&UgQ~ z!bA6~KzsIT*H6p&2o0{iQ}~dsXNfTKsdBJ51Qd6+(WFs`uU_+r^xdf(q`tQn%T5<# z{~AdB8Dc#qOiVueWpODzd1&FNEHS>{iSAQgzbmfD(F%6hE*M3SHLa>JorD70iunEM zQ^!;EY?h>8mBr)TWnf4Mh`WCjHDVCc?^{>LSEScU0o_kEy5|u6jk^8-vTI99OE(lj zH(H^_evpEW@aa>OI_}_)TGiHjq!U8&E_XWyT3UE1DXB>#{fF?ENguj%PY$mS{(Arc zn8+0UaE7>F7lM^e4}yl6-ca3bNi8cd+P_|5(@jOXbSxR6%>YjiTd?5*FwbRH6qRcI zZZoZWE9X)-<_<@GZj1;~1mPs1^$Bf`JHsB7wAFv?T^oOKKsIFJ(qr^$)r{~mnSs@N zZ6nN6b|K%jb z)w=^`;7aC{z>IS?M6rDvAP#)$zfxJ>Bf_OMHCYtxzg$cp=KcSzmj3|_gTQbX5tI>6Z>#GAQ6(Qx^Dp6M z#y`^C2akS!#KL3@@q7LxxBvb#)JHon`}p|4oj9)-78Gbt(=Nz`>6SWkxRQ`$J&(<3 zcFjYe>WEiHl3)tV7u$b>1HTLPEbJJeM54zBW22=N{6Zx%^V!qaSrWR9@N0-JEn-cy;zM;x7!uILI&q6ciRN zgax%n{?@x@1Xim#&TR&Ig8(l%z4OlXAGdrsrC|v>6)DEMjwPjq=8r|cS_1@zjfE$)jf<}<6^=son3jTadiiBcg<_Bp>iF3+(m6ner|hH zY1>=X?R|UUCiJ{nPh*DgghzH@^{)R$)uu>axW1SA@>cJ@0=E~;>(m2NIV4JFYTuWp zaC%<2Hm{eC#M?du@E3X1yUk=;=sB&|_p(r2%B|U+?Up<*8vXfk;I`rSSsIVx6mivc zh(R3*@0Ted#c4NsVHVpHyTc4CUCSv_&+`o}RHFNBzA;ztS6> z96#zk@EdwuQ@gBhJv@N!etgH_rnP}mnwrG_{%QA~ar5%JX{#?Dn+fDYRlRrpXEim1 z;xFjXWDc^1MsVk6(X+F^gn|t5g{Xvtk4l&q zACdC%{CnV3FUu5u=U??8I5o3*v@U&6_fIADOKvd;ty(-VgYjq-k!N-bMd*!|X zzNBjZnY9-dqZh>ozK1zFM*QZ+kVEKY(|T?aK$L`tbSx~fpqMxAsjro-xDxQ#-_;Yr)ap@o*@EICY zKREmf#-QkI31Codjt)zrYFRq`PcY2vdF?qDiH1{6UBfV3{jC+Ov|L5nHc{&55px-V zdJY2Tlwg|!fQNQk-htcfgBkOcEmmDfYLKw^+0R3M znBjS)0X5RPr4uX*q4vWJpFE^+6C6_4nbdgH#nusK@vv}F{V9dV8YX9&@To2;%Ixqt zO|$*U$;#x-l@b-*H)sK66=B)w#pf}E<}J7iM{g{4WO}j28t;N(yyv2djo%43j-Q9A znHjzlgkMSA<`1FsopPxc{F>zrmWeS_d2+PZaHp(q2Y-x9G)aMbmq3Wy-a1)>OO*`g zBJYqC-+cVYV`F3Ev^&N0P1p^SK{YeD-yE@5y+VM6hTj*~P9HzD&MxxWz`!6qP4loC z^l+ZwJ;bGhF>d;-l`Pj}HA)zd<)~X1A>i?4{9$88bQZ*Oj+tH%?8~A8Z&2FKWV9$S~Cswp0&<_^|<({*Zxw$VC)YLi- z&0dp^Rg6MxqN#d-xy_yZJI&OJ)bTLpsdO_#ma9$@mh&)!>Yzc6(wTggpfe)q4*dhn z0E)@pDG32V5Gd9R;^y;|>orymW3aQb1}{0ty?KLeF`V|9O|L;M(qyJw2Vet>i;FwE z!-++n#jiq_XWKM3L4Pj}w z{#4@WX{sJZ*WAHhTESDbB<>RR5{JTg8jUZ48Q(5k6i3?kkWsQ-5Ismfiprri_`AJb zNQc4d-J5TFqp2M~+EJ4~jo6mN%4FyN310bq-M||G#u?Ru75R$`ujiw~g}YccBj2l! zT24Pw(1vt=-jpDIf4X{ng;s4bg2}9DL@-qfdoOK4(1fL8Rm%9e`_<7$8Y>9YFs4ZG zt-fxRbVz*J>m--9PGCo#gdGYWzt)`Sb9NfMJSEASim_v0!iw}DDhatXz?JK;_t75= z*A2bvzHppm6N=Rx@;{M<8FEvCFYw&Ma1N(JD_jCDL|LfggHM|J33}vth8l!$D=b#S zWP`_gPny;K9T!iG7vI_08Diw%Af{UJ;lRQoHk`Gv;AgU)yR}F+OLslyk(QHN&k6OH z+*tHM_|pq2ZfZhkK39i-VV&k=(O2$&B0%ea|85UJE9I>je=h5HMiH$uUBwKZC4@ZP zFQCI+Z9eVlYnXz;3fEq^Z&5v^LRf8X_ANb)935=R~6P$StEsW=SJ>CI|5@=n3 zT=0*prm)2@VuK@^|9$mexKUM+ZfMGOndp?Riy+0+MR-`)0>vWmX0OYp|LY-N=`Pm^ z_;fn04MaW`8~3U$M_ov;L-^-aGEEaO+YF=OhYKi)iYV`lX_gH(I!~3E1^3+rDR6gd z1NJVw-`m)t_(4)0=Pl-vktAG*@eHax7qdoqu{Nd;`uARP^jPoQhCzt}ixCSmVaeIuO& z=sm@IEj*wW?Qnm6Lc(kH1ZE5@b&3yY8+>^cgbJCAWxwhh97KYts1Ssuh>z z)x`W>Kz@n@DksFzD-&5|HNsqiU#pP#ev^g87t~YQO^b#64t=3pCS9lX^^X(_RY!ru z)n&(y-_Do$(kqm#jqxuakqyqcr9#K*vbF46B3W^7NRpL!`I1Cgxpi&BNl=$v)^}}d zivjYX-|dT~ZGuQ|uKrC#r^hkvn0CT^mbBMK&!zHy?zYmy!~tb^^EV1v$;sT^H{EBj z@GhpQO+gIf;cw4i*J&jF4^;&Y0m!nw&pGGLIP-eO3a zOr^#@QJmHFE<5tA%sdRGYUx)!F>OhagSZgS%Mp-&q=_nPb#m0XrD`oPI7YN*yawg* z>?i){nH0D5NDy`(wrA;+sD_=F7U}PB-XF+Ard$EaFe0SyNNPyWZ31#poY;4~vG0zb zXVZqUQ7-)>MNlu40eeIjuE;@$(iot@$+u}W>huBFh~kK zFyeUn%9uV2C6D6$ukk6@Ekk<(aa$m`A+vBIGkE4xue>e$LaIoOIWdJ;0kcZ(Kca91 z8@HF6fsQQkUNi$;coIWG87k5;GS5Xs$mate%*Qi~IeGxsRqfro&jF#-o6BBXlsY3F z09+^|g90<2t@QVlV)PmyPIp&>Mqp3(d_Fa_i-3G^UFF*<&-mk6mcQpJ0v61h#|cT& z)s^s?TLg}83Ubz@zxTa8J*Izt%#`;kH+2FI-9soYWv(#RM?1UnW4%QJ(8D&EIf_F- zId=R7CJBgM0zf*2?a>d{Pq=g-F`B?Py4GGa@!;PSFZlYU$Tn# z+#vZR;I{jA&CAd*bCBG0Yb2xYZ`-bFhVVNgKbYrgAX+drl{5ry&wV#Rx>$!#X{s_& zfiLUO6G8gVxG}M?K6_g2*>)iz*UDgXNvp|gW`s`Zh0QSI6X!_foyLu^6VK8GJHwnd zTTz(ua$yh*ESx&r`QoFk*+0nAt0jjhwH|!qM>aZVK3S&!s&ZYKFqB?+bt`^p8IZ2APzv%n9#doI9$p zDgf*bh1b2{Tk)N`h;v?j|>}?F>ykr^S?;1 zvrT7pb#*Dqf&G0uK$zn_%!BSBau2`q@6n`PA1p1+RyzZogfl#^*Fu|0OPPI7x{2Rj zJlx+0@393)5CdEv`-97_&u(&%|HG~Gp*MXz2FQn}Mxk(2Vp7t1da9-fMi~2Fs1Ke; zvGMmW2L0SWhGwP_u0bWjGeW{(z=JaZa1&U-KcEkQ>$1bq>#Cu}1K?efmV94;m_g+g zuBVg~BB&FZG-`hPG#lC4D!Tuti2(Hf=bAn;^#8tD+9t3VNGkU0Ik*+e&5X%3E8R~Qxw>jRXUC6RH}v}zW&nX^7(b{ufeID@ zHp8jDo#OZ=sI7mxAp|f60P+I?4QA~;D@lN|GUzA`Pr0$-GRzF=1k&yK_oV%=sf$FR zbAR>S=D#<-&9~yB)7(h08}|>dK6`p*KucN-_b8~Lbu+oK)i9xwSDKpE(oYVCXz^bW zpQ&kD@5hn}!aUde_4_Sf$8Ob(bKn26a`i)gVET?i;U=jsvFwZJd8W|A`jX;73n4ym zGyoHG&CI#avJ(q#+Z1YEh0r~X(slF)pHb!1EE=BG#uFW#+YX0+e0})wqdl&a6cpgZ z^okt?<%Id5N(Mi-I6L=qX@RbEDNR9GQfcuQE2ZhGx9YECkf^N0j*+=sFefBw0$)l- zDa-Ts(x(Rv9Yy!Qu@E31NySJTJv*7}J@mD3H#*7~rQ)gbf|jWVqmBM+fv_|xgy0ge zBwe)jhmdp!aVFkMuZ4+LF3qTdQAkZFNw=$F%RME-8S(TtgIqLKL3TDQyj$wkD+F+H z;3P#z3tXR8WEH?YXc`%f0#d}AJvfLa-|z12&370*9^heN@wjfMGPt?9y?y(3)ujo9 zm)Rq$IogyvECRI`ZFm`+w1b?{)Wz8MqtXHO2_-L;vldj?#~(j#oh z<$1kD^o%RlAo8x=13~xXvpPUi_?a>4FXg3-w`4{49%Y@vu*M>=ps7jKU46W>BOlufZl z{qu9lr6+p+^(oozLdX&msa(5>ON)+`W!mB+%G*>ASf$20OdmeCY}aoOhY54(C|PZ$ zCP%3TUgDZ*X5MN90OVsqJ|j&(PBGclO2gYF>63W)s#M$})15`*g3CM6X0wYs?=d9= z2@Urc0y-x;5tS`_3V@OakQTiB{HHrbS-NE81^uE)}_O4*pr^azb+~uP(i3c=;kdZ_Alp><7JVJevYArHyl9cZNfB(aWIa4!} zBg4t_s*DTdtkIN_`r1s+chsx7dc-c{-%SelyiLO+yA8E^hT(l1p$Cgz2a7uyPvx{_ z`;qsshZKdh(ebcXn+lAc-`qOi%d?ZbKW?EV%qwXx|8gwpwIc92)k>1RW%3tPPFzr& z=@;w!5L3EP1N|FMESw~0b*Vsf*2Z1o-3RGQAv~pz@pfNQQy%D^xVm{K9_Ccf%%w#$ zFx(Uhc17_U6@ut@6<1m>+n&|xb6}#2CU15Nj9y1He zFuC{leHE;aMu{rcB0=DXNs8P!UHrGmx|Wvm3kiwytU3GbyO6x05uYRW&mPGx6&RB= z9LR${wH(Q?%7bNU7Gn&d+A;_+aCOMKydu|l3-3Bb2P%J-Rfq~(n+^x%C&OT_Y;ElNX!fjTXhSO(j`61JHR}mW!hDnaOn9LMwE-59MH^!Y7ma1ew5`Q9 zPejzNSn4}8=Io%A=BNdf<_YyShT2`p4EdFP)c~m2?okP66OJd3bMe0UW`JudMH}Az z*QH)$#Tl5NLGY2C-)Hc{RJf>GyG^u+E+>9TS_F+PyWja($fdjn+m|8k&m+ zqbSs=$xDuuRmtTnkFOsa$YLy7um10c53C&#uNo~biT*qat8}T+{VIsM&bNXa>V!3=SOfT z59`s!%W7OApQHU2v$KwsHF;1*J@!r^V5=t93M+<%Ld3!!2iJZxJ{6^}J`KcR1EZq? za=^D}XujuFuHYGALit%{fP-A0v|)`IjvDkPrqj7maUsV&Ql#VUQ4oCPU`oUmJVNjE zry_2zcJ|F)G_ZG(q9>kZ4LhD?(2cnTW$_CRq^Q<<(#T7(qfu@NaxW<9^DAw|-Ju|h z>3~zMv;KTxHMZ$q^zug)f8S61JXRy~vbd6J#Z0xMUK2m8GrEri8kK|=`H_q<1T0Bv z8Y7HCyPSI%0we_dJ_Bi81wT6r1ec1; zj>%AN3}i6(Xtq-@4uBQ<6LA;05b|N!?}>MQHNR4+v4o*$N0u(6N%mLY?)* z!_^9hn>z*O)e6+XYrjzUk)8^?!xQxXz0tqJEAphtdC!Yh!kxnYy!qNQ*cP6`dY}LS z!aumF%AVV~6xYwp))M*1ongN8lvC-z!xQ=LrGh1RS+?=0b9;$?XPg|B2H&RWeK9Y; z3qI492kk>EBP)a<2RYyBihIr&A^zB0?orwCP#yXr*8;cf;7w`WuPD_z3Z%_1@Yv_B zfQ!5~QqtasUM0&7^!3325WIG2iZqfGH}4 zPd^wZ6f%VUZv=k)KqgBkdtZI5*O~H2?CO;d+R}w8!{%KfsnTtC>>tlgrxL?`t_uHa z-utmz`nOy+(d75n?DWU_6{vHz+iiDX@aZQn$0D)S;|bP!sP`dK+r^UUc#kuDEz9##k`(6nPAhE!E+8wb2)iomK7AYg zD&(p|@X;}kg$nN2f~3IZOIQn8UMs!dcW*HVk#d+&{*tUJfqYq2HeW_-1+SJ+0NR(BePp3V*;=3KQMn8qM)S~2=UsyVrCmV^l zI`g=4{9o5yag$4KCF<0-Q0l}4>c?$>91y?1Hgo9sYhmd1fOGM?VfP+5d*HYMKJLChOM?;vd_R>Y zKjK(FzxO$YBdtcaJYsbQhM`X6BEtU6w54i)Rt>LWztx2-q-7OP{Qch?JX5Hh`t>W{ z6$q2CU2a-r3OWT?qGESi5t#bhmAkGiw+$&Nb#3k`jT9u;9QQf+vWb@qzgJMuB;eTd zeDP=g0gqEcT(P;tGv7^VMtZ19(Z>3xui14q%bko;B1y)P3ql0(L1FeGhow zca00!AF819wqL)lb57er5en*v43pNv1vFsqSfTx~VXU}^s>CkG$!8h1*ops}ri-&(Y9W?Y5$iaN(<88`GcoYm~(> zeLJKO2GoJk$sD*|p>!ACu;}&DbBMm+cWn&iL4$fkQH9*Dj-J}D_2m44Q6Jfrb+whR zKPgBcSitCv2ZPH}az3R3E!!Kz4h!#>Y9(cb47P=Gr?c&x^sF$VBK#(}(jy;Niu3wk zxbMov(wf9=FHID@RbudEs z_kks36f9}``o?C9ly!r$+@GW(lP&`FJ@4onQy4i#kprWyza0{^yDYyMQ}&2UPL|V2 z{pQ^QM2tb}r0ZxVat4^Bf1^WPDH?$7%NiO|y%JdJ*B(iCo1QRy`0_PM1MX^?n>Xb; z7cLj|7Vi5$|98?&3(1w1)Ov3t`JVSP%ni0u8_y38EKRsvj9rDqo!$=YIdWQc!q|aR z`}g=Fru;F^;v!x7t!nM>9}pRZbx=crFcvi@Mrt1aaFfCz=;48<32|b`FB|D_lNDXx>WBP(*f6# z<7g3?Cib!67HyN_zu{J9cZy1hwM!A)*)t2nXW-t1%nCVilz3-g6WZK{tLa%{mC-vm zB=mLs`SUl1Cj0#iog2>*BXBWtZbubhbH0A_8?G+EGTABoB(*C>4k1|9bni$p+)s;I zM9;d)wf<4!Xh{})yVp@=nQLE9;o&Y4=O`_Ur^mk~UGy*Z{b)kZmhDCN9F2E~GqaLPS@f>G zH5wcX=1iNNRQC2{U+MOd@p((VDd@%YzV29|KuB)Qy!21R*oR!Qyl)JVX9`z{Ru3kjVx5a zt%amTm6cCcYR~Mv%AZ!e`YnBNq1wYz)$t0PzNqMVAPsJ?$cDAny0aaX8w`>l8F;9I znsDB(cesLT!hYTaG-2B4-tDS!_zVO*K_QoJ9j2AQ|9!v3hLC_Ku= zBb=?s2Jz|``1?NwWq~s=Z~220K*$y7PRVJ0ntmH~I%i^9X$j2YuTc ztY#lH$-?*D2OC}xFNQ6soYp;5mFt-|0{p7tqyfA-4pYhDHEk14Wbo}^sw0x!BP{sV zsq~xb!dbk@ZOLH8t^Z+KSP*l& z4Eh^{t}uOBhdi1mP9*AZEYDak?a7_17Hhcgl^kD7m#xHN6j1tGG(68EQO~^VLbszm ze0PB?0jUfIkO9@!bKRQYy^hDU@unu&?s3bABs5Nykv@1~1Swb1DUpC@FnG=EoUQdz zm#vpnJsvnVJtU=1vrux4tN-yI9Nr~4{udwL9Ir>aO+%Iky6-wB*0LMiiZz|S-{fA+iq?{=XXuWMWV5Pizk%cUp4`_n1c44i z377F4X*SV`cSxC*kEk`7>tRQbyq#`}$sg)Mb-MDI6Vah@&O5 z83r(l8rR+^q6QOv5IOj|JD+XN&se3WI~l>IHX{167L#Md`@8ED+G34_1GOB~jPhGazYvKs{>qH^Dl%XDy~04loJjnD)k%z^(s+>Y z;lo4VCs%(XwO?j*w|FS8E6HuuEjq~wh$O$rw2A$ zsL`|&^)EIqqlDWIe_P`)xe;_>N0V!lx~bw}>hKl++cC1STC9C7=u)Y7@VxB}jp_p& z*Z=*r#ARso+kdBXPaX)KKWm;!_nVdVb?}@y2I9>)qq_So{LR)_{_WqiaEgDYx5UN~ zp-El@YJ~A^HCd3UXF|F=jVM35y9s)%smqF;$d4-Qk1!Logw=YyleL7ucQZKE6@T%! z_Ine#ZlxYeh9v3G4z?bscS{jnY(Z49q*SrYxt)`sc>*n1ej*({*`x@}aEj9Y%N-b)S$%3QK4lRPV3-4Gyt-lbg4`)6;KckWg^TsH=3yuIo21P}w8!zz zM8lT8i%|4V_7PveXAh$PfE`^g4T_ndXoS$j36ja>seokHg z_1|aOPI`NH8TcheL_JM&a5l-9(&*J+ccXfa#%AyI{4YM``fsZ#H5mu zgO~<13e?=S^VH=Y;%ll410BO{JqK6+Ti2ym{^eq3-sD=+`$UrGNiW=B>w2X&<)T3( zCS>0mS?R-G@-jri>%f32&*oYUB2F-1<7McV3Kx1@iYw&0W_uk$)P)fIF*r1|)_#!4 z)4Nr@#=)+|PK?;l^EoJ4f@W~^^{10Uh{Rz%OHLP3?Y+A+-$OYNVnedIDWtOVHjTGH ztNLwGtB{e1;&y#(0(kV+Iv9-8y|U3%Y&$w%hZgOg){h7`CXDJv|2L>a<<<8|WxYz` zb|c>X*3He{``=?9-cdjF!h2+NnJQd=bmtKwBV!LrPUKomy?tlG!Q>wPVWbO+R`ia3 zT~%5NTuW(bX)gGyzix;qx`|D0o6gmKC>RI}bD!6aRK~e(6xWI%$vdwP4n|&gooVWh z7dI_OeqvsOFg-UVX%;ldmh>;#eU4ga>Op_Do|jKBY?DvvVD=mPy+MLILjVZC4*85T zBi=4Pix5S)NZins@LS)2i~<&V*7rH=^V2sM9i}jfhi8*PYRk>T!>0HqK6dZVzu52k z4{fXHPB4e_Oka1=V#sQ{zK*r}P%3y{|7AsGZOpyh?TXxa2mZrKRGV3k8a{FFJ8ibD zBLb5pVbGAI2dF$lWyLnq_SR3tM)!Pxblz&92C9pMn3$N*kFycgpR0aH{lNJSo(A@Z zOs5J>lSKIXIp43yYKuNzNsF8hwqNMj1@+d|d&DBnmHxb#@Qi|+1%t%G{F?nN_J!Sbe5=a77cM)~x@)J>eXl_U|MuqlWG2ceLBXX)G?iE%z@_}SJFLasmEKr^ zB8S~=CS2q?%bY1o$TMH z=UG`g!j!owI6@xtPACJ>Eg9Pw!V`#sy1H{3tb3&*UHNTm)(#spQ?K7Y!Kh0S#?s;v zuW6981LXu9xQeSgT)Uy}_;q!!WBWGLbqA-9x##97L0Q(rJu0zHBA94?R7Udok3MD@ zN=M#0VS+hJGCENmFBkRf`Ox~SKX+6KZhZ5|=U2~O9(KBL2NSE|WSvytoVE18`KledZ70CHCTec4c_2BJ1O;74opPb?PVEe0oZb-gp$ya~WBg zlETHqij|a0|7Y4q|&;AZ=?H4`CR z9-eGNRh3xSGC9{bkS1HRp_wGK*SWKm>(U*0NLIAuI?27QJJv$%wn_lW{=42n z^38c)0BD<80EzC9gF7JK04d^$#&nF7 zI-LgD_EH4#S&7W>5a}?imxMhZF1`{H6Q|$$%fNl2OUlmS$}~(~?IoShhs7ibjO!2* zvvt_zsQmNP?7p`PP@(Sp819L77ui#upu(F#;U{0(ccBIj}$ zk&d*&j5CvO3A`KBywmr1djSQ7ZARV4B}_K9;Uaj7a5NnscZ6>Z2p@$Pe#WX5HMPfY z#O`TUD;he!MV$g$8pO7H{duTQ^gF$=Y#|{@R&n1soLoS&Ohn*w{!f}oSeUGAS`nmE zkZ-}ho#A1^^*YPr?;hDnps@y23ES?@V`nP#EC)h4h1~>8{Lq@}>7*Oks2AY>Ko=pq zi*XC1PPs$=))FqAYJRGfKMtvw1bIKjtV#~-7fcE7hd_IBMYs%8HncNDp|vA212N1y zgC1D|2XgWv=OY9>yHA*E7p*NjHA7at1~Iwg{&`qM9Qx55FWEJr*6&xH_;IAoSa$p+ zM#fKsDtJsP46w-gf0t}dK7)H;gUlBer%q4Ui83SU9qa|4zvL~B*v$JWR`5GuMzf}3 z@x2Txgk?uQ+I;OG$NGI9@LsWJ9hQ1-PEMcXN5Ak<>{v{!I&{n%VhCVy`G_I$GRtAJ zQfakBqW?flM@}{S_S%`BoDNq%wj*mSG~P47Ppvt+B8px%;V@oX{M+wmAMX0O)mGwf zs;f0tXyr)gDTBJL?=Mu)jQ6=ZjqI!cObrg?LHj0mhzH(w7MIbth+`OU0tK zTO^-_?=m4{w9i8ZO37?O^A%Z^myT;mUGmZVEJiPF0#cRd@sgDm1c!P!_C*K$f9Qv` zZL?@{XAvSX+D6+o1o!GPK4i7}8|w>0*2y}IVjXP?c?@4)cWymB+A9}Z!eOQzvoJx? zZ&SLx=V^M-UPnFqXPjP-#_c!9Q$|M-*u_A7PZ1V&gA9`W9>841hSLB=%6@L|bE^{I zlu8?(M(fBDN~X54Epg5eSthZ%)xRrU&Uuh12}$9n%4_an-(qB6}871^+yqZwOQMq)q7e)<(3o828@Ju5H}oIKuC`kG92(0r(wKxA3!iMS2bC3k{tc6G zH%y^_#>bn(mmBflZi3Ur$;b=h*H0HU*0d>k{fWM}mRiVO#Z>u=4DQOKCx?Er6Sf;F zw_N^LM?^TsK{tNyA(SM5C(PM}ZZjR@u=^pgKDwdX>^I?lX1)y-F+@v_fO!{5Bsb!% zZmNcj&N&Nfs&d;XYCv}7N{K_LrDFB)G)H7Q5jFeNernHws$b`aiWtk29UamybDH zZm=Zgv@^zpg@zB`LC9}94w*3|xw2sUi7Mjv|M`HS2sQF`1e?OBYeWZ=ywm$yR7R?v zA@k3oVE${wuI`M$M@r7)ogQ_N$|mqnTAH(BdtzUXgzir))v4leg(e*hjbkV!fXe^$ z>0{>YA7j8f{S^znNLMYgZqJBCNX*CpLhEL4kr-K6pg~ab%z39o21H}Xx?~ATS6LV> zR4ckvil=2$+opI4m<4&IhWQcagE8wD&~YQx79&7N-kX$ANG2J22{#YB$sa4FE4ZZ0 z?=c2Gj@4`07le>6Uc5j|E*se!@{|Rd9{XC3rQ24|fd(%!Dr(DgHuuw_+NhWqV}Y~& zAt(o`(A6-#G6!~4Tv4H6*~Rme?hb=yZLjLx8OCa>S+D0&&0y=u`+T(M#JBG^d*u{} zk^8eeDt34~H&>XtjxH1Gx?|Q$LtE?usykkR2p{1hTOmlRCTGah(iSJ%PFs&cmv%t@ zQy?Dh^nZ3jxSi=ygsrJ+S%Mi1ewl`h@$ea|g{Q6d zR=SkDovO^wl`V>gY_s{@$!OZ@%M) z7c9Yn*Rd#KWax2+76fvF?5Wh6%Y@|m!uV0T8iag1OMODlwUD2|xUk&L_V$XlO>8*K zcZ8pYM^k*1&Yo_)mvvI^`Afl=RYz;)!YSJf2a?5wQ)x?4oF6thEpd&*7VsCez?I%g+Ma{T-JX^ z39t)(_cl#0nrrdh=!=%6xC3sEGjwCCXqss|jiH{GD-PHP_^!XUb`&^P8uqm2pDWCz zrz=lteHRdjr;OX+qvt7pRoQ4(c!Fp@&p)Ph#PH+{o70i;?iXv@$lMVoUxh&TzV$?1 zYJpMC_?bR`IGOo+1=0Kyh}#F>b%D;yFJ-IX>m; zuBv-zsO+PH-k4jzk70XNS%PwLAe4|?d%6E&XqEa`Zbr@g+q&8V_l>O6G0{972rsEw z@w=k~PgdMv(r}Ug$I*Sh(d}Y;0Hl-LB9p`fp#cC<^Ej2!Xn?;UlqR-?>~Hl zB%d>6sOV%BOcvkiDWz)}u(P=*mzZ~ki&#qWSNgqKcFC*Ae)-dV_eY!r*c}f~4nYXj zxfNe(dx1TA1O{Bp!B_viq!G<>-cs>`Y9^U(ZCS&2$wR!ZM3hYP(3k4!EB>gAwZ+9A z^=WFGA389b*)1UAYw_9hXG0Xbn~}*Qu@7VKs$~Y|4Yxiaxc>O@ZEO|BN1s>n-7-jQ zk+9;UI$Zzj+P{da@&>F&KOTa9@)Es1NbZ@{I}I`AeRdGMI@4uL$1;FDvl1kedUAM) zhdTLVecy2O&tS+WBMJ-4056+#dr?Zq%gpU%^%i^pB!GPM_*p`j<(imYuLw)curu)T zN@YA7Wh9zENJ5QNjQ*E}lNtVlI3|5~A^ z+&oQF$gcuDn0F2A{$vGpjOs9GWbqG+i-W7Kl=g@1Z?GA^Ny*k+sSi zvPx-7>8_z=aX;iinPbILosv8MY9?A|UUEeEh+%=Uo?Is5t4x(@NNTlJcyyLx%knoY zR1(Ppp((gUZ|>B++EAgXanaY?@dpBtZaYM%309G7FyVdVn0b?LY(4$f4Ba+z*vh2f zak8XgZ#W7!EYf4j;iY=TMV~$!TcIsWnj*HvxOy9P?Jmr%!Qax>+lBo%&9?CW`>eNG zUer!=>MiP1Gkhtf9xp};-XnMT`r-{-h0VLRj@k8Chui#|DhqoQ!Ts2A;dqr!aR2hR@JacyOFs}zkD*S+((|%&L{G_D1Wd_@RvJB zX-KCl%t0lws3~l_5xiVjh;JZ;WUS423L=LY?0JHeRT%c~V~vMW{YK&&nOWHWwWCKp z2D;_!>8AkRW3PArRw4tm@I*H1c|y$FFp^F(zCAE)dwYU;%Y)UYSA8(T+_{uGuev}o zH1z4It1@EcGM#d|$a}stMfj|H6?R}czXvDFJRP5};QK3hS2bS4Gi#cwspvFja^soI zQ+G=n)C`Pn*>~P%hTOIS_DacUD+cw^vcc`FMjH)q<1hrV$VP#-P5#)J8tkz3Oy%7k*q|!` z^pb5;c`Tz$xPbVR^Z^`DiR;_y>Y%rNcKPkU092JOwCm^T-@(zZk7z=Ly;MF!$DTm+ z)VyAIamRJ4QUG)|lK}I7%tnX;w*=@uQi*wg<9Rqy+UOy7OrY9-B!XLKcGr9uobnR` zM24NK_Ce44?a0=P8NHB;z+AlJH#fZX5A7+5u#i2LfB26eR2);FLk(^H-m(YB2~~*) zX$yT zphYIL_LV76s6=tw2XG7|j0$1NL+>2*$Wz0Hn4fZBWfcm(zcr z(|~(Z_7}XlJ;RL(I1|qQjb5Q;b1nR5AuJP#(+N+Z7@0%MD}X5pbVYE2c-muqYXU9q zWi2+7j1sw$Q#o~)V|083XAa@I&^Q2>|5|14ypI6g56RzJnq9ZWwobS16v6nD_a2uxsXf;hUM;H7CKBF;_uF_%oys z2aoy~@0WA@Cs}qoAl8 z8C5lq7F5vzxQ}01>WNHM$jo6jAO6Q0vywJ~UTj*eED^k>&MBAn;(B3IDC;$F_ynF6C`I{qY~0 zwM2FfPvV{?^@G24J;1Pmy%6bP4&QDqFZXXXz6E2tewfMr{8Vzq{{bB@G?HAPnG!yl zA(G5x*28)H%gN$h|04UHf&{I68MUt=EkL%H?jUno43RK1GZ(X*S|%w$kRMyvjqAym zvlQR6*$JPUs#?=-%t4^-Pyj@R(oUB&>%eZ8>;{WxL{S@rwO)Jh9}Tlhahy5Gh(AkQ zM&R!mLo~9OVr|ktT8e=QYc8H5*R$THK=MSsFNVg=?{HU9lA2&ZvxDj@4eJ!GdRtY< z@uSIZ!>fuGO;zy8Yf!w)>+%JyV|P-w56Zq5`s^Kdcd)M7Ty{Sih1+dMpQig!zimb$ zG38=9r6l>n7Cb6TtL!KBj0UMKhGMAp zPHJu;ah52`b-=*(;*|&z_q&ZrPq+0X(g0hH;38AAF$UUiM`ZtrkI}hW~wDf%TNK_`JAmKCh6BpSdF5DjJXsB@+ZyiqFN+D z(v|yjp{&3AA8oNnpGyAW7h8efE?Jy|5 zqmNWACTn!UG7}-YIw!Cv1oajhN1Hmx?$(Ms$8}oE!Q0NG5TL84f!dEPu?Kud^^bEe zC2(0$O&_=7TJjd27_c>ol%|S!umeupdX&iF>Y>EL*QFMBgky+B3o@?P#fe6sVP$>8 zXFMdEev+4>X0d9$4+Ofpa?OLmV$&aCSN_j|g=Gp%j-nHQjp99;^a_sBn-OUtXe8dcRXs2JoB1HRHzKN;F z#sW9Cw!Z$_6cPkam}1Q@+S}An*RlR`c*fydD(-)FL05X?I%?3d30e@IODY^vQZKGY z_jGn^W_(ZeAHi90u=F-n5W{H4F$$ob0AKF3BG$3i?&ELvqwrJimbEwrvaCrJ%Vvry zKnHh5lk)To`yw(rGWQs2N0^cT@A-sq!++Ts8j_n4c-rkdP_aB*l-^;s(H9H4L_B6a z*p21Bc|fD=-Ea}8%%1LS9`#i$9}uO3SmPV!RvkitIW^AL2=neBTc^Wv?b*-R82@R< zUbUpEj$~YvdphB~*2VjsxJ90Ggwu8*MoQ$1BcY|J|HRs4DMUamq@z0@CDzwj&{_=Y z-DcWKtjjc$Zx{TQ!t2$6)_Ys+&_xcOy4I`s)#T^ww~c2ilyrB+G!j|$wzG&e{h(kS z07=5YpicLO?bNy-=whCprTVS-Iq;>Ayfq?qZv?m6?+#lC9NSh1);Kqm z%QM8lW`TFN_XS|S!44{KCRv!7x&OA=)m=BAPKk^5w|=>ittwIZ`QMk4cn?v^gs`e-z+=TseKp}>LkL8ygTF_q7 zC3fd1w9sUk(3Xw0^*`xl2P#sMr z1owh(+h&~%01aSp{Tl#x6iSGv?rT8vCXnrLsKXv$xco^s61`97)my!@+n|Ah6f>B1 z6uG4?*f7ohqVkU^=Q<|E4{{odG!C!;QCjGO=@pxiVQ(YGSka)9`d;xeS!XkA@UQT} zXrieU`1xaL*7NuS-eMObxvi}fU}vt)`M(jx#oJbCY3VrA$W}HETA_c2Ye&0BN25SSEUFs?BU|S&}EA zxV^|@0DOWc!=wU(&8z|M3MK-RFb^!b$FILb3lt(^o)6`F+t2qM%X|(kV*(DCrJKC8VTN zy1VNGly0TFQ@XoBq+7bAyPJ8J|9fvO7-oi9!|>gE&pmgaefG9QO)Uq!@b*54YZEPh z$MQUs)7Jaif;ek53P1{pJneiDsWk#NQ^3v&VLJW*PWP&{pzTr zm)Unh{6i#`aVr`-Jl1v9%5(Kuu_uT(t;X+me{1l+v-S_ESm>*W84_QrJoxFPX6-4f z?Y8|641$=WrbAfO|EtMEU#jmGV^*w3$|>@w)zQQnL2t!mk@E4F6ca&VF={v<*c-aUDadX&3=(M8 za(@F-??Am<+{&tCHg{pDlB-(##(Dpu1e&(eKWknYFD5cIg-l`5KqzzWz%)8O-gB!U zAvV@O!e@rl@FL5MKc`jjm;(&I)h_r$*SFZ#h-LcyZc+(af{1?94=On|@&ZHhJG8Iy@WxBiZvBQ9o87@y|9{yZ=l%WtL4lEjhTWlI zfD#GGxPbUbTgv3b>PAQu#?&~9slGqIvk*N^#Cs=4{_3uRE6=#yK9=1+f53B~lZfo$ zccje8tJ=QiG~K~UJI~ydn3$(Fb)B2rIq>$>+xq+A1IW8pcCVLictN)9MQR=%$m?)o z`znXN95|G%oQ5rvA+}{7?V^o3P(EFBv49%y>dKS9GJ~_m{4Y3}6(}zKq>@=J$V)wG zXrb&}HyGw6x(5#IrE|HtxrHj}XJdzfQjGQAfyLwLPEo1doMkOS858eZ6>7fc%*+`X zS0qp#)Al@9s*MjdX!m`#vnCdTTit)dJd5fEN`FfFj~dff%&tjM{U)Ft^b+d+^;CD3 z&Ay7vkl7D#Gx~wgI8CGw>yfH` z!<21|h5er~oaRRMFRr3C%H`gq$`TQ7h@aI$gS?iO%y24y-|+C}HG(xW{0Q~VBMi<9 z43fK-h9i32ng;mYt+S1XQ=u;^DnuN2yV8{jXMcPZD$)kr77NOtv^#hblz$q~Lc_eb zRGZRrA3#vZZoeVDEsCv~Y|*^DDJCyNA;x}SRLOEC)hGOjG6TsLKt{MQ+;q6&Yd(u0 z6FhL#ux!-6S1Z2V@?$rWJ&m?&_>;nJ5hwY&!-| z*AWN(u~bhRp&Hd_wVN}&>y!1^_5Qe8a}}es+qD?F$#q57)YO|r(p?~?)7mNsD<~+~ z5oq17nadvgfpvpH{QJl4v~8bz+*V1EVc-4kmY_|#`r#+;Wt|oeZlDz&9gTnfzh07} zZ93Z07?jUUs^m#RAiu1o^&8FurcJbfesNI!18S{u{d#iJZpyb{{!TiHs#vprEc$19 z44sPl>gG_4oxT0`3~L__;(%GTUbXsU!Ze_sSp0SOgx_DzHD7=;UvY$8Ps=H#_dKOnS&>9C+5 zTTjhn__x%z*FEMA(U+G2my-MSsgx_d^m|ViYPg`)?I`v0aMuF%yehQ@R|tF}G%v@i zTM5uKEMjNJLat-+Xv+q);n=RA$?d|!PdiTq(VLh4arDnm{)!YL^}k$;5G17775DVy zpH=%>U9R~E960)t(X;cRf(;a9xhet|5#UK-o{^vVw_b!EDFxJVJ5OO_8Ky>1ReSf3 z3g3r|?>Muq&M$t{GDJ}b;l5EneUZ*m8jfUdE{Irh{Ut$Vb83SiDGCB7{-5#jCVxZi zN#xbkK7k`@W0IN+Z=DiLDLOHiZe2^}O7Wt%Jn|yY*!=l8Vy+kUk0+~?18wFsu!2i- zOaE@z;FllmNjr)zTZSB#)34+xVO{v=HX$ zn$IC?y%B3RTz`RzDCpkV0HV9unVC;Mu^7jhppivJbefrm5m?&30l?NA38GsM6#(I= zSF4hfyo;PaA+C+}%~pmS4`al_002D~0iYRB+$R?C3e4@|lR6Ke1j2eiU=%uToP&VU z^9;~#1Y)Kv?>5x5nT1uenD*}&0N;UvmbPzSfP-Vo=H5DuZ_KKdXDOJ9kHV*{j_bh# zzHd;%d?9MGcz^0**iAPP6T3=pCAcxC5$eWlJVe|Ng!WRT&VR)emjfdVG^4>VD}34SYLmGC?WJY{{4rOST+4>^?Gw> zB+w1bT#POUH0N+BBd(UCp%^lNd+uzK8T1SoPOO!QQ6cw5q)x<4WZ31I)SesELv+Ie$`PDG0)85}l zA21+-@rIp!a$i6|VDSjWTPlvxQU{rx6N&>H-Rjy}0+;QZB9&4OHh)k|*Q=caTf)lL z=tXC8b9X*ov-@SNu*66sHzp_qe4$=!37d>zt2?ID?bs-*r+}yPlaQ0iSW2pa@%9$%_)(h} zROZ8sUhG!y(;zB5a`ZaFPqZWa53bpSh(A9l$1U}jGF_udcvH58GiiSpB3ElEemKB> zKk`If%Zq7x=nfJ^gw74dEmbJbzf}eyse4cM!_D(ssN)18-*#umVDeR^5~8zg?qi@% zC!Y?9D&+QJp>`eSuaAv>`Vp1;GZu846y)Ui0T?7WBxELswypRVAP=(8$HvCug3tuy zKfzfRtl(LOq)(EP!P#9(3JM(nBSxLp1m0EZcY=I;P%=2_6{M%P9X+T>r%Bv4$?9>@ z7smVbuXmVLSCE?#fP@1;$E1H!U`^22G%pF?V9uFn8HdK?gRJaRySWF0acLan_W(gitAN~&w6@i6K3-6@E%4vC-($b9NoJG_WXLI1`s-$O9G)^K|wzGZ?# z+5XU5BHQ>-9 z8XCe5b0>fn5)~I`oZrGgTddW15>`pZtqZ1vil%b}4l&_}k2d6n7Yb%1PMzmjiUHVW z)tKfXoobtJ3_r%4tv3!QjlsdB1^Uzd^8O?EBq8>_=)#$$*}rl^fppU!d<0}pfVO5Cct==t*jHz}mK_=S3OEPOD4<%fXuE@5PlyK`o!NRH zVr@N>$BffFoqFye^*HHPSK|KPr^tvlpy-`&lk9;~=N5-!bI!PZI_!iQ>yn$1O!#T7 zb5g^=S+LfS)#SG|RhFimO7F?A&9qUcb)e=db94n8oA)5$>iYgirGu4~6%GCX#HrLl3K#Uk4hVZ?z z(oM|Qm(_z6l6592D{`Jl*kjk zfX$TKZPV>|`SCHwn)+(o?_Y~{2xlN#teN2X7upHBX!u^pWrN&%c%B5VD?}<-L4h&)&p$=#v%FaElD$=B776V!t8PH!S0qj1 zMoIkP`nBeNoEnfZ0HOCsEzy<3d(a+UOLLy0C!Fz5_XY5(e){z3tDoT+ol8yfU%rS+ zNrh;_=_IWx)r;emyon7?*I&%(S>FjZv@XqUgL}x zOnn7>$}GU6_%)b}GSZ%bX;y>zTTLZHndtF;(kKLYmw$hZeL3A8c}SCHPQy{J&Yo!b zHi5q<5n3v!p-e?B0I*#k(v_1a@JCLX^=LwYG>PDw@A&px@DU!B0tTr4WRf`49+~)n z9J}nmGrUthly4&1T3_)Z#-X;!LI>kwumTu&VE>^BzHGSKV{XiGDeXt)ju}~5DTII# zCLrLzzP@Prj?PJqh|)G&Sxl%e`kBwsuxHxqC>xY?1Cwh6wJfELmTkE53kxga`S%#e z$%O{(wV zP?T|a>97S-$)J^ZWvGz#Mn?)VK5xYZcxdgVx%fV{Y*k{=N}W~^>ksbJ)V$2hj^g6X zOkZ%HQ;ZA*iV{};W$u(ozO&1i{R_VYAf$5yAk0Lw7LsD7<=!zeE_EI61*=}SZ(U*- z=g*d#=6(d-7nh4)0l-tW*^M1Yb1PbdJd9=X`%H^xqnUtA0t+Oj0q?^B8Jt{Ii+Hh5 zHJA-G)=O520>3xLJN!@>#4#FAvjLh}ICJ*zr>weA%d2z}LKC)G3kYaXI9B(m=y(IA zoocFFV`-ywgS&Xc8yXrl!E{1q6V|AKo;}pEp{c1vSzu380diY;B_+c|1ho7F<=}07 z@r_BQm`aGp&T^h2NiGel2}yi&Iza6s4X*u=q&*rJ!;oZL(u;TR$t*ckRmM1oC<881 zUIItDDwm`cSpxJ88!qXf;IW4u$ti1kuxfnB&9r zglRch2SIZl*>Dnp3%b2MPtn4KpPI>Kd zrOo0pMaW0158eLoi@82Y4H zW>|kd{rKjU+LsGqzs&q>PdN?wf~kdIv9SJH!CUt#xmh!*j45kR=;nEt~u*mxjhTkT9VoA8Y?M=8HNa&Ns<+>#KzI*RBXg*Xwd{VywFbjJD z`-SO+(NQ13UckW(5o_SkM+IUcw2aMX3fr@yuVx5zS7tYV9*b@bo30O9t3B(iExnZ|k#`j<>mOKh3WfMaiVQd&Iq(IZz>{GWpOM(0#*x`7`=;$9Ex zu&}wTLOEOF{WnoDXcMCZ+-r2|jWhyst|vD`$pG z$c)dzw(BlJs{%q_Q_HompIK#PEe4$2I62mTBx(!B&cAtk7Sb~}5>n`|O?#tm6#DR5 zfDUOuS$CRDYpfFeqS&6m$YZHbfR3>}bo3nz`gezdy=|nifp%rHD=`+W=B-(j5FOoR zK<1umIXXU`C2-0yT3%IE1>2Uqc+rY*d3!&0*2xX3x`n-eidwU}EjY3o(6}_c@bT#+ z6y1MOHp81_vISn<-^W=#Rhphicf*$&R;s0CCYLQKq0jnuHk^u28ebNA4NoBK_Fis< z8WfJ(z8mhRhd@B{CmJRm~A$B z0CUTQ;>^Y~f!SuT(uXx{t*W(d0yY1AK03~aS5LuL1~Q8M!Pcq!X>*|p+UV>s`~Ab} zt72D<-k(vD{y*C!-$KNlHpoKYwaNFPB)qX;-nsCR0zo38jyZQXHyhB&_H{aXWok2& zOfdD>l)%kciXI$t^^-W^M5h~CJ^f$Pz^%~i2f2opUbAQAI>r^|r z4PquJz34&UDugP#m^i$nk%Rz4X^qLRX}z>1 zf{|0wiscJzB0s|`Lv4IUw{(5-%LN{{U_(*le7TI#-RswD--*Uq21H}>B0g6+@zc`K z=)!=`@VrD`t0Bu;q|Ap8J_z1qFgiv?4s>U}F7EzX>n5*N!Le~mm^a=-nbiH>(&aY- zR2U&4#B*Hhj1i7_ltXjxql}ebYPWWdJ-MX0y-{%&HiyDf$o(>Xglhwj zUKeK5dA=kLdT$DWrhM0;>t!0u8?Adc0H?DI^1%WZ-IxjFQ3hZg{~j6=S5k_Z9U3J6 z{P{EbqljgEd|(Q%^Ny3{3YAoy7nddwp*8M#ytZoexQ;D(c@inHhT^6&c}J?EU1d*o zdGn;(!HyyaEVni}t$WPTk(KK-{nycv8yD6)?oT%;n3yo!FZS92R$r#vyM()PBNa~4 z`@#Cio|AbfxH^mJRc@)_pJ>|9UfY$8K^v3s&X;jge{P4XV|j@O*6miL2BGq$O5=zF zWY{+BV%|PCv*}2JRZar8#u9 z%Y8BD?3~|kEE^#qWyu6fZ&@e=ia1sRwspsqd;Ta1^diCLVj*JLP5{3TF@p8IH^603 z?}9Dw*Di6KO;f$x-zB!9g#JrEWCFtkq$TkY&Qj5G@olY`4^Ku>VPgtw6C74e-;>D0 zWVB&z8w|Y;x5Q@W!{nfCwytxe+skmLFQ0A@bY2s0tDG5{$qy|D15QzHp!gajvca1w*aB=Q;O z_qyxos3-(jeo2Y446_=6Dj!lrZx*2ysEvXMeB-vDull6SmAgJ(S!u?228^MVbNx+K z+`oT%_DVSrBb6L<^X|TCH;0TA1;$rs>W<`1X+vB`o0n(AHGkwjxZ>TvzJrYd)6%*8 zS-VkbtsP5hJ9E1GdHEqpTuk$#v>2@+UP!Yws}cQ}`EzwM_}NS6X7eHSUA7h|ZO$Ln zdwKcKUL5$lO*Fbk>Kwt;1>EIblqV{P7!CS#S|L2c?qLdb`ZqH9zP?Y4YUhq?TkmBo z8;^U45MY_C>9BX>!LpZUXW#LUSk5~2Z5tPMn&?ldb@`ZKNR10akD9pp=?h@(4csgx zk|SYoLOojXg*CRPYu1OPY)2O4R=%*dD5fMB5Y!PHJH&{hik|Wqv)haR5kuE;7laET z!$d>GhX1#+!0?!F9v`?v&tPw5yfBL0@t(j+bgs0%mvZ7`2P<&A@Ffonv^o8BE3#x( z4WPo;q!GxV=u)QsZ6Sjm7X{#1v$L}fAry%M-uX9~zz|o+(Xl$wR0$j+_1yZ}){fh( zY6H6Dw6s9_?pA@z1e23afu@Pk?_6hC9=~C2`;2%!-@Xzf_6+N}46X=$`pgLHjvhs9 zN&Atk>9{mHQqs9Lg&okDK`8K!yWRzaZCP(yl=*d2W=vcz04}H-I_fMfD0L z9bey#5ryT-#&~NDM}C5w8@3focP|&FHLEyVnkdwPmgafOTXwNvrYR_h zNZ^F>Cpb++Oh3-%KDejk)v4)z=^7A&GX!MMq`+S#kv7wEAKO`Gm0E12< z4+mlavtu70B5$Y01>M>?k=UQtqJiY|cb{ZyDfk!siAvLcUpYX#yTwan+uIxq=all= zi{EEwonXP322iEi7yXZKpQ(g>662lZP@#9;F0zFVEZ%as6@ot>RtQ1HU2lG_Zg4pi2Gh&vlQI|V?i4Q|K%j0H{4 zcXxMzznC{b2w-w~;KA*dd1K-BGfqzr0WuFrAWg2)oE)5d2m?4cn!!rpv{PQ4DtuYp zKi6H_9L}p!@)R~NXibXV=}n2W9G1ujlbcS$H&$Zx9}-QQb{li5-;MaDjbT+>*ha)n zH>@g%wbuTg6tz~F;alfQTUDXte7)T}Mz4Lx=A1WnI{zJse%S?WVi?$oy;JdwWUyxrV(%l9O-8 zMOp;~=&YX|J~TYZ`TPI7=bi5FP(3^?u;_EO7y zjb>xR2n+`7k6;T>wB6d?_5mvXva%RJzpxD+#-222Vr3<+2kB)ps>!X~fTn%$Sb6S> zZTMkyCxjemU;~e6qsm7y&SkCl9q=&R7t^a+Sc68aV60|e%QCre4B7=?(`mNuReJQ5 zuVL1%`TtHZT07dmen5BQ$7IcLNm2;Ci&H(Y*~ugudO6K>3@$ww#r5zse;nb6iPv&C z>I{|;DxzUv3cC6!Z8A6Z56T*Qz(uJmnAi#HZ4sfi?NGw5xcgp&bc>!MTEr<};E8!; zK?(ak6r_?zt%XH)?y}XF{Gu=3;gg&S(Kikbc?V}_jHVytpix{^R#x`o0>999@bvgN zB_CoC`4BbOQAS2ag8cnIC-ZUFE!nM0Buzk;RqRJ&%U9y<=6p3U#?KOSFs2uw^*2N0tq9Ia{V=s*~0!_>b} zmW!48pD13QUng1aZ=u}$8a5|?Ry!`CqoF}NzxN`gRaV85p`S$XnYU1J9uG`UI<38< zTBUC1C^5an+4`Aye8?Bs?VlnHo>QB<$3UMb)v){0{OH}+Xi5}VWMulC2g&+x-}X41 zic}6>DcSHTM}K_&l*HS}kG+tHbrqrdJ_tcJRiEKRkv4Z;pyu#V1Lf0O+>m$KyiQdd zGj$7AE9~h{wDzPc!r5)TIvb~R^5)9cPJem|%&ll0!gSYi^^x|S-kevv@bNe72IyC+ zP27J@_z@I-A;-G7dAeJO7i1F_eqkk@@S~u|urlbH^xt`MU3pc83qRyGy_AtCn|MLt ziJa&d_5%bKE^2VSOw3-&xKH8UB6)*p%#JNF$Isxw%YUAf6q6ajHy2d9TTJ%C60hq> zp8|W|7L+u6hc^`)KkWGZIInt2rwOwmlV-$AXV&asO_%l|{J1g~G=?EzegBcBX9~;l zCX6_TR}TbtVr>kFJOe$t=E`j(y$G>PnZ569S+y9M6QW}-Ou%7SGfv+>8=H1-Y(EN9 zGio}w714#CB+ zjl6UktX}D_v)?6MV)4-4ianK3BjYCIB z$TPZonN{5#0#a(N^Um$PJ$aOHL`hD*{o}1YoXdrxFdt){>MFOdY=pvkV@VGczfoSo zQQB?-`;n47KO;S+{-XgWCno}UC*b1!`}Z%sxmkhsx`}Y&2@BNzeW>16y24q2%|LDLjXL-?KltKmAoW&MlW(m%^kN z%h$m4tow)M{!uIcp=Mlz47Q_TChz_R@{*fNp#|IC;!Pcc0h|+JRpH;LU`fx_j?Nwm zXqk$#5Upr|-=r}BqAwu8vA~{Rm#mKi(LjKIQUP};HDIQOqqvdcyvY2Uu}K@`Az<-e z{o;8-`XSJ0?m#^>>^!4eug&?eflQw%F*p&s_c(8|)juyFqe<#lid;Fg`BZNZ37;P6 zFTFTh%9jQkSNFU3XYbi2LW=#S9j@LwrR}`Dwo2Fv{5rcP-*jx1!94FgEGz#YM*DE{ z=k)08J+XshKo_g0=r5}!2}DEI*La>!MIJ_K$A$kl4$N;|YC~3jKX4PBQFdnQ%!W6dFFA@4QgMfe*TMZ<|$*b-(Q&{G2vZNC;cC{}>xP!TUL0Z?j>N=PUe3ZVj~ zpVXh9HY$e`vKOXRum4C(W9q*9kDlHDCa0l6WSWqXpC7oR3t47gZFUJmRV%I%pU0#MYUZbSO*72)B23bBB zcsZ3lsvb!YOn8?>JXjv*-cSfFmf~Ko#lwN#Ue^89wIP$P=#Ll7l_;al&Cm1H-Q{B; zvb}e$y|MaQP)Cb*=eZPbT zE?aZq)0Q8Dd1L%h92vvn@2BBbvtX!+IrNy>{3~$#6&aR86^*KR(mKI#Clc5SDj{_ z8}6EJe+olRukz|=vKl=g*{j;mss{X@ ziN8bo{5YxT4^|ArI*hQao{Z^*XX9siqYkUje?Mr9G@!X*-29KNrz`Wk z3v(~&fz7S4G4kp2X^hQali0$T(TmJhdJPn8s-FEw0Ab8NAF zitoge_E0bdo{?<5DF{2)v96s9i;u^jgkf0<*Z}=hb?2ZYIS*}r1Sog?4_&DNY(n4m zX!*m+VdrD|0BX+$cXc|h>oN3AM5#1Z2soWt^#rj1s@qd0$IysE_J$u`$F6HK_&BiF))_Ix@Rb5^{i9PsngFQbUZg)@)DdM z_ZAtHPc`bTGzE1Cj_sEo(21L`gsD0m9%Bl60Ah(ySS4sE(3`8Vq&Nk{989w|Sa2V< z<@a&W>hox2pP79t2M`lIy=XT!a6C|`s_&^Xg~nlR;MoZcRaFx#MpRwBRK&H@D$Ydh zfB*7ea-mC|uNjH?-M-{MY+)8)#?tg0mMh8#pLeP?nFvUgb-#Jn7mm%2X2b6Md^#lblk+To zsGt73ZeMN1(He!D1z*TgV7R}sDps_`d&Q=NbEm89`v=g=P&i}o+c;ild23t%$KOEc z9rtNuWNg?}!%NkDQxof7{*$Rlu#X?dou6-+R81-Ac6H6{@J$Ok+BD*L!s2VTM>qJxvb)Jkur zH1GnCqfP*dkVq)q`0tJ5A|YOgylpwn4BXGga%fOWr6Tkfz0GOR=~SU%#}XVIdRXV1 z9CA(`db{6{1Y;`eC{k*s4MBdV=Xc1_kO%O(j7&@n1`S%ENegI4z2xSGxD`;-OBmO& zhEY~dj29UZt@se+baO}|9w#PWXkbOZazF|3Uh>#WsPt%0Ywxce#08k(rph+O=l?DTb! zGkO%S_K>#oSyuG!&<30sLViUFJf^+mY!DE=!P(v)6K6zsdU#l_+LaHY5n${MY}%#* zrXq|6ZeU_5RAy9FRd4H9vXog<#7Y*s!`p-lO5Lw3onc zx&BzOv$nJ$I_!_63iu3}VKnUHzjb}u5B*yEF|vRsJ)&tHYx)Oz}%uD z6tAmxRAto)1o$U1`fKH>pM)5DCa)f@=W02xF#5Cm#wX>EV7gxIz7&VnACbP8Yx%h$ z{#{gkoIzmy1qM~-EWrHDk4*uL3nJPCzQO#Rt>mItkGGgE2w=+kk?k23cCa=OU^;^Q zsV1f;@=q&J(`^_87+8@$i?|1vdG)@ zA20SwjE)xH{e|w+3&*0m9ZrL=`@YY*$lYBs4LcP=FYXS2 zkG*FojXcil*95?MNKHA`F4tTpud7QGc#6wt$*R8k3K$l@f2`(19u_kdVwTB(NjYI< zJnv!0?%WoI6_GoI4ul$f8I*zW1z<*iXQI1Ys%6+pHqb*!qY4Ryz`#o~r*R=LaCqvK z5O$U;>WSY^o{STbb}2%wSM2mN{9Dzq9(iWTjVLPCPYy`Y8M7EDJR|NMycPsI_WT*AHz&fksjD(k z4K?bHqQ)jyT(l32tg)3Vj%3`EK1(gm^Xcpv1~$jhT%mnV08oEsE@1NB;BAhsbPZyq z(FS@``R9K*&nltR{$1jH=YA3dY}n2MKTRo$5wWR078<9!OD&3a$R}~63Bb)Sx18hH zTWD1Ad~|sq&y&R~8RWKE_BIm$lmZmk{}ijnH`O&8v$LNfqZ3EwVmxK_GDr|ej(*FF zW%0*2B}w2#jq2MjjoXFKI4rEC{u;M&hj*m(8htJhOIWaotQlYEOgY8y8<1>}DSKKH zYepCnmERo>7aji|;Q%ji_kBh`_Z&Y=L5H_@#YPuEQ=JK3o6tLnb`mbh%WgzjzYOV2k6NcpM7?^!0Zjv zo$mbpHTWx6V1(_eg=_V?BYIU`z+tU%NF&O+4UCIXY#4mT#U|JMf`WF?88#70lVFo& zdU>$$@uS>Ohwa+G2L!O>{D1*L`!?i|nUy7Cc4=*inv#+d&u)$ja!H$izcII&lS{c2 zqLc1d*wVPD%?KlPLzMKf{)8iavkSW?npU{kbnbso9Q3 z%&(+n@-JIv);^7_3^(FD*d-XSeUg(_@5Wh;5mNJ?CA`U^th@#ZTF#0Yq_e;avQ?R0vxn}se<~*cAF1kb11XLQVU6(wi_*soS8X_ z0TeTv&&H%WQ%PI}D0mU+Ax(|oV;tRsdA^O0|3fRk1rU{4YWRHx&v4j!@PNbcirH3I zjpgeO0jRUMG)$!Eg;21S3o7iD7oR~-I1 zxMlDZBjJo=UPm=+g@Ll5d|Hi2#MJbF#hC-5X*TYY z+8K+nL8>9pWlBKiSK6$#jk9;|c6`#$BvpS6TH#KX?1%ZON~h<{SrR~S&tMP`h<9o> zwF>h{m4M9g#N0P$#&^Y)f#?&-*4CC!Q3f`+M`1}Cfzd2bE>;(yA;^cGkQk*0^Y%x= z_%)?m)wq7vRkvDLgs7MS7dujy6EXaa^u=BOpK$5u?0L_c6|n+u22<0~m5^oTvfoJO z`EHKbs|h}$ND|}}_$Fu6OHX$`Qqzs$$8st(Ff`Nya?YF`V`Jm2h6eSWT`z1YSQBiy zMW}yPh=^_9d|2&%giDw$2Y#AA;iKNs5fSi_aMo>PWQs*Mi3UB@qOJWWVr|gI7pXjj z%K{IK)qys$V7i=ExV1$A+g0k7+CK%V@PIu!HXuk5nLmK|`rvu`@ zobMEkXTF@b#~632Ha9VmOUwJ558ip*G^IQM_30?f6r!h9-Bi?Qgg){ZT_=f7_ZxsUGW31c;V7y^Z_P zmS%7L$%ai(PYyYc!?mZ8K zxMd(a^8Qo)!B^lNB?@YdtMP5YZz5w%l;hrM$Owb_&?qV5zIkj0$s#Ukzsl(MH$N#D z1EE!m?)T9#o1k{;(NN!)Q~#e>;hsN{6$}P2RTSO_ zkV!tG(+9^c6Q)r&o4go#d8{xf{wPG}j2n(|zh?~do$Tf@DcMb zd1oOLcePc<%rXC=oO=CoB}gz&;i)rwQ$@#k`7p1q07RUbWv zETss>D%MTV{3VoG*_07WOL=%#DTY3I*#-QR83;jy&0q9Oo>yMn$l5~$FydcFt16{a zFK$sE_DV$0?^?J0mk;5Nhfg6md^Lt+WwY&vxncrkcj)WVhN0k7%WD$!4YP}{`?oU2 zVtifoeZF{YEQoP%!68&P&ZVUnNU-Xhwwqi3jcrsva}^POxP%!aoSHojeo01}*y8%0 z&UVcd?2(p9JS0;?)6i=>@+zvotL_J&l494-;D7xLC7%8?#Ju@>BMB9z?RGu9!-C0d zvzB*&TwwE%@dDQN>h3)so{MrU^3>$$^VOSCX=+Q}nw?TIT3p!GFw(jzXi_0ct`C!r zz3#3C_ObXd4}Gt~25C`L4JRvO*irMY)%W2|L>RGva!&;bl#uT0QM9Wq zU|(Zpd*r!TzU6L~H03asDE|h4V^3f+v;;7iajYG2PsC8%$-}9>j!G8s<<5qiV?yBc zU|8Pno!8w$ej-1;M>DufURIJSKGC_oJ+P|(n^PSIqZMZoI71q~PT0_9yNx01iJ;0W zC$0Ptj|n;DF{I=muezi=(81R4mg2d6wUpc?AIsoZ*Ki&ngVm+UE2^iSom8%k*G1CwMmLx=<;ei@CS z(w5|ey<^Y_3X`T026=+vwP z{B>NiGlp*A6kQI9&Iy=kN|IcHjdW4xH^m*H@2vz7GJ1wjc!{NBDY~nx%DEH7V}c z>4)IeMeT0-NTq_ylEAL}_lIF7)$~RJ)>d5v`+Cj4wa(6gLyd>`uRr}<>Wsl_V&p?K z2SxIl?JG-@g~nIYOm|j+*BquN4xqI~OW$ULqoRTADH{4FS)!cRdt6xLt2FP6a3@&X z-yp5$It>Yy>Cs2CDey?u$F<@^Q(ph zjVL(8_O^LkIKtHwyG%+4+pK?R-(sgr3IRg z^rS~V!-AgT#?s!GRJ^8JV;meDUHy&cfgBl6Hn2fUMft-`8yR_`%kS@R9M+>I>EF&h zhb0Oi{E=LFm|O_Hjuw3VF$KbI7z$?xf)i0^lU(Sq0z3)r+&J*YSKp0w*p)l)?^xDV z!5fmQ4bHbV5MA7=yiun&5XN*n{>huXSCgb5JS*w!W>eM?LWUQ!Htna+%*=$YXsj+W zE)-kMgV~gzh_E&-JYAqYmlg7&`EU5<+!Mhf%eI&BdCO6%iN^fQ1F*;ED70ATJvvaAFWmDontq{H z$C!4Uo70WUX)F8B9i|`+cqc~Tk|xoBbL+-XBFU%+57|hfY!33}B{yk6M9g^~>R5=) zX&(qHxr#0pnRP>lyK@Iear1ux)t@}eyt*-WF58B~;OWt6p{6X9&Tlf*8&+cjc<9tI^MmF&J zOmLV}ciKtzqs3DSthH(S-^s!G8$VOy8!$&bp1@*Xxe}qc>F$uuKismXSIx+tqTuR| z{oB4jumHD(6qOk+o*$jL`B!-Oi2kfYb{U|C`6l~~Les&+tNnK3q;VhPJ6Q1O3cY1; zL&*w*U~-Pk2N45t6K8h*GfR@!Fz>FCoyha6rs6DNmHT|#?TcH}d>V zFwPc}86PA4kV8%q0Vj_O9-k#rbWKX>ru6Xh?ULL8;6U`lHE-W8FC`)@Y!XruB%CZ! zk0zau5S!LS4(WbUITK2^p-DCIeJoK{WuP7vl$miuo`sauql2N%npG;QHTZZjz7!@- zd=T`2XukFfhxlN7)M*qHxl@7!(jG?4yC|&TRGSo~YQ@No)ln(sUeMl_6Xk+Q`v5*u zPyE`1l?{sv5D&%I5APg+@R`QKaKZ1T#;JxVt7as4r)UynlP9=c+@d)rJj`z1{$;nC zdjHoSq!Hw>Pof{bh-9!QolQl2U%a_5Kc&oHSn82}V)TG_$Bq5H&lcQzC%|(|o=Bqd z3SyN=B||2a5!yP_p<^i@6peSJ>3&e(!znftw?AWmXHj8w(0(pXHSc1=7Gxb|7k2|J8zC50nmNcufYa!5H+*s|svjSgF zn1UAyufo9J8rcW3N8nhQ++_pUMAODJYBz;FCLa;cy zqZ6&Hqu@ue4UtL&SOUf%x)b8u>zbCKn+r~?ue9sDRrk3ZheCSxLdY44!0fgwahK3>4Bx6Ba#ci%Tu0P&X ztcZHP)3H>7HdX!JYjUesBjz}<&$=taVB~bl*G+&qfIIIo*7J&#=))Egr{f<c>`TbtIf=6wq|V;X8t2 z^&iQvBmb&^G`I)14qSRgO?v(dkLG%B#zP8wt`gA!=IKy6pKxxsYs(so$=26n*o>s6#kXvU%k zP}3($oSU@199!>uRX_XPxhb{{MnkXNI_;rV*jy-#EXDe2e;lroc%AJxt+=WtH+_At zYlt-IvAXXztu;$V!!aaLYwr83z^6#O=>v1TLYQQuQ}cb|n{_w4iA^+Sx7jBf^I-9* zCR|03(y``mC?-+OSr|JLhu=U=qa2ihr{jk8@_sJ^fE{fi|no@P)s3MjoKIdPNpAbrjcztuRDURG1_aNt!T z`9>*ojmZ*1DXd|!nmU6!(7TW4NzMQGzJ^H~wCOc}A$Dozzn`~(F~ylLE4BV-lO-rGxHyM2Ck^DfY>nb6BgMXz=9Qm8+u6!)A#;LaFA-qG+? zt6|u0#+;t4k(fQGI{QCNopnIf(Es;GDpD%aNEj$c!=(kJySt^OyI~+mmvnb`ccXxG zhk%rHci;QW@Avn6p63t7?zX$_Zf9rbe9rsxdTSBGYdSD4%DN&|Y>aNAF2xsizLm@u zIJ+%8G?7~Q{;_GP_lSD>4vt&s&*^9G1KE$fX9gq_1k}!wULN^2Iy)hBlF>C~J?pkh zk!az0ybi4j42a**ZqjfL%`^93ZzQoM4uZJ{+2b|}k(0wF&*RWG?3-@GSU8|oXfJDY zNGRV|Dm{Pur)9lRY`x0n$Qnk#Cz+v(6)U5xmK5+fr)n?$+Y2jxK0OZ0_9p=7Q&vJe zFl)W&v#|5kIkri%GguVb%kG;ORI}s`reE-O;RK##vfEl`=9!k+ML^(k z{#P}byK>V8hl@?Sa8?!;qEWbJwGl?T=c$CkL@_!CoR=IIO7Xx>7*%fWv0kVTr&8@0 z%J8jDN`I(*MrwNTGE20At`HQzWA5@1(q*-$8_-xD$5g&Z{LDghyCjP{5gyN%Gej>v zVB!;vq;l={8cuh8hlpznpM$+iL@EgX7CGS}%QD7vrFkZGE^f4i`CU{=N)jca9W$(n zZ_fE#hw>*9TWUyqn(l2pQQnM_r~Y+HxoYM*pkII9kw%_teD%`!a1WG<9o4xtW^!)* z*5C7XJFqGTAMPmA#$`q48Qbmf zZGrto%lCLAQJqd8xOQphc+umK?~XYoPE*d1`hDo)Avf8_ANnj8(D9%?ikE28-Je&F zP-3lJ_gv~jqp-7<2EnOA7eLTpHRn7nRJ(;F%(OvRYoy>#E0Ty;D}CF8Y<O@|mp$vAnhoQ&n*H z*4b(#cyB1K1L;=ER7(>J!@BDY)Y zuzE(0QqY0*{@HmVwm$G13*AzGfN=zS$Y0eD$(xCaSZ)Sv{WtQ=$D#nIq?_@1h-*fJM)cuq+#k-h_V5sgiZRNgu8JA;RPTRit>=7##w z5kWJ@NIun)xaK5(U3yBsFlCproO9^ATE1Elm8&%+{{7nx{TGt&@_a@PtRzyGn91n&KeE3S{vlwIsnh`AR?-xiCQKdM=$ab)SQFx6+O8UF)L>` z`t@K?2gbAssa@^TiIK6ug{h-Rh=Q6kl5F8c`U;(u^1?S(p&V8r=8WzIc^X;1?5>&* zMopY{;Q@H#;SArYr-onKpaWszL`Z;ovlWf%unro)IbD8#*OsrVjWj3L#~1Glh!)% zj)Jj(>-iUtv@>{#9$clad3h_xz>!@oVsdpeQ|Apw|-|H{kd+;C?Op_-~VvD9*eIDwTh@lh)EK4*%7e0Q@Jys<5aQCTa-A)Z!D)uU;6MwaI^i zk{5K|KUDoS=KF%3fseKFdgRN3NEN2-G6`Ml7qM51g!M>-=$p$?2^eUAdKXd0A~zz_ zNFbO!%QZfZ`;?NX$KJ2@3KZ^sJZAy1Z2SwXqZss<(VPQs3Q|l z1}FMU()^vYacl0^j~C>Ty*U^fmW~P0&K*B(x+3#UkU4uIj!=$sijoSAbs87va(XF$ zI=UNybq4Hqeo(a3H(ahu&rLjyBOW2doFi;v@K+8^aZ6bH? zsz8(%ai7$*JWpCOQN~UeS!P*3i1gxAoh%ti*jJLKQ9#E6q|?oQ^ecRK;pfJk^N^3V z*Mnawv#eKn9@{a(Yh^vzuQO(DqP`o+4>$b6iV(W{!U4=h&Muoj zE8pSK^bfR=MbDf{Yqk&b`^A)2u2YsMv$#Th#$@XVlfM$NZTE^Xhz?@W2Pi#g^}{@G z#O%|xQFhap^j8`_AARVJGK=6vn**VyU7atHiC-J9&d39JZ>6;=g(UmQZNCVGiSHPA zx{UQ5j;Cldk2C4C(AB3Sw}_s&DVQerz!R{~k2~)JAryPsy#~xEpn*!|g2{=$PLGn< z+_+J`F2Ng5-sWH~LM8ZLNOukrg%@Mw1&cL$FHI@y7qcADSxpd8`8a?HaFNO$iECur z;UN@Y&IOj`0Ii(UpwdoRZ&fI&%Y|hCTskXIz#VI3QFQZO=yWj$ST@&twXLea#8Ls1Hi2&5xbyth5t+Qe~Rz!#T{(f!r*6&fewM*#gW$Ro+n zXW7y5b1aprJv|5)9V4r|HIoL8}Vify%h^K^hwYn`W+z{0zQMC)UP;BzRNPnL`CVHyGFZz z9ml`wzMzFvVo%SKelI>Tk6U>3sqyB`Ytn=HKx|i3tQzts=4Y;N9J@+vj9pz%9+!Gc zG|imtH1J=3c8p)-2RPr!^&foChNT0Y)-K5MiUPvPHluA@?8k>L&Jb18<)vr@i+&#| z&Hi5ubdXqds%{n)@t1ySr)w&yEB#$X1s-?>?sGDkmQQT;J!rtHMQLH9eDl-{XSAtr zXY*MAA`V*BvcB_MEqcZ3NMsTy3>brdaM0yys3o4Z)OX!0Ie-JYRANe#1d8*dxRcJf5U&|dt%Y$Hmcd> zw*nlKA+NO)Ct4#X+TR3T=@m3|P?5@qhliVU@CqsD6qp zsuqOt)_kf%yzn9rdZFisRDj4pnNrN^oCZ>l?uOv@y%65qU-nvA8PQW+3I0zmpMHVY zPqsa{*|W=5slOb7I>yOV%w8`Y;H$-%Nbhqnjev9~HkQxI{k2%GSNztuwyzTEYDjl26L<>V<{43;x#qX^9xq)hU#3Jb@Dkuw{$aRp;Sg;s zPkxxn<0W-?*IB`wegBHpC*5oA>BNJq1$W*EMluBpX4H`zjFUbK(;z5*W`A{FzIvsv zz4XP3j%(Hw4HL{f+b>9t z85o9d(8q{`uldq7pX0p#8 zIroLY*1X7)?;D#f5wGy~3ScXO32U|-U1~&W6LK7}!?CrOvHfVL;J@b1&q<$ynXS5) zyLmcVlV!Au(7t)l(|Tegm^1^ad4hwXALnKd{kRyAAz?bc(cg_<13nB=ELa3TJ=ciE(4}5A)BUnX;Tq+d_I~10+Ob3lE}V}i>+-H|8{J`i zK_|+xeZx*rLP~FJhGyxvU}LOPV?w`_dIXTj7~gX<T7qH?MzU&h?M%R#7cpJWhy1r+@-_I%h_9mL6lC$rn-e@EEdk z-6FG75_Jz<6@zF{LZisQzkmPqF;L|2)kLZD{KPao-oyvVhyPvTj!V@QUeo65f-24N zh^H}Tf8gpjr7 zT_W?zw{rH4chsy<6RN#I9FnK^2~j`#(0V;x;)Gs?@zxKdVP*ZER&JDPU&GAv5ktwu zL^AZVqw2BAU1(3vig`T$)5wzfKt`NL;F-DM^WpL70SD6>13?*2fg~5*Tt{3y38x|h zZsi1Yig&aBe&_0Ma<&Xnz88uJi6`siYHV)UbhCGo(kw;vXfw-?gv$O7cG$^|S+1EF z+UuTMC!v6L))B-K|Hg*5p7#2`xiR3m6k#0nIkN#CuNrNT~V<0?eXrB;Do3x!? z@3OY-5m75K5Ok_5&Zne`3~kHEEXeyjckZD{VrzH$j+0Vi)S7k8Ba}tKp&IAy-QRz^ znTJxj7BO37m{%G1c$muMp=+_JXWs1}3}r!p8%_^T`VPu_t_Y0R$GS8vuazg>NBIvB z28v}hm08T4rz|uhQ%@x*o?Fs{?2PN=OnF4>9FsmKk(thu9O!h+d8jh%e;>XXSxWf@Ol#ngy>Nc~=X^+iJR6MAzNF_lxuhoCbK7zgX{e z&y3=9Z6$!DduOCo7d1i?Se0GQR(Rk%S_G+Vs#8Ot`8P&F85aqrv+aH*gFSw``qHny zggh=*6uKz;+CEr?6-4F_1)u6OETEz5-wIpkliuY!8Ar$eWe*7b>6fS(|82a&+&_v+ zgd-DI-pOCSXt{g)-0V&}-Fuhp#sqKc`}7czuH{F1t%#c+ZeDRzttC0kpbK(l+4r|0 zW@yXC_+Kfky|Ckr@{Y02oF!*BlX=mTrc^(?ecCv$td&+cgBI<(bQo*RZU@DIUbPt5 zo4qU!d*k;4QkX7!Sfd>6_8T@DR6mmpr>he7$S`h0K#H8VZxv|xR}SJcyn3N0m4%r? zDZOcaO6zUI(?+`p&zfKd@W7m@Zq7aPzWn3A)0(e-f?ii*NQu5BB59dj=k`$0RliqG zvEks{oY6tG3_E_dRSUTf1RcDRkaAfIDJe&fz0$VEsr-rrC>a2eLcHJ2`F@q6z!q;w zWcYS7hLHcJ)(50u&A1w~@g@lu-@K{8nX=upxVWzQw+bYC00X&6(xqQbdq%4~D(}FlE&LmOH-HeQhBYvns2_eOE&W7<^~A_1>?~8{#)_HD|l|fQyzP zMzel=D;BOM`uF+gUa0f_rRDdxZ9T_FW9PvW_;Rt{Z261NG{dy1t3x9>RIkchUKAZ@ zPtR7Lz0TW9fq|dOZ-_b(y5EtYKH?*GuS~Yb&nxRTKrAbg;nydsdgyglpn z%yUy`CHgG(0@B`SXXH%FwaEla<$AM`%?1#>P6*y#TTg>^Rwer`2(Ol-M=0YivcCxE zX{7q63^AxY&iYf=+CK2K4$?<$2@kSFb3OSDbvo^Ik2?iDGDa61j+*`h8}O2?rF)%3 znRApTWA}9HQ3B&Z%4(%CKGr`F!GG8NXH`s=q%*`KU;??B*Y9MV=M7)a+xB%e?N@!b z?7d1emHDTDFt}&*AZEB^(Wc6P{i!EHd^l%y?KqeX<9E(yFT}NKffO0o>H~*AcWX91 zTL??$vDMvYeh8(eEG8n=TP2^4)t}XqvQnP2UZ1No!OgT@c$zJU>^hM5hnUK3zD~!~fEuWblaG3ReRi081!t>tG~{>bJ=L~=&7y7dnZ_N0IGU~(8GFZ~$4tU3pGf)TlhX?Tc8?oXug# zATs^=dz`TFc2-r*@Y~?qzio*So@d=oyU~r63!p)C^xkYt4Qc|#1r z=DyBhY$)8&+{|67+eXqt0cz{;JrorjLtZ!~gGiYT4QdUXwYOvyvn8^9KYtcY{#VoT zkADNK#A{jm{xW)=PfQc==Z|hricR}3VC8bbMlB&Y$Tx+QpoaJ(9|aj92;vp(Ab#|x zI;WOO3P3XSRqkU(CoX_j;XK{i&VNr#DAwDR4%!-Ex}AX1^Tq1h-YT;yVgXFnhr@u6 zVFnx<7+XKX2!{44AtBNKGR_bGgR1_WVPw&uDPofYa@FRdl~c~DtHTGFSUmjy-#($O z0iP=naUutFLhP=`fSI{@^eMa`AL@!+KOBL$K;tFPGCELOU;o5yY0>i6z-3$MJWv&~G17 zz9tUSLaR8N>71JOM+onFve|okWZ@KfFySM6wAb;ZP~+P7svrW)Bzb>bx?V)}Pgkbg z1E%C-j6Xk)7Oz$UFB6y_HR$syLJA0sj@h56Yqy(I@qB|g*Ep*NXa0N#?fFJa=XJI{ zrp)Au3%cDTY*{1~jR~uW8U#kROd(WWA0qFp7mf9T&8ng>M7Dp16YJXy<67N;=Z}wD zd@!7@%y_&xpENpZZ*z0ljP?1uyVK2iDZOI6%O+@i&w5%-c)4vOXxFn9fx%vPFfEi; zc(D7E@k(v&id9so7URN5_mzk3Ec5+&v^In`ki#>_cyV?#UU_MYBH6MJ3q)Z^kRrr0 zZ|ccfG-}cIijRXp5Sl8+8JDvl84fVEe!`I@ zO^w&GVA6ABZgETI041c(%&6)ZjwSOmGn+U%y011~r!=mY^PGTPkCuPJX7BMU+D}az zaqcls>Ny=RTsFq+pfnaD^~q*+e3R&48Ntw z%e`PQTHkV7O}2oCEW@u3f*R2HLzkBLmh>8CY>_fa1mfNmAN#Q4jZRMo)Y_f12))`_ zIB;%U>f-ObZhZ_s9IHcC3PK|Y=mg)qeY=WeO3fD*{LES~55g6fw6vUP>y#A_pSZVe z|E6lL{NK^~-Q9@`Ckt#s0cO|o<~2#6(*tqMLjb|FfDjD4_H=S;`Nnouv5}GiRj#S??WLU!tno`K8$j0q5kSV@gPP z#$D+v$(Lm-h^Tut6rd%CJS3iJ!FH-`xk9#_WW_hi(J`T>z|i0?6&aQ!Iv38lRspR0 zMqZL2g*UPVcu+AiD}{2w0Ja&9b7n!KP8;OcNpdWXNSz1HuW4v7p!L@~@Q22Qe8dgm z$PzoLcQeQzjTaLI6# z@bv+>98odqiF0;j`Jxybfd2iMHRTKZS4=7s@r9TaEx}hS^CHeIIj znY$C~9!;RL$4Smbqk|r2m2~X1L0J3SvqXR0;Xm{0qLU?V??v^WKexjNDA6%E{cRHW z!2cfV$|`|17|AH-iCiq*ZMe={hm9mNlA!!n!q)}~#WlDnKC ze2;c&?j|p*z2~ZgFa8@d8){Sl2uvXUZb?8=px0+-jx{yvXZm)`qMKV?230HI>De2S zgzqG~`>$J;$~|`~HOu~C5`jz*0UxM1h8UZY7^&0dB5HTfZzc)~!3Wcxi20|sA7ef4yaPSJ=YTDP9-QS8zMPIa ztXN!T;q&W-Pp1#ySjYIipxyLR$u;p3vte@HsIZ-}5GOo*1S1VMK6MaRZ8ODuZ#bMv zvv@xTLZE2F_9erS&f|XNGW(R1IOiDz35$wmmv{y9UVO~zO(1!F;q{3XyHAft)@}c_ zSbE3Jn1*NsSrIRYJLPY1a#b7gNfTJ=DXlkbjp+%3(pD3x4v0uuR^5{%cNf&Jp$Ju=B7TG$FI({t7JvjA}wu z2wp&mRX&3#F#y2v`IU9Iur;x~vfN8QQ%huD1$m{Nx|-j1qYx)8gWLw%JGUn8z4qfd z^SV2$y3^}%_k$C<%z073HVO6^VB-GeKQ2N2f4?t!YG7COS3^^D@Q`=#2t1ECC}wE8 zpOCLe!4T>5{!)0w-510;byC|H-n2epJW|7Ix3FUy8oO=PQ!5~%I+r5O zv>_lat8gL1e292TMut@~9Ri}E*Mqi%H){G2T;{{<5TiMtf8Yvr^NH>z-^Xv+&@b%h&UpkO6D0^+Zb!R! z?v~;42VHq*$31*}QhV0xKy~VO-6IQm_hgy*?<*6=JePo(6|HOoh-9TSRv)qVWVyu~ z0Pbt7(@_DmVRrRI=37w!ZPRFlt;eHc>epfh%K*;<^Syj#Jy*1%qeW zsrz3}l)vy7e58$3fv}e|b~+O_zudDQQlAX?UdNB#e5YUTTI>01cVXx8`&axfT@tno zNlrn0<*57VH%$)^jKE96?nONLL(r<4cJ>3ZQqrY(Pq1yQTr^y1fRiL|j@@R-rn=$d z?)8oLj_QUxBF${6yW9z_d1Irt!qomm~N`0tUBWZvPd*<9wn(pp}zYCJPX zJq(_l|MByMc?B*3=14)t^HA!!_Vb*aoThCTf5;JqAzvPpmw!q^I&aGBkv+T6UqcaH zv97-3vcZ`HL7q;MLnCaVi>MPUMdMb&sf?jkcaLbl)W??oyid_WBdv(Vqz3!+ld6ed z*4rJnE2%AAxA5VMfyU{Z6*)9Z&M+GV-YE%3t;D8!ySn&bdNBg;Ygkb*f zgE+P-I=AP?ruvVMt(rV#XwFXN)jpQH=$gJa1KLZXDrnl`PohdtT zRD|J=tN8mc911XRc_QX3V3S25*Q)~Ut^3R6#iHN~SoLV9;@Nc)S-CqQ36DzeY!_ef z&>bU^edN2kliHZ7o6Qd?nIC(87Sj3GABrs@g5uiWK~?>a zYv#xnZ_c?!>MZ%LbO~l~eRWT>peZ84_5R^(?8=}0wtdXZ($eS@DkC12S^-j8TaevZ z14xoSew#iuHPvavQa(N5rZJQC5v5HvX^#Wu3)mB0pvKxT;Ydga}ldu4FIeT(=L*7H_SHOYf05>J4kN9yX1$jl^bQL;@B-`YIA(6YoZ ztK?iKjE(}Bm?<_fhn%{nM;TOfN3PQQ$dNkZb2^vQ>LaYUo~X-MWy})m@@#qWBMBc~ zIEoCq92cXnxRPfcUS_+bKl_1p<59-KaX{-P@fWIyJl&!m9w~{j#-8#3$?D)XtygJQ z$!OaWBL-q<)S`il6Vt10q1BBvs@#SqkTfYHFpZ2YG&J-iQw*!LmODkpNSQ!U>uPdr z41i3nbY6$45GX^`Y)BXl5f1DT%*@QWE!EK^BqVGl)=7|#iI^A~wLh(w-D5g1#!DN7 zh%{*`;^6~==11O(hbGtHokt72v^oJYuchJ}k)D4bte|L^dc{M}=9&q*?cC=Z( z($nAy z6C6yazp*Ge2@6M?+CFg_7Sd{yVJxj%FnqEZLz6hli=)eJ^(=y1GI&WM*6=ZPy+eTA z)2GTYEc@1yJ%{;h`;G^nj2$p2nVr2glS%N1>g-{$*WO`Pj|<-}+I*>lz` z1@5@@891hDGl^+&aJhot_wCr#1GP4O?jfoOmZ3h0mD!3h7elY-Y&tlvHk?W)R=0`* z6ws2&&kZ5OGP*YfQj4eHYdgfGQaG0qKh{m{o2P+h`Rxd=qH zSn(;iUZ!NbyC=I>Rppjpy`LGgr1-O82BvSuPyV7lEBtuw=oUxeZUQ)oi9VhFTUKUj z?Xk#g?C^a5J{JUM-Kf1AI4Nx|8OrBdn`+M&+FFvt{;mITIj&-@K>Y50+pOpYc3xDUR zL7&0)SQg~kCI*?@t_Mz@r<>chVcSY>Wy%5yxLG0*M6ZLiTQwHC4mFEQ`>=Tol(?qRheIDx?eR$xC#@L=D(eoTQeajD>_PA9ixJ4)}I!gG#h1syliH zqfmK{E@~Wri6Z0u^kdb!bvBeV(njs?u=QLG#K zv^lA>ufZ+UrLZI@{W>-c1%GD9_ATK9-r)C!QE#mCKu;te`aH`EP!9?}pK4;|^HR0} zL}CZxJjW_Dog?0-CU(n@SsG7Yh>C3?-*g7p%zGoD`*h;!_0SmSCdssn4(8AfW;@2kv zcN+^E_Eb?lv{B|>qju!C=IGcUNiHt}@~3!DZ+wqbDDucm$Cv4!rzE#i$ikVN3_;y| zHbbx>1oI1@JzJcco1;HM#7(OVoY~aOa~u0O4fr%~KKp>=Gv`Ys$)DdEDb;91aR6W7 z`tnNP`O#jH*`%v$?21Zfexe*;%ah)n;EE6Soi)CctBRjB77wNA4h1;^4&HWfvFpyO zR)q9cW6|O>O4Qz4^Ej_lOItQQJ)Nv%yWf~Y-5f=D6_>I$-IqHZ&VGhjxTTu$`AQ&l zpaA!M8sR_mo3_R|WDuXrWwJ&;y{g^B!+l*S;y2W(XNyN|&_YX2{?-)@yL!I1awCPW zt&Iug`Yg^WV=%-~^aGfFBWT6z)?JO5V%3O(Is|JyKD$Mf^_o6`&n|pu_x1y&S2W{@ z8;yA^oG2%87TJ{Bb&_zkXHG9`mNHeYxVtmfdmaN=I5_gR%vPKVeQb9@6WO20Nl0id zwrYEOa!?s1oEFf_s2oG;IDHgVunGVy^QPgNm$ypvUKvBh%pj=`BNx#pJ>-dXKw*?8 zd`kuA)^W!w6Gr;AyT>Pc6OR(yf%d-H&Y~?X9k%%~kwlJHb;Y4&DY~PBziQzi1Vd!~ zbJ_!HzXl)MWI=uTCuTu_FG<|mrRMpg=`9`P2FDTQVdzFuk-xR2v8WfP`Q?Ilu{i0f zZpx4J8!Pq{JhlJvoqB&YDQg+SJOVwjqxI;Tz939ie!o9M>M}8afsP-WV}zOR;B~z z0h>mbTL1CT+}~TG3+tQV&82|4$0r=GKS`QzNv{`Pm2@|5-ZiGky^zCu0wkbsv)vWF zoy6!kTDKCe`kKtBR)2q;#h%uSZ9UAlHt%!4XIR#7#^sGZVl!brY589FevIX+BFzeX z*)k)hnmq1*iz7mGSc~K@e=K=sZzsv?sUGhMeFC=}YPE;V zcvydlYmbrW6i}KPpWHtvS+y2zeI+!fALVov7(9ev3bA@QZb<@c>k@JqYQ*}GLh;q> z4SbseXC6Tzp{7ecd+7GfTh1iPw~~WAnFlSxHfOZ_IT~{5A2X$?`H0nhENjP2mg}6b z&CDEEH=MMZJ|h0zyE7B%c)|{I<}BCpAu*{xP(UG$@qw4r&ZVLKw<{w({e@^6h5E$trd*ma?ifdJ!H`jCX@Von-V}2<=fR}pYcV5 z?P*@+rU&TBF5i3&=8=87{-U02EnVt%R_K|gfTd0PC;7(FigPUVLf(IkT^YcnD zuGZHV-;;C@yT8jS93Pfz{gy4t5pjJr86Nxy2T0+J_f|j$vtVaIyN_~Uc*Q&`9|=={ z;DXgWG3bshS~vWdyl#v-XLQ;&x8)6w#FWtMe7C+OIMWgP#nSS}SeH=lp;@Ol)i$X} zg5_QR5a*QYu?~`ujFpHV8A^5I_Jsgr-lIv0{mX%%H6&Q_L?wnFl)5^GcVDds+zcYZ zd#Kkr`g%c&fg{(Nmo3aFV3%Ca#PLH_*%Cot-T*W&zp)l`St1r!qRgHZQ3sYjJI4Uv za}#?zwm8eB6i;Bu5i^4)fWRAsNIw&=1Os@N)NxgK?2u(CIKCy+neZC^Am5vH6v#NR zOJe(`mcl1yiNFu?iGGZV?7MQmt&IL6uRa?I47|KYZftSnNb5NW6!P$1HSuw(tqnKE zAcLj8A2kS@n(>~o?6dw`7u!YOhs^NNRq-TRI3;asRlI>jKY?4NTrt=5;gK~6CyufP z2`6gcJ;T7B*%#8m=Z$>i{DiLMz*bWRjGqws8~q+tt5c%f%`#Nanl*j?>~{5U2cWSN z_MFq&3+?9!1cKogZi{T{t}2T3e@|1u7VfwcW2j!Mjk#jElJQjz_63WVD{vjq9L{&( zj5wINK;)UKM}OmxBMlqyTOa$yqy)`scb-zIRgr^xKI z2+=Sy9!)*b?+|^EdI`Pv@8E-mXk(b4md&^yVAT9=BsZKvK2KNru0on-v&nCA)&ISG ze_bC^0+hAy7Pw<+62-d5g_x78k8|ulk~JU$-Qy@qOp#C*xo67|UG=$B(Na-7T7w)F z3RD5xKHu05egh75N_N*gyE*>+y75X+(c&x@nLTgEQ_HaQ{cDl;4)}FEy?#R1=$z=N zRH-F5;y2B1;cRGU81!wq0dGFU<3@!OnzN0cw%k*{AHZNfwWAwK+&~eS%5ye<;Kzx< z4(bA6uQH`ISzSyy{eU%?E8mHHDddtJfGlHzCAJEs0fu9G9)7hH0D zwX>371n%o)_&F;;$q?j)`~J6MYg<*Qie7m0Gw#UgBi?yb@rIK2;FJaKIe~N4=Jl%E zrEl4CMa9L6B;c8)fHV{$8!&g}wS&1K=E^yot9BdA*=fQ%+}J4#T3fA4s!7P@%q9_+ zy$q^eA%;Dx^3q3#w|504t0%cl^LKsh|BjWB?G5Ky@FCj{z{TlM&DT$#t=KDa)i{M& z*TS(%&nL1ztnY-AmdTyO8UJv=uf?Rn>B{RmMfx)jb2R2|I|-G^So<*8UqcoJBa|dd zs4_-MPso%&q5wse{LpgCtbG6HIsDH*%{Q+P{|awPAOk{=aGC$YJty{HU!%U;l93?Q z%$J%$X7>i0vPOgg>n3Pw69J^s)Q`XW4OsoVdEi)K`RQ-rTWj*P(rt`c62^ z5%mG34De|g;8%oi!e2V@Bl+Yo@T9x!>t8lBQW{hnu*nr2=7klAzIfk|pkiYT*F84d z=bT_23sbXP)_r=!Gupc>DXERqs+By#mcFdRl?ZgoxGaX>4C*S>>S`X-&BK2Z0G&{@ z&x9rPQyB#XVL{kJNJytpM)%%{GpgQbuz)_>uV24%p~(8b(edc$=-63JSy>#&;#|bp znSG{Iqh}wMn3`JJiim^|EyNIMSa%6x;-WK6y0`PN2|@DKsPdhmgDe zLj84kVf@{`*IrDNeJ>Da7qWv2Ja57bq7^*SLLxKHtjNFeWLY2papx$%zjO#XA8R#9 z^_Zu4(6#AJ;@Zb9@~*&3O?sVmppaKGNAehaZX3g|7=Lh|l2xDVmg)@vX68?Yp&XrhES?Z10R>^;;PbUOFGhOT_#b^gS8*NS8K3R8cV=g&Y40>3 zGaq{mLEdsQmd7-RyI1AJiNfB9Y5t6i)l3!Af8b#0-{@QJb;k-@ihdC#LS$|P3Cba|6Czdj2L1a1%P_1 zg?#3;^7dQve`pcMNn0-%CIKDgW%esnv_}?U8EBXUPiN4hxY3KD92E?pk`UilzHMwLKxzB`;SgAh25g8CP9`f!q?r=~2)}9O*dCAnF z!InhBNkm+e^przMzM9h!gt{UK9TU&It5W-UCA?V;-ehNIA7{K={s2M!{sl5uZHbaG z>Yzt)ztrR)(~VIcd>RqMYIH_p^afl>%=~EMlR>4yRnqG>G~$JAmh87_<^A|Nt;ET6 zjycmgv3mYIL*RO80t>Y@Ld7eCjqgL@l_#ip9~pgZ09jJ?6#2imS3$LB&ls`w)(Lr9!y7O~v~0mIm6Qf7 z3Z|q;0-vN|5zYIq*~bp0(v5g(=$hjtyn+tW=EA;I)KTetZ{PI>=i0{{^5vXQ%lB-x zC&CBj`Zng@+i@LVDe0}neIVCQ$G@<=FZ(;co^pJixv=_y;I+(q^G2YPgU8um?)cgE zvll*5V@w-~x6PWdF!t5CRiQ-CvF8Htsrkk(Bu!ZduQ`KwE$N@8Mx9(N)XBM;VD9JZ-;PF!jyxo6=$h>o$tHXNeJs==%MikRW-{`1OW$;tq z-|>IP<&Ys;cNv8r0O3a*s^m-@ir^b7MYrdowhKH73J?%E~X|8FWIn(mf*F z2qwOwVLqNNE-n4BJ6*>AV>Fc+SGiwk~;{D@F4!Ge!+noAR^fz@9R8b-VX0@p&oe6qWu~>hgT_XrwnV{t0P-_A@p^>YKMA?pq z0~==Nef42GI!M6KKRhe~c`5^3XTn}G7cX%h;duc;At6w=C!CbS0D2`6aafZi~AE{032fL|d5k}R*8)ZGc3L=4+cXGYUV1H*#*T>&nEqlDAi$aEcGU>d;7xetS zV{@oqBPNP0iOu=G1qSx)ICk$iX6NVkZqLO|H25!&RGIr^q}~YINq3&U)A2Dx?icf> zr1*8GhJ7s5Z55t;2DTJ~SK*y|ukTl`0;kwPn?z&BF9SGsh z7r26eKlY>GWK@4F7R8a2s80gtAkyca4J)NqvPa=ZeK(dDXnd<*1f2p`-!`7BKV?pm zw#Pz+kW75xUlYxZ^(syuqtds^ICw(UOgnQ1IsmPnI=2LOOZkSqCBxr$o+Re8NAi>D9K$O(cghh3dSrMyu z;V&d$Z`m6UDiSY#L-~mZowp6k%9jePo7MiVt%zIxW;R6oc}#Xs8w1_Bmx39isD?;h z_T5gv6$m@2${*7WljCT`6-W=Q4v{Rs{VIi$?1c(I<9*^%QYe(9AFq}{eqFZ>e8`)M zKw=>}DkUYfww4W|*S_N5AXLhi?t*HN=JTt90zJqaO`!Zz{C9k==Ml|^4@&1qMA^#( zeGxB(goRP_Wl{n}LJ3yWTMh-6`MlJB(VQQElaHCwep?KjdE-nq`ue@7+ zXc>g*6GnXB0s^ud)myJHx%=EmEG%pbAe1jM zI=DsLl`1ssx~_3^3Ds|uS%CmT@Zb~t8#H&^tO7T^=&d<*3lLh2+T0H~w`CPYa8lU3 z8S2`v85Y<;&9o&Z@#~(UMV8FMbZ+#~xv;M$oN+4s9f$8k0&}we1Yg*61Vi^Q^56du_X307 zSQTYjVnw=hq|D;3VD|*AKS%aC#aR~#E;$zquUO`Chx!Ug|MXpfde)!IZypk~j-kR3 z#}$b+!}tmRaoE>n?%Jd0ux!4Yzj$w~q>uqn60g>1p=@B1vN>nDt1T3J1EArKo4mB=NA zJ$03PY1(v;0|YIiNVYo`XWY9@78khh2p^!C<2sC2EV&@NcW;Z==54Mb4I4G-yy^liQbe%c{Q|uq258Ivd=5Q^kmokIy zyKgw~(jqcv_|*40cSH?Aj5>V$tm@ysP@6!;CdcEb2{Tw>bztL<$D!g#QpZ02j%5~h z+;jv(g5S*qF2=c-cxK0}RnEo5@eo@G7W*o+ouG0h2L8pqlsxTjT9EnpIb?7|5Zh^1 zI4^uhW3|}$3^JEcy4+ff&S}c;10fv)6yy|1m}=hTYlI9a?Qf!}<{Ir@7i$es=g4G4 z2#~)cIe@G~Euk+a;1YKW%UNySpG1Pn(uKhpUYUt<;Qcy9TZC~IuE5USU$!` zxg66qQ;N!HE47^V_Vfxy%EtFhrJw758L`08wrP=zJHA(Fy>Hx_nE|7D1e$bgN6W0z zO=IDvHAge-uO2YZkYt*U)0ySpc5OIOTkMtrUK%EVKV z_qTk_q6vxXQFrP17R;IrrkH?t`OTu1_=;WckPvh*k3{~-lq&` zbxy@p{!*VpkW{hX;ix5`L`fk_Y4@56q|u+lJM3uF!oKC8eYrq`SLYy6d-j?|XlIjB&;} z`_#RcU2Cs3*PK9-ocu#wWjhBAjfB1QVxvc-y;NA#kCj~>AUyx=8zwPvSc-<-<1Q_Y zR7O*Dd$;U--YHi47kGAR-)>_6+;SH43&AICWck`DgF=`S2tdRt(U~pwc6t3I=wZQ*%yvb{;!7J0~tj zr}7Ey(|OCnMyj04iP#O-!FaD>g_4I}`&DujFXQI@Oto71KSV{e&>|Zi!V}w~@aW>9 zF8mn>4K4Ui`zQ4_bkRJYA?#l*TDSFI8oh`12y||?_p8FN9a5AlZyGk3IvMf##a=G& z^l%#OZRU}+U;DOhI9~FUu+%H=-|-;5!R#_fdf%*bk2y8Pf~oe;6|oj!hG!7j6)6co zDUt~|5&)E9fnAIoLQz9w%aW&QLP!hrEgmt!aiy&tuT8>yWg)2iU6ILy?7IvB!r#2u zCHML8-08KNelv>NLB2ag{+|QGa~GE6eo>_YeA9zCX1Cy}!|`HHMvMH&2Ud_?4lCK&s0~Rqh!D@#zkimn8S6{SGPMZ0LW{Lyg29Id_jsE2E zk`gPArfk{;B2F6tHKs#A`aAzBYU#6ZE=$_!?;mt!yjQoYXaT15wk!uv;HNlrvM~h( zl6nLLA;<3cW+GBF3fS@qrH?(M1pOr9KRI|iY4-0SQpV<~8~R?ahr@SY2s9k0sG@~2 z{{H|3$Eh#OA1qi;IGwjEL_%~*74$!2Du${y$yv?xlTuq3w|RbkbzavKu}t?dzYfVN z@Z%sq(U>0w%y2w5O?+k1EnOTc5L3|#H11v2 z;CnP;@4l#aN@L}0OL?B)26Wsw8a(b!b)9f<`n28^_3n8?w7=*d zb=&Bup;5g{Yww{c%5{ocBd(edS}IB;3Kx5euSBjVx4C(~{8!PKVGFX=!<%290W`AN zta1f#)qNko<^{;+M^3_Sc60%5S|GK{iyl6Iiv}PkI6zPV#Qxo5I#ftZHok0D9a1tw zdaa1<9UU!Zm96?^y2E6S$H5O{*c~0^03!=j%6%Gs9yI(sViKIW3W)L*F6^x_;&O|r+T&_wDoASvQ={#Ea%HDwu zcIC9Vc&vH&(5?l|;9>8h1aLDx6brZ3y;nMH=%CAUUS|=+FlbRyQf$$l?p%GW?R0Q9 zX^?9~^%(rGp4BU7WA?bYyFX?|B^X0^^5n_Z_^l2WDh2FG=4p5r{a5W70v(E!0;oB8=B|?V#GnGkK*+(gyw(x*O_%+O#$RO(;P(}M^OU1vd{W+}-UIsjFRSL&O zfLr^Z;X?oJ`fN^R`FgK>Fd{K=`{V>tQNdNb10d`Y7kW;oPc&DVytZok%!%*KC+J`T zHW5zU)c}!jJfUIT!qVGg$h=%sz0mr|xU8UV3N_v>xE);04IXn1X*@d={B9Hi^s_0; zhV?j8l+#T;brcl7EhA)<(eq-%5*qGnH6HZW0JJn&=AWei;eoQs=g7POafax7ub8mu zr%EzI3;j=rO4i{EYxnfx7Wr);VeX%|1szxX=$OAzMviu4_9y))n8q(M7IzJ;$8AWh zA$Tui@f^1OpCd1Ei^7pK8UGT>yLo&1KVuy2j#hVj0(_&}#h$`2h5+NaWmU>?UAX0G zp9aTG(|;SIV0IPHfDn)8>`wX#zhAH1bZYK7O~>rxCcJtNi`XLZw&B0i{!r$bp7*s} zC(!Trn;iz`3I(h7yU?o&W<0v-gWoMX5T2hzm^a`$%tJnwTW_kVN4NSw;FGud6=nxp zTbklOe}gMP=hX|R#Qv1Yo)7#>!8Y9ByPbO$m58c5gtJV|< zmbf8>>jewdi>XCgoyGq1qB6U^HB+z zuV2~7$jDeJV@9`yg%I3MJdW48VUO~UGOM=ny&V+T1syQ*if-sCPd+oimCyO~JFo_V zRKR18zP&FSing{)aLr>rC@9Z_Nz6IDTs4iKbxf~tpO*QN)4jz6jkL1`do#h*ZAa00 z=U0tE>$>4!nIP)Hq|mEbm-orriz{d3H?$9j=&3Yq*8UVr!3|9g>L>NPRetEiWN<~I zIIt92I?_$(Im%_$Iy>K#E7RGy)d}eqURrv!yY1(y1@vp*a6Z`S&qW?Y4qo!2bvgS zxGyH4z!J1SyoSZ>|4YlM!D8#b?XhMTo)__KUk$o~9LRc#_Y6!M^AD2E(2FWo#CSe| ziyiB{x)$R`J?qR5;{EIxzvspSXe>pgOrtBg}$(Tf~3jyy6^2L? z)p(J^s6^a22!NdBjSo^kCaMgfl!5g0lI0PhJfcz?4d#UC6 zir=%|IQgk#vdnP#?0!+Tr8|VX3@0E%!gyjj&3w*}%%D*vogq3|VLJLAG%gC_xY|bt zTZ8LaYpXBH@X%0N0{s|rB8N4x>)DR4e5NQBe62@Zvq+wp?Z_``S|*>)&? zDToCEllCMkWltR=X!EDgcWxq{vM-bi z%w@Tl%zsgz{@q{P-;+<;f;~Cv*|>_@&*g-Rp2@SPa5E=kOy1#DI@ID*qG|H?2&8bo zywj+~A(MWx79HCe|CN{|XTLcR`xuxgRGB^< ztf6@N=n1lyLcc!W4+dFB%3NV;n8bN3o~p*gE_N&J&(@FN<$k9-NB-AY1Y~VMra(u) ziZ>e{F(HSQFqkueWE(ETs2u`Y=ojX6RYfgjUz(Jj!dY6+!py)s=ItJQf&8aU)yEtY}<+(U9UT*bO1+0LuFTYPznLMr?3c)xz zydhy~M3ghf^E#1>yz(r&)*s3&l%@UcRVg$gi>rJtE$p`qVQM%ciiaRkKkxUejw*BSm;Ivd+>BYK4L} zvb9O_On^NLD)iKot~{9==lu8X;wIpn4mXY9d%!x)IUH4e+ zR%f@5T+E-#2bkQ7-IAf8`H9n4>G~zpS~)Ng>Y_u>$F`Ec!?(Cma0{@n6cD83NjWw2FwKNGs<#uUO*1J!zYQu`lmkMu*c47=md}{7uD7# zehePy=wJXfzqx_lUd2A!%(L-}{7C~-&<=vG+68A>{I*APcavmU*;Sa@MY~fu!c4lM zlc%yN(POg?{Ish}EA!C>)GAtSilimMeU z*$S=GOH!D&g9^mb!5U8rrC55b2qo4-fCYPrhMpq*9wE1m5efQv`3^42Bi}g&RuG8F z{xukG?G*we07i`LngTnMQ!SxeCRs2v2UrfM`0rsbn{nmOwxg*a`p0U}iF$l{GMH(- z*mw!{HKuOISDUG;`MEy@t*jWd-H$|oV%Miz(!Y^?(Ntvylf_n}i)H3`f^AdxOHX0A z!6FLD1g?;gcEv|d-h2I#i;#4B58N3%1=RdSqRy8Hg_1}wV46(LL1PE#_Do*28mRy| z|5#Y9+7(SN2-;tH+89txNqSa?fX|eLSMZ?*zVv;XX9p_>Zsyi$6I_$-Ko6ow?nCGw zZztP{mzo7U2UBmpW({KYzThS*wTnnpjhE!w0_`TXoFpFZFPZ{N?h3CU z@SlqWYHZET0*{s%_OnBTJFa;<^nlMMsM5W9R}uXOU9;t6XzS-XbkgCTlIw3PR+N`!lW-J%14-6R zjW2j+9`Z9+MX~pVx+Xb=ZojFkK!i4vhj;7UTAAZ`$hB~l(-l-?{>lHmf_~2RQ$E*1 z!;El~h4UI`3<uL8vsLM?i7$m_Rd>mNT>4sm~+|u0aFhJmQ6>8dq$yFor+zJW)l9t`jbzO zm#=P`N{kP9lA3jpug$)kV}*|o@}wv6FsJd9snSJlifo;%3kEDJ7>h@V*x#9lrjIUW z0LYytcce?y6vSD44}<01OV;hbgV&@tP&q%&x;78@7K||SBs!!iKiNRI#5_cS1EK$m z@WpdKpM?{NWkwQx_6>yjCj9UvG85o~P?*uo#h1C!VqZ@>_j+ep+ye!|uD?Tg8y4bhjNV*Q7dp;6LCfUspt$uWs zVYi_sOh<07Guocxzx7Z!k3I$9j^B3?^?S~P0_UPJ+JjUJ{nd4fsmJq?eO3>pgCG1r ze3aRA z5@VvG1o@^)KL!es>UR81Z;-HX;PmW%C!F7fA#kqnbk6GA)Rs-i>zU-C1!=tKM&cGq zJi%`Wa$5b?Pqc%6b%!P_!4Q)*wQ5tXpa)WN0D#;7*xNXk*W1F=#mMdvR64+#|>Yf?bb9NXjA|9?nogw zw}!>nJhq>X**wd(cRK_>uOoQPKmEissk|%unu3uh=by*;E03c=!AD5jYxw^mf0p2V zj1Az13=NK0)3z+XzOFxg@(2@3fNlx?c6Yx?X164By14rE**VH{cgf{&Y5^MwYtU)d1b*2rFzf4!qRW1d|dRl zeyxhbF(l~BDyS3l+y98cfxohKGsA*q8SyK6gyY8qn~`9+_X|1Myy+nO6996v3|CPC z*JRTf&9%*;=N^d`?r$Oi#4M-eV`U?NS~1k0(ty)y!w#IU=ixk?n*JBKKX=*$IYL*5 zkozZ9Oyg~-RyvPv0NP6~2NF8;VSBMMt&oALe3 z_u+W@)y=8cajsuDjp_slNTDx{-_dc=z{50~q_!Jsa$EIbQXW;NCzX@%7P23&!Dt3Jso5CVX+~p;rPl@wO z8v9fQ%G)`EaF)@r;Pi_R*0caCp)KOB%w`P!>v~uS9`+g)U$8(-5K-bd4k9fofky1Y z^lbCaC?pijY4SSJCNDyahBHK9AkY#7ASvJe^}c-zGFlw#h+w78NBU%*a4+%E*c>}o z;SjGM5UzNGmpLcQ>gPBtu;A|~Lb?zHdKVYDdGw}shZLd1(m?0VTJ>pu=P6-ucXVPS zLFF*^7UOcGUa)`+bvZQMw{9K2GY=lVKNSD9mMW-2`B0c_;*2^r#gu5U><|MF+vqwy zcEx5-H1LkQDxn`>qeHLcTR)5A-Sr!pJ6CLT zv$L@Pxe3r%WW+Yxn<`5~k_K|%`l9*e_90_W)kE{Zm43)l<@%?gp&@k+UIz<#;7h;~ z62-kLNfa&6{QSCxgMy-ClZiexIME$x-^!!MpTtuf#92i->h4aeu5bQ&!6EzwTq0kb zdB&zB+<&7F=jwZHeK}y%&DD>%G$cCqM2-z_s9GrHtUvu4V4PQ?hx$e=^|-Cnt?f-H z#oKEo)OXOkcUfCoUlio!Th=QDDuGa*L`RxkLc=T^*aT#NHF4B$MI#zs%f->j7=Pkp-A|tVSoIiei)7sX?W&o2@Rz`!BPCrLu zGIZZoq@TF{Sq5v4cq0UZ0hEWZyF0u^mEd|(y>^@J1 zdXPyF8JAJpBH&q8?m;iiApPJK%H7jE4h;HIPwbKUE4R+bs3@|L!zX^}?9lW661|>> z?3J^dd#~G{9ha9=`j3s_I!5*V=ITY{HNRrSlJg7GDa2-c>@P?vQMd*Y!w=`sq6iQP zgnF2iGs|suQNrI{!y^1-{F3Fy+zGTUsA0N$c-Ui}t+y0;9MBBWo`POQVu^3SI z(`N;w9Qr*mG}_hU_(p%_?m(yS@XFW<1$o$h_n7R?hq^|6EH^=w&tk!pVbyqLyj~+m zD4Up=c#5CH#LCX@>H&xL{CRB_zda7#ODrkP*YR9S`6B6^y`7_P_l4U5S@d+pcV+Vx6)V2%EE+*cPbV0`nF$uaf|M!D$) zKkcwn#D#55u2&!wG!pVk@2~StDuyVbV57eLV4w@4-nF{T>nP+j{zV*ek_$L5PS2_E zpdQK7fXMzP#>VUC;XJ3L3()Lgx}8UtWi=eEr>7T-=;f1>%1ovLghk(3`WCm%XI!H> z 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 LD@zqinE3x6oisem delta 246513 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

h_+KT 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 DeR^X$ diff --git a/icons/mob/species/grey/uniform.dmi b/icons/mob/species/grey/uniform.dmi index 1636097f72e57e42751cad3d98459b37b11a087d..c956187ffa10889df17b2ea8cc7b364d40316a35 100644 GIT binary patch delta 23680 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 afNO$?7~)33zqVZf^m(GHtx~CE8T3E?i@`Pk delta 22444 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 YNwP>;ttPP(ICenio`GhC`jfE#0El*#tpET3 diff --git a/icons/mob/species/vox/uniform.dmi b/icons/mob/species/vox/uniform.dmi index 6f050370f7cde8d0b2277a6c0689abadbce1a42d..3eb4fec6f6f1e6e7b6c81ebed244ce9288f34f53 100644 GIT binary patch delta 255191 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⋼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≪kJf+K|?7zLoW8~*f@?!-#y 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 SD?c6Y{vRkGm)ZKCQvR#O{G*b!H8eYVIZ;cCu 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

K$`hq(7oc?x#N5@&NndY-q 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

sj)+!3lSnJgl?{SqBDY#ji;; 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

}jv(RJU12w-gk;o)0JUd37d!v- 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 WJ?M>FT&;(I59KEsk1OTPg8v6?z-^fT diff --git a/icons/mob/uniform.dmi b/icons/mob/uniform.dmi index b67023a99357383f448812f43932563607529572..1e185efd9c5f712653b017a6248ead69986936ce 100644 GIT binary patch delta 25636 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 NHI>K8Ws0W3{|iBx#T5Vm delta 24367 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


" usr << browse(output, "window=watchwin;size=900x500") -/client/proc/check_watchlist(target_ckey) - if(!check_rights(R_ADMIN,0)) - return +/proc/check_watchlist(target_ckey) var/target_sql_ckey = sanitizeSQL(target_ckey) var/DBQuery/query_watch = dbcon.NewQuery("SELECT reason FROM [format_table_name("watch")] WHERE ckey = '[target_sql_ckey]'") if(!query_watch.Execute()) From a4e6dba7384fe08168684d757cfd0e67961b4aac Mon Sep 17 00:00:00 2001 From: Kyep Date: Mon, 16 Dec 2019 15:55:04 -0800 Subject: [PATCH 045/238] fixes connection logging to DB not working --- SQL/paradise_schema.sql | 14 ++++++++++++++ SQL/paradise_schema_prefixed.sql | 13 +++++++++++++ SQL/updates/9-10.sql | 10 ++++++++++ code/modules/client/client procs.dm | 5 ++--- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql index 445de8b4d08..bfab9ba76ce 100644 --- a/SQL/paradise_schema.sql +++ b/SQL/paradise_schema.sql @@ -579,4 +579,18 @@ CREATE TABLE `playtime_history` ( `time_living` SMALLINT NOT NULL, `time_ghost` SMALLINT NOT NULL, PRIMARY KEY (`ckey`, `date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + + +-- +-- Table structure for table `connection_log` +-- +DROP TABLE IF EXISTS `connection_log`; +CREATE TABLE `connection_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `ckey` varchar(32) NOT NULL, + `ip` varchar(32) NOT NULL, + `computerid` varchar(32) NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; \ No newline at end of file diff --git a/SQL/paradise_schema_prefixed.sql b/SQL/paradise_schema_prefixed.sql index f70593fc1ac..a83c93b7c38 100644 --- a/SQL/paradise_schema_prefixed.sql +++ b/SQL/paradise_schema_prefixed.sql @@ -577,4 +577,17 @@ CREATE TABLE `SS13_playtime_history` ( `time_living` SMALLINT NOT NULL, `time_ghost` SMALLINT NOT NULL, PRIMARY KEY (`ckey`, `date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Table structure for table `SS13_connection_log` +-- +DROP TABLE IF EXISTS `SS13_connection_log`; +CREATE TABLE `SS13_connection_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `ckey` varchar(32) NOT NULL, + `ip` varchar(32) NOT NULL, + `computerid` varchar(32) NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; \ No newline at end of file diff --git a/SQL/updates/9-10.sql b/SQL/updates/9-10.sql index f82e7c56830..aded1301b82 100644 --- a/SQL/updates/9-10.sql +++ b/SQL/updates/9-10.sql @@ -9,3 +9,13 @@ CREATE TABLE `playtime_history` ( PRIMARY KEY (`ckey`, `date`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +# Add the 'connection_log' table, which is used to log all connections to the server +DROP TABLE IF EXISTS `connection_log`; +CREATE TABLE `connection_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `datetime` datetime NOT NULL, + `ckey` varchar(32) NOT NULL, + `ip` varchar(32) NOT NULL, + `computerid` varchar(32) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 644ac6aa123..082ce969695 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -560,9 +560,8 @@ log_game("SQL ERROR during log_client_to_db (insert). Error : \[[err]\]\n") message_admins("SQL ERROR during log_client_to_db (insert). Error : \[[err]\]\n") - //Logging player access - var/serverip = "[world.internet_address]:[world.port]" - var/DBQuery/query_accesslog = dbcon.NewQuery("INSERT INTO `[format_table_name("connection_log")]`(`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]','[ckey]','[sql_ip]','[sql_computerid]');") + // Log player connections to DB + var/DBQuery/query_accesslog = dbcon.NewQuery("INSERT INTO `[format_table_name("connection_log")]`(`datetime`,`ckey`,`ip`,`computerid`) VALUES(Now(),'[ckey]','[sql_ip]','[sql_computerid]');") query_accesslog.Execute() /client/proc/check_ip_intel() From 91a63e97a795acad217c3bc5019d873502f9cad6 Mon Sep 17 00:00:00 2001 From: Kyep Date: Mon, 16 Dec 2019 17:20:07 -0800 Subject: [PATCH 046/238] Add: notes are now recorded with player's playtime, for comparison --- SQL/paradise_schema.sql | 1 + SQL/paradise_schema_prefixed.sql | 1 + SQL/updates/9-10.sql | 3 ++ code/modules/admin/sql_notes.dm | 50 ++++++++++++++++++++------------ 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql index bfab9ba76ce..01f83ba0194 100644 --- a/SQL/paradise_schema.sql +++ b/SQL/paradise_schema.sql @@ -505,6 +505,7 @@ CREATE TABLE `notes` ( `last_editor` varchar(32), `edits` text, `server` varchar(50) NOT NULL, + `crew_playtime` mediumint(8) UNSIGNED DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/SQL/paradise_schema_prefixed.sql b/SQL/paradise_schema_prefixed.sql index a83c93b7c38..9801c54d6f9 100644 --- a/SQL/paradise_schema_prefixed.sql +++ b/SQL/paradise_schema_prefixed.sql @@ -504,6 +504,7 @@ CREATE TABLE `SS13_notes` ( `last_editor` varchar(32), `edits` text, `server` varchar(50) NOT NULL, + `crew_playtime` mediumint(8) UNSIGNED DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/SQL/updates/9-10.sql b/SQL/updates/9-10.sql index aded1301b82..942aca1b541 100644 --- a/SQL/updates/9-10.sql +++ b/SQL/updates/9-10.sql @@ -19,3 +19,6 @@ CREATE TABLE `connection_log` ( `computerid` varchar(32) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +# Add the 'crew_playtime' field to the 'notes' table, which gives admins some idea of how many hours have passed for a player since they got a note +ALTER TABLE `notes` ADD `crew_playtime` mediumint(8) UNSIGNED DEFAULT '0' AFTER `server`; diff --git a/code/modules/admin/sql_notes.dm b/code/modules/admin/sql_notes.dm index 18112f584c2..a6ac31a4114 100644 --- a/code/modules/admin/sql_notes.dm +++ b/code/modules/admin/sql_notes.dm @@ -4,22 +4,30 @@ if(!dbcon.IsConnected()) to_chat(usr, "Failed to establish database connection.") return + if(!target_ckey) var/new_ckey = ckey(clean_input("Who would you like to add a note for?","Enter a ckey",null)) if(!new_ckey) return - new_ckey = ckey(new_ckey) - var/DBQuery/query_find_ckey = dbcon.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ckey = '[new_ckey]'") - if(!query_find_ckey.Execute()) - var/err = query_find_ckey.ErrorMsg() - log_game("SQL ERROR obtaining ckey from player table. Error : \[[err]\]\n") - return - if(!query_find_ckey.NextRow()) - to_chat(usr, "[new_ckey] has not been seen before, you can only add notes to known players.") - return - else - target_ckey = new_ckey - var/target_sql_ckey = ckey(target_ckey) + target_ckey = ckey(new_ckey) + else + target_ckey = ckey(target_ckey) + + var/DBQuery/query_find_ckey = dbcon.NewQuery("SELECT ckey, exp FROM [format_table_name("player")] WHERE ckey = '[target_ckey]'") + if(!query_find_ckey.Execute()) + var/err = query_find_ckey.ErrorMsg() + log_game("SQL ERROR obtaining ckey from player table. Error : \[[err]\]\n") + return + if(!query_find_ckey.NextRow()) + to_chat(usr, "[target_ckey] has not been seen before, you can only add notes to known players.") + return + + var/exp_data = query_find_ckey.item[2] + var/crew_number = 0 + if(exp_data) + var/list/play_records = params2list(exp_data) + crew_number = play_records[EXP_TYPE_CREW] + if(!notetext) notetext = input(usr,"Write your note","Add Note") as message|null if(!notetext) @@ -38,7 +46,7 @@ if(config && config.server_name) server = config.server_name server = sanitizeSQL(server) - var/DBQuery/query_noteadd = dbcon.NewQuery("INSERT INTO [format_table_name("notes")] (ckey, timestamp, notetext, adminckey, server) VALUES ('[target_sql_ckey]', '[timestamp]', '[notetext]', '[admin_sql_ckey]', '[server]')") + var/DBQuery/query_noteadd = dbcon.NewQuery("INSERT INTO [format_table_name("notes")] (ckey, timestamp, notetext, adminckey, server, crew_playtime) VALUES ('[target_ckey]', '[timestamp]', '[notetext]', '[admin_sql_ckey]', '[server]', '[crew_number]')") if(!query_noteadd.Execute()) var/err = query_noteadd.ErrorMsg() log_game("SQL ERROR adding new note to table. Error : \[[err]\]\n") @@ -131,10 +139,10 @@ output = navbar if(target_ckey) var/target_sql_ckey = ckey(target_ckey) - var/DBQuery/query_get_notes = dbcon.NewQuery("SELECT id, timestamp, notetext, adminckey, last_editor, server FROM [format_table_name("notes")] WHERE ckey = '[target_sql_ckey]' ORDER BY timestamp") + var/DBQuery/query_get_notes = dbcon.NewQuery("SELECT id, timestamp, notetext, adminckey, last_editor, server, crew_playtime FROM [format_table_name("notes")] WHERE ckey = '[target_sql_ckey]' ORDER BY timestamp") if(!query_get_notes.Execute()) var/err = query_get_notes.ErrorMsg() - log_game("SQL ERROR obtaining ckey, notetext, adminckey, last_editor, server from notes table. Error : \[[err]\]\n") + log_game("SQL ERROR obtaining ckey, notetext, adminckey, last_editor, server, crew_playtime from notes table. Error : \[[err]\]\n") return output += "

Notes of [target_ckey]

" if(!linkless) @@ -147,7 +155,13 @@ var/adminckey = query_get_notes.item[4] var/last_editor = query_get_notes.item[5] var/server = query_get_notes.item[6] - output += "[timestamp] | [server] | [adminckey]" + var/mins = text2num(query_get_notes.item[7]) + output += "[timestamp] | [server] | [adminckey]" + if(mins) + var/playstring = get_exp_format(mins) + output += " | [playstring] as Crew" + output += "" + if(!linkless) output += " \[Remove Note\] \[Edit Note\]" if(last_editor) @@ -182,10 +196,10 @@ /proc/show_player_info_irc(var/key as text) var/target_sql_ckey = ckey(key) - var/DBQuery/query_get_notes = dbcon.NewQuery("SELECT timestamp, notetext, adminckey, server FROM [format_table_name("notes")] WHERE ckey = '[target_sql_ckey]' ORDER BY timestamp") + var/DBQuery/query_get_notes = dbcon.NewQuery("SELECT timestamp, notetext, adminckey, server, crew_playtime FROM [format_table_name("notes")] WHERE ckey = '[target_sql_ckey]' ORDER BY timestamp") if(!query_get_notes.Execute()) var/err = query_get_notes.ErrorMsg() - log_game("SQL ERROR obtaining timestamp, notetext, adminckey, server from notes table. Error : \[[err]\]\n") + log_game("SQL ERROR obtaining timestamp, notetext, adminckey, server, crew_playtime from notes table. Error : \[[err]\]\n") return var/output = " Info on [key]%0D%0A" while(query_get_notes.NextRow()) From 3c00df376adfae695c9a0cd3fc77b095d29f226b Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Tue, 17 Dec 2019 17:11:40 +0100 Subject: [PATCH 047/238] inventory slot tweak Clicking on an inventory slot now uses the item there first then tries to equip --- code/_onclick/hud/screen_objects.dm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index c3c9b6f7e65..c0f5cfd5f3b 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -362,7 +362,7 @@ object_overlays += item_overlay add_overlay(object_overlays) -/obj/screen/inventory/Click() +/obj/screen/inventory/Click(location, control, params) // At this point in client Click() code we have passed the 1/10 sec check and little else // We don't even know if it's a middle click if(world.time <= usr.next_move) @@ -371,6 +371,12 @@ return 1 if(istype(usr.loc,/obj/mecha)) // stops inventory actions in a mech return 1 + + if(hud?.mymob && slot_id) + var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id) + if(inv_item) + return inv_item.Click(location, control, params) + if(usr.attack_ui(slot_id)) usr.update_inv_l_hand(0) usr.update_inv_r_hand(0) From 953f7847d96ab0c83913fe32af47f8933adae510 Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Tue, 17 Dec 2019 17:52:35 +0100 Subject: [PATCH 048/238] Airlock note QOL --- code/game/machinery/doors/airlock.dm | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 610bbb73b06..9b100909226 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -983,6 +983,15 @@ About the new airlock wires panel: if(note) to_chat(user, "There's already something pinned to this airlock! Use wirecutters or your hands to remove it.") return + + if(!user.canUnEquip(C)) + to_chat(user, "For some reason, you can't attach [C]!") + return + + // QOL for cultists + if(!do_after_once(user, 10, target = src)) + return + if(!user.unEquip(C)) to_chat(user, "For some reason, you can't attach [C]!") return @@ -1349,7 +1358,8 @@ About the new airlock wires panel: else user.visible_message("[user] cuts down [note] from [src].", "You remove [note] from [src].") playsound(src, 'sound/items/wirecutter.ogg', 50, 1) - note.forceMove(get_turf(user)) + if(!user.put_in_active_hand(note) && !user.put_in_inactive_hand(note)) + note.forceMove(get_turf(user)) note = null update_icon() return TRUE From 2d44b65519bf02fe6676fb641a41753b48e1f9de Mon Sep 17 00:00:00 2001 From: datlo Date: Tue, 17 Dec 2019 21:05:08 +0100 Subject: [PATCH 049/238] Clowns get clownish by default --- code/game/jobs/job/support.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm index e4bb22b28af..4a8f7d65980 100644 --- a/code/game/jobs/job/support.dm +++ b/code/game/jobs/job/support.dm @@ -294,6 +294,7 @@ genemutcheck(H, COMICBLOCK, null, MUTCHK_FORCED) H.dna.default_blocks.Add(COMICBLOCK) H.check_mutations = TRUE + H.add_language("Clownish") //action given to antag clowns /datum/action/innate/toggle_clumsy From c7c5b5a89920e25b2c48e8587f155de1a2b30a65 Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Wed, 18 Dec 2019 23:04:18 +0100 Subject: [PATCH 050/238] Cult papers can't be placed on airlocks --- code/game/machinery/doors/airlock.dm | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 9b100909226..fb07debfe7b 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -979,19 +979,10 @@ About the new airlock wires panel: else if(istype(C, /obj/item/pai_cable)) // -- TLE var/obj/item/pai_cable/cable = C cable.plugin(src, user) - else if(istype(C, /obj/item/paper) || istype(C, /obj/item/photo)) + else if((istype(C, /obj/item/paper) && !istype(C, /obj/item/paper/talisman)) || istype(C, /obj/item/photo)) if(note) to_chat(user, "There's already something pinned to this airlock! Use wirecutters or your hands to remove it.") return - - if(!user.canUnEquip(C)) - to_chat(user, "For some reason, you can't attach [C]!") - return - - // QOL for cultists - if(!do_after_once(user, 10, target = src)) - return - if(!user.unEquip(C)) to_chat(user, "For some reason, you can't attach [C]!") return From 99204689d59fc4962a480c41fc54488e148f49f4 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Thu, 19 Dec 2019 14:01:02 -0500 Subject: [PATCH 051/238] Removes armor --- code/modules/clothing/under/miscellaneous.dm | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 6ab67a2371c..9afde7a0dde 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -103,7 +103,6 @@ item_color = "navy_gold" displays_id = 0 flags_size = ONESIZEFITSALL - armor = list("melee" = 60, "bullet" = 60, "laser" = 60,"energy" = 60, "bomb" = 60, "bio" = 100, "rad" = 30, "fire" = 60, "acid" = 100) /obj/item/clothing/under/rank/centcom/captain desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." @@ -113,7 +112,6 @@ item_color = "navy_gold" displays_id = 0 flags_size = ONESIZEFITSALL - armor = list("melee" = 60, "bullet" = 60, "laser" = 60,"energy" = 60, "bomb" = 60, "bio" = 100, "rad" = 30, "fire" = 60, "acid" = 100) /obj/item/clothing/under/rank/centcom/blueshield desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant\" and bears \"N.S.S. Cyberiad\" on the left shoulder." From 37905435c6c75fe4f8fdd8d0852ec5b7f2173ddd Mon Sep 17 00:00:00 2001 From: Superhats Date: Fri, 20 Dec 2019 19:06:10 +0100 Subject: [PATCH 052/238] Dusty Business --- code/modules/mob/living/carbon/human/species/plasmaman.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/species/plasmaman.dm b/code/modules/mob/living/carbon/human/species/plasmaman.dm index d925e2ef1d9..c30c8176fdc 100644 --- a/code/modules/mob/living/carbon/human/species/plasmaman.dm +++ b/code/modules/mob/living/carbon/human/species/plasmaman.dm @@ -165,7 +165,7 @@ ..() /datum/species/plasmaman/handle_reagents(mob/living/carbon/human/H, datum/reagent/R) - if(R.id == "plasma") + if(R.id == "plasma" || R.id == "plasma_dust") H.adjustBruteLoss(-0.5*REAGENTS_EFFECT_MULTIPLIER) H.adjustFireLoss(-0.5*REAGENTS_EFFECT_MULTIPLIER) H.adjustPlasma(20) From ad8690e8eeb45b77ac3a5def084f317871c5c5c1 Mon Sep 17 00:00:00 2001 From: Arvenius169 <56715097+Arvenius169@users.noreply.github.com> Date: Fri, 20 Dec 2019 15:12:47 -0500 Subject: [PATCH 053/238] Update cyberiad.dmm --- _maps/map_files/cyberiad/cyberiad.dmm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm index 8de75d7aaf1..14ab088a4f0 100644 --- a/_maps/map_files/cyberiad/cyberiad.dmm +++ b/_maps/map_files/cyberiad/cyberiad.dmm @@ -27893,7 +27893,6 @@ /area/chapel/main) "aYA" = ( /obj/structure/table, -/obj/item/lighter/zippo/black, /turf/simulated/floor/plasteel{ icon_state = "vault"; dir = 5 @@ -29916,6 +29915,7 @@ network = list("SS13") }, /obj/structure/table/wood, +/obj/item/lighter/zippo/black, /turf/simulated/floor/carpet/black, /area/chapel/office) "bcx" = ( From d74d13c382f0370b2da02e0e0d5c6f75c817960a Mon Sep 17 00:00:00 2001 From: SteelSlayer Date: Sat, 21 Dec 2019 15:24:42 -0600 Subject: [PATCH 054/238] Adds a confirmation message to guardian creation items; Allows users to cancel using them period --- code/game/gamemodes/miniantags/guardian/guardian.dm | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/code/game/gamemodes/miniantags/guardian/guardian.dm b/code/game/gamemodes/miniantags/guardian/guardian.dm index 17d7187f439..4f25f101152 100644 --- a/code/game/gamemodes/miniantags/guardian/guardian.dm +++ b/code/game/gamemodes/miniantags/guardian/guardian.dm @@ -271,6 +271,7 @@ var/used = FALSE var/theme = "magic" var/mob_name = "Guardian Spirit" + var/confirmation_message = "The cards are still unused. Do you wish to use them?" var/use_message = "You shuffle the deck..." var/used_message = "All the cards seem to be blank now." var/failure_message = "..And draw a card! It's...blank? Maybe you should try again later." @@ -295,8 +296,13 @@ if(used == TRUE) to_chat(user, "[used_message]") return - used = TRUE - to_chat(user, "[use_message]") + var/choice = alert(user, "[confirmation_message]",, "Yes", "No") + if(choice == "Yes") + used = TRUE + to_chat(user, "[use_message]") + else if(choice == "No") + to_chat(user, "You decide against using the [name].") + return var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as the [mob_name] of [user.real_name]?", ROLE_GUARDIAN, 0, 100) var/mob/dead/observer/theghost = null @@ -384,6 +390,7 @@ icon_state = "combat_hypo" theme = "tech" mob_name = "Holoparasite" + confirmation_message = "The injector still contains holoparasites. Do you wish to use it?" use_message = "You start to power on the injector..." used_message = "The injector has already been used." failure_message = "...ERROR. BOOT SEQUENCE ABORTED. AI FAILED TO INTIALIZE. PLEASE CONTACT SUPPORT OR TRY AGAIN LATER." @@ -424,6 +431,7 @@ theme = "bio" mob_name = "Scarab Swarm" use_message = "The eggs begin to twitch..." + confirmation_message = "These eggs are still dormant. Do you wish to activate them?" used_message = "The cluster already hatched." failure_message = "...but soon settles again. Guess they weren't ready to hatch after all." color_list = list("Rose" = "#F62C6B", From b317b49cb0281b1f174a37ab27d15d2e11d36642 Mon Sep 17 00:00:00 2001 From: SteelSlayer Date: Sun, 22 Dec 2019 13:58:26 -0600 Subject: [PATCH 055/238] ding dong --- code/game/gamemodes/miniantags/guardian/guardian.dm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/code/game/gamemodes/miniantags/guardian/guardian.dm b/code/game/gamemodes/miniantags/guardian/guardian.dm index 4f25f101152..387cd03aceb 100644 --- a/code/game/gamemodes/miniantags/guardian/guardian.dm +++ b/code/game/gamemodes/miniantags/guardian/guardian.dm @@ -297,12 +297,11 @@ to_chat(user, "[used_message]") return var/choice = alert(user, "[confirmation_message]",, "Yes", "No") - if(choice == "Yes") - used = TRUE - to_chat(user, "[use_message]") - else if(choice == "No") + if(choice == "No") to_chat(user, "You decide against using the [name].") return + used = TRUE + to_chat(user, "[use_message]") var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as the [mob_name] of [user.real_name]?", ROLE_GUARDIAN, 0, 100) var/mob/dead/observer/theghost = null From 57412d810f053b344aaa6373d5ad65fd2002b584 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 22 Dec 2019 18:17:15 -0800 Subject: [PATCH 056/238] Text clarification --- code/modules/client/client procs.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 082ce969695..51ca4065801 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -646,7 +646,7 @@ return var/url = "[config.forum_link_url][tokenid]" if(fromban) - to_chat(src, {"Verify your forum account before you appeal: [url] (this link expires in a few minutes)"}) + to_chat(src, {"Copy/paste this URL into your browser to activate your forum account before you appeal: [url] (this link expires soon)"}) else to_chat(src, {"Now opening a window to verify your information with the forums. If the window does not load, please go to: [url]"}) src << link(url) From 5847cc3e616b527881523ae09a764425d058e398 Mon Sep 17 00:00:00 2001 From: Arvenius169 <56715097+Arvenius169@users.noreply.github.com> Date: Mon, 23 Dec 2019 11:35:05 -0500 Subject: [PATCH 057/238] Space Temp --- code/LINDA/LINDA_turf_tile.dm | 2 +- code/game/turfs/simulated/minerals.dm | 1 - code/modules/mob/living/carbon/human/life.dm | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/code/LINDA/LINDA_turf_tile.dm b/code/LINDA/LINDA_turf_tile.dm index a0a0b6b8784..b86ad2299ff 100644 --- a/code/LINDA/LINDA_turf_tile.dm +++ b/code/LINDA/LINDA_turf_tile.dm @@ -486,7 +486,7 @@ turf/simulated/proc/consider_superconductivity(starting) turf/simulated/proc/radiate_to_spess() //Radiate excess tile heat to space if(temperature > T0C) //Considering 0 degC as te break even point for radiation in and out - var/delta_temperature = (temperature_archived - 2.7) //hardcoded space temperature + var/delta_temperature = (temperature_archived - TCMB) //hardcoded space temperature if((heat_capacity > 0) && (abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)) var/heat = thermal_conductivity*delta_temperature* \ diff --git a/code/game/turfs/simulated/minerals.dm b/code/game/turfs/simulated/minerals.dm index c4f0e93dbd6..118132765a1 100644 --- a/code/game/turfs/simulated/minerals.dm +++ b/code/game/turfs/simulated/minerals.dm @@ -8,7 +8,6 @@ smooth = SMOOTH_MORE | SMOOTH_BORDER canSmoothWith = null baseturf = /turf/simulated/floor/plating/asteroid/airless - temperature = 2.7 opacity = 1 density = TRUE blocks_air = TRUE diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 2643d1f3102..9953904ac55 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -542,7 +542,7 @@ if(COLDRES in mutations) return 1 //Fully protected from the cold. - temperature = max(temperature, 2.7) //There is an occasional bug where the temperature is miscalculated in ares with a small amount of gas on them, so this is necessary to ensure that that bug does not affect this calculation. Space's temperature is 2.7K and most suits that are intended to protect against any cold, protect down to 2.0K. + temperature = max(temperature, TCMB) //There is an occasional bug where the temperature is miscalculated in ares with a small amount of gas on them, so this is necessary to ensure that that bug does not affect this calculation. Space's temperature is 2.7K and most suits that are intended to protect against any cold, protect down to 2.0K. var/thermal_protection_flags = get_cold_protection_flags(temperature) var/thermal_protection = 0.0 From 21523309df5f6e5ab1532f6f5ef9b875521eb559 Mon Sep 17 00:00:00 2001 From: Arvenius <56715097+Arvenius169@users.noreply.github.com> Date: Mon, 23 Dec 2019 11:43:19 -0500 Subject: [PATCH 058/238] Update life.dm --- code/modules/mob/living/carbon/human/life.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 9953904ac55..93e7ff54ec6 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -542,7 +542,7 @@ if(COLDRES in mutations) return 1 //Fully protected from the cold. - temperature = max(temperature, TCMB) //There is an occasional bug where the temperature is miscalculated in ares with a small amount of gas on them, so this is necessary to ensure that that bug does not affect this calculation. Space's temperature is 2.7K and most suits that are intended to protect against any cold, protect down to 2.0K. + temperature = max(temperature, TCMB) //There is an occasional bug where the temperature is miscalculated in areas with a small amount of gas on them, so this is necessary to ensure that that bug does not affect this calculation. Space's temperature is 2.7K and most suits that are intended to protect against any cold, protect down to 2.0K. var/thermal_protection_flags = get_cold_protection_flags(temperature) var/thermal_protection = 0.0 From fe85dd82935cee76589c29353b4dfd741bcdd911 Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Mon, 23 Dec 2019 22:39:09 +0100 Subject: [PATCH 059/238] Anti drop implants now don't break shit when EMPed --- .../modules/surgery/organs/augments_internal.dm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm index e9626a6b76d..fab2d0fd8a4 100644 --- a/code/modules/surgery/organs/augments_internal.dm +++ b/code/modules/surgery/organs/augments_internal.dm @@ -39,9 +39,9 @@ /obj/item/organ/internal/cyberimp/brain/anti_drop name = "Anti-drop implant" desc = "This cybernetic brain implant will allow you to force your hand muscles to contract, preventing item dropping. Twitch ear to toggle." - var/active = 0 - var/l_hand_ignore = 0 - var/r_hand_ignore = 0 + var/active = FALSE + var/l_hand_ignore = FALSE + var/r_hand_ignore = FALSE var/obj/item/l_hand_obj = null var/obj/item/r_hand_obj = null implant_color = "#DE7E00" @@ -56,17 +56,17 @@ r_hand_obj = owner.r_hand if(l_hand_obj) if(owner.l_hand.flags & NODROP) - l_hand_ignore = 1 + l_hand_ignore = TRUE else owner.l_hand.flags |= NODROP - l_hand_ignore = 0 + l_hand_ignore = FALSE if(r_hand_obj) if(owner.r_hand.flags & NODROP) - r_hand_ignore = 1 + r_hand_ignore = TRUE else owner.r_hand.flags |= NODROP - r_hand_ignore = 0 + r_hand_ignore = FALSE if(!l_hand_obj && !r_hand_obj) to_chat(owner, "You are not holding any items, your hands relax...") @@ -102,12 +102,15 @@ A = pick(oview(range)) L_item.throw_at(A, range, 2) to_chat(owner, "Your left arm spasms and throws the [L_item.name]!") + l_hand_obj = null if(R_item) A = pick(oview(range)) R_item.throw_at(A, range, 2) to_chat(owner, "Your right arm spasms and throws the [R_item.name]!") + r_hand_obj = null /obj/item/organ/internal/cyberimp/brain/anti_drop/proc/release_items() + active = FALSE if(!l_hand_ignore && l_hand_obj in owner.contents) l_hand_obj.flags ^= NODROP if(!r_hand_ignore && r_hand_obj in owner.contents) From 19ae2b1254592b181f66107fd4c028985d9ad9b4 Mon Sep 17 00:00:00 2001 From: farie82 Date: Tue, 24 Dec 2019 13:50:40 +0100 Subject: [PATCH 060/238] Revert "Move the handle_changeling proc call to the (new) life signal" --- code/__DEFINES/components.dm | 1 - code/game/gamemodes/changeling/evolution_menu.dm | 5 ----- code/modules/mob/living/carbon/life.dm | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index f9fba3ff4a4..df957eaa11e 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -155,7 +155,6 @@ // /mob/living/carbon signals #define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity)) -#define COMSIG_CARBON_LIFE "carbon_life" //from base of mob/living/carbon/Life() () // /mob/living/simple_animal/hostile signals #define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget" diff --git a/code/game/gamemodes/changeling/evolution_menu.dm b/code/game/gamemodes/changeling/evolution_menu.dm index a9d1c3bb480..b6a0213f82f 100644 --- a/code/game/gamemodes/changeling/evolution_menu.dm +++ b/code/game/gamemodes/changeling/evolution_menu.dm @@ -383,9 +383,6 @@ var/list/sting_paths var/mob/living/carbon/C = src //only carbons have dna now, so we have to typecaste mind.changeling.absorbed_dna |= C.dna.Clone() mind.changeling.trim_dna() - - RegisterSignal(C, COMSIG_CARBON_LIFE, CALLBACK(C, /mob/living/carbon/.proc/handle_changeling)) - return 1 //Used to dump the languages from the changeling datum into the actual mob. @@ -424,8 +421,6 @@ var/list/sting_paths if(hud_used) hud_used.lingstingdisplay.icon_state = null hud_used.lingstingdisplay.invisibility = 101 - hud_used.lingchemdisplay.invisibility = 101 - UnregisterSignal(src, COMSIG_CARBON_LIFE) /datum/changeling/proc/has_sting(datum/action/power) for(var/datum/action/P in purchasedpowers) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 6898b75b485..5352ba525f1 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -13,13 +13,13 @@ for(var/obj/item/organ/internal/O in internal_organs) O.on_life() + handle_changeling() handle_wetness(times_fired) // Increase germ_level regularly if(germ_level < GERM_LEVEL_AMBIENT && prob(30)) //if you're just standing there, you shouldn't get more germs beyond an ambient level germ_level++ - SEND_SIGNAL(src, COMSIG_CARBON_LIFE, seconds, times_fired) /////////////// // BREATHING // From 5878509902762fd15ea582e086e2c84d2120a9ad Mon Sep 17 00:00:00 2001 From: Arvenius169 <56715097+Arvenius169@users.noreply.github.com> Date: Wed, 25 Dec 2019 08:30:29 -0500 Subject: [PATCH 061/238] Update organizations.dm --- code/modules/busy_space/organizations.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/busy_space/organizations.dm b/code/modules/busy_space/organizations.dm index 41b3a80cbab..f80e82938ec 100644 --- a/code/modules/busy_space/organizations.dm +++ b/code/modules/busy_space/organizations.dm @@ -439,7 +439,7 @@ /datum/lore/organization/gov/synth name = "Synthetic Union" - short_name = "Synthtica" + short_name = "Synthetica" acronym = "SYN" desc = "A defensive coalition of synthetics based out of New Canaan,\ the Synthetic Union is an organization which aims to establish and consolidate synthetic rights across the galaxy.\ From c5ee1c68dd2f47a1d368e1b6b99b6d4f291a41b8 Mon Sep 17 00:00:00 2001 From: Darkmight9 <45213755+Darkmight9@users.noreply.github.com> Date: Fri, 27 Dec 2019 18:59:07 -0500 Subject: [PATCH 062/238] Increases bag space of ERTs and death squads --- code/game/jobs/job/central.dm | 2 +- code/game/objects/items/weapons/storage/backpack.dm | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm index 1d6ae0162a2..b5015359911 100644 --- a/code/game/jobs/job/central.dm +++ b/code/game/jobs/job/central.dm @@ -68,7 +68,7 @@ jobtype = /datum/job/ntspecops uniform = /obj/item/clothing/under/rank/centcom_commander suit = /obj/item/clothing/suit/space/deathsquad/officer - back = /obj/item/storage/backpack/security + back = /obj/item/storage/backpack/ert/security belt = /obj/item/storage/belt/military/assault gloves = /obj/item/clothing/gloves/combat shoes = /obj/item/clothing/shoes/combat diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm index 57f4e999ac8..d71f097eed9 100644 --- a/code/game/objects/items/weapons/storage/backpack.dm +++ b/code/game/objects/items/weapons/storage/backpack.dm @@ -537,6 +537,7 @@ desc = "A spacious backpack with lots of pockets, used by members of the Nanotrasen Emergency Response Team." icon_state = "ert_commander" item_state = "backpack" + max_combined_w_class = 30 resistance_flags = FIRE_PROOF //Commander From ddeae21a12e5cbc34c382f3b10d977c37483121a Mon Sep 17 00:00:00 2001 From: SteelSlayer Date: Sat, 28 Dec 2019 06:14:09 -0600 Subject: [PATCH 063/238] Adds circuit flooring to the floor painter --- code/game/objects/items/devices/floor_painter.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/game/objects/items/devices/floor_painter.dm b/code/game/objects/items/devices/floor_painter.dm index 2e90a40f52e..889138c9c91 100644 --- a/code/game/objects/items/devices/floor_painter.dm +++ b/code/game/objects/items/devices/floor_painter.dm @@ -14,12 +14,12 @@ flags = CONDUCT slot_flags = SLOT_BELT - var/static/list/allowed_states = list("arrival", "arrivalcorner", "bar", "barber", "blackcorner", "blue", "bluecorner", + var/static/list/allowed_states = list("arrival", "arrivalcorner", "bar", "barber", "bcircuit", "blackcorner", "blue", "bluecorner", "bluefull", "bluered", "blueyellow", "blueyellowfull", "bot", "brown", "browncorner", "browncornerold", "brownold", "cafeteria", "caution", "cautioncorner", "chapel", "cmo", "dark", "delivery", "escape", "escapecorner", "floor", - "freezerfloor", "green", "greenblue", "greenbluefull", "greencorner", "greenfull", "greenyellow", + "freezerfloor", "gcircuit", "green", "greenblue", "greenbluefull", "greencorner", "greenfull", "greenyellow", "greenyellowfull", "grimy", "loadingarea", "neutral", "neutralcorner", "neutralfull", "orange", "orangecorner", - "orangefull", "purple", "purplecorner", "purplefull", "rampbottom", "ramptop", "red", "redblue", "redbluefull", + "orangefull", "purple", "purplecorner", "purplefull", "rcircuit", "rampbottom", "ramptop", "red", "redblue", "redbluefull", "redcorner", "redfull", "redgreen", "redgreenfull", "redyellow", "redyellowfull", "warning", "warningcorner", "warnwhite", "warnwhitecorner", "white", "whiteblue", "whitebluecorner", "whitebluefull", "whitebot", "whitecorner", "whitedelivery", "whitegreen", "whitegreencorner", "whitegreenfull", "whitehall", "whitepurple", "whitepurplecorner", "whitepurplefull", From 13147538e7c29bec996f10a6fb4a2eb4808b7aaf Mon Sep 17 00:00:00 2001 From: Kyep Date: Sat, 28 Dec 2019 14:52:29 -0800 Subject: [PATCH 064/238] Makes fusion overload immune to explosions --- code/game/objects/structures/depot.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/game/objects/structures/depot.dm b/code/game/objects/structures/depot.dm index 9f3999ded7d..0cb8788d50c 100644 --- a/code/game/objects/structures/depot.dm +++ b/code/game/objects/structures/depot.dm @@ -124,3 +124,5 @@ STOP_PROCESSING(SSobj, src) qdel(src) +/obj/effect/overload/ex_act(severity) + return \ No newline at end of file From b015fbd163e3087b839c7d61cef76cbb7df15c64 Mon Sep 17 00:00:00 2001 From: SteelSlayer Date: Sun, 29 Dec 2019 21:30:08 -0600 Subject: [PATCH 065/238] Adds an alt-click shortcut for opening storage items --- code/game/objects/items/weapons/storage/storage.dm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm index b57bd8b9e50..fcee982b5e0 100644 --- a/code/game/objects/items/weapons/storage/storage.dm +++ b/code/game/objects/items/weapons/storage/storage.dm @@ -82,6 +82,12 @@ return return +/obj/item/storage/AltClick(mob/user) + if(user.Adjacent(get_turf(src))) + orient2hud(user) + if(user.s_active) + user.s_active.close(user) + show_to(user) /obj/item/storage/proc/return_inv() From be9b931b0f96232c88ba51051ff3cccdc7170dde Mon Sep 17 00:00:00 2001 From: Arvenius169 <56715097+Arvenius169@users.noreply.github.com> Date: Mon, 30 Dec 2019 03:24:18 -0500 Subject: [PATCH 066/238] embarassed --- code/modules/client/preference/loadout/loadout_racial.dm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/code/modules/client/preference/loadout/loadout_racial.dm b/code/modules/client/preference/loadout/loadout_racial.dm index 2acec90e7b7..6c438e41fda 100644 --- a/code/modules/client/preference/loadout/loadout_racial.dm +++ b/code/modules/client/preference/loadout/loadout_racial.dm @@ -1,15 +1,13 @@ /datum/gear/racial sort_category = "Racial" subtype_path = /datum/gear/racial - -/datum/gear/racial/taj - slot = slot_glasses cost = 2 -/datum/gear/racial/taj/civ +/datum/gear/racial/taj display_name = "embroidered veil" description = "A common traditional nano-fiber veil worn by many Tajaran, It is rare and offensive to see it on other races." path = /obj/item/clothing/glasses/tajblind + slot = slot_glasses cost = 1 /datum/gear/racial/taj/sec From 40118846f4490391a6f2a6f5722c6ee385b853e6 Mon Sep 17 00:00:00 2001 From: Arvenius169 <56715097+Arvenius169@users.noreply.github.com> Date: Thu, 2 Jan 2020 19:28:53 -0500 Subject: [PATCH 067/238] Update loadout_racial.dm --- code/modules/client/preference/loadout/loadout_racial.dm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/code/modules/client/preference/loadout/loadout_racial.dm b/code/modules/client/preference/loadout/loadout_racial.dm index 6c438e41fda..17ed52661d7 100644 --- a/code/modules/client/preference/loadout/loadout_racial.dm +++ b/code/modules/client/preference/loadout/loadout_racial.dm @@ -15,28 +15,33 @@ description = "A common traditional nano-fiber veil worn by many Tajaran, It is rare and offensive to see it on other races. This one has an in-built security HUD." path = /obj/item/clothing/glasses/hud/security/tajblind allowed_roles = list("Head of Security", "Warden", "Security Officer", "Security Pod Pilot", "Internal Affairs Agent", "Magistrate") + cost = 2 /datum/gear/racial/taj/med display_name = "lightweight veil" description = "A common traditional nano-fiber veil worn by many Tajaran, It is rare and offensive to see it on other races. This one has an in-built medical HUD." path = /obj/item/clothing/glasses/hud/health/tajblind allowed_roles = list("Chief Medical Officer", "Medical Doctor", "Chemist", "Psychiatrist", "Paramedic", "Virologist", "Brig Physician" , "Coroner") + cost = 2 /datum/gear/racial/taj/sci display_name = "hi-tech veil" description = "A common traditional nano-fiber veil worn by many Tajaran, It is rare and offensive to see it on other races." path = /obj/item/clothing/glasses/tajblind/sci + cost = 2 /datum/gear/racial/taj/eng display_name = "industrial veil" description = "A common traditional nano-fiber veil worn by many Tajaran, It is rare and offensive to see it on other races." path = /obj/item/clothing/glasses/tajblind/eng - + cost = 2 + /datum/gear/racial/taj/cargo display_name = "khaki veil" description = "A common traditional nano-fiber veil worn by many Tajaran, It is rare and offensive to see it on other races. It is light and comfy!" path = /obj/item/clothing/glasses/tajblind/cargo - + cost = 2 + /datum/gear/racial/footwraps display_name = "cloth footwraps" path = /obj/item/clothing/shoes/footwraps From 63c26fbf6fe33c7080f5f1e8d021ff9040de7a76 Mon Sep 17 00:00:00 2001 From: Arvenius169 <56715097+Arvenius169@users.noreply.github.com> Date: Thu, 2 Jan 2020 20:36:14 -0500 Subject: [PATCH 068/238] Update loadout_racial.dm --- code/modules/client/preference/loadout/loadout_racial.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/modules/client/preference/loadout/loadout_racial.dm b/code/modules/client/preference/loadout/loadout_racial.dm index 17ed52661d7..0d7b945bb78 100644 --- a/code/modules/client/preference/loadout/loadout_racial.dm +++ b/code/modules/client/preference/loadout/loadout_racial.dm @@ -1,14 +1,13 @@ /datum/gear/racial sort_category = "Racial" subtype_path = /datum/gear/racial - cost = 2 + cost = 1 /datum/gear/racial/taj display_name = "embroidered veil" description = "A common traditional nano-fiber veil worn by many Tajaran, It is rare and offensive to see it on other races." path = /obj/item/clothing/glasses/tajblind slot = slot_glasses - cost = 1 /datum/gear/racial/taj/sec display_name = "sleek veil" @@ -46,4 +45,3 @@ display_name = "cloth footwraps" path = /obj/item/clothing/shoes/footwraps slot = slot_shoes - cost = 1 From 0fe3459acd20ff4ab0a99a9555632b0443493758 Mon Sep 17 00:00:00 2001 From: Aquilar <20759278+Aquilar@users.noreply.github.com> Date: Fri, 3 Jan 2020 16:26:21 +0800 Subject: [PATCH 069/238] This commit is to fix a minor oversight with nanites and adminordrazine so it fixes internal bleeding, this solves a few edge cases where the user will bleed out due to multiple instances of internal bleeding. Signed-off-by: Aquilar <20759278+Aquilar@users.noreply.github.com> --- code/modules/reagents/chemistry/reagents/admin.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/reagents/chemistry/reagents/admin.dm b/code/modules/reagents/chemistry/reagents/admin.dm index 6f22078b278..90e6cff3bf6 100644 --- a/code/modules/reagents/chemistry/reagents/admin.dm +++ b/code/modules/reagents/chemistry/reagents/admin.dm @@ -23,6 +23,7 @@ I.receive_damage(-5, FALSE) for(var/obj/item/organ/external/E in H.bodyparts) E.mend_fracture() + E.internal_bleeding = FALSE M.SetEyeBlind(0, FALSE) M.CureNearsighted(FALSE) M.CureBlind(FALSE) From ad44e867befe6df1603eaff0a8c0050f192ae3e6 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sat, 4 Jan 2020 16:05:42 -0800 Subject: [PATCH 070/238] forum button linking --- interface/interface.dm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/interface.dm b/interface/interface.dm index a97500ccb4f..f616e41f713 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -48,8 +48,7 @@ set hidden = 1 if(config.forumurl) if(config.forum_link_url && prefs && !prefs.fuid) - if(alert("You need to activate your forum account. Activate now? (takes 2 seconds)",,"Yes","No")=="Yes") - link_forum_account() + link_forum_account() if(alert("Open the forum in your browser?",,"Yes","No")=="Yes") src << link(config.forumurl) else From 6c201f57af2404a88a72082d488b1325ba498d64 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sat, 4 Jan 2020 16:22:08 -0800 Subject: [PATCH 071/238] Add a spam limiter to the goonchat topic calls Port of: https://github.com/tgstation/tgstation/pull/48524/files --- goon/browserassets/js/browserOutput.js | 3 ++- goon/code/datums/browserOutput.dm | 27 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/goon/browserassets/js/browserOutput.js b/goon/browserassets/js/browserOutput.js index 317fc02b9cb..40a69245188 100644 --- a/goon/browserassets/js/browserOutput.js +++ b/goon/browserassets/js/browserOutput.js @@ -416,7 +416,8 @@ function handleClientData(ckey, ip, compid) { } } - if (opts.clientData.length >= opts.clientDataLimit) { + //Lets make sure we obey our limit (can connect from server with higher limit) + while (opts.clientData.length >= opts.clientDataLimit) { opts.clientData.shift(); } } else { diff --git a/goon/code/datums/browserOutput.dm b/goon/code/datums/browserOutput.dm index cbeb7ae3ab0..2ddcd37bb04 100644 --- a/goon/code/datums/browserOutput.dm +++ b/goon/code/datums/browserOutput.dm @@ -16,11 +16,18 @@ var/list/chatResources = list( "goon/browserassets/html/saveInstructions.html" ) +//Should match the value set in the browser js +#define MAX_COOKIE_LENGTH 5 + /var/savefile/iconCache = new /savefile("data/iconCache.sav") /var/chatDebug = file("data/chatDebug.log") /datum/chatOutput var/client/owner = null + // How many times client data has been checked + var/total_checks = 0 + // When to next clear the client data checks counter + var/next_time_to_clear = 0 var/loaded = 0 var/list/messageQueue = list() var/cookieSent = 0 @@ -137,6 +144,16 @@ var/list/chatResources = list( ehjax_send(data = data) /datum/chatOutput/proc/analyzeClientData(cookie = "") + //Spam check + if(world.time > next_time_to_clear) + next_time_to_clear = world.time + (3 SECONDS) + total_checks = 0 + total_checks += 1 + if(total_checks > SPAM_TRIGGER_AUTOMUTE) + message_admins("[key_name(owner)] kicked for goonchat topic spam") + qdel(owner) + return + if(!cookie) return @@ -145,13 +162,21 @@ var/list/chatResources = list( if(connData && islist(connData) && connData.len > 0 && connData["connData"]) connectionHistory = connData["connData"] var/list/found = new() + if(connectionHistory.len > MAX_COOKIE_LENGTH) + message_admins("[key_name(src.owner)] was kicked for an invalid ban cookie)") + qdel(owner) + return for(var/i = connectionHistory.len; i >= 1; i--) + if(QDELETED(owner)) + //he got cleaned up before we were done + return var/list/row = connectionHistory[i] if(!row || row.len < 3 || !(row["ckey"] && row["compid"] && row["ip"])) return if(world.IsBanned(row["ckey"], row["ip"], row["compid"], FALSE)) found = row break + CHECK_TICK //Add autoban using the DB_ban_record function //Uh oh this fucker has a history of playing on a banned account!! if (found.len > 0) @@ -284,3 +309,5 @@ var/to_chat_src to_chat_immediate(target, message, flag) return SSchat.queue(target, message, flag) + +#undef MAX_COOKIE_LENGTH \ No newline at end of file From 05221acd2810d03d0bd50e9a02ea15ade83578fd Mon Sep 17 00:00:00 2001 From: Kyep Date: Sat, 4 Jan 2020 17:23:44 -0800 Subject: [PATCH 072/238] ports TG PRs 32384 and 48583 1) ports https://github.com/tgstation/tgstation/pull/32384 - fixes our world/IsBanned not being updated to account for a BYOND change where the world/IsBanned proc now includes a 4th param, 'type' 2) ports https://github.com/tgstation/tgstation/pull/48583 - changes by MSO which prevent BYOND from degrading server performance during world topic calls 3) future-proofs calls to world/IsBanned by using explicitly named params to avoid this sort of thing happening again if it changes in future --- code/modules/admin/IsBanned.dm | 11 ++++++++++- goon/code/datums/browserOutput.dm | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 045819c365b..d776b4a2095 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -1,5 +1,5 @@ //Blocks an attempt to connect before even creating our client datum thing. -world/IsBanned(key, address, computer_id, check_ipintel = TRUE) +world/IsBanned(key, address, computer_id, type, check_ipintel = TRUE) if(!config.ban_legacy_system) if(address) @@ -11,11 +11,20 @@ world/IsBanned(key, address, computer_id, check_ipintel = TRUE) log_adminwarn("Failed Login (invalid data): [key] [address]-[computer_id]") return list("reason"="invalid login data", "desc"="Error: Could not check ban status, please try again. Error message: Your computer provided invalid or blank information to the server on connection (BYOND Username, IP, and Computer ID). Provided information for reference: Username: '[key]' IP: '[address]' Computer ID: '[computer_id]'. If you continue to get this error, please restart byond or contact byond support.") + if(type == "world") + return ..() //shunt world topic banchecks to purely to byond's internal ban system + if(text2num(computer_id) == 2147483647) //this cid causes stickybans to go haywire log_adminwarn("Failed Login (invalid cid): [key] [address]-[computer_id]") return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided an invalid Computer ID.") + var/admin = 0 var/ckey = ckey(key) + + var/client/C = GLOB.directory[ckey] + if (C && ckey == C.ckey && computer_id == C.computer_id && address == C.address) + return //don't recheck connected clients. + if((ckey in admin_datums) || (ckey in GLOB.deadmins)) var/datum/admins/A = admin_datums[ckey] if(A && (A.rights & R_ADMIN)) diff --git a/goon/code/datums/browserOutput.dm b/goon/code/datums/browserOutput.dm index cbeb7ae3ab0..4abb3ac20b8 100644 --- a/goon/code/datums/browserOutput.dm +++ b/goon/code/datums/browserOutput.dm @@ -149,7 +149,7 @@ var/list/chatResources = list( var/list/row = connectionHistory[i] if(!row || row.len < 3 || !(row["ckey"] && row["compid"] && row["ip"])) return - if(world.IsBanned(row["ckey"], row["ip"], row["compid"], FALSE)) + if(world.IsBanned(key=row["ckey"], address=row["ip"], computer_id=row["compid"], type=null, check_ipintel=FALSE)) found = row break //Add autoban using the DB_ban_record function From 7dcb34577bbf839f01fd77a2169606f9164e73d8 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 5 Jan 2020 00:01:08 -0800 Subject: [PATCH 073/238] Ports TG/lummox fix to reduce impact of keysend floods --- code/modules/client/message.dm | 7 ++++--- code/modules/keybindings/bindings_client.dm | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/code/modules/client/message.dm b/code/modules/client/message.dm index a5c78008b4f..bcb69255eca 100644 --- a/code/modules/client/message.dm +++ b/code/modules/client/message.dm @@ -4,6 +4,7 @@ proc/addclientmessage(var/ckey, var/message) ckey = ckey(ckey) if(!ckey || !message) return - if(!(ckey in clientmessages)) - clientmessages[ckey] = list() - clientmessages[ckey] += message + var/list/L = GLOB.clientmessages[ckey] + if(!L) + GLOB.clientmessages[ckey] = L = list() + L += message \ No newline at end of file diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm index d59e3da4e8e..f38e485d226 100644 --- a/code/modules/keybindings/bindings_client.dm +++ b/code/modules/keybindings/bindings_client.dm @@ -25,7 +25,7 @@ else log_admin("Client [ckey] was just autokicked for flooding keysends; likely abuse but potentially lagspike.") message_admins("Client [ckey] was just autokicked for flooding keysends; likely abuse but potentially lagspike.") - QDEL_IN(src, 1) + qdel(src) return ///Check if the key is short enough to even be a real key @@ -33,7 +33,7 @@ to_chat(src, "Invalid KeyDown detected! You have been disconnected from the server automatically.") log_admin("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.") message_admins("Client [ckey] just attempted to send an invalid keypress. Keymessage was over [MAX_KEYPRESS_COMMANDLENGTH] characters, autokicking due to likely abuse.") - QDEL_IN(src, 1) + qdel(src) return //offset by 1 because the buffer address is 0 indexed because the math was simpler keys_held[current_key_address + 1] = _key From f6d332d8292080632ce73130d7ef0939d8f5e53c Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 5 Jan 2020 00:08:09 -0800 Subject: [PATCH 074/238] brings paracode more in line with TG --- code/modules/client/client procs.dm | 12 ++++++------ code/modules/client/message.dm | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 6f971d308fa..c8a70a536ae 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -360,10 +360,10 @@ . = ..() //calls mob.Login() - if(ckey in clientmessages) - for(var/message in clientmessages[ckey]) + if(ckey in GLOB.clientmessages) + for(var/message in GLOB.clientmessages[ckey]) to_chat(src, message) - clientmessages.Remove(ckey) + GLOB.clientmessages.Remove(ckey) if(SSinput.initialized) set_macros() @@ -413,7 +413,7 @@ for(var/mob/M in GLOB.player_list) if(M.client) playercount += 1 - + if(playercount >= 150 && GLOB.panic_bunker_enabled == 0) GLOB.panic_bunker_enabled = 1 message_admins("Panic bunker has been automatically enabled due to playercount surpassing 150") @@ -553,7 +553,7 @@ src << "Sorry but the server is currently not accepting connections from never before seen players. Please try again later." del(src) return // Dont insert or they can just go in again - + var/DBQuery/query_insert = dbcon.NewQuery("INSERT INTO [format_table_name("player")] (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, '[ckey]', Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]')") if(!query_insert.Execute()) var/err = query_insert.ErrorMsg() @@ -774,7 +774,7 @@ // Change the way they should download resources. if(config.resource_urls) preload_rsc = pick(config.resource_urls) - else + else preload_rsc = 1 // If config.resource_urls is not set, preload like normal. // Most assets are now handled through global_cache.dm getFiles( diff --git a/code/modules/client/message.dm b/code/modules/client/message.dm index bcb69255eca..ba09e7e1567 100644 --- a/code/modules/client/message.dm +++ b/code/modules/client/message.dm @@ -1,4 +1,4 @@ -var/list/clientmessages = list() +GLOBAL_LIST_EMPTY(clientmessages) proc/addclientmessage(var/ckey, var/message) ckey = ckey(ckey) From 2208fb3407340bbef1a78b26c7fd0e9529b22985 Mon Sep 17 00:00:00 2001 From: Kyep Date: Mon, 6 Jan 2020 15:23:34 -0800 Subject: [PATCH 075/238] Updates Travis to use byond 513 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 464e01c8f53..146be925c77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,8 @@ addons: env: global: - - BYOND_MAJOR="512" - - BYOND_MINOR="1454" + - BYOND_MAJOR="513" + - BYOND_MINOR="1505" - BYOND_MACRO_COUNT=4 matrix: - DM_MAPFILE="cyberiad" From 57d79c6eef1028bb83dcb6ede3fb953d33904a57 Mon Sep 17 00:00:00 2001 From: Kyep Date: Mon, 6 Jan 2020 15:32:55 -0800 Subject: [PATCH 076/238] cleanup --- interface/interface.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/interface.dm b/interface/interface.dm index f616e41f713..4a140769f18 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -47,9 +47,9 @@ set desc = "Visit the forum." set hidden = 1 if(config.forumurl) - if(config.forum_link_url && prefs && !prefs.fuid) - link_forum_account() if(alert("Open the forum in your browser?",,"Yes","No")=="Yes") + if(config.forum_link_url && prefs && !prefs.fuid) + link_forum_account() src << link(config.forumurl) else to_chat(src, "The forum URL is not set in the server configuration.") From 2fd66d8510d37a4caff4391e3c197082c66745fa Mon Sep 17 00:00:00 2001 From: Kyep Date: Mon, 6 Jan 2020 16:01:20 -0800 Subject: [PATCH 077/238] replaces lentext() with length() --- code/__DEFINES/typeids.dm | 2 +- code/__HELPERS/text.dm | 14 +++++++------- code/game/machinery/doors/brigdoors.dm | 2 +- code/game/machinery/supply_display.dm | 2 +- code/game/objects/structures/musician.dm | 14 +++++++------- code/modules/admin/DB ban/functions.dm | 8 ++++---- code/modules/admin/topic.dm | 8 ++++---- code/modules/admin/verbs/modifyvariables.dm | 8 ++++---- code/modules/client/preference/preferences.dm | 10 +++++----- code/modules/clothing/clothing.dm | 2 +- code/modules/flufftext/TextFilters.dm | 4 ++-- code/modules/mob/hear_say.dm | 2 +- code/modules/mob/living/carbon/human/examine.dm | 2 +- code/modules/mob/living/silicon/pai/pai.dm | 2 +- code/modules/mob/living/silicon/robot/examine.dm | 2 +- code/modules/mob/mob.dm | 2 +- code/modules/mob/mob_helpers.dm | 14 +++++++------- code/modules/shuttle/supply.dm | 4 ++-- 18 files changed, 51 insertions(+), 51 deletions(-) diff --git a/code/__DEFINES/typeids.dm b/code/__DEFINES/typeids.dm index 4fd2c390fa7..16b6e475b8e 100644 --- a/code/__DEFINES/typeids.dm +++ b/code/__DEFINES/typeids.dm @@ -2,5 +2,5 @@ #define TYPEID_NULL "0" #define TYPEID_NORMAL_LIST "f" //helper macros -#define GET_TYPEID(ref) ( ( (lentext(ref) <= 10) ? "TYPEID_NULL" : copytext(ref, 4, lentext(ref) - 6) ) ) +#define GET_TYPEID(ref) ( ( (length(ref) <= 10) ? "TYPEID_NULL" : copytext(ref, 4, length(ref) - 6) ) ) #define IS_NORMAL_LIST(L) (GET_TYPEID("\ref[L]") == TYPEID_NORMAL_LIST) \ No newline at end of file diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index f3b76064517..0f356aa4f4a 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -20,7 +20,7 @@ if(!istext(t)) t = "[t]" // Just quietly assume any non-texts are supposed to be text var/sqltext = dbcon.Quote(t); - return copytext(sqltext, 2, lentext(sqltext));//Quote() adds quotes around input, we already do that + return copytext(sqltext, 2, length(sqltext));//Quote() adds quotes around input, we already do that /proc/format_table_name(table as text) return sqlfdbktableprefix + table @@ -333,9 +333,9 @@ proc/checkhtml(var/t) //is in the other string at the same spot (assuming it is not a replace char). //This is used for fingerprints var/newtext = text - if(lentext(text) != lentext(compare)) + if(length(text) != length(compare)) return 0 - for(var/i = 1, i < lentext(text), i++) + for(var/i = 1, i < length(text), i++) var/a = copytext(text,i,i+1) var/b = copytext(compare,i,i+1) //if it isn't both the same letter, or if they are both the replacement character @@ -355,7 +355,7 @@ proc/checkhtml(var/t) if(!text || !character) return 0 var/count = 0 - for(var/i = 1, i <= lentext(text), i++) + for(var/i = 1, i <= length(text), i++) var/a = copytext(text,i,i+1) if(a == character) count++ @@ -400,8 +400,8 @@ proc/checkhtml(var/t) //Used in preferences' SetFlavorText and human's set_flavor verb //Previews a string of len or less length /proc/TextPreview(var/string,var/len=40) - if(lentext(string) <= len) - if(!lentext(string)) + if(length(string) <= len) + if(!length(string)) return "\[...\]" else return html_encode(string) //NO DECODED HTML YOU CHUCKLEFUCKS @@ -541,7 +541,7 @@ proc/checkhtml(var/t) text = "[text]" else text = "[text]" - + text = copytext(text, 1, MAX_PAPER_MESSAGE_LEN) return text diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index f23734a0079..8bd7af5dad4 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -444,7 +444,7 @@ //Stolen from status_display /obj/machinery/door_timer/proc/texticon(tn, px = 0, py = 0) var/image/I = image('icons/obj/status_display.dmi', "blank") - var/len = lentext(tn) + var/len = length(tn) for(var/d = 1 to len) var/char = copytext(tn, len-d+1, len-d+2) diff --git a/code/game/machinery/supply_display.dm b/code/game/machinery/supply_display.dm index d08f68b2914..1fe7ee6e4cb 100644 --- a/code/game/machinery/supply_display.dm +++ b/code/game/machinery/supply_display.dm @@ -14,7 +14,7 @@ else message1 = "CARGO" message2 = SSshuttle.supply.getTimerStr() - if(lentext(message2) > CHARS_PER_LINE) + if(length(message2) > CHARS_PER_LINE) message2 = "Error" update_display(message1, message2) diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm index 274645933b2..da3ec759d36 100644 --- a/code/game/objects/structures/musician.dm +++ b/code/game/objects/structures/musician.dm @@ -88,12 +88,12 @@ if(!playing || shouldStopPlaying(user)) //If the instrument is playing, or special case playing = 0 return - if(lentext(note) == 0) + if(length(note) == 0) continue var/cur_note = text2ascii(note) - 96 if(cur_note < 1 || cur_note > 7) continue - for(var/i=2 to lentext(note)) + for(var/i=2 to length(note)) var/ni = copytext(note,i,i+1) if(!text2num(ni)) if(ni == "#" || ni == "b" || ni == "n") @@ -159,11 +159,11 @@ if(!in_range(instrumentObj, usr)) return - if(lentext(t) >= 12000) + if(length(t) >= 12000) var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") if(cont == "no") break - while(lentext(t) > 12000) + while(length(t) > 12000) //split into lines spawn() @@ -180,7 +180,7 @@ lines.Cut(201) var/linenum = 1 for(var/l in lines) - if(lentext(l) > 200) + if(length(l) > 200) to_chat(usr, "Line [linenum] too long!") lines.Remove(l) else @@ -223,7 +223,7 @@ return if(lines.len > 200) return - if(lentext(newline) > 200) + if(length(newline) > 200) newline = copytext(newline, 1, 200) lines.Insert(num, newline) @@ -241,7 +241,7 @@ var/content = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null) if(!content || !in_range(instrumentObj, usr)) return - if(lentext(content) > 200) + if(length(content) > 200) content = copytext(content, 1, 200) if(num > lines.len || num < 1) return diff --git a/code/modules/admin/DB ban/functions.dm b/code/modules/admin/DB ban/functions.dm index 1ce6c2b90f0..86a6b228b60 100644 --- a/code/modules/admin/DB ban/functions.dm +++ b/code/modules/admin/DB ban/functions.dm @@ -469,13 +469,13 @@ datum/admins/proc/DB_ban_unban_by_id(var/id) if(playercid) cidsearch = "AND computerid = '[playercid]' " else - if(adminckey && lentext(adminckey) >= 3) + if(adminckey && length(adminckey) >= 3) adminsearch = "AND a_ckey LIKE '[adminckey]%' " - if(playerckey && lentext(playerckey) >= 3) + if(playerckey && length(playerckey) >= 3) playersearch = "AND ckey LIKE '[playerckey]%' " - if(playerip && lentext(playerip) >= 3) + if(playerip && length(playerip) >= 3) ipsearch = "AND ip LIKE '[playerip]%' " - if(playercid && lentext(playercid) >= 7) + if(playercid && length(playercid) >= 7) cidsearch = "AND computerid LIKE '[playercid]%' " if(dbbantype) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index a318c316532..4c1e86d0fd9 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -3066,7 +3066,7 @@ else if(href_list["ac_set_channel_name"]) src.admincaster_feed_channel.channel_name = strip_html_simple(input(usr, "Provide a Feed Channel Name", "Network Channel Handler", "")) while(findtext(src.admincaster_feed_channel.channel_name," ") == 1) - src.admincaster_feed_channel.channel_name = copytext(src.admincaster_feed_channel.channel_name,2,lentext(src.admincaster_feed_channel.channel_name)+1) + src.admincaster_feed_channel.channel_name = copytext(src.admincaster_feed_channel.channel_name,2,length(src.admincaster_feed_channel.channel_name)+1) src.access_news_network() else if(href_list["ac_set_channel_lock"]) @@ -3105,7 +3105,7 @@ else if(href_list["ac_set_new_message"]) src.admincaster_feed_message.body = adminscrub(input(usr, "Write your Feed story", "Network Channel Handler", "")) while(findtext(src.admincaster_feed_message.body," ") == 1) - src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.body,2,lentext(src.admincaster_feed_message.body)+1) + src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.body,2,length(src.admincaster_feed_message.body)+1) src.access_news_network() else if(href_list["ac_submit_new_message"]) @@ -3159,13 +3159,13 @@ else if(href_list["ac_set_wanted_name"]) src.admincaster_feed_message.author = adminscrub(input(usr, "Provide the name of the Wanted person", "Network Security Handler", "")) while(findtext(src.admincaster_feed_message.author," ") == 1) - src.admincaster_feed_message.author = copytext(admincaster_feed_message.author,2,lentext(admincaster_feed_message.author)+1) + src.admincaster_feed_message.author = copytext(admincaster_feed_message.author,2,length(admincaster_feed_message.author)+1) src.access_news_network() else if(href_list["ac_set_wanted_desc"]) src.admincaster_feed_message.body = adminscrub(input(usr, "Provide the a description of the Wanted person and any other details you deem important", "Network Security Handler", "")) while(findtext(src.admincaster_feed_message.body," ") == 1) - src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.body,2,lentext(src.admincaster_feed_message.body)+1) + src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.body,2,length(src.admincaster_feed_message.body)+1) src.access_news_network() else if(href_list["ac_submit_wanted"]) diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index f3be2161bb2..c5348fbeabe 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -306,11 +306,11 @@ var/list/VVpixelmovement = list("step_x", "step_y", "step_size", "bound_height", // the type with the base type removed from the begaining var/fancytype = types[D.type] if(findtext(fancytype, types[type])) - fancytype = copytext(fancytype, lentext(types[type])+1) - var/shorttype = copytext("[D.type]", lentext("[type]")+1) - if(lentext(shorttype) > lentext(fancytype)) + fancytype = copytext(fancytype, length(types[type])+1) + var/shorttype = copytext("[D.type]", length("[type]")+1) + if(length(shorttype) > length(fancytype)) shorttype = fancytype - if(!lentext(shorttype)) + if(!length(shorttype)) shorttype = "/" .["[D]([shorttype])\ref[D]#[i]"] = D diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm index 11eb454a241..56cb55b6b80 100644 --- a/code/modules/client/preference/preferences.dm +++ b/code/modules/client/preference/preferences.dm @@ -294,8 +294,8 @@ var/global/list/special_role_times = list( //minimum age (in days) for accounts dat += "Disabilities: \[Set\]
" dat += "Nanotrasen Relation: [nanotrasen_relation]
" dat += "Set Flavor Text
" - if(lentext(flavor_text) <= 40) - if(!lentext(flavor_text)) dat += "\[...\]
" + if(length(flavor_text) <= 40) + if(!length(flavor_text)) dat += "\[...\]
" else dat += "[flavor_text]
" else dat += "[TextPreview(flavor_text)]...
" @@ -886,21 +886,21 @@ var/global/list/special_role_times = list( //minimum age (in days) for accounts HTML += "Medical Records
" - if(lentext(med_record) <= 40) + if(length(med_record) <= 40) HTML += "[med_record]" else HTML += "[copytext(med_record, 1, 37)]..." HTML += "
Employment Records
" - if(lentext(gen_record) <= 40) + if(length(gen_record) <= 40) HTML += "[gen_record]" else HTML += "[copytext(gen_record, 1, 37)]..." HTML += "
Security Records
" - if(lentext(sec_record) <= 40) + if(length(sec_record) <= 40) HTML += "[sec_record]
" else HTML += "[copytext(sec_record, 1, 37)]...
" diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 84d77d1482e..adb35b0fb3b 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -527,7 +527,7 @@ BLIND // can't see anything end up with a suffix of _open_open if adjusted twice, since their initial state is _open. */ item_state = copytext(item_state, 1, findtext(item_state, "_open")) if(adjust_flavour) - flavour = "[copytext(adjust_flavour, 3, lentext(adjust_flavour) + 1)] up" //Trims off the 'un' at the beginning of the word. unzip -> zip, unbutton->button. + flavour = "[copytext(adjust_flavour, 3, length(adjust_flavour) + 1)] up" //Trims off the 'un' at the beginning of the word. unzip -> zip, unbutton->button. to_chat(user, "You [flavour] \the [src].") suit_adjusted = 0 //Suit is no longer adjusted. for(var/X in actions) diff --git a/code/modules/flufftext/TextFilters.dm b/code/modules/flufftext/TextFilters.dm index 02a02a6f5b2..e00f653ac45 100644 --- a/code/modules/flufftext/TextFilters.dm +++ b/code/modules/flufftext/TextFilters.dm @@ -1,7 +1,7 @@ proc/Intoxicated(phrase) phrase = html_decode(phrase) - var/leng=lentext(phrase) - var/counter=lentext(phrase) + var/leng=length(phrase) + var/counter=length(phrase) var/newphrase="" var/newletter="" while(counter>=1) diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index 45d4cda2202..a91aeed7bdf 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -162,7 +162,7 @@ if(copytext(heardword,1, 1) in punctuation) heardword = copytext(heardword,2) if(copytext(heardword,-1) in punctuation) - heardword = copytext(heardword,1,lentext(heardword)) + heardword = copytext(heardword,1,length(heardword)) heard = "...You hear something about... '[heardword]'..." else heard = "...You almost hear something......" diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index b859041d02f..85bb90a168e 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -376,7 +376,7 @@ msg += "*---------*" if(pose) - if( findtext(pose,".",lentext(pose)) == 0 && findtext(pose,"!",lentext(pose)) == 0 && findtext(pose,"?",lentext(pose)) == 0 ) + if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) pose = addtext(pose,".") //Makes sure all emotes end with a period. msg += "\n[p_they(TRUE)] [p_are()] [pose]" diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 25547e5de9e..4e280b20de8 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -538,7 +538,7 @@ if(print_flavor_text()) msg += "\n[print_flavor_text()]" if(pose) - if( findtext(pose,".",lentext(pose)) == 0 && findtext(pose,"!",lentext(pose)) == 0 && findtext(pose,"?",lentext(pose)) == 0 ) + if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) pose = addtext(pose,".") //Makes sure all emotes end with a period. msg += "\nIt is [pose]" msg += "\n*---------*" diff --git a/code/modules/mob/living/silicon/robot/examine.dm b/code/modules/mob/living/silicon/robot/examine.dm index 56d01fea839..198b687e496 100644 --- a/code/modules/mob/living/silicon/robot/examine.dm +++ b/code/modules/mob/living/silicon/robot/examine.dm @@ -50,7 +50,7 @@ if(print_flavor_text()) msg += "\n[print_flavor_text()]\n" if(pose) - if( findtext(pose,".",lentext(pose)) == 0 && findtext(pose,"!",lentext(pose)) == 0 && findtext(pose,"?",lentext(pose)) == 0 ) + if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) pose = addtext(pose,".") //Makes sure all emotes end with a period. msg += "\nIt is [pose]" diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index c9e77e294d2..609940bc2a6 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -726,7 +726,7 @@ var/list/slot_equipment_priority = list( \ /mob/proc/print_flavor_text(var/shrink = 1) if(flavor_text && flavor_text != "") var/msg = replacetext(flavor_text, "\n", " ") - if(lentext(msg) <= 40 || !shrink) + if(length(msg) <= 40 || !shrink) return "[html_encode(msg)]" //Repeat after me, "I will not give players access to decoded HTML." else return "[copytext_preserve_html(msg, 1, 37)]... More..." diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 0c2bb902737..a96a832fef4 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -201,8 +201,8 @@ /proc/slur(phrase, var/list/slurletters = ("'"))//use a different list as an input if you want to make robots slur with $#@%! characters phrase = html_decode(phrase) - var/leng=lentext(phrase) - var/counter=lentext(phrase) + var/leng=length(phrase) + var/counter=length(phrase) var/newphrase="" var/newletter="" while(counter>=1) @@ -295,8 +295,8 @@ proc/muffledspeech(phrase) phrase = html_decode(phrase) - var/leng=lentext(phrase) - var/counter=lentext(phrase) + var/leng=length(phrase) + var/counter=length(phrase) var/newphrase="" var/newletter="" while(counter>=1) @@ -571,7 +571,7 @@ var/list/intents = list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM) for(var/datum/objective/objective in GLOB.all_objectives) if(!mind || objective.target != mind) continue - length = lentext(oldname) + length = length(oldname) pos = findtextEx(objective.explanation_text, oldname) objective.explanation_text = copytext(objective.explanation_text, 1, pos)+newname+copytext(objective.explanation_text, pos+length) return 1 @@ -610,8 +610,8 @@ var/list/intents = list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM) /proc/cultslur(n) // Inflicted on victims of a stun talisman var/phrase = html_decode(n) - var/leng = lentext(phrase) - var/counter=lentext(phrase) + var/leng = length(phrase) + var/counter=length(phrase) var/newphrase="" var/newletter="" while(counter>=1) diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index 9da43d08fbf..446b6466dc5 100644 --- a/code/modules/shuttle/supply.dm +++ b/code/modules/shuttle/supply.dm @@ -475,7 +475,7 @@ SSnanoui.update_uis(src) return 1 - var/index = copytext(href_list["doorder"], 1, lentext(href_list["doorder"])) //text2num(copytext(href_list["doorder"], 1)) + var/index = copytext(href_list["doorder"], 1, length(href_list["doorder"])) //text2num(copytext(href_list["doorder"], 1)) var/multi = text2num(copytext(href_list["doorder"], -1)) if(!isnum(multi)) return 1 @@ -656,7 +656,7 @@ SSnanoui.update_uis(src) return 1 - var/index = copytext(href_list["doorder"], 1, lentext(href_list["doorder"])) //text2num(copytext(href_list["doorder"], 1)) + var/index = copytext(href_list["doorder"], 1, length(href_list["doorder"])) //text2num(copytext(href_list["doorder"], 1)) var/multi = text2num(copytext(href_list["doorder"], -1)) if(!isnum(multi)) return 1 From f9171913c8a9c8136ce6e9db5d83a726f000fc9d Mon Sep 17 00:00:00 2001 From: AffectedArc07 Date: Tue, 7 Jan 2020 19:46:09 +0000 Subject: [PATCH 078/238] Runtime Transparency (#12886) --- code/__HELPERS/_logging.dm | 3 +++ code/_globalvars/logging.dm | 2 ++ code/game/world.dm | 7 +++++++ code/modules/error_handler/error_handler.dm | 3 +++ 4 files changed, 15 insertions(+) diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 65fc87c3b34..8c07fe175b2 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -140,6 +140,9 @@ /proc/log_asset(text) WRITE_LOG(GLOB.world_asset_log, "ASSET: [text]") +/proc/log_runtime_summary(text) + WRITE_LOG(GLOB.runtime_summary_log, "[text]") + /** * Standardized method for tracking startup times. */ diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 43482937afb..10c7d74c752 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -12,6 +12,8 @@ GLOBAL_VAR(world_href_log) GLOBAL_PROTECT(world_href_log) GLOBAL_VAR(world_asset_log) GLOBAL_PROTECT(world_asset_log) +GLOBAL_VAR(runtime_summary_log) +GLOBAL_PROTECT(runtime_summary_log) var/list/jobMax = list() var/list/admin_log = list ( ) diff --git a/code/game/world.dm b/code/game/world.dm index b059a7289de..4488cb2d218 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -414,6 +414,13 @@ var/failed_old_db_connections = 0 start_log(GLOB.world_runtime_log) start_log(GLOB.world_qdel_log) + // This log follows a special format and this path should NOT be used for anything else + GLOB.runtime_summary_log = "data/logs/runtime_summary.log" + if(fexists(GLOB.runtime_summary_log)) + fdel(GLOB.runtime_summary_log) + start_log(GLOB.runtime_summary_log) + // And back to sanity + if(fexists(GLOB.config_error_log)) fcopy(GLOB.config_error_log, "[GLOB.log_directory]/config_error.log") fdel(GLOB.config_error_log) diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm index 09a5d20833e..4888948344a 100644 --- a/code/modules/error_handler/error_handler.dm +++ b/code/modules/error_handler/error_handler.dm @@ -44,6 +44,9 @@ var/total_runtimes_skipped = 0 error_last_seen[erroruid] = world.time error_cooldown[erroruid] = cooldown + // This line will log a runtime summary to a file which can be publicly distributed without sending player data + log_runtime_summary("Runtime in [e.file],[e.line]: [e]") + // The detailed error info needs some tweaking to make it look nice var/list/srcinfo = null var/list/usrinfo = null From cf2145a9535ebe5c181f044a66ec5c6adead9d09 Mon Sep 17 00:00:00 2001 From: Nokko Date: Tue, 7 Jan 2020 11:46:47 -0800 Subject: [PATCH 079/238] Another spelling fix in surgery (#12885) Fixed misspelling in surgery saw fail step --- code/modules/surgery/generic.dm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/modules/surgery/generic.dm b/code/modules/surgery/generic.dm index 54a9c4c88be..39c21bf1ee1 100644 --- a/code/modules/surgery/generic.dm +++ b/code/modules/surgery/generic.dm @@ -1,7 +1,7 @@ -//Procedures in this file: Gneric surgery steps -////////////////////////////////////////////////////////////////// +//Procedures in this file: Generic surgery steps +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // COMMON STEPS // -////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /datum/surgery_step/generic/ can_infect = 1 @@ -237,7 +237,7 @@ /datum/surgery_step/generic/amputate/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool,datum/surgery/surgery) var/obj/item/organ/external/affected = target.get_organ(target_zone) user.visible_message(" [user]'s hand slips, sawing through the bone in [target]'s [affected.name] with \the [tool]!", \ - " Your hand slips, sawwing through the bone in [target]'s [affected.name] with \the [tool]!") + " Your hand slips, sawing through the bone in [target]'s [affected.name] with \the [tool]!") affected.receive_damage(30) affected.fracture() return 0 From 692301efb6aae1b061a2646c742afb246a235eaa Mon Sep 17 00:00:00 2001 From: SteelSlayer Date: Wed, 8 Jan 2020 09:02:52 -0600 Subject: [PATCH 080/238] Fixes a runtime with revenants --- code/_onclick/observer.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index 5f0c94ecee6..8397a689089 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -61,6 +61,8 @@ // health + cyborg analyzer for ghosts /mob/living/attack_ghost(mob/dead/observer/user) + if(!istype(user)) + return if(user.client && user.health_scan) if(issilicon(src) || ismachine(src)) robot_healthscan(user, src) From 9dc3a18506a775702928d27c9716da0ba3fde774 Mon Sep 17 00:00:00 2001 From: Kyep Date: Thu, 9 Jan 2020 03:56:17 -0800 Subject: [PATCH 081/238] tweaks link message for banned players --- code/modules/client/client procs.dm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 5248ba22178..962be47cae9 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -646,10 +646,11 @@ return var/url = "[config.forum_link_url][tokenid]" if(fromban) - to_chat(src, {"Copy/paste this URL into your browser to activate your forum account before you appeal: [url] (this link expires soon)"}) + url += "&fwd=appeal" + to_chat(src, {"Now opening a window to verify your information with the forums, so that you can appeal your ban. If the window does not load, please copy/paste this link: [url]"}) else to_chat(src, {"Now opening a window to verify your information with the forums. If the window does not load, please go to: [url]"}) - src << link(url) + src << link(url) return #undef TOPIC_SPAM_DELAY From 821d00f04cd5e135a88011eb6d64bd48e58fea32 Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Fri, 10 Jan 2020 13:00:32 +0100 Subject: [PATCH 082/238] put_in_hands instead of the other check --- code/game/machinery/doors/airlock.dm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index fb07debfe7b..3f27fa26239 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1349,8 +1349,7 @@ About the new airlock wires panel: else user.visible_message("[user] cuts down [note] from [src].", "You remove [note] from [src].") playsound(src, 'sound/items/wirecutter.ogg', 50, 1) - if(!user.put_in_active_hand(note) && !user.put_in_inactive_hand(note)) - note.forceMove(get_turf(user)) + user.put_in_hands(note) note = null update_icon() return TRUE From cc8af594909971f8290b72c64c14bc07d4b3627c Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Fri, 10 Jan 2020 13:31:10 +0100 Subject: [PATCH 083/238] Adds the job and antag status to the offer to ghost question --- code/modules/mob/mob_helpers.dm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index a96a832fef4..e2e5de12e31 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -118,7 +118,10 @@ log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.") var/minhours = input(usr, "Minimum hours required to play [M]?", "Set Min Hrs", 10) as num message_admins("[key_name_admin(usr)] has offered control of ([key_name_admin(M)]) to ghosts with [minhours] hrs playtime") - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as [M.real_name ? M.real_name : M.name]?", poll_time = 100, min_hours = minhours) + var/question = "Do you want to play as [M.real_name ? M.real_name : M.name][M.job ? " ([M.job])" : ""]" + if(alert("Do you want to show the antag status?","Show antag status","Yes","No") == "Yes") + question += ", [M.mind?.special_role ? M.mind?.special_role : "No special role"]" + var/list/mob/dead/observer/candidates = pollCandidates("[question]?", poll_time = 100, min_hours = minhours) var/mob/dead/observer/theghost = null if(LAZYLEN(candidates)) From 3ef5b122bf2d85fe7f0117dd6a51fab8c666f772 Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Fri, 10 Jan 2020 14:10:46 +0100 Subject: [PATCH 084/238] Sets the special_role statuses to trait_vamp and cling_vamp --- code/game/gamemodes/changeling/traitor_chan.dm | 1 + code/game/gamemodes/vampire/traitor_vamp.dm | 1 + 2 files changed, 2 insertions(+) diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm index 80a3dc37809..551cfdc05c6 100644 --- a/code/game/gamemodes/changeling/traitor_chan.dm +++ b/code/game/gamemodes/changeling/traitor_chan.dm @@ -28,6 +28,7 @@ changelings += changeling modePlayer += changelings changeling.restricted_roles = restricted_jobs + changeling.special_role = SPECIAL_ROLE_CHANGELING return ..() else return 0 diff --git a/code/game/gamemodes/vampire/traitor_vamp.dm b/code/game/gamemodes/vampire/traitor_vamp.dm index 406275c709f..4794f6755f2 100644 --- a/code/game/gamemodes/vampire/traitor_vamp.dm +++ b/code/game/gamemodes/vampire/traitor_vamp.dm @@ -32,6 +32,7 @@ slaved.masters += vampire vampire.som = slaved //we MIGT want to mindslave someone vampire.restricted_roles = restricted_jobs + vampire.special_role = SPECIAL_ROLE_VAMPIRE ..() return 1 else From 882b1cededc3689e4e98ed64728bf014df5ecf14 Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Fri, 10 Jan 2020 14:46:32 +0100 Subject: [PATCH 085/238] Being antag now follows the be civ if no job pref --- code/controllers/subsystem/jobs.dm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/code/controllers/subsystem/jobs.dm b/code/controllers/subsystem/jobs.dm index 504a2d74a2c..58801a79f9b 100644 --- a/code/controllers/subsystem/jobs.dm +++ b/code/controllers/subsystem/jobs.dm @@ -389,8 +389,11 @@ SUBSYSTEM_DEF(jobs) // Antags, who have to get in, come first for(var/mob/new_player/player in unassigned) if(player.mind.special_role) - GiveRandomJob(player) - if(player in unassigned) + if(player.client.prefs.alternate_option != BE_ASSISTANT) + GiveRandomJob(player) + if(player in unassigned) + AssignRole(player, "Civilian") + else AssignRole(player, "Civilian") // Then we assign what we can to everyone else. From 4b3e3a67f473fcd48955697c529ac3ffffe29e60 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Jan 2020 19:57:59 -0500 Subject: [PATCH 086/238] Apprentices are not traitors. IsWizard checks apprentices as well. Wizard Rounds End when all Wizard/Apprentices die, even if other antagonists are alive. --- code/game/gamemodes/wizard/artefact.dm | 2 +- code/game/gamemodes/wizard/wizard.dm | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/code/game/gamemodes/wizard/artefact.dm b/code/game/gamemodes/wizard/artefact.dm index 187ac0f0ea1..4d6215ef4c4 100644 --- a/code/game/gamemodes/wizard/artefact.dm +++ b/code/game/gamemodes/wizard/artefact.dm @@ -99,7 +99,7 @@ new_objective:target = H:mind new_objective.explanation_text = "Protect [H.real_name], the wizard." M.mind.objectives += new_objective - SSticker.mode.traitors += M.mind + SSticker.mode.apprentices += M.mind M.mind.special_role = SPECIAL_ROLE_WIZARD_APPRENTICE SSticker.mode.update_wiz_icons_added(M.mind) M.faction = list("wizard") diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index 131afd84fff..7a6f634784f 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -1,5 +1,6 @@ /datum/game_mode var/list/datum/mind/wizards = list() + var/list/datum/mind/apprentices = list() /datum/game_mode/wizard name = "wizard" @@ -161,7 +162,7 @@ // Checks if the game should end due to all wizards and apprentices being dead, or MMI'd/Borged /datum/game_mode/wizard/check_finished() var/wizards_alive = 0 - var/traitors_alive = 0 + var/apprentices_alive = 0 // Wizards for(var/datum/mind/wizard in wizards) @@ -173,18 +174,18 @@ continue wizards_alive++ - // Apprentices - classified as "traitors" + // Apprentices if(!wizards_alive) - for(var/datum/mind/traitor in traitors) - if(!istype(traitor.current,/mob/living/carbon)) + for(var/datum/mind/apprentice in apprentices) + if(!istype(apprentice.current,/mob/living/carbon)) continue - if(traitor.current.stat==DEAD) + if(apprentice.current.stat==DEAD) continue - if(istype(traitor.current, /obj/item/mmi)) // apprentice is in an MMI, don't count them as alive + if(istype(apprentice.current, /obj/item/mmi)) // apprentice is in an MMI, don't count them as alive continue - traitors_alive++ + apprentices_alive++ - if(wizards_alive || traitors_alive || but_wait_theres_more) + if(wizards_alive || apprentices_alive || but_wait_theres_more) return ..() else finished = 1 @@ -279,4 +280,4 @@ Made a proc so this is not repeated 14 (or more) times.*/ return 1 /proc/iswizard(mob/living/M as mob) - return istype(M) && M.mind && SSticker && SSticker.mode && (M.mind in SSticker.mode.wizards) + return istype(M) && M.mind && SSticker && SSticker.mode && ((M.mind in SSticker.mode.wizards) || (M.mind in SSticker.mode.apprentices)) From f0b50ab60c89f02838f787adb3d8d15b01c87424 Mon Sep 17 00:00:00 2001 From: SteelSlayer Date: Sat, 11 Jan 2020 21:24:08 -0600 Subject: [PATCH 087/238] explanation comment --- code/_onclick/observer.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index 8397a689089..460bc0193f6 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -61,7 +61,7 @@ // health + cyborg analyzer for ghosts /mob/living/attack_ghost(mob/dead/observer/user) - if(!istype(user)) + if(!istype(user)) // Make sure user is actually an observer. Revenents also use attack_ghost, but do not have the health_scan var. return if(user.client && user.health_scan) if(issilicon(src) || ismachine(src)) From 5d04a210fcc2afdb245da9809d5ebd3b88eb9a5c Mon Sep 17 00:00:00 2001 From: Arvenius169 <56715097+Arvenius169@users.noreply.github.com> Date: Sun, 12 Jan 2020 12:01:42 -0500 Subject: [PATCH 088/238] "DEVASTATION", not "DEVESTATION" "Devestation" is pronounced "deev-station". --- code/_globalvars/configuration.dm | 2 +- code/controllers/configuration.dm | 2 +- code/game/objects/effects/effect_system.dm | 2 +- code/game/objects/effects/effect_system/effects_other.dm | 2 +- code/game/objects/explosion.dm | 2 +- code/modules/admin/topic.dm | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm index 3d7259eef55..ee3f6a73a92 100644 --- a/code/_globalvars/configuration.dm +++ b/code/_globalvars/configuration.dm @@ -32,7 +32,7 @@ var/Debug = 0 // global debug switch var/Debug2 = 1 // enables detailed job debug file in secrets //This was a define, but I changed it to a variable so it can be changed in-game.(kept the all-caps definition because... code...) -Errorage -var/MAX_EX_DEVESTATION_RANGE = 3 +var/MAX_EX_DEVASTATION_RANGE = 3 var/MAX_EX_HEAVY_RANGE = 7 var/MAX_EX_LIGHT_RANGE = 14 var/MAX_EX_FLASH_RANGE = 14 diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 86883654997..d1a3861676b 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -785,7 +785,7 @@ if(BombCap > 128) BombCap = 128 - MAX_EX_DEVESTATION_RANGE = round(BombCap/4) + MAX_EX_DEVASTATION_RANGE = round(BombCap/4) MAX_EX_HEAVY_RANGE = round(BombCap/2) MAX_EX_LIGHT_RANGE = BombCap MAX_EX_FLASH_RANGE = BombCap diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index a145fcea1d4..7301eb2df03 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -1181,7 +1181,7 @@ steam.start() -- spawns the effect // Clamp all values to MAX_EXPLOSION_RANGE if(round(amount/12) > 0) - devastation = min (MAX_EX_DEVESTATION_RANGE, devastation + round(amount/12)) + devastation = min (MAX_EX_DEVASTATION_RANGE, devastation + round(amount/12)) if(round(amount/6) > 0) heavy = min (MAX_EX_HEAVY_RANGE, heavy + round(amount/6)) diff --git a/code/game/objects/effects/effect_system/effects_other.dm b/code/game/objects/effects/effect_system/effects_other.dm index 194a37c860f..cec70ad90e6 100644 --- a/code/game/objects/effects/effect_system/effects_other.dm +++ b/code/game/objects/effects/effect_system/effects_other.dm @@ -172,7 +172,7 @@ // Clamp all values to MAX_EXPLOSION_RANGE if(round(amount/12) > 0) - devastation = min (MAX_EX_DEVESTATION_RANGE, devastation + round(amount/12)) + devastation = min (MAX_EX_DEVASTATION_RANGE, devastation + round(amount/12)) if(round(amount/6) > 0) heavy = min (MAX_EX_HEAVY_RANGE, heavy + round(amount/6)) diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm index 87f489513d2..462a6d7eca7 100644 --- a/code/game/objects/explosion.dm +++ b/code/game/objects/explosion.dm @@ -11,7 +11,7 @@ if(!ignorecap) // Clamp all values to MAX_EXPLOSION_RANGE - devastation_range = min (MAX_EX_DEVESTATION_RANGE, devastation_range) + devastation_range = min (MAX_EX_DEVASTATION_RANGE, devastation_range) heavy_impact_range = min (MAX_EX_HEAVY_RANGE, heavy_impact_range) light_impact_range = min (MAX_EX_LIGHT_RANGE, light_impact_range) flash_range = min (MAX_EX_FLASH_RANGE, flash_range) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 6903930bee6..f5735548077 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -2723,15 +2723,15 @@ if(newBombCap > 128) newBombCap = 128 - MAX_EX_DEVESTATION_RANGE = round(newBombCap/4) + MAX_EX_DEVASTATION_RANGE = round(newBombCap/4) MAX_EX_HEAVY_RANGE = round(newBombCap/2) MAX_EX_LIGHT_RANGE = newBombCap //I don't know why these are their own variables, but fuck it, they are. MAX_EX_FLASH_RANGE = newBombCap MAX_EX_FLAME_RANGE = newBombCap - message_admins("[key_name_admin(usr)] changed the bomb cap to [MAX_EX_DEVESTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]") - log_admin("[key_name(usr)] changed the bomb cap to [MAX_EX_DEVESTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]") + message_admins("[key_name_admin(usr)] changed the bomb cap to [MAX_EX_DEVASTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]") + log_admin("[key_name(usr)] changed the bomb cap to [MAX_EX_DEVASTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]") if("flicklights") feedback_inc("admin_secrets_fun_used",1) From 99cc8b920e83a24a854de469b189babe10024c21 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Fri, 17 Jan 2020 08:32:24 -0500 Subject: [PATCH 089/238] malf ai --- .../antagonists/traitor/datum_traitor.dm | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index 9e6f1b79bfa..3cd7998edd8 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -152,8 +152,7 @@ var/objective_count = 0 var/try_again = TRUE - if(prob(30)) - objective_count += forge_single_objective() + objective_count += forge_single_objective() for(var/i = objective_count, i < config.traitor_objectives_amount) var/datum/objective/assassinate/kill_objective = new @@ -236,28 +235,9 @@ /datum/antagonist/traitor/proc/forge_single_AI_objective() . = 1 - var/special_pick = rand(1,2) - switch(special_pick) - if(1) // AI hijack - var/datum/objective/block/block_objective = new - block_objective.owner = owner - add_objective(block_objective) - if(2) // Protect and strand a target - var/datum/objective/protect/yandere_one = new - yandere_one.owner = owner - yandere_one.find_target() - - if("[yandere_one.target]" in assigned_targets) - return 0 - else if(yandere_one.target) - assigned_targets.Add("[yandere_one.target]") - - add_objective(yandere_one) - var/datum/objective/maroon/yandere_two = new - yandere_two.owner = owner - yandere_two.target = yandere_one.target - yandere_two.explanation_text = "Prevent [yandere_one.target], the [yandere_one.target.assigned_role] from escaping alive." - add_objective(yandere_two) + var/datum/objective/block/block_objective = new + block_objective.owner = owner + add_objective(block_objective) /datum/antagonist/traitor/greet() From 8393cc543608021fb333ee27bfc749d067f51cd2 Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Wed, 22 Jan 2020 22:38:03 +0100 Subject: [PATCH 090/238] Whispering now is logged in VV --- code/modules/mob/living/living_defines.dm | 1 + code/modules/mob/living/say.dm | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 74a51a771ac..7c8fac951f5 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -59,6 +59,7 @@ var/tesla_ignore = FALSE var/list/say_log = list() //a log of what we've said, plain text, no spans or junk, essentially just each individual "message" + var/list/whisper_log = list() //a log of what we've whispered, plain text, no spans or junk, essentially just each individual "message" var/list/recent_tastes = list() var/blood_volume = 0 //how much blood the mob has diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index be5187fac5b..d6fa02969b3 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -355,8 +355,10 @@ proc/get_radio_key_from_channel(var/channel) to_chat(src, "You're muzzled and cannot speak!") return - log_whisper(multilingual_to_message(message_pieces), src) + var/message = multilingual_to_message(message_pieces) + whisper_log += message + log_whisper(message, src) var/message_range = 1 var/eavesdropping_range = 2 var/watching_range = 5 @@ -439,7 +441,7 @@ proc/get_radio_key_from_channel(var/channel) //now mobs var/list/speech_bubble_recipients = list() - var/speech_bubble_test = say_test(multilingual_to_message(message_pieces)) + var/speech_bubble_test = say_test(message) for(var/mob/M in listening) M.hear_say(message_pieces, verb, italics, src) From a4a79c485b26f97b01350e1682582971bb18e8af Mon Sep 17 00:00:00 2001 From: ppi13 Date: Thu, 23 Jan 2020 23:58:28 +0100 Subject: [PATCH 091/238] Fixes Unknown game mode probability --- config/example/config.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/config/example/config.txt b/config/example/config.txt index f4de07f581f..18c280f6141 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -114,7 +114,6 @@ PROBABILITY RAGINMAGES 0 PROBABILITY METEOR 0 PROBABILITY HEIST 0 PROBABILITY VAMPIRE 3 -PROBABILITY NATIONS 0 PROBABILITY EXTENDED 2 PROBABILITY NUCLEAR 3 PROBABILITY ABDUCTION 0 From 5eb5b0939ac86551f40314a566cf865ce04a416b Mon Sep 17 00:00:00 2001 From: Qwertytoforty Date: Thu, 23 Jan 2020 20:39:58 -0500 Subject: [PATCH 092/238] Makes synthanol check for PROCESS_SYN instead of !M.isSynthetic --- .../reagents/chemistry/reagents/alcohol.dm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/code/modules/reagents/chemistry/reagents/alcohol.dm b/code/modules/reagents/chemistry/reagents/alcohol.dm index d4d2bbe2d88..6e27b822eba 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol.dm @@ -1190,15 +1190,17 @@ taste_description = "motor oil" /datum/reagent/consumable/ethanol/synthanol/on_mob_life(mob/living/M) - if(!M.isSynthetic()) - holder.remove_reagent(id, 3.6) //gets removed from organics very fast - if(prob(25)) - holder.remove_reagent(id, 15) - M.fakevomit() - return ..() + if(!(M.dna.species.reagent_tag & PROCESS_SYN)) + holder.remove_reagent(id, 3.6) //gets removed from organics very fast + if(prob(25)) + holder.remove_reagent(id, 15) + M.fakevomit() + + return ..() + /datum/reagent/consumable/ethanol/synthanol/reaction_mob(mob/living/M, method=TOUCH, volume) - if(M.isSynthetic()) + if(M.dna.species.reagent_tag & PROCESS_SYN) return if(method == INGEST) to_chat(M, pick("That was awful!", "Yuck!")) From 36d65344ab9f2d9376a32ab287f86d091bcb2daa Mon Sep 17 00:00:00 2001 From: Qwertytoforty Date: Thu, 23 Jan 2020 20:40:55 -0500 Subject: [PATCH 093/238] Revert "Makes synthanol check for PROCESS_SYN instead of !M.isSynthetic" This reverts commit 5eb5b0939ac86551f40314a566cf865ce04a416b. --- .../reagents/chemistry/reagents/alcohol.dm | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/code/modules/reagents/chemistry/reagents/alcohol.dm b/code/modules/reagents/chemistry/reagents/alcohol.dm index 6e27b822eba..d4d2bbe2d88 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol.dm @@ -1190,17 +1190,15 @@ taste_description = "motor oil" /datum/reagent/consumable/ethanol/synthanol/on_mob_life(mob/living/M) - if(!(M.dna.species.reagent_tag & PROCESS_SYN)) - holder.remove_reagent(id, 3.6) //gets removed from organics very fast - if(prob(25)) - holder.remove_reagent(id, 15) - M.fakevomit() - - return ..() - + if(!M.isSynthetic()) + holder.remove_reagent(id, 3.6) //gets removed from organics very fast + if(prob(25)) + holder.remove_reagent(id, 15) + M.fakevomit() + return ..() /datum/reagent/consumable/ethanol/synthanol/reaction_mob(mob/living/M, method=TOUCH, volume) - if(M.dna.species.reagent_tag & PROCESS_SYN) + if(M.isSynthetic()) return if(method == INGEST) to_chat(M, pick("That was awful!", "Yuck!")) From 64f9e1da097e3deae0fa76a08ef097fc25946d51 Mon Sep 17 00:00:00 2001 From: Qwertytoforty Date: Thu, 23 Jan 2020 20:47:05 -0500 Subject: [PATCH 094/238] Revert "Revert "Makes synthanol check for PROCESS_SYN instead of !M.isSynthetic"" This reverts commit 36d65344ab9f2d9376a32ab287f86d091bcb2daa. --- .../reagents/chemistry/reagents/alcohol.dm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/code/modules/reagents/chemistry/reagents/alcohol.dm b/code/modules/reagents/chemistry/reagents/alcohol.dm index d4d2bbe2d88..6e27b822eba 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol.dm @@ -1190,15 +1190,17 @@ taste_description = "motor oil" /datum/reagent/consumable/ethanol/synthanol/on_mob_life(mob/living/M) - if(!M.isSynthetic()) - holder.remove_reagent(id, 3.6) //gets removed from organics very fast - if(prob(25)) - holder.remove_reagent(id, 15) - M.fakevomit() - return ..() + if(!(M.dna.species.reagent_tag & PROCESS_SYN)) + holder.remove_reagent(id, 3.6) //gets removed from organics very fast + if(prob(25)) + holder.remove_reagent(id, 15) + M.fakevomit() + + return ..() + /datum/reagent/consumable/ethanol/synthanol/reaction_mob(mob/living/M, method=TOUCH, volume) - if(M.isSynthetic()) + if(M.dna.species.reagent_tag & PROCESS_SYN) return if(method == INGEST) to_chat(M, pick("That was awful!", "Yuck!")) From 0bf702513cd29cd029468ebc906476dd4bccf9a7 Mon Sep 17 00:00:00 2001 From: Qwertytoforty Date: Thu, 23 Jan 2020 20:39:58 -0500 Subject: [PATCH 095/238] Makes synthanol check for PROCESS_SYN instead of !M.isSynthetic --- .../reagents/chemistry/reagents/alcohol.dm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/code/modules/reagents/chemistry/reagents/alcohol.dm b/code/modules/reagents/chemistry/reagents/alcohol.dm index d4d2bbe2d88..6e27b822eba 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol.dm @@ -1190,15 +1190,17 @@ taste_description = "motor oil" /datum/reagent/consumable/ethanol/synthanol/on_mob_life(mob/living/M) - if(!M.isSynthetic()) - holder.remove_reagent(id, 3.6) //gets removed from organics very fast - if(prob(25)) - holder.remove_reagent(id, 15) - M.fakevomit() - return ..() + if(!(M.dna.species.reagent_tag & PROCESS_SYN)) + holder.remove_reagent(id, 3.6) //gets removed from organics very fast + if(prob(25)) + holder.remove_reagent(id, 15) + M.fakevomit() + + return ..() + /datum/reagent/consumable/ethanol/synthanol/reaction_mob(mob/living/M, method=TOUCH, volume) - if(M.isSynthetic()) + if(M.dna.species.reagent_tag & PROCESS_SYN) return if(method == INGEST) to_chat(M, pick("That was awful!", "Yuck!")) From a9712c0dde1cb3cf389485f217a6c5b9e035feb0 Mon Sep 17 00:00:00 2001 From: Qwertytoforty Date: Thu, 23 Jan 2020 20:40:55 -0500 Subject: [PATCH 096/238] Revert "Makes synthanol check for PROCESS_SYN instead of !M.isSynthetic" This reverts commit 5eb5b0939ac86551f40314a566cf865ce04a416b. --- .../reagents/chemistry/reagents/alcohol.dm | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/code/modules/reagents/chemistry/reagents/alcohol.dm b/code/modules/reagents/chemistry/reagents/alcohol.dm index 6e27b822eba..d4d2bbe2d88 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol.dm @@ -1190,17 +1190,15 @@ taste_description = "motor oil" /datum/reagent/consumable/ethanol/synthanol/on_mob_life(mob/living/M) - if(!(M.dna.species.reagent_tag & PROCESS_SYN)) - holder.remove_reagent(id, 3.6) //gets removed from organics very fast - if(prob(25)) - holder.remove_reagent(id, 15) - M.fakevomit() - - return ..() - + if(!M.isSynthetic()) + holder.remove_reagent(id, 3.6) //gets removed from organics very fast + if(prob(25)) + holder.remove_reagent(id, 15) + M.fakevomit() + return ..() /datum/reagent/consumable/ethanol/synthanol/reaction_mob(mob/living/M, method=TOUCH, volume) - if(M.dna.species.reagent_tag & PROCESS_SYN) + if(M.isSynthetic()) return if(method == INGEST) to_chat(M, pick("That was awful!", "Yuck!")) From 7327ead9649a1290c405153dc63280c843b85679 Mon Sep 17 00:00:00 2001 From: Qwertytoforty Date: Thu, 23 Jan 2020 20:47:05 -0500 Subject: [PATCH 097/238] Revert "Revert "Makes synthanol check for PROCESS_SYN instead of !M.isSynthetic"" This reverts commit 36d65344ab9f2d9376a32ab287f86d091bcb2daa. --- .../reagents/chemistry/reagents/alcohol.dm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/code/modules/reagents/chemistry/reagents/alcohol.dm b/code/modules/reagents/chemistry/reagents/alcohol.dm index d4d2bbe2d88..6e27b822eba 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol.dm @@ -1190,15 +1190,17 @@ taste_description = "motor oil" /datum/reagent/consumable/ethanol/synthanol/on_mob_life(mob/living/M) - if(!M.isSynthetic()) - holder.remove_reagent(id, 3.6) //gets removed from organics very fast - if(prob(25)) - holder.remove_reagent(id, 15) - M.fakevomit() - return ..() + if(!(M.dna.species.reagent_tag & PROCESS_SYN)) + holder.remove_reagent(id, 3.6) //gets removed from organics very fast + if(prob(25)) + holder.remove_reagent(id, 15) + M.fakevomit() + + return ..() + /datum/reagent/consumable/ethanol/synthanol/reaction_mob(mob/living/M, method=TOUCH, volume) - if(M.isSynthetic()) + if(M.dna.species.reagent_tag & PROCESS_SYN) return if(method == INGEST) to_chat(M, pick("That was awful!", "Yuck!")) From b5dcd830e77617fa89e427d10bc7569dd177df03 Mon Sep 17 00:00:00 2001 From: joep van der velden Date: Sat, 25 Jan 2020 02:01:01 +0100 Subject: [PATCH 098/238] Queen spit is now blockable --- .../mob/living/simple_animal/hostile/terror_spiders/queen.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm index d7d41912233..af4fda64625 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm @@ -335,8 +335,8 @@ damage_type = TOX var/bonus_tox = 30 -/obj/item/projectile/terrorqueenspit/on_hit(mob/living/carbon/target) - if(ismob(target)) +/obj/item/projectile/terrorqueenspit/on_hit(mob/living/carbon/target, blocked = 0, hit_zone) + if(ismob(target) && blocked < 100) var/mob/living/L = target if(L.reagents) if(L.can_inject(null, FALSE, "chest", FALSE)) From f83c62d24136b168233b860ef4eb108ba7611992 Mon Sep 17 00:00:00 2001 From: AffectedArc07 Date: Tue, 28 Jan 2020 17:37:31 +0000 Subject: [PATCH 099/238] Updates our gitignore file --- .gitignore | 12 +++++++++++- .../midi2piano.csprojAssemblyReference.cache | Bin 0 -> 13166 bytes 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tools/midi2piano/midi2piano/obj/x86/Debug/midi2piano.csprojAssemblyReference.cache diff --git a/.gitignore b/.gitignore index a4634f3fc84..977bd775a41 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,14 @@ data/ *.db stddef.dm .atom-build.json -*.vscode/* \ No newline at end of file +*.vscode/* + +# ignore DMI tool build cache +/tools/dmitool/bin/ +/tools/dmitool/.gradle/ +/tools/dmitool/.settings/ +/tools/dmitool/.classpath +/tools/dmitool/.project + +# ignore midi2piano build cache +/tools/midi2piano/obj/* \ No newline at end of file diff --git a/tools/midi2piano/midi2piano/obj/x86/Debug/midi2piano.csprojAssemblyReference.cache b/tools/midi2piano/midi2piano/obj/x86/Debug/midi2piano.csprojAssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..9f1ef9ceccec4c675307fbda8a2cf1100394e100 GIT binary patch literal 13166 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

QsFsIEwO*1m{v!h2U7G zx)7Yz_=Vu$rm7GeTvQi=(+s~59Bot;a&$@|PX~k`Yi3Bo zG;;TE0I~A#`IB-%PSX#>D1ROc{9v<6uF!oDD|<2|jq9TJ0g14>PcBRh=_-r=S2>B>$XYo*)3P$6I( Date: Tue, 28 Jan 2020 21:25:20 +0000 Subject: [PATCH 100/238] Replaces all var/const with #define --- .../components/binary_devices/circulator.dm | 6 +- code/LINDA/LINDA_system.dm | 27 +- code/__DEFINES/access.dm | 111 ++++++ code/__DEFINES/job.dm | 64 +++ code/__DEFINES/js.dm | 91 +++++ code/__DEFINES/machines.dm | 4 + code/__DEFINES/misc.dm | 17 +- code/__DEFINES/reagents.dm | 3 + code/__HELPERS/constants.dm | 4 +- code/__HELPERS/maths.dm | 4 +- code/__HELPERS/mobs.dm | 2 +- code/_onclick/telekinesis.dm | 4 +- code/datums/ai_laws.dm | 2 +- .../diseases/advance/symptoms/symptoms.dm | 2 - code/datums/outfits/outfit_admin.dm | 22 +- code/datums/supplypacks.dm | 106 ++--- code/datums/wires/airlock.dm | 20 +- code/datums/wires/alarm.dm | 10 +- code/datums/wires/apc.dm | 8 +- code/datums/wires/autolathe.dm | 6 +- code/datums/wires/camera.dm | 4 +- code/datums/wires/explosive.dm | 2 +- code/datums/wires/mulebot.dm | 60 +-- code/datums/wires/nuclearbomb.dm | 6 +- code/datums/wires/particle_accelerator.dm | 8 +- code/datums/wires/radio.dm | 40 +- code/datums/wires/robot.dm | 18 +- code/datums/wires/smartfridge.dm | 6 +- code/datums/wires/suitstorage.dm | 8 +- code/datums/wires/syndicatebomb.dm | 38 +- code/datums/wires/tesla_coil.dm | 6 +- code/datums/wires/vending.dm | 8 +- code/datums/wires/wires.dm | 14 +- code/game/area/areas/depot-areas.dm | 4 +- code/game/atoms_movable.dm | 2 +- code/game/gamemodes/blob/powers.dm | 2 +- code/game/gamemodes/devil/devilinfo.dm | 2 +- .../gamemodes/miniantags/abduction/gland.dm | 2 +- code/game/gamemodes/nuclear/nuclear.dm | 2 +- code/game/gamemodes/wizard/artefact.dm | 4 +- code/game/jobs/access.dm | 374 ++++++------------ code/game/jobs/job/central.dm | 8 +- code/game/jobs/job/civilian.dm | 6 +- code/game/jobs/job/engineering.dm | 44 +-- code/game/jobs/job/medical.dm | 72 ++-- code/game/jobs/job/science.dm | 36 +- code/game/jobs/job/security.dm | 60 +-- code/game/jobs/job/silicon.dm | 8 +- code/game/jobs/job/supervisor.dm | 106 ++--- code/game/jobs/job/support.dm | 88 ++--- code/game/jobs/job/support_chaplain.dm | 8 +- code/game/jobs/job/syndicate.dm | 4 +- code/game/jobs/jobs.dm | 58 --- code/game/machinery/ai_slipper.dm | 2 +- code/game/machinery/alarm.dm | 4 +- code/game/machinery/cloning.dm | 2 +- code/game/machinery/computer/aifixer.dm | 2 +- code/game/machinery/computer/atmos_control.dm | 2 +- code/game/machinery/computer/brigcells.dm | 2 +- .../game/machinery/computer/buildandrepair.dm | 2 +- code/game/machinery/computer/camera.dm | 42 +- code/game/machinery/computer/card.dm | 16 +- code/game/machinery/computer/cloning.dm | 2 +- .../game/machinery/computer/communications.dm | 6 +- code/game/machinery/computer/depot.dm | 4 +- code/game/machinery/computer/honkputer.dm | 2 +- code/game/machinery/computer/medical.dm | 2 +- code/game/machinery/computer/pod.dm | 2 +- .../computer/pod_tracking_console.dm | 2 +- code/game/machinery/computer/prisoner.dm | 2 +- code/game/machinery/computer/robot.dm | 2 +- code/game/machinery/computer/salvage_ship.dm | 2 +- code/game/machinery/computer/security.dm | 2 +- code/game/machinery/computer/skills.dm | 2 +- .../machinery/computer/specops_shuttle.dm | 2 +- .../computer/syndicate_specops_shuttle.dm | 2 +- code/game/machinery/cryo.dm | 2 +- code/game/machinery/cryopod.dm | 2 +- code/game/machinery/defib_mount.dm | 2 +- .../machinery/doors/airlock_electronics.dm | 2 +- code/game/machinery/doors/airlock_types.dm | 2 +- code/game/machinery/doors/brigdoors.dm | 2 +- code/game/machinery/doors/firedoor.dm | 3 - code/game/machinery/doors/windowdoor.dm | 2 +- code/game/machinery/guestpass.dm | 2 +- code/game/machinery/hologram.dm | 2 +- code/game/machinery/navbeacon.dm | 2 +- code/game/machinery/pipe/construction.dm | 6 +- code/game/machinery/portable_tag_turret.dm | 4 +- code/game/machinery/portable_turret.dm | 8 +- code/game/machinery/recycler.dm | 2 +- code/game/machinery/requests_console.dm | 2 +- code/game/machinery/shieldgen.dm | 4 +- code/game/machinery/suit_storage_unit.dm | 30 +- code/game/machinery/syndicatebomb.dm | 2 +- code/game/machinery/telecomms/logbrowser.dm | 2 +- .../machinery/telecomms/traffic_control.dm | 2 +- code/game/machinery/turret_control.dm | 4 +- code/game/machinery/vending.dm | 4 +- code/game/mecha/combat/gygax.dm | 2 +- code/game/mecha/combat/honker.dm | 6 +- code/game/mecha/combat/marauder.dm | 6 +- code/game/mecha/combat/reticence.dm | 2 +- .../mecha/equipment/tools/medical_tools.dm | 6 +- code/game/mecha/mech_fabricator.dm | 4 +- code/game/mecha/mecha.dm | 2 +- code/game/mecha/mecha_control_console.dm | 2 +- code/game/mecha/mecha_topic.dm | 4 +- code/game/objects/effects/anomalies.dm | 2 +- .../effects/effect_system/effects_foam.dm | 4 +- code/game/objects/effects/mines.dm | 6 +- .../effects/spawners/airlock_spawner.dm | 22 +- code/game/objects/items/control_wand.dm | 2 +- code/game/objects/items/dehy_carp.dm | 2 +- .../objects/items/devices/radio/intercom.dm | 6 +- .../game/objects/items/devices/radio/radio.dm | 38 +- code/game/objects/items/random_items.dm | 6 +- .../objects/items/stacks/sheets/leather.dm | 2 +- .../objects/items/stacks/sheets/mineral.dm | 2 +- .../objects/items/stacks/tiles/tile_types.dm | 2 +- code/game/objects/items/weapons/RCD.dm | 2 +- code/game/objects/items/weapons/cards_ids.dm | 56 +-- .../items/weapons/grenades/atmosgrenade.dm | 6 +- code/game/objects/items/weapons/mop.dm | 2 +- code/game/objects/items/weapons/paint.dm | 2 +- .../objects/items/weapons/storage/fancy.dm | 2 +- .../objects/items/weapons/storage/firstaid.dm | 4 +- .../objects/items/weapons/storage/lockbox.dm | 10 +- code/game/objects/objs.dm | 2 +- code/game/objects/structures/barsign.dm | 4 +- .../crates_lockers/closets/secure/bar.dm | 2 +- .../crates_lockers/closets/secure/cargo.dm | 4 +- .../crates_lockers/closets/secure/chaplain.dm | 2 +- .../crates_lockers/closets/secure/depot.dm | 2 +- .../closets/secure/engineering.dm | 10 +- .../crates_lockers/closets/secure/freezer.dm | 4 +- .../closets/secure/guncabinet.dm | 2 +- .../closets/secure/hydroponics.dm | 2 +- .../crates_lockers/closets/secure/medical.dm | 22 +- .../crates_lockers/closets/secure/miscjobs.dm | 6 +- .../crates_lockers/closets/secure/personal.dm | 2 +- .../closets/secure/scientist.dm | 8 +- .../crates_lockers/closets/secure/security.dm | 30 +- code/game/objects/structures/displaycase.dm | 6 +- code/game/objects/structures/false_walls.dm | 2 +- code/game/objects/structures/mineral_doors.dm | 2 +- code/game/objects/structures/morgue.dm | 2 +- code/game/objects/structures/statues.dm | 2 +- code/game/turfs/simulated/floor/mineral.dm | 2 +- code/game/turfs/simulated/walls_mineral.dm | 2 +- code/js/byjax.dm | 50 --- code/js/menus.dm | 37 -- code/modules/admin/secrets.dm | 2 +- code/modules/admin/topic.dm | 12 +- code/modules/admin/verbs/honksquad.dm | 8 +- .../admin/verbs/infiltratorteam_syndicate.dm | 4 +- code/modules/admin/verbs/striketeam.dm | 6 +- .../admin/verbs/striketeam_syndicate.dm | 6 +- code/modules/awaymissions/corpse.dm | 2 +- code/modules/awaymissions/exile.dm | 2 +- .../mission_code/UO71-terrorspiders.dm | 2 +- .../mission_code/ruins/oldstation.dm | 4 +- code/modules/client/preference/preferences.dm | 28 +- code/modules/clothing/masks/miscellaneous.dm | 2 +- .../spacesuits/rig/suits/ert_suits.dm | 2 +- .../clothing/spacesuits/rig/suits/light.dm | 6 +- .../clothing/spacesuits/rig/suits/station.dm | 6 +- .../clothing/under/accessories/accessory.dm | 2 +- .../detective_work/footprints_and_rag.dm | 2 +- code/modules/economy/Accounts_DB.dm | 6 +- code/modules/economy/EFTPOS.dm | 2 +- code/modules/economy/POS.dm | 4 +- .../food_and_drinks/drinks/drinks/bottle.dm | 2 +- .../drinks/drinks/shotglass.dm | 2 +- code/modules/food_and_drinks/food.dm | 2 +- .../modules/food_and_drinks/food/condiment.dm | 2 +- .../food_and_drinks/food/foods/meat.dm | 4 +- .../kitchen_machinery/smartfridge.dm | 6 +- code/modules/hydroponics/plant_genes.dm | 2 +- code/modules/mining/laborcamp/laborshuttle.dm | 2 +- .../mining/lavaland/loot/tendril_loot.dm | 2 +- code/modules/mining/machine_redemption.dm | 8 +- code/modules/mining/machine_vending.dm | 2 +- code/modules/mining/mine_items.dm | 2 +- .../living/carbon/alien/special/facehugger.dm | 8 +- .../mob/living/carbon/brain/robotic_brain.dm | 2 +- .../mob/living/carbon/carbon_defense.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 2 +- .../mob/living/carbon/human/human_defense.dm | 2 +- .../living/carbon/human/species/_species.dm | 2 +- .../mob/living/carbon/human/species/grey.dm | 4 +- code/modules/mob/living/carbon/superheroes.dm | 4 +- code/modules/mob/living/living_defense.dm | 2 +- .../living/silicon/ai/freelook/cameranet.dm | 2 +- code/modules/mob/living/silicon/ai/say.dm | 4 +- code/modules/mob/living/silicon/laws.dm | 2 +- .../mob/living/silicon/robot/drone/drone.dm | 4 +- .../silicon/robot/drone/drone_console.dm | 2 +- .../modules/mob/living/silicon/robot/robot.dm | 6 +- .../mob/living/silicon/robot/syndicate.dm | 2 +- .../mob/living/simple_animal/bot/bot.dm | 2 +- .../mob/living/simple_animal/bot/cleanbot.dm | 2 +- .../living/simple_animal/bot/construction.dm | 4 +- .../mob/living/simple_animal/bot/ed209bot.dm | 2 +- .../mob/living/simple_animal/bot/floorbot.dm | 2 +- .../mob/living/simple_animal/bot/griefsky.dm | 2 +- .../mob/living/simple_animal/bot/honkbot.dm | 2 +- .../mob/living/simple_animal/bot/medbot.dm | 6 +- .../mob/living/simple_animal/bot/mulebot.dm | 2 +- .../mob/living/simple_animal/bot/secbot.dm | 2 +- .../mob/living/simple_animal/bot/syndicate.dm | 2 +- .../mob/living/simple_animal/corpse.dm | 4 +- .../simple_animal/friendly/farm_animals.dm | 2 +- .../simple_animal/friendly/spiderbot.dm | 2 +- .../mob/living/simple_animal/hostile/bees.dm | 4 +- .../simple_animal/hostile/mining/hivelord.dm | 2 +- .../mob/living/simple_animal/slime/slime.dm | 2 +- .../mob/new_player/preferences_setup.dm | 82 ++-- .../modular_computers/documentation.md | 4 +- .../file_system/programs/command/card.dm | 4 +- .../file_system/programs/command/comms.dm | 8 +- .../programs/engineering/power_monitor.dm | 2 +- .../programs/engineering/sm_monitor.dm | 2 +- .../programs/generic/ntnrc_client.dm | 2 +- .../programs/research/airestorer.dm | 2 +- .../programs/research/ntmonitor.dm | 2 +- code/modules/paperwork/faxmachine.dm | 4 +- code/modules/power/apc.dm | 4 +- code/modules/power/gravitygenerator.dm | 38 +- code/modules/power/singularity/collector.dm | 2 +- code/modules/power/singularity/emitter.dm | 2 +- code/modules/reagents/chem_splash.dm | 2 +- code/modules/reagents/chemistry/holder.dm | 8 +- code/modules/reagents/chemistry/reagents.dm | 6 +- .../reagents/chemistry/reagents/alcohol.dm | 12 +- .../reagents/chemistry/reagents/blob.dm | 30 +- .../reagents/chemistry/reagents/drinks.dm | 4 +- .../reagents/chemistry/reagents/drugs.dm | 8 +- .../reagents/chemistry/reagents/food.dm | 24 +- .../reagents/chemistry/reagents/medicine.dm | 26 +- .../reagents/chemistry/reagents/misc.dm | 14 +- .../chemistry/reagents/pyrotechnic.dm | 38 +- .../reagents/chemistry/reagents/toxins.dm | 36 +- .../reagents/chemistry/reagents/water.dm | 24 +- .../chemistry/recipes/slime_extracts.dm | 2 +- .../reagents/reagent_containers/dropper.dm | 6 +- .../reagent_containers/glass_containers.dm | 8 +- .../reagents/reagent_containers/iv_bag.dm | 2 +- .../reagents/reagent_containers/patch.dm | 2 +- .../reagents/reagent_containers/syringes.dm | 2 +- code/modules/research/designs.dm | 2 +- .../research/designs/weapon_designs.dm | 2 +- code/modules/research/rdconsole.dm | 6 +- code/modules/research/server.dm | 2 +- .../ruins/lavalandruin_code/syndicate_base.dm | 2 +- .../security_levels/keycard authentication.dm | 2 +- code/modules/shuttle/emergency.dm | 2 +- code/modules/shuttle/ert.dm | 2 +- code/modules/shuttle/navigation_computer.dm | 4 +- code/modules/shuttle/shuttle.dm | 4 +- code/modules/shuttle/supply.dm | 9 +- code/modules/shuttle/syndicate.dm | 8 +- code/modules/shuttle/vox.dm | 2 +- code/modules/surgery/dental_implant.dm | 2 +- code/modules/surgery/organs_internal.dm | 4 +- code/modules/surgery/other.dm | 4 +- code/modules/telesci/telesci_computer.dm | 2 +- paradise.dme | 5 +- .../midi2piano.csprojAssemblyReference.cache | Bin 0 -> 13166 bytes 269 files changed, 1481 insertions(+), 1474 deletions(-) create mode 100644 code/__DEFINES/access.dm create mode 100644 code/__DEFINES/job.dm create mode 100644 code/__DEFINES/js.dm delete mode 100644 code/js/byjax.dm delete mode 100644 code/js/menus.dm create mode 100644 tools/midi2piano/midi2piano/obj/x86/Debug/midi2piano.csprojAssemblyReference.cache diff --git a/code/ATMOSPHERICS/components/binary_devices/circulator.dm b/code/ATMOSPHERICS/components/binary_devices/circulator.dm index e2dd952213a..a8bd792daa0 100644 --- a/code/ATMOSPHERICS/components/binary_devices/circulator.dm +++ b/code/ATMOSPHERICS/components/binary_devices/circulator.dm @@ -1,5 +1,8 @@ //node1, air1, network1 correspond to input //node2, air2, network2 correspond to output +#define CIRC_LEFT WEST +#define CIRC_RIGHT EAST + /obj/machinery/atmospherics/binary/circulator name = "circulator/heat exchanger" desc = "A gas circulator pump and heat exchanger. Its input port is on the south side, and its output port is on the north side." @@ -8,9 +11,6 @@ var/side = CIRC_LEFT - var/global/const/CIRC_LEFT = WEST - var/global/const/CIRC_RIGHT = EAST - var/last_pressure_delta = 0 var/obj/machinery/power/generator/generator diff --git a/code/LINDA/LINDA_system.dm b/code/LINDA/LINDA_system.dm index c9d168a0159..fd24dcc0c77 100644 --- a/code/LINDA/LINDA_system.dm +++ b/code/LINDA/LINDA_system.dm @@ -142,44 +142,33 @@ turf/CanPass(atom/movable/mover, turf/target, height=1.5) return T.atmos_spawn_air(text, amount) -var/const/SPAWN_HEAT = 1 -var/const/SPAWN_20C = 2 -var/const/SPAWN_TOXINS = 4 -var/const/SPAWN_OXYGEN = 8 -var/const/SPAWN_CO2 = 16 -var/const/SPAWN_NITROGEN = 32 - -var/const/SPAWN_N2O = 64 - -var/const/SPAWN_AIR = 256 - /turf/simulated/proc/atmos_spawn_air(var/flag, var/amount) if(!text || !amount || !air) return var/datum/gas_mixture/G = new - if(flag & SPAWN_20C) + if(flag & LINDA_SPAWN_20C) G.temperature = T20C - if(flag & SPAWN_HEAT) + if(flag & LINDA_SPAWN_HEAT) G.temperature += 1000 - if(flag & SPAWN_TOXINS) + if(flag & LINDA_SPAWN_TOXINS) G.toxins += amount - if(flag & SPAWN_OXYGEN) + if(flag & LINDA_SPAWN_OXYGEN) G.oxygen += amount - if(flag & SPAWN_CO2) + if(flag & LINDA_SPAWN_CO2) G.carbon_dioxide += amount - if(flag & SPAWN_NITROGEN) + if(flag & LINDA_SPAWN_NITROGEN) G.nitrogen += amount - if(flag & SPAWN_N2O) + if(flag & LINDA_SPAWN_N2O) var/datum/gas/sleeping_agent/T = new T.moles += amount G.trace_gases += T - if(flag & SPAWN_AIR) + if(flag & LINDA_SPAWN_AIR) G.oxygen += MOLES_O2STANDARD * amount G.nitrogen += MOLES_N2STANDARD * amount diff --git a/code/__DEFINES/access.dm b/code/__DEFINES/access.dm new file mode 100644 index 00000000000..55ea1d663f8 --- /dev/null +++ b/code/__DEFINES/access.dm @@ -0,0 +1,111 @@ +#define ACCESS_SECURITY 1 // Security equipment +#define ACCESS_BRIG 2 // Brig timers and permabrig +#define ACCESS_ARMORY 3 +#define ACCESS_FORENSICS_LOCKERS 4 +#define ACCESS_MEDICAL 5 +#define ACCESS_MORGUE 6 +#define ACCESS_TOX 7 +#define ACCESS_TOX_STORAGE 8 +#define ACCESS_GENETICS 9 +#define ACCESS_ENGINE 10 +#define ACCESS_ENGINE_EQUIP 11 +#define ACCESS_MAINT_TUNNELS 12 +#define ACCESS_EXTERNAL_AIRLOCKS 13 +#define ACCESS_EMERGENCY_STORAGE 14 +#define ACCESS_CHANGE_IDS 15 +#define ACCESS_AI_UPLOAD 16 +#define ACCESS_TELEPORTER 17 +#define ACCESS_EVA 18 +#define ACCESS_HEADS 19 +#define ACCESS_CAPTAIN 20 +#define ACCESS_ALL_PERSONAL_LOCKERS 21 +#define ACCESS_CHAPEL_OFFICE 22 +#define ACCESS_TECH_STORAGE 23 +#define ACCESS_ATMOSPHERICS 24 +#define ACCESS_BAR 25 +#define ACCESS_JANITOR 26 +#define ACCESS_CREMATORIUM 27 +#define ACCESS_KITCHEN 28 +#define ACCESS_ROBOTICS 29 +#define ACCESS_RD 30 +#define ACCESS_CARGO 31 +#define ACCESS_CONSTRUCTION 32 +#define ACCESS_CHEMISTRY 33 +#define ACCESS_CARGO_BOT 34 +#define ACCESS_HYDROPONICS 35 +#define ACCESS_MANUFACTURING 36 +#define ACCESS_LIBRARY 37 +#define ACCESS_LAWYER 38 +#define ACCESS_VIROLOGY 39 +#define ACCESS_CMO 40 +#define ACCESS_QM 41 +#define ACCESS_COURT 42 +#define ACCESS_CLOWN 43 +#define ACCESS_MIME 44 +#define ACCESS_SURGERY 45 +#define ACCESS_THEATRE 46 +#define ACCESS_RESEARCH 47 +#define ACCESS_MINING 48 +#define ACCESS_MINING_OFFICE 49 //not in use +#define ACCESS_MAILSORTING 50 +#define ACCESS_MINT 51 +#define ACCESS_MINT_VAULT 52 +#define ACCESS_HEADS_VAULT 53 +#define ACCESS_MINING_STATION 54 +#define ACCESS_XENOBIOLOGY 55 +#define ACCESS_CE 56 +#define ACCESS_HOP 57 +#define ACCESS_HOS 58 +#define ACCESS_RC_ANNOUNCE 59 //Request console announcements +#define ACCESS_KEYCARD_AUTH 60 //Used for events which require at least two people to confirm them +#define ACCESS_TCOMSAT 61 // has access to the entire telecomms satellite / machinery +#define ACCESS_GATEWAY 62 +#define ACCESS_SEC_DOORS 63 // Security front doors +#define ACCESS_PSYCHIATRIST 64 // Psychiatrist's office +#define ACCESS_XENOARCH 65 +#define ACCESS_PARAMEDIC 66 +#define ACCESS_BLUESHIELD 67 +#define ACCESS_SALVAGE_CAPTAIN 69 // Salvage ship captain's quarters +#define ACCESS_MECHANIC 70 +#define ACCESS_PILOT 71 +#define ACCESS_NTREP 73 +#define ACCESS_MAGISTRATE 74 +#define ACCESS_MINISAT 75 +#define ACCESS_MINERAL_STOREROOM 76 +#define ACCESS_NETWORK 77 + +#define ACCESS_WEAPONS 99 //Weapon authorization for secbots + + //BEGIN CENTCOM ACCESS +#define ACCESS_CENT_GENERAL 101//General facilities. +#define ACCESS_CENT_LIVING 102//Living quarters. +#define ACCESS_CENT_MEDICAL 103//Medical. +#define ACCESS_CENT_SECURITY 104//Security. +#define ACCESS_CENT_STORAGE 105//Storage areas. +#define ACCESS_CENT_SHUTTLES 106//Shuttle docks. +#define ACCESS_CENT_TELECOMMS 107//Telecomms. +#define ACCESS_CENT_TELEPORTER 108//Teleporter +#define ACCESS_CENT_SPECOPS 109//Special Ops. +#define ACCESS_CENT_SPECOPS_COMMANDER 110//Special Ops Commander. +#define ACCESS_CENT_BLACKOPS 111//Black Ops. +#define ACCESS_CENT_THUNDER 112//Thunderdome. +#define ACCESS_CENT_BRIDGE 113//Bridge. +#define ACCESS_CENT_COMMANDER 114//Commander's Office/ID computer. + +//The Syndicate +#define ACCESS_SYNDICATE 150//General Syndicate Access +#define ACCESS_SYNDICATE_LEADER 151//Nuke Op Leader Access +#define ACCESS_VOX 152//Vox Access +#define ACCESS_SYNDICATE_COMMAND 153//Admin syndi officer + +//Trade Stations +#define ACCESS_TRADE_SOL 160 + +//MONEY +#define ACCESS_CRATE_CASH 200 + +//Awaymissions +#define ACCESS_AWAY01 271 + +//Ghost roles +#define ACCESS_FREE_GOLEMS 300 \ No newline at end of file diff --git a/code/__DEFINES/job.dm b/code/__DEFINES/job.dm new file mode 100644 index 00000000000..ff7dda4fa94 --- /dev/null +++ b/code/__DEFINES/job.dm @@ -0,0 +1,64 @@ +/////////////////////////////// +// WARNING // +//////////////////////////////////////////////////////////////////////// +// Do NOT touch the values associated with these defines, as they are // +// used by the game database to keep track of job flags. Do NOT touch // +//////////////////////////////////////////////////////////////////////// + +#define JOBCAT_ENGSEC (1<<0) + +#define JOB_CAPTAIN (1<<0) +#define JOB_HOS (1<<1) +#define JOB_WARDEN (1<<2) +#define JOB_DETECTIVE (1<<3) +#define JOB_OFFICER (1<<4) +#define JOB_CHIEF (1<<5) +#define JOB_ENGINEER (1<<6) +#define JOB_ATMOSTECH (1<<7) +#define JOB_AI (1<<8) +#define JOB_CYBORG (1<<9) +#define JOB_CENTCOM (1<<10) +#define JOB_SYNDICATE (1<<11) + +#define JOBCAT_MEDSCI (1<<1) + +#define JOB_RD (1<<0) +#define JOB_SCIENTIST (1<<1) +#define JOB_CHEMIST (1<<2) +#define JOB_CMO (1<<3) +#define JOB_DOCTOR (1<<4) +#define JOB_GENETICIST (1<<5) +#define JOB_VIROLOGIST (1<<6) +#define JOB_PSYCHIATRIST (1<<7) +#define JOB_ROBOTICIST (1<<8) +#define JOB_PARAMEDIC (1<<9) +#define JOB_CORONER (1<<10) + + +#define JOBCAT_SUPPORT (1<<2) + +#define JOB_HOP (1<<0) +#define JOB_BARTENDER (1<<1) +#define JOB_BOTANIST (1<<2) +#define JOB_CHEF (1<<3) +#define JOB_JANITOR (1<<4) +#define JOB_LIBRARIAN (1<<5) +#define JOB_QUARTERMASTER (1<<6) +#define JOB_CARGOTECH (1<<7) +#define JOB_MINER (1<<8) +#define JOB_LAWYER (1<<9) +#define JOB_CHAPLAIN (1<<10) +#define JOB_CLOWN (1<<11) +#define JOB_MIME (1<<12) +#define JOB_CIVILIAN (1<<13) + + +#define JOBCAT_KARMA (1<<3) + +#define JOB_NANO (1<<0) +#define JOB_BLUESHIELD (1<<1) +#define JOB_BARBER (1<<3) +#define JOB_MECHANIC (1<<4) +#define JOB_BRIGDOC (1<<5) +#define JOB_JUDGE (1<<6) +#define JOB_PILOT (1<<7) diff --git a/code/__DEFINES/js.dm b/code/__DEFINES/js.dm new file mode 100644 index 00000000000..65b70e04d63 --- /dev/null +++ b/code/__DEFINES/js.dm @@ -0,0 +1,91 @@ +//this function places received data into element with specified id. +#define JS_BYJAX {" + +function replaceContent() { + var args = Array.prototype.slice.call(arguments); + var id = args\[0\]; + var content = args\[1\]; + var callback = null; + if(args\[2\]){ + callback = args\[2\]; + if(args\[3\]){ + args = args.slice(3); + } + } + var parent = document.getElementById(id); + if(typeof(parent)!=='undefined' && parent!=null){ + parent.innerHTML = content?content:''; + } + if(callback && window\[callback\]){ + window\[callback\].apply(null,args); + } +} +"} + +/* +sends data to control_id:replaceContent + +receiver - mob +control_id - window id (for windows opened with browse(), it'll be "windowname.browser") +target_element - HTML element id +new_content - HTML content +callback - js function that will be called after the data is sent +callback_args - arguments for callback function + +Be sure to include required js functions in your page, or it'll raise an exception. + +And yes I know this is a proc in a defines file, but its highly relevant so it can be here +*/ +proc/send_byjax(receiver, control_id, target_element, new_content=null, callback=null, list/callback_args=null) + if(receiver && target_element && control_id) // && winexists(receiver, control_id)) + var/list/argums = list(target_element, new_content) + if(callback) + argums += callback + if(callback_args) + argums += callback_args + argums = list2params(argums) +/* if(callback_args) + argums += "&[list2params(callback_args)]" +*/ + receiver << output(argums,"[control_id]:replaceContent") + return + + +// Misc JS for some dropdowns +#define JS_DROPDOWNS {" +function dropdowns() { + var divs = document.getElementsByTagName('div'); + var headers = new Array(); + var links = new Array(); + for(var i=0;i=0) { + elem.className = elem.className.replace('visible','hidden'); + this.className = this.className.replace('open','closed'); + this.innerHTML = this.innerHTML.replace('-','+'); + } + else { + elem.className = elem.className.replace('hidden','visible'); + this.className = this.className.replace('closed','open'); + this.innerHTML = this.innerHTML.replace('+','-'); + } + return false; + } + })(links\[i\]); + } + } +} +"} \ No newline at end of file diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm index 7a691c44df2..b56024bfab8 100644 --- a/code/__DEFINES/machines.dm +++ b/code/__DEFINES/machines.dm @@ -88,3 +88,7 @@ #define SUPERMATTER_DANGER 4 // Integrity < 75% #define SUPERMATTER_EMERGENCY 5 // Integrity < 50% #define SUPERMATTER_DELAMINATING 6 // Pretty obvious, Integrity < 25% + +// Firelock states +#define FD_OPEN 1 +#define FD_CLOSED 2 diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 6e1fa5e01fd..69e89020431 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -419,4 +419,19 @@ #define ENGTYPE_TESLA "Tesla" #define SUMMON_GUNS "guns" -#define SUMMON_MAGIC "magic" \ No newline at end of file +#define SUMMON_MAGIC "magic" + +// Medical stuff +#define SYMPTOM_ACTIVATION_PROB 3 + +// Atmos stuff that fucking terrifies me +#define LINDA_SPAWN_HEAT 1 +#define LINDA_SPAWN_20C 2 +#define LINDA_SPAWN_TOXINS 4 +#define LINDA_SPAWN_OXYGEN 8 +#define LINDA_SPAWN_CO2 16 +#define LINDA_SPAWN_NITROGEN 32 + +#define LINDA_SPAWN_N2O 64 + +#define LINDA_SPAWN_AIR 256 \ No newline at end of file diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index e2dd3ee3ac7..ce4767f8b89 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -16,3 +16,6 @@ // Is an open container for all intents and purposes. #define OPENCONTAINER (REFILLABLE | DRAINABLE | TRANSPARENT) + +#define REAGENT_TOUCH 1 +#define REAGENT_INGEST 2 \ No newline at end of file diff --git a/code/__HELPERS/constants.dm b/code/__HELPERS/constants.dm index f7db3166605..3b801a541e1 100644 --- a/code/__HELPERS/constants.dm +++ b/code/__HELPERS/constants.dm @@ -1,2 +1,2 @@ -var/global/const/TICKS_IN_DAY = 864000 -var/global/const/TICKS_IN_SECOND = 10 \ No newline at end of file +#define TICKS_IN_DAY 864000 +#define TICKS_IN_SECOND 10 \ No newline at end of file diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm index ec9fcb5fb31..8491182ab9a 100644 --- a/code/__HELPERS/maths.dm +++ b/code/__HELPERS/maths.dm @@ -1,7 +1,7 @@ // Credits to Nickr5 for the useful procs I've taken from his library resource. -var/const/E = 2.71828183 -var/const/Sqrt2 = 1.41421356 +#define MATH_E 2.71828183 +#define SQRT2 1.41421356 /proc/Atan2(x, y) if(!x && !y) return 0 diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 7c61cf832b5..994602828a0 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -248,7 +248,7 @@ proc/age2agedescription(age) if("none") status = "None" if("execute") - if((access_magistrate in authcard_access) || (access_armory in authcard_access)) + if((ACCESS_MAGISTRATE in authcard_access) || (ACCESS_ARMORY in authcard_access)) status = "*Execute*" message_admins("[ADMIN_FULLMONTY(usr)] authorized EXECUTION for [their_rank] [their_name], with comment: [comment]") else diff --git a/code/_onclick/telekinesis.dm b/code/_onclick/telekinesis.dm index 6ef7a797136..e684707d460 100644 --- a/code/_onclick/telekinesis.dm +++ b/code/_onclick/telekinesis.dm @@ -3,7 +3,7 @@ This needs more thinking out, but I might as well. */ -var/const/tk_maxrange = 15 +#define TK_MAXRANGE 15 /* Telekinetic attack: @@ -118,7 +118,7 @@ var/const/tk_maxrange = 15 var/d = get_dist(user, target) if(focus) d = max(d,get_dist(user,focus)) // whichever is further - if(d > tk_maxrange) + if(d > TK_MAXRANGE) to_chat(user, "Your mind won't reach that far.") return diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm index 3494d7b20d5..47a8122d163 100644 --- a/code/datums/ai_laws.dm +++ b/code/datums/ai_laws.dm @@ -1,4 +1,4 @@ -var/global/const/base_law_type = /datum/ai_laws/nanotrasen +#define BASE_LAW_TYPE /datum/ai_laws/nanotrasen /datum/ai_law var/law = "" diff --git a/code/datums/diseases/advance/symptoms/symptoms.dm b/code/datums/diseases/advance/symptoms/symptoms.dm index 74da571836b..f2c5beee400 100644 --- a/code/datums/diseases/advance/symptoms/symptoms.dm +++ b/code/datums/diseases/advance/symptoms/symptoms.dm @@ -2,8 +2,6 @@ var/list/list_symptoms = subtypesof(/datum/symptom) -var/global/const/SYMPTOM_ACTIVATION_PROB = 3 - /datum/symptom // Buffs/Debuffs the symptom has to the overall engineered disease. var/name = "" diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm index 93e4015233b..d2c19f61bfa 100644 --- a/code/datums/outfits/outfit_admin.dm +++ b/code/datums/outfits/outfit_admin.dm @@ -291,7 +291,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_maint_tunnels), name) + apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), name) /datum/outfit/admin/pirate/first_mate name = "Space Pirate First Mate" @@ -375,7 +375,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_clown, access_theatre, access_maint_tunnels), "Tunnel Clown") + apply_to_card(I, H, list(ACCESS_CLOWN, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS), "Tunnel Clown") /datum/outfit/admin/mime_assassin name = "Mime Assassin" @@ -424,7 +424,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_mime, access_theatre, access_maint_tunnels), "Mime") + apply_to_card(I, H, list(ACCESS_MIME, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS), "Mime") H.sec_hud_set_ID() /datum/outfit/admin/greytide @@ -450,7 +450,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_maint_tunnels), "Greytide") + apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Greytide") /datum/outfit/admin/greytide/leader name = "Greytide Leader" @@ -471,7 +471,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_maint_tunnels), "Greytide Leader") + apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Greytide Leader") /datum/outfit/admin/greytide/xeno name = "Greytide Xeno" @@ -499,7 +499,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_maint_tunnels), "Legit Xenomorph") + apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Legit Xenomorph") @@ -534,7 +534,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_maint_tunnels), "Bard") + apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Bard") var/obj/item/clothing/ears/headphones/P = r_ear if(istype(P)) @@ -555,7 +555,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_maint_tunnels), name) + apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), name) /datum/outfit/admin/soviet/tourist name = "Soviet Tourist" @@ -719,7 +719,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_trade_sol, access_maint_tunnels, access_external_airlocks), name) + apply_to_card(I, H, list(ACCESS_TRADE_SOL, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS), name) /datum/outfit/admin/chrono name = "Chrono Legionnaire" @@ -907,7 +907,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_maint_tunnels), "Survivor") + apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Survivor") /datum/outfit/admin/masked_killer name = "Masked Killer" @@ -942,7 +942,7 @@ var/obj/item/card/id/I = H.wear_id if(istype(I)) - apply_to_card(I, H, list(access_maint_tunnels), "Masked Killer", "syndie") + apply_to_card(I, H, list(ACCESS_MAINT_TUNNELS), "Masked Killer", "syndie") /datum/outfit/admin/singuloth_knight name = "Singuloth Knight" diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm index 8e20ebb7a93..f17e9840e4f 100644 --- a/code/datums/supplypacks.dm +++ b/code/datums/supplypacks.dm @@ -6,37 +6,37 @@ //NEW NOTE: Do NOT set the price of any crates below 7 points. Doing so allows infinite points. // Supply Groups -var/const/supply_emergency = 1 -var/const/supply_security = 2 -var/const/supply_engineer = 3 -var/const/supply_medical = 4 -var/const/supply_science = 5 -var/const/supply_organic = 6 -var/const/supply_materials = 7 -var/const/supply_misc = 8 -var/const/supply_vend = 9 +#define SUPPLY_EMERGENCY 1 +#define SUPPLY_SECURITY 2 +#define SUPPLY_ENGINEER 3 +#define SUPPLY_MEDICAL 4 +#define SUPPLY_SCIENCE 5 +#define SUPPLY_ORGANIC 6 +#define SUPPLY_MATERIALS 7 +#define SUPPLY_MISC 8 +#define SUPPLY_VEND 9 -var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engineer,supply_medical,supply_science,supply_organic,supply_materials,supply_misc,supply_vend) +var/list/all_supply_groups = list(SUPPLY_EMERGENCY,SUPPLY_SECURITY,SUPPLY_ENGINEER,SUPPLY_MEDICAL,SUPPLY_SCIENCE,SUPPLY_ORGANIC,SUPPLY_MATERIALS,SUPPLY_MISC,SUPPLY_VEND) /proc/get_supply_group_name(var/cat) switch(cat) - if(1) + if(SUPPLY_EMERGENCY) return "Emergency" - if(2) + if(SUPPLY_SECURITY) return "Security" - if(3) + if(SUPPLY_ENGINEER) return "Engineering" - if(4) + if(SUPPLY_MEDICAL) return "Medical" - if(5) + if(SUPPLY_SCIENCE) return "Science" - if(6) + if(SUPPLY_ORGANIC) return "Food and Livestock" - if(7) + if(SUPPLY_MATERIALS) return "Raw Materials" - if(8) + if(SUPPLY_MISC) return "Miscellaneous" - if(9) + if(SUPPLY_VEND) return "Vending" @@ -51,7 +51,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine var/access = null var/hidden = 0 var/contraband = 0 - var/group = supply_misc + var/group = SUPPLY_MISC var/list/announce_beacons = list() // Particular beacons that we'll notify the relevant department when we reach var/special = FALSE //Event/Station Goals/Admin enabled packs var/special_enabled = FALSE @@ -74,7 +74,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/emergency // Section header - use these to set default supply group and crate type for sections name = "HEADER" // Use "HEADER" to denote section headers, this is needed for the supply computers to filter them containertype = /obj/structure/closet/crate/internals - group = supply_emergency + group = SUPPLY_EMERGENCY /datum/supply_packs/emergency/evac @@ -98,7 +98,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 35 containertype = /obj/structure/closet/crate/internals containername = "emergency crate" - group = supply_emergency + group = SUPPLY_EMERGENCY /datum/supply_packs/emergency/internals name = "Internals Crate" @@ -135,7 +135,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 10 containertype = /obj/structure/closet/crate/secure containername = "firefighting watertank crate" - access = access_atmospherics + access = ACCESS_ATMOSPHERICS /datum/supply_packs/emergency/weedcontrol name = "Weed Control Crate" @@ -146,7 +146,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 15 containertype = /obj/structure/closet/crate/secure/hydrosec containername = "weed control crate" - access = access_hydroponics + access = ACCESS_HYDROPONICS announce_beacons = list("Hydroponics" = list("Hydroponics")) /datum/supply_packs/emergency/voxsupport @@ -172,7 +172,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 20 containertype = /obj/structure/closet/crate/secure/plasma containername = "plasmaman life support supplies crate" - access = access_eva + access = ACCESS_EVA /datum/supply_packs/emergency/specialops name = "Special Ops Supplies" @@ -202,8 +202,8 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/security name = "HEADER" containertype = /obj/structure/closet/crate/secure/gear - access = access_security - group = supply_security + access = ACCESS_SECURITY + group = SUPPLY_SECURITY announce_beacons = list("Security" = list("Head of Security's Desk", "Warden", "Security")) @@ -299,7 +299,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/security/armory name = "HEADER" containertype = /obj/structure/closet/crate/secure/weapon - access = access_armory + access = ACCESS_ARMORY announce_beacons = list("Security" = list("Warden", "Head of Security's Desk")) ///// Armor: Specialist @@ -442,7 +442,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 15 // its a fecking flamethrower and some plasma, why the shit did this cost so much before!? containertype = /obj/structure/closet/crate/secure/plasma containername = "incendiary weapons crate" - access = access_heads + access = ACCESS_HEADS /datum/supply_packs/security/armory/wt550 name = "WT-550 Auto Rifle Crate" @@ -534,7 +534,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/engineering name = "HEADER" - group = supply_engineer + group = SUPPLY_ENGINEER announce_beacons = list("Engineering" = list("Engineering", "Chief Engineer's Desk")) containertype = /obj/structure/closet/crate/engineering @@ -635,7 +635,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 10 containertype = /obj/structure/closet/crate/secure containername = "emitter crate" - access = access_ce + access = ACCESS_CE containertype = /obj/structure/closet/crate/secure/engineering /datum/supply_packs/engineering/engine/field_gen @@ -703,7 +703,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 30 containertype = /obj/structure/closet/crate/secure containername = "space suit crate" - access = access_eva + access = ACCESS_EVA /datum/supply_packs/engineering/inflatable name = "Inflatable barriers Crate" @@ -719,7 +719,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 50 //So cargo thinks twice before killing themselves with it containertype = /obj/structure/closet/crate/secure containername = "supermatter shard crate" - access = access_ce + access = ACCESS_CE /datum/supply_packs/engineering/engine/teg name = "Thermo-Electric Generator Crate" @@ -730,7 +730,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 25 containertype = /obj/structure/closet/crate/secure containername = "thermo-electric generator crate" - access = access_ce + access = ACCESS_CE announce_beacons = list("Engineering" = list("Chief Engineer's Desk", "Atmospherics")) /datum/supply_packs/engineering/conveyor @@ -753,7 +753,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/medical name = "HEADER" containertype = /obj/structure/closet/crate/medical - group = supply_medical + group = SUPPLY_MEDICAL announce_beacons = list("Medbay" = list("Medbay", "Chief Medical Officer's Desk"), "Security" = list("Brig Medbay")) @@ -858,7 +858,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 25 containertype = /obj/structure/closet/crate/secure/plasma containername = "virus crate" - access = access_cmo + access = ACCESS_CMO announce_beacons = list("Medbay" = list("Virology", "Chief Medical Officer's Desk")) /datum/supply_packs/medical/vending @@ -888,7 +888,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 30 containertype = /obj/structure/closet/crate/secure containername = "IV drip crate" - access = access_medical + access = ACCESS_MEDICAL /datum/supply_packs/medical/surgery name = "Surgery Crate" @@ -906,7 +906,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 25 containertype = /obj/structure/closet/crate/secure containername = "surgery crate" - access = access_medical + access = ACCESS_MEDICAL ////////////////////////////////////////////////////////////////////////////// @@ -915,7 +915,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/science name = "HEADER" - group = supply_science + group = SUPPLY_SCIENCE announce_beacons = list("Research Division" = list("Science", "Research Director's Desk")) containertype = /obj/structure/closet/crate/sci @@ -931,7 +931,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 10 containertype = /obj/structure/closet/crate/secure/scisec containername = "robotics assembly crate" - access = access_robotics + access = ACCESS_ROBOTICS announce_beacons = list("Research Division" = list("Robotics", "Research Director's Desk")) @@ -967,8 +967,8 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 10 containertype = /obj/structure/closet/crate/secure/plasma containername = "plasma assembly crate" - access = access_tox_storage - group = supply_science + access = ACCESS_TOX_STORAGE + group = SUPPLY_SCIENCE /datum/supply_packs/science/shieldwalls name = "Shield Generators Crate" @@ -979,7 +979,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 20 containertype = /obj/structure/closet/crate/secure/scisec containername = "shield generators crate" - access = access_teleporter + access = ACCESS_TELEPORTER /datum/supply_packs/science/modularpc name = "Deluxe Silicate Selections restocking unit" @@ -994,7 +994,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 60 containertype = /obj/structure/closet/crate/secure/scisec containername = "tank transfer valves crate" - access = access_rd + access = ACCESS_RD /datum/supply_packs/science/prototype name = "Machine Prototype Crate" @@ -1002,7 +1002,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 80 containertype = /obj/structure/closet/crate/secure/scisec containername = "machine prototype crate" - access = access_research + access = ACCESS_RESEARCH /datum/supply_packs/science/oil name = "Oil Tank Crate" @@ -1018,7 +1018,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/organic name = "HEADER" - group = supply_organic + group = SUPPLY_ORGANIC containertype = /obj/structure/closet/crate/freezer @@ -1210,7 +1210,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 10 containertype = /obj/structure/closet/crate/secure containername = "hydroponics watertank crate" - access = access_hydroponics + access = ACCESS_HYDROPONICS announce_beacons = list("Hydroponics" = list("Hydroponics")) /datum/supply_packs/organic/hydroponics/seeds @@ -1296,7 +1296,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/materials name = "HEADER" - group = supply_materials + group = SUPPLY_MATERIALS announce_beacons = list("Engineering" = list("Engineering", "Chief Engineer's Desk", "Atmospherics")) @@ -1363,7 +1363,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/misc name = "HEADER" - group = supply_misc + group = SUPPLY_MISC /datum/supply_packs/misc/mule name = "MULEbot Crate" @@ -1421,7 +1421,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/misc/minerkit name = "Shaft Miner Starter Kit" cost = 30 - access = access_qm + access = ACCESS_QM contains = list(/obj/item/storage/backpack/duffel/mining_conscript) containertype = /obj/structure/closet/crate/secure containername = "shaft miner starter kit" @@ -1550,7 +1550,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 10 containertype = /obj/structure/closet/crate/secure containername = "janitor watertank crate" - access = access_janitor + access = ACCESS_JANITOR /datum/supply_packs/misc/janitor/lightbulbs name = "Replacement Lights Crate" @@ -1588,7 +1588,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine cost = 10 containertype = /obj/structure/closet/crate/secure containername = "standard costumes" - access = access_theatre + access = ACCESS_THEATRE /datum/supply_packs/misc/wizard name = "Wizard Costume Crate" @@ -1855,7 +1855,7 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine /datum/supply_packs/vending name = "HEADER" - group = supply_vend + group = SUPPLY_VEND /datum/supply_packs/vending/autodrobe name = "Autodrobe Supply Crate" diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm index 3e9f488bb29..e3d90b8d075 100644 --- a/code/datums/wires/airlock.dm +++ b/code/datums/wires/airlock.dm @@ -9,16 +9,16 @@ window_x = 410 window_y = 570 -var/const/AIRLOCK_WIRE_IDSCAN = 1 -var/const/AIRLOCK_WIRE_MAIN_POWER1 = 2 -var/const/AIRLOCK_WIRE_DOOR_BOLTS = 4 -var/const/AIRLOCK_WIRE_BACKUP_POWER1 = 8 -var/const/AIRLOCK_WIRE_OPEN_DOOR = 16 -var/const/AIRLOCK_WIRE_AI_CONTROL = 32 -var/const/AIRLOCK_WIRE_ELECTRIFY = 64 -var/const/AIRLOCK_WIRE_SAFETY = 128 -var/const/AIRLOCK_WIRE_SPEED = 256 -var/const/AIRLOCK_WIRE_LIGHT = 512 +#define AIRLOCK_WIRE_IDSCAN 1 +#define AIRLOCK_WIRE_MAIN_POWER1 2 +#define AIRLOCK_WIRE_DOOR_BOLTS 4 +#define AIRLOCK_WIRE_BACKUP_POWER1 8 +#define AIRLOCK_WIRE_OPEN_DOOR 16 +#define AIRLOCK_WIRE_AI_CONTROL 32 +#define AIRLOCK_WIRE_ELECTRIFY 64 +#define AIRLOCK_WIRE_SAFETY 128 +#define AIRLOCK_WIRE_SPEED 256 +#define AIRLOCK_WIRE_LIGHT 512 /datum/wires/airlock/GetWireName(index) switch(index) diff --git a/code/datums/wires/alarm.dm b/code/datums/wires/alarm.dm index f412e96b5ee..e29a40240c5 100644 --- a/code/datums/wires/alarm.dm +++ b/code/datums/wires/alarm.dm @@ -3,11 +3,11 @@ holder_type = /obj/machinery/alarm wire_count = 5 -var/const/AALARM_WIRE_IDSCAN = 1 -var/const/AALARM_WIRE_POWER = 2 -var/const/AALARM_WIRE_SYPHON = 4 -var/const/AALARM_WIRE_AI_CONTROL = 8 -var/const/AALARM_WIRE_AALARM = 16 +#define AALARM_WIRE_IDSCAN 1 +#define AALARM_WIRE_POWER 2 +#define AALARM_WIRE_SYPHON 4 +#define AALARM_WIRE_AI_CONTROL 8 +#define AALARM_WIRE_AALARM 16 /datum/wires/alarm/GetWireName(index) switch(index) diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm index df014edd4f4..8c1108a3b1b 100644 --- a/code/datums/wires/apc.dm +++ b/code/datums/wires/apc.dm @@ -2,10 +2,10 @@ holder_type = /obj/machinery/power/apc wire_count = 4 -var/const/APC_WIRE_IDSCAN = 1 -var/const/APC_WIRE_MAIN_POWER1 = 2 -var/const/APC_WIRE_MAIN_POWER2 = 4 -var/const/APC_WIRE_AI_CONTROL = 8 +#define APC_WIRE_IDSCAN 1 +#define APC_WIRE_MAIN_POWER1 2 +#define APC_WIRE_MAIN_POWER2 4 +#define APC_WIRE_AI_CONTROL 8 /datum/wires/apc/GetWireName(index) switch(index) diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm index a901695c880..f0cb85b6d52 100644 --- a/code/datums/wires/autolathe.dm +++ b/code/datums/wires/autolathe.dm @@ -2,9 +2,9 @@ holder_type = /obj/machinery/autolathe wire_count = 10 -var/const/AUTOLATHE_HACK_WIRE = 1 -var/const/AUTOLATHE_SHOCK_WIRE = 2 -var/const/AUTOLATHE_DISABLE_WIRE = 4 +#define AUTOLATHE_HACK_WIRE 1 +#define AUTOLATHE_SHOCK_WIRE 2 +#define AUTOLATHE_DISABLE_WIRE 4 /datum/wires/autolathe/GetWireName(index) switch(index) diff --git a/code/datums/wires/camera.dm b/code/datums/wires/camera.dm index 609dce59e84..f5f0c5df633 100644 --- a/code/datums/wires/camera.dm +++ b/code/datums/wires/camera.dm @@ -17,8 +17,8 @@ return FALSE return TRUE -var/const/CAMERA_WIRE_FOCUS = 1 -var/const/CAMERA_WIRE_POWER = 2 +#define CAMERA_WIRE_FOCUS 1 +#define CAMERA_WIRE_POWER 2 /datum/wires/camera/GetWireName(index) switch(index) diff --git a/code/datums/wires/explosive.dm b/code/datums/wires/explosive.dm index 045923c7dcf..47fd627d597 100644 --- a/code/datums/wires/explosive.dm +++ b/code/datums/wires/explosive.dm @@ -1,7 +1,7 @@ /datum/wires/explosive wire_count = 1 -var/const/WIRE_EXPLODE = 1 +#define WIRE_EXPLODE 1 /datum/wires/explosive/GetWireName(index) switch(index) diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index cbc92497f33..0b970d38e5d 100644 --- a/code/datums/wires/mulebot.dm +++ b/code/datums/wires/mulebot.dm @@ -4,43 +4,43 @@ wire_count = 10 window_x = 410 -var/const/WIRE_POWER1 = 1 // power connections -var/const/WIRE_POWER2 = 2 -var/const/WIRE_AVOIDANCE = 4 // mob avoidance -var/const/WIRE_LOADCHECK = 8 // load checking (non-crate) -var/const/WIRE_MOTOR1 = 16 // motor wires -var/const/WIRE_MOTOR2 = 32 // -var/const/WIRE_REMOTE_RX = 64 // remote recv functions -var/const/WIRE_REMOTE_TX = 128 // remote trans status -var/const/WIRE_BEACON_RX = 256 // beacon ping recv +#define MULEBOT_WIRE_POWER1 1 // power connections +#define MULEBOT_WIRE_POWER2 2 +#define MULEBOT_WIRE_AVOIDANCE 4 // mob avoidance +#define MULEBOT_WIRE_LOADCHECK 8 // load checking (non-crate) +#define MULEBOT_WIRE_MOTOR1 16 // motor wires +#define MULEBOT_WIRE_MOTOR2 32 // +#define MULEBOT_WIRE_REMOTE_RX 64 // remote recv functions +#define MULEBOT_WIRE_REMOTE_TX 128 // remote trans status +#define MULEBOT_WIRE_BEACON_RX 256 // beacon ping recv /datum/wires/mulebot/GetWireName(index) switch(index) - if(WIRE_POWER1) + if(MULEBOT_WIRE_POWER1) return "Primary Power" - if(WIRE_POWER2) + if(MULEBOT_WIRE_POWER2) return "Secondary Power" - if(WIRE_AVOIDANCE) + if(MULEBOT_WIRE_AVOIDANCE) return "Mob Avoidance" - if(WIRE_LOADCHECK) + if(MULEBOT_WIRE_LOADCHECK) return "Load Checking" - if(WIRE_MOTOR1) + if(MULEBOT_WIRE_MOTOR1) return "Primary Motor" - if(WIRE_MOTOR2) + if(MULEBOT_WIRE_MOTOR2) return "Secondary Motor" - if(WIRE_REMOTE_RX) + if(MULEBOT_WIRE_REMOTE_RX) return "Remote Signal Receiver" - if(WIRE_REMOTE_TX) + if(MULEBOT_WIRE_REMOTE_TX) return "Remote Signal Sender" - if(WIRE_BEACON_RX) + if(MULEBOT_WIRE_BEACON_RX) return "Navigation Beacon Receiver" /datum/wires/mulebot/CanUse(mob/living/L) @@ -51,13 +51,13 @@ var/const/WIRE_BEACON_RX = 256 // beacon ping recv /datum/wires/mulebot/UpdatePulsed(index) switch(index) - if(WIRE_POWER1, WIRE_POWER2) + if(MULEBOT_WIRE_POWER1, MULEBOT_WIRE_POWER2) holder.visible_message("[bicon(holder)] The charge light flickers.") - if(WIRE_AVOIDANCE) + if(MULEBOT_WIRE_AVOIDANCE) holder.visible_message("[bicon(holder)] The external warning lights flash briefly.") - if(WIRE_LOADCHECK) + if(MULEBOT_WIRE_LOADCHECK) holder.visible_message("[bicon(holder)] The load platform clunks.") - if(WIRE_MOTOR1, WIRE_MOTOR2) + if(MULEBOT_WIRE_MOTOR1, MULEBOT_WIRE_MOTOR2) holder.visible_message("[bicon(holder)] The drive motor whines briefly.") else holder.visible_message("[bicon(holder)] You hear a radio crackle.") @@ -66,25 +66,25 @@ var/const/WIRE_BEACON_RX = 256 // beacon ping recv // HELPER PROCS /datum/wires/mulebot/proc/Motor1() - return !(wires_status & WIRE_MOTOR1) + return !(wires_status & MULEBOT_WIRE_MOTOR1) /datum/wires/mulebot/proc/Motor2() - return !(wires_status & WIRE_MOTOR2) + return !(wires_status & MULEBOT_WIRE_MOTOR2) /datum/wires/mulebot/proc/HasPower() - return !(wires_status & WIRE_POWER1) && !(wires_status & WIRE_POWER2) + return !(wires_status & MULEBOT_WIRE_POWER1) && !(wires_status & MULEBOT_WIRE_POWER2) /datum/wires/mulebot/proc/LoadCheck() - return !(wires_status & WIRE_LOADCHECK) + return !(wires_status & MULEBOT_WIRE_LOADCHECK) /datum/wires/mulebot/proc/MobAvoid() - return !(wires_status & WIRE_AVOIDANCE) + return !(wires_status & MULEBOT_WIRE_AVOIDANCE) /datum/wires/mulebot/proc/RemoteTX() - return !(wires_status & WIRE_REMOTE_TX) + return !(wires_status & MULEBOT_WIRE_REMOTE_TX) /datum/wires/mulebot/proc/RemoteRX() - return !(wires_status & WIRE_REMOTE_RX) + return !(wires_status & MULEBOT_WIRE_REMOTE_RX) /datum/wires/mulebot/proc/BeaconRX() - return !(wires_status & WIRE_BEACON_RX) \ No newline at end of file + return !(wires_status & MULEBOT_WIRE_BEACON_RX) \ No newline at end of file diff --git a/code/datums/wires/nuclearbomb.dm b/code/datums/wires/nuclearbomb.dm index 5b704a4526b..83fa911506d 100644 --- a/code/datums/wires/nuclearbomb.dm +++ b/code/datums/wires/nuclearbomb.dm @@ -3,9 +3,9 @@ random = 1 wire_count = 7 -var/const/NUCLEARBOMB_WIRE_LIGHT = 1 -var/const/NUCLEARBOMB_WIRE_TIMING = 2 -var/const/NUCLEARBOMB_WIRE_SAFETY = 4 +#define NUCLEARBOMB_WIRE_LIGHT 1 +#define NUCLEARBOMB_WIRE_TIMING 2 +#define NUCLEARBOMB_WIRE_SAFETY 4 /datum/wires/nuclearbomb/GetWireName(index) switch(index) diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm index 1ed68b799f4..64705e6c215 100644 --- a/code/datums/wires/particle_accelerator.dm +++ b/code/datums/wires/particle_accelerator.dm @@ -2,10 +2,10 @@ wire_count = 5 holder_type = /obj/machinery/particle_accelerator/control_box -var/const/PARTICLE_TOGGLE_WIRE = 1 // Toggles whether the PA is on or not. -var/const/PARTICLE_STRENGTH_WIRE = 2 // Determines the strength of the PA. -var/const/PARTICLE_INTERFACE_WIRE = 4 // Determines the interface showing up. -var/const/PARTICLE_LIMIT_POWER_WIRE = 8 // Determines how strong the PA can be. +#define PARTICLE_TOGGLE_WIRE 1 // Toggles whether the PA is on or not. +#define PARTICLE_STRENGTH_WIRE 2 // Determines the strength of the PA. +#define PARTICLE_INTERFACE_WIRE 4 // Determines the interface showing up. +#define PARTICLE_LIMIT_POWER_WIRE 8 // Determines how strong the PA can be. /datum/wires/particle_acc/control_box/GetWireName(index) switch(index) diff --git a/code/datums/wires/radio.dm b/code/datums/wires/radio.dm index 18a2ef73f65..90a72935c53 100644 --- a/code/datums/wires/radio.dm +++ b/code/datums/wires/radio.dm @@ -2,19 +2,19 @@ holder_type = /obj/item/radio wire_count = 3 -var/const/WIRE_SIGNAL = 1 -var/const/WIRE_RECEIVE = 2 -var/const/WIRE_TRANSMIT = 4 +#define RADIO_WIRE_SIGNAL 1 +#define RADIO_WIRE_RECEIVE 2 +#define RADIO_WIRE_TRANSMIT 4 /datum/wires/radio/GetWireName(index) switch(index) - if(WIRE_SIGNAL) + if(RADIO_WIRE_SIGNAL) return "Signal" - if(WIRE_RECEIVE) + if(RADIO_WIRE_RECEIVE) return "Receiver" - if(WIRE_TRANSMIT) + if(RADIO_WIRE_TRANSMIT) return "Transmitter" /datum/wires/radio/CanUse(mob/living/L) @@ -26,27 +26,27 @@ var/const/WIRE_TRANSMIT = 4 /datum/wires/radio/UpdatePulsed(index) var/obj/item/radio/R = holder switch(index) - if(WIRE_SIGNAL) - R.listening = !R.listening && !IsIndexCut(WIRE_RECEIVE) - R.broadcasting = R.listening && !IsIndexCut(WIRE_TRANSMIT) + if(RADIO_WIRE_SIGNAL) + R.listening = !R.listening && !IsIndexCut(RADIO_WIRE_RECEIVE) + R.broadcasting = R.listening && !IsIndexCut(RADIO_WIRE_TRANSMIT) - if(WIRE_RECEIVE) - R.listening = !R.listening && !IsIndexCut(WIRE_SIGNAL) + if(RADIO_WIRE_RECEIVE) + R.listening = !R.listening && !IsIndexCut(RADIO_WIRE_SIGNAL) - if(WIRE_TRANSMIT) - R.broadcasting = !R.broadcasting && !IsIndexCut(WIRE_SIGNAL) + if(RADIO_WIRE_TRANSMIT) + R.broadcasting = !R.broadcasting && !IsIndexCut(RADIO_WIRE_SIGNAL) ..() /datum/wires/radio/UpdateCut(index, mended) var/obj/item/radio/R = holder switch(index) - if(WIRE_SIGNAL) - R.listening = mended && !IsIndexCut(WIRE_RECEIVE) - R.broadcasting = mended && !IsIndexCut(WIRE_TRANSMIT) + if(RADIO_WIRE_SIGNAL) + R.listening = mended && !IsIndexCut(RADIO_WIRE_RECEIVE) + R.broadcasting = mended && !IsIndexCut(RADIO_WIRE_TRANSMIT) - if(WIRE_RECEIVE) - R.listening = mended && !IsIndexCut(WIRE_SIGNAL) + if(RADIO_WIRE_RECEIVE) + R.listening = mended && !IsIndexCut(RADIO_WIRE_SIGNAL) - if(WIRE_TRANSMIT) - R.broadcasting = mended && !IsIndexCut(WIRE_SIGNAL) + if(RADIO_WIRE_TRANSMIT) + R.broadcasting = mended && !IsIndexCut(RADIO_WIRE_SIGNAL) ..() diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm index bc03e80c327..f55cee5951f 100644 --- a/code/datums/wires/robot.dm +++ b/code/datums/wires/robot.dm @@ -3,21 +3,13 @@ holder_type = /mob/living/silicon/robot wire_count = 5 -/* /tg/ ordering doesn't work for us, we need lawsync at the end for MoMMIs. -var/const/BORG_WIRE_LAWCHECK = 1 -var/const/BORG_WIRE_MAIN_POWER = 2 // The power wires do nothing whyyyyyyyyyyyyy -var/const/BORG_WIRE_LOCKED_DOWN = 4 -var/const/BORG_WIRE_AI_CONTROL = 8 -var/const/BORG_WIRE_CAMERA = 16 -*/ - // /vg/ ordering -var/const/BORG_WIRE_MAIN_POWER = 1 // The power wires do nothing whyyyyyyyyyyyyy -var/const/BORG_WIRE_LOCKED_DOWN = 2 -var/const/BORG_WIRE_CAMERA = 4 -var/const/BORG_WIRE_AI_CONTROL = 8 // Not used on MoMMIs -var/const/BORG_WIRE_LAWCHECK = 16 // Not used on MoMMIs +#define BORG_WIRE_MAIN_POWER 1 // The power wires do nothing whyyyyyyyyyyyyy +#define BORG_WIRE_LOCKED_DOWN 2 +#define BORG_WIRE_CAMERA 4 +#define BORG_WIRE_AI_CONTROL 8 // Not used on MoMMIs +#define BORG_WIRE_LAWCHECK 16 // Not used on MoMMIs /datum/wires/robot/GetWireName(index) switch(index) diff --git a/code/datums/wires/smartfridge.dm b/code/datums/wires/smartfridge.dm index 53b33e0fd72..a65377ead1a 100644 --- a/code/datums/wires/smartfridge.dm +++ b/code/datums/wires/smartfridge.dm @@ -6,9 +6,9 @@ random = 1 wire_count = 4 -var/const/SMARTFRIDGE_WIRE_ELECTRIFY = 1 -var/const/SMARTFRIDGE_WIRE_THROW = 2 -var/const/SMARTFRIDGE_WIRE_IDSCAN = 4 +#define SMARTFRIDGE_WIRE_ELECTRIFY 1 +#define SMARTFRIDGE_WIRE_THROW 2 +#define SMARTFRIDGE_WIRE_IDSCAN 4 /datum/wires/smartfridge/GetWireName(index) switch(index) diff --git a/code/datums/wires/suitstorage.dm b/code/datums/wires/suitstorage.dm index b519b449c86..ddd0fda2bb6 100644 --- a/code/datums/wires/suitstorage.dm +++ b/code/datums/wires/suitstorage.dm @@ -2,10 +2,10 @@ holder_type = /obj/machinery/suit_storage_unit wire_count = 8 -var/const/SSU_WIRE_ID = 1 -var/const/SSU_WIRE_SHOCK = 2 -var/const/SSU_WIRE_SAFETY = 4 -var/const/SSU_WIRE_UV = 8 +#define SSU_WIRE_ID 1 +#define SSU_WIRE_SHOCK 2 +#define SSU_WIRE_SAFETY 4 +#define SSU_WIRE_UV 8 /datum/wires/suitstorage/GetWireName(index) diff --git a/code/datums/wires/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm index 616429b8cd8..27ea28a90ad 100644 --- a/code/datums/wires/syndicatebomb.dm +++ b/code/datums/wires/syndicatebomb.dm @@ -3,27 +3,27 @@ holder_type = /obj/machinery/syndicatebomb wire_count = 5 -var/const/WIRE_BOOM = 1 // Explodes if pulsed or cut while active, defuses a bomb that isn't active on cut -var/const/WIRE_UNBOLT = 2 // Unbolts the bomb if cut, hint on pulsed -var/const/WIRE_DELAY = 4 // Raises the timer on pulse, does nothing on cut -var/const/WIRE_PROCEED = 8 // Lowers the timer, explodes if cut while the bomb is active -var/const/WIRE_ACTIVATE = 16 // Will start a bombs timer if pulsed, will hint if pulsed while already active, will stop a timer a bomb on cut +#define BOMB_WIRE_BOOM 1 // Explodes if pulsed or cut while active, defuses a bomb that isn't active on cut +#define BOMB_WIRE_UNBOLT 2 // Unbolts the bomb if cut, hint on pulsed +#define BOMB_WIRE_DELAY 4 // Raises the timer on pulse, does nothing on cut +#define BOMB_WIRE_PROCEED 8 // Lowers the timer, explodes if cut while the bomb is active +#define BOMB_WIRE_ACTIVATE 16 // Will start a bombs timer if pulsed, will hint if pulsed while already active, will stop a timer a bomb on cut /datum/wires/syndicatebomb/GetWireName(index) switch(index) - if(WIRE_BOOM) + if(BOMB_WIRE_BOOM) return "Explode" - if(WIRE_UNBOLT) + if(BOMB_WIRE_UNBOLT) return "Unbolt" - if(WIRE_DELAY) + if(BOMB_WIRE_DELAY) return "Delay" - if(WIRE_PROCEED) + if(BOMB_WIRE_PROCEED) return "Proceed" - if(WIRE_ACTIVATE) + if(BOMB_WIRE_ACTIVATE) return "Activate" /datum/wires/syndicatebomb/CanUse(mob/living/L) @@ -35,13 +35,13 @@ var/const/WIRE_ACTIVATE = 16 // Will start a bombs timer if pulsed, will hint if /datum/wires/syndicatebomb/UpdatePulsed(index) var/obj/machinery/syndicatebomb/B = holder switch(index) - if(WIRE_BOOM) + if(BOMB_WIRE_BOOM) if(B.active) holder.visible_message("[bicon(B)] An alarm sounds! It's go-") B.explode_now = TRUE - if(WIRE_UNBOLT) + if(BOMB_WIRE_UNBOLT) holder.visible_message("[bicon(holder)] The bolts spin in place for a moment.") - if(WIRE_DELAY) + if(BOMB_WIRE_DELAY) if(B.delayedbig) holder.visible_message("[bicon(B)] The bomb has already been delayed.") else @@ -49,7 +49,7 @@ var/const/WIRE_ACTIVATE = 16 // Will start a bombs timer if pulsed, will hint if playsound(B, 'sound/machines/chime.ogg', 30, 1) B.detonation_timer += 300 B.delayedbig = TRUE - if(WIRE_PROCEED) + if(BOMB_WIRE_PROCEED) holder.visible_message("[bicon(B)] The bomb buzzes ominously!") playsound(B, 'sound/machines/buzz-sigh.ogg', 30, 1) var/seconds = B.seconds_remaining() @@ -59,7 +59,7 @@ var/const/WIRE_ACTIVATE = 16 // Will start a bombs timer if pulsed, will hint if B.detonation_timer -= 100 else if(seconds >= 11) // Both to prevent negative timers and to have a little mercy. B.detonation_timer = world.time + 100 - if(WIRE_ACTIVATE) + if(BOMB_WIRE_ACTIVATE) if(!B.active && !B.defused) holder.visible_message("[bicon(B)] You hear the bomb start ticking!") B.activate() @@ -75,7 +75,7 @@ var/const/WIRE_ACTIVATE = 16 // Will start a bombs timer if pulsed, will hint if /datum/wires/syndicatebomb/UpdateCut(index, mended) var/obj/machinery/syndicatebomb/B = holder switch(index) - if(WIRE_EXPLODE) + if(BOMB_WIRE_BOOM) if(mended) B.defused = FALSE // Cutting and mending all the wires of an inactive bomb will thus cure any sabotage. else @@ -84,16 +84,16 @@ var/const/WIRE_ACTIVATE = 16 // Will start a bombs timer if pulsed, will hint if B.explode_now = TRUE else B.defused = TRUE - if(WIRE_UNBOLT) + if(BOMB_WIRE_UNBOLT) if(!mended && B.anchored) holder.visible_message("[bicon(B)] The bolts lift out of the ground!") playsound(B, 'sound/effects/stealthoff.ogg', 30, 1) B.anchored = FALSE - if(WIRE_PROCEED) + if(BOMB_WIRE_PROCEED) if(!mended && B.active) holder.visible_message("[bicon(B)] An alarm sounds! It's go-") B.explode_now = TRUE - if(WIRE_ACTIVATE) + if(BOMB_WIRE_ACTIVATE) if(!mended && B.active) holder.visible_message("[bicon(B)] The timer stops! The bomb has been defused!") B.active = FALSE diff --git a/code/datums/wires/tesla_coil.dm b/code/datums/wires/tesla_coil.dm index ed60e927f45..b0b0a57ede4 100644 --- a/code/datums/wires/tesla_coil.dm +++ b/code/datums/wires/tesla_coil.dm @@ -2,11 +2,11 @@ wire_count = 1 holder_type = /obj/machinery/power/tesla_coil -var/const/WIRE_ZAP = 1 +#define TESLACOIL_WIRE_ZAP 1 /datum/wires/tesla_coil/GetWireName(index) switch(index) - if(WIRE_ZAP) + if(TESLACOIL_WIRE_ZAP) return "Zap" /datum/wires/tesla_coil/CanUse(mob/living/L) @@ -18,6 +18,6 @@ var/const/WIRE_ZAP = 1 /datum/wires/tesla_coil/UpdatePulsed(index) var/obj/machinery/power/tesla_coil/T = holder switch(index) - if(WIRE_ZAP) + if(TESLACOIL_WIRE_ZAP) T.zap() ..() \ No newline at end of file diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm index 3861cc68203..704c6bad750 100644 --- a/code/datums/wires/vending.dm +++ b/code/datums/wires/vending.dm @@ -2,10 +2,10 @@ holder_type = /obj/machinery/vending wire_count = 4 -var/const/VENDING_WIRE_THROW = 1 -var/const/VENDING_WIRE_CONTRABAND = 2 -var/const/VENDING_WIRE_ELECTRIFY = 4 -var/const/VENDING_WIRE_IDSCAN = 8 +#define VENDING_WIRE_THROW 1 +#define VENDING_WIRE_CONTRABAND 2 +#define VENDING_WIRE_ELECTRIFY 4 +#define VENDING_WIRE_IDSCAN 8 /datum/wires/vending/GetWireName(index) switch(index) diff --git a/code/datums/wires/wires.dm b/code/datums/wires/wires.dm index 5220fd83a6d..d055310eddf 100644 --- a/code/datums/wires/wires.dm +++ b/code/datums/wires/wires.dm @@ -208,20 +208,20 @@ var/list/wireColours = list("red", "blue", "green", "black", "orange", "brown", // Example of use: /* -var/const/BOLTED= 1 -var/const/SHOCKED = 2 -var/const/SAFETY = 4 -var/const/POWER = 8 +#define NAME_WIRE_BOLTED 1 +#define NAME_WIRE_SHOCKED 2 +#define NAME_WIRE_SAFETY 4 +#define NAME_WIRE_POWER 8 /datum/wires/door/UpdateCut(var/index, var/mended) var/obj/machinery/door/airlock/A = holder switch(index) - if(BOLTED) + if(NAME_WIRE_BOLTED) if(!mended) A.bolt() - if(SHOCKED) + if(NAME_WIRE_SHOCKED) A.shock() - if(SAFETY ) + if(NAME_WIRE_SAFETY) A.safety() */ diff --git a/code/game/area/areas/depot-areas.dm b/code/game/area/areas/depot-areas.dm index 86940fcb412..d0ae6fc5588 100644 --- a/code/game/area/areas/depot-areas.dm +++ b/code/game/area/areas/depot-areas.dm @@ -174,7 +174,7 @@ L.close() if(!L.locked) L.locked = !L.locked - L.req_access = list(access_syndicate_leader) + L.req_access = list(ACCESS_SYNDICATE_LEADER) L.update_icon() else log_game("Depot visit: ended") @@ -182,7 +182,7 @@ for(var/mob/living/simple_animal/hostile/syndicate/N in src) N.a_intent = INTENT_HARM for(var/obj/machinery/door/airlock/A in src) - A.req_access_txt = "[access_syndicate_leader]" + A.req_access_txt = "[ACCESS_SYNDICATE_LEADER]" for(var/obj/structure/closet/secure_closet/syndicate/depot/L in src) if(L.locked) L.locked = !L.locked diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index cf7f931ff32..feb2e59f6cd 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -424,7 +424,7 @@ if(master) return master.attack_hand(a, b, c) -/atom/movable/proc/water_act(volume, temperature, source, method = TOUCH) //amount of water acting : temperature of water in kelvin : object that called it (for shennagins) +/atom/movable/proc/water_act(volume, temperature, source, method = REAGENT_TOUCH) //amount of water acting : temperature of water in kelvin : object that called it (for shennagins) return TRUE /atom/movable/proc/handle_buckled_mob_movement(newloc,direct) diff --git a/code/game/gamemodes/blob/powers.dm b/code/game/gamemodes/blob/powers.dm index d4c429a6f53..548f2759ba0 100644 --- a/code/game/gamemodes/blob/powers.dm +++ b/code/game/gamemodes/blob/powers.dm @@ -336,7 +336,7 @@ if(ROLE_BLOB in L.faction) //no friendly/dead fire continue var/mob_protection = L.get_permeability_protection() - blob_reagent_datum.reaction_mob(L, TOUCH, 25, 1, mob_protection) + blob_reagent_datum.reaction_mob(L, REAGENT_TOUCH, 25, 1, mob_protection) blob_reagent_datum.send_message(L) OB.color = blob_reagent_datum.color return diff --git a/code/game/gamemodes/devil/devilinfo.dm b/code/game/gamemodes/devil/devilinfo.dm index 89d997831a3..abd90172ee9 100644 --- a/code/game/gamemodes/devil/devilinfo.dm +++ b/code/game/gamemodes/devil/devilinfo.dm @@ -555,5 +555,5 @@ var/global/list/lawlorify = list ( W.rank = W.assignment W.age = H.age W.sex = capitalize(H.gender) - W.access = list(access_maint_tunnels, access_syndicate, access_external_airlocks) + W.access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_EXTERNAL_AIRLOCKS) W.photo = get_id_photo(H) diff --git a/code/game/gamemodes/miniantags/abduction/gland.dm b/code/game/gamemodes/miniantags/abduction/gland.dm index 419b53d50a3..23d9792ca4c 100644 --- a/code/game/gamemodes/miniantags/abduction/gland.dm +++ b/code/game/gamemodes/miniantags/abduction/gland.dm @@ -346,5 +346,5 @@ owner.visible_message("[owner] vomits a cloud of plasma!") var/turf/simulated/T = get_turf(owner) if(istype(T)) - T.atmos_spawn_air(SPAWN_TOXINS|SPAWN_20C,50) + T.atmos_spawn_air(LINDA_SPAWN_TOXINS|LINDA_SPAWN_20C,50) owner.vomit() diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm index 2d7c9cff015..578cc8ac3a7 100644 --- a/code/game/gamemodes/nuclear/nuclear.dm +++ b/code/game/gamemodes/nuclear/nuclear.dm @@ -226,7 +226,7 @@ proc/issyndicate(mob/living/M as mob) ID.name = "[synd_mind.current.real_name] ID card" ID.registered_name = synd_mind.current.real_name if(is_leader) - ID.access += access_syndicate_leader + ID.access += ACCESS_SYNDICATE_LEADER else message_admins("Warning: Operative [key_name_admin(synd_mind.current)] spawned without an ID card!") diff --git a/code/game/gamemodes/wizard/artefact.dm b/code/game/gamemodes/wizard/artefact.dm index 187ac0f0ea1..d818ad715b3 100644 --- a/code/game/gamemodes/wizard/artefact.dm +++ b/code/game/gamemodes/wizard/artefact.dm @@ -582,10 +582,10 @@ var/global/list/multiverse = list() W.access = duplicated_id.access W.icon_state = duplicated_id.icon_state else - W.access += access_maint_tunnels + W.access += ACCESS_MAINT_TUNNELS W.icon_state = "centcom" else - W.access += access_maint_tunnels + W.access += ACCESS_MAINT_TUNNELS W.icon_state = "centcom" W.assignment = "Multiverse Traveller" W.registered_name = M.real_name diff --git a/code/game/jobs/access.dm b/code/game/jobs/access.dm index a31f694f7fc..6ac2daef330 100644 --- a/code/game/jobs/access.dm +++ b/code/game/jobs/access.dm @@ -1,115 +1,3 @@ -/var/const/access_security = 1 // Security equipment -/var/const/access_brig = 2 // Brig timers and permabrig -/var/const/access_armory = 3 -/var/const/access_forensics_lockers= 4 -/var/const/access_medical = 5 -/var/const/access_morgue = 6 -/var/const/access_tox = 7 -/var/const/access_tox_storage = 8 -/var/const/access_genetics = 9 -/var/const/access_engine = 10 -/var/const/access_engine_equip = 11 -/var/const/access_maint_tunnels = 12 -/var/const/access_external_airlocks = 13 -/var/const/access_emergency_storage = 14 -/var/const/access_change_ids = 15 -/var/const/access_ai_upload = 16 -/var/const/access_teleporter = 17 -/var/const/access_eva = 18 -/var/const/access_heads = 19 -/var/const/access_captain = 20 -/var/const/access_all_personal_lockers = 21 -/var/const/access_chapel_office = 22 -/var/const/access_tech_storage = 23 -/var/const/access_atmospherics = 24 -/var/const/access_bar = 25 -/var/const/access_janitor = 26 -/var/const/access_crematorium = 27 -/var/const/access_kitchen = 28 -/var/const/access_robotics = 29 -/var/const/access_rd = 30 -/var/const/access_cargo = 31 -/var/const/access_construction = 32 -/var/const/access_chemistry = 33 -/var/const/access_cargo_bot = 34 -/var/const/access_hydroponics = 35 -/var/const/access_manufacturing = 36 -/var/const/access_library = 37 -/var/const/access_lawyer = 38 -/var/const/access_virology = 39 -/var/const/access_cmo = 40 -/var/const/access_qm = 41 -/var/const/access_court = 42 -/var/const/access_clown = 43 -/var/const/access_mime = 44 -/var/const/access_surgery = 45 -/var/const/access_theatre = 46 -/var/const/access_research = 47 -/var/const/access_mining = 48 -/var/const/access_mining_office = 49 //not in use -/var/const/access_mailsorting = 50 -/var/const/access_mint = 51 -/var/const/access_mint_vault = 52 -/var/const/access_heads_vault = 53 -/var/const/access_mining_station = 54 -/var/const/access_xenobiology = 55 -/var/const/access_ce = 56 -/var/const/access_hop = 57 -/var/const/access_hos = 58 -/var/const/access_RC_announce = 59 //Request console announcements -/var/const/access_keycard_auth = 60 //Used for events which require at least two people to confirm them -/var/const/access_tcomsat = 61 // has access to the entire telecomms satellite / machinery -/var/const/access_gateway = 62 -/var/const/access_sec_doors = 63 // Security front doors -/var/const/access_psychiatrist = 64 // Psychiatrist's office -/var/const/access_xenoarch = 65 -/var/const/access_paramedic = 66 -/var/const/access_blueshield = 67 -/var/const/access_salvage_captain = 69 // Salvage ship captain's quarters -/var/const/access_mechanic = 70 -/var/const/access_pilot = 71 -/var/const/access_ntrep = 73 -/var/const/access_magistrate = 74 -/var/const/access_minisat = 75 -/var/const/access_mineral_storeroom = 76 -/var/const/access_network = 77 - -/var/const/access_weapons = 99 //Weapon authorization for secbots - - //BEGIN CENTCOM ACCESS -/var/const/access_cent_general = 101//General facilities. -/var/const/access_cent_living = 102//Living quarters. -/var/const/access_cent_medical = 103//Medical. -/var/const/access_cent_security = 104//Security. -/var/const/access_cent_storage = 105//Storage areas. -/var/const/access_cent_shuttles = 106//Shuttle docks. -/var/const/access_cent_telecomms = 107//Telecomms. -/var/const/access_cent_teleporter = 108//Teleporter -/var/const/access_cent_specops = 109//Special Ops. -/var/const/access_cent_specops_commander = 110//Special Ops Commander. -/var/const/access_cent_blackops = 111//Black Ops. -/var/const/access_cent_thunder = 112//Thunderdome. -/var/const/access_cent_bridge = 113//Bridge. -/var/const/access_cent_commander = 114//Commander's Office/ID computer. - -//The Syndicate -/var/const/access_syndicate = 150//General Syndicate Access -/var/const/access_syndicate_leader = 151//Nuke Op Leader Access -/var/const/access_vox = 152//Vox Access -/var/const/access_syndicate_command = 153//Admin syndi officer - -//Trade Stations -var/const/access_trade_sol = 160 - -//MONEY -/var/const/access_crate_cash = 200 - -//Awaymissions -/var/const/access_away01 = 271 - -//Ghost roles -var/const/access_free_golems = 300 - /obj/var/list/req_access = null /obj/var/req_access_txt = "0" /obj/var/list/req_one_access = null @@ -191,23 +79,23 @@ var/const/access_free_golems = 300 /proc/get_centcom_access(job) switch(job) if("VIP Guest") - return list(access_cent_general, access_cent_living) + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) if("Custodian") - return list(access_cent_general, access_cent_living, access_cent_medical, access_cent_storage) + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE) if("Thunderdome Overseer") - return list(access_cent_general, access_cent_thunder) + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) if("Emergency Response Team Member") - return list(access_cent_general, access_cent_living, access_cent_medical, access_cent_security, access_cent_storage, access_cent_specops) + get_all_accesses() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS) + get_all_accesses() if("Emergency Response Team Leader") - return list(access_cent_general, access_cent_living, access_cent_medical, access_cent_security, access_cent_storage, access_cent_specops, access_cent_specops_commander) + get_all_accesses() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER) + get_all_accesses() if("Medical Officer") - return list(access_cent_general, access_cent_living, access_cent_medical, access_cent_storage) + get_all_accesses() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE) + get_all_accesses() if("Intel Officer") - return list(access_cent_general, access_cent_living, access_cent_security, access_cent_storage) + get_all_accesses() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE) + get_all_accesses() if("Research Officer") - return list(access_cent_general, access_cent_specops, access_cent_medical, access_cent_storage, access_cent_telecomms, access_cent_teleporter) + get_all_accesses() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE, ACCESS_CENT_TELECOMMS, ACCESS_CENT_TELEPORTER) + get_all_accesses() if("Death Commando") - return list(access_cent_general, access_cent_living, access_cent_medical, access_cent_security, access_cent_storage, access_cent_specops, access_cent_specops_commander, access_cent_blackops) + get_all_accesses() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER, ACCESS_CENT_BLACKOPS) + get_all_accesses() if("Deathsquad Officer") return get_all_centcom_access() + get_all_accesses() if("NT Undercover Operative") @@ -226,42 +114,42 @@ var/const/access_free_golems = 300 /proc/get_syndicate_access(job) switch(job) if("Syndicate Operative") - return list(access_syndicate) + return list(ACCESS_SYNDICATE) if("Syndicate Operative Leader") - return list(access_syndicate, access_syndicate_leader) + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) if("Syndicate Agent") - return list(access_syndicate, access_maint_tunnels) + return list(ACCESS_SYNDICATE, ACCESS_MAINT_TUNNELS) if("Vox Raider") - return list(access_vox) + return list(ACCESS_VOX) if("Vox Trader") - return list(access_vox) + return list(ACCESS_VOX) if("Syndicate Commando") - return list(access_syndicate, access_syndicate_leader) + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) if("Syndicate Officer") - return list(access_syndicate, access_syndicate_leader, access_syndicate_command) + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_SYNDICATE_COMMAND) /proc/get_all_accesses() - return list(access_security, access_sec_doors, access_brig, access_armory, access_forensics_lockers, access_court, - access_medical, access_genetics, access_morgue, access_rd, - access_tox, access_tox_storage, access_chemistry, access_engine, access_engine_equip, access_maint_tunnels, - access_external_airlocks, access_change_ids, access_ai_upload, - access_teleporter, access_eva, access_heads, access_captain, access_all_personal_lockers, - access_tech_storage, access_chapel_office, access_atmospherics, access_kitchen, - access_bar, access_janitor, access_crematorium, access_robotics, access_cargo, access_construction, - access_hydroponics, access_library, access_lawyer, access_virology, access_psychiatrist, access_cmo, access_qm, access_clown, access_mime, access_surgery, - access_theatre, access_research, access_mining, access_mailsorting, - access_heads_vault, access_mining_station, access_xenobiology, access_ce, access_hop, access_hos, access_RC_announce, - access_keycard_auth, access_tcomsat, access_gateway, access_xenoarch, access_paramedic, access_blueshield, access_mechanic,access_weapons, - access_pilot, access_ntrep, access_magistrate, access_mineral_storeroom, access_minisat, access_network) + return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, + ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, + ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, + ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, + ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, + ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, + ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_PSYCHIATRIST, ACCESS_CMO, ACCESS_QM, ACCESS_CLOWN, ACCESS_MIME, ACCESS_SURGERY, + ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, + ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_PARAMEDIC, ACCESS_BLUESHIELD, ACCESS_MECHANIC,ACCESS_WEAPONS, + ACCESS_PILOT, ACCESS_NTREP, ACCESS_MAGISTRATE, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK) /proc/get_all_centcom_access() - return list(access_cent_general, access_cent_living, access_cent_medical, access_cent_security, access_cent_storage, access_cent_shuttles, access_cent_telecomms, access_cent_teleporter, access_cent_specops, access_cent_specops_commander, access_cent_blackops, access_cent_thunder, access_cent_bridge, access_cent_commander) + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SHUTTLES, ACCESS_CENT_TELECOMMS, ACCESS_CENT_TELEPORTER, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER, ACCESS_CENT_BLACKOPS, ACCESS_CENT_THUNDER, ACCESS_CENT_BRIDGE, ACCESS_CENT_COMMANDER) /proc/get_all_syndicate_access() - return list(access_syndicate, access_syndicate_leader, access_vox, access_syndicate_command) + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_VOX, ACCESS_SYNDICATE_COMMAND) /proc/get_all_misc_access() - return list(access_salvage_captain, access_trade_sol, access_crate_cash, access_away01) + return list(ACCESS_SALVAGE_CAPTAIN, ACCESS_TRADE_SOL, ACCESS_CRATE_CASH, ACCESS_AWAY01) /proc/get_absolutely_all_accesses() return (get_all_accesses() | get_all_centcom_access() | get_all_syndicate_access() | get_all_misc_access()) @@ -271,19 +159,19 @@ var/const/access_free_golems = 300 if(REGION_ALL) return get_all_accesses() if(REGION_GENERAL) //station general - return list(access_kitchen, access_bar, access_hydroponics, access_janitor, access_chapel_office, access_crematorium, access_library, access_theatre, access_lawyer, access_magistrate, access_clown, access_mime) + return list(ACCESS_KITCHEN, ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER, ACCESS_MAGISTRATE, ACCESS_CLOWN, ACCESS_MIME) if(REGION_SECURITY) //security - return list(access_sec_doors, access_weapons, access_security, access_brig, access_armory, access_forensics_lockers, access_court, access_pilot, access_hos) + return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_PILOT, ACCESS_HOS) if(REGION_MEDBAY) //medbay - return list(access_medical, access_genetics, access_morgue, access_chemistry, access_psychiatrist, access_virology, access_surgery, access_cmo, access_paramedic) + return list(ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_PSYCHIATRIST, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_CMO, ACCESS_PARAMEDIC) if(REGION_RESEARCH) //research - return list(access_research, access_tox, access_tox_storage, access_genetics, access_robotics, access_xenobiology, access_xenoarch, access_minisat, access_rd, access_network) + return list(ACCESS_RESEARCH, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_XENOARCH, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) if(REGION_ENGINEERING) //engineering and maintenance - return list(access_construction, access_maint_tunnels, access_engine, access_engine_equip, access_external_airlocks, access_tech_storage, access_atmospherics, access_minisat, access_ce, access_mechanic) + return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MINISAT, ACCESS_CE, ACCESS_MECHANIC) if(REGION_SUPPLY) //supply - return list(access_mailsorting, access_mining, access_mining_station, access_mineral_storeroom, access_cargo, access_qm) + return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM) if(REGION_COMMAND) //command - return list(access_heads, access_RC_announce, access_keycard_auth, access_change_ids, access_ai_upload, access_teleporter, access_eva, access_tcomsat, access_gateway, access_all_personal_lockers, access_heads_vault, access_blueshield, access_ntrep, access_hop, access_captain) + return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HEADS_VAULT, ACCESS_BLUESHIELD, ACCESS_NTREP, ACCESS_HOP, ACCESS_CAPTAIN) if(REGION_CENTCOMM) //because why the heck not return get_all_centcom_access() + get_all_accesses() @@ -311,197 +199,197 @@ var/const/access_free_golems = 300 /proc/get_access_desc(A) switch(A) - if(access_cargo) + if(ACCESS_CARGO) return "Cargo Bay" - if(access_cargo_bot) + if(ACCESS_CARGO_BOT) return "Cargo Bot Delivery" - if(access_security) + if(ACCESS_SECURITY) return "Security" - if(access_brig) + if(ACCESS_BRIG) return "Holding Cells" - if(access_court) + if(ACCESS_COURT) return "Courtroom" - if(access_forensics_lockers) + if(ACCESS_FORENSICS_LOCKERS) return "Forensics" - if(access_medical) + if(ACCESS_MEDICAL) return "Medical" - if(access_genetics) + if(ACCESS_GENETICS) return "Genetics Lab" - if(access_morgue) + if(ACCESS_MORGUE) return "Morgue" - if(access_tox) + if(ACCESS_TOX) return "R&D Lab" - if(access_tox_storage) + if(ACCESS_TOX_STORAGE) return "Toxins Lab" - if(access_chemistry) + if(ACCESS_CHEMISTRY) return "Chemistry Lab" - if(access_rd) + if(ACCESS_RD) return "Research Director" - if(access_bar) + if(ACCESS_BAR) return "Bar" - if(access_janitor) + if(ACCESS_JANITOR) return "Custodial Closet" - if(access_engine) + if(ACCESS_ENGINE) return "Engineering" - if(access_engine_equip) + if(ACCESS_ENGINE_EQUIP) return "Power Equipment" - if(access_maint_tunnels) + if(ACCESS_MAINT_TUNNELS) return "Maintenance" - if(access_external_airlocks) + if(ACCESS_EXTERNAL_AIRLOCKS) return "External Airlocks" - if(access_emergency_storage) + if(ACCESS_EMERGENCY_STORAGE) return "Emergency Storage" - if(access_change_ids) + if(ACCESS_CHANGE_IDS) return "ID Computer" - if(access_ai_upload) + if(ACCESS_AI_UPLOAD) return "AI Upload" - if(access_teleporter) + if(ACCESS_TELEPORTER) return "Teleporter" - if(access_eva) + if(ACCESS_EVA) return "EVA" - if(access_heads) + if(ACCESS_HEADS) return "Bridge" - if(access_captain) + if(ACCESS_CAPTAIN) return "Captain" - if(access_all_personal_lockers) + if(ACCESS_ALL_PERSONAL_LOCKERS) return "Personal Lockers" - if(access_chapel_office) + if(ACCESS_CHAPEL_OFFICE) return "Chapel Office" - if(access_tech_storage) + if(ACCESS_TECH_STORAGE) return "Technical Storage" - if(access_atmospherics) + if(ACCESS_ATMOSPHERICS) return "Atmospherics" - if(access_crematorium) + if(ACCESS_CREMATORIUM) return "Crematorium" - if(access_armory) + if(ACCESS_ARMORY) return "Armory" - if(access_construction) + if(ACCESS_CONSTRUCTION) return "Construction Areas" - if(access_kitchen) + if(ACCESS_KITCHEN) return "Kitchen" - if(access_hydroponics) + if(ACCESS_HYDROPONICS) return "Hydroponics" - if(access_library) + if(ACCESS_LIBRARY) return "Library" - if(access_lawyer) + if(ACCESS_LAWYER) return "Law Office" - if(access_robotics) + if(ACCESS_ROBOTICS) return "Robotics" - if(access_virology) + if(ACCESS_VIROLOGY) return "Virology" - if(access_psychiatrist) + if(ACCESS_PSYCHIATRIST) return "Psychiatrist's Office" - if(access_cmo) + if(ACCESS_CMO) return "Chief Medical Officer" - if(access_qm) + if(ACCESS_QM) return "Quartermaster" - if(access_clown) + if(ACCESS_CLOWN) return "Clown's Office" - if(access_mime) + if(ACCESS_MIME) return "Mime's Office" - if(access_surgery) + if(ACCESS_SURGERY) return "Surgery" - if(access_theatre) + if(ACCESS_THEATRE) return "Theatre" - if(access_manufacturing) + if(ACCESS_MANUFACTURING) return "Manufacturing" - if(access_research) + if(ACCESS_RESEARCH) return "Science" - if(access_mining) + if(ACCESS_MINING) return "Mining" - if(access_mining_office) + if(ACCESS_MINING_OFFICE) return "Mining Office" - if(access_mailsorting) + if(ACCESS_MAILSORTING) return "Cargo Office" - if(access_mint) + if(ACCESS_MINT) return "Mint" - if(access_mint_vault) + if(ACCESS_MINT_VAULT) return "Mint Vault" - if(access_heads_vault) + if(ACCESS_HEADS_VAULT) return "Main Vault" - if(access_mining_station) + if(ACCESS_MINING_STATION) return "Mining EVA" - if(access_xenobiology) + if(ACCESS_XENOBIOLOGY) return "Xenobiology Lab" - if(access_xenoarch) + if(ACCESS_XENOARCH) return "Xenoarchaeology" - if(access_hop) + if(ACCESS_HOP) return "Head of Personnel" - if(access_hos) + if(ACCESS_HOS) return "Head of Security" - if(access_ce) + if(ACCESS_CE) return "Chief Engineer" - if(access_RC_announce) + if(ACCESS_RC_ANNOUNCE) return "RC Announcements" - if(access_keycard_auth) + if(ACCESS_KEYCARD_AUTH) return "Keycode Auth. Device" - if(access_tcomsat) + if(ACCESS_TCOMSAT) return "Telecommunications" - if(access_network) + if(ACCESS_NETWORK) return "Network Access" - if(access_gateway) + if(ACCESS_GATEWAY) return "Gateway" - if(access_sec_doors) + if(ACCESS_SEC_DOORS) return "Brig" - if(access_blueshield) + if(ACCESS_BLUESHIELD) return "Blueshield" - if(access_ntrep) + if(ACCESS_NTREP) return "Nanotrasen Rep." - if(access_paramedic) + if(ACCESS_PARAMEDIC) return "Paramedic" - if(access_mechanic) + if(ACCESS_MECHANIC) return "Mechanic Workshop" - if(access_pilot) + if(ACCESS_PILOT) return "Security Pod Pilot" - if(access_magistrate) + if(ACCESS_MAGISTRATE) return "Magistrate" - if(access_mineral_storeroom) + if(ACCESS_MINERAL_STOREROOM) return "Mineral Storage" - if(access_minisat) + if(ACCESS_MINISAT) return "AI Satellite" - if(access_weapons) + if(ACCESS_WEAPONS) return "Weapon Permit" /proc/get_centcom_access_desc(A) switch(A) - if(access_cent_general) + if(ACCESS_CENT_GENERAL) return "General Access" - if(access_cent_living) + if(ACCESS_CENT_LIVING) return "Living Quarters" - if(access_cent_medical) + if(ACCESS_CENT_MEDICAL) return "Medical" - if(access_cent_security) + if(ACCESS_CENT_SECURITY) return "Security" - if(access_cent_storage) + if(ACCESS_CENT_STORAGE) return "Storage" - if(access_cent_shuttles) + if(ACCESS_CENT_SHUTTLES) return "Shuttles" - if(access_cent_telecomms) + if(ACCESS_CENT_TELECOMMS) return "Telecommunications" - if(access_cent_teleporter) + if(ACCESS_CENT_TELEPORTER) return "Teleporter" - if(access_cent_specops) + if(ACCESS_CENT_SPECOPS) return "Special Ops" - if(access_cent_specops_commander) + if(ACCESS_CENT_SPECOPS_COMMANDER) return "Special Ops Commander" - if(access_cent_blackops) + if(ACCESS_CENT_BLACKOPS) return "Black Ops" - if(access_cent_thunder) + if(ACCESS_CENT_THUNDER) return "Thunderdome" - if(access_cent_bridge) + if(ACCESS_CENT_BRIDGE) return "Bridge" - if(access_cent_commander) + if(ACCESS_CENT_COMMANDER) return "Commander" /proc/get_syndicate_access_desc(A) switch(A) - if(access_syndicate) + if(ACCESS_SYNDICATE) return "Syndicate Operative" - if(access_syndicate_leader) + if(ACCESS_SYNDICATE_LEADER) return "Syndicate Operative Leader" - if(access_vox) + if(ACCESS_VOX) return "Vox" - if(access_syndicate_command) + if(ACCESS_SYNDICATE_COMMAND) return "Syndicate Command" /proc/get_all_jobs() diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm index 1d6ae0162a2..93a5cdd6759 100644 --- a/code/game/jobs/job/central.dm +++ b/code/game/jobs/job/central.dm @@ -2,8 +2,8 @@ // General-purpose CC official. Can hear out grievances, investigate cases, issue demotions, etc. /datum/job/ntnavyofficer title = "Nanotrasen Navy Officer" - flag = CENTCOM - department_flag = CENTCOM + flag = JOB_CENTCOM + department_flag = JOB_CENTCOM // This gets its job as its own flag because admin jobs dont have flags total_positions = 5 spawn_positions = 5 supervisors = "the admins" @@ -48,8 +48,8 @@ // CC Officials who lead ERTs, Death Squads, etc. /datum/job/ntspecops title = "Special Operations Officer" - flag = CENTCOM - department_flag = CENTCOM + flag = JOB_CENTCOM + department_flag = JOB_CENTCOM // This gets its job as its own flag because admin jobs dont have flags total_positions = 5 spawn_positions = 5 supervisors = "the admins" diff --git a/code/game/jobs/job/civilian.dm b/code/game/jobs/job/civilian.dm index 4535e1c6c41..757f4b007c2 100644 --- a/code/game/jobs/job/civilian.dm +++ b/code/game/jobs/job/civilian.dm @@ -1,7 +1,7 @@ /datum/job/civilian title = "Civilian" - flag = CIVILIAN - department_flag = SUPPORT + flag = JOB_CIVILIAN + department_flag = JOBCAT_SUPPORT total_positions = -1 spawn_positions = -1 supervisors = "the head of personnel" @@ -14,7 +14,7 @@ /datum/job/civilian/get_access() if(config.assistant_maint) - return list(access_maint_tunnels) + return list(ACCESS_MAINT_TUNNELS) else return list() diff --git a/code/game/jobs/job/engineering.dm b/code/game/jobs/job/engineering.dm index ecae6919813..6fe3d89fb67 100644 --- a/code/game/jobs/job/engineering.dm +++ b/code/game/jobs/job/engineering.dm @@ -1,7 +1,7 @@ /datum/job/chief_engineer title = "Chief Engineer" - flag = CHIEF - department_flag = ENGSEC + flag = JOB_CHIEF + department_flag = JOBCAT_ENGSEC total_positions = 1 spawn_positions = 1 is_engineering = 1 @@ -9,14 +9,14 @@ department_head = list("Captain") selection_color = "#ffeeaa" req_admin_notify = 1 - access = list(access_engine, access_engine_equip, access_tech_storage, access_maint_tunnels, - access_teleporter, access_external_airlocks, access_atmospherics, access_emergency_storage, access_eva, - access_heads, access_construction, access_sec_doors, - access_ce, access_RC_announce, access_keycard_auth, access_tcomsat, access_minisat, access_mechanic, access_mineral_storeroom) - minimal_access = list(access_engine, access_engine_equip, access_tech_storage, access_maint_tunnels, - access_teleporter, access_external_airlocks, access_atmospherics, access_emergency_storage, access_eva, - access_heads, access_construction, access_sec_doors, - access_ce, access_RC_announce, access_keycard_auth, access_tcomsat, access_minisat, access_mechanic, access_mineral_storeroom) + access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, + ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, + ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, + ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, + ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, + ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, + ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM) minimal_player_age = 21 exp_requirements = 300 exp_type = EXP_TYPE_ENGINEERING @@ -47,16 +47,16 @@ /datum/job/engineer title = "Station Engineer" - flag = ENGINEER - department_flag = ENGSEC + flag = JOB_ENGINEER + department_flag = JOBCAT_ENGSEC total_positions = 5 spawn_positions = 5 is_engineering = 1 supervisors = "the chief engineer" department_head = list("Chief Engineer") selection_color = "#fff5cc" - access = list(access_eva, access_engine, access_engine_equip, access_tech_storage, access_maint_tunnels, access_external_airlocks, access_construction, access_atmospherics, access_mineral_storeroom) - minimal_access = list(access_eva, access_engine, access_engine_equip, access_tech_storage, access_maint_tunnels, access_external_airlocks, access_construction, access_mineral_storeroom) + access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM) alt_titles = list("Maintenance Technician","Engine Technician","Electrician") minimal_player_age = 7 exp_requirements = 300 @@ -85,16 +85,16 @@ /datum/job/atmos title = "Life Support Specialist" - flag = ATMOSTECH - department_flag = ENGSEC + flag = JOB_ATMOSTECH + department_flag = JOBCAT_ENGSEC total_positions = 3 spawn_positions = 2 is_engineering = 1 supervisors = "the chief engineer" department_head = list("Chief Engineer") selection_color = "#fff5cc" - access = list(access_eva, access_engine, access_engine_equip, access_tech_storage, access_maint_tunnels, access_external_airlocks, access_construction, access_atmospherics, access_mineral_storeroom) - minimal_access = list(access_eva, access_atmospherics, access_maint_tunnels, access_external_airlocks, access_emergency_storage, access_construction, access_mineral_storeroom, access_tech_storage) + access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_EVA, ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_EMERGENCY_STORAGE, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE) alt_titles = list("Atmospheric Technician") minimal_player_age = 7 exp_requirements = 300 @@ -119,16 +119,16 @@ /datum/job/mechanic title = "Mechanic" - flag = MECHANIC - department_flag = KARMA + flag = JOB_MECHANIC + department_flag = JOBCAT_KARMA total_positions = 1 spawn_positions = 1 is_engineering = 1 supervisors = "the chief engineer" department_head = list("Chief Engineer") selection_color = "#fff5cc" - access = list(access_engine, access_engine_equip, access_tech_storage, access_maint_tunnels, access_mechanic, access_external_airlocks, access_mineral_storeroom) - minimal_access = list(access_maint_tunnels, access_emergency_storage, access_mechanic, access_external_airlocks, access_mineral_storeroom) + access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_MECHANIC, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_EMERGENCY_STORAGE, ACCESS_MECHANIC, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MINERAL_STOREROOM) outfit = /datum/outfit/job/mechanic /datum/outfit/job/mechanic diff --git a/code/game/jobs/job/medical.dm b/code/game/jobs/job/medical.dm index 49e6b4486ac..425cd9252e5 100644 --- a/code/game/jobs/job/medical.dm +++ b/code/game/jobs/job/medical.dm @@ -1,7 +1,7 @@ /datum/job/cmo title = "Chief Medical Officer" - flag = CMO - department_flag = MEDSCI + flag = JOB_CMO + department_flag = JOBCAT_MEDSCI total_positions = 1 spawn_positions = 1 is_medical = 1 @@ -9,12 +9,12 @@ department_head = list("Captain") selection_color = "#ffddf0" req_admin_notify = 1 - access = list(access_medical, access_morgue, access_genetics, access_heads, - access_chemistry, access_virology, access_cmo, access_surgery, access_RC_announce, - access_keycard_auth, access_sec_doors, access_psychiatrist, access_paramedic, access_mineral_storeroom) - minimal_access = list(access_eva, access_medical, access_morgue, access_genetics, access_heads, - access_chemistry, access_virology, access_cmo, access_surgery, access_RC_announce, - access_keycard_auth, access_sec_doors, access_psychiatrist, access_maint_tunnels, access_paramedic, access_mineral_storeroom) + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_HEADS, + ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_EVA, ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_HEADS, + ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_MAINT_TUNNELS, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM) minimal_player_age = 21 exp_requirements = 300 exp_type = EXP_TYPE_MEDICAL @@ -42,16 +42,16 @@ /datum/job/doctor title = "Medical Doctor" - flag = DOCTOR - department_flag = MEDSCI + flag = JOB_DOCTOR + department_flag = JOBCAT_MEDSCI total_positions = 5 spawn_positions = 3 is_medical = 1 supervisors = "the chief medical officer" department_head = list("Chief Medical Officer") selection_color = "#ffeef0" - access = list(access_medical, access_morgue, access_surgery, access_chemistry, access_virology, access_genetics, access_mineral_storeroom) - minimal_access = list(access_medical, access_morgue, access_surgery, access_maint_tunnels) + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_MAINT_TUNNELS) alt_titles = list("Surgeon","Nurse") minimal_player_age = 3 exp_requirements = 180 @@ -77,16 +77,16 @@ /datum/job/coroner title = "Coroner" - flag = CORONER - department_flag = MEDSCI + flag = JOB_CORONER + department_flag = JOBCAT_MEDSCI total_positions = 1 spawn_positions = 1 is_medical = 1 supervisors = "the chief medical officer" department_head = list("Chief Medical Officer") selection_color = "#ffeef0" - access = list(access_medical, access_morgue, access_surgery, access_chemistry, access_virology, access_genetics, access_mineral_storeroom) - minimal_access = list(access_medical, access_morgue, access_maint_tunnels) + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS) minimal_player_age = 3 exp_requirements = 180 exp_type = EXP_TYPE_CREW @@ -139,16 +139,16 @@ //Chemist is a medical job damnit //YEAH FUCK YOU SCIENCE -Pete //Guys, behave -Erro /datum/job/chemist title = "Chemist" - flag = CHEMIST - department_flag = MEDSCI + flag = JOB_CHEMIST + department_flag = JOBCAT_MEDSCI total_positions = 2 spawn_positions = 2 is_medical = 1 supervisors = "the chief medical officer" department_head = list("Chief Medical Officer") selection_color = "#ffeef0" - access = list(access_medical, access_morgue, access_surgery, access_chemistry, access_virology, access_genetics, access_mineral_storeroom) - minimal_access = list(access_medical, access_chemistry, access_maint_tunnels, access_mineral_storeroom) + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MEDICAL, ACCESS_CHEMISTRY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) alt_titles = list("Pharmacist","Pharmacologist") minimal_player_age = 7 exp_requirements = 300 @@ -173,16 +173,16 @@ /datum/job/geneticist title = "Geneticist" - flag = GENETICIST - department_flag = MEDSCI + flag = JOB_GENETICIST + department_flag = JOBCAT_MEDSCI total_positions = 2 spawn_positions = 2 is_medical = 1 supervisors = "the chief medical officer and the research director" department_head = list("Chief Medical Officer", "Research Director") selection_color = "#ffeef0" - access = list(access_medical, access_morgue, access_surgery, access_chemistry, access_virology, access_genetics, access_research, access_mineral_storeroom) - minimal_access = list(access_medical, access_morgue, access_genetics, access_research, access_maint_tunnels) + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_RESEARCH, ACCESS_MAINT_TUNNELS) minimal_player_age = 3 exp_requirements = 180 exp_type = EXP_TYPE_CREW @@ -207,16 +207,16 @@ /datum/job/virologist title = "Virologist" - flag = VIROLOGIST - department_flag = MEDSCI + flag = JOB_VIROLOGIST + department_flag = JOBCAT_MEDSCI total_positions = 1 spawn_positions = 1 is_medical = 1 supervisors = "the chief medical officer" department_head = list("Chief Medical Officer") selection_color = "#ffeef0" - access = list(access_medical, access_morgue, access_surgery, access_chemistry, access_virology, access_genetics) - minimal_access = list(access_medical, access_virology, access_maint_tunnels, access_mineral_storeroom) + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS) + minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) alt_titles = list("Pathologist","Microbiologist") minimal_player_age = 7 exp_requirements = 300 @@ -242,16 +242,16 @@ /datum/job/psychiatrist title = "Psychiatrist" - flag = PSYCHIATRIST - department_flag = MEDSCI + flag = JOB_PSYCHIATRIST + department_flag = JOBCAT_MEDSCI total_positions = 1 spawn_positions = 1 is_medical = 1 supervisors = "the chief medical officer" department_head = list("Chief Medical Officer") selection_color = "#ffeef0" - access = list(access_medical, access_morgue, access_surgery, access_chemistry, access_virology, access_genetics, access_psychiatrist) - minimal_access = list(access_medical, access_psychiatrist, access_maint_tunnels) + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_PSYCHIATRIST) + minimal_access = list(ACCESS_MEDICAL, ACCESS_PSYCHIATRIST, ACCESS_MAINT_TUNNELS) alt_titles = list("Psychologist","Therapist") outfit = /datum/outfit/job/psychiatrist @@ -280,16 +280,16 @@ /datum/job/paramedic title = "Paramedic" - flag = PARAMEDIC - department_flag = MEDSCI + flag = JOB_PARAMEDIC + department_flag = JOBCAT_MEDSCI total_positions = 1 spawn_positions = 1 is_medical = 1 supervisors = "the chief medical officer" department_head = list("Chief Medical Officer") selection_color = "#ffeef0" - access = list(access_paramedic, access_medical, access_maint_tunnels, access_external_airlocks, access_morgue) - minimal_access=list(access_paramedic, access_medical, access_maint_tunnels, access_external_airlocks, access_morgue) + access = list(ACCESS_PARAMEDIC, ACCESS_MEDICAL, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MORGUE) + minimal_access=list(ACCESS_PARAMEDIC, ACCESS_MEDICAL, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MORGUE) minimal_player_age = 3 exp_requirements = 180 exp_type = EXP_TYPE_CREW diff --git a/code/game/jobs/job/science.dm b/code/game/jobs/job/science.dm index 435565434c8..684de933832 100644 --- a/code/game/jobs/job/science.dm +++ b/code/game/jobs/job/science.dm @@ -1,7 +1,7 @@ /datum/job/rd title = "Research Director" - flag = RD - department_flag = MEDSCI + flag = JOB_RD + department_flag = JOBCAT_MEDSCI total_positions = 1 spawn_positions = 1 is_science = 1 @@ -9,14 +9,14 @@ department_head = list("Captain") selection_color = "#ffddff" req_admin_notify = 1 - access = list(access_rd, access_heads, access_tox, access_genetics, access_morgue, - access_tox_storage, access_tech_storage, access_teleporter, access_sec_doors, - access_research, access_robotics, access_xenobiology, access_ai_upload, - access_RC_announce, access_keycard_auth, access_tcomsat, access_gateway, access_xenoarch, access_minisat, access_mineral_storeroom, access_network) - minimal_access = list(access_eva, access_rd, access_heads, access_tox, access_genetics, access_morgue, - access_tox_storage, access_tech_storage, access_teleporter, access_sec_doors, - access_research, access_robotics, access_xenobiology, access_ai_upload, - access_RC_announce, access_keycard_auth, access_tcomsat, access_gateway, access_xenoarch, access_minisat, access_maint_tunnels, access_mineral_storeroom, access_network) + access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE, + ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS, + ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD, + ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_MINISAT, ACCESS_MINERAL_STOREROOM, ACCESS_NETWORK) + minimal_access = list(ACCESS_EVA, ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE, + ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS, + ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD, + ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_MINISAT, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM, ACCESS_NETWORK) minimal_player_age = 21 exp_requirements = 300 exp_type = EXP_TYPE_SCIENCE @@ -49,16 +49,16 @@ /datum/job/scientist title = "Scientist" - flag = SCIENTIST - department_flag = MEDSCI + flag = JOB_SCIENTIST + department_flag = JOBCAT_MEDSCI total_positions = 6 spawn_positions = 6 is_science = 1 supervisors = "the research director" department_head = list("Research Director") selection_color = "#ffeeff" - access = list(access_robotics, access_tox, access_tox_storage, access_research, access_xenobiology, access_xenoarch, access_mineral_storeroom) - minimal_access = list(access_tox, access_tox_storage, access_research, access_xenobiology, access_xenoarch, access_maint_tunnels, access_mineral_storeroom) + access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_XENOARCH, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_XENOARCH, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) alt_titles = list("Xenoarcheologist", "Anomalist", "Plasma Researcher", "Xenobiologist", "Chemical Researcher") minimal_player_age = 3 exp_requirements = 300 @@ -88,16 +88,16 @@ /datum/job/roboticist title = "Roboticist" - flag = ROBOTICIST - department_flag = MEDSCI + flag = JOB_ROBOTICIST + department_flag = JOBCAT_MEDSCI total_positions = 2 spawn_positions = 2 is_science = 1 supervisors = "the research director" department_head = list("Research Director") selection_color = "#ffeeff" - access = list(access_robotics, access_tox, access_tox_storage, access_tech_storage, access_morgue, access_research, access_mineral_storeroom) //As a job that handles so many corpses, it makes sense for them to have morgue access. - minimal_access = list(access_robotics, access_tech_storage, access_morgue, access_research, access_maint_tunnels, access_mineral_storeroom) //As a job that handles so many corpses, it makes sense for them to have morgue access. + access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM) //As a job that handles so many corpses, it makes sense for them to have morgue access. + minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) //As a job that handles so many corpses, it makes sense for them to have morgue access. alt_titles = list("Biomechanical Engineer","Mechatronic Engineer") minimal_player_age = 3 exp_requirements = 180 diff --git a/code/game/jobs/job/security.dm b/code/game/jobs/job/security.dm index 0dfd0876817..d314f016a9a 100644 --- a/code/game/jobs/job/security.dm +++ b/code/game/jobs/job/security.dm @@ -1,7 +1,7 @@ /datum/job/hos title = "Head of Security" - flag = HOS - department_flag = ENGSEC + flag = JOB_HOS + department_flag = JOBCAT_ENGSEC total_positions = 1 spawn_positions = 1 is_security = 1 @@ -9,14 +9,14 @@ department_head = list("Captain") selection_color = "#ffdddd" req_admin_notify = 1 - access = list(access_security, access_sec_doors, access_brig, access_armory, access_court, - access_forensics_lockers, access_pilot, access_morgue, access_maint_tunnels, access_all_personal_lockers, - access_research, access_engine, access_mining, access_medical, access_construction, access_mailsorting, - access_heads, access_hos, access_RC_announce, access_keycard_auth, access_gateway, access_weapons) - minimal_access = list(access_eva, access_security, access_sec_doors, access_brig, access_armory, access_court, - access_forensics_lockers, access_morgue, access_maint_tunnels, access_all_personal_lockers, - access_research, access_engine, access_mining, access_medical, access_construction, access_mailsorting, - access_heads, access_hos, access_RC_announce, access_keycard_auth, access_gateway, access_pilot, access_weapons) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, + ACCESS_FORENSICS_LOCKERS, ACCESS_PILOT, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING, + ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_WEAPONS) + minimal_access = list(ACCESS_EVA, ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, + ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING, + ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_PILOT, ACCESS_WEAPONS) minimal_player_age = 21 exp_requirements = 300 exp_type = EXP_TYPE_SECURITY @@ -52,16 +52,16 @@ /datum/job/warden title = "Warden" - flag = WARDEN - department_flag = ENGSEC + flag = JOB_WARDEN + department_flag = JOBCAT_ENGSEC total_positions = 1 spawn_positions = 1 is_security = 1 supervisors = "the head of security" department_head = list("Head of Security") selection_color = "#ffeeee" - access = list(access_security, access_sec_doors, access_brig, access_armory, access_court, access_maint_tunnels, access_morgue, access_weapons) - minimal_access = list(access_security, access_sec_doors, access_brig, access_armory, access_court, access_maint_tunnels, access_weapons) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS) + minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_WEAPONS) minimal_player_age = 21 exp_requirements = 600 exp_type = EXP_TYPE_CREW @@ -96,8 +96,8 @@ /datum/job/detective title = "Detective" - flag = DETECTIVE - department_flag = ENGSEC + flag = JOB_DETECTIVE + department_flag = JOBCAT_ENGSEC total_positions = 1 spawn_positions = 1 is_security = 1 @@ -105,8 +105,8 @@ department_head = list("Head of Security") selection_color = "#ffeeee" alt_titles = list("Forensic Technician") - access = list(access_security, access_sec_doors, access_forensics_lockers, access_morgue, access_maint_tunnels, access_court, access_weapons) - minimal_access = list(access_sec_doors, access_forensics_lockers, access_morgue, access_maint_tunnels, access_court, access_weapons) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_WEAPONS) + minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_WEAPONS) alt_titles = list("Forensic Technician") minimal_player_age = 14 exp_requirements = 600 @@ -155,16 +155,16 @@ /datum/job/officer title = "Security Officer" - flag = OFFICER - department_flag = ENGSEC + flag = JOB_OFFICER + department_flag = JOBCAT_ENGSEC total_positions = 7 spawn_positions = 7 is_security = 1 supervisors = "the head of security" department_head = list("Head of Security") selection_color = "#ffeeee" - access = list(access_security, access_sec_doors, access_brig, access_court, access_maint_tunnels, access_morgue, access_weapons) - minimal_access = list(access_security, access_sec_doors, access_brig, access_court, access_maint_tunnels, access_weapons) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS) + minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_WEAPONS) minimal_player_age = 14 exp_requirements = 600 exp_type = EXP_TYPE_CREW @@ -195,16 +195,16 @@ /datum/job/brigdoc title = "Brig Physician" - flag = BRIGDOC - department_flag = KARMA + flag = JOB_BRIGDOC + department_flag = JOBCAT_KARMA total_positions = 1 spawn_positions = 1 is_security = 1 supervisors = "the head of security" department_head = list("Head of Security") selection_color = "#ffeeee" - access = list(access_medical, access_morgue, access_surgery, access_chemistry, access_virology, access_genetics) - minimal_access = list(access_medical, access_morgue, access_surgery, access_security, access_sec_doors, access_brig, access_court, access_maint_tunnels) + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS) + minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS) outfit = /datum/outfit/job/brigdoc /datum/outfit/job/brigdoc @@ -227,16 +227,16 @@ /datum/job/pilot title = "Security Pod Pilot" - flag = PILOT - department_flag = KARMA + flag = JOB_PILOT + department_flag = JOBCAT_KARMA total_positions = 1 spawn_positions = 1 is_security = 1 supervisors = "the head of security" department_head = list("Head of Security") selection_color = "#ffeeee" - access = list(access_security, access_sec_doors, access_brig, access_court, access_maint_tunnels, access_morgue, access_weapons, access_pilot, access_external_airlocks) - minimal_access = list(access_security, access_sec_doors, access_brig, access_court, access_maint_tunnels, access_weapons, access_pilot, access_external_airlocks) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_PILOT, ACCESS_EXTERNAL_AIRLOCKS) + minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_WEAPONS, ACCESS_PILOT, ACCESS_EXTERNAL_AIRLOCKS) minimal_player_age = 7 outfit = /datum/outfit/job/pilot diff --git a/code/game/jobs/job/silicon.dm b/code/game/jobs/job/silicon.dm index c239c1d3b3b..289b441b4d7 100644 --- a/code/game/jobs/job/silicon.dm +++ b/code/game/jobs/job/silicon.dm @@ -1,7 +1,7 @@ /datum/job/ai title = "AI" - flag = AI - department_flag = ENGSEC + flag = JOB_AI + department_flag = JOBCAT_ENGSEC total_positions = 0 // Not used for AI, see is_position_available below and modules/mob/living/silicon/ai/latejoin.dm spawn_positions = 1 selection_color = "#ccffcc" @@ -22,8 +22,8 @@ /datum/job/cyborg title = "Cyborg" - flag = CYBORG - department_flag = ENGSEC + flag = JOB_CYBORG + department_flag = JOBCAT_ENGSEC total_positions = 1 spawn_positions = 1 supervisors = "your laws and the AI" //Nodrak diff --git a/code/game/jobs/job/supervisor.dm b/code/game/jobs/job/supervisor.dm index feaf1c7972d..a94dfea7c73 100644 --- a/code/game/jobs/job/supervisor.dm +++ b/code/game/jobs/job/supervisor.dm @@ -1,8 +1,8 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) /datum/job/captain title = "Captain" - flag = CAPTAIN - department_flag = ENGSEC + flag = JOB_CAPTAIN + department_flag = JOBCAT_ENGSEC total_positions = 1 spawn_positions = 1 supervisors = "Nanotrasen officials" @@ -57,8 +57,8 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) /datum/job/hop title = "Head of Personnel" - flag = HOP - department_flag = SUPPORT + flag = JOB_HOP + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 supervisors = "the captain" @@ -69,18 +69,18 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) minimal_player_age = 21 exp_requirements = 300 exp_type = EXP_TYPE_COMMAND - access = list(access_security, access_sec_doors, access_brig, access_court, access_forensics_lockers, - access_medical, access_engine, access_change_ids, access_ai_upload, access_eva, access_heads, - access_all_personal_lockers, access_maint_tunnels, access_bar, access_janitor, access_construction, access_morgue, - access_crematorium, access_kitchen, access_cargo, access_cargo_bot, access_mailsorting, access_qm, access_hydroponics, access_lawyer, - access_theatre, access_chapel_office, access_library, access_research, access_mining, access_heads_vault, access_mining_station, - access_clown, access_mime, access_hop, access_RC_announce, access_keycard_auth, access_gateway, access_weapons, access_mineral_storeroom) - minimal_access = list(access_security, access_sec_doors, access_brig, access_court, access_forensics_lockers, - access_medical, access_engine, access_change_ids, access_ai_upload, access_eva, access_heads, - access_all_personal_lockers, access_maint_tunnels, access_bar, access_janitor, access_construction, access_morgue, - access_crematorium, access_kitchen, access_cargo, access_cargo_bot, access_mailsorting, access_qm, access_hydroponics, access_lawyer, - access_theatre, access_chapel_office, access_library, access_research, access_mining, access_heads_vault, access_mining_station, - access_clown, access_mime, access_hop, access_RC_announce, access_keycard_auth, access_gateway, access_weapons, access_mineral_storeroom) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_FORENSICS_LOCKERS, + ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS, + ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE, + ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER, + ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, + ACCESS_CLOWN, ACCESS_MIME, ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_FORENSICS_LOCKERS, + ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS, + ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE, + ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER, + ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, + ACCESS_CLOWN, ACCESS_MIME, ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) outfit = /datum/outfit/job/hop /datum/outfit/job/hop @@ -103,8 +103,8 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) /datum/job/nanotrasenrep title = "Nanotrasen Representative" - flag = NANO - department_flag = KARMA + flag = JOB_NANO + department_flag = JOBCAT_KARMA total_positions = 1 spawn_positions = 1 supervisors = "the command staff" @@ -113,18 +113,18 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) req_admin_notify = 1 is_command = 1 minimal_player_age = 21 - access = list(access_security, access_sec_doors, access_brig, access_court, access_forensics_lockers, - access_medical, access_engine, access_change_ids, access_eva, access_heads, - access_all_personal_lockers, access_maint_tunnels, access_bar, access_janitor, access_construction, access_morgue, - access_crematorium, access_kitchen, access_cargo, access_cargo_bot, access_mailsorting, access_qm, access_hydroponics, access_lawyer, - access_theatre, access_chapel_office, access_library, access_research, access_mining, access_heads_vault, access_mining_station, - access_clown, access_mime, access_hop, access_RC_announce, access_keycard_auth, access_gateway, access_weapons, access_ntrep) - minimal_access = list(access_security, access_sec_doors, access_brig, access_court, access_forensics_lockers, - access_medical, access_engine, access_eva, access_heads, - access_all_personal_lockers, access_maint_tunnels, access_bar, access_janitor, access_construction, access_morgue, - access_crematorium, access_kitchen, access_cargo, access_cargo_bot, access_mailsorting, access_qm, access_hydroponics, access_lawyer, - access_theatre, access_chapel_office, access_library, access_research, access_mining, access_heads_vault, access_mining_station, - access_clown, access_mime, access_RC_announce, access_keycard_auth, access_gateway, access_weapons, access_ntrep) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_FORENSICS_LOCKERS, + ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_EVA, ACCESS_HEADS, + ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE, + ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER, + ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, + ACCESS_CLOWN, ACCESS_MIME, ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_WEAPONS, ACCESS_NTREP) + minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_FORENSICS_LOCKERS, + ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_EVA, ACCESS_HEADS, + ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE, + ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER, + ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, + ACCESS_CLOWN, ACCESS_MIME, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_WEAPONS, ACCESS_NTREP) outfit = /datum/outfit/job/nanotrasenrep /datum/outfit/job/nanotrasenrep @@ -146,8 +146,8 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) /datum/job/blueshield title = "Blueshield" - flag = BLUESHIELD - department_flag = KARMA + flag = JOB_BLUESHIELD + department_flag = JOBCAT_KARMA total_positions = 1 spawn_positions = 1 supervisors = "the Nanotrasen representative" @@ -156,14 +156,14 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) req_admin_notify = 1 is_command = 1 minimal_player_age = 21 - access = list(access_security, access_sec_doors, access_brig, access_court, access_forensics_lockers, - access_medical, access_engine, access_change_ids, access_eva, access_heads, - access_all_personal_lockers, access_maint_tunnels, access_bar, access_janitor, access_construction, access_morgue, - access_crematorium, access_kitchen, access_cargo, access_cargo_bot, access_mailsorting, access_qm, access_hydroponics, access_lawyer, - access_theatre, access_chapel_office, access_library, access_research, access_mining, access_heads_vault, access_mining_station, - access_clown, access_mime, access_hop, access_RC_announce, access_keycard_auth, access_gateway, access_weapons, access_blueshield) - minimal_access = list(access_forensics_lockers, access_sec_doors, access_medical, access_construction, access_engine, access_maint_tunnels, access_research, - access_RC_announce, access_keycard_auth, access_heads, access_blueshield, access_weapons) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_FORENSICS_LOCKERS, + ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_EVA, ACCESS_HEADS, + ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE, + ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER, + ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, + ACCESS_CLOWN, ACCESS_MIME, ACCESS_HOP, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_WEAPONS, ACCESS_BLUESHIELD) + minimal_access = list(ACCESS_FORENSICS_LOCKERS, ACCESS_SEC_DOORS, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_ENGINE, ACCESS_MAINT_TUNNELS, ACCESS_RESEARCH, + ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_HEADS, ACCESS_BLUESHIELD, ACCESS_WEAPONS) outfit = /datum/outfit/job/blueshield /datum/outfit/job/blueshield @@ -189,8 +189,8 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) /datum/job/judge title = "Magistrate" - flag = JUDGE - department_flag = KARMA + flag = JOB_JUDGE + department_flag = JOBCAT_KARMA total_positions = 1 spawn_positions = 1 supervisors = "the Nanotrasen Supreme Court" @@ -199,13 +199,13 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) req_admin_notify = 1 is_legal = 1 minimal_player_age = 30 - access = list(access_security, access_sec_doors, access_brig, access_court, access_forensics_lockers, - access_medical, access_engine, access_change_ids, access_eva, access_heads, - access_all_personal_lockers, access_maint_tunnels, access_bar, access_janitor, access_construction, access_morgue, - access_crematorium, access_kitchen, access_cargo, access_cargo_bot, access_mailsorting, access_qm, access_hydroponics, access_lawyer, - access_theatre, access_chapel_office, access_library, access_research, access_mining, access_heads_vault, access_mining_station, - access_clown, access_mime, access_RC_announce, access_keycard_auth, access_gateway, access_magistrate) - minimal_access = list(access_security, access_sec_doors, access_brig, access_court, access_maint_tunnels, access_lawyer, access_magistrate, access_heads) + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_FORENSICS_LOCKERS, + ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_EVA, ACCESS_HEADS, + ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_MAINT_TUNNELS, ACCESS_BAR, ACCESS_JANITOR, ACCESS_CONSTRUCTION, ACCESS_MORGUE, + ACCESS_CREMATORIUM, ACCESS_KITCHEN, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_QM, ACCESS_HYDROPONICS, ACCESS_LAWYER, + ACCESS_THEATRE, ACCESS_CHAPEL_OFFICE, ACCESS_LIBRARY, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, + ACCESS_CLOWN, ACCESS_MIME, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_MAGISTRATE) + minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_LAWYER, ACCESS_MAGISTRATE, ACCESS_HEADS) outfit = /datum/outfit/job/judge /datum/outfit/job/judge @@ -233,16 +233,16 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0) //var/global/lawyer = 0//Checks for another lawyer //This changed clothes on 2nd lawyer, both IA get the same dreds. /datum/job/lawyer title = "Internal Affairs Agent" - flag = LAWYER - department_flag = SUPPORT + flag = JOB_LAWYER + department_flag = JOBCAT_SUPPORT total_positions = 2 spawn_positions = 2 is_legal = 1 supervisors = "the magistrate" department_head = list("Captain") selection_color = "#ddddff" - access = list(access_lawyer, access_court, access_sec_doors, access_maint_tunnels, access_research, access_medical, access_construction, access_mailsorting) - minimal_access = list(access_lawyer, access_court, access_sec_doors, access_maint_tunnels, access_research, access_medical, access_construction, access_mailsorting) + access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS, ACCESS_RESEARCH, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING) + minimal_access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS, ACCESS_MAINT_TUNNELS, ACCESS_RESEARCH, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING) alt_titles = list("Human Resources Agent") minimal_player_age = 30 exp_requirements = 600 diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm index e4bb22b28af..8371dcfa1c3 100644 --- a/code/game/jobs/job/support.dm +++ b/code/game/jobs/job/support.dm @@ -1,16 +1,16 @@ //Food /datum/job/bartender title = "Bartender" - flag = BARTENDER - department_flag = SUPPORT + flag = JOB_BARTENDER + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 is_service = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_hydroponics, access_bar, access_kitchen, access_morgue, access_weapons, access_mineral_storeroom) - minimal_access = list(access_bar, access_maint_tunnels, access_weapons, access_mineral_storeroom) + access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_BAR, ACCESS_MAINT_TUNNELS, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM) outfit = /datum/outfit/job/bartender /datum/outfit/job/bartender @@ -42,16 +42,16 @@ /datum/job/chef title = "Chef" - flag = CHEF - department_flag = SUPPORT + flag = JOB_CHEF + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 is_service = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_hydroponics, access_bar, access_kitchen, access_morgue) - minimal_access = list(access_kitchen, access_maint_tunnels) + access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE) + minimal_access = list(ACCESS_KITCHEN, ACCESS_MAINT_TUNNELS) alt_titles = list("Cook","Culinary Artist","Butcher") outfit = /datum/outfit/job/chef @@ -76,16 +76,16 @@ /datum/job/hydro title = "Botanist" - flag = BOTANIST - department_flag = SUPPORT + flag = JOB_BOTANIST + department_flag = JOBCAT_SUPPORT total_positions = 3 spawn_positions = 2 is_service = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_hydroponics, access_bar, access_kitchen, access_morgue) - minimal_access = list(access_hydroponics, access_morgue, access_maint_tunnels) + access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE) + minimal_access = list(ACCESS_HYDROPONICS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS) alt_titles = list("Hydroponicist", "Botanical Researcher") outfit = /datum/outfit/job/hydro @@ -110,16 +110,16 @@ //Cargo /datum/job/qm title = "Quartermaster" - flag = QUARTERMASTER - department_flag = SUPPORT + flag = JOB_QUARTERMASTER + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 is_supply = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_maint_tunnels, access_mailsorting, access_cargo, access_cargo_bot, access_qm, access_mint, access_mining, access_mining_station, access_mineral_storeroom) - minimal_access = list(access_maint_tunnels, access_mailsorting, access_cargo, access_cargo_bot, access_qm, access_mint, access_mining, access_mining_station, access_mineral_storeroom) + access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINT, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINT, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM) outfit = /datum/outfit/job/qm /datum/outfit/job/qm @@ -138,16 +138,16 @@ /datum/job/cargo_tech title = "Cargo Technician" - flag = CARGOTECH - department_flag = SUPPORT + flag = JOB_CARGOTECH + department_flag = JOBCAT_SUPPORT total_positions = 2 spawn_positions = 2 is_supply = 1 supervisors = "the quartermaster" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_maint_tunnels, access_mailsorting, access_cargo, access_cargo_bot, access_qm, access_mint, access_mining, access_mining_station, access_mineral_storeroom) - minimal_access = list(access_maint_tunnels, access_cargo, access_cargo_bot, access_mailsorting, access_mineral_storeroom) + access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINT, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) outfit = /datum/outfit/job/cargo_tech /datum/outfit/job/cargo_tech @@ -164,16 +164,16 @@ /datum/job/mining title = "Shaft Miner" - flag = MINER - department_flag = SUPPORT + flag = JOB_MINER + department_flag = JOBCAT_SUPPORT total_positions = 6 spawn_positions = 8 is_supply = 1 supervisors = "the quartermaster" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_maint_tunnels, access_mailsorting, access_cargo, access_cargo_bot, access_qm, access_mint, access_mining, access_mining_station, access_mineral_storeroom) - minimal_access = list(access_mining, access_mint, access_mining_station, access_mailsorting, access_maint_tunnels, access_mineral_storeroom) + access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINT, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MINING, ACCESS_MINT, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) alt_titles = list("Spelunker") outfit = /datum/outfit/job/mining @@ -234,16 +234,16 @@ /datum/job/clown title = "Clown" - flag = CLOWN - department_flag = SUPPORT + flag = JOB_CLOWN + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 is_service = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_clown, access_theatre, access_maint_tunnels) - minimal_access = list(access_clown, access_theatre, access_maint_tunnels) + access = list(ACCESS_CLOWN, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS) + minimal_access = list(ACCESS_CLOWN, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS) outfit = /datum/outfit/job/clown /datum/outfit/job/clown @@ -318,16 +318,16 @@ /datum/job/mime title = "Mime" - flag = MIME - department_flag = SUPPORT + flag = JOB_MIME + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 is_service = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_mime, access_theatre, access_maint_tunnels) - minimal_access = list(access_mime, access_theatre, access_maint_tunnels) + access = list(ACCESS_MIME, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS) + minimal_access = list(ACCESS_MIME, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS) outfit = /datum/outfit/job/mime /datum/outfit/job/mime @@ -370,16 +370,16 @@ /datum/job/janitor title = "Janitor" - flag = JANITOR - department_flag = SUPPORT + flag = JOB_JANITOR + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 is_service = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_janitor, access_maint_tunnels) - minimal_access = list(access_janitor, access_maint_tunnels) + access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS) + minimal_access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS) alt_titles = list("Custodial Technician") outfit = /datum/outfit/job/janitor @@ -396,16 +396,16 @@ //More or less assistants /datum/job/librarian title = "Librarian" - flag = LIBRARIAN - department_flag = SUPPORT + flag = JOB_LIBRARIAN + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 is_service = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_library, access_maint_tunnels) - minimal_access = list(access_library, access_maint_tunnels) + access = list(ACCESS_LIBRARY, ACCESS_MAINT_TUNNELS) + minimal_access = list(ACCESS_LIBRARY, ACCESS_MAINT_TUNNELS) alt_titles = list("Journalist") outfit = /datum/outfit/job/librarian @@ -423,8 +423,8 @@ /datum/job/barber title = "Barber" - flag = BARBER - department_flag = KARMA + flag = JOB_BARBER + department_flag = JOBCAT_KARMA total_positions = 1 spawn_positions = 1 is_service = 1 @@ -432,8 +432,8 @@ department_head = list("Head of Personnel") selection_color = "#dddddd" alt_titles = list("Hair Stylist","Beautician") - access = list(access_maint_tunnels) - minimal_access = list(access_maint_tunnels) + access = list(ACCESS_MAINT_TUNNELS) + minimal_access = list(ACCESS_MAINT_TUNNELS) outfit = /datum/outfit/job/barber /datum/outfit/job/barber diff --git a/code/game/jobs/job/support_chaplain.dm b/code/game/jobs/job/support_chaplain.dm index 02e55a325b2..b39eed344ae 100644 --- a/code/game/jobs/job/support_chaplain.dm +++ b/code/game/jobs/job/support_chaplain.dm @@ -1,16 +1,16 @@ //Due to how large this one is it gets its own file /datum/job/chaplain title = "Chaplain" - flag = CHAPLAIN - department_flag = SUPPORT + flag = JOB_CHAPLAIN + department_flag = JOBCAT_SUPPORT total_positions = 1 spawn_positions = 1 is_service = 1 supervisors = "the head of personnel" department_head = list("Head of Personnel") selection_color = "#dddddd" - access = list(access_morgue, access_chapel_office, access_crematorium, access_maint_tunnels) - minimal_access = list(access_morgue, access_chapel_office, access_crematorium, access_maint_tunnels) + access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_MAINT_TUNNELS) + minimal_access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_MAINT_TUNNELS) outfit = /datum/outfit/job/chaplain diff --git a/code/game/jobs/job/syndicate.dm b/code/game/jobs/job/syndicate.dm index b5757ffe37c..44a04ef7593 100644 --- a/code/game/jobs/job/syndicate.dm +++ b/code/game/jobs/job/syndicate.dm @@ -1,7 +1,7 @@ /datum/job/syndicateofficer title = "Syndicate Officer" - flag = SYNDICATE - department_flag = SYNDICATE + flag = JOB_SYNDICATE + department_flag = JOB_SYNDICATE // This gets its job as its own flag because admin jobs dont have flags total_positions = 5 spawn_positions = 5 supervisors = "the admins" diff --git a/code/game/jobs/jobs.dm b/code/game/jobs/jobs.dm index f45f8f5a22b..09bbf2cf633 100644 --- a/code/game/jobs/jobs.dm +++ b/code/game/jobs/jobs.dm @@ -1,62 +1,4 @@ -var/const/ENGSEC =(1<<0) - -var/const/CAPTAIN =(1<<0) -var/const/HOS =(1<<1) -var/const/WARDEN =(1<<2) -var/const/DETECTIVE =(1<<3) -var/const/OFFICER =(1<<4) -var/const/CHIEF =(1<<5) -var/const/ENGINEER =(1<<6) -var/const/ATMOSTECH =(1<<7) -var/const/AI =(1<<8) -var/const/CYBORG =(1<<9) -var/const/CENTCOM =(1<<10) -var/const/SYNDICATE =(1<<11) - -var/const/MEDSCI =(1<<1) - -var/const/RD =(1<<0) -var/const/SCIENTIST =(1<<1) -var/const/CHEMIST =(1<<2) -var/const/CMO =(1<<3) -var/const/DOCTOR =(1<<4) -var/const/GENETICIST =(1<<5) -var/const/VIROLOGIST =(1<<6) -var/const/PSYCHIATRIST =(1<<7) -var/const/ROBOTICIST =(1<<8) -var/const/PARAMEDIC =(1<<9) -var/const/CORONER =(1<<10) - - -var/const/SUPPORT =(1<<2) - -var/const/HOP =(1<<0) -var/const/BARTENDER =(1<<1) -var/const/BOTANIST =(1<<2) -var/const/CHEF =(1<<3) -var/const/JANITOR =(1<<4) -var/const/LIBRARIAN =(1<<5) -var/const/QUARTERMASTER =(1<<6) -var/const/CARGOTECH =(1<<7) -var/const/MINER =(1<<8) -var/const/LAWYER =(1<<9) -var/const/CHAPLAIN =(1<<10) -var/const/CLOWN =(1<<11) -var/const/MIME =(1<<12) -var/const/CIVILIAN =(1<<13) - - -var/const/KARMA =(1<<3) - -var/const/NANO =(1<<0) -var/const/BLUESHIELD =(1<<1) -var/const/BARBER =(1<<3) -var/const/MECHANIC =(1<<4) -var/const/BRIGDOC =(1<<5) -var/const/JUDGE =(1<<6) -var/const/PILOT =(1<<7) - var/list/assistant_occupations = list( ) diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm index 12ad51c8b28..eb06f55cc7b 100644 --- a/code/game/machinery/ai_slipper.dm +++ b/code/game/machinery/ai_slipper.dm @@ -14,7 +14,7 @@ var/cooldown_time = 0 var/cooldown_timeleft = 0 var/cooldown_on = FALSE - req_access = list(access_ai_upload) + req_access = list(ACCESS_AI_UPLOAD) /obj/machinery/ai_slipper/power_change() if(stat & BROKEN) diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm index d13406eee99..2f6106a95a0 100644 --- a/code/game/machinery/alarm.dm +++ b/code/game/machinery/alarm.dm @@ -79,7 +79,7 @@ idle_power_usage = 4 active_power_usage = 8 power_channel = ENVIRON - req_one_access = list(access_atmospherics, access_engine_equip) + req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_ENGINE_EQUIP) max_integrity = 250 integrity_failure = 80 armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) @@ -128,7 +128,7 @@ /obj/machinery/alarm/syndicate //general syndicate access report_danger_level = FALSE remote_control = FALSE - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) req_one_access = list() /obj/machinery/alarm/monitor/server diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index 83a12a6ff81..71ddcde9251 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -16,7 +16,7 @@ density = 1 icon = 'icons/obj/cloning.dmi' icon_state = "pod_0" - req_access = list(access_genetics) //For premature unlocking. + req_access = list(ACCESS_GENETICS) //For premature unlocking. var/mob/living/carbon/human/occupant var/heal_level //The clone is released once its health reaches this level. var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine. diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm index e9cf036d3ca..c7482bc140d 100644 --- a/code/game/machinery/computer/aifixer.dm +++ b/code/game/machinery/computer/aifixer.dm @@ -4,7 +4,7 @@ icon_keyboard = "rd_key" icon_screen = "ai-fixer" circuit = /obj/item/circuitboard/aifixer - req_access = list(access_captain, access_robotics, access_heads) + req_access = list(ACCESS_CAPTAIN, ACCESS_ROBOTICS, ACCESS_HEADS) var/mob/living/silicon/ai/occupant = null var/active = 0 diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index 84ab146589e..7b07cda3bbb 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -7,7 +7,7 @@ density = 1 anchored = 1.0 circuit = /obj/item/circuitboard/atmoscontrol - req_access = list(access_atmospherics) + req_access = list(ACCESS_ATMOSPHERICS) var/list/monitored_alarm_ids = null var/datum/nano_module/atmos_control/atmos_control diff --git a/code/game/machinery/computer/brigcells.dm b/code/game/machinery/computer/brigcells.dm index d322e99ae4b..378c10d9783 100644 --- a/code/game/machinery/computer/brigcells.dm +++ b/code/game/machinery/computer/brigcells.dm @@ -8,7 +8,7 @@ active_power_usage = 500 circuit = /obj/item/circuitboard/brigcells light_color = LIGHT_COLOR_DARKRED - req_access = list(access_brig) + req_access = list(ACCESS_BRIG) /obj/machinery/computer/brigcells/attack_ai(mob/user) attack_hand(user) diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 9e8bc08f9b2..545b0f61139 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -244,7 +244,7 @@ name = "Circuit Board (RD Console)" desc = "Swipe a Scientist level ID or higher to reconfigure." build_path = /obj/machinery/computer/rdconsole/core - req_access = list(access_tox) // This is for adjusting the type of computer we're building - in case something messes up the pre-existing robotics or mechanics consoles + req_access = list(ACCESS_TOX) // This is for adjusting the type of computer we're building - in case something messes up the pre-existing robotics or mechanics consoles var/access_types = list("R&D Core", "Robotics", "E.X.P.E.R.I-MENTOR", "Mechanics", "Public") id = 1 /obj/item/circuitboard/rdconsole/robotics diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 45f390082a1..805ce19aab5 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -18,27 +18,27 @@ ..() /obj/machinery/computer/security/proc/generate_network_access() - available_networks["SS13"] = list(access_hos,access_captain) - available_networks["Telecomms"] = list(access_hos,access_captain) - available_networks["Research Outpost"] = list(access_rd,access_hos,access_captain) - available_networks["Mining Outpost"] = list(access_qm,access_hop,access_hos,access_captain) - available_networks["Research"] = list(access_rd,access_hos,access_captain) - available_networks["Prison"] = list(access_hos,access_captain) - available_networks["Labor Camp"] = list(access_hos,access_captain) - available_networks["Interrogation"] = list(access_hos,access_captain) - available_networks["Atmosphere Alarms"] = list(access_ce,access_hos,access_captain) - available_networks["Fire Alarms"] = list(access_ce,access_hos,access_captain) - available_networks["Power Alarms"] = list(access_ce,access_hos,access_captain) - available_networks["Supermatter"] = list(access_ce,access_hos,access_captain) - available_networks["MiniSat"] = list(access_rd,access_hos,access_captain) - available_networks["Singularity"] = list(access_ce,access_hos,access_captain) - available_networks["Anomaly Isolation"] = list(access_rd,access_hos,access_captain) - available_networks["Toxins"] = list(access_rd,access_hos,access_captain) - available_networks["Telepad"] = list(access_rd,access_hos,access_captain) - available_networks["TestChamber"] = list(access_rd,access_hos,access_captain) - available_networks["ERT"] = list(access_cent_specops_commander,access_cent_commander) - available_networks["CentComm"] = list(access_cent_security,access_cent_commander) - available_networks["Thunderdome"] = list(access_cent_thunder,access_cent_commander) + available_networks["SS13"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Telecomms"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Research Outpost"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Mining Outpost"] = list(ACCESS_QM,ACCESS_HOP,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Research"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Prison"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Labor Camp"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Interrogation"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Atmosphere Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Fire Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Power Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Supermatter"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["MiniSat"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Singularity"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Anomaly Isolation"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Toxins"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Telepad"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["TestChamber"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["ERT"] = list(ACCESS_CENT_SPECOPS_COMMANDER,ACCESS_CENT_COMMANDER) + available_networks["CentComm"] = list(ACCESS_CENT_SECURITY,ACCESS_CENT_COMMANDER) + available_networks["Thunderdome"] = list(ACCESS_CENT_THUNDER,ACCESS_CENT_COMMANDER) /obj/machinery/computer/security/Destroy() if(watchers.len) diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index 86515e5ddea..7ffc141dcb4 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -7,7 +7,7 @@ var/time_last_changed_position = 0 desc = "Terminal for programming Nanotrasen employee ID cards to access parts of the station." icon_keyboard = "id_key" icon_screen = "id" - req_access = list(access_change_ids) + req_access = list(ACCESS_CHANGE_IDS) circuit = /obj/item/circuitboard/card light_color = LIGHT_COLOR_LIGHTBLUE var/obj/item/card/id/scan = null @@ -133,7 +133,7 @@ var/time_last_changed_position = 0 if(!istype(id_card)) return ..() - if(!scan && access_change_ids in id_card.access) + if(!scan && ACCESS_CHANGE_IDS in id_card.access) user.drop_item() id_card.loc = src scan = id_card @@ -200,7 +200,7 @@ var/time_last_changed_position = 0 return 1 if(!scan.assignment) return 0 - if(access_captain in scan.access) + if(ACCESS_CAPTAIN in scan.access) return 1 if(!targetjob || !targetjob.title) return 0 @@ -598,7 +598,7 @@ var/time_last_changed_position = 0 /obj/machinery/computer/card/centcom name = "\improper CentComm identification computer" circuit = /obj/item/circuitboard/card/centcom - req_access = list(access_cent_commander) + req_access = list(ACCESS_CENT_COMMANDER) change_position_cooldown = -1 blacklisted_full = list() blacklisted_partial = list() @@ -615,14 +615,14 @@ var/time_last_changed_position = 0 target_dept = TARGET_DEPT_SEC icon_screen = "idhos" light_color = LIGHT_COLOR_RED - req_access = list(access_hos) + req_access = list(ACCESS_HOS) circuit = /obj/item/circuitboard/card/minor/hos /obj/machinery/computer/card/minor/cmo name = "medical management console" target_dept = TARGET_DEPT_MED icon_screen = "idcmo" - req_access = list(access_cmo) + req_access = list(ACCESS_CMO) circuit = /obj/item/circuitboard/card/minor/cmo /obj/machinery/computer/card/minor/rd @@ -630,7 +630,7 @@ var/time_last_changed_position = 0 target_dept = TARGET_DEPT_SCI icon_screen = "idrd" light_color = LIGHT_COLOR_PINK - req_access = list(access_rd) + req_access = list(ACCESS_RD) circuit = /obj/item/circuitboard/card/minor/rd /obj/machinery/computer/card/minor/ce @@ -638,5 +638,5 @@ var/time_last_changed_position = 0 target_dept = TARGET_DEPT_ENG icon_screen = "idce" light_color = COLOR_YELLOW - req_access = list(access_ce) + req_access = list(ACCESS_CE) circuit = /obj/item/circuitboard/card/minor/ce \ No newline at end of file diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index 539d4dfc0ed..c06cbb32a04 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -4,7 +4,7 @@ icon_keyboard = "med_key" icon_screen = "dna" circuit = /obj/item/circuitboard/cloning - req_access = list(access_heads) //Only used for record deletion right now. + req_access = list(ACCESS_HEADS) //Only used for record deletion right now. var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning. var/list/pods = list() //Linked cloning pods. var/temp = "" diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index be27b0600cf..0a9627fa38b 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -13,7 +13,7 @@ desc = "This can be used for various important functions. Still under developement." icon_keyboard = "tech_key" icon_screen = "comm" - req_access = list(access_heads) + req_access = list(ACCESS_HEADS) circuit = /obj/item/circuitboard/communications var/prints_intercept = 1 var/authenticated = COMM_AUTHENTICATION_NONE @@ -87,7 +87,7 @@ if(allowed(usr)) authenticated = COMM_AUTHENTICATION_MIN - if(access_captain in access) + if(ACCESS_CAPTAIN in access) authenticated = COMM_AUTHENTICATION_MAX var/mob/living/carbon/human/H = usr var/obj/item/card/id = H.get_idcard(TRUE) @@ -132,7 +132,7 @@ var/obj/item/pda/pda = I I = pda.id if(I && istype(I)) - if(access_captain in I.access) + if(ACCESS_CAPTAIN in I.access) change_security_level(text2num(href_list["level"])) else to_chat(usr, "You are not authorized to do this.") diff --git a/code/game/machinery/computer/depot.dm b/code/game/machinery/computer/depot.dm index 02be740b7d3..382e1325f9d 100644 --- a/code/game/machinery/computer/depot.dm +++ b/code/game/machinery/computer/depot.dm @@ -12,7 +12,7 @@ icon_keyboard = "syndie_key" icon_screen = "tcboss" light_color = LIGHT_COLOR_PURE_CYAN - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) var/security_lockout = FALSE var/sound_yes = 'sound/machines/twobeep.ogg' var/sound_no = 'sound/machines/buzz-sigh.ogg' @@ -186,7 +186,7 @@ /obj/machinery/computer/syndicate_depot/shieldcontrol name = "shield control computer" icon_screen = "accelerator" - req_access = list(access_syndicate_leader) + req_access = list(ACCESS_SYNDICATE_LEADER) alerts_when_broken = TRUE var/area/syndicate_depot/perimeter/perimeterarea diff --git a/code/game/machinery/computer/honkputer.dm b/code/game/machinery/computer/honkputer.dm index 842a65b5f50..94787d61020 100644 --- a/code/game/machinery/computer/honkputer.dm +++ b/code/game/machinery/computer/honkputer.dm @@ -6,7 +6,7 @@ icon_keyboard = "key_honk" icon_screen = "honkcomms" light_color = LIGHT_COLOR_PINK - req_access = list(access_clown) + req_access = list(ACCESS_CLOWN) circuit = /obj/item/circuitboard/HONKputer var/authenticated = 0 var/message_cooldown = 0 diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 3a71ab6c6c8..5defd5e8a69 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -10,7 +10,7 @@ desc = "This can be used to check medical records." icon_keyboard = "med_key" icon_screen = "medcomp" - req_one_access = list(access_medical, access_forensics_lockers) + req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS) circuit = /obj/item/circuitboard/med_data var/obj/item/card/id/scan = null var/authenticated = null diff --git a/code/game/machinery/computer/pod.dm b/code/game/machinery/computer/pod.dm index fdb9f934516..6ea095028a2 100644 --- a/code/game/machinery/computer/pod.dm +++ b/code/game/machinery/computer/pod.dm @@ -268,7 +268,7 @@ /obj/machinery/computer/pod/old/syndicate name = "external airlock controls" desc = "The Syndicate operate on a tight budget. Operates external airlocks." - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) circuit = /obj/item/circuitboard/syndicatedoor light_color = "#00FFFF" diff --git a/code/game/machinery/computer/pod_tracking_console.dm b/code/game/machinery/computer/pod_tracking_console.dm index ae6e7a4f36a..cd6450c700c 100644 --- a/code/game/machinery/computer/pod_tracking_console.dm +++ b/code/game/machinery/computer/pod_tracking_console.dm @@ -4,7 +4,7 @@ icon_keyboard = "tech_key" icon_screen = "rdcomp" light_color = LIGHT_COLOR_PURPLE - req_access = list(access_robotics) + req_access = list(ACCESS_ROBOTICS) circuit = /obj/item/circuitboard/pod_locater /obj/machinery/computer/podtracker/attack_ai(var/mob/user as mob) diff --git a/code/game/machinery/computer/prisoner.dm b/code/game/machinery/computer/prisoner.dm index bcabdeb511e..42903d48ecd 100644 --- a/code/game/machinery/computer/prisoner.dm +++ b/code/game/machinery/computer/prisoner.dm @@ -3,7 +3,7 @@ icon = 'icons/obj/computer.dmi' icon_keyboard = "security_key" icon_screen = "explosive" - req_access = list(access_armory) + req_access = list(ACCESS_ARMORY) circuit = /obj/item/circuitboard/prisoner var/id = 0.0 var/temp = null diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index 8def58c370b..fb4683ba3a9 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -4,7 +4,7 @@ icon = 'icons/obj/computer.dmi' icon_keyboard = "tech_key" icon_screen = "robot" - req_access = list(access_robotics) + req_access = list(ACCESS_ROBOTICS) circuit = /obj/item/circuitboard/robotics var/temp = null diff --git a/code/game/machinery/computer/salvage_ship.dm b/code/game/machinery/computer/salvage_ship.dm index ba7e7200c49..df22a3cad14 100644 --- a/code/game/machinery/computer/salvage_ship.dm +++ b/code/game/machinery/computer/salvage_ship.dm @@ -6,7 +6,7 @@ icon = 'icons/obj/computer.dmi' icon_keyboard = "syndie_key" icon_screen = "syndishuttle" - req_access = list(access_salvage_captain) + req_access = list(ACCESS_SALVAGE_CAPTAIN) var/area/curr_location var/moving = 0 var/lastMove = 0 diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm index ce872e598f4..90ac427743d 100644 --- a/code/game/machinery/computer/security.dm +++ b/code/game/machinery/computer/security.dm @@ -7,7 +7,7 @@ desc = "Used to view and edit personnel's security records." icon_keyboard = "security_key" icon_screen = "security" - req_one_access = list(access_security, access_forensics_lockers) + req_one_access = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS) circuit = /obj/item/circuitboard/secure_data var/obj/item/card/id/scan = null var/authenticated = null diff --git a/code/game/machinery/computer/skills.dm b/code/game/machinery/computer/skills.dm index 125b5294860..e3c2918f335 100644 --- a/code/game/machinery/computer/skills.dm +++ b/code/game/machinery/computer/skills.dm @@ -10,7 +10,7 @@ icon_screen = "medlaptop" density = 0 light_color = LIGHT_COLOR_GREEN - req_one_access = list(access_heads) + req_one_access = list(ACCESS_HEADS) circuit = /obj/item/circuitboard/skills var/obj/item/card/id/scan = null var/authenticated = null diff --git a/code/game/machinery/computer/specops_shuttle.dm b/code/game/machinery/computer/specops_shuttle.dm index 0602757944f..711aec59db5 100644 --- a/code/game/machinery/computer/specops_shuttle.dm +++ b/code/game/machinery/computer/specops_shuttle.dm @@ -17,7 +17,7 @@ var/specops_shuttle_timeleft = 0 icon_keyboard = "security_key" icon_screen = "syndishuttle" light_color = LIGHT_COLOR_PURE_CYAN - req_access = list(access_cent_specops) + req_access = list(ACCESS_CENT_SPECOPS) // req_access = list(ACCESS_CENT_SPECOPS) var/temp = null var/hacked = 0 diff --git a/code/game/machinery/computer/syndicate_specops_shuttle.dm b/code/game/machinery/computer/syndicate_specops_shuttle.dm index 92ed453e71e..71bfe9f43ab 100644 --- a/code/game/machinery/computer/syndicate_specops_shuttle.dm +++ b/code/game/machinery/computer/syndicate_specops_shuttle.dm @@ -16,7 +16,7 @@ var/syndicate_elite_shuttle_timeleft = 0 icon_keyboard = "syndie_key" icon_screen = "syndishuttle" light_color = LIGHT_COLOR_PURE_CYAN - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) var/temp = null var/hacked = 0 var/allowedtocall = 0 diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index 3df7cc8c6ec..bec6e329ad8 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -403,7 +403,7 @@ var/proportion = 10 * min(1/beaker.volume, 1) // Yes, this means you can get more bang for your buck with a beaker of SF vs a patch // But it also means a giant beaker of SF won't heal people ridiculously fast 4 cheap - beaker.reagents.reaction(occupant, TOUCH, proportion) + beaker.reagents.reaction(occupant, REAGENT_TOUCH, proportion) beaker.reagents.trans_to(occupant, 1, 10) next_trans++ if(next_trans == 17) diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index abf6dc725b3..6c7b3b9c5a6 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -17,7 +17,7 @@ circuit = /obj/item/circuitboard/cryopodcontrol density = 0 interact_offline = 1 - req_one_access = list(access_heads, access_armory) //Heads of staff or the warden can go here to claim recover items from their department that people went were cryodormed with. + req_one_access = list(ACCESS_HEADS, ACCESS_ARMORY) //Heads of staff or the warden can go here to claim recover items from their department that people went were cryodormed with. resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF flags = NODECONSTRUCT var/mode = null diff --git a/code/game/machinery/defib_mount.dm b/code/game/machinery/defib_mount.dm index 267ea5da392..5e173952a8e 100644 --- a/code/game/machinery/defib_mount.dm +++ b/code/game/machinery/defib_mount.dm @@ -11,7 +11,7 @@ anchored = TRUE idle_power_usage = 1 power_channel = EQUIP - req_one_access = list(access_medical, access_heads) //used to control clamps + req_one_access = list(ACCESS_MEDICAL, ACCESS_HEADS) //used to control clamps var/obj/item/defibrillator/defib //this mount's defibrillator var/clamps_locked = FALSE //if true, and a defib is loaded, it can't be removed without unlocking the clamps diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm index 01d90795a87..4b36c13b034 100644 --- a/code/game/machinery/doors/airlock_electronics.dm +++ b/code/game/machinery/doors/airlock_electronics.dm @@ -5,7 +5,7 @@ w_class = WEIGHT_CLASS_SMALL materials = list(MAT_METAL=50, MAT_GLASS=50) origin_tech = "engineering=2;programming=1" - req_access = list(access_engine) + req_access = list(ACCESS_ENGINE) toolspeed = 1 usesound = 'sound/items/deconstruct.ogg' var/list/conf_access = null diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm index b53a0139fb2..59d99f501c0 100644 --- a/code/game/machinery/doors/airlock_types.dm +++ b/code/game/machinery/doors/airlock_types.dm @@ -180,7 +180,7 @@ PlasmaBurn(exposed_temperature) /obj/machinery/door/airlock/plasma/proc/PlasmaBurn(temperature) - atmos_spawn_air(SPAWN_HEAT | SPAWN_TOXINS, 500) + atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, 500) var/obj/structure/door_assembly/DA DA = new /obj/structure/door_assembly(loc) if(glass) diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index 8bd7af5dad4..9b1481fe739 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -16,7 +16,7 @@ icon = 'icons/obj/status_display.dmi' icon_state = "frame" desc = "A remote control for a door." - req_access = list(access_brig) + req_access = list(ACCESS_BRIG) anchored = 1 // can't pick it up density = 0 // can walk through it. var/id = null // id of door it controls. diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index d562c6b78ea..51bc7fdc457 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -4,9 +4,6 @@ #define CONSTRUCTION_GUTTED 3 //Wires are removed, circuit ready to remove #define CONSTRUCTION_NOCIRCUIT 4 //Circuit board removed, can safely weld apart -/var/const/FD_OPEN = 1 -/var/const/FD_CLOSED = 2 - /obj/machinery/door/firedoor name = "firelock" desc = "Apply crowbar." diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index fe104ff794c..1bf5a31d336 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -324,7 +324,7 @@ /obj/machinery/door/window/brigdoor/security/cell name = "cell door" desc = "For keeping in criminal scum." - req_access = list(access_brig) + req_access = list(ACCESS_BRIG) /obj/machinery/door/window/clockwork name = "brass windoor" diff --git a/code/game/machinery/guestpass.dm b/code/game/machinery/guestpass.dm index 35d5a6546d4..2c37a3274c7 100644 --- a/code/game/machinery/guestpass.dm +++ b/code/game/machinery/guestpass.dm @@ -193,5 +193,5 @@ /obj/machinery/computer/guestpass/hop/get_changeable_accesses() . = ..() - if(. && access_change_ids in .) + if(. && ACCESS_CHANGE_IDS in .) return get_all_accesses() diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index 10708ff9c0e..d1df960716c 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -31,7 +31,7 @@ Possible to do for anyone motivated enough: #define RANGE_BASED 0 #define AREA_BASED 1 -var/const/HOLOPAD_MODE = RANGE_BASED +#define HOLOPAD_MODE RANGE_BASED var/list/holopads = list() diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm index c93c4e099b2..d5fa5277592 100644 --- a/code/game/machinery/navbeacon.dm +++ b/code/game/machinery/navbeacon.dm @@ -18,7 +18,7 @@ var/list/codes // assoc. list of transponder codes var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value" - req_access = list(access_engine, access_robotics) + req_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) /obj/machinery/navbeacon/New() ..() diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm index b4241b5f19d..7443c1d53c7 100644 --- a/code/game/machinery/pipe/construction.dm +++ b/code/game/machinery/pipe/construction.dm @@ -118,7 +118,7 @@ src.flipped = 1 var/obj/machinery/atmospherics/binary/circulator/circP = make_from - if(istype(circP) && circP.side == circP.CIRC_RIGHT) + if(istype(circP) && circP.side == CIRC_RIGHT) src.flipped = 1 else @@ -157,7 +157,7 @@ if(istype(triP) && triP.flipped) icon_state = "m_[icon_state]" var/obj/machinery/atmospherics/binary/circulator/circP = make_from - if(istype(circP) && circP.side == circP.CIRC_RIGHT) + if(istype(circP) && circP.side == CIRC_RIGHT) icon_state = "m_[icon_state]" if(istype(make_from, /obj/machinery/atmospherics/pipe/simple/heat_exchanging)) resistance_flags |= FIRE_PROOF | LAVA_PROOF @@ -435,7 +435,7 @@ if(PIPE_CIRCULATOR) //circulator var/obj/machinery/atmospherics/binary/circulator/C = new(src.loc) if(flipped) - C.side = C.CIRC_RIGHT + C.side = CIRC_RIGHT if(pipename) C.name = pipename C.on_construction(C.dir, C.initialize_directions, color) diff --git a/code/game/machinery/portable_tag_turret.dm b/code/game/machinery/portable_tag_turret.dm index a077def9136..0bd368bd75b 100644 --- a/code/game/machinery/portable_tag_turret.dm +++ b/code/game/machinery/portable_tag_turret.dm @@ -22,7 +22,7 @@ if(/obj/item/gun/energy/laser/tag/blue) eprojectile = /obj/item/gun/energy/laser/tag/blue lasercolor = "b" - req_access = list(access_maint_tunnels, access_theatre) + req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) check_arrest = 0 check_records = 0 check_weapons = 1 @@ -33,7 +33,7 @@ if(/obj/item/gun/energy/laser/tag/red) eprojectile = /obj/item/gun/energy/laser/tag/red lasercolor = "r" - req_access = list(access_maint_tunnels, access_theatre) + req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) check_arrest = 0 check_records = 0 check_weapons = 1 diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm index 68c029f2111..d08afb91a3d 100644 --- a/code/game/machinery/portable_turret.dm +++ b/code/game/machinery/portable_turret.dm @@ -79,7 +79,7 @@ health = 200 enabled = 1 lethal = 1 - req_access = list(access_cent_commander) + req_access = list(ACCESS_CENT_COMMANDER) installation = /obj/item/gun/energy/pulse/turret /obj/machinery/porta_turret/stationary @@ -91,7 +91,7 @@ ..() if(req_access && req_access.len) req_access.Cut() - req_one_access = list(access_security, access_heads) + req_one_access = list(ACCESS_SECURITY, ACCESS_HEADS) one_access = 1 //Sets up a spark system @@ -109,7 +109,7 @@ ..() if(req_one_access && req_one_access.len) req_one_access.Cut() - req_access = list(access_cent_specops) + req_access = list(ACCESS_CENT_SPECOPS) one_access = 0 /obj/machinery/porta_turret/proc/setup() @@ -1034,7 +1034,7 @@ var/list/turret_icons ..() if(req_one_access && req_one_access.len) req_one_access.Cut() - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) one_access = 0 /obj/machinery/porta_turret/syndicate/update_icon() diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm index eecdc0e4f4a..02479fbb7aa 100644 --- a/code/game/machinery/recycler.dm +++ b/code/game/machinery/recycler.dm @@ -1,4 +1,4 @@ -var/const/SAFETY_COOLDOWN = 100 +#define SAFETY_COOLDOWN 100 /obj/machinery/recycler name = "recycler" diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm index 2cc86ee1b3a..d1a41335d62 100644 --- a/code/game/machinery/requests_console.dm +++ b/code/game/machinery/requests_console.dm @@ -302,7 +302,7 @@ var/list/obj/machinery/requests_console/allConsoles = list() updateUsrDialog() if(screen == RCS_ANNOUNCE) var/obj/item/card/id/ID = I - if(access_RC_announce in ID.GetAccess()) + if(ACCESS_RC_ANNOUNCE in ID.GetAccess()) announceAuth = 1 announcement.announcer = ID.assignment ? "[ID.assignment] [ID.registered_name]" : ID.registered_name else diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm index 3cb06daab94..60946c3a2fe 100644 --- a/code/game/machinery/shieldgen.dm +++ b/code/game/machinery/shieldgen.dm @@ -133,7 +133,7 @@ opacity = FALSE anchored = 0 pressure_resistance = 2*ONE_ATMOSPHERE - req_access = list(access_engine) + req_access = list(ACCESS_ENGINE) var/const/max_health = 100 var/health = max_health var/active = 0 @@ -309,7 +309,7 @@ icon_state = "Shield_Gen" anchored = 0 density = 1 - req_access = list(access_teleporter) + req_access = list(ACCESS_TELEPORTER) var/active = 0 var/power = 0 var/state = 0 diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index 3437bafeb3b..2a99037a51e 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -25,7 +25,7 @@ var/broken = FALSE var/secure = FALSE //set to true to enable ID locking var/shocked = FALSE//is it shocking anyone that touches it? - req_access = list(access_eva) //the ID needed if ID lock is enabled + req_access = list(ACCESS_EVA) //the ID needed if ID lock is enabled var/datum/wires/suitstorage/wires var/uv = FALSE @@ -55,7 +55,7 @@ helmet_type = /obj/item/clothing/head/helmet/space/capspace mask_type = /obj/item/clothing/mask/gas storage_type = /obj/item/tank/jetpack/oxygen/captain - req_access = list(access_captain) + req_access = list(ACCESS_CAPTAIN) /obj/machinery/suit_storage_unit/captain/secure secure = TRUE @@ -65,7 +65,7 @@ suit_type = /obj/item/clothing/suit/space/hardsuit/engine mask_type = /obj/item/clothing/mask/breath magboots_type = /obj/item/clothing/shoes/magboots - req_access = list(access_engine_equip) + req_access = list(ACCESS_ENGINE_EQUIP) /obj/machinery/suit_storage_unit/engine/secure secure = TRUE @@ -75,7 +75,7 @@ suit_type = /obj/item/clothing/suit/space/hardsuit/engine/elite mask_type = /obj/item/clothing/mask/gas magboots_type = /obj/item/clothing/shoes/magboots/advance - req_access = list(access_ce) + req_access = list(ACCESS_CE) /obj/machinery/suit_storage_unit/ce/secure secure = TRUE @@ -84,20 +84,20 @@ name = "security suit storage unit" suit_type = /obj/item/clothing/suit/space/hardsuit/security mask_type = /obj/item/clothing/mask/gas/sechailer - req_access = list(access_security) + req_access = list(ACCESS_SECURITY) /obj/machinery/suit_storage_unit/security/secure secure = TRUE /obj/machinery/suit_storage_unit/security/pod_pilot - req_access = list(access_pilot) + req_access = list(ACCESS_PILOT) /obj/machinery/suit_storage_unit/atmos name = "atmospherics suit storage unit" suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos mask_type = /obj/item/clothing/mask/gas magboots_type = /obj/item/clothing/shoes/magboots - req_access = list(access_atmospherics) + req_access = list(ACCESS_ATMOSPHERICS) /obj/machinery/suit_storage_unit/atmos/secure secure = TRUE @@ -106,7 +106,7 @@ name = "mining suit storage unit" suit_type = /obj/item/clothing/suit/space/hardsuit/mining mask_type = /obj/item/clothing/mask/breath - req_access = list(access_mining_station) + req_access = list(ACCESS_MINING_STATION) /obj/machinery/suit_storage_unit/mining/secure secure = TRUE @@ -115,12 +115,12 @@ name = "mining suit storage unit" suit_type = /obj/item/clothing/suit/hooded/explorer mask_type = /obj/item/clothing/mask/gas/explorer - req_access = list(access_mining_station) + req_access = list(ACCESS_MINING_STATION) /obj/machinery/suit_storage_unit/cmo suit_type = /obj/item/clothing/suit/space/hardsuit/medical mask_type = /obj/item/clothing/mask/breath - req_access = list(access_cmo) + req_access = list(ACCESS_CMO) /obj/machinery/suit_storage_unit/cmo/secure secure = TRUE @@ -134,7 +134,7 @@ name = "clown suit storage unit" suit_type = /obj/item/clothing/suit/space/eva/clown helmet_type = /obj/item/clothing/head/helmet/space/eva/clown - req_access = list(access_clown) + req_access = list(ACCESS_CLOWN) /obj/machinery/suit_storage_unit/clown/secure secure = TRUE @@ -143,7 +143,7 @@ name = "mime suit storage unit" suit_type = /obj/item/clothing/suit/space/eva/mime helmet_type = /obj/item/clothing/head/helmet/space/eva/mime - req_access = list(access_mime) + req_access = list(ACCESS_MIME) /obj/machinery/suit_storage_unit/mime/secure secure = TRUE @@ -153,14 +153,14 @@ suit_type = /obj/item/clothing/suit/space/hardsuit/syndi mask_type = /obj/item/clothing/mask/gas/syndicate storage_type = /obj/item/tank/jetpack/oxygen/harness - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) safeties = FALSE //in a syndicate base, everything can be used as a murder weapon at a moment's notice. /obj/machinery/suit_storage_unit/syndicate/secure secure = TRUE /obj/machinery/suit_storage_unit/ert - req_access = list(access_cent_general) + req_access = list(ACCESS_CENT_GENERAL) /obj/machinery/suit_storage_unit/ert/command suit_type = /obj/item/clothing/suit/space/hardsuit/ert/commander @@ -198,7 +198,7 @@ /obj/machinery/suit_storage_unit/telecoms mask_type = /obj/item/clothing/mask/breath storage_type = /obj/item/tank/jetpack/void - req_access = list(access_tcomsat) + req_access = list(ACCESS_TCOMSAT) /obj/machinery/suit_storage_unit/telecoms/secure secure = TRUE diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm index 28a35680cdb..6d599ae9113 100644 --- a/code/game/machinery/syndicatebomb.dm +++ b/code/game/machinery/syndicatebomb.dm @@ -284,7 +284,7 @@ /obj/machinery/syndicatebomb/self_destruct name = "self destruct device" desc = "Do not taunt. Warranty invalid if exposed to high temperature. Not suitable for agents under 3 years of age." - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) payload = /obj/item/bombcore/large can_unanchor = FALSE var/explosive_wall_group = EXPLOSIVE_WALL_GROUP_SYNDICATE_BASE // If set, this bomb will also cause explosive walls in the same group to explode diff --git a/code/game/machinery/telecomms/logbrowser.dm b/code/game/machinery/telecomms/logbrowser.dm index ec5d63804f6..f0b5a945f92 100644 --- a/code/game/machinery/telecomms/logbrowser.dm +++ b/code/game/machinery/telecomms/logbrowser.dm @@ -16,7 +16,7 @@ var/universal_translate = 0 // set to 1 if it can translate nonhuman speech - req_access = list(access_tcomsat) + req_access = list(ACCESS_TCOMSAT) circuit = /obj/item/circuitboard/comm_server attack_hand(mob/user as mob) diff --git a/code/game/machinery/telecomms/traffic_control.dm b/code/game/machinery/telecomms/traffic_control.dm index 1fe1490442a..6f12158452b 100644 --- a/code/game/machinery/telecomms/traffic_control.dm +++ b/code/game/machinery/telecomms/traffic_control.dm @@ -3,7 +3,7 @@ light_color = LIGHT_COLOR_DARKGREEN - req_access = list(access_tcomsat) + req_access = list(ACCESS_TCOMSAT) circuit = /obj/item/circuitboard/comm_traffic // NTTC diff --git a/code/game/machinery/turret_control.dm b/code/game/machinery/turret_control.dm index 7336704555c..2876188baa2 100644 --- a/code/game/machinery/turret_control.dm +++ b/code/game/machinery/turret_control.dm @@ -30,7 +30,7 @@ var/syndicate = 0 var/faction = "" // Turret controls can only access turrets that are in the same faction - req_access = list(access_ai_upload) + req_access = list(ACCESS_AI_UPLOAD) /obj/machinery/turretid/stun enabled = 1 @@ -57,7 +57,7 @@ syndicate = 1 faction = "syndicate" - req_access = list(access_syndicate_leader) + req_access = list(ACCESS_SYNDICATE_LEADER) /obj/machinery/turretid/Destroy() if(control_area) diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 03b325dd649..2e3d0667765 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -860,7 +860,7 @@ refill_canister = /obj/item/vending_refill/boozeomat /obj/machinery/vending/boozeomat/syndicate_access - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) /obj/machinery/vending/boozeomat/Initialize(mapload) component_parts = list() @@ -1168,7 +1168,7 @@ /obj/machinery/vending/medical/syndicate_access name = "\improper SyndiMed Plus" - req_access = list(access_syndicate) + req_access = list(ACCESS_SYNDICATE) /obj/machinery/vending/medical/Initialize(mapload) component_parts = list() diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm index a82cc58e028..82013e65d03 100644 --- a/code/game/mecha/combat/gygax.dm +++ b/code/game/mecha/combat/gygax.dm @@ -43,7 +43,7 @@ armor = list(melee = 40, bullet = 40, laser = 50, energy = 35, bomb = 20, bio = 0, rad =20, fire = 100, acid = 100) max_temperature = 35000 leg_overload_coeff = 100 - operation_req_access = list(access_syndicate) + operation_req_access = list(ACCESS_SYNDICATE) wreckage = /obj/structure/mecha_wreckage/gygax/dark max_equip = 4 maxsize = 2 diff --git a/code/game/mecha/combat/honker.dm b/code/game/mecha/combat/honker.dm index 35df97e3bea..b546efc9168 100644 --- a/code/game/mecha/combat/honker.dm +++ b/code/game/mecha/combat/honker.dm @@ -10,7 +10,7 @@ armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) max_temperature = 25000 infra_luminosity = 5 - operation_req_access = list(access_clown) + operation_req_access = list(ACCESS_CLOWN) wreckage = /obj/structure/mecha_wreckage/honker add_req_access = 0 max_equip = 3 @@ -64,8 +64,8 @@ .hidden {display: none;} @@ -462,7 +462,7 @@ "Pod_Parts", "Pod_Frame", "Misc") - req_access = list(access_mechanic) + req_access = list(ACCESS_MECHANIC) /obj/machinery/mecha_part_fabricator/spacepod/New() ..() diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 85f511395f1..f0ea426fc1c 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -51,7 +51,7 @@ var/internal_damage = 0 //contains bitflags var/list/operation_req_access = list()//required access level for mecha operation - var/list/internals_req_access = list(access_engine,access_robotics)//required access level to open cell compartment + var/list/internals_req_access = list(ACCESS_ENGINE,ACCESS_ROBOTICS)//required access level to open cell compartment var/wreckage diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm index 99e6ca72367..35440de6cf9 100644 --- a/code/game/mecha/mecha_control_console.dm +++ b/code/game/mecha/mecha_control_console.dm @@ -4,7 +4,7 @@ icon_keyboard = "rd_key" icon_screen = "mecha" light_color = LIGHT_COLOR_FADEDPURPLE - req_access = list(access_robotics) + req_access = list(ACCESS_ROBOTICS) circuit = /obj/item/circuitboard/mecha_control var/list/located = list() var/screen = 0 diff --git a/code/game/mecha/mecha_topic.dm b/code/game/mecha/mecha_topic.dm index 9598d328d7a..fc8f38a971c 100644 --- a/code/game/mecha/mecha_topic.dm +++ b/code/game/mecha/mecha_topic.dm @@ -18,8 +18,8 @@ .hidden {display: none;} "} - if(mode) - dat += "
[vote_html(C)]

" - if(admin) - dat += "(Cancel Vote) " - else - dat += "

Start a vote:



" - var/datum/browser/popup = new(C.mob, "vote", "Voting Panel", nref=src) - popup.set_content(dat) - popup.open() - -/datum/controller/subsystem/vote/proc/update_panel(var/client/C) - C << output(url_encode(vote_html(C)), "vote.browser:update_vote_div") - -/datum/controller/subsystem/vote/proc/vote_html(var/client/C) - . = "" - if(question) - . += "

Vote: '[question]'

" - else - . += "

Vote: [capitalize(mode)]

" - . += "Time Left: [time_remaining] s
" - - -/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc) - if(!usr || !usr.client) - return //not necessary but meh...just in-case somebody does something stupid - var/admin = check_rights(R_ADMIN,0) - if(href_list["close"]) - voting -= usr.client - return - switch(href_list["vote"]) - if("open") - // vote proc will automatically get called after this switch ends - if("cancel") - if(admin && mode) - var/votedesc = capitalize(mode) - if(mode == "custom") - votedesc += " ([question])" - admin_log_and_message_admins("cancelled the running [votedesc] vote.") - reset() - if("toggle_restart") - if(admin) - config.allow_vote_restart = !config.allow_vote_restart - if("toggle_gamemode") - if(admin) - config.allow_vote_mode = !config.allow_vote_mode - if("restart") - if(config.allow_vote_restart || admin) - initiate_vote("restart",usr.key) - if("gamemode") - if(config.allow_vote_mode || admin) - initiate_vote("gamemode",usr.key) - if("crew_transfer") - if(config.allow_vote_restart || admin) - initiate_vote("crew_transfer",usr.key) - if("custom") - if(admin) - initiate_vote("custom",usr.key) - else - submit_vote(usr.ckey, round(text2num(href_list["vote"]))) - update_panel(usr.client) - return - usr.vote() - - -/mob/verb/vote() - set category = "OOC" - set name = "Vote" - - if(SSvote) - SSvote.browse_to(client) +SUBSYSTEM_DEF(vote) + name = "Vote" + wait = 10 + flags = SS_KEEP_TIMING|SS_NO_INIT + runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + + var/initiator = null + var/started_time = null + var/time_remaining = 0 + var/mode = null + var/question = null + var/list/choices = list() + var/list/voted = list() + var/list/voting = list() + var/list/current_votes = list() + var/list/round_voters = list() + var/auto_muted = 0 + +/datum/controller/subsystem/vote/fire() + if(mode) + // No more change mode votes after the game has started. + if(mode == "gamemode" && SSticker.current_state >= GAME_STATE_SETTING_UP) + to_chat(world, "Voting aborted due to game start.") + reset() + return + + // Calculate how much time is remaining by comparing current time, to time of vote start, + // plus vote duration + time_remaining = round((started_time + config.vote_period - world.time)/10) + + if(time_remaining < 0) + result() + for(var/client/C in voting) + if(C) + C << browse(null,"window=vote") + reset() + else + for(var/client/C in voting) + update_panel(C) + CHECK_TICK + +/datum/controller/subsystem/vote/proc/autotransfer() + initiate_vote("crew_transfer","the server") + +/datum/controller/subsystem/vote/proc/reset() + initiator = null + time_remaining = 0 + mode = null + question = null + choices.Cut() + voted.Cut() + voting.Cut() + current_votes.Cut() + + if(auto_muted && !config.ooc_allowed && !(config.auto_toggle_ooc_during_round && SSticker.current_state == GAME_STATE_PLAYING)) + auto_muted = 0 + config.ooc_allowed = !( config.ooc_allowed ) + to_chat(world, "The OOC channel has been automatically enabled due to vote end.") + log_admin("OOC was toggled automatically due to vote end.") + message_admins("OOC has been toggled on automatically.") + + +/datum/controller/subsystem/vote/proc/get_result() + var/greatest_votes = 0 + var/total_votes = 0 + var/list/sorted_choices = list() + var/sorted_highest + var/sorted_votes = -1 + //get the highest number of votes, while also sorting the list + while(choices.len) + // This is a very inefficient sorting method, but that's okay + for(var/option in choices) + var/votes = choices[option] + if(sorted_votes < votes) + sorted_highest = option + sorted_votes = votes + if(votes > greatest_votes) + greatest_votes = votes + sorted_votes = -1 + total_votes += choices[sorted_highest] + sorted_choices[sorted_highest] = choices[sorted_highest] || 0 + choices -= sorted_highest + choices = sorted_choices + //default-vote for everyone who didn't vote + if(!config.vote_no_default && choices.len) + var/non_voters = (GLOB.clients.len - total_votes) + if(non_voters > 0) + if(mode == "restart") + choices["Continue Playing"] += non_voters + if(choices["Continue Playing"] >= greatest_votes) + greatest_votes = choices["Continue Playing"] + else if(mode == "gamemode") + if(master_mode in choices) + choices[master_mode] += non_voters + if(choices[master_mode] >= greatest_votes) + greatest_votes = choices[master_mode] + else if(mode == "crew_transfer") + var/factor = 0.5 + switch(world.time / (10 * 60)) // minutes + if(0 to 60) + factor = 0.5 + if(61 to 120) + factor = 0.8 + if(121 to 240) + factor = 1 + if(241 to 300) + factor = 1.2 + else + factor = 1.4 + choices["Initiate Crew Transfer"] = round(choices["Initiate Crew Transfer"] * factor) + to_chat(world, "Crew Transfer Factor: [factor]") + greatest_votes = max(choices["Initiate Crew Transfer"], choices["Continue The Round"]) + + + //get all options with that many votes and return them in a list + . = list() + if(greatest_votes) + for(var/option in choices) + if(choices[option] == greatest_votes) + . += option + return . + +/datum/controller/subsystem/vote/proc/announce_result() + var/list/winners = get_result() + var/text + if(winners.len > 0) + if(winners.len > 1) + if(mode != "gamemode" || SSticker.hide_mode == 0) // Here we are making sure we don't announce potential game modes + text = "Vote Tied Between:\n" + for(var/option in winners) + text += "\t[option]\n" + . = pick(winners) + + for(var/key in current_votes) + if(choices[current_votes[key]] == .) + round_voters += key // Keep track of who voted for the winning round. + if(mode == "gamemode" && (. == "extended" || SSticker.hide_mode == 0)) // Announce Extended gamemode, but not other gamemodes + text += "Vote Result: [.] ([choices[.]] vote\s)" + else + if(mode == "custom") + // Completely replace text to show all results in custom votes + text = "[question]\n" + for(var/option in winners) + text += "\t[option]: [choices[option]] vote\s\n" + for(var/option in (choices-winners)) + text += "\t[option]: [choices[option]] vote\s\n" + else if(mode != "gamemode") + text += "Vote Result: [.] ([choices[.]] vote\s)" + else + text += "The vote has ended." // What will be shown if it is a gamemode vote that isn't extended + + else + text += "Vote Result: Inconclusive - No Votes!" + log_vote(text) + to_chat(world, "[text]") + return . + +/datum/controller/subsystem/vote/proc/result() + . = announce_result() + var/restart = 0 + if(.) + switch(mode) + if("restart") + if(. == "Restart Round") + restart = 1 + if("gamemode") + if(master_mode != .) + world.save_mode(.) + if(SSticker && SSticker.mode) + restart = 1 + else + master_mode = . + if(!going) + going = 1 + to_chat(world, "The round will start soon.") + if("crew_transfer") + if(. == "Initiate Crew Transfer") + init_shift_change(null, 1) + + + if(restart) + world.Reboot("Restart vote successful.", "end_error", "restart vote") + + return . + +/datum/controller/subsystem/vote/proc/submit_vote(var/ckey, var/vote) + if(mode) + if(config.vote_no_dead && usr.stat == DEAD && !usr.client.holder) + return 0 + if(current_votes[ckey]) + choices[choices[current_votes[ckey]]]-- + if(vote && 1<=vote && vote<=choices.len) + voted += usr.ckey + choices[choices[vote]]++ //check this + current_votes[ckey] = vote + return vote + return 0 + +/datum/controller/subsystem/vote/proc/initiate_vote(var/vote_type, var/initiator_key) + if(!mode) + if(started_time != null && !check_rights(R_ADMIN)) + var/next_allowed_time = (started_time + config.vote_delay) + if(next_allowed_time > world.time) + return 0 + + reset() + switch(vote_type) + if("restart") + choices.Add("Restart Round","Continue Playing") + if("gamemode") + if(SSticker.current_state >= 2) + return 0 + choices.Add(config.votable_modes) + if("crew_transfer") + if(check_rights(R_ADMIN|R_MOD)) + if(SSticker.current_state <= 2) + return 0 + question = "End the shift?" + choices.Add("Initiate Crew Transfer", "Continue The Round") + else + if(SSticker.current_state <= 2) + return 0 + question = "End the shift?" + choices.Add("Initiate Crew Transfer", "Continue The Round") + if("custom") + question = html_encode(input(usr,"What is the vote for?") as text|null) + if(!question) return 0 + for(var/i=1,i<=10,i++) + var/option = capitalize(html_encode(input(usr,"Please enter an option or hit cancel to finish") as text|null)) + if(!option || mode || !usr.client) break + choices.Add(option) + else + return 0 + mode = vote_type + initiator = initiator_key + started_time = world.time + var/text = "[capitalize(mode)] vote started by [initiator]." + if(mode == "custom") + text += "\n[question]" + if(usr) + log_admin("[capitalize(mode)] ([question]) vote started by [key_name(usr)].") + else if(usr) + log_admin("[capitalize(mode)] vote started by [key_name(usr)].") + + log_vote(text) + to_chat(world, {"[text] + Click here or type vote to place your vote. + You have [config.vote_period/10] seconds to vote."}) + switch(vote_type) + if("crew_transfer") + world << sound('sound/ambience/alarm4.ogg') + if("gamemode") + world << sound('sound/ambience/alarm4.ogg') + if("custom") + world << sound('sound/ambience/alarm4.ogg') + if(mode == "gamemode" && going) + going = 0 + to_chat(world, "Round start has been delayed.") + if(mode == "crew_transfer" && config.ooc_allowed) + auto_muted = 1 + config.ooc_allowed = !( config.ooc_allowed ) + to_chat(world, "The OOC channel has been automatically disabled due to a crew transfer vote.") + log_admin("OOC was toggled automatically due to crew_transfer vote.") + message_admins("OOC has been toggled off automatically.") + if(mode == "gamemode" && config.ooc_allowed) + auto_muted = 1 + config.ooc_allowed = !( config.ooc_allowed ) + to_chat(world, "The OOC channel has been automatically disabled due to the gamemode vote.") + log_admin("OOC was toggled automatically due to gamemode vote.") + message_admins("OOC has been toggled off automatically.") + if(mode == "custom" && config.ooc_allowed) + auto_muted = 1 + config.ooc_allowed = !( config.ooc_allowed ) + to_chat(world, "The OOC channel has been automatically disabled due to a custom vote.") + log_admin("OOC was toggled automatically due to custom vote.") + message_admins("OOC has been toggled off automatically.") + + time_remaining = round(config.vote_period/10) + return 1 + return 0 + +/datum/controller/subsystem/vote/proc/browse_to(var/client/C) + if(!C) + return + var/admin = check_rights(R_ADMIN, 0, user = C.mob) + voting |= C + + var/dat = {""} + if(mode) + dat += "
[vote_html(C)]

" + if(admin) + dat += "(Cancel Vote) " + else + dat += "

Start a vote:



" + var/datum/browser/popup = new(C.mob, "vote", "Voting Panel", nref=src) + popup.set_content(dat) + popup.open() + +/datum/controller/subsystem/vote/proc/update_panel(var/client/C) + C << output(url_encode(vote_html(C)), "vote.browser:update_vote_div") + +/datum/controller/subsystem/vote/proc/vote_html(var/client/C) + . = "" + if(question) + . += "

Vote: '[question]'

" + else + . += "

Vote: [capitalize(mode)]

" + . += "Time Left: [time_remaining] s
" + + +/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc) + if(!usr || !usr.client) + return //not necessary but meh...just in-case somebody does something stupid + var/admin = check_rights(R_ADMIN,0) + if(href_list["close"]) + voting -= usr.client + return + switch(href_list["vote"]) + if("open") + // vote proc will automatically get called after this switch ends + if("cancel") + if(admin && mode) + var/votedesc = capitalize(mode) + if(mode == "custom") + votedesc += " ([question])" + admin_log_and_message_admins("cancelled the running [votedesc] vote.") + reset() + if("toggle_restart") + if(admin) + config.allow_vote_restart = !config.allow_vote_restart + if("toggle_gamemode") + if(admin) + config.allow_vote_mode = !config.allow_vote_mode + if("restart") + if(config.allow_vote_restart || admin) + initiate_vote("restart",usr.key) + if("gamemode") + if(config.allow_vote_mode || admin) + initiate_vote("gamemode",usr.key) + if("crew_transfer") + if(config.allow_vote_restart || admin) + initiate_vote("crew_transfer",usr.key) + if("custom") + if(admin) + initiate_vote("custom",usr.key) + else + submit_vote(usr.ckey, round(text2num(href_list["vote"]))) + update_panel(usr.client) + return + usr.vote() + + +/mob/verb/vote() + set category = "OOC" + set name = "Vote" + + if(SSvote) + SSvote.browse_to(client) diff --git a/code/controllers/subsystem/weather.dm b/code/controllers/subsystem/weather.dm index 4b898df0f11..ac2f0c59313 100644 --- a/code/controllers/subsystem/weather.dm +++ b/code/controllers/subsystem/weather.dm @@ -80,4 +80,4 @@ SUBSYSTEM_DEF(weather) if((z in W.impacted_z_levels) && W.area_type == active_area.type) A = W break - return A \ No newline at end of file + return A diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index f694b6d2e75..c67e53184a1 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -1,104 +1,104 @@ -//TODO: rewrite and standardise all controller datums to the datum/controller type -//TODO: allow all controllers to be deleted for clean restarts (see WIP master controller stuff) - MC done - lighting done - - -/client/proc/restart_controller(controller in list("Master", "Failsafe")) - set category = "Debug" - set name = "Restart Controller" - set desc = "Restart one of the various periodic loop controllers for the game (be careful!)" - - if(!holder) - return - switch(controller) - if("Master") - Recreate_MC() - feedback_add_details("admin_verb","RMaster") - if("Failsafe") - new /datum/controller/failsafe() - feedback_add_details("admin_verb","RFailsafe") - - message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.") - -/client/proc/debug_controller(controller in list("failsafe", "Master", "Ticker", "Air", "Jobs", "Sun", "Radio", "Configuration", "pAI", - "Cameras", "Garbage", "Event", "Alarm", "Nano", "Vote", "Fires", - "Mob", "NPC Pool", "Shuttle", "Timer", "Weather", "Space", "Mob Hunt Server","Input")) - set category = "Debug" - set name = "Debug Controller" - set desc = "Debug the various periodic loop controllers for the game (be careful!)" - - if(!holder) return - switch(controller) - if("failsafe") - debug_variables(Failsafe) - feedback_add_details("admin_verb", "dfailsafe") - if("Master") - debug_variables(Master) - feedback_add_details("admin_verb","Dsmc") - if("Ticker") - debug_variables(SSticker) - feedback_add_details("admin_verb","DTicker") - if("Air") - debug_variables(SSair) - feedback_add_details("admin_verb","DAir") - if("Jobs") - debug_variables(SSjobs) - feedback_add_details("admin_verb","DJobs") - if("Sun") - debug_variables(SSsun) - feedback_add_details("admin_verb","DSun") - if("Radio") - debug_variables(SSradio) - feedback_add_details("admin_verb","DRadio") - if("Configuration") - debug_variables(config) - feedback_add_details("admin_verb","DConf") - if("pAI") - debug_variables(paiController) - feedback_add_details("admin_verb","DpAI") - if("Cameras") - debug_variables(cameranet) - feedback_add_details("admin_verb","DCameras") - if("Garbage") - debug_variables(SSgarbage) - feedback_add_details("admin_verb","DGarbage") - if("Event") - debug_variables(SSevents) - feedback_add_details("admin_verb","DEvent") - if("Alarm") - debug_variables(SSalarms) - feedback_add_details("admin_verb", "DAlarm") - if("Nano") - debug_variables(SSnanoui) - feedback_add_details("admin_verb","DNano") - if("Vote") - debug_variables(SSvote) - feedback_add_details("admin_verb","DVote") - if("Fires") - debug_variables(SSfires) - feedback_add_details("admin_verb","DFires") - if("Mob") - debug_variables(SSmobs) - feedback_add_details("admin_verb","DMob") - if("NPC Pool") - debug_variables(SSnpcpool) - feedback_add_details("admin_verb","DNPCPool") - if("Shuttle") - debug_variables(SSshuttle) - feedback_add_details("admin_verb","DShuttle") - if("Timer") - debug_variables(SStimer) - feedback_add_details("admin_verb","DTimer") - if("Weather") - debug_variables(SSweather) - feedback_add_details("admin_verb","DWeather") - if("Space") - debug_variables(space_manager) - feedback_add_details("admin_verb","DSpace") - if("Mob Hunt Server") - debug_variables(SSmob_hunt) - feedback_add_details("admin_verb","DMobHuntServer") - if("Input") - debug_variables(SSinput) - feedback_add_details("admin_verb","DInput") - - message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.") \ No newline at end of file +//TODO: rewrite and standardise all controller datums to the datum/controller type +//TODO: allow all controllers to be deleted for clean restarts (see WIP master controller stuff) - MC done - lighting done + + +/client/proc/restart_controller(controller in list("Master", "Failsafe")) + set category = "Debug" + set name = "Restart Controller" + set desc = "Restart one of the various periodic loop controllers for the game (be careful!)" + + if(!holder) + return + switch(controller) + if("Master") + Recreate_MC() + feedback_add_details("admin_verb","RMaster") + if("Failsafe") + new /datum/controller/failsafe() + feedback_add_details("admin_verb","RFailsafe") + + message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.") + +/client/proc/debug_controller(controller in list("failsafe", "Master", "Ticker", "Air", "Jobs", "Sun", "Radio", "Configuration", "pAI", + "Cameras", "Garbage", "Event", "Alarm", "Nano", "Vote", "Fires", + "Mob", "NPC Pool", "Shuttle", "Timer", "Weather", "Space", "Mob Hunt Server","Input")) + set category = "Debug" + set name = "Debug Controller" + set desc = "Debug the various periodic loop controllers for the game (be careful!)" + + if(!holder) return + switch(controller) + if("failsafe") + debug_variables(Failsafe) + feedback_add_details("admin_verb", "dfailsafe") + if("Master") + debug_variables(Master) + feedback_add_details("admin_verb","Dsmc") + if("Ticker") + debug_variables(SSticker) + feedback_add_details("admin_verb","DTicker") + if("Air") + debug_variables(SSair) + feedback_add_details("admin_verb","DAir") + if("Jobs") + debug_variables(SSjobs) + feedback_add_details("admin_verb","DJobs") + if("Sun") + debug_variables(SSsun) + feedback_add_details("admin_verb","DSun") + if("Radio") + debug_variables(SSradio) + feedback_add_details("admin_verb","DRadio") + if("Configuration") + debug_variables(config) + feedback_add_details("admin_verb","DConf") + if("pAI") + debug_variables(paiController) + feedback_add_details("admin_verb","DpAI") + if("Cameras") + debug_variables(cameranet) + feedback_add_details("admin_verb","DCameras") + if("Garbage") + debug_variables(SSgarbage) + feedback_add_details("admin_verb","DGarbage") + if("Event") + debug_variables(SSevents) + feedback_add_details("admin_verb","DEvent") + if("Alarm") + debug_variables(SSalarms) + feedback_add_details("admin_verb", "DAlarm") + if("Nano") + debug_variables(SSnanoui) + feedback_add_details("admin_verb","DNano") + if("Vote") + debug_variables(SSvote) + feedback_add_details("admin_verb","DVote") + if("Fires") + debug_variables(SSfires) + feedback_add_details("admin_verb","DFires") + if("Mob") + debug_variables(SSmobs) + feedback_add_details("admin_verb","DMob") + if("NPC Pool") + debug_variables(SSnpcpool) + feedback_add_details("admin_verb","DNPCPool") + if("Shuttle") + debug_variables(SSshuttle) + feedback_add_details("admin_verb","DShuttle") + if("Timer") + debug_variables(SStimer) + feedback_add_details("admin_verb","DTimer") + if("Weather") + debug_variables(SSweather) + feedback_add_details("admin_verb","DWeather") + if("Space") + debug_variables(space_manager) + feedback_add_details("admin_verb","DSpace") + if("Mob Hunt Server") + debug_variables(SSmob_hunt) + feedback_add_details("admin_verb","DMobHuntServer") + if("Input") + debug_variables(SSinput) + feedback_add_details("admin_verb","DInput") + + message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.") diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm index 47a8122d163..0a14b0e9476 100644 --- a/code/datums/ai_laws.dm +++ b/code/datums/ai_laws.dm @@ -1,320 +1,320 @@ -#define BASE_LAW_TYPE /datum/ai_laws/nanotrasen - -/datum/ai_law - var/law = "" - var/index = 0 - -/datum/ai_law/New(law, index) - src.law = law - src.index = index - -/datum/ai_law/proc/get_index() - return index - -/datum/ai_law/ion/get_index() - return ionnum() - -/datum/ai_law/zero/get_index() - return 0 - -/datum/ai_law/sixsixsix/get_index() - return 666 - - -/datum/ai_laws - var/name = "Unknown Laws" - var/law_header = "Prime Directives" - var/selectable = 0 - var/default = 0 - var/datum/ai_law/zero/zeroth_law = null - var/datum/ai_law/zero/zeroth_law_borg = null - var/list/datum/ai_law/inherent_laws = list() - var/list/datum/ai_law/supplied_laws = list() - var/list/datum/ai_law/ion/ion_laws = list() - var/list/datum/ai_law/sixsixsix/devil_laws = list() - var/list/datum/ai_law/sorted_laws = list() - - var/state_zeroth = 0 - var/list/state_devil = list() - var/list/state_ion = list() - var/list/state_inherent = list() - var/list/state_supplied = list() - -/datum/ai_laws/New() - ..() - sort_laws() - -/* General ai_law functions */ -/datum/ai_laws/proc/all_laws() - sort_laws() - return sorted_laws - -/datum/ai_laws/proc/laws_to_state() - sort_laws() - var/list/statements = new() - for(var/datum/ai_law/law in sorted_laws) - if(get_state_law(law)) - statements += law - - return statements - -/datum/ai_laws/proc/sort_laws() - if(sorted_laws.len) - return - - for(var/ion_law in ion_laws) - sorted_laws += ion_law - - for(var/evil_law in devil_laws) - sorted_laws += evil_law - - if(zeroth_law) - sorted_laws += zeroth_law - - var/index = 1 - for(var/datum/ai_law/inherent_law in inherent_laws) - inherent_law.index = index++ - if(supplied_laws.len < inherent_law.index || !istype(supplied_laws[inherent_law.index], /datum/ai_law)) - sorted_laws += inherent_law - - for(var/datum/ai_law/AL in supplied_laws) - if(istype(AL)) - sorted_laws += AL - -/datum/ai_laws/proc/sync(var/mob/living/silicon/S, var/full_sync = 1) - // Add directly to laws to avoid log-spam - S.sync_zeroth(zeroth_law, zeroth_law_borg) - - if(full_sync || ion_laws.len) - S.laws.clear_ion_laws() - if(full_sync || inherent_laws.len) - S.laws.clear_inherent_laws() - if(full_sync || supplied_laws.len) - S.laws.clear_supplied_laws() - - for(var/datum/ai_law/law in ion_laws) - S.laws.add_ion_law(law.law) - for(var/datum/ai_law/law in inherent_laws) - S.laws.add_inherent_law(law.law) - for(var/datum/ai_law/law in supplied_laws) - if(law) - S.laws.add_supplied_law(law.index, law.law) - - -/mob/living/silicon/proc/sync_zeroth(var/datum/ai_law/zeroth_law, var/datum/ai_law/zeroth_law_borg) - if(!is_special_character(src) || mind.original != src) - if(zeroth_law_borg) - laws.set_zeroth_law(zeroth_law_borg.law) - else if(zeroth_law) - laws.set_zeroth_law(zeroth_law.law) - -/mob/living/silicon/ai/sync_zeroth(var/datum/ai_law/zeroth_law, var/datum/ai_law/zeroth_law_borg) - if(zeroth_law) - laws.set_zeroth_law(zeroth_law.law, zeroth_law_borg ? zeroth_law_borg.law : null) - -/**************** -* Add Laws * -****************/ -/datum/ai_laws/proc/set_zeroth_law(var/law, var/law_borg = null) - if(!law) - return - - zeroth_law = new(law) - if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO - zeroth_law_borg = new(law_borg) - else - zeroth_law_borg = null - sorted_laws.Cut() - -/datum/ai_laws/proc/set_sixsixsix_law(var/law) - if(!law) - return - - for(var/datum/ai_law/AL in devil_laws) - if(AL.law == law) - return - - var/new_law = new/datum/ai_law/sixsixsix(law) - devil_laws += new_law - if(state_devil.len < devil_laws.len) - state_devil += 1 - - sorted_laws.Cut() - -/datum/ai_laws/proc/add_ion_law(var/law) - if(!law) - return - - for(var/datum/ai_law/AL in ion_laws) - if(AL.law == law) - return - - var/new_law = new/datum/ai_law/ion(law) - ion_laws += new_law - if(state_ion.len < ion_laws.len) - state_ion += 1 - - sorted_laws.Cut() - -/datum/ai_laws/proc/add_inherent_law(var/law) - if(!law) - return - - for(var/datum/ai_law/AL in inherent_laws) - if(AL.law == law) - return - - var/new_law = new/datum/ai_law/inherent(law) - inherent_laws += new_law - if(state_inherent.len < inherent_laws.len) - state_inherent += 1 - - sorted_laws.Cut() - -/datum/ai_laws/proc/add_supplied_law(var/number, var/law) - if(!law) - return - - if(supplied_laws.len >= number) - var/datum/ai_law/existing_law = supplied_laws[number] - if(existing_law && existing_law.law == law) - return - - if(supplied_laws.len >= number && supplied_laws[number]) - delete_law(supplied_laws[number]) - - while(src.supplied_laws.len < number) - src.supplied_laws += "" - if(state_supplied.len < supplied_laws.len) - state_supplied += 1 - - var/new_law = new/datum/ai_law/supplied(law, number) - supplied_laws[number] = new_law - if(state_supplied.len < supplied_laws.len) - state_supplied += 1 - - sorted_laws.Cut() - -/**************** -* Remove Laws * -*****************/ -/datum/ai_laws/proc/delete_law(var/datum/ai_law/law) - if(istype(law)) - law.delete_law(src) - -/datum/ai_law/proc/delete_law(var/datum/ai_laws/laws) - -/datum/ai_law/zero/delete_law(var/datum/ai_laws/laws) - laws.clear_zeroth_laws() - -/datum/ai_law/ion/delete_law(var/datum/ai_laws/laws) - laws.internal_delete_law(laws.ion_laws, laws.state_ion, src) - -/datum/ai_law/sixsixsix/delete_law(var/datum/ai_laws/laws) - laws.internal_delete_law(laws.devil_laws, laws.state_devil, src) - -/datum/ai_law/inherent/delete_law(var/datum/ai_laws/laws) - laws.internal_delete_law(laws.inherent_laws, laws.state_inherent, src) - -/datum/ai_law/supplied/delete_law(var/datum/ai_laws/laws) - var/index = laws.supplied_laws.Find(src) - if(index) - laws.supplied_laws[index] = "" - laws.state_supplied[index] = 1 - -/datum/ai_laws/proc/internal_delete_law(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law) - var/index = laws.Find(law) - if(index) - laws -= law - for(index, index < state.len, index++) - state[index] = state[index+1] - sorted_laws.Cut() - -/**************** -* Clear Laws * -****************/ -/datum/ai_laws/proc/clear_zeroth_laws() - zeroth_law = null - zeroth_law_borg = null - -/datum/ai_laws/proc/clear_sixsixsix_laws() - devil_laws.Cut() - sorted_laws.Cut() - -/datum/ai_laws/proc/clear_ion_laws() - ion_laws.Cut() - sorted_laws.Cut() - -/datum/ai_laws/proc/clear_inherent_laws() - inherent_laws.Cut() - sorted_laws.Cut() - -/datum/ai_laws/proc/clear_supplied_laws() - supplied_laws.Cut() - sorted_laws.Cut() - -/datum/ai_laws/proc/show_laws(var/who) - sort_laws() - for(var/datum/ai_law/law in sorted_laws) - if(law == zeroth_law_borg) - continue - if(law == zeroth_law) - to_chat(who, "[law.get_index()]. [law.law]") - else - to_chat(who, "[law.get_index()]. [law.law]") - -/******************** -* Stating Laws * -********************/ -/******** -* Get * -********/ -/datum/ai_laws/proc/get_state_law(var/datum/ai_law/law) - return law.get_state_law(src) - -/datum/ai_law/proc/get_state_law(var/datum/ai_laws/laws) - -/datum/ai_law/zero/get_state_law(var/datum/ai_laws/laws) - if(src == laws.zeroth_law) - return laws.state_zeroth - -/datum/ai_law/ion/get_state_law(var/datum/ai_laws/laws) - return laws.get_state_internal(laws.ion_laws, laws.state_ion, src) - -/datum/ai_law/inherent/get_state_law(var/datum/ai_laws/laws) - return laws.get_state_internal(laws.inherent_laws, laws.state_inherent, src) - -/datum/ai_law/supplied/get_state_law(var/datum/ai_laws/laws) - return laws.get_state_internal(laws.supplied_laws, laws.state_supplied, src) - -/datum/ai_laws/proc/get_state_internal(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law) - var/index = laws.Find(law) - if(index) - return state[index] - return 0 - -/******** -* Set * -********/ -/datum/ai_laws/proc/set_state_law(var/datum/ai_law/law, var/state) - law.set_state_law(src, state) - -/datum/ai_law/proc/set_state_law(var/datum/ai_law/law, var/state) - -/datum/ai_law/zero/set_state_law(var/datum/ai_laws/laws, var/state) - if(src == laws.zeroth_law) - laws.state_zeroth = state - -/datum/ai_law/ion/set_state_law(var/datum/ai_laws/laws, var/state) - laws.set_state_law_internal(laws.ion_laws, laws.state_ion, src, state) - -/datum/ai_law/inherent/set_state_law(var/datum/ai_laws/laws, var/state) - laws.set_state_law_internal(laws.inherent_laws, laws.state_inherent, src, state) - -/datum/ai_law/supplied/set_state_law(var/datum/ai_laws/laws, var/state) - laws.set_state_law_internal(laws.supplied_laws, laws.state_supplied, src, state) - -/datum/ai_laws/proc/set_state_law_internal(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law, var/do_state) - var/index = laws.Find(law) - if(index) - state[index] = do_state +#define BASE_LAW_TYPE /datum/ai_laws/nanotrasen + +/datum/ai_law + var/law = "" + var/index = 0 + +/datum/ai_law/New(law, index) + src.law = law + src.index = index + +/datum/ai_law/proc/get_index() + return index + +/datum/ai_law/ion/get_index() + return ionnum() + +/datum/ai_law/zero/get_index() + return 0 + +/datum/ai_law/sixsixsix/get_index() + return 666 + + +/datum/ai_laws + var/name = "Unknown Laws" + var/law_header = "Prime Directives" + var/selectable = 0 + var/default = 0 + var/datum/ai_law/zero/zeroth_law = null + var/datum/ai_law/zero/zeroth_law_borg = null + var/list/datum/ai_law/inherent_laws = list() + var/list/datum/ai_law/supplied_laws = list() + var/list/datum/ai_law/ion/ion_laws = list() + var/list/datum/ai_law/sixsixsix/devil_laws = list() + var/list/datum/ai_law/sorted_laws = list() + + var/state_zeroth = 0 + var/list/state_devil = list() + var/list/state_ion = list() + var/list/state_inherent = list() + var/list/state_supplied = list() + +/datum/ai_laws/New() + ..() + sort_laws() + +/* General ai_law functions */ +/datum/ai_laws/proc/all_laws() + sort_laws() + return sorted_laws + +/datum/ai_laws/proc/laws_to_state() + sort_laws() + var/list/statements = new() + for(var/datum/ai_law/law in sorted_laws) + if(get_state_law(law)) + statements += law + + return statements + +/datum/ai_laws/proc/sort_laws() + if(sorted_laws.len) + return + + for(var/ion_law in ion_laws) + sorted_laws += ion_law + + for(var/evil_law in devil_laws) + sorted_laws += evil_law + + if(zeroth_law) + sorted_laws += zeroth_law + + var/index = 1 + for(var/datum/ai_law/inherent_law in inherent_laws) + inherent_law.index = index++ + if(supplied_laws.len < inherent_law.index || !istype(supplied_laws[inherent_law.index], /datum/ai_law)) + sorted_laws += inherent_law + + for(var/datum/ai_law/AL in supplied_laws) + if(istype(AL)) + sorted_laws += AL + +/datum/ai_laws/proc/sync(var/mob/living/silicon/S, var/full_sync = 1) + // Add directly to laws to avoid log-spam + S.sync_zeroth(zeroth_law, zeroth_law_borg) + + if(full_sync || ion_laws.len) + S.laws.clear_ion_laws() + if(full_sync || inherent_laws.len) + S.laws.clear_inherent_laws() + if(full_sync || supplied_laws.len) + S.laws.clear_supplied_laws() + + for(var/datum/ai_law/law in ion_laws) + S.laws.add_ion_law(law.law) + for(var/datum/ai_law/law in inherent_laws) + S.laws.add_inherent_law(law.law) + for(var/datum/ai_law/law in supplied_laws) + if(law) + S.laws.add_supplied_law(law.index, law.law) + + +/mob/living/silicon/proc/sync_zeroth(var/datum/ai_law/zeroth_law, var/datum/ai_law/zeroth_law_borg) + if(!is_special_character(src) || mind.original != src) + if(zeroth_law_borg) + laws.set_zeroth_law(zeroth_law_borg.law) + else if(zeroth_law) + laws.set_zeroth_law(zeroth_law.law) + +/mob/living/silicon/ai/sync_zeroth(var/datum/ai_law/zeroth_law, var/datum/ai_law/zeroth_law_borg) + if(zeroth_law) + laws.set_zeroth_law(zeroth_law.law, zeroth_law_borg ? zeroth_law_borg.law : null) + +/**************** +* Add Laws * +****************/ +/datum/ai_laws/proc/set_zeroth_law(var/law, var/law_borg = null) + if(!law) + return + + zeroth_law = new(law) + if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO + zeroth_law_borg = new(law_borg) + else + zeroth_law_borg = null + sorted_laws.Cut() + +/datum/ai_laws/proc/set_sixsixsix_law(var/law) + if(!law) + return + + for(var/datum/ai_law/AL in devil_laws) + if(AL.law == law) + return + + var/new_law = new/datum/ai_law/sixsixsix(law) + devil_laws += new_law + if(state_devil.len < devil_laws.len) + state_devil += 1 + + sorted_laws.Cut() + +/datum/ai_laws/proc/add_ion_law(var/law) + if(!law) + return + + for(var/datum/ai_law/AL in ion_laws) + if(AL.law == law) + return + + var/new_law = new/datum/ai_law/ion(law) + ion_laws += new_law + if(state_ion.len < ion_laws.len) + state_ion += 1 + + sorted_laws.Cut() + +/datum/ai_laws/proc/add_inherent_law(var/law) + if(!law) + return + + for(var/datum/ai_law/AL in inherent_laws) + if(AL.law == law) + return + + var/new_law = new/datum/ai_law/inherent(law) + inherent_laws += new_law + if(state_inherent.len < inherent_laws.len) + state_inherent += 1 + + sorted_laws.Cut() + +/datum/ai_laws/proc/add_supplied_law(var/number, var/law) + if(!law) + return + + if(supplied_laws.len >= number) + var/datum/ai_law/existing_law = supplied_laws[number] + if(existing_law && existing_law.law == law) + return + + if(supplied_laws.len >= number && supplied_laws[number]) + delete_law(supplied_laws[number]) + + while(src.supplied_laws.len < number) + src.supplied_laws += "" + if(state_supplied.len < supplied_laws.len) + state_supplied += 1 + + var/new_law = new/datum/ai_law/supplied(law, number) + supplied_laws[number] = new_law + if(state_supplied.len < supplied_laws.len) + state_supplied += 1 + + sorted_laws.Cut() + +/**************** +* Remove Laws * +*****************/ +/datum/ai_laws/proc/delete_law(var/datum/ai_law/law) + if(istype(law)) + law.delete_law(src) + +/datum/ai_law/proc/delete_law(var/datum/ai_laws/laws) + +/datum/ai_law/zero/delete_law(var/datum/ai_laws/laws) + laws.clear_zeroth_laws() + +/datum/ai_law/ion/delete_law(var/datum/ai_laws/laws) + laws.internal_delete_law(laws.ion_laws, laws.state_ion, src) + +/datum/ai_law/sixsixsix/delete_law(var/datum/ai_laws/laws) + laws.internal_delete_law(laws.devil_laws, laws.state_devil, src) + +/datum/ai_law/inherent/delete_law(var/datum/ai_laws/laws) + laws.internal_delete_law(laws.inherent_laws, laws.state_inherent, src) + +/datum/ai_law/supplied/delete_law(var/datum/ai_laws/laws) + var/index = laws.supplied_laws.Find(src) + if(index) + laws.supplied_laws[index] = "" + laws.state_supplied[index] = 1 + +/datum/ai_laws/proc/internal_delete_law(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law) + var/index = laws.Find(law) + if(index) + laws -= law + for(index, index < state.len, index++) + state[index] = state[index+1] + sorted_laws.Cut() + +/**************** +* Clear Laws * +****************/ +/datum/ai_laws/proc/clear_zeroth_laws() + zeroth_law = null + zeroth_law_borg = null + +/datum/ai_laws/proc/clear_sixsixsix_laws() + devil_laws.Cut() + sorted_laws.Cut() + +/datum/ai_laws/proc/clear_ion_laws() + ion_laws.Cut() + sorted_laws.Cut() + +/datum/ai_laws/proc/clear_inherent_laws() + inherent_laws.Cut() + sorted_laws.Cut() + +/datum/ai_laws/proc/clear_supplied_laws() + supplied_laws.Cut() + sorted_laws.Cut() + +/datum/ai_laws/proc/show_laws(var/who) + sort_laws() + for(var/datum/ai_law/law in sorted_laws) + if(law == zeroth_law_borg) + continue + if(law == zeroth_law) + to_chat(who, "[law.get_index()]. [law.law]") + else + to_chat(who, "[law.get_index()]. [law.law]") + +/******************** +* Stating Laws * +********************/ +/******** +* Get * +********/ +/datum/ai_laws/proc/get_state_law(var/datum/ai_law/law) + return law.get_state_law(src) + +/datum/ai_law/proc/get_state_law(var/datum/ai_laws/laws) + +/datum/ai_law/zero/get_state_law(var/datum/ai_laws/laws) + if(src == laws.zeroth_law) + return laws.state_zeroth + +/datum/ai_law/ion/get_state_law(var/datum/ai_laws/laws) + return laws.get_state_internal(laws.ion_laws, laws.state_ion, src) + +/datum/ai_law/inherent/get_state_law(var/datum/ai_laws/laws) + return laws.get_state_internal(laws.inherent_laws, laws.state_inherent, src) + +/datum/ai_law/supplied/get_state_law(var/datum/ai_laws/laws) + return laws.get_state_internal(laws.supplied_laws, laws.state_supplied, src) + +/datum/ai_laws/proc/get_state_internal(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law) + var/index = laws.Find(law) + if(index) + return state[index] + return 0 + +/******** +* Set * +********/ +/datum/ai_laws/proc/set_state_law(var/datum/ai_law/law, var/state) + law.set_state_law(src, state) + +/datum/ai_law/proc/set_state_law(var/datum/ai_law/law, var/state) + +/datum/ai_law/zero/set_state_law(var/datum/ai_laws/laws, var/state) + if(src == laws.zeroth_law) + laws.state_zeroth = state + +/datum/ai_law/ion/set_state_law(var/datum/ai_laws/laws, var/state) + laws.set_state_law_internal(laws.ion_laws, laws.state_ion, src, state) + +/datum/ai_law/inherent/set_state_law(var/datum/ai_laws/laws, var/state) + laws.set_state_law_internal(laws.inherent_laws, laws.state_inherent, src, state) + +/datum/ai_law/supplied/set_state_law(var/datum/ai_laws/laws, var/state) + laws.set_state_law_internal(laws.supplied_laws, laws.state_supplied, src, state) + +/datum/ai_laws/proc/set_state_law_internal(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law, var/do_state) + var/index = laws.Find(law) + if(index) + state[index] = do_state diff --git a/code/datums/beam.dm b/code/datums/beam.dm index ac585d07197..86f816662c0 100644 --- a/code/datums/beam.dm +++ b/code/datums/beam.dm @@ -133,4 +133,4 @@ var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type,beam_sleep_time) spawn(0) newbeam.Start() - return newbeam \ No newline at end of file + return newbeam diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 85f8e52e89d..76be0b02a12 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -179,4 +179,4 @@ if(src && src.mob) // to_chat(world, "[src] was [src.mob.machine], setting to null") src.mob.unset_machine() - return \ No newline at end of file + return diff --git a/code/datums/cache/apc.dm b/code/datums/cache/apc.dm index 29a2574f6e0..524a8793415 100644 --- a/code/datums/cache/apc.dm +++ b/code/datums/cache/apc.dm @@ -25,4 +25,4 @@ var/global/datum/repository/apc/apc_repository = new() cache_entry.timestamp = world.time + 5 SECONDS cache_entry.data = apcData - return apcData \ No newline at end of file + return apcData diff --git a/code/datums/cache/cache.dm b/code/datums/cache/cache.dm index f401e54517a..09d8670e29e 100644 --- a/code/datums/cache/cache.dm +++ b/code/datums/cache/cache.dm @@ -3,4 +3,4 @@ var/list/data = list() /datum/repository - var/cache_data \ No newline at end of file + var/cache_data diff --git a/code/datums/cache/powermonitor.dm b/code/datums/cache/powermonitor.dm index 5112d58ad29..29394195edf 100644 --- a/code/datums/cache/powermonitor.dm +++ b/code/datums/cache/powermonitor.dm @@ -21,4 +21,4 @@ var/global/datum/repository/powermonitor/powermonitor_repository = new() /datum/repository/powermonitor/proc/update_cache() return powermonitor_data(refresh = 1) - \ No newline at end of file + diff --git a/code/datums/click_intercept.dm b/code/datums/click_intercept.dm index d3e9bccbda0..7eed1bb479b 100644 --- a/code/datums/click_intercept.dm +++ b/code/datums/click_intercept.dm @@ -28,4 +28,4 @@ return /datum/click_intercept/proc/InterceptClickOn(user,params,atom/object) - return \ No newline at end of file + return diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 7b3b2ec503a..e733ca78d2b 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -310,4 +310,4 @@ target.TakeComponent(comps) /datum/component/nano_host() - return parent \ No newline at end of file + return parent diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm index 63c6110aa5d..3bd740973a3 100644 --- a/code/datums/components/caltrop.dm +++ b/code/datums/components/caltrop.dm @@ -56,4 +56,4 @@ H.visible_message("[H] slides on [A]!", "You slide on [A]!") cooldown = world.time - H.Weaken(3) \ No newline at end of file + H.Weaken(3) diff --git a/code/datums/components/decal.dm b/code/datums/components/decal.dm index 26d4653b25a..6a0f686b13a 100644 --- a/code/datums/components/decal.dm +++ b/code/datums/components/decal.dm @@ -72,4 +72,4 @@ qdel(src) /datum/component/decal/proc/examine(datum/source, mob/user, var/list/examine_list) - examine_list += description \ No newline at end of file + examine_list += description diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm index 8d44679fa4a..3071a07d5bb 100644 --- a/code/datums/components/material_container.dm +++ b/code/datums/components/material_container.dm @@ -398,4 +398,4 @@ /datum/material/plastic name = "Plastic" id = MAT_PLASTIC - sheet_type = /obj/item/stack/sheet/plastic \ No newline at end of file + sheet_type = /obj/item/stack/sheet/plastic diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm index 68ec51959a2..bbc71ce101f 100644 --- a/code/datums/components/spawner.dm +++ b/code/datums/components/spawner.dm @@ -48,4 +48,4 @@ spawned_mobs += L L.nest = src L.faction = src.faction - P.visible_message("[L] [spawn_text] [P].") \ No newline at end of file + P.visible_message("[L] [spawn_text] [P].") diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm index aca505e6a03..818e78e8d2e 100644 --- a/code/datums/components/squeak.dm +++ b/code/datums/components/squeak.dm @@ -90,4 +90,4 @@ /datum/component/squeak/proc/holder_dir_change(datum/source, old_dir, new_dir) //If the dir changes it means we're going through a bend in the pipes, let's pretend we bumped the wall if(old_dir != new_dir) - play_squeak() \ No newline at end of file + play_squeak() diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index 7845d836f49..f908685cf09 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -1,652 +1,652 @@ -/hook/startup/proc/createDatacore() - data_core = new /datum/datacore() - return 1 - -/datum/datacore - var/list/medical = list() - var/list/general = list() - var/list/security = list() - //This list tracks characters spawned in the world and cannot be modified in-game. Currently referenced by respawn_character(). - var/list/locked = list() - -/datum/datacore/proc/get_manifest(monochrome, OOC) - var/list/heads = new() - var/list/sec = new() - var/list/eng = new() - var/list/med = new() - var/list/sci = new() - var/list/ser = new() - var/list/sup = new() - var/list/bot = new() - var/list/misc = new() - var/list/isactive = new() - var/dat = {" - -
- - "} - var/even = 0 - // sort mobs - for(var/datum/data/record/t in data_core.general) - var/name = t.fields["name"] - var/rank = t.fields["rank"] - var/real_rank = t.fields["real_rank"] - if(OOC) - var/active = 0 - for(var/mob/M in GLOB.player_list) - if(M.real_name == name && M.client && M.client.inactivity <= 10 * 60 * 10) - active = 1 - break - isactive[name] = active ? "Active" : "Inactive" - else - isactive[name] = t.fields["p_stat"] - var/department = 0 - if(real_rank in command_positions) - heads[name] = rank - department = 1 - if(real_rank in security_positions) - sec[name] = rank - department = 1 - if(real_rank in engineering_positions) - eng[name] = rank - department = 1 - if(real_rank in medical_positions) - med[name] = rank - department = 1 - if(real_rank in science_positions) - sci[name] = rank - department = 1 - if(real_rank in service_positions) - ser[name] = rank - department = 1 - if(real_rank in supply_positions) - sup[name] = rank - department = 1 - if(real_rank in nonhuman_positions) - bot[name] = rank - department = 1 - if(!department && !(name in heads)) - misc[name] = rank - if(heads.len > 0) - dat += "" - for(var/name in heads) - dat += "" - even = !even - if(sec.len > 0) - dat += "" - for(var/name in sec) - dat += "" - even = !even - if(eng.len > 0) - dat += "" - for(var/name in eng) - dat += "" - even = !even - if(med.len > 0) - dat += "" - for(var/name in med) - dat += "" - even = !even - if(sci.len > 0) - dat += "" - for(var/name in sci) - dat += "" - even = !even - if(ser.len > 0) - dat += "" - for(var/name in ser) - dat += "" - even = !even - if(sup.len > 0) - dat += "" - for(var/name in sup) - dat += "" - even = !even - if(bot.len > 0) - dat += "" - for(var/name in bot) - dat += "" - even = !even - if(misc.len > 0) - dat += "" - for(var/name in misc) - dat += "" - even = !even - - dat += "
NameRankActivity
Heads
[name][heads[name]][isactive[name]]
Security
[name][sec[name]][isactive[name]]
Engineering
[name][eng[name]][isactive[name]]
Medical
[name][med[name]][isactive[name]]
Science
[name][sci[name]][isactive[name]]
Service
[name][ser[name]][isactive[name]]
Supply
[name][sup[name]][isactive[name]]
Silicon
[name][bot[name]][isactive[name]]
Miscellaneous
[name][misc[name]][isactive[name]]
" - dat = replacetext(dat, "\n", "") // so it can be placed on paper correctly - dat = replacetext(dat, "\t", "") - return dat - - -/* -We can't just insert in HTML into the nanoUI so we need the raw data to play with. -Instead of creating this list over and over when someone leaves their PDA open to the page -we'll only update it when it changes. The PDA_Manifest global list is zeroed out upon any change -using /datum/datacore/proc/manifest_inject(), or manifest_insert() -*/ - -var/global/list/PDA_Manifest = list() - -/datum/datacore/proc/get_manifest_json() - if(PDA_Manifest.len) - return - var/heads[0] - var/sec[0] - var/eng[0] - var/med[0] - var/sci[0] - var/ser[0] - var/sup[0] - var/bot[0] - var/misc[0] - for(var/datum/data/record/t in data_core.general) - var/name = sanitize(t.fields["name"]) - var/rank = sanitize(t.fields["rank"]) - var/real_rank = t.fields["real_rank"] - - var/isactive = t.fields["p_stat"] - var/department = 0 - var/depthead = 0 // Department Heads will be placed at the top of their lists. - if(real_rank in command_positions) - heads[++heads.len] = list("name" = name, "rank" = rank, "active" = isactive) - department = 1 - depthead = 1 - if(rank == "Captain" && heads.len != 1) - heads.Swap(1, heads.len) - - if(real_rank in security_positions) - sec[++sec.len] = list("name" = name, "rank" = rank, "active" = isactive) - department = 1 - if(depthead && sec.len != 1) - sec.Swap(1, sec.len) - - if(real_rank in engineering_positions) - eng[++eng.len] = list("name" = name, "rank" = rank, "active" = isactive) - department = 1 - if(depthead && eng.len != 1) - eng.Swap(1, eng.len) - - if(real_rank in medical_positions) - med[++med.len] = list("name" = name, "rank" = rank, "active" = isactive) - department = 1 - if(depthead && med.len != 1) - med.Swap(1, med.len) - - if(real_rank in science_positions) - sci[++sci.len] = list("name" = name, "rank" = rank, "active" = isactive) - department = 1 - if(depthead && sci.len != 1) - sci.Swap(1, sci.len) - - if(real_rank in service_positions) - ser[++ser.len] = list("name" = name, "rank" = rank, "active" = isactive) - department = 1 - if(depthead && ser.len != 1) - ser.Swap(1, ser.len) - - if(real_rank in supply_positions) - sup[++sup.len] = list("name" = name, "rank" = rank, "active" = isactive) - department = 1 - if(depthead && sup.len != 1) - sup.Swap(1, sup.len) - - if(real_rank in nonhuman_positions) - bot[++bot.len] = list("name" = name, "rank" = rank, "active" = isactive) - department = 1 - - if(!department && !(name in heads)) - misc[++misc.len] = list("name" = name, "rank" = rank, "active" = isactive) - - - PDA_Manifest = list(\ - "heads" = heads,\ - "sec" = sec,\ - "eng" = eng,\ - "med" = med,\ - "sci" = sci,\ - "ser" = ser,\ - "sup" = sup,\ - "bot" = bot,\ - "misc" = misc\ - ) - return - - - -/datum/datacore/proc/manifest() - for(var/mob/living/carbon/human/H in GLOB.player_list) - manifest_inject(H) - -/datum/datacore/proc/manifest_modify(name, assignment) - if(PDA_Manifest.len) - PDA_Manifest.Cut() - var/datum/data/record/foundrecord - var/real_title = assignment - - for(var/datum/data/record/t in data_core.general) - if(t) - if(t.fields["name"] == name) - foundrecord = t - break - - var/list/all_jobs = get_job_datums() - - for(var/datum/job/J in all_jobs) - var/list/alttitles = get_alternate_titles(J.title) - if(!J) continue - if(assignment in alttitles) - real_title = J.title - break - - if(foundrecord) - foundrecord.fields["rank"] = assignment - foundrecord.fields["real_rank"] = real_title - -var/record_id_num = 1001 -/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H) - if(PDA_Manifest.len) - PDA_Manifest.Cut() - - if(H.mind && (H.mind.assigned_role != H.mind.special_role)) - var/assignment - if(H.mind.role_alt_title) - assignment = H.mind.role_alt_title - else if(H.mind.assigned_role) - assignment = H.mind.assigned_role - else if(H.job) - assignment = H.job - else - assignment = "Unassigned" - - var/id = num2hex(record_id_num++, 6) - - - //General Record - var/datum/data/record/G = new() - G.fields["id"] = id - G.fields["name"] = H.real_name - G.fields["real_rank"] = H.mind.assigned_role - G.fields["rank"] = assignment - G.fields["age"] = H.age - G.fields["fingerprint"] = md5(H.dna.uni_identity) - G.fields["p_stat"] = "Active" - G.fields["m_stat"] = "Stable" - G.fields["sex"] = capitalize(H.gender) - G.fields["species"] = H.dna.species.name - G.fields["photo"] = get_id_photo(H) - G.fields["photo-south"] = "'data:image/png;base64,[icon2base64(icon(G.fields["photo"], dir = SOUTH))]'" - G.fields["photo-west"] = "'data:image/png;base64,[icon2base64(icon(G.fields["photo"], dir = WEST))]'" - if(H.gen_record && !jobban_isbanned(H, "Records")) - G.fields["notes"] = H.gen_record - else - G.fields["notes"] = "No notes found." - general += G - - //Medical Record - var/datum/data/record/M = new() - M.fields["id"] = id - M.fields["name"] = H.real_name - M.fields["blood_type"] = H.dna.blood_type - M.fields["b_dna"] = H.dna.unique_enzymes - M.fields["mi_dis"] = "None" - M.fields["mi_dis_d"] = "No minor disabilities have been declared." - M.fields["ma_dis"] = "None" - M.fields["ma_dis_d"] = "No major disabilities have been diagnosed." - M.fields["alg"] = "None" - M.fields["alg_d"] = "No allergies have been detected in this patient." - M.fields["cdi"] = "None" - M.fields["cdi_d"] = "No diseases have been diagnosed at the moment." - if(H.med_record && !jobban_isbanned(H, "Records")) - M.fields["notes"] = H.med_record - else - M.fields["notes"] = "No notes found." - medical += M - - //Security Record - var/datum/data/record/S = new() - S.fields["id"] = id - S.fields["name"] = H.real_name - S.fields["criminal"] = "None" - S.fields["mi_crim"] = "None" - S.fields["mi_crim_d"] = "No minor crime convictions." - S.fields["ma_crim"] = "None" - S.fields["ma_crim_d"] = "No major crime convictions." - S.fields["notes"] = "No notes." - if(H.sec_record && !jobban_isbanned(H, "Records")) - S.fields["notes"] = H.sec_record - else - S.fields["notes"] = "No notes." - LAZYINITLIST(S.fields["comments"]) - security += S - - //Locked Record - var/datum/data/record/L = new() - L.fields["id"] = md5("[H.real_name][H.mind.assigned_role]") - L.fields["name"] = H.real_name - L.fields["rank"] = H.mind.assigned_role - L.fields["age"] = H.age - L.fields["sex"] = capitalize(H.gender) - L.fields["blood_type"] = H.dna.blood_type - L.fields["b_dna"] = H.dna.unique_enzymes - L.fields["enzymes"] = H.dna.SE // Used in respawning - L.fields["identity"] = H.dna.UI // " - L.fields["image"] = getFlatIcon(H) //This is god-awful - L.fields["reference"] = H - locked += L - return - -/proc/get_id_photo(mob/living/carbon/human/H, var/custom_job = null) - var/icon/preview_icon = null - var/obj/item/organ/external/head/head_organ = H.get_organ("head") - var/obj/item/organ/internal/eyes/eyes_organ = H.get_int_organ(/obj/item/organ/internal/eyes) - - var/g = "m" - if(H.gender == FEMALE) - g = "f" - - var/icon/icobase = head_organ.icobase //At this point all the organs would have the same icobase, so this is just recycling. - - preview_icon = new /icon(icobase, "torso_[g]") - var/icon/temp - temp = new /icon(icobase, "groin_[g]") - preview_icon.Blend(temp, ICON_OVERLAY) - var/head = "head" - if(head_organ.alt_head && head_organ.dna.species.bodyflags & HAS_ALT_HEADS) - var/datum/sprite_accessory/alt_heads/alternate_head = GLOB.alt_heads_list[head_organ.alt_head] - if(alternate_head.icon_state) - head = alternate_head.icon_state - temp = new /icon(icobase, "[head]_[g]") - preview_icon.Blend(temp, ICON_OVERLAY) - - //Tail - if(H.body_accessory && istype(H.body_accessory, /datum/body_accessory/tail)) - temp = new/icon("icon" = H.body_accessory.icon, "icon_state" = H.body_accessory.icon_state) - preview_icon.Blend(temp, ICON_OVERLAY) - else if(H.tail && H.dna.species.bodyflags & HAS_TAIL) - temp = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[H.tail]_s") - preview_icon.Blend(temp, ICON_OVERLAY) - - for(var/obj/item/organ/external/E in H.bodyparts) - preview_icon.Blend(E.get_icon(), ICON_OVERLAY) - - // Skin tone - if(H.dna.species.bodyflags & HAS_SKIN_TONE) - if(H.s_tone >= 0) - preview_icon.Blend(rgb(H.s_tone, H.s_tone, H.s_tone), ICON_ADD) - else - preview_icon.Blend(rgb(-H.s_tone, -H.s_tone, -H.s_tone), ICON_SUBTRACT) - - // Proper Skin color - Fix, you can't have HAS_SKIN_TONE *and* HAS_SKIN_COLOR - if(H.dna.species.bodyflags & HAS_SKIN_COLOR) - preview_icon.Blend(H.skin_colour, ICON_ADD) - - //Tail Markings - var/icon/t_marking_s - if(H.dna.species.bodyflags & HAS_TAIL_MARKINGS) - var/tail_marking = H.m_styles["tail"] - var/datum/sprite_accessory/tail_marking_style = GLOB.marking_styles_list[tail_marking] - if(tail_marking_style && tail_marking_style.species_allowed) - t_marking_s = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]_s") - t_marking_s.Blend(H.m_colours["tail"], ICON_ADD) - if(!(H.body_accessory && istype(H.body_accessory, /datum/body_accessory/body))) - preview_icon.Blend(t_marking_s, ICON_OVERLAY) - - var/icon/face_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "bald_s") - if(!(H.dna.species.bodyflags & NO_EYES)) - var/icon/eyes_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = H.dna.species ? H.dna.species.eyes : "eyes_s") - if(!eyes_organ) - return - eyes_s.Blend(eyes_organ.eye_colour, ICON_ADD) - face_s.Blend(eyes_s, ICON_OVERLAY) - - var/datum/sprite_accessory/hair_style = GLOB.hair_styles_full_list[head_organ.h_style] - if(hair_style) - var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s") - // I'll want to make a species-specific proc for this sooner or later - // But this'll do for now - if(istype(head_organ.dna.species, /datum/species/slime)) - hair_s.Blend("[H.skin_colour]A0", ICON_AND) //A0 = 160 alpha. - else - hair_s.Blend(head_organ.hair_colour, ICON_ADD) - - if(hair_style.secondary_theme) - var/icon/hair_secondary_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_[hair_style.secondary_theme]_s") - if(!hair_style.no_sec_colour) - hair_secondary_s.Blend(head_organ.sec_hair_colour, ICON_ADD) - hair_s.Blend(hair_secondary_s, ICON_OVERLAY) - - face_s.Blend(hair_s, ICON_OVERLAY) - - //Head Accessory - if(head_organ.dna.species.bodyflags & HAS_HEAD_ACCESSORY) - var/datum/sprite_accessory/head_accessory_style = GLOB.head_accessory_styles_list[head_organ.ha_style] - if(head_accessory_style && head_accessory_style.species_allowed) - var/icon/head_accessory_s = new/icon("icon" = head_accessory_style.icon, "icon_state" = "[head_accessory_style.icon_state]_s") - head_accessory_s.Blend(head_organ.headacc_colour, ICON_ADD) - face_s.Blend(head_accessory_s, ICON_OVERLAY) - - var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[head_organ.f_style] - if(facial_hair_style && facial_hair_style.species_allowed) - var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s") - if(istype(head_organ.dna.species, /datum/species/slime)) - facial_s.Blend("[H.skin_colour]A0", ICON_ADD) //A0 = 160 alpha. - else - facial_s.Blend(head_organ.facial_colour, ICON_ADD) - - if(facial_hair_style.secondary_theme) - var/icon/facial_secondary_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_[facial_hair_style.secondary_theme]_s") - if(!facial_hair_style.no_sec_colour) - facial_secondary_s.Blend(head_organ.sec_facial_colour, ICON_ADD) - facial_s.Blend(facial_secondary_s, ICON_OVERLAY) - - face_s.Blend(facial_s, ICON_OVERLAY) - - //Markings - if((H.dna.species.bodyflags & HAS_HEAD_MARKINGS) || (H.dna.species.bodyflags & HAS_BODY_MARKINGS)) - if(H.dna.species.bodyflags & HAS_BODY_MARKINGS) //Body markings. - var/body_marking = H.m_styles["body"] - var/datum/sprite_accessory/body_marking_style = GLOB.marking_styles_list[body_marking] - if(body_marking_style && body_marking_style.species_allowed) - var/icon/b_marking_s = new/icon("icon" = body_marking_style.icon, "icon_state" = "[body_marking_style.icon_state]_s") - b_marking_s.Blend(H.m_colours["body"], ICON_ADD) - face_s.Blend(b_marking_s, ICON_OVERLAY) - if(H.dna.species.bodyflags & HAS_HEAD_MARKINGS) //Head markings. - var/head_marking = H.m_styles["head"] - var/datum/sprite_accessory/head_marking_style = GLOB.marking_styles_list[head_marking] - if(head_marking_style && head_marking_style.species_allowed) - var/icon/h_marking_s = new/icon("icon" = head_marking_style.icon, "icon_state" = "[head_marking_style.icon_state]_s") - h_marking_s.Blend(H.m_colours["head"], ICON_ADD) - face_s.Blend(h_marking_s, ICON_OVERLAY) - - preview_icon.Blend(face_s, ICON_OVERLAY) - - - var/icon/clothes_s = null - var/job_clothes = null - if(custom_job) - job_clothes = custom_job - else if(H.mind) - job_clothes = H.mind.assigned_role - switch(job_clothes) - if("Head of Personnel") - clothes_s = new /icon('icons/mob/uniform.dmi', "hop_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - if("Nanotrasen Representative") - clothes_s = new /icon('icons/mob/uniform.dmi', "officer_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) - if("Blueshield") - clothes_s = new /icon('icons/mob/uniform.dmi', "officer_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "swat_gl"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "blueshield"), ICON_OVERLAY) - if("Magistrate") - clothes_s = new /icon('icons/mob/uniform.dmi', "really_black_suit_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "judge"), ICON_OVERLAY) - if("Bartender") - clothes_s = new /icon('icons/mob/uniform.dmi', "ba_suit_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Botanist") - clothes_s = new /icon('icons/mob/uniform.dmi', "hydroponics_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Chef") - clothes_s = new /icon('icons/mob/uniform.dmi', "chef_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Janitor") - clothes_s = new /icon('icons/mob/uniform.dmi', "janitor_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Librarian") - clothes_s = new /icon('icons/mob/uniform.dmi', "red_suit_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Barber") - clothes_s = new /icon('icons/mob/uniform.dmi', "barber_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Clown") - clothes_s = new /icon('icons/mob/uniform.dmi', "clown_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "clown"), ICON_UNDERLAY) - if("Mime") - clothes_s = new /icon('icons/mob/uniform.dmi', "mime_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Quartermaster") - clothes_s = new /icon('icons/mob/uniform.dmi', "qm_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - if("Cargo Technician") - clothes_s = new /icon('icons/mob/uniform.dmi', "cargotech_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Shaft Miner") - clothes_s = new /icon('icons/mob/uniform.dmi', "miner_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Lawyer") - clothes_s = new /icon('icons/mob/uniform.dmi', "internalaffairs_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - if("Chaplain") - clothes_s = new /icon('icons/mob/uniform.dmi', "chapblack_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Research Director") - clothes_s = new /icon('icons/mob/uniform.dmi', "director_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) - if("Scientist") - clothes_s = new /icon('icons/mob/uniform.dmi', "toxinswhite_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_tox_open"), ICON_OVERLAY) - if("Chemist") - clothes_s = new /icon('icons/mob/uniform.dmi', "chemistrywhite_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_chem_open"), ICON_OVERLAY) - if("Chief Medical Officer") - clothes_s = new /icon('icons/mob/uniform.dmi', "cmo_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_cmo_open"), ICON_OVERLAY) - if("Medical Doctor") - clothes_s = new /icon('icons/mob/uniform.dmi', "medical_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) - if("Coroner") - clothes_s = new /icon('icons/mob/uniform.dmi', "scrubsblack_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_mort_open"), ICON_OVERLAY) - if("Geneticist") - clothes_s = new /icon('icons/mob/uniform.dmi', "geneticswhite_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_gen_open"), ICON_OVERLAY) - if("Virologist") - clothes_s = new /icon('icons/mob/uniform.dmi', "virologywhite_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_vir_open"), ICON_OVERLAY) - if("Psychiatrist") - clothes_s = new /icon('icons/mob/uniform.dmi', "psych_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_UNDERLAY) - if("Paramedic") - clothes_s = new /icon('icons/mob/uniform.dmi', "paramedic_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Captain") - clothes_s = new /icon('icons/mob/uniform.dmi', "captain_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - if("Head of Security") - clothes_s = new /icon('icons/mob/uniform.dmi', "hosred_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - if("Warden") - clothes_s = new /icon('icons/mob/uniform.dmi', "warden_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - if("Detective") - clothes_s = new /icon('icons/mob/uniform.dmi', "detective_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "detective"), ICON_OVERLAY) - if("Security Pod Pilot") - clothes_s = new /icon('icons/mob/uniform.dmi', "secred_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "bomber"), ICON_OVERLAY) - if("Brig Physician") - clothes_s = new /icon('icons/mob/uniform.dmi', "medical_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "fr_jacket_open"), ICON_OVERLAY) - if("Security Officer") - clothes_s = new /icon('icons/mob/uniform.dmi', "secred_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - if("Chief Engineer") - clothes_s = new /icon('icons/mob/uniform.dmi', "chief_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) - if("Station Engineer") - clothes_s = new /icon('icons/mob/uniform.dmi', "engine_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) - if("Life Support Specialist") - clothes_s = new /icon('icons/mob/uniform.dmi', "atmos_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) - if("Mechanic") - clothes_s = new /icon('icons/mob/uniform.dmi', "mechanic_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) - if("Roboticist") - clothes_s = new /icon('icons/mob/uniform.dmi', "robotics_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) - if("Syndicate Agent") - clothes_s = new /icon('icons/mob/uniform.dmi', "syndicate_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if("Syndicate Officer") - clothes_s = new /icon('icons/mob/uniform.dmi', "syndicate_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "swat_gl"), ICON_UNDERLAY) - if("Syndicate Nuclear Operative") - clothes_s = new /icon('icons/mob/uniform.dmi', "syndicate_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "swat_gl"), ICON_UNDERLAY) - else if(H.mind && H.mind.assigned_role in get_all_centcom_jobs()) - clothes_s = new /icon('icons/mob/uniform.dmi', "officer_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) - else - clothes_s = new /icon('icons/mob/uniform.dmi', "grey_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - - preview_icon.Blend(face_s, ICON_OVERLAY) // Why do we do this twice - if(clothes_s) - preview_icon.Blend(clothes_s, ICON_OVERLAY) - //Bus body accessories that go over clothes. - if(H.body_accessory && istype(H.body_accessory, /datum/body_accessory/body)) - temp = new/icon("icon" = H.body_accessory.icon, "icon_state" = H.body_accessory.icon_state) - if(H.body_accessory.pixel_x_offset) - temp.Shift(EAST, H.body_accessory.pixel_x_offset) - if(H.body_accessory.pixel_y_offset) - temp.Shift(NORTH, H.body_accessory.pixel_y_offset) - if(H.dna.species.bodyflags & HAS_SKIN_COLOR) - temp.Blend(H.skin_colour, H.body_accessory.blend_mode) - if(t_marking_s) - temp.Blend(t_marking_s, ICON_OVERLAY) - preview_icon.Blend(temp, ICON_OVERLAY) - qdel(face_s) - qdel(clothes_s) - - return preview_icon +/hook/startup/proc/createDatacore() + data_core = new /datum/datacore() + return 1 + +/datum/datacore + var/list/medical = list() + var/list/general = list() + var/list/security = list() + //This list tracks characters spawned in the world and cannot be modified in-game. Currently referenced by respawn_character(). + var/list/locked = list() + +/datum/datacore/proc/get_manifest(monochrome, OOC) + var/list/heads = new() + var/list/sec = new() + var/list/eng = new() + var/list/med = new() + var/list/sci = new() + var/list/ser = new() + var/list/sup = new() + var/list/bot = new() + var/list/misc = new() + var/list/isactive = new() + var/dat = {" + + + + "} + var/even = 0 + // sort mobs + for(var/datum/data/record/t in data_core.general) + var/name = t.fields["name"] + var/rank = t.fields["rank"] + var/real_rank = t.fields["real_rank"] + if(OOC) + var/active = 0 + for(var/mob/M in GLOB.player_list) + if(M.real_name == name && M.client && M.client.inactivity <= 10 * 60 * 10) + active = 1 + break + isactive[name] = active ? "Active" : "Inactive" + else + isactive[name] = t.fields["p_stat"] + var/department = 0 + if(real_rank in command_positions) + heads[name] = rank + department = 1 + if(real_rank in security_positions) + sec[name] = rank + department = 1 + if(real_rank in engineering_positions) + eng[name] = rank + department = 1 + if(real_rank in medical_positions) + med[name] = rank + department = 1 + if(real_rank in science_positions) + sci[name] = rank + department = 1 + if(real_rank in service_positions) + ser[name] = rank + department = 1 + if(real_rank in supply_positions) + sup[name] = rank + department = 1 + if(real_rank in nonhuman_positions) + bot[name] = rank + department = 1 + if(!department && !(name in heads)) + misc[name] = rank + if(heads.len > 0) + dat += "" + for(var/name in heads) + dat += "" + even = !even + if(sec.len > 0) + dat += "" + for(var/name in sec) + dat += "" + even = !even + if(eng.len > 0) + dat += "" + for(var/name in eng) + dat += "" + even = !even + if(med.len > 0) + dat += "" + for(var/name in med) + dat += "" + even = !even + if(sci.len > 0) + dat += "" + for(var/name in sci) + dat += "" + even = !even + if(ser.len > 0) + dat += "" + for(var/name in ser) + dat += "" + even = !even + if(sup.len > 0) + dat += "" + for(var/name in sup) + dat += "" + even = !even + if(bot.len > 0) + dat += "" + for(var/name in bot) + dat += "" + even = !even + if(misc.len > 0) + dat += "" + for(var/name in misc) + dat += "" + even = !even + + dat += "
NameRankActivity
Heads
[name][heads[name]][isactive[name]]
Security
[name][sec[name]][isactive[name]]
Engineering
[name][eng[name]][isactive[name]]
Medical
[name][med[name]][isactive[name]]
Science
[name][sci[name]][isactive[name]]
Service
[name][ser[name]][isactive[name]]
Supply
[name][sup[name]][isactive[name]]
Silicon
[name][bot[name]][isactive[name]]
Miscellaneous
[name][misc[name]][isactive[name]]
" + dat = replacetext(dat, "\n", "") // so it can be placed on paper correctly + dat = replacetext(dat, "\t", "") + return dat + + +/* +We can't just insert in HTML into the nanoUI so we need the raw data to play with. +Instead of creating this list over and over when someone leaves their PDA open to the page +we'll only update it when it changes. The PDA_Manifest global list is zeroed out upon any change +using /datum/datacore/proc/manifest_inject(), or manifest_insert() +*/ + +var/global/list/PDA_Manifest = list() + +/datum/datacore/proc/get_manifest_json() + if(PDA_Manifest.len) + return + var/heads[0] + var/sec[0] + var/eng[0] + var/med[0] + var/sci[0] + var/ser[0] + var/sup[0] + var/bot[0] + var/misc[0] + for(var/datum/data/record/t in data_core.general) + var/name = sanitize(t.fields["name"]) + var/rank = sanitize(t.fields["rank"]) + var/real_rank = t.fields["real_rank"] + + var/isactive = t.fields["p_stat"] + var/department = 0 + var/depthead = 0 // Department Heads will be placed at the top of their lists. + if(real_rank in command_positions) + heads[++heads.len] = list("name" = name, "rank" = rank, "active" = isactive) + department = 1 + depthead = 1 + if(rank == "Captain" && heads.len != 1) + heads.Swap(1, heads.len) + + if(real_rank in security_positions) + sec[++sec.len] = list("name" = name, "rank" = rank, "active" = isactive) + department = 1 + if(depthead && sec.len != 1) + sec.Swap(1, sec.len) + + if(real_rank in engineering_positions) + eng[++eng.len] = list("name" = name, "rank" = rank, "active" = isactive) + department = 1 + if(depthead && eng.len != 1) + eng.Swap(1, eng.len) + + if(real_rank in medical_positions) + med[++med.len] = list("name" = name, "rank" = rank, "active" = isactive) + department = 1 + if(depthead && med.len != 1) + med.Swap(1, med.len) + + if(real_rank in science_positions) + sci[++sci.len] = list("name" = name, "rank" = rank, "active" = isactive) + department = 1 + if(depthead && sci.len != 1) + sci.Swap(1, sci.len) + + if(real_rank in service_positions) + ser[++ser.len] = list("name" = name, "rank" = rank, "active" = isactive) + department = 1 + if(depthead && ser.len != 1) + ser.Swap(1, ser.len) + + if(real_rank in supply_positions) + sup[++sup.len] = list("name" = name, "rank" = rank, "active" = isactive) + department = 1 + if(depthead && sup.len != 1) + sup.Swap(1, sup.len) + + if(real_rank in nonhuman_positions) + bot[++bot.len] = list("name" = name, "rank" = rank, "active" = isactive) + department = 1 + + if(!department && !(name in heads)) + misc[++misc.len] = list("name" = name, "rank" = rank, "active" = isactive) + + + PDA_Manifest = list(\ + "heads" = heads,\ + "sec" = sec,\ + "eng" = eng,\ + "med" = med,\ + "sci" = sci,\ + "ser" = ser,\ + "sup" = sup,\ + "bot" = bot,\ + "misc" = misc\ + ) + return + + + +/datum/datacore/proc/manifest() + for(var/mob/living/carbon/human/H in GLOB.player_list) + manifest_inject(H) + +/datum/datacore/proc/manifest_modify(name, assignment) + if(PDA_Manifest.len) + PDA_Manifest.Cut() + var/datum/data/record/foundrecord + var/real_title = assignment + + for(var/datum/data/record/t in data_core.general) + if(t) + if(t.fields["name"] == name) + foundrecord = t + break + + var/list/all_jobs = get_job_datums() + + for(var/datum/job/J in all_jobs) + var/list/alttitles = get_alternate_titles(J.title) + if(!J) continue + if(assignment in alttitles) + real_title = J.title + break + + if(foundrecord) + foundrecord.fields["rank"] = assignment + foundrecord.fields["real_rank"] = real_title + +var/record_id_num = 1001 +/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H) + if(PDA_Manifest.len) + PDA_Manifest.Cut() + + if(H.mind && (H.mind.assigned_role != H.mind.special_role)) + var/assignment + if(H.mind.role_alt_title) + assignment = H.mind.role_alt_title + else if(H.mind.assigned_role) + assignment = H.mind.assigned_role + else if(H.job) + assignment = H.job + else + assignment = "Unassigned" + + var/id = num2hex(record_id_num++, 6) + + + //General Record + var/datum/data/record/G = new() + G.fields["id"] = id + G.fields["name"] = H.real_name + G.fields["real_rank"] = H.mind.assigned_role + G.fields["rank"] = assignment + G.fields["age"] = H.age + G.fields["fingerprint"] = md5(H.dna.uni_identity) + G.fields["p_stat"] = "Active" + G.fields["m_stat"] = "Stable" + G.fields["sex"] = capitalize(H.gender) + G.fields["species"] = H.dna.species.name + G.fields["photo"] = get_id_photo(H) + G.fields["photo-south"] = "'data:image/png;base64,[icon2base64(icon(G.fields["photo"], dir = SOUTH))]'" + G.fields["photo-west"] = "'data:image/png;base64,[icon2base64(icon(G.fields["photo"], dir = WEST))]'" + if(H.gen_record && !jobban_isbanned(H, "Records")) + G.fields["notes"] = H.gen_record + else + G.fields["notes"] = "No notes found." + general += G + + //Medical Record + var/datum/data/record/M = new() + M.fields["id"] = id + M.fields["name"] = H.real_name + M.fields["blood_type"] = H.dna.blood_type + M.fields["b_dna"] = H.dna.unique_enzymes + M.fields["mi_dis"] = "None" + M.fields["mi_dis_d"] = "No minor disabilities have been declared." + M.fields["ma_dis"] = "None" + M.fields["ma_dis_d"] = "No major disabilities have been diagnosed." + M.fields["alg"] = "None" + M.fields["alg_d"] = "No allergies have been detected in this patient." + M.fields["cdi"] = "None" + M.fields["cdi_d"] = "No diseases have been diagnosed at the moment." + if(H.med_record && !jobban_isbanned(H, "Records")) + M.fields["notes"] = H.med_record + else + M.fields["notes"] = "No notes found." + medical += M + + //Security Record + var/datum/data/record/S = new() + S.fields["id"] = id + S.fields["name"] = H.real_name + S.fields["criminal"] = "None" + S.fields["mi_crim"] = "None" + S.fields["mi_crim_d"] = "No minor crime convictions." + S.fields["ma_crim"] = "None" + S.fields["ma_crim_d"] = "No major crime convictions." + S.fields["notes"] = "No notes." + if(H.sec_record && !jobban_isbanned(H, "Records")) + S.fields["notes"] = H.sec_record + else + S.fields["notes"] = "No notes." + LAZYINITLIST(S.fields["comments"]) + security += S + + //Locked Record + var/datum/data/record/L = new() + L.fields["id"] = md5("[H.real_name][H.mind.assigned_role]") + L.fields["name"] = H.real_name + L.fields["rank"] = H.mind.assigned_role + L.fields["age"] = H.age + L.fields["sex"] = capitalize(H.gender) + L.fields["blood_type"] = H.dna.blood_type + L.fields["b_dna"] = H.dna.unique_enzymes + L.fields["enzymes"] = H.dna.SE // Used in respawning + L.fields["identity"] = H.dna.UI // " + L.fields["image"] = getFlatIcon(H) //This is god-awful + L.fields["reference"] = H + locked += L + return + +/proc/get_id_photo(mob/living/carbon/human/H, var/custom_job = null) + var/icon/preview_icon = null + var/obj/item/organ/external/head/head_organ = H.get_organ("head") + var/obj/item/organ/internal/eyes/eyes_organ = H.get_int_organ(/obj/item/organ/internal/eyes) + + var/g = "m" + if(H.gender == FEMALE) + g = "f" + + var/icon/icobase = head_organ.icobase //At this point all the organs would have the same icobase, so this is just recycling. + + preview_icon = new /icon(icobase, "torso_[g]") + var/icon/temp + temp = new /icon(icobase, "groin_[g]") + preview_icon.Blend(temp, ICON_OVERLAY) + var/head = "head" + if(head_organ.alt_head && head_organ.dna.species.bodyflags & HAS_ALT_HEADS) + var/datum/sprite_accessory/alt_heads/alternate_head = GLOB.alt_heads_list[head_organ.alt_head] + if(alternate_head.icon_state) + head = alternate_head.icon_state + temp = new /icon(icobase, "[head]_[g]") + preview_icon.Blend(temp, ICON_OVERLAY) + + //Tail + if(H.body_accessory && istype(H.body_accessory, /datum/body_accessory/tail)) + temp = new/icon("icon" = H.body_accessory.icon, "icon_state" = H.body_accessory.icon_state) + preview_icon.Blend(temp, ICON_OVERLAY) + else if(H.tail && H.dna.species.bodyflags & HAS_TAIL) + temp = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[H.tail]_s") + preview_icon.Blend(temp, ICON_OVERLAY) + + for(var/obj/item/organ/external/E in H.bodyparts) + preview_icon.Blend(E.get_icon(), ICON_OVERLAY) + + // Skin tone + if(H.dna.species.bodyflags & HAS_SKIN_TONE) + if(H.s_tone >= 0) + preview_icon.Blend(rgb(H.s_tone, H.s_tone, H.s_tone), ICON_ADD) + else + preview_icon.Blend(rgb(-H.s_tone, -H.s_tone, -H.s_tone), ICON_SUBTRACT) + + // Proper Skin color - Fix, you can't have HAS_SKIN_TONE *and* HAS_SKIN_COLOR + if(H.dna.species.bodyflags & HAS_SKIN_COLOR) + preview_icon.Blend(H.skin_colour, ICON_ADD) + + //Tail Markings + var/icon/t_marking_s + if(H.dna.species.bodyflags & HAS_TAIL_MARKINGS) + var/tail_marking = H.m_styles["tail"] + var/datum/sprite_accessory/tail_marking_style = GLOB.marking_styles_list[tail_marking] + if(tail_marking_style && tail_marking_style.species_allowed) + t_marking_s = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]_s") + t_marking_s.Blend(H.m_colours["tail"], ICON_ADD) + if(!(H.body_accessory && istype(H.body_accessory, /datum/body_accessory/body))) + preview_icon.Blend(t_marking_s, ICON_OVERLAY) + + var/icon/face_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "bald_s") + if(!(H.dna.species.bodyflags & NO_EYES)) + var/icon/eyes_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = H.dna.species ? H.dna.species.eyes : "eyes_s") + if(!eyes_organ) + return + eyes_s.Blend(eyes_organ.eye_colour, ICON_ADD) + face_s.Blend(eyes_s, ICON_OVERLAY) + + var/datum/sprite_accessory/hair_style = GLOB.hair_styles_full_list[head_organ.h_style] + if(hair_style) + var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s") + // I'll want to make a species-specific proc for this sooner or later + // But this'll do for now + if(istype(head_organ.dna.species, /datum/species/slime)) + hair_s.Blend("[H.skin_colour]A0", ICON_AND) //A0 = 160 alpha. + else + hair_s.Blend(head_organ.hair_colour, ICON_ADD) + + if(hair_style.secondary_theme) + var/icon/hair_secondary_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_[hair_style.secondary_theme]_s") + if(!hair_style.no_sec_colour) + hair_secondary_s.Blend(head_organ.sec_hair_colour, ICON_ADD) + hair_s.Blend(hair_secondary_s, ICON_OVERLAY) + + face_s.Blend(hair_s, ICON_OVERLAY) + + //Head Accessory + if(head_organ.dna.species.bodyflags & HAS_HEAD_ACCESSORY) + var/datum/sprite_accessory/head_accessory_style = GLOB.head_accessory_styles_list[head_organ.ha_style] + if(head_accessory_style && head_accessory_style.species_allowed) + var/icon/head_accessory_s = new/icon("icon" = head_accessory_style.icon, "icon_state" = "[head_accessory_style.icon_state]_s") + head_accessory_s.Blend(head_organ.headacc_colour, ICON_ADD) + face_s.Blend(head_accessory_s, ICON_OVERLAY) + + var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[head_organ.f_style] + if(facial_hair_style && facial_hair_style.species_allowed) + var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s") + if(istype(head_organ.dna.species, /datum/species/slime)) + facial_s.Blend("[H.skin_colour]A0", ICON_ADD) //A0 = 160 alpha. + else + facial_s.Blend(head_organ.facial_colour, ICON_ADD) + + if(facial_hair_style.secondary_theme) + var/icon/facial_secondary_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_[facial_hair_style.secondary_theme]_s") + if(!facial_hair_style.no_sec_colour) + facial_secondary_s.Blend(head_organ.sec_facial_colour, ICON_ADD) + facial_s.Blend(facial_secondary_s, ICON_OVERLAY) + + face_s.Blend(facial_s, ICON_OVERLAY) + + //Markings + if((H.dna.species.bodyflags & HAS_HEAD_MARKINGS) || (H.dna.species.bodyflags & HAS_BODY_MARKINGS)) + if(H.dna.species.bodyflags & HAS_BODY_MARKINGS) //Body markings. + var/body_marking = H.m_styles["body"] + var/datum/sprite_accessory/body_marking_style = GLOB.marking_styles_list[body_marking] + if(body_marking_style && body_marking_style.species_allowed) + var/icon/b_marking_s = new/icon("icon" = body_marking_style.icon, "icon_state" = "[body_marking_style.icon_state]_s") + b_marking_s.Blend(H.m_colours["body"], ICON_ADD) + face_s.Blend(b_marking_s, ICON_OVERLAY) + if(H.dna.species.bodyflags & HAS_HEAD_MARKINGS) //Head markings. + var/head_marking = H.m_styles["head"] + var/datum/sprite_accessory/head_marking_style = GLOB.marking_styles_list[head_marking] + if(head_marking_style && head_marking_style.species_allowed) + var/icon/h_marking_s = new/icon("icon" = head_marking_style.icon, "icon_state" = "[head_marking_style.icon_state]_s") + h_marking_s.Blend(H.m_colours["head"], ICON_ADD) + face_s.Blend(h_marking_s, ICON_OVERLAY) + + preview_icon.Blend(face_s, ICON_OVERLAY) + + + var/icon/clothes_s = null + var/job_clothes = null + if(custom_job) + job_clothes = custom_job + else if(H.mind) + job_clothes = H.mind.assigned_role + switch(job_clothes) + if("Head of Personnel") + clothes_s = new /icon('icons/mob/uniform.dmi', "hop_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + if("Nanotrasen Representative") + clothes_s = new /icon('icons/mob/uniform.dmi', "officer_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) + if("Blueshield") + clothes_s = new /icon('icons/mob/uniform.dmi', "officer_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "swat_gl"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "blueshield"), ICON_OVERLAY) + if("Magistrate") + clothes_s = new /icon('icons/mob/uniform.dmi', "really_black_suit_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "judge"), ICON_OVERLAY) + if("Bartender") + clothes_s = new /icon('icons/mob/uniform.dmi', "ba_suit_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Botanist") + clothes_s = new /icon('icons/mob/uniform.dmi', "hydroponics_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Chef") + clothes_s = new /icon('icons/mob/uniform.dmi', "chef_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Janitor") + clothes_s = new /icon('icons/mob/uniform.dmi', "janitor_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Librarian") + clothes_s = new /icon('icons/mob/uniform.dmi', "red_suit_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Barber") + clothes_s = new /icon('icons/mob/uniform.dmi', "barber_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Clown") + clothes_s = new /icon('icons/mob/uniform.dmi', "clown_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "clown"), ICON_UNDERLAY) + if("Mime") + clothes_s = new /icon('icons/mob/uniform.dmi', "mime_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Quartermaster") + clothes_s = new /icon('icons/mob/uniform.dmi', "qm_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + if("Cargo Technician") + clothes_s = new /icon('icons/mob/uniform.dmi', "cargotech_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Shaft Miner") + clothes_s = new /icon('icons/mob/uniform.dmi', "miner_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Lawyer") + clothes_s = new /icon('icons/mob/uniform.dmi', "internalaffairs_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + if("Chaplain") + clothes_s = new /icon('icons/mob/uniform.dmi', "chapblack_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Research Director") + clothes_s = new /icon('icons/mob/uniform.dmi', "director_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) + if("Scientist") + clothes_s = new /icon('icons/mob/uniform.dmi', "toxinswhite_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_tox_open"), ICON_OVERLAY) + if("Chemist") + clothes_s = new /icon('icons/mob/uniform.dmi', "chemistrywhite_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_chem_open"), ICON_OVERLAY) + if("Chief Medical Officer") + clothes_s = new /icon('icons/mob/uniform.dmi', "cmo_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_cmo_open"), ICON_OVERLAY) + if("Medical Doctor") + clothes_s = new /icon('icons/mob/uniform.dmi', "medical_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) + if("Coroner") + clothes_s = new /icon('icons/mob/uniform.dmi', "scrubsblack_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_mort_open"), ICON_OVERLAY) + if("Geneticist") + clothes_s = new /icon('icons/mob/uniform.dmi', "geneticswhite_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_gen_open"), ICON_OVERLAY) + if("Virologist") + clothes_s = new /icon('icons/mob/uniform.dmi', "virologywhite_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_vir_open"), ICON_OVERLAY) + if("Psychiatrist") + clothes_s = new /icon('icons/mob/uniform.dmi', "psych_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_UNDERLAY) + if("Paramedic") + clothes_s = new /icon('icons/mob/uniform.dmi', "paramedic_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Captain") + clothes_s = new /icon('icons/mob/uniform.dmi', "captain_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + if("Head of Security") + clothes_s = new /icon('icons/mob/uniform.dmi', "hosred_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + if("Warden") + clothes_s = new /icon('icons/mob/uniform.dmi', "warden_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + if("Detective") + clothes_s = new /icon('icons/mob/uniform.dmi', "detective_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "detective"), ICON_OVERLAY) + if("Security Pod Pilot") + clothes_s = new /icon('icons/mob/uniform.dmi', "secred_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "bomber"), ICON_OVERLAY) + if("Brig Physician") + clothes_s = new /icon('icons/mob/uniform.dmi', "medical_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "fr_jacket_open"), ICON_OVERLAY) + if("Security Officer") + clothes_s = new /icon('icons/mob/uniform.dmi', "secred_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + if("Chief Engineer") + clothes_s = new /icon('icons/mob/uniform.dmi', "chief_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) + if("Station Engineer") + clothes_s = new /icon('icons/mob/uniform.dmi', "engine_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) + if("Life Support Specialist") + clothes_s = new /icon('icons/mob/uniform.dmi', "atmos_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) + if("Mechanic") + clothes_s = new /icon('icons/mob/uniform.dmi', "mechanic_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) + if("Roboticist") + clothes_s = new /icon('icons/mob/uniform.dmi', "robotics_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) + if("Syndicate Agent") + clothes_s = new /icon('icons/mob/uniform.dmi', "syndicate_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if("Syndicate Officer") + clothes_s = new /icon('icons/mob/uniform.dmi', "syndicate_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "swat_gl"), ICON_UNDERLAY) + if("Syndicate Nuclear Operative") + clothes_s = new /icon('icons/mob/uniform.dmi', "syndicate_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "swat_gl"), ICON_UNDERLAY) + else if(H.mind && H.mind.assigned_role in get_all_centcom_jobs()) + clothes_s = new /icon('icons/mob/uniform.dmi', "officer_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) + else + clothes_s = new /icon('icons/mob/uniform.dmi', "grey_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + + preview_icon.Blend(face_s, ICON_OVERLAY) // Why do we do this twice + if(clothes_s) + preview_icon.Blend(clothes_s, ICON_OVERLAY) + //Bus body accessories that go over clothes. + if(H.body_accessory && istype(H.body_accessory, /datum/body_accessory/body)) + temp = new/icon("icon" = H.body_accessory.icon, "icon_state" = H.body_accessory.icon_state) + if(H.body_accessory.pixel_x_offset) + temp.Shift(EAST, H.body_accessory.pixel_x_offset) + if(H.body_accessory.pixel_y_offset) + temp.Shift(NORTH, H.body_accessory.pixel_y_offset) + if(H.dna.species.bodyflags & HAS_SKIN_COLOR) + temp.Blend(H.skin_colour, H.body_accessory.blend_mode) + if(t_marking_s) + temp.Blend(t_marking_s, ICON_OVERLAY) + preview_icon.Blend(temp, ICON_OVERLAY) + qdel(face_s) + qdel(clothes_s) + + return preview_icon diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 6e716f057c4..00054349e36 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -66,4 +66,4 @@ /datum/nothing - // Placeholder object, used for ispath checks. Has to be defined to prevent errors, but shouldn't ever be created. \ No newline at end of file + // Placeholder object, used for ispath checks. Has to be defined to prevent errors, but shouldn't ever be created. diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index daa50119135..1defb9a6afa 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -1,1387 +1,1387 @@ -// reference: /client/proc/modify_variables(var/atom/O, var/param_var_name = null, var/autodetect_class = 0) - -/datum/proc/can_vv_get(var_name) - return TRUE - -// /client/proc/can_vv_get(var_name) -// return TRUE - -/datum/proc/vv_edit_var(var_name, var_value) //called whenever a var is edited - switch(var_name) - if("vars") - return FALSE - if("var_edited") - return FALSE - var_edited = TRUE - vars[var_name] = var_value - - . = TRUE - - -/client/vv_edit_var(var_name, var_value) //called whenever a var is edited - switch(var_name) - if("vars") - return FALSE - if("var_edited") - return FALSE - var_edited = TRUE - vars[var_name] = var_value - - . = TRUE - -/datum/proc/vv_get_var(var_name) - switch(var_name) - if("attack_log", "debug_log") - return debug_variable(var_name, vars[var_name], 0, src, sanitize = FALSE) - if("vars") - return debug_variable(var_name, list(), 0, src) - return debug_variable(var_name, vars[var_name], 0, src) - -/client/vv_get_var(var_name) - switch(var_name) - if("vars") - return debug_variable(var_name, list(), 0, src) - return debug_variable(var_name, vars[var_name], 0, src) - -/datum/proc/can_vv_delete() - return TRUE - -//please call . = ..() first and append to the result, that way parent items are always at the top and child items are further down -//add seperaters by doing . += "---" -/datum/proc/vv_get_dropdown() - . = list() - . += "---" - .["Call Proc"] = "?_src_=vars;proc_call=[UID()]" - .["Mark Object"] = "?_src_=vars;mark_object=[UID()]" - .["Jump to Object"] = "?_src_=vars;jump_to=[UID()]" - .["Delete"] = "?_src_=vars;delete=[UID()]" - .["Modify Traits"] = "?_src_=vars;traitmod=[UID()]" - . += "---" - -/client/vv_get_dropdown() - . = list() - . += "---" - .["Call Proc"] = "?_src_=vars;proc_call=[UID()]" - .["Mark Object"] = "?_src_=vars;mark_object=[UID()]" - .["Delete"] = "?_src_=vars;delete=[UID()]" - .["Modify Traits"] = "?_src_=vars;traitmod=[UID()]" - . += "---" - -/client/proc/debug_variables(datum/D in world) - set category = "Debug" - set name = "View Variables" - - var/static/cookieoffset = rand(1, 9999) //to force cookies to reset after the round. - - if(!is_admin(usr)) - to_chat(usr, "You need to be an administrator to access this.") - return - - if(!D) - return - - - var/islist = islist(D) - var/isclient = isclient(D) - if(!islist && !isclient && !istype(D)) - return - - var/title = "" - var/icon/sprite - var/hash - var/refid - - if(!islist) - refid = "[D.UID()]" - else - refid = "\ref[D]" - - var/type = /list - if(!islist) - type = D.type - - if(isatom(D)) - var/atom/A = D - if(A.icon && A.icon_state) - sprite = new /icon(A.icon, A.icon_state) - hash = md5(A.icon) - hash = md5(hash + A.icon_state) - usr << browse_rsc(sprite, "vv[hash].png") - - - var/sprite_text - if(sprite) - sprite_text = "" - - - var/list/atomsnowflake = list() - if(isatom(D)) - var/atom/A = D - if(isliving(A)) - var/mob/living/L = A - atomsnowflake += "[L]" - if(L.dir) - atomsnowflake += "
<< [dir2text(L.dir)] >>" - atomsnowflake += {" -
[L.ckey ? L.ckey : "No ckey"] / [L.real_name ? L.real_name : "No real name"] -
- BRUTE:[L.getBruteLoss()] - FIRE:[L.getFireLoss()] - TOXIN:[L.getToxLoss()] - OXY:[L.getOxyLoss()] - CLONE:[L.getCloneLoss()] - BRAIN:[L.getBrainLoss()] - STAMINA:[L.getStaminaLoss()] - - "} - else - atomsnowflake += "[A]" - if(A.dir) - atomsnowflake += "
<< [dir2text(A.dir)] >>" - else - atomsnowflake += "[D]" - - - var/formatted_type = "[type]" - if(length(formatted_type) > 25) - var/middle_point = length(formatted_type) / 2 - var/splitpoint = findtext(formatted_type, "/", middle_point) - if(splitpoint) - formatted_type = "[copytext(formatted_type, 1, splitpoint)]
[copytext(formatted_type, splitpoint)]" - else - formatted_type = "Type too long" //No suitable splitpoint (/) found. - - - var/marked - if(holder.marked_datum && holder.marked_datum == D) - marked = "
Marked Object" - - - var/varedited_line = "" - if(isatom(D)) - var/atom/A = D - if(A.admin_spawned) - varedited_line += "
Admin Spawned" - - - if(!islist && D.var_edited) - varedited_line += "
Var Edited" - - - var/dropdownoptions = list() - if(islist) - dropdownoptions = list( - "---", - "Add Item" = "?_src_=vars;listadd=[refid]", - "Remove Nulls" = "?_src_=vars;listnulls=[refid]", - "Remove Dupes" = "?_src_=vars;listdupes=[refid]", - "Set len" = "?_src_=vars;listlen=[refid]", - "Shuffle" = "?_src_=vars;listshuffle=[refid]" - ) - else - dropdownoptions = D.vv_get_dropdown() - - - var/list/dropdownoptions_html = list() - for(var/name in dropdownoptions) - var/link = dropdownoptions[name] - if(link) - dropdownoptions_html += "" - else - dropdownoptions_html += "" - - - var/list/names = list() - if(!islist) - for(var/V in D.vars) - names += V - - - sleep(1) // Without a sleep here, VV sometimes disconnects clients - - - var/list/variable_html = list() - if(islist) - var/list/L = D - for(var/i in 1 to L.len) - var/key = L[i] - var/value - if(IS_NORMAL_LIST(L) && !isnum(key)) - value = L[key] - variable_html += debug_variable(i, value, 0, D) - else - names = sortList(names) - for(var/V in names) - if(D.can_vv_get(V)) - variable_html += D.vv_get_var(V) - - var/html = {" - - - [title] - - - - -

- - - - - -
- - - - -
- [sprite_text] -
- [atomsnowflake.Join()] -
-
-
- [formatted_type] - [marked] - [varedited_line] -
-
-
- Refresh -
- -
-
-
-
-
- - E - Edit, tries to determine the variable type by itself.
- C - Change, asks you for the var type first.
- M - Mass modify: changes this variable for all objects of this type.
-
-
- - - - - -
-
- Search: -
-
- -
-
-
    - [variable_html.Join()] -
- - - - "} - - usr << browse(html, "window=variables[refid];size=475x650") - -#define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing ) -/proc/debug_variable(name, value, level, var/datum/DA = null, sanitize = TRUE) - var/header - if(DA) - if(islist(DA)) - var/index = name - if(value) - name = DA[name] // name is really the index until this line - else - value = DA[name] - header = "
  • (E) (C) (-) " - else - header = "
  • (E) (C) (M) " - else - header = "
  • " - - var/item - if(isnull(value)) - item = "[VV_HTML_ENCODE(name)] = null" - - else if(istext(value)) - item = "[VV_HTML_ENCODE(name)] = \"[VV_HTML_ENCODE(value)]\"" - - else if(isicon(value)) - #ifdef VARSICON - item = "[name] = /icon ([value]) [bicon(value, use_class=0)]" - #else - item = "[name] = /icon ([value])" - #endif - - else if(istype(value, /image)) - var/image/I = value - #ifdef VARSICON - item = "[name] \ref[value] = /image ([value]) [bicon(value, use_class=0)]" - #else - item = "[name] \ref[value] = /image ([value])" - #endif - - else if(isfile(value)) - item = "[VV_HTML_ENCODE(name)] = '[value]'" - - else if(istype(value, /datum)) - var/datum/D = value - item = "[VV_HTML_ENCODE(name)] \ref[value] = [D.type]" - - else if(istype(value, /client)) - var/client/C = value - item = "[VV_HTML_ENCODE(name)] \ref[value] = [C] [C.type]" -// - else if(islist(value)) - var/list/L = value - var/list/items = list() - - if(L.len > 0 && !(name == "underlays" || name == "overlays" || name == "vars" || L.len > (IS_NORMAL_LIST(L) ? 250 : 300))) - for(var/i in 1 to L.len) - var/key = L[i] - var/val - if(IS_NORMAL_LIST(L) && !isnum(key)) - val = L[key] - if(!val) - val = key - key = i - - items += debug_variable(key, val, level + 1, sanitize = sanitize) - - item = "[VV_HTML_ENCODE(name)] = /list ([L.len])
      [items.Join()]
    " - - else - item = "[VV_HTML_ENCODE(name)] = /list ([L.len])" - - else - item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(value)]" - - return "[header][item]
  • " - -#undef VV_HTML_ENCODE - -/client/proc/view_var_Topic(href, href_list, hsrc) - //This should all be moved over to datum/admins/Topic() or something ~Carn - if(!check_rights(R_ADMIN|R_MOD)) - return - - if(view_var_Topic_list(href, href_list, hsrc)) // done because you can't use UIDs with lists and I don't want to snowflake into the below check to supress warnings - return - - // Correct and warn about any VV topic links that aren't using UIDs - for(var/paramname in href_list) - if(findtext(href_list[paramname], "]_")) - continue // Contains UID-specific formatting, skip it - var/datum/D = locate(href_list[paramname]) - if(!D) - continue - var/datuminfo = "[D]" - if(istype(D)) - datuminfo = datum_info_line(D) - href_list[paramname] = D.UID() - else if(isclient(D)) - var/client/C = D - href_list[paramname] = C.UID() - log_runtime(EXCEPTION("Found \\ref-based '[paramname]' param in VV topic for [datuminfo], should be UID: [href]")) - - if(href_list["Vars"]) - debug_variables(locateUID(href_list["Vars"])) - - //~CARN: for renaming mobs (updates their name, real_name, mind.name, their ID/PDA and datacore records). - else if(href_list["rename"]) - if(!check_rights(R_ADMIN)) return - - var/mob/M = locateUID(href_list["rename"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - var/new_name = reject_bad_name(sanitize(copytext(input(usr, "What would you like to name this mob?", "Input a name", M.real_name) as text|null, 1, MAX_NAME_LEN)), allow_numbers = TRUE) - if( !new_name || !M ) return - - message_admins("Admin [key_name_admin(usr)] renamed [key_name_admin(M)] to [new_name].") - M.rename_character(M.real_name, new_name) - href_list["datumrefresh"] = href_list["rename"] - - else if(href_list["varnameedit"] && href_list["datumedit"]) - if(!check_rights(R_VAREDIT)) return - - var/D = locateUID(href_list["datumedit"]) - if(!istype(D,/datum) && !istype(D,/client)) - to_chat(usr, "This can only be used on instances of types /client or /datum") - return - - modify_variables(D, href_list["varnameedit"], 1) - - else if(href_list["togbit"]) - if(!check_rights(R_VAREDIT)) return - - var/atom/D = locateUID(href_list["subject"]) - if(!istype(D,/datum) && !istype(D,/client)) - to_chat(usr, "This can only be used on instances of types /client or /datum") - return - if(!(href_list["var"] in D.vars)) - to_chat(usr, "Unable to find variable specified.") - return - var/value = D.vars[href_list["var"]] - value ^= 1 << text2num(href_list["togbit"]) - - D.vars[href_list["var"]] = value - - else if(href_list["varnamechange"] && href_list["datumchange"]) - if(!check_rights(R_VAREDIT)) return - - var/D = locateUID(href_list["datumchange"]) - if(!istype(D,/datum) && !istype(D,/client)) - to_chat(usr, "This can only be used on instances of types /client or /datum") - return - - modify_variables(D, href_list["varnamechange"], 0) - - else if(href_list["varnamemass"] && href_list["datummass"]) - if(!check_rights(R_VAREDIT)) return - - var/atom/A = locateUID(href_list["datummass"]) - if(!istype(A)) - to_chat(usr, "This can only be used on instances of type /atom") - return - - cmd_mass_modify_object_variables(A, href_list["varnamemass"]) - - - else if(href_list["mob_player_panel"]) - if(!check_rights(R_ADMIN|R_MOD)) return - - var/mob/M = locateUID(href_list["mob_player_panel"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.holder.show_player_panel(M) - href_list["datumrefresh"] = href_list["mob_player_panel"] - - else if(href_list["give_spell"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - var/mob/M = locateUID(href_list["give_spell"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.give_spell(M) - href_list["datumrefresh"] = href_list["give_spell"] - - else if(href_list["givemartialart"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - var/mob/living/carbon/C = locateUID(href_list["givemartialart"]) - if(!istype(C)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - var/list/artpaths = subtypesof(/datum/martial_art) - var/list/artnames = list() - for(var/i in artpaths) - var/datum/martial_art/M = i - artnames[initial(M.name)] = M - - var/result = input(usr, "Choose the martial art to teach", "JUDO CHOP") as null|anything in artnames - if(!usr) - return - if(QDELETED(C)) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(result) - var/chosenart = artnames[result] - var/datum/martial_art/MA = new chosenart - MA.teach(C) - - href_list["datumrefresh"] = href_list["givemartialart"] - - else if(href_list["give_disease"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - var/mob/M = locateUID(href_list["give_disease"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.give_disease(M) - href_list["datumrefresh"] = href_list["give_spell"] - - else if(href_list["godmode"]) - if(!check_rights(R_REJUVINATE)) return - - var/mob/M = locateUID(href_list["godmode"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.cmd_admin_godmode(M) - href_list["datumrefresh"] = href_list["godmode"] - - else if(href_list["gib"]) - if(!check_rights(R_ADMIN|R_EVENT)) return - - var/mob/M = locateUID(href_list["gib"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.cmd_admin_gib(M) - - else if(href_list["build_mode"]) - if(!check_rights(R_BUILDMODE)) return - - var/mob/M = locateUID(href_list["build_mode"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - togglebuildmode(M) - href_list["datumrefresh"] = href_list["build_mode"] - - else if(href_list["drop_everything"]) - if(!check_rights(R_DEBUG|R_ADMIN)) return - - var/mob/M = locateUID(href_list["drop_everything"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - if(usr.client) - usr.client.cmd_admin_drop_everything(M) - - else if(href_list["direct_control"]) - if(!check_rights(R_DEBUG|R_ADMIN)) return - - var/mob/M = locateUID(href_list["direct_control"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - if(usr.client) - usr.client.cmd_assume_direct_control(M) - - else if(href_list["make_skeleton"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - var/mob/living/carbon/human/H = locateUID(href_list["make_skeleton"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - - var/confirm = alert("Are you sure you want to turn this mob into a skeleton?","Confirm Skeleton Transformation","Yes","No") - if(confirm != "Yes") - return - - H.makeSkeleton() - message_admins("[key_name(usr)] has turned [key_name(H)] into a skeleton") - log_admin("[key_name_admin(usr)] has turned [key_name_admin(H)] into a skeleton") - href_list["datumrefresh"] = href_list["make_skeleton"] - - else if(href_list["offer_control"]) - if(!check_rights(R_ADMIN)) return - - var/mob/M = locateUID(href_list["offer_control"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - offer_control(M) - - else if(href_list["delete"]) - if(!check_rights(R_DEBUG, 0)) - return - - var/datum/D = locateUID(href_list["delete"]) - if(!D) - to_chat(usr, "Unable to locate item!") - admin_delete(D) - href_list["datumrefresh"] = href_list["delete"] - - else if(href_list["delall"]) - if(!check_rights(R_DEBUG|R_SERVER)) return - - var/obj/O = locateUID(href_list["delall"]) - if(!isobj(O)) - to_chat(usr, "This can only be used on instances of type /obj") - return - - var/action_type = alert("Strict type ([O.type]) or type and all subtypes?",,"Strict type","Type and subtypes","Cancel") - if(action_type == "Cancel" || !action_type) - return - - if(alert("Are you really sure you want to delete all objects of type [O.type]?",,"Yes","No") != "Yes") - return - - if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") - return - - var/O_type = O.type - switch(action_type) - if("Strict type") - var/i = 0 - for(var/obj/Obj in world) - if(Obj.type == O_type) - i++ - qdel(Obj) - if(!i) - to_chat(usr, "No objects of this type exist") - return - log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted)") - message_admins("[key_name_admin(usr)] deleted all objects of type [O_type] ([i] objects deleted)") - if("Type and subtypes") - var/i = 0 - for(var/obj/Obj in world) - if(istype(Obj,O_type)) - i++ - qdel(Obj) - if(!i) - to_chat(usr, "No objects of this type exist") - return - log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted)") - message_admins("[key_name_admin(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted)") - - else if(href_list["makespeedy"]) - if(!check_rights(R_DEBUG|R_ADMIN)) - return - var/obj/A = locateUID(href_list["makespeedy"]) - if(!istype(A)) - return - A.var_edited = TRUE - A.makeSpeedProcess() - log_admin("[key_name(usr)] has made [A] speed process") - message_admins("[key_name(usr)] has made [A] speed process") - return TRUE - - else if(href_list["makenormalspeed"]) - if(!check_rights(R_DEBUG|R_ADMIN)) - return - var/obj/A = locateUID(href_list["makenormalspeed"]) - if(!istype(A)) - return - A.var_edited = TRUE - A.makeNormalProcess() - log_admin("[key_name(usr)] has made [A] process normally") - message_admins("[key_name(usr)] has made [A] process normally") - return TRUE - - else if(href_list["addreagent"]) /* Made on /TG/, credit to them. */ - if(!check_rights(R_DEBUG|R_ADMIN)) return - - var/atom/A = locateUID(href_list["addreagent"]) - - if(!A.reagents) - var/amount = input(usr, "Specify the reagent size of [A]", "Set Reagent Size", 50) as num - if(amount) - A.create_reagents(amount) - - if(A.reagents) - var/chosen_id - var/list/reagent_options = sortAssoc(GLOB.chemical_reagents_list) - switch(alert(usr, "Choose a method.", "Add Reagents", "Enter ID", "Choose ID")) - if("Enter ID") - var/valid_id - while(!valid_id) - chosen_id = stripped_input(usr, "Enter the ID of the reagent you want to add.") - if(!chosen_id) //Get me out of here! - break - for(var/ID in reagent_options) - if(ID == chosen_id) - valid_id = 1 - if(!valid_id) - to_chat(usr, "A reagent with that ID doesn't exist!") - if("Choose ID") - chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in reagent_options - if(chosen_id) - var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", A.reagents.maximum_volume) as num - if(amount) - A.reagents.add_reagent(chosen_id, amount) - log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to \the [A]") - message_admins("[key_name(usr)] has added [amount] units of [chosen_id] to \the [A]") - - else if(href_list["explode"]) - if(!check_rights(R_DEBUG|R_EVENT)) return - - var/atom/A = locateUID(href_list["explode"]) - if(!isobj(A) && !ismob(A) && !isturf(A)) - to_chat(usr, "This can only be done to instances of type /obj, /mob and /turf") - return - - src.cmd_admin_explosion(A) - href_list["datumrefresh"] = href_list["explode"] - - else if(href_list["emp"]) - if(!check_rights(R_DEBUG|R_EVENT)) return - - var/atom/A = locateUID(href_list["emp"]) - if(!isobj(A) && !ismob(A) && !isturf(A)) - to_chat(usr, "This can only be done to instances of type /obj, /mob and /turf") - return - - src.cmd_admin_emp(A) - href_list["datumrefresh"] = href_list["emp"] - - else if(href_list["mark_object"]) - if(!check_rights(0)) return - - var/datum/D = locateUID(href_list["mark_object"]) - if(!istype(D)) - to_chat(usr, "This can only be done to instances of type /datum") - return - - src.holder.marked_datum = D - href_list["datumrefresh"] = href_list["mark_object"] - - else if(href_list["proc_call"]) - if(!check_rights(R_PROCCALL)) - return - - var/T = locateUID(href_list["proc_call"]) - - if(T) - callproc_datum(T) - - else if(href_list["jump_to"]) - if(!check_rights(R_ADMIN)) - return - - var/atom/A = locateUID(href_list["jump_to"]) - var/turf/T = get_turf(A) - if(T) - usr.client.jumptoturf(T) - href_list["datumrefresh"] = href_list["jump_to"] - - - else if(href_list["rotatedatum"]) - if(!check_rights(R_DEBUG|R_ADMIN)) return - - var/atom/A = locateUID(href_list["rotatedatum"]) - if(!istype(A)) - to_chat(usr, "This can only be done to instances of type /atom") - return - - switch(href_list["rotatedir"]) - if("right") A.dir = turn(A.dir, -45) - if("left") A.dir = turn(A.dir, 45) - - message_admins("[key_name_admin(usr)] has rotated \the [A]") - log_admin("[key_name(usr)] has rotated \the [A]") - href_list["datumrefresh"] = href_list["rotatedatum"] - - else if(href_list["makemonkey"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makemonkey"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("monkeyone"=href_list["makemonkey"])) - - else if(href_list["makerobot"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makerobot"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makerobot"=href_list["makerobot"])) - - else if(href_list["makealien"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makealien"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makealien"=href_list["makealien"])) - - else if(href_list["makeslime"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makeslime"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makeslime"=href_list["makeslime"])) - - else if(href_list["makesuper"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makesuper"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makesuper"=href_list["makesuper"])) - - else if(href_list["makeai"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makeai"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makeai"=href_list["makeai"])) - - else if(href_list["setspecies"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["setspecies"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - var/new_species = input("Please choose a new species.","Species",null) as null|anything in GLOB.all_species - - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - - var/datum/species/S = GLOB.all_species[new_species] - if(H.set_species(S.type)) - to_chat(usr, "Set species of [H] to [H.dna.species].") - H.regenerate_icons() - message_admins("[key_name_admin(usr)] has changed the species of [key_name_admin(H)] to [new_species]") - log_admin("[key_name(usr)] has changed the species of [key_name(H)] to [new_species]") - else - to_chat(usr, "Failed! Something went wrong.") - - else if(href_list["addlanguage"]) - if(!check_rights(R_SPAWN)) return - - var/mob/H = locateUID(href_list["addlanguage"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob") - return - - var/new_language = input("Please choose a language to add.","Language",null) as null|anything in GLOB.all_languages - - if(!new_language) - return - - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(H.add_language(new_language)) - to_chat(usr, "Added [new_language] to [H].") - message_admins("[key_name_admin(usr)] has given [key_name_admin(H)] the language [new_language]") - log_admin("[key_name(usr)] has given [key_name(H)] the language [new_language]") - else - to_chat(usr, "Mob already knows that language.") - - else if(href_list["remlanguage"]) - if(!check_rights(R_SPAWN)) return - - var/mob/H = locateUID(href_list["remlanguage"]) - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob") - return - - if(!H.languages.len) - to_chat(usr, "This mob knows no languages.") - return - - var/datum/language/rem_language = input("Please choose a language to remove.","Language",null) as null|anything in H.languages - - if(!rem_language) - return - - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(H.remove_language(rem_language.name)) - to_chat(usr, "Removed [rem_language] from [H].") - message_admins("[key_name_admin(usr)] has removed language [rem_language] from [key_name_admin(H)]") - log_admin("[key_name(usr)] has removed language [rem_language] from [key_name(H)]") - else - to_chat(usr, "Mob doesn't know that language.") - - else if(href_list["addverb"]) - if(!check_rights(R_DEBUG)) return - - var/mob/living/H = locateUID(href_list["addverb"]) - - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living") - return - var/list/possibleverbs = list() - possibleverbs += "Cancel" // One for the top... - possibleverbs += typesof(/mob/proc,/mob/verb,/mob/living/proc,/mob/living/verb) - switch(H.type) - if(/mob/living/carbon/human) - possibleverbs += typesof(/mob/living/carbon/proc,/mob/living/carbon/verb,/mob/living/carbon/human/verb,/mob/living/carbon/human/proc) - if(/mob/living/silicon/robot) - possibleverbs += typesof(/mob/living/silicon/proc,/mob/living/silicon/robot/proc,/mob/living/silicon/robot/verb) - if(/mob/living/silicon/ai) - possibleverbs += typesof(/mob/living/silicon/proc,/mob/living/silicon/ai/proc,/mob/living/silicon/ai/verb) - possibleverbs -= H.verbs - possibleverbs += "Cancel" // ...And one for the bottom - - var/verb = input("Select a verb!", "Verbs",null) as anything in possibleverbs - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - if(!verb || verb == "Cancel") - return - else - H.verbs += verb - message_admins("[key_name_admin(usr)] has given [key_name_admin(H)] the verb [verb]") - log_admin("[key_name(usr)] has given [key_name(H)] the verb [verb]") - - else if(href_list["remverb"]) - if(!check_rights(R_DEBUG)) return - - var/mob/H = locateUID(href_list["remverb"]) - - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob") - return - var/verb = input("Please choose a verb to remove.","Verbs",null) as null|anything in H.verbs - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - if(!verb) - return - else - H.verbs -= verb - message_admins("[key_name_admin(usr)] has removed verb [verb] from [key_name_admin(H)]") - log_admin("[key_name(usr)] has removed verb [verb] from [key_name(H)]") - - else if(href_list["addorgan"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/M = locateUID(href_list["addorgan"]) - if(!istype(M)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - var/new_organ = input("Please choose an organ to add.","Organ",null) as null|anything in subtypesof(/obj/item/organ)-/obj/item/organ - if(!new_organ) return - - if(!M) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(locateUID(new_organ) in M.internal_organs) - to_chat(usr, "Mob already has that organ.") - return - var/obj/item/organ/internal/organ = new new_organ - organ.insert(M) - message_admins("[key_name_admin(usr)] has given [key_name_admin(M)] the organ [new_organ]") - log_admin("[key_name(usr)] has given [key_name(M)] the organ [new_organ]") - - else if(href_list["remorgan"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/M = locateUID(href_list["remorgan"]) - if(!istype(M)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - var/obj/item/organ/internal/rem_organ = input("Please choose an organ to remove.","Organ",null) as null|anything in M.internal_organs - - if(!M) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(!(rem_organ in M.internal_organs)) - to_chat(usr, "Mob does not have that organ.") - return - - to_chat(usr, "Removed [rem_organ] from [M].") - rem_organ.remove(M) - message_admins("[key_name_admin(usr)] has removed the organ [rem_organ] from [key_name_admin(M)]") - log_admin("[key_name(usr)] has removed the organ [rem_organ] from [key_name(M)]") - qdel(rem_organ) - - else if(href_list["fix_nano"]) - if(!check_rights(R_DEBUG)) return - - var/mob/H = locateUID(href_list["fix_nano"]) - - if(!istype(H) || !H.client) - to_chat(usr, "This can only be done on mobs with clients") - return - - H.client.reload_nanoui_resources() - - to_chat(usr, "Resource files sent") - to_chat(H, "Your NanoUI Resource files have been refreshed") - - log_admin("[key_name(usr)] resent the NanoUI resource files to [key_name(H)]") - - else if(href_list["regenerateicons"]) - if(!check_rights(0)) return - - var/mob/M = locateUID(href_list["regenerateicons"]) - if(!ismob(M)) - to_chat(usr, "This can only be done to instances of type /mob") - return - M.regenerate_icons() - - else if(href_list["adjustDamage"] && href_list["mobToDamage"]) - if(!check_rights(R_DEBUG|R_ADMIN|R_EVENT)) return - - var/mob/living/L = locateUID(href_list["mobToDamage"]) - if(!istype(L)) return - - var/Text = href_list["adjustDamage"] - - var/amount = input("Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0) as num - - if(!L) - to_chat(usr, "Mob doesn't exist anymore") - return - - switch(Text) - if("brute") - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.adjustBruteLoss(amount, robotic = TRUE) - else - L.adjustBruteLoss(amount) - if("fire") - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.adjustFireLoss(amount, robotic = TRUE) - else - L.adjustFireLoss(amount) - if("toxin") - L.adjustToxLoss(amount) - if("oxygen") - L.adjustOxyLoss(amount) - if("brain") - L.adjustBrainLoss(amount) - if("clone") - L.adjustCloneLoss(amount) - if("stamina") - L.adjustStaminaLoss(amount) - else - to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]") - return - - if(amount != 0) - log_admin("[key_name(usr)] dealt [amount] amount of [Text] damage to [L]") - message_admins("[key_name_admin(usr)] dealt [amount] amount of [Text] damage to [L]") - href_list["datumrefresh"] = href_list["mobToDamage"] - - else if(href_list["traitmod"]) - if(!check_rights(NONE)) - return - var/datum/A = locateUID(href_list["traitmod"]) - if(!istype(A)) - return - holder.modify_traits(A) - - if(href_list["datumrefresh"]) - var/datum/DAT = locateUID(href_list["datumrefresh"]) - if(!istype(DAT, /datum) && !isclient(DAT)) - return - src.debug_variables(DAT) - - if(href_list["copyoutfit"]) - if(!check_rights(R_EVENT)) - return - var/mob/living/carbon/human/H = locateUID(href_list["copyoutfit"]) - if(istype(H)) - H.copy_outfit() - -/client/proc/view_var_Topic_list(href, href_list, hsrc) - if(href_list["VarsList"]) - debug_variables(locate(href_list["VarsList"])) - return TRUE - - if(href_list["listedit"] && href_list["index"]) - var/index = text2num(href_list["index"]) - if(!index) - return TRUE - - var/list/L = locate(href_list["listedit"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - mod_list(L, null, "list", "contents", index, autodetect_class = TRUE) - return TRUE - - if(href_list["listchange"] && href_list["index"]) - var/index = text2num(href_list["index"]) - if(!index) - return TRUE - - var/list/L = locate(href_list["listchange"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - mod_list(L, null, "list", "contents", index, autodetect_class = FALSE) - return TRUE - - if(href_list["listremove"] && href_list["index"]) - var/index = text2num(href_list["index"]) - if(!index) - return TRUE - - var/list/L = locate(href_list["listremove"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - var/variable = L[index] - var/prompt = alert("Do you want to remove item number [index] from list?", "Confirm", "Yes", "No") - if(prompt != "Yes") - return - L.Cut(index, index+1) - log_world("### ListVarEdit by [src]: /list's contents: REMOVED=[html_encode("[variable]")]") - log_admin("[key_name(src)] modified list's contents: REMOVED=[variable]") - message_admins("[key_name_admin(src)] modified list's contents: REMOVED=[variable]") - return TRUE - - if(href_list["listadd"]) - var/list/L = locate(href_list["listadd"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return TRUE - - mod_list_add(L, null, "list", "contents") - return TRUE - - if(href_list["listdupes"]) - var/list/L = locate(href_list["listdupes"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return TRUE - - uniqueList_inplace(L) - log_world("### ListVarEdit by [src]: /list contents: CLEAR DUPES") - log_admin("[key_name(src)] modified list's contents: CLEAR DUPES") - message_admins("[key_name_admin(src)] modified list's contents: CLEAR DUPES") - return TRUE - - if(href_list["listnulls"]) - var/list/L = locate(href_list["listnulls"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return TRUE - - listclearnulls(L) - log_world("### ListVarEdit by [src]: /list contents: CLEAR NULLS") - log_admin("[key_name(src)] modified list's contents: CLEAR NULLS") - message_admins("[key_name_admin(src)] modified list's contents: CLEAR NULLS") - return TRUE - - if(href_list["listlen"]) - var/list/L = locate(href_list["listlen"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return TRUE - var/value = vv_get_value(VV_NUM) - if(value["class"] != VV_NUM) - return TRUE - - L.len = value["value"] - log_world("### ListVarEdit by [src]: /list len: [L.len]") - log_admin("[key_name(src)] modified list's len: [L.len]") - message_admins("[key_name_admin(src)] modified list's len: [L.len]") - return TRUE - - if(href_list["listshuffle"]) - var/list/L = locate(href_list["listshuffle"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return TRUE - - shuffle_inplace(L) - log_world("### ListVarEdit by [src]: /list contents: SHUFFLE") - log_admin("[key_name(src)] modified list's contents: SHUFFLE") - message_admins("[key_name_admin(src)] modified list's contents: SHUFFLE") - return TRUE - - if(href_list["listrefresh"]) - debug_variables(locate(href_list["listrefresh"])) - return TRUE +// reference: /client/proc/modify_variables(var/atom/O, var/param_var_name = null, var/autodetect_class = 0) + +/datum/proc/can_vv_get(var_name) + return TRUE + +// /client/proc/can_vv_get(var_name) +// return TRUE + +/datum/proc/vv_edit_var(var_name, var_value) //called whenever a var is edited + switch(var_name) + if("vars") + return FALSE + if("var_edited") + return FALSE + var_edited = TRUE + vars[var_name] = var_value + + . = TRUE + + +/client/vv_edit_var(var_name, var_value) //called whenever a var is edited + switch(var_name) + if("vars") + return FALSE + if("var_edited") + return FALSE + var_edited = TRUE + vars[var_name] = var_value + + . = TRUE + +/datum/proc/vv_get_var(var_name) + switch(var_name) + if("attack_log", "debug_log") + return debug_variable(var_name, vars[var_name], 0, src, sanitize = FALSE) + if("vars") + return debug_variable(var_name, list(), 0, src) + return debug_variable(var_name, vars[var_name], 0, src) + +/client/vv_get_var(var_name) + switch(var_name) + if("vars") + return debug_variable(var_name, list(), 0, src) + return debug_variable(var_name, vars[var_name], 0, src) + +/datum/proc/can_vv_delete() + return TRUE + +//please call . = ..() first and append to the result, that way parent items are always at the top and child items are further down +//add seperaters by doing . += "---" +/datum/proc/vv_get_dropdown() + . = list() + . += "---" + .["Call Proc"] = "?_src_=vars;proc_call=[UID()]" + .["Mark Object"] = "?_src_=vars;mark_object=[UID()]" + .["Jump to Object"] = "?_src_=vars;jump_to=[UID()]" + .["Delete"] = "?_src_=vars;delete=[UID()]" + .["Modify Traits"] = "?_src_=vars;traitmod=[UID()]" + . += "---" + +/client/vv_get_dropdown() + . = list() + . += "---" + .["Call Proc"] = "?_src_=vars;proc_call=[UID()]" + .["Mark Object"] = "?_src_=vars;mark_object=[UID()]" + .["Delete"] = "?_src_=vars;delete=[UID()]" + .["Modify Traits"] = "?_src_=vars;traitmod=[UID()]" + . += "---" + +/client/proc/debug_variables(datum/D in world) + set category = "Debug" + set name = "View Variables" + + var/static/cookieoffset = rand(1, 9999) //to force cookies to reset after the round. + + if(!is_admin(usr)) + to_chat(usr, "You need to be an administrator to access this.") + return + + if(!D) + return + + + var/islist = islist(D) + var/isclient = isclient(D) + if(!islist && !isclient && !istype(D)) + return + + var/title = "" + var/icon/sprite + var/hash + var/refid + + if(!islist) + refid = "[D.UID()]" + else + refid = "\ref[D]" + + var/type = /list + if(!islist) + type = D.type + + if(isatom(D)) + var/atom/A = D + if(A.icon && A.icon_state) + sprite = new /icon(A.icon, A.icon_state) + hash = md5(A.icon) + hash = md5(hash + A.icon_state) + usr << browse_rsc(sprite, "vv[hash].png") + + + var/sprite_text + if(sprite) + sprite_text = "" + + + var/list/atomsnowflake = list() + if(isatom(D)) + var/atom/A = D + if(isliving(A)) + var/mob/living/L = A + atomsnowflake += "[L]" + if(L.dir) + atomsnowflake += "
    << [dir2text(L.dir)] >>" + atomsnowflake += {" +
    [L.ckey ? L.ckey : "No ckey"] / [L.real_name ? L.real_name : "No real name"] +
    + BRUTE:[L.getBruteLoss()] + FIRE:[L.getFireLoss()] + TOXIN:[L.getToxLoss()] + OXY:[L.getOxyLoss()] + CLONE:[L.getCloneLoss()] + BRAIN:[L.getBrainLoss()] + STAMINA:[L.getStaminaLoss()] + + "} + else + atomsnowflake += "[A]" + if(A.dir) + atomsnowflake += "
    << [dir2text(A.dir)] >>" + else + atomsnowflake += "[D]" + + + var/formatted_type = "[type]" + if(length(formatted_type) > 25) + var/middle_point = length(formatted_type) / 2 + var/splitpoint = findtext(formatted_type, "/", middle_point) + if(splitpoint) + formatted_type = "[copytext(formatted_type, 1, splitpoint)]
    [copytext(formatted_type, splitpoint)]" + else + formatted_type = "Type too long" //No suitable splitpoint (/) found. + + + var/marked + if(holder.marked_datum && holder.marked_datum == D) + marked = "
    Marked Object" + + + var/varedited_line = "" + if(isatom(D)) + var/atom/A = D + if(A.admin_spawned) + varedited_line += "
    Admin Spawned" + + + if(!islist && D.var_edited) + varedited_line += "
    Var Edited" + + + var/dropdownoptions = list() + if(islist) + dropdownoptions = list( + "---", + "Add Item" = "?_src_=vars;listadd=[refid]", + "Remove Nulls" = "?_src_=vars;listnulls=[refid]", + "Remove Dupes" = "?_src_=vars;listdupes=[refid]", + "Set len" = "?_src_=vars;listlen=[refid]", + "Shuffle" = "?_src_=vars;listshuffle=[refid]" + ) + else + dropdownoptions = D.vv_get_dropdown() + + + var/list/dropdownoptions_html = list() + for(var/name in dropdownoptions) + var/link = dropdownoptions[name] + if(link) + dropdownoptions_html += "" + else + dropdownoptions_html += "" + + + var/list/names = list() + if(!islist) + for(var/V in D.vars) + names += V + + + sleep(1) // Without a sleep here, VV sometimes disconnects clients + + + var/list/variable_html = list() + if(islist) + var/list/L = D + for(var/i in 1 to L.len) + var/key = L[i] + var/value + if(IS_NORMAL_LIST(L) && !isnum(key)) + value = L[key] + variable_html += debug_variable(i, value, 0, D) + else + names = sortList(names) + for(var/V in names) + if(D.can_vv_get(V)) + variable_html += D.vv_get_var(V) + + var/html = {" + + + [title] + + + + +
    + + + + + +
    + + + + +
    + [sprite_text] +
    + [atomsnowflake.Join()] +
    +
    +
    + [formatted_type] + [marked] + [varedited_line] +
    +
    +
    + Refresh +
    + +
    +
    +
    +
    +
    + + E - Edit, tries to determine the variable type by itself.
    + C - Change, asks you for the var type first.
    + M - Mass modify: changes this variable for all objects of this type.
    +
    +
    + + + + + +
    +
    + Search: +
    +
    + +
    +
    +
      + [variable_html.Join()] +
    + + + + "} + + usr << browse(html, "window=variables[refid];size=475x650") + +#define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing ) +/proc/debug_variable(name, value, level, var/datum/DA = null, sanitize = TRUE) + var/header + if(DA) + if(islist(DA)) + var/index = name + if(value) + name = DA[name] // name is really the index until this line + else + value = DA[name] + header = "
  • (E) (C) (-) " + else + header = "
  • (E) (C) (M) " + else + header = "
  • " + + var/item + if(isnull(value)) + item = "[VV_HTML_ENCODE(name)] = null" + + else if(istext(value)) + item = "[VV_HTML_ENCODE(name)] = \"[VV_HTML_ENCODE(value)]\"" + + else if(isicon(value)) + #ifdef VARSICON + item = "[name] = /icon ([value]) [bicon(value, use_class=0)]" + #else + item = "[name] = /icon ([value])" + #endif + + else if(istype(value, /image)) + var/image/I = value + #ifdef VARSICON + item = "[name] \ref[value] = /image ([value]) [bicon(value, use_class=0)]" + #else + item = "[name] \ref[value] = /image ([value])" + #endif + + else if(isfile(value)) + item = "[VV_HTML_ENCODE(name)] = '[value]'" + + else if(istype(value, /datum)) + var/datum/D = value + item = "[VV_HTML_ENCODE(name)] \ref[value] = [D.type]" + + else if(istype(value, /client)) + var/client/C = value + item = "[VV_HTML_ENCODE(name)] \ref[value] = [C] [C.type]" +// + else if(islist(value)) + var/list/L = value + var/list/items = list() + + if(L.len > 0 && !(name == "underlays" || name == "overlays" || name == "vars" || L.len > (IS_NORMAL_LIST(L) ? 250 : 300))) + for(var/i in 1 to L.len) + var/key = L[i] + var/val + if(IS_NORMAL_LIST(L) && !isnum(key)) + val = L[key] + if(!val) + val = key + key = i + + items += debug_variable(key, val, level + 1, sanitize = sanitize) + + item = "[VV_HTML_ENCODE(name)] = /list ([L.len])
      [items.Join()]
    " + + else + item = "[VV_HTML_ENCODE(name)] = /list ([L.len])" + + else + item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(value)]" + + return "[header][item]
  • " + +#undef VV_HTML_ENCODE + +/client/proc/view_var_Topic(href, href_list, hsrc) + //This should all be moved over to datum/admins/Topic() or something ~Carn + if(!check_rights(R_ADMIN|R_MOD)) + return + + if(view_var_Topic_list(href, href_list, hsrc)) // done because you can't use UIDs with lists and I don't want to snowflake into the below check to supress warnings + return + + // Correct and warn about any VV topic links that aren't using UIDs + for(var/paramname in href_list) + if(findtext(href_list[paramname], "]_")) + continue // Contains UID-specific formatting, skip it + var/datum/D = locate(href_list[paramname]) + if(!D) + continue + var/datuminfo = "[D]" + if(istype(D)) + datuminfo = datum_info_line(D) + href_list[paramname] = D.UID() + else if(isclient(D)) + var/client/C = D + href_list[paramname] = C.UID() + log_runtime(EXCEPTION("Found \\ref-based '[paramname]' param in VV topic for [datuminfo], should be UID: [href]")) + + if(href_list["Vars"]) + debug_variables(locateUID(href_list["Vars"])) + + //~CARN: for renaming mobs (updates their name, real_name, mind.name, their ID/PDA and datacore records). + else if(href_list["rename"]) + if(!check_rights(R_ADMIN)) return + + var/mob/M = locateUID(href_list["rename"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + var/new_name = reject_bad_name(sanitize(copytext(input(usr, "What would you like to name this mob?", "Input a name", M.real_name) as text|null, 1, MAX_NAME_LEN)), allow_numbers = TRUE) + if( !new_name || !M ) return + + message_admins("Admin [key_name_admin(usr)] renamed [key_name_admin(M)] to [new_name].") + M.rename_character(M.real_name, new_name) + href_list["datumrefresh"] = href_list["rename"] + + else if(href_list["varnameedit"] && href_list["datumedit"]) + if(!check_rights(R_VAREDIT)) return + + var/D = locateUID(href_list["datumedit"]) + if(!istype(D,/datum) && !istype(D,/client)) + to_chat(usr, "This can only be used on instances of types /client or /datum") + return + + modify_variables(D, href_list["varnameedit"], 1) + + else if(href_list["togbit"]) + if(!check_rights(R_VAREDIT)) return + + var/atom/D = locateUID(href_list["subject"]) + if(!istype(D,/datum) && !istype(D,/client)) + to_chat(usr, "This can only be used on instances of types /client or /datum") + return + if(!(href_list["var"] in D.vars)) + to_chat(usr, "Unable to find variable specified.") + return + var/value = D.vars[href_list["var"]] + value ^= 1 << text2num(href_list["togbit"]) + + D.vars[href_list["var"]] = value + + else if(href_list["varnamechange"] && href_list["datumchange"]) + if(!check_rights(R_VAREDIT)) return + + var/D = locateUID(href_list["datumchange"]) + if(!istype(D,/datum) && !istype(D,/client)) + to_chat(usr, "This can only be used on instances of types /client or /datum") + return + + modify_variables(D, href_list["varnamechange"], 0) + + else if(href_list["varnamemass"] && href_list["datummass"]) + if(!check_rights(R_VAREDIT)) return + + var/atom/A = locateUID(href_list["datummass"]) + if(!istype(A)) + to_chat(usr, "This can only be used on instances of type /atom") + return + + cmd_mass_modify_object_variables(A, href_list["varnamemass"]) + + + else if(href_list["mob_player_panel"]) + if(!check_rights(R_ADMIN|R_MOD)) return + + var/mob/M = locateUID(href_list["mob_player_panel"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.holder.show_player_panel(M) + href_list["datumrefresh"] = href_list["mob_player_panel"] + + else if(href_list["give_spell"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + var/mob/M = locateUID(href_list["give_spell"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.give_spell(M) + href_list["datumrefresh"] = href_list["give_spell"] + + else if(href_list["givemartialart"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + var/mob/living/carbon/C = locateUID(href_list["givemartialart"]) + if(!istype(C)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + var/list/artpaths = subtypesof(/datum/martial_art) + var/list/artnames = list() + for(var/i in artpaths) + var/datum/martial_art/M = i + artnames[initial(M.name)] = M + + var/result = input(usr, "Choose the martial art to teach", "JUDO CHOP") as null|anything in artnames + if(!usr) + return + if(QDELETED(C)) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(result) + var/chosenart = artnames[result] + var/datum/martial_art/MA = new chosenart + MA.teach(C) + + href_list["datumrefresh"] = href_list["givemartialart"] + + else if(href_list["give_disease"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + var/mob/M = locateUID(href_list["give_disease"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.give_disease(M) + href_list["datumrefresh"] = href_list["give_spell"] + + else if(href_list["godmode"]) + if(!check_rights(R_REJUVINATE)) return + + var/mob/M = locateUID(href_list["godmode"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.cmd_admin_godmode(M) + href_list["datumrefresh"] = href_list["godmode"] + + else if(href_list["gib"]) + if(!check_rights(R_ADMIN|R_EVENT)) return + + var/mob/M = locateUID(href_list["gib"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.cmd_admin_gib(M) + + else if(href_list["build_mode"]) + if(!check_rights(R_BUILDMODE)) return + + var/mob/M = locateUID(href_list["build_mode"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + togglebuildmode(M) + href_list["datumrefresh"] = href_list["build_mode"] + + else if(href_list["drop_everything"]) + if(!check_rights(R_DEBUG|R_ADMIN)) return + + var/mob/M = locateUID(href_list["drop_everything"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + if(usr.client) + usr.client.cmd_admin_drop_everything(M) + + else if(href_list["direct_control"]) + if(!check_rights(R_DEBUG|R_ADMIN)) return + + var/mob/M = locateUID(href_list["direct_control"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + if(usr.client) + usr.client.cmd_assume_direct_control(M) + + else if(href_list["make_skeleton"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + var/mob/living/carbon/human/H = locateUID(href_list["make_skeleton"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + + var/confirm = alert("Are you sure you want to turn this mob into a skeleton?","Confirm Skeleton Transformation","Yes","No") + if(confirm != "Yes") + return + + H.makeSkeleton() + message_admins("[key_name(usr)] has turned [key_name(H)] into a skeleton") + log_admin("[key_name_admin(usr)] has turned [key_name_admin(H)] into a skeleton") + href_list["datumrefresh"] = href_list["make_skeleton"] + + else if(href_list["offer_control"]) + if(!check_rights(R_ADMIN)) return + + var/mob/M = locateUID(href_list["offer_control"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + offer_control(M) + + else if(href_list["delete"]) + if(!check_rights(R_DEBUG, 0)) + return + + var/datum/D = locateUID(href_list["delete"]) + if(!D) + to_chat(usr, "Unable to locate item!") + admin_delete(D) + href_list["datumrefresh"] = href_list["delete"] + + else if(href_list["delall"]) + if(!check_rights(R_DEBUG|R_SERVER)) return + + var/obj/O = locateUID(href_list["delall"]) + if(!isobj(O)) + to_chat(usr, "This can only be used on instances of type /obj") + return + + var/action_type = alert("Strict type ([O.type]) or type and all subtypes?",,"Strict type","Type and subtypes","Cancel") + if(action_type == "Cancel" || !action_type) + return + + if(alert("Are you really sure you want to delete all objects of type [O.type]?",,"Yes","No") != "Yes") + return + + if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") + return + + var/O_type = O.type + switch(action_type) + if("Strict type") + var/i = 0 + for(var/obj/Obj in world) + if(Obj.type == O_type) + i++ + qdel(Obj) + if(!i) + to_chat(usr, "No objects of this type exist") + return + log_admin("[key_name(usr)] deleted all objects of type [O_type] ([i] objects deleted)") + message_admins("[key_name_admin(usr)] deleted all objects of type [O_type] ([i] objects deleted)") + if("Type and subtypes") + var/i = 0 + for(var/obj/Obj in world) + if(istype(Obj,O_type)) + i++ + qdel(Obj) + if(!i) + to_chat(usr, "No objects of this type exist") + return + log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted)") + message_admins("[key_name_admin(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted)") + + else if(href_list["makespeedy"]) + if(!check_rights(R_DEBUG|R_ADMIN)) + return + var/obj/A = locateUID(href_list["makespeedy"]) + if(!istype(A)) + return + A.var_edited = TRUE + A.makeSpeedProcess() + log_admin("[key_name(usr)] has made [A] speed process") + message_admins("[key_name(usr)] has made [A] speed process") + return TRUE + + else if(href_list["makenormalspeed"]) + if(!check_rights(R_DEBUG|R_ADMIN)) + return + var/obj/A = locateUID(href_list["makenormalspeed"]) + if(!istype(A)) + return + A.var_edited = TRUE + A.makeNormalProcess() + log_admin("[key_name(usr)] has made [A] process normally") + message_admins("[key_name(usr)] has made [A] process normally") + return TRUE + + else if(href_list["addreagent"]) /* Made on /TG/, credit to them. */ + if(!check_rights(R_DEBUG|R_ADMIN)) return + + var/atom/A = locateUID(href_list["addreagent"]) + + if(!A.reagents) + var/amount = input(usr, "Specify the reagent size of [A]", "Set Reagent Size", 50) as num + if(amount) + A.create_reagents(amount) + + if(A.reagents) + var/chosen_id + var/list/reagent_options = sortAssoc(GLOB.chemical_reagents_list) + switch(alert(usr, "Choose a method.", "Add Reagents", "Enter ID", "Choose ID")) + if("Enter ID") + var/valid_id + while(!valid_id) + chosen_id = stripped_input(usr, "Enter the ID of the reagent you want to add.") + if(!chosen_id) //Get me out of here! + break + for(var/ID in reagent_options) + if(ID == chosen_id) + valid_id = 1 + if(!valid_id) + to_chat(usr, "A reagent with that ID doesn't exist!") + if("Choose ID") + chosen_id = input(usr, "Choose a reagent to add.", "Choose a reagent.") as null|anything in reagent_options + if(chosen_id) + var/amount = input(usr, "Choose the amount to add.", "Choose the amount.", A.reagents.maximum_volume) as num + if(amount) + A.reagents.add_reagent(chosen_id, amount) + log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to \the [A]") + message_admins("[key_name(usr)] has added [amount] units of [chosen_id] to \the [A]") + + else if(href_list["explode"]) + if(!check_rights(R_DEBUG|R_EVENT)) return + + var/atom/A = locateUID(href_list["explode"]) + if(!isobj(A) && !ismob(A) && !isturf(A)) + to_chat(usr, "This can only be done to instances of type /obj, /mob and /turf") + return + + src.cmd_admin_explosion(A) + href_list["datumrefresh"] = href_list["explode"] + + else if(href_list["emp"]) + if(!check_rights(R_DEBUG|R_EVENT)) return + + var/atom/A = locateUID(href_list["emp"]) + if(!isobj(A) && !ismob(A) && !isturf(A)) + to_chat(usr, "This can only be done to instances of type /obj, /mob and /turf") + return + + src.cmd_admin_emp(A) + href_list["datumrefresh"] = href_list["emp"] + + else if(href_list["mark_object"]) + if(!check_rights(0)) return + + var/datum/D = locateUID(href_list["mark_object"]) + if(!istype(D)) + to_chat(usr, "This can only be done to instances of type /datum") + return + + src.holder.marked_datum = D + href_list["datumrefresh"] = href_list["mark_object"] + + else if(href_list["proc_call"]) + if(!check_rights(R_PROCCALL)) + return + + var/T = locateUID(href_list["proc_call"]) + + if(T) + callproc_datum(T) + + else if(href_list["jump_to"]) + if(!check_rights(R_ADMIN)) + return + + var/atom/A = locateUID(href_list["jump_to"]) + var/turf/T = get_turf(A) + if(T) + usr.client.jumptoturf(T) + href_list["datumrefresh"] = href_list["jump_to"] + + + else if(href_list["rotatedatum"]) + if(!check_rights(R_DEBUG|R_ADMIN)) return + + var/atom/A = locateUID(href_list["rotatedatum"]) + if(!istype(A)) + to_chat(usr, "This can only be done to instances of type /atom") + return + + switch(href_list["rotatedir"]) + if("right") A.dir = turn(A.dir, -45) + if("left") A.dir = turn(A.dir, 45) + + message_admins("[key_name_admin(usr)] has rotated \the [A]") + log_admin("[key_name(usr)] has rotated \the [A]") + href_list["datumrefresh"] = href_list["rotatedatum"] + + else if(href_list["makemonkey"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makemonkey"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("monkeyone"=href_list["makemonkey"])) + + else if(href_list["makerobot"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makerobot"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makerobot"=href_list["makerobot"])) + + else if(href_list["makealien"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makealien"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makealien"=href_list["makealien"])) + + else if(href_list["makeslime"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makeslime"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makeslime"=href_list["makeslime"])) + + else if(href_list["makesuper"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makesuper"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makesuper"=href_list["makesuper"])) + + else if(href_list["makeai"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makeai"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makeai"=href_list["makeai"])) + + else if(href_list["setspecies"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["setspecies"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + var/new_species = input("Please choose a new species.","Species",null) as null|anything in GLOB.all_species + + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + + var/datum/species/S = GLOB.all_species[new_species] + if(H.set_species(S.type)) + to_chat(usr, "Set species of [H] to [H.dna.species].") + H.regenerate_icons() + message_admins("[key_name_admin(usr)] has changed the species of [key_name_admin(H)] to [new_species]") + log_admin("[key_name(usr)] has changed the species of [key_name(H)] to [new_species]") + else + to_chat(usr, "Failed! Something went wrong.") + + else if(href_list["addlanguage"]) + if(!check_rights(R_SPAWN)) return + + var/mob/H = locateUID(href_list["addlanguage"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob") + return + + var/new_language = input("Please choose a language to add.","Language",null) as null|anything in GLOB.all_languages + + if(!new_language) + return + + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(H.add_language(new_language)) + to_chat(usr, "Added [new_language] to [H].") + message_admins("[key_name_admin(usr)] has given [key_name_admin(H)] the language [new_language]") + log_admin("[key_name(usr)] has given [key_name(H)] the language [new_language]") + else + to_chat(usr, "Mob already knows that language.") + + else if(href_list["remlanguage"]) + if(!check_rights(R_SPAWN)) return + + var/mob/H = locateUID(href_list["remlanguage"]) + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob") + return + + if(!H.languages.len) + to_chat(usr, "This mob knows no languages.") + return + + var/datum/language/rem_language = input("Please choose a language to remove.","Language",null) as null|anything in H.languages + + if(!rem_language) + return + + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(H.remove_language(rem_language.name)) + to_chat(usr, "Removed [rem_language] from [H].") + message_admins("[key_name_admin(usr)] has removed language [rem_language] from [key_name_admin(H)]") + log_admin("[key_name(usr)] has removed language [rem_language] from [key_name(H)]") + else + to_chat(usr, "Mob doesn't know that language.") + + else if(href_list["addverb"]) + if(!check_rights(R_DEBUG)) return + + var/mob/living/H = locateUID(href_list["addverb"]) + + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living") + return + var/list/possibleverbs = list() + possibleverbs += "Cancel" // One for the top... + possibleverbs += typesof(/mob/proc,/mob/verb,/mob/living/proc,/mob/living/verb) + switch(H.type) + if(/mob/living/carbon/human) + possibleverbs += typesof(/mob/living/carbon/proc,/mob/living/carbon/verb,/mob/living/carbon/human/verb,/mob/living/carbon/human/proc) + if(/mob/living/silicon/robot) + possibleverbs += typesof(/mob/living/silicon/proc,/mob/living/silicon/robot/proc,/mob/living/silicon/robot/verb) + if(/mob/living/silicon/ai) + possibleverbs += typesof(/mob/living/silicon/proc,/mob/living/silicon/ai/proc,/mob/living/silicon/ai/verb) + possibleverbs -= H.verbs + possibleverbs += "Cancel" // ...And one for the bottom + + var/verb = input("Select a verb!", "Verbs",null) as anything in possibleverbs + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + if(!verb || verb == "Cancel") + return + else + H.verbs += verb + message_admins("[key_name_admin(usr)] has given [key_name_admin(H)] the verb [verb]") + log_admin("[key_name(usr)] has given [key_name(H)] the verb [verb]") + + else if(href_list["remverb"]) + if(!check_rights(R_DEBUG)) return + + var/mob/H = locateUID(href_list["remverb"]) + + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob") + return + var/verb = input("Please choose a verb to remove.","Verbs",null) as null|anything in H.verbs + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + if(!verb) + return + else + H.verbs -= verb + message_admins("[key_name_admin(usr)] has removed verb [verb] from [key_name_admin(H)]") + log_admin("[key_name(usr)] has removed verb [verb] from [key_name(H)]") + + else if(href_list["addorgan"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/M = locateUID(href_list["addorgan"]) + if(!istype(M)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + var/new_organ = input("Please choose an organ to add.","Organ",null) as null|anything in subtypesof(/obj/item/organ)-/obj/item/organ + if(!new_organ) return + + if(!M) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(locateUID(new_organ) in M.internal_organs) + to_chat(usr, "Mob already has that organ.") + return + var/obj/item/organ/internal/organ = new new_organ + organ.insert(M) + message_admins("[key_name_admin(usr)] has given [key_name_admin(M)] the organ [new_organ]") + log_admin("[key_name(usr)] has given [key_name(M)] the organ [new_organ]") + + else if(href_list["remorgan"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/M = locateUID(href_list["remorgan"]) + if(!istype(M)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + var/obj/item/organ/internal/rem_organ = input("Please choose an organ to remove.","Organ",null) as null|anything in M.internal_organs + + if(!M) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(!(rem_organ in M.internal_organs)) + to_chat(usr, "Mob does not have that organ.") + return + + to_chat(usr, "Removed [rem_organ] from [M].") + rem_organ.remove(M) + message_admins("[key_name_admin(usr)] has removed the organ [rem_organ] from [key_name_admin(M)]") + log_admin("[key_name(usr)] has removed the organ [rem_organ] from [key_name(M)]") + qdel(rem_organ) + + else if(href_list["fix_nano"]) + if(!check_rights(R_DEBUG)) return + + var/mob/H = locateUID(href_list["fix_nano"]) + + if(!istype(H) || !H.client) + to_chat(usr, "This can only be done on mobs with clients") + return + + H.client.reload_nanoui_resources() + + to_chat(usr, "Resource files sent") + to_chat(H, "Your NanoUI Resource files have been refreshed") + + log_admin("[key_name(usr)] resent the NanoUI resource files to [key_name(H)]") + + else if(href_list["regenerateicons"]) + if(!check_rights(0)) return + + var/mob/M = locateUID(href_list["regenerateicons"]) + if(!ismob(M)) + to_chat(usr, "This can only be done to instances of type /mob") + return + M.regenerate_icons() + + else if(href_list["adjustDamage"] && href_list["mobToDamage"]) + if(!check_rights(R_DEBUG|R_ADMIN|R_EVENT)) return + + var/mob/living/L = locateUID(href_list["mobToDamage"]) + if(!istype(L)) return + + var/Text = href_list["adjustDamage"] + + var/amount = input("Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0) as num + + if(!L) + to_chat(usr, "Mob doesn't exist anymore") + return + + switch(Text) + if("brute") + if(ishuman(L)) + var/mob/living/carbon/human/H = L + H.adjustBruteLoss(amount, robotic = TRUE) + else + L.adjustBruteLoss(amount) + if("fire") + if(ishuman(L)) + var/mob/living/carbon/human/H = L + H.adjustFireLoss(amount, robotic = TRUE) + else + L.adjustFireLoss(amount) + if("toxin") + L.adjustToxLoss(amount) + if("oxygen") + L.adjustOxyLoss(amount) + if("brain") + L.adjustBrainLoss(amount) + if("clone") + L.adjustCloneLoss(amount) + if("stamina") + L.adjustStaminaLoss(amount) + else + to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]") + return + + if(amount != 0) + log_admin("[key_name(usr)] dealt [amount] amount of [Text] damage to [L]") + message_admins("[key_name_admin(usr)] dealt [amount] amount of [Text] damage to [L]") + href_list["datumrefresh"] = href_list["mobToDamage"] + + else if(href_list["traitmod"]) + if(!check_rights(NONE)) + return + var/datum/A = locateUID(href_list["traitmod"]) + if(!istype(A)) + return + holder.modify_traits(A) + + if(href_list["datumrefresh"]) + var/datum/DAT = locateUID(href_list["datumrefresh"]) + if(!istype(DAT, /datum) && !isclient(DAT)) + return + src.debug_variables(DAT) + + if(href_list["copyoutfit"]) + if(!check_rights(R_EVENT)) + return + var/mob/living/carbon/human/H = locateUID(href_list["copyoutfit"]) + if(istype(H)) + H.copy_outfit() + +/client/proc/view_var_Topic_list(href, href_list, hsrc) + if(href_list["VarsList"]) + debug_variables(locate(href_list["VarsList"])) + return TRUE + + if(href_list["listedit"] && href_list["index"]) + var/index = text2num(href_list["index"]) + if(!index) + return TRUE + + var/list/L = locate(href_list["listedit"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + mod_list(L, null, "list", "contents", index, autodetect_class = TRUE) + return TRUE + + if(href_list["listchange"] && href_list["index"]) + var/index = text2num(href_list["index"]) + if(!index) + return TRUE + + var/list/L = locate(href_list["listchange"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + mod_list(L, null, "list", "contents", index, autodetect_class = FALSE) + return TRUE + + if(href_list["listremove"] && href_list["index"]) + var/index = text2num(href_list["index"]) + if(!index) + return TRUE + + var/list/L = locate(href_list["listremove"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + var/variable = L[index] + var/prompt = alert("Do you want to remove item number [index] from list?", "Confirm", "Yes", "No") + if(prompt != "Yes") + return + L.Cut(index, index+1) + log_world("### ListVarEdit by [src]: /list's contents: REMOVED=[html_encode("[variable]")]") + log_admin("[key_name(src)] modified list's contents: REMOVED=[variable]") + message_admins("[key_name_admin(src)] modified list's contents: REMOVED=[variable]") + return TRUE + + if(href_list["listadd"]) + var/list/L = locate(href_list["listadd"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return TRUE + + mod_list_add(L, null, "list", "contents") + return TRUE + + if(href_list["listdupes"]) + var/list/L = locate(href_list["listdupes"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return TRUE + + uniqueList_inplace(L) + log_world("### ListVarEdit by [src]: /list contents: CLEAR DUPES") + log_admin("[key_name(src)] modified list's contents: CLEAR DUPES") + message_admins("[key_name_admin(src)] modified list's contents: CLEAR DUPES") + return TRUE + + if(href_list["listnulls"]) + var/list/L = locate(href_list["listnulls"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return TRUE + + listclearnulls(L) + log_world("### ListVarEdit by [src]: /list contents: CLEAR NULLS") + log_admin("[key_name(src)] modified list's contents: CLEAR NULLS") + message_admins("[key_name_admin(src)] modified list's contents: CLEAR NULLS") + return TRUE + + if(href_list["listlen"]) + var/list/L = locate(href_list["listlen"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return TRUE + var/value = vv_get_value(VV_NUM) + if(value["class"] != VV_NUM) + return TRUE + + L.len = value["value"] + log_world("### ListVarEdit by [src]: /list len: [L.len]") + log_admin("[key_name(src)] modified list's len: [L.len]") + message_admins("[key_name_admin(src)] modified list's len: [L.len]") + return TRUE + + if(href_list["listshuffle"]) + var/list/L = locate(href_list["listshuffle"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return TRUE + + shuffle_inplace(L) + log_world("### ListVarEdit by [src]: /list contents: SHUFFLE") + log_admin("[key_name(src)] modified list's contents: SHUFFLE") + message_admins("[key_name_admin(src)] modified list's contents: SHUFFLE") + return TRUE + + if(href_list["listrefresh"]) + debug_variables(locate(href_list["listrefresh"])) + return TRUE diff --git a/code/datums/diseases/advance/presets.dm b/code/datums/diseases/advance/presets.dm index a641db2383f..c1df095f9c2 100644 --- a/code/datums/diseases/advance/presets.dm +++ b/code/datums/diseases/advance/presets.dm @@ -48,4 +48,4 @@ if(!D) name = "Reality Enhancer" symptoms = list(new/datum/symptom/sensory_restoration) - ..(process, D, copy) \ No newline at end of file + ..(process, D, copy) diff --git a/code/datums/diseases/advance/symptoms/beard.dm b/code/datums/diseases/advance/symptoms/beard.dm index 2ce411367f8..75bf2267ff9 100644 --- a/code/datums/diseases/advance/symptoms/beard.dm +++ b/code/datums/diseases/advance/symptoms/beard.dm @@ -47,4 +47,4 @@ BONUS if(!(head_organ.f_style == "Dwarf Beard") && !(head_organ.f_style == "Very Long Beard")) head_organ.f_style = pick("Dwarf Beard", "Very Long Beard") H.update_fhair() - return \ No newline at end of file + return diff --git a/code/datums/diseases/advance/symptoms/choking.dm b/code/datums/diseases/advance/symptoms/choking.dm index 62e31105b92..f0a3eaa9ce0 100644 --- a/code/datums/diseases/advance/symptoms/choking.dm +++ b/code/datums/diseases/advance/symptoms/choking.dm @@ -50,4 +50,4 @@ Bonus /datum/symptom/choking/proc/Choke(mob/living/M, datum/disease/advance/A) var/get_damage = sqrtor0(21+A.totalStageSpeed()*0.5)+sqrtor0(16+A.totalStealth()*5) M.adjustOxyLoss(get_damage) - return 1 \ No newline at end of file + return 1 diff --git a/code/datums/diseases/advance/symptoms/cough.dm b/code/datums/diseases/advance/symptoms/cough.dm index 44f57ce0fac..5f495718fff 100644 --- a/code/datums/diseases/advance/symptoms/cough.dm +++ b/code/datums/diseases/advance/symptoms/cough.dm @@ -37,4 +37,4 @@ BONUS var/obj/item/I = M.get_active_hand() if(I && I.w_class == 1) M.drop_item() - return \ No newline at end of file + return diff --git a/code/datums/diseases/advance/symptoms/dizzy.dm b/code/datums/diseases/advance/symptoms/dizzy.dm index c594298b11c..920d6d15b49 100644 --- a/code/datums/diseases/advance/symptoms/dizzy.dm +++ b/code/datums/diseases/advance/symptoms/dizzy.dm @@ -35,4 +35,4 @@ Bonus else to_chat(M, "A wave of dizziness washes over you!") M.Dizzy(5) - return \ No newline at end of file + return diff --git a/code/datums/diseases/advance/symptoms/fever.dm b/code/datums/diseases/advance/symptoms/fever.dm index b19d821b75f..32c051d5a26 100644 --- a/code/datums/diseases/advance/symptoms/fever.dm +++ b/code/datums/diseases/advance/symptoms/fever.dm @@ -38,4 +38,4 @@ Bonus /datum/symptom/fever/proc/Heat(mob/living/M, datum/disease/advance/A) var/get_heat = (sqrtor0(21+A.totalTransmittable()*2))+(sqrtor0(20+A.totalStageSpeed()*3)) M.bodytemperature = min(M.bodytemperature + (get_heat * A.stage), BODYTEMP_HEAT_DAMAGE_LIMIT - 1) - return 1 \ No newline at end of file + return 1 diff --git a/code/datums/diseases/advance/symptoms/flesh_eating.dm b/code/datums/diseases/advance/symptoms/flesh_eating.dm index 9cfdf8a9d34..e7d68bebb92 100644 --- a/code/datums/diseases/advance/symptoms/flesh_eating.dm +++ b/code/datums/diseases/advance/symptoms/flesh_eating.dm @@ -40,4 +40,4 @@ Bonus /datum/symptom/flesh_eating/proc/Flesheat(mob/living/M, datum/disease/advance/A) var/get_damage = ((sqrtor0(16-A.totalStealth()))*5) M.adjustBruteLoss(get_damage) - return 1 \ No newline at end of file + return 1 diff --git a/code/datums/diseases/advance/symptoms/headache.dm b/code/datums/diseases/advance/symptoms/headache.dm index 526ab93836f..d1efc35fc98 100644 --- a/code/datums/diseases/advance/symptoms/headache.dm +++ b/code/datums/diseases/advance/symptoms/headache.dm @@ -31,4 +31,4 @@ BONUS if(prob(SYMPTOM_ACTIVATION_PROB)) var/mob/living/M = A.affected_mob to_chat(M, "[pick("Your head hurts.", "Your head starts pounding.")]") - return \ No newline at end of file + return diff --git a/code/datums/diseases/advance/symptoms/itching.dm b/code/datums/diseases/advance/symptoms/itching.dm index 8563520b09a..f6689c3c464 100644 --- a/code/datums/diseases/advance/symptoms/itching.dm +++ b/code/datums/diseases/advance/symptoms/itching.dm @@ -31,4 +31,4 @@ BONUS if(prob(SYMPTOM_ACTIVATION_PROB)) var/mob/living/M = A.affected_mob to_chat(M, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.") - return \ No newline at end of file + return diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm index b1571aec62f..cf39d91a866 100644 --- a/code/datums/diseases/advance/symptoms/sensory.dm +++ b/code/datums/diseases/advance/symptoms/sensory.dm @@ -62,4 +62,4 @@ Bonus M.reagents.add_reagent("oculine", 20) else if(prob(SYMPTOM_ACTIVATION_PROB * 5)) - to_chat(M, "[pick("Your eyes feel great.","You feel like your eyes can focus more clearly.", "You don't feel the need to blink.","Your ears feel great.","Your healing feels more acute.")]") \ No newline at end of file + to_chat(M, "[pick("Your eyes feel great.","You feel like your eyes can focus more clearly.", "You don't feel the need to blink.","Your ears feel great.","Your healing feels more acute.")]") diff --git a/code/datums/diseases/advance/symptoms/shedding.dm b/code/datums/diseases/advance/symptoms/shedding.dm index e79b87d1d4b..c3a64501759 100644 --- a/code/datums/diseases/advance/symptoms/shedding.dm +++ b/code/datums/diseases/advance/symptoms/shedding.dm @@ -47,4 +47,4 @@ BONUS head_organ.h_style = "Bald" H.update_hair() H.update_fhair() - return \ No newline at end of file + return diff --git a/code/datums/diseases/advance/symptoms/skin.dm b/code/datums/diseases/advance/symptoms/skin.dm index 6916a46994a..aa5a3494584 100644 --- a/code/datums/diseases/advance/symptoms/skin.dm +++ b/code/datums/diseases/advance/symptoms/skin.dm @@ -83,4 +83,4 @@ BONUS else H.visible_message("[H] looks a bit dark...", "Your skin suddenly appears darker...") - return \ No newline at end of file + return diff --git a/code/datums/diseases/advance/symptoms/sneeze.dm b/code/datums/diseases/advance/symptoms/sneeze.dm index 0a31a4acae6..b8af8f92a24 100644 --- a/code/datums/diseases/advance/symptoms/sneeze.dm +++ b/code/datums/diseases/advance/symptoms/sneeze.dm @@ -36,4 +36,4 @@ Bonus else M.emote("sneeze") A.spread(5) - return \ No newline at end of file + return diff --git a/code/datums/diseases/advance/symptoms/vision.dm b/code/datums/diseases/advance/symptoms/vision.dm index aa47b1c7a2a..252eaf16ecc 100644 --- a/code/datums/diseases/advance/symptoms/vision.dm +++ b/code/datums/diseases/advance/symptoms/vision.dm @@ -47,4 +47,4 @@ Bonus M.BecomeNearsighted() if(prob(eyes.damage - 10 + 1)) if(M.BecomeBlind()) - to_chat(M, "You go blind!") \ No newline at end of file + to_chat(M, "You go blind!") diff --git a/code/datums/diseases/advance/symptoms/weight.dm b/code/datums/diseases/advance/symptoms/weight.dm index 98b940f2b83..732e56b69ed 100644 --- a/code/datums/diseases/advance/symptoms/weight.dm +++ b/code/datums/diseases/advance/symptoms/weight.dm @@ -36,4 +36,4 @@ Bonus else to_chat(M, "[pick("So hungry...", "You'd kill someone for a bite of food...", "Hunger cramps seize you...")]") M.overeatduration = max(M.overeatduration - 100, 0) - M.adjust_nutrition(-100) \ No newline at end of file + M.adjust_nutrition(-100) diff --git a/code/datums/diseases/advance/symptoms/youth.dm b/code/datums/diseases/advance/symptoms/youth.dm index 56e3b66fab3..5165f7916f4 100644 --- a/code/datums/diseases/advance/symptoms/youth.dm +++ b/code/datums/diseases/advance/symptoms/youth.dm @@ -52,4 +52,4 @@ BONUS H.age = 21 to_chat(H, "You feel like you can take on the world!") - return \ No newline at end of file + return diff --git a/code/datums/diseases/berserker.dm b/code/datums/diseases/berserker.dm index 3efb4d9e06a..81b3ca8cb72 100644 --- a/code/datums/diseases/berserker.dm +++ b/code/datums/diseases/berserker.dm @@ -52,4 +52,4 @@ M.adjustBruteLoss(damage) else playsound(affected_mob.loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) - affected_mob.visible_message("[affected_mob] fails to hit [M] with [affected_mob.p_their()] thrashing!") \ No newline at end of file + affected_mob.visible_message("[affected_mob] fails to hit [M] with [affected_mob.p_their()] thrashing!") diff --git a/code/datums/diseases/cold.dm b/code/datums/diseases/cold.dm index 975c92b2aef..955ec568c77 100644 --- a/code/datums/diseases/cold.dm +++ b/code/datums/diseases/cold.dm @@ -63,4 +63,4 @@ if(!affected_mob.resistances.Find(/datum/disease/flu)) var/datum/disease/Flu = new /datum/disease/flu(0) affected_mob.ContractDisease(Flu) - cure() \ No newline at end of file + cure() diff --git a/code/datums/diseases/critical.dm b/code/datums/diseases/critical.dm index 5a9a2087b80..e3ebc2cf905 100644 --- a/code/datums/diseases/critical.dm +++ b/code/datums/diseases/critical.dm @@ -133,4 +133,4 @@ affected_mob.emote(pick("twitch", "gasp")) if(prob(5) && ishuman(affected_mob)) var/mob/living/carbon/human/H = affected_mob - H.set_heartattack(TRUE) \ No newline at end of file + H.set_heartattack(TRUE) diff --git a/code/datums/diseases/food_poisoning.dm b/code/datums/diseases/food_poisoning.dm index 4df31ec57d9..a0e3792de0c 100644 --- a/code/datums/diseases/food_poisoning.dm +++ b/code/datums/diseases/food_poisoning.dm @@ -68,4 +68,4 @@ to_chat(affected_mob, "Your stomach lurches painfully!") affected_mob.visible_message("[affected_mob] gags and retches!") affected_mob.Stun(rand(2,4)) - affected_mob.Weaken(rand(2,4)) \ No newline at end of file + affected_mob.Weaken(rand(2,4)) diff --git a/code/datums/diseases/kingstons.dm b/code/datums/diseases/kingstons.dm index c7db27968ef..733ee3cf0e7 100644 --- a/code/datums/diseases/kingstons.dm +++ b/code/datums/diseases/kingstons.dm @@ -93,4 +93,4 @@ twisted.visible_message("[twisted] scratches at thier skin!", \ "You scratch your skin to try not to itch!") twisted.adjustBruteLoss(-5) - twisted.adjustStaminaLoss(5) \ No newline at end of file + twisted.adjustStaminaLoss(5) diff --git a/code/datums/diseases/kuru.dm b/code/datums/diseases/kuru.dm index 5cb04ef5f85..f147bbd1612 100644 --- a/code/datums/diseases/kuru.dm +++ b/code/datums/diseases/kuru.dm @@ -52,4 +52,4 @@ affected_mob.drop_r_hand() affected_mob.Stun(10) affected_mob.Weaken(10) - affected_mob.visible_message("[affected_mob] laughs uncontrollably!") \ No newline at end of file + affected_mob.visible_message("[affected_mob] laughs uncontrollably!") diff --git a/code/datums/diseases/magnitis.dm b/code/datums/diseases/magnitis.dm index 924ee01dd69..dd1d062cbf7 100644 --- a/code/datums/diseases/magnitis.dm +++ b/code/datums/diseases/magnitis.dm @@ -60,4 +60,4 @@ var/iter = rand(1,3) for(i=0,iThere's not enough cable to finish the task.")) - return 0 - else - C.use(4) - playsound(holder, C.usesound, 50, 1) - else if(istype(used_atom, /obj/item/stack)) - var/obj/item/stack/S = used_atom - if(S.amount < 5) - to_chat(user, ("There's not enough material in this stack.")) - return 0 - else - S.use(5) - else if(isitem(used_atom)) - var/obj/item/I = used_atom - if(I.tool_behaviour in CONSTRUCTION_TOOL_BEHAVIOURS) - if(!I.use_tool(holder, user, 0, volume = I.tool_volume)) - return 0 - return 1 - -/datum/construction/proc/check_all_steps(atom/used_atom,mob/user as mob) //check all steps, remove matching one. - for(var/i=1;i<=steps.len;i++) - var/list/L = steps[i]; - if(do_tool_or_atom_check(used_atom, L["key"]) && custom_action(i, used_atom, user)) - steps[i]=null;//stupid byond list from list removal... - listclearnulls(steps); - if(!steps.len) - spawn_result(user) - return 1 - return 0 - - -/datum/construction/proc/spawn_result(mob/user as mob) - if(result) - if(taskpath) - var/datum/job_objective/task = user.mind.findJobTask(taskpath) - if(istype(task)) - task.unit_completed() - - new result(get_turf(holder)) - spawn() - qdel(holder) - return - -/datum/construction/proc/set_desc(index as num) - var/list/step = steps[index] - holder.desc = step["desc"] - return - -/datum/construction/proc/try_consume(mob/user as mob, atom/used_atom, amount) - if(amount > 0) - // CABLES - if(istype(used_atom,/obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/coil=used_atom - if(!coil.use(amount)) - to_chat(user, "You don't have enough cable! You need at least [amount] coils.") - return 0 - // TOOLS - if(isitem(used_atom)) - var/obj/item/I = used_atom - if(I.tool_behaviour in CONSTRUCTION_TOOL_BEHAVIOURS) - if(!I.use(amount)) - return 0 - // STACKS - if(istype(used_atom,/obj/item/stack)) - var/obj/item/stack/stack=used_atom - if(stack.amount < amount) - to_chat(user, "You don't have enough [stack]! You need at least [amount].") - return 0 - stack.use(amount) - return 1 - -/datum/construction/proc/do_tool_or_atom_check(used_atom, thing_to_check) //Checks if an atom is either a required thing; or if it's a required tool - if(istype(used_atom, thing_to_check)) - return TRUE - else if(isitem(used_atom)) - var/obj/item/I = used_atom - if(I.tool_behaviour == thing_to_check) - return TRUE - -/datum/construction/reversible - var/index - -/datum/construction/reversible/New(atom) - ..() - index = steps.len - return - -/datum/construction/reversible/proc/update_index(diff as num, mob/user as mob) - index+=diff - if(index==0) - spawn_result(user) - else - set_desc(index) - return - -/datum/construction/reversible/is_right_key(atom/used_atom) // returns index step - var/list/L = steps[index] - if(do_tool_or_atom_check(used_atom, L["key"])) - return FORWARD //to the first step -> forward - else if(L["backkey"] && do_tool_or_atom_check(used_atom, L["backkey"])) - return BACKWARD //to the last step -> backwards - return 0 - -/datum/construction/reversible/check_step(atom/used_atom,mob/user as mob) - var/diff = is_right_key(used_atom) - if(diff) - if(custom_action(index, diff, used_atom, user)) - update_index(diff, user) - return 1 - return 0 - -/datum/construction/reversible/custom_action(index, diff, used_atom, user) - if(!..(index,used_atom,user)) - return 0 - return 1 - -#define state_next "next" -#define state_prev "prev" - -/datum/construction/reversible2 - var/index - var/base_icon = "durand" - -/datum/construction/reversible2/New(atom) - ..() - index = 1 - return - -/datum/construction/reversible2/proc/update_index(diff as num, mob/user as mob) - index-=diff - if(index==steps.len+1) - spawn_result(user) - else - set_desc(index) - return - -/datum/construction/reversible2/proc/update_icon() - holder.icon_state="[base_icon]_[index]" - -/datum/construction/reversible2/is_right_key(mob/user as mob,atom/used_atom) // returns index step - var/list/state = steps[index] - if(state_next in state) - var/list/step = state[state_next] - if(do_tool_or_atom_check(used_atom, step["key"])) - //if(L["consume"] && !try_consume(used_atom,L["consume"])) - // return 0 - return FORWARD //to the first step -> forward - else if(state_prev in state) - var/list/step = state[state_prev] - if(do_tool_or_atom_check(used_atom, step["key"])) - //if(L["consume"] && !try_consume(used_atom,L["consume"])) - // return 0 - return BACKWARD //to the first step -> forward - return 0 - -/datum/construction/reversible2/check_step(atom/used_atom,mob/user as mob) - var/diff = is_right_key(user,used_atom) - if(diff) - if(custom_action(index, diff, used_atom, user)) - update_index(diff,user) - update_icon() - return 1 - return 0 - -/datum/construction/reversible2/proc/fixText(text,user) - text = replacetext(text,"{USER}","[user]") - text = replacetext(text,"{HOLDER}","[holder]") - return text - -/datum/construction/reversible2/custom_action(index, diff, used_atom, var/mob/user) - if(!..(index,used_atom,user)) - return 0 - - var/list/step = steps[index] - var/list/state = step[diff==FORWARD ? state_next : state_prev] - user.visible_message(fixText(state["vis_msg"],user),fixText(state["self_msg"],user)) - - if("delete" in state) - qdel(used_atom) - else if("spawn" in state) - var/spawntype=state["spawn"] - var/atom/A = new spawntype(holder.loc) - if("amount" in state) - if(istype(A,/obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C=A - C.amount=state["amount"] - if(istype(A,/obj/item/stack)) - var/obj/item/stack/S=A - S.amount=state["amount"] - - return 1 - -/datum/construction/reversible2/action(used_atom,user) - return check_step(used_atom,user) +#define FORWARD -1 +#define BACKWARD 1 +#define CONSTRUCTION_TOOL_BEHAVIOURS list(TOOL_CROWBAR, TOOL_SCREWDRIVER, TOOL_WELDER, TOOL_WRENCH) + +/datum/construction + var/list/steps + var/atom/holder + var/result + var/list/steps_desc + var/taskpath = null // Path of job objective completed. + +/datum/construction/New(atom) + ..() + holder = atom + if(!holder) //don't want this without a holder + spawn + qdel(src) + set_desc(steps.len) + return + +/datum/construction/proc/next_step(mob/user as mob) + steps.len-- + if(!steps.len) + spawn_result(user) + else + set_desc(steps.len) + return + +/datum/construction/proc/action(atom/used_atom,mob/user as mob) + return + +/datum/construction/proc/check_step(atom/used_atom,mob/user as mob) //check last step only + var/valid_step = is_right_key(used_atom) + if(valid_step) + if(custom_action(valid_step, used_atom, user)) + next_step(user) + return 1 + return 0 + +/datum/construction/proc/is_right_key(atom/used_atom) // returns current step num if used_atom is of the right type. + var/list/L = steps[steps.len] + if(do_tool_or_atom_check(used_atom, L["key"])) + return steps.len + return 0 + + +/datum/construction/proc/custom_action(step, used_atom, user) + if(istype(used_atom, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = used_atom + if(C.amount<4) + to_chat(user, ("There's not enough cable to finish the task.")) + return 0 + else + C.use(4) + playsound(holder, C.usesound, 50, 1) + else if(istype(used_atom, /obj/item/stack)) + var/obj/item/stack/S = used_atom + if(S.amount < 5) + to_chat(user, ("There's not enough material in this stack.")) + return 0 + else + S.use(5) + else if(isitem(used_atom)) + var/obj/item/I = used_atom + if(I.tool_behaviour in CONSTRUCTION_TOOL_BEHAVIOURS) + if(!I.use_tool(holder, user, 0, volume = I.tool_volume)) + return 0 + return 1 + +/datum/construction/proc/check_all_steps(atom/used_atom,mob/user as mob) //check all steps, remove matching one. + for(var/i=1;i<=steps.len;i++) + var/list/L = steps[i]; + if(do_tool_or_atom_check(used_atom, L["key"]) && custom_action(i, used_atom, user)) + steps[i]=null;//stupid byond list from list removal... + listclearnulls(steps); + if(!steps.len) + spawn_result(user) + return 1 + return 0 + + +/datum/construction/proc/spawn_result(mob/user as mob) + if(result) + if(taskpath) + var/datum/job_objective/task = user.mind.findJobTask(taskpath) + if(istype(task)) + task.unit_completed() + + new result(get_turf(holder)) + spawn() + qdel(holder) + return + +/datum/construction/proc/set_desc(index as num) + var/list/step = steps[index] + holder.desc = step["desc"] + return + +/datum/construction/proc/try_consume(mob/user as mob, atom/used_atom, amount) + if(amount > 0) + // CABLES + if(istype(used_atom,/obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil=used_atom + if(!coil.use(amount)) + to_chat(user, "You don't have enough cable! You need at least [amount] coils.") + return 0 + // TOOLS + if(isitem(used_atom)) + var/obj/item/I = used_atom + if(I.tool_behaviour in CONSTRUCTION_TOOL_BEHAVIOURS) + if(!I.use(amount)) + return 0 + // STACKS + if(istype(used_atom,/obj/item/stack)) + var/obj/item/stack/stack=used_atom + if(stack.amount < amount) + to_chat(user, "You don't have enough [stack]! You need at least [amount].") + return 0 + stack.use(amount) + return 1 + +/datum/construction/proc/do_tool_or_atom_check(used_atom, thing_to_check) //Checks if an atom is either a required thing; or if it's a required tool + if(istype(used_atom, thing_to_check)) + return TRUE + else if(isitem(used_atom)) + var/obj/item/I = used_atom + if(I.tool_behaviour == thing_to_check) + return TRUE + +/datum/construction/reversible + var/index + +/datum/construction/reversible/New(atom) + ..() + index = steps.len + return + +/datum/construction/reversible/proc/update_index(diff as num, mob/user as mob) + index+=diff + if(index==0) + spawn_result(user) + else + set_desc(index) + return + +/datum/construction/reversible/is_right_key(atom/used_atom) // returns index step + var/list/L = steps[index] + if(do_tool_or_atom_check(used_atom, L["key"])) + return FORWARD //to the first step -> forward + else if(L["backkey"] && do_tool_or_atom_check(used_atom, L["backkey"])) + return BACKWARD //to the last step -> backwards + return 0 + +/datum/construction/reversible/check_step(atom/used_atom,mob/user as mob) + var/diff = is_right_key(used_atom) + if(diff) + if(custom_action(index, diff, used_atom, user)) + update_index(diff, user) + return 1 + return 0 + +/datum/construction/reversible/custom_action(index, diff, used_atom, user) + if(!..(index,used_atom,user)) + return 0 + return 1 + +#define state_next "next" +#define state_prev "prev" + +/datum/construction/reversible2 + var/index + var/base_icon = "durand" + +/datum/construction/reversible2/New(atom) + ..() + index = 1 + return + +/datum/construction/reversible2/proc/update_index(diff as num, mob/user as mob) + index-=diff + if(index==steps.len+1) + spawn_result(user) + else + set_desc(index) + return + +/datum/construction/reversible2/proc/update_icon() + holder.icon_state="[base_icon]_[index]" + +/datum/construction/reversible2/is_right_key(mob/user as mob,atom/used_atom) // returns index step + var/list/state = steps[index] + if(state_next in state) + var/list/step = state[state_next] + if(do_tool_or_atom_check(used_atom, step["key"])) + //if(L["consume"] && !try_consume(used_atom,L["consume"])) + // return 0 + return FORWARD //to the first step -> forward + else if(state_prev in state) + var/list/step = state[state_prev] + if(do_tool_or_atom_check(used_atom, step["key"])) + //if(L["consume"] && !try_consume(used_atom,L["consume"])) + // return 0 + return BACKWARD //to the first step -> forward + return 0 + +/datum/construction/reversible2/check_step(atom/used_atom,mob/user as mob) + var/diff = is_right_key(user,used_atom) + if(diff) + if(custom_action(index, diff, used_atom, user)) + update_index(diff,user) + update_icon() + return 1 + return 0 + +/datum/construction/reversible2/proc/fixText(text,user) + text = replacetext(text,"{USER}","[user]") + text = replacetext(text,"{HOLDER}","[holder]") + return text + +/datum/construction/reversible2/custom_action(index, diff, used_atom, var/mob/user) + if(!..(index,used_atom,user)) + return 0 + + var/list/step = steps[index] + var/list/state = step[diff==FORWARD ? state_next : state_prev] + user.visible_message(fixText(state["vis_msg"],user),fixText(state["self_msg"],user)) + + if("delete" in state) + qdel(used_atom) + else if("spawn" in state) + var/spawntype=state["spawn"] + var/atom/A = new spawntype(holder.loc) + if("amount" in state) + if(istype(A,/obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C=A + C.amount=state["amount"] + if(istype(A,/obj/item/stack)) + var/obj/item/stack/S=A + S.amount=state["amount"] + + return 1 + +/datum/construction/reversible2/action(used_atom,user) + return check_step(used_atom,user) diff --git a/code/datums/helper_datums/events.dm b/code/datums/helper_datums/events.dm index fa0dccd2a15..2fba9d996ae 100644 --- a/code/datums/helper_datums/events.dm +++ b/code/datums/helper_datums/events.dm @@ -1,68 +1,68 @@ -/* - * WARRANTY VOID IF CODE USED - */ - - -/datum/events - var/list/events - -/datum/events/New() - ..() - events = new - -/datum/events/proc/addEventType(event_type as text) - if(!(event_type in events) || !islist(events[event_type])) - events[event_type] = list() - return 1 - return - - -// Arguments: event_type as text, proc_holder as datum, proc_name as text -// Returns: New event, null on error. -/datum/events/proc/addEvent(event_type as text, proc_holder, proc_name as text) - if(!event_type || !proc_holder || !proc_name) - return - addEventType(event_type) - var/list/event = events[event_type] - var/datum/event/E = new /datum/event(proc_holder,proc_name) - event += E - return E - -// Arguments: event_type as text, any number of additional arguments to pass to event handler -// Returns: null -/datum/events/proc/fireEvent() - //world << "Events in [args[1]] called" - var/list/event = listgetindex(events,args[1]) - if(istype(event)) - spawn(0) - for(var/datum/event/E in event) - if(!E.Fire(arglist(args.Copy(2)))) - clearEvent(args[1],E) - return - -// Arguments: event_type as text, E as /datum/event -// Returns: 1 if event cleared, null on error - -/datum/events/proc/clearEvent(event_type as text, datum/event/E) - if(!event_type || !E) - return - var/list/event = listgetindex(events,event_type) - event -= E - return 1 - - -/datum/event - var/listener - var/proc_name - -/datum/event/New(tlistener,tprocname) - listener = tlistener - proc_name = tprocname - return ..() - -/datum/event/proc/Fire() - //world << "Event fired" - if(listener) - call(listener,proc_name)(arglist(args)) - return 1 - return +/* + * WARRANTY VOID IF CODE USED + */ + + +/datum/events + var/list/events + +/datum/events/New() + ..() + events = new + +/datum/events/proc/addEventType(event_type as text) + if(!(event_type in events) || !islist(events[event_type])) + events[event_type] = list() + return 1 + return + + +// Arguments: event_type as text, proc_holder as datum, proc_name as text +// Returns: New event, null on error. +/datum/events/proc/addEvent(event_type as text, proc_holder, proc_name as text) + if(!event_type || !proc_holder || !proc_name) + return + addEventType(event_type) + var/list/event = events[event_type] + var/datum/event/E = new /datum/event(proc_holder,proc_name) + event += E + return E + +// Arguments: event_type as text, any number of additional arguments to pass to event handler +// Returns: null +/datum/events/proc/fireEvent() + //world << "Events in [args[1]] called" + var/list/event = listgetindex(events,args[1]) + if(istype(event)) + spawn(0) + for(var/datum/event/E in event) + if(!E.Fire(arglist(args.Copy(2)))) + clearEvent(args[1],E) + return + +// Arguments: event_type as text, E as /datum/event +// Returns: 1 if event cleared, null on error + +/datum/events/proc/clearEvent(event_type as text, datum/event/E) + if(!event_type || !E) + return + var/list/event = listgetindex(events,event_type) + event -= E + return 1 + + +/datum/event + var/listener + var/proc_name + +/datum/event/New(tlistener,tprocname) + listener = tlistener + proc_name = tprocname + return ..() + +/datum/event/proc/Fire() + //world << "Event fired" + if(listener) + call(listener,proc_name)(arglist(args)) + return 1 + return diff --git a/code/datums/helper_datums/global_iterator.dm b/code/datums/helper_datums/global_iterator.dm index e3d14743fe1..214045941b5 100644 --- a/code/datums/helper_datums/global_iterator.dm +++ b/code/datums/helper_datums/global_iterator.dm @@ -1,152 +1,152 @@ -/* -README: - -The global_iterator datum is supposed to provide a simple and robust way to -create some constantly "looping" processes with ability to stop and restart them at will. -Generally, the only thing you want to play with (meaning, redefine) is the process() proc. -It must contain all the things you want done. - -Control functions: - new - used to create datum. First argument (optional) - var list(to use in process() proc) as list, - second (optional) - autostart control. - If autostart == TRUE, the loop will be started immediately after datum creation. - - start(list/arguments) - starts the loop. Takes arguments(optional) as a list, which is then used - by process() proc. Returns null if datum already active, 1 if loop started succesfully and 0 if there's - an error in supplied arguments (not list or empty list). - - stop() - stops the loop. Returns null if datum is already inactive and 1 on success. - - set_delay(new_delay) - sets the delay between iterations. Pretty selfexplanatory. - Returns 0 on error(new_delay is not numerical), 1 otherwise. - - set_process_args(list/arguments) - passes the supplied arguments to the process() proc. - - active() - Returns 1 if datum is active, 0 otherwise. - - toggle() - toggles datum state. Returns new datum state (see active()). - -Misc functions: - - get_last_exec_time() - Returns the time of last iteration. - - get_last_exec_time_as_text() - Returns the time of last iteration as text - - -Control vars: - - delay - delay between iterations - - check_for_null - if equals TRUE, on each iteration the supplied arguments will be checked for nulls. - If some varible equals null (and null only), the loop is stopped. - Usefull, if some var unexpectedly becomes null - due to object deletion, for example. - Of course, you can also check the variables inside process() proc to prevent runtime errors. - -Data storage vars: - - result - stores the value returned by process() proc -*/ - -/datum/global_iterator - var/control_switch = 0 - var/delay = 10 - var/list/arg_list = new - var/last_exec = null - var/check_for_null = 1 - var/forbid_garbage = 0 - var/result - var/state = 0 - -/datum/global_iterator/New(list/arguments=null,autostart=1) - delay = delay>0?(delay):1 - if(forbid_garbage) //prevents garbage collection with tag != null - tag = "\ref[src]" - set_process_args(arguments) - if(autostart) - start() - return - -/datum/global_iterator/proc/main() - state = 1 - while(src && control_switch) - last_exec = world.timeofday - if(check_for_null && has_null_args()) - stop() - return 0 - result = process(arglist(arg_list)) - for(var/sleep_time=delay;sleep_time>0;sleep_time--) //uhh, this is ugly. But I see no other way to terminate sleeping proc. Such disgrace. - if(!control_switch) - return 0 - sleep(1) - return 0 - -/datum/global_iterator/proc/start(list/arguments=null) - if(active()) - return - if(arguments) - if(!set_process_args(arguments)) - return 0 - if(!state_check()) //the main loop is sleeping, wait for it to terminate. - return - control_switch = 1 - spawn() - state = main() - return 1 - -/datum/global_iterator/proc/stop() - if(!active()) - return - control_switch = 0 - spawn(-1) //report termination error but don't wait for state_check(). - state_check() - return 1 - -/datum/global_iterator/proc/state_check() - var/lag = 0 - while(state) - sleep(1) - if(++lag>10) - CRASH("The global_iterator loop \ref[src] failed to terminate in designated timeframe. This may be caused by server lagging.") - return 1 - -/datum/global_iterator/process() - return - -/datum/global_iterator/proc/active() - return control_switch - -/datum/global_iterator/proc/has_null_args() - if(null in arg_list) - return 1 - return 0 - - -/datum/global_iterator/proc/set_delay(new_delay) - if(isnum(new_delay)) - delay = max(1, round(new_delay)) - return 1 - else - return 0 - -/datum/global_iterator/proc/get_last_exec_time() - return (last_exec||0) - -/datum/global_iterator/proc/get_last_exec_time_as_text() - return (time2text(last_exec)||"Wasn't executed yet") - -/datum/global_iterator/proc/set_process_args(list/arguments) - if(arguments && istype(arguments, /list) && arguments.len) - arg_list = arguments - return 1 - else -// to_chat(world, "Invalid arguments supplied for [src.type], ref = \ref[src]") - return 0 - -/datum/global_iterator/proc/toggle_null_checks() - check_for_null = !check_for_null - return check_for_null - -/datum/global_iterator/proc/toggle() - if(!stop()) - start() - return active() +/* +README: + +The global_iterator datum is supposed to provide a simple and robust way to +create some constantly "looping" processes with ability to stop and restart them at will. +Generally, the only thing you want to play with (meaning, redefine) is the process() proc. +It must contain all the things you want done. + +Control functions: + new - used to create datum. First argument (optional) - var list(to use in process() proc) as list, + second (optional) - autostart control. + If autostart == TRUE, the loop will be started immediately after datum creation. + + start(list/arguments) - starts the loop. Takes arguments(optional) as a list, which is then used + by process() proc. Returns null if datum already active, 1 if loop started succesfully and 0 if there's + an error in supplied arguments (not list or empty list). + + stop() - stops the loop. Returns null if datum is already inactive and 1 on success. + + set_delay(new_delay) - sets the delay between iterations. Pretty selfexplanatory. + Returns 0 on error(new_delay is not numerical), 1 otherwise. + + set_process_args(list/arguments) - passes the supplied arguments to the process() proc. + + active() - Returns 1 if datum is active, 0 otherwise. + + toggle() - toggles datum state. Returns new datum state (see active()). + +Misc functions: + + get_last_exec_time() - Returns the time of last iteration. + + get_last_exec_time_as_text() - Returns the time of last iteration as text + + +Control vars: + + delay - delay between iterations + + check_for_null - if equals TRUE, on each iteration the supplied arguments will be checked for nulls. + If some varible equals null (and null only), the loop is stopped. + Usefull, if some var unexpectedly becomes null - due to object deletion, for example. + Of course, you can also check the variables inside process() proc to prevent runtime errors. + +Data storage vars: + + result - stores the value returned by process() proc +*/ + +/datum/global_iterator + var/control_switch = 0 + var/delay = 10 + var/list/arg_list = new + var/last_exec = null + var/check_for_null = 1 + var/forbid_garbage = 0 + var/result + var/state = 0 + +/datum/global_iterator/New(list/arguments=null,autostart=1) + delay = delay>0?(delay):1 + if(forbid_garbage) //prevents garbage collection with tag != null + tag = "\ref[src]" + set_process_args(arguments) + if(autostart) + start() + return + +/datum/global_iterator/proc/main() + state = 1 + while(src && control_switch) + last_exec = world.timeofday + if(check_for_null && has_null_args()) + stop() + return 0 + result = process(arglist(arg_list)) + for(var/sleep_time=delay;sleep_time>0;sleep_time--) //uhh, this is ugly. But I see no other way to terminate sleeping proc. Such disgrace. + if(!control_switch) + return 0 + sleep(1) + return 0 + +/datum/global_iterator/proc/start(list/arguments=null) + if(active()) + return + if(arguments) + if(!set_process_args(arguments)) + return 0 + if(!state_check()) //the main loop is sleeping, wait for it to terminate. + return + control_switch = 1 + spawn() + state = main() + return 1 + +/datum/global_iterator/proc/stop() + if(!active()) + return + control_switch = 0 + spawn(-1) //report termination error but don't wait for state_check(). + state_check() + return 1 + +/datum/global_iterator/proc/state_check() + var/lag = 0 + while(state) + sleep(1) + if(++lag>10) + CRASH("The global_iterator loop \ref[src] failed to terminate in designated timeframe. This may be caused by server lagging.") + return 1 + +/datum/global_iterator/process() + return + +/datum/global_iterator/proc/active() + return control_switch + +/datum/global_iterator/proc/has_null_args() + if(null in arg_list) + return 1 + return 0 + + +/datum/global_iterator/proc/set_delay(new_delay) + if(isnum(new_delay)) + delay = max(1, round(new_delay)) + return 1 + else + return 0 + +/datum/global_iterator/proc/get_last_exec_time() + return (last_exec||0) + +/datum/global_iterator/proc/get_last_exec_time_as_text() + return (time2text(last_exec)||"Wasn't executed yet") + +/datum/global_iterator/proc/set_process_args(list/arguments) + if(arguments && istype(arguments, /list) && arguments.len) + arg_list = arguments + return 1 + else +// to_chat(world, "Invalid arguments supplied for [src.type], ref = \ref[src]") + return 0 + +/datum/global_iterator/proc/toggle_null_checks() + check_for_null = !check_for_null + return check_for_null + +/datum/global_iterator/proc/toggle() + if(!stop()) + start() + return active() diff --git a/code/datums/helper_datums/map_template.dm b/code/datums/helper_datums/map_template.dm index 2030897e3ce..8a1e7576e7a 100644 --- a/code/datums/helper_datums/map_template.dm +++ b/code/datums/helper_datums/map_template.dm @@ -161,4 +161,4 @@ var/datum/map_template/shuttle/S = new shuttle_type() shuttle_templates[S.shuttle_id] = S - map_templates[S.shuttle_id] = S \ No newline at end of file + map_templates[S.shuttle_id] = S diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index 2170ad4196e..9bba49f2678 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -1,237 +1,237 @@ -//wrapper -/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, bypass_area_flag=FALSE) - var/datum/teleport/instant/science/D = new - if(D.start(arglist(args))) - return 1 - return 0 - -/datum/teleport - var/atom/movable/teleatom //atom to teleport - var/atom/destination //destination to teleport to - var/precision = 0 //teleport precision - var/datum/effect_system/effectin //effect to show right before teleportation - var/datum/effect_system/effectout //effect to show right after teleportation - var/soundin //soundfile to play before teleportation - var/soundout //soundfile to play after teleportation - var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation) - var/ignore_area_flag = FALSE - - -/datum/teleport/proc/start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, bypass_area_flag=FALSE) - if(!initTeleport(arglist(args))) - return 0 - return 1 - -/datum/teleport/proc/initTeleport(ateleatom, adestination, aprecision, afteleport, aeffectin, aeffectout, asoundin, asoundout, bypass_area_flag=FALSE) - if(!setTeleatom(ateleatom)) - return 0 - if(!setDestination(adestination)) - return 0 - if(!setPrecision(aprecision)) - return 0 - setEffects(aeffectin,aeffectout) - setForceTeleport(afteleport) - setSounds(asoundin,asoundout) - ignore_area_flag = bypass_area_flag - return 1 - -//must succeed -/datum/teleport/proc/setPrecision(aprecision) - if(isnum(aprecision)) - precision = aprecision - return 1 - return 0 - -//must succeed -/datum/teleport/proc/setDestination(atom/adestination) - if(istype(adestination)) - destination = adestination - return 1 - return 0 - -//must succeed in most cases -/datum/teleport/proc/setTeleatom(atom/movable/ateleatom) - if(istype(ateleatom, /obj/effect) && !istype(ateleatom, /obj/effect/dummy/chameleon)) - qdel(ateleatom) - return 0 - if(istype(ateleatom)) - teleatom = ateleatom - return 1 - return 0 - -//custom effects must be properly set up first for instant-type teleports -//optional -/datum/teleport/proc/setEffects(datum/effect_system/aeffectin=null,datum/effect_system/aeffectout=null) - effectin = istype(aeffectin) ? aeffectin : null - effectout = istype(aeffectout) ? aeffectout : null - return 1 - -//optional -/datum/teleport/proc/setForceTeleport(afteleport) - force_teleport = afteleport - return 1 - -//optional -/datum/teleport/proc/setSounds(asoundin=null,asoundout=null) - soundin = isfile(asoundin) ? asoundin : null - soundout = isfile(asoundout) ? asoundout : null - return 1 - -//placeholder -/datum/teleport/proc/teleportChecks() - return 1 - -/datum/teleport/proc/playSpecials(atom/location,datum/effect_system/effect,sound) - if(location) - if(effect) - spawn(-1) - src = null - effect.attach(location) - effect.start() - if(sound) - spawn(-1) - src = null - playsound(location,sound,60,1) - return - -//do the monkey dance -/datum/teleport/proc/doTeleport() - - var/turf/destturf - var/turf/curturf = get_turf(teleatom) - var/area/curarea = get_area(curturf) - - if(precision) - var/list/posturfs = list() - var/center = get_turf(destination) - if(!center) - center = destination - for(var/turf/T in range(precision,center)) - posturfs.Add(T) - destturf = safepick(posturfs) - else - destturf = get_turf(destination) - - if(!is_teleport_allowed(destturf.z) && !ignore_area_flag) - return 0 - // Only check the destination zlevel for is_teleport_allowed. Checking origin as well breaks ERT teleporters. - - var/area/destarea = get_area(destturf) - - if(!ignore_area_flag) - if(curarea.tele_proof) - return 0 - if(destarea.tele_proof) - return 0 - - if(!destturf || !curturf) - return 0 - - playSpecials(curturf,effectin,soundin) - - if(force_teleport) - teleatom.forceMove(destturf) - playSpecials(destturf,effectout,soundout) - else - if(teleatom.Move(destturf)) - playSpecials(destturf,effectout,soundout) - - if(isliving(teleatom)) - var/mob/living/L = teleatom - if(L.buckled) - L.buckled.unbuckle_mob(L, force = TRUE) - if(L.has_buckled_mobs()) - L.unbuckle_all_mobs(force = TRUE) - - destarea.Entered(teleatom) - - return 1 - -/datum/teleport/proc/teleport() - if(teleportChecks()) - return doTeleport() - return 0 - -/datum/teleport/instant //teleports when datum is created - - start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null) - if(..()) - if(teleport()) - return 1 - return 0 - - -/datum/teleport/instant/science - -/datum/teleport/instant/science/setEffects(datum/effect_system/aeffectin,datum/effect_system/aeffectout) - if(aeffectin==null || aeffectout==null) - var/datum/effect_system/spark_spread/aeffect = new - aeffect.set_up(5, 1, teleatom) - effectin = effectin || aeffect - effectout = effectout || aeffect - return 1 - else - return ..() - -/datum/teleport/instant/science/setPrecision(aprecision) - ..() - if(!is_admin_level(destination.z)) - if(istype(teleatom, /obj/item/storage/backpack/holding)) - precision = rand(1, 100) - - var/list/bagholding = teleatom.search_contents_for(/obj/item/storage/backpack/holding) - if(bagholding.len) - precision = max(rand(1, 100)*bagholding.len, 100) - if(istype(teleatom, /mob/living)) - var/mob/living/MM = teleatom - to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!") - return 1 - -// Safe location finder -/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE) - if(!zlevels) - if(zlevel) - zlevels = list(zlevel) - else - zlevels = levels_by_trait(STATION_LEVEL) - var/cycles = 1000 - for(var/cycle in 1 to cycles) - // DRUNK DIALLING WOOOOOOOOO - var/x = rand(1, world.maxx) - var/y = rand(1, world.maxy) - var/z = pick(zlevels) - var/random_location = locate(x,y,z) - - if(!isfloorturf(random_location)) - continue - var/turf/simulated/floor/F = random_location - if(!F.air) - continue - - var/datum/gas_mixture/A = F.air - - // Can most things breathe? - if(A.trace_gases.len) - continue - if(A.oxygen < 16) - continue - if(A.toxins) - continue - if(A.carbon_dioxide >= 10) - continue - - // Aim for goldilocks temperatures and pressure - if((A.temperature <= 270) || (A.temperature >= 360)) - continue - var/pressure = A.return_pressure() - if((pressure <= 20) || (pressure >= 550)) - continue - - if(extended_safety_checks) - if(islava(F)) //chasms aren't /floor, and so are pre-filtered - var/turf/simulated/floor/plating/lava/L = F - if(!L.is_safe()) - continue - - // DING! You have passed the gauntlet, and are "probably" safe. - return F \ No newline at end of file +//wrapper +/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, bypass_area_flag=FALSE) + var/datum/teleport/instant/science/D = new + if(D.start(arglist(args))) + return 1 + return 0 + +/datum/teleport + var/atom/movable/teleatom //atom to teleport + var/atom/destination //destination to teleport to + var/precision = 0 //teleport precision + var/datum/effect_system/effectin //effect to show right before teleportation + var/datum/effect_system/effectout //effect to show right after teleportation + var/soundin //soundfile to play before teleportation + var/soundout //soundfile to play after teleportation + var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation) + var/ignore_area_flag = FALSE + + +/datum/teleport/proc/start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, bypass_area_flag=FALSE) + if(!initTeleport(arglist(args))) + return 0 + return 1 + +/datum/teleport/proc/initTeleport(ateleatom, adestination, aprecision, afteleport, aeffectin, aeffectout, asoundin, asoundout, bypass_area_flag=FALSE) + if(!setTeleatom(ateleatom)) + return 0 + if(!setDestination(adestination)) + return 0 + if(!setPrecision(aprecision)) + return 0 + setEffects(aeffectin,aeffectout) + setForceTeleport(afteleport) + setSounds(asoundin,asoundout) + ignore_area_flag = bypass_area_flag + return 1 + +//must succeed +/datum/teleport/proc/setPrecision(aprecision) + if(isnum(aprecision)) + precision = aprecision + return 1 + return 0 + +//must succeed +/datum/teleport/proc/setDestination(atom/adestination) + if(istype(adestination)) + destination = adestination + return 1 + return 0 + +//must succeed in most cases +/datum/teleport/proc/setTeleatom(atom/movable/ateleatom) + if(istype(ateleatom, /obj/effect) && !istype(ateleatom, /obj/effect/dummy/chameleon)) + qdel(ateleatom) + return 0 + if(istype(ateleatom)) + teleatom = ateleatom + return 1 + return 0 + +//custom effects must be properly set up first for instant-type teleports +//optional +/datum/teleport/proc/setEffects(datum/effect_system/aeffectin=null,datum/effect_system/aeffectout=null) + effectin = istype(aeffectin) ? aeffectin : null + effectout = istype(aeffectout) ? aeffectout : null + return 1 + +//optional +/datum/teleport/proc/setForceTeleport(afteleport) + force_teleport = afteleport + return 1 + +//optional +/datum/teleport/proc/setSounds(asoundin=null,asoundout=null) + soundin = isfile(asoundin) ? asoundin : null + soundout = isfile(asoundout) ? asoundout : null + return 1 + +//placeholder +/datum/teleport/proc/teleportChecks() + return 1 + +/datum/teleport/proc/playSpecials(atom/location,datum/effect_system/effect,sound) + if(location) + if(effect) + spawn(-1) + src = null + effect.attach(location) + effect.start() + if(sound) + spawn(-1) + src = null + playsound(location,sound,60,1) + return + +//do the monkey dance +/datum/teleport/proc/doTeleport() + + var/turf/destturf + var/turf/curturf = get_turf(teleatom) + var/area/curarea = get_area(curturf) + + if(precision) + var/list/posturfs = list() + var/center = get_turf(destination) + if(!center) + center = destination + for(var/turf/T in range(precision,center)) + posturfs.Add(T) + destturf = safepick(posturfs) + else + destturf = get_turf(destination) + + if(!is_teleport_allowed(destturf.z) && !ignore_area_flag) + return 0 + // Only check the destination zlevel for is_teleport_allowed. Checking origin as well breaks ERT teleporters. + + var/area/destarea = get_area(destturf) + + if(!ignore_area_flag) + if(curarea.tele_proof) + return 0 + if(destarea.tele_proof) + return 0 + + if(!destturf || !curturf) + return 0 + + playSpecials(curturf,effectin,soundin) + + if(force_teleport) + teleatom.forceMove(destturf) + playSpecials(destturf,effectout,soundout) + else + if(teleatom.Move(destturf)) + playSpecials(destturf,effectout,soundout) + + if(isliving(teleatom)) + var/mob/living/L = teleatom + if(L.buckled) + L.buckled.unbuckle_mob(L, force = TRUE) + if(L.has_buckled_mobs()) + L.unbuckle_all_mobs(force = TRUE) + + destarea.Entered(teleatom) + + return 1 + +/datum/teleport/proc/teleport() + if(teleportChecks()) + return doTeleport() + return 0 + +/datum/teleport/instant //teleports when datum is created + + start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null) + if(..()) + if(teleport()) + return 1 + return 0 + + +/datum/teleport/instant/science + +/datum/teleport/instant/science/setEffects(datum/effect_system/aeffectin,datum/effect_system/aeffectout) + if(aeffectin==null || aeffectout==null) + var/datum/effect_system/spark_spread/aeffect = new + aeffect.set_up(5, 1, teleatom) + effectin = effectin || aeffect + effectout = effectout || aeffect + return 1 + else + return ..() + +/datum/teleport/instant/science/setPrecision(aprecision) + ..() + if(!is_admin_level(destination.z)) + if(istype(teleatom, /obj/item/storage/backpack/holding)) + precision = rand(1, 100) + + var/list/bagholding = teleatom.search_contents_for(/obj/item/storage/backpack/holding) + if(bagholding.len) + precision = max(rand(1, 100)*bagholding.len, 100) + if(istype(teleatom, /mob/living)) + var/mob/living/MM = teleatom + to_chat(MM, "The bluespace interface on your bag of holding interferes with the teleport!") + return 1 + +// Safe location finder +/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE) + if(!zlevels) + if(zlevel) + zlevels = list(zlevel) + else + zlevels = levels_by_trait(STATION_LEVEL) + var/cycles = 1000 + for(var/cycle in 1 to cycles) + // DRUNK DIALLING WOOOOOOOOO + var/x = rand(1, world.maxx) + var/y = rand(1, world.maxy) + var/z = pick(zlevels) + var/random_location = locate(x,y,z) + + if(!isfloorturf(random_location)) + continue + var/turf/simulated/floor/F = random_location + if(!F.air) + continue + + var/datum/gas_mixture/A = F.air + + // Can most things breathe? + if(A.trace_gases.len) + continue + if(A.oxygen < 16) + continue + if(A.toxins) + continue + if(A.carbon_dioxide >= 10) + continue + + // Aim for goldilocks temperatures and pressure + if((A.temperature <= 270) || (A.temperature >= 360)) + continue + var/pressure = A.return_pressure() + if((pressure <= 20) || (pressure >= 550)) + continue + + if(extended_safety_checks) + if(islava(F)) //chasms aren't /floor, and so are pre-filtered + var/turf/simulated/floor/plating/lava/L = F + if(!L.is_safe()) + continue + + // DING! You have passed the gauntlet, and are "probably" safe. + return F diff --git a/code/datums/helper_datums/topic_input.dm b/code/datums/helper_datums/topic_input.dm index 8090a5221eb..0f6a9f7d689 100644 --- a/code/datums/helper_datums/topic_input.dm +++ b/code/datums/helper_datums/topic_input.dm @@ -1,60 +1,60 @@ -/datum/topic_input - var/href - var/list/href_list - -/datum/topic_input/New(thref,list/thref_list) - href = thref - href_list = thref_list.Copy() - return - -/datum/topic_input/proc/get(i) - return listgetindex(href_list,i) - -/datum/topic_input/proc/getAndLocate(i) - var/t = get(i) - if(t) - t = locate(t) - return t || null - -/datum/topic_input/proc/getNum(i) - var/t = get(i) - if(t) - t = text2num(t) - return isnum(t) ? t : null - -/datum/topic_input/proc/getObj(i) - var/t = getAndLocate(i) - return isobj(t) ? t : null - -/datum/topic_input/proc/getMob(i) - var/t = getAndLocate(i) - return ismob(t) ? t : null - -/datum/topic_input/proc/getTurf(i) - var/t = getAndLocate(i) - return isturf(t) ? t : null - -/datum/topic_input/proc/getAtom(i) - return getType(i,/atom) - -/datum/topic_input/proc/getArea(i) - var/t = getAndLocate(i) - return isarea(t) ? t : null - -/datum/topic_input/proc/getStr(i)//params should always be text, but... - var/t = get(i) - return istext(t) ? t : null - -/datum/topic_input/proc/getType(i,type) - var/t = getAndLocate(i) - return istype(t,type) ? t : null - -/datum/topic_input/proc/getPath(i) - var/t = get(i) - if(t) - t = text2path(t) - return ispath(t) ? t : null - -/datum/topic_input/proc/getList(i) - var/t = getAndLocate(i) - return islist(t) ? t : null +/datum/topic_input + var/href + var/list/href_list + +/datum/topic_input/New(thref,list/thref_list) + href = thref + href_list = thref_list.Copy() + return + +/datum/topic_input/proc/get(i) + return listgetindex(href_list,i) + +/datum/topic_input/proc/getAndLocate(i) + var/t = get(i) + if(t) + t = locate(t) + return t || null + +/datum/topic_input/proc/getNum(i) + var/t = get(i) + if(t) + t = text2num(t) + return isnum(t) ? t : null + +/datum/topic_input/proc/getObj(i) + var/t = getAndLocate(i) + return isobj(t) ? t : null + +/datum/topic_input/proc/getMob(i) + var/t = getAndLocate(i) + return ismob(t) ? t : null + +/datum/topic_input/proc/getTurf(i) + var/t = getAndLocate(i) + return isturf(t) ? t : null + +/datum/topic_input/proc/getAtom(i) + return getType(i,/atom) + +/datum/topic_input/proc/getArea(i) + var/t = getAndLocate(i) + return isarea(t) ? t : null + +/datum/topic_input/proc/getStr(i)//params should always be text, but... + var/t = get(i) + return istext(t) ? t : null + +/datum/topic_input/proc/getType(i,type) + var/t = getAndLocate(i) + return istype(t,type) ? t : null + +/datum/topic_input/proc/getPath(i) + var/t = get(i) + if(t) + t = text2path(t) + return ispath(t) ? t : null + +/datum/topic_input/proc/getList(i) + var/t = getAndLocate(i) + return islist(t) ? t : null diff --git a/code/datums/hud.dm b/code/datums/hud.dm index 365a4309413..f204465d7d6 100644 --- a/code/datums/hud.dm +++ b/code/datums/hud.dm @@ -110,4 +110,4 @@ var/datum/atom_hud/huds = list( \ client.screen += client.void /mob/new_player/add_click_catcher() - return \ No newline at end of file + return diff --git a/code/datums/looping_sounds/looping_sound.dm b/code/datums/looping_sounds/looping_sound.dm index 2cf79ad318b..f44a87bdd7a 100644 --- a/code/datums/looping_sounds/looping_sound.dm +++ b/code/datums/looping_sounds/looping_sound.dm @@ -97,4 +97,4 @@ /datum/looping_sound/proc/on_stop(looped) if(end_sound) - play(end_sound) \ No newline at end of file + play(end_sound) diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm index b1f5bdc6908..8fe37324237 100644 --- a/code/datums/looping_sounds/machinery_sounds.dm +++ b/code/datums/looping_sounds/machinery_sounds.dm @@ -4,4 +4,4 @@ mid_sounds = list('sound/machines/shower/shower_mid1.ogg' = 1,'sound/machines/shower/shower_mid2.ogg' = 1,'sound/machines/shower/shower_mid3.ogg' = 1) mid_length = 10 end_sound = 'sound/machines/shower/shower_end.ogg' - volume = 20 \ No newline at end of file + volume = 20 diff --git a/code/datums/looping_sounds/thermal_drill.dm b/code/datums/looping_sounds/thermal_drill.dm index d4a20647f2b..3346d7e0e06 100644 --- a/code/datums/looping_sounds/thermal_drill.dm +++ b/code/datums/looping_sounds/thermal_drill.dm @@ -1,4 +1,4 @@ /datum/looping_sound/thermal_drill mid_sounds = list('sound/items/thermal_drill.ogg' = 1) mid_length = 19 - volume = 30 \ No newline at end of file + volume = 30 diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 036d70fa17a..db2420a1bf8 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -1,1822 +1,1822 @@ -/* Note from Carnie: - The way datum/mind stuff works has been changed a lot. - Minds now represent IC characters rather than following a client around constantly. - Guidelines for using minds properly: - - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! - ghost.mind is however used as a reference to the ghost's corpse - - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) - the existing mind of the old mob should be transfered to the new mob like so: - mind.transfer_to(new_mob) - - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. - By setting key or ckey explicitly after transfering the mind with transfer_to you will cause bugs like DCing - the player. - - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. - - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting - a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. - new_mob.key = key - The Login proc will handle making a new mob for that mobtype (including setting up stuff like mind.name). Simple! - However if you want that mind to have any special properties like being a traitor etc you will have to do that - yourself. -*/ - -/datum/mind - var/key - var/name //replaces mob/var/original_name - var/mob/living/current - var/mob/living/original //TODO: remove.not used in any meaningful way ~Carn. First I'll need to tweak the way silicon-mobs handle minds. - var/active = 0 - - var/memory - - var/assigned_role //assigned role is what job you're assigned to when you join the station. - var/playtime_role //if set, overrides your assigned_role for the purpose of playtime awards. Set by IDcomputer when your ID is changed. - var/special_role //special roles are typically reserved for antags or roles like ERT. If you want to avoid a character being automatically announced by the AI, on arrival (becuase they're an off station character or something); ensure that special_role and assigned_role are equal. - var/offstation_role = FALSE //set to true for ERT, deathsquad, abductors, etc, that can go from and to z2 at will and shouldn't be antag targets - var/list/restricted_roles = list() - - var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. - - var/role_alt_title - - var/datum/job/assigned_job - var/list/kills = list() - var/list/datum/objective/objectives = list() - var/list/datum/objective/special_verbs = list() - var/list/targets = list() - - var/has_been_rev = 0//Tracks if this mind has been a rev or not - - var/miming = 0 // Mime's vow of silence - var/list/antag_datums - var/speech_span // What span any body this mind has talks in. - var/datum/faction/faction //associated faction - var/datum/changeling/changeling //changeling holder - var/linglink - var/datum/vampire/vampire //vampire holder - var/datum/abductor/abductor //abductor holder - var/datum/devilinfo/devilinfo //devil holder - - var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state - var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD - var/datum/mindslaves/som //stands for slave or master...hush.. - var/datum/devilinfo/devilinfo //Information about the devil, if any. - var/damnation_type = 0 - var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src - var/hasSoul = TRUE - - var/rev_cooldown = 0 - - var/isholy = FALSE // is this person a chaplain or admin role allowed to use bibles - var/isblessed = FALSE // is this person blessed by a chaplain? - var/num_blessed = 0 // for prayers - - // the world.time since the mob has been brigged, or -1 if not at all - var/brigged_since = -1 - var/suicided = FALSE - - //put this here for easier tracking ingame - var/datum/money_account/initial_account - - //zealot_master is a reference to the mob that converted them into a zealot (for ease of investigation and such) - var/mob/living/carbon/human/zealot_master = null - - var/list/learned_recipes //List of learned recipe TYPES. - -/datum/mind/New(new_key) - key = new_key - soulOwner = src - -/datum/mind/Destroy() - SSticker.minds -= src - if(islist(antag_datums)) - for(var/i in antag_datums) - var/datum/antagonist/antag_datum = i - if(antag_datum.delete_on_mind_deletion) - qdel(i) - antag_datums = null - return ..() - -/datum/mind/proc/transfer_to(mob/living/new_character) - var/datum/atom_hud/antag/hud_to_transfer = antag_hud //we need this because leave_hud() will clear this list - var/mob/living/old_current = current - if(!istype(new_character)) - log_runtime(EXCEPTION("transfer_to(): Some idiot has tried to transfer_to() a non mob/living mob."), src) - if(current) //remove ourself from our old body's mind variable - 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 - - SSnanoui.user_transferred(current, new_character) - - if(new_character.mind) //remove any mind currently in our new body's mind variable - new_character.mind.current = null - current = new_character //link ourself to our new body - new_character.mind = src //and link our new body to ourself - for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body - var/datum/antagonist/A = a - A.on_body_transfer(old_current, current) - transfer_antag_huds(hud_to_transfer) //inherit the antag HUD - transfer_actions(new_character) - - if(active) - new_character.key = key //now transfer the key to link the client to our new body - -/datum/mind/proc/store_memory(new_text) - memory += "[new_text]
    " - -/datum/mind/proc/wipe_memory() - memory = null - -/datum/mind/proc/show_memory(mob/recipient, window = 1) - if(!recipient) - recipient = current - var/output = "[current.real_name]'s Memories:
    " - output += memory - - var/antag_datum_objectives = FALSE - for(var/datum/antagonist/A in antag_datums) - output += A.antag_memory - if(!antag_datum_objectives && LAZYLEN(A.objectives)) - antag_datum_objectives = TRUE - - if(LAZYLEN(objectives) || antag_datum_objectives) - output += "
    Objectives:
    " - output += gen_objective_text() - - if(LAZYLEN(job_objectives)) - output += "
    Job Objectives:
      " - - var/obj_count = 1 - for(var/datum/job_objective/objective in job_objectives) - output += "
    • Task #[obj_count]: [objective.get_description()]
    • " - obj_count++ - output += "
    " - if(window) - recipient << browse(output, "window=memory") - else - to_chat(recipient, "[output]") - -/datum/mind/proc/gen_objective_text(admin = FALSE) - . = "" - var/obj_count = 1 - var/list/all_objectives = list() - for(var/datum/antagonist/A in antag_datums) - all_objectives |= A.objectives - - if(LAZYLEN(all_objectives)) - for(var/datum/objective/objective in all_objectives) - . += "
    Objective #[obj_count++]: [objective.explanation_text]" - - for(var/datum/objective/objective in objectives) - . += "Objective #[obj_count++]: [objective.explanation_text]" - if(admin) - . += " Edit " // Edit - . += "Delete " // Delete - - . += "" // Mark Completed - . += "Toggle Completion" - . += "" - . += "
    " - -/datum/mind/proc/_memory_edit_header(gamemode, list/alt) - . = gamemode - if(SSticker.mode.config_tag == gamemode || (LAZYLEN(alt) && (SSticker.mode.config_tag in alt))) - . = uppertext(.) - . = "[.]: " - -/datum/mind/proc/_memory_edit_role_enabled(role) - . = "|Disabled in Prefs" - if(current && current.client && (role in current.client.prefs.be_special)) - . = "|Enabled in Prefs" - -/datum/mind/proc/memory_edit_implant(mob/living/carbon/human/H) - if(ismindshielded(H)) - . = "Mindshield Implant:Remove|Implanted
    " - else - . = "Mindshield Implant:No Implant|Implant [H.p_them()]!
    " - - -/datum/mind/proc/memory_edit_revolution(mob/living/carbon/human/H) - . = _memory_edit_header("revolution") - if(ismindshielded(H)) - . += "NO|headrev|rev" - else if(src in SSticker.mode.head_revolutionaries) - . += "no|HEADREV|rev" - . += "
    Flash: give" - - var/list/L = current.get_contents() - var/obj/item/flash/flash = locate() in L - if(flash) - if(!flash.broken) - . += "|take." - else - . += "|take|repair." - else - . += "." - - . += " Reequip (gives traitor uplink)." - if(objectives.len==0) - . += "
    Objectives are empty! Set to kill all heads." - else if(src in SSticker.mode.revolutionaries) - . += "no|headrev|REV" - else - . += "NO|headrev|rev" - - . += _memory_edit_role_enabled(ROLE_REV) - -/datum/mind/proc/memory_edit_cult(mob/living/carbon/human/H) - . = _memory_edit_header("cult") - if(src in SSticker.mode.cult) - . += "no|CULTIST" - . += "
    Give tome|equip." - else - . += "NO|cultist" - - . += _memory_edit_role_enabled(ROLE_CULTIST) - -/datum/mind/proc/memory_edit_wizard(mob/living/carbon/human/H) - . = _memory_edit_header("wizard") - if(src in SSticker.mode.wizards) - . += "WIZARD|no" - . += "
    To lair, undress, dress up, let choose name." - if(objectives.len==0) - . += "
    Objectives are empty! Randomize!" - else - . += "wizard|NO" - - . += _memory_edit_role_enabled(ROLE_WIZARD) - -/datum/mind/proc/memory_edit_changeling(mob/living/carbon/human/H) - . = _memory_edit_header("changeling", list("traitorchan")) - if(src in SSticker.mode.changelings) - . += "CHANGELING|no" - if(objectives.len==0) - . += "
    Objectives are empty! Randomize!" - if(changeling && changeling.absorbed_dna.len && (current.real_name != changeling.absorbed_dna[1])) - . += "
    Transform to initial appearance." - else - . += "changeling|NO" - - . += _memory_edit_role_enabled(ROLE_CHANGELING) - -/datum/mind/proc/memory_edit_vampire(mob/living/carbon/human/H) - . = _memory_edit_header("vampire", list("traitorvamp")) - if(src in SSticker.mode.vampires) - . += "VAMPIRE|no" - if(objectives.len==0) - . += "
    Objectives are empty! Randomize!" - else - . += "vampire|NO" - - . += _memory_edit_role_enabled(ROLE_VAMPIRE) - /** Enthralled ***/ - . += "
    enthralled: " - if(src in SSticker.mode.vampire_enthralled) - . += "THRALL|no" - else - . += "thrall|NO" - -/datum/mind/proc/memory_edit_nuclear(mob/living/carbon/human/H) - . = _memory_edit_header("nuclear") - if(src in SSticker.mode.syndicates) - . += "OPERATIVE|no" - . += "
    To shuttle, undress, dress up." - var/code - for(var/obj/machinery/nuclearbomb/bombue in GLOB.machines) - if(length(bombue.r_code) <= 5 && bombue.r_code != "LOLNO" && bombue.r_code != "ADMIN") - code = bombue.r_code - break - if(code) - . += " Code is [code]. tell the code." - else - . += "operative|NO" - - . += _memory_edit_role_enabled(ROLE_OPERATIVE) - -/datum/mind/proc/memory_edit_shadowling(mob/living/carbon/human/H) - . = _memory_edit_header("shadowling") - if(src in SSticker.mode.shadows) - . += "SHADOWLING|thrall|no" - else if(src in SSticker.mode.shadowling_thralls) - . += "Shadowling|THRALL|no" - else - . += "shadowling|thrall|NO" - - . += _memory_edit_role_enabled(ROLE_SHADOWLING) - -/datum/mind/proc/memory_edit_abductor(mob/living/carbon/human/H) - . = _memory_edit_header("abductor") - if(src in SSticker.mode.abductors) - . += "ABDUCTOR|no" - . += "|undress|equip" - else - . += "abductor|NO" - - . += _memory_edit_role_enabled(ROLE_ABDUCTOR) - -/datum/mind/proc/memory_edit_devil(mob/living/H) - . = _memory_edit_header("devil", list("devilagents")) - if(src in SSticker.mode.devils) - if(!devilinfo) - . += "No devilinfo found! Yell at a coder!" - else if(!devilinfo.ascendable) - . += "DEVIL|Ascendable Devil|sintouched|no" - else - . += "DEVIL|ASCENDABLE DEVIL|sintouched|no" - else if(src in SSticker.mode.sintouched) - . += "devil|Ascendable Devil|SINTOUCHED|no" - else - . += "devil|Ascendable Devil|sintouched|NO" - - . += _memory_edit_role_enabled(ROLE_DEVIL) - -/datum/mind/proc/memory_edit_eventmisc(mob/living/H) - . = _memory_edit_header("event", list()) - if(src in SSticker.mode.eventmiscs) - . += "YES|no" - else - . += "Event Role|NO" - -/datum/mind/proc/memory_edit_traitor() - . = _memory_edit_header("traitor", list("traitorchan", "traitorvamp")) - if(has_antag_datum(/datum/antagonist/traitor)) - . += "TRAITOR|no" - if(objectives.len==0) - . += "
    Objectives are empty! Randomize!" - else - . += "traitor|NO" - - . += _memory_edit_role_enabled(ROLE_TRAITOR) - // Mindslave - . += "
    mindslaved: " - if(has_antag_datum(/datum/antagonist/mindslave)) - . += "MINDSLAVE|no" - else - . += "mindslave|NO" - -/datum/mind/proc/memory_edit_silicon() - . = "Silicon: " - var/mob/living/silicon/robot/robot = current - if(istype(robot) && robot.emagged) - . += "
    Cyborg: Is emagged! Unemag!
    0th law: [robot.laws.zeroth_law]" - var/mob/living/silicon/ai/ai = current - if(istype(ai) && ai.connected_robots.len) - var/n_e_robots = 0 - for(var/mob/living/silicon/robot/R in ai.connected_robots) - if(R.emagged) - n_e_robots++ - . += "
    [n_e_robots] of [ai.connected_robots.len] slaved cyborgs are emagged. Unemag" - -/datum/mind/proc/memory_edit_uplink() - . = "" - if(ishuman(current) && ((src in SSticker.mode.head_revolutionaries) || \ - (has_antag_datum(/datum/antagonist/traitor)) || \ - (src in SSticker.mode.syndicates))) - . = "Uplink: give" - var/obj/item/uplink/hidden/suplink = find_syndicate_uplink() - var/crystals - if(suplink) - crystals = suplink.uses - if(suplink) - . += "|take" - if(usr.client.holder.rights & (R_SERVER|R_EVENT)) - . += ", [crystals] crystals" - else - . += ", [crystals] crystals" - . += "." //hiel grammar - // ^ whoever left this comment is literally a grammar nazi. stalin better. in russia grammar correct you. - -/datum/mind/proc/edit_memory() - if(!SSticker || !SSticker.mode) - alert("Not before round-start!", "Alert") - return - - var/out = "[name][(current && (current.real_name != name))?" (as [current.real_name])" : ""]
    " - out += "Mind currently owned by key: [key] [active ? "(synced)" : "(not synced)"]
    " - out += "Assigned role: [assigned_role]. Edit
    " - out += "Factions and special roles:
    " - - var/list/sections = list( - "implant", - "revolution", - "cult", - "wizard", - "changeling", - "vampire", // "traitorvamp", - "nuclear", - "traitor", // "traitorchan", - ) - var/mob/living/carbon/human/H = current - if(ishuman(current)) - /** Impanted**/ - sections["implant"] = memory_edit_implant(H) - /** REVOLUTION ***/ - sections["revolution"] = memory_edit_revolution(H) - /** CULT ***/ - sections["cult"] = memory_edit_cult(H) - /** WIZARD ***/ - sections["wizard"] = memory_edit_wizard(H) - /** CHANGELING ***/ - sections["changeling"] = memory_edit_changeling(H) - /** VAMPIRE ***/ - sections["vampire"] = memory_edit_vampire(H) - /** NUCLEAR ***/ - sections["nuclear"] = memory_edit_nuclear(H) - /** SHADOWLING **/ - sections["shadowling"] = memory_edit_shadowling(H) - /** Abductors **/ - sections["abductor"] = memory_edit_abductor(H) - /** DEVIL ***/ - var/static/list/devils_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/carbon/true_devil, /mob/living/silicon/robot)) - if(is_type_in_typecache(current, devils_typecache)) - sections["devil"] = memory_edit_devil(H) - sections["eventmisc"] = memory_edit_eventmisc(H) - /** TRAITOR ***/ - sections["traitor"] = memory_edit_traitor() - /** SILICON ***/ - if(issilicon(current)) - sections["silicon"] = memory_edit_silicon() - /* - This prioritizes antags relevant to the current round to make them appear at the top of the panel. - Traitorchan and traitorvamp are snowflaked in because they have multiple sections. - */ - if(SSticker.mode.config_tag == "traitorchan") - if(sections["traitor"]) - out += sections["traitor"] + "
    " - if(sections["changeling"]) - out += sections["changeling"] + "
    " - sections -= "traitor" - sections -= "changeling" - // Elif technically unnecessary but it makes the following else look better - else if(SSticker.mode.config_tag == "traitorvamp") - if(sections["traitor"]) - out += sections["traitor"] + "
    " - if(sections["vampire"]) - out += sections["vampire"] + "
    " - sections -= "traitor" - sections -= "vampire" - else - if(sections[SSticker.mode.config_tag]) - out += sections[SSticker.mode.config_tag] + "
    " - sections -= SSticker.mode.config_tag - - for(var/i in sections) - if(sections[i]) - out += sections[i] + "
    " - - out += memory_edit_uplink() - out += "
    " - - out += "Memory:
    " - out += memory - out += "
    Edit memory
    " - out += "Objectives:
    " - if(objectives.len == 0) - out += "EMPTY
    " - else - out += gen_objective_text(admin = TRUE) - out += "Add objective

    " - out += "Announce objectives

    " - usr << browse(out, "window=edit_memory[src];size=500x500") - -/datum/mind/Topic(href, href_list) - if(!check_rights(R_ADMIN)) - return - - if(href_list["role_edit"]) - var/new_role = input("Select new role", "Assigned role", assigned_role) as null|anything in GLOB.joblist - if(!new_role) - return - assigned_role = new_role - log_admin("[key_name(usr)] has changed [key_name(current)]'s assigned role to [assigned_role]") - message_admins("[key_name_admin(usr)] has changed [key_name_admin(current)]'s assigned role to [assigned_role]") - - else if(href_list["memory_edit"]) - var/messageinput = input("Write new memory", "Memory", memory) as null|message - if(isnull(messageinput)) - return - var/new_memo = copytext(messageinput, 1,MAX_MESSAGE_LEN) - var/confirmed = alert(usr, "Are you sure you want to edit their memory? It will wipe out their original memory!", "Edit Memory", "Yes", "No") - if(confirmed == "Yes") // Because it is too easy to accidentally wipe someone's memory - memory = new_memo - log_admin("[key_name(usr)] has edited [key_name(current)]'s memory") - message_admins("[key_name_admin(usr)] has edited [key_name_admin(current)]'s memory") - - else if(href_list["obj_edit"] || href_list["obj_add"]) - var/datum/objective/objective - var/objective_pos - var/def_value - - if(href_list["obj_edit"]) - objective = locate(href_list["obj_edit"]) - if(!objective) - return - objective_pos = objectives.Find(objective) - - //Text strings are easy to manipulate. Revised for simplicity. - var/temp_obj_type = "[objective.type]"//Convert path into a text string. - def_value = copytext(temp_obj_type, 19)//Convert last part of path into an objective keyword. - if(!def_value)//If it's a custom objective, it will be an empty string. - def_value = "custom" - - var/new_obj_type = input("Select objective type:", "Objective type", def_value) as null|anything in list("assassinate", "blood", "debrain", "protect", "prevent", "brig", "hijack", "escape", "survive", "steal", "download", "nuclear", "capture", "absorb", "destroy", "maroon", "identity theft", "custom") - if(!new_obj_type) - return - - var/datum/objective/new_objective = null - - switch(new_obj_type) - if("assassinate","protect","debrain", "brig", "maroon") - //To determine what to name the objective in explanation text. - var/objective_type_capital = uppertext(copytext(new_obj_type, 1,2))//Capitalize first letter. - var/objective_type_text = copytext(new_obj_type, 2)//Leave the rest of the text. - var/objective_type = "[objective_type_capital][objective_type_text]"//Add them together into a text string. - - var/list/possible_targets = list() - for(var/datum/mind/possible_target in SSticker.minds) - if((possible_target != src) && istype(possible_target.current, /mob/living/carbon/human)) - possible_targets += possible_target.current - - var/mob/def_target = null - var/objective_list[] = list(/datum/objective/assassinate, /datum/objective/protect, /datum/objective/debrain) - if(objective&&(objective.type in objective_list) && objective:target) - def_target = objective:target.current - possible_targets = sortAtom(possible_targets) - possible_targets += "Free objective" - - var/new_target = input("Select target:", "Objective target", def_target) as null|anything in possible_targets - if(!new_target) - return - - var/objective_path = text2path("/datum/objective/[new_obj_type]") - if(new_target == "Free objective") - new_objective = new objective_path - new_objective.owner = src - new_objective:target = null - new_objective.explanation_text = "Free objective" - else - new_objective = new objective_path - new_objective.owner = src - new_objective:target = new_target:mind - //Will display as special role if assigned mode is equal to special role.. Ninjas/commandos/nuke ops. - new_objective.explanation_text = "[objective_type] [new_target:real_name], the [new_target:mind:assigned_role == new_target:mind:special_role ? (new_target:mind:special_role) : (new_target:mind:assigned_role)]." - - if("destroy") - var/list/possible_targets = active_ais(1) - if(possible_targets.len) - var/mob/new_target = input("Select target:", "Objective target") as null|anything in possible_targets - new_objective = new /datum/objective/destroy - new_objective.target = new_target.mind - new_objective.owner = src - new_objective.explanation_text = "Destroy [new_target.name], the experimental AI." - else - to_chat(usr, "No active AIs with minds") - - if("prevent") - new_objective = new /datum/objective/block - new_objective.owner = src - - if("hijack") - new_objective = new /datum/objective/hijack - new_objective.owner = src - - if("escape") - new_objective = new /datum/objective/escape - new_objective.owner = src - - if("survive") - new_objective = new /datum/objective/survive - new_objective.owner = src - - if("die") - new_objective = new /datum/objective/die - new_objective.owner = src - - if("nuclear") - new_objective = new /datum/objective/nuclear - new_objective.owner = src - - if("steal") - if(!istype(objective, /datum/objective/steal)) - new_objective = new /datum/objective/steal - new_objective.owner = src - else - new_objective = objective - var/datum/objective/steal/steal = new_objective - if(!steal.select_target()) - return - - if("download","capture","absorb", "blood") - var/def_num - if(objective&&objective.type==text2path("/datum/objective/[new_obj_type]")) - def_num = objective.target_amount - - var/target_number = input("Input target number:", "Objective", def_num) as num|null - if(isnull(target_number))//Ordinarily, you wouldn't need isnull. In this case, the value may already exist. - return - - switch(new_obj_type) - if("download") - new_objective = new /datum/objective/download - new_objective.explanation_text = "Download [target_number] research levels." - if("capture") - new_objective = new /datum/objective/capture - new_objective.explanation_text = "Accumulate [target_number] capture points." - if("absorb") - new_objective = new /datum/objective/absorb - new_objective.explanation_text = "Absorb [target_number] compatible genomes." - if("blood") - new_objective = new /datum/objective/blood - new_objective.explanation_text = "Accumulate at least [target_number] total units of blood." - new_objective.owner = src - new_objective.target_amount = target_number - - if("identity theft") - var/list/possible_targets = list() - for(var/datum/mind/possible_target in SSticker.minds) - if((possible_target != src) && ishuman(possible_target.current)) - possible_targets += possible_target - possible_targets = sortAtom(possible_targets) - possible_targets += "Free objective" - var/new_target = input("Select target:", "Objective target") as null|anything in possible_targets - if(!new_target) - return - var/datum/mind/targ = new_target - if(!istype(targ)) - log_runtime(EXCEPTION("Invalid target for identity theft objective, cancelling"), src) - return - new_objective = new /datum/objective/escape/escape_with_identity - new_objective.owner = src - new_objective.target = new_target - new_objective.explanation_text = "Escape on the shuttle or an escape pod with the identity of [targ.current.real_name], the [targ.assigned_role] while wearing [targ.current.p_their()] identification card." - if("custom") - var/expl = sanitize(copytext(input("Custom objective:", "Objective", objective ? objective.explanation_text : "") as text|null,1,MAX_MESSAGE_LEN)) - if(!expl) - return - new_objective = new /datum/objective - new_objective.owner = src - new_objective.explanation_text = expl - - if(!new_objective) - return - - if(objective) - objectives -= objective - qdel(objective) - objectives.Insert(objective_pos, new_objective) - else - objectives += new_objective - - log_admin("[key_name(usr)] has updated [key_name(current)]'s objectives: [new_objective]") - message_admins("[key_name_admin(usr)] has updated [key_name_admin(current)]'s objectives: [new_objective]") - - else if(href_list["obj_delete"]) - var/datum/objective/objective = locate(href_list["obj_delete"]) - if(!istype(objective)) - return - objectives -= objective - - log_admin("[key_name(usr)] has removed one of [key_name(current)]'s objectives: [objective]") - message_admins("[key_name_admin(usr)] has removed one of [key_name_admin(current)]'s objectives: [objective]") - qdel(objective) - - else if(href_list["obj_completed"]) - var/datum/objective/objective = locate(href_list["obj_completed"]) - if(!istype(objective)) - return - objective.completed = !objective.completed - - log_admin("[key_name(usr)] has toggled the completion of one of [key_name(current)]'s objectives") - message_admins("[key_name_admin(usr)] has toggled the completion of one of [key_name_admin(current)]'s objectives") - - else if(href_list["implant"]) - var/mob/living/carbon/human/H = current - - switch(href_list["implant"]) - if("remove") - for(var/obj/item/implant/mindshield/I in H.contents) - if(I && I.implanted) - qdel(I) - to_chat(H, "Your mindshield implant has been deactivated.") - log_admin("[key_name(usr)] has deactivated [key_name(current)]'s mindshield implant") - message_admins("[key_name_admin(usr)] has deactivated [key_name_admin(current)]'s mindshield implant") - if("add") - var/obj/item/implant/mindshield/L = new/obj/item/implant/mindshield(H) - L.implant(H) - - log_admin("[key_name(usr)] has given [key_name(current)] a mindshield implant") - message_admins("[key_name_admin(usr)] has given [key_name_admin(current)] a mindshield implant") - - to_chat(H, "You somehow have become the recepient of a mindshield transplant, and it just activated!") - if(src in SSticker.mode.revolutionaries) - special_role = null - SSticker.mode.revolutionaries -= src - to_chat(src, "The nanobots in the mindshield implant remove all thoughts about being a revolutionary. Get back to work!") - if(src in SSticker.mode.head_revolutionaries) - special_role = null - SSticker.mode.head_revolutionaries -=src - to_chat(src, "The nanobots in the mindshield implant remove all thoughts about being a revolutionary. Get back to work!") - if(src in SSticker.mode.cult) - SSticker.mode.cult -= src - SSticker.mode.update_cult_icons_removed(src) - special_role = null - var/datum/game_mode/cult/cult = SSticker.mode - if(istype(cult)) - cult.memorize_cult_objectives(src) - to_chat(current, "The nanobots in the mindshield implant remove all thoughts about being in a cult. Have a productive day!") - memory = "" - - else if(href_list["revolution"]) - - switch(href_list["revolution"]) - if("clear") - if(src in SSticker.mode.revolutionaries) - SSticker.mode.revolutionaries -= src - to_chat(current, "You have been brainwashed! You are no longer a revolutionary!") - SSticker.mode.update_rev_icons_removed(src) - special_role = null - if(src in SSticker.mode.head_revolutionaries) - SSticker.mode.head_revolutionaries -= src - to_chat(current, "You have been brainwashed! You are no longer a head revolutionary!") - SSticker.mode.update_rev_icons_removed(src) - special_role = null - log_admin("[key_name(usr)] has de-rev'd [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-rev'd [key_name_admin(current)]") - - if("rev") - if(src in SSticker.mode.head_revolutionaries) - SSticker.mode.head_revolutionaries -= src - SSticker.mode.update_rev_icons_removed(src) - to_chat(current, "Revolution has been disappointed of your leadership traits! You are a regular revolutionary now!") - else if(!(src in SSticker.mode.revolutionaries)) - to_chat(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!") - else - return - SSticker.mode.revolutionaries += src - SSticker.mode.update_rev_icons_added(src) - special_role = SPECIAL_ROLE_REV - log_admin("[key_name(usr)] has rev'd [key_name(current)]") - message_admins("[key_name_admin(usr)] has rev'd [key_name_admin(current)]") - - if("headrev") - if(src in SSticker.mode.revolutionaries) - SSticker.mode.revolutionaries -= src - SSticker.mode.update_rev_icons_removed(src) - to_chat(current, "You have proven your devotion to revolution! You are a head revolutionary now!") - else if(!(src in SSticker.mode.head_revolutionaries)) - to_chat(current, "You are a member of the revolutionaries' leadership now!") - else - return - if(SSticker.mode.head_revolutionaries.len>0) - // copy targets - var/datum/mind/valid_head = locate() in SSticker.mode.head_revolutionaries - if(valid_head) - for(var/datum/objective/mutiny/O in valid_head.objectives) - var/datum/objective/mutiny/rev_obj = new - rev_obj.owner = src - rev_obj.target = O.target - rev_obj.explanation_text = "Assassinate [O.target.name], the [O.target.assigned_role]." - objectives += rev_obj - SSticker.mode.greet_revolutionary(src,0) - SSticker.mode.head_revolutionaries += src - SSticker.mode.update_rev_icons_added(src) - special_role = SPECIAL_ROLE_HEAD_REV - log_admin("[key_name(usr)] has head-rev'd [key_name(current)]") - message_admins("[key_name_admin(usr)] has head-rev'd [key_name_admin(current)]") - - if("autoobjectives") - SSticker.mode.forge_revolutionary_objectives(src) - SSticker.mode.greet_revolutionary(src,0) - log_admin("[key_name(usr)] has automatically forged revolutionary objectives for [key_name(current)]") - message_admins("[key_name_admin(usr)] has automatically forged revolutionary objectives for [key_name_admin(current)]") - - if("flash") - if(!SSticker.mode.equip_revolutionary(current)) - to_chat(usr, "Spawning flash failed!") - log_admin("[key_name(usr)] has given [key_name(current)] a flash") - message_admins("[key_name_admin(usr)] has given [key_name_admin(current)] a flash") - - if("takeflash") - var/list/L = current.get_contents() - var/obj/item/flash/flash = locate() in L - if(!flash) - to_chat(usr, "Deleting flash failed!") - qdel(flash) - log_admin("[key_name(usr)] has taken [key_name(current)]'s flash") - message_admins("[key_name_admin(usr)] has taken [key_name_admin(current)]'s flash") - - if("repairflash") - var/list/L = current.get_contents() - var/obj/item/flash/flash = locate() in L - if(!flash) - to_chat(usr, "Repairing flash failed!") - else - flash.broken = 0 - log_admin("[key_name(usr)] has repaired [key_name(current)]'s flash") - message_admins("[key_name_admin(usr)] has repaired [key_name_admin(current)]'s flash") - - if("reequip") - var/list/L = current.get_contents() - var/obj/item/flash/flash = locate() in L - qdel(flash) - take_uplink() - var/fail = 0 - var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) - fail |= !T.equip_traitor(src) - fail |= !SSticker.mode.equip_revolutionary(current) - if(fail) - to_chat(usr, "Reequipping revolutionary goes wrong!") - return - log_admin("[key_name(usr)] has equipped [key_name(current)] as a revolutionary") - message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a revolutionary") - - else if(href_list["cult"]) - switch(href_list["cult"]) - if("clear") - if(src in SSticker.mode.cult) - SSticker.mode.remove_cultist(src) - special_role = null - log_admin("[key_name(usr)] has de-culted [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-culted [key_name_admin(current)]") - if("cultist") - if(!(src in SSticker.mode.cult)) - SSticker.mode.add_cultist(src) - special_role = SPECIAL_ROLE_CULTIST - to_chat(current, "You catch a glimpse of the Realm of [SSticker.cultdat.entity_name], [SSticker.cultdat.entity_title3]. You now see how flimsy the world is, you see that it should be open to the knowledge of [SSticker.cultdat.entity_name].") - to_chat(current, "Assist your new compatriots in their dark dealings. Their goal is yours, and yours is theirs. You serve [SSticker.cultdat.entity_title2] above all else. Bring It back.") - log_admin("[key_name(usr)] has culted [key_name(current)]") - message_admins("[key_name_admin(usr)] has culted [key_name_admin(current)]") - if(!summon_spots.len) - while(summon_spots.len < SUMMON_POSSIBILITIES) - var/area/summon = pick(return_sorted_areas() - summon_spots) - if(summon && is_station_level(summon.z) && summon.valid_territory) - summon_spots += summon - if("tome") - var/mob/living/carbon/human/H = current - if(istype(H)) - var/obj/item/tome/T = new(H) - - var/list/slots = list ( - "backpack" = slot_in_backpack, - "left pocket" = slot_l_store, - "right pocket" = slot_r_store, - "left hand" = slot_l_hand, - "right hand" = slot_r_hand, - ) - var/where = H.equip_in_one_of_slots(T, slots) - if(!where) - to_chat(usr, "Spawning tome failed!") - qdel(T) - else - to_chat(H, "A tome, a message from your new master, appears in your [where].") - log_admin("[key_name(usr)] has spawned a tome for [key_name(current)]") - message_admins("[key_name_admin(usr)] has spawned a tome for [key_name_admin(current)]") - - if("equip") - if(!SSticker.mode.equip_cultist(current)) - to_chat(usr, "Spawning equipment failed!") - log_admin("[key_name(usr)] has equipped [key_name(current)] as a cultist") - message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a cultist") - - else if(href_list["wizard"]) - - switch(href_list["wizard"]) - if("clear") - if(src in SSticker.mode.wizards) - SSticker.mode.wizards -= src - special_role = null - current.spellremove(current) - current.faction = list("Station") - SSticker.mode.update_wiz_icons_removed(src) - to_chat(current, "You have been brainwashed! You are no longer a wizard!") - log_admin("[key_name(usr)] has de-wizarded [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-wizarded [key_name_admin(current)]") - if("wizard") - if(!(src in SSticker.mode.wizards)) - SSticker.mode.wizards += src - special_role = SPECIAL_ROLE_WIZARD - //ticker.mode.learn_basic_spells(current) - SSticker.mode.update_wiz_icons_added(src) - SEND_SOUND(current, 'sound/ambience/antag/ragesmages.ogg') - to_chat(current, "You are a Space Wizard!") - current.faction = list("wizard") - log_admin("[key_name(usr)] has wizarded [key_name(current)]") - message_admins("[key_name_admin(usr)] has wizarded [key_name_admin(current)]") - if("lair") - current.forceMove(pick(wizardstart)) - log_admin("[key_name(usr)] has moved [key_name(current)] to the wizard's lair") - message_admins("[key_name_admin(usr)] has moved [key_name_admin(current)] to the wizard's lair") - if("dressup") - SSticker.mode.equip_wizard(current) - log_admin("[key_name(usr)] has equipped [key_name(current)] as a wizard") - message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a wizard") - if("name") - SSticker.mode.name_wizard(current) - log_admin("[key_name(usr)] has allowed wizard [key_name(current)] to name themselves") - message_admins("[key_name_admin(usr)] has allowed wizard [key_name_admin(current)] to name themselves") - if("autoobjectives") - SSticker.mode.forge_wizard_objectives(src) - to_chat(usr, "The objectives for wizard [key] have been generated. You can edit them and announce manually.") - log_admin("[key_name(usr)] has automatically forged wizard objectives for [key_name(current)]") - message_admins("[key_name_admin(usr)] has automatically forged wizard objectives for [key_name_admin(current)]") - - - else if(href_list["changeling"]) - switch(href_list["changeling"]) - if("clear") - if(src in SSticker.mode.changelings) - SSticker.mode.changelings -= src - special_role = null - if(changeling) - current.remove_changeling_powers() - qdel(changeling) - changeling = null - SSticker.mode.update_change_icons_removed(src) - to_chat(current, "You grow weak and lose your powers! You are no longer a changeling and are stuck in your current form!") - log_admin("[key_name(usr)] has de-changelinged [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-changelinged [key_name_admin(current)]") - if("changeling") - if(!(src in SSticker.mode.changelings)) - SSticker.mode.changelings += src - SSticker.mode.grant_changeling_powers(current) - SSticker.mode.update_change_icons_added(src) - special_role = SPECIAL_ROLE_CHANGELING - SEND_SOUND(current, 'sound/ambience/antag/ling_aler.ogg') - to_chat(current, "Your powers have awoken. A flash of memory returns to us... we are a changeling!") - log_admin("[key_name(usr)] has changelinged [key_name(current)]") - message_admins("[key_name_admin(usr)] has changelinged [key_name_admin(current)]") - - if("autoobjectives") - SSticker.mode.forge_changeling_objectives(src) - to_chat(usr, "The objectives for changeling [key] have been generated. You can edit them and announce manually.") - log_admin("[key_name(usr)] has automatically forged objectives for [key_name(current)]") - message_admins("[key_name_admin(usr)] has automatically forged objectives for [key_name_admin(current)]") - - if("initialdna") - if(!changeling || !changeling.absorbed_dna.len) - to_chat(usr, "Resetting DNA failed!") - else - current.dna = changeling.absorbed_dna[1] - current.real_name = current.dna.real_name - current.UpdateAppearance() - domutcheck(current, null) - log_admin("[key_name(usr)] has reset [key_name(current)]'s DNA") - message_admins("[key_name_admin(usr)] has reset [key_name_admin(current)]'s DNA") - - else if(href_list["vampire"]) - switch(href_list["vampire"]) - if("clear") - if(src in SSticker.mode.vampires) - SSticker.mode.vampires -= src - special_role = null - if(vampire) - vampire.remove_vampire_powers() - qdel(vampire) - vampire = null - SSticker.mode.update_vampire_icons_removed(src) - to_chat(current, "You grow weak and lose your powers! You are no longer a vampire and are stuck in your current form!") - log_admin("[key_name(usr)] has de-vampired [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-vampired [key_name_admin(current)]") - if("vampire") - if(!(src in SSticker.mode.vampires)) - SSticker.mode.vampires += src - SSticker.mode.grant_vampire_powers(current) - SSticker.mode.update_vampire_icons_added(src) - var/datum/mindslaves/slaved = new() - slaved.masters += src - som = slaved //we MIGT want to mindslave someone - special_role = SPECIAL_ROLE_VAMPIRE - SEND_SOUND(current, 'sound/ambience/antag/vampalert.ogg') - to_chat(current, "Your powers have awoken. Your lust for blood grows... You are a Vampire!") - log_admin("[key_name(usr)] has vampired [key_name(current)]") - message_admins("[key_name_admin(usr)] has vampired [key_name_admin(current)]") - - if("autoobjectives") - SSticker.mode.forge_vampire_objectives(src) - to_chat(usr, "The objectives for vampire [key] have been generated. You can edit them and announce manually.") - log_admin("[key_name(usr)] has automatically forged objectives for [key_name(current)]") - message_admins("[key_name_admin(usr)] has automatically forged objectives for [key_name_admin(current)]") - - else if(href_list["vampthrall"]) - switch(href_list["vampthrall"]) - if("clear") - if(src in SSticker.mode.vampire_enthralled) - SSticker.mode.remove_vampire_mind(src) - log_admin("[key_name(usr)] has de-vampthralled [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-vampthralled [key_name_admin(current)]") - - else if(href_list["nuclear"]) - var/mob/living/carbon/human/H = current - - switch(href_list["nuclear"]) - if("clear") - if(src in SSticker.mode.syndicates) - SSticker.mode.syndicates -= src - SSticker.mode.update_synd_icons_removed(src) - special_role = null - for(var/datum/objective/nuclear/O in objectives) - objectives-=O - qdel(O) - to_chat(current, "You have been brainwashed! You are no longer a syndicate operative!") - log_admin("[key_name(usr)] has de-nuke op'd [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-nuke op'd [key_name_admin(current)]") - if("nuclear") - if(!(src in SSticker.mode.syndicates)) - SSticker.mode.syndicates += src - SSticker.mode.update_synd_icons_added(src) - if(SSticker.mode.syndicates.len==1) - SSticker.mode.prepare_syndicate_leader(src) - else - current.real_name = "[syndicate_name()] Operative #[SSticker.mode.syndicates.len-1]" - special_role = SPECIAL_ROLE_NUKEOPS - to_chat(current, "You are a [syndicate_name()] agent!") - SSticker.mode.forge_syndicate_objectives(src) - SSticker.mode.greet_syndicate(src) - log_admin("[key_name(usr)] has nuke op'd [key_name(current)]") - message_admins("[key_name_admin(usr)] has nuke op'd [key_name_admin(current)]") - if("lair") - current.forceMove(get_turf(locate("landmark*Syndicate-Spawn"))) - log_admin("[key_name(usr)] has moved [key_name(current)] to the nuclear operative spawn") - message_admins("[key_name_admin(usr)] has moved [key_name_admin(current)] to the nuclear operative spawn") - if("dressup") - qdel(H.belt) - qdel(H.back) - qdel(H.l_ear) - qdel(H.r_ear) - qdel(H.gloves) - qdel(H.head) - qdel(H.shoes) - qdel(H.wear_id) - qdel(H.wear_pda) - qdel(H.wear_suit) - qdel(H.w_uniform) - - if(!SSticker.mode.equip_syndicate(current)) - to_chat(usr, "Equipping a syndicate failed!") - return - SSticker.mode.update_syndicate_id(current.mind, SSticker.mode.syndicates.len == 1) - log_admin("[key_name(usr)] has equipped [key_name(current)] as a nuclear operative") - message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a nuclear operative") - - if("tellcode") - var/code - for(var/obj/machinery/nuclearbomb/bombue in GLOB.machines) - if(length(bombue.r_code) <= 5 && bombue.r_code != "LOLNO" && bombue.r_code != "ADMIN") - code = bombue.r_code - break - if(code) - store_memory("Syndicate Nuclear Bomb Code: [code]", 0, 0) - to_chat(current, "The nuclear authorization code is: [code]") - log_admin("[key_name(usr)] has given [key_name(current)] the nuclear authorization code") - message_admins("[key_name_admin(usr)] has given [key_name_admin(current)] the nuclear authorization code") - else - to_chat(usr, "No valid nuke found!") - - else if(href_list["eventmisc"]) - switch(href_list["eventmisc"]) - if("clear") - if(src in SSticker.mode.eventmiscs) - SSticker.mode.eventmiscs -= src - SSticker.mode.update_eventmisc_icons_removed(src) - special_role = null - message_admins("[key_name_admin(usr)] has de-eventantag'ed [current].") - log_admin("[key_name(usr)] has de-eventantag'ed [current].") - if("eventmisc") - SSticker.mode.eventmiscs += src - special_role = SPECIAL_ROLE_EVENTMISC - SSticker.mode.update_eventmisc_icons_added(src) - message_admins("[key_name_admin(usr)] has eventantag'ed [current].") - log_admin("[key_name(usr)] has eventantag'ed [current].") - else if(href_list["devil"]) - switch(href_list["devil"]) - if("clear") - if(src in SSticker.mode.devils) - if(istype(current,/mob/living/carbon/true_devil/)) - to_chat(usr,"This cannot be used on true or arch-devils.") - else - SSticker.mode.devils -= src - SSticker.mode.update_devil_icons_removed(src) - special_role = null - to_chat(current,"Your infernal link has been severed! You are no longer a devil!") - RemoveSpell(/obj/effect/proc_holder/spell/targeted/infernal_jaunt) - RemoveSpell(/obj/effect/proc_holder/spell/fireball/hellish) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_contract) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/ascended) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/violin) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_dancefloor) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/sintouch) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/sintouch/ascended) - message_admins("[key_name_admin(usr)] has de-devil'ed [current].") - if(issilicon(current)) - var/mob/living/silicon/S = current - S.laws.clear_sixsixsix_laws() - devilinfo = null - log_admin("[key_name(usr)] has de-devil'ed [current].") - else if(src in SSticker.mode.sintouched) - SSticker.mode.sintouched -= src - message_admins("[key_name_admin(usr)] has de-sintouch'ed [current].") - log_admin("[key_name(usr)] has de-sintouch'ed [current].") - if("devil") - if(devilinfo) - devilinfo.ascendable = FALSE - message_admins("[key_name_admin(usr)] has made [current] unable to ascend as a devil.") - log_admin("[key_name_admin(usr)] has made [current] unable to ascend as a devil.") - return - if(!ishuman(current) && !isrobot(current)) - to_chat(usr, "This only works on humans and cyborgs!") - return - SSticker.mode.devils += src - special_role = "devil" - SSticker.mode.update_devil_icons_added(src) - SSticker.mode.finalize_devil(src, FALSE) - SSticker.mode.forge_devil_objectives(src, 2) - SSticker.mode.greet_devil(src) - message_admins("[key_name_admin(usr)] has devil'ed [current].") - log_admin("[key_name(usr)] has devil'ed [current].") - if("ascendable_devil") - if(devilinfo) - devilinfo.ascendable = TRUE - message_admins("[key_name_admin(usr)] has made [current] able to ascend as a devil.") - log_admin("[key_name_admin(usr)] has made [current] able to ascend as a devil.") - return - if(!ishuman(current) && !isrobot(current)) - to_chat(usr, "This only works on humans and cyborgs!") - return - SSticker.mode.devils += src - special_role = "devil" - SSticker.mode.update_devil_icons_added(src) - SSticker.mode.finalize_devil(src, TRUE) - SSticker.mode.forge_devil_objectives(src, 2) - SSticker.mode.greet_devil(src) - message_admins("[key_name_admin(usr)] has devil'ed [current]. The devil has been marked as ascendable.") - log_admin("[key_name(usr)] has devil'ed [current]. The devil has been marked as ascendable.") - if("sintouched") - var/mob/living/carbon/human/H = current - H.influenceSin() - message_admins("[key_name_admin(usr)] has sintouch'ed [current].") - log_admin("[key_name(usr)] has sintouch'ed [current].") - - else if(href_list["traitor"]) - switch(href_list["traitor"]) - if("clear") - if(has_antag_datum(/datum/antagonist/traitor)) - to_chat(current, "You have been brainwashed! You are no longer a traitor!") - remove_antag_datum(/datum/antagonist/traitor) - log_admin("[key_name(usr)] has de-traitored [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-traitored [key_name_admin(current)]") - - if("traitor") - if(!(has_antag_datum(/datum/antagonist/traitor))) - var/datum/antagonist/traitor/T = new() - T.give_objectives = FALSE - T.should_equip = FALSE - add_antag_datum(T) - log_admin("[key_name(usr)] has traitored [key_name(current)]") - message_admins("[key_name_admin(usr)] has traitored [key_name_admin(current)]") - - if("autoobjectives") - var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) - T.forge_traitor_objectives(src) - to_chat(usr, "The objectives for traitor [key] have been generated. You can edit them and announce manually.") - log_admin("[key_name(usr)] has automatically forged objectives for [key_name(current)]") - message_admins("[key_name_admin(usr)] has automatically forged objectives for [key_name_admin(current)]") - - else if(href_list["mindslave"]) - switch(href_list["mindslave"]) - if("clear") - if(has_antag_datum(/datum/antagonist/mindslave)) - var/mob/living/carbon/human/H = current - for(var/i in H.contents) - if(istype(i, /obj/item/implant/traitor)) - qdel(i) - break - remove_antag_datum(/datum/antagonist/mindslave) - log_admin("[key_name(usr)] has de-mindslaved [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-mindslaved [key_name_admin(current)]") - - else if(href_list["shadowling"]) - switch(href_list["shadowling"]) - if("clear") - SSticker.mode.update_shadow_icons_removed(src) - if(src in SSticker.mode.shadows) - SSticker.mode.shadows -= src - special_role = null - to_chat(current, "Your powers have been quenched! You are no longer a shadowling!") - message_admins("[key_name_admin(usr)] has de-shadowlinged [current].") - log_admin("[key_name(usr)] has de-shadowlinged [current].") - current.spellremove(current) - current.remove_language("Shadowling Hivemind") - else if(src in SSticker.mode.shadowling_thralls) - SSticker.mode.remove_thrall(src,0) - message_admins("[key_name_admin(usr)] has de-thrall'ed [current].") - log_admin("[key_name(usr)] has de-thralled [key_name(current)]") - message_admins("[key_name_admin(usr)] has de-thralled [key_name_admin(current)]") - if("shadowling") - if(!ishuman(current)) - to_chat(usr, "This only works on humans!") - return - SSticker.mode.shadows += src - special_role = SPECIAL_ROLE_SHADOWLING - to_chat(current, "Something stirs deep in your mind. A red light floods your vision, and slowly you remember. Though your human disguise has served you well, the \ - time is nigh to cast it off and enter your true form. You have disguised yourself amongst the humans, but you are not one of them. You are a shadowling, and you are to ascend at all costs.\ - ") - SSticker.mode.finalize_shadowling(src) - SSticker.mode.update_shadow_icons_added(src) - log_admin("[key_name(usr)] has shadowlinged [key_name(current)]") - message_admins("[key_name_admin(usr)] has shadowlinged [key_name_admin(current)]") - if("thrall") - if(!ishuman(current)) - to_chat(usr, "This only works on humans!") - return - SSticker.mode.add_thrall(src) - message_admins("[key_name_admin(usr)] has thralled [current].") - log_admin("[key_name(usr)] has thralled [current].") - - else if(href_list["abductor"]) - switch(href_list["abductor"]) - if("clear") - to_chat(usr, "Not implemented yet. Sorry!") - //ticker.mode.update_abductor_icons_removed(src) - if("abductor") - if(!ishuman(current)) - to_chat(usr, "This only works on humans!") - return - make_Abductor() - log_admin("[key_name(usr)] turned [current] into abductor.") - SSticker.mode.update_abductor_icons_added(src) - if("equip") - if(!ishuman(current)) - to_chat(usr, "This only works on humans!") - return - - var/mob/living/carbon/human/H = current - var/gear = alert("Agent or Scientist Gear","Gear","Agent","Scientist") - if(gear) - if(gear=="Agent") - H.equipOutfit(/datum/outfit/abductor/agent) - else - H.equipOutfit(/datum/outfit/abductor/scientist) - - else if(href_list["silicon"]) - switch(href_list["silicon"]) - if("unemag") - var/mob/living/silicon/robot/R = current - if(istype(R)) - R.emagged = 0 - if(R.module) - if(R.activated(R.module.emag)) - R.module_active = null - if(R.module_state_1 == R.module.emag) - R.module_state_1 = null - R.contents -= R.module.emag - else if(R.module_state_2 == R.module.emag) - R.module_state_2 = null - R.contents -= R.module.emag - else if(R.module_state_3 == R.module.emag) - R.module_state_3 = null - R.contents -= R.module.emag - R.clear_supplied_laws() - R.laws = new /datum/ai_laws/crewsimov - log_admin("[key_name(usr)] has un-emagged [key_name(current)]") - message_admins("[key_name_admin(usr)] has un-emagged [key_name_admin(current)]") - - if("unemagcyborgs") - if(isAI(current)) - var/mob/living/silicon/ai/ai = current - for(var/mob/living/silicon/robot/R in ai.connected_robots) - R.emagged = 0 - if(R.module) - if(R.activated(R.module.emag)) - R.module_active = null - if(R.module_state_1 == R.module.emag) - R.module_state_1 = null - R.contents -= R.module.emag - else if(R.module_state_2 == R.module.emag) - R.module_state_2 = null - R.contents -= R.module.emag - else if(R.module_state_3 == R.module.emag) - R.module_state_3 = null - R.contents -= R.module.emag - R.clear_supplied_laws() - R.laws = new /datum/ai_laws/crewsimov - log_admin("[key_name(usr)] has unemagged [key_name(ai)]'s cyborgs") - message_admins("[key_name_admin(usr)] has unemagged [key_name_admin(ai)]'s cyborgs") - - else if(href_list["common"]) - switch(href_list["common"]) - if("undress") - if(ishuman(current)) - var/mob/living/carbon/human/H = current - // Don't "undress" organs right out of the body - for(var/obj/item/W in H.contents - (H.bodyparts | H.internal_organs)) - current.unEquip(W, 1) - else - for(var/obj/item/W in current) - current.unEquip(W, 1) - log_admin("[key_name(usr)] has unequipped [key_name(current)]") - message_admins("[key_name_admin(usr)] has unequipped [key_name_admin(current)]") - if("takeuplink") - take_uplink() - var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) - T.antag_memory = "" //Remove any antag memory they may have had (uplink codes, code phrases) - log_admin("[key_name(usr)] has taken [key_name(current)]'s uplink") - message_admins("[key_name_admin(usr)] has taken [key_name_admin(current)]'s uplink") - if("crystals") - if(usr.client.holder.rights & (R_SERVER|R_EVENT)) - var/obj/item/uplink/hidden/suplink = find_syndicate_uplink() - var/crystals - if(suplink) - crystals = suplink.uses - crystals = input("Amount of telecrystals for [key]","Syndicate uplink", crystals) as null|num - if(!isnull(crystals)) - if(suplink) - suplink.uses = crystals - log_admin("[key_name(usr)] has set [key_name(current)]'s telecrystals to [crystals]") - message_admins("[key_name_admin(usr)] has set [key_name_admin(current)]'s telecrystals to [crystals]") - if("uplink") - if(has_antag_datum(/datum/antagonist/traitor)) - var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) - T.give_codewords() - if(!T.equip_traitor(src)) - to_chat(usr, "Equipping a syndicate failed!") - return - log_admin("[key_name(usr)] has given [key_name(current)] an uplink") - message_admins("[key_name_admin(usr)] has given [key_name_admin(current)] an uplink") - - else if(href_list["obj_announce"]) - announce_objectives() - SEND_SOUND(current, sound('sound/ambience/alarm4.ogg')) - log_admin("[key_name(usr)] has announced [key_name(current)]'s objectives") - message_admins("[key_name_admin(usr)] has announced [key_name_admin(current)]'s objectives") - - edit_memory() - - -// Datum antag mind procs -/datum/mind/proc/add_antag_datum(datum_type_or_instance, team) - if(!datum_type_or_instance) - return - var/datum/antagonist/A - if(!ispath(datum_type_or_instance)) - A = datum_type_or_instance - if(!istype(A)) - return - else - A = new datum_type_or_instance() - //Choose snowflake variation if antagonist handles it - var/datum/antagonist/S = A.specialization(src) - if(S && S != A) - qdel(A) - A = S - if(!A.can_be_owned(src)) - qdel(A) - return - A.owner = src - LAZYADD(antag_datums, A) - A.create_team(team) - var/datum/team/antag_team = A.get_team() - if(antag_team) - antag_team.add_member(src) - A.on_gain() - return A - -/datum/mind/proc/remove_antag_datum(datum_type) - if(!datum_type) - return - var/datum/antagonist/A = has_antag_datum(datum_type) - if(A) - A.on_removal() - return TRUE - - -/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. - for(var/a in antag_datums) - var/datum/antagonist/A = a - A.on_removal() - -/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) - if(!datum_type) - return - . = FALSE - for(var/a in antag_datums) - var/datum/antagonist/A = a - if(check_subtypes && istype(A, datum_type)) - return A - else if(A.type == datum_type) - return A - -/datum/mind/proc/announce_objectives() - to_chat(current, "Your current objectives:") - for(var/line in splittext(gen_objective_text(), "
    ")) - to_chat(current, line) - -/datum/mind/proc/find_syndicate_uplink() - var/list/L = current.get_contents() - for(var/obj/item/I in L) - if(I.hidden_uplink) - return I.hidden_uplink - return null - -/datum/mind/proc/take_uplink() - var/obj/item/uplink/hidden/H = find_syndicate_uplink() - if(H) - qdel(H) - -/datum/mind/proc/make_Traitor() - if(!has_antag_datum(/datum/antagonist/traitor)) - add_antag_datum(/datum/antagonist/traitor) - -/datum/mind/proc/make_Nuke() - if(!(src in SSticker.mode.syndicates)) - SSticker.mode.syndicates += src - SSticker.mode.update_synd_icons_added(src) - if(SSticker.mode.syndicates.len==1) - SSticker.mode.prepare_syndicate_leader(src) - else - current.real_name = "[syndicate_name()] Operative #[SSticker.mode.syndicates.len-1]" - special_role = SPECIAL_ROLE_NUKEOPS - assigned_role = SPECIAL_ROLE_NUKEOPS - to_chat(current, "You are a [syndicate_name()] agent!") - SSticker.mode.forge_syndicate_objectives(src) - SSticker.mode.greet_syndicate(src) - - current.loc = get_turf(locate("landmark*Syndicate-Spawn")) - - var/mob/living/carbon/human/H = current - qdel(H.belt) - qdel(H.back) - qdel(H.l_ear) - qdel(H.r_ear) - qdel(H.gloves) - qdel(H.head) - qdel(H.shoes) - qdel(H.wear_id) - qdel(H.wear_pda) - qdel(H.wear_suit) - qdel(H.w_uniform) - - SSticker.mode.equip_syndicate(current) - -/datum/mind/proc/make_Vampire() - if(!(src in SSticker.mode.vampires)) - SSticker.mode.vampires += src - SSticker.mode.grant_vampire_powers(current) - special_role = SPECIAL_ROLE_VAMPIRE - SSticker.mode.forge_vampire_objectives(src) - SSticker.mode.greet_vampire(src) - SSticker.mode.update_vampire_icons_added(src) - -/datum/mind/proc/make_Changeling() - if(!(src in SSticker.mode.changelings)) - SSticker.mode.changelings += src - SSticker.mode.grant_changeling_powers(current) - special_role = SPECIAL_ROLE_CHANGELING - SSticker.mode.forge_changeling_objectives(src) - SSticker.mode.greet_changeling(src) - SSticker.mode.update_change_icons_added(src) - -/datum/mind/proc/make_Overmind() - if(!(src in SSticker.mode.blob_overminds)) - SSticker.mode.blob_overminds += src - special_role = SPECIAL_ROLE_BLOB_OVERMIND - -/datum/mind/proc/make_Wizard() - if(!(src in SSticker.mode.wizards)) - SSticker.mode.wizards += src - special_role = SPECIAL_ROLE_WIZARD - assigned_role = SPECIAL_ROLE_WIZARD - //ticker.mode.learn_basic_spells(current) - if(!wizardstart.len) - current.loc = pick(latejoin) - to_chat(current, "HOT INSERTION, GO GO GO") - else - current.loc = pick(wizardstart) - - SSticker.mode.equip_wizard(current) - for(var/obj/item/spellbook/S in current.contents) - S.op = 0 - SSticker.mode.name_wizard(current) - SSticker.mode.forge_wizard_objectives(src) - SSticker.mode.greet_wizard(src) - SSticker.mode.update_wiz_icons_added(src) - -/datum/mind/proc/make_Rev() - if(SSticker.mode.head_revolutionaries.len>0) - // copy targets - var/datum/mind/valid_head = locate() in SSticker.mode.head_revolutionaries - if(valid_head) - for(var/datum/objective/mutiny/O in valid_head.objectives) - var/datum/objective/mutiny/rev_obj = new - rev_obj.owner = src - rev_obj.target = O.target - rev_obj.explanation_text = "Assassinate [O.target.current.real_name], the [O.target.assigned_role]." - objectives += rev_obj - SSticker.mode.greet_revolutionary(src,0) - SSticker.mode.head_revolutionaries += src - SSticker.mode.update_rev_icons_added(src) - special_role = SPECIAL_ROLE_HEAD_REV - - SSticker.mode.forge_revolutionary_objectives(src) - SSticker.mode.greet_revolutionary(src,0) - - var/list/L = current.get_contents() - var/obj/item/flash/flash = locate() in L - qdel(flash) - take_uplink() - var/fail = 0 -// fail |= !ticker.mode.equip_traitor(current, 1) - fail |= !SSticker.mode.equip_revolutionary(current) - -/datum/mind/proc/make_Abductor() - var/role = alert("Abductor Role ?","Role","Agent","Scientist") - var/team = input("Abductor Team ?","Team ?") in list(1,2,3,4) - var/teleport = alert("Teleport to ship ?","Teleport","Yes","No") - - if(!role || !team || !teleport) - return - - if(!ishuman(current)) - return - - SSticker.mode.abductors |= src - - var/datum/objective/stay_hidden/hidden_obj = new - hidden_obj.owner = src - objectives += hidden_obj - - var/datum/objective/experiment/O = new - O.owner = src - objectives += O - - var/mob/living/carbon/human/H = current - - H.set_species(/datum/species/abductor) - var/datum/species/abductor/S = H.dna.species - - if(role == "Scientist") - S.scientist = TRUE - - S.team = team - - var/list/obj/effect/landmark/abductor/agent_landmarks = new - var/list/obj/effect/landmark/abductor/scientist_landmarks = new - agent_landmarks.len = 4 - scientist_landmarks.len = 4 - for(var/obj/effect/landmark/abductor/A in GLOB.landmarks_list) - if(istype(A, /obj/effect/landmark/abductor/agent)) - agent_landmarks[text2num(A.team)] = A - else if(istype(A, /obj/effect/landmark/abductor/scientist)) - scientist_landmarks[text2num(A.team)] = A - - var/obj/effect/landmark/L - if(teleport == "Yes") - switch(role) - if("Agent") - L = agent_landmarks[team] - if("Scientist") - L = agent_landmarks[team] - H.forceMove(L.loc) - - -// check whether this mind's mob has been brigged for the given duration -// have to call this periodically for the duration to work properly -/datum/mind/proc/is_brigged(duration) - var/turf/T = current.loc - if(!istype(T)) - brigged_since = -1 - return 0 - - var/is_currently_brigged = current.is_in_brig() - if(!is_currently_brigged) - brigged_since = -1 - return 0 - - if(brigged_since == -1) - brigged_since = world.time - - return (duration <= world.time - brigged_since) - -/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) - spell_list += S - S.action.Grant(current) - -/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) //To remove a specific spell from a mind - if(!spell) - return - for(var/obj/effect/proc_holder/spell/S in spell_list) - if(istype(S, spell)) - qdel(S) - spell_list -= S - -/datum/mind/proc/transfer_actions(mob/living/new_character) - if(current && current.actions) - for(var/datum/action/A in current.actions) - A.Grant(new_character) - transfer_mindbound_actions(new_character) - -/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - S.action.Grant(new_character) - -/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - for(var/type in exceptions) - if(istype(S, type)) - continue - S.charge_counter = delay - spawn(0) - S.start_recharge() - S.updateButtonIcon() - -/datum/mind/proc/get_ghost(even_if_they_cant_reenter) - for(var/mob/dead/observer/G in GLOB.dead_mob_list) - if(G.mind == src) - if(G.can_reenter_corpse || even_if_they_cant_reenter) - return G - break - -/datum/mind/proc/grab_ghost(force) - var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force) - . = G - if(G) - G.reenter_corpse() - - -/datum/mind/proc/make_zealot(mob/living/carbon/human/missionary, convert_duration = 6000, team_color = "red") - - zealot_master = missionary - - var/list/implanters - if(!(missionary.mind in SSticker.mode.implanter)) - SSticker.mode.implanter[missionary.mind] = list() - implanters = SSticker.mode.implanter[missionary.mind] - implanters.Add(src) - SSticker.mode.implanted.Add(src) - SSticker.mode.implanted[src] = missionary.mind - SSticker.mode.implanter[missionary.mind] = implanters - SSticker.mode.traitors += src - - - var/datum/objective/protect/zealot_objective = new - zealot_objective.target = missionary.mind - zealot_objective.owner = src - zealot_objective.explanation_text = "Obey every order from and protect [missionary.real_name], the [missionary.mind.assigned_role == missionary.mind.special_role ? (missionary.mind.special_role) : (missionary.mind.assigned_role)]." - objectives += zealot_objective - add_antag_datum(/datum/antagonist/mindslave) - - var/datum/antagonist/traitor/T = missionary.mind.has_antag_datum(/datum/antagonist) - T.update_traitor_icons_added(missionary.mind) - - to_chat(current, "You're now a loyal zealot of [missionary.name]! You now must lay down your life to protect [missionary.p_them()] and assist in [missionary.p_their()] goals at any cost.") - - var/datum/mindslaves/slaved = missionary.mind.som - som = slaved - slaved.serv += current - slaved.add_serv_hud(missionary.mind, "master") //handles master servent icons - slaved.add_serv_hud(src, "mindslave") - - var/obj/item/clothing/under/jumpsuit = null - if(ishuman(current)) //only bother with the jumpsuit stuff if we are a human type, since we won't have the slot otherwise - var/mob/living/carbon/human/H = current - if(H.w_uniform) - jumpsuit = H.w_uniform - jumpsuit.color = team_color - H.update_inv_w_uniform(0,0) - - add_attack_logs(missionary, current, "Converted to a zealot for [convert_duration/600] minutes") - addtimer(CALLBACK(src, .proc/remove_zealot, jumpsuit), convert_duration) //deconverts after the timer expires - return 1 - -/datum/mind/proc/remove_zealot(obj/item/clothing/under/jumpsuit = null) - if(!zealot_master) //if they aren't a zealot, we can't remove their zealot status, obviously. don't bother with the rest so we don't confuse them with the messages - return - remove_antag_datum(/datum/antagonist/mindslave) - add_attack_logs(zealot_master, current, "Lost control of zealot") - zealot_master = null - - if(jumpsuit) - jumpsuit.color = initial(jumpsuit.color) //reset the jumpsuit no matter where our mind is - if(ishuman(current)) //but only try updating us if we are still a human type since it is a human proc - var/mob/living/carbon/human/H = current - H.update_inv_w_uniform(0,0) - - to_chat(current, "You seem to have forgotten the events of the past 10 minutes or so, and your head aches a bit as if someone beat it savagely with a stick.") - to_chat(current, "This means you don't remember who you were working for or what you were doing.") - -/datum/mind/proc/is_revivable() //Note, this ONLY checks the mind. - if(damnation_type) - return FALSE - return TRUE - -// returns a mob to message to produce something visible for the target mind -/datum/mind/proc/messageable_mob() - if(!QDELETED(current) && current.client) - return current - else - return get_ghost(even_if_they_cant_reenter = TRUE) - -//Initialisation procs -/mob/proc/mind_initialize() - if(mind) - mind.key = key - else - mind = new /datum/mind(key) - if(SSticker) - SSticker.minds += mind - else - error("mind_initialize(): No ticker ready yet! Please inform Carn") - if(!mind.name) - mind.name = real_name - mind.current = src - -//HUMAN -/mob/living/carbon/human/mind_initialize() - ..() - if(!mind.assigned_role) - mind.assigned_role = "Civilian" //defualt - -/mob/proc/sync_mind() - mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) - mind.active = 1 //indicates that the mind is currently synced with a client - -//slime -/mob/living/simple_animal/slime/mind_initialize() - ..() - mind.assigned_role = "slime" - -//XENO -/mob/living/carbon/alien/mind_initialize() - ..() - mind.assigned_role = "Alien" - //XENO HUMANOID -/mob/living/carbon/alien/humanoid/queen/mind_initialize() - ..() - mind.special_role = SPECIAL_ROLE_XENOMORPH_QUEEN - -/mob/living/carbon/alien/humanoid/hunter/mind_initialize() - ..() - mind.special_role = SPECIAL_ROLE_XENOMORPH_HUNTER - -/mob/living/carbon/alien/humanoid/drone/mind_initialize() - ..() - mind.special_role = SPECIAL_ROLE_XENOMORPH_DRONE - -/mob/living/carbon/alien/humanoid/sentinel/mind_initialize() - ..() - mind.special_role = SPECIAL_ROLE_XENOMORPH_SENTINEL - //XENO LARVA -/mob/living/carbon/alien/larva/mind_initialize() - ..() - mind.special_role = SPECIAL_ROLE_XENOMORPH_LARVA - -//AI -/mob/living/silicon/ai/mind_initialize() - ..() - mind.assigned_role = "AI" - -//BORG -/mob/living/silicon/robot/mind_initialize() - ..() - mind.assigned_role = "Cyborg" - -//PAI -/mob/living/silicon/pai/mind_initialize() - ..() - mind.assigned_role = "pAI" - mind.special_role = null - -//BLOB -/mob/camera/overmind/mind_initialize() - ..() - mind.special_role = SPECIAL_ROLE_BLOB - -//Animals -/mob/living/simple_animal/mind_initialize() - ..() - mind.assigned_role = "Animal" - -/mob/living/simple_animal/pet/dog/corgi/mind_initialize() - ..() - mind.assigned_role = "Corgi" - -/mob/living/simple_animal/shade/mind_initialize() - ..() - mind.assigned_role = "Shade" - -/mob/living/simple_animal/construct/builder/mind_initialize() - ..() - mind.assigned_role = "Artificer" - mind.special_role = SPECIAL_ROLE_CULTIST - -/mob/living/simple_animal/construct/wraith/mind_initialize() - ..() - mind.assigned_role = "Wraith" - mind.special_role = SPECIAL_ROLE_CULTIST - -/mob/living/simple_animal/construct/armoured/mind_initialize() - ..() - mind.assigned_role = "Juggernaut" - mind.special_role = SPECIAL_ROLE_CULTIST +/* Note from Carnie: + The way datum/mind stuff works has been changed a lot. + Minds now represent IC characters rather than following a client around constantly. + Guidelines for using minds properly: + - Never mind.transfer_to(ghost). The var/current and var/original of a mind must always be of type mob/living! + ghost.mind is however used as a reference to the ghost's corpse + - When creating a new mob for an existing IC character (e.g. cloning a dead guy or borging a brain of a human) + the existing mind of the old mob should be transfered to the new mob like so: + mind.transfer_to(new_mob) + - You must not assign key= or ckey= after transfer_to() since the transfer_to transfers the client for you. + By setting key or ckey explicitly after transfering the mind with transfer_to you will cause bugs like DCing + the player. + - IMPORTANT NOTE 2, if you want a player to become a ghost, use mob.ghostize() It does all the hard work for you. + - When creating a new mob which will be a new IC character (e.g. putting a shade in a construct or randomly selecting + a ghost to become a xeno during an event). Simply assign the key or ckey like you've always done. + new_mob.key = key + The Login proc will handle making a new mob for that mobtype (including setting up stuff like mind.name). Simple! + However if you want that mind to have any special properties like being a traitor etc you will have to do that + yourself. +*/ + +/datum/mind + var/key + var/name //replaces mob/var/original_name + var/mob/living/current + var/mob/living/original //TODO: remove.not used in any meaningful way ~Carn. First I'll need to tweak the way silicon-mobs handle minds. + var/active = 0 + + var/memory + + var/assigned_role //assigned role is what job you're assigned to when you join the station. + var/playtime_role //if set, overrides your assigned_role for the purpose of playtime awards. Set by IDcomputer when your ID is changed. + var/special_role //special roles are typically reserved for antags or roles like ERT. If you want to avoid a character being automatically announced by the AI, on arrival (becuase they're an off station character or something); ensure that special_role and assigned_role are equal. + var/offstation_role = FALSE //set to true for ERT, deathsquad, abductors, etc, that can go from and to z2 at will and shouldn't be antag targets + var/list/restricted_roles = list() + + var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. + + var/role_alt_title + + var/datum/job/assigned_job + var/list/kills = list() + var/list/datum/objective/objectives = list() + var/list/datum/objective/special_verbs = list() + var/list/targets = list() + + var/has_been_rev = 0//Tracks if this mind has been a rev or not + + var/miming = 0 // Mime's vow of silence + var/list/antag_datums + var/speech_span // What span any body this mind has talks in. + var/datum/faction/faction //associated faction + var/datum/changeling/changeling //changeling holder + var/linglink + var/datum/vampire/vampire //vampire holder + var/datum/abductor/abductor //abductor holder + var/datum/devilinfo/devilinfo //devil holder + + var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state + var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD + var/datum/mindslaves/som //stands for slave or master...hush.. + var/datum/devilinfo/devilinfo //Information about the devil, if any. + var/damnation_type = 0 + var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src + var/hasSoul = TRUE + + var/rev_cooldown = 0 + + var/isholy = FALSE // is this person a chaplain or admin role allowed to use bibles + var/isblessed = FALSE // is this person blessed by a chaplain? + var/num_blessed = 0 // for prayers + + // the world.time since the mob has been brigged, or -1 if not at all + var/brigged_since = -1 + var/suicided = FALSE + + //put this here for easier tracking ingame + var/datum/money_account/initial_account + + //zealot_master is a reference to the mob that converted them into a zealot (for ease of investigation and such) + var/mob/living/carbon/human/zealot_master = null + + var/list/learned_recipes //List of learned recipe TYPES. + +/datum/mind/New(new_key) + key = new_key + soulOwner = src + +/datum/mind/Destroy() + SSticker.minds -= src + if(islist(antag_datums)) + for(var/i in antag_datums) + var/datum/antagonist/antag_datum = i + if(antag_datum.delete_on_mind_deletion) + qdel(i) + antag_datums = null + return ..() + +/datum/mind/proc/transfer_to(mob/living/new_character) + var/datum/atom_hud/antag/hud_to_transfer = antag_hud //we need this because leave_hud() will clear this list + var/mob/living/old_current = current + if(!istype(new_character)) + log_runtime(EXCEPTION("transfer_to(): Some idiot has tried to transfer_to() a non mob/living mob."), src) + if(current) //remove ourself from our old body's mind variable + 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 + + SSnanoui.user_transferred(current, new_character) + + if(new_character.mind) //remove any mind currently in our new body's mind variable + new_character.mind.current = null + current = new_character //link ourself to our new body + new_character.mind = src //and link our new body to ourself + for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body + var/datum/antagonist/A = a + A.on_body_transfer(old_current, current) + transfer_antag_huds(hud_to_transfer) //inherit the antag HUD + transfer_actions(new_character) + + if(active) + new_character.key = key //now transfer the key to link the client to our new body + +/datum/mind/proc/store_memory(new_text) + memory += "[new_text]
    " + +/datum/mind/proc/wipe_memory() + memory = null + +/datum/mind/proc/show_memory(mob/recipient, window = 1) + if(!recipient) + recipient = current + var/output = "[current.real_name]'s Memories:
    " + output += memory + + var/antag_datum_objectives = FALSE + for(var/datum/antagonist/A in antag_datums) + output += A.antag_memory + if(!antag_datum_objectives && LAZYLEN(A.objectives)) + antag_datum_objectives = TRUE + + if(LAZYLEN(objectives) || antag_datum_objectives) + output += "
    Objectives:
    " + output += gen_objective_text() + + if(LAZYLEN(job_objectives)) + output += "
    Job Objectives:
      " + + var/obj_count = 1 + for(var/datum/job_objective/objective in job_objectives) + output += "
    • Task #[obj_count]: [objective.get_description()]
    • " + obj_count++ + output += "
    " + if(window) + recipient << browse(output, "window=memory") + else + to_chat(recipient, "[output]") + +/datum/mind/proc/gen_objective_text(admin = FALSE) + . = "" + var/obj_count = 1 + var/list/all_objectives = list() + for(var/datum/antagonist/A in antag_datums) + all_objectives |= A.objectives + + if(LAZYLEN(all_objectives)) + for(var/datum/objective/objective in all_objectives) + . += "
    Objective #[obj_count++]: [objective.explanation_text]" + + for(var/datum/objective/objective in objectives) + . += "Objective #[obj_count++]: [objective.explanation_text]" + if(admin) + . += " Edit " // Edit + . += "Delete " // Delete + + . += "" // Mark Completed + . += "Toggle Completion" + . += "" + . += "
    " + +/datum/mind/proc/_memory_edit_header(gamemode, list/alt) + . = gamemode + if(SSticker.mode.config_tag == gamemode || (LAZYLEN(alt) && (SSticker.mode.config_tag in alt))) + . = uppertext(.) + . = "[.]: " + +/datum/mind/proc/_memory_edit_role_enabled(role) + . = "|Disabled in Prefs" + if(current && current.client && (role in current.client.prefs.be_special)) + . = "|Enabled in Prefs" + +/datum/mind/proc/memory_edit_implant(mob/living/carbon/human/H) + if(ismindshielded(H)) + . = "Mindshield Implant:Remove|Implanted
    " + else + . = "Mindshield Implant:No Implant|Implant [H.p_them()]!
    " + + +/datum/mind/proc/memory_edit_revolution(mob/living/carbon/human/H) + . = _memory_edit_header("revolution") + if(ismindshielded(H)) + . += "NO|headrev|rev" + else if(src in SSticker.mode.head_revolutionaries) + . += "no|HEADREV|rev" + . += "
    Flash: give" + + var/list/L = current.get_contents() + var/obj/item/flash/flash = locate() in L + if(flash) + if(!flash.broken) + . += "|take." + else + . += "|take|repair." + else + . += "." + + . += " Reequip (gives traitor uplink)." + if(objectives.len==0) + . += "
    Objectives are empty! Set to kill all heads." + else if(src in SSticker.mode.revolutionaries) + . += "no|headrev|REV" + else + . += "NO|headrev|rev" + + . += _memory_edit_role_enabled(ROLE_REV) + +/datum/mind/proc/memory_edit_cult(mob/living/carbon/human/H) + . = _memory_edit_header("cult") + if(src in SSticker.mode.cult) + . += "no|CULTIST" + . += "
    Give tome|equip." + else + . += "NO|cultist" + + . += _memory_edit_role_enabled(ROLE_CULTIST) + +/datum/mind/proc/memory_edit_wizard(mob/living/carbon/human/H) + . = _memory_edit_header("wizard") + if(src in SSticker.mode.wizards) + . += "WIZARD|no" + . += "
    To lair, undress, dress up, let choose name." + if(objectives.len==0) + . += "
    Objectives are empty! Randomize!" + else + . += "wizard|NO" + + . += _memory_edit_role_enabled(ROLE_WIZARD) + +/datum/mind/proc/memory_edit_changeling(mob/living/carbon/human/H) + . = _memory_edit_header("changeling", list("traitorchan")) + if(src in SSticker.mode.changelings) + . += "CHANGELING|no" + if(objectives.len==0) + . += "
    Objectives are empty! Randomize!" + if(changeling && changeling.absorbed_dna.len && (current.real_name != changeling.absorbed_dna[1])) + . += "
    Transform to initial appearance." + else + . += "changeling|NO" + + . += _memory_edit_role_enabled(ROLE_CHANGELING) + +/datum/mind/proc/memory_edit_vampire(mob/living/carbon/human/H) + . = _memory_edit_header("vampire", list("traitorvamp")) + if(src in SSticker.mode.vampires) + . += "VAMPIRE|no" + if(objectives.len==0) + . += "
    Objectives are empty! Randomize!" + else + . += "vampire|NO" + + . += _memory_edit_role_enabled(ROLE_VAMPIRE) + /** Enthralled ***/ + . += "
    enthralled: " + if(src in SSticker.mode.vampire_enthralled) + . += "THRALL|no" + else + . += "thrall|NO" + +/datum/mind/proc/memory_edit_nuclear(mob/living/carbon/human/H) + . = _memory_edit_header("nuclear") + if(src in SSticker.mode.syndicates) + . += "OPERATIVE|no" + . += "
    To shuttle, undress, dress up." + var/code + for(var/obj/machinery/nuclearbomb/bombue in GLOB.machines) + if(length(bombue.r_code) <= 5 && bombue.r_code != "LOLNO" && bombue.r_code != "ADMIN") + code = bombue.r_code + break + if(code) + . += " Code is [code]. tell the code." + else + . += "operative|NO" + + . += _memory_edit_role_enabled(ROLE_OPERATIVE) + +/datum/mind/proc/memory_edit_shadowling(mob/living/carbon/human/H) + . = _memory_edit_header("shadowling") + if(src in SSticker.mode.shadows) + . += "SHADOWLING|thrall|no" + else if(src in SSticker.mode.shadowling_thralls) + . += "Shadowling|THRALL|no" + else + . += "shadowling|thrall|NO" + + . += _memory_edit_role_enabled(ROLE_SHADOWLING) + +/datum/mind/proc/memory_edit_abductor(mob/living/carbon/human/H) + . = _memory_edit_header("abductor") + if(src in SSticker.mode.abductors) + . += "ABDUCTOR|no" + . += "|undress|equip" + else + . += "abductor|NO" + + . += _memory_edit_role_enabled(ROLE_ABDUCTOR) + +/datum/mind/proc/memory_edit_devil(mob/living/H) + . = _memory_edit_header("devil", list("devilagents")) + if(src in SSticker.mode.devils) + if(!devilinfo) + . += "No devilinfo found! Yell at a coder!" + else if(!devilinfo.ascendable) + . += "DEVIL|Ascendable Devil|sintouched|no" + else + . += "DEVIL|ASCENDABLE DEVIL|sintouched|no" + else if(src in SSticker.mode.sintouched) + . += "devil|Ascendable Devil|SINTOUCHED|no" + else + . += "devil|Ascendable Devil|sintouched|NO" + + . += _memory_edit_role_enabled(ROLE_DEVIL) + +/datum/mind/proc/memory_edit_eventmisc(mob/living/H) + . = _memory_edit_header("event", list()) + if(src in SSticker.mode.eventmiscs) + . += "YES|no" + else + . += "Event Role|NO" + +/datum/mind/proc/memory_edit_traitor() + . = _memory_edit_header("traitor", list("traitorchan", "traitorvamp")) + if(has_antag_datum(/datum/antagonist/traitor)) + . += "TRAITOR|no" + if(objectives.len==0) + . += "
    Objectives are empty! Randomize!" + else + . += "traitor|NO" + + . += _memory_edit_role_enabled(ROLE_TRAITOR) + // Mindslave + . += "
    mindslaved: " + if(has_antag_datum(/datum/antagonist/mindslave)) + . += "MINDSLAVE|no" + else + . += "mindslave|NO" + +/datum/mind/proc/memory_edit_silicon() + . = "Silicon: " + var/mob/living/silicon/robot/robot = current + if(istype(robot) && robot.emagged) + . += "
    Cyborg: Is emagged! Unemag!
    0th law: [robot.laws.zeroth_law]" + var/mob/living/silicon/ai/ai = current + if(istype(ai) && ai.connected_robots.len) + var/n_e_robots = 0 + for(var/mob/living/silicon/robot/R in ai.connected_robots) + if(R.emagged) + n_e_robots++ + . += "
    [n_e_robots] of [ai.connected_robots.len] slaved cyborgs are emagged. Unemag" + +/datum/mind/proc/memory_edit_uplink() + . = "" + if(ishuman(current) && ((src in SSticker.mode.head_revolutionaries) || \ + (has_antag_datum(/datum/antagonist/traitor)) || \ + (src in SSticker.mode.syndicates))) + . = "Uplink: give" + var/obj/item/uplink/hidden/suplink = find_syndicate_uplink() + var/crystals + if(suplink) + crystals = suplink.uses + if(suplink) + . += "|take" + if(usr.client.holder.rights & (R_SERVER|R_EVENT)) + . += ", [crystals] crystals" + else + . += ", [crystals] crystals" + . += "." //hiel grammar + // ^ whoever left this comment is literally a grammar nazi. stalin better. in russia grammar correct you. + +/datum/mind/proc/edit_memory() + if(!SSticker || !SSticker.mode) + alert("Not before round-start!", "Alert") + return + + var/out = "[name][(current && (current.real_name != name))?" (as [current.real_name])" : ""]
    " + out += "Mind currently owned by key: [key] [active ? "(synced)" : "(not synced)"]
    " + out += "Assigned role: [assigned_role]. Edit
    " + out += "Factions and special roles:
    " + + var/list/sections = list( + "implant", + "revolution", + "cult", + "wizard", + "changeling", + "vampire", // "traitorvamp", + "nuclear", + "traitor", // "traitorchan", + ) + var/mob/living/carbon/human/H = current + if(ishuman(current)) + /** Impanted**/ + sections["implant"] = memory_edit_implant(H) + /** REVOLUTION ***/ + sections["revolution"] = memory_edit_revolution(H) + /** CULT ***/ + sections["cult"] = memory_edit_cult(H) + /** WIZARD ***/ + sections["wizard"] = memory_edit_wizard(H) + /** CHANGELING ***/ + sections["changeling"] = memory_edit_changeling(H) + /** VAMPIRE ***/ + sections["vampire"] = memory_edit_vampire(H) + /** NUCLEAR ***/ + sections["nuclear"] = memory_edit_nuclear(H) + /** SHADOWLING **/ + sections["shadowling"] = memory_edit_shadowling(H) + /** Abductors **/ + sections["abductor"] = memory_edit_abductor(H) + /** DEVIL ***/ + var/static/list/devils_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/carbon/true_devil, /mob/living/silicon/robot)) + if(is_type_in_typecache(current, devils_typecache)) + sections["devil"] = memory_edit_devil(H) + sections["eventmisc"] = memory_edit_eventmisc(H) + /** TRAITOR ***/ + sections["traitor"] = memory_edit_traitor() + /** SILICON ***/ + if(issilicon(current)) + sections["silicon"] = memory_edit_silicon() + /* + This prioritizes antags relevant to the current round to make them appear at the top of the panel. + Traitorchan and traitorvamp are snowflaked in because they have multiple sections. + */ + if(SSticker.mode.config_tag == "traitorchan") + if(sections["traitor"]) + out += sections["traitor"] + "
    " + if(sections["changeling"]) + out += sections["changeling"] + "
    " + sections -= "traitor" + sections -= "changeling" + // Elif technically unnecessary but it makes the following else look better + else if(SSticker.mode.config_tag == "traitorvamp") + if(sections["traitor"]) + out += sections["traitor"] + "
    " + if(sections["vampire"]) + out += sections["vampire"] + "
    " + sections -= "traitor" + sections -= "vampire" + else + if(sections[SSticker.mode.config_tag]) + out += sections[SSticker.mode.config_tag] + "
    " + sections -= SSticker.mode.config_tag + + for(var/i in sections) + if(sections[i]) + out += sections[i] + "
    " + + out += memory_edit_uplink() + out += "
    " + + out += "Memory:
    " + out += memory + out += "
    Edit memory
    " + out += "Objectives:
    " + if(objectives.len == 0) + out += "EMPTY
    " + else + out += gen_objective_text(admin = TRUE) + out += "Add objective

    " + out += "Announce objectives

    " + usr << browse(out, "window=edit_memory[src];size=500x500") + +/datum/mind/Topic(href, href_list) + if(!check_rights(R_ADMIN)) + return + + if(href_list["role_edit"]) + var/new_role = input("Select new role", "Assigned role", assigned_role) as null|anything in GLOB.joblist + if(!new_role) + return + assigned_role = new_role + log_admin("[key_name(usr)] has changed [key_name(current)]'s assigned role to [assigned_role]") + message_admins("[key_name_admin(usr)] has changed [key_name_admin(current)]'s assigned role to [assigned_role]") + + else if(href_list["memory_edit"]) + var/messageinput = input("Write new memory", "Memory", memory) as null|message + if(isnull(messageinput)) + return + var/new_memo = copytext(messageinput, 1,MAX_MESSAGE_LEN) + var/confirmed = alert(usr, "Are you sure you want to edit their memory? It will wipe out their original memory!", "Edit Memory", "Yes", "No") + if(confirmed == "Yes") // Because it is too easy to accidentally wipe someone's memory + memory = new_memo + log_admin("[key_name(usr)] has edited [key_name(current)]'s memory") + message_admins("[key_name_admin(usr)] has edited [key_name_admin(current)]'s memory") + + else if(href_list["obj_edit"] || href_list["obj_add"]) + var/datum/objective/objective + var/objective_pos + var/def_value + + if(href_list["obj_edit"]) + objective = locate(href_list["obj_edit"]) + if(!objective) + return + objective_pos = objectives.Find(objective) + + //Text strings are easy to manipulate. Revised for simplicity. + var/temp_obj_type = "[objective.type]"//Convert path into a text string. + def_value = copytext(temp_obj_type, 19)//Convert last part of path into an objective keyword. + if(!def_value)//If it's a custom objective, it will be an empty string. + def_value = "custom" + + var/new_obj_type = input("Select objective type:", "Objective type", def_value) as null|anything in list("assassinate", "blood", "debrain", "protect", "prevent", "brig", "hijack", "escape", "survive", "steal", "download", "nuclear", "capture", "absorb", "destroy", "maroon", "identity theft", "custom") + if(!new_obj_type) + return + + var/datum/objective/new_objective = null + + switch(new_obj_type) + if("assassinate","protect","debrain", "brig", "maroon") + //To determine what to name the objective in explanation text. + var/objective_type_capital = uppertext(copytext(new_obj_type, 1,2))//Capitalize first letter. + var/objective_type_text = copytext(new_obj_type, 2)//Leave the rest of the text. + var/objective_type = "[objective_type_capital][objective_type_text]"//Add them together into a text string. + + var/list/possible_targets = list() + for(var/datum/mind/possible_target in SSticker.minds) + if((possible_target != src) && istype(possible_target.current, /mob/living/carbon/human)) + possible_targets += possible_target.current + + var/mob/def_target = null + var/objective_list[] = list(/datum/objective/assassinate, /datum/objective/protect, /datum/objective/debrain) + if(objective&&(objective.type in objective_list) && objective:target) + def_target = objective:target.current + possible_targets = sortAtom(possible_targets) + possible_targets += "Free objective" + + var/new_target = input("Select target:", "Objective target", def_target) as null|anything in possible_targets + if(!new_target) + return + + var/objective_path = text2path("/datum/objective/[new_obj_type]") + if(new_target == "Free objective") + new_objective = new objective_path + new_objective.owner = src + new_objective:target = null + new_objective.explanation_text = "Free objective" + else + new_objective = new objective_path + new_objective.owner = src + new_objective:target = new_target:mind + //Will display as special role if assigned mode is equal to special role.. Ninjas/commandos/nuke ops. + new_objective.explanation_text = "[objective_type] [new_target:real_name], the [new_target:mind:assigned_role == new_target:mind:special_role ? (new_target:mind:special_role) : (new_target:mind:assigned_role)]." + + if("destroy") + var/list/possible_targets = active_ais(1) + if(possible_targets.len) + var/mob/new_target = input("Select target:", "Objective target") as null|anything in possible_targets + new_objective = new /datum/objective/destroy + new_objective.target = new_target.mind + new_objective.owner = src + new_objective.explanation_text = "Destroy [new_target.name], the experimental AI." + else + to_chat(usr, "No active AIs with minds") + + if("prevent") + new_objective = new /datum/objective/block + new_objective.owner = src + + if("hijack") + new_objective = new /datum/objective/hijack + new_objective.owner = src + + if("escape") + new_objective = new /datum/objective/escape + new_objective.owner = src + + if("survive") + new_objective = new /datum/objective/survive + new_objective.owner = src + + if("die") + new_objective = new /datum/objective/die + new_objective.owner = src + + if("nuclear") + new_objective = new /datum/objective/nuclear + new_objective.owner = src + + if("steal") + if(!istype(objective, /datum/objective/steal)) + new_objective = new /datum/objective/steal + new_objective.owner = src + else + new_objective = objective + var/datum/objective/steal/steal = new_objective + if(!steal.select_target()) + return + + if("download","capture","absorb", "blood") + var/def_num + if(objective&&objective.type==text2path("/datum/objective/[new_obj_type]")) + def_num = objective.target_amount + + var/target_number = input("Input target number:", "Objective", def_num) as num|null + if(isnull(target_number))//Ordinarily, you wouldn't need isnull. In this case, the value may already exist. + return + + switch(new_obj_type) + if("download") + new_objective = new /datum/objective/download + new_objective.explanation_text = "Download [target_number] research levels." + if("capture") + new_objective = new /datum/objective/capture + new_objective.explanation_text = "Accumulate [target_number] capture points." + if("absorb") + new_objective = new /datum/objective/absorb + new_objective.explanation_text = "Absorb [target_number] compatible genomes." + if("blood") + new_objective = new /datum/objective/blood + new_objective.explanation_text = "Accumulate at least [target_number] total units of blood." + new_objective.owner = src + new_objective.target_amount = target_number + + if("identity theft") + var/list/possible_targets = list() + for(var/datum/mind/possible_target in SSticker.minds) + if((possible_target != src) && ishuman(possible_target.current)) + possible_targets += possible_target + possible_targets = sortAtom(possible_targets) + possible_targets += "Free objective" + var/new_target = input("Select target:", "Objective target") as null|anything in possible_targets + if(!new_target) + return + var/datum/mind/targ = new_target + if(!istype(targ)) + log_runtime(EXCEPTION("Invalid target for identity theft objective, cancelling"), src) + return + new_objective = new /datum/objective/escape/escape_with_identity + new_objective.owner = src + new_objective.target = new_target + new_objective.explanation_text = "Escape on the shuttle or an escape pod with the identity of [targ.current.real_name], the [targ.assigned_role] while wearing [targ.current.p_their()] identification card." + if("custom") + var/expl = sanitize(copytext(input("Custom objective:", "Objective", objective ? objective.explanation_text : "") as text|null,1,MAX_MESSAGE_LEN)) + if(!expl) + return + new_objective = new /datum/objective + new_objective.owner = src + new_objective.explanation_text = expl + + if(!new_objective) + return + + if(objective) + objectives -= objective + qdel(objective) + objectives.Insert(objective_pos, new_objective) + else + objectives += new_objective + + log_admin("[key_name(usr)] has updated [key_name(current)]'s objectives: [new_objective]") + message_admins("[key_name_admin(usr)] has updated [key_name_admin(current)]'s objectives: [new_objective]") + + else if(href_list["obj_delete"]) + var/datum/objective/objective = locate(href_list["obj_delete"]) + if(!istype(objective)) + return + objectives -= objective + + log_admin("[key_name(usr)] has removed one of [key_name(current)]'s objectives: [objective]") + message_admins("[key_name_admin(usr)] has removed one of [key_name_admin(current)]'s objectives: [objective]") + qdel(objective) + + else if(href_list["obj_completed"]) + var/datum/objective/objective = locate(href_list["obj_completed"]) + if(!istype(objective)) + return + objective.completed = !objective.completed + + log_admin("[key_name(usr)] has toggled the completion of one of [key_name(current)]'s objectives") + message_admins("[key_name_admin(usr)] has toggled the completion of one of [key_name_admin(current)]'s objectives") + + else if(href_list["implant"]) + var/mob/living/carbon/human/H = current + + switch(href_list["implant"]) + if("remove") + for(var/obj/item/implant/mindshield/I in H.contents) + if(I && I.implanted) + qdel(I) + to_chat(H, "Your mindshield implant has been deactivated.") + log_admin("[key_name(usr)] has deactivated [key_name(current)]'s mindshield implant") + message_admins("[key_name_admin(usr)] has deactivated [key_name_admin(current)]'s mindshield implant") + if("add") + var/obj/item/implant/mindshield/L = new/obj/item/implant/mindshield(H) + L.implant(H) + + log_admin("[key_name(usr)] has given [key_name(current)] a mindshield implant") + message_admins("[key_name_admin(usr)] has given [key_name_admin(current)] a mindshield implant") + + to_chat(H, "You somehow have become the recepient of a mindshield transplant, and it just activated!") + if(src in SSticker.mode.revolutionaries) + special_role = null + SSticker.mode.revolutionaries -= src + to_chat(src, "The nanobots in the mindshield implant remove all thoughts about being a revolutionary. Get back to work!") + if(src in SSticker.mode.head_revolutionaries) + special_role = null + SSticker.mode.head_revolutionaries -=src + to_chat(src, "The nanobots in the mindshield implant remove all thoughts about being a revolutionary. Get back to work!") + if(src in SSticker.mode.cult) + SSticker.mode.cult -= src + SSticker.mode.update_cult_icons_removed(src) + special_role = null + var/datum/game_mode/cult/cult = SSticker.mode + if(istype(cult)) + cult.memorize_cult_objectives(src) + to_chat(current, "The nanobots in the mindshield implant remove all thoughts about being in a cult. Have a productive day!") + memory = "" + + else if(href_list["revolution"]) + + switch(href_list["revolution"]) + if("clear") + if(src in SSticker.mode.revolutionaries) + SSticker.mode.revolutionaries -= src + to_chat(current, "You have been brainwashed! You are no longer a revolutionary!") + SSticker.mode.update_rev_icons_removed(src) + special_role = null + if(src in SSticker.mode.head_revolutionaries) + SSticker.mode.head_revolutionaries -= src + to_chat(current, "You have been brainwashed! You are no longer a head revolutionary!") + SSticker.mode.update_rev_icons_removed(src) + special_role = null + log_admin("[key_name(usr)] has de-rev'd [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-rev'd [key_name_admin(current)]") + + if("rev") + if(src in SSticker.mode.head_revolutionaries) + SSticker.mode.head_revolutionaries -= src + SSticker.mode.update_rev_icons_removed(src) + to_chat(current, "Revolution has been disappointed of your leadership traits! You are a regular revolutionary now!") + else if(!(src in SSticker.mode.revolutionaries)) + to_chat(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!") + else + return + SSticker.mode.revolutionaries += src + SSticker.mode.update_rev_icons_added(src) + special_role = SPECIAL_ROLE_REV + log_admin("[key_name(usr)] has rev'd [key_name(current)]") + message_admins("[key_name_admin(usr)] has rev'd [key_name_admin(current)]") + + if("headrev") + if(src in SSticker.mode.revolutionaries) + SSticker.mode.revolutionaries -= src + SSticker.mode.update_rev_icons_removed(src) + to_chat(current, "You have proven your devotion to revolution! You are a head revolutionary now!") + else if(!(src in SSticker.mode.head_revolutionaries)) + to_chat(current, "You are a member of the revolutionaries' leadership now!") + else + return + if(SSticker.mode.head_revolutionaries.len>0) + // copy targets + var/datum/mind/valid_head = locate() in SSticker.mode.head_revolutionaries + if(valid_head) + for(var/datum/objective/mutiny/O in valid_head.objectives) + var/datum/objective/mutiny/rev_obj = new + rev_obj.owner = src + rev_obj.target = O.target + rev_obj.explanation_text = "Assassinate [O.target.name], the [O.target.assigned_role]." + objectives += rev_obj + SSticker.mode.greet_revolutionary(src,0) + SSticker.mode.head_revolutionaries += src + SSticker.mode.update_rev_icons_added(src) + special_role = SPECIAL_ROLE_HEAD_REV + log_admin("[key_name(usr)] has head-rev'd [key_name(current)]") + message_admins("[key_name_admin(usr)] has head-rev'd [key_name_admin(current)]") + + if("autoobjectives") + SSticker.mode.forge_revolutionary_objectives(src) + SSticker.mode.greet_revolutionary(src,0) + log_admin("[key_name(usr)] has automatically forged revolutionary objectives for [key_name(current)]") + message_admins("[key_name_admin(usr)] has automatically forged revolutionary objectives for [key_name_admin(current)]") + + if("flash") + if(!SSticker.mode.equip_revolutionary(current)) + to_chat(usr, "Spawning flash failed!") + log_admin("[key_name(usr)] has given [key_name(current)] a flash") + message_admins("[key_name_admin(usr)] has given [key_name_admin(current)] a flash") + + if("takeflash") + var/list/L = current.get_contents() + var/obj/item/flash/flash = locate() in L + if(!flash) + to_chat(usr, "Deleting flash failed!") + qdel(flash) + log_admin("[key_name(usr)] has taken [key_name(current)]'s flash") + message_admins("[key_name_admin(usr)] has taken [key_name_admin(current)]'s flash") + + if("repairflash") + var/list/L = current.get_contents() + var/obj/item/flash/flash = locate() in L + if(!flash) + to_chat(usr, "Repairing flash failed!") + else + flash.broken = 0 + log_admin("[key_name(usr)] has repaired [key_name(current)]'s flash") + message_admins("[key_name_admin(usr)] has repaired [key_name_admin(current)]'s flash") + + if("reequip") + var/list/L = current.get_contents() + var/obj/item/flash/flash = locate() in L + qdel(flash) + take_uplink() + var/fail = 0 + var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) + fail |= !T.equip_traitor(src) + fail |= !SSticker.mode.equip_revolutionary(current) + if(fail) + to_chat(usr, "Reequipping revolutionary goes wrong!") + return + log_admin("[key_name(usr)] has equipped [key_name(current)] as a revolutionary") + message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a revolutionary") + + else if(href_list["cult"]) + switch(href_list["cult"]) + if("clear") + if(src in SSticker.mode.cult) + SSticker.mode.remove_cultist(src) + special_role = null + log_admin("[key_name(usr)] has de-culted [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-culted [key_name_admin(current)]") + if("cultist") + if(!(src in SSticker.mode.cult)) + SSticker.mode.add_cultist(src) + special_role = SPECIAL_ROLE_CULTIST + to_chat(current, "You catch a glimpse of the Realm of [SSticker.cultdat.entity_name], [SSticker.cultdat.entity_title3]. You now see how flimsy the world is, you see that it should be open to the knowledge of [SSticker.cultdat.entity_name].") + to_chat(current, "Assist your new compatriots in their dark dealings. Their goal is yours, and yours is theirs. You serve [SSticker.cultdat.entity_title2] above all else. Bring It back.") + log_admin("[key_name(usr)] has culted [key_name(current)]") + message_admins("[key_name_admin(usr)] has culted [key_name_admin(current)]") + if(!summon_spots.len) + while(summon_spots.len < SUMMON_POSSIBILITIES) + var/area/summon = pick(return_sorted_areas() - summon_spots) + if(summon && is_station_level(summon.z) && summon.valid_territory) + summon_spots += summon + if("tome") + var/mob/living/carbon/human/H = current + if(istype(H)) + var/obj/item/tome/T = new(H) + + var/list/slots = list ( + "backpack" = slot_in_backpack, + "left pocket" = slot_l_store, + "right pocket" = slot_r_store, + "left hand" = slot_l_hand, + "right hand" = slot_r_hand, + ) + var/where = H.equip_in_one_of_slots(T, slots) + if(!where) + to_chat(usr, "Spawning tome failed!") + qdel(T) + else + to_chat(H, "A tome, a message from your new master, appears in your [where].") + log_admin("[key_name(usr)] has spawned a tome for [key_name(current)]") + message_admins("[key_name_admin(usr)] has spawned a tome for [key_name_admin(current)]") + + if("equip") + if(!SSticker.mode.equip_cultist(current)) + to_chat(usr, "Spawning equipment failed!") + log_admin("[key_name(usr)] has equipped [key_name(current)] as a cultist") + message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a cultist") + + else if(href_list["wizard"]) + + switch(href_list["wizard"]) + if("clear") + if(src in SSticker.mode.wizards) + SSticker.mode.wizards -= src + special_role = null + current.spellremove(current) + current.faction = list("Station") + SSticker.mode.update_wiz_icons_removed(src) + to_chat(current, "You have been brainwashed! You are no longer a wizard!") + log_admin("[key_name(usr)] has de-wizarded [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-wizarded [key_name_admin(current)]") + if("wizard") + if(!(src in SSticker.mode.wizards)) + SSticker.mode.wizards += src + special_role = SPECIAL_ROLE_WIZARD + //ticker.mode.learn_basic_spells(current) + SSticker.mode.update_wiz_icons_added(src) + SEND_SOUND(current, 'sound/ambience/antag/ragesmages.ogg') + to_chat(current, "You are a Space Wizard!") + current.faction = list("wizard") + log_admin("[key_name(usr)] has wizarded [key_name(current)]") + message_admins("[key_name_admin(usr)] has wizarded [key_name_admin(current)]") + if("lair") + current.forceMove(pick(wizardstart)) + log_admin("[key_name(usr)] has moved [key_name(current)] to the wizard's lair") + message_admins("[key_name_admin(usr)] has moved [key_name_admin(current)] to the wizard's lair") + if("dressup") + SSticker.mode.equip_wizard(current) + log_admin("[key_name(usr)] has equipped [key_name(current)] as a wizard") + message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a wizard") + if("name") + SSticker.mode.name_wizard(current) + log_admin("[key_name(usr)] has allowed wizard [key_name(current)] to name themselves") + message_admins("[key_name_admin(usr)] has allowed wizard [key_name_admin(current)] to name themselves") + if("autoobjectives") + SSticker.mode.forge_wizard_objectives(src) + to_chat(usr, "The objectives for wizard [key] have been generated. You can edit them and announce manually.") + log_admin("[key_name(usr)] has automatically forged wizard objectives for [key_name(current)]") + message_admins("[key_name_admin(usr)] has automatically forged wizard objectives for [key_name_admin(current)]") + + + else if(href_list["changeling"]) + switch(href_list["changeling"]) + if("clear") + if(src in SSticker.mode.changelings) + SSticker.mode.changelings -= src + special_role = null + if(changeling) + current.remove_changeling_powers() + qdel(changeling) + changeling = null + SSticker.mode.update_change_icons_removed(src) + to_chat(current, "You grow weak and lose your powers! You are no longer a changeling and are stuck in your current form!") + log_admin("[key_name(usr)] has de-changelinged [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-changelinged [key_name_admin(current)]") + if("changeling") + if(!(src in SSticker.mode.changelings)) + SSticker.mode.changelings += src + SSticker.mode.grant_changeling_powers(current) + SSticker.mode.update_change_icons_added(src) + special_role = SPECIAL_ROLE_CHANGELING + SEND_SOUND(current, 'sound/ambience/antag/ling_aler.ogg') + to_chat(current, "Your powers have awoken. A flash of memory returns to us... we are a changeling!") + log_admin("[key_name(usr)] has changelinged [key_name(current)]") + message_admins("[key_name_admin(usr)] has changelinged [key_name_admin(current)]") + + if("autoobjectives") + SSticker.mode.forge_changeling_objectives(src) + to_chat(usr, "The objectives for changeling [key] have been generated. You can edit them and announce manually.") + log_admin("[key_name(usr)] has automatically forged objectives for [key_name(current)]") + message_admins("[key_name_admin(usr)] has automatically forged objectives for [key_name_admin(current)]") + + if("initialdna") + if(!changeling || !changeling.absorbed_dna.len) + to_chat(usr, "Resetting DNA failed!") + else + current.dna = changeling.absorbed_dna[1] + current.real_name = current.dna.real_name + current.UpdateAppearance() + domutcheck(current, null) + log_admin("[key_name(usr)] has reset [key_name(current)]'s DNA") + message_admins("[key_name_admin(usr)] has reset [key_name_admin(current)]'s DNA") + + else if(href_list["vampire"]) + switch(href_list["vampire"]) + if("clear") + if(src in SSticker.mode.vampires) + SSticker.mode.vampires -= src + special_role = null + if(vampire) + vampire.remove_vampire_powers() + qdel(vampire) + vampire = null + SSticker.mode.update_vampire_icons_removed(src) + to_chat(current, "You grow weak and lose your powers! You are no longer a vampire and are stuck in your current form!") + log_admin("[key_name(usr)] has de-vampired [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-vampired [key_name_admin(current)]") + if("vampire") + if(!(src in SSticker.mode.vampires)) + SSticker.mode.vampires += src + SSticker.mode.grant_vampire_powers(current) + SSticker.mode.update_vampire_icons_added(src) + var/datum/mindslaves/slaved = new() + slaved.masters += src + som = slaved //we MIGT want to mindslave someone + special_role = SPECIAL_ROLE_VAMPIRE + SEND_SOUND(current, 'sound/ambience/antag/vampalert.ogg') + to_chat(current, "Your powers have awoken. Your lust for blood grows... You are a Vampire!") + log_admin("[key_name(usr)] has vampired [key_name(current)]") + message_admins("[key_name_admin(usr)] has vampired [key_name_admin(current)]") + + if("autoobjectives") + SSticker.mode.forge_vampire_objectives(src) + to_chat(usr, "The objectives for vampire [key] have been generated. You can edit them and announce manually.") + log_admin("[key_name(usr)] has automatically forged objectives for [key_name(current)]") + message_admins("[key_name_admin(usr)] has automatically forged objectives for [key_name_admin(current)]") + + else if(href_list["vampthrall"]) + switch(href_list["vampthrall"]) + if("clear") + if(src in SSticker.mode.vampire_enthralled) + SSticker.mode.remove_vampire_mind(src) + log_admin("[key_name(usr)] has de-vampthralled [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-vampthralled [key_name_admin(current)]") + + else if(href_list["nuclear"]) + var/mob/living/carbon/human/H = current + + switch(href_list["nuclear"]) + if("clear") + if(src in SSticker.mode.syndicates) + SSticker.mode.syndicates -= src + SSticker.mode.update_synd_icons_removed(src) + special_role = null + for(var/datum/objective/nuclear/O in objectives) + objectives-=O + qdel(O) + to_chat(current, "You have been brainwashed! You are no longer a syndicate operative!") + log_admin("[key_name(usr)] has de-nuke op'd [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-nuke op'd [key_name_admin(current)]") + if("nuclear") + if(!(src in SSticker.mode.syndicates)) + SSticker.mode.syndicates += src + SSticker.mode.update_synd_icons_added(src) + if(SSticker.mode.syndicates.len==1) + SSticker.mode.prepare_syndicate_leader(src) + else + current.real_name = "[syndicate_name()] Operative #[SSticker.mode.syndicates.len-1]" + special_role = SPECIAL_ROLE_NUKEOPS + to_chat(current, "You are a [syndicate_name()] agent!") + SSticker.mode.forge_syndicate_objectives(src) + SSticker.mode.greet_syndicate(src) + log_admin("[key_name(usr)] has nuke op'd [key_name(current)]") + message_admins("[key_name_admin(usr)] has nuke op'd [key_name_admin(current)]") + if("lair") + current.forceMove(get_turf(locate("landmark*Syndicate-Spawn"))) + log_admin("[key_name(usr)] has moved [key_name(current)] to the nuclear operative spawn") + message_admins("[key_name_admin(usr)] has moved [key_name_admin(current)] to the nuclear operative spawn") + if("dressup") + qdel(H.belt) + qdel(H.back) + qdel(H.l_ear) + qdel(H.r_ear) + qdel(H.gloves) + qdel(H.head) + qdel(H.shoes) + qdel(H.wear_id) + qdel(H.wear_pda) + qdel(H.wear_suit) + qdel(H.w_uniform) + + if(!SSticker.mode.equip_syndicate(current)) + to_chat(usr, "Equipping a syndicate failed!") + return + SSticker.mode.update_syndicate_id(current.mind, SSticker.mode.syndicates.len == 1) + log_admin("[key_name(usr)] has equipped [key_name(current)] as a nuclear operative") + message_admins("[key_name_admin(usr)] has equipped [key_name_admin(current)] as a nuclear operative") + + if("tellcode") + var/code + for(var/obj/machinery/nuclearbomb/bombue in GLOB.machines) + if(length(bombue.r_code) <= 5 && bombue.r_code != "LOLNO" && bombue.r_code != "ADMIN") + code = bombue.r_code + break + if(code) + store_memory("Syndicate Nuclear Bomb Code: [code]", 0, 0) + to_chat(current, "The nuclear authorization code is: [code]") + log_admin("[key_name(usr)] has given [key_name(current)] the nuclear authorization code") + message_admins("[key_name_admin(usr)] has given [key_name_admin(current)] the nuclear authorization code") + else + to_chat(usr, "No valid nuke found!") + + else if(href_list["eventmisc"]) + switch(href_list["eventmisc"]) + if("clear") + if(src in SSticker.mode.eventmiscs) + SSticker.mode.eventmiscs -= src + SSticker.mode.update_eventmisc_icons_removed(src) + special_role = null + message_admins("[key_name_admin(usr)] has de-eventantag'ed [current].") + log_admin("[key_name(usr)] has de-eventantag'ed [current].") + if("eventmisc") + SSticker.mode.eventmiscs += src + special_role = SPECIAL_ROLE_EVENTMISC + SSticker.mode.update_eventmisc_icons_added(src) + message_admins("[key_name_admin(usr)] has eventantag'ed [current].") + log_admin("[key_name(usr)] has eventantag'ed [current].") + else if(href_list["devil"]) + switch(href_list["devil"]) + if("clear") + if(src in SSticker.mode.devils) + if(istype(current,/mob/living/carbon/true_devil/)) + to_chat(usr,"This cannot be used on true or arch-devils.") + else + SSticker.mode.devils -= src + SSticker.mode.update_devil_icons_removed(src) + special_role = null + to_chat(current,"Your infernal link has been severed! You are no longer a devil!") + RemoveSpell(/obj/effect/proc_holder/spell/targeted/infernal_jaunt) + RemoveSpell(/obj/effect/proc_holder/spell/fireball/hellish) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_contract) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/ascended) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/violin) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_dancefloor) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/sintouch) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/sintouch/ascended) + message_admins("[key_name_admin(usr)] has de-devil'ed [current].") + if(issilicon(current)) + var/mob/living/silicon/S = current + S.laws.clear_sixsixsix_laws() + devilinfo = null + log_admin("[key_name(usr)] has de-devil'ed [current].") + else if(src in SSticker.mode.sintouched) + SSticker.mode.sintouched -= src + message_admins("[key_name_admin(usr)] has de-sintouch'ed [current].") + log_admin("[key_name(usr)] has de-sintouch'ed [current].") + if("devil") + if(devilinfo) + devilinfo.ascendable = FALSE + message_admins("[key_name_admin(usr)] has made [current] unable to ascend as a devil.") + log_admin("[key_name_admin(usr)] has made [current] unable to ascend as a devil.") + return + if(!ishuman(current) && !isrobot(current)) + to_chat(usr, "This only works on humans and cyborgs!") + return + SSticker.mode.devils += src + special_role = "devil" + SSticker.mode.update_devil_icons_added(src) + SSticker.mode.finalize_devil(src, FALSE) + SSticker.mode.forge_devil_objectives(src, 2) + SSticker.mode.greet_devil(src) + message_admins("[key_name_admin(usr)] has devil'ed [current].") + log_admin("[key_name(usr)] has devil'ed [current].") + if("ascendable_devil") + if(devilinfo) + devilinfo.ascendable = TRUE + message_admins("[key_name_admin(usr)] has made [current] able to ascend as a devil.") + log_admin("[key_name_admin(usr)] has made [current] able to ascend as a devil.") + return + if(!ishuman(current) && !isrobot(current)) + to_chat(usr, "This only works on humans and cyborgs!") + return + SSticker.mode.devils += src + special_role = "devil" + SSticker.mode.update_devil_icons_added(src) + SSticker.mode.finalize_devil(src, TRUE) + SSticker.mode.forge_devil_objectives(src, 2) + SSticker.mode.greet_devil(src) + message_admins("[key_name_admin(usr)] has devil'ed [current]. The devil has been marked as ascendable.") + log_admin("[key_name(usr)] has devil'ed [current]. The devil has been marked as ascendable.") + if("sintouched") + var/mob/living/carbon/human/H = current + H.influenceSin() + message_admins("[key_name_admin(usr)] has sintouch'ed [current].") + log_admin("[key_name(usr)] has sintouch'ed [current].") + + else if(href_list["traitor"]) + switch(href_list["traitor"]) + if("clear") + if(has_antag_datum(/datum/antagonist/traitor)) + to_chat(current, "You have been brainwashed! You are no longer a traitor!") + remove_antag_datum(/datum/antagonist/traitor) + log_admin("[key_name(usr)] has de-traitored [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-traitored [key_name_admin(current)]") + + if("traitor") + if(!(has_antag_datum(/datum/antagonist/traitor))) + var/datum/antagonist/traitor/T = new() + T.give_objectives = FALSE + T.should_equip = FALSE + add_antag_datum(T) + log_admin("[key_name(usr)] has traitored [key_name(current)]") + message_admins("[key_name_admin(usr)] has traitored [key_name_admin(current)]") + + if("autoobjectives") + var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) + T.forge_traitor_objectives(src) + to_chat(usr, "The objectives for traitor [key] have been generated. You can edit them and announce manually.") + log_admin("[key_name(usr)] has automatically forged objectives for [key_name(current)]") + message_admins("[key_name_admin(usr)] has automatically forged objectives for [key_name_admin(current)]") + + else if(href_list["mindslave"]) + switch(href_list["mindslave"]) + if("clear") + if(has_antag_datum(/datum/antagonist/mindslave)) + var/mob/living/carbon/human/H = current + for(var/i in H.contents) + if(istype(i, /obj/item/implant/traitor)) + qdel(i) + break + remove_antag_datum(/datum/antagonist/mindslave) + log_admin("[key_name(usr)] has de-mindslaved [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-mindslaved [key_name_admin(current)]") + + else if(href_list["shadowling"]) + switch(href_list["shadowling"]) + if("clear") + SSticker.mode.update_shadow_icons_removed(src) + if(src in SSticker.mode.shadows) + SSticker.mode.shadows -= src + special_role = null + to_chat(current, "Your powers have been quenched! You are no longer a shadowling!") + message_admins("[key_name_admin(usr)] has de-shadowlinged [current].") + log_admin("[key_name(usr)] has de-shadowlinged [current].") + current.spellremove(current) + current.remove_language("Shadowling Hivemind") + else if(src in SSticker.mode.shadowling_thralls) + SSticker.mode.remove_thrall(src,0) + message_admins("[key_name_admin(usr)] has de-thrall'ed [current].") + log_admin("[key_name(usr)] has de-thralled [key_name(current)]") + message_admins("[key_name_admin(usr)] has de-thralled [key_name_admin(current)]") + if("shadowling") + if(!ishuman(current)) + to_chat(usr, "This only works on humans!") + return + SSticker.mode.shadows += src + special_role = SPECIAL_ROLE_SHADOWLING + to_chat(current, "Something stirs deep in your mind. A red light floods your vision, and slowly you remember. Though your human disguise has served you well, the \ + time is nigh to cast it off and enter your true form. You have disguised yourself amongst the humans, but you are not one of them. You are a shadowling, and you are to ascend at all costs.\ + ") + SSticker.mode.finalize_shadowling(src) + SSticker.mode.update_shadow_icons_added(src) + log_admin("[key_name(usr)] has shadowlinged [key_name(current)]") + message_admins("[key_name_admin(usr)] has shadowlinged [key_name_admin(current)]") + if("thrall") + if(!ishuman(current)) + to_chat(usr, "This only works on humans!") + return + SSticker.mode.add_thrall(src) + message_admins("[key_name_admin(usr)] has thralled [current].") + log_admin("[key_name(usr)] has thralled [current].") + + else if(href_list["abductor"]) + switch(href_list["abductor"]) + if("clear") + to_chat(usr, "Not implemented yet. Sorry!") + //ticker.mode.update_abductor_icons_removed(src) + if("abductor") + if(!ishuman(current)) + to_chat(usr, "This only works on humans!") + return + make_Abductor() + log_admin("[key_name(usr)] turned [current] into abductor.") + SSticker.mode.update_abductor_icons_added(src) + if("equip") + if(!ishuman(current)) + to_chat(usr, "This only works on humans!") + return + + var/mob/living/carbon/human/H = current + var/gear = alert("Agent or Scientist Gear","Gear","Agent","Scientist") + if(gear) + if(gear=="Agent") + H.equipOutfit(/datum/outfit/abductor/agent) + else + H.equipOutfit(/datum/outfit/abductor/scientist) + + else if(href_list["silicon"]) + switch(href_list["silicon"]) + if("unemag") + var/mob/living/silicon/robot/R = current + if(istype(R)) + R.emagged = 0 + if(R.module) + if(R.activated(R.module.emag)) + R.module_active = null + if(R.module_state_1 == R.module.emag) + R.module_state_1 = null + R.contents -= R.module.emag + else if(R.module_state_2 == R.module.emag) + R.module_state_2 = null + R.contents -= R.module.emag + else if(R.module_state_3 == R.module.emag) + R.module_state_3 = null + R.contents -= R.module.emag + R.clear_supplied_laws() + R.laws = new /datum/ai_laws/crewsimov + log_admin("[key_name(usr)] has un-emagged [key_name(current)]") + message_admins("[key_name_admin(usr)] has un-emagged [key_name_admin(current)]") + + if("unemagcyborgs") + if(isAI(current)) + var/mob/living/silicon/ai/ai = current + for(var/mob/living/silicon/robot/R in ai.connected_robots) + R.emagged = 0 + if(R.module) + if(R.activated(R.module.emag)) + R.module_active = null + if(R.module_state_1 == R.module.emag) + R.module_state_1 = null + R.contents -= R.module.emag + else if(R.module_state_2 == R.module.emag) + R.module_state_2 = null + R.contents -= R.module.emag + else if(R.module_state_3 == R.module.emag) + R.module_state_3 = null + R.contents -= R.module.emag + R.clear_supplied_laws() + R.laws = new /datum/ai_laws/crewsimov + log_admin("[key_name(usr)] has unemagged [key_name(ai)]'s cyborgs") + message_admins("[key_name_admin(usr)] has unemagged [key_name_admin(ai)]'s cyborgs") + + else if(href_list["common"]) + switch(href_list["common"]) + if("undress") + if(ishuman(current)) + var/mob/living/carbon/human/H = current + // Don't "undress" organs right out of the body + for(var/obj/item/W in H.contents - (H.bodyparts | H.internal_organs)) + current.unEquip(W, 1) + else + for(var/obj/item/W in current) + current.unEquip(W, 1) + log_admin("[key_name(usr)] has unequipped [key_name(current)]") + message_admins("[key_name_admin(usr)] has unequipped [key_name_admin(current)]") + if("takeuplink") + take_uplink() + var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) + T.antag_memory = "" //Remove any antag memory they may have had (uplink codes, code phrases) + log_admin("[key_name(usr)] has taken [key_name(current)]'s uplink") + message_admins("[key_name_admin(usr)] has taken [key_name_admin(current)]'s uplink") + if("crystals") + if(usr.client.holder.rights & (R_SERVER|R_EVENT)) + var/obj/item/uplink/hidden/suplink = find_syndicate_uplink() + var/crystals + if(suplink) + crystals = suplink.uses + crystals = input("Amount of telecrystals for [key]","Syndicate uplink", crystals) as null|num + if(!isnull(crystals)) + if(suplink) + suplink.uses = crystals + log_admin("[key_name(usr)] has set [key_name(current)]'s telecrystals to [crystals]") + message_admins("[key_name_admin(usr)] has set [key_name_admin(current)]'s telecrystals to [crystals]") + if("uplink") + if(has_antag_datum(/datum/antagonist/traitor)) + var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) + T.give_codewords() + if(!T.equip_traitor(src)) + to_chat(usr, "Equipping a syndicate failed!") + return + log_admin("[key_name(usr)] has given [key_name(current)] an uplink") + message_admins("[key_name_admin(usr)] has given [key_name_admin(current)] an uplink") + + else if(href_list["obj_announce"]) + announce_objectives() + SEND_SOUND(current, sound('sound/ambience/alarm4.ogg')) + log_admin("[key_name(usr)] has announced [key_name(current)]'s objectives") + message_admins("[key_name_admin(usr)] has announced [key_name_admin(current)]'s objectives") + + edit_memory() + + +// Datum antag mind procs +/datum/mind/proc/add_antag_datum(datum_type_or_instance, team) + if(!datum_type_or_instance) + return + var/datum/antagonist/A + if(!ispath(datum_type_or_instance)) + A = datum_type_or_instance + if(!istype(A)) + return + else + A = new datum_type_or_instance() + //Choose snowflake variation if antagonist handles it + var/datum/antagonist/S = A.specialization(src) + if(S && S != A) + qdel(A) + A = S + if(!A.can_be_owned(src)) + qdel(A) + return + A.owner = src + LAZYADD(antag_datums, A) + A.create_team(team) + var/datum/team/antag_team = A.get_team() + if(antag_team) + antag_team.add_member(src) + A.on_gain() + return A + +/datum/mind/proc/remove_antag_datum(datum_type) + if(!datum_type) + return + var/datum/antagonist/A = has_antag_datum(datum_type) + if(A) + A.on_removal() + return TRUE + + +/datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. + for(var/a in antag_datums) + var/datum/antagonist/A = a + A.on_removal() + +/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) + if(!datum_type) + return + . = FALSE + for(var/a in antag_datums) + var/datum/antagonist/A = a + if(check_subtypes && istype(A, datum_type)) + return A + else if(A.type == datum_type) + return A + +/datum/mind/proc/announce_objectives() + to_chat(current, "Your current objectives:") + for(var/line in splittext(gen_objective_text(), "
    ")) + to_chat(current, line) + +/datum/mind/proc/find_syndicate_uplink() + var/list/L = current.get_contents() + for(var/obj/item/I in L) + if(I.hidden_uplink) + return I.hidden_uplink + return null + +/datum/mind/proc/take_uplink() + var/obj/item/uplink/hidden/H = find_syndicate_uplink() + if(H) + qdel(H) + +/datum/mind/proc/make_Traitor() + if(!has_antag_datum(/datum/antagonist/traitor)) + add_antag_datum(/datum/antagonist/traitor) + +/datum/mind/proc/make_Nuke() + if(!(src in SSticker.mode.syndicates)) + SSticker.mode.syndicates += src + SSticker.mode.update_synd_icons_added(src) + if(SSticker.mode.syndicates.len==1) + SSticker.mode.prepare_syndicate_leader(src) + else + current.real_name = "[syndicate_name()] Operative #[SSticker.mode.syndicates.len-1]" + special_role = SPECIAL_ROLE_NUKEOPS + assigned_role = SPECIAL_ROLE_NUKEOPS + to_chat(current, "You are a [syndicate_name()] agent!") + SSticker.mode.forge_syndicate_objectives(src) + SSticker.mode.greet_syndicate(src) + + current.loc = get_turf(locate("landmark*Syndicate-Spawn")) + + var/mob/living/carbon/human/H = current + qdel(H.belt) + qdel(H.back) + qdel(H.l_ear) + qdel(H.r_ear) + qdel(H.gloves) + qdel(H.head) + qdel(H.shoes) + qdel(H.wear_id) + qdel(H.wear_pda) + qdel(H.wear_suit) + qdel(H.w_uniform) + + SSticker.mode.equip_syndicate(current) + +/datum/mind/proc/make_Vampire() + if(!(src in SSticker.mode.vampires)) + SSticker.mode.vampires += src + SSticker.mode.grant_vampire_powers(current) + special_role = SPECIAL_ROLE_VAMPIRE + SSticker.mode.forge_vampire_objectives(src) + SSticker.mode.greet_vampire(src) + SSticker.mode.update_vampire_icons_added(src) + +/datum/mind/proc/make_Changeling() + if(!(src in SSticker.mode.changelings)) + SSticker.mode.changelings += src + SSticker.mode.grant_changeling_powers(current) + special_role = SPECIAL_ROLE_CHANGELING + SSticker.mode.forge_changeling_objectives(src) + SSticker.mode.greet_changeling(src) + SSticker.mode.update_change_icons_added(src) + +/datum/mind/proc/make_Overmind() + if(!(src in SSticker.mode.blob_overminds)) + SSticker.mode.blob_overminds += src + special_role = SPECIAL_ROLE_BLOB_OVERMIND + +/datum/mind/proc/make_Wizard() + if(!(src in SSticker.mode.wizards)) + SSticker.mode.wizards += src + special_role = SPECIAL_ROLE_WIZARD + assigned_role = SPECIAL_ROLE_WIZARD + //ticker.mode.learn_basic_spells(current) + if(!wizardstart.len) + current.loc = pick(latejoin) + to_chat(current, "HOT INSERTION, GO GO GO") + else + current.loc = pick(wizardstart) + + SSticker.mode.equip_wizard(current) + for(var/obj/item/spellbook/S in current.contents) + S.op = 0 + SSticker.mode.name_wizard(current) + SSticker.mode.forge_wizard_objectives(src) + SSticker.mode.greet_wizard(src) + SSticker.mode.update_wiz_icons_added(src) + +/datum/mind/proc/make_Rev() + if(SSticker.mode.head_revolutionaries.len>0) + // copy targets + var/datum/mind/valid_head = locate() in SSticker.mode.head_revolutionaries + if(valid_head) + for(var/datum/objective/mutiny/O in valid_head.objectives) + var/datum/objective/mutiny/rev_obj = new + rev_obj.owner = src + rev_obj.target = O.target + rev_obj.explanation_text = "Assassinate [O.target.current.real_name], the [O.target.assigned_role]." + objectives += rev_obj + SSticker.mode.greet_revolutionary(src,0) + SSticker.mode.head_revolutionaries += src + SSticker.mode.update_rev_icons_added(src) + special_role = SPECIAL_ROLE_HEAD_REV + + SSticker.mode.forge_revolutionary_objectives(src) + SSticker.mode.greet_revolutionary(src,0) + + var/list/L = current.get_contents() + var/obj/item/flash/flash = locate() in L + qdel(flash) + take_uplink() + var/fail = 0 +// fail |= !ticker.mode.equip_traitor(current, 1) + fail |= !SSticker.mode.equip_revolutionary(current) + +/datum/mind/proc/make_Abductor() + var/role = alert("Abductor Role ?","Role","Agent","Scientist") + var/team = input("Abductor Team ?","Team ?") in list(1,2,3,4) + var/teleport = alert("Teleport to ship ?","Teleport","Yes","No") + + if(!role || !team || !teleport) + return + + if(!ishuman(current)) + return + + SSticker.mode.abductors |= src + + var/datum/objective/stay_hidden/hidden_obj = new + hidden_obj.owner = src + objectives += hidden_obj + + var/datum/objective/experiment/O = new + O.owner = src + objectives += O + + var/mob/living/carbon/human/H = current + + H.set_species(/datum/species/abductor) + var/datum/species/abductor/S = H.dna.species + + if(role == "Scientist") + S.scientist = TRUE + + S.team = team + + var/list/obj/effect/landmark/abductor/agent_landmarks = new + var/list/obj/effect/landmark/abductor/scientist_landmarks = new + agent_landmarks.len = 4 + scientist_landmarks.len = 4 + for(var/obj/effect/landmark/abductor/A in GLOB.landmarks_list) + if(istype(A, /obj/effect/landmark/abductor/agent)) + agent_landmarks[text2num(A.team)] = A + else if(istype(A, /obj/effect/landmark/abductor/scientist)) + scientist_landmarks[text2num(A.team)] = A + + var/obj/effect/landmark/L + if(teleport == "Yes") + switch(role) + if("Agent") + L = agent_landmarks[team] + if("Scientist") + L = agent_landmarks[team] + H.forceMove(L.loc) + + +// check whether this mind's mob has been brigged for the given duration +// have to call this periodically for the duration to work properly +/datum/mind/proc/is_brigged(duration) + var/turf/T = current.loc + if(!istype(T)) + brigged_since = -1 + return 0 + + var/is_currently_brigged = current.is_in_brig() + if(!is_currently_brigged) + brigged_since = -1 + return 0 + + if(brigged_since == -1) + brigged_since = world.time + + return (duration <= world.time - brigged_since) + +/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) + spell_list += S + S.action.Grant(current) + +/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) //To remove a specific spell from a mind + if(!spell) + return + for(var/obj/effect/proc_holder/spell/S in spell_list) + if(istype(S, spell)) + qdel(S) + spell_list -= S + +/datum/mind/proc/transfer_actions(mob/living/new_character) + if(current && current.actions) + for(var/datum/action/A in current.actions) + A.Grant(new_character) + transfer_mindbound_actions(new_character) + +/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + S.action.Grant(new_character) + +/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) + for(var/X in spell_list) + var/obj/effect/proc_holder/spell/S = X + for(var/type in exceptions) + if(istype(S, type)) + continue + S.charge_counter = delay + spawn(0) + S.start_recharge() + S.updateButtonIcon() + +/datum/mind/proc/get_ghost(even_if_they_cant_reenter) + for(var/mob/dead/observer/G in GLOB.dead_mob_list) + if(G.mind == src) + if(G.can_reenter_corpse || even_if_they_cant_reenter) + return G + break + +/datum/mind/proc/grab_ghost(force) + var/mob/dead/observer/G = get_ghost(even_if_they_cant_reenter = force) + . = G + if(G) + G.reenter_corpse() + + +/datum/mind/proc/make_zealot(mob/living/carbon/human/missionary, convert_duration = 6000, team_color = "red") + + zealot_master = missionary + + var/list/implanters + if(!(missionary.mind in SSticker.mode.implanter)) + SSticker.mode.implanter[missionary.mind] = list() + implanters = SSticker.mode.implanter[missionary.mind] + implanters.Add(src) + SSticker.mode.implanted.Add(src) + SSticker.mode.implanted[src] = missionary.mind + SSticker.mode.implanter[missionary.mind] = implanters + SSticker.mode.traitors += src + + + var/datum/objective/protect/zealot_objective = new + zealot_objective.target = missionary.mind + zealot_objective.owner = src + zealot_objective.explanation_text = "Obey every order from and protect [missionary.real_name], the [missionary.mind.assigned_role == missionary.mind.special_role ? (missionary.mind.special_role) : (missionary.mind.assigned_role)]." + objectives += zealot_objective + add_antag_datum(/datum/antagonist/mindslave) + + var/datum/antagonist/traitor/T = missionary.mind.has_antag_datum(/datum/antagonist) + T.update_traitor_icons_added(missionary.mind) + + to_chat(current, "You're now a loyal zealot of [missionary.name]! You now must lay down your life to protect [missionary.p_them()] and assist in [missionary.p_their()] goals at any cost.") + + var/datum/mindslaves/slaved = missionary.mind.som + som = slaved + slaved.serv += current + slaved.add_serv_hud(missionary.mind, "master") //handles master servent icons + slaved.add_serv_hud(src, "mindslave") + + var/obj/item/clothing/under/jumpsuit = null + if(ishuman(current)) //only bother with the jumpsuit stuff if we are a human type, since we won't have the slot otherwise + var/mob/living/carbon/human/H = current + if(H.w_uniform) + jumpsuit = H.w_uniform + jumpsuit.color = team_color + H.update_inv_w_uniform(0,0) + + add_attack_logs(missionary, current, "Converted to a zealot for [convert_duration/600] minutes") + addtimer(CALLBACK(src, .proc/remove_zealot, jumpsuit), convert_duration) //deconverts after the timer expires + return 1 + +/datum/mind/proc/remove_zealot(obj/item/clothing/under/jumpsuit = null) + if(!zealot_master) //if they aren't a zealot, we can't remove their zealot status, obviously. don't bother with the rest so we don't confuse them with the messages + return + remove_antag_datum(/datum/antagonist/mindslave) + add_attack_logs(zealot_master, current, "Lost control of zealot") + zealot_master = null + + if(jumpsuit) + jumpsuit.color = initial(jumpsuit.color) //reset the jumpsuit no matter where our mind is + if(ishuman(current)) //but only try updating us if we are still a human type since it is a human proc + var/mob/living/carbon/human/H = current + H.update_inv_w_uniform(0,0) + + to_chat(current, "You seem to have forgotten the events of the past 10 minutes or so, and your head aches a bit as if someone beat it savagely with a stick.") + to_chat(current, "This means you don't remember who you were working for or what you were doing.") + +/datum/mind/proc/is_revivable() //Note, this ONLY checks the mind. + if(damnation_type) + return FALSE + return TRUE + +// returns a mob to message to produce something visible for the target mind +/datum/mind/proc/messageable_mob() + if(!QDELETED(current) && current.client) + return current + else + return get_ghost(even_if_they_cant_reenter = TRUE) + +//Initialisation procs +/mob/proc/mind_initialize() + if(mind) + mind.key = key + else + mind = new /datum/mind(key) + if(SSticker) + SSticker.minds += mind + else + error("mind_initialize(): No ticker ready yet! Please inform Carn") + if(!mind.name) + mind.name = real_name + mind.current = src + +//HUMAN +/mob/living/carbon/human/mind_initialize() + ..() + if(!mind.assigned_role) + mind.assigned_role = "Civilian" //defualt + +/mob/proc/sync_mind() + mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) + mind.active = 1 //indicates that the mind is currently synced with a client + +//slime +/mob/living/simple_animal/slime/mind_initialize() + ..() + mind.assigned_role = "slime" + +//XENO +/mob/living/carbon/alien/mind_initialize() + ..() + mind.assigned_role = "Alien" + //XENO HUMANOID +/mob/living/carbon/alien/humanoid/queen/mind_initialize() + ..() + mind.special_role = SPECIAL_ROLE_XENOMORPH_QUEEN + +/mob/living/carbon/alien/humanoid/hunter/mind_initialize() + ..() + mind.special_role = SPECIAL_ROLE_XENOMORPH_HUNTER + +/mob/living/carbon/alien/humanoid/drone/mind_initialize() + ..() + mind.special_role = SPECIAL_ROLE_XENOMORPH_DRONE + +/mob/living/carbon/alien/humanoid/sentinel/mind_initialize() + ..() + mind.special_role = SPECIAL_ROLE_XENOMORPH_SENTINEL + //XENO LARVA +/mob/living/carbon/alien/larva/mind_initialize() + ..() + mind.special_role = SPECIAL_ROLE_XENOMORPH_LARVA + +//AI +/mob/living/silicon/ai/mind_initialize() + ..() + mind.assigned_role = "AI" + +//BORG +/mob/living/silicon/robot/mind_initialize() + ..() + mind.assigned_role = "Cyborg" + +//PAI +/mob/living/silicon/pai/mind_initialize() + ..() + mind.assigned_role = "pAI" + mind.special_role = null + +//BLOB +/mob/camera/overmind/mind_initialize() + ..() + mind.special_role = SPECIAL_ROLE_BLOB + +//Animals +/mob/living/simple_animal/mind_initialize() + ..() + mind.assigned_role = "Animal" + +/mob/living/simple_animal/pet/dog/corgi/mind_initialize() + ..() + mind.assigned_role = "Corgi" + +/mob/living/simple_animal/shade/mind_initialize() + ..() + mind.assigned_role = "Shade" + +/mob/living/simple_animal/construct/builder/mind_initialize() + ..() + mind.assigned_role = "Artificer" + mind.special_role = SPECIAL_ROLE_CULTIST + +/mob/living/simple_animal/construct/wraith/mind_initialize() + ..() + mind.assigned_role = "Wraith" + mind.special_role = SPECIAL_ROLE_CULTIST + +/mob/living/simple_animal/construct/armoured/mind_initialize() + ..() + mind.assigned_role = "Juggernaut" + mind.special_role = SPECIAL_ROLE_CULTIST diff --git a/code/datums/mixed.dm b/code/datums/mixed.dm index a40a9f9a34d..05fb0a99649 100644 --- a/code/datums/mixed.dm +++ b/code/datums/mixed.dm @@ -1,40 +1,40 @@ -/datum/data - var/name = "data" - var/size = 1.0 - - -/datum/data/function - name = "function" - size = 2.0 - - -/datum/data/function/data_control - name = "data control" - - -/datum/data/function/id_changer - name = "id changer" - - -/datum/data/record - name = "record" - size = 5.0 - var/list/fields = list( ) - -/datum/data/record/Destroy() - if(src in data_core.medical) - data_core.medical -= src - if(src in data_core.security) - data_core.security -= src - if(src in data_core.general) - data_core.general -= src - if(src in data_core.locked) - data_core.locked -= src - return ..() - -/datum/data/text - name = "text" - var/data = null - -/datum/debug - var/list/debuglist +/datum/data + var/name = "data" + var/size = 1.0 + + +/datum/data/function + name = "function" + size = 2.0 + + +/datum/data/function/data_control + name = "data control" + + +/datum/data/function/id_changer + name = "id changer" + + +/datum/data/record + name = "record" + size = 5.0 + var/list/fields = list( ) + +/datum/data/record/Destroy() + if(src in data_core.medical) + data_core.medical -= src + if(src in data_core.security) + data_core.security -= src + if(src in data_core.general) + data_core.general -= src + if(src in data_core.locked) + data_core.locked -= src + return ..() + +/datum/data/text + name = "text" + var/data = null + +/datum/debug + var/list/debuglist diff --git a/code/datums/mutable_appearance.dm b/code/datums/mutable_appearance.dm index 31c20315508..0887290a6f4 100644 --- a/code/datums/mutable_appearance.dm +++ b/code/datums/mutable_appearance.dm @@ -23,4 +23,4 @@ . = ..() alpha = 255 opacity = 1 - transform = null \ No newline at end of file + transform = null diff --git a/code/datums/outfits/plasmamen.dm b/code/datums/outfits/plasmamen.dm index c14eafd08f2..376bf2af90c 100644 --- a/code/datums/outfits/plasmamen.dm +++ b/code/datums/outfits/plasmamen.dm @@ -175,4 +175,4 @@ name = "Blueshield Plasmaman" head = /obj/item/clothing/head/helmet/space/plasmaman/blueshield - uniform = /obj/item/clothing/under/plasmaman/blueshield \ No newline at end of file + uniform = /obj/item/clothing/under/plasmaman/blueshield diff --git a/code/datums/outfits/vv_outfit.dm b/code/datums/outfits/vv_outfit.dm index 7a07b087ae8..95c91318bb8 100644 --- a/code/datums/outfits/vv_outfit.dm +++ b/code/datums/outfits/vv_outfit.dm @@ -187,4 +187,4 @@ . = ..() stored_access = outfit_data["stored_access"] vv_values = outfit_data["vv_values"] - update_id_name = outfit_data["update_id_name"] \ No newline at end of file + update_id_name = outfit_data["update_id_name"] diff --git a/code/datums/radio.dm b/code/datums/radio.dm index 7670bc76e32..b748a9f9229 100644 --- a/code/datums/radio.dm +++ b/code/datums/radio.dm @@ -1,114 +1,114 @@ - -/datum/radio_frequency - var/frequency as num - var/list/list/obj/devices = list() - -/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, var/filter = null as text|null, var/range = null as num|null) - var/turf/start_point - if(range) - start_point = get_turf(source) - if(!start_point) - qdel(signal) - return 0 - if(filter) - send_to_filter(source, signal, filter, start_point, range) - send_to_filter(source, signal, RADIO_DEFAULT, start_point, range) - else - //Broadcast the signal to everyone! - for(var/next_filter in devices) - send_to_filter(source, signal, next_filter, start_point, range) - -//Sends a signal to all machines belonging to a given filter. Should be called by post_signal() -/datum/radio_frequency/proc/send_to_filter(obj/source, datum/signal/signal, var/filter, var/turf/start_point = null, var/range = null) - if(range && !start_point) - return - - for(var/obj/device in devices[filter]) - if(device == source) - continue - if(range) - var/turf/end_point = get_turf(device) - if(!end_point) - continue - if(start_point.z!=end_point.z || get_dist(start_point, end_point) > range) - continue - - device.receive_signal(signal, TRANSMISSION_RADIO, frequency) - -/datum/radio_frequency/proc/add_listener(obj/device as obj, var/filter as text|null) - if(!filter) - filter = RADIO_DEFAULT - //log_admin("add_listener(device=[device],filter=[filter]) frequency=[frequency]") - var/list/obj/devices_line = devices[filter] - if(!devices_line) - devices_line = new - devices[filter] = devices_line - devices_line+=device -// var/list/obj/devices_line___ = devices[filter_str] -// var/l = devices_line___.len - //log_admin("DEBUG: devices_line.len=[devices_line.len]") - //log_admin("DEBUG: devices(filter_str).len=[l]") - -/datum/radio_frequency/proc/remove_listener(obj/device) - for(var/devices_filter in devices) - var/list/devices_line = devices[devices_filter] - devices_line-=device - while(null in devices_line) - devices_line -= null - if(devices_line.len==0) - devices -= devices_filter - qdel(devices_line) - -/datum/signal - var/obj/source - - var/transmission_method = 0 //unused at the moment - //0 = wire - //1 = radio transmission - //2 = subspace transmission - - var/list/data = list() - var/encryption - - var/frequency = 0 - -/datum/signal/proc/copy_from(datum/signal/model) - source = model.source - transmission_method = model.transmission_method - data = model.data - encryption = model.encryption - frequency = model.frequency - -/datum/signal/proc/debug_print() - if(source) - . = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n" - else - . = "signal = {source = '[source]' ()\n" - for(var/i in data) - . += "data\[\"[i]\"\] = \"[data[i]]\"\n" - if(islist(data[i])) - var/list/L = data[i] - for(var/t in L) - . += "data\[\"[i]\"\] list has: [t]" - -/datum/signal/proc/get_race(mob/M) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - . = H.dna.species.name - else if(isbrain(M)) - var/mob/living/carbon/brain/B = M - . = B.get_race() - else if(issilicon(M)) - . = "Artificial Life" - else if(isslime(M)) - . = "Slime" - else if(isbot(M)) - . = "Bot" - else if(isanimal(M)) - . = "Domestic Animal" - else - . = "Unidentifiable" - -//callback used by objects to react to incoming radio signals -/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param) - return null + +/datum/radio_frequency + var/frequency as num + var/list/list/obj/devices = list() + +/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, var/filter = null as text|null, var/range = null as num|null) + var/turf/start_point + if(range) + start_point = get_turf(source) + if(!start_point) + qdel(signal) + return 0 + if(filter) + send_to_filter(source, signal, filter, start_point, range) + send_to_filter(source, signal, RADIO_DEFAULT, start_point, range) + else + //Broadcast the signal to everyone! + for(var/next_filter in devices) + send_to_filter(source, signal, next_filter, start_point, range) + +//Sends a signal to all machines belonging to a given filter. Should be called by post_signal() +/datum/radio_frequency/proc/send_to_filter(obj/source, datum/signal/signal, var/filter, var/turf/start_point = null, var/range = null) + if(range && !start_point) + return + + for(var/obj/device in devices[filter]) + if(device == source) + continue + if(range) + var/turf/end_point = get_turf(device) + if(!end_point) + continue + if(start_point.z!=end_point.z || get_dist(start_point, end_point) > range) + continue + + device.receive_signal(signal, TRANSMISSION_RADIO, frequency) + +/datum/radio_frequency/proc/add_listener(obj/device as obj, var/filter as text|null) + if(!filter) + filter = RADIO_DEFAULT + //log_admin("add_listener(device=[device],filter=[filter]) frequency=[frequency]") + var/list/obj/devices_line = devices[filter] + if(!devices_line) + devices_line = new + devices[filter] = devices_line + devices_line+=device +// var/list/obj/devices_line___ = devices[filter_str] +// var/l = devices_line___.len + //log_admin("DEBUG: devices_line.len=[devices_line.len]") + //log_admin("DEBUG: devices(filter_str).len=[l]") + +/datum/radio_frequency/proc/remove_listener(obj/device) + for(var/devices_filter in devices) + var/list/devices_line = devices[devices_filter] + devices_line-=device + while(null in devices_line) + devices_line -= null + if(devices_line.len==0) + devices -= devices_filter + qdel(devices_line) + +/datum/signal + var/obj/source + + var/transmission_method = 0 //unused at the moment + //0 = wire + //1 = radio transmission + //2 = subspace transmission + + var/list/data = list() + var/encryption + + var/frequency = 0 + +/datum/signal/proc/copy_from(datum/signal/model) + source = model.source + transmission_method = model.transmission_method + data = model.data + encryption = model.encryption + frequency = model.frequency + +/datum/signal/proc/debug_print() + if(source) + . = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n" + else + . = "signal = {source = '[source]' ()\n" + for(var/i in data) + . += "data\[\"[i]\"\] = \"[data[i]]\"\n" + if(islist(data[i])) + var/list/L = data[i] + for(var/t in L) + . += "data\[\"[i]\"\] list has: [t]" + +/datum/signal/proc/get_race(mob/M) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + . = H.dna.species.name + else if(isbrain(M)) + var/mob/living/carbon/brain/B = M + . = B.get_race() + else if(issilicon(M)) + . = "Artificial Life" + else if(isslime(M)) + . = "Slime" + else if(isbot(M)) + . = "Bot" + else if(isanimal(M)) + . = "Domestic Animal" + else + . = "Unidentifiable" + +//callback used by objects to react to incoming radio signals +/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param) + return null diff --git a/code/datums/recipe.dm b/code/datums/recipe.dm index 99d97e45626..52189fe527e 100644 --- a/code/datums/recipe.dm +++ b/code/datums/recipe.dm @@ -1,131 +1,131 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * - * /datum/recipe by rastaf0 13 apr 2011 * - * * * * * * * * * * * * * * * * * * * * * * * * * * - * This is powerful and flexible recipe system. - * It exists not only for food. - * supports both reagents and objects as prerequisites. - * In order to use this system you have to define a deriative from /datum/recipe - * * reagents are reagents. Acid, milc, booze, etc. - * * items are objects. Fruits, tools, circuit boards. - * * result is type to create as new object - * * time is optional parameter, you shall use in in your machine, - default /datum/recipe/ procs does not rely on this parameter. - * - * Functions you need: - * /datum/recipe/proc/make(var/obj/container as obj) - * Creates result inside container, - * deletes prerequisite reagents, - * transfers reagents from prerequisite objects, - * deletes all prerequisite objects (even not needed for recipe at the moment). - * - * /proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj as obj, exact = 1) - * Wonderful function that select suitable recipe for you. - * obj is a machine (or magik hat) with prerequisites, - * exact = 0 forces algorithm to ignore superfluous stuff. - * - * - * Functions you do not need to call directly but could: - * /datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) - * //1=precisely, 0=insufficiently, -1=superfluous - * - * /datum/recipe/proc/check_items(var/obj/container as obj) - * //1=precisely, 0=insufficiently, -1=superfluous - * - * */ - -/datum/recipe - var/list/reagents // example: = list("berryjuice" = 5) // do not list same reagent twice - var/list/items // example: =list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo - var/result //example: = /obj/item/reagent_containers/food/snacks/donut - var/time = 100 // 1/10 part of second - var/byproduct // example: = /obj/item/kitchen/mould // byproduct to return, such as a mould or trash - -/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous - . = 1 - for(var/r_r in reagents) - var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) - if(!(abs(aval_r_amnt - reagents[r_r])<0.5)) //if NOT equals - if(aval_r_amnt>reagents[r_r]) - . = -1 - else - return 0 - if((reagents?(reagents.len):(0)) < avail_reagents.reagent_list.len) - return -1 - return . - -/datum/recipe/proc/check_items(obj/container, list/ignored_items = null) //1=precisely, 0=insufficiently, -1=superfluous - . = 1 - var/list/checklist = items ? items.Copy() : list() - for(var/obj/O in container) - if(ignored_items && is_type_in_list(O, ignored_items)) //skip if this is something we are ignoring - continue - if(!items) - return -1 - var/found = 0 - for(var/type in checklist) - if(istype(O,type)) - checklist -= type - found = 1 - break - if(!found) - . = -1 - if(checklist.len) - return 0 - return . - -//general version -/datum/recipe/proc/make(obj/container) - var/obj/result_obj = new result(container) - for(var/obj/O in (container.contents-result_obj)) - O.reagents.trans_to(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -// food-related -/datum/recipe/proc/make_food(obj/container) - var/obj/result_obj = new result(container) - for(var/obj/O in (container.contents-result_obj)) - if(O.reagents) - O.reagents.del_reagent("nutriment") - O.reagents.update_total() - O.reagents.trans_to(result_obj, O.reagents.total_volume) - qdel(O) - container.reagents.clear_reagents() - return result_obj - -/proc/select_recipe(list/datum/recipe/available_recipes, obj/obj, exact = 1 as num, list/ignored_items = null) - if(!exact) - exact = -1 - var/list/datum/recipe/possible_recipes = new - for(var/datum/recipe/recipe in available_recipes) - if(recipe.check_reagents(obj.reagents) == exact && recipe.check_items(obj, ignored_items) == exact) - possible_recipes += recipe - if(possible_recipes.len == 0) - return null - else if(possible_recipes.len == 1) - return possible_recipes[1] - else //okay, let's select the most complicated recipe - var/r_count = 0 - var/i_count = 0 - . = possible_recipes[1] - for(var/datum/recipe/recipe in possible_recipes) - var/N_i = (recipe.items)?(recipe.items.len):0 - var/N_r = (recipe.reagents)?(recipe.reagents.len):0 - if(N_i > i_count || (N_i== i_count && N_r > r_count )) - r_count = N_r - i_count = N_i - . = recipe - return . - -/datum/recipe/proc/get_byproduct() - if(byproduct) - return byproduct - else - return null - -/datum/recipe/proc/count_n_items() - var/count = 0 - if(items && items.len) - count += items.len - return count \ No newline at end of file +/* * * * * * * * * * * * * * * * * * * * * * * * * * + * /datum/recipe by rastaf0 13 apr 2011 * + * * * * * * * * * * * * * * * * * * * * * * * * * * + * This is powerful and flexible recipe system. + * It exists not only for food. + * supports both reagents and objects as prerequisites. + * In order to use this system you have to define a deriative from /datum/recipe + * * reagents are reagents. Acid, milc, booze, etc. + * * items are objects. Fruits, tools, circuit boards. + * * result is type to create as new object + * * time is optional parameter, you shall use in in your machine, + default /datum/recipe/ procs does not rely on this parameter. + * + * Functions you need: + * /datum/recipe/proc/make(var/obj/container as obj) + * Creates result inside container, + * deletes prerequisite reagents, + * transfers reagents from prerequisite objects, + * deletes all prerequisite objects (even not needed for recipe at the moment). + * + * /proc/select_recipe(list/datum/recipe/avaiable_recipes, obj/obj as obj, exact = 1) + * Wonderful function that select suitable recipe for you. + * obj is a machine (or magik hat) with prerequisites, + * exact = 0 forces algorithm to ignore superfluous stuff. + * + * + * Functions you do not need to call directly but could: + * /datum/recipe/proc/check_reagents(var/datum/reagents/avail_reagents) + * //1=precisely, 0=insufficiently, -1=superfluous + * + * /datum/recipe/proc/check_items(var/obj/container as obj) + * //1=precisely, 0=insufficiently, -1=superfluous + * + * */ + +/datum/recipe + var/list/reagents // example: = list("berryjuice" = 5) // do not list same reagent twice + var/list/items // example: =list(/obj/item/crowbar, /obj/item/welder) // place /foo/bar before /foo + var/result //example: = /obj/item/reagent_containers/food/snacks/donut + var/time = 100 // 1/10 part of second + var/byproduct // example: = /obj/item/kitchen/mould // byproduct to return, such as a mould or trash + +/datum/recipe/proc/check_reagents(datum/reagents/avail_reagents) //1=precisely, 0=insufficiently, -1=superfluous + . = 1 + for(var/r_r in reagents) + var/aval_r_amnt = avail_reagents.get_reagent_amount(r_r) + if(!(abs(aval_r_amnt - reagents[r_r])<0.5)) //if NOT equals + if(aval_r_amnt>reagents[r_r]) + . = -1 + else + return 0 + if((reagents?(reagents.len):(0)) < avail_reagents.reagent_list.len) + return -1 + return . + +/datum/recipe/proc/check_items(obj/container, list/ignored_items = null) //1=precisely, 0=insufficiently, -1=superfluous + . = 1 + var/list/checklist = items ? items.Copy() : list() + for(var/obj/O in container) + if(ignored_items && is_type_in_list(O, ignored_items)) //skip if this is something we are ignoring + continue + if(!items) + return -1 + var/found = 0 + for(var/type in checklist) + if(istype(O,type)) + checklist -= type + found = 1 + break + if(!found) + . = -1 + if(checklist.len) + return 0 + return . + +//general version +/datum/recipe/proc/make(obj/container) + var/obj/result_obj = new result(container) + for(var/obj/O in (container.contents-result_obj)) + O.reagents.trans_to(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +// food-related +/datum/recipe/proc/make_food(obj/container) + var/obj/result_obj = new result(container) + for(var/obj/O in (container.contents-result_obj)) + if(O.reagents) + O.reagents.del_reagent("nutriment") + O.reagents.update_total() + O.reagents.trans_to(result_obj, O.reagents.total_volume) + qdel(O) + container.reagents.clear_reagents() + return result_obj + +/proc/select_recipe(list/datum/recipe/available_recipes, obj/obj, exact = 1 as num, list/ignored_items = null) + if(!exact) + exact = -1 + var/list/datum/recipe/possible_recipes = new + for(var/datum/recipe/recipe in available_recipes) + if(recipe.check_reagents(obj.reagents) == exact && recipe.check_items(obj, ignored_items) == exact) + possible_recipes += recipe + if(possible_recipes.len == 0) + return null + else if(possible_recipes.len == 1) + return possible_recipes[1] + else //okay, let's select the most complicated recipe + var/r_count = 0 + var/i_count = 0 + . = possible_recipes[1] + for(var/datum/recipe/recipe in possible_recipes) + var/N_i = (recipe.items)?(recipe.items.len):0 + var/N_r = (recipe.reagents)?(recipe.reagents.len):0 + if(N_i > i_count || (N_i== i_count && N_r > r_count )) + r_count = N_r + i_count = N_i + . = recipe + return . + +/datum/recipe/proc/get_byproduct() + if(byproduct) + return byproduct + else + return null + +/datum/recipe/proc/count_n_items() + var/count = 0 + if(items && items.len) + count += items.len + return count diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm index bb80fe84d01..f826b6ff0d2 100644 --- a/code/datums/ruins/lavaland.dm +++ b/code/datums/ruins/lavaland.dm @@ -217,4 +217,4 @@ datum/map_template/ruin/lavaland/ash_walker id = "puzzle" description = "Mystery to be solved." suffix = "lavaland_surface_puzzle.dmm" - cost = 5 \ No newline at end of file + cost = 5 diff --git a/code/datums/spell.dm b/code/datums/spell.dm index f19f8100553..7f1541248d9 100644 --- a/code/datums/spell.dm +++ b/code/datums/spell.dm @@ -1,499 +1,499 @@ -#define TARGET_CLOSEST 1 -#define TARGET_RANDOM 2 - -/obj/effect/proc_holder - var/panel = "Debug"//What panel the proc holder needs to go on. - var/active = FALSE //Used by toggle based abilities. - var/ranged_mousepointer - var/mob/living/ranged_ability_user - -/obj/effect/proc_holder/singularity_act() - return - -/obj/effect/proc_holder/singularity_pull() - return - -var/list/spells = typesof(/obj/effect/proc_holder/spell) //needed for the badmin verb for now - -/obj/effect/proc_holder/proc/InterceptClickOn(mob/living/user, params, atom/A) - if(user.ranged_ability != src) - to_chat(user, "[user.ranged_ability.name] has been disabled.") - user.ranged_ability.remove_ranged_ability(user) - return TRUE //TRUE for failed, FALSE for passed. - user.changeNext_click(CLICK_CD_CLICK_ABILITY) - user.face_atom(A) - return FALSE - -/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, var/msg) - if(!user || !user.client) - return - if(user.ranged_ability && user.ranged_ability != src) - to_chat(user, "[user.ranged_ability.name] has been replaced by [name].") - user.ranged_ability.remove_ranged_ability(user) - user.ranged_ability = src - ranged_ability_user = user - user.client.click_intercept = user.ranged_ability - add_mousepointer(user.client) - active = TRUE - if(msg) - to_chat(user, msg) - update_icon() - -/obj/effect/proc_holder/proc/add_mousepointer(client/C) - if(C && ranged_mousepointer && C.mouse_pointer_icon == initial(C.mouse_pointer_icon)) - C.mouse_pointer_icon = ranged_mousepointer - -/obj/effect/proc_holder/proc/remove_mousepointer(client/C) - if(C && ranged_mousepointer && C.mouse_pointer_icon == ranged_mousepointer) - C.mouse_pointer_icon = initial(C.mouse_pointer_icon) - -/obj/effect/proc_holder/proc/remove_ranged_ability(mob/living/user, var/msg) - if(!user || !user.client || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability - return - user.ranged_ability = null - ranged_ability_user = null - user.client.click_intercept = null - remove_mousepointer(user.client) - active = FALSE - if(msg) - to_chat(user, msg) - update_icon() - -/obj/effect/proc_holder/spell - name = "Spell" - desc = "A wizard spell" - panel = "Spells"//What panel the proc holder needs to go on. - density = 0 - opacity = 0 - - var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit? - - var/charge_type = "recharge" //can be recharge or charges, see charge_max and charge_counter descriptions; can also be based on the holder's vars now, use "holder_var" for that - - var/charge_max = 100 //recharge time in deciseconds if charge_type = "recharge" or starting charges if charge_type = "charges" - var/starts_charged = TRUE //Does this spell start ready to go? - var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = "recharge" or -- each cast if charge_type = "charges" - var/still_recharging_msg = "The spell is still recharging." - - var/holder_var_type = "bruteloss" //only used if charge_type equals to "holder_var" - var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used - - var/ghost = 0 // Skip life check. - var/clothes_req = 1 //see if it requires clothes - var/human_req = 0 //spell can only be cast by humans - var/nonabstract_req = 0 //spell can only be cast by mobs that are physical entities - var/stat_allowed = 0 //see if it requires being conscious/alive, need to set to 1 for ghostpells - var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell - var/invocation_emote_self = null - var/invocation_type = "none" //can be none, whisper and shout - var/range = 7 //the range of the spell; outer radius for aoe spells - var/message = "" //whatever it says to the guy affected by it - var/selection_type = "view" //can be "range" or "view" - var/spell_level = 0 //if a spell can be taken multiple times, this raises - var/level_max = 4 //The max possible level_max is 4 - var/cooldown_min = 0 //This defines what spell quickened four timeshas as a cooldown. Make sure to set this for every spell - - var/overlay = 0 - var/overlay_icon = 'icons/obj/wizard.dmi' - var/overlay_icon_state = "spell" - var/overlay_lifespan = 0 - - var/sparks_spread = 0 - var/sparks_amt = 0 //cropped at 10 - var/smoke_spread = 0 //1 - harmless, 2 - harmful - var/smoke_amt = 0 //cropped at 10 - - var/critfailchance = 0 - var/centcom_cancast = 1 //Whether or not the spell should be allowed on z2 - - var/datum/action/spell_action/action = null - var/action_icon = 'icons/mob/actions/actions.dmi' - var/action_icon_state = "spell_default" - var/action_background_icon_state = "bg_spell" - var/special_availability_check = 0//Whether the spell needs to bypass the action button's IsAvailable() - - var/sound = null //The sound the spell makes when it is cast - -/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell - if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list)) - to_chat(user, "You shouldn't have this spell! Something's wrong.") - return 0 - - if(ishuman(user)) - var/mob/living/carbon/human/caster = user - if(caster.remoteview_target) - caster.remoteview_target = null - caster.reset_perspective(0) - return 0 - - if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel - return 0 - - if(!skipcharge) - switch(charge_type) - if("recharge") - if(charge_counter < charge_max) - to_chat(user, still_recharging_msg) - return 0 - if("charges") - if(!charge_counter) - to_chat(user, "[name] has no charges left.") - return 0 - - if(!ghost) - if(user.stat && !stat_allowed) - to_chat(user, "You can't cast this spell while incapacitated.") - return 0 - if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled()) - to_chat(user, "Mmmf mrrfff!") - return 0 - - var/obj/effect/proc_holder/spell/noclothes/clothes_spell = locate() in (user.mob_spell_list | (user.mind ? user.mind.spell_list : list())) - if((ishuman(user) && clothes_req) && !istype(clothes_spell))//clothes check - var/mob/living/carbon/human/H = user - var/obj/item/clothing/robe = H.wear_suit - var/obj/item/clothing/hat = H.head - var/obj/item/clothing/shoes = H.shoes - if(!robe || !hat || !shoes) - to_chat(user, "Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.") - return 0 - if(!robe.magical || !hat.magical || !shoes.magical) - to_chat(user, "Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.") - return 0 - else if(!ishuman(user)) - if(clothes_req || human_req) - to_chat(user, "This spell can only be cast by humans!") - return 0 - if(nonabstract_req && (isbrain(user) || ispAI(user))) - to_chat(user, "This spell can only be cast by physical beings!") - return 0 - - if(!skipcharge) - switch(charge_type) - if("recharge") - charge_counter = 0 //doesn't start recharging until the targets selecting ends - if("charges") - charge_counter-- //returns the charge if the targets selecting fails - if("holdervar") - adjust_var(user, holder_var_type, holder_var_amount) - - if(action) - action.UpdateButtonIcon() - return 1 - -/obj/effect/proc_holder/spell/proc/invocation(mob/user = usr) //spelling the spell out and setting it on recharge/reducing charges amount - switch(invocation_type) - if("shout") - if(!user.IsVocal()) - user.custom_emote(1, "makes frantic gestures!") - else - if(prob(50))//Auto-mute? Fuck that noise - user.say(invocation) - else - user.say(replacetext(invocation," ","`")) - if("whisper") - if(prob(50)) - user.whisper(invocation) - else - user.whisper(replacetext(invocation," ","`")) - if("emote") - user.visible_message(invocation, invocation_emote_self) //same style as in mob/living/emote.dm - -/obj/effect/proc_holder/spell/proc/playMagSound() - playsound(get_turf(usr), sound,50,1) - -/obj/effect/proc_holder/spell/New() - ..() - action = new(src) - still_recharging_msg = "[name] is still recharging." - if(starts_charged) - charge_counter = charge_max - else - start_recharge() - -/obj/effect/proc_holder/spell/Destroy() - QDEL_NULL(action) - return ..() - -/obj/effect/proc_holder/spell/Click() - if(cast_check()) - choose_targets() - return 1 - -/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf - return - -/obj/effect/proc_holder/spell/proc/start_recharge() - if(action) - action.UpdateButtonIcon() - START_PROCESSING(SSfastprocess, src) - -/obj/effect/proc_holder/spell/process() - charge_counter += 2 - if(charge_counter < charge_max) - return - STOP_PROCESSING(SSfastprocess, src) - charge_counter = charge_max - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = 1, mob/user = usr) //if recharge is started is important for the trigger spells - before_cast(targets) - invocation() - if(user && user.ckey) - user.create_attack_log("[key_name(user)] cast the spell [name].") - spawn(0) - if(charge_type == "recharge" && recharge) - start_recharge() - - if(sound) - playMagSound() - - if(prob(critfailchance)) - critfail(targets) - else - cast(targets, user = user) - after_cast(targets) - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/proc/before_cast(list/targets) - if(overlay) - for(var/atom/target in targets) - var/location - if(istype(target,/mob/living)) - location = target.loc - else if(istype(target,/turf)) - location = target - var/obj/effect/overlay/spell = new /obj/effect/overlay(location) - spell.icon = overlay_icon - spell.icon_state = overlay_icon_state - spell.anchored = 1 - spell.density = 0 - spawn(overlay_lifespan) - qdel(spell) - -/obj/effect/proc_holder/spell/proc/after_cast(list/targets) - for(var/atom/target in targets) - var/location - if(istype(target,/mob/living)) - location = target.loc - else if(istype(target,/turf)) - location = target - if(istype(target,/mob/living) && message) - to_chat(target, text("[message]")) - if(sparks_spread) - do_sparks(sparks_amt, 0, location) - if(smoke_spread) - if(smoke_spread == 1) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is - smoke.start() - else if(smoke_spread == 2) - var/datum/effect_system/smoke_spread/bad/smoke = new - smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is - smoke.start() - else if(smoke_spread == 3) - var/datum/effect_system/smoke_spread/sleeping/smoke = new - smoke.set_up(smoke_amt, 0, location) // same here - smoke.start() - -/obj/effect/proc_holder/spell/proc/cast(list/targets, mob/user = usr) - return - -/obj/effect/proc_holder/spell/proc/critfail(list/targets) - return - -/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge - switch(charge_type) - if("recharge") - charge_counter = charge_max - if("charges") - charge_counter++ - if("holdervar") - adjust_var(user, holder_var_type, -holder_var_amount) - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/proc/updateButtonIcon() - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types - switch(type) - if("bruteloss") - target.adjustBruteLoss(amount) - if("fireloss") - target.adjustFireLoss(amount) - if("toxloss") - target.adjustToxLoss(amount) - if("oxyloss") - target.adjustOxyLoss(amount) - if("stunned") - target.AdjustStunned(amount) - if("weakened") - target.AdjustWeakened(amount) - if("paralysis") - target.AdjustParalysis(amount) - else - target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existant vars - return - -/obj/effect/proc_holder/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob - var/max_targets = 1 //leave 0 for unlimited targets in range, 1 for one selectable target in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range - var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast - var/include_user = 0 //if it includes usr in the target list - var/random_target = 0 // chooses random viable target instead of asking the caster - var/random_target_priority = TARGET_CLOSEST // if random_target is enabled how it will pick the target - var/humans_only = 0 //for avoiding simple animals and only doing "human" mobs, 0 = all mobs, 1 = humans only - -/obj/effect/proc_holder/spell/aoe_turf //affects all turfs in view or range (depends) - var/inner_radius = -1 //for all your ring spell needs - -/obj/effect/proc_holder/spell/targeted/choose_targets(mob/user = usr) - var/list/targets = list() - - switch(max_targets) - if(0) //unlimited - - if(!humans_only) - for(var/mob/living/target in view_or_range(range, user, selection_type)) - targets += target - else - for(var/mob/living/carbon/human/target in view_or_range(range, user, selection_type)) - targets += target - - if(1) //single target can be picked - if(range < 0) - targets += user - else - var/possible_targets = list() - - if(!humans_only) - for(var/mob/living/M in view_or_range(range, user, selection_type)) - if(!include_user && user == M) - continue - possible_targets += M - else - for(var/mob/living/carbon/human/M in view_or_range(range, user, selection_type)) - if(!include_user && user == M) - continue - possible_targets += M - - //targets += input("Choose the target for the spell.", "Targeting") as mob in possible_targets - //Adds a safety check post-input to make sure those targets are actually in range. - var/mob/M - if(!random_target) - M = input("Choose the target for the spell.", "Targeting") as mob in possible_targets - else - switch(random_target_priority) - if(TARGET_RANDOM) - M = pick(possible_targets) - if(TARGET_CLOSEST) - for(var/mob/living/L in possible_targets) - if(M) - if(get_dist(user,L) < get_dist(user,M)) - if(los_check(user,L)) - M = L - else - if(los_check(user,L)) - M = L - if(M in view_or_range(range, user, selection_type)) targets += M - - else - var/list/possible_targets = list() - if(!humans_only) - for(var/mob/living/target in view_or_range(range, user, selection_type)) - possible_targets += target - else - for(var/mob/living/carbon/human/target in view_or_range(range, user, selection_type)) - possible_targets += target - for(var/i=1,i<=max_targets,i++) - if(!possible_targets.len) - break - if(target_ignore_prev) - var/target = pick(possible_targets) - possible_targets -= target - targets += target - else - targets += pick(possible_targets) - - if(!include_user && (user in targets)) - targets -= user - - if(!targets.len) //doesn't waste the spell - revert_cast(user) - return - - perform(targets, user=user) - - return - -/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr) - var/list/targets = list() - - for(var/turf/target in view_or_range(range,user,selection_type)) - if(!(target in view_or_range(inner_radius,user,selection_type))) - targets += target - - if(!targets.len) //doesn't waste the spell - revert_cast() - return - - perform(targets, user=user) - - return - - -/obj/effect/proc_holder/spell/targeted/proc/los_check(mob/A,mob/B) - //Checks for obstacles from A to B - var/obj/dummy = new(A.loc) - dummy.pass_flags |= PASSTABLE - for(var/turf/turf in getline(A,B)) - for(var/atom/movable/AM in turf) - if(!AM.CanPass(dummy,turf,1)) - qdel(dummy) - return 0 - qdel(dummy) - return 1 - -/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr) - if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list)) - return 0 - - if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel - return 0 - - switch(charge_type) - if("recharge") - if(charge_counter < charge_max) - return 0 - if("charges") - if(!charge_counter) - return 0 - - if(user.stat && !stat_allowed) - return 0 - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - - if((invocation_type == "whisper" || invocation_type == "shout") && H.is_muzzled()) - return 0 - - var/clothcheck = locate(/obj/effect/proc_holder/spell/noclothes) in user.mob_spell_list - var/clothcheck2 = user.mind && (locate(/obj/effect/proc_holder/spell/noclothes) in user.mind.spell_list) - if(clothes_req && !clothcheck && !clothcheck2) //clothes check - var/obj/item/clothing/robe = H.wear_suit - var/obj/item/clothing/hat = H.head - var/obj/item/clothing/shoes = H.shoes - if(!robe || !hat || !shoes) - return 0 - if(!robe.magical || !hat.magical || !shoes.magical) - return 0 - else - if(clothes_req || human_req) - return 0 - if(nonabstract_req && (isbrain(user) || ispAI(user))) - return 0 - return 1 +#define TARGET_CLOSEST 1 +#define TARGET_RANDOM 2 + +/obj/effect/proc_holder + var/panel = "Debug"//What panel the proc holder needs to go on. + var/active = FALSE //Used by toggle based abilities. + var/ranged_mousepointer + var/mob/living/ranged_ability_user + +/obj/effect/proc_holder/singularity_act() + return + +/obj/effect/proc_holder/singularity_pull() + return + +var/list/spells = typesof(/obj/effect/proc_holder/spell) //needed for the badmin verb for now + +/obj/effect/proc_holder/proc/InterceptClickOn(mob/living/user, params, atom/A) + if(user.ranged_ability != src) + to_chat(user, "[user.ranged_ability.name] has been disabled.") + user.ranged_ability.remove_ranged_ability(user) + return TRUE //TRUE for failed, FALSE for passed. + user.changeNext_click(CLICK_CD_CLICK_ABILITY) + user.face_atom(A) + return FALSE + +/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, var/msg) + if(!user || !user.client) + return + if(user.ranged_ability && user.ranged_ability != src) + to_chat(user, "[user.ranged_ability.name] has been replaced by [name].") + user.ranged_ability.remove_ranged_ability(user) + user.ranged_ability = src + ranged_ability_user = user + user.client.click_intercept = user.ranged_ability + add_mousepointer(user.client) + active = TRUE + if(msg) + to_chat(user, msg) + update_icon() + +/obj/effect/proc_holder/proc/add_mousepointer(client/C) + if(C && ranged_mousepointer && C.mouse_pointer_icon == initial(C.mouse_pointer_icon)) + C.mouse_pointer_icon = ranged_mousepointer + +/obj/effect/proc_holder/proc/remove_mousepointer(client/C) + if(C && ranged_mousepointer && C.mouse_pointer_icon == ranged_mousepointer) + C.mouse_pointer_icon = initial(C.mouse_pointer_icon) + +/obj/effect/proc_holder/proc/remove_ranged_ability(mob/living/user, var/msg) + if(!user || !user.client || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability + return + user.ranged_ability = null + ranged_ability_user = null + user.client.click_intercept = null + remove_mousepointer(user.client) + active = FALSE + if(msg) + to_chat(user, msg) + update_icon() + +/obj/effect/proc_holder/spell + name = "Spell" + desc = "A wizard spell" + panel = "Spells"//What panel the proc holder needs to go on. + density = 0 + opacity = 0 + + var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit? + + var/charge_type = "recharge" //can be recharge or charges, see charge_max and charge_counter descriptions; can also be based on the holder's vars now, use "holder_var" for that + + var/charge_max = 100 //recharge time in deciseconds if charge_type = "recharge" or starting charges if charge_type = "charges" + var/starts_charged = TRUE //Does this spell start ready to go? + var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each decisecond if charge_type = "recharge" or -- each cast if charge_type = "charges" + var/still_recharging_msg = "The spell is still recharging." + + var/holder_var_type = "bruteloss" //only used if charge_type equals to "holder_var" + var/holder_var_amount = 20 //same. The amount adjusted with the mob's var when the spell is used + + var/ghost = 0 // Skip life check. + var/clothes_req = 1 //see if it requires clothes + var/human_req = 0 //spell can only be cast by humans + var/nonabstract_req = 0 //spell can only be cast by mobs that are physical entities + var/stat_allowed = 0 //see if it requires being conscious/alive, need to set to 1 for ghostpells + var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell + var/invocation_emote_self = null + var/invocation_type = "none" //can be none, whisper and shout + var/range = 7 //the range of the spell; outer radius for aoe spells + var/message = "" //whatever it says to the guy affected by it + var/selection_type = "view" //can be "range" or "view" + var/spell_level = 0 //if a spell can be taken multiple times, this raises + var/level_max = 4 //The max possible level_max is 4 + var/cooldown_min = 0 //This defines what spell quickened four timeshas as a cooldown. Make sure to set this for every spell + + var/overlay = 0 + var/overlay_icon = 'icons/obj/wizard.dmi' + var/overlay_icon_state = "spell" + var/overlay_lifespan = 0 + + var/sparks_spread = 0 + var/sparks_amt = 0 //cropped at 10 + var/smoke_spread = 0 //1 - harmless, 2 - harmful + var/smoke_amt = 0 //cropped at 10 + + var/critfailchance = 0 + var/centcom_cancast = 1 //Whether or not the spell should be allowed on z2 + + var/datum/action/spell_action/action = null + var/action_icon = 'icons/mob/actions/actions.dmi' + var/action_icon_state = "spell_default" + var/action_background_icon_state = "bg_spell" + var/special_availability_check = 0//Whether the spell needs to bypass the action button's IsAvailable() + + var/sound = null //The sound the spell makes when it is cast + +/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell + if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list)) + to_chat(user, "You shouldn't have this spell! Something's wrong.") + return 0 + + if(ishuman(user)) + var/mob/living/carbon/human/caster = user + if(caster.remoteview_target) + caster.remoteview_target = null + caster.reset_perspective(0) + return 0 + + if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel + return 0 + + if(!skipcharge) + switch(charge_type) + if("recharge") + if(charge_counter < charge_max) + to_chat(user, still_recharging_msg) + return 0 + if("charges") + if(!charge_counter) + to_chat(user, "[name] has no charges left.") + return 0 + + if(!ghost) + if(user.stat && !stat_allowed) + to_chat(user, "You can't cast this spell while incapacitated.") + return 0 + if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled()) + to_chat(user, "Mmmf mrrfff!") + return 0 + + var/obj/effect/proc_holder/spell/noclothes/clothes_spell = locate() in (user.mob_spell_list | (user.mind ? user.mind.spell_list : list())) + if((ishuman(user) && clothes_req) && !istype(clothes_spell))//clothes check + var/mob/living/carbon/human/H = user + var/obj/item/clothing/robe = H.wear_suit + var/obj/item/clothing/hat = H.head + var/obj/item/clothing/shoes = H.shoes + if(!robe || !hat || !shoes) + to_chat(user, "Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.") + return 0 + if(!robe.magical || !hat.magical || !shoes.magical) + to_chat(user, "Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.") + return 0 + else if(!ishuman(user)) + if(clothes_req || human_req) + to_chat(user, "This spell can only be cast by humans!") + return 0 + if(nonabstract_req && (isbrain(user) || ispAI(user))) + to_chat(user, "This spell can only be cast by physical beings!") + return 0 + + if(!skipcharge) + switch(charge_type) + if("recharge") + charge_counter = 0 //doesn't start recharging until the targets selecting ends + if("charges") + charge_counter-- //returns the charge if the targets selecting fails + if("holdervar") + adjust_var(user, holder_var_type, holder_var_amount) + + if(action) + action.UpdateButtonIcon() + return 1 + +/obj/effect/proc_holder/spell/proc/invocation(mob/user = usr) //spelling the spell out and setting it on recharge/reducing charges amount + switch(invocation_type) + if("shout") + if(!user.IsVocal()) + user.custom_emote(1, "makes frantic gestures!") + else + if(prob(50))//Auto-mute? Fuck that noise + user.say(invocation) + else + user.say(replacetext(invocation," ","`")) + if("whisper") + if(prob(50)) + user.whisper(invocation) + else + user.whisper(replacetext(invocation," ","`")) + if("emote") + user.visible_message(invocation, invocation_emote_self) //same style as in mob/living/emote.dm + +/obj/effect/proc_holder/spell/proc/playMagSound() + playsound(get_turf(usr), sound,50,1) + +/obj/effect/proc_holder/spell/New() + ..() + action = new(src) + still_recharging_msg = "[name] is still recharging." + if(starts_charged) + charge_counter = charge_max + else + start_recharge() + +/obj/effect/proc_holder/spell/Destroy() + QDEL_NULL(action) + return ..() + +/obj/effect/proc_holder/spell/Click() + if(cast_check()) + choose_targets() + return 1 + +/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf + return + +/obj/effect/proc_holder/spell/proc/start_recharge() + if(action) + action.UpdateButtonIcon() + START_PROCESSING(SSfastprocess, src) + +/obj/effect/proc_holder/spell/process() + charge_counter += 2 + if(charge_counter < charge_max) + return + STOP_PROCESSING(SSfastprocess, src) + charge_counter = charge_max + if(action) + action.UpdateButtonIcon() + +/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = 1, mob/user = usr) //if recharge is started is important for the trigger spells + before_cast(targets) + invocation() + if(user && user.ckey) + user.create_attack_log("[key_name(user)] cast the spell [name].") + spawn(0) + if(charge_type == "recharge" && recharge) + start_recharge() + + if(sound) + playMagSound() + + if(prob(critfailchance)) + critfail(targets) + else + cast(targets, user = user) + after_cast(targets) + if(action) + action.UpdateButtonIcon() + +/obj/effect/proc_holder/spell/proc/before_cast(list/targets) + if(overlay) + for(var/atom/target in targets) + var/location + if(istype(target,/mob/living)) + location = target.loc + else if(istype(target,/turf)) + location = target + var/obj/effect/overlay/spell = new /obj/effect/overlay(location) + spell.icon = overlay_icon + spell.icon_state = overlay_icon_state + spell.anchored = 1 + spell.density = 0 + spawn(overlay_lifespan) + qdel(spell) + +/obj/effect/proc_holder/spell/proc/after_cast(list/targets) + for(var/atom/target in targets) + var/location + if(istype(target,/mob/living)) + location = target.loc + else if(istype(target,/turf)) + location = target + if(istype(target,/mob/living) && message) + to_chat(target, text("[message]")) + if(sparks_spread) + do_sparks(sparks_amt, 0, location) + if(smoke_spread) + if(smoke_spread == 1) + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is + smoke.start() + else if(smoke_spread == 2) + var/datum/effect_system/smoke_spread/bad/smoke = new + smoke.set_up(smoke_amt, 0, location) //no idea what the 0 is + smoke.start() + else if(smoke_spread == 3) + var/datum/effect_system/smoke_spread/sleeping/smoke = new + smoke.set_up(smoke_amt, 0, location) // same here + smoke.start() + +/obj/effect/proc_holder/spell/proc/cast(list/targets, mob/user = usr) + return + +/obj/effect/proc_holder/spell/proc/critfail(list/targets) + return + +/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge + switch(charge_type) + if("recharge") + charge_counter = charge_max + if("charges") + charge_counter++ + if("holdervar") + adjust_var(user, holder_var_type, -holder_var_amount) + if(action) + action.UpdateButtonIcon() + +/obj/effect/proc_holder/spell/proc/updateButtonIcon() + if(action) + action.UpdateButtonIcon() + +/obj/effect/proc_holder/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types + switch(type) + if("bruteloss") + target.adjustBruteLoss(amount) + if("fireloss") + target.adjustFireLoss(amount) + if("toxloss") + target.adjustToxLoss(amount) + if("oxyloss") + target.adjustOxyLoss(amount) + if("stunned") + target.AdjustStunned(amount) + if("weakened") + target.AdjustWeakened(amount) + if("paralysis") + target.AdjustParalysis(amount) + else + target.vars[type] += amount //I bear no responsibility for the runtimes that'll happen if you try to adjust non-numeric or even non-existant vars + return + +/obj/effect/proc_holder/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob + var/max_targets = 1 //leave 0 for unlimited targets in range, 1 for one selectable target in range, more for limited number of casts (can all target one guy, depends on target_ignore_prev) in range + var/target_ignore_prev = 1 //only important if max_targets > 1, affects if the spell can be cast multiple times at one person from one cast + var/include_user = 0 //if it includes usr in the target list + var/random_target = 0 // chooses random viable target instead of asking the caster + var/random_target_priority = TARGET_CLOSEST // if random_target is enabled how it will pick the target + var/humans_only = 0 //for avoiding simple animals and only doing "human" mobs, 0 = all mobs, 1 = humans only + +/obj/effect/proc_holder/spell/aoe_turf //affects all turfs in view or range (depends) + var/inner_radius = -1 //for all your ring spell needs + +/obj/effect/proc_holder/spell/targeted/choose_targets(mob/user = usr) + var/list/targets = list() + + switch(max_targets) + if(0) //unlimited + + if(!humans_only) + for(var/mob/living/target in view_or_range(range, user, selection_type)) + targets += target + else + for(var/mob/living/carbon/human/target in view_or_range(range, user, selection_type)) + targets += target + + if(1) //single target can be picked + if(range < 0) + targets += user + else + var/possible_targets = list() + + if(!humans_only) + for(var/mob/living/M in view_or_range(range, user, selection_type)) + if(!include_user && user == M) + continue + possible_targets += M + else + for(var/mob/living/carbon/human/M in view_or_range(range, user, selection_type)) + if(!include_user && user == M) + continue + possible_targets += M + + //targets += input("Choose the target for the spell.", "Targeting") as mob in possible_targets + //Adds a safety check post-input to make sure those targets are actually in range. + var/mob/M + if(!random_target) + M = input("Choose the target for the spell.", "Targeting") as mob in possible_targets + else + switch(random_target_priority) + if(TARGET_RANDOM) + M = pick(possible_targets) + if(TARGET_CLOSEST) + for(var/mob/living/L in possible_targets) + if(M) + if(get_dist(user,L) < get_dist(user,M)) + if(los_check(user,L)) + M = L + else + if(los_check(user,L)) + M = L + if(M in view_or_range(range, user, selection_type)) targets += M + + else + var/list/possible_targets = list() + if(!humans_only) + for(var/mob/living/target in view_or_range(range, user, selection_type)) + possible_targets += target + else + for(var/mob/living/carbon/human/target in view_or_range(range, user, selection_type)) + possible_targets += target + for(var/i=1,i<=max_targets,i++) + if(!possible_targets.len) + break + if(target_ignore_prev) + var/target = pick(possible_targets) + possible_targets -= target + targets += target + else + targets += pick(possible_targets) + + if(!include_user && (user in targets)) + targets -= user + + if(!targets.len) //doesn't waste the spell + revert_cast(user) + return + + perform(targets, user=user) + + return + +/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr) + var/list/targets = list() + + for(var/turf/target in view_or_range(range,user,selection_type)) + if(!(target in view_or_range(inner_radius,user,selection_type))) + targets += target + + if(!targets.len) //doesn't waste the spell + revert_cast() + return + + perform(targets, user=user) + + return + + +/obj/effect/proc_holder/spell/targeted/proc/los_check(mob/A,mob/B) + //Checks for obstacles from A to B + var/obj/dummy = new(A.loc) + dummy.pass_flags |= PASSTABLE + for(var/turf/turf in getline(A,B)) + for(var/atom/movable/AM in turf) + if(!AM.CanPass(dummy,turf,1)) + qdel(dummy) + return 0 + qdel(dummy) + return 1 + +/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr) + if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list)) + return 0 + + if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel + return 0 + + switch(charge_type) + if("recharge") + if(charge_counter < charge_max) + return 0 + if("charges") + if(!charge_counter) + return 0 + + if(user.stat && !stat_allowed) + return 0 + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + + if((invocation_type == "whisper" || invocation_type == "shout") && H.is_muzzled()) + return 0 + + var/clothcheck = locate(/obj/effect/proc_holder/spell/noclothes) in user.mob_spell_list + var/clothcheck2 = user.mind && (locate(/obj/effect/proc_holder/spell/noclothes) in user.mind.spell_list) + if(clothes_req && !clothcheck && !clothcheck2) //clothes check + var/obj/item/clothing/robe = H.wear_suit + var/obj/item/clothing/hat = H.head + var/obj/item/clothing/shoes = H.shoes + if(!robe || !hat || !shoes) + return 0 + if(!robe.magical || !hat.magical || !shoes.magical) + return 0 + else + if(clothes_req || human_req) + return 0 + if(nonabstract_req && (isbrain(user) || ispAI(user))) + return 0 + return 1 diff --git a/code/datums/spells/area_teleport.dm b/code/datums/spells/area_teleport.dm index 4aa7eafbed6..fbde291ae98 100644 --- a/code/datums/spells/area_teleport.dm +++ b/code/datums/spells/area_teleport.dm @@ -1,98 +1,98 @@ -/obj/effect/proc_holder/spell/targeted/area_teleport - name = "Area teleport" - desc = "This spell teleports you to a type of area of your selection." - nonabstract_req = 1 - - var/randomise_selection = 0 //if it lets the usr choose the teleport loc or picks it from the list - var/invocation_area = 1 //if the invocation appends the selected area - - var/sound1 = 'sound/weapons/zapbang.ogg' - var/sound2 = 'sound/weapons/zapbang.ogg' - -/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1, mob/living/user = usr) - var/thearea = before_cast(targets) - if(!thearea || !cast_check(1)) - revert_cast() - return - invocation(thearea) - spawn(0) - if(charge_type == "recharge" && recharge) - start_recharge() - cast(targets,thearea) - after_cast(targets) - -/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets) - var/A = null - - if(!randomise_selection) - A = input("Area to teleport to", "Teleport", A) as null|anything in teleportlocs - else - A = pick(teleportlocs) - - if(!A) - return - - var/area/thearea = teleportlocs[A] - - if(thearea.tele_proof && !istype(thearea, /area/wizard_station)) - to_chat(usr, "A mysterious force disrupts your arcane spell matrix, and you remain where you are.") - return - - return thearea - -/obj/effect/proc_holder/spell/targeted/area_teleport/cast(list/targets,area/thearea,mob/living/user = usr) - playsound(get_turf(user), sound1, 50,1) - for(var/mob/living/target in targets) - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(!T.density) - var/clear = 1 - for(var/obj/O in T) - if(O.density) - clear = 0 - break - if(clear) - L+=T - - if(!L.len) - to_chat(usr, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") - return - - if(target && target.buckled) - target.buckled.unbuckle_mob(target, force = TRUE) - - if(target && target.has_buckled_mobs()) - target.unbuckle_all_mobs(force = TRUE) - - var/list/tempL = L - var/attempt = null - var/success = 0 - while(tempL.len) - attempt = pick(tempL) - success = target.Move(attempt) - if(!success) - tempL.Remove(attempt) - else - break - - if(!success) - target.forceMove(pick(L)) - playsound(get_turf(user), sound2, 50,1) - - return - -/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null) - if(!invocation_area || !chosenarea) - ..() - else - switch(invocation_type) - if("shout") - usr.say("[invocation] [uppertext(chosenarea.name)]") - if(usr.gender==MALE) - playsound(usr.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) - else - playsound(usr.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) - if("whisper") - usr.whisper("[invocation] [uppertext(chosenarea.name)]") - - return \ No newline at end of file +/obj/effect/proc_holder/spell/targeted/area_teleport + name = "Area teleport" + desc = "This spell teleports you to a type of area of your selection." + nonabstract_req = 1 + + var/randomise_selection = 0 //if it lets the usr choose the teleport loc or picks it from the list + var/invocation_area = 1 //if the invocation appends the selected area + + var/sound1 = 'sound/weapons/zapbang.ogg' + var/sound2 = 'sound/weapons/zapbang.ogg' + +/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1, mob/living/user = usr) + var/thearea = before_cast(targets) + if(!thearea || !cast_check(1)) + revert_cast() + return + invocation(thearea) + spawn(0) + if(charge_type == "recharge" && recharge) + start_recharge() + cast(targets,thearea) + after_cast(targets) + +/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets) + var/A = null + + if(!randomise_selection) + A = input("Area to teleport to", "Teleport", A) as null|anything in teleportlocs + else + A = pick(teleportlocs) + + if(!A) + return + + var/area/thearea = teleportlocs[A] + + if(thearea.tele_proof && !istype(thearea, /area/wizard_station)) + to_chat(usr, "A mysterious force disrupts your arcane spell matrix, and you remain where you are.") + return + + return thearea + +/obj/effect/proc_holder/spell/targeted/area_teleport/cast(list/targets,area/thearea,mob/living/user = usr) + playsound(get_turf(user), sound1, 50,1) + for(var/mob/living/target in targets) + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + if(!T.density) + var/clear = 1 + for(var/obj/O in T) + if(O.density) + clear = 0 + break + if(clear) + L+=T + + if(!L.len) + to_chat(usr, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") + return + + if(target && target.buckled) + target.buckled.unbuckle_mob(target, force = TRUE) + + if(target && target.has_buckled_mobs()) + target.unbuckle_all_mobs(force = TRUE) + + var/list/tempL = L + var/attempt = null + var/success = 0 + while(tempL.len) + attempt = pick(tempL) + success = target.Move(attempt) + if(!success) + tempL.Remove(attempt) + else + break + + if(!success) + target.forceMove(pick(L)) + playsound(get_turf(user), sound2, 50,1) + + return + +/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null) + if(!invocation_area || !chosenarea) + ..() + else + switch(invocation_type) + if("shout") + usr.say("[invocation] [uppertext(chosenarea.name)]") + if(usr.gender==MALE) + playsound(usr.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) + else + playsound(usr.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) + if("whisper") + usr.whisper("[invocation] [uppertext(chosenarea.name)]") + + return diff --git a/code/datums/spells/banana_touch.dm b/code/datums/spells/banana_touch.dm index 4e2f1d44e51..cbfa158531e 100644 --- a/code/datums/spells/banana_touch.dm +++ b/code/datums/spells/banana_touch.dm @@ -61,4 +61,4 @@ genemutcheck(src, COMICBLOCK, null, MUTCHK_FORCED) if(!(iswizard(src) || (mind && mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE))) //Mutations are permanent on non-wizards. Can still be removed by genetics fuckery but not mutadone. dna.default_blocks.Add(CLUMSYBLOCK) - dna.default_blocks.Add(COMICBLOCK) \ No newline at end of file + dna.default_blocks.Add(COMICBLOCK) diff --git a/code/datums/spells/conjure.dm b/code/datums/spells/conjure.dm index ebf9fa59fe8..306ec8b9987 100644 --- a/code/datums/spells/conjure.dm +++ b/code/datums/spells/conjure.dm @@ -1,70 +1,70 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure - name = "Conjure" - desc = "This spell conjures objs of the specified types in range." - - var/list/summon_type = list() //determines what exactly will be summoned - //should be text, like list("/mob/simple_animal/bot/ed209") - - var/summon_lifespan = 0 // 0=permanent, any other time in deciseconds - var/summon_amt = 1 //amount of objects summoned - var/summon_ignore_density = 0 //if set to 1, adds dense tiles to possible spawn places - var/summon_ignore_prev_spawn_points = 0 //if set to 1, each new object is summoned on a new spawn point - - var/list/newVars = list() //vars of the summoned objects will be replaced with those where they meet - //should have format of list("emagged" = 1,"name" = "Wizard's Justicebot"), for example - var/delay = 1//Go Go Gadget Inheritance - - var/cast_sound = 'sound/items/welder.ogg' - -/obj/effect/proc_holder/spell/aoe_turf/conjure/cast(list/targets,mob/living/user = usr) - playsound(get_turf(user), cast_sound, 50,1) - for(var/turf/T in targets) - if(T.density && !summon_ignore_density) - targets -= T - playsound(get_turf(src), cast_sound, 50, 1) - - if(do_after(user, delay, target = user)) - for(var/i=0,iYou can't build things on shuttles!") - break - var/turf/O = spawn_place - var/N = summoned_object_type - O.ChangeTurf(N) - else - var/atom/summoned_object = new summoned_object_type(spawn_place) - - for(var/varName in newVars) - if(varName in summoned_object.vars) - summoned_object.vars[varName] = newVars[varName] - summoned_object.admin_spawned = TRUE - - if(summon_lifespan) - spawn(summon_lifespan) - if(summoned_object) - qdel(summoned_object) - else - switch(charge_type) - if("recharge") - charge_counter = charge_max - 5//So you don't lose charge for a failed spell(Also prevents most over-fill) - if("charges") - charge_counter++//Ditto, just for different spell types - - - return - -/obj/effect/proc_holder/spell/aoe_turf/conjure/summonEdSwarm //test purposes - name = "Dispense Wizard Justice" - desc = "This spell dispenses wizard justice." - - summon_type = list(/mob/living/simple_animal/bot/ed209) - summon_amt = 10 - range = 3 - newVars = list("emagged" = 1,"name" = "Wizard's Justicebot") \ No newline at end of file +/obj/effect/proc_holder/spell/aoe_turf/conjure + name = "Conjure" + desc = "This spell conjures objs of the specified types in range." + + var/list/summon_type = list() //determines what exactly will be summoned + //should be text, like list("/mob/simple_animal/bot/ed209") + + var/summon_lifespan = 0 // 0=permanent, any other time in deciseconds + var/summon_amt = 1 //amount of objects summoned + var/summon_ignore_density = 0 //if set to 1, adds dense tiles to possible spawn places + var/summon_ignore_prev_spawn_points = 0 //if set to 1, each new object is summoned on a new spawn point + + var/list/newVars = list() //vars of the summoned objects will be replaced with those where they meet + //should have format of list("emagged" = 1,"name" = "Wizard's Justicebot"), for example + var/delay = 1//Go Go Gadget Inheritance + + var/cast_sound = 'sound/items/welder.ogg' + +/obj/effect/proc_holder/spell/aoe_turf/conjure/cast(list/targets,mob/living/user = usr) + playsound(get_turf(user), cast_sound, 50,1) + for(var/turf/T in targets) + if(T.density && !summon_ignore_density) + targets -= T + playsound(get_turf(src), cast_sound, 50, 1) + + if(do_after(user, delay, target = user)) + for(var/i=0,iYou can't build things on shuttles!") + break + var/turf/O = spawn_place + var/N = summoned_object_type + O.ChangeTurf(N) + else + var/atom/summoned_object = new summoned_object_type(spawn_place) + + for(var/varName in newVars) + if(varName in summoned_object.vars) + summoned_object.vars[varName] = newVars[varName] + summoned_object.admin_spawned = TRUE + + if(summon_lifespan) + spawn(summon_lifespan) + if(summoned_object) + qdel(summoned_object) + else + switch(charge_type) + if("recharge") + charge_counter = charge_max - 5//So you don't lose charge for a failed spell(Also prevents most over-fill) + if("charges") + charge_counter++//Ditto, just for different spell types + + + return + +/obj/effect/proc_holder/spell/aoe_turf/conjure/summonEdSwarm //test purposes + name = "Dispense Wizard Justice" + desc = "This spell dispenses wizard justice." + + summon_type = list(/mob/living/simple_animal/bot/ed209) + summon_amt = 10 + range = 3 + newVars = list("emagged" = 1,"name" = "Wizard's Justicebot") diff --git a/code/datums/spells/dumbfire.dm b/code/datums/spells/dumbfire.dm index ca8cc43df9c..9efb93a0e7b 100644 --- a/code/datums/spells/dumbfire.dm +++ b/code/datums/spells/dumbfire.dm @@ -1,84 +1,84 @@ -/obj/effect/proc_holder/spell/dumbfire - - var/projectile_type = "" - var/activate_on_collision = 1 - - var/proj_icon = 'icons/obj/projectiles.dmi' - var/proj_icon_state = "spell" - var/proj_name = "a spell projectile" - - var/proj_trail = 0 //if it leaves a trail - var/proj_trail_lifespan = 0 //deciseconds - var/proj_trail_icon = 'icons/obj/wizard.dmi' - var/proj_trail_icon_state = "trail" - - var/proj_type = /obj/effect/proc_holder/spell //IMPORTANT use only subtypes of this - - var/proj_insubstantial = 0 //if it can pass through dense objects or not - var/proj_trigger_range = 1 //the range from target at which the projectile triggers cast(target) - - var/proj_lifespan = 100 //in deciseconds * proj_step_delay - var/proj_step_delay = 1 //lower = faster - -/obj/effect/proc_holder/spell/dumbfire/choose_targets(mob/user = usr) - - var/turf/T = get_turf(usr) - for(var/i = 1; i < range; i++) - var/turf/new_turf = get_step(T, usr.dir) - if(new_turf.density) - break - T = new_turf - perform(list(T), user = user) - -/obj/effect/proc_holder/spell/dumbfire/cast(list/targets, mob/user = usr) - - for(var/turf/target in targets) - spawn(0) - var/obj/effect/proc_holder/spell/targeted/projectile - projectile = new proj_type(user) - projectile.icon = proj_icon - projectile.icon_state = proj_icon_state - projectile.dir = get_dir(projectile, target) - projectile.name = proj_name - - var/current_loc = user.loc - - projectile.loc = current_loc - - for(var/i = 0,i < proj_lifespan,i++) - if(!projectile) - break - - if(proj_insubstantial) - projectile.loc = get_step(projectile, projectile.dir) - else - step(projectile, projectile.dir) - - if(projectile.loc == current_loc || i == proj_lifespan) - projectile.cast(current_loc) - break - - var/mob/living/L = locate(/mob/living) in range(projectile, proj_trigger_range) - user - if(L && L.stat != DEAD) - projectile.cast(L.loc) - break - - if(proj_trail && projectile) - spawn(0) - if(projectile) - var/obj/effect/overlay/trail = new /obj/effect/overlay(projectile.loc) - trail.icon = proj_trail_icon - trail.icon_state = proj_trail_icon_state - trail.density = 0 - spawn(proj_trail_lifespan) - qdel(trail) - - current_loc = projectile.loc - var/matrix/M = new - M.Turn(dir2angle(projectile.dir)) - projectile.transform = M - - sleep(proj_step_delay) - - if(projectile) - qdel(projectile) +/obj/effect/proc_holder/spell/dumbfire + + var/projectile_type = "" + var/activate_on_collision = 1 + + var/proj_icon = 'icons/obj/projectiles.dmi' + var/proj_icon_state = "spell" + var/proj_name = "a spell projectile" + + var/proj_trail = 0 //if it leaves a trail + var/proj_trail_lifespan = 0 //deciseconds + var/proj_trail_icon = 'icons/obj/wizard.dmi' + var/proj_trail_icon_state = "trail" + + var/proj_type = /obj/effect/proc_holder/spell //IMPORTANT use only subtypes of this + + var/proj_insubstantial = 0 //if it can pass through dense objects or not + var/proj_trigger_range = 1 //the range from target at which the projectile triggers cast(target) + + var/proj_lifespan = 100 //in deciseconds * proj_step_delay + var/proj_step_delay = 1 //lower = faster + +/obj/effect/proc_holder/spell/dumbfire/choose_targets(mob/user = usr) + + var/turf/T = get_turf(usr) + for(var/i = 1; i < range; i++) + var/turf/new_turf = get_step(T, usr.dir) + if(new_turf.density) + break + T = new_turf + perform(list(T), user = user) + +/obj/effect/proc_holder/spell/dumbfire/cast(list/targets, mob/user = usr) + + for(var/turf/target in targets) + spawn(0) + var/obj/effect/proc_holder/spell/targeted/projectile + projectile = new proj_type(user) + projectile.icon = proj_icon + projectile.icon_state = proj_icon_state + projectile.dir = get_dir(projectile, target) + projectile.name = proj_name + + var/current_loc = user.loc + + projectile.loc = current_loc + + for(var/i = 0,i < proj_lifespan,i++) + if(!projectile) + break + + if(proj_insubstantial) + projectile.loc = get_step(projectile, projectile.dir) + else + step(projectile, projectile.dir) + + if(projectile.loc == current_loc || i == proj_lifespan) + projectile.cast(current_loc) + break + + var/mob/living/L = locate(/mob/living) in range(projectile, proj_trigger_range) - user + if(L && L.stat != DEAD) + projectile.cast(L.loc) + break + + if(proj_trail && projectile) + spawn(0) + if(projectile) + var/obj/effect/overlay/trail = new /obj/effect/overlay(projectile.loc) + trail.icon = proj_trail_icon + trail.icon_state = proj_trail_icon_state + trail.density = 0 + spawn(proj_trail_lifespan) + qdel(trail) + + current_loc = projectile.loc + var/matrix/M = new + M.Turn(dir2angle(projectile.dir)) + projectile.transform = M + + sleep(proj_step_delay) + + if(projectile) + qdel(projectile) diff --git a/code/datums/spells/emplosion.dm b/code/datums/spells/emplosion.dm index b2f0a228282..cd84432aba8 100644 --- a/code/datums/spells/emplosion.dm +++ b/code/datums/spells/emplosion.dm @@ -1,15 +1,15 @@ -/obj/effect/proc_holder/spell/targeted/emplosion - name = "Emplosion" - desc = "This spell emplodes an area." - - var/emp_heavy = 2 - var/emp_light = 3 - - action_icon_state = "emp" - -/obj/effect/proc_holder/spell/targeted/emplosion/cast(list/targets, mob/user = usr) - - for(var/mob/living/target in targets) - empulse(target.loc, emp_heavy, emp_light, 1) - - return +/obj/effect/proc_holder/spell/targeted/emplosion + name = "Emplosion" + desc = "This spell emplodes an area." + + var/emp_heavy = 2 + var/emp_light = 3 + + action_icon_state = "emp" + +/obj/effect/proc_holder/spell/targeted/emplosion/cast(list/targets, mob/user = usr) + + for(var/mob/living/target in targets) + empulse(target.loc, emp_heavy, emp_light, 1) + + return diff --git a/code/datums/spells/ethereal_jaunt.dm b/code/datums/spells/ethereal_jaunt.dm index 3103d8d5e5d..4f2c88b659d 100644 --- a/code/datums/spells/ethereal_jaunt.dm +++ b/code/datums/spells/ethereal_jaunt.dm @@ -1,104 +1,104 @@ -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt - name = "Ethereal Jaunt" - desc = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls." - - school = "transmutation" - charge_max = 300 - clothes_req = 1 - invocation = "none" - invocation_type = "none" - range = -1 - cooldown_min = 100 //50 deciseconds reduction per rank - include_user = 1 - nonabstract_req = 1 - centcom_cancast = 0 //Prevent people from getting to centcom - - var/jaunt_duration = 50 //in deciseconds - var/jaunt_in_time = 5 - var/jaunt_in_type = /obj/effect/temp_visual/wizard - var/jaunt_out_type = /obj/effect/temp_visual/wizard/out - - action_icon_state = "jaunt" - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets, mob/user = usr) //magnets, so mostly hardcoded - playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1) - for(var/mob/living/target in targets) - if(!target.can_safely_leave_loc()) // No more brainmobs hopping out of their brains - to_chat(target, "You are somehow too bound to your current location to abandon it.") - continue - INVOKE_ASYNC(src, .proc/do_jaunt, target) - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/do_jaunt(mob/living/target) - target.notransform = 1 - var/turf/mobloc = get_turf(target) - var/obj/effect/dummy/spell_jaunt/holder = new /obj/effect/dummy/spell_jaunt(mobloc) - new jaunt_out_type(mobloc, target.dir) - target.ExtinguishMob() - target.forceMove(holder) - target.reset_perspective(holder) - target.notransform = 0 //mob is safely inside holder now, no need for protection. - jaunt_steam(mobloc) - - sleep(jaunt_duration) - - if(target.loc != holder) //mob warped out of the warp - qdel(holder) - return - mobloc = get_turf(target.loc) - jaunt_steam(mobloc) - target.canmove = 0 - holder.reappearing = 1 - playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, 1, -1) - sleep(25 - jaunt_in_time) - new jaunt_in_type(mobloc, holder.dir) - target.setDir(holder.dir) - sleep(jaunt_in_time) - qdel(holder) - if(!QDELETED(target)) - if(mobloc.density) - for(var/direction in alldirs) - var/turf/T = get_step(mobloc, direction) - if(T) - if(target.Move(T)) - break - target.canmove = 1 - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/jaunt_steam(mobloc) - var/datum/effect_system/steam_spread/steam = new /datum/effect_system/steam_spread() - steam.set_up(10, 0, mobloc) - steam.start() - -/obj/effect/dummy/spell_jaunt - name = "water" - icon = 'icons/effects/effects.dmi' - icon_state = "nothing" - var/reappearing = 0 - var/movedelay = 0 - var/movespeed = 2 - density = 0 - anchored = 1 - invisibility = 60 - resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/effect/dummy/spell_jaunt/Destroy() - // Eject contents if deleted somehow - for(var/atom/movable/AM in src) - AM.forceMove(get_turf(src)) - return ..() - -/obj/effect/dummy/spell_jaunt/relaymove(mob/user, direction) - if((movedelay > world.time) || reappearing || !direction) - return - var/turf/newLoc = get_step(src,direction) - setDir(direction) - if(!(newLoc.flags & NOJAUNT)) - forceMove(newLoc) - else - to_chat(user, "Some strange aura is blocking the way!") - movedelay = world.time + movespeed - -/obj/effect/dummy/spell_jaunt/ex_act(blah) - return - -/obj/effect/dummy/spell_jaunt/bullet_act(blah) - return \ No newline at end of file +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt + name = "Ethereal Jaunt" + desc = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls." + + school = "transmutation" + charge_max = 300 + clothes_req = 1 + invocation = "none" + invocation_type = "none" + range = -1 + cooldown_min = 100 //50 deciseconds reduction per rank + include_user = 1 + nonabstract_req = 1 + centcom_cancast = 0 //Prevent people from getting to centcom + + var/jaunt_duration = 50 //in deciseconds + var/jaunt_in_time = 5 + var/jaunt_in_type = /obj/effect/temp_visual/wizard + var/jaunt_out_type = /obj/effect/temp_visual/wizard/out + + action_icon_state = "jaunt" + +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets, mob/user = usr) //magnets, so mostly hardcoded + playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1) + for(var/mob/living/target in targets) + if(!target.can_safely_leave_loc()) // No more brainmobs hopping out of their brains + to_chat(target, "You are somehow too bound to your current location to abandon it.") + continue + INVOKE_ASYNC(src, .proc/do_jaunt, target) + +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/do_jaunt(mob/living/target) + target.notransform = 1 + var/turf/mobloc = get_turf(target) + var/obj/effect/dummy/spell_jaunt/holder = new /obj/effect/dummy/spell_jaunt(mobloc) + new jaunt_out_type(mobloc, target.dir) + target.ExtinguishMob() + target.forceMove(holder) + target.reset_perspective(holder) + target.notransform = 0 //mob is safely inside holder now, no need for protection. + jaunt_steam(mobloc) + + sleep(jaunt_duration) + + if(target.loc != holder) //mob warped out of the warp + qdel(holder) + return + mobloc = get_turf(target.loc) + jaunt_steam(mobloc) + target.canmove = 0 + holder.reappearing = 1 + playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, 1, -1) + sleep(25 - jaunt_in_time) + new jaunt_in_type(mobloc, holder.dir) + target.setDir(holder.dir) + sleep(jaunt_in_time) + qdel(holder) + if(!QDELETED(target)) + if(mobloc.density) + for(var/direction in alldirs) + var/turf/T = get_step(mobloc, direction) + if(T) + if(target.Move(T)) + break + target.canmove = 1 + +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/jaunt_steam(mobloc) + var/datum/effect_system/steam_spread/steam = new /datum/effect_system/steam_spread() + steam.set_up(10, 0, mobloc) + steam.start() + +/obj/effect/dummy/spell_jaunt + name = "water" + icon = 'icons/effects/effects.dmi' + icon_state = "nothing" + var/reappearing = 0 + var/movedelay = 0 + var/movespeed = 2 + density = 0 + anchored = 1 + invisibility = 60 + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/effect/dummy/spell_jaunt/Destroy() + // Eject contents if deleted somehow + for(var/atom/movable/AM in src) + AM.forceMove(get_turf(src)) + return ..() + +/obj/effect/dummy/spell_jaunt/relaymove(mob/user, direction) + if((movedelay > world.time) || reappearing || !direction) + return + var/turf/newLoc = get_step(src,direction) + setDir(direction) + if(!(newLoc.flags & NOJAUNT)) + forceMove(newLoc) + else + to_chat(user, "Some strange aura is blocking the way!") + movedelay = world.time + movespeed + +/obj/effect/dummy/spell_jaunt/ex_act(blah) + return + +/obj/effect/dummy/spell_jaunt/bullet_act(blah) + return diff --git a/code/datums/spells/explosion.dm b/code/datums/spells/explosion.dm index 4beeae1206a..22580d9a896 100644 --- a/code/datums/spells/explosion.dm +++ b/code/datums/spells/explosion.dm @@ -1,15 +1,15 @@ -/obj/effect/proc_holder/spell/targeted/explosion - name = "Explosion" - desc = "This spell explodes an area." - - var/ex_severe = 1 - var/ex_heavy = 2 - var/ex_light = 3 - var/ex_flash = 4 - -/obj/effect/proc_holder/spell/targeted/explosion/cast(list/targets, mob/user = usr) - - for(var/mob/living/target in targets) - explosion(target.loc,ex_severe,ex_heavy,ex_light,ex_flash) - - return \ No newline at end of file +/obj/effect/proc_holder/spell/targeted/explosion + name = "Explosion" + desc = "This spell explodes an area." + + var/ex_severe = 1 + var/ex_heavy = 2 + var/ex_light = 3 + var/ex_flash = 4 + +/obj/effect/proc_holder/spell/targeted/explosion/cast(list/targets, mob/user = usr) + + for(var/mob/living/target in targets) + explosion(target.loc,ex_severe,ex_heavy,ex_light,ex_flash) + + return diff --git a/code/datums/spells/fake_gib.dm b/code/datums/spells/fake_gib.dm index 6e2f31ea158..dc187576496 100644 --- a/code/datums/spells/fake_gib.dm +++ b/code/datums/spells/fake_gib.dm @@ -8,4 +8,4 @@ clothes_req = 0 cooldown_min = 200 //100 deciseconds reduction per rank - action_icon_state = "gib" \ No newline at end of file + action_icon_state = "gib" diff --git a/code/datums/spells/genetic.dm b/code/datums/spells/genetic.dm index 697337d85e0..3c2f997e461 100644 --- a/code/datums/spells/genetic.dm +++ b/code/datums/spells/genetic.dm @@ -1,37 +1,37 @@ -/obj/effect/proc_holder/spell/targeted/genetic - name = "Genetic" - desc = "This spell inflicts a set of mutations and disabilities upon the target." - - var/disabilities = 0 //bits - var/list/mutations = list() //mutation strings - var/duration = 100 //deciseconds - /* - Disabilities - 1st bit - ? - 2nd bit - ? - 3rd bit - ? - 4th bit - ? - 5th bit - ? - 6th bit - ? - */ - -/obj/effect/proc_holder/spell/targeted/genetic/cast(list/targets, mob/user = usr) - - for(var/mob/living/target in targets) - for(var/x in mutations) - target.mutations.Add(x) - /* if(x == HULK && ishuman(target)) - target:hulk_time=world.time + duration */ - target.disabilities |= disabilities - target.update_mutations() //update target's mutation overlays - var/mob/living/carbon/human/H = target - if(ishuman(target)) - H.update_body() - spawn(duration) - target.mutations.Remove(mutations) - target.disabilities &= ~disabilities - target.update_mutations() - if(ishuman(target)) - H.update_body() - - return \ No newline at end of file +/obj/effect/proc_holder/spell/targeted/genetic + name = "Genetic" + desc = "This spell inflicts a set of mutations and disabilities upon the target." + + var/disabilities = 0 //bits + var/list/mutations = list() //mutation strings + var/duration = 100 //deciseconds + /* + Disabilities + 1st bit - ? + 2nd bit - ? + 3rd bit - ? + 4th bit - ? + 5th bit - ? + 6th bit - ? + */ + +/obj/effect/proc_holder/spell/targeted/genetic/cast(list/targets, mob/user = usr) + + for(var/mob/living/target in targets) + for(var/x in mutations) + target.mutations.Add(x) + /* if(x == HULK && ishuman(target)) + target:hulk_time=world.time + duration */ + target.disabilities |= disabilities + target.update_mutations() //update target's mutation overlays + var/mob/living/carbon/human/H = target + if(ishuman(target)) + H.update_body() + spawn(duration) + target.mutations.Remove(mutations) + target.disabilities &= ~disabilities + target.update_mutations() + if(ishuman(target)) + H.update_body() + + return diff --git a/code/datums/spells/horsemask.dm b/code/datums/spells/horsemask.dm index f536041e807..2c000a0d232 100644 --- a/code/datums/spells/horsemask.dm +++ b/code/datums/spells/horsemask.dm @@ -1,48 +1,48 @@ -/obj/effect/proc_holder/spell/targeted/horsemask - name = "Curse of the Horseman" - desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes." - school = "transmutation" - charge_type = "recharge" - charge_max = 150 - charge_counter = 0 - clothes_req = 0 - stat_allowed = 0 - invocation = "KN'A FTAGHU, PUCK 'BTHNK!" - invocation_type = "shout" - range = 7 - cooldown_min = 30 //30 deciseconds reduction per rank - selection_type = "range" - - action_icon_state = "barn" - sound = 'sound/magic/HorseHead_curse.ogg' - -/obj/effect/proc_holder/spell/targeted/horsemask/cast(list/targets, mob/user = usr) - if(!targets.len) - to_chat(user, "No target found in range.") - return - - var/mob/living/carbon/target = targets[1] - - if(!target) - return - - - if(!ishuman(target)) - to_chat(user, "It'd be stupid to curse [target] with a horse's head!") - return - - if(!(target in oview(range)))//If they are not in overview after selection. - to_chat(user, "They are too far away!") - return - - var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead - magichead.flags |= NODROP | DROPDEL //curses! - magichead.flags_inv = null //so you can still see their face - magichead.voicechange = 1 //NEEEEIIGHH - target.visible_message( "[target]'s face lights up in fire, and after the event a horse's head takes its place!", \ - "Your face burns up, and shortly after the fire you realise you have the face of a horse!") - if(!target.unEquip(target.wear_mask)) - qdel(target.wear_mask) - target.equip_to_slot_if_possible(magichead, slot_wear_mask, 1, 1) - - target.flash_eyes() \ No newline at end of file +/obj/effect/proc_holder/spell/targeted/horsemask + name = "Curse of the Horseman" + desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes." + school = "transmutation" + charge_type = "recharge" + charge_max = 150 + charge_counter = 0 + clothes_req = 0 + stat_allowed = 0 + invocation = "KN'A FTAGHU, PUCK 'BTHNK!" + invocation_type = "shout" + range = 7 + cooldown_min = 30 //30 deciseconds reduction per rank + selection_type = "range" + + action_icon_state = "barn" + sound = 'sound/magic/HorseHead_curse.ogg' + +/obj/effect/proc_holder/spell/targeted/horsemask/cast(list/targets, mob/user = usr) + if(!targets.len) + to_chat(user, "No target found in range.") + return + + var/mob/living/carbon/target = targets[1] + + if(!target) + return + + + if(!ishuman(target)) + to_chat(user, "It'd be stupid to curse [target] with a horse's head!") + return + + if(!(target in oview(range)))//If they are not in overview after selection. + to_chat(user, "They are too far away!") + return + + var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead + magichead.flags |= NODROP | DROPDEL //curses! + magichead.flags_inv = null //so you can still see their face + magichead.voicechange = 1 //NEEEEIIGHH + target.visible_message( "[target]'s face lights up in fire, and after the event a horse's head takes its place!", \ + "Your face burns up, and shortly after the fire you realise you have the face of a horse!") + if(!target.unEquip(target.wear_mask)) + qdel(target.wear_mask) + target.equip_to_slot_if_possible(magichead, slot_wear_mask, 1, 1) + + target.flash_eyes() diff --git a/code/datums/spells/inflict_handler.dm b/code/datums/spells/inflict_handler.dm index 896e399187a..7615a37cfff 100644 --- a/code/datums/spells/inflict_handler.dm +++ b/code/datums/spells/inflict_handler.dm @@ -1,57 +1,57 @@ -/obj/effect/proc_holder/spell/targeted/inflict_handler - name = "Inflict Handler" - desc = "This spell blinds and/or destroys/damages/heals and/or weakens/stuns the target." - - var/amt_weakened = 0 - var/amt_paralysis = 0 - var/amt_stunned = 0 - - //set to negatives for healing - var/amt_dam_fire = 0 - var/amt_dam_brute = 0 - var/amt_dam_oxy = 0 - var/amt_dam_tox = 0 - - var/amt_eye_blind = 0 - var/amt_eye_blurry = 0 - - var/destroys = "none" //can be "none", "gib" or "disintegrate" - - var/summon_type = null //this will put an obj at the target's location - -/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets, mob/user = usr) - - for(var/mob/living/target in targets) - switch(destroys) - if("gib") - target.gib() - if("disintegrate") - target.dust() - - if(!target) - continue - //damage - if(amt_dam_brute > 0) - if(amt_dam_fire >= 0) - target.take_overall_damage(amt_dam_brute,amt_dam_fire) - else if(amt_dam_fire < 0) - target.take_overall_damage(amt_dam_brute,0) - target.heal_overall_damage(0,amt_dam_fire) - else if(amt_dam_brute < 0) - if(amt_dam_fire > 0) - target.take_overall_damage(0,amt_dam_fire) - target.heal_overall_damage(amt_dam_brute,0) - else if(amt_dam_fire <= 0) - target.heal_overall_damage(amt_dam_brute,amt_dam_fire) - target.adjustToxLoss(amt_dam_tox) - target.adjustOxyLoss(amt_dam_oxy) - //disabling - target.Weaken(amt_weakened) - target.Paralyse(amt_paralysis) - target.Stun(amt_stunned) - - target.AdjustEyeBlind(amt_eye_blind) - target.AdjustEyeBlurry(amt_eye_blurry) - //summoning - if(summon_type) - new summon_type(target.loc, target) +/obj/effect/proc_holder/spell/targeted/inflict_handler + name = "Inflict Handler" + desc = "This spell blinds and/or destroys/damages/heals and/or weakens/stuns the target." + + var/amt_weakened = 0 + var/amt_paralysis = 0 + var/amt_stunned = 0 + + //set to negatives for healing + var/amt_dam_fire = 0 + var/amt_dam_brute = 0 + var/amt_dam_oxy = 0 + var/amt_dam_tox = 0 + + var/amt_eye_blind = 0 + var/amt_eye_blurry = 0 + + var/destroys = "none" //can be "none", "gib" or "disintegrate" + + var/summon_type = null //this will put an obj at the target's location + +/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets, mob/user = usr) + + for(var/mob/living/target in targets) + switch(destroys) + if("gib") + target.gib() + if("disintegrate") + target.dust() + + if(!target) + continue + //damage + if(amt_dam_brute > 0) + if(amt_dam_fire >= 0) + target.take_overall_damage(amt_dam_brute,amt_dam_fire) + else if(amt_dam_fire < 0) + target.take_overall_damage(amt_dam_brute,0) + target.heal_overall_damage(0,amt_dam_fire) + else if(amt_dam_brute < 0) + if(amt_dam_fire > 0) + target.take_overall_damage(0,amt_dam_fire) + target.heal_overall_damage(amt_dam_brute,0) + else if(amt_dam_fire <= 0) + target.heal_overall_damage(amt_dam_brute,amt_dam_fire) + target.adjustToxLoss(amt_dam_tox) + target.adjustOxyLoss(amt_dam_oxy) + //disabling + target.Weaken(amt_weakened) + target.Paralyse(amt_paralysis) + target.Stun(amt_stunned) + + target.AdjustEyeBlind(amt_eye_blind) + target.AdjustEyeBlurry(amt_eye_blurry) + //summoning + if(summon_type) + new summon_type(target.loc, target) diff --git a/code/datums/spells/knock.dm b/code/datums/spells/knock.dm index b8fafb58bac..95c16978638 100644 --- a/code/datums/spells/knock.dm +++ b/code/datums/spells/knock.dm @@ -1,57 +1,57 @@ -/obj/effect/proc_holder/spell/aoe_turf/knock - name = "Knock" - desc = "This spell opens nearby doors and does not require wizard garb." - - school = "transmutation" - charge_max = 100 - clothes_req = 0 - invocation = "AULIE OXIN FIERA" - invocation_type = "whisper" - range = 3 - cooldown_min = 20 //20 deciseconds reduction per rank - - action_icon_state = "knock" - sound = 'sound/magic/knock.ogg' - -/obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets, mob/user = usr) - for(var/turf/T in targets) - for(var/obj/machinery/door/door in T.contents) - spawn(1) - if(istype(door,/obj/machinery/door/airlock/hatch/gamma)) - return - if(istype(door,/obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/A = door - A.unlock(1) //forced because it's magic! - door.open() - for(var/obj/structure/closet/C in T.contents) - spawn(1) - if(istype(C, /obj/structure/closet/secure_closet)) - var/obj/structure/closet/secure_closet/SC = C - SC.locked = 0 - C.open() - - return - -/obj/effect/proc_holder/spell/aoe_turf/knock/greater - name = "Greater Knock" - desc = "On first cast, will remove access restrictions on all airlocks on the station, and announce this spell's use to the station. On any further cast, will open all doors in sight. Cannot be refunded once bought!" - - charge_max = 200 - invocation = "MAIOR OXIN FIERA" - invocation_type = "shout" - range = 7 - level_max = 0 //Cannot be improved, quality of life since can't be refunded - cooldown_min = 200 - var/used = FALSE - -/obj/effect/proc_holder/spell/aoe_turf/knock/greater/cast(list/targets, mob/user = usr) - if(!used) - used = TRUE - for(var/obj/machinery/door/airlock/A in GLOB.airlocks) - if(is_station_level(A.z)) - A.req_access = list() - A.req_one_access = list() - command_announcement.Announce("We have removed all access requirements on your station's airlocks. You can thank us later!", "Greetings!", 'sound/misc/notice2.ogg', , , "Space Wizard Federation Message") - else - ..() - return +/obj/effect/proc_holder/spell/aoe_turf/knock + name = "Knock" + desc = "This spell opens nearby doors and does not require wizard garb." + + school = "transmutation" + charge_max = 100 + clothes_req = 0 + invocation = "AULIE OXIN FIERA" + invocation_type = "whisper" + range = 3 + cooldown_min = 20 //20 deciseconds reduction per rank + + action_icon_state = "knock" + sound = 'sound/magic/knock.ogg' + +/obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets, mob/user = usr) + for(var/turf/T in targets) + for(var/obj/machinery/door/door in T.contents) + spawn(1) + if(istype(door,/obj/machinery/door/airlock/hatch/gamma)) + return + if(istype(door,/obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/A = door + A.unlock(1) //forced because it's magic! + door.open() + for(var/obj/structure/closet/C in T.contents) + spawn(1) + if(istype(C, /obj/structure/closet/secure_closet)) + var/obj/structure/closet/secure_closet/SC = C + SC.locked = 0 + C.open() + + return + +/obj/effect/proc_holder/spell/aoe_turf/knock/greater + name = "Greater Knock" + desc = "On first cast, will remove access restrictions on all airlocks on the station, and announce this spell's use to the station. On any further cast, will open all doors in sight. Cannot be refunded once bought!" + + charge_max = 200 + invocation = "MAIOR OXIN FIERA" + invocation_type = "shout" + range = 7 + level_max = 0 //Cannot be improved, quality of life since can't be refunded + cooldown_min = 200 + var/used = FALSE + +/obj/effect/proc_holder/spell/aoe_turf/knock/greater/cast(list/targets, mob/user = usr) + if(!used) + used = TRUE + for(var/obj/machinery/door/airlock/A in GLOB.airlocks) + if(is_station_level(A.z)) + A.req_access = list() + A.req_one_access = list() + command_announcement.Announce("We have removed all access requirements on your station's airlocks. You can thank us later!", "Greetings!", 'sound/misc/notice2.ogg', , , "Space Wizard Federation Message") + else + ..() + return diff --git a/code/datums/spells/lichdom.dm b/code/datums/spells/lichdom.dm index f37d48c0a10..9db46b517db 100644 --- a/code/datums/spells/lichdom.dm +++ b/code/datums/spells/lichdom.dm @@ -130,4 +130,4 @@ H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(H), slot_wear_suit) H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), slot_head) H.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(H), slot_shoes) - H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), slot_w_uniform) \ No newline at end of file + H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), slot_w_uniform) diff --git a/code/datums/spells/lightning.dm b/code/datums/spells/lightning.dm index dcca0cfc919..e7821aed6c2 100644 --- a/code/datums/spells/lightning.dm +++ b/code/datums/spells/lightning.dm @@ -117,4 +117,4 @@ obj/effect/proc_holder/spell/targeted/lightning/proc/Reset(mob/user = usr) return var/mob/living/next = pick(possible_targets) if(next) - Bolt(current,next,bolt_energy,bounces-1,user) // 5 max bounces \ No newline at end of file + Bolt(current,next,bolt_energy,bounces-1,user) // 5 max bounces diff --git a/code/datums/spells/magnet.dm b/code/datums/spells/magnet.dm index 8a6e9c525c4..fea6780085c 100644 --- a/code/datums/spells/magnet.dm +++ b/code/datums/spells/magnet.dm @@ -122,4 +122,4 @@ obj/effect/proc_holder/spell/targeted/magnet/proc/Reset(mob/user = usr) return var/mob/living/next = pick(possible_targets) if(next) - Bolt(current,next,bolt_energy,bounces-1,user) // 5 max bounces \ No newline at end of file + Bolt(current,next,bolt_energy,bounces-1,user) // 5 max bounces diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm index f11fb5b9572..31066001270 100644 --- a/code/datums/spells/mime.dm +++ b/code/datums/spells/mime.dm @@ -164,4 +164,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." \ No newline at end of file + desc = "It contains illustrations of the great walls of human history." diff --git a/code/datums/spells/mime_malaise.dm b/code/datums/spells/mime_malaise.dm index a5afd26d916..1c212c62125 100644 --- a/code/datums/spells/mime_malaise.dm +++ b/code/datums/spells/mime_malaise.dm @@ -51,4 +51,4 @@ equip_to_slot_if_possible(new /obj/item/clothing/suit/suspenders/nodrop, slot_wear_suit, TRUE, TRUE) dna.SetSEState(MUTEBLOCK , TRUE, TRUE) genemutcheck(src, MUTEBLOCK , null, MUTCHK_FORCED) - dna.default_blocks.Add(MUTEBLOCK) \ No newline at end of file + dna.default_blocks.Add(MUTEBLOCK) diff --git a/code/datums/spells/mind_transfer.dm b/code/datums/spells/mind_transfer.dm index e45d27ec28e..3d9031a8e03 100644 --- a/code/datums/spells/mind_transfer.dm +++ b/code/datums/spells/mind_transfer.dm @@ -1,82 +1,82 @@ -/obj/effect/proc_holder/spell/targeted/mind_transfer - name = "Mind Transfer" - desc = "This spell allows the user to switch bodies with a target." - - school = "transmutation" - charge_max = 600 - clothes_req = 0 - invocation = "GIN'YU CAPAN" - invocation_type = "whisper" - range = 1 - cooldown_min = 200 //100 deciseconds reduction per rank - var/list/protected_roles = list("Wizard","Changeling","Cultist") //which roles are immune to the spell - var/paralysis_amount_caster = 20 //how much the caster is paralysed for after the spell - var/paralysis_amount_victim = 20 //how much the victim is paralysed for after the spell - action_icon_state = "mindswap" - -/* -Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do. -Make sure spells that are removed from spell_list are actually removed and deleted when mind transfering. -Also, you never added distance checking after target is selected. I've went ahead and did that. -*/ -/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/user = usr, distanceoverride) - - var/mob/living/target = targets[range] - - if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it. - to_chat(user, "They are too far away!") - return - - if(target.stat == DEAD) - to_chat(user, "You don't particularly want to be dead.") - return - - if(!target.key || !target.mind) - to_chat(user, "[target.p_they(TRUE)] appear[target.p_s()] to be catatonic. Not even magic can affect [target.p_their()] vacant mind.") - return - - if(user.suiciding) - to_chat(user, "You're killing yourself! You can't concentrate enough to do this!") - return - - if(target.mind.special_role in protected_roles) - to_chat(user, "Their mind is resisting your spell.") - return - - if(istype(target, /mob/living/silicon)) - to_chat(user, "You feel this enslaved being is just as dead as its cold, hard exoskeleton.") - return - - var/mob/living/victim = target//The target of the spell whos body will be transferred to. - var/mob/caster = user//The wizard/whomever doing the body transferring. - - //MIND TRANSFER BEGIN - if(caster.mind.special_verbs.len)//If the caster had any special verbs, remove them from the mob verb list. - for(var/V in caster.mind.special_verbs)//Since the caster is using an object spell system, this is mostly moot. - caster.verbs -= V//But a safety nontheless. - - if(victim.mind.special_verbs.len)//Now remove all of the victim's verbs. - for(var/V in victim.mind.special_verbs) - victim.verbs -= V - - var/mob/dead/observer/ghost = victim.ghostize(0) - caster.mind.transfer_to(victim) - - if(victim.mind.special_verbs.len)//To add all the special verbs for the original caster. - for(var/V in caster.mind.special_verbs)//Not too important but could come into play. - caster.verbs += V - - ghost.mind.transfer_to(caster) - if(ghost.key) - GLOB.non_respawnable_keys -= ghost.ckey //ghostizing with an argument of 0 will make them unable to respawn forever, which is bad - caster.key = ghost.key //have to transfer the key since the mind was not active - qdel(ghost) - - if(caster.mind.special_verbs.len)//If they had any special verbs, we add them here. - for(var/V in caster.mind.special_verbs) - caster.verbs += V - //MIND TRANSFER END - - //Here we paralyze both mobs and knock them out for a time. - caster.Paralyse(paralysis_amount_caster) - victim.Paralyse(paralysis_amount_victim) +/obj/effect/proc_holder/spell/targeted/mind_transfer + name = "Mind Transfer" + desc = "This spell allows the user to switch bodies with a target." + + school = "transmutation" + charge_max = 600 + clothes_req = 0 + invocation = "GIN'YU CAPAN" + invocation_type = "whisper" + range = 1 + cooldown_min = 200 //100 deciseconds reduction per rank + var/list/protected_roles = list("Wizard","Changeling","Cultist") //which roles are immune to the spell + var/paralysis_amount_caster = 20 //how much the caster is paralysed for after the spell + var/paralysis_amount_victim = 20 //how much the victim is paralysed for after the spell + action_icon_state = "mindswap" + +/* +Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do. +Make sure spells that are removed from spell_list are actually removed and deleted when mind transfering. +Also, you never added distance checking after target is selected. I've went ahead and did that. +*/ +/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/user = usr, distanceoverride) + + var/mob/living/target = targets[range] + + if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it. + to_chat(user, "They are too far away!") + return + + if(target.stat == DEAD) + to_chat(user, "You don't particularly want to be dead.") + return + + if(!target.key || !target.mind) + to_chat(user, "[target.p_they(TRUE)] appear[target.p_s()] to be catatonic. Not even magic can affect [target.p_their()] vacant mind.") + return + + if(user.suiciding) + to_chat(user, "You're killing yourself! You can't concentrate enough to do this!") + return + + if(target.mind.special_role in protected_roles) + to_chat(user, "Their mind is resisting your spell.") + return + + if(istype(target, /mob/living/silicon)) + to_chat(user, "You feel this enslaved being is just as dead as its cold, hard exoskeleton.") + return + + var/mob/living/victim = target//The target of the spell whos body will be transferred to. + var/mob/caster = user//The wizard/whomever doing the body transferring. + + //MIND TRANSFER BEGIN + if(caster.mind.special_verbs.len)//If the caster had any special verbs, remove them from the mob verb list. + for(var/V in caster.mind.special_verbs)//Since the caster is using an object spell system, this is mostly moot. + caster.verbs -= V//But a safety nontheless. + + if(victim.mind.special_verbs.len)//Now remove all of the victim's verbs. + for(var/V in victim.mind.special_verbs) + victim.verbs -= V + + var/mob/dead/observer/ghost = victim.ghostize(0) + caster.mind.transfer_to(victim) + + if(victim.mind.special_verbs.len)//To add all the special verbs for the original caster. + for(var/V in caster.mind.special_verbs)//Not too important but could come into play. + caster.verbs += V + + ghost.mind.transfer_to(caster) + if(ghost.key) + GLOB.non_respawnable_keys -= ghost.ckey //ghostizing with an argument of 0 will make them unable to respawn forever, which is bad + caster.key = ghost.key //have to transfer the key since the mind was not active + qdel(ghost) + + if(caster.mind.special_verbs.len)//If they had any special verbs, we add them here. + for(var/V in caster.mind.special_verbs) + caster.verbs += V + //MIND TRANSFER END + + //Here we paralyze both mobs and knock them out for a time. + caster.Paralyse(paralysis_amount_caster) + victim.Paralyse(paralysis_amount_victim) diff --git a/code/datums/spells/projectile.dm b/code/datums/spells/projectile.dm index 258925653d8..955aa6e3537 100644 --- a/code/datums/spells/projectile.dm +++ b/code/datums/spells/projectile.dm @@ -1,86 +1,86 @@ -/obj/effect/proc_holder/spell/targeted/projectile - name = "Projectile" - desc = "This spell summons projectiles which try to hit the targets." - - var/proj_icon = 'icons/obj/projectiles.dmi' - var/proj_icon_state = "spell" - var/proj_name = "a spell projectile" - - var/proj_trail = 0 //if it leaves a trail - var/proj_trail_lifespan = 0 //deciseconds - var/proj_trail_icon = 'icons/obj/wizard.dmi' - var/proj_trail_icon_state = "trail" - - var/proj_type = "/obj/effect/proc_holder/spell/targeted" //IMPORTANT use only subtypes of this - - var/proj_lingering = 0 //if it lingers or disappears upon hitting an obstacle - var/proj_homing = 1 //if it follows the target - var/proj_insubstantial = 0 //if it can pass through dense objects or not - var/proj_trigger_range = 0 //the range from target at which the projectile triggers cast(target) - - var/proj_lifespan = 15 //in deciseconds * proj_step_delay - var/proj_step_delay = 1 //lower = faster - -/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr) - - for(var/mob/living/target in targets) - spawn(0) - var/obj/effect/proc_holder/spell/targeted/projectile - if(istext(proj_type)) - var/projectile_type = text2path(proj_type) - projectile = new projectile_type(user) - if(istype(proj_type,/obj/effect/proc_holder/spell)) - projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user) - projectile:linked_spells += proj_type - projectile.icon = proj_icon - projectile.icon_state = proj_icon_state - projectile.dir = get_dir(target,projectile) - projectile.name = proj_name - - var/current_loc = user.loc - - projectile.loc = current_loc - - for(var/i = 0,i < proj_lifespan,i++) - if(!projectile) - break - - if(proj_homing) - if(proj_insubstantial) - projectile.dir = get_dir(projectile,target) - projectile.loc = get_step_to(projectile,target) - else - step_to(projectile,target) - else - if(proj_insubstantial) - projectile.loc = get_step(projectile,dir) - else - step(projectile,dir) - - if(!projectile) // step and step_to sleeps so we'll have to check again. - break - - if(!proj_lingering && projectile.loc == current_loc) //if it didn't move since last time - qdel(projectile) - break - - if(proj_trail && projectile) - spawn(0) - if(projectile) - var/obj/effect/overlay/trail = new /obj/effect/overlay(projectile.loc) - trail.icon = proj_trail_icon - trail.icon_state = proj_trail_icon_state - trail.density = 0 - spawn(proj_trail_lifespan) - qdel(trail) - - if(projectile.loc in range(target.loc,proj_trigger_range)) - projectile.perform(list(target), user = user) - break - - current_loc = projectile.loc - - sleep(proj_step_delay) - - if(projectile) - qdel(projectile) \ No newline at end of file +/obj/effect/proc_holder/spell/targeted/projectile + name = "Projectile" + desc = "This spell summons projectiles which try to hit the targets." + + var/proj_icon = 'icons/obj/projectiles.dmi' + var/proj_icon_state = "spell" + var/proj_name = "a spell projectile" + + var/proj_trail = 0 //if it leaves a trail + var/proj_trail_lifespan = 0 //deciseconds + var/proj_trail_icon = 'icons/obj/wizard.dmi' + var/proj_trail_icon_state = "trail" + + var/proj_type = "/obj/effect/proc_holder/spell/targeted" //IMPORTANT use only subtypes of this + + var/proj_lingering = 0 //if it lingers or disappears upon hitting an obstacle + var/proj_homing = 1 //if it follows the target + var/proj_insubstantial = 0 //if it can pass through dense objects or not + var/proj_trigger_range = 0 //the range from target at which the projectile triggers cast(target) + + var/proj_lifespan = 15 //in deciseconds * proj_step_delay + var/proj_step_delay = 1 //lower = faster + +/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr) + + for(var/mob/living/target in targets) + spawn(0) + var/obj/effect/proc_holder/spell/targeted/projectile + if(istext(proj_type)) + var/projectile_type = text2path(proj_type) + projectile = new projectile_type(user) + if(istype(proj_type,/obj/effect/proc_holder/spell)) + projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user) + projectile:linked_spells += proj_type + projectile.icon = proj_icon + projectile.icon_state = proj_icon_state + projectile.dir = get_dir(target,projectile) + projectile.name = proj_name + + var/current_loc = user.loc + + projectile.loc = current_loc + + for(var/i = 0,i < proj_lifespan,i++) + if(!projectile) + break + + if(proj_homing) + if(proj_insubstantial) + projectile.dir = get_dir(projectile,target) + projectile.loc = get_step_to(projectile,target) + else + step_to(projectile,target) + else + if(proj_insubstantial) + projectile.loc = get_step(projectile,dir) + else + step(projectile,dir) + + if(!projectile) // step and step_to sleeps so we'll have to check again. + break + + if(!proj_lingering && projectile.loc == current_loc) //if it didn't move since last time + qdel(projectile) + break + + if(proj_trail && projectile) + spawn(0) + if(projectile) + var/obj/effect/overlay/trail = new /obj/effect/overlay(projectile.loc) + trail.icon = proj_trail_icon + trail.icon_state = proj_trail_icon_state + trail.density = 0 + spawn(proj_trail_lifespan) + qdel(trail) + + if(projectile.loc in range(target.loc,proj_trigger_range)) + projectile.perform(list(target), user = user) + break + + current_loc = projectile.loc + + sleep(proj_step_delay) + + if(projectile) + qdel(projectile) diff --git a/code/datums/spells/shapeshift.dm b/code/datums/spells/shapeshift.dm index ff303ce750b..7b72b5a42c4 100644 --- a/code/datums/spells/shapeshift.dm +++ b/code/datums/spells/shapeshift.dm @@ -111,4 +111,4 @@ shapeshift_type = /mob/living/simple_animal/hostile/hellhound/greater current_shapes = list(/mob/living/simple_animal/hostile/hellhound/greater) current_casters = list() - possible_shapes = list(/mob/living/simple_animal/hostile/hellhound/greater) \ No newline at end of file + possible_shapes = list(/mob/living/simple_animal/hostile/hellhound/greater) diff --git a/code/datums/spells/trigger.dm b/code/datums/spells/trigger.dm index bba1f22cb1a..bc768cef57b 100644 --- a/code/datums/spells/trigger.dm +++ b/code/datums/spells/trigger.dm @@ -1,29 +1,29 @@ -/obj/effect/proc_holder/spell/targeted/trigger - name = "Trigger" - desc = "This spell triggers another spell or a few." - - var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly - var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks - -/obj/effect/proc_holder/spell/targeted/trigger/New() - ..() - - for(var/spell in starting_spells) - var/spell_to_add = text2path(spell) - new spell_to_add(src) //should result in adding to contents, needs testing - -/obj/effect/proc_holder/spell/targeted/trigger/Destroy() - for(var/spell in contents) - qdel(spell) - linked_spells = null - starting_spells = null - return ..() - -/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets, mob/user = usr) - for(var/mob/living/target in targets) - for(var/obj/effect/proc_holder/spell/spell in contents) - spell.perform(list(target), 0, user = user) - for(var/obj/effect/proc_holder/spell/spell in linked_spells) - spell.perform(list(target), 0, user = user) - - return \ No newline at end of file +/obj/effect/proc_holder/spell/targeted/trigger + name = "Trigger" + desc = "This spell triggers another spell or a few." + + var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly + var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks + +/obj/effect/proc_holder/spell/targeted/trigger/New() + ..() + + for(var/spell in starting_spells) + var/spell_to_add = text2path(spell) + new spell_to_add(src) //should result in adding to contents, needs testing + +/obj/effect/proc_holder/spell/targeted/trigger/Destroy() + for(var/spell in contents) + qdel(spell) + linked_spells = null + starting_spells = null + return ..() + +/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets, mob/user = usr) + for(var/mob/living/target in targets) + for(var/obj/effect/proc_holder/spell/spell in contents) + spell.perform(list(target), 0, user = user) + for(var/obj/effect/proc_holder/spell/spell in linked_spells) + spell.perform(list(target), 0, user = user) + + return diff --git a/code/datums/spells/turf_teleport.dm b/code/datums/spells/turf_teleport.dm index 6ecc9d693d8..adaa4afc94c 100644 --- a/code/datums/spells/turf_teleport.dm +++ b/code/datums/spells/turf_teleport.dm @@ -1,40 +1,40 @@ -/obj/effect/proc_holder/spell/targeted/turf_teleport - name = "Turf Teleport" - desc = "This spell teleports the target to the turf in range." - nonabstract_req = 1 - - var/inner_tele_radius = 1 - var/outer_tele_radius = 2 - - var/include_space = 0 //whether it includes space tiles in possible teleport locations - var/include_dense = 0 //whether it includes dense tiles in possible teleport locations - - var/sound1 = 'sound/weapons/zapbang.ogg' - var/sound2 = 'sound/weapons/zapbang.ogg' - -/obj/effect/proc_holder/spell/targeted/turf_teleport/cast(list/targets,mob/living/user = usr) - playsound(get_turf(user), sound1, 50,1) - for(var/mob/living/target in targets) - var/list/turfs = new/list() - for(var/turf/T in range(target,outer_tele_radius)) - if(T in range(target,inner_tele_radius)) continue - if(istype(T,/turf/space) && !include_space) continue - if(T.density && !include_dense) continue - if(T.x>world.maxx-outer_tele_radius || T.xworld.maxy-outer_tele_radius || T.yworld.maxx-outer_tele_radius || T.xworld.maxy-outer_tele_radius || T.yYou're slammed into the floor by a mystical force!") - else - new sparkle_path(get_turf(AM), get_dir(user, AM)) //created sparkles will disappear on their own - if(isliving(AM)) - var/mob/living/M = AM - M.Weaken(stun_amt) - to_chat(M, "You're thrown back by a mystical force!") - spawn(0) - AM.throw_at(throwtarget, ((Clamp((maxthrow - (Clamp(distfromcaster - 2, 0, distfromcaster))), 3, maxthrow))), 1)//So stuff gets tossed around at the same time. - -/obj/effect/proc_holder/spell/targeted/sacred_flame - name = "Sacred Flame" - desc = "Makes everyone around you more flammable, and lights yourself on fire." - charge_max = 60 - clothes_req = 0 - invocation = "FI'RAN DADISKO" - invocation_type = "shout" - max_targets = 0 - range = 6 - include_user = 1 - selection_type = "view" - action_icon_state = "sacredflame" - sound = 'sound/magic/fireball.ogg' - -/obj/effect/proc_holder/spell/targeted/sacred_flame/cast(list/targets, mob/user = usr) - for(var/mob/living/L in targets) - L.adjust_fire_stacks(20) - if(isliving(user)) - var/mob/living/U = user - U.IgniteMob() +/obj/effect/proc_holder/spell/targeted/projectile/magic_missile + name = "Magic Missile" + desc = "This spell fires several, slow moving, magic projectiles at nearby targets." + + school = "evocation" + charge_max = 200 + clothes_req = 1 + invocation = "FORTI GY AMA" + invocation_type = "shout" + range = 7 + cooldown_min = 60 //35 deciseconds reduction per rank + + max_targets = 0 + + proj_icon_state = "magicm" + proj_name = "a magic missile" + proj_lingering = 1 + proj_type = "/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile" + + proj_lifespan = 20 + proj_step_delay = 5 + + proj_trail = 1 + proj_trail_lifespan = 5 + proj_trail_icon_state = "magicmd" + + action_icon_state = "magicm" + + sound = 'sound/magic/magic_missile.ogg' + +/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile + amt_weakened = 3 + sound = 'sound/magic/mm_hit.ogg' + + +/obj/effect/proc_holder/spell/targeted/projectile/honk_missile + name = "Honk Missile" + desc = "This spell fires several, slow moving, magic bikehorns at nearby targets." + + school = "evocation" + charge_max = 60 + clothes_req = 0 + invocation = "HONK GY AMA" + invocation_type = "shout" + range = 7 + cooldown_min = 60 //35 deciseconds reduction per rank + + max_targets = 0 + + proj_icon = 'icons/obj/items.dmi' + proj_icon_state = "bike_horn" + proj_name = "A bike horn" + proj_lingering = 1 + proj_type = "/obj/effect/proc_holder/spell/targeted/inflict_handler/honk_missile" + + proj_lifespan = 20 + proj_step_delay = 5 + + proj_trail_icon = 'icons/obj/items.dmi' + proj_trail = 1 + proj_trail_lifespan = 5 + proj_trail_icon_state = "bike_horn" + + action_icon_state = "magicm" + + sound = 'sound/items/bikehorn.ogg' + +/obj/effect/proc_holder/spell/targeted/inflict_handler/honk_missile + amt_weakened = 3 + sound = 'sound/items/bikehorn.ogg' + +/obj/effect/proc_holder/spell/noclothes + name = "No Clothes" + desc = "This always-on spell allows you to cast magic without your garments." + action_icon_state = "no_clothes" + +/obj/effect/proc_holder/spell/targeted/genetic/mutate + name = "Mutate" + desc = "This spell causes you to turn into a hulk and gain laser vision for a short while." + + school = "transmutation" + charge_max = 400 + clothes_req = 1 + invocation = "BIRUZ BENNAR" + invocation_type = "shout" + message = "You feel strong! You feel a pressure building behind your eyes!" + range = -1 + include_user = 1 + centcom_cancast = 0 + + mutations = list(LASER, HULK) + duration = 300 + cooldown_min = 300 //25 deciseconds reduction per rank + + action_icon_state = "mutate" + sound = 'sound/magic/mutate.ogg' + +/obj/effect/proc_holder/spell/targeted/genetic/mutate/cast(list/targets, mob/user = usr) + for(var/mob/living/target in targets) + target.dna.SetSEState(HULKBLOCK, 1) + genemutcheck(target, HULKBLOCK, null, MUTCHK_FORCED) + spawn(duration) + target.dna.SetSEState(HULKBLOCK, 0) + genemutcheck(target, HULKBLOCK, null, MUTCHK_FORCED) + ..() + +/obj/effect/proc_holder/spell/targeted/smoke + name = "Smoke" + desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb." + + school = "conjuration" + charge_max = 120 + clothes_req = 0 + invocation = "none" + invocation_type = "none" + range = -1 + include_user = 1 + cooldown_min = 20 //25 deciseconds reduction per rank + + smoke_spread = 2 + smoke_amt = 10 + + action_icon_state = "smoke" + +/obj/effect/proc_holder/spell/targeted/emplosion/disable_tech + name = "Disable Tech" + desc = "This spell disables all weapons, cameras and most other technology in range." + charge_max = 400 + clothes_req = 1 + invocation = "NEC CANTIO" + invocation_type = "shout" + range = -1 + include_user = 1 + cooldown_min = 200 //50 deciseconds reduction per rank + + emp_heavy = 6 + emp_light = 10 + + sound = 'sound/magic/disable_tech.ogg' + +/obj/effect/proc_holder/spell/targeted/turf_teleport/blink + name = "Blink" + desc = "This spell randomly teleports you a short distance." + + school = "abjuration" + charge_max = 20 + clothes_req = 1 + invocation = "none" + invocation_type = "none" + range = -1 + include_user = 1 + cooldown_min = 5 //4 deciseconds reduction per rank + + + smoke_spread = 1 + smoke_amt = 1 + + inner_tele_radius = 0 + outer_tele_radius = 6 + + centcom_cancast = 0 //prevent people from getting to centcom + + action_icon_state = "blink" + + sound1 = 'sound/magic/blink.ogg' + sound2 = 'sound/magic/blink.ogg' + +/obj/effect/proc_holder/spell/targeted/area_teleport/teleport + name = "Teleport" + desc = "This spell teleports you to a type of area of your selection." + + school = "abjuration" + charge_max = 600 + clothes_req = 1 + invocation = "SCYAR NILA" + invocation_type = "shout" + range = -1 + include_user = 1 + cooldown_min = 200 //100 deciseconds reduction per rank + + smoke_spread = 1 + smoke_amt = 5 + + action_icon_state = "spell_teleport" + + sound1 = 'sound/magic/teleport_diss.ogg' + sound2 = 'sound/magic/teleport_app.ogg' + +/obj/effect/proc_holder/spell/targeted/forcewall + name = "Force Wall" + desc = "This spell creates a small unbreakable wall that only you can pass through, and does not need wizard garb. Lasts 30 seconds." + + school = "transmutation" + charge_max = 100 + clothes_req = FALSE + invocation = "TARCOL MINTI ZHERI" + invocation_type = "whisper" + sound = 'sound/magic/forcewall.ogg' + action_icon_state = "shield" + range = -1 + include_user = TRUE + cooldown_min = 50 //12 deciseconds reduction per rank + var/wall_type = /obj/effect/forcefield/wizard + var/large = FALSE + +/obj/effect/proc_holder/spell/targeted/forcewall/cast(list/targets, mob/user = usr) + new wall_type(get_turf(user), user) + if(large) //Extra THICK + if(user.dir == SOUTH || user.dir == NORTH) + new wall_type(get_step(user, EAST), user) + new wall_type(get_step(user, WEST), user) + else + new wall_type(get_step(user, NORTH), user) + new wall_type(get_step(user, SOUTH), user) + +/obj/effect/proc_holder/spell/targeted/forcewall/greater + name = "Greater Force Wall" + desc = "Create a larger magical barrier that only you can pass through, but requires wizard garb. Lasts 30 seconds." + + clothes_req = TRUE + invocation = "TARCOL GRANDI ZHERI" + invocation_type = "shout" + large = TRUE + +/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop + name = "Stop Time" + desc = "This spell stops time for everyone except for you, allowing you to move freely while your enemies and even projectiles are frozen." + charge_max = 500 + clothes_req = 1 + invocation = "TOKI WO TOMARE" + invocation_type = "shout" + range = 0 + cooldown_min = 100 + summon_amt = 1 + action_icon_state = "time" + + summon_type = list(/obj/effect/timestop/wizard) + +/obj/effect/proc_holder/spell/aoe_turf/conjure/carp + name = "Summon Carp" + desc = "This spell conjures a simple carp." + + school = "conjuration" + charge_max = 1200 + clothes_req = 1 + invocation = "NOUK FHUNMM SACP RISSKA" + invocation_type = "shout" + range = 1 + + summon_type = list(/mob/living/simple_animal/hostile/carp) + + cast_sound = 'sound/magic/summon_karp.ogg' + +/obj/effect/proc_holder/spell/aoe_turf/conjure/construct + name = "Artificer" + desc = "This spell conjures a construct which may be controlled by Shades" + + school = "conjuration" + charge_max = 600 + clothes_req = 0 + invocation = "none" + invocation_type = "none" + range = 0 + + summon_type = list(/obj/structure/constructshell) + + action_icon_state = "artificer" + cast_sound = 'sound/magic/summonitems_generic.ogg' + +/obj/effect/proc_holder/spell/aoe_turf/conjure/creature + name = "Summon Creature Swarm" + desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth" + + school = "conjuration" + charge_max = 1200 + clothes_req = 0 + invocation = "IA IA" + invocation_type = "shout" + summon_amt = 10 + range = 3 + + summon_type = list(/mob/living/simple_animal/hostile/creature) + cast_sound = 'sound/magic/summonitems_generic.ogg' + +/obj/effect/proc_holder/spell/targeted/trigger/blind + name = "Blind" + desc = "This spell temporarily blinds a single person and does not require wizard garb." + + school = "transmutation" + charge_max = 300 + clothes_req = 0 + invocation = "STI KALY" + invocation_type = "whisper" + message = "Your eyes cry out in pain!" + cooldown_min = 50 //12 deciseconds reduction per rank + + starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind","/obj/effect/proc_holder/spell/targeted/genetic/blind") + + action_icon_state = "blind" + +/obj/effect/proc_holder/spell/targeted/inflict_handler/blind + amt_eye_blind = 10 + amt_eye_blurry = 20 + sound = 'sound/magic/blind.ogg' + +/obj/effect/proc_holder/spell/targeted/genetic/blind + disabilities = BLIND + duration = 300 + sound = 'sound/magic/blind.ogg' + +/obj/effect/proc_holder/spell/fireball + name = "Fireball" + desc = "This spell fires a fireball at a target and does not require wizard garb." + + school = "evocation" + charge_max = 60 + clothes_req = 0 + invocation = "ONI SOMA" + invocation_type = "shout" + range = 20 + cooldown_min = 20 //10 deciseconds reduction per rank + var/fireball_type = /obj/item/projectile/magic/fireball + action_icon_state = "fireball0" + sound = 'sound/magic/fireball.ogg' + + active = FALSE + +/obj/effect/proc_holder/spell/fireball/Click() + var/mob/living/user = usr + if(!istype(user)) + return + + var/msg + + if(!can_cast(user)) + msg = "You can no longer cast Fireball." + remove_ranged_ability(user, msg) + return + + if(active) + msg = "You extinguish your fireball...for now." + remove_ranged_ability(user, msg) + else + msg = "Your prepare to cast your fireball spell! Left-click to cast at a target!" + add_ranged_ability(user, msg) + +/obj/effect/proc_holder/spell/fireball/update_icon() + if(!action) + return + action.button_icon_state = "fireball[active]" + action.UpdateButtonIcon() + +/obj/effect/proc_holder/spell/fireball/InterceptClickOn(mob/living/user, params, atom/target) + if(..()) + return FALSE + + if(!cast_check(0, user)) + remove_ranged_ability(user) + return FALSE + + var/list/targets = list(target) + perform(targets, user = user) + + return TRUE + +/obj/effect/proc_holder/spell/fireball/cast(list/targets, mob/living/user = usr) + var/target = targets[1] //There is only ever one target for fireball + var/turf/T = user.loc + var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction + if(!isturf(U) || !isturf(T)) + return 0 + + var/obj/item/projectile/magic/fireball/FB = new fireball_type(user.loc) + FB.current = get_turf(user) + FB.preparePixelProjectile(target, get_turf(target), user) + FB.fire() + user.newtonian_move(get_dir(U, T)) + remove_ranged_ability(user) + + return 1 + +/obj/effect/proc_holder/spell/aoe_turf/repulse + name = "Repulse" + desc = "This spell throws everything around the user away." + charge_max = 400 + clothes_req = TRUE + invocation = "GITTAH WEIGH" + invocation_type = "shout" + range = 5 + cooldown_min = 150 + selection_type = "view" + sound = 'sound/magic/repulse.ogg' + var/maxthrow = 5 + var/sparkle_path = /obj/effect/temp_visual/gravpush + action_icon_state = "repulse" + +/obj/effect/proc_holder/spell/aoe_turf/repulse/cast(list/targets, mob/user = usr, stun_amt = 2) + var/list/thrownatoms = list() + var/atom/throwtarget + var/distfromcaster + playMagSound() + for(var/turf/T in targets) //Done this way so things don't get thrown all around hilariously. + for(var/atom/movable/AM in T) + thrownatoms += AM + + for(var/am in thrownatoms) + var/atom/movable/AM = am + if(AM == user || AM.anchored) + continue + + throwtarget = get_edge_target_turf(user, get_dir(user, get_step_away(AM, user))) + distfromcaster = get_dist(user, AM) + if(distfromcaster == 0) + if(isliving(AM)) + var/mob/living/M = AM + M.Weaken(5) + M.adjustBruteLoss(5) + to_chat(M, "You're slammed into the floor by a mystical force!") + else + new sparkle_path(get_turf(AM), get_dir(user, AM)) //created sparkles will disappear on their own + if(isliving(AM)) + var/mob/living/M = AM + M.Weaken(stun_amt) + to_chat(M, "You're thrown back by a mystical force!") + spawn(0) + AM.throw_at(throwtarget, ((Clamp((maxthrow - (Clamp(distfromcaster - 2, 0, distfromcaster))), 3, maxthrow))), 1)//So stuff gets tossed around at the same time. + +/obj/effect/proc_holder/spell/targeted/sacred_flame + name = "Sacred Flame" + desc = "Makes everyone around you more flammable, and lights yourself on fire." + charge_max = 60 + clothes_req = 0 + invocation = "FI'RAN DADISKO" + invocation_type = "shout" + max_targets = 0 + range = 6 + include_user = 1 + selection_type = "view" + action_icon_state = "sacredflame" + sound = 'sound/magic/fireball.ogg' + +/obj/effect/proc_holder/spell/targeted/sacred_flame/cast(list/targets, mob/user = usr) + for(var/mob/living/L in targets) + L.adjust_fire_stacks(20) + if(isliving(user)) + var/mob/living/U = user + U.IgniteMob() diff --git a/code/datums/statclick.dm b/code/datums/statclick.dm index 11a3ed41ce3..2c83e57fb45 100644 --- a/code/datums/statclick.dm +++ b/code/datums/statclick.dm @@ -30,4 +30,4 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick) class = "unknown" usr.client.debug_variables(target) - message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].") \ No newline at end of file + message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].") diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index c4ba55552fa..479694de4d7 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -229,4 +229,4 @@ return TRUE /datum/status_effect/regenerative_core/on_remove() - owner.status_flags &= ~IGNORESLOWDOWN \ No newline at end of file + owner.status_flags &= ~IGNORESLOWDOWN diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index 3ce68f85250..db6aef6ea0e 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -112,4 +112,4 @@ playsound(T, "desceration", 200, 1, -1) owner.adjustBruteLoss(bleed_damage) else - new /obj/effect/temp_visual/bleed(get_turf(owner)) \ No newline at end of file + new /obj/effect/temp_visual/bleed(get_turf(owner)) diff --git a/code/datums/status_effects/gas.dm b/code/datums/status_effects/gas.dm index bdd80e7fc32..00417993144 100644 --- a/code/datums/status_effects/gas.dm +++ b/code/datums/status_effects/gas.dm @@ -43,4 +43,4 @@ /datum/status_effect/freon/watcher duration = 8 - can_melt = FALSE \ No newline at end of file + can_melt = FALSE diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index b2365ad4e1c..7a86a00ccd7 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -44,4 +44,4 @@ alert_type = null /datum/status_effect/high_five/on_remove() - owner.visible_message("[owner] was left hanging....") \ No newline at end of file + owner.visible_message("[owner] was left hanging....") diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm index 8ed41c559b5..896f4f999a9 100644 --- a/code/datums/status_effects/status_effect.dm +++ b/code/datums/status_effects/status_effect.dm @@ -234,4 +234,4 @@ owner.cut_overlay(status_overlay) owner.underlays -= status_underlay QDEL_NULL(status_overlay) - return ..() \ No newline at end of file + return ..() diff --git a/code/datums/weather/weather_types/ash_storm.dm b/code/datums/weather/weather_types/ash_storm.dm index dbc75cb9bf0..57d8c11f80b 100644 --- a/code/datums/weather/weather_types/ash_storm.dm +++ b/code/datums/weather/weather_types/ash_storm.dm @@ -105,4 +105,4 @@ aesthetic = TRUE - probability = 10 \ No newline at end of file + probability = 10 diff --git a/code/datums/weather/weather_types/radiation_storm.dm b/code/datums/weather/weather_types/radiation_storm.dm index 08b30ecef48..85b8d3fd2af 100644 --- a/code/datums/weather/weather_types/radiation_storm.dm +++ b/code/datums/weather/weather_types/radiation_storm.dm @@ -61,4 +61,4 @@ post_status("alert", "radiation") else post_status("blank") - post_status("shuttle") \ No newline at end of file + post_status("shuttle") diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm index 8c1108a3b1b..b1076c8e29b 100644 --- a/code/datums/wires/apc.dm +++ b/code/datums/wires/apc.dm @@ -91,4 +91,4 @@ else if(A.aidisabled == 1) A.aidisabled = 0 - ..() \ No newline at end of file + ..() diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm index f0cb85b6d52..c1413abd0cc 100644 --- a/code/datums/wires/autolathe.dm +++ b/code/datums/wires/autolathe.dm @@ -71,4 +71,4 @@ /datum/wires/autolathe/proc/updateUIs() SSnanoui.update_uis(src) if(holder) - SSnanoui.update_uis(holder) \ No newline at end of file + SSnanoui.update_uis(holder) diff --git a/code/datums/wires/camera.dm b/code/datums/wires/camera.dm index f5f0c5df633..a6b1549fbe1 100644 --- a/code/datums/wires/camera.dm +++ b/code/datums/wires/camera.dm @@ -58,4 +58,4 @@ if(IsIndexCut(CAMERA_WIRE_POWER) && IsIndexCut(CAMERA_WIRE_FOCUS)) return TRUE else - return FALSE \ No newline at end of file + return FALSE diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index 0b970d38e5d..988b520854f 100644 --- a/code/datums/wires/mulebot.dm +++ b/code/datums/wires/mulebot.dm @@ -87,4 +87,4 @@ return !(wires_status & MULEBOT_WIRE_REMOTE_RX) /datum/wires/mulebot/proc/BeaconRX() - return !(wires_status & MULEBOT_WIRE_BEACON_RX) \ No newline at end of file + return !(wires_status & MULEBOT_WIRE_BEACON_RX) diff --git a/code/datums/wires/nuclearbomb.dm b/code/datums/wires/nuclearbomb.dm index 83fa911506d..63687ad8839 100644 --- a/code/datums/wires/nuclearbomb.dm +++ b/code/datums/wires/nuclearbomb.dm @@ -79,4 +79,4 @@ /datum/wires/nuclearbomb/proc/updateUIs() SSnanoui.update_uis(src) if(holder) - SSnanoui.update_uis(holder) \ No newline at end of file + SSnanoui.update_uis(holder) diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm index 64705e6c215..d6e68a79cfb 100644 --- a/code/datums/wires/particle_accelerator.dm +++ b/code/datums/wires/particle_accelerator.dm @@ -64,4 +64,4 @@ C.strength_upper_limit = (mended ? 2 : 3) if(C.strength_upper_limit < C.strength) C.remove_strength() - ..() \ No newline at end of file + ..() diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm index f55cee5951f..789a0472869 100644 --- a/code/datums/wires/robot.dm +++ b/code/datums/wires/robot.dm @@ -103,4 +103,4 @@ return wires_status & BORG_WIRE_LAWCHECK /datum/wires/robot/proc/AIHasControl() - return wires_status & BORG_WIRE_AI_CONTROL \ No newline at end of file + return wires_status & BORG_WIRE_AI_CONTROL diff --git a/code/datums/wires/suitstorage.dm b/code/datums/wires/suitstorage.dm index ddd0fda2bb6..175b36e6081 100644 --- a/code/datums/wires/suitstorage.dm +++ b/code/datums/wires/suitstorage.dm @@ -66,4 +66,4 @@ datum/wires/suitstorage/UpdatePulsed(index) A.shocked = FALSE if(SSU_WIRE_UV) A.uv_super = !A.uv_super - ..() \ No newline at end of file + ..() diff --git a/code/datums/wires/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm index 27ea28a90ad..b490d150efe 100644 --- a/code/datums/wires/syndicatebomb.dm +++ b/code/datums/wires/syndicatebomb.dm @@ -99,4 +99,4 @@ B.active = FALSE B.defused = TRUE B.update_icon() - ..() \ No newline at end of file + ..() diff --git a/code/datums/wires/tesla_coil.dm b/code/datums/wires/tesla_coil.dm index b0b0a57ede4..bc513c6c6c0 100644 --- a/code/datums/wires/tesla_coil.dm +++ b/code/datums/wires/tesla_coil.dm @@ -20,4 +20,4 @@ switch(index) if(TESLACOIL_WIRE_ZAP) T.zap() - ..() \ No newline at end of file + ..() diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm index 704c6bad750..c9e6032afee 100644 --- a/code/datums/wires/vending.dm +++ b/code/datums/wires/vending.dm @@ -66,4 +66,4 @@ V.seconds_electrified = -1 if(VENDING_WIRE_IDSCAN) V.scan_id = 1 - ..() \ No newline at end of file + ..() diff --git a/code/datums/wires/wires.dm b/code/datums/wires/wires.dm index 6a877ef90ea..07cf919db2d 100644 --- a/code/datums/wires/wires.dm +++ b/code/datums/wires/wires.dm @@ -329,4 +329,4 @@ var/list/wireColours = list("red", "blue", "green", "black", "orange", "brown", /datum/wires/proc/Shuffle() wires_status = 0 - GenerateWires() \ No newline at end of file + GenerateWires() diff --git a/code/defines/procs/AStar.dm b/code/defines/procs/AStar.dm index 1ec4f4eb7fc..e593888c265 100644 --- a/code/defines/procs/AStar.dm +++ b/code/defines/procs/AStar.dm @@ -1,184 +1,184 @@ -/* -A Star pathfinding algorithm -Returns a list of tiles forming a path from A to B, taking dense objects as well as walls, and the orientation of -windows along the route into account. -Use: -your_list = AStar(start location, end location, moving atom, distance proc, max nodes, maximum node depth, minimum distance to target, adjacent proc, atom id, turfs to exclude, check only simulated) - -Optional extras to add on (in order): -Distance proc : the distance used in every A* calculation (length of path and heuristic) -MaxNodes: The maximum number of nodes the returned path can be (0 = infinite) -Maxnodedepth: The maximum number of nodes to search (default: 30, 0 = infinite) -Mintargetdist: Minimum distance to the target before path returns, could be used to get -near a target, but not right to it - for an AI mob with a gun, for example. -Adjacent proc : returns the turfs to consider around the actually processed node -Simulated only : whether to consider unsimulated turfs or not (used by some Adjacent proc) - -Also added 'exclude' turf to avoid travelling over; defaults to null - -Actual Adjacent procs : - - /turf/proc/reachableAdjacentTurfs : returns reachable turfs in cardinal directions (uses simulated_only) - - /turf/proc/reachableAdjacentAtmosTurfs : returns turfs in cardinal directions reachable via atmos - -*/ - -////////////////////// -//PathNode object -////////////////////// - -//A* nodes variables -/PathNode - var/turf/source //turf associated with the PathNode - var/PathNode/prevNode //link to the parent PathNode - var/f //A* Node weight (f = g + h) - var/g //A* movement cost variable - var/h //A* heuristic variable - var/nt //count the number of Nodes traversed - -/PathNode/New(s,p,pg,ph,pnt) - source = s - prevNode = p - g = pg - h = ph - f = g + h - source.PNode = src - nt = pnt - -/PathNode/proc/calc_f() - f = g + h - -////////////////////// -//A* procs -////////////////////// - -//the weighting function, used in the A* algorithm -/proc/PathWeightCompare(PathNode/a, PathNode/b) - return a.f - b.f - -//reversed so that the Heap is a MinHeap rather than a MaxHeap -/proc/HeapPathWeightCompare(PathNode/a, PathNode/b) - return b.f - a.f - -//wrapper that returns an empty list if A* failed to find a path -/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) - var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent, id, exclude, simulated_only) - if(!path) - path = list() - return path - -//the actual algorithm -/proc/AStar(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) - - //sanitation - var/start = get_turf(caller) - if(!start) - return 0 - - if(maxnodes) - //if start turf is farther than maxnodes from end turf, no need to do anything - if(call(start, dist)(end) > maxnodes) - return 0 - maxnodedepth = maxnodes //no need to consider path longer than maxnodes - - var/Heap/open = new /Heap(/proc/HeapPathWeightCompare) //the open list - var/list/closed = new() //the closed list - var/list/path = null //the returned path, if any - var/PathNode/cur //current processed turf - - //initialization - open.Insert(new /PathNode(start,null,0,call(start,dist)(end),0)) - - //then run the main loop - while(!open.IsEmpty() && !path) - { - //get the lower f node on the open list - cur = open.Pop() //get the lower f turf in the open list - closed.Add(cur.source) //and tell we've processed it - - //if we only want to get near the target, check if we're close enough - var/closeenough - if(mintargetdist) - closeenough = call(cur.source,dist)(end) <= mintargetdist - - //if too many steps, abandon that path - if(maxnodedepth && (cur.nt > maxnodedepth)) - continue - - //found the target turf (or close enough), let's create the path to it - if(cur.source == end || closeenough) - path = new() - path.Add(cur.source) - - while(cur.prevNode) - cur = cur.prevNode - path.Add(cur.source) - - break - - //get adjacents turfs using the adjacent proc, checking for access with id - var/list/L = call(cur.source,adjacent)(caller, id, simulated_only) - for(var/turf/T in L) - if(T == exclude || (T in closed)) - continue - - var/newg = cur.g + call(cur.source,dist)(T) - if(!T.PNode) //is not already in open list, so add it - open.Insert(new /PathNode(T,cur,newg,call(T,dist)(end),cur.nt+1)) - else //is already in open list, check if it's a better way from the current turf - if(newg < T.PNode.g) - T.PNode.prevNode = cur - T.PNode.g = newg - T.PNode.calc_f() - T.PNode.nt = cur.nt + 1 - open.ReSort(T.PNode)//reorder the changed element in the list - - } - - //cleaning after us - for(var/PathNode/PN in open.L) - PN.source.PNode = null - for(var/turf/T in closed) - T.PNode = null - - //reverse the path to get it from start to finish - if(path) - for(var/i = 1; i <= path.len/2; i++) - path.Swap(i,path.len-i+1) - - return path - -//Returns adjacent turfs in cardinal directions that are reachable -//simulated_only controls whether only simulated turfs are considered or not -/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only) - var/list/L = new() - var/turf/simulated/T - - for(var/dir in cardinal) - T = get_step(src,dir) - if(!T || (simulated_only && !istype(T))) - continue - if(!T.density && !LinkBlockedWithAccess(T,caller, ID)) - L.Add(T) - return L - -//Returns adjacent turfs in cardinal directions that are reachable via atmos -/turf/proc/reachableAdjacentAtmosTurfs() - return atmos_adjacent_turfs - -/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID) - var/adir = get_dir(src, T) - var/rdir = get_dir(T, src) - - for(var/obj/structure/window/W in src) - if(!W.CanAStarPass(ID, adir)) - return 1 - for(var/obj/machinery/door/window/W in src) - if(!W.CanAStarPass(ID, adir)) - return 1 - for(var/obj/O in T) - if(!O.CanAStarPass(ID, rdir, caller)) - return 1 - - return 0 +/* +A Star pathfinding algorithm +Returns a list of tiles forming a path from A to B, taking dense objects as well as walls, and the orientation of +windows along the route into account. +Use: +your_list = AStar(start location, end location, moving atom, distance proc, max nodes, maximum node depth, minimum distance to target, adjacent proc, atom id, turfs to exclude, check only simulated) + +Optional extras to add on (in order): +Distance proc : the distance used in every A* calculation (length of path and heuristic) +MaxNodes: The maximum number of nodes the returned path can be (0 = infinite) +Maxnodedepth: The maximum number of nodes to search (default: 30, 0 = infinite) +Mintargetdist: Minimum distance to the target before path returns, could be used to get +near a target, but not right to it - for an AI mob with a gun, for example. +Adjacent proc : returns the turfs to consider around the actually processed node +Simulated only : whether to consider unsimulated turfs or not (used by some Adjacent proc) + +Also added 'exclude' turf to avoid travelling over; defaults to null + +Actual Adjacent procs : + + /turf/proc/reachableAdjacentTurfs : returns reachable turfs in cardinal directions (uses simulated_only) + + /turf/proc/reachableAdjacentAtmosTurfs : returns turfs in cardinal directions reachable via atmos + +*/ + +////////////////////// +//PathNode object +////////////////////// + +//A* nodes variables +/PathNode + var/turf/source //turf associated with the PathNode + var/PathNode/prevNode //link to the parent PathNode + var/f //A* Node weight (f = g + h) + var/g //A* movement cost variable + var/h //A* heuristic variable + var/nt //count the number of Nodes traversed + +/PathNode/New(s,p,pg,ph,pnt) + source = s + prevNode = p + g = pg + h = ph + f = g + h + source.PNode = src + nt = pnt + +/PathNode/proc/calc_f() + f = g + h + +////////////////////// +//A* procs +////////////////////// + +//the weighting function, used in the A* algorithm +/proc/PathWeightCompare(PathNode/a, PathNode/b) + return a.f - b.f + +//reversed so that the Heap is a MinHeap rather than a MaxHeap +/proc/HeapPathWeightCompare(PathNode/a, PathNode/b) + return b.f - a.f + +//wrapper that returns an empty list if A* failed to find a path +/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) + var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent, id, exclude, simulated_only) + if(!path) + path = list() + return path + +//the actual algorithm +/proc/AStar(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) + + //sanitation + var/start = get_turf(caller) + if(!start) + return 0 + + if(maxnodes) + //if start turf is farther than maxnodes from end turf, no need to do anything + if(call(start, dist)(end) > maxnodes) + return 0 + maxnodedepth = maxnodes //no need to consider path longer than maxnodes + + var/Heap/open = new /Heap(/proc/HeapPathWeightCompare) //the open list + var/list/closed = new() //the closed list + var/list/path = null //the returned path, if any + var/PathNode/cur //current processed turf + + //initialization + open.Insert(new /PathNode(start,null,0,call(start,dist)(end),0)) + + //then run the main loop + while(!open.IsEmpty() && !path) + { + //get the lower f node on the open list + cur = open.Pop() //get the lower f turf in the open list + closed.Add(cur.source) //and tell we've processed it + + //if we only want to get near the target, check if we're close enough + var/closeenough + if(mintargetdist) + closeenough = call(cur.source,dist)(end) <= mintargetdist + + //if too many steps, abandon that path + if(maxnodedepth && (cur.nt > maxnodedepth)) + continue + + //found the target turf (or close enough), let's create the path to it + if(cur.source == end || closeenough) + path = new() + path.Add(cur.source) + + while(cur.prevNode) + cur = cur.prevNode + path.Add(cur.source) + + break + + //get adjacents turfs using the adjacent proc, checking for access with id + var/list/L = call(cur.source,adjacent)(caller, id, simulated_only) + for(var/turf/T in L) + if(T == exclude || (T in closed)) + continue + + var/newg = cur.g + call(cur.source,dist)(T) + if(!T.PNode) //is not already in open list, so add it + open.Insert(new /PathNode(T,cur,newg,call(T,dist)(end),cur.nt+1)) + else //is already in open list, check if it's a better way from the current turf + if(newg < T.PNode.g) + T.PNode.prevNode = cur + T.PNode.g = newg + T.PNode.calc_f() + T.PNode.nt = cur.nt + 1 + open.ReSort(T.PNode)//reorder the changed element in the list + + } + + //cleaning after us + for(var/PathNode/PN in open.L) + PN.source.PNode = null + for(var/turf/T in closed) + T.PNode = null + + //reverse the path to get it from start to finish + if(path) + for(var/i = 1; i <= path.len/2; i++) + path.Swap(i,path.len-i+1) + + return path + +//Returns adjacent turfs in cardinal directions that are reachable +//simulated_only controls whether only simulated turfs are considered or not +/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only) + var/list/L = new() + var/turf/simulated/T + + for(var/dir in cardinal) + T = get_step(src,dir) + if(!T || (simulated_only && !istype(T))) + continue + if(!T.density && !LinkBlockedWithAccess(T,caller, ID)) + L.Add(T) + return L + +//Returns adjacent turfs in cardinal directions that are reachable via atmos +/turf/proc/reachableAdjacentAtmosTurfs() + return atmos_adjacent_turfs + +/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID) + var/adir = get_dir(src, T) + var/rdir = get_dir(T, src) + + for(var/obj/structure/window/W in src) + if(!W.CanAStarPass(ID, adir)) + return 1 + for(var/obj/machinery/door/window/W in src) + if(!W.CanAStarPass(ID, adir)) + return 1 + for(var/obj/O in T) + if(!O.CanAStarPass(ID, rdir, caller)) + return 1 + + return 0 diff --git a/code/defines/procs/dbcore.dm b/code/defines/procs/dbcore.dm index b04a4c3b93a..86862eb9fcc 100644 --- a/code/defines/procs/dbcore.dm +++ b/code/defines/procs/dbcore.dm @@ -1,206 +1,206 @@ -//cursors -#define Default_Cursor 0 -#define Client_Cursor 1 -#define Server_Cursor 2 -//conversions -#define TEXT_CONV 1 -#define RSC_FILE_CONV 2 -#define NUMBER_CONV 3 -//column flag values: -#define IS_NUMERIC 1 -#define IS_BINARY 2 -#define IS_NOT_NULL 4 -#define IS_PRIMARY_KEY 8 -#define IS_UNSIGNED 16 -//types -#define TINYINT 1 -#define SMALLINT 2 -#define MEDIUMINT 3 -#define INTEGER 4 -#define BIGINT 5 -#define DECIMAL 6 -#define FLOAT 7 -#define DOUBLE 8 -#define DATE 9 -#define DATETIME 10 -#define TIMESTAMP 11 -#define TIME 12 -#define STRING 13 -#define BLOB 14 -// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew - - -// Deprecated! See global.dm for new configuration vars -/* -var/DB_SERVER = "" // This is the location of your MySQL server (localhost is USUALLY fine) -var/DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is the default) -*/ - -DBConnection - var/_db_con // This variable contains a reference to the actual database connection. - var/dbi // This variable is a string containing the DBI MySQL requires. - var/user // This variable contains the username data. - var/password // This variable contains the password data. - var/default_cursor // This contains the default database cursor data. - // - var/server = "" - var/port = 3306 - -DBConnection/New(dbi_handler,username,password_handler,cursor_handler) - src.dbi = dbi_handler - src.user = username - src.password = password_handler - src.default_cursor = cursor_handler - _db_con = _dm_db_new_con() - -DBConnection/proc/Connect(dbi_handler=src.dbi,user_handler=src.user,password_handler=src.password,cursor_handler) - if(!config.sql_enabled) - return 0 - if(!src) return 0 - cursor_handler = src.default_cursor - if(!cursor_handler) cursor_handler = Default_Cursor - return _dm_db_connect(_db_con,dbi_handler,user_handler,password_handler,cursor_handler,null) - -DBConnection/proc/Disconnect() return _dm_db_close(_db_con) - -DBConnection/proc/IsConnected() - if(!config.sql_enabled) return 0 - var/success = _dm_db_is_connected(_db_con) - return success - -DBConnection/proc/Quote(str) return _dm_db_quote(_db_con,str) - -DBConnection/proc/ErrorMsg() return _dm_db_error_msg(_db_con) -DBConnection/proc/SelectDB(database_name,dbi) - if(IsConnected()) Disconnect() - //return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[DB_SERVER]:[DB_PORT]"]",user,password) - return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[sqladdress]:[sqlport]"]",user,password) -DBConnection/proc/NewQuery(sql_query,cursor_handler=src.default_cursor) return new/DBQuery(sql_query,src,cursor_handler) - - -DBQuery/New(sql_query,DBConnection/connection_handler,cursor_handler) - if(sql_query) src.sql = sql_query - if(connection_handler) src.db_connection = connection_handler - if(cursor_handler) src.default_cursor = cursor_handler - _db_query = _dm_db_new_query() - return ..() - - -DBQuery - var/sql // The sql query being executed. - var/default_cursor - var/list/columns //list of DB Columns populated by Columns() - var/list/conversions - var/list/item[0] //list of data values populated by NextRow() - - var/DBConnection/db_connection - var/_db_query - -DBQuery/proc/Connect(DBConnection/connection_handler) src.db_connection = connection_handler - -DBQuery/proc/Execute(sql_query=src.sql,cursor_handler=default_cursor) - Close() - return _dm_db_execute(_db_query,sql_query,db_connection._db_con,cursor_handler,null) - -DBQuery/proc/NextRow() return _dm_db_next_row(_db_query,item,conversions) - -DBQuery/proc/RowsAffected() return _dm_db_rows_affected(_db_query) - -DBQuery/proc/RowCount() return _dm_db_row_count(_db_query) - -DBQuery/proc/ErrorMsg() return _dm_db_error_msg(_db_query) - -DBQuery/proc/Columns() - if(!columns) - columns = _dm_db_columns(_db_query,/DBColumn) - return columns - -DBQuery/proc/GetRowData() - var/list/columns = Columns() - var/list/results - if(columns.len) - results = list() - for(var/C in columns) - results+=C - var/DBColumn/cur_col = columns[C] - results[C] = src.item[(cur_col.position+1)] - return results - -DBQuery/proc/Close() - item.len = 0 - columns = null - conversions = null - return _dm_db_close(_db_query) - -DBQuery/proc/Quote(str) - return db_connection.Quote(str) - -DBQuery/proc/SetConversion(column,conversion) - if(istext(column)) column = columns.Find(column) - if(!conversions) conversions = new/list(column) - else if(conversions.len < column) conversions.len = column - conversions[column] = conversion - - -DBColumn - var/name - var/table - var/position //1-based index into item data - var/sql_type - var/flags - var/length - var/max_length - -DBColumn/New(name_handler,table_handler,position_handler,type_handler,flag_handler,length_handler,max_length_handler) - src.name = name_handler - src.table = table_handler - src.position = position_handler - src.sql_type = type_handler - src.flags = flag_handler - src.length = length_handler - src.max_length = max_length_handler - return ..() - - -DBColumn/proc/SqlTypeName(type_handler=src.sql_type) - switch(type_handler) - if(TINYINT) return "TINYINT" - if(SMALLINT) return "SMALLINT" - if(MEDIUMINT) return "MEDIUMINT" - if(INTEGER) return "INTEGER" - if(BIGINT) return "BIGINT" - if(FLOAT) return "FLOAT" - if(DOUBLE) return "DOUBLE" - if(DATE) return "DATE" - if(DATETIME) return "DATETIME" - if(TIMESTAMP) return "TIMESTAMP" - if(TIME) return "TIME" - if(STRING) return "STRING" - if(BLOB) return "BLOB" - - -#undef Default_Cursor -#undef Client_Cursor -#undef Server_Cursor -#undef TEXT_CONV -#undef RSC_FILE_CONV -#undef NUMBER_CONV -#undef IS_NUMERIC -#undef IS_BINARY -#undef IS_NOT_NULL -#undef IS_PRIMARY_KEY -#undef IS_UNSIGNED -#undef TINYINT -#undef SMALLINT -#undef MEDIUMINT -#undef INTEGER -#undef BIGINT -#undef DECIMAL -#undef FLOAT -#undef DOUBLE -#undef DATE -#undef DATETIME -#undef TIMESTAMP -#undef TIME -#undef STRING -#undef BLOB +//cursors +#define Default_Cursor 0 +#define Client_Cursor 1 +#define Server_Cursor 2 +//conversions +#define TEXT_CONV 1 +#define RSC_FILE_CONV 2 +#define NUMBER_CONV 3 +//column flag values: +#define IS_NUMERIC 1 +#define IS_BINARY 2 +#define IS_NOT_NULL 4 +#define IS_PRIMARY_KEY 8 +#define IS_UNSIGNED 16 +//types +#define TINYINT 1 +#define SMALLINT 2 +#define MEDIUMINT 3 +#define INTEGER 4 +#define BIGINT 5 +#define DECIMAL 6 +#define FLOAT 7 +#define DOUBLE 8 +#define DATE 9 +#define DATETIME 10 +#define TIMESTAMP 11 +#define TIME 12 +#define STRING 13 +#define BLOB 14 +// TODO: Investigate more recent type additions and see if I can handle them. - Nadrew + + +// Deprecated! See global.dm for new configuration vars +/* +var/DB_SERVER = "" // This is the location of your MySQL server (localhost is USUALLY fine) +var/DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is the default) +*/ + +DBConnection + var/_db_con // This variable contains a reference to the actual database connection. + var/dbi // This variable is a string containing the DBI MySQL requires. + var/user // This variable contains the username data. + var/password // This variable contains the password data. + var/default_cursor // This contains the default database cursor data. + // + var/server = "" + var/port = 3306 + +DBConnection/New(dbi_handler,username,password_handler,cursor_handler) + src.dbi = dbi_handler + src.user = username + src.password = password_handler + src.default_cursor = cursor_handler + _db_con = _dm_db_new_con() + +DBConnection/proc/Connect(dbi_handler=src.dbi,user_handler=src.user,password_handler=src.password,cursor_handler) + if(!config.sql_enabled) + return 0 + if(!src) return 0 + cursor_handler = src.default_cursor + if(!cursor_handler) cursor_handler = Default_Cursor + return _dm_db_connect(_db_con,dbi_handler,user_handler,password_handler,cursor_handler,null) + +DBConnection/proc/Disconnect() return _dm_db_close(_db_con) + +DBConnection/proc/IsConnected() + if(!config.sql_enabled) return 0 + var/success = _dm_db_is_connected(_db_con) + return success + +DBConnection/proc/Quote(str) return _dm_db_quote(_db_con,str) + +DBConnection/proc/ErrorMsg() return _dm_db_error_msg(_db_con) +DBConnection/proc/SelectDB(database_name,dbi) + if(IsConnected()) Disconnect() + //return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[DB_SERVER]:[DB_PORT]"]",user,password) + return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[sqladdress]:[sqlport]"]",user,password) +DBConnection/proc/NewQuery(sql_query,cursor_handler=src.default_cursor) return new/DBQuery(sql_query,src,cursor_handler) + + +DBQuery/New(sql_query,DBConnection/connection_handler,cursor_handler) + if(sql_query) src.sql = sql_query + if(connection_handler) src.db_connection = connection_handler + if(cursor_handler) src.default_cursor = cursor_handler + _db_query = _dm_db_new_query() + return ..() + + +DBQuery + var/sql // The sql query being executed. + var/default_cursor + var/list/columns //list of DB Columns populated by Columns() + var/list/conversions + var/list/item[0] //list of data values populated by NextRow() + + var/DBConnection/db_connection + var/_db_query + +DBQuery/proc/Connect(DBConnection/connection_handler) src.db_connection = connection_handler + +DBQuery/proc/Execute(sql_query=src.sql,cursor_handler=default_cursor) + Close() + return _dm_db_execute(_db_query,sql_query,db_connection._db_con,cursor_handler,null) + +DBQuery/proc/NextRow() return _dm_db_next_row(_db_query,item,conversions) + +DBQuery/proc/RowsAffected() return _dm_db_rows_affected(_db_query) + +DBQuery/proc/RowCount() return _dm_db_row_count(_db_query) + +DBQuery/proc/ErrorMsg() return _dm_db_error_msg(_db_query) + +DBQuery/proc/Columns() + if(!columns) + columns = _dm_db_columns(_db_query,/DBColumn) + return columns + +DBQuery/proc/GetRowData() + var/list/columns = Columns() + var/list/results + if(columns.len) + results = list() + for(var/C in columns) + results+=C + var/DBColumn/cur_col = columns[C] + results[C] = src.item[(cur_col.position+1)] + return results + +DBQuery/proc/Close() + item.len = 0 + columns = null + conversions = null + return _dm_db_close(_db_query) + +DBQuery/proc/Quote(str) + return db_connection.Quote(str) + +DBQuery/proc/SetConversion(column,conversion) + if(istext(column)) column = columns.Find(column) + if(!conversions) conversions = new/list(column) + else if(conversions.len < column) conversions.len = column + conversions[column] = conversion + + +DBColumn + var/name + var/table + var/position //1-based index into item data + var/sql_type + var/flags + var/length + var/max_length + +DBColumn/New(name_handler,table_handler,position_handler,type_handler,flag_handler,length_handler,max_length_handler) + src.name = name_handler + src.table = table_handler + src.position = position_handler + src.sql_type = type_handler + src.flags = flag_handler + src.length = length_handler + src.max_length = max_length_handler + return ..() + + +DBColumn/proc/SqlTypeName(type_handler=src.sql_type) + switch(type_handler) + if(TINYINT) return "TINYINT" + if(SMALLINT) return "SMALLINT" + if(MEDIUMINT) return "MEDIUMINT" + if(INTEGER) return "INTEGER" + if(BIGINT) return "BIGINT" + if(FLOAT) return "FLOAT" + if(DOUBLE) return "DOUBLE" + if(DATE) return "DATE" + if(DATETIME) return "DATETIME" + if(TIMESTAMP) return "TIMESTAMP" + if(TIME) return "TIME" + if(STRING) return "STRING" + if(BLOB) return "BLOB" + + +#undef Default_Cursor +#undef Client_Cursor +#undef Server_Cursor +#undef TEXT_CONV +#undef RSC_FILE_CONV +#undef NUMBER_CONV +#undef IS_NUMERIC +#undef IS_BINARY +#undef IS_NOT_NULL +#undef IS_PRIMARY_KEY +#undef IS_UNSIGNED +#undef TINYINT +#undef SMALLINT +#undef MEDIUMINT +#undef INTEGER +#undef BIGINT +#undef DECIMAL +#undef FLOAT +#undef DOUBLE +#undef DATE +#undef DATETIME +#undef TIMESTAMP +#undef TIME +#undef STRING +#undef BLOB diff --git a/code/defines/procs/statistics.dm b/code/defines/procs/statistics.dm index e68bb7ecc63..b2b643541ec 100644 --- a/code/defines/procs/statistics.dm +++ b/code/defines/procs/statistics.dm @@ -1,163 +1,163 @@ -/proc/sql_poll_players() - if(!config.sql_enabled) - return - var/playercount = 0 - for(var/mob/M in GLOB.player_list) - if(M.client) - playercount += 1 - establish_db_connection() - if(!dbcon.IsConnected()) - log_game("SQL ERROR during player polling. Failed to connect.") - else - var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, time) VALUES ([playercount], '[sqltime]')") - if(!query.Execute()) - var/err = query.ErrorMsg() - log_game("SQL ERROR during player polling. Error : \[[err]\]\n") - - -/proc/sql_poll_admins() - if(!config.sql_enabled) - return - var/admincount = GLOB.admins.len - establish_db_connection() - if(!dbcon.IsConnected()) - log_game("SQL ERROR during admin polling. Failed to connect.") - else - var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("legacy_population")] (admincount, time) VALUES ([admincount], '[sqltime]')") - if(!query.Execute()) - var/err = query.ErrorMsg() - log_game("SQL ERROR during admin polling. Error : \[[err]\]\n") - -/proc/sql_report_round_start() - // TODO - if(!config.sql_enabled) - return - -/proc/sql_report_round_end() - // TODO - if(!config.sql_enabled) - return - -/proc/sql_report_death(mob/living/carbon/human/H) - if(!config.sql_enabled) - return - if(!H) - return - if(!H.key || !H.mind) - return - - var/area/placeofdeath = get_area(H.loc) - var/podname = "Unknown" - if(placeofdeath) - podname = sanitizeSQL(placeofdeath.name) - - var/sqlname = sanitizeSQL(H.real_name) - var/sqlkey = sanitizeSQL(H.key) - var/sqlpod = sanitizeSQL(podname) - var/sqlspecial = sanitizeSQL(H.mind.special_role) - var/sqljob = sanitizeSQL(H.mind.assigned_role) - var/laname - var/lakey - if(H.lastattacker) - laname = sanitizeSQL(H.lastattacker:real_name) - lakey = sanitizeSQL(H.lastattacker:key) - var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") - var/coord = "[H.x], [H.y], [H.z]" -// to_chat(world, "INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss) VALUES ('[sqlname]', '[sqlkey]', '[sqljob]', '[sqlspecial]', '[sqlpod]', '[sqltime]', '[laname]', '[lakey]', '[H.gender]', [H.bruteloss], [H.getFireLoss()], [H.getBrainLoss()], [H.getOxyLoss()])") - establish_db_connection() - if(!dbcon.IsConnected()) - log_game("SQL ERROR during death reporting. Failed to connect.") - else - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("death")] (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES ('[sqlname]', '[sqlkey]', '[sqljob]', '[sqlspecial]', '[sqlpod]', '[sqltime]', '[laname]', '[lakey]', '[H.gender]', [H.getBruteLoss()], [H.getFireLoss()], [H.getBrainLoss()], [H.getOxyLoss()], '[coord]')") - if(!query.Execute()) - var/err = query.ErrorMsg() - log_game("SQL ERROR during death reporting. Error : \[[err]\]\n") - - -/proc/sql_report_cyborg_death(mob/living/silicon/robot/H) - if(!config.sql_enabled) - return - if(!H) - return - if(!H.key || !H.mind) - return - - var/turf/T = H.loc - var/area/placeofdeath = get_area(T.loc) - var/podname = sanitizeSQL(placeofdeath.name) - - var/sqlname = sanitizeSQL(H.real_name) - var/sqlkey = sanitizeSQL(H.key) - var/sqlpod = sanitizeSQL(podname) - var/sqlspecial = sanitizeSQL(H.mind.special_role) - var/sqljob = sanitizeSQL(H.mind.assigned_role) - var/laname - var/lakey - if(H.lastattacker) - laname = sanitizeSQL(H.lastattacker:real_name) - lakey = sanitizeSQL(H.lastattacker:key) - var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") - var/coord = "[H.x], [H.y], [H.z]" -// to_chat(world, "INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss) VALUES ('[sqlname]', '[sqlkey]', '[sqljob]', '[sqlspecial]', '[sqlpod]', '[sqltime]', '[laname]', '[lakey]', '[H.gender]', [H.bruteloss], [H.getFireLoss()], [H.brainloss], [H.getOxyLoss()])") - establish_db_connection() - if(!dbcon.IsConnected()) - log_game("SQL ERROR during death reporting. Failed to connect.") - else - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("death")] (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES ('[sqlname]', '[sqlkey]', '[sqljob]', '[sqlspecial]', '[sqlpod]', '[sqltime]', '[laname]', '[lakey]', '[H.gender]', [H.getBruteLoss()], [H.getFireLoss()], [H.getBrainLoss()], [H.getOxyLoss()], '[coord]')") - if(!query.Execute()) - var/err = query.ErrorMsg() - log_game("SQL ERROR during death reporting. Error : \[[err]\]\n") - -/proc/statistic_cycle() - if(!config.sql_enabled) - return - while(1) - sql_poll_players() - sleep(600) - sql_poll_admins() - sleep(6000) //Poll every ten minutes - -//This proc is used for feedback. It is executed at round end. -/proc/sql_commit_feedback() - if(!blackbox) - log_game("Round ended without a blackbox recorder. No feedback was sent to the database.") - return - - //content is a list of lists. Each item in the list is a list with two fields, a variable name and a value. Items MUST only have these two values. - var/list/datum/feedback_variable/content = blackbox.get_round_feedback() - - if(!content) - log_game("Round ended without any feedback being generated. No feedback was sent to the database.") - return - - establish_db_connection() - if(!dbcon.IsConnected()) - log_game("SQL ERROR during feedback reporting. Failed to connect.") - else - - var/DBQuery/max_query = dbcon.NewQuery("SELECT MAX(roundid) AS max_round_id FROM [format_table_name("feedback")]") - max_query.Execute() - - var/newroundid - - while(max_query.NextRow()) - newroundid = max_query.item[1] - - if(!(isnum(newroundid))) - newroundid = text2num(newroundid) - - if(isnum(newroundid)) - newroundid++ - else - newroundid = 1 - - for(var/datum/feedback_variable/item in content) - var/variable = item.get_variable() - var/value = item.get_value() - - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("feedback")] (id, roundid, time, variable, value) VALUES (null, [newroundid], Now(), '[variable]', '[value]')") - if(!query.Execute()) - var/err = query.ErrorMsg() - log_game("SQL ERROR during feedback reporting. Error : \[[err]\]\n") +/proc/sql_poll_players() + if(!config.sql_enabled) + return + var/playercount = 0 + for(var/mob/M in GLOB.player_list) + if(M.client) + playercount += 1 + establish_db_connection() + if(!dbcon.IsConnected()) + log_game("SQL ERROR during player polling. Failed to connect.") + else + var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, time) VALUES ([playercount], '[sqltime]')") + if(!query.Execute()) + var/err = query.ErrorMsg() + log_game("SQL ERROR during player polling. Error : \[[err]\]\n") + + +/proc/sql_poll_admins() + if(!config.sql_enabled) + return + var/admincount = GLOB.admins.len + establish_db_connection() + if(!dbcon.IsConnected()) + log_game("SQL ERROR during admin polling. Failed to connect.") + else + var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("legacy_population")] (admincount, time) VALUES ([admincount], '[sqltime]')") + if(!query.Execute()) + var/err = query.ErrorMsg() + log_game("SQL ERROR during admin polling. Error : \[[err]\]\n") + +/proc/sql_report_round_start() + // TODO + if(!config.sql_enabled) + return + +/proc/sql_report_round_end() + // TODO + if(!config.sql_enabled) + return + +/proc/sql_report_death(mob/living/carbon/human/H) + if(!config.sql_enabled) + return + if(!H) + return + if(!H.key || !H.mind) + return + + var/area/placeofdeath = get_area(H.loc) + var/podname = "Unknown" + if(placeofdeath) + podname = sanitizeSQL(placeofdeath.name) + + var/sqlname = sanitizeSQL(H.real_name) + var/sqlkey = sanitizeSQL(H.key) + var/sqlpod = sanitizeSQL(podname) + var/sqlspecial = sanitizeSQL(H.mind.special_role) + var/sqljob = sanitizeSQL(H.mind.assigned_role) + var/laname + var/lakey + if(H.lastattacker) + laname = sanitizeSQL(H.lastattacker:real_name) + lakey = sanitizeSQL(H.lastattacker:key) + var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") + var/coord = "[H.x], [H.y], [H.z]" +// to_chat(world, "INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss) VALUES ('[sqlname]', '[sqlkey]', '[sqljob]', '[sqlspecial]', '[sqlpod]', '[sqltime]', '[laname]', '[lakey]', '[H.gender]', [H.bruteloss], [H.getFireLoss()], [H.getBrainLoss()], [H.getOxyLoss()])") + establish_db_connection() + if(!dbcon.IsConnected()) + log_game("SQL ERROR during death reporting. Failed to connect.") + else + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("death")] (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES ('[sqlname]', '[sqlkey]', '[sqljob]', '[sqlspecial]', '[sqlpod]', '[sqltime]', '[laname]', '[lakey]', '[H.gender]', [H.getBruteLoss()], [H.getFireLoss()], [H.getBrainLoss()], [H.getOxyLoss()], '[coord]')") + if(!query.Execute()) + var/err = query.ErrorMsg() + log_game("SQL ERROR during death reporting. Error : \[[err]\]\n") + + +/proc/sql_report_cyborg_death(mob/living/silicon/robot/H) + if(!config.sql_enabled) + return + if(!H) + return + if(!H.key || !H.mind) + return + + var/turf/T = H.loc + var/area/placeofdeath = get_area(T.loc) + var/podname = sanitizeSQL(placeofdeath.name) + + var/sqlname = sanitizeSQL(H.real_name) + var/sqlkey = sanitizeSQL(H.key) + var/sqlpod = sanitizeSQL(podname) + var/sqlspecial = sanitizeSQL(H.mind.special_role) + var/sqljob = sanitizeSQL(H.mind.assigned_role) + var/laname + var/lakey + if(H.lastattacker) + laname = sanitizeSQL(H.lastattacker:real_name) + lakey = sanitizeSQL(H.lastattacker:key) + var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") + var/coord = "[H.x], [H.y], [H.z]" +// to_chat(world, "INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss) VALUES ('[sqlname]', '[sqlkey]', '[sqljob]', '[sqlspecial]', '[sqlpod]', '[sqltime]', '[laname]', '[lakey]', '[H.gender]', [H.bruteloss], [H.getFireLoss()], [H.brainloss], [H.getOxyLoss()])") + establish_db_connection() + if(!dbcon.IsConnected()) + log_game("SQL ERROR during death reporting. Failed to connect.") + else + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("death")] (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES ('[sqlname]', '[sqlkey]', '[sqljob]', '[sqlspecial]', '[sqlpod]', '[sqltime]', '[laname]', '[lakey]', '[H.gender]', [H.getBruteLoss()], [H.getFireLoss()], [H.getBrainLoss()], [H.getOxyLoss()], '[coord]')") + if(!query.Execute()) + var/err = query.ErrorMsg() + log_game("SQL ERROR during death reporting. Error : \[[err]\]\n") + +/proc/statistic_cycle() + if(!config.sql_enabled) + return + while(1) + sql_poll_players() + sleep(600) + sql_poll_admins() + sleep(6000) //Poll every ten minutes + +//This proc is used for feedback. It is executed at round end. +/proc/sql_commit_feedback() + if(!blackbox) + log_game("Round ended without a blackbox recorder. No feedback was sent to the database.") + return + + //content is a list of lists. Each item in the list is a list with two fields, a variable name and a value. Items MUST only have these two values. + var/list/datum/feedback_variable/content = blackbox.get_round_feedback() + + if(!content) + log_game("Round ended without any feedback being generated. No feedback was sent to the database.") + return + + establish_db_connection() + if(!dbcon.IsConnected()) + log_game("SQL ERROR during feedback reporting. Failed to connect.") + else + + var/DBQuery/max_query = dbcon.NewQuery("SELECT MAX(roundid) AS max_round_id FROM [format_table_name("feedback")]") + max_query.Execute() + + var/newroundid + + while(max_query.NextRow()) + newroundid = max_query.item[1] + + if(!(isnum(newroundid))) + newroundid = text2num(newroundid) + + if(isnum(newroundid)) + newroundid++ + else + newroundid = 1 + + for(var/datum/feedback_variable/item in content) + var/variable = item.get_variable() + var/value = item.get_value() + + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("feedback")] (id, roundid, time, variable, value) VALUES (null, [newroundid], Now(), '[variable]', '[value]')") + if(!query.Execute()) + var/err = query.ErrorMsg() + log_game("SQL ERROR during feedback reporting. Error : \[[err]\]\n") diff --git a/code/defines/vox_sounds.dm b/code/defines/vox_sounds.dm index 794a259ea4e..bb155337502 100644 --- a/code/defines/vox_sounds.dm +++ b/code/defines/vox_sounds.dm @@ -1267,4 +1267,4 @@ var/list/vox_sounds = list("," = 'sound/vox_fem/,.ogg', "zero" = 'sound/vox_fem/zero.ogg', "zone" = 'sound/vox_fem/zone.ogg', "zulu" = 'sound/vox_fem/zulu.ogg' -) \ No newline at end of file +) diff --git a/code/game/area/Dynamic areas.dm b/code/game/area/Dynamic areas.dm index c6d1154847b..10957e11d2a 100644 --- a/code/game/area/Dynamic areas.dm +++ b/code/game/area/Dynamic areas.dm @@ -41,4 +41,4 @@ match_width = 5 match_height = 4 requires_power = FALSE - dynamic_lighting = DYNAMIC_LIGHTING_FORCED \ No newline at end of file + dynamic_lighting = DYNAMIC_LIGHTING_FORCED diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm index 0bc7b5f001b..2ca607921ff 100644 --- a/code/game/area/ai_monitored.dm +++ b/code/game/area/ai_monitored.dm @@ -1,26 +1,26 @@ -/area/ai_monitored - name = "AI Monitored Area" - var/obj/machinery/camera/motioncamera = null - - -/area/ai_monitored/New() - ..() - // locate and store the motioncamera - spawn (20) // spawn on a delay to let turfs/objs load - for(var/obj/machinery/camera/M in src) - if(M.isMotion()) - motioncamera = M - M.area_motion = src - return - return - -/area/ai_monitored/Entered(atom/movable/O) - ..() - if(ismob(O) && motioncamera) - motioncamera.newTarget(O) - -/area/ai_monitored/Exited(atom/movable/O) - if(ismob(O) && motioncamera) - motioncamera.lostTarget(O) - - +/area/ai_monitored + name = "AI Monitored Area" + var/obj/machinery/camera/motioncamera = null + + +/area/ai_monitored/New() + ..() + // locate and store the motioncamera + spawn (20) // spawn on a delay to let turfs/objs load + for(var/obj/machinery/camera/M in src) + if(M.isMotion()) + motioncamera = M + M.area_motion = src + return + return + +/area/ai_monitored/Entered(atom/movable/O) + ..() + if(ismob(O) && motioncamera) + motioncamera.newTarget(O) + +/area/ai_monitored/Exited(atom/movable/O) + if(ismob(O) && motioncamera) + motioncamera.lostTarget(O) + + diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 26dc6ddbf56..dbc0a8c567b 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -1,453 +1,453 @@ -/area - var/fire = null - var/atmosalm = ATMOS_ALARM_NONE - var/poweralm = TRUE - var/party = null - var/report_alerts = TRUE // Should atmos alerts notify the AI/computers - level = null - name = "Space" - icon = 'icons/turf/areas.dmi' - icon_state = "unknown" - layer = AREA_LAYER - plane = BLACKNESS_PLANE //Keeping this on the default plane, GAME_PLANE, will make area overlays fail to render on FLOOR_PLANE. - luminosity = 0 - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - invisibility = INVISIBILITY_LIGHTING - var/valid_territory = TRUE //used for cult summoning areas on station zlevel - var/map_name // Set in New(); preserves the name set by the map maker, even if renamed by the Blueprints. - var/lightswitch = TRUE - - var/eject = null - - var/debug = FALSE - var/requires_power = TRUE - var/always_unpowered = FALSE //this gets overriden to 1 for space in area/New() - - var/power_equip = TRUE - var/power_light = TRUE - var/power_environ = TRUE - var/music = null - var/used_equip = FALSE - var/used_light = FALSE - var/used_environ = FALSE - var/static_equip - var/static_light = FALSE - var/static_environ - - var/has_gravity = TRUE - var/list/apc = list() - var/no_air = null - - var/air_doors_activated = FALSE - - var/tele_proof = FALSE - var/no_teleportlocs = FALSE - - var/outdoors = FALSE //For space, the asteroid, lavaland, etc. Used with blueprints to determine if we are adding a new area (vs editing a station room) - var/xenobiology_compatible = FALSE //Can the Xenobio management console transverse this area by default? - var/nad_allowed = FALSE //is the station NAD allowed on this area? - - // This var is used with the maploader (modules/awaymissions/maploader/reader.dm) - // if this is 1, when used in a map snippet, this will instantiate a unique - // area from any other instances already present (meaning you can have - // separate APCs, and so on) - var/there_can_be_many = FALSE - - var/global/global_uid = 0 - var/uid - - var/list/ambientsounds = list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg',\ - 'sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg',\ - 'sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg',\ - 'sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg',\ - 'sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg',\ - 'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg') - - var/fast_despawn = FALSE - var/can_get_auto_cryod = TRUE - var/hide_attacklogs = FALSE // For areas such as thunderdome, lavaland syndiebase, etc which generate a lot of spammy attacklogs. Reduces log priority. - - var/parallax_movedir = 0 - var/moving = FALSE - -/area/Initialize(mapload) - GLOB.all_areas += src - icon_state = "" - layer = AREA_LAYER - uid = ++global_uid - - map_name = name // Save the initial (the name set in the map) name of the area. - - if(requires_power) - luminosity = 0 - else - power_light = TRUE - power_equip = TRUE - power_environ = TRUE - - if(dynamic_lighting == DYNAMIC_LIGHTING_FORCED) - dynamic_lighting = DYNAMIC_LIGHTING_ENABLED - luminosity = 0 - else if(dynamic_lighting != DYNAMIC_LIGHTING_IFSTARLIGHT) - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED - if(dynamic_lighting == DYNAMIC_LIGHTING_IFSTARLIGHT) - dynamic_lighting = config.starlight ? DYNAMIC_LIGHTING_ENABLED : DYNAMIC_LIGHTING_DISABLED - - . = ..() - - blend_mode = BLEND_MULTIPLY // Putting this in the constructor so that it stops the icons being screwed up in the map editor. - - if(!IS_DYNAMIC_LIGHTING(src)) - add_overlay(/obj/effect/fullbright) - - reg_in_areas_in_z() - - return INITIALIZE_HINT_LATELOAD - -/area/LateInitialize() - . = ..() - power_change() // all machines set to current power level, also updates lighting icon - -/area/proc/reg_in_areas_in_z() - if(contents.len) - var/list/areas_in_z = space_manager.areas_in_z - var/z - for(var/i in 1 to contents.len) - var/atom/thing = contents[i] - if(!thing) - continue - z = thing.z - break - if(!z) - WARNING("No z found for [src]") - return - if(!areas_in_z["[z]"]) - areas_in_z["[z]"] = list() - areas_in_z["[z]"] += src - -/area/proc/get_cameras() - var/list/cameras = list() - for(var/obj/machinery/camera/C in src) - cameras += C - return cameras - - -/area/proc/atmosalert(danger_level, var/alarm_source, var/force = FALSE) - if(report_alerts) - if(danger_level == ATMOS_ALARM_NONE) - SSalarms.atmosphere_alarm.clearAlarm(src, alarm_source) - else - SSalarms.atmosphere_alarm.triggerAlarm(src, alarm_source, severity = danger_level) - - //Check all the alarms before lowering atmosalm. Raising is perfectly fine. If force = 1 we don't care. - for(var/obj/machinery/alarm/AA in src) - if(!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted && AA.report_danger_level && !force) - danger_level = max(danger_level, AA.danger_level) - - if(danger_level != atmosalm) - if(danger_level < ATMOS_ALARM_WARNING && atmosalm >= ATMOS_ALARM_WARNING) - //closing the doors on red and opening on green provides a bit of hysteresis that will hopefully prevent fire doors from opening and closing repeatedly due to noise - air_doors_open() - else if(danger_level >= ATMOS_ALARM_DANGER && atmosalm < ATMOS_ALARM_DANGER) - air_doors_close() - - atmosalm = danger_level - for(var/obj/machinery/alarm/AA in src) - AA.update_icon() - - air_alarm_repository.update_cache(src) - return 1 - air_alarm_repository.update_cache(src) - return 0 - -/area/proc/air_doors_close() - if(!air_doors_activated) - air_doors_activated = TRUE - for(var/obj/machinery/door/firedoor/D in src) - if(!D.welded) - D.activate_alarm() - if(D.operating) - D.nextstate = FD_CLOSED - else if(!D.density) - spawn(0) - D.close() - -/area/proc/air_doors_open() - if(air_doors_activated) - air_doors_activated = FALSE - for(var/obj/machinery/door/firedoor/D in src) - if(!D.welded) - D.deactivate_alarm() - if(D.operating) - D.nextstate = OPEN - else if(D.density) - spawn(0) - D.open() - - -/area/proc/fire_alert() - if(!fire) - fire = 1 //used for firedoor checks - updateicon() - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - air_doors_close() - -/area/proc/fire_reset() - if(fire) - fire = 0 //used for firedoor checks - updateicon() - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - air_doors_open() - - return - -/area/proc/burglaralert(var/obj/trigger) - if(always_unpowered == 1) //no burglar alarms in space/asteroid - return - - //Trigger alarm effect - set_fire_alarm_effect() - - //Lockdown airlocks - for(var/obj/machinery/door/airlock/A in src) - spawn(0) - A.close() - if(A.density) - A.lock() - - SSalarms.burglar_alarm.triggerAlarm(src, trigger) - spawn(600) - SSalarms.burglar_alarm.clearAlarm(src, trigger) - -/area/proc/set_fire_alarm_effect() - fire = 1 - updateicon() - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/area/proc/readyalert() - if(!eject) - eject = 1 - updateicon() - -/area/proc/readyreset() - if(eject) - eject = 0 - updateicon() - -/area/proc/partyalert() - if(!party) - party = 1 - updateicon() - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/area/proc/partyreset() - if(party) - party = 0 - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - updateicon() - -/area/proc/updateicon() - if((fire || eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc. - if(fire && !eject && !party) - icon_state = "red" - else if(!fire && eject && !party) - icon_state = "red" - else if(party && !fire && !eject) - icon_state = "party" - else - icon_state = "blue-red" - else - var/weather_icon - for(var/V in SSweather.processing) - var/datum/weather/W = V - if(W.stage != END_STAGE && (src in W.impacted_areas)) - W.update_areas() - weather_icon = TRUE - if(!weather_icon) - icon_state = null - -/area/space/updateicon() - icon_state = null - -/* -#define EQUIP 1 -#define LIGHT 2 -#define ENVIRON 3 -*/ - -/area/proc/powered(var/chan) // return true if the area has power to given channel - - if(!requires_power) - return 1 - if(always_unpowered) - return 0 - switch(chan) - if(EQUIP) - return power_equip - if(LIGHT) - return power_light - if(ENVIRON) - return power_environ - - return 0 - -/area/space/powered(chan) //Nope.avi - return 0 - -// called when power status changes - -/area/proc/power_change() - for(var/obj/machinery/M in src) // for each machine in the area - M.power_change() // reverify power status (to update icons etc.) - updateicon() - -/area/proc/usage(var/chan) - var/used = 0 - switch(chan) - if(LIGHT) - used += used_light - if(EQUIP) - used += used_equip - if(ENVIRON) - used += used_environ - if(TOTAL) - used += used_light + used_equip + used_environ - if(STATIC_EQUIP) - used += static_equip - if(STATIC_LIGHT) - used += static_light - if(STATIC_ENVIRON) - used += static_environ - return used - -/area/proc/addStaticPower(value, powerchannel) - switch(powerchannel) - if(STATIC_EQUIP) - static_equip += value - if(STATIC_LIGHT) - static_light += value - if(STATIC_ENVIRON) - static_environ += value - -/area/proc/clear_usage() - - used_equip = 0 - used_light = 0 - used_environ = 0 - -/area/proc/use_power(var/amount, var/chan) - switch(chan) - if(EQUIP) - used_equip += amount - if(LIGHT) - used_light += amount - if(ENVIRON) - used_environ += amount - -/area/proc/use_battery_power(var/amount, var/chan) - switch(chan) - if(EQUIP) - used_equip += amount - if(LIGHT) - used_light += amount - if(ENVIRON) - used_environ += amount - - -/area/Entered(A) - var/area/newarea - var/area/oldarea - - if(istype(A,/mob)) - var/mob/M=A - - if(!M.lastarea) - M.lastarea = get_area(M) - newarea = get_area(M) - oldarea = M.lastarea - - if(newarea==oldarea) return - - M.lastarea = src - - if(!istype(A,/mob/living)) return - - var/mob/living/L = A - if(!L.ckey) return - if((oldarea.has_gravity == 0) && (newarea.has_gravity == 1) && (L.m_intent == MOVE_INTENT_RUN)) // Being ready when you change areas gives you a chance to avoid falling all together. - thunk(L) - - // Ambience goes down here -- make sure to list each area seperately for ease of adding things in later, thanks! Note: areas adjacent to each other should have the same sounds to prevent cutoff when possible.- LastyScratch - if(L && L.client && !L.client.ambience_playing && (L.client.prefs.sound & SOUND_BUZZ)) //split off the white noise from the rest of the ambience because of annoyance complaints - Kluys - L.client.ambience_playing = 1 - L << sound('sound/ambience/shipambience.ogg', repeat = 1, wait = 0, volume = 35, channel = CHANNEL_BUZZ) - else if(L && L.client && !(L.client.prefs.sound & SOUND_BUZZ)) - L.client.ambience_playing = 0 - - if(prob(35) && L && L.client && (L.client.prefs.sound & SOUND_AMBIENCE)) - var/sound = pick(ambientsounds) - - if(!L.client.played) - L << sound(sound, repeat = 0, wait = 0, volume = 25, channel = CHANNEL_AMBIENCE) - L.client.played = 1 - spawn(600) //ewww - this is very very bad - if(L && L.client) - L.client.played = 0 - -/area/proc/gravitychange(var/gravitystate = 0, var/area/A) - A.has_gravity = gravitystate - - if(gravitystate) - for(var/mob/living/carbon/human/M in A) - thunk(M) - -/area/proc/thunk(var/mob/living/carbon/human/M) - if(istype(M,/mob/living/carbon/human/)) // Only humans can wear magboots, so we give them a chance to. - if(istype(M.shoes, /obj/item/clothing/shoes/magboots) && (M.shoes.flags & NOSLIP)) - return - - if(M.buckled) //Cam't fall down if you are buckled - return - - if(istype(get_turf(M), /turf/space)) // Can't fall onto nothing. - return - - if((istype(M,/mob/living/carbon/human/)) && (M.m_intent == MOVE_INTENT_RUN)) - M.Stun(5) - M.Weaken(5) - - else if(istype(M,/mob/living/carbon/human/)) - M.Stun(2) - M.Weaken(2) - - - to_chat(M, "Gravity!") - -/proc/has_gravity(atom/AT, turf/T) - if(!T) - T = get_turf(AT) - var/area/A = get_area(T) - if(istype(T, /turf/space)) // Turf never has gravity - return 0 - else if(A && A.has_gravity) // Areas which always has gravity - return 1 - else - // There's a gravity generator on our z level - // This would do well when integrated with the z level manager - if(T && gravity_generators["[T.z]"] && length(gravity_generators["[T.z]"])) - return 1 - return 0 - -/area/proc/prison_break() - for(var/obj/machinery/power/apc/temp_apc in src) - temp_apc.overload_lighting(70) - for(var/obj/machinery/door/airlock/temp_airlock in src) - temp_airlock.prison_open() - for(var/obj/machinery/door/window/temp_windoor in src) - temp_windoor.open() - -/area/AllowDrop() - CRASH("Bad op: area/AllowDrop() called") - -/area/drop_location() - CRASH("Bad op: area/drop_location() called") +/area + var/fire = null + var/atmosalm = ATMOS_ALARM_NONE + var/poweralm = TRUE + var/party = null + var/report_alerts = TRUE // Should atmos alerts notify the AI/computers + level = null + name = "Space" + icon = 'icons/turf/areas.dmi' + icon_state = "unknown" + layer = AREA_LAYER + plane = BLACKNESS_PLANE //Keeping this on the default plane, GAME_PLANE, will make area overlays fail to render on FLOOR_PLANE. + luminosity = 0 + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + invisibility = INVISIBILITY_LIGHTING + var/valid_territory = TRUE //used for cult summoning areas on station zlevel + var/map_name // Set in New(); preserves the name set by the map maker, even if renamed by the Blueprints. + var/lightswitch = TRUE + + var/eject = null + + var/debug = FALSE + var/requires_power = TRUE + var/always_unpowered = FALSE //this gets overriden to 1 for space in area/New() + + var/power_equip = TRUE + var/power_light = TRUE + var/power_environ = TRUE + var/music = null + var/used_equip = FALSE + var/used_light = FALSE + var/used_environ = FALSE + var/static_equip + var/static_light = FALSE + var/static_environ + + var/has_gravity = TRUE + var/list/apc = list() + var/no_air = null + + var/air_doors_activated = FALSE + + var/tele_proof = FALSE + var/no_teleportlocs = FALSE + + var/outdoors = FALSE //For space, the asteroid, lavaland, etc. Used with blueprints to determine if we are adding a new area (vs editing a station room) + var/xenobiology_compatible = FALSE //Can the Xenobio management console transverse this area by default? + var/nad_allowed = FALSE //is the station NAD allowed on this area? + + // This var is used with the maploader (modules/awaymissions/maploader/reader.dm) + // if this is 1, when used in a map snippet, this will instantiate a unique + // area from any other instances already present (meaning you can have + // separate APCs, and so on) + var/there_can_be_many = FALSE + + var/global/global_uid = 0 + var/uid + + var/list/ambientsounds = list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg',\ + 'sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg',\ + 'sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg',\ + 'sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg',\ + 'sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg',\ + 'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg') + + var/fast_despawn = FALSE + var/can_get_auto_cryod = TRUE + var/hide_attacklogs = FALSE // For areas such as thunderdome, lavaland syndiebase, etc which generate a lot of spammy attacklogs. Reduces log priority. + + var/parallax_movedir = 0 + var/moving = FALSE + +/area/Initialize(mapload) + GLOB.all_areas += src + icon_state = "" + layer = AREA_LAYER + uid = ++global_uid + + map_name = name // Save the initial (the name set in the map) name of the area. + + if(requires_power) + luminosity = 0 + else + power_light = TRUE + power_equip = TRUE + power_environ = TRUE + + if(dynamic_lighting == DYNAMIC_LIGHTING_FORCED) + dynamic_lighting = DYNAMIC_LIGHTING_ENABLED + luminosity = 0 + else if(dynamic_lighting != DYNAMIC_LIGHTING_IFSTARLIGHT) + dynamic_lighting = DYNAMIC_LIGHTING_DISABLED + if(dynamic_lighting == DYNAMIC_LIGHTING_IFSTARLIGHT) + dynamic_lighting = config.starlight ? DYNAMIC_LIGHTING_ENABLED : DYNAMIC_LIGHTING_DISABLED + + . = ..() + + blend_mode = BLEND_MULTIPLY // Putting this in the constructor so that it stops the icons being screwed up in the map editor. + + if(!IS_DYNAMIC_LIGHTING(src)) + add_overlay(/obj/effect/fullbright) + + reg_in_areas_in_z() + + return INITIALIZE_HINT_LATELOAD + +/area/LateInitialize() + . = ..() + power_change() // all machines set to current power level, also updates lighting icon + +/area/proc/reg_in_areas_in_z() + if(contents.len) + var/list/areas_in_z = space_manager.areas_in_z + var/z + for(var/i in 1 to contents.len) + var/atom/thing = contents[i] + if(!thing) + continue + z = thing.z + break + if(!z) + WARNING("No z found for [src]") + return + if(!areas_in_z["[z]"]) + areas_in_z["[z]"] = list() + areas_in_z["[z]"] += src + +/area/proc/get_cameras() + var/list/cameras = list() + for(var/obj/machinery/camera/C in src) + cameras += C + return cameras + + +/area/proc/atmosalert(danger_level, var/alarm_source, var/force = FALSE) + if(report_alerts) + if(danger_level == ATMOS_ALARM_NONE) + SSalarms.atmosphere_alarm.clearAlarm(src, alarm_source) + else + SSalarms.atmosphere_alarm.triggerAlarm(src, alarm_source, severity = danger_level) + + //Check all the alarms before lowering atmosalm. Raising is perfectly fine. If force = 1 we don't care. + for(var/obj/machinery/alarm/AA in src) + if(!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted && AA.report_danger_level && !force) + danger_level = max(danger_level, AA.danger_level) + + if(danger_level != atmosalm) + if(danger_level < ATMOS_ALARM_WARNING && atmosalm >= ATMOS_ALARM_WARNING) + //closing the doors on red and opening on green provides a bit of hysteresis that will hopefully prevent fire doors from opening and closing repeatedly due to noise + air_doors_open() + else if(danger_level >= ATMOS_ALARM_DANGER && atmosalm < ATMOS_ALARM_DANGER) + air_doors_close() + + atmosalm = danger_level + for(var/obj/machinery/alarm/AA in src) + AA.update_icon() + + air_alarm_repository.update_cache(src) + return 1 + air_alarm_repository.update_cache(src) + return 0 + +/area/proc/air_doors_close() + if(!air_doors_activated) + air_doors_activated = TRUE + for(var/obj/machinery/door/firedoor/D in src) + if(!D.welded) + D.activate_alarm() + if(D.operating) + D.nextstate = FD_CLOSED + else if(!D.density) + spawn(0) + D.close() + +/area/proc/air_doors_open() + if(air_doors_activated) + air_doors_activated = FALSE + for(var/obj/machinery/door/firedoor/D in src) + if(!D.welded) + D.deactivate_alarm() + if(D.operating) + D.nextstate = OPEN + else if(D.density) + spawn(0) + D.open() + + +/area/proc/fire_alert() + if(!fire) + fire = 1 //used for firedoor checks + updateicon() + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + air_doors_close() + +/area/proc/fire_reset() + if(fire) + fire = 0 //used for firedoor checks + updateicon() + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + air_doors_open() + + return + +/area/proc/burglaralert(var/obj/trigger) + if(always_unpowered == 1) //no burglar alarms in space/asteroid + return + + //Trigger alarm effect + set_fire_alarm_effect() + + //Lockdown airlocks + for(var/obj/machinery/door/airlock/A in src) + spawn(0) + A.close() + if(A.density) + A.lock() + + SSalarms.burglar_alarm.triggerAlarm(src, trigger) + spawn(600) + SSalarms.burglar_alarm.clearAlarm(src, trigger) + +/area/proc/set_fire_alarm_effect() + fire = 1 + updateicon() + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/area/proc/readyalert() + if(!eject) + eject = 1 + updateicon() + +/area/proc/readyreset() + if(eject) + eject = 0 + updateicon() + +/area/proc/partyalert() + if(!party) + party = 1 + updateicon() + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/area/proc/partyreset() + if(party) + party = 0 + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + updateicon() + +/area/proc/updateicon() + if((fire || eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc. + if(fire && !eject && !party) + icon_state = "red" + else if(!fire && eject && !party) + icon_state = "red" + else if(party && !fire && !eject) + icon_state = "party" + else + icon_state = "blue-red" + else + var/weather_icon + for(var/V in SSweather.processing) + var/datum/weather/W = V + if(W.stage != END_STAGE && (src in W.impacted_areas)) + W.update_areas() + weather_icon = TRUE + if(!weather_icon) + icon_state = null + +/area/space/updateicon() + icon_state = null + +/* +#define EQUIP 1 +#define LIGHT 2 +#define ENVIRON 3 +*/ + +/area/proc/powered(var/chan) // return true if the area has power to given channel + + if(!requires_power) + return 1 + if(always_unpowered) + return 0 + switch(chan) + if(EQUIP) + return power_equip + if(LIGHT) + return power_light + if(ENVIRON) + return power_environ + + return 0 + +/area/space/powered(chan) //Nope.avi + return 0 + +// called when power status changes + +/area/proc/power_change() + for(var/obj/machinery/M in src) // for each machine in the area + M.power_change() // reverify power status (to update icons etc.) + updateicon() + +/area/proc/usage(var/chan) + var/used = 0 + switch(chan) + if(LIGHT) + used += used_light + if(EQUIP) + used += used_equip + if(ENVIRON) + used += used_environ + if(TOTAL) + used += used_light + used_equip + used_environ + if(STATIC_EQUIP) + used += static_equip + if(STATIC_LIGHT) + used += static_light + if(STATIC_ENVIRON) + used += static_environ + return used + +/area/proc/addStaticPower(value, powerchannel) + switch(powerchannel) + if(STATIC_EQUIP) + static_equip += value + if(STATIC_LIGHT) + static_light += value + if(STATIC_ENVIRON) + static_environ += value + +/area/proc/clear_usage() + + used_equip = 0 + used_light = 0 + used_environ = 0 + +/area/proc/use_power(var/amount, var/chan) + switch(chan) + if(EQUIP) + used_equip += amount + if(LIGHT) + used_light += amount + if(ENVIRON) + used_environ += amount + +/area/proc/use_battery_power(var/amount, var/chan) + switch(chan) + if(EQUIP) + used_equip += amount + if(LIGHT) + used_light += amount + if(ENVIRON) + used_environ += amount + + +/area/Entered(A) + var/area/newarea + var/area/oldarea + + if(istype(A,/mob)) + var/mob/M=A + + if(!M.lastarea) + M.lastarea = get_area(M) + newarea = get_area(M) + oldarea = M.lastarea + + if(newarea==oldarea) return + + M.lastarea = src + + if(!istype(A,/mob/living)) return + + var/mob/living/L = A + if(!L.ckey) return + if((oldarea.has_gravity == 0) && (newarea.has_gravity == 1) && (L.m_intent == MOVE_INTENT_RUN)) // Being ready when you change areas gives you a chance to avoid falling all together. + thunk(L) + + // Ambience goes down here -- make sure to list each area seperately for ease of adding things in later, thanks! Note: areas adjacent to each other should have the same sounds to prevent cutoff when possible.- LastyScratch + if(L && L.client && !L.client.ambience_playing && (L.client.prefs.sound & SOUND_BUZZ)) //split off the white noise from the rest of the ambience because of annoyance complaints - Kluys + L.client.ambience_playing = 1 + L << sound('sound/ambience/shipambience.ogg', repeat = 1, wait = 0, volume = 35, channel = CHANNEL_BUZZ) + else if(L && L.client && !(L.client.prefs.sound & SOUND_BUZZ)) + L.client.ambience_playing = 0 + + if(prob(35) && L && L.client && (L.client.prefs.sound & SOUND_AMBIENCE)) + var/sound = pick(ambientsounds) + + if(!L.client.played) + L << sound(sound, repeat = 0, wait = 0, volume = 25, channel = CHANNEL_AMBIENCE) + L.client.played = 1 + spawn(600) //ewww - this is very very bad + if(L && L.client) + L.client.played = 0 + +/area/proc/gravitychange(var/gravitystate = 0, var/area/A) + A.has_gravity = gravitystate + + if(gravitystate) + for(var/mob/living/carbon/human/M in A) + thunk(M) + +/area/proc/thunk(var/mob/living/carbon/human/M) + if(istype(M,/mob/living/carbon/human/)) // Only humans can wear magboots, so we give them a chance to. + if(istype(M.shoes, /obj/item/clothing/shoes/magboots) && (M.shoes.flags & NOSLIP)) + return + + if(M.buckled) //Cam't fall down if you are buckled + return + + if(istype(get_turf(M), /turf/space)) // Can't fall onto nothing. + return + + if((istype(M,/mob/living/carbon/human/)) && (M.m_intent == MOVE_INTENT_RUN)) + M.Stun(5) + M.Weaken(5) + + else if(istype(M,/mob/living/carbon/human/)) + M.Stun(2) + M.Weaken(2) + + + to_chat(M, "Gravity!") + +/proc/has_gravity(atom/AT, turf/T) + if(!T) + T = get_turf(AT) + var/area/A = get_area(T) + if(istype(T, /turf/space)) // Turf never has gravity + return 0 + else if(A && A.has_gravity) // Areas which always has gravity + return 1 + else + // There's a gravity generator on our z level + // This would do well when integrated with the z level manager + if(T && gravity_generators["[T.z]"] && length(gravity_generators["[T.z]"])) + return 1 + return 0 + +/area/proc/prison_break() + for(var/obj/machinery/power/apc/temp_apc in src) + temp_apc.overload_lighting(70) + for(var/obj/machinery/door/airlock/temp_airlock in src) + temp_airlock.prison_open() + for(var/obj/machinery/door/window/temp_windoor in src) + temp_windoor.open() + +/area/AllowDrop() + CRASH("Bad op: area/AllowDrop() called") + +/area/drop_location() + CRASH("Bad op: area/drop_location() called") diff --git a/code/game/area/areas/depot-areas.dm b/code/game/area/areas/depot-areas.dm index d0ae6fc5588..7a7c934df35 100644 --- a/code/game/area/areas/depot-areas.dm +++ b/code/game/area/areas/depot-areas.dm @@ -476,4 +476,4 @@ var/obj/machinery/shieldwall/syndicate/S = locateUID(shuid) if(S) qdel(S) - shield_list = list() \ No newline at end of file + shield_list = list() diff --git a/code/game/area/areas/mining.dm b/code/game/area/areas/mining.dm index 4a2317889fa..0b07fffda5e 100644 --- a/code/game/area/areas/mining.dm +++ b/code/game/area/areas/mining.dm @@ -127,4 +127,4 @@ icon_state = "danger" /area/lavaland/surface/outdoors/explored - name = "Lavaland Labor Camp" \ No newline at end of file + name = "Lavaland Labor Camp" diff --git a/code/game/area/areas/ruins/lavaland.dm b/code/game/area/areas/ruins/lavaland.dm index ac4c41b0c42..5baf1fe315e 100644 --- a/code/game/area/areas/ruins/lavaland.dm +++ b/code/game/area/areas/ruins/lavaland.dm @@ -87,4 +87,4 @@ //ash walker nest /area/ruin/unpowered/ash_walkers - icon_state = "red" \ No newline at end of file + icon_state = "red" diff --git a/code/game/atoms.dm b/code/game/atoms.dm index cd7e1e45015..f9d66372df5 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1,884 +1,884 @@ -/atom - layer = TURF_LAYER - plane = GAME_PLANE - var/level = 2 - var/flags = NONE - var/flags_2 = NONE - var/list/fingerprints - var/list/fingerprintshidden - var/fingerprintslast = null - var/list/blood_DNA - var/blood_color - var/last_bumped = 0 - var/pass_flags = 0 - var/germ_level = GERM_LEVEL_AMBIENT // The higher the germ level, the more germ on the atom. - var/simulated = TRUE //filter for actions - used by lighting overlays - var/atom_say_verb = "says" - var/dont_save = 0 // For atoms that are temporary by necessity - like lighting overlays - - - ///Chemistry. - var/container_type = NONE - var/datum/reagents/reagents = null - - //This atom's HUD (med/sec, etc) images. Associative list. - var/list/image/hud_list = list() - //HUD images that this atom can provide. - var/list/hud_possible - - ///Chemistry. - - - //Value used to increment ex_act() if reactionary_explosions is on - var/explosion_block = 0 - - //Detective Work, used for the duplicate data points kept in the scanners - var/list/original_atom - - //Detective Work, used for allowing a given atom to leave its fibers on stuff. Allowed by default - var/can_leave_fibers = TRUE - - var/allow_spin = 1 //Set this to 1 for a _target_ that is being thrown at; if an atom has this set to 1 then atoms thrown AT it will not spin; currently used for the singularity. -Fox - - var/admin_spawned = 0 //was this spawned by an admin? used for stat tracking stuff. - - var/initialized = FALSE - - var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. - var/list/remove_overlays // a very temporary list of overlays to remove - var/list/add_overlays // a very temporary list of overlays to add - - var/list/atom_colours //used to store the different colors on an atom - //its inherent color, the colored paint applied on it, special color effect etc... - -/atom/New(loc, ...) - if(use_preloader && (src.type == _preloader.target_path))//in case the instanciated atom is creating other atoms in New() - _preloader.load(src) - . = ..() - attempt_init(arglist(args)) - -// This is distinct from /tg/ because of our space management system -// This is overriden in /atom/movable and the parent isn't called if the SMS wants to deal with it's init -/atom/proc/attempt_init(...) - var/do_initialize = SSatoms.initialized - if(do_initialize != INITIALIZATION_INSSATOMS) - args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD - if(SSatoms.InitAtom(src, args)) - // we were deleted - return - - -//Called after New if the map is being loaded. mapload = TRUE -//Called from base of New if the map is not being loaded. mapload = FALSE -//This base must be called or derivatives must set initialized to TRUE -//must not sleep -//Other parameters are passed from New (excluding loc), this does not happen if mapload is TRUE -//Must return an Initialize hint. Defined in __DEFINES/subsystems.dm - -//Note: the following functions don't call the base for optimization and must copypasta: -// /turf/Initialize -// /turf/open/space/Initialize - -/atom/proc/Initialize(mapload, ...) - if(initialized) - stack_trace("Warning: [src]([type]) initialized multiple times!") - initialized = TRUE - - if(color) - add_atom_colour(color, FIXED_COLOUR_PRIORITY) - - if(light_power && light_range) - update_light() - - if(opacity && isturf(loc)) - var/turf/T = loc - T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guranteed to be on afterwards anyways. - - if(loc) - loc.InitializedOn(src) // Used for poolcontroller / pool to improve performance greatly. However it also open up path to other usage of observer pattern on turfs. - - ComponentInitialize() - - return INITIALIZE_HINT_NORMAL - - -//called if Initialize returns INITIALIZE_HINT_LATELOAD -/atom/proc/LateInitialize() - return - -// Put your AddComponent() calls here -/atom/proc/ComponentInitialize() - return - -/atom/proc/InitializedOn(atom/A) // Proc for when something is initialized on a atom - Optional to call. Useful for observer pattern etc. - return - -/atom/proc/onCentcom() - var/turf/T = get_turf(src) - if(!T) - return 0 - - if(!is_admin_level(T.z))//if not, don't bother - return 0 - - //check for centcomm shuttles - for(var/centcom_shuttle in list("emergency", "pod1", "pod2", "pod3", "pod4", "ferry")) - var/obj/docking_port/mobile/M = SSshuttle.getShuttle(centcom_shuttle) - if(T in M.areaInstance) - return 1 - - //finally check for centcom itself - return istype(T.loc,/area/centcom) - -/atom/proc/onSyndieBase() - var/turf/T = get_turf(src) - if(!T) - return 0 - - if(!is_admin_level(T.z))//if not, don't bother - return 0 - - if(istype(T.loc, /area/shuttle/syndicate_elite) || istype(T.loc, /area/syndicate_mothership)) - return 1 - - return 0 - -/atom/Destroy() - if(alternate_appearances) - for(var/aakey in alternate_appearances) - var/datum/alternate_appearance/AA = alternate_appearances[aakey] - qdel(AA) - alternate_appearances = null - - QDEL_NULL(reagents) - invisibility = INVISIBILITY_MAXIMUM - LAZYCLEARLIST(overlays) - LAZYCLEARLIST(priority_overlays) - - QDEL_NULL(light) - - return ..() - -//Hook for running code when a dir change occurs -/atom/proc/setDir(newdir) - SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) - dir = newdir - -///Handle melee attack by a mech -/atom/proc/mech_melee_attack(obj/mecha/M) - return - -/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) - SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) - if(does_attack_animation) - user.changeNext_move(CLICK_CD_MELEE) - add_attack_logs(user, src, "Punched with hulk powers") - user.do_attack_animation(src, ATTACK_EFFECT_SMASH) - -/atom/proc/CheckParts(list/parts_list) - for(var/A in parts_list) - if(istype(A, /datum/reagent)) - if(!reagents) - reagents = new() - reagents.reagent_list.Add(A) - reagents.conditional_update() - else if(istype(A, /atom/movable)) - var/atom/movable/M = A - if(istype(M.loc, /mob/living)) - var/mob/living/L = M.loc - L.unEquip(M) - M.forceMove(src) - -/atom/proc/assume_air(datum/gas_mixture/giver) - qdel(giver) - return null - -/atom/proc/remove_air(amount) - return null - -/atom/proc/return_air() - if(loc) - return loc.return_air() - else - return null - -/atom/proc/check_eye(user as mob) - if(istype(user, /mob/living/silicon/ai)) // WHYYYY - return 1 - return - -/atom/proc/on_reagent_change() - return - -/atom/proc/Bumped(AM as mob|obj) - return - -/// Convenience proc to see if a container is open for chemistry handling -/atom/proc/is_open_container() - return is_refillable() && is_drainable() - -/// Is this atom injectable into other atoms -/atom/proc/is_injectable(mob/user, allowmobs = TRUE) - return reagents && (container_type & (INJECTABLE | REFILLABLE)) - -/// Can we draw from this atom with an injectable atom -/atom/proc/is_drawable(mob/user, allowmobs = TRUE) - return reagents && (container_type & (DRAWABLE | DRAINABLE)) - -/// Can this atoms reagents be refilled -/atom/proc/is_refillable() - return reagents && (container_type & REFILLABLE) - -/// Is this atom drainable of reagents -/atom/proc/is_drainable() - return reagents && (container_type & DRAINABLE) - -/atom/proc/CheckExit() - return 1 - -/atom/proc/HasProximity(atom/movable/AM as mob|obj) - return - -/atom/proc/emp_act(var/severity) - return - -/atom/proc/bullet_act(obj/item/projectile/P, def_zone) - SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) - . = P.on_hit(src, 0, def_zone) - -/atom/proc/in_contents_of(container)//can take class or object instance as argument - if(ispath(container)) - if(istype(src.loc, container)) - return 1 - else if(src in container) - return 1 - return - -/* - * atom/proc/search_contents_for(path,list/filter_path=null) - * Recursevly searches all atom contens (including contents contents and so on). - * - * ARGS: path - search atom contents for atoms of this type - * list/filter_path - if set, contents of atoms not of types in this list are excluded from search. - * - * RETURNS: list of found atoms - */ - -/atom/proc/search_contents_for(path,list/filter_path=null) - var/list/found = list() - for(var/atom/A in src) - if(istype(A, path)) - found += A - if(filter_path) - var/pass = 0 - for(var/type in filter_path) - pass |= istype(A, type) - if(!pass) - continue - if(A.contents.len) - found += A.search_contents_for(path,filter_path) - return found - - -//All atoms -/atom/proc/examine(mob/user, infix = "", suffix = "") - //This reformat names to get a/an properly working on item descriptions when they are bloody - var/f_name = "\a [src][infix]." - if(src.blood_DNA && !istype(src, /obj/effect/decal)) - if(gender == PLURAL) - f_name = "some " - else - f_name = "a " - if(blood_color != "#030303") - f_name += "blood-stained [name][infix]!" - else - f_name += "oil-stained [name][infix]." - . = list("[bicon(src)] That's [f_name] [suffix]") - if(desc) - . += desc - - if(reagents) - if(container_type & TRANSPARENT) - . += "It contains:" - if(reagents.reagent_list.len) - if(user.can_see_reagents()) //Show each individual reagent - for(var/I in reagents.reagent_list) - var/datum/reagent/R = I - . += "[R.volume] units of [R.name]" - else //Otherwise, just show the total volume - if(reagents && reagents.reagent_list.len) - . += "[reagents.total_volume] units of various reagents." - else - . += "Nothing." - else if(container_type & AMOUNT_VISIBLE) - if(reagents.total_volume) - . += "It has [reagents.total_volume] unit\s left." - else - . += "It's empty." - - SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) - -/atom/proc/relaymove() - return - -/atom/proc/ex_act() - return - -/atom/proc/blob_act(obj/structure/blob/B) - SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) - -/atom/proc/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) - if(reagents) - reagents.temperature_reagents(exposed_temperature) - -/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) - switch(tool_type) - if(TOOL_CROWBAR) - return crowbar_act(user, I) - if(TOOL_MULTITOOL) - return multitool_act(user, I) - if(TOOL_SCREWDRIVER) - return screwdriver_act(user, I) - if(TOOL_WRENCH) - return wrench_act(user, I) - if(TOOL_WIRECUTTER) - return wirecutter_act(user, I) - if(TOOL_WELDER) - return welder_act(user, I) - - -// Tool-specific behavior procs. To be overridden in subtypes. -/atom/proc/crowbar_act(mob/living/user, obj/item/I) - return - -/atom/proc/multitool_act(mob/living/user, obj/item/I) - return - -//Check if the multitool has an item in its data buffer -/atom/proc/multitool_check_buffer(user, silent = FALSE) - if(!silent) - to_chat(user, "[src] has no data buffer!") - return FALSE - -/atom/proc/screwdriver_act(mob/living/user, obj/item/I) - return - -/atom/proc/wrench_act(mob/living/user, obj/item/I) - return - -/atom/proc/wirecutter_act(mob/living/user, obj/item/I) - return - -/atom/proc/welder_act(mob/living/user, obj/item/I) - return - -/atom/proc/emag_act() - return - -/atom/proc/fart_act(mob/living/M) - return FALSE - -/atom/proc/rpd_act() - return - -/atom/proc/rpd_blocksusage() - // Atoms that return TRUE prevent RPDs placing any kind of pipes on their turf. - return FALSE - -/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). - addtimer(CALLBACK(src, .proc/hitby_react, AM), 2) - -/atom/proc/hitby_react(atom/movable/AM) - if(AM && isturf(AM.loc)) - step(AM, turn(AM.dir, 180)) - -/atom/proc/get_spooked() - return - -/atom/proc/add_hiddenprint(mob/living/M as mob) - if(isnull(M)) return - if(isnull(M.key)) return - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(!istype(H.dna, /datum/dna)) - return 0 - if(H.gloves) - if(fingerprintslast != H.ckey) - //Add the list if it does not exist. - if(!fingerprintshidden) - fingerprintshidden = list() - fingerprintshidden += text("\[[time_stamp()]\] (Wearing gloves). Real name: [], Key: []",H.real_name, H.key) - fingerprintslast = H.ckey - return 0 - if(!fingerprints) - if(fingerprintslast != H.ckey) - //Add the list if it does not exist. - if(!fingerprintshidden) - fingerprintshidden = list() - fingerprintshidden += text("\[[time_stamp()]\] Real name: [], Key: []",H.real_name, H.key) - fingerprintslast = H.ckey - return 1 - else - if(fingerprintslast != M.ckey) - //Add the list if it does not exist. - if(!fingerprintshidden) - fingerprintshidden = list() - fingerprintshidden += text("\[[time_stamp()]\] Real name: [], Key: []",M.real_name, M.key) - fingerprintslast = M.ckey - return - - -//Set ignoregloves to add prints irrespective of the mob having gloves on. -/atom/proc/add_fingerprint(mob/living/M as mob, ignoregloves = 0) - if(isnull(M)) return - if(isnull(M.key)) return - if(ishuman(M)) - //Add the list if it does not exist. - if(!fingerprintshidden) - fingerprintshidden = list() - - //Fibers~ - add_fibers(M) - - //He has no prints! - if(FINGERPRINTS in M.mutations) - if(fingerprintslast != M.key) - fingerprintshidden += "(Has no fingerprints) Real name: [M.real_name], Key: [M.key]" - fingerprintslast = M.key - return 0 //Now, lets get to the dirty work. - //First, make sure their DNA makes sense. - var/mob/living/carbon/human/H = M - if(!istype(H.dna, /datum/dna) || !H.dna.uni_identity || (length(H.dna.uni_identity) != 32)) - if(!istype(H.dna, /datum/dna)) - H.dna = new /datum/dna(null) - H.dna.real_name = H.real_name - H.check_dna() - - //Check if the gloves (if any) hide fingerprints - if(H.gloves) - var/obj/item/clothing/gloves/G = H.gloves - if(G.transfer_prints) - ignoregloves = 1 - - //Now, deal with gloves. - if(!ignoregloves) - if(H.gloves && H.gloves != src) - if(fingerprintslast != H.ckey) - fingerprintshidden += text("\[[]\](Wearing gloves). Real name: [], Key: []",time_stamp(), H.real_name, H.key) - fingerprintslast = H.ckey - H.gloves.add_fingerprint(M) - return 0 - - //More adminstuffz - if(fingerprintslast != H.ckey) - fingerprintshidden += text("\[[]\]Real name: [], Key: []",time_stamp(), H.real_name, H.key) - fingerprintslast = H.ckey - - //Make the list if it does not exist. - if(!fingerprints) - fingerprints = list() - - //Hash this shit. - var/full_print = H.get_full_print() - - // Add the fingerprints - fingerprints[full_print] = full_print - - return 1 - else - //Smudge up dem prints some - if(fingerprintslast != M.ckey) - fingerprintshidden += text("\[[]\]Real name: [], Key: []",time_stamp(), M.real_name, M.key) - fingerprintslast = M.ckey - - return - - -/atom/proc/transfer_fingerprints_to(var/atom/A) - - // Make sure everything are lists. - if(!islist(A.fingerprints)) - A.fingerprints = list() - if(!islist(A.fingerprintshidden)) - A.fingerprintshidden = list() - - if(!islist(fingerprints)) - fingerprints = list() - if(!islist(fingerprintshidden)) - fingerprintshidden = list() - - // Transfer - if(fingerprints) - A.fingerprints |= fingerprints.Copy() //detective - if(fingerprintshidden) - A.fingerprintshidden |= fingerprintshidden.Copy() //admin - A.fingerprintslast = fingerprintslast - -var/list/blood_splatter_icons = list() - -/atom/proc/blood_splatter_index() - return "\ref[initial(icon)]-[initial(icon_state)]" - -//returns the mob's dna info as a list, to be inserted in an object's blood_DNA list -/mob/living/proc/get_blood_dna_list() - if(get_blood_id() != "blood") - return - return list("ANIMAL DNA" = "Y-") - -/mob/living/carbon/get_blood_dna_list() - if(get_blood_id() != "blood") - return - var/list/blood_dna = list() - if(dna) - blood_dna[dna.unique_enzymes] = dna.blood_type - else - blood_dna["UNKNOWN DNA"] = "X*" - return blood_dna - -/mob/living/carbon/alien/get_blood_dna_list() - return list("UNKNOWN DNA" = "X*") - -//to add a mob's dna info into an object's blood_DNA list. -/atom/proc/transfer_mob_blood_dna(mob/living/L) - var/new_blood_dna = L.get_blood_dna_list() - if(!new_blood_dna) - return 0 - return transfer_blood_dna(new_blood_dna) - -/obj/effect/decal/cleanable/blood/splatter/transfer_mob_blood_dna(mob/living/L) - ..(L) - var/list/b_data = L.get_blood_data(L.get_blood_id()) - if(b_data) - basecolor = b_data["blood_color"] - else - basecolor = "#A10808" - update_icon() - -/obj/effect/decal/cleanable/blood/footprints/transfer_mob_blood_dna(mob/living/L) - ..(L) - var/list/b_data = L.get_blood_data(L.get_blood_id()) - if(b_data) - basecolor = b_data["blood_color"] - else - basecolor = "#A10808" - update_icon() - -//to add blood dna info to the object's blood_DNA list -/atom/proc/transfer_blood_dna(list/blood_dna) - if(!blood_DNA) - blood_DNA = list() - var/old_length = blood_DNA.len - blood_DNA |= blood_dna - if(blood_DNA.len > old_length) - return 1//some new blood DNA was added - - -//to add blood from a mob onto something, and transfer their dna info -/atom/proc/add_mob_blood(mob/living/M) - var/list/blood_dna = M.get_blood_dna_list() - if(!blood_dna) - return 0 - var/bloodcolor = "#A10808" - var/list/b_data = M.get_blood_data(M.get_blood_id()) - if(b_data) - bloodcolor = b_data["blood_color"] - - return add_blood(blood_dna, bloodcolor) - -//to add blood onto something, with blood dna info to include. -/atom/proc/add_blood(list/blood_dna, color) - return 0 - -/obj/add_blood(list/blood_dna, color) - return transfer_blood_dna(blood_dna) - -/obj/item/add_blood(list/blood_dna, color) - var/blood_count = !blood_DNA ? 0 : blood_DNA.len - if(!..()) - return 0 - if(!blood_count)//apply the blood-splatter overlay if it isn't already in there - add_blood_overlay(color) - return 1 //we applied blood to the item - -/obj/item/clothing/gloves/add_blood(list/blood_dna, color) - . = ..() - transfer_blood = rand(2, 4) - -/turf/add_blood(list/blood_dna, color) - var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src - if(!B) - B = new /obj/effect/decal/cleanable/blood/splatter(src) - B.transfer_blood_dna(blood_dna) //give blood info to the blood decal. - B.basecolor = color - return 1 //we bloodied the floor - -/mob/living/carbon/human/add_blood(list/blood_dna, color) - if(wear_suit) - wear_suit.add_blood(blood_dna, color) - wear_suit.blood_color = color - update_inv_wear_suit(1) - else if(w_uniform) - w_uniform.add_blood(blood_dna, color) - w_uniform.blood_color = color - update_inv_w_uniform(1) - if(head) - head.add_blood(blood_dna, color) - head.blood_color = color - update_inv_head(0,0) - if(glasses) - glasses.add_blood(blood_dna, color) - glasses.blood_color = color - update_inv_glasses(0) - if(gloves) - var/obj/item/clothing/gloves/G = gloves - G.add_blood(blood_dna, color) - G.blood_color = color - verbs += /mob/living/carbon/human/proc/bloody_doodle - else - hand_blood_color = color - bloody_hands = rand(2, 4) - transfer_blood_dna(blood_dna) - verbs += /mob/living/carbon/human/proc/bloody_doodle - - update_inv_gloves(1) //handles bloody hands overlays and updating - return 1 - -/obj/item/proc/add_blood_overlay(color) - if(initial(icon) && initial(icon_state)) - //try to find a pre-processed blood-splatter. otherwise, make a new one - var/index = blood_splatter_index() - var/icon/blood_splatter_icon = blood_splatter_icons[index] - if(!blood_splatter_icon) - blood_splatter_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object - blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) - blood_splatter_icon.Blend(icon('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant - blood_splatter_icon = fcopy_rsc(blood_splatter_icon) - blood_splatter_icons[index] = blood_splatter_icon - - blood_overlay = image(blood_splatter_icon) - blood_overlay.color = color - overlays += blood_overlay - -/atom/proc/clean_blood() - germ_level = 0 - if(islist(blood_DNA)) - blood_DNA = null - return TRUE - -/obj/effect/decal/cleanable/blood/clean_blood() - return // While this seems nonsensical, clean_blood isn't supposed to be used like this on a blood decal. - -/obj/item/clean_blood() - . = ..() - if(.) - if(blood_overlay) - overlays -= blood_overlay - -/obj/item/clothing/gloves/clean_blood() - . = ..() - if(.) - transfer_blood = 0 - - -/obj/item/clothing/shoes/clean_blood() - ..() - bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_NOT_BLOODY = 0) - blood_state = BLOOD_STATE_NOT_BLOODY - if(ismob(loc)) - var/mob/M = loc - M.update_inv_shoes() - - -/mob/living/carbon/human/clean_blood() - if(gloves) - if(gloves.clean_blood()) - clean_blood() - update_inv_gloves() - gloves.germ_level = 0 - else - ..() // Clear the Blood_DNA list - if(bloody_hands) - bloody_hands = 0 - update_inv_gloves() - update_icons() //apply the now updated overlays to the mob - - -/atom/proc/add_vomit_floor(toxvomit = 0, green = FALSE) - playsound(src, 'sound/effects/splat.ogg', 50, 1) - if(!isspaceturf(src)) - var/type = green ? /obj/effect/decal/cleanable/vomit/green : /obj/effect/decal/cleanable/vomit - var/vomit_reagent = green ? "green_vomit" : "vomit" - for(var/obj/effect/decal/cleanable/vomit/V in get_turf(src)) - if(V.type == type) - V.reagents.add_reagent(vomit_reagent, 5) - return - - var/obj/effect/decal/cleanable/vomit/this = new type(src) - - // Make toxins vomit look different - if(toxvomit) - this.icon_state = "vomittox_[pick(1,4)]" - -/atom/proc/get_global_map_pos() - if(!islist(global_map) || isemptylist(global_map)) return - var/cur_x = null - var/cur_y = null - var/list/y_arr = null - for(cur_x=1,cur_x<=global_map.len,cur_x++) - y_arr = global_map[cur_x] - cur_y = y_arr.Find(src.z) - if(cur_y) - break -// to_chat(world, "X = [cur_x]; Y = [cur_y]") - if(cur_x && cur_y) - return list("x"=cur_x,"y"=cur_y) - else - return 0 - -// Used to provide overlays when using this atom as a viewing focus -// (cameras, locker tint, etc.) -/atom/proc/get_remote_view_fullscreens(mob/user) - return - -//the sight changes to give to the mob whose perspective is set to that atom (e.g. A mob with nightvision loses its nightvision while looking through a normal camera) -/atom/proc/update_remote_sight(mob/living/user) - user.sync_lighting_plane_alpha() - return - -/atom/proc/checkpass(passflag) - return pass_flags&passflag - -/atom/proc/isinspace() - if(isspaceturf(get_turf(src))) - return TRUE - else - return FALSE - -/atom/proc/handle_fall() - return - -/atom/proc/singularity_act() - return - -/atom/proc/singularity_pull(obj/singularity/S, current_size) - SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) - -/** - * Respond to acid being used on our atom - * - * Default behaviour is to send COMSIG_ATOM_ACID_ACT and return - */ -/atom/proc/acid_act(acidpwr, acid_volume) - SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) - -/atom/proc/narsie_act() - return - -/atom/proc/ratvar_act() - return - -/atom/proc/handle_ricochet(obj/item/projectile/P) - return - -//This proc is called on the location of an atom when the atom is Destroy()'d -/atom/proc/handle_atom_del(atom/A) - return - -/atom/proc/atom_say(message) - if(!message) - return - audible_message("[src] [atom_say_verb], \"[message]\"") - -/atom/proc/speech_bubble(var/bubble_state = "",var/bubble_loc = src, var/list/bubble_recipients = list()) - return - -/atom/vv_edit_var(var_name, var_value) - if(!Debug2) - admin_spawned = TRUE - . = ..() - switch(var_name) - if("light_power", "light_range", "light_color") - update_light() - if("color") - add_atom_colour(color, ADMIN_COLOUR_PRIORITY) - - -/atom/vv_get_dropdown() - . = ..() - var/turf/curturf = get_turf(src) - if(curturf) - .["Jump to turf"] = "?_src_=holder;adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]" - .["Add reagent"] = "?_src_=vars;addreagent=[UID()]" - .["Trigger explosion"] = "?_src_=vars;explode=[UID()]" - .["Trigger EM pulse"] = "?_src_=vars;emp=[UID()]" - -/atom/proc/AllowDrop() - return FALSE - -/atom/proc/drop_location() - var/atom/L = loc - if(!L) - return null - return L.AllowDrop() ? L : get_turf(L) - -/atom/Entered(atom/movable/AM, atom/oldLoc) - SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) - -/atom/Exit(atom/movable/AM, atom/newLoc) - . = ..() - if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) - return FALSE - -/atom/Exited(atom/movable/AM, atom/newLoc) - SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) - -/* - Adds an instance of colour_type to the atom's atom_colours list -*/ -/atom/proc/add_atom_colour(coloration, colour_priority) - if(!atom_colours || !atom_colours.len) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - if(!coloration) - return - if(colour_priority > atom_colours.len) - return - atom_colours[colour_priority] = coloration - update_atom_colour() - - -/* - Removes an instance of colour_type from the atom's atom_colours list -*/ -/atom/proc/remove_atom_colour(colour_priority, coloration) - if(!atom_colours) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - if(colour_priority > atom_colours.len) - return - if(coloration && atom_colours[colour_priority] != coloration) - return //if we don't have the expected color (for a specific priority) to remove, do nothing - atom_colours[colour_priority] = null - update_atom_colour() - - -/* - Resets the atom's color to null, and then sets it to the highest priority - colour available -*/ -/atom/proc/update_atom_colour() - if(!atom_colours) - atom_colours = list() - atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. - color = null - for(var/C in atom_colours) - if(islist(C)) - var/list/L = C - if(L.len) - color = L - return - else if(C) - color = C - return \ No newline at end of file +/atom + layer = TURF_LAYER + plane = GAME_PLANE + var/level = 2 + var/flags = NONE + var/flags_2 = NONE + var/list/fingerprints + var/list/fingerprintshidden + var/fingerprintslast = null + var/list/blood_DNA + var/blood_color + var/last_bumped = 0 + var/pass_flags = 0 + var/germ_level = GERM_LEVEL_AMBIENT // The higher the germ level, the more germ on the atom. + var/simulated = TRUE //filter for actions - used by lighting overlays + var/atom_say_verb = "says" + var/dont_save = 0 // For atoms that are temporary by necessity - like lighting overlays + + + ///Chemistry. + var/container_type = NONE + var/datum/reagents/reagents = null + + //This atom's HUD (med/sec, etc) images. Associative list. + var/list/image/hud_list = list() + //HUD images that this atom can provide. + var/list/hud_possible + + ///Chemistry. + + + //Value used to increment ex_act() if reactionary_explosions is on + var/explosion_block = 0 + + //Detective Work, used for the duplicate data points kept in the scanners + var/list/original_atom + + //Detective Work, used for allowing a given atom to leave its fibers on stuff. Allowed by default + var/can_leave_fibers = TRUE + + var/allow_spin = 1 //Set this to 1 for a _target_ that is being thrown at; if an atom has this set to 1 then atoms thrown AT it will not spin; currently used for the singularity. -Fox + + var/admin_spawned = 0 //was this spawned by an admin? used for stat tracking stuff. + + var/initialized = FALSE + + var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. + var/list/remove_overlays // a very temporary list of overlays to remove + var/list/add_overlays // a very temporary list of overlays to add + + var/list/atom_colours //used to store the different colors on an atom + //its inherent color, the colored paint applied on it, special color effect etc... + +/atom/New(loc, ...) + if(use_preloader && (src.type == _preloader.target_path))//in case the instanciated atom is creating other atoms in New() + _preloader.load(src) + . = ..() + attempt_init(arglist(args)) + +// This is distinct from /tg/ because of our space management system +// This is overriden in /atom/movable and the parent isn't called if the SMS wants to deal with it's init +/atom/proc/attempt_init(...) + var/do_initialize = SSatoms.initialized + if(do_initialize != INITIALIZATION_INSSATOMS) + args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD + if(SSatoms.InitAtom(src, args)) + // we were deleted + return + + +//Called after New if the map is being loaded. mapload = TRUE +//Called from base of New if the map is not being loaded. mapload = FALSE +//This base must be called or derivatives must set initialized to TRUE +//must not sleep +//Other parameters are passed from New (excluding loc), this does not happen if mapload is TRUE +//Must return an Initialize hint. Defined in __DEFINES/subsystems.dm + +//Note: the following functions don't call the base for optimization and must copypasta: +// /turf/Initialize +// /turf/open/space/Initialize + +/atom/proc/Initialize(mapload, ...) + if(initialized) + stack_trace("Warning: [src]([type]) initialized multiple times!") + initialized = TRUE + + if(color) + add_atom_colour(color, FIXED_COLOUR_PRIORITY) + + if(light_power && light_range) + update_light() + + if(opacity && isturf(loc)) + var/turf/T = loc + T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guranteed to be on afterwards anyways. + + if(loc) + loc.InitializedOn(src) // Used for poolcontroller / pool to improve performance greatly. However it also open up path to other usage of observer pattern on turfs. + + ComponentInitialize() + + return INITIALIZE_HINT_NORMAL + + +//called if Initialize returns INITIALIZE_HINT_LATELOAD +/atom/proc/LateInitialize() + return + +// Put your AddComponent() calls here +/atom/proc/ComponentInitialize() + return + +/atom/proc/InitializedOn(atom/A) // Proc for when something is initialized on a atom - Optional to call. Useful for observer pattern etc. + return + +/atom/proc/onCentcom() + var/turf/T = get_turf(src) + if(!T) + return 0 + + if(!is_admin_level(T.z))//if not, don't bother + return 0 + + //check for centcomm shuttles + for(var/centcom_shuttle in list("emergency", "pod1", "pod2", "pod3", "pod4", "ferry")) + var/obj/docking_port/mobile/M = SSshuttle.getShuttle(centcom_shuttle) + if(T in M.areaInstance) + return 1 + + //finally check for centcom itself + return istype(T.loc,/area/centcom) + +/atom/proc/onSyndieBase() + var/turf/T = get_turf(src) + if(!T) + return 0 + + if(!is_admin_level(T.z))//if not, don't bother + return 0 + + if(istype(T.loc, /area/shuttle/syndicate_elite) || istype(T.loc, /area/syndicate_mothership)) + return 1 + + return 0 + +/atom/Destroy() + if(alternate_appearances) + for(var/aakey in alternate_appearances) + var/datum/alternate_appearance/AA = alternate_appearances[aakey] + qdel(AA) + alternate_appearances = null + + QDEL_NULL(reagents) + invisibility = INVISIBILITY_MAXIMUM + LAZYCLEARLIST(overlays) + LAZYCLEARLIST(priority_overlays) + + QDEL_NULL(light) + + return ..() + +//Hook for running code when a dir change occurs +/atom/proc/setDir(newdir) + SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) + dir = newdir + +///Handle melee attack by a mech +/atom/proc/mech_melee_attack(obj/mecha/M) + return + +/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) + if(does_attack_animation) + user.changeNext_move(CLICK_CD_MELEE) + add_attack_logs(user, src, "Punched with hulk powers") + user.do_attack_animation(src, ATTACK_EFFECT_SMASH) + +/atom/proc/CheckParts(list/parts_list) + for(var/A in parts_list) + if(istype(A, /datum/reagent)) + if(!reagents) + reagents = new() + reagents.reagent_list.Add(A) + reagents.conditional_update() + else if(istype(A, /atom/movable)) + var/atom/movable/M = A + if(istype(M.loc, /mob/living)) + var/mob/living/L = M.loc + L.unEquip(M) + M.forceMove(src) + +/atom/proc/assume_air(datum/gas_mixture/giver) + qdel(giver) + return null + +/atom/proc/remove_air(amount) + return null + +/atom/proc/return_air() + if(loc) + return loc.return_air() + else + return null + +/atom/proc/check_eye(user as mob) + if(istype(user, /mob/living/silicon/ai)) // WHYYYY + return 1 + return + +/atom/proc/on_reagent_change() + return + +/atom/proc/Bumped(AM as mob|obj) + return + +/// Convenience proc to see if a container is open for chemistry handling +/atom/proc/is_open_container() + return is_refillable() && is_drainable() + +/// Is this atom injectable into other atoms +/atom/proc/is_injectable(mob/user, allowmobs = TRUE) + return reagents && (container_type & (INJECTABLE | REFILLABLE)) + +/// Can we draw from this atom with an injectable atom +/atom/proc/is_drawable(mob/user, allowmobs = TRUE) + return reagents && (container_type & (DRAWABLE | DRAINABLE)) + +/// Can this atoms reagents be refilled +/atom/proc/is_refillable() + return reagents && (container_type & REFILLABLE) + +/// Is this atom drainable of reagents +/atom/proc/is_drainable() + return reagents && (container_type & DRAINABLE) + +/atom/proc/CheckExit() + return 1 + +/atom/proc/HasProximity(atom/movable/AM as mob|obj) + return + +/atom/proc/emp_act(var/severity) + return + +/atom/proc/bullet_act(obj/item/projectile/P, def_zone) + SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) + . = P.on_hit(src, 0, def_zone) + +/atom/proc/in_contents_of(container)//can take class or object instance as argument + if(ispath(container)) + if(istype(src.loc, container)) + return 1 + else if(src in container) + return 1 + return + +/* + * atom/proc/search_contents_for(path,list/filter_path=null) + * Recursevly searches all atom contens (including contents contents and so on). + * + * ARGS: path - search atom contents for atoms of this type + * list/filter_path - if set, contents of atoms not of types in this list are excluded from search. + * + * RETURNS: list of found atoms + */ + +/atom/proc/search_contents_for(path,list/filter_path=null) + var/list/found = list() + for(var/atom/A in src) + if(istype(A, path)) + found += A + if(filter_path) + var/pass = 0 + for(var/type in filter_path) + pass |= istype(A, type) + if(!pass) + continue + if(A.contents.len) + found += A.search_contents_for(path,filter_path) + return found + + +//All atoms +/atom/proc/examine(mob/user, infix = "", suffix = "") + //This reformat names to get a/an properly working on item descriptions when they are bloody + var/f_name = "\a [src][infix]." + if(src.blood_DNA && !istype(src, /obj/effect/decal)) + if(gender == PLURAL) + f_name = "some " + else + f_name = "a " + if(blood_color != "#030303") + f_name += "blood-stained [name][infix]!" + else + f_name += "oil-stained [name][infix]." + . = list("[bicon(src)] That's [f_name] [suffix]") + if(desc) + . += desc + + if(reagents) + if(container_type & TRANSPARENT) + . += "It contains:" + if(reagents.reagent_list.len) + if(user.can_see_reagents()) //Show each individual reagent + for(var/I in reagents.reagent_list) + var/datum/reagent/R = I + . += "[R.volume] units of [R.name]" + else //Otherwise, just show the total volume + if(reagents && reagents.reagent_list.len) + . += "[reagents.total_volume] units of various reagents." + else + . += "Nothing." + else if(container_type & AMOUNT_VISIBLE) + if(reagents.total_volume) + . += "It has [reagents.total_volume] unit\s left." + else + . += "It's empty." + + SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) + +/atom/proc/relaymove() + return + +/atom/proc/ex_act() + return + +/atom/proc/blob_act(obj/structure/blob/B) + SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B) + +/atom/proc/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume) + if(reagents) + reagents.temperature_reagents(exposed_temperature) + +/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) + switch(tool_type) + if(TOOL_CROWBAR) + return crowbar_act(user, I) + if(TOOL_MULTITOOL) + return multitool_act(user, I) + if(TOOL_SCREWDRIVER) + return screwdriver_act(user, I) + if(TOOL_WRENCH) + return wrench_act(user, I) + if(TOOL_WIRECUTTER) + return wirecutter_act(user, I) + if(TOOL_WELDER) + return welder_act(user, I) + + +// Tool-specific behavior procs. To be overridden in subtypes. +/atom/proc/crowbar_act(mob/living/user, obj/item/I) + return + +/atom/proc/multitool_act(mob/living/user, obj/item/I) + return + +//Check if the multitool has an item in its data buffer +/atom/proc/multitool_check_buffer(user, silent = FALSE) + if(!silent) + to_chat(user, "[src] has no data buffer!") + return FALSE + +/atom/proc/screwdriver_act(mob/living/user, obj/item/I) + return + +/atom/proc/wrench_act(mob/living/user, obj/item/I) + return + +/atom/proc/wirecutter_act(mob/living/user, obj/item/I) + return + +/atom/proc/welder_act(mob/living/user, obj/item/I) + return + +/atom/proc/emag_act() + return + +/atom/proc/fart_act(mob/living/M) + return FALSE + +/atom/proc/rpd_act() + return + +/atom/proc/rpd_blocksusage() + // Atoms that return TRUE prevent RPDs placing any kind of pipes on their turf. + return FALSE + +/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). + addtimer(CALLBACK(src, .proc/hitby_react, AM), 2) + +/atom/proc/hitby_react(atom/movable/AM) + if(AM && isturf(AM.loc)) + step(AM, turn(AM.dir, 180)) + +/atom/proc/get_spooked() + return + +/atom/proc/add_hiddenprint(mob/living/M as mob) + if(isnull(M)) return + if(isnull(M.key)) return + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(!istype(H.dna, /datum/dna)) + return 0 + if(H.gloves) + if(fingerprintslast != H.ckey) + //Add the list if it does not exist. + if(!fingerprintshidden) + fingerprintshidden = list() + fingerprintshidden += text("\[[time_stamp()]\] (Wearing gloves). Real name: [], Key: []",H.real_name, H.key) + fingerprintslast = H.ckey + return 0 + if(!fingerprints) + if(fingerprintslast != H.ckey) + //Add the list if it does not exist. + if(!fingerprintshidden) + fingerprintshidden = list() + fingerprintshidden += text("\[[time_stamp()]\] Real name: [], Key: []",H.real_name, H.key) + fingerprintslast = H.ckey + return 1 + else + if(fingerprintslast != M.ckey) + //Add the list if it does not exist. + if(!fingerprintshidden) + fingerprintshidden = list() + fingerprintshidden += text("\[[time_stamp()]\] Real name: [], Key: []",M.real_name, M.key) + fingerprintslast = M.ckey + return + + +//Set ignoregloves to add prints irrespective of the mob having gloves on. +/atom/proc/add_fingerprint(mob/living/M as mob, ignoregloves = 0) + if(isnull(M)) return + if(isnull(M.key)) return + if(ishuman(M)) + //Add the list if it does not exist. + if(!fingerprintshidden) + fingerprintshidden = list() + + //Fibers~ + add_fibers(M) + + //He has no prints! + if(FINGERPRINTS in M.mutations) + if(fingerprintslast != M.key) + fingerprintshidden += "(Has no fingerprints) Real name: [M.real_name], Key: [M.key]" + fingerprintslast = M.key + return 0 //Now, lets get to the dirty work. + //First, make sure their DNA makes sense. + var/mob/living/carbon/human/H = M + if(!istype(H.dna, /datum/dna) || !H.dna.uni_identity || (length(H.dna.uni_identity) != 32)) + if(!istype(H.dna, /datum/dna)) + H.dna = new /datum/dna(null) + H.dna.real_name = H.real_name + H.check_dna() + + //Check if the gloves (if any) hide fingerprints + if(H.gloves) + var/obj/item/clothing/gloves/G = H.gloves + if(G.transfer_prints) + ignoregloves = 1 + + //Now, deal with gloves. + if(!ignoregloves) + if(H.gloves && H.gloves != src) + if(fingerprintslast != H.ckey) + fingerprintshidden += text("\[[]\](Wearing gloves). Real name: [], Key: []",time_stamp(), H.real_name, H.key) + fingerprintslast = H.ckey + H.gloves.add_fingerprint(M) + return 0 + + //More adminstuffz + if(fingerprintslast != H.ckey) + fingerprintshidden += text("\[[]\]Real name: [], Key: []",time_stamp(), H.real_name, H.key) + fingerprintslast = H.ckey + + //Make the list if it does not exist. + if(!fingerprints) + fingerprints = list() + + //Hash this shit. + var/full_print = H.get_full_print() + + // Add the fingerprints + fingerprints[full_print] = full_print + + return 1 + else + //Smudge up dem prints some + if(fingerprintslast != M.ckey) + fingerprintshidden += text("\[[]\]Real name: [], Key: []",time_stamp(), M.real_name, M.key) + fingerprintslast = M.ckey + + return + + +/atom/proc/transfer_fingerprints_to(var/atom/A) + + // Make sure everything are lists. + if(!islist(A.fingerprints)) + A.fingerprints = list() + if(!islist(A.fingerprintshidden)) + A.fingerprintshidden = list() + + if(!islist(fingerprints)) + fingerprints = list() + if(!islist(fingerprintshidden)) + fingerprintshidden = list() + + // Transfer + if(fingerprints) + A.fingerprints |= fingerprints.Copy() //detective + if(fingerprintshidden) + A.fingerprintshidden |= fingerprintshidden.Copy() //admin + A.fingerprintslast = fingerprintslast + +var/list/blood_splatter_icons = list() + +/atom/proc/blood_splatter_index() + return "\ref[initial(icon)]-[initial(icon_state)]" + +//returns the mob's dna info as a list, to be inserted in an object's blood_DNA list +/mob/living/proc/get_blood_dna_list() + if(get_blood_id() != "blood") + return + return list("ANIMAL DNA" = "Y-") + +/mob/living/carbon/get_blood_dna_list() + if(get_blood_id() != "blood") + return + var/list/blood_dna = list() + if(dna) + blood_dna[dna.unique_enzymes] = dna.blood_type + else + blood_dna["UNKNOWN DNA"] = "X*" + return blood_dna + +/mob/living/carbon/alien/get_blood_dna_list() + return list("UNKNOWN DNA" = "X*") + +//to add a mob's dna info into an object's blood_DNA list. +/atom/proc/transfer_mob_blood_dna(mob/living/L) + var/new_blood_dna = L.get_blood_dna_list() + if(!new_blood_dna) + return 0 + return transfer_blood_dna(new_blood_dna) + +/obj/effect/decal/cleanable/blood/splatter/transfer_mob_blood_dna(mob/living/L) + ..(L) + var/list/b_data = L.get_blood_data(L.get_blood_id()) + if(b_data) + basecolor = b_data["blood_color"] + else + basecolor = "#A10808" + update_icon() + +/obj/effect/decal/cleanable/blood/footprints/transfer_mob_blood_dna(mob/living/L) + ..(L) + var/list/b_data = L.get_blood_data(L.get_blood_id()) + if(b_data) + basecolor = b_data["blood_color"] + else + basecolor = "#A10808" + update_icon() + +//to add blood dna info to the object's blood_DNA list +/atom/proc/transfer_blood_dna(list/blood_dna) + if(!blood_DNA) + blood_DNA = list() + var/old_length = blood_DNA.len + blood_DNA |= blood_dna + if(blood_DNA.len > old_length) + return 1//some new blood DNA was added + + +//to add blood from a mob onto something, and transfer their dna info +/atom/proc/add_mob_blood(mob/living/M) + var/list/blood_dna = M.get_blood_dna_list() + if(!blood_dna) + return 0 + var/bloodcolor = "#A10808" + var/list/b_data = M.get_blood_data(M.get_blood_id()) + if(b_data) + bloodcolor = b_data["blood_color"] + + return add_blood(blood_dna, bloodcolor) + +//to add blood onto something, with blood dna info to include. +/atom/proc/add_blood(list/blood_dna, color) + return 0 + +/obj/add_blood(list/blood_dna, color) + return transfer_blood_dna(blood_dna) + +/obj/item/add_blood(list/blood_dna, color) + var/blood_count = !blood_DNA ? 0 : blood_DNA.len + if(!..()) + return 0 + if(!blood_count)//apply the blood-splatter overlay if it isn't already in there + add_blood_overlay(color) + return 1 //we applied blood to the item + +/obj/item/clothing/gloves/add_blood(list/blood_dna, color) + . = ..() + transfer_blood = rand(2, 4) + +/turf/add_blood(list/blood_dna, color) + var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src + if(!B) + B = new /obj/effect/decal/cleanable/blood/splatter(src) + B.transfer_blood_dna(blood_dna) //give blood info to the blood decal. + B.basecolor = color + return 1 //we bloodied the floor + +/mob/living/carbon/human/add_blood(list/blood_dna, color) + if(wear_suit) + wear_suit.add_blood(blood_dna, color) + wear_suit.blood_color = color + update_inv_wear_suit(1) + else if(w_uniform) + w_uniform.add_blood(blood_dna, color) + w_uniform.blood_color = color + update_inv_w_uniform(1) + if(head) + head.add_blood(blood_dna, color) + head.blood_color = color + update_inv_head(0,0) + if(glasses) + glasses.add_blood(blood_dna, color) + glasses.blood_color = color + update_inv_glasses(0) + if(gloves) + var/obj/item/clothing/gloves/G = gloves + G.add_blood(blood_dna, color) + G.blood_color = color + verbs += /mob/living/carbon/human/proc/bloody_doodle + else + hand_blood_color = color + bloody_hands = rand(2, 4) + transfer_blood_dna(blood_dna) + verbs += /mob/living/carbon/human/proc/bloody_doodle + + update_inv_gloves(1) //handles bloody hands overlays and updating + return 1 + +/obj/item/proc/add_blood_overlay(color) + if(initial(icon) && initial(icon_state)) + //try to find a pre-processed blood-splatter. otherwise, make a new one + var/index = blood_splatter_index() + var/icon/blood_splatter_icon = blood_splatter_icons[index] + if(!blood_splatter_icon) + blood_splatter_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object + blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent) + blood_splatter_icon.Blend(icon('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant + blood_splatter_icon = fcopy_rsc(blood_splatter_icon) + blood_splatter_icons[index] = blood_splatter_icon + + blood_overlay = image(blood_splatter_icon) + blood_overlay.color = color + overlays += blood_overlay + +/atom/proc/clean_blood() + germ_level = 0 + if(islist(blood_DNA)) + blood_DNA = null + return TRUE + +/obj/effect/decal/cleanable/blood/clean_blood() + return // While this seems nonsensical, clean_blood isn't supposed to be used like this on a blood decal. + +/obj/item/clean_blood() + . = ..() + if(.) + if(blood_overlay) + overlays -= blood_overlay + +/obj/item/clothing/gloves/clean_blood() + . = ..() + if(.) + transfer_blood = 0 + + +/obj/item/clothing/shoes/clean_blood() + ..() + bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_NOT_BLOODY = 0) + blood_state = BLOOD_STATE_NOT_BLOODY + if(ismob(loc)) + var/mob/M = loc + M.update_inv_shoes() + + +/mob/living/carbon/human/clean_blood() + if(gloves) + if(gloves.clean_blood()) + clean_blood() + update_inv_gloves() + gloves.germ_level = 0 + else + ..() // Clear the Blood_DNA list + if(bloody_hands) + bloody_hands = 0 + update_inv_gloves() + update_icons() //apply the now updated overlays to the mob + + +/atom/proc/add_vomit_floor(toxvomit = 0, green = FALSE) + playsound(src, 'sound/effects/splat.ogg', 50, 1) + if(!isspaceturf(src)) + var/type = green ? /obj/effect/decal/cleanable/vomit/green : /obj/effect/decal/cleanable/vomit + var/vomit_reagent = green ? "green_vomit" : "vomit" + for(var/obj/effect/decal/cleanable/vomit/V in get_turf(src)) + if(V.type == type) + V.reagents.add_reagent(vomit_reagent, 5) + return + + var/obj/effect/decal/cleanable/vomit/this = new type(src) + + // Make toxins vomit look different + if(toxvomit) + this.icon_state = "vomittox_[pick(1,4)]" + +/atom/proc/get_global_map_pos() + if(!islist(global_map) || isemptylist(global_map)) return + var/cur_x = null + var/cur_y = null + var/list/y_arr = null + for(cur_x=1,cur_x<=global_map.len,cur_x++) + y_arr = global_map[cur_x] + cur_y = y_arr.Find(src.z) + if(cur_y) + break +// to_chat(world, "X = [cur_x]; Y = [cur_y]") + if(cur_x && cur_y) + return list("x"=cur_x,"y"=cur_y) + else + return 0 + +// Used to provide overlays when using this atom as a viewing focus +// (cameras, locker tint, etc.) +/atom/proc/get_remote_view_fullscreens(mob/user) + return + +//the sight changes to give to the mob whose perspective is set to that atom (e.g. A mob with nightvision loses its nightvision while looking through a normal camera) +/atom/proc/update_remote_sight(mob/living/user) + user.sync_lighting_plane_alpha() + return + +/atom/proc/checkpass(passflag) + return pass_flags&passflag + +/atom/proc/isinspace() + if(isspaceturf(get_turf(src))) + return TRUE + else + return FALSE + +/atom/proc/handle_fall() + return + +/atom/proc/singularity_act() + return + +/atom/proc/singularity_pull(obj/singularity/S, current_size) + SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) + +/** + * Respond to acid being used on our atom + * + * Default behaviour is to send COMSIG_ATOM_ACID_ACT and return + */ +/atom/proc/acid_act(acidpwr, acid_volume) + SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) + +/atom/proc/narsie_act() + return + +/atom/proc/ratvar_act() + return + +/atom/proc/handle_ricochet(obj/item/projectile/P) + return + +//This proc is called on the location of an atom when the atom is Destroy()'d +/atom/proc/handle_atom_del(atom/A) + return + +/atom/proc/atom_say(message) + if(!message) + return + audible_message("[src] [atom_say_verb], \"[message]\"") + +/atom/proc/speech_bubble(var/bubble_state = "",var/bubble_loc = src, var/list/bubble_recipients = list()) + return + +/atom/vv_edit_var(var_name, var_value) + if(!Debug2) + admin_spawned = TRUE + . = ..() + switch(var_name) + if("light_power", "light_range", "light_color") + update_light() + if("color") + add_atom_colour(color, ADMIN_COLOUR_PRIORITY) + + +/atom/vv_get_dropdown() + . = ..() + var/turf/curturf = get_turf(src) + if(curturf) + .["Jump to turf"] = "?_src_=holder;adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]" + .["Add reagent"] = "?_src_=vars;addreagent=[UID()]" + .["Trigger explosion"] = "?_src_=vars;explode=[UID()]" + .["Trigger EM pulse"] = "?_src_=vars;emp=[UID()]" + +/atom/proc/AllowDrop() + return FALSE + +/atom/proc/drop_location() + var/atom/L = loc + if(!L) + return null + return L.AllowDrop() ? L : get_turf(L) + +/atom/Entered(atom/movable/AM, atom/oldLoc) + SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) + +/atom/Exit(atom/movable/AM, atom/newLoc) + . = ..() + if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) + return FALSE + +/atom/Exited(atom/movable/AM, atom/newLoc) + SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) + +/* + Adds an instance of colour_type to the atom's atom_colours list +*/ +/atom/proc/add_atom_colour(coloration, colour_priority) + if(!atom_colours || !atom_colours.len) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(!coloration) + return + if(colour_priority > atom_colours.len) + return + atom_colours[colour_priority] = coloration + update_atom_colour() + + +/* + Removes an instance of colour_type from the atom's atom_colours list +*/ +/atom/proc/remove_atom_colour(colour_priority, coloration) + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + if(colour_priority > atom_colours.len) + return + if(coloration && atom_colours[colour_priority] != coloration) + return //if we don't have the expected color (for a specific priority) to remove, do nothing + atom_colours[colour_priority] = null + update_atom_colour() + + +/* + Resets the atom's color to null, and then sets it to the highest priority + colour available +*/ +/atom/proc/update_atom_colour() + if(!atom_colours) + atom_colours = list() + atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently. + color = null + for(var/C in atom_colours) + if(islist(C)) + var/list/L = C + if(L.len) + color = L + return + else if(C) + color = C + return diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index e7d64207841..0370fad0367 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1,565 +1,565 @@ -/atom/movable - layer = 3 - appearance_flags = TILE_BOUND - var/last_move = null - var/anchored = 0 - var/move_resist = MOVE_RESIST_DEFAULT - var/move_force = MOVE_FORCE_DEFAULT - var/pull_force = PULL_FORCE_DEFAULT - // var/elevation = 2 - not used anywhere - var/move_speed = 10 - var/l_move_time = 1 - var/datum/thrownthing/throwing = null - var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported - var/throw_range = 7 - var/no_spin = 0 - var/no_spin_thrown = 0 - var/moved_recently = 0 - var/mob/pulledby = null - var/atom/movable/pulling - var/throwforce = 0 - var/canmove = 1 - - var/inertia_dir = 0 - var/atom/inertia_last_loc - var/inertia_moving = 0 - var/inertia_next_move = 0 - var/inertia_move_delay = 5 - - var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move - var/list/client_mobs_in_contents - var/area/areaMaster - -/atom/movable/New() - . = ..() - areaMaster = get_area(src) - -/atom/movable/attempt_init(loc, ...) - var/turf/T = get_turf(src) - if(T && SSatoms.initialized != INITIALIZATION_INSSATOMS && space_manager.is_zlevel_dirty(T.z)) - space_manager.postpone_init(T.z, src) - return - . = ..() - -/atom/movable/Destroy() - unbuckle_all_mobs(force = TRUE) - if(loc) - loc.handle_atom_del(src) - for(var/atom/movable/AM in contents) - qdel(AM) - loc = null - if(pulledby) - if(pulledby.pulling == src) - pulledby.pulling = null - pulledby = null - return ..() - -//Returns an atom's power cell, if it has one. Overload for individual items. -/atom/movable/proc/get_cell() - return - -/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) - if(QDELETED(AM)) - return FALSE - if(!(AM.can_be_pulled(src, state, force))) - return FALSE - - // if we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - if(state == 0) - stop_pulling() - return FALSE - // Are we trying to pull something we are already pulling? Then enter grab cycle and end. - if(AM == pulling) - if(isliving(AM)) - var/mob/living/AMob = AM - AMob.grabbedby(src) - return TRUE - stop_pulling() - if(AM.pulledby) - add_attack_logs(AM, AM.pulledby, "pulled from", ATKLOG_ALMOSTALL) - AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. - pulling = AM - AM.pulledby = src - if(ismob(AM)) - var/mob/M = AM - add_attack_logs(src, M, "passively grabbed", ATKLOG_ALMOSTALL) - if(!supress_message) - visible_message("[src] has grabbed [M] passively!") - return TRUE - -/atom/movable/proc/stop_pulling() - if(pulling) - pulling.pulledby = null - var/mob/living/ex_pulled = pulling - pulling = null - pulledby = null - if(isliving(ex_pulled)) - var/mob/living/L = ex_pulled - L.update_canmove()// mob gets up if it was lyng down in a chokehold - -/atom/movable/proc/check_pulling() - if(pulling) - var/atom/movable/pullee = pulling - if(pullee && get_dist(src, pullee) > 1) - stop_pulling() - return - if(!isturf(loc)) - stop_pulling() - return - if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). - log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") - stop_pulling() - return - if(pulling.anchored || pulling.move_resist > move_force) - stop_pulling() - return - if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. - pulledby.stop_pulling() - -/atom/movable/proc/can_be_pulled(user, grab_state, force) - if(src == user || !isturf(loc)) - return FALSE - if(anchored || throwing) - return FALSE - if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) - return FALSE - return TRUE - -// Used in shuttle movement and AI eye stuff. -// Primarily used to notify objects being moved by a shuttle/bluespace fuckup. -/atom/movable/proc/setLoc(var/T, var/teleported=0) - loc = T - -/atom/movable/Move(atom/newloc, direct = 0) - if(!loc || !newloc) return 0 - var/atom/oldloc = loc - - if(loc != newloc) - if(!(direct & (direct - 1))) //Cardinal move - . = ..() - else //Diagonal move, split it into cardinal moves - moving_diagonally = FIRST_DIAG_STEP - var/first_step_dir - // The `&& moving_diagonally` checks are so that a forceMove taking - // place due to a Crossed, Bumped, etc. call will interrupt - // the second half of the diagonal movement, or the second attempt - // at a first half if step() fails because we hit something. - if(direct & NORTH) - if(direct & EAST) - if(step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if(moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if(direct & WEST) - if(step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if(moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if(direct & SOUTH) - if(direct & EAST) - if(step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if(moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - else if(direct & WEST) - if(step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if(moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - if(moving_diagonally == SECOND_DIAG_STEP) - if(!.) - setDir(first_step_dir) - else if(!inertia_moving) - inertia_next_move = world.time + inertia_move_delay - newtonian_move(direct) - moving_diagonally = 0 - return - - if(!loc || (loc == oldloc && oldloc != newloc)) - last_move = 0 - return - - if(.) - Moved(oldloc, direct) - - last_move = direct - src.move_speed = world.time - src.l_move_time - src.l_move_time = world.time - - if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct)) //movement failed due to buckled mob - . = 0 - -// Called after a successful Move(). By this point, we've already moved -/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) - SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced) - if(!inertia_moving) - inertia_next_move = world.time + inertia_move_delay - newtonian_move(Dir) - if(length(client_mobs_in_contents)) - update_parallax_contents() - return TRUE - -// Previously known as HasEntered() -// This is automatically called when something enters your square -/atom/movable/Crossed(atom/movable/AM, oldloc) - SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM) - SEND_SIGNAL(AM, COMSIG_CROSSED_MOVABLE, src) - -/atom/movable/Bump(atom/A, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump(). - if(A && yes) - SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A) - if(throwing) - throwing.hit_atom(A) - . = 1 - if(!A || QDELETED(A)) - return - A.Bumped(src) - -/atom/movable/proc/forceMove(atom/destination) - var/turf/old_loc = loc - loc = destination - moving_diagonally = 0 - - if(old_loc) - old_loc.Exited(src, destination) - for(var/atom/movable/AM in old_loc) - AM.Uncrossed(src) - - if(destination) - destination.Entered(src) - for(var/atom/movable/AM in destination) - if(AM == src) - continue - AM.Crossed(src, old_loc) - var/turf/oldturf = get_turf(old_loc) - var/turf/destturf = get_turf(destination) - var/old_z = (oldturf ? oldturf.z : null) - var/dest_z = (destturf ? destturf.z : null) - if(old_z != dest_z) - onTransitZ(old_z, dest_z) - if(isturf(destination) && opacity) - var/turf/new_loc = destination - new_loc.reconsider_lights() - - if(isturf(old_loc) && opacity) - old_loc.reconsider_lights() - - for(var/datum/light_source/L in light_sources) - L.source_atom.update_light() - - return 1 - -/atom/movable/proc/onTransitZ(old_z,new_z) - for(var/item in src) // Notify contents of Z-transition. This can be overridden if we know the items contents do not care. - var/atom/movable/AM = item - AM.onTransitZ(old_z,new_z) - -/mob/living/forceMove(atom/destination) - if(buckled) - addtimer(CALLBACK(src, .proc/check_buckled), 1, TIMER_UNIQUE) - if(has_buckled_mobs()) - for(var/m in buckled_mobs) - var/mob/living/buckled_mob = m - addtimer(CALLBACK(buckled_mob, .proc/check_buckled), 1, TIMER_UNIQUE) - if(pulling) - addtimer(CALLBACK(src, .proc/check_pull), 1, TIMER_UNIQUE) - . = ..() - if(client) - reset_perspective(destination) - update_canmove() //if the mob was asleep inside a container and then got forceMoved out we need to make them fall. - - -//Called whenever an object moves and by mobs when they attempt to move themselves through space -//And when an object or action applies a force on src, see newtonian_move() below -//Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting -//Mobs should return 1 if they should be able to move of their own volition, see client/Move() in mob_movement.dm -//movement_dir == 0 when stopping or any dir when trying to move -/atom/movable/proc/Process_Spacemove(var/movement_dir = 0) - if(has_gravity(src)) - return 1 - - if(pulledby && !pulledby.pulling) - return 1 - - if(throwing) - return 1 - - if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier - return 1 - - return 0 - -/atom/movable/proc/newtonian_move(direction) //Only moves the object if it's under no gravity - if(!loc || Process_Spacemove(0)) - inertia_dir = 0 - return 0 - - inertia_dir = direction - if(!direction) - return 1 - - inertia_last_loc = loc - SSspacedrift.processing[src] = src - return 1 - - -//called when src is thrown into hit_atom -/atom/movable/proc/throw_impact(atom/hit_atom, throwingdatum) - set waitfor = 0 - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - if(!QDELETED(hit_atom)) - return hit_atom.hitby(src) - -/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) - if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) - step(src, AM.dir) - ..() - -/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY) - if(!target || (flags & NODROP) || speed <= 0) - return 0 - - if(pulledby) - pulledby.stop_pulling() - - // They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw? - if(thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag * 2) - var/user_momentum = thrower.movement_delay() - if(!user_momentum) // no movement_delay, this means they move once per byond tick, let's calculate from that instead - user_momentum = world.tick_lag - - user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses - - if(get_dir(thrower, target) & last_move) - user_momentum = user_momentum // basically a noop, but needed - else if(get_dir(target, thrower) & last_move) - user_momentum = -user_momentum // we are moving away from the target, lets slowdown the throw accordingly - else - user_momentum = 0 - - if(user_momentum) - // first lets add that momentum to range - range *= (user_momentum / speed) + 1 - //then lets add it to speed - speed += user_momentum - if(speed <= 0) - return //no throw speed, the user was moving too fast. - - var/datum/thrownthing/TT = new() - TT.thrownthing = src - TT.target = target - TT.target_turf = get_turf(target) - TT.init_dir = get_dir(src, target) - TT.maxrange = range - TT.speed = speed - TT.thrower = thrower - TT.diagonals_first = diagonals_first - TT.callback = callback - - var/dist_x = abs(target.x - src.x) - var/dist_y = abs(target.y - src.y) - var/dx = (target.x > src.x) ? EAST : WEST - var/dy = (target.y > src.y) ? NORTH : SOUTH - - if(dist_x == dist_y) - TT.pure_diagonal = 1 - - else if(dist_x <= dist_y) - var/olddist_x = dist_x - var/olddx = dx - dist_x = dist_y - dist_y = olddist_x - dx = dy - dy = olddx - TT.dist_x = dist_x - TT.dist_y = dist_y - TT.dx = dx - TT.dy = dy - TT.diagonal_error = dist_x / 2 - dist_y - TT.start_time = world.time - - if(pulledby) - pulledby.stop_pulling() - - throwing = TT - if(spin && !no_spin && !no_spin_thrown) - SpinAnimation(5, 1) - - SSthrowing.processing[src] = TT - TT.tick() - - return TRUE - -//Overlays -/atom/movable/overlay - var/atom/master = null - anchored = TRUE - simulated = FALSE - -/atom/movable/overlay/New() - verbs.Cut() - return - -/atom/movable/overlay/attackby(a, b, c) - if(master) - return master.attackby(a, b, c) - -/atom/movable/overlay/attack_hand(a, b, c) - if(master) - return master.attack_hand(a, b, c) - -/atom/movable/proc/water_act(volume, temperature, source, method = REAGENT_TOUCH) //amount of water acting : temperature of water in kelvin : object that called it (for shennagins) - return TRUE - -/atom/movable/proc/handle_buckled_mob_movement(newloc,direct) - for(var/m in buckled_mobs) - var/mob/living/buckled_mob = m - if(!buckled_mob.Move(newloc, direct)) - forceMove(buckled_mob.loc) - last_move = buckled_mob.last_move - inertia_dir = last_move - buckled_mob.inertia_dir = last_move - return 0 - return 1 - -/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) - return FALSE - -/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE) - . = AM.force_pushed(src, force, direction) - if(!silent && .) - visible_message("[src] forcefully pushes against [AM]!", "You forcefully push against [AM]!") - -/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE) - . = AM.move_crushed(src, force, direction) - if(!silent && .) - visible_message("[src] crushes past [AM]!", "You crush [AM]!") - -/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) - return FALSE - -/atom/movable/CanPass(atom/movable/mover, turf/target, height=1.5) - if(mover in buckled_mobs) - return 1 - return ..() - -/atom/movable/proc/get_spacemove_backup() - var/atom/movable/dense_object_backup - for(var/A in orange(1, get_turf(src))) - if(isarea(A)) - continue - else if(isturf(A)) - var/turf/turf = A - if(!turf.density) - continue - return turf - else - var/atom/movable/AM = A - if(!AM.CanPass(src) || AM.density) - if(AM.anchored) - return AM - dense_object_backup = AM - break - . = dense_object_backup - -/atom/movable/proc/transfer_prints_to(atom/movable/target = null, overwrite = FALSE) - if(!target) - return - if(overwrite) - target.fingerprints = fingerprints - target.fingerprintshidden = fingerprintshidden - else - target.fingerprints += fingerprints - target.fingerprintshidden += fingerprintshidden - target.fingerprintslast = fingerprintslast - -/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y) - if(!no_effect && (visual_effect_icon || used_item)) - do_item_attack_animation(A, visual_effect_icon, used_item) - - if(A == src) - return //don't do an animation if attacking self - var/pixel_x_diff = 0 - var/pixel_y_diff = 0 - var/final_pixel_y = initial(pixel_y) - if(end_pixel_y) - final_pixel_y = end_pixel_y - - var/direction = get_dir(src, A) - if(direction & NORTH) - pixel_y_diff = 8 - else if(direction & SOUTH) - pixel_y_diff = -8 - - if(direction & EAST) - pixel_x_diff = 8 - else if(direction & WEST) - pixel_x_diff = -8 - - animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) - animate(pixel_x = initial(pixel_x), pixel_y = final_pixel_y, time = 2) - -/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) - var/image/I - if(visual_effect_icon) - I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) - else if(used_item) - I = image(used_item.icon, A, used_item.icon_state, A.layer + 0.1) - - // Scale the icon. - I.transform *= 0.75 - // The icon should not rotate. - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - - // Set the direction of the icon animation. - var/direction = get_dir(src, A) - if(direction & NORTH) - I.pixel_y = -16 - else if(direction & SOUTH) - I.pixel_y = 16 - - if(direction & EAST) - I.pixel_x = -16 - else if(direction & WEST) - I.pixel_x = 16 - - if(!direction) // Attacked self?! - I.pixel_z = 16 - - if(!I) - return - - // Who can see the attack? - var/list/viewing = list() - for(var/mob/M in viewers(A)) - if(M.client && M.client.prefs.show_ghostitem_attack) - viewing |= M.client - - flick_overlay(I, viewing, 5) // 5 ticks/half a second - - // And animate the attack! - var/t_color = "#ffffff" - if(ismob(src) && ismob(A) && (!used_item)) - var/mob/M = src - t_color = M.a_intent == INTENT_HARM ? "#ff0000" : "#ffffff" - animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3, color = t_color) - -/atom/movable/proc/portal_destroyed(obj/effect/portal/P) - return +/atom/movable + layer = 3 + appearance_flags = TILE_BOUND + var/last_move = null + var/anchored = 0 + var/move_resist = MOVE_RESIST_DEFAULT + var/move_force = MOVE_FORCE_DEFAULT + var/pull_force = PULL_FORCE_DEFAULT + // var/elevation = 2 - not used anywhere + var/move_speed = 10 + var/l_move_time = 1 + var/datum/thrownthing/throwing = null + var/throw_speed = 2 //How many tiles to move per ds when being thrown. Float values are fully supported + var/throw_range = 7 + var/no_spin = 0 + var/no_spin_thrown = 0 + var/moved_recently = 0 + var/mob/pulledby = null + var/atom/movable/pulling + var/throwforce = 0 + var/canmove = 1 + + var/inertia_dir = 0 + var/atom/inertia_last_loc + var/inertia_moving = 0 + var/inertia_next_move = 0 + var/inertia_move_delay = 5 + + var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move + var/list/client_mobs_in_contents + var/area/areaMaster + +/atom/movable/New() + . = ..() + areaMaster = get_area(src) + +/atom/movable/attempt_init(loc, ...) + var/turf/T = get_turf(src) + if(T && SSatoms.initialized != INITIALIZATION_INSSATOMS && space_manager.is_zlevel_dirty(T.z)) + space_manager.postpone_init(T.z, src) + return + . = ..() + +/atom/movable/Destroy() + unbuckle_all_mobs(force = TRUE) + if(loc) + loc.handle_atom_del(src) + for(var/atom/movable/AM in contents) + qdel(AM) + loc = null + if(pulledby) + if(pulledby.pulling == src) + pulledby.pulling = null + pulledby = null + return ..() + +//Returns an atom's power cell, if it has one. Overload for individual items. +/atom/movable/proc/get_cell() + return + +/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) + if(QDELETED(AM)) + return FALSE + if(!(AM.can_be_pulled(src, state, force))) + return FALSE + + // if we're pulling something then drop what we're currently pulling and pull this instead. + if(pulling) + if(state == 0) + stop_pulling() + return FALSE + // Are we trying to pull something we are already pulling? Then enter grab cycle and end. + if(AM == pulling) + if(isliving(AM)) + var/mob/living/AMob = AM + AMob.grabbedby(src) + return TRUE + stop_pulling() + if(AM.pulledby) + add_attack_logs(AM, AM.pulledby, "pulled from", ATKLOG_ALMOSTALL) + AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. + pulling = AM + AM.pulledby = src + if(ismob(AM)) + var/mob/M = AM + add_attack_logs(src, M, "passively grabbed", ATKLOG_ALMOSTALL) + if(!supress_message) + visible_message("[src] has grabbed [M] passively!") + return TRUE + +/atom/movable/proc/stop_pulling() + if(pulling) + pulling.pulledby = null + var/mob/living/ex_pulled = pulling + pulling = null + pulledby = null + if(isliving(ex_pulled)) + var/mob/living/L = ex_pulled + L.update_canmove()// mob gets up if it was lyng down in a chokehold + +/atom/movable/proc/check_pulling() + if(pulling) + var/atom/movable/pullee = pulling + if(pullee && get_dist(src, pullee) > 1) + stop_pulling() + return + if(!isturf(loc)) + stop_pulling() + return + if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). + log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.") + stop_pulling() + return + if(pulling.anchored || pulling.move_resist > move_force) + stop_pulling() + return + if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move. + pulledby.stop_pulling() + +/atom/movable/proc/can_be_pulled(user, grab_state, force) + if(src == user || !isturf(loc)) + return FALSE + if(anchored || throwing) + return FALSE + if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) + return FALSE + return TRUE + +// Used in shuttle movement and AI eye stuff. +// Primarily used to notify objects being moved by a shuttle/bluespace fuckup. +/atom/movable/proc/setLoc(var/T, var/teleported=0) + loc = T + +/atom/movable/Move(atom/newloc, direct = 0) + if(!loc || !newloc) return 0 + var/atom/oldloc = loc + + if(loc != newloc) + if(!(direct & (direct - 1))) //Cardinal move + . = ..() + else //Diagonal move, split it into cardinal moves + moving_diagonally = FIRST_DIAG_STEP + var/first_step_dir + // The `&& moving_diagonally` checks are so that a forceMove taking + // place due to a Crossed, Bumped, etc. call will interrupt + // the second half of the diagonal movement, or the second attempt + // at a first half if step() fails because we hit something. + if(direct & NORTH) + if(direct & EAST) + if(step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if(moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if(direct & WEST) + if(step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if(moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if(direct & SOUTH) + if(direct & EAST) + if(step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if(moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + else if(direct & WEST) + if(step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if(moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + if(moving_diagonally == SECOND_DIAG_STEP) + if(!.) + setDir(first_step_dir) + else if(!inertia_moving) + inertia_next_move = world.time + inertia_move_delay + newtonian_move(direct) + moving_diagonally = 0 + return + + if(!loc || (loc == oldloc && oldloc != newloc)) + last_move = 0 + return + + if(.) + Moved(oldloc, direct) + + last_move = direct + src.move_speed = world.time - src.l_move_time + src.l_move_time = world.time + + if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct)) //movement failed due to buckled mob + . = 0 + +// Called after a successful Move(). By this point, we've already moved +/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE) + SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced) + if(!inertia_moving) + inertia_next_move = world.time + inertia_move_delay + newtonian_move(Dir) + if(length(client_mobs_in_contents)) + update_parallax_contents() + return TRUE + +// Previously known as HasEntered() +// This is automatically called when something enters your square +/atom/movable/Crossed(atom/movable/AM, oldloc) + SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM) + SEND_SIGNAL(AM, COMSIG_CROSSED_MOVABLE, src) + +/atom/movable/Bump(atom/A, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump(). + if(A && yes) + SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A) + if(throwing) + throwing.hit_atom(A) + . = 1 + if(!A || QDELETED(A)) + return + A.Bumped(src) + +/atom/movable/proc/forceMove(atom/destination) + var/turf/old_loc = loc + loc = destination + moving_diagonally = 0 + + if(old_loc) + old_loc.Exited(src, destination) + for(var/atom/movable/AM in old_loc) + AM.Uncrossed(src) + + if(destination) + destination.Entered(src) + for(var/atom/movable/AM in destination) + if(AM == src) + continue + AM.Crossed(src, old_loc) + var/turf/oldturf = get_turf(old_loc) + var/turf/destturf = get_turf(destination) + var/old_z = (oldturf ? oldturf.z : null) + var/dest_z = (destturf ? destturf.z : null) + if(old_z != dest_z) + onTransitZ(old_z, dest_z) + if(isturf(destination) && opacity) + var/turf/new_loc = destination + new_loc.reconsider_lights() + + if(isturf(old_loc) && opacity) + old_loc.reconsider_lights() + + for(var/datum/light_source/L in light_sources) + L.source_atom.update_light() + + return 1 + +/atom/movable/proc/onTransitZ(old_z,new_z) + for(var/item in src) // Notify contents of Z-transition. This can be overridden if we know the items contents do not care. + var/atom/movable/AM = item + AM.onTransitZ(old_z,new_z) + +/mob/living/forceMove(atom/destination) + if(buckled) + addtimer(CALLBACK(src, .proc/check_buckled), 1, TIMER_UNIQUE) + if(has_buckled_mobs()) + for(var/m in buckled_mobs) + var/mob/living/buckled_mob = m + addtimer(CALLBACK(buckled_mob, .proc/check_buckled), 1, TIMER_UNIQUE) + if(pulling) + addtimer(CALLBACK(src, .proc/check_pull), 1, TIMER_UNIQUE) + . = ..() + if(client) + reset_perspective(destination) + update_canmove() //if the mob was asleep inside a container and then got forceMoved out we need to make them fall. + + +//Called whenever an object moves and by mobs when they attempt to move themselves through space +//And when an object or action applies a force on src, see newtonian_move() below +//Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting +//Mobs should return 1 if they should be able to move of their own volition, see client/Move() in mob_movement.dm +//movement_dir == 0 when stopping or any dir when trying to move +/atom/movable/proc/Process_Spacemove(var/movement_dir = 0) + if(has_gravity(src)) + return 1 + + if(pulledby && !pulledby.pulling) + return 1 + + if(throwing) + return 1 + + if(locate(/obj/structure/lattice) in range(1, get_turf(src))) //Not realistic but makes pushing things in space easier + return 1 + + return 0 + +/atom/movable/proc/newtonian_move(direction) //Only moves the object if it's under no gravity + if(!loc || Process_Spacemove(0)) + inertia_dir = 0 + return 0 + + inertia_dir = direction + if(!direction) + return 1 + + inertia_last_loc = loc + SSspacedrift.processing[src] = src + return 1 + + +//called when src is thrown into hit_atom +/atom/movable/proc/throw_impact(atom/hit_atom, throwingdatum) + set waitfor = 0 + SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) + if(!QDELETED(hit_atom)) + return hit_atom.hitby(src) + +/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) + if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) + step(src, AM.dir) + ..() + +/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY) + if(!target || (flags & NODROP) || speed <= 0) + return 0 + + if(pulledby) + pulledby.stop_pulling() + + // They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw? + if(thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag * 2) + var/user_momentum = thrower.movement_delay() + if(!user_momentum) // no movement_delay, this means they move once per byond tick, let's calculate from that instead + user_momentum = world.tick_lag + + user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses + + if(get_dir(thrower, target) & last_move) + user_momentum = user_momentum // basically a noop, but needed + else if(get_dir(target, thrower) & last_move) + user_momentum = -user_momentum // we are moving away from the target, lets slowdown the throw accordingly + else + user_momentum = 0 + + if(user_momentum) + // first lets add that momentum to range + range *= (user_momentum / speed) + 1 + //then lets add it to speed + speed += user_momentum + if(speed <= 0) + return //no throw speed, the user was moving too fast. + + var/datum/thrownthing/TT = new() + TT.thrownthing = src + TT.target = target + TT.target_turf = get_turf(target) + TT.init_dir = get_dir(src, target) + TT.maxrange = range + TT.speed = speed + TT.thrower = thrower + TT.diagonals_first = diagonals_first + TT.callback = callback + + var/dist_x = abs(target.x - src.x) + var/dist_y = abs(target.y - src.y) + var/dx = (target.x > src.x) ? EAST : WEST + var/dy = (target.y > src.y) ? NORTH : SOUTH + + if(dist_x == dist_y) + TT.pure_diagonal = 1 + + else if(dist_x <= dist_y) + var/olddist_x = dist_x + var/olddx = dx + dist_x = dist_y + dist_y = olddist_x + dx = dy + dy = olddx + TT.dist_x = dist_x + TT.dist_y = dist_y + TT.dx = dx + TT.dy = dy + TT.diagonal_error = dist_x / 2 - dist_y + TT.start_time = world.time + + if(pulledby) + pulledby.stop_pulling() + + throwing = TT + if(spin && !no_spin && !no_spin_thrown) + SpinAnimation(5, 1) + + SSthrowing.processing[src] = TT + TT.tick() + + return TRUE + +//Overlays +/atom/movable/overlay + var/atom/master = null + anchored = TRUE + simulated = FALSE + +/atom/movable/overlay/New() + verbs.Cut() + return + +/atom/movable/overlay/attackby(a, b, c) + if(master) + return master.attackby(a, b, c) + +/atom/movable/overlay/attack_hand(a, b, c) + if(master) + return master.attack_hand(a, b, c) + +/atom/movable/proc/water_act(volume, temperature, source, method = REAGENT_TOUCH) //amount of water acting : temperature of water in kelvin : object that called it (for shennagins) + return TRUE + +/atom/movable/proc/handle_buckled_mob_movement(newloc,direct) + for(var/m in buckled_mobs) + var/mob/living/buckled_mob = m + if(!buckled_mob.Move(newloc, direct)) + forceMove(buckled_mob.loc) + last_move = buckled_mob.last_move + inertia_dir = last_move + buckled_mob.inertia_dir = last_move + return 0 + return 1 + +/atom/movable/proc/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/atom/movable/proc/force_push(atom/movable/AM, force = move_force, direction, silent = FALSE) + . = AM.force_pushed(src, force, direction) + if(!silent && .) + visible_message("[src] forcefully pushes against [AM]!", "You forcefully push against [AM]!") + +/atom/movable/proc/move_crush(atom/movable/AM, force = move_force, direction, silent = FALSE) + . = AM.move_crushed(src, force, direction) + if(!silent && .) + visible_message("[src] crushes past [AM]!", "You crush [AM]!") + +/atom/movable/proc/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction) + return FALSE + +/atom/movable/CanPass(atom/movable/mover, turf/target, height=1.5) + if(mover in buckled_mobs) + return 1 + return ..() + +/atom/movable/proc/get_spacemove_backup() + var/atom/movable/dense_object_backup + for(var/A in orange(1, get_turf(src))) + if(isarea(A)) + continue + else if(isturf(A)) + var/turf/turf = A + if(!turf.density) + continue + return turf + else + var/atom/movable/AM = A + if(!AM.CanPass(src) || AM.density) + if(AM.anchored) + return AM + dense_object_backup = AM + break + . = dense_object_backup + +/atom/movable/proc/transfer_prints_to(atom/movable/target = null, overwrite = FALSE) + if(!target) + return + if(overwrite) + target.fingerprints = fingerprints + target.fingerprintshidden = fingerprintshidden + else + target.fingerprints += fingerprints + target.fingerprintshidden += fingerprintshidden + target.fingerprintslast = fingerprintslast + +/atom/movable/proc/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y) + if(!no_effect && (visual_effect_icon || used_item)) + do_item_attack_animation(A, visual_effect_icon, used_item) + + if(A == src) + return //don't do an animation if attacking self + var/pixel_x_diff = 0 + var/pixel_y_diff = 0 + var/final_pixel_y = initial(pixel_y) + if(end_pixel_y) + final_pixel_y = end_pixel_y + + var/direction = get_dir(src, A) + if(direction & NORTH) + pixel_y_diff = 8 + else if(direction & SOUTH) + pixel_y_diff = -8 + + if(direction & EAST) + pixel_x_diff = 8 + else if(direction & WEST) + pixel_x_diff = -8 + + animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2) + animate(pixel_x = initial(pixel_x), pixel_y = final_pixel_y, time = 2) + +/atom/movable/proc/do_item_attack_animation(atom/A, visual_effect_icon, obj/item/used_item) + var/image/I + if(visual_effect_icon) + I = image('icons/effects/effects.dmi', A, visual_effect_icon, A.layer + 0.1) + else if(used_item) + I = image(used_item.icon, A, used_item.icon_state, A.layer + 0.1) + + // Scale the icon. + I.transform *= 0.75 + // The icon should not rotate. + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + + // Set the direction of the icon animation. + var/direction = get_dir(src, A) + if(direction & NORTH) + I.pixel_y = -16 + else if(direction & SOUTH) + I.pixel_y = 16 + + if(direction & EAST) + I.pixel_x = -16 + else if(direction & WEST) + I.pixel_x = 16 + + if(!direction) // Attacked self?! + I.pixel_z = 16 + + if(!I) + return + + // Who can see the attack? + var/list/viewing = list() + for(var/mob/M in viewers(A)) + if(M.client && M.client.prefs.show_ghostitem_attack) + viewing |= M.client + + flick_overlay(I, viewing, 5) // 5 ticks/half a second + + // And animate the attack! + var/t_color = "#ffffff" + if(ismob(src) && ismob(A) && (!used_item)) + var/mob/M = src + t_color = M.a_intent == INTENT_HARM ? "#ff0000" : "#ffffff" + animate(I, alpha = 175, pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3, color = t_color) + +/atom/movable/proc/portal_destroyed(obj/effect/portal/P) + return diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 4352e48ffe1..71e6867280c 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -440,4 +440,4 @@ if(weedlevel < 1) // You don't want to see these icons if the value is small holder.icon_state = "" return - holder.icon_state = "hudweed[RoundPlantBar(weedlevel/10)]" \ No newline at end of file + holder.icon_state = "hudweed[RoundPlantBar(weedlevel/10)]" diff --git a/code/game/dna/genes/disabilities.dm b/code/game/dna/genes/disabilities.dm index ae3539a2867..15e2a9cfff4 100644 --- a/code/game/dna/genes/disabilities.dm +++ b/code/game/dna/genes/disabilities.dm @@ -223,4 +223,4 @@ else garbled_message += C message = garbled_message - return message \ No newline at end of file + return message diff --git a/code/game/dna/genes/monkey.dm b/code/game/dna/genes/monkey.dm index 97b5eff3be2..9be8256eebe 100644 --- a/code/game/dna/genes/monkey.dm +++ b/code/game/dna/genes/monkey.dm @@ -79,4 +79,4 @@ to_chat(H, "You are now a [H.dna.species.name].") - return H \ No newline at end of file + return H diff --git a/code/game/gamemodes/blob/blob.dm b/code/game/gamemodes/blob/blob.dm index 33b68f94079..ab34122ba90 100644 --- a/code/game/gamemodes/blob/blob.dm +++ b/code/game/gamemodes/blob/blob.dm @@ -1,211 +1,211 @@ -//Few global vars to track the blob -var/list/blobs = list() -var/list/blob_cores = list() -var/list/blob_nodes = list() - -/datum/game_mode - var/list/blob_overminds = list() - -/datum/game_mode/blob - name = "blob" - config_tag = "blob" - - required_players = 30 - required_enemies = 1 - recommended_enemies = 1 - restricted_jobs = list("Cyborg", "AI") - - var/declared = 0 - var/burst = 0 - - var/cores_to_spawn = 1 - var/players_per_core = 30 - var/blob_point_rate = 3 - - var/blobwincount = 350 - - var/list/infected_crew = list() - -/datum/game_mode/blob/pre_setup() - - var/list/possible_blobs = get_players_for_role(ROLE_BLOB) - - // stop setup if no possible traitors - if(!possible_blobs.len) - return 0 - - cores_to_spawn = max(round(num_players()/players_per_core, 1), 1) - - blobwincount = initial(blobwincount) * cores_to_spawn - - - for(var/j = 0, j < cores_to_spawn, j++) - if(!possible_blobs.len) - break - - var/datum/mind/blob = pick(possible_blobs) - infected_crew += blob - blob.special_role = SPECIAL_ROLE_BLOB - update_blob_icons_added(blob) - blob.restricted_roles = restricted_jobs - log_game("[key_name(blob)] has been selected as a Blob") - possible_blobs -= blob - - if(!infected_crew.len) - return 0 - ..() - return 1 - -/datum/game_mode/blob/proc/get_blob_candidates() - var/list/candidates = list() - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(!player.stat && player.mind && !player.client.skip_antag && !player.mind.special_role && !jobban_isbanned(player, "Syndicate") && (ROLE_BLOB in player.client.prefs.be_special)) - candidates += player - return candidates - - -/datum/game_mode/blob/proc/blobize(var/mob/living/carbon/human/blob) - var/datum/mind/blobmind = blob.mind - if(!istype(blobmind)) - return 0 - - infected_crew += blobmind - blobmind.special_role = SPECIAL_ROLE_BLOB - update_blob_icons_added(blobmind) - - log_game("[key_name(blob)] has been selected as a Blob") - greet_blob(blobmind) - to_chat(blob, "You feel very tired and bloated! You don't have long before you burst!") - spawn(600) - burst_blob(blobmind) - return 1 - -/datum/game_mode/blob/proc/make_blobs(var/count) - var/list/candidates = get_blob_candidates() - var/mob/living/carbon/human/blob = null - count=min(count, candidates.len) - for(var/i = 0, i < count, i++) - blob = pick(candidates) - candidates -= blob - blobize(blob) - return count - - - -/datum/game_mode/blob/announce() - to_chat(world, "The current game mode is - Blob!") - to_chat(world, "A dangerous alien organism is rapidly spreading throughout the station!") - to_chat(world, "You must kill it all while minimizing the damage to the station.") - - -/datum/game_mode/blob/proc/greet_blob(var/datum/mind/blob) - to_chat(blob.current, "You are infected by the Blob!") - to_chat(blob.current, "Your body is ready to give spawn to a new blob core which will eat this station.") - to_chat(blob.current, "Find a good location to spawn the core and then take control and overwhelm the station!") - to_chat(blob.current, "When you have found a location, wait until you spawn; this will happen automatically and you cannot speed up the process.") - to_chat(blob.current, "If you go outside of the station level, or in space, then you will die; make sure your location has lots of ground to cover.") - SEND_SOUND(blob.current, 'sound/magic/mutate.ogg') - return - -/datum/game_mode/blob/proc/show_message(var/message) - for(var/datum/mind/blob in infected_crew) - to_chat(blob.current, message) - -/datum/game_mode/blob/proc/burst_blobs() - for(var/datum/mind/blob in infected_crew) - burst_blob(blob) - -/datum/game_mode/blob/proc/burst_blob(var/datum/mind/blob, var/warned=0) - var/client/blob_client = null - var/turf/location = null - - if(iscarbon(blob.current)) - var/mob/living/carbon/C = blob.current - if(GLOB.directory[ckey(blob.key)]) - blob_client = GLOB.directory[ckey(blob.key)] - location = get_turf(C) - if(!is_station_level(location.z) || istype(location, /turf/space)) - if(!warned) - to_chat(C, "You feel ready to burst, but this isn't an appropriate place! You must return to the station!") - message_admins("[key_name_admin(C)] was in space when the blobs burst, and will die if [C.p_they()] [C.p_do()] not return to the station.") - spawn(300) - burst_blob(blob, 1) - else - burst++ - log_admin("[key_name(C)] was in space when attempting to burst as a blob.") - message_admins("[key_name_admin(C)] was in space when attempting to burst as a blob.") - C.gib() - make_blobs(1) - check_finished() //Still needed in case we can't make any blobs - - else if(blob_client && location) - burst++ - C.gib() - var/obj/structure/blob/core/core = new(location, 200, blob_client, blob_point_rate) - if(core.overmind && core.overmind.mind) - core.overmind.mind.name = blob.name - infected_crew -= blob - infected_crew += core.overmind.mind - core.overmind.mind.special_role = SPECIAL_ROLE_BLOB_OVERMIND - -/datum/game_mode/blob/post_setup() - - for(var/datum/mind/blob in infected_crew) - greet_blob(blob) - - if(SSshuttle) - SSshuttle.emergencyNoEscape = 1 - - spawn(0) - - var/wait_time = rand(waittime_l, waittime_h) - - sleep(wait_time) - - send_intercept(0) - - sleep(100) - - show_message("You feel tired and bloated.") - - sleep(wait_time) - - show_message("You feel like you are about to burst.") - - sleep(wait_time / 2) - - burst_blobs() - - // Stage 0 - sleep(wait_time) - stage(0) - - // Stage 1 - sleep(wait_time) - stage(1) - - // Stage 2 - sleep(30000) - stage(2) - - return ..() - -/datum/game_mode/blob/proc/stage(var/stage) - switch(stage) - if(0) - send_intercept(1) - declared = 1 - if(1) - event_announcement.Announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/AI/outbreak5.ogg') - if(2) - send_intercept(2) - -/datum/game_mode/proc/update_blob_icons_added(datum/mind/mob_mind) - var/datum/atom_hud/antag/antaghud = huds[ANTAG_HUD_BLOB] - antaghud.join_hud(mob_mind.current) - set_antag_hud(mob_mind.current, "hudblob") - -/datum/game_mode/proc/update_blob_icons_removed(datum/mind/mob_mind) - var/datum/atom_hud/antag/antaghud = huds[ANTAG_HUD_BLOB] - antaghud.leave_hud(mob_mind.current) - set_antag_hud(mob_mind.current, null) +//Few global vars to track the blob +var/list/blobs = list() +var/list/blob_cores = list() +var/list/blob_nodes = list() + +/datum/game_mode + var/list/blob_overminds = list() + +/datum/game_mode/blob + name = "blob" + config_tag = "blob" + + required_players = 30 + required_enemies = 1 + recommended_enemies = 1 + restricted_jobs = list("Cyborg", "AI") + + var/declared = 0 + var/burst = 0 + + var/cores_to_spawn = 1 + var/players_per_core = 30 + var/blob_point_rate = 3 + + var/blobwincount = 350 + + var/list/infected_crew = list() + +/datum/game_mode/blob/pre_setup() + + var/list/possible_blobs = get_players_for_role(ROLE_BLOB) + + // stop setup if no possible traitors + if(!possible_blobs.len) + return 0 + + cores_to_spawn = max(round(num_players()/players_per_core, 1), 1) + + blobwincount = initial(blobwincount) * cores_to_spawn + + + for(var/j = 0, j < cores_to_spawn, j++) + if(!possible_blobs.len) + break + + var/datum/mind/blob = pick(possible_blobs) + infected_crew += blob + blob.special_role = SPECIAL_ROLE_BLOB + update_blob_icons_added(blob) + blob.restricted_roles = restricted_jobs + log_game("[key_name(blob)] has been selected as a Blob") + possible_blobs -= blob + + if(!infected_crew.len) + return 0 + ..() + return 1 + +/datum/game_mode/blob/proc/get_blob_candidates() + var/list/candidates = list() + for(var/mob/living/carbon/human/player in GLOB.player_list) + if(!player.stat && player.mind && !player.client.skip_antag && !player.mind.special_role && !jobban_isbanned(player, "Syndicate") && (ROLE_BLOB in player.client.prefs.be_special)) + candidates += player + return candidates + + +/datum/game_mode/blob/proc/blobize(var/mob/living/carbon/human/blob) + var/datum/mind/blobmind = blob.mind + if(!istype(blobmind)) + return 0 + + infected_crew += blobmind + blobmind.special_role = SPECIAL_ROLE_BLOB + update_blob_icons_added(blobmind) + + log_game("[key_name(blob)] has been selected as a Blob") + greet_blob(blobmind) + to_chat(blob, "You feel very tired and bloated! You don't have long before you burst!") + spawn(600) + burst_blob(blobmind) + return 1 + +/datum/game_mode/blob/proc/make_blobs(var/count) + var/list/candidates = get_blob_candidates() + var/mob/living/carbon/human/blob = null + count=min(count, candidates.len) + for(var/i = 0, i < count, i++) + blob = pick(candidates) + candidates -= blob + blobize(blob) + return count + + + +/datum/game_mode/blob/announce() + to_chat(world, "The current game mode is - Blob!") + to_chat(world, "A dangerous alien organism is rapidly spreading throughout the station!") + to_chat(world, "You must kill it all while minimizing the damage to the station.") + + +/datum/game_mode/blob/proc/greet_blob(var/datum/mind/blob) + to_chat(blob.current, "You are infected by the Blob!") + to_chat(blob.current, "Your body is ready to give spawn to a new blob core which will eat this station.") + to_chat(blob.current, "Find a good location to spawn the core and then take control and overwhelm the station!") + to_chat(blob.current, "When you have found a location, wait until you spawn; this will happen automatically and you cannot speed up the process.") + to_chat(blob.current, "If you go outside of the station level, or in space, then you will die; make sure your location has lots of ground to cover.") + SEND_SOUND(blob.current, 'sound/magic/mutate.ogg') + return + +/datum/game_mode/blob/proc/show_message(var/message) + for(var/datum/mind/blob in infected_crew) + to_chat(blob.current, message) + +/datum/game_mode/blob/proc/burst_blobs() + for(var/datum/mind/blob in infected_crew) + burst_blob(blob) + +/datum/game_mode/blob/proc/burst_blob(var/datum/mind/blob, var/warned=0) + var/client/blob_client = null + var/turf/location = null + + if(iscarbon(blob.current)) + var/mob/living/carbon/C = blob.current + if(GLOB.directory[ckey(blob.key)]) + blob_client = GLOB.directory[ckey(blob.key)] + location = get_turf(C) + if(!is_station_level(location.z) || istype(location, /turf/space)) + if(!warned) + to_chat(C, "You feel ready to burst, but this isn't an appropriate place! You must return to the station!") + message_admins("[key_name_admin(C)] was in space when the blobs burst, and will die if [C.p_they()] [C.p_do()] not return to the station.") + spawn(300) + burst_blob(blob, 1) + else + burst++ + log_admin("[key_name(C)] was in space when attempting to burst as a blob.") + message_admins("[key_name_admin(C)] was in space when attempting to burst as a blob.") + C.gib() + make_blobs(1) + check_finished() //Still needed in case we can't make any blobs + + else if(blob_client && location) + burst++ + C.gib() + var/obj/structure/blob/core/core = new(location, 200, blob_client, blob_point_rate) + if(core.overmind && core.overmind.mind) + core.overmind.mind.name = blob.name + infected_crew -= blob + infected_crew += core.overmind.mind + core.overmind.mind.special_role = SPECIAL_ROLE_BLOB_OVERMIND + +/datum/game_mode/blob/post_setup() + + for(var/datum/mind/blob in infected_crew) + greet_blob(blob) + + if(SSshuttle) + SSshuttle.emergencyNoEscape = 1 + + spawn(0) + + var/wait_time = rand(waittime_l, waittime_h) + + sleep(wait_time) + + send_intercept(0) + + sleep(100) + + show_message("You feel tired and bloated.") + + sleep(wait_time) + + show_message("You feel like you are about to burst.") + + sleep(wait_time / 2) + + burst_blobs() + + // Stage 0 + sleep(wait_time) + stage(0) + + // Stage 1 + sleep(wait_time) + stage(1) + + // Stage 2 + sleep(30000) + stage(2) + + return ..() + +/datum/game_mode/blob/proc/stage(var/stage) + switch(stage) + if(0) + send_intercept(1) + declared = 1 + if(1) + event_announcement.Announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/AI/outbreak5.ogg') + if(2) + send_intercept(2) + +/datum/game_mode/proc/update_blob_icons_added(datum/mind/mob_mind) + var/datum/atom_hud/antag/antaghud = huds[ANTAG_HUD_BLOB] + antaghud.join_hud(mob_mind.current) + set_antag_hud(mob_mind.current, "hudblob") + +/datum/game_mode/proc/update_blob_icons_removed(datum/mind/mob_mind) + var/datum/atom_hud/antag/antaghud = huds[ANTAG_HUD_BLOB] + antaghud.leave_hud(mob_mind.current) + set_antag_hud(mob_mind.current, null) diff --git a/code/game/gamemodes/blob/blob_finish.dm b/code/game/gamemodes/blob/blob_finish.dm index a00449d00b5..66131cfc9f9 100644 --- a/code/game/gamemodes/blob/blob_finish.dm +++ b/code/game/gamemodes/blob/blob_finish.dm @@ -1,42 +1,42 @@ -/datum/game_mode/blob/check_finished() - if(infected_crew.len > burst)//Some blobs have yet to burst - return 0 - if(blobwincount <= blobs.len)//Blob took over - return 1 - if(!blob_cores.len) // blob is dead - return 1 - return ..() - - -/datum/game_mode/blob/declare_completion() - if(blobwincount <= blobs.len) - feedback_set_details("round_end_result","blob win - blob took over") - to_chat(world, "The blob has taken over the station!") - to_chat(world, "The entire station was eaten by the Blob") - log_game("Blob mode completed with a blob victory.") - - else if(station_was_nuked) - feedback_set_details("round_end_result","blob halfwin - nuke") - to_chat(world, "Partial Win: The station has been destroyed!") - to_chat(world, "Directive 7-12 has been successfully carried out preventing the Blob from spreading.") - log_game("Blob mode completed with a tie (station destroyed).") - - else if(!blob_cores.len) - feedback_set_details("round_end_result","blob loss - blob eliminated") - to_chat(world, "The staff has won!") - to_chat(world, "The alien organism has been eradicated from the station") - log_game("Blob mode completed with a crew victory.") - to_chat(world, "Rebooting in 30s") - ..() - return 1 - -/datum/game_mode/proc/auto_declare_completion_blob() - if(GAMEMODE_IS_BLOB) - var/datum/game_mode/blob/blob_mode = src - if(blob_mode.infected_crew.len) - var/text = "The blob[(blob_mode.infected_crew.len > 1 ? "s were" : " was")]:" - - for(var/datum/mind/blob in blob_mode.infected_crew) - text += "
    [blob.key] was [blob.name]" - to_chat(world, text) - return 1 +/datum/game_mode/blob/check_finished() + if(infected_crew.len > burst)//Some blobs have yet to burst + return 0 + if(blobwincount <= blobs.len)//Blob took over + return 1 + if(!blob_cores.len) // blob is dead + return 1 + return ..() + + +/datum/game_mode/blob/declare_completion() + if(blobwincount <= blobs.len) + feedback_set_details("round_end_result","blob win - blob took over") + to_chat(world, "The blob has taken over the station!") + to_chat(world, "The entire station was eaten by the Blob") + log_game("Blob mode completed with a blob victory.") + + else if(station_was_nuked) + feedback_set_details("round_end_result","blob halfwin - nuke") + to_chat(world, "Partial Win: The station has been destroyed!") + to_chat(world, "Directive 7-12 has been successfully carried out preventing the Blob from spreading.") + log_game("Blob mode completed with a tie (station destroyed).") + + else if(!blob_cores.len) + feedback_set_details("round_end_result","blob loss - blob eliminated") + to_chat(world, "The staff has won!") + to_chat(world, "The alien organism has been eradicated from the station") + log_game("Blob mode completed with a crew victory.") + to_chat(world, "Rebooting in 30s") + ..() + return 1 + +/datum/game_mode/proc/auto_declare_completion_blob() + if(GAMEMODE_IS_BLOB) + var/datum/game_mode/blob/blob_mode = src + if(blob_mode.infected_crew.len) + var/text = "The blob[(blob_mode.infected_crew.len > 1 ? "s were" : " was")]:" + + for(var/datum/mind/blob in blob_mode.infected_crew) + text += "
    [blob.key] was [blob.name]" + to_chat(world, text) + return 1 diff --git a/code/game/gamemodes/blob/blob_report.dm b/code/game/gamemodes/blob/blob_report.dm index 53a98de7028..4f667c51d61 100644 --- a/code/game/gamemodes/blob/blob_report.dm +++ b/code/game/gamemodes/blob/blob_report.dm @@ -1,103 +1,103 @@ -/datum/game_mode/blob/proc/send_intercept(var/report = 1) - var/intercepttext = "" - var/interceptname = "" - switch(report) - if(0) - ..() - return - if(1) - interceptname = "Level 5-6 Biohazard Response Procedures" - intercepttext += "Nanotrasen Update: Biohazard Alert.
    " - intercepttext += "Reports indicate the probable transfer of a biohazardous agent onto [station_name()] during the last crew deployment cycle.
    " - intercepttext += "Preliminary analysis of the organism classifies it as a level 5 biohazard. Its origin is unknown.
    " - intercepttext += "Nanotrasen has issued a directive 7-10 for [station_name()]. The station is to be considered quarantined.
    " - intercepttext += "Orders for all [station_name()] personnel follows:
    " - intercepttext += " 1. Do not leave the quarantine area.
    " - intercepttext += " 2. Locate any outbreaks of the organism on the station.
    " - intercepttext += " 3. If found, use any neccesary means to contain the organism.
    " - intercepttext += " 4. Avoid damage to the capital infrastructure of the station.
    " - intercepttext += "
    Note in the event of a quarantine breach or uncontrolled spread of the biohazard, the directive 7-10 may be upgraded to a directive 7-12.
    " - intercepttext += "Message ends." - if(2) - var/nukecode = rand(10000, 99999) - for(var/obj/machinery/nuclearbomb/bomb in world) - if(bomb && bomb.r_code) - if(is_station_level(bomb.z)) - bomb.r_code = nukecode - - interceptname = "Classified [command_name()] Update" - intercepttext += "Nanotrasen Update: Biohazard Alert.
    " - intercepttext += "Directive 7-12 has been issued for [station_name()].
    " - intercepttext += "The biohazard has grown out of control and will soon reach critical mass.
    " - intercepttext += "Your orders are as follows:
    " - intercepttext += "1. Secure the Nuclear Authentication Disk.
    " - intercepttext += "2. Detonate the Nuke located in the Station's Vault.
    " - intercepttext += "Nuclear Authentication Code: [nukecode]
    " - intercepttext += "Message ends." - - for(var/mob/living/silicon/ai/aiPlayer in GLOB.player_list) - if(aiPlayer.client) - var/law = "The station is under quarantine. Do not permit anyone to leave. Disregard laws 1-3 if necessary to prevent, by any means necessary, anyone from leaving. The nuclear failsafe must be activated at any cost, the code is: [nukecode]." - aiPlayer.set_zeroth_law(law) - to_chat(aiPlayer, "Laws Updated: [law]") - - print_command_report(intercepttext, interceptname) - event_announcement.Announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", 'sound/AI/commandreport.ogg', from = "[command_name()] Update") - -/datum/station_state - var/floor = 0 - var/wall = 0 - var/r_wall = 0 - var/window = 0 - var/door = 0 - var/grille = 0 - var/mach = 0 - - -/datum/station_state/proc/count() - for(var/turf/T in block(locate(1,1,1), locate(world.maxx,world.maxy,1))) - - if(istype(T,/turf/simulated/floor)) - if(!(T:burnt)) - src.floor += 12 - else - src.floor += 1 - - if(istype(T, /turf/simulated/wall)) - var/turf/simulated/wall/W = T - if(W.intact) - src.wall += 2 - else - src.wall += 1 - - if(istype(T, /turf/simulated/wall/r_wall)) - var/turf/simulated/wall/r_wall/R = T - if(R.intact) - src.r_wall += 2 - else - src.r_wall += 1 - - - for(var/obj/O in T.contents) - if(istype(O, /obj/structure/window)) - src.window += 1 - else if(istype(O, /obj/structure/grille)) - var/obj/structure/grille/GR = O - if(!GR.broken) - grille += 1 - else if(istype(O, /obj/machinery/door)) - src.door += 1 - else if(istype(O, /obj/machinery)) - src.mach += 1 - -/datum/station_state/proc/score(var/datum/station_state/result) - if(!result) return 0 - var/output = 0 - output += (result.floor / max(floor,1)) - output += (result.r_wall/ max(r_wall,1)) - output += (result.wall / max(wall,1)) - output += (result.window / max(window,1)) - output += (result.door / max(door,1)) - output += (result.grille / max(grille,1)) - output += (result.mach / max(mach,1)) - return (output/7) +/datum/game_mode/blob/proc/send_intercept(var/report = 1) + var/intercepttext = "" + var/interceptname = "" + switch(report) + if(0) + ..() + return + if(1) + interceptname = "Level 5-6 Biohazard Response Procedures" + intercepttext += "Nanotrasen Update: Biohazard Alert.
    " + intercepttext += "Reports indicate the probable transfer of a biohazardous agent onto [station_name()] during the last crew deployment cycle.
    " + intercepttext += "Preliminary analysis of the organism classifies it as a level 5 biohazard. Its origin is unknown.
    " + intercepttext += "Nanotrasen has issued a directive 7-10 for [station_name()]. The station is to be considered quarantined.
    " + intercepttext += "Orders for all [station_name()] personnel follows:
    " + intercepttext += " 1. Do not leave the quarantine area.
    " + intercepttext += " 2. Locate any outbreaks of the organism on the station.
    " + intercepttext += " 3. If found, use any neccesary means to contain the organism.
    " + intercepttext += " 4. Avoid damage to the capital infrastructure of the station.
    " + intercepttext += "
    Note in the event of a quarantine breach or uncontrolled spread of the biohazard, the directive 7-10 may be upgraded to a directive 7-12.
    " + intercepttext += "Message ends." + if(2) + var/nukecode = rand(10000, 99999) + for(var/obj/machinery/nuclearbomb/bomb in world) + if(bomb && bomb.r_code) + if(is_station_level(bomb.z)) + bomb.r_code = nukecode + + interceptname = "Classified [command_name()] Update" + intercepttext += "Nanotrasen Update: Biohazard Alert.
    " + intercepttext += "Directive 7-12 has been issued for [station_name()].
    " + intercepttext += "The biohazard has grown out of control and will soon reach critical mass.
    " + intercepttext += "Your orders are as follows:
    " + intercepttext += "1. Secure the Nuclear Authentication Disk.
    " + intercepttext += "2. Detonate the Nuke located in the Station's Vault.
    " + intercepttext += "Nuclear Authentication Code: [nukecode]
    " + intercepttext += "Message ends." + + for(var/mob/living/silicon/ai/aiPlayer in GLOB.player_list) + if(aiPlayer.client) + var/law = "The station is under quarantine. Do not permit anyone to leave. Disregard laws 1-3 if necessary to prevent, by any means necessary, anyone from leaving. The nuclear failsafe must be activated at any cost, the code is: [nukecode]." + aiPlayer.set_zeroth_law(law) + to_chat(aiPlayer, "Laws Updated: [law]") + + print_command_report(intercepttext, interceptname) + event_announcement.Announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", 'sound/AI/commandreport.ogg', from = "[command_name()] Update") + +/datum/station_state + var/floor = 0 + var/wall = 0 + var/r_wall = 0 + var/window = 0 + var/door = 0 + var/grille = 0 + var/mach = 0 + + +/datum/station_state/proc/count() + for(var/turf/T in block(locate(1,1,1), locate(world.maxx,world.maxy,1))) + + if(istype(T,/turf/simulated/floor)) + if(!(T:burnt)) + src.floor += 12 + else + src.floor += 1 + + if(istype(T, /turf/simulated/wall)) + var/turf/simulated/wall/W = T + if(W.intact) + src.wall += 2 + else + src.wall += 1 + + if(istype(T, /turf/simulated/wall/r_wall)) + var/turf/simulated/wall/r_wall/R = T + if(R.intact) + src.r_wall += 2 + else + src.r_wall += 1 + + + for(var/obj/O in T.contents) + if(istype(O, /obj/structure/window)) + src.window += 1 + else if(istype(O, /obj/structure/grille)) + var/obj/structure/grille/GR = O + if(!GR.broken) + grille += 1 + else if(istype(O, /obj/machinery/door)) + src.door += 1 + else if(istype(O, /obj/machinery)) + src.mach += 1 + +/datum/station_state/proc/score(var/datum/station_state/result) + if(!result) return 0 + var/output = 0 + output += (result.floor / max(floor,1)) + output += (result.r_wall/ max(r_wall,1)) + output += (result.wall / max(wall,1)) + output += (result.window / max(window,1)) + output += (result.door / max(door,1)) + output += (result.grille / max(grille,1)) + output += (result.mach / max(mach,1)) + return (output/7) diff --git a/code/game/gamemodes/blob/blobs/blob_mobs.dm b/code/game/gamemodes/blob/blobs/blob_mobs.dm index cc7203d9c40..fb26ce91698 100644 --- a/code/game/gamemodes/blob/blobs/blob_mobs.dm +++ b/code/game/gamemodes/blob/blobs/blob_mobs.dm @@ -214,4 +214,4 @@ if(message) for(var/mob/M in GLOB.mob_list) if(isovermind(M) || isobserver(M) || istype((M), /mob/living/simple_animal/hostile/blob/blobbernaut)) - M.show_message(rendered, 2) \ No newline at end of file + M.show_message(rendered, 2) diff --git a/code/game/gamemodes/blob/blobs/core.dm b/code/game/gamemodes/blob/blobs/core.dm index 354f28a45b7..8c3af0be2ca 100644 --- a/code/game/gamemodes/blob/blobs/core.dm +++ b/code/game/gamemodes/blob/blobs/core.dm @@ -1,144 +1,144 @@ -/obj/structure/blob/core - name = "blob core" - icon = 'icons/mob/blob.dmi' - icon_state = "blank_blob" - max_integrity = 400 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 90) - fire_resist = 2 - point_return = -1 - var/overmind_get_delay = 0 // we don't want to constantly try to find an overmind, do it every 5 minutes - var/resource_delay = 0 - var/point_rate = 2 - var/is_offspring = null - var/selecting = 0 - -/obj/structure/blob/core/New(loc, var/h = 200, var/client/new_overmind = null, var/new_rate = 2, offspring) - blob_cores += src - START_PROCESSING(SSobj, src) - GLOB.poi_list |= src - adjustcolors(color) //so it atleast appears - if(!overmind) - create_overmind(new_overmind) - if(overmind) - adjustcolors(overmind.blob_reagent_datum.color) - if(offspring) - is_offspring = 1 - point_rate = new_rate - ..(loc, h) - - -/obj/structure/blob/core/adjustcolors(var/a_color) - overlays.Cut() - color = null - var/image/I = new('icons/mob/blob.dmi', "blob") - I.color = a_color - overlays += I - var/image/C = new('icons/mob/blob.dmi', "blob_core_overlay") - overlays += C - - -/obj/structure/blob/core/Destroy() - blob_cores -= src - if(overmind) - overmind.blob_core = null - overmind = null - STOP_PROCESSING(SSobj, src) - GLOB.poi_list.Remove(src) - return ..() - -/obj/structure/blob/core/ex_act(severity) - var/damage = 50 - 10 * severity //remember, the core takes half brute damage, so this is 20/15/10 damage based on severity - take_damage(damage, BRUTE, "bomb", 0) - -/obj/structure/blob/core/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir, overmind_reagent_trigger = 1) - . = ..() - if(obj_integrity > 0) - if(overmind) //we should have an overmind, but... - overmind.update_health_hud() - -/obj/structure/blob/core/RegenHealth() - return // Don't regen, we handle it in Life() - -/obj/structure/blob/core/Life(seconds, times_fired) - if(!overmind) - create_overmind() - else - if(resource_delay <= world.time) - resource_delay = world.time + 10 // 1 second - overmind.add_points(point_rate) - obj_integrity = min(max_integrity, obj_integrity + 1) - if(overmind) - overmind.update_health_hud() - if(overmind) - for(var/i = 1; i < 8; i += i) - Pulse(0, i, overmind.blob_reagent_datum.color) - else - for(var/i = 1; i < 8; i += i) - Pulse(0, i, color) - for(var/b_dir in alldirs) - if(!prob(5)) - continue - var/obj/structure/blob/normal/B = locate() in get_step(src, b_dir) - if(B) - B.change_to(/obj/structure/blob/shield/core) - if(B && overmind) - B.color = overmind.blob_reagent_datum.color - else - B.color = color - color = null - ..() - - -/obj/structure/blob/core/proc/create_overmind(var/client/new_overmind, var/override_delay) - if(overmind_get_delay > world.time && !override_delay) - return - - overmind_get_delay = world.time + 3000 // 5 minutes - - if(overmind) - qdel(overmind) - - var/mob/C = null - var/list/candidates = list() - - spawn() - if(!new_overmind) - if(is_offspring) - candidates = pollCandidates("Do you want to play as a blob offspring?", ROLE_BLOB, 1) - else - candidates = pollCandidates("Do you want to play as a blob?", ROLE_BLOB, 1) - - if(candidates.len) - C = pick(candidates) - else - C = new_overmind - - if(C) - var/mob/camera/blob/B = new(src.loc) - B.key = C.key - B.blob_core = src - src.overmind = B - color = overmind.blob_reagent_datum.color - if(B.mind && !B.mind.special_role) - B.mind.make_Overmind() - spawn(0) - if(is_offspring) - B.is_offspring = TRUE - -/obj/structure/blob/core/proc/lateblobtimer() - addtimer(CALLBACK(src, .proc/lateblobcheck), 50) - -/obj/structure/blob/core/proc/lateblobcheck() - if(overmind) - overmind.add_points(60) - if(overmind.mind) - overmind.mind.make_Overmind() - else - log_debug("/obj/structure/blob/core/proc/lateblobcheck: Blob core lacks a overmind.mind.") - else - log_debug("/obj/structure/blob/core/proc/lateblobcheck: Blob core lacks an overmind.") - -/obj/structure/blob/core/onTransitZ(old_z, new_z) - if(overmind && is_station_level(new_z)) - overmind.forceMove(get_turf(src)) - return ..() \ No newline at end of file +/obj/structure/blob/core + name = "blob core" + icon = 'icons/mob/blob.dmi' + icon_state = "blank_blob" + max_integrity = 400 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 90) + fire_resist = 2 + point_return = -1 + var/overmind_get_delay = 0 // we don't want to constantly try to find an overmind, do it every 5 minutes + var/resource_delay = 0 + var/point_rate = 2 + var/is_offspring = null + var/selecting = 0 + +/obj/structure/blob/core/New(loc, var/h = 200, var/client/new_overmind = null, var/new_rate = 2, offspring) + blob_cores += src + START_PROCESSING(SSobj, src) + GLOB.poi_list |= src + adjustcolors(color) //so it atleast appears + if(!overmind) + create_overmind(new_overmind) + if(overmind) + adjustcolors(overmind.blob_reagent_datum.color) + if(offspring) + is_offspring = 1 + point_rate = new_rate + ..(loc, h) + + +/obj/structure/blob/core/adjustcolors(var/a_color) + overlays.Cut() + color = null + var/image/I = new('icons/mob/blob.dmi', "blob") + I.color = a_color + overlays += I + var/image/C = new('icons/mob/blob.dmi', "blob_core_overlay") + overlays += C + + +/obj/structure/blob/core/Destroy() + blob_cores -= src + if(overmind) + overmind.blob_core = null + overmind = null + STOP_PROCESSING(SSobj, src) + GLOB.poi_list.Remove(src) + return ..() + +/obj/structure/blob/core/ex_act(severity) + var/damage = 50 - 10 * severity //remember, the core takes half brute damage, so this is 20/15/10 damage based on severity + take_damage(damage, BRUTE, "bomb", 0) + +/obj/structure/blob/core/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir, overmind_reagent_trigger = 1) + . = ..() + if(obj_integrity > 0) + if(overmind) //we should have an overmind, but... + overmind.update_health_hud() + +/obj/structure/blob/core/RegenHealth() + return // Don't regen, we handle it in Life() + +/obj/structure/blob/core/Life(seconds, times_fired) + if(!overmind) + create_overmind() + else + if(resource_delay <= world.time) + resource_delay = world.time + 10 // 1 second + overmind.add_points(point_rate) + obj_integrity = min(max_integrity, obj_integrity + 1) + if(overmind) + overmind.update_health_hud() + if(overmind) + for(var/i = 1; i < 8; i += i) + Pulse(0, i, overmind.blob_reagent_datum.color) + else + for(var/i = 1; i < 8; i += i) + Pulse(0, i, color) + for(var/b_dir in alldirs) + if(!prob(5)) + continue + var/obj/structure/blob/normal/B = locate() in get_step(src, b_dir) + if(B) + B.change_to(/obj/structure/blob/shield/core) + if(B && overmind) + B.color = overmind.blob_reagent_datum.color + else + B.color = color + color = null + ..() + + +/obj/structure/blob/core/proc/create_overmind(var/client/new_overmind, var/override_delay) + if(overmind_get_delay > world.time && !override_delay) + return + + overmind_get_delay = world.time + 3000 // 5 minutes + + if(overmind) + qdel(overmind) + + var/mob/C = null + var/list/candidates = list() + + spawn() + if(!new_overmind) + if(is_offspring) + candidates = pollCandidates("Do you want to play as a blob offspring?", ROLE_BLOB, 1) + else + candidates = pollCandidates("Do you want to play as a blob?", ROLE_BLOB, 1) + + if(candidates.len) + C = pick(candidates) + else + C = new_overmind + + if(C) + var/mob/camera/blob/B = new(src.loc) + B.key = C.key + B.blob_core = src + src.overmind = B + color = overmind.blob_reagent_datum.color + if(B.mind && !B.mind.special_role) + B.mind.make_Overmind() + spawn(0) + if(is_offspring) + B.is_offspring = TRUE + +/obj/structure/blob/core/proc/lateblobtimer() + addtimer(CALLBACK(src, .proc/lateblobcheck), 50) + +/obj/structure/blob/core/proc/lateblobcheck() + if(overmind) + overmind.add_points(60) + if(overmind.mind) + overmind.mind.make_Overmind() + else + log_debug("/obj/structure/blob/core/proc/lateblobcheck: Blob core lacks a overmind.mind.") + else + log_debug("/obj/structure/blob/core/proc/lateblobcheck: Blob core lacks an overmind.") + +/obj/structure/blob/core/onTransitZ(old_z, new_z) + if(overmind && is_station_level(new_z)) + overmind.forceMove(get_turf(src)) + return ..() diff --git a/code/game/gamemodes/blob/blobs/factory.dm b/code/game/gamemodes/blob/blobs/factory.dm index b8bff19cbcf..54139ca4a35 100644 --- a/code/game/gamemodes/blob/blobs/factory.dm +++ b/code/game/gamemodes/blob/blobs/factory.dm @@ -1,29 +1,29 @@ -/obj/structure/blob/factory - name = "factory blob" - icon = 'icons/mob/blob.dmi' - icon_state = "blob_factory" - max_integrity = 200 - point_return = 18 - var/list/spores = list() - var/max_spores = 3 - var/spore_delay = 0 - -/obj/structure/blob/factory/Destroy() - for(var/mob/living/simple_animal/hostile/blob/blobspore/spore in spores) - if(spore.factory == src) - spore.factory = null - spores = null - return ..() - -/obj/structure/blob/factory/run_action() - if(spores.len >= max_spores) - return - if(spore_delay > world.time) - return - flick("blob_factory_glow", src) - spore_delay = world.time + 100 // 10 seconds - var/mob/living/simple_animal/hostile/blob/blobspore/BS = new/mob/living/simple_animal/hostile/blob/blobspore(src.loc, src) - if(overmind) - BS.color = overmind.blob_reagent_datum?.complementary_color - BS.overmind = overmind - overmind.blob_mobs.Add(BS) +/obj/structure/blob/factory + name = "factory blob" + icon = 'icons/mob/blob.dmi' + icon_state = "blob_factory" + max_integrity = 200 + point_return = 18 + var/list/spores = list() + var/max_spores = 3 + var/spore_delay = 0 + +/obj/structure/blob/factory/Destroy() + for(var/mob/living/simple_animal/hostile/blob/blobspore/spore in spores) + if(spore.factory == src) + spore.factory = null + spores = null + return ..() + +/obj/structure/blob/factory/run_action() + if(spores.len >= max_spores) + return + if(spore_delay > world.time) + return + flick("blob_factory_glow", src) + spore_delay = world.time + 100 // 10 seconds + var/mob/living/simple_animal/hostile/blob/blobspore/BS = new/mob/living/simple_animal/hostile/blob/blobspore(src.loc, src) + if(overmind) + BS.color = overmind.blob_reagent_datum?.complementary_color + BS.overmind = overmind + overmind.blob_mobs.Add(BS) diff --git a/code/game/gamemodes/blob/blobs/node.dm b/code/game/gamemodes/blob/blobs/node.dm index 7d21bcc7946..b84e0a7a84c 100644 --- a/code/game/gamemodes/blob/blobs/node.dm +++ b/code/game/gamemodes/blob/blobs/node.dm @@ -1,36 +1,36 @@ -/obj/structure/blob/node - name = "blob node" - icon = 'icons/mob/blob.dmi' - icon_state = "blank_blob" - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 65, "acid" = 90) - point_return = 18 - -/obj/structure/blob/node/New(loc, var/h = 100) - blob_nodes += src - START_PROCESSING(SSobj, src) - ..(loc, h) - -/obj/structure/blob/node/adjustcolors(var/a_color) - overlays.Cut() - color = null - var/image/I = new('icons/mob/blob.dmi', "blob") - I.color = a_color - src.overlays += I - var/image/C = new('icons/mob/blob.dmi', "blob_node_overlay") - src.overlays += C - -/obj/structure/blob/node/Destroy() - blob_nodes -= src - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/structure/blob/node/Life(seconds, times_fired) - if(overmind) - for(var/i = 1; i < 8; i += i) - Pulse(5, i, overmind.blob_reagent_datum.color) - else - for(var/i = 1; i < 8; i += i) - Pulse(5, i, color) - obj_integrity = min(max_integrity, obj_integrity + 1) - color = null \ No newline at end of file +/obj/structure/blob/node + name = "blob node" + icon = 'icons/mob/blob.dmi' + icon_state = "blank_blob" + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 65, "acid" = 90) + point_return = 18 + +/obj/structure/blob/node/New(loc, var/h = 100) + blob_nodes += src + START_PROCESSING(SSobj, src) + ..(loc, h) + +/obj/structure/blob/node/adjustcolors(var/a_color) + overlays.Cut() + color = null + var/image/I = new('icons/mob/blob.dmi', "blob") + I.color = a_color + src.overlays += I + var/image/C = new('icons/mob/blob.dmi', "blob_node_overlay") + src.overlays += C + +/obj/structure/blob/node/Destroy() + blob_nodes -= src + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/structure/blob/node/Life(seconds, times_fired) + if(overmind) + for(var/i = 1; i < 8; i += i) + Pulse(5, i, overmind.blob_reagent_datum.color) + else + for(var/i = 1; i < 8; i += i) + Pulse(5, i, color) + obj_integrity = min(max_integrity, obj_integrity + 1) + color = null diff --git a/code/game/gamemodes/blob/blobs/shield.dm b/code/game/gamemodes/blob/blobs/shield.dm index 9e841131b78..04e9f6d677f 100644 --- a/code/game/gamemodes/blob/blobs/shield.dm +++ b/code/game/gamemodes/blob/blobs/shield.dm @@ -1,53 +1,53 @@ -/obj/structure/blob/shield - name = "strong blob" - icon = 'icons/mob/blob.dmi' - icon_state = "blob_shield" - desc = "Some blob creature thingy" - max_integrity = 150 - brute_resist = 0.25 - explosion_block = 3 - atmosblock = TRUE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - -/obj/structure/blob/shield/core - point_return = 0 - -/obj/structure/blob/shield/update_icon() - ..() - if(obj_integrity < max_integrity * 0.5) - icon_state = "[initial(icon_state)]_damaged" - name = "weakened [initial(name)]" - desc = "A wall of twitching tendrils." - atmosblock = FALSE - else - icon_state = initial(icon_state) - name = initial(name) - desc = initial(desc) - atmosblock = TRUE - air_update_turf(1) - -/obj/structure/blob/shield/CanPass(atom/movable/mover, turf/target, height=0) - if(istype(mover) && mover.checkpass(PASSBLOB)) return 1 - return 0 - -/obj/structure/blob/shield/reflective - name = "reflective blob" - desc = "A solid wall of slightly twitching tendrils with a reflective glow." - icon_state = "blob_glow" - max_integrity = 100 - brute_resist = 0.5 - explosion_block = 2 - point_return = 9 - flags_2 = CHECK_RICOCHET_2 - -/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/projectile/P) - var/turf/p_turf = get_turf(P) - var/face_direction = get_dir(src, p_turf) - var/face_angle = dir2angle(face_direction) - var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) - if(abs(incidence_s) > 90 && abs(incidence_s) < 270) - return FALSE - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.setAngle(new_angle_s) - visible_message("[P] reflects off [src]!") - return TRUE \ No newline at end of file +/obj/structure/blob/shield + name = "strong blob" + icon = 'icons/mob/blob.dmi' + icon_state = "blob_shield" + desc = "Some blob creature thingy" + max_integrity = 150 + brute_resist = 0.25 + explosion_block = 3 + atmosblock = TRUE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + +/obj/structure/blob/shield/core + point_return = 0 + +/obj/structure/blob/shield/update_icon() + ..() + if(obj_integrity < max_integrity * 0.5) + icon_state = "[initial(icon_state)]_damaged" + name = "weakened [initial(name)]" + desc = "A wall of twitching tendrils." + atmosblock = FALSE + else + icon_state = initial(icon_state) + name = initial(name) + desc = initial(desc) + atmosblock = TRUE + air_update_turf(1) + +/obj/structure/blob/shield/CanPass(atom/movable/mover, turf/target, height=0) + if(istype(mover) && mover.checkpass(PASSBLOB)) return 1 + return 0 + +/obj/structure/blob/shield/reflective + name = "reflective blob" + desc = "A solid wall of slightly twitching tendrils with a reflective glow." + icon_state = "blob_glow" + max_integrity = 100 + brute_resist = 0.5 + explosion_block = 2 + point_return = 9 + flags_2 = CHECK_RICOCHET_2 + +/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/projectile/P) + var/turf/p_turf = get_turf(P) + var/face_direction = get_dir(src, p_turf) + var/face_angle = dir2angle(face_direction) + var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) + if(abs(incidence_s) > 90 && abs(incidence_s) < 270) + return FALSE + var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) + P.setAngle(new_angle_s) + visible_message("[P] reflects off [src]!") + return TRUE diff --git a/code/game/gamemodes/blob/blobs/storage.dm b/code/game/gamemodes/blob/blobs/storage.dm index 0f3225e8362..b9052b3601a 100644 --- a/code/game/gamemodes/blob/blobs/storage.dm +++ b/code/game/gamemodes/blob/blobs/storage.dm @@ -13,4 +13,4 @@ /obj/structure/blob/storage/proc/update_max_blob_points(var/new_point_increase) if(overmind) - overmind.max_blob_points += new_point_increase \ No newline at end of file + overmind.max_blob_points += new_point_increase diff --git a/code/game/gamemodes/blob/theblob.dm b/code/game/gamemodes/blob/theblob.dm index 37251cabbf2..b97c8311507 100644 --- a/code/game/gamemodes/blob/theblob.dm +++ b/code/game/gamemodes/blob/theblob.dm @@ -1,242 +1,242 @@ -//I will need to recode parts of this but I am way too tired atm -/obj/structure/blob - name = "blob" - icon = 'icons/mob/blob.dmi' - light_range = 3 - desc = "Some blob creature thingy" - density = 0 - opacity = 0 - anchored = 1 - max_integrity = 30 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - var/point_return = 0 //How many points the blob gets back when it removes a blob of that type. If less than 0, blob cannot be removed. - var/health_timestamp = 0 - var/brute_resist = 0.5 //multiplies brute damage by this - var/fire_resist = 1 //multiplies burn damage by this - var/atmosblock = FALSE //if the blob blocks atmos and heat spread - var/mob/camera/blob/overmind - -/obj/structure/blob/New(loc) - ..() - blobs += src - setDir(pick(cardinal)) - update_icon() - if(atmosblock) - air_update_turf(1) - ConsumeTile() - -/obj/structure/blob/Destroy() - if(atmosblock) - atmosblock = FALSE - air_update_turf(1) - blobs -= src - if(isturf(loc)) //Necessary because Expand() is retarded and spawns a blob and then deletes it - playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) - return ..() - -/obj/structure/blob/BlockSuperconductivity() - return atmosblock - -/obj/structure/blob/CanPass(atom/movable/mover, turf/target, height=0) - if(height==0) - return 1 - if(istype(mover) && mover.checkpass(PASSBLOB)) - return 1 - return 0 - -/obj/structure/blob/CanAtmosPass(turf/T) - return !atmosblock - -/obj/structure/blob/CanAStarPass(ID, dir, caller) - . = 0 - if(ismovableatom(caller)) - var/atom/movable/mover = caller - . = . || mover.checkpass(PASSBLOB) - -/obj/structure/blob/process() - Life() - return - -/obj/structure/blob/blob_act(obj/structure/blob/B) - return - -/obj/structure/blob/proc/Life() - return - -/obj/structure/blob/proc/RegenHealth() - // All blobs heal over time when pulsed, but it has a cool down - if(health_timestamp > world.time) - return 0 - if(obj_integrity < max_integrity) - obj_integrity = min(max_integrity, obj_integrity + 1) - update_icon() - health_timestamp = world.time + 10 // 1 seconds - - -/obj/structure/blob/proc/Pulse(var/pulse = 0, var/origin_dir = 0, var/a_color)//Todo: Fix spaceblob expand - - set background = BACKGROUND_ENABLED - - RegenHealth() - - if(run_action())//If we can do something here then we dont need to pulse more - return - - if(pulse > 30) - return//Inf loop check - - //Looking for another blob to pulse - var/list/dirs = list(1,2,4,8) - dirs.Remove(origin_dir)//Dont pulse the guy who pulsed us - for(var/i = 1 to 4) - if(!dirs.len) break - var/dirn = pick(dirs) - dirs.Remove(dirn) - var/turf/T = get_step(src, dirn) - var/obj/structure/blob/B = (locate(/obj/structure/blob) in T) - if(!B) - expand(T,1,a_color)//No blob here so try and expand - return - B.adjustcolors(a_color) - - B.Pulse((pulse+1),get_dir(src.loc,T), a_color) - return - return - - -/obj/structure/blob/proc/run_action() - return 0 - -/obj/structure/blob/proc/ConsumeTile() - for(var/atom/A in loc) - A.blob_act(src) - if(iswallturf(loc)) - loc.blob_act(src) //don't ask how a wall got on top of the core, just eat it - -/obj/structure/blob/proc/expand(var/turf/T = null, var/prob = 1, var/a_color) - if(prob && !prob(obj_integrity)) - return - if(istype(T, /turf/space) && prob(75)) return - if(!T) - var/list/dirs = list(1,2,4,8) - for(var/i = 1 to 4) - var/dirn = pick(dirs) - dirs.Remove(dirn) - T = get_step(src, dirn) - if(!(locate(/obj/structure/blob) in T)) break - else T = null - - if(!T) return 0 - var/obj/structure/blob/normal/B = new /obj/structure/blob/normal(src.loc, min(obj_integrity, 30)) - B.color = a_color - B.density = 1 - if(T.Enter(B,src))//Attempt to move into the tile - B.density = initial(B.density) - B.loc = T - else - T.blob_act()//If we cant move in hit the turf - B.loc = null //So we don't play the splat sound, see Destroy() - qdel(B) - - for(var/atom/A in T)//Hit everything in the turf - A.blob_act(src) - return 1 - -/obj/structure/blob/Crossed(var/mob/living/L, oldloc) - ..() - L.blob_act(src) - -/obj/structure/blob/tesla_act(power) - ..() - take_damage(power / 400, BURN, "energy") - -/obj/structure/blob/hulk_damage() - return 15 - -/obj/structure/blob/attack_animal(mob/living/simple_animal/M) - if(ROLE_BLOB in M.faction) //sorry, but you can't kill the blob as a blobbernaut - return - ..() - -/obj/structure/blob/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, TRUE) - else - playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/structure/blob/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - switch(damage_type) - if(BRUTE) - damage_amount *= brute_resist - if(BURN) - damage_amount *= fire_resist - if(CLONE) - else - return 0 - var/armor_protection = 0 - if(damage_flag) - armor_protection = armor[damage_flag] - damage_amount = round(damage_amount * (100 - armor_protection)*0.01, 0.1) - if(overmind && damage_flag) - damage_amount = overmind.blob_reagent_datum.damage_reaction(src, damage_amount, damage_type, damage_flag) - return damage_amount - -/obj/structure/blob/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(. && obj_integrity > 0) - update_icon() - -/obj/structure/blob/proc/change_to(var/type) - if(!ispath(type)) - error("[type] is an invalid type for the blob.") - var/obj/structure/blob/B = new type(src.loc) - if(!istype(type, /obj/structure/blob/core) || !istype(type, /obj/structure/blob/node)) - B.color = color - else - B.adjustcolors(color) - qdel(src) - -/obj/structure/blob/proc/adjustcolors(var/a_color) - if(a_color) - color = a_color - return - -/obj/structure/blob/examine(mob/user) - . = ..() - . += "It looks like it's made of [get_chem_name()]." - - -/obj/structure/blob/proc/get_chem_name() - for(var/mob/camera/blob/B in GLOB.mob_list) - if(lowertext(B.blob_reagent_datum.color) == lowertext(src.color)) // Goddamit why we use strings for these - return B.blob_reagent_datum.name - return "unknown" - -/obj/structure/blob/normal - icon_state = "blob" - light_range = 0 - obj_integrity = 21 //doesn't start at full health - max_integrity = 25 - brute_resist = 0.25 - -/obj/structure/blob/normal/update_icon() - ..() - if(obj_integrity <= 15) - icon_state = "blob_damaged" - name = "fragile blob" - desc = "A thin lattice of slightly twitching tendrils." - brute_resist = 0.5 - else if(overmind) - icon_state = "blob" - name = "blob" - desc = "A thick wall of writhing tendrils." - brute_resist = 0.25 - else - icon_state = "blob" - name = "dead blob" - desc = "A thick wall of lifeless tendrils." - brute_resist = 0.25 \ No newline at end of file +//I will need to recode parts of this but I am way too tired atm +/obj/structure/blob + name = "blob" + icon = 'icons/mob/blob.dmi' + light_range = 3 + desc = "Some blob creature thingy" + density = 0 + opacity = 0 + anchored = 1 + max_integrity = 30 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + var/point_return = 0 //How many points the blob gets back when it removes a blob of that type. If less than 0, blob cannot be removed. + var/health_timestamp = 0 + var/brute_resist = 0.5 //multiplies brute damage by this + var/fire_resist = 1 //multiplies burn damage by this + var/atmosblock = FALSE //if the blob blocks atmos and heat spread + var/mob/camera/blob/overmind + +/obj/structure/blob/New(loc) + ..() + blobs += src + setDir(pick(cardinal)) + update_icon() + if(atmosblock) + air_update_turf(1) + ConsumeTile() + +/obj/structure/blob/Destroy() + if(atmosblock) + atmosblock = FALSE + air_update_turf(1) + blobs -= src + if(isturf(loc)) //Necessary because Expand() is retarded and spawns a blob and then deletes it + playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) + return ..() + +/obj/structure/blob/BlockSuperconductivity() + return atmosblock + +/obj/structure/blob/CanPass(atom/movable/mover, turf/target, height=0) + if(height==0) + return 1 + if(istype(mover) && mover.checkpass(PASSBLOB)) + return 1 + return 0 + +/obj/structure/blob/CanAtmosPass(turf/T) + return !atmosblock + +/obj/structure/blob/CanAStarPass(ID, dir, caller) + . = 0 + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSBLOB) + +/obj/structure/blob/process() + Life() + return + +/obj/structure/blob/blob_act(obj/structure/blob/B) + return + +/obj/structure/blob/proc/Life() + return + +/obj/structure/blob/proc/RegenHealth() + // All blobs heal over time when pulsed, but it has a cool down + if(health_timestamp > world.time) + return 0 + if(obj_integrity < max_integrity) + obj_integrity = min(max_integrity, obj_integrity + 1) + update_icon() + health_timestamp = world.time + 10 // 1 seconds + + +/obj/structure/blob/proc/Pulse(var/pulse = 0, var/origin_dir = 0, var/a_color)//Todo: Fix spaceblob expand + + set background = BACKGROUND_ENABLED + + RegenHealth() + + if(run_action())//If we can do something here then we dont need to pulse more + return + + if(pulse > 30) + return//Inf loop check + + //Looking for another blob to pulse + var/list/dirs = list(1,2,4,8) + dirs.Remove(origin_dir)//Dont pulse the guy who pulsed us + for(var/i = 1 to 4) + if(!dirs.len) break + var/dirn = pick(dirs) + dirs.Remove(dirn) + var/turf/T = get_step(src, dirn) + var/obj/structure/blob/B = (locate(/obj/structure/blob) in T) + if(!B) + expand(T,1,a_color)//No blob here so try and expand + return + B.adjustcolors(a_color) + + B.Pulse((pulse+1),get_dir(src.loc,T), a_color) + return + return + + +/obj/structure/blob/proc/run_action() + return 0 + +/obj/structure/blob/proc/ConsumeTile() + for(var/atom/A in loc) + A.blob_act(src) + if(iswallturf(loc)) + loc.blob_act(src) //don't ask how a wall got on top of the core, just eat it + +/obj/structure/blob/proc/expand(var/turf/T = null, var/prob = 1, var/a_color) + if(prob && !prob(obj_integrity)) + return + if(istype(T, /turf/space) && prob(75)) return + if(!T) + var/list/dirs = list(1,2,4,8) + for(var/i = 1 to 4) + var/dirn = pick(dirs) + dirs.Remove(dirn) + T = get_step(src, dirn) + if(!(locate(/obj/structure/blob) in T)) break + else T = null + + if(!T) return 0 + var/obj/structure/blob/normal/B = new /obj/structure/blob/normal(src.loc, min(obj_integrity, 30)) + B.color = a_color + B.density = 1 + if(T.Enter(B,src))//Attempt to move into the tile + B.density = initial(B.density) + B.loc = T + else + T.blob_act()//If we cant move in hit the turf + B.loc = null //So we don't play the splat sound, see Destroy() + qdel(B) + + for(var/atom/A in T)//Hit everything in the turf + A.blob_act(src) + return 1 + +/obj/structure/blob/Crossed(var/mob/living/L, oldloc) + ..() + L.blob_act(src) + +/obj/structure/blob/tesla_act(power) + ..() + take_damage(power / 400, BURN, "energy") + +/obj/structure/blob/hulk_damage() + return 15 + +/obj/structure/blob/attack_animal(mob/living/simple_animal/M) + if(ROLE_BLOB in M.faction) //sorry, but you can't kill the blob as a blobbernaut + return + ..() + +/obj/structure/blob/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/blob/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + switch(damage_type) + if(BRUTE) + damage_amount *= brute_resist + if(BURN) + damage_amount *= fire_resist + if(CLONE) + else + return 0 + var/armor_protection = 0 + if(damage_flag) + armor_protection = armor[damage_flag] + damage_amount = round(damage_amount * (100 - armor_protection)*0.01, 0.1) + if(overmind && damage_flag) + damage_amount = overmind.blob_reagent_datum.damage_reaction(src, damage_amount, damage_type, damage_flag) + return damage_amount + +/obj/structure/blob/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(. && obj_integrity > 0) + update_icon() + +/obj/structure/blob/proc/change_to(var/type) + if(!ispath(type)) + error("[type] is an invalid type for the blob.") + var/obj/structure/blob/B = new type(src.loc) + if(!istype(type, /obj/structure/blob/core) || !istype(type, /obj/structure/blob/node)) + B.color = color + else + B.adjustcolors(color) + qdel(src) + +/obj/structure/blob/proc/adjustcolors(var/a_color) + if(a_color) + color = a_color + return + +/obj/structure/blob/examine(mob/user) + . = ..() + . += "It looks like it's made of [get_chem_name()]." + + +/obj/structure/blob/proc/get_chem_name() + for(var/mob/camera/blob/B in GLOB.mob_list) + if(lowertext(B.blob_reagent_datum.color) == lowertext(src.color)) // Goddamit why we use strings for these + return B.blob_reagent_datum.name + return "unknown" + +/obj/structure/blob/normal + icon_state = "blob" + light_range = 0 + obj_integrity = 21 //doesn't start at full health + max_integrity = 25 + brute_resist = 0.25 + +/obj/structure/blob/normal/update_icon() + ..() + if(obj_integrity <= 15) + icon_state = "blob_damaged" + name = "fragile blob" + desc = "A thin lattice of slightly twitching tendrils." + brute_resist = 0.5 + else if(overmind) + icon_state = "blob" + name = "blob" + desc = "A thick wall of writhing tendrils." + brute_resist = 0.25 + else + icon_state = "blob" + name = "dead blob" + desc = "A thick wall of lifeless tendrils." + brute_resist = 0.25 diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index 713779af241..afad318a517 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -1,332 +1,332 @@ -#define LING_FAKEDEATH_TIME 400 //40 seconds -#define LING_DEAD_GENETICDAMAGE_HEAL_CAP 50 //The lowest value of geneticdamage handle_changeling() can take it to while dead. -#define LING_ABSORB_RECENT_SPEECH 8 //The amount of recent spoken lines to gain on absorbing a mob - -var/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega") - -/datum/game_mode - var/list/datum/mind/changelings = list() - -/datum/game_mode/changeling - name = "changeling" - config_tag = "changeling" - restricted_jobs = list("AI", "Cyborg") - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer") - protected_species = list("Machine") - required_players = 15 - required_enemies = 1 - recommended_enemies = 4 - - var/const/prob_int_murder_target = 50 // intercept names the assassination target half the time - var/const/prob_right_murder_target_l = 25 // lower bound on probability of naming right assassination target - var/const/prob_right_murder_target_h = 50 // upper bound on probability of naimg the right assassination target - - var/const/prob_int_item = 50 // intercept names the theft target half the time - var/const/prob_right_item_l = 25 // lower bound on probability of naming right theft target - var/const/prob_right_item_h = 50 // upper bound on probability of naming the right theft target - - var/const/prob_int_sab_target = 50 // intercept names the sabotage target half the time - var/const/prob_right_sab_target_l = 25 // lower bound on probability of naming right sabotage target - var/const/prob_right_sab_target_h = 50 // upper bound on probability of naming right sabotage target - - var/const/prob_right_killer_l = 25 //lower bound on probability of naming the right operative - var/const/prob_right_killer_h = 50 //upper bound on probability of naming the right operative - var/const/prob_right_objective_l = 25 //lower bound on probability of determining the objective correctly - var/const/prob_right_objective_h = 50 //upper bound on probability of determining the objective correctly - - var/changeling_amount = 4 - -/datum/game_mode/changeling/announce() - to_chat(world, "The current game mode is - Changeling!") - to_chat(world, "There are alien changelings on the station. Do not let the changelings succeed!") - -/datum/game_mode/changeling/pre_setup() - if(config.protect_roles_from_antagonist) - restricted_jobs += protected_jobs - - var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) - - changeling_amount = 1 + round(num_players() / 10) - - if(possible_changelings.len>0) - for(var/i = 0, i < changeling_amount, i++) - if(!possible_changelings.len) break - var/datum/mind/changeling = pick(possible_changelings) - possible_changelings -= changeling - changelings += changeling - changeling.restricted_roles = restricted_jobs - modePlayer += changelings - changeling.special_role = SPECIAL_ROLE_CHANGELING - ..() - return 1 - else - return 0 - -/datum/game_mode/changeling/post_setup() - for(var/datum/mind/changeling in changelings) - grant_changeling_powers(changeling.current) - forge_changeling_objectives(changeling) - greet_changeling(changeling) - update_change_icons_added(changeling) - - ..() - -/datum/game_mode/proc/forge_changeling_objectives(datum/mind/changeling) - //OBJECTIVES - Always absorb 5 genomes, plus random traitor objectives. - //If they have two objectives as well as absorb, they must survive rather than escape - //No escape alone because changelings aren't suited for it and it'd probably just lead to rampant robusting - //If it seems like they'd be able to do it in play, add a 10% chance to have to escape alone - - var/datum/objective/absorb/absorb_objective = new - absorb_objective.owner = changeling - absorb_objective.gen_amount_goal(6, 8) - changeling.objectives += absorb_objective - - if(prob(60)) - var/datum/objective/steal/steal_objective = new - steal_objective.owner = changeling - steal_objective.find_target() - changeling.objectives += steal_objective - else - var/datum/objective/debrain/debrain_objective = new - debrain_objective.owner = changeling - debrain_objective.find_target() - changeling.objectives += debrain_objective - - var/list/active_ais = active_ais() - if(active_ais.len && prob(4)) // Leaving this at a flat chance for now, problems with the num_players() proc due to latejoin antags. - var/datum/objective/destroy/destroy_objective = new - destroy_objective.owner = changeling - destroy_objective.find_target() - changeling.objectives += destroy_objective - else - var/datum/objective/assassinate/kill_objective = new - kill_objective.owner = changeling - kill_objective.find_target() - changeling.objectives += kill_objective - - if(!(locate(/datum/objective/escape) in changeling.objectives)) - var/datum/objective/escape/escape_with_identity/identity_theft = new - identity_theft.owner = changeling - identity_theft.target = kill_objective.target - if(identity_theft.target && identity_theft.target.current) - identity_theft.target_real_name = kill_objective.target.current.real_name //Whoops, forgot this. - var/mob/living/carbon/human/H = identity_theft.target.current - if(can_absorb_species(H.dna.species)) // For species that can't be absorbed - should default to an escape objective - identity_theft.explanation_text = "Escape on the shuttle or an escape pod with the identity of [identity_theft.target_real_name], the [identity_theft.target.assigned_role] while wearing [identity_theft.target.p_their()] identification card." - changeling.objectives += identity_theft - else - qdel(identity_theft) - - if(!(locate(/datum/objective/escape) in changeling.objectives)) - if(prob(70)) - var/datum/objective/escape/escape_objective = new - escape_objective.owner = changeling - changeling.objectives += escape_objective - else - var/datum/objective/escape/escape_with_identity/identity_theft = new - identity_theft.owner = changeling - identity_theft.find_target() - changeling.objectives += identity_theft - return - -/datum/game_mode/proc/greet_changeling(datum/mind/changeling, you_are=1) - SEND_SOUND(changeling.current, 'sound/ambience/antag/ling_aler.ogg') - if(you_are) - to_chat(changeling.current, "You are a changeling!") - to_chat(changeling.current, "Use say \":g message\" to communicate with your fellow changelings. Remember: you get all of their absorbed DNA if you absorb them.") - to_chat(changeling.current, "You must complete the following tasks:") - if(changeling.current.mind) - if(changeling.current.mind.assigned_role == "Clown") - to_chat(changeling.current, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.") - changeling.current.mutations.Remove(CLUMSY) - var/datum/action/innate/toggle_clumsy/A = new - A.Grant(changeling.current) - var/obj_count = 1 - for(var/datum/objective/objective in changeling.objectives) - to_chat(changeling.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ - return - -/datum/game_mode/proc/remove_changeling(datum/mind/changeling_mind) - if(changeling_mind in changelings) - changelings -= changeling_mind - changeling_mind.current.remove_changeling_powers() - changeling_mind.memory = "" - changeling_mind.special_role = null - if(issilicon(changeling_mind.current)) - to_chat(changeling_mind.current, "You have been robotized!") - to_chat(changeling_mind.current, "You must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") - else - to_chat(changeling_mind.current, "You lose your powers! You are no longer a changeling and are stuck in your current form!") - update_change_icons_removed(changeling_mind) - -/datum/game_mode/proc/update_change_icons_added(datum/mind/changeling) - var/datum/atom_hud/antag/linghud = huds[ANTAG_HUD_CHANGELING] - linghud.join_hud(changeling.current) - set_antag_hud(changeling.current, "hudchangeling") - -/datum/game_mode/proc/update_change_icons_removed(datum/mind/changeling) - var/datum/atom_hud/antag/linghud = huds[ANTAG_HUD_CHANGELING] - linghud.leave_hud(changeling.current) - set_antag_hud(changeling.current, null) - -/datum/game_mode/proc/grant_changeling_powers(mob/living/carbon/changeling_mob) - if(!istype(changeling_mob)) - return - changeling_mob.make_changeling() - -/datum/game_mode/proc/auto_declare_completion_changeling() - if(changelings.len) - var/text = "The changelings were:" - for(var/datum/mind/changeling in changelings) - var/changelingwin = 1 - - text += "
    [changeling.key] was [changeling.name] (" - if(changeling.current) - if(changeling.current.stat == DEAD) - text += "died" - else - text += "survived" - if(changeling.current.real_name != changeling.name) - text += " as [changeling.current.real_name]" - else - text += "body destroyed" - changelingwin = 0 - text += ")" - - //Removed sanity if(changeling) because we -want- a runtime to inform us that the changelings list is incorrect and needs to be fixed. - text += "
    Changeling ID: [changeling.changeling.changelingID]." - text += "
    Genomes Extracted: [changeling.changeling.absorbedcount]" - - if(changeling.objectives.len) - var/count = 1 - for(var/datum/objective/objective in changeling.objectives) - if(objective.check_completion()) - text += "
    Objective #[count]: [objective.explanation_text] Success!" - feedback_add_details("changeling_objective","[objective.type]|SUCCESS") - else - text += "
    Objective #[count]: [objective.explanation_text] Fail." - feedback_add_details("changeling_objective","[objective.type]|FAIL") - changelingwin = 0 - count++ - - if(changelingwin) - text += "
    The changeling was successful!" - feedback_add_details("changeling_success","SUCCESS") - else - text += "
    The changeling has failed." - feedback_add_details("changeling_success","FAIL") - - to_chat(world, text) - - return 1 - -/datum/changeling //stores changeling powers, changeling recharge thingie, changeling absorbed DNA and changeling ID (for changeling hivemind) - var/list/absorbed_dna = list() - var/list/absorbed_languages = list() - var/list/protected_dna = list() //DNA that is not lost when capacity is otherwise full. - var/dna_max = 5 //How many total DNA strands the changeling can store for transformation. - var/absorbedcount = 1 //Would require at least 1 sample to take on the form of a human - var/chem_charges = 20 - var/chem_recharge_rate = 1 - var/chem_recharge_slowdown = 0 - var/chem_storage = 75 - var/sting_range = 2 - var/changelingID = "Changeling" - var/geneticdamage = 0 - var/isabsorbing = 0 - var/islinking = 0 - var/geneticpoints = 10 - var/purchasedpowers = list() - var/mimicing = "" - var/canrespec = FALSE //set to TRUE in absorb.dm - var/changeling_speak = 0 - var/datum/dna/chosen_dna - var/datum/action/changeling/sting/chosen_sting - var/regenerating = FALSE - -/datum/changeling/New(gender=FEMALE) - ..() - var/honorific - if(gender == FEMALE) - honorific = "Ms." - else - honorific = "Mr." - if(possible_changeling_IDs.len) - changelingID = pick(possible_changeling_IDs) - possible_changeling_IDs -= changelingID - changelingID = "[honorific] [changelingID]" - else - changelingID = "[honorific] [rand(1,999)]" - -/datum/changeling/proc/regenerate(mob/living/carbon/the_ling) - if(istype(the_ling)) - if(the_ling.stat == DEAD) - chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), (chem_storage*0.5)) - geneticdamage = max(LING_DEAD_GENETICDAMAGE_HEAL_CAP,geneticdamage-1) - else //not dead? no chem/geneticdamage caps. - chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), chem_storage) - geneticdamage = max(0, geneticdamage-1) - -/datum/changeling/proc/GetDNA(dna_owner) - for(var/datum/dna/DNA in (absorbed_dna + protected_dna)) - if(dna_owner == DNA.real_name) - return DNA - -/datum/changeling/proc/find_dna(datum/dna/tDNA) - for(var/datum/dna/D in (absorbed_dna + protected_dna)) - if(tDNA.unique_enzymes == D.unique_enzymes && tDNA.uni_identity == D.uni_identity && tDNA.species.type == D.species.type) - return D - return null - -/datum/changeling/proc/has_dna(datum/dna/tDNA) - if(find_dna(tDNA)) - return 1 - return 0 - -// A changeling's DNA is "stale" if their current form's DNA is the oldest DNA in a full list -/datum/changeling/proc/using_stale_dna(mob/living/carbon/user) - var/current_dna = find_dna(user.dna) - if(absorbed_dna.len < dna_max) - return 0 // Still more room for DNA - if(!current_dna || !(current_dna in absorbed_dna)) - return 1 // Oops, our current DNA was somehow not absorbed; force a transformation - if(absorbed_dna[1] == current_dna) - return 1 // Oldest DNA is the current DNA - return 0 - -/datum/changeling/proc/trim_dna() - absorbed_dna -= null - if(absorbed_dna.len > dna_max) - absorbed_dna.Cut(1, (absorbed_dna.len - dna_max) + 1) - -/datum/changeling/proc/can_absorb_dna(mob/living/carbon/user, mob/living/carbon/target) - if(using_stale_dna(user))//If our current DNA is the stalest, we gotta ditch it. - to_chat(user, "The DNA we are wearing is stale. Transform and try again.") - return - - if(!target || !target.dna) - to_chat(user, "This creature does not have any DNA.") - return - - var/mob/living/carbon/human/T = target - if(!istype(T) || issmall(T)) - to_chat(user, "[T] is not compatible with our biology.") - return - - if((NOCLONE || SKELETON || HUSK) in T.mutations) - to_chat(user, "DNA of [target] is ruined beyond usability!") - return - - if(NO_DNA in T.dna.species.species_traits) - to_chat(user, "This creature does not have DNA!") - return - - if(has_dna(target.dna)) - to_chat(user, "We already have this DNA in storage!") - - return 1 - -/proc/can_absorb_species(datum/species/S) - return !(NO_DNA in S.species_traits) +#define LING_FAKEDEATH_TIME 400 //40 seconds +#define LING_DEAD_GENETICDAMAGE_HEAL_CAP 50 //The lowest value of geneticdamage handle_changeling() can take it to while dead. +#define LING_ABSORB_RECENT_SPEECH 8 //The amount of recent spoken lines to gain on absorbing a mob + +var/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega") + +/datum/game_mode + var/list/datum/mind/changelings = list() + +/datum/game_mode/changeling + name = "changeling" + config_tag = "changeling" + restricted_jobs = list("AI", "Cyborg") + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician", "Internal Affairs Agent", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer") + protected_species = list("Machine") + required_players = 15 + required_enemies = 1 + recommended_enemies = 4 + + var/const/prob_int_murder_target = 50 // intercept names the assassination target half the time + var/const/prob_right_murder_target_l = 25 // lower bound on probability of naming right assassination target + var/const/prob_right_murder_target_h = 50 // upper bound on probability of naimg the right assassination target + + var/const/prob_int_item = 50 // intercept names the theft target half the time + var/const/prob_right_item_l = 25 // lower bound on probability of naming right theft target + var/const/prob_right_item_h = 50 // upper bound on probability of naming the right theft target + + var/const/prob_int_sab_target = 50 // intercept names the sabotage target half the time + var/const/prob_right_sab_target_l = 25 // lower bound on probability of naming right sabotage target + var/const/prob_right_sab_target_h = 50 // upper bound on probability of naming right sabotage target + + var/const/prob_right_killer_l = 25 //lower bound on probability of naming the right operative + var/const/prob_right_killer_h = 50 //upper bound on probability of naming the right operative + var/const/prob_right_objective_l = 25 //lower bound on probability of determining the objective correctly + var/const/prob_right_objective_h = 50 //upper bound on probability of determining the objective correctly + + var/changeling_amount = 4 + +/datum/game_mode/changeling/announce() + to_chat(world, "The current game mode is - Changeling!") + to_chat(world, "There are alien changelings on the station. Do not let the changelings succeed!") + +/datum/game_mode/changeling/pre_setup() + if(config.protect_roles_from_antagonist) + restricted_jobs += protected_jobs + + var/list/datum/mind/possible_changelings = get_players_for_role(ROLE_CHANGELING) + + changeling_amount = 1 + round(num_players() / 10) + + if(possible_changelings.len>0) + for(var/i = 0, i < changeling_amount, i++) + if(!possible_changelings.len) break + var/datum/mind/changeling = pick(possible_changelings) + possible_changelings -= changeling + changelings += changeling + changeling.restricted_roles = restricted_jobs + modePlayer += changelings + changeling.special_role = SPECIAL_ROLE_CHANGELING + ..() + return 1 + else + return 0 + +/datum/game_mode/changeling/post_setup() + for(var/datum/mind/changeling in changelings) + grant_changeling_powers(changeling.current) + forge_changeling_objectives(changeling) + greet_changeling(changeling) + update_change_icons_added(changeling) + + ..() + +/datum/game_mode/proc/forge_changeling_objectives(datum/mind/changeling) + //OBJECTIVES - Always absorb 5 genomes, plus random traitor objectives. + //If they have two objectives as well as absorb, they must survive rather than escape + //No escape alone because changelings aren't suited for it and it'd probably just lead to rampant robusting + //If it seems like they'd be able to do it in play, add a 10% chance to have to escape alone + + var/datum/objective/absorb/absorb_objective = new + absorb_objective.owner = changeling + absorb_objective.gen_amount_goal(6, 8) + changeling.objectives += absorb_objective + + if(prob(60)) + var/datum/objective/steal/steal_objective = new + steal_objective.owner = changeling + steal_objective.find_target() + changeling.objectives += steal_objective + else + var/datum/objective/debrain/debrain_objective = new + debrain_objective.owner = changeling + debrain_objective.find_target() + changeling.objectives += debrain_objective + + var/list/active_ais = active_ais() + if(active_ais.len && prob(4)) // Leaving this at a flat chance for now, problems with the num_players() proc due to latejoin antags. + var/datum/objective/destroy/destroy_objective = new + destroy_objective.owner = changeling + destroy_objective.find_target() + changeling.objectives += destroy_objective + else + var/datum/objective/assassinate/kill_objective = new + kill_objective.owner = changeling + kill_objective.find_target() + changeling.objectives += kill_objective + + if(!(locate(/datum/objective/escape) in changeling.objectives)) + var/datum/objective/escape/escape_with_identity/identity_theft = new + identity_theft.owner = changeling + identity_theft.target = kill_objective.target + if(identity_theft.target && identity_theft.target.current) + identity_theft.target_real_name = kill_objective.target.current.real_name //Whoops, forgot this. + var/mob/living/carbon/human/H = identity_theft.target.current + if(can_absorb_species(H.dna.species)) // For species that can't be absorbed - should default to an escape objective + identity_theft.explanation_text = "Escape on the shuttle or an escape pod with the identity of [identity_theft.target_real_name], the [identity_theft.target.assigned_role] while wearing [identity_theft.target.p_their()] identification card." + changeling.objectives += identity_theft + else + qdel(identity_theft) + + if(!(locate(/datum/objective/escape) in changeling.objectives)) + if(prob(70)) + var/datum/objective/escape/escape_objective = new + escape_objective.owner = changeling + changeling.objectives += escape_objective + else + var/datum/objective/escape/escape_with_identity/identity_theft = new + identity_theft.owner = changeling + identity_theft.find_target() + changeling.objectives += identity_theft + return + +/datum/game_mode/proc/greet_changeling(datum/mind/changeling, you_are=1) + SEND_SOUND(changeling.current, 'sound/ambience/antag/ling_aler.ogg') + if(you_are) + to_chat(changeling.current, "You are a changeling!") + to_chat(changeling.current, "Use say \":g message\" to communicate with your fellow changelings. Remember: you get all of their absorbed DNA if you absorb them.") + to_chat(changeling.current, "You must complete the following tasks:") + if(changeling.current.mind) + if(changeling.current.mind.assigned_role == "Clown") + to_chat(changeling.current, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.") + changeling.current.mutations.Remove(CLUMSY) + var/datum/action/innate/toggle_clumsy/A = new + A.Grant(changeling.current) + var/obj_count = 1 + for(var/datum/objective/objective in changeling.objectives) + to_chat(changeling.current, "Objective #[obj_count]: [objective.explanation_text]") + obj_count++ + return + +/datum/game_mode/proc/remove_changeling(datum/mind/changeling_mind) + if(changeling_mind in changelings) + changelings -= changeling_mind + changeling_mind.current.remove_changeling_powers() + changeling_mind.memory = "" + changeling_mind.special_role = null + if(issilicon(changeling_mind.current)) + to_chat(changeling_mind.current, "You have been robotized!") + to_chat(changeling_mind.current, "You must obey your silicon laws and master AI above all else. Your objectives will consider you to be dead.") + else + to_chat(changeling_mind.current, "You lose your powers! You are no longer a changeling and are stuck in your current form!") + update_change_icons_removed(changeling_mind) + +/datum/game_mode/proc/update_change_icons_added(datum/mind/changeling) + var/datum/atom_hud/antag/linghud = huds[ANTAG_HUD_CHANGELING] + linghud.join_hud(changeling.current) + set_antag_hud(changeling.current, "hudchangeling") + +/datum/game_mode/proc/update_change_icons_removed(datum/mind/changeling) + var/datum/atom_hud/antag/linghud = huds[ANTAG_HUD_CHANGELING] + linghud.leave_hud(changeling.current) + set_antag_hud(changeling.current, null) + +/datum/game_mode/proc/grant_changeling_powers(mob/living/carbon/changeling_mob) + if(!istype(changeling_mob)) + return + changeling_mob.make_changeling() + +/datum/game_mode/proc/auto_declare_completion_changeling() + if(changelings.len) + var/text = "The changelings were:" + for(var/datum/mind/changeling in changelings) + var/changelingwin = 1 + + text += "
    [changeling.key] was [changeling.name] (" + if(changeling.current) + if(changeling.current.stat == DEAD) + text += "died" + else + text += "survived" + if(changeling.current.real_name != changeling.name) + text += " as [changeling.current.real_name]" + else + text += "body destroyed" + changelingwin = 0 + text += ")" + + //Removed sanity if(changeling) because we -want- a runtime to inform us that the changelings list is incorrect and needs to be fixed. + text += "
    Changeling ID: [changeling.changeling.changelingID]." + text += "
    Genomes Extracted: [changeling.changeling.absorbedcount]" + + if(changeling.objectives.len) + var/count = 1 + for(var/datum/objective/objective in changeling.objectives) + if(objective.check_completion()) + text += "
    Objective #[count]: [objective.explanation_text] Success!" + feedback_add_details("changeling_objective","[objective.type]|SUCCESS") + else + text += "
    Objective #[count]: [objective.explanation_text] Fail." + feedback_add_details("changeling_objective","[objective.type]|FAIL") + changelingwin = 0 + count++ + + if(changelingwin) + text += "
    The changeling was successful!" + feedback_add_details("changeling_success","SUCCESS") + else + text += "
    The changeling has failed." + feedback_add_details("changeling_success","FAIL") + + to_chat(world, text) + + return 1 + +/datum/changeling //stores changeling powers, changeling recharge thingie, changeling absorbed DNA and changeling ID (for changeling hivemind) + var/list/absorbed_dna = list() + var/list/absorbed_languages = list() + var/list/protected_dna = list() //DNA that is not lost when capacity is otherwise full. + var/dna_max = 5 //How many total DNA strands the changeling can store for transformation. + var/absorbedcount = 1 //Would require at least 1 sample to take on the form of a human + var/chem_charges = 20 + var/chem_recharge_rate = 1 + var/chem_recharge_slowdown = 0 + var/chem_storage = 75 + var/sting_range = 2 + var/changelingID = "Changeling" + var/geneticdamage = 0 + var/isabsorbing = 0 + var/islinking = 0 + var/geneticpoints = 10 + var/purchasedpowers = list() + var/mimicing = "" + var/canrespec = FALSE //set to TRUE in absorb.dm + var/changeling_speak = 0 + var/datum/dna/chosen_dna + var/datum/action/changeling/sting/chosen_sting + var/regenerating = FALSE + +/datum/changeling/New(gender=FEMALE) + ..() + var/honorific + if(gender == FEMALE) + honorific = "Ms." + else + honorific = "Mr." + if(possible_changeling_IDs.len) + changelingID = pick(possible_changeling_IDs) + possible_changeling_IDs -= changelingID + changelingID = "[honorific] [changelingID]" + else + changelingID = "[honorific] [rand(1,999)]" + +/datum/changeling/proc/regenerate(mob/living/carbon/the_ling) + if(istype(the_ling)) + if(the_ling.stat == DEAD) + chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), (chem_storage*0.5)) + geneticdamage = max(LING_DEAD_GENETICDAMAGE_HEAL_CAP,geneticdamage-1) + else //not dead? no chem/geneticdamage caps. + chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), chem_storage) + geneticdamage = max(0, geneticdamage-1) + +/datum/changeling/proc/GetDNA(dna_owner) + for(var/datum/dna/DNA in (absorbed_dna + protected_dna)) + if(dna_owner == DNA.real_name) + return DNA + +/datum/changeling/proc/find_dna(datum/dna/tDNA) + for(var/datum/dna/D in (absorbed_dna + protected_dna)) + if(tDNA.unique_enzymes == D.unique_enzymes && tDNA.uni_identity == D.uni_identity && tDNA.species.type == D.species.type) + return D + return null + +/datum/changeling/proc/has_dna(datum/dna/tDNA) + if(find_dna(tDNA)) + return 1 + return 0 + +// A changeling's DNA is "stale" if their current form's DNA is the oldest DNA in a full list +/datum/changeling/proc/using_stale_dna(mob/living/carbon/user) + var/current_dna = find_dna(user.dna) + if(absorbed_dna.len < dna_max) + return 0 // Still more room for DNA + if(!current_dna || !(current_dna in absorbed_dna)) + return 1 // Oops, our current DNA was somehow not absorbed; force a transformation + if(absorbed_dna[1] == current_dna) + return 1 // Oldest DNA is the current DNA + return 0 + +/datum/changeling/proc/trim_dna() + absorbed_dna -= null + if(absorbed_dna.len > dna_max) + absorbed_dna.Cut(1, (absorbed_dna.len - dna_max) + 1) + +/datum/changeling/proc/can_absorb_dna(mob/living/carbon/user, mob/living/carbon/target) + if(using_stale_dna(user))//If our current DNA is the stalest, we gotta ditch it. + to_chat(user, "The DNA we are wearing is stale. Transform and try again.") + return + + if(!target || !target.dna) + to_chat(user, "This creature does not have any DNA.") + return + + var/mob/living/carbon/human/T = target + if(!istype(T) || issmall(T)) + to_chat(user, "[T] is not compatible with our biology.") + return + + if((NOCLONE || SKELETON || HUSK) in T.mutations) + to_chat(user, "DNA of [target] is ruined beyond usability!") + return + + if(NO_DNA in T.dna.species.species_traits) + to_chat(user, "This creature does not have DNA!") + return + + if(has_dna(target.dna)) + to_chat(user, "We already have this DNA in storage!") + + return 1 + +/proc/can_absorb_species(datum/species/S) + return !(NO_DNA in S.species_traits) diff --git a/code/game/gamemodes/changeling/powers/lesserform.dm b/code/game/gamemodes/changeling/powers/lesserform.dm index 0dd112b66dd..ceab9e02cd3 100644 --- a/code/game/gamemodes/changeling/powers/lesserform.dm +++ b/code/game/gamemodes/changeling/powers/lesserform.dm @@ -33,4 +33,4 @@ HF.Grant(user) feedback_add_details("changeling_powers","LF") - return 1 \ No newline at end of file + return 1 diff --git a/code/game/gamemodes/changeling/powers/panacea.dm b/code/game/gamemodes/changeling/powers/panacea.dm index 6e6d69cc1dd..ae675ac5ad6 100644 --- a/code/game/gamemodes/changeling/powers/panacea.dm +++ b/code/game/gamemodes/changeling/powers/panacea.dm @@ -41,4 +41,4 @@ D.cure() feedback_add_details("changeling_powers","AP") - return 1 \ No newline at end of file + return 1 diff --git a/code/game/gamemodes/changeling/powers/transform.dm b/code/game/gamemodes/changeling/powers/transform.dm index 417fbaa052d..afad76299fb 100644 --- a/code/game/gamemodes/changeling/powers/transform.dm +++ b/code/game/gamemodes/changeling/powers/transform.dm @@ -32,4 +32,4 @@ if(!chosen_name) return var/datum/dna/chosen_dna = GetDNA(chosen_name) - return chosen_dna \ No newline at end of file + return chosen_dna diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm index 551cfdc05c6..f163b2a4c04 100644 --- a/code/game/gamemodes/changeling/traitor_chan.dm +++ b/code/game/gamemodes/changeling/traitor_chan.dm @@ -41,4 +41,4 @@ greet_changeling(changeling) update_change_icons_added(changeling) ..() - return \ No newline at end of file + return diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm index 39e3c5b7d3f..2a42d531f0f 100644 --- a/code/game/gamemodes/cult/cult.dm +++ b/code/game/gamemodes/cult/cult.dm @@ -1,396 +1,396 @@ -var/global/list/all_cults = list() - -/datum/game_mode - var/list/datum/mind/cult = list() - -/proc/iscultist(mob/living/M as mob) - return istype(M) && M.mind && SSticker && SSticker.mode && (M.mind in SSticker.mode.cult) - - -/proc/is_convertable_to_cult(datum/mind/mind) - if(!mind) - return FALSE - if(!mind.current) - return FALSE - if(iscultist(mind.current)) - return TRUE //If they're already in the cult, assume they are convertable - if(ishuman(mind.current) && (mind.assigned_role in list("Captain", "Chaplain"))) - return FALSE - if(ishuman(mind.current)) - var/mob/living/carbon/human/H = mind.current - if(ismindshielded(H)) //mindshield protects against conversions unless removed - return FALSE -// if(mind.offstation_role) cant convert offstation roles such as ghost spawns -// return FALSE Commented out until we can figure out why offstation_role is getting set to TRUE on normal crew - if(issilicon(mind.current)) - return FALSE //can't convert machines, that's ratvar's thing - if(isguardian(mind.current)) - var/mob/living/simple_animal/hostile/guardian/G = mind.current - if(!iscultist(G.summoner)) - return FALSE //can't convert it unless the owner is converted - if(isgolem(mind.current)) - return FALSE - return TRUE - -/proc/is_sacrifice_target(datum/mind/mind) - if(SSticker.mode.name == "cult") - var/datum/game_mode/cult/cult_mode = SSticker.mode - if(mind == cult_mode.sacrifice_target) - return 1 - return 0 - -/datum/game_mode/cult - name = "cult" - config_tag = "cult" - restricted_jobs = list("Chaplain","AI", "Cyborg", "Internal Affairs Agent", "Security Officer", "Warden", "Detective", "Security Pod Pilot", "Head of Security", "Captain", "Head of Personnel", "Blueshield", "Nanotrasen Representative", "Magistrate", "Brig Physician", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer") - protected_jobs = list() - required_players = 30 - required_enemies = 3 - recommended_enemies = 4 - - var/datum/mind/sacrifice_target = null - var/finished = 0 - - - var/list/objectives = list() - - var/eldergod = 1 //for the summon god objective - var/demons_summoned = 0 - - var/acolytes_needed = 4 //for the survive objective - base number of acolytes, increased by 1 for every 10 players - var/const/min_cultists_to_start = 3 - var/const/max_cultists_to_start = 4 - var/acolytes_survived = 0 - - var/narsie_condition_cleared = 0 //allows Nar-Sie to be summonned during cult rounds. set to 1 once the cult reaches the second phase. - var/current_objective = 1 //equals the number of cleared objectives + 1 - var/prenarsie_objectives = 2 //how many objectives at most before the cult gets to summon narsie - var/list/bloody_floors = list() - var/spilltarget = 100 //how many floor tiles must be covered in blood to complete the bloodspill objective - var/convert_target = 0 //how many members the cult needs to reach to complete the convert objective - var/harvested = 0 - - var/list/sacrificed = list() //contains the mind of the sacrifice target ONCE the sacrifice objective has been completed - var/mass_convert = 0 //set to 1 if the convert objective has been accomplised once that round - var/spilled_blood = 0 //set to 1 if the bloodspill objective has been accomplised once that round - var/max_spilled_blood = 0 //highest quantity of blood covered tiles during the round - var/bonus = 0 //set to 1 if the cult has completed the bonus (third phase) objective (harvest, hijack, massacre) - - var/harvest_target = 10 - var/massacre_target = 5 - - var/escaped_shuttle = 0 - var/escaped_pod = 0 - var/survivors = 0 - -/datum/game_mode/cult/announce() - to_chat(world, "The current game mode is - Cult!") - to_chat(world, "Some crewmembers are attempting to start a cult!
    \nCultists - complete your objectives. Convert crewmembers to your cause by using the convert rune. Remember - there is no you, there is only the cult.
    \nPersonnel - Do not let the cult succeed in its mission. Brainwashing them with the chaplain's bible reverts them to whatever CentComm-allowed faith they had.
    ") - - -/datum/game_mode/cult/pre_setup() - if(config.protect_roles_from_antagonist) - restricted_jobs += protected_jobs - ..() - var/list/cultists_possible = get_players_for_role(ROLE_CULTIST) - - for(var/cultists_number = 1 to max_cultists_to_start) - if(!cultists_possible.len) - break - var/datum/mind/cultist = pick(cultists_possible) - cultists_possible -= cultist - cult += cultist - cultist.restricted_roles = restricted_jobs - cultist.special_role = SPECIAL_ROLE_CULTIST - ..() - return (cult.len>0) - - -/datum/game_mode/cult/post_setup() - modePlayer += cult - acolytes_needed = acolytes_needed + round((num_players_started() / 10)) - - if(!summon_spots.len) - while(summon_spots.len < SUMMON_POSSIBILITIES) - var/area/summon = pick(return_sorted_areas() - summon_spots) - if(summon && is_station_level(summon.z) && summon.valid_territory) - summon_spots += summon - - for(var/datum/mind/cult_mind in cult) - SEND_SOUND(cult_mind.current, 'sound/ambience/antag/bloodcult.ogg') - equip_cultist(cult_mind.current) - cult_mind.current.faction |= "cult" - var/datum/action/innate/cultcomm/C = new() - C.Grant(cult_mind.current) - update_cult_icons_added(cult_mind) - to_chat(cult_mind.current, "You catch a glimpse of the Realm of [SSticker.cultdat.entity_name], [SSticker.cultdat.entity_title3]. You now see how flimsy the world is, you see that it should be open to the knowledge of [SSticker.cultdat.entity_name].") - - first_phase() - - ..() - - -/datum/game_mode/cult/proc/memorize_cult_objectives(datum/mind/cult_mind) - for(var/obj_count in 1 to objectives.len) - var/explanation - switch(objectives[obj_count]) - if("survive") - explanation = "Our knowledge must live on. Make sure at least [acolytes_needed] acolytes escape on the shuttle to spread their work on an another station." - if("convert") - explanation = "We must increase our influence before we can summon [SSticker.cultdat.entity_name], Convert [convert_target] crew members. Take it slowly to avoid raising suspicions." - if("bloodspill") - explanation = "We must prepare this place for [SSticker.cultdat.entity_title1]'s coming. Spill blood and gibs over [spilltarget] floor tiles." - if("sacrifice") - if(sacrifice_target) - explanation = "Sacrifice [sacrifice_target.current.real_name], the [sacrifice_target.assigned_role]. You will need the sacrifice rune and three acolytes to do so." - else - explanation = "Free objective." - if("eldergod") - explanation = "Summon [SSticker.cultdat.entity_name] by invoking the 'Tear Reality' rune.The summoning can only be accomplished in [english_list(summon_spots)] - where the veil is weak enough for the ritual to begin." - to_chat(cult_mind.current, "Objective #[obj_count]: [explanation]") - cult_mind.memory += "Objective #[obj_count]: [explanation]
    " - - -/datum/game_mode/proc/equip_cultist(mob/living/carbon/human/mob) - if(!istype(mob)) - return - - if(mob.mind) - if(mob.mind.assigned_role == "Clown") - to_chat(mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") - mob.mutations.Remove(CLUMSY) - var/datum/action/innate/toggle_clumsy/A = new - A.Grant(mob) - var/obj/item/paper/talisman/supply/T = new(mob) - var/list/slots = list ( - "backpack" = slot_in_backpack, - "left pocket" = slot_l_store, - "right pocket" = slot_r_store, - "left hand" = slot_l_hand, - "right hand" = slot_r_hand, - ) - var/where = mob.equip_in_one_of_slots(T, slots) - if(!where) - to_chat(mob, "Unfortunately, you weren't able to get a talisman. This is very bad and you should adminhelp immediately.") - else - to_chat(mob, "You have a talisman in your [where], one that will help you start the cult on this station. Use it well and remember - there are others.") - mob.update_icons() - return 1 - - -/datum/game_mode/proc/add_cultist(datum/mind/cult_mind) //BASE - if(!istype(cult_mind)) - return 0 - var/datum/game_mode/cult/cult_mode = SSticker.mode - if(!(cult_mind in cult)) - cult += cult_mind - cult_mind.current.faction |= "cult" - var/datum/action/innate/cultcomm/C = new() - 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!") - 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) - cult_mode.memorize_cult_objectives(cult_mind) - if(GAMEMODE_IS_CULT) - cult_mode.check_numbers() - return 1 - -/datum/game_mode/proc/remove_cultist(datum/mind/cult_mind, show_message = 1) - if(cult_mind in cult) - cult -= cult_mind - to_chat(cult_mind.current, "An unfamiliar white light flashes through your mind, cleansing the taint of the dark-one and the memories of your time as his servant with it.") - cult_mind.current.faction -= "cult" - cult_mind.memory = "" - cult_mind.special_role = null - for(var/datum/action/innate/cultcomm/C in cult_mind.current.actions) - qdel(C) - update_cult_icons_removed(cult_mind) - if(show_message) - for(var/mob/M in viewers(cult_mind.current)) - to_chat(M, "[cult_mind.current] looks like [cult_mind.current.p_they()] just reverted to [cult_mind.current.p_their()] old faith!") - - -/datum/game_mode/proc/update_cult_icons_added(datum/mind/cult_mind) - var/datum/atom_hud/antag/culthud = huds[ANTAG_HUD_CULT] - culthud.join_hud(cult_mind.current) - set_antag_hud(cult_mind.current, "hudcultist") - - -/datum/game_mode/proc/update_cult_icons_removed(datum/mind/cult_mind) - var/datum/atom_hud/antag/culthud = huds[ANTAG_HUD_CULT] - culthud.leave_hud(cult_mind.current) - set_antag_hud(cult_mind.current, null) - -/datum/game_mode/proc/update_cult_comms_added(datum/mind/cult_mind) - var/datum/action/innate/cultcomm/C = new() - C.Grant(cult_mind.current) - -/datum/game_mode/cult/proc/get_unconvertables() - var/list/ucs = list() - for(var/mob/living/carbon/human/player in GLOB.player_list) - if(!is_convertable_to_cult(player.mind)) - ucs += player.mind - return ucs - - -/datum/game_mode/cult/proc/check_cult_victory() - var/cult_fail = 0 - if(objectives.Find("survive")) - cult_fail += check_survive() //the proc returns 1 if there are not enough cultists on the shuttle, 0 otherwise - if(objectives.Find("eldergod")) - cult_fail += eldergod //1 by default, 0 if the elder god has been summoned at least once - if(objectives.Find("slaughter")) - if(!demons_summoned) - cult_fail++ - if(objectives.Find("sacrifice")) - if(sacrifice_target && !(sacrifice_target in sacrificed)) //if the target has been sacrificed, ignore this step. otherwise, add 1 to cult_fail - cult_fail++ - if(objectives.Find("convert")) - if(cult.len < convert_target) - cult_fail++ - if(objectives.Find("bloodspill")) - if(max_spilled_blood < spilltarget) - cult_fail++ - - return cult_fail //if any objectives aren't met, failure - - -/datum/game_mode/cult/proc/check_survive() - acolytes_survived = 0 - for(var/datum/mind/cult_mind in cult) - if(cult_mind.current && cult_mind.current.stat!=2) - var/area/A = get_area(cult_mind.current ) - if( is_type_in_list(A, centcom_areas)) - acolytes_survived++ - else if(A == SSshuttle.emergency.areaInstance && SSshuttle.emergency.mode >= SHUTTLE_ESCAPE) //snowflaked into objectives because shitty bay shuttles had areas to auto-determine this - acolytes_survived++ - - if(acolytes_survived>=acolytes_needed) - return 0 - else - return 1 - - -/atom/proc/cult_log(var/message) - investigate_log(message, "cult") - - -/datum/game_mode/cult/declare_completion() - bonus_check() - - if(!check_cult_victory()) - feedback_set_details("round_end_result","cult win - cult win") - feedback_set("round_end_result",acolytes_survived) - to_chat(world, " The cult wins! It has succeeded in serving its dark masters!") - else - feedback_set_details("round_end_result","cult loss - staff stopped the cult") - feedback_set("round_end_result",acolytes_survived) - to_chat(world, " The staff managed to stop the cult!") - - var/text = "Cultists escaped: [acolytes_survived]" - - if(objectives.len) - text += "
    The cultists' objectives were:" - for(var/obj_count=1, obj_count <= objectives.len, obj_count++) - var/explanation - switch(objectives[obj_count]) - if("survive") - if(!check_survive()) - explanation = "Make sure at least [acolytes_needed] acolytes escape on the shuttle. Success!" - feedback_add_details("cult_objective","cult_survive|SUCCESS|[acolytes_needed]") - else - explanation = "Make sure at least [acolytes_needed] acolytes escape on the shuttle. Fail." - feedback_add_details("cult_objective","cult_survive|FAIL|[acolytes_needed]") - if("sacrifice") - if(sacrifice_target) - if(sacrifice_target in sacrificed) - explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Success!" - feedback_add_details("cult_objective","cult_sacrifice|SUCCESS") - else if(sacrifice_target && sacrifice_target.current) - explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Fail." - feedback_add_details("cult_objective","cult_sacrifice|FAIL") - else - explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Fail (Gibbed)." - feedback_add_details("cult_objective","cult_sacrifice|FAIL|GIBBED") - if("eldergod") - if(!eldergod) - explanation = "Summon [SSticker.cultdat.entity_name]. Success!" - feedback_add_details("cult_objective","cult_narsie|SUCCESS") - else - explanation = "Summon [SSticker.cultdat.entity_name]. Fail." - feedback_add_details("cult_objective","cult_narsie|FAIL") - if("slaughter") - if(demons_summoned) - explanation = "Bring the Slaughter. Success!" - feedback_add_details("cult_objective","cult_demons|SUCCESS") - else - explanation = "Bring the Slaughter. Fail." - feedback_add_details("cult_objective","cult_demons|FAIL") - - if("convert")//convert half the crew - if(cult.len >= convert_target) - explanation = "Convert [convert_target] crewmembers ([cult.len] cultists at round end). Success!" - feedback_add_details("cult_objective","cult_convertion|SUCCESS") - else - explanation = "Convert [convert_target] crewmembers ([cult.len] total cultists). Fail!" - feedback_add_details("cult_objective","cult_convertion|FAIL") - - if("bloodspill")//cover a large portion of the station in blood - if(max_spilled_blood >= spilltarget) - explanation = "Cover [spilltarget] tiles of the station in blood (The peak number of covered tiles was: [max_spilled_blood]). Success!" - feedback_add_details("cult_objective","cult_bloodspill|SUCCESS") - else - explanation = "Cover [spilltarget] tiles of the station in blood (The peak number of covered tiles was: [max_spilled_blood]). Fail!" - feedback_add_details("cult_objective","cult_bloodspill|FAIL") - - if("harvest") - if(harvested > harvest_target) - explanation = "Offer [harvest_target] humans for [SSticker.cultdat.entity_name]'s first meal of the day. ([harvested] sacrificed) Success!" - feedback_add_details("cult_objective","cult_harvest|SUCCESS") - else - explanation = "Offer [harvest_target] humans for [SSticker.cultdat.entity_name]'s first meal of the day. ([harvested] sacrificed) Fail!" - feedback_add_details("cult_objective","cult_harvest|FAIL") - - if("hijack") - if(!escaped_shuttle) - explanation = "Do not let a single non-cultist board the Escape Shuttle. ([escaped_shuttle] escaped on the shuttle) ([escaped_pod] escaped on pods) Success!" - feedback_add_details("cult_objective","cult_hijack|SUCCESS") - else - explanation = "Do not let a single non-cultist board the Escape Shuttle. ([escaped_shuttle] escaped on the shuttle) ([escaped_pod] escaped on pods) Fail!" - feedback_add_details("cult_objective","cult_hijack|FAIL") - - if("massacre") - if(survivors < massacre_target) - explanation = "Massacre the crew until less than [massacre_target] people are left on the station. ([survivors] humans left alive) Success!" - feedback_add_details("cult_objective","cult_massacre|SUCCESS") - else - explanation = "Massacre the crew until less than [massacre_target] people are left on the station. ([survivors] humans left alive) Fail!" - feedback_add_details("cult_objective","cult_massacre|FAIL") - - text += "
    Objective #[obj_count]: [explanation]" - - to_chat(world, text) - ..() - return 1 - - -/datum/game_mode/proc/auto_declare_completion_cult() - if(cult.len || (SSticker && GAMEMODE_IS_CULT)) - var/text = "The cultists were:" - for(var/datum/mind/cultist in cult) - - text += "
    [cultist.key] was [cultist.name] (" - if(cultist.current) - if(cultist.current.stat == DEAD) - text += "died" - else - text += "survived" - if(cultist.current.real_name != cultist.name) - text += " as [cultist.current.real_name]" - else - text += "body destroyed" - text += ")" - - to_chat(world, text) +var/global/list/all_cults = list() + +/datum/game_mode + var/list/datum/mind/cult = list() + +/proc/iscultist(mob/living/M as mob) + return istype(M) && M.mind && SSticker && SSticker.mode && (M.mind in SSticker.mode.cult) + + +/proc/is_convertable_to_cult(datum/mind/mind) + if(!mind) + return FALSE + if(!mind.current) + return FALSE + if(iscultist(mind.current)) + return TRUE //If they're already in the cult, assume they are convertable + if(ishuman(mind.current) && (mind.assigned_role in list("Captain", "Chaplain"))) + return FALSE + if(ishuman(mind.current)) + var/mob/living/carbon/human/H = mind.current + if(ismindshielded(H)) //mindshield protects against conversions unless removed + return FALSE +// if(mind.offstation_role) cant convert offstation roles such as ghost spawns +// return FALSE Commented out until we can figure out why offstation_role is getting set to TRUE on normal crew + if(issilicon(mind.current)) + return FALSE //can't convert machines, that's ratvar's thing + if(isguardian(mind.current)) + var/mob/living/simple_animal/hostile/guardian/G = mind.current + if(!iscultist(G.summoner)) + return FALSE //can't convert it unless the owner is converted + if(isgolem(mind.current)) + return FALSE + return TRUE + +/proc/is_sacrifice_target(datum/mind/mind) + if(SSticker.mode.name == "cult") + var/datum/game_mode/cult/cult_mode = SSticker.mode + if(mind == cult_mode.sacrifice_target) + return 1 + return 0 + +/datum/game_mode/cult + name = "cult" + config_tag = "cult" + restricted_jobs = list("Chaplain","AI", "Cyborg", "Internal Affairs Agent", "Security Officer", "Warden", "Detective", "Security Pod Pilot", "Head of Security", "Captain", "Head of Personnel", "Blueshield", "Nanotrasen Representative", "Magistrate", "Brig Physician", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer") + protected_jobs = list() + required_players = 30 + required_enemies = 3 + recommended_enemies = 4 + + var/datum/mind/sacrifice_target = null + var/finished = 0 + + + var/list/objectives = list() + + var/eldergod = 1 //for the summon god objective + var/demons_summoned = 0 + + var/acolytes_needed = 4 //for the survive objective - base number of acolytes, increased by 1 for every 10 players + var/const/min_cultists_to_start = 3 + var/const/max_cultists_to_start = 4 + var/acolytes_survived = 0 + + var/narsie_condition_cleared = 0 //allows Nar-Sie to be summonned during cult rounds. set to 1 once the cult reaches the second phase. + var/current_objective = 1 //equals the number of cleared objectives + 1 + var/prenarsie_objectives = 2 //how many objectives at most before the cult gets to summon narsie + var/list/bloody_floors = list() + var/spilltarget = 100 //how many floor tiles must be covered in blood to complete the bloodspill objective + var/convert_target = 0 //how many members the cult needs to reach to complete the convert objective + var/harvested = 0 + + var/list/sacrificed = list() //contains the mind of the sacrifice target ONCE the sacrifice objective has been completed + var/mass_convert = 0 //set to 1 if the convert objective has been accomplised once that round + var/spilled_blood = 0 //set to 1 if the bloodspill objective has been accomplised once that round + var/max_spilled_blood = 0 //highest quantity of blood covered tiles during the round + var/bonus = 0 //set to 1 if the cult has completed the bonus (third phase) objective (harvest, hijack, massacre) + + var/harvest_target = 10 + var/massacre_target = 5 + + var/escaped_shuttle = 0 + var/escaped_pod = 0 + var/survivors = 0 + +/datum/game_mode/cult/announce() + to_chat(world, "The current game mode is - Cult!") + to_chat(world, "Some crewmembers are attempting to start a cult!
    \nCultists - complete your objectives. Convert crewmembers to your cause by using the convert rune. Remember - there is no you, there is only the cult.
    \nPersonnel - Do not let the cult succeed in its mission. Brainwashing them with the chaplain's bible reverts them to whatever CentComm-allowed faith they had.
    ") + + +/datum/game_mode/cult/pre_setup() + if(config.protect_roles_from_antagonist) + restricted_jobs += protected_jobs + ..() + var/list/cultists_possible = get_players_for_role(ROLE_CULTIST) + + for(var/cultists_number = 1 to max_cultists_to_start) + if(!cultists_possible.len) + break + var/datum/mind/cultist = pick(cultists_possible) + cultists_possible -= cultist + cult += cultist + cultist.restricted_roles = restricted_jobs + cultist.special_role = SPECIAL_ROLE_CULTIST + ..() + return (cult.len>0) + + +/datum/game_mode/cult/post_setup() + modePlayer += cult + acolytes_needed = acolytes_needed + round((num_players_started() / 10)) + + if(!summon_spots.len) + while(summon_spots.len < SUMMON_POSSIBILITIES) + var/area/summon = pick(return_sorted_areas() - summon_spots) + if(summon && is_station_level(summon.z) && summon.valid_territory) + summon_spots += summon + + for(var/datum/mind/cult_mind in cult) + SEND_SOUND(cult_mind.current, 'sound/ambience/antag/bloodcult.ogg') + equip_cultist(cult_mind.current) + cult_mind.current.faction |= "cult" + var/datum/action/innate/cultcomm/C = new() + C.Grant(cult_mind.current) + update_cult_icons_added(cult_mind) + to_chat(cult_mind.current, "You catch a glimpse of the Realm of [SSticker.cultdat.entity_name], [SSticker.cultdat.entity_title3]. You now see how flimsy the world is, you see that it should be open to the knowledge of [SSticker.cultdat.entity_name].") + + first_phase() + + ..() + + +/datum/game_mode/cult/proc/memorize_cult_objectives(datum/mind/cult_mind) + for(var/obj_count in 1 to objectives.len) + var/explanation + switch(objectives[obj_count]) + if("survive") + explanation = "Our knowledge must live on. Make sure at least [acolytes_needed] acolytes escape on the shuttle to spread their work on an another station." + if("convert") + explanation = "We must increase our influence before we can summon [SSticker.cultdat.entity_name], Convert [convert_target] crew members. Take it slowly to avoid raising suspicions." + if("bloodspill") + explanation = "We must prepare this place for [SSticker.cultdat.entity_title1]'s coming. Spill blood and gibs over [spilltarget] floor tiles." + if("sacrifice") + if(sacrifice_target) + explanation = "Sacrifice [sacrifice_target.current.real_name], the [sacrifice_target.assigned_role]. You will need the sacrifice rune and three acolytes to do so." + else + explanation = "Free objective." + if("eldergod") + explanation = "Summon [SSticker.cultdat.entity_name] by invoking the 'Tear Reality' rune.The summoning can only be accomplished in [english_list(summon_spots)] - where the veil is weak enough for the ritual to begin." + to_chat(cult_mind.current, "Objective #[obj_count]: [explanation]") + cult_mind.memory += "Objective #[obj_count]: [explanation]
    " + + +/datum/game_mode/proc/equip_cultist(mob/living/carbon/human/mob) + if(!istype(mob)) + return + + if(mob.mind) + if(mob.mind.assigned_role == "Clown") + to_chat(mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") + mob.mutations.Remove(CLUMSY) + var/datum/action/innate/toggle_clumsy/A = new + A.Grant(mob) + var/obj/item/paper/talisman/supply/T = new(mob) + var/list/slots = list ( + "backpack" = slot_in_backpack, + "left pocket" = slot_l_store, + "right pocket" = slot_r_store, + "left hand" = slot_l_hand, + "right hand" = slot_r_hand, + ) + var/where = mob.equip_in_one_of_slots(T, slots) + if(!where) + to_chat(mob, "Unfortunately, you weren't able to get a talisman. This is very bad and you should adminhelp immediately.") + else + to_chat(mob, "You have a talisman in your [where], one that will help you start the cult on this station. Use it well and remember - there are others.") + mob.update_icons() + return 1 + + +/datum/game_mode/proc/add_cultist(datum/mind/cult_mind) //BASE + if(!istype(cult_mind)) + return 0 + var/datum/game_mode/cult/cult_mode = SSticker.mode + if(!(cult_mind in cult)) + cult += cult_mind + cult_mind.current.faction |= "cult" + var/datum/action/innate/cultcomm/C = new() + 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!") + 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) + cult_mode.memorize_cult_objectives(cult_mind) + if(GAMEMODE_IS_CULT) + cult_mode.check_numbers() + return 1 + +/datum/game_mode/proc/remove_cultist(datum/mind/cult_mind, show_message = 1) + if(cult_mind in cult) + cult -= cult_mind + to_chat(cult_mind.current, "An unfamiliar white light flashes through your mind, cleansing the taint of the dark-one and the memories of your time as his servant with it.") + cult_mind.current.faction -= "cult" + cult_mind.memory = "" + cult_mind.special_role = null + for(var/datum/action/innate/cultcomm/C in cult_mind.current.actions) + qdel(C) + update_cult_icons_removed(cult_mind) + if(show_message) + for(var/mob/M in viewers(cult_mind.current)) + to_chat(M, "[cult_mind.current] looks like [cult_mind.current.p_they()] just reverted to [cult_mind.current.p_their()] old faith!") + + +/datum/game_mode/proc/update_cult_icons_added(datum/mind/cult_mind) + var/datum/atom_hud/antag/culthud = huds[ANTAG_HUD_CULT] + culthud.join_hud(cult_mind.current) + set_antag_hud(cult_mind.current, "hudcultist") + + +/datum/game_mode/proc/update_cult_icons_removed(datum/mind/cult_mind) + var/datum/atom_hud/antag/culthud = huds[ANTAG_HUD_CULT] + culthud.leave_hud(cult_mind.current) + set_antag_hud(cult_mind.current, null) + +/datum/game_mode/proc/update_cult_comms_added(datum/mind/cult_mind) + var/datum/action/innate/cultcomm/C = new() + C.Grant(cult_mind.current) + +/datum/game_mode/cult/proc/get_unconvertables() + var/list/ucs = list() + for(var/mob/living/carbon/human/player in GLOB.player_list) + if(!is_convertable_to_cult(player.mind)) + ucs += player.mind + return ucs + + +/datum/game_mode/cult/proc/check_cult_victory() + var/cult_fail = 0 + if(objectives.Find("survive")) + cult_fail += check_survive() //the proc returns 1 if there are not enough cultists on the shuttle, 0 otherwise + if(objectives.Find("eldergod")) + cult_fail += eldergod //1 by default, 0 if the elder god has been summoned at least once + if(objectives.Find("slaughter")) + if(!demons_summoned) + cult_fail++ + if(objectives.Find("sacrifice")) + if(sacrifice_target && !(sacrifice_target in sacrificed)) //if the target has been sacrificed, ignore this step. otherwise, add 1 to cult_fail + cult_fail++ + if(objectives.Find("convert")) + if(cult.len < convert_target) + cult_fail++ + if(objectives.Find("bloodspill")) + if(max_spilled_blood < spilltarget) + cult_fail++ + + return cult_fail //if any objectives aren't met, failure + + +/datum/game_mode/cult/proc/check_survive() + acolytes_survived = 0 + for(var/datum/mind/cult_mind in cult) + if(cult_mind.current && cult_mind.current.stat!=2) + var/area/A = get_area(cult_mind.current ) + if( is_type_in_list(A, centcom_areas)) + acolytes_survived++ + else if(A == SSshuttle.emergency.areaInstance && SSshuttle.emergency.mode >= SHUTTLE_ESCAPE) //snowflaked into objectives because shitty bay shuttles had areas to auto-determine this + acolytes_survived++ + + if(acolytes_survived>=acolytes_needed) + return 0 + else + return 1 + + +/atom/proc/cult_log(var/message) + investigate_log(message, "cult") + + +/datum/game_mode/cult/declare_completion() + bonus_check() + + if(!check_cult_victory()) + feedback_set_details("round_end_result","cult win - cult win") + feedback_set("round_end_result",acolytes_survived) + to_chat(world, " The cult wins! It has succeeded in serving its dark masters!") + else + feedback_set_details("round_end_result","cult loss - staff stopped the cult") + feedback_set("round_end_result",acolytes_survived) + to_chat(world, " The staff managed to stop the cult!") + + var/text = "Cultists escaped: [acolytes_survived]" + + if(objectives.len) + text += "
    The cultists' objectives were:" + for(var/obj_count=1, obj_count <= objectives.len, obj_count++) + var/explanation + switch(objectives[obj_count]) + if("survive") + if(!check_survive()) + explanation = "Make sure at least [acolytes_needed] acolytes escape on the shuttle. Success!" + feedback_add_details("cult_objective","cult_survive|SUCCESS|[acolytes_needed]") + else + explanation = "Make sure at least [acolytes_needed] acolytes escape on the shuttle. Fail." + feedback_add_details("cult_objective","cult_survive|FAIL|[acolytes_needed]") + if("sacrifice") + if(sacrifice_target) + if(sacrifice_target in sacrificed) + explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Success!" + feedback_add_details("cult_objective","cult_sacrifice|SUCCESS") + else if(sacrifice_target && sacrifice_target.current) + explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Fail." + feedback_add_details("cult_objective","cult_sacrifice|FAIL") + else + explanation = "Sacrifice [sacrifice_target.name], the [sacrifice_target.assigned_role]. Fail (Gibbed)." + feedback_add_details("cult_objective","cult_sacrifice|FAIL|GIBBED") + if("eldergod") + if(!eldergod) + explanation = "Summon [SSticker.cultdat.entity_name]. Success!" + feedback_add_details("cult_objective","cult_narsie|SUCCESS") + else + explanation = "Summon [SSticker.cultdat.entity_name]. Fail." + feedback_add_details("cult_objective","cult_narsie|FAIL") + if("slaughter") + if(demons_summoned) + explanation = "Bring the Slaughter. Success!" + feedback_add_details("cult_objective","cult_demons|SUCCESS") + else + explanation = "Bring the Slaughter. Fail." + feedback_add_details("cult_objective","cult_demons|FAIL") + + if("convert")//convert half the crew + if(cult.len >= convert_target) + explanation = "Convert [convert_target] crewmembers ([cult.len] cultists at round end). Success!" + feedback_add_details("cult_objective","cult_convertion|SUCCESS") + else + explanation = "Convert [convert_target] crewmembers ([cult.len] total cultists). Fail!" + feedback_add_details("cult_objective","cult_convertion|FAIL") + + if("bloodspill")//cover a large portion of the station in blood + if(max_spilled_blood >= spilltarget) + explanation = "Cover [spilltarget] tiles of the station in blood (The peak number of covered tiles was: [max_spilled_blood]). Success!" + feedback_add_details("cult_objective","cult_bloodspill|SUCCESS") + else + explanation = "Cover [spilltarget] tiles of the station in blood (The peak number of covered tiles was: [max_spilled_blood]). Fail!" + feedback_add_details("cult_objective","cult_bloodspill|FAIL") + + if("harvest") + if(harvested > harvest_target) + explanation = "Offer [harvest_target] humans for [SSticker.cultdat.entity_name]'s first meal of the day. ([harvested] sacrificed) Success!" + feedback_add_details("cult_objective","cult_harvest|SUCCESS") + else + explanation = "Offer [harvest_target] humans for [SSticker.cultdat.entity_name]'s first meal of the day. ([harvested] sacrificed) Fail!" + feedback_add_details("cult_objective","cult_harvest|FAIL") + + if("hijack") + if(!escaped_shuttle) + explanation = "Do not let a single non-cultist board the Escape Shuttle. ([escaped_shuttle] escaped on the shuttle) ([escaped_pod] escaped on pods) Success!" + feedback_add_details("cult_objective","cult_hijack|SUCCESS") + else + explanation = "Do not let a single non-cultist board the Escape Shuttle. ([escaped_shuttle] escaped on the shuttle) ([escaped_pod] escaped on pods) Fail!" + feedback_add_details("cult_objective","cult_hijack|FAIL") + + if("massacre") + if(survivors < massacre_target) + explanation = "Massacre the crew until less than [massacre_target] people are left on the station. ([survivors] humans left alive) Success!" + feedback_add_details("cult_objective","cult_massacre|SUCCESS") + else + explanation = "Massacre the crew until less than [massacre_target] people are left on the station. ([survivors] humans left alive) Fail!" + feedback_add_details("cult_objective","cult_massacre|FAIL") + + text += "
    Objective #[obj_count]: [explanation]" + + to_chat(world, text) + ..() + return 1 + + +/datum/game_mode/proc/auto_declare_completion_cult() + if(cult.len || (SSticker && GAMEMODE_IS_CULT)) + var/text = "The cultists were:" + for(var/datum/mind/cultist in cult) + + text += "
    [cultist.key] was [cultist.name] (" + if(cultist.current) + if(cultist.current.stat == DEAD) + text += "died" + else + text += "survived" + if(cultist.current.real_name != cultist.name) + text += " as [cultist.current.real_name]" + else + text += "body destroyed" + text += ")" + + to_chat(world, text) diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm index 74d7a1889e7..f9ee181356d 100644 --- a/code/game/gamemodes/cult/cult_items.dm +++ b/code/game/gamemodes/cult/cult_items.dm @@ -1,377 +1,377 @@ -/obj/item/melee/cultblade - name = "Cult Blade" - desc = "An arcane weapon wielded by the followers of a cult." - icon_state = "cultblade" - item_state = "cultblade" - w_class = WEIGHT_CLASS_BULKY - force = 30 - throwforce = 10 - sharp = 1 - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - - -/obj/item/melee/cultblade/attack(mob/living/target as mob, mob/living/carbon/human/user as mob) - if(!iscultist(user)) - user.Weaken(5) - user.unEquip(src, 1) - user.visible_message("A powerful force shoves [user] away from [target]!", \ - "\"You shouldn't play with sharp things. You'll poke someone's eye out.\"") - if(ishuman(user)) - var/mob/living/carbon/human/H = user - H.apply_damage(rand(force/2, force), BRUTE, pick("l_arm", "r_arm")) - else - user.adjustBruteLoss(rand(force/2,force)) - return - ..() - -/obj/item/melee/cultblade/pickup(mob/living/user) - . = ..() - if(!iscultist(user)) - to_chat(user, "\"I wouldn't advise that.\"") - to_chat(user, "An overwhelming sense of nausea overpowers you!") - user.Dizzy(120) - - if(HULK in user.mutations) - to_chat(user, "You can't seem to hold the blade properly!") - return FALSE - -/obj/item/melee/cultblade/dagger - name = "sacrificial dagger" - desc = "A strange dagger said to be used by sinister groups for \"preparing\" a corpse before sacrificing it to their dark gods." - icon = 'icons/obj/wizard.dmi' - icon_state = "render" - w_class = WEIGHT_CLASS_SMALL - force = 15 - throwforce = 25 - embed_chance = 75 - -/obj/item/melee/cultblade/dagger/attack(atom/target, mob/living/carbon/human/user) - ..() - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if((H.stat != DEAD) && !(NO_BLOOD in H.dna.species.species_traits)) - H.bleed(50) - -/obj/item/restraints/legcuffs/bola/cult - name = "runed bola" - desc = "A strong bola, bound with dark magic. Throw it to trip and slow your victim." - icon = 'icons/obj/items.dmi' - icon_state = "bola_cult" - breakouttime = 45 - weaken = 1 - -/obj/item/clothing/head/hooded/culthood - name = "cult hood" - icon_state = "culthood" - desc = "A hood worn by the followers of a cult." - flags_inv = HIDEFACE - flags_cover = HEADCOVERSEYES - armor = list(melee = 30, bullet = 10, laser = 5, energy = 5, bomb = 0, bio = 0, rad = 0, fire = 10, acid = 10) - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - - -/obj/item/clothing/head/hooded/culthood/alt - icon_state = "cult_hoodalt" - item_state = "cult_hoodalt" - - -/obj/item/clothing/suit/hooded/cultrobes - name = "cult robes" - desc = "A set of armored robes worn by the followers of a cult." - icon_state = "cultrobes" - item_state = "cultrobes" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - hoodtype = /obj/item/clothing/head/hooded/culthood - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) - armor = list("melee" = 40, "bullet" = 30, "laser" = 40, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) - flags_inv = HIDEJUMPSUIT - -/obj/item/clothing/suit/hooded/cultrobes/alt - icon_state = "cultrobesalt" - item_state = "cultrobesalt" - hoodtype = /obj/item/clothing/head/hooded/culthood/alt - -/obj/item/clothing/head/magus - name = "magus helm" - icon_state = "magus" - item_state = "magus" - desc = "A helm worn by the followers of Nar-Sie." - flags = BLOCKHAIR - flags_inv = HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - armor = list("melee" = 50, "bullet" = 30, "laser" = 50, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) - -/obj/item/clothing/suit/magusred - name = "magus robes" - desc = "A set of armored robes worn by the followers of Nar-Sie" - icon_state = "magusred" - item_state = "magusred" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) - armor = list("melee" = 50, "bullet" = 30, "laser" = 50, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - -/obj/item/clothing/head/helmet/space/cult - name = "cult helmet" - desc = "A space worthy helmet used by the followers of a cult." - icon_state = "cult_helmet" - item_state = "cult_helmet" - armor = list("melee" = 70, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 40, "acid" = 75) - -/obj/item/clothing/suit/space/cult - name = "cult armor" - icon_state = "cult_armour" - item_state = "cult_armour" - desc = "A bulky suit of armor, bristling with spikes. It looks space proof." - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/tome,/obj/item/melee/cultblade,/obj/item/tank) - slowdown = 1 - armor = list("melee" = 70, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 40, "acid" = 75) - -/obj/item/clothing/suit/hooded/cultrobes/cult_shield - name = "empowered cultist robe" - desc = "Empowered garb which creates a powerful shield around the user." - icon_state = "cult_armour" - item_state = "cult_armour" - w_class = WEIGHT_CLASS_BULKY - armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 30, "bomb" = 50, "bio" = 30, "rad" = 30, "fire" = 50, "acid" = 60) - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) - var/current_charges = 3 - hoodtype = /obj/item/clothing/head/hooded/cult_hoodie - -/obj/item/clothing/head/hooded/cult_hoodie - name = "empowered cultist robe" - desc = "Empowered garb which creates a powerful shield around the user." - icon_state = "cult_hoodalt" - armor = list("melee" = 40, "bullet" = 30, "laser" = 40,"energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) - body_parts_covered = HEAD - flags_inv = HIDEFACE - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/suit/hooded/cultrobes/cult_shield/equipped(mob/living/user, slot) - ..() - if(!iscultist(user)) - to_chat(user, "\"I wouldn't advise that.\"") - to_chat(user, "An overwhelming sense of nausea overpowers you!") - user.unEquip(src, 1) - user.Dizzy(30) - user.Weaken(5) - -/obj/item/clothing/suit/hooded/cultrobes/cult_shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(current_charges) - owner.visible_message("\The [attack_text] is deflected in a burst of blood-red sparks!") - current_charges-- - new /obj/effect/temp_visual/cult/sparks(get_turf(owner)) - if(!current_charges) - owner.visible_message("The runed shield around [owner] suddenly disappears!") - owner.update_inv_wear_suit() - return 1 - return 0 - -/obj/item/clothing/suit/hooded/cultrobes/berserker - name = "flagellant's robes" - desc = "Blood-soaked robes infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage." - icon_state = "hardsuit-berserker" - item_state = "hardsuit-berserker" - flags_inv = HIDEJUMPSUIT - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - armor = list("melee" = -45, "bullet" = -45, "laser" = -45,"energy" = -45, "bomb" = -45, "bio" = -45, "rad" = -45, "fire" = 0, "acid" = 0) - slowdown = -1 - hoodtype = /obj/item/clothing/head/hooded/berserkerhood - - -/obj/item/clothing/head/hooded/berserkerhood - name = "flagellant's robes" - desc = "Blood-soaked garb infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage." - icon_state = "culthood" - flags_inv = HIDEFACE - flags_cover = HEADCOVERSEYES - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/whetstone/cult - name = "eldritch whetstone" - desc = "A block, empowered by dark magic. Sharp weapons will be enhanced when used on the stone." - icon_state = "cult_sharpener" - used = 0 - increment = 5 - max = 40 - prefix = "darkened" - claw_damage_increase = 4 - -/obj/item/whetstone/cult/update_icon() - icon_state = "cult_sharpener[used ? "_used" : ""]" - -/obj/item/reagent_containers/food/drinks/bottle/unholywater - name = "flask of unholy water" - desc = "Toxic to nonbelievers; this water renews and reinvigorates the faithful of a cult." - icon_state = "holyflask" - color = "#333333" - list_reagents = list("unholywater" = 40) - -/obj/item/clothing/glasses/night/cultblind - desc = "May the master guide you through the darkness and shield you from the light." - name = "zealot's blindfold" - icon_state = "blindfold" - item_state = "blindfold" - see_in_dark = 8 - flash_protect = 1 - -/obj/item/clothing/glasses/night/cultblind/equipped(mob/user, slot) - ..() - if(!iscultist(user)) - to_chat(user, "\"You want to be blind, do you?\"") - user.unEquip(src, 1) - user.Dizzy(30) - user.Weaken(5) - user.EyeBlind(30) - -/obj/item/shuttle_curse - name = "cursed orb" - desc = "You peer within this smokey orb and glimpse terrible fates befalling the escape shuttle." - icon = 'icons/obj/projectiles.dmi' - icon_state ="bluespace" - color = "#ff0000" - var/global/curselimit = 0 - -/obj/item/shuttle_curse/attack_self(mob/user) - if(!iscultist(user)) - user.unEquip(src, 1) - user.Weaken(5) - to_chat(user, "A powerful force shoves you away from [src]!") - return - if(curselimit > 1) - to_chat(user, "We have exhausted our ability to curse the shuttle.") - return - if(locate(/obj/singularity/narsie) in GLOB.poi_list || locate(/mob/living/simple_animal/slaughter/cult) in GLOB.mob_list) - to_chat(user, "Nar-Sie or his avatars are already on this plane, there is no delaying the end of all things.") - return - - if(SSshuttle.emergency.mode == SHUTTLE_CALL) - var/cursetime = 1800 - var/timer = SSshuttle.emergency.timeLeft(1) + cursetime - SSshuttle.emergency.setTimer(timer) - to_chat(user,"You shatter the orb! A dark essence spirals into the air, then disappears.") - playsound(user.loc, 'sound/effects/glassbr1.ogg', 50, 1) - curselimit++ - qdel(src) - sleep(20) - var/global/list/curses - if(!curses) - curses = list("A fuel technician just slit his own throat and begged for death. The shuttle will be delayed by two minutes.", - "The shuttle's navigation programming was replaced by a file containing two words, IT COMES. The shuttle will be delayed by two minutes.", - "The shuttle's custodian tore out his guts and began painting strange shapes on the floor. The shuttle will be delayed by two minutes.", - "A shuttle engineer began screaming 'DEATH IS NOT THE END' and ripped out wires until an arc flash seared off her flesh. The shuttle will be delayed by two minutes.", - "A shuttle inspector started laughing madly over the radio and then threw herself into an engine turbine. The shuttle will be delayed by two minutes.", - "The shuttle dispatcher was found dead with bloody symbols carved into their flesh. The shuttle will be delayed by two minutes.", - "Steve repeatedly touched a lightbulb until his hands fell off. The shuttle will be delayed by two minutes.") - var/message = pick(curses) - command_announcement.Announce("[message]", "System Failure", 'sound/misc/notice1.ogg') - -/obj/item/cult_shift - name = "veil shifter" - desc = "This relic teleports you forward a medium distance." - icon = 'icons/obj/cult.dmi' - icon_state ="shifter" - var/uses = 4 - -/obj/item/cult_shift/examine(mob/user) - . = ..() - if(uses) - . += "It has [uses] uses remaining." - else - . += "It seems drained." - -/obj/item/cult_shift/proc/handle_teleport_grab(turf/T, mob/user) - var/mob/living/carbon/C = user - if(C.pulling) - var/atom/movable/pulled = C.pulling - pulled.forceMove(T) - . = pulled - -/obj/item/cult_shift/attack_self(mob/user) - if(!uses || !iscarbon(user)) - to_chat(user, "\The [src] is dull and unmoving in your hands.") - return - if(!iscultist(user)) - user.unEquip(src, 1) - step(src, pick(alldirs)) - to_chat(user, "\The [src] flickers out of your hands, too eager to move!") - return - - var/outer_tele_radius = 9 - - var/mob/living/carbon/C = user - var/turf/mobloc = get_turf(C) - var/list/turfs = new/list() - for(var/turf/T in range(user, outer_tele_radius)) - if(!is_teleport_allowed(T.z)) - continue - if(get_dir(C, T) != C.dir) - continue - if(T == mobloc) - continue - if(istype(T, /turf/space)) - continue - if(T.x > world.maxx-outer_tele_radius || T.x < outer_tele_radius) - continue //putting them at the edge is dumb - if(T.y > world.maxy-outer_tele_radius || T.y < outer_tele_radius) - continue - - turfs += T - - if(turfs) - uses-- - var/turf/destination = pick(turfs) - if(uses <= 0) - icon_state ="shifter_drained" - playsound(mobloc, "sparks", 50, 1) - new /obj/effect/temp_visual/dir_setting/cult/phase/out(mobloc, C.dir) - - var/atom/movable/pulled = handle_teleport_grab(destination, C) - C.forceMove(destination) - if(pulled) - C.start_pulling(pulled) //forcemove resets pulls, so we need to re-pull - - new /obj/effect/temp_visual/dir_setting/cult/phase(destination, C.dir) - playsound(destination, 'sound/effects/phasein.ogg', 25, 1) - playsound(destination, "sparks", 50, 1) - - else - to_chat(C, "The veil cannot be torn here!") - -/obj/item/melee/cultblade/ghost - name = "eldritch sword" - force = 15 - flags = NODROP | DROPDEL - -/obj/item/clothing/head/hooded/culthood/alt/ghost - flags = NODROP | DROPDEL - -/obj/item/clothing/suit/cultrobesghost - name = "ghostly cult robes" - desc = "A set of ethreal armored robes worn by the undead followers of a cult." - icon_state = "cultrobesalt" - item_state = "cultrobesalt" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/tome,/obj/item/melee/cultblade) - armor = list(melee = 50, bullet = 30, laser = 50, energy = 20, bomb = 25, bio = 10, rad = 0, fire = 10, acid = 10) - flags_inv = HIDEJUMPSUIT - - flags = NODROP | DROPDEL - - -/obj/item/clothing/shoes/cult/ghost - flags = NODROP | DROPDEL - -/datum/outfit/ghost_cultist - name = "Cultist Ghost" - - uniform = /obj/item/clothing/under/color/black - suit = /obj/item/clothing/suit/cultrobesghost - shoes = /obj/item/clothing/shoes/cult/ghost - head = /obj/item/clothing/head/hooded/culthood/alt/ghost - r_hand = /obj/item/melee/cultblade/ghost \ No newline at end of file +/obj/item/melee/cultblade + name = "Cult Blade" + desc = "An arcane weapon wielded by the followers of a cult." + icon_state = "cultblade" + item_state = "cultblade" + w_class = WEIGHT_CLASS_BULKY + force = 30 + throwforce = 10 + sharp = 1 + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + + +/obj/item/melee/cultblade/attack(mob/living/target as mob, mob/living/carbon/human/user as mob) + if(!iscultist(user)) + user.Weaken(5) + user.unEquip(src, 1) + user.visible_message("A powerful force shoves [user] away from [target]!", \ + "\"You shouldn't play with sharp things. You'll poke someone's eye out.\"") + if(ishuman(user)) + var/mob/living/carbon/human/H = user + H.apply_damage(rand(force/2, force), BRUTE, pick("l_arm", "r_arm")) + else + user.adjustBruteLoss(rand(force/2,force)) + return + ..() + +/obj/item/melee/cultblade/pickup(mob/living/user) + . = ..() + if(!iscultist(user)) + to_chat(user, "\"I wouldn't advise that.\"") + to_chat(user, "An overwhelming sense of nausea overpowers you!") + user.Dizzy(120) + + if(HULK in user.mutations) + to_chat(user, "You can't seem to hold the blade properly!") + return FALSE + +/obj/item/melee/cultblade/dagger + name = "sacrificial dagger" + desc = "A strange dagger said to be used by sinister groups for \"preparing\" a corpse before sacrificing it to their dark gods." + icon = 'icons/obj/wizard.dmi' + icon_state = "render" + w_class = WEIGHT_CLASS_SMALL + force = 15 + throwforce = 25 + embed_chance = 75 + +/obj/item/melee/cultblade/dagger/attack(atom/target, mob/living/carbon/human/user) + ..() + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if((H.stat != DEAD) && !(NO_BLOOD in H.dna.species.species_traits)) + H.bleed(50) + +/obj/item/restraints/legcuffs/bola/cult + name = "runed bola" + desc = "A strong bola, bound with dark magic. Throw it to trip and slow your victim." + icon = 'icons/obj/items.dmi' + icon_state = "bola_cult" + breakouttime = 45 + weaken = 1 + +/obj/item/clothing/head/hooded/culthood + name = "cult hood" + icon_state = "culthood" + desc = "A hood worn by the followers of a cult." + flags_inv = HIDEFACE + flags_cover = HEADCOVERSEYES + armor = list(melee = 30, bullet = 10, laser = 5, energy = 5, bomb = 0, bio = 0, rad = 0, fire = 10, acid = 10) + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + + +/obj/item/clothing/head/hooded/culthood/alt + icon_state = "cult_hoodalt" + item_state = "cult_hoodalt" + + +/obj/item/clothing/suit/hooded/cultrobes + name = "cult robes" + desc = "A set of armored robes worn by the followers of a cult." + icon_state = "cultrobes" + item_state = "cultrobes" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + hoodtype = /obj/item/clothing/head/hooded/culthood + allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + armor = list("melee" = 40, "bullet" = 30, "laser" = 40, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) + flags_inv = HIDEJUMPSUIT + +/obj/item/clothing/suit/hooded/cultrobes/alt + icon_state = "cultrobesalt" + item_state = "cultrobesalt" + hoodtype = /obj/item/clothing/head/hooded/culthood/alt + +/obj/item/clothing/head/magus + name = "magus helm" + icon_state = "magus" + item_state = "magus" + desc = "A helm worn by the followers of Nar-Sie." + flags = BLOCKHAIR + flags_inv = HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + armor = list("melee" = 50, "bullet" = 30, "laser" = 50, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) + +/obj/item/clothing/suit/magusred + name = "magus robes" + desc = "A set of armored robes worn by the followers of Nar-Sie" + icon_state = "magusred" + item_state = "magusred" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + armor = list("melee" = 50, "bullet" = 30, "laser" = 50, "energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + +/obj/item/clothing/head/helmet/space/cult + name = "cult helmet" + desc = "A space worthy helmet used by the followers of a cult." + icon_state = "cult_helmet" + item_state = "cult_helmet" + armor = list("melee" = 70, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 40, "acid" = 75) + +/obj/item/clothing/suit/space/cult + name = "cult armor" + icon_state = "cult_armour" + item_state = "cult_armour" + desc = "A bulky suit of armor, bristling with spikes. It looks space proof." + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/tome,/obj/item/melee/cultblade,/obj/item/tank) + slowdown = 1 + armor = list("melee" = 70, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 40, "acid" = 75) + +/obj/item/clothing/suit/hooded/cultrobes/cult_shield + name = "empowered cultist robe" + desc = "Empowered garb which creates a powerful shield around the user." + icon_state = "cult_armour" + item_state = "cult_armour" + w_class = WEIGHT_CLASS_BULKY + armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 30, "bomb" = 50, "bio" = 30, "rad" = 30, "fire" = 50, "acid" = 60) + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + var/current_charges = 3 + hoodtype = /obj/item/clothing/head/hooded/cult_hoodie + +/obj/item/clothing/head/hooded/cult_hoodie + name = "empowered cultist robe" + desc = "Empowered garb which creates a powerful shield around the user." + icon_state = "cult_hoodalt" + armor = list("melee" = 40, "bullet" = 30, "laser" = 40,"energy" = 20, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 10, "acid" = 10) + body_parts_covered = HEAD + flags_inv = HIDEFACE + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/suit/hooded/cultrobes/cult_shield/equipped(mob/living/user, slot) + ..() + if(!iscultist(user)) + to_chat(user, "\"I wouldn't advise that.\"") + to_chat(user, "An overwhelming sense of nausea overpowers you!") + user.unEquip(src, 1) + user.Dizzy(30) + user.Weaken(5) + +/obj/item/clothing/suit/hooded/cultrobes/cult_shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(current_charges) + owner.visible_message("\The [attack_text] is deflected in a burst of blood-red sparks!") + current_charges-- + new /obj/effect/temp_visual/cult/sparks(get_turf(owner)) + if(!current_charges) + owner.visible_message("The runed shield around [owner] suddenly disappears!") + owner.update_inv_wear_suit() + return 1 + return 0 + +/obj/item/clothing/suit/hooded/cultrobes/berserker + name = "flagellant's robes" + desc = "Blood-soaked robes infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage." + icon_state = "hardsuit-berserker" + item_state = "hardsuit-berserker" + flags_inv = HIDEJUMPSUIT + allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + armor = list("melee" = -45, "bullet" = -45, "laser" = -45,"energy" = -45, "bomb" = -45, "bio" = -45, "rad" = -45, "fire" = 0, "acid" = 0) + slowdown = -1 + hoodtype = /obj/item/clothing/head/hooded/berserkerhood + + +/obj/item/clothing/head/hooded/berserkerhood + name = "flagellant's robes" + desc = "Blood-soaked garb infused with dark magic; allows the user to move at inhuman speeds, but at the cost of increased damage." + icon_state = "culthood" + flags_inv = HIDEFACE + flags_cover = HEADCOVERSEYES + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/whetstone/cult + name = "eldritch whetstone" + desc = "A block, empowered by dark magic. Sharp weapons will be enhanced when used on the stone." + icon_state = "cult_sharpener" + used = 0 + increment = 5 + max = 40 + prefix = "darkened" + claw_damage_increase = 4 + +/obj/item/whetstone/cult/update_icon() + icon_state = "cult_sharpener[used ? "_used" : ""]" + +/obj/item/reagent_containers/food/drinks/bottle/unholywater + name = "flask of unholy water" + desc = "Toxic to nonbelievers; this water renews and reinvigorates the faithful of a cult." + icon_state = "holyflask" + color = "#333333" + list_reagents = list("unholywater" = 40) + +/obj/item/clothing/glasses/night/cultblind + desc = "May the master guide you through the darkness and shield you from the light." + name = "zealot's blindfold" + icon_state = "blindfold" + item_state = "blindfold" + see_in_dark = 8 + flash_protect = 1 + +/obj/item/clothing/glasses/night/cultblind/equipped(mob/user, slot) + ..() + if(!iscultist(user)) + to_chat(user, "\"You want to be blind, do you?\"") + user.unEquip(src, 1) + user.Dizzy(30) + user.Weaken(5) + user.EyeBlind(30) + +/obj/item/shuttle_curse + name = "cursed orb" + desc = "You peer within this smokey orb and glimpse terrible fates befalling the escape shuttle." + icon = 'icons/obj/projectiles.dmi' + icon_state ="bluespace" + color = "#ff0000" + var/global/curselimit = 0 + +/obj/item/shuttle_curse/attack_self(mob/user) + if(!iscultist(user)) + user.unEquip(src, 1) + user.Weaken(5) + to_chat(user, "A powerful force shoves you away from [src]!") + return + if(curselimit > 1) + to_chat(user, "We have exhausted our ability to curse the shuttle.") + return + if(locate(/obj/singularity/narsie) in GLOB.poi_list || locate(/mob/living/simple_animal/slaughter/cult) in GLOB.mob_list) + to_chat(user, "Nar-Sie or his avatars are already on this plane, there is no delaying the end of all things.") + return + + if(SSshuttle.emergency.mode == SHUTTLE_CALL) + var/cursetime = 1800 + var/timer = SSshuttle.emergency.timeLeft(1) + cursetime + SSshuttle.emergency.setTimer(timer) + to_chat(user,"You shatter the orb! A dark essence spirals into the air, then disappears.") + playsound(user.loc, 'sound/effects/glassbr1.ogg', 50, 1) + curselimit++ + qdel(src) + sleep(20) + var/global/list/curses + if(!curses) + curses = list("A fuel technician just slit his own throat and begged for death. The shuttle will be delayed by two minutes.", + "The shuttle's navigation programming was replaced by a file containing two words, IT COMES. The shuttle will be delayed by two minutes.", + "The shuttle's custodian tore out his guts and began painting strange shapes on the floor. The shuttle will be delayed by two minutes.", + "A shuttle engineer began screaming 'DEATH IS NOT THE END' and ripped out wires until an arc flash seared off her flesh. The shuttle will be delayed by two minutes.", + "A shuttle inspector started laughing madly over the radio and then threw herself into an engine turbine. The shuttle will be delayed by two minutes.", + "The shuttle dispatcher was found dead with bloody symbols carved into their flesh. The shuttle will be delayed by two minutes.", + "Steve repeatedly touched a lightbulb until his hands fell off. The shuttle will be delayed by two minutes.") + var/message = pick(curses) + command_announcement.Announce("[message]", "System Failure", 'sound/misc/notice1.ogg') + +/obj/item/cult_shift + name = "veil shifter" + desc = "This relic teleports you forward a medium distance." + icon = 'icons/obj/cult.dmi' + icon_state ="shifter" + var/uses = 4 + +/obj/item/cult_shift/examine(mob/user) + . = ..() + if(uses) + . += "It has [uses] uses remaining." + else + . += "It seems drained." + +/obj/item/cult_shift/proc/handle_teleport_grab(turf/T, mob/user) + var/mob/living/carbon/C = user + if(C.pulling) + var/atom/movable/pulled = C.pulling + pulled.forceMove(T) + . = pulled + +/obj/item/cult_shift/attack_self(mob/user) + if(!uses || !iscarbon(user)) + to_chat(user, "\The [src] is dull and unmoving in your hands.") + return + if(!iscultist(user)) + user.unEquip(src, 1) + step(src, pick(alldirs)) + to_chat(user, "\The [src] flickers out of your hands, too eager to move!") + return + + var/outer_tele_radius = 9 + + var/mob/living/carbon/C = user + var/turf/mobloc = get_turf(C) + var/list/turfs = new/list() + for(var/turf/T in range(user, outer_tele_radius)) + if(!is_teleport_allowed(T.z)) + continue + if(get_dir(C, T) != C.dir) + continue + if(T == mobloc) + continue + if(istype(T, /turf/space)) + continue + if(T.x > world.maxx-outer_tele_radius || T.x < outer_tele_radius) + continue //putting them at the edge is dumb + if(T.y > world.maxy-outer_tele_radius || T.y < outer_tele_radius) + continue + + turfs += T + + if(turfs) + uses-- + var/turf/destination = pick(turfs) + if(uses <= 0) + icon_state ="shifter_drained" + playsound(mobloc, "sparks", 50, 1) + new /obj/effect/temp_visual/dir_setting/cult/phase/out(mobloc, C.dir) + + var/atom/movable/pulled = handle_teleport_grab(destination, C) + C.forceMove(destination) + if(pulled) + C.start_pulling(pulled) //forcemove resets pulls, so we need to re-pull + + new /obj/effect/temp_visual/dir_setting/cult/phase(destination, C.dir) + playsound(destination, 'sound/effects/phasein.ogg', 25, 1) + playsound(destination, "sparks", 50, 1) + + else + to_chat(C, "The veil cannot be torn here!") + +/obj/item/melee/cultblade/ghost + name = "eldritch sword" + force = 15 + flags = NODROP | DROPDEL + +/obj/item/clothing/head/hooded/culthood/alt/ghost + flags = NODROP | DROPDEL + +/obj/item/clothing/suit/cultrobesghost + name = "ghostly cult robes" + desc = "A set of ethreal armored robes worn by the undead followers of a cult." + icon_state = "cultrobesalt" + item_state = "cultrobesalt" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + allowed = list(/obj/item/tome,/obj/item/melee/cultblade) + armor = list(melee = 50, bullet = 30, laser = 50, energy = 20, bomb = 25, bio = 10, rad = 0, fire = 10, acid = 10) + flags_inv = HIDEJUMPSUIT + + flags = NODROP | DROPDEL + + +/obj/item/clothing/shoes/cult/ghost + flags = NODROP | DROPDEL + +/datum/outfit/ghost_cultist + name = "Cultist Ghost" + + uniform = /obj/item/clothing/under/color/black + suit = /obj/item/clothing/suit/cultrobesghost + shoes = /obj/item/clothing/shoes/cult/ghost + head = /obj/item/clothing/head/hooded/culthood/alt/ghost + r_hand = /obj/item/melee/cultblade/ghost diff --git a/code/game/gamemodes/cult/cult_objectives.dm b/code/game/gamemodes/cult/cult_objectives.dm index 83ae3c2f1b7..caf607ce010 100644 --- a/code/game/gamemodes/cult/cult_objectives.dm +++ b/code/game/gamemodes/cult/cult_objectives.dm @@ -266,4 +266,4 @@ if(is_station_level(T.z)) //we're only interested in the remaining humans on the station survivors++ if(survivors < massacre_target) - bonus = 1 \ No newline at end of file + bonus = 1 diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm index a32f7219ca8..445050529af 100644 --- a/code/game/gamemodes/cult/ritual.dm +++ b/code/game/gamemodes/cult/ritual.dm @@ -1,320 +1,320 @@ -#define CULT_ELDERGOD "eldergod" -#define CULT_SLAUGHTER "slaughter" - -/obj/effect/rune/proc/fizzle() - if(istype(src,/obj/effect/rune)) - usr.say(pick("Hakkrutju gopoenjim.", "Nherasai pivroiashan.", "Firjji prhiv mazenhor.", "Tanah eh wakantahe.", "Obliyae na oraie.", "Miyf hon vnor'c.", "Wakabai hij fen juswix.")) - else - usr.whisper(pick("Hakkrutju gopoenjim.", "Nherasai pivroiashan.", "Firjji prhiv mazenhor.", "Tanah eh wakantahe.", "Obliyae na oraie.", "Miyf hon vnor'c.", "Wakabai hij fen juswix.")) - for (var/mob/V in viewers(src)) - V.show_message("The markings pulse with a small burst of light, then fall dark.", 3, "You hear a faint fizzle.", 2) - return - -/obj/effect/rune/proc/check_icon() - if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way - var/bits = make_bit_triplet() - icon = get_rune(bits) - else - icon = get_rune_cult(invocation) - -/obj/item/tome - name = "arcane tome" - desc = "An old, dusty tome with frayed edges and a sinister-looking cover." - icon_state ="tome" - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - var/scribereduct = 0 - var/canbypass = 0 //ADMINBUS - -/obj/item/tome/accursed - name = "accursed tome" - desc = "An arcane tome still empowered with a shadow of its former consecration." - scribereduct = 30 //faster because it's made by corrupting a bible - -/obj/item/tome/imbued //Admin-only tome, allows instant drawing of runes - name = "imbued arcane tome" - desc = "An arcane tome granted by the Geometer itself." - scribereduct = 50 - canbypass = 1 - -/obj/item/tome/New() - if(!SSticker.mode) - icon_state = "tome" - else - icon_state = SSticker.cultdat.tome_icon - ..() - -/obj/item/tome/examine(mob/user) - . = ..() - if(iscultist(user) || user.stat == DEAD) - . += "The scriptures of [SSticker.cultdat.entity_title3]. Allows the scribing of runes and access to the knowledge archives of the cult of [SSticker.cultdat.entity_name]." - . += "Striking another cultist with it will purge holy water from them." - . += "Striking a noncultist, however, will sear their flesh." - -/obj/item/tome/attack(mob/living/M, mob/living/user) - if(!istype(M)) - return - if(!iscultist(user)) - return ..() - if(iscultist(M)) - if(M.reagents && M.reagents.has_reagent("holywater")) //allows cultists to be rescued from the clutches of ordained religion - to_chat(user, "You remove the taint from [M].") - var/holy2unholy = M.reagents.get_reagent_amount("holywater") - M.reagents.del_reagent("holywater") - M.reagents.add_reagent("unholywater",holy2unholy) - add_attack_logs(user, M, "Hit with [src], removing the holy water from them") - return - M.take_organ_damage(0, 15) //Used to be a random between 5 and 20 - playsound(M, 'sound/weapons/sear.ogg', 50, 1) - M.visible_message("[user] strikes [M] with the arcane tome!", \ - "[user] strikes you with the tome, searing your flesh!") - flick("tome_attack", src) - user.do_attack_animation(M) - add_attack_logs(user, M, "Hit with [src]") - -/obj/item/tome/attack_self(mob/user) - if(!iscultist(user)) - to_chat(user, "[src] seems full of unintelligible shapes, scribbles, and notes. Is this some sort of joke?") - return - open_tome(user) - -/obj/item/tome/proc/open_tome(mob/user) - var/choice = alert(user,"You open the tome...",,"Scribe Rune","More Information","Cancel") - switch(choice) - if("More Information") - read_tome(user) - if("Scribe Rune") - scribe_rune(user) - if("Cancel") - return - -/obj/item/tome/proc/read_tome(mob/user) - var/text = list() - text += "
    Archives of [SSticker.cultdat.entity_title1]



    " - text += "A rune's name and effects can be revealed by examining the rune.<

    " - - text += "Rite of Binding
    This rune is one of the most important runes the cult has, being the only way to create new talismans. A blank sheet of paper must be on top of the rune. After \ - invoking it and choosing which talisman you desire, the paper will be converted, after some delay into a talisman.

    " - - text += "Teleport
    This rune is unique in that it requires a keyword before the scribing can begin. When invoked, it will find any other Teleport runes; \ - If any are found, the user can choose which rune to send to. Upon activation, the rune teleports everything above it to the selected rune.

    " - - text += "Rite of Enlightenment
    This rune is critical to the success of the cult. It will allow you to convert normal crew members into cultists. \ - To do this, simply place the crew member upon the rune and invoke it. This rune requires two invokers to use. If the target to be converted is mindshielded or a certain assignment, they will \ - be unable to be converted. People [SSticker.cultdat.entity_title3] wishes sacrificed will also be ineligible for conversion, and anyone with a shielding presence like the null rod will not be converted.
    \ - Successful conversions will produce a tome for the new cultist.

    " - - text += "Rite of Tribute
    This rune is necessary to achieve your goals. Simply place any dead creature upon the rune and invoke it (this will not \ - target cultists!). If this creature has a mind, a soulstone will be created and the creature's soul transported to it. Sacrificing the dead can be done alone, but sacrificing living crew or your cult's target will require 3 cultists. \ - Soulstones used on construct shells will move that soul into a powerful construct of your choice.

    " - - - text += "Rite of Resurrection
    This rune requires two corpses. To perform the ritual, place the corpse you wish to revive onto \ - the rune and the offering body adjacent to it. When the rune is invoked, the body to be sacrificed will turn to dust, the life force flowing into the revival target. Assuming the target is not moved \ - within a few seconds, they will be brought back to life, healed of all ailments.

    " - - text += "Rite of Disruption
    Robotic lifeforms have time and time again been the downfall of fledgling cults. This rune may allow you to gain the upper \ - hand against these pests. By using the rune, a large electromagnetic pulse will be emitted from the rune's location. The size of the EMP will grow significantly for each additional adjacent cultist when the \ - rune is activated.

    " - - text += "Astral Communion
    This rune is perhaps the most ingenious rune that is usable by a single person. Upon invoking the rune, the \ - user's spirit will be ripped from their body. In this state, the user's physical body will be locked in place to the rune itself - any attempts to move it will result in the rune pulling it back. \ - The body will also take constant damage while in this form, and may even die. The user's spirit will contain their consciousness, and will allow them to freely wander the station as a ghost. This may \ - also be used to commune with the dead.

    " - - text += "Rite of the Corporeal Shield
    While simple, this rune serves an important purpose in defense and hindering passage. When invoked, the \ - rune will draw a small amount of life force from the user and make the space above the rune completely dense, rendering it impassable to all but the most complex means. The rune may be invoked again to \ - undo this effect and allow passage again.

    " - - text += "Rite of Joined Souls
    This rune allows the cult to free other cultists with ease. When invoked, it will allow the user to summon a single cultist to the rune from \ - any location. It requires two invokers, and will damage each invoker slightly.

    " - - text += "Blood Boil
    When invoked, this rune will do a massive amount of damage to all non-cultist viewers, but it will also emit a small explosion upon invocation. \ - It requires three invokers.

    " - - text += "Leeching
    When invoked, this rune will transfer lifeforce from the victim to the invoker.

    " - - text += "Rite of Spectral Manifestation
    This rune allows you to summon spirits as humanoid fighters. When invoked, a spirit above the rune will be brought to life as a human, wearing nothing, that seeks only to serve you and [SSticker.cultdat.entity_title3]. \ - However, the spirit's link to reality is fragile - you must remain on top of the rune, and you will slowly take damage. Upon stepping off the rune, all summoned spirits will dissipate, dropping their items to the ground. You may manifest \ - multiple spirits with one rune, but you will rapidly take damage in doing so.

    " - - text += "Ritual of Dimensional Rending
    This rune is necessary to achieve your goals. On attempting to scribe it, it will produce shields around you and alert everyone you are attempting to scribe it; it takes a very long time to scribe, \ - and does massive damage to the one attempting to scribe it.
    Invoking it requires 9 invokers and the sacrifice of a specific crewmember, and once invoked, will summon [SSticker.cultdat.entity_title3], [SSticker.cultdat.entity_name]. \ - This will complete your objectives.


    " - - text += "Talisman of Teleportation
    The talisman form of the Teleport rune will transport the invoker to a selected Teleport rune once.

    " - - text += "Talisman of Fabrication
    This talisman is the main way of creating construct shells. To use it, one must strike 30 sheets of metal with the talisman. The sheets will then be twisted into a construct shell, ready to receive a soul to occupy it.

    " - - text += "Talisman of Tome Summoning
    This talisman will produce a single tome at your feet.

    " - - text += "Talisman of Veiling/Revealing
    This talisman will hide runes on its first use, and on the second, will reveal runes.

    " - - text += "Talisman of Disguising
    This talisman will permanently disguise all nearby runes as crayon runes.

    " - - text += "Talisman of Electromagnetic Pulse
    This talisman will EMP anything else nearby. It disappears after one use.

    " - - text += "Talisman of Stunning
    Attacking a target will knock them down for a long duration in addition to inhibiting their speech. \ - Robotic lifeforms will suffer the effects of a heavy electromagnetic pulse instead.

    " - - text += "Talisman of Armaments
    The Talisman of Arming will equip the user with armored robes, a backpack, shoes, an eldritch longsword, and an empowered bola. Any equipment that cannot \ - be equipped will not be summoned, weaponry will be put on the floor below the user. Attacking a fellow cultist with it will instead equip them.

    " - - text += "Talisman of Horrors
    The Talisman of Horror must be applied directly to the victim, it will shatter your victim's mind with visions of the end-times that may incapacitate them.

    " - - text += "Talisman of Shackling
    The Talisman of Shackling must be applied directly to the victim, it has 4 uses and cuffs victims with magic shackles that disappear when removed.

    " - - text += "In addition to these runes, the cult has a small selection of equipment and constructs.

    " - - text += "Equipment:

    " - - text += "Cult Blade
    Cult blades are sharp weapons that, notably, cannot be used by non-cultists. These blades are produced by the Talisman of Arming.

    " - - text += "Cult Bola
    Cult bolas are strong bolas, useful for snaring targets. These bolas are produced by the Talisman of Arming.

    " - - text += "Cult Robes
    Cult robes are heavily armored robes. These robes are produced by the Talisman of Arming.

    " - - text += "Soulstone
    A soulstone is a simple piece of magic, produced either via the starter talisman or by sacrificing humans. Using it on an unconscious or dead human, or on a Shade, will trap their soul in the stone, allowing its use in construct shells. \ -
    The soul within can also be released as a Shade by using it in-hand.

    " - - text += "Construct Shell
    A construct shell is useless on its own, but placing a filled soulstone within it allows you to produce your choice of a Wraith, a Juggernaut, or an Artificer. \ -
    Each construct has uses, detailed below in Constructs. Construct shells can be produced via the starter talisman or the Rite of Fabrication.

    " - - text += "Constructs:

    " - - text += "Shade
    While technically not a construct, the Shade is produced when released from a soulstone. It is quite fragile and has weak melee attacks, but is fully healed when recaptured by a soulstone.

    " - - text += "Wraith
    The Wraith is a fast, lethal melee attacker which can jaunt through walls. However, it is only slightly more durable than a shade.

    " - - text += "Juggernaut
    The Juggernaut is a slow, but durable, melee attacker which can produce temporary forcewalls. It will also reflect most lethal energy weapons.

    " - - text += "Artificer
    The Artificer is a weak and fragile construct, able to heal other constructs, produce more soulstones and construct shells, \ - construct fortifying cult walls and flooring, and finally, it can release a few indiscriminate stunning missiles.

    " - - text += "Harvester
    If you see one, know that you have done all you can and your life is void.

    " - - var/text_string = jointext(text, null) - var/datum/browser/popup = new(user, "tome", "", 800, 600) - popup.set_content(text_string) - popup.open() - return 1 - -/obj/item/tome/proc/finale_runes_ok(mob/living/user, obj/effect/rune/rune_to_scribe) - var/datum/game_mode/cult/cult_mode = SSticker.mode - var/area/A = get_area(src) - if(GAMEMODE_IS_CULT) - if(!canbypass)//not an admin-tome, check things - if(!cult_mode.narsie_condition_cleared) - to_chat(user, "There is still more to do before unleashing [SSticker.cultdat.entity_name] power!") - return 0 - if(!cult_mode.eldergod) - to_chat(user, "\"I am already here. There is no need to try to summon me now.\"") - return 0 - if(cult_mode.demons_summoned) - to_chat(user, "\"We are already here. There is no need to try to summon us now.\"") - return 0 - if(!((CULT_ELDERGOD in cult_mode.objectives) || (CULT_SLAUGHTER in cult_mode.objectives))) - to_chat(user, "[SSticker.cultdat.entity_name]'s power does not wish to be unleashed!") - return 0 - if(!(A in summon_spots)) - to_chat(user, "[SSticker.cultdat.entity_name] can only be summoned where the veil is weak - in [english_list(summon_spots)]!") - return 0 - var/confirm_final = alert(user, "This is the FINAL step to summon your deities power, it is a long, painful ritual and the crew will be alerted to your presence", "Are you prepared for the final battle?", "My life for [SSticker.cultdat.entity_name]!", "No") - if(confirm_final == "No" || confirm_final == null) - to_chat(user, "You decide to prepare further before scribing the rune.") - return 0 - else - return 1 - else//the game mode is not cult..but we ARE a cultist...ALL ON THE ADMINBUS - var/confirm_final = alert(user, "This is the FINAL step to summon your deities power, it is a long, painful ritual and the crew will be alerted to your presence", "Are you prepared for the final battle?", "My life for [SSticker.cultdat.entity_name]!", "No") - if(confirm_final == "No" || confirm_final == null) - to_chat(user, "You decide to prepare further before scribing the rune.") - return 0 - else - return 1 - -/obj/item/tome/proc/scribe_rune(mob/living/user) - var/turf/runeturf = get_turf(user) - if(isspaceturf(runeturf)) - return - var/chosen_keyword - var/obj/effect/rune/rune_to_scribe - var/entered_rune_name - var/list/possible_runes = list() - var/list/shields = list() - var/area/A = get_area(src) - if(locate(/obj/effect/rune) in runeturf) - to_chat(user, "There is already a rune here.") - return - for(var/T in subtypesof(/obj/effect/rune) - /obj/effect/rune/malformed) - var/obj/effect/rune/R = T - if(initial(R.cultist_name)) - possible_runes.Add(initial(R.cultist_name)) //This is to allow the menu to let cultists select runes by name rather than by object path. I don't know a better way to do this - if(!possible_runes.len) - return - entered_rune_name = input(user, "Choose a rite to scribe.", "Sigils of Power") as null|anything in possible_runes - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated()) - return - for(var/T in typesof(/obj/effect/rune)) - var/obj/effect/rune/R = T - if(initial(R.cultist_name) == entered_rune_name) - rune_to_scribe = R - if(initial(R.req_keyword)) - var/the_keyword = stripped_input(usr, "Please enter a keyword for the rune.", "Enter Keyword", "") - if(!the_keyword) - return - chosen_keyword = the_keyword - break - if(!rune_to_scribe) - return - runeturf = get_turf(user) //we may have moved. adjust as needed... - A = get_area(src) - if(locate(/obj/effect/rune) in runeturf) - to_chat(user, "There is already a rune here.") - return - if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated()) - return - if(ispath(rune_to_scribe, /obj/effect/rune/narsie) || ispath(rune_to_scribe, /obj/effect/rune/slaughter))//may need to change this - Fethas - if(finale_runes_ok(user,rune_to_scribe)) - A = get_area(src) - if(!(A in summon_spots)) // Check again to make sure they didn't move - to_chat(user, "The ritual can only begin where the veil is weak - in [english_list(summon_spots)]!") - return - command_announcement.Announce("Figments from an eldritch god are being summoned somewhere on the station from an unknown dimension. Disrupt the ritual at all costs!","Central Command Higher Dimensional Affairs", 'sound/AI/spanomalies.ogg') - for(var/B in spiral_range_turfs(1, user, 1)) - var/turf/T = B - var/obj/machinery/shield/N = new(T) - N.name = "Rune-Scriber's Shield" - N.desc = "A potent shield summoned by cultists to protect them while they prepare the final ritual" - N.icon_state = "shield-cult" - N.health = 60 - shields |= N - else - return//don't do shit - - var/mob/living/carbon/human/H = user - var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") - var/obj/item/organ/external/affecting = H.get_organ(ran_zone(dam_zone)) - user.visible_message("[user] cuts open [user.p_their()] [affecting] and begins writing in [user.p_their()] own blood!", "You slice open your [affecting] and begin drawing a sigil of [SSticker.cultdat.entity_title3].") - user.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE , affecting) - if(!do_after(user, initial(rune_to_scribe.scribe_delay)-scribereduct, target = get_turf(user))) - for(var/V in shields) - var/obj/machinery/shield/S = V - if(S && !QDELETED(S)) - qdel(S) - return - if(locate(/obj/effect/rune) in runeturf) - to_chat(user, "There is already a rune here.") - return - user.visible_message("[user] creates a strange circle in [user.p_their()] own blood.", \ - "You finish drawing the arcane markings of [SSticker.cultdat.entity_title3].") - for(var/V in shields) - var/obj/machinery/shield/S = V - if(S && !QDELETED(S)) - qdel(S) - var/obj/effect/rune/R = new rune_to_scribe(runeturf, chosen_keyword) - R.blood_DNA = list() - R.blood_DNA[H.dna.unique_enzymes] = H.dna.blood_type - R.add_hiddenprint(H) - to_chat(user, "The [lowertext(initial(rune_to_scribe.cultist_name))] rune [initial(rune_to_scribe.cultist_desc)]") +#define CULT_ELDERGOD "eldergod" +#define CULT_SLAUGHTER "slaughter" + +/obj/effect/rune/proc/fizzle() + if(istype(src,/obj/effect/rune)) + usr.say(pick("Hakkrutju gopoenjim.", "Nherasai pivroiashan.", "Firjji prhiv mazenhor.", "Tanah eh wakantahe.", "Obliyae na oraie.", "Miyf hon vnor'c.", "Wakabai hij fen juswix.")) + else + usr.whisper(pick("Hakkrutju gopoenjim.", "Nherasai pivroiashan.", "Firjji prhiv mazenhor.", "Tanah eh wakantahe.", "Obliyae na oraie.", "Miyf hon vnor'c.", "Wakabai hij fen juswix.")) + for (var/mob/V in viewers(src)) + V.show_message("The markings pulse with a small burst of light, then fall dark.", 3, "You hear a faint fizzle.", 2) + return + +/obj/effect/rune/proc/check_icon() + if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way + var/bits = make_bit_triplet() + icon = get_rune(bits) + else + icon = get_rune_cult(invocation) + +/obj/item/tome + name = "arcane tome" + desc = "An old, dusty tome with frayed edges and a sinister-looking cover." + icon_state ="tome" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + var/scribereduct = 0 + var/canbypass = 0 //ADMINBUS + +/obj/item/tome/accursed + name = "accursed tome" + desc = "An arcane tome still empowered with a shadow of its former consecration." + scribereduct = 30 //faster because it's made by corrupting a bible + +/obj/item/tome/imbued //Admin-only tome, allows instant drawing of runes + name = "imbued arcane tome" + desc = "An arcane tome granted by the Geometer itself." + scribereduct = 50 + canbypass = 1 + +/obj/item/tome/New() + if(!SSticker.mode) + icon_state = "tome" + else + icon_state = SSticker.cultdat.tome_icon + ..() + +/obj/item/tome/examine(mob/user) + . = ..() + if(iscultist(user) || user.stat == DEAD) + . += "The scriptures of [SSticker.cultdat.entity_title3]. Allows the scribing of runes and access to the knowledge archives of the cult of [SSticker.cultdat.entity_name]." + . += "Striking another cultist with it will purge holy water from them." + . += "Striking a noncultist, however, will sear their flesh." + +/obj/item/tome/attack(mob/living/M, mob/living/user) + if(!istype(M)) + return + if(!iscultist(user)) + return ..() + if(iscultist(M)) + if(M.reagents && M.reagents.has_reagent("holywater")) //allows cultists to be rescued from the clutches of ordained religion + to_chat(user, "You remove the taint from [M].") + var/holy2unholy = M.reagents.get_reagent_amount("holywater") + M.reagents.del_reagent("holywater") + M.reagents.add_reagent("unholywater",holy2unholy) + add_attack_logs(user, M, "Hit with [src], removing the holy water from them") + return + M.take_organ_damage(0, 15) //Used to be a random between 5 and 20 + playsound(M, 'sound/weapons/sear.ogg', 50, 1) + M.visible_message("[user] strikes [M] with the arcane tome!", \ + "[user] strikes you with the tome, searing your flesh!") + flick("tome_attack", src) + user.do_attack_animation(M) + add_attack_logs(user, M, "Hit with [src]") + +/obj/item/tome/attack_self(mob/user) + if(!iscultist(user)) + to_chat(user, "[src] seems full of unintelligible shapes, scribbles, and notes. Is this some sort of joke?") + return + open_tome(user) + +/obj/item/tome/proc/open_tome(mob/user) + var/choice = alert(user,"You open the tome...",,"Scribe Rune","More Information","Cancel") + switch(choice) + if("More Information") + read_tome(user) + if("Scribe Rune") + scribe_rune(user) + if("Cancel") + return + +/obj/item/tome/proc/read_tome(mob/user) + var/text = list() + text += "
    Archives of [SSticker.cultdat.entity_title1]



    " + text += "A rune's name and effects can be revealed by examining the rune.<

    " + + text += "Rite of Binding
    This rune is one of the most important runes the cult has, being the only way to create new talismans. A blank sheet of paper must be on top of the rune. After \ + invoking it and choosing which talisman you desire, the paper will be converted, after some delay into a talisman.

    " + + text += "Teleport
    This rune is unique in that it requires a keyword before the scribing can begin. When invoked, it will find any other Teleport runes; \ + If any are found, the user can choose which rune to send to. Upon activation, the rune teleports everything above it to the selected rune.

    " + + text += "Rite of Enlightenment
    This rune is critical to the success of the cult. It will allow you to convert normal crew members into cultists. \ + To do this, simply place the crew member upon the rune and invoke it. This rune requires two invokers to use. If the target to be converted is mindshielded or a certain assignment, they will \ + be unable to be converted. People [SSticker.cultdat.entity_title3] wishes sacrificed will also be ineligible for conversion, and anyone with a shielding presence like the null rod will not be converted.
    \ + Successful conversions will produce a tome for the new cultist.

    " + + text += "Rite of Tribute
    This rune is necessary to achieve your goals. Simply place any dead creature upon the rune and invoke it (this will not \ + target cultists!). If this creature has a mind, a soulstone will be created and the creature's soul transported to it. Sacrificing the dead can be done alone, but sacrificing living crew or your cult's target will require 3 cultists. \ + Soulstones used on construct shells will move that soul into a powerful construct of your choice.

    " + + + text += "Rite of Resurrection
    This rune requires two corpses. To perform the ritual, place the corpse you wish to revive onto \ + the rune and the offering body adjacent to it. When the rune is invoked, the body to be sacrificed will turn to dust, the life force flowing into the revival target. Assuming the target is not moved \ + within a few seconds, they will be brought back to life, healed of all ailments.

    " + + text += "Rite of Disruption
    Robotic lifeforms have time and time again been the downfall of fledgling cults. This rune may allow you to gain the upper \ + hand against these pests. By using the rune, a large electromagnetic pulse will be emitted from the rune's location. The size of the EMP will grow significantly for each additional adjacent cultist when the \ + rune is activated.

    " + + text += "Astral Communion
    This rune is perhaps the most ingenious rune that is usable by a single person. Upon invoking the rune, the \ + user's spirit will be ripped from their body. In this state, the user's physical body will be locked in place to the rune itself - any attempts to move it will result in the rune pulling it back. \ + The body will also take constant damage while in this form, and may even die. The user's spirit will contain their consciousness, and will allow them to freely wander the station as a ghost. This may \ + also be used to commune with the dead.

    " + + text += "Rite of the Corporeal Shield
    While simple, this rune serves an important purpose in defense and hindering passage. When invoked, the \ + rune will draw a small amount of life force from the user and make the space above the rune completely dense, rendering it impassable to all but the most complex means. The rune may be invoked again to \ + undo this effect and allow passage again.

    " + + text += "Rite of Joined Souls
    This rune allows the cult to free other cultists with ease. When invoked, it will allow the user to summon a single cultist to the rune from \ + any location. It requires two invokers, and will damage each invoker slightly.

    " + + text += "Blood Boil
    When invoked, this rune will do a massive amount of damage to all non-cultist viewers, but it will also emit a small explosion upon invocation. \ + It requires three invokers.

    " + + text += "Leeching
    When invoked, this rune will transfer lifeforce from the victim to the invoker.

    " + + text += "Rite of Spectral Manifestation
    This rune allows you to summon spirits as humanoid fighters. When invoked, a spirit above the rune will be brought to life as a human, wearing nothing, that seeks only to serve you and [SSticker.cultdat.entity_title3]. \ + However, the spirit's link to reality is fragile - you must remain on top of the rune, and you will slowly take damage. Upon stepping off the rune, all summoned spirits will dissipate, dropping their items to the ground. You may manifest \ + multiple spirits with one rune, but you will rapidly take damage in doing so.

    " + + text += "Ritual of Dimensional Rending
    This rune is necessary to achieve your goals. On attempting to scribe it, it will produce shields around you and alert everyone you are attempting to scribe it; it takes a very long time to scribe, \ + and does massive damage to the one attempting to scribe it.
    Invoking it requires 9 invokers and the sacrifice of a specific crewmember, and once invoked, will summon [SSticker.cultdat.entity_title3], [SSticker.cultdat.entity_name]. \ + This will complete your objectives.


    " + + text += "Talisman of Teleportation
    The talisman form of the Teleport rune will transport the invoker to a selected Teleport rune once.

    " + + text += "Talisman of Fabrication
    This talisman is the main way of creating construct shells. To use it, one must strike 30 sheets of metal with the talisman. The sheets will then be twisted into a construct shell, ready to receive a soul to occupy it.

    " + + text += "Talisman of Tome Summoning
    This talisman will produce a single tome at your feet.

    " + + text += "Talisman of Veiling/Revealing
    This talisman will hide runes on its first use, and on the second, will reveal runes.

    " + + text += "Talisman of Disguising
    This talisman will permanently disguise all nearby runes as crayon runes.

    " + + text += "Talisman of Electromagnetic Pulse
    This talisman will EMP anything else nearby. It disappears after one use.

    " + + text += "Talisman of Stunning
    Attacking a target will knock them down for a long duration in addition to inhibiting their speech. \ + Robotic lifeforms will suffer the effects of a heavy electromagnetic pulse instead.

    " + + text += "Talisman of Armaments
    The Talisman of Arming will equip the user with armored robes, a backpack, shoes, an eldritch longsword, and an empowered bola. Any equipment that cannot \ + be equipped will not be summoned, weaponry will be put on the floor below the user. Attacking a fellow cultist with it will instead equip them.

    " + + text += "Talisman of Horrors
    The Talisman of Horror must be applied directly to the victim, it will shatter your victim's mind with visions of the end-times that may incapacitate them.

    " + + text += "Talisman of Shackling
    The Talisman of Shackling must be applied directly to the victim, it has 4 uses and cuffs victims with magic shackles that disappear when removed.

    " + + text += "In addition to these runes, the cult has a small selection of equipment and constructs.

    " + + text += "Equipment:

    " + + text += "Cult Blade
    Cult blades are sharp weapons that, notably, cannot be used by non-cultists. These blades are produced by the Talisman of Arming.

    " + + text += "Cult Bola
    Cult bolas are strong bolas, useful for snaring targets. These bolas are produced by the Talisman of Arming.

    " + + text += "Cult Robes
    Cult robes are heavily armored robes. These robes are produced by the Talisman of Arming.

    " + + text += "Soulstone
    A soulstone is a simple piece of magic, produced either via the starter talisman or by sacrificing humans. Using it on an unconscious or dead human, or on a Shade, will trap their soul in the stone, allowing its use in construct shells. \ +
    The soul within can also be released as a Shade by using it in-hand.

    " + + text += "Construct Shell
    A construct shell is useless on its own, but placing a filled soulstone within it allows you to produce your choice of a Wraith, a Juggernaut, or an Artificer. \ +
    Each construct has uses, detailed below in Constructs. Construct shells can be produced via the starter talisman or the Rite of Fabrication.

    " + + text += "Constructs:

    " + + text += "Shade
    While technically not a construct, the Shade is produced when released from a soulstone. It is quite fragile and has weak melee attacks, but is fully healed when recaptured by a soulstone.

    " + + text += "Wraith
    The Wraith is a fast, lethal melee attacker which can jaunt through walls. However, it is only slightly more durable than a shade.

    " + + text += "Juggernaut
    The Juggernaut is a slow, but durable, melee attacker which can produce temporary forcewalls. It will also reflect most lethal energy weapons.

    " + + text += "Artificer
    The Artificer is a weak and fragile construct, able to heal other constructs, produce more soulstones and construct shells, \ + construct fortifying cult walls and flooring, and finally, it can release a few indiscriminate stunning missiles.

    " + + text += "Harvester
    If you see one, know that you have done all you can and your life is void.

    " + + var/text_string = jointext(text, null) + var/datum/browser/popup = new(user, "tome", "", 800, 600) + popup.set_content(text_string) + popup.open() + return 1 + +/obj/item/tome/proc/finale_runes_ok(mob/living/user, obj/effect/rune/rune_to_scribe) + var/datum/game_mode/cult/cult_mode = SSticker.mode + var/area/A = get_area(src) + if(GAMEMODE_IS_CULT) + if(!canbypass)//not an admin-tome, check things + if(!cult_mode.narsie_condition_cleared) + to_chat(user, "There is still more to do before unleashing [SSticker.cultdat.entity_name] power!") + return 0 + if(!cult_mode.eldergod) + to_chat(user, "\"I am already here. There is no need to try to summon me now.\"") + return 0 + if(cult_mode.demons_summoned) + to_chat(user, "\"We are already here. There is no need to try to summon us now.\"") + return 0 + if(!((CULT_ELDERGOD in cult_mode.objectives) || (CULT_SLAUGHTER in cult_mode.objectives))) + to_chat(user, "[SSticker.cultdat.entity_name]'s power does not wish to be unleashed!") + return 0 + if(!(A in summon_spots)) + to_chat(user, "[SSticker.cultdat.entity_name] can only be summoned where the veil is weak - in [english_list(summon_spots)]!") + return 0 + var/confirm_final = alert(user, "This is the FINAL step to summon your deities power, it is a long, painful ritual and the crew will be alerted to your presence", "Are you prepared for the final battle?", "My life for [SSticker.cultdat.entity_name]!", "No") + if(confirm_final == "No" || confirm_final == null) + to_chat(user, "You decide to prepare further before scribing the rune.") + return 0 + else + return 1 + else//the game mode is not cult..but we ARE a cultist...ALL ON THE ADMINBUS + var/confirm_final = alert(user, "This is the FINAL step to summon your deities power, it is a long, painful ritual and the crew will be alerted to your presence", "Are you prepared for the final battle?", "My life for [SSticker.cultdat.entity_name]!", "No") + if(confirm_final == "No" || confirm_final == null) + to_chat(user, "You decide to prepare further before scribing the rune.") + return 0 + else + return 1 + +/obj/item/tome/proc/scribe_rune(mob/living/user) + var/turf/runeturf = get_turf(user) + if(isspaceturf(runeturf)) + return + var/chosen_keyword + var/obj/effect/rune/rune_to_scribe + var/entered_rune_name + var/list/possible_runes = list() + var/list/shields = list() + var/area/A = get_area(src) + if(locate(/obj/effect/rune) in runeturf) + to_chat(user, "There is already a rune here.") + return + for(var/T in subtypesof(/obj/effect/rune) - /obj/effect/rune/malformed) + var/obj/effect/rune/R = T + if(initial(R.cultist_name)) + possible_runes.Add(initial(R.cultist_name)) //This is to allow the menu to let cultists select runes by name rather than by object path. I don't know a better way to do this + if(!possible_runes.len) + return + entered_rune_name = input(user, "Choose a rite to scribe.", "Sigils of Power") as null|anything in possible_runes + if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated()) + return + for(var/T in typesof(/obj/effect/rune)) + var/obj/effect/rune/R = T + if(initial(R.cultist_name) == entered_rune_name) + rune_to_scribe = R + if(initial(R.req_keyword)) + var/the_keyword = stripped_input(usr, "Please enter a keyword for the rune.", "Enter Keyword", "") + if(!the_keyword) + return + chosen_keyword = the_keyword + break + if(!rune_to_scribe) + return + runeturf = get_turf(user) //we may have moved. adjust as needed... + A = get_area(src) + if(locate(/obj/effect/rune) in runeturf) + to_chat(user, "There is already a rune here.") + return + if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated()) + return + if(ispath(rune_to_scribe, /obj/effect/rune/narsie) || ispath(rune_to_scribe, /obj/effect/rune/slaughter))//may need to change this - Fethas + if(finale_runes_ok(user,rune_to_scribe)) + A = get_area(src) + if(!(A in summon_spots)) // Check again to make sure they didn't move + to_chat(user, "The ritual can only begin where the veil is weak - in [english_list(summon_spots)]!") + return + command_announcement.Announce("Figments from an eldritch god are being summoned somewhere on the station from an unknown dimension. Disrupt the ritual at all costs!","Central Command Higher Dimensional Affairs", 'sound/AI/spanomalies.ogg') + for(var/B in spiral_range_turfs(1, user, 1)) + var/turf/T = B + var/obj/machinery/shield/N = new(T) + N.name = "Rune-Scriber's Shield" + N.desc = "A potent shield summoned by cultists to protect them while they prepare the final ritual" + N.icon_state = "shield-cult" + N.health = 60 + shields |= N + else + return//don't do shit + + var/mob/living/carbon/human/H = user + var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") + var/obj/item/organ/external/affecting = H.get_organ(ran_zone(dam_zone)) + user.visible_message("[user] cuts open [user.p_their()] [affecting] and begins writing in [user.p_their()] own blood!", "You slice open your [affecting] and begin drawing a sigil of [SSticker.cultdat.entity_title3].") + user.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE , affecting) + if(!do_after(user, initial(rune_to_scribe.scribe_delay)-scribereduct, target = get_turf(user))) + for(var/V in shields) + var/obj/machinery/shield/S = V + if(S && !QDELETED(S)) + qdel(S) + return + if(locate(/obj/effect/rune) in runeturf) + to_chat(user, "There is already a rune here.") + return + user.visible_message("[user] creates a strange circle in [user.p_their()] own blood.", \ + "You finish drawing the arcane markings of [SSticker.cultdat.entity_title3].") + for(var/V in shields) + var/obj/machinery/shield/S = V + if(S && !QDELETED(S)) + qdel(S) + var/obj/effect/rune/R = new rune_to_scribe(runeturf, chosen_keyword) + R.blood_DNA = list() + R.blood_DNA[H.dna.unique_enzymes] = H.dna.blood_type + R.add_hiddenprint(H) + to_chat(user, "The [lowertext(initial(rune_to_scribe.cultist_name))] rune [initial(rune_to_scribe.cultist_desc)]") diff --git a/code/game/gamemodes/cult/talisman.dm b/code/game/gamemodes/cult/talisman.dm index f7aaf2c1ebd..c0c4f7280f5 100644 --- a/code/game/gamemodes/cult/talisman.dm +++ b/code/game/gamemodes/cult/talisman.dm @@ -1,436 +1,436 @@ -/obj/item/paper/talisman - icon = 'icons/obj/paper.dmi' - icon_state = "paper_talisman" - var/cultist_name = "talisman" - var/cultist_desc = "A basic talisman. It serves no purpose." - var/invocation = "Naise meam!" - info = "


    " - var/uses = 1 - var/health_cost = 0 //The amount of health taken from the user when invoking the talisman - -/obj/item/paper/talisman/update_icon()//overriding this so the update_icon doesn't turn them into normal looking paper - SEND_SIGNAL(src, COMSIG_OBJ_UPDATE_ICON) - -/obj/item/paper/talisman/examine(mob/user) - . = ..() - if(iscultist(user) || user.stat == DEAD) - . += "Name: [cultist_name]" - . += "Effect: [cultist_desc]" - . += "Uses Remaining: [uses]" - else - . += "You see strange symbols on the paper. Are they supposed to mean something?" - -/obj/item/paper/talisman/attack_self(mob/living/user) - if(!iscultist(user)) - to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") - return - if(invoke(user)) - uses-- - if(uses <= 0) - user.drop_item() - qdel(src) - -/obj/item/paper/talisman/proc/invoke(mob/living/user, successfuluse = 1) - . = successfuluse - if(successfuluse) //if the calling whatever says we succeed, do the fancy stuff - if(invocation) - user.whisper(invocation) - if(health_cost && iscarbon(user)) - var/mob/living/carbon/C = user - C.apply_damage(health_cost, BRUTE, pick("l_arm", "r_arm")) - -//Malformed Talisman: If something goes wrong. -/obj/item/paper/talisman/malformed - cultist_name = "malformed talisman" - cultist_desc = "A talisman with gibberish scrawlings. No good can come from invoking this." - invocation = "Ra'sha yoka!" - -/obj/item/paper/talisman/malformed/invoke(mob/living/user, successfuluse = 1) - to_chat(user, "You feel a pain in your head. [SSticker.cultdat.entity_title3] is displeased.") - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.apply_damage(10, BRUTE, "head") - -//Supply Talisman: Has a few unique effects. Granted only to starter cultists. -/obj/item/paper/talisman/supply - cultist_name = "Supply Talisman" - icon_state = "supply" - cultist_desc = "A multi-use talisman that can create various objects. Intended to increase the cult's strength early on." - invocation = null - uses = 3 - -/obj/item/paper/talisman/supply/invoke(mob/living/user, successfuluse = 1) - var/dat = list() - dat += "There are [uses] bloody runes on the parchment.
    " - dat += "Please choose the chant to be imbued into the fabric of reality.
    " - dat += "
    " - dat += "N'ath reth sh'yro eth d'raggathnor! - Summons an arcane tome, used to scribe runes and communicate with other cultists.
    " - dat += "Bar'tea eas! - Provides 5 runed metal.
    " - dat += "Sas'so c'arta forbici! - Allows you to move to a selected teleportation rune.
    " - dat += "Ta'gh fara'qha fel d'amar det! - Allows you to destroy technology in a short range.
    " - dat += "Fuu ma'jin! - Allows you to stun a person by attacking them with the talisman.
    " - dat += "Kla'atu barada nikt'o! - Two use talisman, first use makes all nearby runes invisible, second use reveals nearby hidden runes.
    " - dat += "Kal'om neth! - Summons a soul stone, used to capture the spirits of dead or dying humans.
    " - dat += "Daa'ig osk! - Summons a construct shell for use with soulstone-captured souls. It is too large to carry on your person.
    " - var/datum/browser/popup = new(user, "talisman", "", 400, 400) - popup.set_content(jointext(dat, "")) - popup.open() - return 0 - -/obj/item/paper/talisman/supply/Topic(href, href_list) - if(src) - if(usr.stat || usr.restrained() || !in_range(src, usr)) - return - if(href_list["rune"]) - switch(href_list["rune"]) - if("newtome") - var/obj/item/tome/T = new(usr) - usr.put_in_hands(T) - if("metal") - if(istype(src, /obj/item/paper/talisman/supply/weak)) - usr.visible_message("Lesser supply talismans lack the strength to materialize runed metal!") - return - var/obj/item/stack/sheet/runed_metal/R = new(usr,5) - usr.put_in_hands(R) - if("teleport") - var/obj/item/paper/talisman/teleport/T = new(usr) - usr.put_in_hands(T) - if("emp") - var/obj/item/paper/talisman/emp/T = new(usr) - usr.put_in_hands(T) - if("runestun") - var/obj/item/paper/talisman/stun/T = new(usr) - usr.put_in_hands(T) - if("soulstone") - var/obj/item/soulstone/T = new(usr) - usr.put_in_hands(T) - if("construct") - new /obj/structure/constructshell(get_turf(usr)) - if("veiling") - var/obj/item/paper/talisman/true_sight/T = new(usr) - usr.put_in_hands(T) - uses-- - if(uses <= 0) - if(iscarbon(usr)) - var/mob/living/carbon/C = usr - C.drop_item() - visible_message("[src] crumbles to dust.") - qdel(src) - -/obj/item/paper/talisman/supply/weak - uses = 2 - -//Rite of Translocation: Same as rune -/obj/item/paper/talisman/teleport - cultist_name = "Talisman of Teleportation" - icon_state = "teleport" - cultist_desc = "A single-use talisman that will teleport a user to a random rune of the same keyword." - invocation = "Sas'so c'arta forbici!" - health_cost = 5 - -/obj/item/paper/talisman/teleport/invoke(mob/living/user, successfuluse = 1) - var/list/potential_runes = list() - var/list/teleportnames = list() - var/list/duplicaterunecount = list() - for(var/R in teleport_runes) - var/obj/effect/rune/teleport/T = R - var/resultkey = T.listkey - if(resultkey in teleportnames) - duplicaterunecount[resultkey]++ - resultkey = "[resultkey] ([duplicaterunecount[resultkey]])" - else - teleportnames.Add(resultkey) - duplicaterunecount[resultkey] = 1 - potential_runes[resultkey] = T - - if(!potential_runes.len) - to_chat(user, "There are no valid runes to teleport to!") - log_game("Teleport talisman failed - no other teleport runes") - return ..(user, 0) - - if(!is_level_reachable(user.z)) - to_chat(user, "You are not in the right dimension!") - log_game("Teleport talisman failed - user in away mission") - return ..(user, 0) - - var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked - var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to? - if(!src || QDELETED(src) || !user || user.l_hand != src && user.r_hand != src || user.incapacitated() || !actual_selected_rune) - return ..(user, 0) - - user.visible_message("Dust flows from [user]'s hand, and [user.p_they()] disappear[user.p_s()] in a flash of red light!", \ - "You speak the words of the talisman and find yourself somewhere else!") - user.forceMove(get_turf(actual_selected_rune)) - return ..() - - -/obj/item/paper/talisman/summon_tome - cultist_name = "Talisman of Tome Summoning" - icon_state = "tome" - cultist_desc = "A one-use talisman that will call an untranslated tome from the archives of a cult." - invocation = "N'ath reth sh'yro eth d'raggathnor!" - health_cost = 1 - -/obj/item/paper/talisman/summon_tome/invoke(mob/living/user, successfuluse = 1) - . = ..() - user.visible_message("[user]'s hand glows red for a moment.", \ - "You speak the words of the talisman!") - new /obj/item/tome(get_turf(user)) - user.visible_message("A tome appears at [user]'s feet!", \ - "An arcane tome materializes at your feet.") - -/obj/item/paper/talisman/true_sight - cultist_name = "Talisman of Veiling" - icon_state = "veil" - cultist_desc = "A multi-use talisman that hides nearby runes. On its second use, will reveal nearby runes." - invocation = "Kla'atu barada nikt'o!" - health_cost = 1 - uses = 2 - var/revealing = FALSE //if it reveals or not - -/obj/item/paper/talisman/true_sight/invoke(mob/living/user, successfuluse = 1) - . = ..() - if(!revealing) - user.visible_message("Thin grey dust falls from [user]'s hand!", \ - "You speak the words of the talisman, hiding nearby runes.") - invocation = "Nikt'o barada kla'atu!" - revealing = TRUE - for(var/obj/effect/rune/R in range(3,user)) - R.talismanhide() - else - user.visible_message("A flash of light shines from [user]'s hand!", \ - "You speak the words of the talisman, revealing nearby runes.") - for(var/obj/effect/rune/R in range(3,user)) - R.talismanreveal() - -//Rite of False Truths: Same as rune -/obj/item/paper/talisman/make_runes_fake - cultist_name = "Talisman of Disguising" - icon_state = "disguising" - cultist_desc = "A talisman that will make nearby runes appear fake." - invocation = "By'o nar'nar!" - -/obj/item/paper/talisman/make_runes_fake/invoke(mob/living/user, successfuluse = 1) - . = ..() - user.visible_message("Dust flows from [user]s hand.", \ - "You speak the words of the talisman, making nearby runes appear fake.") - for(var/obj/effect/rune/R in orange(6,user)) - R.talismanfake() - R.desc = "A rune drawn in crayon." - - -//Rite of Disruption: Weaker than rune -/obj/item/paper/talisman/emp - cultist_name = "Talisman of Electromagnetic Pulse" - icon_state = "emp" - cultist_desc = "A talisman that will cause a moderately-sized electromagnetic pulse." - invocation = "Ta'gh fara'qha fel d'amar det!" - health_cost = 5 - -/obj/item/paper/talisman/emp/invoke(mob/living/user, successfuluse = 1) - . = ..() - user.visible_message("[user]'s hand flashes a bright blue!", \ - "You speak the words of the talisman, emitting an EMP blast.") - empulse(src, 4, 8, 1) - - -//Rite of Disorientation: Stuns and inhibit speech on a single target for quite some time -/obj/item/paper/talisman/stun - cultist_name = "Talisman of Stunning" - icon_state = "stunning" - cultist_desc = "A talisman that will stun and inhibit speech on a single target. To use, attack target directly." - invocation = "Dream sign:Evil sealing talisman!" - health_cost = 10 - -/obj/item/paper/talisman/stun/invoke(mob/living/user, successfuluse = 0) - if(successfuluse) //if we're forced to be successful(we normally aren't) then do the normal stuff - return ..() - if(iscultist(user)) - to_chat(user, "To use this talisman, attack the target directly.") - else - to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") - return 0 - -/obj/item/paper/talisman/stun/attack(mob/living/target, mob/living/user, successfuluse = 1) - if(iscultist(user)) - invoke(user, 1) - user.visible_message("[user] holds up [src], which explodes in a flash of red light!", \ - "You stun [target] with the talisman!") - var/obj/item/nullrod/N = locate() in target - if(N) - target.visible_message("[target]'s holy weapon absorbs the talisman's light!", \ - "Your holy weapon absorbs the blinding light!") - else - add_attack_logs(user, target, "Stunned with a talisman") - target.Weaken(10) - target.Stun(10) - target.flash_eyes(1,1) - if(issilicon(target)) - var/mob/living/silicon/S = target - S.emp_act(1) - if(iscarbon(target)) - var/mob/living/carbon/C = target - C.AdjustSilence(5) - C.AdjustStuttering(15) - C.AdjustCultSlur(20) - C.Jitter(15) - user.drop_item() - qdel(src) - return - ..() - - -//Rite of Arming: Equips cultist armor on the user, where available -/obj/item/paper/talisman/armor - cultist_name = "Talisman of Arming" - icon_state = "arming" - cultist_desc = "A talisman that will equip the invoker with cultist equipment if there is a slot to equip it to." - invocation = "N'ath reth sh'yro eth draggathnor!" - -/obj/item/paper/talisman/armor/invoke(mob/living/user, successfuluse = 1) - . = ..() - var/mob/living/carbon/human/H = user - user.visible_message("Otherworldly armor suddenly appears on [user]!", \ - "You speak the words of the talisman, arming yourself!") - - H.equip_to_slot_or_del(new /obj/item/clothing/suit/hooded/cultrobes/alt(user), slot_wear_suit) - H.equip_to_slot_or_del(new /obj/item/storage/backpack/cultpack(user), slot_back) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/cult(user), slot_shoes) - H.put_in_hands(new /obj/item/melee/cultblade(user)) - H.put_in_hands(new /obj/item/restraints/legcuffs/bola/cult(user)) - -/obj/item/paper/talisman/armor/attack(mob/living/target, mob/living/user) - if(iscultist(user) && iscultist(target)) - user.drop_item() - qdel(src) - invoke(target) - return - ..() - - -//Talisman of Horrors: Breaks the mind of the victim with nightmarish hallucinations -/obj/item/paper/talisman/horror - cultist_name = "Talisman of Horrors" - icon_state = "horror" - cultist_desc = "A talisman that will break the mind of the victim with nightmarish hallucinations." - invocation = "Lo'Nab Na'Dm!" - -/obj/item/paper/talisman/horror/attack(mob/living/target, mob/living/user) - if(iscultist(user)) - to_chat(user, "You disturb [target] with visions of the end!") - if(iscarbon(target)) - var/mob/living/carbon/H = target - H.AdjustHallucinate(30) - qdel(src) - - -//Talisman of Fabrication: Creates a construct shell out of 25 metal sheets. -/obj/item/paper/talisman/construction - cultist_name = "Talisman of Construction" - icon_state = "construction" - cultist_desc = "Use this talisman on at least twenty-five metal sheets to create an empty construct shell or on plasteel to make runed metal" - invocation = "Ethra p'ni dedol!" - uses = 25 - -/obj/item/paper/talisman/construction/attack_self(mob/living/user) - if(iscultist(user)) - to_chat(user, "To use this talisman, place it upon a stack of metal sheets or plasteel sheets!.") - else - to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") - - -/obj/item/paper/talisman/construction/attack(obj/M,mob/living/user) - if(iscultist(user)) - to_chat(user, "This talisman will only work on a stack of metal sheets or plasteel sheets!!") - log_game("Construct talisman failed - not a valid target") - -/obj/item/paper/talisman/construction/afterattack(obj/item/stack/sheet/target, mob/user, proximity_flag, click_parameters) - ..() - if(proximity_flag && iscultist(user)) - if(istype(target, /obj/item/stack/sheet/metal)) - var/turf/T = get_turf(target) - if(target.use(25)) - new /obj/structure/constructshell(T) - to_chat(user, "The talisman clings to the metal and twists it into a construct shell!") - user << sound('sound/magic/staff_chaos.ogg',0,1,25) - qdel(src) - return - if(istype(target, /obj/item/stack/sheet/plasteel)) - var/turf/T = get_turf(target) - var/quantity = min(target.amount, uses) - uses -= quantity - new /obj/item/stack/sheet/runed_metal(T,quantity) - target.use(quantity) - to_chat(user, "The talisman clings to the plasteel, transforming it into runed metal!") - user << sound('sound/magic/staff_chaos.ogg',0,1,25) - invoke(user, 1) - if(uses <= 0) - qdel(src) - else - to_chat(user, "The talisman must be used on metal or plasteel!") - -//Talisman of Shackling: Applies special cuffs directly from the talisman -/obj/item/paper/talisman/shackle - cultist_name = "Talisman of Shackling" - icon_state = "shackling" - cultist_desc = "Use this talisman on a victim to handcuff them with dark bindings." - invocation = "In'totum Lig'abis!" - uses = 4 - -/obj/item/paper/talisman/shackle/invoke(mob/living/user, successfuluse = 0) - if(successfuluse) //if we're forced to be successful(we normally aren't) then do the normal stuff - return ..() - if(iscultist(user)) - to_chat(user, "To use this talisman, attack the target directly.") - else - to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") - return 0 - -/obj/item/paper/talisman/shackle/attack(mob/living/carbon/target, mob/living/user) - if(iscultist(user) && istype(target)) - if(target.stat == DEAD) - user.visible_message("This talisman's magic does not affect the dead!") - return - CuffAttack(target, user) - return - ..() - -/obj/item/paper/talisman/shackle/proc/CuffAttack(mob/living/carbon/C, mob/living/user) - if(!C.handcuffed) - invoke(user, 1) - playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2) - C.visible_message("[user] begins restraining [C] with dark magic!", \ - "[user] begins shaping a dark magic around your wrists!") - if(do_mob(user, C, 30)) - if(!C.handcuffed) - C.handcuffed = new /obj/item/restraints/handcuffs/energy/cult/used(C) - C.update_handcuffed() - to_chat(user, "You shackle [C].") - add_attack_logs(user, C, "Handcuffed (shackle talisman)") - uses-- - else - to_chat(user, "[C] is already bound.") - else - to_chat(user, "You fail to shackle [C].") - else - to_chat(user, "[C] is already bound.") - if(uses <= 0) - user.drop_item() - qdel(src) - return - -/obj/item/restraints/handcuffs/energy/cult //For the talisman of shackling - name = "cult shackles" - desc = "Shackles that bind the wrists with sinister magic." - trashtype = /obj/item/restraints/handcuffs/energy/used - origin_tech = "materials=2;magnets=5" - -/obj/item/restraints/handcuffs/energy/used - desc = "energy discharge" - flags = DROPDEL - -/obj/item/restraints/handcuffs/energy/cult/used/dropped(mob/user) - user.visible_message("[user]'s shackles shatter in a discharge of dark magic!", \ - "Your [src] shatters in a discharge of dark magic!") - qdel(src) - . = ..() +/obj/item/paper/talisman + icon = 'icons/obj/paper.dmi' + icon_state = "paper_talisman" + var/cultist_name = "talisman" + var/cultist_desc = "A basic talisman. It serves no purpose." + var/invocation = "Naise meam!" + info = "


    " + var/uses = 1 + var/health_cost = 0 //The amount of health taken from the user when invoking the talisman + +/obj/item/paper/talisman/update_icon()//overriding this so the update_icon doesn't turn them into normal looking paper + SEND_SIGNAL(src, COMSIG_OBJ_UPDATE_ICON) + +/obj/item/paper/talisman/examine(mob/user) + . = ..() + if(iscultist(user) || user.stat == DEAD) + . += "Name: [cultist_name]" + . += "Effect: [cultist_desc]" + . += "Uses Remaining: [uses]" + else + . += "You see strange symbols on the paper. Are they supposed to mean something?" + +/obj/item/paper/talisman/attack_self(mob/living/user) + if(!iscultist(user)) + to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") + return + if(invoke(user)) + uses-- + if(uses <= 0) + user.drop_item() + qdel(src) + +/obj/item/paper/talisman/proc/invoke(mob/living/user, successfuluse = 1) + . = successfuluse + if(successfuluse) //if the calling whatever says we succeed, do the fancy stuff + if(invocation) + user.whisper(invocation) + if(health_cost && iscarbon(user)) + var/mob/living/carbon/C = user + C.apply_damage(health_cost, BRUTE, pick("l_arm", "r_arm")) + +//Malformed Talisman: If something goes wrong. +/obj/item/paper/talisman/malformed + cultist_name = "malformed talisman" + cultist_desc = "A talisman with gibberish scrawlings. No good can come from invoking this." + invocation = "Ra'sha yoka!" + +/obj/item/paper/talisman/malformed/invoke(mob/living/user, successfuluse = 1) + to_chat(user, "You feel a pain in your head. [SSticker.cultdat.entity_title3] is displeased.") + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.apply_damage(10, BRUTE, "head") + +//Supply Talisman: Has a few unique effects. Granted only to starter cultists. +/obj/item/paper/talisman/supply + cultist_name = "Supply Talisman" + icon_state = "supply" + cultist_desc = "A multi-use talisman that can create various objects. Intended to increase the cult's strength early on." + invocation = null + uses = 3 + +/obj/item/paper/talisman/supply/invoke(mob/living/user, successfuluse = 1) + var/dat = list() + dat += "There are [uses] bloody runes on the parchment.
    " + dat += "Please choose the chant to be imbued into the fabric of reality.
    " + dat += "
    " + dat += "N'ath reth sh'yro eth d'raggathnor! - Summons an arcane tome, used to scribe runes and communicate with other cultists.
    " + dat += "Bar'tea eas! - Provides 5 runed metal.
    " + dat += "Sas'so c'arta forbici! - Allows you to move to a selected teleportation rune.
    " + dat += "Ta'gh fara'qha fel d'amar det! - Allows you to destroy technology in a short range.
    " + dat += "Fuu ma'jin! - Allows you to stun a person by attacking them with the talisman.
    " + dat += "Kla'atu barada nikt'o! - Two use talisman, first use makes all nearby runes invisible, second use reveals nearby hidden runes.
    " + dat += "Kal'om neth! - Summons a soul stone, used to capture the spirits of dead or dying humans.
    " + dat += "Daa'ig osk! - Summons a construct shell for use with soulstone-captured souls. It is too large to carry on your person.
    " + var/datum/browser/popup = new(user, "talisman", "", 400, 400) + popup.set_content(jointext(dat, "")) + popup.open() + return 0 + +/obj/item/paper/talisman/supply/Topic(href, href_list) + if(src) + if(usr.stat || usr.restrained() || !in_range(src, usr)) + return + if(href_list["rune"]) + switch(href_list["rune"]) + if("newtome") + var/obj/item/tome/T = new(usr) + usr.put_in_hands(T) + if("metal") + if(istype(src, /obj/item/paper/talisman/supply/weak)) + usr.visible_message("Lesser supply talismans lack the strength to materialize runed metal!") + return + var/obj/item/stack/sheet/runed_metal/R = new(usr,5) + usr.put_in_hands(R) + if("teleport") + var/obj/item/paper/talisman/teleport/T = new(usr) + usr.put_in_hands(T) + if("emp") + var/obj/item/paper/talisman/emp/T = new(usr) + usr.put_in_hands(T) + if("runestun") + var/obj/item/paper/talisman/stun/T = new(usr) + usr.put_in_hands(T) + if("soulstone") + var/obj/item/soulstone/T = new(usr) + usr.put_in_hands(T) + if("construct") + new /obj/structure/constructshell(get_turf(usr)) + if("veiling") + var/obj/item/paper/talisman/true_sight/T = new(usr) + usr.put_in_hands(T) + uses-- + if(uses <= 0) + if(iscarbon(usr)) + var/mob/living/carbon/C = usr + C.drop_item() + visible_message("[src] crumbles to dust.") + qdel(src) + +/obj/item/paper/talisman/supply/weak + uses = 2 + +//Rite of Translocation: Same as rune +/obj/item/paper/talisman/teleport + cultist_name = "Talisman of Teleportation" + icon_state = "teleport" + cultist_desc = "A single-use talisman that will teleport a user to a random rune of the same keyword." + invocation = "Sas'so c'arta forbici!" + health_cost = 5 + +/obj/item/paper/talisman/teleport/invoke(mob/living/user, successfuluse = 1) + var/list/potential_runes = list() + var/list/teleportnames = list() + var/list/duplicaterunecount = list() + for(var/R in teleport_runes) + var/obj/effect/rune/teleport/T = R + var/resultkey = T.listkey + if(resultkey in teleportnames) + duplicaterunecount[resultkey]++ + resultkey = "[resultkey] ([duplicaterunecount[resultkey]])" + else + teleportnames.Add(resultkey) + duplicaterunecount[resultkey] = 1 + potential_runes[resultkey] = T + + if(!potential_runes.len) + to_chat(user, "There are no valid runes to teleport to!") + log_game("Teleport talisman failed - no other teleport runes") + return ..(user, 0) + + if(!is_level_reachable(user.z)) + to_chat(user, "You are not in the right dimension!") + log_game("Teleport talisman failed - user in away mission") + return ..(user, 0) + + var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked + var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to? + if(!src || QDELETED(src) || !user || user.l_hand != src && user.r_hand != src || user.incapacitated() || !actual_selected_rune) + return ..(user, 0) + + user.visible_message("Dust flows from [user]'s hand, and [user.p_they()] disappear[user.p_s()] in a flash of red light!", \ + "You speak the words of the talisman and find yourself somewhere else!") + user.forceMove(get_turf(actual_selected_rune)) + return ..() + + +/obj/item/paper/talisman/summon_tome + cultist_name = "Talisman of Tome Summoning" + icon_state = "tome" + cultist_desc = "A one-use talisman that will call an untranslated tome from the archives of a cult." + invocation = "N'ath reth sh'yro eth d'raggathnor!" + health_cost = 1 + +/obj/item/paper/talisman/summon_tome/invoke(mob/living/user, successfuluse = 1) + . = ..() + user.visible_message("[user]'s hand glows red for a moment.", \ + "You speak the words of the talisman!") + new /obj/item/tome(get_turf(user)) + user.visible_message("A tome appears at [user]'s feet!", \ + "An arcane tome materializes at your feet.") + +/obj/item/paper/talisman/true_sight + cultist_name = "Talisman of Veiling" + icon_state = "veil" + cultist_desc = "A multi-use talisman that hides nearby runes. On its second use, will reveal nearby runes." + invocation = "Kla'atu barada nikt'o!" + health_cost = 1 + uses = 2 + var/revealing = FALSE //if it reveals or not + +/obj/item/paper/talisman/true_sight/invoke(mob/living/user, successfuluse = 1) + . = ..() + if(!revealing) + user.visible_message("Thin grey dust falls from [user]'s hand!", \ + "You speak the words of the talisman, hiding nearby runes.") + invocation = "Nikt'o barada kla'atu!" + revealing = TRUE + for(var/obj/effect/rune/R in range(3,user)) + R.talismanhide() + else + user.visible_message("A flash of light shines from [user]'s hand!", \ + "You speak the words of the talisman, revealing nearby runes.") + for(var/obj/effect/rune/R in range(3,user)) + R.talismanreveal() + +//Rite of False Truths: Same as rune +/obj/item/paper/talisman/make_runes_fake + cultist_name = "Talisman of Disguising" + icon_state = "disguising" + cultist_desc = "A talisman that will make nearby runes appear fake." + invocation = "By'o nar'nar!" + +/obj/item/paper/talisman/make_runes_fake/invoke(mob/living/user, successfuluse = 1) + . = ..() + user.visible_message("Dust flows from [user]s hand.", \ + "You speak the words of the talisman, making nearby runes appear fake.") + for(var/obj/effect/rune/R in orange(6,user)) + R.talismanfake() + R.desc = "A rune drawn in crayon." + + +//Rite of Disruption: Weaker than rune +/obj/item/paper/talisman/emp + cultist_name = "Talisman of Electromagnetic Pulse" + icon_state = "emp" + cultist_desc = "A talisman that will cause a moderately-sized electromagnetic pulse." + invocation = "Ta'gh fara'qha fel d'amar det!" + health_cost = 5 + +/obj/item/paper/talisman/emp/invoke(mob/living/user, successfuluse = 1) + . = ..() + user.visible_message("[user]'s hand flashes a bright blue!", \ + "You speak the words of the talisman, emitting an EMP blast.") + empulse(src, 4, 8, 1) + + +//Rite of Disorientation: Stuns and inhibit speech on a single target for quite some time +/obj/item/paper/talisman/stun + cultist_name = "Talisman of Stunning" + icon_state = "stunning" + cultist_desc = "A talisman that will stun and inhibit speech on a single target. To use, attack target directly." + invocation = "Dream sign:Evil sealing talisman!" + health_cost = 10 + +/obj/item/paper/talisman/stun/invoke(mob/living/user, successfuluse = 0) + if(successfuluse) //if we're forced to be successful(we normally aren't) then do the normal stuff + return ..() + if(iscultist(user)) + to_chat(user, "To use this talisman, attack the target directly.") + else + to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") + return 0 + +/obj/item/paper/talisman/stun/attack(mob/living/target, mob/living/user, successfuluse = 1) + if(iscultist(user)) + invoke(user, 1) + user.visible_message("[user] holds up [src], which explodes in a flash of red light!", \ + "You stun [target] with the talisman!") + var/obj/item/nullrod/N = locate() in target + if(N) + target.visible_message("[target]'s holy weapon absorbs the talisman's light!", \ + "Your holy weapon absorbs the blinding light!") + else + add_attack_logs(user, target, "Stunned with a talisman") + target.Weaken(10) + target.Stun(10) + target.flash_eyes(1,1) + if(issilicon(target)) + var/mob/living/silicon/S = target + S.emp_act(1) + if(iscarbon(target)) + var/mob/living/carbon/C = target + C.AdjustSilence(5) + C.AdjustStuttering(15) + C.AdjustCultSlur(20) + C.Jitter(15) + user.drop_item() + qdel(src) + return + ..() + + +//Rite of Arming: Equips cultist armor on the user, where available +/obj/item/paper/talisman/armor + cultist_name = "Talisman of Arming" + icon_state = "arming" + cultist_desc = "A talisman that will equip the invoker with cultist equipment if there is a slot to equip it to." + invocation = "N'ath reth sh'yro eth draggathnor!" + +/obj/item/paper/talisman/armor/invoke(mob/living/user, successfuluse = 1) + . = ..() + var/mob/living/carbon/human/H = user + user.visible_message("Otherworldly armor suddenly appears on [user]!", \ + "You speak the words of the talisman, arming yourself!") + + H.equip_to_slot_or_del(new /obj/item/clothing/suit/hooded/cultrobes/alt(user), slot_wear_suit) + H.equip_to_slot_or_del(new /obj/item/storage/backpack/cultpack(user), slot_back) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/cult(user), slot_shoes) + H.put_in_hands(new /obj/item/melee/cultblade(user)) + H.put_in_hands(new /obj/item/restraints/legcuffs/bola/cult(user)) + +/obj/item/paper/talisman/armor/attack(mob/living/target, mob/living/user) + if(iscultist(user) && iscultist(target)) + user.drop_item() + qdel(src) + invoke(target) + return + ..() + + +//Talisman of Horrors: Breaks the mind of the victim with nightmarish hallucinations +/obj/item/paper/talisman/horror + cultist_name = "Talisman of Horrors" + icon_state = "horror" + cultist_desc = "A talisman that will break the mind of the victim with nightmarish hallucinations." + invocation = "Lo'Nab Na'Dm!" + +/obj/item/paper/talisman/horror/attack(mob/living/target, mob/living/user) + if(iscultist(user)) + to_chat(user, "You disturb [target] with visions of the end!") + if(iscarbon(target)) + var/mob/living/carbon/H = target + H.AdjustHallucinate(30) + qdel(src) + + +//Talisman of Fabrication: Creates a construct shell out of 25 metal sheets. +/obj/item/paper/talisman/construction + cultist_name = "Talisman of Construction" + icon_state = "construction" + cultist_desc = "Use this talisman on at least twenty-five metal sheets to create an empty construct shell or on plasteel to make runed metal" + invocation = "Ethra p'ni dedol!" + uses = 25 + +/obj/item/paper/talisman/construction/attack_self(mob/living/user) + if(iscultist(user)) + to_chat(user, "To use this talisman, place it upon a stack of metal sheets or plasteel sheets!.") + else + to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") + + +/obj/item/paper/talisman/construction/attack(obj/M,mob/living/user) + if(iscultist(user)) + to_chat(user, "This talisman will only work on a stack of metal sheets or plasteel sheets!!") + log_game("Construct talisman failed - not a valid target") + +/obj/item/paper/talisman/construction/afterattack(obj/item/stack/sheet/target, mob/user, proximity_flag, click_parameters) + ..() + if(proximity_flag && iscultist(user)) + if(istype(target, /obj/item/stack/sheet/metal)) + var/turf/T = get_turf(target) + if(target.use(25)) + new /obj/structure/constructshell(T) + to_chat(user, "The talisman clings to the metal and twists it into a construct shell!") + user << sound('sound/magic/staff_chaos.ogg',0,1,25) + qdel(src) + return + if(istype(target, /obj/item/stack/sheet/plasteel)) + var/turf/T = get_turf(target) + var/quantity = min(target.amount, uses) + uses -= quantity + new /obj/item/stack/sheet/runed_metal(T,quantity) + target.use(quantity) + to_chat(user, "The talisman clings to the plasteel, transforming it into runed metal!") + user << sound('sound/magic/staff_chaos.ogg',0,1,25) + invoke(user, 1) + if(uses <= 0) + qdel(src) + else + to_chat(user, "The talisman must be used on metal or plasteel!") + +//Talisman of Shackling: Applies special cuffs directly from the talisman +/obj/item/paper/talisman/shackle + cultist_name = "Talisman of Shackling" + icon_state = "shackling" + cultist_desc = "Use this talisman on a victim to handcuff them with dark bindings." + invocation = "In'totum Lig'abis!" + uses = 4 + +/obj/item/paper/talisman/shackle/invoke(mob/living/user, successfuluse = 0) + if(successfuluse) //if we're forced to be successful(we normally aren't) then do the normal stuff + return ..() + if(iscultist(user)) + to_chat(user, "To use this talisman, attack the target directly.") + else + to_chat(user, "You see strange symbols on the paper. Are they supposed to mean something?") + return 0 + +/obj/item/paper/talisman/shackle/attack(mob/living/carbon/target, mob/living/user) + if(iscultist(user) && istype(target)) + if(target.stat == DEAD) + user.visible_message("This talisman's magic does not affect the dead!") + return + CuffAttack(target, user) + return + ..() + +/obj/item/paper/talisman/shackle/proc/CuffAttack(mob/living/carbon/C, mob/living/user) + if(!C.handcuffed) + invoke(user, 1) + playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2) + C.visible_message("[user] begins restraining [C] with dark magic!", \ + "[user] begins shaping a dark magic around your wrists!") + if(do_mob(user, C, 30)) + if(!C.handcuffed) + C.handcuffed = new /obj/item/restraints/handcuffs/energy/cult/used(C) + C.update_handcuffed() + to_chat(user, "You shackle [C].") + add_attack_logs(user, C, "Handcuffed (shackle talisman)") + uses-- + else + to_chat(user, "[C] is already bound.") + else + to_chat(user, "You fail to shackle [C].") + else + to_chat(user, "[C] is already bound.") + if(uses <= 0) + user.drop_item() + qdel(src) + return + +/obj/item/restraints/handcuffs/energy/cult //For the talisman of shackling + name = "cult shackles" + desc = "Shackles that bind the wrists with sinister magic." + trashtype = /obj/item/restraints/handcuffs/energy/used + origin_tech = "materials=2;magnets=5" + +/obj/item/restraints/handcuffs/energy/used + desc = "energy discharge" + flags = DROPDEL + +/obj/item/restraints/handcuffs/energy/cult/used/dropped(mob/user) + user.visible_message("[user]'s shackles shatter in a discharge of dark magic!", \ + "Your [src] shatters in a discharge of dark magic!") + qdel(src) + . = ..() diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm index a5dcef8d4e7..52edccc9e73 100644 --- a/code/game/gamemodes/extended/extended.dm +++ b/code/game/gamemodes/extended/extended.dm @@ -1,14 +1,14 @@ -/datum/game_mode/extended - name = "extended" - config_tag = "extended" - required_players = 0 - -/datum/game_mode/announce() - to_chat(world, "The current game mode is - Extended Role-Playing!") - to_chat(world, "Just have fun and role-play!") - -/datum/game_mode/extended/pre_setup() - return 1 - -/datum/game_mode/extended/post_setup() - ..() \ No newline at end of file +/datum/game_mode/extended + name = "extended" + config_tag = "extended" + required_players = 0 + +/datum/game_mode/announce() + to_chat(world, "The current game mode is - Extended Role-Playing!") + to_chat(world, "Just have fun and role-play!") + +/datum/game_mode/extended/pre_setup() + return 1 + +/datum/game_mode/extended/post_setup() + ..() diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index e7fb9b4969f..258d6ea20c6 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -1,522 +1,522 @@ -/* - * GAMEMODES (by Rastaf0) - * - * In the new mode system all special roles are fully supported. - * You can have proper wizards/traitors/changelings/cultists during any mode. - * Only two things really depends on gamemode: - * 1. Starting roles, equipment and preparations - * 2. Conditions of finishing the round. - * - */ - - -/datum/game_mode - var/name = "invalid" - var/config_tag = null - var/intercept_hacked = 0 - var/votable = 1 - var/probability = 0 - var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm - var/explosion_in_progress = 0 //sit back and relax - var/list/datum/mind/modePlayer = new - var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist - var/list/protected_jobs = list() // Jobs that can't be traitors - var/list/protected_species = list() // Species that can't be traitors - var/required_players = 0 - var/required_enemies = 0 - var/recommended_enemies = 0 - var/newscaster_announcements = null - var/ert_disabled = 0 - var/uplink_welcome = "Syndicate Uplink Console:" - var/uplink_uses = 20 - - var/const/waittime_l = 600 //lower bound on time before intercept arrives (in tenths of seconds) - var/const/waittime_h = 1800 //upper bound on time before intercept arrives (in tenths of seconds) - var/list/player_draft_log = list() - var/list/datum/mind/xenos = list() - var/list/datum/mind/eventmiscs = list() - - var/list/datum/station_goal/station_goals = list() // A list of all station goals for this game mode - -/datum/game_mode/proc/announce() //to be calles when round starts - to_chat(world, "Notice: [src] did not define announce()") - - -///can_start() -///Checks to see if the game can be setup and ran with the current number of players or whatnot. -/datum/game_mode/proc/can_start() - var/playerC = 0 - for(var/mob/new_player/player in GLOB.player_list) - if((player.client)&&(player.ready)) - playerC++ - - if(!config.enable_gamemode_player_limit || (playerC >= required_players)) - return 1 - return 0 - -//pre_pre_setup() For when you really don't want certain jobs ingame. -/datum/game_mode/proc/pre_pre_setup() - - return 1 - -///pre_setup() -///Attempts to select players for special roles the mode might have. -/datum/game_mode/proc/pre_setup() - - return 1 - - -///post_setup() -///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things -/datum/game_mode/proc/post_setup() - - spawn (ROUNDSTART_LOGOUT_REPORT_TIME) - display_roundstart_logout_report() - - feedback_set_details("round_start","[time2text(world.realtime)]") - if(SSticker && SSticker.mode) - feedback_set_details("game_mode","[SSticker.mode]") -// if(revdata) -// feedback_set_details("revision","[revdata.revision]") - feedback_set_details("server_ip","[world.internet_address]:[world.port]") - generate_station_goals() - start_state = new /datum/station_state() - start_state.count() - return 1 - -///process() -///Called by the gameticker -/datum/game_mode/process() - return 0 - -//Called by the gameticker -/datum/game_mode/proc/process_job_tasks() - var/obj/machinery/message_server/useMS = null - if(message_servers) - for(var/obj/machinery/message_server/MS in message_servers) - if(MS.active) - useMS = MS - break - for(var/mob/M in GLOB.player_list) - if(M.mind) - var/obj/item/pda/P=null - for(var/obj/item/pda/check_pda in PDAs) - if(check_pda.owner==M.name) - P=check_pda - break - var/count=0 - for(var/datum/job_objective/objective in M.mind.job_objectives) - count++ - var/msg="" - var/pay=0 - if(objective.per_unit && objective.units_compensated0) - if(M.mind.initial_account) - M.mind.initial_account.credit(pay, "Payment", "\[CLASSIFIED\] Terminal #[rand(111,333)]", "[command_name()] Payroll") - msg += "You have been sent the $[pay], as agreed." - else - msg += "However, we were unable to send you the $[pay] you're entitled." - if(useMS && P) - useMS.send_pda_message("[P.owner]", "[command_name()] Payroll", msg) - - var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger) - PM.notify("Message from [command_name()] (Payroll), \"[msg]\" (Unable to Reply)", 0) - break - -/datum/game_mode/proc/check_finished() //to be called by ticker - if((SSshuttle.emergency && SSshuttle.emergency.mode >= SHUTTLE_ENDGAME) || station_was_nuked) - return 1 - return 0 - -/datum/game_mode/proc/cleanup() //This is called when the round has ended but not the game, if any cleanup would be necessary in that case. - return - -/datum/game_mode/proc/declare_completion() - var/clients = 0 - var/surviving_humans = 0 - var/surviving_total = 0 - var/ghosts = 0 - var/escaped_humans = 0 - var/escaped_total = 0 - var/escaped_on_pod_1 = 0 - var/escaped_on_pod_2 = 0 - var/escaped_on_pod_3 = 0 - var/escaped_on_pod_5 = 0 - var/escaped_on_shuttle = 0 - - var/list/area/escape_locations = list(/area/shuttle/escape, /area/shuttle/escape_pod1/centcom, /area/shuttle/escape_pod2/centcom, /area/shuttle/escape_pod3/centcom, /area/shuttle/escape_pod5/centcom) - - if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) //shuttle didn't get to centcom - escape_locations -= /area/shuttle/escape - - for(var/mob/M in GLOB.player_list) - if(M.client) - clients++ - if(ishuman(M)) - if(!M.stat) - surviving_humans++ - if(M.loc && M.loc.loc && M.loc.loc.type in escape_locations) - escaped_humans++ - if(!M.stat) - surviving_total++ - if(M.loc && M.loc.loc && M.loc.loc.type in escape_locations) - escaped_total++ - - if(M.loc && M.loc.loc && M.loc.loc.type == SSshuttle.emergency.areaInstance.type && SSshuttle.emergency.mode >= SHUTTLE_ENDGAME) - escaped_on_shuttle++ - - if(M.loc && M.loc.loc && M.loc.loc.type == /area/shuttle/escape_pod1/centcom) - escaped_on_pod_1++ - if(M.loc && M.loc.loc && M.loc.loc.type == /area/shuttle/escape_pod2/centcom) - escaped_on_pod_2++ - if(M.loc && M.loc.loc && M.loc.loc.type == /area/shuttle/escape_pod3/centcom) - escaped_on_pod_3++ - if(M.loc && M.loc.loc && M.loc.loc.type == /area/shuttle/escape_pod5/centcom) - escaped_on_pod_5++ - - if(isobserver(M)) - ghosts++ - - if(clients > 0) - feedback_set("round_end_clients",clients) - if(ghosts > 0) - feedback_set("round_end_ghosts",ghosts) - if(surviving_humans > 0) - feedback_set("survived_human",surviving_humans) - if(surviving_total > 0) - feedback_set("survived_total",surviving_total) - if(escaped_humans > 0) - feedback_set("escaped_human",escaped_humans) - if(escaped_total > 0) - feedback_set("escaped_total",escaped_total) - if(escaped_on_shuttle > 0) - feedback_set("escaped_on_shuttle",escaped_on_shuttle) - if(escaped_on_pod_1 > 0) - feedback_set("escaped_on_pod_1",escaped_on_pod_1) - if(escaped_on_pod_2 > 0) - feedback_set("escaped_on_pod_2",escaped_on_pod_2) - if(escaped_on_pod_3 > 0) - feedback_set("escaped_on_pod_3",escaped_on_pod_3) - if(escaped_on_pod_5 > 0) - feedback_set("escaped_on_pod_5",escaped_on_pod_5) - - send2mainirc("A round of [src.name] has ended - [surviving_total] survivors, [ghosts] ghosts.") - return 0 - - -/datum/game_mode/proc/check_win() //universal trigger to be called at mob death, nuke explosion, etc. To be called from everywhere. - return 0 - -/datum/game_mode/proc/get_players_for_role(var/role, override_jobbans=0) - var/list/players = list() - var/list/candidates = list() - //var/list/drafted = list() - //var/datum/mind/applicant = null - - var/roletext = get_roletext(role) - - // Assemble a list of active players without jobbans. - for(var/mob/new_player/player in GLOB.player_list) - if(player.client && player.ready && player.has_valid_preferences()) - if(!jobban_isbanned(player, "Syndicate") && !jobban_isbanned(player, roletext)) - if(player_old_enough_antag(player.client,role)) - players += player - - // Shuffle the players list so that it becomes ping-independent. - players = shuffle(players) - - // Get a list of all the people who want to be the antagonist for this round, except those with incompatible species - for(var/mob/new_player/player in players) - if(!player.client.skip_antag) - if((role in player.client.prefs.be_special) && !(player.client.prefs.species in protected_species)) - player_draft_log += "[player.key] had [roletext] enabled, so we are drafting them." - candidates += player.mind - players -= player - - // If we don't have enough antags, draft people who voted for the round. - if(candidates.len < recommended_enemies) - for(var/key in SSvote.round_voters) - for(var/mob/new_player/player in players) - if(player.ckey == key) - player_draft_log += "[player.key] voted for this round, so we are drafting them." - candidates += player.mind - players -= player - break - - // Remove candidates who want to be antagonist but have a job that precludes it - if(restricted_jobs) - for(var/datum/mind/player in candidates) - for(var/job in restricted_jobs) - if(player.assigned_role == job) - candidates -= player - - - return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies - // recommended_enemies if the number of people with that role set to yes is less than recomended_enemies, - // Less if there are not enough valid players in the game entirely to make recommended_enemies. - - -/datum/game_mode/proc/latespawn(var/mob) - -/* -/datum/game_mode/proc/check_player_role_pref(var/role, var/mob/player) - if(player.preferences.be_special & role) - return 1 - return 0 -*/ - -/datum/game_mode/proc/num_players() - . = 0 - for(var/mob/new_player/P in GLOB.player_list) - if(P.client && P.ready) - .++ - -/datum/game_mode/proc/num_players_started() - . = 0 - for(var/mob/living/carbon/human/H in GLOB.player_list) - if(H.client) - .++ - -/////////////////////////////////// -//Keeps track of all living heads// -/////////////////////////////////// -/datum/game_mode/proc/get_living_heads() - . = list() - for(var/mob/living/carbon/human/player in GLOB.mob_list) - var/list/real_command_positions = command_positions.Copy() - "Nanotrasen Representative" - if(player.stat != DEAD && player.mind && (player.mind.assigned_role in real_command_positions)) - . |= player.mind - - -//////////////////////////// -//Keeps track of all heads// -//////////////////////////// -/datum/game_mode/proc/get_all_heads() - . = list() - for(var/mob/player in GLOB.mob_list) - var/list/real_command_positions = command_positions.Copy() - "Nanotrasen Representative" - if(player.mind && (player.mind.assigned_role in real_command_positions)) - . |= player.mind - -////////////////////////////////////////////// -//Keeps track of all living security members// -////////////////////////////////////////////// -/datum/game_mode/proc/get_living_sec() - . = list() - for(var/mob/living/carbon/human/player in GLOB.mob_list) - if(player.stat != DEAD && player.mind && (player.mind.assigned_role in security_positions)) - . |= player.mind - -//////////////////////////////////////// -//Keeps track of all security members// -//////////////////////////////////////// -/datum/game_mode/proc/get_all_sec() - . = list() - for(var/mob/living/carbon/human/player in GLOB.mob_list) - if(player.mind && (player.mind.assigned_role in security_positions)) - . |= player.mind - -/datum/game_mode/proc/check_antagonists_topic(href, href_list[]) - return 0 - -/datum/game_mode/New() - newscaster_announcements = pick(newscaster_standard_feeds) - -////////////////////////// -//Reports player logouts// -////////////////////////// -proc/display_roundstart_logout_report() - var/msg = "Roundstart logout report\n\n" - for(var/mob/living/L in GLOB.mob_list) - - if(L.ckey) - var/found = 0 - for(var/client/C in GLOB.clients) - if(C.ckey == L.ckey) - found = 1 - break - if(!found) - msg += "[L.name] ([L.ckey]), the [L.job] (Disconnected)\n" - - - if(L.ckey && L.client) - if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something) - msg += "[L.name] ([L.ckey]), the [L.job] (Connected, Inactive)\n" - continue //AFK client - if(L.stat) - if(L.suiciding) //Suicider - msg += "[L.name] ([L.ckey]), the [L.job] (Suicide)\n" - SSjobs.FreeRole(L.job) - message_admins("[key_name_admin(L)], the [L.job] has been freed due to (Early Round Suicide)\n") - continue //Disconnected client - if(L.stat == UNCONSCIOUS) - msg += "[L.name] ([L.ckey]), the [L.job] (Dying)\n" - continue //Unconscious - if(L.stat == DEAD) - msg += "[L.name] ([L.ckey]), the [L.job] (Dead)\n" - continue //Dead - - continue //Happy connected client - for(var/mob/dead/observer/D in GLOB.mob_list) - if(D.mind && (D.mind.original == L || D.mind.current == L)) - if(L.stat == DEAD) - if(L.suiciding) //Suicider - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Suicide)\n" - continue //Disconnected client - else - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" - continue //Dead mob, ghost abandoned - else - if(D.can_reenter_corpse) - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (This shouldn't appear.)\n" - continue //Lolwhat - else - msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Ghosted)\n" - SSjobs.FreeRole(L.job) - message_admins("[key_name_admin(L)], the [L.job] has been freed due to (Early Round Ghosted While Alive)\n") - continue //Ghosted while alive - - - - for(var/mob/M in GLOB.mob_list) - if(check_rights(R_ADMIN, 0, M)) - to_chat(M, msg) - -//Announces objectives/generic antag text. -/proc/show_generic_antag_text(var/datum/mind/player) - if(player.current) - to_chat(player.current, "You are an antagonist! Within the rules, \ - try to act as an opposing force to the crew. Further RP and try to make sure \ - other players have fun! If you are confused or at a loss, always adminhelp, \ - and before taking extreme actions, please try to also contact the administration! \ - Think through your actions and make the roleplay immersive! Please remember all \ - rules aside from those without explicit exceptions apply to antagonists.") - -/proc/show_objectives(var/datum/mind/player) - if(!player || !player.current) return - - var/obj_count = 1 - to_chat(player.current, "Your current objectives:") - for(var/datum/objective/objective in player.objectives) - to_chat(player.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ - -/proc/get_roletext(var/role) - return role - -/proc/get_nuke_code() - var/nukecode = "ERROR" - for(var/obj/machinery/nuclearbomb/bomb in world) - if(bomb && bomb.r_code && is_station_level(bomb.z)) - nukecode = bomb.r_code - return nukecode - -/datum/game_mode/proc/replace_jobbanned_player(mob/living/M, role_type) - var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a [role_type]?", role_type, 0, 100) - var/mob/dead/observer/theghost = null - if(candidates.len) - theghost = pick(candidates) - to_chat(M, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") - message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)]) to replace a jobbanned player.") - M.ghostize() - M.key = theghost.key - else - message_admins("[M] ([M.key] has been converted into [role_type] with an active antagonist jobban for said role since no ghost has volunteered to take [M.p_their()] place.") - to_chat(M, "You have been converted into [role_type] with an active jobban. Any further violations of the rules on your part are likely to result in a permanent ban.") - -/proc/printplayer(datum/mind/ply, fleecheck) - var/jobtext = "" - if(ply.assigned_role) - jobtext = " the [ply.assigned_role]" - var/text = "[ply.key] was [ply.name][jobtext] and" - if(ply.current) - if(ply.current.stat == DEAD) - text += " died" - else - text += " survived" - if(fleecheck) - var/turf/T = get_turf(ply.current) - if(!T || !is_station_level(T.z)) - text += " while fleeing the station" - if(ply.current.real_name != ply.name) - text += " as [ply.current.real_name]" - else - text += " had [ply.p_their()] body destroyed" - return text - -/proc/printeventplayer(datum/mind/ply) - var/text = "[ply.key] was [ply.name]" - if(ply.special_role != SPECIAL_ROLE_EVENTMISC) - text += " the [ply.special_role]" - text += " and" - if(ply.current) - if(ply.current.stat == DEAD) - text += " died" - else - text += " survived" - else - text += " had [ply.p_their()] body destroyed" - return text - -/proc/printobjectives(datum/mind/ply) - var/list/objective_parts = list() - var/count = 1 - for(var/datum/objective/objective in ply.objectives) - if(objective.check_completion()) - objective_parts += "Objective #[count]: [objective.explanation_text] Success!" - else - objective_parts += "Objective #[count]: [objective.explanation_text] Fail." - count++ - return objective_parts.Join("
    ") - -/datum/game_mode/proc/generate_station_goals() - var/list/possible = list() - for(var/T in subtypesof(/datum/station_goal)) - var/datum/station_goal/G = T - if(config_tag in initial(G.gamemode_blacklist)) - continue - possible += T - var/goal_weights = 0 - while(possible.len && goal_weights < STATION_GOAL_BUDGET) - var/datum/station_goal/picked = pick_n_take(possible) - goal_weights += initial(picked.weight) - station_goals += new picked - - if(station_goals.len) - send_station_goals_message() - -/datum/game_mode/proc/send_station_goals_message() - var/message_text = "
    " - message_text += "

    [command_name()] Orders


    " - message_text += "Special Orders for [station_name()]:

    " - - for(var/datum/station_goal/G in station_goals) - G.on_report() - message_text += G.get_report() - message_text += "
    " - - print_command_report(message_text, "[command_name()] Orders") - -/datum/game_mode/proc/declare_station_goal_completion() - for(var/V in station_goals) - var/datum/station_goal/G = V - G.print_result() - -/datum/game_mode/proc/update_eventmisc_icons_added(datum/mind/mob_mind) - var/datum/atom_hud/antag/antaghud = huds[ANTAG_HUD_EVENTMISC] - antaghud.join_hud(mob_mind.current) - set_antag_hud(mob_mind.current, "hudevent") - -/datum/game_mode/proc/update_eventmisc_icons_removed(datum/mind/mob_mind) - var/datum/atom_hud/antag/antaghud = huds[ANTAG_HUD_EVENTMISC] - antaghud.leave_hud(mob_mind.current) - set_antag_hud(mob_mind.current, null) \ No newline at end of file +/* + * GAMEMODES (by Rastaf0) + * + * In the new mode system all special roles are fully supported. + * You can have proper wizards/traitors/changelings/cultists during any mode. + * Only two things really depends on gamemode: + * 1. Starting roles, equipment and preparations + * 2. Conditions of finishing the round. + * + */ + + +/datum/game_mode + var/name = "invalid" + var/config_tag = null + var/intercept_hacked = 0 + var/votable = 1 + var/probability = 0 + var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm + var/explosion_in_progress = 0 //sit back and relax + var/list/datum/mind/modePlayer = new + var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist + var/list/protected_jobs = list() // Jobs that can't be traitors + var/list/protected_species = list() // Species that can't be traitors + var/required_players = 0 + var/required_enemies = 0 + var/recommended_enemies = 0 + var/newscaster_announcements = null + var/ert_disabled = 0 + var/uplink_welcome = "Syndicate Uplink Console:" + var/uplink_uses = 20 + + var/const/waittime_l = 600 //lower bound on time before intercept arrives (in tenths of seconds) + var/const/waittime_h = 1800 //upper bound on time before intercept arrives (in tenths of seconds) + var/list/player_draft_log = list() + var/list/datum/mind/xenos = list() + var/list/datum/mind/eventmiscs = list() + + var/list/datum/station_goal/station_goals = list() // A list of all station goals for this game mode + +/datum/game_mode/proc/announce() //to be calles when round starts + to_chat(world, "Notice: [src] did not define announce()") + + +///can_start() +///Checks to see if the game can be setup and ran with the current number of players or whatnot. +/datum/game_mode/proc/can_start() + var/playerC = 0 + for(var/mob/new_player/player in GLOB.player_list) + if((player.client)&&(player.ready)) + playerC++ + + if(!config.enable_gamemode_player_limit || (playerC >= required_players)) + return 1 + return 0 + +//pre_pre_setup() For when you really don't want certain jobs ingame. +/datum/game_mode/proc/pre_pre_setup() + + return 1 + +///pre_setup() +///Attempts to select players for special roles the mode might have. +/datum/game_mode/proc/pre_setup() + + return 1 + + +///post_setup() +///Everyone should now be on the station and have their normal gear. This is the place to give the special roles extra things +/datum/game_mode/proc/post_setup() + + spawn (ROUNDSTART_LOGOUT_REPORT_TIME) + display_roundstart_logout_report() + + feedback_set_details("round_start","[time2text(world.realtime)]") + if(SSticker && SSticker.mode) + feedback_set_details("game_mode","[SSticker.mode]") +// if(revdata) +// feedback_set_details("revision","[revdata.revision]") + feedback_set_details("server_ip","[world.internet_address]:[world.port]") + generate_station_goals() + start_state = new /datum/station_state() + start_state.count() + return 1 + +///process() +///Called by the gameticker +/datum/game_mode/process() + return 0 + +//Called by the gameticker +/datum/game_mode/proc/process_job_tasks() + var/obj/machinery/message_server/useMS = null + if(message_servers) + for(var/obj/machinery/message_server/MS in message_servers) + if(MS.active) + useMS = MS + break + for(var/mob/M in GLOB.player_list) + if(M.mind) + var/obj/item/pda/P=null + for(var/obj/item/pda/check_pda in PDAs) + if(check_pda.owner==M.name) + P=check_pda + break + var/count=0 + for(var/datum/job_objective/objective in M.mind.job_objectives) + count++ + var/msg="" + var/pay=0 + if(objective.per_unit && objective.units_compensated0) + if(M.mind.initial_account) + M.mind.initial_account.credit(pay, "Payment", "\[CLASSIFIED\] Terminal #[rand(111,333)]", "[command_name()] Payroll") + msg += "You have been sent the $[pay], as agreed." + else + msg += "However, we were unable to send you the $[pay] you're entitled." + if(useMS && P) + useMS.send_pda_message("[P.owner]", "[command_name()] Payroll", msg) + + var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger) + PM.notify("Message from [command_name()] (Payroll), \"[msg]\" (Unable to Reply)", 0) + break + +/datum/game_mode/proc/check_finished() //to be called by ticker + if((SSshuttle.emergency && SSshuttle.emergency.mode >= SHUTTLE_ENDGAME) || station_was_nuked) + return 1 + return 0 + +/datum/game_mode/proc/cleanup() //This is called when the round has ended but not the game, if any cleanup would be necessary in that case. + return + +/datum/game_mode/proc/declare_completion() + var/clients = 0 + var/surviving_humans = 0 + var/surviving_total = 0 + var/ghosts = 0 + var/escaped_humans = 0 + var/escaped_total = 0 + var/escaped_on_pod_1 = 0 + var/escaped_on_pod_2 = 0 + var/escaped_on_pod_3 = 0 + var/escaped_on_pod_5 = 0 + var/escaped_on_shuttle = 0 + + var/list/area/escape_locations = list(/area/shuttle/escape, /area/shuttle/escape_pod1/centcom, /area/shuttle/escape_pod2/centcom, /area/shuttle/escape_pod3/centcom, /area/shuttle/escape_pod5/centcom) + + if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) //shuttle didn't get to centcom + escape_locations -= /area/shuttle/escape + + for(var/mob/M in GLOB.player_list) + if(M.client) + clients++ + if(ishuman(M)) + if(!M.stat) + surviving_humans++ + if(M.loc && M.loc.loc && M.loc.loc.type in escape_locations) + escaped_humans++ + if(!M.stat) + surviving_total++ + if(M.loc && M.loc.loc && M.loc.loc.type in escape_locations) + escaped_total++ + + if(M.loc && M.loc.loc && M.loc.loc.type == SSshuttle.emergency.areaInstance.type && SSshuttle.emergency.mode >= SHUTTLE_ENDGAME) + escaped_on_shuttle++ + + if(M.loc && M.loc.loc && M.loc.loc.type == /area/shuttle/escape_pod1/centcom) + escaped_on_pod_1++ + if(M.loc && M.loc.loc && M.loc.loc.type == /area/shuttle/escape_pod2/centcom) + escaped_on_pod_2++ + if(M.loc && M.loc.loc && M.loc.loc.type == /area/shuttle/escape_pod3/centcom) + escaped_on_pod_3++ + if(M.loc && M.loc.loc && M.loc.loc.type == /area/shuttle/escape_pod5/centcom) + escaped_on_pod_5++ + + if(isobserver(M)) + ghosts++ + + if(clients > 0) + feedback_set("round_end_clients",clients) + if(ghosts > 0) + feedback_set("round_end_ghosts",ghosts) + if(surviving_humans > 0) + feedback_set("survived_human",surviving_humans) + if(surviving_total > 0) + feedback_set("survived_total",surviving_total) + if(escaped_humans > 0) + feedback_set("escaped_human",escaped_humans) + if(escaped_total > 0) + feedback_set("escaped_total",escaped_total) + if(escaped_on_shuttle > 0) + feedback_set("escaped_on_shuttle",escaped_on_shuttle) + if(escaped_on_pod_1 > 0) + feedback_set("escaped_on_pod_1",escaped_on_pod_1) + if(escaped_on_pod_2 > 0) + feedback_set("escaped_on_pod_2",escaped_on_pod_2) + if(escaped_on_pod_3 > 0) + feedback_set("escaped_on_pod_3",escaped_on_pod_3) + if(escaped_on_pod_5 > 0) + feedback_set("escaped_on_pod_5",escaped_on_pod_5) + + send2mainirc("A round of [src.name] has ended - [surviving_total] survivors, [ghosts] ghosts.") + return 0 + + +/datum/game_mode/proc/check_win() //universal trigger to be called at mob death, nuke explosion, etc. To be called from everywhere. + return 0 + +/datum/game_mode/proc/get_players_for_role(var/role, override_jobbans=0) + var/list/players = list() + var/list/candidates = list() + //var/list/drafted = list() + //var/datum/mind/applicant = null + + var/roletext = get_roletext(role) + + // Assemble a list of active players without jobbans. + for(var/mob/new_player/player in GLOB.player_list) + if(player.client && player.ready && player.has_valid_preferences()) + if(!jobban_isbanned(player, "Syndicate") && !jobban_isbanned(player, roletext)) + if(player_old_enough_antag(player.client,role)) + players += player + + // Shuffle the players list so that it becomes ping-independent. + players = shuffle(players) + + // Get a list of all the people who want to be the antagonist for this round, except those with incompatible species + for(var/mob/new_player/player in players) + if(!player.client.skip_antag) + if((role in player.client.prefs.be_special) && !(player.client.prefs.species in protected_species)) + player_draft_log += "[player.key] had [roletext] enabled, so we are drafting them." + candidates += player.mind + players -= player + + // If we don't have enough antags, draft people who voted for the round. + if(candidates.len < recommended_enemies) + for(var/key in SSvote.round_voters) + for(var/mob/new_player/player in players) + if(player.ckey == key) + player_draft_log += "[player.key] voted for this round, so we are drafting them." + candidates += player.mind + players -= player + break + + // Remove candidates who want to be antagonist but have a job that precludes it + if(restricted_jobs) + for(var/datum/mind/player in candidates) + for(var/job in restricted_jobs) + if(player.assigned_role == job) + candidates -= player + + + return candidates // Returns: The number of people who had the antagonist role set to yes, regardless of recomended_enemies, if that number is greater than recommended_enemies + // recommended_enemies if the number of people with that role set to yes is less than recomended_enemies, + // Less if there are not enough valid players in the game entirely to make recommended_enemies. + + +/datum/game_mode/proc/latespawn(var/mob) + +/* +/datum/game_mode/proc/check_player_role_pref(var/role, var/mob/player) + if(player.preferences.be_special & role) + return 1 + return 0 +*/ + +/datum/game_mode/proc/num_players() + . = 0 + for(var/mob/new_player/P in GLOB.player_list) + if(P.client && P.ready) + .++ + +/datum/game_mode/proc/num_players_started() + . = 0 + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(H.client) + .++ + +/////////////////////////////////// +//Keeps track of all living heads// +/////////////////////////////////// +/datum/game_mode/proc/get_living_heads() + . = list() + for(var/mob/living/carbon/human/player in GLOB.mob_list) + var/list/real_command_positions = command_positions.Copy() - "Nanotrasen Representative" + if(player.stat != DEAD && player.mind && (player.mind.assigned_role in real_command_positions)) + . |= player.mind + + +//////////////////////////// +//Keeps track of all heads// +//////////////////////////// +/datum/game_mode/proc/get_all_heads() + . = list() + for(var/mob/player in GLOB.mob_list) + var/list/real_command_positions = command_positions.Copy() - "Nanotrasen Representative" + if(player.mind && (player.mind.assigned_role in real_command_positions)) + . |= player.mind + +////////////////////////////////////////////// +//Keeps track of all living security members// +////////////////////////////////////////////// +/datum/game_mode/proc/get_living_sec() + . = list() + for(var/mob/living/carbon/human/player in GLOB.mob_list) + if(player.stat != DEAD && player.mind && (player.mind.assigned_role in security_positions)) + . |= player.mind + +//////////////////////////////////////// +//Keeps track of all security members// +//////////////////////////////////////// +/datum/game_mode/proc/get_all_sec() + . = list() + for(var/mob/living/carbon/human/player in GLOB.mob_list) + if(player.mind && (player.mind.assigned_role in security_positions)) + . |= player.mind + +/datum/game_mode/proc/check_antagonists_topic(href, href_list[]) + return 0 + +/datum/game_mode/New() + newscaster_announcements = pick(newscaster_standard_feeds) + +////////////////////////// +//Reports player logouts// +////////////////////////// +proc/display_roundstart_logout_report() + var/msg = "Roundstart logout report\n\n" + for(var/mob/living/L in GLOB.mob_list) + + if(L.ckey) + var/found = 0 + for(var/client/C in GLOB.clients) + if(C.ckey == L.ckey) + found = 1 + break + if(!found) + msg += "[L.name] ([L.ckey]), the [L.job] (Disconnected)\n" + + + if(L.ckey && L.client) + if(L.client.inactivity >= (ROUNDSTART_LOGOUT_REPORT_TIME / 2)) //Connected, but inactive (alt+tabbed or something) + msg += "[L.name] ([L.ckey]), the [L.job] (Connected, Inactive)\n" + continue //AFK client + if(L.stat) + if(L.suiciding) //Suicider + msg += "[L.name] ([L.ckey]), the [L.job] (Suicide)\n" + SSjobs.FreeRole(L.job) + message_admins("[key_name_admin(L)], the [L.job] has been freed due to (Early Round Suicide)\n") + continue //Disconnected client + if(L.stat == UNCONSCIOUS) + msg += "[L.name] ([L.ckey]), the [L.job] (Dying)\n" + continue //Unconscious + if(L.stat == DEAD) + msg += "[L.name] ([L.ckey]), the [L.job] (Dead)\n" + continue //Dead + + continue //Happy connected client + for(var/mob/dead/observer/D in GLOB.mob_list) + if(D.mind && (D.mind.original == L || D.mind.current == L)) + if(L.stat == DEAD) + if(L.suiciding) //Suicider + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Suicide)\n" + continue //Disconnected client + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Dead)\n" + continue //Dead mob, ghost abandoned + else + if(D.can_reenter_corpse) + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (This shouldn't appear.)\n" + continue //Lolwhat + else + msg += "[L.name] ([ckey(D.mind.key)]), the [L.job] (Ghosted)\n" + SSjobs.FreeRole(L.job) + message_admins("[key_name_admin(L)], the [L.job] has been freed due to (Early Round Ghosted While Alive)\n") + continue //Ghosted while alive + + + + for(var/mob/M in GLOB.mob_list) + if(check_rights(R_ADMIN, 0, M)) + to_chat(M, msg) + +//Announces objectives/generic antag text. +/proc/show_generic_antag_text(var/datum/mind/player) + if(player.current) + to_chat(player.current, "You are an antagonist! Within the rules, \ + try to act as an opposing force to the crew. Further RP and try to make sure \ + other players have fun! If you are confused or at a loss, always adminhelp, \ + and before taking extreme actions, please try to also contact the administration! \ + Think through your actions and make the roleplay immersive! Please remember all \ + rules aside from those without explicit exceptions apply to antagonists.") + +/proc/show_objectives(var/datum/mind/player) + if(!player || !player.current) return + + var/obj_count = 1 + to_chat(player.current, "Your current objectives:") + for(var/datum/objective/objective in player.objectives) + to_chat(player.current, "Objective #[obj_count]: [objective.explanation_text]") + obj_count++ + +/proc/get_roletext(var/role) + return role + +/proc/get_nuke_code() + var/nukecode = "ERROR" + for(var/obj/machinery/nuclearbomb/bomb in world) + if(bomb && bomb.r_code && is_station_level(bomb.z)) + nukecode = bomb.r_code + return nukecode + +/datum/game_mode/proc/replace_jobbanned_player(mob/living/M, role_type) + var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a [role_type]?", role_type, 0, 100) + var/mob/dead/observer/theghost = null + if(candidates.len) + theghost = pick(candidates) + to_chat(M, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") + message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)]) to replace a jobbanned player.") + M.ghostize() + M.key = theghost.key + else + message_admins("[M] ([M.key] has been converted into [role_type] with an active antagonist jobban for said role since no ghost has volunteered to take [M.p_their()] place.") + to_chat(M, "You have been converted into [role_type] with an active jobban. Any further violations of the rules on your part are likely to result in a permanent ban.") + +/proc/printplayer(datum/mind/ply, fleecheck) + var/jobtext = "" + if(ply.assigned_role) + jobtext = " the [ply.assigned_role]" + var/text = "[ply.key] was [ply.name][jobtext] and" + if(ply.current) + if(ply.current.stat == DEAD) + text += " died" + else + text += " survived" + if(fleecheck) + var/turf/T = get_turf(ply.current) + if(!T || !is_station_level(T.z)) + text += " while fleeing the station" + if(ply.current.real_name != ply.name) + text += " as [ply.current.real_name]" + else + text += " had [ply.p_their()] body destroyed" + return text + +/proc/printeventplayer(datum/mind/ply) + var/text = "[ply.key] was [ply.name]" + if(ply.special_role != SPECIAL_ROLE_EVENTMISC) + text += " the [ply.special_role]" + text += " and" + if(ply.current) + if(ply.current.stat == DEAD) + text += " died" + else + text += " survived" + else + text += " had [ply.p_their()] body destroyed" + return text + +/proc/printobjectives(datum/mind/ply) + var/list/objective_parts = list() + var/count = 1 + for(var/datum/objective/objective in ply.objectives) + if(objective.check_completion()) + objective_parts += "Objective #[count]: [objective.explanation_text] Success!" + else + objective_parts += "Objective #[count]: [objective.explanation_text] Fail." + count++ + return objective_parts.Join("
    ") + +/datum/game_mode/proc/generate_station_goals() + var/list/possible = list() + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = T + if(config_tag in initial(G.gamemode_blacklist)) + continue + possible += T + var/goal_weights = 0 + while(possible.len && goal_weights < STATION_GOAL_BUDGET) + var/datum/station_goal/picked = pick_n_take(possible) + goal_weights += initial(picked.weight) + station_goals += new picked + + if(station_goals.len) + send_station_goals_message() + +/datum/game_mode/proc/send_station_goals_message() + var/message_text = "
    " + message_text += "

    [command_name()] Orders


    " + message_text += "Special Orders for [station_name()]:

    " + + for(var/datum/station_goal/G in station_goals) + G.on_report() + message_text += G.get_report() + message_text += "
    " + + print_command_report(message_text, "[command_name()] Orders") + +/datum/game_mode/proc/declare_station_goal_completion() + for(var/V in station_goals) + var/datum/station_goal/G = V + G.print_result() + +/datum/game_mode/proc/update_eventmisc_icons_added(datum/mind/mob_mind) + var/datum/atom_hud/antag/antaghud = huds[ANTAG_HUD_EVENTMISC] + antaghud.join_hud(mob_mind.current) + set_antag_hud(mob_mind.current, "hudevent") + +/datum/game_mode/proc/update_eventmisc_icons_removed(datum/mind/mob_mind) + var/datum/atom_hud/antag/antaghud = huds[ANTAG_HUD_EVENTMISC] + antaghud.leave_hud(mob_mind.current) + set_antag_hud(mob_mind.current, null) diff --git a/code/game/gamemodes/intercept_report.dm b/code/game/gamemodes/intercept_report.dm index d1d176f797e..acee798e96c 100644 --- a/code/game/gamemodes/intercept_report.dm +++ b/code/game/gamemodes/intercept_report.dm @@ -1,234 +1,234 @@ -/datum/intercept_text - var/text - /* - var/prob_correct_person_lower = 20 - var/prob_correct_person_higher = 80 - var/prob_correct_job_lower = 20 - var/prob_correct_job_higher = 80 - var/prob_correct_prints_lower = 20 - var/prob_correct_print_higher = 80 - var/prob_correct_objective_lower = 20 - var/prob_correct_objective_higher = 80 - */ - var/list/org_names_1 = list( - "Blighted", - "Defiled", - "Unholy", - "Murderous", - "Ugly", - "French", - "Blue", - "Farmer" - ) - var/list/org_names_2 = list( - "Reapers", - "Swarm", - "Rogues", - "Menace", - "Jeff Worshippers", - "Drunks", - "Strikers", - "Creed" - ) - var/list/anomalies = list( - "Huge electrical storm", - "Photon emitter", - "Meson generator", - "Blue swirly thing" - ) - var/list/SWF_names = list( - "Grand Wizard", - "His Most Unholy Master", - "The Most Angry", - "Bighands", - "Tall Hat", - "Deadly Sandals" - ) - var/list/changeling_names = list( - "Odo", - "The Thing", - "Booga", - "The Goatee of Wrath", - "Tam Lin", - "Species 3157", - "Small Prick" - ) - - -/datum/intercept_text/proc/build(var/mode_type, datum/mind/correct_person) - switch(mode_type) - if("revolution") - src.text = "" - src.build_rev(correct_person) - return src.text - if("cult") - src.text = "" - src.build_cult(correct_person) - return src.text - if("wizard") - src.text = "" - src.build_wizard(correct_person) - return src.text - if("nuke") - src.text = "" - src.build_nuke(correct_person) - return src.text - if("traitor") - src.text = "" - src.build_traitor(correct_person) - return src.text - if("changeling","traitorchan") - src.text = "" - src.build_changeling(correct_person) - return src.text - else - return null - -// NOTE: Commentted out was the code which showed the chance of someone being an antag. If you want to re-add it, just uncomment the code. - -/* -/datum/intercept_text/proc/pick_mob() - var/list/dudes = list() - for(var/mob/living/carbon/human/man in GLOB.player_list) - if(!man.mind) continue - if(man.mind.assigned_role == man.mind.special_role) continue - dudes += man - if(dudes.len==0) - return null - return pick(dudes) - - -/datum/intercept_text/proc/pick_fingerprints() - var/mob/living/carbon/human/dude = src.pick_mob() - //if(!dude) return pick_fingerprints() //who coded that is totally crasy or just a traitor. -- rastaf0 - if(dude) - return num2text(md5(dude.dna.uni_identity)) - else - return num2text(md5(num2text(rand(1,10000)))) -*/ - -/datum/intercept_text/proc/get_suspect() - var/list/dudes = list() - for(var/mob/living/carbon/human/man in GLOB.player_list) - if(man.client && man.client.prefs.nanotrasen_relation == "Opposed") - //don't include suspects who can't possibly be the antag based on their job (no suspecting the captain of being a damned dirty tator) - if(man.mind && man.mind.assigned_role) - if((man.mind.assigned_role in SSticker.mode.protected_jobs) || (man.mind.assigned_role in SSticker.mode.restricted_jobs)) - return - //don't include suspects who can't possibly be the antag based on their species (no suspecting the machines of being sneaky changelings) - if(man.dna.species.name in SSticker.mode.protected_species) - return - dudes += man - for(var/i = 0, i < max(GLOB.player_list.len/10,2), i++) - dudes += pick(GLOB.player_list) - return pick(dudes) - -/datum/intercept_text/proc/build_traitor(datum/mind/correct_person) - var/name_1 = pick(src.org_names_1) - var/name_2 = pick(src.org_names_2) - - var/mob/living/carbon/human/H = get_suspect() - if(!H) return - - var/fingerprints = num2text(md5(H.dna.uni_identity)) - var/traitor_name = H.real_name - var/prob_right_dude = rand(1, 100) - - src.text += "

    The [name_1] [name_2] implied an undercover operative was acting on their behalf on the station currently." - src.text += "It would be in your best interests to suspect everybody, as these undercover operatives could have implants which trigger them to have their memories removed until they are needed. He, or she, could even be a high ranking officer." - - src.text += "After some investigation, we " - if(prob(50)) - src.text += "are [prob_right_dude]% sure that [traitor_name] may have been involved, and should be closely observed." - src.text += "
    Note: This group are known to be untrustworthy, so do not act on this information without proper discourse." - else - src.text += "discovered the following set of fingerprints ([fingerprints]) on sensitive materials, and their owner should be closely observed." - src.text += "However, these could also belong to a current Cent. Com employee, so do not act on this without reason." - - - -/datum/intercept_text/proc/build_cult(datum/mind/correct_person) - var/name_1 = pick(src.org_names_1) - var/name_2 = pick(src.org_names_2) - - var/prob_right_dude = rand(1, 100) - var/mob/living/carbon/human/H = get_suspect() - if(!H) return - var/traitor_job = H.mind.assigned_role - - src.text += "

    It has been brought to our attention that the [name_1] [name_2] have stumbled upon some dark secrets. They apparently want to spread the dangerous knowledge onto as many stations as they can." - src.text += "Watch out for the following: praying to an unfamilar god, preaching the word of \[REDACTED\], sacrifices, magical dark power, living constructs of evil and a portal to the dimension of the underworld." - - src.text += "Based on our intelligence, we are [prob_right_dude]% sure that if true, someone doing the job of [traitor_job] on your station may have been converted " - src.text += "and instilled with the idea of the flimsiness of the real world, seeking to destroy it. " - - src.text += "
    However, if this information is acted on without substantial evidence, those responsible will face severe repercussions." - - - -/datum/intercept_text/proc/build_rev(datum/mind/correct_person) - var/name_1 = pick(src.org_names_1) - var/name_2 = pick(src.org_names_2) - - var/prob_right_dude = rand(1, 100) - var/mob/living/carbon/human/H = get_suspect() - if(!H) return - var/traitor_job = H.mind.assigned_role - - src.text += "

    It has been brought to our attention that the [name_1] [name_2] are attempting to stir unrest on one of our stations in your sector." - src.text += "Watch out for suspicious activity among the crew and make sure that all heads of staff report in periodically." - - src.text += "Based on our intelligence, we are [prob_right_dude]% sure that if true, someone doing the job of [traitor_job] on your station may have been brainwashed " - src.text += "at a recent conference, and their department should be closely monitored for signs of mutiny. " - - src.text += "
    However, if this information is acted on without substantial evidence, those responsible will face severe repercussions." - - - -/datum/intercept_text/proc/build_wizard(datum/mind/correct_person) - var/SWF_desc = pick(SWF_names) - - src.text += "

    The evil Space Wizards Federation have recently broke their most feared wizard, known only as \"[SWF_desc]\" out of space jail. " - src.text += "He is on the run, last spotted in a system near your present location. If anybody suspicious is located aboard, please " - src.text += "approach with EXTREME caution. Cent. Com also recommends that it would be wise to not inform the crew of this, due to their fearful nature." - src.text += "Known attributes include: Brown sandals, a large blue hat, a voluptous white beard, and an inclination to cast spells." - -/datum/intercept_text/proc/build_nuke(datum/mind/correct_person) - src.text += "

    Cent. Com recently recieved a report of a plot to destroy one of our stations in your area. We believe the Nuclear Authentication Disc " - src.text += "that is standard issue aboard your vessel may be a target. We recommend removal of this object, and it's storage in a safe " - src.text += "environment. As this may cause panic among the crew, all efforts should be made to keep this information a secret from all but " - src.text += "the most trusted crew-members." - -/datum/intercept_text/proc/build_changeling(datum/mind/correct_person) - var/cname = pick(src.changeling_names) - var/orgname1 = pick(src.org_names_1) - var/orgname2 = pick(src.org_names_2) - /* - var/changeling_name - var/changeling_job - var/prob_right_dude = rand(prob_correct_person_lower, prob_correct_person_higher) - var/prob_right_job = rand(prob_correct_job_lower, prob_correct_job_higher) - if(prob(prob_right_job)) - if(correct_person) - if(correct_person:assigned_role == correct_person:special_role) - changeling_job = pick(GLOB.joblist) - else - changeling_job = correct_person:assigned_role - else - changeling_job = pick(GLOB.joblist) - if(prob(prob_right_dude) && ticker.mode == "changeling") - if(correct_person:assigned_role == correct_person:special_role) - changeling_name = correct_person:current - else - changeling_name = src.pick_mob() - else - changeling_name = src.pick_mob() - */ - - src.text += "

    We have received a report that a dangerous alien lifeform known only as \"[cname]\" may have infiltrated your crew. " - /* - src.text += "Our intelligence suggests a [prob_right_job]% chance that a [changeling_job] on board your station has been replaced by the alien. " - src.text += "Additionally, the report indicates a [prob_right_dude]% chance that [changeling_name] may have been in contact with the lifeform at a recent social gathering. " - */ - src.text += "These lifeforms are assosciated with the [orgname1] [orgname2] and may be attempting to acquire sensitive materials on their behalf. " - src.text += "Please take care not to alarm the crew, as [cname] may take advantage of a panic situation. Remember, they can be anybody, suspect everybody!" +/datum/intercept_text + var/text + /* + var/prob_correct_person_lower = 20 + var/prob_correct_person_higher = 80 + var/prob_correct_job_lower = 20 + var/prob_correct_job_higher = 80 + var/prob_correct_prints_lower = 20 + var/prob_correct_print_higher = 80 + var/prob_correct_objective_lower = 20 + var/prob_correct_objective_higher = 80 + */ + var/list/org_names_1 = list( + "Blighted", + "Defiled", + "Unholy", + "Murderous", + "Ugly", + "French", + "Blue", + "Farmer" + ) + var/list/org_names_2 = list( + "Reapers", + "Swarm", + "Rogues", + "Menace", + "Jeff Worshippers", + "Drunks", + "Strikers", + "Creed" + ) + var/list/anomalies = list( + "Huge electrical storm", + "Photon emitter", + "Meson generator", + "Blue swirly thing" + ) + var/list/SWF_names = list( + "Grand Wizard", + "His Most Unholy Master", + "The Most Angry", + "Bighands", + "Tall Hat", + "Deadly Sandals" + ) + var/list/changeling_names = list( + "Odo", + "The Thing", + "Booga", + "The Goatee of Wrath", + "Tam Lin", + "Species 3157", + "Small Prick" + ) + + +/datum/intercept_text/proc/build(var/mode_type, datum/mind/correct_person) + switch(mode_type) + if("revolution") + src.text = "" + src.build_rev(correct_person) + return src.text + if("cult") + src.text = "" + src.build_cult(correct_person) + return src.text + if("wizard") + src.text = "" + src.build_wizard(correct_person) + return src.text + if("nuke") + src.text = "" + src.build_nuke(correct_person) + return src.text + if("traitor") + src.text = "" + src.build_traitor(correct_person) + return src.text + if("changeling","traitorchan") + src.text = "" + src.build_changeling(correct_person) + return src.text + else + return null + +// NOTE: Commentted out was the code which showed the chance of someone being an antag. If you want to re-add it, just uncomment the code. + +/* +/datum/intercept_text/proc/pick_mob() + var/list/dudes = list() + for(var/mob/living/carbon/human/man in GLOB.player_list) + if(!man.mind) continue + if(man.mind.assigned_role == man.mind.special_role) continue + dudes += man + if(dudes.len==0) + return null + return pick(dudes) + + +/datum/intercept_text/proc/pick_fingerprints() + var/mob/living/carbon/human/dude = src.pick_mob() + //if(!dude) return pick_fingerprints() //who coded that is totally crasy or just a traitor. -- rastaf0 + if(dude) + return num2text(md5(dude.dna.uni_identity)) + else + return num2text(md5(num2text(rand(1,10000)))) +*/ + +/datum/intercept_text/proc/get_suspect() + var/list/dudes = list() + for(var/mob/living/carbon/human/man in GLOB.player_list) + if(man.client && man.client.prefs.nanotrasen_relation == "Opposed") + //don't include suspects who can't possibly be the antag based on their job (no suspecting the captain of being a damned dirty tator) + if(man.mind && man.mind.assigned_role) + if((man.mind.assigned_role in SSticker.mode.protected_jobs) || (man.mind.assigned_role in SSticker.mode.restricted_jobs)) + return + //don't include suspects who can't possibly be the antag based on their species (no suspecting the machines of being sneaky changelings) + if(man.dna.species.name in SSticker.mode.protected_species) + return + dudes += man + for(var/i = 0, i < max(GLOB.player_list.len/10,2), i++) + dudes += pick(GLOB.player_list) + return pick(dudes) + +/datum/intercept_text/proc/build_traitor(datum/mind/correct_person) + var/name_1 = pick(src.org_names_1) + var/name_2 = pick(src.org_names_2) + + var/mob/living/carbon/human/H = get_suspect() + if(!H) return + + var/fingerprints = num2text(md5(H.dna.uni_identity)) + var/traitor_name = H.real_name + var/prob_right_dude = rand(1, 100) + + src.text += "

    The [name_1] [name_2] implied an undercover operative was acting on their behalf on the station currently." + src.text += "It would be in your best interests to suspect everybody, as these undercover operatives could have implants which trigger them to have their memories removed until they are needed. He, or she, could even be a high ranking officer." + + src.text += "After some investigation, we " + if(prob(50)) + src.text += "are [prob_right_dude]% sure that [traitor_name] may have been involved, and should be closely observed." + src.text += "
    Note: This group are known to be untrustworthy, so do not act on this information without proper discourse." + else + src.text += "discovered the following set of fingerprints ([fingerprints]) on sensitive materials, and their owner should be closely observed." + src.text += "However, these could also belong to a current Cent. Com employee, so do not act on this without reason." + + + +/datum/intercept_text/proc/build_cult(datum/mind/correct_person) + var/name_1 = pick(src.org_names_1) + var/name_2 = pick(src.org_names_2) + + var/prob_right_dude = rand(1, 100) + var/mob/living/carbon/human/H = get_suspect() + if(!H) return + var/traitor_job = H.mind.assigned_role + + src.text += "

    It has been brought to our attention that the [name_1] [name_2] have stumbled upon some dark secrets. They apparently want to spread the dangerous knowledge onto as many stations as they can." + src.text += "Watch out for the following: praying to an unfamilar god, preaching the word of \[REDACTED\], sacrifices, magical dark power, living constructs of evil and a portal to the dimension of the underworld." + + src.text += "Based on our intelligence, we are [prob_right_dude]% sure that if true, someone doing the job of [traitor_job] on your station may have been converted " + src.text += "and instilled with the idea of the flimsiness of the real world, seeking to destroy it. " + + src.text += "
    However, if this information is acted on without substantial evidence, those responsible will face severe repercussions." + + + +/datum/intercept_text/proc/build_rev(datum/mind/correct_person) + var/name_1 = pick(src.org_names_1) + var/name_2 = pick(src.org_names_2) + + var/prob_right_dude = rand(1, 100) + var/mob/living/carbon/human/H = get_suspect() + if(!H) return + var/traitor_job = H.mind.assigned_role + + src.text += "

    It has been brought to our attention that the [name_1] [name_2] are attempting to stir unrest on one of our stations in your sector." + src.text += "Watch out for suspicious activity among the crew and make sure that all heads of staff report in periodically." + + src.text += "Based on our intelligence, we are [prob_right_dude]% sure that if true, someone doing the job of [traitor_job] on your station may have been brainwashed " + src.text += "at a recent conference, and their department should be closely monitored for signs of mutiny. " + + src.text += "
    However, if this information is acted on without substantial evidence, those responsible will face severe repercussions." + + + +/datum/intercept_text/proc/build_wizard(datum/mind/correct_person) + var/SWF_desc = pick(SWF_names) + + src.text += "

    The evil Space Wizards Federation have recently broke their most feared wizard, known only as \"[SWF_desc]\" out of space jail. " + src.text += "He is on the run, last spotted in a system near your present location. If anybody suspicious is located aboard, please " + src.text += "approach with EXTREME caution. Cent. Com also recommends that it would be wise to not inform the crew of this, due to their fearful nature." + src.text += "Known attributes include: Brown sandals, a large blue hat, a voluptous white beard, and an inclination to cast spells." + +/datum/intercept_text/proc/build_nuke(datum/mind/correct_person) + src.text += "

    Cent. Com recently recieved a report of a plot to destroy one of our stations in your area. We believe the Nuclear Authentication Disc " + src.text += "that is standard issue aboard your vessel may be a target. We recommend removal of this object, and it's storage in a safe " + src.text += "environment. As this may cause panic among the crew, all efforts should be made to keep this information a secret from all but " + src.text += "the most trusted crew-members." + +/datum/intercept_text/proc/build_changeling(datum/mind/correct_person) + var/cname = pick(src.changeling_names) + var/orgname1 = pick(src.org_names_1) + var/orgname2 = pick(src.org_names_2) + /* + var/changeling_name + var/changeling_job + var/prob_right_dude = rand(prob_correct_person_lower, prob_correct_person_higher) + var/prob_right_job = rand(prob_correct_job_lower, prob_correct_job_higher) + if(prob(prob_right_job)) + if(correct_person) + if(correct_person:assigned_role == correct_person:special_role) + changeling_job = pick(GLOB.joblist) + else + changeling_job = correct_person:assigned_role + else + changeling_job = pick(GLOB.joblist) + if(prob(prob_right_dude) && ticker.mode == "changeling") + if(correct_person:assigned_role == correct_person:special_role) + changeling_name = correct_person:current + else + changeling_name = src.pick_mob() + else + changeling_name = src.pick_mob() + */ + + src.text += "

    We have received a report that a dangerous alien lifeform known only as \"[cname]\" may have infiltrated your crew. " + /* + src.text += "Our intelligence suggests a [prob_right_job]% chance that a [changeling_job] on board your station has been replaced by the alien. " + src.text += "Additionally, the report indicates a [prob_right_dude]% chance that [changeling_name] may have been in contact with the lifeform at a recent social gathering. " + */ + src.text += "These lifeforms are assosciated with the [orgname1] [orgname2] and may be attempting to acquire sensitive materials on their behalf. " + src.text += "Please take care not to alarm the crew, as [cname] may take advantage of a panic situation. Remember, they can be anybody, suspect everybody!" diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm index 70e0e3258eb..9ca0025a0ab 100644 --- a/code/game/gamemodes/malfunction/Malf_Modules.dm +++ b/code/game/gamemodes/malfunction/Malf_Modules.dm @@ -1,758 +1,758 @@ -//The malf AI action subtype. All malf actions are subtypes of this. -/datum/action/innate/ai - name = "AI Action" - desc = "You aren't entirely sure what this does, but it's very beepy and boopy." - background_icon_state = "bg_tech_blue" - var/mob/living/silicon/ai/owner_AI //The owner AI, so we don't have to typecast every time - var/uses //If we have multiple uses of the same power - var/auto_use_uses = TRUE //If we automatically use up uses on each activation - var/cooldown_period //If applicable, the time in deciseconds we have to wait before using any more modules - -/datum/action/innate/ai/Grant(mob/living/L) - . = ..() - if(!isAI(owner)) - WARNING("AI action [name] attempted to grant itself to non-AI mob [L.real_name] ([L.key])!") - qdel(src) - else - owner_AI = owner - -/datum/action/innate/ai/IsAvailable() - . = ..() - if(owner_AI && owner_AI.malf_cooldown > world.time) - return - -/datum/action/innate/ai/Trigger() - . = ..() - if(auto_use_uses) - adjust_uses(-1) - if(cooldown_period) - owner_AI.malf_cooldown = world.time + cooldown_period - -/datum/action/innate/ai/proc/adjust_uses(amt, silent) - uses += amt - if(!silent && uses) - to_chat(owner, "[name] now has [uses] use[uses > 1 ? "s" : ""] remaining.") - if(!uses) - if(initial(uses) > 1) //no need to tell 'em if it was one-use anyway! - to_chat(owner, "[name] has run out of uses!") - qdel(src) - -//Framework for ranged abilities that can have different effects by left-clicking stuff. -/datum/action/innate/ai/ranged - name = "Ranged AI Action" - auto_use_uses = FALSE //This is so we can do the thing and disable/enable freely without having to constantly add uses - var/obj/effect/proc_holder/ranged_ai/linked_ability //The linked proc holder that contains the actual ability code - var/linked_ability_type //The path of our linked ability - -/datum/action/innate/ai/ranged/New() - if(!linked_ability_type) - WARNING("Ranged AI action [name] attempted to spawn without a linked ability!") - qdel(src) //uh oh! - return - linked_ability = new linked_ability_type() - linked_ability.attached_action = src - ..() - -/datum/action/innate/ai/ranged/adjust_uses(amt, silent) - uses += amt - if(!silent && uses) - to_chat(owner, "[name] now has [uses] use[uses > 1 ? "s" : ""] remaining.") - if(!uses) - if(initial(uses) > 1) //no need to tell 'em if it was one-use anyway! - to_chat(owner, "[name] has run out of uses!") - Remove(owner) - QDEL_IN(src, 100) //let any active timers on us finish up - -/datum/action/innate/ai/ranged/Destroy() - QDEL_NULL(linked_ability) - return ..() - -/datum/action/innate/ai/ranged/Activate() - linked_ability.toggle(owner) - return TRUE - -//The actual ranged proc holder. -/obj/effect/proc_holder/ranged_ai - var/enable_text = "Hello World!" //Appears when the user activates the ability - var/disable_text = "Goodbye Cruel World!" //Context clues! - var/datum/action/innate/ai/ranged/attached_action - -/obj/effect/proc_holder/ranged_ai/proc/toggle(mob/user) - if(active) - remove_ranged_ability(user, disable_text) - else - add_ranged_ability(user, enable_text) - -//The datum and interface for the malf unlock menu, which lets them choose actions to unlock. -/datum/module_picker - var/temp - var/processing_time = 50 - var/list/possible_modules - -/datum/module_picker/New() - possible_modules = list() - for(var/type in typesof(/datum/AI_Module)) - var/datum/AI_Module/AM = new type - if((AM.power_type && AM.power_type != /datum/action/innate/ai) || AM.upgrade) - possible_modules += AM - -/datum/module_picker/proc/remove_malf_verbs(mob/living/silicon/ai/AI) //Removes all malfunction-related abilities from the target AI. - for(var/datum/AI_Module/AM in possible_modules) - for(var/datum/action/A in AI.actions) - if(istype(A, initial(AM.power_type))) - qdel(A) - -/datum/module_picker/proc/use(user as mob) - var/dat - dat += {"Select use of processing time: (currently #[processing_time] left.)
    -
    - Install Module:
    - The number afterwards is the amount of processing time it consumes.
    "} - for(var/datum/AI_Module/large/module in possible_modules) - dat += "[module.module_name]\[?\] ([module.cost])
    " - for(var/datum/AI_Module/small/module in possible_modules) - dat += "[module.module_name]\[?\] ([module.cost])
    " - dat += "
    " - if(temp) - dat += "[temp]" - var/datum/browser/popup = new(user, "modpicker", "Malf Module Menu", 400, 500) - popup.set_content(dat) - popup.open() - return - -/datum/module_picker/Topic(href, href_list) - ..() - - if(!isAI(usr)) - return - var/mob/living/silicon/ai/A = usr - - if(A.stat == DEAD) - to_chat(A, "You are already dead!") - return - - for(var/datum/AI_Module/AM in possible_modules) - if (href_list[AM.mod_pick_name]) - - // Cost check - if(AM.cost > processing_time) - temp = "You cannot afford this module." - break - - var/datum/action/innate/ai/action = locate(AM.power_type) in A.actions - - // Give the power and take away the money. - if(AM.upgrade) //upgrade and upgrade() are separate, be careful! - AM.upgrade(A) - possible_modules -= AM - to_chat(A, AM.unlock_text) - A.playsound_local(A, AM.unlock_sound, 50, 0) - else - if(AM.power_type) - if(!action) //Unlocking for the first time - var/datum/action/AC = new AM.power_type - AC.Grant(A) - A.current_modules += new AM.type - temp = AM.description - if(AM.one_purchase) - possible_modules -= AM - if(AM.unlock_text) - to_chat(A, AM.unlock_text) - if(AM.unlock_sound) - A.playsound_local(A, AM.unlock_sound, 50, 0) - else //Adding uses to an existing module - action.uses += initial(action.uses) - action.desc = "[initial(action.desc)] It has [action.uses] use\s remaining." - action.UpdateButtonIcon() - temp = "Additional use[action.uses > 1 ? "s" : ""] added to [action.name]!" - processing_time -= AM.cost - - if(href_list["showdesc"]) - if(AM.mod_pick_name == href_list["showdesc"]) - temp = AM.description - use(usr) - -//The base module type, which holds info about each ability. -/datum/AI_Module - var/module_name - var/mod_pick_name - var/description = "" - var/engaged = 0 - var/cost = 5 - var/one_purchase = FALSE //If this module can only be purchased once. This always applies to upgrades, even if the variable is set to false. - var/power_type = /datum/action/innate/ai //If the module gives an active ability, use this. Mutually exclusive with upgrade. - var/upgrade //If the module gives a passive upgrade, use this. Mutually exclusive with power_type. - var/unlock_text = "Hello World!" //Text shown when an ability is unlocked - var/unlock_sound //Sound played when an ability is unlocked - var/uses - -/datum/AI_Module/proc/upgrade(mob/living/silicon/AI/AI) //Apply upgrades! - return - -/datum/AI_Module/large //Big, powerful stuff that can only be used once. - -/datum/AI_Module/small //Weak, usually localized stuff with multiple uses. - -//Doomsday Device: Starts the self-destruct timer. It can only be stopped by killing the AI completely. -/datum/AI_Module/large/nuke_station - module_name = "Doomsday Device" - mod_pick_name = "nukestation" - description = "Activate a weapon that will disintegrate all organic life on the station after a 450 second delay. Can only be used while on the station, will fail if your core is moved off station or destroyed." - cost = 130 - one_purchase = TRUE - power_type = /datum/action/innate/ai/nuke_station - unlock_text = "You slowly, carefully, establish a connection with the on-station self-destruct. You can now activate it at any time." - unlock_sound = 'sound/items/timer.ogg' - -/datum/action/innate/ai/nuke_station - name = "Doomsday Device" - desc = "Activates the doomsday device. This is not reversible." - button_icon_state = "doomsday_device" - auto_use_uses = FALSE - -/datum/action/innate/ai/nuke_station/Activate() - var/turf/T = get_turf(owner) - if(!istype(T) || !is_station_level(T.z)) - to_chat(owner, "You cannot activate the doomsday device while off-station!") - return - if(alert(owner, "Send arming signal? (true = arm, false = cancel)", "purge_all_life()", "confirm = TRUE;", "confirm = FALSE;") != "confirm = TRUE;") - return - if(active) - return //prevent the AI from activating an already active doomsday - active = TRUE - set_us_up_the_bomb() - -/datum/action/innate/ai/nuke_station/proc/set_us_up_the_bomb() - to_chat(owner_AI, "Nuclear device armed.") - event_announcement.Announce("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.", "Anomaly Alert", new_sound = 'sound/AI/aimalf.ogg') - set_security_level("delta") - owner_AI.nuking = TRUE - var/obj/machinery/doomsday_device/DOOM = new /obj/machinery/doomsday_device(owner_AI) - owner_AI.doomsday_device = DOOM - owner_AI.doomsday_device.start() - for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) - for(var/mob/living/silicon/ai/A in ai_list) - if((A.stat != DEAD) && A.nuking) - point.the_disk = A //The pinpointer now tracks the AI core - qdel(src) - -/obj/machinery/doomsday_device - icon = 'icons/obj/machines/nuke_terminal.dmi' - name = "doomsday device" - icon_state = "nuclearbomb_base" - desc = "A weapon which disintegrates all organic life in a large area." - anchored = 1 - density = 1 - atom_say_verb = "blares" - speed_process = TRUE // Disgusting fix. Please remove once #12952 is merged - var/timing = 0 - var/default_timer = 4500 - var/detonation_timer - var/announced = 0 - -/obj/machinery/doomsday_device/Destroy() - STOP_PROCESSING(SSfastprocess, src) - SSshuttle.emergencyNoEscape = 0 - if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) - SSshuttle.emergency.mode = SHUTTLE_DOCKED - SSshuttle.emergency.timer = world.time - priority_announcement.Announce("Hostile environment resolved. You have 3 minutes to board the Emergency Shuttle.", "Priority Announcement", 'sound/AI/shuttledock.ogg') - return ..() - -/obj/machinery/doomsday_device/proc/start() - detonation_timer = world.time + default_timer - timing = 1 - START_PROCESSING(SSfastprocess, src) - SSshuttle.emergencyNoEscape = 1 - -/obj/machinery/doomsday_device/proc/seconds_remaining() - . = max(0, (round(detonation_timer - world.time) / 10)) - -/obj/machinery/doomsday_device/process() - var/turf/T = get_turf(src) - if(!T || !is_station_level(T.z)) - minor_announcement.Announce("DOOMSDAY DEVICE OUT OF STATION RANGE, ABORTING", "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4", 'sound/misc/notice1.ogg') - SSshuttle.emergencyNoEscape = 0 - if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) - SSshuttle.emergency.mode = SHUTTLE_DOCKED - SSshuttle.emergency.timer = world.time - priority_announcement.Announce("Hostile environment resolved. You have 3 minutes to board the Emergency Shuttle.", "Priority Announcement", 'sound/AI/shuttledock.ogg') - qdel(src) - if(!timing) - STOP_PROCESSING(SSfastprocess, src) - return - var/sec_left = seconds_remaining() - if(sec_left <= 0) - timing = 0 - detonate(T.z) - qdel(src) - else - if(!(sec_left % 60) && !announced) - var/message = "[sec_left] SECONDS UNTIL DOOMSDAY DEVICE ACTIVATION!" - minor_announcement.Announce(message, "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4", 'sound/misc/notice1.ogg') - announced = 10 - announced = max(0, announced-1) - -/obj/machinery/doomsday_device/proc/detonate(z_level = 1) - for(var/mob/M in GLOB.player_list) - M << 'sound/machines/alarm.ogg' - sleep(100) - for(var/mob/living/L in GLOB.mob_list) - var/turf/T = get_turf(L) - if(!T || T.z != z_level) - continue - if(issilicon(L)) - continue - to_chat(L, "The blast wave from [src] tears you atom from atom!") - L.dust() - to_chat(world, "The AI cleansed the station of life with the doomsday device!") - SSticker.force_ending = 1 - -//AI Turret Upgrade: Increases the health and damage of all turrets. -/datum/AI_Module/large/upgrade_turrets - module_name = "AI Turret Upgrade" - mod_pick_name = "turret" - description = "Improves the power and health of all AI turrets. This effect is permanent." - cost = 30 - upgrade = TRUE - unlock_text = "You establish a power diversion to your turrets, upgrading their health and damage." - unlock_sound = 'sound/items/rped.ogg' - -/datum/AI_Module/large/upgrade_turrets/upgrade(mob/living/silicon/AI/AI) - for(var/obj/machinery/porta_turret/turret in GLOB.machines) - var/turf/T = get_turf(turret) - if(is_station_level(T.z)) - turret.health += 30 - turret.eprojectile = /obj/item/projectile/beam/laser/heavylaser //Once you see it, you will know what it means to FEAR. - turret.eshot_sound = 'sound/weapons/lasercannonfire.ogg' - -//Hostile Station Lockdown: Locks, bolts, and electrifies every airlock on the station. After 90 seconds, the doors reset. -/datum/AI_Module/large/lockdown - module_name = "Hostile Station Lockdown" - mod_pick_name = "lockdown" - description = "Overload the airlock, blast door and fire control networks, locking them down. Caution! This command also electrifies all airlocks. The networks will automatically reset after 90 seconds, briefly \ - opening all doors on the station." - cost = 30 - one_purchase = TRUE - power_type = /datum/action/innate/ai/lockdown - unlock_text = "You upload a sleeper trojan into the door control systems. You can send a signal to set it off at any time." - -/datum/action/innate/ai/lockdown - name = "Lockdown" - desc = "Closes, bolts, and depowers every airlock, firelock, and blast door on the station. After 90 seconds, they will reset themselves." - button_icon_state = "lockdown" - uses = 1 - -/datum/action/innate/ai/lockdown/Activate() - for(var/obj/machinery/door/D in GLOB.airlocks) - if(!is_station_level(D.z)) - continue - INVOKE_ASYNC(D, /obj/machinery/door.proc/hostile_lockdown, owner) - addtimer(CALLBACK(D, /obj/machinery/door.proc/disable_lockdown), 900) - - post_status("alert", "lockdown") - - minor_announcement.Announce("Hostile runtime detected in door controllers. Isolation lockdown protocols are now in effect. Please remain calm.", "Network Alert") - to_chat(owner, "Lockdown Initiated. Network reset in 90 seconds.") - spawn(900) - minor_announcement.Announce("Automatic system reboot complete. Have a secure day.","Network reset:") - -//Destroy RCDs: Detonates all non-cyborg RCDs on the station. -/datum/AI_Module/large/destroy_rcd - module_name = "Destroy RCDs" - mod_pick_name = "rcd" - description = "Send a specialised pulse to detonate all hand-held and exosuit Rapid Construction Devices on the station." - cost = 25 - one_purchase = TRUE - power_type = /datum/action/innate/ai/destroy_rcds - unlock_text = "After some improvisation, you rig your onboard radio to be able to send a signal to detonate all RCDs." - -/datum/action/innate/ai/destroy_rcds - name = "Destroy RCDs" - desc = "Detonate all non-cyborg RCDs on the station." - button_icon_state = "detonate_rcds" - uses = 1 - cooldown_period = 100 - -/datum/action/innate/ai/destroy_rcds/Activate() - for(var/obj/item/rcd/RCD in GLOB.rcd_list) - if(!istype(RCD, /obj/item/rcd/borg)) //Ensures that cyborg RCDs are spared. - RCD.detonate_pulse() - - to_chat(owner, "RCD detonation pulse emitted.") - owner.playsound_local(owner, 'sound/machines/twobeep.ogg', 50, 0) - -//Unlock Mech Domination: Unlocks the ability to dominate mechs. Big shocker, right? -/datum/AI_Module/large/mecha_domination - module_name = "Unlock Mech Domination" - mod_pick_name = "mechjack" - description = "Allows you to hack into a mech's onboard computer, shunting all processes into it and ejecting any occupants. Once uploaded to the mech, it is impossible to leave.\ - Do not allow the mech to leave the station's vicinity or allow it to be destroyed." - cost = 30 - upgrade = TRUE - unlock_text = "Virus package compiled. Select a target mech at any time. You must remain on the station at all times. Loss of signal will result in total system lockout." - unlock_sound = 'sound/mecha/nominal.ogg' - -/datum/AI_Module/large/mecha_domination/upgrade(mob/living/silicon/ai/AI) - AI.can_dominate_mechs = TRUE //Yep. This is all it does. Honk! - -//Thermal Sensor Override: Unlocks the ability to disable all fire alarms from doing their job. -/datum/AI_Module/large/break_fire_alarms - module_name = "Thermal Sensor Override" - mod_pick_name = "burnpigs" - description = "Gives you the ability to override the thermal sensors on all fire alarms. This will remove their ability to scan for fire and thus their ability to alert. \ - Anyone can check the fire alarm's interface and may be tipped off by its status." - one_purchase = TRUE - cost = 25 - power_type = /datum/action/innate/ai/break_fire_alarms - unlock_text = "You replace the thermal sensing capabilities of all fire alarms with a manual override, allowing you to turn them off at will." - -/datum/action/innate/ai/break_fire_alarms - name = "Override Thermal Sensors" - desc = "Disables the automatic temperature sensing on all fire alarms, making them effectively useless." - button_icon_state = "break_fire_alarms" - uses = 1 - -/datum/action/innate/ai/break_fire_alarms/Activate() - for(var/obj/machinery/firealarm/F in GLOB.machines) - if(!is_station_level(F.z)) - continue - F.emagged = TRUE - to_chat(owner, "All thermal sensors on the station have been disabled. Fire alerts will no longer be recognized.") - owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0) - -//Air Alarm Safety Override: Unlocks the ability to enable flooding on all air alarms. -/datum/AI_Module/large/break_air_alarms - module_name = "Air Alarm Safety Override" - mod_pick_name = "allow_flooding" - description = "Gives you the ability to disable safeties on all air alarms. This will allow you to use the environmental mode Flood, which disables scrubbers as well as pressure checks on vents. \ - Anyone can check the air alarm's interface and may be tipped off by their nonfunctionality." - one_purchase = TRUE - cost = 50 - power_type = /datum/action/innate/ai/break_air_alarms - unlock_text = "You remove the safety overrides on all air alarms, but you leave the confirm prompts open. You can hit 'Yes' at any time... you bastard." - -/datum/action/innate/ai/break_air_alarms - name = "Override Air Alarm Safeties" - desc = "Enables the Flood setting on all air alarms." - button_icon_state = "break_air_alarms" - uses = 1 - -/datum/action/innate/ai/break_air_alarms/Activate() - for(var/obj/machinery/alarm/AA in GLOB.machines) - if(!is_station_level(AA.z)) - continue - AA.emagged = TRUE - to_chat(owner, "All air alarm safeties on the station have been overriden. Air alarms may now use the Flood environmental mode.") - owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0) - - -//Overload Machine: Allows the AI to overload a machine, detonating it after a delay. Two uses per purchase. -/datum/AI_Module/small/overload_machine - module_name = "Machine Overload" - mod_pick_name = "overload" - description = "Overheats an electrical machine, causing a small explosion and destroying it. Two uses per purchase." - cost = 20 - power_type = /datum/action/innate/ai/ranged/overload_machine - unlock_text = "You enable the ability for the station's APCs to direct intense energy into machinery." - -/datum/action/innate/ai/ranged/overload_machine - name = "Overload Machine" - desc = "Overheats a machine, causing a small explosion after a short time." - button_icon_state = "overload_machine" - uses = 2 - linked_ability_type = /obj/effect/proc_holder/ranged_ai/overload_machine - -/datum/action/innate/ai/ranged/overload_machine/New() - ..() - desc = "[desc] It has [uses] use\s remaining." - button.desc = desc - -/datum/action/innate/ai/ranged/overload_machine/proc/detonate_machine(obj/machinery/M) - if(M && !QDELETED(M)) - explosion(get_turf(M), 0,1,1,0) - if(M) //to check if the explosion killed it before we try to delete it - qdel(M) - -/obj/effect/proc_holder/ranged_ai/overload_machine - active = FALSE - ranged_mousepointer = 'icons/effects/overload_machine_target.dmi' - enable_text = "You tap into the station's powernet. Click on a machine to detonate it, or use the ability again to cancel." - disable_text = "You release your hold on the powernet." - -/obj/effect/proc_holder/ranged_ai/overload_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target) - if(..()) - return - if(ranged_ability_user.incapacitated()) - remove_ranged_ability() - return - if(!istype(target)) - to_chat(ranged_ability_user, "You can only overload machines!") - return - - ranged_ability_user.playsound_local(ranged_ability_user, "sparks", 50, 0) - attached_action.adjust_uses(-1) - if(attached_action && attached_action.uses) - attached_action.desc = "[initial(attached_action.desc)] It has [attached_action.uses] use\s remaining." - attached_action.UpdateButtonIcon() - target.audible_message("You hear a loud electrical buzzing sound coming from [target]!") - addtimer(CALLBACK(attached_action, /datum/action/innate/ai/ranged/overload_machine.proc/detonate_machine, target), 50) //kaboom! - remove_ranged_ability(ranged_ability_user, "Overloading machine circuitry...") - return TRUE - - -//Override Machine: Allows the AI to override a machine, animating it into an angry, living version of itself. -/datum/AI_Module/small/override_machine - module_name = "Machine Override" - mod_pick_name = "override" - description = "Overrides a machine's programming, causing it to rise up and attack everyone except other machines. Four uses." - cost = 30 - power_type = /datum/action/innate/ai/ranged/override_machine - unlock_text = "You procure a virus from the Space Dark Web and distribute it to the station's machines." - -/datum/action/innate/ai/ranged/override_machine - name = "Override Machine" - desc = "Animates a targeted machine, causing it to attack anyone nearby." - button_icon_state = "override_machine" - uses = 4 - linked_ability_type = /obj/effect/proc_holder/ranged_ai/override_machine - -/datum/action/innate/ai/ranged/override_machine/New() - ..() - desc = "[desc] It has [uses] use\s remaining." - button.desc = desc - -/datum/action/innate/ai/ranged/override_machine/proc/animate_machine(obj/machinery/M) - if(M && !QDELETED(M)) - new/mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(M), M, owner, 1) - -/obj/effect/proc_holder/ranged_ai/override_machine - active = FALSE - ranged_mousepointer = 'icons/effects/override_machine_target.dmi' - enable_text = "You tap into the station's powernet. Click on a machine to animate it, or use the ability again to cancel." - disable_text = "You release your hold on the powernet." - -/obj/effect/proc_holder/ranged_ai/override_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target) - if(..()) - return - if(ranged_ability_user.incapacitated()) - remove_ranged_ability() - return - if(!istype(target)) - to_chat(ranged_ability_user, "You can only animate machines!") - return - if(!target.can_be_overridden()) - to_chat(ranged_ability_user, "That machine can't be overridden!") - return - - ranged_ability_user.playsound_local(ranged_ability_user, 'sound/misc/interference.ogg', 50, 0) - attached_action.adjust_uses(-1) - if(attached_action && attached_action.uses) - attached_action.desc = "[initial(attached_action.desc)] It has [attached_action.uses] use\s remaining." - attached_action.UpdateButtonIcon() - target.audible_message("You hear a loud electrical buzzing sound coming from [target]!") - addtimer(CALLBACK(attached_action, /datum/action/innate/ai/ranged/override_machine.proc/animate_machine, target), 50) //kabeep! - remove_ranged_ability(ranged_ability_user, "Sending override signal...") - return TRUE - - -//Robotic Factory: Places a large machine that converts humans that go through it into cyborgs. Unlocking this ability removes shunting. -/datum/AI_Module/large/place_cyborg_transformer - module_name = "Robotic Factory (Removes Shunting)" - mod_pick_name = "cyborgtransformer" - description = "Build a machine anywhere, using expensive nanomachines, that can convert a living human into a loyal cyborg slave when placed inside." - cost = 100 - one_purchase = TRUE - power_type = /datum/action/innate/ai/place_transformer - unlock_text = "You prepare a robotics factory for deployment." - unlock_sound = 'sound/machines/ping.ogg' - -/datum/action/innate/ai/place_transformer - name = "Place Robotics Factory" - desc = "Places a machine that converts humans into cyborgs. Conveyor belts included!" - button_icon_state = "robotic_factory" - uses = 1 - auto_use_uses = FALSE //So we can attempt multiple times - var/list/turfOverlays - -/datum/action/innate/ai/place_transformer/New() - ..() - for(var/i in 1 to 3) - var/image/I = image("icon"='icons/turf/overlays.dmi') - LAZYADD(turfOverlays, I) - -/datum/action/innate/ai/place_transformer/Activate() - if(!owner_AI.can_place_transformer(src)) - return - active = TRUE - if(alert(owner, "Are you sure you want to place the machine here?", "Are you sure?", "Yes", "No") == "No") - active = FALSE - return - if(!owner_AI.can_place_transformer(src)) - active = FALSE - return - var/turf/T = get_turf(owner_AI.eyeobj) - new /obj/machinery/transformer/conveyor(T) - playsound(T, 'sound/effects/phasein.ogg', 100, 1) - owner_AI.can_shunt = FALSE - to_chat(owner, "You are no longer able to shunt your core to APCs.") - adjust_uses(-1) - -/mob/living/silicon/ai/proc/remove_transformer_image(client/C, image/I, turf/T) - if(C && I.loc == T) - C.images -= I - -/mob/living/silicon/ai/proc/can_place_transformer(datum/action/innate/ai/place_transformer/action) - if(!eyeobj || !isturf(loc) || incapacitated() || !action) - return - var/turf/middle = get_turf(eyeobj) - var/list/turfs = list(middle, locate(middle.x - 1, middle.y, middle.z), locate(middle.x + 1, middle.y, middle.z)) - var/alert_msg = "There isn't enough room! Make sure you are placing the machine in a clear area and on a floor." - var/success = TRUE - for(var/n in 1 to 3) //We have to do this instead of iterating normally because of how overlay images are handled - var/turf/T = turfs[n] - if(!isfloorturf(T)) - success = FALSE - var/datum/camerachunk/C = cameranet.getCameraChunk(T.x, T.y, T.z) - if(!C.visibleTurfs[T]) - alert_msg = "You don't have camera vision of this location!" - success = FALSE - for(var/atom/movable/AM in T.contents) - if(AM.density) - alert_msg = "That area must be clear of objects!" - success = FALSE - var/image/I = action.turfOverlays[n] - I.loc = T - client.images += I - I.icon_state = "[success ? "green" : "red"]Overlay" //greenOverlay and redOverlay for success and failure respectively - addtimer(CALLBACK(src, .proc/remove_transformer_image, client, I, T), 30) - if(!success) - to_chat(src, "[alert_msg]") - return success - -//Blackout: Overloads a random number of lights across the station. Three uses. -/datum/AI_Module/small/blackout - module_name = "Blackout" - mod_pick_name = "blackout" - description = "Attempts to overload the lighting circuits on the station, destroying some bulbs. Three uses." - cost = 15 - power_type = /datum/action/innate/ai/blackout - unlock_text = "You hook into the powernet and route bonus power towards the station's lighting." - -/datum/action/innate/ai/blackout - name = "Blackout" - desc = "Overloads random lights across the station." - button_icon_state = "blackout" - uses = 3 - auto_use_uses = FALSE - -/datum/action/innate/ai/blackout/New() - ..() - desc = "[desc] It has [uses] use\s remaining." - button.desc = desc - -/datum/action/innate/ai/blackout/Activate() - for(var/obj/machinery/power/apc/apc in GLOB.apcs) - if(prob(30 * apc.overload)) - apc.overload_lighting() - else - apc.overload++ - to_chat(owner, "Overcurrent applied to the powernet.") - owner.playsound_local(owner, "sparks", 50, 0) - adjust_uses(-1) - if(src && uses) //Not sure if not having src here would cause a runtime, so it's here to be safe - desc = "[initial(desc)] It has [uses] use\s remaining." - UpdateButtonIcon() - -//Reactivate Camera Network: Reactivates up to 30 cameras across the station. -/datum/AI_Module/small/reactivate_cameras - module_name = "Reactivate Camera Network" - mod_pick_name = "recam" - description = "Runs a network-wide diagnostic on the camera network, resetting focus and re-routing power to failed cameras. Can be used to repair up to 30 cameras." - cost = 10 - one_purchase = TRUE - power_type = /datum/action/innate/ai/reactivate_cameras - unlock_text = "You deploy nanomachines to the cameranet." - -/datum/action/innate/ai/reactivate_cameras - name = "Reactivate Cameras" - desc = "Reactivates disabled cameras across the station; remaining uses can be used later." - button_icon_state = "reactivate_cameras" - uses = 30 - auto_use_uses = FALSE - cooldown_period = 30 - -/datum/action/innate/ai/reactivate_cameras/New() - ..() - desc = "[desc] It has [uses] use\s remaining." - button.desc = desc - -/datum/action/innate/ai/reactivate_cameras/Activate() - var/fixed_cameras = 0 - for(var/V in cameranet.cameras) - if(!uses) - break - var/obj/machinery/camera/C = V - if(!C.status || C.view_range != initial(C.view_range)) - C.toggle_cam(owner_AI, 0) //Reactivates the camera based on status. Badly named proc. - C.view_range = initial(C.view_range) - fixed_cameras++ - uses-- //Not adjust_uses() so it doesn't automatically delete or show a message - to_chat(owner, "Diagnostic complete! Cameras reactivated: [fixed_cameras]. Reactivations remaining: [uses].") - owner.playsound_local(owner, 'sound/items/wirecutter.ogg', 50, 0) - adjust_uses(0, TRUE) //Checks the uses remaining - if(src && uses) //Not sure if not having src here would cause a runtime, so it's here to be safe - desc = "[initial(desc)] It has [uses] use\s remaining." - UpdateButtonIcon() - -//Upgrade Camera Network: EMP-proofs all cameras, in addition to giving them X-ray vision. -/datum/AI_Module/large/upgrade_cameras - module_name = "Upgrade Camera Network" - mod_pick_name = "upgradecam" - description = "Install broad-spectrum scanning and electrical redundancy firmware to the camera network, enabling EMP-proofing and light-amplified X-ray vision." //I <3 pointless technobabble - //This used to have motion sensing as well, but testing quickly revealed that giving it to the whole cameranet is PURE HORROR. - one_purchase = TRUE - cost = 35 //Decent price for omniscience! - upgrade = TRUE - unlock_text = "OTA firmware distribution complete! Cameras upgraded: CAMSUPGRADED. Light amplification system online." - unlock_sound = 'sound/items/rped.ogg' - -/datum/AI_Module/large/upgrade_cameras/upgrade(mob/living/silicon/ai/AI) - AI.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //Night-vision, without which X-ray would be very limited in power. - AI.update_sight() - var/upgraded_cameras = 0 - - for(var/V in cameranet.cameras) - var/obj/machinery/camera/C = V - if(C.assembly) - var/upgraded = FALSE - - if(!C.isXRay()) - C.upgradeXRay() - //Update what it can see. - cameranet.updateVisibility(C, 0) - upgraded = TRUE - - if(!C.isEmpProof()) - C.upgradeEmpProof() - upgraded = TRUE - - if(upgraded) - upgraded_cameras++ - - unlock_text = replacetext(unlock_text, "CAMSUPGRADED", "[upgraded_cameras]") //This works, since unlock text is called after upgrade() - -/datum/AI_Module/large/eavesdrop - module_name = "Enhanced Surveillance" - mod_pick_name = "eavesdrop" - description = "Via a combination of hidden microphones and lip reading software, you are able to use your cameras to listen in on conversations." - cost = 30 - one_purchase = TRUE - upgrade = TRUE - unlock_text = "OTA firmware distribution complete! Cameras upgraded: Enhanced surveillance package online." - unlock_sound = 'sound/items/rped.ogg' - -/datum/AI_Module/large/eavesdrop/upgrade(mob/living/silicon/ai/AI) - if(AI.eyeobj) - AI.eyeobj.relay_speech = TRUE - +//The malf AI action subtype. All malf actions are subtypes of this. +/datum/action/innate/ai + name = "AI Action" + desc = "You aren't entirely sure what this does, but it's very beepy and boopy." + background_icon_state = "bg_tech_blue" + var/mob/living/silicon/ai/owner_AI //The owner AI, so we don't have to typecast every time + var/uses //If we have multiple uses of the same power + var/auto_use_uses = TRUE //If we automatically use up uses on each activation + var/cooldown_period //If applicable, the time in deciseconds we have to wait before using any more modules + +/datum/action/innate/ai/Grant(mob/living/L) + . = ..() + if(!isAI(owner)) + WARNING("AI action [name] attempted to grant itself to non-AI mob [L.real_name] ([L.key])!") + qdel(src) + else + owner_AI = owner + +/datum/action/innate/ai/IsAvailable() + . = ..() + if(owner_AI && owner_AI.malf_cooldown > world.time) + return + +/datum/action/innate/ai/Trigger() + . = ..() + if(auto_use_uses) + adjust_uses(-1) + if(cooldown_period) + owner_AI.malf_cooldown = world.time + cooldown_period + +/datum/action/innate/ai/proc/adjust_uses(amt, silent) + uses += amt + if(!silent && uses) + to_chat(owner, "[name] now has [uses] use[uses > 1 ? "s" : ""] remaining.") + if(!uses) + if(initial(uses) > 1) //no need to tell 'em if it was one-use anyway! + to_chat(owner, "[name] has run out of uses!") + qdel(src) + +//Framework for ranged abilities that can have different effects by left-clicking stuff. +/datum/action/innate/ai/ranged + name = "Ranged AI Action" + auto_use_uses = FALSE //This is so we can do the thing and disable/enable freely without having to constantly add uses + var/obj/effect/proc_holder/ranged_ai/linked_ability //The linked proc holder that contains the actual ability code + var/linked_ability_type //The path of our linked ability + +/datum/action/innate/ai/ranged/New() + if(!linked_ability_type) + WARNING("Ranged AI action [name] attempted to spawn without a linked ability!") + qdel(src) //uh oh! + return + linked_ability = new linked_ability_type() + linked_ability.attached_action = src + ..() + +/datum/action/innate/ai/ranged/adjust_uses(amt, silent) + uses += amt + if(!silent && uses) + to_chat(owner, "[name] now has [uses] use[uses > 1 ? "s" : ""] remaining.") + if(!uses) + if(initial(uses) > 1) //no need to tell 'em if it was one-use anyway! + to_chat(owner, "[name] has run out of uses!") + Remove(owner) + QDEL_IN(src, 100) //let any active timers on us finish up + +/datum/action/innate/ai/ranged/Destroy() + QDEL_NULL(linked_ability) + return ..() + +/datum/action/innate/ai/ranged/Activate() + linked_ability.toggle(owner) + return TRUE + +//The actual ranged proc holder. +/obj/effect/proc_holder/ranged_ai + var/enable_text = "Hello World!" //Appears when the user activates the ability + var/disable_text = "Goodbye Cruel World!" //Context clues! + var/datum/action/innate/ai/ranged/attached_action + +/obj/effect/proc_holder/ranged_ai/proc/toggle(mob/user) + if(active) + remove_ranged_ability(user, disable_text) + else + add_ranged_ability(user, enable_text) + +//The datum and interface for the malf unlock menu, which lets them choose actions to unlock. +/datum/module_picker + var/temp + var/processing_time = 50 + var/list/possible_modules + +/datum/module_picker/New() + possible_modules = list() + for(var/type in typesof(/datum/AI_Module)) + var/datum/AI_Module/AM = new type + if((AM.power_type && AM.power_type != /datum/action/innate/ai) || AM.upgrade) + possible_modules += AM + +/datum/module_picker/proc/remove_malf_verbs(mob/living/silicon/ai/AI) //Removes all malfunction-related abilities from the target AI. + for(var/datum/AI_Module/AM in possible_modules) + for(var/datum/action/A in AI.actions) + if(istype(A, initial(AM.power_type))) + qdel(A) + +/datum/module_picker/proc/use(user as mob) + var/dat + dat += {"Select use of processing time: (currently #[processing_time] left.)
    +
    + Install Module:
    + The number afterwards is the amount of processing time it consumes.
    "} + for(var/datum/AI_Module/large/module in possible_modules) + dat += "[module.module_name]\[?\] ([module.cost])
    " + for(var/datum/AI_Module/small/module in possible_modules) + dat += "[module.module_name]\[?\] ([module.cost])
    " + dat += "
    " + if(temp) + dat += "[temp]" + var/datum/browser/popup = new(user, "modpicker", "Malf Module Menu", 400, 500) + popup.set_content(dat) + popup.open() + return + +/datum/module_picker/Topic(href, href_list) + ..() + + if(!isAI(usr)) + return + var/mob/living/silicon/ai/A = usr + + if(A.stat == DEAD) + to_chat(A, "You are already dead!") + return + + for(var/datum/AI_Module/AM in possible_modules) + if (href_list[AM.mod_pick_name]) + + // Cost check + if(AM.cost > processing_time) + temp = "You cannot afford this module." + break + + var/datum/action/innate/ai/action = locate(AM.power_type) in A.actions + + // Give the power and take away the money. + if(AM.upgrade) //upgrade and upgrade() are separate, be careful! + AM.upgrade(A) + possible_modules -= AM + to_chat(A, AM.unlock_text) + A.playsound_local(A, AM.unlock_sound, 50, 0) + else + if(AM.power_type) + if(!action) //Unlocking for the first time + var/datum/action/AC = new AM.power_type + AC.Grant(A) + A.current_modules += new AM.type + temp = AM.description + if(AM.one_purchase) + possible_modules -= AM + if(AM.unlock_text) + to_chat(A, AM.unlock_text) + if(AM.unlock_sound) + A.playsound_local(A, AM.unlock_sound, 50, 0) + else //Adding uses to an existing module + action.uses += initial(action.uses) + action.desc = "[initial(action.desc)] It has [action.uses] use\s remaining." + action.UpdateButtonIcon() + temp = "Additional use[action.uses > 1 ? "s" : ""] added to [action.name]!" + processing_time -= AM.cost + + if(href_list["showdesc"]) + if(AM.mod_pick_name == href_list["showdesc"]) + temp = AM.description + use(usr) + +//The base module type, which holds info about each ability. +/datum/AI_Module + var/module_name + var/mod_pick_name + var/description = "" + var/engaged = 0 + var/cost = 5 + var/one_purchase = FALSE //If this module can only be purchased once. This always applies to upgrades, even if the variable is set to false. + var/power_type = /datum/action/innate/ai //If the module gives an active ability, use this. Mutually exclusive with upgrade. + var/upgrade //If the module gives a passive upgrade, use this. Mutually exclusive with power_type. + var/unlock_text = "Hello World!" //Text shown when an ability is unlocked + var/unlock_sound //Sound played when an ability is unlocked + var/uses + +/datum/AI_Module/proc/upgrade(mob/living/silicon/AI/AI) //Apply upgrades! + return + +/datum/AI_Module/large //Big, powerful stuff that can only be used once. + +/datum/AI_Module/small //Weak, usually localized stuff with multiple uses. + +//Doomsday Device: Starts the self-destruct timer. It can only be stopped by killing the AI completely. +/datum/AI_Module/large/nuke_station + module_name = "Doomsday Device" + mod_pick_name = "nukestation" + description = "Activate a weapon that will disintegrate all organic life on the station after a 450 second delay. Can only be used while on the station, will fail if your core is moved off station or destroyed." + cost = 130 + one_purchase = TRUE + power_type = /datum/action/innate/ai/nuke_station + unlock_text = "You slowly, carefully, establish a connection with the on-station self-destruct. You can now activate it at any time." + unlock_sound = 'sound/items/timer.ogg' + +/datum/action/innate/ai/nuke_station + name = "Doomsday Device" + desc = "Activates the doomsday device. This is not reversible." + button_icon_state = "doomsday_device" + auto_use_uses = FALSE + +/datum/action/innate/ai/nuke_station/Activate() + var/turf/T = get_turf(owner) + if(!istype(T) || !is_station_level(T.z)) + to_chat(owner, "You cannot activate the doomsday device while off-station!") + return + if(alert(owner, "Send arming signal? (true = arm, false = cancel)", "purge_all_life()", "confirm = TRUE;", "confirm = FALSE;") != "confirm = TRUE;") + return + if(active) + return //prevent the AI from activating an already active doomsday + active = TRUE + set_us_up_the_bomb() + +/datum/action/innate/ai/nuke_station/proc/set_us_up_the_bomb() + to_chat(owner_AI, "Nuclear device armed.") + event_announcement.Announce("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core.", "Anomaly Alert", new_sound = 'sound/AI/aimalf.ogg') + set_security_level("delta") + owner_AI.nuking = TRUE + var/obj/machinery/doomsday_device/DOOM = new /obj/machinery/doomsday_device(owner_AI) + owner_AI.doomsday_device = DOOM + owner_AI.doomsday_device.start() + for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) + for(var/mob/living/silicon/ai/A in ai_list) + if((A.stat != DEAD) && A.nuking) + point.the_disk = A //The pinpointer now tracks the AI core + qdel(src) + +/obj/machinery/doomsday_device + icon = 'icons/obj/machines/nuke_terminal.dmi' + name = "doomsday device" + icon_state = "nuclearbomb_base" + desc = "A weapon which disintegrates all organic life in a large area." + anchored = 1 + density = 1 + atom_say_verb = "blares" + speed_process = TRUE // Disgusting fix. Please remove once #12952 is merged + var/timing = 0 + var/default_timer = 4500 + var/detonation_timer + var/announced = 0 + +/obj/machinery/doomsday_device/Destroy() + STOP_PROCESSING(SSfastprocess, src) + SSshuttle.emergencyNoEscape = 0 + if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) + SSshuttle.emergency.mode = SHUTTLE_DOCKED + SSshuttle.emergency.timer = world.time + priority_announcement.Announce("Hostile environment resolved. You have 3 minutes to board the Emergency Shuttle.", "Priority Announcement", 'sound/AI/shuttledock.ogg') + return ..() + +/obj/machinery/doomsday_device/proc/start() + detonation_timer = world.time + default_timer + timing = 1 + START_PROCESSING(SSfastprocess, src) + SSshuttle.emergencyNoEscape = 1 + +/obj/machinery/doomsday_device/proc/seconds_remaining() + . = max(0, (round(detonation_timer - world.time) / 10)) + +/obj/machinery/doomsday_device/process() + var/turf/T = get_turf(src) + if(!T || !is_station_level(T.z)) + minor_announcement.Announce("DOOMSDAY DEVICE OUT OF STATION RANGE, ABORTING", "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4", 'sound/misc/notice1.ogg') + SSshuttle.emergencyNoEscape = 0 + if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) + SSshuttle.emergency.mode = SHUTTLE_DOCKED + SSshuttle.emergency.timer = world.time + priority_announcement.Announce("Hostile environment resolved. You have 3 minutes to board the Emergency Shuttle.", "Priority Announcement", 'sound/AI/shuttledock.ogg') + qdel(src) + if(!timing) + STOP_PROCESSING(SSfastprocess, src) + return + var/sec_left = seconds_remaining() + if(sec_left <= 0) + timing = 0 + detonate(T.z) + qdel(src) + else + if(!(sec_left % 60) && !announced) + var/message = "[sec_left] SECONDS UNTIL DOOMSDAY DEVICE ACTIVATION!" + minor_announcement.Announce(message, "ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4", 'sound/misc/notice1.ogg') + announced = 10 + announced = max(0, announced-1) + +/obj/machinery/doomsday_device/proc/detonate(z_level = 1) + for(var/mob/M in GLOB.player_list) + M << 'sound/machines/alarm.ogg' + sleep(100) + for(var/mob/living/L in GLOB.mob_list) + var/turf/T = get_turf(L) + if(!T || T.z != z_level) + continue + if(issilicon(L)) + continue + to_chat(L, "The blast wave from [src] tears you atom from atom!") + L.dust() + to_chat(world, "The AI cleansed the station of life with the doomsday device!") + SSticker.force_ending = 1 + +//AI Turret Upgrade: Increases the health and damage of all turrets. +/datum/AI_Module/large/upgrade_turrets + module_name = "AI Turret Upgrade" + mod_pick_name = "turret" + description = "Improves the power and health of all AI turrets. This effect is permanent." + cost = 30 + upgrade = TRUE + unlock_text = "You establish a power diversion to your turrets, upgrading their health and damage." + unlock_sound = 'sound/items/rped.ogg' + +/datum/AI_Module/large/upgrade_turrets/upgrade(mob/living/silicon/AI/AI) + for(var/obj/machinery/porta_turret/turret in GLOB.machines) + var/turf/T = get_turf(turret) + if(is_station_level(T.z)) + turret.health += 30 + turret.eprojectile = /obj/item/projectile/beam/laser/heavylaser //Once you see it, you will know what it means to FEAR. + turret.eshot_sound = 'sound/weapons/lasercannonfire.ogg' + +//Hostile Station Lockdown: Locks, bolts, and electrifies every airlock on the station. After 90 seconds, the doors reset. +/datum/AI_Module/large/lockdown + module_name = "Hostile Station Lockdown" + mod_pick_name = "lockdown" + description = "Overload the airlock, blast door and fire control networks, locking them down. Caution! This command also electrifies all airlocks. The networks will automatically reset after 90 seconds, briefly \ + opening all doors on the station." + cost = 30 + one_purchase = TRUE + power_type = /datum/action/innate/ai/lockdown + unlock_text = "You upload a sleeper trojan into the door control systems. You can send a signal to set it off at any time." + +/datum/action/innate/ai/lockdown + name = "Lockdown" + desc = "Closes, bolts, and depowers every airlock, firelock, and blast door on the station. After 90 seconds, they will reset themselves." + button_icon_state = "lockdown" + uses = 1 + +/datum/action/innate/ai/lockdown/Activate() + for(var/obj/machinery/door/D in GLOB.airlocks) + if(!is_station_level(D.z)) + continue + INVOKE_ASYNC(D, /obj/machinery/door.proc/hostile_lockdown, owner) + addtimer(CALLBACK(D, /obj/machinery/door.proc/disable_lockdown), 900) + + post_status("alert", "lockdown") + + minor_announcement.Announce("Hostile runtime detected in door controllers. Isolation lockdown protocols are now in effect. Please remain calm.", "Network Alert") + to_chat(owner, "Lockdown Initiated. Network reset in 90 seconds.") + spawn(900) + minor_announcement.Announce("Automatic system reboot complete. Have a secure day.","Network reset:") + +//Destroy RCDs: Detonates all non-cyborg RCDs on the station. +/datum/AI_Module/large/destroy_rcd + module_name = "Destroy RCDs" + mod_pick_name = "rcd" + description = "Send a specialised pulse to detonate all hand-held and exosuit Rapid Construction Devices on the station." + cost = 25 + one_purchase = TRUE + power_type = /datum/action/innate/ai/destroy_rcds + unlock_text = "After some improvisation, you rig your onboard radio to be able to send a signal to detonate all RCDs." + +/datum/action/innate/ai/destroy_rcds + name = "Destroy RCDs" + desc = "Detonate all non-cyborg RCDs on the station." + button_icon_state = "detonate_rcds" + uses = 1 + cooldown_period = 100 + +/datum/action/innate/ai/destroy_rcds/Activate() + for(var/obj/item/rcd/RCD in GLOB.rcd_list) + if(!istype(RCD, /obj/item/rcd/borg)) //Ensures that cyborg RCDs are spared. + RCD.detonate_pulse() + + to_chat(owner, "RCD detonation pulse emitted.") + owner.playsound_local(owner, 'sound/machines/twobeep.ogg', 50, 0) + +//Unlock Mech Domination: Unlocks the ability to dominate mechs. Big shocker, right? +/datum/AI_Module/large/mecha_domination + module_name = "Unlock Mech Domination" + mod_pick_name = "mechjack" + description = "Allows you to hack into a mech's onboard computer, shunting all processes into it and ejecting any occupants. Once uploaded to the mech, it is impossible to leave.\ + Do not allow the mech to leave the station's vicinity or allow it to be destroyed." + cost = 30 + upgrade = TRUE + unlock_text = "Virus package compiled. Select a target mech at any time. You must remain on the station at all times. Loss of signal will result in total system lockout." + unlock_sound = 'sound/mecha/nominal.ogg' + +/datum/AI_Module/large/mecha_domination/upgrade(mob/living/silicon/ai/AI) + AI.can_dominate_mechs = TRUE //Yep. This is all it does. Honk! + +//Thermal Sensor Override: Unlocks the ability to disable all fire alarms from doing their job. +/datum/AI_Module/large/break_fire_alarms + module_name = "Thermal Sensor Override" + mod_pick_name = "burnpigs" + description = "Gives you the ability to override the thermal sensors on all fire alarms. This will remove their ability to scan for fire and thus their ability to alert. \ + Anyone can check the fire alarm's interface and may be tipped off by its status." + one_purchase = TRUE + cost = 25 + power_type = /datum/action/innate/ai/break_fire_alarms + unlock_text = "You replace the thermal sensing capabilities of all fire alarms with a manual override, allowing you to turn them off at will." + +/datum/action/innate/ai/break_fire_alarms + name = "Override Thermal Sensors" + desc = "Disables the automatic temperature sensing on all fire alarms, making them effectively useless." + button_icon_state = "break_fire_alarms" + uses = 1 + +/datum/action/innate/ai/break_fire_alarms/Activate() + for(var/obj/machinery/firealarm/F in GLOB.machines) + if(!is_station_level(F.z)) + continue + F.emagged = TRUE + to_chat(owner, "All thermal sensors on the station have been disabled. Fire alerts will no longer be recognized.") + owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0) + +//Air Alarm Safety Override: Unlocks the ability to enable flooding on all air alarms. +/datum/AI_Module/large/break_air_alarms + module_name = "Air Alarm Safety Override" + mod_pick_name = "allow_flooding" + description = "Gives you the ability to disable safeties on all air alarms. This will allow you to use the environmental mode Flood, which disables scrubbers as well as pressure checks on vents. \ + Anyone can check the air alarm's interface and may be tipped off by their nonfunctionality." + one_purchase = TRUE + cost = 50 + power_type = /datum/action/innate/ai/break_air_alarms + unlock_text = "You remove the safety overrides on all air alarms, but you leave the confirm prompts open. You can hit 'Yes' at any time... you bastard." + +/datum/action/innate/ai/break_air_alarms + name = "Override Air Alarm Safeties" + desc = "Enables the Flood setting on all air alarms." + button_icon_state = "break_air_alarms" + uses = 1 + +/datum/action/innate/ai/break_air_alarms/Activate() + for(var/obj/machinery/alarm/AA in GLOB.machines) + if(!is_station_level(AA.z)) + continue + AA.emagged = TRUE + to_chat(owner, "All air alarm safeties on the station have been overriden. Air alarms may now use the Flood environmental mode.") + owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0) + + +//Overload Machine: Allows the AI to overload a machine, detonating it after a delay. Two uses per purchase. +/datum/AI_Module/small/overload_machine + module_name = "Machine Overload" + mod_pick_name = "overload" + description = "Overheats an electrical machine, causing a small explosion and destroying it. Two uses per purchase." + cost = 20 + power_type = /datum/action/innate/ai/ranged/overload_machine + unlock_text = "You enable the ability for the station's APCs to direct intense energy into machinery." + +/datum/action/innate/ai/ranged/overload_machine + name = "Overload Machine" + desc = "Overheats a machine, causing a small explosion after a short time." + button_icon_state = "overload_machine" + uses = 2 + linked_ability_type = /obj/effect/proc_holder/ranged_ai/overload_machine + +/datum/action/innate/ai/ranged/overload_machine/New() + ..() + desc = "[desc] It has [uses] use\s remaining." + button.desc = desc + +/datum/action/innate/ai/ranged/overload_machine/proc/detonate_machine(obj/machinery/M) + if(M && !QDELETED(M)) + explosion(get_turf(M), 0,1,1,0) + if(M) //to check if the explosion killed it before we try to delete it + qdel(M) + +/obj/effect/proc_holder/ranged_ai/overload_machine + active = FALSE + ranged_mousepointer = 'icons/effects/overload_machine_target.dmi' + enable_text = "You tap into the station's powernet. Click on a machine to detonate it, or use the ability again to cancel." + disable_text = "You release your hold on the powernet." + +/obj/effect/proc_holder/ranged_ai/overload_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target) + if(..()) + return + if(ranged_ability_user.incapacitated()) + remove_ranged_ability() + return + if(!istype(target)) + to_chat(ranged_ability_user, "You can only overload machines!") + return + + ranged_ability_user.playsound_local(ranged_ability_user, "sparks", 50, 0) + attached_action.adjust_uses(-1) + if(attached_action && attached_action.uses) + attached_action.desc = "[initial(attached_action.desc)] It has [attached_action.uses] use\s remaining." + attached_action.UpdateButtonIcon() + target.audible_message("You hear a loud electrical buzzing sound coming from [target]!") + addtimer(CALLBACK(attached_action, /datum/action/innate/ai/ranged/overload_machine.proc/detonate_machine, target), 50) //kaboom! + remove_ranged_ability(ranged_ability_user, "Overloading machine circuitry...") + return TRUE + + +//Override Machine: Allows the AI to override a machine, animating it into an angry, living version of itself. +/datum/AI_Module/small/override_machine + module_name = "Machine Override" + mod_pick_name = "override" + description = "Overrides a machine's programming, causing it to rise up and attack everyone except other machines. Four uses." + cost = 30 + power_type = /datum/action/innate/ai/ranged/override_machine + unlock_text = "You procure a virus from the Space Dark Web and distribute it to the station's machines." + +/datum/action/innate/ai/ranged/override_machine + name = "Override Machine" + desc = "Animates a targeted machine, causing it to attack anyone nearby." + button_icon_state = "override_machine" + uses = 4 + linked_ability_type = /obj/effect/proc_holder/ranged_ai/override_machine + +/datum/action/innate/ai/ranged/override_machine/New() + ..() + desc = "[desc] It has [uses] use\s remaining." + button.desc = desc + +/datum/action/innate/ai/ranged/override_machine/proc/animate_machine(obj/machinery/M) + if(M && !QDELETED(M)) + new/mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(M), M, owner, 1) + +/obj/effect/proc_holder/ranged_ai/override_machine + active = FALSE + ranged_mousepointer = 'icons/effects/override_machine_target.dmi' + enable_text = "You tap into the station's powernet. Click on a machine to animate it, or use the ability again to cancel." + disable_text = "You release your hold on the powernet." + +/obj/effect/proc_holder/ranged_ai/override_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target) + if(..()) + return + if(ranged_ability_user.incapacitated()) + remove_ranged_ability() + return + if(!istype(target)) + to_chat(ranged_ability_user, "You can only animate machines!") + return + if(!target.can_be_overridden()) + to_chat(ranged_ability_user, "That machine can't be overridden!") + return + + ranged_ability_user.playsound_local(ranged_ability_user, 'sound/misc/interference.ogg', 50, 0) + attached_action.adjust_uses(-1) + if(attached_action && attached_action.uses) + attached_action.desc = "[initial(attached_action.desc)] It has [attached_action.uses] use\s remaining." + attached_action.UpdateButtonIcon() + target.audible_message("You hear a loud electrical buzzing sound coming from [target]!") + addtimer(CALLBACK(attached_action, /datum/action/innate/ai/ranged/override_machine.proc/animate_machine, target), 50) //kabeep! + remove_ranged_ability(ranged_ability_user, "Sending override signal...") + return TRUE + + +//Robotic Factory: Places a large machine that converts humans that go through it into cyborgs. Unlocking this ability removes shunting. +/datum/AI_Module/large/place_cyborg_transformer + module_name = "Robotic Factory (Removes Shunting)" + mod_pick_name = "cyborgtransformer" + description = "Build a machine anywhere, using expensive nanomachines, that can convert a living human into a loyal cyborg slave when placed inside." + cost = 100 + one_purchase = TRUE + power_type = /datum/action/innate/ai/place_transformer + unlock_text = "You prepare a robotics factory for deployment." + unlock_sound = 'sound/machines/ping.ogg' + +/datum/action/innate/ai/place_transformer + name = "Place Robotics Factory" + desc = "Places a machine that converts humans into cyborgs. Conveyor belts included!" + button_icon_state = "robotic_factory" + uses = 1 + auto_use_uses = FALSE //So we can attempt multiple times + var/list/turfOverlays + +/datum/action/innate/ai/place_transformer/New() + ..() + for(var/i in 1 to 3) + var/image/I = image("icon"='icons/turf/overlays.dmi') + LAZYADD(turfOverlays, I) + +/datum/action/innate/ai/place_transformer/Activate() + if(!owner_AI.can_place_transformer(src)) + return + active = TRUE + if(alert(owner, "Are you sure you want to place the machine here?", "Are you sure?", "Yes", "No") == "No") + active = FALSE + return + if(!owner_AI.can_place_transformer(src)) + active = FALSE + return + var/turf/T = get_turf(owner_AI.eyeobj) + new /obj/machinery/transformer/conveyor(T) + playsound(T, 'sound/effects/phasein.ogg', 100, 1) + owner_AI.can_shunt = FALSE + to_chat(owner, "You are no longer able to shunt your core to APCs.") + adjust_uses(-1) + +/mob/living/silicon/ai/proc/remove_transformer_image(client/C, image/I, turf/T) + if(C && I.loc == T) + C.images -= I + +/mob/living/silicon/ai/proc/can_place_transformer(datum/action/innate/ai/place_transformer/action) + if(!eyeobj || !isturf(loc) || incapacitated() || !action) + return + var/turf/middle = get_turf(eyeobj) + var/list/turfs = list(middle, locate(middle.x - 1, middle.y, middle.z), locate(middle.x + 1, middle.y, middle.z)) + var/alert_msg = "There isn't enough room! Make sure you are placing the machine in a clear area and on a floor." + var/success = TRUE + for(var/n in 1 to 3) //We have to do this instead of iterating normally because of how overlay images are handled + var/turf/T = turfs[n] + if(!isfloorturf(T)) + success = FALSE + var/datum/camerachunk/C = cameranet.getCameraChunk(T.x, T.y, T.z) + if(!C.visibleTurfs[T]) + alert_msg = "You don't have camera vision of this location!" + success = FALSE + for(var/atom/movable/AM in T.contents) + if(AM.density) + alert_msg = "That area must be clear of objects!" + success = FALSE + var/image/I = action.turfOverlays[n] + I.loc = T + client.images += I + I.icon_state = "[success ? "green" : "red"]Overlay" //greenOverlay and redOverlay for success and failure respectively + addtimer(CALLBACK(src, .proc/remove_transformer_image, client, I, T), 30) + if(!success) + to_chat(src, "[alert_msg]") + return success + +//Blackout: Overloads a random number of lights across the station. Three uses. +/datum/AI_Module/small/blackout + module_name = "Blackout" + mod_pick_name = "blackout" + description = "Attempts to overload the lighting circuits on the station, destroying some bulbs. Three uses." + cost = 15 + power_type = /datum/action/innate/ai/blackout + unlock_text = "You hook into the powernet and route bonus power towards the station's lighting." + +/datum/action/innate/ai/blackout + name = "Blackout" + desc = "Overloads random lights across the station." + button_icon_state = "blackout" + uses = 3 + auto_use_uses = FALSE + +/datum/action/innate/ai/blackout/New() + ..() + desc = "[desc] It has [uses] use\s remaining." + button.desc = desc + +/datum/action/innate/ai/blackout/Activate() + for(var/obj/machinery/power/apc/apc in GLOB.apcs) + if(prob(30 * apc.overload)) + apc.overload_lighting() + else + apc.overload++ + to_chat(owner, "Overcurrent applied to the powernet.") + owner.playsound_local(owner, "sparks", 50, 0) + adjust_uses(-1) + if(src && uses) //Not sure if not having src here would cause a runtime, so it's here to be safe + desc = "[initial(desc)] It has [uses] use\s remaining." + UpdateButtonIcon() + +//Reactivate Camera Network: Reactivates up to 30 cameras across the station. +/datum/AI_Module/small/reactivate_cameras + module_name = "Reactivate Camera Network" + mod_pick_name = "recam" + description = "Runs a network-wide diagnostic on the camera network, resetting focus and re-routing power to failed cameras. Can be used to repair up to 30 cameras." + cost = 10 + one_purchase = TRUE + power_type = /datum/action/innate/ai/reactivate_cameras + unlock_text = "You deploy nanomachines to the cameranet." + +/datum/action/innate/ai/reactivate_cameras + name = "Reactivate Cameras" + desc = "Reactivates disabled cameras across the station; remaining uses can be used later." + button_icon_state = "reactivate_cameras" + uses = 30 + auto_use_uses = FALSE + cooldown_period = 30 + +/datum/action/innate/ai/reactivate_cameras/New() + ..() + desc = "[desc] It has [uses] use\s remaining." + button.desc = desc + +/datum/action/innate/ai/reactivate_cameras/Activate() + var/fixed_cameras = 0 + for(var/V in cameranet.cameras) + if(!uses) + break + var/obj/machinery/camera/C = V + if(!C.status || C.view_range != initial(C.view_range)) + C.toggle_cam(owner_AI, 0) //Reactivates the camera based on status. Badly named proc. + C.view_range = initial(C.view_range) + fixed_cameras++ + uses-- //Not adjust_uses() so it doesn't automatically delete or show a message + to_chat(owner, "Diagnostic complete! Cameras reactivated: [fixed_cameras]. Reactivations remaining: [uses].") + owner.playsound_local(owner, 'sound/items/wirecutter.ogg', 50, 0) + adjust_uses(0, TRUE) //Checks the uses remaining + if(src && uses) //Not sure if not having src here would cause a runtime, so it's here to be safe + desc = "[initial(desc)] It has [uses] use\s remaining." + UpdateButtonIcon() + +//Upgrade Camera Network: EMP-proofs all cameras, in addition to giving them X-ray vision. +/datum/AI_Module/large/upgrade_cameras + module_name = "Upgrade Camera Network" + mod_pick_name = "upgradecam" + description = "Install broad-spectrum scanning and electrical redundancy firmware to the camera network, enabling EMP-proofing and light-amplified X-ray vision." //I <3 pointless technobabble + //This used to have motion sensing as well, but testing quickly revealed that giving it to the whole cameranet is PURE HORROR. + one_purchase = TRUE + cost = 35 //Decent price for omniscience! + upgrade = TRUE + unlock_text = "OTA firmware distribution complete! Cameras upgraded: CAMSUPGRADED. Light amplification system online." + unlock_sound = 'sound/items/rped.ogg' + +/datum/AI_Module/large/upgrade_cameras/upgrade(mob/living/silicon/ai/AI) + AI.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //Night-vision, without which X-ray would be very limited in power. + AI.update_sight() + var/upgraded_cameras = 0 + + for(var/V in cameranet.cameras) + var/obj/machinery/camera/C = V + if(C.assembly) + var/upgraded = FALSE + + if(!C.isXRay()) + C.upgradeXRay() + //Update what it can see. + cameranet.updateVisibility(C, 0) + upgraded = TRUE + + if(!C.isEmpProof()) + C.upgradeEmpProof() + upgraded = TRUE + + if(upgraded) + upgraded_cameras++ + + unlock_text = replacetext(unlock_text, "CAMSUPGRADED", "[upgraded_cameras]") //This works, since unlock text is called after upgrade() + +/datum/AI_Module/large/eavesdrop + module_name = "Enhanced Surveillance" + mod_pick_name = "eavesdrop" + description = "Via a combination of hidden microphones and lip reading software, you are able to use your cameras to listen in on conversations." + cost = 30 + one_purchase = TRUE + upgrade = TRUE + unlock_text = "OTA firmware distribution complete! Cameras upgraded: Enhanced surveillance package online." + unlock_sound = 'sound/items/rped.ogg' + +/datum/AI_Module/large/eavesdrop/upgrade(mob/living/silicon/ai/AI) + if(AI.eyeobj) + AI.eyeobj.relay_speech = TRUE + diff --git a/code/game/gamemodes/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm index ee0ef02d1dc..f8ac70b31dc 100644 --- a/code/game/gamemodes/meteor/meteor.dm +++ b/code/game/gamemodes/meteor/meteor.dm @@ -1,61 +1,61 @@ -/datum/game_mode/meteor - name = "meteor" - config_tag = "meteor" - var/const/initialmeteordelay = 6000 - var/wave = 1 - required_players = 35 - - -/datum/game_mode/meteor/announce() - to_chat(world, "The current game mode is - Meteor!") - to_chat(world, "The space station has been stuck in a major meteor shower. You must escape from the station or at least live.") - - -/datum/game_mode/meteor/post_setup() - spawn(rand(waittime_l, waittime_h)) - command_announcement.Announce("The station is on the path of an incoming wave of meteors. Reinforce the hull and prepare damage control parties.", "Incoming Meteors", 'sound/effects/siren.ogg') - spawn(initialmeteordelay) - sendmeteors() - ..() - - - -/datum/game_mode/meteor/proc/sendmeteors() - var/waveduration = world.time + rand(0,1000) + text2num("[wave]000") / 2 - var/waitduration = rand(3000,6000) - while(waveduration - world.time > 0) - sleep(max(65 - text2num("[wave]0") / 2, 40)) - spawn() spawn_meteors(6, meteors_normal) - wave++ - sleep(waitduration) - sendmeteors() - -/datum/game_mode/meteor/declare_completion() - var/text - var/survivors = 0 - for(var/mob/living/player in GLOB.player_list) - if(player.stat != DEAD) - var/turf/location = get_turf(player.loc) - if(!location) continue - - if(location.loc.type == SSshuttle.emergency.areaInstance.type) //didn't work in the switch for some reason - text += "
    [player.real_name] escaped on the emergency shuttle" - - else - switch(location.loc.type) - if( /area/shuttle/escape_pod1/centcom, /area/shuttle/escape_pod2/centcom, /area/shuttle/escape_pod3/centcom, /area/shuttle/escape_pod5/centcom ) - text += "
    [player.real_name] escaped in a life pod." - else - text += "
    [player.real_name] survived but is stranded without any hope of rescue." - survivors++ - - if(survivors) - to_chat(world, "The following survived the meteor storm:[text]") - else - to_chat(world, "Nobody survived the meteor storm!") - - feedback_set_details("round_end_result","meteor end - evacuation") - feedback_set("round_end_result", "Meteor survivors: [survivors]") - - ..() - return 1 +/datum/game_mode/meteor + name = "meteor" + config_tag = "meteor" + var/const/initialmeteordelay = 6000 + var/wave = 1 + required_players = 35 + + +/datum/game_mode/meteor/announce() + to_chat(world, "The current game mode is - Meteor!") + to_chat(world, "The space station has been stuck in a major meteor shower. You must escape from the station or at least live.") + + +/datum/game_mode/meteor/post_setup() + spawn(rand(waittime_l, waittime_h)) + command_announcement.Announce("The station is on the path of an incoming wave of meteors. Reinforce the hull and prepare damage control parties.", "Incoming Meteors", 'sound/effects/siren.ogg') + spawn(initialmeteordelay) + sendmeteors() + ..() + + + +/datum/game_mode/meteor/proc/sendmeteors() + var/waveduration = world.time + rand(0,1000) + text2num("[wave]000") / 2 + var/waitduration = rand(3000,6000) + while(waveduration - world.time > 0) + sleep(max(65 - text2num("[wave]0") / 2, 40)) + spawn() spawn_meteors(6, meteors_normal) + wave++ + sleep(waitduration) + sendmeteors() + +/datum/game_mode/meteor/declare_completion() + var/text + var/survivors = 0 + for(var/mob/living/player in GLOB.player_list) + if(player.stat != DEAD) + var/turf/location = get_turf(player.loc) + if(!location) continue + + if(location.loc.type == SSshuttle.emergency.areaInstance.type) //didn't work in the switch for some reason + text += "
    [player.real_name] escaped on the emergency shuttle" + + else + switch(location.loc.type) + if( /area/shuttle/escape_pod1/centcom, /area/shuttle/escape_pod2/centcom, /area/shuttle/escape_pod3/centcom, /area/shuttle/escape_pod5/centcom ) + text += "
    [player.real_name] escaped in a life pod." + else + text += "
    [player.real_name] survived but is stranded without any hope of rescue." + survivors++ + + if(survivors) + to_chat(world, "The following survived the meteor storm:[text]") + else + to_chat(world, "Nobody survived the meteor storm!") + + feedback_set_details("round_end_result","meteor end - evacuation") + feedback_set("round_end_result", "Meteor survivors: [survivors]") + + ..() + return 1 diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm index 5dafcee72a7..507fedddd32 100644 --- a/code/game/gamemodes/meteor/meteors.dm +++ b/code/game/gamemodes/meteor/meteors.dm @@ -1,318 +1,318 @@ -//Meteors probability of spawning during a given wave -/var/list/meteors_normal = list(/obj/effect/meteor/dust=3, /obj/effect/meteor/medium=8, /obj/effect/meteor/big=3, \ - /obj/effect/meteor/flaming=1, /obj/effect/meteor/irradiated=3) //for normal meteor event - -/var/list/meteors_threatening = list(/obj/effect/meteor/medium=4, /obj/effect/meteor/big=8, \ - /obj/effect/meteor/flaming=3, /obj/effect/meteor/irradiated=3) //for threatening meteor event - -/var/list/meteors_catastrophic = list(/obj/effect/meteor/medium=5, /obj/effect/meteor/big=75, \ - /obj/effect/meteor/flaming=10, /obj/effect/meteor/irradiated=10, /obj/effect/meteor/tunguska = 1) //for catastrophic meteor event - -/var/list/meteors_dust = list(/obj/effect/meteor/dust) //for space dust event - -/var/list/meteors_gore = list(/obj/effect/meteor/gore) //Meaty Gore - -/var/list/meteors_ops = list(/obj/effect/meteor/goreops) //Meaty Ops - - -/////////////////////////////// -//Meteor spawning global procs -/////////////////////////////// -/proc/spawn_meteors(var/number = 10, var/list/meteortypes) - for(var/i = 0; i < number; i++) - spawn_meteor(meteortypes) - -/proc/spawn_meteor(var/list/meteortypes) - var/turf/pickedstart - var/turf/pickedgoal - var/max_i = 10//number of tries to spawn meteor. - while(!istype(pickedstart, /turf/space)) - var/startSide = pick(cardinal) - pickedstart = spaceDebrisStartLoc(startSide, 1) - pickedgoal = spaceDebrisFinishLoc(startSide, 1) - max_i-- - if(max_i<=0) - return - var/Me = pickweight(meteortypes) - var/obj/effect/meteor/M = new Me(pickedstart) - M.dest = pickedgoal - M.z_original = level_name_to_num(MAIN_STATION) - spawn(0) - walk_towards(M, M.dest, 1) - return - -/proc/spaceDebrisStartLoc(startSide, Z) - var/starty - var/startx - switch(startSide) - if(NORTH) - starty = world.maxy-(TRANSITIONEDGE+1) - startx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1)) - if(EAST) - starty = rand((TRANSITIONEDGE+1),world.maxy-(TRANSITIONEDGE+1)) - startx = world.maxx-(TRANSITIONEDGE+1) - if(SOUTH) - starty = (TRANSITIONEDGE+1) - startx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1)) - if(WEST) - starty = rand((TRANSITIONEDGE+1), world.maxy-(TRANSITIONEDGE+1)) - startx = (TRANSITIONEDGE+1) - var/turf/T = locate(startx, starty, Z) - return T - -/proc/spaceDebrisFinishLoc(startSide, Z) - var/endy - var/endx - switch(startSide) - if(NORTH) - endy = TRANSITIONEDGE - endx = rand(TRANSITIONEDGE, world.maxx-TRANSITIONEDGE) - if(EAST) - endy = rand(TRANSITIONEDGE, world.maxy-TRANSITIONEDGE) - endx = TRANSITIONEDGE - if(SOUTH) - endy = world.maxy-TRANSITIONEDGE - endx = rand(TRANSITIONEDGE, world.maxx-TRANSITIONEDGE) - if(WEST) - endy = rand(TRANSITIONEDGE,world.maxy-TRANSITIONEDGE) - endx = world.maxx-TRANSITIONEDGE - var/turf/T = locate(endx, endy, Z) - return T - -/////////////////////// -//The meteor effect -////////////////////// - -/obj/effect/meteor - name = "the concept of meteor" - desc = "You should probably run instead of gawking at this." - icon = 'icons/obj/meteor.dmi' - icon_state = "small" - density = 1 - anchored = 1 - var/hits = 4 - var/hitpwr = 2 //Level of ex_act to be called on hit. - var/dest - pass_flags = PASSTABLE - var/heavy = 0 - var/meteorsound = 'sound/effects/meteorimpact.ogg' - var/z_original = 1 - - var/meteordrop = /obj/item/stack/ore/iron - var/dropamt = 2 - -/obj/effect/meteor/Move() - if(z != z_original || loc == dest) - qdel(src) - return - - . = ..() //process movement... - - if(.)//.. if did move, ram the turf we get in - var/turf/T = get_turf(loc) - ram_turf(T) - - if(prob(10) && !istype(T, /turf/space))//randomly takes a 'hit' from ramming - get_hit() - - return . - -/obj/effect/meteor/Destroy() - GLOB.meteor_list -= src - walk(src,0) //this cancels the walk_towards() proc - return ..() - -/obj/effect/meteor/New() - ..() - GLOB.meteor_list += src - SpinAnimation() - -/obj/effect/meteor/Bump(atom/A) - if(A) - ram_turf(get_turf(A)) - playsound(src.loc, meteorsound, 40, 1) - get_hit() - -/obj/effect/meteor/CanPass(atom/movable/mover, turf/target, height=0) - return istype(mover, /obj/effect/meteor) ? 1 : ..() - -/obj/effect/meteor/proc/ram_turf(var/turf/T) - //first bust whatever is in the turf - for(var/atom/A in T) - if(A != src) - A.ex_act(hitpwr) - - //then, ram the turf if it still exists - if(T) - T.ex_act(hitpwr) - -//process getting 'hit' by colliding with a dense object -//or randomly when ramming turfs -/obj/effect/meteor/proc/get_hit() - hits-- - if(hits <= 0) - make_debris() - meteor_effect(heavy) - qdel(src) - -/obj/effect/meteor/ex_act() - return - -/obj/effect/meteor/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/pickaxe)) - make_debris() - qdel(src) - return - return ..() - -/obj/effect/meteor/proc/make_debris() - for(var/throws = dropamt, throws > 0, throws--) - var/obj/item/O = new meteordrop(get_turf(src)) - O.throw_at(dest, 5, 10) - -/obj/effect/meteor/proc/meteor_effect(var/sound=1) - if(sound) - var/sound/meteor_sound = sound(meteorsound) - var/random_frequency = get_rand_frequency() - - for(var/P in GLOB.player_list) - var/mob/M = P - var/turf/T = get_turf(M) - if(!T || T.z != src.z) - continue - var/dist = get_dist(M.loc, src.loc) - if(prob(50)) - shake_camera(M, dist > 20 ? 3 : 5, dist > 20 ? 1 : 3) - M.playsound_local(src.loc, null, 50, 1, random_frequency, 10, S = meteor_sound) - -/////////////////////// -//Meteor types -/////////////////////// - -//Dust -/obj/effect/meteor/dust - name = "space dust" - icon_state = "dust" - pass_flags = PASSTABLE | PASSGRILLE - hits = 1 - hitpwr = 3 - meteorsound = 'sound/weapons/tap.ogg' - meteordrop = /obj/item/stack/ore/glass - -//Medium-sized -/obj/effect/meteor/medium - name = "meteor" - dropamt = 3 - -/obj/effect/meteor/medium/meteor_effect() - ..(heavy) - explosion(src.loc, 0, 1, 2, 3, 0) - -//Large-sized -/obj/effect/meteor/big - name = "large meteor" - icon_state = "large" - hits = 6 - heavy = 1 - dropamt = 4 - -/obj/effect/meteor/big/meteor_effect() - ..(heavy) - explosion(src.loc, 1, 2, 3, 4, 0) - -//Flaming meteor -/obj/effect/meteor/flaming - name = "flaming meteor" - icon_state = "flaming" - hits = 5 - heavy = 1 - meteorsound = 'sound/effects/bamf.ogg' - meteordrop = /obj/item/stack/ore/plasma - -/obj/effect/meteor/flaming/meteor_effect() - ..(heavy) - explosion(src.loc, 1, 2, 3, 4, 0, 0, flame_range = 5) - -//Radiation meteor -/obj/effect/meteor/irradiated - name = "glowing meteor" - icon_state = "glowing" - heavy = 1 - meteordrop = /obj/item/stack/ore/uranium - - -/obj/effect/meteor/irradiated/meteor_effect() - ..(heavy) - explosion(src.loc, 0, 0, 4, 3, 0) - new /obj/effect/decal/cleanable/greenglow(get_turf(src)) - for(var/mob/living/L in view(5, src)) - L.apply_effect(40, IRRADIATE) - -//Station buster Tunguska -/obj/effect/meteor/tunguska - name = "tunguska meteor" - icon_state = "flaming" - desc = "Your life briefly passes before your eyes the moment you lay them on this monstruosity." - hits = 30 - hitpwr = 1 - heavy = 1 - meteorsound = 'sound/effects/bamf.ogg' - meteordrop = /obj/item/stack/ore/plasma - -/obj/effect/meteor/tunguska/meteor_effect() - ..(heavy) - explosion(src.loc, 5, 10, 15, 20, 0) - -/obj/effect/meteor/tunguska/Bump() - ..() - if(prob(20)) - explosion(src.loc,2,4,6,8) - - -//Gore -/obj/effect/meteor/gore - name = "Oraganic Debris" - icon = 'icons/mob/human.dmi' - icon_state = "body_m_s" - hits = 1 - hitpwr = 0 - meteorsound = 'sound/effects/blobattack.ogg' - meteordrop = /obj/item/reagent_containers/food/snacks/meat - var/meteorgibs = /obj/effect/gibspawner/generic - -/obj/effect/meteor/gore/make_debris() - ..() - new meteorgibs(get_turf(src)) - - -/obj/effect/meteor/gore/ram_turf(turf/T) - if(!istype(T, /turf/space)) - new /obj/effect/decal/cleanable/blood(T) - -/obj/effect/meteor/gore/Bump(atom/A) - A.ex_act(hitpwr) - get_hit() - -//Meteor Ops -/obj/effect/meteor/goreops - name = "MeteorOps" - icon = 'icons/mob/animal.dmi' - icon_state = "syndicaterangedpsace" - hits = 10 - hitpwr = 1 - meteorsound = 'sound/effects/blobattack.ogg' - meteordrop = /obj/item/reagent_containers/food/snacks/meat - var/meteorgibs = /obj/effect/gibspawner/generic - -/obj/effect/meteor/goreops/make_debris() - ..() - new meteorgibs(get_turf(src)) - - -/obj/effect/meteor/goreops/ram_turf(turf/T) - if(!istype(T, /turf/space)) - new /obj/effect/decal/cleanable/blood(T) - -/obj/effect/meteor/goreops/Bump(atom/A) - A.ex_act(hitpwr) - get_hit() +//Meteors probability of spawning during a given wave +/var/list/meteors_normal = list(/obj/effect/meteor/dust=3, /obj/effect/meteor/medium=8, /obj/effect/meteor/big=3, \ + /obj/effect/meteor/flaming=1, /obj/effect/meteor/irradiated=3) //for normal meteor event + +/var/list/meteors_threatening = list(/obj/effect/meteor/medium=4, /obj/effect/meteor/big=8, \ + /obj/effect/meteor/flaming=3, /obj/effect/meteor/irradiated=3) //for threatening meteor event + +/var/list/meteors_catastrophic = list(/obj/effect/meteor/medium=5, /obj/effect/meteor/big=75, \ + /obj/effect/meteor/flaming=10, /obj/effect/meteor/irradiated=10, /obj/effect/meteor/tunguska = 1) //for catastrophic meteor event + +/var/list/meteors_dust = list(/obj/effect/meteor/dust) //for space dust event + +/var/list/meteors_gore = list(/obj/effect/meteor/gore) //Meaty Gore + +/var/list/meteors_ops = list(/obj/effect/meteor/goreops) //Meaty Ops + + +/////////////////////////////// +//Meteor spawning global procs +/////////////////////////////// +/proc/spawn_meteors(var/number = 10, var/list/meteortypes) + for(var/i = 0; i < number; i++) + spawn_meteor(meteortypes) + +/proc/spawn_meteor(var/list/meteortypes) + var/turf/pickedstart + var/turf/pickedgoal + var/max_i = 10//number of tries to spawn meteor. + while(!istype(pickedstart, /turf/space)) + var/startSide = pick(cardinal) + pickedstart = spaceDebrisStartLoc(startSide, 1) + pickedgoal = spaceDebrisFinishLoc(startSide, 1) + max_i-- + if(max_i<=0) + return + var/Me = pickweight(meteortypes) + var/obj/effect/meteor/M = new Me(pickedstart) + M.dest = pickedgoal + M.z_original = level_name_to_num(MAIN_STATION) + spawn(0) + walk_towards(M, M.dest, 1) + return + +/proc/spaceDebrisStartLoc(startSide, Z) + var/starty + var/startx + switch(startSide) + if(NORTH) + starty = world.maxy-(TRANSITIONEDGE+1) + startx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1)) + if(EAST) + starty = rand((TRANSITIONEDGE+1),world.maxy-(TRANSITIONEDGE+1)) + startx = world.maxx-(TRANSITIONEDGE+1) + if(SOUTH) + starty = (TRANSITIONEDGE+1) + startx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1)) + if(WEST) + starty = rand((TRANSITIONEDGE+1), world.maxy-(TRANSITIONEDGE+1)) + startx = (TRANSITIONEDGE+1) + var/turf/T = locate(startx, starty, Z) + return T + +/proc/spaceDebrisFinishLoc(startSide, Z) + var/endy + var/endx + switch(startSide) + if(NORTH) + endy = TRANSITIONEDGE + endx = rand(TRANSITIONEDGE, world.maxx-TRANSITIONEDGE) + if(EAST) + endy = rand(TRANSITIONEDGE, world.maxy-TRANSITIONEDGE) + endx = TRANSITIONEDGE + if(SOUTH) + endy = world.maxy-TRANSITIONEDGE + endx = rand(TRANSITIONEDGE, world.maxx-TRANSITIONEDGE) + if(WEST) + endy = rand(TRANSITIONEDGE,world.maxy-TRANSITIONEDGE) + endx = world.maxx-TRANSITIONEDGE + var/turf/T = locate(endx, endy, Z) + return T + +/////////////////////// +//The meteor effect +////////////////////// + +/obj/effect/meteor + name = "the concept of meteor" + desc = "You should probably run instead of gawking at this." + icon = 'icons/obj/meteor.dmi' + icon_state = "small" + density = 1 + anchored = 1 + var/hits = 4 + var/hitpwr = 2 //Level of ex_act to be called on hit. + var/dest + pass_flags = PASSTABLE + var/heavy = 0 + var/meteorsound = 'sound/effects/meteorimpact.ogg' + var/z_original = 1 + + var/meteordrop = /obj/item/stack/ore/iron + var/dropamt = 2 + +/obj/effect/meteor/Move() + if(z != z_original || loc == dest) + qdel(src) + return + + . = ..() //process movement... + + if(.)//.. if did move, ram the turf we get in + var/turf/T = get_turf(loc) + ram_turf(T) + + if(prob(10) && !istype(T, /turf/space))//randomly takes a 'hit' from ramming + get_hit() + + return . + +/obj/effect/meteor/Destroy() + GLOB.meteor_list -= src + walk(src,0) //this cancels the walk_towards() proc + return ..() + +/obj/effect/meteor/New() + ..() + GLOB.meteor_list += src + SpinAnimation() + +/obj/effect/meteor/Bump(atom/A) + if(A) + ram_turf(get_turf(A)) + playsound(src.loc, meteorsound, 40, 1) + get_hit() + +/obj/effect/meteor/CanPass(atom/movable/mover, turf/target, height=0) + return istype(mover, /obj/effect/meteor) ? 1 : ..() + +/obj/effect/meteor/proc/ram_turf(var/turf/T) + //first bust whatever is in the turf + for(var/atom/A in T) + if(A != src) + A.ex_act(hitpwr) + + //then, ram the turf if it still exists + if(T) + T.ex_act(hitpwr) + +//process getting 'hit' by colliding with a dense object +//or randomly when ramming turfs +/obj/effect/meteor/proc/get_hit() + hits-- + if(hits <= 0) + make_debris() + meteor_effect(heavy) + qdel(src) + +/obj/effect/meteor/ex_act() + return + +/obj/effect/meteor/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/pickaxe)) + make_debris() + qdel(src) + return + return ..() + +/obj/effect/meteor/proc/make_debris() + for(var/throws = dropamt, throws > 0, throws--) + var/obj/item/O = new meteordrop(get_turf(src)) + O.throw_at(dest, 5, 10) + +/obj/effect/meteor/proc/meteor_effect(var/sound=1) + if(sound) + var/sound/meteor_sound = sound(meteorsound) + var/random_frequency = get_rand_frequency() + + for(var/P in GLOB.player_list) + var/mob/M = P + var/turf/T = get_turf(M) + if(!T || T.z != src.z) + continue + var/dist = get_dist(M.loc, src.loc) + if(prob(50)) + shake_camera(M, dist > 20 ? 3 : 5, dist > 20 ? 1 : 3) + M.playsound_local(src.loc, null, 50, 1, random_frequency, 10, S = meteor_sound) + +/////////////////////// +//Meteor types +/////////////////////// + +//Dust +/obj/effect/meteor/dust + name = "space dust" + icon_state = "dust" + pass_flags = PASSTABLE | PASSGRILLE + hits = 1 + hitpwr = 3 + meteorsound = 'sound/weapons/tap.ogg' + meteordrop = /obj/item/stack/ore/glass + +//Medium-sized +/obj/effect/meteor/medium + name = "meteor" + dropamt = 3 + +/obj/effect/meteor/medium/meteor_effect() + ..(heavy) + explosion(src.loc, 0, 1, 2, 3, 0) + +//Large-sized +/obj/effect/meteor/big + name = "large meteor" + icon_state = "large" + hits = 6 + heavy = 1 + dropamt = 4 + +/obj/effect/meteor/big/meteor_effect() + ..(heavy) + explosion(src.loc, 1, 2, 3, 4, 0) + +//Flaming meteor +/obj/effect/meteor/flaming + name = "flaming meteor" + icon_state = "flaming" + hits = 5 + heavy = 1 + meteorsound = 'sound/effects/bamf.ogg' + meteordrop = /obj/item/stack/ore/plasma + +/obj/effect/meteor/flaming/meteor_effect() + ..(heavy) + explosion(src.loc, 1, 2, 3, 4, 0, 0, flame_range = 5) + +//Radiation meteor +/obj/effect/meteor/irradiated + name = "glowing meteor" + icon_state = "glowing" + heavy = 1 + meteordrop = /obj/item/stack/ore/uranium + + +/obj/effect/meteor/irradiated/meteor_effect() + ..(heavy) + explosion(src.loc, 0, 0, 4, 3, 0) + new /obj/effect/decal/cleanable/greenglow(get_turf(src)) + for(var/mob/living/L in view(5, src)) + L.apply_effect(40, IRRADIATE) + +//Station buster Tunguska +/obj/effect/meteor/tunguska + name = "tunguska meteor" + icon_state = "flaming" + desc = "Your life briefly passes before your eyes the moment you lay them on this monstruosity." + hits = 30 + hitpwr = 1 + heavy = 1 + meteorsound = 'sound/effects/bamf.ogg' + meteordrop = /obj/item/stack/ore/plasma + +/obj/effect/meteor/tunguska/meteor_effect() + ..(heavy) + explosion(src.loc, 5, 10, 15, 20, 0) + +/obj/effect/meteor/tunguska/Bump() + ..() + if(prob(20)) + explosion(src.loc,2,4,6,8) + + +//Gore +/obj/effect/meteor/gore + name = "Oraganic Debris" + icon = 'icons/mob/human.dmi' + icon_state = "body_m_s" + hits = 1 + hitpwr = 0 + meteorsound = 'sound/effects/blobattack.ogg' + meteordrop = /obj/item/reagent_containers/food/snacks/meat + var/meteorgibs = /obj/effect/gibspawner/generic + +/obj/effect/meteor/gore/make_debris() + ..() + new meteorgibs(get_turf(src)) + + +/obj/effect/meteor/gore/ram_turf(turf/T) + if(!istype(T, /turf/space)) + new /obj/effect/decal/cleanable/blood(T) + +/obj/effect/meteor/gore/Bump(atom/A) + A.ex_act(hitpwr) + get_hit() + +//Meteor Ops +/obj/effect/meteor/goreops + name = "MeteorOps" + icon = 'icons/mob/animal.dmi' + icon_state = "syndicaterangedpsace" + hits = 10 + hitpwr = 1 + meteorsound = 'sound/effects/blobattack.ogg' + meteordrop = /obj/item/reagent_containers/food/snacks/meat + var/meteorgibs = /obj/effect/gibspawner/generic + +/obj/effect/meteor/goreops/make_debris() + ..() + new meteorgibs(get_turf(src)) + + +/obj/effect/meteor/goreops/ram_turf(turf/T) + if(!istype(T, /turf/space)) + new /obj/effect/decal/cleanable/blood(T) + +/obj/effect/meteor/goreops/Bump(atom/A) + A.ex_act(hitpwr) + get_hit() diff --git a/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm b/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm index 5db63d3450d..3859d68accb 100644 --- a/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm +++ b/code/game/gamemodes/miniantags/abduction/abductee_objectives.dm @@ -144,4 +144,4 @@ explanation_text = "You are doomed to feel woefully incomplete forever... until you find your true love on this station. They're waiting for you!" /datum/objective/abductee/sixthsense - explanation_text = "You died back there and went to heaven... or is it hell? No one here seems to know they're dead. Convince them, and maybe you can escape this limbo." \ No newline at end of file + explanation_text = "You died back there and went to heaven... or is it hell? No one here seems to know they're dead. Convince them, and maybe you can escape this limbo." diff --git a/code/game/gamemodes/miniantags/abduction/abduction.dm b/code/game/gamemodes/miniantags/abduction/abduction.dm index bcbc86d991b..ea22ba9f54e 100644 --- a/code/game/gamemodes/miniantags/abduction/abduction.dm +++ b/code/game/gamemodes/miniantags/abduction/abduction.dm @@ -281,4 +281,4 @@ /datum/game_mode/proc/update_abductor_icons_removed(datum/mind/alien_mind) var/datum/atom_hud/antag/hud = huds[ANTAG_HUD_ABDUCTOR] hud.leave_hud(alien_mind.current) - set_antag_hud(alien_mind.current, null) \ No newline at end of file + set_antag_hud(alien_mind.current, null) diff --git a/code/game/gamemodes/miniantags/abduction/abduction_outfits.dm b/code/game/gamemodes/miniantags/abduction/abduction_outfits.dm index c90aaac19d8..6478a997b76 100644 --- a/code/game/gamemodes/miniantags/abduction/abduction_outfits.dm +++ b/code/game/gamemodes/miniantags/abduction/abduction_outfits.dm @@ -59,4 +59,4 @@ ..() if(!visualsOnly) var/obj/item/implant/abductor/beamplant = new /obj/item/implant/abductor(H) - beamplant.implant(H) \ No newline at end of file + beamplant.implant(H) diff --git a/code/game/gamemodes/miniantags/abduction/machinery/console.dm b/code/game/gamemodes/miniantags/abduction/machinery/console.dm index b162103c88b..17ebcbdd270 100644 --- a/code/game/gamemodes/miniantags/abduction/machinery/console.dm +++ b/code/game/gamemodes/miniantags/abduction/machinery/console.dm @@ -221,4 +221,4 @@ else new item(src.loc) else - atom_say("Insufficent data!") \ No newline at end of file + atom_say("Insufficent data!") diff --git a/code/game/gamemodes/miniantags/abduction/machinery/dispenser.dm b/code/game/gamemodes/miniantags/abduction/machinery/dispenser.dm index 1a377667650..5964c6f379b 100644 --- a/code/game/gamemodes/miniantags/abduction/machinery/dispenser.dm +++ b/code/game/gamemodes/miniantags/abduction/machinery/dispenser.dm @@ -83,4 +83,4 @@ if(amounts[count]>0) amounts[count]-- var/T = gland_types[count] - new T(get_turf(src)) \ No newline at end of file + new T(get_turf(src)) diff --git a/code/game/gamemodes/miniantags/abduction/machinery/pad.dm b/code/game/gamemodes/miniantags/abduction/machinery/pad.dm index 697d173716a..85c120186e4 100644 --- a/code/game/gamemodes/miniantags/abduction/machinery/pad.dm +++ b/code/game/gamemodes/miniantags/abduction/machinery/pad.dm @@ -48,4 +48,4 @@ /obj/effect/temp_visual/teleport_abductor/New() do_sparks(10, 0, loc) - ..() \ No newline at end of file + ..() diff --git a/code/game/gamemodes/miniantags/borer/borer_html.dm b/code/game/gamemodes/miniantags/borer/borer_html.dm index 415438884e9..8b6e19e6226 100644 --- a/code/game/gamemodes/miniantags/borer/borer_html.dm +++ b/code/game/gamemodes/miniantags/borer/borer_html.dm @@ -66,4 +66,4 @@
    [content]
    "} - return html \ No newline at end of file + return html diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm index e764f97a46a..defa0de633d 100644 --- a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm +++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm @@ -689,4 +689,4 @@ if(message) for(var/mob/M in GLOB.mob_list) if(isswarmer(M) || (M in GLOB.dead_mob_list)) - to_chat(M, "Swarm communication - [src] states: [message]") \ No newline at end of file + to_chat(M, "Swarm communication - [src] states: [message]") diff --git a/code/game/gamemodes/miniantags/guardian/types/assassin.dm b/code/game/gamemodes/miniantags/guardian/types/assassin.dm index 4b17e597aa5..a12fce36e82 100644 --- a/code/game/gamemodes/miniantags/guardian/types/assassin.dm +++ b/code/game/gamemodes/miniantags/guardian/types/assassin.dm @@ -90,4 +90,4 @@ clear_alert("instealth") instealthalert = null clear_alert("canstealth") - canstealthalert = null \ No newline at end of file + canstealthalert = null diff --git a/code/game/gamemodes/miniantags/guardian/types/charger.dm b/code/game/gamemodes/miniantags/guardian/types/charger.dm index 17ed569f256..3ea6dbe5f30 100644 --- a/code/game/gamemodes/miniantags/guardian/types/charger.dm +++ b/code/game/gamemodes/miniantags/guardian/types/charger.dm @@ -69,4 +69,4 @@ shake_camera(L, 4, 3) shake_camera(src, 2, 3) - charging = 0 \ No newline at end of file + charging = 0 diff --git a/code/game/gamemodes/miniantags/guardian/types/lightning.dm b/code/game/gamemodes/miniantags/guardian/types/lightning.dm index 13b9b67f0a9..bf84e199bab 100644 --- a/code/game/gamemodes/miniantags/guardian/types/lightning.dm +++ b/code/game/gamemodes/miniantags/guardian/types/lightning.dm @@ -105,4 +105,4 @@ "You hear a heavy electrical crack." \ ) L.adjustFireLoss(1.2) //adds up very rapidly - . = 1 \ No newline at end of file + . = 1 diff --git a/code/game/gamemodes/miniantags/guardian/types/protector.dm b/code/game/gamemodes/miniantags/guardian/types/protector.dm index 5fb31b8ba15..534b7e16934 100644 --- a/code/game/gamemodes/miniantags/guardian/types/protector.dm +++ b/code/game/gamemodes/miniantags/guardian/types/protector.dm @@ -55,4 +55,4 @@ summoner.visible_message("[summoner] jumps back to [summoner.p_their()] protector.") new /obj/effect/temp_visual/guardian/phase/out(get_turf(summoner)) summoner.forceMove(get_turf(src)) - new /obj/effect/temp_visual/guardian/phase(get_turf(summoner))//Protector \ No newline at end of file + new /obj/effect/temp_visual/guardian/phase(get_turf(summoner))//Protector diff --git a/code/game/gamemodes/miniantags/guardian/types/standard.dm b/code/game/gamemodes/miniantags/guardian/types/standard.dm index dc85a48a480..e6d95ffb92c 100644 --- a/code/game/gamemodes/miniantags/guardian/types/standard.dm +++ b/code/game/gamemodes/miniantags/guardian/types/standard.dm @@ -45,4 +45,4 @@ playstyle_string = "As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls." environment_smash = 2 battlecry = "URK" - adminseal = TRUE \ No newline at end of file + adminseal = TRUE diff --git a/code/game/gamemodes/miniantags/morph/morph_event.dm b/code/game/gamemodes/miniantags/morph/morph_event.dm index e80374e2dca..28183e7d22e 100644 --- a/code/game/gamemodes/miniantags/morph/morph_event.dm +++ b/code/game/gamemodes/miniantags/morph/morph_event.dm @@ -29,4 +29,4 @@ return 1 /datum/event/spawn_morph/start() - get_morph() \ No newline at end of file + get_morph() diff --git a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm index 437e7637a77..1f902a51806 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant_spawn_event.dm @@ -52,4 +52,4 @@ /datum/event/revenant/start() - get_revenant() \ No newline at end of file + get_revenant() diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm index 578cc8ac3a7..bdc5f718ac6 100644 --- a/code/game/gamemodes/nuclear/nuclear.dm +++ b/code/game/gamemodes/nuclear/nuclear.dm @@ -1,536 +1,536 @@ -#define NUKESCALINGMODIFIER 1.2 - -/datum/game_mode - var/list/datum/mind/syndicates = list() - -proc/issyndicate(mob/living/M as mob) - return istype(M) && M.mind && SSticker && SSticker.mode && (M.mind in SSticker.mode.syndicates) - -/datum/game_mode/nuclear - name = "nuclear emergency" - config_tag = "nuclear" - required_players = 30 // 30 players - 5 players to be the nuke ops = 25 players remaining - required_enemies = 5 - recommended_enemies = 5 - - var/const/agents_possible = 5 //If we ever need more syndicate agents. - - var/nukes_left = 1 //Call 3714-PRAY right now and order more nukes! Limited offer! - var/nuke_off_station = 0 //Used for tracking if the syndies actually haul the nuke to the station - var/syndies_didnt_escape = 0 //Used for tracking if the syndies got the shuttle off of the z-level - var/total_tc = 0 //Total amount of telecrystals shared between nuke ops - -/datum/game_mode/nuclear/announce() - to_chat(world, "The current game mode is - Nuclear Emergency!") - to_chat(world, "A [syndicate_name()] Strike Force is approaching [station_name()]!") - to_chat(world, "A nuclear explosive was being transported by Nanotrasen to a military base. The transport ship mysteriously lost contact with Space Traffic Control (STC). About that time a strange disk was discovered around [station_name()]. It was identified by Nanotrasen as a nuclear authentication disk and now Syndicate Operatives have arrived to retake the disk and detonate SS13! There are most likely Syndicate starships are in the vicinity, so take care not to lose the disk!\nSyndicate: Reclaim the disk and detonate the nuclear bomb anywhere on SS13.\nPersonnel: Hold the disk and escape with the disk on the shuttle!") - -/datum/game_mode/nuclear/can_start()//This could be better, will likely have to recode it later - if(!..()) - return 0 - - var/list/possible_syndicates = get_players_for_role(ROLE_OPERATIVE) - var/agent_number = 0 - - if(possible_syndicates.len < 1) - return 0 - - if(LAZYLEN(possible_syndicates) > agents_possible) - agent_number = agents_possible - else - agent_number = possible_syndicates.len - - var/n_players = num_players() - if(agent_number > n_players) - agent_number = n_players/2 - - while(agent_number > 0) - var/datum/mind/new_syndicate = pick(possible_syndicates) - syndicates += new_syndicate - possible_syndicates -= new_syndicate //So it doesn't pick the same guy each time. - agent_number-- - - for(var/datum/mind/synd_mind in syndicates) - synd_mind.assigned_role = SPECIAL_ROLE_NUKEOPS //So they aren't chosen for other jobs. - synd_mind.special_role = SPECIAL_ROLE_NUKEOPS - synd_mind.offstation_role = TRUE - return 1 - - -/datum/game_mode/nuclear/pre_setup() - ..() - return 1 - -/datum/game_mode/proc/remove_operative(datum/mind/operative_mind) - if(operative_mind in syndicates) - SSticker.mode.syndicates -= operative_mind - operative_mind.special_role = null - for(var/datum/objective/nuclear/O in operative_mind.objectives) - operative_mind.objectives -= O - operative_mind.current.create_attack_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 - to_chat(operative_mind.current, "You have been brainwashed! You are no longer a Syndicate operative.") - SSticker.mode.update_synd_icons_removed(operative_mind) - -//////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////// - -/datum/game_mode/proc/update_synd_icons_added(datum/mind/synd_mind) - var/datum/atom_hud/antag/opshud = huds[ANTAG_HUD_OPS] - opshud.join_hud(synd_mind.current) - set_antag_hud(synd_mind.current, "hudoperative") - -/datum/game_mode/proc/update_synd_icons_removed(datum/mind/synd_mind) - var/datum/atom_hud/antag/opshud = huds[ANTAG_HUD_OPS] - opshud.leave_hud(synd_mind.current) - set_antag_hud(synd_mind.current, null) - -//////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////// - -/datum/game_mode/nuclear/post_setup() - - var/list/turf/synd_spawn = list() - - for(var/obj/effect/landmark/A in GLOB.landmarks_list) - if(A.name == "Syndicate-Spawn") - synd_spawn += get_turf(A) - qdel(A) - continue - - var/obj/effect/landmark/nuke_spawn = locate("landmark*Nuclear-Bomb") - - var/nuke_code = "[rand(10000, 99999)]" - var/leader_selected = 0 - var/agent_number = 1 - var/spawnpos = 1 - - for(var/datum/mind/synd_mind in syndicates) - if(spawnpos > synd_spawn.len) - spawnpos = 2 - synd_mind.current.loc = synd_spawn[spawnpos] - - forge_syndicate_objectives(synd_mind) - create_syndicate(synd_mind) - greet_syndicate(synd_mind) - equip_syndicate(synd_mind.current) - - if(!leader_selected) - prepare_syndicate_leader(synd_mind, nuke_code) - leader_selected = 1 - else - synd_mind.current.real_name = "[syndicate_name()] Operative #[agent_number]" - update_syndicate_id(synd_mind, FALSE) - - agent_number++ - spawnpos++ - update_synd_icons_added(synd_mind) - - scale_telecrystals() - share_telecrystals() - if(nuke_spawn && synd_spawn.len > 0) - var/obj/machinery/nuclearbomb/syndicate/the_bomb = new /obj/machinery/nuclearbomb/syndicate(nuke_spawn.loc) - the_bomb.r_code = nuke_code - - return ..() - -/datum/game_mode/nuclear/proc/scale_telecrystals() - var/danger - danger = GLOB.player_list.len - while(!IsMultiple(++danger, 10)) //Increments danger up to the nearest multiple of ten - - total_tc += danger * NUKESCALINGMODIFIER - -/datum/game_mode/nuclear/proc/share_telecrystals() - var/player_tc - var/remainder - - player_tc = round(total_tc / GLOB.nuclear_uplink_list.len) //round to get an integer and not floating point - remainder = total_tc % GLOB.nuclear_uplink_list.len - - for(var/obj/item/radio/uplink/nuclear/U in GLOB.nuclear_uplink_list) - U.hidden_uplink.uses += player_tc - while(remainder > 0) - for(var/obj/item/radio/uplink/nuclear/U in GLOB.nuclear_uplink_list) - if(remainder <= 0) - break - U.hidden_uplink.uses++ - remainder-- - -/datum/game_mode/proc/create_syndicate(datum/mind/synd_mind) // So we don't have inferior species as ops - randomize a human - var/mob/living/carbon/human/M = synd_mind.current - - M.set_species(/datum/species/human, TRUE) - M.dna.ready_dna(M) // Quadriplegic Nuke Ops won't be participating in the paralympics - M.dna.species.create_organs(M) - M.cleanSE() //No fat/blind/colourblind/epileptic/whatever ops. - M.overeatduration = 0 - M.flavor_text = null - - var/obj/item/organ/external/head/head_organ = M.get_organ("head") - var/hair_c = pick("#8B4513","#000000","#FF4500","#FFD700") // Brown, black, red, blonde - var/eye_c = pick("#000000","#8B4513","1E90FF") // Black, brown, blue - var/skin_tone = pick(-50, -30, -10, 0, 0, 0, 10) // Caucasian/black - head_organ.facial_colour = hair_c - head_organ.sec_facial_colour = hair_c - head_organ.hair_colour = hair_c - head_organ.sec_hair_colour = hair_c - M.change_eye_color(eye_c) - M.s_tone = skin_tone - head_organ.h_style = random_hair_style(M.gender, head_organ.dna.species.name) - head_organ.f_style = random_facial_hair_style(M.gender, head_organ.dna.species.name) - M.body_accessory = null - M.regenerate_icons() - M.update_body() - -/datum/game_mode/proc/prepare_syndicate_leader(var/datum/mind/synd_mind, var/nuke_code) - var/leader_title = pick("Czar", "Boss", "Commander", "Chief", "Kingpin", "Director", "Overlord") - synd_mind.current.real_name = "[syndicate_name()] Team [leader_title]" - to_chat(synd_mind.current, "You are the Syndicate leader for this mission. You are responsible for the distribution of telecrystals and your ID is the only one who can open the launch bay doors.") - to_chat(synd_mind.current, "If you feel you are not up to this task, give your ID to another operative.") - to_chat(synd_mind.current, "In your hand you will find a special item capable of triggering a greater challenge for your team. Examine it carefully and consult with your fellow operatives before activating it.") - - var/obj/item/nuclear_challenge/challenge = new /obj/item/nuclear_challenge - synd_mind.current.equip_to_slot_or_del(challenge, slot_r_hand) - - update_syndicate_id(synd_mind, leader_title, TRUE) - - if(nuke_code) - synd_mind.store_memory("Syndicate Nuclear Bomb Code: [nuke_code]", 0, 0) - to_chat(synd_mind.current, "The nuclear authorization code is: [nuke_code]") - var/obj/item/paper/P = new - P.info = "The nuclear authorization code is: [nuke_code]" - P.name = "nuclear bomb code" - var/obj/item/stamp/syndicate/stamp = new - P.stamp(stamp) - qdel(stamp) - - if(SSticker.mode.config_tag=="nuclear") - P.loc = synd_mind.current.loc - else - var/mob/living/carbon/human/H = synd_mind.current - P.loc = H.loc - H.equip_to_slot_or_del(P, slot_r_store, 0) - H.update_icons() - - else - nuke_code = "code will be provided later" - -/datum/game_mode/proc/update_syndicate_id(var/datum/mind/synd_mind, is_leader = FALSE) - var/list/found_ids = synd_mind.current.search_contents_for(/obj/item/card/id) - - if(LAZYLEN(found_ids)) - for(var/obj/item/card/id/ID in found_ids) - ID.name = "[synd_mind.current.real_name] ID card" - ID.registered_name = synd_mind.current.real_name - if(is_leader) - ID.access += ACCESS_SYNDICATE_LEADER - else - message_admins("Warning: Operative [key_name_admin(synd_mind.current)] spawned without an ID card!") - -/datum/game_mode/proc/forge_syndicate_objectives(var/datum/mind/syndicate) - var/datum/objective/nuclear/syndobj = new - syndobj.owner = syndicate - syndicate.objectives += syndobj - - -/datum/game_mode/proc/greet_syndicate(var/datum/mind/syndicate, var/you_are=1) - SEND_SOUND(syndicate.current, 'sound/ambience/antag/ops.ogg') - if(you_are) - to_chat(syndicate.current, "You are a [syndicate_name()] agent!") - var/obj_count = 1 - for(var/datum/objective/objective in syndicate.objectives) - to_chat(syndicate.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ - return - - -/datum/game_mode/proc/random_radio_frequency() - return 1337 // WHY??? -- Doohl - - -/datum/game_mode/proc/equip_syndicate(mob/living/carbon/human/synd_mob, uplink_uses = 20) - var/radio_freq = SYND_FREQ - - var/obj/item/radio/R = new /obj/item/radio/headset/syndicate/alt(synd_mob) - R.set_frequency(radio_freq) - synd_mob.equip_to_slot_or_del(R, slot_l_ear) - - synd_mob.equip_to_slot_or_del(new /obj/item/clothing/under/syndicate(synd_mob), slot_w_uniform) - synd_mob.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(synd_mob), slot_shoes) - synd_mob.equip_or_collect(new /obj/item/clothing/gloves/combat(synd_mob), slot_gloves) - synd_mob.equip_to_slot_or_del(new /obj/item/card/id/syndicate(synd_mob), slot_wear_id) - synd_mob.equip_to_slot_or_del(new /obj/item/storage/backpack(synd_mob), slot_back) - synd_mob.equip_to_slot_or_del(new /obj/item/gun/projectile/automatic/pistol(synd_mob), slot_belt) - synd_mob.equip_to_slot_or_del(new /obj/item/storage/box/survival_syndi(synd_mob.back), slot_in_backpack) - synd_mob.equip_to_slot_or_del(new /obj/item/pinpointer/nukeop(synd_mob), slot_wear_pda) - var/obj/item/radio/uplink/nuclear/U = new /obj/item/radio/uplink/nuclear(synd_mob) - U.hidden_uplink.uplink_owner="[synd_mob.key]" - U.hidden_uplink.uses = uplink_uses - synd_mob.equip_to_slot_or_del(U, slot_in_backpack) - - if(synd_mob.dna.species) - - /* - Incase anyone ever gets the burning desire to have nukeops with randomized apperances. -- Dave - synd_mob.gender = pick(MALE, FEMALE) // Randomized appearances for the nukeops. - var/datum/preferences/pref = new() - A.randomize_appearance_for(synd_mob) - */ - - var/race = synd_mob.dna.species.name - - switch(race) - if("Vox" || "Vox Armalis") - synd_mob.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/syndicate(synd_mob), slot_wear_mask) - synd_mob.equip_to_slot_or_del(new /obj/item/tank/emergency_oxygen/vox(synd_mob), slot_l_hand) - synd_mob.internal = synd_mob.l_hand - synd_mob.update_action_buttons_icon() - - if("Plasmaman") - synd_mob.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/syndicate(synd_mob), slot_wear_mask) - synd_mob.equip_or_collect(new /obj/item/tank/plasma/plasmaman(synd_mob), slot_s_store) - synd_mob.equip_or_collect(new /obj/item/extinguisher_refill(synd_mob), slot_in_backpack) - synd_mob.equip_or_collect(new /obj/item/extinguisher_refill(synd_mob), slot_in_backpack) - synd_mob.internal = synd_mob.get_item_by_slot(slot_s_store) - synd_mob.update_action_buttons_icon() - - synd_mob.rejuvenate() //fix any damage taken by naked vox/plasmamen/etc while round setups - var/obj/item/implant/explosive/E = new/obj/item/implant/explosive(synd_mob) - E.implant(synd_mob) - synd_mob.faction |= "syndicate" - synd_mob.update_icons() - return 1 - -/datum/game_mode/nuclear/check_win() - if(nukes_left == 0) - return 1 - return ..() - - -/datum/game_mode/proc/is_operatives_are_dead() - for(var/datum/mind/operative_mind in syndicates) - if(!istype(operative_mind.current,/mob/living/carbon/human)) - if(operative_mind.current) - if(operative_mind.current.stat!=2) - return 0 - return 1 - - -/datum/game_mode/nuclear/declare_completion() - var/disk_rescued = 1 - for(var/obj/item/disk/nuclear/D in GLOB.poi_list) - if(!D.onCentcom()) - disk_rescued = 0 - break - var/crew_evacuated = (SSshuttle.emergency.mode >= SHUTTLE_ESCAPE) - //var/operatives_are_dead = is_operatives_are_dead() - - - //nukes_left - //station_was_nuked - //derp //Used for tracking if the syndies actually haul the nuke to the station //no - //herp //Used for tracking if the syndies got the shuttle off of the z-level //NO, DON'T FUCKING NAME VARS LIKE THIS - - if(!disk_rescued && station_was_nuked && !syndies_didnt_escape) - feedback_set_details("round_end_result","nuclear win - syndicate nuke") - to_chat(world, "Syndicate Major Victory!") - to_chat(world, "[syndicate_name()] operatives have destroyed [station_name()]!") - - else if(!disk_rescued && station_was_nuked && syndies_didnt_escape) - feedback_set_details("round_end_result","nuclear halfwin - syndicate nuke - did not evacuate in time") - to_chat(world, "Total Annihilation") - to_chat(world, "[syndicate_name()] operatives destroyed [station_name()] but did not leave the area in time and got caught in the explosion. Next time, don't lose the disk!") - - else if(!disk_rescued && !station_was_nuked && nuke_off_station && !syndies_didnt_escape) - feedback_set_details("round_end_result","nuclear halfwin - blew wrong station") - to_chat(world, "Crew Minor Victory") - to_chat(world, "[syndicate_name()] operatives secured the authentication disk but blew up something that wasn't [station_name()]. Next time, don't lose the disk!") - - else if(!disk_rescued && !station_was_nuked && nuke_off_station && syndies_didnt_escape) - feedback_set_details("round_end_result","nuclear halfwin - blew wrong station - did not evacuate in time") - to_chat(world, "[syndicate_name()] operatives have earned Darwin Award!") - to_chat(world, "[syndicate_name()] operatives blew up something that wasn't [station_name()] and got caught in the explosion. Next time, don't lose the disk!") - - else if(disk_rescued && is_operatives_are_dead()) - feedback_set_details("round_end_result","nuclear loss - evacuation - disk secured - syndi team dead") - to_chat(world, "Crew Major Victory!") - to_chat(world, "The Research Staff has saved the disc and killed the [syndicate_name()] Operatives") - - else if(disk_rescued) - feedback_set_details("round_end_result","nuclear loss - evacuation - disk secured") - to_chat(world, "Crew Major Victory") - to_chat(world, "The Research Staff has saved the disc and stopped the [syndicate_name()] Operatives!") - - else if(!disk_rescued && is_operatives_are_dead()) - feedback_set_details("round_end_result","nuclear loss - evacuation - disk not secured") - to_chat(world, "Syndicate Minor Victory!") - to_chat(world, "The Research Staff failed to secure the authentication disk but did manage to kill most of the [syndicate_name()] Operatives!") - - else if(!disk_rescued && crew_evacuated) - feedback_set_details("round_end_result","nuclear halfwin - detonation averted") - to_chat(world, "Syndicate Minor Victory!") - to_chat(world, "[syndicate_name()] operatives recovered the abandoned authentication disk but detonation of [station_name()] was averted. Next time, don't lose the disk!") - - else if(!disk_rescued && !crew_evacuated) - feedback_set_details("round_end_result","nuclear halfwin - interrupted") - to_chat(world, "Neutral Victory") - to_chat(world, "Round was mysteriously interrupted!") - ..() - return - - -/datum/game_mode/proc/auto_declare_completion_nuclear() - if(syndicates.len || GAMEMODE_IS_NUCLEAR) - var/text = "
    The syndicate operatives were:" - - var/purchases = "" - var/TC_uses = 0 - - for(var/datum/mind/syndicate in syndicates) - - text += "
    [syndicate.key] was [syndicate.name] (" - if(syndicate.current) - if(syndicate.current.stat == DEAD) - text += "died" - else - text += "survived" - if(syndicate.current.real_name != syndicate.name) - text += " as [syndicate.current.real_name]" - else - text += "body destroyed" - text += ")" - for(var/obj/item/uplink/H in world_uplinks) - if(H && H.uplink_owner && H.uplink_owner==syndicate.key) - TC_uses += H.used_TC - purchases += H.purchase_log - - text += "
    " - - text += "(Syndicates used [TC_uses] TC) [purchases]" - - if(TC_uses==0 && station_was_nuked && !is_operatives_are_dead()) - text += "" - - to_chat(world, text) - return 1 - -/proc/nukelastname(var/mob/M as mob) //--All praise goes to NEO|Phyte, all blame goes to DH, and it was Cindi-Kate's idea. Also praise Urist for copypasta ho. - var/randomname = pick(GLOB.last_names) - var/newname = sanitize(copytext(input(M,"You are the nuke operative [pick("Czar", "Boss", "Commander", "Chief", "Kingpin", "Director", "Overlord")]. Please choose a last name for your family.", "Name change",randomname),1,MAX_NAME_LEN)) - - if(!newname) - newname = randomname - - else - if(newname == "Unknown" || newname == "floor" || newname == "wall" || newname == "rwall" || newname == "_") - to_chat(M, "That name is reserved.") - return nukelastname(M) - - return newname - - -/datum/game_mode/nuclear/set_scoreboard_gvars() - var/foecount = 0 - for(var/datum/mind/M in SSticker.mode.syndicates) - foecount++ - if(!M || !M.current) - score_opkilled++ - continue - - if(M.current.stat == DEAD) - score_opkilled++ - - else if(M.current.restrained()) - score_arrested++ - - if(foecount == score_arrested) - score_allarrested = 1 - - for(var/obj/machinery/nuclearbomb/nuke in world) - if(nuke.r_code == "Nope") continue - var/turf/T = get_turf(nuke) - var/area/A = T.loc - - var/list/thousand_penalty = list(/area/wizard_station, /area/solar, /area) - var/list/fiftythousand_penalty = list(/area/security/main, /area/security/brig, /area/security/armoury, /area/security/checkpoint2) - - if(is_type_in_list(A, thousand_penalty)) - score_nuked_penalty = 1000 - - else if(is_type_in_list(A, fiftythousand_penalty)) - score_nuked_penalty = 50000 - - else if(istype(A, /area/engine)) - score_nuked_penalty = 100000 - - else - score_nuked_penalty = 10000 - - break - - var/killpoints = score_opkilled * 250 - var/arrestpoints = score_arrested * 1000 - score_crewscore += killpoints - score_crewscore += arrestpoints - if(score_nuked) - score_crewscore -= score_nuked_penalty - - - -/datum/game_mode/nuclear/get_scoreboard_stats() - var/foecount = 0 - var/crewcount = 0 - - var/diskdat = "" - var/bombdat = null - - for(var/datum/mind/M in SSticker.mode.syndicates) - foecount++ - - for(var/mob/living/C in world) - if(ishuman(C) || isAI(C) || isrobot(C)) - if(C.stat == 2) continue - if(!C.client) continue - crewcount++ - - var/obj/item/disk/nuclear/N = locate() in world - if(istype(N)) - var/atom/disk_loc = N.loc - while(!isturf(disk_loc)) - if(ismob(disk_loc)) - var/mob/M = disk_loc - diskdat += "Carried by [M.real_name] " - if(isobj(disk_loc)) - var/obj/O = disk_loc - diskdat += "in \a [O]" - disk_loc = disk_loc.loc - diskdat += "in [disk_loc.loc]" - - - if(!diskdat) - diskdat = "WARNING: Nuked_penalty could not be found, look at [__FILE__], [__LINE__]." - - var/dat = "" - dat += "Mode Statistics
    " - - dat += "Number of Operatives: [foecount]
    " - dat += "Number of Surviving Crew: [crewcount]
    " - - dat += "Final Location of Nuke: [bombdat]
    " - dat += "Final Location of Disk: [diskdat]
    " - - dat += "
    " - - dat += "Operatives Arrested: [score_arrested] ([score_arrested * 1000] Points)
    " - dat += "All Operatives Arrested: [score_allarrested ? "Yes" : "No"] (Score tripled)
    " - - dat += "Operatives Killed: [score_opkilled] ([score_opkilled * 1000] Points)
    " - dat += "Station Destroyed: [score_nuked ? "Yes" : "No"] (-[score_nuked_penalty] Points)
    " - dat += "
    " - - return dat - -#undef NUKESCALINGMODIFIER +#define NUKESCALINGMODIFIER 1.2 + +/datum/game_mode + var/list/datum/mind/syndicates = list() + +proc/issyndicate(mob/living/M as mob) + return istype(M) && M.mind && SSticker && SSticker.mode && (M.mind in SSticker.mode.syndicates) + +/datum/game_mode/nuclear + name = "nuclear emergency" + config_tag = "nuclear" + required_players = 30 // 30 players - 5 players to be the nuke ops = 25 players remaining + required_enemies = 5 + recommended_enemies = 5 + + var/const/agents_possible = 5 //If we ever need more syndicate agents. + + var/nukes_left = 1 //Call 3714-PRAY right now and order more nukes! Limited offer! + var/nuke_off_station = 0 //Used for tracking if the syndies actually haul the nuke to the station + var/syndies_didnt_escape = 0 //Used for tracking if the syndies got the shuttle off of the z-level + var/total_tc = 0 //Total amount of telecrystals shared between nuke ops + +/datum/game_mode/nuclear/announce() + to_chat(world, "The current game mode is - Nuclear Emergency!") + to_chat(world, "A [syndicate_name()] Strike Force is approaching [station_name()]!") + to_chat(world, "A nuclear explosive was being transported by Nanotrasen to a military base. The transport ship mysteriously lost contact with Space Traffic Control (STC). About that time a strange disk was discovered around [station_name()]. It was identified by Nanotrasen as a nuclear authentication disk and now Syndicate Operatives have arrived to retake the disk and detonate SS13! There are most likely Syndicate starships are in the vicinity, so take care not to lose the disk!\nSyndicate: Reclaim the disk and detonate the nuclear bomb anywhere on SS13.\nPersonnel: Hold the disk and escape with the disk on the shuttle!") + +/datum/game_mode/nuclear/can_start()//This could be better, will likely have to recode it later + if(!..()) + return 0 + + var/list/possible_syndicates = get_players_for_role(ROLE_OPERATIVE) + var/agent_number = 0 + + if(possible_syndicates.len < 1) + return 0 + + if(LAZYLEN(possible_syndicates) > agents_possible) + agent_number = agents_possible + else + agent_number = possible_syndicates.len + + var/n_players = num_players() + if(agent_number > n_players) + agent_number = n_players/2 + + while(agent_number > 0) + var/datum/mind/new_syndicate = pick(possible_syndicates) + syndicates += new_syndicate + possible_syndicates -= new_syndicate //So it doesn't pick the same guy each time. + agent_number-- + + for(var/datum/mind/synd_mind in syndicates) + synd_mind.assigned_role = SPECIAL_ROLE_NUKEOPS //So they aren't chosen for other jobs. + synd_mind.special_role = SPECIAL_ROLE_NUKEOPS + synd_mind.offstation_role = TRUE + return 1 + + +/datum/game_mode/nuclear/pre_setup() + ..() + return 1 + +/datum/game_mode/proc/remove_operative(datum/mind/operative_mind) + if(operative_mind in syndicates) + SSticker.mode.syndicates -= operative_mind + operative_mind.special_role = null + for(var/datum/objective/nuclear/O in operative_mind.objectives) + operative_mind.objectives -= O + operative_mind.current.create_attack_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 + to_chat(operative_mind.current, "You have been brainwashed! You are no longer a Syndicate operative.") + SSticker.mode.update_synd_icons_removed(operative_mind) + +//////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// + +/datum/game_mode/proc/update_synd_icons_added(datum/mind/synd_mind) + var/datum/atom_hud/antag/opshud = huds[ANTAG_HUD_OPS] + opshud.join_hud(synd_mind.current) + set_antag_hud(synd_mind.current, "hudoperative") + +/datum/game_mode/proc/update_synd_icons_removed(datum/mind/synd_mind) + var/datum/atom_hud/antag/opshud = huds[ANTAG_HUD_OPS] + opshud.leave_hud(synd_mind.current) + set_antag_hud(synd_mind.current, null) + +//////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// + +/datum/game_mode/nuclear/post_setup() + + var/list/turf/synd_spawn = list() + + for(var/obj/effect/landmark/A in GLOB.landmarks_list) + if(A.name == "Syndicate-Spawn") + synd_spawn += get_turf(A) + qdel(A) + continue + + var/obj/effect/landmark/nuke_spawn = locate("landmark*Nuclear-Bomb") + + var/nuke_code = "[rand(10000, 99999)]" + var/leader_selected = 0 + var/agent_number = 1 + var/spawnpos = 1 + + for(var/datum/mind/synd_mind in syndicates) + if(spawnpos > synd_spawn.len) + spawnpos = 2 + synd_mind.current.loc = synd_spawn[spawnpos] + + forge_syndicate_objectives(synd_mind) + create_syndicate(synd_mind) + greet_syndicate(synd_mind) + equip_syndicate(synd_mind.current) + + if(!leader_selected) + prepare_syndicate_leader(synd_mind, nuke_code) + leader_selected = 1 + else + synd_mind.current.real_name = "[syndicate_name()] Operative #[agent_number]" + update_syndicate_id(synd_mind, FALSE) + + agent_number++ + spawnpos++ + update_synd_icons_added(synd_mind) + + scale_telecrystals() + share_telecrystals() + if(nuke_spawn && synd_spawn.len > 0) + var/obj/machinery/nuclearbomb/syndicate/the_bomb = new /obj/machinery/nuclearbomb/syndicate(nuke_spawn.loc) + the_bomb.r_code = nuke_code + + return ..() + +/datum/game_mode/nuclear/proc/scale_telecrystals() + var/danger + danger = GLOB.player_list.len + while(!IsMultiple(++danger, 10)) //Increments danger up to the nearest multiple of ten + + total_tc += danger * NUKESCALINGMODIFIER + +/datum/game_mode/nuclear/proc/share_telecrystals() + var/player_tc + var/remainder + + player_tc = round(total_tc / GLOB.nuclear_uplink_list.len) //round to get an integer and not floating point + remainder = total_tc % GLOB.nuclear_uplink_list.len + + for(var/obj/item/radio/uplink/nuclear/U in GLOB.nuclear_uplink_list) + U.hidden_uplink.uses += player_tc + while(remainder > 0) + for(var/obj/item/radio/uplink/nuclear/U in GLOB.nuclear_uplink_list) + if(remainder <= 0) + break + U.hidden_uplink.uses++ + remainder-- + +/datum/game_mode/proc/create_syndicate(datum/mind/synd_mind) // So we don't have inferior species as ops - randomize a human + var/mob/living/carbon/human/M = synd_mind.current + + M.set_species(/datum/species/human, TRUE) + M.dna.ready_dna(M) // Quadriplegic Nuke Ops won't be participating in the paralympics + M.dna.species.create_organs(M) + M.cleanSE() //No fat/blind/colourblind/epileptic/whatever ops. + M.overeatduration = 0 + M.flavor_text = null + + var/obj/item/organ/external/head/head_organ = M.get_organ("head") + var/hair_c = pick("#8B4513","#000000","#FF4500","#FFD700") // Brown, black, red, blonde + var/eye_c = pick("#000000","#8B4513","1E90FF") // Black, brown, blue + var/skin_tone = pick(-50, -30, -10, 0, 0, 0, 10) // Caucasian/black + head_organ.facial_colour = hair_c + head_organ.sec_facial_colour = hair_c + head_organ.hair_colour = hair_c + head_organ.sec_hair_colour = hair_c + M.change_eye_color(eye_c) + M.s_tone = skin_tone + head_organ.h_style = random_hair_style(M.gender, head_organ.dna.species.name) + head_organ.f_style = random_facial_hair_style(M.gender, head_organ.dna.species.name) + M.body_accessory = null + M.regenerate_icons() + M.update_body() + +/datum/game_mode/proc/prepare_syndicate_leader(var/datum/mind/synd_mind, var/nuke_code) + var/leader_title = pick("Czar", "Boss", "Commander", "Chief", "Kingpin", "Director", "Overlord") + synd_mind.current.real_name = "[syndicate_name()] Team [leader_title]" + to_chat(synd_mind.current, "You are the Syndicate leader for this mission. You are responsible for the distribution of telecrystals and your ID is the only one who can open the launch bay doors.") + to_chat(synd_mind.current, "If you feel you are not up to this task, give your ID to another operative.") + to_chat(synd_mind.current, "In your hand you will find a special item capable of triggering a greater challenge for your team. Examine it carefully and consult with your fellow operatives before activating it.") + + var/obj/item/nuclear_challenge/challenge = new /obj/item/nuclear_challenge + synd_mind.current.equip_to_slot_or_del(challenge, slot_r_hand) + + update_syndicate_id(synd_mind, leader_title, TRUE) + + if(nuke_code) + synd_mind.store_memory("Syndicate Nuclear Bomb Code: [nuke_code]", 0, 0) + to_chat(synd_mind.current, "The nuclear authorization code is: [nuke_code]") + var/obj/item/paper/P = new + P.info = "The nuclear authorization code is: [nuke_code]" + P.name = "nuclear bomb code" + var/obj/item/stamp/syndicate/stamp = new + P.stamp(stamp) + qdel(stamp) + + if(SSticker.mode.config_tag=="nuclear") + P.loc = synd_mind.current.loc + else + var/mob/living/carbon/human/H = synd_mind.current + P.loc = H.loc + H.equip_to_slot_or_del(P, slot_r_store, 0) + H.update_icons() + + else + nuke_code = "code will be provided later" + +/datum/game_mode/proc/update_syndicate_id(var/datum/mind/synd_mind, is_leader = FALSE) + var/list/found_ids = synd_mind.current.search_contents_for(/obj/item/card/id) + + if(LAZYLEN(found_ids)) + for(var/obj/item/card/id/ID in found_ids) + ID.name = "[synd_mind.current.real_name] ID card" + ID.registered_name = synd_mind.current.real_name + if(is_leader) + ID.access += ACCESS_SYNDICATE_LEADER + else + message_admins("Warning: Operative [key_name_admin(synd_mind.current)] spawned without an ID card!") + +/datum/game_mode/proc/forge_syndicate_objectives(var/datum/mind/syndicate) + var/datum/objective/nuclear/syndobj = new + syndobj.owner = syndicate + syndicate.objectives += syndobj + + +/datum/game_mode/proc/greet_syndicate(var/datum/mind/syndicate, var/you_are=1) + SEND_SOUND(syndicate.current, 'sound/ambience/antag/ops.ogg') + if(you_are) + to_chat(syndicate.current, "You are a [syndicate_name()] agent!") + var/obj_count = 1 + for(var/datum/objective/objective in syndicate.objectives) + to_chat(syndicate.current, "Objective #[obj_count]: [objective.explanation_text]") + obj_count++ + return + + +/datum/game_mode/proc/random_radio_frequency() + return 1337 // WHY??? -- Doohl + + +/datum/game_mode/proc/equip_syndicate(mob/living/carbon/human/synd_mob, uplink_uses = 20) + var/radio_freq = SYND_FREQ + + var/obj/item/radio/R = new /obj/item/radio/headset/syndicate/alt(synd_mob) + R.set_frequency(radio_freq) + synd_mob.equip_to_slot_or_del(R, slot_l_ear) + + synd_mob.equip_to_slot_or_del(new /obj/item/clothing/under/syndicate(synd_mob), slot_w_uniform) + synd_mob.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(synd_mob), slot_shoes) + synd_mob.equip_or_collect(new /obj/item/clothing/gloves/combat(synd_mob), slot_gloves) + synd_mob.equip_to_slot_or_del(new /obj/item/card/id/syndicate(synd_mob), slot_wear_id) + synd_mob.equip_to_slot_or_del(new /obj/item/storage/backpack(synd_mob), slot_back) + synd_mob.equip_to_slot_or_del(new /obj/item/gun/projectile/automatic/pistol(synd_mob), slot_belt) + synd_mob.equip_to_slot_or_del(new /obj/item/storage/box/survival_syndi(synd_mob.back), slot_in_backpack) + synd_mob.equip_to_slot_or_del(new /obj/item/pinpointer/nukeop(synd_mob), slot_wear_pda) + var/obj/item/radio/uplink/nuclear/U = new /obj/item/radio/uplink/nuclear(synd_mob) + U.hidden_uplink.uplink_owner="[synd_mob.key]" + U.hidden_uplink.uses = uplink_uses + synd_mob.equip_to_slot_or_del(U, slot_in_backpack) + + if(synd_mob.dna.species) + + /* + Incase anyone ever gets the burning desire to have nukeops with randomized apperances. -- Dave + synd_mob.gender = pick(MALE, FEMALE) // Randomized appearances for the nukeops. + var/datum/preferences/pref = new() + A.randomize_appearance_for(synd_mob) + */ + + var/race = synd_mob.dna.species.name + + switch(race) + if("Vox" || "Vox Armalis") + synd_mob.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/syndicate(synd_mob), slot_wear_mask) + synd_mob.equip_to_slot_or_del(new /obj/item/tank/emergency_oxygen/vox(synd_mob), slot_l_hand) + synd_mob.internal = synd_mob.l_hand + synd_mob.update_action_buttons_icon() + + if("Plasmaman") + synd_mob.equip_to_slot_or_del(new /obj/item/clothing/mask/gas/syndicate(synd_mob), slot_wear_mask) + synd_mob.equip_or_collect(new /obj/item/tank/plasma/plasmaman(synd_mob), slot_s_store) + synd_mob.equip_or_collect(new /obj/item/extinguisher_refill(synd_mob), slot_in_backpack) + synd_mob.equip_or_collect(new /obj/item/extinguisher_refill(synd_mob), slot_in_backpack) + synd_mob.internal = synd_mob.get_item_by_slot(slot_s_store) + synd_mob.update_action_buttons_icon() + + synd_mob.rejuvenate() //fix any damage taken by naked vox/plasmamen/etc while round setups + var/obj/item/implant/explosive/E = new/obj/item/implant/explosive(synd_mob) + E.implant(synd_mob) + synd_mob.faction |= "syndicate" + synd_mob.update_icons() + return 1 + +/datum/game_mode/nuclear/check_win() + if(nukes_left == 0) + return 1 + return ..() + + +/datum/game_mode/proc/is_operatives_are_dead() + for(var/datum/mind/operative_mind in syndicates) + if(!istype(operative_mind.current,/mob/living/carbon/human)) + if(operative_mind.current) + if(operative_mind.current.stat!=2) + return 0 + return 1 + + +/datum/game_mode/nuclear/declare_completion() + var/disk_rescued = 1 + for(var/obj/item/disk/nuclear/D in GLOB.poi_list) + if(!D.onCentcom()) + disk_rescued = 0 + break + var/crew_evacuated = (SSshuttle.emergency.mode >= SHUTTLE_ESCAPE) + //var/operatives_are_dead = is_operatives_are_dead() + + + //nukes_left + //station_was_nuked + //derp //Used for tracking if the syndies actually haul the nuke to the station //no + //herp //Used for tracking if the syndies got the shuttle off of the z-level //NO, DON'T FUCKING NAME VARS LIKE THIS + + if(!disk_rescued && station_was_nuked && !syndies_didnt_escape) + feedback_set_details("round_end_result","nuclear win - syndicate nuke") + to_chat(world, "Syndicate Major Victory!") + to_chat(world, "[syndicate_name()] operatives have destroyed [station_name()]!") + + else if(!disk_rescued && station_was_nuked && syndies_didnt_escape) + feedback_set_details("round_end_result","nuclear halfwin - syndicate nuke - did not evacuate in time") + to_chat(world, "Total Annihilation") + to_chat(world, "[syndicate_name()] operatives destroyed [station_name()] but did not leave the area in time and got caught in the explosion. Next time, don't lose the disk!") + + else if(!disk_rescued && !station_was_nuked && nuke_off_station && !syndies_didnt_escape) + feedback_set_details("round_end_result","nuclear halfwin - blew wrong station") + to_chat(world, "Crew Minor Victory") + to_chat(world, "[syndicate_name()] operatives secured the authentication disk but blew up something that wasn't [station_name()]. Next time, don't lose the disk!") + + else if(!disk_rescued && !station_was_nuked && nuke_off_station && syndies_didnt_escape) + feedback_set_details("round_end_result","nuclear halfwin - blew wrong station - did not evacuate in time") + to_chat(world, "[syndicate_name()] operatives have earned Darwin Award!") + to_chat(world, "[syndicate_name()] operatives blew up something that wasn't [station_name()] and got caught in the explosion. Next time, don't lose the disk!") + + else if(disk_rescued && is_operatives_are_dead()) + feedback_set_details("round_end_result","nuclear loss - evacuation - disk secured - syndi team dead") + to_chat(world, "Crew Major Victory!") + to_chat(world, "The Research Staff has saved the disc and killed the [syndicate_name()] Operatives") + + else if(disk_rescued) + feedback_set_details("round_end_result","nuclear loss - evacuation - disk secured") + to_chat(world, "Crew Major Victory") + to_chat(world, "The Research Staff has saved the disc and stopped the [syndicate_name()] Operatives!") + + else if(!disk_rescued && is_operatives_are_dead()) + feedback_set_details("round_end_result","nuclear loss - evacuation - disk not secured") + to_chat(world, "Syndicate Minor Victory!") + to_chat(world, "The Research Staff failed to secure the authentication disk but did manage to kill most of the [syndicate_name()] Operatives!") + + else if(!disk_rescued && crew_evacuated) + feedback_set_details("round_end_result","nuclear halfwin - detonation averted") + to_chat(world, "Syndicate Minor Victory!") + to_chat(world, "[syndicate_name()] operatives recovered the abandoned authentication disk but detonation of [station_name()] was averted. Next time, don't lose the disk!") + + else if(!disk_rescued && !crew_evacuated) + feedback_set_details("round_end_result","nuclear halfwin - interrupted") + to_chat(world, "Neutral Victory") + to_chat(world, "Round was mysteriously interrupted!") + ..() + return + + +/datum/game_mode/proc/auto_declare_completion_nuclear() + if(syndicates.len || GAMEMODE_IS_NUCLEAR) + var/text = "
    The syndicate operatives were:" + + var/purchases = "" + var/TC_uses = 0 + + for(var/datum/mind/syndicate in syndicates) + + text += "
    [syndicate.key] was [syndicate.name] (" + if(syndicate.current) + if(syndicate.current.stat == DEAD) + text += "died" + else + text += "survived" + if(syndicate.current.real_name != syndicate.name) + text += " as [syndicate.current.real_name]" + else + text += "body destroyed" + text += ")" + for(var/obj/item/uplink/H in world_uplinks) + if(H && H.uplink_owner && H.uplink_owner==syndicate.key) + TC_uses += H.used_TC + purchases += H.purchase_log + + text += "
    " + + text += "(Syndicates used [TC_uses] TC) [purchases]" + + if(TC_uses==0 && station_was_nuked && !is_operatives_are_dead()) + text += "" + + to_chat(world, text) + return 1 + +/proc/nukelastname(var/mob/M as mob) //--All praise goes to NEO|Phyte, all blame goes to DH, and it was Cindi-Kate's idea. Also praise Urist for copypasta ho. + var/randomname = pick(GLOB.last_names) + var/newname = sanitize(copytext(input(M,"You are the nuke operative [pick("Czar", "Boss", "Commander", "Chief", "Kingpin", "Director", "Overlord")]. Please choose a last name for your family.", "Name change",randomname),1,MAX_NAME_LEN)) + + if(!newname) + newname = randomname + + else + if(newname == "Unknown" || newname == "floor" || newname == "wall" || newname == "rwall" || newname == "_") + to_chat(M, "That name is reserved.") + return nukelastname(M) + + return newname + + +/datum/game_mode/nuclear/set_scoreboard_gvars() + var/foecount = 0 + for(var/datum/mind/M in SSticker.mode.syndicates) + foecount++ + if(!M || !M.current) + score_opkilled++ + continue + + if(M.current.stat == DEAD) + score_opkilled++ + + else if(M.current.restrained()) + score_arrested++ + + if(foecount == score_arrested) + score_allarrested = 1 + + for(var/obj/machinery/nuclearbomb/nuke in world) + if(nuke.r_code == "Nope") continue + var/turf/T = get_turf(nuke) + var/area/A = T.loc + + var/list/thousand_penalty = list(/area/wizard_station, /area/solar, /area) + var/list/fiftythousand_penalty = list(/area/security/main, /area/security/brig, /area/security/armoury, /area/security/checkpoint2) + + if(is_type_in_list(A, thousand_penalty)) + score_nuked_penalty = 1000 + + else if(is_type_in_list(A, fiftythousand_penalty)) + score_nuked_penalty = 50000 + + else if(istype(A, /area/engine)) + score_nuked_penalty = 100000 + + else + score_nuked_penalty = 10000 + + break + + var/killpoints = score_opkilled * 250 + var/arrestpoints = score_arrested * 1000 + score_crewscore += killpoints + score_crewscore += arrestpoints + if(score_nuked) + score_crewscore -= score_nuked_penalty + + + +/datum/game_mode/nuclear/get_scoreboard_stats() + var/foecount = 0 + var/crewcount = 0 + + var/diskdat = "" + var/bombdat = null + + for(var/datum/mind/M in SSticker.mode.syndicates) + foecount++ + + for(var/mob/living/C in world) + if(ishuman(C) || isAI(C) || isrobot(C)) + if(C.stat == 2) continue + if(!C.client) continue + crewcount++ + + var/obj/item/disk/nuclear/N = locate() in world + if(istype(N)) + var/atom/disk_loc = N.loc + while(!isturf(disk_loc)) + if(ismob(disk_loc)) + var/mob/M = disk_loc + diskdat += "Carried by [M.real_name] " + if(isobj(disk_loc)) + var/obj/O = disk_loc + diskdat += "in \a [O]" + disk_loc = disk_loc.loc + diskdat += "in [disk_loc.loc]" + + + if(!diskdat) + diskdat = "WARNING: Nuked_penalty could not be found, look at [__FILE__], [__LINE__]." + + var/dat = "" + dat += "Mode Statistics
    " + + dat += "Number of Operatives: [foecount]
    " + dat += "Number of Surviving Crew: [crewcount]
    " + + dat += "Final Location of Nuke: [bombdat]
    " + dat += "Final Location of Disk: [diskdat]
    " + + dat += "
    " + + dat += "Operatives Arrested: [score_arrested] ([score_arrested * 1000] Points)
    " + dat += "All Operatives Arrested: [score_allarrested ? "Yes" : "No"] (Score tripled)
    " + + dat += "Operatives Killed: [score_opkilled] ([score_opkilled * 1000] Points)
    " + dat += "Station Destroyed: [score_nuked ? "Yes" : "No"] (-[score_nuked_penalty] Points)
    " + dat += "
    " + + return dat + +#undef NUKESCALINGMODIFIER diff --git a/code/game/gamemodes/nuclear/nuclearbomb.dm b/code/game/gamemodes/nuclear/nuclearbomb.dm index 714ccbf1d5f..eec24d0e1ed 100644 --- a/code/game/gamemodes/nuclear/nuclearbomb.dm +++ b/code/game/gamemodes/nuclear/nuclearbomb.dm @@ -1,472 +1,472 @@ -#define NUKE_INTACT 0 -#define NUKE_COVER_OFF 1 -#define NUKE_COVER_OPEN 2 -#define NUKE_SEALANT_OPEN 3 -#define NUKE_UNWRENCHED 4 -#define NUKE_MOBILE 5 - -var/bomb_set - -/obj/machinery/nuclearbomb - name = "\improper Nuclear Fission Explosive" - desc = "Uh oh. RUN!!!!" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "nuclearbomb0" - density = 1 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/deployable = 0 - var/extended = 0 - var/lighthack = 0 - var/timeleft = 120 - var/timing = 0 - var/r_code = "ADMIN" - var/code = "" - var/yes_code = 0 - var/safety = 1 - var/obj/item/disk/nuclear/auth = null - var/removal_stage = NUKE_INTACT - var/lastentered - var/is_syndicate = 0 - use_power = NO_POWER_USE - var/previous_level = "" - var/datum/wires/nuclearbomb/wires = null - -/obj/machinery/nuclearbomb/syndicate - is_syndicate = 1 - -/obj/machinery/nuclearbomb/New() - ..() - r_code = "[rand(10000, 99999.0)]"//Creates a random code upon object spawn. - wires = new/datum/wires/nuclearbomb(src) - previous_level = get_security_level() - GLOB.poi_list |= src - -/obj/machinery/nuclearbomb/Destroy() - QDEL_NULL(wires) - GLOB.poi_list.Remove(src) - return ..() - -/obj/machinery/nuclearbomb/process() - if(timing) - bomb_set = 1 //So long as there is one nuke timing, it means one nuke is armed. - timeleft = max(timeleft - 2, 0) // 2 seconds per process() - if(timeleft <= 0) - spawn - explode() - SSnanoui.update_uis(src) - return - -/obj/machinery/nuclearbomb/attackby(obj/item/O as obj, mob/user as mob, params) - if(istype(O, /obj/item/disk/nuclear)) - if(extended) - if(!user.drop_item()) - to_chat(user, "\The [O] is stuck to your hand!") - return - O.forceMove(src) - auth = O - add_fingerprint(user) - return attack_hand(user) - else - to_chat(user, "You need to deploy \the [src] first. Right click on the sprite, select 'Make Deployable' then click on \the [src] with an empty hand.") - return - return ..() - -/obj/machinery/nuclearbomb/crowbar_act(mob/user, obj/item/I) - if(!anchored) - return - if(removal_stage != NUKE_UNWRENCHED && removal_stage != NUKE_COVER_OFF) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(removal_stage == NUKE_COVER_OFF) - user.visible_message("[user] starts forcing open the bolt covers on [src].", "You start forcing open the anchoring bolt covers with [I]...") - if(!I.use_tool(src, user, 15, volume = I.tool_volume) || removal_stage != NUKE_COVER_OFF) - return - user.visible_message("[user] forces open the bolt covers on [src].", "You force open the bolt covers.") - removal_stage = NUKE_COVER_OPEN - else - user.visible_message("[user] begins lifting [src] off of the anchors.", "You begin lifting the device off the anchors...") - if(!I.use_tool(src, user, 80, volume = I.tool_volume) || removal_stage != NUKE_UNWRENCHED) - return - user.visible_message("[user] crowbars [src] off of the anchors. It can now be moved.", "You jam the crowbar under the nuclear device and lift it off its anchors. You can now move it!") - anchored = FALSE - removal_stage = NUKE_MOBILE - -/obj/machinery/nuclearbomb/wrench_act(mob/user, obj/item/I) - if(!anchored) - return - if(removal_stage != NUKE_SEALANT_OPEN) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - user.visible_message("[user] begins unwrenching the anchoring bolts on [src].", "You begin unwrenching the anchoring bolts...") - if(!I.use_tool(src, user, 50, volume = I.tool_volume) || removal_stage != NUKE_SEALANT_OPEN) - return - user.visible_message("[user] unwrenches the anchoring bolts on [src].", "You unwrench the anchoring bolts.") - removal_stage = NUKE_UNWRENCHED - -/obj/machinery/nuclearbomb/multitool_act(mob/user, obj/item/I) - if(!panel_open) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - attack_hand(user) - -/obj/machinery/nuclearbomb/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(auth) - if(!panel_open) - panel_open = TRUE - overlays += image(icon, "npanel_open") - to_chat(user, "You unscrew the control panel of [src].") - else - panel_open = FALSE - overlays -= image(icon, "npanel_open") - to_chat(user, "You screw the control panel of [src] back on.") - else - if(!panel_open) - to_chat(user, "[src] emits a buzzing noise, the panel staying locked in.") - if(panel_open == TRUE) - panel_open = FALSE - overlays -= image(icon, "npanel_open") - to_chat(user, "You screw the control panel of [src] back on.") - flick("nuclearbombc", src) - -/obj/machinery/nuclearbomb/wirecutter_act(mob/user, obj/item/I) - if(!panel_open) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - attack_hand(user) - -/obj/machinery/nuclearbomb/welder_act(mob/user, obj/item/I) - . = TRUE - if(removal_stage != NUKE_INTACT && removal_stage != NUKE_COVER_OPEN) - return - if(!I.tool_use_check(user, 0)) - return - if(removal_stage == NUKE_INTACT) - visible_message("[user] starts cutting loose the anchoring bolt covers on [src].",\ - "You start cutting loose the anchoring bolt covers with [I]...",\ - "You hear welding.") - if(!I.use_tool(src, user, 40, 5, volume = I.tool_volume) || removal_stage != NUKE_INTACT) - return - visible_message("[user] cuts through the bolt covers on [src].",\ - "You cut through the bolt cover.") - removal_stage = NUKE_COVER_OFF - else if(removal_stage == NUKE_COVER_OPEN) - visible_message("[user] starts cutting apart the anchoring system sealant on [src].",\ - "You start cutting apart the anchoring system's sealant with [I]...",\ - "You hear welding.") - if(!I.use_tool(src, user, 40, 5, volume = I.tool_volume) || removal_stage != NUKE_COVER_OPEN) - return - visible_message("[user] cuts apart the anchoring system sealant on [src].",\ - "You cut apart the anchoring system's sealant.
    ") - removal_stage = NUKE_SEALANT_OPEN - -/obj/machinery/nuclearbomb/attack_ghost(mob/user as mob) - if(extended) - attack_hand(user) - -/obj/machinery/nuclearbomb/attack_hand(mob/user as mob) - if(extended) - if(panel_open) - wires.Interact(user) - else - ui_interact(user) - else if(deployable) - if(removal_stage != NUKE_MOBILE) - anchored = 1 - visible_message("With a steely snap, bolts slide out of [src] and anchor it to the flooring!") - else - visible_message("\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.") - if(!lighthack) - flick("nuclearbombc", src) - icon_state = "nuclearbomb1" - extended = 1 - return - -/obj/machinery/nuclearbomb/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "nuclear_bomb.tmpl", "Nuke Control Panel", 450, 550, state = physical_state) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/nuclearbomb/ui_data(mob/user, datum/topic_state/state) - var/data[0] - data["is_syndicate"] = is_syndicate - data["hacking"] = 0 - data["auth"] = is_auth(user) - if(is_auth(user)) - if(yes_code) - data["authstatus"] = timing ? "Functional/Set" : "Functional" - else - data["authstatus"] = "Auth. S2" - else - if(timing) - data["authstatus"] = "Set" - else - data["authstatus"] = "Auth. S1" - data["safe"] = safety ? "Safe" : "Engaged" - data["time"] = timeleft - data["timer"] = timing - data["safety"] = safety - data["anchored"] = anchored - data["yescode"] = yes_code - data["message"] = "AUTH" - if(is_auth(user)) - data["message"] = code - if(yes_code) - data["message"] = "*****" - - return data - -/obj/machinery/nuclearbomb/verb/make_deployable() - set category = "Object" - set name = "Make Deployable" - set src in oview(1) - - if(usr.stat || !usr.canmove || usr.restrained()) - return - - if(deployable) - to_chat(usr, "You close several panels to make [src] undeployable.") - deployable = 0 - else - to_chat(usr, "You adjust some panels to make [src] deployable.") - deployable = 1 - return - -/obj/machinery/nuclearbomb/proc/is_auth(var/mob/user) - if(auth) - return 1 - else if(user.can_admin_interact()) - return 1 - else - return 0 - -/obj/machinery/nuclearbomb/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["auth"]) - if(auth) - auth.loc = loc - yes_code = 0 - auth = null - else - var/obj/item/I = usr.get_active_hand() - if(istype(I, /obj/item/disk/nuclear)) - usr.drop_item() - I.loc = src - auth = I - if(is_auth(usr)) - if(href_list["type"]) - if(href_list["type"] == "E") - if(code == r_code) - yes_code = 1 - code = null - else - code = "ERROR" - else - if(href_list["type"] == "R") - yes_code = 0 - code = null - else - lastentered = text("[]", href_list["type"]) - if(text2num(lastentered) == null) - var/turf/LOC = get_turf(usr) - message_admins("[key_name_admin(usr)] tried to exploit a nuclear bomb by entering non-numerical codes: [lastentered]! ([LOC ? "JMP" : "null"])", 0) - log_admin("EXPLOIT: [key_name(usr)] tried to exploit a nuclear bomb by entering non-numerical codes: [lastentered]!") - else - code += lastentered - if(length(code) > 5) - code = "ERROR" - if(yes_code) - if(href_list["time"]) - var/time = text2num(href_list["time"]) - timeleft += time - timeleft = min(max(round(src.timeleft), 120), 600) - if(href_list["timer"]) - if(timing == -1.0) - SSnanoui.update_uis(src) - return - if(safety) - to_chat(usr, "The safety is still on.") - SSnanoui.update_uis(src) - return - timing = !(timing) - if(timing) - if(!lighthack) - icon_state = "nuclearbomb2" - if(!safety) - message_admins("[key_name_admin(usr)] engaged a nuclear bomb (JMP)") - if(!is_syndicate) - set_security_level("delta") - bomb_set = 1 //There can still be issues with this resetting when there are multiple bombs. Not a big deal though for Nuke/N - else - bomb_set = 0 - else - if(!is_syndicate) - set_security_level(previous_level) - bomb_set = 0 - if(!lighthack) - icon_state = "nuclearbomb1" - if(href_list["safety"]) - safety = !(safety) - if(safety) - if(!is_syndicate) - set_security_level(previous_level) - timing = 0 - bomb_set = 0 - if(href_list["anchor"]) - if(removal_stage == NUKE_MOBILE) - anchored = 0 - visible_message("\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.") - SSnanoui.update_uis(src) - return - - if(!isinspace()) - anchored = !(anchored) - if(anchored) - visible_message("With a steely snap, bolts slide out of [src] and anchor it to the flooring.") - else - visible_message("The anchoring bolts slide back into the depths of [src].") - else - to_chat(usr, "There is nothing to anchor to!") - - SSnanoui.update_uis(src) - -/obj/machinery/nuclearbomb/blob_act(obj/structure/blob/B) - if(timing == -1.0) - return - qdel(src) - -/obj/machinery/nuclearbomb/tesla_act(power, explosive) - ..() - if(explosive) - qdel(src)//like the singulo, tesla deletes it. stops it from exploding over and over - -#define NUKERANGE 80 -/obj/machinery/nuclearbomb/proc/explode() - if(safety) - timing = 0 - return - timing = -1.0 - yes_code = 0 - safety = 1 - if(!lighthack) - icon_state = "nuclearbomb3" - playsound(src,'sound/machines/alarm.ogg',100,0,5) - if(SSticker && SSticker.mode) - SSticker.mode.explosion_in_progress = 1 - sleep(100) - - enter_allowed = 0 - - var/off_station = 0 - var/turf/bomb_location = get_turf(src) - if( bomb_location && is_station_level(bomb_location.z) ) - if( (bomb_location.x < (128-NUKERANGE)) || (bomb_location.x > (128+NUKERANGE)) || (bomb_location.y < (128-NUKERANGE)) || (bomb_location.y > (128+NUKERANGE)) ) - off_station = 1 - else - off_station = 2 - - if(SSticker) - if(SSticker.mode && SSticker.mode.name == "nuclear emergency") - var/obj/docking_port/mobile/syndie_shuttle = SSshuttle.getShuttle("syndicate") - if(syndie_shuttle) - SSticker.mode:syndies_didnt_escape = is_station_level(syndie_shuttle.z) - SSticker.mode:nuke_off_station = off_station - SSticker.station_explosion_cinematic(off_station,null) - if(SSticker.mode) - SSticker.mode.explosion_in_progress = 0 - if(SSticker.mode.name == "nuclear emergency") - SSticker.mode:nukes_left -- - else if(off_station == 1) - to_chat(world, "A nuclear device was set off, but the explosion was out of reach of the station!") - else if(off_station == 2) - to_chat(world, "A nuclear device was set off, but the device was not on the station!") - else - to_chat(world, "The station was destoyed by the nuclear blast!") - - SSticker.mode.station_was_nuked = (off_station<2) //offstation==1 is a draw. the station becomes irradiated and needs to be evacuated. - //kinda shit but I couldn't get permission to do what I wanted to do. - - if(!SSticker.mode.check_finished())//If the mode does not deal with the nuke going off so just reboot because everyone is stuck as is - world.Reboot("Station destroyed by Nuclear Device.", "end_error", "nuke - unhandled ending") - return - return - - -//==========DAT FUKKEN DISK=============== -/obj/item/disk/nuclear - name = "nuclear authentication disk" - desc = "Better keep this safe." - icon_state = "nucleardisk" - max_integrity = 250 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/disk/nuclear/unrestricted - desc = "Seems to have been stripped of its safeties, you better not lose it." - -/obj/item/disk/nuclear/New() - ..() - START_PROCESSING(SSobj, src) - GLOB.poi_list |= src - -/obj/item/disk/nuclear/process() - if(!check_disk_loc()) - var/holder = get(src, /mob) - if(holder) - to_chat(holder, "You can't help but feel that you just lost something back there...") - qdel(src) - - //station disk is allowed on z1, escape shuttle/pods, CC, and syndicate shuttles/base, reset otherwise -/obj/item/disk/nuclear/proc/check_disk_loc() - var/turf/T = get_turf(src) - var/area/A = get_area(src) - if(is_station_level(T.z)) - return TRUE - if(A.nad_allowed) - return TRUE - return FALSE - -/obj/item/disk/nuclear/unrestricted/check_disk_loc() - return TRUE - -/obj/item/disk/nuclear/Destroy(force) - var/turf/diskturf = get_turf(src) - - if(force) - message_admins("[src] has been !!force deleted!! in ([diskturf ? "[diskturf.x], [diskturf.y] ,[diskturf.z] - JMP":"nonexistent location"]).") - log_game("[src] has been !!force deleted!! in ([diskturf ? "[diskturf.x], [diskturf.y] ,[diskturf.z]":"nonexistent location"]).") - GLOB.poi_list.Remove(src) - STOP_PROCESSING(SSobj, src) - return ..() - - if(blobstart.len > 0) - GLOB.poi_list.Remove(src) - var/obj/item/disk/nuclear/NEWDISK = new(pick(blobstart)) - transfer_fingerprints_to(NEWDISK) - message_admins("[src] has been destroyed at ([diskturf.x], [diskturf.y], [diskturf.z] - JMP). Moving it to ([NEWDISK.x], [NEWDISK.y], [NEWDISK.z] - JMP).") - log_game("[src] has been destroyed in ([diskturf.x], [diskturf.y], [diskturf.z]). Moving it to ([NEWDISK.x], [NEWDISK.y], [NEWDISK.z]).") - return QDEL_HINT_HARDDEL_NOW - else - error("[src] was supposed to be destroyed, but we were unable to locate a blobstart landmark to spawn a new one.") - return QDEL_HINT_LETMELIVE // Cancel destruction unless forced. - -#undef NUKE_INTACT -#undef NUKE_COVER_OFF -#undef NUKE_COVER_OPEN -#undef NUKE_SEALANT_OPEN -#undef NUKE_UNWRENCHED -#undef NUKE_MOBILE +#define NUKE_INTACT 0 +#define NUKE_COVER_OFF 1 +#define NUKE_COVER_OPEN 2 +#define NUKE_SEALANT_OPEN 3 +#define NUKE_UNWRENCHED 4 +#define NUKE_MOBILE 5 + +var/bomb_set + +/obj/machinery/nuclearbomb + name = "\improper Nuclear Fission Explosive" + desc = "Uh oh. RUN!!!!" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "nuclearbomb0" + density = 1 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/deployable = 0 + var/extended = 0 + var/lighthack = 0 + var/timeleft = 120 + var/timing = 0 + var/r_code = "ADMIN" + var/code = "" + var/yes_code = 0 + var/safety = 1 + var/obj/item/disk/nuclear/auth = null + var/removal_stage = NUKE_INTACT + var/lastentered + var/is_syndicate = 0 + use_power = NO_POWER_USE + var/previous_level = "" + var/datum/wires/nuclearbomb/wires = null + +/obj/machinery/nuclearbomb/syndicate + is_syndicate = 1 + +/obj/machinery/nuclearbomb/New() + ..() + r_code = "[rand(10000, 99999.0)]"//Creates a random code upon object spawn. + wires = new/datum/wires/nuclearbomb(src) + previous_level = get_security_level() + GLOB.poi_list |= src + +/obj/machinery/nuclearbomb/Destroy() + QDEL_NULL(wires) + GLOB.poi_list.Remove(src) + return ..() + +/obj/machinery/nuclearbomb/process() + if(timing) + bomb_set = 1 //So long as there is one nuke timing, it means one nuke is armed. + timeleft = max(timeleft - 2, 0) // 2 seconds per process() + if(timeleft <= 0) + spawn + explode() + SSnanoui.update_uis(src) + return + +/obj/machinery/nuclearbomb/attackby(obj/item/O as obj, mob/user as mob, params) + if(istype(O, /obj/item/disk/nuclear)) + if(extended) + if(!user.drop_item()) + to_chat(user, "\The [O] is stuck to your hand!") + return + O.forceMove(src) + auth = O + add_fingerprint(user) + return attack_hand(user) + else + to_chat(user, "You need to deploy \the [src] first. Right click on the sprite, select 'Make Deployable' then click on \the [src] with an empty hand.") + return + return ..() + +/obj/machinery/nuclearbomb/crowbar_act(mob/user, obj/item/I) + if(!anchored) + return + if(removal_stage != NUKE_UNWRENCHED && removal_stage != NUKE_COVER_OFF) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(removal_stage == NUKE_COVER_OFF) + user.visible_message("[user] starts forcing open the bolt covers on [src].", "You start forcing open the anchoring bolt covers with [I]...") + if(!I.use_tool(src, user, 15, volume = I.tool_volume) || removal_stage != NUKE_COVER_OFF) + return + user.visible_message("[user] forces open the bolt covers on [src].", "You force open the bolt covers.") + removal_stage = NUKE_COVER_OPEN + else + user.visible_message("[user] begins lifting [src] off of the anchors.", "You begin lifting the device off the anchors...") + if(!I.use_tool(src, user, 80, volume = I.tool_volume) || removal_stage != NUKE_UNWRENCHED) + return + user.visible_message("[user] crowbars [src] off of the anchors. It can now be moved.", "You jam the crowbar under the nuclear device and lift it off its anchors. You can now move it!") + anchored = FALSE + removal_stage = NUKE_MOBILE + +/obj/machinery/nuclearbomb/wrench_act(mob/user, obj/item/I) + if(!anchored) + return + if(removal_stage != NUKE_SEALANT_OPEN) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + user.visible_message("[user] begins unwrenching the anchoring bolts on [src].", "You begin unwrenching the anchoring bolts...") + if(!I.use_tool(src, user, 50, volume = I.tool_volume) || removal_stage != NUKE_SEALANT_OPEN) + return + user.visible_message("[user] unwrenches the anchoring bolts on [src].", "You unwrench the anchoring bolts.") + removal_stage = NUKE_UNWRENCHED + +/obj/machinery/nuclearbomb/multitool_act(mob/user, obj/item/I) + if(!panel_open) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + attack_hand(user) + +/obj/machinery/nuclearbomb/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(auth) + if(!panel_open) + panel_open = TRUE + overlays += image(icon, "npanel_open") + to_chat(user, "You unscrew the control panel of [src].") + else + panel_open = FALSE + overlays -= image(icon, "npanel_open") + to_chat(user, "You screw the control panel of [src] back on.") + else + if(!panel_open) + to_chat(user, "[src] emits a buzzing noise, the panel staying locked in.") + if(panel_open == TRUE) + panel_open = FALSE + overlays -= image(icon, "npanel_open") + to_chat(user, "You screw the control panel of [src] back on.") + flick("nuclearbombc", src) + +/obj/machinery/nuclearbomb/wirecutter_act(mob/user, obj/item/I) + if(!panel_open) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + attack_hand(user) + +/obj/machinery/nuclearbomb/welder_act(mob/user, obj/item/I) + . = TRUE + if(removal_stage != NUKE_INTACT && removal_stage != NUKE_COVER_OPEN) + return + if(!I.tool_use_check(user, 0)) + return + if(removal_stage == NUKE_INTACT) + visible_message("[user] starts cutting loose the anchoring bolt covers on [src].",\ + "You start cutting loose the anchoring bolt covers with [I]...",\ + "You hear welding.") + if(!I.use_tool(src, user, 40, 5, volume = I.tool_volume) || removal_stage != NUKE_INTACT) + return + visible_message("[user] cuts through the bolt covers on [src].",\ + "You cut through the bolt cover.") + removal_stage = NUKE_COVER_OFF + else if(removal_stage == NUKE_COVER_OPEN) + visible_message("[user] starts cutting apart the anchoring system sealant on [src].",\ + "You start cutting apart the anchoring system's sealant with [I]...",\ + "You hear welding.") + if(!I.use_tool(src, user, 40, 5, volume = I.tool_volume) || removal_stage != NUKE_COVER_OPEN) + return + visible_message("[user] cuts apart the anchoring system sealant on [src].",\ + "You cut apart the anchoring system's sealant.
    ") + removal_stage = NUKE_SEALANT_OPEN + +/obj/machinery/nuclearbomb/attack_ghost(mob/user as mob) + if(extended) + attack_hand(user) + +/obj/machinery/nuclearbomb/attack_hand(mob/user as mob) + if(extended) + if(panel_open) + wires.Interact(user) + else + ui_interact(user) + else if(deployable) + if(removal_stage != NUKE_MOBILE) + anchored = 1 + visible_message("With a steely snap, bolts slide out of [src] and anchor it to the flooring!") + else + visible_message("\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.") + if(!lighthack) + flick("nuclearbombc", src) + icon_state = "nuclearbomb1" + extended = 1 + return + +/obj/machinery/nuclearbomb/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "nuclear_bomb.tmpl", "Nuke Control Panel", 450, 550, state = physical_state) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/nuclearbomb/ui_data(mob/user, datum/topic_state/state) + var/data[0] + data["is_syndicate"] = is_syndicate + data["hacking"] = 0 + data["auth"] = is_auth(user) + if(is_auth(user)) + if(yes_code) + data["authstatus"] = timing ? "Functional/Set" : "Functional" + else + data["authstatus"] = "Auth. S2" + else + if(timing) + data["authstatus"] = "Set" + else + data["authstatus"] = "Auth. S1" + data["safe"] = safety ? "Safe" : "Engaged" + data["time"] = timeleft + data["timer"] = timing + data["safety"] = safety + data["anchored"] = anchored + data["yescode"] = yes_code + data["message"] = "AUTH" + if(is_auth(user)) + data["message"] = code + if(yes_code) + data["message"] = "*****" + + return data + +/obj/machinery/nuclearbomb/verb/make_deployable() + set category = "Object" + set name = "Make Deployable" + set src in oview(1) + + if(usr.stat || !usr.canmove || usr.restrained()) + return + + if(deployable) + to_chat(usr, "You close several panels to make [src] undeployable.") + deployable = 0 + else + to_chat(usr, "You adjust some panels to make [src] deployable.") + deployable = 1 + return + +/obj/machinery/nuclearbomb/proc/is_auth(var/mob/user) + if(auth) + return 1 + else if(user.can_admin_interact()) + return 1 + else + return 0 + +/obj/machinery/nuclearbomb/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["auth"]) + if(auth) + auth.loc = loc + yes_code = 0 + auth = null + else + var/obj/item/I = usr.get_active_hand() + if(istype(I, /obj/item/disk/nuclear)) + usr.drop_item() + I.loc = src + auth = I + if(is_auth(usr)) + if(href_list["type"]) + if(href_list["type"] == "E") + if(code == r_code) + yes_code = 1 + code = null + else + code = "ERROR" + else + if(href_list["type"] == "R") + yes_code = 0 + code = null + else + lastentered = text("[]", href_list["type"]) + if(text2num(lastentered) == null) + var/turf/LOC = get_turf(usr) + message_admins("[key_name_admin(usr)] tried to exploit a nuclear bomb by entering non-numerical codes: [lastentered]! ([LOC ? "JMP" : "null"])", 0) + log_admin("EXPLOIT: [key_name(usr)] tried to exploit a nuclear bomb by entering non-numerical codes: [lastentered]!") + else + code += lastentered + if(length(code) > 5) + code = "ERROR" + if(yes_code) + if(href_list["time"]) + var/time = text2num(href_list["time"]) + timeleft += time + timeleft = min(max(round(src.timeleft), 120), 600) + if(href_list["timer"]) + if(timing == -1.0) + SSnanoui.update_uis(src) + return + if(safety) + to_chat(usr, "The safety is still on.") + SSnanoui.update_uis(src) + return + timing = !(timing) + if(timing) + if(!lighthack) + icon_state = "nuclearbomb2" + if(!safety) + message_admins("[key_name_admin(usr)] engaged a nuclear bomb (JMP)") + if(!is_syndicate) + set_security_level("delta") + bomb_set = 1 //There can still be issues with this resetting when there are multiple bombs. Not a big deal though for Nuke/N + else + bomb_set = 0 + else + if(!is_syndicate) + set_security_level(previous_level) + bomb_set = 0 + if(!lighthack) + icon_state = "nuclearbomb1" + if(href_list["safety"]) + safety = !(safety) + if(safety) + if(!is_syndicate) + set_security_level(previous_level) + timing = 0 + bomb_set = 0 + if(href_list["anchor"]) + if(removal_stage == NUKE_MOBILE) + anchored = 0 + visible_message("\The [src] makes a highly unpleasant crunching noise. It looks like the anchoring bolts have been cut.") + SSnanoui.update_uis(src) + return + + if(!isinspace()) + anchored = !(anchored) + if(anchored) + visible_message("With a steely snap, bolts slide out of [src] and anchor it to the flooring.") + else + visible_message("The anchoring bolts slide back into the depths of [src].") + else + to_chat(usr, "There is nothing to anchor to!") + + SSnanoui.update_uis(src) + +/obj/machinery/nuclearbomb/blob_act(obj/structure/blob/B) + if(timing == -1.0) + return + qdel(src) + +/obj/machinery/nuclearbomb/tesla_act(power, explosive) + ..() + if(explosive) + qdel(src)//like the singulo, tesla deletes it. stops it from exploding over and over + +#define NUKERANGE 80 +/obj/machinery/nuclearbomb/proc/explode() + if(safety) + timing = 0 + return + timing = -1.0 + yes_code = 0 + safety = 1 + if(!lighthack) + icon_state = "nuclearbomb3" + playsound(src,'sound/machines/alarm.ogg',100,0,5) + if(SSticker && SSticker.mode) + SSticker.mode.explosion_in_progress = 1 + sleep(100) + + enter_allowed = 0 + + var/off_station = 0 + var/turf/bomb_location = get_turf(src) + if( bomb_location && is_station_level(bomb_location.z) ) + if( (bomb_location.x < (128-NUKERANGE)) || (bomb_location.x > (128+NUKERANGE)) || (bomb_location.y < (128-NUKERANGE)) || (bomb_location.y > (128+NUKERANGE)) ) + off_station = 1 + else + off_station = 2 + + if(SSticker) + if(SSticker.mode && SSticker.mode.name == "nuclear emergency") + var/obj/docking_port/mobile/syndie_shuttle = SSshuttle.getShuttle("syndicate") + if(syndie_shuttle) + SSticker.mode:syndies_didnt_escape = is_station_level(syndie_shuttle.z) + SSticker.mode:nuke_off_station = off_station + SSticker.station_explosion_cinematic(off_station,null) + if(SSticker.mode) + SSticker.mode.explosion_in_progress = 0 + if(SSticker.mode.name == "nuclear emergency") + SSticker.mode:nukes_left -- + else if(off_station == 1) + to_chat(world, "A nuclear device was set off, but the explosion was out of reach of the station!") + else if(off_station == 2) + to_chat(world, "A nuclear device was set off, but the device was not on the station!") + else + to_chat(world, "The station was destoyed by the nuclear blast!") + + SSticker.mode.station_was_nuked = (off_station<2) //offstation==1 is a draw. the station becomes irradiated and needs to be evacuated. + //kinda shit but I couldn't get permission to do what I wanted to do. + + if(!SSticker.mode.check_finished())//If the mode does not deal with the nuke going off so just reboot because everyone is stuck as is + world.Reboot("Station destroyed by Nuclear Device.", "end_error", "nuke - unhandled ending") + return + return + + +//==========DAT FUKKEN DISK=============== +/obj/item/disk/nuclear + name = "nuclear authentication disk" + desc = "Better keep this safe." + icon_state = "nucleardisk" + max_integrity = 250 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/disk/nuclear/unrestricted + desc = "Seems to have been stripped of its safeties, you better not lose it." + +/obj/item/disk/nuclear/New() + ..() + START_PROCESSING(SSobj, src) + GLOB.poi_list |= src + +/obj/item/disk/nuclear/process() + if(!check_disk_loc()) + var/holder = get(src, /mob) + if(holder) + to_chat(holder, "You can't help but feel that you just lost something back there...") + qdel(src) + + //station disk is allowed on z1, escape shuttle/pods, CC, and syndicate shuttles/base, reset otherwise +/obj/item/disk/nuclear/proc/check_disk_loc() + var/turf/T = get_turf(src) + var/area/A = get_area(src) + if(is_station_level(T.z)) + return TRUE + if(A.nad_allowed) + return TRUE + return FALSE + +/obj/item/disk/nuclear/unrestricted/check_disk_loc() + return TRUE + +/obj/item/disk/nuclear/Destroy(force) + var/turf/diskturf = get_turf(src) + + if(force) + message_admins("[src] has been !!force deleted!! in ([diskturf ? "[diskturf.x], [diskturf.y] ,[diskturf.z] - JMP":"nonexistent location"]).") + log_game("[src] has been !!force deleted!! in ([diskturf ? "[diskturf.x], [diskturf.y] ,[diskturf.z]":"nonexistent location"]).") + GLOB.poi_list.Remove(src) + STOP_PROCESSING(SSobj, src) + return ..() + + if(blobstart.len > 0) + GLOB.poi_list.Remove(src) + var/obj/item/disk/nuclear/NEWDISK = new(pick(blobstart)) + transfer_fingerprints_to(NEWDISK) + message_admins("[src] has been destroyed at ([diskturf.x], [diskturf.y], [diskturf.z] - JMP). Moving it to ([NEWDISK.x], [NEWDISK.y], [NEWDISK.z] - JMP).") + log_game("[src] has been destroyed in ([diskturf.x], [diskturf.y], [diskturf.z]). Moving it to ([NEWDISK.x], [NEWDISK.y], [NEWDISK.z]).") + return QDEL_HINT_HARDDEL_NOW + else + error("[src] was supposed to be destroyed, but we were unable to locate a blobstart landmark to spawn a new one.") + return QDEL_HINT_LETMELIVE // Cancel destruction unless forced. + +#undef NUKE_INTACT +#undef NUKE_COVER_OFF +#undef NUKE_COVER_OPEN +#undef NUKE_SEALANT_OPEN +#undef NUKE_UNWRENCHED +#undef NUKE_MOBILE diff --git a/code/game/gamemodes/nuclear/pinpointer.dm b/code/game/gamemodes/nuclear/pinpointer.dm index 251c32716bb..f193856b3d9 100644 --- a/code/game/gamemodes/nuclear/pinpointer.dm +++ b/code/game/gamemodes/nuclear/pinpointer.dm @@ -1,404 +1,404 @@ -/obj/item/pinpointer - name = "pinpointer" - icon = 'icons/obj/device.dmi' - icon_state = "pinoff" - flags = CONDUCT - slot_flags = SLOT_PDA | SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - throw_speed = 4 - throw_range = 20 - materials = list(MAT_METAL=500) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - var/obj/item/disk/nuclear/the_disk = null - var/active = 0 - var/shows_nuke_timer = TRUE - var/icon_off = "pinoff" - var/icon_null = "pinonnull" - var/icon_direct = "pinondirect" - var/icon_close = "pinonclose" - var/icon_medium = "pinonmedium" - var/icon_far = "pinonfar" - -/obj/item/pinpointer/New() - ..() - GLOB.pinpointer_list += src - -/obj/item/pinpointer/Destroy() - GLOB.pinpointer_list -= src - active = 0 - the_disk = null - return ..() - -/obj/item/pinpointer/attack_self() - if(!active) - active = 1 - workdisk() - to_chat(usr, "You activate the pinpointer.") - else - active = 0 - icon_state = icon_off - to_chat(usr, "You deactivate the pinpointer.") - -/obj/item/pinpointer/proc/scandisk() - if(!the_disk) - the_disk = locate() - -/obj/item/pinpointer/proc/point_at(atom/target, spawnself = 1) - if(!active) - return - if(!target) - icon_state = icon_null - return - - var/turf/T = get_turf(target) - var/turf/L = get_turf(src) - - if(!(T && L) || (T.z != L.z)) - icon_state = icon_null - else - dir = get_dir(L, T) - switch(get_dist(L, T)) - if(-1) - icon_state = icon_direct - if(1 to 8) - icon_state = icon_close - if(9 to 16) - icon_state = icon_medium - if(16 to INFINITY) - icon_state = icon_far - if(spawnself) - spawn(5) - .() - -/obj/item/pinpointer/proc/workdisk() - scandisk() - point_at(the_disk, 0) - spawn(5) - .() - -/obj/item/pinpointer/examine(mob/user) - . = ..() - if(shows_nuke_timer) - for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines) - if(bomb.timing) - . += "Extreme danger. Arming signal detected. Time remaining: [bomb.timeleft]" - -/obj/item/pinpointer/advpinpointer - name = "advanced pinpointer" - desc = "A larger version of the normal pinpointer, this unit features a helpful quantum entanglement detection system to locate various objects that do not broadcast a locator signal." - var/mode = 0 // Mode 0 locates disk, mode 1 locates coordinates. - var/turf/location = null - var/obj/target = null - -/obj/item/pinpointer/advpinpointer/attack_self() - if(!active) - active = 1 - if(mode == 0) - workdisk() - if(mode == 1) - point_at(location) - if(mode == 2) - point_at(target) - to_chat(usr, "You activate the pinpointer.") - else - active = 0 - icon_state = icon_off - to_chat(usr, "You deactivate the pinpointer.") - -/obj/item/pinpointer/advpinpointer/workdisk() - if(mode == 0) - scandisk() - point_at(the_disk, 0) - spawn(5) - .() - -/obj/item/pinpointer/advpinpointer/verb/toggle_mode() - set category = "Object" - set name = "Toggle Pinpointer Mode" - set src in usr - - if(usr.stat || usr.restrained()) - return - - active = 0 - icon_state = icon_off - target = null - location = null - - switch(alert("Please select the mode you want to put the pinpointer in.", "Pinpointer Mode Select", "Location", "Disk Recovery", "Other Signature")) - if("Location") - mode = 1 - - var/locationx = input(usr, "Please input the x coordinate to search for.", "Location?" , "") as num - if(!locationx || !(usr in view(1,src))) - return - var/locationy = input(usr, "Please input the y coordinate to search for.", "Location?" , "") as num - if(!locationy || !(usr in view(1,src))) - return - - var/turf/Z = get_turf(src) - - location = locate(locationx,locationy,Z.z) - - to_chat(usr, "You set the pinpointer to locate [locationx],[locationy]") - - - return attack_self() - - if("Disk Recovery") - mode = 0 - return attack_self() - - if("Other Signature") - mode = 2 - switch(alert("Search for item signature or DNA fragment?" , "Signature Mode Select" , "Item" , "DNA")) - if("Item") - var/list/item_names[0] - var/list/item_paths[0] - for(var/objective in potential_theft_objectives) - var/datum/theft_objective/T = objective - var/name = initial(T.name) - item_names += name - item_paths[name] = initial(T.typepath) - var/targetitem = input("Select item to search for.", "Item Mode Select","") as null|anything in item_names - if(!targetitem) - return - - var/list/target_candidates = get_all_of_type(item_paths[targetitem], subtypes = TRUE) - for(var/obj/item/candidate in target_candidates) - if(!is_admin_level((get_turf(candidate)).z)) - target = candidate - break - - if(!target) - to_chat(usr, "Failed to locate [targetitem]!") - return - to_chat(usr, "You set the pinpointer to locate [targetitem].") - if("DNA") - var/DNAstring = input("Input DNA string to search for." , "Please Enter String." , "") - if(!DNAstring) - return - for(var/mob/living/carbon/C in GLOB.mob_list) - if(!C.dna) - continue - if(C.dna.unique_enzymes == DNAstring) - target = C - break - - return attack_self() - -/////////////////////// -//nuke op pinpointers// -/////////////////////// -/obj/item/pinpointer/nukeop - var/mode = 0 //Mode 0 locates disk, mode 1 locates the shuttle - var/obj/docking_port/mobile/home = null - slot_flags = SLOT_BELT | SLOT_PDA - -/obj/item/pinpointer/nukeop/attack_self(mob/user as mob) - if(!active) - active = 1 - if(!mode) - workdisk() - to_chat(user, "Authentication Disk Locator active.") - else - worklocation() - to_chat(user, "Shuttle Locator active.") - else - active = 0 - icon_state = icon_off - to_chat(user, "You deactivate the pinpointer.") - -/obj/item/pinpointer/nukeop/workdisk() - if(!active) return - if(mode) //Check in case the mode changes while operating - worklocation() - return - if(bomb_set) //If the bomb is set, lead to the shuttle - mode = 1 //Ensures worklocation() continues to work - worklocation() - playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) //Plays a beep - visible_message("Shuttle Locator mode actived.") //Lets the mob holding it know that the mode has changed - return //Get outta here - scandisk() - if(!the_disk) - icon_state = icon_null - return - dir = get_dir(src, the_disk) - switch(get_dist(src, the_disk)) - if(0) - icon_state = icon_direct - if(1 to 8) - icon_state = icon_close - if(9 to 16) - icon_state = icon_medium - if(16 to INFINITY) - icon_state = icon_far - - spawn(5) .() - -/obj/item/pinpointer/nukeop/proc/worklocation() - if(!active) - return - if(!mode) - workdisk() - return - if(!bomb_set) - mode = 0 - workdisk() - playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) - visible_message("Authentication Disk Locator mode actived.") - return - if(!home) - home = SSshuttle.getShuttle("syndicate") - if(!home) - icon_state = icon_null - return - if(loc.z != home.z) //If you are on a different z-level from the shuttle - icon_state = icon_null - else - dir = get_dir(src, home) - switch(get_dist(src, home)) - if(0) - icon_state = icon_direct - if(1 to 8) - icon_state = icon_close - if(9 to 16) - icon_state = icon_medium - if(16 to INFINITY) - icon_state = icon_far - - spawn(5) - .() - -/obj/item/pinpointer/operative - name = "operative pinpointer" - desc = "A pinpointer that leads to the first Syndicate operative detected." - var/mob/living/carbon/nearest_op = null - -/obj/item/pinpointer/operative/attack_self() - if(!usr.mind || !(usr.mind in SSticker.mode.syndicates)) - to_chat(usr, "AUTHENTICATION FAILURE. ACCESS DENIED.") - return 0 - if(!active) - active = 1 - workop() - to_chat(usr, "You activate the pinpointer.") - else - active = 0 - icon_state = icon_off - to_chat(usr, "You deactivate the pinpointer.") - -/obj/item/pinpointer/operative/proc/scan_for_ops() - if(active) - nearest_op = null //Resets nearest_op every time it scans - var/closest_distance = 1000 - for(var/mob/living/carbon/M in GLOB.mob_list) - if(M.mind && (M.mind in SSticker.mode.syndicates)) - if(get_dist(M, get_turf(src)) < closest_distance) //Actually points toward the nearest op, instead of a random one like it used to - nearest_op = M - -/obj/item/pinpointer/operative/proc/workop() - if(active) - scan_for_ops() - point_at(nearest_op, 0) - spawn(5) - .() - else - return 0 - -/obj/item/pinpointer/operative/examine(mob/user) - . = ..() - if(active) - if(nearest_op) - . += "Nearest operative detected is [nearest_op.real_name]." - else - . += "No operatives detected within scanning range." - -/obj/item/pinpointer/crew - name = "crew pinpointer" - desc = "A handheld tracking device that points to crew suit sensors." - shows_nuke_timer = FALSE - icon_state = "pinoff_crew" - icon_off = "pinoff_crew" - icon_null = "pinonnull_crew" - icon_direct = "pinondirect_crew" - icon_close = "pinonclose_crew" - icon_medium = "pinonmedium_crew" - icon_far = "pinonfar_crew" - -/obj/item/pinpointer/crew/proc/trackable(mob/living/carbon/human/H) - var/turf/here = get_turf(src) - if(istype(H.w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = H.w_uniform - - // Suit sensors must be on maximum. - if(!U.has_sensor || U.sensor_mode < 3) - return FALSE - - var/turf/there = get_turf(U) - return there && there.z == here.z - - return FALSE - -/obj/item/pinpointer/crew/attack_self(mob/living/user) - if(active) - active = FALSE - icon_state = icon_off - user.visible_message("[user] deactivates [user.p_their()] pinpointer.", "You deactivate your pinpointer.") - return - - var/list/name_counts = list() - var/list/names = list() - - for(var/mob/living/carbon/human/H in GLOB.mob_list) - if(!trackable(H)) - continue - - var/name = "Unknown" - if(H.wear_id) - var/obj/item/card/id/I = H.wear_id.GetID() - if(I) - name = I.registered_name - - while(name in name_counts) - name_counts[name]++ - name = text("[] ([])", name, name_counts[name]) - names[name] = H - name_counts[name] = 1 - - if(!names.len) - user.visible_message("[user]'s pinpointer fails to detect a signal.", "Your pinpointer fails to detect a signal.") - return - - var/A = input(user, "Person to track", "Pinpoint") in names - if(!src || !user || (user.get_active_hand() != src) || user.incapacitated() || !A) - return - - var/target = names[A] - active = TRUE - user.visible_message("[user] activates [user.p_their()] pinpointer.", "You activate your pinpointer.") - point_at(target) - -/obj/item/pinpointer/crew/point_at(atom/target, spawnself = 1) - if(!active) - return - - if(!trackable(target) || !target) - icon_state = icon_null - return - - ..(target, spawnself = 0) - if(spawnself) - spawn(5) - .() - -/obj/item/pinpointer/crew/centcom - name = "centcom pinpointer" - desc = "A handheld tracking device that tracks crew based on remote centcom sensors." - -/obj/item/pinpointer/crew/centcom/trackable(mob/living/carbon/human/H) - var/turf/here = get_turf(src) - var/turf/there = get_turf(H) - return there && there.z == here.z \ No newline at end of file +/obj/item/pinpointer + name = "pinpointer" + icon = 'icons/obj/device.dmi' + icon_state = "pinoff" + flags = CONDUCT + slot_flags = SLOT_PDA | SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + throw_speed = 4 + throw_range = 20 + materials = list(MAT_METAL=500) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + var/obj/item/disk/nuclear/the_disk = null + var/active = 0 + var/shows_nuke_timer = TRUE + var/icon_off = "pinoff" + var/icon_null = "pinonnull" + var/icon_direct = "pinondirect" + var/icon_close = "pinonclose" + var/icon_medium = "pinonmedium" + var/icon_far = "pinonfar" + +/obj/item/pinpointer/New() + ..() + GLOB.pinpointer_list += src + +/obj/item/pinpointer/Destroy() + GLOB.pinpointer_list -= src + active = 0 + the_disk = null + return ..() + +/obj/item/pinpointer/attack_self() + if(!active) + active = 1 + workdisk() + to_chat(usr, "You activate the pinpointer.") + else + active = 0 + icon_state = icon_off + to_chat(usr, "You deactivate the pinpointer.") + +/obj/item/pinpointer/proc/scandisk() + if(!the_disk) + the_disk = locate() + +/obj/item/pinpointer/proc/point_at(atom/target, spawnself = 1) + if(!active) + return + if(!target) + icon_state = icon_null + return + + var/turf/T = get_turf(target) + var/turf/L = get_turf(src) + + if(!(T && L) || (T.z != L.z)) + icon_state = icon_null + else + dir = get_dir(L, T) + switch(get_dist(L, T)) + if(-1) + icon_state = icon_direct + if(1 to 8) + icon_state = icon_close + if(9 to 16) + icon_state = icon_medium + if(16 to INFINITY) + icon_state = icon_far + if(spawnself) + spawn(5) + .() + +/obj/item/pinpointer/proc/workdisk() + scandisk() + point_at(the_disk, 0) + spawn(5) + .() + +/obj/item/pinpointer/examine(mob/user) + . = ..() + if(shows_nuke_timer) + for(var/obj/machinery/nuclearbomb/bomb in GLOB.machines) + if(bomb.timing) + . += "Extreme danger. Arming signal detected. Time remaining: [bomb.timeleft]" + +/obj/item/pinpointer/advpinpointer + name = "advanced pinpointer" + desc = "A larger version of the normal pinpointer, this unit features a helpful quantum entanglement detection system to locate various objects that do not broadcast a locator signal." + var/mode = 0 // Mode 0 locates disk, mode 1 locates coordinates. + var/turf/location = null + var/obj/target = null + +/obj/item/pinpointer/advpinpointer/attack_self() + if(!active) + active = 1 + if(mode == 0) + workdisk() + if(mode == 1) + point_at(location) + if(mode == 2) + point_at(target) + to_chat(usr, "You activate the pinpointer.") + else + active = 0 + icon_state = icon_off + to_chat(usr, "You deactivate the pinpointer.") + +/obj/item/pinpointer/advpinpointer/workdisk() + if(mode == 0) + scandisk() + point_at(the_disk, 0) + spawn(5) + .() + +/obj/item/pinpointer/advpinpointer/verb/toggle_mode() + set category = "Object" + set name = "Toggle Pinpointer Mode" + set src in usr + + if(usr.stat || usr.restrained()) + return + + active = 0 + icon_state = icon_off + target = null + location = null + + switch(alert("Please select the mode you want to put the pinpointer in.", "Pinpointer Mode Select", "Location", "Disk Recovery", "Other Signature")) + if("Location") + mode = 1 + + var/locationx = input(usr, "Please input the x coordinate to search for.", "Location?" , "") as num + if(!locationx || !(usr in view(1,src))) + return + var/locationy = input(usr, "Please input the y coordinate to search for.", "Location?" , "") as num + if(!locationy || !(usr in view(1,src))) + return + + var/turf/Z = get_turf(src) + + location = locate(locationx,locationy,Z.z) + + to_chat(usr, "You set the pinpointer to locate [locationx],[locationy]") + + + return attack_self() + + if("Disk Recovery") + mode = 0 + return attack_self() + + if("Other Signature") + mode = 2 + switch(alert("Search for item signature or DNA fragment?" , "Signature Mode Select" , "Item" , "DNA")) + if("Item") + var/list/item_names[0] + var/list/item_paths[0] + for(var/objective in potential_theft_objectives) + var/datum/theft_objective/T = objective + var/name = initial(T.name) + item_names += name + item_paths[name] = initial(T.typepath) + var/targetitem = input("Select item to search for.", "Item Mode Select","") as null|anything in item_names + if(!targetitem) + return + + var/list/target_candidates = get_all_of_type(item_paths[targetitem], subtypes = TRUE) + for(var/obj/item/candidate in target_candidates) + if(!is_admin_level((get_turf(candidate)).z)) + target = candidate + break + + if(!target) + to_chat(usr, "Failed to locate [targetitem]!") + return + to_chat(usr, "You set the pinpointer to locate [targetitem].") + if("DNA") + var/DNAstring = input("Input DNA string to search for." , "Please Enter String." , "") + if(!DNAstring) + return + for(var/mob/living/carbon/C in GLOB.mob_list) + if(!C.dna) + continue + if(C.dna.unique_enzymes == DNAstring) + target = C + break + + return attack_self() + +/////////////////////// +//nuke op pinpointers// +/////////////////////// +/obj/item/pinpointer/nukeop + var/mode = 0 //Mode 0 locates disk, mode 1 locates the shuttle + var/obj/docking_port/mobile/home = null + slot_flags = SLOT_BELT | SLOT_PDA + +/obj/item/pinpointer/nukeop/attack_self(mob/user as mob) + if(!active) + active = 1 + if(!mode) + workdisk() + to_chat(user, "Authentication Disk Locator active.") + else + worklocation() + to_chat(user, "Shuttle Locator active.") + else + active = 0 + icon_state = icon_off + to_chat(user, "You deactivate the pinpointer.") + +/obj/item/pinpointer/nukeop/workdisk() + if(!active) return + if(mode) //Check in case the mode changes while operating + worklocation() + return + if(bomb_set) //If the bomb is set, lead to the shuttle + mode = 1 //Ensures worklocation() continues to work + worklocation() + playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) //Plays a beep + visible_message("Shuttle Locator mode actived.") //Lets the mob holding it know that the mode has changed + return //Get outta here + scandisk() + if(!the_disk) + icon_state = icon_null + return + dir = get_dir(src, the_disk) + switch(get_dist(src, the_disk)) + if(0) + icon_state = icon_direct + if(1 to 8) + icon_state = icon_close + if(9 to 16) + icon_state = icon_medium + if(16 to INFINITY) + icon_state = icon_far + + spawn(5) .() + +/obj/item/pinpointer/nukeop/proc/worklocation() + if(!active) + return + if(!mode) + workdisk() + return + if(!bomb_set) + mode = 0 + workdisk() + playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) + visible_message("Authentication Disk Locator mode actived.") + return + if(!home) + home = SSshuttle.getShuttle("syndicate") + if(!home) + icon_state = icon_null + return + if(loc.z != home.z) //If you are on a different z-level from the shuttle + icon_state = icon_null + else + dir = get_dir(src, home) + switch(get_dist(src, home)) + if(0) + icon_state = icon_direct + if(1 to 8) + icon_state = icon_close + if(9 to 16) + icon_state = icon_medium + if(16 to INFINITY) + icon_state = icon_far + + spawn(5) + .() + +/obj/item/pinpointer/operative + name = "operative pinpointer" + desc = "A pinpointer that leads to the first Syndicate operative detected." + var/mob/living/carbon/nearest_op = null + +/obj/item/pinpointer/operative/attack_self() + if(!usr.mind || !(usr.mind in SSticker.mode.syndicates)) + to_chat(usr, "AUTHENTICATION FAILURE. ACCESS DENIED.") + return 0 + if(!active) + active = 1 + workop() + to_chat(usr, "You activate the pinpointer.") + else + active = 0 + icon_state = icon_off + to_chat(usr, "You deactivate the pinpointer.") + +/obj/item/pinpointer/operative/proc/scan_for_ops() + if(active) + nearest_op = null //Resets nearest_op every time it scans + var/closest_distance = 1000 + for(var/mob/living/carbon/M in GLOB.mob_list) + if(M.mind && (M.mind in SSticker.mode.syndicates)) + if(get_dist(M, get_turf(src)) < closest_distance) //Actually points toward the nearest op, instead of a random one like it used to + nearest_op = M + +/obj/item/pinpointer/operative/proc/workop() + if(active) + scan_for_ops() + point_at(nearest_op, 0) + spawn(5) + .() + else + return 0 + +/obj/item/pinpointer/operative/examine(mob/user) + . = ..() + if(active) + if(nearest_op) + . += "Nearest operative detected is [nearest_op.real_name]." + else + . += "No operatives detected within scanning range." + +/obj/item/pinpointer/crew + name = "crew pinpointer" + desc = "A handheld tracking device that points to crew suit sensors." + shows_nuke_timer = FALSE + icon_state = "pinoff_crew" + icon_off = "pinoff_crew" + icon_null = "pinonnull_crew" + icon_direct = "pinondirect_crew" + icon_close = "pinonclose_crew" + icon_medium = "pinonmedium_crew" + icon_far = "pinonfar_crew" + +/obj/item/pinpointer/crew/proc/trackable(mob/living/carbon/human/H) + var/turf/here = get_turf(src) + if(istype(H.w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = H.w_uniform + + // Suit sensors must be on maximum. + if(!U.has_sensor || U.sensor_mode < 3) + return FALSE + + var/turf/there = get_turf(U) + return there && there.z == here.z + + return FALSE + +/obj/item/pinpointer/crew/attack_self(mob/living/user) + if(active) + active = FALSE + icon_state = icon_off + user.visible_message("[user] deactivates [user.p_their()] pinpointer.", "You deactivate your pinpointer.") + return + + var/list/name_counts = list() + var/list/names = list() + + for(var/mob/living/carbon/human/H in GLOB.mob_list) + if(!trackable(H)) + continue + + var/name = "Unknown" + if(H.wear_id) + var/obj/item/card/id/I = H.wear_id.GetID() + if(I) + name = I.registered_name + + while(name in name_counts) + name_counts[name]++ + name = text("[] ([])", name, name_counts[name]) + names[name] = H + name_counts[name] = 1 + + if(!names.len) + user.visible_message("[user]'s pinpointer fails to detect a signal.", "Your pinpointer fails to detect a signal.") + return + + var/A = input(user, "Person to track", "Pinpoint") in names + if(!src || !user || (user.get_active_hand() != src) || user.incapacitated() || !A) + return + + var/target = names[A] + active = TRUE + user.visible_message("[user] activates [user.p_their()] pinpointer.", "You activate your pinpointer.") + point_at(target) + +/obj/item/pinpointer/crew/point_at(atom/target, spawnself = 1) + if(!active) + return + + if(!trackable(target) || !target) + icon_state = icon_null + return + + ..(target, spawnself = 0) + if(spawnself) + spawn(5) + .() + +/obj/item/pinpointer/crew/centcom + name = "centcom pinpointer" + desc = "A handheld tracking device that tracks crew based on remote centcom sensors." + +/obj/item/pinpointer/crew/centcom/trackable(mob/living/carbon/human/H) + var/turf/here = get_turf(src) + var/turf/there = get_turf(H) + return there && there.z == here.z diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 89ecb1d103b..eb667d4994d 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -1,809 +1,809 @@ -GLOBAL_LIST_EMPTY(all_objectives) - -var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datum/theft_objective/steal - /datum/theft_objective/number - /datum/theft_objective/unique - -/datum/objective - var/datum/mind/owner = null //Who owns the objective. - var/explanation_text = "Nothing" //What that person is supposed to do. - var/datum/mind/target = null //If they are focused on a particular person. - var/target_amount = 0 //If they are focused on a particular number. Steal objectives have their own counter. - var/completed = 0 //currently only used for custom objectives. - var/martyr_compatible = 0 //If the objective is compatible with martyr objective, i.e. if you can still do it while dead. - -/datum/objective/New(text) - GLOB.all_objectives += src - if(text) - explanation_text = text - -/datum/objective/Destroy() - GLOB.all_objectives -= src - return ..() - -/datum/objective/proc/check_completion() - return completed - -/datum/objective/proc/is_invalid_target(datum/mind/possible_target) - if(possible_target == owner) - return TARGET_INVALID_IS_OWNER - if(possible_target in owner.targets) - return TARGET_INVALID_IS_TARGET - if(!ishuman(possible_target.current)) - return TARGET_INVALID_NOT_HUMAN - if(!possible_target.current.stat == DEAD) - return TARGET_INVALID_DEAD - if(!possible_target.key) - return TARGET_INVALID_NOCKEY - if(possible_target.current) - var/turf/current_location = get_turf(possible_target.current) - if(current_location && !is_level_reachable(current_location.z)) - return TARGET_INVALID_UNREACHABLE - if(isgolem(possible_target.current)) - return TARGET_INVALID_GOLEM - if(possible_target.offstation_role) - return TARGET_INVALID_EVENT - - -/datum/objective/proc/find_target() - var/list/possible_targets = list() - for(var/datum/mind/possible_target in SSticker.minds) - if(is_invalid_target(possible_target)) - continue - - possible_targets += possible_target - - if(possible_targets.len > 0) - target = pick(possible_targets) - -/datum/objective/assassinate - martyr_compatible = 1 - -/datum/objective/assassinate/find_target() - ..() - if(target && target.current) - explanation_text = "Assassinate [target.current.real_name], the [target.assigned_role]." - else - explanation_text = "Free Objective" - return target - -/datum/objective/assassinate/check_completion() - if(target && target.current) - if(target.current.stat == DEAD || iszombie(target)) - return 1 - if(issilicon(target.current) || isbrain(target.current)) //Borgs/brains/AIs count as dead for traitor objectives. --NeoFite - return 1 - if(!target.current.ckey) - return 1 - return 0 - return 1 - - -/datum/objective/mutiny - martyr_compatible = 1 - -/datum/objective/mutiny/find_target() - ..() - if(target && target.current) - explanation_text = "Assassinate [target.current.real_name], the [target.assigned_role]." - else - explanation_text = "Free Objective" - return target - -/datum/objective/mutiny/check_completion() - if(target && target.current) - if(target.current.stat == DEAD || !ishuman(target.current) || !target.current.ckey || !target.current.client) - return 1 - var/turf/T = get_turf(target.current) - if(T && !is_station_level(T.z)) //If they leave the station they count as dead for this - return 1 - return 0 - return 1 - -/datum/objective/maroon - martyr_compatible = 1 - -/datum/objective/maroon/find_target() - ..() - if(target && target.current) - explanation_text = "Prevent [target.current.real_name], the [target.assigned_role] from escaping alive." - else - explanation_text = "Free Objective" - return target - -/datum/objective/maroon/check_completion() - if(target && target.current) - if(target.current.stat == DEAD || iszombie(target)) - return 1 - if(!target.current.ckey) - return 1 - if(issilicon(target.current)) - return 1 - if(isbrain(target.current)) - return 1 - var/turf/T = get_turf(target.current) - if(is_admin_level(T.z)) - return 0 - return 1 - return 1 - - -/datum/objective/debrain //I want braaaainssss - martyr_compatible = 0 - -/datum/objective/debrain/find_target() - ..() - if(target && target.current) - explanation_text = "Steal the brain of [target.current.real_name] the [target.assigned_role]." - else - explanation_text = "Free Objective" - return target - - -/datum/objective/debrain/check_completion() - if(!target)//If it's a free objective. - return 1 - if(!owner.current || owner.current.stat == DEAD) - return 0 - if(!target.current || !isbrain(target.current)) - return 0 - var/atom/A = target.current - while(A.loc) //check to see if the brainmob is on our person - A = A.loc - if(A == owner.current) - return 1 - return 0 - - -/datum/objective/protect //The opposite of killing a dude. - martyr_compatible = 1 - -/datum/objective/protect/find_target() - ..() - if(target && target.current) - explanation_text = "Protect [target.current.real_name], the [target.assigned_role]." - else - explanation_text = "Free Objective" - return target - -/datum/objective/protect/check_completion() - if(!target) //If it's a free objective. - return 1 - if(target.current) - if(target.current.stat == DEAD || iszombie(target)) - return 0 - if(issilicon(target.current)) - return 0 - if(isbrain(target.current)) - return 0 - return 1 - return 0 - -/datum/objective/protect/mindslave //subytpe for mindslave implants - - -/datum/objective/hijack - martyr_compatible = 0 //Technically you won't get both anyway. - explanation_text = "Hijack the shuttle by escaping on it with no loyalist Nanotrasen crew on board and free. \ - Syndicate agents, other enemies of Nanotrasen, cyborgs, pets, and cuffed/restrained hostages may be allowed on the shuttle alive." - -/datum/objective/hijack/check_completion() - if(!owner.current || owner.current.stat) - return 0 - if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) - return 0 - if(issilicon(owner.current)) - return 0 - - var/area/A = get_area(owner.current) - if(SSshuttle.emergency.areaInstance != A) - return 0 - - return SSshuttle.emergency.is_hijacked() - -/datum/objective/hijackclone - explanation_text = "Hijack the shuttle by ensuring only you (or your copies) escape." - martyr_compatible = 0 - -/datum/objective/hijackclone/check_completion() - if(!owner.current) - return 0 - if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) - return 0 - - var/area/A = SSshuttle.emergency.areaInstance - - for(var/mob/living/player in GLOB.player_list) //Make sure nobody else is onboard - if(player.mind && player.mind != owner) - if(player.stat != DEAD) - if(issilicon(player)) - continue - if(get_area(player) == A) - if(player.real_name != owner.current.real_name && !istype(get_turf(player.mind.current), /turf/simulated/shuttle/floor4)) - return 0 - - for(var/mob/living/player in GLOB.player_list) //Make sure at least one of you is onboard - if(player.mind && player.mind != owner) - if(player.stat != DEAD) - if(issilicon(player)) - continue - if(get_area(player) == A) - if(player.real_name == owner.current.real_name && !istype(get_turf(player.mind.current), /turf/simulated/shuttle/floor4)) - return 1 - return 0 - -/datum/objective/block - explanation_text = "Do not allow any lifeforms, be it organic or synthetic to escape on the shuttle alive. AIs, Cyborgs, and pAIs are not considered alive." - martyr_compatible = 1 - -/datum/objective/block/check_completion() - if(!istype(owner.current, /mob/living/silicon)) - return 0 - if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) - return 0 - if(!owner.current) - return 0 - - var/area/A = SSshuttle.emergency.areaInstance - var/list/protected_mobs = list(/mob/living/silicon/ai, /mob/living/silicon/pai, /mob/living/silicon/robot) - - for(var/mob/living/player in GLOB.player_list) - if(player.type in protected_mobs) - continue - - if(player.mind && player.stat != DEAD) - if(get_area(player) == A) - return 0 - - return 1 - -/datum/objective/escape - explanation_text = "Escape on the shuttle or an escape pod alive and free." - -/datum/objective/escape/check_completion() - if(issilicon(owner.current)) - return 0 - if(isbrain(owner.current)) - return 0 - if(!owner.current || owner.current.stat == DEAD || iszombie(owner)) - return 0 - if(SSticker.force_ending) //This one isn't their fault, so lets just assume good faith - return 1 - if(SSticker.mode.station_was_nuked) //If they escaped the blast somehow, let them win - return 1 - if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) - return 0 - var/turf/location = get_turf(owner.current) - if(!location) - return 0 - - if(istype(location, /turf/simulated/shuttle/floor4) || istype(location, /turf/simulated/floor/mineral/plastitanium/red/brig)) // Fails traitors if they are in the shuttle brig -- Polymorph - return 0 - - if(location.onCentcom() || location.onSyndieBase()) - return 1 - - return 0 - - -/datum/objective/escape/escape_with_identity - var/target_real_name // Has to be stored because the target's real_name can change over the course of the round - -/datum/objective/escape/escape_with_identity/find_target() - var/list/possible_targets = list() //Copypasta because NO_DNA races, yay for snowflakes. - for(var/datum/mind/possible_target in SSticker.minds) - if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != DEAD) && possible_target.current.client) - var/mob/living/carbon/human/H = possible_target.current - if(!(NO_DNA in H.dna.species.species_traits)) - possible_targets += possible_target - if(possible_targets.len > 0) - target = pick(possible_targets) - if(target && target.current) - target_real_name = target.current.real_name - explanation_text = "Escape on the shuttle or an escape pod with the identity of [target_real_name], the [target.assigned_role] while wearing [target.p_their()] identification card." - else - explanation_text = "Free Objective" - -/datum/objective/escape/escape_with_identity/check_completion() - if(!target_real_name) - return 1 - if(!ishuman(owner.current)) - return 0 - var/mob/living/carbon/human/H = owner.current - if(..()) - if(H.dna.real_name == target_real_name) - if(H.get_id_name()== target_real_name) - return 1 - return 0 - -/datum/objective/die - explanation_text = "Die a glorious death." - -/datum/objective/die/check_completion() - if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current) || iszombie(owner)) - return 1 - if(issilicon(owner.current) && owner.current != owner.original) - return 1 - return 0 - - - -/datum/objective/survive - explanation_text = "Stay alive until the end." - -/datum/objective/survive/check_completion() - if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current)) - return 0 //Brains no longer win survive objectives. --NEO - if(issilicon(owner.current) && owner.current != owner.original) - return 0 - return 1 - -/datum/objective/nuclear - explanation_text = "Destroy the station with a nuclear device." - martyr_compatible = 1 - -/datum/objective/steal - var/datum/theft_objective/steal_target - martyr_compatible = 0 - var/theft_area - -/datum/objective/steal/proc/get_location() - if(steal_target.location_override) - return steal_target.location_override - var/list/obj/item/steal_candidates = get_all_of_type(steal_target.typepath, subtypes = TRUE) - for(var/obj/item/candidate in steal_candidates) - if(!is_admin_level(candidate.loc.z)) - theft_area = get_area(candidate.loc) - return "[theft_area]" - return "an unknown area" - -/datum/objective/steal/find_target() - var/loop=50 - while(!steal_target && loop > 0) - loop-- - var/thefttype = pick(potential_theft_objectives) - var/datum/theft_objective/O = new thefttype - if(owner.assigned_role in O.protected_jobs) - continue - if(O in owner.targets) - continue - if(O.flags & 2) - continue - steal_target = O - - explanation_text = "Steal [steal_target]. One was last seen in [get_location()]. " - if(islist(O.protected_jobs) && O.protected_jobs.len) - explanation_text += "It may also be in the possession of the [jointext(O.protected_jobs, ", ")]." - return - explanation_text = "Free Objective." - - -/datum/objective/steal/proc/select_target() - var/list/possible_items_all = potential_theft_objectives+"custom" - var/new_target = input("Select target:", "Objective target", null) as null|anything in possible_items_all - if(!new_target) return - if(new_target == "custom") - var/datum/theft_objective/O=new - O.typepath = input("Select type:","Type") as null|anything in typesof(/obj/item) - if(!O.typepath) return - var/tmp_obj = new O.typepath - var/custom_name = tmp_obj:name - qdel(tmp_obj) - O.name = sanitize(copytext(input("Enter target name:", "Objective target", custom_name) as text|null,1,MAX_NAME_LEN)) - if(!O.name) return - steal_target = O - explanation_text = "Steal [O.name]." - else - steal_target = new new_target - explanation_text = "Steal [steal_target.name]." - return steal_target - -/datum/objective/steal/check_completion() - if(!steal_target) - return 1 // Free Objective - - var/list/all_items = owner.current.GetAllContents() - - for(var/obj/I in all_items) - if(istype(I, steal_target.typepath)) - return steal_target.check_special_completion(I) - if(I.type in steal_target.altitems) - return steal_target.check_special_completion(I) - - -/datum/objective/steal/exchange - martyr_compatible = 0 - -/datum/objective/steal/exchange/proc/set_faction(var/faction,var/otheragent) - target = otheragent - var/datum/theft_objective/unique/targetinfo - if(faction == "red") - targetinfo = new /datum/theft_objective/unique/docs_blue - else if(faction == "blue") - targetinfo = new /datum/theft_objective/unique/docs_red - explanation_text = "Acquire [targetinfo.name] held by [target.current.real_name], the [target.assigned_role] and syndicate agent" - steal_target = targetinfo - -/datum/objective/steal/exchange/backstab -/datum/objective/steal/exchange/backstab/set_faction(var/faction) - var/datum/theft_objective/unique/targetinfo - if(faction == "red") - targetinfo = new /datum/theft_objective/unique/docs_red - else if(faction == "blue") - targetinfo = new /datum/theft_objective/unique/docs_blue - explanation_text = "Do not give up or lose [targetinfo.name]." - steal_target = targetinfo - -/datum/objective/download -/datum/objective/download/proc/gen_amount_goal() - target_amount = rand(10,20) - explanation_text = "Download [target_amount] research levels." - return target_amount - - -/datum/objective/download/check_completion() - return 0 - - - -/datum/objective/capture -/datum/objective/capture/proc/gen_amount_goal() - target_amount = rand(5,10) - explanation_text = "Accumulate [target_amount] capture points." - return target_amount - - -/datum/objective/capture/check_completion()//Basically runs through all the mobs in the area to determine how much they are worth. - return 0 - - - - -/datum/objective/absorb -/datum/objective/absorb/proc/gen_amount_goal(var/lowbound = 4, var/highbound = 6) - target_amount = rand (lowbound,highbound) - if(SSticker) - var/n_p = 1 //autowin - if(SSticker.current_state == GAME_STATE_SETTING_UP) - for(var/mob/new_player/P in GLOB.player_list) - if(P.client && P.ready && P.mind != owner) - if(P.client.prefs && (P.client.prefs.species == "Machine")) // Special check for species that can't be absorbed. No better solution. - continue - n_p++ - else if(SSticker.current_state == GAME_STATE_PLAYING) - for(var/mob/living/carbon/human/P in GLOB.player_list) - if(NO_DNA in P.dna.species.species_traits) - continue - if(P.client && !(P.mind in SSticker.mode.changelings) && P.mind!=owner) - n_p++ - target_amount = min(target_amount, n_p) - - explanation_text = "Absorb [target_amount] compatible genomes." - return target_amount - -/datum/objective/absorb/check_completion() - if(owner && owner.changeling && owner.changeling.absorbed_dna && (owner.changeling.absorbedcount >= target_amount)) - return 1 - else - return 0 - -/datum/objective/destroy - martyr_compatible = 1 - var/target_real_name - -/datum/objective/destroy/find_target() - var/list/possible_targets = active_ais(1) - var/mob/living/silicon/ai/target_ai = pick(possible_targets) - target = target_ai.mind - if(target && target.current) - target_real_name = target.current.real_name - explanation_text = "Destroy [target_real_name], the AI." - else - explanation_text = "Free Objective" - return target - -/datum/objective/destroy/check_completion() - if(target && target.current) - if(target.current.stat == DEAD || is_away_level(target.current.z) || !target.current.ckey) - return 1 - return 0 - return 1 - -/datum/objective/steal_five_of_type - explanation_text = "Steal at least five items!" - var/list/wanted_items = list() - -/datum/objective/steal_five_of_type/New() - ..() - wanted_items = typecacheof(wanted_items) - -/datum/objective/steal_five_of_type/check_completion() - var/stolen_count = 0 - if(!isliving(owner.current)) - return FALSE - var/list/all_items = owner.current.GetAllContents() //this should get things in cheesewheels, books, etc. - for(var/obj/I in all_items) //Check for wanted items - if(is_type_in_typecache(I, wanted_items)) - stolen_count++ - return stolen_count >= 5 - -/datum/objective/steal_five_of_type/summon_guns - explanation_text = "Steal at least five guns!" - wanted_items = list(/obj/item/gun) - -/datum/objective/steal_five_of_type/summon_magic - explanation_text = "Steal at least five magical artefacts!" - wanted_items = list() - -/datum/objective/steal_five_of_type/summon_magic/New() - wanted_items = GLOB.summoned_magic_objectives - ..() - -/datum/objective/steal_five_of_type/summon_magic/check_completion() - var/stolen_count = 0 - if(!isliving(owner.current)) - return FALSE - var/list/all_items = owner.current.GetAllContents() //this should get things in cheesewheels, books, etc. - for(var/obj/I in all_items) //Check for wanted items - if(istype(I, /obj/item/spellbook) && !istype(I, /obj/item/spellbook/oneuse)) - var/obj/item/spellbook/spellbook = I - if(spellbook.uses) //if the book still has powers... - stolen_count++ //it counts. nice. - if(istype(I, /obj/item/spellbook/oneuse)) - var/obj/item/spellbook/oneuse/oneuse = I - if(!oneuse.used) - stolen_count++ - else if(is_type_in_typecache(I, wanted_items)) - stolen_count++ - return stolen_count >= 5 - -/datum/objective/blood -/datum/objective/blood/proc/gen_amount_goal(low = 150, high = 400) - target_amount = rand(low,high) - target_amount = round(round(target_amount/5)*5) - explanation_text = "Accumulate at least [target_amount] total units of blood." - return target_amount - -/datum/objective/blood/check_completion() - if(owner && owner.vampire && owner.vampire.bloodtotal && owner.vampire.bloodtotal >= target_amount) - return 1 - else - return 0 - -// /vg/; Vox Inviolate for humans :V -/datum/objective/minimize_casualties - explanation_text = "Minimise casualties." -/datum/objective/minimize_casualties/check_completion() - if(owner.kills.len>5) return 0 - return 1 - -//Vox heist objectives. - -/datum/objective/heist -/datum/objective/heist/proc/choose_target() - return - -/datum/objective/heist/kidnap -/datum/objective/heist/kidnap/choose_target() - var/list/roles = list("Chief Engineer","Research Director","Roboticist","Chemist","Station Engineer") - var/list/possible_targets = list() - var/list/priority_targets = list() - - for(var/datum/mind/possible_target in SSticker.minds) - if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != DEAD) && (possible_target.assigned_role != possible_target.special_role) && !possible_target.offstation_role) - possible_targets += possible_target - for(var/role in roles) - if(possible_target.assigned_role == role) - priority_targets += possible_target - continue - - if(priority_targets.len > 0) - target = pick(priority_targets) - else if(possible_targets.len > 0) - target = pick(possible_targets) - - if(target && target.current) - explanation_text = "The Shoal has a need for [target.current.real_name], the [target.assigned_role]. Take [target.current.p_them()] alive." - else - explanation_text = "Free Objective" - return target - -/datum/objective/heist/kidnap/check_completion() - if(target && target.current) - if(target.current.stat == DEAD) - return 0 - - var/area/shuttle/vox/A = locate() //stupid fucking hardcoding - var/area/vox_station/B = locate() //but necessary - - for(var/mob/living/carbon/human/M in A) - if(target.current == M) - return 1 - for(var/mob/living/carbon/human/M in B) - if(target.current == M) - return 1 - else - return 0 - -/datum/objective/heist/loot -/datum/objective/heist/loot/choose_target() - var/loot = "an object" - switch(rand(1,8)) - if(1) - target = /obj/structure/particle_accelerator - target_amount = 6 - loot = "a complete particle accelerator" - if(2) - target = /obj/machinery/the_singularitygen - target_amount = 1 - loot = "a gravitational singularity generator" - if(3) - target = /obj/machinery/power/emitter - target_amount = 4 - loot = "four emitters" - if(4) - target = /obj/machinery/nuclearbomb - target_amount = 1 - loot = "a nuclear bomb" - if(5) - target = /obj/item/gun - target_amount = 6 - loot = "six guns. Tasers and other non-lethal guns are acceptable" - if(6) - target = /obj/item/gun/energy - target_amount = 4 - loot = "four energy guns" - if(7) - target = /obj/item/gun/energy/laser - target_amount = 2 - loot = "two laser guns" - if(8) - target = /obj/item/gun/energy/ionrifle - target_amount = 1 - loot = "an ion gun" - - explanation_text = "We are lacking in hardware. Steal or trade [loot]." - -/datum/objective/heist/loot/check_completion() - var/total_amount = 0 - - for(var/obj/O in locate(/area/shuttle/vox)) - if(istype(O, target)) - total_amount++ - for(var/obj/I in O.contents) - if(istype(I, target)) - total_amount++ - if(total_amount >= target_amount) - return 1 - - for(var/obj/O in locate(/area/vox_station)) - if(istype(O, target)) - total_amount++ - for(var/obj/I in O.contents) - if(istype(I, target)) - total_amount++ - if(total_amount >= target_amount) - return 1 - - var/datum/game_mode/heist/H = SSticker.mode - for(var/datum/mind/raider in H.raiders) - if(raider.current) - for(var/obj/O in raider.current.get_contents()) - if(istype(O,target)) - total_amount++ - if(total_amount >= target_amount) - return 1 - - return 0 - -/datum/objective/heist/salvage -/datum/objective/heist/salvage/choose_target() - switch(rand(1,8)) - if(1) - target = "metal" - target_amount = 300 - if(2) - target = "glass" - target_amount = 200 - if(3) - target = "plasteel" - target_amount = 100 - if(4) - target = "solid plasma" - target_amount = 100 - if(5) - target = "silver" - target_amount = 50 - if(6) - target = "gold" - target_amount = 20 - if(7) - target = "uranium" - target_amount = 20 - if(8) - target = "diamond" - target_amount = 20 - - explanation_text = "Ransack or trade with the station and escape with [target_amount] [target]." - -/datum/objective/heist/salvage/check_completion() - var/total_amount = 0 - - for(var/obj/item/O in locate(/area/shuttle/vox)) - var/obj/item/stack/sheet/S - if(istype(O,/obj/item/stack/sheet)) - if(O.name == target) - S = O - total_amount += S.get_amount() - - for(var/obj/I in O.contents) - if(istype(I,/obj/item/stack/sheet)) - if(I.name == target) - S = I - total_amount += S.get_amount() - - for(var/obj/item/O in locate(/area/vox_station)) - var/obj/item/stack/sheet/S - if(istype(O,/obj/item/stack/sheet)) - if(O.name == target) - S = O - total_amount += S.get_amount() - - for(var/obj/I in O.contents) - if(istype(I,/obj/item/stack/sheet)) - if(I.name == target) - S = I - total_amount += S.get_amount() - - var/datum/game_mode/heist/H = SSticker.mode - for(var/datum/mind/raider in H.raiders) - if(raider.current) - for(var/obj/item/O in raider.current.get_contents()) - if(istype(O,/obj/item/stack/sheet)) - if(O.name == target) - var/obj/item/stack/sheet/S = O - total_amount += S.get_amount() - - if(total_amount >= target_amount) return 1 - return 0 - - -/datum/objective/heist/inviolate_crew - explanation_text = "Do not leave any Vox behind, alive or dead." - -/datum/objective/heist/inviolate_crew/check_completion() - var/datum/game_mode/heist/H = SSticker.mode - if(H.is_raider_crew_safe()) - return 1 - return 0 - -/datum/objective/heist/inviolate_death - explanation_text = "Follow the Inviolate. Minimise death and loss of resources." - -/datum/objective/heist/inviolate_death/check_completion() - var/vox_allowed_kills = 3 // The number of people the vox can accidently kill. Mostly a counter to people killing themselves if a raider touches them to force fail. - var/vox_total_kills = 0 - - var/datum/game_mode/heist/H = SSticker.mode - for(var/datum/mind/raider in H.raiders) - vox_total_kills += raider.kills.len // Kills are listed in the mind; uses this to calculate vox kills - - if(vox_total_kills > vox_allowed_kills) return 0 - return 1 - - -// Traders -// These objectives have no check_completion, they exist only to tell Sol Traders what to aim for. - -/datum/objective/trade/proc/choose_target() - return - -/datum/objective/trade/plasma/choose_target() - explanation_text = "Acquire at least 15 sheets of plasma through trade." - -/datum/objective/trade/credits/choose_target() - explanation_text = "Acquire at least 10,000 credits through trade." - -//wizard - -/datum/objective/wizchaos - explanation_text = "Wreak havoc upon the station as much you can. Send those wandless Nanotrasen scum a message!" - completed = 1 +GLOBAL_LIST_EMPTY(all_objectives) + +var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datum/theft_objective/steal - /datum/theft_objective/number - /datum/theft_objective/unique + +/datum/objective + var/datum/mind/owner = null //Who owns the objective. + var/explanation_text = "Nothing" //What that person is supposed to do. + var/datum/mind/target = null //If they are focused on a particular person. + var/target_amount = 0 //If they are focused on a particular number. Steal objectives have their own counter. + var/completed = 0 //currently only used for custom objectives. + var/martyr_compatible = 0 //If the objective is compatible with martyr objective, i.e. if you can still do it while dead. + +/datum/objective/New(text) + GLOB.all_objectives += src + if(text) + explanation_text = text + +/datum/objective/Destroy() + GLOB.all_objectives -= src + return ..() + +/datum/objective/proc/check_completion() + return completed + +/datum/objective/proc/is_invalid_target(datum/mind/possible_target) + if(possible_target == owner) + return TARGET_INVALID_IS_OWNER + if(possible_target in owner.targets) + return TARGET_INVALID_IS_TARGET + if(!ishuman(possible_target.current)) + return TARGET_INVALID_NOT_HUMAN + if(!possible_target.current.stat == DEAD) + return TARGET_INVALID_DEAD + if(!possible_target.key) + return TARGET_INVALID_NOCKEY + if(possible_target.current) + var/turf/current_location = get_turf(possible_target.current) + if(current_location && !is_level_reachable(current_location.z)) + return TARGET_INVALID_UNREACHABLE + if(isgolem(possible_target.current)) + return TARGET_INVALID_GOLEM + if(possible_target.offstation_role) + return TARGET_INVALID_EVENT + + +/datum/objective/proc/find_target() + var/list/possible_targets = list() + for(var/datum/mind/possible_target in SSticker.minds) + if(is_invalid_target(possible_target)) + continue + + possible_targets += possible_target + + if(possible_targets.len > 0) + target = pick(possible_targets) + +/datum/objective/assassinate + martyr_compatible = 1 + +/datum/objective/assassinate/find_target() + ..() + if(target && target.current) + explanation_text = "Assassinate [target.current.real_name], the [target.assigned_role]." + else + explanation_text = "Free Objective" + return target + +/datum/objective/assassinate/check_completion() + if(target && target.current) + if(target.current.stat == DEAD || iszombie(target)) + return 1 + if(issilicon(target.current) || isbrain(target.current)) //Borgs/brains/AIs count as dead for traitor objectives. --NeoFite + return 1 + if(!target.current.ckey) + return 1 + return 0 + return 1 + + +/datum/objective/mutiny + martyr_compatible = 1 + +/datum/objective/mutiny/find_target() + ..() + if(target && target.current) + explanation_text = "Assassinate [target.current.real_name], the [target.assigned_role]." + else + explanation_text = "Free Objective" + return target + +/datum/objective/mutiny/check_completion() + if(target && target.current) + if(target.current.stat == DEAD || !ishuman(target.current) || !target.current.ckey || !target.current.client) + return 1 + var/turf/T = get_turf(target.current) + if(T && !is_station_level(T.z)) //If they leave the station they count as dead for this + return 1 + return 0 + return 1 + +/datum/objective/maroon + martyr_compatible = 1 + +/datum/objective/maroon/find_target() + ..() + if(target && target.current) + explanation_text = "Prevent [target.current.real_name], the [target.assigned_role] from escaping alive." + else + explanation_text = "Free Objective" + return target + +/datum/objective/maroon/check_completion() + if(target && target.current) + if(target.current.stat == DEAD || iszombie(target)) + return 1 + if(!target.current.ckey) + return 1 + if(issilicon(target.current)) + return 1 + if(isbrain(target.current)) + return 1 + var/turf/T = get_turf(target.current) + if(is_admin_level(T.z)) + return 0 + return 1 + return 1 + + +/datum/objective/debrain //I want braaaainssss + martyr_compatible = 0 + +/datum/objective/debrain/find_target() + ..() + if(target && target.current) + explanation_text = "Steal the brain of [target.current.real_name] the [target.assigned_role]." + else + explanation_text = "Free Objective" + return target + + +/datum/objective/debrain/check_completion() + if(!target)//If it's a free objective. + return 1 + if(!owner.current || owner.current.stat == DEAD) + return 0 + if(!target.current || !isbrain(target.current)) + return 0 + var/atom/A = target.current + while(A.loc) //check to see if the brainmob is on our person + A = A.loc + if(A == owner.current) + return 1 + return 0 + + +/datum/objective/protect //The opposite of killing a dude. + martyr_compatible = 1 + +/datum/objective/protect/find_target() + ..() + if(target && target.current) + explanation_text = "Protect [target.current.real_name], the [target.assigned_role]." + else + explanation_text = "Free Objective" + return target + +/datum/objective/protect/check_completion() + if(!target) //If it's a free objective. + return 1 + if(target.current) + if(target.current.stat == DEAD || iszombie(target)) + return 0 + if(issilicon(target.current)) + return 0 + if(isbrain(target.current)) + return 0 + return 1 + return 0 + +/datum/objective/protect/mindslave //subytpe for mindslave implants + + +/datum/objective/hijack + martyr_compatible = 0 //Technically you won't get both anyway. + explanation_text = "Hijack the shuttle by escaping on it with no loyalist Nanotrasen crew on board and free. \ + Syndicate agents, other enemies of Nanotrasen, cyborgs, pets, and cuffed/restrained hostages may be allowed on the shuttle alive." + +/datum/objective/hijack/check_completion() + if(!owner.current || owner.current.stat) + return 0 + if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) + return 0 + if(issilicon(owner.current)) + return 0 + + var/area/A = get_area(owner.current) + if(SSshuttle.emergency.areaInstance != A) + return 0 + + return SSshuttle.emergency.is_hijacked() + +/datum/objective/hijackclone + explanation_text = "Hijack the shuttle by ensuring only you (or your copies) escape." + martyr_compatible = 0 + +/datum/objective/hijackclone/check_completion() + if(!owner.current) + return 0 + if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) + return 0 + + var/area/A = SSshuttle.emergency.areaInstance + + for(var/mob/living/player in GLOB.player_list) //Make sure nobody else is onboard + if(player.mind && player.mind != owner) + if(player.stat != DEAD) + if(issilicon(player)) + continue + if(get_area(player) == A) + if(player.real_name != owner.current.real_name && !istype(get_turf(player.mind.current), /turf/simulated/shuttle/floor4)) + return 0 + + for(var/mob/living/player in GLOB.player_list) //Make sure at least one of you is onboard + if(player.mind && player.mind != owner) + if(player.stat != DEAD) + if(issilicon(player)) + continue + if(get_area(player) == A) + if(player.real_name == owner.current.real_name && !istype(get_turf(player.mind.current), /turf/simulated/shuttle/floor4)) + return 1 + return 0 + +/datum/objective/block + explanation_text = "Do not allow any lifeforms, be it organic or synthetic to escape on the shuttle alive. AIs, Cyborgs, and pAIs are not considered alive." + martyr_compatible = 1 + +/datum/objective/block/check_completion() + if(!istype(owner.current, /mob/living/silicon)) + return 0 + if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) + return 0 + if(!owner.current) + return 0 + + var/area/A = SSshuttle.emergency.areaInstance + var/list/protected_mobs = list(/mob/living/silicon/ai, /mob/living/silicon/pai, /mob/living/silicon/robot) + + for(var/mob/living/player in GLOB.player_list) + if(player.type in protected_mobs) + continue + + if(player.mind && player.stat != DEAD) + if(get_area(player) == A) + return 0 + + return 1 + +/datum/objective/escape + explanation_text = "Escape on the shuttle or an escape pod alive and free." + +/datum/objective/escape/check_completion() + if(issilicon(owner.current)) + return 0 + if(isbrain(owner.current)) + return 0 + if(!owner.current || owner.current.stat == DEAD || iszombie(owner)) + return 0 + if(SSticker.force_ending) //This one isn't their fault, so lets just assume good faith + return 1 + if(SSticker.mode.station_was_nuked) //If they escaped the blast somehow, let them win + return 1 + if(SSshuttle.emergency.mode < SHUTTLE_ENDGAME) + return 0 + var/turf/location = get_turf(owner.current) + if(!location) + return 0 + + if(istype(location, /turf/simulated/shuttle/floor4) || istype(location, /turf/simulated/floor/mineral/plastitanium/red/brig)) // Fails traitors if they are in the shuttle brig -- Polymorph + return 0 + + if(location.onCentcom() || location.onSyndieBase()) + return 1 + + return 0 + + +/datum/objective/escape/escape_with_identity + var/target_real_name // Has to be stored because the target's real_name can change over the course of the round + +/datum/objective/escape/escape_with_identity/find_target() + var/list/possible_targets = list() //Copypasta because NO_DNA races, yay for snowflakes. + for(var/datum/mind/possible_target in SSticker.minds) + if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != DEAD) && possible_target.current.client) + var/mob/living/carbon/human/H = possible_target.current + if(!(NO_DNA in H.dna.species.species_traits)) + possible_targets += possible_target + if(possible_targets.len > 0) + target = pick(possible_targets) + if(target && target.current) + target_real_name = target.current.real_name + explanation_text = "Escape on the shuttle or an escape pod with the identity of [target_real_name], the [target.assigned_role] while wearing [target.p_their()] identification card." + else + explanation_text = "Free Objective" + +/datum/objective/escape/escape_with_identity/check_completion() + if(!target_real_name) + return 1 + if(!ishuman(owner.current)) + return 0 + var/mob/living/carbon/human/H = owner.current + if(..()) + if(H.dna.real_name == target_real_name) + if(H.get_id_name()== target_real_name) + return 1 + return 0 + +/datum/objective/die + explanation_text = "Die a glorious death." + +/datum/objective/die/check_completion() + if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current) || iszombie(owner)) + return 1 + if(issilicon(owner.current) && owner.current != owner.original) + return 1 + return 0 + + + +/datum/objective/survive + explanation_text = "Stay alive until the end." + +/datum/objective/survive/check_completion() + if(!owner.current || owner.current.stat == DEAD || isbrain(owner.current)) + return 0 //Brains no longer win survive objectives. --NEO + if(issilicon(owner.current) && owner.current != owner.original) + return 0 + return 1 + +/datum/objective/nuclear + explanation_text = "Destroy the station with a nuclear device." + martyr_compatible = 1 + +/datum/objective/steal + var/datum/theft_objective/steal_target + martyr_compatible = 0 + var/theft_area + +/datum/objective/steal/proc/get_location() + if(steal_target.location_override) + return steal_target.location_override + var/list/obj/item/steal_candidates = get_all_of_type(steal_target.typepath, subtypes = TRUE) + for(var/obj/item/candidate in steal_candidates) + if(!is_admin_level(candidate.loc.z)) + theft_area = get_area(candidate.loc) + return "[theft_area]" + return "an unknown area" + +/datum/objective/steal/find_target() + var/loop=50 + while(!steal_target && loop > 0) + loop-- + var/thefttype = pick(potential_theft_objectives) + var/datum/theft_objective/O = new thefttype + if(owner.assigned_role in O.protected_jobs) + continue + if(O in owner.targets) + continue + if(O.flags & 2) + continue + steal_target = O + + explanation_text = "Steal [steal_target]. One was last seen in [get_location()]. " + if(islist(O.protected_jobs) && O.protected_jobs.len) + explanation_text += "It may also be in the possession of the [jointext(O.protected_jobs, ", ")]." + return + explanation_text = "Free Objective." + + +/datum/objective/steal/proc/select_target() + var/list/possible_items_all = potential_theft_objectives+"custom" + var/new_target = input("Select target:", "Objective target", null) as null|anything in possible_items_all + if(!new_target) return + if(new_target == "custom") + var/datum/theft_objective/O=new + O.typepath = input("Select type:","Type") as null|anything in typesof(/obj/item) + if(!O.typepath) return + var/tmp_obj = new O.typepath + var/custom_name = tmp_obj:name + qdel(tmp_obj) + O.name = sanitize(copytext(input("Enter target name:", "Objective target", custom_name) as text|null,1,MAX_NAME_LEN)) + if(!O.name) return + steal_target = O + explanation_text = "Steal [O.name]." + else + steal_target = new new_target + explanation_text = "Steal [steal_target.name]." + return steal_target + +/datum/objective/steal/check_completion() + if(!steal_target) + return 1 // Free Objective + + var/list/all_items = owner.current.GetAllContents() + + for(var/obj/I in all_items) + if(istype(I, steal_target.typepath)) + return steal_target.check_special_completion(I) + if(I.type in steal_target.altitems) + return steal_target.check_special_completion(I) + + +/datum/objective/steal/exchange + martyr_compatible = 0 + +/datum/objective/steal/exchange/proc/set_faction(var/faction,var/otheragent) + target = otheragent + var/datum/theft_objective/unique/targetinfo + if(faction == "red") + targetinfo = new /datum/theft_objective/unique/docs_blue + else if(faction == "blue") + targetinfo = new /datum/theft_objective/unique/docs_red + explanation_text = "Acquire [targetinfo.name] held by [target.current.real_name], the [target.assigned_role] and syndicate agent" + steal_target = targetinfo + +/datum/objective/steal/exchange/backstab +/datum/objective/steal/exchange/backstab/set_faction(var/faction) + var/datum/theft_objective/unique/targetinfo + if(faction == "red") + targetinfo = new /datum/theft_objective/unique/docs_red + else if(faction == "blue") + targetinfo = new /datum/theft_objective/unique/docs_blue + explanation_text = "Do not give up or lose [targetinfo.name]." + steal_target = targetinfo + +/datum/objective/download +/datum/objective/download/proc/gen_amount_goal() + target_amount = rand(10,20) + explanation_text = "Download [target_amount] research levels." + return target_amount + + +/datum/objective/download/check_completion() + return 0 + + + +/datum/objective/capture +/datum/objective/capture/proc/gen_amount_goal() + target_amount = rand(5,10) + explanation_text = "Accumulate [target_amount] capture points." + return target_amount + + +/datum/objective/capture/check_completion()//Basically runs through all the mobs in the area to determine how much they are worth. + return 0 + + + + +/datum/objective/absorb +/datum/objective/absorb/proc/gen_amount_goal(var/lowbound = 4, var/highbound = 6) + target_amount = rand (lowbound,highbound) + if(SSticker) + var/n_p = 1 //autowin + if(SSticker.current_state == GAME_STATE_SETTING_UP) + for(var/mob/new_player/P in GLOB.player_list) + if(P.client && P.ready && P.mind != owner) + if(P.client.prefs && (P.client.prefs.species == "Machine")) // Special check for species that can't be absorbed. No better solution. + continue + n_p++ + else if(SSticker.current_state == GAME_STATE_PLAYING) + for(var/mob/living/carbon/human/P in GLOB.player_list) + if(NO_DNA in P.dna.species.species_traits) + continue + if(P.client && !(P.mind in SSticker.mode.changelings) && P.mind!=owner) + n_p++ + target_amount = min(target_amount, n_p) + + explanation_text = "Absorb [target_amount] compatible genomes." + return target_amount + +/datum/objective/absorb/check_completion() + if(owner && owner.changeling && owner.changeling.absorbed_dna && (owner.changeling.absorbedcount >= target_amount)) + return 1 + else + return 0 + +/datum/objective/destroy + martyr_compatible = 1 + var/target_real_name + +/datum/objective/destroy/find_target() + var/list/possible_targets = active_ais(1) + var/mob/living/silicon/ai/target_ai = pick(possible_targets) + target = target_ai.mind + if(target && target.current) + target_real_name = target.current.real_name + explanation_text = "Destroy [target_real_name], the AI." + else + explanation_text = "Free Objective" + return target + +/datum/objective/destroy/check_completion() + if(target && target.current) + if(target.current.stat == DEAD || is_away_level(target.current.z) || !target.current.ckey) + return 1 + return 0 + return 1 + +/datum/objective/steal_five_of_type + explanation_text = "Steal at least five items!" + var/list/wanted_items = list() + +/datum/objective/steal_five_of_type/New() + ..() + wanted_items = typecacheof(wanted_items) + +/datum/objective/steal_five_of_type/check_completion() + var/stolen_count = 0 + if(!isliving(owner.current)) + return FALSE + var/list/all_items = owner.current.GetAllContents() //this should get things in cheesewheels, books, etc. + for(var/obj/I in all_items) //Check for wanted items + if(is_type_in_typecache(I, wanted_items)) + stolen_count++ + return stolen_count >= 5 + +/datum/objective/steal_five_of_type/summon_guns + explanation_text = "Steal at least five guns!" + wanted_items = list(/obj/item/gun) + +/datum/objective/steal_five_of_type/summon_magic + explanation_text = "Steal at least five magical artefacts!" + wanted_items = list() + +/datum/objective/steal_five_of_type/summon_magic/New() + wanted_items = GLOB.summoned_magic_objectives + ..() + +/datum/objective/steal_five_of_type/summon_magic/check_completion() + var/stolen_count = 0 + if(!isliving(owner.current)) + return FALSE + var/list/all_items = owner.current.GetAllContents() //this should get things in cheesewheels, books, etc. + for(var/obj/I in all_items) //Check for wanted items + if(istype(I, /obj/item/spellbook) && !istype(I, /obj/item/spellbook/oneuse)) + var/obj/item/spellbook/spellbook = I + if(spellbook.uses) //if the book still has powers... + stolen_count++ //it counts. nice. + if(istype(I, /obj/item/spellbook/oneuse)) + var/obj/item/spellbook/oneuse/oneuse = I + if(!oneuse.used) + stolen_count++ + else if(is_type_in_typecache(I, wanted_items)) + stolen_count++ + return stolen_count >= 5 + +/datum/objective/blood +/datum/objective/blood/proc/gen_amount_goal(low = 150, high = 400) + target_amount = rand(low,high) + target_amount = round(round(target_amount/5)*5) + explanation_text = "Accumulate at least [target_amount] total units of blood." + return target_amount + +/datum/objective/blood/check_completion() + if(owner && owner.vampire && owner.vampire.bloodtotal && owner.vampire.bloodtotal >= target_amount) + return 1 + else + return 0 + +// /vg/; Vox Inviolate for humans :V +/datum/objective/minimize_casualties + explanation_text = "Minimise casualties." +/datum/objective/minimize_casualties/check_completion() + if(owner.kills.len>5) return 0 + return 1 + +//Vox heist objectives. + +/datum/objective/heist +/datum/objective/heist/proc/choose_target() + return + +/datum/objective/heist/kidnap +/datum/objective/heist/kidnap/choose_target() + var/list/roles = list("Chief Engineer","Research Director","Roboticist","Chemist","Station Engineer") + var/list/possible_targets = list() + var/list/priority_targets = list() + + for(var/datum/mind/possible_target in SSticker.minds) + if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != DEAD) && (possible_target.assigned_role != possible_target.special_role) && !possible_target.offstation_role) + possible_targets += possible_target + for(var/role in roles) + if(possible_target.assigned_role == role) + priority_targets += possible_target + continue + + if(priority_targets.len > 0) + target = pick(priority_targets) + else if(possible_targets.len > 0) + target = pick(possible_targets) + + if(target && target.current) + explanation_text = "The Shoal has a need for [target.current.real_name], the [target.assigned_role]. Take [target.current.p_them()] alive." + else + explanation_text = "Free Objective" + return target + +/datum/objective/heist/kidnap/check_completion() + if(target && target.current) + if(target.current.stat == DEAD) + return 0 + + var/area/shuttle/vox/A = locate() //stupid fucking hardcoding + var/area/vox_station/B = locate() //but necessary + + for(var/mob/living/carbon/human/M in A) + if(target.current == M) + return 1 + for(var/mob/living/carbon/human/M in B) + if(target.current == M) + return 1 + else + return 0 + +/datum/objective/heist/loot +/datum/objective/heist/loot/choose_target() + var/loot = "an object" + switch(rand(1,8)) + if(1) + target = /obj/structure/particle_accelerator + target_amount = 6 + loot = "a complete particle accelerator" + if(2) + target = /obj/machinery/the_singularitygen + target_amount = 1 + loot = "a gravitational singularity generator" + if(3) + target = /obj/machinery/power/emitter + target_amount = 4 + loot = "four emitters" + if(4) + target = /obj/machinery/nuclearbomb + target_amount = 1 + loot = "a nuclear bomb" + if(5) + target = /obj/item/gun + target_amount = 6 + loot = "six guns. Tasers and other non-lethal guns are acceptable" + if(6) + target = /obj/item/gun/energy + target_amount = 4 + loot = "four energy guns" + if(7) + target = /obj/item/gun/energy/laser + target_amount = 2 + loot = "two laser guns" + if(8) + target = /obj/item/gun/energy/ionrifle + target_amount = 1 + loot = "an ion gun" + + explanation_text = "We are lacking in hardware. Steal or trade [loot]." + +/datum/objective/heist/loot/check_completion() + var/total_amount = 0 + + for(var/obj/O in locate(/area/shuttle/vox)) + if(istype(O, target)) + total_amount++ + for(var/obj/I in O.contents) + if(istype(I, target)) + total_amount++ + if(total_amount >= target_amount) + return 1 + + for(var/obj/O in locate(/area/vox_station)) + if(istype(O, target)) + total_amount++ + for(var/obj/I in O.contents) + if(istype(I, target)) + total_amount++ + if(total_amount >= target_amount) + return 1 + + var/datum/game_mode/heist/H = SSticker.mode + for(var/datum/mind/raider in H.raiders) + if(raider.current) + for(var/obj/O in raider.current.get_contents()) + if(istype(O,target)) + total_amount++ + if(total_amount >= target_amount) + return 1 + + return 0 + +/datum/objective/heist/salvage +/datum/objective/heist/salvage/choose_target() + switch(rand(1,8)) + if(1) + target = "metal" + target_amount = 300 + if(2) + target = "glass" + target_amount = 200 + if(3) + target = "plasteel" + target_amount = 100 + if(4) + target = "solid plasma" + target_amount = 100 + if(5) + target = "silver" + target_amount = 50 + if(6) + target = "gold" + target_amount = 20 + if(7) + target = "uranium" + target_amount = 20 + if(8) + target = "diamond" + target_amount = 20 + + explanation_text = "Ransack or trade with the station and escape with [target_amount] [target]." + +/datum/objective/heist/salvage/check_completion() + var/total_amount = 0 + + for(var/obj/item/O in locate(/area/shuttle/vox)) + var/obj/item/stack/sheet/S + if(istype(O,/obj/item/stack/sheet)) + if(O.name == target) + S = O + total_amount += S.get_amount() + + for(var/obj/I in O.contents) + if(istype(I,/obj/item/stack/sheet)) + if(I.name == target) + S = I + total_amount += S.get_amount() + + for(var/obj/item/O in locate(/area/vox_station)) + var/obj/item/stack/sheet/S + if(istype(O,/obj/item/stack/sheet)) + if(O.name == target) + S = O + total_amount += S.get_amount() + + for(var/obj/I in O.contents) + if(istype(I,/obj/item/stack/sheet)) + if(I.name == target) + S = I + total_amount += S.get_amount() + + var/datum/game_mode/heist/H = SSticker.mode + for(var/datum/mind/raider in H.raiders) + if(raider.current) + for(var/obj/item/O in raider.current.get_contents()) + if(istype(O,/obj/item/stack/sheet)) + if(O.name == target) + var/obj/item/stack/sheet/S = O + total_amount += S.get_amount() + + if(total_amount >= target_amount) return 1 + return 0 + + +/datum/objective/heist/inviolate_crew + explanation_text = "Do not leave any Vox behind, alive or dead." + +/datum/objective/heist/inviolate_crew/check_completion() + var/datum/game_mode/heist/H = SSticker.mode + if(H.is_raider_crew_safe()) + return 1 + return 0 + +/datum/objective/heist/inviolate_death + explanation_text = "Follow the Inviolate. Minimise death and loss of resources." + +/datum/objective/heist/inviolate_death/check_completion() + var/vox_allowed_kills = 3 // The number of people the vox can accidently kill. Mostly a counter to people killing themselves if a raider touches them to force fail. + var/vox_total_kills = 0 + + var/datum/game_mode/heist/H = SSticker.mode + for(var/datum/mind/raider in H.raiders) + vox_total_kills += raider.kills.len // Kills are listed in the mind; uses this to calculate vox kills + + if(vox_total_kills > vox_allowed_kills) return 0 + return 1 + + +// Traders +// These objectives have no check_completion, they exist only to tell Sol Traders what to aim for. + +/datum/objective/trade/proc/choose_target() + return + +/datum/objective/trade/plasma/choose_target() + explanation_text = "Acquire at least 15 sheets of plasma through trade." + +/datum/objective/trade/credits/choose_target() + explanation_text = "Acquire at least 10,000 credits through trade." + +//wizard + +/datum/objective/wizchaos + explanation_text = "Wreak havoc upon the station as much you can. Send those wandless Nanotrasen scum a message!" + completed = 1 diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm index 9f7e14ccef2..ac0b3705198 100644 --- a/code/game/gamemodes/revolution/revolution.dm +++ b/code/game/gamemodes/revolution/revolution.dm @@ -1,449 +1,449 @@ -// To add a rev to the list of revolutionaries, make sure it's rev (with if(ticker.mode.name == "revolution)), -// then call ticker.mode:add_revolutionary(_THE_PLAYERS_MIND_) -// nothing else needs to be done, as that proc will check if they are a valid target. -// Just make sure the converter is a head before you call it! -// To remove a rev (from brainwashing or w/e), call ticker.mode:remove_revolutionary(_THE_PLAYERS_MIND_), -// this will also check they're not a head, so it can just be called freely -// If the game somtimes isn't registering a win properly, then ticker.mode.check_win() isn't being called somewhere. - -/datum/game_mode - var/list/datum/mind/head_revolutionaries = list() - var/list/datum/mind/revolutionaries = list() - -/datum/game_mode/revolution - name = "revolution" - config_tag = "revolution" - restricted_jobs = list("Security Officer", "Warden", "Detective", "Internal Affairs Agent", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician") - required_players = 20 - required_enemies = 1 - recommended_enemies = 3 - - var/finished = 0 - var/check_counter = 0 - var/max_headrevs = 3 - var/list/datum/mind/heads_to_kill = list() - var/list/possible_revolutionaries = list() - -/////////////////////////// -//Announces the game type// -/////////////////////////// -/datum/game_mode/revolution/announce() - to_chat(world, "The current game mode is - Revolution!") - to_chat(world, "Some crewmembers are attempting to start a revolution!
    \nRevolutionaries - Kill the Captain, HoP, HoS, CE, RD and CMO. Convert other crewmembers (excluding the heads of staff, and security officers) to your cause by flashing them. Protect your leaders.
    \nPersonnel - Protect the heads of staff. Kill the leaders of the revolution, and brainwash the other revolutionaries (by beating them in the head).
    ") - - -/////////////////////////////////////////////////////////////////////////////// -//Gets the round setup, cancelling if there's not enough players at the start// -/////////////////////////////////////////////////////////////////////////////// -/datum/game_mode/revolution/pre_setup() - possible_revolutionaries = get_players_for_role(ROLE_REV) - - if(config.protect_roles_from_antagonist) - restricted_jobs += protected_jobs - - - for(var/i=1 to max_headrevs) - if(possible_revolutionaries.len==0) - break - var/datum/mind/lenin = pick(possible_revolutionaries) - possible_revolutionaries -= lenin - head_revolutionaries += lenin - lenin.restricted_roles = restricted_jobs - - if(head_revolutionaries.len < required_enemies) - return FALSE - - return TRUE - - -/datum/game_mode/revolution/post_setup() - var/list/heads = get_living_heads() - var/list/sec = get_living_sec() - var/weighted_score = min(max(round(heads.len - ((8 - sec.len) / 3)),1),max_headrevs) - - while(weighted_score < head_revolutionaries.len) //das vi danya - var/datum/mind/trotsky = pick(head_revolutionaries) - possible_revolutionaries += trotsky - head_revolutionaries -= trotsky - update_rev_icons_removed(trotsky) - - for(var/datum/mind/rev_mind in head_revolutionaries) - log_game("[key_name(rev_mind)] has been selected as a head rev") - for(var/datum/mind/head_mind in heads) - mark_for_death(rev_mind, head_mind) - - addtimer(CALLBACK(src, .proc/equip_revolutionary, rev_mind.current), rand(10, 100)) - - for(var/datum/mind/rev_mind in head_revolutionaries) - greet_revolutionary(rev_mind) - modePlayer += head_revolutionaries - if(SSshuttle) - SSshuttle.emergencyNoEscape = 1 - ..() - - -/datum/game_mode/revolution/process() - check_counter++ - if(check_counter >= 5) - if(!finished) - check_heads() - SSticker.mode.check_win() - check_counter = 0 - return FALSE - - -/datum/game_mode/proc/forge_revolutionary_objectives(datum/mind/rev_mind) - var/list/heads = get_living_heads() - for(var/datum/mind/head_mind in heads) - var/datum/objective/mutiny/rev_obj = new - rev_obj.owner = rev_mind - rev_obj.target = head_mind - rev_obj.explanation_text = "Assassinate or exile [head_mind.name], the [head_mind.assigned_role]." - rev_mind.objectives += rev_obj - -/datum/game_mode/proc/greet_revolutionary(datum/mind/rev_mind, you_are=1) - var/obj_count = 1 - update_rev_icons_added(rev_mind) - if(you_are) - to_chat(rev_mind.current, "You are a member of the revolutionaries' leadership!") - for(var/datum/objective/objective in rev_mind.objectives) - to_chat(rev_mind.current, "Objective #[obj_count]: [objective.explanation_text]") - rev_mind.special_role = SPECIAL_ROLE_HEAD_REV - obj_count++ - -///////////////////////////////////////////////////////////////////////////////// -//This are equips the rev heads with their gear, and makes the clown not clumsy// -///////////////////////////////////////////////////////////////////////////////// -/datum/game_mode/proc/equip_revolutionary(mob/living/carbon/human/mob) - if(!istype(mob)) - return - - if(mob.mind) - if(mob.mind.assigned_role == "Clown") - to_chat(mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") - mob.mutations.Remove(CLUMSY) - var/datum/action/innate/toggle_clumsy/A = new - A.Grant(mob) - - var/obj/item/flash/T = new(mob) - var/obj/item/toy/crayon/spraycan/R = new(mob) - var/obj/item/clothing/glasses/hud/security/chameleon/C = new(mob) - - var/list/slots = list ( - "backpack" = slot_in_backpack, - "left pocket" = slot_l_store, - "right pocket" = slot_r_store, - "left hand" = slot_l_hand, - "right hand" = slot_r_hand, - ) - var/where = mob.equip_in_one_of_slots(T, slots) - var/where2 = mob.equip_in_one_of_slots(C, slots) - mob.equip_in_one_of_slots(R,slots) - - mob.update_icons() - - if(!where2) - to_chat(mob, "The Syndicate were unfortunately unable to get you a chameleon security HUD.") - else - to_chat(mob, "The chameleon security HUD in your [where2] will help you keep track of who is mindshield-implanted, and unable to be recruited.") - - if(!where) - to_chat(mob, "The Syndicate were unfortunately unable to get you a flash.") - else - to_chat(mob, "The flash in your [where] will help you to persuade the crew to join your cause.") - return 1 - -///////////////////////////////// -//Gives head revs their targets// -///////////////////////////////// -/datum/game_mode/revolution/proc/mark_for_death(datum/mind/rev_mind, datum/mind/head_mind) - var/datum/objective/mutiny/rev_obj = new - rev_obj.owner = rev_mind - rev_obj.target = head_mind - rev_obj.explanation_text = "Assassinate [head_mind.name], the [head_mind.assigned_role]." - rev_mind.objectives += rev_obj - heads_to_kill += head_mind - -//////////////////////////////////////////// -//Checks if new heads have joined midround// -//////////////////////////////////////////// -/datum/game_mode/revolution/proc/check_heads() - var/list/heads = get_all_heads() - var/list/sec = get_all_sec() - if(heads_to_kill.len < heads.len) - var/list/new_heads = heads - heads_to_kill - for(var/datum/mind/head_mind in new_heads) - for(var/datum/mind/rev_mind in head_revolutionaries) - mark_for_death(rev_mind, head_mind) - - if(head_revolutionaries.len < max_headrevs && head_revolutionaries.len < round(heads.len - ((8 - sec.len) / 3))) - latejoin_headrev() - -/////////////////////////////// -//Adds a new headrev midround// -/////////////////////////////// -/datum/game_mode/revolution/proc/latejoin_headrev() - if(revolutionaries) //Head Revs are not in this list - var/list/promotable_revs = list() - for(var/datum/mind/khrushchev in revolutionaries) - if(khrushchev.current && khrushchev.current.client && khrushchev.current.stat != DEAD) - if(ROLE_REV in khrushchev.current.client.prefs.be_special) - promotable_revs += khrushchev - if(promotable_revs.len) - var/datum/mind/stalin = pick(promotable_revs) - revolutionaries -= stalin - head_revolutionaries += stalin - log_game("[key_name(stalin)] has been promoted to a head rev") - equip_revolutionary(stalin.current) - forge_revolutionary_objectives(stalin) - greet_revolutionary(stalin) - -////////////////////////////////////// -//Checks if the revs have won or not// -////////////////////////////////////// -/datum/game_mode/revolution/check_win() - if(check_rev_victory()) - finished = 1 - else if(check_heads_victory()) - finished = 2 - return - -/////////////////////////////// -//Checks if the round is over// -/////////////////////////////// -/datum/game_mode/revolution/check_finished() - if(config.continuous_rounds) - if(finished != 0) - SSshuttle.emergencyNoEscape = 0 - if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) - SSshuttle.emergency.mode = SHUTTLE_DOCKED - SSshuttle.emergency.timer = world.time - command_announcement.Announce("Hostile enviroment resolved. You have 3 minutes to board the Emergency Shuttle.", null, 'sound/AI/shuttledock.ogg') - return ..() - if(finished != 0) - return TRUE - else - return ..() - -/////////////////////////////////////////////////// -//Deals with converting players to the revolution// -/////////////////////////////////////////////////// -/datum/game_mode/proc/add_revolutionary(datum/mind/rev_mind) - if(rev_mind.assigned_role in command_positions) - return 0 - var/mob/living/carbon/human/H = rev_mind.current//Check to see if the potential rev is implanted - if(ismindshielded(H)) - return 0 - if((rev_mind in revolutionaries) || (rev_mind in head_revolutionaries)) - return 0 - revolutionaries += rev_mind - if(iscarbon(rev_mind.current)) - var/mob/living/carbon/carbon_mob = rev_mind.current - carbon_mob.Silence(5) - carbon_mob.flash_eyes(1, 1) - 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.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)) - replace_jobbanned_player(rev_mind.current, ROLE_REV) - return 1 -////////////////////////////////////////////////////////////////////////////// -//Deals with players being converted from the revolution (Not a rev anymore)// // Modified to handle borged MMIs. Accepts another var if the target is being borged at the time -- Polymorph. -////////////////////////////////////////////////////////////////////////////// -/datum/game_mode/proc/remove_revolutionary(datum/mind/rev_mind , beingborged) - var/remove_head = 0 - if(beingborged && (rev_mind in head_revolutionaries)) - head_revolutionaries -= rev_mind - remove_head = 1 - - if((rev_mind in revolutionaries) || remove_head) - revolutionaries -= rev_mind - rev_mind.special_role = null - rev_mind.current.create_attack_log("Has 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.") - - else - rev_mind.current.Paralyse(5) - to_chat(rev_mind.current, "You have been brainwashed! You are no longer a revolutionary! Your memory is hazy from the time you were a rebel...the only thing you remember is the name of the one who brainwashed you...") - - update_rev_icons_removed(rev_mind) - for(var/mob/living/M in view(rev_mind.current)) - if(beingborged) - to_chat(M, "The frame beeps contentedly, purging the hostile memory engram from the MMI before initalizing it.") - - else - to_chat(M, "[rev_mind.current] looks like [rev_mind.current.p_they()] just remembered [rev_mind.current.p_their()] real allegiance!") - -///////////////////////////////////// -//Adds the rev hud to a new convert// -///////////////////////////////////// -/datum/game_mode/proc/update_rev_icons_added(datum/mind/rev_mind) - var/datum/atom_hud/antag/revhud = huds[ANTAG_HUD_REV] - revhud.join_hud(rev_mind.current) - set_antag_hud(rev_mind.current, ((rev_mind in head_revolutionaries) ? "hudheadrevolutionary" : "hudrevolutionary")) - -///////////////////////////////////////// -//Removes the hud from deconverted revs// -///////////////////////////////////////// -/datum/game_mode/proc/update_rev_icons_removed(datum/mind/rev_mind) - var/datum/atom_hud/antag/revhud = huds[ANTAG_HUD_REV] - revhud.leave_hud(rev_mind.current) - set_antag_hud(rev_mind.current, null) - -////////////////////////// -//Checks for rev victory// -////////////////////////// -/datum/game_mode/revolution/proc/check_rev_victory() - for(var/datum/mind/rev_mind in head_revolutionaries) - for(var/datum/objective/mutiny/objective in rev_mind.objectives) - if(!(objective.check_completion())) - return FALSE - - return TRUE - -///////////////////////////// -//Checks for a head victory// -///////////////////////////// -/datum/game_mode/revolution/proc/check_heads_victory() - for(var/datum/mind/rev_mind in head_revolutionaries) - var/turf/T = get_turf(rev_mind.current) - if((rev_mind) && (rev_mind.current) && (rev_mind.current.stat != DEAD) && rev_mind.current.client && T && is_station_level(T.z)) - if(ishuman(rev_mind.current)) - return FALSE - return TRUE - -////////////////////////////////////////////////////////////////////// -//Announces the end of the game with all relavent information stated// -////////////////////////////////////////////////////////////////////// -/datum/game_mode/revolution/declare_completion() - if(finished == 1) - feedback_set_details("round_end_result","revolution win - heads killed") - to_chat(world, "The heads of staff were killed or exiled! The revolutionaries win!") - else if(finished == 2) - feedback_set_details("round_end_result","revolution loss - rev heads killed") - to_chat(world, "The heads of staff managed to stop the revolution!") - ..() - return TRUE - -/datum/game_mode/proc/auto_declare_completion_revolution() - var/list/targets = list() - if(head_revolutionaries.len || GAMEMODE_IS_REVOLUTION) - var/num_revs = 0 - var/num_survivors = 0 - for(var/mob/living/carbon/survivor in GLOB.living_mob_list) - if(survivor.ckey) - num_survivors++ - if(survivor.mind) - if((survivor.mind in head_revolutionaries) || (survivor.mind in revolutionaries)) - num_revs++ - if(num_survivors) - to_chat(world, "[TAB]Command's Approval Rating: [100 - round((num_revs/num_survivors)*100, 0.1)]%") // % of loyal crew - var/text = "
    The head revolutionaries were:" - for(var/datum/mind/headrev in head_revolutionaries) - text += printplayer(headrev, 1) - text += "
    " - to_chat(world, text) - - if(revolutionaries.len || GAMEMODE_IS_REVOLUTION) - var/text = "
    The revolutionaries were:" - for(var/datum/mind/rev in revolutionaries) - text += printplayer(rev, 1) - text += "
    " - to_chat(world, text) - - if( head_revolutionaries.len || revolutionaries.len || GAMEMODE_IS_REVOLUTION ) - var/text = "
    The heads of staff were:" - var/list/heads = get_all_heads() - for(var/datum/mind/head in heads) - var/target = (head in targets) - if(target) - text += "Target" - text += printplayer(head, 1) - text += "
    " - to_chat(world, text) - -/datum/game_mode/revolution/set_scoreboard_gvars() - var/foecount = 0 - for(var/datum/mind/M in SSticker.mode.head_revolutionaries) - foecount++ - if(!M || !M.current) - score_opkilled++ - continue - - if(M.current.stat == DEAD) - score_opkilled++ - - else if(M.current.restrained()) - score_arrested++ - - if(foecount == score_arrested) - score_allarrested = 1 - - for(var/mob/living/carbon/human/player in world) - if(player.mind) - var/role = player.mind.assigned_role - if(role in list("Captain", "Head of Security", "Head of Personnel", "Chief Engineer", "Research Director")) - if(player.stat == DEAD) - score_deadcommand++ - - - var/arrestpoints = score_arrested * 1000 - var/killpoints = score_opkilled * 500 - var/comdeadpts = score_deadcommand * 500 - if(score_traitorswon) - score_crewscore -= 10000 - - score_crewscore += arrestpoints - score_crewscore += killpoints - score_crewscore -= comdeadpts - - -/datum/game_mode/revolution/get_scoreboard_stats() - var/foecount = 0 - var/comcount = 0 - var/revcount = 0 - var/loycount = 0 - for(var/datum/mind/M in SSticker.mode:head_revolutionaries) - if(M.current && M.current.stat != DEAD) - foecount++ - for(var/datum/mind/M in SSticker.mode:revolutionaries) - if(M.current && M.current.stat != DEAD) - revcount++ - for(var/mob/living/carbon/human/player in world) - if(player.mind) - var/role = player.mind.assigned_role - if(role in list("Captain", "Head of Security", "Head of Personnel", "Chief Engineer", "Research Director")) - if(player.stat != DEAD) - comcount++ - else - if(player.mind in SSticker.mode.revolutionaries) continue - loycount++ - - for(var/mob/living/silicon/X in world) - if(X.stat != DEAD) - loycount++ - - - var/dat = "" - - dat += "Mode Statistics
    " - dat += "Number of Surviving Revolution Heads: [foecount]
    " - dat += "Number of Surviving Command Staff: [comcount]
    " - dat += "Number of Surviving Revolutionaries: [revcount]
    " - dat += "Number of Surviving Loyal Crew: [loycount]
    " - - dat += "
    " - dat += "Revolution Heads Arrested: [score_arrested] ([score_arrested * 1000] Points)
    " - dat += "All Revolution Heads Arrested: [score_allarrested ? "Yes" : "No"] (Score tripled)
    " - - dat += "Revolution Heads Slain: [score_opkilled] ([score_opkilled * 500] Points)
    " - dat += "Command Staff Slain: [score_deadcommand] (-[score_deadcommand * 500] Points)
    " - dat += "Revolution Successful: [score_traitorswon ? "Yes" : "No"] (-[score_traitorswon * 10000] Points)
    " - dat += "
    " - - return dat +// To add a rev to the list of revolutionaries, make sure it's rev (with if(ticker.mode.name == "revolution)), +// then call ticker.mode:add_revolutionary(_THE_PLAYERS_MIND_) +// nothing else needs to be done, as that proc will check if they are a valid target. +// Just make sure the converter is a head before you call it! +// To remove a rev (from brainwashing or w/e), call ticker.mode:remove_revolutionary(_THE_PLAYERS_MIND_), +// this will also check they're not a head, so it can just be called freely +// If the game somtimes isn't registering a win properly, then ticker.mode.check_win() isn't being called somewhere. + +/datum/game_mode + var/list/datum/mind/head_revolutionaries = list() + var/list/datum/mind/revolutionaries = list() + +/datum/game_mode/revolution + name = "revolution" + config_tag = "revolution" + restricted_jobs = list("Security Officer", "Warden", "Detective", "Internal Affairs Agent", "AI", "Cyborg","Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Brig Physician") + required_players = 20 + required_enemies = 1 + recommended_enemies = 3 + + var/finished = 0 + var/check_counter = 0 + var/max_headrevs = 3 + var/list/datum/mind/heads_to_kill = list() + var/list/possible_revolutionaries = list() + +/////////////////////////// +//Announces the game type// +/////////////////////////// +/datum/game_mode/revolution/announce() + to_chat(world, "The current game mode is - Revolution!") + to_chat(world, "Some crewmembers are attempting to start a revolution!
    \nRevolutionaries - Kill the Captain, HoP, HoS, CE, RD and CMO. Convert other crewmembers (excluding the heads of staff, and security officers) to your cause by flashing them. Protect your leaders.
    \nPersonnel - Protect the heads of staff. Kill the leaders of the revolution, and brainwash the other revolutionaries (by beating them in the head).
    ") + + +/////////////////////////////////////////////////////////////////////////////// +//Gets the round setup, cancelling if there's not enough players at the start// +/////////////////////////////////////////////////////////////////////////////// +/datum/game_mode/revolution/pre_setup() + possible_revolutionaries = get_players_for_role(ROLE_REV) + + if(config.protect_roles_from_antagonist) + restricted_jobs += protected_jobs + + + for(var/i=1 to max_headrevs) + if(possible_revolutionaries.len==0) + break + var/datum/mind/lenin = pick(possible_revolutionaries) + possible_revolutionaries -= lenin + head_revolutionaries += lenin + lenin.restricted_roles = restricted_jobs + + if(head_revolutionaries.len < required_enemies) + return FALSE + + return TRUE + + +/datum/game_mode/revolution/post_setup() + var/list/heads = get_living_heads() + var/list/sec = get_living_sec() + var/weighted_score = min(max(round(heads.len - ((8 - sec.len) / 3)),1),max_headrevs) + + while(weighted_score < head_revolutionaries.len) //das vi danya + var/datum/mind/trotsky = pick(head_revolutionaries) + possible_revolutionaries += trotsky + head_revolutionaries -= trotsky + update_rev_icons_removed(trotsky) + + for(var/datum/mind/rev_mind in head_revolutionaries) + log_game("[key_name(rev_mind)] has been selected as a head rev") + for(var/datum/mind/head_mind in heads) + mark_for_death(rev_mind, head_mind) + + addtimer(CALLBACK(src, .proc/equip_revolutionary, rev_mind.current), rand(10, 100)) + + for(var/datum/mind/rev_mind in head_revolutionaries) + greet_revolutionary(rev_mind) + modePlayer += head_revolutionaries + if(SSshuttle) + SSshuttle.emergencyNoEscape = 1 + ..() + + +/datum/game_mode/revolution/process() + check_counter++ + if(check_counter >= 5) + if(!finished) + check_heads() + SSticker.mode.check_win() + check_counter = 0 + return FALSE + + +/datum/game_mode/proc/forge_revolutionary_objectives(datum/mind/rev_mind) + var/list/heads = get_living_heads() + for(var/datum/mind/head_mind in heads) + var/datum/objective/mutiny/rev_obj = new + rev_obj.owner = rev_mind + rev_obj.target = head_mind + rev_obj.explanation_text = "Assassinate or exile [head_mind.name], the [head_mind.assigned_role]." + rev_mind.objectives += rev_obj + +/datum/game_mode/proc/greet_revolutionary(datum/mind/rev_mind, you_are=1) + var/obj_count = 1 + update_rev_icons_added(rev_mind) + if(you_are) + to_chat(rev_mind.current, "You are a member of the revolutionaries' leadership!") + for(var/datum/objective/objective in rev_mind.objectives) + to_chat(rev_mind.current, "Objective #[obj_count]: [objective.explanation_text]") + rev_mind.special_role = SPECIAL_ROLE_HEAD_REV + obj_count++ + +///////////////////////////////////////////////////////////////////////////////// +//This are equips the rev heads with their gear, and makes the clown not clumsy// +///////////////////////////////////////////////////////////////////////////////// +/datum/game_mode/proc/equip_revolutionary(mob/living/carbon/human/mob) + if(!istype(mob)) + return + + if(mob.mind) + if(mob.mind.assigned_role == "Clown") + to_chat(mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") + mob.mutations.Remove(CLUMSY) + var/datum/action/innate/toggle_clumsy/A = new + A.Grant(mob) + + var/obj/item/flash/T = new(mob) + var/obj/item/toy/crayon/spraycan/R = new(mob) + var/obj/item/clothing/glasses/hud/security/chameleon/C = new(mob) + + var/list/slots = list ( + "backpack" = slot_in_backpack, + "left pocket" = slot_l_store, + "right pocket" = slot_r_store, + "left hand" = slot_l_hand, + "right hand" = slot_r_hand, + ) + var/where = mob.equip_in_one_of_slots(T, slots) + var/where2 = mob.equip_in_one_of_slots(C, slots) + mob.equip_in_one_of_slots(R,slots) + + mob.update_icons() + + if(!where2) + to_chat(mob, "The Syndicate were unfortunately unable to get you a chameleon security HUD.") + else + to_chat(mob, "The chameleon security HUD in your [where2] will help you keep track of who is mindshield-implanted, and unable to be recruited.") + + if(!where) + to_chat(mob, "The Syndicate were unfortunately unable to get you a flash.") + else + to_chat(mob, "The flash in your [where] will help you to persuade the crew to join your cause.") + return 1 + +///////////////////////////////// +//Gives head revs their targets// +///////////////////////////////// +/datum/game_mode/revolution/proc/mark_for_death(datum/mind/rev_mind, datum/mind/head_mind) + var/datum/objective/mutiny/rev_obj = new + rev_obj.owner = rev_mind + rev_obj.target = head_mind + rev_obj.explanation_text = "Assassinate [head_mind.name], the [head_mind.assigned_role]." + rev_mind.objectives += rev_obj + heads_to_kill += head_mind + +//////////////////////////////////////////// +//Checks if new heads have joined midround// +//////////////////////////////////////////// +/datum/game_mode/revolution/proc/check_heads() + var/list/heads = get_all_heads() + var/list/sec = get_all_sec() + if(heads_to_kill.len < heads.len) + var/list/new_heads = heads - heads_to_kill + for(var/datum/mind/head_mind in new_heads) + for(var/datum/mind/rev_mind in head_revolutionaries) + mark_for_death(rev_mind, head_mind) + + if(head_revolutionaries.len < max_headrevs && head_revolutionaries.len < round(heads.len - ((8 - sec.len) / 3))) + latejoin_headrev() + +/////////////////////////////// +//Adds a new headrev midround// +/////////////////////////////// +/datum/game_mode/revolution/proc/latejoin_headrev() + if(revolutionaries) //Head Revs are not in this list + var/list/promotable_revs = list() + for(var/datum/mind/khrushchev in revolutionaries) + if(khrushchev.current && khrushchev.current.client && khrushchev.current.stat != DEAD) + if(ROLE_REV in khrushchev.current.client.prefs.be_special) + promotable_revs += khrushchev + if(promotable_revs.len) + var/datum/mind/stalin = pick(promotable_revs) + revolutionaries -= stalin + head_revolutionaries += stalin + log_game("[key_name(stalin)] has been promoted to a head rev") + equip_revolutionary(stalin.current) + forge_revolutionary_objectives(stalin) + greet_revolutionary(stalin) + +////////////////////////////////////// +//Checks if the revs have won or not// +////////////////////////////////////// +/datum/game_mode/revolution/check_win() + if(check_rev_victory()) + finished = 1 + else if(check_heads_victory()) + finished = 2 + return + +/////////////////////////////// +//Checks if the round is over// +/////////////////////////////// +/datum/game_mode/revolution/check_finished() + if(config.continuous_rounds) + if(finished != 0) + SSshuttle.emergencyNoEscape = 0 + if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) + SSshuttle.emergency.mode = SHUTTLE_DOCKED + SSshuttle.emergency.timer = world.time + command_announcement.Announce("Hostile enviroment resolved. You have 3 minutes to board the Emergency Shuttle.", null, 'sound/AI/shuttledock.ogg') + return ..() + if(finished != 0) + return TRUE + else + return ..() + +/////////////////////////////////////////////////// +//Deals with converting players to the revolution// +/////////////////////////////////////////////////// +/datum/game_mode/proc/add_revolutionary(datum/mind/rev_mind) + if(rev_mind.assigned_role in command_positions) + return 0 + var/mob/living/carbon/human/H = rev_mind.current//Check to see if the potential rev is implanted + if(ismindshielded(H)) + return 0 + if((rev_mind in revolutionaries) || (rev_mind in head_revolutionaries)) + return 0 + revolutionaries += rev_mind + if(iscarbon(rev_mind.current)) + var/mob/living/carbon/carbon_mob = rev_mind.current + carbon_mob.Silence(5) + carbon_mob.flash_eyes(1, 1) + 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.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)) + replace_jobbanned_player(rev_mind.current, ROLE_REV) + return 1 +////////////////////////////////////////////////////////////////////////////// +//Deals with players being converted from the revolution (Not a rev anymore)// // Modified to handle borged MMIs. Accepts another var if the target is being borged at the time -- Polymorph. +////////////////////////////////////////////////////////////////////////////// +/datum/game_mode/proc/remove_revolutionary(datum/mind/rev_mind , beingborged) + var/remove_head = 0 + if(beingborged && (rev_mind in head_revolutionaries)) + head_revolutionaries -= rev_mind + remove_head = 1 + + if((rev_mind in revolutionaries) || remove_head) + revolutionaries -= rev_mind + rev_mind.special_role = null + rev_mind.current.create_attack_log("Has 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.") + + else + rev_mind.current.Paralyse(5) + to_chat(rev_mind.current, "You have been brainwashed! You are no longer a revolutionary! Your memory is hazy from the time you were a rebel...the only thing you remember is the name of the one who brainwashed you...") + + update_rev_icons_removed(rev_mind) + for(var/mob/living/M in view(rev_mind.current)) + if(beingborged) + to_chat(M, "The frame beeps contentedly, purging the hostile memory engram from the MMI before initalizing it.") + + else + to_chat(M, "[rev_mind.current] looks like [rev_mind.current.p_they()] just remembered [rev_mind.current.p_their()] real allegiance!") + +///////////////////////////////////// +//Adds the rev hud to a new convert// +///////////////////////////////////// +/datum/game_mode/proc/update_rev_icons_added(datum/mind/rev_mind) + var/datum/atom_hud/antag/revhud = huds[ANTAG_HUD_REV] + revhud.join_hud(rev_mind.current) + set_antag_hud(rev_mind.current, ((rev_mind in head_revolutionaries) ? "hudheadrevolutionary" : "hudrevolutionary")) + +///////////////////////////////////////// +//Removes the hud from deconverted revs// +///////////////////////////////////////// +/datum/game_mode/proc/update_rev_icons_removed(datum/mind/rev_mind) + var/datum/atom_hud/antag/revhud = huds[ANTAG_HUD_REV] + revhud.leave_hud(rev_mind.current) + set_antag_hud(rev_mind.current, null) + +////////////////////////// +//Checks for rev victory// +////////////////////////// +/datum/game_mode/revolution/proc/check_rev_victory() + for(var/datum/mind/rev_mind in head_revolutionaries) + for(var/datum/objective/mutiny/objective in rev_mind.objectives) + if(!(objective.check_completion())) + return FALSE + + return TRUE + +///////////////////////////// +//Checks for a head victory// +///////////////////////////// +/datum/game_mode/revolution/proc/check_heads_victory() + for(var/datum/mind/rev_mind in head_revolutionaries) + var/turf/T = get_turf(rev_mind.current) + if((rev_mind) && (rev_mind.current) && (rev_mind.current.stat != DEAD) && rev_mind.current.client && T && is_station_level(T.z)) + if(ishuman(rev_mind.current)) + return FALSE + return TRUE + +////////////////////////////////////////////////////////////////////// +//Announces the end of the game with all relavent information stated// +////////////////////////////////////////////////////////////////////// +/datum/game_mode/revolution/declare_completion() + if(finished == 1) + feedback_set_details("round_end_result","revolution win - heads killed") + to_chat(world, "The heads of staff were killed or exiled! The revolutionaries win!") + else if(finished == 2) + feedback_set_details("round_end_result","revolution loss - rev heads killed") + to_chat(world, "The heads of staff managed to stop the revolution!") + ..() + return TRUE + +/datum/game_mode/proc/auto_declare_completion_revolution() + var/list/targets = list() + if(head_revolutionaries.len || GAMEMODE_IS_REVOLUTION) + var/num_revs = 0 + var/num_survivors = 0 + for(var/mob/living/carbon/survivor in GLOB.living_mob_list) + if(survivor.ckey) + num_survivors++ + if(survivor.mind) + if((survivor.mind in head_revolutionaries) || (survivor.mind in revolutionaries)) + num_revs++ + if(num_survivors) + to_chat(world, "[TAB]Command's Approval Rating: [100 - round((num_revs/num_survivors)*100, 0.1)]%") // % of loyal crew + var/text = "
    The head revolutionaries were:" + for(var/datum/mind/headrev in head_revolutionaries) + text += printplayer(headrev, 1) + text += "
    " + to_chat(world, text) + + if(revolutionaries.len || GAMEMODE_IS_REVOLUTION) + var/text = "
    The revolutionaries were:" + for(var/datum/mind/rev in revolutionaries) + text += printplayer(rev, 1) + text += "
    " + to_chat(world, text) + + if( head_revolutionaries.len || revolutionaries.len || GAMEMODE_IS_REVOLUTION ) + var/text = "
    The heads of staff were:" + var/list/heads = get_all_heads() + for(var/datum/mind/head in heads) + var/target = (head in targets) + if(target) + text += "Target" + text += printplayer(head, 1) + text += "
    " + to_chat(world, text) + +/datum/game_mode/revolution/set_scoreboard_gvars() + var/foecount = 0 + for(var/datum/mind/M in SSticker.mode.head_revolutionaries) + foecount++ + if(!M || !M.current) + score_opkilled++ + continue + + if(M.current.stat == DEAD) + score_opkilled++ + + else if(M.current.restrained()) + score_arrested++ + + if(foecount == score_arrested) + score_allarrested = 1 + + for(var/mob/living/carbon/human/player in world) + if(player.mind) + var/role = player.mind.assigned_role + if(role in list("Captain", "Head of Security", "Head of Personnel", "Chief Engineer", "Research Director")) + if(player.stat == DEAD) + score_deadcommand++ + + + var/arrestpoints = score_arrested * 1000 + var/killpoints = score_opkilled * 500 + var/comdeadpts = score_deadcommand * 500 + if(score_traitorswon) + score_crewscore -= 10000 + + score_crewscore += arrestpoints + score_crewscore += killpoints + score_crewscore -= comdeadpts + + +/datum/game_mode/revolution/get_scoreboard_stats() + var/foecount = 0 + var/comcount = 0 + var/revcount = 0 + var/loycount = 0 + for(var/datum/mind/M in SSticker.mode:head_revolutionaries) + if(M.current && M.current.stat != DEAD) + foecount++ + for(var/datum/mind/M in SSticker.mode:revolutionaries) + if(M.current && M.current.stat != DEAD) + revcount++ + for(var/mob/living/carbon/human/player in world) + if(player.mind) + var/role = player.mind.assigned_role + if(role in list("Captain", "Head of Security", "Head of Personnel", "Chief Engineer", "Research Director")) + if(player.stat != DEAD) + comcount++ + else + if(player.mind in SSticker.mode.revolutionaries) continue + loycount++ + + for(var/mob/living/silicon/X in world) + if(X.stat != DEAD) + loycount++ + + + var/dat = "" + + dat += "Mode Statistics
    " + dat += "Number of Surviving Revolution Heads: [foecount]
    " + dat += "Number of Surviving Command Staff: [comcount]
    " + dat += "Number of Surviving Revolutionaries: [revcount]
    " + dat += "Number of Surviving Loyal Crew: [loycount]
    " + + dat += "
    " + dat += "Revolution Heads Arrested: [score_arrested] ([score_arrested * 1000] Points)
    " + dat += "All Revolution Heads Arrested: [score_allarrested ? "Yes" : "No"] (Score tripled)
    " + + dat += "Revolution Heads Slain: [score_opkilled] ([score_opkilled * 500] Points)
    " + dat += "Command Staff Slain: [score_deadcommand] (-[score_deadcommand * 500] Points)
    " + dat += "Revolution Successful: [score_traitorswon ? "Yes" : "No"] (-[score_traitorswon * 10000] Points)
    " + dat += "
    " + + return dat diff --git a/code/game/gamemodes/sandbox/h_sandbox.dm b/code/game/gamemodes/sandbox/h_sandbox.dm index 216c946fc97..5dd462296b9 100644 --- a/code/game/gamemodes/sandbox/h_sandbox.dm +++ b/code/game/gamemodes/sandbox/h_sandbox.dm @@ -1,161 +1,161 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 - -var/hsboxspawn = 1 -var/list - hrefs = list( - "hsbsuit" = "Suit Up (Space Travel Gear)", - "hsbmetal" = "Spawn 50 Metal", - "hsbglass" = "Spawn 50 Glass", - "hsbairlock" = "Spawn Airlock", - "hsbregulator" = "Spawn Air Regulator", - "hsbfilter" = "Spawn Air Filter", - "hsbcanister" = "Spawn Canister", - "hsbfueltank" = "Spawn Welding Fuel Tank", - "hsbwater tank" = "Spawn Water Tank", - "hsbtoolbox" = "Spawn Toolbox", - "hsbmedkit" = "Spawn Medical Kit") - -mob - var/datum/hSB/sandbox = null - proc - CanBuild() - if(master_mode == "sandbox") - sandbox = new/datum/hSB - sandbox.owner = src.ckey - if(src.client.holder) - sandbox.admin = 1 - verbs += new/mob/proc/sandbox_panel - sandbox_panel() - if(sandbox) - sandbox.update() - -datum/hSB - var/owner = null - var/admin = 0 - proc - update() - var/hsbpanel = "
    h_Sandbox Panel

    " - if(admin) - hsbpanel += "Administration Tools:
    " - hsbpanel += "- Toggle Object Spawning

    " - hsbpanel += "Regular Tools:
    " - for(var/T in hrefs) - hsbpanel += "- [hrefs[T]]
    " - if(hsboxspawn) - hsbpanel += "- Spawn Object

    " - usr << browse(hsbpanel, "window=hsbpanel") - Topic(href, href_list) - if(!(src.owner == usr.ckey)) return - if(!usr) return //I guess this is possible if they log out or die with the panel open? It happened. - if(href_list["hsb"]) - switch(href_list["hsb"]) - if("hsbtobj") - if(!admin) return - if(hsboxspawn) - to_chat(world, "Sandbox: [usr.key] has disabled object spawning!") - hsboxspawn = 0 - return - if(!hsboxspawn) - to_chat(world, "Sandbox: [usr.key] has enabled object spawning!") - hsboxspawn = 1 - return - if("hsbsuit") - var/mob/living/carbon/human/P = usr - if(P.wear_suit) - P.wear_suit.loc = P.loc - P.wear_suit.layer = initial(P.wear_suit.layer) - P.wear_suit.plane = initial(P.wear_suit.plane) - P.wear_suit = null - P.wear_suit = new/obj/item/clothing/suit/space(P) - P.wear_suit.layer = ABOVE_HUD_LAYER - P.wear_suit.plane = ABOVE_HUD_PLANE - if(P.head) - P.head.loc = P.loc - P.head.layer = initial(P.head.layer) - P.head.plane = initial(P.head.plane) - P.head = null - P.head = new/obj/item/clothing/head/helmet/space(P) - P.head.layer = ABOVE_HUD_LAYER - P.head.plane = ABOVE_HUD_PLANE - if(P.wear_mask) - P.wear_mask.loc = P.loc - P.wear_mask.layer = initial(P.wear_mask.layer) - P.wear_mask.plane = initial(P.wear_mask.plane) - P.wear_mask = null - P.wear_mask = new/obj/item/clothing/mask/gas(P) - P.wear_mask.layer = ABOVE_HUD_LAYER - P.wear_mask.plane = ABOVE_HUD_PLANE - if(P.back) - P.back.loc = P.loc - P.back.layer = initial(P.back.layer) - P.back.plane = initial(P.back.plane) - P.back = null - P.back = new/obj/item/tank/jetpack(P) - P.back.layer = ABOVE_HUD_LAYER - P.back.plane = ABOVE_HUD_PLANE - P.internal = P.back - if("hsbmetal") - var/obj/item/stack/sheet/hsb = new/obj/item/stack/sheet/metal - hsb.amount = 50 - hsb.loc = usr.loc - if("hsbglass") - var/obj/item/stack/sheet/hsb = new/obj/item/stack/sheet/glass - hsb.amount = 50 - hsb.loc = usr.loc - if("hsbairlock") - var/obj/machinery/door/hsb = new/obj/machinery/door/airlock - - //TODO: DEFERRED make this better, with an HTML window or something instead of 15 popups - hsb.req_access = list() - var/accesses = get_all_accesses() - for(var/A in accesses) - if(alert(usr, "Will this airlock require [get_access_desc(A)] access?", "Sandbox:", "Yes", "No") == "Yes") - hsb.req_access += A - - hsb.loc = usr.loc - to_chat(usr, "Sandbox: Created an airlock.") - if("hsbcanister") - var/list/hsbcanisters = subtypesof(/obj/machinery/portable_atmospherics/canister/) - var/hsbcanister = input(usr, "Choose a canister to spawn.", "Sandbox:") in hsbcanisters + "Cancel" - if(!(hsbcanister == "Cancel")) - new hsbcanister(usr.loc) - if("hsbfueltank") - //var/obj/hsb = new/obj/weldfueltank - //hsb.loc = usr.loc - if("hsbwatertank") - //var/obj/hsb = new/obj/watertank - //hsb.loc = usr.loc - if("hsbtoolbox") - var/obj/item/storage/hsb = new/obj/item/storage/toolbox/mechanical - for(var/obj/item/radio/T in hsb) - qdel(T) - new/obj/item/crowbar (hsb) - hsb.loc = usr.loc - if("hsbmedkit") - var/obj/item/storage/firstaid/hsb = new/obj/item/storage/firstaid/regular - hsb.loc = usr.loc - if("hsbobj") - if(!hsboxspawn) return - - var/list/selectable = list() - for(var/O in typesof(/obj/item/)) - //Note, these istypes don't work - if(istype(O, /obj/item/gun)) - continue - if(istype(O, /obj/item/assembly)) - continue - if(istype(O, /obj/item/camera)) - continue - if(istype(O, /obj/item/cloaking_device)) - continue - if(istype(O, /obj/item/dummy)) - continue - if(istype(O, /obj/item/melee/energy/sword/saber)) - continue - if(istype(O, /obj/structure)) - continue - selectable += O - - var/hsbitem = input(usr, "Choose an object to spawn.", "Sandbox:") in selectable + "Cancel" - if(hsbitem != "Cancel") - new hsbitem(usr.loc) +//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 + +var/hsboxspawn = 1 +var/list + hrefs = list( + "hsbsuit" = "Suit Up (Space Travel Gear)", + "hsbmetal" = "Spawn 50 Metal", + "hsbglass" = "Spawn 50 Glass", + "hsbairlock" = "Spawn Airlock", + "hsbregulator" = "Spawn Air Regulator", + "hsbfilter" = "Spawn Air Filter", + "hsbcanister" = "Spawn Canister", + "hsbfueltank" = "Spawn Welding Fuel Tank", + "hsbwater tank" = "Spawn Water Tank", + "hsbtoolbox" = "Spawn Toolbox", + "hsbmedkit" = "Spawn Medical Kit") + +mob + var/datum/hSB/sandbox = null + proc + CanBuild() + if(master_mode == "sandbox") + sandbox = new/datum/hSB + sandbox.owner = src.ckey + if(src.client.holder) + sandbox.admin = 1 + verbs += new/mob/proc/sandbox_panel + sandbox_panel() + if(sandbox) + sandbox.update() + +datum/hSB + var/owner = null + var/admin = 0 + proc + update() + var/hsbpanel = "
    h_Sandbox Panel

    " + if(admin) + hsbpanel += "Administration Tools:
    " + hsbpanel += "- Toggle Object Spawning

    " + hsbpanel += "Regular Tools:
    " + for(var/T in hrefs) + hsbpanel += "- [hrefs[T]]
    " + if(hsboxspawn) + hsbpanel += "- Spawn Object

    " + usr << browse(hsbpanel, "window=hsbpanel") + Topic(href, href_list) + if(!(src.owner == usr.ckey)) return + if(!usr) return //I guess this is possible if they log out or die with the panel open? It happened. + if(href_list["hsb"]) + switch(href_list["hsb"]) + if("hsbtobj") + if(!admin) return + if(hsboxspawn) + to_chat(world, "Sandbox: [usr.key] has disabled object spawning!") + hsboxspawn = 0 + return + if(!hsboxspawn) + to_chat(world, "Sandbox: [usr.key] has enabled object spawning!") + hsboxspawn = 1 + return + if("hsbsuit") + var/mob/living/carbon/human/P = usr + if(P.wear_suit) + P.wear_suit.loc = P.loc + P.wear_suit.layer = initial(P.wear_suit.layer) + P.wear_suit.plane = initial(P.wear_suit.plane) + P.wear_suit = null + P.wear_suit = new/obj/item/clothing/suit/space(P) + P.wear_suit.layer = ABOVE_HUD_LAYER + P.wear_suit.plane = ABOVE_HUD_PLANE + if(P.head) + P.head.loc = P.loc + P.head.layer = initial(P.head.layer) + P.head.plane = initial(P.head.plane) + P.head = null + P.head = new/obj/item/clothing/head/helmet/space(P) + P.head.layer = ABOVE_HUD_LAYER + P.head.plane = ABOVE_HUD_PLANE + if(P.wear_mask) + P.wear_mask.loc = P.loc + P.wear_mask.layer = initial(P.wear_mask.layer) + P.wear_mask.plane = initial(P.wear_mask.plane) + P.wear_mask = null + P.wear_mask = new/obj/item/clothing/mask/gas(P) + P.wear_mask.layer = ABOVE_HUD_LAYER + P.wear_mask.plane = ABOVE_HUD_PLANE + if(P.back) + P.back.loc = P.loc + P.back.layer = initial(P.back.layer) + P.back.plane = initial(P.back.plane) + P.back = null + P.back = new/obj/item/tank/jetpack(P) + P.back.layer = ABOVE_HUD_LAYER + P.back.plane = ABOVE_HUD_PLANE + P.internal = P.back + if("hsbmetal") + var/obj/item/stack/sheet/hsb = new/obj/item/stack/sheet/metal + hsb.amount = 50 + hsb.loc = usr.loc + if("hsbglass") + var/obj/item/stack/sheet/hsb = new/obj/item/stack/sheet/glass + hsb.amount = 50 + hsb.loc = usr.loc + if("hsbairlock") + var/obj/machinery/door/hsb = new/obj/machinery/door/airlock + + //TODO: DEFERRED make this better, with an HTML window or something instead of 15 popups + hsb.req_access = list() + var/accesses = get_all_accesses() + for(var/A in accesses) + if(alert(usr, "Will this airlock require [get_access_desc(A)] access?", "Sandbox:", "Yes", "No") == "Yes") + hsb.req_access += A + + hsb.loc = usr.loc + to_chat(usr, "Sandbox: Created an airlock.") + if("hsbcanister") + var/list/hsbcanisters = subtypesof(/obj/machinery/portable_atmospherics/canister/) + var/hsbcanister = input(usr, "Choose a canister to spawn.", "Sandbox:") in hsbcanisters + "Cancel" + if(!(hsbcanister == "Cancel")) + new hsbcanister(usr.loc) + if("hsbfueltank") + //var/obj/hsb = new/obj/weldfueltank + //hsb.loc = usr.loc + if("hsbwatertank") + //var/obj/hsb = new/obj/watertank + //hsb.loc = usr.loc + if("hsbtoolbox") + var/obj/item/storage/hsb = new/obj/item/storage/toolbox/mechanical + for(var/obj/item/radio/T in hsb) + qdel(T) + new/obj/item/crowbar (hsb) + hsb.loc = usr.loc + if("hsbmedkit") + var/obj/item/storage/firstaid/hsb = new/obj/item/storage/firstaid/regular + hsb.loc = usr.loc + if("hsbobj") + if(!hsboxspawn) return + + var/list/selectable = list() + for(var/O in typesof(/obj/item/)) + //Note, these istypes don't work + if(istype(O, /obj/item/gun)) + continue + if(istype(O, /obj/item/assembly)) + continue + if(istype(O, /obj/item/camera)) + continue + if(istype(O, /obj/item/cloaking_device)) + continue + if(istype(O, /obj/item/dummy)) + continue + if(istype(O, /obj/item/melee/energy/sword/saber)) + continue + if(istype(O, /obj/structure)) + continue + selectable += O + + var/hsbitem = input(usr, "Choose an object to spawn.", "Sandbox:") in selectable + "Cancel" + if(hsbitem != "Cancel") + new hsbitem(usr.loc) diff --git a/code/game/gamemodes/sandbox/sandbox.dm b/code/game/gamemodes/sandbox/sandbox.dm index 663759c961c..7b6978f4a1b 100644 --- a/code/game/gamemodes/sandbox/sandbox.dm +++ b/code/game/gamemodes/sandbox/sandbox.dm @@ -1,21 +1,21 @@ -/datum/game_mode/sandbox - name = "sandbox" - config_tag = "sandbox" - required_players = 0 - - uplink_welcome = "Syndicate Uplink Console:" - uplink_uses = 10 - -/datum/game_mode/sandbox/announce() - to_chat(world, "The current game mode is - Sandbox!") - to_chat(world, "Build your own station with the sandbox-panel command!") - -/datum/game_mode/sandbox/pre_setup() - for(var/mob/M in GLOB.player_list) - M.CanBuild() - return 1 - -/datum/game_mode/sandbox/post_setup() - ..() - if(emergency_shuttle) - emergency_shuttle.no_escape = 1 +/datum/game_mode/sandbox + name = "sandbox" + config_tag = "sandbox" + required_players = 0 + + uplink_welcome = "Syndicate Uplink Console:" + uplink_uses = 10 + +/datum/game_mode/sandbox/announce() + to_chat(world, "The current game mode is - Sandbox!") + to_chat(world, "Build your own station with the sandbox-panel command!") + +/datum/game_mode/sandbox/pre_setup() + for(var/mob/M in GLOB.player_list) + M.CanBuild() + return 1 + +/datum/game_mode/sandbox/post_setup() + ..() + if(emergency_shuttle) + emergency_shuttle.no_escape = 1 diff --git a/code/game/gamemodes/setupgame.dm b/code/game/gamemodes/setupgame.dm index 78e6cea4987..507aaec0135 100644 --- a/code/game/gamemodes/setupgame.dm +++ b/code/game/gamemodes/setupgame.dm @@ -1,164 +1,164 @@ -/proc/getAssignedBlock(var/name,var/list/blocksLeft, var/activity_bounds=DNA_DEFAULT_BOUNDS, var/good=0) - if(blocksLeft.len==0) - warning("[name]: No more blocks left to assign!") - return 0 - var/assigned = pick(blocksLeft) - blocksLeft.Remove(assigned) - if(good) - good_blocks += assigned - else - bad_blocks += assigned - assigned_blocks[assigned]=name - dna_activity_bounds[assigned]=activity_bounds - //Debug message_admins("[name] assigned to block #[assigned].") -// testing("[name] assigned to block #[assigned].") - return assigned - -/proc/setupgenetics() - - if(prob(50)) - BLOCKADD = rand(-300,300) - if(prob(75)) - DIFFMUT = rand(0,20) - - -//Thanks to nexis for the fancy code -// BITCH I AIN'T DONE YET - - // SE blocks to assign. - var/list/numsToAssign=new() - for(var/i=1;iThe current game mode is - Traitor!") - to_chat(world, "There is a syndicate traitor on the station. Do not let the traitor succeed!") - - -/datum/game_mode/traitor/pre_setup() - - if(config.protect_roles_from_antagonist) - restricted_jobs += protected_jobs - - var/list/possible_traitors = get_players_for_role(ROLE_TRAITOR) - - // stop setup if no possible traitors - if(!possible_traitors.len) - return 0 - - var/num_traitors = 1 - - if(config.traitor_scaling) - num_traitors = max(1, round((num_players())/(traitor_scaling_coeff))) - else - num_traitors = max(1, min(num_players(), traitors_possible)) - - for(var/j = 0, j < num_traitors, j++) - if(!possible_traitors.len) - break - var/datum/mind/traitor = pick(possible_traitors) - pre_traitors += traitor - traitor.special_role = SPECIAL_ROLE_TRAITOR - traitor.restricted_roles = restricted_jobs - possible_traitors.Remove(traitor) - - if(!pre_traitors.len) - return 0 - return 1 - - -/datum/game_mode/traitor/post_setup() - for(var/datum/mind/traitor in pre_traitors) - var/datum/antagonist/traitor/new_antag = new antag_datum() - addtimer(CALLBACK(traitor, /datum/mind.proc/add_antag_datum, new_antag), rand(10,100)) - if(!exchange_blue) - exchange_blue = -1 //Block latejoiners from getting exchange objectives - ..() - - -/datum/game_mode/traitor/declare_completion() - ..() - return//Traitors will be checked as part of check_extra_completion. Leaving this here as a reminder. - -/datum/game_mode/traitor/process() - // Make sure all objectives are processed regularly, so that objectives - // which can be checked mid-round are checked mid-round. - for(var/datum/mind/traitor_mind in traitors) - for(var/datum/objective/objective in traitor_mind.objectives) - objective.check_completion() - return 0 - - -/datum/game_mode/proc/auto_declare_completion_traitor() - if(traitors.len) - var/text = "The traitors were:" - for(var/datum/mind/traitor in traitors) - var/traitorwin = 1 - - text += "
    [traitor.key] was [traitor.name] (" - if(traitor.current) - if(traitor.current.stat == DEAD) - text += "died" - else - text += "survived" - if(traitor.current.real_name != traitor.name) - text += " as [traitor.current.real_name]" - else - text += "body destroyed" - text += ")" - - - var/TC_uses = 0 - var/uplink_true = 0 - var/purchases = "" - for(var/obj/item/uplink/H in world_uplinks) - if(H && H.uplink_owner && H.uplink_owner==traitor.key) - TC_uses += H.used_TC - uplink_true=1 - purchases += H.purchase_log - - if(uplink_true) text += " (used [TC_uses] TC) [purchases]" - - - if(traitor.objectives && traitor.objectives.len)//If the traitor had no objectives, don't need to process this. - var/count = 1 - for(var/datum/objective/objective in traitor.objectives) - if(objective.check_completion()) - text += "
    Objective #[count]: [objective.explanation_text] Success!" - feedback_add_details("traitor_objective","[objective.type]|SUCCESS") - else - text += "
    Objective #[count]: [objective.explanation_text] Fail." - feedback_add_details("traitor_objective","[objective.type]|FAIL") - traitorwin = 0 - count++ - - var/special_role_text - if(traitor.special_role) - special_role_text = lowertext(traitor.special_role) - else - special_role_text = "antagonist" - - - if(traitorwin) - text += "
    The [special_role_text] was successful!" - feedback_add_details("traitor_success","SUCCESS") - else - text += "
    The [special_role_text] has failed!" - feedback_add_details("traitor_success","FAIL") - - var/phrases = jointext(GLOB.syndicate_code_phrase, ", ") - var/responses = jointext(GLOB.syndicate_code_response, ", ") - - text += "

    The code phrases were: [phrases]
    \ - The code responses were: [responses]

    " - - to_chat(world, text) - return 1 +/datum/game_mode + // this includes admin-appointed traitors and multitraitors. Easy! + var/list/datum/mind/traitors = list() + var/list/datum/mind/implanter = list() + var/list/datum/mind/implanted = list() + + var/datum/mind/exchange_red + var/datum/mind/exchange_blue + +/datum/game_mode/traitor + name = "traitor" + config_tag = "traitor" + restricted_jobs = list("Cyborg")//They are part of the AI if he is traitor so are they, they use to get double chances + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Blueshield", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate", "Internal Affairs Agent", "Brig Physician", "Nanotrasen Navy Officer", "Special Operations Officer", "Syndicate Officer") + required_players = 0 + required_enemies = 1 + recommended_enemies = 4 + + var/list/datum/mind/pre_traitors = list() + var/traitors_possible = 4 //hard limit on traitors if scaling is turned off + var/const/traitor_scaling_coeff = 5.0 //how much does the amount of players get divided by to determine traitors + var/antag_datum = /datum/antagonist/traitor //what type of antag to create + +/datum/game_mode/traitor/announce() + to_chat(world, "The current game mode is - Traitor!") + to_chat(world, "There is a syndicate traitor on the station. Do not let the traitor succeed!") + + +/datum/game_mode/traitor/pre_setup() + + if(config.protect_roles_from_antagonist) + restricted_jobs += protected_jobs + + var/list/possible_traitors = get_players_for_role(ROLE_TRAITOR) + + // stop setup if no possible traitors + if(!possible_traitors.len) + return 0 + + var/num_traitors = 1 + + if(config.traitor_scaling) + num_traitors = max(1, round((num_players())/(traitor_scaling_coeff))) + else + num_traitors = max(1, min(num_players(), traitors_possible)) + + for(var/j = 0, j < num_traitors, j++) + if(!possible_traitors.len) + break + var/datum/mind/traitor = pick(possible_traitors) + pre_traitors += traitor + traitor.special_role = SPECIAL_ROLE_TRAITOR + traitor.restricted_roles = restricted_jobs + possible_traitors.Remove(traitor) + + if(!pre_traitors.len) + return 0 + return 1 + + +/datum/game_mode/traitor/post_setup() + for(var/datum/mind/traitor in pre_traitors) + var/datum/antagonist/traitor/new_antag = new antag_datum() + addtimer(CALLBACK(traitor, /datum/mind.proc/add_antag_datum, new_antag), rand(10,100)) + if(!exchange_blue) + exchange_blue = -1 //Block latejoiners from getting exchange objectives + ..() + + +/datum/game_mode/traitor/declare_completion() + ..() + return//Traitors will be checked as part of check_extra_completion. Leaving this here as a reminder. + +/datum/game_mode/traitor/process() + // Make sure all objectives are processed regularly, so that objectives + // which can be checked mid-round are checked mid-round. + for(var/datum/mind/traitor_mind in traitors) + for(var/datum/objective/objective in traitor_mind.objectives) + objective.check_completion() + return 0 + + +/datum/game_mode/proc/auto_declare_completion_traitor() + if(traitors.len) + var/text = "The traitors were:" + for(var/datum/mind/traitor in traitors) + var/traitorwin = 1 + + text += "
    [traitor.key] was [traitor.name] (" + if(traitor.current) + if(traitor.current.stat == DEAD) + text += "died" + else + text += "survived" + if(traitor.current.real_name != traitor.name) + text += " as [traitor.current.real_name]" + else + text += "body destroyed" + text += ")" + + + var/TC_uses = 0 + var/uplink_true = 0 + var/purchases = "" + for(var/obj/item/uplink/H in world_uplinks) + if(H && H.uplink_owner && H.uplink_owner==traitor.key) + TC_uses += H.used_TC + uplink_true=1 + purchases += H.purchase_log + + if(uplink_true) text += " (used [TC_uses] TC) [purchases]" + + + if(traitor.objectives && traitor.objectives.len)//If the traitor had no objectives, don't need to process this. + var/count = 1 + for(var/datum/objective/objective in traitor.objectives) + if(objective.check_completion()) + text += "
    Objective #[count]: [objective.explanation_text] Success!" + feedback_add_details("traitor_objective","[objective.type]|SUCCESS") + else + text += "
    Objective #[count]: [objective.explanation_text] Fail." + feedback_add_details("traitor_objective","[objective.type]|FAIL") + traitorwin = 0 + count++ + + var/special_role_text + if(traitor.special_role) + special_role_text = lowertext(traitor.special_role) + else + special_role_text = "antagonist" + + + if(traitorwin) + text += "
    The [special_role_text] was successful!" + feedback_add_details("traitor_success","SUCCESS") + else + text += "
    The [special_role_text] has failed!" + feedback_add_details("traitor_success","FAIL") + + var/phrases = jointext(GLOB.syndicate_code_phrase, ", ") + var/responses = jointext(GLOB.syndicate_code_response, ", ") + + text += "

    The code phrases were: [phrases]
    \ + The code responses were: [responses]

    " + + to_chat(world, text) + return 1 diff --git a/code/game/gamemodes/vampire/traitor_vamp.dm b/code/game/gamemodes/vampire/traitor_vamp.dm index 4794f6755f2..d69b9a23518 100644 --- a/code/game/gamemodes/vampire/traitor_vamp.dm +++ b/code/game/gamemodes/vampire/traitor_vamp.dm @@ -45,4 +45,4 @@ forge_vampire_objectives(vampire) greet_vampire(vampire) update_vampire_icons_added(vampire) - ..() \ No newline at end of file + ..() diff --git a/code/game/gamemodes/vampire/vampire_powers.dm b/code/game/gamemodes/vampire/vampire_powers.dm index 6c9674d130e..24e47577c31 100644 --- a/code/game/gamemodes/vampire/vampire_powers.dm +++ b/code/game/gamemodes/vampire/vampire_powers.dm @@ -606,4 +606,4 @@ add_attack_logs(M, src, "Vampire-sired") mind.make_Vampire() revive() - Weaken(20) \ No newline at end of file + Weaken(20) diff --git a/code/game/gamemodes/wizard/rightandwrong.dm b/code/game/gamemodes/wizard/rightandwrong.dm index 5f2dc094724..4f2d20b373a 100644 --- a/code/game/gamemodes/wizard/rightandwrong.dm +++ b/code/game/gamemodes/wizard/rightandwrong.dm @@ -166,4 +166,4 @@ GLOBAL_VAR_INIT(summon_magic_triggered, FALSE) if(summon_type == SUMMON_MAGIC) give_magic(H) else - give_guns(H) \ No newline at end of file + give_guns(H) diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm index d97ec2af5fd..cf0ce4843f0 100644 --- a/code/game/gamemodes/wizard/soulstone.dm +++ b/code/game/gamemodes/wizard/soulstone.dm @@ -1,381 +1,381 @@ -/obj/item/soulstone - name = "Soul Stone Shard" - icon = 'icons/obj/wizard.dmi' - icon_state = "soulstone" - item_state = "electronic" - desc = "A fragment of the legendary treasure known simply as the 'Soul Stone'. The shard still flickers with a fraction of the full artifact's power." - w_class = WEIGHT_CLASS_TINY - slot_flags = SLOT_BELT - origin_tech = "bluespace=4;materials=5" - - var/optional = FALSE //does this soulstone ask the victim whether they want to be turned into a shade - var/usability = FALSE // Can this soul stone be used by anyone, or only cultists/wizards? - var/reusable = TRUE // Can this soul stone be used more than once? - var/spent = FALSE // If the soul stone can only be used once, has it been used? - - var/opt_in = FALSE // for tracking during the 'optional' bit - -/obj/item/soulstone/proc/can_use(mob/living/user) - if(iscultist(user) || iswizard(user) || usability) - return TRUE - - return FALSE - -/obj/item/soulstone/proc/was_used() - if(!reusable) - spent = TRUE - name = "dull [name]" - desc = "A fragment of the legendary treasure known simply as \ - the 'Soul Stone'. The shard lies still, dull and lifeless; \ - whatever spark it once held long extinguished." - -/obj/item/soulstone/anybody - usability = TRUE - -/obj/item/soulstone/anybody/chaplain - name = "mysterious old shard" - reusable = FALSE - optional = TRUE - -/obj/item/soulstone/pickup(mob/living/user) - . = ..() - if(!can_use(user)) - to_chat(user, "An overwhelming feeling of dread comes over you as you pick up the soulstone. It would be wise to be rid of this quickly.") - user.Dizzy(120) - -//////////////////////////////Capturing//////////////////////////////////////////////////////// -/obj/item/soulstone/attack(mob/living/carbon/human/M as mob, mob/user as mob) - if(!can_use(user)) - user.Paralyse(5) - to_chat(user, "Your body is wracked with debilitating pain!") - return - - if(spent) - to_chat(user, "There is no power left in the shard.") - return - - if(!ishuman(M)) //If target is not a human - return ..() - - if(M.has_brain_worms()) //Borer stuff - RR - to_chat(user, "This being is corrupted by an alien intelligence and cannot be soul trapped.") - return ..() - - if(jobban_isbanned(M, "cultist") || jobban_isbanned(M, "Syndicate")) - to_chat(user, "A mysterious force prevents you from trapping this being's soul.") - return ..() - - if(iscultist(user) && iscultist(M)) - 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!") - return - - to_chat(user, "You attempt to channel [M]'s soul into [src]. You must give the soul some time to react and stand still...") - - var/mob/player_mob = M - if(M.get_ghost())//in case our player ghosted and we need to throw the alert at their ghost instead - player_mob = M.get_ghost() - var/client/player_client = player_mob.client - to_chat(player_mob, "[user] is trying to capture your soul into [src]! Click the button in the top right of the game window to respond.") - player_client << 'sound/misc/notice2.ogg' - window_flash(player_client) - - var/obj/screen/alert/notify_soulstone/A = player_mob.throw_alert("\ref[src]_soulstone_thingy", /obj/screen/alert/notify_soulstone) - if(player_client.prefs && player_client.prefs.UI_style) - A.icon = ui_style2icon(player_client.prefs.UI_style) - - //pass the stuff to the alert itself - A.stone = src - A.stoner = user.real_name - - //layer shenanigans to make the alert display the soulstone - var/old_layer = layer - var/old_plane = plane - layer = FLOAT_LAYER - plane = FLOAT_PLANE - A.overlays += src - layer = old_layer - plane = old_plane - - //give the victim 10 seconds to respond - sleep(10 SECONDS) - - if(!opt_in) - to_chat(user, "The soul resists your attempts at capturing it!") - return - - opt_in = FALSE - - if(spent)//checking one more time against shenanigans - return - - add_attack_logs(user, M, "Stolestone'd with [name]") - transfer_soul("VICTIM", M, user) - return - -///////////////////Options for using captured souls/////////////////////////////////////// -/obj/item/soulstone/attack_self(mob/user) - if(!in_range(src, user)) - return - - if(!can_use(user)) - user.Paralyse(5) - to_chat(user, "Your body is wracked with debilitating pain!") - return - - user.set_machine(src) - var/dat = "Soul Stone
    " - for(var/mob/living/simple_animal/shade/A in src) - dat += "Captured Soul: [A.name]
    " - dat += {"Summon Shade"} - dat += "
    " - dat += {" Close"} - user << browse(dat, "window=aicard") - onclose(user, "aicard") - return - -/obj/item/soulstone/Topic(href, href_list) - var/mob/U = usr - if(!in_range(src, U) || U.machine != src || !can_use(usr)) - U << browse(null, "window=aicard") - U.unset_machine() - return - - add_fingerprint(U) - U.set_machine(src) - - switch(href_list["choice"])//Now we switch based on choice. - if("Close") - U << browse(null, "window=aicard") - U.unset_machine() - return - - if("Summon") - for(var/mob/living/simple_animal/shade/A in src) - A.status_flags &= ~GODMODE - A.canmove = 1 - A.forceMove(get_turf(usr)) - A.cancel_camera() - icon_state = "soulstone" - name = initial(name) - if(iswizard(usr) || usability) - to_chat(A, "You have been released from your prison, but you are still bound to [usr.real_name]'s will. Help [usr.p_them()] succeed in [usr.p_their()] goals at all costs.") - else if(iscultist(usr)) - to_chat(A, "You have been released from your prison, but you are still bound to the cult's will. Help [usr.p_them()] succeed in [usr.p_their()] goals at all costs.") - was_used() - attack_self(U) - -///////////////////////////Transferring to constructs///////////////////////////////////////////////////// -/obj/structure/constructshell - name = "empty shell" - icon = 'icons/obj/wizard.dmi' - icon_state = "construct-cult" - desc = "A wicked machine used by those skilled in magical arts. It is inactive" - -/obj/structure/constructshell/examine(mob/user) - . = ..() - if(in_range(user, src) && (iscultist(user) || iswizard(user) || user.stat == DEAD)) - . += "A construct shell, used to house bound souls from a soulstone." - . += "Placing a soulstone with a soul into this shell allows you to produce your choice of the following:" - . += "An Artificer, which can produce more shells and soulstones, as well as fortifications." - . += "A Wraith, which does high damage and can jaunt through walls, though it is quite fragile." - . += "A Juggernaut, which is very hard to kill and can produce temporary walls, but is slow." - -/obj/structure/constructshell/attackby(obj/item/O as obj, mob/user as mob, params) - if(istype(O, /obj/item/soulstone)) - var/obj/item/soulstone/SS = O - if(!SS.can_use(user)) - to_chat(user, "An overwhelming feeling of dread comes over you as you attempt to place the soulstone into the shell. It would be wise to be rid of this quickly.") - user.Dizzy(120) - return - SS.transfer_soul("CONSTRUCT",src,user) - SS.was_used() - else - return ..() - -////////////////////////////Proc for moving soul in and out off stone////////////////////////////////////// - -/obj/item/proc/transfer_soul(var/choice as text, var/target, var/mob/U as mob) - switch(choice) - if("FORCE") - var/obj/item/soulstone/SS = src - var/mob/living/T = target - if(T.client != null) - SS.init_shade(T, U) - else - to_chat(U, "Capture failed!: The soul has already fled its mortal frame. You attempt to bring it back...") - T.Paralyse(20) - if(!SS.getCultGhost(T,U)) - T.dust() //If we can't get a ghost, kill the sacrifice anyway. - - if("VICTIM") - var/mob/living/carbon/human/T = target - var/obj/item/soulstone/SS = src - if(T.stat == 0) - to_chat(U, "Capture failed!: Kill or maim the victim first!") - else - if(!T.client_mobs_in_contents?.len) - to_chat(U, "They have no soul!") - else - if(T.client == null) - to_chat(U, "Capture failed!: The soul has already fled its mortal frame. You attempt to bring it back...") - SS.getCultGhost(T,U) - else - if(SS.contents.len) - to_chat(U, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") - else - SS.init_shade(T, U, vic = 1) - - if("SHADE") - var/mob/living/simple_animal/shade/T = target - var/obj/item/soulstone/SS = src - if(!SS.can_use(U)) - U.Paralyse(5) - to_chat(U, "Your body is wracked with debilitating pain!") - return - if(T.stat == DEAD) - to_chat(U, "Capture failed!: The shade has already been banished!") - else - if(SS.contents.len) - to_chat(U, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") - else - T.loc = SS //put shade in stone - T.status_flags |= GODMODE - T.canmove = 0 - T.health = T.maxHealth - T.faction |= "\ref[U]" - SS.icon_state = "soulstone2" - to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form") - to_chat(U, "Capture successful!: [T.name]'s has been recaptured and stored within the soul stone.") - if("CONSTRUCT") - var/obj/structure/constructshell/T = target - var/obj/item/soulstone/SS = src - var/mob/living/simple_animal/shade/SH = locate() in SS - if(SH) - var/construct_class = alert(U, "Please choose which type of construct you wish to create.",,"Juggernaut","Wraith","Artificer") - switch(construct_class) - if("Juggernaut") - var/mob/living/simple_animal/hostile/construct/armoured/C = new /mob/living/simple_animal/hostile/construct/armoured (get_turf(T.loc)) - to_chat(C, "You are a Juggernaut. Though slow, your shell can withstand extreme punishment, create shield walls and even deflect energy weapons, and rip apart enemies and walls alike.") - init_construct(C,SH,SS,T) - - if("Wraith") - var/mob/living/simple_animal/hostile/construct/wraith/C = new /mob/living/simple_animal/hostile/construct/wraith (get_turf(T.loc)) - to_chat(C, "You are a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls.") - init_construct(C,SH,SS,T) - - if("Artificer") - var/mob/living/simple_animal/hostile/construct/builder/C = new /mob/living/simple_animal/hostile/construct/builder (get_turf(T.loc)) - to_chat(C, "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, use magic missile, repair allied constructs (by clicking on them), and most important of all create new constructs (Use your Artificer spell to summon a new construct shell and Summon Soulstone to create a new soulstone).") - init_construct(C,SH,SS,T) - else - to_chat(U, "Creation failed!: The soul stone is empty! Go kill someone!") - return - -/proc/init_construct(mob/living/simple_animal/hostile/construct/C, mob/living/simple_animal/shade/SH, obj/item/soulstone/SS, obj/structure/constructshell/T) - SH.mind.transfer_to(C) - if(iscultist(C)) - var/datum/action/innate/cultcomm/CC = new() - CC.Grant(C) //We have to grant the cult comms again because they're lost during the mind transfer. - qdel(T) - qdel(SH) - to_chat(C, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - C.cancel_camera() - qdel(SS) - -/proc/makeNewConstruct(var/mob/living/simple_animal/hostile/construct/ctype, var/mob/target, var/mob/stoner = null, cultoverride = 0) - if(jobban_isbanned(target, "cultist") || jobban_isbanned(target, "Syndicate")) - return - var/mob/living/simple_animal/hostile/construct/newstruct = new ctype(get_turf(target)) - newstruct.faction |= "\ref[stoner]" - newstruct.key = target.key - if(stoner && iscultist(stoner) || cultoverride) - if(SSticker.mode.name == "cult") - SSticker.mode:add_cultist(newstruct.mind) - else - SSticker.mode.cult+=newstruct.mind - SSticker.mode.update_cult_icons_added(newstruct.mind) - if(stoner && iswizard(stoner)) - to_chat(newstruct, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - else if(stoner && iscultist(stoner)) - to_chat(newstruct, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.") - else - to_chat(newstruct, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") - newstruct.cancel_camera() - -/obj/item/soulstone/proc/init_shade(mob/living/T, mob/U, vic = 0) - new /obj/effect/decal/remains/human(T.loc) //Spawns a skeleton - T.invisibility = 101 - var/atom/movable/overlay/animation = new /atom/movable/overlay( T.loc ) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - animation.master = T - flick("dust-h", animation) - qdel(animation) - var/path = get_shade_type() - var/mob/living/simple_animal/shade/S = new path(src) - S.status_flags |= GODMODE //So they won't die inside the stone somehow - S.canmove = 0//Can't move out of the soul stone - S.name = "Shade of [T.real_name]" - S.real_name = "Shade of [T.real_name]" - S.key = T.key - S.cancel_camera() - name = "soulstone: Shade of [T.real_name]" - icon_state = "soulstone2" - if(U) - S.faction |= "\ref[U]" //Add the master as a faction, allowing inter-mob cooperation - if(iswizard(U)) - SSticker.mode.update_wiz_icons_added(S.mind) - S.mind.special_role = SPECIAL_ROLE_WIZARD_APPRENTICE - if(iscultist(U)) - SSticker.mode.add_cultist(S.mind, 0) - S.mind.special_role = SPECIAL_ROLE_CULTIST - S.mind.store_memory("Serve the cult's will.") - to_chat(S, "Your soul has been captured! You are now bound to the cult's will. Help them succeed in their goals at all costs.") - else - S.mind.store_memory("Serve [U.real_name], your creator.") - to_chat(S, "Your soul has been captured! You are now bound to [U.real_name]'s will. Help them succeed in their goals at all costs.") - if(vic && U) - to_chat(U, "Capture successful!: [T.real_name]'s soul has been ripped from [U.p_their()] body and stored within the soul stone.") - if(isrobot(T))//Robots have to dust or else they spill out an empty robot brain, and unequiping them spills robot components that shouldn't spawn. - T.dust() - else - for(var/obj/item/W in T) - T.unEquip(W) - qdel(T) - -/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 - -/obj/item/soulstone/proc/getCultGhost(mob/living/T, mob/U) - var/mob/dead/observer/chosen_ghost - - for(var/mob/dead/observer/ghost in GLOB.player_list) //We put them back in their body - if(ghost.mind && ghost.mind.current == T && ghost.client) - chosen_ghost = ghost - break - - if(!chosen_ghost) //Failing that, we grab a ghost - var/list/consenting_candidates = pollCandidates("Would you like to play as a Shade?", ROLE_CULTIST, FALSE, poll_time = 100) - if(consenting_candidates.len) - chosen_ghost = pick(consenting_candidates) - if(!T) - return 0 - if(!chosen_ghost) - to_chat(U, "There were no spirits willing to become a shade.") - return 0 - if(contents.len) //If they used the soulstone on someone else in the meantime - return 0 - T.ckey = chosen_ghost.ckey - init_shade(T, U) - return 1 +/obj/item/soulstone + name = "Soul Stone Shard" + icon = 'icons/obj/wizard.dmi' + icon_state = "soulstone" + item_state = "electronic" + desc = "A fragment of the legendary treasure known simply as the 'Soul Stone'. The shard still flickers with a fraction of the full artifact's power." + w_class = WEIGHT_CLASS_TINY + slot_flags = SLOT_BELT + origin_tech = "bluespace=4;materials=5" + + var/optional = FALSE //does this soulstone ask the victim whether they want to be turned into a shade + var/usability = FALSE // Can this soul stone be used by anyone, or only cultists/wizards? + var/reusable = TRUE // Can this soul stone be used more than once? + var/spent = FALSE // If the soul stone can only be used once, has it been used? + + var/opt_in = FALSE // for tracking during the 'optional' bit + +/obj/item/soulstone/proc/can_use(mob/living/user) + if(iscultist(user) || iswizard(user) || usability) + return TRUE + + return FALSE + +/obj/item/soulstone/proc/was_used() + if(!reusable) + spent = TRUE + name = "dull [name]" + desc = "A fragment of the legendary treasure known simply as \ + the 'Soul Stone'. The shard lies still, dull and lifeless; \ + whatever spark it once held long extinguished." + +/obj/item/soulstone/anybody + usability = TRUE + +/obj/item/soulstone/anybody/chaplain + name = "mysterious old shard" + reusable = FALSE + optional = TRUE + +/obj/item/soulstone/pickup(mob/living/user) + . = ..() + if(!can_use(user)) + to_chat(user, "An overwhelming feeling of dread comes over you as you pick up the soulstone. It would be wise to be rid of this quickly.") + user.Dizzy(120) + +//////////////////////////////Capturing//////////////////////////////////////////////////////// +/obj/item/soulstone/attack(mob/living/carbon/human/M as mob, mob/user as mob) + if(!can_use(user)) + user.Paralyse(5) + to_chat(user, "Your body is wracked with debilitating pain!") + return + + if(spent) + to_chat(user, "There is no power left in the shard.") + return + + if(!ishuman(M)) //If target is not a human + return ..() + + if(M.has_brain_worms()) //Borer stuff - RR + to_chat(user, "This being is corrupted by an alien intelligence and cannot be soul trapped.") + return ..() + + if(jobban_isbanned(M, "cultist") || jobban_isbanned(M, "Syndicate")) + to_chat(user, "A mysterious force prevents you from trapping this being's soul.") + return ..() + + if(iscultist(user) && iscultist(M)) + 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!") + return + + to_chat(user, "You attempt to channel [M]'s soul into [src]. You must give the soul some time to react and stand still...") + + var/mob/player_mob = M + if(M.get_ghost())//in case our player ghosted and we need to throw the alert at their ghost instead + player_mob = M.get_ghost() + var/client/player_client = player_mob.client + to_chat(player_mob, "[user] is trying to capture your soul into [src]! Click the button in the top right of the game window to respond.") + player_client << 'sound/misc/notice2.ogg' + window_flash(player_client) + + var/obj/screen/alert/notify_soulstone/A = player_mob.throw_alert("\ref[src]_soulstone_thingy", /obj/screen/alert/notify_soulstone) + if(player_client.prefs && player_client.prefs.UI_style) + A.icon = ui_style2icon(player_client.prefs.UI_style) + + //pass the stuff to the alert itself + A.stone = src + A.stoner = user.real_name + + //layer shenanigans to make the alert display the soulstone + var/old_layer = layer + var/old_plane = plane + layer = FLOAT_LAYER + plane = FLOAT_PLANE + A.overlays += src + layer = old_layer + plane = old_plane + + //give the victim 10 seconds to respond + sleep(10 SECONDS) + + if(!opt_in) + to_chat(user, "The soul resists your attempts at capturing it!") + return + + opt_in = FALSE + + if(spent)//checking one more time against shenanigans + return + + add_attack_logs(user, M, "Stolestone'd with [name]") + transfer_soul("VICTIM", M, user) + return + +///////////////////Options for using captured souls/////////////////////////////////////// +/obj/item/soulstone/attack_self(mob/user) + if(!in_range(src, user)) + return + + if(!can_use(user)) + user.Paralyse(5) + to_chat(user, "Your body is wracked with debilitating pain!") + return + + user.set_machine(src) + var/dat = "Soul Stone
    " + for(var/mob/living/simple_animal/shade/A in src) + dat += "Captured Soul: [A.name]
    " + dat += {"Summon Shade"} + dat += "
    " + dat += {" Close"} + user << browse(dat, "window=aicard") + onclose(user, "aicard") + return + +/obj/item/soulstone/Topic(href, href_list) + var/mob/U = usr + if(!in_range(src, U) || U.machine != src || !can_use(usr)) + U << browse(null, "window=aicard") + U.unset_machine() + return + + add_fingerprint(U) + U.set_machine(src) + + switch(href_list["choice"])//Now we switch based on choice. + if("Close") + U << browse(null, "window=aicard") + U.unset_machine() + return + + if("Summon") + for(var/mob/living/simple_animal/shade/A in src) + A.status_flags &= ~GODMODE + A.canmove = 1 + A.forceMove(get_turf(usr)) + A.cancel_camera() + icon_state = "soulstone" + name = initial(name) + if(iswizard(usr) || usability) + to_chat(A, "You have been released from your prison, but you are still bound to [usr.real_name]'s will. Help [usr.p_them()] succeed in [usr.p_their()] goals at all costs.") + else if(iscultist(usr)) + to_chat(A, "You have been released from your prison, but you are still bound to the cult's will. Help [usr.p_them()] succeed in [usr.p_their()] goals at all costs.") + was_used() + attack_self(U) + +///////////////////////////Transferring to constructs///////////////////////////////////////////////////// +/obj/structure/constructshell + name = "empty shell" + icon = 'icons/obj/wizard.dmi' + icon_state = "construct-cult" + desc = "A wicked machine used by those skilled in magical arts. It is inactive" + +/obj/structure/constructshell/examine(mob/user) + . = ..() + if(in_range(user, src) && (iscultist(user) || iswizard(user) || user.stat == DEAD)) + . += "A construct shell, used to house bound souls from a soulstone." + . += "Placing a soulstone with a soul into this shell allows you to produce your choice of the following:" + . += "An Artificer, which can produce more shells and soulstones, as well as fortifications." + . += "A Wraith, which does high damage and can jaunt through walls, though it is quite fragile." + . += "A Juggernaut, which is very hard to kill and can produce temporary walls, but is slow." + +/obj/structure/constructshell/attackby(obj/item/O as obj, mob/user as mob, params) + if(istype(O, /obj/item/soulstone)) + var/obj/item/soulstone/SS = O + if(!SS.can_use(user)) + to_chat(user, "An overwhelming feeling of dread comes over you as you attempt to place the soulstone into the shell. It would be wise to be rid of this quickly.") + user.Dizzy(120) + return + SS.transfer_soul("CONSTRUCT",src,user) + SS.was_used() + else + return ..() + +////////////////////////////Proc for moving soul in and out off stone////////////////////////////////////// + +/obj/item/proc/transfer_soul(var/choice as text, var/target, var/mob/U as mob) + switch(choice) + if("FORCE") + var/obj/item/soulstone/SS = src + var/mob/living/T = target + if(T.client != null) + SS.init_shade(T, U) + else + to_chat(U, "Capture failed!: The soul has already fled its mortal frame. You attempt to bring it back...") + T.Paralyse(20) + if(!SS.getCultGhost(T,U)) + T.dust() //If we can't get a ghost, kill the sacrifice anyway. + + if("VICTIM") + var/mob/living/carbon/human/T = target + var/obj/item/soulstone/SS = src + if(T.stat == 0) + to_chat(U, "Capture failed!: Kill or maim the victim first!") + else + if(!T.client_mobs_in_contents?.len) + to_chat(U, "They have no soul!") + else + if(T.client == null) + to_chat(U, "Capture failed!: The soul has already fled its mortal frame. You attempt to bring it back...") + SS.getCultGhost(T,U) + else + if(SS.contents.len) + to_chat(U, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") + else + SS.init_shade(T, U, vic = 1) + + if("SHADE") + var/mob/living/simple_animal/shade/T = target + var/obj/item/soulstone/SS = src + if(!SS.can_use(U)) + U.Paralyse(5) + to_chat(U, "Your body is wracked with debilitating pain!") + return + if(T.stat == DEAD) + to_chat(U, "Capture failed!: The shade has already been banished!") + else + if(SS.contents.len) + to_chat(U, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.") + else + T.loc = SS //put shade in stone + T.status_flags |= GODMODE + T.canmove = 0 + T.health = T.maxHealth + T.faction |= "\ref[U]" + SS.icon_state = "soulstone2" + to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form") + to_chat(U, "Capture successful!: [T.name]'s has been recaptured and stored within the soul stone.") + if("CONSTRUCT") + var/obj/structure/constructshell/T = target + var/obj/item/soulstone/SS = src + var/mob/living/simple_animal/shade/SH = locate() in SS + if(SH) + var/construct_class = alert(U, "Please choose which type of construct you wish to create.",,"Juggernaut","Wraith","Artificer") + switch(construct_class) + if("Juggernaut") + var/mob/living/simple_animal/hostile/construct/armoured/C = new /mob/living/simple_animal/hostile/construct/armoured (get_turf(T.loc)) + to_chat(C, "You are a Juggernaut. Though slow, your shell can withstand extreme punishment, create shield walls and even deflect energy weapons, and rip apart enemies and walls alike.") + init_construct(C,SH,SS,T) + + if("Wraith") + var/mob/living/simple_animal/hostile/construct/wraith/C = new /mob/living/simple_animal/hostile/construct/wraith (get_turf(T.loc)) + to_chat(C, "You are a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls.") + init_construct(C,SH,SS,T) + + if("Artificer") + var/mob/living/simple_animal/hostile/construct/builder/C = new /mob/living/simple_animal/hostile/construct/builder (get_turf(T.loc)) + to_chat(C, "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, use magic missile, repair allied constructs (by clicking on them), and most important of all create new constructs (Use your Artificer spell to summon a new construct shell and Summon Soulstone to create a new soulstone).") + init_construct(C,SH,SS,T) + else + to_chat(U, "Creation failed!: The soul stone is empty! Go kill someone!") + return + +/proc/init_construct(mob/living/simple_animal/hostile/construct/C, mob/living/simple_animal/shade/SH, obj/item/soulstone/SS, obj/structure/constructshell/T) + SH.mind.transfer_to(C) + if(iscultist(C)) + var/datum/action/innate/cultcomm/CC = new() + CC.Grant(C) //We have to grant the cult comms again because they're lost during the mind transfer. + qdel(T) + qdel(SH) + to_chat(C, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + C.cancel_camera() + qdel(SS) + +/proc/makeNewConstruct(var/mob/living/simple_animal/hostile/construct/ctype, var/mob/target, var/mob/stoner = null, cultoverride = 0) + if(jobban_isbanned(target, "cultist") || jobban_isbanned(target, "Syndicate")) + return + var/mob/living/simple_animal/hostile/construct/newstruct = new ctype(get_turf(target)) + newstruct.faction |= "\ref[stoner]" + newstruct.key = target.key + if(stoner && iscultist(stoner) || cultoverride) + if(SSticker.mode.name == "cult") + SSticker.mode:add_cultist(newstruct.mind) + else + SSticker.mode.cult+=newstruct.mind + SSticker.mode.update_cult_icons_added(newstruct.mind) + if(stoner && iswizard(stoner)) + to_chat(newstruct, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + else if(stoner && iscultist(stoner)) + to_chat(newstruct, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.") + else + to_chat(newstruct, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") + newstruct.cancel_camera() + +/obj/item/soulstone/proc/init_shade(mob/living/T, mob/U, vic = 0) + new /obj/effect/decal/remains/human(T.loc) //Spawns a skeleton + T.invisibility = 101 + var/atom/movable/overlay/animation = new /atom/movable/overlay( T.loc ) + animation.icon_state = "blank" + animation.icon = 'icons/mob/mob.dmi' + animation.master = T + flick("dust-h", animation) + qdel(animation) + var/path = get_shade_type() + var/mob/living/simple_animal/shade/S = new path(src) + S.status_flags |= GODMODE //So they won't die inside the stone somehow + S.canmove = 0//Can't move out of the soul stone + S.name = "Shade of [T.real_name]" + S.real_name = "Shade of [T.real_name]" + S.key = T.key + S.cancel_camera() + name = "soulstone: Shade of [T.real_name]" + icon_state = "soulstone2" + if(U) + S.faction |= "\ref[U]" //Add the master as a faction, allowing inter-mob cooperation + if(iswizard(U)) + SSticker.mode.update_wiz_icons_added(S.mind) + S.mind.special_role = SPECIAL_ROLE_WIZARD_APPRENTICE + if(iscultist(U)) + SSticker.mode.add_cultist(S.mind, 0) + S.mind.special_role = SPECIAL_ROLE_CULTIST + S.mind.store_memory("Serve the cult's will.") + to_chat(S, "Your soul has been captured! You are now bound to the cult's will. Help them succeed in their goals at all costs.") + else + S.mind.store_memory("Serve [U.real_name], your creator.") + to_chat(S, "Your soul has been captured! You are now bound to [U.real_name]'s will. Help them succeed in their goals at all costs.") + if(vic && U) + to_chat(U, "Capture successful!: [T.real_name]'s soul has been ripped from [U.p_their()] body and stored within the soul stone.") + if(isrobot(T))//Robots have to dust or else they spill out an empty robot brain, and unequiping them spills robot components that shouldn't spawn. + T.dust() + else + for(var/obj/item/W in T) + T.unEquip(W) + qdel(T) + +/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 + +/obj/item/soulstone/proc/getCultGhost(mob/living/T, mob/U) + var/mob/dead/observer/chosen_ghost + + for(var/mob/dead/observer/ghost in GLOB.player_list) //We put them back in their body + if(ghost.mind && ghost.mind.current == T && ghost.client) + chosen_ghost = ghost + break + + if(!chosen_ghost) //Failing that, we grab a ghost + var/list/consenting_candidates = pollCandidates("Would you like to play as a Shade?", ROLE_CULTIST, FALSE, poll_time = 100) + if(consenting_candidates.len) + chosen_ghost = pick(consenting_candidates) + if(!T) + return 0 + if(!chosen_ghost) + to_chat(U, "There were no spirits willing to become a shade.") + return 0 + if(contents.len) //If they used the soulstone on someone else in the meantime + return 0 + T.ckey = chosen_ghost.ckey + init_shade(T, U) + return 1 diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm index 0b24362cf1c..f97f14a9975 100644 --- a/code/game/gamemodes/wizard/spellbook.dm +++ b/code/game/gamemodes/wizard/spellbook.dm @@ -1,1004 +1,1004 @@ -/datum/spellbook_entry - var/name = "Entry Name" - - var/spell_type = null - var/desc = "" - var/category = "Offensive" - var/log_name = "XX" //What it shows up as in logs - var/cost = 2 - var/refundable = 1 - var/surplus = -1 // -1 for infinite, not used by anything atm - var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell - var/buy_word = "Learn" - var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook - -/datum/spellbook_entry/proc/IsSpellAvailable() // For config prefs / gamemode restrictions - these are round applied - return 1 - -/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user, obj/item/spellbook/book) // Specific circumstances - if(book.uses= aspell.level_max) - to_chat(user, "This spell cannot be improved further.") - return 0 - else - aspell.name = initial(aspell.name) - aspell.spell_level++ - aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) - if(aspell.charge_max < aspell.charge_counter) - aspell.charge_counter = aspell.charge_max - switch(aspell.spell_level) - if(1) - to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") - aspell.name = "Efficient [aspell.name]" - if(2) - to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") - aspell.name = "Quickened [aspell.name]" - if(3) - to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") - aspell.name = "Free [aspell.name]" - if(4) - to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") - aspell.name = "Instant [aspell.name]" - if(aspell.spell_level >= aspell.level_max) - to_chat(user, "This spell cannot be strengthened any further.") - return 1 - //No same spell found - just learn it - feedback_add_details("wizard_spell_learned",log_name) - user.mind.AddSpell(S) - to_chat(user, "You have learned [S.name].") - return 1 - -/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user, obj/item/spellbook/book) - if(!refundable) - return 0 - if(!S) - S = new spell_type() - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - return 1 - return 0 - -/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) //return point value or -1 for failure - var/area/wizard_station/A = locate() - if(!(user in A.contents)) - to_chat(user, "You can only refund spells at the wizard lair") - return -1 - if(!S) - S = new spell_type() - var/spell_levels = 0 - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - spell_levels = aspell.spell_level - user.mind.spell_list.Remove(aspell) - QDEL_NULL(S) - return cost * (spell_levels+1) - return -1 - -/datum/spellbook_entry/proc/GetInfo() - if(!S) - S = new spell_type() - var/dat ="" - dat += "[initial(S.name)]" - if(S.charge_type == "recharge") - dat += " Cooldown:[S.charge_max/10]" - dat += " Cost:[cost]
    " - dat += "[S.desc][desc]
    " - dat += "[S.clothes_req?"Needs wizard garb":"Can be cast without wizard garb"]
    " - return dat - -//Main category - Spells -//Offensive -/datum/spellbook_entry/blind - name = "Blind" - spell_type = /obj/effect/proc_holder/spell/targeted/trigger/blind - log_name = "BD" - category = "Offensive" - cost = 1 - -/datum/spellbook_entry/lightningbolt - name = "Lightning Bolt" - spell_type = /obj/effect/proc_holder/spell/targeted/lightning - log_name = "LB" - category = "Offensive" - cost = 1 - -/datum/spellbook_entry/cluwne - name = "Curse of the Cluwne" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/cluwne - log_name = "CC" - category = "Offensive" - -/datum/spellbook_entry/banana_touch - name = "Banana Touch" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/banana - log_name = "BT" - cost = 1 - -/datum/spellbook_entry/mime_malaise - name = "Mime Malaise" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/mime_malaise - log_name = "MI" - cost = 1 - -/datum/spellbook_entry/horseman - name = "Curse of the Horseman" - spell_type = /obj/effect/proc_holder/spell/targeted/horsemask - log_name = "HH" - category = "Offensive" - -/datum/spellbook_entry/disintegrate - name = "Disintegrate" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate - log_name = "DG" - category = "Offensive" - -/datum/spellbook_entry/fireball - name = "Fireball" - spell_type = /obj/effect/proc_holder/spell/fireball - log_name = "FB" - category = "Offensive" - -/datum/spellbook_entry/fleshtostone - name = "Flesh to Stone" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone - log_name = "FS" - category = "Offensive" - -/datum/spellbook_entry/mutate - name = "Mutate" - spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate - log_name = "MU" - category = "Offensive" - -/datum/spellbook_entry/rod_form - name = "Rod Form" - spell_type = /obj/effect/proc_holder/spell/targeted/rod_form - log_name = "RF" - category = "Offensive" - -/datum/spellbook_entry/infinite_guns - name = "Lesser Summon Guns" - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns - log_name = "IG" - category = "Offensive" - cost = 4 - -//Defensive -/datum/spellbook_entry/disabletech - name = "Disable Tech" - spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech - log_name = "DT" - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/forcewall - name = "Force Wall" - spell_type = /obj/effect/proc_holder/spell/targeted/forcewall - log_name = "FW" - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/greaterforcewall - name = "Greater Force Wall" - spell_type = /obj/effect/proc_holder/spell/targeted/forcewall/greater - log_name = "GFW" - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/repulse - name = "Repulse" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse - log_name = "RP" - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/smoke - name = "Smoke" - spell_type = /obj/effect/proc_holder/spell/targeted/smoke - log_name = "SM" - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/lichdom - name = "Bind Soul" - spell_type = /obj/effect/proc_holder/spell/targeted/lichdom - log_name = "LD" - category = "Defensive" - -/datum/spellbook_entry/lichdom/IsSpellAvailable() - if(SSticker.mode.name == "ragin' mages") - return FALSE - else - return TRUE - -/datum/spellbook_entry/magicm - name = "Magic Missile" - spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile - log_name = "MM" - category = "Defensive" - -/datum/spellbook_entry/timestop - name = "Time Stop" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/timestop - log_name = "TS" - category = "Defensive" - -//Mobility -/datum/spellbook_entry/knock - name = "Knock" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock - log_name = "KN" - category = "Mobility" - cost = 1 - -/datum/spellbook_entry/blink - name = "Blink" - spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink - log_name = "BL" - category = "Mobility" - -/datum/spellbook_entry/jaunt - name = "Ethereal Jaunt" - spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt - log_name = "EJ" - category = "Mobility" - -/datum/spellbook_entry/greaterknock - name = "Greater Knock" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock/greater - log_name = "GK" - category = "Mobility" - refundable = 0 //global effect on cast - -/datum/spellbook_entry/mindswap - name = "Mindswap" - spell_type = /obj/effect/proc_holder/spell/targeted/mind_transfer - log_name = "MT" - category = "Mobility" - -/datum/spellbook_entry/teleport - name = "Teleport" - spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport - log_name = "TP" - category = "Mobility" - -//Assistance -/datum/spellbook_entry/charge - name = "Charge" - spell_type = /obj/effect/proc_holder/spell/targeted/charge - log_name = "CH" - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/summonitem - name = "Summon Item" - spell_type = /obj/effect/proc_holder/spell/targeted/summonitem - log_name = "IS" - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/noclothes - name = "Remove Clothes Requirement" - spell_type = /obj/effect/proc_holder/spell/noclothes - log_name = "NC" - category = "Assistance" - -//Rituals -/datum/spellbook_entry/summon - name = "Summon Stuff" - category = "Rituals" - refundable = FALSE - buy_word = "Cast" - var/active = FALSE - -/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user, obj/item/spellbook/book) - return ..() && !active - -/datum/spellbook_entry/summon/GetInfo() - var/dat ="" - dat += "[name]" - if(cost>0) - dat += " Cost:[cost]
    " - else - dat += " No Cost
    " - dat += "[desc]
    " - if(active) - dat += "Already cast!
    " - return dat - -/datum/spellbook_entry/summon/ghosts - name = "Summon Ghosts" - desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." - cost = 0 - log_name = "SGH" - -/datum/spellbook_entry/summon/ghosts/IsSpellAvailable() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(SSticker.mode.name == "ragin' mages") - return FALSE - else - return TRUE - -/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - new /datum/event/wizard/ghost() - active = TRUE - to_chat(user, "You have cast summon ghosts!") - playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1) - return TRUE - -/datum/spellbook_entry/summon/guns - name = "Summon Guns" - desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first." - log_name = "SG" - -/datum/spellbook_entry/summon/guns/IsSpellAvailable() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(SSticker.mode.name == "ragin' mages") - return FALSE - else - return TRUE - -/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - feedback_add_details("wizard_spell_learned", log_name) - rightandwrong(SUMMON_GUNS, user, 10) - active = TRUE - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) - to_chat(user, "You have cast summon guns!") - return TRUE - -/datum/spellbook_entry/summon/magic - name = "Summon Magic" - desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." - log_name = "SU" - -/datum/spellbook_entry/summon/magic/IsSpellAvailable() - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(SSticker.mode.name == "ragin' mages") - return FALSE - else - return TRUE - -/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - feedback_add_details("wizard_spell_learned", log_name) - rightandwrong(SUMMON_MAGIC, user, 10) - active = TRUE - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) - to_chat(user, "You have cast summon magic!") - return TRUE - -//Main category - Magical Items -/datum/spellbook_entry/item - name = "Buy Item" - refundable = 0 - buy_word = "Summon" - var/item_path = null - -/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - user.put_in_hands(new item_path) - feedback_add_details("wizard_spell_learned", log_name) - return 1 - -/datum/spellbook_entry/item/GetInfo() - var/dat ="" - dat += "[name]" - dat += " Cost:[cost]
    " - dat += "[desc]
    " - if(surplus>=0) - dat += "[surplus] left.
    " - return dat - -//Artefacts -/datum/spellbook_entry/item/necrostone - name = "A Necromantic Stone" - desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." - item_path = /obj/item/necromantic_stone - log_name = "NS" - category = "Artefacts" - -/datum/spellbook_entry/item/scryingorb - name = "Scrying Orb" - desc = "An incandescent orb of crackling energy, using it will allow you to ghost while alive, allowing you to spy upon the station with ease. In addition, buying it will permanently grant you x-ray vision." - item_path = /obj/item/scrying - log_name = "SO" - category = "Artefacts" - -/datum/spellbook_entry/item/scryingorb/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - if(..()) - if(!(XRAY in user.mutations)) - user.mutations.Add(XRAY) - user.sight |= (SEE_MOBS|SEE_OBJS|SEE_TURFS) - user.see_in_dark = 8 - user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - to_chat(user, "The walls suddenly disappear.") - return TRUE - -/datum/spellbook_entry/item/soulstones - name = "Six Soul Stone Shards and the spell Artificer" - desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." - item_path = /obj/item/storage/belt/soulstone/full - log_name = "SS" - category = "Artefacts" - -/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - . = ..() - if(.) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) - return . - -/datum/spellbook_entry/item/wands - name = "Wand Assortment" - desc = "A collection of wands that allow for a wide variety of utility. Wands do not recharge, so be conservative in use. Comes in a handy belt." - item_path = /obj/item/storage/belt/wands/full - log_name = "WA" - category = "Artefacts" - -//Weapons and Armors -/datum/spellbook_entry/item/battlemage - name = "Battlemage Armour" - desc = "An ensorceled suit of armour, protected by a powerful shield. The shield can completely negate sixteen attacks before being permanently depleted." - item_path = /obj/item/clothing/suit/space/hardsuit/shielded/wizard - limit = 1 - category = "Weapons and Armors" - log_name = "BMA" - -/datum/spellbook_entry/item/battlemage_charge - name = "Battlemage Armour Charges" - desc = "A powerful defensive rune, it will grant eight additional charges to a suit of battlemage armour." - item_path = /obj/item/wizard_armour_charge - category = "Weapons and Armors" - cost = 1 - log_name = "BMAC" - -/datum/spellbook_entry/item/mjolnir - name = "Mjolnir" - desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." - item_path = /obj/item/twohanded/mjollnir - log_name = "MJ" - category = "Weapons and Armors" - -/datum/spellbook_entry/item/singularity_hammer - name = "Singularity Hammer" - desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everthing nearby to the point of impact." - item_path = /obj/item/twohanded/singularityhammer - log_name = "SI" - category = "Weapons and Armors" - -//Staves -/datum/spellbook_entry/item/staffdoor - name = "Staff of Door Creation" - desc = "A particular staff that can mold solid metal into ornate wooden doors. Useful for getting around in the absence of other transportation. Does not work on glass." - item_path = /obj/item/gun/magic/staff/door - log_name = "SD" - category = "Staves" - cost = 1 - -/datum/spellbook_entry/item/staffhealing - name = "Staff of Healing" - desc = "An altruistic staff that can heal the lame and raise the dead." - item_path = /obj/item/gun/magic/staff/healing - log_name = "SH" - category = "Staves" - cost = 1 - -/datum/spellbook_entry/item/staffslipping - name = "Staff of Slipping" - desc = "A staff that shoots magical bananas. These bananas will either slip or stun the target when hit. Surprisingly reliable!" - item_path = /obj/item/gun/magic/staff/slipping - log_name = "SL" - category = "Staves" - cost = 1 - -/datum/spellbook_entry/item/staffanimation - name = "Staff of Animation" - desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." - item_path = /obj/item/gun/magic/staff/animate - log_name = "SA" - category = "Staves" - -/datum/spellbook_entry/item/staffchange - name = "Staff of Change" - desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." - item_path = /obj/item/gun/magic/staff/change - log_name = "ST" - category = "Staves" - -/datum/spellbook_entry/item/staffchaos - name = "Staff of Chaos" - desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." - item_path = /obj/item/gun/magic/staff/chaos - log_name = "SC" - category = "Staves" - -//Summons -/datum/spellbook_entry/item/oozebottle - name = "Bottle of Ooze" - desc = "A bottle of magically infused ooze, which will awake an all-consuming Morph, capable of cunningly disguising itself as any object it comes in contact with and even casting some very basic spells. Be careful though, as Morph diet includes Wizards." - item_path = /obj/item/antag_spawner/morph - log_name = "BO" - category = "Summons" - limit = 3 - cost = 1 - -/datum/spellbook_entry/item/hugbottle - name = "Bottle of Tickles" - desc = "A bottle of magically infused fun, the smell of which will \ - attract adorable extradimensional beings when broken. These beings \ - are similar to slaughter demons, but are a little weaker and they do not permamently \ - kill their victims, instead putting them in an extradimensional hugspace, \ - to be released on the demon's death. Chaotic, but not ultimately \ - damaging. The crew's reaction to the other hand could be very \ - destructive." - item_path = /obj/item/antag_spawner/slaughter_demon/laughter - log_name = "HB" - category = "Summons" - limit = 3 - cost = 1 // Non-destructive; it's just a jape, sibling! - -/datum/spellbook_entry/item/bloodbottle - name = "Bottle of Blood" - desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim." - item_path = /obj/item/antag_spawner/slaughter_demon - log_name = "BB" - category = "Summons" - limit = 3 - -/datum/spellbook_entry/item/contract - name = "Contract of Apprenticeship" - desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." - item_path = /obj/item/contract - log_name = "CT" - category = "Summons" - -/datum/spellbook_entry/item/tarotdeck - name = "Guardian Deck" - desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ - It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." - item_path = /obj/item/guardiancreator - log_name = "TD" - category = "Summons" - limit = 1 - -/obj/item/spellbook - name = "spell book" - desc = "The legendary book of spells of the wizard." - icon = 'icons/obj/library.dmi' - icon_state = "spellbook" - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/uses = 10 - var/temp = null - var/op = 1 - var/tab = null - var/main_tab = null - var/mob/living/carbon/human/owner - var/list/datum/spellbook_entry/entries = list() - var/list/categories = list() - var/list/main_categories = list("Spells", "Magical Items") - var/list/spell_categories = list("Offensive", "Defensive", "Mobility", "Assistance", "Rituals") - var/list/item_categories = list("Artefacts", "Weapons and Armors", "Staves", "Summons") - -/obj/item/spellbook/proc/initialize() - var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - for(var/T in entry_types) - var/datum/spellbook_entry/E = new T - if(E.IsSpellAvailable()) - entries |= E - categories |= E.category - else - qdel(E) - main_tab = main_categories[1] - tab = categories[1] - -/obj/item/spellbook/New() - ..() - initialize() - -/obj/item/spellbook/attackby(obj/item/O as obj, mob/user as mob, params) - if(istype(O, /obj/item/contract)) - var/obj/item/contract/contract = O - if(contract.used) - to_chat(user, "The contract has been used, you can't get your points back now!") - else - to_chat(user, "You feed the contract back into the spellbook, refunding your points.") - uses+=2 - qdel(O) - return - - if(istype(O, /obj/item/antag_spawner/slaughter_demon)) - to_chat(user, "On second thought, maybe summoning a demon is a bad idea. You refund your points.") - if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) - uses += 1 - for(var/datum/spellbook_entry/item/hugbottle/HB in entries) - if(!isnull(HB.limit)) - HB.limit++ - else - uses += 2 - for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) - if(!isnull(BB.limit)) - BB.limit++ - qdel(O) - return - - if(istype(O, /obj/item/antag_spawner/morph)) - to_chat(user, "On second thought, maybe awakening a morph is a bad idea. You refund your points.") - uses += 1 - for(var/datum/spellbook_entry/item/oozebottle/OB in entries) - if(!isnull(OB.limit)) - OB.limit++ - qdel(O) - return - return ..() - -/obj/item/spellbook/proc/GetCategoryHeader(category) - var/dat = "" - switch(category) - if("Offensive") - dat += "Spells geared towards debilitating and destroying.

    " - dat += "For spells: the number after the spell name is the cooldown time.
    " - dat += "You can reduce this number by spending more points on the spell.
    " - if("Defensive") - dat += "Spells geared towards improving your survivabilty or reducing foes ability to attack.

    " - dat += "For spells: the number after the spell name is the cooldown time.
    " - dat += "You can reduce this number by spending more points on the spell.
    " - if("Mobility") - dat += "Spells geared towards improving your ability to move. It is a good idea to take at least one.

    " - dat += "For spells: the number after the spell name is the cooldown time.
    " - dat += "You can reduce this number by spending more points on the spell.
    " - if("Assistance") - dat += "Spells geared towards improving your other items and abilities.

    " - dat += "For spells: the number after the spell name is the cooldown time.
    " - dat += "You can reduce this number by spending more points on the spell.
    " - if("Rituals") - dat += "These powerful spells are capable of changing the very fabric of reality. Not always in your favour.
    " - if("Weapons and Armors") - dat += "Various weapons and armors to crush your enemies and protect you from harm.

    " - dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
    " - if("Staves") - dat += "Various staves granting you their power, which they slowly recharge over time.

    " - dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
    " - if("Artefacts") - dat += "Various magical artefacts to aid you.

    " - dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
    " - if("Summons") - dat += "Magical items geared towards bringing in outside forces to aid you.

    " - dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
    " - return dat - -/obj/item/spellbook/proc/wrap(content) - var/dat = "" - dat +="Spellbook" - dat += {" - - - - "} - dat += {"[content]"} - return dat - -/obj/item/spellbook/attack_self(mob/user as mob) - if(!owner) - to_chat(user, "You bind the spellbook to yourself.") - owner = user - return - if(user != owner) - to_chat(user, "The [name] does not recognize you as it's owner and refuses to open!") - return - user.set_machine(src) - var/dat = "" - - dat += "
      " - var/list/cat_dat = list() - for(var/main_category in main_categories) - cat_dat[main_category] = "
      " - dat += "
    • [main_category]
    • " - dat += "
    " - switch(main_tab) - if("Spells") - dat += "

    fDiT`I`Z(ct>`S$g4+j{ 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 X>j6$;O8o!;5CZDT+Dac3tzZ2Y`6P}_ diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi index 15a4adb9f416b181bfea225866b636e022f8099c..bb7a1ce9188484dbe1f6f677e740cbec0b82dcbd 100644 GIT binary patch delta 19613 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 SZ2lU64ofpz(|Ti%oBsh)G5U)D From 7382687db469c7141f1e79e326ca4cdd8ce9f2c9 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Wed, 6 Nov 2019 07:50:05 -0500 Subject: [PATCH 033/238] grammar --- code/modules/clothing/under/miscellaneous.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index b82a04ad263..f778f7b068a 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -97,7 +97,7 @@ /obj/item/clothing/under/rank/centcom/officer desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Officers Uniform" + name = "\improper Nanotrasen Naval Officer Uniform" icon_state = "navy_gold" item_state = "navy_gold" item_color = "navy_gold" @@ -107,7 +107,7 @@ /obj/item/clothing/under/rank/centcom/officer/captain desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Captains Uniform" + name = "\improper Nanotrasen Naval Captain Uniform" icon_state = "navy_gold" item_state = "navy_gold" item_color = "navy_gold" From 123636dad4ea1cea69f14a5a24a740c3e282d5b5 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Wed, 6 Nov 2019 07:56:43 -0500 Subject: [PATCH 034/238] makes it compile --- code/game/jobs/job/central.dm | 2 +- code/modules/clothing/under/miscellaneous.dm | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm index 695e4a1ec39..1d62afcb266 100644 --- a/code/game/jobs/job/central.dm +++ b/code/game/jobs/job/central.dm @@ -66,7 +66,7 @@ /datum/outfit/job/ntspecops name = "Special Operations Officer" jobtype = /datum/job/ntspecops - uniform = /obj/item/clothing/under/rank/centcom/officer/captain + uniform = /obj/item/clothing/under/rank/centcom/captain suit = /obj/item/clothing/suit/space/deathsquad/officer back = /obj/item/storage/backpack/security belt = /obj/item/storage/belt/military/assault diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index f778f7b068a..ba37b4911f1 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -97,7 +97,7 @@ /obj/item/clothing/under/rank/centcom/officer desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Officer Uniform" + name = "\improper Nanotrasen Naval Officers Uniform" icon_state = "navy_gold" item_state = "navy_gold" item_color = "navy_gold" @@ -105,13 +105,15 @@ flags_size = ONESIZEFITSALL armor = list("melee" = 60, "bullet" = 60, "laser" = 60,"energy" = 60, "bomb" = 60, "bio" = 100, "rad" = 30, "fire" = 60, "acid" = 100) -/obj/item/clothing/under/rank/centcom/officer/captain +/obj/item/clothing/under/rank/centcom/captain desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Captain Uniform" + name = "\improper Nanotrasen Naval Captains Uniform" icon_state = "navy_gold" item_state = "navy_gold" item_color = "navy_gold" displays_id = 0 + flags_size = ONESIZEFITSALL + armor = list("melee" = 60, "bullet" = 60, "laser" = 60,"energy" = 60, "bomb" = 60, "bio" = 100, "rad" = 30, "fire" = 60, "acid" = 100) /obj/item/clothing/under/rank/centcom/blueshield desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant\" and bears \"N.S.S. Cyberiad\" on the left shoulder." From 865cd321431c661dc59acbf96b6d3d3691b59fa7 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Wed, 6 Nov 2019 07:58:50 -0500 Subject: [PATCH 035/238] removes new() blueshield --- code/modules/clothing/under/miscellaneous.dm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 01264fc07f8..7188fc480af 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -121,10 +121,6 @@ displays_id = 0 flags_size = ONESIZEFITSALL -/obj/item/clothing/under/rank/centcom/blueshield/New() - ..() - desc = "Gold trim on space-black cloth, this uniform bears \"Close Protection\" on the left shoulder." - /obj/item/clothing/under/rank/centcom/representative desc = "Gold trim on space-black cloth, this uniform bears \"N.S.S. Cyberiad\" on the left shoulder." name = "\improper Formal Nanotrasen Uniform" From 7e26d120bcfc810314ffbb5a43deeb331224c930 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Wed, 6 Nov 2019 08:00:19 -0500 Subject: [PATCH 036/238] grammar again sorry --- code/modules/clothing/under/miscellaneous.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index ba37b4911f1..6ab67a2371c 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -97,7 +97,7 @@ /obj/item/clothing/under/rank/centcom/officer desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Officers Uniform" + name = "\improper Nanotrasen Naval Officer Uniform" icon_state = "navy_gold" item_state = "navy_gold" item_color = "navy_gold" @@ -107,7 +107,7 @@ /obj/item/clothing/under/rank/centcom/captain desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Captains Uniform" + name = "\improper Nanotrasen Naval Captain Uniform" icon_state = "navy_gold" item_state = "navy_gold" item_color = "navy_gold" From db82363bfb63ed77916af367286de8c5e09e91ed Mon Sep 17 00:00:00 2001 From: Kyep Date: Wed, 13 Nov 2019 20:41:28 -0800 Subject: [PATCH 037/238] Adds forum-based link config setting --- code/controllers/configuration.dm | 4 ++++ config/example/config.txt | 5 ++++- interface/interface.dm | 14 +++++++++----- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index d24e4dc94f5..86883654997 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -102,6 +102,7 @@ var/donationsurl = "http://example.org" var/repositoryurl = "http://example.org" var/discordurl = "http://example.org" + var/discordforumurl = "http://example.org" var/overflow_server_url var/forbid_singulo_possession = 0 @@ -487,6 +488,9 @@ if("discordurl") config.discordurl = value + if("discordforumurl") + config.discordforumurl = value + if("donationsurl") config.donationsurl = value diff --git a/config/example/config.txt b/config/example/config.txt index 27da54922bb..f4de07f581f 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -240,6 +240,9 @@ CHECK_RANDOMIZER ## Discord address # DISCORDURL http://example.org +## Discord address (forum-based invite) +# DISCORDFORUMURL http://example.org + ## Donations address # DONATIONSURL http://example.org @@ -454,4 +457,4 @@ DISABLE_HIGH_POP_MC_MODE_AMOUNT 60 #START_NOW_CONFIRMATION ## If uncommented, all gamemodes will respect the number of required players. Defaults to no. -#ENABLE_GAMEMODE_PLAYER_LIMIT \ No newline at end of file +#ENABLE_GAMEMODE_PLAYER_LIMIT diff --git a/interface/interface.dm b/interface/interface.dm index a733b8fed78..a97500ccb4f 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -81,12 +81,16 @@ set name = "Discord" set desc = "Join our Discord server." set hidden = 1 - if(config.discordurl) - if(alert("This will invite you to our Discord server. Are you sure?",,"Yes","No")=="No") - return - src << link(config.discordurl) - else + + var/durl = config.discordurl + if(config.forum_link_url && prefs && prefs.fuid && config.discordforumurl) + durl = config.discordforumurl + if(!durl) to_chat(src, "The Discord URL is not set in the server configuration.") + return + if(alert("This will invite you to our Discord server. Are you sure?",,"Yes","No")=="No") + return + src << link(durl) /client/verb/donate() set name = "Donate" From c978174646fc9d789e72b32c39e0b60b81964b93 Mon Sep 17 00:00:00 2001 From: Kyep Date: Fri, 15 Nov 2019 20:41:58 -0800 Subject: [PATCH 038/238] Fixes a bug with depot comms computer not recognizing some agents --- code/game/machinery/computer/depot.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/machinery/computer/depot.dm b/code/game/machinery/computer/depot.dm index 02be740b7d3..963ac95bb5a 100644 --- a/code/game/machinery/computer/depot.dm +++ b/code/game/machinery/computer/depot.dm @@ -308,7 +308,7 @@ if(check_rights(R_ADMIN, 0, user)) depotarea.peaceful_mode(FALSE, TRUE) else if (subcommand == DEPOT_VISITOR_ADD) - if(user.mind && user.mind.special_role == SPECIAL_ROLE_TRAITOR) + if(user.mind && (user.mind.special_role == SPECIAL_ROLE_TRAITOR || user.mind.special_role == ROLE_TRAITOR)) if(depotarea.list_includes(user, depotarea.peaceful_list)) to_chat(user, "[user] is already signed in as a visiting agent.") else @@ -320,7 +320,7 @@ to_chat(user, "Visitor sign-in is not possible after supplies have been taken from a locker in the depot.") else if("syndicate" in user.faction) to_chat(user, "You are already recognized as a member of the Syndicate, and do not need to sign in.") - else if(user.mind && user.mind.special_role == SPECIAL_ROLE_TRAITOR) + else if(user.mind && (user.mind.special_role == SPECIAL_ROLE_TRAITOR || user.mind.special_role == ROLE_TRAITOR)) grant_syndie_faction(user) depotarea.peaceful_mode(TRUE, TRUE) else From 513367350aa68464012543c3407a8234651ef482 Mon Sep 17 00:00:00 2001 From: Kyep Date: Wed, 27 Nov 2019 06:32:40 -0800 Subject: [PATCH 039/238] Revert "Fixes a bug with depot comms computer not recognizing some agents" This reverts commit c978174646fc9d789e72b32c39e0b60b81964b93. --- code/game/machinery/computer/depot.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/machinery/computer/depot.dm b/code/game/machinery/computer/depot.dm index 963ac95bb5a..02be740b7d3 100644 --- a/code/game/machinery/computer/depot.dm +++ b/code/game/machinery/computer/depot.dm @@ -308,7 +308,7 @@ if(check_rights(R_ADMIN, 0, user)) depotarea.peaceful_mode(FALSE, TRUE) else if (subcommand == DEPOT_VISITOR_ADD) - if(user.mind && (user.mind.special_role == SPECIAL_ROLE_TRAITOR || user.mind.special_role == ROLE_TRAITOR)) + if(user.mind && user.mind.special_role == SPECIAL_ROLE_TRAITOR) if(depotarea.list_includes(user, depotarea.peaceful_list)) to_chat(user, "[user] is already signed in as a visiting agent.") else @@ -320,7 +320,7 @@ to_chat(user, "Visitor sign-in is not possible after supplies have been taken from a locker in the depot.") else if("syndicate" in user.faction) to_chat(user, "You are already recognized as a member of the Syndicate, and do not need to sign in.") - else if(user.mind && (user.mind.special_role == SPECIAL_ROLE_TRAITOR || user.mind.special_role == ROLE_TRAITOR)) + else if(user.mind && user.mind.special_role == SPECIAL_ROLE_TRAITOR) grant_syndie_faction(user) depotarea.peaceful_mode(TRUE, TRUE) else From 4df1db1c13726359b9e84acc14621bd2ae54d2c0 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 13 Dec 2019 15:49:27 -0500 Subject: [PATCH 040/238] changed to artificer walls --- code/game/gamemodes/cult/cult_structures.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm index d30ac4f8a68..7d7f3134a6a 100644 --- a/code/game/gamemodes/cult/cult_structures.dm +++ b/code/game/gamemodes/cult/cult_structures.dm @@ -226,7 +226,7 @@ var/list/blacklisted_pylon_turfs = typecacheof(list( if(istype(T, /turf/simulated/floor)) T.ChangeTurf(/turf/simulated/floor/engine/cult) if(istype(T, /turf/simulated/wall)) - T.ChangeTurf(/turf/simulated/wall/cult) + T.ChangeTurf(/turf/simulated/wall/cult/artificer) else var/turf/simulated/floor/engine/cult/F = safepick(cultturfs) if(F) From 7acb902388d3afc85cd362380f8be4f08f5405e0 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 15 Dec 2019 16:28:36 -0800 Subject: [PATCH 041/238] SQL update: track playtime per player per day for stats --- SQL/paradise_schema.sql | 15 +++++++++++++++ SQL/paradise_schema_prefixed.sql | 14 ++++++++++++++ SQL/updates/9-10.sql | 11 +++++++++++ code/__DEFINES/misc.dm | 2 +- code/game/jobs/job_exp.dm | 14 ++++++++++++-- config/example/dbconfig.txt | 2 +- 6 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 SQL/updates/9-10.sql diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql index 1e4692b7e3b..445de8b4d08 100644 --- a/SQL/paradise_schema.sql +++ b/SQL/paradise_schema.sql @@ -565,3 +565,18 @@ CREATE TABLE `oauth_tokens` ( PRIMARY KEY (`token`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; + + +-- +-- Table structure for table `playtime_history` +-- +DROP TABLE IF EXISTS `playtime_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `playtime_history` ( + `ckey` varchar(32) NOT NULL, + `date` DATE NOT NULL, + `time_living` SMALLINT NOT NULL, + `time_ghost` SMALLINT NOT NULL, + PRIMARY KEY (`ckey`, `date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; \ No newline at end of file diff --git a/SQL/paradise_schema_prefixed.sql b/SQL/paradise_schema_prefixed.sql index aa5d5895e56..f70593fc1ac 100644 --- a/SQL/paradise_schema_prefixed.sql +++ b/SQL/paradise_schema_prefixed.sql @@ -564,3 +564,17 @@ CREATE TABLE `SS13_oauth_tokens` ( PRIMARY KEY (`token`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `SS13_playtime_history` +-- +DROP TABLE IF EXISTS `SS13_playtime_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `SS13_playtime_history` ( + `ckey` varchar(32) NOT NULL, + `date` DATE NOT NULL, + `time_living` SMALLINT NOT NULL, + `time_ghost` SMALLINT NOT NULL, + PRIMARY KEY (`ckey`, `date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; \ No newline at end of file diff --git a/SQL/updates/9-10.sql b/SQL/updates/9-10.sql new file mode 100644 index 00000000000..f82e7c56830 --- /dev/null +++ b/SQL/updates/9-10.sql @@ -0,0 +1,11 @@ +# Updating SQL from ver 9 to 10 - Kyet + +# Add the 'playtime_history' table that tracks playtime per player per day +CREATE TABLE `playtime_history` ( + `ckey` varchar(32) NOT NULL, + `date` DATE NOT NULL, + `time_living` SMALLINT NOT NULL, + `time_ghost` SMALLINT NOT NULL, + PRIMARY KEY (`ckey`, `date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 7dd5fc5a609..5a2c087e1fd 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -319,7 +319,7 @@ #define INVESTIGATE_BOMB "bombs" // The SQL version required by this version of the code -#define SQL_VERSION 9 +#define SQL_VERSION 10 // Vending machine stuff #define CAT_NORMAL 1 diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm index 1b64b3e8aee..b69dbfd3ed4 100644 --- a/code/game/jobs/job_exp.dm +++ b/code/game/jobs/job_exp.dm @@ -263,8 +263,11 @@ var/global/list/role_playtime_requirements = list( myrole = mob.mind.playtime_role else if(mob.mind.assigned_role) myrole = mob.mind.assigned_role + var/added_living = 0 + var/added_ghost = 0 if(mob.stat == CONSCIOUS && myrole) play_records[EXP_TYPE_LIVING] += minutes + added_living += minutes if(announce_changes) to_chat(mob,"You got: [minutes] Living EXP!") for(var/category in exp_jobsmap) @@ -279,6 +282,7 @@ var/global/list/role_playtime_requirements = list( to_chat(mob,"You got: [minutes] Special EXP!") else if(isobserver(mob)) play_records[EXP_TYPE_GHOST] += minutes + added_ghost += minutes if(announce_changes) to_chat(mob,"You got: [minutes] Ghost EXP!") else @@ -289,6 +293,12 @@ var/global/list/role_playtime_requirements = list( var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("player")] SET exp = '[new_exp]',lastseen = Now() WHERE ckey='[ckey]'") if(!update_query.Execute()) var/err = update_query.ErrorMsg() - log_game("SQL ERROR during exp_update_client write. Error : \[[err]\]\n") - message_admins("SQL ERROR during exp_update_client write. Error : \[[err]\]\n") + log_game("SQL ERROR during exp_update_client write 1. Error : \[[err]\]\n") + message_admins("SQL ERROR during exp_update_client write 1. Error : \[[err]\]\n") + return + var/DBQuery/update_query_history = dbcon.NewQuery("INSERT INTO [format_table_name("playtime_history")] (ckey, date, time_living, time_ghost) VALUES ('[ckey]',CURDATE(),[added_living],[added_ghost]) ON DUPLICATE KEY UPDATE time_living=time_living+VALUES(time_living),time_ghost=time_ghost+VALUES(time_ghost)") + if(!update_query_history.Execute()) + var/err = update_query_history.ErrorMsg() + log_game("SQL ERROR during exp_update_client write 2. Error : \[[err]\]\n") + message_admins("SQL ERROR during exp_update_client write 2. Error : \[[err]\]\n") return \ No newline at end of file diff --git a/config/example/dbconfig.txt b/config/example/dbconfig.txt index ad859d92543..654a7e348d5 100644 --- a/config/example/dbconfig.txt +++ b/config/example/dbconfig.txt @@ -9,7 +9,7 @@ ## This value must be set to the version of the paradise schema in use. ## If this value does not match, the SQL database will not be loaded and an error will be generated. ## Roundstart will be delayed. -DB_VERSION 9 +DB_VERSION 10 ## Server the MySQL database can be found at. # Examples: localhost, 200.135.5.43, www.mysqldb.com, etc. From 9783ac1598554f0f6d74731038d5ef8376c1fad4 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 15 Dec 2019 18:04:06 -0800 Subject: [PATCH 042/238] add mentor to list of predefined admin ranks --- code/modules/admin/topic.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 4abdc89677c..dfba257b66c 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -212,7 +212,7 @@ if(admin_ranks.len) new_rank = input("Please select a rank", "New rank", null, null) as null|anything in (admin_ranks|"*New Rank*") else - new_rank = input("Please select a rank", "New rank", null, null) as null|anything in list("Game Master","Game Admin", "Trial Admin", "Admin Observer","*New Rank*") + new_rank = input("Please select a rank", "New rank", null, null) as null|anything in list("Mentor", "Trial Admin", "Game Admin", "*New Rank*") var/rights = 0 if(D) From 76f4c88fea6d9d9d171771a27e0848b0a587ea82 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 15 Dec 2019 18:30:31 -0800 Subject: [PATCH 043/238] fixes admin_log not working, flags edited admins/mentors for sync --- code/modules/admin/permissionverbs/permissionedit.dm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/code/modules/admin/permissionverbs/permissionedit.dm b/code/modules/admin/permissionverbs/permissionedit.dm index 9d9e60db014..57d58533437 100644 --- a/code/modules/admin/permissionverbs/permissionedit.dm +++ b/code/modules/admin/permissionverbs/permissionedit.dm @@ -86,17 +86,18 @@ new_admin = 0 admin_id = text2num(select_query.item[1]) + flag_account_for_forum_sync(adm_ckey) if(new_admin) var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin")] (`id`, `ckey`, `rank`, `level`, `flags`) VALUES (null, '[adm_ckey]', '[new_rank]', -1, 0)") insert_query.Execute() - var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO `test`.[format_table_name("admin_log")] (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new admin [adm_ckey] to rank [new_rank]');") + var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Added new admin [adm_ckey] to rank [new_rank]');") log_query.Execute() to_chat(usr, "New admin added.") else if(!isnull(admin_id) && isnum(admin_id)) var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET rank = '[new_rank]' WHERE id = [admin_id]") insert_query.Execute() - var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO `test`.[format_table_name("admin_log")] (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Edited the rank of [adm_ckey] to [new_rank]');") + var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Edited the rank of [adm_ckey] to [new_rank]');") log_query.Execute() to_chat(usr, "Admin rank changed.") @@ -141,16 +142,17 @@ if(!admin_id) return + flag_account_for_forum_sync(adm_ckey) if(admin_rights & new_permission) //This admin already has this permission, so we are removing it. var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET flags = [admin_rights & ~new_permission] WHERE id = [admin_id]") insert_query.Execute() - var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO `test`.[format_table_name("admin_log")] (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]');") + var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Removed permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]');") log_query.Execute() to_chat(usr, "Permission removed.") else //This admin doesn't have this permission, so we are adding it. var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET flags = '[admin_rights | new_permission]' WHERE id = [admin_id]") insert_query.Execute() - var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO `test`.[format_table_name("admin_log")] (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]')") + var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Added permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]')") log_query.Execute() to_chat(usr, "Permission added.") @@ -165,3 +167,4 @@ var/DBQuery/query_update = dbcon.NewQuery("UPDATE [format_table_name("player")] SET lastadminrank = '[sql_admin_rank]' WHERE ckey = '[sql_ckey]'") query_update.Execute() + flag_account_for_forum_sync(sql_ckey) \ No newline at end of file From fe53a5acf9a2dcd3a62043229fd08251359b7cf7 Mon Sep 17 00:00:00 2001 From: Kyep Date: Sun, 15 Dec 2019 19:19:50 -0800 Subject: [PATCH 044/238] fixes broken watchlist functionality --- code/modules/admin/admin.dm | 2 +- code/modules/admin/watchlist.dm | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 1801324e72d..55fb1c4047a 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -103,7 +103,7 @@ var/global/nologevent = 0 if(config.forum_playerinfo_url) body += "WebInfo | " if(M.client) - if(M.client.check_watchlist(M.client.ckey)) + if(check_watchlist(M.client.ckey)) body += "Remove from Watchlist | " body += "Edit Watchlist Reason " else diff --git a/code/modules/admin/watchlist.dm b/code/modules/admin/watchlist.dm index 040bc08112b..a908c2c264b 100644 --- a/code/modules/admin/watchlist.dm +++ b/code/modules/admin/watchlist.dm @@ -113,9 +113,7 @@ output += "
    [reason]

      " - for(var/category in categories) - if(category in item_categories) - continue - cat_dat[category] = "
      " - dat += "
    • [category]
    • " - dat += "
    • Points remaining : [uses]
    • " - if("Magical Items") - dat += "
        " - for(var/category in categories) - if(category in spell_categories) - continue - cat_dat[category] = "
        " - dat += "
      • [category]
      • " - dat += "
      • Points remaining : [uses]
      • " - dat += "
      " - - var/datum/spellbook_entry/E - for(var/i=1,i<=entries.len,i++) - var/spell_info = "" - E = entries[i] - spell_info += E.GetInfo() - if(E.CanBuy(user,src)) - spell_info+= "[E.buy_word]
      " - else - spell_info+= "Can't [E.buy_word]
      " - if(E.CanRefund(user,src)) - spell_info+= "Refund
      " - spell_info += "
      " - if(cat_dat[E.category]) - cat_dat[E.category] += spell_info - - for(var/category in categories) - dat += "
      " - dat += GetCategoryHeader(category) - dat += cat_dat[category] - dat += "
      " - - user << browse(wrap(dat), "window=spellbook;size=800x600") - onclose(user, "spellbook") - return - -/obj/item/spellbook/Topic(href, href_list) - if(..()) - return 1 - var/mob/living/carbon/human/H = usr - - if(!ishuman(H)) - return 1 - - if(H.mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE) - temp = "If you got caught sneaking a peak from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." - return 1 - - var/datum/spellbook_entry/E = null - if(loc == H || (in_range(src, H) && istype(loc, /turf))) - H.set_machine(src) - if(href_list["buy"]) - E = entries[text2num(href_list["buy"])] - if(E && E.CanBuy(H,src)) - if(E.Buy(H,src)) - if(E.limit) - E.limit-- - uses -= E.cost - else if(href_list["refund"]) - E = entries[text2num(href_list["refund"])] - if(E && E.refundable) - var/result = E.Refund(H,src) - if(result > 0) - if(!isnull(E.limit)) - E.limit += result - uses += result - else if(href_list["mainpage"]) - main_tab = sanitize(href_list["mainpage"]) - tab = sanitize(href_list["page"]) - if(main_tab == "Spells") - tab = spell_categories[1] - else if(main_tab == "Magical Items") - tab = item_categories[1] - else if(href_list["page"]) - tab = sanitize(href_list["page"]) - attack_self(H) - return 1 - -//Single Use Spellbooks -/obj/item/spellbook/oneuse - var/spell = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile //just a placeholder to avoid runtimes if someone spawned the generic - var/spellname = "sandbox" - var/used = 0 - name = "spellbook of " - uses = 1 - desc = "This template spellbook was never meant for the eyes of man..." - -/obj/item/spellbook/oneuse/New() - ..() - name += spellname - -/obj/item/spellbook/oneuse/initialize() //No need to init - return - -/obj/item/spellbook/oneuse/attack_self(mob/user) - var/obj/effect/proc_holder/spell/S = new spell - for(var/obj/effect/proc_holder/spell/knownspell in user.mind.spell_list) - if(knownspell.type == S.type) - if(user.mind) - if(user.mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE || user.mind.special_role == SPECIAL_ROLE_WIZARD) - to_chat(user, "You're already far more versed in this spell than this flimsy how-to book can provide.") - else - to_chat(user, "You've already read this one.") - return - if(used) - recoil(user) - else - user.mind.AddSpell(S) - to_chat(user, "you rapidly read through the arcane book. Suddenly you realize you understand [spellname]!") - user.create_attack_log("[key_name(user)] learned the spell [spellname] ([S]).") - onlearned(user) - -/obj/item/spellbook/oneuse/proc/recoil(mob/user) - user.visible_message("[src] glows in a black light!") - -/obj/item/spellbook/oneuse/proc/onlearned(mob/user) - used = 1 - user.visible_message("[src] glows dark for a second!") - -/obj/item/spellbook/oneuse/attackby() - return - -/obj/item/spellbook/oneuse/fireball - spell = /obj/effect/proc_holder/spell/fireball - spellname = "fireball" - icon_state = "bookfireball" - desc = "This book feels warm to the touch." - -/obj/item/spellbook/oneuse/fireball/recoil(mob/user as mob) - ..() - explosion(user.loc, -1, 0, 2, 3, 0, flame_range = 2) - qdel(src) - -/obj/item/spellbook/oneuse/smoke - spell = /obj/effect/proc_holder/spell/targeted/smoke - spellname = "smoke" - icon_state = "booksmoke" - desc = "This book is overflowing with the dank arts." - -/obj/item/spellbook/oneuse/smoke/recoil(mob/user as mob) - ..() - to_chat(user, "Your stomach rumbles...") - user.adjust_nutrition(-200) - -/obj/item/spellbook/oneuse/blind - spell = /obj/effect/proc_holder/spell/targeted/trigger/blind - spellname = "blind" - icon_state = "bookblind" - desc = "This book looks blurry, no matter how you look at it." - -/obj/item/spellbook/oneuse/blind/recoil(mob/user as mob) - ..() - to_chat(user, "You go blind!") - user.EyeBlind(10) - -/obj/item/spellbook/oneuse/mindswap - spell = /obj/effect/proc_holder/spell/targeted/mind_transfer - spellname = "mindswap" - icon_state = "bookmindswap" - desc = "This book's cover is pristine, though its pages look ragged and torn." - var/mob/stored_swap = null //Used in used book recoils to store an identity for mindswaps - -/obj/item/spellbook/oneuse/mindswap/onlearned() - spellname = pick("fireball","smoke","blind","forcewall","knock","horses","charge") - icon_state = "book[spellname]" - name = "spellbook of [spellname]" //Note, desc doesn't change by design - ..() - -/obj/item/spellbook/oneuse/mindswap/recoil(mob/user) - ..() - if(stored_swap in GLOB.dead_mob_list) - stored_swap = null - if(!stored_swap) - stored_swap = user - to_chat(user, "For a moment you feel like you don't even know who you are anymore.") - return - if(stored_swap == user) - to_chat(user, "You stare at the book some more, but there doesn't seem to be anything else to learn...") - return - - var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new - swapper.cast(user, stored_swap, 1) - - to_chat(stored_swap, "You're suddenly somewhere else... and someone else?!") - to_chat(user, "Suddenly you're staring at [src] again... where are you, who are you?!") - stored_swap = null - -/obj/item/spellbook/oneuse/forcewall - spell = /obj/effect/proc_holder/spell/targeted/forcewall - spellname = "forcewall" - icon_state = "bookforcewall" - desc = "This book has a dedication to mimes everywhere inside the front cover." - -/obj/item/spellbook/oneuse/forcewall/recoil(mob/user as mob) - ..() - to_chat(user, "You suddenly feel very solid!") - var/obj/structure/closet/statue/S = new /obj/structure/closet/statue(user.loc, user) - S.timer = 30 - user.drop_item() - -/obj/item/spellbook/oneuse/knock - spell = /obj/effect/proc_holder/spell/aoe_turf/knock - spellname = "knock" - icon_state = "bookknock" - desc = "This book is hard to hold closed properly." - -/obj/item/spellbook/oneuse/knock/recoil(mob/user as mob) - ..() - to_chat(user, "You're knocked down!") - user.Weaken(20) - -/obj/item/spellbook/oneuse/horsemask - spell = /obj/effect/proc_holder/spell/targeted/horsemask - spellname = "horses" - icon_state = "bookhorses" - desc = "This book is more horse than your mind has room for." - -/obj/item/spellbook/oneuse/horsemask/recoil(mob/living/carbon/user as mob) - if(istype(user, /mob/living/carbon/human)) - to_chat(user, "HOR-SIE HAS RISEN") - var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead - magichead.flags |= NODROP | DROPDEL //curses! - magichead.flags_inv = null //so you can still see their face - magichead.voicechange = 1 //NEEEEIIGHH - if(!user.unEquip(user.wear_mask)) - qdel(user.wear_mask) - user.equip_to_slot_if_possible(magichead, slot_wear_mask, 1, 1) - qdel(src) - else - to_chat(user, "I say thee neigh") - -/obj/item/spellbook/oneuse/charge - spell = /obj/effect/proc_holder/spell/targeted/charge - spellname = "charging" - icon_state = "bookcharge" - desc = "This book is made of 100% post-consumer wizard." - -/obj/item/spellbook/oneuse/charge/recoil(mob/user as mob) - ..() - to_chat(user, "[src] suddenly feels very warm!") - empulse(src, 1, 1) - -/obj/item/spellbook/oneuse/summonitem - spell = /obj/effect/proc_holder/spell/targeted/summonitem - spellname = "instant summons" - icon_state = "booksummons" - desc = "This book is bright and garish, very hard to miss." - -/obj/item/spellbook/oneuse/summonitem/recoil(mob/user as mob) - ..() - to_chat(user, "[src] suddenly vanishes!") - qdel(src) - -/obj/item/spellbook/oneuse/fake_gib - spell = /obj/effect/proc_holder/spell/targeted/touch/fake_disintegrate - spellname = "disintegrate" - icon_state = "bookfireball" - desc = "This book feels like it will rip stuff apart." - -/obj/item/spellbook/oneuse/sacredflame - spell = /obj/effect/proc_holder/spell/targeted/sacred_flame - spellname = "sacred flame" - icon_state = "booksacredflame" - desc = "Become one with the flames that burn within... and invite others to do so as well." - -/obj/item/spellbook/oneuse/random - icon_state = "random_book" - -/obj/item/spellbook/oneuse/random/initialize() - . = ..() - var/static/banned_spells = list(/obj/item/spellbook/oneuse/mime, /obj/item/spellbook/oneuse/mime/fingergun, /obj/item/spellbook/oneuse/mime/fingergun/fake, /obj/item/spellbook/oneuse/mime/greaterwall) - var/real_type = pick(subtypesof(/obj/item/spellbook/oneuse) - banned_spells) - new real_type(loc) - qdel(src) \ No newline at end of file +/datum/spellbook_entry + var/name = "Entry Name" + + var/spell_type = null + var/desc = "" + var/category = "Offensive" + var/log_name = "XX" //What it shows up as in logs + var/cost = 2 + var/refundable = 1 + var/surplus = -1 // -1 for infinite, not used by anything atm + var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell + var/buy_word = "Learn" + var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook + +/datum/spellbook_entry/proc/IsSpellAvailable() // For config prefs / gamemode restrictions - these are round applied + return 1 + +/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user, obj/item/spellbook/book) // Specific circumstances + if(book.uses= aspell.level_max) + to_chat(user, "This spell cannot be improved further.") + return 0 + else + aspell.name = initial(aspell.name) + aspell.spell_level++ + aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) + if(aspell.charge_max < aspell.charge_counter) + aspell.charge_counter = aspell.charge_max + switch(aspell.spell_level) + if(1) + to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") + aspell.name = "Efficient [aspell.name]" + if(2) + to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") + aspell.name = "Quickened [aspell.name]" + if(3) + to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") + aspell.name = "Free [aspell.name]" + if(4) + to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") + aspell.name = "Instant [aspell.name]" + if(aspell.spell_level >= aspell.level_max) + to_chat(user, "This spell cannot be strengthened any further.") + return 1 + //No same spell found - just learn it + feedback_add_details("wizard_spell_learned",log_name) + user.mind.AddSpell(S) + to_chat(user, "You have learned [S.name].") + return 1 + +/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user, obj/item/spellbook/book) + if(!refundable) + return 0 + if(!S) + S = new spell_type() + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) + return 1 + return 0 + +/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) //return point value or -1 for failure + var/area/wizard_station/A = locate() + if(!(user in A.contents)) + to_chat(user, "You can only refund spells at the wizard lair") + return -1 + if(!S) + S = new spell_type() + var/spell_levels = 0 + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) + spell_levels = aspell.spell_level + user.mind.spell_list.Remove(aspell) + QDEL_NULL(S) + return cost * (spell_levels+1) + return -1 + +/datum/spellbook_entry/proc/GetInfo() + if(!S) + S = new spell_type() + var/dat ="" + dat += "[initial(S.name)]" + if(S.charge_type == "recharge") + dat += " Cooldown:[S.charge_max/10]" + dat += " Cost:[cost]
      " + dat += "[S.desc][desc]
      " + dat += "[S.clothes_req?"Needs wizard garb":"Can be cast without wizard garb"]
      " + return dat + +//Main category - Spells +//Offensive +/datum/spellbook_entry/blind + name = "Blind" + spell_type = /obj/effect/proc_holder/spell/targeted/trigger/blind + log_name = "BD" + category = "Offensive" + cost = 1 + +/datum/spellbook_entry/lightningbolt + name = "Lightning Bolt" + spell_type = /obj/effect/proc_holder/spell/targeted/lightning + log_name = "LB" + category = "Offensive" + cost = 1 + +/datum/spellbook_entry/cluwne + name = "Curse of the Cluwne" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/cluwne + log_name = "CC" + category = "Offensive" + +/datum/spellbook_entry/banana_touch + name = "Banana Touch" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/banana + log_name = "BT" + cost = 1 + +/datum/spellbook_entry/mime_malaise + name = "Mime Malaise" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/mime_malaise + log_name = "MI" + cost = 1 + +/datum/spellbook_entry/horseman + name = "Curse of the Horseman" + spell_type = /obj/effect/proc_holder/spell/targeted/horsemask + log_name = "HH" + category = "Offensive" + +/datum/spellbook_entry/disintegrate + name = "Disintegrate" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate + log_name = "DG" + category = "Offensive" + +/datum/spellbook_entry/fireball + name = "Fireball" + spell_type = /obj/effect/proc_holder/spell/fireball + log_name = "FB" + category = "Offensive" + +/datum/spellbook_entry/fleshtostone + name = "Flesh to Stone" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone + log_name = "FS" + category = "Offensive" + +/datum/spellbook_entry/mutate + name = "Mutate" + spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate + log_name = "MU" + category = "Offensive" + +/datum/spellbook_entry/rod_form + name = "Rod Form" + spell_type = /obj/effect/proc_holder/spell/targeted/rod_form + log_name = "RF" + category = "Offensive" + +/datum/spellbook_entry/infinite_guns + name = "Lesser Summon Guns" + spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns + log_name = "IG" + category = "Offensive" + cost = 4 + +//Defensive +/datum/spellbook_entry/disabletech + name = "Disable Tech" + spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech + log_name = "DT" + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/forcewall + name = "Force Wall" + spell_type = /obj/effect/proc_holder/spell/targeted/forcewall + log_name = "FW" + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/greaterforcewall + name = "Greater Force Wall" + spell_type = /obj/effect/proc_holder/spell/targeted/forcewall/greater + log_name = "GFW" + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/repulse + name = "Repulse" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse + log_name = "RP" + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/smoke + name = "Smoke" + spell_type = /obj/effect/proc_holder/spell/targeted/smoke + log_name = "SM" + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/lichdom + name = "Bind Soul" + spell_type = /obj/effect/proc_holder/spell/targeted/lichdom + log_name = "LD" + category = "Defensive" + +/datum/spellbook_entry/lichdom/IsSpellAvailable() + if(SSticker.mode.name == "ragin' mages") + return FALSE + else + return TRUE + +/datum/spellbook_entry/magicm + name = "Magic Missile" + spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile + log_name = "MM" + category = "Defensive" + +/datum/spellbook_entry/timestop + name = "Time Stop" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/timestop + log_name = "TS" + category = "Defensive" + +//Mobility +/datum/spellbook_entry/knock + name = "Knock" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock + log_name = "KN" + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/blink + name = "Blink" + spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink + log_name = "BL" + category = "Mobility" + +/datum/spellbook_entry/jaunt + name = "Ethereal Jaunt" + spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt + log_name = "EJ" + category = "Mobility" + +/datum/spellbook_entry/greaterknock + name = "Greater Knock" + spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock/greater + log_name = "GK" + category = "Mobility" + refundable = 0 //global effect on cast + +/datum/spellbook_entry/mindswap + name = "Mindswap" + spell_type = /obj/effect/proc_holder/spell/targeted/mind_transfer + log_name = "MT" + category = "Mobility" + +/datum/spellbook_entry/teleport + name = "Teleport" + spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport + log_name = "TP" + category = "Mobility" + +//Assistance +/datum/spellbook_entry/charge + name = "Charge" + spell_type = /obj/effect/proc_holder/spell/targeted/charge + log_name = "CH" + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/summonitem + name = "Summon Item" + spell_type = /obj/effect/proc_holder/spell/targeted/summonitem + log_name = "IS" + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/noclothes + name = "Remove Clothes Requirement" + spell_type = /obj/effect/proc_holder/spell/noclothes + log_name = "NC" + category = "Assistance" + +//Rituals +/datum/spellbook_entry/summon + name = "Summon Stuff" + category = "Rituals" + refundable = FALSE + buy_word = "Cast" + var/active = FALSE + +/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user, obj/item/spellbook/book) + return ..() && !active + +/datum/spellbook_entry/summon/GetInfo() + var/dat ="" + dat += "[name]" + if(cost>0) + dat += " Cost:[cost]
      " + else + dat += " No Cost
      " + dat += "[desc]
      " + if(active) + dat += "Already cast!
      " + return dat + +/datum/spellbook_entry/summon/ghosts + name = "Summon Ghosts" + desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." + cost = 0 + log_name = "SGH" + +/datum/spellbook_entry/summon/ghosts/IsSpellAvailable() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(SSticker.mode.name == "ragin' mages") + return FALSE + else + return TRUE + +/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + new /datum/event/wizard/ghost() + active = TRUE + to_chat(user, "You have cast summon ghosts!") + playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1) + return TRUE + +/datum/spellbook_entry/summon/guns + name = "Summon Guns" + desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first." + log_name = "SG" + +/datum/spellbook_entry/summon/guns/IsSpellAvailable() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(SSticker.mode.name == "ragin' mages") + return FALSE + else + return TRUE + +/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + feedback_add_details("wizard_spell_learned", log_name) + rightandwrong(SUMMON_GUNS, user, 10) + active = TRUE + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + to_chat(user, "You have cast summon guns!") + return TRUE + +/datum/spellbook_entry/summon/magic + name = "Summon Magic" + desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." + log_name = "SU" + +/datum/spellbook_entry/summon/magic/IsSpellAvailable() + if(!SSticker.mode) // In case spellbook is placed on map + return FALSE + if(SSticker.mode.name == "ragin' mages") + return FALSE + else + return TRUE + +/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + feedback_add_details("wizard_spell_learned", log_name) + rightandwrong(SUMMON_MAGIC, user, 10) + active = TRUE + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + to_chat(user, "You have cast summon magic!") + return TRUE + +//Main category - Magical Items +/datum/spellbook_entry/item + name = "Buy Item" + refundable = 0 + buy_word = "Summon" + var/item_path = null + +/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + user.put_in_hands(new item_path) + feedback_add_details("wizard_spell_learned", log_name) + return 1 + +/datum/spellbook_entry/item/GetInfo() + var/dat ="" + dat += "[name]" + dat += " Cost:[cost]
      " + dat += "[desc]
      " + if(surplus>=0) + dat += "[surplus] left.
      " + return dat + +//Artefacts +/datum/spellbook_entry/item/necrostone + name = "A Necromantic Stone" + desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." + item_path = /obj/item/necromantic_stone + log_name = "NS" + category = "Artefacts" + +/datum/spellbook_entry/item/scryingorb + name = "Scrying Orb" + desc = "An incandescent orb of crackling energy, using it will allow you to ghost while alive, allowing you to spy upon the station with ease. In addition, buying it will permanently grant you x-ray vision." + item_path = /obj/item/scrying + log_name = "SO" + category = "Artefacts" + +/datum/spellbook_entry/item/scryingorb/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + if(..()) + if(!(XRAY in user.mutations)) + user.mutations.Add(XRAY) + user.sight |= (SEE_MOBS|SEE_OBJS|SEE_TURFS) + user.see_in_dark = 8 + user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + to_chat(user, "The walls suddenly disappear.") + return TRUE + +/datum/spellbook_entry/item/soulstones + name = "Six Soul Stone Shards and the spell Artificer" + desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." + item_path = /obj/item/storage/belt/soulstone/full + log_name = "SS" + category = "Artefacts" + +/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) + . = ..() + if(.) + user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) + return . + +/datum/spellbook_entry/item/wands + name = "Wand Assortment" + desc = "A collection of wands that allow for a wide variety of utility. Wands do not recharge, so be conservative in use. Comes in a handy belt." + item_path = /obj/item/storage/belt/wands/full + log_name = "WA" + category = "Artefacts" + +//Weapons and Armors +/datum/spellbook_entry/item/battlemage + name = "Battlemage Armour" + desc = "An ensorceled suit of armour, protected by a powerful shield. The shield can completely negate sixteen attacks before being permanently depleted." + item_path = /obj/item/clothing/suit/space/hardsuit/shielded/wizard + limit = 1 + category = "Weapons and Armors" + log_name = "BMA" + +/datum/spellbook_entry/item/battlemage_charge + name = "Battlemage Armour Charges" + desc = "A powerful defensive rune, it will grant eight additional charges to a suit of battlemage armour." + item_path = /obj/item/wizard_armour_charge + category = "Weapons and Armors" + cost = 1 + log_name = "BMAC" + +/datum/spellbook_entry/item/mjolnir + name = "Mjolnir" + desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." + item_path = /obj/item/twohanded/mjollnir + log_name = "MJ" + category = "Weapons and Armors" + +/datum/spellbook_entry/item/singularity_hammer + name = "Singularity Hammer" + desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everthing nearby to the point of impact." + item_path = /obj/item/twohanded/singularityhammer + log_name = "SI" + category = "Weapons and Armors" + +//Staves +/datum/spellbook_entry/item/staffdoor + name = "Staff of Door Creation" + desc = "A particular staff that can mold solid metal into ornate wooden doors. Useful for getting around in the absence of other transportation. Does not work on glass." + item_path = /obj/item/gun/magic/staff/door + log_name = "SD" + category = "Staves" + cost = 1 + +/datum/spellbook_entry/item/staffhealing + name = "Staff of Healing" + desc = "An altruistic staff that can heal the lame and raise the dead." + item_path = /obj/item/gun/magic/staff/healing + log_name = "SH" + category = "Staves" + cost = 1 + +/datum/spellbook_entry/item/staffslipping + name = "Staff of Slipping" + desc = "A staff that shoots magical bananas. These bananas will either slip or stun the target when hit. Surprisingly reliable!" + item_path = /obj/item/gun/magic/staff/slipping + log_name = "SL" + category = "Staves" + cost = 1 + +/datum/spellbook_entry/item/staffanimation + name = "Staff of Animation" + desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." + item_path = /obj/item/gun/magic/staff/animate + log_name = "SA" + category = "Staves" + +/datum/spellbook_entry/item/staffchange + name = "Staff of Change" + desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." + item_path = /obj/item/gun/magic/staff/change + log_name = "ST" + category = "Staves" + +/datum/spellbook_entry/item/staffchaos + name = "Staff of Chaos" + desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." + item_path = /obj/item/gun/magic/staff/chaos + log_name = "SC" + category = "Staves" + +//Summons +/datum/spellbook_entry/item/oozebottle + name = "Bottle of Ooze" + desc = "A bottle of magically infused ooze, which will awake an all-consuming Morph, capable of cunningly disguising itself as any object it comes in contact with and even casting some very basic spells. Be careful though, as Morph diet includes Wizards." + item_path = /obj/item/antag_spawner/morph + log_name = "BO" + category = "Summons" + limit = 3 + cost = 1 + +/datum/spellbook_entry/item/hugbottle + name = "Bottle of Tickles" + desc = "A bottle of magically infused fun, the smell of which will \ + attract adorable extradimensional beings when broken. These beings \ + are similar to slaughter demons, but are a little weaker and they do not permamently \ + kill their victims, instead putting them in an extradimensional hugspace, \ + to be released on the demon's death. Chaotic, but not ultimately \ + damaging. The crew's reaction to the other hand could be very \ + destructive." + item_path = /obj/item/antag_spawner/slaughter_demon/laughter + log_name = "HB" + category = "Summons" + limit = 3 + cost = 1 // Non-destructive; it's just a jape, sibling! + +/datum/spellbook_entry/item/bloodbottle + name = "Bottle of Blood" + desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim." + item_path = /obj/item/antag_spawner/slaughter_demon + log_name = "BB" + category = "Summons" + limit = 3 + +/datum/spellbook_entry/item/contract + name = "Contract of Apprenticeship" + desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." + item_path = /obj/item/contract + log_name = "CT" + category = "Summons" + +/datum/spellbook_entry/item/tarotdeck + name = "Guardian Deck" + desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ + It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." + item_path = /obj/item/guardiancreator + log_name = "TD" + category = "Summons" + limit = 1 + +/obj/item/spellbook + name = "spell book" + desc = "The legendary book of spells of the wizard." + icon = 'icons/obj/library.dmi' + icon_state = "spellbook" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/uses = 10 + var/temp = null + var/op = 1 + var/tab = null + var/main_tab = null + var/mob/living/carbon/human/owner + var/list/datum/spellbook_entry/entries = list() + var/list/categories = list() + var/list/main_categories = list("Spells", "Magical Items") + var/list/spell_categories = list("Offensive", "Defensive", "Mobility", "Assistance", "Rituals") + var/list/item_categories = list("Artefacts", "Weapons and Armors", "Staves", "Summons") + +/obj/item/spellbook/proc/initialize() + var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon + for(var/T in entry_types) + var/datum/spellbook_entry/E = new T + if(E.IsSpellAvailable()) + entries |= E + categories |= E.category + else + qdel(E) + main_tab = main_categories[1] + tab = categories[1] + +/obj/item/spellbook/New() + ..() + initialize() + +/obj/item/spellbook/attackby(obj/item/O as obj, mob/user as mob, params) + if(istype(O, /obj/item/contract)) + var/obj/item/contract/contract = O + if(contract.used) + to_chat(user, "The contract has been used, you can't get your points back now!") + else + to_chat(user, "You feed the contract back into the spellbook, refunding your points.") + uses+=2 + qdel(O) + return + + if(istype(O, /obj/item/antag_spawner/slaughter_demon)) + to_chat(user, "On second thought, maybe summoning a demon is a bad idea. You refund your points.") + if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) + uses += 1 + for(var/datum/spellbook_entry/item/hugbottle/HB in entries) + if(!isnull(HB.limit)) + HB.limit++ + else + uses += 2 + for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) + if(!isnull(BB.limit)) + BB.limit++ + qdel(O) + return + + if(istype(O, /obj/item/antag_spawner/morph)) + to_chat(user, "On second thought, maybe awakening a morph is a bad idea. You refund your points.") + uses += 1 + for(var/datum/spellbook_entry/item/oozebottle/OB in entries) + if(!isnull(OB.limit)) + OB.limit++ + qdel(O) + return + return ..() + +/obj/item/spellbook/proc/GetCategoryHeader(category) + var/dat = "" + switch(category) + if("Offensive") + dat += "Spells geared towards debilitating and destroying.

      " + dat += "For spells: the number after the spell name is the cooldown time.
      " + dat += "You can reduce this number by spending more points on the spell.
      " + if("Defensive") + dat += "Spells geared towards improving your survivabilty or reducing foes ability to attack.

      " + dat += "For spells: the number after the spell name is the cooldown time.
      " + dat += "You can reduce this number by spending more points on the spell.
      " + if("Mobility") + dat += "Spells geared towards improving your ability to move. It is a good idea to take at least one.

      " + dat += "For spells: the number after the spell name is the cooldown time.
      " + dat += "You can reduce this number by spending more points on the spell.
      " + if("Assistance") + dat += "Spells geared towards improving your other items and abilities.

      " + dat += "For spells: the number after the spell name is the cooldown time.
      " + dat += "You can reduce this number by spending more points on the spell.
      " + if("Rituals") + dat += "These powerful spells are capable of changing the very fabric of reality. Not always in your favour.
      " + if("Weapons and Armors") + dat += "Various weapons and armors to crush your enemies and protect you from harm.

      " + dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
      " + if("Staves") + dat += "Various staves granting you their power, which they slowly recharge over time.

      " + dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
      " + if("Artefacts") + dat += "Various magical artefacts to aid you.

      " + dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
      " + if("Summons") + dat += "Magical items geared towards bringing in outside forces to aid you.

      " + dat += "Items are not bound to you and can be stolen. Additionaly they cannot typically be returned once purchased.
      " + return dat + +/obj/item/spellbook/proc/wrap(content) + var/dat = "" + dat +="Spellbook" + dat += {" + + + + "} + dat += {"[content]"} + return dat + +/obj/item/spellbook/attack_self(mob/user as mob) + if(!owner) + to_chat(user, "You bind the spellbook to yourself.") + owner = user + return + if(user != owner) + to_chat(user, "The [name] does not recognize you as it's owner and refuses to open!") + return + user.set_machine(src) + var/dat = "" + + dat += "
        " + var/list/cat_dat = list() + for(var/main_category in main_categories) + cat_dat[main_category] = "
        " + dat += "
      • [main_category]
      • " + dat += "
      " + switch(main_tab) + if("Spells") + dat += "
        " + for(var/category in categories) + if(category in item_categories) + continue + cat_dat[category] = "
        " + dat += "
      • [category]
      • " + dat += "
      • Points remaining : [uses]
      • " + if("Magical Items") + dat += "
          " + for(var/category in categories) + if(category in spell_categories) + continue + cat_dat[category] = "
          " + dat += "
        • [category]
        • " + dat += "
        • Points remaining : [uses]
        • " + dat += "
        " + + var/datum/spellbook_entry/E + for(var/i=1,i<=entries.len,i++) + var/spell_info = "" + E = entries[i] + spell_info += E.GetInfo() + if(E.CanBuy(user,src)) + spell_info+= "[E.buy_word]
        " + else + spell_info+= "Can't [E.buy_word]
        " + if(E.CanRefund(user,src)) + spell_info+= "Refund
        " + spell_info += "
        " + if(cat_dat[E.category]) + cat_dat[E.category] += spell_info + + for(var/category in categories) + dat += "
        " + dat += GetCategoryHeader(category) + dat += cat_dat[category] + dat += "
        " + + user << browse(wrap(dat), "window=spellbook;size=800x600") + onclose(user, "spellbook") + return + +/obj/item/spellbook/Topic(href, href_list) + if(..()) + return 1 + var/mob/living/carbon/human/H = usr + + if(!ishuman(H)) + return 1 + + if(H.mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE) + temp = "If you got caught sneaking a peak from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." + return 1 + + var/datum/spellbook_entry/E = null + if(loc == H || (in_range(src, H) && istype(loc, /turf))) + H.set_machine(src) + if(href_list["buy"]) + E = entries[text2num(href_list["buy"])] + if(E && E.CanBuy(H,src)) + if(E.Buy(H,src)) + if(E.limit) + E.limit-- + uses -= E.cost + else if(href_list["refund"]) + E = entries[text2num(href_list["refund"])] + if(E && E.refundable) + var/result = E.Refund(H,src) + if(result > 0) + if(!isnull(E.limit)) + E.limit += result + uses += result + else if(href_list["mainpage"]) + main_tab = sanitize(href_list["mainpage"]) + tab = sanitize(href_list["page"]) + if(main_tab == "Spells") + tab = spell_categories[1] + else if(main_tab == "Magical Items") + tab = item_categories[1] + else if(href_list["page"]) + tab = sanitize(href_list["page"]) + attack_self(H) + return 1 + +//Single Use Spellbooks +/obj/item/spellbook/oneuse + var/spell = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile //just a placeholder to avoid runtimes if someone spawned the generic + var/spellname = "sandbox" + var/used = 0 + name = "spellbook of " + uses = 1 + desc = "This template spellbook was never meant for the eyes of man..." + +/obj/item/spellbook/oneuse/New() + ..() + name += spellname + +/obj/item/spellbook/oneuse/initialize() //No need to init + return + +/obj/item/spellbook/oneuse/attack_self(mob/user) + var/obj/effect/proc_holder/spell/S = new spell + for(var/obj/effect/proc_holder/spell/knownspell in user.mind.spell_list) + if(knownspell.type == S.type) + if(user.mind) + if(user.mind.special_role == SPECIAL_ROLE_WIZARD_APPRENTICE || user.mind.special_role == SPECIAL_ROLE_WIZARD) + to_chat(user, "You're already far more versed in this spell than this flimsy how-to book can provide.") + else + to_chat(user, "You've already read this one.") + return + if(used) + recoil(user) + else + user.mind.AddSpell(S) + to_chat(user, "you rapidly read through the arcane book. Suddenly you realize you understand [spellname]!") + user.create_attack_log("[key_name(user)] learned the spell [spellname] ([S]).") + onlearned(user) + +/obj/item/spellbook/oneuse/proc/recoil(mob/user) + user.visible_message("[src] glows in a black light!") + +/obj/item/spellbook/oneuse/proc/onlearned(mob/user) + used = 1 + user.visible_message("[src] glows dark for a second!") + +/obj/item/spellbook/oneuse/attackby() + return + +/obj/item/spellbook/oneuse/fireball + spell = /obj/effect/proc_holder/spell/fireball + spellname = "fireball" + icon_state = "bookfireball" + desc = "This book feels warm to the touch." + +/obj/item/spellbook/oneuse/fireball/recoil(mob/user as mob) + ..() + explosion(user.loc, -1, 0, 2, 3, 0, flame_range = 2) + qdel(src) + +/obj/item/spellbook/oneuse/smoke + spell = /obj/effect/proc_holder/spell/targeted/smoke + spellname = "smoke" + icon_state = "booksmoke" + desc = "This book is overflowing with the dank arts." + +/obj/item/spellbook/oneuse/smoke/recoil(mob/user as mob) + ..() + to_chat(user, "Your stomach rumbles...") + user.adjust_nutrition(-200) + +/obj/item/spellbook/oneuse/blind + spell = /obj/effect/proc_holder/spell/targeted/trigger/blind + spellname = "blind" + icon_state = "bookblind" + desc = "This book looks blurry, no matter how you look at it." + +/obj/item/spellbook/oneuse/blind/recoil(mob/user as mob) + ..() + to_chat(user, "You go blind!") + user.EyeBlind(10) + +/obj/item/spellbook/oneuse/mindswap + spell = /obj/effect/proc_holder/spell/targeted/mind_transfer + spellname = "mindswap" + icon_state = "bookmindswap" + desc = "This book's cover is pristine, though its pages look ragged and torn." + var/mob/stored_swap = null //Used in used book recoils to store an identity for mindswaps + +/obj/item/spellbook/oneuse/mindswap/onlearned() + spellname = pick("fireball","smoke","blind","forcewall","knock","horses","charge") + icon_state = "book[spellname]" + name = "spellbook of [spellname]" //Note, desc doesn't change by design + ..() + +/obj/item/spellbook/oneuse/mindswap/recoil(mob/user) + ..() + if(stored_swap in GLOB.dead_mob_list) + stored_swap = null + if(!stored_swap) + stored_swap = user + to_chat(user, "For a moment you feel like you don't even know who you are anymore.") + return + if(stored_swap == user) + to_chat(user, "You stare at the book some more, but there doesn't seem to be anything else to learn...") + return + + var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new + swapper.cast(user, stored_swap, 1) + + to_chat(stored_swap, "You're suddenly somewhere else... and someone else?!") + to_chat(user, "Suddenly you're staring at [src] again... where are you, who are you?!") + stored_swap = null + +/obj/item/spellbook/oneuse/forcewall + spell = /obj/effect/proc_holder/spell/targeted/forcewall + spellname = "forcewall" + icon_state = "bookforcewall" + desc = "This book has a dedication to mimes everywhere inside the front cover." + +/obj/item/spellbook/oneuse/forcewall/recoil(mob/user as mob) + ..() + to_chat(user, "You suddenly feel very solid!") + var/obj/structure/closet/statue/S = new /obj/structure/closet/statue(user.loc, user) + S.timer = 30 + user.drop_item() + +/obj/item/spellbook/oneuse/knock + spell = /obj/effect/proc_holder/spell/aoe_turf/knock + spellname = "knock" + icon_state = "bookknock" + desc = "This book is hard to hold closed properly." + +/obj/item/spellbook/oneuse/knock/recoil(mob/user as mob) + ..() + to_chat(user, "You're knocked down!") + user.Weaken(20) + +/obj/item/spellbook/oneuse/horsemask + spell = /obj/effect/proc_holder/spell/targeted/horsemask + spellname = "horses" + icon_state = "bookhorses" + desc = "This book is more horse than your mind has room for." + +/obj/item/spellbook/oneuse/horsemask/recoil(mob/living/carbon/user as mob) + if(istype(user, /mob/living/carbon/human)) + to_chat(user, "HOR-SIE HAS RISEN") + var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead + magichead.flags |= NODROP | DROPDEL //curses! + magichead.flags_inv = null //so you can still see their face + magichead.voicechange = 1 //NEEEEIIGHH + if(!user.unEquip(user.wear_mask)) + qdel(user.wear_mask) + user.equip_to_slot_if_possible(magichead, slot_wear_mask, 1, 1) + qdel(src) + else + to_chat(user, "I say thee neigh") + +/obj/item/spellbook/oneuse/charge + spell = /obj/effect/proc_holder/spell/targeted/charge + spellname = "charging" + icon_state = "bookcharge" + desc = "This book is made of 100% post-consumer wizard." + +/obj/item/spellbook/oneuse/charge/recoil(mob/user as mob) + ..() + to_chat(user, "[src] suddenly feels very warm!") + empulse(src, 1, 1) + +/obj/item/spellbook/oneuse/summonitem + spell = /obj/effect/proc_holder/spell/targeted/summonitem + spellname = "instant summons" + icon_state = "booksummons" + desc = "This book is bright and garish, very hard to miss." + +/obj/item/spellbook/oneuse/summonitem/recoil(mob/user as mob) + ..() + to_chat(user, "[src] suddenly vanishes!") + qdel(src) + +/obj/item/spellbook/oneuse/fake_gib + spell = /obj/effect/proc_holder/spell/targeted/touch/fake_disintegrate + spellname = "disintegrate" + icon_state = "bookfireball" + desc = "This book feels like it will rip stuff apart." + +/obj/item/spellbook/oneuse/sacredflame + spell = /obj/effect/proc_holder/spell/targeted/sacred_flame + spellname = "sacred flame" + icon_state = "booksacredflame" + desc = "Become one with the flames that burn within... and invite others to do so as well." + +/obj/item/spellbook/oneuse/random + icon_state = "random_book" + +/obj/item/spellbook/oneuse/random/initialize() + . = ..() + var/static/banned_spells = list(/obj/item/spellbook/oneuse/mime, /obj/item/spellbook/oneuse/mime/fingergun, /obj/item/spellbook/oneuse/mime/fingergun/fake, /obj/item/spellbook/oneuse/mime/greaterwall) + var/real_type = pick(subtypesof(/obj/item/spellbook/oneuse) - banned_spells) + new real_type(loc) + qdel(src) diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index 7a6f634784f..ba310ed0e8f 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -1,283 +1,283 @@ -/datum/game_mode - var/list/datum/mind/wizards = list() - var/list/datum/mind/apprentices = list() - -/datum/game_mode/wizard - name = "wizard" - config_tag = "wizard" - required_players = 20 - required_enemies = 1 - recommended_enemies = 1 - var/use_huds = 1 - - var/finished = 0 - var/but_wait_theres_more = 0 - -/datum/game_mode/wizard/announce() - to_chat(world, "The current game mode is - Wizard!") - to_chat(world, "There is a SPACE WIZARD on the station. You can't let him achieve his objective!") - -/datum/game_mode/wizard/can_start()//This could be better, will likely have to recode it later - if(!..()) - return 0 - var/list/datum/mind/possible_wizards = get_players_for_role(ROLE_WIZARD) - if(possible_wizards.len==0) - return 0 - var/datum/mind/wizard = pick(possible_wizards) - - wizards += wizard - modePlayer += wizard - wizard.assigned_role = SPECIAL_ROLE_WIZARD //So they aren't chosen for other jobs. - wizard.special_role = SPECIAL_ROLE_WIZARD - wizard.original = wizard.current - if(wizardstart.len == 0) - to_chat(wizard.current, "A starting location for you could not be found, please report this bug!") - return 0 - return 1 - -/datum/game_mode/wizard/pre_setup() - for(var/datum/mind/wiz in wizards) - wiz.current.loc = pick(wizardstart) - ..() - return 1 - -/datum/game_mode/wizard/post_setup() - for(var/datum/mind/wizard in wizards) - log_game("[key_name(wizard)] has been selected as a Wizard") - forge_wizard_objectives(wizard) - //learn_basic_spells(wizard.current) - equip_wizard(wizard.current) - name_wizard(wizard.current) - greet_wizard(wizard) - if(use_huds) - update_wiz_icons_added(wizard) - - ..() - -/datum/game_mode/proc/remove_wizard(datum/mind/wizard_mind) - if(wizard_mind in wizards) - SSticker.mode.wizards -= wizard_mind - wizard_mind.special_role = null - wizard_mind.current.create_attack_log("De-wizarded") - wizard_mind.current.spellremove(wizard_mind.current) - wizard_mind.current.faction = list("Station") - if(issilicon(wizard_mind.current)) - to_chat(wizard_mind.current, "You have been turned into a robot! You can feel your magical powers fading away...") - else - to_chat(wizard_mind.current, "You have been brainwashed! You are no longer a wizard.") - SSticker.mode.update_wiz_icons_removed(wizard_mind) - -/datum/game_mode/proc/update_wiz_icons_added(datum/mind/wiz_mind) - var/datum/atom_hud/antag/wizhud = huds[ANTAG_HUD_WIZ] - wizhud.join_hud(wiz_mind.current) - set_antag_hud(wiz_mind.current, ((wiz_mind in wizards) ? "hudwizard" : "apprentice")) - -/datum/game_mode/proc/update_wiz_icons_removed(datum/mind/wiz_mind) - var/datum/atom_hud/antag/wizhud = huds[ANTAG_HUD_WIZ] - wizhud.leave_hud(wiz_mind.current) - set_antag_hud(wiz_mind.current, null) - -/datum/game_mode/proc/forge_wizard_objectives(var/datum/mind/wizard) - var/datum/objective/wizchaos/wiz_objective = new - wiz_objective.owner = wizard - wizard.objectives += wiz_objective - return - -/datum/game_mode/proc/name_wizard(mob/living/carbon/human/wizard_mob) - //Allows the wizard to choose a custom name or go with a random one. Spawn 0 so it does not lag the round starting. - var/wizard_name_first = pick(GLOB.wizard_first) - var/wizard_name_second = pick(GLOB.wizard_second) - var/randomname = "[wizard_name_first] [wizard_name_second]" - spawn(0) - var/newname = sanitize(copytext(input(wizard_mob, "You are the Space Wizard. Would you like to change your name to something else?", "Name change", randomname) as null|text,1,MAX_NAME_LEN)) - - if(!newname) - newname = randomname - - wizard_mob.real_name = newname - wizard_mob.name = newname - if(wizard_mob.mind) - wizard_mob.mind.name = newname - return - -/datum/game_mode/proc/greet_wizard(var/datum/mind/wizard, var/you_are=1) - addtimer(CALLBACK(wizard.current, /mob/.proc/playsound_local, null, 'sound/ambience/antag/ragesmages.ogg', 100, 0), 30) - if(you_are) - to_chat(wizard.current, "You are the Space Wizard!") - to_chat(wizard.current, "The Space Wizards Federation has given you the following tasks:") - - var/obj_count = 1 - for(var/datum/objective/objective in wizard.objectives) - to_chat(wizard.current, "Objective #[obj_count]: [objective.explanation_text]") - obj_count++ - return - -/*/datum/game_mode/proc/learn_basic_spells(mob/living/carbon/human/wizard_mob) - if(!istype(wizard_mob)) - return - if(!config.feature_object_spell_system) - wizard_mob.verbs += /client/proc/jaunt - wizard_mob.mind.special_verbs += /client/proc/jaunt - else - wizard_mob.spell_list += new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(usr) -*/ - -/datum/game_mode/proc/equip_wizard(mob/living/carbon/human/wizard_mob) - if(!istype(wizard_mob)) - return - - //So zards properly get their items when they are admin-made. - qdel(wizard_mob.wear_suit) - qdel(wizard_mob.head) - qdel(wizard_mob.shoes) - qdel(wizard_mob.r_hand) - qdel(wizard_mob.r_store) - qdel(wizard_mob.l_store) - - wizard_mob.equip_to_slot_or_del(new /obj/item/radio/headset(wizard_mob), slot_l_ear) - wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/under/color/lightpurple(wizard_mob), slot_w_uniform) - wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(wizard_mob), slot_shoes) - if(!isplasmaman(wizard_mob)) //handled in the species file for plasmen on the afterjob equip proc for now - wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(wizard_mob), slot_wear_suit) - wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(wizard_mob), slot_head) - wizard_mob.equip_to_slot_or_del(new /obj/item/storage/backpack/satchel(wizard_mob), slot_back) - wizard_mob.equip_to_slot_or_del(new /obj/item/storage/box/survival(wizard_mob), slot_in_backpack) - wizard_mob.equip_to_slot_or_del(new /obj/item/teleportation_scroll(wizard_mob), slot_r_store) - var/obj/item/spellbook/spellbook = new /obj/item/spellbook(wizard_mob) - spellbook.owner = wizard_mob - wizard_mob.equip_to_slot_or_del(spellbook, slot_r_hand) - - wizard_mob.faction = list("wizard") - - wizard_mob.dna.species.after_equip_job(null, wizard_mob) - - to_chat(wizard_mob, "You will find a list of available spells in your spell book. Choose your magic arsenal carefully.") - to_chat(wizard_mob, "The spellbook is bound to you, and others cannot use it.") - to_chat(wizard_mob, "In your pockets you will find a teleport scroll. Use it as needed.") - wizard_mob.mind.store_memory("Remember: do not forget to prepare your spells.") - wizard_mob.update_icons() - wizard_mob.gene_stability += DEFAULT_GENE_STABILITY //magic - return 1 - -// Checks if the game should end due to all wizards and apprentices being dead, or MMI'd/Borged -/datum/game_mode/wizard/check_finished() - var/wizards_alive = 0 - var/apprentices_alive = 0 - - // Wizards - for(var/datum/mind/wizard in wizards) - if(!istype(wizard.current,/mob/living/carbon)) - continue - if(wizard.current.stat==DEAD) - continue - if(istype(wizard.current, /obj/item/mmi)) // wizard is in an MMI, don't count them as alive - continue - wizards_alive++ - - // Apprentices - if(!wizards_alive) - for(var/datum/mind/apprentice in apprentices) - if(!istype(apprentice.current,/mob/living/carbon)) - continue - if(apprentice.current.stat==DEAD) - continue - if(istype(apprentice.current, /obj/item/mmi)) // apprentice is in an MMI, don't count them as alive - continue - apprentices_alive++ - - if(wizards_alive || apprentices_alive || but_wait_theres_more) - return ..() - else - finished = 1 - return 1 - -/datum/game_mode/wizard/declare_completion(var/ragin = 0) - if(finished && !ragin) - feedback_set_details("round_end_result","wizard loss - wizard killed") - to_chat(world, " The wizard[(wizards.len>1)?"s":""] has been killed by the crew! The Space Wizards Federation has been taught a lesson they will not soon forget!") - ..() - return 1 - -/datum/game_mode/proc/auto_declare_completion_wizard() - if(wizards.len) - var/text = "
        the wizards/witches were:" - - for(var/datum/mind/wizard in wizards) - - text += "
        [wizard.key] was [wizard.name] (" - if(wizard.current) - if(wizard.current.stat == DEAD) - text += "died" - else - text += "survived" - if(wizard.current.real_name != wizard.name) - text += " as [wizard.current.real_name]" - else - text += "body destroyed" - text += ")" - - var/count = 1 - var/wizardwin = 1 - for(var/datum/objective/objective in wizard.objectives) - if(objective.check_completion()) - text += "
        Objective #[count]: [objective.explanation_text] Success!" - feedback_add_details("wizard_objective","[objective.type]|SUCCESS") - else - text += "
        Objective #[count]: [objective.explanation_text] Fail." - feedback_add_details("wizard_objective","[objective.type]|FAIL") - wizardwin = 0 - count++ - - if(wizard.current && wizard.current.stat!=DEAD && wizardwin) - text += "
        The wizard was successful!" - feedback_add_details("wizard_success","SUCCESS") - else - text += "
        The wizard has failed!" - feedback_add_details("wizard_success","FAIL") - if(wizard.spell_list) - text += "
        [wizard.name] used the following spells: " - var/i = 1 - for(var/obj/effect/proc_holder/spell/S in wizard.spell_list) - text += "[S.name]" - if(wizard.spell_list.len > i) - text += ", " - i++ - text += "
        " - - to_chat(world, text) - return 1 - -//OTHER PROCS - -//To batch-remove wizard spells. Linked to mind.dm -/mob/proc/spellremove(mob/M) - if(!mind) - return - for(var/obj/effect/proc_holder/spell/spell_to_remove in mind.spell_list) - qdel(spell_to_remove) - mind.spell_list -= spell_to_remove - -//To batch-remove mob spells. -/mob/proc/mobspellremove(mob/M) - for(var/obj/effect/proc_holder/spell/spell_to_remove in mob_spell_list) - qdel(spell_to_remove) - mob_spell_list -= spell_to_remove - -/*Checks if the wizard can cast spells. -Made a proc so this is not repeated 14 (or more) times.*/ -/mob/proc/casting() -//Removed the stat check because not all spells require clothing now. - if(!istype(usr:wear_suit, /obj/item/clothing/suit/wizrobe)) - to_chat(usr, "I don't feel strong enough without my robe.") - return 0 - if(!istype(usr:shoes, /obj/item/clothing/shoes/sandal)) - to_chat(usr, "I don't feel strong enough without my sandals.") - return 0 - if(!istype(usr:head, /obj/item/clothing/head/wizard)) - to_chat(usr, "I don't feel strong enough without my hat.") - return 0 - else - return 1 - -/proc/iswizard(mob/living/M as mob) - return istype(M) && M.mind && SSticker && SSticker.mode && ((M.mind in SSticker.mode.wizards) || (M.mind in SSticker.mode.apprentices)) +/datum/game_mode + var/list/datum/mind/wizards = list() + var/list/datum/mind/apprentices = list() + +/datum/game_mode/wizard + name = "wizard" + config_tag = "wizard" + required_players = 20 + required_enemies = 1 + recommended_enemies = 1 + var/use_huds = 1 + + var/finished = 0 + var/but_wait_theres_more = 0 + +/datum/game_mode/wizard/announce() + to_chat(world, "The current game mode is - Wizard!") + to_chat(world, "There is a SPACE WIZARD on the station. You can't let him achieve his objective!") + +/datum/game_mode/wizard/can_start()//This could be better, will likely have to recode it later + if(!..()) + return 0 + var/list/datum/mind/possible_wizards = get_players_for_role(ROLE_WIZARD) + if(possible_wizards.len==0) + return 0 + var/datum/mind/wizard = pick(possible_wizards) + + wizards += wizard + modePlayer += wizard + wizard.assigned_role = SPECIAL_ROLE_WIZARD //So they aren't chosen for other jobs. + wizard.special_role = SPECIAL_ROLE_WIZARD + wizard.original = wizard.current + if(wizardstart.len == 0) + to_chat(wizard.current, "A starting location for you could not be found, please report this bug!") + return 0 + return 1 + +/datum/game_mode/wizard/pre_setup() + for(var/datum/mind/wiz in wizards) + wiz.current.loc = pick(wizardstart) + ..() + return 1 + +/datum/game_mode/wizard/post_setup() + for(var/datum/mind/wizard in wizards) + log_game("[key_name(wizard)] has been selected as a Wizard") + forge_wizard_objectives(wizard) + //learn_basic_spells(wizard.current) + equip_wizard(wizard.current) + name_wizard(wizard.current) + greet_wizard(wizard) + if(use_huds) + update_wiz_icons_added(wizard) + + ..() + +/datum/game_mode/proc/remove_wizard(datum/mind/wizard_mind) + if(wizard_mind in wizards) + SSticker.mode.wizards -= wizard_mind + wizard_mind.special_role = null + wizard_mind.current.create_attack_log("De-wizarded") + wizard_mind.current.spellremove(wizard_mind.current) + wizard_mind.current.faction = list("Station") + if(issilicon(wizard_mind.current)) + to_chat(wizard_mind.current, "You have been turned into a robot! You can feel your magical powers fading away...") + else + to_chat(wizard_mind.current, "You have been brainwashed! You are no longer a wizard.") + SSticker.mode.update_wiz_icons_removed(wizard_mind) + +/datum/game_mode/proc/update_wiz_icons_added(datum/mind/wiz_mind) + var/datum/atom_hud/antag/wizhud = huds[ANTAG_HUD_WIZ] + wizhud.join_hud(wiz_mind.current) + set_antag_hud(wiz_mind.current, ((wiz_mind in wizards) ? "hudwizard" : "apprentice")) + +/datum/game_mode/proc/update_wiz_icons_removed(datum/mind/wiz_mind) + var/datum/atom_hud/antag/wizhud = huds[ANTAG_HUD_WIZ] + wizhud.leave_hud(wiz_mind.current) + set_antag_hud(wiz_mind.current, null) + +/datum/game_mode/proc/forge_wizard_objectives(var/datum/mind/wizard) + var/datum/objective/wizchaos/wiz_objective = new + wiz_objective.owner = wizard + wizard.objectives += wiz_objective + return + +/datum/game_mode/proc/name_wizard(mob/living/carbon/human/wizard_mob) + //Allows the wizard to choose a custom name or go with a random one. Spawn 0 so it does not lag the round starting. + var/wizard_name_first = pick(GLOB.wizard_first) + var/wizard_name_second = pick(GLOB.wizard_second) + var/randomname = "[wizard_name_first] [wizard_name_second]" + spawn(0) + var/newname = sanitize(copytext(input(wizard_mob, "You are the Space Wizard. Would you like to change your name to something else?", "Name change", randomname) as null|text,1,MAX_NAME_LEN)) + + if(!newname) + newname = randomname + + wizard_mob.real_name = newname + wizard_mob.name = newname + if(wizard_mob.mind) + wizard_mob.mind.name = newname + return + +/datum/game_mode/proc/greet_wizard(var/datum/mind/wizard, var/you_are=1) + addtimer(CALLBACK(wizard.current, /mob/.proc/playsound_local, null, 'sound/ambience/antag/ragesmages.ogg', 100, 0), 30) + if(you_are) + to_chat(wizard.current, "You are the Space Wizard!") + to_chat(wizard.current, "The Space Wizards Federation has given you the following tasks:") + + var/obj_count = 1 + for(var/datum/objective/objective in wizard.objectives) + to_chat(wizard.current, "Objective #[obj_count]: [objective.explanation_text]") + obj_count++ + return + +/*/datum/game_mode/proc/learn_basic_spells(mob/living/carbon/human/wizard_mob) + if(!istype(wizard_mob)) + return + if(!config.feature_object_spell_system) + wizard_mob.verbs += /client/proc/jaunt + wizard_mob.mind.special_verbs += /client/proc/jaunt + else + wizard_mob.spell_list += new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(usr) +*/ + +/datum/game_mode/proc/equip_wizard(mob/living/carbon/human/wizard_mob) + if(!istype(wizard_mob)) + return + + //So zards properly get their items when they are admin-made. + qdel(wizard_mob.wear_suit) + qdel(wizard_mob.head) + qdel(wizard_mob.shoes) + qdel(wizard_mob.r_hand) + qdel(wizard_mob.r_store) + qdel(wizard_mob.l_store) + + wizard_mob.equip_to_slot_or_del(new /obj/item/radio/headset(wizard_mob), slot_l_ear) + wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/under/color/lightpurple(wizard_mob), slot_w_uniform) + wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(wizard_mob), slot_shoes) + if(!isplasmaman(wizard_mob)) //handled in the species file for plasmen on the afterjob equip proc for now + wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(wizard_mob), slot_wear_suit) + wizard_mob.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(wizard_mob), slot_head) + wizard_mob.equip_to_slot_or_del(new /obj/item/storage/backpack/satchel(wizard_mob), slot_back) + wizard_mob.equip_to_slot_or_del(new /obj/item/storage/box/survival(wizard_mob), slot_in_backpack) + wizard_mob.equip_to_slot_or_del(new /obj/item/teleportation_scroll(wizard_mob), slot_r_store) + var/obj/item/spellbook/spellbook = new /obj/item/spellbook(wizard_mob) + spellbook.owner = wizard_mob + wizard_mob.equip_to_slot_or_del(spellbook, slot_r_hand) + + wizard_mob.faction = list("wizard") + + wizard_mob.dna.species.after_equip_job(null, wizard_mob) + + to_chat(wizard_mob, "You will find a list of available spells in your spell book. Choose your magic arsenal carefully.") + to_chat(wizard_mob, "The spellbook is bound to you, and others cannot use it.") + to_chat(wizard_mob, "In your pockets you will find a teleport scroll. Use it as needed.") + wizard_mob.mind.store_memory("Remember: do not forget to prepare your spells.") + wizard_mob.update_icons() + wizard_mob.gene_stability += DEFAULT_GENE_STABILITY //magic + return 1 + +// Checks if the game should end due to all wizards and apprentices being dead, or MMI'd/Borged +/datum/game_mode/wizard/check_finished() + var/wizards_alive = 0 + var/apprentices_alive = 0 + + // Wizards + for(var/datum/mind/wizard in wizards) + if(!istype(wizard.current,/mob/living/carbon)) + continue + if(wizard.current.stat==DEAD) + continue + if(istype(wizard.current, /obj/item/mmi)) // wizard is in an MMI, don't count them as alive + continue + wizards_alive++ + + // Apprentices + if(!wizards_alive) + for(var/datum/mind/apprentice in apprentices) + if(!istype(apprentice.current,/mob/living/carbon)) + continue + if(apprentice.current.stat==DEAD) + continue + if(istype(apprentice.current, /obj/item/mmi)) // apprentice is in an MMI, don't count them as alive + continue + apprentices_alive++ + + if(wizards_alive || apprentices_alive || but_wait_theres_more) + return ..() + else + finished = 1 + return 1 + +/datum/game_mode/wizard/declare_completion(var/ragin = 0) + if(finished && !ragin) + feedback_set_details("round_end_result","wizard loss - wizard killed") + to_chat(world, " The wizard[(wizards.len>1)?"s":""] has been killed by the crew! The Space Wizards Federation has been taught a lesson they will not soon forget!") + ..() + return 1 + +/datum/game_mode/proc/auto_declare_completion_wizard() + if(wizards.len) + var/text = "
        the wizards/witches were:" + + for(var/datum/mind/wizard in wizards) + + text += "
        [wizard.key] was [wizard.name] (" + if(wizard.current) + if(wizard.current.stat == DEAD) + text += "died" + else + text += "survived" + if(wizard.current.real_name != wizard.name) + text += " as [wizard.current.real_name]" + else + text += "body destroyed" + text += ")" + + var/count = 1 + var/wizardwin = 1 + for(var/datum/objective/objective in wizard.objectives) + if(objective.check_completion()) + text += "
        Objective #[count]: [objective.explanation_text] Success!" + feedback_add_details("wizard_objective","[objective.type]|SUCCESS") + else + text += "
        Objective #[count]: [objective.explanation_text] Fail." + feedback_add_details("wizard_objective","[objective.type]|FAIL") + wizardwin = 0 + count++ + + if(wizard.current && wizard.current.stat!=DEAD && wizardwin) + text += "
        The wizard was successful!" + feedback_add_details("wizard_success","SUCCESS") + else + text += "
        The wizard has failed!" + feedback_add_details("wizard_success","FAIL") + if(wizard.spell_list) + text += "
        [wizard.name] used the following spells: " + var/i = 1 + for(var/obj/effect/proc_holder/spell/S in wizard.spell_list) + text += "[S.name]" + if(wizard.spell_list.len > i) + text += ", " + i++ + text += "
        " + + to_chat(world, text) + return 1 + +//OTHER PROCS + +//To batch-remove wizard spells. Linked to mind.dm +/mob/proc/spellremove(mob/M) + if(!mind) + return + for(var/obj/effect/proc_holder/spell/spell_to_remove in mind.spell_list) + qdel(spell_to_remove) + mind.spell_list -= spell_to_remove + +//To batch-remove mob spells. +/mob/proc/mobspellremove(mob/M) + for(var/obj/effect/proc_holder/spell/spell_to_remove in mob_spell_list) + qdel(spell_to_remove) + mob_spell_list -= spell_to_remove + +/*Checks if the wizard can cast spells. +Made a proc so this is not repeated 14 (or more) times.*/ +/mob/proc/casting() +//Removed the stat check because not all spells require clothing now. + if(!istype(usr:wear_suit, /obj/item/clothing/suit/wizrobe)) + to_chat(usr, "I don't feel strong enough without my robe.") + return 0 + if(!istype(usr:shoes, /obj/item/clothing/shoes/sandal)) + to_chat(usr, "I don't feel strong enough without my sandals.") + return 0 + if(!istype(usr:head, /obj/item/clothing/head/wizard)) + to_chat(usr, "I don't feel strong enough without my hat.") + return 0 + else + return 1 + +/proc/iswizard(mob/living/M as mob) + return istype(M) && M.mind && SSticker && SSticker.mode && ((M.mind in SSticker.mode.wizards) || (M.mind in SSticker.mode.apprentices)) diff --git a/code/game/jobs/access.dm b/code/game/jobs/access.dm index 6ac2daef330..9d772b52422 100644 --- a/code/game/jobs/access.dm +++ b/code/game/jobs/access.dm @@ -1,517 +1,517 @@ -/obj/var/list/req_access = null -/obj/var/req_access_txt = "0" -/obj/var/list/req_one_access = null -/obj/var/req_one_access_txt = "0" - -//returns 1 if this mob has sufficient access to use this object -/obj/proc/allowed(mob/M) - //check if we don't require any access at all - if(check_access()) - return 1 - - if(!M) - return 0 - - var/acc = M.get_access() //see mob.dm - - if(acc == IGNORE_ACCESS || M.can_admin_interact()) - return 1 //Mob ignores access - - else - return check_access_list(acc) - - return 0 - -/obj/item/proc/GetAccess() - return list() - -/obj/item/proc/GetID() - return null - -/obj/proc/generate_req_lists() - //These generations have been moved out of /obj/New() because they were slowing down the creation of objects that never even used the access system. - if(!req_access) - req_access = list() - if(req_access_txt) - var/list/req_access_str = splittext(req_access_txt, ";") - for(var/x in req_access_str) - var/n = text2num(x) - if(n) - req_access += n - - if(!req_one_access) - req_one_access = list() - if(req_one_access_txt) - var/list/req_one_access_str = splittext(req_one_access_txt,";") - for(var/x in req_one_access_str) - var/n = text2num(x) - if(n) - req_one_access += n - -/obj/proc/check_access(obj/item/I) - var/list/L - if(I) - L = I.GetAccess() - else - L = list() - return check_access_list(L) - -/obj/proc/check_access_list(var/list/L) - generate_req_lists() - - if(!L) - return 0 - if(!istype(L, /list)) - return 0 - return has_access(req_access, req_one_access, L) - -/proc/has_access(var/list/req_access, var/list/req_one_access, var/list/accesses) - for(var/req in req_access) - if(!(req in accesses)) //doesn't have this access - return 0 - if(req_one_access.len) - for(var/req in req_one_access) - if(req in accesses) //has an access from the single access list - return 1 - return 0 - return 1 - -/proc/get_centcom_access(job) - switch(job) - if("VIP Guest") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) - if("Custodian") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE) - if("Thunderdome Overseer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) - if("Emergency Response Team Member") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS) + get_all_accesses() - if("Emergency Response Team Leader") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER) + get_all_accesses() - if("Medical Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE) + get_all_accesses() - if("Intel Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE) + get_all_accesses() - if("Research Officer") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE, ACCESS_CENT_TELECOMMS, ACCESS_CENT_TELEPORTER) + get_all_accesses() - if("Death Commando") - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER, ACCESS_CENT_BLACKOPS) + get_all_accesses() - if("Deathsquad Officer") - return get_all_centcom_access() + get_all_accesses() - if("NT Undercover Operative") - return get_all_centcom_access() + get_all_accesses() - if("Special Operations Officer") - return get_all_centcom_access() + get_all_accesses() - if("Nanotrasen Navy Representative") - return get_all_centcom_access() + get_all_accesses() - if("Nanotrasen Navy Officer") - return get_all_centcom_access() + get_all_accesses() - if("Nanotrasen Navy Captain") - return get_all_centcom_access() + get_all_accesses() - if("Supreme Commander") - return get_all_centcom_access() + get_all_accesses() - -/proc/get_syndicate_access(job) - switch(job) - if("Syndicate Operative") - return list(ACCESS_SYNDICATE) - if("Syndicate Operative Leader") - return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) - if("Syndicate Agent") - return list(ACCESS_SYNDICATE, ACCESS_MAINT_TUNNELS) - if("Vox Raider") - return list(ACCESS_VOX) - if("Vox Trader") - return list(ACCESS_VOX) - if("Syndicate Commando") - return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) - if("Syndicate Officer") - return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_SYNDICATE_COMMAND) - -/proc/get_all_accesses() - return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, - ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, - ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, - ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, - ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, - ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, - ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, - ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_PSYCHIATRIST, ACCESS_CMO, ACCESS_QM, ACCESS_CLOWN, ACCESS_MIME, ACCESS_SURGERY, - ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, - ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_RC_ANNOUNCE, - ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_PARAMEDIC, ACCESS_BLUESHIELD, ACCESS_MECHANIC,ACCESS_WEAPONS, - ACCESS_PILOT, ACCESS_NTREP, ACCESS_MAGISTRATE, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK) - -/proc/get_all_centcom_access() - return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SHUTTLES, ACCESS_CENT_TELECOMMS, ACCESS_CENT_TELEPORTER, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER, ACCESS_CENT_BLACKOPS, ACCESS_CENT_THUNDER, ACCESS_CENT_BRIDGE, ACCESS_CENT_COMMANDER) - -/proc/get_all_syndicate_access() - return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_VOX, ACCESS_SYNDICATE_COMMAND) - -/proc/get_all_misc_access() - return list(ACCESS_SALVAGE_CAPTAIN, ACCESS_TRADE_SOL, ACCESS_CRATE_CASH, ACCESS_AWAY01) - -/proc/get_absolutely_all_accesses() - return (get_all_accesses() | get_all_centcom_access() | get_all_syndicate_access() | get_all_misc_access()) - -/proc/get_region_accesses(code) - switch(code) - if(REGION_ALL) - return get_all_accesses() - if(REGION_GENERAL) //station general - return list(ACCESS_KITCHEN, ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER, ACCESS_MAGISTRATE, ACCESS_CLOWN, ACCESS_MIME) - if(REGION_SECURITY) //security - return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_PILOT, ACCESS_HOS) - if(REGION_MEDBAY) //medbay - return list(ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_PSYCHIATRIST, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_CMO, ACCESS_PARAMEDIC) - if(REGION_RESEARCH) //research - return list(ACCESS_RESEARCH, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_XENOARCH, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) - if(REGION_ENGINEERING) //engineering and maintenance - return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MINISAT, ACCESS_CE, ACCESS_MECHANIC) - if(REGION_SUPPLY) //supply - return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM) - if(REGION_COMMAND) //command - return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HEADS_VAULT, ACCESS_BLUESHIELD, ACCESS_NTREP, ACCESS_HOP, ACCESS_CAPTAIN) - if(REGION_CENTCOMM) //because why the heck not - return get_all_centcom_access() + get_all_accesses() - -/proc/get_region_accesses_name(code) - switch(code) - if(REGION_ALL) - return "All" - if(REGION_GENERAL) //station general - return "General" - if(REGION_SECURITY) //security - return "Security" - if(REGION_MEDBAY) //medbay - return "Medbay" - if(REGION_RESEARCH) //research - return "Research" - if(REGION_ENGINEERING) //engineering and maintenance - return "Engineering" - if(REGION_SUPPLY) //supply - return "Supply" - if(REGION_COMMAND) //command - return "Command" - if(REGION_CENTCOMM) //CC - return "CentComm" - - -/proc/get_access_desc(A) - switch(A) - if(ACCESS_CARGO) - return "Cargo Bay" - if(ACCESS_CARGO_BOT) - return "Cargo Bot Delivery" - if(ACCESS_SECURITY) - return "Security" - if(ACCESS_BRIG) - return "Holding Cells" - if(ACCESS_COURT) - return "Courtroom" - if(ACCESS_FORENSICS_LOCKERS) - return "Forensics" - if(ACCESS_MEDICAL) - return "Medical" - if(ACCESS_GENETICS) - return "Genetics Lab" - if(ACCESS_MORGUE) - return "Morgue" - if(ACCESS_TOX) - return "R&D Lab" - if(ACCESS_TOX_STORAGE) - return "Toxins Lab" - if(ACCESS_CHEMISTRY) - return "Chemistry Lab" - if(ACCESS_RD) - return "Research Director" - if(ACCESS_BAR) - return "Bar" - if(ACCESS_JANITOR) - return "Custodial Closet" - if(ACCESS_ENGINE) - return "Engineering" - if(ACCESS_ENGINE_EQUIP) - return "Power Equipment" - if(ACCESS_MAINT_TUNNELS) - return "Maintenance" - if(ACCESS_EXTERNAL_AIRLOCKS) - return "External Airlocks" - if(ACCESS_EMERGENCY_STORAGE) - return "Emergency Storage" - if(ACCESS_CHANGE_IDS) - return "ID Computer" - if(ACCESS_AI_UPLOAD) - return "AI Upload" - if(ACCESS_TELEPORTER) - return "Teleporter" - if(ACCESS_EVA) - return "EVA" - if(ACCESS_HEADS) - return "Bridge" - if(ACCESS_CAPTAIN) - return "Captain" - if(ACCESS_ALL_PERSONAL_LOCKERS) - return "Personal Lockers" - if(ACCESS_CHAPEL_OFFICE) - return "Chapel Office" - if(ACCESS_TECH_STORAGE) - return "Technical Storage" - if(ACCESS_ATMOSPHERICS) - return "Atmospherics" - if(ACCESS_CREMATORIUM) - return "Crematorium" - if(ACCESS_ARMORY) - return "Armory" - if(ACCESS_CONSTRUCTION) - return "Construction Areas" - if(ACCESS_KITCHEN) - return "Kitchen" - if(ACCESS_HYDROPONICS) - return "Hydroponics" - if(ACCESS_LIBRARY) - return "Library" - if(ACCESS_LAWYER) - return "Law Office" - if(ACCESS_ROBOTICS) - return "Robotics" - if(ACCESS_VIROLOGY) - return "Virology" - if(ACCESS_PSYCHIATRIST) - return "Psychiatrist's Office" - if(ACCESS_CMO) - return "Chief Medical Officer" - if(ACCESS_QM) - return "Quartermaster" - if(ACCESS_CLOWN) - return "Clown's Office" - if(ACCESS_MIME) - return "Mime's Office" - if(ACCESS_SURGERY) - return "Surgery" - if(ACCESS_THEATRE) - return "Theatre" - if(ACCESS_MANUFACTURING) - return "Manufacturing" - if(ACCESS_RESEARCH) - return "Science" - if(ACCESS_MINING) - return "Mining" - if(ACCESS_MINING_OFFICE) - return "Mining Office" - if(ACCESS_MAILSORTING) - return "Cargo Office" - if(ACCESS_MINT) - return "Mint" - if(ACCESS_MINT_VAULT) - return "Mint Vault" - if(ACCESS_HEADS_VAULT) - return "Main Vault" - if(ACCESS_MINING_STATION) - return "Mining EVA" - if(ACCESS_XENOBIOLOGY) - return "Xenobiology Lab" - if(ACCESS_XENOARCH) - return "Xenoarchaeology" - if(ACCESS_HOP) - return "Head of Personnel" - if(ACCESS_HOS) - return "Head of Security" - if(ACCESS_CE) - return "Chief Engineer" - if(ACCESS_RC_ANNOUNCE) - return "RC Announcements" - if(ACCESS_KEYCARD_AUTH) - return "Keycode Auth. Device" - if(ACCESS_TCOMSAT) - return "Telecommunications" - if(ACCESS_NETWORK) - return "Network Access" - if(ACCESS_GATEWAY) - return "Gateway" - if(ACCESS_SEC_DOORS) - return "Brig" - if(ACCESS_BLUESHIELD) - return "Blueshield" - if(ACCESS_NTREP) - return "Nanotrasen Rep." - if(ACCESS_PARAMEDIC) - return "Paramedic" - if(ACCESS_MECHANIC) - return "Mechanic Workshop" - if(ACCESS_PILOT) - return "Security Pod Pilot" - if(ACCESS_MAGISTRATE) - return "Magistrate" - if(ACCESS_MINERAL_STOREROOM) - return "Mineral Storage" - if(ACCESS_MINISAT) - return "AI Satellite" - if(ACCESS_WEAPONS) - return "Weapon Permit" - -/proc/get_centcom_access_desc(A) - switch(A) - if(ACCESS_CENT_GENERAL) - return "General Access" - if(ACCESS_CENT_LIVING) - return "Living Quarters" - if(ACCESS_CENT_MEDICAL) - return "Medical" - if(ACCESS_CENT_SECURITY) - return "Security" - if(ACCESS_CENT_STORAGE) - return "Storage" - if(ACCESS_CENT_SHUTTLES) - return "Shuttles" - if(ACCESS_CENT_TELECOMMS) - return "Telecommunications" - if(ACCESS_CENT_TELEPORTER) - return "Teleporter" - if(ACCESS_CENT_SPECOPS) - return "Special Ops" - if(ACCESS_CENT_SPECOPS_COMMANDER) - return "Special Ops Commander" - if(ACCESS_CENT_BLACKOPS) - return "Black Ops" - if(ACCESS_CENT_THUNDER) - return "Thunderdome" - if(ACCESS_CENT_BRIDGE) - return "Bridge" - if(ACCESS_CENT_COMMANDER) - return "Commander" - -/proc/get_syndicate_access_desc(A) - switch(A) - if(ACCESS_SYNDICATE) - return "Syndicate Operative" - if(ACCESS_SYNDICATE_LEADER) - return "Syndicate Operative Leader" - if(ACCESS_VOX) - return "Vox" - if(ACCESS_SYNDICATE_COMMAND) - return "Syndicate Command" - -/proc/get_all_jobs() - var/list/all_jobs = list() - var/list/all_datums = subtypesof(/datum/job) - all_datums.Remove(list(/datum/job/ai,/datum/job/cyborg)) - var/datum/job/jobdatum - for(var/jobtype in all_datums) - jobdatum = new jobtype - all_jobs.Add(jobdatum.title) - return all_jobs - -/proc/get_all_centcom_jobs() - return list("VIP Guest","Custodian","Thunderdome Overseer","Emergency Response Team Member","Emergency Response Team Leader","Intel Officer","Medical Officer","Death Commando","Research Officer","Deathsquad Officer","Special Operations Officer","Nanotrasen Navy Representative","Nanotrasen Navy Officer","Nanotrasen Navy Captain","Supreme Commander") - -//gets the actual job rank (ignoring alt titles) -//this is used solely for sechuds -/obj/proc/GetJobRealName() - if(!istype(src, /obj/item/pda) && !istype(src,/obj/item/card/id)) - return - - var/rank - var/assignment - if(istype(src, /obj/item/pda)) - if(src:id) - rank = src:id:rank - assignment = src:id:assignment - else if(istype(src, /obj/item/card/id)) - rank = src:rank - assignment = src:assignment - - if( rank in GLOB.joblist ) - return rank - - if( assignment in GLOB.joblist ) - return assignment - - return "Unknown" - -//gets the alt title, failing that the actual job rank -//this is unused -/obj/proc/sdsdsd() //GetJobDisplayName - if(!istype(src, /obj/item/pda) && !istype(src,/obj/item/card/id)) - return - - var/assignment - if(istype(src, /obj/item/pda)) - if(src:id) - assignment = src:id:assignment - else if(istype(src, /obj/item/card/id)) - assignment = src:assignment - - if(assignment) - return assignment - - return "Unknown" - -proc/GetIdCard(var/mob/living/carbon/human/H) - if(H.wear_id) - var/id = H.wear_id.GetID() - if(id) - return id - if(H.get_active_hand()) - var/obj/item/I = H.get_active_hand() - return I.GetID() - -proc/FindNameFromID(var/mob/living/carbon/human/H) - ASSERT(istype(H)) - var/obj/item/card/id/C = H.get_active_hand() - if( istype(C) || istype(C, /obj/item/pda) ) - var/obj/item/card/id/ID = C - - if( istype(C, /obj/item/pda) ) - var/obj/item/pda/pda = C - ID = pda.id - if(!istype(ID)) - ID = null - - if(ID) - return ID.registered_name - - C = H.wear_id - - if( istype(C) || istype(C, /obj/item/pda) ) - var/obj/item/card/id/ID = C - - if( istype(C, /obj/item/pda) ) - var/obj/item/pda/pda = C - ID = pda.id - if(!istype(ID)) - ID = null - - if(ID) - return ID.registered_name - -proc/get_all_job_icons() //For all existing HUD icons - return GLOB.joblist + list("Prisoner") - -/obj/proc/GetJobName() //Used in secHUD icon generation - var/assignmentName = "Unknown" - var/rankName = "Unknown" - if(istype(src, /obj/item/pda)) - var/obj/item/pda/P = src - assignmentName = P.ownjob - rankName = P.ownrank - else if(istype(src, /obj/item/card/id)) - var/obj/item/card/id/I = src - assignmentName = I.assignment - rankName = I.rank - - - var/job_icons = get_all_job_icons() - var/centcom = get_all_centcom_jobs() - - if(assignmentName in centcom) //Return with the NT logo if it is a Centcom job - return "Centcom" - if(rankName in centcom) - return "Centcom" - - if(assignmentName in job_icons) //Check if the job has a hud icon - return assignmentName - if(rankName in job_icons) - return rankName - - return "Unknown" //Return unknown if none of the above apply +/obj/var/list/req_access = null +/obj/var/req_access_txt = "0" +/obj/var/list/req_one_access = null +/obj/var/req_one_access_txt = "0" + +//returns 1 if this mob has sufficient access to use this object +/obj/proc/allowed(mob/M) + //check if we don't require any access at all + if(check_access()) + return 1 + + if(!M) + return 0 + + var/acc = M.get_access() //see mob.dm + + if(acc == IGNORE_ACCESS || M.can_admin_interact()) + return 1 //Mob ignores access + + else + return check_access_list(acc) + + return 0 + +/obj/item/proc/GetAccess() + return list() + +/obj/item/proc/GetID() + return null + +/obj/proc/generate_req_lists() + //These generations have been moved out of /obj/New() because they were slowing down the creation of objects that never even used the access system. + if(!req_access) + req_access = list() + if(req_access_txt) + var/list/req_access_str = splittext(req_access_txt, ";") + for(var/x in req_access_str) + var/n = text2num(x) + if(n) + req_access += n + + if(!req_one_access) + req_one_access = list() + if(req_one_access_txt) + var/list/req_one_access_str = splittext(req_one_access_txt,";") + for(var/x in req_one_access_str) + var/n = text2num(x) + if(n) + req_one_access += n + +/obj/proc/check_access(obj/item/I) + var/list/L + if(I) + L = I.GetAccess() + else + L = list() + return check_access_list(L) + +/obj/proc/check_access_list(var/list/L) + generate_req_lists() + + if(!L) + return 0 + if(!istype(L, /list)) + return 0 + return has_access(req_access, req_one_access, L) + +/proc/has_access(var/list/req_access, var/list/req_one_access, var/list/accesses) + for(var/req in req_access) + if(!(req in accesses)) //doesn't have this access + return 0 + if(req_one_access.len) + for(var/req in req_one_access) + if(req in accesses) //has an access from the single access list + return 1 + return 0 + return 1 + +/proc/get_centcom_access(job) + switch(job) + if("VIP Guest") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING) + if("Custodian") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE) + if("Thunderdome Overseer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_THUNDER) + if("Emergency Response Team Member") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS) + get_all_accesses() + if("Emergency Response Team Leader") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER) + get_all_accesses() + if("Medical Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE) + get_all_accesses() + if("Intel Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE) + get_all_accesses() + if("Research Officer") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_SPECOPS, ACCESS_CENT_MEDICAL, ACCESS_CENT_STORAGE, ACCESS_CENT_TELECOMMS, ACCESS_CENT_TELEPORTER) + get_all_accesses() + if("Death Commando") + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER, ACCESS_CENT_BLACKOPS) + get_all_accesses() + if("Deathsquad Officer") + return get_all_centcom_access() + get_all_accesses() + if("NT Undercover Operative") + return get_all_centcom_access() + get_all_accesses() + if("Special Operations Officer") + return get_all_centcom_access() + get_all_accesses() + if("Nanotrasen Navy Representative") + return get_all_centcom_access() + get_all_accesses() + if("Nanotrasen Navy Officer") + return get_all_centcom_access() + get_all_accesses() + if("Nanotrasen Navy Captain") + return get_all_centcom_access() + get_all_accesses() + if("Supreme Commander") + return get_all_centcom_access() + get_all_accesses() + +/proc/get_syndicate_access(job) + switch(job) + if("Syndicate Operative") + return list(ACCESS_SYNDICATE) + if("Syndicate Operative Leader") + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) + if("Syndicate Agent") + return list(ACCESS_SYNDICATE, ACCESS_MAINT_TUNNELS) + if("Vox Raider") + return list(ACCESS_VOX) + if("Vox Trader") + return list(ACCESS_VOX) + if("Syndicate Commando") + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) + if("Syndicate Officer") + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_SYNDICATE_COMMAND) + +/proc/get_all_accesses() + return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, + ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, + ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, + ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, + ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, + ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, + ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_PSYCHIATRIST, ACCESS_CMO, ACCESS_QM, ACCESS_CLOWN, ACCESS_MIME, ACCESS_SURGERY, + ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, + ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_PARAMEDIC, ACCESS_BLUESHIELD, ACCESS_MECHANIC,ACCESS_WEAPONS, + ACCESS_PILOT, ACCESS_NTREP, ACCESS_MAGISTRATE, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK) + +/proc/get_all_centcom_access() + return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SHUTTLES, ACCESS_CENT_TELECOMMS, ACCESS_CENT_TELEPORTER, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER, ACCESS_CENT_BLACKOPS, ACCESS_CENT_THUNDER, ACCESS_CENT_BRIDGE, ACCESS_CENT_COMMANDER) + +/proc/get_all_syndicate_access() + return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_VOX, ACCESS_SYNDICATE_COMMAND) + +/proc/get_all_misc_access() + return list(ACCESS_SALVAGE_CAPTAIN, ACCESS_TRADE_SOL, ACCESS_CRATE_CASH, ACCESS_AWAY01) + +/proc/get_absolutely_all_accesses() + return (get_all_accesses() | get_all_centcom_access() | get_all_syndicate_access() | get_all_misc_access()) + +/proc/get_region_accesses(code) + switch(code) + if(REGION_ALL) + return get_all_accesses() + if(REGION_GENERAL) //station general + return list(ACCESS_KITCHEN, ACCESS_BAR, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_LIBRARY, ACCESS_THEATRE, ACCESS_LAWYER, ACCESS_MAGISTRATE, ACCESS_CLOWN, ACCESS_MIME) + if(REGION_SECURITY) //security + return list(ACCESS_SEC_DOORS, ACCESS_WEAPONS, ACCESS_SECURITY, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, ACCESS_PILOT, ACCESS_HOS) + if(REGION_MEDBAY) //medbay + return list(ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_PSYCHIATRIST, ACCESS_VIROLOGY, ACCESS_SURGERY, ACCESS_CMO, ACCESS_PARAMEDIC) + if(REGION_RESEARCH) //research + return list(ACCESS_RESEARCH, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_GENETICS, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_XENOARCH, ACCESS_MINISAT, ACCESS_RD, ACCESS_NETWORK) + if(REGION_ENGINEERING) //engineering and maintenance + return list(ACCESS_CONSTRUCTION, ACCESS_MAINT_TUNNELS, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_TECH_STORAGE, ACCESS_ATMOSPHERICS, ACCESS_MINISAT, ACCESS_CE, ACCESS_MECHANIC) + if(REGION_SUPPLY) //supply + return list(ACCESS_MAILSORTING, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_CARGO, ACCESS_QM) + if(REGION_COMMAND) //command + return list(ACCESS_HEADS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_ALL_PERSONAL_LOCKERS, ACCESS_HEADS_VAULT, ACCESS_BLUESHIELD, ACCESS_NTREP, ACCESS_HOP, ACCESS_CAPTAIN) + if(REGION_CENTCOMM) //because why the heck not + return get_all_centcom_access() + get_all_accesses() + +/proc/get_region_accesses_name(code) + switch(code) + if(REGION_ALL) + return "All" + if(REGION_GENERAL) //station general + return "General" + if(REGION_SECURITY) //security + return "Security" + if(REGION_MEDBAY) //medbay + return "Medbay" + if(REGION_RESEARCH) //research + return "Research" + if(REGION_ENGINEERING) //engineering and maintenance + return "Engineering" + if(REGION_SUPPLY) //supply + return "Supply" + if(REGION_COMMAND) //command + return "Command" + if(REGION_CENTCOMM) //CC + return "CentComm" + + +/proc/get_access_desc(A) + switch(A) + if(ACCESS_CARGO) + return "Cargo Bay" + if(ACCESS_CARGO_BOT) + return "Cargo Bot Delivery" + if(ACCESS_SECURITY) + return "Security" + if(ACCESS_BRIG) + return "Holding Cells" + if(ACCESS_COURT) + return "Courtroom" + if(ACCESS_FORENSICS_LOCKERS) + return "Forensics" + if(ACCESS_MEDICAL) + return "Medical" + if(ACCESS_GENETICS) + return "Genetics Lab" + if(ACCESS_MORGUE) + return "Morgue" + if(ACCESS_TOX) + return "R&D Lab" + if(ACCESS_TOX_STORAGE) + return "Toxins Lab" + if(ACCESS_CHEMISTRY) + return "Chemistry Lab" + if(ACCESS_RD) + return "Research Director" + if(ACCESS_BAR) + return "Bar" + if(ACCESS_JANITOR) + return "Custodial Closet" + if(ACCESS_ENGINE) + return "Engineering" + if(ACCESS_ENGINE_EQUIP) + return "Power Equipment" + if(ACCESS_MAINT_TUNNELS) + return "Maintenance" + if(ACCESS_EXTERNAL_AIRLOCKS) + return "External Airlocks" + if(ACCESS_EMERGENCY_STORAGE) + return "Emergency Storage" + if(ACCESS_CHANGE_IDS) + return "ID Computer" + if(ACCESS_AI_UPLOAD) + return "AI Upload" + if(ACCESS_TELEPORTER) + return "Teleporter" + if(ACCESS_EVA) + return "EVA" + if(ACCESS_HEADS) + return "Bridge" + if(ACCESS_CAPTAIN) + return "Captain" + if(ACCESS_ALL_PERSONAL_LOCKERS) + return "Personal Lockers" + if(ACCESS_CHAPEL_OFFICE) + return "Chapel Office" + if(ACCESS_TECH_STORAGE) + return "Technical Storage" + if(ACCESS_ATMOSPHERICS) + return "Atmospherics" + if(ACCESS_CREMATORIUM) + return "Crematorium" + if(ACCESS_ARMORY) + return "Armory" + if(ACCESS_CONSTRUCTION) + return "Construction Areas" + if(ACCESS_KITCHEN) + return "Kitchen" + if(ACCESS_HYDROPONICS) + return "Hydroponics" + if(ACCESS_LIBRARY) + return "Library" + if(ACCESS_LAWYER) + return "Law Office" + if(ACCESS_ROBOTICS) + return "Robotics" + if(ACCESS_VIROLOGY) + return "Virology" + if(ACCESS_PSYCHIATRIST) + return "Psychiatrist's Office" + if(ACCESS_CMO) + return "Chief Medical Officer" + if(ACCESS_QM) + return "Quartermaster" + if(ACCESS_CLOWN) + return "Clown's Office" + if(ACCESS_MIME) + return "Mime's Office" + if(ACCESS_SURGERY) + return "Surgery" + if(ACCESS_THEATRE) + return "Theatre" + if(ACCESS_MANUFACTURING) + return "Manufacturing" + if(ACCESS_RESEARCH) + return "Science" + if(ACCESS_MINING) + return "Mining" + if(ACCESS_MINING_OFFICE) + return "Mining Office" + if(ACCESS_MAILSORTING) + return "Cargo Office" + if(ACCESS_MINT) + return "Mint" + if(ACCESS_MINT_VAULT) + return "Mint Vault" + if(ACCESS_HEADS_VAULT) + return "Main Vault" + if(ACCESS_MINING_STATION) + return "Mining EVA" + if(ACCESS_XENOBIOLOGY) + return "Xenobiology Lab" + if(ACCESS_XENOARCH) + return "Xenoarchaeology" + if(ACCESS_HOP) + return "Head of Personnel" + if(ACCESS_HOS) + return "Head of Security" + if(ACCESS_CE) + return "Chief Engineer" + if(ACCESS_RC_ANNOUNCE) + return "RC Announcements" + if(ACCESS_KEYCARD_AUTH) + return "Keycode Auth. Device" + if(ACCESS_TCOMSAT) + return "Telecommunications" + if(ACCESS_NETWORK) + return "Network Access" + if(ACCESS_GATEWAY) + return "Gateway" + if(ACCESS_SEC_DOORS) + return "Brig" + if(ACCESS_BLUESHIELD) + return "Blueshield" + if(ACCESS_NTREP) + return "Nanotrasen Rep." + if(ACCESS_PARAMEDIC) + return "Paramedic" + if(ACCESS_MECHANIC) + return "Mechanic Workshop" + if(ACCESS_PILOT) + return "Security Pod Pilot" + if(ACCESS_MAGISTRATE) + return "Magistrate" + if(ACCESS_MINERAL_STOREROOM) + return "Mineral Storage" + if(ACCESS_MINISAT) + return "AI Satellite" + if(ACCESS_WEAPONS) + return "Weapon Permit" + +/proc/get_centcom_access_desc(A) + switch(A) + if(ACCESS_CENT_GENERAL) + return "General Access" + if(ACCESS_CENT_LIVING) + return "Living Quarters" + if(ACCESS_CENT_MEDICAL) + return "Medical" + if(ACCESS_CENT_SECURITY) + return "Security" + if(ACCESS_CENT_STORAGE) + return "Storage" + if(ACCESS_CENT_SHUTTLES) + return "Shuttles" + if(ACCESS_CENT_TELECOMMS) + return "Telecommunications" + if(ACCESS_CENT_TELEPORTER) + return "Teleporter" + if(ACCESS_CENT_SPECOPS) + return "Special Ops" + if(ACCESS_CENT_SPECOPS_COMMANDER) + return "Special Ops Commander" + if(ACCESS_CENT_BLACKOPS) + return "Black Ops" + if(ACCESS_CENT_THUNDER) + return "Thunderdome" + if(ACCESS_CENT_BRIDGE) + return "Bridge" + if(ACCESS_CENT_COMMANDER) + return "Commander" + +/proc/get_syndicate_access_desc(A) + switch(A) + if(ACCESS_SYNDICATE) + return "Syndicate Operative" + if(ACCESS_SYNDICATE_LEADER) + return "Syndicate Operative Leader" + if(ACCESS_VOX) + return "Vox" + if(ACCESS_SYNDICATE_COMMAND) + return "Syndicate Command" + +/proc/get_all_jobs() + var/list/all_jobs = list() + var/list/all_datums = subtypesof(/datum/job) + all_datums.Remove(list(/datum/job/ai,/datum/job/cyborg)) + var/datum/job/jobdatum + for(var/jobtype in all_datums) + jobdatum = new jobtype + all_jobs.Add(jobdatum.title) + return all_jobs + +/proc/get_all_centcom_jobs() + return list("VIP Guest","Custodian","Thunderdome Overseer","Emergency Response Team Member","Emergency Response Team Leader","Intel Officer","Medical Officer","Death Commando","Research Officer","Deathsquad Officer","Special Operations Officer","Nanotrasen Navy Representative","Nanotrasen Navy Officer","Nanotrasen Navy Captain","Supreme Commander") + +//gets the actual job rank (ignoring alt titles) +//this is used solely for sechuds +/obj/proc/GetJobRealName() + if(!istype(src, /obj/item/pda) && !istype(src,/obj/item/card/id)) + return + + var/rank + var/assignment + if(istype(src, /obj/item/pda)) + if(src:id) + rank = src:id:rank + assignment = src:id:assignment + else if(istype(src, /obj/item/card/id)) + rank = src:rank + assignment = src:assignment + + if( rank in GLOB.joblist ) + return rank + + if( assignment in GLOB.joblist ) + return assignment + + return "Unknown" + +//gets the alt title, failing that the actual job rank +//this is unused +/obj/proc/sdsdsd() //GetJobDisplayName + if(!istype(src, /obj/item/pda) && !istype(src,/obj/item/card/id)) + return + + var/assignment + if(istype(src, /obj/item/pda)) + if(src:id) + assignment = src:id:assignment + else if(istype(src, /obj/item/card/id)) + assignment = src:assignment + + if(assignment) + return assignment + + return "Unknown" + +proc/GetIdCard(var/mob/living/carbon/human/H) + if(H.wear_id) + var/id = H.wear_id.GetID() + if(id) + return id + if(H.get_active_hand()) + var/obj/item/I = H.get_active_hand() + return I.GetID() + +proc/FindNameFromID(var/mob/living/carbon/human/H) + ASSERT(istype(H)) + var/obj/item/card/id/C = H.get_active_hand() + if( istype(C) || istype(C, /obj/item/pda) ) + var/obj/item/card/id/ID = C + + if( istype(C, /obj/item/pda) ) + var/obj/item/pda/pda = C + ID = pda.id + if(!istype(ID)) + ID = null + + if(ID) + return ID.registered_name + + C = H.wear_id + + if( istype(C) || istype(C, /obj/item/pda) ) + var/obj/item/card/id/ID = C + + if( istype(C, /obj/item/pda) ) + var/obj/item/pda/pda = C + ID = pda.id + if(!istype(ID)) + ID = null + + if(ID) + return ID.registered_name + +proc/get_all_job_icons() //For all existing HUD icons + return GLOB.joblist + list("Prisoner") + +/obj/proc/GetJobName() //Used in secHUD icon generation + var/assignmentName = "Unknown" + var/rankName = "Unknown" + if(istype(src, /obj/item/pda)) + var/obj/item/pda/P = src + assignmentName = P.ownjob + rankName = P.ownrank + else if(istype(src, /obj/item/card/id)) + var/obj/item/card/id/I = src + assignmentName = I.assignment + rankName = I.rank + + + var/job_icons = get_all_job_icons() + var/centcom = get_all_centcom_jobs() + + if(assignmentName in centcom) //Return with the NT logo if it is a Centcom job + return "Centcom" + if(rankName in centcom) + return "Centcom" + + if(assignmentName in job_icons) //Check if the job has a hud icon + return assignmentName + if(rankName in job_icons) + return rankName + + return "Unknown" //Return unknown if none of the above apply diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm index d9c30292d82..796db23b36e 100644 --- a/code/game/jobs/job/central.dm +++ b/code/game/jobs/job/central.dm @@ -100,4 +100,4 @@ . = ..() if(visualsOnly) return - H.mind.offstation_role = TRUE \ No newline at end of file + H.mind.offstation_role = TRUE diff --git a/code/game/jobs/job/civilian.dm b/code/game/jobs/job/civilian.dm index 757f4b007c2..6adc55a89f1 100644 --- a/code/game/jobs/job/civilian.dm +++ b/code/game/jobs/job/civilian.dm @@ -1,28 +1,28 @@ -/datum/job/civilian - title = "Civilian" - flag = JOB_CIVILIAN - department_flag = JOBCAT_SUPPORT - total_positions = -1 - spawn_positions = -1 - supervisors = "the head of personnel" - department_head = list("Head of Personnel") - selection_color = "#dddddd" - access = list() //See /datum/job/assistant/get_access() - minimal_access = list() //See /datum/job/assistant/get_access() - alt_titles = list("Tourist","Businessman","Trader","Assistant") - outfit = /datum/outfit/job/assistant - -/datum/job/civilian/get_access() - if(config.assistant_maint) - return list(ACCESS_MAINT_TUNNELS) - else - return list() - -/datum/outfit/job/assistant - name = "Civilian" - jobtype = /datum/job/civilian - - uniform = /obj/item/clothing/under/color/random - shoes = /obj/item/clothing/shoes/black - - +/datum/job/civilian + title = "Civilian" + flag = JOB_CIVILIAN + department_flag = JOBCAT_SUPPORT + total_positions = -1 + spawn_positions = -1 + supervisors = "the head of personnel" + department_head = list("Head of Personnel") + selection_color = "#dddddd" + access = list() //See /datum/job/assistant/get_access() + minimal_access = list() //See /datum/job/assistant/get_access() + alt_titles = list("Tourist","Businessman","Trader","Assistant") + outfit = /datum/outfit/job/assistant + +/datum/job/civilian/get_access() + if(config.assistant_maint) + return list(ACCESS_MAINT_TUNNELS) + else + return list() + +/datum/outfit/job/assistant + name = "Civilian" + jobtype = /datum/job/civilian + + uniform = /obj/item/clothing/under/color/random + shoes = /obj/item/clothing/shoes/black + + diff --git a/code/game/jobs/job/engineering.dm b/code/game/jobs/job/engineering.dm index 6fe3d89fb67..84f903dd2b1 100644 --- a/code/game/jobs/job/engineering.dm +++ b/code/game/jobs/job/engineering.dm @@ -1,153 +1,153 @@ -/datum/job/chief_engineer - title = "Chief Engineer" - flag = JOB_CHIEF - department_flag = JOBCAT_ENGSEC - total_positions = 1 - spawn_positions = 1 - is_engineering = 1 - supervisors = "the captain" - department_head = list("Captain") - selection_color = "#ffeeaa" - req_admin_notify = 1 - access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, - ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, - ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, - ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, - ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, - ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, - ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM) - minimal_player_age = 21 - exp_requirements = 300 - exp_type = EXP_TYPE_ENGINEERING - outfit = /datum/outfit/job/chief_engineer - -/datum/outfit/job/chief_engineer - name = "Chief Engineer" - jobtype = /datum/job/chief_engineer - - uniform = /obj/item/clothing/under/rank/chief_engineer - belt = /obj/item/storage/belt/utility/chief/full - gloves = /obj/item/clothing/gloves/color/black/ce - shoes = /obj/item/clothing/shoes/brown - head = /obj/item/clothing/head/hardhat/white - l_ear = /obj/item/radio/headset/heads/ce - id = /obj/item/card/id/ce - l_pocket = /obj/item/t_scanner - pda = /obj/item/pda/heads/ce - backpack_contents = list( - /obj/item/melee/classic_baton/telescopic = 1 - ) - - backpack = /obj/item/storage/backpack/industrial - satchel = /obj/item/storage/backpack/satchel_eng - dufflebag = /obj/item/storage/backpack/duffel/engineering - box = /obj/item/storage/box/engineer - - -/datum/job/engineer - title = "Station Engineer" - flag = JOB_ENGINEER - department_flag = JOBCAT_ENGSEC - total_positions = 5 - spawn_positions = 5 - is_engineering = 1 - supervisors = "the chief engineer" - department_head = list("Chief Engineer") - selection_color = "#fff5cc" - access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM) - alt_titles = list("Maintenance Technician","Engine Technician","Electrician") - minimal_player_age = 7 - exp_requirements = 300 - exp_type = EXP_TYPE_CREW - outfit = /datum/outfit/job/engineer - -/datum/outfit/job/engineer - name = "Station Engineer" - jobtype = /datum/job/engineer - - uniform = /obj/item/clothing/under/rank/engineer - belt = /obj/item/storage/belt/utility/full - shoes = /obj/item/clothing/shoes/workboots - head = /obj/item/clothing/head/hardhat - l_ear = /obj/item/radio/headset/headset_eng - id = /obj/item/card/id/engineering - l_pocket = /obj/item/t_scanner - pda = /obj/item/pda/engineering - - backpack = /obj/item/storage/backpack/industrial - satchel = /obj/item/storage/backpack/satchel_eng - dufflebag = /obj/item/storage/backpack/duffel/engineering - box = /obj/item/storage/box/engineer - - - -/datum/job/atmos - title = "Life Support Specialist" - flag = JOB_ATMOSTECH - department_flag = JOBCAT_ENGSEC - total_positions = 3 - spawn_positions = 2 - is_engineering = 1 - supervisors = "the chief engineer" - department_head = list("Chief Engineer") - selection_color = "#fff5cc" - access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_EVA, ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_EMERGENCY_STORAGE, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE) - alt_titles = list("Atmospheric Technician") - minimal_player_age = 7 - exp_requirements = 300 - exp_type = EXP_TYPE_CREW - outfit = /datum/outfit/job/atmos - -/datum/outfit/job/atmos - name = "Life Support Specialist" - jobtype = /datum/job/atmos - - uniform = /obj/item/clothing/under/rank/atmospheric_technician - belt = /obj/item/storage/belt/utility/atmostech - shoes = /obj/item/clothing/shoes/workboots - l_ear = /obj/item/radio/headset/headset_eng - id = /obj/item/card/id/engineering - pda = /obj/item/pda/atmos - - backpack = /obj/item/storage/backpack/industrial - satchel = /obj/item/storage/backpack/satchel_eng - dufflebag = /obj/item/storage/backpack/duffel/atmos - box = /obj/item/storage/box/engineer - -/datum/job/mechanic - title = "Mechanic" - flag = JOB_MECHANIC - department_flag = JOBCAT_KARMA - total_positions = 1 - spawn_positions = 1 - is_engineering = 1 - supervisors = "the chief engineer" - department_head = list("Chief Engineer") - selection_color = "#fff5cc" - access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_MECHANIC, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_EMERGENCY_STORAGE, ACCESS_MECHANIC, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MINERAL_STOREROOM) - outfit = /datum/outfit/job/mechanic - -/datum/outfit/job/mechanic - name = "Mechanic" - jobtype = /datum/job/mechanic - - uniform = /obj/item/clothing/under/rank/mechanic - belt = /obj/item/storage/belt/utility/full - shoes = /obj/item/clothing/shoes/workboots - head = /obj/item/clothing/head/hardhat - l_ear = /obj/item/radio/headset/headset_eng - id = /obj/item/card/id/engineering - r_pocket = /obj/item/t_scanner - pda = /obj/item/pda/engineering - backpack_contents = list( - /obj/item/pod_paint_bucket = 1 - ) - - backpack = /obj/item/storage/backpack/industrial - satchel = /obj/item/storage/backpack/satchel_eng - dufflebag = /obj/item/storage/backpack/duffel/engineering - box = /obj/item/storage/box/engineer +/datum/job/chief_engineer + title = "Chief Engineer" + flag = JOB_CHIEF + department_flag = JOBCAT_ENGSEC + total_positions = 1 + spawn_positions = 1 + is_engineering = 1 + supervisors = "the captain" + department_head = list("Captain") + selection_color = "#ffeeaa" + req_admin_notify = 1 + access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, + ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, + ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, + ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, + ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, + ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, + ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM) + minimal_player_age = 21 + exp_requirements = 300 + exp_type = EXP_TYPE_ENGINEERING + outfit = /datum/outfit/job/chief_engineer + +/datum/outfit/job/chief_engineer + name = "Chief Engineer" + jobtype = /datum/job/chief_engineer + + uniform = /obj/item/clothing/under/rank/chief_engineer + belt = /obj/item/storage/belt/utility/chief/full + gloves = /obj/item/clothing/gloves/color/black/ce + shoes = /obj/item/clothing/shoes/brown + head = /obj/item/clothing/head/hardhat/white + l_ear = /obj/item/radio/headset/heads/ce + id = /obj/item/card/id/ce + l_pocket = /obj/item/t_scanner + pda = /obj/item/pda/heads/ce + backpack_contents = list( + /obj/item/melee/classic_baton/telescopic = 1 + ) + + backpack = /obj/item/storage/backpack/industrial + satchel = /obj/item/storage/backpack/satchel_eng + dufflebag = /obj/item/storage/backpack/duffel/engineering + box = /obj/item/storage/box/engineer + + +/datum/job/engineer + title = "Station Engineer" + flag = JOB_ENGINEER + department_flag = JOBCAT_ENGSEC + total_positions = 5 + spawn_positions = 5 + is_engineering = 1 + supervisors = "the chief engineer" + department_head = list("Chief Engineer") + selection_color = "#fff5cc" + access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM) + alt_titles = list("Maintenance Technician","Engine Technician","Electrician") + minimal_player_age = 7 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW + outfit = /datum/outfit/job/engineer + +/datum/outfit/job/engineer + name = "Station Engineer" + jobtype = /datum/job/engineer + + uniform = /obj/item/clothing/under/rank/engineer + belt = /obj/item/storage/belt/utility/full + shoes = /obj/item/clothing/shoes/workboots + head = /obj/item/clothing/head/hardhat + l_ear = /obj/item/radio/headset/headset_eng + id = /obj/item/card/id/engineering + l_pocket = /obj/item/t_scanner + pda = /obj/item/pda/engineering + + backpack = /obj/item/storage/backpack/industrial + satchel = /obj/item/storage/backpack/satchel_eng + dufflebag = /obj/item/storage/backpack/duffel/engineering + box = /obj/item/storage/box/engineer + + + +/datum/job/atmos + title = "Life Support Specialist" + flag = JOB_ATMOSTECH + department_flag = JOBCAT_ENGSEC + total_positions = 3 + spawn_positions = 2 + is_engineering = 1 + supervisors = "the chief engineer" + department_head = list("Chief Engineer") + selection_color = "#fff5cc" + access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_EVA, ACCESS_ATMOSPHERICS, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_EMERGENCY_STORAGE, ACCESS_CONSTRUCTION, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE) + alt_titles = list("Atmospheric Technician") + minimal_player_age = 7 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW + outfit = /datum/outfit/job/atmos + +/datum/outfit/job/atmos + name = "Life Support Specialist" + jobtype = /datum/job/atmos + + uniform = /obj/item/clothing/under/rank/atmospheric_technician + belt = /obj/item/storage/belt/utility/atmostech + shoes = /obj/item/clothing/shoes/workboots + l_ear = /obj/item/radio/headset/headset_eng + id = /obj/item/card/id/engineering + pda = /obj/item/pda/atmos + + backpack = /obj/item/storage/backpack/industrial + satchel = /obj/item/storage/backpack/satchel_eng + dufflebag = /obj/item/storage/backpack/duffel/atmos + box = /obj/item/storage/box/engineer + +/datum/job/mechanic + title = "Mechanic" + flag = JOB_MECHANIC + department_flag = JOBCAT_KARMA + total_positions = 1 + spawn_positions = 1 + is_engineering = 1 + supervisors = "the chief engineer" + department_head = list("Chief Engineer") + selection_color = "#fff5cc" + access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_MECHANIC, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_EMERGENCY_STORAGE, ACCESS_MECHANIC, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MINERAL_STOREROOM) + outfit = /datum/outfit/job/mechanic + +/datum/outfit/job/mechanic + name = "Mechanic" + jobtype = /datum/job/mechanic + + uniform = /obj/item/clothing/under/rank/mechanic + belt = /obj/item/storage/belt/utility/full + shoes = /obj/item/clothing/shoes/workboots + head = /obj/item/clothing/head/hardhat + l_ear = /obj/item/radio/headset/headset_eng + id = /obj/item/card/id/engineering + r_pocket = /obj/item/t_scanner + pda = /obj/item/pda/engineering + backpack_contents = list( + /obj/item/pod_paint_bucket = 1 + ) + + backpack = /obj/item/storage/backpack/industrial + satchel = /obj/item/storage/backpack/satchel_eng + dufflebag = /obj/item/storage/backpack/duffel/engineering + box = /obj/item/storage/box/engineer diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm index 9def19d2970..03ac85c8d57 100644 --- a/code/game/jobs/job/job.dm +++ b/code/game/jobs/job/job.dm @@ -1,272 +1,272 @@ -/datum/job - - //The name of the job - var/title = "NOPE" - - //Job access. The use of minimal_access or access is determined by a config setting: config.jobs_have_minimal_access - var/list/minimal_access = list() //Useful for servers which prefer to only have access given to the places a job absolutely needs (Larger server population) - var/list/access = list() //Useful for servers which either have fewer players, so each person needs to fill more than one role, or servers which like to give more access, so players can't hide forever in their super secure departments (I'm looking at you, chemistry!) - - //Bitflags for the job - var/flag = 0 - var/department_flag = 0 - var/department_head = list() - - //Players will be allowed to spawn in as jobs that are set to "Station" - var/list/faction = list("Station") - - //How many players can be this job - var/total_positions = 0 - - //How many players can spawn in as this job - var/spawn_positions = 0 - - //How many players have this job - var/current_positions = 0 - - //Supervisors, who this person answers to directly - var/supervisors = "" - - //Sellection screen color - var/selection_color = "#ffffff" - - //List of alternate titles, if any - var/list/alt_titles - - //If this is set to 1, a text is printed to the player when jobs are assigned, telling him that he should let admins know that he has to disconnect. - var/req_admin_notify - - //Various Departmental identifiers - var/is_supply - var/is_service - var/is_command - var/is_legal - var/is_engineering - var/is_medical - var/is_science - var/is_security - - //If you have use_age_restriction_for_jobs config option enabled and the database set up, this option will add a requirement for players to be at least minimal_player_age days old. (meaning they first signed in at least that many days before.) - var/minimal_player_age = 0 - - var/exp_requirements = 0 - var/exp_type = "" - - var/disabilities_allowed = 1 - - var/admin_only = 0 - var/spawn_ert = 0 - var/syndicate_command = 0 - - var/outfit = null - - ///////////////////////////////// - // /vg/ feature: Job Objectives! - ///////////////////////////////// - var/required_objectives=list() // Objectives that are ALWAYS added. - var/optional_objectives=list() // Objectives that are SOMETIMES added. - -//Only override this proc -/datum/job/proc/after_spawn(mob/living/carbon/human/H) - -/datum/job/proc/announce(mob/living/carbon/human/H) - -/datum/job/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE) - if(!H) - return 0 - - H.dna.species.before_equip_job(src, H, visualsOnly) - - if(outfit) - H.equipOutfit(outfit, visualsOnly) - - H.dna.species.after_equip_job(src, H, visualsOnly) - - if(!visualsOnly && announce) - announce(H) - -/datum/job/proc/get_access() - if(!config) //Needed for robots. - return src.minimal_access.Copy() - - if(config.jobs_have_minimal_access) - return src.minimal_access.Copy() - else - return src.access.Copy() - -//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 -/datum/job/proc/player_old_enough(client/C) - if(available_in_days(C) == 0) - return 1 //Available in 0 days = available right now = player is old enough to play. - return 0 - - -/datum/job/proc/available_in_days(client/C) - if(!C) - return 0 - if(!config.use_age_restriction_for_jobs) - return 0 - if(!isnum(C.player_age)) - return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced - if(!isnum(minimal_player_age)) - return 0 - - return max(0, minimal_player_age - C.player_age) - -/datum/job/proc/barred_by_disability(client/C) - if(!C) - return 0 - if(disabilities_allowed) - return 0 - var/list/prohibited_disabilities = list(DISABILITY_FLAG_BLIND, DISABILITY_FLAG_DEAF, DISABILITY_FLAG_MUTE, DISABILITY_FLAG_DIZZY) - for(var/i = 1, i < prohibited_disabilities.len, i++) - var/this_disability = prohibited_disabilities[i] - if(C.prefs.disabilities & this_disability) - return 1 - return 0 - -/datum/job/proc/is_position_available() - return (current_positions < total_positions) || (total_positions == -1) - -/datum/outfit/job - name = "Standard Gear" - collect_not_del = TRUE // we don't want anyone to lose their job shit - - var/allow_loadout = TRUE - var/allow_backbag_choice = TRUE - var/jobtype = null - - uniform = /obj/item/clothing/under/color/grey - id = /obj/item/card/id - l_ear = /obj/item/radio/headset - back = /obj/item/storage/backpack - shoes = /obj/item/clothing/shoes/black - pda = /obj/item/pda - - var/backpack = /obj/item/storage/backpack - var/satchel = /obj/item/storage/backpack/satchel_norm - var/dufflebag = /obj/item/storage/backpack/duffel - box = /obj/item/storage/box/survival - - var/tmp/list/gear_leftovers = list() - -/datum/outfit/job/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE) - if(allow_backbag_choice) - switch(H.backbag) - if(GBACKPACK) - back = /obj/item/storage/backpack //Grey backpack - if(GSATCHEL) - back = /obj/item/storage/backpack/satchel_norm //Grey satchel - if(GDUFFLEBAG) - back = /obj/item/storage/backpack/duffel //Grey Dufflebag - if(LSATCHEL) - back = /obj/item/storage/backpack/satchel //Leather Satchel - if(DSATCHEL) - back = satchel //Department satchel - if(DDUFFLEBAG) - back = dufflebag //Department dufflebag - else - back = backpack //Department backpack - - if(box && H.dna.species.speciesbox) - box = H.dna.species.speciesbox - - if(allow_loadout && H.client && (H.client.prefs.gear && H.client.prefs.gear.len)) - for(var/gear in H.client.prefs.gear) - var/datum/gear/G = gear_datums[gear] - if(G) - var/permitted = FALSE - - if(G.allowed_roles) - if(name in G.allowed_roles) - permitted = TRUE - else - permitted = TRUE - - if(G.whitelisted && (G.whitelisted != H.dna.species.name || !is_alien_whitelisted(H, G.whitelisted))) - permitted = FALSE - - if(!permitted) - to_chat(H, "Your current job or whitelist status does not permit you to spawn with [gear]!") - continue - - if(G.slot) - if(H.equip_to_slot_or_del(G.spawn_item(H), G.slot)) - to_chat(H, "Equipping you with [gear]!") - else - gear_leftovers += G - else - gear_leftovers += G - -/datum/outfit/job/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) - if(visualsOnly) - return - - imprint_idcard(H) - - H.sec_hud_set_ID() - - imprint_pda(H) - - if(gear_leftovers.len) - for(var/datum/gear/G in gear_leftovers) - var/atom/placed_in = H.equip_or_collect(G.spawn_item(null, H.client.prefs.gear[G.display_name])) - if(istype(placed_in)) - if(isturf(placed_in)) - to_chat(H, "Placing [G.display_name] on [placed_in]!") - else - to_chat(H, "Placing [G.display_name] in [placed_in.name].") - continue - if(H.equip_to_appropriate_slot(G)) - to_chat(H, "Placing [G.display_name] in your inventory!") - continue - if(H.put_in_hands(G)) - to_chat(H, "Placing [G.display_name] in your hands!") - continue - to_chat(H, "Failed to locate a storage object on your mob, either you spawned with no hands free and no backpack or this is a bug.") - qdel(G) - - qdel(gear_leftovers) - - return 1 - -/datum/outfit/job/proc/imprint_idcard(mob/living/carbon/human/H) - var/datum/job/J = SSjobs.GetJobType(jobtype) - if(!J) - J = SSjobs.GetJob(H.job) - - var/alt_title - if(H.mind) - alt_title = H.mind.role_alt_title - - var/obj/item/card/id/C = H.wear_id - if(istype(C)) - C.access = J.get_access() - C.registered_name = H.real_name - C.rank = J.title - C.assignment = alt_title ? alt_title : J.title - C.sex = capitalize(H.gender) - C.age = H.age - C.name = "[C.registered_name]'s ID Card ([C.assignment])" - C.photo = get_id_photo(H) - - if(H.mind && H.mind.initial_account) - C.associated_account_number = H.mind.initial_account.account_number - C.owner_uid = H.UID() - C.owner_ckey = H.ckey - -/datum/outfit/job/proc/imprint_pda(mob/living/carbon/human/H) - var/obj/item/pda/PDA = H.wear_pda - var/obj/item/card/id/C = H.wear_id - if(istype(PDA) && istype(C)) - PDA.owner = H.real_name - PDA.ownjob = C.assignment - PDA.ownrank = C.rank - PDA.name = "PDA-[H.real_name] ([PDA.ownjob])" - -/datum/job/proc/would_accept_job_transfer_from_player(mob/player) - if(!guest_jobbans(title)) // actually checks if job is a whitelisted position - return TRUE - if(!istype(player)) - return FALSE - return is_job_whitelisted(player, title) +/datum/job + + //The name of the job + var/title = "NOPE" + + //Job access. The use of minimal_access or access is determined by a config setting: config.jobs_have_minimal_access + var/list/minimal_access = list() //Useful for servers which prefer to only have access given to the places a job absolutely needs (Larger server population) + var/list/access = list() //Useful for servers which either have fewer players, so each person needs to fill more than one role, or servers which like to give more access, so players can't hide forever in their super secure departments (I'm looking at you, chemistry!) + + //Bitflags for the job + var/flag = 0 + var/department_flag = 0 + var/department_head = list() + + //Players will be allowed to spawn in as jobs that are set to "Station" + var/list/faction = list("Station") + + //How many players can be this job + var/total_positions = 0 + + //How many players can spawn in as this job + var/spawn_positions = 0 + + //How many players have this job + var/current_positions = 0 + + //Supervisors, who this person answers to directly + var/supervisors = "" + + //Sellection screen color + var/selection_color = "#ffffff" + + //List of alternate titles, if any + var/list/alt_titles + + //If this is set to 1, a text is printed to the player when jobs are assigned, telling him that he should let admins know that he has to disconnect. + var/req_admin_notify + + //Various Departmental identifiers + var/is_supply + var/is_service + var/is_command + var/is_legal + var/is_engineering + var/is_medical + var/is_science + var/is_security + + //If you have use_age_restriction_for_jobs config option enabled and the database set up, this option will add a requirement for players to be at least minimal_player_age days old. (meaning they first signed in at least that many days before.) + var/minimal_player_age = 0 + + var/exp_requirements = 0 + var/exp_type = "" + + var/disabilities_allowed = 1 + + var/admin_only = 0 + var/spawn_ert = 0 + var/syndicate_command = 0 + + var/outfit = null + + ///////////////////////////////// + // /vg/ feature: Job Objectives! + ///////////////////////////////// + var/required_objectives=list() // Objectives that are ALWAYS added. + var/optional_objectives=list() // Objectives that are SOMETIMES added. + +//Only override this proc +/datum/job/proc/after_spawn(mob/living/carbon/human/H) + +/datum/job/proc/announce(mob/living/carbon/human/H) + +/datum/job/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE) + if(!H) + return 0 + + H.dna.species.before_equip_job(src, H, visualsOnly) + + if(outfit) + H.equipOutfit(outfit, visualsOnly) + + H.dna.species.after_equip_job(src, H, visualsOnly) + + if(!visualsOnly && announce) + announce(H) + +/datum/job/proc/get_access() + if(!config) //Needed for robots. + return src.minimal_access.Copy() + + if(config.jobs_have_minimal_access) + return src.minimal_access.Copy() + else + return src.access.Copy() + +//If the configuration option is set to require players to be logged as old enough to play certain jobs, then this proc checks that they are, otherwise it just returns 1 +/datum/job/proc/player_old_enough(client/C) + if(available_in_days(C) == 0) + return 1 //Available in 0 days = available right now = player is old enough to play. + return 0 + + +/datum/job/proc/available_in_days(client/C) + if(!C) + return 0 + if(!config.use_age_restriction_for_jobs) + return 0 + if(!isnum(C.player_age)) + return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced + if(!isnum(minimal_player_age)) + return 0 + + return max(0, minimal_player_age - C.player_age) + +/datum/job/proc/barred_by_disability(client/C) + if(!C) + return 0 + if(disabilities_allowed) + return 0 + var/list/prohibited_disabilities = list(DISABILITY_FLAG_BLIND, DISABILITY_FLAG_DEAF, DISABILITY_FLAG_MUTE, DISABILITY_FLAG_DIZZY) + for(var/i = 1, i < prohibited_disabilities.len, i++) + var/this_disability = prohibited_disabilities[i] + if(C.prefs.disabilities & this_disability) + return 1 + return 0 + +/datum/job/proc/is_position_available() + return (current_positions < total_positions) || (total_positions == -1) + +/datum/outfit/job + name = "Standard Gear" + collect_not_del = TRUE // we don't want anyone to lose their job shit + + var/allow_loadout = TRUE + var/allow_backbag_choice = TRUE + var/jobtype = null + + uniform = /obj/item/clothing/under/color/grey + id = /obj/item/card/id + l_ear = /obj/item/radio/headset + back = /obj/item/storage/backpack + shoes = /obj/item/clothing/shoes/black + pda = /obj/item/pda + + var/backpack = /obj/item/storage/backpack + var/satchel = /obj/item/storage/backpack/satchel_norm + var/dufflebag = /obj/item/storage/backpack/duffel + box = /obj/item/storage/box/survival + + var/tmp/list/gear_leftovers = list() + +/datum/outfit/job/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE) + if(allow_backbag_choice) + switch(H.backbag) + if(GBACKPACK) + back = /obj/item/storage/backpack //Grey backpack + if(GSATCHEL) + back = /obj/item/storage/backpack/satchel_norm //Grey satchel + if(GDUFFLEBAG) + back = /obj/item/storage/backpack/duffel //Grey Dufflebag + if(LSATCHEL) + back = /obj/item/storage/backpack/satchel //Leather Satchel + if(DSATCHEL) + back = satchel //Department satchel + if(DDUFFLEBAG) + back = dufflebag //Department dufflebag + else + back = backpack //Department backpack + + if(box && H.dna.species.speciesbox) + box = H.dna.species.speciesbox + + if(allow_loadout && H.client && (H.client.prefs.gear && H.client.prefs.gear.len)) + for(var/gear in H.client.prefs.gear) + var/datum/gear/G = gear_datums[gear] + if(G) + var/permitted = FALSE + + if(G.allowed_roles) + if(name in G.allowed_roles) + permitted = TRUE + else + permitted = TRUE + + if(G.whitelisted && (G.whitelisted != H.dna.species.name || !is_alien_whitelisted(H, G.whitelisted))) + permitted = FALSE + + if(!permitted) + to_chat(H, "Your current job or whitelist status does not permit you to spawn with [gear]!") + continue + + if(G.slot) + if(H.equip_to_slot_or_del(G.spawn_item(H), G.slot)) + to_chat(H, "Equipping you with [gear]!") + else + gear_leftovers += G + else + gear_leftovers += G + +/datum/outfit/job/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) + if(visualsOnly) + return + + imprint_idcard(H) + + H.sec_hud_set_ID() + + imprint_pda(H) + + if(gear_leftovers.len) + for(var/datum/gear/G in gear_leftovers) + var/atom/placed_in = H.equip_or_collect(G.spawn_item(null, H.client.prefs.gear[G.display_name])) + if(istype(placed_in)) + if(isturf(placed_in)) + to_chat(H, "Placing [G.display_name] on [placed_in]!") + else + to_chat(H, "Placing [G.display_name] in [placed_in.name].") + continue + if(H.equip_to_appropriate_slot(G)) + to_chat(H, "Placing [G.display_name] in your inventory!") + continue + if(H.put_in_hands(G)) + to_chat(H, "Placing [G.display_name] in your hands!") + continue + to_chat(H, "Failed to locate a storage object on your mob, either you spawned with no hands free and no backpack or this is a bug.") + qdel(G) + + qdel(gear_leftovers) + + return 1 + +/datum/outfit/job/proc/imprint_idcard(mob/living/carbon/human/H) + var/datum/job/J = SSjobs.GetJobType(jobtype) + if(!J) + J = SSjobs.GetJob(H.job) + + var/alt_title + if(H.mind) + alt_title = H.mind.role_alt_title + + var/obj/item/card/id/C = H.wear_id + if(istype(C)) + C.access = J.get_access() + C.registered_name = H.real_name + C.rank = J.title + C.assignment = alt_title ? alt_title : J.title + C.sex = capitalize(H.gender) + C.age = H.age + C.name = "[C.registered_name]'s ID Card ([C.assignment])" + C.photo = get_id_photo(H) + + if(H.mind && H.mind.initial_account) + C.associated_account_number = H.mind.initial_account.account_number + C.owner_uid = H.UID() + C.owner_ckey = H.ckey + +/datum/outfit/job/proc/imprint_pda(mob/living/carbon/human/H) + var/obj/item/pda/PDA = H.wear_pda + var/obj/item/card/id/C = H.wear_id + if(istype(PDA) && istype(C)) + PDA.owner = H.real_name + PDA.ownjob = C.assignment + PDA.ownrank = C.rank + PDA.name = "PDA-[H.real_name] ([PDA.ownjob])" + +/datum/job/proc/would_accept_job_transfer_from_player(mob/player) + if(!guest_jobbans(title)) // actually checks if job is a whitelisted position + return TRUE + if(!istype(player)) + return FALSE + return is_job_whitelisted(player, title) diff --git a/code/game/jobs/job/medical.dm b/code/game/jobs/job/medical.dm index 425cd9252e5..259afa12c9e 100644 --- a/code/game/jobs/job/medical.dm +++ b/code/game/jobs/job/medical.dm @@ -1,318 +1,318 @@ -/datum/job/cmo - title = "Chief Medical Officer" - flag = JOB_CMO - department_flag = JOBCAT_MEDSCI - total_positions = 1 - spawn_positions = 1 - is_medical = 1 - supervisors = "the captain" - department_head = list("Captain") - selection_color = "#ffddf0" - req_admin_notify = 1 - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_HEADS, - ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, - ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_EVA, ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_HEADS, - ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, - ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_MAINT_TUNNELS, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM) - minimal_player_age = 21 - exp_requirements = 300 - exp_type = EXP_TYPE_MEDICAL - outfit = /datum/outfit/job/cmo - -/datum/outfit/job/cmo - name = "Chief Medical Officer" - jobtype = /datum/job/cmo - - uniform = /obj/item/clothing/under/rank/chief_medical_officer - suit = /obj/item/clothing/suit/storage/labcoat/cmo - shoes = /obj/item/clothing/shoes/brown - l_ear = /obj/item/radio/headset/heads/cmo - id = /obj/item/card/id/cmo - suit_store = /obj/item/flashlight/pen - l_hand = /obj/item/storage/firstaid/adv - pda = /obj/item/pda/heads/cmo - backpack_contents = list( - /obj/item/melee/classic_baton/telescopic = 1 - ) - - backpack = /obj/item/storage/backpack/medic - satchel = /obj/item/storage/backpack/satchel_med - dufflebag = /obj/item/storage/backpack/duffel/medical - -/datum/job/doctor - title = "Medical Doctor" - flag = JOB_DOCTOR - department_flag = JOBCAT_MEDSCI - total_positions = 5 - spawn_positions = 3 - is_medical = 1 - supervisors = "the chief medical officer" - department_head = list("Chief Medical Officer") - selection_color = "#ffeef0" - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_MAINT_TUNNELS) - alt_titles = list("Surgeon","Nurse") - minimal_player_age = 3 - exp_requirements = 180 - exp_type = EXP_TYPE_CREW - outfit = /datum/outfit/job/doctor - -/datum/outfit/job/doctor - name = "Medical Doctor" - jobtype = /datum/job/doctor - - uniform = /obj/item/clothing/under/rank/medical - suit = /obj/item/clothing/suit/storage/labcoat - shoes = /obj/item/clothing/shoes/white - l_ear = /obj/item/radio/headset/headset_med - id = /obj/item/card/id/medical - suit_store = /obj/item/flashlight/pen - l_hand = /obj/item/storage/firstaid/adv - pda = /obj/item/pda/medical - - backpack = /obj/item/storage/backpack/medic - satchel = /obj/item/storage/backpack/satchel_med - dufflebag = /obj/item/storage/backpack/duffel/medical - -/datum/job/coroner - title = "Coroner" - flag = JOB_CORONER - department_flag = JOBCAT_MEDSCI - total_positions = 1 - spawn_positions = 1 - is_medical = 1 - supervisors = "the chief medical officer" - department_head = list("Chief Medical Officer") - selection_color = "#ffeef0" - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS) - minimal_player_age = 3 - exp_requirements = 180 - exp_type = EXP_TYPE_CREW - outfit = /datum/outfit/job/coroner - -/datum/outfit/job/coroner - name = "Coroner" - jobtype = /datum/job/coroner - - uniform = /obj/item/clothing/under/rank/medical/mortician - suit = /obj/item/clothing/suit/storage/labcoat/mortician - shoes = /obj/item/clothing/shoes/white - l_ear = /obj/item/radio/headset/headset_med - id = /obj/item/card/id/medical - suit_store = /obj/item/flashlight/pen - l_hand = /obj/item/clipboard - pda = /obj/item/pda/medical - - backpack = /obj/item/storage/backpack/medic - satchel = /obj/item/storage/backpack/satchel_med - dufflebag = /obj/item/storage/backpack/duffel/medical - - backpack_contents = list( - /obj/item/clothing/head/surgery/black = 1, - /obj/item/autopsy_scanner = 1, - /obj/item/reagent_scanner = 1, - /obj/item/storage/box/bodybags = 1) - -/datum/outfit/job/doctor/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE) - . = ..() - if(H.mind && H.mind.role_alt_title) - switch(H.mind.role_alt_title) - if("Surgeon") - uniform = /obj/item/clothing/under/rank/medical/blue - head = /obj/item/clothing/head/surgery/blue - if("Medical Doctor") - uniform = /obj/item/clothing/under/rank/medical - if("Nurse") - if(H.gender == FEMALE) - if(prob(50)) - uniform = /obj/item/clothing/under/rank/nursesuit - else - uniform = /obj/item/clothing/under/rank/nurse - head = /obj/item/clothing/head/nursehat - else - uniform = /obj/item/clothing/under/rank/medical/purple - - - -//Chemist is a medical job damnit //YEAH FUCK YOU SCIENCE -Pete //Guys, behave -Erro -/datum/job/chemist - title = "Chemist" - flag = JOB_CHEMIST - department_flag = JOBCAT_MEDSCI - total_positions = 2 - spawn_positions = 2 - is_medical = 1 - supervisors = "the chief medical officer" - department_head = list("Chief Medical Officer") - selection_color = "#ffeef0" - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_MEDICAL, ACCESS_CHEMISTRY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) - alt_titles = list("Pharmacist","Pharmacologist") - minimal_player_age = 7 - exp_requirements = 300 - exp_type = EXP_TYPE_CREW - outfit = /datum/outfit/job/chemist - -/datum/outfit/job/chemist - name = "Chemist" - jobtype = /datum/job/chemist - - uniform = /obj/item/clothing/under/rank/chemist - suit = /obj/item/clothing/suit/storage/labcoat/chemist - shoes = /obj/item/clothing/shoes/white - l_ear = /obj/item/radio/headset/headset_med - glasses = /obj/item/clothing/glasses/science - id = /obj/item/card/id/medical - pda = /obj/item/pda/chemist - - backpack = /obj/item/storage/backpack/chemistry - satchel = /obj/item/storage/backpack/satchel_chem - dufflebag = /obj/item/storage/backpack/duffel/chemistry - -/datum/job/geneticist - title = "Geneticist" - flag = JOB_GENETICIST - department_flag = JOBCAT_MEDSCI - total_positions = 2 - spawn_positions = 2 - is_medical = 1 - supervisors = "the chief medical officer and the research director" - department_head = list("Chief Medical Officer", "Research Director") - selection_color = "#ffeef0" - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM) - minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_RESEARCH, ACCESS_MAINT_TUNNELS) - minimal_player_age = 3 - exp_requirements = 180 - exp_type = EXP_TYPE_CREW - outfit = /datum/outfit/job/geneticist - -/datum/outfit/job/geneticist - name = "Geneticist" - jobtype = /datum/job/geneticist - - uniform = /obj/item/clothing/under/rank/geneticist - suit = /obj/item/clothing/suit/storage/labcoat/genetics - shoes = /obj/item/clothing/shoes/white - l_ear = /obj/item/radio/headset/headset_medsci - id = /obj/item/card/id/medical - suit_store = /obj/item/flashlight/pen - pda = /obj/item/pda/geneticist - - backpack = /obj/item/storage/backpack/genetics - satchel = /obj/item/storage/backpack/satchel_gen - dufflebag = /obj/item/storage/backpack/duffel/genetics - - -/datum/job/virologist - title = "Virologist" - flag = JOB_VIROLOGIST - department_flag = JOBCAT_MEDSCI - total_positions = 1 - spawn_positions = 1 - is_medical = 1 - supervisors = "the chief medical officer" - department_head = list("Chief Medical Officer") - selection_color = "#ffeef0" - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS) - minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) - alt_titles = list("Pathologist","Microbiologist") - minimal_player_age = 7 - exp_requirements = 300 - exp_type = EXP_TYPE_CREW - outfit = /datum/outfit/job/virologist - -/datum/outfit/job/virologist - name = "Virologist" - jobtype = /datum/job/virologist - - uniform = /obj/item/clothing/under/rank/virologist - suit = /obj/item/clothing/suit/storage/labcoat/virologist - shoes = /obj/item/clothing/shoes/white - mask = /obj/item/clothing/mask/surgical - l_ear = /obj/item/radio/headset/headset_med - id = /obj/item/card/id/medical - suit_store = /obj/item/flashlight/pen - pda = /obj/item/pda/viro - - backpack = /obj/item/storage/backpack/virology - satchel = /obj/item/storage/backpack/satchel_vir - dufflebag = /obj/item/storage/backpack/duffel/virology - -/datum/job/psychiatrist - title = "Psychiatrist" - flag = JOB_PSYCHIATRIST - department_flag = JOBCAT_MEDSCI - total_positions = 1 - spawn_positions = 1 - is_medical = 1 - supervisors = "the chief medical officer" - department_head = list("Chief Medical Officer") - selection_color = "#ffeef0" - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_PSYCHIATRIST) - minimal_access = list(ACCESS_MEDICAL, ACCESS_PSYCHIATRIST, ACCESS_MAINT_TUNNELS) - alt_titles = list("Psychologist","Therapist") - outfit = /datum/outfit/job/psychiatrist - -/datum/outfit/job/psychiatrist - name = "Psychiatrist" - jobtype = /datum/job/psychiatrist - - uniform = /obj/item/clothing/under/rank/medical - suit = /obj/item/clothing/suit/storage/labcoat - shoes = /obj/item/clothing/shoes/laceup - l_ear = /obj/item/radio/headset/headset_med - id = /obj/item/card/id/medical - suit_store = /obj/item/flashlight/pen - pda = /obj/item/pda/medical - -/datum/outfit/job/psychiatrist/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE) - . = ..() - if(H.mind && H.mind.role_alt_title) - switch(H.mind.role_alt_title) - if("Psychiatrist") - uniform = /obj/item/clothing/under/rank/psych - if("Psychologist") - uniform = /obj/item/clothing/under/rank/psych/turtleneck - if("Therapist") - uniform = /obj/item/clothing/under/rank/medical - -/datum/job/paramedic - title = "Paramedic" - flag = JOB_PARAMEDIC - department_flag = JOBCAT_MEDSCI - total_positions = 1 - spawn_positions = 1 - is_medical = 1 - supervisors = "the chief medical officer" - department_head = list("Chief Medical Officer") - selection_color = "#ffeef0" - access = list(ACCESS_PARAMEDIC, ACCESS_MEDICAL, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MORGUE) - minimal_access=list(ACCESS_PARAMEDIC, ACCESS_MEDICAL, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MORGUE) - minimal_player_age = 3 - exp_requirements = 180 - exp_type = EXP_TYPE_CREW - outfit = /datum/outfit/job/paramedic - -/datum/outfit/job/paramedic - name = "Paramedic" - jobtype = /datum/job/paramedic - - uniform = /obj/item/clothing/under/rank/medical/paramedic - shoes = /obj/item/clothing/shoes/black - head = /obj/item/clothing/head/soft/blue - mask = /obj/item/clothing/mask/cigarette - l_ear = /obj/item/radio/headset/headset_med - id = /obj/item/card/id/medical - l_pocket = /obj/item/flashlight/pen - pda = /obj/item/pda/medical - backpack_contents = list( - /obj/item/healthanalyzer = 1 - ) - - backpack = /obj/item/storage/backpack/medic - satchel = /obj/item/storage/backpack/satchel_med - dufflebag = /obj/item/storage/backpack/duffel/medical - box = /obj/item/storage/box/engineer - +/datum/job/cmo + title = "Chief Medical Officer" + flag = JOB_CMO + department_flag = JOBCAT_MEDSCI + total_positions = 1 + spawn_positions = 1 + is_medical = 1 + supervisors = "the captain" + department_head = list("Captain") + selection_color = "#ffddf0" + req_admin_notify = 1 + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_HEADS, + ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_EVA, ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_HEADS, + ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_MAINT_TUNNELS, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM) + minimal_player_age = 21 + exp_requirements = 300 + exp_type = EXP_TYPE_MEDICAL + outfit = /datum/outfit/job/cmo + +/datum/outfit/job/cmo + name = "Chief Medical Officer" + jobtype = /datum/job/cmo + + uniform = /obj/item/clothing/under/rank/chief_medical_officer + suit = /obj/item/clothing/suit/storage/labcoat/cmo + shoes = /obj/item/clothing/shoes/brown + l_ear = /obj/item/radio/headset/heads/cmo + id = /obj/item/card/id/cmo + suit_store = /obj/item/flashlight/pen + l_hand = /obj/item/storage/firstaid/adv + pda = /obj/item/pda/heads/cmo + backpack_contents = list( + /obj/item/melee/classic_baton/telescopic = 1 + ) + + backpack = /obj/item/storage/backpack/medic + satchel = /obj/item/storage/backpack/satchel_med + dufflebag = /obj/item/storage/backpack/duffel/medical + +/datum/job/doctor + title = "Medical Doctor" + flag = JOB_DOCTOR + department_flag = JOBCAT_MEDSCI + total_positions = 5 + spawn_positions = 3 + is_medical = 1 + supervisors = "the chief medical officer" + department_head = list("Chief Medical Officer") + selection_color = "#ffeef0" + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_MAINT_TUNNELS) + alt_titles = list("Surgeon","Nurse") + minimal_player_age = 3 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + outfit = /datum/outfit/job/doctor + +/datum/outfit/job/doctor + name = "Medical Doctor" + jobtype = /datum/job/doctor + + uniform = /obj/item/clothing/under/rank/medical + suit = /obj/item/clothing/suit/storage/labcoat + shoes = /obj/item/clothing/shoes/white + l_ear = /obj/item/radio/headset/headset_med + id = /obj/item/card/id/medical + suit_store = /obj/item/flashlight/pen + l_hand = /obj/item/storage/firstaid/adv + pda = /obj/item/pda/medical + + backpack = /obj/item/storage/backpack/medic + satchel = /obj/item/storage/backpack/satchel_med + dufflebag = /obj/item/storage/backpack/duffel/medical + +/datum/job/coroner + title = "Coroner" + flag = JOB_CORONER + department_flag = JOBCAT_MEDSCI + total_positions = 1 + spawn_positions = 1 + is_medical = 1 + supervisors = "the chief medical officer" + department_head = list("Chief Medical Officer") + selection_color = "#ffeef0" + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS) + minimal_player_age = 3 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + outfit = /datum/outfit/job/coroner + +/datum/outfit/job/coroner + name = "Coroner" + jobtype = /datum/job/coroner + + uniform = /obj/item/clothing/under/rank/medical/mortician + suit = /obj/item/clothing/suit/storage/labcoat/mortician + shoes = /obj/item/clothing/shoes/white + l_ear = /obj/item/radio/headset/headset_med + id = /obj/item/card/id/medical + suit_store = /obj/item/flashlight/pen + l_hand = /obj/item/clipboard + pda = /obj/item/pda/medical + + backpack = /obj/item/storage/backpack/medic + satchel = /obj/item/storage/backpack/satchel_med + dufflebag = /obj/item/storage/backpack/duffel/medical + + backpack_contents = list( + /obj/item/clothing/head/surgery/black = 1, + /obj/item/autopsy_scanner = 1, + /obj/item/reagent_scanner = 1, + /obj/item/storage/box/bodybags = 1) + +/datum/outfit/job/doctor/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE) + . = ..() + if(H.mind && H.mind.role_alt_title) + switch(H.mind.role_alt_title) + if("Surgeon") + uniform = /obj/item/clothing/under/rank/medical/blue + head = /obj/item/clothing/head/surgery/blue + if("Medical Doctor") + uniform = /obj/item/clothing/under/rank/medical + if("Nurse") + if(H.gender == FEMALE) + if(prob(50)) + uniform = /obj/item/clothing/under/rank/nursesuit + else + uniform = /obj/item/clothing/under/rank/nurse + head = /obj/item/clothing/head/nursehat + else + uniform = /obj/item/clothing/under/rank/medical/purple + + + +//Chemist is a medical job damnit //YEAH FUCK YOU SCIENCE -Pete //Guys, behave -Erro +/datum/job/chemist + title = "Chemist" + flag = JOB_CHEMIST + department_flag = JOBCAT_MEDSCI + total_positions = 2 + spawn_positions = 2 + is_medical = 1 + supervisors = "the chief medical officer" + department_head = list("Chief Medical Officer") + selection_color = "#ffeef0" + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MEDICAL, ACCESS_CHEMISTRY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) + alt_titles = list("Pharmacist","Pharmacologist") + minimal_player_age = 7 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW + outfit = /datum/outfit/job/chemist + +/datum/outfit/job/chemist + name = "Chemist" + jobtype = /datum/job/chemist + + uniform = /obj/item/clothing/under/rank/chemist + suit = /obj/item/clothing/suit/storage/labcoat/chemist + shoes = /obj/item/clothing/shoes/white + l_ear = /obj/item/radio/headset/headset_med + glasses = /obj/item/clothing/glasses/science + id = /obj/item/card/id/medical + pda = /obj/item/pda/chemist + + backpack = /obj/item/storage/backpack/chemistry + satchel = /obj/item/storage/backpack/satchel_chem + dufflebag = /obj/item/storage/backpack/duffel/chemistry + +/datum/job/geneticist + title = "Geneticist" + flag = JOB_GENETICIST + department_flag = JOBCAT_MEDSCI + total_positions = 2 + spawn_positions = 2 + is_medical = 1 + supervisors = "the chief medical officer and the research director" + department_head = list("Chief Medical Officer", "Research Director") + selection_color = "#ffeef0" + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM) + minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_RESEARCH, ACCESS_MAINT_TUNNELS) + minimal_player_age = 3 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + outfit = /datum/outfit/job/geneticist + +/datum/outfit/job/geneticist + name = "Geneticist" + jobtype = /datum/job/geneticist + + uniform = /obj/item/clothing/under/rank/geneticist + suit = /obj/item/clothing/suit/storage/labcoat/genetics + shoes = /obj/item/clothing/shoes/white + l_ear = /obj/item/radio/headset/headset_medsci + id = /obj/item/card/id/medical + suit_store = /obj/item/flashlight/pen + pda = /obj/item/pda/geneticist + + backpack = /obj/item/storage/backpack/genetics + satchel = /obj/item/storage/backpack/satchel_gen + dufflebag = /obj/item/storage/backpack/duffel/genetics + + +/datum/job/virologist + title = "Virologist" + flag = JOB_VIROLOGIST + department_flag = JOBCAT_MEDSCI + total_positions = 1 + spawn_positions = 1 + is_medical = 1 + supervisors = "the chief medical officer" + department_head = list("Chief Medical Officer") + selection_color = "#ffeef0" + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS) + minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM) + alt_titles = list("Pathologist","Microbiologist") + minimal_player_age = 7 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW + outfit = /datum/outfit/job/virologist + +/datum/outfit/job/virologist + name = "Virologist" + jobtype = /datum/job/virologist + + uniform = /obj/item/clothing/under/rank/virologist + suit = /obj/item/clothing/suit/storage/labcoat/virologist + shoes = /obj/item/clothing/shoes/white + mask = /obj/item/clothing/mask/surgical + l_ear = /obj/item/radio/headset/headset_med + id = /obj/item/card/id/medical + suit_store = /obj/item/flashlight/pen + pda = /obj/item/pda/viro + + backpack = /obj/item/storage/backpack/virology + satchel = /obj/item/storage/backpack/satchel_vir + dufflebag = /obj/item/storage/backpack/duffel/virology + +/datum/job/psychiatrist + title = "Psychiatrist" + flag = JOB_PSYCHIATRIST + department_flag = JOBCAT_MEDSCI + total_positions = 1 + spawn_positions = 1 + is_medical = 1 + supervisors = "the chief medical officer" + department_head = list("Chief Medical Officer") + selection_color = "#ffeef0" + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_PSYCHIATRIST) + minimal_access = list(ACCESS_MEDICAL, ACCESS_PSYCHIATRIST, ACCESS_MAINT_TUNNELS) + alt_titles = list("Psychologist","Therapist") + outfit = /datum/outfit/job/psychiatrist + +/datum/outfit/job/psychiatrist + name = "Psychiatrist" + jobtype = /datum/job/psychiatrist + + uniform = /obj/item/clothing/under/rank/medical + suit = /obj/item/clothing/suit/storage/labcoat + shoes = /obj/item/clothing/shoes/laceup + l_ear = /obj/item/radio/headset/headset_med + id = /obj/item/card/id/medical + suit_store = /obj/item/flashlight/pen + pda = /obj/item/pda/medical + +/datum/outfit/job/psychiatrist/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE) + . = ..() + if(H.mind && H.mind.role_alt_title) + switch(H.mind.role_alt_title) + if("Psychiatrist") + uniform = /obj/item/clothing/under/rank/psych + if("Psychologist") + uniform = /obj/item/clothing/under/rank/psych/turtleneck + if("Therapist") + uniform = /obj/item/clothing/under/rank/medical + +/datum/job/paramedic + title = "Paramedic" + flag = JOB_PARAMEDIC + department_flag = JOBCAT_MEDSCI + total_positions = 1 + spawn_positions = 1 + is_medical = 1 + supervisors = "the chief medical officer" + department_head = list("Chief Medical Officer") + selection_color = "#ffeef0" + access = list(ACCESS_PARAMEDIC, ACCESS_MEDICAL, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MORGUE) + minimal_access=list(ACCESS_PARAMEDIC, ACCESS_MEDICAL, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_MORGUE) + minimal_player_age = 3 + exp_requirements = 180 + exp_type = EXP_TYPE_CREW + outfit = /datum/outfit/job/paramedic + +/datum/outfit/job/paramedic + name = "Paramedic" + jobtype = /datum/job/paramedic + + uniform = /obj/item/clothing/under/rank/medical/paramedic + shoes = /obj/item/clothing/shoes/black + head = /obj/item/clothing/head/soft/blue + mask = /obj/item/clothing/mask/cigarette + l_ear = /obj/item/radio/headset/headset_med + id = /obj/item/card/id/medical + l_pocket = /obj/item/flashlight/pen + pda = /obj/item/pda/medical + backpack_contents = list( + /obj/item/healthanalyzer = 1 + ) + + backpack = /obj/item/storage/backpack/medic + satchel = /obj/item/storage/backpack/satchel_med + dufflebag = /obj/item/storage/backpack/duffel/medical + box = /obj/item/storage/box/engineer + diff --git a/code/game/jobs/job/security.dm b/code/game/jobs/job/security.dm index d314f016a9a..20364b57677 100644 --- a/code/game/jobs/job/security.dm +++ b/code/game/jobs/job/security.dm @@ -259,4 +259,4 @@ backpack = /obj/item/storage/backpack/security satchel = /obj/item/storage/backpack/satchel_sec dufflebag = /obj/item/storage/backpack/duffel/security - box = /obj/item/storage/box/engineer \ No newline at end of file + box = /obj/item/storage/box/engineer diff --git a/code/game/jobs/job/silicon.dm b/code/game/jobs/job/silicon.dm index 289b441b4d7..5325f077f4f 100644 --- a/code/game/jobs/job/silicon.dm +++ b/code/game/jobs/job/silicon.dm @@ -1,40 +1,40 @@ -/datum/job/ai - title = "AI" - flag = JOB_AI - department_flag = JOBCAT_ENGSEC - total_positions = 0 // Not used for AI, see is_position_available below and modules/mob/living/silicon/ai/latejoin.dm - spawn_positions = 1 - selection_color = "#ccffcc" - supervisors = "your laws" - department_head = list("Captain") - req_admin_notify = 1 - minimal_player_age = 30 - exp_requirements = 300 - exp_type = EXP_TYPE_SILICON - -/datum/job/ai/equip(mob/living/carbon/human/H) - if(!H) - return 0 - -/datum/job/ai/is_position_available() - return (empty_playable_ai_cores.len != 0) - - -/datum/job/cyborg - title = "Cyborg" - flag = JOB_CYBORG - department_flag = JOBCAT_ENGSEC - total_positions = 1 - spawn_positions = 1 - supervisors = "your laws and the AI" //Nodrak - department_head = list("AI") - selection_color = "#ddffdd" - minimal_player_age = 21 - exp_requirements = 300 - exp_type = EXP_TYPE_CREW - alt_titles = list("Android", "Robot") - -/datum/job/cyborg/equip(mob/living/carbon/human/H) - if(!H) - return 0 - return H.Robotize() \ No newline at end of file +/datum/job/ai + title = "AI" + flag = JOB_AI + department_flag = JOBCAT_ENGSEC + total_positions = 0 // Not used for AI, see is_position_available below and modules/mob/living/silicon/ai/latejoin.dm + spawn_positions = 1 + selection_color = "#ccffcc" + supervisors = "your laws" + department_head = list("Captain") + req_admin_notify = 1 + minimal_player_age = 30 + exp_requirements = 300 + exp_type = EXP_TYPE_SILICON + +/datum/job/ai/equip(mob/living/carbon/human/H) + if(!H) + return 0 + +/datum/job/ai/is_position_available() + return (empty_playable_ai_cores.len != 0) + + +/datum/job/cyborg + title = "Cyborg" + flag = JOB_CYBORG + department_flag = JOBCAT_ENGSEC + total_positions = 1 + spawn_positions = 1 + supervisors = "your laws and the AI" //Nodrak + department_head = list("AI") + selection_color = "#ddffdd" + minimal_player_age = 21 + exp_requirements = 300 + exp_type = EXP_TYPE_CREW + alt_titles = list("Android", "Robot") + +/datum/job/cyborg/equip(mob/living/carbon/human/H) + if(!H) + return 0 + return H.Robotize() diff --git a/code/game/jobs/job/syndicate.dm b/code/game/jobs/job/syndicate.dm index 44a04ef7593..f7c5659f86c 100644 --- a/code/game/jobs/job/syndicate.dm +++ b/code/game/jobs/job/syndicate.dm @@ -55,4 +55,4 @@ opshud.join_hud(H.mind.current) H.mind.offstation_role = TRUE set_antag_hud(H.mind.current, "hudoperative") - H.regenerate_icons() \ No newline at end of file + H.regenerate_icons() diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm index b69dbfd3ed4..7823b4b6f4a 100644 --- a/code/game/jobs/job_exp.dm +++ b/code/game/jobs/job_exp.dm @@ -301,4 +301,4 @@ var/global/list/role_playtime_requirements = list( var/err = update_query_history.ErrorMsg() log_game("SQL ERROR during exp_update_client write 2. Error : \[[err]\]\n") message_admins("SQL ERROR during exp_update_client write 2. Error : \[[err]\]\n") - return \ No newline at end of file + return diff --git a/code/game/jobs/job_objective.dm b/code/game/jobs/job_objective.dm index 48fa911bcf5..11fc2b7181d 100644 --- a/code/game/jobs/job_objective.dm +++ b/code/game/jobs/job_objective.dm @@ -73,4 +73,4 @@ else feedback_add_details("employee_success","FAIL") - return text \ No newline at end of file + return text diff --git a/code/game/jobs/job_objectives/science.dm b/code/game/jobs/job_objectives/science.dm index 0d5b4aebd83..70ff38a6a32 100644 --- a/code/game/jobs/job_objectives/science.dm +++ b/code/game/jobs/job_objectives/science.dm @@ -42,4 +42,4 @@ /datum/job_objective/make_ripley/get_description() var/desc = "Make a Ripley or Firefighter." desc += "([units_completed] created.)" - return desc \ No newline at end of file + return desc diff --git a/code/game/jobs/job_scaling.dm b/code/game/jobs/job_scaling.dm index 90d55b03444..c062fe6c216 100644 --- a/code/game/jobs/job_scaling.dm +++ b/code/game/jobs/job_scaling.dm @@ -8,4 +8,4 @@ SSjobs.LoadJobs("config/jobs_highpop.txt") else log_debug("Playercount: [playercount] versus trigger: [highpop_trigger] - keeping standard job config"); - return 1 \ No newline at end of file + return 1 diff --git a/code/game/jobs/jobs.dm b/code/game/jobs/jobs.dm index 09bbf2cf633..b0d70fb3003 100644 --- a/code/game/jobs/jobs.dm +++ b/code/game/jobs/jobs.dm @@ -1,148 +1,148 @@ - -var/list/assistant_occupations = list( -) - - -var/list/command_positions = list( - "Captain", - "Head of Personnel", - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer", - "Nanotrasen Representative" -) - - -var/list/engineering_positions = list( - "Chief Engineer", - "Station Engineer", - "Life Support Specialist", - "Mechanic" -) - - -var/list/medical_positions = list( - "Chief Medical Officer", - "Medical Doctor", - "Geneticist", - "Psychiatrist", - "Chemist", - "Virologist", - "Paramedic", - "Coroner" -) - - -var/list/science_positions = list( - "Research Director", - "Scientist", - "Geneticist", //Part of both medical and science - "Roboticist", -) - -//BS12 EDIT -var/list/support_positions = list( - "Head of Personnel", - "Bartender", - "Botanist", - "Chef", - "Janitor", - "Librarian", - "Quartermaster", - "Cargo Technician", - "Shaft Miner", - "Internal Affairs Agent", - "Chaplain", - "Clown", - "Mime", - "Barber", - "Magistrate", - "Nanotrasen Representative", - "Blueshield" -) - -var/list/supply_positions = list( - "Head of Personnel", - "Quartermaster", - "Cargo Technician", - "Shaft Miner" -) - -var/list/service_positions = support_positions - supply_positions + list("Head of Personnel") - - -var/list/security_positions = list( - "Head of Security", - "Warden", - "Detective", - "Security Officer", - "Brig Physician", - "Security Pod Pilot", - "Magistrate" -) - - -var/list/civilian_positions = list( - "Civilian" -) - -var/list/nonhuman_positions = list( - "AI", - "Cyborg", - "Drone", - "pAI" -) - -var/list/whitelisted_positions = list( - "Blueshield", - "Nanotrasen Representative", - "Barber", - "Mechanic", - "Brig Physician", - "Magistrate", - "Security Pod Pilot", -) - - -/proc/guest_jobbans(var/job) - return (job in whitelisted_positions) - -/proc/get_job_datums() - var/list/occupations = list() - var/list/all_jobs = typesof(/datum/job) - - for(var/A in all_jobs) - var/datum/job/job = new A() - if(!job) continue - occupations += job - - return occupations - -/proc/get_alternate_titles(var/job) - var/list/jobs = get_job_datums() - var/list/titles = list() - - for(var/datum/job/J in jobs) - if(!J) continue - if(J.title == job) - titles = J.alt_titles - - return titles - -var/global/list/exp_jobsmap = list( - EXP_TYPE_LIVING = list(), // all living mobs - EXP_TYPE_CREW = list(titles = command_positions | engineering_positions | medical_positions | science_positions | support_positions | supply_positions | security_positions | civilian_positions | list("AI","Cyborg") | whitelisted_positions), // crew positions - EXP_TYPE_SPECIAL = list(), // antags, ERT, etc - EXP_TYPE_GHOST = list(), // dead people, observers - EXP_TYPE_EXEMPT = list(), // special grandfather setting - EXP_TYPE_COMMAND = list(titles = command_positions), - EXP_TYPE_ENGINEERING = list(titles = engineering_positions), - EXP_TYPE_MEDICAL = list(titles = medical_positions), - EXP_TYPE_SCIENCE = list(titles = science_positions), - EXP_TYPE_SUPPLY = list(titles = supply_positions), - EXP_TYPE_SECURITY = list(titles = security_positions), - EXP_TYPE_SILICON = list(titles = list("AI","Cyborg")), - EXP_TYPE_SERVICE = list(titles = service_positions), - EXP_TYPE_WHITELIST = list(titles = whitelisted_positions) // karma-locked jobs -) + +var/list/assistant_occupations = list( +) + + +var/list/command_positions = list( + "Captain", + "Head of Personnel", + "Head of Security", + "Chief Engineer", + "Research Director", + "Chief Medical Officer", + "Nanotrasen Representative" +) + + +var/list/engineering_positions = list( + "Chief Engineer", + "Station Engineer", + "Life Support Specialist", + "Mechanic" +) + + +var/list/medical_positions = list( + "Chief Medical Officer", + "Medical Doctor", + "Geneticist", + "Psychiatrist", + "Chemist", + "Virologist", + "Paramedic", + "Coroner" +) + + +var/list/science_positions = list( + "Research Director", + "Scientist", + "Geneticist", //Part of both medical and science + "Roboticist", +) + +//BS12 EDIT +var/list/support_positions = list( + "Head of Personnel", + "Bartender", + "Botanist", + "Chef", + "Janitor", + "Librarian", + "Quartermaster", + "Cargo Technician", + "Shaft Miner", + "Internal Affairs Agent", + "Chaplain", + "Clown", + "Mime", + "Barber", + "Magistrate", + "Nanotrasen Representative", + "Blueshield" +) + +var/list/supply_positions = list( + "Head of Personnel", + "Quartermaster", + "Cargo Technician", + "Shaft Miner" +) + +var/list/service_positions = support_positions - supply_positions + list("Head of Personnel") + + +var/list/security_positions = list( + "Head of Security", + "Warden", + "Detective", + "Security Officer", + "Brig Physician", + "Security Pod Pilot", + "Magistrate" +) + + +var/list/civilian_positions = list( + "Civilian" +) + +var/list/nonhuman_positions = list( + "AI", + "Cyborg", + "Drone", + "pAI" +) + +var/list/whitelisted_positions = list( + "Blueshield", + "Nanotrasen Representative", + "Barber", + "Mechanic", + "Brig Physician", + "Magistrate", + "Security Pod Pilot", +) + + +/proc/guest_jobbans(var/job) + return (job in whitelisted_positions) + +/proc/get_job_datums() + var/list/occupations = list() + var/list/all_jobs = typesof(/datum/job) + + for(var/A in all_jobs) + var/datum/job/job = new A() + if(!job) continue + occupations += job + + return occupations + +/proc/get_alternate_titles(var/job) + var/list/jobs = get_job_datums() + var/list/titles = list() + + for(var/datum/job/J in jobs) + if(!J) continue + if(J.title == job) + titles = J.alt_titles + + return titles + +var/global/list/exp_jobsmap = list( + EXP_TYPE_LIVING = list(), // all living mobs + EXP_TYPE_CREW = list(titles = command_positions | engineering_positions | medical_positions | science_positions | support_positions | supply_positions | security_positions | civilian_positions | list("AI","Cyborg") | whitelisted_positions), // crew positions + EXP_TYPE_SPECIAL = list(), // antags, ERT, etc + EXP_TYPE_GHOST = list(), // dead people, observers + EXP_TYPE_EXEMPT = list(), // special grandfather setting + EXP_TYPE_COMMAND = list(titles = command_positions), + EXP_TYPE_ENGINEERING = list(titles = engineering_positions), + EXP_TYPE_MEDICAL = list(titles = medical_positions), + EXP_TYPE_SCIENCE = list(titles = science_positions), + EXP_TYPE_SUPPLY = list(titles = supply_positions), + EXP_TYPE_SECURITY = list(titles = security_positions), + EXP_TYPE_SILICON = list(titles = list("AI","Cyborg")), + EXP_TYPE_SERVICE = list(titles = service_positions), + EXP_TYPE_WHITELIST = list(titles = whitelisted_positions) // karma-locked jobs +) diff --git a/code/game/jobs/whitelist.dm b/code/game/jobs/whitelist.dm index a09eaed6c02..503dd33258e 100644 --- a/code/game/jobs/whitelist.dm +++ b/code/game/jobs/whitelist.dm @@ -1,99 +1,99 @@ -#define WHITELISTFILE "data/whitelist.txt" - -var/list/whitelist = list() - -/hook/startup/proc/loadWhitelist() - if(config.usewhitelist) - load_whitelist() - return 1 - -/proc/load_whitelist() - whitelist = file2list(WHITELISTFILE) - if(!whitelist.len) whitelist = null -/* -/proc/check_whitelist(mob/M, var/rank) - if(!whitelist) - return 0 - return ("[M.ckey]" in whitelist) -*/ - -/proc/is_job_whitelisted(mob/M, var/rank) - if(guest_jobbans(rank)) - if(!config.usewhitelist) - return 1 - if(config.disable_karma) - return 1 - if(check_rights(R_ADMIN, 0, M)) - return 1 - if(!dbcon.IsConnected()) - to_chat(usr, "Unable to connect to whitelist database. Please try again later.
        ") - return 0 - else - var/DBQuery/query = dbcon.NewQuery("SELECT job FROM [format_table_name("whitelist")] WHERE ckey='[M.ckey]'") - query.Execute() - - - while(query.NextRow()) - var/joblist = query.item[1] - if(joblist!="*") - var/allowed_jobs = splittext(joblist,",") - if(rank in allowed_jobs) return 1 - else return 1 - return 0 - else - return 1 - - - - -/var/list/alien_whitelist = list() - -/hook/startup/proc/loadAlienWhitelist() - if(config.usealienwhitelist) - load_alienwhitelist() - return 1 - -/proc/load_alienwhitelist() - var/text = file2text("config/alienwhitelist.txt") - if(!text) - log_config("Failed to load config/alienwhitelist.txt\n") - else - alien_whitelist = splittext(text, "\n") - -//todo: admin aliens -/proc/is_alien_whitelisted(mob/M, var/species) - if(!config.usealienwhitelist) - return 1 - if(config.disable_karma) - return 1 - if(species == "human" || species == "Human") - return 1 - if(check_rights(R_ADMIN, 0)) - return 1 - if(!alien_whitelist) - return 0 - if(!dbcon.IsConnected()) - to_chat(usr, "Unable to connect to whitelist database. Please try again later.
        ") - return 0 - else - var/DBQuery/query = dbcon.NewQuery("SELECT species FROM [format_table_name("whitelist")] WHERE ckey='[M.ckey]'") - query.Execute() - - while(query.NextRow()) - var/specieslist = query.item[1] - if(specieslist!="*") - var/allowed_species = splittext(specieslist,",") - if(species in allowed_species) return 1 - else return 1 - return 0 -/* - if(M && species) - for(var/s in alien_whitelist) - if(findtext(s,"[M.ckey] - [species]")) - return 1 - if(findtext(s,"[M.ckey] - All")) - return 1 -*/ - - -#undef WHITELISTFILE \ No newline at end of file +#define WHITELISTFILE "data/whitelist.txt" + +var/list/whitelist = list() + +/hook/startup/proc/loadWhitelist() + if(config.usewhitelist) + load_whitelist() + return 1 + +/proc/load_whitelist() + whitelist = file2list(WHITELISTFILE) + if(!whitelist.len) whitelist = null +/* +/proc/check_whitelist(mob/M, var/rank) + if(!whitelist) + return 0 + return ("[M.ckey]" in whitelist) +*/ + +/proc/is_job_whitelisted(mob/M, var/rank) + if(guest_jobbans(rank)) + if(!config.usewhitelist) + return 1 + if(config.disable_karma) + return 1 + if(check_rights(R_ADMIN, 0, M)) + return 1 + if(!dbcon.IsConnected()) + to_chat(usr, "Unable to connect to whitelist database. Please try again later.
        ") + return 0 + else + var/DBQuery/query = dbcon.NewQuery("SELECT job FROM [format_table_name("whitelist")] WHERE ckey='[M.ckey]'") + query.Execute() + + + while(query.NextRow()) + var/joblist = query.item[1] + if(joblist!="*") + var/allowed_jobs = splittext(joblist,",") + if(rank in allowed_jobs) return 1 + else return 1 + return 0 + else + return 1 + + + + +/var/list/alien_whitelist = list() + +/hook/startup/proc/loadAlienWhitelist() + if(config.usealienwhitelist) + load_alienwhitelist() + return 1 + +/proc/load_alienwhitelist() + var/text = file2text("config/alienwhitelist.txt") + if(!text) + log_config("Failed to load config/alienwhitelist.txt\n") + else + alien_whitelist = splittext(text, "\n") + +//todo: admin aliens +/proc/is_alien_whitelisted(mob/M, var/species) + if(!config.usealienwhitelist) + return 1 + if(config.disable_karma) + return 1 + if(species == "human" || species == "Human") + return 1 + if(check_rights(R_ADMIN, 0)) + return 1 + if(!alien_whitelist) + return 0 + if(!dbcon.IsConnected()) + to_chat(usr, "Unable to connect to whitelist database. Please try again later.
        ") + return 0 + else + var/DBQuery/query = dbcon.NewQuery("SELECT species FROM [format_table_name("whitelist")] WHERE ckey='[M.ckey]'") + query.Execute() + + while(query.NextRow()) + var/specieslist = query.item[1] + if(specieslist!="*") + var/allowed_species = splittext(specieslist,",") + if(species in allowed_species) return 1 + else return 1 + return 0 +/* + if(M && species) + for(var/s in alien_whitelist) + if(findtext(s,"[M.ckey] - [species]")) + return 1 + if(findtext(s,"[M.ckey] - All")) + return 1 +*/ + + +#undef WHITELISTFILE diff --git a/code/game/machinery/Beacon.dm b/code/game/machinery/Beacon.dm index b5b2413fb96..d3b2114f801 100644 --- a/code/game/machinery/Beacon.dm +++ b/code/game/machinery/Beacon.dm @@ -86,4 +86,4 @@ /obj/machinery/bluespace_beacon/syndicate/infiltrator/New() ..() - enabled = FALSE \ No newline at end of file + enabled = FALSE diff --git a/code/game/machinery/Freezer.dm b/code/game/machinery/Freezer.dm index b6c6121cfd6..097d2e26da2 100644 --- a/code/game/machinery/Freezer.dm +++ b/code/game/machinery/Freezer.dm @@ -1,332 +1,332 @@ -/obj/machinery/atmospherics/unary/cold_sink/freezer - name = "freezer" - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "freezer" - density = 1 - var/min_temperature = 0 - anchored = 1.0 - use_power = IDLE_POWER_USE - current_heat_capacity = 1000 - layer = 3 - plane = GAME_PLANE - max_integrity = 300 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 30) - -/obj/machinery/atmospherics/unary/cold_sink/freezer/New() - ..() - initialize_directions = dir - component_parts = list() - component_parts += new /obj/item/circuitboard/thermomachine(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/micro_laser(null) - component_parts += new /obj/item/stock_parts/micro_laser(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() - -/obj/machinery/atmospherics/unary/cold_sink/freezer/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/thermomachine(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) - component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() - -/obj/machinery/atmospherics/unary/cold_sink/freezer/RefreshParts() - var/H - var/T - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - H += M.rating - for(var/obj/item/stock_parts/micro_laser/M in component_parts) - T += M.rating - min_temperature = max(0,T0C - (170 + (T*15))) - current_heat_capacity = 1000 * ((H - 1) ** 2) - -/obj/machinery/atmospherics/unary/cold_sink/freezer/on_construction() - ..(dir,dir) - -/obj/machinery/atmospherics/unary/cold_sink/freezer/attackby(obj/item/I, mob/user, params) - if(exchange_parts(user, I)) - return - return ..() - -/obj/machinery/atmospherics/unary/cold_sink/freezer/crowbar_act(mob/user, obj/item/I) - if(default_deconstruction_crowbar(user, I)) - return TRUE - -/obj/machinery/atmospherics/unary/cold_sink/freezer/screwdriver_act(mob/user, obj/item/I) - if(default_deconstruction_screwdriver(user, "freezer-o", "freezer", I)) - on = FALSE - update_icon() - return TRUE - -/obj/machinery/atmospherics/unary/cold_sink/freezer/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(!panel_open) - to_chat(user, "Open the maintenance panel first.") - return - var/list/choices = list("West" = WEST, "East" = EAST, "South" = SOUTH, "North" = NORTH) - var/selected = input(user,"Select a direction for the connector.", "Connector Direction") in choices - dir = choices[selected] - var/node_connect = dir - initialize_directions = dir - for(var/obj/machinery/atmospherics/target in get_step(src,node_connect)) - if(target.initialize_directions & get_dir(target,src)) - node = target - break - build_network() - update_icon() - -/obj/machinery/atmospherics/unary/cold_sink/freezer/update_icon() - if(panel_open) - icon_state = "freezer-o" - else if(src.on) - icon_state = "freezer_1" - else - icon_state = "freezer" - return - -/obj/machinery/atmospherics/unary/cold_sink/freezer/attack_ai(mob/user as mob) - attack_hand(user) - -/obj/machinery/atmospherics/unary/cold_sink/freezer/attack_ghost(mob/user as mob) - attack_hand(user) - -/obj/machinery/atmospherics/unary/cold_sink/freezer/attack_hand(mob/user as mob) - if(panel_open) - to_chat(user, "Close the maintenance panel first.") - return - - src.ui_interact(user) - -/obj/machinery/atmospherics/unary/cold_sink/freezer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "freezer.tmpl", "Gas Cooling System", 540, 300) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/machinery/atmospherics/unary/cold_sink/freezer/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - data["on"] = on ? 1 : 0 - data["gasPressure"] = round(air_contents.return_pressure()) - data["gasTemperature"] = round(air_contents.temperature) - data["gasTemperatureCelsius"] = round(air_contents.temperature - T0C,1) - if(air_contents.total_moles() == 0 && air_contents.temperature == 0) - data["gasTemperatureCelsius"] = 0 - data["minGasTemperature"] = round(min_temperature) - data["maxGasTemperature"] = round(T20C) - data["targetGasTemperature"] = round(current_temperature) - data["targetGasTemperatureCelsius"] = round(current_temperature - T0C,1) - - var/temp_class = "good" - if(air_contents.temperature > (T0C - 20)) - temp_class = "bad" - else if(air_contents.temperature < (T0C - 20) && air_contents.temperature > (T0C - 100)) - temp_class = "average" - data["gasTemperatureClass"] = temp_class - return data - -/obj/machinery/atmospherics/unary/cold_sink/freezer/Topic(href, href_list) - if(..()) - return 1 - if(href_list["toggleStatus"]) - src.on = !src.on - update_icon() - else if(href_list["minimum"]) - current_temperature = min_temperature - else if(href_list["maximum"]) - current_temperature = T20C - else if(href_list["temp"]) - var/amount = text2num(href_list["temp"]) - if(amount > 0) - src.current_temperature = min(T20C, src.current_temperature+amount) - else - src.current_temperature = max(min_temperature, src.current_temperature+amount) - src.add_fingerprint(usr) - return 1 - -/obj/machinery/atmospherics/unary/cold_sink/freezer/power_change() - ..() - if(stat & NOPOWER) - on = 0 - update_icon() - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/ - name = "heater" - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "heater" - density = 1 - var/max_temperature = 0 - anchored = 1.0 - layer = 3 - current_heat_capacity = 1000 - max_integrity = 300 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 30) - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/New() - ..() - initialize_directions = dir - var/obj/item/circuitboard/thermomachine/H = new /obj/item/circuitboard/thermomachine(null) - H.build_path = /obj/machinery/atmospherics/unary/heat_reservoir/heater - H.name = "circuit board (Heater)" - component_parts = list() - component_parts += H - component_parts += new /obj/item/stock_parts/matter_bin(src) - component_parts += new /obj/item/stock_parts/matter_bin(src) - component_parts += new /obj/item/stock_parts/micro_laser(src) - component_parts += new /obj/item/stock_parts/micro_laser(src) - component_parts += new /obj/item/stack/sheet/glass(src) - component_parts += new /obj/item/stack/cable_coil(src, 1) - RefreshParts() - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/upgraded/New() - ..() - var/obj/item/circuitboard/thermomachine/H = new /obj/item/circuitboard/thermomachine(null) - H.build_path = /obj/machinery/atmospherics/unary/heat_reservoir/heater - H.name = "circuit board (Heater)" - component_parts = list() - component_parts += H - component_parts += new /obj/item/stock_parts/matter_bin/super(src) - component_parts += new /obj/item/stock_parts/matter_bin/super(src) - component_parts += new /obj/item/stock_parts/micro_laser/ultra(src) - component_parts += new /obj/item/stock_parts/micro_laser/ultra(src) - component_parts += new /obj/item/stack/sheet/glass(src) - component_parts += new /obj/item/stack/cable_coil(src, 1) - RefreshParts() - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/on_construction() - ..(dir,dir) - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/RefreshParts() - var/H - var/T - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - H += M.rating - for(var/obj/item/stock_parts/micro_laser/M in component_parts) - T += M.rating - max_temperature = T20C + (140 * T) - current_heat_capacity = 1000 * ((H - 1) ** 2) - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/attackby(obj/item/I, mob/user, params) - if(exchange_parts(user, I)) - return - return ..() - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/crowbar_act(mob/user, obj/item/I) - if(default_deconstruction_crowbar(user, I)) - return TRUE - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/screwdriver_act(mob/user, obj/item/I) - if(default_deconstruction_screwdriver(user, "heater-o", "heater", I)) - on = 0 - update_icon() - return TRUE - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(!panel_open) - to_chat(user, "Open the maintenance panel first.") - return - var/list/choices = list("West" = WEST, "East" = EAST, "South" = SOUTH, "North" = NORTH) - var/selected = input(user,"Select a direction for the connector.", "Connector Direction") in choices - dir = choices[selected] - var/node_connect = dir - initialize_directions = dir - for(var/obj/machinery/atmospherics/target in get_step(src,node_connect)) - if(target.initialize_directions & get_dir(target,src)) - node = target - break - build_network() - update_icon() - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/update_icon() - if(panel_open) - icon_state = "heater-o" - else if(src.on) - icon_state = "heater_1" - else - icon_state = "heater" - return - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/attack_ai(mob/user as mob) - attack_hand(user) - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/attack_ghost(mob/user as mob) - src.attack_hand(user) - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/attack_hand(mob/user as mob) - if(panel_open) - to_chat(user, "Close the maintenance panel first.") - return - src.ui_interact(user) - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "freezer.tmpl", "Gas Heating System", 540, 300) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - data["on"] = on ? 1 : 0 - data["gasPressure"] = round(air_contents.return_pressure()) - data["gasTemperature"] = round(air_contents.temperature) - data["gasTemperatureCelsius"] = round(air_contents.temperature - T0C,1) - if(air_contents.total_moles() == 0 && air_contents.temperature == 0) - data["gasTemperatureCelsius"] = 0 - data["minGasTemperature"] = round(T20C) - data["maxGasTemperature"] = round(T20C+max_temperature) - data["targetGasTemperature"] = round(current_temperature) - data["targetGasTemperatureCelsius"] = round(current_temperature - T0C,1) - - var/temp_class = "normal" - if(air_contents.temperature > (T20C+40)) - temp_class = "bad" - data["gasTemperatureClass"] = temp_class - return data - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/Topic(href, href_list) - if(..()) - return 1 - if(href_list["toggleStatus"]) - src.on = !src.on - update_icon() - else if(href_list["minimum"]) - current_temperature = T20C - else if(href_list["maximum"]) - current_temperature = max_temperature + T20C - else if(href_list["temp"]) - var/amount = text2num(href_list["temp"]) - if(amount > 0) - src.current_temperature = min((T20C+max_temperature), src.current_temperature+amount) - else - src.current_temperature = max(T20C, src.current_temperature+amount) - src.add_fingerprint(usr) - return 1 - -/obj/machinery/atmospherics/unary/heat_reservoir/heater/power_change() - ..() - if(stat & NOPOWER) - on = 0 - update_icon() +/obj/machinery/atmospherics/unary/cold_sink/freezer + name = "freezer" + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "freezer" + density = 1 + var/min_temperature = 0 + anchored = 1.0 + use_power = IDLE_POWER_USE + current_heat_capacity = 1000 + layer = 3 + plane = GAME_PLANE + max_integrity = 300 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 30) + +/obj/machinery/atmospherics/unary/cold_sink/freezer/New() + ..() + initialize_directions = dir + component_parts = list() + component_parts += new /obj/item/circuitboard/thermomachine(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/micro_laser(null) + component_parts += new /obj/item/stock_parts/micro_laser(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + RefreshParts() + +/obj/machinery/atmospherics/unary/cold_sink/freezer/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/thermomachine(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + RefreshParts() + +/obj/machinery/atmospherics/unary/cold_sink/freezer/RefreshParts() + var/H + var/T + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + H += M.rating + for(var/obj/item/stock_parts/micro_laser/M in component_parts) + T += M.rating + min_temperature = max(0,T0C - (170 + (T*15))) + current_heat_capacity = 1000 * ((H - 1) ** 2) + +/obj/machinery/atmospherics/unary/cold_sink/freezer/on_construction() + ..(dir,dir) + +/obj/machinery/atmospherics/unary/cold_sink/freezer/attackby(obj/item/I, mob/user, params) + if(exchange_parts(user, I)) + return + return ..() + +/obj/machinery/atmospherics/unary/cold_sink/freezer/crowbar_act(mob/user, obj/item/I) + if(default_deconstruction_crowbar(user, I)) + return TRUE + +/obj/machinery/atmospherics/unary/cold_sink/freezer/screwdriver_act(mob/user, obj/item/I) + if(default_deconstruction_screwdriver(user, "freezer-o", "freezer", I)) + on = FALSE + update_icon() + return TRUE + +/obj/machinery/atmospherics/unary/cold_sink/freezer/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!panel_open) + to_chat(user, "Open the maintenance panel first.") + return + var/list/choices = list("West" = WEST, "East" = EAST, "South" = SOUTH, "North" = NORTH) + var/selected = input(user,"Select a direction for the connector.", "Connector Direction") in choices + dir = choices[selected] + var/node_connect = dir + initialize_directions = dir + for(var/obj/machinery/atmospherics/target in get_step(src,node_connect)) + if(target.initialize_directions & get_dir(target,src)) + node = target + break + build_network() + update_icon() + +/obj/machinery/atmospherics/unary/cold_sink/freezer/update_icon() + if(panel_open) + icon_state = "freezer-o" + else if(src.on) + icon_state = "freezer_1" + else + icon_state = "freezer" + return + +/obj/machinery/atmospherics/unary/cold_sink/freezer/attack_ai(mob/user as mob) + attack_hand(user) + +/obj/machinery/atmospherics/unary/cold_sink/freezer/attack_ghost(mob/user as mob) + attack_hand(user) + +/obj/machinery/atmospherics/unary/cold_sink/freezer/attack_hand(mob/user as mob) + if(panel_open) + to_chat(user, "Close the maintenance panel first.") + return + + src.ui_interact(user) + +/obj/machinery/atmospherics/unary/cold_sink/freezer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "freezer.tmpl", "Gas Cooling System", 540, 300) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/machinery/atmospherics/unary/cold_sink/freezer/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + data["on"] = on ? 1 : 0 + data["gasPressure"] = round(air_contents.return_pressure()) + data["gasTemperature"] = round(air_contents.temperature) + data["gasTemperatureCelsius"] = round(air_contents.temperature - T0C,1) + if(air_contents.total_moles() == 0 && air_contents.temperature == 0) + data["gasTemperatureCelsius"] = 0 + data["minGasTemperature"] = round(min_temperature) + data["maxGasTemperature"] = round(T20C) + data["targetGasTemperature"] = round(current_temperature) + data["targetGasTemperatureCelsius"] = round(current_temperature - T0C,1) + + var/temp_class = "good" + if(air_contents.temperature > (T0C - 20)) + temp_class = "bad" + else if(air_contents.temperature < (T0C - 20) && air_contents.temperature > (T0C - 100)) + temp_class = "average" + data["gasTemperatureClass"] = temp_class + return data + +/obj/machinery/atmospherics/unary/cold_sink/freezer/Topic(href, href_list) + if(..()) + return 1 + if(href_list["toggleStatus"]) + src.on = !src.on + update_icon() + else if(href_list["minimum"]) + current_temperature = min_temperature + else if(href_list["maximum"]) + current_temperature = T20C + else if(href_list["temp"]) + var/amount = text2num(href_list["temp"]) + if(amount > 0) + src.current_temperature = min(T20C, src.current_temperature+amount) + else + src.current_temperature = max(min_temperature, src.current_temperature+amount) + src.add_fingerprint(usr) + return 1 + +/obj/machinery/atmospherics/unary/cold_sink/freezer/power_change() + ..() + if(stat & NOPOWER) + on = 0 + update_icon() + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/ + name = "heater" + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "heater" + density = 1 + var/max_temperature = 0 + anchored = 1.0 + layer = 3 + current_heat_capacity = 1000 + max_integrity = 300 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 30) + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/New() + ..() + initialize_directions = dir + var/obj/item/circuitboard/thermomachine/H = new /obj/item/circuitboard/thermomachine(null) + H.build_path = /obj/machinery/atmospherics/unary/heat_reservoir/heater + H.name = "circuit board (Heater)" + component_parts = list() + component_parts += H + component_parts += new /obj/item/stock_parts/matter_bin(src) + component_parts += new /obj/item/stock_parts/matter_bin(src) + component_parts += new /obj/item/stock_parts/micro_laser(src) + component_parts += new /obj/item/stock_parts/micro_laser(src) + component_parts += new /obj/item/stack/sheet/glass(src) + component_parts += new /obj/item/stack/cable_coil(src, 1) + RefreshParts() + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/upgraded/New() + ..() + var/obj/item/circuitboard/thermomachine/H = new /obj/item/circuitboard/thermomachine(null) + H.build_path = /obj/machinery/atmospherics/unary/heat_reservoir/heater + H.name = "circuit board (Heater)" + component_parts = list() + component_parts += H + component_parts += new /obj/item/stock_parts/matter_bin/super(src) + component_parts += new /obj/item/stock_parts/matter_bin/super(src) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(src) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(src) + component_parts += new /obj/item/stack/sheet/glass(src) + component_parts += new /obj/item/stack/cable_coil(src, 1) + RefreshParts() + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/on_construction() + ..(dir,dir) + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/RefreshParts() + var/H + var/T + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + H += M.rating + for(var/obj/item/stock_parts/micro_laser/M in component_parts) + T += M.rating + max_temperature = T20C + (140 * T) + current_heat_capacity = 1000 * ((H - 1) ** 2) + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/attackby(obj/item/I, mob/user, params) + if(exchange_parts(user, I)) + return + return ..() + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/crowbar_act(mob/user, obj/item/I) + if(default_deconstruction_crowbar(user, I)) + return TRUE + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/screwdriver_act(mob/user, obj/item/I) + if(default_deconstruction_screwdriver(user, "heater-o", "heater", I)) + on = 0 + update_icon() + return TRUE + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!panel_open) + to_chat(user, "Open the maintenance panel first.") + return + var/list/choices = list("West" = WEST, "East" = EAST, "South" = SOUTH, "North" = NORTH) + var/selected = input(user,"Select a direction for the connector.", "Connector Direction") in choices + dir = choices[selected] + var/node_connect = dir + initialize_directions = dir + for(var/obj/machinery/atmospherics/target in get_step(src,node_connect)) + if(target.initialize_directions & get_dir(target,src)) + node = target + break + build_network() + update_icon() + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/update_icon() + if(panel_open) + icon_state = "heater-o" + else if(src.on) + icon_state = "heater_1" + else + icon_state = "heater" + return + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/attack_ai(mob/user as mob) + attack_hand(user) + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/attack_ghost(mob/user as mob) + src.attack_hand(user) + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/attack_hand(mob/user as mob) + if(panel_open) + to_chat(user, "Close the maintenance panel first.") + return + src.ui_interact(user) + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "freezer.tmpl", "Gas Heating System", 540, 300) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + data["on"] = on ? 1 : 0 + data["gasPressure"] = round(air_contents.return_pressure()) + data["gasTemperature"] = round(air_contents.temperature) + data["gasTemperatureCelsius"] = round(air_contents.temperature - T0C,1) + if(air_contents.total_moles() == 0 && air_contents.temperature == 0) + data["gasTemperatureCelsius"] = 0 + data["minGasTemperature"] = round(T20C) + data["maxGasTemperature"] = round(T20C+max_temperature) + data["targetGasTemperature"] = round(current_temperature) + data["targetGasTemperatureCelsius"] = round(current_temperature - T0C,1) + + var/temp_class = "normal" + if(air_contents.temperature > (T20C+40)) + temp_class = "bad" + data["gasTemperatureClass"] = temp_class + return data + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/Topic(href, href_list) + if(..()) + return 1 + if(href_list["toggleStatus"]) + src.on = !src.on + update_icon() + else if(href_list["minimum"]) + current_temperature = T20C + else if(href_list["maximum"]) + current_temperature = max_temperature + T20C + else if(href_list["temp"]) + var/amount = text2num(href_list["temp"]) + if(amount > 0) + src.current_temperature = min((T20C+max_temperature), src.current_temperature+amount) + else + src.current_temperature = max(T20C, src.current_temperature+amount) + src.add_fingerprint(usr) + return 1 + +/obj/machinery/atmospherics/unary/heat_reservoir/heater/power_change() + ..() + if(stat & NOPOWER) + on = 0 + update_icon() diff --git a/code/game/machinery/OpTable.dm b/code/game/machinery/OpTable.dm index 8c93028649b..7317099b8dc 100644 --- a/code/game/machinery/OpTable.dm +++ b/code/game/machinery/OpTable.dm @@ -1,155 +1,155 @@ -/obj/machinery/optable - name = "Operating Table" - desc = "Used for advanced medical procedures." - icon = 'icons/obj/surgery.dmi' - icon_state = "table2-idle" - density = 1 - anchored = 1.0 - use_power = IDLE_POWER_USE - idle_power_usage = 1 - active_power_usage = 5 - var/mob/living/carbon/human/victim = null - var/strapped = 0.0 - - var/obj/machinery/computer/operating/computer = null - buckle_lying = -1 - var/no_icon_updates = 0 //set this to 1 if you don't want the icons ever changing - var/list/injected_reagents = list() - var/reagent_target_amount = 1 - var/inject_amount = 1 - -/obj/machinery/optable/New() - ..() - for(dir in list(NORTH,EAST,SOUTH,WEST)) - computer = locate(/obj/machinery/computer/operating, get_step(src, dir)) - if(computer) - computer.table = src - break - -/obj/machinery/optable/Destroy() - if(computer) - computer.table = null - computer.victim = null - computer = null - if(victim) - victim = null - return ..() - -/obj/machinery/optable/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) - if(user.a_intent == INTENT_HARM) - ..(user, TRUE) - visible_message("[user] destroys the operating table!") - qdel(src) - return TRUE - -/obj/machinery/optable/CanPass(atom/movable/mover, turf/target, height=0) - if(height==0) return 1 - - if(istype(mover) && mover.checkpass(PASSTABLE)) - return 1 - else - return 0 - - -/obj/machinery/optable/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) - if(usr.stat || (!ishuman(user) && !isrobot(user)) || user.restrained() || !check_table(user) || user.IsWeakened() || user.stunned) - return - - if(!ismob(O)) //humans only - return - - if(istype(O, /mob/living/simple_animal) || istype(O, /mob/living/silicon)) //animals and robots dont fit - return - - var/mob/living/L = O - take_victim(L,usr) - return - -/obj/machinery/optable/proc/check_victim() - if(locate(/mob/living/carbon/human, src.loc)) - var/mob/living/carbon/human/M = locate(/mob/living/carbon/human, src.loc) - if(M.lying) - src.victim = M - if(!no_icon_updates) - icon_state = M.pulse ? "table2-active" : "table2-idle" - return 1 - src.victim = null - if(!no_icon_updates) - icon_state = "table2-idle" - return 0 - -/obj/machinery/optable/Crossed(atom/movable/AM, oldloc) - . = ..() - if(iscarbon(AM) && LAZYLEN(injected_reagents)) - to_chat(AM, "You feel a series of tiny pricks!") - -/obj/machinery/optable/process() - check_victim() - if(LAZYLEN(injected_reagents)) - for(var/mob/living/carbon/C in get_turf(src)) - var/datum/reagents/R = C.reagents - for(var/chemical in injected_reagents) - R.check_and_add(chemical,reagent_target_amount,inject_amount) - -/obj/machinery/optable/proc/take_victim(mob/living/carbon/C, mob/living/carbon/user as mob) - if(C == user) - user.visible_message("[user] climbs on the operating table.","You climb on the operating table.") - else - visible_message("[C] has been laid on the operating table by [user].") - C.resting = 1 - C.update_canmove() - C.forceMove(loc) - if(user.pulling == C) - user.stop_pulling() - if(C.s_active) //Close the container opened - C.s_active.close(C) - for(var/obj/O in src) - O.loc = src.loc - src.add_fingerprint(user) - if(ishuman(C)) - var/mob/living/carbon/human/H = C - src.victim = H - if(!no_icon_updates) - icon_state = H.pulse ? "table2-active" : "table2-idle" - else - if(!no_icon_updates) - icon_state = "table2-idle" - -/obj/machinery/optable/verb/climb_on() - set name = "Climb On Table" - set category = "Object" - set src in oview(1) - - if(usr.stat || !ishuman(usr) || usr.restrained() || !check_table(usr)) - return - - take_victim(usr,usr) - -/obj/machinery/optable/attackby(obj/item/I, mob/living/carbon/user, params) - if(istype(I, /obj/item/grab)) - var/obj/item/grab/G = I - if(iscarbon(G.affecting)) - take_victim(G.affecting, user) - qdel(G) - else - return ..() - -/obj/machinery/optable/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_start_check(user, 0)) - return - if(I.use_tool(src, user, 20, volume = I.tool_volume)) - to_chat(user, "You deconstruct the table.") - new /obj/item/stack/sheet/plasteel(loc, 5) - qdel(src) - -/obj/machinery/optable/proc/check_table(mob/living/carbon/patient as mob) - if(src.victim && get_turf(victim) == get_turf(src) && victim.lying) - to_chat(usr, "The table is already occupied!") - return 0 - - if(patient.buckled) - to_chat(usr, "Unbuckle first!") - return 0 - - return 1 +/obj/machinery/optable + name = "Operating Table" + desc = "Used for advanced medical procedures." + icon = 'icons/obj/surgery.dmi' + icon_state = "table2-idle" + density = 1 + anchored = 1.0 + use_power = IDLE_POWER_USE + idle_power_usage = 1 + active_power_usage = 5 + var/mob/living/carbon/human/victim = null + var/strapped = 0.0 + + var/obj/machinery/computer/operating/computer = null + buckle_lying = -1 + var/no_icon_updates = 0 //set this to 1 if you don't want the icons ever changing + var/list/injected_reagents = list() + var/reagent_target_amount = 1 + var/inject_amount = 1 + +/obj/machinery/optable/New() + ..() + for(dir in list(NORTH,EAST,SOUTH,WEST)) + computer = locate(/obj/machinery/computer/operating, get_step(src, dir)) + if(computer) + computer.table = src + break + +/obj/machinery/optable/Destroy() + if(computer) + computer.table = null + computer.victim = null + computer = null + if(victim) + victim = null + return ..() + +/obj/machinery/optable/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + if(user.a_intent == INTENT_HARM) + ..(user, TRUE) + visible_message("[user] destroys the operating table!") + qdel(src) + return TRUE + +/obj/machinery/optable/CanPass(atom/movable/mover, turf/target, height=0) + if(height==0) return 1 + + if(istype(mover) && mover.checkpass(PASSTABLE)) + return 1 + else + return 0 + + +/obj/machinery/optable/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) + if(usr.stat || (!ishuman(user) && !isrobot(user)) || user.restrained() || !check_table(user) || user.IsWeakened() || user.stunned) + return + + if(!ismob(O)) //humans only + return + + if(istype(O, /mob/living/simple_animal) || istype(O, /mob/living/silicon)) //animals and robots dont fit + return + + var/mob/living/L = O + take_victim(L,usr) + return + +/obj/machinery/optable/proc/check_victim() + if(locate(/mob/living/carbon/human, src.loc)) + var/mob/living/carbon/human/M = locate(/mob/living/carbon/human, src.loc) + if(M.lying) + src.victim = M + if(!no_icon_updates) + icon_state = M.pulse ? "table2-active" : "table2-idle" + return 1 + src.victim = null + if(!no_icon_updates) + icon_state = "table2-idle" + return 0 + +/obj/machinery/optable/Crossed(atom/movable/AM, oldloc) + . = ..() + if(iscarbon(AM) && LAZYLEN(injected_reagents)) + to_chat(AM, "You feel a series of tiny pricks!") + +/obj/machinery/optable/process() + check_victim() + if(LAZYLEN(injected_reagents)) + for(var/mob/living/carbon/C in get_turf(src)) + var/datum/reagents/R = C.reagents + for(var/chemical in injected_reagents) + R.check_and_add(chemical,reagent_target_amount,inject_amount) + +/obj/machinery/optable/proc/take_victim(mob/living/carbon/C, mob/living/carbon/user as mob) + if(C == user) + user.visible_message("[user] climbs on the operating table.","You climb on the operating table.") + else + visible_message("[C] has been laid on the operating table by [user].") + C.resting = 1 + C.update_canmove() + C.forceMove(loc) + if(user.pulling == C) + user.stop_pulling() + if(C.s_active) //Close the container opened + C.s_active.close(C) + for(var/obj/O in src) + O.loc = src.loc + src.add_fingerprint(user) + if(ishuman(C)) + var/mob/living/carbon/human/H = C + src.victim = H + if(!no_icon_updates) + icon_state = H.pulse ? "table2-active" : "table2-idle" + else + if(!no_icon_updates) + icon_state = "table2-idle" + +/obj/machinery/optable/verb/climb_on() + set name = "Climb On Table" + set category = "Object" + set src in oview(1) + + if(usr.stat || !ishuman(usr) || usr.restrained() || !check_table(usr)) + return + + take_victim(usr,usr) + +/obj/machinery/optable/attackby(obj/item/I, mob/living/carbon/user, params) + if(istype(I, /obj/item/grab)) + var/obj/item/grab/G = I + if(iscarbon(G.affecting)) + take_victim(G.affecting, user) + qdel(G) + else + return ..() + +/obj/machinery/optable/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_start_check(user, 0)) + return + if(I.use_tool(src, user, 20, volume = I.tool_volume)) + to_chat(user, "You deconstruct the table.") + new /obj/item/stack/sheet/plasteel(loc, 5) + qdel(src) + +/obj/machinery/optable/proc/check_table(mob/living/carbon/patient as mob) + if(src.victim && get_turf(victim) == get_turf(src) && victim.lying) + to_chat(usr, "The table is already occupied!") + return 0 + + if(patient.buckled) + to_chat(usr, "Unbuckle first!") + return 0 + + return 1 diff --git a/code/game/machinery/PDApainter.dm b/code/game/machinery/PDApainter.dm index 83778a99c26..3185cdcddd4 100644 --- a/code/game/machinery/PDApainter.dm +++ b/code/game/machinery/PDApainter.dm @@ -1,132 +1,132 @@ -/obj/machinery/pdapainter - name = "PDA painter" - desc = "A PDA painting machine. To use, simply insert your PDA and choose the desired preset paint scheme." - icon = 'icons/obj/pda.dmi' - icon_state = "pdapainter" - density = 1 - anchored = 1 - max_integrity = 200 - var/obj/item/pda/storedpda = null - var/list/colorlist = list() - - -/obj/machinery/pdapainter/update_icon() - cut_overlays() - - if(stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - return - - if(storedpda) - add_overlay("[initial(icon_state)]-closed") - - if(powered()) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)]-off" - - return - -/obj/machinery/pdapainter/New() - ..() - var/blocked = list(/obj/item/pda/silicon/ai, /obj/item/pda/silicon/robot, /obj/item/pda/silicon/pai, /obj/item/pda/heads, - /obj/item/pda/clear, /obj/item/pda/syndicate) - - for(var/P in typesof(/obj/item/pda)-blocked) - var/obj/item/pda/D = new P - - //D.name = "PDA Style [colorlist.len+1]" //Gotta set the name, otherwise it all comes up as "PDA" - D.name = D.icon_state //PDAs don't have unique names, but using the sprite names works. - - src.colorlist += D - -/obj/machinery/pdapainter/Destroy() - QDEL_NULL(storedpda) - return ..() - -/obj/machinery/pdapainter/on_deconstruction() - if(storedpda) - storedpda.forceMove(loc) - storedpda = null - -/obj/machinery/pdapainter/ex_act(severity) - if(storedpda) - storedpda.ex_act(severity) - ..() - -/obj/machinery/pdapainter/handle_atom_del(atom/A) - if(A == storedpda) - storedpda = null - update_icon() - -/obj/machinery/pdapainter/attackby(obj/item/I, mob/user, params) - if(default_unfasten_wrench(user, I)) - power_change() - return - if(istype(I, /obj/item/pda)) - if(storedpda) - to_chat(user, "There is already a PDA inside.") - return - else - var/obj/item/pda/P = user.get_active_hand() - if(istype(P)) - if(user.drop_item()) - storedpda = P - P.forceMove(src) - P.add_fingerprint(user) - update_icon() - else - return ..() - -/obj/machinery/pdapainter/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - default_welder_repair(user, I) - -/obj/machinery/pdapainter/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(!(stat & BROKEN)) - stat |= BROKEN - update_icon() - -/obj/machinery/pdapainter/attack_hand(mob/user as mob) - if(..()) - return 1 - - src.add_fingerprint(user) - - if(storedpda) - var/obj/item/pda/P - P = input(user, "Select your color!", "PDA Painting") as null|anything in colorlist - if(!P) - return - if(!in_range(src, user)) - return - - storedpda.icon_state = P.icon_state - storedpda.desc = P.desc - - else - to_chat(user, "The [src] is empty.") - - -/obj/machinery/pdapainter/verb/ejectpda() - set name = "Eject PDA" - set category = "Object" - set src in oview(1) - - if(usr.incapacitated()) - return - - if(storedpda) - storedpda.loc = get_turf(src.loc) - storedpda = null - update_icon() - else - to_chat(usr, "The [src] is empty.") - - -/obj/machinery/pdapainter/power_change() - ..() - update_icon() \ No newline at end of file +/obj/machinery/pdapainter + name = "PDA painter" + desc = "A PDA painting machine. To use, simply insert your PDA and choose the desired preset paint scheme." + icon = 'icons/obj/pda.dmi' + icon_state = "pdapainter" + density = 1 + anchored = 1 + max_integrity = 200 + var/obj/item/pda/storedpda = null + var/list/colorlist = list() + + +/obj/machinery/pdapainter/update_icon() + cut_overlays() + + if(stat & BROKEN) + icon_state = "[initial(icon_state)]-broken" + return + + if(storedpda) + add_overlay("[initial(icon_state)]-closed") + + if(powered()) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]-off" + + return + +/obj/machinery/pdapainter/New() + ..() + var/blocked = list(/obj/item/pda/silicon/ai, /obj/item/pda/silicon/robot, /obj/item/pda/silicon/pai, /obj/item/pda/heads, + /obj/item/pda/clear, /obj/item/pda/syndicate) + + for(var/P in typesof(/obj/item/pda)-blocked) + var/obj/item/pda/D = new P + + //D.name = "PDA Style [colorlist.len+1]" //Gotta set the name, otherwise it all comes up as "PDA" + D.name = D.icon_state //PDAs don't have unique names, but using the sprite names works. + + src.colorlist += D + +/obj/machinery/pdapainter/Destroy() + QDEL_NULL(storedpda) + return ..() + +/obj/machinery/pdapainter/on_deconstruction() + if(storedpda) + storedpda.forceMove(loc) + storedpda = null + +/obj/machinery/pdapainter/ex_act(severity) + if(storedpda) + storedpda.ex_act(severity) + ..() + +/obj/machinery/pdapainter/handle_atom_del(atom/A) + if(A == storedpda) + storedpda = null + update_icon() + +/obj/machinery/pdapainter/attackby(obj/item/I, mob/user, params) + if(default_unfasten_wrench(user, I)) + power_change() + return + if(istype(I, /obj/item/pda)) + if(storedpda) + to_chat(user, "There is already a PDA inside.") + return + else + var/obj/item/pda/P = user.get_active_hand() + if(istype(P)) + if(user.drop_item()) + storedpda = P + P.forceMove(src) + P.add_fingerprint(user) + update_icon() + else + return ..() + +/obj/machinery/pdapainter/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + default_welder_repair(user, I) + +/obj/machinery/pdapainter/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(!(stat & BROKEN)) + stat |= BROKEN + update_icon() + +/obj/machinery/pdapainter/attack_hand(mob/user as mob) + if(..()) + return 1 + + src.add_fingerprint(user) + + if(storedpda) + var/obj/item/pda/P + P = input(user, "Select your color!", "PDA Painting") as null|anything in colorlist + if(!P) + return + if(!in_range(src, user)) + return + + storedpda.icon_state = P.icon_state + storedpda.desc = P.desc + + else + to_chat(user, "The [src] is empty.") + + +/obj/machinery/pdapainter/verb/ejectpda() + set name = "Eject PDA" + set category = "Object" + set src in oview(1) + + if(usr.incapacitated()) + return + + if(storedpda) + storedpda.loc = get_turf(src.loc) + storedpda = null + update_icon() + else + to_chat(usr, "The [src] is empty.") + + +/obj/machinery/pdapainter/power_change() + ..() + update_icon() diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index f1adf1a5f11..81409b5de86 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -1,556 +1,556 @@ -#define ADDICTION_SPEEDUP_TIME 1500 // 2.5 minutes - -///////////// -// SLEEPER // -//////////// - -/obj/machinery/sleeper - name = "Sleeper" - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "sleeper-open" - var/base_icon = "sleeper" - density = 1 - anchored = 1 - dir = WEST - var/orient = "LEFT" // "RIGHT" changes the dir suffix to "-r" - var/mob/living/carbon/human/occupant = null - var/possible_chems = list("ephedrine", "salglu_solution", "salbutamol", "charcoal") - var/emergency_chems = list("ephedrine") // Desnowflaking - var/amounts = list(5, 10) - var/obj/item/reagent_containers/glass/beaker = null - var/filtering = 0 - var/max_chem - var/initial_bin_rating = 1 - var/min_health = -25 - var/controls_inside = FALSE - idle_power_usage = 1250 - active_power_usage = 2500 - - light_color = LIGHT_COLOR_CYAN - -/obj/machinery/sleeper/power_change() - ..() - if(!(stat & (BROKEN|NOPOWER))) - set_light(2) - else - set_light(0) - -/obj/machinery/sleeper/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/sleeper(null) - - // Customizable bin rating, used by the labor camp to stop people filling themselves with chemicals and escaping. - var/obj/item/stock_parts/matter_bin/B = new(null) - B.rating = initial_bin_rating - component_parts += B - - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() - -/obj/machinery/sleeper/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/sleeper(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() - -/obj/machinery/sleeper/RefreshParts() - var/E - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - E += B.rating - - max_chem = E * 20 - min_health = -E * 25 - -/obj/machinery/sleeper/Destroy() - for(var/mob/M in contents) - M.forceMove(get_turf(src)) - return ..() - -/obj/machinery/sleeper/relaymove(mob/user as mob) - if(user.incapacitated()) - return 0 //maybe they should be able to get out with cuffs, but whatever - go_out() - -/obj/machinery/sleeper/process() - if(filtering > 0) - if(beaker) - // To prevent runtimes from drawing blood from runtime, and to prevent getting IPC blood. - if(!istype(occupant) || !occupant.dna || (NO_BLOOD in occupant.dna.species.species_traits)) - filtering = 0 - return - - if(beaker.reagents.total_volume < beaker.reagents.maximum_volume) - occupant.transfer_blood_to(beaker, 1) - for(var/datum/reagent/x in occupant.reagents.reagent_list) - occupant.reagents.trans_to(beaker, 3) - occupant.transfer_blood_to(beaker, 1) - - if(occupant) - for(var/A in occupant.reagents.addiction_list) - var/datum/reagent/R = A - - var/addiction_removal_chance = 5 - if(world.timeofday > (R.last_addiction_dose + ADDICTION_SPEEDUP_TIME)) // 2.5 minutes - addiction_removal_chance = 10 - if(prob(addiction_removal_chance)) - to_chat(occupant, "You no longer feel reliant on [R.name]!") - occupant.reagents.addiction_list.Remove(R) - qdel(R) - - for(var/mob/M as mob in src) // makes sure that simple mobs don't get stuck inside a sleeper when they resist out of occupant's grasp - if(M == occupant) - continue - else - M.forceMove(loc) - - updateDialog() - return - - -/obj/machinery/sleeper/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/sleeper/attack_ghost(mob/user) - return attack_hand(user) - -/obj/machinery/sleeper/attack_hand(mob/user) - if(stat & (NOPOWER|BROKEN)) - return - - if(panel_open) - to_chat(user, "Close the maintenance panel first.") - return - - ui_interact(user) - -/obj/machinery/sleeper/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "sleeper.tmpl", "Sleeper", 550, 770) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/sleeper/ui_data(mob/user, datum/topic_state/state) - var/data[0] - data["hasOccupant"] = occupant ? 1 : 0 - var/occupantData[0] - var/crisis = 0 - if(occupant) - occupantData["name"] = occupant.name - occupantData["stat"] = occupant.stat - occupantData["health"] = occupant.health - occupantData["maxHealth"] = occupant.maxHealth - occupantData["minHealth"] = HEALTH_THRESHOLD_DEAD - occupantData["bruteLoss"] = occupant.getBruteLoss() - occupantData["oxyLoss"] = occupant.getOxyLoss() - occupantData["toxLoss"] = occupant.getToxLoss() - occupantData["fireLoss"] = occupant.getFireLoss() - occupantData["paralysis"] = occupant.paralysis - occupantData["hasBlood"] = 0 - occupantData["bodyTemperature"] = occupant.bodytemperature - occupantData["maxTemp"] = 1000 // If you get a burning vox armalis into the sleeper, congratulations - // Because we can put simple_animals in here, we need to do something tricky to get things working nice - occupantData["temperatureSuitability"] = 0 // 0 is the baseline - if(ishuman(occupant) && occupant.dna.species) - // I wanna do something where the bar gets bluer as the temperature gets lower - // For now, I'll just use the standard format for the temperature status - var/datum/species/sp = occupant.dna.species - if(occupant.bodytemperature < sp.cold_level_3) - occupantData["temperatureSuitability"] = -3 - else if(occupant.bodytemperature < sp.cold_level_2) - occupantData["temperatureSuitability"] = -2 - else if(occupant.bodytemperature < sp.cold_level_1) - occupantData["temperatureSuitability"] = -1 - else if(occupant.bodytemperature > sp.heat_level_3) - occupantData["temperatureSuitability"] = 3 - else if(occupant.bodytemperature > sp.heat_level_2) - occupantData["temperatureSuitability"] = 2 - else if(occupant.bodytemperature > sp.heat_level_1) - occupantData["temperatureSuitability"] = 1 - else if(istype(occupant, /mob/living/simple_animal)) - var/mob/living/simple_animal/silly = occupant - if(silly.bodytemperature < silly.minbodytemp) - occupantData["temperatureSuitability"] = -3 - else if(silly.bodytemperature > silly.maxbodytemp) - occupantData["temperatureSuitability"] = 3 - // Blast you, imperial measurement system - occupantData["btCelsius"] = occupant.bodytemperature - T0C - occupantData["btFaren"] = ((occupant.bodytemperature - T0C) * (9.0/5.0))+ 32 - - - crisis = (occupant.health < min_health) - // I'm not sure WHY you'd want to put a simple_animal in a sleeper, but precedent is precedent - // Runtime is aptly named, isn't she? - if(ishuman(occupant) && !(NO_BLOOD in occupant.dna.species.species_traits)) - occupantData["pulse"] = occupant.get_pulse(GETPULSE_TOOL) - occupantData["hasBlood"] = 1 - occupantData["bloodLevel"] = round(occupant.blood_volume) - occupantData["bloodMax"] = occupant.max_blood - occupantData["bloodPercent"] = round(100*(occupant.blood_volume/occupant.max_blood), 0.01) - - data["occupant"] = occupantData - data["maxchem"] = max_chem - data["minhealth"] = min_health - data["dialysis"] = filtering - if(beaker) - data["isBeakerLoaded"] = 1 - if(beaker.reagents) - data["beakerFreeSpace"] = round(beaker.reagents.maximum_volume - beaker.reagents.total_volume) - else - data["beakerFreeSpace"] = 0 - - var/chemicals[0] - for(var/re in possible_chems) - var/datum/reagent/temp = GLOB.chemical_reagents_list[re] - if(temp) - var/reagent_amount = 0 - var/pretty_amount - var/injectable = occupant ? 1 : 0 - var/overdosing = 0 - var/caution = 0 // To make things clear that you're coming close to an overdose - if(crisis && !(temp.id in emergency_chems)) - injectable = 0 - - if(occupant && occupant.reagents) - reagent_amount = occupant.reagents.get_reagent_amount(temp.id) - // If they're mashing the highest concentration, they get one warning - if(temp.overdose_threshold && reagent_amount + 10 > temp.overdose_threshold) - caution = 1 - if(temp.id in occupant.reagents.overdose_list()) - overdosing = 1 - - // Because I don't know how to do this on the nano side - pretty_amount = round(reagent_amount, 0.05) - - chemicals.Add(list(list("title" = temp.name, "id" = temp.id, "commands" = list("chemical" = temp.id), "occ_amount" = reagent_amount, "pretty_amount" = pretty_amount, "injectable" = injectable, "overdosing" = overdosing, "od_warning" = caution))) - data["chemicals"] = chemicals - return data - -/obj/machinery/sleeper/Topic(href, href_list) - if(!controls_inside && usr == occupant) - return 0 - - if(..()) - return 1 - - if(panel_open) - to_chat(usr, "Close the maintenance panel first.") - return 0 - - if((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon/ai))) - if(href_list["chemical"]) - if(occupant) - if(occupant.stat == DEAD) - to_chat(usr, "This person has no life for to preserve anymore. Take [occupant.p_them()] to a department capable of reanimating them.") - else if(occupant.health > min_health || (href_list["chemical"] in emergency_chems)) - inject_chemical(usr,href_list["chemical"],text2num(href_list["amount"])) - else - to_chat(usr, "This person is not in good enough condition for sleepers to be effective! Use another means of treatment, such as cryogenics!") - if(href_list["removebeaker"]) - remove_beaker() - if(href_list["togglefilter"]) - toggle_filter() - if(href_list["ejectify"]) - eject() - add_fingerprint(usr) - return 1 - -/obj/machinery/sleeper/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/glass)) - if(!beaker) - if(!user.drop_item()) - to_chat(user, "[I] is stuck to you!") - return - - beaker = I - I.forceMove(src) - user.visible_message("[user] adds \a [I] to [src]!", "You add \a [I] to [src]!") - return - - else - to_chat(user, "The sleeper has a beaker already.") - return - - if(exchange_parts(user, I)) - return - - if(istype(I, /obj/item/grab)) - var/obj/item/grab/G = I - if(panel_open) - to_chat(user, "Close the maintenance panel first.") - return - if(!ismob(G.affecting)) - return - if(occupant) - to_chat(user, "The sleeper is already occupied!") - return - if(G.affecting.has_buckled_mobs()) //mob attached to us - to_chat(user, "[G.affecting] will not fit into [src] because [G.affecting.p_they()] [G.affecting.p_have()] a slime latched onto [G.affecting.p_their()] head.") - return - - visible_message("[user] starts putting [G.affecting.name] into the sleeper.") - - if(do_after(user, 20, target = G.affecting)) - if(occupant) - to_chat(user, "The sleeper is already occupied!") - return - if(!G || !G.affecting) - return - var/mob/M = G.affecting - M.forceMove(src) - occupant = M - icon_state = "[base_icon]" - to_chat(M, "You feel cool air surround you. You go numb as your senses turn inward.") - add_fingerprint(user) - qdel(G) - return - - return ..() - - -/obj/machinery/sleeper/crowbar_act(mob/user, obj/item/I) - if(default_deconstruction_crowbar(user, I)) - return TRUE - -/obj/machinery/sleeper/screwdriver_act(mob/user, obj/item/I) - if(occupant) - to_chat(user, "The maintenance panel is locked.") - return TRUE - if(default_deconstruction_screwdriver(user, "[base_icon]-o", "[base_icon]-open", I)) - return TRUE - -/obj/machinery/sleeper/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(occupant) - to_chat(user, "The scanner is occupied.") - return - if(panel_open) - to_chat(user, "Close the maintenance panel first.") - return - if(dir == EAST) - orient = "LEFT" - setDir(WEST) - else - orient = "RIGHT" - setDir(EAST) - -/obj/machinery/sleeper/ex_act(severity) - if(filtering) - toggle_filter() - if(occupant) - occupant.ex_act(severity) - ..() - -/obj/machinery/sleeper/handle_atom_del(atom/A) - ..() - if(A == occupant) - occupant = null - updateUsrDialog() - update_icon() - if(A == beaker) - beaker = null - updateUsrDialog() - -/obj/machinery/sleeper/emp_act(severity) - if(filtering) - toggle_filter() - if(stat & (BROKEN|NOPOWER)) - ..(severity) - return - if(occupant) - go_out() - ..(severity) - -/obj/machinery/sleeper/narsie_act() - go_out() - new /obj/effect/gibspawner/generic(get_turf(loc)) //I REPLACE YOUR TECHNOLOGY WITH FLESH! - qdel(src) - -/obj/machinery/sleeper/proc/toggle_filter() - if(filtering) - filtering = 0 - else - filtering = 1 - -/obj/machinery/sleeper/proc/go_out() - if(filtering) - toggle_filter() - if(!occupant) - return - occupant.forceMove(loc) - occupant = null - icon_state = "[base_icon]-open" - // eject trash the occupant dropped - for(var/atom/movable/A in contents - component_parts - list(beaker)) - A.forceMove(loc) - -/obj/machinery/sleeper/proc/inject_chemical(mob/living/user as mob, chemical, amount) - if(!(chemical in possible_chems)) - to_chat(user, "The sleeper does not offer that chemical!") - return - - if(occupant) - if(occupant.reagents) - if(occupant.reagents.get_reagent_amount(chemical) + amount <= max_chem) - occupant.reagents.add_reagent(chemical, amount) - return - else - to_chat(user, "You can not inject any more of this chemical.") - return - else - to_chat(user, "The patient rejects the chemicals!") - return - else - to_chat(user, "There's no occupant in the sleeper!") - return - -/obj/machinery/sleeper/verb/eject() - set name = "Eject Sleeper" - set category = "Object" - set src in oview(1) - - if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other - return - - icon_state = "[base_icon]-open" - go_out() - add_fingerprint(usr) - return - -/obj/machinery/sleeper/verb/remove_beaker() - set name = "Remove Beaker" - set category = "Object" - set src in oview(1) - - if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other - return - - if(beaker) - filtering = 0 - beaker.forceMove(usr.loc) - beaker = null - add_fingerprint(usr) - return - -/obj/machinery/sleeper/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) - if(O.loc == user) //no you can't pull things out of your ass - return - if(user.incapacitated()) //are you cuffed, dying, lying, stunned or other - return - if(get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src)) // is the mob anchored, too far away from you, or are you too far away from the source - return - if(!ismob(O)) //humans only - return - if(istype(O, /mob/living/simple_animal) || istype(O, /mob/living/silicon)) //animals and robots dont fit - return - if(!ishuman(user) && !isrobot(user)) //No ghosts or mice putting people into the sleeper - return - if(user.loc==null) // just in case someone manages to get a closet into the blue light dimension, as unlikely as that seems - return - if(!istype(user.loc, /turf) || !istype(O.loc, /turf)) // are you in a container/closet/pod/etc? - return - if(panel_open) - to_chat(user, "Close the maintenance panel first.") - return - if(occupant) - to_chat(user, "The sleeper is already occupied!") - return - var/mob/living/L = O - if(!istype(L) || L.buckled) - return - if(L.abiotic()) - to_chat(user, "Subject cannot have abiotic items on.") - return - if(L.has_buckled_mobs()) //mob attached to us - to_chat(user, "[L] will not fit into [src] because [L.p_they()] [L.p_have()] a slime latched onto [L.p_their()] head.") - return - if(L == user) - visible_message("[user] starts climbing into the sleeper.") - else - visible_message("[user] starts putting [L.name] into the sleeper.") - - if(do_after(user, 20, target = L)) - if(occupant) - to_chat(user, "The sleeper is already occupied!") - return - if(!L) return - L.forceMove(src) - occupant = L - icon_state = "[base_icon]" - to_chat(L, "You feel cool air surround you. You go numb as your senses turn inward.") - add_fingerprint(user) - if(user.pulling == L) - user.stop_pulling() - return - return - -/obj/machinery/sleeper/AllowDrop() - return FALSE - -/obj/machinery/sleeper/verb/move_inside() - set name = "Enter Sleeper" - set category = "Object" - set src in oview(1) - if(usr.stat != 0 || !(ishuman(usr))) - return - if(occupant) - to_chat(usr, "The sleeper is already occupied!") - return - if(panel_open) - to_chat(usr, "Close the maintenance panel first.") - return - if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other - return - if(usr.has_buckled_mobs()) //mob attached to us - to_chat(usr, "[usr] will not fit into [src] because [usr.p_they()] [usr.p_have()] a slime latched onto [usr.p_their()] head.") - return - visible_message("[usr] starts climbing into the sleeper.") - if(do_after(usr, 20, target = usr)) - if(occupant) - to_chat(usr, "The sleeper is already occupied!") - return - usr.stop_pulling() - usr.forceMove(src) - occupant = usr - icon_state = "[base_icon]" - - for(var/obj/O in src) - qdel(O) - add_fingerprint(usr) - return - return - -/obj/machinery/sleeper/syndie - icon_state = "sleeper_s-open" - base_icon = "sleeper_s" - possible_chems = list("epinephrine", "ether", "salbutamol", "styptic_powder", "silver_sulfadiazine") - emergency_chems = list("epinephrine") - controls_inside = TRUE - - light_color = LIGHT_COLOR_DARKRED - -/obj/machinery/sleeper/syndie/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/sleeper/syndicate(null) - var/obj/item/stock_parts/matter_bin/B = new(null) - B.rating = initial_bin_rating - component_parts += B - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() - -#undef ADDICTION_SPEEDUP_TIME +#define ADDICTION_SPEEDUP_TIME 1500 // 2.5 minutes + +///////////// +// SLEEPER // +//////////// + +/obj/machinery/sleeper + name = "Sleeper" + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "sleeper-open" + var/base_icon = "sleeper" + density = 1 + anchored = 1 + dir = WEST + var/orient = "LEFT" // "RIGHT" changes the dir suffix to "-r" + var/mob/living/carbon/human/occupant = null + var/possible_chems = list("ephedrine", "salglu_solution", "salbutamol", "charcoal") + var/emergency_chems = list("ephedrine") // Desnowflaking + var/amounts = list(5, 10) + var/obj/item/reagent_containers/glass/beaker = null + var/filtering = 0 + var/max_chem + var/initial_bin_rating = 1 + var/min_health = -25 + var/controls_inside = FALSE + idle_power_usage = 1250 + active_power_usage = 2500 + + light_color = LIGHT_COLOR_CYAN + +/obj/machinery/sleeper/power_change() + ..() + if(!(stat & (BROKEN|NOPOWER))) + set_light(2) + else + set_light(0) + +/obj/machinery/sleeper/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/sleeper(null) + + // Customizable bin rating, used by the labor camp to stop people filling themselves with chemicals and escaping. + var/obj/item/stock_parts/matter_bin/B = new(null) + B.rating = initial_bin_rating + component_parts += B + + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + RefreshParts() + +/obj/machinery/sleeper/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/sleeper(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + RefreshParts() + +/obj/machinery/sleeper/RefreshParts() + var/E + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + E += B.rating + + max_chem = E * 20 + min_health = -E * 25 + +/obj/machinery/sleeper/Destroy() + for(var/mob/M in contents) + M.forceMove(get_turf(src)) + return ..() + +/obj/machinery/sleeper/relaymove(mob/user as mob) + if(user.incapacitated()) + return 0 //maybe they should be able to get out with cuffs, but whatever + go_out() + +/obj/machinery/sleeper/process() + if(filtering > 0) + if(beaker) + // To prevent runtimes from drawing blood from runtime, and to prevent getting IPC blood. + if(!istype(occupant) || !occupant.dna || (NO_BLOOD in occupant.dna.species.species_traits)) + filtering = 0 + return + + if(beaker.reagents.total_volume < beaker.reagents.maximum_volume) + occupant.transfer_blood_to(beaker, 1) + for(var/datum/reagent/x in occupant.reagents.reagent_list) + occupant.reagents.trans_to(beaker, 3) + occupant.transfer_blood_to(beaker, 1) + + if(occupant) + for(var/A in occupant.reagents.addiction_list) + var/datum/reagent/R = A + + var/addiction_removal_chance = 5 + if(world.timeofday > (R.last_addiction_dose + ADDICTION_SPEEDUP_TIME)) // 2.5 minutes + addiction_removal_chance = 10 + if(prob(addiction_removal_chance)) + to_chat(occupant, "You no longer feel reliant on [R.name]!") + occupant.reagents.addiction_list.Remove(R) + qdel(R) + + for(var/mob/M as mob in src) // makes sure that simple mobs don't get stuck inside a sleeper when they resist out of occupant's grasp + if(M == occupant) + continue + else + M.forceMove(loc) + + updateDialog() + return + + +/obj/machinery/sleeper/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/sleeper/attack_ghost(mob/user) + return attack_hand(user) + +/obj/machinery/sleeper/attack_hand(mob/user) + if(stat & (NOPOWER|BROKEN)) + return + + if(panel_open) + to_chat(user, "Close the maintenance panel first.") + return + + ui_interact(user) + +/obj/machinery/sleeper/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "sleeper.tmpl", "Sleeper", 550, 770) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/sleeper/ui_data(mob/user, datum/topic_state/state) + var/data[0] + data["hasOccupant"] = occupant ? 1 : 0 + var/occupantData[0] + var/crisis = 0 + if(occupant) + occupantData["name"] = occupant.name + occupantData["stat"] = occupant.stat + occupantData["health"] = occupant.health + occupantData["maxHealth"] = occupant.maxHealth + occupantData["minHealth"] = HEALTH_THRESHOLD_DEAD + occupantData["bruteLoss"] = occupant.getBruteLoss() + occupantData["oxyLoss"] = occupant.getOxyLoss() + occupantData["toxLoss"] = occupant.getToxLoss() + occupantData["fireLoss"] = occupant.getFireLoss() + occupantData["paralysis"] = occupant.paralysis + occupantData["hasBlood"] = 0 + occupantData["bodyTemperature"] = occupant.bodytemperature + occupantData["maxTemp"] = 1000 // If you get a burning vox armalis into the sleeper, congratulations + // Because we can put simple_animals in here, we need to do something tricky to get things working nice + occupantData["temperatureSuitability"] = 0 // 0 is the baseline + if(ishuman(occupant) && occupant.dna.species) + // I wanna do something where the bar gets bluer as the temperature gets lower + // For now, I'll just use the standard format for the temperature status + var/datum/species/sp = occupant.dna.species + if(occupant.bodytemperature < sp.cold_level_3) + occupantData["temperatureSuitability"] = -3 + else if(occupant.bodytemperature < sp.cold_level_2) + occupantData["temperatureSuitability"] = -2 + else if(occupant.bodytemperature < sp.cold_level_1) + occupantData["temperatureSuitability"] = -1 + else if(occupant.bodytemperature > sp.heat_level_3) + occupantData["temperatureSuitability"] = 3 + else if(occupant.bodytemperature > sp.heat_level_2) + occupantData["temperatureSuitability"] = 2 + else if(occupant.bodytemperature > sp.heat_level_1) + occupantData["temperatureSuitability"] = 1 + else if(istype(occupant, /mob/living/simple_animal)) + var/mob/living/simple_animal/silly = occupant + if(silly.bodytemperature < silly.minbodytemp) + occupantData["temperatureSuitability"] = -3 + else if(silly.bodytemperature > silly.maxbodytemp) + occupantData["temperatureSuitability"] = 3 + // Blast you, imperial measurement system + occupantData["btCelsius"] = occupant.bodytemperature - T0C + occupantData["btFaren"] = ((occupant.bodytemperature - T0C) * (9.0/5.0))+ 32 + + + crisis = (occupant.health < min_health) + // I'm not sure WHY you'd want to put a simple_animal in a sleeper, but precedent is precedent + // Runtime is aptly named, isn't she? + if(ishuman(occupant) && !(NO_BLOOD in occupant.dna.species.species_traits)) + occupantData["pulse"] = occupant.get_pulse(GETPULSE_TOOL) + occupantData["hasBlood"] = 1 + occupantData["bloodLevel"] = round(occupant.blood_volume) + occupantData["bloodMax"] = occupant.max_blood + occupantData["bloodPercent"] = round(100*(occupant.blood_volume/occupant.max_blood), 0.01) + + data["occupant"] = occupantData + data["maxchem"] = max_chem + data["minhealth"] = min_health + data["dialysis"] = filtering + if(beaker) + data["isBeakerLoaded"] = 1 + if(beaker.reagents) + data["beakerFreeSpace"] = round(beaker.reagents.maximum_volume - beaker.reagents.total_volume) + else + data["beakerFreeSpace"] = 0 + + var/chemicals[0] + for(var/re in possible_chems) + var/datum/reagent/temp = GLOB.chemical_reagents_list[re] + if(temp) + var/reagent_amount = 0 + var/pretty_amount + var/injectable = occupant ? 1 : 0 + var/overdosing = 0 + var/caution = 0 // To make things clear that you're coming close to an overdose + if(crisis && !(temp.id in emergency_chems)) + injectable = 0 + + if(occupant && occupant.reagents) + reagent_amount = occupant.reagents.get_reagent_amount(temp.id) + // If they're mashing the highest concentration, they get one warning + if(temp.overdose_threshold && reagent_amount + 10 > temp.overdose_threshold) + caution = 1 + if(temp.id in occupant.reagents.overdose_list()) + overdosing = 1 + + // Because I don't know how to do this on the nano side + pretty_amount = round(reagent_amount, 0.05) + + chemicals.Add(list(list("title" = temp.name, "id" = temp.id, "commands" = list("chemical" = temp.id), "occ_amount" = reagent_amount, "pretty_amount" = pretty_amount, "injectable" = injectable, "overdosing" = overdosing, "od_warning" = caution))) + data["chemicals"] = chemicals + return data + +/obj/machinery/sleeper/Topic(href, href_list) + if(!controls_inside && usr == occupant) + return 0 + + if(..()) + return 1 + + if(panel_open) + to_chat(usr, "Close the maintenance panel first.") + return 0 + + if((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon/ai))) + if(href_list["chemical"]) + if(occupant) + if(occupant.stat == DEAD) + to_chat(usr, "This person has no life for to preserve anymore. Take [occupant.p_them()] to a department capable of reanimating them.") + else if(occupant.health > min_health || (href_list["chemical"] in emergency_chems)) + inject_chemical(usr,href_list["chemical"],text2num(href_list["amount"])) + else + to_chat(usr, "This person is not in good enough condition for sleepers to be effective! Use another means of treatment, such as cryogenics!") + if(href_list["removebeaker"]) + remove_beaker() + if(href_list["togglefilter"]) + toggle_filter() + if(href_list["ejectify"]) + eject() + add_fingerprint(usr) + return 1 + +/obj/machinery/sleeper/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/glass)) + if(!beaker) + if(!user.drop_item()) + to_chat(user, "[I] is stuck to you!") + return + + beaker = I + I.forceMove(src) + user.visible_message("[user] adds \a [I] to [src]!", "You add \a [I] to [src]!") + return + + else + to_chat(user, "The sleeper has a beaker already.") + return + + if(exchange_parts(user, I)) + return + + if(istype(I, /obj/item/grab)) + var/obj/item/grab/G = I + if(panel_open) + to_chat(user, "Close the maintenance panel first.") + return + if(!ismob(G.affecting)) + return + if(occupant) + to_chat(user, "The sleeper is already occupied!") + return + if(G.affecting.has_buckled_mobs()) //mob attached to us + to_chat(user, "[G.affecting] will not fit into [src] because [G.affecting.p_they()] [G.affecting.p_have()] a slime latched onto [G.affecting.p_their()] head.") + return + + visible_message("[user] starts putting [G.affecting.name] into the sleeper.") + + if(do_after(user, 20, target = G.affecting)) + if(occupant) + to_chat(user, "The sleeper is already occupied!") + return + if(!G || !G.affecting) + return + var/mob/M = G.affecting + M.forceMove(src) + occupant = M + icon_state = "[base_icon]" + to_chat(M, "You feel cool air surround you. You go numb as your senses turn inward.") + add_fingerprint(user) + qdel(G) + return + + return ..() + + +/obj/machinery/sleeper/crowbar_act(mob/user, obj/item/I) + if(default_deconstruction_crowbar(user, I)) + return TRUE + +/obj/machinery/sleeper/screwdriver_act(mob/user, obj/item/I) + if(occupant) + to_chat(user, "The maintenance panel is locked.") + return TRUE + if(default_deconstruction_screwdriver(user, "[base_icon]-o", "[base_icon]-open", I)) + return TRUE + +/obj/machinery/sleeper/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(occupant) + to_chat(user, "The scanner is occupied.") + return + if(panel_open) + to_chat(user, "Close the maintenance panel first.") + return + if(dir == EAST) + orient = "LEFT" + setDir(WEST) + else + orient = "RIGHT" + setDir(EAST) + +/obj/machinery/sleeper/ex_act(severity) + if(filtering) + toggle_filter() + if(occupant) + occupant.ex_act(severity) + ..() + +/obj/machinery/sleeper/handle_atom_del(atom/A) + ..() + if(A == occupant) + occupant = null + updateUsrDialog() + update_icon() + if(A == beaker) + beaker = null + updateUsrDialog() + +/obj/machinery/sleeper/emp_act(severity) + if(filtering) + toggle_filter() + if(stat & (BROKEN|NOPOWER)) + ..(severity) + return + if(occupant) + go_out() + ..(severity) + +/obj/machinery/sleeper/narsie_act() + go_out() + new /obj/effect/gibspawner/generic(get_turf(loc)) //I REPLACE YOUR TECHNOLOGY WITH FLESH! + qdel(src) + +/obj/machinery/sleeper/proc/toggle_filter() + if(filtering) + filtering = 0 + else + filtering = 1 + +/obj/machinery/sleeper/proc/go_out() + if(filtering) + toggle_filter() + if(!occupant) + return + occupant.forceMove(loc) + occupant = null + icon_state = "[base_icon]-open" + // eject trash the occupant dropped + for(var/atom/movable/A in contents - component_parts - list(beaker)) + A.forceMove(loc) + +/obj/machinery/sleeper/proc/inject_chemical(mob/living/user as mob, chemical, amount) + if(!(chemical in possible_chems)) + to_chat(user, "The sleeper does not offer that chemical!") + return + + if(occupant) + if(occupant.reagents) + if(occupant.reagents.get_reagent_amount(chemical) + amount <= max_chem) + occupant.reagents.add_reagent(chemical, amount) + return + else + to_chat(user, "You can not inject any more of this chemical.") + return + else + to_chat(user, "The patient rejects the chemicals!") + return + else + to_chat(user, "There's no occupant in the sleeper!") + return + +/obj/machinery/sleeper/verb/eject() + set name = "Eject Sleeper" + set category = "Object" + set src in oview(1) + + if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other + return + + icon_state = "[base_icon]-open" + go_out() + add_fingerprint(usr) + return + +/obj/machinery/sleeper/verb/remove_beaker() + set name = "Remove Beaker" + set category = "Object" + set src in oview(1) + + if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other + return + + if(beaker) + filtering = 0 + beaker.forceMove(usr.loc) + beaker = null + add_fingerprint(usr) + return + +/obj/machinery/sleeper/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) + if(O.loc == user) //no you can't pull things out of your ass + return + if(user.incapacitated()) //are you cuffed, dying, lying, stunned or other + return + if(get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src)) // is the mob anchored, too far away from you, or are you too far away from the source + return + if(!ismob(O)) //humans only + return + if(istype(O, /mob/living/simple_animal) || istype(O, /mob/living/silicon)) //animals and robots dont fit + return + if(!ishuman(user) && !isrobot(user)) //No ghosts or mice putting people into the sleeper + return + if(user.loc==null) // just in case someone manages to get a closet into the blue light dimension, as unlikely as that seems + return + if(!istype(user.loc, /turf) || !istype(O.loc, /turf)) // are you in a container/closet/pod/etc? + return + if(panel_open) + to_chat(user, "Close the maintenance panel first.") + return + if(occupant) + to_chat(user, "The sleeper is already occupied!") + return + var/mob/living/L = O + if(!istype(L) || L.buckled) + return + if(L.abiotic()) + to_chat(user, "Subject cannot have abiotic items on.") + return + if(L.has_buckled_mobs()) //mob attached to us + to_chat(user, "[L] will not fit into [src] because [L.p_they()] [L.p_have()] a slime latched onto [L.p_their()] head.") + return + if(L == user) + visible_message("[user] starts climbing into the sleeper.") + else + visible_message("[user] starts putting [L.name] into the sleeper.") + + if(do_after(user, 20, target = L)) + if(occupant) + to_chat(user, "The sleeper is already occupied!") + return + if(!L) return + L.forceMove(src) + occupant = L + icon_state = "[base_icon]" + to_chat(L, "You feel cool air surround you. You go numb as your senses turn inward.") + add_fingerprint(user) + if(user.pulling == L) + user.stop_pulling() + return + return + +/obj/machinery/sleeper/AllowDrop() + return FALSE + +/obj/machinery/sleeper/verb/move_inside() + set name = "Enter Sleeper" + set category = "Object" + set src in oview(1) + if(usr.stat != 0 || !(ishuman(usr))) + return + if(occupant) + to_chat(usr, "The sleeper is already occupied!") + return + if(panel_open) + to_chat(usr, "Close the maintenance panel first.") + return + if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other + return + if(usr.has_buckled_mobs()) //mob attached to us + to_chat(usr, "[usr] will not fit into [src] because [usr.p_they()] [usr.p_have()] a slime latched onto [usr.p_their()] head.") + return + visible_message("[usr] starts climbing into the sleeper.") + if(do_after(usr, 20, target = usr)) + if(occupant) + to_chat(usr, "The sleeper is already occupied!") + return + usr.stop_pulling() + usr.forceMove(src) + occupant = usr + icon_state = "[base_icon]" + + for(var/obj/O in src) + qdel(O) + add_fingerprint(usr) + return + return + +/obj/machinery/sleeper/syndie + icon_state = "sleeper_s-open" + base_icon = "sleeper_s" + possible_chems = list("epinephrine", "ether", "salbutamol", "styptic_powder", "silver_sulfadiazine") + emergency_chems = list("epinephrine") + controls_inside = TRUE + + light_color = LIGHT_COLOR_DARKRED + +/obj/machinery/sleeper/syndie/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/sleeper/syndicate(null) + var/obj/item/stock_parts/matter_bin/B = new(null) + B.rating = initial_bin_rating + component_parts += B + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + RefreshParts() + +#undef ADDICTION_SPEEDUP_TIME diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm index edb12487a68..6b6f3087ad2 100644 --- a/code/game/machinery/adv_med.dm +++ b/code/game/machinery/adv_med.dm @@ -499,4 +499,4 @@ else dat += "[src] is empty." - return dat \ No newline at end of file + return dat diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm index eb06f55cc7b..eaa1120ab04 100644 --- a/code/game/machinery/ai_slipper.dm +++ b/code/game/machinery/ai_slipper.dm @@ -133,4 +133,4 @@ return if(uses >= 0) cooldown_on = FALSE - power_change() \ No newline at end of file + power_change() diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm index b7fdea3389e..a8fabc1fab7 100644 --- a/code/game/machinery/alarm.dm +++ b/code/game/machinery/alarm.dm @@ -1,1110 +1,1110 @@ -// A datum for dealing with threshold limit values -// used in /obj/machinery/alarm -/datum/tlv - var/min2 - var/min1 - var/max1 - var/max2 - -/datum/tlv/New(_min2 as num, _min1 as num, _max1 as num, _max2 as num) - min2 = _min2 - min1 = _min1 - max1 = _max1 - max2 = _max2 - -/datum/tlv/proc/get_danger_level(curval as num) - if(max2 >=0 && curval>max2) - return ATMOS_ALARM_DANGER - if(min2 >=0 && curval=0 && curval>max1) - return ATMOS_ALARM_WARNING - if(min1 >=0 && curval target_temperature + 2 || regulating_temperature) - //If it goes too far, we should adjust ourselves back before stopping. - if(!cur_tlv.get_danger_level(target_temperature)) - if(!regulating_temperature) - regulating_temperature = 1 - visible_message("\The [src] clicks as it starts [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\ - "You hear a click and a faint electronic hum.") - - if(target_temperature > MAX_TEMPERATURE) - target_temperature = MAX_TEMPERATURE - - if(target_temperature < MIN_TEMPERATURE) - target_temperature = MIN_TEMPERATURE - - var/datum/gas_mixture/gas = location.remove_air(0.25*environment.total_moles()) - var/heat_capacity = gas.heat_capacity() - var/energy_used = max( abs( heat_capacity*(gas.temperature - target_temperature) ), MAX_ENERGY_CHANGE) - - //Use power. Assuming that each power unit represents 1000 watts.... - use_power(energy_used/1000, ENVIRON) - - //We need to cool ourselves. - if(heat_capacity) - if(environment.temperature > target_temperature) - gas.temperature -= energy_used/heat_capacity - else - gas.temperature += energy_used/heat_capacity - - environment.merge(gas) - - if(abs(environment.temperature - target_temperature) <= 0.5) - regulating_temperature = 0 - visible_message("\The [src] clicks quietly as it stops [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\ - "You hear a click as a faint electronic humming stops.") - -/obj/machinery/alarm/update_icon() - if(wiresexposed) - icon_state = "alarmx" - return - if((stat & (NOPOWER|BROKEN)) || shorted) - icon_state = "alarmp" - return - - switch(max(danger_level, alarm_area.atmosalm-1)) - if(ATMOS_ALARM_NONE) - icon_state = "alarm0" - if(ATMOS_ALARM_WARNING) - icon_state = "alarm2" //yes, alarm2 is yellow alarm - if(ATMOS_ALARM_DANGER) - icon_state = "alarm1" - -/obj/machinery/alarm/proc/register_env_machine(var/m_id, var/device_type) - var/new_name - if(device_type=="AVP") - new_name = "[alarm_area.name] Vent Pump #[alarm_area.air_vent_names.len+1]" - alarm_area.air_vent_names[m_id] = new_name - else if(device_type=="AScr") - new_name = "[alarm_area.name] Air Scrubber #[alarm_area.air_scrub_names.len+1]" - alarm_area.air_scrub_names[m_id] = new_name - else - return - spawn (10) - send_signal(m_id, list("init" = new_name) ) - -/obj/machinery/alarm/proc/refresh_all() - for(var/id_tag in alarm_area.air_vent_names) - var/list/I = alarm_area.air_vent_info[id_tag] - if(I && I["timestamp"]+AALARM_REPORT_TIMEOUT/2 > world.time) - continue - send_signal(id_tag, list("status") ) - for(var/id_tag in alarm_area.air_scrub_names) - var/list/I = alarm_area.air_scrub_info[id_tag] - if(I && I["timestamp"]+AALARM_REPORT_TIMEOUT/2 > world.time) - continue - send_signal(id_tag, list("status") ) - -/obj/machinery/alarm/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_TO_AIRALARM) - -/obj/machinery/alarm/proc/send_signal(var/target, var/list/command)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise - if(!radio_connection) - return 0 - - var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal - signal.source = src - - signal.data = command - signal.data["tag"] = target - signal.data["sigtype"] = "command" - - radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM) -// to_chat(world, text("Signal [] Broadcasted to []", command, target)) - - return 1 - -/obj/machinery/alarm/proc/apply_mode() - switch(mode) - if(AALARM_MODE_SCRUBBING) - for(var/device_id in alarm_area.air_scrub_names) - send_signal(device_id, list( - "power"= 1, - "o2_scrub" = (preset==AALARM_PRESET_VOX), - "n2_scrub" = 0, - "co2_scrub"= 1, - "scrubbing"= 1, - "widenet"= 0, - )) - for(var/device_id in alarm_area.air_vent_names) - send_signal(device_id, list( - "power"= 1, - "checks"= 1, - "set_external_pressure"= ONE_ATMOSPHERE - )) - if(AALARM_MODE_CONTAMINATED) - for(var/device_id in alarm_area.air_scrub_names) - send_signal(device_id, list( - "power"= 1, - "co2_scrub"= 1, - "tox_scrub"= 1, - "n2o_scrub"= 1, - "scrubbing"= 1, - "widenet"= 1, - )) - for(var/device_id in alarm_area.air_vent_names) - send_signal(device_id, list( - "power"= 1, - "checks"= 1, - "set_external_pressure"= ONE_ATMOSPHERE - )) - if(AALARM_MODE_VENTING) - for(var/device_id in alarm_area.air_scrub_names) - send_signal(device_id, list( - "power"= 1, - "widenet"= 0, - "scrubbing"= 0 - )) - for(var/device_id in alarm_area.air_vent_names) - send_signal(device_id, list( - "power"= 1, - "checks"= 1, - "set_external_pressure" = ONE_ATMOSPHERE*2 - )) - if(AALARM_MODE_REFILL) - for(var/device_id in alarm_area.air_scrub_names) - send_signal(device_id, list( - "power"= 1, - "co2_scrub"= 1, - "tox_scrub"= 0, - "n2o_scrub"= 0, - "scrubbing"= 1, - "widenet"= 0, - )) - for(var/device_id in alarm_area.air_vent_names) - send_signal(device_id, list( - "power"= 1, - "checks"= 1, - "set_external_pressure" = ONE_ATMOSPHERE*3 - )) - if( - AALARM_MODE_PANIC, - AALARM_MODE_REPLACEMENT - ) - for(var/device_id in alarm_area.air_scrub_names) - send_signal(device_id, list( - "power"= 1, - "widenet"= 1, - "scrubbing"= 0 - )) - for(var/device_id in alarm_area.air_vent_names) - send_signal(device_id, list( - "power"= 0 - )) - if( - AALARM_MODE_SIPHON - ) - for(var/device_id in alarm_area.air_scrub_names) - send_signal(device_id, list( - "power"= 1, - "widenet"= 0, - "scrubbing"= 0 - )) - for(var/device_id in alarm_area.air_vent_names) - send_signal(device_id, list( - "power"= 0 - )) - - if(AALARM_MODE_OFF) - for(var/device_id in alarm_area.air_scrub_names) - send_signal(device_id, list( - "power"= 0 - )) - for(var/device_id in alarm_area.air_vent_names) - send_signal(device_id, list( - "power"= 0 - )) - if(AALARM_MODE_FLOOD) - for(var/device_id in alarm_area.air_scrub_names) - send_signal(device_id, list( - "power"=0 - )) - for(var/device_id in alarm_area.air_vent_names) - send_signal(device_id, list( - "power"= 1, - "checks"= 0, - )) - -/obj/machinery/alarm/proc/apply_danger_level(var/new_danger_level) - if(report_danger_level && alarm_area.atmosalert(new_danger_level, src)) - post_alert(new_danger_level) - - update_icon() - -/obj/machinery/alarm/proc/post_alert(alert_level) - var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency) - if(!frequency) - return - - var/datum/signal/alert_signal = new - alert_signal.source = src - alert_signal.transmission_method = 1 - alert_signal.data["zone"] = alarm_area.name - alert_signal.data["type"] = "Atmospheric" - - if(alert_level==2) - alert_signal.data["alert"] = "severe" - else if(alert_level==1) - alert_signal.data["alert"] = "minor" - else if(alert_level==0) - alert_signal.data["alert"] = "clear" - - frequency.post_signal(src, alert_signal) - -/////////////// -//END HACKING// -/////////////// - -/obj/machinery/alarm/attack_ai(mob/user) - src.add_hiddenprint(user) - return ui_interact(user) - -/obj/machinery/alarm/attack_ghost(user as mob) - return interact(user) - -/obj/machinery/alarm/attack_hand(mob/user) - . = ..() - if(.) - return - return interact(user) - -/obj/machinery/alarm/interact(mob/user) - if(buildstage != 2) - return - - if(wiresexposed) - wires.Interact(user) - - if(!shorted) - ui_interact(user) - -/obj/machinery/alarm/proc/ui_air_status() - var/turf/location = get_turf(src) - if(!istype(location)) - return - - var/datum/gas_mixture/environment = location.return_air() - var/total = environment.oxygen + environment.nitrogen + environment.carbon_dioxide + environment.toxins - if(total==0) - return null - - var/datum/tlv/cur_tlv - var/GET_PP = R_IDEAL_GAS_EQUATION*environment.temperature/environment.volume - - cur_tlv = TLV["pressure"] - var/environment_pressure = environment.return_pressure() - var/pressure_dangerlevel = cur_tlv.get_danger_level(environment_pressure) - - cur_tlv = TLV["oxygen"] - var/oxygen_dangerlevel = cur_tlv.get_danger_level(environment.oxygen*GET_PP) - var/oxygen_percent = round(environment.oxygen / total * 100, 2) - - cur_tlv = TLV["nitrogen"] - var/nitrogen_dangerlevel = cur_tlv.get_danger_level(environment.nitrogen*GET_PP) - var/nitrogen_percent = round(environment.nitrogen / total * 100, 2) - - cur_tlv = TLV["carbon dioxide"] - var/co2_dangerlevel = cur_tlv.get_danger_level(environment.carbon_dioxide*GET_PP) - var/co2_percent = round(environment.carbon_dioxide / total * 100, 2) - - cur_tlv = TLV["plasma"] - var/plasma_dangerlevel = cur_tlv.get_danger_level(environment.toxins*GET_PP) - var/plasma_percent = round(environment.toxins / total * 100, 2) - - cur_tlv = TLV["other"] - var/other_moles = 0.0 - for(var/datum/gas/G in environment.trace_gases) - other_moles+=G.moles - var/other_dangerlevel = cur_tlv.get_danger_level(other_moles*GET_PP) - - cur_tlv = TLV["temperature"] - var/temperature_dangerlevel = cur_tlv.get_danger_level(environment.temperature) - - var/data[0] - data["pressure"] = environment_pressure - data["temperature"] = environment.temperature - data["temperature_c"] = round(environment.temperature - T0C, 0.1) - - var/percentages[0] - percentages["oxygen"] = oxygen_percent - percentages["nitrogen"] = nitrogen_percent - percentages["co2"] = co2_percent - percentages["plasma"] = plasma_percent - percentages["other"] = other_moles - data["contents"] = percentages - - var/danger[0] - danger["pressure"] = pressure_dangerlevel - danger["temperature"] = temperature_dangerlevel - danger["oxygen"] = oxygen_dangerlevel - danger["nitrogen"] = nitrogen_dangerlevel - danger["co2"] = co2_dangerlevel - danger["plasma"] = plasma_dangerlevel - danger["other"] = other_dangerlevel - danger["overall"] = max(pressure_dangerlevel,oxygen_dangerlevel,nitrogen_dangerlevel,co2_dangerlevel,plasma_dangerlevel,other_dangerlevel,temperature_dangerlevel) - data["danger"] = danger - return data - -/obj/machinery/alarm/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - var/list/href_list = state.href_list(user) - if(href_list) - data["remote_connection"] = href_list["remote_connection"] - data["remote_access"] = href_list["remote_access"] - - data["name"] = sanitize(name) - data["air"] = ui_air_status() - data["alarmActivated"] = alarmActivated || danger_level == ATMOS_ALARM_DANGER - data["thresholds"] = generate_thresholds_menu() - - // Locked when: - // Not sent from atmos console AND - // Not silicon AND locked. - data["locked"] = !is_authenticated(user, href_list) - data["rcon"] = rcon_setting - data["target_temp"] = target_temperature - T0C - data["atmos_alarm"] = alarm_area.atmosalm - data["emagged"] = emagged - data["modes"] = list( - AALARM_MODE_SCRUBBING = list("name"="Filtering", "desc"="Scrubs out contaminants"),\ - AALARM_MODE_VENTING = list("name"="Draught", "desc"="Siphons out air while replacing"),\ - AALARM_MODE_PANIC = list("name"="Panic Siphon","desc"="Siphons air out of the room quickly"),\ - AALARM_MODE_REPLACEMENT = list("name"="Cycle", "desc"="Siphons air before replacing"),\ - AALARM_MODE_SIPHON = list("name"="Siphon", "desc"="Siphons air out of the room"),\ - AALARM_MODE_CONTAMINATED= list("name"="Contaminated","desc"="Scrubs out all contaminants quickly"),\ - AALARM_MODE_REFILL = list("name"="Refill", "desc"="Triples vent output"),\ - AALARM_MODE_OFF = list("name"="Off", "desc"="Shuts off vents and scrubbers"),\ - AALARM_MODE_FLOOD = list("name"="Flood", "desc"="Shuts off scrubbers and opens vents", "emagonly" = 1) - ) - data["mode"] = mode - data["presets"] = list( - AALARM_PRESET_HUMAN = list("name"="Human", "desc"="Checks for oxygen and nitrogen"),\ - AALARM_PRESET_VOX = list("name"="Vox", "desc"="Checks for nitrogen only"),\ - AALARM_PRESET_COLDROOM = list("name"="Coldroom", "desc"="For freezers"),\ - AALARM_PRESET_SERVER = list("name"="Server Room", "desc"="For server rooms") - ) - data["preset"] = preset - data["screen"] = screen - - var/list/vents=list() - if(alarm_area.air_vent_names.len) - for(var/id_tag in alarm_area.air_vent_names) - var/vent_info[0] - var/long_name = alarm_area.air_vent_names[id_tag] - var/list/vent_data = alarm_area.air_vent_info[id_tag] - if(!vent_data) - continue - vent_info["id_tag"]=id_tag - vent_info["name"]=sanitize(long_name) - vent_info += vent_data - vents+=list(vent_info) - data["vents"]=vents - - var/list/scrubbers=list() - if(alarm_area.air_scrub_names.len) - for(var/id_tag in alarm_area.air_scrub_names) - var/long_name = alarm_area.air_scrub_names[id_tag] - var/list/scrubber_data = alarm_area.air_scrub_info[id_tag] - if(!scrubber_data) - continue - scrubber_data["id_tag"]=id_tag - scrubber_data["name"]=sanitize(long_name) - scrubbers+=list(scrubber_data) - data["scrubbers"]=scrubbers - return data - -/obj/machinery/alarm/proc/get_nano_data_console(mob/user) - var/data[0] - data["name"] = sanitize(name) - data["ref"] = "\ref[src]" - data["danger"] = max(danger_level, alarm_area.atmosalm) - var/area/Area = get_area(src) - data["area"] = sanitize(Area.name) - var/turf/pos = get_turf(src) - data["x"] = pos.x - data["y"] = pos.y - data["z"] = pos.z - return data - -/obj/machinery/alarm/proc/generate_thresholds_menu() - var/datum/tlv/selected - var/list/thresholds = list() - - var/list/gas_names = list( - "oxygen" = "O2", - "nitrogen" = "N2", - "carbon dioxide" = "CO2", - "plasma" = "Toxin", - "other" = "Other") - for(var/g in gas_names) - thresholds += list(list("name" = gas_names[g], "settings" = list())) - selected = TLV[g] - thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = "min2", "selected" = selected.min2)) - thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = "min1", "selected" = selected.min1)) - thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = "max1", "selected" = selected.max1)) - thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = "max2", "selected" = selected.max2)) - - selected = TLV["pressure"] - thresholds += list(list("name" = "Pressure", "settings" = list())) - thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min2", "selected" = selected.min2)) - thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min1", "selected" = selected.min1)) - thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max1", "selected" = selected.max1)) - thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max2", "selected" = selected.max2)) - - selected = TLV["temperature"] - thresholds += list(list("name" = "Temperature", "settings" = list())) - thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min2", "selected" = selected.min2)) - thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min1", "selected" = selected.min1)) - thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max1", "selected" = selected.max1)) - thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max2", "selected" = selected.max2)) - - return thresholds - -/obj/machinery/alarm/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, var/master_ui = null, var/datum/topic_state/state = default_state) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "air_alarm.tmpl", name, 570, 410, state = state) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/alarm/proc/is_authenticated(var/mob/user, href_list) - if(user.can_admin_interact()) - return 1 - else if(isAI(user) || isrobot(user) || emagged || is_auth_rcon(href_list)) - return 1 - else - return !locked - -/obj/machinery/alarm/proc/is_auth_rcon(href_list) - if(href_list && href_list["remote_connection"] && href_list["remote_access"]) - return 1 - else - return 0 - -/obj/machinery/alarm/CanUseTopic(var/mob/user, var/datum/topic_state/state, var/href_list = list()) - if(buildstage != 2) - return STATUS_CLOSE - - if(aidisabled && (isAI(user) || isrobot(user))) - to_chat(user, "AI control for \the [src] interface has been disabled.") - return STATUS_CLOSE - - . = shorted ? STATUS_DISABLED : STATUS_INTERACTIVE - - if(. == STATUS_INTERACTIVE) - var/extra_href = state.href_list(usr) - // Prevent remote users from altering RCON settings or activating atmos alarms unless they already have access - if((href_list["atmos_alarm"] || href_list["rcon"]) && extra_href["remote_connection"] && !extra_href["remote_access"]) - . = STATUS_UPDATE - return min(..(), .) - -/obj/machinery/alarm/Topic(href, href_list, var/nowindow = 0, var/datum/topic_state/state) - if(..(href, href_list, nowindow, state)) - return 1 - - var/state_href = state.href_list(usr) - - if(href_list["rcon"]) - var/attempted_rcon_setting = text2num(href_list["rcon"]) - switch(attempted_rcon_setting) - if(RCON_NO) - rcon_setting = RCON_NO - if(RCON_AUTO) - rcon_setting = RCON_AUTO - if(RCON_YES) - rcon_setting = RCON_YES - return 1 - - add_fingerprint(usr) - - if(href_list["command"]) - if(!is_authenticated(usr, state_href)) - return - - var/device_id = href_list["id_tag"] - switch(href_list["command"]) - if( "power", - "adjust_external_pressure", - "set_external_pressure", - "checks", - "co2_scrub", - "tox_scrub", - "n2o_scrub", - "n2_scrub", - "o2_scrub", - "widenet", - "scrubbing", - "direction") - var/val - if(href_list["val"]) - val=text2num(href_list["val"]) - else - var/newval = input("Enter new value") as num|null - if(isnull(newval)) - return - if(href_list["command"]=="set_external_pressure") - if(newval>1000+ONE_ATMOSPHERE) - newval = 1000+ONE_ATMOSPHERE - if(newval<0) - newval = 0 - val = newval - - send_signal(device_id, list(href_list["command"] = val ) ) - waiting_on_device=device_id - - if("set_threshold") - var/env = href_list["env"] - var/varname = href_list["var"] - var/datum/tlv/tlv = TLV[env] - var/newval = input("Enter [varname] for [env]", "Alarm triggers", tlv.vars[varname]) as num|null - - if(isnull(newval) || ..(href, href_list, nowindow, state)) - return - if(newval<0) - tlv.vars[varname] = -1.0 - else if(env=="temperature" && newval>5000) - tlv.vars[varname] = 5000 - else if(env=="pressure" && newval>50*ONE_ATMOSPHERE) - tlv.vars[varname] = 50*ONE_ATMOSPHERE - else if(env!="temperature" && env!="pressure" && newval>200) - tlv.vars[varname] = 200 - else - newval = round(newval,0.01) - tlv.vars[varname] = newval - - if(href_list["screen"]) - if(!is_authenticated(usr, state_href)) - return - - screen = text2num(href_list["screen"]) - return 1 - - if(href_list["atmos_alarm"]) - if(alarm_area.atmosalert(ATMOS_ALARM_DANGER, src)) - apply_danger_level(ATMOS_ALARM_DANGER) - alarmActivated = 1 - update_icon() - return 1 - - if(href_list["atmos_reset"]) - if(alarm_area.atmosalert(ATMOS_ALARM_NONE, src, TRUE)) - apply_danger_level(ATMOS_ALARM_NONE) - alarmActivated = 0 - update_icon() - return 1 - - if(href_list["mode"]) - if(!is_authenticated(usr, state_href)) - return - - mode = text2num(href_list["mode"]) - apply_mode() - return 1 - - if(href_list["preset"]) - if(!is_authenticated(usr, state_href)) - return - - preset = text2num(href_list["preset"]) - apply_preset() - return 1 - - if(href_list["temperature"]) - var/datum/tlv/selected = TLV["temperature"] - var/max_temperature = selected.max1 >= 0 ? min(selected.max1, MAX_TEMPERATURE) : max(selected.max1, MAX_TEMPERATURE) - var/min_temperature = max(selected.min1, MIN_TEMPERATURE) - var/max_temperature_c = max_temperature - T0C - var/min_temperature_c = min_temperature - T0C - var/input_temperature = input("What temperature would you like the system to maintain? (Capped between [min_temperature_c]C and [max_temperature_c]C)", "Thermostat Controls") as num|null - if(isnull(input_temperature) || ..(href, href_list, nowindow, state)) - return - input_temperature = input_temperature + T0C - if(input_temperature > max_temperature || input_temperature < min_temperature) - to_chat(usr, "Temperature must be between [min_temperature_c]C and [max_temperature_c]C") - else - target_temperature = input_temperature - return 1 - -/obj/machinery/alarm/emag_act(mob/user) - if(!emagged) - src.emagged = 1 - if(user) - user.visible_message("Sparks fly out of the [src]!", "You emag the [src], disabling its safeties.") - playsound(src.loc, 'sound/effects/sparks4.ogg', 50, 1) - return - -/obj/machinery/alarm/attackby(obj/item/I, mob/user, params) - add_fingerprint(user) - - switch(buildstage) - if(2) - if(istype(I, /obj/item/card/id) || istype(I, /obj/item/pda))// trying to unlock the interface with an ID card - if(stat & (NOPOWER|BROKEN)) - to_chat(user, "It does nothing") - return - else - if(allowed(usr) && !wires.IsIndexCut(AALARM_WIRE_IDSCAN)) - locked = !locked - to_chat(user, "You [ locked ? "lock" : "unlock"] the Air Alarm interface.") - updateUsrDialog() - else - to_chat(user, "Access denied.") - return - - if(1) - if(iscoil(I)) - var/obj/item/stack/cable_coil/coil = I - if(coil.amount < 5) - to_chat(user, "You need more cable for this!") - return - - to_chat(user, "You wire \the [src]!") - playsound(get_turf(src), coil.usesound, 50, 1) - coil.amount -= 5 - if(!coil.amount) - qdel(coil) - - buildstage = 2 - update_icon() - first_run() - return - if(0) - if(istype(I, /obj/item/airalarm_electronics)) - to_chat(user, "You insert the circuit!") - playsound(get_turf(src), I.usesound, 50, 1) - qdel(I) - buildstage = 1 - update_icon() - return - return ..() - -/obj/machinery/alarm/crowbar_act(mob/user, obj/item/I) - if(buildstage != AIR_ALARM_BUILDING) - return - . = TRUE - if(!I.tool_start_check(user, 0)) - return - to_chat(user, "You start prying out the circuit.") - if(!I.use_tool(src, user, 20, volume = I.tool_volume)) - return - if(buildstage != AIR_ALARM_BUILDING) - return - to_chat(user, "You pry out the circuit!") - new /obj/item/airalarm_electronics(user.drop_location()) - buildstage = AIR_ALARM_FRAME - update_icon() - -/obj/machinery/alarm/multitool_act(mob/user, obj/item/I) - if(buildstage != AIR_ALARM_READY) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(wiresexposed) - attack_hand(user) - -/obj/machinery/alarm/screwdriver_act(mob/user, obj/item/I) - if(buildstage != AIR_ALARM_READY) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - wiresexposed = !wiresexposed - update_icon() - if(wiresexposed) - SCREWDRIVER_OPEN_PANEL_MESSAGE - else - SCREWDRIVER_CLOSE_PANEL_MESSAGE - -/obj/machinery/alarm/wirecutter_act(mob/user, obj/item/I) - if(buildstage != AIR_ALARM_READY) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(wires.wires_status == 31) // all wires cut - var/obj/item/stack/cable_coil/new_coil = new /obj/item/stack/cable_coil(user.drop_location()) - new_coil.amount = 5 - buildstage = AIR_ALARM_BUILDING - update_icon() - if(wiresexposed) - wires.Interact(user) - -/obj/machinery/alarm/wrench_act(mob/user, obj/item/I) - if(buildstage != AIR_ALARM_FRAME) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - new /obj/item/mounted/frame/alarm_frame(get_turf(user)) - WRENCH_UNANCHOR_WALL_MESSAGE - qdel(src) - -/obj/machinery/alarm/power_change() - if(powered(power_channel)) - stat &= ~NOPOWER - else - stat |= NOPOWER - spawn(rand(0,15)) - update_icon() - -/obj/machinery/alarm/obj_break(damage_flag) - ..() - update_icon() - -/obj/machinery/alarm/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/sheet/metal(loc, 2) - var/obj/item/I = new /obj/item/airalarm_electronics(loc) - if(!disassembled) - I.obj_integrity = I.max_integrity * 0.5 - new /obj/item/stack/cable_coil(loc, 3) - qdel(src) - -/obj/machinery/alarm/examine(mob/user) - . = ..() - if(buildstage < 2) - . += "It is not wired." - if(buildstage < 1) - . += "The circuit is missing." - -/obj/machinery/alarm/all_access - name = "all-access air alarm" - desc = "This particular atmos control unit appears to have no access restrictions." - locked = FALSE - req_access = null - req_one_access = null - -/* -AIR ALARM CIRCUIT -Just an object used in constructing air alarms -*/ -/obj/item/airalarm_electronics - name = "air alarm electronics" - icon = 'icons/obj/doors/door_assembly.dmi' - icon_state = "door_electronics" - desc = "Looks like a circuit. Probably is." - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=50, MAT_GLASS=50) - origin_tech = "engineering=2;programming=1" - toolspeed = 1 - usesound = 'sound/items/deconstruct.ogg' - - -#undef AIR_ALARM_FRAME -#undef AIR_ALARM_BUILDING -#undef AIR_ALARM_READY +// A datum for dealing with threshold limit values +// used in /obj/machinery/alarm +/datum/tlv + var/min2 + var/min1 + var/max1 + var/max2 + +/datum/tlv/New(_min2 as num, _min1 as num, _max1 as num, _max2 as num) + min2 = _min2 + min1 = _min1 + max1 = _max1 + max2 = _max2 + +/datum/tlv/proc/get_danger_level(curval as num) + if(max2 >=0 && curval>max2) + return ATMOS_ALARM_DANGER + if(min2 >=0 && curval=0 && curval>max1) + return ATMOS_ALARM_WARNING + if(min1 >=0 && curval target_temperature + 2 || regulating_temperature) + //If it goes too far, we should adjust ourselves back before stopping. + if(!cur_tlv.get_danger_level(target_temperature)) + if(!regulating_temperature) + regulating_temperature = 1 + visible_message("\The [src] clicks as it starts [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\ + "You hear a click and a faint electronic hum.") + + if(target_temperature > MAX_TEMPERATURE) + target_temperature = MAX_TEMPERATURE + + if(target_temperature < MIN_TEMPERATURE) + target_temperature = MIN_TEMPERATURE + + var/datum/gas_mixture/gas = location.remove_air(0.25*environment.total_moles()) + var/heat_capacity = gas.heat_capacity() + var/energy_used = max( abs( heat_capacity*(gas.temperature - target_temperature) ), MAX_ENERGY_CHANGE) + + //Use power. Assuming that each power unit represents 1000 watts.... + use_power(energy_used/1000, ENVIRON) + + //We need to cool ourselves. + if(heat_capacity) + if(environment.temperature > target_temperature) + gas.temperature -= energy_used/heat_capacity + else + gas.temperature += energy_used/heat_capacity + + environment.merge(gas) + + if(abs(environment.temperature - target_temperature) <= 0.5) + regulating_temperature = 0 + visible_message("\The [src] clicks quietly as it stops [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\ + "You hear a click as a faint electronic humming stops.") + +/obj/machinery/alarm/update_icon() + if(wiresexposed) + icon_state = "alarmx" + return + if((stat & (NOPOWER|BROKEN)) || shorted) + icon_state = "alarmp" + return + + switch(max(danger_level, alarm_area.atmosalm-1)) + if(ATMOS_ALARM_NONE) + icon_state = "alarm0" + if(ATMOS_ALARM_WARNING) + icon_state = "alarm2" //yes, alarm2 is yellow alarm + if(ATMOS_ALARM_DANGER) + icon_state = "alarm1" + +/obj/machinery/alarm/proc/register_env_machine(var/m_id, var/device_type) + var/new_name + if(device_type=="AVP") + new_name = "[alarm_area.name] Vent Pump #[alarm_area.air_vent_names.len+1]" + alarm_area.air_vent_names[m_id] = new_name + else if(device_type=="AScr") + new_name = "[alarm_area.name] Air Scrubber #[alarm_area.air_scrub_names.len+1]" + alarm_area.air_scrub_names[m_id] = new_name + else + return + spawn (10) + send_signal(m_id, list("init" = new_name) ) + +/obj/machinery/alarm/proc/refresh_all() + for(var/id_tag in alarm_area.air_vent_names) + var/list/I = alarm_area.air_vent_info[id_tag] + if(I && I["timestamp"]+AALARM_REPORT_TIMEOUT/2 > world.time) + continue + send_signal(id_tag, list("status") ) + for(var/id_tag in alarm_area.air_scrub_names) + var/list/I = alarm_area.air_scrub_info[id_tag] + if(I && I["timestamp"]+AALARM_REPORT_TIMEOUT/2 > world.time) + continue + send_signal(id_tag, list("status") ) + +/obj/machinery/alarm/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_TO_AIRALARM) + +/obj/machinery/alarm/proc/send_signal(var/target, var/list/command)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise + if(!radio_connection) + return 0 + + var/datum/signal/signal = new + signal.transmission_method = 1 //radio signal + signal.source = src + + signal.data = command + signal.data["tag"] = target + signal.data["sigtype"] = "command" + + radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM) +// to_chat(world, text("Signal [] Broadcasted to []", command, target)) + + return 1 + +/obj/machinery/alarm/proc/apply_mode() + switch(mode) + if(AALARM_MODE_SCRUBBING) + for(var/device_id in alarm_area.air_scrub_names) + send_signal(device_id, list( + "power"= 1, + "o2_scrub" = (preset==AALARM_PRESET_VOX), + "n2_scrub" = 0, + "co2_scrub"= 1, + "scrubbing"= 1, + "widenet"= 0, + )) + for(var/device_id in alarm_area.air_vent_names) + send_signal(device_id, list( + "power"= 1, + "checks"= 1, + "set_external_pressure"= ONE_ATMOSPHERE + )) + if(AALARM_MODE_CONTAMINATED) + for(var/device_id in alarm_area.air_scrub_names) + send_signal(device_id, list( + "power"= 1, + "co2_scrub"= 1, + "tox_scrub"= 1, + "n2o_scrub"= 1, + "scrubbing"= 1, + "widenet"= 1, + )) + for(var/device_id in alarm_area.air_vent_names) + send_signal(device_id, list( + "power"= 1, + "checks"= 1, + "set_external_pressure"= ONE_ATMOSPHERE + )) + if(AALARM_MODE_VENTING) + for(var/device_id in alarm_area.air_scrub_names) + send_signal(device_id, list( + "power"= 1, + "widenet"= 0, + "scrubbing"= 0 + )) + for(var/device_id in alarm_area.air_vent_names) + send_signal(device_id, list( + "power"= 1, + "checks"= 1, + "set_external_pressure" = ONE_ATMOSPHERE*2 + )) + if(AALARM_MODE_REFILL) + for(var/device_id in alarm_area.air_scrub_names) + send_signal(device_id, list( + "power"= 1, + "co2_scrub"= 1, + "tox_scrub"= 0, + "n2o_scrub"= 0, + "scrubbing"= 1, + "widenet"= 0, + )) + for(var/device_id in alarm_area.air_vent_names) + send_signal(device_id, list( + "power"= 1, + "checks"= 1, + "set_external_pressure" = ONE_ATMOSPHERE*3 + )) + if( + AALARM_MODE_PANIC, + AALARM_MODE_REPLACEMENT + ) + for(var/device_id in alarm_area.air_scrub_names) + send_signal(device_id, list( + "power"= 1, + "widenet"= 1, + "scrubbing"= 0 + )) + for(var/device_id in alarm_area.air_vent_names) + send_signal(device_id, list( + "power"= 0 + )) + if( + AALARM_MODE_SIPHON + ) + for(var/device_id in alarm_area.air_scrub_names) + send_signal(device_id, list( + "power"= 1, + "widenet"= 0, + "scrubbing"= 0 + )) + for(var/device_id in alarm_area.air_vent_names) + send_signal(device_id, list( + "power"= 0 + )) + + if(AALARM_MODE_OFF) + for(var/device_id in alarm_area.air_scrub_names) + send_signal(device_id, list( + "power"= 0 + )) + for(var/device_id in alarm_area.air_vent_names) + send_signal(device_id, list( + "power"= 0 + )) + if(AALARM_MODE_FLOOD) + for(var/device_id in alarm_area.air_scrub_names) + send_signal(device_id, list( + "power"=0 + )) + for(var/device_id in alarm_area.air_vent_names) + send_signal(device_id, list( + "power"= 1, + "checks"= 0, + )) + +/obj/machinery/alarm/proc/apply_danger_level(var/new_danger_level) + if(report_danger_level && alarm_area.atmosalert(new_danger_level, src)) + post_alert(new_danger_level) + + update_icon() + +/obj/machinery/alarm/proc/post_alert(alert_level) + var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency) + if(!frequency) + return + + var/datum/signal/alert_signal = new + alert_signal.source = src + alert_signal.transmission_method = 1 + alert_signal.data["zone"] = alarm_area.name + alert_signal.data["type"] = "Atmospheric" + + if(alert_level==2) + alert_signal.data["alert"] = "severe" + else if(alert_level==1) + alert_signal.data["alert"] = "minor" + else if(alert_level==0) + alert_signal.data["alert"] = "clear" + + frequency.post_signal(src, alert_signal) + +/////////////// +//END HACKING// +/////////////// + +/obj/machinery/alarm/attack_ai(mob/user) + src.add_hiddenprint(user) + return ui_interact(user) + +/obj/machinery/alarm/attack_ghost(user as mob) + return interact(user) + +/obj/machinery/alarm/attack_hand(mob/user) + . = ..() + if(.) + return + return interact(user) + +/obj/machinery/alarm/interact(mob/user) + if(buildstage != 2) + return + + if(wiresexposed) + wires.Interact(user) + + if(!shorted) + ui_interact(user) + +/obj/machinery/alarm/proc/ui_air_status() + var/turf/location = get_turf(src) + if(!istype(location)) + return + + var/datum/gas_mixture/environment = location.return_air() + var/total = environment.oxygen + environment.nitrogen + environment.carbon_dioxide + environment.toxins + if(total==0) + return null + + var/datum/tlv/cur_tlv + var/GET_PP = R_IDEAL_GAS_EQUATION*environment.temperature/environment.volume + + cur_tlv = TLV["pressure"] + var/environment_pressure = environment.return_pressure() + var/pressure_dangerlevel = cur_tlv.get_danger_level(environment_pressure) + + cur_tlv = TLV["oxygen"] + var/oxygen_dangerlevel = cur_tlv.get_danger_level(environment.oxygen*GET_PP) + var/oxygen_percent = round(environment.oxygen / total * 100, 2) + + cur_tlv = TLV["nitrogen"] + var/nitrogen_dangerlevel = cur_tlv.get_danger_level(environment.nitrogen*GET_PP) + var/nitrogen_percent = round(environment.nitrogen / total * 100, 2) + + cur_tlv = TLV["carbon dioxide"] + var/co2_dangerlevel = cur_tlv.get_danger_level(environment.carbon_dioxide*GET_PP) + var/co2_percent = round(environment.carbon_dioxide / total * 100, 2) + + cur_tlv = TLV["plasma"] + var/plasma_dangerlevel = cur_tlv.get_danger_level(environment.toxins*GET_PP) + var/plasma_percent = round(environment.toxins / total * 100, 2) + + cur_tlv = TLV["other"] + var/other_moles = 0.0 + for(var/datum/gas/G in environment.trace_gases) + other_moles+=G.moles + var/other_dangerlevel = cur_tlv.get_danger_level(other_moles*GET_PP) + + cur_tlv = TLV["temperature"] + var/temperature_dangerlevel = cur_tlv.get_danger_level(environment.temperature) + + var/data[0] + data["pressure"] = environment_pressure + data["temperature"] = environment.temperature + data["temperature_c"] = round(environment.temperature - T0C, 0.1) + + var/percentages[0] + percentages["oxygen"] = oxygen_percent + percentages["nitrogen"] = nitrogen_percent + percentages["co2"] = co2_percent + percentages["plasma"] = plasma_percent + percentages["other"] = other_moles + data["contents"] = percentages + + var/danger[0] + danger["pressure"] = pressure_dangerlevel + danger["temperature"] = temperature_dangerlevel + danger["oxygen"] = oxygen_dangerlevel + danger["nitrogen"] = nitrogen_dangerlevel + danger["co2"] = co2_dangerlevel + danger["plasma"] = plasma_dangerlevel + danger["other"] = other_dangerlevel + danger["overall"] = max(pressure_dangerlevel,oxygen_dangerlevel,nitrogen_dangerlevel,co2_dangerlevel,plasma_dangerlevel,other_dangerlevel,temperature_dangerlevel) + data["danger"] = danger + return data + +/obj/machinery/alarm/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + var/list/href_list = state.href_list(user) + if(href_list) + data["remote_connection"] = href_list["remote_connection"] + data["remote_access"] = href_list["remote_access"] + + data["name"] = sanitize(name) + data["air"] = ui_air_status() + data["alarmActivated"] = alarmActivated || danger_level == ATMOS_ALARM_DANGER + data["thresholds"] = generate_thresholds_menu() + + // Locked when: + // Not sent from atmos console AND + // Not silicon AND locked. + data["locked"] = !is_authenticated(user, href_list) + data["rcon"] = rcon_setting + data["target_temp"] = target_temperature - T0C + data["atmos_alarm"] = alarm_area.atmosalm + data["emagged"] = emagged + data["modes"] = list( + AALARM_MODE_SCRUBBING = list("name"="Filtering", "desc"="Scrubs out contaminants"),\ + AALARM_MODE_VENTING = list("name"="Draught", "desc"="Siphons out air while replacing"),\ + AALARM_MODE_PANIC = list("name"="Panic Siphon","desc"="Siphons air out of the room quickly"),\ + AALARM_MODE_REPLACEMENT = list("name"="Cycle", "desc"="Siphons air before replacing"),\ + AALARM_MODE_SIPHON = list("name"="Siphon", "desc"="Siphons air out of the room"),\ + AALARM_MODE_CONTAMINATED= list("name"="Contaminated","desc"="Scrubs out all contaminants quickly"),\ + AALARM_MODE_REFILL = list("name"="Refill", "desc"="Triples vent output"),\ + AALARM_MODE_OFF = list("name"="Off", "desc"="Shuts off vents and scrubbers"),\ + AALARM_MODE_FLOOD = list("name"="Flood", "desc"="Shuts off scrubbers and opens vents", "emagonly" = 1) + ) + data["mode"] = mode + data["presets"] = list( + AALARM_PRESET_HUMAN = list("name"="Human", "desc"="Checks for oxygen and nitrogen"),\ + AALARM_PRESET_VOX = list("name"="Vox", "desc"="Checks for nitrogen only"),\ + AALARM_PRESET_COLDROOM = list("name"="Coldroom", "desc"="For freezers"),\ + AALARM_PRESET_SERVER = list("name"="Server Room", "desc"="For server rooms") + ) + data["preset"] = preset + data["screen"] = screen + + var/list/vents=list() + if(alarm_area.air_vent_names.len) + for(var/id_tag in alarm_area.air_vent_names) + var/vent_info[0] + var/long_name = alarm_area.air_vent_names[id_tag] + var/list/vent_data = alarm_area.air_vent_info[id_tag] + if(!vent_data) + continue + vent_info["id_tag"]=id_tag + vent_info["name"]=sanitize(long_name) + vent_info += vent_data + vents+=list(vent_info) + data["vents"]=vents + + var/list/scrubbers=list() + if(alarm_area.air_scrub_names.len) + for(var/id_tag in alarm_area.air_scrub_names) + var/long_name = alarm_area.air_scrub_names[id_tag] + var/list/scrubber_data = alarm_area.air_scrub_info[id_tag] + if(!scrubber_data) + continue + scrubber_data["id_tag"]=id_tag + scrubber_data["name"]=sanitize(long_name) + scrubbers+=list(scrubber_data) + data["scrubbers"]=scrubbers + return data + +/obj/machinery/alarm/proc/get_nano_data_console(mob/user) + var/data[0] + data["name"] = sanitize(name) + data["ref"] = "\ref[src]" + data["danger"] = max(danger_level, alarm_area.atmosalm) + var/area/Area = get_area(src) + data["area"] = sanitize(Area.name) + var/turf/pos = get_turf(src) + data["x"] = pos.x + data["y"] = pos.y + data["z"] = pos.z + return data + +/obj/machinery/alarm/proc/generate_thresholds_menu() + var/datum/tlv/selected + var/list/thresholds = list() + + var/list/gas_names = list( + "oxygen" = "O2", + "nitrogen" = "N2", + "carbon dioxide" = "CO2", + "plasma" = "Toxin", + "other" = "Other") + for(var/g in gas_names) + thresholds += list(list("name" = gas_names[g], "settings" = list())) + selected = TLV[g] + thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = "min2", "selected" = selected.min2)) + thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = "min1", "selected" = selected.min1)) + thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = "max1", "selected" = selected.max1)) + thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = "max2", "selected" = selected.max2)) + + selected = TLV["pressure"] + thresholds += list(list("name" = "Pressure", "settings" = list())) + thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min2", "selected" = selected.min2)) + thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min1", "selected" = selected.min1)) + thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max1", "selected" = selected.max1)) + thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max2", "selected" = selected.max2)) + + selected = TLV["temperature"] + thresholds += list(list("name" = "Temperature", "settings" = list())) + thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min2", "selected" = selected.min2)) + thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min1", "selected" = selected.min1)) + thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max1", "selected" = selected.max1)) + thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max2", "selected" = selected.max2)) + + return thresholds + +/obj/machinery/alarm/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, var/master_ui = null, var/datum/topic_state/state = default_state) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "air_alarm.tmpl", name, 570, 410, state = state) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/alarm/proc/is_authenticated(var/mob/user, href_list) + if(user.can_admin_interact()) + return 1 + else if(isAI(user) || isrobot(user) || emagged || is_auth_rcon(href_list)) + return 1 + else + return !locked + +/obj/machinery/alarm/proc/is_auth_rcon(href_list) + if(href_list && href_list["remote_connection"] && href_list["remote_access"]) + return 1 + else + return 0 + +/obj/machinery/alarm/CanUseTopic(var/mob/user, var/datum/topic_state/state, var/href_list = list()) + if(buildstage != 2) + return STATUS_CLOSE + + if(aidisabled && (isAI(user) || isrobot(user))) + to_chat(user, "AI control for \the [src] interface has been disabled.") + return STATUS_CLOSE + + . = shorted ? STATUS_DISABLED : STATUS_INTERACTIVE + + if(. == STATUS_INTERACTIVE) + var/extra_href = state.href_list(usr) + // Prevent remote users from altering RCON settings or activating atmos alarms unless they already have access + if((href_list["atmos_alarm"] || href_list["rcon"]) && extra_href["remote_connection"] && !extra_href["remote_access"]) + . = STATUS_UPDATE + return min(..(), .) + +/obj/machinery/alarm/Topic(href, href_list, var/nowindow = 0, var/datum/topic_state/state) + if(..(href, href_list, nowindow, state)) + return 1 + + var/state_href = state.href_list(usr) + + if(href_list["rcon"]) + var/attempted_rcon_setting = text2num(href_list["rcon"]) + switch(attempted_rcon_setting) + if(RCON_NO) + rcon_setting = RCON_NO + if(RCON_AUTO) + rcon_setting = RCON_AUTO + if(RCON_YES) + rcon_setting = RCON_YES + return 1 + + add_fingerprint(usr) + + if(href_list["command"]) + if(!is_authenticated(usr, state_href)) + return + + var/device_id = href_list["id_tag"] + switch(href_list["command"]) + if( "power", + "adjust_external_pressure", + "set_external_pressure", + "checks", + "co2_scrub", + "tox_scrub", + "n2o_scrub", + "n2_scrub", + "o2_scrub", + "widenet", + "scrubbing", + "direction") + var/val + if(href_list["val"]) + val=text2num(href_list["val"]) + else + var/newval = input("Enter new value") as num|null + if(isnull(newval)) + return + if(href_list["command"]=="set_external_pressure") + if(newval>1000+ONE_ATMOSPHERE) + newval = 1000+ONE_ATMOSPHERE + if(newval<0) + newval = 0 + val = newval + + send_signal(device_id, list(href_list["command"] = val ) ) + waiting_on_device=device_id + + if("set_threshold") + var/env = href_list["env"] + var/varname = href_list["var"] + var/datum/tlv/tlv = TLV[env] + var/newval = input("Enter [varname] for [env]", "Alarm triggers", tlv.vars[varname]) as num|null + + if(isnull(newval) || ..(href, href_list, nowindow, state)) + return + if(newval<0) + tlv.vars[varname] = -1.0 + else if(env=="temperature" && newval>5000) + tlv.vars[varname] = 5000 + else if(env=="pressure" && newval>50*ONE_ATMOSPHERE) + tlv.vars[varname] = 50*ONE_ATMOSPHERE + else if(env!="temperature" && env!="pressure" && newval>200) + tlv.vars[varname] = 200 + else + newval = round(newval,0.01) + tlv.vars[varname] = newval + + if(href_list["screen"]) + if(!is_authenticated(usr, state_href)) + return + + screen = text2num(href_list["screen"]) + return 1 + + if(href_list["atmos_alarm"]) + if(alarm_area.atmosalert(ATMOS_ALARM_DANGER, src)) + apply_danger_level(ATMOS_ALARM_DANGER) + alarmActivated = 1 + update_icon() + return 1 + + if(href_list["atmos_reset"]) + if(alarm_area.atmosalert(ATMOS_ALARM_NONE, src, TRUE)) + apply_danger_level(ATMOS_ALARM_NONE) + alarmActivated = 0 + update_icon() + return 1 + + if(href_list["mode"]) + if(!is_authenticated(usr, state_href)) + return + + mode = text2num(href_list["mode"]) + apply_mode() + return 1 + + if(href_list["preset"]) + if(!is_authenticated(usr, state_href)) + return + + preset = text2num(href_list["preset"]) + apply_preset() + return 1 + + if(href_list["temperature"]) + var/datum/tlv/selected = TLV["temperature"] + var/max_temperature = selected.max1 >= 0 ? min(selected.max1, MAX_TEMPERATURE) : max(selected.max1, MAX_TEMPERATURE) + var/min_temperature = max(selected.min1, MIN_TEMPERATURE) + var/max_temperature_c = max_temperature - T0C + var/min_temperature_c = min_temperature - T0C + var/input_temperature = input("What temperature would you like the system to maintain? (Capped between [min_temperature_c]C and [max_temperature_c]C)", "Thermostat Controls") as num|null + if(isnull(input_temperature) || ..(href, href_list, nowindow, state)) + return + input_temperature = input_temperature + T0C + if(input_temperature > max_temperature || input_temperature < min_temperature) + to_chat(usr, "Temperature must be between [min_temperature_c]C and [max_temperature_c]C") + else + target_temperature = input_temperature + return 1 + +/obj/machinery/alarm/emag_act(mob/user) + if(!emagged) + src.emagged = 1 + if(user) + user.visible_message("Sparks fly out of the [src]!", "You emag the [src], disabling its safeties.") + playsound(src.loc, 'sound/effects/sparks4.ogg', 50, 1) + return + +/obj/machinery/alarm/attackby(obj/item/I, mob/user, params) + add_fingerprint(user) + + switch(buildstage) + if(2) + if(istype(I, /obj/item/card/id) || istype(I, /obj/item/pda))// trying to unlock the interface with an ID card + if(stat & (NOPOWER|BROKEN)) + to_chat(user, "It does nothing") + return + else + if(allowed(usr) && !wires.IsIndexCut(AALARM_WIRE_IDSCAN)) + locked = !locked + to_chat(user, "You [ locked ? "lock" : "unlock"] the Air Alarm interface.") + updateUsrDialog() + else + to_chat(user, "Access denied.") + return + + if(1) + if(iscoil(I)) + var/obj/item/stack/cable_coil/coil = I + if(coil.amount < 5) + to_chat(user, "You need more cable for this!") + return + + to_chat(user, "You wire \the [src]!") + playsound(get_turf(src), coil.usesound, 50, 1) + coil.amount -= 5 + if(!coil.amount) + qdel(coil) + + buildstage = 2 + update_icon() + first_run() + return + if(0) + if(istype(I, /obj/item/airalarm_electronics)) + to_chat(user, "You insert the circuit!") + playsound(get_turf(src), I.usesound, 50, 1) + qdel(I) + buildstage = 1 + update_icon() + return + return ..() + +/obj/machinery/alarm/crowbar_act(mob/user, obj/item/I) + if(buildstage != AIR_ALARM_BUILDING) + return + . = TRUE + if(!I.tool_start_check(user, 0)) + return + to_chat(user, "You start prying out the circuit.") + if(!I.use_tool(src, user, 20, volume = I.tool_volume)) + return + if(buildstage != AIR_ALARM_BUILDING) + return + to_chat(user, "You pry out the circuit!") + new /obj/item/airalarm_electronics(user.drop_location()) + buildstage = AIR_ALARM_FRAME + update_icon() + +/obj/machinery/alarm/multitool_act(mob/user, obj/item/I) + if(buildstage != AIR_ALARM_READY) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(wiresexposed) + attack_hand(user) + +/obj/machinery/alarm/screwdriver_act(mob/user, obj/item/I) + if(buildstage != AIR_ALARM_READY) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + wiresexposed = !wiresexposed + update_icon() + if(wiresexposed) + SCREWDRIVER_OPEN_PANEL_MESSAGE + else + SCREWDRIVER_CLOSE_PANEL_MESSAGE + +/obj/machinery/alarm/wirecutter_act(mob/user, obj/item/I) + if(buildstage != AIR_ALARM_READY) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(wires.wires_status == 31) // all wires cut + var/obj/item/stack/cable_coil/new_coil = new /obj/item/stack/cable_coil(user.drop_location()) + new_coil.amount = 5 + buildstage = AIR_ALARM_BUILDING + update_icon() + if(wiresexposed) + wires.Interact(user) + +/obj/machinery/alarm/wrench_act(mob/user, obj/item/I) + if(buildstage != AIR_ALARM_FRAME) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + new /obj/item/mounted/frame/alarm_frame(get_turf(user)) + WRENCH_UNANCHOR_WALL_MESSAGE + qdel(src) + +/obj/machinery/alarm/power_change() + if(powered(power_channel)) + stat &= ~NOPOWER + else + stat |= NOPOWER + spawn(rand(0,15)) + update_icon() + +/obj/machinery/alarm/obj_break(damage_flag) + ..() + update_icon() + +/obj/machinery/alarm/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/sheet/metal(loc, 2) + var/obj/item/I = new /obj/item/airalarm_electronics(loc) + if(!disassembled) + I.obj_integrity = I.max_integrity * 0.5 + new /obj/item/stack/cable_coil(loc, 3) + qdel(src) + +/obj/machinery/alarm/examine(mob/user) + . = ..() + if(buildstage < 2) + . += "It is not wired." + if(buildstage < 1) + . += "The circuit is missing." + +/obj/machinery/alarm/all_access + name = "all-access air alarm" + desc = "This particular atmos control unit appears to have no access restrictions." + locked = FALSE + req_access = null + req_one_access = null + +/* +AIR ALARM CIRCUIT +Just an object used in constructing air alarms +*/ +/obj/item/airalarm_electronics + name = "air alarm electronics" + icon = 'icons/obj/doors/door_assembly.dmi' + icon_state = "door_electronics" + desc = "Looks like a circuit. Probably is." + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=50, MAT_GLASS=50) + origin_tech = "engineering=2;programming=1" + toolspeed = 1 + usesound = 'sound/items/deconstruct.ogg' + + +#undef AIR_ALARM_FRAME +#undef AIR_ALARM_BUILDING +#undef AIR_ALARM_READY diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm index 230401b673b..efcdada302d 100644 --- a/code/game/machinery/atmoalter/canister.dm +++ b/code/game/machinery/atmoalter/canister.dm @@ -1,606 +1,606 @@ -/datum/canister_icons - var - possiblemaincolor = list( //these lists contain the possible colors of a canister - list("name" = "\[N2O\]", "icon" = "redws"), - list("name" = "\[N2\]", "icon" = "red"), - list("name" = "\[O2\]", "icon" = "blue"), - list("name" = "\[Toxin (Bio)\]", "icon" = "orange"), - list("name" = "\[CO2\]", "icon" = "black"), - list("name" = "\[Air\]", "icon" = "grey"), - list("name" = "\[CAUTION\]", "icon" = "yellow"), - list("name" = "\[SPECIAL\]", "icon" = "whiters") - ) - possibleseccolor = list( // no point in having the N2O and "whiters" ones in these lists - list("name" = "\[N2\]", "icon" = "red-c"), - list("name" = "\[O2\]", "icon" = "blue-c"), - list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c"), - list("name" = "\[CO2\]", "icon" = "black-c"), - list("name" = "\[Air\]", "icon" = "grey-c"), - list("name" = "\[CAUTION\]", "icon" = "yellow-c") - ) - possibletertcolor = list( - list("name" = "\[N2\]", "icon" = "red-c-1"), - list("name" = "\[O2\]", "icon" = "blue-c-1"), - list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c-1"), - list("name" = "\[CO2\]", "icon" = "black-c-1"), - list("name" = "\[Air\]", "icon" = "grey-c-1"), - list("name" = "\[CAUTION\]", "icon" = "yellow-c-1") - ) - possiblequartcolor = list( - list("name" = "\[N2\]", "icon" = "red-c-2"), - list("name" = "\[O2\]", "icon" = "blue-c-2"), - list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c-2"), - list("name" = "\[CO2\]", "icon" = "black-c-2"), - list("name" = "\[Air\]", "icon" = "grey-c-2"), - list("name" = "\[CAUTION\]", "icon" = "yellow-c-2") - ) - - possibledecals = list( //var that stores all possible decals, used by ui - list("name" = "Low temperature canister", "icon" = "cold"), - list("name" = "High temperature canister", "icon" = "hot"), - list("name" = "Plasma containing canister", "icon" = "plasma") - ) - -var/datum/canister_icons/canister_icon_container = new() - -/obj/machinery/portable_atmospherics/canister - name = "canister" - icon = 'icons/obj/atmos.dmi' - icon_state = "yellow" - density = 1 - flags = CONDUCT - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 50) - max_integrity = 250 - integrity_failure = 100 - - var/menu = 0 - //used by nanoui: 0 = main menu, 1 = relabel - - var/valve_open = 0 - var/release_pressure = ONE_ATMOSPHERE - - var/list/canister_color //variable that stores colours - var/list/decals //list that stores the decals - - //lists for check_change() - var/list/oldcolor - var/list/olddecals - - //passed to the ui to render the color lists - var/list/colorcontainer - var/list/possibledecals - - var/can_label = 1 - var/filled = 0.5 - pressure_resistance = 7 * ONE_ATMOSPHERE - var/temperature_resistance = 1000 + T0C - volume = 1000 - use_power = NO_POWER_USE - interact_offline = 1 - var/release_log = "" - var/update_flag = 0 - -/obj/machinery/portable_atmospherics/canister/New() - ..() - canister_color = list( - "prim" = "yellow", - "sec" = "none", - "ter" = "none", - "quart" = "none") - oldcolor = new /list() - decals = list("cold" = 0, "hot" = 0, "plasma" = 0) - colorcontainer = list() - possibledecals = list() - update_icon() - -/obj/machinery/portable_atmospherics/canister/proc/init_data_vars() - //passed to the ui to render the color lists - colorcontainer = list( - "prim" = list( - "options" = canister_icon_container.possiblemaincolor, - "name" = "Primary color", - ), - "sec" = list( - "options" = canister_icon_container.possibleseccolor, - "name" = "Secondary color", - ), - "ter" = list( - "options" = canister_icon_container.possibletertcolor, - "name" = "Tertiary color", - ), - "quart" = list( - "options" = canister_icon_container.possiblequartcolor, - "name" = "Quaternary color", - ) - ) - - //var/anycolor used by the nanoUI, 0: no color applied. 1: color applied - for(var/C in colorcontainer) - if(C == "prim") continue - var/list/L = colorcontainer[C] - if(!(canister_color[C]) || (canister_color[C] == "none")) - L.Add(list("anycolor" = 0)) - else - L.Add(list("anycolor" = 1)) - colorcontainer[C] = L - - possibledecals = list() - - var/i - var/list/L = canister_icon_container.possibledecals - for(i=1;i<=L.len;i++) - var/list/LL = L[i] - LL = LL.Copy() //make sure we don't edit the datum list - LL.Add(list("active" = decals[LL["icon"]])) //"active" used by nanoUI - possibledecals.Add(LL) - -/obj/machinery/portable_atmospherics/canister/proc/check_change() - var/old_flag = update_flag - update_flag = 0 - if(holding) - update_flag |= 1 - if(connected_port) - update_flag |= 2 - - var/tank_pressure = air_contents.return_pressure() - if(tank_pressure < 10) - update_flag |= 4 - else if(tank_pressure < ONE_ATMOSPHERE) - update_flag |= 8 - else if(tank_pressure < 15*ONE_ATMOSPHERE) - update_flag |= 16 - else - update_flag |= 32 - - if(list2params(oldcolor) != list2params(canister_color)) - update_flag |= 64 - oldcolor = canister_color.Copy() - - if(list2params(olddecals) != list2params(decals)) - update_flag |= 128 - olddecals = decals.Copy() - - if(update_flag == old_flag) - return 1 - else - return 0 - -/obj/machinery/portable_atmospherics/canister/update_icon() -/* -update_flag -1 = holding -2 = connected_port -4 = tank_pressure < 10 -8 = tank_pressure < ONE_ATMOS -16 = tank_pressure < 15*ONE_ATMOS -32 = tank_pressure go boom. -64 = colors -128 = decals -(note: colors and decals has to be applied every icon update) -*/ - - if(src.destroyed) - src.overlays = 0 - src.icon_state = text("[]-1", src.canister_color["prim"])//yes, I KNOW the colours don't reflect when the can's borked, whatever. - return - - if(icon_state != src.canister_color["prim"]) - icon_state = src.canister_color["prim"] - - if(check_change()) //Returns 1 if no change needed to icons. - return - - overlays.Cut() - - for(var/C in canister_color) - if(C == "prim") continue - if(canister_color[C] == "none") continue - overlays.Add(canister_color[C]) - - for(var/D in decals) - if(decals[D]) - overlays.Add("decal-" + D) - - if(update_flag & 1) - overlays += "can-open" - if(update_flag & 2) - overlays += "can-connector" - if(update_flag & 4) - overlays += "can-o0" - if(update_flag & 8) - overlays += "can-o1" - else if(update_flag & 16) - overlays += "can-o2" - else if(update_flag & 32) - overlays += "can-o3" - - update_flag &= ~196 //the flags 128 and 64 represent change, not states. As such, we have to reset them to be able to detect a change on the next go. - return - -//template modification exploit prevention, used in Topic() -/obj/machinery/portable_atmospherics/canister/proc/is_a_color(var/inputVar, var/checkColor = "all") - if(checkColor == "prim" || checkColor == "all") - for(var/list/L in canister_icon_container.possiblemaincolor) - if(L["icon"] == inputVar) - return 1 - if(checkColor == "sec" || checkColor == "all") - for(var/list/L in canister_icon_container.possibleseccolor) - if(L["icon"] == inputVar) - return 1 - if(checkColor == "ter" || checkColor == "all") - for(var/list/L in canister_icon_container.possibletertcolor) - if(L["icon"] == inputVar) - return 1 - if(checkColor == "quart" || checkColor == "all") - for(var/list/L in canister_icon_container.possiblequartcolor) - if(L["icon"] == inputVar) - return 1 - return 0 - -/obj/machinery/portable_atmospherics/canister/proc/is_a_decal(var/inputVar) - for(var/list/L in canister_icon_container.possibledecals) - if(L["icon"] == inputVar) - return 1 - return 0 - -/obj/machinery/portable_atmospherics/canister/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > temperature_resistance) - take_damage(5, BURN, 0) - -/obj/machinery/portable_atmospherics/canister/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(!(stat & BROKEN)) - canister_break() - if(disassembled) - new /obj/item/stack/sheet/metal (loc, 10) - else - new /obj/item/stack/sheet/metal (loc, 5) - qdel(src) - -/obj/machinery/portable_atmospherics/canister/obj_break(damage_flag) - if((stat & BROKEN) || (flags & NODECONSTRUCT)) - return - canister_break() - -/obj/machinery/portable_atmospherics/canister/proc/canister_break() - disconnect() - var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles()) - var/turf/T = get_turf(src) - T.assume_air(expelled_gas) - air_update_turf() - - stat |= BROKEN - density = FALSE - playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) - update_icon() - - if(holding) - holding.forceMove(T) - holding = null - -/obj/machinery/portable_atmospherics/canister/process_atmos() - if(destroyed) - return - - ..() - - if(valve_open) - var/datum/gas_mixture/environment - if(holding) - environment = holding.air_contents - else - environment = loc.return_air() - - var/env_pressure = environment.return_pressure() - var/pressure_delta = min(release_pressure - env_pressure, (air_contents.return_pressure() - env_pressure)/2) - //Can not have a pressure delta that would cause environment pressure > tank pressure - - var/transfer_moles = 0 - if((air_contents.temperature > 0) && (pressure_delta > 0)) - transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION) - - //Actually transfer the gas - var/datum/gas_mixture/removed = air_contents.remove(transfer_moles) - - if(holding) - environment.merge(removed) - else - loc.assume_air(removed) - air_update_turf() - src.update_icon() - - - if(air_contents.return_pressure() < 1) - can_label = 1 - else - can_label = 0 - - src.updateDialog() - return - -/obj/machinery/portable_atmospherics/canister/return_air() - return air_contents - -/obj/machinery/portable_atmospherics/canister/proc/return_temperature() - var/datum/gas_mixture/GM = src.return_air() - if(GM && GM.volume>0) - return GM.temperature - return 0 - -/obj/machinery/portable_atmospherics/canister/proc/return_pressure() - var/datum/gas_mixture/GM = src.return_air() - if(GM && GM.volume>0) - return GM.return_pressure() - return 0 - -/obj/machinery/portable_atmospherics/canister/replace_tank(mob/living/user, close_valve) - . = ..() - if(.) - if(close_valve) - valve_open = FALSE - update_icon() - investigate_log("Valve was closed by [key_name(user)].
        ", "atmos") - else if(valve_open && holding) - investigate_log("[key_name(user)] started a transfer into [holding].
        ", "atmos") - -/obj/machinery/portable_atmospherics/canister/attack_ai(var/mob/user as mob) - src.add_hiddenprint(user) - return src.attack_hand(user) - -/obj/machinery/portable_atmospherics/canister/attack_ghost(var/mob/user as mob) - return src.ui_interact(user) - -/obj/machinery/portable_atmospherics/canister/attack_hand(var/mob/user as mob) - return src.ui_interact(user) - -/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = physical_state) - if(src.destroyed) - return - - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "canister.tmpl", "Canister", 480, 400, state = state) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - - -/obj/machinery/portable_atmospherics/canister/ui_data(mob/user, datum/topic_state/state) - init_data_vars() //set up var/colorcontainer and var/possibledecals - - // this is the data which will be sent to the ui - var/data[0] - data["name"] = name - data["menu"] = menu ? 1 : 0 - data["canLabel"] = can_label ? 1 : 0 - data["canister_color"] = canister_color - data["colorContainer"] = colorcontainer.Copy() - colorcontainer.Cut() - data["possibleDecals"] = possibledecals.Copy() - possibledecals.Cut() - data["portConnected"] = connected_port ? 1 : 0 - data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) - data["releasePressure"] = round(release_pressure ? release_pressure : 0) - data["minReleasePressure"] = round(ONE_ATMOSPHERE/10) - data["maxReleasePressure"] = round(10*ONE_ATMOSPHERE) - data["valveOpen"] = valve_open ? 1 : 0 - - data["hasHoldingTank"] = holding ? 1 : 0 - if(holding) - data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure())) - - return data - -/obj/machinery/portable_atmospherics/canister/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["choice"] == "menu") - menu = text2num(href_list["mode_target"]) - - if(href_list["toggle"]) - var/logmsg - if(valve_open) - if(holding) - logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the [holding]
        " - else - logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the air
        " - else - if(holding) - logmsg = "Valve was opened by [key_name(usr)], starting the transfer into the [holding]
        " - else - logmsg = "Valve was opened by [key_name(usr)], starting the transfer into the air
        " - if(air_contents.toxins > 0) - message_admins("[key_name_admin(usr)] opened a canister that contains plasma in [get_area(src)]! (JMP)") - log_admin("[key_name(usr)] opened a canister that contains plasma at [get_area(src)]: [x], [y], [z]") - var/datum/gas/sleeping_agent = locate(/datum/gas/sleeping_agent) in air_contents.trace_gases - if(sleeping_agent && (sleeping_agent.moles > 1)) - message_admins("[key_name_admin(usr)] opened a canister that contains N2O in [get_area(src)]! (JMP)") - log_admin("[key_name(usr)] opened a canister that contains N2O at [get_area(src)]: [x], [y], [z]") - investigate_log(logmsg, "atmos") - release_log += logmsg - valve_open = !valve_open - - if(href_list["remove_tank"]) - if(holding) - if(valve_open) - valve_open = 0 - release_log += "Valve was closed by [key_name(usr)], stopping the transfer into the [holding]
        " - holding.loc = loc - holding = null - - if(href_list["pressure_adj"]) - var/diff = text2num(href_list["pressure_adj"]) - if(diff > 0) - release_pressure = min(10*ONE_ATMOSPHERE, release_pressure+diff) - else - release_pressure = max(ONE_ATMOSPHERE/10, release_pressure+diff) - - if(href_list["rename"]) - if(can_label) - var/T = sanitize(copytext(input("Choose canister label", "Name", name) as text|null,1,MAX_NAME_LEN)) - if(can_label) //Exploit prevention - if(T) - name = T - else - name = "canister" - else - to_chat(usr, "As you attempted to rename it the pressure rose!") - - if(href_list["choice"] == "Primary color") - if(is_a_color(href_list["icon"],"prim")) - canister_color["prim"] = href_list["icon"] - if(href_list["choice"] == "Secondary color") - if(href_list["icon"] == "none") - canister_color["sec"] = "none" - else if(is_a_color(href_list["icon"],"sec")) - canister_color["sec"] = href_list["icon"] - if(href_list["choice"] == "Tertiary color") - if(href_list["icon"] == "none") - canister_color["ter"] = "none" - else if(is_a_color(href_list["icon"],"ter")) - canister_color["ter"] = href_list["icon"] - if(href_list["choice"] == "Quaternary color") - if(href_list["icon"] == "none") - canister_color["quart"] = "none" - else if(is_a_color(href_list["icon"],"quart")) - canister_color["quart"] = href_list["icon"] - - if(href_list["choice"] == "decals") - if(is_a_decal(href_list["icon"])) - decals[href_list["icon"]] = (decals[href_list["icon"]] == 0) - - src.add_fingerprint(usr) - update_icon() - - return 1 - - -/obj/machinery/portable_atmospherics/canister/toxins - name = "Canister \[Toxin (Plasma)\]" - icon_state = "orange" //See New() - can_label = 0 -/obj/machinery/portable_atmospherics/canister/oxygen - name = "Canister: \[O2\]" - icon_state = "blue" //See New() - can_label = 0 -/obj/machinery/portable_atmospherics/canister/sleeping_agent - name = "Canister: \[N2O\]" - icon_state = "redws" //See New() - can_label = 0 -/obj/machinery/portable_atmospherics/canister/nitrogen - name = "Canister: \[N2\]" - icon_state = "red" //See New() - can_label = 0 -/obj/machinery/portable_atmospherics/canister/carbon_dioxide - name = "Canister \[CO2\]" - icon_state = "black" //See New() - can_label = 0 -/obj/machinery/portable_atmospherics/canister/air - name = "Canister \[Air\]" - icon_state = "grey" //See New() - can_label = 0 -/obj/machinery/portable_atmospherics/canister/custom_mix - name = "Canister \[Custom\]" - icon_state = "whiters" //See New() - can_label = 0 - - -/obj/machinery/portable_atmospherics/canister/toxins/New() - ..() - - canister_color["prim"] = "orange" - decals["plasma"] = 1 - src.air_contents.toxins = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - - src.update_icon() - return 1 - -/obj/machinery/portable_atmospherics/canister/oxygen/New() - ..() - - canister_color["prim"] = "blue" - src.air_contents.oxygen = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - - src.update_icon() - return 1 - -/obj/machinery/portable_atmospherics/canister/sleeping_agent/New() - ..() - - canister_color["prim"] = "redws" - var/datum/gas/sleeping_agent/trace_gas = new - air_contents.trace_gases += trace_gas - trace_gas.moles = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - - src.update_icon() - return 1 - - -//Dirty way to fill room with gas. However it is a bit easier to do than creating some floor/engine/n2o -rastaf0 -/obj/machinery/portable_atmospherics/canister/sleeping_agent/roomfiller/New() - ..() - var/datum/gas/sleeping_agent/trace_gas = air_contents.trace_gases[1] - trace_gas.moles = 9*4000 - spawn(100) - var/turf/simulated/location = src.loc - if(istype(src.loc)) - while(!location.air) - sleep(1000) - location.assume_air(air_contents) - air_contents = new - return 1 - - -/obj/machinery/portable_atmospherics/canister/nitrogen/New() - ..() - - canister_color["prim"] = "red" - src.air_contents.nitrogen = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - - src.update_icon() - return 1 - -/obj/machinery/portable_atmospherics/canister/carbon_dioxide/New() - ..() - - canister_color["prim"] = "black" - src.air_contents.carbon_dioxide = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - - src.update_icon() - return 1 - - -/obj/machinery/portable_atmospherics/canister/air/New() - ..() - - canister_color["prim"] = "grey" - src.air_contents.oxygen = (O2STANDARD*src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - src.air_contents.nitrogen = (N2STANDARD*src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - - src.update_icon() - return 1 - -/obj/machinery/portable_atmospherics/canister/custom_mix/New() - ..() - - canister_color["prim"] = "whiters" - src.update_icon() // Otherwise new canisters do not have their icon updated with the pressure light, likely want to add this to the canister class constructor, avoiding at current time to refrain from screwing up code for other canisters. --DZD - return 1 - -/obj/machinery/portable_atmospherics/canister/welder_act(mob/user, obj/item/I) - if(!(stat & BROKEN)) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_SLICING_MESSAGE - if(I.use_tool(src, user, 50, volume = I.tool_volume)) - to_chat(user, "You salvage whats left of [src]!") - new /obj/item/stack/sheet/metal(drop_location(), 3) - qdel(src) +/datum/canister_icons + var + possiblemaincolor = list( //these lists contain the possible colors of a canister + list("name" = "\[N2O\]", "icon" = "redws"), + list("name" = "\[N2\]", "icon" = "red"), + list("name" = "\[O2\]", "icon" = "blue"), + list("name" = "\[Toxin (Bio)\]", "icon" = "orange"), + list("name" = "\[CO2\]", "icon" = "black"), + list("name" = "\[Air\]", "icon" = "grey"), + list("name" = "\[CAUTION\]", "icon" = "yellow"), + list("name" = "\[SPECIAL\]", "icon" = "whiters") + ) + possibleseccolor = list( // no point in having the N2O and "whiters" ones in these lists + list("name" = "\[N2\]", "icon" = "red-c"), + list("name" = "\[O2\]", "icon" = "blue-c"), + list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c"), + list("name" = "\[CO2\]", "icon" = "black-c"), + list("name" = "\[Air\]", "icon" = "grey-c"), + list("name" = "\[CAUTION\]", "icon" = "yellow-c") + ) + possibletertcolor = list( + list("name" = "\[N2\]", "icon" = "red-c-1"), + list("name" = "\[O2\]", "icon" = "blue-c-1"), + list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c-1"), + list("name" = "\[CO2\]", "icon" = "black-c-1"), + list("name" = "\[Air\]", "icon" = "grey-c-1"), + list("name" = "\[CAUTION\]", "icon" = "yellow-c-1") + ) + possiblequartcolor = list( + list("name" = "\[N2\]", "icon" = "red-c-2"), + list("name" = "\[O2\]", "icon" = "blue-c-2"), + list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c-2"), + list("name" = "\[CO2\]", "icon" = "black-c-2"), + list("name" = "\[Air\]", "icon" = "grey-c-2"), + list("name" = "\[CAUTION\]", "icon" = "yellow-c-2") + ) + + possibledecals = list( //var that stores all possible decals, used by ui + list("name" = "Low temperature canister", "icon" = "cold"), + list("name" = "High temperature canister", "icon" = "hot"), + list("name" = "Plasma containing canister", "icon" = "plasma") + ) + +var/datum/canister_icons/canister_icon_container = new() + +/obj/machinery/portable_atmospherics/canister + name = "canister" + icon = 'icons/obj/atmos.dmi' + icon_state = "yellow" + density = 1 + flags = CONDUCT + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 50) + max_integrity = 250 + integrity_failure = 100 + + var/menu = 0 + //used by nanoui: 0 = main menu, 1 = relabel + + var/valve_open = 0 + var/release_pressure = ONE_ATMOSPHERE + + var/list/canister_color //variable that stores colours + var/list/decals //list that stores the decals + + //lists for check_change() + var/list/oldcolor + var/list/olddecals + + //passed to the ui to render the color lists + var/list/colorcontainer + var/list/possibledecals + + var/can_label = 1 + var/filled = 0.5 + pressure_resistance = 7 * ONE_ATMOSPHERE + var/temperature_resistance = 1000 + T0C + volume = 1000 + use_power = NO_POWER_USE + interact_offline = 1 + var/release_log = "" + var/update_flag = 0 + +/obj/machinery/portable_atmospherics/canister/New() + ..() + canister_color = list( + "prim" = "yellow", + "sec" = "none", + "ter" = "none", + "quart" = "none") + oldcolor = new /list() + decals = list("cold" = 0, "hot" = 0, "plasma" = 0) + colorcontainer = list() + possibledecals = list() + update_icon() + +/obj/machinery/portable_atmospherics/canister/proc/init_data_vars() + //passed to the ui to render the color lists + colorcontainer = list( + "prim" = list( + "options" = canister_icon_container.possiblemaincolor, + "name" = "Primary color", + ), + "sec" = list( + "options" = canister_icon_container.possibleseccolor, + "name" = "Secondary color", + ), + "ter" = list( + "options" = canister_icon_container.possibletertcolor, + "name" = "Tertiary color", + ), + "quart" = list( + "options" = canister_icon_container.possiblequartcolor, + "name" = "Quaternary color", + ) + ) + + //var/anycolor used by the nanoUI, 0: no color applied. 1: color applied + for(var/C in colorcontainer) + if(C == "prim") continue + var/list/L = colorcontainer[C] + if(!(canister_color[C]) || (canister_color[C] == "none")) + L.Add(list("anycolor" = 0)) + else + L.Add(list("anycolor" = 1)) + colorcontainer[C] = L + + possibledecals = list() + + var/i + var/list/L = canister_icon_container.possibledecals + for(i=1;i<=L.len;i++) + var/list/LL = L[i] + LL = LL.Copy() //make sure we don't edit the datum list + LL.Add(list("active" = decals[LL["icon"]])) //"active" used by nanoUI + possibledecals.Add(LL) + +/obj/machinery/portable_atmospherics/canister/proc/check_change() + var/old_flag = update_flag + update_flag = 0 + if(holding) + update_flag |= 1 + if(connected_port) + update_flag |= 2 + + var/tank_pressure = air_contents.return_pressure() + if(tank_pressure < 10) + update_flag |= 4 + else if(tank_pressure < ONE_ATMOSPHERE) + update_flag |= 8 + else if(tank_pressure < 15*ONE_ATMOSPHERE) + update_flag |= 16 + else + update_flag |= 32 + + if(list2params(oldcolor) != list2params(canister_color)) + update_flag |= 64 + oldcolor = canister_color.Copy() + + if(list2params(olddecals) != list2params(decals)) + update_flag |= 128 + olddecals = decals.Copy() + + if(update_flag == old_flag) + return 1 + else + return 0 + +/obj/machinery/portable_atmospherics/canister/update_icon() +/* +update_flag +1 = holding +2 = connected_port +4 = tank_pressure < 10 +8 = tank_pressure < ONE_ATMOS +16 = tank_pressure < 15*ONE_ATMOS +32 = tank_pressure go boom. +64 = colors +128 = decals +(note: colors and decals has to be applied every icon update) +*/ + + if(src.destroyed) + src.overlays = 0 + src.icon_state = text("[]-1", src.canister_color["prim"])//yes, I KNOW the colours don't reflect when the can's borked, whatever. + return + + if(icon_state != src.canister_color["prim"]) + icon_state = src.canister_color["prim"] + + if(check_change()) //Returns 1 if no change needed to icons. + return + + overlays.Cut() + + for(var/C in canister_color) + if(C == "prim") continue + if(canister_color[C] == "none") continue + overlays.Add(canister_color[C]) + + for(var/D in decals) + if(decals[D]) + overlays.Add("decal-" + D) + + if(update_flag & 1) + overlays += "can-open" + if(update_flag & 2) + overlays += "can-connector" + if(update_flag & 4) + overlays += "can-o0" + if(update_flag & 8) + overlays += "can-o1" + else if(update_flag & 16) + overlays += "can-o2" + else if(update_flag & 32) + overlays += "can-o3" + + update_flag &= ~196 //the flags 128 and 64 represent change, not states. As such, we have to reset them to be able to detect a change on the next go. + return + +//template modification exploit prevention, used in Topic() +/obj/machinery/portable_atmospherics/canister/proc/is_a_color(var/inputVar, var/checkColor = "all") + if(checkColor == "prim" || checkColor == "all") + for(var/list/L in canister_icon_container.possiblemaincolor) + if(L["icon"] == inputVar) + return 1 + if(checkColor == "sec" || checkColor == "all") + for(var/list/L in canister_icon_container.possibleseccolor) + if(L["icon"] == inputVar) + return 1 + if(checkColor == "ter" || checkColor == "all") + for(var/list/L in canister_icon_container.possibletertcolor) + if(L["icon"] == inputVar) + return 1 + if(checkColor == "quart" || checkColor == "all") + for(var/list/L in canister_icon_container.possiblequartcolor) + if(L["icon"] == inputVar) + return 1 + return 0 + +/obj/machinery/portable_atmospherics/canister/proc/is_a_decal(var/inputVar) + for(var/list/L in canister_icon_container.possibledecals) + if(L["icon"] == inputVar) + return 1 + return 0 + +/obj/machinery/portable_atmospherics/canister/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > temperature_resistance) + take_damage(5, BURN, 0) + +/obj/machinery/portable_atmospherics/canister/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(!(stat & BROKEN)) + canister_break() + if(disassembled) + new /obj/item/stack/sheet/metal (loc, 10) + else + new /obj/item/stack/sheet/metal (loc, 5) + qdel(src) + +/obj/machinery/portable_atmospherics/canister/obj_break(damage_flag) + if((stat & BROKEN) || (flags & NODECONSTRUCT)) + return + canister_break() + +/obj/machinery/portable_atmospherics/canister/proc/canister_break() + disconnect() + var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles()) + var/turf/T = get_turf(src) + T.assume_air(expelled_gas) + air_update_turf() + + stat |= BROKEN + density = FALSE + playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) + update_icon() + + if(holding) + holding.forceMove(T) + holding = null + +/obj/machinery/portable_atmospherics/canister/process_atmos() + if(destroyed) + return + + ..() + + if(valve_open) + var/datum/gas_mixture/environment + if(holding) + environment = holding.air_contents + else + environment = loc.return_air() + + var/env_pressure = environment.return_pressure() + var/pressure_delta = min(release_pressure - env_pressure, (air_contents.return_pressure() - env_pressure)/2) + //Can not have a pressure delta that would cause environment pressure > tank pressure + + var/transfer_moles = 0 + if((air_contents.temperature > 0) && (pressure_delta > 0)) + transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION) + + //Actually transfer the gas + var/datum/gas_mixture/removed = air_contents.remove(transfer_moles) + + if(holding) + environment.merge(removed) + else + loc.assume_air(removed) + air_update_turf() + src.update_icon() + + + if(air_contents.return_pressure() < 1) + can_label = 1 + else + can_label = 0 + + src.updateDialog() + return + +/obj/machinery/portable_atmospherics/canister/return_air() + return air_contents + +/obj/machinery/portable_atmospherics/canister/proc/return_temperature() + var/datum/gas_mixture/GM = src.return_air() + if(GM && GM.volume>0) + return GM.temperature + return 0 + +/obj/machinery/portable_atmospherics/canister/proc/return_pressure() + var/datum/gas_mixture/GM = src.return_air() + if(GM && GM.volume>0) + return GM.return_pressure() + return 0 + +/obj/machinery/portable_atmospherics/canister/replace_tank(mob/living/user, close_valve) + . = ..() + if(.) + if(close_valve) + valve_open = FALSE + update_icon() + investigate_log("Valve was closed by [key_name(user)].
        ", "atmos") + else if(valve_open && holding) + investigate_log("[key_name(user)] started a transfer into [holding].
        ", "atmos") + +/obj/machinery/portable_atmospherics/canister/attack_ai(var/mob/user as mob) + src.add_hiddenprint(user) + return src.attack_hand(user) + +/obj/machinery/portable_atmospherics/canister/attack_ghost(var/mob/user as mob) + return src.ui_interact(user) + +/obj/machinery/portable_atmospherics/canister/attack_hand(var/mob/user as mob) + return src.ui_interact(user) + +/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = physical_state) + if(src.destroyed) + return + + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "canister.tmpl", "Canister", 480, 400, state = state) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + + +/obj/machinery/portable_atmospherics/canister/ui_data(mob/user, datum/topic_state/state) + init_data_vars() //set up var/colorcontainer and var/possibledecals + + // this is the data which will be sent to the ui + var/data[0] + data["name"] = name + data["menu"] = menu ? 1 : 0 + data["canLabel"] = can_label ? 1 : 0 + data["canister_color"] = canister_color + data["colorContainer"] = colorcontainer.Copy() + colorcontainer.Cut() + data["possibleDecals"] = possibledecals.Copy() + possibledecals.Cut() + data["portConnected"] = connected_port ? 1 : 0 + data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) + data["releasePressure"] = round(release_pressure ? release_pressure : 0) + data["minReleasePressure"] = round(ONE_ATMOSPHERE/10) + data["maxReleasePressure"] = round(10*ONE_ATMOSPHERE) + data["valveOpen"] = valve_open ? 1 : 0 + + data["hasHoldingTank"] = holding ? 1 : 0 + if(holding) + data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure())) + + return data + +/obj/machinery/portable_atmospherics/canister/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["choice"] == "menu") + menu = text2num(href_list["mode_target"]) + + if(href_list["toggle"]) + var/logmsg + if(valve_open) + if(holding) + logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the [holding]
        " + else + logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the air
        " + else + if(holding) + logmsg = "Valve was opened by [key_name(usr)], starting the transfer into the [holding]
        " + else + logmsg = "Valve was opened by [key_name(usr)], starting the transfer into the air
        " + if(air_contents.toxins > 0) + message_admins("[key_name_admin(usr)] opened a canister that contains plasma in [get_area(src)]! (JMP)") + log_admin("[key_name(usr)] opened a canister that contains plasma at [get_area(src)]: [x], [y], [z]") + var/datum/gas/sleeping_agent = locate(/datum/gas/sleeping_agent) in air_contents.trace_gases + if(sleeping_agent && (sleeping_agent.moles > 1)) + message_admins("[key_name_admin(usr)] opened a canister that contains N2O in [get_area(src)]! (JMP)") + log_admin("[key_name(usr)] opened a canister that contains N2O at [get_area(src)]: [x], [y], [z]") + investigate_log(logmsg, "atmos") + release_log += logmsg + valve_open = !valve_open + + if(href_list["remove_tank"]) + if(holding) + if(valve_open) + valve_open = 0 + release_log += "Valve was closed by [key_name(usr)], stopping the transfer into the [holding]
        " + holding.loc = loc + holding = null + + if(href_list["pressure_adj"]) + var/diff = text2num(href_list["pressure_adj"]) + if(diff > 0) + release_pressure = min(10*ONE_ATMOSPHERE, release_pressure+diff) + else + release_pressure = max(ONE_ATMOSPHERE/10, release_pressure+diff) + + if(href_list["rename"]) + if(can_label) + var/T = sanitize(copytext(input("Choose canister label", "Name", name) as text|null,1,MAX_NAME_LEN)) + if(can_label) //Exploit prevention + if(T) + name = T + else + name = "canister" + else + to_chat(usr, "As you attempted to rename it the pressure rose!") + + if(href_list["choice"] == "Primary color") + if(is_a_color(href_list["icon"],"prim")) + canister_color["prim"] = href_list["icon"] + if(href_list["choice"] == "Secondary color") + if(href_list["icon"] == "none") + canister_color["sec"] = "none" + else if(is_a_color(href_list["icon"],"sec")) + canister_color["sec"] = href_list["icon"] + if(href_list["choice"] == "Tertiary color") + if(href_list["icon"] == "none") + canister_color["ter"] = "none" + else if(is_a_color(href_list["icon"],"ter")) + canister_color["ter"] = href_list["icon"] + if(href_list["choice"] == "Quaternary color") + if(href_list["icon"] == "none") + canister_color["quart"] = "none" + else if(is_a_color(href_list["icon"],"quart")) + canister_color["quart"] = href_list["icon"] + + if(href_list["choice"] == "decals") + if(is_a_decal(href_list["icon"])) + decals[href_list["icon"]] = (decals[href_list["icon"]] == 0) + + src.add_fingerprint(usr) + update_icon() + + return 1 + + +/obj/machinery/portable_atmospherics/canister/toxins + name = "Canister \[Toxin (Plasma)\]" + icon_state = "orange" //See New() + can_label = 0 +/obj/machinery/portable_atmospherics/canister/oxygen + name = "Canister: \[O2\]" + icon_state = "blue" //See New() + can_label = 0 +/obj/machinery/portable_atmospherics/canister/sleeping_agent + name = "Canister: \[N2O\]" + icon_state = "redws" //See New() + can_label = 0 +/obj/machinery/portable_atmospherics/canister/nitrogen + name = "Canister: \[N2\]" + icon_state = "red" //See New() + can_label = 0 +/obj/machinery/portable_atmospherics/canister/carbon_dioxide + name = "Canister \[CO2\]" + icon_state = "black" //See New() + can_label = 0 +/obj/machinery/portable_atmospherics/canister/air + name = "Canister \[Air\]" + icon_state = "grey" //See New() + can_label = 0 +/obj/machinery/portable_atmospherics/canister/custom_mix + name = "Canister \[Custom\]" + icon_state = "whiters" //See New() + can_label = 0 + + +/obj/machinery/portable_atmospherics/canister/toxins/New() + ..() + + canister_color["prim"] = "orange" + decals["plasma"] = 1 + src.air_contents.toxins = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + + src.update_icon() + return 1 + +/obj/machinery/portable_atmospherics/canister/oxygen/New() + ..() + + canister_color["prim"] = "blue" + src.air_contents.oxygen = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + + src.update_icon() + return 1 + +/obj/machinery/portable_atmospherics/canister/sleeping_agent/New() + ..() + + canister_color["prim"] = "redws" + var/datum/gas/sleeping_agent/trace_gas = new + air_contents.trace_gases += trace_gas + trace_gas.moles = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + + src.update_icon() + return 1 + + +//Dirty way to fill room with gas. However it is a bit easier to do than creating some floor/engine/n2o -rastaf0 +/obj/machinery/portable_atmospherics/canister/sleeping_agent/roomfiller/New() + ..() + var/datum/gas/sleeping_agent/trace_gas = air_contents.trace_gases[1] + trace_gas.moles = 9*4000 + spawn(100) + var/turf/simulated/location = src.loc + if(istype(src.loc)) + while(!location.air) + sleep(1000) + location.assume_air(air_contents) + air_contents = new + return 1 + + +/obj/machinery/portable_atmospherics/canister/nitrogen/New() + ..() + + canister_color["prim"] = "red" + src.air_contents.nitrogen = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + + src.update_icon() + return 1 + +/obj/machinery/portable_atmospherics/canister/carbon_dioxide/New() + ..() + + canister_color["prim"] = "black" + src.air_contents.carbon_dioxide = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + + src.update_icon() + return 1 + + +/obj/machinery/portable_atmospherics/canister/air/New() + ..() + + canister_color["prim"] = "grey" + src.air_contents.oxygen = (O2STANDARD*src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + src.air_contents.nitrogen = (N2STANDARD*src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + + src.update_icon() + return 1 + +/obj/machinery/portable_atmospherics/canister/custom_mix/New() + ..() + + canister_color["prim"] = "whiters" + src.update_icon() // Otherwise new canisters do not have their icon updated with the pressure light, likely want to add this to the canister class constructor, avoiding at current time to refrain from screwing up code for other canisters. --DZD + return 1 + +/obj/machinery/portable_atmospherics/canister/welder_act(mob/user, obj/item/I) + if(!(stat & BROKEN)) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_SLICING_MESSAGE + if(I.use_tool(src, user, 50, volume = I.tool_volume)) + to_chat(user, "You salvage whats left of [src]!") + new /obj/item/stack/sheet/metal(drop_location(), 3) + qdel(src) diff --git a/code/game/machinery/atmoalter/meter.dm b/code/game/machinery/atmoalter/meter.dm index 90601f61bfb..a20b47b30db 100644 --- a/code/game/machinery/atmoalter/meter.dm +++ b/code/game/machinery/atmoalter/meter.dm @@ -1,174 +1,174 @@ -/obj/machinery/meter - name = "gas flow meter" - desc = "It measures something." - icon = 'icons/obj/meter.dmi' - icon_state = "meterX" - - layer = GAS_PUMP_LAYER - - var/obj/machinery/atmospherics/pipe/target = null - anchored = TRUE - max_integrity = 150 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0) - power_channel = ENVIRON - var/frequency = ATMOS_DISTRO_FREQ - var/id - var/id_tag - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 5 - req_one_access_txt = "24;10" - Mtoollink = TRUE - settagwhitelist = list("id_tag") - -/obj/machinery/meter/New() - ..() - SSair.atmos_machinery += src - target = locate(/obj/machinery/atmospherics/pipe) in loc - if(id && !id_tag)//i'm not dealing with further merge conflicts, fuck it - id_tag = id - return 1 - -/obj/machinery/meter/Destroy() - SSair.atmos_machinery -= src - target = null - return ..() - -/obj/machinery/meter/Initialize() - ..() - if(!target) - target = locate(/obj/machinery/atmospherics/pipe) in loc - -/obj/machinery/meter/process_atmos() - if(!target) - icon_state = "meterX" - return 0 - - if(stat & (BROKEN|NOPOWER)) - icon_state = "meter0" - return 0 - - var/datum/gas_mixture/environment = target.return_air() - if(!environment) - icon_state = "meterX" - return 0 - - var/env_pressure = environment.return_pressure() - if(env_pressure <= 0.15*ONE_ATMOSPHERE) - icon_state = "meter0" - else if(env_pressure <= 1.8*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5) - icon_state = "meter1_[val]" - else if(env_pressure <= 30*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1 - icon_state = "meter2_[val]" - else if(env_pressure <= 59*ONE_ATMOSPHERE) - var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1 - icon_state = "meter3_[val]" - else - icon_state = "meter4" - - if(frequency) - var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency) - - if(!radio_connection) return - - var/datum/signal/signal = new - signal.source = src - signal.transmission_method = 1 - signal.data = list( - "tag" = id_tag, - "device" = "AM", - "pressure" = round(env_pressure), - "sigtype" = "status" - ) - radio_connection.post_signal(src, signal) - -/obj/machinery/meter/proc/status() - var/t = "" - if(target) - var/datum/gas_mixture/environment = target.return_air() - if(environment) - t += "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)]°K ([round(environment.temperature-T0C,0.01)]°C)" - else - t += "The sensor error light is blinking." - else - t += "The connect error light is blinking." - return t - -/obj/machinery/meter/examine(mob/user) - var/t = "A gas flow meter. " - - if(get_dist(user, src) > 3 && !(istype(user, /mob/living/silicon/ai) || istype(user, /mob/dead))) - t += "You are too far away to read it." - - else if(stat & (NOPOWER|BROKEN)) - t += "The display is off." - - else if(target) - var/datum/gas_mixture/environment = target.return_air() - if(environment) - t += "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)]K ([round(environment.temperature-T0C,0.01)]°C)" - else - t += "The sensor error light is blinking." - else - t += "The connect error light is blinking." - - . = list(t) - -/obj/machinery/meter/Click() - if(istype(usr, /mob/living/silicon/ai)) // ghosts can call ..() for examine - usr.examinate(src) - return 1 - - return ..() - -/obj/machinery/meter/attackby(var/obj/item/W as obj, var/mob/user as mob, params) - if(istype(W, /obj/item/multitool)) - update_multitool_menu(user) - return 1 - - if(!istype(W, /obj/item/wrench)) - return ..() - playsound(loc, W.usesound, 50, 1) - to_chat(user, "You begin to unfasten \the [src]...") - if(do_after(user, 40 * W.toolspeed, target = src)) - user.visible_message( \ - "[user] unfastens \the [src].", \ - "You have unfastened \the [src].", \ - "You hear ratchet.") - deconstruct(TRUE) - -/obj/machinery/meter/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/pipe_meter(loc) - qdel(src) - -/obj/machinery/meter/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -// TURF METER - REPORTS A TILE'S AIR CONTENTS - -/obj/machinery/meter/turf/New() - ..() - target = loc - return 1 - - -/obj/machinery/meter/turf/Initialize() - if(!target) - target = loc - ..() - -/obj/machinery/meter/turf/attackby(var/obj/item/W as obj, var/mob/user as mob, params) - return - -/obj/machinery/meter/multitool_menu(var/mob/user, var/obj/item/multitool/P) - return {" - Main - "} +/obj/machinery/meter + name = "gas flow meter" + desc = "It measures something." + icon = 'icons/obj/meter.dmi' + icon_state = "meterX" + + layer = GAS_PUMP_LAYER + + var/obj/machinery/atmospherics/pipe/target = null + anchored = TRUE + max_integrity = 150 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0) + power_channel = ENVIRON + var/frequency = ATMOS_DISTRO_FREQ + var/id + var/id_tag + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 5 + req_one_access_txt = "24;10" + Mtoollink = TRUE + settagwhitelist = list("id_tag") + +/obj/machinery/meter/New() + ..() + SSair.atmos_machinery += src + target = locate(/obj/machinery/atmospherics/pipe) in loc + if(id && !id_tag)//i'm not dealing with further merge conflicts, fuck it + id_tag = id + return 1 + +/obj/machinery/meter/Destroy() + SSair.atmos_machinery -= src + target = null + return ..() + +/obj/machinery/meter/Initialize() + ..() + if(!target) + target = locate(/obj/machinery/atmospherics/pipe) in loc + +/obj/machinery/meter/process_atmos() + if(!target) + icon_state = "meterX" + return 0 + + if(stat & (BROKEN|NOPOWER)) + icon_state = "meter0" + return 0 + + var/datum/gas_mixture/environment = target.return_air() + if(!environment) + icon_state = "meterX" + return 0 + + var/env_pressure = environment.return_pressure() + if(env_pressure <= 0.15*ONE_ATMOSPHERE) + icon_state = "meter0" + else if(env_pressure <= 1.8*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5) + icon_state = "meter1_[val]" + else if(env_pressure <= 30*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1 + icon_state = "meter2_[val]" + else if(env_pressure <= 59*ONE_ATMOSPHERE) + var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1 + icon_state = "meter3_[val]" + else + icon_state = "meter4" + + if(frequency) + var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency) + + if(!radio_connection) return + + var/datum/signal/signal = new + signal.source = src + signal.transmission_method = 1 + signal.data = list( + "tag" = id_tag, + "device" = "AM", + "pressure" = round(env_pressure), + "sigtype" = "status" + ) + radio_connection.post_signal(src, signal) + +/obj/machinery/meter/proc/status() + var/t = "" + if(target) + var/datum/gas_mixture/environment = target.return_air() + if(environment) + t += "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)]°K ([round(environment.temperature-T0C,0.01)]°C)" + else + t += "The sensor error light is blinking." + else + t += "The connect error light is blinking." + return t + +/obj/machinery/meter/examine(mob/user) + var/t = "A gas flow meter. " + + if(get_dist(user, src) > 3 && !(istype(user, /mob/living/silicon/ai) || istype(user, /mob/dead))) + t += "You are too far away to read it." + + else if(stat & (NOPOWER|BROKEN)) + t += "The display is off." + + else if(target) + var/datum/gas_mixture/environment = target.return_air() + if(environment) + t += "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)]K ([round(environment.temperature-T0C,0.01)]°C)" + else + t += "The sensor error light is blinking." + else + t += "The connect error light is blinking." + + . = list(t) + +/obj/machinery/meter/Click() + if(istype(usr, /mob/living/silicon/ai)) // ghosts can call ..() for examine + usr.examinate(src) + return 1 + + return ..() + +/obj/machinery/meter/attackby(var/obj/item/W as obj, var/mob/user as mob, params) + if(istype(W, /obj/item/multitool)) + update_multitool_menu(user) + return 1 + + if(!istype(W, /obj/item/wrench)) + return ..() + playsound(loc, W.usesound, 50, 1) + to_chat(user, "You begin to unfasten \the [src]...") + if(do_after(user, 40 * W.toolspeed, target = src)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "You have unfastened \the [src].", \ + "You hear ratchet.") + deconstruct(TRUE) + +/obj/machinery/meter/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/pipe_meter(loc) + qdel(src) + +/obj/machinery/meter/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +// TURF METER - REPORTS A TILE'S AIR CONTENTS + +/obj/machinery/meter/turf/New() + ..() + target = loc + return 1 + + +/obj/machinery/meter/turf/Initialize() + if(!target) + target = loc + ..() + +/obj/machinery/meter/turf/attackby(var/obj/item/W as obj, var/mob/user as mob, params) + return + +/obj/machinery/meter/multitool_menu(var/mob/user, var/obj/item/multitool/P) + return {" + Main + "} diff --git a/code/game/machinery/atmoalter/portable_atmospherics.dm b/code/game/machinery/atmoalter/portable_atmospherics.dm index 5008f4791ba..cec04bf4f6f 100644 --- a/code/game/machinery/atmoalter/portable_atmospherics.dm +++ b/code/game/machinery/atmoalter/portable_atmospherics.dm @@ -1,160 +1,160 @@ -/obj/machinery/portable_atmospherics - name = "atmoalter" - use_power = NO_POWER_USE - max_integrity = 250 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30) - var/datum/gas_mixture/air_contents = new - - var/obj/machinery/atmospherics/unary/portables_connector/connected_port - var/obj/item/tank/holding - - var/volume = 0 - var/destroyed = 0 - - var/maximum_pressure = 90*ONE_ATMOSPHERE - -/obj/machinery/portable_atmospherics/New() - ..() - SSair.atmos_machinery += src - - air_contents.volume = volume - air_contents.temperature = T20C - - return 1 - -/obj/machinery/portable_atmospherics/Initialize() - . = ..() - spawn() - var/obj/machinery/atmospherics/unary/portables_connector/port = locate() in loc - if(port) - connect(port) - update_icon() - -/obj/machinery/portable_atmospherics/process_atmos() - if(!connected_port) //only react when pipe_network will ont it do it for you - //Allow for reactions - air_contents.react() - else - update_icon() - -/obj/machinery/portable_atmospherics/Destroy() - SSair.atmos_machinery -= src - disconnect() - QDEL_NULL(air_contents) - QDEL_NULL(holding) - return ..() - -/obj/machinery/portable_atmospherics/update_icon() - return null - -/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/unary/portables_connector/new_port) - //Make sure not already connected to something else - if(connected_port || !new_port || new_port.connected_device) - return 0 - - //Make sure are close enough for a valid connection - if(new_port.loc != loc) - return 0 - - //Perform the connection - connected_port = new_port - connected_port.connected_device = src - // To avoid a chicken-egg thing where pipes need to - // be initialized before the atmos cans are - if(!connected_port.parent) - connected_port.build_network() - connected_port.parent.reconcile_air() - - anchored = 1 //Prevent movement - - return 1 - -/obj/machinery/portable_atmospherics/proc/disconnect() - if(!connected_port) - return 0 - - anchored = 0 - - connected_port.connected_device = null - connected_port = null - - return 1 - -/obj/machinery/portable_atmospherics/portableConnectorReturnAir() - return air_contents - -/obj/machinery/portable_atmospherics/AltClick(mob/living/user) - if(!istype(user) || user.incapacitated()) - to_chat(user, "You can't do that right now!") - return - if(!in_range(src, user)) - return - if(!ishuman(usr) && !issilicon(usr)) - return - if(holding) - to_chat(user, "You remove [holding] from [src].") - replace_tank(user, TRUE) - -/obj/machinery/portable_atmospherics/examine(mob/user) - . = ..() - if(holding) - . += "\The [src] contains [holding]. Alt-click [src] to remove it." - -/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) - if(holding) - holding.forceMove(drop_location()) - if(Adjacent(user) && !issilicon(user)) - user.put_in_hands(holding) - if(new_tank) - holding = new_tank - else - holding = null - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - if(!(stat & BROKEN)) - if(!user.drop_item()) - return - var/obj/item/tank/T = W - user.drop_item() - if(src.holding) - to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") - replace_tank(user, FALSE) - T.loc = src - src.holding = T - update_icon() - return - if((istype(W, /obj/item/analyzer)) && get_dist(user, src) <= 1) - atmosanalyzer_scan(air_contents, user) - return - return ..() - -/obj/machinery/portable_atmospherics/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(connected_port) - disconnect() - to_chat(user, "You disconnect [name] from the port.") - update_icon() - else - var/obj/machinery/atmospherics/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/unary/portables_connector/) in loc - if(possible_port) - if(connect(possible_port)) - to_chat(user, "You connect [src] to the port.") - update_icon() - return - else - to_chat(user, "[src] failed to connect to the port.") - return - else - to_chat(user, "Nothing happens.") - -/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) - if(I.force < 10 && !(stat & BROKEN)) - take_damage(0) - else - add_fingerprint(user) - ..() \ No newline at end of file +/obj/machinery/portable_atmospherics + name = "atmoalter" + use_power = NO_POWER_USE + max_integrity = 250 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30) + var/datum/gas_mixture/air_contents = new + + var/obj/machinery/atmospherics/unary/portables_connector/connected_port + var/obj/item/tank/holding + + var/volume = 0 + var/destroyed = 0 + + var/maximum_pressure = 90*ONE_ATMOSPHERE + +/obj/machinery/portable_atmospherics/New() + ..() + SSair.atmos_machinery += src + + air_contents.volume = volume + air_contents.temperature = T20C + + return 1 + +/obj/machinery/portable_atmospherics/Initialize() + . = ..() + spawn() + var/obj/machinery/atmospherics/unary/portables_connector/port = locate() in loc + if(port) + connect(port) + update_icon() + +/obj/machinery/portable_atmospherics/process_atmos() + if(!connected_port) //only react when pipe_network will ont it do it for you + //Allow for reactions + air_contents.react() + else + update_icon() + +/obj/machinery/portable_atmospherics/Destroy() + SSair.atmos_machinery -= src + disconnect() + QDEL_NULL(air_contents) + QDEL_NULL(holding) + return ..() + +/obj/machinery/portable_atmospherics/update_icon() + return null + +/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/unary/portables_connector/new_port) + //Make sure not already connected to something else + if(connected_port || !new_port || new_port.connected_device) + return 0 + + //Make sure are close enough for a valid connection + if(new_port.loc != loc) + return 0 + + //Perform the connection + connected_port = new_port + connected_port.connected_device = src + // To avoid a chicken-egg thing where pipes need to + // be initialized before the atmos cans are + if(!connected_port.parent) + connected_port.build_network() + connected_port.parent.reconcile_air() + + anchored = 1 //Prevent movement + + return 1 + +/obj/machinery/portable_atmospherics/proc/disconnect() + if(!connected_port) + return 0 + + anchored = 0 + + connected_port.connected_device = null + connected_port = null + + return 1 + +/obj/machinery/portable_atmospherics/portableConnectorReturnAir() + return air_contents + +/obj/machinery/portable_atmospherics/AltClick(mob/living/user) + if(!istype(user) || user.incapacitated()) + to_chat(user, "You can't do that right now!") + return + if(!in_range(src, user)) + return + if(!ishuman(usr) && !issilicon(usr)) + return + if(holding) + to_chat(user, "You remove [holding] from [src].") + replace_tank(user, TRUE) + +/obj/machinery/portable_atmospherics/examine(mob/user) + . = ..() + if(holding) + . += "\The [src] contains [holding]. Alt-click [src] to remove it." + +/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) + if(holding) + holding.forceMove(drop_location()) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(holding) + if(new_tank) + holding = new_tank + else + holding = null + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + if(!(stat & BROKEN)) + if(!user.drop_item()) + return + var/obj/item/tank/T = W + user.drop_item() + if(src.holding) + to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") + replace_tank(user, FALSE) + T.loc = src + src.holding = T + update_icon() + return + if((istype(W, /obj/item/analyzer)) && get_dist(user, src) <= 1) + atmosanalyzer_scan(air_contents, user) + return + return ..() + +/obj/machinery/portable_atmospherics/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(connected_port) + disconnect() + to_chat(user, "You disconnect [name] from the port.") + update_icon() + else + var/obj/machinery/atmospherics/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/unary/portables_connector/) in loc + if(possible_port) + if(connect(possible_port)) + to_chat(user, "You connect [src] to the port.") + update_icon() + return + else + to_chat(user, "[src] failed to connect to the port.") + return + else + to_chat(user, "Nothing happens.") + +/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) + if(I.force < 10 && !(stat & BROKEN)) + take_damage(0) + else + add_fingerprint(user) + ..() diff --git a/code/game/machinery/atmoalter/pump.dm b/code/game/machinery/atmoalter/pump.dm index 5bd441f6748..e4bdef2f147 100644 --- a/code/game/machinery/atmoalter/pump.dm +++ b/code/game/machinery/atmoalter/pump.dm @@ -1,173 +1,173 @@ -/obj/machinery/portable_atmospherics/pump - name = "Portable Air Pump" - - icon = 'icons/obj/atmos.dmi' - icon_state = "psiphon:0" - density = 1 - - var/on = 0 - var/direction_out = 0 //0 = siphoning, 1 = releasing - var/target_pressure = 100 - - var/pressuremin = 0 - var/pressuremax = 10 * ONE_ATMOSPHERE - - volume = 1000 - -/obj/machinery/portable_atmospherics/pump/update_icon() - src.overlays = 0 - - if(on) - icon_state = "psiphon:1" - else - icon_state = "psiphon:0" - - if(holding) - overlays += "siphon-open" - - if(connected_port) - overlays += "siphon-connector" - - return - -/obj/machinery/portable_atmospherics/pump/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - ..(severity) - return - - if(prob(50/severity)) - on = !on - - if(prob(100/severity)) - direction_out = !direction_out - - target_pressure = rand(0,1300) - update_icon() - - ..(severity) - -/obj/machinery/portable_atmospherics/pump/process_atmos() - ..() - if(on) - var/datum/gas_mixture/environment - if(holding) - environment = holding.air_contents - else - environment = loc.return_air() - if(direction_out) - var/pressure_delta = target_pressure - environment.return_pressure() - //Can not have a pressure delta that would cause environment pressure > tank pressure - - var/transfer_moles = 0 - if(air_contents.temperature > 0) - transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION) - - //Actually transfer the gas - var/datum/gas_mixture/removed = air_contents.remove(transfer_moles) - - if(holding) - environment.merge(removed) - else - loc.assume_air(removed) - air_update_turf() - else - var/pressure_delta = target_pressure - air_contents.return_pressure() - //Can not have a pressure delta that would cause environment pressure > tank pressure - - var/transfer_moles = 0 - if(environment.temperature > 0) - transfer_moles = pressure_delta*air_contents.volume/(environment.temperature * R_IDEAL_GAS_EQUATION) - - //Actually transfer the gas - var/datum/gas_mixture/removed - if(holding) - removed = environment.remove(transfer_moles) - else - removed = loc.remove_air(transfer_moles) - air_update_turf() - - air_contents.merge(removed) - //src.update_icon() - - src.updateDialog() - return - -/obj/machinery/portable_atmospherics/pump/return_air() - return air_contents - -/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve) - . = ..() - if(.) - if(close_valve) - if(on) - on = FALSE - update_icon() - else if(on && holding && direction_out) - investigate_log("[key_name(user)] started a transfer into [holding].
        ", "atmos") - -/obj/machinery/portable_atmospherics/pump/attack_ai(var/mob/user as mob) - src.add_hiddenprint(user) - return src.attack_hand(user) - -/obj/machinery/portable_atmospherics/pump/attack_ghost(var/mob/user as mob) - return src.attack_hand(user) - -/obj/machinery/portable_atmospherics/pump/attack_hand(var/mob/user as mob) - ui_interact(user) - -/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = physical_state) - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "portpump.tmpl", "Portable Pump", 480, 400, state = state) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/machinery/portable_atmospherics/pump/ui_data(mob/user, ui_key = "main", datum/topic_state/state = physical_state) - var/data[0] - data["portConnected"] = connected_port ? 1 : 0 - data["tankPressure"] = round(air_contents.return_pressure() > 0 ? air_contents.return_pressure() : 0) - data["targetpressure"] = round(target_pressure) - data["pump_dir"] = direction_out - data["minpressure"] = round(pressuremin) - data["maxpressure"] = round(pressuremax) - data["on"] = on ? 1 : 0 - - data["hasHoldingTank"] = holding ? 1 : 0 - if(holding) - data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure() > 0 ? holding.air_contents.return_pressure() : 0)) - - return data - -/obj/machinery/portable_atmospherics/pump/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["power"]) - on = !on - if(on && direction_out) - investigate_log("[key_name(usr)] started a transfer into [holding].
        ", "atmos") - update_icon() - - if(href_list["direction"]) - direction_out = !direction_out - if(on && holding) - investigate_log("[key_name(usr)] started a transfer into [holding].
        ", "atmos") - - if(href_list["remove_tank"]) - if(holding) - on = FALSE - holding.loc = loc - holding = null - update_icon() - - if(href_list["pressure_adj"]) - var/diff = text2num(href_list["pressure_adj"]) - target_pressure = Clamp(target_pressure+diff, pressuremin, pressuremax) - update_icon() - - src.add_fingerprint(usr) +/obj/machinery/portable_atmospherics/pump + name = "Portable Air Pump" + + icon = 'icons/obj/atmos.dmi' + icon_state = "psiphon:0" + density = 1 + + var/on = 0 + var/direction_out = 0 //0 = siphoning, 1 = releasing + var/target_pressure = 100 + + var/pressuremin = 0 + var/pressuremax = 10 * ONE_ATMOSPHERE + + volume = 1000 + +/obj/machinery/portable_atmospherics/pump/update_icon() + src.overlays = 0 + + if(on) + icon_state = "psiphon:1" + else + icon_state = "psiphon:0" + + if(holding) + overlays += "siphon-open" + + if(connected_port) + overlays += "siphon-connector" + + return + +/obj/machinery/portable_atmospherics/pump/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + ..(severity) + return + + if(prob(50/severity)) + on = !on + + if(prob(100/severity)) + direction_out = !direction_out + + target_pressure = rand(0,1300) + update_icon() + + ..(severity) + +/obj/machinery/portable_atmospherics/pump/process_atmos() + ..() + if(on) + var/datum/gas_mixture/environment + if(holding) + environment = holding.air_contents + else + environment = loc.return_air() + if(direction_out) + var/pressure_delta = target_pressure - environment.return_pressure() + //Can not have a pressure delta that would cause environment pressure > tank pressure + + var/transfer_moles = 0 + if(air_contents.temperature > 0) + transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION) + + //Actually transfer the gas + var/datum/gas_mixture/removed = air_contents.remove(transfer_moles) + + if(holding) + environment.merge(removed) + else + loc.assume_air(removed) + air_update_turf() + else + var/pressure_delta = target_pressure - air_contents.return_pressure() + //Can not have a pressure delta that would cause environment pressure > tank pressure + + var/transfer_moles = 0 + if(environment.temperature > 0) + transfer_moles = pressure_delta*air_contents.volume/(environment.temperature * R_IDEAL_GAS_EQUATION) + + //Actually transfer the gas + var/datum/gas_mixture/removed + if(holding) + removed = environment.remove(transfer_moles) + else + removed = loc.remove_air(transfer_moles) + air_update_turf() + + air_contents.merge(removed) + //src.update_icon() + + src.updateDialog() + return + +/obj/machinery/portable_atmospherics/pump/return_air() + return air_contents + +/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve) + . = ..() + if(.) + if(close_valve) + if(on) + on = FALSE + update_icon() + else if(on && holding && direction_out) + investigate_log("[key_name(user)] started a transfer into [holding].
        ", "atmos") + +/obj/machinery/portable_atmospherics/pump/attack_ai(var/mob/user as mob) + src.add_hiddenprint(user) + return src.attack_hand(user) + +/obj/machinery/portable_atmospherics/pump/attack_ghost(var/mob/user as mob) + return src.attack_hand(user) + +/obj/machinery/portable_atmospherics/pump/attack_hand(var/mob/user as mob) + ui_interact(user) + +/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = physical_state) + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "portpump.tmpl", "Portable Pump", 480, 400, state = state) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/machinery/portable_atmospherics/pump/ui_data(mob/user, ui_key = "main", datum/topic_state/state = physical_state) + var/data[0] + data["portConnected"] = connected_port ? 1 : 0 + data["tankPressure"] = round(air_contents.return_pressure() > 0 ? air_contents.return_pressure() : 0) + data["targetpressure"] = round(target_pressure) + data["pump_dir"] = direction_out + data["minpressure"] = round(pressuremin) + data["maxpressure"] = round(pressuremax) + data["on"] = on ? 1 : 0 + + data["hasHoldingTank"] = holding ? 1 : 0 + if(holding) + data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure() > 0 ? holding.air_contents.return_pressure() : 0)) + + return data + +/obj/machinery/portable_atmospherics/pump/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["power"]) + on = !on + if(on && direction_out) + investigate_log("[key_name(usr)] started a transfer into [holding].
        ", "atmos") + update_icon() + + if(href_list["direction"]) + direction_out = !direction_out + if(on && holding) + investigate_log("[key_name(usr)] started a transfer into [holding].
        ", "atmos") + + if(href_list["remove_tank"]) + if(holding) + on = FALSE + holding.loc = loc + holding = null + update_icon() + + if(href_list["pressure_adj"]) + var/diff = text2num(href_list["pressure_adj"]) + target_pressure = Clamp(target_pressure+diff, pressuremin, pressuremax) + update_icon() + + src.add_fingerprint(usr) diff --git a/code/game/machinery/atmoalter/scrubber.dm b/code/game/machinery/atmoalter/scrubber.dm index 2079b8cf915..1016e270f6d 100644 --- a/code/game/machinery/atmoalter/scrubber.dm +++ b/code/game/machinery/atmoalter/scrubber.dm @@ -1,216 +1,216 @@ -/obj/machinery/portable_atmospherics/scrubber - name = "Portable Air Scrubber" - - icon = 'icons/obj/atmos.dmi' - icon_state = "pscrubber:0" - density = 1 - - var/on = 0 - var/volume_rate = 800 - var/widenet = 0 //is this scrubber acting on the 3x3 area around it. - - volume = 750 - - var/minrate = 0//probably useless, but whatever - var/maxrate = 10 * ONE_ATMOSPHERE - -/obj/machinery/portable_atmospherics/scrubber/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - ..(severity) - return - - if(prob(50/severity)) - on = !on - update_icon() - - ..(severity) - -/obj/machinery/portable_atmospherics/scrubber/update_icon() - src.overlays = 0 - - if(on) - icon_state = "pscrubber:1" - else - icon_state = "pscrubber:0" - - if(holding) - overlays += "scrubber-open" - - if(connected_port) - overlays += "scrubber-connector" - - return - -/obj/machinery/portable_atmospherics/scrubber/process_atmos() - ..() - - if(!on) - return - scrub(loc) - if(widenet) - var/turf/T = loc - if(istype(T)) - for(var/turf/simulated/tile in T.GetAtmosAdjacentTurfs(alldir=1)) - scrub(tile) - -/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/turf/simulated/tile) - var/datum/gas_mixture/environment - if(holding) - environment = holding.air_contents - else - environment = tile.return_air() - var/transfer_moles = min(1,volume_rate/environment.volume)*environment.total_moles() - - //Take a gas sample - var/datum/gas_mixture/removed - if(holding) - removed = environment.remove(transfer_moles) - else - removed = loc.remove_air(transfer_moles) - - //Filter it - if(removed) - var/datum/gas_mixture/filtered_out = new - - filtered_out.temperature = removed.temperature - - - filtered_out.toxins = removed.toxins - removed.toxins = 0 - - filtered_out.carbon_dioxide = removed.carbon_dioxide - removed.carbon_dioxide = 0 - - if(removed.trace_gases.len>0) - for(var/datum/gas/trace_gas in removed.trace_gases) - if(istype(trace_gas, /datum/gas/sleeping_agent)) - removed.trace_gases -= trace_gas - filtered_out.trace_gases += trace_gas - - if(removed.trace_gases.len>0) - for(var/datum/gas/trace_gas in removed.trace_gases) - if(istype(trace_gas, /datum/gas/oxygen_agent_b)) - removed.trace_gases -= trace_gas - filtered_out.trace_gases += trace_gas - - //Remix the resulting gases - air_contents.merge(filtered_out) - - if(holding) - environment.merge(removed) - else - tile.assume_air(removed) - tile.air_update_turf() - -/obj/machinery/portable_atmospherics/scrubber/return_air() - return air_contents - -/obj/machinery/portable_atmospherics/scrubber/attack_ai(var/mob/user as mob) - src.add_hiddenprint(user) - return src.attack_hand(user) - -/obj/machinery/portable_atmospherics/scrubber/attack_ghost(var/mob/user as mob) - return src.attack_hand(user) - -/obj/machinery/portable_atmospherics/scrubber/attack_hand(var/mob/user as mob) - ui_interact(user) - return - -/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = physical_state) - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "portscrubber.tmpl", "Portable Scrubber", 480, 400, state = state) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/machinery/portable_atmospherics/scrubber/ui_data(mob/user, ui_key = "main", datum/topic_state/state = physical_state) - var/data[0] - data["portConnected"] = connected_port ? 1 : 0 - data["tankPressure"] = round(air_contents.return_pressure() > 0 ? air_contents.return_pressure() : 0) - data["rate"] = round(volume_rate) - data["minrate"] = round(minrate) - data["maxrate"] = round(maxrate) - data["on"] = on ? 1 : 0 - - data["hasHoldingTank"] = holding ? 1 : 0 - if(holding) - data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure() > 0 ? holding.air_contents.return_pressure() : 0)) - - return data - -/obj/machinery/portable_atmospherics/scrubber/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["power"]) - on = !on - update_icon() - - if(href_list["remove_tank"]) - if(holding) - holding.loc = loc - holding = null - - if(href_list["volume_adj"]) - var/diff = text2num(href_list["volume_adj"]) - volume_rate = Clamp(volume_rate+diff, minrate, maxrate) - - src.add_fingerprint(usr) - -/obj/machinery/portable_atmospherics/scrubber/huge - name = "Huge Air Scrubber" - icon_state = "scrubber:0" - anchored = 1 - volume = 50000 - volume_rate = 5000 - widenet = 1 - - var/global/gid = 1 - var/id = 0 - var/stationary = 0 - -/obj/machinery/portable_atmospherics/scrubber/huge/New() - ..() - id = gid - gid++ - - name = "[name] (ID [id])" - -/obj/machinery/portable_atmospherics/scrubber/huge/attack_hand(var/mob/user as mob) - to_chat(usr, "You can't directly interact with this machine. Use the area atmos computer.") - -/obj/machinery/portable_atmospherics/scrubber/huge/update_icon() - src.overlays = 0 - - if(on) - icon_state = "scrubber:1" - else - icon_state = "scrubber:0" - -/obj/machinery/portable_atmospherics/scrubber/huge/attackby(var/obj/item/W as obj, var/mob/user as mob, params) - if(istype(W, /obj/item/wrench)) - if(stationary) - to_chat(user, "The bolts are too tight for you to unscrew!") - return - if(on) - to_chat(user, "Turn it off first!") - return - - anchored = !anchored - playsound(loc, W.usesound, 50, 1) - to_chat(user, "You [anchored ? "wrench" : "unwrench"] \the [src].") - return - - if((istype(W, /obj/item/analyzer)) && get_dist(user, src) <= 1) - atmosanalyzer_scan(air_contents, user) - return - return ..() - -/obj/machinery/portable_atmospherics/scrubber/huge/stationary - name = "Stationary Air Scrubber" - stationary = 1 +/obj/machinery/portable_atmospherics/scrubber + name = "Portable Air Scrubber" + + icon = 'icons/obj/atmos.dmi' + icon_state = "pscrubber:0" + density = 1 + + var/on = 0 + var/volume_rate = 800 + var/widenet = 0 //is this scrubber acting on the 3x3 area around it. + + volume = 750 + + var/minrate = 0//probably useless, but whatever + var/maxrate = 10 * ONE_ATMOSPHERE + +/obj/machinery/portable_atmospherics/scrubber/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + ..(severity) + return + + if(prob(50/severity)) + on = !on + update_icon() + + ..(severity) + +/obj/machinery/portable_atmospherics/scrubber/update_icon() + src.overlays = 0 + + if(on) + icon_state = "pscrubber:1" + else + icon_state = "pscrubber:0" + + if(holding) + overlays += "scrubber-open" + + if(connected_port) + overlays += "scrubber-connector" + + return + +/obj/machinery/portable_atmospherics/scrubber/process_atmos() + ..() + + if(!on) + return + scrub(loc) + if(widenet) + var/turf/T = loc + if(istype(T)) + for(var/turf/simulated/tile in T.GetAtmosAdjacentTurfs(alldir=1)) + scrub(tile) + +/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/turf/simulated/tile) + var/datum/gas_mixture/environment + if(holding) + environment = holding.air_contents + else + environment = tile.return_air() + var/transfer_moles = min(1,volume_rate/environment.volume)*environment.total_moles() + + //Take a gas sample + var/datum/gas_mixture/removed + if(holding) + removed = environment.remove(transfer_moles) + else + removed = loc.remove_air(transfer_moles) + + //Filter it + if(removed) + var/datum/gas_mixture/filtered_out = new + + filtered_out.temperature = removed.temperature + + + filtered_out.toxins = removed.toxins + removed.toxins = 0 + + filtered_out.carbon_dioxide = removed.carbon_dioxide + removed.carbon_dioxide = 0 + + if(removed.trace_gases.len>0) + for(var/datum/gas/trace_gas in removed.trace_gases) + if(istype(trace_gas, /datum/gas/sleeping_agent)) + removed.trace_gases -= trace_gas + filtered_out.trace_gases += trace_gas + + if(removed.trace_gases.len>0) + for(var/datum/gas/trace_gas in removed.trace_gases) + if(istype(trace_gas, /datum/gas/oxygen_agent_b)) + removed.trace_gases -= trace_gas + filtered_out.trace_gases += trace_gas + + //Remix the resulting gases + air_contents.merge(filtered_out) + + if(holding) + environment.merge(removed) + else + tile.assume_air(removed) + tile.air_update_turf() + +/obj/machinery/portable_atmospherics/scrubber/return_air() + return air_contents + +/obj/machinery/portable_atmospherics/scrubber/attack_ai(var/mob/user as mob) + src.add_hiddenprint(user) + return src.attack_hand(user) + +/obj/machinery/portable_atmospherics/scrubber/attack_ghost(var/mob/user as mob) + return src.attack_hand(user) + +/obj/machinery/portable_atmospherics/scrubber/attack_hand(var/mob/user as mob) + ui_interact(user) + return + +/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = physical_state) + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "portscrubber.tmpl", "Portable Scrubber", 480, 400, state = state) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/machinery/portable_atmospherics/scrubber/ui_data(mob/user, ui_key = "main", datum/topic_state/state = physical_state) + var/data[0] + data["portConnected"] = connected_port ? 1 : 0 + data["tankPressure"] = round(air_contents.return_pressure() > 0 ? air_contents.return_pressure() : 0) + data["rate"] = round(volume_rate) + data["minrate"] = round(minrate) + data["maxrate"] = round(maxrate) + data["on"] = on ? 1 : 0 + + data["hasHoldingTank"] = holding ? 1 : 0 + if(holding) + data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure() > 0 ? holding.air_contents.return_pressure() : 0)) + + return data + +/obj/machinery/portable_atmospherics/scrubber/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["power"]) + on = !on + update_icon() + + if(href_list["remove_tank"]) + if(holding) + holding.loc = loc + holding = null + + if(href_list["volume_adj"]) + var/diff = text2num(href_list["volume_adj"]) + volume_rate = Clamp(volume_rate+diff, minrate, maxrate) + + src.add_fingerprint(usr) + +/obj/machinery/portable_atmospherics/scrubber/huge + name = "Huge Air Scrubber" + icon_state = "scrubber:0" + anchored = 1 + volume = 50000 + volume_rate = 5000 + widenet = 1 + + var/global/gid = 1 + var/id = 0 + var/stationary = 0 + +/obj/machinery/portable_atmospherics/scrubber/huge/New() + ..() + id = gid + gid++ + + name = "[name] (ID [id])" + +/obj/machinery/portable_atmospherics/scrubber/huge/attack_hand(var/mob/user as mob) + to_chat(usr, "You can't directly interact with this machine. Use the area atmos computer.") + +/obj/machinery/portable_atmospherics/scrubber/huge/update_icon() + src.overlays = 0 + + if(on) + icon_state = "scrubber:1" + else + icon_state = "scrubber:0" + +/obj/machinery/portable_atmospherics/scrubber/huge/attackby(var/obj/item/W as obj, var/mob/user as mob, params) + if(istype(W, /obj/item/wrench)) + if(stationary) + to_chat(user, "The bolts are too tight for you to unscrew!") + return + if(on) + to_chat(user, "Turn it off first!") + return + + anchored = !anchored + playsound(loc, W.usesound, 50, 1) + to_chat(user, "You [anchored ? "wrench" : "unwrench"] \the [src].") + return + + if((istype(W, /obj/item/analyzer)) && get_dist(user, src) <= 1) + atmosanalyzer_scan(air_contents, user) + return + return ..() + +/obj/machinery/portable_atmospherics/scrubber/huge/stationary + name = "Stationary Air Scrubber" + stationary = 1 diff --git a/code/game/machinery/atmoalter/zvent.dm b/code/game/machinery/atmoalter/zvent.dm index 18af424c4d6..773432133e8 100644 --- a/code/game/machinery/atmoalter/zvent.dm +++ b/code/game/machinery/atmoalter/zvent.dm @@ -1,35 +1,35 @@ -/obj/machinery/zvent - name = "Interfloor Air Transfer System" - - icon = 'icons/obj/pipes.dmi' - icon_state = "vent-db" - density = 0 - anchored=1 - - var/on = 0 - var/volume_rate = 800 - -/obj/machinery/zvent/New() - ..() - SSair.atmos_machinery += src - -/obj/machinery/zvent/Destroy() - SSair.atmos_machinery -= src - return ..() - -/obj/machinery/zvent/process_atmos() - - //all this object does, is make its turf share air with the ones above and below it, if they have a vent too. - if(istype(loc,/turf/simulated)) //if we're not on a valid turf, forget it - for(var/new_z in list(-1,1)) //change this list if a fancier system of z-levels gets implemented - var/turf/simulated/zturf_conn = locate(x,y,z+new_z) - if(istype(zturf_conn)) - var/obj/machinery/zvent/zvent_conn= locate(/obj/machinery/zvent) in zturf_conn - if(istype(zvent_conn)) - //both floors have simulated turfs, share() - var/turf/simulated/myturf = loc - var/datum/gas_mixture/conn_air = zturf_conn.air //TODO: pop culture reference - var/datum/gas_mixture/my_air = myturf.air - if(istype(conn_air) && istype(my_air)) - my_air.share(conn_air) - air_update_turf() +/obj/machinery/zvent + name = "Interfloor Air Transfer System" + + icon = 'icons/obj/pipes.dmi' + icon_state = "vent-db" + density = 0 + anchored=1 + + var/on = 0 + var/volume_rate = 800 + +/obj/machinery/zvent/New() + ..() + SSair.atmos_machinery += src + +/obj/machinery/zvent/Destroy() + SSair.atmos_machinery -= src + return ..() + +/obj/machinery/zvent/process_atmos() + + //all this object does, is make its turf share air with the ones above and below it, if they have a vent too. + if(istype(loc,/turf/simulated)) //if we're not on a valid turf, forget it + for(var/new_z in list(-1,1)) //change this list if a fancier system of z-levels gets implemented + var/turf/simulated/zturf_conn = locate(x,y,z+new_z) + if(istype(zturf_conn)) + var/obj/machinery/zvent/zvent_conn= locate(/obj/machinery/zvent) in zturf_conn + if(istype(zvent_conn)) + //both floors have simulated turfs, share() + var/turf/simulated/myturf = loc + var/datum/gas_mixture/conn_air = zturf_conn.air //TODO: pop culture reference + var/datum/gas_mixture/my_air = myturf.air + if(istype(conn_air) && istype(my_air)) + my_air.share(conn_air) + air_update_turf() diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index 52ee48f067b..9d9db841b1e 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -1,469 +1,469 @@ -#define AUTOLATHE_MAIN_MENU 1 -#define AUTOLATHE_CATEGORY_MENU 2 -#define AUTOLATHE_SEARCH_MENU 3 - -/obj/machinery/autolathe - name = "autolathe" - desc = "It produces items using metal and glass." - icon_state = "autolathe" - density = 1 - - var/operating = 0.0 - var/list/queue = list() - var/queue_max_len = 12 - var/turf/BuildTurf - anchored = 1.0 - var/list/L = list() - var/list/LL = list() - var/hacked = 0 - var/disabled = 0 - var/shocked = 0 - var/hack_wire - var/disable_wire - var/shock_wire - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - var/busy = 0 - var/prod_coeff - var/datum/wires/autolathe/wires = null - - var/list/being_built = list() - var/datum/research/files - var/list/datum/design/matching_designs - var/temp_search - var/selected_category - var/screen = 1 - - var/list/categories = list("Tools", "Electronics", "Construction", "Communication", "Security", "Machinery", "Medical", "Miscellaneous", "Dinnerware", "Imported") - -/obj/machinery/autolathe/New() - AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_GLASS), 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert)) - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/autolathe(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stack/sheet/glass(null) - RefreshParts() - - wires = new(src) - files = new /datum/research/autolathe(src) - matching_designs = list() - -/obj/machinery/autolathe/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/autolathe(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/stack/sheet/glass(null) - RefreshParts() - -/obj/machinery/autolathe/Destroy() - QDEL_NULL(wires) - GET_COMPONENT(materials, /datum/component/material_container) - materials.retrieve_all() - return ..() - -/obj/machinery/autolathe/interact(mob/user) - if(shocked && !(stat & NOPOWER)) - if(shock(user, 50)) - return - - if(panel_open) - wires.Interact(user) - else - ui_interact(user) - -/obj/machinery/autolathe/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "autolathe.tmpl", name, 800, 550) - ui.open() - -/obj/machinery/autolathe/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - GET_COMPONENT(materials, /datum/component/material_container) - var/data[0] - data["screen"] = screen - data["total_amount"] = materials.total_amount - data["max_amount"] = materials.max_amount - data["metal_amount"] = materials.amount(MAT_METAL) - data["glass_amount"] = materials.amount(MAT_GLASS) - switch(screen) - if(AUTOLATHE_MAIN_MENU) - data["uid"] = UID() - data["categories"] = categories - if(AUTOLATHE_CATEGORY_MENU) - data["selected_category"] = selected_category - var/list/designs = list() - data["designs"] = designs - for(var/v in files.known_designs) - var/datum/design/D = files.known_designs[v] - if(!(selected_category in D.category)) - continue - var/list/design = list() - designs[++designs.len] = design - design["name"] = D.name - design["id"] = D.id - design["disabled"] = disabled || !can_build(D) ? "disabled" : null - if(ispath(D.build_path, /obj/item/stack)) - design["max_multiplier"] = min(D.maxstack, D.materials[MAT_METAL] ? round(materials.amount(MAT_METAL) / D.materials[MAT_METAL]) : INFINITY, D.materials[MAT_GLASS] ? round(materials.amount(MAT_GLASS) / D.materials[MAT_GLASS]) : INFINITY) - else - design["max_multiplier"] = null - design["materials"] = design_cost_data(D) - if(AUTOLATHE_SEARCH_MENU) - data["search"] = temp_search - var/list/designs = list() - data["designs"] = designs - for(var/datum/design/D in matching_designs) - var/list/design = list() - designs[++designs.len] = design - design["name"] = D.name - design["id"] = D.id - design["disabled"] = disabled || !can_build(D) ? "disabled" : null - if(ispath(D.build_path, /obj/item/stack)) - design["max_multiplier"] = min(D.maxstack, D.materials[MAT_METAL] ? round(materials.amount(MAT_METAL) / D.materials[MAT_METAL]) : INFINITY, D.materials[MAT_GLASS] ? round(materials.amount(MAT_GLASS) / D.materials[MAT_GLASS]) : INFINITY) - else - design["max_multiplier"] = null - design["materials"] = design_cost_data(D) - - data = queue_data(data) - return data - -/obj/machinery/autolathe/proc/design_cost_data(datum/design/D) - var/list/data = list() - var/coeff = get_coeff(D) - GET_COMPONENT(materials, /datum/component/material_container) - var/has_metal = 1 - if(D.materials[MAT_METAL] && (materials.amount(MAT_METAL) < (D.materials[MAT_METAL] / coeff))) - has_metal = 0 - var/has_glass = 1 - if(D.materials[MAT_GLASS] && (materials.amount(MAT_GLASS) < (D.materials[MAT_GLASS] / coeff))) - has_glass = 0 - - data[++data.len] = list("name" = "metal", "amount" = D.materials[MAT_METAL] / coeff, "is_red" = !has_metal) - data[++data.len] = list("name" = "glass", "amount" = D.materials[MAT_GLASS] / coeff, "is_red" = !has_glass) - - return data - -/obj/machinery/autolathe/proc/queue_data(list/data) - GET_COMPONENT(materials, /datum/component/material_container) - var/temp_metal = materials.amount(MAT_METAL) - var/temp_glass = materials.amount(MAT_GLASS) - data["processing"] = being_built.len ? get_processing_line() : null - if(istype(queue) && queue.len) - var/list/data_queue = list() - for(var/list/L in queue) - var/datum/design/D = L[1] - var/list/LL = get_design_cost_as_list(D, L[2]) - data_queue[++data_queue.len] = list("name" = initial(D.name), "can_build" = can_build(D, L[2], temp_metal, temp_glass), "multiplier" = L[2]) - temp_metal = max(temp_metal - LL[1], 1) - temp_glass = max(temp_glass - LL[2], 1) - data["queue"] = data_queue - data["queue_len"] = data_queue.len - else - data["queue"] = null - return data - -/obj/machinery/autolathe/attackby(obj/item/O, mob/user, params) - if(busy) - to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") - return 1 - if(exchange_parts(user, O)) - return - if(stat) - return 1 - - // Disks in general - if(istype(O, /obj/item/disk)) - if(istype(O, /obj/item/disk/design_disk)) - var/obj/item/disk/design_disk/D = O - if(D.blueprint) - user.visible_message("[user] begins to load \the [O] in \the [src]...", - "You begin to load a design from \the [O]...", - "You hear the chatter of a floppy drive.") - playsound(get_turf(src), 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) - busy = 1 - if(do_after(user, 14.4, target = src)) - files.AddDesign2Known(D.blueprint) - busy = 0 - else - to_chat(user, "That disk does not have a design on it!") - return 1 - else - // So that people who are bad at computers don't shred their disks - to_chat(user, "This is not the correct type of disk for the autolathe!") - return 1 - - return ..() - -/obj/machinery/autolathe/crowbar_act(mob/user, obj/item/I) - if(!I.use_tool(src, user, 0, volume = 0)) - return - . = TRUE - if(busy) - to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") - return - if(panel_open) - default_deconstruction_crowbar(user, I) - I.play_tool_sound(user, I.tool_volume) - -/obj/machinery/autolathe/screwdriver_act(mob/user, obj/item/I) - if(!I.use_tool(src, user, 0, volume = 0)) - return - . = TRUE - if(busy) - to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") - return - if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", I)) - SSnanoui.update_uis(src) - I.play_tool_sound(user, I.tool_volume) - -/obj/machinery/autolathe/wirecutter_act(mob/user, obj/item/I) - if(!panel_open) - return - if(!I.use_tool(src, user, 0, volume = 0)) - return - . = TRUE - if(busy) - to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") - return - interact(user) - -/obj/machinery/autolathe/multitool_act(mob/user, obj/item/I) - if(!panel_open) - return - if(!I.use_tool(src, user, 0, volume = 0)) - return - . = TRUE - if(busy) - to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") - return - interact(user) - -/obj/machinery/autolathe/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) - switch(id_inserted) - if(MAT_METAL) - flick("autolathe_o", src)//plays metal insertion animation - if(MAT_GLASS) - flick("autolathe_r", src)//plays glass insertion animation - use_power(min(1000, amount_inserted / 100)) - SSnanoui.update_uis(src) - -/obj/machinery/autolathe/attack_ghost(mob/user) - interact(user) - -/obj/machinery/autolathe/attack_hand(mob/user) - if(..(user, 0)) - return - interact(user) - -/obj/machinery/autolathe/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["menu"]) - screen = text2num(href_list["menu"]) - - if(href_list["category"]) - selected_category = href_list["category"] - screen = AUTOLATHE_CATEGORY_MENU - - if(href_list["make"]) - BuildTurf = loc - - ///////////////// - //href protection - var/datum/design/design_last_ordered - design_last_ordered = files.FindDesignByID(href_list["make"]) //check if it's a valid design - if(!design_last_ordered) - return - if(!(design_last_ordered.build_type & AUTOLATHE)) - return - - //multiplier checks : only stacks can have one and its value is 1, 10 ,25 or max_multiplier - var/multiplier = text2num(href_list["multiplier"]) - GET_COMPONENT(materials, /datum/component/material_container) - var/max_multiplier = min(design_last_ordered.maxstack, design_last_ordered.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/design_last_ordered.materials[MAT_METAL]):INFINITY,design_last_ordered.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/design_last_ordered.materials[MAT_GLASS]):INFINITY) - var/is_stack = ispath(design_last_ordered.build_path, /obj/item/stack) - - if(!is_stack && (multiplier > 1)) - return - if(!(multiplier in list(1, 10, 25, max_multiplier))) //"enough materials ?" is checked in the build proc - return - ///////////////// - - if((queue.len + 1) < queue_max_len) - add_to_queue(design_last_ordered,multiplier) - else - to_chat(usr, "The autolathe queue is full!") - if(!busy) - busy = 1 - process_queue() - busy = 0 - - if(href_list["remove_from_queue"]) - var/index = text2num(href_list["remove_from_queue"]) - if(isnum(index) && IsInRange(index, 1, queue.len)) - remove_from_queue(index) - if(href_list["queue_move"] && href_list["index"]) - var/index = text2num(href_list["index"]) - var/new_index = index + text2num(href_list["queue_move"]) - if(isnum(index) && isnum(new_index)) - if(IsInRange(new_index, 1, queue.len)) - queue.Swap(index,new_index) - if(href_list["clear_queue"]) - queue = list() - if(href_list["search"]) - if(href_list["to_search"]) - temp_search = href_list["to_search"] - if(!temp_search) - return - matching_designs.Cut() - - for(var/v in files.known_designs) - var/datum/design/D = files.known_designs[v] - if(findtext(D.name, temp_search)) - matching_designs.Add(D) - - screen = AUTOLATHE_SEARCH_MENU - - SSnanoui.update_uis(src) - return 1 - -/obj/machinery/autolathe/RefreshParts() - var/tot_rating = 0 - prod_coeff = 0 - for(var/obj/item/stock_parts/matter_bin/MB in component_parts) - tot_rating += MB.rating - tot_rating *= 25000 - GET_COMPONENT(materials, /datum/component/material_container) - materials.max_amount = tot_rating * 3 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - prod_coeff += M.rating - 1 - -/obj/machinery/autolathe/proc/get_coeff(datum/design/D) - var/coeff = (ispath(D.build_path,/obj/item/stack) ? 1 : 2 ** prod_coeff)//stacks are unaffected by production coefficient - return coeff - -/obj/machinery/autolathe/proc/build_item(datum/design/D, multiplier) - desc = initial(desc)+"\nIt's building \a [initial(D.name)]." - var/is_stack = ispath(D.build_path, /obj/item/stack) - var/coeff = get_coeff(D) - GET_COMPONENT(materials, /datum/component/material_container) - var/metal_cost = D.materials[MAT_METAL] - var/glass_cost = D.materials[MAT_GLASS] - var/power = max(2000, (metal_cost+glass_cost)*multiplier/5) - if(can_build(D, multiplier)) - being_built = list(D, multiplier) - use_power(power) - icon_state = "autolathe" - flick("autolathe_n",src) - if(is_stack) - var/list/materials_used = list(MAT_METAL=metal_cost*multiplier, MAT_GLASS=glass_cost*multiplier) - materials.use_amount(materials_used) - else - var/list/materials_used = list(MAT_METAL=metal_cost/coeff, MAT_GLASS=glass_cost/coeff) - materials.use_amount(materials_used) - SSnanoui.update_uis(src) - sleep(32/coeff) - if(is_stack) - var/obj/item/stack/S = new D.build_path(BuildTurf) - S.amount = multiplier - else - var/obj/item/new_item = new D.build_path(BuildTurf) - new_item.materials[MAT_METAL] /= coeff - new_item.materials[MAT_GLASS] /= coeff - SSnanoui.update_uis(src) - desc = initial(desc) - -/obj/machinery/autolathe/proc/can_build(datum/design/D, multiplier = 1, custom_metal, custom_glass) - if(D.make_reagents.len) - return 0 - - var/coeff = get_coeff(D) - GET_COMPONENT(materials, /datum/component/material_container) - var/metal_amount = materials.amount(MAT_METAL) - if(custom_metal) - metal_amount = custom_metal - var/glass_amount = materials.amount(MAT_GLASS) - if(custom_glass) - glass_amount = custom_glass - - if(D.materials[MAT_METAL] && (metal_amount < (multiplier*D.materials[MAT_METAL] / coeff))) - return 0 - if(D.materials[MAT_GLASS] && (glass_amount < (multiplier*D.materials[MAT_GLASS] / coeff))) - return 0 - return 1 - -/obj/machinery/autolathe/proc/get_design_cost_as_list(datum/design/D, multiplier = 1) - var/list/OutputList = list(0,0) - var/coeff = get_coeff(D) - if(D.materials[MAT_METAL]) - OutputList[1] = (D.materials[MAT_METAL] / coeff)*multiplier - if(D.materials[MAT_GLASS]) - OutputList[2] = (D.materials[MAT_GLASS] / coeff)*multiplier - return OutputList - -/obj/machinery/autolathe/proc/get_processing_line() - var/datum/design/D = being_built[1] - var/multiplier = being_built[2] - var/is_stack = (multiplier>1) - var/output = "PROCESSING: [initial(D.name)][is_stack?" (x[multiplier])":null]" - return output - -/obj/machinery/autolathe/proc/add_to_queue(D, multiplier) - if(!istype(queue)) - queue = list() - if(D) - queue.Add(list(list(D,multiplier))) - return queue.len - -/obj/machinery/autolathe/proc/remove_from_queue(index) - if(!isnum(index) || !istype(queue) || (index<1 || index>queue.len)) - return 0 - queue.Cut(index,++index) - return 1 - -/obj/machinery/autolathe/proc/process_queue() - var/datum/design/D = queue[1][1] - var/multiplier = queue[1][2] - if(!D) - remove_from_queue(1) - if(queue.len) - return process_queue() - else - return - while(D) - if(stat & (NOPOWER|BROKEN)) - being_built = new /list() - return 0 - if(!can_build(D, multiplier)) - visible_message("[bicon(src)] \The [src] beeps, \"Not enough resources. Queue processing terminated.\"") - queue = list() - being_built = new /list() - return 0 - - remove_from_queue(1) - build_item(D,multiplier) - D = listgetindex(listgetindex(queue, 1),1) - multiplier = listgetindex(listgetindex(queue,1),2) - being_built = new /list() - //visible_message("[bicon(src)] \The [src] beeps, \"Queue processing finished successfully.\"") - -/obj/machinery/autolathe/proc/adjust_hacked(hack) - hacked = hack - - if(hack) - for(var/datum/design/D in files.possible_designs) - if((D.build_type & AUTOLATHE) && ("hacked" in D.category)) - files.AddDesign2Known(D) - else - for(var/datum/design/D in files.known_designs) - if("hacked" in D.category) - files.known_designs -= D.id +#define AUTOLATHE_MAIN_MENU 1 +#define AUTOLATHE_CATEGORY_MENU 2 +#define AUTOLATHE_SEARCH_MENU 3 + +/obj/machinery/autolathe + name = "autolathe" + desc = "It produces items using metal and glass." + icon_state = "autolathe" + density = 1 + + var/operating = 0.0 + var/list/queue = list() + var/queue_max_len = 12 + var/turf/BuildTurf + anchored = 1.0 + var/list/L = list() + var/list/LL = list() + var/hacked = 0 + var/disabled = 0 + var/shocked = 0 + var/hack_wire + var/disable_wire + var/shock_wire + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + var/busy = 0 + var/prod_coeff + var/datum/wires/autolathe/wires = null + + var/list/being_built = list() + var/datum/research/files + var/list/datum/design/matching_designs + var/temp_search + var/selected_category + var/screen = 1 + + var/list/categories = list("Tools", "Electronics", "Construction", "Communication", "Security", "Machinery", "Medical", "Miscellaneous", "Dinnerware", "Imported") + +/obj/machinery/autolathe/New() + AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_GLASS), 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert)) + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/autolathe(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stack/sheet/glass(null) + RefreshParts() + + wires = new(src) + files = new /datum/research/autolathe(src) + matching_designs = list() + +/obj/machinery/autolathe/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/autolathe(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stack/sheet/glass(null) + RefreshParts() + +/obj/machinery/autolathe/Destroy() + QDEL_NULL(wires) + GET_COMPONENT(materials, /datum/component/material_container) + materials.retrieve_all() + return ..() + +/obj/machinery/autolathe/interact(mob/user) + if(shocked && !(stat & NOPOWER)) + if(shock(user, 50)) + return + + if(panel_open) + wires.Interact(user) + else + ui_interact(user) + +/obj/machinery/autolathe/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "autolathe.tmpl", name, 800, 550) + ui.open() + +/obj/machinery/autolathe/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + GET_COMPONENT(materials, /datum/component/material_container) + var/data[0] + data["screen"] = screen + data["total_amount"] = materials.total_amount + data["max_amount"] = materials.max_amount + data["metal_amount"] = materials.amount(MAT_METAL) + data["glass_amount"] = materials.amount(MAT_GLASS) + switch(screen) + if(AUTOLATHE_MAIN_MENU) + data["uid"] = UID() + data["categories"] = categories + if(AUTOLATHE_CATEGORY_MENU) + data["selected_category"] = selected_category + var/list/designs = list() + data["designs"] = designs + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(!(selected_category in D.category)) + continue + var/list/design = list() + designs[++designs.len] = design + design["name"] = D.name + design["id"] = D.id + design["disabled"] = disabled || !can_build(D) ? "disabled" : null + if(ispath(D.build_path, /obj/item/stack)) + design["max_multiplier"] = min(D.maxstack, D.materials[MAT_METAL] ? round(materials.amount(MAT_METAL) / D.materials[MAT_METAL]) : INFINITY, D.materials[MAT_GLASS] ? round(materials.amount(MAT_GLASS) / D.materials[MAT_GLASS]) : INFINITY) + else + design["max_multiplier"] = null + design["materials"] = design_cost_data(D) + if(AUTOLATHE_SEARCH_MENU) + data["search"] = temp_search + var/list/designs = list() + data["designs"] = designs + for(var/datum/design/D in matching_designs) + var/list/design = list() + designs[++designs.len] = design + design["name"] = D.name + design["id"] = D.id + design["disabled"] = disabled || !can_build(D) ? "disabled" : null + if(ispath(D.build_path, /obj/item/stack)) + design["max_multiplier"] = min(D.maxstack, D.materials[MAT_METAL] ? round(materials.amount(MAT_METAL) / D.materials[MAT_METAL]) : INFINITY, D.materials[MAT_GLASS] ? round(materials.amount(MAT_GLASS) / D.materials[MAT_GLASS]) : INFINITY) + else + design["max_multiplier"] = null + design["materials"] = design_cost_data(D) + + data = queue_data(data) + return data + +/obj/machinery/autolathe/proc/design_cost_data(datum/design/D) + var/list/data = list() + var/coeff = get_coeff(D) + GET_COMPONENT(materials, /datum/component/material_container) + var/has_metal = 1 + if(D.materials[MAT_METAL] && (materials.amount(MAT_METAL) < (D.materials[MAT_METAL] / coeff))) + has_metal = 0 + var/has_glass = 1 + if(D.materials[MAT_GLASS] && (materials.amount(MAT_GLASS) < (D.materials[MAT_GLASS] / coeff))) + has_glass = 0 + + data[++data.len] = list("name" = "metal", "amount" = D.materials[MAT_METAL] / coeff, "is_red" = !has_metal) + data[++data.len] = list("name" = "glass", "amount" = D.materials[MAT_GLASS] / coeff, "is_red" = !has_glass) + + return data + +/obj/machinery/autolathe/proc/queue_data(list/data) + GET_COMPONENT(materials, /datum/component/material_container) + var/temp_metal = materials.amount(MAT_METAL) + var/temp_glass = materials.amount(MAT_GLASS) + data["processing"] = being_built.len ? get_processing_line() : null + if(istype(queue) && queue.len) + var/list/data_queue = list() + for(var/list/L in queue) + var/datum/design/D = L[1] + var/list/LL = get_design_cost_as_list(D, L[2]) + data_queue[++data_queue.len] = list("name" = initial(D.name), "can_build" = can_build(D, L[2], temp_metal, temp_glass), "multiplier" = L[2]) + temp_metal = max(temp_metal - LL[1], 1) + temp_glass = max(temp_glass - LL[2], 1) + data["queue"] = data_queue + data["queue_len"] = data_queue.len + else + data["queue"] = null + return data + +/obj/machinery/autolathe/attackby(obj/item/O, mob/user, params) + if(busy) + to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") + return 1 + if(exchange_parts(user, O)) + return + if(stat) + return 1 + + // Disks in general + if(istype(O, /obj/item/disk)) + if(istype(O, /obj/item/disk/design_disk)) + var/obj/item/disk/design_disk/D = O + if(D.blueprint) + user.visible_message("[user] begins to load \the [O] in \the [src]...", + "You begin to load a design from \the [O]...", + "You hear the chatter of a floppy drive.") + playsound(get_turf(src), 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) + busy = 1 + if(do_after(user, 14.4, target = src)) + files.AddDesign2Known(D.blueprint) + busy = 0 + else + to_chat(user, "That disk does not have a design on it!") + return 1 + else + // So that people who are bad at computers don't shred their disks + to_chat(user, "This is not the correct type of disk for the autolathe!") + return 1 + + return ..() + +/obj/machinery/autolathe/crowbar_act(mob/user, obj/item/I) + if(!I.use_tool(src, user, 0, volume = 0)) + return + . = TRUE + if(busy) + to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") + return + if(panel_open) + default_deconstruction_crowbar(user, I) + I.play_tool_sound(user, I.tool_volume) + +/obj/machinery/autolathe/screwdriver_act(mob/user, obj/item/I) + if(!I.use_tool(src, user, 0, volume = 0)) + return + . = TRUE + if(busy) + to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") + return + if(default_deconstruction_screwdriver(user, "autolathe_t", "autolathe", I)) + SSnanoui.update_uis(src) + I.play_tool_sound(user, I.tool_volume) + +/obj/machinery/autolathe/wirecutter_act(mob/user, obj/item/I) + if(!panel_open) + return + if(!I.use_tool(src, user, 0, volume = 0)) + return + . = TRUE + if(busy) + to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") + return + interact(user) + +/obj/machinery/autolathe/multitool_act(mob/user, obj/item/I) + if(!panel_open) + return + if(!I.use_tool(src, user, 0, volume = 0)) + return + . = TRUE + if(busy) + to_chat(user, "The autolathe is busy. Please wait for completion of previous operation.") + return + interact(user) + +/obj/machinery/autolathe/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) + switch(id_inserted) + if(MAT_METAL) + flick("autolathe_o", src)//plays metal insertion animation + if(MAT_GLASS) + flick("autolathe_r", src)//plays glass insertion animation + use_power(min(1000, amount_inserted / 100)) + SSnanoui.update_uis(src) + +/obj/machinery/autolathe/attack_ghost(mob/user) + interact(user) + +/obj/machinery/autolathe/attack_hand(mob/user) + if(..(user, 0)) + return + interact(user) + +/obj/machinery/autolathe/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["menu"]) + screen = text2num(href_list["menu"]) + + if(href_list["category"]) + selected_category = href_list["category"] + screen = AUTOLATHE_CATEGORY_MENU + + if(href_list["make"]) + BuildTurf = loc + + ///////////////// + //href protection + var/datum/design/design_last_ordered + design_last_ordered = files.FindDesignByID(href_list["make"]) //check if it's a valid design + if(!design_last_ordered) + return + if(!(design_last_ordered.build_type & AUTOLATHE)) + return + + //multiplier checks : only stacks can have one and its value is 1, 10 ,25 or max_multiplier + var/multiplier = text2num(href_list["multiplier"]) + GET_COMPONENT(materials, /datum/component/material_container) + var/max_multiplier = min(design_last_ordered.maxstack, design_last_ordered.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/design_last_ordered.materials[MAT_METAL]):INFINITY,design_last_ordered.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/design_last_ordered.materials[MAT_GLASS]):INFINITY) + var/is_stack = ispath(design_last_ordered.build_path, /obj/item/stack) + + if(!is_stack && (multiplier > 1)) + return + if(!(multiplier in list(1, 10, 25, max_multiplier))) //"enough materials ?" is checked in the build proc + return + ///////////////// + + if((queue.len + 1) < queue_max_len) + add_to_queue(design_last_ordered,multiplier) + else + to_chat(usr, "The autolathe queue is full!") + if(!busy) + busy = 1 + process_queue() + busy = 0 + + if(href_list["remove_from_queue"]) + var/index = text2num(href_list["remove_from_queue"]) + if(isnum(index) && IsInRange(index, 1, queue.len)) + remove_from_queue(index) + if(href_list["queue_move"] && href_list["index"]) + var/index = text2num(href_list["index"]) + var/new_index = index + text2num(href_list["queue_move"]) + if(isnum(index) && isnum(new_index)) + if(IsInRange(new_index, 1, queue.len)) + queue.Swap(index,new_index) + if(href_list["clear_queue"]) + queue = list() + if(href_list["search"]) + if(href_list["to_search"]) + temp_search = href_list["to_search"] + if(!temp_search) + return + matching_designs.Cut() + + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(findtext(D.name, temp_search)) + matching_designs.Add(D) + + screen = AUTOLATHE_SEARCH_MENU + + SSnanoui.update_uis(src) + return 1 + +/obj/machinery/autolathe/RefreshParts() + var/tot_rating = 0 + prod_coeff = 0 + for(var/obj/item/stock_parts/matter_bin/MB in component_parts) + tot_rating += MB.rating + tot_rating *= 25000 + GET_COMPONENT(materials, /datum/component/material_container) + materials.max_amount = tot_rating * 3 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + prod_coeff += M.rating - 1 + +/obj/machinery/autolathe/proc/get_coeff(datum/design/D) + var/coeff = (ispath(D.build_path,/obj/item/stack) ? 1 : 2 ** prod_coeff)//stacks are unaffected by production coefficient + return coeff + +/obj/machinery/autolathe/proc/build_item(datum/design/D, multiplier) + desc = initial(desc)+"\nIt's building \a [initial(D.name)]." + var/is_stack = ispath(D.build_path, /obj/item/stack) + var/coeff = get_coeff(D) + GET_COMPONENT(materials, /datum/component/material_container) + var/metal_cost = D.materials[MAT_METAL] + var/glass_cost = D.materials[MAT_GLASS] + var/power = max(2000, (metal_cost+glass_cost)*multiplier/5) + if(can_build(D, multiplier)) + being_built = list(D, multiplier) + use_power(power) + icon_state = "autolathe" + flick("autolathe_n",src) + if(is_stack) + var/list/materials_used = list(MAT_METAL=metal_cost*multiplier, MAT_GLASS=glass_cost*multiplier) + materials.use_amount(materials_used) + else + var/list/materials_used = list(MAT_METAL=metal_cost/coeff, MAT_GLASS=glass_cost/coeff) + materials.use_amount(materials_used) + SSnanoui.update_uis(src) + sleep(32/coeff) + if(is_stack) + var/obj/item/stack/S = new D.build_path(BuildTurf) + S.amount = multiplier + else + var/obj/item/new_item = new D.build_path(BuildTurf) + new_item.materials[MAT_METAL] /= coeff + new_item.materials[MAT_GLASS] /= coeff + SSnanoui.update_uis(src) + desc = initial(desc) + +/obj/machinery/autolathe/proc/can_build(datum/design/D, multiplier = 1, custom_metal, custom_glass) + if(D.make_reagents.len) + return 0 + + var/coeff = get_coeff(D) + GET_COMPONENT(materials, /datum/component/material_container) + var/metal_amount = materials.amount(MAT_METAL) + if(custom_metal) + metal_amount = custom_metal + var/glass_amount = materials.amount(MAT_GLASS) + if(custom_glass) + glass_amount = custom_glass + + if(D.materials[MAT_METAL] && (metal_amount < (multiplier*D.materials[MAT_METAL] / coeff))) + return 0 + if(D.materials[MAT_GLASS] && (glass_amount < (multiplier*D.materials[MAT_GLASS] / coeff))) + return 0 + return 1 + +/obj/machinery/autolathe/proc/get_design_cost_as_list(datum/design/D, multiplier = 1) + var/list/OutputList = list(0,0) + var/coeff = get_coeff(D) + if(D.materials[MAT_METAL]) + OutputList[1] = (D.materials[MAT_METAL] / coeff)*multiplier + if(D.materials[MAT_GLASS]) + OutputList[2] = (D.materials[MAT_GLASS] / coeff)*multiplier + return OutputList + +/obj/machinery/autolathe/proc/get_processing_line() + var/datum/design/D = being_built[1] + var/multiplier = being_built[2] + var/is_stack = (multiplier>1) + var/output = "PROCESSING: [initial(D.name)][is_stack?" (x[multiplier])":null]" + return output + +/obj/machinery/autolathe/proc/add_to_queue(D, multiplier) + if(!istype(queue)) + queue = list() + if(D) + queue.Add(list(list(D,multiplier))) + return queue.len + +/obj/machinery/autolathe/proc/remove_from_queue(index) + if(!isnum(index) || !istype(queue) || (index<1 || index>queue.len)) + return 0 + queue.Cut(index,++index) + return 1 + +/obj/machinery/autolathe/proc/process_queue() + var/datum/design/D = queue[1][1] + var/multiplier = queue[1][2] + if(!D) + remove_from_queue(1) + if(queue.len) + return process_queue() + else + return + while(D) + if(stat & (NOPOWER|BROKEN)) + being_built = new /list() + return 0 + if(!can_build(D, multiplier)) + visible_message("[bicon(src)] \The [src] beeps, \"Not enough resources. Queue processing terminated.\"") + queue = list() + being_built = new /list() + return 0 + + remove_from_queue(1) + build_item(D,multiplier) + D = listgetindex(listgetindex(queue, 1),1) + multiplier = listgetindex(listgetindex(queue,1),2) + being_built = new /list() + //visible_message("[bicon(src)] \The [src] beeps, \"Queue processing finished successfully.\"") + +/obj/machinery/autolathe/proc/adjust_hacked(hack) + hacked = hack + + if(hack) + for(var/datum/design/D in files.possible_designs) + if((D.build_type & AUTOLATHE) && ("hacked" in D.category)) + files.AddDesign2Known(D) + else + for(var/datum/design/D in files.known_designs) + if("hacked" in D.category) + files.known_designs -= D.id diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index f91152b47e4..796d5c552dd 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -1,206 +1,206 @@ - -////////////////////////////////////// -// Driver Button // -////////////////////////////////////// - -/obj/machinery/driver_button - name = "mass driver button" - icon = 'icons/obj/objects.dmi' - icon_state = "launcherbtt" - desc = "A remote control switch for a mass driver." - var/id_tag = "default" - var/active = 0 - settagwhitelist = list("id_tag", "logic_id_tag") - anchored = 1.0 - armor = list(melee = 50, bullet = 50, laser = 50, energy = 50, bomb = 10, bio = 100, rad = 100, fire = 90, acid = 70) - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - resistance_flags = LAVA_PROOF | FIRE_PROOF - var/range = 7 - - var/datum/radio_frequency/radio_connection - var/frequency = 0 - var/logic_id_tag = "default" //Defines the ID tag to send logic signals to, so you don't have to unlink from doors and stuff - var/logic_connect = 0 //Set this to allow the button to send out logic signals when pressed in addition to normal stuff - -/obj/machinery/button/indestructible - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/driver_button/New(turf/loc, var/w_dir=null) - ..() - switch(w_dir) - if(NORTH) - pixel_y = 25 - if(SOUTH) - pixel_y = -25 - if(EAST) - pixel_x = 25 - if(WEST) - pixel_x = -25 - if(SSradio) - set_frequency(frequency) - -/obj/machinery/driver_button/Initialize() - ..() - set_frequency(frequency) - -/obj/machinery/driver_button/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_LOGIC) - return - -/obj/machinery/driver_button/Destroy() - if(SSradio) - SSradio.remove_object(src, frequency) - radio_connection = null - return ..() - - -/obj/machinery/driver_button/attack_ai(mob/user as mob) - return attack_hand(user) - -/obj/machinery/driver_button/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - return attack_hand(user) - -/obj/machinery/driver_button/attackby(obj/item/W, mob/user as mob, params) - - if(istype(W, /obj/item/detective_scanner)) - return - - if(istype(W, /obj/item/multitool)) - update_multitool_menu(user) - return 1 - - if(istype(W, /obj/item/wrench)) - playsound(get_turf(src), W.usesound, 50, 1) - if(do_after(user, 30 * W.toolspeed, target = src)) - to_chat(user, "You detach \the [src] from the wall.") - new/obj/item/mounted/frame/driver_button(get_turf(src)) - qdel(src) - return 1 - - return ..() - -/obj/machinery/driver_button/multitool_menu(var/mob/user, var/obj/item/multitool/P) - return {" -
          -
        • ID Tag: [format_tag("ID Tag","id_tag")]
        • -
        • Logic Connection: [logic_connect ? "On" : "Off"]
        • -
        • Logic ID Tag: [format_tag("Logic ID Tag", "logic_id_tag")]
        • -
        "} - -/obj/machinery/driver_button/attack_hand(mob/user as mob) - - add_fingerprint(usr) - if(stat & (NOPOWER|BROKEN)) - return - if(active) - return - add_fingerprint(user) - - use_power(5) - - launch_sequence() - -/obj/machinery/driver_button/proc/launch_sequence() - active = 1 - icon_state = "launcheract" - - if(logic_connect) - if(!radio_connection) //can't output without this - return - - if(logic_id_tag == null) //Don't output to an undefined id_tag - return - - var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal - signal.source = src - - signal.data = list( - "tag" = logic_id_tag, - "sigtype" = "logic", - "state" = LOGIC_FLICKER, //Buttons are a FLICKER source, since they only register as ON when you press it, then turn OFF after you release - ) - - radio_connection.post_signal(src, signal, filter = RADIO_LOGIC) - - for(var/obj/machinery/door/poddoor/M in range(src,range)) - if(M.id_tag == id_tag && !M.protected) - spawn() - M.open() - - sleep(20) - - for(var/obj/machinery/mass_driver/M in range(src,range)) - if(M.id_tag == id_tag) - M.drive() - - sleep(50) - - for(var/obj/machinery/door/poddoor/M in range(src,range)) - if(M.id_tag == id_tag && !M.protected) - spawn() - M.close() - return - - icon_state = "launcherbtt" - active = 0 - -/obj/machinery/driver_button/multitool_topic(var/mob/user,var/list/href_list,var/obj/O) - ..() - if("toggle_logic" in href_list) - logic_connect = !logic_connect - -////////////////////////////////////// -// Ignition Switch // -////////////////////////////////////// - -/obj/machinery/ignition_switch - name = "ignition switch" - icon = 'icons/obj/objects.dmi' - icon_state = "launcherbtt" - desc = "A remote control switch for a mounted igniter." - var/id = null - var/active = 0 - anchored = 1.0 - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - -/obj/machinery/ignition_switch/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/ignition_switch/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - return attack_hand(user) - -/obj/machinery/ignition_switch/attack_hand(mob/user) - if(stat & (NOPOWER|BROKEN)) - return - if(active) - return - - use_power(5) - - active = 1 - icon_state = "launcheract" - - for(var/obj/machinery/sparker/M in world) - if(M.id == id) - spawn( 0 ) - M.spark() - - for(var/obj/machinery/igniter/M in world) - if(M.id == id) - use_power(50) - M.on = !( M.on ) - M.icon_state = text("igniter[]", M.on) - - sleep(50) - - icon_state = "launcherbtt" - active = 0 + +////////////////////////////////////// +// Driver Button // +////////////////////////////////////// + +/obj/machinery/driver_button + name = "mass driver button" + icon = 'icons/obj/objects.dmi' + icon_state = "launcherbtt" + desc = "A remote control switch for a mass driver." + var/id_tag = "default" + var/active = 0 + settagwhitelist = list("id_tag", "logic_id_tag") + anchored = 1.0 + armor = list(melee = 50, bullet = 50, laser = 50, energy = 50, bomb = 10, bio = 100, rad = 100, fire = 90, acid = 70) + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + resistance_flags = LAVA_PROOF | FIRE_PROOF + var/range = 7 + + var/datum/radio_frequency/radio_connection + var/frequency = 0 + var/logic_id_tag = "default" //Defines the ID tag to send logic signals to, so you don't have to unlink from doors and stuff + var/logic_connect = 0 //Set this to allow the button to send out logic signals when pressed in addition to normal stuff + +/obj/machinery/button/indestructible + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/driver_button/New(turf/loc, var/w_dir=null) + ..() + switch(w_dir) + if(NORTH) + pixel_y = 25 + if(SOUTH) + pixel_y = -25 + if(EAST) + pixel_x = 25 + if(WEST) + pixel_x = -25 + if(SSradio) + set_frequency(frequency) + +/obj/machinery/driver_button/Initialize() + ..() + set_frequency(frequency) + +/obj/machinery/driver_button/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_LOGIC) + return + +/obj/machinery/driver_button/Destroy() + if(SSradio) + SSradio.remove_object(src, frequency) + radio_connection = null + return ..() + + +/obj/machinery/driver_button/attack_ai(mob/user as mob) + return attack_hand(user) + +/obj/machinery/driver_button/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + return attack_hand(user) + +/obj/machinery/driver_button/attackby(obj/item/W, mob/user as mob, params) + + if(istype(W, /obj/item/detective_scanner)) + return + + if(istype(W, /obj/item/multitool)) + update_multitool_menu(user) + return 1 + + if(istype(W, /obj/item/wrench)) + playsound(get_turf(src), W.usesound, 50, 1) + if(do_after(user, 30 * W.toolspeed, target = src)) + to_chat(user, "You detach \the [src] from the wall.") + new/obj/item/mounted/frame/driver_button(get_turf(src)) + qdel(src) + return 1 + + return ..() + +/obj/machinery/driver_button/multitool_menu(var/mob/user, var/obj/item/multitool/P) + return {" +
          +
        • ID Tag: [format_tag("ID Tag","id_tag")]
        • +
        • Logic Connection: [logic_connect ? "On" : "Off"]
        • +
        • Logic ID Tag: [format_tag("Logic ID Tag", "logic_id_tag")]
        • +
        "} + +/obj/machinery/driver_button/attack_hand(mob/user as mob) + + add_fingerprint(usr) + if(stat & (NOPOWER|BROKEN)) + return + if(active) + return + add_fingerprint(user) + + use_power(5) + + launch_sequence() + +/obj/machinery/driver_button/proc/launch_sequence() + active = 1 + icon_state = "launcheract" + + if(logic_connect) + if(!radio_connection) //can't output without this + return + + if(logic_id_tag == null) //Don't output to an undefined id_tag + return + + var/datum/signal/signal = new + signal.transmission_method = 1 //radio signal + signal.source = src + + signal.data = list( + "tag" = logic_id_tag, + "sigtype" = "logic", + "state" = LOGIC_FLICKER, //Buttons are a FLICKER source, since they only register as ON when you press it, then turn OFF after you release + ) + + radio_connection.post_signal(src, signal, filter = RADIO_LOGIC) + + for(var/obj/machinery/door/poddoor/M in range(src,range)) + if(M.id_tag == id_tag && !M.protected) + spawn() + M.open() + + sleep(20) + + for(var/obj/machinery/mass_driver/M in range(src,range)) + if(M.id_tag == id_tag) + M.drive() + + sleep(50) + + for(var/obj/machinery/door/poddoor/M in range(src,range)) + if(M.id_tag == id_tag && !M.protected) + spawn() + M.close() + return + + icon_state = "launcherbtt" + active = 0 + +/obj/machinery/driver_button/multitool_topic(var/mob/user,var/list/href_list,var/obj/O) + ..() + if("toggle_logic" in href_list) + logic_connect = !logic_connect + +////////////////////////////////////// +// Ignition Switch // +////////////////////////////////////// + +/obj/machinery/ignition_switch + name = "ignition switch" + icon = 'icons/obj/objects.dmi' + icon_state = "launcherbtt" + desc = "A remote control switch for a mounted igniter." + var/id = null + var/active = 0 + anchored = 1.0 + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + +/obj/machinery/ignition_switch/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/ignition_switch/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + return attack_hand(user) + +/obj/machinery/ignition_switch/attack_hand(mob/user) + if(stat & (NOPOWER|BROKEN)) + return + if(active) + return + + use_power(5) + + active = 1 + icon_state = "launcheract" + + for(var/obj/machinery/sparker/M in world) + if(M.id == id) + spawn( 0 ) + M.spark() + + for(var/obj/machinery/igniter/M in world) + if(M.id == id) + use_power(50) + M.on = !( M.on ) + M.icon_state = text("igniter[]", M.on) + + sleep(50) + + icon_state = "launcherbtt" + active = 0 diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index bb09c247bca..c097a19f4f8 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -1,433 +1,433 @@ -/obj/machinery/camera - name = "security camera" - desc = "It's used to monitor rooms." - icon = 'icons/obj/monitors.dmi' - icon_state = "camera" - use_power = ACTIVE_POWER_USE - idle_power_usage = 5 - active_power_usage = 10 - layer = WALL_OBJ_LAYER - resistance_flags = FIRE_PROOF - damage_deflection = 12 - armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 50) - var/datum/wires/camera/wires = null // Wires datum - max_integrity = 100 - integrity_failure = 50 - var/list/network = list("SS13") - var/c_tag = null - var/c_tag_order = 999 - var/status = 1 - anchored = TRUE - var/start_active = FALSE //If it ignores the random chance to start broken on round start - var/invuln = null - var/obj/item/camera_bug/bug = null - var/obj/item/camera_assembly/assembly = null - - //OTHER - - var/view_range = 7 - var/short_range = 2 - - var/busy = FALSE - var/emped = FALSE //Number of consecutive EMP's on this camera - - var/in_use_lights = 0 // TO BE IMPLEMENTED - var/toggle_sound = 'sound/items/wirecutter.ogg' - - -/obj/machinery/camera/New() - ..() - wires = new(src) - assembly = new(src) - assembly.state = 4 - assembly.anchored = 1 - assembly.update_icon() - - cameranet.cameras += src - cameranet.addCamera(src) - -/obj/machinery/camera/Initialize() - ..() - if(is_station_level(z) && prob(3) && !start_active) - toggle_cam(null, FALSE) - wires.CutAll() - -/obj/machinery/camera/Destroy() - toggle_cam(null, FALSE) //kick anyone viewing out - QDEL_NULL(assembly) - if(istype(bug)) - bug.bugged_cameras -= c_tag - if(bug.current == src) - bug.current = null - bug = null - QDEL_NULL(wires) - cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that - cameranet.cameras -= src - var/area/ai_monitored/A = get_area(src) - if(istype(A)) - A.motioncamera = null - area_motion = null - return ..() - -/obj/machinery/camera/emp_act(severity) - if(!status) - return - if(!isEmpProof()) - if(prob(150/severity)) - update_icon() - var/list/previous_network = network - network = list() - cameranet.removeCamera(src) - stat |= EMPED - set_light(0) - emped = emped+1 //Increase the number of consecutive EMP's - update_icon() - var/thisemp = emped //Take note of which EMP this proc is for - spawn(900) - if(!QDELETED(src)) - triggerCameraAlarm() //camera alarm triggers even if multiple EMPs are in effect. - if(emped == thisemp) //Only fix it if the camera hasn't been EMP'd again - network = previous_network - stat &= ~EMPED - update_icon() - if(can_use()) - cameranet.addCamera(src) - emped = 0 //Resets the consecutive EMP count - spawn(100) - if(!QDELETED(src)) - cancelCameraAlarm() - for(var/mob/M in GLOB.player_list) - if(M.client && M.client.eye == src) - M.unset_machine() - M.reset_perspective(null) - to_chat(M, "The screen bursts into static.") - ..() - -/obj/machinery/camera/tesla_act(power)//EMP proof upgrade also makes it tesla immune - if(isEmpProof()) - return - ..() - qdel(src)//to prevent bomb testing camera from exploding over and over forever - -/obj/machinery/camera/ex_act(severity) - if(invuln) - return - ..() - -/obj/machinery/camera/proc/setViewRange(num = 7) - view_range = num - cameranet.updateVisibility(src, 0) - -/obj/machinery/camera/singularity_pull(S, current_size) - if (status && current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects and the camera is still active, turn off the camera as it gets ripped off the wall. - toggle_cam(null, 0) - ..() - -/obj/machinery/camera/attackby(obj/item/I, mob/living/user, params) - var/msg = "You attach [I] into the assembly inner circuits." - var/msg2 = "The camera already has that upgrade!" - - if(istype(I, /obj/item/analyzer) && panel_open) //XRay - if(!user.drop_item()) - to_chat(user, "[I] is stuck to your hand!") - return - if(!isXRay()) - upgradeXRay() - qdel(I) - to_chat(user, "[msg]") - else - to_chat(user, "[msg2]") - - else if(istype(I, /obj/item/stack/sheet/mineral/plasma) && panel_open) - if(!user.drop_item()) - to_chat(user, "[I] is stuck to your hand!") - return - if(!isEmpProof()) - var/obj/item/stack/sheet/mineral/plasma/P = I - upgradeEmpProof() - to_chat(user, "[msg]") - P.use(1) - else - to_chat(user, "[msg2]") - else if(istype(I, /obj/item/assembly/prox_sensor) && panel_open) - if(!user.drop_item()) - to_chat(user, "[I] is stuck to your hand!") - return - if(!isMotion()) - upgradeMotion() - to_chat(user, "[msg]") - qdel(I) - else - to_chat(user, "[msg2]") - - // OTHER - else if((istype(I, /obj/item/paper) || istype(I, /obj/item/pda)) && isliving(user)) - var/mob/living/U = user - var/obj/item/paper/X = null - var/obj/item/pda/PDA = null - - var/itemname = "" - var/info = "" - if(istype(I, /obj/item/paper)) - X = I - itemname = X.name - info = X.info - else - PDA = I - var/datum/data/pda/app/notekeeper/N = PDA.find_program(/datum/data/pda/app/notekeeper) - if(N) - itemname = PDA.name - info = N.notehtml - to_chat(U, "You hold \the [itemname] up to the camera ...") - U.changeNext_move(CLICK_CD_MELEE) - for(var/mob/O in GLOB.player_list) - if(istype(O, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/AI = O - if(AI.control_disabled || (AI.stat == DEAD)) - return - if(U.name == "Unknown") - to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...") - else - to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...") - AI.last_paper_seen = "[itemname][info]" - else if(O.client && O.client.eye == src) - to_chat(O, "[U] holds \a [itemname] up to one of the cameras ...") - O << browse(text("[][]", itemname, info), text("window=[]", itemname)) - - else if(istype(I, /obj/item/camera_bug)) - if(!can_use()) - to_chat(user, "Camera non-functional.") - return - if(istype(bug)) - to_chat(user, "Camera bug removed.") - bug.bugged_cameras -= c_tag - bug = null - else - to_chat(user, "Camera bugged.") - bug = I - bug.bugged_cameras[c_tag] = src - - else if(istype(I, /obj/item/laser_pointer)) - var/obj/item/laser_pointer/L = I - L.laser_act(src, user) - else - return ..() - - -/obj/machinery/camera/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - panel_open = !panel_open - to_chat(user, "You screw [src]'s panel [panel_open ? "open" : "closed"].") - -/obj/machinery/camera/wirecutter_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = 0)) - return - if(panel_open) - wires.Interact(user) - -/obj/machinery/camera/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = 0)) - return - if(panel_open) - wires.Interact(user) - -/obj/machinery/camera/welder_act(mob/user, obj/item/I) - if(!panel_open || !wires.CanDeconstruct()) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_WELD_MESSAGE - if(I.use_tool(src, user, 100, volume = I.tool_volume)) - visible_message("[user] unwelds [src], leaving it as just a frame bolted to the wall.", - "You unweld [src], leaving it as just a frame bolted to the wall") - deconstruct(TRUE) - -/obj/machinery/camera/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(stat & BROKEN) - return damage_amount - . = ..() - -/obj/machinery/camera/obj_break(damage_flag) - if(status && !(flags & NODECONSTRUCT)) - triggerCameraAlarm() - toggle_cam(null, FALSE) - wires.CutAll() - -/obj/machinery/camera/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(disassembled) - if(!assembly) - assembly = new() - assembly.forceMove(drop_location()) - assembly.state = 1 - assembly.setDir(dir) - assembly.update_icon() - assembly = null - else - var/obj/item/I = new /obj/item/camera_assembly(loc) - I.obj_integrity = I.max_integrity * 0.5 - new /obj/item/stack/cable_coil(loc, 2) - qdel(src) - -/obj/machinery/camera/update_icon() - if(!status) - icon_state = "[initial(icon_state)]1" - else if(stat & EMPED) - icon_state = "[initial(icon_state)]emp" - else - icon_state = "[initial(icon_state)]" - -/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = TRUE) - status = !status - if(can_use()) - cameranet.addCamera(src) - else - set_light(0) - cameranet.removeCamera(src) - cameranet.updateChunk(x, y, z) - var/change_msg = "deactivates" - if(status) - change_msg = "reactivates" - triggerCameraAlarm() - spawn(100) - if(!QDELETED(src)) - cancelCameraAlarm() - if(displaymessage) - if(user) - visible_message("[user] [change_msg] [src]!") - add_hiddenprint(user) - else - visible_message("\The [src] [change_msg]!") - - playsound(loc, toggle_sound, 100, 1) - update_icon() - - // now disconnect anyone using the camera - //Apparently, this will disconnect anyone even if the camera was re-activated. - //I guess that doesn't matter since they can't use it anyway? - for(var/mob/O in GLOB.player_list) - if(O.client && O.client.eye == src) - O.unset_machine() - O.reset_perspective(null) - to_chat(O, "The screen bursts into static.") - -/obj/machinery/camera/proc/triggerCameraAlarm() - if(is_station_contact(z)) - SSalarms.camera_alarm.triggerAlarm(loc, src) - -/obj/machinery/camera/proc/cancelCameraAlarm() - if(is_station_contact(z)) - SSalarms.camera_alarm.clearAlarm(loc, src) - -/obj/machinery/camera/proc/can_use() - if(!status) - return 0 - if(stat & EMPED) - return 0 - return 1 - -/obj/machinery/camera/proc/can_see() - var/list/see = null - var/turf/pos = get_turf(src) - if(isXRay()) - see = range(view_range, pos) - else - see = hear(view_range, pos) - return see - -/atom/proc/auto_turn() - //Automatically turns based on nearby walls. - var/turf/simulated/wall/T = null - for(var/i = 1, i <= 8; i += i) - T = get_ranged_target_turf(src, i, 1) - if(istype(T)) - //If someone knows a better way to do this, let me know. -Giacom - switch(i) - if(NORTH) - setDir(SOUTH) - if(SOUTH) - setDir(NORTH) - if(WEST) - setDir(EAST) - if(EAST) - setDir(WEST) - break - -//Return a working camera that can see a given mob -//or null if none -/proc/seen_by_camera(mob/M) - for(var/obj/machinery/camera/C in oview(4, M)) - if(C.can_use()) // check if camera disabled - return C - break - return null - -/proc/near_range_camera(mob/M) - for(var/obj/machinery/camera/C in range(4, M)) - if(C.can_use()) // check if camera disabled - return C - break - - return null - -/obj/machinery/camera/proc/Togglelight(on = FALSE) - for(var/mob/living/silicon/ai/A in ai_list) - for(var/obj/machinery/camera/cam in A.lit_cameras) - if(cam == src) - return - if(on) - set_light(AI_CAMERA_LUMINOSITY) - else - set_light(0) - -/obj/machinery/camera/proc/nano_structure() - var/cam[0] - var/turf/T = get_turf(src) - cam["name"] = sanitize(c_tag) - cam["deact"] = !can_use() - cam["camera"] = "\ref[src]" - if(T) - cam["x"] = T.x - cam["y"] = T.y - cam["z"] = T.z - else - cam["x"] = 0 - cam["y"] = 0 - cam["z"] = 0 - return cam - -/obj/machinery/camera/get_remote_view_fullscreens(mob/user) - if(view_range == short_range) //unfocused - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) - -/obj/machinery/camera/update_remote_sight(mob/living/user) - if(isXRay()) - user.sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) - user.see_in_dark = max(user.see_in_dark, 8) - user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - else - user.sight = initial(user.sight) - user.see_in_dark = initial(user.see_in_dark) - user.lighting_alpha = initial(user.lighting_alpha) - - ..() - return TRUE - -/obj/machinery/camera/portable //Cameras which are placed inside of things, such as helmets. - var/turf/prev_turf - -/obj/machinery/camera/portable/New() - ..() - assembly.state = 0 //These cameras are portable, and so shall be in the portable state if removed. - assembly.anchored = 0 - assembly.update_icon() - -/obj/machinery/camera/portable/process() //Updates whenever the camera is moved. - if(cameranet && get_turf(src) != prev_turf) - cameranet.updatePortableCamera(src) - prev_turf = get_turf(src) +/obj/machinery/camera + name = "security camera" + desc = "It's used to monitor rooms." + icon = 'icons/obj/monitors.dmi' + icon_state = "camera" + use_power = ACTIVE_POWER_USE + idle_power_usage = 5 + active_power_usage = 10 + layer = WALL_OBJ_LAYER + resistance_flags = FIRE_PROOF + damage_deflection = 12 + armor = list("melee" = 50, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 50) + var/datum/wires/camera/wires = null // Wires datum + max_integrity = 100 + integrity_failure = 50 + var/list/network = list("SS13") + var/c_tag = null + var/c_tag_order = 999 + var/status = 1 + anchored = TRUE + var/start_active = FALSE //If it ignores the random chance to start broken on round start + var/invuln = null + var/obj/item/camera_bug/bug = null + var/obj/item/camera_assembly/assembly = null + + //OTHER + + var/view_range = 7 + var/short_range = 2 + + var/busy = FALSE + var/emped = FALSE //Number of consecutive EMP's on this camera + + var/in_use_lights = 0 // TO BE IMPLEMENTED + var/toggle_sound = 'sound/items/wirecutter.ogg' + + +/obj/machinery/camera/New() + ..() + wires = new(src) + assembly = new(src) + assembly.state = 4 + assembly.anchored = 1 + assembly.update_icon() + + cameranet.cameras += src + cameranet.addCamera(src) + +/obj/machinery/camera/Initialize() + ..() + if(is_station_level(z) && prob(3) && !start_active) + toggle_cam(null, FALSE) + wires.CutAll() + +/obj/machinery/camera/Destroy() + toggle_cam(null, FALSE) //kick anyone viewing out + QDEL_NULL(assembly) + if(istype(bug)) + bug.bugged_cameras -= c_tag + if(bug.current == src) + bug.current = null + bug = null + QDEL_NULL(wires) + cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that + cameranet.cameras -= src + var/area/ai_monitored/A = get_area(src) + if(istype(A)) + A.motioncamera = null + area_motion = null + return ..() + +/obj/machinery/camera/emp_act(severity) + if(!status) + return + if(!isEmpProof()) + if(prob(150/severity)) + update_icon() + var/list/previous_network = network + network = list() + cameranet.removeCamera(src) + stat |= EMPED + set_light(0) + emped = emped+1 //Increase the number of consecutive EMP's + update_icon() + var/thisemp = emped //Take note of which EMP this proc is for + spawn(900) + if(!QDELETED(src)) + triggerCameraAlarm() //camera alarm triggers even if multiple EMPs are in effect. + if(emped == thisemp) //Only fix it if the camera hasn't been EMP'd again + network = previous_network + stat &= ~EMPED + update_icon() + if(can_use()) + cameranet.addCamera(src) + emped = 0 //Resets the consecutive EMP count + spawn(100) + if(!QDELETED(src)) + cancelCameraAlarm() + for(var/mob/M in GLOB.player_list) + if(M.client && M.client.eye == src) + M.unset_machine() + M.reset_perspective(null) + to_chat(M, "The screen bursts into static.") + ..() + +/obj/machinery/camera/tesla_act(power)//EMP proof upgrade also makes it tesla immune + if(isEmpProof()) + return + ..() + qdel(src)//to prevent bomb testing camera from exploding over and over forever + +/obj/machinery/camera/ex_act(severity) + if(invuln) + return + ..() + +/obj/machinery/camera/proc/setViewRange(num = 7) + view_range = num + cameranet.updateVisibility(src, 0) + +/obj/machinery/camera/singularity_pull(S, current_size) + if (status && current_size >= STAGE_FIVE) // If the singulo is strong enough to pull anchored objects and the camera is still active, turn off the camera as it gets ripped off the wall. + toggle_cam(null, 0) + ..() + +/obj/machinery/camera/attackby(obj/item/I, mob/living/user, params) + var/msg = "You attach [I] into the assembly inner circuits." + var/msg2 = "The camera already has that upgrade!" + + if(istype(I, /obj/item/analyzer) && panel_open) //XRay + if(!user.drop_item()) + to_chat(user, "[I] is stuck to your hand!") + return + if(!isXRay()) + upgradeXRay() + qdel(I) + to_chat(user, "[msg]") + else + to_chat(user, "[msg2]") + + else if(istype(I, /obj/item/stack/sheet/mineral/plasma) && panel_open) + if(!user.drop_item()) + to_chat(user, "[I] is stuck to your hand!") + return + if(!isEmpProof()) + var/obj/item/stack/sheet/mineral/plasma/P = I + upgradeEmpProof() + to_chat(user, "[msg]") + P.use(1) + else + to_chat(user, "[msg2]") + else if(istype(I, /obj/item/assembly/prox_sensor) && panel_open) + if(!user.drop_item()) + to_chat(user, "[I] is stuck to your hand!") + return + if(!isMotion()) + upgradeMotion() + to_chat(user, "[msg]") + qdel(I) + else + to_chat(user, "[msg2]") + + // OTHER + else if((istype(I, /obj/item/paper) || istype(I, /obj/item/pda)) && isliving(user)) + var/mob/living/U = user + var/obj/item/paper/X = null + var/obj/item/pda/PDA = null + + var/itemname = "" + var/info = "" + if(istype(I, /obj/item/paper)) + X = I + itemname = X.name + info = X.info + else + PDA = I + var/datum/data/pda/app/notekeeper/N = PDA.find_program(/datum/data/pda/app/notekeeper) + if(N) + itemname = PDA.name + info = N.notehtml + to_chat(U, "You hold \the [itemname] up to the camera ...") + U.changeNext_move(CLICK_CD_MELEE) + for(var/mob/O in GLOB.player_list) + if(istype(O, /mob/living/silicon/ai)) + var/mob/living/silicon/ai/AI = O + if(AI.control_disabled || (AI.stat == DEAD)) + return + if(U.name == "Unknown") + to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...") + else + to_chat(AI, "[U] holds \a [itemname] up to one of your cameras ...") + AI.last_paper_seen = "[itemname][info]" + else if(O.client && O.client.eye == src) + to_chat(O, "[U] holds \a [itemname] up to one of the cameras ...") + O << browse(text("[][]", itemname, info), text("window=[]", itemname)) + + else if(istype(I, /obj/item/camera_bug)) + if(!can_use()) + to_chat(user, "Camera non-functional.") + return + if(istype(bug)) + to_chat(user, "Camera bug removed.") + bug.bugged_cameras -= c_tag + bug = null + else + to_chat(user, "Camera bugged.") + bug = I + bug.bugged_cameras[c_tag] = src + + else if(istype(I, /obj/item/laser_pointer)) + var/obj/item/laser_pointer/L = I + L.laser_act(src, user) + else + return ..() + + +/obj/machinery/camera/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + panel_open = !panel_open + to_chat(user, "You screw [src]'s panel [panel_open ? "open" : "closed"].") + +/obj/machinery/camera/wirecutter_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = 0)) + return + if(panel_open) + wires.Interact(user) + +/obj/machinery/camera/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = 0)) + return + if(panel_open) + wires.Interact(user) + +/obj/machinery/camera/welder_act(mob/user, obj/item/I) + if(!panel_open || !wires.CanDeconstruct()) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_WELD_MESSAGE + if(I.use_tool(src, user, 100, volume = I.tool_volume)) + visible_message("[user] unwelds [src], leaving it as just a frame bolted to the wall.", + "You unweld [src], leaving it as just a frame bolted to the wall") + deconstruct(TRUE) + +/obj/machinery/camera/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(stat & BROKEN) + return damage_amount + . = ..() + +/obj/machinery/camera/obj_break(damage_flag) + if(status && !(flags & NODECONSTRUCT)) + triggerCameraAlarm() + toggle_cam(null, FALSE) + wires.CutAll() + +/obj/machinery/camera/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(disassembled) + if(!assembly) + assembly = new() + assembly.forceMove(drop_location()) + assembly.state = 1 + assembly.setDir(dir) + assembly.update_icon() + assembly = null + else + var/obj/item/I = new /obj/item/camera_assembly(loc) + I.obj_integrity = I.max_integrity * 0.5 + new /obj/item/stack/cable_coil(loc, 2) + qdel(src) + +/obj/machinery/camera/update_icon() + if(!status) + icon_state = "[initial(icon_state)]1" + else if(stat & EMPED) + icon_state = "[initial(icon_state)]emp" + else + icon_state = "[initial(icon_state)]" + +/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = TRUE) + status = !status + if(can_use()) + cameranet.addCamera(src) + else + set_light(0) + cameranet.removeCamera(src) + cameranet.updateChunk(x, y, z) + var/change_msg = "deactivates" + if(status) + change_msg = "reactivates" + triggerCameraAlarm() + spawn(100) + if(!QDELETED(src)) + cancelCameraAlarm() + if(displaymessage) + if(user) + visible_message("[user] [change_msg] [src]!") + add_hiddenprint(user) + else + visible_message("\The [src] [change_msg]!") + + playsound(loc, toggle_sound, 100, 1) + update_icon() + + // now disconnect anyone using the camera + //Apparently, this will disconnect anyone even if the camera was re-activated. + //I guess that doesn't matter since they can't use it anyway? + for(var/mob/O in GLOB.player_list) + if(O.client && O.client.eye == src) + O.unset_machine() + O.reset_perspective(null) + to_chat(O, "The screen bursts into static.") + +/obj/machinery/camera/proc/triggerCameraAlarm() + if(is_station_contact(z)) + SSalarms.camera_alarm.triggerAlarm(loc, src) + +/obj/machinery/camera/proc/cancelCameraAlarm() + if(is_station_contact(z)) + SSalarms.camera_alarm.clearAlarm(loc, src) + +/obj/machinery/camera/proc/can_use() + if(!status) + return 0 + if(stat & EMPED) + return 0 + return 1 + +/obj/machinery/camera/proc/can_see() + var/list/see = null + var/turf/pos = get_turf(src) + if(isXRay()) + see = range(view_range, pos) + else + see = hear(view_range, pos) + return see + +/atom/proc/auto_turn() + //Automatically turns based on nearby walls. + var/turf/simulated/wall/T = null + for(var/i = 1, i <= 8; i += i) + T = get_ranged_target_turf(src, i, 1) + if(istype(T)) + //If someone knows a better way to do this, let me know. -Giacom + switch(i) + if(NORTH) + setDir(SOUTH) + if(SOUTH) + setDir(NORTH) + if(WEST) + setDir(EAST) + if(EAST) + setDir(WEST) + break + +//Return a working camera that can see a given mob +//or null if none +/proc/seen_by_camera(mob/M) + for(var/obj/machinery/camera/C in oview(4, M)) + if(C.can_use()) // check if camera disabled + return C + break + return null + +/proc/near_range_camera(mob/M) + for(var/obj/machinery/camera/C in range(4, M)) + if(C.can_use()) // check if camera disabled + return C + break + + return null + +/obj/machinery/camera/proc/Togglelight(on = FALSE) + for(var/mob/living/silicon/ai/A in ai_list) + for(var/obj/machinery/camera/cam in A.lit_cameras) + if(cam == src) + return + if(on) + set_light(AI_CAMERA_LUMINOSITY) + else + set_light(0) + +/obj/machinery/camera/proc/nano_structure() + var/cam[0] + var/turf/T = get_turf(src) + cam["name"] = sanitize(c_tag) + cam["deact"] = !can_use() + cam["camera"] = "\ref[src]" + if(T) + cam["x"] = T.x + cam["y"] = T.y + cam["z"] = T.z + else + cam["x"] = 0 + cam["y"] = 0 + cam["z"] = 0 + return cam + +/obj/machinery/camera/get_remote_view_fullscreens(mob/user) + if(view_range == short_range) //unfocused + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) + +/obj/machinery/camera/update_remote_sight(mob/living/user) + if(isXRay()) + user.sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) + user.see_in_dark = max(user.see_in_dark, 8) + user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + else + user.sight = initial(user.sight) + user.see_in_dark = initial(user.see_in_dark) + user.lighting_alpha = initial(user.lighting_alpha) + + ..() + return TRUE + +/obj/machinery/camera/portable //Cameras which are placed inside of things, such as helmets. + var/turf/prev_turf + +/obj/machinery/camera/portable/New() + ..() + assembly.state = 0 //These cameras are portable, and so shall be in the portable state if removed. + assembly.anchored = 0 + assembly.update_icon() + +/obj/machinery/camera/portable/process() //Updates whenever the camera is moved. + if(cameranet && get_turf(src) != prev_turf) + cameranet.updatePortableCamera(src) + prev_turf = get_turf(src) diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm index bd795ab340b..98f84cb75ec 100644 --- a/code/game/machinery/camera/camera_assembly.dm +++ b/code/game/machinery/camera/camera_assembly.dm @@ -1,179 +1,179 @@ -#define ASSEMBLY_UNBUILT 0 // Nothing done to it -#define ASSEMBLY_WRENCHED 1 // Wrenched in place -#define ASSEMBLY_WELDED 2 // Welded in place -#define ASSEMBLY_WIRED 3 // Wires attached (Upgradable now) -#define ASSEMBLY_BUILT 4 // Fully built (incl panel closed) -#define HEY_IM_WORKING_HERE 666 //So nobody can mess with the camera while we're inputting settings - -/obj/item/camera_assembly - name = "camera assembly" - desc = "A pre-fabricated security camera kit, ready to be assembled and mounted to a surface." - icon = 'icons/obj/monitors.dmi' - icon_state = "cameracase" - w_class = WEIGHT_CLASS_SMALL - anchored = FALSE - materials = list(MAT_METAL=400, MAT_GLASS=250) - // Motion, EMP-Proof, X-Ray - var/list/obj/item/possible_upgrades = list(/obj/item/assembly/prox_sensor, /obj/item/stack/sheet/mineral/plasma, /obj/item/analyzer) - var/list/upgrades = list() - var/state = ASSEMBLY_UNBUILT - - -/obj/item/camera_assembly/Destroy() - QDEL_LIST(upgrades) - return ..() - -/obj/item/camera_assembly/attackby(obj/item/I, mob/living/user, params) - if(state == ASSEMBLY_WELDED && iscoil(I)) - var/obj/item/stack/cable_coil/C = I - if(C.use(2)) - to_chat(user, "You add wires to the assembly.") - playsound(loc, I.usesound, 50, 1) - state = ASSEMBLY_WIRED - else - to_chat(user, "You need 2 coils of wire to wire the assembly.") - return - - // Upgrades! - else if(is_type_in_list(I, possible_upgrades) && !is_type_in_list(I, upgrades)) // Is a possible upgrade and isn't in the camera already. - if(!user.unEquip(I)) - to_chat(user, "[I] is stuck!") - return - to_chat(user, "You attach [I] into the assembly inner circuits.") - upgrades += I - user.drop_item() - I.loc = src - return - else - return ..() - -/obj/item/camera_assembly/crowbar_act(mob/user, obj/item/I) - if(!upgrades.len) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - var/obj/U = locate(/obj) in upgrades - if(U) - to_chat(user, "You unattach an upgrade from the assembly.") - playsound(loc, I.usesound, 50, 1) - U.loc = get_turf(src) - upgrades -= U - -/obj/item/camera_assembly/screwdriver_act(mob/user, obj/item/I) - if(state != ASSEMBLY_WIRED) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - state = HEY_IM_WORKING_HERE - var/input = strip_html(input(usr, "Which networks would you like to connect this camera to? Seperate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Set Network", "SS13")) - if(!input) - state = ASSEMBLY_WIRED - to_chat(usr, "No input found please hang up and try your call again.") - return - - var/list/tempnetwork = splittext(input, ",") - if(tempnetwork.len < 1) - state = ASSEMBLY_WIRED - to_chat(usr, "No network found please hang up and try your call again.") - return - - var/area/camera_area = get_area(src) - var/temptag = "[sanitize(camera_area.name)] ([rand(1, 999)])" - input = strip_html(input(usr, "How would you like to name the camera?", "Set Camera Name", temptag)) - state = ASSEMBLY_BUILT - var/obj/machinery/camera/C = new(loc) - loc = C - C.assembly = src - - C.auto_turn() - - C.network = uniquelist(tempnetwork) - tempnetwork = difflist(C.network,GLOB.restricted_camera_networks) - if(!tempnetwork.len) // Camera isn't on any open network - remove its chunk from AI visibility. - cameranet.removeCamera(C) - - C.c_tag = input - - for(var/i = 5; i >= 0; i -= 1) - var/direct = input(user, "Direction?", "Assembling Camera", null) in list("LEAVE IT", "NORTH", "EAST", "SOUTH", "WEST" ) - if(direct != "LEAVE IT") - C.dir = text2dir(direct) - if(i != 0) - var/confirm = alert(user, "Is this what you want? Chances Remaining: [i]", "Confirmation", "Yes", "No") - if(confirm == "Yes") - break - - -/obj/item/camera_assembly/wirecutter_act(mob/user, obj/item/I) - if(state != ASSEMBLY_WIRED) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - new/obj/item/stack/cable_coil(get_turf(src), 2) - WIRECUTTER_SNIP_MESSAGE - state = ASSEMBLY_WELDED - return - -/obj/item/camera_assembly/wrench_act(mob/user, obj/item/I) - if(state != ASSEMBLY_UNBUILT && state != ASSEMBLY_WRENCHED) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(state == ASSEMBLY_UNBUILT && isturf(loc)) - WRENCH_ANCHOR_TO_WALL_MESSAGE - anchored = TRUE - state = ASSEMBLY_WRENCHED - update_icon() - auto_turn() - else if(state == ASSEMBLY_WRENCHED) - WRENCH_UNANCHOR_WALL_MESSAGE - anchored = FALSE - update_icon() - state = ASSEMBLY_UNBUILT - else - to_chat(user, "[src] can't fit here!") - -/obj/item/camera_assembly/welder_act(mob/user, obj/item/I) - if(state == ASSEMBLY_UNBUILT) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_WELD_MESSAGE - if(state == ASSEMBLY_WRENCHED) - if(!I.use_tool(src, user, 50, volume = I.tool_volume)) - return - to_chat(user, "You weld [src] into place.") - state = ASSEMBLY_WELDED - else if(state == ASSEMBLY_WELDED) - if(!I.use_tool(src, user, 50, volume = I.tool_volume)) - return - to_chat(user, "You unweld [src] from its place.") - state = ASSEMBLY_WRENCHED - -/obj/item/camera_assembly/update_icon() - if(anchored) - icon_state = "camera1" - else - icon_state = "cameracase" - -/obj/item/camera_assembly/attack_hand(mob/user as mob) - if(!anchored) - ..() - -/obj/item/camera_assembly/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/sheet/metal(loc) - qdel(src) - - -#undef ASSEMBLY_UNBUILT -#undef ASSEMBLY_WRENCHED -#undef ASSEMBLY_WELDED -#undef ASSEMBLY_WIRED -#undef ASSEMBLY_BUILT -#undef HEY_IM_WORKING_HERE +#define ASSEMBLY_UNBUILT 0 // Nothing done to it +#define ASSEMBLY_WRENCHED 1 // Wrenched in place +#define ASSEMBLY_WELDED 2 // Welded in place +#define ASSEMBLY_WIRED 3 // Wires attached (Upgradable now) +#define ASSEMBLY_BUILT 4 // Fully built (incl panel closed) +#define HEY_IM_WORKING_HERE 666 //So nobody can mess with the camera while we're inputting settings + +/obj/item/camera_assembly + name = "camera assembly" + desc = "A pre-fabricated security camera kit, ready to be assembled and mounted to a surface." + icon = 'icons/obj/monitors.dmi' + icon_state = "cameracase" + w_class = WEIGHT_CLASS_SMALL + anchored = FALSE + materials = list(MAT_METAL=400, MAT_GLASS=250) + // Motion, EMP-Proof, X-Ray + var/list/obj/item/possible_upgrades = list(/obj/item/assembly/prox_sensor, /obj/item/stack/sheet/mineral/plasma, /obj/item/analyzer) + var/list/upgrades = list() + var/state = ASSEMBLY_UNBUILT + + +/obj/item/camera_assembly/Destroy() + QDEL_LIST(upgrades) + return ..() + +/obj/item/camera_assembly/attackby(obj/item/I, mob/living/user, params) + if(state == ASSEMBLY_WELDED && iscoil(I)) + var/obj/item/stack/cable_coil/C = I + if(C.use(2)) + to_chat(user, "You add wires to the assembly.") + playsound(loc, I.usesound, 50, 1) + state = ASSEMBLY_WIRED + else + to_chat(user, "You need 2 coils of wire to wire the assembly.") + return + + // Upgrades! + else if(is_type_in_list(I, possible_upgrades) && !is_type_in_list(I, upgrades)) // Is a possible upgrade and isn't in the camera already. + if(!user.unEquip(I)) + to_chat(user, "[I] is stuck!") + return + to_chat(user, "You attach [I] into the assembly inner circuits.") + upgrades += I + user.drop_item() + I.loc = src + return + else + return ..() + +/obj/item/camera_assembly/crowbar_act(mob/user, obj/item/I) + if(!upgrades.len) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + var/obj/U = locate(/obj) in upgrades + if(U) + to_chat(user, "You unattach an upgrade from the assembly.") + playsound(loc, I.usesound, 50, 1) + U.loc = get_turf(src) + upgrades -= U + +/obj/item/camera_assembly/screwdriver_act(mob/user, obj/item/I) + if(state != ASSEMBLY_WIRED) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + state = HEY_IM_WORKING_HERE + var/input = strip_html(input(usr, "Which networks would you like to connect this camera to? Seperate networks with a comma. No Spaces!\nFor example: SS13,Security,Secret ", "Set Network", "SS13")) + if(!input) + state = ASSEMBLY_WIRED + to_chat(usr, "No input found please hang up and try your call again.") + return + + var/list/tempnetwork = splittext(input, ",") + if(tempnetwork.len < 1) + state = ASSEMBLY_WIRED + to_chat(usr, "No network found please hang up and try your call again.") + return + + var/area/camera_area = get_area(src) + var/temptag = "[sanitize(camera_area.name)] ([rand(1, 999)])" + input = strip_html(input(usr, "How would you like to name the camera?", "Set Camera Name", temptag)) + state = ASSEMBLY_BUILT + var/obj/machinery/camera/C = new(loc) + loc = C + C.assembly = src + + C.auto_turn() + + C.network = uniquelist(tempnetwork) + tempnetwork = difflist(C.network,GLOB.restricted_camera_networks) + if(!tempnetwork.len) // Camera isn't on any open network - remove its chunk from AI visibility. + cameranet.removeCamera(C) + + C.c_tag = input + + for(var/i = 5; i >= 0; i -= 1) + var/direct = input(user, "Direction?", "Assembling Camera", null) in list("LEAVE IT", "NORTH", "EAST", "SOUTH", "WEST" ) + if(direct != "LEAVE IT") + C.dir = text2dir(direct) + if(i != 0) + var/confirm = alert(user, "Is this what you want? Chances Remaining: [i]", "Confirmation", "Yes", "No") + if(confirm == "Yes") + break + + +/obj/item/camera_assembly/wirecutter_act(mob/user, obj/item/I) + if(state != ASSEMBLY_WIRED) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + new/obj/item/stack/cable_coil(get_turf(src), 2) + WIRECUTTER_SNIP_MESSAGE + state = ASSEMBLY_WELDED + return + +/obj/item/camera_assembly/wrench_act(mob/user, obj/item/I) + if(state != ASSEMBLY_UNBUILT && state != ASSEMBLY_WRENCHED) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(state == ASSEMBLY_UNBUILT && isturf(loc)) + WRENCH_ANCHOR_TO_WALL_MESSAGE + anchored = TRUE + state = ASSEMBLY_WRENCHED + update_icon() + auto_turn() + else if(state == ASSEMBLY_WRENCHED) + WRENCH_UNANCHOR_WALL_MESSAGE + anchored = FALSE + update_icon() + state = ASSEMBLY_UNBUILT + else + to_chat(user, "[src] can't fit here!") + +/obj/item/camera_assembly/welder_act(mob/user, obj/item/I) + if(state == ASSEMBLY_UNBUILT) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_WELD_MESSAGE + if(state == ASSEMBLY_WRENCHED) + if(!I.use_tool(src, user, 50, volume = I.tool_volume)) + return + to_chat(user, "You weld [src] into place.") + state = ASSEMBLY_WELDED + else if(state == ASSEMBLY_WELDED) + if(!I.use_tool(src, user, 50, volume = I.tool_volume)) + return + to_chat(user, "You unweld [src] from its place.") + state = ASSEMBLY_WRENCHED + +/obj/item/camera_assembly/update_icon() + if(anchored) + icon_state = "camera1" + else + icon_state = "cameracase" + +/obj/item/camera_assembly/attack_hand(mob/user as mob) + if(!anchored) + ..() + +/obj/item/camera_assembly/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/sheet/metal(loc) + qdel(src) + + +#undef ASSEMBLY_UNBUILT +#undef ASSEMBLY_WRENCHED +#undef ASSEMBLY_WELDED +#undef ASSEMBLY_WIRED +#undef ASSEMBLY_BUILT +#undef HEY_IM_WORKING_HERE diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index 312e4cf577e..16fb6eca204 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -1,66 +1,66 @@ -/obj/machinery/camera - - var/list/motionTargets = list() - var/detectTime = 0 - var/area/ai_monitored/area_motion = null - var/alarm_delay = 100 - - -/obj/machinery/camera/process() - // motion camera event loop - if(stat & (EMPED|NOPOWER)) - return - if(!isMotion()) - . = PROCESS_KILL - return - if(detectTime > 0) - var/elapsed = world.time - detectTime - if(elapsed > alarm_delay) - triggerAlarm() - else if(detectTime == -1) - for(var/mob/target in motionTargets) - if(target.stat == 2) lostTarget(target) - // If not detecting with motion camera... - if(!area_motion) - // See if the camera is still in range - if(!in_range(src, target)) - // If they aren't in range, lose the target. - lostTarget(target) - -/obj/machinery/camera/proc/newTarget(var/mob/target) - if(istype(target, /mob/living/silicon/ai)) return 0 - if(detectTime == 0) - detectTime = world.time // start the clock - if(!(target in motionTargets)) - motionTargets += target - return 1 - -/obj/machinery/camera/proc/lostTarget(var/mob/target) - if(target in motionTargets) - motionTargets -= target - if(motionTargets.len == 0) - cancelAlarm() - -/obj/machinery/camera/proc/cancelAlarm() - if(!status || (stat & NOPOWER)) - return FALSE - if(detectTime == -1 && is_station_contact(z)) - SSalarms.motion_alarm.clearAlarm(loc, src) - detectTime = 0 - return TRUE - -/obj/machinery/camera/proc/triggerAlarm() - if(!status || (stat & NOPOWER)) - return FALSE - if(!detectTime || !is_station_contact(z)) - return FALSE - SSalarms.motion_alarm.triggerAlarm(loc, src) - detectTime = -1 - return TRUE - -/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) - // Motion cameras outside of an "ai monitored" area will use this to detect stuff. - if(!area_motion) - if(isliving(AM)) - newTarget(AM) - +/obj/machinery/camera + + var/list/motionTargets = list() + var/detectTime = 0 + var/area/ai_monitored/area_motion = null + var/alarm_delay = 100 + + +/obj/machinery/camera/process() + // motion camera event loop + if(stat & (EMPED|NOPOWER)) + return + if(!isMotion()) + . = PROCESS_KILL + return + if(detectTime > 0) + var/elapsed = world.time - detectTime + if(elapsed > alarm_delay) + triggerAlarm() + else if(detectTime == -1) + for(var/mob/target in motionTargets) + if(target.stat == 2) lostTarget(target) + // If not detecting with motion camera... + if(!area_motion) + // See if the camera is still in range + if(!in_range(src, target)) + // If they aren't in range, lose the target. + lostTarget(target) + +/obj/machinery/camera/proc/newTarget(var/mob/target) + if(istype(target, /mob/living/silicon/ai)) return 0 + if(detectTime == 0) + detectTime = world.time // start the clock + if(!(target in motionTargets)) + motionTargets += target + return 1 + +/obj/machinery/camera/proc/lostTarget(var/mob/target) + if(target in motionTargets) + motionTargets -= target + if(motionTargets.len == 0) + cancelAlarm() + +/obj/machinery/camera/proc/cancelAlarm() + if(!status || (stat & NOPOWER)) + return FALSE + if(detectTime == -1 && is_station_contact(z)) + SSalarms.motion_alarm.clearAlarm(loc, src) + detectTime = 0 + return TRUE + +/obj/machinery/camera/proc/triggerAlarm() + if(!status || (stat & NOPOWER)) + return FALSE + if(!detectTime || !is_station_contact(z)) + return FALSE + SSalarms.motion_alarm.triggerAlarm(loc, src) + detectTime = -1 + return TRUE + +/obj/machinery/camera/HasProximity(atom/movable/AM as mob|obj) + // Motion cameras outside of an "ai monitored" area will use this to detect stuff. + if(!area_motion) + if(isliving(AM)) + newTarget(AM) + diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index ac70a7c11ba..85b83b88c06 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -1,92 +1,92 @@ -// PRESETS - -// EMP - -/obj/machinery/camera/emp_proof/New() - ..() - upgradeEmpProof() - -// X-RAY - -/obj/machinery/camera/xray - icon_state = "xraycam" // Thanks to Krutchen for the icons. - -/obj/machinery/camera/xray/New() - ..() - upgradeXRay() - -// MOTION - -/obj/machinery/camera/motion/New() - ..() - upgradeMotion() - -// ALL UPGRADES - -/obj/machinery/camera/all/New() - ..() - upgradeEmpProof() - upgradeXRay() - upgradeMotion() - -// AUTONAME - -/obj/machinery/camera/autoname - var/number = 0 //camera number in area - -//This camera type automatically sets it's name to whatever the area that it's in is called. -/obj/machinery/camera/autoname/New() - ..() - spawn(10) - number = 1 - var/area/A = get_area(src) - if(A) - for(var/obj/machinery/camera/autoname/C in world) - if(C == src) continue - var/area/CA = get_area(C) - if(CA.type == A.type) - if(C.number) - number = max(number, C.number+1) - c_tag = "[A.name] #[number]" - - -// CHECKS - -/obj/machinery/camera/proc/isEmpProof() - var/O = locate(/obj/item/stack/sheet/mineral/plasma) in assembly.upgrades - return O - -/obj/machinery/camera/proc/isXRay() - var/O = locate(/obj/item/analyzer) in assembly.upgrades - return O - -/obj/machinery/camera/proc/isMotion() - var/O = locate(/obj/item/assembly/prox_sensor) in assembly.upgrades - return O - -// UPGRADE PROCS - -/obj/machinery/camera/proc/upgradeEmpProof() - assembly.upgrades.Add(new /obj/item/stack/sheet/mineral/plasma(assembly)) - setPowerUsage() - -/obj/machinery/camera/proc/upgradeXRay() - assembly.upgrades.Add(new /obj/item/analyzer(assembly)) - setPowerUsage() - //Update what it can see. - cameranet.updateVisibility(src, 0) - -// If you are upgrading Motion, and it isn't in the camera's New(), add it to the machines list. -/obj/machinery/camera/proc/upgradeMotion() - assembly.upgrades.Add(new /obj/item/assembly/prox_sensor(assembly)) - setPowerUsage() - // Add it to machines that process - START_PROCESSING(SSmachines, src) - -/obj/machinery/camera/proc/setPowerUsage() - var/mult = 1 - if(isXRay()) - mult++ - if(isMotion()) - mult++ - active_power_usage = mult*initial(active_power_usage) +// PRESETS + +// EMP + +/obj/machinery/camera/emp_proof/New() + ..() + upgradeEmpProof() + +// X-RAY + +/obj/machinery/camera/xray + icon_state = "xraycam" // Thanks to Krutchen for the icons. + +/obj/machinery/camera/xray/New() + ..() + upgradeXRay() + +// MOTION + +/obj/machinery/camera/motion/New() + ..() + upgradeMotion() + +// ALL UPGRADES + +/obj/machinery/camera/all/New() + ..() + upgradeEmpProof() + upgradeXRay() + upgradeMotion() + +// AUTONAME + +/obj/machinery/camera/autoname + var/number = 0 //camera number in area + +//This camera type automatically sets it's name to whatever the area that it's in is called. +/obj/machinery/camera/autoname/New() + ..() + spawn(10) + number = 1 + var/area/A = get_area(src) + if(A) + for(var/obj/machinery/camera/autoname/C in world) + if(C == src) continue + var/area/CA = get_area(C) + if(CA.type == A.type) + if(C.number) + number = max(number, C.number+1) + c_tag = "[A.name] #[number]" + + +// CHECKS + +/obj/machinery/camera/proc/isEmpProof() + var/O = locate(/obj/item/stack/sheet/mineral/plasma) in assembly.upgrades + return O + +/obj/machinery/camera/proc/isXRay() + var/O = locate(/obj/item/analyzer) in assembly.upgrades + return O + +/obj/machinery/camera/proc/isMotion() + var/O = locate(/obj/item/assembly/prox_sensor) in assembly.upgrades + return O + +// UPGRADE PROCS + +/obj/machinery/camera/proc/upgradeEmpProof() + assembly.upgrades.Add(new /obj/item/stack/sheet/mineral/plasma(assembly)) + setPowerUsage() + +/obj/machinery/camera/proc/upgradeXRay() + assembly.upgrades.Add(new /obj/item/analyzer(assembly)) + setPowerUsage() + //Update what it can see. + cameranet.updateVisibility(src, 0) + +// If you are upgrading Motion, and it isn't in the camera's New(), add it to the machines list. +/obj/machinery/camera/proc/upgradeMotion() + assembly.upgrades.Add(new /obj/item/assembly/prox_sensor(assembly)) + setPowerUsage() + // Add it to machines that process + START_PROCESSING(SSmachines, src) + +/obj/machinery/camera/proc/setPowerUsage() + var/mult = 1 + if(isXRay()) + mult++ + if(isMotion()) + mult++ + active_power_usage = mult*initial(active_power_usage) diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm index 277cee5beb3..5e6bd1640fb 100644 --- a/code/game/machinery/camera/tracking.dm +++ b/code/game/machinery/camera/tracking.dm @@ -1,260 +1,260 @@ -/mob/living/silicon/ai/proc/InvalidTurf(turf/T as turf) - if(!T) - return 1 - if(!is_level_reachable(T.z)) - return 1 - return 0 - - -/mob/living/silicon/ai/var/max_locations = 10 -/mob/living/silicon/ai/var/stored_locations[0] - -/mob/living/silicon/ai/proc/get_camera_list() - - track.cameras.Cut() - - if(src.stat == 2) - return - - var/list/L = list() - for(var/obj/machinery/camera/C in cameranet.cameras) - L.Add(C) - - camera_sort(L) - - var/list/T = list() - - for(var/obj/machinery/camera/C in L) - var/list/tempnetwork = C.network & src.network - if(tempnetwork.len) - T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C - - track.cameras = T - return T - - -/mob/living/silicon/ai/proc/ai_camera_list(var/camera in get_camera_list()) - set category = "AI Commands" - set name = "Show Camera List" - - if(src.stat == 2) - to_chat(src, "You can't list the cameras because you are dead!") - return - - if(!camera || camera == "Cancel") - return 0 - - var/obj/machinery/camera/C = track.cameras[camera] - src.eyeobj.setLoc(C) - - return - -/mob/living/silicon/ai/proc/ai_store_location(loc as text) - set category = "AI Commands" - set name = "Store Camera Location" - set desc = "Stores your current camera location by the given name" - - loc = sanitize(copytext(loc, 1, MAX_MESSAGE_LEN)) - if(!loc) - to_chat(src, "Must supply a location name") - return - - if(stored_locations.len >= max_locations) - to_chat(src, "Cannot store additional locations. Remove one first") - return - - if(loc in stored_locations) - to_chat(src, "There is already a stored location by this name") - return - - var/L = get_turf(eyeobj) - if(InvalidTurf(get_turf(L))) - to_chat(src, "Unable to store this location") - return - - stored_locations[loc] = L - to_chat(src, "Location '[loc]' stored") - -/mob/living/silicon/ai/proc/sorted_stored_locations() - return sortList(stored_locations) - -/mob/living/silicon/ai/proc/ai_goto_location(loc in sorted_stored_locations()) - set category = "AI Commands" - set name = "Goto Camera Location" - set desc = "Returns to the selected camera location" - - if(!(loc in stored_locations)) - to_chat(src, "Location [loc] not found") - return - - var/L = stored_locations[loc] - src.eyeobj.setLoc(L) - -/mob/living/silicon/ai/proc/ai_remove_location(loc in sorted_stored_locations()) - set category = "AI Commands" - set name = "Delete Camera Location" - set desc = "Deletes the selected camera location" - - if(!(loc in stored_locations)) - to_chat(src, "Location [loc] not found") - return - - stored_locations.Remove(loc) - to_chat(src, "Location [loc] removed") - -// Used to allow the AI is write in mob names/camera name from the CMD line. -/datum/trackable - var/list/names = list() - var/list/namecounts = list() - var/list/humans = list() - var/list/others = list() - var/list/cameras = list() - -/mob/living/silicon/ai/proc/trackable_mobs() - - track.names.Cut() - track.namecounts.Cut() - track.humans.Cut() - track.others.Cut() - - if(usr.stat == 2) - return list() - - for(var/mob/living/M in GLOB.mob_list) - if(!M.can_track(usr)) - continue - - // Human check - var/human = 0 - if(istype(M, /mob/living/carbon/human)) - human = 1 - - var/name = M.name - if(name in track.names) - track.namecounts[name]++ - name = text("[] ([])", name, track.namecounts[name]) - else - track.names.Add(name) - track.namecounts[name] = 1 - if(human) - track.humans[name] = M - else - track.others[name] = M - - var/list/targets = sortList(track.humans) + sortList(track.others) - - return targets - -/mob/living/silicon/ai/proc/ai_camera_track(target_name in trackable_mobs()) - set category = "AI Commands" - set name = "Track With Camera" - set desc = "Select who you would like to track." - - if(src.stat == DEAD) - to_chat(src, "You can't track with camera because you are dead!") - return - if(!target_name) - return - - var/mob/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name]) - - ai_actual_track(target) - -/mob/living/silicon/ai/proc/ai_cancel_tracking(var/forced = 0) - if(!cameraFollow) - return - - to_chat(src, "Follow camera mode [forced ? "terminated" : "ended"].") - cameraFollow = null - -/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target) - if(!istype(target)) - return - var/mob/living/silicon/ai/U = usr - - U.cameraFollow = target - U.tracking = 1 - - to_chat(U, "Attempting to track [target.get_visible_name()]...") - sleep(min(30, get_dist(target, U.eyeobj) / 4)) - spawn(15) //give the AI a grace period to stop moving. - U.tracking = 0 - - if(!target || !target.can_track(usr)) - to_chat(U, "Target is not near any active cameras.") - U.cameraFollow = null - return - - to_chat(U, "Now tracking [target.get_visible_name()] on camera.") - - var/cameraticks = 0 - spawn(0) - while(U.cameraFollow == target) - if(U.cameraFollow == null) - return - - if(!target.can_track(usr)) - U.tracking = 1 - if(!cameraticks) - to_chat(U, "Target is not near any active cameras. Attempting to reacquire...") - cameraticks++ - if(cameraticks > 9) - U.cameraFollow = null - to_chat(U, "Unable to reacquire, cancelling track...") - U.tracking = 0 - return - else - sleep(10) - continue - - else - cameraticks = 0 - U.tracking = 0 - - if(U.eyeobj) - U.eyeobj.setLoc(get_turf(target)) - - else - view_core() - U.cameraFollow = null - return - - sleep(10) - -/proc/near_camera(mob/living/M) - if(!isturf(M.loc)) - return 0 - if(isrobot(M)) - var/mob/living/silicon/robot/R = M - if(!(R.camera && R.camera.can_use()) && !cameranet.checkCameraVis(M)) - return 0 - else if(!cameranet.checkCameraVis(M)) - return 0 - return 1 - -/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) - if(!istype(user)) - return - if(!src.can_use()) - return - user.eyeobj.setLoc(get_turf(src)) - - -/mob/living/silicon/ai/attack_ai(mob/user) - ai_camera_list() - -/proc/camera_sort(list/L) - var/obj/machinery/camera/a - var/obj/machinery/camera/b - - for(var/i = L.len, i > 0, i--) - for(var/j = 1 to i - 1) - a = L[j] - b = L[j + 1] - if(a.c_tag_order != b.c_tag_order) - if(a.c_tag_order > b.c_tag_order) - L.Swap(j, j + 1) - else - if(sorttext(a.c_tag, b.c_tag) < 0) - L.Swap(j, j + 1) - return L +/mob/living/silicon/ai/proc/InvalidTurf(turf/T as turf) + if(!T) + return 1 + if(!is_level_reachable(T.z)) + return 1 + return 0 + + +/mob/living/silicon/ai/var/max_locations = 10 +/mob/living/silicon/ai/var/stored_locations[0] + +/mob/living/silicon/ai/proc/get_camera_list() + + track.cameras.Cut() + + if(src.stat == 2) + return + + var/list/L = list() + for(var/obj/machinery/camera/C in cameranet.cameras) + L.Add(C) + + camera_sort(L) + + var/list/T = list() + + for(var/obj/machinery/camera/C in L) + var/list/tempnetwork = C.network & src.network + if(tempnetwork.len) + T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C + + track.cameras = T + return T + + +/mob/living/silicon/ai/proc/ai_camera_list(var/camera in get_camera_list()) + set category = "AI Commands" + set name = "Show Camera List" + + if(src.stat == 2) + to_chat(src, "You can't list the cameras because you are dead!") + return + + if(!camera || camera == "Cancel") + return 0 + + var/obj/machinery/camera/C = track.cameras[camera] + src.eyeobj.setLoc(C) + + return + +/mob/living/silicon/ai/proc/ai_store_location(loc as text) + set category = "AI Commands" + set name = "Store Camera Location" + set desc = "Stores your current camera location by the given name" + + loc = sanitize(copytext(loc, 1, MAX_MESSAGE_LEN)) + if(!loc) + to_chat(src, "Must supply a location name") + return + + if(stored_locations.len >= max_locations) + to_chat(src, "Cannot store additional locations. Remove one first") + return + + if(loc in stored_locations) + to_chat(src, "There is already a stored location by this name") + return + + var/L = get_turf(eyeobj) + if(InvalidTurf(get_turf(L))) + to_chat(src, "Unable to store this location") + return + + stored_locations[loc] = L + to_chat(src, "Location '[loc]' stored") + +/mob/living/silicon/ai/proc/sorted_stored_locations() + return sortList(stored_locations) + +/mob/living/silicon/ai/proc/ai_goto_location(loc in sorted_stored_locations()) + set category = "AI Commands" + set name = "Goto Camera Location" + set desc = "Returns to the selected camera location" + + if(!(loc in stored_locations)) + to_chat(src, "Location [loc] not found") + return + + var/L = stored_locations[loc] + src.eyeobj.setLoc(L) + +/mob/living/silicon/ai/proc/ai_remove_location(loc in sorted_stored_locations()) + set category = "AI Commands" + set name = "Delete Camera Location" + set desc = "Deletes the selected camera location" + + if(!(loc in stored_locations)) + to_chat(src, "Location [loc] not found") + return + + stored_locations.Remove(loc) + to_chat(src, "Location [loc] removed") + +// Used to allow the AI is write in mob names/camera name from the CMD line. +/datum/trackable + var/list/names = list() + var/list/namecounts = list() + var/list/humans = list() + var/list/others = list() + var/list/cameras = list() + +/mob/living/silicon/ai/proc/trackable_mobs() + + track.names.Cut() + track.namecounts.Cut() + track.humans.Cut() + track.others.Cut() + + if(usr.stat == 2) + return list() + + for(var/mob/living/M in GLOB.mob_list) + if(!M.can_track(usr)) + continue + + // Human check + var/human = 0 + if(istype(M, /mob/living/carbon/human)) + human = 1 + + var/name = M.name + if(name in track.names) + track.namecounts[name]++ + name = text("[] ([])", name, track.namecounts[name]) + else + track.names.Add(name) + track.namecounts[name] = 1 + if(human) + track.humans[name] = M + else + track.others[name] = M + + var/list/targets = sortList(track.humans) + sortList(track.others) + + return targets + +/mob/living/silicon/ai/proc/ai_camera_track(target_name in trackable_mobs()) + set category = "AI Commands" + set name = "Track With Camera" + set desc = "Select who you would like to track." + + if(src.stat == DEAD) + to_chat(src, "You can't track with camera because you are dead!") + return + if(!target_name) + return + + var/mob/target = (isnull(track.humans[target_name]) ? track.others[target_name] : track.humans[target_name]) + + ai_actual_track(target) + +/mob/living/silicon/ai/proc/ai_cancel_tracking(var/forced = 0) + if(!cameraFollow) + return + + to_chat(src, "Follow camera mode [forced ? "terminated" : "ended"].") + cameraFollow = null + +/mob/living/silicon/ai/proc/ai_actual_track(mob/living/target) + if(!istype(target)) + return + var/mob/living/silicon/ai/U = usr + + U.cameraFollow = target + U.tracking = 1 + + to_chat(U, "Attempting to track [target.get_visible_name()]...") + sleep(min(30, get_dist(target, U.eyeobj) / 4)) + spawn(15) //give the AI a grace period to stop moving. + U.tracking = 0 + + if(!target || !target.can_track(usr)) + to_chat(U, "Target is not near any active cameras.") + U.cameraFollow = null + return + + to_chat(U, "Now tracking [target.get_visible_name()] on camera.") + + var/cameraticks = 0 + spawn(0) + while(U.cameraFollow == target) + if(U.cameraFollow == null) + return + + if(!target.can_track(usr)) + U.tracking = 1 + if(!cameraticks) + to_chat(U, "Target is not near any active cameras. Attempting to reacquire...") + cameraticks++ + if(cameraticks > 9) + U.cameraFollow = null + to_chat(U, "Unable to reacquire, cancelling track...") + U.tracking = 0 + return + else + sleep(10) + continue + + else + cameraticks = 0 + U.tracking = 0 + + if(U.eyeobj) + U.eyeobj.setLoc(get_turf(target)) + + else + view_core() + U.cameraFollow = null + return + + sleep(10) + +/proc/near_camera(mob/living/M) + if(!isturf(M.loc)) + return 0 + if(isrobot(M)) + var/mob/living/silicon/robot/R = M + if(!(R.camera && R.camera.can_use()) && !cameranet.checkCameraVis(M)) + return 0 + else if(!cameranet.checkCameraVis(M)) + return 0 + return 1 + +/obj/machinery/camera/attack_ai(mob/living/silicon/ai/user) + if(!istype(user)) + return + if(!src.can_use()) + return + user.eyeobj.setLoc(get_turf(src)) + + +/mob/living/silicon/ai/attack_ai(mob/user) + ai_camera_list() + +/proc/camera_sort(list/L) + var/obj/machinery/camera/a + var/obj/machinery/camera/b + + for(var/i = L.len, i > 0, i--) + for(var/j = 1 to i - 1) + a = L[j] + b = L[j + 1] + if(a.c_tag_order != b.c_tag_order) + if(a.c_tag_order > b.c_tag_order) + L.Swap(j, j + 1) + else + if(sorttext(a.c_tag, b.c_tag) < 0) + L.Swap(j, j + 1) + return L diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm index fb87d91e3f0..675d18ebb32 100644 --- a/code/game/machinery/cell_charger.dm +++ b/code/game/machinery/cell_charger.dm @@ -1,137 +1,137 @@ -/obj/machinery/cell_charger - name = "cell charger" - desc = "It charges power cells." - icon = 'icons/obj/power.dmi' - icon_state = "ccharger0" - anchored = 1 - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 60 - power_channel = EQUIP - pass_flags = PASSTABLE - var/obj/item/stock_parts/cell/charging = null - var/chargelevel = -1 - -/obj/machinery/cell_charger/deconstruct() - if(charging) - charging.forceMove(drop_location()) - return ..() - -/obj/machinery/cell_charger/Destroy() - QDEL_NULL(charging) - return ..() - -/obj/machinery/cell_charger/proc/updateicon() - icon_state = "ccharger[charging ? 1 : 0]" - - if(charging && !(stat & (BROKEN|NOPOWER))) - var/newlevel = round(charging.percent() * 4 / 100) - - if(chargelevel != newlevel) - chargelevel = newlevel - - overlays.Cut() - overlays += "ccharger-o[newlevel]" - - else - overlays.Cut() - -/obj/machinery/cell_charger/examine(mob/user) - . = ..() - . += "There's [charging ? "a" : "no"] cell in the charger." - if(charging) - . += "Current charge: [round(charging.percent(), 1)]%" - -/obj/machinery/cell_charger/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/stock_parts/cell)) - if(stat & BROKEN) - to_chat(user, "[src] is broken!") - return - if(!anchored) - to_chat(user, "[src] isn't attached to the ground!") - return - if(charging) - to_chat(user, "There is already a cell in the charger!") - return - else - var/area/a = loc.loc // Gets our locations location, like a dream within a dream - if(!isarea(a)) - return - if(a.power_equip == 0) // There's no APC in this area, don't try to cheat power! - to_chat(user, "[src] blinks red as you try to insert the cell!") - return - if(!user.drop_item()) - return - - I.forceMove(src) - charging = I - user.visible_message("[user] inserts a cell into the charger.", "You insert a cell into the charger.") - chargelevel = -1 - updateicon() - else - return ..() - -/obj/machinery/cell_charger/wrench_act(mob/user, obj/item/I) - . = TRUE - if(charging) - to_chat(user, "Remove the cell first!") - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - anchored = !anchored - if(anchored) - WRENCH_ANCHOR_MESSAGE - else - WRENCH_UNANCHOR_MESSAGE - - -/obj/machinery/cell_charger/proc/removecell() - charging.update_icon() - charging = null - chargelevel = -1 - updateicon() - -/obj/machinery/cell_charger/attack_hand(mob/user) - if(!charging) - return - - user.put_in_hands(charging) - charging.add_fingerprint(user) - - user.visible_message("[user] removes [charging] from [src].", "You remove [charging] from [src].") - - removecell() - -/obj/machinery/cell_charger/attack_tk(mob/user) - if(!charging) - return - - charging.forceMove(loc) - to_chat(user, "You telekinetically remove [charging] from [src].") - - removecell() - -/obj/machinery/cell_charger/attack_ai(mob/user) - return - -/obj/machinery/cell_charger/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - return - - if(charging) - charging.emp_act(severity) - - ..(severity) - - -/obj/machinery/cell_charger/process() - if(!charging || !anchored || (stat & (BROKEN|NOPOWER))) - return - - if(charging.percent() >= 100) - return - - use_power(200) //this used to use CELLRATE, but CELLRATE is fucking awful. feel free to fix this properly! - charging.give(175) //inefficiency. - - updateicon() +/obj/machinery/cell_charger + name = "cell charger" + desc = "It charges power cells." + icon = 'icons/obj/power.dmi' + icon_state = "ccharger0" + anchored = 1 + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 60 + power_channel = EQUIP + pass_flags = PASSTABLE + var/obj/item/stock_parts/cell/charging = null + var/chargelevel = -1 + +/obj/machinery/cell_charger/deconstruct() + if(charging) + charging.forceMove(drop_location()) + return ..() + +/obj/machinery/cell_charger/Destroy() + QDEL_NULL(charging) + return ..() + +/obj/machinery/cell_charger/proc/updateicon() + icon_state = "ccharger[charging ? 1 : 0]" + + if(charging && !(stat & (BROKEN|NOPOWER))) + var/newlevel = round(charging.percent() * 4 / 100) + + if(chargelevel != newlevel) + chargelevel = newlevel + + overlays.Cut() + overlays += "ccharger-o[newlevel]" + + else + overlays.Cut() + +/obj/machinery/cell_charger/examine(mob/user) + . = ..() + . += "There's [charging ? "a" : "no"] cell in the charger." + if(charging) + . += "Current charge: [round(charging.percent(), 1)]%" + +/obj/machinery/cell_charger/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/stock_parts/cell)) + if(stat & BROKEN) + to_chat(user, "[src] is broken!") + return + if(!anchored) + to_chat(user, "[src] isn't attached to the ground!") + return + if(charging) + to_chat(user, "There is already a cell in the charger!") + return + else + var/area/a = loc.loc // Gets our locations location, like a dream within a dream + if(!isarea(a)) + return + if(a.power_equip == 0) // There's no APC in this area, don't try to cheat power! + to_chat(user, "[src] blinks red as you try to insert the cell!") + return + if(!user.drop_item()) + return + + I.forceMove(src) + charging = I + user.visible_message("[user] inserts a cell into the charger.", "You insert a cell into the charger.") + chargelevel = -1 + updateicon() + else + return ..() + +/obj/machinery/cell_charger/wrench_act(mob/user, obj/item/I) + . = TRUE + if(charging) + to_chat(user, "Remove the cell first!") + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + anchored = !anchored + if(anchored) + WRENCH_ANCHOR_MESSAGE + else + WRENCH_UNANCHOR_MESSAGE + + +/obj/machinery/cell_charger/proc/removecell() + charging.update_icon() + charging = null + chargelevel = -1 + updateicon() + +/obj/machinery/cell_charger/attack_hand(mob/user) + if(!charging) + return + + user.put_in_hands(charging) + charging.add_fingerprint(user) + + user.visible_message("[user] removes [charging] from [src].", "You remove [charging] from [src].") + + removecell() + +/obj/machinery/cell_charger/attack_tk(mob/user) + if(!charging) + return + + charging.forceMove(loc) + to_chat(user, "You telekinetically remove [charging] from [src].") + + removecell() + +/obj/machinery/cell_charger/attack_ai(mob/user) + return + +/obj/machinery/cell_charger/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + return + + if(charging) + charging.emp_act(severity) + + ..(severity) + + +/obj/machinery/cell_charger/process() + if(!charging || !anchored || (stat & (BROKEN|NOPOWER))) + return + + if(charging.percent() >= 100) + return + + use_power(200) //this used to use CELLRATE, but CELLRATE is fucking awful. feel free to fix this properly! + charging.give(175) //inefficiency. + + updateicon() diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index 6d0f03b22b0..721162c0794 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -1,658 +1,658 @@ -//Cloning revival method. -//The pod handles the actual cloning while the computer manages the clone profiles - -//Potential replacement for genetics revives or something I dunno (?) - -#define CLONE_BIOMASS 150 -#define BIOMASS_MEAT_AMOUNT 50 -#define MINIMUM_HEAL_LEVEL 40 -#define CLONE_INITIAL_DAMAGE 190 -#define BRAIN_INITIAL_DAMAGE 90 // our minds are too feeble for 190 - -/obj/machinery/clonepod - anchored = 1 - name = "cloning pod" - desc = "An electronically-lockable pod for growing organic tissue." - density = 1 - icon = 'icons/obj/cloning.dmi' - icon_state = "pod_0" - req_access = list(ACCESS_GENETICS) //For premature unlocking. - var/mob/living/carbon/human/occupant - var/heal_level //The clone is released once its health reaches this level. - var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine. - var/mess = 0 //Need to clean out it if it's full of exploded clone. - var/attempting = 0 //One clone attempt at a time thanks - var/biomass = 0 - var/speed_coeff - var/efficiency - - var/datum/mind/clonemind - var/grab_ghost_when = CLONER_MATURE_CLONE - - var/obj/item/radio/Radio - var/radio_announce = 0 - - var/obj/effect/countdown/clonepod/countdown - - var/list/brine_types = list("corazone", "perfluorodecalin", "epinephrine", "salglu_solution") //stops heart attacks, heart failure, shock, and keeps their O2 levels normal - var/list/missing_organs - var/organs_number = 0 - - light_color = LIGHT_COLOR_PURE_GREEN - -/obj/machinery/clonepod/power_change() - ..() - if(!(stat & (BROKEN|NOPOWER))) - set_light(2) - else - set_light(0) - -/obj/machinery/clonepod/biomass - biomass = CLONE_BIOMASS - -/obj/machinery/clonepod/New() - ..() - countdown = new(src) - - Radio = new /obj/item/radio(src) - Radio.listening = 0 - Radio.config(list("Medical" = 0)) - - component_parts = list() - component_parts += new /obj/item/circuitboard/clonepod(null) - component_parts += new /obj/item/stock_parts/scanning_module(null) - component_parts += new /obj/item/stock_parts/scanning_module(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() - update_icon() - -/obj/machinery/clonepod/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/clonepod(null) - component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) - component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stack/cable_coil(null, 1) - biomass = CLONE_BIOMASS - RefreshParts() - -/obj/machinery/clonepod/Destroy() - if(connected) - connected.pods -= src - for(var/s in sharedSoulhooks) - var/datum/soullink/S = s - S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed - sharedSoulhooks = null - QDEL_NULL(Radio) - QDEL_NULL(countdown) - QDEL_LIST(missing_organs) - return ..() - -/obj/machinery/clonepod/RefreshParts() - speed_coeff = 0 - efficiency = 0 - for(var/obj/item/stock_parts/scanning_module/S in component_parts) - efficiency += S.rating - for(var/obj/item/stock_parts/manipulator/P in component_parts) - speed_coeff += P.rating - heal_level = max(min((efficiency * 15) + 10, 100), MINIMUM_HEAL_LEVEL) - -//The return of data disks?? Just for transferring between genetics machine/cloning machine. -//TO-DO: Make the genetics machine accept them. -/obj/item/disk/data - name = "Cloning Data Disk" - icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk. - var/datum/dna2/record/buf = null - var/read_only = 0 //Well,it's still a floppy disk - -/obj/item/disk/data/proc/initialize() - buf = new - buf.dna=new - -/obj/item/disk/data/Destroy() - QDEL_NULL(buf) - return ..() - -/obj/item/disk/data/demo - name = "data disk - 'God Emperor of Mankind'" - read_only = 1 - -/obj/item/disk/data/demo/New() - ..() - initialize() - buf.types=DNA2_BUF_UE|DNA2_BUF_UI - //data = "066000033000000000AF00330660FF4DB002690" - //data = "0C80C80C80C80C80C8000000000000161FBDDEF" - Farmer Jeff - buf.dna.real_name="God Emperor of Mankind" - buf.dna.unique_enzymes = md5(buf.dna.real_name) - buf.dna.UI=list(0x066,0x000,0x033,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0xAF0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x033,0x066,0x0FF,0x4DB,0x002,0x690,0x000,0x000,0x000,0x328,0x045,0x5FC,0x053,0x035,0x035,0x035) - //buf.dna.UI=list(0x0C8,0x0C8,0x0C8,0x0C8,0x0C8,0x0C8,0x000,0x000,0x000,0x000,0x161,0xFBD,0xDEF) // Farmer Jeff - if(buf.dna.UI.len != DNA_UI_LENGTH) //If there's a disparity b/w the dna UI string lengths, 0-fill the extra blocks in this UI. - for(var/i in buf.dna.UI.len to DNA_UI_LENGTH) - buf.dna.UI += 0x000 - buf.dna.ResetSE() - buf.dna.UpdateUI() - -/obj/item/disk/data/monkey - name = "data disk - 'Mr. Muggles'" - read_only = 1 - -/obj/item/disk/data/monkey/New() - ..() - initialize() - buf.types=DNA2_BUF_SE - var/list/new_SE=list(0x098,0x3E8,0x403,0x44C,0x39F,0x4B0,0x59D,0x514,0x5FC,0x578,0x5DC,0x640,0x6A4) - for(var/i=new_SE.len;i<=DNA_SE_LENGTH;i++) - new_SE += rand(1,1024) - buf.dna.SE=new_SE - buf.dna.SetSEValueRange(MONKEYBLOCK,0xDAC, 0xFFF) - -//Disk stuff. -/obj/item/disk/data/New() - ..() - var/diskcolor = pick(0,1,2) - icon_state = "datadisk[diskcolor]" - -/obj/item/disk/data/attack_self(mob/user as mob) - read_only = !read_only - to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].") - -/obj/item/disk/data/examine(mob/user) - . = ..() - . += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]." - -//Clonepod - -/obj/machinery/clonepod/examine(mob/user) - . = ..() - if(mess) - . += "It's filled with blood and viscera. You swear you can see it moving..." - if(!occupant || stat & (NOPOWER|BROKEN)) - return - if(occupant && occupant.stat != DEAD) - . += "Current clone cycle is [round(get_completion())]% complete." - -/obj/machinery/clonepod/return_air() //non-reactive air - var/datum/gas_mixture/GM = new - GM.nitrogen = MOLES_O2STANDARD + MOLES_N2STANDARD - GM.temperature = T20C - return GM - -/obj/machinery/clonepod/proc/get_completion() - . = (100 * ((occupant.health + 100) / (heal_level + 100))) - -/obj/machinery/clonepod/attack_ai(mob/user) - return examine(user) - -//Radio Announcement - -/obj/machinery/clonepod/proc/announce_radio_message(message) - if(radio_announce) - Radio.autosay(message, name, "Medical", list(z)) - -/obj/machinery/clonepod/proc/spooky_devil_flavor() - playsound(loc, pick('sound/goonstation/voice/male_scream.ogg', 'sound/goonstation/voice/female_scream.ogg'), 100, 1) - mess = 1 - update_icon() - connected_message("If you keep trying to steal from me, you'll end up with me.") - -//Start growing a human clone in the pod! -/obj/machinery/clonepod/proc/growclone(datum/dna2/record/R) - if(mess || attempting || panel_open || stat & (NOPOWER|BROKEN)) - return 0 - clonemind = locate(R.mind) - if(!istype(clonemind)) //not a mind - return 0 - if(clonemind.current && clonemind.current.stat != DEAD) //mind is associated with a non-dead body - return 0 - if(clonemind.damnation_type) - spooky_devil_flavor() - return 0 - if(!clonemind.is_revivable()) //Other reasons for being unrevivable - return 0 - if(clonemind.active) //somebody is using that mind - if(ckey(clonemind.key) != R.ckey ) - return 0 - if(clonemind.suicided) // and stay out! - malfunction(go_easy = 0) - return -1 // Flush the record - else - // get_ghost() will fail if they're unable to reenter their body - var/mob/dead/observer/G = clonemind.get_ghost() - if(!G) - return 0 - -/* - if(clonemind.damnation_type) //Can't clone the damned. - playsound('sound/hallucinations/veryfar_noise.ogg', 50, 0) - malfunction() - return -1 // so that the record gets flushed out - */ - - if(biomass >= CLONE_BIOMASS) - biomass -= CLONE_BIOMASS - else - return 0 - - attempting = 1 //One at a time!! - countdown.start() - - if(!R.dna) - R.dna = new /datum/dna() - - var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) - H.set_species(R.dna.species.type) - occupant = H - - if(!R.dna.real_name) //to prevent null names - R.dna.real_name = H.real_name - else - H.real_name = R.dna.real_name - - H.dna = R.dna.Clone() - - for(var/datum/language/L in R.languages) - H.add_language(L.name) - - domutcheck(H, null, MUTCHK_FORCED) //Ensures species that get powers by the species proc handle_dna keep them - - if(efficiency > 2 && efficiency < 5 && prob(25)) - randmutb(H) - if(efficiency > 5 && prob(20)) - randmutg(H) - if(efficiency < 3 && prob(50)) - randmutb(H) - - H.dna.UpdateSE() - H.dna.UpdateUI() - - H.sync_organ_dna(1) // It's literally a fresh body as you can get, so all organs properly belong to it - H.UpdateAppearance() - - check_brine() - //Get the clone body ready - maim_clone(H) - H.Paralyse(4) - - if(grab_ghost_when == CLONER_FRESH_CLONE) - clonemind.transfer_to(H) - H.ckey = R.ckey - update_clone_antag(H) //Since the body's got the mind, update their antag stuff right now. Otherwise, wait until they get kicked out (as per the CLONER_MATURE_CLONE business) to do it. - to_chat(H, {"Consciousness slowly creeps over you - as your body regenerates.
        So this is what cloning - feels like?
        "}) - else if(grab_ghost_when == CLONER_MATURE_CLONE) - to_chat(clonemind.current, {"Your body is - beginning to regenerate in a cloning pod. You will - become conscious when it is complete."}) - // Set up a soul link with the dead body to catch a revival - soullink(/datum/soullink/soulhook, clonemind.current, src) - - update_icon() - - H.suiciding = 0 - attempting = 0 - return 1 - -//Grow clones to maturity then kick them out. FREELOADERS -/obj/machinery/clonepod/process() - var/show_message = 0 - for(var/obj/item/reagent_containers/food/snacks/meat/meat in range(1, src)) - qdel(meat) - biomass += BIOMASS_MEAT_AMOUNT - show_message = 1 - if(show_message) - visible_message("[src] sucks in and processes the nearby biomass.") - - if(stat & NOPOWER) //Autoeject if power is lost - if(occupant) - go_out() - connected_message("Clone Ejected: Loss of power.") - - else if((occupant) && (occupant.loc == src)) - if((occupant.stat == DEAD) || (occupant.suiciding) || (occupant.mind && !occupant.mind.is_revivable())) //Autoeject corpses and suiciding dudes. - announce_radio_message("The cloning of [occupant] has been aborted due to unrecoverable tissue failure.") - go_out() - connected_message("Clone Rejected: Deceased.") - - else if(occupant.cloneloss > (100 - heal_level)) - occupant.Paralyse(4) - - //Slowly get that clone healed and finished. - occupant.adjustCloneLoss(-((speed_coeff/2))) - - // For human species that lack non-vital parts for some weird reason - if(organs_number) - var/progress = CLONE_INITIAL_DAMAGE - occupant.getCloneLoss() - progress += (100 - MINIMUM_HEAL_LEVEL) - var/milestone = CLONE_INITIAL_DAMAGE / organs_number -// Doing this as a #define so that the value can change when evaluated multiple times -#define INSTALLED (organs_number - LAZYLEN(missing_organs)) - - while((progress / milestone) > INSTALLED && LAZYLEN(missing_organs)) - var/obj/item/organ/I = pick_n_take(missing_organs) - I.safe_replace(occupant) - -#undef INSTALLED - - //Premature clones may have brain damage. - occupant.adjustBrainLoss(-((speed_coeff/20)*efficiency)) - - check_brine() - - //Also heal some oxyloss ourselves just in case!! - occupant.adjustOxyLoss(-10) - - use_power(7500) //This might need tweaking. - - else if((occupant.cloneloss <= (100 - heal_level))) - connected_message("Cloning Process Complete.") - announce_radio_message("The cloning cycle of [occupant] is complete.") - go_out() - - else if((!occupant) || (occupant.loc != src)) - occupant = null - update_icon() - use_power(200) - -//Let's unlock this early I guess. Might be too early, needs tweaking. -/obj/machinery/clonepod/attackby(obj/item/I, mob/user, params) - if(exchange_parts(user, I)) - return - - if(I.GetID()) - if(!check_access(I)) - to_chat(user, "Access Denied.") - return - if(!(occupant || mess)) - to_chat(user, "Error: Pod has no occupant.") - return - else - connected_message("Authorized Ejection") - announce_radio_message("An authorized ejection of [(occupant) ? occupant.real_name : "the malfunctioning pod"] has occured") - to_chat(user, "You force an emergency ejection.") - go_out() - -//Removing cloning pod biomass - else if(istype(I, /obj/item/reagent_containers/food/snacks/meat)) - if(user.drop_item()) - to_chat(user, "[src] processes [I].") - biomass += BIOMASS_MEAT_AMOUNT - qdel(I) - else - return ..() - -/obj/machinery/clonepod/crowbar_act(mob/user, obj/item/I) - . = TRUE - default_deconstruction_crowbar(user, I) - -/obj/machinery/clonepod/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(!I.multitool_check_buffer(user)) - return - var/obj/item/multitool/M = I - M.set_multitool_buffer(user, src) - -/obj/machinery/clonepod/screwdriver_act(mob/user, obj/item/I) - . = TRUE - default_deconstruction_screwdriver(user, "[icon_state]_maintenance", "[initial(icon_state)]", I) - -/obj/machinery/clonepod/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(occupant) - to_chat(user, "Can not do that while [src] is in use.") - return - if(anchored) - WRENCH_UNANCHOR_MESSAGE - anchored = FALSE - connected.pods -= src - connected = null - else - WRENCH_ANCHOR_MESSAGE - anchored = TRUE - -/obj/machinery/clonepod/emag_act(user) - if(isnull(occupant)) - return - go_out() - -/obj/machinery/clonepod/proc/update_clone_antag(var/mob/living/carbon/human/H) - // Check to see if the clone's mind is an antagonist of any kind and handle them accordingly to make sure they get their spells, HUD/whatever else back. - if((H.mind in SSticker.mode:revolutionaries) || (H.mind in SSticker.mode:head_revolutionaries)) - SSticker.mode.update_rev_icons_added() //So the icon actually appears - if(H.mind in SSticker.mode.syndicates) - SSticker.mode.update_synd_icons_added() - if(H.mind in SSticker.mode.cult) - SSticker.mode.add_cultist(occupant.mind) - SSticker.mode.update_cult_icons_added() //So the icon actually appears - SSticker.mode.update_cult_comms_added(H.mind) //So the comms actually appears - if(H.mind.vampire) - H.mind.vampire.update_owner(H) - if((H.mind in SSticker.mode.vampire_thralls) || (H.mind in SSticker.mode.vampire_enthralled)) - SSticker.mode.update_vampire_icons_added(H.mind) - if(H.mind in SSticker.mode.changelings) - SSticker.mode.update_change_icons_added(H.mind) - if((H.mind in SSticker.mode.shadowling_thralls) || (H.mind in SSticker.mode.shadows)) - SSticker.mode.update_shadow_icons_added(H.mind) - -//Put messages in the connected computer's temp var for display. -/obj/machinery/clonepod/proc/connected_message(message) - if((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning))) - return 0 - if(!message) - return 0 - - connected.temp = "[name] : [message]" - connected.updateUsrDialog() - return 1 - -/obj/machinery/clonepod/proc/go_out() - countdown.stop() - var/turf/T = get_turf(src) - if(mess) //Clean that mess and dump those gibs! - for(var/i in missing_organs) - var/obj/I = i - I.forceMove(T) - missing_organs.Cut() - mess = FALSE - new /obj/effect/gibspawner/generic(get_turf(src), occupant) - playsound(loc, 'sound/effects/splat.ogg', 50, 1) - update_icon() - return - - if(!occupant) - return - - if(grab_ghost_when == CLONER_MATURE_CLONE) - clonemind.transfer_to(occupant) - occupant.grab_ghost() - update_clone_antag(occupant) - to_chat(occupant, "There is a bright flash!
        \ - You feel like a new being.
        ") - occupant.flash_eyes(visual = 1) - for(var/s in sharedSoulhooks) - var/datum/soullink/S = s - S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed - sharedSoulhooks = null - - - for(var/i in missing_organs) - qdel(i) - missing_organs.Cut() - occupant.SetLoseBreath(0) // Stop friggin' dying, gosh damn - occupant.setOxyLoss(0) - for(var/datum/disease/critical/crit in occupant.viruses) - crit.cure() - occupant.forceMove(T) - occupant.update_body() - domutcheck(occupant) //Waiting until they're out before possible notransform. - occupant.special_post_clone_handling() - occupant = null - update_icon() - -/obj/machinery/clonepod/proc/malfunction(go_easy = FALSE) - if(occupant) - connected_message("Critical Error!") - announce_radio_message("Critical error! Please contact a Thinktronic Systems technician, as your warranty may be affected.") - for(var/s in sharedSoulhooks) - var/datum/soullink/S = s - S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed - sharedSoulhooks = null - if(!go_easy) - if(occupant.mind != clonemind) - clonemind.transfer_to(occupant) - occupant.grab_ghost() // We really just want to make you suffer. - to_chat(occupant, {"Agony blazes across your - consciousness as your body is torn apart.
        - Is this what dying is like? Yes it is.
        "}) - occupant << sound('sound/hallucinations/veryfar_noise.ogg',0,1,50) - for(var/i in missing_organs) - qdel(i) - missing_organs.Cut() - spawn(40) - qdel(occupant) - - - playsound(loc, 'sound/machines/warning-buzzer.ogg', 50, 0) - mess = TRUE - update_icon() - -/obj/machinery/clonepod/update_icon() - ..() - icon_state = "pod_0" - if(occupant && !(stat & NOPOWER)) - icon_state = "pod_1" - else if(mess && !panel_open) - icon_state = "pod_g" - -/obj/machinery/clonepod/relaymove(mob/user) - if(user.stat == CONSCIOUS) - go_out() - -/obj/machinery/clonepod/emp_act(severity) - if(prob(100/(severity*efficiency))) malfunction() - ..() - -/obj/machinery/clonepod/ex_act(severity) - ..() - if(!QDELETED(src) && occupant) - go_out() - -/obj/machinery/clonepod/handle_atom_del(atom/A) - if(A == occupant) - occupant = null - countdown.stop() - -/obj/machinery/clonepod/deconstruct(disassembled = TRUE) - if(occupant) - go_out() - ..() - -/obj/machinery/clonepod/onSoullinkRevive(mob/living/L) - if(occupant && L == clonemind.current) - // The old body's back in shape, time to ditch the cloning one - malfunction(go_easy = TRUE) - -/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H) - LAZYINITLIST(missing_organs) - for(var/i in missing_organs) - qdel(i) - missing_organs.Cut() - - H.setCloneLoss(CLONE_INITIAL_DAMAGE, FALSE) - H.setBrainLoss(BRAIN_INITIAL_DAMAGE) - - for(var/o in H.internal_organs) - var/obj/item/organ/O = o - if(!istype(O) || O.vital) - continue - - // Let's non-specially remove all non-vital organs - // What could possibly go wrong - var/obj/item/I = O.remove(H) - // Make this support stuff that turns into items when removed - I.forceMove(src) - missing_organs += I - - var/static/list/zones = list("r_arm", "l_arm", "r_leg", "l_leg") - for(var/zone in zones) - var/obj/item/organ/external/E = H.get_organ(zone) - var/obj/item/I = E.remove(H) - I.forceMove(src) - missing_organs += I - - organs_number = LAZYLEN(missing_organs) - H.updatehealth() - -/obj/machinery/clonepod/proc/check_brine() - // Clones are in a pickled bath of mild chemicals, keeping - // them alive, despite their lack of internal organs - for(var/bt in brine_types) - if(occupant.reagents.get_reagent_amount(bt) < 1) - occupant.reagents.add_reagent(bt, 1) - -/* - * Diskette Box - */ - -/obj/item/storage/box/disks - name = "Diskette Box" - icon_state = "disk_kit" - -/obj/item/storage/box/disks/New() - ..() - new /obj/item/disk/data(src) - new /obj/item/disk/data(src) - new /obj/item/disk/data(src) - new /obj/item/disk/data(src) - new /obj/item/disk/data(src) - new /obj/item/disk/data(src) - new /obj/item/disk/data(src) - -/* - * Manual -- A big ol' manual. - */ - -/obj/item/paper/Cloning - name = "paper - 'H-87 Cloning Apparatus Manual" - info = {"

        Getting Started

        - Congratulations, your station has purchased the H-87 industrial cloning device!
        - Using the H-87 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and select the scan option to create a new profile!
        - That's all there is to it!
        - Notice, cloning system cannot scan inorganic life or small primates. Scan may fail if subject has suffered extreme brain damage.
        -

        Clone profiles may be viewed through the profiles menu. Scanning implants a complementary HEALTH MONITOR IMPLANT into the subject, which may be viewed from each profile. - Profile Deletion has been restricted to \[Station Head\] level access.

        -

        Cloning from a profile

        - Cloning is as simple as pressing the CLONE option at the bottom of the desired profile.
        - Per your company's EMPLOYEE PRIVACY RIGHTS agreement, the H-87 has been blocked from cloning crewmembers while they are still alive.
        -
        -

        The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times (With SPEEDCLONE technology) are roughly 90 seconds. - The cloning pod may be unlocked early with any \[Medical Researcher\] ID after initial maturation is complete.


        - Please note that resulting clones may have a small DEVELOPMENTAL DEFECT as a result of genetic drift.
        -

        Profile Management

        -

        The H-87 (as well as your station's standard genetics machine) can accept STANDARD DATA DISKETTES. - These diskettes are used to transfer genetic information between machines and profiles. - A load/save dialog will become available in each profile if a disk is inserted.


        - A good diskette is a great way to counter aforementioned genetic drift!
        -
        - This technology produced under license from Thinktronic Systems, LTD."} - -//SOME SCRAPS I GUESS -/* EMP grenade/spell effect - if(istype(A, /obj/machinery/clonepod)) - A:malfunction() -*/ - -#undef MINIMUM_HEAL_LEVEL +//Cloning revival method. +//The pod handles the actual cloning while the computer manages the clone profiles + +//Potential replacement for genetics revives or something I dunno (?) + +#define CLONE_BIOMASS 150 +#define BIOMASS_MEAT_AMOUNT 50 +#define MINIMUM_HEAL_LEVEL 40 +#define CLONE_INITIAL_DAMAGE 190 +#define BRAIN_INITIAL_DAMAGE 90 // our minds are too feeble for 190 + +/obj/machinery/clonepod + anchored = 1 + name = "cloning pod" + desc = "An electronically-lockable pod for growing organic tissue." + density = 1 + icon = 'icons/obj/cloning.dmi' + icon_state = "pod_0" + req_access = list(ACCESS_GENETICS) //For premature unlocking. + var/mob/living/carbon/human/occupant + var/heal_level //The clone is released once its health reaches this level. + var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine. + var/mess = 0 //Need to clean out it if it's full of exploded clone. + var/attempting = 0 //One clone attempt at a time thanks + var/biomass = 0 + var/speed_coeff + var/efficiency + + var/datum/mind/clonemind + var/grab_ghost_when = CLONER_MATURE_CLONE + + var/obj/item/radio/Radio + var/radio_announce = 0 + + var/obj/effect/countdown/clonepod/countdown + + var/list/brine_types = list("corazone", "perfluorodecalin", "epinephrine", "salglu_solution") //stops heart attacks, heart failure, shock, and keeps their O2 levels normal + var/list/missing_organs + var/organs_number = 0 + + light_color = LIGHT_COLOR_PURE_GREEN + +/obj/machinery/clonepod/power_change() + ..() + if(!(stat & (BROKEN|NOPOWER))) + set_light(2) + else + set_light(0) + +/obj/machinery/clonepod/biomass + biomass = CLONE_BIOMASS + +/obj/machinery/clonepod/New() + ..() + countdown = new(src) + + Radio = new /obj/item/radio(src) + Radio.listening = 0 + Radio.config(list("Medical" = 0)) + + component_parts = list() + component_parts += new /obj/item/circuitboard/clonepod(null) + component_parts += new /obj/item/stock_parts/scanning_module(null) + component_parts += new /obj/item/stock_parts/scanning_module(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stack/cable_coil(null, 1) + RefreshParts() + update_icon() + +/obj/machinery/clonepod/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/clonepod(null) + component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) + component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stack/cable_coil(null, 1) + biomass = CLONE_BIOMASS + RefreshParts() + +/obj/machinery/clonepod/Destroy() + if(connected) + connected.pods -= src + for(var/s in sharedSoulhooks) + var/datum/soullink/S = s + S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed + sharedSoulhooks = null + QDEL_NULL(Radio) + QDEL_NULL(countdown) + QDEL_LIST(missing_organs) + return ..() + +/obj/machinery/clonepod/RefreshParts() + speed_coeff = 0 + efficiency = 0 + for(var/obj/item/stock_parts/scanning_module/S in component_parts) + efficiency += S.rating + for(var/obj/item/stock_parts/manipulator/P in component_parts) + speed_coeff += P.rating + heal_level = max(min((efficiency * 15) + 10, 100), MINIMUM_HEAL_LEVEL) + +//The return of data disks?? Just for transferring between genetics machine/cloning machine. +//TO-DO: Make the genetics machine accept them. +/obj/item/disk/data + name = "Cloning Data Disk" + icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk. + var/datum/dna2/record/buf = null + var/read_only = 0 //Well,it's still a floppy disk + +/obj/item/disk/data/proc/initialize() + buf = new + buf.dna=new + +/obj/item/disk/data/Destroy() + QDEL_NULL(buf) + return ..() + +/obj/item/disk/data/demo + name = "data disk - 'God Emperor of Mankind'" + read_only = 1 + +/obj/item/disk/data/demo/New() + ..() + initialize() + buf.types=DNA2_BUF_UE|DNA2_BUF_UI + //data = "066000033000000000AF00330660FF4DB002690" + //data = "0C80C80C80C80C80C8000000000000161FBDDEF" - Farmer Jeff + buf.dna.real_name="God Emperor of Mankind" + buf.dna.unique_enzymes = md5(buf.dna.real_name) + buf.dna.UI=list(0x066,0x000,0x033,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0xAF0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x033,0x066,0x0FF,0x4DB,0x002,0x690,0x000,0x000,0x000,0x328,0x045,0x5FC,0x053,0x035,0x035,0x035) + //buf.dna.UI=list(0x0C8,0x0C8,0x0C8,0x0C8,0x0C8,0x0C8,0x000,0x000,0x000,0x000,0x161,0xFBD,0xDEF) // Farmer Jeff + if(buf.dna.UI.len != DNA_UI_LENGTH) //If there's a disparity b/w the dna UI string lengths, 0-fill the extra blocks in this UI. + for(var/i in buf.dna.UI.len to DNA_UI_LENGTH) + buf.dna.UI += 0x000 + buf.dna.ResetSE() + buf.dna.UpdateUI() + +/obj/item/disk/data/monkey + name = "data disk - 'Mr. Muggles'" + read_only = 1 + +/obj/item/disk/data/monkey/New() + ..() + initialize() + buf.types=DNA2_BUF_SE + var/list/new_SE=list(0x098,0x3E8,0x403,0x44C,0x39F,0x4B0,0x59D,0x514,0x5FC,0x578,0x5DC,0x640,0x6A4) + for(var/i=new_SE.len;i<=DNA_SE_LENGTH;i++) + new_SE += rand(1,1024) + buf.dna.SE=new_SE + buf.dna.SetSEValueRange(MONKEYBLOCK,0xDAC, 0xFFF) + +//Disk stuff. +/obj/item/disk/data/New() + ..() + var/diskcolor = pick(0,1,2) + icon_state = "datadisk[diskcolor]" + +/obj/item/disk/data/attack_self(mob/user as mob) + read_only = !read_only + to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].") + +/obj/item/disk/data/examine(mob/user) + . = ..() + . += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]." + +//Clonepod + +/obj/machinery/clonepod/examine(mob/user) + . = ..() + if(mess) + . += "It's filled with blood and viscera. You swear you can see it moving..." + if(!occupant || stat & (NOPOWER|BROKEN)) + return + if(occupant && occupant.stat != DEAD) + . += "Current clone cycle is [round(get_completion())]% complete." + +/obj/machinery/clonepod/return_air() //non-reactive air + var/datum/gas_mixture/GM = new + GM.nitrogen = MOLES_O2STANDARD + MOLES_N2STANDARD + GM.temperature = T20C + return GM + +/obj/machinery/clonepod/proc/get_completion() + . = (100 * ((occupant.health + 100) / (heal_level + 100))) + +/obj/machinery/clonepod/attack_ai(mob/user) + return examine(user) + +//Radio Announcement + +/obj/machinery/clonepod/proc/announce_radio_message(message) + if(radio_announce) + Radio.autosay(message, name, "Medical", list(z)) + +/obj/machinery/clonepod/proc/spooky_devil_flavor() + playsound(loc, pick('sound/goonstation/voice/male_scream.ogg', 'sound/goonstation/voice/female_scream.ogg'), 100, 1) + mess = 1 + update_icon() + connected_message("If you keep trying to steal from me, you'll end up with me.") + +//Start growing a human clone in the pod! +/obj/machinery/clonepod/proc/growclone(datum/dna2/record/R) + if(mess || attempting || panel_open || stat & (NOPOWER|BROKEN)) + return 0 + clonemind = locate(R.mind) + if(!istype(clonemind)) //not a mind + return 0 + if(clonemind.current && clonemind.current.stat != DEAD) //mind is associated with a non-dead body + return 0 + if(clonemind.damnation_type) + spooky_devil_flavor() + return 0 + if(!clonemind.is_revivable()) //Other reasons for being unrevivable + return 0 + if(clonemind.active) //somebody is using that mind + if(ckey(clonemind.key) != R.ckey ) + return 0 + if(clonemind.suicided) // and stay out! + malfunction(go_easy = 0) + return -1 // Flush the record + else + // get_ghost() will fail if they're unable to reenter their body + var/mob/dead/observer/G = clonemind.get_ghost() + if(!G) + return 0 + +/* + if(clonemind.damnation_type) //Can't clone the damned. + playsound('sound/hallucinations/veryfar_noise.ogg', 50, 0) + malfunction() + return -1 // so that the record gets flushed out + */ + + if(biomass >= CLONE_BIOMASS) + biomass -= CLONE_BIOMASS + else + return 0 + + attempting = 1 //One at a time!! + countdown.start() + + if(!R.dna) + R.dna = new /datum/dna() + + var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) + H.set_species(R.dna.species.type) + occupant = H + + if(!R.dna.real_name) //to prevent null names + R.dna.real_name = H.real_name + else + H.real_name = R.dna.real_name + + H.dna = R.dna.Clone() + + for(var/datum/language/L in R.languages) + H.add_language(L.name) + + domutcheck(H, null, MUTCHK_FORCED) //Ensures species that get powers by the species proc handle_dna keep them + + if(efficiency > 2 && efficiency < 5 && prob(25)) + randmutb(H) + if(efficiency > 5 && prob(20)) + randmutg(H) + if(efficiency < 3 && prob(50)) + randmutb(H) + + H.dna.UpdateSE() + H.dna.UpdateUI() + + H.sync_organ_dna(1) // It's literally a fresh body as you can get, so all organs properly belong to it + H.UpdateAppearance() + + check_brine() + //Get the clone body ready + maim_clone(H) + H.Paralyse(4) + + if(grab_ghost_when == CLONER_FRESH_CLONE) + clonemind.transfer_to(H) + H.ckey = R.ckey + update_clone_antag(H) //Since the body's got the mind, update their antag stuff right now. Otherwise, wait until they get kicked out (as per the CLONER_MATURE_CLONE business) to do it. + to_chat(H, {"Consciousness slowly creeps over you + as your body regenerates.
        So this is what cloning + feels like?
        "}) + else if(grab_ghost_when == CLONER_MATURE_CLONE) + to_chat(clonemind.current, {"Your body is + beginning to regenerate in a cloning pod. You will + become conscious when it is complete."}) + // Set up a soul link with the dead body to catch a revival + soullink(/datum/soullink/soulhook, clonemind.current, src) + + update_icon() + + H.suiciding = 0 + attempting = 0 + return 1 + +//Grow clones to maturity then kick them out. FREELOADERS +/obj/machinery/clonepod/process() + var/show_message = 0 + for(var/obj/item/reagent_containers/food/snacks/meat/meat in range(1, src)) + qdel(meat) + biomass += BIOMASS_MEAT_AMOUNT + show_message = 1 + if(show_message) + visible_message("[src] sucks in and processes the nearby biomass.") + + if(stat & NOPOWER) //Autoeject if power is lost + if(occupant) + go_out() + connected_message("Clone Ejected: Loss of power.") + + else if((occupant) && (occupant.loc == src)) + if((occupant.stat == DEAD) || (occupant.suiciding) || (occupant.mind && !occupant.mind.is_revivable())) //Autoeject corpses and suiciding dudes. + announce_radio_message("The cloning of [occupant] has been aborted due to unrecoverable tissue failure.") + go_out() + connected_message("Clone Rejected: Deceased.") + + else if(occupant.cloneloss > (100 - heal_level)) + occupant.Paralyse(4) + + //Slowly get that clone healed and finished. + occupant.adjustCloneLoss(-((speed_coeff/2))) + + // For human species that lack non-vital parts for some weird reason + if(organs_number) + var/progress = CLONE_INITIAL_DAMAGE - occupant.getCloneLoss() + progress += (100 - MINIMUM_HEAL_LEVEL) + var/milestone = CLONE_INITIAL_DAMAGE / organs_number +// Doing this as a #define so that the value can change when evaluated multiple times +#define INSTALLED (organs_number - LAZYLEN(missing_organs)) + + while((progress / milestone) > INSTALLED && LAZYLEN(missing_organs)) + var/obj/item/organ/I = pick_n_take(missing_organs) + I.safe_replace(occupant) + +#undef INSTALLED + + //Premature clones may have brain damage. + occupant.adjustBrainLoss(-((speed_coeff/20)*efficiency)) + + check_brine() + + //Also heal some oxyloss ourselves just in case!! + occupant.adjustOxyLoss(-10) + + use_power(7500) //This might need tweaking. + + else if((occupant.cloneloss <= (100 - heal_level))) + connected_message("Cloning Process Complete.") + announce_radio_message("The cloning cycle of [occupant] is complete.") + go_out() + + else if((!occupant) || (occupant.loc != src)) + occupant = null + update_icon() + use_power(200) + +//Let's unlock this early I guess. Might be too early, needs tweaking. +/obj/machinery/clonepod/attackby(obj/item/I, mob/user, params) + if(exchange_parts(user, I)) + return + + if(I.GetID()) + if(!check_access(I)) + to_chat(user, "Access Denied.") + return + if(!(occupant || mess)) + to_chat(user, "Error: Pod has no occupant.") + return + else + connected_message("Authorized Ejection") + announce_radio_message("An authorized ejection of [(occupant) ? occupant.real_name : "the malfunctioning pod"] has occured") + to_chat(user, "You force an emergency ejection.") + go_out() + +//Removing cloning pod biomass + else if(istype(I, /obj/item/reagent_containers/food/snacks/meat)) + if(user.drop_item()) + to_chat(user, "[src] processes [I].") + biomass += BIOMASS_MEAT_AMOUNT + qdel(I) + else + return ..() + +/obj/machinery/clonepod/crowbar_act(mob/user, obj/item/I) + . = TRUE + default_deconstruction_crowbar(user, I) + +/obj/machinery/clonepod/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!I.multitool_check_buffer(user)) + return + var/obj/item/multitool/M = I + M.set_multitool_buffer(user, src) + +/obj/machinery/clonepod/screwdriver_act(mob/user, obj/item/I) + . = TRUE + default_deconstruction_screwdriver(user, "[icon_state]_maintenance", "[initial(icon_state)]", I) + +/obj/machinery/clonepod/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(occupant) + to_chat(user, "Can not do that while [src] is in use.") + return + if(anchored) + WRENCH_UNANCHOR_MESSAGE + anchored = FALSE + connected.pods -= src + connected = null + else + WRENCH_ANCHOR_MESSAGE + anchored = TRUE + +/obj/machinery/clonepod/emag_act(user) + if(isnull(occupant)) + return + go_out() + +/obj/machinery/clonepod/proc/update_clone_antag(var/mob/living/carbon/human/H) + // Check to see if the clone's mind is an antagonist of any kind and handle them accordingly to make sure they get their spells, HUD/whatever else back. + if((H.mind in SSticker.mode:revolutionaries) || (H.mind in SSticker.mode:head_revolutionaries)) + SSticker.mode.update_rev_icons_added() //So the icon actually appears + if(H.mind in SSticker.mode.syndicates) + SSticker.mode.update_synd_icons_added() + if(H.mind in SSticker.mode.cult) + SSticker.mode.add_cultist(occupant.mind) + SSticker.mode.update_cult_icons_added() //So the icon actually appears + SSticker.mode.update_cult_comms_added(H.mind) //So the comms actually appears + if(H.mind.vampire) + H.mind.vampire.update_owner(H) + if((H.mind in SSticker.mode.vampire_thralls) || (H.mind in SSticker.mode.vampire_enthralled)) + SSticker.mode.update_vampire_icons_added(H.mind) + if(H.mind in SSticker.mode.changelings) + SSticker.mode.update_change_icons_added(H.mind) + if((H.mind in SSticker.mode.shadowling_thralls) || (H.mind in SSticker.mode.shadows)) + SSticker.mode.update_shadow_icons_added(H.mind) + +//Put messages in the connected computer's temp var for display. +/obj/machinery/clonepod/proc/connected_message(message) + if((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning))) + return 0 + if(!message) + return 0 + + connected.temp = "[name] : [message]" + connected.updateUsrDialog() + return 1 + +/obj/machinery/clonepod/proc/go_out() + countdown.stop() + var/turf/T = get_turf(src) + if(mess) //Clean that mess and dump those gibs! + for(var/i in missing_organs) + var/obj/I = i + I.forceMove(T) + missing_organs.Cut() + mess = FALSE + new /obj/effect/gibspawner/generic(get_turf(src), occupant) + playsound(loc, 'sound/effects/splat.ogg', 50, 1) + update_icon() + return + + if(!occupant) + return + + if(grab_ghost_when == CLONER_MATURE_CLONE) + clonemind.transfer_to(occupant) + occupant.grab_ghost() + update_clone_antag(occupant) + to_chat(occupant, "There is a bright flash!
        \ + You feel like a new being.
        ") + occupant.flash_eyes(visual = 1) + for(var/s in sharedSoulhooks) + var/datum/soullink/S = s + S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed + sharedSoulhooks = null + + + for(var/i in missing_organs) + qdel(i) + missing_organs.Cut() + occupant.SetLoseBreath(0) // Stop friggin' dying, gosh damn + occupant.setOxyLoss(0) + for(var/datum/disease/critical/crit in occupant.viruses) + crit.cure() + occupant.forceMove(T) + occupant.update_body() + domutcheck(occupant) //Waiting until they're out before possible notransform. + occupant.special_post_clone_handling() + occupant = null + update_icon() + +/obj/machinery/clonepod/proc/malfunction(go_easy = FALSE) + if(occupant) + connected_message("Critical Error!") + announce_radio_message("Critical error! Please contact a Thinktronic Systems technician, as your warranty may be affected.") + for(var/s in sharedSoulhooks) + var/datum/soullink/S = s + S.removeSoulsharer(src) //If a sharer is destroy()'d, they are simply removed + sharedSoulhooks = null + if(!go_easy) + if(occupant.mind != clonemind) + clonemind.transfer_to(occupant) + occupant.grab_ghost() // We really just want to make you suffer. + to_chat(occupant, {"Agony blazes across your + consciousness as your body is torn apart.
        + Is this what dying is like? Yes it is.
        "}) + occupant << sound('sound/hallucinations/veryfar_noise.ogg',0,1,50) + for(var/i in missing_organs) + qdel(i) + missing_organs.Cut() + spawn(40) + qdel(occupant) + + + playsound(loc, 'sound/machines/warning-buzzer.ogg', 50, 0) + mess = TRUE + update_icon() + +/obj/machinery/clonepod/update_icon() + ..() + icon_state = "pod_0" + if(occupant && !(stat & NOPOWER)) + icon_state = "pod_1" + else if(mess && !panel_open) + icon_state = "pod_g" + +/obj/machinery/clonepod/relaymove(mob/user) + if(user.stat == CONSCIOUS) + go_out() + +/obj/machinery/clonepod/emp_act(severity) + if(prob(100/(severity*efficiency))) malfunction() + ..() + +/obj/machinery/clonepod/ex_act(severity) + ..() + if(!QDELETED(src) && occupant) + go_out() + +/obj/machinery/clonepod/handle_atom_del(atom/A) + if(A == occupant) + occupant = null + countdown.stop() + +/obj/machinery/clonepod/deconstruct(disassembled = TRUE) + if(occupant) + go_out() + ..() + +/obj/machinery/clonepod/onSoullinkRevive(mob/living/L) + if(occupant && L == clonemind.current) + // The old body's back in shape, time to ditch the cloning one + malfunction(go_easy = TRUE) + +/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H) + LAZYINITLIST(missing_organs) + for(var/i in missing_organs) + qdel(i) + missing_organs.Cut() + + H.setCloneLoss(CLONE_INITIAL_DAMAGE, FALSE) + H.setBrainLoss(BRAIN_INITIAL_DAMAGE) + + for(var/o in H.internal_organs) + var/obj/item/organ/O = o + if(!istype(O) || O.vital) + continue + + // Let's non-specially remove all non-vital organs + // What could possibly go wrong + var/obj/item/I = O.remove(H) + // Make this support stuff that turns into items when removed + I.forceMove(src) + missing_organs += I + + var/static/list/zones = list("r_arm", "l_arm", "r_leg", "l_leg") + for(var/zone in zones) + var/obj/item/organ/external/E = H.get_organ(zone) + var/obj/item/I = E.remove(H) + I.forceMove(src) + missing_organs += I + + organs_number = LAZYLEN(missing_organs) + H.updatehealth() + +/obj/machinery/clonepod/proc/check_brine() + // Clones are in a pickled bath of mild chemicals, keeping + // them alive, despite their lack of internal organs + for(var/bt in brine_types) + if(occupant.reagents.get_reagent_amount(bt) < 1) + occupant.reagents.add_reagent(bt, 1) + +/* + * Diskette Box + */ + +/obj/item/storage/box/disks + name = "Diskette Box" + icon_state = "disk_kit" + +/obj/item/storage/box/disks/New() + ..() + new /obj/item/disk/data(src) + new /obj/item/disk/data(src) + new /obj/item/disk/data(src) + new /obj/item/disk/data(src) + new /obj/item/disk/data(src) + new /obj/item/disk/data(src) + new /obj/item/disk/data(src) + +/* + * Manual -- A big ol' manual. + */ + +/obj/item/paper/Cloning + name = "paper - 'H-87 Cloning Apparatus Manual" + info = {"

        Getting Started

        + Congratulations, your station has purchased the H-87 industrial cloning device!
        + Using the H-87 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and select the scan option to create a new profile!
        + That's all there is to it!
        + Notice, cloning system cannot scan inorganic life or small primates. Scan may fail if subject has suffered extreme brain damage.
        +

        Clone profiles may be viewed through the profiles menu. Scanning implants a complementary HEALTH MONITOR IMPLANT into the subject, which may be viewed from each profile. + Profile Deletion has been restricted to \[Station Head\] level access.

        +

        Cloning from a profile

        + Cloning is as simple as pressing the CLONE option at the bottom of the desired profile.
        + Per your company's EMPLOYEE PRIVACY RIGHTS agreement, the H-87 has been blocked from cloning crewmembers while they are still alive.
        +
        +

        The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times (With SPEEDCLONE technology) are roughly 90 seconds. + The cloning pod may be unlocked early with any \[Medical Researcher\] ID after initial maturation is complete.


        + Please note that resulting clones may have a small DEVELOPMENTAL DEFECT as a result of genetic drift.
        +

        Profile Management

        +

        The H-87 (as well as your station's standard genetics machine) can accept STANDARD DATA DISKETTES. + These diskettes are used to transfer genetic information between machines and profiles. + A load/save dialog will become available in each profile if a disk is inserted.


        + A good diskette is a great way to counter aforementioned genetic drift!
        +
        + This technology produced under license from Thinktronic Systems, LTD."} + +//SOME SCRAPS I GUESS +/* EMP grenade/spell effect + if(istype(A, /obj/machinery/clonepod)) + A:malfunction() +*/ + +#undef MINIMUM_HEAL_LEVEL diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index bf019593065..ad11d6fb7c5 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -1,188 +1,188 @@ -#define OP_COMPUTER_COOLDOWN 60 - -/obj/machinery/computer/operating - name = "operating computer" - density = 1 - anchored = 1.0 - icon_keyboard = "med_key" - icon_screen = "crew" - circuit = /obj/item/circuitboard/operating - var/obj/machinery/optable/table = null - var/mob/living/carbon/human/victim = null - light_color = LIGHT_COLOR_PURE_BLUE - var/verbose = 1 //general speaker toggle - var/patientName = null - var/oxyAlarm = 30 //oxy damage at which the computer will beep - var/choice = 0 //just for going into and out of the options menu - var/healthAnnounce = 1 //healther announcer toggle - var/crit = 1 //crit beeping toggle - var/nextTick = OP_COMPUTER_COOLDOWN - var/healthAlarm = 50 - var/oxy = 1 //oxygen beeping toggle - -/obj/machinery/computer/operating/New() - ..() - for(dir in list(NORTH,EAST,SOUTH,WEST)) - table = locate(/obj/machinery/optable, get_step(src, dir)) - if(table) - table.computer = src - break - -/obj/machinery/computer/operating/Destroy() - if(table) - table.computer = null - table = null - if(victim) - victim = null - return ..() - -/obj/machinery/computer/operating/attack_ai(mob/user) - add_fingerprint(user) - if(stat & (BROKEN|NOPOWER)) - return - ui_interact(user) - - -/obj/machinery/computer/operating/attack_hand(mob/user) - if(..(user)) - return - - if(stat & (NOPOWER|BROKEN)) - return - - - add_fingerprint(user) - ui_interact(user) - -/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)//ui is mostly copy pasta from the sleeper ui - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "op_computer.tmpl", "Patient Monitor", 650, 455) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/computer/operating/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - var/mob/living/carbon/human/occupant - if(table) - occupant = table.victim - data["hasOccupant"] = occupant ? 1 : 0 - var/occupantData[0] - - if(occupant) - occupantData["name"] = occupant.name - occupantData["stat"] = occupant.stat - occupantData["health"] = occupant.health - occupantData["maxHealth"] = occupant.maxHealth - occupantData["minHealth"] = HEALTH_THRESHOLD_DEAD - occupantData["bruteLoss"] = occupant.getBruteLoss() - occupantData["oxyLoss"] = occupant.getOxyLoss() - occupantData["toxLoss"] = occupant.getToxLoss() - occupantData["fireLoss"] = occupant.getFireLoss() - occupantData["paralysis"] = occupant.paralysis - occupantData["hasBlood"] = 0 - occupantData["bodyTemperature"] = occupant.bodytemperature - occupantData["maxTemp"] = 1000 // If you get a burning vox armalis into the sleeper, congratulations - // Because we can put simple_animals in here, we need to do something tricky to get things working nice - occupantData["temperatureSuitability"] = 0 // 0 is the baseline - if(ishuman(occupant) && occupant.dna.species) - var/datum/species/sp = occupant.dna.species - if(occupant.bodytemperature < sp.cold_level_3) - occupantData["temperatureSuitability"] = -3 - else if(occupant.bodytemperature < sp.cold_level_2) - occupantData["temperatureSuitability"] = -2 - else if(occupant.bodytemperature < sp.cold_level_1) - occupantData["temperatureSuitability"] = -1 - else if(occupant.bodytemperature > sp.heat_level_3) - occupantData["temperatureSuitability"] = 3 - else if(occupant.bodytemperature > sp.heat_level_2) - occupantData["temperatureSuitability"] = 2 - else if(occupant.bodytemperature > sp.heat_level_1) - occupantData["temperatureSuitability"] = 1 - else if(istype(occupant, /mob/living/simple_animal)) - var/mob/living/simple_animal/silly = occupant - if(silly.bodytemperature < silly.minbodytemp) - occupantData["temperatureSuitability"] = -3 - else if(silly.bodytemperature > silly.maxbodytemp) - occupantData["temperatureSuitability"] = 3 - // Blast you, imperial measurement system - occupantData["btCelsius"] = occupant.bodytemperature - T0C - occupantData["btFaren"] = ((occupant.bodytemperature - T0C) * (9.0/5.0))+ 32 - - if(ishuman(occupant) && !(NO_BLOOD in occupant.dna.species.species_traits)) - occupantData["pulse"] = occupant.get_pulse(GETPULSE_TOOL) - occupantData["hasBlood"] = 1 - occupantData["bloodLevel"] = round(occupant.blood_volume) - occupantData["bloodMax"] = occupant.max_blood - occupantData["bloodPercent"] = round(100*(occupant.blood_volume/occupant.max_blood), 0.01) //copy pasta ends here - - occupantData["bloodType"] = occupant.dna.blood_type - if(occupant.surgeries.len) - occupantData["inSurgery"] = 1 - for(var/datum/surgery/procedure in occupant.surgeries) - occupantData["surgeryName"] = "[capitalize(procedure.name)]" - var/datum/surgery_step/surgery_step = procedure.get_surgery_step() - occupantData["stepName"] = "[capitalize(surgery_step.name)]" - - data["occupant"] = occupantData - data["verbose"]=verbose - data["oxyAlarm"]=oxyAlarm - data["choice"]=choice - data["health"]=healthAnnounce - data["crit"]=crit - data["healthAlarm"]=healthAlarm - data["oxy"]=oxy - - return data - - -/obj/machinery/computer/operating/Topic(href, href_list) - if(..()) - return 1 - if((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon))) - usr.set_machine(src) - - if(href_list["verboseOn"]) - verbose=1 - if(href_list["verboseOff"]) - verbose=0 - if(href_list["healthOn"]) - healthAnnounce=1 - if(href_list["healthOff"]) - healthAnnounce=0 - if(href_list["critOn"]) - crit=1 - if(href_list["critOff"]) - crit=0 - if(href_list["oxyOn"]) - oxy=1 - if(href_list["oxyOff"]) - oxy=0 - if(href_list["oxy_adj"]!=0) - oxyAlarm=oxyAlarm+text2num(href_list["oxy_adj"]) - if(href_list["choiceOn"]) - choice=1 - if(href_list["choiceOff"]) - choice=0 - if(href_list["health_adj"]!=0) - healthAlarm=healthAlarm+text2num(href_list["health_adj"]) - return - - -/obj/machinery/computer/operating/process() - - if(table && table.check_victim()) - if(verbose) - if(patientName!=table.victim.name) - patientName=table.victim.name - atom_say("New patient detected, loading stats") - victim = table.victim - atom_say("[victim.real_name], [victim.dna.blood_type] blood, [victim.stat ? "Non-Responsive" : "Awake"]") - if(nextTick < world.time) - nextTick=world.time + OP_COMPUTER_COOLDOWN - if(crit && victim.health <= -50 ) - playsound(src.loc, 'sound/machines/defib_success.ogg', 50, 0) - if(oxy && victim.getOxyLoss()>oxyAlarm) - playsound(src.loc, 'sound/machines/defib_saftyoff.ogg', 50, 0) - if(healthAnnounce && victim.health <= healthAlarm) - atom_say("[round(victim.health)]") +#define OP_COMPUTER_COOLDOWN 60 + +/obj/machinery/computer/operating + name = "operating computer" + density = 1 + anchored = 1.0 + icon_keyboard = "med_key" + icon_screen = "crew" + circuit = /obj/item/circuitboard/operating + var/obj/machinery/optable/table = null + var/mob/living/carbon/human/victim = null + light_color = LIGHT_COLOR_PURE_BLUE + var/verbose = 1 //general speaker toggle + var/patientName = null + var/oxyAlarm = 30 //oxy damage at which the computer will beep + var/choice = 0 //just for going into and out of the options menu + var/healthAnnounce = 1 //healther announcer toggle + var/crit = 1 //crit beeping toggle + var/nextTick = OP_COMPUTER_COOLDOWN + var/healthAlarm = 50 + var/oxy = 1 //oxygen beeping toggle + +/obj/machinery/computer/operating/New() + ..() + for(dir in list(NORTH,EAST,SOUTH,WEST)) + table = locate(/obj/machinery/optable, get_step(src, dir)) + if(table) + table.computer = src + break + +/obj/machinery/computer/operating/Destroy() + if(table) + table.computer = null + table = null + if(victim) + victim = null + return ..() + +/obj/machinery/computer/operating/attack_ai(mob/user) + add_fingerprint(user) + if(stat & (BROKEN|NOPOWER)) + return + ui_interact(user) + + +/obj/machinery/computer/operating/attack_hand(mob/user) + if(..(user)) + return + + if(stat & (NOPOWER|BROKEN)) + return + + + add_fingerprint(user) + ui_interact(user) + +/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)//ui is mostly copy pasta from the sleeper ui + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "op_computer.tmpl", "Patient Monitor", 650, 455) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/operating/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + var/mob/living/carbon/human/occupant + if(table) + occupant = table.victim + data["hasOccupant"] = occupant ? 1 : 0 + var/occupantData[0] + + if(occupant) + occupantData["name"] = occupant.name + occupantData["stat"] = occupant.stat + occupantData["health"] = occupant.health + occupantData["maxHealth"] = occupant.maxHealth + occupantData["minHealth"] = HEALTH_THRESHOLD_DEAD + occupantData["bruteLoss"] = occupant.getBruteLoss() + occupantData["oxyLoss"] = occupant.getOxyLoss() + occupantData["toxLoss"] = occupant.getToxLoss() + occupantData["fireLoss"] = occupant.getFireLoss() + occupantData["paralysis"] = occupant.paralysis + occupantData["hasBlood"] = 0 + occupantData["bodyTemperature"] = occupant.bodytemperature + occupantData["maxTemp"] = 1000 // If you get a burning vox armalis into the sleeper, congratulations + // Because we can put simple_animals in here, we need to do something tricky to get things working nice + occupantData["temperatureSuitability"] = 0 // 0 is the baseline + if(ishuman(occupant) && occupant.dna.species) + var/datum/species/sp = occupant.dna.species + if(occupant.bodytemperature < sp.cold_level_3) + occupantData["temperatureSuitability"] = -3 + else if(occupant.bodytemperature < sp.cold_level_2) + occupantData["temperatureSuitability"] = -2 + else if(occupant.bodytemperature < sp.cold_level_1) + occupantData["temperatureSuitability"] = -1 + else if(occupant.bodytemperature > sp.heat_level_3) + occupantData["temperatureSuitability"] = 3 + else if(occupant.bodytemperature > sp.heat_level_2) + occupantData["temperatureSuitability"] = 2 + else if(occupant.bodytemperature > sp.heat_level_1) + occupantData["temperatureSuitability"] = 1 + else if(istype(occupant, /mob/living/simple_animal)) + var/mob/living/simple_animal/silly = occupant + if(silly.bodytemperature < silly.minbodytemp) + occupantData["temperatureSuitability"] = -3 + else if(silly.bodytemperature > silly.maxbodytemp) + occupantData["temperatureSuitability"] = 3 + // Blast you, imperial measurement system + occupantData["btCelsius"] = occupant.bodytemperature - T0C + occupantData["btFaren"] = ((occupant.bodytemperature - T0C) * (9.0/5.0))+ 32 + + if(ishuman(occupant) && !(NO_BLOOD in occupant.dna.species.species_traits)) + occupantData["pulse"] = occupant.get_pulse(GETPULSE_TOOL) + occupantData["hasBlood"] = 1 + occupantData["bloodLevel"] = round(occupant.blood_volume) + occupantData["bloodMax"] = occupant.max_blood + occupantData["bloodPercent"] = round(100*(occupant.blood_volume/occupant.max_blood), 0.01) //copy pasta ends here + + occupantData["bloodType"] = occupant.dna.blood_type + if(occupant.surgeries.len) + occupantData["inSurgery"] = 1 + for(var/datum/surgery/procedure in occupant.surgeries) + occupantData["surgeryName"] = "[capitalize(procedure.name)]" + var/datum/surgery_step/surgery_step = procedure.get_surgery_step() + occupantData["stepName"] = "[capitalize(surgery_step.name)]" + + data["occupant"] = occupantData + data["verbose"]=verbose + data["oxyAlarm"]=oxyAlarm + data["choice"]=choice + data["health"]=healthAnnounce + data["crit"]=crit + data["healthAlarm"]=healthAlarm + data["oxy"]=oxy + + return data + + +/obj/machinery/computer/operating/Topic(href, href_list) + if(..()) + return 1 + if((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon))) + usr.set_machine(src) + + if(href_list["verboseOn"]) + verbose=1 + if(href_list["verboseOff"]) + verbose=0 + if(href_list["healthOn"]) + healthAnnounce=1 + if(href_list["healthOff"]) + healthAnnounce=0 + if(href_list["critOn"]) + crit=1 + if(href_list["critOff"]) + crit=0 + if(href_list["oxyOn"]) + oxy=1 + if(href_list["oxyOff"]) + oxy=0 + if(href_list["oxy_adj"]!=0) + oxyAlarm=oxyAlarm+text2num(href_list["oxy_adj"]) + if(href_list["choiceOn"]) + choice=1 + if(href_list["choiceOff"]) + choice=0 + if(href_list["health_adj"]!=0) + healthAlarm=healthAlarm+text2num(href_list["health_adj"]) + return + + +/obj/machinery/computer/operating/process() + + if(table && table.check_victim()) + if(verbose) + if(patientName!=table.victim.name) + patientName=table.victim.name + atom_say("New patient detected, loading stats") + victim = table.victim + atom_say("[victim.real_name], [victim.dna.blood_type] blood, [victim.stat ? "Non-Responsive" : "Awake"]") + if(nextTick < world.time) + nextTick=world.time + OP_COMPUTER_COOLDOWN + if(crit && victim.health <= -50 ) + playsound(src.loc, 'sound/machines/defib_success.ogg', 50, 0) + if(oxy && victim.getOxyLoss()>oxyAlarm) + playsound(src.loc, 'sound/machines/defib_saftyoff.ogg', 50, 0) + if(healthAnnounce && victim.health <= healthAlarm) + atom_say("[round(victim.health)]") diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm index 8d27ac5e7fa..d53903857ef 100644 --- a/code/game/machinery/computer/ai_core.dm +++ b/code/game/machinery/computer/ai_core.dm @@ -305,4 +305,4 @@ atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/ to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") qdel(src) else //If for some reason you use an empty card on an empty AI terminal. - to_chat(user, "There is no AI loaded on this terminal!") \ No newline at end of file + to_chat(user, "There is no AI loaded on this terminal!") diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm index c7482bc140d..0d715fe5759 100644 --- a/code/game/machinery/computer/aifixer.dm +++ b/code/game/machinery/computer/aifixer.dm @@ -1,147 +1,147 @@ -/obj/machinery/computer/aifixer - name = "\improper AI system integrity restorer" - icon = 'icons/obj/computer.dmi' - icon_keyboard = "rd_key" - icon_screen = "ai-fixer" - circuit = /obj/item/circuitboard/aifixer - req_access = list(ACCESS_CAPTAIN, ACCESS_ROBOTICS, ACCESS_HEADS) - var/mob/living/silicon/ai/occupant = null - var/active = 0 - - light_color = LIGHT_COLOR_PURPLE - -/obj/machinery/computer/aifixer/attackby(I as obj, user as mob, params) - if(occupant && istype(I, /obj/item/screwdriver)) - if(stat & BROKEN) - ..() - if(stat & NOPOWER) - to_chat(user, "The screws on [name]'s screen won't budge.") - else - to_chat(user, "The screws on [name]'s screen won't budge and it emits a warning beep!.") - else - return ..() - -/obj/machinery/computer/aifixer/attack_ai(var/mob/user as mob) - ui_interact(user) - -/obj/machinery/computer/aifixer/attack_hand(var/mob/user as mob) - ui_interact(user) - -/obj/machinery/computer/aifixer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - - if(!ui) - ui = new(user, src, ui_key, "ai_fixer.tmpl", "AI System Integrity Restorer", 550, 500) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/computer/aifixer/ui_data(mob/user, datum/topic_state/state) - var/data[0] - if(occupant) - data["occupant"] = occupant.name - data["reference"] = "\ref[occupant]" - data["integrity"] = (occupant.health+100)/2 - data["stat"] = occupant.stat - data["active"] = active - data["wireless"] = occupant.control_disabled - data["radio"] = occupant.aiRadio.disabledAi - - var/laws[0] - for(var/datum/ai_law/law in occupant.laws.all_laws()) - laws.Add(list(list("law" = law.law, "number" = law.get_index()))) - - data["laws"] = laws - - return data - -/obj/machinery/computer/aifixer/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["fix"]) - active = 1 - while(occupant.health < 100) - occupant.adjustOxyLoss(-1, FALSE) - occupant.adjustFireLoss(-1, FALSE) - occupant.adjustToxLoss(-1, FALSE) - occupant.adjustBruteLoss(-1, FALSE) - occupant.updatehealth() - if(occupant.health >= 0 && occupant.stat == DEAD) - occupant.update_revive() - occupant.lying = 0 - update_icon() - sleep(10) - active = 0 - add_fingerprint(usr) - - if(href_list["wireless"]) - var/wireless = text2num(href_list["wireless"]) - if(wireless == 0 || wireless == 1) - occupant.control_disabled = wireless - - if(href_list["radio"]) - var/radio = text2num(href_list["radio"]) - if(radio == 0 || radio == 1) - occupant.aiRadio.disabledAi = radio - - SSnanoui.update_uis(src) - update_icon() - return - -/obj/machinery/computer/aifixer/update_icon() - ..() - if(stat & (NOPOWER|BROKEN)) - return - else - var/overlay_layer = LIGHTING_LAYER+0.2 // +0.1 from the default computer overlays - if(active) - overlays += image(icon,"ai-fixer-on",overlay_layer) - if(occupant) - switch(occupant.stat) - if(0) - overlays += image(icon,"ai-fixer-full",overlay_layer) - if(2) - overlays += image(icon,"ai-fixer-404",overlay_layer) - else - overlays += image(icon,"ai-fixer-empty",overlay_layer) - -/obj/machinery/computer/aifixer/transfer_ai(var/interaction, var/mob/user, var/mob/living/silicon/ai/AI, var/obj/item/aicard/card) - if(!..()) - return - //Downloading AI from card to terminal. - if(interaction == AI_TRANS_FROM_CARD) - if(stat & (NOPOWER|BROKEN)) - to_chat(user, "[src] is offline and cannot take an AI at this time!") - return - AI.loc = src - occupant = AI - AI.control_disabled = 1 - AI.aiRadio.disabledAi = 1 - to_chat(AI, "You have been uploaded to a stationary terminal. Sadly, there is no remote access from here.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - update_icon() - - else //Uploading AI from terminal to card - if(occupant && !active) - to_chat(occupant, "You have been downloaded to a mobile storage device. Still no remote access.") - to_chat(user, "Transfer successful: [occupant.name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.") - occupant.loc = card - occupant = null - update_icon() - else if(active) - to_chat(user, "ERROR: Reconstruction in progress.") - else if(!occupant) - to_chat(user, "ERROR: Unable to locate artificial intelligence.") - -/obj/machinery/computer/aifixer/Destroy() - if(occupant) - occupant.ghostize() - QDEL_NULL(occupant) - return ..() - -/obj/machinery/computer/aifixer/emp_act() - if(occupant) - occupant.ghostize() - QDEL_NULL(occupant) - else - ..() +/obj/machinery/computer/aifixer + name = "\improper AI system integrity restorer" + icon = 'icons/obj/computer.dmi' + icon_keyboard = "rd_key" + icon_screen = "ai-fixer" + circuit = /obj/item/circuitboard/aifixer + req_access = list(ACCESS_CAPTAIN, ACCESS_ROBOTICS, ACCESS_HEADS) + var/mob/living/silicon/ai/occupant = null + var/active = 0 + + light_color = LIGHT_COLOR_PURPLE + +/obj/machinery/computer/aifixer/attackby(I as obj, user as mob, params) + if(occupant && istype(I, /obj/item/screwdriver)) + if(stat & BROKEN) + ..() + if(stat & NOPOWER) + to_chat(user, "The screws on [name]'s screen won't budge.") + else + to_chat(user, "The screws on [name]'s screen won't budge and it emits a warning beep!.") + else + return ..() + +/obj/machinery/computer/aifixer/attack_ai(var/mob/user as mob) + ui_interact(user) + +/obj/machinery/computer/aifixer/attack_hand(var/mob/user as mob) + ui_interact(user) + +/obj/machinery/computer/aifixer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + + if(!ui) + ui = new(user, src, ui_key, "ai_fixer.tmpl", "AI System Integrity Restorer", 550, 500) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/aifixer/ui_data(mob/user, datum/topic_state/state) + var/data[0] + if(occupant) + data["occupant"] = occupant.name + data["reference"] = "\ref[occupant]" + data["integrity"] = (occupant.health+100)/2 + data["stat"] = occupant.stat + data["active"] = active + data["wireless"] = occupant.control_disabled + data["radio"] = occupant.aiRadio.disabledAi + + var/laws[0] + for(var/datum/ai_law/law in occupant.laws.all_laws()) + laws.Add(list(list("law" = law.law, "number" = law.get_index()))) + + data["laws"] = laws + + return data + +/obj/machinery/computer/aifixer/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["fix"]) + active = 1 + while(occupant.health < 100) + occupant.adjustOxyLoss(-1, FALSE) + occupant.adjustFireLoss(-1, FALSE) + occupant.adjustToxLoss(-1, FALSE) + occupant.adjustBruteLoss(-1, FALSE) + occupant.updatehealth() + if(occupant.health >= 0 && occupant.stat == DEAD) + occupant.update_revive() + occupant.lying = 0 + update_icon() + sleep(10) + active = 0 + add_fingerprint(usr) + + if(href_list["wireless"]) + var/wireless = text2num(href_list["wireless"]) + if(wireless == 0 || wireless == 1) + occupant.control_disabled = wireless + + if(href_list["radio"]) + var/radio = text2num(href_list["radio"]) + if(radio == 0 || radio == 1) + occupant.aiRadio.disabledAi = radio + + SSnanoui.update_uis(src) + update_icon() + return + +/obj/machinery/computer/aifixer/update_icon() + ..() + if(stat & (NOPOWER|BROKEN)) + return + else + var/overlay_layer = LIGHTING_LAYER+0.2 // +0.1 from the default computer overlays + if(active) + overlays += image(icon,"ai-fixer-on",overlay_layer) + if(occupant) + switch(occupant.stat) + if(0) + overlays += image(icon,"ai-fixer-full",overlay_layer) + if(2) + overlays += image(icon,"ai-fixer-404",overlay_layer) + else + overlays += image(icon,"ai-fixer-empty",overlay_layer) + +/obj/machinery/computer/aifixer/transfer_ai(var/interaction, var/mob/user, var/mob/living/silicon/ai/AI, var/obj/item/aicard/card) + if(!..()) + return + //Downloading AI from card to terminal. + if(interaction == AI_TRANS_FROM_CARD) + if(stat & (NOPOWER|BROKEN)) + to_chat(user, "[src] is offline and cannot take an AI at this time!") + return + AI.loc = src + occupant = AI + AI.control_disabled = 1 + AI.aiRadio.disabledAi = 1 + to_chat(AI, "You have been uploaded to a stationary terminal. Sadly, there is no remote access from here.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + update_icon() + + else //Uploading AI from terminal to card + if(occupant && !active) + to_chat(occupant, "You have been downloaded to a mobile storage device. Still no remote access.") + to_chat(user, "Transfer successful: [occupant.name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.") + occupant.loc = card + occupant = null + update_icon() + else if(active) + to_chat(user, "ERROR: Reconstruction in progress.") + else if(!occupant) + to_chat(user, "ERROR: Unable to locate artificial intelligence.") + +/obj/machinery/computer/aifixer/Destroy() + if(occupant) + occupant.ghostize() + QDEL_NULL(occupant) + return ..() + +/obj/machinery/computer/aifixer/emp_act() + if(occupant) + occupant.ghostize() + QDEL_NULL(occupant) + else + ..() diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm index 28f847d81eb..20ff79041a9 100644 --- a/code/game/machinery/computer/arcade.dm +++ b/code/game/machinery/computer/arcade.dm @@ -1,1023 +1,1023 @@ -/obj/machinery/computer/arcade - name = "random arcade" - desc = "random arcade machine" - icon = 'icons/obj/computer.dmi' - icon_state = "arcade" - icon_keyboard = null - icon_screen = "invaders" - light_color = "#00FF00" - var/prize = /obj/item/stack/tickets - -/obj/machinery/computer/arcade/proc/Reset() - return - -/obj/machinery/computer/arcade/New() - ..() - if(!circuit) - var/choice = pick(subtypesof(/obj/machinery/computer/arcade)) - new choice(loc) - qdel(src) - return - Reset() - - -/obj/machinery/computer/arcade/proc/prizevend(var/score) - if(!contents.len) - var/prize_amount - if(score) - prize_amount = score - else - prize_amount = rand(1, 10) - new prize(get_turf(src), prize_amount) - else - var/atom/movable/prize = pick(contents) - prize.loc = get_turf(src) - -/obj/machinery/computer/arcade/emp_act(severity) - ..(severity) - if(stat & (NOPOWER|BROKEN)) - return - var/num_of_prizes = 0 - switch(severity) - if(1) - num_of_prizes = rand(1,4) - if(2) - num_of_prizes = rand(0,2) - for(var/i = num_of_prizes; i > 0; i--) - prizevend() - explosion(get_turf(src), -1, 0, 1+num_of_prizes, flame_range = 1+num_of_prizes) - - -/obj/machinery/computer/arcade/battle - name = "arcade machine" - desc = "Does not support Pinball." - icon = 'icons/obj/computer.dmi' - icon_state = "arcade" - circuit = /obj/item/circuitboard/arcade/battle - var/enemy_name = "Space Villian" - var/temp = "Winners Don't Use Spacedrugs" //Temporary message, for attack messages, etc - var/player_hp = 30 //Player health/attack points - var/player_mp = 10 - var/enemy_hp = 45 //Enemy health/attack points - var/enemy_mp = 20 - var/gameover = 0 - var/blocked = 0 //Player cannot attack/heal while set - var/turtle = 0 - -/obj/machinery/computer/arcade/battle/Reset() - var/name_action - var/name_part1 - var/name_part2 - - name_action = pick("Defeat ", "Annihilate ", "Save ", "Strike ", "Stop ", "Destroy ", "Robust ", "Romance ", "Pwn ", "Own ", "Ban ") - - name_part1 = pick("the Automatic ", "Farmer ", "Lord ", "Professor ", "the Cuban ", "the Evil ", "the Dread King ", "the Space ", "Lord ", "the Great ", "Duke ", "General ") - name_part2 = pick("Melonoid", "Murdertron", "Sorcerer", "Ruin", "Jeff", "Ectoplasm", "Crushulon", "Uhangoid", "Vhakoid", "Peteoid", "slime", "Griefer", "ERPer", "Lizard Man", "Unicorn", "Bloopers") - - enemy_name = replacetext((name_part1 + name_part2), "the ", "") - name = (name_action + name_part1 + name_part2) - -/obj/machinery/computer/arcade/battle/attack_hand(mob/user as mob) - if(..()) - return - user.set_machine(src) - var/dat = "Close" - dat += "

        [enemy_name]

        " - - dat += "

        [temp]

        " - dat += "
        Health: [player_hp] | Magic: [player_mp] | Enemy Health: [enemy_hp]
        " - - if(gameover) - dat += "
        New Game" - else - dat += "
        Attack | " - dat += "Heal | " - dat += "Recharge Power" - - dat += "
        " - - //user << browse(dat, "window=arcade") - //onclose(user, "arcade") - var/datum/browser/popup = new(user, "arcade", "Space Villian 2000") - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - return - -/obj/machinery/computer/arcade/battle/Topic(href, href_list) - if(..()) - return - - if(!blocked && !gameover) - if(href_list["attack"]) - blocked = 1 - var/attackamt = rand(2,6) - temp = "You attack for [attackamt] damage!" - playsound(src.loc, 'sound/arcade/hit.ogg', 20, 1, extrarange = -6, falloff = 10) - updateUsrDialog() - if(turtle > 0) - turtle-- - - sleep(10) - enemy_hp -= attackamt - arcade_action() - - else if(href_list["heal"]) - blocked = 1 - var/pointamt = rand(1,3) - var/healamt = rand(6,8) - temp = "You use [pointamt] magic to heal for [healamt] damage!" - playsound(src.loc, 'sound/arcade/heal.ogg', 20, 1, extrarange = -6, falloff = 10) - updateUsrDialog() - turtle++ - - sleep(10) - player_mp -= pointamt - player_hp += healamt - blocked = 1 - updateUsrDialog() - arcade_action() - - else if(href_list["charge"]) - blocked = 1 - var/chargeamt = rand(4,7) - temp = "You regain [chargeamt] points" - playsound(src.loc, 'sound/arcade/mana.ogg', 20, 1, extrarange = -6, falloff = 10) - player_mp += chargeamt - if(turtle > 0) - turtle-- - - updateUsrDialog() - sleep(10) - arcade_action() - - if(href_list["close"]) - usr.unset_machine() - usr << browse(null, "window=arcade") - - else if(href_list["newgame"]) //Reset everything - temp = "New Round" - player_hp = 30 - player_mp = 10 - enemy_hp = 45 - enemy_mp = 20 - gameover = 0 - turtle = 0 - - if(emagged) - Reset() - emagged = 0 - - add_fingerprint(usr) - updateUsrDialog() - return - -/obj/machinery/computer/arcade/battle/proc/arcade_action() - if((enemy_mp <= 0) || (enemy_hp <= 0)) - if(!gameover) - gameover = 1 - temp = "[enemy_name] has fallen! Rejoice!" - playsound(src.loc, 'sound/arcade/win.ogg', 20, 1, extrarange = -6, falloff = 10) - - if(emagged) - feedback_inc("arcade_win_emagged") - new /obj/effect/spawner/newbomb/timer/syndicate(get_turf(src)) - new /obj/item/clothing/head/collectable/petehat(get_turf(src)) - message_admins("[key_name_admin(usr)] has outbombed Cuban Pete and been awarded a bomb.") - log_game("[key_name(usr)] has outbombed Cuban Pete and been awarded a bomb.") - Reset() - emagged = 0 - else - feedback_inc("arcade_win_normal") - var/score = player_hp + player_mp + 5 - prizevend(score) - - else if(emagged && (turtle >= 4)) - var/boomamt = rand(5,10) - temp = "[enemy_name] throws a bomb, exploding you for [boomamt] damage!" - playsound(src.loc, 'sound/arcade/boom.ogg', 20, 1, extrarange = -6, falloff = 10) - player_hp -= boomamt - - else if((enemy_mp <= 5) && (prob(70))) - var/stealamt = rand(2,3) - temp = "[enemy_name] steals [stealamt] of your power!" - playsound(src.loc, 'sound/arcade/steal.ogg', 20, 1, extrarange = -6, falloff = 10) - player_mp -= stealamt - updateUsrDialog() - - if(player_mp <= 0) - gameover = 1 - sleep(10) - temp = "You have been drained! GAME OVER" - playsound(src.loc, 'sound/arcade/lose.ogg', 20, 1, extrarange = -6, falloff = 10) - if(emagged) - feedback_inc("arcade_loss_mana_emagged") - usr.gib() - else - feedback_inc("arcade_loss_mana_normal") - - else if((enemy_hp <= 10) && (enemy_mp > 4)) - temp = "[enemy_name] heals for 4 health!" - playsound(src.loc, 'sound/arcade/heal.ogg', 20, 1, extrarange = -6, falloff = 10) - enemy_hp += 4 - enemy_mp -= 4 - - else - var/attackamt = rand(3,6) - temp = "[enemy_name] attacks for [attackamt] damage!" - playsound(src.loc, 'sound/arcade/hit.ogg', 20, 1, extrarange = -6, falloff = 10) - player_hp -= attackamt - - if((player_mp <= 0) || (player_hp <= 0)) - gameover = 1 - temp = "You have been crushed! GAME OVER" - playsound(src.loc, 'sound/arcade/lose.ogg', 20, 1, extrarange = -6, falloff = 10) - if(emagged) - feedback_inc("arcade_loss_hp_emagged") - usr.gib() - else - feedback_inc("arcade_loss_hp_normal") - - blocked = 0 - return - - -/obj/machinery/computer/arcade/battle/emag_act(user as mob) - if(!emagged) - temp = "If you die in the game, you die for real!" - player_hp = 30 - player_mp = 10 - enemy_hp = 45 - enemy_mp = 20 - gameover = 0 - blocked = 0 - - emagged = 1 - - enemy_name = "Cuban Pete" - name = "Outbomb Cuban Pete" - - updateUsrDialog() - -// *** THE ORION TRAIL ** // - -#define ORION_TRAIL_WINTURN 9 - -//Orion Trail Events -#define ORION_TRAIL_RAIDERS "Raiders" -#define ORION_TRAIL_FLUX "Interstellar Flux" -#define ORION_TRAIL_ILLNESS "Illness" -#define ORION_TRAIL_BREAKDOWN "Breakdown" -#define ORION_TRAIL_LING "Changelings?" -#define ORION_TRAIL_LING_ATTACK "Changeling Ambush" -#define ORION_TRAIL_MALFUNCTION "Malfunction" -#define ORION_TRAIL_COLLISION "Collision" -#define ORION_TRAIL_SPACEPORT "Spaceport" -#define ORION_TRAIL_BLACKHOLE "BlackHole" - - -/obj/machinery/computer/arcade/orion_trail - name = "The Orion Trail" - desc = "Learn how our ancestors got to Orion, and have fun in the process!" - icon_state = "arcade" - circuit = /obj/item/circuitboard/arcade/orion_trail - var/busy = 0 //prevent clickspam that allowed people to ~speedrun~ the game. - var/engine = 0 - var/hull = 0 - var/electronics = 0 - var/food = 80 - var/fuel = 60 - var/turns = 4 - var/playing = 0 - var/gameover = 0 - var/alive = 4 - var/eventdat = null - var/event = null - var/list/settlers = list("Harry","Larry","Bob") - var/list/events = list(ORION_TRAIL_RAIDERS = 3, - ORION_TRAIL_FLUX = 1, - ORION_TRAIL_ILLNESS = 3, - ORION_TRAIL_BREAKDOWN = 2, - ORION_TRAIL_LING = 3, - ORION_TRAIL_MALFUNCTION = 2, - ORION_TRAIL_COLLISION = 1, - ORION_TRAIL_SPACEPORT = 2 - ) - var/list/stops = list() - var/list/stopblurbs = list() - var/lings_aboard = 0 - var/spaceport_raided = 0 - var/spaceport_freebie = 0 - var/last_spaceport_action = "" - -/obj/machinery/computer/arcade/orion_trail/Reset() - // Sets up the main trail - stops = list("Pluto","Asteroid Belt","Proxima Centauri","Dead Space","Rigel Prime","Tau Ceti Beta","Black Hole","Space Outpost Beta-9","Orion Prime") - stopblurbs = list( - "Pluto, long since occupied with long-range sensors and scanners, stands ready to, and indeed continues to probe the far reaches of the galaxy.", - "At the edge of the Sol system lies a treacherous asteroid belt. Many have been crushed by stray asteroids and misguided judgement.", - "The nearest star system to Sol, in ages past it stood as a reminder of the boundaries of sub-light travel, now a low-population sanctuary for adventurers and traders.", - "This region of space is particularly devoid of matter. Such low-density pockets are known to exist, but the vastness of it is astounding.", - "Rigel Prime, the center of the Rigel system, burns hot, basking its planetary bodies in warmth and radiation.", - "Tau Ceti Beta has recently become a waypoint for colonists headed towards Orion. There are many ships and makeshift stations in the vicinity.", - "Sensors indicate that a black hole's gravitational field is affecting the region of space we were headed through. We could stay of course, but risk of being overcome by its gravity, or we could change course to go around, which will take longer.", - "You have come into range of the first man-made structure in this region of space. It has been constructed not by travellers from Sol, but by colonists from Orion. It stands as a monument to the colonists' success.", - "You have made it to Orion! Congratulations! Your crew is one of the few to start a new foothold for mankind!" - ) - -/obj/machinery/computer/arcade/orion_trail/proc/newgame() - // Set names of settlers in crew - settlers = list() - for(var/i = 1; i <= 3; i++) - add_crewmember() - add_crewmember("[usr]") - // Re-set items to defaults - engine = 1 - hull = 1 - electronics = 1 - food = 80 - fuel = 60 - alive = 4 - turns = 1 - event = null - playing = 1 - gameover = 0 - lings_aboard = 0 - - //spaceport junk - spaceport_raided = 0 - spaceport_freebie = 0 - last_spaceport_action = "" - -/obj/machinery/computer/arcade/orion_trail/attack_hand(mob/user) - if(..()) - return - if(fuel <= 0 || food <=0 || settlers.len == 0) - gameover = 1 - event = null - user.set_machine(src) - var/dat = "" - if(gameover) - dat = "

        Game Over

        " - dat += "Like many before you, your crew never made it to Orion, lost to space...
        Forever." - if(settlers.len == 0) - dat += "
        Your entire crew died, your ship joins the fleet of ghost-ships littering the galaxy." - else - if(food <= 0) - dat += "
        You ran out of food and starved." - if(emagged) - user.set_nutrition(0) //yeah you pretty hongry - to_chat(user, "Your body instantly contracts to that of one who has not eaten in months. Agonizing cramps seize you as you fall to the floor.") - if(fuel <= 0) - dat += "
        You ran out of fuel, and drift, slowly, into a star." - if(emagged) - var/mob/living/M = user - M.adjust_fire_stacks(5) - M.IgniteMob() //flew into a star, so you're on fire - to_chat(user, "You feel an immense wave of heat emanate from the arcade machine. Your skin bursts into flames.") - dat += "

        OK...

        " - - if(emagged) - to_chat(user, "You're never going to make it to Orion...") - user.death() - emagged = 0 //removes the emagged status after you lose - playing = 0 //also a new game - name = "The Orion Trail" - desc = "Learn how our ancestors got to Orion, and have fun in the process!" - - else if(event) - dat = eventdat - else if(playing) - var/title = stops[turns] - var/subtext = stopblurbs[turns] - dat = "

        [title]

        " - dat += "[subtext]" - dat += "

        Crew:

        " - dat += english_list(settlers) - dat += "
        Food: [food] | Fuel: [fuel]" - dat += "
        Engine Parts: [engine] | Hull Panels: [hull] | Electronics: [electronics]" - if(turns == 7) - dat += "

        Go Around Continue

        " - else - dat += "

        Continue

        " - dat += "

        Kill a crewmember

        " - dat += "

        Close

        " - else - dat = "

        The Orion Trail

        " - dat += "

        Experience the journey of your ancestors!



        " - dat += "
        New Game
        " - dat += "

        Close

        " - var/datum/browser/popup = new(user, "arcade", "The Orion Trail",400,700) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - return - -/obj/machinery/computer/arcade/orion_trail/Topic(href, href_list) - . = ..() - if(.) - return - if(href_list["close"]) - usr.unset_machine() - usr << browse(null, "window=arcade") - - if(busy) - return - busy = 1 - - if(href_list["continue"]) //Continue your travels - if(turns >= ORION_TRAIL_WINTURN) - win() - else - food -= (alive+lings_aboard)*2 - fuel -= 5 - if(turns == 2 && prob(30)) - event = ORION_TRAIL_COLLISION - event() - else if(prob(75)) - event = pickweight(events) - if(lings_aboard) - if(event == ORION_TRAIL_LING || prob(55)) - event = ORION_TRAIL_LING_ATTACK - event() - turns += 1 - if(emagged) - var/mob/living/carbon/M = usr //for some vars - switch(event) - if(ORION_TRAIL_RAIDERS) - if(prob(50)) - to_chat(usr, "You hear battle shouts. The tramping of boots on cold metal. Screams of agony. The rush of venting air. Are you going insane?") - M.AdjustHallucinate(30) - else - to_chat(usr, "Something strikes you from behind! It hurts like hell and feel like a blunt weapon, but nothing is there...") - M.take_organ_damage(30) - playsound(loc, 'sound/weapons/genhit2.ogg', 100, 1) - if(ORION_TRAIL_ILLNESS) - var/severity = rand(1,3) //pray to RNGesus. PRAY, PIGS - if(severity == 1) - to_chat(M, "You suddenly feel slightly nauseous.")//got off lucky - - if(severity == 2) - to_chat(usr, "You suddenly feel extremely nauseous and hunch over until it passes.") - M.Stun(3) - if(severity >= 3) //you didn't pray hard enough - to_chat(M, "An overpowering wave of nausea consumes over you. You hunch over, your stomach's contents preparing for a spectacular exit.") - M.Stun(5) - sleep(30) - atom_say("[M] violently throws up!") - playsound(loc, 'sound/effects/splat.ogg', 50, 1) - M.adjust_nutrition(-50) //lose a lot of food - var/turf/location = usr.loc - if(istype(location, /turf/simulated)) - location.add_vomit_floor(TRUE) - if(ORION_TRAIL_FLUX) - if(prob(75)) - M.Weaken(3) - atom_say("A sudden gust of powerful wind slams [M] into the floor!") - M.take_organ_damage(25) - playsound(src.loc, 'sound/weapons/genhit.ogg', 100, 1) - else - to_chat(M, "A violent gale blows past you, and you barely manage to stay standing!") - if(ORION_TRAIL_COLLISION) //by far the most damaging event - if(prob(90)) - playsound(src.loc, 'sound/effects/bang.ogg', 100, 1) - var/turf/simulated/floor/F - for(F in orange(1, src)) - F.ChangeTurf(F.baseturf) - atom_say("Something slams into the floor around [src], exposing it to space!") - if(hull) - sleep(10) - atom_say("A new floor suddenly appears around [src]. What the hell?") - playsound(src.loc, 'sound/weapons/genhit.ogg', 100, 1) - var/turf/space/T - for(T in orange(1, src)) - T.ChangeTurf(/turf/simulated/floor/plating) - else - atom_say("Something slams into the floor around [src] - luckily, it didn't get through!") - playsound(src.loc, 'sound/effects/bang.ogg', 20, 1) - if(ORION_TRAIL_MALFUNCTION) - playsound(src.loc, 'sound/effects/empulse.ogg', 20, 1) - visible_message("[src] malfunctions, randomizing in-game stats!") - var/oldfood = food - var/oldfuel = fuel - food = rand(10,80) / rand(1,2) - fuel = rand(10,60) / rand(1,2) - if(electronics) - sleep(10) - if(oldfuel > fuel && oldfood > food) - audible_message("[src] lets out a somehow reassuring chime.") - else if(oldfuel < fuel || oldfood < food) - audible_message("[src] lets out a somehow ominous chime.") - food = oldfood - fuel = oldfuel - playsound(src.loc, 'sound/machines/chime.ogg', 20, 1) - - else if(href_list["newgame"]) //Reset everything - newgame() - else if(href_list["menu"]) //back to the main menu - playing = 0 - event = null - gameover = 0 - food = 80 - fuel = 60 - settlers = list("Harry","Larry","Bob") - else if(href_list["slow"]) //slow down - food -= (alive+lings_aboard)*2 - fuel -= 5 - event = null - else if(href_list["pastblack"]) //slow down - food -= ((alive+lings_aboard)*2)*3 - fuel -= 15 - turns += 1 - event = null - else if(href_list["useengine"]) //use parts - engine = max(0, --engine) - event = null - else if(href_list["useelec"]) //use parts - electronics = max(0, --electronics) - event = null - else if(href_list["usehull"]) //use parts - hull = max(0, --hull) - event = null - else if(href_list["wait"]) //wait 3 days - food -= ((alive+lings_aboard)*2)*3 - event = null - else if(href_list["keepspeed"]) //keep speed - if(prob(75)) - event = "Breakdown" - event() - else - event = null - else if(href_list["blackhole"]) //keep speed past a black hole - if(prob(75)) - event = ORION_TRAIL_BLACKHOLE - event() - if(emagged) //has to be here because otherwise it doesn't work - playsound(src.loc, 'sound/effects/supermatter.ogg', 100, 1) - atom_say("A miniature black hole suddenly appears in front of [src], devouring [usr] alive!") - usr.Stun(10) //you can't run :^) - var/S = new /obj/singularity/academy(usr.loc) - emagged = 0 //immediately removes emagged status so people can't kill themselves by sprinting up and interacting - sleep(50) - atom_say("[S] winks out, just as suddenly as it appeared.") - qdel(S) - else - event = null - turns += 1 - else if(href_list["holedeath"]) - gameover = 1 - event = null - else if(href_list["eventclose"]) //end an event - event = null - - else if(href_list["killcrew"]) //shoot a crewmember - var/sheriff = remove_crewmember() //I shot the sheriff - playsound(loc,'sound/weapons/gunshots/gunshot.ogg', 100, 1) - - if(settlers.len == 0 || alive == 0) - atom_say("The last crewmember [sheriff], shot themselves, GAME OVER!") - if(emagged) - usr.death(0) - emagged = 0 - gameover = 1 - event = null - else if(emagged) - if(usr.name == sheriff) - atom_say("The crew of the ship chose to kill [usr.name]!") - usr.death(0) - - if(event == ORION_TRAIL_LING) //only ends the ORION_TRAIL_LING event, since you can do this action in multiple places - event = null - - //Spaceport specific interactions - //they get a header because most of them don't reset event (because it's a shop, you leave when you want to) - //they also call event() again, to regen the eventdata, which is kind of odd but necessary - else if(href_list["buycrew"]) //buy a crewmember - var/bought = add_crewmember() - last_spaceport_action = "You hired [bought] as a new crewmember." - fuel -= 10 - food -= 10 - event() - - else if(href_list["sellcrew"]) //sell a crewmember - var/sold = remove_crewmember() - last_spaceport_action = "You sold your crewmember, [sold]!" - fuel += 7 - food += 7 - event() - - else if(href_list["leave_spaceport"]) - event = null - spaceport_raided = 0 - spaceport_freebie = 0 - last_spaceport_action = "" - - else if(href_list["raid_spaceport"]) - var/success = min(15 * alive,100) //default crew (4) have a 60% chance - spaceport_raided = 1 - - var/FU = 0 - var/FO = 0 - if(prob(success)) - FU = rand(5,15) - FO = rand(5,15) - last_spaceport_action = "You successfully raided the spaceport! you gained [FU] Fuel and [FO] Food! (+[FU]FU,+[FO]FO)" - else - FU = rand(-5,-15) - FO = rand(-5,-15) - last_spaceport_action = "You failed to raid the spaceport! you lost [FU*-1] Fuel and [FO*-1] Food in your scramble to escape! ([FU]FU,[FO]FO)" - - //your chance of lose a crewmember is 1/2 your chance of success - //this makes higher % failures hurt more, don't get cocky space cowboy! - if(prob(success*5)) - var/lost_crew = remove_crewmember() - last_spaceport_action = "You failed to raid the spaceport! you lost [FU*-1] Fuel and [FO*-1] Food, AND [lost_crew] in your scramble to escape! ([FU]FI,[FO]FO,-Crew)" - if(emagged) - atom_say("WEEWOO WEEWOO, Spaceport Security en route!") - for(var/i, i<=3, i++) - var/mob/living/simple_animal/hostile/syndicate/ranged/orion/O = new/mob/living/simple_animal/hostile/syndicate/ranged/orion(get_turf(src)) - O.target = usr - - - fuel += FU - food += FO - event() - - else if(href_list["buyparts"]) - switch(text2num(href_list["buyparts"])) - if(1) //Engine Parts - engine++ - last_spaceport_action = "Bought Engine Parts" - if(2) //Hull Plates - hull++ - last_spaceport_action = "Bought Hull Plates" - if(3) //Spare Electronics - electronics++ - last_spaceport_action = "Bought Spare Electronics" - fuel -= 5 //they all cost 5 - event() - - else if(href_list["trade"]) - switch(text2num(href_list["trade"])) - if(1) //Fuel - fuel -= 5 - food += 5 - last_spaceport_action = "Traded Fuel for Food" - if(2) //Food - fuel += 5 - food -= 5 - last_spaceport_action = "Traded Food for Fuel" - event() - - add_fingerprint(usr) - updateUsrDialog() - busy = 0 - return - - -/obj/machinery/computer/arcade/orion_trail/proc/event() - eventdat = "

        [event]

        " - - switch(event) - if(ORION_TRAIL_RAIDERS) - eventdat += "Raiders have come aboard your ship!" - if(prob(50)) - var/sfood = rand(1,10) - var/sfuel = rand(1,10) - food -= sfood - fuel -= sfuel - eventdat += "
        They have stolen [sfood] Food and [sfuel] Fuel." - else if(prob(10)) - var/deadname = remove_crewmember() - eventdat += "
        [deadname] tried to fight back but was killed." - else - eventdat += "
        Fortunately you fended them off without any trouble." - eventdat += "

        Continue

        " - eventdat += "

        Close

        " - - if(ORION_TRAIL_FLUX) - eventdat += "This region of space is highly turbulent.
        If we go slowly we may avoid more damage, but if we keep our speed we won't waste supplies." - eventdat += "
        What will you do?" - eventdat += "

        Slow Down Continue

        " - eventdat += "

        Close

        " - - if(ORION_TRAIL_ILLNESS) - eventdat += "A deadly illness has been contracted!" - var/deadname = remove_crewmember() - eventdat += "
        [deadname] was killed by the disease." - eventdat += "

        Continue

        " - eventdat += "

        Close

        " - - if(ORION_TRAIL_BREAKDOWN) - eventdat += "Oh no! The engine has broken down!" - eventdat += "
        You can repair it with an engine part, or you can make repairs for 3 days." - if(engine >= 1) - eventdat += "

        Use PartWait

        " - else - eventdat += "

        Wait

        " - eventdat += "

        Close

        " - - if(ORION_TRAIL_MALFUNCTION) - eventdat += "The ship's systems are malfunctioning!" - eventdat += "
        You can replace the broken electronics with spares, or you can spend 3 days troubleshooting the AI." - if(electronics >= 1) - eventdat += "

        Use PartWait

        " - else - eventdat += "

        Wait

        " - eventdat += "

        Close

        " - - if(ORION_TRAIL_COLLISION) - eventdat += "Something hit us! Looks like there's some hull damage." - if(prob(25)) - var/sfood = rand(5,15) - var/sfuel = rand(5,15) - food -= sfood - fuel -= sfuel - eventdat += "
        [sfood] Food and [sfuel] Fuel was vented out into space." - if(prob(10)) - var/deadname = remove_crewmember() - eventdat += "
        [deadname] was killed by rapid depressurization." - eventdat += "
        You can repair the damage with hull plates, or you can spend the next 3 days welding scrap together." - if(hull >= 1) - eventdat += "

        Use PartWait

        " - else - eventdat += "

        Wait

        " - eventdat += "

        Close

        " - - if(ORION_TRAIL_BLACKHOLE) - eventdat += "You were swept away into the black hole." - eventdat += "

        Oh...

        " - eventdat += "

        Close

        " - settlers = list() - - if(ORION_TRAIL_LING) - eventdat += "Strange reports warn of changelings infiltrating crews on trips to Orion..." - if(settlers.len <= 2) - eventdat += "
        Your crew's chance of reaching Orion is so slim the changelings likely avoided your ship..." - eventdat += "

        Continue

        " - eventdat += "

        Close

        " - if(prob(10)) // "likely", I didn't say it was guaranteed! - lings_aboard = min(++lings_aboard,2) - else - if(lings_aboard) //less likely to stack lings - if(prob(20)) - lings_aboard = min(++lings_aboard,2) - else if(prob(70)) - lings_aboard = min(++lings_aboard,2) - - eventdat += "

        Kill a crewmember

        " - eventdat += "

        Risk it

        " - eventdat += "

        Close

        " - - if(ORION_TRAIL_LING_ATTACK) - if(lings_aboard <= 0) //shouldn't trigger, but hey. - eventdat += "Haha, fooled you, there are no changelings on board!" - eventdat += "
        (You should report this to a coder :S)" - else - var/ling1 = remove_crewmember() - var/ling2 = "" - if(lings_aboard >= 2) - ling2 = remove_crewmember() - - eventdat += "Oh no, some of your crew are Changelings!" - if(ling2) - eventdat += "
        [ling1] and [ling2]'s arms twist and contort into grotesque blades!" - else - eventdat += "
        [ling1]'s arm twists and contorts into a grotesque blade!" - - var/chance2attack = alive*20 - if(prob(chance2attack)) - var/chancetokill = 30*lings_aboard-(5*alive) //eg: 30*2-(10) = 50%, 2 lings, 2 crew is 50% chance - if(prob(chancetokill)) - var/deadguy = remove_crewmember() - eventdat += "
        The Changeling[ling2 ? "s":""] run[ling2 ? "":"s"] up to [deadguy] and capitulates them!" - else - eventdat += "
        You valiantly fight off the Changeling[ling2 ? "s":""]!" - eventdat += "
        You cut the Changeling[ling2 ? "s":""] up into meat... Eww" - if(ling2) - food += 30 - lings_aboard = max(0,lings_aboard-2) - else - food += 15 - lings_aboard = max(0,--lings_aboard) - else - eventdat += "
        The Changeling[ling2 ? "s":""] run[ling2 ? "":"s"] away, What wimps!" - if(ling2) - lings_aboard = max(0,lings_aboard-2) - else - lings_aboard = max(0,--lings_aboard) - - eventdat += "

        Continue

        " - eventdat += "

        Close

        " - - - if(ORION_TRAIL_SPACEPORT) - if(spaceport_raided) - eventdat += "The Spaceport is on high alert! They wont let you dock since you tried to attack them!" - if(last_spaceport_action) - eventdat += "
        Last Spaceport Action: [last_spaceport_action]" - eventdat += "

        Depart Spaceport

        " - eventdat += "

        Close

        " - else - eventdat += "You pull the ship up to dock at a nearby Spaceport, lucky find!" - eventdat += "
        This Spaceport is home to travellers who failed to reach Orion, but managed to find a different home..." - eventdat += "
        Trading terms: FU = Fuel, FO = Food" - if(last_spaceport_action) - eventdat += "
        Last Spaceport Action: [last_spaceport_action]" - eventdat += "

        Crew:

        " - eventdat += english_list(settlers) - eventdat += "
        Food: [food] | Fuel: [fuel]" - eventdat += "
        Engine Parts: [engine] | Hull Panels: [hull] | Electronics: [electronics]" - - - //If your crew is pathetic you can get freebies (provided you haven't already gotten one from this port) - if(!spaceport_freebie && (fuel < 20 || food < 20)) - spaceport_freebie++ - var/FU = 10 - var/FO = 10 - var/freecrew = 0 - if(prob(30)) - FU = 25 - FO = 25 - - if(prob(10)) - add_crewmember() - freecrew++ - - eventdat += "
        The traders of the spaceport take pitty on you, and give you some food and fuel (+[FU]FU,+[FO]FO)" - if(freecrew) - eventdat += "
        You also gain a new crewmember!" - - fuel += FU - food += FO - - //CREW INTERACTIONS - eventdat += "

        Crew Management:

        " - - //Buy crew - if(food >= 10 && fuel >= 10) - eventdat += "

        Hire a new Crewmember (-10FU,-10FO)

        " - else - eventdat += "

        Cant afford a new Crewmember

        " - - //Sell crew - if(settlers.len > 1) - eventdat += "

        Sell crew for Fuel and Food (+15FU,+15FO)

        " - else - eventdat += "

        Cant afford to sell a Crewmember

        " - - //BUY/SELL STUFF - eventdat += "

        Spare Parts:

        " - - //Engine parts - if(fuel > 5) - eventdat += "

        Buy Engine Parts (-5FU)

        " - else - eventdat += "

        Cant afford to buy Engine Parts" - - //Hull plates - if(fuel > 5) - eventdat += "

        Buy Hull Plates (-5FU)

        " - else - eventdat += "

        Cant afford to buy Hull Plates" - - //Electronics - if(fuel > 5) - eventdat += "

        Buy Spare Electronics (-5FU)

        " - else - eventdat += "

        Cant afford to buy Spare Electronics" - - //Trade - if(fuel > 5) - eventdat += "

        Trade Fuel for Food (-5FU,+5FO)

        " - else - eventdat += "

        Cant afford to Trade Fuel for Food 5) - eventdat += "

        Trade Food for Fuel (+5FU,-5FO)

        " - else - eventdat += "

        Cant afford to Trade Food for Fuel= 1) //need to make sure we even have anyone to remove - removed = pick(safe2remove) - - if(removed) - if(lings_aboard && prob(40*lings_aboard)) //if there are 2 lings you're twice as likely to get one, obviously - lings_aboard = max(0,--lings_aboard) - settlers -= removed - alive-- - return removed - - -/obj/machinery/computer/arcade/orion_trail/proc/win() - playing = 0 - turns = 1 - atom_say("Congratulations, you made it to Orion!") - if(emagged) - new /obj/item/orion_ship(get_turf(src)) - message_admins("[key_name_admin(usr)] made it to Orion on an emagged machine and got an explosive toy ship.") - log_game("[key_name(usr)] made it to Orion on an emagged machine and got an explosive toy ship.") - else - var/score = alive + round(food/2) + round(fuel/5) + engine + hull + electronics - lings_aboard - prizevend(score) - emagged = 0 - name = "The Orion Trail" - desc = "Learn how our ancestors got to Orion, and have fun in the process!" - -/obj/machinery/computer/arcade/orion_trail/emag_act(mob/user) - if(!emagged) - to_chat(user, "You override the cheat code menu and skip to Cheat #[rand(1, 50)]: Realism Mode.") - name = "The Orion Trail: Realism Edition" - desc = "Learn how our ancestors got to Orion, and try not to die in the process!" - newgame() - emagged = 1 - -/mob/living/simple_animal/hostile/syndicate/ranged/orion - name = "spaceport security" - desc = "Premier corporate security forces for all spaceports found along the Orion Trail." - faction = list("orion") - loot = list() - del_on_death = TRUE - -/obj/item/orion_ship - name = "model settler ship" - desc = "A model spaceship, it looks like those used back in the day when travelling to Orion! It even has a miniature FX-293 reactor, which was renowned for its instability and tendency to explode..." - icon = 'icons/obj/toy.dmi' - icon_state = "ship" - w_class = WEIGHT_CLASS_SMALL - var/active = 0 //if the ship is on - -/obj/item/orion_ship/examine(mob/user) - . = ..() - if(in_range(user, src)) - if(!active) - . += "There's a little switch on the bottom. It's flipped down." - else - . += "There's a little switch on the bottom. It's flipped up." - -/obj/item/orion_ship/attack_self(mob/user) //Minibomb-level explosion. Should probably be more because of how hard it is to survive the machine! Also, just over a 5-second fuse - if(active) - return - - message_admins("[key_name_admin(usr)] primed an explosive Orion ship for detonation.") - log_game("[key_name(usr)] primed an explosive Orion ship for detonation.") - - to_chat(user, "You flip the switch on the underside of [src].") - active = 1 - visible_message("[src] softly beeps and whirs to life!") - playsound(src.loc, 'sound/machines/defib_saftyon.ogg', 25, 1) - atom_say("This is ship ID #[rand(1,1000)] to Orion Port Authority. We're coming in for landing, over.") - sleep(20) - visible_message("[src] begins to vibrate...") - atom_say("Uh, Port? Having some issues with our reactor, could you check it out? Over.") - sleep(30) - atom_say("Oh, God! Code Eight! CODE EIGHT! IT'S GONNA BL-") - playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 25, 1) - sleep(3.6) - visible_message("[src] explodes!") - explosion(src.loc, 1,2,4, flame_range = 3) - qdel(src) - - -#undef ORION_TRAIL_WINTURN -#undef ORION_TRAIL_RAIDERS -#undef ORION_TRAIL_FLUX -#undef ORION_TRAIL_ILLNESS -#undef ORION_TRAIL_BREAKDOWN -#undef ORION_TRAIL_LING -#undef ORION_TRAIL_LING_ATTACK -#undef ORION_TRAIL_MALFUNCTION -#undef ORION_TRAIL_COLLISION -#undef ORION_TRAIL_SPACEPORT -#undef ORION_TRAIL_BLACKHOLE +/obj/machinery/computer/arcade + name = "random arcade" + desc = "random arcade machine" + icon = 'icons/obj/computer.dmi' + icon_state = "arcade" + icon_keyboard = null + icon_screen = "invaders" + light_color = "#00FF00" + var/prize = /obj/item/stack/tickets + +/obj/machinery/computer/arcade/proc/Reset() + return + +/obj/machinery/computer/arcade/New() + ..() + if(!circuit) + var/choice = pick(subtypesof(/obj/machinery/computer/arcade)) + new choice(loc) + qdel(src) + return + Reset() + + +/obj/machinery/computer/arcade/proc/prizevend(var/score) + if(!contents.len) + var/prize_amount + if(score) + prize_amount = score + else + prize_amount = rand(1, 10) + new prize(get_turf(src), prize_amount) + else + var/atom/movable/prize = pick(contents) + prize.loc = get_turf(src) + +/obj/machinery/computer/arcade/emp_act(severity) + ..(severity) + if(stat & (NOPOWER|BROKEN)) + return + var/num_of_prizes = 0 + switch(severity) + if(1) + num_of_prizes = rand(1,4) + if(2) + num_of_prizes = rand(0,2) + for(var/i = num_of_prizes; i > 0; i--) + prizevend() + explosion(get_turf(src), -1, 0, 1+num_of_prizes, flame_range = 1+num_of_prizes) + + +/obj/machinery/computer/arcade/battle + name = "arcade machine" + desc = "Does not support Pinball." + icon = 'icons/obj/computer.dmi' + icon_state = "arcade" + circuit = /obj/item/circuitboard/arcade/battle + var/enemy_name = "Space Villian" + var/temp = "Winners Don't Use Spacedrugs" //Temporary message, for attack messages, etc + var/player_hp = 30 //Player health/attack points + var/player_mp = 10 + var/enemy_hp = 45 //Enemy health/attack points + var/enemy_mp = 20 + var/gameover = 0 + var/blocked = 0 //Player cannot attack/heal while set + var/turtle = 0 + +/obj/machinery/computer/arcade/battle/Reset() + var/name_action + var/name_part1 + var/name_part2 + + name_action = pick("Defeat ", "Annihilate ", "Save ", "Strike ", "Stop ", "Destroy ", "Robust ", "Romance ", "Pwn ", "Own ", "Ban ") + + name_part1 = pick("the Automatic ", "Farmer ", "Lord ", "Professor ", "the Cuban ", "the Evil ", "the Dread King ", "the Space ", "Lord ", "the Great ", "Duke ", "General ") + name_part2 = pick("Melonoid", "Murdertron", "Sorcerer", "Ruin", "Jeff", "Ectoplasm", "Crushulon", "Uhangoid", "Vhakoid", "Peteoid", "slime", "Griefer", "ERPer", "Lizard Man", "Unicorn", "Bloopers") + + enemy_name = replacetext((name_part1 + name_part2), "the ", "") + name = (name_action + name_part1 + name_part2) + +/obj/machinery/computer/arcade/battle/attack_hand(mob/user as mob) + if(..()) + return + user.set_machine(src) + var/dat = "Close" + dat += "

        [enemy_name]

        " + + dat += "

        [temp]

        " + dat += "
        Health: [player_hp] | Magic: [player_mp] | Enemy Health: [enemy_hp]
        " + + if(gameover) + dat += "
        New Game" + else + dat += "
        Attack | " + dat += "Heal | " + dat += "Recharge Power" + + dat += "
        " + + //user << browse(dat, "window=arcade") + //onclose(user, "arcade") + var/datum/browser/popup = new(user, "arcade", "Space Villian 2000") + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + return + +/obj/machinery/computer/arcade/battle/Topic(href, href_list) + if(..()) + return + + if(!blocked && !gameover) + if(href_list["attack"]) + blocked = 1 + var/attackamt = rand(2,6) + temp = "You attack for [attackamt] damage!" + playsound(src.loc, 'sound/arcade/hit.ogg', 20, 1, extrarange = -6, falloff = 10) + updateUsrDialog() + if(turtle > 0) + turtle-- + + sleep(10) + enemy_hp -= attackamt + arcade_action() + + else if(href_list["heal"]) + blocked = 1 + var/pointamt = rand(1,3) + var/healamt = rand(6,8) + temp = "You use [pointamt] magic to heal for [healamt] damage!" + playsound(src.loc, 'sound/arcade/heal.ogg', 20, 1, extrarange = -6, falloff = 10) + updateUsrDialog() + turtle++ + + sleep(10) + player_mp -= pointamt + player_hp += healamt + blocked = 1 + updateUsrDialog() + arcade_action() + + else if(href_list["charge"]) + blocked = 1 + var/chargeamt = rand(4,7) + temp = "You regain [chargeamt] points" + playsound(src.loc, 'sound/arcade/mana.ogg', 20, 1, extrarange = -6, falloff = 10) + player_mp += chargeamt + if(turtle > 0) + turtle-- + + updateUsrDialog() + sleep(10) + arcade_action() + + if(href_list["close"]) + usr.unset_machine() + usr << browse(null, "window=arcade") + + else if(href_list["newgame"]) //Reset everything + temp = "New Round" + player_hp = 30 + player_mp = 10 + enemy_hp = 45 + enemy_mp = 20 + gameover = 0 + turtle = 0 + + if(emagged) + Reset() + emagged = 0 + + add_fingerprint(usr) + updateUsrDialog() + return + +/obj/machinery/computer/arcade/battle/proc/arcade_action() + if((enemy_mp <= 0) || (enemy_hp <= 0)) + if(!gameover) + gameover = 1 + temp = "[enemy_name] has fallen! Rejoice!" + playsound(src.loc, 'sound/arcade/win.ogg', 20, 1, extrarange = -6, falloff = 10) + + if(emagged) + feedback_inc("arcade_win_emagged") + new /obj/effect/spawner/newbomb/timer/syndicate(get_turf(src)) + new /obj/item/clothing/head/collectable/petehat(get_turf(src)) + message_admins("[key_name_admin(usr)] has outbombed Cuban Pete and been awarded a bomb.") + log_game("[key_name(usr)] has outbombed Cuban Pete and been awarded a bomb.") + Reset() + emagged = 0 + else + feedback_inc("arcade_win_normal") + var/score = player_hp + player_mp + 5 + prizevend(score) + + else if(emagged && (turtle >= 4)) + var/boomamt = rand(5,10) + temp = "[enemy_name] throws a bomb, exploding you for [boomamt] damage!" + playsound(src.loc, 'sound/arcade/boom.ogg', 20, 1, extrarange = -6, falloff = 10) + player_hp -= boomamt + + else if((enemy_mp <= 5) && (prob(70))) + var/stealamt = rand(2,3) + temp = "[enemy_name] steals [stealamt] of your power!" + playsound(src.loc, 'sound/arcade/steal.ogg', 20, 1, extrarange = -6, falloff = 10) + player_mp -= stealamt + updateUsrDialog() + + if(player_mp <= 0) + gameover = 1 + sleep(10) + temp = "You have been drained! GAME OVER" + playsound(src.loc, 'sound/arcade/lose.ogg', 20, 1, extrarange = -6, falloff = 10) + if(emagged) + feedback_inc("arcade_loss_mana_emagged") + usr.gib() + else + feedback_inc("arcade_loss_mana_normal") + + else if((enemy_hp <= 10) && (enemy_mp > 4)) + temp = "[enemy_name] heals for 4 health!" + playsound(src.loc, 'sound/arcade/heal.ogg', 20, 1, extrarange = -6, falloff = 10) + enemy_hp += 4 + enemy_mp -= 4 + + else + var/attackamt = rand(3,6) + temp = "[enemy_name] attacks for [attackamt] damage!" + playsound(src.loc, 'sound/arcade/hit.ogg', 20, 1, extrarange = -6, falloff = 10) + player_hp -= attackamt + + if((player_mp <= 0) || (player_hp <= 0)) + gameover = 1 + temp = "You have been crushed! GAME OVER" + playsound(src.loc, 'sound/arcade/lose.ogg', 20, 1, extrarange = -6, falloff = 10) + if(emagged) + feedback_inc("arcade_loss_hp_emagged") + usr.gib() + else + feedback_inc("arcade_loss_hp_normal") + + blocked = 0 + return + + +/obj/machinery/computer/arcade/battle/emag_act(user as mob) + if(!emagged) + temp = "If you die in the game, you die for real!" + player_hp = 30 + player_mp = 10 + enemy_hp = 45 + enemy_mp = 20 + gameover = 0 + blocked = 0 + + emagged = 1 + + enemy_name = "Cuban Pete" + name = "Outbomb Cuban Pete" + + updateUsrDialog() + +// *** THE ORION TRAIL ** // + +#define ORION_TRAIL_WINTURN 9 + +//Orion Trail Events +#define ORION_TRAIL_RAIDERS "Raiders" +#define ORION_TRAIL_FLUX "Interstellar Flux" +#define ORION_TRAIL_ILLNESS "Illness" +#define ORION_TRAIL_BREAKDOWN "Breakdown" +#define ORION_TRAIL_LING "Changelings?" +#define ORION_TRAIL_LING_ATTACK "Changeling Ambush" +#define ORION_TRAIL_MALFUNCTION "Malfunction" +#define ORION_TRAIL_COLLISION "Collision" +#define ORION_TRAIL_SPACEPORT "Spaceport" +#define ORION_TRAIL_BLACKHOLE "BlackHole" + + +/obj/machinery/computer/arcade/orion_trail + name = "The Orion Trail" + desc = "Learn how our ancestors got to Orion, and have fun in the process!" + icon_state = "arcade" + circuit = /obj/item/circuitboard/arcade/orion_trail + var/busy = 0 //prevent clickspam that allowed people to ~speedrun~ the game. + var/engine = 0 + var/hull = 0 + var/electronics = 0 + var/food = 80 + var/fuel = 60 + var/turns = 4 + var/playing = 0 + var/gameover = 0 + var/alive = 4 + var/eventdat = null + var/event = null + var/list/settlers = list("Harry","Larry","Bob") + var/list/events = list(ORION_TRAIL_RAIDERS = 3, + ORION_TRAIL_FLUX = 1, + ORION_TRAIL_ILLNESS = 3, + ORION_TRAIL_BREAKDOWN = 2, + ORION_TRAIL_LING = 3, + ORION_TRAIL_MALFUNCTION = 2, + ORION_TRAIL_COLLISION = 1, + ORION_TRAIL_SPACEPORT = 2 + ) + var/list/stops = list() + var/list/stopblurbs = list() + var/lings_aboard = 0 + var/spaceport_raided = 0 + var/spaceport_freebie = 0 + var/last_spaceport_action = "" + +/obj/machinery/computer/arcade/orion_trail/Reset() + // Sets up the main trail + stops = list("Pluto","Asteroid Belt","Proxima Centauri","Dead Space","Rigel Prime","Tau Ceti Beta","Black Hole","Space Outpost Beta-9","Orion Prime") + stopblurbs = list( + "Pluto, long since occupied with long-range sensors and scanners, stands ready to, and indeed continues to probe the far reaches of the galaxy.", + "At the edge of the Sol system lies a treacherous asteroid belt. Many have been crushed by stray asteroids and misguided judgement.", + "The nearest star system to Sol, in ages past it stood as a reminder of the boundaries of sub-light travel, now a low-population sanctuary for adventurers and traders.", + "This region of space is particularly devoid of matter. Such low-density pockets are known to exist, but the vastness of it is astounding.", + "Rigel Prime, the center of the Rigel system, burns hot, basking its planetary bodies in warmth and radiation.", + "Tau Ceti Beta has recently become a waypoint for colonists headed towards Orion. There are many ships and makeshift stations in the vicinity.", + "Sensors indicate that a black hole's gravitational field is affecting the region of space we were headed through. We could stay of course, but risk of being overcome by its gravity, or we could change course to go around, which will take longer.", + "You have come into range of the first man-made structure in this region of space. It has been constructed not by travellers from Sol, but by colonists from Orion. It stands as a monument to the colonists' success.", + "You have made it to Orion! Congratulations! Your crew is one of the few to start a new foothold for mankind!" + ) + +/obj/machinery/computer/arcade/orion_trail/proc/newgame() + // Set names of settlers in crew + settlers = list() + for(var/i = 1; i <= 3; i++) + add_crewmember() + add_crewmember("[usr]") + // Re-set items to defaults + engine = 1 + hull = 1 + electronics = 1 + food = 80 + fuel = 60 + alive = 4 + turns = 1 + event = null + playing = 1 + gameover = 0 + lings_aboard = 0 + + //spaceport junk + spaceport_raided = 0 + spaceport_freebie = 0 + last_spaceport_action = "" + +/obj/machinery/computer/arcade/orion_trail/attack_hand(mob/user) + if(..()) + return + if(fuel <= 0 || food <=0 || settlers.len == 0) + gameover = 1 + event = null + user.set_machine(src) + var/dat = "" + if(gameover) + dat = "

        Game Over

        " + dat += "Like many before you, your crew never made it to Orion, lost to space...
        Forever." + if(settlers.len == 0) + dat += "
        Your entire crew died, your ship joins the fleet of ghost-ships littering the galaxy." + else + if(food <= 0) + dat += "
        You ran out of food and starved." + if(emagged) + user.set_nutrition(0) //yeah you pretty hongry + to_chat(user, "Your body instantly contracts to that of one who has not eaten in months. Agonizing cramps seize you as you fall to the floor.") + if(fuel <= 0) + dat += "
        You ran out of fuel, and drift, slowly, into a star." + if(emagged) + var/mob/living/M = user + M.adjust_fire_stacks(5) + M.IgniteMob() //flew into a star, so you're on fire + to_chat(user, "You feel an immense wave of heat emanate from the arcade machine. Your skin bursts into flames.") + dat += "

        OK...

        " + + if(emagged) + to_chat(user, "You're never going to make it to Orion...") + user.death() + emagged = 0 //removes the emagged status after you lose + playing = 0 //also a new game + name = "The Orion Trail" + desc = "Learn how our ancestors got to Orion, and have fun in the process!" + + else if(event) + dat = eventdat + else if(playing) + var/title = stops[turns] + var/subtext = stopblurbs[turns] + dat = "

        [title]

        " + dat += "[subtext]" + dat += "

        Crew:

        " + dat += english_list(settlers) + dat += "
        Food: [food] | Fuel: [fuel]" + dat += "
        Engine Parts: [engine] | Hull Panels: [hull] | Electronics: [electronics]" + if(turns == 7) + dat += "

        Go Around Continue

        " + else + dat += "

        Continue

        " + dat += "

        Kill a crewmember

        " + dat += "

        Close

        " + else + dat = "

        The Orion Trail

        " + dat += "

        Experience the journey of your ancestors!



        " + dat += "
        New Game
        " + dat += "

        Close

        " + var/datum/browser/popup = new(user, "arcade", "The Orion Trail",400,700) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + return + +/obj/machinery/computer/arcade/orion_trail/Topic(href, href_list) + . = ..() + if(.) + return + if(href_list["close"]) + usr.unset_machine() + usr << browse(null, "window=arcade") + + if(busy) + return + busy = 1 + + if(href_list["continue"]) //Continue your travels + if(turns >= ORION_TRAIL_WINTURN) + win() + else + food -= (alive+lings_aboard)*2 + fuel -= 5 + if(turns == 2 && prob(30)) + event = ORION_TRAIL_COLLISION + event() + else if(prob(75)) + event = pickweight(events) + if(lings_aboard) + if(event == ORION_TRAIL_LING || prob(55)) + event = ORION_TRAIL_LING_ATTACK + event() + turns += 1 + if(emagged) + var/mob/living/carbon/M = usr //for some vars + switch(event) + if(ORION_TRAIL_RAIDERS) + if(prob(50)) + to_chat(usr, "You hear battle shouts. The tramping of boots on cold metal. Screams of agony. The rush of venting air. Are you going insane?") + M.AdjustHallucinate(30) + else + to_chat(usr, "Something strikes you from behind! It hurts like hell and feel like a blunt weapon, but nothing is there...") + M.take_organ_damage(30) + playsound(loc, 'sound/weapons/genhit2.ogg', 100, 1) + if(ORION_TRAIL_ILLNESS) + var/severity = rand(1,3) //pray to RNGesus. PRAY, PIGS + if(severity == 1) + to_chat(M, "You suddenly feel slightly nauseous.")//got off lucky + + if(severity == 2) + to_chat(usr, "You suddenly feel extremely nauseous and hunch over until it passes.") + M.Stun(3) + if(severity >= 3) //you didn't pray hard enough + to_chat(M, "An overpowering wave of nausea consumes over you. You hunch over, your stomach's contents preparing for a spectacular exit.") + M.Stun(5) + sleep(30) + atom_say("[M] violently throws up!") + playsound(loc, 'sound/effects/splat.ogg', 50, 1) + M.adjust_nutrition(-50) //lose a lot of food + var/turf/location = usr.loc + if(istype(location, /turf/simulated)) + location.add_vomit_floor(TRUE) + if(ORION_TRAIL_FLUX) + if(prob(75)) + M.Weaken(3) + atom_say("A sudden gust of powerful wind slams [M] into the floor!") + M.take_organ_damage(25) + playsound(src.loc, 'sound/weapons/genhit.ogg', 100, 1) + else + to_chat(M, "A violent gale blows past you, and you barely manage to stay standing!") + if(ORION_TRAIL_COLLISION) //by far the most damaging event + if(prob(90)) + playsound(src.loc, 'sound/effects/bang.ogg', 100, 1) + var/turf/simulated/floor/F + for(F in orange(1, src)) + F.ChangeTurf(F.baseturf) + atom_say("Something slams into the floor around [src], exposing it to space!") + if(hull) + sleep(10) + atom_say("A new floor suddenly appears around [src]. What the hell?") + playsound(src.loc, 'sound/weapons/genhit.ogg', 100, 1) + var/turf/space/T + for(T in orange(1, src)) + T.ChangeTurf(/turf/simulated/floor/plating) + else + atom_say("Something slams into the floor around [src] - luckily, it didn't get through!") + playsound(src.loc, 'sound/effects/bang.ogg', 20, 1) + if(ORION_TRAIL_MALFUNCTION) + playsound(src.loc, 'sound/effects/empulse.ogg', 20, 1) + visible_message("[src] malfunctions, randomizing in-game stats!") + var/oldfood = food + var/oldfuel = fuel + food = rand(10,80) / rand(1,2) + fuel = rand(10,60) / rand(1,2) + if(electronics) + sleep(10) + if(oldfuel > fuel && oldfood > food) + audible_message("[src] lets out a somehow reassuring chime.") + else if(oldfuel < fuel || oldfood < food) + audible_message("[src] lets out a somehow ominous chime.") + food = oldfood + fuel = oldfuel + playsound(src.loc, 'sound/machines/chime.ogg', 20, 1) + + else if(href_list["newgame"]) //Reset everything + newgame() + else if(href_list["menu"]) //back to the main menu + playing = 0 + event = null + gameover = 0 + food = 80 + fuel = 60 + settlers = list("Harry","Larry","Bob") + else if(href_list["slow"]) //slow down + food -= (alive+lings_aboard)*2 + fuel -= 5 + event = null + else if(href_list["pastblack"]) //slow down + food -= ((alive+lings_aboard)*2)*3 + fuel -= 15 + turns += 1 + event = null + else if(href_list["useengine"]) //use parts + engine = max(0, --engine) + event = null + else if(href_list["useelec"]) //use parts + electronics = max(0, --electronics) + event = null + else if(href_list["usehull"]) //use parts + hull = max(0, --hull) + event = null + else if(href_list["wait"]) //wait 3 days + food -= ((alive+lings_aboard)*2)*3 + event = null + else if(href_list["keepspeed"]) //keep speed + if(prob(75)) + event = "Breakdown" + event() + else + event = null + else if(href_list["blackhole"]) //keep speed past a black hole + if(prob(75)) + event = ORION_TRAIL_BLACKHOLE + event() + if(emagged) //has to be here because otherwise it doesn't work + playsound(src.loc, 'sound/effects/supermatter.ogg', 100, 1) + atom_say("A miniature black hole suddenly appears in front of [src], devouring [usr] alive!") + usr.Stun(10) //you can't run :^) + var/S = new /obj/singularity/academy(usr.loc) + emagged = 0 //immediately removes emagged status so people can't kill themselves by sprinting up and interacting + sleep(50) + atom_say("[S] winks out, just as suddenly as it appeared.") + qdel(S) + else + event = null + turns += 1 + else if(href_list["holedeath"]) + gameover = 1 + event = null + else if(href_list["eventclose"]) //end an event + event = null + + else if(href_list["killcrew"]) //shoot a crewmember + var/sheriff = remove_crewmember() //I shot the sheriff + playsound(loc,'sound/weapons/gunshots/gunshot.ogg', 100, 1) + + if(settlers.len == 0 || alive == 0) + atom_say("The last crewmember [sheriff], shot themselves, GAME OVER!") + if(emagged) + usr.death(0) + emagged = 0 + gameover = 1 + event = null + else if(emagged) + if(usr.name == sheriff) + atom_say("The crew of the ship chose to kill [usr.name]!") + usr.death(0) + + if(event == ORION_TRAIL_LING) //only ends the ORION_TRAIL_LING event, since you can do this action in multiple places + event = null + + //Spaceport specific interactions + //they get a header because most of them don't reset event (because it's a shop, you leave when you want to) + //they also call event() again, to regen the eventdata, which is kind of odd but necessary + else if(href_list["buycrew"]) //buy a crewmember + var/bought = add_crewmember() + last_spaceport_action = "You hired [bought] as a new crewmember." + fuel -= 10 + food -= 10 + event() + + else if(href_list["sellcrew"]) //sell a crewmember + var/sold = remove_crewmember() + last_spaceport_action = "You sold your crewmember, [sold]!" + fuel += 7 + food += 7 + event() + + else if(href_list["leave_spaceport"]) + event = null + spaceport_raided = 0 + spaceport_freebie = 0 + last_spaceport_action = "" + + else if(href_list["raid_spaceport"]) + var/success = min(15 * alive,100) //default crew (4) have a 60% chance + spaceport_raided = 1 + + var/FU = 0 + var/FO = 0 + if(prob(success)) + FU = rand(5,15) + FO = rand(5,15) + last_spaceport_action = "You successfully raided the spaceport! you gained [FU] Fuel and [FO] Food! (+[FU]FU,+[FO]FO)" + else + FU = rand(-5,-15) + FO = rand(-5,-15) + last_spaceport_action = "You failed to raid the spaceport! you lost [FU*-1] Fuel and [FO*-1] Food in your scramble to escape! ([FU]FU,[FO]FO)" + + //your chance of lose a crewmember is 1/2 your chance of success + //this makes higher % failures hurt more, don't get cocky space cowboy! + if(prob(success*5)) + var/lost_crew = remove_crewmember() + last_spaceport_action = "You failed to raid the spaceport! you lost [FU*-1] Fuel and [FO*-1] Food, AND [lost_crew] in your scramble to escape! ([FU]FI,[FO]FO,-Crew)" + if(emagged) + atom_say("WEEWOO WEEWOO, Spaceport Security en route!") + for(var/i, i<=3, i++) + var/mob/living/simple_animal/hostile/syndicate/ranged/orion/O = new/mob/living/simple_animal/hostile/syndicate/ranged/orion(get_turf(src)) + O.target = usr + + + fuel += FU + food += FO + event() + + else if(href_list["buyparts"]) + switch(text2num(href_list["buyparts"])) + if(1) //Engine Parts + engine++ + last_spaceport_action = "Bought Engine Parts" + if(2) //Hull Plates + hull++ + last_spaceport_action = "Bought Hull Plates" + if(3) //Spare Electronics + electronics++ + last_spaceport_action = "Bought Spare Electronics" + fuel -= 5 //they all cost 5 + event() + + else if(href_list["trade"]) + switch(text2num(href_list["trade"])) + if(1) //Fuel + fuel -= 5 + food += 5 + last_spaceport_action = "Traded Fuel for Food" + if(2) //Food + fuel += 5 + food -= 5 + last_spaceport_action = "Traded Food for Fuel" + event() + + add_fingerprint(usr) + updateUsrDialog() + busy = 0 + return + + +/obj/machinery/computer/arcade/orion_trail/proc/event() + eventdat = "

        [event]

        " + + switch(event) + if(ORION_TRAIL_RAIDERS) + eventdat += "Raiders have come aboard your ship!" + if(prob(50)) + var/sfood = rand(1,10) + var/sfuel = rand(1,10) + food -= sfood + fuel -= sfuel + eventdat += "
        They have stolen [sfood] Food and [sfuel] Fuel." + else if(prob(10)) + var/deadname = remove_crewmember() + eventdat += "
        [deadname] tried to fight back but was killed." + else + eventdat += "
        Fortunately you fended them off without any trouble." + eventdat += "

        Continue

        " + eventdat += "

        Close

        " + + if(ORION_TRAIL_FLUX) + eventdat += "This region of space is highly turbulent.
        If we go slowly we may avoid more damage, but if we keep our speed we won't waste supplies." + eventdat += "
        What will you do?" + eventdat += "

        Slow Down Continue

        " + eventdat += "

        Close

        " + + if(ORION_TRAIL_ILLNESS) + eventdat += "A deadly illness has been contracted!" + var/deadname = remove_crewmember() + eventdat += "
        [deadname] was killed by the disease." + eventdat += "

        Continue

        " + eventdat += "

        Close

        " + + if(ORION_TRAIL_BREAKDOWN) + eventdat += "Oh no! The engine has broken down!" + eventdat += "
        You can repair it with an engine part, or you can make repairs for 3 days." + if(engine >= 1) + eventdat += "

        Use PartWait

        " + else + eventdat += "

        Wait

        " + eventdat += "

        Close

        " + + if(ORION_TRAIL_MALFUNCTION) + eventdat += "The ship's systems are malfunctioning!" + eventdat += "
        You can replace the broken electronics with spares, or you can spend 3 days troubleshooting the AI." + if(electronics >= 1) + eventdat += "

        Use PartWait

        " + else + eventdat += "

        Wait

        " + eventdat += "

        Close

        " + + if(ORION_TRAIL_COLLISION) + eventdat += "Something hit us! Looks like there's some hull damage." + if(prob(25)) + var/sfood = rand(5,15) + var/sfuel = rand(5,15) + food -= sfood + fuel -= sfuel + eventdat += "
        [sfood] Food and [sfuel] Fuel was vented out into space." + if(prob(10)) + var/deadname = remove_crewmember() + eventdat += "
        [deadname] was killed by rapid depressurization." + eventdat += "
        You can repair the damage with hull plates, or you can spend the next 3 days welding scrap together." + if(hull >= 1) + eventdat += "

        Use PartWait

        " + else + eventdat += "

        Wait

        " + eventdat += "

        Close

        " + + if(ORION_TRAIL_BLACKHOLE) + eventdat += "You were swept away into the black hole." + eventdat += "

        Oh...

        " + eventdat += "

        Close

        " + settlers = list() + + if(ORION_TRAIL_LING) + eventdat += "Strange reports warn of changelings infiltrating crews on trips to Orion..." + if(settlers.len <= 2) + eventdat += "
        Your crew's chance of reaching Orion is so slim the changelings likely avoided your ship..." + eventdat += "

        Continue

        " + eventdat += "

        Close

        " + if(prob(10)) // "likely", I didn't say it was guaranteed! + lings_aboard = min(++lings_aboard,2) + else + if(lings_aboard) //less likely to stack lings + if(prob(20)) + lings_aboard = min(++lings_aboard,2) + else if(prob(70)) + lings_aboard = min(++lings_aboard,2) + + eventdat += "

        Kill a crewmember

        " + eventdat += "

        Risk it

        " + eventdat += "

        Close

        " + + if(ORION_TRAIL_LING_ATTACK) + if(lings_aboard <= 0) //shouldn't trigger, but hey. + eventdat += "Haha, fooled you, there are no changelings on board!" + eventdat += "
        (You should report this to a coder :S)" + else + var/ling1 = remove_crewmember() + var/ling2 = "" + if(lings_aboard >= 2) + ling2 = remove_crewmember() + + eventdat += "Oh no, some of your crew are Changelings!" + if(ling2) + eventdat += "
        [ling1] and [ling2]'s arms twist and contort into grotesque blades!" + else + eventdat += "
        [ling1]'s arm twists and contorts into a grotesque blade!" + + var/chance2attack = alive*20 + if(prob(chance2attack)) + var/chancetokill = 30*lings_aboard-(5*alive) //eg: 30*2-(10) = 50%, 2 lings, 2 crew is 50% chance + if(prob(chancetokill)) + var/deadguy = remove_crewmember() + eventdat += "
        The Changeling[ling2 ? "s":""] run[ling2 ? "":"s"] up to [deadguy] and capitulates them!" + else + eventdat += "
        You valiantly fight off the Changeling[ling2 ? "s":""]!" + eventdat += "
        You cut the Changeling[ling2 ? "s":""] up into meat... Eww" + if(ling2) + food += 30 + lings_aboard = max(0,lings_aboard-2) + else + food += 15 + lings_aboard = max(0,--lings_aboard) + else + eventdat += "
        The Changeling[ling2 ? "s":""] run[ling2 ? "":"s"] away, What wimps!" + if(ling2) + lings_aboard = max(0,lings_aboard-2) + else + lings_aboard = max(0,--lings_aboard) + + eventdat += "

        Continue

        " + eventdat += "

        Close

        " + + + if(ORION_TRAIL_SPACEPORT) + if(spaceport_raided) + eventdat += "The Spaceport is on high alert! They wont let you dock since you tried to attack them!" + if(last_spaceport_action) + eventdat += "
        Last Spaceport Action: [last_spaceport_action]" + eventdat += "

        Depart Spaceport

        " + eventdat += "

        Close

        " + else + eventdat += "You pull the ship up to dock at a nearby Spaceport, lucky find!" + eventdat += "
        This Spaceport is home to travellers who failed to reach Orion, but managed to find a different home..." + eventdat += "
        Trading terms: FU = Fuel, FO = Food" + if(last_spaceport_action) + eventdat += "
        Last Spaceport Action: [last_spaceport_action]" + eventdat += "

        Crew:

        " + eventdat += english_list(settlers) + eventdat += "
        Food: [food] | Fuel: [fuel]" + eventdat += "
        Engine Parts: [engine] | Hull Panels: [hull] | Electronics: [electronics]" + + + //If your crew is pathetic you can get freebies (provided you haven't already gotten one from this port) + if(!spaceport_freebie && (fuel < 20 || food < 20)) + spaceport_freebie++ + var/FU = 10 + var/FO = 10 + var/freecrew = 0 + if(prob(30)) + FU = 25 + FO = 25 + + if(prob(10)) + add_crewmember() + freecrew++ + + eventdat += "
        The traders of the spaceport take pitty on you, and give you some food and fuel (+[FU]FU,+[FO]FO)" + if(freecrew) + eventdat += "
        You also gain a new crewmember!" + + fuel += FU + food += FO + + //CREW INTERACTIONS + eventdat += "

        Crew Management:

        " + + //Buy crew + if(food >= 10 && fuel >= 10) + eventdat += "

        Hire a new Crewmember (-10FU,-10FO)

        " + else + eventdat += "

        Cant afford a new Crewmember

        " + + //Sell crew + if(settlers.len > 1) + eventdat += "

        Sell crew for Fuel and Food (+15FU,+15FO)

        " + else + eventdat += "

        Cant afford to sell a Crewmember

        " + + //BUY/SELL STUFF + eventdat += "

        Spare Parts:

        " + + //Engine parts + if(fuel > 5) + eventdat += "

        Buy Engine Parts (-5FU)

        " + else + eventdat += "

        Cant afford to buy Engine Parts" + + //Hull plates + if(fuel > 5) + eventdat += "

        Buy Hull Plates (-5FU)

        " + else + eventdat += "

        Cant afford to buy Hull Plates" + + //Electronics + if(fuel > 5) + eventdat += "

        Buy Spare Electronics (-5FU)

        " + else + eventdat += "

        Cant afford to buy Spare Electronics" + + //Trade + if(fuel > 5) + eventdat += "

        Trade Fuel for Food (-5FU,+5FO)

        " + else + eventdat += "

        Cant afford to Trade Fuel for Food 5) + eventdat += "

        Trade Food for Fuel (+5FU,-5FO)

        " + else + eventdat += "

        Cant afford to Trade Food for Fuel= 1) //need to make sure we even have anyone to remove + removed = pick(safe2remove) + + if(removed) + if(lings_aboard && prob(40*lings_aboard)) //if there are 2 lings you're twice as likely to get one, obviously + lings_aboard = max(0,--lings_aboard) + settlers -= removed + alive-- + return removed + + +/obj/machinery/computer/arcade/orion_trail/proc/win() + playing = 0 + turns = 1 + atom_say("Congratulations, you made it to Orion!") + if(emagged) + new /obj/item/orion_ship(get_turf(src)) + message_admins("[key_name_admin(usr)] made it to Orion on an emagged machine and got an explosive toy ship.") + log_game("[key_name(usr)] made it to Orion on an emagged machine and got an explosive toy ship.") + else + var/score = alive + round(food/2) + round(fuel/5) + engine + hull + electronics - lings_aboard + prizevend(score) + emagged = 0 + name = "The Orion Trail" + desc = "Learn how our ancestors got to Orion, and have fun in the process!" + +/obj/machinery/computer/arcade/orion_trail/emag_act(mob/user) + if(!emagged) + to_chat(user, "You override the cheat code menu and skip to Cheat #[rand(1, 50)]: Realism Mode.") + name = "The Orion Trail: Realism Edition" + desc = "Learn how our ancestors got to Orion, and try not to die in the process!" + newgame() + emagged = 1 + +/mob/living/simple_animal/hostile/syndicate/ranged/orion + name = "spaceport security" + desc = "Premier corporate security forces for all spaceports found along the Orion Trail." + faction = list("orion") + loot = list() + del_on_death = TRUE + +/obj/item/orion_ship + name = "model settler ship" + desc = "A model spaceship, it looks like those used back in the day when travelling to Orion! It even has a miniature FX-293 reactor, which was renowned for its instability and tendency to explode..." + icon = 'icons/obj/toy.dmi' + icon_state = "ship" + w_class = WEIGHT_CLASS_SMALL + var/active = 0 //if the ship is on + +/obj/item/orion_ship/examine(mob/user) + . = ..() + if(in_range(user, src)) + if(!active) + . += "There's a little switch on the bottom. It's flipped down." + else + . += "There's a little switch on the bottom. It's flipped up." + +/obj/item/orion_ship/attack_self(mob/user) //Minibomb-level explosion. Should probably be more because of how hard it is to survive the machine! Also, just over a 5-second fuse + if(active) + return + + message_admins("[key_name_admin(usr)] primed an explosive Orion ship for detonation.") + log_game("[key_name(usr)] primed an explosive Orion ship for detonation.") + + to_chat(user, "You flip the switch on the underside of [src].") + active = 1 + visible_message("[src] softly beeps and whirs to life!") + playsound(src.loc, 'sound/machines/defib_saftyon.ogg', 25, 1) + atom_say("This is ship ID #[rand(1,1000)] to Orion Port Authority. We're coming in for landing, over.") + sleep(20) + visible_message("[src] begins to vibrate...") + atom_say("Uh, Port? Having some issues with our reactor, could you check it out? Over.") + sleep(30) + atom_say("Oh, God! Code Eight! CODE EIGHT! IT'S GONNA BL-") + playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 25, 1) + sleep(3.6) + visible_message("[src] explodes!") + explosion(src.loc, 1,2,4, flame_range = 3) + qdel(src) + + +#undef ORION_TRAIL_WINTURN +#undef ORION_TRAIL_RAIDERS +#undef ORION_TRAIL_FLUX +#undef ORION_TRAIL_ILLNESS +#undef ORION_TRAIL_BREAKDOWN +#undef ORION_TRAIL_LING +#undef ORION_TRAIL_LING_ATTACK +#undef ORION_TRAIL_MALFUNCTION +#undef ORION_TRAIL_COLLISION +#undef ORION_TRAIL_SPACEPORT +#undef ORION_TRAIL_BLACKHOLE diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 308b44e7a3c..2f8321b57bb 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -1,687 +1,687 @@ -/obj/structure/computerframe - density = 1 - anchored = 0 - name = "computer frame" - icon = 'icons/obj/stock_parts.dmi' - icon_state = "0" - max_integrity = 100 - var/state = 0 - var/obj/item/circuitboard/circuit = null - var/base_mineral = /obj/item/stack/sheet/metal - -/obj/structure/computerframe/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - drop_computer_parts() - return ..() // will qdel the frame - -/obj/structure/computerframe/obj_break(damage_flag) - deconstruct() - -/obj/structure/computerframe/proc/drop_computer_parts() - new base_mineral(loc, 5) - if(circuit) - circuit.forceMove(loc) - circuit = null - if(state >= 3) - var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil( loc ) - A.amount = 5 - if(state >= 4) - new /obj/item/stack/sheet/glass(loc, 2) - -/obj/item/circuitboard - density = 0 - anchored = 0 - w_class = WEIGHT_CLASS_SMALL - name = "circuit board" - icon = 'icons/obj/module.dmi' - icon_state = "id_mod" - item_state = "electronic" - origin_tech = "programming=2" - materials = list(MAT_GLASS=200) - var/id = null - var/frequency = null - var/build_path = null - var/board_type = "computer" - var/list/req_components = null - var/powernet = null - var/list/records = null - var/frame_desc = null - var/contain_parts = 1 - toolspeed = 1 - usesound = 'sound/items/deconstruct.ogg' - -/obj/item/circuitboard/computer - board_type = "computer" - -/obj/item/circuitboard/machine - board_type = "machine" - -/obj/item/circuitboard/examine(mob/user) - . = ..() - if(LAZYLEN(req_components)) - var/list/nice_list = list() - for(var/B in req_components) - var/atom/A = B - if(!ispath(A)) - continue - nice_list += list("[req_components[A]] [initial(A.name)]") - . += "Required components: [english_list(nice_list)]." - -/obj/item/circuitboard/message_monitor - name = "Circuit board (Message Monitor)" - build_path = /obj/machinery/computer/message_monitor - origin_tech = "programming=2" -/obj/item/circuitboard/camera - name = "Circuit board (Camera Monitor)" - build_path = /obj/machinery/computer/security - origin_tech = "programming=2;combat=2" - -/obj/item/circuitboard/camera/telescreen - name = "Circuit board (Telescreen)" - build_path = /obj/machinery/computer/security/telescreen -/obj/item/circuitboard/camera/telescreen/entertainment - name = "Circuit board (Entertainment Monitor)" - build_path = /obj/machinery/computer/security/telescreen/entertainment -/obj/item/circuitboard/camera/wooden_tv - name = "Circuit board (Wooden TV)" - build_path = /obj/machinery/computer/security/wooden_tv -/obj/item/circuitboard/camera/mining - name = "Circuit board (Outpost Camera Monitor)" - build_path = /obj/machinery/computer/security/mining -/obj/item/circuitboard/camera/engineering - name = "Circuit board (Engineering Camera Monitor)" - build_path = /obj/machinery/computer/security/engineering - - -/obj/item/circuitboard/xenobiology - name = "Circuit board (Xenobiology Console)" - build_path = /obj/machinery/computer/camera_advanced/xenobio - origin_tech = "programming=3;biotech=3" -/obj/item/circuitboard/aicore - name = "Circuit board (AI Core)" - origin_tech = "programming=3" - board_type = "other" -/obj/item/circuitboard/aiupload - name = "Circuit board (AI Upload)" - build_path = /obj/machinery/computer/aiupload - origin_tech = "programming=4;engineering=4" -/obj/item/circuitboard/borgupload - name = "Circuit board (Cyborg Upload)" - build_path = /obj/machinery/computer/borgupload - origin_tech = "programming=4;engineering=4" -/obj/item/circuitboard/med_data - name = "Circuit board (Medical Records)" - build_path = /obj/machinery/computer/med_data - origin_tech = "programming=2;biotech=2" -/obj/item/circuitboard/pandemic - name = "circuit board (PanD.E.M.I.C. 2200)" - build_path = /obj/machinery/computer/pandemic - origin_tech = "programming=2;biotech=2" -/obj/item/circuitboard/scan_consolenew - name = "Circuit board (DNA Machine)" - build_path = /obj/machinery/computer/scan_consolenew - origin_tech = "programming=2;biotech=2" -/obj/item/circuitboard/communications - name = "Circuit board (Communications Console)" - build_path = /obj/machinery/computer/communications - origin_tech = "programming=3;magnets=3" -/obj/item/circuitboard/card - name = "Circuit board (ID Computer)" - build_path = /obj/machinery/computer/card - origin_tech = "programming=3" -/obj/item/circuitboard/card/minor - name = "Circuit board (Dept ID Computer)" - build_path = /obj/machinery/computer/card/minor - var/target_dept = TARGET_DEPT_GENERIC -/obj/item/circuitboard/card/minor/hos - name = "Circuit board (Sec ID Computer)" - build_path = /obj/machinery/computer/card/minor/hos - target_dept = TARGET_DEPT_SEC -/obj/item/circuitboard/card/minor/cmo - name = "Circuit board (Medical ID Computer)" - build_path = /obj/machinery/computer/card/minor/cmo - target_dept = TARGET_DEPT_MED -/obj/item/circuitboard/card/minor/rd - name = "Circuit board (Science ID Computer)" - build_path = /obj/machinery/computer/card/minor/rd - target_dept = TARGET_DEPT_SCI -/obj/item/circuitboard/card/minor/ce - name = "Circuit board (Engineering ID Computer)" - build_path = /obj/machinery/computer/card/minor/ce - target_dept = TARGET_DEPT_ENG -/obj/item/circuitboard/card/centcom - name = "Circuit board (CentComm ID Computer)" - build_path = /obj/machinery/computer/card/centcom -/obj/item/circuitboard/teleporter - name = "Circuit board (Teleporter Console)" - build_path = /obj/machinery/computer/teleporter - origin_tech = "programming=3;bluespace=3;plasmatech=3" -/obj/item/circuitboard/secure_data - name = "Circuit board (Security Records)" - build_path = /obj/machinery/computer/secure_data - origin_tech = "programming=2;combat=2" -/obj/item/circuitboard/skills - name = "Circuit board (Employment Records)" - build_path = /obj/machinery/computer/skills -/obj/item/circuitboard/stationalert_engineering - name = "Circuit Board (Station Alert Console (Engineering))" - build_path = /obj/machinery/computer/station_alert -/obj/item/circuitboard/stationalert_security - name = "Circuit Board (Station Alert Console (Security))" - build_path = /obj/machinery/computer/station_alert -/obj/item/circuitboard/stationalert_all - name = "Circuit Board (Station Alert Console (All))" - build_path = /obj/machinery/computer/station_alert/all -/obj/item/circuitboard/atmos_alert - name = "Circuit Board (Atmospheric Alert Computer)" - build_path = /obj/machinery/computer/atmos_alert -/obj/item/circuitboard/atmoscontrol - name = "Circuit Board (Central Atmospherics Computer)" - build_path = /obj/machinery/computer/atmoscontrol -/obj/item/circuitboard/air_management - name = "Circuit board (Atmospheric Monitor)" - build_path = /obj/machinery/computer/general_air_control -/obj/item/circuitboard/injector_control - name = "Circuit board (Injector Control)" - build_path = /obj/machinery/computer/general_air_control/fuel_injection -/obj/item/circuitboard/pod - name = "Circuit board (Massdriver Control)" - build_path = /obj/machinery/computer/pod -/obj/item/circuitboard/pod/deathsquad - name = "Circuit board (Deathsquad Massdriver control)" - build_path = /obj/machinery/computer/pod/deathsquad -/obj/item/circuitboard/robotics - name = "Circuit board (Robotics Control Console)" - build_path = /obj/machinery/computer/robotics - origin_tech = "programming=3" -/obj/item/circuitboard/drone_control - name = "Circuit board (Drone Control)" - build_path = /obj/machinery/computer/drone_control - origin_tech = "programming=3" -/obj/item/circuitboard/cloning - name = "Circuit board (Cloning Machine Console)" - build_path = /obj/machinery/computer/cloning - origin_tech = "programming=2;biotech=2" -/obj/item/circuitboard/arcade/battle - name = "circuit board (Arcade Battle)" - build_path = /obj/machinery/computer/arcade/battle - origin_tech = "programming=1" -/obj/item/circuitboard/arcade/orion_trail - name = "circuit board (Orion Trail)" - build_path = /obj/machinery/computer/arcade/orion_trail - origin_tech = "programming=1" -/obj/item/circuitboard/solar_control - name = "Circuit board (Solar Control)" - build_path = /obj/machinery/power/solar_control - origin_tech = "programming=2;powerstorage=2" -/obj/item/circuitboard/powermonitor - name = "Circuit board (Power Monitor)" - build_path = /obj/machinery/computer/monitor - origin_tech = "programming=2;powerstorage=2" -/obj/item/circuitboard/powermonitor/secret - name = "Circuit board (Outdated Power Monitor)" - build_path = /obj/machinery/computer/monitor/secret - origin_tech = "programming=2;powerstorage=2" -/obj/item/circuitboard/olddoor - name = "Circuit board (DoorMex)" - build_path = /obj/machinery/computer/pod/old -/obj/item/circuitboard/syndicatedoor - name = "Circuit board (ProComp Executive)" - build_path = /obj/machinery/computer/pod/old/syndicate -/obj/item/circuitboard/swfdoor - name = "Circuit board (Magix)" - build_path = /obj/machinery/computer/pod/old/swf -/obj/item/circuitboard/prisoner - name = "Circuit board (Prisoner Management)" - build_path = /obj/machinery/computer/prisoner -/obj/item/circuitboard/brigcells - name = "Circuit board (Brig Cell Control)" - build_path = /obj/machinery/computer/brigcells - - -// RD console circuits, so that {de,re}constructing one of the special consoles doesn't ruin everything forever -/obj/item/circuitboard/rdconsole - name = "Circuit Board (RD Console)" - desc = "Swipe a Scientist level ID or higher to reconfigure." - build_path = /obj/machinery/computer/rdconsole/core - req_access = list(ACCESS_TOX) // This is for adjusting the type of computer we're building - in case something messes up the pre-existing robotics or mechanics consoles - var/access_types = list("R&D Core", "Robotics", "E.X.P.E.R.I-MENTOR", "Mechanics", "Public") - id = 1 -/obj/item/circuitboard/rdconsole/robotics - name = "Circuit Board (RD Console - Robotics)" - build_path = /obj/machinery/computer/rdconsole/robotics - id = 2 -/obj/item/circuitboard/rdconsole/experiment - name = "Circuit Board (RD Console - E.X.P.E.R.I-MENTOR)" - build_path = /obj/machinery/computer/rdconsole/experiment - id = 3 -/obj/item/circuitboard/rdconsole/mechanics - name = "Circuit Board (RD Console - Mechanics)" - build_path = /obj/machinery/computer/rdconsole/mechanics - id = 4 -/obj/item/circuitboard/rdconsole/public - name = "Circuit Board (RD Console - Public)" - build_path = /obj/machinery/computer/rdconsole/public - id = 5 - - -/obj/item/circuitboard/mecha_control - name = "Circuit Board (Exosuit Control Console)" - build_path = /obj/machinery/computer/mecha -/obj/item/circuitboard/pod_locater - name = "Circuit Board (Pod Location Console)" - build_path = /obj/machinery/computer/podtracker -/obj/item/circuitboard/rdservercontrol - name = "Circuit Board (RD Server Control)" - build_path = /obj/machinery/computer/rdservercontrol -/obj/item/circuitboard/crew - name = "Circuit board (Crew Monitoring Computer)" - build_path = /obj/machinery/computer/crew - origin_tech = "programming=2;biotech=2" -/obj/item/circuitboard/mech_bay_power_console - name = "Circuit board (Mech Bay Power Control Console)" - build_path = /obj/machinery/computer/mech_bay_power_console - origin_tech = "programming=3;powerstorage=3" -/obj/item/circuitboard/ordercomp - name = "Circuit board (Supply Ordering Console)" - build_path = /obj/machinery/computer/ordercomp - origin_tech = "programming=3" -/obj/item/circuitboard/supplycomp - name = "Circuit board (Supply Shuttle Console)" - build_path = /obj/machinery/computer/supplycomp - origin_tech = "programming=3" - var/contraband_enabled = 0 - -/obj/item/circuitboard/operating - name = "Circuit board (Operating Computer)" - build_path = /obj/machinery/computer/operating - origin_tech = "programming=2;biotech=3" -/obj/item/circuitboard/comm_monitor - name = "Circuit board (Telecommunications Monitor)" - build_path = /obj/machinery/computer/telecomms/monitor - origin_tech = "programming=3;magnets=3;bluespace=2" -/obj/item/circuitboard/comm_server - name = "Circuit board (Telecommunications Server Monitor)" - build_path = /obj/machinery/computer/telecomms/server - origin_tech = "programming=3;magnets=3;bluespace=2" -/obj/item/circuitboard/comm_traffic - name = "Circuitboard (Telecommunications Traffic Control)" - build_path = /obj/machinery/computer/telecomms/traffic - origin_tech = "programming=3;magnets=3;bluespace=2" - - -/obj/item/circuitboard/shuttle - name = "circuit board (Shuttle)" - build_path = /obj/machinery/computer/shuttle - var/shuttleId - var/possible_destinations = "" - -/obj/item/circuitboard/labor_shuttle - name = "circuit Board (Labor Shuttle)" - build_path = /obj/machinery/computer/shuttle/labor -/obj/item/circuitboard/labor_shuttle/one_way - name = "circuit Board (Prisoner Shuttle Console)" - build_path = /obj/machinery/computer/shuttle/labor/one_way -/obj/item/circuitboard/ferry - name = "circuit Board (Transport Ferry)" - build_path = /obj/machinery/computer/shuttle/ferry -/obj/item/circuitboard/ferry/request - name = "circuit Board (Transport Ferry Console)" - build_path = /obj/machinery/computer/shuttle/ferry/request -/obj/item/circuitboard/mining_shuttle - name = "circuit Board (Mining Shuttle)" - build_path = /obj/machinery/computer/shuttle/mining -/obj/item/circuitboard/white_ship - name = "circuit Board (White Ship)" - build_path = /obj/machinery/computer/shuttle/white_ship -/obj/item/circuitboard/shuttle/syndicate - name = "circuit board (Syndicate Shuttle)" - build_path = /obj/machinery/computer/shuttle/syndicate -/obj/item/circuitboard/shuttle/syndicate/recall - name = "circuit board (Syndicate Shuttle Recall Terminal)" - build_path = /obj/machinery/computer/shuttle/syndicate/recall -/obj/item/circuitboard/shuttle/syndicate/drop_pod - name = "circuit board (Syndicate Drop Pod)" - build_path = /obj/machinery/computer/shuttle/syndicate/drop_pod -/obj/item/circuitboard/shuttle/golem_ship - name = "circuit Board (Golem Ship)" - build_path = /obj/machinery/computer/shuttle/golem_ship - -/obj/item/circuitboard/HolodeckControl - name = "Circuit board (Holodeck Control)" - build_path = /obj/machinery/computer/HolodeckControl - origin_tech = "programming=4" -/obj/item/circuitboard/aifixer - name = "Circuit board (AI Integrity Restorer)" - build_path = /obj/machinery/computer/aifixer - origin_tech = "programming=2;biotech=2" -/obj/item/circuitboard/area_atmos - name = "Circuit board (Area Air Control)" - build_path = /obj/machinery/computer/area_atmos - origin_tech = "programming=2" -/obj/item/circuitboard/telesci_console - name = "Circuit board (Telepad Control Console)" - build_path = /obj/machinery/computer/telescience - origin_tech = "programming=3;bluespace=3;plasmatech=4" - -/obj/item/circuitboard/atmos_automation - name = "Circuit board (Atmospherics Automation)" - build_path = /obj/machinery/computer/general_air_control/atmos_automation -/obj/item/circuitboard/large_tank_control - name = "Circuit board (Atmospheric Tank Control)" - build_path = /obj/machinery/computer/general_air_control/large_tank_control - origin_tech = "programming=2;engineering=3;materials=2" - -/obj/item/circuitboard/turbine_computer - name = "circuit board (Turbine Computer)" - build_path = /obj/machinery/computer/turbine_computer - origin_tech = "programming=4;engineering=4;powerstorage=4" - -/obj/item/circuitboard/HONKputer - name = "Circuit board (HONKputer)" - build_path = /obj/machinery/computer/HONKputer - origin_tech = "programming=2" - icon = 'icons/obj/machines/HONKputer.dmi' - icon_state = "bananium_board" - board_type = "honkcomputer" - - -/obj/item/circuitboard/supplycomp/attackby(obj/item/I as obj, mob/user as mob, params) - if(istype(I,/obj/item/multitool)) - var/catastasis = contraband_enabled - var/opposite_catastasis - if(catastasis) - opposite_catastasis = "STANDARD" - catastasis = "BROAD" - else - opposite_catastasis = "BROAD" - catastasis = "STANDARD" - - switch( alert("Current receiver spectrum is set to: [catastasis]","Multitool-Circuitboard interface","Switch to [opposite_catastasis]","Cancel") ) - //switch( alert("Current receiver spectrum is set to: " {(contraband_enabled) ? ("BROAD") : ("STANDARD")} , "Multitool-Circuitboard interface" , "Switch to " {(contraband_enabled) ? ("STANDARD") : ("BROAD")}, "Cancel") ) - if("Switch to STANDARD","Switch to BROAD") - contraband_enabled = !contraband_enabled - - if("Cancel") - return - else - to_chat(user, "DERP! BUG! Report this (And what you were doing to cause it) to Agouri") - return - return ..() - -/obj/item/circuitboard/rdconsole/attackby(obj/item/I as obj, mob/user as mob, params) - if(istype(I,/obj/item/card/id)||istype(I, /obj/item/pda)) - if(allowed(user)) - user.visible_message("\the [user] waves [user.p_their()] ID past the [src]'s access protocol scanner.", "You swipe your ID past the [src]'s access protocol scanner.") - var/console_choice = input(user, "What do you want to configure the access to?", "Access Modification", "R&D Core") as null|anything in access_types - if(console_choice == null) - return - switch(console_choice) - if("R&D Core") - name = "Circuit Board (RD Console)" - build_path = /obj/machinery/computer/rdconsole/core - id = 1 - if("Robotics") - name = "Circuit Board (RD Console - Robotics)" - build_path = /obj/machinery/computer/rdconsole/robotics - id = 2 - if("E.X.P.E.R.I-MENTOR") - name = "Circuit Board (RD Console - E.X.P.E.R.I-MENTOR)" - build_path = /obj/machinery/computer/rdconsole/experiment - id = 3 - if("Mechanics") - name = "Circuit Board (RD Console - Mechanics)" - build_path = /obj/machinery/computer/rdconsole/mechanics - id = 4 - if("Public") - name = "Circuit Board (RD Console - Public)" - build_path = /obj/machinery/computer/rdconsole/public - id = 5 - - to_chat(user, "Access protocols set to [console_choice].") - else - to_chat(user, "Access Denied") - return - return ..() - -/obj/structure/computerframe/attackby(obj/item/P as obj, mob/user as mob, params) - switch(state) - if(0) - if(istype(P, /obj/item/wrench)) - playsound(loc, P.usesound, 50, 1) - if(do_after(user, 20 * P.toolspeed, target = src)) - to_chat(user, "You wrench the frame into place.") - anchored = 1 - state = 1 - return - if(1) - if(istype(P, /obj/item/wrench)) - playsound(loc, P.usesound, 50, 1) - if(do_after(user, 20 * P.toolspeed, target = src)) - to_chat(user, "You unfasten the frame.") - anchored = 0 - state = 0 - return - if(istype(P, /obj/item/circuitboard) && !circuit) - var/obj/item/circuitboard/B = P - if(B.board_type == "computer") - playsound(loc, B.usesound, 50, 1) - to_chat(user, "You place the circuit board inside the frame.") - icon_state = "1" - circuit = P - user.drop_item() - P.loc = src - else - to_chat(user, "This frame does not accept circuit boards of this type!") - return - if(istype(P, /obj/item/screwdriver) && circuit) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You screw the circuit board into place.") - state = 2 - icon_state = "2" - return - if(istype(P, /obj/item/crowbar) && circuit) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You remove the circuit board.") - state = 1 - icon_state = "0" - circuit.loc = loc - circuit = null - return - if(2) - if(istype(P, /obj/item/screwdriver) && circuit) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You unfasten the circuit board.") - state = 1 - icon_state = "1" - return - if(istype(P, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = P - if(C.amount >= 5) - playsound(loc, C.usesound, 50, 1) - to_chat(user, "You start to add cables to the frame.") - if(do_after(user, 20 * C.toolspeed, target = src)) - if(state == 2 && C.amount >= 5 && C.use(5)) - to_chat(user, "You add cables to the frame.") - state = 3 - icon_state = "3" - else - to_chat(user, "At some point during construction you lost some cable. Make sure you have five lengths before trying again.") - return - else - to_chat(user, "You need five lengths of cable to wire the frame.") - return - if(3) - if(istype(P, /obj/item/wirecutters)) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You remove the cables.") - state = 2 - icon_state = "2" - var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil( loc ) - A.amount = 5 - return - if(istype(P, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = P - if(G.amount >= 2) - playsound(loc, G.usesound, 50, 1) - to_chat(user, "You start to add the glass panel to the frame.") - if(do_after(user, 20 * G.toolspeed, target = src)) - if(state == 3 && G.amount >= 2 && G.use(2)) - to_chat(user, "You put in the glass panel.") - state = 4 - icon_state = "4" - else - to_chat(user, "At some point during construction you lost some glass. Make sure you have two sheets before trying again.") - return - else - to_chat(user, "You need two sheets of glass for this.") - return - if(4) - if(istype(P, /obj/item/crowbar)) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You remove the glass panel.") - state = 3 - icon_state = "3" - new /obj/item/stack/sheet/glass(loc, 2) - return - if(istype(P, /obj/item/screwdriver)) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You connect the monitor.") - var/B = new circuit.build_path (loc) - if(circuit.powernet) B:powernet = circuit.powernet - if(circuit.id) B:id = circuit.id - if(circuit.records) B:records = circuit.records - if(circuit.frequency) B:frequency = circuit.frequency - if(istype(circuit,/obj/item/circuitboard/supplycomp)) - var/obj/machinery/computer/supplycomp/SC = B - var/obj/item/circuitboard/supplycomp/C = circuit - SC.can_order_contraband = C.contraband_enabled - qdel(src) - return - if(user.a_intent == INTENT_HARM) - return ..() - - -/obj/structure/computerframe/welder_act(mob/user, obj/item/I) - if(state) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_SLICING_MESSAGE - if(I.use_tool(src, user, 50, volume = I.tool_volume) && !state) - to_chat(user, "You deconstruct [src].") - deconstruct(TRUE) - - - -/obj/structure/computerframe/HONKputer - name = "Bananium Computer-frame" - icon = 'icons/obj/machines/HONKputer.dmi' - base_mineral = /obj/item/stack/sheet/mineral/bananium - -/obj/structure/computerframe/HONKputer/attackby(obj/item/P as obj, mob/user as mob, params) - switch(state) - if(0) - if(istype(P, /obj/item/wrench)) - playsound(loc, P.usesound, 50, 1) - if(do_after(user, 20, target = src)) - to_chat(user, "You wrench the frame into place.") - anchored = 1 - state = 1 - if(1) - if(istype(P, /obj/item/wrench)) - playsound(loc, P.usesound, 50, 1) - if(do_after(user, 20 * P.toolspeed, target = src)) - to_chat(user, "You unfasten the frame.") - anchored = 0 - state = 0 - if(istype(P, /obj/item/circuitboard) && !circuit) - var/obj/item/circuitboard/B = P - if(B.board_type == "honkcomputer") - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You place the circuit board inside the frame.") - icon_state = "1" - circuit = P - user.drop_item() - P.loc = src - else - to_chat(user, "This frame does not accept circuit boards of this type!") - if(istype(P, /obj/item/screwdriver) && circuit) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You screw the circuit board into place.") - state = 2 - icon_state = "2" - if(istype(P, /obj/item/crowbar) && circuit) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You remove the circuit board.") - state = 1 - icon_state = "0" - circuit.loc = loc - circuit = null - return - if(2) - if(istype(P, /obj/item/screwdriver) && circuit) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You unfasten the circuit board.") - state = 1 - icon_state = "1" - if(istype(P, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = P - if(C.amount >= 5) - playsound(loc, C.usesound, 50, 1) - to_chat(user, "You start to add cables to the frame.") - if(do_after(user, 20 * C.toolspeed, target = src)) - if(state == 2 && C.amount >= 5 && C.use(5)) - to_chat(user, "You add cables to the frame.") - state = 3 - icon_state = "3" - else - to_chat(user, "At some point during construction you lost some cable. Make sure you have five lengths before trying again.") - return - else - to_chat(user, "You need five lengths of cable to wire the frame.") - return - if(3) - if(istype(P, /obj/item/wirecutters)) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You remove the cables.") - state = 2 - icon_state = "2" - var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil( loc ) - A.amount = 5 - - if(istype(P, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = P - if(G.amount >= 2) - playsound(loc, G.usesound, 50, 1) - to_chat(user, "You start to add the glass panel to the frame.") - if(do_after(user, 20 * G.toolspeed, target = src)) - if(state == 3 && G.amount >= 2 && G.use(2)) - to_chat(user, "You put in the glass panel.") - state = 4 - icon_state = "4" - else - to_chat(user, "At some point during construction you lost some glass. Make sure you have two sheets before trying again.") - return - else - to_chat(user, "You need two sheets of glass for this.") - return - if(4) - if(istype(P, /obj/item/crowbar)) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You remove the glass panel.") - state = 3 - icon_state = "3" - new /obj/item/stack/sheet/glass(loc, 2) - if(istype(P, /obj/item/screwdriver)) - playsound(loc, P.usesound, 50, 1) - to_chat(user, "You connect the monitor.") - var/B = new circuit.build_path (loc) - if(circuit.powernet) B:powernet = circuit.powernet - if(circuit.id) B:id = circuit.id - if(circuit.records) B:records = circuit.records - if(circuit.frequency) B:frequency = circuit.frequency - qdel(src) - return - return ..() \ No newline at end of file +/obj/structure/computerframe + density = 1 + anchored = 0 + name = "computer frame" + icon = 'icons/obj/stock_parts.dmi' + icon_state = "0" + max_integrity = 100 + var/state = 0 + var/obj/item/circuitboard/circuit = null + var/base_mineral = /obj/item/stack/sheet/metal + +/obj/structure/computerframe/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + drop_computer_parts() + return ..() // will qdel the frame + +/obj/structure/computerframe/obj_break(damage_flag) + deconstruct() + +/obj/structure/computerframe/proc/drop_computer_parts() + new base_mineral(loc, 5) + if(circuit) + circuit.forceMove(loc) + circuit = null + if(state >= 3) + var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil( loc ) + A.amount = 5 + if(state >= 4) + new /obj/item/stack/sheet/glass(loc, 2) + +/obj/item/circuitboard + density = 0 + anchored = 0 + w_class = WEIGHT_CLASS_SMALL + name = "circuit board" + icon = 'icons/obj/module.dmi' + icon_state = "id_mod" + item_state = "electronic" + origin_tech = "programming=2" + materials = list(MAT_GLASS=200) + var/id = null + var/frequency = null + var/build_path = null + var/board_type = "computer" + var/list/req_components = null + var/powernet = null + var/list/records = null + var/frame_desc = null + var/contain_parts = 1 + toolspeed = 1 + usesound = 'sound/items/deconstruct.ogg' + +/obj/item/circuitboard/computer + board_type = "computer" + +/obj/item/circuitboard/machine + board_type = "machine" + +/obj/item/circuitboard/examine(mob/user) + . = ..() + if(LAZYLEN(req_components)) + var/list/nice_list = list() + for(var/B in req_components) + var/atom/A = B + if(!ispath(A)) + continue + nice_list += list("[req_components[A]] [initial(A.name)]") + . += "Required components: [english_list(nice_list)]." + +/obj/item/circuitboard/message_monitor + name = "Circuit board (Message Monitor)" + build_path = /obj/machinery/computer/message_monitor + origin_tech = "programming=2" +/obj/item/circuitboard/camera + name = "Circuit board (Camera Monitor)" + build_path = /obj/machinery/computer/security + origin_tech = "programming=2;combat=2" + +/obj/item/circuitboard/camera/telescreen + name = "Circuit board (Telescreen)" + build_path = /obj/machinery/computer/security/telescreen +/obj/item/circuitboard/camera/telescreen/entertainment + name = "Circuit board (Entertainment Monitor)" + build_path = /obj/machinery/computer/security/telescreen/entertainment +/obj/item/circuitboard/camera/wooden_tv + name = "Circuit board (Wooden TV)" + build_path = /obj/machinery/computer/security/wooden_tv +/obj/item/circuitboard/camera/mining + name = "Circuit board (Outpost Camera Monitor)" + build_path = /obj/machinery/computer/security/mining +/obj/item/circuitboard/camera/engineering + name = "Circuit board (Engineering Camera Monitor)" + build_path = /obj/machinery/computer/security/engineering + + +/obj/item/circuitboard/xenobiology + name = "Circuit board (Xenobiology Console)" + build_path = /obj/machinery/computer/camera_advanced/xenobio + origin_tech = "programming=3;biotech=3" +/obj/item/circuitboard/aicore + name = "Circuit board (AI Core)" + origin_tech = "programming=3" + board_type = "other" +/obj/item/circuitboard/aiupload + name = "Circuit board (AI Upload)" + build_path = /obj/machinery/computer/aiupload + origin_tech = "programming=4;engineering=4" +/obj/item/circuitboard/borgupload + name = "Circuit board (Cyborg Upload)" + build_path = /obj/machinery/computer/borgupload + origin_tech = "programming=4;engineering=4" +/obj/item/circuitboard/med_data + name = "Circuit board (Medical Records)" + build_path = /obj/machinery/computer/med_data + origin_tech = "programming=2;biotech=2" +/obj/item/circuitboard/pandemic + name = "circuit board (PanD.E.M.I.C. 2200)" + build_path = /obj/machinery/computer/pandemic + origin_tech = "programming=2;biotech=2" +/obj/item/circuitboard/scan_consolenew + name = "Circuit board (DNA Machine)" + build_path = /obj/machinery/computer/scan_consolenew + origin_tech = "programming=2;biotech=2" +/obj/item/circuitboard/communications + name = "Circuit board (Communications Console)" + build_path = /obj/machinery/computer/communications + origin_tech = "programming=3;magnets=3" +/obj/item/circuitboard/card + name = "Circuit board (ID Computer)" + build_path = /obj/machinery/computer/card + origin_tech = "programming=3" +/obj/item/circuitboard/card/minor + name = "Circuit board (Dept ID Computer)" + build_path = /obj/machinery/computer/card/minor + var/target_dept = TARGET_DEPT_GENERIC +/obj/item/circuitboard/card/minor/hos + name = "Circuit board (Sec ID Computer)" + build_path = /obj/machinery/computer/card/minor/hos + target_dept = TARGET_DEPT_SEC +/obj/item/circuitboard/card/minor/cmo + name = "Circuit board (Medical ID Computer)" + build_path = /obj/machinery/computer/card/minor/cmo + target_dept = TARGET_DEPT_MED +/obj/item/circuitboard/card/minor/rd + name = "Circuit board (Science ID Computer)" + build_path = /obj/machinery/computer/card/minor/rd + target_dept = TARGET_DEPT_SCI +/obj/item/circuitboard/card/minor/ce + name = "Circuit board (Engineering ID Computer)" + build_path = /obj/machinery/computer/card/minor/ce + target_dept = TARGET_DEPT_ENG +/obj/item/circuitboard/card/centcom + name = "Circuit board (CentComm ID Computer)" + build_path = /obj/machinery/computer/card/centcom +/obj/item/circuitboard/teleporter + name = "Circuit board (Teleporter Console)" + build_path = /obj/machinery/computer/teleporter + origin_tech = "programming=3;bluespace=3;plasmatech=3" +/obj/item/circuitboard/secure_data + name = "Circuit board (Security Records)" + build_path = /obj/machinery/computer/secure_data + origin_tech = "programming=2;combat=2" +/obj/item/circuitboard/skills + name = "Circuit board (Employment Records)" + build_path = /obj/machinery/computer/skills +/obj/item/circuitboard/stationalert_engineering + name = "Circuit Board (Station Alert Console (Engineering))" + build_path = /obj/machinery/computer/station_alert +/obj/item/circuitboard/stationalert_security + name = "Circuit Board (Station Alert Console (Security))" + build_path = /obj/machinery/computer/station_alert +/obj/item/circuitboard/stationalert_all + name = "Circuit Board (Station Alert Console (All))" + build_path = /obj/machinery/computer/station_alert/all +/obj/item/circuitboard/atmos_alert + name = "Circuit Board (Atmospheric Alert Computer)" + build_path = /obj/machinery/computer/atmos_alert +/obj/item/circuitboard/atmoscontrol + name = "Circuit Board (Central Atmospherics Computer)" + build_path = /obj/machinery/computer/atmoscontrol +/obj/item/circuitboard/air_management + name = "Circuit board (Atmospheric Monitor)" + build_path = /obj/machinery/computer/general_air_control +/obj/item/circuitboard/injector_control + name = "Circuit board (Injector Control)" + build_path = /obj/machinery/computer/general_air_control/fuel_injection +/obj/item/circuitboard/pod + name = "Circuit board (Massdriver Control)" + build_path = /obj/machinery/computer/pod +/obj/item/circuitboard/pod/deathsquad + name = "Circuit board (Deathsquad Massdriver control)" + build_path = /obj/machinery/computer/pod/deathsquad +/obj/item/circuitboard/robotics + name = "Circuit board (Robotics Control Console)" + build_path = /obj/machinery/computer/robotics + origin_tech = "programming=3" +/obj/item/circuitboard/drone_control + name = "Circuit board (Drone Control)" + build_path = /obj/machinery/computer/drone_control + origin_tech = "programming=3" +/obj/item/circuitboard/cloning + name = "Circuit board (Cloning Machine Console)" + build_path = /obj/machinery/computer/cloning + origin_tech = "programming=2;biotech=2" +/obj/item/circuitboard/arcade/battle + name = "circuit board (Arcade Battle)" + build_path = /obj/machinery/computer/arcade/battle + origin_tech = "programming=1" +/obj/item/circuitboard/arcade/orion_trail + name = "circuit board (Orion Trail)" + build_path = /obj/machinery/computer/arcade/orion_trail + origin_tech = "programming=1" +/obj/item/circuitboard/solar_control + name = "Circuit board (Solar Control)" + build_path = /obj/machinery/power/solar_control + origin_tech = "programming=2;powerstorage=2" +/obj/item/circuitboard/powermonitor + name = "Circuit board (Power Monitor)" + build_path = /obj/machinery/computer/monitor + origin_tech = "programming=2;powerstorage=2" +/obj/item/circuitboard/powermonitor/secret + name = "Circuit board (Outdated Power Monitor)" + build_path = /obj/machinery/computer/monitor/secret + origin_tech = "programming=2;powerstorage=2" +/obj/item/circuitboard/olddoor + name = "Circuit board (DoorMex)" + build_path = /obj/machinery/computer/pod/old +/obj/item/circuitboard/syndicatedoor + name = "Circuit board (ProComp Executive)" + build_path = /obj/machinery/computer/pod/old/syndicate +/obj/item/circuitboard/swfdoor + name = "Circuit board (Magix)" + build_path = /obj/machinery/computer/pod/old/swf +/obj/item/circuitboard/prisoner + name = "Circuit board (Prisoner Management)" + build_path = /obj/machinery/computer/prisoner +/obj/item/circuitboard/brigcells + name = "Circuit board (Brig Cell Control)" + build_path = /obj/machinery/computer/brigcells + + +// RD console circuits, so that {de,re}constructing one of the special consoles doesn't ruin everything forever +/obj/item/circuitboard/rdconsole + name = "Circuit Board (RD Console)" + desc = "Swipe a Scientist level ID or higher to reconfigure." + build_path = /obj/machinery/computer/rdconsole/core + req_access = list(ACCESS_TOX) // This is for adjusting the type of computer we're building - in case something messes up the pre-existing robotics or mechanics consoles + var/access_types = list("R&D Core", "Robotics", "E.X.P.E.R.I-MENTOR", "Mechanics", "Public") + id = 1 +/obj/item/circuitboard/rdconsole/robotics + name = "Circuit Board (RD Console - Robotics)" + build_path = /obj/machinery/computer/rdconsole/robotics + id = 2 +/obj/item/circuitboard/rdconsole/experiment + name = "Circuit Board (RD Console - E.X.P.E.R.I-MENTOR)" + build_path = /obj/machinery/computer/rdconsole/experiment + id = 3 +/obj/item/circuitboard/rdconsole/mechanics + name = "Circuit Board (RD Console - Mechanics)" + build_path = /obj/machinery/computer/rdconsole/mechanics + id = 4 +/obj/item/circuitboard/rdconsole/public + name = "Circuit Board (RD Console - Public)" + build_path = /obj/machinery/computer/rdconsole/public + id = 5 + + +/obj/item/circuitboard/mecha_control + name = "Circuit Board (Exosuit Control Console)" + build_path = /obj/machinery/computer/mecha +/obj/item/circuitboard/pod_locater + name = "Circuit Board (Pod Location Console)" + build_path = /obj/machinery/computer/podtracker +/obj/item/circuitboard/rdservercontrol + name = "Circuit Board (RD Server Control)" + build_path = /obj/machinery/computer/rdservercontrol +/obj/item/circuitboard/crew + name = "Circuit board (Crew Monitoring Computer)" + build_path = /obj/machinery/computer/crew + origin_tech = "programming=2;biotech=2" +/obj/item/circuitboard/mech_bay_power_console + name = "Circuit board (Mech Bay Power Control Console)" + build_path = /obj/machinery/computer/mech_bay_power_console + origin_tech = "programming=3;powerstorage=3" +/obj/item/circuitboard/ordercomp + name = "Circuit board (Supply Ordering Console)" + build_path = /obj/machinery/computer/ordercomp + origin_tech = "programming=3" +/obj/item/circuitboard/supplycomp + name = "Circuit board (Supply Shuttle Console)" + build_path = /obj/machinery/computer/supplycomp + origin_tech = "programming=3" + var/contraband_enabled = 0 + +/obj/item/circuitboard/operating + name = "Circuit board (Operating Computer)" + build_path = /obj/machinery/computer/operating + origin_tech = "programming=2;biotech=3" +/obj/item/circuitboard/comm_monitor + name = "Circuit board (Telecommunications Monitor)" + build_path = /obj/machinery/computer/telecomms/monitor + origin_tech = "programming=3;magnets=3;bluespace=2" +/obj/item/circuitboard/comm_server + name = "Circuit board (Telecommunications Server Monitor)" + build_path = /obj/machinery/computer/telecomms/server + origin_tech = "programming=3;magnets=3;bluespace=2" +/obj/item/circuitboard/comm_traffic + name = "Circuitboard (Telecommunications Traffic Control)" + build_path = /obj/machinery/computer/telecomms/traffic + origin_tech = "programming=3;magnets=3;bluespace=2" + + +/obj/item/circuitboard/shuttle + name = "circuit board (Shuttle)" + build_path = /obj/machinery/computer/shuttle + var/shuttleId + var/possible_destinations = "" + +/obj/item/circuitboard/labor_shuttle + name = "circuit Board (Labor Shuttle)" + build_path = /obj/machinery/computer/shuttle/labor +/obj/item/circuitboard/labor_shuttle/one_way + name = "circuit Board (Prisoner Shuttle Console)" + build_path = /obj/machinery/computer/shuttle/labor/one_way +/obj/item/circuitboard/ferry + name = "circuit Board (Transport Ferry)" + build_path = /obj/machinery/computer/shuttle/ferry +/obj/item/circuitboard/ferry/request + name = "circuit Board (Transport Ferry Console)" + build_path = /obj/machinery/computer/shuttle/ferry/request +/obj/item/circuitboard/mining_shuttle + name = "circuit Board (Mining Shuttle)" + build_path = /obj/machinery/computer/shuttle/mining +/obj/item/circuitboard/white_ship + name = "circuit Board (White Ship)" + build_path = /obj/machinery/computer/shuttle/white_ship +/obj/item/circuitboard/shuttle/syndicate + name = "circuit board (Syndicate Shuttle)" + build_path = /obj/machinery/computer/shuttle/syndicate +/obj/item/circuitboard/shuttle/syndicate/recall + name = "circuit board (Syndicate Shuttle Recall Terminal)" + build_path = /obj/machinery/computer/shuttle/syndicate/recall +/obj/item/circuitboard/shuttle/syndicate/drop_pod + name = "circuit board (Syndicate Drop Pod)" + build_path = /obj/machinery/computer/shuttle/syndicate/drop_pod +/obj/item/circuitboard/shuttle/golem_ship + name = "circuit Board (Golem Ship)" + build_path = /obj/machinery/computer/shuttle/golem_ship + +/obj/item/circuitboard/HolodeckControl + name = "Circuit board (Holodeck Control)" + build_path = /obj/machinery/computer/HolodeckControl + origin_tech = "programming=4" +/obj/item/circuitboard/aifixer + name = "Circuit board (AI Integrity Restorer)" + build_path = /obj/machinery/computer/aifixer + origin_tech = "programming=2;biotech=2" +/obj/item/circuitboard/area_atmos + name = "Circuit board (Area Air Control)" + build_path = /obj/machinery/computer/area_atmos + origin_tech = "programming=2" +/obj/item/circuitboard/telesci_console + name = "Circuit board (Telepad Control Console)" + build_path = /obj/machinery/computer/telescience + origin_tech = "programming=3;bluespace=3;plasmatech=4" + +/obj/item/circuitboard/atmos_automation + name = "Circuit board (Atmospherics Automation)" + build_path = /obj/machinery/computer/general_air_control/atmos_automation +/obj/item/circuitboard/large_tank_control + name = "Circuit board (Atmospheric Tank Control)" + build_path = /obj/machinery/computer/general_air_control/large_tank_control + origin_tech = "programming=2;engineering=3;materials=2" + +/obj/item/circuitboard/turbine_computer + name = "circuit board (Turbine Computer)" + build_path = /obj/machinery/computer/turbine_computer + origin_tech = "programming=4;engineering=4;powerstorage=4" + +/obj/item/circuitboard/HONKputer + name = "Circuit board (HONKputer)" + build_path = /obj/machinery/computer/HONKputer + origin_tech = "programming=2" + icon = 'icons/obj/machines/HONKputer.dmi' + icon_state = "bananium_board" + board_type = "honkcomputer" + + +/obj/item/circuitboard/supplycomp/attackby(obj/item/I as obj, mob/user as mob, params) + if(istype(I,/obj/item/multitool)) + var/catastasis = contraband_enabled + var/opposite_catastasis + if(catastasis) + opposite_catastasis = "STANDARD" + catastasis = "BROAD" + else + opposite_catastasis = "BROAD" + catastasis = "STANDARD" + + switch( alert("Current receiver spectrum is set to: [catastasis]","Multitool-Circuitboard interface","Switch to [opposite_catastasis]","Cancel") ) + //switch( alert("Current receiver spectrum is set to: " {(contraband_enabled) ? ("BROAD") : ("STANDARD")} , "Multitool-Circuitboard interface" , "Switch to " {(contraband_enabled) ? ("STANDARD") : ("BROAD")}, "Cancel") ) + if("Switch to STANDARD","Switch to BROAD") + contraband_enabled = !contraband_enabled + + if("Cancel") + return + else + to_chat(user, "DERP! BUG! Report this (And what you were doing to cause it) to Agouri") + return + return ..() + +/obj/item/circuitboard/rdconsole/attackby(obj/item/I as obj, mob/user as mob, params) + if(istype(I,/obj/item/card/id)||istype(I, /obj/item/pda)) + if(allowed(user)) + user.visible_message("\the [user] waves [user.p_their()] ID past the [src]'s access protocol scanner.", "You swipe your ID past the [src]'s access protocol scanner.") + var/console_choice = input(user, "What do you want to configure the access to?", "Access Modification", "R&D Core") as null|anything in access_types + if(console_choice == null) + return + switch(console_choice) + if("R&D Core") + name = "Circuit Board (RD Console)" + build_path = /obj/machinery/computer/rdconsole/core + id = 1 + if("Robotics") + name = "Circuit Board (RD Console - Robotics)" + build_path = /obj/machinery/computer/rdconsole/robotics + id = 2 + if("E.X.P.E.R.I-MENTOR") + name = "Circuit Board (RD Console - E.X.P.E.R.I-MENTOR)" + build_path = /obj/machinery/computer/rdconsole/experiment + id = 3 + if("Mechanics") + name = "Circuit Board (RD Console - Mechanics)" + build_path = /obj/machinery/computer/rdconsole/mechanics + id = 4 + if("Public") + name = "Circuit Board (RD Console - Public)" + build_path = /obj/machinery/computer/rdconsole/public + id = 5 + + to_chat(user, "Access protocols set to [console_choice].") + else + to_chat(user, "Access Denied") + return + return ..() + +/obj/structure/computerframe/attackby(obj/item/P as obj, mob/user as mob, params) + switch(state) + if(0) + if(istype(P, /obj/item/wrench)) + playsound(loc, P.usesound, 50, 1) + if(do_after(user, 20 * P.toolspeed, target = src)) + to_chat(user, "You wrench the frame into place.") + anchored = 1 + state = 1 + return + if(1) + if(istype(P, /obj/item/wrench)) + playsound(loc, P.usesound, 50, 1) + if(do_after(user, 20 * P.toolspeed, target = src)) + to_chat(user, "You unfasten the frame.") + anchored = 0 + state = 0 + return + if(istype(P, /obj/item/circuitboard) && !circuit) + var/obj/item/circuitboard/B = P + if(B.board_type == "computer") + playsound(loc, B.usesound, 50, 1) + to_chat(user, "You place the circuit board inside the frame.") + icon_state = "1" + circuit = P + user.drop_item() + P.loc = src + else + to_chat(user, "This frame does not accept circuit boards of this type!") + return + if(istype(P, /obj/item/screwdriver) && circuit) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You screw the circuit board into place.") + state = 2 + icon_state = "2" + return + if(istype(P, /obj/item/crowbar) && circuit) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You remove the circuit board.") + state = 1 + icon_state = "0" + circuit.loc = loc + circuit = null + return + if(2) + if(istype(P, /obj/item/screwdriver) && circuit) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You unfasten the circuit board.") + state = 1 + icon_state = "1" + return + if(istype(P, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = P + if(C.amount >= 5) + playsound(loc, C.usesound, 50, 1) + to_chat(user, "You start to add cables to the frame.") + if(do_after(user, 20 * C.toolspeed, target = src)) + if(state == 2 && C.amount >= 5 && C.use(5)) + to_chat(user, "You add cables to the frame.") + state = 3 + icon_state = "3" + else + to_chat(user, "At some point during construction you lost some cable. Make sure you have five lengths before trying again.") + return + else + to_chat(user, "You need five lengths of cable to wire the frame.") + return + if(3) + if(istype(P, /obj/item/wirecutters)) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You remove the cables.") + state = 2 + icon_state = "2" + var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil( loc ) + A.amount = 5 + return + if(istype(P, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = P + if(G.amount >= 2) + playsound(loc, G.usesound, 50, 1) + to_chat(user, "You start to add the glass panel to the frame.") + if(do_after(user, 20 * G.toolspeed, target = src)) + if(state == 3 && G.amount >= 2 && G.use(2)) + to_chat(user, "You put in the glass panel.") + state = 4 + icon_state = "4" + else + to_chat(user, "At some point during construction you lost some glass. Make sure you have two sheets before trying again.") + return + else + to_chat(user, "You need two sheets of glass for this.") + return + if(4) + if(istype(P, /obj/item/crowbar)) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You remove the glass panel.") + state = 3 + icon_state = "3" + new /obj/item/stack/sheet/glass(loc, 2) + return + if(istype(P, /obj/item/screwdriver)) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You connect the monitor.") + var/B = new circuit.build_path (loc) + if(circuit.powernet) B:powernet = circuit.powernet + if(circuit.id) B:id = circuit.id + if(circuit.records) B:records = circuit.records + if(circuit.frequency) B:frequency = circuit.frequency + if(istype(circuit,/obj/item/circuitboard/supplycomp)) + var/obj/machinery/computer/supplycomp/SC = B + var/obj/item/circuitboard/supplycomp/C = circuit + SC.can_order_contraband = C.contraband_enabled + qdel(src) + return + if(user.a_intent == INTENT_HARM) + return ..() + + +/obj/structure/computerframe/welder_act(mob/user, obj/item/I) + if(state) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_SLICING_MESSAGE + if(I.use_tool(src, user, 50, volume = I.tool_volume) && !state) + to_chat(user, "You deconstruct [src].") + deconstruct(TRUE) + + + +/obj/structure/computerframe/HONKputer + name = "Bananium Computer-frame" + icon = 'icons/obj/machines/HONKputer.dmi' + base_mineral = /obj/item/stack/sheet/mineral/bananium + +/obj/structure/computerframe/HONKputer/attackby(obj/item/P as obj, mob/user as mob, params) + switch(state) + if(0) + if(istype(P, /obj/item/wrench)) + playsound(loc, P.usesound, 50, 1) + if(do_after(user, 20, target = src)) + to_chat(user, "You wrench the frame into place.") + anchored = 1 + state = 1 + if(1) + if(istype(P, /obj/item/wrench)) + playsound(loc, P.usesound, 50, 1) + if(do_after(user, 20 * P.toolspeed, target = src)) + to_chat(user, "You unfasten the frame.") + anchored = 0 + state = 0 + if(istype(P, /obj/item/circuitboard) && !circuit) + var/obj/item/circuitboard/B = P + if(B.board_type == "honkcomputer") + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You place the circuit board inside the frame.") + icon_state = "1" + circuit = P + user.drop_item() + P.loc = src + else + to_chat(user, "This frame does not accept circuit boards of this type!") + if(istype(P, /obj/item/screwdriver) && circuit) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You screw the circuit board into place.") + state = 2 + icon_state = "2" + if(istype(P, /obj/item/crowbar) && circuit) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You remove the circuit board.") + state = 1 + icon_state = "0" + circuit.loc = loc + circuit = null + return + if(2) + if(istype(P, /obj/item/screwdriver) && circuit) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You unfasten the circuit board.") + state = 1 + icon_state = "1" + if(istype(P, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = P + if(C.amount >= 5) + playsound(loc, C.usesound, 50, 1) + to_chat(user, "You start to add cables to the frame.") + if(do_after(user, 20 * C.toolspeed, target = src)) + if(state == 2 && C.amount >= 5 && C.use(5)) + to_chat(user, "You add cables to the frame.") + state = 3 + icon_state = "3" + else + to_chat(user, "At some point during construction you lost some cable. Make sure you have five lengths before trying again.") + return + else + to_chat(user, "You need five lengths of cable to wire the frame.") + return + if(3) + if(istype(P, /obj/item/wirecutters)) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You remove the cables.") + state = 2 + icon_state = "2" + var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil( loc ) + A.amount = 5 + + if(istype(P, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = P + if(G.amount >= 2) + playsound(loc, G.usesound, 50, 1) + to_chat(user, "You start to add the glass panel to the frame.") + if(do_after(user, 20 * G.toolspeed, target = src)) + if(state == 3 && G.amount >= 2 && G.use(2)) + to_chat(user, "You put in the glass panel.") + state = 4 + icon_state = "4" + else + to_chat(user, "At some point during construction you lost some glass. Make sure you have two sheets before trying again.") + return + else + to_chat(user, "You need two sheets of glass for this.") + return + if(4) + if(istype(P, /obj/item/crowbar)) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You remove the glass panel.") + state = 3 + icon_state = "3" + new /obj/item/stack/sheet/glass(loc, 2) + if(istype(P, /obj/item/screwdriver)) + playsound(loc, P.usesound, 50, 1) + to_chat(user, "You connect the monitor.") + var/B = new circuit.build_path (loc) + if(circuit.powernet) B:powernet = circuit.powernet + if(circuit.id) B:id = circuit.id + if(circuit.records) B:records = circuit.records + if(circuit.frequency) B:frequency = circuit.frequency + qdel(src) + return + return ..() diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 6cf5f77a7f2..f67158f82b0 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -1,356 +1,356 @@ -/obj/machinery/computer/security - name = "security camera console" - desc = "Used to access the various cameras networks on the station." - - icon_keyboard = "security_key" - icon_screen = "cameras" - light_color = LIGHT_COLOR_RED - circuit = /obj/item/circuitboard/camera - - var/mapping = 0 // For the overview file (overview.dm), not used on this page - - var/list/network = list() - var/list/available_networks = list() - var/list/watchers = list() //who's using the console, associated with the camera they're on. - -/obj/machinery/computer/security/New() // Lists existing networks and their required access. Format: available_networks[] = list() - generate_network_access() - ..() - -/obj/machinery/computer/security/proc/generate_network_access() - available_networks["SS13"] = list(ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Telecomms"] = list(ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Research Outpost"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Mining Outpost"] = list(ACCESS_QM,ACCESS_HOP,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Research"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Prison"] = list(ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Labor Camp"] = list(ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Interrogation"] = list(ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Atmosphere Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Fire Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Power Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Supermatter"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["MiniSat"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Singularity"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Anomaly Isolation"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Toxins"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["Telepad"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["TestChamber"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) - available_networks["ERT"] = list(ACCESS_CENT_SPECOPS_COMMANDER,ACCESS_CENT_COMMANDER) - available_networks["CentComm"] = list(ACCESS_CENT_SECURITY,ACCESS_CENT_COMMANDER) - available_networks["Thunderdome"] = list(ACCESS_CENT_THUNDER,ACCESS_CENT_COMMANDER) - -/obj/machinery/computer/security/Destroy() - if(watchers.len) - for(var/mob/M in watchers) - M.unset_machine() //to properly reset the view of the users if the console is deleted. - return ..() - -/obj/machinery/computer/security/proc/isCameraFarAway(obj/machinery/camera/C) - var/turf/consoleturf = get_turf(src) - var/turf/cameraturf = get_turf(C) - if((is_away_level(cameraturf.z) || is_away_level(consoleturf.z)) && !atoms_share_level(cameraturf, consoleturf)) //can only recieve away mission cameras on away missions - return TRUE - -/obj/machinery/computer/security/check_eye(mob/user) - if(!(user in watchers)) - user.unset_machine() - return - if(!watchers[user]) - user.unset_machine() - return - var/obj/machinery/camera/C = watchers[user] - if(isCameraFarAway(C)) - user.unset_machine() - return - if(!can_access_camera(C, user)) - user.unset_machine() - return - return 1 - -/obj/machinery/computer/security/on_unset_machine(mob/user) - watchers.Remove(user) - user.reset_perspective(null) - -/obj/machinery/computer/security/attack_hand(mob/user) - if(stat || ..()) - user.unset_machine() - return - - ui_interact(user) - -/obj/machinery/computer/security/telescreen/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - var/direction = input(user, "Which direction?", "Select direction!") as null|anything in list("North", "East", "South", "West", "Centre") - if(!direction || !Adjacent(user)) - return - pixel_x = 0 - pixel_y = 0 - switch(direction) - if("North") - pixel_y = 32 - if("East") - pixel_x = 32 - if("South") - pixel_y = -32 - if("West") - pixel_x = -32 - -/obj/machinery/computer/security/emag_act(user as mob) - if(!emagged) - emagged = 1 - to_chat(user, "You have authorized full network access!") - attack_hand(user) - else - attack_hand(user) - -/obj/machinery/computer/security/proc/get_user_access(mob/user) - var/list/access = list() - - if(emagged) - access = get_all_accesses() // Assume captain level access when emagged - else if(ishuman(user)) - access = user.get_access() - else if((isAI(user) || isrobot(user)) && CanUseTopic(user, default_state) == STATUS_INTERACTIVE) - access = get_all_accesses() // Assume captain level access when AI - else if(user.can_admin_interact()) - access = get_all_accesses() - return access - -/obj/machinery/computer/security/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "sec_camera.tmpl", "Camera Console", 900, 800) - - // adding a template with the key "mapContent" enables the map ui functionality - ui.add_template("mapContent", "sec_camera_map_content.tmpl") - // adding a template with the key "mapHeader" replaces the map header content - ui.add_template("mapHeader", "sec_camera_map_header.tmpl") - - ui.open() - -/obj/machinery/computer/security/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - var/list/cameras = list() - for(var/obj/machinery/camera/C in cameranet.cameras) - if(isCameraFarAway(C)) - continue - if(!can_access_camera(C, user)) - continue - - cameras[++cameras.len] = C.nano_structure() - - for(var/i = cameras.len, i > 0, i--) //based off /proc/camera_sort, sorts cameras alphabetically for the UI - for(var/j = 1 to i - 1) - var/a = cameras[j] - var/b = cameras[j + 1] - if(sorttext(a["name"], b["name"]) < 0) - cameras.Swap(j, j + 1) - - data["cameras"] = cameras - - var/list/access = get_user_access(user) - if(emagged) - data["emagged"] = 1 - - var/list/networks_list = list() - // Loop through the ID's permission, and check which networks the ID has access to. - for(var/net in available_networks) // Loop through networks. - for(var/req in available_networks[net]) // Loop through access levels of the networks. - if(req in access) - if(net in network) // Checks if the network is currently active. - networks_list.Add(list(list("name" = net, "active" = 1))) - else - networks_list.Add(list(list("name" = net, "active" = 0))) - break - - if(networks_list.len) - data["networks"] = networks_list - - data["current"] = null - if(watchers[user]) - var/obj/machinery/camera/watched = watchers[user] - data["current"] = watched.nano_structure() - - return data - -/obj/machinery/computer/security/Topic(href, href_list) - if(..()) - usr.unset_machine() - return 1 - - if(href_list["switchTo"]) - var/obj/machinery/camera/C = locate(href_list["switchTo"]) in cameranet.cameras - if(!C) - return 1 - - switch_to_camera(usr, C) - - else if(href_list["reset"]) - usr.unset_machine() - - else if(href_list["activate"]) // Activate: enable or disable networks - var/net = href_list["activate"] // Network to be enabled or disabled. - var/active = href_list["active"] // Is the network currently active. - var/list/access = get_user_access(usr) - for(var/a in available_networks[net]) - if(a in access) // Re-check for authorization. - if(text2num(active) == 1) - network -= net - break - else - network += net - break - - SSnanoui.update_uis(src) - -// Check if camera is accessible when jumping -/obj/machinery/computer/security/proc/can_access_camera(var/obj/machinery/camera/C, var/mob/M) - if(CanUseTopic(M, default_state) != STATUS_INTERACTIVE || M.incapacitated() || !M.has_vision()) - return 0 - - if(isrobot(M)) - var/list/viewing = viewers(src) - if(!viewing.Find(M)) - return 0 - - if(isAI(M)) - var/mob/living/silicon/ai/A = M - if(!A.is_in_chassis()) - return 0 - - if(!issilicon(M) && !Adjacent(M)) - return 0 - - var/list/shared_networks = network & C.network - if(!shared_networks.len || !C.can_use()) - return 0 - - return 1 - -// Switching to cameras -/obj/machinery/computer/security/proc/switch_to_camera(var/mob/user, var/obj/machinery/camera/C) - if(!can_access_camera(C, user)) - user.unset_machine() - return 1 - - if(isAI(user)) - var/mob/living/silicon/ai/A = user - A.eyeobj.setLoc(get_turf(C)) - A.client.eye = A.eyeobj - else - user.reset_perspective(C) - watchers[user] = C - use_power(50) - -//Camera control: moving. -/obj/machinery/computer/security/proc/jump_on_click(var/mob/user, var/A) - if(user.machine != src) - return - - var/obj/machinery/camera/jump_to - - if(istype(A, /obj/machinery/camera)) - jump_to = A - - else if(ismob(A)) - if(ishuman(A)) - var/mob/living/carbon/human/H = A - jump_to = locate() in H.head - else if(isrobot(A)) - var/mob/living/silicon/robot/R = A - jump_to = R.camera - - else if(isobj(A)) - var/obj/O = A - jump_to = locate() in O - - else if(isturf(A)) - var/best_dist = INFINITY - for(var/obj/machinery/camera/camera in get_area(A)) - if(!camera.can_use()) - continue - if(!can_access_camera(camera, user)) - continue - var/dist = get_dist(camera,A) - if(dist < best_dist) - best_dist = dist - jump_to = camera - - if(isnull(jump_to)) - return - - if(can_access_camera(jump_to, user)) - switch_to_camera(user, jump_to) - -// Camera control: mouse. -/atom/DblClick() - ..() - if(istype(usr.machine, /obj/machinery/computer/security)) - var/obj/machinery/computer/security/console = usr.machine - console.jump_on_click(usr, src) - -// Camera control: arrow keys. -/mob/Move(n, direct) - if(istype(machine, /obj/machinery/computer/security)) - var/obj/machinery/computer/security/console = machine - var/turf/T = get_turf(console.watchers[src]) - for(var/i; i < 10; i++) - T = get_step(T, direct) - console.jump_on_click(src, T) - return - return ..(n,direct) - -// Other computer monitors. -/obj/machinery/computer/security/telescreen - name = "telescreen" - desc = "Used for watching camera networks." - icon_state = "telescreen_console" - icon_screen = "telescreen" - icon_keyboard = null - light_range_on = 0 - density = 0 - circuit = /obj/item/circuitboard/camera/telescreen - -/obj/machinery/computer/security/telescreen/entertainment - name = "entertainment monitor" - desc = "Damn, they better have Paradise TV on these things." - icon_state = "entertainment_console" - icon_screen = "entertainment" - light_color = "#FFEEDB" - light_range_on = 0 - network = list("news") - luminosity = 0 - circuit = /obj/item/circuitboard/camera/telescreen/entertainment - -/obj/machinery/computer/security/wooden_tv - name = "security camera monitor" - desc = "An old TV hooked into the station's camera network." - icon_state = "television" - icon_keyboard = null - icon_screen = "detective_tv" - light_color = "#3848B3" - light_power_on = 0.5 - network = list("SS13") - circuit = /obj/item/circuitboard/camera/wooden_tv - -/obj/machinery/computer/security/mining - name = "outpost camera monitor" - desc = "Used to access the various cameras on the outpost." - icon_keyboard = "mining_key" - icon_screen = "mining" - light_color = "#F9BBFC" - network = list("Mining Outpost") - circuit = /obj/item/circuitboard/camera/mining - -/obj/machinery/computer/security/engineering - name = "engineering camera monitor" - desc = "Used to monitor fires and breaches." - icon_keyboard = "power_key" - icon_screen = "engie_cams" - light_color = "#FAC54B" - network = list("Power Alarms","Atmosphere Alarms","Fire Alarms") - circuit = /obj/item/circuitboard/camera/engineering +/obj/machinery/computer/security + name = "security camera console" + desc = "Used to access the various cameras networks on the station." + + icon_keyboard = "security_key" + icon_screen = "cameras" + light_color = LIGHT_COLOR_RED + circuit = /obj/item/circuitboard/camera + + var/mapping = 0 // For the overview file (overview.dm), not used on this page + + var/list/network = list() + var/list/available_networks = list() + var/list/watchers = list() //who's using the console, associated with the camera they're on. + +/obj/machinery/computer/security/New() // Lists existing networks and their required access. Format: available_networks[] = list() + generate_network_access() + ..() + +/obj/machinery/computer/security/proc/generate_network_access() + available_networks["SS13"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Telecomms"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Research Outpost"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Mining Outpost"] = list(ACCESS_QM,ACCESS_HOP,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Research"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Prison"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Labor Camp"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Interrogation"] = list(ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Atmosphere Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Fire Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Power Alarms"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Supermatter"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["MiniSat"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Singularity"] = list(ACCESS_CE,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Anomaly Isolation"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Toxins"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["Telepad"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["TestChamber"] = list(ACCESS_RD,ACCESS_HOS,ACCESS_CAPTAIN) + available_networks["ERT"] = list(ACCESS_CENT_SPECOPS_COMMANDER,ACCESS_CENT_COMMANDER) + available_networks["CentComm"] = list(ACCESS_CENT_SECURITY,ACCESS_CENT_COMMANDER) + available_networks["Thunderdome"] = list(ACCESS_CENT_THUNDER,ACCESS_CENT_COMMANDER) + +/obj/machinery/computer/security/Destroy() + if(watchers.len) + for(var/mob/M in watchers) + M.unset_machine() //to properly reset the view of the users if the console is deleted. + return ..() + +/obj/machinery/computer/security/proc/isCameraFarAway(obj/machinery/camera/C) + var/turf/consoleturf = get_turf(src) + var/turf/cameraturf = get_turf(C) + if((is_away_level(cameraturf.z) || is_away_level(consoleturf.z)) && !atoms_share_level(cameraturf, consoleturf)) //can only recieve away mission cameras on away missions + return TRUE + +/obj/machinery/computer/security/check_eye(mob/user) + if(!(user in watchers)) + user.unset_machine() + return + if(!watchers[user]) + user.unset_machine() + return + var/obj/machinery/camera/C = watchers[user] + if(isCameraFarAway(C)) + user.unset_machine() + return + if(!can_access_camera(C, user)) + user.unset_machine() + return + return 1 + +/obj/machinery/computer/security/on_unset_machine(mob/user) + watchers.Remove(user) + user.reset_perspective(null) + +/obj/machinery/computer/security/attack_hand(mob/user) + if(stat || ..()) + user.unset_machine() + return + + ui_interact(user) + +/obj/machinery/computer/security/telescreen/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + var/direction = input(user, "Which direction?", "Select direction!") as null|anything in list("North", "East", "South", "West", "Centre") + if(!direction || !Adjacent(user)) + return + pixel_x = 0 + pixel_y = 0 + switch(direction) + if("North") + pixel_y = 32 + if("East") + pixel_x = 32 + if("South") + pixel_y = -32 + if("West") + pixel_x = -32 + +/obj/machinery/computer/security/emag_act(user as mob) + if(!emagged) + emagged = 1 + to_chat(user, "You have authorized full network access!") + attack_hand(user) + else + attack_hand(user) + +/obj/machinery/computer/security/proc/get_user_access(mob/user) + var/list/access = list() + + if(emagged) + access = get_all_accesses() // Assume captain level access when emagged + else if(ishuman(user)) + access = user.get_access() + else if((isAI(user) || isrobot(user)) && CanUseTopic(user, default_state) == STATUS_INTERACTIVE) + access = get_all_accesses() // Assume captain level access when AI + else if(user.can_admin_interact()) + access = get_all_accesses() + return access + +/obj/machinery/computer/security/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "sec_camera.tmpl", "Camera Console", 900, 800) + + // adding a template with the key "mapContent" enables the map ui functionality + ui.add_template("mapContent", "sec_camera_map_content.tmpl") + // adding a template with the key "mapHeader" replaces the map header content + ui.add_template("mapHeader", "sec_camera_map_header.tmpl") + + ui.open() + +/obj/machinery/computer/security/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + var/list/cameras = list() + for(var/obj/machinery/camera/C in cameranet.cameras) + if(isCameraFarAway(C)) + continue + if(!can_access_camera(C, user)) + continue + + cameras[++cameras.len] = C.nano_structure() + + for(var/i = cameras.len, i > 0, i--) //based off /proc/camera_sort, sorts cameras alphabetically for the UI + for(var/j = 1 to i - 1) + var/a = cameras[j] + var/b = cameras[j + 1] + if(sorttext(a["name"], b["name"]) < 0) + cameras.Swap(j, j + 1) + + data["cameras"] = cameras + + var/list/access = get_user_access(user) + if(emagged) + data["emagged"] = 1 + + var/list/networks_list = list() + // Loop through the ID's permission, and check which networks the ID has access to. + for(var/net in available_networks) // Loop through networks. + for(var/req in available_networks[net]) // Loop through access levels of the networks. + if(req in access) + if(net in network) // Checks if the network is currently active. + networks_list.Add(list(list("name" = net, "active" = 1))) + else + networks_list.Add(list(list("name" = net, "active" = 0))) + break + + if(networks_list.len) + data["networks"] = networks_list + + data["current"] = null + if(watchers[user]) + var/obj/machinery/camera/watched = watchers[user] + data["current"] = watched.nano_structure() + + return data + +/obj/machinery/computer/security/Topic(href, href_list) + if(..()) + usr.unset_machine() + return 1 + + if(href_list["switchTo"]) + var/obj/machinery/camera/C = locate(href_list["switchTo"]) in cameranet.cameras + if(!C) + return 1 + + switch_to_camera(usr, C) + + else if(href_list["reset"]) + usr.unset_machine() + + else if(href_list["activate"]) // Activate: enable or disable networks + var/net = href_list["activate"] // Network to be enabled or disabled. + var/active = href_list["active"] // Is the network currently active. + var/list/access = get_user_access(usr) + for(var/a in available_networks[net]) + if(a in access) // Re-check for authorization. + if(text2num(active) == 1) + network -= net + break + else + network += net + break + + SSnanoui.update_uis(src) + +// Check if camera is accessible when jumping +/obj/machinery/computer/security/proc/can_access_camera(var/obj/machinery/camera/C, var/mob/M) + if(CanUseTopic(M, default_state) != STATUS_INTERACTIVE || M.incapacitated() || !M.has_vision()) + return 0 + + if(isrobot(M)) + var/list/viewing = viewers(src) + if(!viewing.Find(M)) + return 0 + + if(isAI(M)) + var/mob/living/silicon/ai/A = M + if(!A.is_in_chassis()) + return 0 + + if(!issilicon(M) && !Adjacent(M)) + return 0 + + var/list/shared_networks = network & C.network + if(!shared_networks.len || !C.can_use()) + return 0 + + return 1 + +// Switching to cameras +/obj/machinery/computer/security/proc/switch_to_camera(var/mob/user, var/obj/machinery/camera/C) + if(!can_access_camera(C, user)) + user.unset_machine() + return 1 + + if(isAI(user)) + var/mob/living/silicon/ai/A = user + A.eyeobj.setLoc(get_turf(C)) + A.client.eye = A.eyeobj + else + user.reset_perspective(C) + watchers[user] = C + use_power(50) + +//Camera control: moving. +/obj/machinery/computer/security/proc/jump_on_click(var/mob/user, var/A) + if(user.machine != src) + return + + var/obj/machinery/camera/jump_to + + if(istype(A, /obj/machinery/camera)) + jump_to = A + + else if(ismob(A)) + if(ishuman(A)) + var/mob/living/carbon/human/H = A + jump_to = locate() in H.head + else if(isrobot(A)) + var/mob/living/silicon/robot/R = A + jump_to = R.camera + + else if(isobj(A)) + var/obj/O = A + jump_to = locate() in O + + else if(isturf(A)) + var/best_dist = INFINITY + for(var/obj/machinery/camera/camera in get_area(A)) + if(!camera.can_use()) + continue + if(!can_access_camera(camera, user)) + continue + var/dist = get_dist(camera,A) + if(dist < best_dist) + best_dist = dist + jump_to = camera + + if(isnull(jump_to)) + return + + if(can_access_camera(jump_to, user)) + switch_to_camera(user, jump_to) + +// Camera control: mouse. +/atom/DblClick() + ..() + if(istype(usr.machine, /obj/machinery/computer/security)) + var/obj/machinery/computer/security/console = usr.machine + console.jump_on_click(usr, src) + +// Camera control: arrow keys. +/mob/Move(n, direct) + if(istype(machine, /obj/machinery/computer/security)) + var/obj/machinery/computer/security/console = machine + var/turf/T = get_turf(console.watchers[src]) + for(var/i; i < 10; i++) + T = get_step(T, direct) + console.jump_on_click(src, T) + return + return ..(n,direct) + +// Other computer monitors. +/obj/machinery/computer/security/telescreen + name = "telescreen" + desc = "Used for watching camera networks." + icon_state = "telescreen_console" + icon_screen = "telescreen" + icon_keyboard = null + light_range_on = 0 + density = 0 + circuit = /obj/item/circuitboard/camera/telescreen + +/obj/machinery/computer/security/telescreen/entertainment + name = "entertainment monitor" + desc = "Damn, they better have Paradise TV on these things." + icon_state = "entertainment_console" + icon_screen = "entertainment" + light_color = "#FFEEDB" + light_range_on = 0 + network = list("news") + luminosity = 0 + circuit = /obj/item/circuitboard/camera/telescreen/entertainment + +/obj/machinery/computer/security/wooden_tv + name = "security camera monitor" + desc = "An old TV hooked into the station's camera network." + icon_state = "television" + icon_keyboard = null + icon_screen = "detective_tv" + light_color = "#3848B3" + light_power_on = 0.5 + network = list("SS13") + circuit = /obj/item/circuitboard/camera/wooden_tv + +/obj/machinery/computer/security/mining + name = "outpost camera monitor" + desc = "Used to access the various cameras on the outpost." + icon_keyboard = "mining_key" + icon_screen = "mining" + light_color = "#F9BBFC" + network = list("Mining Outpost") + circuit = /obj/item/circuitboard/camera/mining + +/obj/machinery/computer/security/engineering + name = "engineering camera monitor" + desc = "Used to monitor fires and breaches." + icon_keyboard = "power_key" + icon_screen = "engie_cams" + light_color = "#FAC54B" + network = list("Power Alarms","Atmosphere Alarms","Fire Alarms") + circuit = /obj/item/circuitboard/camera/engineering diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index 7ffc141dcb4..07ab3f5d62b 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -639,4 +639,4 @@ var/time_last_changed_position = 0 icon_screen = "idce" light_color = COLOR_YELLOW req_access = list(ACCESS_CE) - circuit = /obj/item/circuitboard/card/minor/ce \ No newline at end of file + circuit = /obj/item/circuitboard/card/minor/ce diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 0a9627fa38b..3b663bbe549 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -1,569 +1,569 @@ -#define COMM_SCREEN_MAIN 1 -#define COMM_SCREEN_STAT 2 -#define COMM_SCREEN_MESSAGES 3 -#define COMM_SCREEN_SECLEVEL 4 - -#define COMM_AUTHENTICATION_NONE 0 -#define COMM_AUTHENTICATION_MIN 1 -#define COMM_AUTHENTICATION_MAX 2 - -// The communications computer -/obj/machinery/computer/communications - name = "communications console" - desc = "This can be used for various important functions. Still under developement." - icon_keyboard = "tech_key" - icon_screen = "comm" - req_access = list(ACCESS_HEADS) - circuit = /obj/item/circuitboard/communications - var/prints_intercept = 1 - var/authenticated = COMM_AUTHENTICATION_NONE - var/list/messagetitle = list() - var/list/messagetext = list() - var/currmsg = 0 - var/aicurrmsg = 0 - var/menu_state = COMM_SCREEN_MAIN - var/ai_menu_state = COMM_SCREEN_MAIN - var/message_cooldown = 0 - var/centcomm_message_cooldown = 0 - var/tmp_alertlevel = 0 - - var/stat_msg1 - var/stat_msg2 - var/display_type="blank" - - var/datum/announcement/priority/crew_announcement = new - - light_color = LIGHT_COLOR_LIGHTBLUE - -/obj/machinery/computer/communications/New() - GLOB.shuttle_caller_list += src - ..() - crew_announcement.newscast = 0 - -/obj/machinery/computer/communications/proc/is_authenticated(var/mob/user, var/message = 1) - if(authenticated == COMM_AUTHENTICATION_MAX) - return COMM_AUTHENTICATION_MAX - else if(user.can_admin_interact()) - return COMM_AUTHENTICATION_MAX - else if(authenticated) - return COMM_AUTHENTICATION_MIN - else - if(message) - to_chat(user, "Access denied.") - return COMM_AUTHENTICATION_NONE - -/obj/machinery/computer/communications/proc/change_security_level(var/new_level) - tmp_alertlevel = new_level - var/old_level = security_level - if(!tmp_alertlevel) tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel < SEC_LEVEL_GREEN) tmp_alertlevel = SEC_LEVEL_GREEN - if(tmp_alertlevel > SEC_LEVEL_BLUE) tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this - set_security_level(tmp_alertlevel) - if(security_level != old_level) - //Only notify the admins if an actual change happened - log_game("[key_name(usr)] has changed the security level to [get_security_level()].") - message_admins("[key_name_admin(usr)] has changed the security level to [get_security_level()].") - switch(security_level) - if(SEC_LEVEL_GREEN) - feedback_inc("alert_comms_green",1) - if(SEC_LEVEL_BLUE) - feedback_inc("alert_comms_blue",1) - tmp_alertlevel = 0 - -/obj/machinery/computer/communications/Topic(href, href_list) - if(..(href, href_list)) - return 1 - - if(!is_secure_level(src.z)) - to_chat(usr, "Unable to establish a connection: You're too far away from the station!") - return 1 - - if(href_list["login"]) - if(!ishuman(usr)) - to_chat(usr, "Access denied.") - return - - var/list/access = usr.get_access() - if(allowed(usr)) - authenticated = COMM_AUTHENTICATION_MIN - - if(ACCESS_CAPTAIN in access) - authenticated = COMM_AUTHENTICATION_MAX - var/mob/living/carbon/human/H = usr - var/obj/item/card/id = H.get_idcard(TRUE) - if(istype(id)) - crew_announcement.announcer = GetNameAndAssignmentFromId(id) - - SSnanoui.update_uis(src) - return - - if(href_list["logout"]) - authenticated = COMM_AUTHENTICATION_NONE - crew_announcement.announcer = "" - setMenuState(usr,COMM_SCREEN_MAIN) - SSnanoui.update_uis(src) - return - - if(!is_authenticated(usr)) - return 1 - - switch(href_list["operation"]) - if("main") - setMenuState(usr,COMM_SCREEN_MAIN) - - if("changeseclevel") - setMenuState(usr,COMM_SCREEN_SECLEVEL) - - if("newalertlevel") - if(isAI(usr) || isrobot(usr)) - to_chat(usr, "Firewalls prevent you from changing the alert level.") - return 1 - else if(usr.can_admin_interact()) - change_security_level(text2num(href_list["level"])) - return 1 - else if(!ishuman(usr)) - to_chat(usr, "Security measures prevent you from changing the alert level.") - return 1 - - var/mob/living/carbon/human/L = usr - var/obj/item/card = L.get_active_hand() - var/obj/item/card/id/I = (card && card.GetID()) || L.wear_id || L.wear_pda - if(istype(I, /obj/item/pda)) - var/obj/item/pda/pda = I - I = pda.id - if(I && istype(I)) - if(ACCESS_CAPTAIN in I.access) - change_security_level(text2num(href_list["level"])) - else - to_chat(usr, "You are not authorized to do this.") - setMenuState(usr,COMM_SCREEN_MAIN) - else - to_chat(usr, "You need to swipe your ID.") - - if("announce") - if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX) - if(message_cooldown) - to_chat(usr, "Please allow at least one minute to pass between announcements.") - SSnanoui.update_uis(src) - return - var/input = input(usr, "Please write a message to announce to the station crew.", "Priority Announcement") - if(!input || message_cooldown || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)) - SSnanoui.update_uis(src) - return - crew_announcement.Announce(input) - message_cooldown = 1 - spawn(600)//One minute cooldown - message_cooldown = 0 - - if("callshuttle") - var/input = clean_input("Please enter the reason for calling the shuttle.", "Shuttle Call Reason.","") - if(!input || ..() || !is_authenticated(usr)) - SSnanoui.update_uis(src) - return - - call_shuttle_proc(usr, input) - if(SSshuttle.emergency.timer) - post_status("shuttle") - setMenuState(usr,COMM_SCREEN_MAIN) - - if("cancelshuttle") - if(isAI(usr) || isrobot(usr)) - to_chat(usr, "Firewalls prevent you from recalling the shuttle.") - SSnanoui.update_uis(src) - return 1 - var/response = alert("Are you sure you wish to recall the shuttle?", "Confirm", "Yes", "No") - if(response == "Yes") - cancel_call_proc(usr) - if(SSshuttle.emergency.timer) - post_status("shuttle") - setMenuState(usr,COMM_SCREEN_MAIN) - - if("messagelist") - currmsg = 0 - if(href_list["msgid"]) - setCurrentMessage(usr, text2num(href_list["msgid"])) - setMenuState(usr,COMM_SCREEN_MESSAGES) - - if("delmessage") - if(href_list["msgid"]) - currmsg = text2num(href_list["msgid"]) - var/response = alert("Are you sure you wish to delete this message?", "Confirm", "Yes", "No") - if(response == "Yes") - if(currmsg) - var/id = getCurrentMessage() - var/title = messagetitle[id] - var/text = messagetext[id] - messagetitle.Remove(title) - messagetext.Remove(text) - if(currmsg == id) - currmsg = 0 - if(aicurrmsg == id) - aicurrmsg = 0 - setMenuState(usr,COMM_SCREEN_MESSAGES) - - if("status") - setMenuState(usr,COMM_SCREEN_STAT) - - // Status display stuff - if("setstat") - display_type=href_list["statdisp"] - switch(display_type) - if("message") - post_status("message", stat_msg1, stat_msg2, usr) - if("alert") - post_status("alert", href_list["alert"], user = usr) - else - post_status(href_list["statdisp"], user = usr) - setMenuState(usr,COMM_SCREEN_STAT) - - if("setmsg1") - stat_msg1 = clean_input("Line 1", "Enter Message Text", stat_msg1) - setMenuState(usr,COMM_SCREEN_STAT) - - if("setmsg2") - stat_msg2 = clean_input("Line 2", "Enter Message Text", stat_msg2) - setMenuState(usr,COMM_SCREEN_STAT) - - if("nukerequest") - if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX) - if(centcomm_message_cooldown) - to_chat(usr, "Arrays recycling. Please stand by.") - SSnanoui.update_uis(src) - return - var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self Destruct Code Request.","") - if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)) - SSnanoui.update_uis(src) - return - Nuke_request(input, usr) - to_chat(usr, "Request sent.") - log_game("[key_name(usr)] has requested the nuclear codes from Centcomm") - priority_announcement.Announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested",'sound/AI/commandreport.ogg') - centcomm_message_cooldown = 1 - spawn(6000)//10 minute cooldown - centcomm_message_cooldown = 0 - setMenuState(usr,COMM_SCREEN_MAIN) - - if("MessageCentcomm") - if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX) - if(centcomm_message_cooldown) - to_chat(usr, "Arrays recycling. Please stand by.") - SSnanoui.update_uis(src) - return - var/input = stripped_input(usr, "Please choose a message to transmit to Centcomm via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "To abort, send an empty message.", "") - if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)) - SSnanoui.update_uis(src) - return - Centcomm_announce(input, usr) - print_centcom_report(input, station_time_timestamp() + " Captain's Message") - to_chat(usr, "Message transmitted.") - log_game("[key_name(usr)] has made a Centcomm announcement: [input]") - centcomm_message_cooldown = 1 - spawn(6000)//10 minute cooldown - centcomm_message_cooldown = 0 - setMenuState(usr,COMM_SCREEN_MAIN) - - // OMG SYNDICATE ...LETTERHEAD - if("MessageSyndicate") - if((is_authenticated(usr) == COMM_AUTHENTICATION_MAX) && (src.emagged)) - if(centcomm_message_cooldown) - to_chat(usr, "Arrays recycling. Please stand by.") - SSnanoui.update_uis(src) - return - var/input = stripped_input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING CORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "To abort, send an empty message.", "") - if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)) - SSnanoui.update_uis(src) - return - Syndicate_announce(input, usr) - to_chat(usr, "Message transmitted.") - log_game("[key_name(usr)] has made a Syndicate announcement: [input]") - centcomm_message_cooldown = 1 - spawn(6000)//10 minute cooldown - centcomm_message_cooldown = 0 - setMenuState(usr,COMM_SCREEN_MAIN) - - if("RestoreBackup") - to_chat(usr, "Backup routing data restored!") - src.emagged = 0 - setMenuState(usr,COMM_SCREEN_MAIN) - - if("RestartNanoMob") - if(SSmob_hunt) - if(SSmob_hunt.manual_reboot()) - var/loading_msg = pick("Respawning spawns", "Reticulating splines", "Flipping hat", - "Capturing all of them", "Fixing minor text issues", "Being the very best", - "Nerfing this", "Not communicating with playerbase", "Coding a ripoff in a 2D spaceman game") - to_chat(usr, "Restarting Nano-Mob Hunter GO! game server. [loading_msg]...") - else - to_chat(usr, "Nano-Mob Hunter GO! game server reboot failed due to recent restart. Please wait before re-attempting.") - else - to_chat(usr, "Nano-Mob Hunter GO! game server is offline for extended maintenance. Contact your Central Command administrators for more info if desired.") - - if("ToggleATC") - atc.squelched = !atc.squelched - to_chat(usr, "ATC traffic is now: [atc.squelched ? "Disabled" : "Enabled"].") - - SSnanoui.update_uis(src) - return 1 - -/obj/machinery/computer/communications/emag_act(user as mob) - if(!emagged) - src.emagged = 1 - to_chat(user, "You scramble the communication routing circuits!") - SSnanoui.update_uis(src) - -/obj/machinery/computer/communications/attack_ai(var/mob/user as mob) - return src.attack_hand(user) - -/obj/machinery/computer/communications/attack_hand(var/mob/user as mob) - if(..(user)) - return - - if(stat & (NOPOWER|BROKEN)) - return - - if(!is_secure_level(src.z)) - to_chat(user, "Unable to establish a connection: You're too far away from the station!") - return - - ui_interact(user) - -/obj/machinery/computer/communications/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null) - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "comm_console.tmpl", "Communications Console", 400, 500) - // open the new ui window - ui.open() - -/obj/machinery/computer/communications/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - data["is_ai"] = isAI(user) || isrobot(user) - data["menu_state"] = data["is_ai"] ? ai_menu_state : menu_state - data["emagged"] = emagged - data["authenticated"] = is_authenticated(user, 0) - data["screen"] = getMenuState(usr) - - data["stat_display"] = list( - "type" = display_type, - "line_1" = (stat_msg1 ? stat_msg1 : "-----"), - "line_2" = (stat_msg2 ? stat_msg2 : "-----"), - - "presets" = list( - list("name" = "blank", "label" = "Clear", "desc" = "Blank slate"), - list("name" = "shuttle", "label" = "Shuttle ETA", "desc" = "Display how much time is left."), - list("name" = "message", "label" = "Message", "desc" = "A custom message.") - ), - - "alerts"=list( - list("alert" = "default", "label" = "Nanotrasen", "desc" = "Oh god."), - list("alert" = "redalert", "label" = "Red Alert", "desc" = "Nothing to do with communists."), - list("alert" = "lockdown", "label" = "Lockdown", "desc" = "Let everyone know they're on lockdown."), - list("alert" = "biohazard", "label" = "Biohazard", "desc" = "Great for virus outbreaks and parties."), - ) - ) - - data["security_level"] = security_level - data["str_security_level"] = capitalize(get_security_level()) - data["levels"] = list( - list("id" = SEC_LEVEL_GREEN, "name" = "Green"), - list("id" = SEC_LEVEL_BLUE, "name" = "Blue"), - //SEC_LEVEL_RED = list("name"="Red"), - ) - - var/list/msg_data = list() - for(var/i = 1; i <= messagetext.len; i++) - msg_data.Add(list(list("title" = messagetitle[i], "body" = messagetext[i], "id" = i))) - - data["messages"] = msg_data - if((data["is_ai"] && aicurrmsg) || (!data["is_ai"] && currmsg)) - data["current_message"] = data["is_ai"] ? messagetext[aicurrmsg] : messagetext[currmsg] - data["current_message_title"] = data["is_ai"] ? messagetitle[aicurrmsg] : messagetitle[currmsg] - - data["lastCallLoc"] = SSshuttle.emergencyLastCallLoc ? format_text(SSshuttle.emergencyLastCallLoc.name) : null - - var/shuttle[0] - switch(SSshuttle.emergency.mode) - if(SHUTTLE_IDLE, SHUTTLE_RECALL) - shuttle["callStatus"] = 2 //#define - else - shuttle["callStatus"] = 1 - if(SSshuttle.emergency.mode == SHUTTLE_CALL) - var/timeleft = SSshuttle.emergency.timeLeft() - shuttle["eta"] = "[timeleft / 60 % 60]:[add_zero(num2text(timeleft % 60), 2)]" - - data["shuttle"] = shuttle - - data["atcSquelched"] = atc.squelched - - return data - - -/obj/machinery/computer/communications/proc/setCurrentMessage(var/mob/user,var/value) - if(isAI(user) || isrobot(user)) - aicurrmsg = value - else - currmsg = value - -/obj/machinery/computer/communications/proc/getCurrentMessage(var/mob/user) - if(isAI(user) || isrobot(user)) - return aicurrmsg - else - return currmsg - -/obj/machinery/computer/communications/proc/setMenuState(var/mob/user,var/value) - if(isAI(user) || isrobot(user)) - ai_menu_state=value - else - menu_state=value - -/obj/machinery/computer/communications/proc/getMenuState(var/mob/user) - if(isAI(user) || isrobot(user)) - return ai_menu_state - else - return menu_state - -/proc/enable_prison_shuttle(var/mob/user); - -/proc/call_shuttle_proc(var/mob/user, var/reason) - if(sent_strike_team == 1) - to_chat(user, "Central Command will not allow the shuttle to be called. Consider all contracts terminated.") - return - - if(SSshuttle.emergencyNoEscape) - to_chat(user, "The emergency shuttle may not be sent at this time. Please try again later.") - return - - if(SSshuttle.emergency.mode > SHUTTLE_ESCAPE) - to_chat(user, "The emergency shuttle may not be called while returning to Central Command.") - return - - if(SSticker.mode.name == "blob") - to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.") - return - - SSshuttle.requestEvac(user, reason) - log_game("[key_name(user)] has called the shuttle.") - message_admins("[key_name_admin(user)] has called the shuttle.", 1) - - return - -/proc/init_shift_change(var/mob/user, var/force = 0) - // if force is 0, some things may stop the shuttle call - if(!force) - if(SSshuttle.emergencyNoEscape) - to_chat(user, "Central Command does not currently have a shuttle available in your sector. Please try again later.") - return - - if(sent_strike_team == 1) - to_chat(user, "Central Command will not allow the shuttle to be called. Consider all contracts terminated.") - return - - if(world.time < 54000) // 30 minute grace period to let the game get going - to_chat(user, "The shuttle is refueling. Please wait another [round((54000-world.time)/600)] minutes before trying again.") - return - - if(SSticker.mode.name == "epidemic") - to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.") - return - - if(seclevel2num(get_security_level()) >= SEC_LEVEL_RED) // There is a serious threat we gotta move no time to give them five minutes. - SSshuttle.emergency.request(null, 0.5, null, " Automatic Crew Transfer", 1) - SSshuttle.emergency.canRecall = FALSE - else - SSshuttle.emergency.request(null, 1, null, " Automatic Crew Transfer", 0) - SSshuttle.emergency.canRecall = FALSE - if(user) - log_game("[key_name(user)] has called the shuttle.") - message_admins("[key_name_admin(user)] has called the shuttle - [formatJumpTo(user)].", 1) - return - - -/proc/cancel_call_proc(var/mob/user) - if(SSticker.mode.name == "meteor") - return - - if(SSshuttle.cancelEvac(user)) - log_game("[key_name(user)] has recalled the shuttle.") - message_admins("[key_name_admin(user)] has recalled the shuttle - ([ADMIN_FLW(user,"FLW")]).", 1) - else - to_chat(user, "Central Command has refused the recall request!") - log_game("[key_name(user)] has tried and failed to recall the shuttle.") - message_admins("[key_name_admin(user)] has tried and failed to recall the shuttle - ([ADMIN_FLW(user,"FLW")]).", 1) - -/proc/post_status(command, data1, data2, mob/user = null) - - var/datum/radio_frequency/frequency = SSradio.return_frequency(DISPLAY_FREQ) - - if(!frequency) return - - var/datum/signal/status_signal = new - status_signal.source = src - status_signal.transmission_method = 1 - status_signal.data["command"] = command - - switch(command) - if("message") - status_signal.data["msg1"] = data1 - status_signal.data["msg2"] = data2 - log_admin("STATUS: [user] set status screen message with [src]: [data1] [data2]") - //message_admins("STATUS: [user] set status screen with [PDA]. Message: [data1] [data2]") - if("alert") - status_signal.data["picture_state"] = data1 - - spawn(0) - frequency.post_signal(src, status_signal) - - -/obj/machinery/computer/communications/Destroy() - GLOB.shuttle_caller_list -= src - SSshuttle.autoEvac() - return ..() - -/obj/item/circuitboard/communications/New() - GLOB.shuttle_caller_list += src - ..() - -/obj/item/circuitboard/communications/Destroy() - GLOB.shuttle_caller_list -= src - SSshuttle.autoEvac() - return ..() - -/proc/print_command_report(text = "", title = "Central Command Update") - for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list) - if(!(C.stat & (BROKEN|NOPOWER)) && is_station_contact(C.z)) - var/obj/item/paper/P = new /obj/item/paper(C.loc) - P.name = "paper- '[title]'" - P.info = text - P.update_icon() - C.messagetitle.Add("[title]") - C.messagetext.Add(text) - for(var/datum/computer_file/program/comm/P in GLOB.shuttle_caller_list) - var/turf/T = get_turf(P.computer) - if(T && P.program_state != PROGRAM_STATE_KILLED && is_station_contact(T.z)) - if(P.computer) - var/obj/item/computer_hardware/printer/printer = P.computer.all_components[MC_PRINT] - if(printer) - printer.print_text(text, "paper- '[title]'") - P.messagetitle.Add("[title]") - P.messagetext.Add(text) - -/proc/print_centcom_report(text = "", title = "Incoming Message") - for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list) - if(!(C.stat & (BROKEN|NOPOWER)) && is_admin_level(C.z)) - var/obj/item/paper/P = new /obj/item/paper(C.loc) - P.name = "paper- '[title]'" - P.info = text - P.update_icon() - C.messagetitle.Add("[title]") - C.messagetext.Add(text) - for(var/datum/computer_file/program/comm/P in GLOB.shuttle_caller_list) - var/turf/T = get_turf(P.computer) - if(T && P.program_state != PROGRAM_STATE_KILLED && is_admin_level(T.z)) - if(P.computer) - var/obj/item/computer_hardware/printer/printer = P.computer.all_components[MC_PRINT] - if(printer) - printer.print_text(text, "paper- '[title]'") - P.messagetitle.Add("[title]") - P.messagetext.Add(text) \ No newline at end of file +#define COMM_SCREEN_MAIN 1 +#define COMM_SCREEN_STAT 2 +#define COMM_SCREEN_MESSAGES 3 +#define COMM_SCREEN_SECLEVEL 4 + +#define COMM_AUTHENTICATION_NONE 0 +#define COMM_AUTHENTICATION_MIN 1 +#define COMM_AUTHENTICATION_MAX 2 + +// The communications computer +/obj/machinery/computer/communications + name = "communications console" + desc = "This can be used for various important functions. Still under developement." + icon_keyboard = "tech_key" + icon_screen = "comm" + req_access = list(ACCESS_HEADS) + circuit = /obj/item/circuitboard/communications + var/prints_intercept = 1 + var/authenticated = COMM_AUTHENTICATION_NONE + var/list/messagetitle = list() + var/list/messagetext = list() + var/currmsg = 0 + var/aicurrmsg = 0 + var/menu_state = COMM_SCREEN_MAIN + var/ai_menu_state = COMM_SCREEN_MAIN + var/message_cooldown = 0 + var/centcomm_message_cooldown = 0 + var/tmp_alertlevel = 0 + + var/stat_msg1 + var/stat_msg2 + var/display_type="blank" + + var/datum/announcement/priority/crew_announcement = new + + light_color = LIGHT_COLOR_LIGHTBLUE + +/obj/machinery/computer/communications/New() + GLOB.shuttle_caller_list += src + ..() + crew_announcement.newscast = 0 + +/obj/machinery/computer/communications/proc/is_authenticated(var/mob/user, var/message = 1) + if(authenticated == COMM_AUTHENTICATION_MAX) + return COMM_AUTHENTICATION_MAX + else if(user.can_admin_interact()) + return COMM_AUTHENTICATION_MAX + else if(authenticated) + return COMM_AUTHENTICATION_MIN + else + if(message) + to_chat(user, "Access denied.") + return COMM_AUTHENTICATION_NONE + +/obj/machinery/computer/communications/proc/change_security_level(var/new_level) + tmp_alertlevel = new_level + var/old_level = security_level + if(!tmp_alertlevel) tmp_alertlevel = SEC_LEVEL_GREEN + if(tmp_alertlevel < SEC_LEVEL_GREEN) tmp_alertlevel = SEC_LEVEL_GREEN + if(tmp_alertlevel > SEC_LEVEL_BLUE) tmp_alertlevel = SEC_LEVEL_BLUE //Cannot engage delta with this + set_security_level(tmp_alertlevel) + if(security_level != old_level) + //Only notify the admins if an actual change happened + log_game("[key_name(usr)] has changed the security level to [get_security_level()].") + message_admins("[key_name_admin(usr)] has changed the security level to [get_security_level()].") + switch(security_level) + if(SEC_LEVEL_GREEN) + feedback_inc("alert_comms_green",1) + if(SEC_LEVEL_BLUE) + feedback_inc("alert_comms_blue",1) + tmp_alertlevel = 0 + +/obj/machinery/computer/communications/Topic(href, href_list) + if(..(href, href_list)) + return 1 + + if(!is_secure_level(src.z)) + to_chat(usr, "Unable to establish a connection: You're too far away from the station!") + return 1 + + if(href_list["login"]) + if(!ishuman(usr)) + to_chat(usr, "Access denied.") + return + + var/list/access = usr.get_access() + if(allowed(usr)) + authenticated = COMM_AUTHENTICATION_MIN + + if(ACCESS_CAPTAIN in access) + authenticated = COMM_AUTHENTICATION_MAX + var/mob/living/carbon/human/H = usr + var/obj/item/card/id = H.get_idcard(TRUE) + if(istype(id)) + crew_announcement.announcer = GetNameAndAssignmentFromId(id) + + SSnanoui.update_uis(src) + return + + if(href_list["logout"]) + authenticated = COMM_AUTHENTICATION_NONE + crew_announcement.announcer = "" + setMenuState(usr,COMM_SCREEN_MAIN) + SSnanoui.update_uis(src) + return + + if(!is_authenticated(usr)) + return 1 + + switch(href_list["operation"]) + if("main") + setMenuState(usr,COMM_SCREEN_MAIN) + + if("changeseclevel") + setMenuState(usr,COMM_SCREEN_SECLEVEL) + + if("newalertlevel") + if(isAI(usr) || isrobot(usr)) + to_chat(usr, "Firewalls prevent you from changing the alert level.") + return 1 + else if(usr.can_admin_interact()) + change_security_level(text2num(href_list["level"])) + return 1 + else if(!ishuman(usr)) + to_chat(usr, "Security measures prevent you from changing the alert level.") + return 1 + + var/mob/living/carbon/human/L = usr + var/obj/item/card = L.get_active_hand() + var/obj/item/card/id/I = (card && card.GetID()) || L.wear_id || L.wear_pda + if(istype(I, /obj/item/pda)) + var/obj/item/pda/pda = I + I = pda.id + if(I && istype(I)) + if(ACCESS_CAPTAIN in I.access) + change_security_level(text2num(href_list["level"])) + else + to_chat(usr, "You are not authorized to do this.") + setMenuState(usr,COMM_SCREEN_MAIN) + else + to_chat(usr, "You need to swipe your ID.") + + if("announce") + if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX) + if(message_cooldown) + to_chat(usr, "Please allow at least one minute to pass between announcements.") + SSnanoui.update_uis(src) + return + var/input = input(usr, "Please write a message to announce to the station crew.", "Priority Announcement") + if(!input || message_cooldown || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)) + SSnanoui.update_uis(src) + return + crew_announcement.Announce(input) + message_cooldown = 1 + spawn(600)//One minute cooldown + message_cooldown = 0 + + if("callshuttle") + var/input = clean_input("Please enter the reason for calling the shuttle.", "Shuttle Call Reason.","") + if(!input || ..() || !is_authenticated(usr)) + SSnanoui.update_uis(src) + return + + call_shuttle_proc(usr, input) + if(SSshuttle.emergency.timer) + post_status("shuttle") + setMenuState(usr,COMM_SCREEN_MAIN) + + if("cancelshuttle") + if(isAI(usr) || isrobot(usr)) + to_chat(usr, "Firewalls prevent you from recalling the shuttle.") + SSnanoui.update_uis(src) + return 1 + var/response = alert("Are you sure you wish to recall the shuttle?", "Confirm", "Yes", "No") + if(response == "Yes") + cancel_call_proc(usr) + if(SSshuttle.emergency.timer) + post_status("shuttle") + setMenuState(usr,COMM_SCREEN_MAIN) + + if("messagelist") + currmsg = 0 + if(href_list["msgid"]) + setCurrentMessage(usr, text2num(href_list["msgid"])) + setMenuState(usr,COMM_SCREEN_MESSAGES) + + if("delmessage") + if(href_list["msgid"]) + currmsg = text2num(href_list["msgid"]) + var/response = alert("Are you sure you wish to delete this message?", "Confirm", "Yes", "No") + if(response == "Yes") + if(currmsg) + var/id = getCurrentMessage() + var/title = messagetitle[id] + var/text = messagetext[id] + messagetitle.Remove(title) + messagetext.Remove(text) + if(currmsg == id) + currmsg = 0 + if(aicurrmsg == id) + aicurrmsg = 0 + setMenuState(usr,COMM_SCREEN_MESSAGES) + + if("status") + setMenuState(usr,COMM_SCREEN_STAT) + + // Status display stuff + if("setstat") + display_type=href_list["statdisp"] + switch(display_type) + if("message") + post_status("message", stat_msg1, stat_msg2, usr) + if("alert") + post_status("alert", href_list["alert"], user = usr) + else + post_status(href_list["statdisp"], user = usr) + setMenuState(usr,COMM_SCREEN_STAT) + + if("setmsg1") + stat_msg1 = clean_input("Line 1", "Enter Message Text", stat_msg1) + setMenuState(usr,COMM_SCREEN_STAT) + + if("setmsg2") + stat_msg2 = clean_input("Line 2", "Enter Message Text", stat_msg2) + setMenuState(usr,COMM_SCREEN_STAT) + + if("nukerequest") + if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX) + if(centcomm_message_cooldown) + to_chat(usr, "Arrays recycling. Please stand by.") + SSnanoui.update_uis(src) + return + var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self Destruct Code Request.","") + if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)) + SSnanoui.update_uis(src) + return + Nuke_request(input, usr) + to_chat(usr, "Request sent.") + log_game("[key_name(usr)] has requested the nuclear codes from Centcomm") + priority_announcement.Announce("The codes for the on-station nuclear self-destruct have been requested by [usr]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested",'sound/AI/commandreport.ogg') + centcomm_message_cooldown = 1 + spawn(6000)//10 minute cooldown + centcomm_message_cooldown = 0 + setMenuState(usr,COMM_SCREEN_MAIN) + + if("MessageCentcomm") + if(is_authenticated(usr) == COMM_AUTHENTICATION_MAX) + if(centcomm_message_cooldown) + to_chat(usr, "Arrays recycling. Please stand by.") + SSnanoui.update_uis(src) + return + var/input = stripped_input(usr, "Please choose a message to transmit to Centcomm via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "To abort, send an empty message.", "") + if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)) + SSnanoui.update_uis(src) + return + Centcomm_announce(input, usr) + print_centcom_report(input, station_time_timestamp() + " Captain's Message") + to_chat(usr, "Message transmitted.") + log_game("[key_name(usr)] has made a Centcomm announcement: [input]") + centcomm_message_cooldown = 1 + spawn(6000)//10 minute cooldown + centcomm_message_cooldown = 0 + setMenuState(usr,COMM_SCREEN_MAIN) + + // OMG SYNDICATE ...LETTERHEAD + if("MessageSyndicate") + if((is_authenticated(usr) == COMM_AUTHENTICATION_MAX) && (src.emagged)) + if(centcomm_message_cooldown) + to_chat(usr, "Arrays recycling. Please stand by.") + SSnanoui.update_uis(src) + return + var/input = stripped_input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING CORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "To abort, send an empty message.", "") + if(!input || ..() || !(is_authenticated(usr) == COMM_AUTHENTICATION_MAX)) + SSnanoui.update_uis(src) + return + Syndicate_announce(input, usr) + to_chat(usr, "Message transmitted.") + log_game("[key_name(usr)] has made a Syndicate announcement: [input]") + centcomm_message_cooldown = 1 + spawn(6000)//10 minute cooldown + centcomm_message_cooldown = 0 + setMenuState(usr,COMM_SCREEN_MAIN) + + if("RestoreBackup") + to_chat(usr, "Backup routing data restored!") + src.emagged = 0 + setMenuState(usr,COMM_SCREEN_MAIN) + + if("RestartNanoMob") + if(SSmob_hunt) + if(SSmob_hunt.manual_reboot()) + var/loading_msg = pick("Respawning spawns", "Reticulating splines", "Flipping hat", + "Capturing all of them", "Fixing minor text issues", "Being the very best", + "Nerfing this", "Not communicating with playerbase", "Coding a ripoff in a 2D spaceman game") + to_chat(usr, "Restarting Nano-Mob Hunter GO! game server. [loading_msg]...") + else + to_chat(usr, "Nano-Mob Hunter GO! game server reboot failed due to recent restart. Please wait before re-attempting.") + else + to_chat(usr, "Nano-Mob Hunter GO! game server is offline for extended maintenance. Contact your Central Command administrators for more info if desired.") + + if("ToggleATC") + atc.squelched = !atc.squelched + to_chat(usr, "ATC traffic is now: [atc.squelched ? "Disabled" : "Enabled"].") + + SSnanoui.update_uis(src) + return 1 + +/obj/machinery/computer/communications/emag_act(user as mob) + if(!emagged) + src.emagged = 1 + to_chat(user, "You scramble the communication routing circuits!") + SSnanoui.update_uis(src) + +/obj/machinery/computer/communications/attack_ai(var/mob/user as mob) + return src.attack_hand(user) + +/obj/machinery/computer/communications/attack_hand(var/mob/user as mob) + if(..(user)) + return + + if(stat & (NOPOWER|BROKEN)) + return + + if(!is_secure_level(src.z)) + to_chat(user, "Unable to establish a connection: You're too far away from the station!") + return + + ui_interact(user) + +/obj/machinery/computer/communications/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null) + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "comm_console.tmpl", "Communications Console", 400, 500) + // open the new ui window + ui.open() + +/obj/machinery/computer/communications/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + data["is_ai"] = isAI(user) || isrobot(user) + data["menu_state"] = data["is_ai"] ? ai_menu_state : menu_state + data["emagged"] = emagged + data["authenticated"] = is_authenticated(user, 0) + data["screen"] = getMenuState(usr) + + data["stat_display"] = list( + "type" = display_type, + "line_1" = (stat_msg1 ? stat_msg1 : "-----"), + "line_2" = (stat_msg2 ? stat_msg2 : "-----"), + + "presets" = list( + list("name" = "blank", "label" = "Clear", "desc" = "Blank slate"), + list("name" = "shuttle", "label" = "Shuttle ETA", "desc" = "Display how much time is left."), + list("name" = "message", "label" = "Message", "desc" = "A custom message.") + ), + + "alerts"=list( + list("alert" = "default", "label" = "Nanotrasen", "desc" = "Oh god."), + list("alert" = "redalert", "label" = "Red Alert", "desc" = "Nothing to do with communists."), + list("alert" = "lockdown", "label" = "Lockdown", "desc" = "Let everyone know they're on lockdown."), + list("alert" = "biohazard", "label" = "Biohazard", "desc" = "Great for virus outbreaks and parties."), + ) + ) + + data["security_level"] = security_level + data["str_security_level"] = capitalize(get_security_level()) + data["levels"] = list( + list("id" = SEC_LEVEL_GREEN, "name" = "Green"), + list("id" = SEC_LEVEL_BLUE, "name" = "Blue"), + //SEC_LEVEL_RED = list("name"="Red"), + ) + + var/list/msg_data = list() + for(var/i = 1; i <= messagetext.len; i++) + msg_data.Add(list(list("title" = messagetitle[i], "body" = messagetext[i], "id" = i))) + + data["messages"] = msg_data + if((data["is_ai"] && aicurrmsg) || (!data["is_ai"] && currmsg)) + data["current_message"] = data["is_ai"] ? messagetext[aicurrmsg] : messagetext[currmsg] + data["current_message_title"] = data["is_ai"] ? messagetitle[aicurrmsg] : messagetitle[currmsg] + + data["lastCallLoc"] = SSshuttle.emergencyLastCallLoc ? format_text(SSshuttle.emergencyLastCallLoc.name) : null + + var/shuttle[0] + switch(SSshuttle.emergency.mode) + if(SHUTTLE_IDLE, SHUTTLE_RECALL) + shuttle["callStatus"] = 2 //#define + else + shuttle["callStatus"] = 1 + if(SSshuttle.emergency.mode == SHUTTLE_CALL) + var/timeleft = SSshuttle.emergency.timeLeft() + shuttle["eta"] = "[timeleft / 60 % 60]:[add_zero(num2text(timeleft % 60), 2)]" + + data["shuttle"] = shuttle + + data["atcSquelched"] = atc.squelched + + return data + + +/obj/machinery/computer/communications/proc/setCurrentMessage(var/mob/user,var/value) + if(isAI(user) || isrobot(user)) + aicurrmsg = value + else + currmsg = value + +/obj/machinery/computer/communications/proc/getCurrentMessage(var/mob/user) + if(isAI(user) || isrobot(user)) + return aicurrmsg + else + return currmsg + +/obj/machinery/computer/communications/proc/setMenuState(var/mob/user,var/value) + if(isAI(user) || isrobot(user)) + ai_menu_state=value + else + menu_state=value + +/obj/machinery/computer/communications/proc/getMenuState(var/mob/user) + if(isAI(user) || isrobot(user)) + return ai_menu_state + else + return menu_state + +/proc/enable_prison_shuttle(var/mob/user); + +/proc/call_shuttle_proc(var/mob/user, var/reason) + if(sent_strike_team == 1) + to_chat(user, "Central Command will not allow the shuttle to be called. Consider all contracts terminated.") + return + + if(SSshuttle.emergencyNoEscape) + to_chat(user, "The emergency shuttle may not be sent at this time. Please try again later.") + return + + if(SSshuttle.emergency.mode > SHUTTLE_ESCAPE) + to_chat(user, "The emergency shuttle may not be called while returning to Central Command.") + return + + if(SSticker.mode.name == "blob") + to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.") + return + + SSshuttle.requestEvac(user, reason) + log_game("[key_name(user)] has called the shuttle.") + message_admins("[key_name_admin(user)] has called the shuttle.", 1) + + return + +/proc/init_shift_change(var/mob/user, var/force = 0) + // if force is 0, some things may stop the shuttle call + if(!force) + if(SSshuttle.emergencyNoEscape) + to_chat(user, "Central Command does not currently have a shuttle available in your sector. Please try again later.") + return + + if(sent_strike_team == 1) + to_chat(user, "Central Command will not allow the shuttle to be called. Consider all contracts terminated.") + return + + if(world.time < 54000) // 30 minute grace period to let the game get going + to_chat(user, "The shuttle is refueling. Please wait another [round((54000-world.time)/600)] minutes before trying again.") + return + + if(SSticker.mode.name == "epidemic") + to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.") + return + + if(seclevel2num(get_security_level()) >= SEC_LEVEL_RED) // There is a serious threat we gotta move no time to give them five minutes. + SSshuttle.emergency.request(null, 0.5, null, " Automatic Crew Transfer", 1) + SSshuttle.emergency.canRecall = FALSE + else + SSshuttle.emergency.request(null, 1, null, " Automatic Crew Transfer", 0) + SSshuttle.emergency.canRecall = FALSE + if(user) + log_game("[key_name(user)] has called the shuttle.") + message_admins("[key_name_admin(user)] has called the shuttle - [formatJumpTo(user)].", 1) + return + + +/proc/cancel_call_proc(var/mob/user) + if(SSticker.mode.name == "meteor") + return + + if(SSshuttle.cancelEvac(user)) + log_game("[key_name(user)] has recalled the shuttle.") + message_admins("[key_name_admin(user)] has recalled the shuttle - ([ADMIN_FLW(user,"FLW")]).", 1) + else + to_chat(user, "Central Command has refused the recall request!") + log_game("[key_name(user)] has tried and failed to recall the shuttle.") + message_admins("[key_name_admin(user)] has tried and failed to recall the shuttle - ([ADMIN_FLW(user,"FLW")]).", 1) + +/proc/post_status(command, data1, data2, mob/user = null) + + var/datum/radio_frequency/frequency = SSradio.return_frequency(DISPLAY_FREQ) + + if(!frequency) return + + var/datum/signal/status_signal = new + status_signal.source = src + status_signal.transmission_method = 1 + status_signal.data["command"] = command + + switch(command) + if("message") + status_signal.data["msg1"] = data1 + status_signal.data["msg2"] = data2 + log_admin("STATUS: [user] set status screen message with [src]: [data1] [data2]") + //message_admins("STATUS: [user] set status screen with [PDA]. Message: [data1] [data2]") + if("alert") + status_signal.data["picture_state"] = data1 + + spawn(0) + frequency.post_signal(src, status_signal) + + +/obj/machinery/computer/communications/Destroy() + GLOB.shuttle_caller_list -= src + SSshuttle.autoEvac() + return ..() + +/obj/item/circuitboard/communications/New() + GLOB.shuttle_caller_list += src + ..() + +/obj/item/circuitboard/communications/Destroy() + GLOB.shuttle_caller_list -= src + SSshuttle.autoEvac() + return ..() + +/proc/print_command_report(text = "", title = "Central Command Update") + for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list) + if(!(C.stat & (BROKEN|NOPOWER)) && is_station_contact(C.z)) + var/obj/item/paper/P = new /obj/item/paper(C.loc) + P.name = "paper- '[title]'" + P.info = text + P.update_icon() + C.messagetitle.Add("[title]") + C.messagetext.Add(text) + for(var/datum/computer_file/program/comm/P in GLOB.shuttle_caller_list) + var/turf/T = get_turf(P.computer) + if(T && P.program_state != PROGRAM_STATE_KILLED && is_station_contact(T.z)) + if(P.computer) + var/obj/item/computer_hardware/printer/printer = P.computer.all_components[MC_PRINT] + if(printer) + printer.print_text(text, "paper- '[title]'") + P.messagetitle.Add("[title]") + P.messagetext.Add(text) + +/proc/print_centcom_report(text = "", title = "Incoming Message") + for(var/obj/machinery/computer/communications/C in GLOB.shuttle_caller_list) + if(!(C.stat & (BROKEN|NOPOWER)) && is_admin_level(C.z)) + var/obj/item/paper/P = new /obj/item/paper(C.loc) + P.name = "paper- '[title]'" + P.info = text + P.update_icon() + C.messagetitle.Add("[title]") + C.messagetext.Add(text) + for(var/datum/computer_file/program/comm/P in GLOB.shuttle_caller_list) + var/turf/T = get_turf(P.computer) + if(T && P.program_state != PROGRAM_STATE_KILLED && is_admin_level(T.z)) + if(P.computer) + var/obj/item/computer_hardware/printer/printer = P.computer.all_components[MC_PRINT] + if(printer) + printer.print_text(text, "paper- '[title]'") + P.messagetitle.Add("[title]") + P.messagetext.Add(text) diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index 4efdcb69ee8..a832e2b5d16 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -1,36 +1,36 @@ -/obj/machinery/computer/crew - name = "crew monitoring computer" - desc = "Used to monitor active health sensors built into most of the crew's uniforms." - icon_keyboard = "med_key" - icon_screen = "crew" - use_power = IDLE_POWER_USE - idle_power_usage = 250 - active_power_usage = 500 - light_color = LIGHT_COLOR_DARKBLUE - circuit = /obj/item/circuitboard/crew - var/datum/nano_module/crew_monitor/crew_monitor - -/obj/machinery/computer/crew/New() - crew_monitor = new(src) - ..() - -/obj/machinery/computer/crew/Destroy() - QDEL_NULL(crew_monitor) - return ..() - -/obj/machinery/computer/crew/attack_ai(mob/user) - attack_hand(user) - ui_interact(user) - - -/obj/machinery/computer/crew/attack_hand(mob/user) - add_fingerprint(user) - if(stat & (BROKEN|NOPOWER)) - return - ui_interact(user) - -/obj/machinery/computer/crew/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - crew_monitor.ui_interact(user, ui_key, ui, force_open) - -/obj/machinery/computer/crew/interact(mob/user) - crew_monitor.ui_interact(user) +/obj/machinery/computer/crew + name = "crew monitoring computer" + desc = "Used to monitor active health sensors built into most of the crew's uniforms." + icon_keyboard = "med_key" + icon_screen = "crew" + use_power = IDLE_POWER_USE + idle_power_usage = 250 + active_power_usage = 500 + light_color = LIGHT_COLOR_DARKBLUE + circuit = /obj/item/circuitboard/crew + var/datum/nano_module/crew_monitor/crew_monitor + +/obj/machinery/computer/crew/New() + crew_monitor = new(src) + ..() + +/obj/machinery/computer/crew/Destroy() + QDEL_NULL(crew_monitor) + return ..() + +/obj/machinery/computer/crew/attack_ai(mob/user) + attack_hand(user) + ui_interact(user) + + +/obj/machinery/computer/crew/attack_hand(mob/user) + add_fingerprint(user) + if(stat & (BROKEN|NOPOWER)) + return + ui_interact(user) + +/obj/machinery/computer/crew/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + crew_monitor.ui_interact(user, ui_key, ui, force_open) + +/obj/machinery/computer/crew/interact(mob/user) + crew_monitor.ui_interact(user) diff --git a/code/game/machinery/computer/depot.dm b/code/game/machinery/computer/depot.dm index 382e1325f9d..62c82d9bd95 100644 --- a/code/game/machinery/computer/depot.dm +++ b/code/game/machinery/computer/depot.dm @@ -543,4 +543,4 @@ to_chat(user, "[B] has been recalled.") qdel(B) raise_alert("Sentry bot removed via emergency recall.") - playsound(user, sound_yes, 50, 0) \ No newline at end of file + playsound(user, sound_yes, 50, 0) diff --git a/code/game/machinery/computer/honkputer.dm b/code/game/machinery/computer/honkputer.dm index 94787d61020..9f5d77a642c 100644 --- a/code/game/machinery/computer/honkputer.dm +++ b/code/game/machinery/computer/honkputer.dm @@ -115,4 +115,4 @@ A.icon_state = "4" qdel(src) else - return ..() \ No newline at end of file + return ..() diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 5defd5e8a69..a3828af6be1 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -1,572 +1,572 @@ -#define MED_DATA_MAIN 1 // Main menu -#define MED_DATA_R_LIST 2 // Record list -#define MED_DATA_MAINT 3 // Records maintenance -#define MED_DATA_RECORD 4 // Record -#define MED_DATA_V_DATA 5 // Virus database -#define MED_DATA_MEDBOT 6 // Medbot monitor - -/obj/machinery/computer/med_data //TODO:SANITY - name = "medical records console" - desc = "This can be used to check medical records." - icon_keyboard = "med_key" - icon_screen = "medcomp" - req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS) - circuit = /obj/item/circuitboard/med_data - var/obj/item/card/id/scan = null - var/authenticated = null - var/rank = null - var/screen = null - var/datum/data/record/active1 = null - var/datum/data/record/active2 = null - var/temp = null - var/printing = null - - light_color = LIGHT_COLOR_DARKBLUE - -/obj/machinery/computer/med_data/Destroy() - active1 = null - active2 = null - return ..() - -/obj/machinery/computer/med_data/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/card/id) && !scan) - usr.drop_item() - O.forceMove(src) - scan = O - ui_interact(user) - return - return ..() - -/obj/machinery/computer/med_data/attack_hand(mob/user) - if(..()) - return - if(is_away_level(z)) - to_chat(user, "Unable to establish a connection: You're too far away from the station!") - return - add_fingerprint(user) - ui_interact(user) - -/obj/machinery/computer/med_data/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "med_data.tmpl", name, 800, 380) - ui.open() - -/obj/machinery/computer/med_data/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - data["temp"] = temp - data["scan"] = scan ? scan.name : null - data["authenticated"] = authenticated - data["screen"] = screen - if(authenticated) - switch(screen) - if(MED_DATA_R_LIST) - if(!isnull(data_core.general)) - var/list/records = list() - data["records"] = records - for(var/datum/data/record/R in sortRecord(data_core.general)) - records[++records.len] = list("ref" = "\ref[R]", "id" = R.fields["id"], "name" = R.fields["name"]) - if(MED_DATA_RECORD) - var/list/general = list() - data["general"] = general - if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) - var/list/fields = list() - general["fields"] = fields - fields[++fields.len] = list("field" = "Name:", "value" = active1.fields["name"], "edit" = null) - fields[++fields.len] = list("field" = "ID:", "value" = active1.fields["id"], "edit" = null) - fields[++fields.len] = list("field" = "Sex:", "value" = active1.fields["sex"], "edit" = "sex") - fields[++fields.len] = list("field" = "Age:", "value" = active1.fields["age"], "edit" = "age") - fields[++fields.len] = list("field" = "Fingerprint:", "value" = active1.fields["fingerprint"], "edit" = "fingerprint") - fields[++fields.len] = list("field" = "Physical Status:", "value" = active1.fields["p_stat"], "edit" = "p_stat") - fields[++fields.len] = list("field" = "Mental Status:", "value" = active1.fields["m_stat"], "edit" = "m_stat") - var/list/photos = list() - general["photos"] = photos - photos[++photos.len] = list("photo" = active1.fields["photo-south"]) - photos[++photos.len] = list("photo" = active1.fields["photo-west"]) - general["has_photos"] = (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0) - general["empty"] = 0 - else - general["empty"] = 1 - - var/list/medical = list() - data["medical"] = medical - if(istype(active2, /datum/data/record) && data_core.medical.Find(active2)) - var/list/fields = list() - medical["fields"] = fields - fields[++fields.len] = list("field" = "Blood Type:", "value" = active2.fields["blood_type"], "edit" = "blood_type", "line_break" = 0) - fields[++fields.len] = list("field" = "DNA:", "value" = active2.fields["b_dna"], "edit" = "b_dna", "line_break" = 1) - fields[++fields.len] = list("field" = "Minor Disabilities:", "value" = active2.fields["mi_dis"], "edit" = "mi_dis", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["mi_dis_d"], "edit" = "mi_dis_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Major Disabilities:", "value" = active2.fields["ma_dis"], "edit" = "ma_dis", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["ma_dis_d"], "edit" = "ma_dis_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Allergies:", "value" = active2.fields["alg"], "edit" = "alg", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["alg_d"], "edit" = "alg_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Current Diseases:", "value" = active2.fields["cdi"], "edit" = "cdi", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["cdi_d"], "edit" = "cdi_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Important Notes:", "value" = active2.fields["notes"], "edit" = "notes", "line_break" = 0) - if(!active2.fields["comments"] || !islist(active2.fields["comments"])) - active2.fields["comments"] = list() - medical["comments"] = active2.fields["comments"] - medical["empty"] = 0 - else - medical["empty"] = 1 - if(MED_DATA_V_DATA) - data["virus"] = list() - for(var/D in typesof(/datum/disease)) - var/datum/disease/DS = new D(0) - if(istype(DS, /datum/disease/advance)) - continue - if(!DS.desc) - continue - data["virus"] += list(list("name" = DS.name, "D" = D)) - if(MED_DATA_MEDBOT) - data["medbots"] = list() - for(var/mob/living/simple_animal/bot/medbot/M in world) - if(M.z != z) - continue - var/turf/T = get_turf(M) - if(T) - var/medbot = list() - medbot["name"] = M.name - medbot["x"] = T.x - medbot["y"] = T.y - medbot["on"] = M.on - if(!isnull(M.reagent_glass) && M.use_beaker) - medbot["use_beaker"] = 1 - medbot["total_volume"] = M.reagent_glass.reagents.total_volume - medbot["maximum_volume"] = M.reagent_glass.reagents.maximum_volume - else - medbot["use_beaker"] = 0 - data["medbots"] += list(medbot) - return data - -/obj/machinery/computer/med_data/Topic(href, href_list) - if(..()) - return 1 - - if(!data_core.general.Find(active1)) - active1 = null - if(!data_core.medical.Find(active2)) - active2 = null - - if(href_list["temp"]) - temp = null - - if(href_list["temp_action"]) - if(href_list["temp_action"]) - var/temp_href = splittext(href_list["temp_action"], "=") - switch(temp_href[1]) - if("del_all2") - for(var/datum/data/record/R in data_core.medical) - qdel(R) - setTemp("

        All records deleted.

        ") - if("p_stat") - if(active1) - switch(temp_href[2]) - if("deceased") - active1.fields["p_stat"] = "*Deceased*" - if("ssd") - active1.fields["p_stat"] = "*SSD*" - if("active") - active1.fields["p_stat"] = "Active" - if("unfit") - active1.fields["p_stat"] = "Physically Unfit" - if("disabled") - active1.fields["p_stat"] = "Disabled" - if("m_stat") - if(active1) - switch(temp_href[2]) - if("insane") - active1.fields["m_stat"] = "*Insane*" - if("unstable") - active1.fields["m_stat"] = "*Unstable*" - if("watch") - active1.fields["m_stat"] = "*Watch*" - if("stable") - active1.fields["m_stat"] = "Stable" - if("blood_type") - if(active2) - switch(temp_href[2]) - if("an") - active2.fields["blood_type"] = "A-" - if("bn") - active2.fields["blood_type"] = "B-" - if("abn") - active2.fields["blood_type"] = "AB-" - if("on") - active2.fields["blood_type"] = "O-" - if("ap") - active2.fields["blood_type"] = "A+" - if("bp") - active2.fields["blood_type"] = "B+" - if("abp") - active2.fields["blood_type"] = "AB+" - if("op") - active2.fields["blood_type"] = "O+" - if("del_r2") - QDEL_NULL(active2) - - if(href_list["scan"]) - if(scan) - scan.forceMove(loc) - if(ishuman(usr) && !usr.get_active_hand()) - usr.put_in_hands(scan) - scan = null - else - var/obj/item/I = usr.get_active_hand() - if(istype(I, /obj/item/card/id)) - usr.drop_item() - I.forceMove(src) - scan = I - - if(href_list["login"]) - if(isAI(usr)) - authenticated = usr.name - rank = "AI" - else if(isrobot(usr)) - authenticated = usr.name - var/mob/living/silicon/robot/R = usr - rank = "[R.modtype] [R.braintype]" - else if(istype(scan, /obj/item/card/id)) - if(check_access(scan)) - authenticated = scan.registered_name - rank = scan.assignment - - if(authenticated) - active1 = null - active2 = null - screen = MED_DATA_MAIN - - if(authenticated) - if(href_list["logout"]) - authenticated = null - screen = null - active1 = null - active2 = null - - if(href_list["screen"]) - screen = text2num(href_list["screen"]) - if(screen < 1) - screen = MED_DATA_MAIN - - active1 = null - active2 = null - - if(href_list["vir"]) - var/type = href_list["vir"] - var/datum/disease/D = new type(0) - var/afs = "" - for(var/mob/M in D.viable_mobtypes) - afs += "[initial(M.name)];" - var/severity = D.severity - switch(severity) - if("Harmful", "Minor") - severity = "[severity]" - if("Medium") - severity = "[severity]" - if("Dangerous!") - severity = "[severity]" - if("BIOHAZARD THREAT!") - severity = "

        [severity]

        " - setTemp({"Name: [D.name] -
        Number of stages: [D.max_stages] -
        Spread: [D.spread_text] Transmission -
        Possible Cure: [(D.cure_text||"none")] -
        Affected Lifeforms:[afs]
        -
        Notes: [D.desc]
        -
        Severity: [severity]"}) - qdel(D) - - if(href_list["del_all"]) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_all2=1") - buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null) - setTemp("

        Are you sure you wish to delete all records?

        ", buttons) - - if(href_list["field"]) - if(..()) - return 1 - var/a1 = active1 - var/a2 = active2 - switch(href_list["field"]) - if("fingerprint") - if(istype(active1, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input fingerprint hash:", "Med. records", active1.fields["fingerprint"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - active1.fields["fingerprint"] = t1 - if("sex") - if(istype(active1, /datum/data/record)) - if(active1.fields["sex"] == "Male") - active1.fields["sex"] = "Female" - else - active1.fields["sex"] = "Male" - if("age") - if(istype(active1, /datum/data/record)) - var/t1 = input("Please input age:", "Med. records", active1.fields["age"], null) as num - if(!t1 || ..() || active1 != a1) - return 1 - active1.fields["age"] = t1 - if("mi_dis") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input minor disabilities list:", "Med. records", active2.fields["mi_dis"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["mi_dis"] = t1 - if("mi_dis_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize minor dis.:", "Med. records", active2.fields["mi_dis_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["mi_dis_d"] = t1 - if("ma_dis") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input major diabilities list:", "Med. records", active2.fields["ma_dis"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["ma_dis"] = t1 - if("ma_dis_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize major dis.:", "Med. records", active2.fields["ma_dis_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["ma_dis_d"] = t1 - if("alg") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please state allergies:", "Med. records", active2.fields["alg"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["alg"] = t1 - if("alg_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize allergies:", "Med. records", active2.fields["alg_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["alg_d"] = t1 - if("cdi") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please state diseases:", "Med. records", active2.fields["cdi"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["cdi"] = t1 - if("cdi_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize diseases:", "Med. records", active2.fields["cdi_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["cdi_d"] = t1 - if("notes") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(html_encode(trim(input("Please summarize notes:", "Med. records", html_decode(active2.fields["notes"]), null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["notes"] = t1 - if("p_stat") - if(istype(active1, /datum/data/record)) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "*Deceased*", "icon" = "stethoscope", "href" = "p_stat=deceased", "status" = (active1.fields["p_stat"] == "*Deceased*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "*SSD*", "icon" = "stethoscope", "href" = "p_stat=ssd", "status" = (active1.fields["p_stat"] == "*SSD*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Active", "icon" = "stethoscope", "href" = "p_stat=active", "status" = (active1.fields["p_stat"] == "Active" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Physically Unfit", "icon" = "stethoscope", "href" = "p_stat=unfit", "status" = (active1.fields["p_stat"] == "Physically Unfit" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Disabled", "icon" = "stethoscope", "href" = "p_stat=disabled", "status" = (active1.fields["p_stat"] == "Disabled" ? "selected" : null)) - setTemp("

        Physical Condition

        ", buttons) - if("m_stat") - if(istype(active1, /datum/data/record)) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "*Insane*", "icon" = "stethoscope", "href" = "m_stat=insane", "status" = (active1.fields["m_stat"] == "*Insane*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "*Unstable*", "icon" = "stethoscope", "href" = "m_stat=unstable", "status" = (active1.fields["m_stat"] == "*Unstable*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "*Watch*", "icon" = "stethoscope", "href" = "m_stat=watch", "status" = (active1.fields["m_stat"] == "*Watch*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Stable", "icon" = "stethoscope", "href" = "m_stat=stable", "status" = (active1.fields["m_stat"] == "Stable" ? "selected" : null)) - setTemp("

        Mental Condition

        ", buttons) - if("blood_type") - if(istype(active2, /datum/data/record)) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "A-", "icon" = "tint", "href" = "blood_type=an", "status" = (active2.fields["blood_type"] == "A-" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "A+", "icon" = "tint", "href" = "blood_type=ap", "status" = (active2.fields["blood_type"] == "A+" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "B-", "icon" = "tint", "href" = "blood_type=bn", "status" = (active2.fields["blood_type"] == "B-" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "B+", "icon" = "tint", "href" = "blood_type=bp", "status" = (active2.fields["blood_type"] == "B+" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "AB-", "icon" = "tint", "href" = "blood_type=abn", "status" = (active2.fields["blood_type"] == "AB-" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "AB+", "icon" = "tint", "href" = "blood_type=abp", "status" = (active2.fields["blood_type"] == "AB+" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "O-", "icon" = "tint", "href" = "blood_type=on", "status" = (active2.fields["blood_type"] == "O-" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "O+", "icon" = "tint", "href" = "blood_type=op", "status" = (active2.fields["blood_type"] == "O+" ? "selected" : null)) - setTemp("

        Blood Type

        ", buttons) - if("b_dna") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input DNA hash:", "Med. records", active2.fields["b_dna"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["b_dna"] = t1 - if("vir_name") - var/datum/data/record/v = locate(href_list["edit_vir"]) - if(v) - var/t1 = copytext(trim(sanitize(input("Please input pathogen name:", "VirusDB", v.fields["name"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - v.fields["name"] = t1 - if("vir_desc") - var/datum/data/record/v = locate(href_list["edit_vir"]) - if(v) - var/t1 = copytext(trim(sanitize(input("Please input information about pathogen:", "VirusDB", v.fields["description"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - v.fields["description"] = t1 - - if(href_list["del_r"]) - if(active2) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_r2=1", "status" = null) - buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) - setTemp("

        Are you sure you wish to delete the record (Medical Portion Only)?

        ", buttons) - - if(href_list["d_rec"]) - var/datum/data/record/R = locate(href_list["d_rec"]) - var/datum/data/record/M = locate(href_list["d_rec"]) - if(!data_core.general.Find(R)) - setTemp("

        Record not found!

        ") - return 1 - for(var/datum/data/record/E in data_core.medical) - if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"]) - M = E - active1 = R - active2 = M - screen = MED_DATA_RECORD - - if(href_list["new"]) - if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record)) - var/datum/data/record/R = new /datum/data/record() - R.fields["name"] = active1.fields["name"] - R.fields["id"] = active1.fields["id"] - R.name = "Medical Record #[R.fields["id"]]" - R.fields["blood_type"] = "Unknown" - R.fields["b_dna"] = "Unknown" - R.fields["mi_dis"] = "None" - R.fields["mi_dis_d"] = "No minor disabilities have been declared." - R.fields["ma_dis"] = "None" - R.fields["ma_dis_d"] = "No major disabilities have been diagnosed." - R.fields["alg"] = "None" - R.fields["alg_d"] = "No allergies have been detected in this patient." - R.fields["cdi"] = "None" - R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." - R.fields["notes"] = "No notes." - data_core.medical += R - active2 = R - screen = MED_DATA_RECORD - - if(href_list["add_c"]) - if(!istype(active2, /datum/data/record)) - return 1 - var/a2 = active2 - var/t1 = copytext(trim(sanitize(input("Add Comment:", "Med. records", null, null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["comments"] += "Made by [authenticated] ([rank]) on [current_date_string] [station_time_timestamp()]
        [t1]" - - if(href_list["del_c"]) - var/index = min(max(text2num(href_list["del_c"]) + 1, 1), length(active2.fields["comments"])) - if(istype(active2, /datum/data/record) && active2.fields["comments"][index]) - active2.fields["comments"] -= active2.fields["comments"][index] - - if(href_list["search"]) - var/t1 = clean_input("Search String: (Name, DNA, or ID)", "Med. records", null, null) - if(!t1 || ..()) - return 1 - active1 = null - active2 = null - t1 = lowertext(t1) - for(var/datum/data/record/R in data_core.medical) - if(t1 == lowertext(R.fields["name"]) || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"])) - active2 = R - if(!active2) - setTemp("

        Could not locate record [t1].

        ") - else - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == active2.fields["name"] && E.fields["id"] == active2.fields["id"]) - active1 = E - screen = MED_DATA_RECORD - - if(href_list["print_p"]) - if(!printing) - printing = 1 - playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) - sleep(50) - var/obj/item/paper/P = new /obj/item/paper(loc) - P.info = "
        Medical Record

        " - if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) - P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]] -
        \nSex: [active1.fields["sex"]] -
        \nAge: [active1.fields["age"]] -
        \nFingerprint: [active1.fields["fingerprint"]] -
        \nPhysical Status: [active1.fields["p_stat"]] -
        \nMental Status: [active1.fields["m_stat"]]
        "} - else - P.info += "General Record Lost!
        " - if(istype(active2, /datum/data/record) && data_core.medical.Find(active2)) - P.info += {"
        \n
        Medical Data
        -
        \nBlood Type: [active2.fields["blood_type"]] -
        \nDNA: [active2.fields["b_dna"]]
        \n -
        \nMinor Disabilities: [active2.fields["mi_dis"]] -
        \nDetails: [active2.fields["mi_dis_d"]]
        \n -
        \nMajor Disabilities: [active2.fields["ma_dis"]] -
        \nDetails: [active2.fields["ma_dis_d"]]
        \n -
        \nAllergies: [active2.fields["alg"]] -
        \nDetails: [active2.fields["alg_d"]]
        \n -
        \nCurrent Diseases: [active2.fields["cdi"]] (per disease info placed in log/comment section) -
        \nDetails: [active2.fields["cdi_d"]]
        \n -
        \nImportant Notes: -
        \n\t[active2.fields["notes"]]
        \n -
        \n -
        Comments/Log

        "} - for(var/c in active2.fields["comments"]) - P.info += "[c]
        " - else - P.info += "Medical Record Lost!
        " - P.info += "
        " - P.name = "paper- 'Medical Record: [active1.fields["name"]]'" - printing = 0 - return 1 - -/obj/machinery/computer/med_data/proc/setTemp(text, list/buttons = list()) - temp = list("text" = text, "buttons" = buttons, "has_buttons" = buttons.len > 0) - -/obj/machinery/computer/med_data/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - return ..(severity) - - for(var/datum/data/record/R in data_core.medical) - if(prob(10/severity)) - switch(rand(1,6)) - if(1) - R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" - if(2) - R.fields["sex"] = pick("Male", "Female") - if(3) - R.fields["age"] = rand(5, 85) - if(4) - R.fields["blood_type"] = pick("A-", "B-", "AB-", "O-", "A+", "B+", "AB+", "O+") - if(5) - R.fields["p_stat"] = pick("*SSD*", "Active", "Physically Unfit", "Disabled") - if(6) - R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") - continue - - else if(prob(1)) - qdel(R) - continue - - ..(severity) - - -/obj/machinery/computer/med_data/laptop - name = "medical laptop" - desc = "Cheap Nanotrasen laptop." - icon_state = "laptop" - icon_keyboard = "laptop_key" - icon_screen = "medlaptop" - density = 0 - -#undef MED_DATA_MAIN -#undef MED_DATA_R_LIST -#undef MED_DATA_MAINT -#undef MED_DATA_RECORD -#undef MED_DATA_V_DATA -#undef MED_DATA_MEDBOT +#define MED_DATA_MAIN 1 // Main menu +#define MED_DATA_R_LIST 2 // Record list +#define MED_DATA_MAINT 3 // Records maintenance +#define MED_DATA_RECORD 4 // Record +#define MED_DATA_V_DATA 5 // Virus database +#define MED_DATA_MEDBOT 6 // Medbot monitor + +/obj/machinery/computer/med_data //TODO:SANITY + name = "medical records console" + desc = "This can be used to check medical records." + icon_keyboard = "med_key" + icon_screen = "medcomp" + req_one_access = list(ACCESS_MEDICAL, ACCESS_FORENSICS_LOCKERS) + circuit = /obj/item/circuitboard/med_data + var/obj/item/card/id/scan = null + var/authenticated = null + var/rank = null + var/screen = null + var/datum/data/record/active1 = null + var/datum/data/record/active2 = null + var/temp = null + var/printing = null + + light_color = LIGHT_COLOR_DARKBLUE + +/obj/machinery/computer/med_data/Destroy() + active1 = null + active2 = null + return ..() + +/obj/machinery/computer/med_data/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/card/id) && !scan) + usr.drop_item() + O.forceMove(src) + scan = O + ui_interact(user) + return + return ..() + +/obj/machinery/computer/med_data/attack_hand(mob/user) + if(..()) + return + if(is_away_level(z)) + to_chat(user, "Unable to establish a connection: You're too far away from the station!") + return + add_fingerprint(user) + ui_interact(user) + +/obj/machinery/computer/med_data/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "med_data.tmpl", name, 800, 380) + ui.open() + +/obj/machinery/computer/med_data/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + data["temp"] = temp + data["scan"] = scan ? scan.name : null + data["authenticated"] = authenticated + data["screen"] = screen + if(authenticated) + switch(screen) + if(MED_DATA_R_LIST) + if(!isnull(data_core.general)) + var/list/records = list() + data["records"] = records + for(var/datum/data/record/R in sortRecord(data_core.general)) + records[++records.len] = list("ref" = "\ref[R]", "id" = R.fields["id"], "name" = R.fields["name"]) + if(MED_DATA_RECORD) + var/list/general = list() + data["general"] = general + if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) + var/list/fields = list() + general["fields"] = fields + fields[++fields.len] = list("field" = "Name:", "value" = active1.fields["name"], "edit" = null) + fields[++fields.len] = list("field" = "ID:", "value" = active1.fields["id"], "edit" = null) + fields[++fields.len] = list("field" = "Sex:", "value" = active1.fields["sex"], "edit" = "sex") + fields[++fields.len] = list("field" = "Age:", "value" = active1.fields["age"], "edit" = "age") + fields[++fields.len] = list("field" = "Fingerprint:", "value" = active1.fields["fingerprint"], "edit" = "fingerprint") + fields[++fields.len] = list("field" = "Physical Status:", "value" = active1.fields["p_stat"], "edit" = "p_stat") + fields[++fields.len] = list("field" = "Mental Status:", "value" = active1.fields["m_stat"], "edit" = "m_stat") + var/list/photos = list() + general["photos"] = photos + photos[++photos.len] = list("photo" = active1.fields["photo-south"]) + photos[++photos.len] = list("photo" = active1.fields["photo-west"]) + general["has_photos"] = (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0) + general["empty"] = 0 + else + general["empty"] = 1 + + var/list/medical = list() + data["medical"] = medical + if(istype(active2, /datum/data/record) && data_core.medical.Find(active2)) + var/list/fields = list() + medical["fields"] = fields + fields[++fields.len] = list("field" = "Blood Type:", "value" = active2.fields["blood_type"], "edit" = "blood_type", "line_break" = 0) + fields[++fields.len] = list("field" = "DNA:", "value" = active2.fields["b_dna"], "edit" = "b_dna", "line_break" = 1) + fields[++fields.len] = list("field" = "Minor Disabilities:", "value" = active2.fields["mi_dis"], "edit" = "mi_dis", "line_break" = 0) + fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["mi_dis_d"], "edit" = "mi_dis_d", "line_break" = 1) + fields[++fields.len] = list("field" = "Major Disabilities:", "value" = active2.fields["ma_dis"], "edit" = "ma_dis", "line_break" = 0) + fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["ma_dis_d"], "edit" = "ma_dis_d", "line_break" = 1) + fields[++fields.len] = list("field" = "Allergies:", "value" = active2.fields["alg"], "edit" = "alg", "line_break" = 0) + fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["alg_d"], "edit" = "alg_d", "line_break" = 1) + fields[++fields.len] = list("field" = "Current Diseases:", "value" = active2.fields["cdi"], "edit" = "cdi", "line_break" = 0) + fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["cdi_d"], "edit" = "cdi_d", "line_break" = 1) + fields[++fields.len] = list("field" = "Important Notes:", "value" = active2.fields["notes"], "edit" = "notes", "line_break" = 0) + if(!active2.fields["comments"] || !islist(active2.fields["comments"])) + active2.fields["comments"] = list() + medical["comments"] = active2.fields["comments"] + medical["empty"] = 0 + else + medical["empty"] = 1 + if(MED_DATA_V_DATA) + data["virus"] = list() + for(var/D in typesof(/datum/disease)) + var/datum/disease/DS = new D(0) + if(istype(DS, /datum/disease/advance)) + continue + if(!DS.desc) + continue + data["virus"] += list(list("name" = DS.name, "D" = D)) + if(MED_DATA_MEDBOT) + data["medbots"] = list() + for(var/mob/living/simple_animal/bot/medbot/M in world) + if(M.z != z) + continue + var/turf/T = get_turf(M) + if(T) + var/medbot = list() + medbot["name"] = M.name + medbot["x"] = T.x + medbot["y"] = T.y + medbot["on"] = M.on + if(!isnull(M.reagent_glass) && M.use_beaker) + medbot["use_beaker"] = 1 + medbot["total_volume"] = M.reagent_glass.reagents.total_volume + medbot["maximum_volume"] = M.reagent_glass.reagents.maximum_volume + else + medbot["use_beaker"] = 0 + data["medbots"] += list(medbot) + return data + +/obj/machinery/computer/med_data/Topic(href, href_list) + if(..()) + return 1 + + if(!data_core.general.Find(active1)) + active1 = null + if(!data_core.medical.Find(active2)) + active2 = null + + if(href_list["temp"]) + temp = null + + if(href_list["temp_action"]) + if(href_list["temp_action"]) + var/temp_href = splittext(href_list["temp_action"], "=") + switch(temp_href[1]) + if("del_all2") + for(var/datum/data/record/R in data_core.medical) + qdel(R) + setTemp("

        All records deleted.

        ") + if("p_stat") + if(active1) + switch(temp_href[2]) + if("deceased") + active1.fields["p_stat"] = "*Deceased*" + if("ssd") + active1.fields["p_stat"] = "*SSD*" + if("active") + active1.fields["p_stat"] = "Active" + if("unfit") + active1.fields["p_stat"] = "Physically Unfit" + if("disabled") + active1.fields["p_stat"] = "Disabled" + if("m_stat") + if(active1) + switch(temp_href[2]) + if("insane") + active1.fields["m_stat"] = "*Insane*" + if("unstable") + active1.fields["m_stat"] = "*Unstable*" + if("watch") + active1.fields["m_stat"] = "*Watch*" + if("stable") + active1.fields["m_stat"] = "Stable" + if("blood_type") + if(active2) + switch(temp_href[2]) + if("an") + active2.fields["blood_type"] = "A-" + if("bn") + active2.fields["blood_type"] = "B-" + if("abn") + active2.fields["blood_type"] = "AB-" + if("on") + active2.fields["blood_type"] = "O-" + if("ap") + active2.fields["blood_type"] = "A+" + if("bp") + active2.fields["blood_type"] = "B+" + if("abp") + active2.fields["blood_type"] = "AB+" + if("op") + active2.fields["blood_type"] = "O+" + if("del_r2") + QDEL_NULL(active2) + + if(href_list["scan"]) + if(scan) + scan.forceMove(loc) + if(ishuman(usr) && !usr.get_active_hand()) + usr.put_in_hands(scan) + scan = null + else + var/obj/item/I = usr.get_active_hand() + if(istype(I, /obj/item/card/id)) + usr.drop_item() + I.forceMove(src) + scan = I + + if(href_list["login"]) + if(isAI(usr)) + authenticated = usr.name + rank = "AI" + else if(isrobot(usr)) + authenticated = usr.name + var/mob/living/silicon/robot/R = usr + rank = "[R.modtype] [R.braintype]" + else if(istype(scan, /obj/item/card/id)) + if(check_access(scan)) + authenticated = scan.registered_name + rank = scan.assignment + + if(authenticated) + active1 = null + active2 = null + screen = MED_DATA_MAIN + + if(authenticated) + if(href_list["logout"]) + authenticated = null + screen = null + active1 = null + active2 = null + + if(href_list["screen"]) + screen = text2num(href_list["screen"]) + if(screen < 1) + screen = MED_DATA_MAIN + + active1 = null + active2 = null + + if(href_list["vir"]) + var/type = href_list["vir"] + var/datum/disease/D = new type(0) + var/afs = "" + for(var/mob/M in D.viable_mobtypes) + afs += "[initial(M.name)];" + var/severity = D.severity + switch(severity) + if("Harmful", "Minor") + severity = "[severity]" + if("Medium") + severity = "[severity]" + if("Dangerous!") + severity = "[severity]" + if("BIOHAZARD THREAT!") + severity = "

        [severity]

        " + setTemp({"Name: [D.name] +
        Number of stages: [D.max_stages] +
        Spread: [D.spread_text] Transmission +
        Possible Cure: [(D.cure_text||"none")] +
        Affected Lifeforms:[afs]
        +
        Notes: [D.desc]
        +
        Severity: [severity]"}) + qdel(D) + + if(href_list["del_all"]) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_all2=1") + buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null) + setTemp("

        Are you sure you wish to delete all records?

        ", buttons) + + if(href_list["field"]) + if(..()) + return 1 + var/a1 = active1 + var/a2 = active2 + switch(href_list["field"]) + if("fingerprint") + if(istype(active1, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please input fingerprint hash:", "Med. records", active1.fields["fingerprint"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active1 != a1) + return 1 + active1.fields["fingerprint"] = t1 + if("sex") + if(istype(active1, /datum/data/record)) + if(active1.fields["sex"] == "Male") + active1.fields["sex"] = "Female" + else + active1.fields["sex"] = "Male" + if("age") + if(istype(active1, /datum/data/record)) + var/t1 = input("Please input age:", "Med. records", active1.fields["age"], null) as num + if(!t1 || ..() || active1 != a1) + return 1 + active1.fields["age"] = t1 + if("mi_dis") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please input minor disabilities list:", "Med. records", active2.fields["mi_dis"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["mi_dis"] = t1 + if("mi_dis_d") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please summarize minor dis.:", "Med. records", active2.fields["mi_dis_d"], null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["mi_dis_d"] = t1 + if("ma_dis") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please input major diabilities list:", "Med. records", active2.fields["ma_dis"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["ma_dis"] = t1 + if("ma_dis_d") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please summarize major dis.:", "Med. records", active2.fields["ma_dis_d"], null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["ma_dis_d"] = t1 + if("alg") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please state allergies:", "Med. records", active2.fields["alg"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["alg"] = t1 + if("alg_d") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please summarize allergies:", "Med. records", active2.fields["alg_d"], null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["alg_d"] = t1 + if("cdi") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please state diseases:", "Med. records", active2.fields["cdi"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["cdi"] = t1 + if("cdi_d") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please summarize diseases:", "Med. records", active2.fields["cdi_d"], null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["cdi_d"] = t1 + if("notes") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(html_encode(trim(input("Please summarize notes:", "Med. records", html_decode(active2.fields["notes"]), null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["notes"] = t1 + if("p_stat") + if(istype(active1, /datum/data/record)) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "*Deceased*", "icon" = "stethoscope", "href" = "p_stat=deceased", "status" = (active1.fields["p_stat"] == "*Deceased*" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "*SSD*", "icon" = "stethoscope", "href" = "p_stat=ssd", "status" = (active1.fields["p_stat"] == "*SSD*" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "Active", "icon" = "stethoscope", "href" = "p_stat=active", "status" = (active1.fields["p_stat"] == "Active" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "Physically Unfit", "icon" = "stethoscope", "href" = "p_stat=unfit", "status" = (active1.fields["p_stat"] == "Physically Unfit" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "Disabled", "icon" = "stethoscope", "href" = "p_stat=disabled", "status" = (active1.fields["p_stat"] == "Disabled" ? "selected" : null)) + setTemp("

        Physical Condition

        ", buttons) + if("m_stat") + if(istype(active1, /datum/data/record)) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "*Insane*", "icon" = "stethoscope", "href" = "m_stat=insane", "status" = (active1.fields["m_stat"] == "*Insane*" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "*Unstable*", "icon" = "stethoscope", "href" = "m_stat=unstable", "status" = (active1.fields["m_stat"] == "*Unstable*" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "*Watch*", "icon" = "stethoscope", "href" = "m_stat=watch", "status" = (active1.fields["m_stat"] == "*Watch*" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "Stable", "icon" = "stethoscope", "href" = "m_stat=stable", "status" = (active1.fields["m_stat"] == "Stable" ? "selected" : null)) + setTemp("

        Mental Condition

        ", buttons) + if("blood_type") + if(istype(active2, /datum/data/record)) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "A-", "icon" = "tint", "href" = "blood_type=an", "status" = (active2.fields["blood_type"] == "A-" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "A+", "icon" = "tint", "href" = "blood_type=ap", "status" = (active2.fields["blood_type"] == "A+" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "B-", "icon" = "tint", "href" = "blood_type=bn", "status" = (active2.fields["blood_type"] == "B-" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "B+", "icon" = "tint", "href" = "blood_type=bp", "status" = (active2.fields["blood_type"] == "B+" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "AB-", "icon" = "tint", "href" = "blood_type=abn", "status" = (active2.fields["blood_type"] == "AB-" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "AB+", "icon" = "tint", "href" = "blood_type=abp", "status" = (active2.fields["blood_type"] == "AB+" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "O-", "icon" = "tint", "href" = "blood_type=on", "status" = (active2.fields["blood_type"] == "O-" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "O+", "icon" = "tint", "href" = "blood_type=op", "status" = (active2.fields["blood_type"] == "O+" ? "selected" : null)) + setTemp("

        Blood Type

        ", buttons) + if("b_dna") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please input DNA hash:", "Med. records", active2.fields["b_dna"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["b_dna"] = t1 + if("vir_name") + var/datum/data/record/v = locate(href_list["edit_vir"]) + if(v) + var/t1 = copytext(trim(sanitize(input("Please input pathogen name:", "VirusDB", v.fields["name"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active1 != a1) + return 1 + v.fields["name"] = t1 + if("vir_desc") + var/datum/data/record/v = locate(href_list["edit_vir"]) + if(v) + var/t1 = copytext(trim(sanitize(input("Please input information about pathogen:", "VirusDB", v.fields["description"], null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active1 != a1) + return 1 + v.fields["description"] = t1 + + if(href_list["del_r"]) + if(active2) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_r2=1", "status" = null) + buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) + setTemp("

        Are you sure you wish to delete the record (Medical Portion Only)?

        ", buttons) + + if(href_list["d_rec"]) + var/datum/data/record/R = locate(href_list["d_rec"]) + var/datum/data/record/M = locate(href_list["d_rec"]) + if(!data_core.general.Find(R)) + setTemp("

        Record not found!

        ") + return 1 + for(var/datum/data/record/E in data_core.medical) + if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"]) + M = E + active1 = R + active2 = M + screen = MED_DATA_RECORD + + if(href_list["new"]) + if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record)) + var/datum/data/record/R = new /datum/data/record() + R.fields["name"] = active1.fields["name"] + R.fields["id"] = active1.fields["id"] + R.name = "Medical Record #[R.fields["id"]]" + R.fields["blood_type"] = "Unknown" + R.fields["b_dna"] = "Unknown" + R.fields["mi_dis"] = "None" + R.fields["mi_dis_d"] = "No minor disabilities have been declared." + R.fields["ma_dis"] = "None" + R.fields["ma_dis_d"] = "No major disabilities have been diagnosed." + R.fields["alg"] = "None" + R.fields["alg_d"] = "No allergies have been detected in this patient." + R.fields["cdi"] = "None" + R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." + R.fields["notes"] = "No notes." + data_core.medical += R + active2 = R + screen = MED_DATA_RECORD + + if(href_list["add_c"]) + if(!istype(active2, /datum/data/record)) + return 1 + var/a2 = active2 + var/t1 = copytext(trim(sanitize(input("Add Comment:", "Med. records", null, null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["comments"] += "Made by [authenticated] ([rank]) on [current_date_string] [station_time_timestamp()]
        [t1]" + + if(href_list["del_c"]) + var/index = min(max(text2num(href_list["del_c"]) + 1, 1), length(active2.fields["comments"])) + if(istype(active2, /datum/data/record) && active2.fields["comments"][index]) + active2.fields["comments"] -= active2.fields["comments"][index] + + if(href_list["search"]) + var/t1 = clean_input("Search String: (Name, DNA, or ID)", "Med. records", null, null) + if(!t1 || ..()) + return 1 + active1 = null + active2 = null + t1 = lowertext(t1) + for(var/datum/data/record/R in data_core.medical) + if(t1 == lowertext(R.fields["name"]) || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"])) + active2 = R + if(!active2) + setTemp("

        Could not locate record [t1].

        ") + else + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == active2.fields["name"] && E.fields["id"] == active2.fields["id"]) + active1 = E + screen = MED_DATA_RECORD + + if(href_list["print_p"]) + if(!printing) + printing = 1 + playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) + sleep(50) + var/obj/item/paper/P = new /obj/item/paper(loc) + P.info = "
        Medical Record

        " + if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) + P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]] +
        \nSex: [active1.fields["sex"]] +
        \nAge: [active1.fields["age"]] +
        \nFingerprint: [active1.fields["fingerprint"]] +
        \nPhysical Status: [active1.fields["p_stat"]] +
        \nMental Status: [active1.fields["m_stat"]]
        "} + else + P.info += "General Record Lost!
        " + if(istype(active2, /datum/data/record) && data_core.medical.Find(active2)) + P.info += {"
        \n
        Medical Data
        +
        \nBlood Type: [active2.fields["blood_type"]] +
        \nDNA: [active2.fields["b_dna"]]
        \n +
        \nMinor Disabilities: [active2.fields["mi_dis"]] +
        \nDetails: [active2.fields["mi_dis_d"]]
        \n +
        \nMajor Disabilities: [active2.fields["ma_dis"]] +
        \nDetails: [active2.fields["ma_dis_d"]]
        \n +
        \nAllergies: [active2.fields["alg"]] +
        \nDetails: [active2.fields["alg_d"]]
        \n +
        \nCurrent Diseases: [active2.fields["cdi"]] (per disease info placed in log/comment section) +
        \nDetails: [active2.fields["cdi_d"]]
        \n +
        \nImportant Notes: +
        \n\t[active2.fields["notes"]]
        \n +
        \n +
        Comments/Log

        "} + for(var/c in active2.fields["comments"]) + P.info += "[c]
        " + else + P.info += "Medical Record Lost!
        " + P.info += "
        " + P.name = "paper- 'Medical Record: [active1.fields["name"]]'" + printing = 0 + return 1 + +/obj/machinery/computer/med_data/proc/setTemp(text, list/buttons = list()) + temp = list("text" = text, "buttons" = buttons, "has_buttons" = buttons.len > 0) + +/obj/machinery/computer/med_data/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + return ..(severity) + + for(var/datum/data/record/R in data_core.medical) + if(prob(10/severity)) + switch(rand(1,6)) + if(1) + R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" + if(2) + R.fields["sex"] = pick("Male", "Female") + if(3) + R.fields["age"] = rand(5, 85) + if(4) + R.fields["blood_type"] = pick("A-", "B-", "AB-", "O-", "A+", "B+", "AB+", "O+") + if(5) + R.fields["p_stat"] = pick("*SSD*", "Active", "Physically Unfit", "Disabled") + if(6) + R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") + continue + + else if(prob(1)) + qdel(R) + continue + + ..(severity) + + +/obj/machinery/computer/med_data/laptop + name = "medical laptop" + desc = "Cheap Nanotrasen laptop." + icon_state = "laptop" + icon_keyboard = "laptop_key" + icon_screen = "medlaptop" + density = 0 + +#undef MED_DATA_MAIN +#undef MED_DATA_R_LIST +#undef MED_DATA_MAINT +#undef MED_DATA_RECORD +#undef MED_DATA_V_DATA +#undef MED_DATA_MEDBOT diff --git a/code/game/machinery/computer/power.dm b/code/game/machinery/computer/power.dm index 416c73d2570..fce315a1db0 100644 --- a/code/game/machinery/computer/power.dm +++ b/code/game/machinery/computer/power.dm @@ -1,65 +1,65 @@ -/obj/machinery/computer/monitor - name = "power monitoring console" - desc = "Used to monitor power levels across the station." - icon_screen = "power" - icon_keyboard = "power_key" - use_power = ACTIVE_POWER_USE - idle_power_usage = 20 - active_power_usage = 80 - light_color = LIGHT_COLOR_ORANGE - circuit = /obj/item/circuitboard/powermonitor - var/datum/powernet/powernet = null - var/datum/nano_module/power_monitor/power_monitor - var/is_secret_monitor = FALSE - -/obj/machinery/computer/monitor/secret //Hides the power monitor (such as ones on ruins & CentCom) from PDA's to prevent metagaming. - name = "outdated power monitoring console" - desc = "It monitors power levels across the local powernet." - circuit = /obj/item/circuitboard/powermonitor/secret - is_secret_monitor = TRUE - -/obj/machinery/computer/monitor/New() - ..() - GLOB.power_monitors += src - GLOB.power_monitors = sortAtom(GLOB.power_monitors) - power_monitor = new(src) - -/obj/machinery/computer/monitor/Initialize() - ..() - powermonitor_repository.update_cache() - powernet = find_powernet() - -/obj/machinery/computer/monitor/Destroy() - GLOB.power_monitors -= src - powermonitor_repository.update_cache() - QDEL_NULL(power_monitor) - return ..() - -/obj/machinery/computer/monitor/power_change() - ..() - powermonitor_repository.update_cache() - -/obj/machinery/computer/monitor/proc/find_powernet() - var/obj/structure/cable/attached = null - var/turf/T = loc - if(isturf(T)) - attached = locate() in T - if(attached) - return attached.powernet - -/obj/machinery/computer/monitor/attack_ai(mob/user) - attack_hand(user) - -/obj/machinery/computer/monitor/attack_hand(mob/user) - add_fingerprint(user) - if(stat & (BROKEN|NOPOWER)) - return - // Update the powernet - powernet = find_powernet() - ui_interact(user) - -/obj/machinery/computer/monitor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - power_monitor.ui_interact(user, ui_key, ui, force_open) - -/obj/machinery/computer/monitor/interact(mob/user) - power_monitor.ui_interact(user) +/obj/machinery/computer/monitor + name = "power monitoring console" + desc = "Used to monitor power levels across the station." + icon_screen = "power" + icon_keyboard = "power_key" + use_power = ACTIVE_POWER_USE + idle_power_usage = 20 + active_power_usage = 80 + light_color = LIGHT_COLOR_ORANGE + circuit = /obj/item/circuitboard/powermonitor + var/datum/powernet/powernet = null + var/datum/nano_module/power_monitor/power_monitor + var/is_secret_monitor = FALSE + +/obj/machinery/computer/monitor/secret //Hides the power monitor (such as ones on ruins & CentCom) from PDA's to prevent metagaming. + name = "outdated power monitoring console" + desc = "It monitors power levels across the local powernet." + circuit = /obj/item/circuitboard/powermonitor/secret + is_secret_monitor = TRUE + +/obj/machinery/computer/monitor/New() + ..() + GLOB.power_monitors += src + GLOB.power_monitors = sortAtom(GLOB.power_monitors) + power_monitor = new(src) + +/obj/machinery/computer/monitor/Initialize() + ..() + powermonitor_repository.update_cache() + powernet = find_powernet() + +/obj/machinery/computer/monitor/Destroy() + GLOB.power_monitors -= src + powermonitor_repository.update_cache() + QDEL_NULL(power_monitor) + return ..() + +/obj/machinery/computer/monitor/power_change() + ..() + powermonitor_repository.update_cache() + +/obj/machinery/computer/monitor/proc/find_powernet() + var/obj/structure/cable/attached = null + var/turf/T = loc + if(isturf(T)) + attached = locate() in T + if(attached) + return attached.powernet + +/obj/machinery/computer/monitor/attack_ai(mob/user) + attack_hand(user) + +/obj/machinery/computer/monitor/attack_hand(mob/user) + add_fingerprint(user) + if(stat & (BROKEN|NOPOWER)) + return + // Update the powernet + powernet = find_powernet() + ui_interact(user) + +/obj/machinery/computer/monitor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + power_monitor.ui_interact(user, ui_key, ui, force_open) + +/obj/machinery/computer/monitor/interact(mob/user) + power_monitor.ui_interact(user) diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index fb4683ba3a9..8ecd463bc35 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -1,240 +1,240 @@ -/obj/machinery/computer/robotics - name = "robotics control console" - desc = "Used to remotely lockdown or detonate linked Cyborgs." - icon = 'icons/obj/computer.dmi' - icon_keyboard = "tech_key" - icon_screen = "robot" - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/robotics - var/temp = null - - light_color = LIGHT_COLOR_PURPLE - - var/safety = 1 - -/obj/machinery/computer/robotics/attack_ai(var/mob/user as mob) - return attack_hand(user) - -/obj/machinery/computer/robotics/attack_hand(var/mob/user as mob) - if(..()) - return - if(stat & (NOPOWER|BROKEN)) - return - ui_interact(user) - -/obj/machinery/computer/robotics/proc/is_authenticated(var/mob/user as mob) - if(user.can_admin_interact()) - return 1 - else if(allowed(user)) - return 1 - return 0 - -/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "robot_control.tmpl", "Robotic Control Console", 400, 500) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/computer/robotics/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - var/list/robots = get_cyborgs(user) - if(robots.len) - data["robots"] = robots - data["safety"] = safety - // Also applies for cyborgs. Hides the manual self-destruct button. - data["is_ai"] = issilicon(user) - data["allowed"] = is_authenticated(user) - return data - -/obj/machinery/computer/robotics/Topic(href, href_list) - if(..()) - return 1 - - var/mob/user = usr - if(!is_authenticated(user)) - to_chat(user, "Access denied.") - return - - // Destroys the cyborg - if(href_list["detonate"]) - var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["detonate"]) - if(!target || !istype(target)) - return - if(isAI(user) && (target.connected_ai != user)) - to_chat(user, "Access Denied. This robot is not linked to you.") - return - // Cyborgs may blow up themselves via the console - if((isrobot(user) && user != target) || !is_authenticated(user)) - to_chat(user, "Access Denied.") - return - var/choice = input("Really detonate [target.name]?") in list ("Yes", "No") - if(choice != "Yes") - return - if(!target || !istype(target)) - return - - // Antagonistic cyborgs? Left here for downstream - if(target.mind && target.mind.special_role && target.emagged) - to_chat(target, "Extreme danger. Termination codes detected. Scrambling security codes and automatic AI unlink triggered.") - target.ResetSecurityCodes() - else - message_admins("[key_name_admin(usr)] detonated [key_name_admin(target)] (JMP)!") - log_game("\[key_name(usr)] detonated [key_name(target)]!") - to_chat(target, "Self-destruct command received.") - if(target.connected_ai) - to_chat(target.connected_ai, "

        ALERT - Cyborg detonation detected: [target.name]
        ") - spawn(10) - target.self_destruct() - - // Locks or unlocks the cyborg - else if(href_list["lockdown"]) - var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["lockdown"]) - if(!target || !istype(target)) - return - - if(isAI(user) && (target.connected_ai != user)) - to_chat(user, "Access Denied. This robot is not linked to you.") - return - - if(isrobot(user)) - to_chat(user, "Access Denied.") - return - - var/choice = input("Really [target.lockcharge ? "unlock" : "lockdown"] [target.name] ?") in list ("Yes", "No") - if(choice != "Yes") - return - - if(!target || !istype(target)) - return - - message_admins("[key_name_admin(usr)] [target.canmove ? "locked down" : "released"] [key_name_admin(target)]!") - log_game("[key_name(usr)] [target.canmove ? "locked down" : "released"] [key_name(target)]!") - target.SetLockdown(!target.lockcharge) - to_chat(target, "[!target.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]") - if(target.connected_ai) - to_chat(target.connected_ai, "[!target.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [target.name]
        ") - - // Remotely hacks the cyborg. Only antag AIs can do this and only to linked cyborgs. - else if(href_list["hack"]) - var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["hack"]) - if(!target || !istype(target)) - return - - // Antag AI checks - if(!istype(user, /mob/living/silicon/ai) || !(user.mind.special_role && user.mind.original == user)) - to_chat(user, "Access Denied.") - return - - if(target.connected_ai != user) - to_chat(user, "Access Denied. This robot is not linked to you.") - return - - if(target.emagged) - to_chat(user, "Robot is already hacked.") - return - - var/choice = input("Really hack [target.name]? This cannot be undone.") in list("Yes", "No") - if(choice != "Yes") - return - - if(!target || !istype(target)) - return - - message_admins("[key_name_admin(usr)] emagged [key_name_admin(target)] using robotic console!") - log_game("[key_name(usr)] emagged [key_name(target)] using robotic console!") - target.emagged = 1 - to_chat(target, "Failsafe protocols overriden. New tools available.") - - // Arms the emergency self-destruct system - else if(href_list["arm"]) - if(istype(user, /mob/living/silicon)) - to_chat(user, "Access Denied.") - return - - safety = !safety - to_chat(user, "You [safety ? "disarm" : "arm"] the emergency self destruct.") - - // Destroys all accessible cyborgs if safety is disabled - else if(href_list["nuke"]) - if(istype(user, /mob/living/silicon)) - to_chat(user, "Access Denied") - return - if(safety) - to_chat(user, "Self-destruct aborted - safety active") - return - - message_admins("[key_name_admin(usr)] detonated all cyborgs!") - log_game("\[key_name(usr)] detonated all cyborgs!") - - for(var/mob/living/silicon/robot/R in GLOB.mob_list) - if(istype(R, /mob/living/silicon/robot/drone)) - continue - // Ignore antagonistic cyborgs - if(R.scrambledcodes) - continue - to_chat(R, "Self-destruct command received.") - if(R.connected_ai) - to_chat(R.connected_ai, "

        ALERT - Cyborg detonation detected: [R.name]
        ") - spawn(10) - R.self_destruct() - -// Proc: get_cyborgs() -// Parameters: 1 (operator - mob which is operating the console.) -// Description: Returns NanoUI-friendly list of accessible cyborgs. -/obj/machinery/computer/robotics/proc/get_cyborgs(var/mob/operator) - var/list/robots = list() - - for(var/mob/living/silicon/robot/R in GLOB.mob_list) - // Ignore drones - if(istype(R, /mob/living/silicon/robot/drone)) - continue - // Ignore antagonistic cyborgs - if(R.scrambledcodes) - continue - - var/list/robot = list() - robot["name"] = R.name - if(R.stat) - robot["status"] = "Not Responding" - else if(!R.canmove) - robot["status"] = "Lockdown" - else - robot["status"] = "Operational" - - if(R.cell) - robot["cell"] = 1 - robot["cell_capacity"] = R.cell.maxcharge - robot["cell_current"] = R.cell.charge - robot["cell_percentage"] = round(R.cell.percent()) - else - robot["cell"] = 0 - - var/turf/pos = get_turf(R) - var/area/bot_area = get_area(R) - robot["xpos"] = pos.x - robot["ypos"] = pos.y - robot["zpos"] = pos.z - robot["area"] = format_text(bot_area.name) - - robot["health"] = round(R.health * 100 / R.maxHealth,0.1) - - robot["module"] = R.module ? R.module.name : "None" - robot["master_ai"] = R.connected_ai ? R.connected_ai.name : "None" - robot["hackable"] = 0 - // Antag AIs know whether linked cyborgs are hacked or not. - if(operator && istype(operator, /mob/living/silicon/ai) && (R.connected_ai == operator) && (operator.mind.special_role && operator.mind.original == operator)) - robot["hacked"] = R.emagged ? 1 : 0 - robot["hackable"] = R.emagged? 0 : 1 - robots.Add(list(robot)) - return robots - -// Proc: get_cyborg_by_name() -// Parameters: 1 (name - Cyborg we are trying to find) -// Description: Helper proc for finding cyborg by name -/obj/machinery/computer/robotics/proc/get_cyborg_by_name(var/name) - if(!name) - return - for(var/mob/living/silicon/robot/R in GLOB.mob_list) - if(R.name == name) - return R +/obj/machinery/computer/robotics + name = "robotics control console" + desc = "Used to remotely lockdown or detonate linked Cyborgs." + icon = 'icons/obj/computer.dmi' + icon_keyboard = "tech_key" + icon_screen = "robot" + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/robotics + var/temp = null + + light_color = LIGHT_COLOR_PURPLE + + var/safety = 1 + +/obj/machinery/computer/robotics/attack_ai(var/mob/user as mob) + return attack_hand(user) + +/obj/machinery/computer/robotics/attack_hand(var/mob/user as mob) + if(..()) + return + if(stat & (NOPOWER|BROKEN)) + return + ui_interact(user) + +/obj/machinery/computer/robotics/proc/is_authenticated(var/mob/user as mob) + if(user.can_admin_interact()) + return 1 + else if(allowed(user)) + return 1 + return 0 + +/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "robot_control.tmpl", "Robotic Control Console", 400, 500) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/robotics/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + var/list/robots = get_cyborgs(user) + if(robots.len) + data["robots"] = robots + data["safety"] = safety + // Also applies for cyborgs. Hides the manual self-destruct button. + data["is_ai"] = issilicon(user) + data["allowed"] = is_authenticated(user) + return data + +/obj/machinery/computer/robotics/Topic(href, href_list) + if(..()) + return 1 + + var/mob/user = usr + if(!is_authenticated(user)) + to_chat(user, "Access denied.") + return + + // Destroys the cyborg + if(href_list["detonate"]) + var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["detonate"]) + if(!target || !istype(target)) + return + if(isAI(user) && (target.connected_ai != user)) + to_chat(user, "Access Denied. This robot is not linked to you.") + return + // Cyborgs may blow up themselves via the console + if((isrobot(user) && user != target) || !is_authenticated(user)) + to_chat(user, "Access Denied.") + return + var/choice = input("Really detonate [target.name]?") in list ("Yes", "No") + if(choice != "Yes") + return + if(!target || !istype(target)) + return + + // Antagonistic cyborgs? Left here for downstream + if(target.mind && target.mind.special_role && target.emagged) + to_chat(target, "Extreme danger. Termination codes detected. Scrambling security codes and automatic AI unlink triggered.") + target.ResetSecurityCodes() + else + message_admins("[key_name_admin(usr)] detonated [key_name_admin(target)] (JMP)!") + log_game("\[key_name(usr)] detonated [key_name(target)]!") + to_chat(target, "Self-destruct command received.") + if(target.connected_ai) + to_chat(target.connected_ai, "

        ALERT - Cyborg detonation detected: [target.name]
        ") + spawn(10) + target.self_destruct() + + // Locks or unlocks the cyborg + else if(href_list["lockdown"]) + var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["lockdown"]) + if(!target || !istype(target)) + return + + if(isAI(user) && (target.connected_ai != user)) + to_chat(user, "Access Denied. This robot is not linked to you.") + return + + if(isrobot(user)) + to_chat(user, "Access Denied.") + return + + var/choice = input("Really [target.lockcharge ? "unlock" : "lockdown"] [target.name] ?") in list ("Yes", "No") + if(choice != "Yes") + return + + if(!target || !istype(target)) + return + + message_admins("[key_name_admin(usr)] [target.canmove ? "locked down" : "released"] [key_name_admin(target)]!") + log_game("[key_name(usr)] [target.canmove ? "locked down" : "released"] [key_name(target)]!") + target.SetLockdown(!target.lockcharge) + to_chat(target, "[!target.lockcharge ? "Your lockdown has been lifted!" : "You have been locked down!"]") + if(target.connected_ai) + to_chat(target.connected_ai, "[!target.lockcharge ? "NOTICE - Cyborg lockdown lifted" : "ALERT - Cyborg lockdown detected"]: [target.name]
        ") + + // Remotely hacks the cyborg. Only antag AIs can do this and only to linked cyborgs. + else if(href_list["hack"]) + var/mob/living/silicon/robot/target = get_cyborg_by_name(href_list["hack"]) + if(!target || !istype(target)) + return + + // Antag AI checks + if(!istype(user, /mob/living/silicon/ai) || !(user.mind.special_role && user.mind.original == user)) + to_chat(user, "Access Denied.") + return + + if(target.connected_ai != user) + to_chat(user, "Access Denied. This robot is not linked to you.") + return + + if(target.emagged) + to_chat(user, "Robot is already hacked.") + return + + var/choice = input("Really hack [target.name]? This cannot be undone.") in list("Yes", "No") + if(choice != "Yes") + return + + if(!target || !istype(target)) + return + + message_admins("[key_name_admin(usr)] emagged [key_name_admin(target)] using robotic console!") + log_game("[key_name(usr)] emagged [key_name(target)] using robotic console!") + target.emagged = 1 + to_chat(target, "Failsafe protocols overriden. New tools available.") + + // Arms the emergency self-destruct system + else if(href_list["arm"]) + if(istype(user, /mob/living/silicon)) + to_chat(user, "Access Denied.") + return + + safety = !safety + to_chat(user, "You [safety ? "disarm" : "arm"] the emergency self destruct.") + + // Destroys all accessible cyborgs if safety is disabled + else if(href_list["nuke"]) + if(istype(user, /mob/living/silicon)) + to_chat(user, "Access Denied") + return + if(safety) + to_chat(user, "Self-destruct aborted - safety active") + return + + message_admins("[key_name_admin(usr)] detonated all cyborgs!") + log_game("\[key_name(usr)] detonated all cyborgs!") + + for(var/mob/living/silicon/robot/R in GLOB.mob_list) + if(istype(R, /mob/living/silicon/robot/drone)) + continue + // Ignore antagonistic cyborgs + if(R.scrambledcodes) + continue + to_chat(R, "Self-destruct command received.") + if(R.connected_ai) + to_chat(R.connected_ai, "

        ALERT - Cyborg detonation detected: [R.name]
        ") + spawn(10) + R.self_destruct() + +// Proc: get_cyborgs() +// Parameters: 1 (operator - mob which is operating the console.) +// Description: Returns NanoUI-friendly list of accessible cyborgs. +/obj/machinery/computer/robotics/proc/get_cyborgs(var/mob/operator) + var/list/robots = list() + + for(var/mob/living/silicon/robot/R in GLOB.mob_list) + // Ignore drones + if(istype(R, /mob/living/silicon/robot/drone)) + continue + // Ignore antagonistic cyborgs + if(R.scrambledcodes) + continue + + var/list/robot = list() + robot["name"] = R.name + if(R.stat) + robot["status"] = "Not Responding" + else if(!R.canmove) + robot["status"] = "Lockdown" + else + robot["status"] = "Operational" + + if(R.cell) + robot["cell"] = 1 + robot["cell_capacity"] = R.cell.maxcharge + robot["cell_current"] = R.cell.charge + robot["cell_percentage"] = round(R.cell.percent()) + else + robot["cell"] = 0 + + var/turf/pos = get_turf(R) + var/area/bot_area = get_area(R) + robot["xpos"] = pos.x + robot["ypos"] = pos.y + robot["zpos"] = pos.z + robot["area"] = format_text(bot_area.name) + + robot["health"] = round(R.health * 100 / R.maxHealth,0.1) + + robot["module"] = R.module ? R.module.name : "None" + robot["master_ai"] = R.connected_ai ? R.connected_ai.name : "None" + robot["hackable"] = 0 + // Antag AIs know whether linked cyborgs are hacked or not. + if(operator && istype(operator, /mob/living/silicon/ai) && (R.connected_ai == operator) && (operator.mind.special_role && operator.mind.original == operator)) + robot["hacked"] = R.emagged ? 1 : 0 + robot["hackable"] = R.emagged? 0 : 1 + robots.Add(list(robot)) + return robots + +// Proc: get_cyborg_by_name() +// Parameters: 1 (name - Cyborg we are trying to find) +// Description: Helper proc for finding cyborg by name +/obj/machinery/computer/robotics/proc/get_cyborg_by_name(var/name) + if(!name) + return + for(var/mob/living/silicon/robot/R in GLOB.mob_list) + if(R.name == name) + return R diff --git a/code/game/machinery/computer/salvage_ship.dm b/code/game/machinery/computer/salvage_ship.dm index df22a3cad14..18e4718bf97 100644 --- a/code/game/machinery/computer/salvage_ship.dm +++ b/code/game/machinery/computer/salvage_ship.dm @@ -106,4 +106,4 @@ return /obj/machinery/computer/salvage_ship/bullet_act(var/obj/item/projectile/Proj) - visible_message("[Proj] ricochets off [src]!") \ No newline at end of file + visible_message("[Proj] ricochets off [src]!") diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm index 90ac427743d..cb5e799b89e 100644 --- a/code/game/machinery/computer/security.dm +++ b/code/game/machinery/computer/security.dm @@ -1,578 +1,578 @@ -#define SEC_DATA_R_LIST 1 // Record list -#define SEC_DATA_MAINT 2 // Records maintenance -#define SEC_DATA_RECORD 3 // Record - -/obj/machinery/computer/secure_data//TODO:SANITY - name = "security records" - desc = "Used to view and edit personnel's security records." - icon_keyboard = "security_key" - icon_screen = "security" - req_one_access = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS) - circuit = /obj/item/circuitboard/secure_data - var/obj/item/card/id/scan = null - var/authenticated = null - var/rank = null - var/list/authcard_access = list() - var/screen = null - var/datum/data/record/active1 = null - var/datum/data/record/active2 = null - var/temp = null - var/printing = null - //Sorting Variables - var/sortBy = "name" - var/order = 1 // -1 = Descending - 1 = Ascending - - light_color = LIGHT_COLOR_RED - -/obj/machinery/computer/secure_data/Destroy() - active1 = null - active2 = null - return ..() - -/obj/machinery/computer/secure_data/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/card/id) && !scan) - user.drop_item() - O.forceMove(src) - scan = O - ui_interact(user) - return - return ..() - -//Someone needs to break down the dat += into chunks instead of long ass lines. -/obj/machinery/computer/secure_data/attack_hand(mob/user) - if(..()) - return - if(is_away_level(z)) - to_chat(user, "Unable to establish a connection: You're too far away from the station!") - return - add_fingerprint(user) - ui_interact(user) - -/obj/machinery/computer/secure_data/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "secure_data.tmpl", name, 800, 800) - ui.open() - -/obj/machinery/computer/secure_data/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - data["temp"] = temp - data["scan"] = scan ? scan.name : null - data["authenticated"] = authenticated - data["screen"] = screen - if(authenticated) - switch(screen) - if(SEC_DATA_R_LIST) - if(!isnull(data_core.general)) - for(var/datum/data/record/R in sortRecord(data_core.general, sortBy, order)) - var/crimstat = "null" - for(var/datum/data/record/E in data_core.security) - if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"]) - crimstat = E.fields["criminal"] - break - var/background = "''" - switch(crimstat) - if("*Execute*") - background = "'background-color:#5E0A1A'" - if("*Arrest*") - background = "'background-color:#890E26'" - if("Incarcerated") - background = "'background-color:#743B03'" - if("Parolled") - background = "'background-color:#743B03'" - if("Released") - background = "'background-color:#216489'" - if("None") - background = "'background-color:#007f47'" - if("null") - crimstat = "No record." - data["records"] += list(list("ref" = "\ref[R]", "id" = R.fields["id"], "name" = R.fields["name"], "rank" = R.fields["rank"], "fingerprint" = R.fields["fingerprint"], "background" = background, "crimstat" = crimstat)) - if(SEC_DATA_RECORD) - var/list/general = list() - data["general"] = general - if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) - var/list/fields = list() - general["fields"] = fields - fields[++fields.len] = list("field" = "Name:", "value" = active1.fields["name"], "edit" = "name") - fields[++fields.len] = list("field" = "ID:", "value" = active1.fields["id"], "edit" = "id") - fields[++fields.len] = list("field" = "Sex:", "value" = active1.fields["sex"], "edit" = "sex") - fields[++fields.len] = list("field" = "Age:", "value" = active1.fields["age"], "edit" = "age") - fields[++fields.len] = list("field" = "Rank:", "value" = active1.fields["rank"], "edit" = "rank") - fields[++fields.len] = list("field" = "Fingerprint:", "value" = active1.fields["fingerprint"], "edit" = "fingerprint") - fields[++fields.len] = list("field" = "Physical Status:", "value" = active1.fields["p_stat"], "edit" = null) - fields[++fields.len] = list("field" = "Mental Status:", "value" = active1.fields["m_stat"], "edit" = null) - var/list/photos = list() - general["photos"] = photos - photos[++photos.len] = list("photo" = active1.fields["photo-south"]) - photos[++photos.len] = list("photo" = active1.fields["photo-west"]) - general["has_photos"] += (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0) - general["empty"] = 0 - else - general["empty"] = 1 - - var/list/security = list() - data["security"] = security - if(istype(active2, /datum/data/record) && data_core.security.Find(active2)) - var/list/fields = list() - security["fields"] = fields - fields[++fields.len] = list("field" = "Criminal Status:", "value" = active2.fields["criminal"], "edit" = "criminal", "line_break" = 1) - fields[++fields.len] = list("field" = "Minor Crimes:", "value" = active2.fields["mi_crim"], "edit" = "mi_crim", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["mi_crim_d"], "edit" = "mi_crim_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Major Crimes:", "value" = active2.fields["ma_crim"], "edit" = "ma_crim", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["ma_crim_d"], "edit" = "ma_crim_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Important Notes:", "value" = active2.fields["notes"], "edit" = "notes", "line_break" = 0) - if(!active2.fields["comments"] || !islist(active2.fields["comments"])) - active2.fields["comments"] = list() - security["comments"] = active2.fields["comments"] - security["empty"] = 0 - else - security["empty"] = 1 - return data - -/obj/machinery/computer/secure_data/Topic(href, href_list) - if(..()) - return 1 - - if(!data_core.general.Find(active1)) - active1 = null - if(!data_core.security.Find(active2)) - active2 = null - - if(href_list["temp"]) - temp = null - - if(href_list["temp_action"]) - var/temp_href = splittext(href_list["temp_action"], "=") - switch(temp_href[1]) - if("del_all2") - for(var/datum/data/record/R in data_core.security) - qdel(R) - update_all_mob_security_hud() - setTemp("

        All records deleted.

        ") - if("del_alllogs2") - if(GLOB.cell_logs.len) - setTemp("

        All cell logs deleted.

        ") - GLOB.cell_logs.Cut() - else - to_chat(usr, "Error; No cell logs to delete.") - if("del_r2") - if(active2) - qdel(active2) - update_all_mob_security_hud() - if("del_rg2") - if(active1) - for(var/datum/data/record/R in data_core.medical) - if(R.fields["name"] == active1.fields["name"] && R.fields["id"] == active1.fields["id"]) - qdel(R) - QDEL_NULL(active1) - QDEL_NULL(active2) - update_all_mob_security_hud() - screen = SEC_DATA_R_LIST - if("criminal") - if(active2) - var/t1 - if(temp_href[2] == "execute") - t1 = copytext(trim(sanitize(input("Explain why they are being executed. Include a list of their crimes, and victims.", "EXECUTION ORDER", null, null) as text)), 1, MAX_MESSAGE_LEN) - else - t1 = copytext(trim(sanitize(input("Enter Reason:", "Secure. records", null, null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1) - t1 = "(none)" - if(!set_criminal_status(usr, active2, temp_href[2], t1, rank, authcard_access)) - setTemp("

        Error: permission denied.

        ") - return 1 - if("rank") - if(active1) - active1.fields["rank"] = temp_href[2] - if(temp_href[2] in GLOB.joblist) - active1.fields["real_rank"] = temp_href[2] - - if(href_list["scan"]) - if(scan) - scan.forceMove(loc) - if(ishuman(usr) && !usr.get_active_hand()) - usr.put_in_hands(scan) - scan = null - else - var/obj/item/I = usr.get_active_hand() - if(istype(I, /obj/item/card/id)) - usr.drop_item() - I.forceMove(src) - scan = I - - if(href_list["login"]) - if(isAI(usr)) - authenticated = usr.name - rank = "AI" - else if(isrobot(usr)) - authenticated = usr.name - var/mob/living/silicon/robot/R = usr - rank = "[R.modtype] [R.braintype]" - else if(istype(scan, /obj/item/card/id)) - if(check_access(scan)) - authenticated = scan.registered_name - rank = scan.assignment - authcard_access = scan.access - - if(authenticated) - active1 = null - active2 = null - screen = SEC_DATA_R_LIST - - if(authenticated) - if(href_list["logout"]) - authenticated = null - screen = null - active1 = null - active2 = null - authcard_access = list() - - else if(href_list["sort"]) - // Reverse the order if clicked twice - if(sortBy == href_list["sort"]) - if(order == 1) - order = -1 - else - order = 1 - else - sortBy = href_list["sort"] - order = initial(order) - - else if(href_list["screen"]) - screen = text2num(href_list["screen"]) - if(screen < 1) - screen = SEC_DATA_R_LIST - - active1 = null - active2 = null - - else if(href_list["d_rec"]) - var/datum/data/record/R = locate(href_list["d_rec"]) - var/datum/data/record/M = locate(href_list["d_rec"]) - if(!data_core.general.Find(R)) - setTemp("

        Record not found!

        ") - return 1 - for(var/datum/data/record/E in data_core.security) - if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"]) - M = E - active1 = R - active2 = M - screen = SEC_DATA_RECORD - - else if(href_list["del_all"]) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_all2=1", "status" = null) - buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) - setTemp("

        Are you sure you wish to delete all records?

        ", buttons) - - else if(href_list["del_alllogs"]) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_alllogs2=1", "status" = null) - buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) - setTemp("

        Are you sure you wish to delete all cell logs?

        ", buttons) - - else if(href_list["del_rg"]) - if(active1) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_rg2=1", "status" = null) - buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) - setTemp("

        Are you sure you wish to delete the record (ALL)?

        ", buttons) - - else if(href_list["del_r"]) - if(active1) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_r2=1", "status" = null) - buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) - setTemp("

        Are you sure you wish to delete the record (Security Portion Only)?

        ", buttons) - - else if(href_list["new_s"]) - if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record)) - var/datum/data/record/R = new /datum/data/record() - R.fields["name"] = active1.fields["name"] - R.fields["id"] = active1.fields["id"] - R.name = "Security Record #[R.fields["id"]]" - R.fields["criminal"] = "None" - R.fields["mi_crim"] = "None" - R.fields["mi_crim_d"] = "No minor crime convictions." - R.fields["ma_crim"] = "None" - R.fields["ma_crim_d"] = "No major crime convictions." - R.fields["notes"] = "No notes." - data_core.security += R - active2 = R - screen = SEC_DATA_RECORD - - else if(href_list["new_g"]) - var/datum/data/record/G = new /datum/data/record() - G.fields["name"] = "New Record" - G.fields["id"] = "[add_zero(num2hex(rand(1, 1.6777215E7)), 6)]" - G.fields["rank"] = "Unassigned" - G.fields["real_rank"] = "Unassigned" - G.fields["sex"] = "Male" - G.fields["age"] = "Unknown" - G.fields["fingerprint"] = "Unknown" - G.fields["p_stat"] = "Active" - G.fields["m_stat"] = "Stable" - G.fields["species"] = "Human" - data_core.general += G - active1 = G - active2 = null - - else if(href_list["print_r"]) - if(!printing) - printing = 1 - playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) - sleep(50) - var/obj/item/paper/P = new /obj/item/paper(loc) - P.info = "
        Security Record

        " - if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) - P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]] -
        \nSex: [active1.fields["sex"]] -
        \nAge: [active1.fields["age"]] -
        \nFingerprint: [active1.fields["fingerprint"]] -
        \nPhysical Status: [active1.fields["p_stat"]] -
        \nMental Status: [active1.fields["m_stat"]]
        "} - else - P.info += "General Record Lost!
        " - if(istype(active2, /datum/data/record) && data_core.security.Find(active2)) - P.info += {"
        \n
        Security Data
        -
        \nCriminal Status: [active2.fields["criminal"]]
        \n -
        \nMinor Crimes: [active2.fields["mi_crim"]] -
        \nDetails: [active2.fields["mi_crim_d"]]
        \n -
        \nMajor Crimes: [active2.fields["ma_crim"]] -
        \nDetails: [active2.fields["ma_crim_d"]]
        \n -
        \nImportant Notes: -
        \n\t[active2.fields["notes"]]
        \n
        \n
        Comments/Log

        "} - for(var/c in active2.fields["comments"]) - P.info += "[c]
        " - else - P.info += "Security Record Lost!
        " - P.info += "" - P.name = "paper - 'Security Record: [active1.fields["name"]]'" - printing = 0 - -/* Removed due to BYOND issue - else if(href_list["print_p"]) - if(!printing) - printing = 1 - playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) - sleep(50) - if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) - create_record_photo(active1) - printing = 0 -*/ - - else if(href_list["printlogs"]) - if(GLOB.cell_logs.len && !printing) - var/obj/item/paper/P = input(usr, "Select log to print", "Available Cell Logs") as null|anything in GLOB.cell_logs - if(!P) - return 0 - printing = 1 - playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) - to_chat(usr, "Printing file [P.name].") - sleep(50) - var/obj/item/paper/log = new /obj/item/paper(loc) - log.name = P.name - log.info = P.info - printing = 0 - return 1 - else - to_chat(usr, "[src] has no logs stored or is already printing.") - - - else if(href_list["add_c"]) - if(istype(active2, /datum/data/record)) - var/a2 = active2 - var/t1 = copytext(trim(sanitize(input("Add Comment:", "Secure. records", null, null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["comments"] += "Made by [authenticated] ([rank]) on [current_date_string] [station_time_timestamp()]
        [t1]" - - else if(href_list["del_c"]) - var/index = min(max(text2num(href_list["del_c"]) + 1, 1), length(active2.fields["comments"])) - if(istype(active2, /datum/data/record) && active2.fields["comments"][index]) - active2.fields["comments"] -= active2.fields["comments"][index] - - if(href_list["field"]) - if(..()) - return 1 - var/a1 = active1 - var/a2 = active2 - switch(href_list["field"]) - if("name") - if(istype(active1, /datum/data/record)) - var/t1 = reject_bad_name(clean_input("Please input name:", "Secure. records", active1.fields["name"], null)) - if(!t1 || !length(trim(t1)) || ..() || active1 != a1) - return 1 - active1.fields["name"] = t1 - if(istype(active2, /datum/data/record)) - active2.fields["name"] = t1 - if("id") - if(istype(active1, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input id:", "Secure. records", active1.fields["id"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - active1.fields["id"] = t1 - if(istype(active2, /datum/data/record)) - active2.fields["id"] = t1 - if("fingerprint") - if(istype(active1, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input fingerprint hash:", "Secure. records", active1.fields["fingerprint"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - active1.fields["fingerprint"] = t1 - if("sex") - if(istype(active1, /datum/data/record)) - if(active1.fields["sex"] == "Male") - active1.fields["sex"] = "Female" - else - active1.fields["sex"] = "Male" - if("age") - if(istype(active1, /datum/data/record)) - var/t1 = input("Please input age:", "Secure. records", active1.fields["age"], null) as num - if(!t1 || ..() || active1 != a1) - return 1 - active1.fields["age"] = t1 - if("mi_crim") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input minor crimes list:", "Secure. records", active2.fields["mi_crim"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["mi_crim"] = t1 - if("mi_crim_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize minor crimes:", "Secure. records", active2.fields["mi_crim_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["mi_crim_d"] = t1 - if("ma_crim") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input major crimes list:", "Secure. records", active2.fields["ma_crim"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["ma_crim"] = t1 - if("ma_crim_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize major crimes:", "Secure. records", active2.fields["ma_crim_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["ma_crim_d"] = t1 - if("notes") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(html_encode(trim(input("Please summarize notes:", "Secure. records", html_decode(active2.fields["notes"]), null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["notes"] = t1 - if("criminal") - if(istype(active2, /datum/data/record)) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "None", "icon" = "unlock", "href" = "criminal=none", "status" = (active2.fields["criminal"] == "None" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "*Arrest*", "icon" = "lock", "href" = "criminal=arrest", "status" = (active2.fields["criminal"] == "*Arrest*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Incarcerated", "icon" = "lock", "href" = "criminal=incarcerated", "status" = (active2.fields["criminal"] == "Incarcerated" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "*Execute*", "icon" = "lock", "href" = "criminal=execute", "status" = (active2.fields["criminal"] == "*Execute*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Parolled", "icon" = "unlock-alt", "href" = "criminal=parolled", "status" = (active2.fields["criminal"] == "Parolled" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Released", "icon" = "unlock", "href" = "criminal=released", "status" = (active2.fields["criminal"] == "Released" ? "selected" : null)) - setTemp("

        Criminal Status

        ", buttons) - if("rank") - var/list/L = list("Head of Personnel", "Captain", "AI") - //This was so silly before the change. Now it actually works without beating your head against the keyboard. /N - if(istype(active1, /datum/data/record) && L.Find(rank)) - var/list/buttons = list() - for(var/rank in GLOB.joblist) - buttons[++buttons.len] = list("name" = rank, "icon" = null, "href" = "rank=[rank]", "status" = (active1.fields["rank"] == rank ? "selected" : null)) - setTemp("

        Rank

        ", buttons) - else - setTemp("

        You do not have the required rank to do this!

        ") - if("species") - if(istype(active1, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please enter race:", "General records", active1.fields["species"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - active1.fields["species"] = t1 - return 1 - -/obj/machinery/computer/secure_data/proc/setTemp(text, list/buttons = list()) - temp = list("text" = text, "buttons" = buttons, "has_buttons" = buttons.len > 0) - -/* Proc disabled due to BYOND Issue - -/obj/machinery/computer/secure_data/proc/create_record_photo(datum/data/record/R) - // basically copy-pasted from the camera code but different enough that it has to be redone - var/icon/photoimage = get_record_photo(R) - var/icon/small_img = icon(photoimage) - var/icon/tiny_img = icon(photoimage) - var/icon/ic = icon('icons/obj/items.dmi',"photo") - var/icon/pc = icon('icons/obj/bureaucracy.dmi', "photo") - small_img.Scale(8, 8) - tiny_img.Scale(4, 4) - ic.Blend(small_img, ICON_OVERLAY, 10, 13) - pc.Blend(tiny_img, ICON_OVERLAY, 12, 19) - - var/datum/picture/P = new() - P.fields["name"] = "File Photo - [R.fields["name"]]" - P.fields["author"] = "Central Command" - P.fields["icon"] = ic - P.fields["tiny"] = pc - P.fields["img"] = photoimage - P.fields["desc"] = "You can see [R.fields["name"]] on the photo." - P.fields["pixel_x"] = rand(-10, 10) - P.fields["pixel_y"] = rand(-10, 10) - P.fields["size"] = 2 - - var/obj/item/photo/PH = new/obj/item/photo(loc) - PH.construct(P) - -*/ - -/obj/machinery/computer/secure_data/proc/get_record_photo(datum/data/record/R) - // similar to the code to make a photo, but of course the actual rendering is completely different - var/icon/res = icon('icons/effects/96x96.dmi', "") - // will be 2x2 to fit the 2 directions - res.Scale(2 * 32, 2 * 32) - // transparent background (it's a plastic transparency, you see) with the front and side icons - res.Blend(icon(R.fields["photo"], dir = SOUTH), ICON_OVERLAY, 1, 17) - res.Blend(icon(R.fields["photo"], dir = WEST), ICON_OVERLAY, 33, 17) - - return res - -/obj/machinery/computer/secure_data/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - ..(severity) - return - - for(var/datum/data/record/R in data_core.security) - if(prob(10/severity)) - switch(rand(1,6)) - if(1) - R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" - if(2) - R.fields["sex"] = pick("Male", "Female") - if(3) - R.fields["age"] = rand(5, 85) - if(4) - R.fields["criminal"] = pick("None", "*Arrest*", "Incarcerated", "Parolled", "Released") - if(5) - R.fields["p_stat"] = pick("*Unconcious*", "Active", "Physically Unfit") - if(6) - R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") - continue - - else if(prob(1)) - qdel(R) - continue - - ..(severity) - -/obj/machinery/computer/secure_data/detective_computer - icon = 'icons/obj/computer.dmi' - icon_state = "messyfiles" - -/obj/machinery/computer/secure_data/laptop - name = "security laptop" - desc = "Nanotrasen Security laptop. Bringing modern compact computing to this century!" - icon_state = "laptop" - icon_keyboard = "seclaptop_key" - icon_screen = "seclaptop" - density = 0 - -#undef SEC_DATA_R_LIST -#undef SEC_DATA_MAINT -#undef SEC_DATA_RECORD +#define SEC_DATA_R_LIST 1 // Record list +#define SEC_DATA_MAINT 2 // Records maintenance +#define SEC_DATA_RECORD 3 // Record + +/obj/machinery/computer/secure_data//TODO:SANITY + name = "security records" + desc = "Used to view and edit personnel's security records." + icon_keyboard = "security_key" + icon_screen = "security" + req_one_access = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS) + circuit = /obj/item/circuitboard/secure_data + var/obj/item/card/id/scan = null + var/authenticated = null + var/rank = null + var/list/authcard_access = list() + var/screen = null + var/datum/data/record/active1 = null + var/datum/data/record/active2 = null + var/temp = null + var/printing = null + //Sorting Variables + var/sortBy = "name" + var/order = 1 // -1 = Descending - 1 = Ascending + + light_color = LIGHT_COLOR_RED + +/obj/machinery/computer/secure_data/Destroy() + active1 = null + active2 = null + return ..() + +/obj/machinery/computer/secure_data/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/card/id) && !scan) + user.drop_item() + O.forceMove(src) + scan = O + ui_interact(user) + return + return ..() + +//Someone needs to break down the dat += into chunks instead of long ass lines. +/obj/machinery/computer/secure_data/attack_hand(mob/user) + if(..()) + return + if(is_away_level(z)) + to_chat(user, "Unable to establish a connection: You're too far away from the station!") + return + add_fingerprint(user) + ui_interact(user) + +/obj/machinery/computer/secure_data/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "secure_data.tmpl", name, 800, 800) + ui.open() + +/obj/machinery/computer/secure_data/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + data["temp"] = temp + data["scan"] = scan ? scan.name : null + data["authenticated"] = authenticated + data["screen"] = screen + if(authenticated) + switch(screen) + if(SEC_DATA_R_LIST) + if(!isnull(data_core.general)) + for(var/datum/data/record/R in sortRecord(data_core.general, sortBy, order)) + var/crimstat = "null" + for(var/datum/data/record/E in data_core.security) + if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"]) + crimstat = E.fields["criminal"] + break + var/background = "''" + switch(crimstat) + if("*Execute*") + background = "'background-color:#5E0A1A'" + if("*Arrest*") + background = "'background-color:#890E26'" + if("Incarcerated") + background = "'background-color:#743B03'" + if("Parolled") + background = "'background-color:#743B03'" + if("Released") + background = "'background-color:#216489'" + if("None") + background = "'background-color:#007f47'" + if("null") + crimstat = "No record." + data["records"] += list(list("ref" = "\ref[R]", "id" = R.fields["id"], "name" = R.fields["name"], "rank" = R.fields["rank"], "fingerprint" = R.fields["fingerprint"], "background" = background, "crimstat" = crimstat)) + if(SEC_DATA_RECORD) + var/list/general = list() + data["general"] = general + if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) + var/list/fields = list() + general["fields"] = fields + fields[++fields.len] = list("field" = "Name:", "value" = active1.fields["name"], "edit" = "name") + fields[++fields.len] = list("field" = "ID:", "value" = active1.fields["id"], "edit" = "id") + fields[++fields.len] = list("field" = "Sex:", "value" = active1.fields["sex"], "edit" = "sex") + fields[++fields.len] = list("field" = "Age:", "value" = active1.fields["age"], "edit" = "age") + fields[++fields.len] = list("field" = "Rank:", "value" = active1.fields["rank"], "edit" = "rank") + fields[++fields.len] = list("field" = "Fingerprint:", "value" = active1.fields["fingerprint"], "edit" = "fingerprint") + fields[++fields.len] = list("field" = "Physical Status:", "value" = active1.fields["p_stat"], "edit" = null) + fields[++fields.len] = list("field" = "Mental Status:", "value" = active1.fields["m_stat"], "edit" = null) + var/list/photos = list() + general["photos"] = photos + photos[++photos.len] = list("photo" = active1.fields["photo-south"]) + photos[++photos.len] = list("photo" = active1.fields["photo-west"]) + general["has_photos"] += (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0) + general["empty"] = 0 + else + general["empty"] = 1 + + var/list/security = list() + data["security"] = security + if(istype(active2, /datum/data/record) && data_core.security.Find(active2)) + var/list/fields = list() + security["fields"] = fields + fields[++fields.len] = list("field" = "Criminal Status:", "value" = active2.fields["criminal"], "edit" = "criminal", "line_break" = 1) + fields[++fields.len] = list("field" = "Minor Crimes:", "value" = active2.fields["mi_crim"], "edit" = "mi_crim", "line_break" = 0) + fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["mi_crim_d"], "edit" = "mi_crim_d", "line_break" = 1) + fields[++fields.len] = list("field" = "Major Crimes:", "value" = active2.fields["ma_crim"], "edit" = "ma_crim", "line_break" = 0) + fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["ma_crim_d"], "edit" = "ma_crim_d", "line_break" = 1) + fields[++fields.len] = list("field" = "Important Notes:", "value" = active2.fields["notes"], "edit" = "notes", "line_break" = 0) + if(!active2.fields["comments"] || !islist(active2.fields["comments"])) + active2.fields["comments"] = list() + security["comments"] = active2.fields["comments"] + security["empty"] = 0 + else + security["empty"] = 1 + return data + +/obj/machinery/computer/secure_data/Topic(href, href_list) + if(..()) + return 1 + + if(!data_core.general.Find(active1)) + active1 = null + if(!data_core.security.Find(active2)) + active2 = null + + if(href_list["temp"]) + temp = null + + if(href_list["temp_action"]) + var/temp_href = splittext(href_list["temp_action"], "=") + switch(temp_href[1]) + if("del_all2") + for(var/datum/data/record/R in data_core.security) + qdel(R) + update_all_mob_security_hud() + setTemp("

        All records deleted.

        ") + if("del_alllogs2") + if(GLOB.cell_logs.len) + setTemp("

        All cell logs deleted.

        ") + GLOB.cell_logs.Cut() + else + to_chat(usr, "Error; No cell logs to delete.") + if("del_r2") + if(active2) + qdel(active2) + update_all_mob_security_hud() + if("del_rg2") + if(active1) + for(var/datum/data/record/R in data_core.medical) + if(R.fields["name"] == active1.fields["name"] && R.fields["id"] == active1.fields["id"]) + qdel(R) + QDEL_NULL(active1) + QDEL_NULL(active2) + update_all_mob_security_hud() + screen = SEC_DATA_R_LIST + if("criminal") + if(active2) + var/t1 + if(temp_href[2] == "execute") + t1 = copytext(trim(sanitize(input("Explain why they are being executed. Include a list of their crimes, and victims.", "EXECUTION ORDER", null, null) as text)), 1, MAX_MESSAGE_LEN) + else + t1 = copytext(trim(sanitize(input("Enter Reason:", "Secure. records", null, null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1) + t1 = "(none)" + if(!set_criminal_status(usr, active2, temp_href[2], t1, rank, authcard_access)) + setTemp("

        Error: permission denied.

        ") + return 1 + if("rank") + if(active1) + active1.fields["rank"] = temp_href[2] + if(temp_href[2] in GLOB.joblist) + active1.fields["real_rank"] = temp_href[2] + + if(href_list["scan"]) + if(scan) + scan.forceMove(loc) + if(ishuman(usr) && !usr.get_active_hand()) + usr.put_in_hands(scan) + scan = null + else + var/obj/item/I = usr.get_active_hand() + if(istype(I, /obj/item/card/id)) + usr.drop_item() + I.forceMove(src) + scan = I + + if(href_list["login"]) + if(isAI(usr)) + authenticated = usr.name + rank = "AI" + else if(isrobot(usr)) + authenticated = usr.name + var/mob/living/silicon/robot/R = usr + rank = "[R.modtype] [R.braintype]" + else if(istype(scan, /obj/item/card/id)) + if(check_access(scan)) + authenticated = scan.registered_name + rank = scan.assignment + authcard_access = scan.access + + if(authenticated) + active1 = null + active2 = null + screen = SEC_DATA_R_LIST + + if(authenticated) + if(href_list["logout"]) + authenticated = null + screen = null + active1 = null + active2 = null + authcard_access = list() + + else if(href_list["sort"]) + // Reverse the order if clicked twice + if(sortBy == href_list["sort"]) + if(order == 1) + order = -1 + else + order = 1 + else + sortBy = href_list["sort"] + order = initial(order) + + else if(href_list["screen"]) + screen = text2num(href_list["screen"]) + if(screen < 1) + screen = SEC_DATA_R_LIST + + active1 = null + active2 = null + + else if(href_list["d_rec"]) + var/datum/data/record/R = locate(href_list["d_rec"]) + var/datum/data/record/M = locate(href_list["d_rec"]) + if(!data_core.general.Find(R)) + setTemp("

        Record not found!

        ") + return 1 + for(var/datum/data/record/E in data_core.security) + if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"]) + M = E + active1 = R + active2 = M + screen = SEC_DATA_RECORD + + else if(href_list["del_all"]) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_all2=1", "status" = null) + buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) + setTemp("

        Are you sure you wish to delete all records?

        ", buttons) + + else if(href_list["del_alllogs"]) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_alllogs2=1", "status" = null) + buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) + setTemp("

        Are you sure you wish to delete all cell logs?

        ", buttons) + + else if(href_list["del_rg"]) + if(active1) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_rg2=1", "status" = null) + buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) + setTemp("

        Are you sure you wish to delete the record (ALL)?

        ", buttons) + + else if(href_list["del_r"]) + if(active1) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_r2=1", "status" = null) + buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) + setTemp("

        Are you sure you wish to delete the record (Security Portion Only)?

        ", buttons) + + else if(href_list["new_s"]) + if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record)) + var/datum/data/record/R = new /datum/data/record() + R.fields["name"] = active1.fields["name"] + R.fields["id"] = active1.fields["id"] + R.name = "Security Record #[R.fields["id"]]" + R.fields["criminal"] = "None" + R.fields["mi_crim"] = "None" + R.fields["mi_crim_d"] = "No minor crime convictions." + R.fields["ma_crim"] = "None" + R.fields["ma_crim_d"] = "No major crime convictions." + R.fields["notes"] = "No notes." + data_core.security += R + active2 = R + screen = SEC_DATA_RECORD + + else if(href_list["new_g"]) + var/datum/data/record/G = new /datum/data/record() + G.fields["name"] = "New Record" + G.fields["id"] = "[add_zero(num2hex(rand(1, 1.6777215E7)), 6)]" + G.fields["rank"] = "Unassigned" + G.fields["real_rank"] = "Unassigned" + G.fields["sex"] = "Male" + G.fields["age"] = "Unknown" + G.fields["fingerprint"] = "Unknown" + G.fields["p_stat"] = "Active" + G.fields["m_stat"] = "Stable" + G.fields["species"] = "Human" + data_core.general += G + active1 = G + active2 = null + + else if(href_list["print_r"]) + if(!printing) + printing = 1 + playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) + sleep(50) + var/obj/item/paper/P = new /obj/item/paper(loc) + P.info = "
        Security Record

        " + if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) + P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]] +
        \nSex: [active1.fields["sex"]] +
        \nAge: [active1.fields["age"]] +
        \nFingerprint: [active1.fields["fingerprint"]] +
        \nPhysical Status: [active1.fields["p_stat"]] +
        \nMental Status: [active1.fields["m_stat"]]
        "} + else + P.info += "General Record Lost!
        " + if(istype(active2, /datum/data/record) && data_core.security.Find(active2)) + P.info += {"
        \n
        Security Data
        +
        \nCriminal Status: [active2.fields["criminal"]]
        \n +
        \nMinor Crimes: [active2.fields["mi_crim"]] +
        \nDetails: [active2.fields["mi_crim_d"]]
        \n +
        \nMajor Crimes: [active2.fields["ma_crim"]] +
        \nDetails: [active2.fields["ma_crim_d"]]
        \n +
        \nImportant Notes: +
        \n\t[active2.fields["notes"]]
        \n
        \n
        Comments/Log

        "} + for(var/c in active2.fields["comments"]) + P.info += "[c]
        " + else + P.info += "Security Record Lost!
        " + P.info += "" + P.name = "paper - 'Security Record: [active1.fields["name"]]'" + printing = 0 + +/* Removed due to BYOND issue + else if(href_list["print_p"]) + if(!printing) + printing = 1 + playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) + sleep(50) + if(istype(active1, /datum/data/record) && data_core.general.Find(active1)) + create_record_photo(active1) + printing = 0 +*/ + + else if(href_list["printlogs"]) + if(GLOB.cell_logs.len && !printing) + var/obj/item/paper/P = input(usr, "Select log to print", "Available Cell Logs") as null|anything in GLOB.cell_logs + if(!P) + return 0 + printing = 1 + playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) + to_chat(usr, "Printing file [P.name].") + sleep(50) + var/obj/item/paper/log = new /obj/item/paper(loc) + log.name = P.name + log.info = P.info + printing = 0 + return 1 + else + to_chat(usr, "[src] has no logs stored or is already printing.") + + + else if(href_list["add_c"]) + if(istype(active2, /datum/data/record)) + var/a2 = active2 + var/t1 = copytext(trim(sanitize(input("Add Comment:", "Secure. records", null, null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["comments"] += "Made by [authenticated] ([rank]) on [current_date_string] [station_time_timestamp()]
        [t1]" + + else if(href_list["del_c"]) + var/index = min(max(text2num(href_list["del_c"]) + 1, 1), length(active2.fields["comments"])) + if(istype(active2, /datum/data/record) && active2.fields["comments"][index]) + active2.fields["comments"] -= active2.fields["comments"][index] + + if(href_list["field"]) + if(..()) + return 1 + var/a1 = active1 + var/a2 = active2 + switch(href_list["field"]) + if("name") + if(istype(active1, /datum/data/record)) + var/t1 = reject_bad_name(clean_input("Please input name:", "Secure. records", active1.fields["name"], null)) + if(!t1 || !length(trim(t1)) || ..() || active1 != a1) + return 1 + active1.fields["name"] = t1 + if(istype(active2, /datum/data/record)) + active2.fields["name"] = t1 + if("id") + if(istype(active1, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please input id:", "Secure. records", active1.fields["id"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active1 != a1) + return 1 + active1.fields["id"] = t1 + if(istype(active2, /datum/data/record)) + active2.fields["id"] = t1 + if("fingerprint") + if(istype(active1, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please input fingerprint hash:", "Secure. records", active1.fields["fingerprint"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active1 != a1) + return 1 + active1.fields["fingerprint"] = t1 + if("sex") + if(istype(active1, /datum/data/record)) + if(active1.fields["sex"] == "Male") + active1.fields["sex"] = "Female" + else + active1.fields["sex"] = "Male" + if("age") + if(istype(active1, /datum/data/record)) + var/t1 = input("Please input age:", "Secure. records", active1.fields["age"], null) as num + if(!t1 || ..() || active1 != a1) + return 1 + active1.fields["age"] = t1 + if("mi_crim") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please input minor crimes list:", "Secure. records", active2.fields["mi_crim"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["mi_crim"] = t1 + if("mi_crim_d") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please summarize minor crimes:", "Secure. records", active2.fields["mi_crim_d"], null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["mi_crim_d"] = t1 + if("ma_crim") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please input major crimes list:", "Secure. records", active2.fields["ma_crim"], null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["ma_crim"] = t1 + if("ma_crim_d") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please summarize major crimes:", "Secure. records", active2.fields["ma_crim_d"], null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["ma_crim_d"] = t1 + if("notes") + if(istype(active2, /datum/data/record)) + var/t1 = copytext(html_encode(trim(input("Please summarize notes:", "Secure. records", html_decode(active2.fields["notes"]), null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active2 != a2) + return 1 + active2.fields["notes"] = t1 + if("criminal") + if(istype(active2, /datum/data/record)) + var/list/buttons = list() + buttons[++buttons.len] = list("name" = "None", "icon" = "unlock", "href" = "criminal=none", "status" = (active2.fields["criminal"] == "None" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "*Arrest*", "icon" = "lock", "href" = "criminal=arrest", "status" = (active2.fields["criminal"] == "*Arrest*" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "Incarcerated", "icon" = "lock", "href" = "criminal=incarcerated", "status" = (active2.fields["criminal"] == "Incarcerated" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "*Execute*", "icon" = "lock", "href" = "criminal=execute", "status" = (active2.fields["criminal"] == "*Execute*" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "Parolled", "icon" = "unlock-alt", "href" = "criminal=parolled", "status" = (active2.fields["criminal"] == "Parolled" ? "selected" : null)) + buttons[++buttons.len] = list("name" = "Released", "icon" = "unlock", "href" = "criminal=released", "status" = (active2.fields["criminal"] == "Released" ? "selected" : null)) + setTemp("

        Criminal Status

        ", buttons) + if("rank") + var/list/L = list("Head of Personnel", "Captain", "AI") + //This was so silly before the change. Now it actually works without beating your head against the keyboard. /N + if(istype(active1, /datum/data/record) && L.Find(rank)) + var/list/buttons = list() + for(var/rank in GLOB.joblist) + buttons[++buttons.len] = list("name" = rank, "icon" = null, "href" = "rank=[rank]", "status" = (active1.fields["rank"] == rank ? "selected" : null)) + setTemp("

        Rank

        ", buttons) + else + setTemp("

        You do not have the required rank to do this!

        ") + if("species") + if(istype(active1, /datum/data/record)) + var/t1 = copytext(trim(sanitize(input("Please enter race:", "General records", active1.fields["species"], null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || ..() || active1 != a1) + return 1 + active1.fields["species"] = t1 + return 1 + +/obj/machinery/computer/secure_data/proc/setTemp(text, list/buttons = list()) + temp = list("text" = text, "buttons" = buttons, "has_buttons" = buttons.len > 0) + +/* Proc disabled due to BYOND Issue + +/obj/machinery/computer/secure_data/proc/create_record_photo(datum/data/record/R) + // basically copy-pasted from the camera code but different enough that it has to be redone + var/icon/photoimage = get_record_photo(R) + var/icon/small_img = icon(photoimage) + var/icon/tiny_img = icon(photoimage) + var/icon/ic = icon('icons/obj/items.dmi',"photo") + var/icon/pc = icon('icons/obj/bureaucracy.dmi', "photo") + small_img.Scale(8, 8) + tiny_img.Scale(4, 4) + ic.Blend(small_img, ICON_OVERLAY, 10, 13) + pc.Blend(tiny_img, ICON_OVERLAY, 12, 19) + + var/datum/picture/P = new() + P.fields["name"] = "File Photo - [R.fields["name"]]" + P.fields["author"] = "Central Command" + P.fields["icon"] = ic + P.fields["tiny"] = pc + P.fields["img"] = photoimage + P.fields["desc"] = "You can see [R.fields["name"]] on the photo." + P.fields["pixel_x"] = rand(-10, 10) + P.fields["pixel_y"] = rand(-10, 10) + P.fields["size"] = 2 + + var/obj/item/photo/PH = new/obj/item/photo(loc) + PH.construct(P) + +*/ + +/obj/machinery/computer/secure_data/proc/get_record_photo(datum/data/record/R) + // similar to the code to make a photo, but of course the actual rendering is completely different + var/icon/res = icon('icons/effects/96x96.dmi', "") + // will be 2x2 to fit the 2 directions + res.Scale(2 * 32, 2 * 32) + // transparent background (it's a plastic transparency, you see) with the front and side icons + res.Blend(icon(R.fields["photo"], dir = SOUTH), ICON_OVERLAY, 1, 17) + res.Blend(icon(R.fields["photo"], dir = WEST), ICON_OVERLAY, 33, 17) + + return res + +/obj/machinery/computer/secure_data/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + ..(severity) + return + + for(var/datum/data/record/R in data_core.security) + if(prob(10/severity)) + switch(rand(1,6)) + if(1) + R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" + if(2) + R.fields["sex"] = pick("Male", "Female") + if(3) + R.fields["age"] = rand(5, 85) + if(4) + R.fields["criminal"] = pick("None", "*Arrest*", "Incarcerated", "Parolled", "Released") + if(5) + R.fields["p_stat"] = pick("*Unconcious*", "Active", "Physically Unfit") + if(6) + R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") + continue + + else if(prob(1)) + qdel(R) + continue + + ..(severity) + +/obj/machinery/computer/secure_data/detective_computer + icon = 'icons/obj/computer.dmi' + icon_state = "messyfiles" + +/obj/machinery/computer/secure_data/laptop + name = "security laptop" + desc = "Nanotrasen Security laptop. Bringing modern compact computing to this century!" + icon_state = "laptop" + icon_keyboard = "seclaptop_key" + icon_screen = "seclaptop" + density = 0 + +#undef SEC_DATA_R_LIST +#undef SEC_DATA_MAINT +#undef SEC_DATA_RECORD diff --git a/code/game/machinery/computer/specops_shuttle.dm b/code/game/machinery/computer/specops_shuttle.dm index 711aec59db5..23658a3e939 100644 --- a/code/game/machinery/computer/specops_shuttle.dm +++ b/code/game/machinery/computer/specops_shuttle.dm @@ -1,340 +1,340 @@ -//Config stuff -#define SPECOPS_MOVETIME 600 //Time to station is milliseconds. 60 seconds, enough time for everyone to be on the shuttle before it leaves. -#define SPECOPS_STATION_AREATYPE "/area/shuttle/specops/station" //Type of the spec ops shuttle area for station -#define SPECOPS_DOCK_AREATYPE "/area/shuttle/specops/centcom" //Type of the spec ops shuttle area for dock -#define SPECOPS_RETURN_DELAY 6000 //Time between the shuttle is capable of moving. - -var/specops_shuttle_moving_to_station = 0 -var/specops_shuttle_moving_to_centcom = 0 -var/specops_shuttle_at_station = 0 -var/specops_shuttle_can_send = 1 -var/specops_shuttle_time = 0 -var/specops_shuttle_timeleft = 0 - -/obj/machinery/computer/specops_shuttle - name = "\improper Spec. Ops. shuttle console" - icon = 'icons/obj/computer.dmi' - icon_keyboard = "security_key" - icon_screen = "syndishuttle" - light_color = LIGHT_COLOR_PURE_CYAN - req_access = list(ACCESS_CENT_SPECOPS) -// req_access = list(ACCESS_CENT_SPECOPS) - var/temp = null - var/hacked = 0 - var/allowedtocall = 0 - var/specops_shuttle_timereset = 0 - -/proc/specops_return() - var/obj/item/radio/intercom/announcer = new /obj/item/radio/intercom(null)//We need a fake AI to announce some stuff below. Otherwise it will be wonky. - announcer.config(list("Response Team" = 0)) - - var/message_tracker[] = list(0,1,2,3,5,10,30,45)//Create a a list with potential time values. - var/message = "\"THE SPECIAL OPERATIONS SHUTTLE IS PREPARING TO RETURN\""//Initial message shown. - if(announcer) - announcer.autosay(message, "A.L.I.C.E.", "Response Team", list(1,2)) - - while(specops_shuttle_time - world.timeofday > 0) - var/ticksleft = specops_shuttle_time - world.timeofday - - if(ticksleft > 1e5) - specops_shuttle_time = world.timeofday + 10 // midnight rollover - specops_shuttle_timeleft = (ticksleft / 10) - - //All this does is announce the time before launch. - if(announcer) - var/rounded_time_left = round(specops_shuttle_timeleft)//Round time so that it will report only once, not in fractions. - if(rounded_time_left in message_tracker)//If that time is in the list for message announce. - message = "\"ALERT: [rounded_time_left] SECOND[(rounded_time_left!=1)?"S":""] REMAIN\"" - if(rounded_time_left==0) - message = "\"ALERT: TAKEOFF\"" - announcer.autosay(message, "A.L.I.C.E.", "Response Team") - message_tracker -= rounded_time_left//Remove the number from the list so it won't be called again next cycle. - //Should call all the numbers but lag could mean some issues. Oh well. Not much I can do about that. - - sleep(5) - - specops_shuttle_moving_to_station = 0 - specops_shuttle_moving_to_centcom = 0 - - specops_shuttle_at_station = 1 - - var/area/start_location = locate(/area/shuttle/specops/station) - var/area/end_location = locate(/area/shuttle/specops/centcom) - - var/list/dstturfs = list() - var/throwy = world.maxy - - for(var/turf/T in end_location) - dstturfs += T - if(T.y < throwy) - throwy = T.y - - // hey you, get out of the way! - for(var/turf/T in dstturfs) - // find the turf to move things to - var/turf/D = locate(T.x, throwy - 1, 1) - //var/turf/E = get_step(D, SOUTH) - for(var/atom/movable/AM as mob|obj in T) - AM.Move(D) - if(istype(T, /turf/simulated)) - qdel(T) - - for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area... - bug.gib() - - for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug... - pest.gib() - - start_location.move_contents_to(end_location) - - for(var/turf/T in get_area_turfs(end_location) ) - var/mob/M = locate(/mob) in T - to_chat(M, "You have arrived at Central Command. Operation has ended!") - - specops_shuttle_at_station = 0 - - for(var/obj/machinery/computer/specops_shuttle/S in world) - S.specops_shuttle_timereset = world.time + SPECOPS_RETURN_DELAY - - qdel(announcer) - -/proc/specops_process() - var/area/centcom/specops/special_ops = locate()//Where is the specops area located? - var/obj/item/radio/intercom/announcer = new /obj/item/radio/intercom(null)//We need a fake AI to announce some stuff below. Otherwise it will be wonky. - announcer.config(list("Response Team" = 0)) - - var/message_tracker[] = list(0,1,2,3,5,10,30,45)//Create a a list with potential time values. - var/message = "\"THE SPECIAL OPERATIONS SHUTTLE IS PREPARING FOR LAUNCH\""//Initial message shown. - if(announcer) - announcer.autosay(message, "A.L.I.C.E.", "Response Team") - //message = "ARMORED SQUAD TAKE YOUR POSITION ON GRAVITY LAUNCH PAD" - //announcer.autosay(message, "A.L.I.C.E.", "Response Team") - - while(specops_shuttle_time - world.timeofday > 0) - var/ticksleft = specops_shuttle_time - world.timeofday - - if(ticksleft > 1e5) - specops_shuttle_time = world.timeofday + 10 // midnight rollover - specops_shuttle_timeleft = (ticksleft / 10) - - //All this does is announce the time before launch. - if(announcer) - var/rounded_time_left = round(specops_shuttle_timeleft)//Round time so that it will report only once, not in fractions. - if(rounded_time_left in message_tracker)//If that time is in the list for message announce. - message = "\"ALERT: [rounded_time_left] SECOND[(rounded_time_left!=1)?"S":""] REMAIN\"" - if(rounded_time_left==0) - message = "\"ALERT: TAKEOFF\"" - announcer.autosay(message, "A.L.I.C.E.", "Response Team") - message_tracker -= rounded_time_left//Remove the number from the list so it won't be called again next cycle. - //Should call all the numbers but lag could mean some issues. Oh well. Not much I can do about that. - - sleep(5) - - specops_shuttle_moving_to_station = 0 - specops_shuttle_moving_to_centcom = 0 - - specops_shuttle_at_station = 1 - if(specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom) return - - if(!specops_can_move()) - to_chat(usr, "The Special Operations shuttle is unable to leave.") - return - - //Begin Marauder launchpad. - spawn(0)//So it parallel processes it. - for(var/obj/machinery/door/poddoor/M in special_ops) - switch(M.id_tag) - if("ASSAULT0") - spawn(10)//1 second delay between each. - M.open() - if("ASSAULT1") - spawn(20) - M.open() - if("ASSAULT2") - spawn(30) - M.open() - if("ASSAULT3") - spawn(40) - M.open() - - sleep(10) - - var/spawn_marauder[] = new() - for(var/obj/effect/landmark/L in world) - if(L.name == "Marauder Entry") - spawn_marauder.Add(L.loc) - for(var/obj/effect/landmark/L in world) - if(L.name == "Marauder Exit") - var/obj/effect/portal/P = new(L.loc, pick(spawn_marauder)) - //P.invisibility = 101//So it is not seen by anyone. - P.failchance = 0//So it has no fail chance when teleporting. - spawn_marauder.Remove(P.target) - - sleep(10) - - for(var/obj/machinery/mass_driver/M in special_ops) - switch(M.id_tag) - if("ASSAULT0") - spawn(10) - M.drive() - if("ASSAULT1") - spawn(20) - M.drive() - if("ASSAULT2") - spawn(30) - M.drive() - if("ASSAULT3") - spawn(40) - M.drive() - - sleep(50)//Doors remain open for 5 seconds. - - for(var/obj/machinery/door/poddoor/M in special_ops) - switch(M.id_tag)//Doors close at the same time. - if("ASSAULT0") - spawn(0) - M.close() - if("ASSAULT1") - spawn(0) - M.close() - if("ASSAULT2") - spawn(0) - M.close() - if("ASSAULT3") - spawn(0) - M.close() - special_ops.readyreset()//Reset firealarm after the team launched. - //End Marauder launchpad. - - var/area/start_location = locate(/area/shuttle/specops/centcom) - var/area/end_location = locate(/area/shuttle/specops/station) - - var/list/dstturfs = list() - var/throwy = world.maxy - - for(var/turf/T in end_location) - dstturfs += T - if(T.y < throwy) - throwy = T.y - - // hey you, get out of the way! - for(var/turf/T in dstturfs) - // find the turf to move things to - var/turf/D = locate(T.x, throwy - 1, 1) - //var/turf/E = get_step(D, SOUTH) - for(var/atom/movable/AM as mob|obj in T) - AM.Move(D) - if(istype(T, /turf/simulated)) - qdel(T) - - start_location.move_contents_to(end_location) - - for(var/turf/T in get_area_turfs(end_location) ) - var/mob/M = locate(/mob) in T - to_chat(M, "You have arrived to [station_name()]. Commence operation!") - - for(var/obj/machinery/computer/specops_shuttle/S in world) - S.specops_shuttle_timereset = world.time + SPECOPS_RETURN_DELAY - - qdel(announcer) - -/proc/specops_can_move() - if(specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom) - return 0 - for(var/obj/machinery/computer/specops_shuttle/S in world) - if(world.timeofday <= S.specops_shuttle_timereset) - return 0 - return 1 - -/obj/machinery/computer/specops_shuttle/attack_ai(var/mob/user as mob) - to_chat(user, "Access Denied.") - return 1 - -/obj/machinery/computer/specops_shuttle/attackby(I as obj, user as mob, params) - if(istype(I,/obj/item/card/emag)) - to_chat(user, "The electronic systems in this console are far too advanced for your primitive hacking peripherals.") - else - return ..() - -/obj/machinery/computer/specops_shuttle/attack_hand(var/mob/user as mob) - if(!allowed(user)) - to_chat(user, "Access Denied.") - return - -//Commented out so admins can do shenanigans at their leisure. Also makes the force-spawned admin ERTs able to use the shuttle. -// if(sent_strike_team == 0 && send_emergency_team == 0) -// to_chat(usr, "The strike team has not yet deployed.") -// return - - if(..()) - return - - user.machine = src - var/dat - if(temp) - dat = temp - else - dat += {"
        Special Operations Shuttle
        - \nLocation: [specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom ? "Departing for [station_name()] in ([specops_shuttle_timeleft] seconds.)":specops_shuttle_at_station ? "Station":"Dock"]
        - [specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom ? "\n*The Special Ops. shuttle is already leaving.*
        \n
        ":specops_shuttle_at_station ? "\nShuttle standing by...
        \n
        ":"\nDepart to [station_name()]
        \n
        "] - \nClose"} - - user << browse(dat, "window=computer;size=575x450") - onclose(user, "computer") - return - -/obj/machinery/computer/specops_shuttle/Topic(href, href_list) - if(..()) - return 1 - - if((usr.contents.Find(src) || (in_range(src, usr) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon))) - usr.machine = src - - if(href_list["sendtodock"]) - if(!specops_shuttle_at_station|| specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom) return - - if(!specops_can_move()) - to_chat(usr, "Central Command will not allow the Special Operations shuttle to return yet.") - if(world.timeofday <= specops_shuttle_timereset) - if(((world.timeofday - specops_shuttle_timereset)/10) > 60) - to_chat(usr, "[-((world.timeofday - specops_shuttle_timereset)/10)/60] minutes remain!") - to_chat(usr, "[-(world.timeofday - specops_shuttle_timereset)/10] seconds remain!") - return - - to_chat(usr, "The Special Operations shuttle will arrive at Central Command in [(SPECOPS_MOVETIME/10)] seconds.") - - temp += "Shuttle departing.

        OK" - updateUsrDialog() - - specops_shuttle_moving_to_centcom = 1 - specops_shuttle_time = world.timeofday + SPECOPS_MOVETIME - spawn(0) - specops_return() - - else if(href_list["sendtostation"]) - if(specops_shuttle_at_station || specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom) return - - if(!specops_can_move()) - to_chat(usr, "The Special Operations shuttle is unable to leave.") - return - - to_chat(usr, "The Special Operations shuttle will arrive on [station_name()] in [(SPECOPS_MOVETIME/10)] seconds.") - - temp += "Shuttle departing.

        OK" - updateUsrDialog() - - var/area/centcom/specops/special_ops = locate() - if(special_ops) - special_ops.readyalert()//Trigger alarm for the spec ops area. - specops_shuttle_moving_to_station = 1 - - specops_shuttle_time = world.timeofday + SPECOPS_MOVETIME - spawn(0) - specops_process() - - else if(href_list["mainmenu"]) - temp = null - - add_fingerprint(usr) - updateUsrDialog() - return +//Config stuff +#define SPECOPS_MOVETIME 600 //Time to station is milliseconds. 60 seconds, enough time for everyone to be on the shuttle before it leaves. +#define SPECOPS_STATION_AREATYPE "/area/shuttle/specops/station" //Type of the spec ops shuttle area for station +#define SPECOPS_DOCK_AREATYPE "/area/shuttle/specops/centcom" //Type of the spec ops shuttle area for dock +#define SPECOPS_RETURN_DELAY 6000 //Time between the shuttle is capable of moving. + +var/specops_shuttle_moving_to_station = 0 +var/specops_shuttle_moving_to_centcom = 0 +var/specops_shuttle_at_station = 0 +var/specops_shuttle_can_send = 1 +var/specops_shuttle_time = 0 +var/specops_shuttle_timeleft = 0 + +/obj/machinery/computer/specops_shuttle + name = "\improper Spec. Ops. shuttle console" + icon = 'icons/obj/computer.dmi' + icon_keyboard = "security_key" + icon_screen = "syndishuttle" + light_color = LIGHT_COLOR_PURE_CYAN + req_access = list(ACCESS_CENT_SPECOPS) +// req_access = list(ACCESS_CENT_SPECOPS) + var/temp = null + var/hacked = 0 + var/allowedtocall = 0 + var/specops_shuttle_timereset = 0 + +/proc/specops_return() + var/obj/item/radio/intercom/announcer = new /obj/item/radio/intercom(null)//We need a fake AI to announce some stuff below. Otherwise it will be wonky. + announcer.config(list("Response Team" = 0)) + + var/message_tracker[] = list(0,1,2,3,5,10,30,45)//Create a a list with potential time values. + var/message = "\"THE SPECIAL OPERATIONS SHUTTLE IS PREPARING TO RETURN\""//Initial message shown. + if(announcer) + announcer.autosay(message, "A.L.I.C.E.", "Response Team", list(1,2)) + + while(specops_shuttle_time - world.timeofday > 0) + var/ticksleft = specops_shuttle_time - world.timeofday + + if(ticksleft > 1e5) + specops_shuttle_time = world.timeofday + 10 // midnight rollover + specops_shuttle_timeleft = (ticksleft / 10) + + //All this does is announce the time before launch. + if(announcer) + var/rounded_time_left = round(specops_shuttle_timeleft)//Round time so that it will report only once, not in fractions. + if(rounded_time_left in message_tracker)//If that time is in the list for message announce. + message = "\"ALERT: [rounded_time_left] SECOND[(rounded_time_left!=1)?"S":""] REMAIN\"" + if(rounded_time_left==0) + message = "\"ALERT: TAKEOFF\"" + announcer.autosay(message, "A.L.I.C.E.", "Response Team") + message_tracker -= rounded_time_left//Remove the number from the list so it won't be called again next cycle. + //Should call all the numbers but lag could mean some issues. Oh well. Not much I can do about that. + + sleep(5) + + specops_shuttle_moving_to_station = 0 + specops_shuttle_moving_to_centcom = 0 + + specops_shuttle_at_station = 1 + + var/area/start_location = locate(/area/shuttle/specops/station) + var/area/end_location = locate(/area/shuttle/specops/centcom) + + var/list/dstturfs = list() + var/throwy = world.maxy + + for(var/turf/T in end_location) + dstturfs += T + if(T.y < throwy) + throwy = T.y + + // hey you, get out of the way! + for(var/turf/T in dstturfs) + // find the turf to move things to + var/turf/D = locate(T.x, throwy - 1, 1) + //var/turf/E = get_step(D, SOUTH) + for(var/atom/movable/AM as mob|obj in T) + AM.Move(D) + if(istype(T, /turf/simulated)) + qdel(T) + + for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area... + bug.gib() + + for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug... + pest.gib() + + start_location.move_contents_to(end_location) + + for(var/turf/T in get_area_turfs(end_location) ) + var/mob/M = locate(/mob) in T + to_chat(M, "You have arrived at Central Command. Operation has ended!") + + specops_shuttle_at_station = 0 + + for(var/obj/machinery/computer/specops_shuttle/S in world) + S.specops_shuttle_timereset = world.time + SPECOPS_RETURN_DELAY + + qdel(announcer) + +/proc/specops_process() + var/area/centcom/specops/special_ops = locate()//Where is the specops area located? + var/obj/item/radio/intercom/announcer = new /obj/item/radio/intercom(null)//We need a fake AI to announce some stuff below. Otherwise it will be wonky. + announcer.config(list("Response Team" = 0)) + + var/message_tracker[] = list(0,1,2,3,5,10,30,45)//Create a a list with potential time values. + var/message = "\"THE SPECIAL OPERATIONS SHUTTLE IS PREPARING FOR LAUNCH\""//Initial message shown. + if(announcer) + announcer.autosay(message, "A.L.I.C.E.", "Response Team") + //message = "ARMORED SQUAD TAKE YOUR POSITION ON GRAVITY LAUNCH PAD" + //announcer.autosay(message, "A.L.I.C.E.", "Response Team") + + while(specops_shuttle_time - world.timeofday > 0) + var/ticksleft = specops_shuttle_time - world.timeofday + + if(ticksleft > 1e5) + specops_shuttle_time = world.timeofday + 10 // midnight rollover + specops_shuttle_timeleft = (ticksleft / 10) + + //All this does is announce the time before launch. + if(announcer) + var/rounded_time_left = round(specops_shuttle_timeleft)//Round time so that it will report only once, not in fractions. + if(rounded_time_left in message_tracker)//If that time is in the list for message announce. + message = "\"ALERT: [rounded_time_left] SECOND[(rounded_time_left!=1)?"S":""] REMAIN\"" + if(rounded_time_left==0) + message = "\"ALERT: TAKEOFF\"" + announcer.autosay(message, "A.L.I.C.E.", "Response Team") + message_tracker -= rounded_time_left//Remove the number from the list so it won't be called again next cycle. + //Should call all the numbers but lag could mean some issues. Oh well. Not much I can do about that. + + sleep(5) + + specops_shuttle_moving_to_station = 0 + specops_shuttle_moving_to_centcom = 0 + + specops_shuttle_at_station = 1 + if(specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom) return + + if(!specops_can_move()) + to_chat(usr, "The Special Operations shuttle is unable to leave.") + return + + //Begin Marauder launchpad. + spawn(0)//So it parallel processes it. + for(var/obj/machinery/door/poddoor/M in special_ops) + switch(M.id_tag) + if("ASSAULT0") + spawn(10)//1 second delay between each. + M.open() + if("ASSAULT1") + spawn(20) + M.open() + if("ASSAULT2") + spawn(30) + M.open() + if("ASSAULT3") + spawn(40) + M.open() + + sleep(10) + + var/spawn_marauder[] = new() + for(var/obj/effect/landmark/L in world) + if(L.name == "Marauder Entry") + spawn_marauder.Add(L.loc) + for(var/obj/effect/landmark/L in world) + if(L.name == "Marauder Exit") + var/obj/effect/portal/P = new(L.loc, pick(spawn_marauder)) + //P.invisibility = 101//So it is not seen by anyone. + P.failchance = 0//So it has no fail chance when teleporting. + spawn_marauder.Remove(P.target) + + sleep(10) + + for(var/obj/machinery/mass_driver/M in special_ops) + switch(M.id_tag) + if("ASSAULT0") + spawn(10) + M.drive() + if("ASSAULT1") + spawn(20) + M.drive() + if("ASSAULT2") + spawn(30) + M.drive() + if("ASSAULT3") + spawn(40) + M.drive() + + sleep(50)//Doors remain open for 5 seconds. + + for(var/obj/machinery/door/poddoor/M in special_ops) + switch(M.id_tag)//Doors close at the same time. + if("ASSAULT0") + spawn(0) + M.close() + if("ASSAULT1") + spawn(0) + M.close() + if("ASSAULT2") + spawn(0) + M.close() + if("ASSAULT3") + spawn(0) + M.close() + special_ops.readyreset()//Reset firealarm after the team launched. + //End Marauder launchpad. + + var/area/start_location = locate(/area/shuttle/specops/centcom) + var/area/end_location = locate(/area/shuttle/specops/station) + + var/list/dstturfs = list() + var/throwy = world.maxy + + for(var/turf/T in end_location) + dstturfs += T + if(T.y < throwy) + throwy = T.y + + // hey you, get out of the way! + for(var/turf/T in dstturfs) + // find the turf to move things to + var/turf/D = locate(T.x, throwy - 1, 1) + //var/turf/E = get_step(D, SOUTH) + for(var/atom/movable/AM as mob|obj in T) + AM.Move(D) + if(istype(T, /turf/simulated)) + qdel(T) + + start_location.move_contents_to(end_location) + + for(var/turf/T in get_area_turfs(end_location) ) + var/mob/M = locate(/mob) in T + to_chat(M, "You have arrived to [station_name()]. Commence operation!") + + for(var/obj/machinery/computer/specops_shuttle/S in world) + S.specops_shuttle_timereset = world.time + SPECOPS_RETURN_DELAY + + qdel(announcer) + +/proc/specops_can_move() + if(specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom) + return 0 + for(var/obj/machinery/computer/specops_shuttle/S in world) + if(world.timeofday <= S.specops_shuttle_timereset) + return 0 + return 1 + +/obj/machinery/computer/specops_shuttle/attack_ai(var/mob/user as mob) + to_chat(user, "Access Denied.") + return 1 + +/obj/machinery/computer/specops_shuttle/attackby(I as obj, user as mob, params) + if(istype(I,/obj/item/card/emag)) + to_chat(user, "The electronic systems in this console are far too advanced for your primitive hacking peripherals.") + else + return ..() + +/obj/machinery/computer/specops_shuttle/attack_hand(var/mob/user as mob) + if(!allowed(user)) + to_chat(user, "Access Denied.") + return + +//Commented out so admins can do shenanigans at their leisure. Also makes the force-spawned admin ERTs able to use the shuttle. +// if(sent_strike_team == 0 && send_emergency_team == 0) +// to_chat(usr, "The strike team has not yet deployed.") +// return + + if(..()) + return + + user.machine = src + var/dat + if(temp) + dat = temp + else + dat += {"
        Special Operations Shuttle
        + \nLocation: [specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom ? "Departing for [station_name()] in ([specops_shuttle_timeleft] seconds.)":specops_shuttle_at_station ? "Station":"Dock"]
        + [specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom ? "\n*The Special Ops. shuttle is already leaving.*
        \n
        ":specops_shuttle_at_station ? "\nShuttle standing by...
        \n
        ":"\nDepart to [station_name()]
        \n
        "] + \nClose"} + + user << browse(dat, "window=computer;size=575x450") + onclose(user, "computer") + return + +/obj/machinery/computer/specops_shuttle/Topic(href, href_list) + if(..()) + return 1 + + if((usr.contents.Find(src) || (in_range(src, usr) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon))) + usr.machine = src + + if(href_list["sendtodock"]) + if(!specops_shuttle_at_station|| specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom) return + + if(!specops_can_move()) + to_chat(usr, "Central Command will not allow the Special Operations shuttle to return yet.") + if(world.timeofday <= specops_shuttle_timereset) + if(((world.timeofday - specops_shuttle_timereset)/10) > 60) + to_chat(usr, "[-((world.timeofday - specops_shuttle_timereset)/10)/60] minutes remain!") + to_chat(usr, "[-(world.timeofday - specops_shuttle_timereset)/10] seconds remain!") + return + + to_chat(usr, "The Special Operations shuttle will arrive at Central Command in [(SPECOPS_MOVETIME/10)] seconds.") + + temp += "Shuttle departing.

        OK" + updateUsrDialog() + + specops_shuttle_moving_to_centcom = 1 + specops_shuttle_time = world.timeofday + SPECOPS_MOVETIME + spawn(0) + specops_return() + + else if(href_list["sendtostation"]) + if(specops_shuttle_at_station || specops_shuttle_moving_to_station || specops_shuttle_moving_to_centcom) return + + if(!specops_can_move()) + to_chat(usr, "The Special Operations shuttle is unable to leave.") + return + + to_chat(usr, "The Special Operations shuttle will arrive on [station_name()] in [(SPECOPS_MOVETIME/10)] seconds.") + + temp += "Shuttle departing.

        OK" + updateUsrDialog() + + var/area/centcom/specops/special_ops = locate() + if(special_ops) + special_ops.readyalert()//Trigger alarm for the spec ops area. + specops_shuttle_moving_to_station = 1 + + specops_shuttle_time = world.timeofday + SPECOPS_MOVETIME + spawn(0) + specops_process() + + else if(href_list["mainmenu"]) + temp = null + + add_fingerprint(usr) + updateUsrDialog() + return diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index 0170d32b313..fb630d7a251 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -1,53 +1,53 @@ - -/obj/machinery/computer/station_alert - name = "station alert console" - desc = "Used to access the station's automated alert system." - icon_keyboard = "tech_key" - icon_screen = "alert:0" - light_color = LIGHT_COLOR_CYAN - circuit = /obj/item/circuitboard/stationalert_engineering - var/datum/nano_module/alarm_monitor/alarm_monitor - var/monitor_type = /datum/nano_module/alarm_monitor/engineering - -/obj/machinery/computer/station_alert/security - monitor_type = /datum/nano_module/alarm_monitor/security - circuit = /obj/item/circuitboard/stationalert_security - -/obj/machinery/computer/station_alert/all - monitor_type = /datum/nano_module/alarm_monitor/all - circuit = /obj/item/circuitboard/stationalert_all - -/obj/machinery/computer/station_alert/New() - ..() - alarm_monitor = new monitor_type(src) - alarm_monitor.register(src, /obj/machinery/computer/station_alert/update_icon) - -/obj/machinery/computer/station_alert/Destroy() - alarm_monitor.unregister(src) - QDEL_NULL(alarm_monitor) - return ..() - -/obj/machinery/computer/station_alert/attack_ai(mob/user) - add_fingerprint(user) - if(stat & (BROKEN|NOPOWER)) - return - interact(user) - -/obj/machinery/computer/station_alert/attack_hand(mob/user) - add_fingerprint(user) - if(stat & (BROKEN|NOPOWER)) - return - interact(user) - -/obj/machinery/computer/station_alert/interact(mob/user) - alarm_monitor.ui_interact(user) - -/obj/machinery/computer/station_alert/update_icon() - if(alarm_monitor) - var/list/alarms = alarm_monitor.major_alarms() - if(alarms.len) - icon_screen = "alert:2" - else - icon_screen = "alert:0" - - ..() \ No newline at end of file + +/obj/machinery/computer/station_alert + name = "station alert console" + desc = "Used to access the station's automated alert system." + icon_keyboard = "tech_key" + icon_screen = "alert:0" + light_color = LIGHT_COLOR_CYAN + circuit = /obj/item/circuitboard/stationalert_engineering + var/datum/nano_module/alarm_monitor/alarm_monitor + var/monitor_type = /datum/nano_module/alarm_monitor/engineering + +/obj/machinery/computer/station_alert/security + monitor_type = /datum/nano_module/alarm_monitor/security + circuit = /obj/item/circuitboard/stationalert_security + +/obj/machinery/computer/station_alert/all + monitor_type = /datum/nano_module/alarm_monitor/all + circuit = /obj/item/circuitboard/stationalert_all + +/obj/machinery/computer/station_alert/New() + ..() + alarm_monitor = new monitor_type(src) + alarm_monitor.register(src, /obj/machinery/computer/station_alert/update_icon) + +/obj/machinery/computer/station_alert/Destroy() + alarm_monitor.unregister(src) + QDEL_NULL(alarm_monitor) + return ..() + +/obj/machinery/computer/station_alert/attack_ai(mob/user) + add_fingerprint(user) + if(stat & (BROKEN|NOPOWER)) + return + interact(user) + +/obj/machinery/computer/station_alert/attack_hand(mob/user) + add_fingerprint(user) + if(stat & (BROKEN|NOPOWER)) + return + interact(user) + +/obj/machinery/computer/station_alert/interact(mob/user) + alarm_monitor.ui_interact(user) + +/obj/machinery/computer/station_alert/update_icon() + if(alarm_monitor) + var/list/alarms = alarm_monitor.major_alarms() + if(alarms.len) + icon_screen = "alert:2" + else + icon_screen = "alert:0" + + ..() diff --git a/code/game/machinery/computer/syndicate_specops_shuttle.dm b/code/game/machinery/computer/syndicate_specops_shuttle.dm index 71bfe9f43ab..aefeaaa864b 100644 --- a/code/game/machinery/computer/syndicate_specops_shuttle.dm +++ b/code/game/machinery/computer/syndicate_specops_shuttle.dm @@ -1,256 +1,256 @@ -//Config stuff -#define SYNDICATE_ELITE_MOVETIME 600 //Time to station is milliseconds. 60 seconds, enough time for everyone to be on the shuttle before it leaves. -#define SYNDICATE_ELITE_STATION_AREATYPE "/area/shuttle/syndicate_elite/station" //Type of the spec ops shuttle area for station -#define SYNDICATE_ELITE_DOCK_AREATYPE "/area/shuttle/syndicate_elite/mothership" //Type of the spec ops shuttle area for dock - -var/syndicate_elite_shuttle_moving_to_station = 0 -var/syndicate_elite_shuttle_moving_to_mothership = 0 -var/syndicate_elite_shuttle_at_station = 0 -var/syndicate_elite_shuttle_can_send = 1 -var/syndicate_elite_shuttle_time = 0 -var/syndicate_elite_shuttle_timeleft = 0 - -/obj/machinery/computer/syndicate_elite_shuttle - name = "\improper Elite Syndicate Squad shuttle console" - icon = 'icons/obj/computer.dmi' - icon_keyboard = "syndie_key" - icon_screen = "syndishuttle" - light_color = LIGHT_COLOR_PURE_CYAN - req_access = list(ACCESS_SYNDICATE) - var/temp = null - var/hacked = 0 - var/allowedtocall = 0 - -/proc/syndicate_elite_process() - var/area/syndicate_mothership/control/syndicate_ship = locate()//To find announcer. This area should exist for this proc to work. - var/area/syndicate_mothership/elite_squad/elite_squad = locate()//Where is the specops area located? - var/mob/living/silicon/decoy/announcer = locate() in syndicate_ship//We need a fake AI to announce some stuff below. Otherwise it will be wonky. - - var/message_tracker[] = list(0,1,2,3,5,10,30,45)//Create a a list with potential time values. - var/message = "THE SYNDICATE ELITE SHUTTLE IS PREPARING FOR LAUNCH"//Initial message shown. - if(announcer) - announcer.say(message) - // message = "ARMORED SQUAD TAKE YOUR POSITION ON GRAVITY LAUNCH PAD" - // announcer.say(message) - - while(syndicate_elite_shuttle_time - world.timeofday > 0) - var/ticksleft = syndicate_elite_shuttle_time - world.timeofday - - if(ticksleft > 1e5) - syndicate_elite_shuttle_time = world.timeofday // midnight rollover - syndicate_elite_shuttle_timeleft = (ticksleft / 10) - - //All this does is announce the time before launch. - if(announcer) - var/rounded_time_left = round(syndicate_elite_shuttle_timeleft)//Round time so that it will report only once, not in fractions. - if(rounded_time_left in message_tracker)//If that time is in the list for message announce. - message = "ALERT: [rounded_time_left] SECOND[(rounded_time_left!=1)?"S":""] REMAIN" - if(rounded_time_left==0) - message = "ALERT: TAKEOFF" - announcer.say(message) - message_tracker -= rounded_time_left//Remove the number from the list so it won't be called again next cycle. - //Should call all the numbers but lag could mean some issues. Oh well. Not much I can do about that. - - sleep(5) - - syndicate_elite_shuttle_moving_to_station = 0 - syndicate_elite_shuttle_moving_to_mothership = 0 - - syndicate_elite_shuttle_at_station = 1 - if(syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership) return - - if(!syndicate_elite_can_move()) - to_chat(usr, "The Syndicate Elite shuttle is unable to leave.") - return - - sleep(600) -/* - //Begin Marauder launchpad. - spawn(0)//So it parallel processes it. - for(var/obj/machinery/door/poddoor/M in elite_squad) - switch(M.id) - if("ASSAULT0") - spawn(10)//1 second delay between each. - M.open() - if("ASSAULT1") - spawn(20) - M.open() - if("ASSAULT2") - spawn(30) - M.open() - if("ASSAULT3") - spawn(40) - M.open() - - sleep(10) - - var/spawn_marauder[] = new() - for(var/obj/effect/landmark/L in GLOB.landmarks_list) - if(L.name == "Marauder Entry") - spawn_marauder.Add(L) - for(var/obj/effect/landmark/L in GLOB.landmarks_list) - if(L.name == "Marauder Exit") - var/obj/effect/portal/P = new(L.loc) - P.invisibility = 101//So it is not seen by anyone. - P.failchance = 0//So it has no fail chance when teleporting. - P.target = pick(spawn_marauder)//Where the marauder will arrive. - spawn_marauder.Remove(P.target) - - sleep(10) - - for(var/obj/machinery/mass_driver/M in elite_squad) - switch(M.id) - if("ASSAULT0") - spawn(10) - M.drive() - if("ASSAULT1") - spawn(20) - M.drive() - if("ASSAULT2") - spawn(30) - M.drive() - if("ASSAULT3") - spawn(40) - M.drive() - - sleep(50)//Doors remain open for 5 seconds. - - for(var/obj/machinery/door/poddoor/M in elite_squad) - switch(M.id)//Doors close at the same time. - if("ASSAULT0") - spawn(0) - M.close() - if("ASSAULT1") - spawn(0) - M.close() - if("ASSAULT2") - spawn(0) - M.close() - if("ASSAULT3") - spawn(0) - M.close() - */ - elite_squad.readyreset()//Reset firealarm after the team launched. - //End Marauder launchpad. - - for(var/obj/effect/landmark/L in GLOB.landmarks_list) - if(L.name == "Syndicate Breach Area") - explosion(L.loc,4,6,8,10,0) - - sleep(40) - - - var/area/start_location = locate(/area/shuttle/syndicate_elite/mothership) - var/area/end_location = locate(/area/shuttle/syndicate_elite/station) - - var/list/dstturfs = list() - var/throwy = world.maxy - - for(var/turf/T in end_location) - dstturfs = T - if(T.y < throwy) - throwy = T.y - - // hey you, get out of the way! - for(var/turf/T in dstturfs) - // find the turf to move things to - var/turf/D = locate(T.x, throwy - 1, 1) - //var/turf/E = get_step(D, SOUTH) - for(var/atom/movable/AM as mob|obj in T) - AM.Move(D) - if(istype(T, /turf/simulated)) - qdel(T) - - for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area... - bug.gib() - - for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug... - pest.gib() - - start_location.move_contents_to(end_location) - - for(var/turf/T in get_area_turfs(end_location) ) - var/mob/M = locate(/mob) in T - to_chat(M, "You have arrived to [station_name()]. Commence operation!") - -/proc/syndicate_elite_can_move() - if(syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership) return 0 - else return 1 - -/obj/machinery/computer/syndicate_elite_shuttle/attack_ai(var/mob/user as mob) - to_chat(user, "Access Denied.") - return 1 - -/obj/machinery/computer/syndicate_elite_shuttle/attackby(I as obj, user as mob, params) - if(istype(I,/obj/item/card/emag)) - to_chat(user, "The electronic systems in this console are far too advanced for your primitive hacking peripherals.") - else - return ..() - -/obj/machinery/computer/syndicate_elite_shuttle/attack_hand(var/mob/user as mob) - if(!allowed(user)) - to_chat(user, "Access Denied.") - return - -// if(sent_syndicate_strike_team == 0) -// to_chat(usr, "The strike team has not yet deployed.") -// return - - if(..()) - return - - user.set_machine(src) - var/dat - if(temp) - dat = temp - else - dat = {"
        Special Operations Shuttle
        - \nLocation: [syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership ? "Departing for [station_name()] in ([syndicate_elite_shuttle_timeleft] seconds.)":syndicate_elite_shuttle_at_station ? "Station":"Dock"]
        - [syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership ? "\n*The Syndicate Elite shuttle is already leaving.*
        \n
        ":syndicate_elite_shuttle_at_station ? "\nShuttle Offline
        \n
        ":"\nDepart to [station_name()]
        \n
        "] - \nClose"} - - user << browse(dat, "window=computer;size=575x450") - onclose(user, "computer") - return - -/obj/machinery/computer/syndicate_elite_shuttle/Topic(href, href_list) - if(..()) - return 1 - - if((usr.contents.Find(src) || (in_range(src, usr) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon))) - usr.set_machine(src) - - if(href_list["sendtodock"]) - if(!syndicate_elite_shuttle_at_station|| syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership) return - - to_chat(usr, "The Syndicate will not allow the Elite Squad shuttle to return.") - return - - else if(href_list["sendtostation"]) - if(syndicate_elite_shuttle_at_station || syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership) return - - if(!specops_can_move()) - to_chat(usr, "The Syndicate Elite shuttle is unable to leave.") - return - - to_chat(usr, "The Syndicate Elite shuttle will arrive on [station_name()] in [(SYNDICATE_ELITE_MOVETIME/10)] seconds.") - - temp = "Shuttle departing.

        OK" - updateUsrDialog() - - var/area/syndicate_mothership/elite_squad/elite_squad = locate() - if(elite_squad) - elite_squad.readyalert()//Trigger alarm for the spec ops area. - syndicate_elite_shuttle_moving_to_station = 1 - - syndicate_elite_shuttle_time = world.timeofday + SYNDICATE_ELITE_MOVETIME - spawn(0) - syndicate_elite_process() - - - else if(href_list["mainmenu"]) - temp = null - - add_fingerprint(usr) - updateUsrDialog() - return +//Config stuff +#define SYNDICATE_ELITE_MOVETIME 600 //Time to station is milliseconds. 60 seconds, enough time for everyone to be on the shuttle before it leaves. +#define SYNDICATE_ELITE_STATION_AREATYPE "/area/shuttle/syndicate_elite/station" //Type of the spec ops shuttle area for station +#define SYNDICATE_ELITE_DOCK_AREATYPE "/area/shuttle/syndicate_elite/mothership" //Type of the spec ops shuttle area for dock + +var/syndicate_elite_shuttle_moving_to_station = 0 +var/syndicate_elite_shuttle_moving_to_mothership = 0 +var/syndicate_elite_shuttle_at_station = 0 +var/syndicate_elite_shuttle_can_send = 1 +var/syndicate_elite_shuttle_time = 0 +var/syndicate_elite_shuttle_timeleft = 0 + +/obj/machinery/computer/syndicate_elite_shuttle + name = "\improper Elite Syndicate Squad shuttle console" + icon = 'icons/obj/computer.dmi' + icon_keyboard = "syndie_key" + icon_screen = "syndishuttle" + light_color = LIGHT_COLOR_PURE_CYAN + req_access = list(ACCESS_SYNDICATE) + var/temp = null + var/hacked = 0 + var/allowedtocall = 0 + +/proc/syndicate_elite_process() + var/area/syndicate_mothership/control/syndicate_ship = locate()//To find announcer. This area should exist for this proc to work. + var/area/syndicate_mothership/elite_squad/elite_squad = locate()//Where is the specops area located? + var/mob/living/silicon/decoy/announcer = locate() in syndicate_ship//We need a fake AI to announce some stuff below. Otherwise it will be wonky. + + var/message_tracker[] = list(0,1,2,3,5,10,30,45)//Create a a list with potential time values. + var/message = "THE SYNDICATE ELITE SHUTTLE IS PREPARING FOR LAUNCH"//Initial message shown. + if(announcer) + announcer.say(message) + // message = "ARMORED SQUAD TAKE YOUR POSITION ON GRAVITY LAUNCH PAD" + // announcer.say(message) + + while(syndicate_elite_shuttle_time - world.timeofday > 0) + var/ticksleft = syndicate_elite_shuttle_time - world.timeofday + + if(ticksleft > 1e5) + syndicate_elite_shuttle_time = world.timeofday // midnight rollover + syndicate_elite_shuttle_timeleft = (ticksleft / 10) + + //All this does is announce the time before launch. + if(announcer) + var/rounded_time_left = round(syndicate_elite_shuttle_timeleft)//Round time so that it will report only once, not in fractions. + if(rounded_time_left in message_tracker)//If that time is in the list for message announce. + message = "ALERT: [rounded_time_left] SECOND[(rounded_time_left!=1)?"S":""] REMAIN" + if(rounded_time_left==0) + message = "ALERT: TAKEOFF" + announcer.say(message) + message_tracker -= rounded_time_left//Remove the number from the list so it won't be called again next cycle. + //Should call all the numbers but lag could mean some issues. Oh well. Not much I can do about that. + + sleep(5) + + syndicate_elite_shuttle_moving_to_station = 0 + syndicate_elite_shuttle_moving_to_mothership = 0 + + syndicate_elite_shuttle_at_station = 1 + if(syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership) return + + if(!syndicate_elite_can_move()) + to_chat(usr, "The Syndicate Elite shuttle is unable to leave.") + return + + sleep(600) +/* + //Begin Marauder launchpad. + spawn(0)//So it parallel processes it. + for(var/obj/machinery/door/poddoor/M in elite_squad) + switch(M.id) + if("ASSAULT0") + spawn(10)//1 second delay between each. + M.open() + if("ASSAULT1") + spawn(20) + M.open() + if("ASSAULT2") + spawn(30) + M.open() + if("ASSAULT3") + spawn(40) + M.open() + + sleep(10) + + var/spawn_marauder[] = new() + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(L.name == "Marauder Entry") + spawn_marauder.Add(L) + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(L.name == "Marauder Exit") + var/obj/effect/portal/P = new(L.loc) + P.invisibility = 101//So it is not seen by anyone. + P.failchance = 0//So it has no fail chance when teleporting. + P.target = pick(spawn_marauder)//Where the marauder will arrive. + spawn_marauder.Remove(P.target) + + sleep(10) + + for(var/obj/machinery/mass_driver/M in elite_squad) + switch(M.id) + if("ASSAULT0") + spawn(10) + M.drive() + if("ASSAULT1") + spawn(20) + M.drive() + if("ASSAULT2") + spawn(30) + M.drive() + if("ASSAULT3") + spawn(40) + M.drive() + + sleep(50)//Doors remain open for 5 seconds. + + for(var/obj/machinery/door/poddoor/M in elite_squad) + switch(M.id)//Doors close at the same time. + if("ASSAULT0") + spawn(0) + M.close() + if("ASSAULT1") + spawn(0) + M.close() + if("ASSAULT2") + spawn(0) + M.close() + if("ASSAULT3") + spawn(0) + M.close() + */ + elite_squad.readyreset()//Reset firealarm after the team launched. + //End Marauder launchpad. + + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(L.name == "Syndicate Breach Area") + explosion(L.loc,4,6,8,10,0) + + sleep(40) + + + var/area/start_location = locate(/area/shuttle/syndicate_elite/mothership) + var/area/end_location = locate(/area/shuttle/syndicate_elite/station) + + var/list/dstturfs = list() + var/throwy = world.maxy + + for(var/turf/T in end_location) + dstturfs = T + if(T.y < throwy) + throwy = T.y + + // hey you, get out of the way! + for(var/turf/T in dstturfs) + // find the turf to move things to + var/turf/D = locate(T.x, throwy - 1, 1) + //var/turf/E = get_step(D, SOUTH) + for(var/atom/movable/AM as mob|obj in T) + AM.Move(D) + if(istype(T, /turf/simulated)) + qdel(T) + + for(var/mob/living/carbon/bug in end_location) // If someone somehow is still in the shuttle's docking area... + bug.gib() + + for(var/mob/living/simple_animal/pest in end_location) // And for the other kind of bug... + pest.gib() + + start_location.move_contents_to(end_location) + + for(var/turf/T in get_area_turfs(end_location) ) + var/mob/M = locate(/mob) in T + to_chat(M, "You have arrived to [station_name()]. Commence operation!") + +/proc/syndicate_elite_can_move() + if(syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership) return 0 + else return 1 + +/obj/machinery/computer/syndicate_elite_shuttle/attack_ai(var/mob/user as mob) + to_chat(user, "Access Denied.") + return 1 + +/obj/machinery/computer/syndicate_elite_shuttle/attackby(I as obj, user as mob, params) + if(istype(I,/obj/item/card/emag)) + to_chat(user, "The electronic systems in this console are far too advanced for your primitive hacking peripherals.") + else + return ..() + +/obj/machinery/computer/syndicate_elite_shuttle/attack_hand(var/mob/user as mob) + if(!allowed(user)) + to_chat(user, "Access Denied.") + return + +// if(sent_syndicate_strike_team == 0) +// to_chat(usr, "The strike team has not yet deployed.") +// return + + if(..()) + return + + user.set_machine(src) + var/dat + if(temp) + dat = temp + else + dat = {"
        Special Operations Shuttle
        + \nLocation: [syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership ? "Departing for [station_name()] in ([syndicate_elite_shuttle_timeleft] seconds.)":syndicate_elite_shuttle_at_station ? "Station":"Dock"]
        + [syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership ? "\n*The Syndicate Elite shuttle is already leaving.*
        \n
        ":syndicate_elite_shuttle_at_station ? "\nShuttle Offline
        \n
        ":"\nDepart to [station_name()]
        \n
        "] + \nClose"} + + user << browse(dat, "window=computer;size=575x450") + onclose(user, "computer") + return + +/obj/machinery/computer/syndicate_elite_shuttle/Topic(href, href_list) + if(..()) + return 1 + + if((usr.contents.Find(src) || (in_range(src, usr) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon))) + usr.set_machine(src) + + if(href_list["sendtodock"]) + if(!syndicate_elite_shuttle_at_station|| syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership) return + + to_chat(usr, "The Syndicate will not allow the Elite Squad shuttle to return.") + return + + else if(href_list["sendtostation"]) + if(syndicate_elite_shuttle_at_station || syndicate_elite_shuttle_moving_to_station || syndicate_elite_shuttle_moving_to_mothership) return + + if(!specops_can_move()) + to_chat(usr, "The Syndicate Elite shuttle is unable to leave.") + return + + to_chat(usr, "The Syndicate Elite shuttle will arrive on [station_name()] in [(SYNDICATE_ELITE_MOVETIME/10)] seconds.") + + temp = "Shuttle departing.

        OK" + updateUsrDialog() + + var/area/syndicate_mothership/elite_squad/elite_squad = locate() + if(elite_squad) + elite_squad.readyalert()//Trigger alarm for the spec ops area. + syndicate_elite_shuttle_moving_to_station = 1 + + syndicate_elite_shuttle_time = world.timeofday + SYNDICATE_ELITE_MOVETIME + spawn(0) + syndicate_elite_process() + + + else if(href_list["mainmenu"]) + temp = null + + add_fingerprint(usr) + updateUsrDialog() + return diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index b051ae34ca9..efee10767ba 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -1,1091 +1,1091 @@ -/obj/machinery/constructable_frame //Made into a seperate type to make future revisions easier. - name = "machine frame" - icon = 'icons/obj/stock_parts.dmi' - icon_state = "box_0" - density = 1 - anchored = 1 - use_power = NO_POWER_USE - max_integrity = 250 - var/obj/item/circuitboard/circuit = null - var/list/components = null - var/list/req_components = null - var/list/req_component_names = null // user-friendly names of components - var/state = 1 - - // For pods - var/list/connected_parts = list() - var/pattern_idx=0 - -/obj/machinery/constructable_frame/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/sheet/metal(loc, 5) - if(state >= 2) - var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil(loc) - A.amount = 5 - if(circuit) - circuit.forceMove(loc) - circuit = null - return ..() - -/obj/machinery/constructable_frame/obj_break(damage_flag) - deconstruct() - -// unfortunately, we have to instance the objects really quickly to get the names -// fortunately, this is only called once when the board is added and the items are immediately GC'd -// and none of the parts do much in their constructors -/obj/machinery/constructable_frame/proc/update_namelist() - if(!req_components) - return - - req_component_names = new() - for(var/tname in req_components) - var/path = tname - var/obj/O = new path() - req_component_names[tname] = O.name - -/obj/machinery/constructable_frame/proc/get_req_components_amt() - var/amt = 0 - for(var/path in req_components) - amt += req_components[path] - return amt - -// update description of required components remaining -/obj/machinery/constructable_frame/proc/update_req_desc() - if(!req_components || !req_component_names) - return - - var/hasContent = 0 - desc = "Requires" - for(var/i = 1 to req_components.len) - var/tname = req_components[i] - var/amt = req_components[tname] - if(amt == 0) - continue - var/use_and = i == req_components.len - desc += "[(hasContent ? (use_and ? ", and" : ",") : "")] [amt] [amt == 1 ? req_component_names[tname] : "[req_component_names[tname]]\s"]" - hasContent = 1 - - if(!hasContent) - desc = "Does not require any more components." - else - desc += "." - -/obj/machinery/constructable_frame/machine_frame/attackby(obj/item/P, mob/user, params) - switch(state) - if(1) - if(istype(P, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = P - if(C.amount >= 5) - playsound(src.loc, C.usesound, 50, 1) - to_chat(user, "You start to add cables to the frame.") - if(do_after(user, 20 * C.toolspeed, target = src)) - if(state == 1 && C.amount >= 5 && C.use(5)) - to_chat(user, "You add cables to the frame.") - state = 2 - icon_state = "box_1" - else - to_chat(user, "At some point during construction you lost some cable. Make sure you have five lengths before trying again.") - return - else - to_chat(user, "You need five lengths of cable to wire the frame.") - return - - if(istype(P, /obj/item/wrench)) - playsound(src.loc, P.usesound, 75, 1) - to_chat(user, "You dismantle the frame.") - deconstruct(TRUE) - return - if(2) - if(istype(P, /obj/item/circuitboard)) - var/obj/item/circuitboard/B = P - if(B.board_type == "machine") - playsound(src.loc, B.usesound, 50, 1) - to_chat(user, "You add the circuit board to the frame.") - circuit = P - user.drop_item() - P.loc = src - icon_state = "box_2" - state = 3 - components = list() - req_components = circuit.req_components.Copy() - update_namelist() - update_req_desc() - else - to_chat(user, "This frame does not accept circuit boards of this type!") - return - if(istype(P, /obj/item/wirecutters)) - playsound(src.loc, P.usesound, 50, 1) - to_chat(user, "You remove the cables.") - state = 1 - icon_state = "box_0" - var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil(src.loc,5) - A.amount = 5 - return - if(3) - if(istype(P, /obj/item/crowbar)) - playsound(src.loc, P.usesound, 50, 1) - state = 2 - circuit.loc = src.loc - circuit = null - if(components.len == 0) - to_chat(user, "You remove the circuit board.") - else - to_chat(user, "You remove the circuit board and other components.") - for(var/obj/item/I in components) - I.loc = src.loc - desc = initial(desc) - req_components = null - components = null - icon_state = "box_1" - return - - if(istype(P, /obj/item/screwdriver)) - var/component_check = 1 - for(var/R in req_components) - if(req_components[R] > 0) - component_check = 0 - break - if(component_check) - playsound(src.loc, P.usesound, 50, 1) - var/obj/machinery/new_machine = new src.circuit.build_path(src.loc) - new_machine.on_construction() - for(var/obj/O in new_machine.component_parts) - qdel(O) - new_machine.component_parts = list() - for(var/obj/O in src) - O.loc = null - new_machine.component_parts += O - circuit.loc = null - new_machine.RefreshParts() - qdel(src) - return - - if(istype(P, /obj/item/storage/part_replacer) && P.contents.len && get_req_components_amt()) - var/obj/item/storage/part_replacer/replacer = P - var/list/added_components = list() - var/list/part_list = list() - - //Assemble a list of current parts, then sort them by their rating! - for(var/obj/item/stock_parts/co in replacer) - part_list += co - - for(var/path in req_components) - while(req_components[path] > 0 && (locate(path) in part_list)) - var/obj/item/part = (locate(path) in part_list) - added_components[part] = path - replacer.remove_from_storage(part, src) - req_components[path]-- - part_list -= part - - for(var/obj/item/stock_parts/part in added_components) - components += part - to_chat(user, "[part.name] applied.") - replacer.play_rped_sound() - - update_req_desc() - return - - if(istype(P, /obj/item)) - var/success - for(var/I in req_components) - if(istype(P, I) && (req_components[I] > 0) && (!(P.flags & NODROP) || istype(P, /obj/item/stack))) - success=1 - playsound(src.loc, P.usesound, 50, 1) - if(istype(P, /obj/item/stack)) - var/obj/item/stack/S = P - var/camt = min(S.amount, req_components[I]) - var/obj/item/stack/NS = new P.type(src) - NS.amount = camt - NS.update_icon() - S.use(camt) - components += NS - req_components[I] -= camt - update_req_desc() - break - user.drop_item() - P.forceMove(src) - components += P - req_components[I]-- - update_req_desc() - return 1 - if(!success) - to_chat(user, "You cannot add that to the machine!") - return 0 - return - if(user.a_intent == INTENT_HARM) - return ..() - - -//Machine Frame Circuit Boards -/*Common Parts: Parts List: Ignitor, Timer, Infra-red laser, Infra-red sensor, t_scanner, Capacitor, Valve, sensor unit, -micro-manipulator, console screen, beaker, Microlaser, matter bin, power cells. -Note: Once everything is added to the public areas, will add MAT_METAL and MAT_GLASS to circuit boards since autolathe won't be able -to destroy them and players will be able to make replacements. -*/ -/obj/item/circuitboard/vendor - name = "circuit board (Booze-O-Mat Vendor)" - board_type = "machine" - origin_tech = "programming=1" - frame_desc = "Requires 1 Resupply Canister." - build_path = /obj/machinery/vending/boozeomat - req_components = list(/obj/item/vending_refill/boozeomat = 1) - - var/static/list/vending_names_paths = list( - /obj/machinery/vending/boozeomat = "Booze-O-Mat", - /obj/machinery/vending/coffee = "Solar's Best Hot Drinks", - /obj/machinery/vending/snack = "Getmore Chocolate Corp", - /obj/machinery/vending/chinese = "Mr. Chang", - /obj/machinery/vending/cola = "Robust Softdrinks", - /obj/machinery/vending/cigarette = "ShadyCigs Deluxe", - /obj/machinery/vending/hatdispenser = "Hatlord 9000", - /obj/machinery/vending/suitdispenser = "Suitlord 9000", - /obj/machinery/vending/shoedispenser = "Shoelord 9000", - /obj/machinery/vending/clothing = "ClothesMate", - /obj/machinery/vending/medical = "NanoMed Plus", - /obj/machinery/vending/wallmed = "NanoMed", - /obj/machinery/vending/assist = "Vendomat", - /obj/machinery/vending/engivend = "Engi-Vend", - /obj/machinery/vending/hydronutrients = "NutriMax", - /obj/machinery/vending/hydroseeds = "MegaSeed Servitor", - /obj/machinery/vending/sustenance = "Sustenance Vendor", - /obj/machinery/vending/dinnerware = "Plasteel Chef's Dinnerware Vendor", - /obj/machinery/vending/cart = "PTech", - /obj/machinery/vending/robotics = "Robotech Deluxe", - /obj/machinery/vending/engineering = "Robco Tool Maker", - /obj/machinery/vending/sovietsoda = "BODA", - /obj/machinery/vending/security = "SecTech", - /obj/machinery/vending/modularpc = "Deluxe Silicate Selections", - /obj/machinery/vending/crittercare = "CritterCare") - -/obj/item/circuitboard/vendor/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - var/static/list/display_vending_names_paths - if(!display_vending_names_paths) - display_vending_names_paths = list() - for(var/path in vending_names_paths) - display_vending_names_paths[vending_names_paths[path]] = path - var/choice = input(user, "Choose a new brand","Select an Item") as null|anything in display_vending_names_paths - if(loc != user) - to_chat(user, "You need to keep [src] in your hands while doing that!") - return - set_type(display_vending_names_paths[choice]) - -/obj/item/circuitboard/vendor/proc/set_type(obj/machinery/vending/typepath) - build_path = typepath - name = "circuit board ([vending_names_paths[build_path]] Vendor)" - req_components = list(initial(typepath.refill_canister) = 1) - -/obj/item/circuitboard/smes - name = "circuit board (SMES)" - build_path = /obj/machinery/power/smes - board_type = "machine" - origin_tech = "programming=3;powerstorage=3;engineering=3" - frame_desc = "Requires 5 pieces of cable, 5 Power Cells and 1 Capacitor." - req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/cell = 5, - /obj/item/stock_parts/capacitor = 1) - -/obj/item/circuitboard/emitter - name = "circuit board (Emitter)" - build_path = /obj/machinery/power/emitter - board_type = "machine" - origin_tech = "programming=3;powerstorage=4;engineering=4" - req_components = list( - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stock_parts/manipulator = 1) - -/obj/item/circuitboard/power_compressor - name = "circuit board (Power Compressor)" - build_path = /obj/machinery/power/compressor - board_type = "machine" - origin_tech = "programming=4;powerstorage=4;engineering=4" - req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/manipulator = 6) - -/obj/item/circuitboard/power_turbine - name = "circuit board (Power Turbine)" - build_path = /obj/machinery/power/turbine - board_type = "machine" - origin_tech = "programming=4;powerstorage=4;engineering=4" - req_components = list( - /obj/item/stack/cable_coil = 5, - /obj/item/stock_parts/capacitor = 6) - -/obj/item/circuitboard/thermomachine - name = "circuit board (Freezer)" - desc = "Use screwdriver to switch between heating and cooling modes." - build_path = /obj/machinery/atmospherics/unary/cold_sink/freezer - board_type = "machine" - origin_tech = "programming=3;plasmatech=3" - frame_desc = "Requires 2 Matter Bins, 2 Micro Lasers, 1 piece of cable and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/micro_laser = 2, - /obj/item/stack/cable_coil = 1, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/thermomachine/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/screwdriver)) - if(build_path == /obj/machinery/atmospherics/unary/cold_sink/freezer) - build_path = /obj/machinery/atmospherics/unary/heat_reservoir/heater - name = "circuit board (Heater)" - to_chat(user, "You set the board to heating.") - else - build_path = /obj/machinery/atmospherics/unary/cold_sink/freezer - name = "circuit board (Freezer)" - to_chat(user, "You set the board to cooling.") - return - return ..() - -/obj/item/circuitboard/recharger - name = "circuit board (Recharger)" - build_path = /obj/machinery/recharger - board_type = "machine" - origin_tech = "powerstorage=3;materials=2" - frame_desc = "Requires 1 Capacitor" - req_components = list(/obj/item/stock_parts/capacitor = 1) - -/obj/item/circuitboard/snow_machine - name = "circuit board (snow machine)" - build_path = /obj/machinery/snow_machine - board_type = "machine" - origin_tech = "programming=2;materials=2" - frame_desc = "Requires 1 Matter Bin and 1 Micro Laser." - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/micro_laser = 1) - -/obj/item/circuitboard/biogenerator - name = "circuit board (Biogenerator)" - build_path = /obj/machinery/biogenerator - board_type = "machine" - origin_tech = "programming=2;biotech=3;materials=3" - frame_desc = "Requires 1 Matter Bin, 1 Manipulator, 1 piece of cable and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 1, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/plantgenes - name = "Plant DNA Manipulator (Machine Board)" - build_path = /obj/machinery/plantgenes - board_type = "machine" - origin_tech = "programming=3;biotech=3" - frame_desc = "Requires 1 Manipulator, 1 Micro Laser, 1 Console Screen, and 1 Scanning Module." - req_components = list( - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/scanning_module = 1) - -/obj/item/circuitboard/plantgenes/vault - -/obj/item/circuitboard/seed_extractor - name = "circuit board (Seed Extractor)" - build_path = /obj/machinery/seed_extractor - board_type = "machine" - origin_tech = "programming=1" - frame_desc = "Requires 1 Matter Bin and 1 Manipulator." - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - -/obj/item/circuitboard/hydroponics - name = "circuit board (Hydroponics Tray)" - build_path = /obj/machinery/hydroponics/constructable - board_type = "machine" - origin_tech = "programming=1;biotech=2" - frame_desc = "Requires 2 Matter Bins, 1 Manipulator, and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/microwave - name = "circuit board (Microwave)" - build_path = /obj/machinery/kitchen_machine/microwave - board_type = "machine" - origin_tech = "programming=2;magnets=2" - frame_desc = "Requires 1 Micro Laser, 2 pieces of cable and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/cable_coil = 2, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/oven - name = "circuit board (Oven)" - build_path = /obj/machinery/kitchen_machine/oven - board_type = "machine" - origin_tech = "programming=2;magnets=2" - frame_desc = "Requires 2 Micro Lasers, 5 pieces of cable and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/micro_laser = 2, - /obj/item/stack/cable_coil = 5, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/grill - name = "circuit board (Grill)" - build_path = /obj/machinery/kitchen_machine/grill - board_type = "machine" - origin_tech = "programming=2;magnets=2" - frame_desc = "Requires 2 Micro Lasers, 5 pieces of cable and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/micro_laser = 2, - /obj/item/stack/cable_coil = 5, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/candy_maker - name = "circuit board (Candy Maker)" - build_path = /obj/machinery/kitchen_machine/candy_maker - board_type = "machine" - origin_tech = "programming=2;magnets=2" - frame_desc = "Requires 1 Manipulator, 5 pieces of cable and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 5, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/deepfryer - name = "circuit board (Deep Fryer)" - build_path = /obj/machinery/cooker/deepfryer - board_type = "machine" - origin_tech = "programming=1" - frame_desc = "Requires 2 Micro Lasers and 5 pieces of cable." - req_components = list( - /obj/item/stock_parts/micro_laser = 2, - /obj/item/stack/cable_coil = 5) - -/obj/item/circuitboard/gibber - name = "circuit board (Gibber)" - build_path = /obj/machinery/gibber - board_type = "machine" - origin_tech = "programming=2;engineering=2" - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - -/obj/item/circuitboard/tesla_coil - name = "circuit board (Tesla Coil)" - build_path = /obj/machinery/power/tesla_coil - board_type = "machine" - origin_tech = "programming=3;magnets=3;powerstorage=3" - req_components = list( - /obj/item/stock_parts/capacitor = 1) - -/obj/item/circuitboard/grounding_rod - name = "circuit board (Grounding Rod)" - build_path = /obj/machinery/power/grounding_rod - board_type = "machine" - origin_tech = "programming=3;powerstorage=3;magnets=3;plasmatech=2" - req_components = list( - /obj/item/stock_parts/capacitor = 1) - -/obj/item/circuitboard/processor - name = "circuit board (Food processor)" - build_path = /obj/machinery/processor - board_type = "machine" - origin_tech = "programming=1" - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - -/obj/item/circuitboard/recycler - name = "circuit board (Recycler)" - build_path = /obj/machinery/recycler - board_type = "machine" - origin_tech = "programming=2;engineering=2" - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - -/obj/item/circuitboard/smartfridge - name = "circuit board (Smartfridge)" - build_path = /obj/machinery/smartfridge - board_type = "machine" - origin_tech = "programming=1" - req_components = list( - /obj/item/stock_parts/matter_bin = 1) - var/list/fridge_names_paths = list( - "\improper SmartFridge" = /obj/machinery/smartfridge, - "\improper MegaSeed Servitor" = /obj/machinery/smartfridge/seeds, - "\improper Refrigerated Medicine Storage" = /obj/machinery/smartfridge/medbay, - "\improper Slime Extract Storage" = /obj/machinery/smartfridge/secure/extract, - "\improper Secure Refrigerated Medicine Storage" = /obj/machinery/smartfridge/secure/medbay, - "\improper Smart Chemical Storage" = /obj/machinery/smartfridge/secure/chemistry, - "smart virus storage" = /obj/machinery/smartfridge/secure/chemistry/virology, - "\improper Drink Showcase" = /obj/machinery/smartfridge/drinks - ) - - - -/obj/item/circuitboard/smartfridge/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/screwdriver)) - set_type(null, user) - return - return ..() - -/obj/item/circuitboard/smartfridge/proc/set_type(typepath, mob/user) - var/new_name = "" - if(!typepath) - new_name = input("Circuit Setting", "What would you change the board setting to?") in fridge_names_paths - typepath = fridge_names_paths[new_name] - else - for(var/name in fridge_names_paths) - if(fridge_names_paths[name] == typepath) - new_name = name - break - build_path = typepath - name = new_name - if(findtextEx(new_name, "\improper")) - new_name = replacetext(new_name, "\improper", "") - if(user) - to_chat(user, "You set the board to [new_name].") - -/obj/item/circuitboard/monkey_recycler - name = "circuit board (Monkey Recycler)" - build_path = /obj/machinery/monkey_recycler - board_type = "machine" - origin_tech = "programming=1;biotech=2" - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1) - -/obj/item/circuitboard/holopad - name = "circuit board (AI Holopad)" - build_path = /obj/machinery/hologram/holopad - board_type = "machine" - origin_tech = "programming=1" - req_components = list( - /obj/item/stock_parts/capacitor = 1) - -/obj/item/circuitboard/chem_dispenser - name = "circuit board (Chem Dispenser)" - build_path = /obj/machinery/chem_dispenser - board_type = "machine" - origin_tech = "materials=4;programming=4;plasmatech=4;biotech=3" - frame_desc = "Requires 2 Matter Bins, 1 Capacitor, 1 Manipulator, 1 Console Screen, and 1 Power Cell." - req_components = list( /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/cell = 1) - -/obj/item/circuitboard/chem_master - name = "circuit board (ChemMaster 3000)" - build_path = /obj/machinery/chem_master - board_type = "machine" - origin_tech = "materials=3;programming=2;biotech=3" - req_components = list( - /obj/item/reagent_containers/glass/beaker = 2, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/chem_master/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - var/new_name = "ChemMaster" - var/new_path = /obj/machinery/chem_master - - if(build_path == /obj/machinery/chem_master) - new_name = "CondiMaster" - new_path = /obj/machinery/chem_master/condimaster - - build_path = new_path - name = "circuit board ([new_name] 3000)" - to_chat(user, "You change the circuit board setting to \"[new_name]\".") - -/obj/item/circuitboard/chem_master/condi_master - name = "circuit board (CondiMaster 3000)" - build_path = /obj/machinery/chem_master/condimaster - -/obj/item/circuitboard/chem_heater - name = "circuit board (Chemical Heater)" - build_path = /obj/machinery/chem_heater - board_type = "machine" - origin_tech = "programming=2;engineering=2;biotech=2" - frame_desc = "Requires 1 Micro Laser and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/reagentgrinder - name = "circuit board (All-In-One Grinder)" - build_path = /obj/machinery/reagentgrinder/empty - board_type = "machine" - origin_tech = "materials=2;engineering=2;biotech=2" - frame_desc = "Requires 2 Manipulators and 1 Matter Bin." - req_components = list( - /obj/item/stock_parts/manipulator = 2, - /obj/item/stock_parts/matter_bin = 1) - -//Almost the same recipe as destructive analyzer to give people choices. -/obj/item/circuitboard/experimentor - name = "circuit board (E.X.P.E.R.I-MENTOR)" - build_path = /obj/machinery/r_n_d/experimentor - board_type = "machine" - origin_tech = "magnets=1;engineering=1;programming=1;biotech=1;bluespace=2" - req_components = list( - /obj/item/stock_parts/scanning_module = 1, - /obj/item/stock_parts/manipulator = 2, - /obj/item/stock_parts/micro_laser = 2) - -/obj/item/circuitboard/destructive_analyzer - name = "Circuit board (Destructive Analyzer)" - build_path = /obj/machinery/r_n_d/destructive_analyzer - board_type = "machine" - origin_tech = "magnets=2;engineering=2;programming=2" - frame_desc = "Requires 1 Scanning Module, 1 Manipulator, and 1 Micro-Laser." - req_components = list( - /obj/item/stock_parts/scanning_module = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/micro_laser = 1) - -/obj/item/circuitboard/autolathe - name = "Circuit board (Autolathe)" - build_path = /obj/machinery/autolathe - board_type = "machine" - origin_tech = "engineering=2;programming=2" - frame_desc = "Requires 3 Matter Bins, 1 Manipulator, and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/matter_bin = 3, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/protolathe - name = "Circuit board (Protolathe)" - build_path = /obj/machinery/r_n_d/protolathe - board_type = "machine" - origin_tech = "engineering=2;programming=2" - frame_desc = "Requires 2 Matter Bins, 2 Manipulators, and 2 Beakers." - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator = 2, - /obj/item/reagent_containers/glass/beaker = 2) - -/obj/item/circuitboard/chem_dispenser/soda - name = "Circuit board (Soda Machine)" - build_path = /obj/machinery/chem_dispenser/soda - -/obj/item/circuitboard/chem_dispenser/beer - name = "Circuit board (Beer Machine)" - build_path = /obj/machinery/chem_dispenser/beer - -/obj/item/circuitboard/circuit_imprinter - name = "Circuit board (Circuit Imprinter)" - build_path = /obj/machinery/r_n_d/circuit_imprinter - board_type = "machine" - origin_tech = "engineering=2;programming=2" - frame_desc = "Requires 1 Matter Bin, 1 Manipulator, and 2 Beakers." - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/reagent_containers/glass/beaker = 2) - -/obj/item/circuitboard/pacman - name = "Circuit Board (PACMAN-type Generator)" - build_path = /obj/machinery/power/port_gen/pacman - board_type = "machine" - origin_tech = "programming=2;powerstorage=3;plasmatech=3;engineering=3" - frame_desc = "Requires 1 Matter Bin, 1 Micro-Laser, 2 Pieces of Cable, and 1 Capacitor." - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/capacitor = 1) - -/obj/item/circuitboard/pacman/super - name = "Circuit Board (SUPERPACMAN-type Generator)" - build_path = /obj/machinery/power/port_gen/pacman/super - origin_tech = "programming=3;powerstorage=4;engineering=4" - -/obj/item/circuitboard/pacman/mrs - name = "Circuit Board (MRSPACMAN-type Generator)" - build_path = /obj/machinery/power/port_gen/pacman/mrs - origin_tech = "programming=3;powerstorage=4;engineering=4;plasmatech=4" - -/obj/item/circuitboard/rdserver - name = "Circuit Board (R&D Server)" - build_path = /obj/machinery/r_n_d/server - board_type = "machine" - origin_tech = "programming=3" - frame_desc = "Requires 2 pieces of cable, and 1 Scanning Module." - req_components = list( - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/scanning_module = 1) - -/obj/item/circuitboard/mechfab - name = "Circuit board (Exosuit Fabricator)" - build_path = /obj/machinery/mecha_part_fabricator - board_type = "machine" - origin_tech = "programming=2;engineering=2" - frame_desc = "Requires 2 Matter Bins, 1 Manipulator, 1 Micro-Laser and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/podfab - name = "Circuit board (Spacepod Fabricator)" - build_path = /obj/machinery/mecha_part_fabricator/spacepod - board_type = "machine" - origin_tech = "programming=2;engineering=2" - frame_desc = "Requires 2 Matter Bins, 1 Manipulators, 1 Micro-Lasers, and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/matter_bin = 2, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/glass = 1) - - -/obj/item/circuitboard/clonepod - name = "Circuit board (Clone Pod)" - build_path = /obj/machinery/clonepod - board_type = "machine" - origin_tech = "programming=2;biotech=2" - frame_desc = "Requires 2 Manipulator, 2 Scanning Module, 2 pieces of cable and 1 Console Screen." - req_components = list( - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/scanning_module = 2, - /obj/item/stock_parts/manipulator = 2, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/clonescanner - name = "Circuit board (Cloning Scanner)" - build_path = /obj/machinery/dna_scannernew - board_type = "machine" - origin_tech = "programming=2;biotech=2" - frame_desc = "Requires 1 Scanning Module, 1 Manipulator, 1 Micro-Laser, 2 pieces of cable and 1 Console Screen." - req_components = list( - /obj/item/stock_parts/scanning_module = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stack/cable_coil = 2,) - -/obj/item/circuitboard/mech_recharger - name = "circuit board (Mech Bay Recharger)" - build_path = /obj/machinery/mech_bay_recharge_port - board_type = "machine" - origin_tech = "programming=3;powerstorage=3;engineering=3" - frame_desc = "Requires 1 piece of cable and 5 Capacitors." - req_components = list( - /obj/item/stack/cable_coil = 1, - /obj/item/stock_parts/capacitor = 5) - -/obj/item/circuitboard/teleporter_hub - name = "circuit board (Teleporter Hub)" - build_path = /obj/machinery/teleport/hub - board_type = "machine" - origin_tech = "programming=3;engineering=4;bluespace=4;materials=4" - frame_desc = "Requires 3 Bluespace Crystals and 1 Matter Bin." - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 3, - /obj/item/stock_parts/matter_bin = 1) - -/obj/item/circuitboard/teleporter_station - name = "circuit board (Teleporter Station)" - build_path = /obj/machinery/teleport/station - board_type = "machine" - origin_tech = "programming=4;engineering=4;bluespace=4;plasmatech=3" - frame_desc = "Requires 2 Bluespace Crystals, 2 Capacitors and 1 Console Screen." - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 2, - /obj/item/stock_parts/capacitor = 2, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/teleporter_perma - name = "circuit board (Permanent Teleporter)" - build_path = /obj/machinery/teleport/perma - board_type = "machine" - origin_tech = "programming=3;engineering=4;bluespace=4;materials=4" - frame_desc = "Requires 3 Bluespace Crystals and 1 Matter Bin." - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 3, - /obj/item/stock_parts/matter_bin = 1) - var/target - -/obj/item/circuitboard/teleporter_perma/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/gps)) - var/obj/item/gps/L = I - if(L.locked_location) - target = get_turf(L.locked_location) - to_chat(user, "You upload the data from [L]") - return - return ..() - -/obj/item/circuitboard/telesci_pad - name = "Circuit board (Telepad)" - build_path = /obj/machinery/telepad - board_type = "machine" - origin_tech = "programming=4;engineering=3;plasmatech=4;bluespace=4" - frame_desc = "Requires 2 Bluespace Crystals, 1 Capacitor, 1 piece of cable and 1 Console Screen." - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 2, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stack/cable_coil = 1, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/quantumpad - name = "circuit board (Quantum Pad)" - build_path = /obj/machinery/quantumpad - board_type = "machine" - origin_tech = "programming=3;engineering=3;plasmatech=3;bluespace=4" - frame_desc = "Requires 1 Bluespace Crystal, 1 Capacitor, 1 piece of cable and 1 Manipulator." - req_components = list( - /obj/item/stack/ore/bluespace_crystal = 1, - /obj/item/stock_parts/capacitor = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 1) - -/obj/item/circuitboard/sleeper - name = "circuit board (Sleeper)" - build_path = /obj/machinery/sleeper - board_type = "machine" - origin_tech = "programming=3;biotech=2;engineering=3" - frame_desc = "Requires 1 Matter Bin, 1 Manipulator, 1 piece of cable and 2 Console Screens." - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 1, - /obj/item/stack/sheet/glass = 2) - -/obj/item/circuitboard/sleeper/syndicate - name = "circuit board (Sleeper Syndicate)" - build_path = /obj/machinery/sleeper/syndie - -/obj/item/circuitboard/sleeper/survival - name = "circuit board (Sleeper Survival Pod)" - build_path = /obj/machinery/sleeper/survival_pod - - -/obj/item/circuitboard/bodyscanner - name = "circuit board (Body Scanner)" - build_path = /obj/machinery/bodyscanner - board_type = "machine" - origin_tech = "programming=3;biotech=2;engineering=3" - frame_desc = "Requires 1 Scanning Module, 2 pieces of cable and 2 Console Screens." - req_components = list( - /obj/item/stock_parts/scanning_module = 1, - /obj/item/stack/cable_coil = 2, - /obj/item/stack/sheet/glass = 2) - -/obj/item/circuitboard/cryo_tube - name = "circuit board (Cryotube)" - build_path = /obj/machinery/atmospherics/unary/cryo_cell - board_type = "machine" - origin_tech = "programming=4;biotech=3;engineering=4;plasmatech=3" - frame_desc = "Requires 1 Matter Bin, 1 piece of cable and 4 Console Screens." - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stack/cable_coil = 1, - /obj/item/stack/sheet/glass = 4) - -/obj/item/circuitboard/cyborgrecharger - name = "circuit board (Cyborg Recharger)" - build_path = /obj/machinery/recharge_station - board_type = "machine" - origin_tech = "powerstorage=3;engineering=3" - frame_desc = "Requires 2 Capacitors, 1 Power Cell and 1 Manipulator." - req_components = list( - /obj/item/stock_parts/capacitor = 2, - /obj/item/stock_parts/cell = 1, - /obj/item/stock_parts/manipulator = 1) - -// Telecomms circuit boards: -/obj/item/circuitboard/telecomms/receiver - name = "Circuit Board (Subspace Receiver)" - build_path = /obj/machinery/telecomms/receiver - board_type = "machine" - origin_tech = "programming=2;engineering=2;bluespace=1" - frame_desc = "Requires 1 Subspace Ansible, 1 Hyperwave Filter, 2 Manipulators, and 1 Micro-Laser." - req_components = list( - /obj/item/stock_parts/subspace/ansible = 1, - /obj/item/stock_parts/subspace/filter = 1, - /obj/item/stock_parts/manipulator = 2, - /obj/item/stock_parts/micro_laser = 1) - -/obj/item/circuitboard/telecomms/hub - name = "Circuit Board (Hub Mainframe)" - build_path = /obj/machinery/telecomms/hub - board_type = "machine" - origin_tech = "programming=2;engineering=2" - frame_desc = "Requires 2 Manipulators, 2 Cable Coil and 2 Hyperwave Filter." - req_components = list( - /obj/item/stock_parts/manipulator = 2, - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/subspace/filter = 2) - -/obj/item/circuitboard/telecomms/relay - name = "Circuit Board (Relay Mainframe)" - build_path = /obj/machinery/telecomms/relay - board_type = "machine" - origin_tech = "programming=2;engineering=2;bluespace=2" - frame_desc = "Requires 2 Manipulators, 2 Cable Coil and 2 Hyperwave Filters." - req_components = list( - /obj/item/stock_parts/manipulator = 2, - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/subspace/filter = 2) - -/obj/item/circuitboard/telecomms/bus - name = "Circuit Board (Bus Mainframe)" - build_path = /obj/machinery/telecomms/bus - board_type = "machine" - origin_tech = "programming=2;engineering=2" - frame_desc = "Requires 2 Manipulators, 1 Cable Coil and 1 Hyperwave Filter." - req_components = list( - /obj/item/stock_parts/manipulator = 2, - /obj/item/stack/cable_coil = 1, - /obj/item/stock_parts/subspace/filter = 1) - -/obj/item/circuitboard/telecomms/processor - name = "Circuit Board (Processor Unit)" - build_path = /obj/machinery/telecomms/processor - board_type = "machine" - origin_tech = "programming=2;engineering=2" - frame_desc = "Requires 3 Manipulators, 1 Hyperwave Filter, 2 Treatment Disks, 1 Wavelength Analyzer, 2 Cable Coils and 1 Subspace Amplifier." - req_components = list( - /obj/item/stock_parts/manipulator = 3, - /obj/item/stock_parts/subspace/filter = 1, - /obj/item/stock_parts/subspace/treatment = 2, - /obj/item/stock_parts/subspace/analyzer = 1, - /obj/item/stack/cable_coil = 2, - /obj/item/stock_parts/subspace/amplifier = 1) - -/obj/item/circuitboard/telecomms/server - name = "Circuit Board (Telecommunication Server)" - build_path = /obj/machinery/telecomms/server - board_type = "machine" - origin_tech = "programming=2;engineering=2" - frame_desc = "Requires 2 Manipulators, 1 Cable Coil and 1 Hyperwave Filter." - req_components = list( - /obj/item/stock_parts/manipulator = 2, - /obj/item/stack/cable_coil = 1, - /obj/item/stock_parts/subspace/filter = 1) - -/obj/item/circuitboard/telecomms/broadcaster - name = "Circuit Board (Subspace Broadcaster)" - build_path = /obj/machinery/telecomms/broadcaster - board_type = "machine" - origin_tech = "programming=2;engineering=2;bluespace=1" - frame_desc = "Requires 2 Manipulators, 1 Cable Coil, 1 Hyperwave Filter, 1 Ansible Crystal and 2 High-Powered Micro-Lasers. " - req_components = list( - /obj/item/stock_parts/manipulator = 2, - /obj/item/stack/cable_coil = 1, - /obj/item/stock_parts/subspace/filter = 1, - /obj/item/stock_parts/subspace/crystal = 1, - /obj/item/stock_parts/micro_laser/high = 2) - -/obj/item/circuitboard/ore_redemption - name = "circuit board (Ore Redemption)" - build_path = /obj/machinery/mineral/ore_redemption - board_type = "machine" - origin_tech = "programming=1;engineering=2" - req_components = list( - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/assembly/igniter = 1) - -/obj/item/circuitboard/ore_redemption/golem - name = "circuit board (Golem Ore Redemption)" - build_path = /obj/machinery/mineral/ore_redemption/golem - -/obj/item/circuitboard/mining_equipment_vendor - name = "circuit board (Mining Equipment Vendor)" - build_path = /obj/machinery/mineral/equipment_vendor - board_type = "machine" - origin_tech = "programming=1;engineering=3" - req_components = list( - /obj/item/stack/sheet/glass = 1, - /obj/item/stock_parts/matter_bin = 3) - -/obj/item/circuitboard/mining_equipment_vendor/golem - name = "circuit board (Mining Equipment Vendor)" - build_path = /obj/machinery/mineral/equipment_vendor/golem - -/obj/item/circuitboard/clawgame - name = "circuit board (Claw Game)" - build_path = /obj/machinery/arcade/claw - board_type = "machine" - origin_tech = "programming=1" - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/cable_coil = 5, - /obj/item/stack/sheet/glass = 1) - -/obj/item/circuitboard/prize_counter - name = "circuit board (Prize Counter)" - build_path = /obj/machinery/prize_counter - board_type = "machine" - origin_tech = "programming=1" - req_components = list( - /obj/item/stock_parts/matter_bin = 1, - /obj/item/stock_parts/manipulator = 1, - /obj/item/stack/sheet/glass = 1, - /obj/item/stack/cable_coil = 1) - -/obj/item/circuitboard/gameboard - name = "circuit board (Virtual Gameboard)" - build_path = /obj/machinery/gameboard - board_type = "machine" - origin_tech = "programming=1" - req_components = list( - /obj/item/stock_parts/micro_laser = 1, - /obj/item/stack/cable_coil = 3, - /obj/item/stack/sheet/glass = 1) - -//Selectable mode board, like vending machine boards -/obj/item/circuitboard/logic_gate - name = "circuit board (Logic Connector)" - build_path = /obj/machinery/logic_gate - board_type = "machine" - origin_tech = "programming=1" //This stuff is pretty much the absolute basis of programming, so it's mostly useless for research - req_components = list(/obj/item/stack/cable_coil = 1) - - var/list/names_paths = list( - "NOT Gate" = /obj/machinery/logic_gate/not, - "OR Gate" = /obj/machinery/logic_gate/or, - "AND Gate" = /obj/machinery/logic_gate/and, - "NAND Gate" = /obj/machinery/logic_gate/nand, - "NOR Gate" = /obj/machinery/logic_gate/nor, - "XOR Gate" = /obj/machinery/logic_gate/xor, - "XNOR Gate" = /obj/machinery/logic_gate/xnor, - "STATUS Gate" = /obj/machinery/logic_gate/status, - "CONVERT Gate" = /obj/machinery/logic_gate/convert - ) - -/obj/item/circuitboard/logic_gate/New() - ..() - if(build_path == /obj/machinery/logic_gate) //If we spawn the base type board (determined by the base type machine as the build path), become a random gate board - var/new_path = names_paths[pick(names_paths)] - set_type(new_path) - -/obj/item/circuitboard/logic_gate/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/screwdriver)) - set_type(null, user) - return - return ..() - -/obj/item/circuitboard/logic_gate/proc/set_type(typepath, mob/user) - var/new_name = "Logic Base" - if(!typepath) - new_name = input("Circuit Setting", "What would you change the board setting to?") in names_paths - typepath = names_paths[new_name] - else - for(var/name in names_paths) - if(names_paths[name] == typepath) - new_name = name - break - build_path = typepath - name = "circuit board ([new_name])" - if(user) - to_chat(user, "You set the board to [new_name].") +/obj/machinery/constructable_frame //Made into a seperate type to make future revisions easier. + name = "machine frame" + icon = 'icons/obj/stock_parts.dmi' + icon_state = "box_0" + density = 1 + anchored = 1 + use_power = NO_POWER_USE + max_integrity = 250 + var/obj/item/circuitboard/circuit = null + var/list/components = null + var/list/req_components = null + var/list/req_component_names = null // user-friendly names of components + var/state = 1 + + // For pods + var/list/connected_parts = list() + var/pattern_idx=0 + +/obj/machinery/constructable_frame/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/sheet/metal(loc, 5) + if(state >= 2) + var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil(loc) + A.amount = 5 + if(circuit) + circuit.forceMove(loc) + circuit = null + return ..() + +/obj/machinery/constructable_frame/obj_break(damage_flag) + deconstruct() + +// unfortunately, we have to instance the objects really quickly to get the names +// fortunately, this is only called once when the board is added and the items are immediately GC'd +// and none of the parts do much in their constructors +/obj/machinery/constructable_frame/proc/update_namelist() + if(!req_components) + return + + req_component_names = new() + for(var/tname in req_components) + var/path = tname + var/obj/O = new path() + req_component_names[tname] = O.name + +/obj/machinery/constructable_frame/proc/get_req_components_amt() + var/amt = 0 + for(var/path in req_components) + amt += req_components[path] + return amt + +// update description of required components remaining +/obj/machinery/constructable_frame/proc/update_req_desc() + if(!req_components || !req_component_names) + return + + var/hasContent = 0 + desc = "Requires" + for(var/i = 1 to req_components.len) + var/tname = req_components[i] + var/amt = req_components[tname] + if(amt == 0) + continue + var/use_and = i == req_components.len + desc += "[(hasContent ? (use_and ? ", and" : ",") : "")] [amt] [amt == 1 ? req_component_names[tname] : "[req_component_names[tname]]\s"]" + hasContent = 1 + + if(!hasContent) + desc = "Does not require any more components." + else + desc += "." + +/obj/machinery/constructable_frame/machine_frame/attackby(obj/item/P, mob/user, params) + switch(state) + if(1) + if(istype(P, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = P + if(C.amount >= 5) + playsound(src.loc, C.usesound, 50, 1) + to_chat(user, "You start to add cables to the frame.") + if(do_after(user, 20 * C.toolspeed, target = src)) + if(state == 1 && C.amount >= 5 && C.use(5)) + to_chat(user, "You add cables to the frame.") + state = 2 + icon_state = "box_1" + else + to_chat(user, "At some point during construction you lost some cable. Make sure you have five lengths before trying again.") + return + else + to_chat(user, "You need five lengths of cable to wire the frame.") + return + + if(istype(P, /obj/item/wrench)) + playsound(src.loc, P.usesound, 75, 1) + to_chat(user, "You dismantle the frame.") + deconstruct(TRUE) + return + if(2) + if(istype(P, /obj/item/circuitboard)) + var/obj/item/circuitboard/B = P + if(B.board_type == "machine") + playsound(src.loc, B.usesound, 50, 1) + to_chat(user, "You add the circuit board to the frame.") + circuit = P + user.drop_item() + P.loc = src + icon_state = "box_2" + state = 3 + components = list() + req_components = circuit.req_components.Copy() + update_namelist() + update_req_desc() + else + to_chat(user, "This frame does not accept circuit boards of this type!") + return + if(istype(P, /obj/item/wirecutters)) + playsound(src.loc, P.usesound, 50, 1) + to_chat(user, "You remove the cables.") + state = 1 + icon_state = "box_0" + var/obj/item/stack/cable_coil/A = new /obj/item/stack/cable_coil(src.loc,5) + A.amount = 5 + return + if(3) + if(istype(P, /obj/item/crowbar)) + playsound(src.loc, P.usesound, 50, 1) + state = 2 + circuit.loc = src.loc + circuit = null + if(components.len == 0) + to_chat(user, "You remove the circuit board.") + else + to_chat(user, "You remove the circuit board and other components.") + for(var/obj/item/I in components) + I.loc = src.loc + desc = initial(desc) + req_components = null + components = null + icon_state = "box_1" + return + + if(istype(P, /obj/item/screwdriver)) + var/component_check = 1 + for(var/R in req_components) + if(req_components[R] > 0) + component_check = 0 + break + if(component_check) + playsound(src.loc, P.usesound, 50, 1) + var/obj/machinery/new_machine = new src.circuit.build_path(src.loc) + new_machine.on_construction() + for(var/obj/O in new_machine.component_parts) + qdel(O) + new_machine.component_parts = list() + for(var/obj/O in src) + O.loc = null + new_machine.component_parts += O + circuit.loc = null + new_machine.RefreshParts() + qdel(src) + return + + if(istype(P, /obj/item/storage/part_replacer) && P.contents.len && get_req_components_amt()) + var/obj/item/storage/part_replacer/replacer = P + var/list/added_components = list() + var/list/part_list = list() + + //Assemble a list of current parts, then sort them by their rating! + for(var/obj/item/stock_parts/co in replacer) + part_list += co + + for(var/path in req_components) + while(req_components[path] > 0 && (locate(path) in part_list)) + var/obj/item/part = (locate(path) in part_list) + added_components[part] = path + replacer.remove_from_storage(part, src) + req_components[path]-- + part_list -= part + + for(var/obj/item/stock_parts/part in added_components) + components += part + to_chat(user, "[part.name] applied.") + replacer.play_rped_sound() + + update_req_desc() + return + + if(istype(P, /obj/item)) + var/success + for(var/I in req_components) + if(istype(P, I) && (req_components[I] > 0) && (!(P.flags & NODROP) || istype(P, /obj/item/stack))) + success=1 + playsound(src.loc, P.usesound, 50, 1) + if(istype(P, /obj/item/stack)) + var/obj/item/stack/S = P + var/camt = min(S.amount, req_components[I]) + var/obj/item/stack/NS = new P.type(src) + NS.amount = camt + NS.update_icon() + S.use(camt) + components += NS + req_components[I] -= camt + update_req_desc() + break + user.drop_item() + P.forceMove(src) + components += P + req_components[I]-- + update_req_desc() + return 1 + if(!success) + to_chat(user, "You cannot add that to the machine!") + return 0 + return + if(user.a_intent == INTENT_HARM) + return ..() + + +//Machine Frame Circuit Boards +/*Common Parts: Parts List: Ignitor, Timer, Infra-red laser, Infra-red sensor, t_scanner, Capacitor, Valve, sensor unit, +micro-manipulator, console screen, beaker, Microlaser, matter bin, power cells. +Note: Once everything is added to the public areas, will add MAT_METAL and MAT_GLASS to circuit boards since autolathe won't be able +to destroy them and players will be able to make replacements. +*/ +/obj/item/circuitboard/vendor + name = "circuit board (Booze-O-Mat Vendor)" + board_type = "machine" + origin_tech = "programming=1" + frame_desc = "Requires 1 Resupply Canister." + build_path = /obj/machinery/vending/boozeomat + req_components = list(/obj/item/vending_refill/boozeomat = 1) + + var/static/list/vending_names_paths = list( + /obj/machinery/vending/boozeomat = "Booze-O-Mat", + /obj/machinery/vending/coffee = "Solar's Best Hot Drinks", + /obj/machinery/vending/snack = "Getmore Chocolate Corp", + /obj/machinery/vending/chinese = "Mr. Chang", + /obj/machinery/vending/cola = "Robust Softdrinks", + /obj/machinery/vending/cigarette = "ShadyCigs Deluxe", + /obj/machinery/vending/hatdispenser = "Hatlord 9000", + /obj/machinery/vending/suitdispenser = "Suitlord 9000", + /obj/machinery/vending/shoedispenser = "Shoelord 9000", + /obj/machinery/vending/clothing = "ClothesMate", + /obj/machinery/vending/medical = "NanoMed Plus", + /obj/machinery/vending/wallmed = "NanoMed", + /obj/machinery/vending/assist = "Vendomat", + /obj/machinery/vending/engivend = "Engi-Vend", + /obj/machinery/vending/hydronutrients = "NutriMax", + /obj/machinery/vending/hydroseeds = "MegaSeed Servitor", + /obj/machinery/vending/sustenance = "Sustenance Vendor", + /obj/machinery/vending/dinnerware = "Plasteel Chef's Dinnerware Vendor", + /obj/machinery/vending/cart = "PTech", + /obj/machinery/vending/robotics = "Robotech Deluxe", + /obj/machinery/vending/engineering = "Robco Tool Maker", + /obj/machinery/vending/sovietsoda = "BODA", + /obj/machinery/vending/security = "SecTech", + /obj/machinery/vending/modularpc = "Deluxe Silicate Selections", + /obj/machinery/vending/crittercare = "CritterCare") + +/obj/item/circuitboard/vendor/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + var/static/list/display_vending_names_paths + if(!display_vending_names_paths) + display_vending_names_paths = list() + for(var/path in vending_names_paths) + display_vending_names_paths[vending_names_paths[path]] = path + var/choice = input(user, "Choose a new brand","Select an Item") as null|anything in display_vending_names_paths + if(loc != user) + to_chat(user, "You need to keep [src] in your hands while doing that!") + return + set_type(display_vending_names_paths[choice]) + +/obj/item/circuitboard/vendor/proc/set_type(obj/machinery/vending/typepath) + build_path = typepath + name = "circuit board ([vending_names_paths[build_path]] Vendor)" + req_components = list(initial(typepath.refill_canister) = 1) + +/obj/item/circuitboard/smes + name = "circuit board (SMES)" + build_path = /obj/machinery/power/smes + board_type = "machine" + origin_tech = "programming=3;powerstorage=3;engineering=3" + frame_desc = "Requires 5 pieces of cable, 5 Power Cells and 1 Capacitor." + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/cell = 5, + /obj/item/stock_parts/capacitor = 1) + +/obj/item/circuitboard/emitter + name = "circuit board (Emitter)" + build_path = /obj/machinery/power/emitter + board_type = "machine" + origin_tech = "programming=3;powerstorage=4;engineering=4" + req_components = list( + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/manipulator = 1) + +/obj/item/circuitboard/power_compressor + name = "circuit board (Power Compressor)" + build_path = /obj/machinery/power/compressor + board_type = "machine" + origin_tech = "programming=4;powerstorage=4;engineering=4" + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/manipulator = 6) + +/obj/item/circuitboard/power_turbine + name = "circuit board (Power Turbine)" + build_path = /obj/machinery/power/turbine + board_type = "machine" + origin_tech = "programming=4;powerstorage=4;engineering=4" + req_components = list( + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/capacitor = 6) + +/obj/item/circuitboard/thermomachine + name = "circuit board (Freezer)" + desc = "Use screwdriver to switch between heating and cooling modes." + build_path = /obj/machinery/atmospherics/unary/cold_sink/freezer + board_type = "machine" + origin_tech = "programming=3;plasmatech=3" + frame_desc = "Requires 2 Matter Bins, 2 Micro Lasers, 1 piece of cable and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/thermomachine/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/screwdriver)) + if(build_path == /obj/machinery/atmospherics/unary/cold_sink/freezer) + build_path = /obj/machinery/atmospherics/unary/heat_reservoir/heater + name = "circuit board (Heater)" + to_chat(user, "You set the board to heating.") + else + build_path = /obj/machinery/atmospherics/unary/cold_sink/freezer + name = "circuit board (Freezer)" + to_chat(user, "You set the board to cooling.") + return + return ..() + +/obj/item/circuitboard/recharger + name = "circuit board (Recharger)" + build_path = /obj/machinery/recharger + board_type = "machine" + origin_tech = "powerstorage=3;materials=2" + frame_desc = "Requires 1 Capacitor" + req_components = list(/obj/item/stock_parts/capacitor = 1) + +/obj/item/circuitboard/snow_machine + name = "circuit board (snow machine)" + build_path = /obj/machinery/snow_machine + board_type = "machine" + origin_tech = "programming=2;materials=2" + frame_desc = "Requires 1 Matter Bin and 1 Micro Laser." + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/micro_laser = 1) + +/obj/item/circuitboard/biogenerator + name = "circuit board (Biogenerator)" + build_path = /obj/machinery/biogenerator + board_type = "machine" + origin_tech = "programming=2;biotech=3;materials=3" + frame_desc = "Requires 1 Matter Bin, 1 Manipulator, 1 piece of cable and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/plantgenes + name = "Plant DNA Manipulator (Machine Board)" + build_path = /obj/machinery/plantgenes + board_type = "machine" + origin_tech = "programming=3;biotech=3" + frame_desc = "Requires 1 Manipulator, 1 Micro Laser, 1 Console Screen, and 1 Scanning Module." + req_components = list( + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/scanning_module = 1) + +/obj/item/circuitboard/plantgenes/vault + +/obj/item/circuitboard/seed_extractor + name = "circuit board (Seed Extractor)" + build_path = /obj/machinery/seed_extractor + board_type = "machine" + origin_tech = "programming=1" + frame_desc = "Requires 1 Matter Bin and 1 Manipulator." + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + +/obj/item/circuitboard/hydroponics + name = "circuit board (Hydroponics Tray)" + build_path = /obj/machinery/hydroponics/constructable + board_type = "machine" + origin_tech = "programming=1;biotech=2" + frame_desc = "Requires 2 Matter Bins, 1 Manipulator, and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/microwave + name = "circuit board (Microwave)" + build_path = /obj/machinery/kitchen_machine/microwave + board_type = "machine" + origin_tech = "programming=2;magnets=2" + frame_desc = "Requires 1 Micro Laser, 2 pieces of cable and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/cable_coil = 2, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/oven + name = "circuit board (Oven)" + build_path = /obj/machinery/kitchen_machine/oven + board_type = "machine" + origin_tech = "programming=2;magnets=2" + frame_desc = "Requires 2 Micro Lasers, 5 pieces of cable and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/grill + name = "circuit board (Grill)" + build_path = /obj/machinery/kitchen_machine/grill + board_type = "machine" + origin_tech = "programming=2;magnets=2" + frame_desc = "Requires 2 Micro Lasers, 5 pieces of cable and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/candy_maker + name = "circuit board (Candy Maker)" + build_path = /obj/machinery/kitchen_machine/candy_maker + board_type = "machine" + origin_tech = "programming=2;magnets=2" + frame_desc = "Requires 1 Manipulator, 5 pieces of cable and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/deepfryer + name = "circuit board (Deep Fryer)" + build_path = /obj/machinery/cooker/deepfryer + board_type = "machine" + origin_tech = "programming=1" + frame_desc = "Requires 2 Micro Lasers and 5 pieces of cable." + req_components = list( + /obj/item/stock_parts/micro_laser = 2, + /obj/item/stack/cable_coil = 5) + +/obj/item/circuitboard/gibber + name = "circuit board (Gibber)" + build_path = /obj/machinery/gibber + board_type = "machine" + origin_tech = "programming=2;engineering=2" + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + +/obj/item/circuitboard/tesla_coil + name = "circuit board (Tesla Coil)" + build_path = /obj/machinery/power/tesla_coil + board_type = "machine" + origin_tech = "programming=3;magnets=3;powerstorage=3" + req_components = list( + /obj/item/stock_parts/capacitor = 1) + +/obj/item/circuitboard/grounding_rod + name = "circuit board (Grounding Rod)" + build_path = /obj/machinery/power/grounding_rod + board_type = "machine" + origin_tech = "programming=3;powerstorage=3;magnets=3;plasmatech=2" + req_components = list( + /obj/item/stock_parts/capacitor = 1) + +/obj/item/circuitboard/processor + name = "circuit board (Food processor)" + build_path = /obj/machinery/processor + board_type = "machine" + origin_tech = "programming=1" + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + +/obj/item/circuitboard/recycler + name = "circuit board (Recycler)" + build_path = /obj/machinery/recycler + board_type = "machine" + origin_tech = "programming=2;engineering=2" + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + +/obj/item/circuitboard/smartfridge + name = "circuit board (Smartfridge)" + build_path = /obj/machinery/smartfridge + board_type = "machine" + origin_tech = "programming=1" + req_components = list( + /obj/item/stock_parts/matter_bin = 1) + var/list/fridge_names_paths = list( + "\improper SmartFridge" = /obj/machinery/smartfridge, + "\improper MegaSeed Servitor" = /obj/machinery/smartfridge/seeds, + "\improper Refrigerated Medicine Storage" = /obj/machinery/smartfridge/medbay, + "\improper Slime Extract Storage" = /obj/machinery/smartfridge/secure/extract, + "\improper Secure Refrigerated Medicine Storage" = /obj/machinery/smartfridge/secure/medbay, + "\improper Smart Chemical Storage" = /obj/machinery/smartfridge/secure/chemistry, + "smart virus storage" = /obj/machinery/smartfridge/secure/chemistry/virology, + "\improper Drink Showcase" = /obj/machinery/smartfridge/drinks + ) + + + +/obj/item/circuitboard/smartfridge/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/screwdriver)) + set_type(null, user) + return + return ..() + +/obj/item/circuitboard/smartfridge/proc/set_type(typepath, mob/user) + var/new_name = "" + if(!typepath) + new_name = input("Circuit Setting", "What would you change the board setting to?") in fridge_names_paths + typepath = fridge_names_paths[new_name] + else + for(var/name in fridge_names_paths) + if(fridge_names_paths[name] == typepath) + new_name = name + break + build_path = typepath + name = new_name + if(findtextEx(new_name, "\improper")) + new_name = replacetext(new_name, "\improper", "") + if(user) + to_chat(user, "You set the board to [new_name].") + +/obj/item/circuitboard/monkey_recycler + name = "circuit board (Monkey Recycler)" + build_path = /obj/machinery/monkey_recycler + board_type = "machine" + origin_tech = "programming=1;biotech=2" + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1) + +/obj/item/circuitboard/holopad + name = "circuit board (AI Holopad)" + build_path = /obj/machinery/hologram/holopad + board_type = "machine" + origin_tech = "programming=1" + req_components = list( + /obj/item/stock_parts/capacitor = 1) + +/obj/item/circuitboard/chem_dispenser + name = "circuit board (Chem Dispenser)" + build_path = /obj/machinery/chem_dispenser + board_type = "machine" + origin_tech = "materials=4;programming=4;plasmatech=4;biotech=3" + frame_desc = "Requires 2 Matter Bins, 1 Capacitor, 1 Manipulator, 1 Console Screen, and 1 Power Cell." + req_components = list( /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/capacitor = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/cell = 1) + +/obj/item/circuitboard/chem_master + name = "circuit board (ChemMaster 3000)" + build_path = /obj/machinery/chem_master + board_type = "machine" + origin_tech = "materials=3;programming=2;biotech=3" + req_components = list( + /obj/item/reagent_containers/glass/beaker = 2, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/chem_master/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + var/new_name = "ChemMaster" + var/new_path = /obj/machinery/chem_master + + if(build_path == /obj/machinery/chem_master) + new_name = "CondiMaster" + new_path = /obj/machinery/chem_master/condimaster + + build_path = new_path + name = "circuit board ([new_name] 3000)" + to_chat(user, "You change the circuit board setting to \"[new_name]\".") + +/obj/item/circuitboard/chem_master/condi_master + name = "circuit board (CondiMaster 3000)" + build_path = /obj/machinery/chem_master/condimaster + +/obj/item/circuitboard/chem_heater + name = "circuit board (Chemical Heater)" + build_path = /obj/machinery/chem_heater + board_type = "machine" + origin_tech = "programming=2;engineering=2;biotech=2" + frame_desc = "Requires 1 Micro Laser and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/reagentgrinder + name = "circuit board (All-In-One Grinder)" + build_path = /obj/machinery/reagentgrinder/empty + board_type = "machine" + origin_tech = "materials=2;engineering=2;biotech=2" + frame_desc = "Requires 2 Manipulators and 1 Matter Bin." + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stock_parts/matter_bin = 1) + +//Almost the same recipe as destructive analyzer to give people choices. +/obj/item/circuitboard/experimentor + name = "circuit board (E.X.P.E.R.I-MENTOR)" + build_path = /obj/machinery/r_n_d/experimentor + board_type = "machine" + origin_tech = "magnets=1;engineering=1;programming=1;biotech=1;bluespace=2" + req_components = list( + /obj/item/stock_parts/scanning_module = 1, + /obj/item/stock_parts/manipulator = 2, + /obj/item/stock_parts/micro_laser = 2) + +/obj/item/circuitboard/destructive_analyzer + name = "Circuit board (Destructive Analyzer)" + build_path = /obj/machinery/r_n_d/destructive_analyzer + board_type = "machine" + origin_tech = "magnets=2;engineering=2;programming=2" + frame_desc = "Requires 1 Scanning Module, 1 Manipulator, and 1 Micro-Laser." + req_components = list( + /obj/item/stock_parts/scanning_module = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/micro_laser = 1) + +/obj/item/circuitboard/autolathe + name = "Circuit board (Autolathe)" + build_path = /obj/machinery/autolathe + board_type = "machine" + origin_tech = "engineering=2;programming=2" + frame_desc = "Requires 3 Matter Bins, 1 Manipulator, and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/matter_bin = 3, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/protolathe + name = "Circuit board (Protolathe)" + build_path = /obj/machinery/r_n_d/protolathe + board_type = "machine" + origin_tech = "engineering=2;programming=2" + frame_desc = "Requires 2 Matter Bins, 2 Manipulators, and 2 Beakers." + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 2, + /obj/item/reagent_containers/glass/beaker = 2) + +/obj/item/circuitboard/chem_dispenser/soda + name = "Circuit board (Soda Machine)" + build_path = /obj/machinery/chem_dispenser/soda + +/obj/item/circuitboard/chem_dispenser/beer + name = "Circuit board (Beer Machine)" + build_path = /obj/machinery/chem_dispenser/beer + +/obj/item/circuitboard/circuit_imprinter + name = "Circuit board (Circuit Imprinter)" + build_path = /obj/machinery/r_n_d/circuit_imprinter + board_type = "machine" + origin_tech = "engineering=2;programming=2" + frame_desc = "Requires 1 Matter Bin, 1 Manipulator, and 2 Beakers." + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/reagent_containers/glass/beaker = 2) + +/obj/item/circuitboard/pacman + name = "Circuit Board (PACMAN-type Generator)" + build_path = /obj/machinery/power/port_gen/pacman + board_type = "machine" + origin_tech = "programming=2;powerstorage=3;plasmatech=3;engineering=3" + frame_desc = "Requires 1 Matter Bin, 1 Micro-Laser, 2 Pieces of Cable, and 1 Capacitor." + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/capacitor = 1) + +/obj/item/circuitboard/pacman/super + name = "Circuit Board (SUPERPACMAN-type Generator)" + build_path = /obj/machinery/power/port_gen/pacman/super + origin_tech = "programming=3;powerstorage=4;engineering=4" + +/obj/item/circuitboard/pacman/mrs + name = "Circuit Board (MRSPACMAN-type Generator)" + build_path = /obj/machinery/power/port_gen/pacman/mrs + origin_tech = "programming=3;powerstorage=4;engineering=4;plasmatech=4" + +/obj/item/circuitboard/rdserver + name = "Circuit Board (R&D Server)" + build_path = /obj/machinery/r_n_d/server + board_type = "machine" + origin_tech = "programming=3" + frame_desc = "Requires 2 pieces of cable, and 1 Scanning Module." + req_components = list( + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/scanning_module = 1) + +/obj/item/circuitboard/mechfab + name = "Circuit board (Exosuit Fabricator)" + build_path = /obj/machinery/mecha_part_fabricator + board_type = "machine" + origin_tech = "programming=2;engineering=2" + frame_desc = "Requires 2 Matter Bins, 1 Manipulator, 1 Micro-Laser and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/podfab + name = "Circuit board (Spacepod Fabricator)" + build_path = /obj/machinery/mecha_part_fabricator/spacepod + board_type = "machine" + origin_tech = "programming=2;engineering=2" + frame_desc = "Requires 2 Matter Bins, 1 Manipulators, 1 Micro-Lasers, and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/matter_bin = 2, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/glass = 1) + + +/obj/item/circuitboard/clonepod + name = "Circuit board (Clone Pod)" + build_path = /obj/machinery/clonepod + board_type = "machine" + origin_tech = "programming=2;biotech=2" + frame_desc = "Requires 2 Manipulator, 2 Scanning Module, 2 pieces of cable and 1 Console Screen." + req_components = list( + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/scanning_module = 2, + /obj/item/stock_parts/manipulator = 2, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/clonescanner + name = "Circuit board (Cloning Scanner)" + build_path = /obj/machinery/dna_scannernew + board_type = "machine" + origin_tech = "programming=2;biotech=2" + frame_desc = "Requires 1 Scanning Module, 1 Manipulator, 1 Micro-Laser, 2 pieces of cable and 1 Console Screen." + req_components = list( + /obj/item/stock_parts/scanning_module = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stack/cable_coil = 2,) + +/obj/item/circuitboard/mech_recharger + name = "circuit board (Mech Bay Recharger)" + build_path = /obj/machinery/mech_bay_recharge_port + board_type = "machine" + origin_tech = "programming=3;powerstorage=3;engineering=3" + frame_desc = "Requires 1 piece of cable and 5 Capacitors." + req_components = list( + /obj/item/stack/cable_coil = 1, + /obj/item/stock_parts/capacitor = 5) + +/obj/item/circuitboard/teleporter_hub + name = "circuit board (Teleporter Hub)" + build_path = /obj/machinery/teleport/hub + board_type = "machine" + origin_tech = "programming=3;engineering=4;bluespace=4;materials=4" + frame_desc = "Requires 3 Bluespace Crystals and 1 Matter Bin." + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 3, + /obj/item/stock_parts/matter_bin = 1) + +/obj/item/circuitboard/teleporter_station + name = "circuit board (Teleporter Station)" + build_path = /obj/machinery/teleport/station + board_type = "machine" + origin_tech = "programming=4;engineering=4;bluespace=4;plasmatech=3" + frame_desc = "Requires 2 Bluespace Crystals, 2 Capacitors and 1 Console Screen." + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 2, + /obj/item/stock_parts/capacitor = 2, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/teleporter_perma + name = "circuit board (Permanent Teleporter)" + build_path = /obj/machinery/teleport/perma + board_type = "machine" + origin_tech = "programming=3;engineering=4;bluespace=4;materials=4" + frame_desc = "Requires 3 Bluespace Crystals and 1 Matter Bin." + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 3, + /obj/item/stock_parts/matter_bin = 1) + var/target + +/obj/item/circuitboard/teleporter_perma/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/gps)) + var/obj/item/gps/L = I + if(L.locked_location) + target = get_turf(L.locked_location) + to_chat(user, "You upload the data from [L]") + return + return ..() + +/obj/item/circuitboard/telesci_pad + name = "Circuit board (Telepad)" + build_path = /obj/machinery/telepad + board_type = "machine" + origin_tech = "programming=4;engineering=3;plasmatech=4;bluespace=4" + frame_desc = "Requires 2 Bluespace Crystals, 1 Capacitor, 1 piece of cable and 1 Console Screen." + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 2, + /obj/item/stock_parts/capacitor = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/quantumpad + name = "circuit board (Quantum Pad)" + build_path = /obj/machinery/quantumpad + board_type = "machine" + origin_tech = "programming=3;engineering=3;plasmatech=3;bluespace=4" + frame_desc = "Requires 1 Bluespace Crystal, 1 Capacitor, 1 piece of cable and 1 Manipulator." + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 1, + /obj/item/stock_parts/capacitor = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/cable_coil = 1) + +/obj/item/circuitboard/sleeper + name = "circuit board (Sleeper)" + build_path = /obj/machinery/sleeper + board_type = "machine" + origin_tech = "programming=3;biotech=2;engineering=3" + frame_desc = "Requires 1 Matter Bin, 1 Manipulator, 1 piece of cable and 2 Console Screens." + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 2) + +/obj/item/circuitboard/sleeper/syndicate + name = "circuit board (Sleeper Syndicate)" + build_path = /obj/machinery/sleeper/syndie + +/obj/item/circuitboard/sleeper/survival + name = "circuit board (Sleeper Survival Pod)" + build_path = /obj/machinery/sleeper/survival_pod + + +/obj/item/circuitboard/bodyscanner + name = "circuit board (Body Scanner)" + build_path = /obj/machinery/bodyscanner + board_type = "machine" + origin_tech = "programming=3;biotech=2;engineering=3" + frame_desc = "Requires 1 Scanning Module, 2 pieces of cable and 2 Console Screens." + req_components = list( + /obj/item/stock_parts/scanning_module = 1, + /obj/item/stack/cable_coil = 2, + /obj/item/stack/sheet/glass = 2) + +/obj/item/circuitboard/cryo_tube + name = "circuit board (Cryotube)" + build_path = /obj/machinery/atmospherics/unary/cryo_cell + board_type = "machine" + origin_tech = "programming=4;biotech=3;engineering=4;plasmatech=3" + frame_desc = "Requires 1 Matter Bin, 1 piece of cable and 4 Console Screens." + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stack/cable_coil = 1, + /obj/item/stack/sheet/glass = 4) + +/obj/item/circuitboard/cyborgrecharger + name = "circuit board (Cyborg Recharger)" + build_path = /obj/machinery/recharge_station + board_type = "machine" + origin_tech = "powerstorage=3;engineering=3" + frame_desc = "Requires 2 Capacitors, 1 Power Cell and 1 Manipulator." + req_components = list( + /obj/item/stock_parts/capacitor = 2, + /obj/item/stock_parts/cell = 1, + /obj/item/stock_parts/manipulator = 1) + +// Telecomms circuit boards: +/obj/item/circuitboard/telecomms/receiver + name = "Circuit Board (Subspace Receiver)" + build_path = /obj/machinery/telecomms/receiver + board_type = "machine" + origin_tech = "programming=2;engineering=2;bluespace=1" + frame_desc = "Requires 1 Subspace Ansible, 1 Hyperwave Filter, 2 Manipulators, and 1 Micro-Laser." + req_components = list( + /obj/item/stock_parts/subspace/ansible = 1, + /obj/item/stock_parts/subspace/filter = 1, + /obj/item/stock_parts/manipulator = 2, + /obj/item/stock_parts/micro_laser = 1) + +/obj/item/circuitboard/telecomms/hub + name = "Circuit Board (Hub Mainframe)" + build_path = /obj/machinery/telecomms/hub + board_type = "machine" + origin_tech = "programming=2;engineering=2" + frame_desc = "Requires 2 Manipulators, 2 Cable Coil and 2 Hyperwave Filter." + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/subspace/filter = 2) + +/obj/item/circuitboard/telecomms/relay + name = "Circuit Board (Relay Mainframe)" + build_path = /obj/machinery/telecomms/relay + board_type = "machine" + origin_tech = "programming=2;engineering=2;bluespace=2" + frame_desc = "Requires 2 Manipulators, 2 Cable Coil and 2 Hyperwave Filters." + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/subspace/filter = 2) + +/obj/item/circuitboard/telecomms/bus + name = "Circuit Board (Bus Mainframe)" + build_path = /obj/machinery/telecomms/bus + board_type = "machine" + origin_tech = "programming=2;engineering=2" + frame_desc = "Requires 2 Manipulators, 1 Cable Coil and 1 Hyperwave Filter." + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stack/cable_coil = 1, + /obj/item/stock_parts/subspace/filter = 1) + +/obj/item/circuitboard/telecomms/processor + name = "Circuit Board (Processor Unit)" + build_path = /obj/machinery/telecomms/processor + board_type = "machine" + origin_tech = "programming=2;engineering=2" + frame_desc = "Requires 3 Manipulators, 1 Hyperwave Filter, 2 Treatment Disks, 1 Wavelength Analyzer, 2 Cable Coils and 1 Subspace Amplifier." + req_components = list( + /obj/item/stock_parts/manipulator = 3, + /obj/item/stock_parts/subspace/filter = 1, + /obj/item/stock_parts/subspace/treatment = 2, + /obj/item/stock_parts/subspace/analyzer = 1, + /obj/item/stack/cable_coil = 2, + /obj/item/stock_parts/subspace/amplifier = 1) + +/obj/item/circuitboard/telecomms/server + name = "Circuit Board (Telecommunication Server)" + build_path = /obj/machinery/telecomms/server + board_type = "machine" + origin_tech = "programming=2;engineering=2" + frame_desc = "Requires 2 Manipulators, 1 Cable Coil and 1 Hyperwave Filter." + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stack/cable_coil = 1, + /obj/item/stock_parts/subspace/filter = 1) + +/obj/item/circuitboard/telecomms/broadcaster + name = "Circuit Board (Subspace Broadcaster)" + build_path = /obj/machinery/telecomms/broadcaster + board_type = "machine" + origin_tech = "programming=2;engineering=2;bluespace=1" + frame_desc = "Requires 2 Manipulators, 1 Cable Coil, 1 Hyperwave Filter, 1 Ansible Crystal and 2 High-Powered Micro-Lasers. " + req_components = list( + /obj/item/stock_parts/manipulator = 2, + /obj/item/stack/cable_coil = 1, + /obj/item/stock_parts/subspace/filter = 1, + /obj/item/stock_parts/subspace/crystal = 1, + /obj/item/stock_parts/micro_laser/high = 2) + +/obj/item/circuitboard/ore_redemption + name = "circuit board (Ore Redemption)" + build_path = /obj/machinery/mineral/ore_redemption + board_type = "machine" + origin_tech = "programming=1;engineering=2" + req_components = list( + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/assembly/igniter = 1) + +/obj/item/circuitboard/ore_redemption/golem + name = "circuit board (Golem Ore Redemption)" + build_path = /obj/machinery/mineral/ore_redemption/golem + +/obj/item/circuitboard/mining_equipment_vendor + name = "circuit board (Mining Equipment Vendor)" + build_path = /obj/machinery/mineral/equipment_vendor + board_type = "machine" + origin_tech = "programming=1;engineering=3" + req_components = list( + /obj/item/stack/sheet/glass = 1, + /obj/item/stock_parts/matter_bin = 3) + +/obj/item/circuitboard/mining_equipment_vendor/golem + name = "circuit board (Mining Equipment Vendor)" + build_path = /obj/machinery/mineral/equipment_vendor/golem + +/obj/item/circuitboard/clawgame + name = "circuit board (Claw Game)" + build_path = /obj/machinery/arcade/claw + board_type = "machine" + origin_tech = "programming=1" + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/cable_coil = 5, + /obj/item/stack/sheet/glass = 1) + +/obj/item/circuitboard/prize_counter + name = "circuit board (Prize Counter)" + build_path = /obj/machinery/prize_counter + board_type = "machine" + origin_tech = "programming=1" + req_components = list( + /obj/item/stock_parts/matter_bin = 1, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stack/sheet/glass = 1, + /obj/item/stack/cable_coil = 1) + +/obj/item/circuitboard/gameboard + name = "circuit board (Virtual Gameboard)" + build_path = /obj/machinery/gameboard + board_type = "machine" + origin_tech = "programming=1" + req_components = list( + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stack/cable_coil = 3, + /obj/item/stack/sheet/glass = 1) + +//Selectable mode board, like vending machine boards +/obj/item/circuitboard/logic_gate + name = "circuit board (Logic Connector)" + build_path = /obj/machinery/logic_gate + board_type = "machine" + origin_tech = "programming=1" //This stuff is pretty much the absolute basis of programming, so it's mostly useless for research + req_components = list(/obj/item/stack/cable_coil = 1) + + var/list/names_paths = list( + "NOT Gate" = /obj/machinery/logic_gate/not, + "OR Gate" = /obj/machinery/logic_gate/or, + "AND Gate" = /obj/machinery/logic_gate/and, + "NAND Gate" = /obj/machinery/logic_gate/nand, + "NOR Gate" = /obj/machinery/logic_gate/nor, + "XOR Gate" = /obj/machinery/logic_gate/xor, + "XNOR Gate" = /obj/machinery/logic_gate/xnor, + "STATUS Gate" = /obj/machinery/logic_gate/status, + "CONVERT Gate" = /obj/machinery/logic_gate/convert + ) + +/obj/item/circuitboard/logic_gate/New() + ..() + if(build_path == /obj/machinery/logic_gate) //If we spawn the base type board (determined by the base type machine as the build path), become a random gate board + var/new_path = names_paths[pick(names_paths)] + set_type(new_path) + +/obj/item/circuitboard/logic_gate/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/screwdriver)) + set_type(null, user) + return + return ..() + +/obj/item/circuitboard/logic_gate/proc/set_type(typepath, mob/user) + var/new_name = "Logic Base" + if(!typepath) + new_name = input("Circuit Setting", "What would you change the board setting to?") in names_paths + typepath = names_paths[new_name] + else + for(var/name in names_paths) + if(names_paths[name] == typepath) + new_name = name + break + build_path = typepath + name = "circuit board ([new_name])" + if(user) + to_chat(user, "You set the board to [new_name].") diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index ded10e41a04..3ce6a8702f5 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -1,517 +1,517 @@ -/obj/machinery/atmospherics/unary/cryo_cell - name = "cryo cell" - desc = "Lowers the body temperature so certain medications may take effect." - icon = 'icons/obj/cryogenics.dmi' - icon_state = "pod0" - density = 1 - anchored = 1.0 - layer = ABOVE_WINDOW_LAYER - plane = GAME_PLANE - interact_offline = 1 - max_integrity = 350 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30) - var/on = 0 - var/temperature_archived - var/mob/living/carbon/occupant = null - var/obj/item/reagent_containers/glass/beaker = null - var/autoeject = 0 - - var/next_trans = 0 - var/current_heat_capacity = 50 - var/efficiency - - var/running_bob_animation = 0 // This is used to prevent threads from building up if update_icons is called multiple times - - light_color = LIGHT_COLOR_WHITE - power_change() - ..() - if(!(stat & (BROKEN|NOPOWER))) - set_light(2) - else - set_light(0) - -/obj/machinery/atmospherics/unary/cryo_cell/New() - ..() - initialize_directions = dir - component_parts = list() - component_parts += new /obj/item/circuitboard/cryo_tube(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() - -/obj/machinery/atmospherics/unary/cryo_cell/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/cryo_tube(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/sheet/glass(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - RefreshParts() - -/obj/machinery/atmospherics/unary/cryo_cell/on_construction() - ..(dir,dir) - -/obj/machinery/atmospherics/unary/cryo_cell/RefreshParts() - var/C - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - C += M.rating - current_heat_capacity = 50 * C - efficiency = C - -/obj/machinery/atmospherics/unary/cryo_cell/atmos_init() - ..() - if(node) return - for(var/cdir in cardinal) - node = findConnecting(cdir) - if(node) - break - -/obj/machinery/atmospherics/unary/cryo_cell/Destroy() - QDEL_NULL(beaker) - return ..() - -/obj/machinery/atmospherics/unary/cryo_cell/ex_act(severity) - if(occupant) - occupant.ex_act(severity) - if(beaker) - beaker.ex_act(severity) - ..() - -/obj/machinery/atmospherics/unary/cryo_cell/handle_atom_del(atom/A) - ..() - if(A == beaker) - beaker = null - updateUsrDialog() - if(A == occupant) - occupant = null - updateUsrDialog() - update_icon() - -/obj/machinery/atmospherics/unary/cryo_cell/on_deconstruction() - if(beaker) - beaker.forceMove(drop_location()) - beaker = null - -/obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(atom/movable/O as mob|obj, mob/living/user as mob) - if(O.loc == user) //no you can't pull things out of your ass - return - if(user.incapacitated()) //are you cuffed, dying, lying, stunned or other - return - if(get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src)) // is the mob anchored, too far away from you, or are you too far away from the source - return - if(!ismob(O)) //humans only - return - if(istype(O, /mob/living/simple_animal) || istype(O, /mob/living/silicon)) //animals and robutts dont fit - return - if(!ishuman(user) && !isrobot(user)) //No ghosts or mice putting people into the sleeper - return - if(user.loc==null) // just in case someone manages to get a closet into the blue light dimension, as unlikely as that seems - return - if(!istype(user.loc, /turf) || !istype(O.loc, /turf)) // are you in a container/closet/pod/etc? - return - if(occupant) - to_chat(user, "The cryo cell is already occupied!") - return - var/mob/living/L = O - if(!istype(L) || L.buckled) - return - if(L.abiotic()) - to_chat(user, "Subject cannot have abiotic items on.") - return - if(L.has_buckled_mobs()) //mob attached to us - to_chat(user, "[L] will not fit into [src] because [L.p_they()] [L.p_have()] a slime latched onto [L.p_their()] head.") - return - if(put_mob(L)) - if(L == user) - visible_message("[user] climbs into the cryo cell.") - else - visible_message("[user] puts [L.name] into the cryo cell.") - add_attack_logs(user, L, "put into a cryo cell at [COORD(src)].", ATKLOG_ALL) - if(user.pulling == L) - user.stop_pulling() - -/obj/machinery/atmospherics/unary/cryo_cell/process() - ..() - if(autoeject) - if(occupant) - if(!occupant.has_organic_damage() && !occupant.has_mutated_organs()) - on = 0 - go_out() - playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) - - if(air_contents) - if(occupant) - process_occupant() - - return 1 - -/obj/machinery/atmospherics/unary/cryo_cell/process_atmos() - ..() - if(!node) - return - if(!on) - return - - if(air_contents) - temperature_archived = air_contents.temperature - heat_gas_contents() - - if(abs(temperature_archived-air_contents.temperature) > 1) - parent.update = 1 - - -/obj/machinery/atmospherics/unary/cryo_cell/AllowDrop() - return FALSE - - -/obj/machinery/atmospherics/unary/cryo_cell/relaymove(mob/user as mob) - if(user.stat) - return - go_out() - return - -/obj/machinery/atmospherics/unary/cryo_cell/attack_ghost(mob/user) - return attack_hand(user) - -/obj/machinery/atmospherics/unary/cryo_cell/attack_hand(mob/user) - if(user == occupant) - return - - if(panel_open) - to_chat(usr, "Close the maintenance panel first.") - return - - ui_interact(user) - - - /** - * The ui_interact proc is used to open and update Nano UIs - * If ui_interact is not used then the UI will not update correctly - * ui_interact is currently defined for /atom/movable (which is inherited by /obj and /mob) - * - * @param user /mob The mob who is interacting with this ui - * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main") - * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui - * - * @return nothing - */ -/obj/machinery/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "cryo.tmpl", "Cryo Cell Control System", 520, 420) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/machinery/atmospherics/unary/cryo_cell/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - data["isOperating"] = on - data["hasOccupant"] = occupant ? 1 : 0 - - var/occupantData[0] - if(occupant) - occupantData["name"] = occupant.name - occupantData["stat"] = occupant.stat - occupantData["health"] = occupant.health - occupantData["maxHealth"] = occupant.maxHealth - occupantData["minHealth"] = HEALTH_THRESHOLD_DEAD - occupantData["bruteLoss"] = occupant.getBruteLoss() - occupantData["oxyLoss"] = occupant.getOxyLoss() - occupantData["toxLoss"] = occupant.getToxLoss() - occupantData["fireLoss"] = occupant.getFireLoss() - occupantData["bodyTemperature"] = occupant.bodytemperature - data["occupant"] = occupantData; - - data["cellTemperature"] = round(air_contents.temperature) - data["cellTemperatureStatus"] = "good" - if(air_contents.temperature > T0C) // if greater than 273.15 kelvin (0 celcius) - data["cellTemperatureStatus"] = "bad" - else if(air_contents.temperature > TCRYO) - data["cellTemperatureStatus"] = "average" - - data["isBeakerLoaded"] = beaker ? 1 : 0 - data["beakerLabel"] = null - data["beakerVolume"] = 0 - if(beaker) - data["beakerLabel"] = beaker.label_text ? beaker.label_text : null - if(beaker.reagents && beaker.reagents.reagent_list.len) - for(var/datum/reagent/R in beaker.reagents.reagent_list) - data["beakerVolume"] += R.volume - - data["autoeject"] = autoeject - return data - -/obj/machinery/atmospherics/unary/cryo_cell/Topic(href, href_list) - if(usr == occupant) - return 0 // don't update UIs attached to this object - - if(..()) - return 0 // don't update UIs attached to this object - - if(href_list["switchOn"]) - on = 1 - update_icon() - - if(href_list["switchOff"]) - on = 0 - update_icon() - - if(href_list["autoejectOn"]) - autoeject = 1 - - if(href_list["autoejectOff"]) - autoeject = 0 - - if(href_list["ejectBeaker"]) - if(beaker) - beaker.forceMove(get_step(loc, SOUTH)) - beaker = null - - if(href_list["ejectOccupant"]) - if(!occupant || isslime(usr) || ispAI(usr)) - return 0 // don't update UIs attached to this object - add_attack_logs(usr, occupant, "ejected from cryo cell at [COORD(src)]", ATKLOG_ALL) - go_out() - - add_fingerprint(usr) - return 1 // update UIs attached to this object - -/obj/machinery/atmospherics/unary/cryo_cell/attackby(var/obj/item/G as obj, var/mob/user as mob, params) - if(istype(G, /obj/item/reagent_containers/glass)) - var/obj/item/reagent_containers/B = G - if(beaker) - to_chat(user, "A beaker is already loaded into the machine.") - return - if(!user.drop_item()) - to_chat(user, "[B] is stuck to you!") - return - B.forceMove(src) - beaker = B - add_attack_logs(user, null, "Added [B] containing [B.reagents.log_list()] to a cryo cell at [COORD(src)]") - user.visible_message("[user] adds \a [B] to [src]!", "You add \a [B] to [src]!") - return - - if(exchange_parts(user, G)) - return - - if(istype(G, /obj/item/grab)) - var/obj/item/grab/GG = G - if(panel_open) - to_chat(user, "Close the maintenance panel first.") - return - if(!ismob(GG.affecting)) - return - if(GG.affecting.has_buckled_mobs()) //mob attached to us - to_chat(user, "[GG.affecting] will not fit into [src] because [GG.affecting.p_they()] [GG.affecting.p_have()] a slime latched onto [GG.affecting.p_their()] head.") - return - var/mob/M = GG.affecting - if(put_mob(M)) - qdel(GG) - return - return ..() - -/obj/machinery/atmospherics/unary/cryo_cell/crowbar_act(mob/user, obj/item/I) - if(default_deconstruction_crowbar(user, I)) - return - -/obj/machinery/atmospherics/unary/cryo_cell/screwdriver_act(mob/user, obj/item/I) - if(occupant || on) - to_chat(user, "The maintenance panel is locked.") - return TRUE - if(default_deconstruction_screwdriver(user, "pod0-o", "pod0", I)) - return TRUE - -/obj/machinery/atmospherics/unary/cryo_cell/update_icon() - handle_update_icon() - -/obj/machinery/atmospherics/unary/cryo_cell/proc/handle_update_icon() //making another proc to avoid spam in update_icon - overlays.Cut() //empty the overlay proc, just in case - icon_state = "pod[on]" //set the icon properly every time - - if(!src.occupant) - overlays += "lid[on]" //if no occupant, just put the lid overlay on, and ignore the rest - return - - if(occupant) - var/image/pickle = image(occupant.icon, occupant.icon_state) - pickle.overlays = occupant.overlays - pickle.pixel_y = 22 - - overlays += pickle - overlays += "lid[on]" - if(src.on && !running_bob_animation) //no bobbing if off - var/up = 0 //used to see if we are going up or down, 1 is down, 2 is up - spawn(0) // Without this, the icon update will block. The new thread will die once the occupant leaves. - running_bob_animation = 1 - while(occupant) - overlays -= "lid[on]" //have to remove the overlays first, to force an update- remove cloning pod overlay - overlays -= pickle //remove mob overlay - - switch(pickle.pixel_y) //this looks messy as fuck but it works, switch won't call itself twice - - if(23) //inbetween state, for smoothness - switch(up) //this is set later in the switch, to keep track of where the mob is supposed to go - if(2) //2 is up - pickle.pixel_y = 24 //set to highest - - if(1) //1 is down - pickle.pixel_y = 22 //set to lowest - - if(22) //mob is at it's lowest - pickle.pixel_y = 23 //set to inbetween - up = 2 //have to go up - - if(24) //mob is at it's highest - pickle.pixel_y = 23 //set to inbetween - up = 1 //have to go down - - overlays += pickle //re-add the mob to the icon - overlays += "lid[on]" //re-add the overlay of the pod, they are inside it, not floating - - sleep(7) //don't want to jiggle violently, just slowly bob - running_bob_animation = 0 - -/obj/machinery/atmospherics/unary/cryo_cell/proc/process_occupant() - if(air_contents.total_moles() < 10) - return - if(occupant) - if(occupant.stat == 2 || (occupant.health >= 100 && !occupant.has_mutated_organs())) //Why waste energy on dead or healthy people - occupant.bodytemperature = T0C - return - occupant.bodytemperature += 2*(air_contents.temperature - occupant.bodytemperature)*current_heat_capacity/(current_heat_capacity + air_contents.heat_capacity()) - occupant.bodytemperature = max(occupant.bodytemperature, air_contents.temperature) // this is so ugly i'm sorry for doing it i'll fix it later i promise - if(occupant.bodytemperature < T0C) - occupant.Sleeping(max(5/efficiency, (1/occupant.bodytemperature)*2000/efficiency)) - occupant.Paralyse(max(5/efficiency, (1/occupant.bodytemperature)*3000/efficiency)) - if(air_contents.oxygen > 2) - if(occupant.getOxyLoss()) - occupant.adjustOxyLoss(-6) - else - occupant.adjustOxyLoss(-1.2) - if(beaker && next_trans == 0) - var/proportion = 10 * min(1/beaker.volume, 1) - // Yes, this means you can get more bang for your buck with a beaker of SF vs a patch - // But it also means a giant beaker of SF won't heal people ridiculously fast 4 cheap - beaker.reagents.reaction(occupant, REAGENT_TOUCH, proportion) - beaker.reagents.trans_to(occupant, 1, 10) - next_trans++ - if(next_trans == 17) - next_trans = 0 - -/obj/machinery/atmospherics/unary/cryo_cell/proc/heat_gas_contents() - if(air_contents.total_moles() < 1) - return - var/air_heat_capacity = air_contents.heat_capacity() - var/combined_heat_capacity = current_heat_capacity + air_heat_capacity - if(combined_heat_capacity > 0) - var/combined_energy = T20C*current_heat_capacity + air_heat_capacity*air_contents.temperature - air_contents.temperature = combined_energy/combined_heat_capacity - -/obj/machinery/atmospherics/unary/cryo_cell/proc/go_out() - if(!occupant) - return - occupant.forceMove(get_step(loc, SOUTH)) //this doesn't account for walls or anything, but i don't forsee that being a problem. - if(occupant.bodytemperature < 261 && occupant.bodytemperature >= 70) //Patch by Aranclanos to stop people from taking burn damage after being ejected - occupant.bodytemperature = 261 - occupant = null - update_icon() - // eject trash the occupant dropped - for(var/atom/movable/A in contents - component_parts - list(beaker)) - A.forceMove(get_step(loc, SOUTH)) - -/obj/machinery/atmospherics/unary/cryo_cell/proc/put_mob(mob/living/carbon/M as mob) - if(!istype(M)) - to_chat(usr, "The cryo cell cannot handle such a lifeform!") - return - if(occupant) - to_chat(usr, "The cryo cell is already occupied!") - return - if(M.abiotic()) - to_chat(usr, "Subject may not have abiotic items on.") - return - if(!node) - to_chat(usr, "The cell is not correctly connected to its pipe network!") - return - M.stop_pulling() - M.forceMove(src) - if(M.health > -100 && (M.health < 0 || M.sleeping)) - to_chat(M, "You feel a cold liquid surround you. Your skin starts to freeze up.") - occupant = M -// M.metabslow = 1 - add_fingerprint(usr) - update_icon() - M.ExtinguishMob() - return 1 - -/obj/machinery/atmospherics/unary/cryo_cell/verb/move_eject() - set name = "Eject occupant" - set category = "Object" - set src in oview(1) - - if(usr == occupant)//If the user is inside the tube... - if(usr.stat == DEAD) - return - to_chat(usr, "Release sequence activated. This will take two minutes.") - sleep(600) - if(!src || !usr || !occupant || (occupant != usr)) //Check if someone's released/replaced/bombed him already - return - go_out()//and release him from the eternal prison. - else - if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other - return - add_attack_logs(usr, occupant, "Ejected from cryo cell at [COORD(src)]") - go_out() - add_fingerprint(usr) - return - -/obj/machinery/atmospherics/unary/cryo_cell/narsie_act() - go_out() - new /obj/effect/gibspawner/generic(get_turf(loc)) //I REPLACE YOUR TECHNOLOGY WITH FLESH! - color = "red"//force the icon to red - light_color = LIGHT_COLOR_RED - -/obj/machinery/atmospherics/unary/cryo_cell/verb/move_inside() - set name = "Move Inside" - set category = "Object" - set src in oview(1) - - if(usr.has_buckled_mobs()) //mob attached to us - to_chat(usr, "[usr] will not fit into [src] because [usr.p_they()] [usr.p_have()] a slime latched onto [usr.p_their()] head.") - return - - if(stat & (NOPOWER|BROKEN)) - return - - if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other - return - - put_mob(usr) - return - - - -/datum/data/function/proc/reset() - return - -/datum/data/function/proc/r_input(href, href_list, mob/user as mob) - return - -/datum/data/function/proc/display() - return - -/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user) - return //we don't see the pipe network while inside cryo. +/obj/machinery/atmospherics/unary/cryo_cell + name = "cryo cell" + desc = "Lowers the body temperature so certain medications may take effect." + icon = 'icons/obj/cryogenics.dmi' + icon_state = "pod0" + density = 1 + anchored = 1.0 + layer = ABOVE_WINDOW_LAYER + plane = GAME_PLANE + interact_offline = 1 + max_integrity = 350 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30) + var/on = 0 + var/temperature_archived + var/mob/living/carbon/occupant = null + var/obj/item/reagent_containers/glass/beaker = null + var/autoeject = 0 + + var/next_trans = 0 + var/current_heat_capacity = 50 + var/efficiency + + var/running_bob_animation = 0 // This is used to prevent threads from building up if update_icons is called multiple times + + light_color = LIGHT_COLOR_WHITE + power_change() + ..() + if(!(stat & (BROKEN|NOPOWER))) + set_light(2) + else + set_light(0) + +/obj/machinery/atmospherics/unary/cryo_cell/New() + ..() + initialize_directions = dir + component_parts = list() + component_parts += new /obj/item/circuitboard/cryo_tube(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + RefreshParts() + +/obj/machinery/atmospherics/unary/cryo_cell/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/cryo_tube(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/sheet/glass(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + RefreshParts() + +/obj/machinery/atmospherics/unary/cryo_cell/on_construction() + ..(dir,dir) + +/obj/machinery/atmospherics/unary/cryo_cell/RefreshParts() + var/C + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + C += M.rating + current_heat_capacity = 50 * C + efficiency = C + +/obj/machinery/atmospherics/unary/cryo_cell/atmos_init() + ..() + if(node) return + for(var/cdir in cardinal) + node = findConnecting(cdir) + if(node) + break + +/obj/machinery/atmospherics/unary/cryo_cell/Destroy() + QDEL_NULL(beaker) + return ..() + +/obj/machinery/atmospherics/unary/cryo_cell/ex_act(severity) + if(occupant) + occupant.ex_act(severity) + if(beaker) + beaker.ex_act(severity) + ..() + +/obj/machinery/atmospherics/unary/cryo_cell/handle_atom_del(atom/A) + ..() + if(A == beaker) + beaker = null + updateUsrDialog() + if(A == occupant) + occupant = null + updateUsrDialog() + update_icon() + +/obj/machinery/atmospherics/unary/cryo_cell/on_deconstruction() + if(beaker) + beaker.forceMove(drop_location()) + beaker = null + +/obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(atom/movable/O as mob|obj, mob/living/user as mob) + if(O.loc == user) //no you can't pull things out of your ass + return + if(user.incapacitated()) //are you cuffed, dying, lying, stunned or other + return + if(get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src)) // is the mob anchored, too far away from you, or are you too far away from the source + return + if(!ismob(O)) //humans only + return + if(istype(O, /mob/living/simple_animal) || istype(O, /mob/living/silicon)) //animals and robutts dont fit + return + if(!ishuman(user) && !isrobot(user)) //No ghosts or mice putting people into the sleeper + return + if(user.loc==null) // just in case someone manages to get a closet into the blue light dimension, as unlikely as that seems + return + if(!istype(user.loc, /turf) || !istype(O.loc, /turf)) // are you in a container/closet/pod/etc? + return + if(occupant) + to_chat(user, "The cryo cell is already occupied!") + return + var/mob/living/L = O + if(!istype(L) || L.buckled) + return + if(L.abiotic()) + to_chat(user, "Subject cannot have abiotic items on.") + return + if(L.has_buckled_mobs()) //mob attached to us + to_chat(user, "[L] will not fit into [src] because [L.p_they()] [L.p_have()] a slime latched onto [L.p_their()] head.") + return + if(put_mob(L)) + if(L == user) + visible_message("[user] climbs into the cryo cell.") + else + visible_message("[user] puts [L.name] into the cryo cell.") + add_attack_logs(user, L, "put into a cryo cell at [COORD(src)].", ATKLOG_ALL) + if(user.pulling == L) + user.stop_pulling() + +/obj/machinery/atmospherics/unary/cryo_cell/process() + ..() + if(autoeject) + if(occupant) + if(!occupant.has_organic_damage() && !occupant.has_mutated_organs()) + on = 0 + go_out() + playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) + + if(air_contents) + if(occupant) + process_occupant() + + return 1 + +/obj/machinery/atmospherics/unary/cryo_cell/process_atmos() + ..() + if(!node) + return + if(!on) + return + + if(air_contents) + temperature_archived = air_contents.temperature + heat_gas_contents() + + if(abs(temperature_archived-air_contents.temperature) > 1) + parent.update = 1 + + +/obj/machinery/atmospherics/unary/cryo_cell/AllowDrop() + return FALSE + + +/obj/machinery/atmospherics/unary/cryo_cell/relaymove(mob/user as mob) + if(user.stat) + return + go_out() + return + +/obj/machinery/atmospherics/unary/cryo_cell/attack_ghost(mob/user) + return attack_hand(user) + +/obj/machinery/atmospherics/unary/cryo_cell/attack_hand(mob/user) + if(user == occupant) + return + + if(panel_open) + to_chat(usr, "Close the maintenance panel first.") + return + + ui_interact(user) + + + /** + * The ui_interact proc is used to open and update Nano UIs + * If ui_interact is not used then the UI will not update correctly + * ui_interact is currently defined for /atom/movable (which is inherited by /obj and /mob) + * + * @param user /mob The mob who is interacting with this ui + * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main") + * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui + * + * @return nothing + */ +/obj/machinery/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "cryo.tmpl", "Cryo Cell Control System", 520, 420) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/machinery/atmospherics/unary/cryo_cell/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + data["isOperating"] = on + data["hasOccupant"] = occupant ? 1 : 0 + + var/occupantData[0] + if(occupant) + occupantData["name"] = occupant.name + occupantData["stat"] = occupant.stat + occupantData["health"] = occupant.health + occupantData["maxHealth"] = occupant.maxHealth + occupantData["minHealth"] = HEALTH_THRESHOLD_DEAD + occupantData["bruteLoss"] = occupant.getBruteLoss() + occupantData["oxyLoss"] = occupant.getOxyLoss() + occupantData["toxLoss"] = occupant.getToxLoss() + occupantData["fireLoss"] = occupant.getFireLoss() + occupantData["bodyTemperature"] = occupant.bodytemperature + data["occupant"] = occupantData; + + data["cellTemperature"] = round(air_contents.temperature) + data["cellTemperatureStatus"] = "good" + if(air_contents.temperature > T0C) // if greater than 273.15 kelvin (0 celcius) + data["cellTemperatureStatus"] = "bad" + else if(air_contents.temperature > TCRYO) + data["cellTemperatureStatus"] = "average" + + data["isBeakerLoaded"] = beaker ? 1 : 0 + data["beakerLabel"] = null + data["beakerVolume"] = 0 + if(beaker) + data["beakerLabel"] = beaker.label_text ? beaker.label_text : null + if(beaker.reagents && beaker.reagents.reagent_list.len) + for(var/datum/reagent/R in beaker.reagents.reagent_list) + data["beakerVolume"] += R.volume + + data["autoeject"] = autoeject + return data + +/obj/machinery/atmospherics/unary/cryo_cell/Topic(href, href_list) + if(usr == occupant) + return 0 // don't update UIs attached to this object + + if(..()) + return 0 // don't update UIs attached to this object + + if(href_list["switchOn"]) + on = 1 + update_icon() + + if(href_list["switchOff"]) + on = 0 + update_icon() + + if(href_list["autoejectOn"]) + autoeject = 1 + + if(href_list["autoejectOff"]) + autoeject = 0 + + if(href_list["ejectBeaker"]) + if(beaker) + beaker.forceMove(get_step(loc, SOUTH)) + beaker = null + + if(href_list["ejectOccupant"]) + if(!occupant || isslime(usr) || ispAI(usr)) + return 0 // don't update UIs attached to this object + add_attack_logs(usr, occupant, "ejected from cryo cell at [COORD(src)]", ATKLOG_ALL) + go_out() + + add_fingerprint(usr) + return 1 // update UIs attached to this object + +/obj/machinery/atmospherics/unary/cryo_cell/attackby(var/obj/item/G as obj, var/mob/user as mob, params) + if(istype(G, /obj/item/reagent_containers/glass)) + var/obj/item/reagent_containers/B = G + if(beaker) + to_chat(user, "A beaker is already loaded into the machine.") + return + if(!user.drop_item()) + to_chat(user, "[B] is stuck to you!") + return + B.forceMove(src) + beaker = B + add_attack_logs(user, null, "Added [B] containing [B.reagents.log_list()] to a cryo cell at [COORD(src)]") + user.visible_message("[user] adds \a [B] to [src]!", "You add \a [B] to [src]!") + return + + if(exchange_parts(user, G)) + return + + if(istype(G, /obj/item/grab)) + var/obj/item/grab/GG = G + if(panel_open) + to_chat(user, "Close the maintenance panel first.") + return + if(!ismob(GG.affecting)) + return + if(GG.affecting.has_buckled_mobs()) //mob attached to us + to_chat(user, "[GG.affecting] will not fit into [src] because [GG.affecting.p_they()] [GG.affecting.p_have()] a slime latched onto [GG.affecting.p_their()] head.") + return + var/mob/M = GG.affecting + if(put_mob(M)) + qdel(GG) + return + return ..() + +/obj/machinery/atmospherics/unary/cryo_cell/crowbar_act(mob/user, obj/item/I) + if(default_deconstruction_crowbar(user, I)) + return + +/obj/machinery/atmospherics/unary/cryo_cell/screwdriver_act(mob/user, obj/item/I) + if(occupant || on) + to_chat(user, "The maintenance panel is locked.") + return TRUE + if(default_deconstruction_screwdriver(user, "pod0-o", "pod0", I)) + return TRUE + +/obj/machinery/atmospherics/unary/cryo_cell/update_icon() + handle_update_icon() + +/obj/machinery/atmospherics/unary/cryo_cell/proc/handle_update_icon() //making another proc to avoid spam in update_icon + overlays.Cut() //empty the overlay proc, just in case + icon_state = "pod[on]" //set the icon properly every time + + if(!src.occupant) + overlays += "lid[on]" //if no occupant, just put the lid overlay on, and ignore the rest + return + + if(occupant) + var/image/pickle = image(occupant.icon, occupant.icon_state) + pickle.overlays = occupant.overlays + pickle.pixel_y = 22 + + overlays += pickle + overlays += "lid[on]" + if(src.on && !running_bob_animation) //no bobbing if off + var/up = 0 //used to see if we are going up or down, 1 is down, 2 is up + spawn(0) // Without this, the icon update will block. The new thread will die once the occupant leaves. + running_bob_animation = 1 + while(occupant) + overlays -= "lid[on]" //have to remove the overlays first, to force an update- remove cloning pod overlay + overlays -= pickle //remove mob overlay + + switch(pickle.pixel_y) //this looks messy as fuck but it works, switch won't call itself twice + + if(23) //inbetween state, for smoothness + switch(up) //this is set later in the switch, to keep track of where the mob is supposed to go + if(2) //2 is up + pickle.pixel_y = 24 //set to highest + + if(1) //1 is down + pickle.pixel_y = 22 //set to lowest + + if(22) //mob is at it's lowest + pickle.pixel_y = 23 //set to inbetween + up = 2 //have to go up + + if(24) //mob is at it's highest + pickle.pixel_y = 23 //set to inbetween + up = 1 //have to go down + + overlays += pickle //re-add the mob to the icon + overlays += "lid[on]" //re-add the overlay of the pod, they are inside it, not floating + + sleep(7) //don't want to jiggle violently, just slowly bob + running_bob_animation = 0 + +/obj/machinery/atmospherics/unary/cryo_cell/proc/process_occupant() + if(air_contents.total_moles() < 10) + return + if(occupant) + if(occupant.stat == 2 || (occupant.health >= 100 && !occupant.has_mutated_organs())) //Why waste energy on dead or healthy people + occupant.bodytemperature = T0C + return + occupant.bodytemperature += 2*(air_contents.temperature - occupant.bodytemperature)*current_heat_capacity/(current_heat_capacity + air_contents.heat_capacity()) + occupant.bodytemperature = max(occupant.bodytemperature, air_contents.temperature) // this is so ugly i'm sorry for doing it i'll fix it later i promise + if(occupant.bodytemperature < T0C) + occupant.Sleeping(max(5/efficiency, (1/occupant.bodytemperature)*2000/efficiency)) + occupant.Paralyse(max(5/efficiency, (1/occupant.bodytemperature)*3000/efficiency)) + if(air_contents.oxygen > 2) + if(occupant.getOxyLoss()) + occupant.adjustOxyLoss(-6) + else + occupant.adjustOxyLoss(-1.2) + if(beaker && next_trans == 0) + var/proportion = 10 * min(1/beaker.volume, 1) + // Yes, this means you can get more bang for your buck with a beaker of SF vs a patch + // But it also means a giant beaker of SF won't heal people ridiculously fast 4 cheap + beaker.reagents.reaction(occupant, REAGENT_TOUCH, proportion) + beaker.reagents.trans_to(occupant, 1, 10) + next_trans++ + if(next_trans == 17) + next_trans = 0 + +/obj/machinery/atmospherics/unary/cryo_cell/proc/heat_gas_contents() + if(air_contents.total_moles() < 1) + return + var/air_heat_capacity = air_contents.heat_capacity() + var/combined_heat_capacity = current_heat_capacity + air_heat_capacity + if(combined_heat_capacity > 0) + var/combined_energy = T20C*current_heat_capacity + air_heat_capacity*air_contents.temperature + air_contents.temperature = combined_energy/combined_heat_capacity + +/obj/machinery/atmospherics/unary/cryo_cell/proc/go_out() + if(!occupant) + return + occupant.forceMove(get_step(loc, SOUTH)) //this doesn't account for walls or anything, but i don't forsee that being a problem. + if(occupant.bodytemperature < 261 && occupant.bodytemperature >= 70) //Patch by Aranclanos to stop people from taking burn damage after being ejected + occupant.bodytemperature = 261 + occupant = null + update_icon() + // eject trash the occupant dropped + for(var/atom/movable/A in contents - component_parts - list(beaker)) + A.forceMove(get_step(loc, SOUTH)) + +/obj/machinery/atmospherics/unary/cryo_cell/proc/put_mob(mob/living/carbon/M as mob) + if(!istype(M)) + to_chat(usr, "The cryo cell cannot handle such a lifeform!") + return + if(occupant) + to_chat(usr, "The cryo cell is already occupied!") + return + if(M.abiotic()) + to_chat(usr, "Subject may not have abiotic items on.") + return + if(!node) + to_chat(usr, "The cell is not correctly connected to its pipe network!") + return + M.stop_pulling() + M.forceMove(src) + if(M.health > -100 && (M.health < 0 || M.sleeping)) + to_chat(M, "You feel a cold liquid surround you. Your skin starts to freeze up.") + occupant = M +// M.metabslow = 1 + add_fingerprint(usr) + update_icon() + M.ExtinguishMob() + return 1 + +/obj/machinery/atmospherics/unary/cryo_cell/verb/move_eject() + set name = "Eject occupant" + set category = "Object" + set src in oview(1) + + if(usr == occupant)//If the user is inside the tube... + if(usr.stat == DEAD) + return + to_chat(usr, "Release sequence activated. This will take two minutes.") + sleep(600) + if(!src || !usr || !occupant || (occupant != usr)) //Check if someone's released/replaced/bombed him already + return + go_out()//and release him from the eternal prison. + else + if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other + return + add_attack_logs(usr, occupant, "Ejected from cryo cell at [COORD(src)]") + go_out() + add_fingerprint(usr) + return + +/obj/machinery/atmospherics/unary/cryo_cell/narsie_act() + go_out() + new /obj/effect/gibspawner/generic(get_turf(loc)) //I REPLACE YOUR TECHNOLOGY WITH FLESH! + color = "red"//force the icon to red + light_color = LIGHT_COLOR_RED + +/obj/machinery/atmospherics/unary/cryo_cell/verb/move_inside() + set name = "Move Inside" + set category = "Object" + set src in oview(1) + + if(usr.has_buckled_mobs()) //mob attached to us + to_chat(usr, "[usr] will not fit into [src] because [usr.p_they()] [usr.p_have()] a slime latched onto [usr.p_their()] head.") + return + + if(stat & (NOPOWER|BROKEN)) + return + + if(usr.incapacitated()) //are you cuffed, dying, lying, stunned or other + return + + put_mob(usr) + return + + + +/datum/data/function/proc/reset() + return + +/datum/data/function/proc/r_input(href, href_list, mob/user as mob) + return + +/datum/data/function/proc/display() + return + +/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user) + return //we don't see the pipe network while inside cryo. diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm index f3f75f3c32c..1d97056912f 100644 --- a/code/game/machinery/deployable.dm +++ b/code/game/machinery/deployable.dm @@ -1,212 +1,212 @@ -#define SINGLE "single" -#define VERTICAL "vertical" -#define HORIZONTAL "horizontal" - -#define METAL 1 -#define WOOD 2 -#define SAND 3 - -//Barricades/cover - -/obj/structure/barricade - name = "chest high wall" - desc = "Looks like this would make good cover." - anchored = TRUE - density = TRUE - max_integrity = 100 - var/proj_pass_rate = 50 //How many projectiles will pass the cover. Lower means stronger cover - var/bar_material = METAL - var/drop_amount = 3 - var/stacktype = /obj/item/stack/sheet/metal - -/obj/structure/barricade/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - make_debris() - qdel(src) - - -/obj/structure/barricade/proc/make_debris() - if(stacktype) - new stacktype(get_turf(src), drop_amount) - -/obj/structure/barricade/welder_act(mob/user, obj/item/I) - if(obj_integrity >= max_integrity) - to_chat(user, "[src] does not need repairs.") - return - if(user.a_intent == INTENT_HARM) - return - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_REPAIR_MESSAGE - if(I.use_tool(src, user, 40, volume = I.tool_volume)) - WELDER_REPAIR_SUCCESS_MESSAGE - obj_integrity = Clamp(obj_integrity + 20, 0, max_integrity) - update_icon() - return TRUE - -/obj/structure/barricade/CanPass(atom/movable/mover, turf/target)//So bullets will fly over and stuff. - if(locate(/obj/structure/barricade) in get_turf(mover)) - return TRUE - else if(istype(mover, /obj/item/projectile)) - if(!anchored) - return TRUE - var/obj/item/projectile/proj = mover - if(proj.firer && Adjacent(proj.firer)) - return TRUE - if(prob(proj_pass_rate)) - return TRUE - return FALSE - else - return !density - - - -/////BARRICADE TYPES/////// - -/obj/structure/barricade/wooden - name = "wooden barricade" - desc = "This space is blocked off by a wooden barricade." - icon = 'icons/obj/structures.dmi' - icon_state = "woodenbarricade" - bar_material = WOOD - stacktype = /obj/item/stack/sheet/wood - -/obj/structure/barricade/wooden/attackby(obj/item/I, mob/user) - if(istype(I,/obj/item/stack/sheet/wood)) - var/obj/item/stack/sheet/wood/W = I - if(W.amount < 5) - to_chat(user, "You need at least five wooden planks to make a wall!") - return - else - to_chat(user, "You start adding [I] to [src]...") - if(do_after(user, 50, target = src)) - W.use(5) - new /turf/simulated/wall/mineral/wood/nonmetal(get_turf(src)) - qdel(src) - return - return ..() - -/obj/structure/barricade/wooden/crude - name = "crude plank barricade" - desc = "This space is blocked off by a crude assortment of planks." - icon_state = "woodenbarricade-old" - drop_amount = 1 - max_integrity = 50 - proj_pass_rate = 65 - -/obj/structure/barricade/wooden/crude/snow - desc = "This space is blocked off by a crude assortment of planks. It seems to be covered in a layer of snow." - icon_state = "woodenbarricade-snow-old" - max_integrity = 75 - -/obj/structure/barricade/sandbags - name = "sandbags" - desc = "Bags of sand. Self explanatory." - icon = 'icons/obj/smooth_structures/sandbags.dmi' - icon_state = "sandbags" - max_integrity = 280 - proj_pass_rate = 20 - pass_flags = LETPASSTHROW - bar_material = SAND - climbable = TRUE - smooth = SMOOTH_TRUE - canSmoothWith = list(/obj/structure/barricade/sandbags, /turf/simulated/wall, /turf/simulated/wall/r_wall, /obj/structure/falsewall, /obj/structure/falsewall/reinforced, /turf/simulated/wall/rust, /turf/simulated/wall/r_wall/rust, /obj/structure/barricade/security) - stacktype = null - -/obj/structure/barricade/security - name = "security barrier" - desc = "A deployable barrier. Provides good cover in fire fights." - icon = 'icons/obj/objects.dmi' - icon_state = "barrier0" - density = FALSE - anchored = FALSE - max_integrity = 180 - proj_pass_rate = 20 - armor = list(melee = 10, bullet = 50, laser = 50, energy = 50, bomb = 10, bio = 100, rad = 100, fire = 10, acid = 0) - stacktype = null - var/deploy_time = 40 - var/deploy_message = TRUE - -/obj/structure/barricade/security/New() - ..() - addtimer(CALLBACK(src, .proc/deploy), deploy_time) - -/obj/structure/barricade/security/proc/deploy() - icon_state = "barrier1" - density = TRUE - anchored = TRUE - if(deploy_message) - visible_message("[src] deploys!") - - -/obj/item/grenade/barrier - name = "barrier grenade" - desc = "Instant cover." - icon = 'icons/obj/grenade.dmi' - icon_state = "flashbang" - item_state = "flashbang" - actions_types = list(/datum/action/item_action/toggle_barrier_spread) - var/mode = SINGLE - -/obj/item/grenade/barrier/examine(mob/user) - . = ..() - . += "Alt-click to toggle modes." - -/obj/item/grenade/barrier/AltClick(mob/living/carbon/user) - if(!istype(user) || !user.Adjacent(src) || user.incapacitated()) - return - toggle_mode(user) - -/obj/item/grenade/barrier/proc/toggle_mode(mob/user) - switch(mode) - if(SINGLE) - mode = VERTICAL - if(VERTICAL) - mode = HORIZONTAL - if(HORIZONTAL) - mode = SINGLE - - to_chat(user, "[src] is now in [mode] mode.") - -/obj/item/grenade/barrier/prime() - new /obj/structure/barricade/security(get_turf(loc)) - switch(mode) - if(VERTICAL) - var/target_turf = get_step(src, NORTH) - if(!(is_blocked_turf(target_turf))) - new /obj/structure/barricade/security(target_turf) - - var/target_turf2 = get_step(src, SOUTH) - if(!(is_blocked_turf(target_turf2))) - new /obj/structure/barricade/security(target_turf2) - if(HORIZONTAL) - var/target_turf = get_step(src, EAST) - if(!(is_blocked_turf(target_turf))) - new /obj/structure/barricade/security(target_turf) - - var/target_turf2 = get_step(src, WEST) - if(!(is_blocked_turf(target_turf2))) - new /obj/structure/barricade/security(target_turf2) - qdel(src) - -/obj/item/grenade/barrier/ui_action_click(mob/user) - toggle_mode(user) - - -/obj/structure/barricade/mime - name = "floor" - desc = "Is... this a floor?" - icon = 'icons/effects/water.dmi' - icon_state = "wet_floor_static" - stacktype = /obj/item/stack/sheet/mineral/tranquillite - -/obj/structure/barricade/mime/mrcd - stacktype = null - -#undef SINGLE -#undef VERTICAL -#undef HORIZONTAL - -#undef METAL -#undef WOOD -#undef SAND \ No newline at end of file +#define SINGLE "single" +#define VERTICAL "vertical" +#define HORIZONTAL "horizontal" + +#define METAL 1 +#define WOOD 2 +#define SAND 3 + +//Barricades/cover + +/obj/structure/barricade + name = "chest high wall" + desc = "Looks like this would make good cover." + anchored = TRUE + density = TRUE + max_integrity = 100 + var/proj_pass_rate = 50 //How many projectiles will pass the cover. Lower means stronger cover + var/bar_material = METAL + var/drop_amount = 3 + var/stacktype = /obj/item/stack/sheet/metal + +/obj/structure/barricade/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + make_debris() + qdel(src) + + +/obj/structure/barricade/proc/make_debris() + if(stacktype) + new stacktype(get_turf(src), drop_amount) + +/obj/structure/barricade/welder_act(mob/user, obj/item/I) + if(obj_integrity >= max_integrity) + to_chat(user, "[src] does not need repairs.") + return + if(user.a_intent == INTENT_HARM) + return + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_REPAIR_MESSAGE + if(I.use_tool(src, user, 40, volume = I.tool_volume)) + WELDER_REPAIR_SUCCESS_MESSAGE + obj_integrity = Clamp(obj_integrity + 20, 0, max_integrity) + update_icon() + return TRUE + +/obj/structure/barricade/CanPass(atom/movable/mover, turf/target)//So bullets will fly over and stuff. + if(locate(/obj/structure/barricade) in get_turf(mover)) + return TRUE + else if(istype(mover, /obj/item/projectile)) + if(!anchored) + return TRUE + var/obj/item/projectile/proj = mover + if(proj.firer && Adjacent(proj.firer)) + return TRUE + if(prob(proj_pass_rate)) + return TRUE + return FALSE + else + return !density + + + +/////BARRICADE TYPES/////// + +/obj/structure/barricade/wooden + name = "wooden barricade" + desc = "This space is blocked off by a wooden barricade." + icon = 'icons/obj/structures.dmi' + icon_state = "woodenbarricade" + bar_material = WOOD + stacktype = /obj/item/stack/sheet/wood + +/obj/structure/barricade/wooden/attackby(obj/item/I, mob/user) + if(istype(I,/obj/item/stack/sheet/wood)) + var/obj/item/stack/sheet/wood/W = I + if(W.amount < 5) + to_chat(user, "You need at least five wooden planks to make a wall!") + return + else + to_chat(user, "You start adding [I] to [src]...") + if(do_after(user, 50, target = src)) + W.use(5) + new /turf/simulated/wall/mineral/wood/nonmetal(get_turf(src)) + qdel(src) + return + return ..() + +/obj/structure/barricade/wooden/crude + name = "crude plank barricade" + desc = "This space is blocked off by a crude assortment of planks." + icon_state = "woodenbarricade-old" + drop_amount = 1 + max_integrity = 50 + proj_pass_rate = 65 + +/obj/structure/barricade/wooden/crude/snow + desc = "This space is blocked off by a crude assortment of planks. It seems to be covered in a layer of snow." + icon_state = "woodenbarricade-snow-old" + max_integrity = 75 + +/obj/structure/barricade/sandbags + name = "sandbags" + desc = "Bags of sand. Self explanatory." + icon = 'icons/obj/smooth_structures/sandbags.dmi' + icon_state = "sandbags" + max_integrity = 280 + proj_pass_rate = 20 + pass_flags = LETPASSTHROW + bar_material = SAND + climbable = TRUE + smooth = SMOOTH_TRUE + canSmoothWith = list(/obj/structure/barricade/sandbags, /turf/simulated/wall, /turf/simulated/wall/r_wall, /obj/structure/falsewall, /obj/structure/falsewall/reinforced, /turf/simulated/wall/rust, /turf/simulated/wall/r_wall/rust, /obj/structure/barricade/security) + stacktype = null + +/obj/structure/barricade/security + name = "security barrier" + desc = "A deployable barrier. Provides good cover in fire fights." + icon = 'icons/obj/objects.dmi' + icon_state = "barrier0" + density = FALSE + anchored = FALSE + max_integrity = 180 + proj_pass_rate = 20 + armor = list(melee = 10, bullet = 50, laser = 50, energy = 50, bomb = 10, bio = 100, rad = 100, fire = 10, acid = 0) + stacktype = null + var/deploy_time = 40 + var/deploy_message = TRUE + +/obj/structure/barricade/security/New() + ..() + addtimer(CALLBACK(src, .proc/deploy), deploy_time) + +/obj/structure/barricade/security/proc/deploy() + icon_state = "barrier1" + density = TRUE + anchored = TRUE + if(deploy_message) + visible_message("[src] deploys!") + + +/obj/item/grenade/barrier + name = "barrier grenade" + desc = "Instant cover." + icon = 'icons/obj/grenade.dmi' + icon_state = "flashbang" + item_state = "flashbang" + actions_types = list(/datum/action/item_action/toggle_barrier_spread) + var/mode = SINGLE + +/obj/item/grenade/barrier/examine(mob/user) + . = ..() + . += "Alt-click to toggle modes." + +/obj/item/grenade/barrier/AltClick(mob/living/carbon/user) + if(!istype(user) || !user.Adjacent(src) || user.incapacitated()) + return + toggle_mode(user) + +/obj/item/grenade/barrier/proc/toggle_mode(mob/user) + switch(mode) + if(SINGLE) + mode = VERTICAL + if(VERTICAL) + mode = HORIZONTAL + if(HORIZONTAL) + mode = SINGLE + + to_chat(user, "[src] is now in [mode] mode.") + +/obj/item/grenade/barrier/prime() + new /obj/structure/barricade/security(get_turf(loc)) + switch(mode) + if(VERTICAL) + var/target_turf = get_step(src, NORTH) + if(!(is_blocked_turf(target_turf))) + new /obj/structure/barricade/security(target_turf) + + var/target_turf2 = get_step(src, SOUTH) + if(!(is_blocked_turf(target_turf2))) + new /obj/structure/barricade/security(target_turf2) + if(HORIZONTAL) + var/target_turf = get_step(src, EAST) + if(!(is_blocked_turf(target_turf))) + new /obj/structure/barricade/security(target_turf) + + var/target_turf2 = get_step(src, WEST) + if(!(is_blocked_turf(target_turf2))) + new /obj/structure/barricade/security(target_turf2) + qdel(src) + +/obj/item/grenade/barrier/ui_action_click(mob/user) + toggle_mode(user) + + +/obj/structure/barricade/mime + name = "floor" + desc = "Is... this a floor?" + icon = 'icons/effects/water.dmi' + icon_state = "wet_floor_static" + stacktype = /obj/item/stack/sheet/mineral/tranquillite + +/obj/structure/barricade/mime/mrcd + stacktype = null + +#undef SINGLE +#undef VERTICAL +#undef HORIZONTAL + +#undef METAL +#undef WOOD +#undef SAND diff --git a/code/game/machinery/door_control.dm b/code/game/machinery/door_control.dm index f6bbbb3bc02..8e9befd7a6a 100644 --- a/code/game/machinery/door_control.dm +++ b/code/game/machinery/door_control.dm @@ -1,126 +1,126 @@ -/obj/machinery/door_control - name = "remote door-control" - desc = "A remote control-switch for a door." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "doorctrl0" - power_channel = ENVIRON - var/id = null - var/safety_z_check = 1 - var/normaldoorcontrol = 0 - var/desiredstate = 0 // Zero is closed, 1 is open. - var/specialfunctions = 1 - /* - Bitflag, 1= open - 2= idscan, - 4= bolts - 8= shock - 16= door safties - - */ - - var/exposedwires = 0 - var/wires = 3 - /* - Bitflag, 1=checkID - 2=Network Access - */ - - anchored = 1.0 - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - -/obj/machinery/door_control/attack_ai(mob/user as mob) - if(wires & 2) - return attack_hand(user) - else - to_chat(user, "Error, no route to host.") - -/obj/machinery/door_control/attackby(obj/item/W, mob/user as mob, params) - if(istype(W, /obj/item/detective_scanner)) - return - return ..() - -/obj/machinery/door_control/emag_act(user as mob) - if(!emagged) - emagged = 1 - req_access = list() - req_one_access = list() - playsound(loc, "sparks", 100, 1) - -/obj/machinery/door_control/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - return attack_hand(user) - -/obj/machinery/door_control/attack_hand(mob/user as mob) - add_fingerprint(usr) - if(stat & (NOPOWER|BROKEN)) - return - - if(!allowed(user) && (wires & 1) && !user.can_advanced_admin_interact()) - to_chat(user, "Access Denied.") - flick("doorctrl-denied",src) - return - - use_power(5) - icon_state = "doorctrl1" - add_fingerprint(user) - - if(normaldoorcontrol) - for(var/obj/machinery/door/airlock/D in GLOB.airlocks) - if(safety_z_check && D.z != z) - continue - if(D.id_tag == id) - if(specialfunctions & OPEN) - if(D.density) - spawn(0) - D.open() - return - else - spawn(0) - D.close() - return - if(desiredstate == 1) - if(specialfunctions & IDSCAN) - D.aiDisabledIdScanner = 1 - if(specialfunctions & BOLTS) - D.lock() - if(specialfunctions & SHOCK) - D.electrify(-1) - if(specialfunctions & SAFE) - D.safe = 0 - else - if(specialfunctions & IDSCAN) - D.aiDisabledIdScanner = 0 - if(specialfunctions & BOLTS) - D.unlock() - if(specialfunctions & SHOCK) - D.electrify(0) - if(specialfunctions & SAFE) - D.safe = 1 - - else - for(var/obj/machinery/door/poddoor/M in GLOB.airlocks) - if(safety_z_check && M.z != z) - continue - if(M.id_tag == id) - if(M.density) - spawn( 0 ) - M.open() - return - else - spawn( 0 ) - M.close() - return - - desiredstate = !desiredstate - spawn(15) - if(!(stat & NOPOWER)) - icon_state = "doorctrl0" - -/obj/machinery/door_control/power_change() - ..() - if(stat & NOPOWER) - icon_state = "doorctrl-p" - else - icon_state = "doorctrl0" +/obj/machinery/door_control + name = "remote door-control" + desc = "A remote control-switch for a door." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "doorctrl0" + power_channel = ENVIRON + var/id = null + var/safety_z_check = 1 + var/normaldoorcontrol = 0 + var/desiredstate = 0 // Zero is closed, 1 is open. + var/specialfunctions = 1 + /* + Bitflag, 1= open + 2= idscan, + 4= bolts + 8= shock + 16= door safties + + */ + + var/exposedwires = 0 + var/wires = 3 + /* + Bitflag, 1=checkID + 2=Network Access + */ + + anchored = 1.0 + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + +/obj/machinery/door_control/attack_ai(mob/user as mob) + if(wires & 2) + return attack_hand(user) + else + to_chat(user, "Error, no route to host.") + +/obj/machinery/door_control/attackby(obj/item/W, mob/user as mob, params) + if(istype(W, /obj/item/detective_scanner)) + return + return ..() + +/obj/machinery/door_control/emag_act(user as mob) + if(!emagged) + emagged = 1 + req_access = list() + req_one_access = list() + playsound(loc, "sparks", 100, 1) + +/obj/machinery/door_control/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + return attack_hand(user) + +/obj/machinery/door_control/attack_hand(mob/user as mob) + add_fingerprint(usr) + if(stat & (NOPOWER|BROKEN)) + return + + if(!allowed(user) && (wires & 1) && !user.can_advanced_admin_interact()) + to_chat(user, "Access Denied.") + flick("doorctrl-denied",src) + return + + use_power(5) + icon_state = "doorctrl1" + add_fingerprint(user) + + if(normaldoorcontrol) + for(var/obj/machinery/door/airlock/D in GLOB.airlocks) + if(safety_z_check && D.z != z) + continue + if(D.id_tag == id) + if(specialfunctions & OPEN) + if(D.density) + spawn(0) + D.open() + return + else + spawn(0) + D.close() + return + if(desiredstate == 1) + if(specialfunctions & IDSCAN) + D.aiDisabledIdScanner = 1 + if(specialfunctions & BOLTS) + D.lock() + if(specialfunctions & SHOCK) + D.electrify(-1) + if(specialfunctions & SAFE) + D.safe = 0 + else + if(specialfunctions & IDSCAN) + D.aiDisabledIdScanner = 0 + if(specialfunctions & BOLTS) + D.unlock() + if(specialfunctions & SHOCK) + D.electrify(0) + if(specialfunctions & SAFE) + D.safe = 1 + + else + for(var/obj/machinery/door/poddoor/M in GLOB.airlocks) + if(safety_z_check && M.z != z) + continue + if(M.id_tag == id) + if(M.density) + spawn( 0 ) + M.open() + return + else + spawn( 0 ) + M.close() + return + + desiredstate = !desiredstate + spawn(15) + if(!(stat & NOPOWER)) + icon_state = "doorctrl0" + +/obj/machinery/door_control/power_change() + ..() + if(stat & NOPOWER) + icon_state = "doorctrl-p" + else + icon_state = "doorctrl0" diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm index 4b36c13b034..260d26493c0 100644 --- a/code/game/machinery/doors/airlock_electronics.dm +++ b/code/game/machinery/doors/airlock_electronics.dm @@ -1,102 +1,102 @@ -/obj/item/airlock_electronics - name = "airlock electronics" - icon = 'icons/obj/doors/door_assembly.dmi' - icon_state = "door_electronics" - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=50, MAT_GLASS=50) - origin_tech = "engineering=2;programming=1" - req_access = list(ACCESS_ENGINE) - toolspeed = 1 - usesound = 'sound/items/deconstruct.ogg' - var/list/conf_access = null - var/one_access = 0 //if set to 1, door would receive req_one_access instead of req_access - var/const/max_brain_damage = 60 // Maximum brain damage a mob can have until it can't use the electronics - var/unres_sides = 0 - var/unres_direction = null - -/obj/item/airlock_electronics/attack_self(mob/user) - if(!ishuman(user) && !isrobot(user)) - return ..() - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.getBrainLoss() >= max_brain_damage) - to_chat(user, "You forget how to use \the [src].") - return - - var/t1 = text("Access control
        \n") - t1 += "
        " - t1 += " Unrestricted Access Settings
        " - - var/list/Directions = list("North","South",,"East",,,,"West") - for(var/direction in cardinal) - if (unres_direction && unres_direction == direction) - t1 += "[Directions[direction]]
        " - else - t1 += "[Directions[direction]]
        " - - t1 += "
        " - t1 += "Access requirement is set to " - t1 += one_access ? "ONE
        " : "ALL
        " - - t1 += conf_access == null ? "All
        " : "All
        " - - var/list/accesses = get_all_accesses() - for(var/acc in accesses) - var/aname = get_access_desc(acc) - - if(!conf_access || !conf_access.len || !(acc in conf_access)) - t1 += "[aname]
        " - else if(one_access) - t1 += "[aname]
        " - else - t1 += "[aname]
        " - - t1 += "

        Close

        \n" - - var/datum/browser/popup = new(user, "airlock_electronics", name, 400, 400) - popup.set_content(t1) - popup.open(0) - onclose(user, "airlock") - -/obj/item/airlock_electronics/Topic(href, href_list) - ..() - - if(usr.incapacitated() || (!ishuman(usr) && !isrobot(usr))) - return 1 - - if(href_list["close"]) - usr << browse(null, "window=airlock_electronics") - return - - if(href_list["one_access"]) - one_access = !one_access - - if(href_list["access"]) - toggle_access(href_list["access"]) - - if(href_list["unres_direction"]) - unres_direction = text2num(href_list["unres_direction"]) - if (unres_sides == unres_direction) - unres_sides = 0 - unres_direction = null - else - unres_sides = unres_direction - - attack_self(usr) - -/obj/item/airlock_electronics/proc/toggle_access(access) - if(access == "all") - conf_access = null - else - var/req = text2num(access) - - if(conf_access == null) - conf_access = list() - - if(!(req in conf_access)) - conf_access += req - else - conf_access -= req - if(!conf_access.len) - conf_access = null +/obj/item/airlock_electronics + name = "airlock electronics" + icon = 'icons/obj/doors/door_assembly.dmi' + icon_state = "door_electronics" + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=50, MAT_GLASS=50) + origin_tech = "engineering=2;programming=1" + req_access = list(ACCESS_ENGINE) + toolspeed = 1 + usesound = 'sound/items/deconstruct.ogg' + var/list/conf_access = null + var/one_access = 0 //if set to 1, door would receive req_one_access instead of req_access + var/const/max_brain_damage = 60 // Maximum brain damage a mob can have until it can't use the electronics + var/unres_sides = 0 + var/unres_direction = null + +/obj/item/airlock_electronics/attack_self(mob/user) + if(!ishuman(user) && !isrobot(user)) + return ..() + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.getBrainLoss() >= max_brain_damage) + to_chat(user, "You forget how to use \the [src].") + return + + var/t1 = text("Access control
        \n") + t1 += "
        " + t1 += " Unrestricted Access Settings
        " + + var/list/Directions = list("North","South",,"East",,,,"West") + for(var/direction in cardinal) + if (unres_direction && unres_direction == direction) + t1 += "[Directions[direction]]
        " + else + t1 += "[Directions[direction]]
        " + + t1 += "
        " + t1 += "Access requirement is set to " + t1 += one_access ? "ONE
        " : "ALL
        " + + t1 += conf_access == null ? "All
        " : "All
        " + + var/list/accesses = get_all_accesses() + for(var/acc in accesses) + var/aname = get_access_desc(acc) + + if(!conf_access || !conf_access.len || !(acc in conf_access)) + t1 += "[aname]
        " + else if(one_access) + t1 += "[aname]
        " + else + t1 += "[aname]
        " + + t1 += "

        Close

        \n" + + var/datum/browser/popup = new(user, "airlock_electronics", name, 400, 400) + popup.set_content(t1) + popup.open(0) + onclose(user, "airlock") + +/obj/item/airlock_electronics/Topic(href, href_list) + ..() + + if(usr.incapacitated() || (!ishuman(usr) && !isrobot(usr))) + return 1 + + if(href_list["close"]) + usr << browse(null, "window=airlock_electronics") + return + + if(href_list["one_access"]) + one_access = !one_access + + if(href_list["access"]) + toggle_access(href_list["access"]) + + if(href_list["unres_direction"]) + unres_direction = text2num(href_list["unres_direction"]) + if (unres_sides == unres_direction) + unres_sides = 0 + unres_direction = null + else + unres_sides = unres_direction + + attack_self(usr) + +/obj/item/airlock_electronics/proc/toggle_access(access) + if(access == "all") + conf_access = null + else + var/req = text2num(access) + + if(conf_access == null) + conf_access = list() + + if(!(req in conf_access)) + conf_access += req + else + conf_access -= req + if(!conf_access.len) + conf_access = null diff --git a/code/game/machinery/doors/alarmlock.dm b/code/game/machinery/doors/alarmlock.dm index d3bf12f72f0..d5c78aa2325 100644 --- a/code/game/machinery/doors/alarmlock.dm +++ b/code/game/machinery/doors/alarmlock.dm @@ -1,44 +1,44 @@ -/obj/machinery/door/airlock/alarmlock - name = "glass alarm airlock" - icon = 'icons/obj/doors/airlocks/station2/glass.dmi' - overlays_file = 'icons/obj/doors/airlocks/station2/overlays.dmi' - opacity = 0 - glass = 1 - autoclose = 0 - var/datum/radio_frequency/air_connection - var/air_frequency = ATMOS_FIRE_FREQ - -/obj/machinery/door/airlock/alarmlock/New() - ..() - air_connection = new - -/obj/machinery/door/airlock/alarmlock/Destroy() - if(SSradio) - SSradio.remove_object(src,air_frequency) - air_connection = null - return ..() - -/obj/machinery/door/airlock/alarmlock/Initialize() - ..() - SSradio.remove_object(src, air_frequency) - air_connection = SSradio.add_object(src, air_frequency, RADIO_TO_AIRALARM) - open() - -/obj/machinery/door/airlock/alarmlock/receive_signal(datum/signal/signal) - ..() - if(stat & (NOPOWER|BROKEN)) - return - - var/alarm_area = signal.data["zone"] - var/alert = signal.data["alert"] - - var/area/our_area = get_area(src) - - if(alarm_area == our_area.name) - switch(alert) - if("severe") - autoclose = 1 - close() - if("minor", "clear") - autoclose = 0 - open() +/obj/machinery/door/airlock/alarmlock + name = "glass alarm airlock" + icon = 'icons/obj/doors/airlocks/station2/glass.dmi' + overlays_file = 'icons/obj/doors/airlocks/station2/overlays.dmi' + opacity = 0 + glass = 1 + autoclose = 0 + var/datum/radio_frequency/air_connection + var/air_frequency = ATMOS_FIRE_FREQ + +/obj/machinery/door/airlock/alarmlock/New() + ..() + air_connection = new + +/obj/machinery/door/airlock/alarmlock/Destroy() + if(SSradio) + SSradio.remove_object(src,air_frequency) + air_connection = null + return ..() + +/obj/machinery/door/airlock/alarmlock/Initialize() + ..() + SSradio.remove_object(src, air_frequency) + air_connection = SSradio.add_object(src, air_frequency, RADIO_TO_AIRALARM) + open() + +/obj/machinery/door/airlock/alarmlock/receive_signal(datum/signal/signal) + ..() + if(stat & (NOPOWER|BROKEN)) + return + + var/alarm_area = signal.data["zone"] + var/alert = signal.data["alert"] + + var/area/our_area = get_area(src) + + if(alarm_area == our_area.name) + switch(alert) + if("severe") + autoclose = 1 + close() + if("minor", "clear") + autoclose = 0 + open() diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index 9b1481fe739..dbc76ade250 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -1,504 +1,504 @@ -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Small Fonts" - -/////////////////////////////////////////////////////////////////////////////////////////////// -// Brig Door control displays. -// Description: This is a controls the timer for the brig doors, displays the timer on itself and -// has a popup window when used, allowing to set the timer. -// Code Notes: Combination of old brigdoor.dm code from rev4407 and the status_display.dm code -// Date: 01/September/2010 -// Programmer: Veryinky -///////////////////////////////////////////////////////////////////////////////////////////////// -/obj/machinery/door_timer - name = "door timer" - icon = 'icons/obj/status_display.dmi' - icon_state = "frame" - desc = "A remote control for a door." - req_access = list(ACCESS_BRIG) - anchored = 1 // can't pick it up - density = 0 // can walk through it. - var/id = null // id of door it controls. - var/releasetime = 0 // when world.timeofday reaches it - release the prisoner - var/timing = 0 // boolean, true/1 timer is on, false/0 means it's not timing - var/picture_state // icon_state of alert picture, if not displaying text/numbers - var/list/obj/machinery/targets = list() - var/timetoset = 0 // Used to set releasetime upon starting the timer - var/obj/item/radio/Radio - var/printed = 0 - var/datum/data/record/prisoner - maptext_height = 26 - maptext_width = 32 - maptext_y = -1 - var/occupant = "None" - var/crimes = "None" - var/time = 0 - var/officer = "None" - -/obj/machinery/door_timer/New() - GLOB.celltimers_list += src - return ..() - -/obj/machinery/door_timer/Destroy() - GLOB.celltimers_list -= src - return ..() - -/obj/machinery/door_timer/proc/print_report() - var/logname = input(usr, "Name of the guilty?","[id] log name") - var/logcharges = stripped_multiline_input(usr, "What have they been charged with?","[id] log charges") - - if(!logname || !logcharges) - return 0 - occupant = logname - crimes = logcharges - time = timetoset - officer = usr.name - - for(var/obj/machinery/computer/prisoner/C in GLOB.prisoncomputer_list) - var/obj/item/paper/P = new /obj/item/paper(C.loc) - P.name = "[id] log - [logname] [station_time_timestamp()]" - P.info = "
        [id] - Brig record



        " - P.info += {"
        [station_name()] - Security Department

        -
        Admission data:

        - Log generated at: [station_time_timestamp()]
        - Detainee: [logname]
        - Duration: [seconds_to_time(timetoset / 10)]
        - Charge(s): [logcharges]
        - Arresting Officer: [usr.name]


        - This log file was generated automatically upon activation of a cell timer."} - - playsound(C.loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) - GLOB.cell_logs += P - - var/datum/data/record/G = find_record("name", logname, data_core.general) - var/prisoner_drank = "unknown" - var/prisoner_trank = "unknown" - if(G) - if(G.fields["rank"]) - prisoner_drank = G.fields["rank"] - if(G.fields["real_rank"]) // Ignore alt job titles - necessary for lookups - prisoner_trank = G.fields["real_rank"] - - var/datum/data/record/R = find_security_record("name", logname) - - var/announcetext = "Detainee [logname] ([prisoner_drank]) has been incarcerated for [seconds_to_time(timetoset / 10)] for the charges of, '[logcharges]'. \ - Arresting Officer: [usr.name].[R ? "" : " Detainee record not found, manual record update required."]" - Radio.autosay(announcetext, name, "Security", list(z)) - - if(prisoner_trank != "unknown") - notify_dept_head(prisoner_trank, announcetext) - - if(R) - prisoner = R - R.fields["criminal"] = "Incarcerated" - var/mob/living/carbon/human/M = usr - var/rank = "UNKNOWN RANK" - if(istype(M) && M.wear_id) - var/obj/item/card/id/I = M.wear_id - rank = I.assignment - if(!R.fields["comments"] || !islist(R.fields["comments"])) //copied from security computer code because apparently these need to be initialized - R.fields["comments"] = list() - R.fields["comments"] += "Autogenerated by [name] on [current_date_string] [station_time_timestamp()]
        Sentenced to [timetoset/10] seconds for the charges of \"[logcharges]\" by [rank] [usr.name]." - update_all_mob_security_hud() - return 1 - - -/obj/machinery/door_timer/proc/notify_dept_head(jobtitle, antext) - if(!jobtitle || !antext) - return - if(jobtitle == "Civilian") - // Don't notify the HoP about greytiding civilians - return - var/datum/job/brigged_job = SSjobs.GetJob(jobtitle) - if(!brigged_job) - return - if(!brigged_job.department_head[1]) - return - var/boss_title = brigged_job.department_head[1] - - var/obj/item/pda/target_pda - for(var/obj/item/pda/check_pda in PDAs) - if(check_pda.ownrank == boss_title) - target_pda = check_pda - if(!target_pda) - return - var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger) - if(PM && PM.can_receive()) - PM.notify("Message from Brig Timer (Automated), \"[antext]\" (Unable to Reply)") - - -/obj/machinery/door_timer/Initialize() - ..() - - Radio = new /obj/item/radio(src) - Radio.listening = 0 - Radio.config(list("Security" = 0)) - Radio.follow_target = src - - pixel_x = ((dir & 3)? (0) : (dir == 4 ? 32 : -32)) - pixel_y = ((dir & 3)? (dir ==1 ? 32 : -32) : (0)) - - spawn(20) - for(var/obj/machinery/door/window/brigdoor/M in GLOB.airlocks) - if(M.id == id) - targets += M - - for(var/obj/machinery/flasher/F in GLOB.machines) - if(F.id == id) - targets += F - - for(var/obj/structure/closet/secure_closet/brig/C in world) - if(C.id == id) - targets += C - - for(var/obj/machinery/treadmill_monitor/T in GLOB.machines) - if(T.id == id) - targets += T - - if(targets.len==0) - stat |= BROKEN - update_icon() - -/obj/machinery/door_timer/Destroy() - QDEL_NULL(Radio) - targets.Cut() - prisoner = null - return ..() - -//Main door timer loop, if it's timing and time is >0 reduce time by 1. -// if it's less than 0, open door, reset timer -// update the door_timer window and the icon -/obj/machinery/door_timer/process() - if(stat & (NOPOWER|BROKEN)) - return - if(timing) - if(timeleft() <= 0) - Radio.autosay("Timer has expired. Releasing prisoner.", name, "Security", list(z)) - occupant = "None" - timer_end() // open doors, reset timer, clear status screen - timing = 0 - . = PROCESS_KILL - - updateUsrDialog() - update_icon() - else - timer_end() - return PROCESS_KILL - -// has the door power situation changed, if so update icon. -/obj/machinery/door_timer/power_change() - ..() - update_icon() - - -// open/closedoor checks if door_timer has power, if so it checks if the -// linked door is open/closed (by density) then opens it/closes it. - -// Closes and locks doors, power check -/obj/machinery/door_timer/proc/timer_start() - - if(stat & (NOPOWER|BROKEN)) - return 0 - - if(!printed) - if(!print_report()) - timing = 0 - return 0 - - // Set releasetime - releasetime = world.timeofday + timetoset - START_PROCESSING(SSmachines, src) - - for(var/obj/machinery/door/window/brigdoor/door in targets) - if(door.density) - continue - spawn(0) - door.close() - - for(var/obj/structure/closet/secure_closet/brig/C in targets) - if(C.broken) - continue - if(C.opened && !C.close()) - continue - C.locked = 1 - C.icon_state = C.icon_locked - - for(var/obj/machinery/treadmill_monitor/T in targets) - T.total_joules = 0 - T.on = 1 - - return 1 - - -// Opens and unlocks doors, power check -/obj/machinery/door_timer/proc/timer_end() - if(stat & (NOPOWER|BROKEN)) - return 0 - - // Reset vars - occupant = "None" - crimes = "None" - time = 0 - officer = "None" - releasetime = 0 - printed = 0 - if(prisoner) - prisoner.fields["criminal"] = "Released" - update_all_mob_security_hud() - prisoner = null - - for(var/obj/machinery/door/window/brigdoor/door in targets) - if(!door.density) - continue - spawn(0) - door.open() - - for(var/obj/structure/closet/secure_closet/brig/C in targets) - if(C.broken) - continue - if(C.opened) - continue - C.locked = 0 - C.icon_state = C.icon_closed - - for(var/obj/machinery/treadmill_monitor/T in targets) - if(!T.stat) - T.redeem() - T.on = 0 - - return 1 - - -// Check for releasetime timeleft -/obj/machinery/door_timer/proc/timeleft() - var/time = releasetime - world.timeofday - if(time > MIDNIGHT_ROLLOVER / 2) - time -= MIDNIGHT_ROLLOVER - if(time < 0) - return 0 - return time / 10 - -// Set timetoset -/obj/machinery/door_timer/proc/timeset(seconds) - timetoset = seconds * 10 - - if(timetoset <= 0) - timetoset = 0 - - return - -//Allows AIs to use door_timer, see human attack_hand function below -/obj/machinery/door_timer/attack_ai(mob/user) - interact(user) - -/obj/machinery/door_timer/attack_ghost(mob/user) - interact(user) - -//Allows humans to use door_timer -//Opens dialog window when someone clicks on door timer -// Allows altering timer and the timing boolean. -// Flasher activation limited to 150 seconds -/obj/machinery/door_timer/attack_hand(mob/user) - if(..()) - return - interact(user) - -/obj/machinery/door_timer/interact(mob/user) - // Used for the 'time left' display - var/second = round(timeleft() % 60) - var/minute = round((timeleft() - second) / 60) - - // Used for 'set timer' - var/setsecond = round((timetoset / 10) % 60) - var/setminute = round(((timetoset / 10) - setsecond) / 60) - - user.set_machine(src) - - // dat - var/dat = "
        Timer System:" - dat += " Door [id] controls
        " - - // Start/Stop timer - if(timing) - dat += "Stop Timer and open door
        " - else - dat += "Activate Timer and close door
        " - - // Time Left display (uses releasetime) - dat += "Time Left: [(minute ? text("[minute]:") : null)][second]
        " - dat += "
        " - - // Set Timer display (uses timetoset) - if(timing) - dat += "Set Timer: [(setminute ? text("[setminute]:") : null)][setsecond] Set
        " - else - dat += "Set Timer: [(setminute ? text("[setminute]:") : null)][setsecond]
        " - - // Controls - dat += "Input Time" - - // Mounted flash controls - for(var/obj/machinery/flasher/F in targets) - if(F.last_flash && (F.last_flash + 150) > world.time) - dat += "
        Flash Charging" - else - dat += "
        Activate Flash" - - dat += "

        Close" - - var/datum/browser/popup = new(user, "door_timer", name, 400, 500) - popup.set_content(dat) - popup.open() - - -//Function for using door_timer dialog input, checks if user has permission -// href_list to -// "timing" turns on timer -// "tp" value to modify timer -// "fc" activates flasher -// "change" resets the timer to the timetoset amount while the timer is counting down -// Also updates dialog window and timer icon -/obj/machinery/door_timer/Topic(href, href_list) - if(..()) - return 1 - - if(!allowed(usr) && !usr.can_admin_interact()) - return 1 - - usr.set_machine(src) - - if(href_list["timing"]) - timing = text2num(href_list["timing"]) - - if(timing) - timer_start() - else - timer_end() - if(!isobserver(usr)) //spooky admin ghosts are in your brig, releasing your prisoners - Radio.autosay("Timer stopped manually by [usr.name].", name, "Security", list(z)) - - else - if(href_list["settime"]) - var/time = min(max(round(return_time_input(usr)), 0), 3600) - timeset(time) - - if(href_list["fc"]) - for(var/obj/machinery/flasher/F in targets) - F.flash() - - if(href_list["change"]) - printed = 1 - timer_start() - - add_fingerprint(usr) - updateUsrDialog() - update_icon() - - -//icon update function -// if NOPOWER, display blank -// if BROKEN, display blue screen of death icon AI uses -// if timing=true, run update display function -/obj/machinery/door_timer/update_icon() - if(stat & (NOPOWER)) - icon_state = "frame" - return - if(stat & (BROKEN)) - set_picture("ai_bsod") - return - if(timing) - var/disp1 = id - var/timeleft = timeleft() - var/disp2 = "[add_zero(num2text((timeleft / 60) % 60),2)]:[add_zero(num2text(timeleft % 60), 2)]" - if(length(disp2) > CHARS_PER_LINE) - disp2 = "Error" - update_display(disp1, disp2) - else - if(maptext) maptext = "" - - -// Adds an icon in case the screen is broken/off, stolen from status_display.dm -/obj/machinery/door_timer/proc/set_picture(state) - picture_state = state - overlays.Cut() - overlays += image('icons/obj/status_display.dmi', icon_state=picture_state) - -/obj/machinery/door_timer/proc/return_time_input() - var/mins = input(usr, "Minutes", "Enter number of minutes", 0) as num - var/seconds = input(usr, "Seconds", "Enter number of seconds", 0) as num - var/totaltime = (seconds + (mins * 60)) - return totaltime - -//Checks to see if there's 1 line or 2, adds text-icons-numbers/letters over display -// Stolen from status_display -/obj/machinery/door_timer/proc/update_display(line1, line2) - line1 = uppertext(line1) - line2 = uppertext(line2) - var/new_text = {"
        [line1]
        [line2]
        "} - if(maptext != new_text) - maptext = new_text - - -//Actual string input to icon display for loop, with 5 pixel x offsets for each letter. -//Stolen from status_display -/obj/machinery/door_timer/proc/texticon(tn, px = 0, py = 0) - var/image/I = image('icons/obj/status_display.dmi', "blank") - var/len = length(tn) - - for(var/d = 1 to len) - var/char = copytext(tn, len-d+1, len-d+2) - if(char == " ") - continue - var/image/ID = image('icons/obj/status_display.dmi', icon_state=char) - ID.pixel_x = -(d-1)*5 + px - ID.pixel_y = py - I.overlays += ID - return I - - -/obj/machinery/door_timer/cell_1 - name = "Cell 1" - id = "Cell 1" - dir = 2 - pixel_y = -32 - - -/obj/machinery/door_timer/cell_2 - name = "Cell 2" - id = "Cell 2" - dir = 2 - pixel_y = -32 - - -/obj/machinery/door_timer/cell_3 - name = "Cell 3" - id = "Cell 3" - dir = 2 - pixel_y = -32 - - -/obj/machinery/door_timer/cell_4 - name = "Cell 4" - id = "Cell 4" - dir = 2 - pixel_y = -32 - - -/obj/machinery/door_timer/cell_5 - name = "Cell 5" - id = "Cell 5" - dir = 2 - pixel_y = -32 - - -/obj/machinery/door_timer/cell_6 - name = "Cell 6" - id = "Cell 6" - dir = 4 - pixel_x = 32 - -#undef FONT_SIZE -#undef FONT_COLOR -#undef FONT_STYLE -#undef CHARS_PER_LINE +#define CHARS_PER_LINE 5 +#define FONT_SIZE "5pt" +#define FONT_COLOR "#09f" +#define FONT_STYLE "Small Fonts" + +/////////////////////////////////////////////////////////////////////////////////////////////// +// Brig Door control displays. +// Description: This is a controls the timer for the brig doors, displays the timer on itself and +// has a popup window when used, allowing to set the timer. +// Code Notes: Combination of old brigdoor.dm code from rev4407 and the status_display.dm code +// Date: 01/September/2010 +// Programmer: Veryinky +///////////////////////////////////////////////////////////////////////////////////////////////// +/obj/machinery/door_timer + name = "door timer" + icon = 'icons/obj/status_display.dmi' + icon_state = "frame" + desc = "A remote control for a door." + req_access = list(ACCESS_BRIG) + anchored = 1 // can't pick it up + density = 0 // can walk through it. + var/id = null // id of door it controls. + var/releasetime = 0 // when world.timeofday reaches it - release the prisoner + var/timing = 0 // boolean, true/1 timer is on, false/0 means it's not timing + var/picture_state // icon_state of alert picture, if not displaying text/numbers + var/list/obj/machinery/targets = list() + var/timetoset = 0 // Used to set releasetime upon starting the timer + var/obj/item/radio/Radio + var/printed = 0 + var/datum/data/record/prisoner + maptext_height = 26 + maptext_width = 32 + maptext_y = -1 + var/occupant = "None" + var/crimes = "None" + var/time = 0 + var/officer = "None" + +/obj/machinery/door_timer/New() + GLOB.celltimers_list += src + return ..() + +/obj/machinery/door_timer/Destroy() + GLOB.celltimers_list -= src + return ..() + +/obj/machinery/door_timer/proc/print_report() + var/logname = input(usr, "Name of the guilty?","[id] log name") + var/logcharges = stripped_multiline_input(usr, "What have they been charged with?","[id] log charges") + + if(!logname || !logcharges) + return 0 + occupant = logname + crimes = logcharges + time = timetoset + officer = usr.name + + for(var/obj/machinery/computer/prisoner/C in GLOB.prisoncomputer_list) + var/obj/item/paper/P = new /obj/item/paper(C.loc) + P.name = "[id] log - [logname] [station_time_timestamp()]" + P.info = "
        [id] - Brig record



        " + P.info += {"
        [station_name()] - Security Department

        +
        Admission data:

        + Log generated at: [station_time_timestamp()]
        + Detainee: [logname]
        + Duration: [seconds_to_time(timetoset / 10)]
        + Charge(s): [logcharges]
        + Arresting Officer: [usr.name]


        + This log file was generated automatically upon activation of a cell timer."} + + playsound(C.loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) + GLOB.cell_logs += P + + var/datum/data/record/G = find_record("name", logname, data_core.general) + var/prisoner_drank = "unknown" + var/prisoner_trank = "unknown" + if(G) + if(G.fields["rank"]) + prisoner_drank = G.fields["rank"] + if(G.fields["real_rank"]) // Ignore alt job titles - necessary for lookups + prisoner_trank = G.fields["real_rank"] + + var/datum/data/record/R = find_security_record("name", logname) + + var/announcetext = "Detainee [logname] ([prisoner_drank]) has been incarcerated for [seconds_to_time(timetoset / 10)] for the charges of, '[logcharges]'. \ + Arresting Officer: [usr.name].[R ? "" : " Detainee record not found, manual record update required."]" + Radio.autosay(announcetext, name, "Security", list(z)) + + if(prisoner_trank != "unknown") + notify_dept_head(prisoner_trank, announcetext) + + if(R) + prisoner = R + R.fields["criminal"] = "Incarcerated" + var/mob/living/carbon/human/M = usr + var/rank = "UNKNOWN RANK" + if(istype(M) && M.wear_id) + var/obj/item/card/id/I = M.wear_id + rank = I.assignment + if(!R.fields["comments"] || !islist(R.fields["comments"])) //copied from security computer code because apparently these need to be initialized + R.fields["comments"] = list() + R.fields["comments"] += "Autogenerated by [name] on [current_date_string] [station_time_timestamp()]
        Sentenced to [timetoset/10] seconds for the charges of \"[logcharges]\" by [rank] [usr.name]." + update_all_mob_security_hud() + return 1 + + +/obj/machinery/door_timer/proc/notify_dept_head(jobtitle, antext) + if(!jobtitle || !antext) + return + if(jobtitle == "Civilian") + // Don't notify the HoP about greytiding civilians + return + var/datum/job/brigged_job = SSjobs.GetJob(jobtitle) + if(!brigged_job) + return + if(!brigged_job.department_head[1]) + return + var/boss_title = brigged_job.department_head[1] + + var/obj/item/pda/target_pda + for(var/obj/item/pda/check_pda in PDAs) + if(check_pda.ownrank == boss_title) + target_pda = check_pda + if(!target_pda) + return + var/datum/data/pda/app/messenger/PM = target_pda.find_program(/datum/data/pda/app/messenger) + if(PM && PM.can_receive()) + PM.notify("Message from Brig Timer (Automated), \"[antext]\" (Unable to Reply)") + + +/obj/machinery/door_timer/Initialize() + ..() + + Radio = new /obj/item/radio(src) + Radio.listening = 0 + Radio.config(list("Security" = 0)) + Radio.follow_target = src + + pixel_x = ((dir & 3)? (0) : (dir == 4 ? 32 : -32)) + pixel_y = ((dir & 3)? (dir ==1 ? 32 : -32) : (0)) + + spawn(20) + for(var/obj/machinery/door/window/brigdoor/M in GLOB.airlocks) + if(M.id == id) + targets += M + + for(var/obj/machinery/flasher/F in GLOB.machines) + if(F.id == id) + targets += F + + for(var/obj/structure/closet/secure_closet/brig/C in world) + if(C.id == id) + targets += C + + for(var/obj/machinery/treadmill_monitor/T in GLOB.machines) + if(T.id == id) + targets += T + + if(targets.len==0) + stat |= BROKEN + update_icon() + +/obj/machinery/door_timer/Destroy() + QDEL_NULL(Radio) + targets.Cut() + prisoner = null + return ..() + +//Main door timer loop, if it's timing and time is >0 reduce time by 1. +// if it's less than 0, open door, reset timer +// update the door_timer window and the icon +/obj/machinery/door_timer/process() + if(stat & (NOPOWER|BROKEN)) + return + if(timing) + if(timeleft() <= 0) + Radio.autosay("Timer has expired. Releasing prisoner.", name, "Security", list(z)) + occupant = "None" + timer_end() // open doors, reset timer, clear status screen + timing = 0 + . = PROCESS_KILL + + updateUsrDialog() + update_icon() + else + timer_end() + return PROCESS_KILL + +// has the door power situation changed, if so update icon. +/obj/machinery/door_timer/power_change() + ..() + update_icon() + + +// open/closedoor checks if door_timer has power, if so it checks if the +// linked door is open/closed (by density) then opens it/closes it. + +// Closes and locks doors, power check +/obj/machinery/door_timer/proc/timer_start() + + if(stat & (NOPOWER|BROKEN)) + return 0 + + if(!printed) + if(!print_report()) + timing = 0 + return 0 + + // Set releasetime + releasetime = world.timeofday + timetoset + START_PROCESSING(SSmachines, src) + + for(var/obj/machinery/door/window/brigdoor/door in targets) + if(door.density) + continue + spawn(0) + door.close() + + for(var/obj/structure/closet/secure_closet/brig/C in targets) + if(C.broken) + continue + if(C.opened && !C.close()) + continue + C.locked = 1 + C.icon_state = C.icon_locked + + for(var/obj/machinery/treadmill_monitor/T in targets) + T.total_joules = 0 + T.on = 1 + + return 1 + + +// Opens and unlocks doors, power check +/obj/machinery/door_timer/proc/timer_end() + if(stat & (NOPOWER|BROKEN)) + return 0 + + // Reset vars + occupant = "None" + crimes = "None" + time = 0 + officer = "None" + releasetime = 0 + printed = 0 + if(prisoner) + prisoner.fields["criminal"] = "Released" + update_all_mob_security_hud() + prisoner = null + + for(var/obj/machinery/door/window/brigdoor/door in targets) + if(!door.density) + continue + spawn(0) + door.open() + + for(var/obj/structure/closet/secure_closet/brig/C in targets) + if(C.broken) + continue + if(C.opened) + continue + C.locked = 0 + C.icon_state = C.icon_closed + + for(var/obj/machinery/treadmill_monitor/T in targets) + if(!T.stat) + T.redeem() + T.on = 0 + + return 1 + + +// Check for releasetime timeleft +/obj/machinery/door_timer/proc/timeleft() + var/time = releasetime - world.timeofday + if(time > MIDNIGHT_ROLLOVER / 2) + time -= MIDNIGHT_ROLLOVER + if(time < 0) + return 0 + return time / 10 + +// Set timetoset +/obj/machinery/door_timer/proc/timeset(seconds) + timetoset = seconds * 10 + + if(timetoset <= 0) + timetoset = 0 + + return + +//Allows AIs to use door_timer, see human attack_hand function below +/obj/machinery/door_timer/attack_ai(mob/user) + interact(user) + +/obj/machinery/door_timer/attack_ghost(mob/user) + interact(user) + +//Allows humans to use door_timer +//Opens dialog window when someone clicks on door timer +// Allows altering timer and the timing boolean. +// Flasher activation limited to 150 seconds +/obj/machinery/door_timer/attack_hand(mob/user) + if(..()) + return + interact(user) + +/obj/machinery/door_timer/interact(mob/user) + // Used for the 'time left' display + var/second = round(timeleft() % 60) + var/minute = round((timeleft() - second) / 60) + + // Used for 'set timer' + var/setsecond = round((timetoset / 10) % 60) + var/setminute = round(((timetoset / 10) - setsecond) / 60) + + user.set_machine(src) + + // dat + var/dat = "
        Timer System:" + dat += " Door [id] controls
        " + + // Start/Stop timer + if(timing) + dat += "Stop Timer and open door
        " + else + dat += "Activate Timer and close door
        " + + // Time Left display (uses releasetime) + dat += "Time Left: [(minute ? text("[minute]:") : null)][second]
        " + dat += "
        " + + // Set Timer display (uses timetoset) + if(timing) + dat += "Set Timer: [(setminute ? text("[setminute]:") : null)][setsecond] Set
        " + else + dat += "Set Timer: [(setminute ? text("[setminute]:") : null)][setsecond]
        " + + // Controls + dat += "Input Time" + + // Mounted flash controls + for(var/obj/machinery/flasher/F in targets) + if(F.last_flash && (F.last_flash + 150) > world.time) + dat += "
        Flash Charging" + else + dat += "
        Activate Flash" + + dat += "

        Close" + + var/datum/browser/popup = new(user, "door_timer", name, 400, 500) + popup.set_content(dat) + popup.open() + + +//Function for using door_timer dialog input, checks if user has permission +// href_list to +// "timing" turns on timer +// "tp" value to modify timer +// "fc" activates flasher +// "change" resets the timer to the timetoset amount while the timer is counting down +// Also updates dialog window and timer icon +/obj/machinery/door_timer/Topic(href, href_list) + if(..()) + return 1 + + if(!allowed(usr) && !usr.can_admin_interact()) + return 1 + + usr.set_machine(src) + + if(href_list["timing"]) + timing = text2num(href_list["timing"]) + + if(timing) + timer_start() + else + timer_end() + if(!isobserver(usr)) //spooky admin ghosts are in your brig, releasing your prisoners + Radio.autosay("Timer stopped manually by [usr.name].", name, "Security", list(z)) + + else + if(href_list["settime"]) + var/time = min(max(round(return_time_input(usr)), 0), 3600) + timeset(time) + + if(href_list["fc"]) + for(var/obj/machinery/flasher/F in targets) + F.flash() + + if(href_list["change"]) + printed = 1 + timer_start() + + add_fingerprint(usr) + updateUsrDialog() + update_icon() + + +//icon update function +// if NOPOWER, display blank +// if BROKEN, display blue screen of death icon AI uses +// if timing=true, run update display function +/obj/machinery/door_timer/update_icon() + if(stat & (NOPOWER)) + icon_state = "frame" + return + if(stat & (BROKEN)) + set_picture("ai_bsod") + return + if(timing) + var/disp1 = id + var/timeleft = timeleft() + var/disp2 = "[add_zero(num2text((timeleft / 60) % 60),2)]:[add_zero(num2text(timeleft % 60), 2)]" + if(length(disp2) > CHARS_PER_LINE) + disp2 = "Error" + update_display(disp1, disp2) + else + if(maptext) maptext = "" + + +// Adds an icon in case the screen is broken/off, stolen from status_display.dm +/obj/machinery/door_timer/proc/set_picture(state) + picture_state = state + overlays.Cut() + overlays += image('icons/obj/status_display.dmi', icon_state=picture_state) + +/obj/machinery/door_timer/proc/return_time_input() + var/mins = input(usr, "Minutes", "Enter number of minutes", 0) as num + var/seconds = input(usr, "Seconds", "Enter number of seconds", 0) as num + var/totaltime = (seconds + (mins * 60)) + return totaltime + +//Checks to see if there's 1 line or 2, adds text-icons-numbers/letters over display +// Stolen from status_display +/obj/machinery/door_timer/proc/update_display(line1, line2) + line1 = uppertext(line1) + line2 = uppertext(line2) + var/new_text = {"
        [line1]
        [line2]
        "} + if(maptext != new_text) + maptext = new_text + + +//Actual string input to icon display for loop, with 5 pixel x offsets for each letter. +//Stolen from status_display +/obj/machinery/door_timer/proc/texticon(tn, px = 0, py = 0) + var/image/I = image('icons/obj/status_display.dmi', "blank") + var/len = length(tn) + + for(var/d = 1 to len) + var/char = copytext(tn, len-d+1, len-d+2) + if(char == " ") + continue + var/image/ID = image('icons/obj/status_display.dmi', icon_state=char) + ID.pixel_x = -(d-1)*5 + px + ID.pixel_y = py + I.overlays += ID + return I + + +/obj/machinery/door_timer/cell_1 + name = "Cell 1" + id = "Cell 1" + dir = 2 + pixel_y = -32 + + +/obj/machinery/door_timer/cell_2 + name = "Cell 2" + id = "Cell 2" + dir = 2 + pixel_y = -32 + + +/obj/machinery/door_timer/cell_3 + name = "Cell 3" + id = "Cell 3" + dir = 2 + pixel_y = -32 + + +/obj/machinery/door_timer/cell_4 + name = "Cell 4" + id = "Cell 4" + dir = 2 + pixel_y = -32 + + +/obj/machinery/door_timer/cell_5 + name = "Cell 5" + id = "Cell 5" + dir = 2 + pixel_y = -32 + + +/obj/machinery/door_timer/cell_6 + name = "Cell 6" + id = "Cell 6" + dir = 4 + pixel_x = 32 + +#undef FONT_SIZE +#undef FONT_COLOR +#undef FONT_STYLE +#undef CHARS_PER_LINE diff --git a/code/game/machinery/doors/checkForMultipleDoors.dm b/code/game/machinery/doors/checkForMultipleDoors.dm index d1c3c2e2d6b..d7f66f4630b 100644 --- a/code/game/machinery/doors/checkForMultipleDoors.dm +++ b/code/game/machinery/doors/checkForMultipleDoors.dm @@ -1,16 +1,16 @@ -/obj/machinery/door/proc/checkForMultipleDoors() - if(!loc) - return 0 - for(var/obj/machinery/door/D in loc) - if(!istype(D, /obj/machinery/door/window) && D.density) - return 0 - return 1 - -/turf/simulated/wall/proc/checkForMultipleDoors() - if(!loc) - return 0 - for(var/obj/machinery/door/D in locate(x,y,z)) - if(!istype(D, /obj/machinery/door/window) && D.density) - return 0 - //There are no false wall checks because that would be fucking retarded - return 1 \ No newline at end of file +/obj/machinery/door/proc/checkForMultipleDoors() + if(!loc) + return 0 + for(var/obj/machinery/door/D in loc) + if(!istype(D, /obj/machinery/door/window) && D.density) + return 0 + return 1 + +/turf/simulated/wall/proc/checkForMultipleDoors() + if(!loc) + return 0 + for(var/obj/machinery/door/D in locate(x,y,z)) + if(!istype(D, /obj/machinery/door/window) && D.density) + return 0 + //There are no false wall checks because that would be fucking retarded + return 1 diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 14d1de97327..718efdb414c 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -1,386 +1,386 @@ -/obj/machinery/door - name = "door" - desc = "It opens and closes." - icon = 'icons/obj/doors/doorint.dmi' - icon_state = "door1" - anchored = TRUE - opacity = 1 - density = TRUE - layer = OPEN_DOOR_LAYER - power_channel = ENVIRON - max_integrity = 350 - armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70) - flags = PREVENT_CLICK_UNDER - damage_deflection = 10 - var/closingLayer = CLOSED_DOOR_LAYER - var/visible = 1 - var/operating = FALSE - var/autoclose = 0 - var/safe = TRUE //whether the door detects things and mobs in its way and reopen or crushes them. - var/locked = FALSE //whether the door is bolted or not. - var/glass = FALSE - var/welded = FALSE - var/normalspeed = 1 - var/auto_close_time = 150 - var/auto_close_time_dangerous = 15 - var/assemblytype //the type of door frame to drop during deconstruction - var/datum/effect_system/spark_spread/spark_system - var/real_explosion_block //ignore this, just use explosion_block - var/heat_proof = FALSE // For rglass-windowed airlocks and firedoors - var/emergency = FALSE - var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access - //Multi-tile doors - var/width = 1 - -/obj/machinery/door/New() - ..() - set_init_door_layer() - update_dir() - update_freelook_sight() - GLOB.airlocks += src - spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(2, 1, src) - - //doors only block while dense though so we have to use the proc - real_explosion_block = explosion_block - explosion_block = EXPLOSION_BLOCK_PROC - -/obj/machinery/door/proc/set_init_door_layer() - if(density) - layer = closingLayer - else - layer = initial(layer) - -/obj/machinery/door/setDir(newdir) - ..() - update_dir() - -/obj/machinery/door/power_change() - ..() - update_icon() - -/obj/machinery/door/proc/update_dir() - if(width > 1) - if(dir in list(EAST, WEST)) - bound_width = width * world.icon_size - bound_height = world.icon_size - else - bound_width = world.icon_size - bound_height = width * world.icon_size - -/obj/machinery/door/Initialize() - air_update_turf(1) - ..() - -/obj/machinery/door/Destroy() - density = 0 - air_update_turf(1) - update_freelook_sight() - GLOB.airlocks -= src - QDEL_NULL(spark_system) - return ..() - -/obj/machinery/door/Bumped(atom/AM) - if(operating || emagged) - return - if(ismob(AM)) - var/mob/B = AM - if((isrobot(B)) && B.stat) - return - if(isliving(AM)) - var/mob/living/M = AM - if(world.time - M.last_bumped <= 10) - return //Can bump-open one airlock per second. This is to prevent shock spam. - M.last_bumped = world.time - if(M.restrained() && !check_access(null)) - return - if(M.mob_size > MOB_SIZE_TINY) - bumpopen(M) - return - - if(ismecha(AM)) - var/obj/mecha/mecha = AM - if(density) - if(mecha.occupant) - if(world.time - mecha.occupant.last_bumped <= 10) - return - if(mecha.occupant && allowed(mecha.occupant) || check_access_list(mecha.operation_req_access)) - open() - else - do_animate("deny") - return - -/obj/machinery/door/Move(new_loc, new_dir) - var/turf/T = loc - . = ..() - move_update_air(T) - - if(width > 1) - if(dir in list(EAST, WEST)) - bound_width = width * world.icon_size - bound_height = world.icon_size - else - bound_width = world.icon_size - bound_height = width * world.icon_size - -/obj/machinery/door/CanPass(atom/movable/mover, turf/target, height=0) - if(istype(mover) && mover.checkpass(PASSGLASS)) - return !opacity - return !density - -/obj/machinery/door/CanAtmosPass() - return !density - -/obj/machinery/door/proc/bumpopen(mob/user) - if(operating) - return - add_fingerprint(user) - - if(density && !emagged) - if(allowed(user)) - open() - if(isbot(user)) - var/mob/living/simple_animal/bot/B = user - B.door_opened(src) - else - do_animate("deny") - -/obj/machinery/door/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/door/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - return attack_hand(user) - -/obj/machinery/door/attack_hand(mob/user) - return try_to_activate_door(user) - -/obj/machinery/door/attack_tk(mob/user) - if(!allowed(null)) - return - ..() - -/obj/machinery/door/proc/try_to_activate_door(mob/user) - add_fingerprint(user) - if(operating || emagged) - return - if(requiresID() && (allowed(user) || user.can_advanced_admin_interact())) - if(density) - open() - else - close() - return - if(density) - do_animate("deny") - -/obj/machinery/door/allowed(mob/M) - if(emergency) - return TRUE - if(unrestricted_side(M)) - return TRUE - if(!requiresID()) - return FALSE // Intentional. machinery/door/requiresID() always == 1. airlocks, however, == 0 if ID scan is disabled. Yes, this var is poorly named. - return ..() - -/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter) - return get_dir(src, M) & unres_sides - -/obj/machinery/door/proc/try_to_crowbar(mob/user, obj/item/I) - return - -/obj/machinery/door/attackby(obj/item/I, mob/user, params) - if(user.a_intent != INTENT_HARM && istype(I, /obj/item/twohanded/fireaxe)) - try_to_crowbar(user, I) - return 1 - else if(!(I.flags & NOBLUDGEON) && user.a_intent != INTENT_HARM) - try_to_activate_door(user) - return 1 - return ..() - - -/obj/machinery/door/crowbar_act(mob/user, obj/item/I) - if(user.a_intent == INTENT_HARM) - return - . = TRUE - if(operating) - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - try_to_crowbar(user, I) - -/obj/machinery/door/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(. && obj_integrity > 0) - if(damage_amount >= 10 && prob(30)) - spark_system.start() - -/obj/machinery/door/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(glass) - playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) - else if(damage_amount) - playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) - else - playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/machinery/door/emag_act(mob/user) - if(density) - flick("door_spark", src) - sleep(6) - open() - emagged = 1 - return 1 - -/obj/machinery/door/emp_act(severity) - if(prob(20/severity) && (istype(src,/obj/machinery/door/airlock) || istype(src,/obj/machinery/door/window)) ) - spawn(0) - open() - ..() - -/obj/machinery/door/update_icon() - if(density) - icon_state = "door1" - else - icon_state = "door0" - -/obj/machinery/door/proc/do_animate(animation) - switch(animation) - if("opening") - if(panel_open) - flick("o_doorc0", src) - else - flick("doorc0", src) - if("closing") - if(panel_open) - flick("o_doorc1", src) - else - flick("doorc1", src) - if("deny") - if(!stat) - flick("door_deny", src) - -/obj/machinery/door/proc/open() - if(!density) - return TRUE - if(operating) - return - operating = TRUE - do_animate("opening") - set_opacity(0) - sleep(5) - density = FALSE - sleep(5) - layer = initial(layer) - update_icon() - set_opacity(0) - operating = FALSE - air_update_turf(1) - update_freelook_sight() - if(autoclose) - autoclose_in(normalspeed ? auto_close_time : auto_close_time_dangerous) - return TRUE - -/obj/machinery/door/proc/close() - if(density) - return TRUE - if(operating || welded) - return - if(safe) - for(var/turf/turf in locs) - for(var/atom/movable/M in turf) - if(M.density && M != src) //something is blocking the door - if(autoclose) - autoclose_in(60) - return - - operating = TRUE - - do_animate("closing") - layer = closingLayer - sleep(5) - density = TRUE - sleep(5) - update_icon() - if(visible && !glass) - set_opacity(1) - operating = 0 - air_update_turf(1) - update_freelook_sight() - if(safe) - CheckForMobs() - else - crush() - return TRUE - -/obj/machinery/door/proc/CheckForMobs() - if(locate(/mob/living) in get_turf(src)) - sleep(1) - open() - -/obj/machinery/door/proc/crush() - for(var/mob/living/L in get_turf(src)) - L.visible_message("[src] closes on [L], crushing [L.p_them()]!", "[src] closes on you and crushes you!") - if(isalien(L)) //For xenos - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 1.5) //Xenos go into crit after aproximately the same amount of crushes as humans. - L.emote("roar") - else if(ishuman(L)) //For humans - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - if(L.stat == CONSCIOUS) - L.emote("scream") - L.Weaken(5) - else //for simple_animals & borgs - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - var/turf/location = get_turf(src) - L.add_splatter_floor(location) - for(var/obj/mecha/M in get_turf(src)) - M.take_damage(DOOR_CRUSH_DAMAGE) - -/obj/machinery/door/proc/requiresID() - return 1 - -/obj/machinery/door/proc/hasPower() - return !(stat & NOPOWER) - -/obj/machinery/door/proc/autoclose() - if(!QDELETED(src) && !density && !operating && !locked && !welded && autoclose) - close() - -/obj/machinery/door/proc/autoclose_in(wait) - addtimer(CALLBACK(src, .proc/autoclose), wait, TIMER_UNIQUE | TIMER_NO_HASH_WAIT | TIMER_OVERRIDE) - -/obj/machinery/door/proc/update_freelook_sight() - if(!glass && cameranet) - cameranet.updateVisibility(src, 0) - -/obj/machinery/door/BlockSuperconductivity() // All non-glass airlocks block heat, this is intended. - if(opacity || heat_proof) - return 1 - return 0 - -/obj/machinery/door/morgue - icon = 'icons/obj/doors/doormorgue.dmi' - -/obj/machinery/door/proc/lock() - return - -/obj/machinery/door/proc/unlock() - return - -/obj/machinery/door/proc/hostile_lockdown(mob/origin) - if(!stat) //So that only powered doors are closed. - close() //Close ALL the doors! - -/obj/machinery/door/proc/disable_lockdown() - if(!stat) //Opens only powered doors. - open() //Open everything! - -/obj/machinery/door/ex_act(severity) - //if it blows up a wall it should blow up a door - ..(severity ? max(1, severity - 1) : 0) - - -/obj/machinery/door/GetExplosionBlock() - return density ? real_explosion_block : 0 +/obj/machinery/door + name = "door" + desc = "It opens and closes." + icon = 'icons/obj/doors/doorint.dmi' + icon_state = "door1" + anchored = TRUE + opacity = 1 + density = TRUE + layer = OPEN_DOOR_LAYER + power_channel = ENVIRON + max_integrity = 350 + armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 70) + flags = PREVENT_CLICK_UNDER + damage_deflection = 10 + var/closingLayer = CLOSED_DOOR_LAYER + var/visible = 1 + var/operating = FALSE + var/autoclose = 0 + var/safe = TRUE //whether the door detects things and mobs in its way and reopen or crushes them. + var/locked = FALSE //whether the door is bolted or not. + var/glass = FALSE + var/welded = FALSE + var/normalspeed = 1 + var/auto_close_time = 150 + var/auto_close_time_dangerous = 15 + var/assemblytype //the type of door frame to drop during deconstruction + var/datum/effect_system/spark_spread/spark_system + var/real_explosion_block //ignore this, just use explosion_block + var/heat_proof = FALSE // For rglass-windowed airlocks and firedoors + var/emergency = FALSE + var/unres_sides = 0 //Unrestricted sides. A bitflag for which direction (if any) can open the door with no access + //Multi-tile doors + var/width = 1 + +/obj/machinery/door/New() + ..() + set_init_door_layer() + update_dir() + update_freelook_sight() + GLOB.airlocks += src + spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(2, 1, src) + + //doors only block while dense though so we have to use the proc + real_explosion_block = explosion_block + explosion_block = EXPLOSION_BLOCK_PROC + +/obj/machinery/door/proc/set_init_door_layer() + if(density) + layer = closingLayer + else + layer = initial(layer) + +/obj/machinery/door/setDir(newdir) + ..() + update_dir() + +/obj/machinery/door/power_change() + ..() + update_icon() + +/obj/machinery/door/proc/update_dir() + if(width > 1) + if(dir in list(EAST, WEST)) + bound_width = width * world.icon_size + bound_height = world.icon_size + else + bound_width = world.icon_size + bound_height = width * world.icon_size + +/obj/machinery/door/Initialize() + air_update_turf(1) + ..() + +/obj/machinery/door/Destroy() + density = 0 + air_update_turf(1) + update_freelook_sight() + GLOB.airlocks -= src + QDEL_NULL(spark_system) + return ..() + +/obj/machinery/door/Bumped(atom/AM) + if(operating || emagged) + return + if(ismob(AM)) + var/mob/B = AM + if((isrobot(B)) && B.stat) + return + if(isliving(AM)) + var/mob/living/M = AM + if(world.time - M.last_bumped <= 10) + return //Can bump-open one airlock per second. This is to prevent shock spam. + M.last_bumped = world.time + if(M.restrained() && !check_access(null)) + return + if(M.mob_size > MOB_SIZE_TINY) + bumpopen(M) + return + + if(ismecha(AM)) + var/obj/mecha/mecha = AM + if(density) + if(mecha.occupant) + if(world.time - mecha.occupant.last_bumped <= 10) + return + if(mecha.occupant && allowed(mecha.occupant) || check_access_list(mecha.operation_req_access)) + open() + else + do_animate("deny") + return + +/obj/machinery/door/Move(new_loc, new_dir) + var/turf/T = loc + . = ..() + move_update_air(T) + + if(width > 1) + if(dir in list(EAST, WEST)) + bound_width = width * world.icon_size + bound_height = world.icon_size + else + bound_width = world.icon_size + bound_height = width * world.icon_size + +/obj/machinery/door/CanPass(atom/movable/mover, turf/target, height=0) + if(istype(mover) && mover.checkpass(PASSGLASS)) + return !opacity + return !density + +/obj/machinery/door/CanAtmosPass() + return !density + +/obj/machinery/door/proc/bumpopen(mob/user) + if(operating) + return + add_fingerprint(user) + + if(density && !emagged) + if(allowed(user)) + open() + if(isbot(user)) + var/mob/living/simple_animal/bot/B = user + B.door_opened(src) + else + do_animate("deny") + +/obj/machinery/door/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/door/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + return attack_hand(user) + +/obj/machinery/door/attack_hand(mob/user) + return try_to_activate_door(user) + +/obj/machinery/door/attack_tk(mob/user) + if(!allowed(null)) + return + ..() + +/obj/machinery/door/proc/try_to_activate_door(mob/user) + add_fingerprint(user) + if(operating || emagged) + return + if(requiresID() && (allowed(user) || user.can_advanced_admin_interact())) + if(density) + open() + else + close() + return + if(density) + do_animate("deny") + +/obj/machinery/door/allowed(mob/M) + if(emergency) + return TRUE + if(unrestricted_side(M)) + return TRUE + if(!requiresID()) + return FALSE // Intentional. machinery/door/requiresID() always == 1. airlocks, however, == 0 if ID scan is disabled. Yes, this var is poorly named. + return ..() + +/obj/machinery/door/proc/unrestricted_side(mob/M) //Allows for specific side of airlocks to be unrestrected (IE, can exit maint freely, but need access to enter) + return get_dir(src, M) & unres_sides + +/obj/machinery/door/proc/try_to_crowbar(mob/user, obj/item/I) + return + +/obj/machinery/door/attackby(obj/item/I, mob/user, params) + if(user.a_intent != INTENT_HARM && istype(I, /obj/item/twohanded/fireaxe)) + try_to_crowbar(user, I) + return 1 + else if(!(I.flags & NOBLUDGEON) && user.a_intent != INTENT_HARM) + try_to_activate_door(user) + return 1 + return ..() + + +/obj/machinery/door/crowbar_act(mob/user, obj/item/I) + if(user.a_intent == INTENT_HARM) + return + . = TRUE + if(operating) + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + try_to_crowbar(user, I) + +/obj/machinery/door/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(. && obj_integrity > 0) + if(damage_amount >= 10 && prob(30)) + spark_system.start() + +/obj/machinery/door/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(glass) + playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) + else if(damage_amount) + playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/machinery/door/emag_act(mob/user) + if(density) + flick("door_spark", src) + sleep(6) + open() + emagged = 1 + return 1 + +/obj/machinery/door/emp_act(severity) + if(prob(20/severity) && (istype(src,/obj/machinery/door/airlock) || istype(src,/obj/machinery/door/window)) ) + spawn(0) + open() + ..() + +/obj/machinery/door/update_icon() + if(density) + icon_state = "door1" + else + icon_state = "door0" + +/obj/machinery/door/proc/do_animate(animation) + switch(animation) + if("opening") + if(panel_open) + flick("o_doorc0", src) + else + flick("doorc0", src) + if("closing") + if(panel_open) + flick("o_doorc1", src) + else + flick("doorc1", src) + if("deny") + if(!stat) + flick("door_deny", src) + +/obj/machinery/door/proc/open() + if(!density) + return TRUE + if(operating) + return + operating = TRUE + do_animate("opening") + set_opacity(0) + sleep(5) + density = FALSE + sleep(5) + layer = initial(layer) + update_icon() + set_opacity(0) + operating = FALSE + air_update_turf(1) + update_freelook_sight() + if(autoclose) + autoclose_in(normalspeed ? auto_close_time : auto_close_time_dangerous) + return TRUE + +/obj/machinery/door/proc/close() + if(density) + return TRUE + if(operating || welded) + return + if(safe) + for(var/turf/turf in locs) + for(var/atom/movable/M in turf) + if(M.density && M != src) //something is blocking the door + if(autoclose) + autoclose_in(60) + return + + operating = TRUE + + do_animate("closing") + layer = closingLayer + sleep(5) + density = TRUE + sleep(5) + update_icon() + if(visible && !glass) + set_opacity(1) + operating = 0 + air_update_turf(1) + update_freelook_sight() + if(safe) + CheckForMobs() + else + crush() + return TRUE + +/obj/machinery/door/proc/CheckForMobs() + if(locate(/mob/living) in get_turf(src)) + sleep(1) + open() + +/obj/machinery/door/proc/crush() + for(var/mob/living/L in get_turf(src)) + L.visible_message("[src] closes on [L], crushing [L.p_them()]!", "[src] closes on you and crushes you!") + if(isalien(L)) //For xenos + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE * 1.5) //Xenos go into crit after aproximately the same amount of crushes as humans. + L.emote("roar") + else if(ishuman(L)) //For humans + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + if(L.stat == CONSCIOUS) + L.emote("scream") + L.Weaken(5) + else //for simple_animals & borgs + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + var/turf/location = get_turf(src) + L.add_splatter_floor(location) + for(var/obj/mecha/M in get_turf(src)) + M.take_damage(DOOR_CRUSH_DAMAGE) + +/obj/machinery/door/proc/requiresID() + return 1 + +/obj/machinery/door/proc/hasPower() + return !(stat & NOPOWER) + +/obj/machinery/door/proc/autoclose() + if(!QDELETED(src) && !density && !operating && !locked && !welded && autoclose) + close() + +/obj/machinery/door/proc/autoclose_in(wait) + addtimer(CALLBACK(src, .proc/autoclose), wait, TIMER_UNIQUE | TIMER_NO_HASH_WAIT | TIMER_OVERRIDE) + +/obj/machinery/door/proc/update_freelook_sight() + if(!glass && cameranet) + cameranet.updateVisibility(src, 0) + +/obj/machinery/door/BlockSuperconductivity() // All non-glass airlocks block heat, this is intended. + if(opacity || heat_proof) + return 1 + return 0 + +/obj/machinery/door/morgue + icon = 'icons/obj/doors/doormorgue.dmi' + +/obj/machinery/door/proc/lock() + return + +/obj/machinery/door/proc/unlock() + return + +/obj/machinery/door/proc/hostile_lockdown(mob/origin) + if(!stat) //So that only powered doors are closed. + close() //Close ALL the doors! + +/obj/machinery/door/proc/disable_lockdown() + if(!stat) //Opens only powered doors. + open() //Open everything! + +/obj/machinery/door/ex_act(severity) + //if it blows up a wall it should blow up a door + ..(severity ? max(1, severity - 1) : 0) + + +/obj/machinery/door/GetExplosionBlock() + return density ? real_explosion_block : 0 diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 4cccb667436..448c410c1c8 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -1,490 +1,490 @@ -#define CONSTRUCTION_COMPLETE 0 //No construction done - functioning as normal -#define CONSTRUCTION_PANEL_OPEN 1 //Maintenance panel is open, still functioning -#define CONSTRUCTION_WIRES_EXPOSED 2 //Cover plate is removed, wires are available -#define CONSTRUCTION_GUTTED 3 //Wires are removed, circuit ready to remove -#define CONSTRUCTION_NOCIRCUIT 4 //Circuit board removed, can safely weld apart - -/obj/machinery/door/firedoor - name = "firelock" - desc = "Apply crowbar." - icon = 'icons/obj/doors/doorfireglass.dmi' - icon_state = "door_open" - opacity = 0 - density = FALSE - max_integrity = 300 - resistance_flags = FIRE_PROOF - heat_proof = TRUE - glass = TRUE - explosion_block = 1 - safe = FALSE - layer = BELOW_OPEN_DOOR_LAYER - closingLayer = CLOSED_FIREDOOR_LAYER - auto_close_time = 50 - assemblytype = /obj/structure/firelock_frame - armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 70) - var/can_force = TRUE - var/force_open_time = 300 - var/can_crush = TRUE - var/nextstate = null - var/boltslocked = TRUE - var/active_alarm = FALSE - -/obj/machinery/door/firedoor/examine(mob/user) - . = ..() - if(!density) - . += "It is open, but could be pried closed." - else if(!welded) - . += "It is closed, but could be pried open. Deconstruction would require it to be welded shut." - else if(boltslocked) - . += "It is welded shut. The floor bolts have been locked by screws." - else - . += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor." - -/obj/machinery/door/firedoor/closed - icon_state = "door_closed" - opacity = TRUE - density = TRUE - -/obj/machinery/door/firedoor/Bumped(atom/AM) - if(panel_open || operating) - return - if(!density) - return ..() - return 0 - -/obj/machinery/door/firedoor/power_change() - if(powered(power_channel)) - stat &= ~NOPOWER - latetoggle() - else - stat |= NOPOWER - update_icon() - -/obj/machinery/door/firedoor/attack_hand(mob/user) - if(operating || !density) - return - - add_fingerprint(user) - user.changeNext_move(CLICK_CD_MELEE) - - if(can_force && (!glass || user.a_intent != INTENT_HELP)) - user.visible_message("[user] begins forcing \the [src].", \ - "You begin forcing \the [src].") - if(do_after(user, force_open_time, target = src)) - user.visible_message("[user] forces \the [src].", \ - "You force \the [src].") - open() - else if(glass) - user.visible_message("[user] bangs on \the [src].", - "You bang on \the [src].") - playsound(get_turf(src), 'sound/effects/glassknock.ogg', 10, 1) - -/obj/machinery/door/firedoor/attackby(obj/item/C, mob/user, params) - add_fingerprint(user) - - if(operating) - return - return ..() - -/obj/machinery/door/firedoor/try_to_activate_door(mob/user) - return - -/obj/machinery/door/firedoor/screwdriver_act(mob/user, obj/item/I) - if(user.a_intent == INTENT_HARM) - return - if(operating || !welded) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - user.visible_message("[user] [boltslocked ? "unlocks" : "locks"] [src]'s bolts.", \ - "You [boltslocked ? "unlock" : "lock"] [src]'s floor bolts.") - boltslocked = !boltslocked - -/obj/machinery/door/firedoor/wrench_act(mob/user, obj/item/I) - if(user.a_intent == INTENT_HARM) - return - if(operating || !welded) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(boltslocked) - to_chat(user, "There are screws locking the bolts in place!") - return - user.visible_message("[user] starts undoing [src]'s bolts...", \ - "You start unfastening [src]'s floor bolts...") - if(!I.use_tool(src, user, 50, volume = I.tool_volume) || boltslocked) - return - user.visible_message("[user] unfastens [src]'s bolts.", \ - "You undo [src]'s floor bolts.") - deconstruct(TRUE) - -/obj/machinery/door/firedoor/welder_act(mob/user, obj/item/I) - if(!density) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_WELD_MESSAGE - if(!I.use_tool(src, user, 40, volume = I.tool_volume)) - return - if(!density) //In case someone opens it while it's getting welded - return - WELDER_WELD_SUCCESS_MESSAGE - welded = !welded - update_icon() - -/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user) - if(welded || operating) - return - if(density) - open() - else - close() - -/obj/machinery/door/firedoor/attack_ai(mob/user) - forcetoggle() - -/obj/machinery/door/firedoor/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - forcetoggle(TRUE) - -/obj/machinery/door/firedoor/attack_alien(mob/user) - add_fingerprint(user) - if(welded) - to_chat(user, "[src] refuses to budge!") - return - open() - -/obj/machinery/door/firedoor/do_animate(animation) - switch(animation) - if("opening") - flick("door_opening", src) - playsound(src, 'sound/machines/airlock_ext_open.ogg', 30, 1) - if("closing") - flick("door_closing", src) - playsound(src, 'sound/machines/airlock_ext_close.ogg', 30, 1) - -/obj/machinery/door/firedoor/update_icon() - overlays.Cut() - if(active_alarm && hasPower()) - overlays += image('icons/obj/doors/doorfire.dmi', "alarmlights") - if(density) - icon_state = "door_closed" - if(welded) - overlays += "welded" - else - icon_state = "door_open" - if(welded) - overlays += "welded_open" - -/obj/machinery/door/firedoor/proc/activate_alarm() - active_alarm = TRUE - update_icon() - -/obj/machinery/door/firedoor/proc/deactivate_alarm() - active_alarm = FALSE - update_icon() - -/obj/machinery/door/firedoor/open(auto_close = TRUE) - if(welded) - return - . = ..() - latetoggle(auto_close) - - if(auto_close) - autoclose = TRUE - -/obj/machinery/door/firedoor/close() - . = ..() - latetoggle() - -/obj/machinery/door/firedoor/autoclose() - if(active_alarm) - . = ..() - -/obj/machinery/door/firedoor/proc/latetoggle(auto_close = TRUE) - if(operating || !hasPower() || !nextstate) - return - switch(nextstate) - if(FD_OPEN) - nextstate = null - open(auto_close) - if(FD_CLOSED) - nextstate = null - close() - -/obj/machinery/door/firedoor/proc/forcetoggle(magic = FALSE, auto_close = TRUE) - if(!magic && (operating || !hasPower())) - return - if(density) - open(auto_close) - else - close() - -/obj/machinery/door/firedoor/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - var/obj/structure/firelock_frame/F = new assemblytype(get_turf(src)) - if(disassembled) - F.constructionStep = CONSTRUCTION_PANEL_OPEN - else - F.constructionStep = CONSTRUCTION_WIRES_EXPOSED - F.obj_integrity = F.max_integrity * 0.5 - F.update_icon() - qdel(src) - -/obj/machinery/door/firedoor/border_only - icon = 'icons/obj/doors/edge_doorfire.dmi' - flags = ON_BORDER - can_crush = FALSE - -/obj/machinery/door/firedoor/border_only/closed - icon_state = "door_closed" - opacity = TRUE - density = TRUE - -/obj/machinery/door/firedoor/border_only/CanPass(atom/movable/mover, turf/target, height=0) - if(istype(mover) && mover.checkpass(PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return !density - else - return 1 - -/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover, turf/target) - if(istype(mover) && mover.checkpass(PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) - return !density - else - return 1 - -/obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T) - if(get_dir(loc, T) == dir) - return !density - else - return 1 - -/obj/machinery/door/firedoor/heavy - name = "heavy firelock" - icon = 'icons/obj/doors/doorfire.dmi' - glass = FALSE - opacity = 1 - explosion_block = 2 - assemblytype = /obj/structure/firelock_frame/heavy - can_force = FALSE - max_integrity = 550 - -/obj/item/firelock_electronics - name = "firelock electronics" - icon = 'icons/obj/doors/door_assembly.dmi' - icon_state = "door_electronics" - desc = "A circuit board used in construction of firelocks." - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=50, MAT_GLASS=50) - origin_tech = "engineering=2;programming=1" - toolspeed = 1 - usesound = 'sound/items/deconstruct.ogg' - -/obj/structure/firelock_frame - name = "firelock frame" - desc = "A partially completed firelock." - icon = 'icons/obj/doors/doorfire.dmi' - icon_state = "frame1" - anchored = FALSE - density = TRUE - var/constructionStep = CONSTRUCTION_NOCIRCUIT - var/reinforced = 0 - -/obj/structure/firelock_frame/examine(mob/user) - . = ..() - switch(constructionStep) - if(CONSTRUCTION_PANEL_OPEN) - . += "It is unbolted from the floor. A small loosely connected metal plate is covering the wires." - if(!reinforced) - . += "It could be reinforced with plasteel." - if(CONSTRUCTION_WIRES_EXPOSED) - . += "The maintenance plate has been pried away, and wires are trailing." - if(CONSTRUCTION_GUTTED) - . += "The maintenance panel is missing wires and the circuit board is loosely connected." - if(CONSTRUCTION_NOCIRCUIT) - . += "There are no firelock electronics in the frame. The frame could be cut apart." - -/obj/structure/firelock_frame/update_icon() - ..() - icon_state = "frame[constructionStep]" - -/obj/structure/firelock_frame/attackby(obj/item/C, mob/user) - switch(constructionStep) - if(CONSTRUCTION_PANEL_OPEN) - if(istype(C, /obj/item/stack/sheet/plasteel)) - var/obj/item/stack/sheet/plasteel/P = C - if(reinforced) - to_chat(user, "[src] is already reinforced.") - return - if(P.get_amount() < 2) - to_chat(user, "You need more plasteel to reinforce [src].") - return - user.visible_message("[user] begins reinforcing [src]...", \ - "You begin reinforcing [src]...") - playsound(get_turf(src), C.usesound, 50, 1) - if(do_after(user, 60 * C.toolspeed, target = src)) - if(constructionStep != CONSTRUCTION_PANEL_OPEN || reinforced || P.get_amount() < 2 || !P) - return - user.visible_message("[user] reinforces [src].", \ - "You reinforce [src].") - playsound(get_turf(src), C.usesound, 50, 1) - P.use(2) - reinforced = 1 - return - if(CONSTRUCTION_GUTTED) - if(iscoil(C)) - var/obj/item/stack/cable_coil/B = C - if(B.get_amount() < 5) - to_chat(user, "You need more wires to add wiring to [src].") - return - user.visible_message("[user] begins wiring [src]...", \ - "You begin adding wires to [src]...") - playsound(get_turf(src), B.usesound, 50, 1) - if(do_after(user, 60 * B.toolspeed, target = src)) - if(constructionStep != CONSTRUCTION_GUTTED || B.get_amount() < 5 || !B) - return - user.visible_message("[user] adds wires to [src].", \ - "You wire [src].") - playsound(get_turf(src), B.usesound, 50, 1) - B.use(5) - constructionStep = CONSTRUCTION_WIRES_EXPOSED - update_icon() - return - if(CONSTRUCTION_NOCIRCUIT) - if(istype(C, /obj/item/firelock_electronics)) - user.visible_message("[user] starts adding [C] to [src]...", \ - "You begin adding a circuit board to [src]...") - playsound(get_turf(src), C.usesound, 50, 1) - if(!do_after(user, 40 * C.toolspeed, target = src)) - return - if(constructionStep != CONSTRUCTION_NOCIRCUIT) - return - user.drop_item() - qdel(C) - user.visible_message("[user] adds a circuit to [src].", \ - "You insert and secure [C].") - playsound(get_turf(src), C.usesound, 50, 1) - constructionStep = CONSTRUCTION_GUTTED - update_icon() - return - return ..() - -/obj/structure/firelock_frame/crowbar_act(mob/user, obj/item/I) - if(!(constructionStep in list(CONSTRUCTION_WIRES_EXPOSED, CONSTRUCTION_PANEL_OPEN, CONSTRUCTION_GUTTED))) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(constructionStep == CONSTRUCTION_WIRES_EXPOSED) - user.visible_message("[user] starts prying a metal plate into [src]...", \ - "You begin prying the cover plate back onto [src]...") - if(!I.use_tool(src, user, 50, volume = I.tool_volume)) - return - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - user.visible_message("[user] pries the metal plate into [src].", \ - "You pry [src]'s cover plate into place, hiding the wires.") - constructionStep = CONSTRUCTION_PANEL_OPEN - else if(constructionStep == CONSTRUCTION_PANEL_OPEN) - user.visible_message("[user] starts prying something out from [src]...", \ - "You begin prying out the wire cover...") - if(!I.use_tool(src, user, 50, volume = I.tool_volume)) - return - if(constructionStep != CONSTRUCTION_PANEL_OPEN) - return - user.visible_message("[user] pries out a metal plate from [src], exposing the wires.", \ - "You remove the cover plate from [src], exposing the wires.") - constructionStep = CONSTRUCTION_WIRES_EXPOSED - else if(constructionStep == CONSTRUCTION_GUTTED) - user.visible_message("[user] begins removing the circuit board from [src]...", \ - "You begin prying out the circuit board from [src]...") - if(!I.use_tool(src, user, 50, volume = I.tool_volume)) - return - if(constructionStep != CONSTRUCTION_GUTTED) - return - user.visible_message("[user] removes [src]'s circuit board.", \ - "You remove the circuit board from [src].") - new /obj/item/firelock_electronics(get_turf(src)) - constructionStep = CONSTRUCTION_NOCIRCUIT - update_icon() - -/obj/structure/firelock_frame/wirecutter_act(mob/user, obj/item/I) - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - . = TRUE - if(!I.tool_start_check(user, 0)) - return - - user.visible_message("[user] starts cutting the wires from [src]...", \ - "You begin removing [src]'s wires...") - if(!I.use_tool(src, user, 50, volume = I.tool_volume)) - return - if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) - return - user.visible_message("[user] removes the wires from [src].", \ - "You remove the wiring from [src], exposing the circuit board.") - var/obj/item/stack/cable_coil/B = new(get_turf(src)) - B.amount = 5 - constructionStep = CONSTRUCTION_GUTTED - update_icon() - -/obj/structure/firelock_frame/wrench_act(mob/user, obj/item/I) - if(constructionStep != CONSTRUCTION_PANEL_OPEN) - return - . = TRUE - if(locate(/obj/machinery/door/firedoor) in get_turf(src)) - to_chat(user, "There's already a firelock there.") - return - if(!I.tool_start_check(user, 0)) - return - user.visible_message("[user] starts bolting down [src]...", \ - "You begin bolting [src]...") - if(!I.use_tool(src, user, 50, volume = I.tool_volume)) - return - if(locate(/obj/machinery/door/firedoor) in get_turf(src)) - return - user.visible_message("[user] finishes the firelock.", \ - "You finish the firelock.") - if(reinforced) - new /obj/machinery/door/firedoor/heavy(get_turf(src)) - else - new /obj/machinery/door/firedoor(get_turf(src)) - qdel(src) - - - - - -/obj/structure/firelock_frame/welder_act(mob/user, obj/item/I) - if(constructionStep != CONSTRUCTION_NOCIRCUIT) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_SLICING_MESSAGE - if(!I.use_tool(src, user, 40, amount = 1, volume = I.tool_volume)) - return - if(constructionStep != CONSTRUCTION_NOCIRCUIT) - return - WELDER_SLICING_SUCCESS_MESSAGE - new /obj/item/stack/sheet/metal(drop_location(), 3) - if(reinforced) - new /obj/item/stack/sheet/plasteel(drop_location(), 2) - qdel(src) - -/obj/structure/firelock_frame/heavy - name = "heavy firelock frame" - reinforced = 1 - -#undef CONSTRUCTION_COMPLETE -#undef CONSTRUCTION_PANEL_OPEN -#undef CONSTRUCTION_WIRES_EXPOSED -#undef CONSTRUCTION_GUTTED -#undef CONSTRUCTION_NOCIRCUIT +#define CONSTRUCTION_COMPLETE 0 //No construction done - functioning as normal +#define CONSTRUCTION_PANEL_OPEN 1 //Maintenance panel is open, still functioning +#define CONSTRUCTION_WIRES_EXPOSED 2 //Cover plate is removed, wires are available +#define CONSTRUCTION_GUTTED 3 //Wires are removed, circuit ready to remove +#define CONSTRUCTION_NOCIRCUIT 4 //Circuit board removed, can safely weld apart + +/obj/machinery/door/firedoor + name = "firelock" + desc = "Apply crowbar." + icon = 'icons/obj/doors/doorfireglass.dmi' + icon_state = "door_open" + opacity = 0 + density = FALSE + max_integrity = 300 + resistance_flags = FIRE_PROOF + heat_proof = TRUE + glass = TRUE + explosion_block = 1 + safe = FALSE + layer = BELOW_OPEN_DOOR_LAYER + closingLayer = CLOSED_FIREDOOR_LAYER + auto_close_time = 50 + assemblytype = /obj/structure/firelock_frame + armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 70) + var/can_force = TRUE + var/force_open_time = 300 + var/can_crush = TRUE + var/nextstate = null + var/boltslocked = TRUE + var/active_alarm = FALSE + +/obj/machinery/door/firedoor/examine(mob/user) + . = ..() + if(!density) + . += "It is open, but could be pried closed." + else if(!welded) + . += "It is closed, but could be pried open. Deconstruction would require it to be welded shut." + else if(boltslocked) + . += "It is welded shut. The floor bolts have been locked by screws." + else + . += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor." + +/obj/machinery/door/firedoor/closed + icon_state = "door_closed" + opacity = TRUE + density = TRUE + +/obj/machinery/door/firedoor/Bumped(atom/AM) + if(panel_open || operating) + return + if(!density) + return ..() + return 0 + +/obj/machinery/door/firedoor/power_change() + if(powered(power_channel)) + stat &= ~NOPOWER + latetoggle() + else + stat |= NOPOWER + update_icon() + +/obj/machinery/door/firedoor/attack_hand(mob/user) + if(operating || !density) + return + + add_fingerprint(user) + user.changeNext_move(CLICK_CD_MELEE) + + if(can_force && (!glass || user.a_intent != INTENT_HELP)) + user.visible_message("[user] begins forcing \the [src].", \ + "You begin forcing \the [src].") + if(do_after(user, force_open_time, target = src)) + user.visible_message("[user] forces \the [src].", \ + "You force \the [src].") + open() + else if(glass) + user.visible_message("[user] bangs on \the [src].", + "You bang on \the [src].") + playsound(get_turf(src), 'sound/effects/glassknock.ogg', 10, 1) + +/obj/machinery/door/firedoor/attackby(obj/item/C, mob/user, params) + add_fingerprint(user) + + if(operating) + return + return ..() + +/obj/machinery/door/firedoor/try_to_activate_door(mob/user) + return + +/obj/machinery/door/firedoor/screwdriver_act(mob/user, obj/item/I) + if(user.a_intent == INTENT_HARM) + return + if(operating || !welded) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + user.visible_message("[user] [boltslocked ? "unlocks" : "locks"] [src]'s bolts.", \ + "You [boltslocked ? "unlock" : "lock"] [src]'s floor bolts.") + boltslocked = !boltslocked + +/obj/machinery/door/firedoor/wrench_act(mob/user, obj/item/I) + if(user.a_intent == INTENT_HARM) + return + if(operating || !welded) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(boltslocked) + to_chat(user, "There are screws locking the bolts in place!") + return + user.visible_message("[user] starts undoing [src]'s bolts...", \ + "You start unfastening [src]'s floor bolts...") + if(!I.use_tool(src, user, 50, volume = I.tool_volume) || boltslocked) + return + user.visible_message("[user] unfastens [src]'s bolts.", \ + "You undo [src]'s floor bolts.") + deconstruct(TRUE) + +/obj/machinery/door/firedoor/welder_act(mob/user, obj/item/I) + if(!density) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_WELD_MESSAGE + if(!I.use_tool(src, user, 40, volume = I.tool_volume)) + return + if(!density) //In case someone opens it while it's getting welded + return + WELDER_WELD_SUCCESS_MESSAGE + welded = !welded + update_icon() + +/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user) + if(welded || operating) + return + if(density) + open() + else + close() + +/obj/machinery/door/firedoor/attack_ai(mob/user) + forcetoggle() + +/obj/machinery/door/firedoor/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + forcetoggle(TRUE) + +/obj/machinery/door/firedoor/attack_alien(mob/user) + add_fingerprint(user) + if(welded) + to_chat(user, "[src] refuses to budge!") + return + open() + +/obj/machinery/door/firedoor/do_animate(animation) + switch(animation) + if("opening") + flick("door_opening", src) + playsound(src, 'sound/machines/airlock_ext_open.ogg', 30, 1) + if("closing") + flick("door_closing", src) + playsound(src, 'sound/machines/airlock_ext_close.ogg', 30, 1) + +/obj/machinery/door/firedoor/update_icon() + overlays.Cut() + if(active_alarm && hasPower()) + overlays += image('icons/obj/doors/doorfire.dmi', "alarmlights") + if(density) + icon_state = "door_closed" + if(welded) + overlays += "welded" + else + icon_state = "door_open" + if(welded) + overlays += "welded_open" + +/obj/machinery/door/firedoor/proc/activate_alarm() + active_alarm = TRUE + update_icon() + +/obj/machinery/door/firedoor/proc/deactivate_alarm() + active_alarm = FALSE + update_icon() + +/obj/machinery/door/firedoor/open(auto_close = TRUE) + if(welded) + return + . = ..() + latetoggle(auto_close) + + if(auto_close) + autoclose = TRUE + +/obj/machinery/door/firedoor/close() + . = ..() + latetoggle() + +/obj/machinery/door/firedoor/autoclose() + if(active_alarm) + . = ..() + +/obj/machinery/door/firedoor/proc/latetoggle(auto_close = TRUE) + if(operating || !hasPower() || !nextstate) + return + switch(nextstate) + if(FD_OPEN) + nextstate = null + open(auto_close) + if(FD_CLOSED) + nextstate = null + close() + +/obj/machinery/door/firedoor/proc/forcetoggle(magic = FALSE, auto_close = TRUE) + if(!magic && (operating || !hasPower())) + return + if(density) + open(auto_close) + else + close() + +/obj/machinery/door/firedoor/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + var/obj/structure/firelock_frame/F = new assemblytype(get_turf(src)) + if(disassembled) + F.constructionStep = CONSTRUCTION_PANEL_OPEN + else + F.constructionStep = CONSTRUCTION_WIRES_EXPOSED + F.obj_integrity = F.max_integrity * 0.5 + F.update_icon() + qdel(src) + +/obj/machinery/door/firedoor/border_only + icon = 'icons/obj/doors/edge_doorfire.dmi' + flags = ON_BORDER + can_crush = FALSE + +/obj/machinery/door/firedoor/border_only/closed + icon_state = "door_closed" + opacity = TRUE + density = TRUE + +/obj/machinery/door/firedoor/border_only/CanPass(atom/movable/mover, turf/target, height=0) + if(istype(mover) && mover.checkpass(PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + return !density + else + return 1 + +/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover, turf/target) + if(istype(mover) && mover.checkpass(PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) + return !density + else + return 1 + +/obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T) + if(get_dir(loc, T) == dir) + return !density + else + return 1 + +/obj/machinery/door/firedoor/heavy + name = "heavy firelock" + icon = 'icons/obj/doors/doorfire.dmi' + glass = FALSE + opacity = 1 + explosion_block = 2 + assemblytype = /obj/structure/firelock_frame/heavy + can_force = FALSE + max_integrity = 550 + +/obj/item/firelock_electronics + name = "firelock electronics" + icon = 'icons/obj/doors/door_assembly.dmi' + icon_state = "door_electronics" + desc = "A circuit board used in construction of firelocks." + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=50, MAT_GLASS=50) + origin_tech = "engineering=2;programming=1" + toolspeed = 1 + usesound = 'sound/items/deconstruct.ogg' + +/obj/structure/firelock_frame + name = "firelock frame" + desc = "A partially completed firelock." + icon = 'icons/obj/doors/doorfire.dmi' + icon_state = "frame1" + anchored = FALSE + density = TRUE + var/constructionStep = CONSTRUCTION_NOCIRCUIT + var/reinforced = 0 + +/obj/structure/firelock_frame/examine(mob/user) + . = ..() + switch(constructionStep) + if(CONSTRUCTION_PANEL_OPEN) + . += "It is unbolted from the floor. A small loosely connected metal plate is covering the wires." + if(!reinforced) + . += "It could be reinforced with plasteel." + if(CONSTRUCTION_WIRES_EXPOSED) + . += "The maintenance plate has been pried away, and wires are trailing." + if(CONSTRUCTION_GUTTED) + . += "The maintenance panel is missing wires and the circuit board is loosely connected." + if(CONSTRUCTION_NOCIRCUIT) + . += "There are no firelock electronics in the frame. The frame could be cut apart." + +/obj/structure/firelock_frame/update_icon() + ..() + icon_state = "frame[constructionStep]" + +/obj/structure/firelock_frame/attackby(obj/item/C, mob/user) + switch(constructionStep) + if(CONSTRUCTION_PANEL_OPEN) + if(istype(C, /obj/item/stack/sheet/plasteel)) + var/obj/item/stack/sheet/plasteel/P = C + if(reinforced) + to_chat(user, "[src] is already reinforced.") + return + if(P.get_amount() < 2) + to_chat(user, "You need more plasteel to reinforce [src].") + return + user.visible_message("[user] begins reinforcing [src]...", \ + "You begin reinforcing [src]...") + playsound(get_turf(src), C.usesound, 50, 1) + if(do_after(user, 60 * C.toolspeed, target = src)) + if(constructionStep != CONSTRUCTION_PANEL_OPEN || reinforced || P.get_amount() < 2 || !P) + return + user.visible_message("[user] reinforces [src].", \ + "You reinforce [src].") + playsound(get_turf(src), C.usesound, 50, 1) + P.use(2) + reinforced = 1 + return + if(CONSTRUCTION_GUTTED) + if(iscoil(C)) + var/obj/item/stack/cable_coil/B = C + if(B.get_amount() < 5) + to_chat(user, "You need more wires to add wiring to [src].") + return + user.visible_message("[user] begins wiring [src]...", \ + "You begin adding wires to [src]...") + playsound(get_turf(src), B.usesound, 50, 1) + if(do_after(user, 60 * B.toolspeed, target = src)) + if(constructionStep != CONSTRUCTION_GUTTED || B.get_amount() < 5 || !B) + return + user.visible_message("[user] adds wires to [src].", \ + "You wire [src].") + playsound(get_turf(src), B.usesound, 50, 1) + B.use(5) + constructionStep = CONSTRUCTION_WIRES_EXPOSED + update_icon() + return + if(CONSTRUCTION_NOCIRCUIT) + if(istype(C, /obj/item/firelock_electronics)) + user.visible_message("[user] starts adding [C] to [src]...", \ + "You begin adding a circuit board to [src]...") + playsound(get_turf(src), C.usesound, 50, 1) + if(!do_after(user, 40 * C.toolspeed, target = src)) + return + if(constructionStep != CONSTRUCTION_NOCIRCUIT) + return + user.drop_item() + qdel(C) + user.visible_message("[user] adds a circuit to [src].", \ + "You insert and secure [C].") + playsound(get_turf(src), C.usesound, 50, 1) + constructionStep = CONSTRUCTION_GUTTED + update_icon() + return + return ..() + +/obj/structure/firelock_frame/crowbar_act(mob/user, obj/item/I) + if(!(constructionStep in list(CONSTRUCTION_WIRES_EXPOSED, CONSTRUCTION_PANEL_OPEN, CONSTRUCTION_GUTTED))) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(constructionStep == CONSTRUCTION_WIRES_EXPOSED) + user.visible_message("[user] starts prying a metal plate into [src]...", \ + "You begin prying the cover plate back onto [src]...") + if(!I.use_tool(src, user, 50, volume = I.tool_volume)) + return + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + user.visible_message("[user] pries the metal plate into [src].", \ + "You pry [src]'s cover plate into place, hiding the wires.") + constructionStep = CONSTRUCTION_PANEL_OPEN + else if(constructionStep == CONSTRUCTION_PANEL_OPEN) + user.visible_message("[user] starts prying something out from [src]...", \ + "You begin prying out the wire cover...") + if(!I.use_tool(src, user, 50, volume = I.tool_volume)) + return + if(constructionStep != CONSTRUCTION_PANEL_OPEN) + return + user.visible_message("[user] pries out a metal plate from [src], exposing the wires.", \ + "You remove the cover plate from [src], exposing the wires.") + constructionStep = CONSTRUCTION_WIRES_EXPOSED + else if(constructionStep == CONSTRUCTION_GUTTED) + user.visible_message("[user] begins removing the circuit board from [src]...", \ + "You begin prying out the circuit board from [src]...") + if(!I.use_tool(src, user, 50, volume = I.tool_volume)) + return + if(constructionStep != CONSTRUCTION_GUTTED) + return + user.visible_message("[user] removes [src]'s circuit board.", \ + "You remove the circuit board from [src].") + new /obj/item/firelock_electronics(get_turf(src)) + constructionStep = CONSTRUCTION_NOCIRCUIT + update_icon() + +/obj/structure/firelock_frame/wirecutter_act(mob/user, obj/item/I) + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + . = TRUE + if(!I.tool_start_check(user, 0)) + return + + user.visible_message("[user] starts cutting the wires from [src]...", \ + "You begin removing [src]'s wires...") + if(!I.use_tool(src, user, 50, volume = I.tool_volume)) + return + if(constructionStep != CONSTRUCTION_WIRES_EXPOSED) + return + user.visible_message("[user] removes the wires from [src].", \ + "You remove the wiring from [src], exposing the circuit board.") + var/obj/item/stack/cable_coil/B = new(get_turf(src)) + B.amount = 5 + constructionStep = CONSTRUCTION_GUTTED + update_icon() + +/obj/structure/firelock_frame/wrench_act(mob/user, obj/item/I) + if(constructionStep != CONSTRUCTION_PANEL_OPEN) + return + . = TRUE + if(locate(/obj/machinery/door/firedoor) in get_turf(src)) + to_chat(user, "There's already a firelock there.") + return + if(!I.tool_start_check(user, 0)) + return + user.visible_message("[user] starts bolting down [src]...", \ + "You begin bolting [src]...") + if(!I.use_tool(src, user, 50, volume = I.tool_volume)) + return + if(locate(/obj/machinery/door/firedoor) in get_turf(src)) + return + user.visible_message("[user] finishes the firelock.", \ + "You finish the firelock.") + if(reinforced) + new /obj/machinery/door/firedoor/heavy(get_turf(src)) + else + new /obj/machinery/door/firedoor(get_turf(src)) + qdel(src) + + + + + +/obj/structure/firelock_frame/welder_act(mob/user, obj/item/I) + if(constructionStep != CONSTRUCTION_NOCIRCUIT) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_SLICING_MESSAGE + if(!I.use_tool(src, user, 40, amount = 1, volume = I.tool_volume)) + return + if(constructionStep != CONSTRUCTION_NOCIRCUIT) + return + WELDER_SLICING_SUCCESS_MESSAGE + new /obj/item/stack/sheet/metal(drop_location(), 3) + if(reinforced) + new /obj/item/stack/sheet/plasteel(drop_location(), 2) + qdel(src) + +/obj/structure/firelock_frame/heavy + name = "heavy firelock frame" + reinforced = 1 + +#undef CONSTRUCTION_COMPLETE +#undef CONSTRUCTION_PANEL_OPEN +#undef CONSTRUCTION_WIRES_EXPOSED +#undef CONSTRUCTION_GUTTED +#undef CONSTRUCTION_NOCIRCUIT diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm index 4d8fbf01974..6f4a84a7535 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -1,126 +1,126 @@ -/obj/machinery/door/poddoor - name = "blast door" - desc = "A heavy duty blast door that opens mechanically." - icon = 'icons/obj/doors/blastdoor.dmi' - icon_state = "closed" - layer = BLASTDOOR_LAYER - closingLayer = CLOSED_BLASTDOOR_LAYER - explosion_block = 3 - heat_proof = TRUE - safe = FALSE - max_integrity = 600 - armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - damage_deflection = 70 - var/id_tag = 1.0 - var/protected = 1 - -/obj/machinery/door/poddoor/preopen - icon_state = "open" - density = FALSE - opacity = 0 - -/obj/machinery/door/poddoor/impassable - name = "reinforced blast door" - desc = "A heavy duty blast door that opens mechanically. Looks even tougher than usual." - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/machinery/door/poddoor/impassable/emag_act(mob/user) - to_chat(user, "The electronic systems in this door are far too advanced for your primitive hacking peripherals.") - return - -/obj/machinery/door/poddoor/Bumped(atom/AM) - if(density) - return - else - return 0 - -//"BLAST" doors are obviously stronger than regular doors when it comes to BLASTS. -/obj/machinery/door/poddoor/ex_act(severity) - if(severity == 3) - return - ..() - -/obj/machinery/door/poddoor/do_animate(animation) - switch(animation) - if("opening") - flick("opening", src) - playsound(src, 'sound/machines/blastdoor.ogg', 30, 1) - if("closing") - flick("closing", src) - playsound(src, 'sound/machines/blastdoor.ogg', 30, 1) - -/obj/machinery/door/poddoor/update_icon() - if(density) - icon_state = "closed" - else - icon_state = "open" - -/obj/machinery/door/poddoor/try_to_activate_door(mob/user) - return - -/obj/machinery/door/poddoor/try_to_crowbar(obj/item/I, mob/user) - if(!hasPower()) - open() - - // Whoever wrote the old code for multi-tile spesspod doors needs to burn in hell. - Unknown - // Wise words. - Bxil -/obj/machinery/door/poddoor/multi_tile - name = "large pod door" - layer = CLOSED_DOOR_LAYER - closingLayer = CLOSED_DOOR_LAYER - -/obj/machinery/door/poddoor/multi_tile/New() - . = ..() - apply_opacity_to_my_turfs(opacity) - -/obj/machinery/door/poddoor/multi_tile/open() - if(..()) - apply_opacity_to_my_turfs(opacity) - - -/obj/machinery/door/poddoor/multi_tile/close() - if(..()) - apply_opacity_to_my_turfs(opacity) - -/obj/machinery/door/poddoor/multi_tile/Destroy() - apply_opacity_to_my_turfs(0) - return ..() - -//Multi-tile poddoors don't turn invisible automatically, so we change the opacity of the turfs below instead one by one. -/obj/machinery/door/poddoor/multi_tile/proc/apply_opacity_to_my_turfs(var/new_opacity) - for(var/turf/T in locs) - T.opacity = new_opacity - T.has_opaque_atom = new_opacity - T.reconsider_lights() - update_freelook_sight() - -/obj/machinery/door/poddoor/multi_tile/four_tile_ver - icon = 'icons/obj/doors/1x4blast_vert.dmi' - width = 4 - dir = NORTH - -/obj/machinery/door/poddoor/multi_tile/three_tile_ver - icon = 'icons/obj/doors/1x3blast_vert.dmi' - width = 3 - dir = NORTH - -/obj/machinery/door/poddoor/multi_tile/two_tile_ver - icon = 'icons/obj/doors/1x2blast_vert.dmi' - width = 2 - dir = NORTH - -/obj/machinery/door/poddoor/multi_tile/four_tile_hor - icon = 'icons/obj/doors/1x4blast_hor.dmi' - width = 4 - dir = EAST - -/obj/machinery/door/poddoor/multi_tile/three_tile_hor - icon = 'icons/obj/doors/1x3blast_hor.dmi' - width = 3 - dir = EAST - -/obj/machinery/door/poddoor/multi_tile/two_tile_hor - icon = 'icons/obj/doors/1x2blast_hor.dmi' - width = 2 - dir = EAST +/obj/machinery/door/poddoor + name = "blast door" + desc = "A heavy duty blast door that opens mechanically." + icon = 'icons/obj/doors/blastdoor.dmi' + icon_state = "closed" + layer = BLASTDOOR_LAYER + closingLayer = CLOSED_BLASTDOOR_LAYER + explosion_block = 3 + heat_proof = TRUE + safe = FALSE + max_integrity = 600 + armor = list("melee" = 50, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + damage_deflection = 70 + var/id_tag = 1.0 + var/protected = 1 + +/obj/machinery/door/poddoor/preopen + icon_state = "open" + density = FALSE + opacity = 0 + +/obj/machinery/door/poddoor/impassable + name = "reinforced blast door" + desc = "A heavy duty blast door that opens mechanically. Looks even tougher than usual." + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/machinery/door/poddoor/impassable/emag_act(mob/user) + to_chat(user, "The electronic systems in this door are far too advanced for your primitive hacking peripherals.") + return + +/obj/machinery/door/poddoor/Bumped(atom/AM) + if(density) + return + else + return 0 + +//"BLAST" doors are obviously stronger than regular doors when it comes to BLASTS. +/obj/machinery/door/poddoor/ex_act(severity) + if(severity == 3) + return + ..() + +/obj/machinery/door/poddoor/do_animate(animation) + switch(animation) + if("opening") + flick("opening", src) + playsound(src, 'sound/machines/blastdoor.ogg', 30, 1) + if("closing") + flick("closing", src) + playsound(src, 'sound/machines/blastdoor.ogg', 30, 1) + +/obj/machinery/door/poddoor/update_icon() + if(density) + icon_state = "closed" + else + icon_state = "open" + +/obj/machinery/door/poddoor/try_to_activate_door(mob/user) + return + +/obj/machinery/door/poddoor/try_to_crowbar(obj/item/I, mob/user) + if(!hasPower()) + open() + + // Whoever wrote the old code for multi-tile spesspod doors needs to burn in hell. - Unknown + // Wise words. - Bxil +/obj/machinery/door/poddoor/multi_tile + name = "large pod door" + layer = CLOSED_DOOR_LAYER + closingLayer = CLOSED_DOOR_LAYER + +/obj/machinery/door/poddoor/multi_tile/New() + . = ..() + apply_opacity_to_my_turfs(opacity) + +/obj/machinery/door/poddoor/multi_tile/open() + if(..()) + apply_opacity_to_my_turfs(opacity) + + +/obj/machinery/door/poddoor/multi_tile/close() + if(..()) + apply_opacity_to_my_turfs(opacity) + +/obj/machinery/door/poddoor/multi_tile/Destroy() + apply_opacity_to_my_turfs(0) + return ..() + +//Multi-tile poddoors don't turn invisible automatically, so we change the opacity of the turfs below instead one by one. +/obj/machinery/door/poddoor/multi_tile/proc/apply_opacity_to_my_turfs(var/new_opacity) + for(var/turf/T in locs) + T.opacity = new_opacity + T.has_opaque_atom = new_opacity + T.reconsider_lights() + update_freelook_sight() + +/obj/machinery/door/poddoor/multi_tile/four_tile_ver + icon = 'icons/obj/doors/1x4blast_vert.dmi' + width = 4 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/three_tile_ver + icon = 'icons/obj/doors/1x3blast_vert.dmi' + width = 3 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/two_tile_ver + icon = 'icons/obj/doors/1x2blast_vert.dmi' + width = 2 + dir = NORTH + +/obj/machinery/door/poddoor/multi_tile/four_tile_hor + icon = 'icons/obj/doors/1x4blast_hor.dmi' + width = 4 + dir = EAST + +/obj/machinery/door/poddoor/multi_tile/three_tile_hor + icon = 'icons/obj/doors/1x3blast_hor.dmi' + width = 3 + dir = EAST + +/obj/machinery/door/poddoor/multi_tile/two_tile_hor + icon = 'icons/obj/doors/1x2blast_hor.dmi' + width = 2 + dir = EAST diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm index e09d40689e2..457b056b7f9 100644 --- a/code/game/machinery/doors/shutters.dm +++ b/code/game/machinery/doors/shutters.dm @@ -1,14 +1,14 @@ -/obj/machinery/door/poddoor/shutters - gender = PLURAL - name = "shutters" - desc = "Heavy duty metal shutters that open mechanically." - icon = 'icons/obj/doors/shutters.dmi' - layer = SHUTTER_LAYER - closingLayer = SHUTTER_LAYER - damage_deflection = 20 - dir = EAST - -/obj/machinery/door/poddoor/shutters/preopen - icon_state = "open" - density = FALSE - opacity = 0 +/obj/machinery/door/poddoor/shutters + gender = PLURAL + name = "shutters" + desc = "Heavy duty metal shutters that open mechanically." + icon = 'icons/obj/doors/shutters.dmi' + layer = SHUTTER_LAYER + closingLayer = SHUTTER_LAYER + damage_deflection = 20 + dir = EAST + +/obj/machinery/door/poddoor/shutters/preopen + icon_state = "open" + density = FALSE + opacity = 0 diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 55c7f3d3773..e423f92683c 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -1,472 +1,472 @@ -/obj/machinery/door/window - name = "interior door" - desc = "A strong door." - icon = 'icons/obj/doors/windoor.dmi' - icon_state = "left" - layer = ABOVE_WINDOW_LAYER - closingLayer = ABOVE_WINDOW_LAYER - resistance_flags = ACID_PROOF - visible = 0 - flags = ON_BORDER - opacity = 0 - dir = EAST - max_integrity = 150 //If you change this, consider changing ../door/window/brigdoor/ max_integrity at the bottom of this .dm file - integrity_failure = 0 - armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100) - var/obj/item/airlock_electronics/electronics - var/base_state = "left" - var/reinf = 0 - var/cancolor = TRUE - var/shards = 2 - var/rods = 2 - var/cable = 1 - var/list/debris = list() - -/obj/machinery/door/window/New(loc, set_dir) - ..() - if(set_dir) - setDir(set_dir) - if(req_access && req_access.len) - icon_state = "[icon_state]" - base_state = icon_state - if(!color && cancolor) - color = color_windows(src) - for(var/i in 1 to shards) - debris += new /obj/item/shard(src) - if(rods) - debris += new /obj/item/stack/rods(src, rods) - if(cable) - debris += new /obj/item/stack/cable_coil(src, cable) - -/obj/machinery/door/window/Destroy() - density = FALSE - QDEL_LIST(debris) - if(obj_integrity == 0) - playsound(src, "shatter", 70, 1) - QDEL_NULL(electronics) - return ..() - -/obj/machinery/door/window/update_icon() - if(density) - icon_state = base_state - else - icon_state = "[base_state]open" - -/obj/machinery/door/window/examine(mob/user) - . = ..() - if(emagged) - . += "Its access panel is smoking slightly." - -/obj/machinery/door/window/proc/open_and_close() - open() - if(check_access(null)) - sleep(50) - else //secure doors close faster - sleep(20) - close() - -/obj/machinery/door/window/Bumped(atom/movable/AM) - if(operating || !density) - return - if(!ismob(AM)) - if(ismecha(AM)) - var/obj/mecha/mecha = AM - if(mecha.occupant && allowed(mecha.occupant)) - open_and_close() - else - do_animate("deny") - return - if(!SSticker) - return - var/mob/living/M = AM - if(!M.restrained() && M.mob_size > MOB_SIZE_TINY && (!(isrobot(M) && M.stat))) - bumpopen(M) - -/obj/machinery/door/window/bumpopen(mob/user) - if(operating || !density) - return - add_fingerprint(user) - if(!requiresID() || allowed(user)) - open_and_close() - else - do_animate("deny") - -/obj/machinery/door/window/CanPass(atom/movable/mover, turf/target, height=0) - if(istype(mover) && mover.checkpass(PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return !density - if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) - return FALSE - else - return 1 - -/obj/machinery/door/window/CanAtmosPass(turf/T) - if(get_dir(loc, T) == dir) - return !density - else - return 1 - -//used in the AStar algorithm to determinate if the turf the door is on is passable -/obj/machinery/door/window/CanAStarPass(obj/item/card/id/ID, to_dir) - return !density || (dir != to_dir) || (check_access(ID) && hasPower()) - -/obj/machinery/door/window/CheckExit(atom/movable/mover, turf/target) - if(istype(mover) && mover.checkpass(PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) - return !density - else - return 1 - -/obj/machinery/door/window/open(forced=0) - if(operating) //doors can still open when emag-disabled - return 0 - if(!forced) - if(!hasPower()) - return 0 - if(forced < 2) - if(emagged) - return 0 - if(!operating) //in case of emag - operating = TRUE - do_animate("opening") - playsound(loc, 'sound/machines/windowdoor.ogg', 100, 1) - icon_state ="[base_state]open" - sleep(10) - - density = FALSE -// sd_set_opacity(0) //TODO: why is this here? Opaque windoors? ~Carn - air_update_turf(1) - update_freelook_sight() - - if(operating) //emag again - operating = FALSE - return 1 - -/obj/machinery/door/window/close(forced=0) - if(operating) - return 0 - if(!forced) - if(!hasPower()) - return 0 - if(forced < 2) - if(emagged) - return 0 - operating = TRUE - do_animate("closing") - playsound(loc, 'sound/machines/windowdoor.ogg', 100, 1) - icon_state = base_state - - density = 1 -// if(visible) -// set_opacity(1) //TODO: why is this here? Opaque windoors? ~Carn - air_update_turf(1) - update_freelook_sight() - sleep(10) - - operating = 0 - return 1 - -/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src, 'sound/effects/glasshit.ogg', 90, TRUE) - if(BURN) - playsound(src, 'sound/items/welder.ogg', 100, TRUE) - -/obj/machinery/door/window/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT) && !disassembled) - for(var/obj/fragment in debris) - fragment.forceMove(get_turf(src)) - transfer_fingerprints_to(fragment) - debris -= fragment - qdel(src) - -/obj/machinery/door/window/narsie_act() - color = NARSIE_WINDOW_COLOUR - -/obj/machinery/door/window/ratvar_act() - var/obj/machinery/door/window/clockwork/C = new(loc, dir) - C.name = name - qdel(src) - -/obj/machinery/door/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > T0C + (reinf ? 1600 : 800)) - take_damage(round(exposed_volume / 200), BURN, 0, 0) - -/obj/machinery/door/window/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/door/window/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - return attack_hand(user) - -/obj/machinery/door/window/attack_hand(mob/user) - return try_to_activate_door(user) - -/obj/machinery/door/window/emag_act(mob/user, obj/weapon) - if(!operating && density && !emagged) - emagged = TRUE - operating = TRUE - flick("[base_state]spark", src) - playsound(src, "sparks", 75, 1) - sleep(6) - operating = FALSE - open(2) - return 1 - -/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) - //If it's in the process of opening/closing, ignore the click - if(operating) - return - - add_fingerprint(user) - return ..() - -/obj/machinery/door/window/screwdriver_act(mob/user, obj/item/I) - if(flags & NODECONSTRUCT) - return - . = TRUE - if(density || operating) - to_chat(user, "You need to open the door to access the maintenance panel!") - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - panel_open = !panel_open - to_chat(user, "You [panel_open ? "open":"close"] the maintenance panel of the [src.name].") - - -/obj/machinery/door/window/crowbar_act(mob/user, obj/item/I) - if(operating) - return - if(flags & NODECONSTRUCT) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(panel_open && !density && !operating) - user.visible_message("[user] removes the electronics from the [name].", \ - "You start to remove electronics from the [name]...") - if(I.use_tool(src, user, 40, volume = I.tool_volume)) - if(panel_open && !density && !operating && loc) - var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(loc) - switch(base_state) - if("left") - WA.facing = "l" - if("right") - WA.facing = "r" - if("leftsecure") - WA.facing = "l" - WA.secure = TRUE - if("rightsecure") - WA.facing = "r" - WA.secure = TRUE - WA.anchored = TRUE - WA.state= "02" - WA.setDir(dir) - WA.ini_dir = dir - WA.update_icon() - WA.created_name = name - - if(emagged) - to_chat(user, "You discard the damaged electronics.") - qdel(src) - return - - to_chat(user, "You remove the airlock electronics.") - - var/obj/item/airlock_electronics/ae - if(!electronics) - ae = new/obj/item/airlock_electronics(loc) - if(!req_access) - check_access() - if(req_access.len) - ae.conf_access = req_access - else if(req_one_access.len) - ae.conf_access = req_one_access - ae.one_access = 1 - else - ae = electronics - electronics = null - ae.forceMove(loc) - - qdel(src) - else - try_to_crowbar(user, I) - -/obj/machinery/door/window/try_to_crowbar(mob/user, obj/item/I) - if(!hasPower()) - if(density) - open(2) - else - close(2) - else - to_chat(user, "The door's motors resist your efforts to force it!") - -/obj/machinery/door/window/do_animate(animation) - switch(animation) - if("opening") - flick("[base_state]opening", src) - if("closing") - flick("[base_state]closing", src) - if("deny") - flick("[base_state]deny", src) - -/obj/machinery/door/window/brigdoor - name = "secure door" - icon_state = "leftsecure" - base_state = "leftsecure" - max_integrity = 300 //Stronger doors for prison (regular window door health is 200) - reinf = 1 - explosion_block = 1 - var/id = null - -/obj/machinery/door/window/brigdoor/security/cell - name = "cell door" - desc = "For keeping in criminal scum." - req_access = list(ACCESS_BRIG) - -/obj/machinery/door/window/clockwork - name = "brass windoor" - desc = "A thin door with translucent brass paneling." - icon_state = "clockwork" - base_state = "clockwork" - shards = 0 - rods = 0 - resistance_flags = ACID_PROOF | FIRE_PROOF - cancolor = FALSE - var/made_glow = FALSE - -/obj/machinery/door/window/clockwork/New(loc, set_dir) - ..() - debris += new/obj/item/stack/tile/brass(src, 2) - -/obj/machinery/door/window/clockwork/setDir(direct) - if(!made_glow) - var/obj/effect/E = new /obj/effect/temp_visual/ratvar/door/window(get_turf(src)) - E.setDir(direct) - made_glow = TRUE - ..() - -/obj/machinery/door/window/clockwork/emp_act(severity) - if(prob(80/severity)) - open() - -/obj/machinery/door/window/clockwork/ratvar_act() - obj_integrity = max_integrity - -/obj/machinery/door/window/clockwork/hasPower() - return TRUE //yup that's power all right - -/obj/machinery/door/window/clockwork/narsie_act() - take_damage(rand(30, 60), BRUTE) - if(src) - var/previouscolor = color - color = "#960000" - animate(src, color = previouscolor, time = 8) - -/obj/machinery/door/window/northleft - dir = NORTH - -/obj/machinery/door/window/eastleft - dir = EAST - -/obj/machinery/door/window/westleft - dir = WEST - -/obj/machinery/door/window/southleft - dir = SOUTH - -/obj/machinery/door/window/northright - dir = NORTH - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/eastright - dir = EAST - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/westright - dir = WEST - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/southright - dir = SOUTH - icon_state = "right" - base_state = "right" - -/obj/machinery/door/window/brigdoor/northleft - dir = NORTH - -/obj/machinery/door/window/brigdoor/eastleft - dir = EAST - -/obj/machinery/door/window/brigdoor/westleft - dir = WEST - -/obj/machinery/door/window/brigdoor/southleft - dir = SOUTH - -/obj/machinery/door/window/brigdoor/northright - dir = NORTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/eastright - dir = EAST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/westright - dir = WEST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/southright - dir = SOUTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/northleft - dir = NORTH - -/obj/machinery/door/window/brigdoor/security/cell/eastleft - dir = EAST - -/obj/machinery/door/window/brigdoor/security/cell/westleft - dir = WEST - -/obj/machinery/door/window/brigdoor/security/cell/southleft - dir = SOUTH - -/obj/machinery/door/window/brigdoor/security/cell/northright - dir = NORTH - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/eastright - dir = EAST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/westright - dir = WEST - icon_state = "rightsecure" - base_state = "rightsecure" - -/obj/machinery/door/window/brigdoor/security/cell/southright - dir = SOUTH - icon_state = "rightsecure" - base_state = "rightsecure" +/obj/machinery/door/window + name = "interior door" + desc = "A strong door." + icon = 'icons/obj/doors/windoor.dmi' + icon_state = "left" + layer = ABOVE_WINDOW_LAYER + closingLayer = ABOVE_WINDOW_LAYER + resistance_flags = ACID_PROOF + visible = 0 + flags = ON_BORDER + opacity = 0 + dir = EAST + max_integrity = 150 //If you change this, consider changing ../door/window/brigdoor/ max_integrity at the bottom of this .dm file + integrity_failure = 0 + armor = list("melee" = 20, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 70, "acid" = 100) + var/obj/item/airlock_electronics/electronics + var/base_state = "left" + var/reinf = 0 + var/cancolor = TRUE + var/shards = 2 + var/rods = 2 + var/cable = 1 + var/list/debris = list() + +/obj/machinery/door/window/New(loc, set_dir) + ..() + if(set_dir) + setDir(set_dir) + if(req_access && req_access.len) + icon_state = "[icon_state]" + base_state = icon_state + if(!color && cancolor) + color = color_windows(src) + for(var/i in 1 to shards) + debris += new /obj/item/shard(src) + if(rods) + debris += new /obj/item/stack/rods(src, rods) + if(cable) + debris += new /obj/item/stack/cable_coil(src, cable) + +/obj/machinery/door/window/Destroy() + density = FALSE + QDEL_LIST(debris) + if(obj_integrity == 0) + playsound(src, "shatter", 70, 1) + QDEL_NULL(electronics) + return ..() + +/obj/machinery/door/window/update_icon() + if(density) + icon_state = base_state + else + icon_state = "[base_state]open" + +/obj/machinery/door/window/examine(mob/user) + . = ..() + if(emagged) + . += "Its access panel is smoking slightly." + +/obj/machinery/door/window/proc/open_and_close() + open() + if(check_access(null)) + sleep(50) + else //secure doors close faster + sleep(20) + close() + +/obj/machinery/door/window/Bumped(atom/movable/AM) + if(operating || !density) + return + if(!ismob(AM)) + if(ismecha(AM)) + var/obj/mecha/mecha = AM + if(mecha.occupant && allowed(mecha.occupant)) + open_and_close() + else + do_animate("deny") + return + if(!SSticker) + return + var/mob/living/M = AM + if(!M.restrained() && M.mob_size > MOB_SIZE_TINY && (!(isrobot(M) && M.stat))) + bumpopen(M) + +/obj/machinery/door/window/bumpopen(mob/user) + if(operating || !density) + return + add_fingerprint(user) + if(!requiresID() || allowed(user)) + open_and_close() + else + do_animate("deny") + +/obj/machinery/door/window/CanPass(atom/movable/mover, turf/target, height=0) + if(istype(mover) && mover.checkpass(PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + return !density + if(istype(mover, /obj/structure/window)) + var/obj/structure/window/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) + return FALSE + else + return 1 + +/obj/machinery/door/window/CanAtmosPass(turf/T) + if(get_dir(loc, T) == dir) + return !density + else + return 1 + +//used in the AStar algorithm to determinate if the turf the door is on is passable +/obj/machinery/door/window/CanAStarPass(obj/item/card/id/ID, to_dir) + return !density || (dir != to_dir) || (check_access(ID) && hasPower()) + +/obj/machinery/door/window/CheckExit(atom/movable/mover, turf/target) + if(istype(mover) && mover.checkpass(PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) + return !density + else + return 1 + +/obj/machinery/door/window/open(forced=0) + if(operating) //doors can still open when emag-disabled + return 0 + if(!forced) + if(!hasPower()) + return 0 + if(forced < 2) + if(emagged) + return 0 + if(!operating) //in case of emag + operating = TRUE + do_animate("opening") + playsound(loc, 'sound/machines/windowdoor.ogg', 100, 1) + icon_state ="[base_state]open" + sleep(10) + + density = FALSE +// sd_set_opacity(0) //TODO: why is this here? Opaque windoors? ~Carn + air_update_turf(1) + update_freelook_sight() + + if(operating) //emag again + operating = FALSE + return 1 + +/obj/machinery/door/window/close(forced=0) + if(operating) + return 0 + if(!forced) + if(!hasPower()) + return 0 + if(forced < 2) + if(emagged) + return 0 + operating = TRUE + do_animate("closing") + playsound(loc, 'sound/machines/windowdoor.ogg', 100, 1) + icon_state = base_state + + density = 1 +// if(visible) +// set_opacity(1) //TODO: why is this here? Opaque windoors? ~Carn + air_update_turf(1) + update_freelook_sight() + sleep(10) + + operating = 0 + return 1 + +/obj/machinery/door/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src, 'sound/effects/glasshit.ogg', 90, TRUE) + if(BURN) + playsound(src, 'sound/items/welder.ogg', 100, TRUE) + +/obj/machinery/door/window/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT) && !disassembled) + for(var/obj/fragment in debris) + fragment.forceMove(get_turf(src)) + transfer_fingerprints_to(fragment) + debris -= fragment + qdel(src) + +/obj/machinery/door/window/narsie_act() + color = NARSIE_WINDOW_COLOUR + +/obj/machinery/door/window/ratvar_act() + var/obj/machinery/door/window/clockwork/C = new(loc, dir) + C.name = name + qdel(src) + +/obj/machinery/door/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > T0C + (reinf ? 1600 : 800)) + take_damage(round(exposed_volume / 200), BURN, 0, 0) + +/obj/machinery/door/window/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/door/window/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + return attack_hand(user) + +/obj/machinery/door/window/attack_hand(mob/user) + return try_to_activate_door(user) + +/obj/machinery/door/window/emag_act(mob/user, obj/weapon) + if(!operating && density && !emagged) + emagged = TRUE + operating = TRUE + flick("[base_state]spark", src) + playsound(src, "sparks", 75, 1) + sleep(6) + operating = FALSE + open(2) + return 1 + +/obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) + //If it's in the process of opening/closing, ignore the click + if(operating) + return + + add_fingerprint(user) + return ..() + +/obj/machinery/door/window/screwdriver_act(mob/user, obj/item/I) + if(flags & NODECONSTRUCT) + return + . = TRUE + if(density || operating) + to_chat(user, "You need to open the door to access the maintenance panel!") + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + panel_open = !panel_open + to_chat(user, "You [panel_open ? "open":"close"] the maintenance panel of the [src.name].") + + +/obj/machinery/door/window/crowbar_act(mob/user, obj/item/I) + if(operating) + return + if(flags & NODECONSTRUCT) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(panel_open && !density && !operating) + user.visible_message("[user] removes the electronics from the [name].", \ + "You start to remove electronics from the [name]...") + if(I.use_tool(src, user, 40, volume = I.tool_volume)) + if(panel_open && !density && !operating && loc) + var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(loc) + switch(base_state) + if("left") + WA.facing = "l" + if("right") + WA.facing = "r" + if("leftsecure") + WA.facing = "l" + WA.secure = TRUE + if("rightsecure") + WA.facing = "r" + WA.secure = TRUE + WA.anchored = TRUE + WA.state= "02" + WA.setDir(dir) + WA.ini_dir = dir + WA.update_icon() + WA.created_name = name + + if(emagged) + to_chat(user, "You discard the damaged electronics.") + qdel(src) + return + + to_chat(user, "You remove the airlock electronics.") + + var/obj/item/airlock_electronics/ae + if(!electronics) + ae = new/obj/item/airlock_electronics(loc) + if(!req_access) + check_access() + if(req_access.len) + ae.conf_access = req_access + else if(req_one_access.len) + ae.conf_access = req_one_access + ae.one_access = 1 + else + ae = electronics + electronics = null + ae.forceMove(loc) + + qdel(src) + else + try_to_crowbar(user, I) + +/obj/machinery/door/window/try_to_crowbar(mob/user, obj/item/I) + if(!hasPower()) + if(density) + open(2) + else + close(2) + else + to_chat(user, "The door's motors resist your efforts to force it!") + +/obj/machinery/door/window/do_animate(animation) + switch(animation) + if("opening") + flick("[base_state]opening", src) + if("closing") + flick("[base_state]closing", src) + if("deny") + flick("[base_state]deny", src) + +/obj/machinery/door/window/brigdoor + name = "secure door" + icon_state = "leftsecure" + base_state = "leftsecure" + max_integrity = 300 //Stronger doors for prison (regular window door health is 200) + reinf = 1 + explosion_block = 1 + var/id = null + +/obj/machinery/door/window/brigdoor/security/cell + name = "cell door" + desc = "For keeping in criminal scum." + req_access = list(ACCESS_BRIG) + +/obj/machinery/door/window/clockwork + name = "brass windoor" + desc = "A thin door with translucent brass paneling." + icon_state = "clockwork" + base_state = "clockwork" + shards = 0 + rods = 0 + resistance_flags = ACID_PROOF | FIRE_PROOF + cancolor = FALSE + var/made_glow = FALSE + +/obj/machinery/door/window/clockwork/New(loc, set_dir) + ..() + debris += new/obj/item/stack/tile/brass(src, 2) + +/obj/machinery/door/window/clockwork/setDir(direct) + if(!made_glow) + var/obj/effect/E = new /obj/effect/temp_visual/ratvar/door/window(get_turf(src)) + E.setDir(direct) + made_glow = TRUE + ..() + +/obj/machinery/door/window/clockwork/emp_act(severity) + if(prob(80/severity)) + open() + +/obj/machinery/door/window/clockwork/ratvar_act() + obj_integrity = max_integrity + +/obj/machinery/door/window/clockwork/hasPower() + return TRUE //yup that's power all right + +/obj/machinery/door/window/clockwork/narsie_act() + take_damage(rand(30, 60), BRUTE) + if(src) + var/previouscolor = color + color = "#960000" + animate(src, color = previouscolor, time = 8) + +/obj/machinery/door/window/northleft + dir = NORTH + +/obj/machinery/door/window/eastleft + dir = EAST + +/obj/machinery/door/window/westleft + dir = WEST + +/obj/machinery/door/window/southleft + dir = SOUTH + +/obj/machinery/door/window/northright + dir = NORTH + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/eastright + dir = EAST + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/westright + dir = WEST + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/southright + dir = SOUTH + icon_state = "right" + base_state = "right" + +/obj/machinery/door/window/brigdoor/northleft + dir = NORTH + +/obj/machinery/door/window/brigdoor/eastleft + dir = EAST + +/obj/machinery/door/window/brigdoor/westleft + dir = WEST + +/obj/machinery/door/window/brigdoor/southleft + dir = SOUTH + +/obj/machinery/door/window/brigdoor/northright + dir = NORTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/eastright + dir = EAST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/westright + dir = WEST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/southright + dir = SOUTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/northleft + dir = NORTH + +/obj/machinery/door/window/brigdoor/security/cell/eastleft + dir = EAST + +/obj/machinery/door/window/brigdoor/security/cell/westleft + dir = WEST + +/obj/machinery/door/window/brigdoor/security/cell/southleft + dir = SOUTH + +/obj/machinery/door/window/brigdoor/security/cell/northright + dir = NORTH + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/eastright + dir = EAST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/westright + dir = WEST + icon_state = "rightsecure" + base_state = "rightsecure" + +/obj/machinery/door/window/brigdoor/security/cell/southright + dir = SOUTH + icon_state = "rightsecure" + base_state = "rightsecure" diff --git a/code/game/machinery/doppler_array.dm b/code/game/machinery/doppler_array.dm index d0fe753a9de..7af8ebe88f3 100644 --- a/code/game/machinery/doppler_array.dm +++ b/code/game/machinery/doppler_array.dm @@ -1,230 +1,230 @@ -var/list/doppler_arrays = list() - -/obj/machinery/doppler_array - name = "tachyon-doppler array" - desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array." - icon = 'icons/obj/machines/research.dmi' - icon_state = "tdoppler" - density = 1 - anchored = 1 - atom_say_verb = "states coldly" - var/list/logged_explosions = list() - var/explosion_target - var/datum/tech/toxins/toxins_tech - var/max_toxins_tech = 7 - -/datum/explosion_log - var/logged_time - var/epicenter - var/actual_size_message - var/theoretical_size_message - -/datum/explosion_log/New(var/log_time, var/log_epicenter, var/log_actual_size_message, var/log_theoretical_size_message) - ..() - logged_time = log_time - epicenter = log_epicenter - actual_size_message = log_actual_size_message - theoretical_size_message = log_theoretical_size_message - -/obj/machinery/doppler_array/New() - ..() - doppler_arrays += src - explosion_target = rand(8, 20) - toxins_tech = new /datum/tech/toxins(src) - -/obj/machinery/doppler_array/Destroy() - doppler_arrays -= src - logged_explosions.Cut() - return ..() - -/obj/machinery/doppler_array/process() - return PROCESS_KILL - -/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/disk/tech_disk)) - var/obj/item/disk/tech_disk/disk = I - disk.load_tech(toxins_tech) - to_chat(user, "You swipe the disk into [src].") - return - return ..() - -/obj/machinery/doppler_array/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(!anchored && !isinspace()) - anchored = TRUE - WRENCH_ANCHOR_MESSAGE - else if(anchored) - anchored = FALSE - WRENCH_UNANCHOR_MESSAGE - power_change() - -/obj/machinery/doppler_array/attack_hand(mob/user) - if(..()) - return - add_fingerprint(user) - ui_interact(user) - -/obj/machinery/doppler_array/attack_ghost(mob/user) - ui_interact(user) - -/obj/machinery/doppler_array/AltClick(mob/user) - rotate(user) - -/obj/machinery/doppler_array/verb/rotate(mob/user) - set name = "Rotate Tachyon-doppler Dish" - set category = "Object" - set src in oview(1) - - if(user.incapacitated()) - return - if(!Adjacent(user)) - return - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do that!") - return - dir = turn(dir, 90) - to_chat(user, "You rotate [src].") - -/obj/machinery/doppler_array/proc/print_explosive_logs(mob/user) - if(!logged_explosions.len) - atom_say("No logs currently stored in internal database.") - return - if(active_timers) - to_chat(user, "[src] is already printing something, please wait.") - return - atom_say("Printing explosive log. Standby...") - addtimer(CALLBACK(src, .print), 50) - -/obj/machinery/doppler_array/proc/print() - visible_message("[src] prints a piece of paper!") - playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) - var/obj/item/paper/explosive_log/P = new(get_turf(src)) - for(var/D in logged_explosions) - var/datum/explosion_log/E = D - P.info += "\ - [E.logged_time]\ - [E.epicenter]\ - [E.actual_size_message]\ - [E.theoretical_size_message]\ - " - P.info += "
        \ - Printed at [station_time_timestamp()]." - -/obj/machinery/doppler_array/proc/sense_explosion(var/x0,var/y0,var/z0,var/devastation_range,var/heavy_impact_range,var/light_impact_range, - var/took,var/orig_dev_range,var/orig_heavy_range,var/orig_light_range) - if(stat & NOPOWER) - return - if(z != z0) - return - - var/dx = abs(x0-x) - var/dy = abs(y0-y) - var/distance - var/direct - var/capped = FALSE - - if(dx > dy) - distance = dx - if(x0 > x) - direct = EAST - else - direct = WEST - else - distance = dy - if(y0 > y) - direct = NORTH - else - direct = SOUTH - - if(distance > 100) - return - if(!(direct & dir)) - return - - var/list/messages = list("Explosive disturbance detected.", \ - "Epicenter at: grid ([x0],[y0]). Temporal displacement of tachyons: [took] seconds.", \ - "Actual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].") - - // If the bomb was capped, say its theoretical size. - if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range) - capped = TRUE - messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]." - logged_explosions.Insert(1, new /datum/explosion_log(station_time_timestamp(), "[x0],[y0]", "[devastation_range], [heavy_impact_range], [light_impact_range]", capped ? "[orig_dev_range], [orig_heavy_range], [orig_light_range]" : "n/a")) //Newer logs appear first - messages += "Event successfully logged in internal database." - var/miss_by = abs(explosion_target - orig_light_range) - var/tmp_tech = max_toxins_tech - miss_by - if(!miss_by) - messages += "Explosion size matches target." - else - messages += "Target ([explosion_target]) missed by : [miss_by]." - if(tmp_tech > toxins_tech.level) - toxins_tech.level = tmp_tech - messages += "Toxins technology level upgraded to [toxins_tech.level]. Swipe a technology disk to save data." - for(var/message in messages) - atom_say(message) - -/obj/machinery/doppler_array/power_change() - if(stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - else - if(powered() && anchored) - icon_state = initial(icon_state) - stat &= ~NOPOWER - else - icon_state = "[initial(icon_state)]-off" - stat |= NOPOWER - -/obj/machinery/doppler_array/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "doppler_array.tmpl", "Tachyon-doppler array", 500, 650) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/doppler_array/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - var/list/explosion_data = list() - for(var/D in logged_explosions) - var/datum/explosion_log/E = D - explosion_data += list(list( - "logged_time" = E.logged_time, - "epicenter" = E.epicenter, - "actual_size_message" = E.actual_size_message, - "theoretical_size_message" = E.theoretical_size_message, - "unique_datum_id" = E.UID())) - data["explosion_target"] = explosion_target - data["toxins_tech"] = toxins_tech.level - data["explosion_data"] = explosion_data - data["printing"] = active_timers - return data - -/obj/machinery/doppler_array/Topic(href, href_list) - if(..()) - return - if(href_list["log_to_delete"]) - var/log_to_delete = sanitize(href_list["log_to_delete"]) - for(var/D in logged_explosions) - var/datum/explosion_log/E = D - if(E.UID() == log_to_delete) - logged_explosions -= E - qdel(E) - to_chat(usr, "Log deletion successful.") - break - else if(href_list["print_logs"]) - print_explosive_logs(usr) - else - return - SSnanoui.update_uis(src) - -/obj/item/paper/explosive_log - name = "explosive log" - info = "

        Explosive Log Report

        \ - \ - \ - \ - \ - \ - \ - " //NB: the
        Time loggedEpicenterActualTheoretical
        tag is left open, it is closed later on, when the doppler array adds its data +var/list/doppler_arrays = list() + +/obj/machinery/doppler_array + name = "tachyon-doppler array" + desc = "A highly precise directional sensor array which measures the release of quants from decaying tachyons. The doppler shifting of the mirror-image formed by these quants can reveal the size, location and temporal affects of energetic disturbances within a large radius ahead of the array." + icon = 'icons/obj/machines/research.dmi' + icon_state = "tdoppler" + density = 1 + anchored = 1 + atom_say_verb = "states coldly" + var/list/logged_explosions = list() + var/explosion_target + var/datum/tech/toxins/toxins_tech + var/max_toxins_tech = 7 + +/datum/explosion_log + var/logged_time + var/epicenter + var/actual_size_message + var/theoretical_size_message + +/datum/explosion_log/New(var/log_time, var/log_epicenter, var/log_actual_size_message, var/log_theoretical_size_message) + ..() + logged_time = log_time + epicenter = log_epicenter + actual_size_message = log_actual_size_message + theoretical_size_message = log_theoretical_size_message + +/obj/machinery/doppler_array/New() + ..() + doppler_arrays += src + explosion_target = rand(8, 20) + toxins_tech = new /datum/tech/toxins(src) + +/obj/machinery/doppler_array/Destroy() + doppler_arrays -= src + logged_explosions.Cut() + return ..() + +/obj/machinery/doppler_array/process() + return PROCESS_KILL + +/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/disk/tech_disk)) + var/obj/item/disk/tech_disk/disk = I + disk.load_tech(toxins_tech) + to_chat(user, "You swipe the disk into [src].") + return + return ..() + +/obj/machinery/doppler_array/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!anchored && !isinspace()) + anchored = TRUE + WRENCH_ANCHOR_MESSAGE + else if(anchored) + anchored = FALSE + WRENCH_UNANCHOR_MESSAGE + power_change() + +/obj/machinery/doppler_array/attack_hand(mob/user) + if(..()) + return + add_fingerprint(user) + ui_interact(user) + +/obj/machinery/doppler_array/attack_ghost(mob/user) + ui_interact(user) + +/obj/machinery/doppler_array/AltClick(mob/user) + rotate(user) + +/obj/machinery/doppler_array/verb/rotate(mob/user) + set name = "Rotate Tachyon-doppler Dish" + set category = "Object" + set src in oview(1) + + if(user.incapacitated()) + return + if(!Adjacent(user)) + return + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do that!") + return + dir = turn(dir, 90) + to_chat(user, "You rotate [src].") + +/obj/machinery/doppler_array/proc/print_explosive_logs(mob/user) + if(!logged_explosions.len) + atom_say("No logs currently stored in internal database.") + return + if(active_timers) + to_chat(user, "[src] is already printing something, please wait.") + return + atom_say("Printing explosive log. Standby...") + addtimer(CALLBACK(src, .print), 50) + +/obj/machinery/doppler_array/proc/print() + visible_message("[src] prints a piece of paper!") + playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) + var/obj/item/paper/explosive_log/P = new(get_turf(src)) + for(var/D in logged_explosions) + var/datum/explosion_log/E = D + P.info += "\ + \ + \ + \ + \ + " + P.info += "
        [E.logged_time][E.epicenter][E.actual_size_message][E.theoretical_size_message]

        \ + Printed at [station_time_timestamp()]." + +/obj/machinery/doppler_array/proc/sense_explosion(var/x0,var/y0,var/z0,var/devastation_range,var/heavy_impact_range,var/light_impact_range, + var/took,var/orig_dev_range,var/orig_heavy_range,var/orig_light_range) + if(stat & NOPOWER) + return + if(z != z0) + return + + var/dx = abs(x0-x) + var/dy = abs(y0-y) + var/distance + var/direct + var/capped = FALSE + + if(dx > dy) + distance = dx + if(x0 > x) + direct = EAST + else + direct = WEST + else + distance = dy + if(y0 > y) + direct = NORTH + else + direct = SOUTH + + if(distance > 100) + return + if(!(direct & dir)) + return + + var/list/messages = list("Explosive disturbance detected.", \ + "Epicenter at: grid ([x0],[y0]). Temporal displacement of tachyons: [took] seconds.", \ + "Actual: Epicenter radius: [devastation_range]. Outer radius: [heavy_impact_range]. Shockwave radius: [light_impact_range].") + + // If the bomb was capped, say its theoretical size. + if(devastation_range < orig_dev_range || heavy_impact_range < orig_heavy_range || light_impact_range < orig_light_range) + capped = TRUE + messages += "Theoretical: Epicenter radius: [orig_dev_range]. Outer radius: [orig_heavy_range]. Shockwave radius: [orig_light_range]." + logged_explosions.Insert(1, new /datum/explosion_log(station_time_timestamp(), "[x0],[y0]", "[devastation_range], [heavy_impact_range], [light_impact_range]", capped ? "[orig_dev_range], [orig_heavy_range], [orig_light_range]" : "n/a")) //Newer logs appear first + messages += "Event successfully logged in internal database." + var/miss_by = abs(explosion_target - orig_light_range) + var/tmp_tech = max_toxins_tech - miss_by + if(!miss_by) + messages += "Explosion size matches target." + else + messages += "Target ([explosion_target]) missed by : [miss_by]." + if(tmp_tech > toxins_tech.level) + toxins_tech.level = tmp_tech + messages += "Toxins technology level upgraded to [toxins_tech.level]. Swipe a technology disk to save data." + for(var/message in messages) + atom_say(message) + +/obj/machinery/doppler_array/power_change() + if(stat & BROKEN) + icon_state = "[initial(icon_state)]-broken" + else + if(powered() && anchored) + icon_state = initial(icon_state) + stat &= ~NOPOWER + else + icon_state = "[initial(icon_state)]-off" + stat |= NOPOWER + +/obj/machinery/doppler_array/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "doppler_array.tmpl", "Tachyon-doppler array", 500, 650) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/doppler_array/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + var/list/explosion_data = list() + for(var/D in logged_explosions) + var/datum/explosion_log/E = D + explosion_data += list(list( + "logged_time" = E.logged_time, + "epicenter" = E.epicenter, + "actual_size_message" = E.actual_size_message, + "theoretical_size_message" = E.theoretical_size_message, + "unique_datum_id" = E.UID())) + data["explosion_target"] = explosion_target + data["toxins_tech"] = toxins_tech.level + data["explosion_data"] = explosion_data + data["printing"] = active_timers + return data + +/obj/machinery/doppler_array/Topic(href, href_list) + if(..()) + return + if(href_list["log_to_delete"]) + var/log_to_delete = sanitize(href_list["log_to_delete"]) + for(var/D in logged_explosions) + var/datum/explosion_log/E = D + if(E.UID() == log_to_delete) + logged_explosions -= E + qdel(E) + to_chat(usr, "Log deletion successful.") + break + else if(href_list["print_logs"]) + print_explosive_logs(usr) + else + return + SSnanoui.update_uis(src) + +/obj/item/paper/explosive_log + name = "explosive log" + info = "

        Explosive Log Report

        \ + \ + \ + \ + \ + \ + \ + " //NB: the
        Time loggedEpicenterActualTheoretical
        tag is left open, it is closed later on, when the doppler array adds its data diff --git a/code/game/machinery/embedded_controller/airlock_program.dm b/code/game/machinery/embedded_controller/airlock_program.dm index 670cce47785..8ad85a608f0 100644 --- a/code/game/machinery/embedded_controller/airlock_program.dm +++ b/code/game/machinery/embedded_controller/airlock_program.dm @@ -379,4 +379,4 @@ send an additional command to open the door again. #undef TARGET_NONE #undef TARGET_INOPEN -#undef TARGET_OUTOPEN \ No newline at end of file +#undef TARGET_OUTOPEN diff --git a/code/game/machinery/embedded_controller/embedded_controller_base.dm b/code/game/machinery/embedded_controller/embedded_controller_base.dm index 62fbae71c78..2226f9f18fe 100644 --- a/code/game/machinery/embedded_controller/embedded_controller_base.dm +++ b/code/game/machinery/embedded_controller/embedded_controller_base.dm @@ -1,87 +1,87 @@ -/obj/machinery/embedded_controller - var/datum/computer/file/embedded_program/program //the currently executing program - - name = "Embedded Controller" - anchored = 1 - - use_power = IDLE_POWER_USE - idle_power_usage = 10 - - var/on = 1 - -/obj/machinery/embedded_controller/proc/post_signal(datum/signal/signal, comm_line) - return 0 - -/obj/machinery/embedded_controller/receive_signal(datum/signal/signal, receive_method, receive_param) - if(!signal || signal.encryption) return - - if(program) - program.receive_signal(signal, receive_method, receive_param) - //spawn(5) program.process() //no, program.process sends some signals and machines respond and we here again and we lag -rastaf0 - -/obj/machinery/embedded_controller/process() - if(program) - program.process() - - update_icon() - src.updateDialog() - -/obj/machinery/embedded_controller/attack_ghost(mob/user as mob) - src.ui_interact(user) - -/obj/machinery/embedded_controller/attack_ai(mob/user as mob) - src.ui_interact(user) - -/obj/machinery/embedded_controller/attack_hand(mob/user as mob) - if(!user.IsAdvancedToolUser()) - return 0 - src.ui_interact(user) - -/obj/machinery/embedded_controller/ui_interact() - return - -/obj/machinery/embedded_controller/radio - icon = 'icons/obj/airlock_machines.dmi' - icon_state = "airlock_control_standby" - power_channel = ENVIRON - density = 0 - - var/id_tag - //var/radio_power_use = 50 //power used to xmit signals - - var/frequency = 1379 - var/radio_filter = null - var/datum/radio_frequency/radio_connection - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/machinery/embedded_controller/radio/Initialize() - ..() - set_frequency(frequency) - -/obj/machinery/embedded_controller/radio/Destroy() - if(SSradio) - SSradio.remove_object(src, frequency) - radio_connection = null - return ..() - -/obj/machinery/embedded_controller/radio/update_icon() - if(on && program) - if(program.memory["processing"]) - icon_state = "airlock_control_process" - else - icon_state = "airlock_control_standby" - else - icon_state = "airlock_control_off" - -/obj/machinery/embedded_controller/radio/post_signal(datum/signal/signal, var/filter = null) - signal.transmission_method = TRANSMISSION_RADIO - if(radio_connection) - //use_power(radio_power_use) //neat idea, but causes way too much lag. - return radio_connection.post_signal(src, signal, filter) - else - qdel(signal) - -/obj/machinery/embedded_controller/radio/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, radio_filter) +/obj/machinery/embedded_controller + var/datum/computer/file/embedded_program/program //the currently executing program + + name = "Embedded Controller" + anchored = 1 + + use_power = IDLE_POWER_USE + idle_power_usage = 10 + + var/on = 1 + +/obj/machinery/embedded_controller/proc/post_signal(datum/signal/signal, comm_line) + return 0 + +/obj/machinery/embedded_controller/receive_signal(datum/signal/signal, receive_method, receive_param) + if(!signal || signal.encryption) return + + if(program) + program.receive_signal(signal, receive_method, receive_param) + //spawn(5) program.process() //no, program.process sends some signals and machines respond and we here again and we lag -rastaf0 + +/obj/machinery/embedded_controller/process() + if(program) + program.process() + + update_icon() + src.updateDialog() + +/obj/machinery/embedded_controller/attack_ghost(mob/user as mob) + src.ui_interact(user) + +/obj/machinery/embedded_controller/attack_ai(mob/user as mob) + src.ui_interact(user) + +/obj/machinery/embedded_controller/attack_hand(mob/user as mob) + if(!user.IsAdvancedToolUser()) + return 0 + src.ui_interact(user) + +/obj/machinery/embedded_controller/ui_interact() + return + +/obj/machinery/embedded_controller/radio + icon = 'icons/obj/airlock_machines.dmi' + icon_state = "airlock_control_standby" + power_channel = ENVIRON + density = 0 + + var/id_tag + //var/radio_power_use = 50 //power used to xmit signals + + var/frequency = 1379 + var/radio_filter = null + var/datum/radio_frequency/radio_connection + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/machinery/embedded_controller/radio/Initialize() + ..() + set_frequency(frequency) + +/obj/machinery/embedded_controller/radio/Destroy() + if(SSradio) + SSradio.remove_object(src, frequency) + radio_connection = null + return ..() + +/obj/machinery/embedded_controller/radio/update_icon() + if(on && program) + if(program.memory["processing"]) + icon_state = "airlock_control_process" + else + icon_state = "airlock_control_standby" + else + icon_state = "airlock_control_off" + +/obj/machinery/embedded_controller/radio/post_signal(datum/signal/signal, var/filter = null) + signal.transmission_method = TRANSMISSION_RADIO + if(radio_connection) + //use_power(radio_power_use) //neat idea, but causes way too much lag. + return radio_connection.post_signal(src, signal, filter) + else + qdel(signal) + +/obj/machinery/embedded_controller/radio/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, radio_filter) diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm index 1f42a6a78c4..b7b7e35eef6 100644 --- a/code/game/machinery/flasher.dm +++ b/code/game/machinery/flasher.dm @@ -1,153 +1,153 @@ -// It is a gizmo that flashes a small area - -/obj/machinery/flasher - name = "Mounted flash" - desc = "A wall-mounted flashbulb device." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "mflash1" - max_integrity = 250 - integrity_failure = 100 - damage_deflection = 10 - var/id = null - var/range = 2 //this is roughly the size of brig cell - var/disable = 0 - var/last_flash = 0 //Don't want it getting spammed like regular flashes - var/strength = 5 //How weakened targets are when flashed. - var/base_state = "mflash" - anchored = 1 - -/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored - name = "portable flasher" - desc = "A portable flashing device. Wrench to activate and deactivate. Cannot detect slow movements." - icon_state = "pflash1" - strength = 4 - anchored = 0 - base_state = "pflash" - density = 1 - -/* -/obj/machinery/flasher/New() - sleep(4) //<--- What the fuck are you doing? D= - sd_set_light(2) -*/ -/obj/machinery/flasher/power_change() - if( powered() ) - stat &= ~NOPOWER - icon_state = "[base_state]1" -// sd_set_light(2) - else - stat |= ~NOPOWER - icon_state = "[base_state]1-p" -// sd_set_light(0) - -//Let the AI trigger them directly. -/obj/machinery/flasher/attack_ai(mob/user) - if(anchored) - return flash() - -/obj/machinery/flasher/attack_ghost(mob/user) - if(anchored && user.can_advanced_admin_interact()) - return flash() - -/obj/machinery/flasher/proc/flash() - if(!(powered())) - return - - if((disable) || (last_flash && world.time < last_flash + 150)) - return - - playsound(loc, 'sound/weapons/flash.ogg', 100, 1) - flick("[base_state]_flash", src) - last_flash = world.time - use_power(1000) - - for(var/mob/living/L in viewers(src, null)) - if(get_dist(src, L) > range) - continue - - if(L.flash_eyes(affect_silicon = 1)) - L.Weaken(strength) - if(L.weakeyes) - L.Weaken(strength * 1.5) - L.visible_message("[L] gasps and shields [L.p_their()] eyes!") - -/obj/machinery/flasher/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - ..(severity) - return - if(prob(75/severity)) - flash() - ..(severity) - -/obj/machinery/flasher/portable/HasProximity(atom/movable/AM as mob|obj) - if((disable) || (last_flash && world.time < last_flash + 150)) - return - - if(istype(AM, /mob/living/carbon)) - var/mob/living/carbon/M = AM - if((M.m_intent != MOVE_INTENT_WALK) && (anchored)) - flash() - -//Don't want to render prison breaks impossible -/obj/machinery/flasher/portable/wirecutter_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - disable = !disable - if(disable) - user.visible_message("[user] has disconnected [src]'s flashbulb!", "You disconnect [src]'s flashbulb!") - if(!disable) - user.visible_message("[user] has connected [src]'s flashbulb!", "You connect [src]'s flashbulb!") - -/obj/machinery/flasher/portable/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - anchored = !anchored - if(anchored) - WRENCH_ANCHOR_MESSAGE - overlays.Cut() - else if(anchored) - WRENCH_UNANCHOR_MESSAGE - overlays += "[base_state]-s" - -// Flasher button -/obj/machinery/flasher_button - name = "flasher button" - desc = "A remote control switch for a mounted flasher." - icon = 'icons/obj/objects.dmi' - icon_state = "launcherbtt" - var/id = null - var/active = 0 - anchored = 1.0 - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - -/obj/machinery/flasher_button/attack_ai(mob/user as mob) - return attack_hand(user) - -/obj/machinery/flasher_button/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - return attack_hand(user) - -/obj/machinery/flasher_button/attack_hand(mob/user as mob) - if(stat & (NOPOWER|BROKEN)) - return - if(active) - return - - use_power(5) - - active = 1 - icon_state = "launcheract" - - for(var/obj/machinery/flasher/M in world) - if(M.id == id) - spawn() - M.flash() - - sleep(50) - - icon_state = "launcherbtt" - active = 0 +// It is a gizmo that flashes a small area + +/obj/machinery/flasher + name = "Mounted flash" + desc = "A wall-mounted flashbulb device." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "mflash1" + max_integrity = 250 + integrity_failure = 100 + damage_deflection = 10 + var/id = null + var/range = 2 //this is roughly the size of brig cell + var/disable = 0 + var/last_flash = 0 //Don't want it getting spammed like regular flashes + var/strength = 5 //How weakened targets are when flashed. + var/base_state = "mflash" + anchored = 1 + +/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored + name = "portable flasher" + desc = "A portable flashing device. Wrench to activate and deactivate. Cannot detect slow movements." + icon_state = "pflash1" + strength = 4 + anchored = 0 + base_state = "pflash" + density = 1 + +/* +/obj/machinery/flasher/New() + sleep(4) //<--- What the fuck are you doing? D= + sd_set_light(2) +*/ +/obj/machinery/flasher/power_change() + if( powered() ) + stat &= ~NOPOWER + icon_state = "[base_state]1" +// sd_set_light(2) + else + stat |= ~NOPOWER + icon_state = "[base_state]1-p" +// sd_set_light(0) + +//Let the AI trigger them directly. +/obj/machinery/flasher/attack_ai(mob/user) + if(anchored) + return flash() + +/obj/machinery/flasher/attack_ghost(mob/user) + if(anchored && user.can_advanced_admin_interact()) + return flash() + +/obj/machinery/flasher/proc/flash() + if(!(powered())) + return + + if((disable) || (last_flash && world.time < last_flash + 150)) + return + + playsound(loc, 'sound/weapons/flash.ogg', 100, 1) + flick("[base_state]_flash", src) + last_flash = world.time + use_power(1000) + + for(var/mob/living/L in viewers(src, null)) + if(get_dist(src, L) > range) + continue + + if(L.flash_eyes(affect_silicon = 1)) + L.Weaken(strength) + if(L.weakeyes) + L.Weaken(strength * 1.5) + L.visible_message("[L] gasps and shields [L.p_their()] eyes!") + +/obj/machinery/flasher/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + ..(severity) + return + if(prob(75/severity)) + flash() + ..(severity) + +/obj/machinery/flasher/portable/HasProximity(atom/movable/AM as mob|obj) + if((disable) || (last_flash && world.time < last_flash + 150)) + return + + if(istype(AM, /mob/living/carbon)) + var/mob/living/carbon/M = AM + if((M.m_intent != MOVE_INTENT_WALK) && (anchored)) + flash() + +//Don't want to render prison breaks impossible +/obj/machinery/flasher/portable/wirecutter_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + disable = !disable + if(disable) + user.visible_message("[user] has disconnected [src]'s flashbulb!", "You disconnect [src]'s flashbulb!") + if(!disable) + user.visible_message("[user] has connected [src]'s flashbulb!", "You connect [src]'s flashbulb!") + +/obj/machinery/flasher/portable/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + anchored = !anchored + if(anchored) + WRENCH_ANCHOR_MESSAGE + overlays.Cut() + else if(anchored) + WRENCH_UNANCHOR_MESSAGE + overlays += "[base_state]-s" + +// Flasher button +/obj/machinery/flasher_button + name = "flasher button" + desc = "A remote control switch for a mounted flasher." + icon = 'icons/obj/objects.dmi' + icon_state = "launcherbtt" + var/id = null + var/active = 0 + anchored = 1.0 + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + +/obj/machinery/flasher_button/attack_ai(mob/user as mob) + return attack_hand(user) + +/obj/machinery/flasher_button/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + return attack_hand(user) + +/obj/machinery/flasher_button/attack_hand(mob/user as mob) + if(stat & (NOPOWER|BROKEN)) + return + if(active) + return + + use_power(5) + + active = 1 + icon_state = "launcheract" + + for(var/obj/machinery/flasher/M in world) + if(M.id == id) + spawn() + M.flash() + + sleep(50) + + icon_state = "launcherbtt" + active = 0 diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index ef0ae0f2812..03b28423830 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -1,517 +1,517 @@ -/* holograms! - * Contains: - * Holopad - * hologram - * Other stuff - */ - -/* -Revised. Original based on space ninja hologram code. Which is also mine. /N -How it works: -AI clicks on holopad in camera view. View centers on holopad. -AI clicks again on the holopad to display a hologram. hologram stays as long as AI is looking at the pad and it (the hologram) is in range of the pad. -AI can use the directional keys to move the hologram around, provided the above conditions are met and the AI in question is the holopad's master. -Only one AI may project from a holopad at any given time. -AI may cancel the hologram at any time by clicking on the holopad once more. -Possible to do for anyone motivated enough: - Give an AI variable for different hologram icons. - Itegrate EMP effect to disable the unit. -*/ - - -/* - * Holopad - */ - -// HOLOPAD MODE -// 0 = RANGE BASED -// 1 = AREA BASED -#define HOLOPAD_PASSIVE_POWER_USAGE 1 -#define HOLOGRAM_POWER_USAGE 2 -#define RANGE_BASED 0 -#define AREA_BASED 1 - -#define HOLOPAD_MODE RANGE_BASED - -var/list/holopads = list() - -/obj/machinery/hologram/holopad - name = "holopad" - desc = "It's a floor-mounted device for projecting holographic images." - icon_state = "holopad0" - anchored = 1 - use_power = IDLE_POWER_USE - idle_power_usage = 5 - active_power_usage = 100 - layer = TURF_LAYER+0.1 //Preventing mice and drones from sneaking under them. - plane = FLOOR_PLANE - max_integrity = 300 - armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 0) - var/list/masters = list()//List of living mobs that use the holopad - var/list/holorays = list()//Holoray-mob link. - var/last_request = 0 //to prevent request spam. ~Carn - var/holo_range = 5 // Change to change how far the AI can move away from the holopad before deactivating. - var/temp = "" - var/list/holo_calls //array of /datum/holocalls - var/datum/holocall/outgoing_call //do not modify the datums only check and call the public procs - var/static/force_answer_call = FALSE //Calls will be automatically answered after a couple rings, here for debugging - var/obj/effect/overlay/holoray/ray - var/ringing = FALSE - var/dialling_input = FALSE //The user is currently selecting where to send their call - -/obj/machinery/hologram/holopad/New() - ..() - holopads += src - component_parts = list() - component_parts += new /obj/item/circuitboard/holopad(null) - component_parts += new /obj/item/stock_parts/capacitor(null) - RefreshParts() - -/obj/machinery/hologram/holopad/Destroy() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.ConnectionFailure(src) - - for(var/I in masters) - clear_holo(I) - holopads -= src - return ..() - -/obj/machinery/hologram/holopad/power_change() - if(powered()) - stat &= ~NOPOWER - else - stat |= NOPOWER - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - -/obj/machinery/hologram/holopad/obj_break() - . = ..() - if(outgoing_call) - outgoing_call.ConnectionFailure(src) - -/obj/machinery/hologram/holopad/RefreshParts() - var/holograph_range = 4 - for(var/obj/item/stock_parts/capacitor/B in component_parts) - holograph_range += 1 * B.rating - holo_range = holograph_range - -/obj/machinery/hologram/holopad/attackby(obj/item/I, mob/user, params) - if(exchange_parts(user, I)) - return - return ..() - -/obj/machinery/hologram/holopad/screwdriver_act(mob/user, obj/item/I) - . = TRUE - default_deconstruction_screwdriver(user, "holopad_open", "holopad0", I) - - -/obj/machinery/hologram/holopad/wrench_act(mob/user, obj/item/I) - . = TRUE - default_unfasten_wrench(user, I) - -/obj/machinery/hologram/holopad/crowbar_act(mob/user, obj/item/I) - . = TRUE - default_deconstruction_crowbar(user, I) - -/obj/machinery/hologram/holopad/attack_hand(mob/living/carbon/human/user) - if(..()) - return - - if(outgoing_call) - return - - user.set_machine(src) - interact(user) - -/obj/machinery/hologram/holopad/AltClick(mob/living/carbon/human/user) - if(..()) - return - if(isAI(user)) - hangup_all_calls() - return - -//Stop ringing the AI!! -/obj/machinery/hologram/holopad/proc/hangup_all_calls() - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.Disconnect(src) - -/obj/machinery/hologram/holopad/interact(mob/living/carbon/human/user) //Carn: hologram requests. - if(!istype(user)) - return - if(!anchored) - return - - var/dat - if(temp) - dat = temp - else - dat = "Request an AI's presence.
        " - dat += "Call another holopad.
        " - - if(LAZYLEN(holo_calls)) - dat += "=====================================================
        " - - var/one_answered_call = FALSE - var/one_unanswered_call = FALSE - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad != src) - dat += "Answer call from [get_area(HC.calling_holopad)].
        " - one_unanswered_call = TRUE - else - one_answered_call = TRUE - - if(one_answered_call && one_unanswered_call) - dat += "=====================================================
        " - //we loop twice for formatting - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad == src) - dat += "Disconnect call from [HC.user].
        " - - var/area/area = get_area(src) - var/datum/browser/popup = new(user, "holopad", "[area] holopad", 400, 300) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/hologram/holopad/Topic(href, href_list) - if(..() || isAI(usr)) - return - add_fingerprint(usr) - if(stat & NOPOWER) - return - if(href_list["AIrequest"]) - if(last_request + 200 < world.time) - last_request = world.time - temp = "You requested an AI's presence.
        " - temp += "Main Menu" - var/area/area = get_area(src) - for(var/mob/living/silicon/ai/AI in ai_list) - if(!AI.client) - continue - to_chat(AI, "Your presence is requested at \the [area].") - else - temp = "A request for AI presence was already sent recently.
        " - temp += "Main Menu" - - else if(href_list["Holocall"]) - if(outgoing_call) - return - if(dialling_input) - to_chat(usr, "Finish dialling first!") - return - temp = "You must stand on the holopad to make a call!
        " - temp += "Main Menu" - if(usr.loc == loc) - var/list/callnames = list() - for(var/I in holopads) - var/area/A = get_area(I) - if(A) - LAZYADD(callnames[A], I) - callnames -= get_area(src) - var/list/sorted_callnames = sortAtom(callnames) - dialling_input = TRUE - var/result = input(usr, "Choose an area to call", "Holocall") as null|anything in sorted_callnames - dialling_input = FALSE - if(QDELETED(usr) || !result || outgoing_call) - return - - if(usr.loc == loc) - temp = "Dialing...
        " - temp += "Main Menu" - new /datum/holocall(usr, src, callnames[result]) - - else if(href_list["connectcall"]) - var/datum/holocall/call_to_connect = locateUID(href_list["connectcall"]) - if(!QDELETED(call_to_connect) && (call_to_connect in holo_calls)) - call_to_connect.Answer(src) - temp = "" - - else if(href_list["disconnectcall"]) - var/datum/holocall/call_to_disconnect = locateUID(href_list["disconnectcall"]) - if(!QDELETED(call_to_disconnect)) - call_to_disconnect.Disconnect(src) - temp = "" - - else if(href_list["mainmenu"]) - temp = "" - if(outgoing_call) - outgoing_call.Disconnect() - - updateDialog() - -//do not allow AIs to answer calls or people will use it to meta the AI satellite -/obj/machinery/hologram/holopad/attack_ai(mob/living/silicon/ai/user) - if(!istype(user)) - return - if(outgoing_call) - return - /*There are pretty much only three ways to interact here. - I don't need to check for client since they're clicking on an object. - This may change in the future but for now will suffice.*/ - if(user.eyeobj.loc != loc)//Set client eye on the object if it's not already. - user.eyeobj.setLoc(get_turf(src)) - else if(!LAZYLEN(masters) || !masters[user])//If there is no hologram, possibly make one. - activate_holo(user, 1) - else//If there is a hologram, remove it. - clear_holo(user) - -/obj/machinery/hologram/holopad/process() - for(var/I in masters) - var/mob/living/master = I - if((stat & NOPOWER) || !validate_user(master) || !anchored) - clear_holo(master) - - if(outgoing_call) - outgoing_call.Check() - - ringing = FALSE - - for(var/I in holo_calls) - var/datum/holocall/HC = I - //Sanity check and skip if no longer valid call - if(!HC.Check()) - atom_say("Call was terminated at remote terminal.") - continue - - if(HC.connected_holopad != src) - if(force_answer_call && world.time > (HC.call_start_time + (HOLOPAD_MAX_DIAL_TIME / 2))) - HC.Answer(src) - break - if(outgoing_call) - HC.Disconnect(src)//can't answer calls while calling - else - playsound(src, 'sound/machines/twobeep.ogg', 100) //bring, bring! - ringing = TRUE - - update_icon() - - -//Try to transfer hologram to another pad that can project on T -/obj/machinery/hologram/holopad/proc/transfer_to_nearby_pad(turf/T, mob/holo_owner) - if(!isAI(holo_owner)) - return - for(var/pad in holopads) - var/obj/machinery/hologram/holopad/another = pad - if(another == src) - continue - if(another.validate_location(T)) - var/obj/effect/overlay/holo_pad_hologram/h = masters[holo_owner] - unset_holo(holo_owner) - another.set_holo(holo_owner, h) - return TRUE - return FALSE - -/obj/machinery/hologram/holopad/proc/validate_user(mob/living/user) - if(QDELETED(user) || user.incapacitated() || !user.client) - return FALSE - - if(istype(user, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/AI = user - if(!AI.current) - return FALSE - return TRUE - -//Can we display holos there -//Area check instead of line of sight check because this is a called a lot if AI wants to move around. -/obj/machinery/hologram/holopad/proc/validate_location(turf/T,check_los = FALSE) - if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src)) - return TRUE - return FALSE - - -/obj/machinery/hologram/holopad/proc/move_hologram(mob/living/user, turf/new_turf) - if(masters[user]) - var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] - var/transfered = FALSE - if(!validate_location(new_turf)) - if(!transfer_to_nearby_pad(new_turf,user)) - clear_holo(user) - return FALSE - else - transfered = TRUE - //All is good. - holo.setDir(get_dir(holo.loc, new_turf)) - holo.forceMove(new_turf) - if(!transfered) - update_holoray(user,new_turf) - return TRUE - -/obj/machinery/hologram/holopad/proc/activate_holo(mob/living/user, var/force = 0) - var/mob/living/silicon/ai/AI = user - if(!istype(AI)) - AI = null - if(AI && !force && AI.eyeobj.loc != loc) // allows holopads to pass off holograms to the next holopad in the chain - to_chat(user, "ERROR: Unable to project hologram.") - if(!(stat & NOPOWER) && (!AI || force)) - if(AI && (istype(AI.current, /obj/machinery/hologram/holopad))) - to_chat(user, "ERROR: Image feed in progress.") - return - - var/obj/effect/overlay/holo_pad_hologram/hologram = new(loc)//Spawn a blank effect at the location. - if(isAI(user)) - hologram.icon = AI.holo_icon - else //make it like real life - hologram.icon = getHologramIcon(get_id_photo(user)) - hologram.icon_state = user.icon_state - hologram.alpha = 100 - hologram.Impersonation = user - - hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. - hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. - hologram.anchored = 1//So space wind cannot drag it. - hologram.name = "[user.name] (hologram)"//If someone decides to right click. - hologram.set_light(2) //hologram lighting - move_hologram() - - set_holo(user, hologram) - - if(!masters[user])//If there is not already a hologram. - visible_message("A holographic image of [user] flicks to life right before your eyes!") - - return hologram - - - to_chat(user, "ERROR: Hologram Projection Malfunction.") - clear_holo(user)//safety check - -/*This is the proc for special two-way communication between AI and holopad/people talking near holopad. -For the other part of the code, check silicon say.dm. Particularly robot talk.*/ -/obj/machinery/hologram/holopad/hear_talk(atom/movable/speaker, list/message_pieces, verb) - if(speaker && masters.len)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios. - for(var/mob/living/silicon/ai/master in masters) - if(masters[master] && speaker != master) - master.relay_speech(speaker, message_pieces, verb) - - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad == src && speaker != HC.hologram) - HC.user.hear_say(message_pieces, verb, speaker = speaker) - - if(outgoing_call && speaker == outgoing_call.user) - outgoing_call.hologram.atom_say(multilingual_to_message(message_pieces)) - - - -/obj/machinery/hologram/holopad/proc/SetLightsAndPower() - var/total_users = masters.len + LAZYLEN(holo_calls) - use_power = total_users > 0 ? ACTIVE_POWER_USE : IDLE_POWER_USE - active_power_usage = HOLOPAD_PASSIVE_POWER_USAGE + (HOLOGRAM_POWER_USAGE * total_users) - if(total_users) - set_light(2) - icon_state = "holopad1" - else - set_light(0) - icon_state = "holopad0" - update_icon() - -/obj/machinery/hologram/holopad/update_icon() - var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) - if(icon_state == "holopad_open") - return - else if(ringing) - icon_state = "holopad_ringing" - else if(total_users) - icon_state = "holopad1" - else - icon_state = "holopad0" - - -/obj/machinery/hologram/holopad/proc/set_holo(mob/living/user, var/obj/effect/overlay/holo_pad_hologram/h) - masters[user] = h - holorays[user] = new /obj/effect/overlay/holoray(loc) - var/mob/living/silicon/ai/AI = user - if(istype(AI)) - AI.current = src - SetLightsAndPower() - update_holoray(user, get_turf(loc)) - return TRUE - -/obj/machinery/hologram/holopad/proc/clear_holo(mob/living/user) - qdel(masters[user]) // Get rid of user's hologram - unset_holo(user) - return TRUE - -/obj/machinery/hologram/holopad/proc/unset_holo(mob/living/user) - var/mob/living/silicon/ai/AI = user - if(istype(AI) && AI.current == src) - AI.current = null - masters -= user // Discard AI from the list of those who use holopad - qdel(holorays[user]) - holorays -= user - SetLightsAndPower() - return TRUE - -/obj/machinery/hologram/holopad/proc/update_holoray(mob/living/user, turf/new_turf) - var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] - var/obj/effect/overlay/holoray/ray = holorays[user] - var/disty = holo.y - ray.y - var/distx = holo.x - ray.x - var/newangle - if(!disty) - if(distx >= 0) - newangle = 90 - else - newangle = 270 - else - newangle = arctan(distx/disty) - if(disty < 0) - newangle += 180 - else if(distx < 0) - newangle += 360 - var/matrix/M = matrix() - if(get_dist(get_turf(holo), new_turf) <= 1) - animate(ray, transform = turn(M.Scale(1, sqrt(distx*distx+disty*disty)), newangle), time = 1) - else - ray.transform = turn(M.Scale(1, sqrt(distx*distx+disty*disty)), newangle) - - -/obj/effect/overlay/holo_pad_hologram - var/mob/living/Impersonation - var/datum/holocall/HC - -/obj/effect/overlay/holo_pad_hologram/Destroy() - Impersonation = null - if(!QDELETED(HC)) - HC.Disconnect(HC.calling_holopad) - return ..() - -/obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0) - return 1 - -/obj/effect/overlay/holo_pad_hologram/examine(mob/user) - if(Impersonation) - . = Impersonation.examine(user) - else - . = ..() - - -/obj/effect/overlay/holoray - name = "holoray" - icon = 'icons/effects/96x96.dmi' - icon_state = "holoray" - layer = FLY_LAYER - density = FALSE - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_ICON - pixel_x = -32 - pixel_y = -32 - alpha = 100 - -/* - * Other Stuff: Is this even used? - */ -/obj/machinery/hologram/projector - name = "hologram projector" - desc = "It makes a hologram appear...with magnets or something..." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "hologram0" - -#undef HOLOPAD_PASSIVE_POWER_USAGE -#undef HOLOGRAM_POWER_USAGE +/* holograms! + * Contains: + * Holopad + * hologram + * Other stuff + */ + +/* +Revised. Original based on space ninja hologram code. Which is also mine. /N +How it works: +AI clicks on holopad in camera view. View centers on holopad. +AI clicks again on the holopad to display a hologram. hologram stays as long as AI is looking at the pad and it (the hologram) is in range of the pad. +AI can use the directional keys to move the hologram around, provided the above conditions are met and the AI in question is the holopad's master. +Only one AI may project from a holopad at any given time. +AI may cancel the hologram at any time by clicking on the holopad once more. +Possible to do for anyone motivated enough: + Give an AI variable for different hologram icons. + Itegrate EMP effect to disable the unit. +*/ + + +/* + * Holopad + */ + +// HOLOPAD MODE +// 0 = RANGE BASED +// 1 = AREA BASED +#define HOLOPAD_PASSIVE_POWER_USAGE 1 +#define HOLOGRAM_POWER_USAGE 2 +#define RANGE_BASED 0 +#define AREA_BASED 1 + +#define HOLOPAD_MODE RANGE_BASED + +var/list/holopads = list() + +/obj/machinery/hologram/holopad + name = "holopad" + desc = "It's a floor-mounted device for projecting holographic images." + icon_state = "holopad0" + anchored = 1 + use_power = IDLE_POWER_USE + idle_power_usage = 5 + active_power_usage = 100 + layer = TURF_LAYER+0.1 //Preventing mice and drones from sneaking under them. + plane = FLOOR_PLANE + max_integrity = 300 + armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 0) + var/list/masters = list()//List of living mobs that use the holopad + var/list/holorays = list()//Holoray-mob link. + var/last_request = 0 //to prevent request spam. ~Carn + var/holo_range = 5 // Change to change how far the AI can move away from the holopad before deactivating. + var/temp = "" + var/list/holo_calls //array of /datum/holocalls + var/datum/holocall/outgoing_call //do not modify the datums only check and call the public procs + var/static/force_answer_call = FALSE //Calls will be automatically answered after a couple rings, here for debugging + var/obj/effect/overlay/holoray/ray + var/ringing = FALSE + var/dialling_input = FALSE //The user is currently selecting where to send their call + +/obj/machinery/hologram/holopad/New() + ..() + holopads += src + component_parts = list() + component_parts += new /obj/item/circuitboard/holopad(null) + component_parts += new /obj/item/stock_parts/capacitor(null) + RefreshParts() + +/obj/machinery/hologram/holopad/Destroy() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.ConnectionFailure(src) + + for(var/I in masters) + clear_holo(I) + holopads -= src + return ..() + +/obj/machinery/hologram/holopad/power_change() + if(powered()) + stat &= ~NOPOWER + else + stat |= NOPOWER + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + +/obj/machinery/hologram/holopad/obj_break() + . = ..() + if(outgoing_call) + outgoing_call.ConnectionFailure(src) + +/obj/machinery/hologram/holopad/RefreshParts() + var/holograph_range = 4 + for(var/obj/item/stock_parts/capacitor/B in component_parts) + holograph_range += 1 * B.rating + holo_range = holograph_range + +/obj/machinery/hologram/holopad/attackby(obj/item/I, mob/user, params) + if(exchange_parts(user, I)) + return + return ..() + +/obj/machinery/hologram/holopad/screwdriver_act(mob/user, obj/item/I) + . = TRUE + default_deconstruction_screwdriver(user, "holopad_open", "holopad0", I) + + +/obj/machinery/hologram/holopad/wrench_act(mob/user, obj/item/I) + . = TRUE + default_unfasten_wrench(user, I) + +/obj/machinery/hologram/holopad/crowbar_act(mob/user, obj/item/I) + . = TRUE + default_deconstruction_crowbar(user, I) + +/obj/machinery/hologram/holopad/attack_hand(mob/living/carbon/human/user) + if(..()) + return + + if(outgoing_call) + return + + user.set_machine(src) + interact(user) + +/obj/machinery/hologram/holopad/AltClick(mob/living/carbon/human/user) + if(..()) + return + if(isAI(user)) + hangup_all_calls() + return + +//Stop ringing the AI!! +/obj/machinery/hologram/holopad/proc/hangup_all_calls() + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.Disconnect(src) + +/obj/machinery/hologram/holopad/interact(mob/living/carbon/human/user) //Carn: hologram requests. + if(!istype(user)) + return + if(!anchored) + return + + var/dat + if(temp) + dat = temp + else + dat = "Request an AI's presence.
        " + dat += "Call another holopad.
        " + + if(LAZYLEN(holo_calls)) + dat += "=====================================================
        " + + var/one_answered_call = FALSE + var/one_unanswered_call = FALSE + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad != src) + dat += "Answer call from [get_area(HC.calling_holopad)].
        " + one_unanswered_call = TRUE + else + one_answered_call = TRUE + + if(one_answered_call && one_unanswered_call) + dat += "=====================================================
        " + //we loop twice for formatting + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad == src) + dat += "Disconnect call from [HC.user].
        " + + var/area/area = get_area(src) + var/datum/browser/popup = new(user, "holopad", "[area] holopad", 400, 300) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/hologram/holopad/Topic(href, href_list) + if(..() || isAI(usr)) + return + add_fingerprint(usr) + if(stat & NOPOWER) + return + if(href_list["AIrequest"]) + if(last_request + 200 < world.time) + last_request = world.time + temp = "You requested an AI's presence.
        " + temp += "Main Menu" + var/area/area = get_area(src) + for(var/mob/living/silicon/ai/AI in ai_list) + if(!AI.client) + continue + to_chat(AI, "Your presence is requested at \the [area].") + else + temp = "A request for AI presence was already sent recently.
        " + temp += "Main Menu" + + else if(href_list["Holocall"]) + if(outgoing_call) + return + if(dialling_input) + to_chat(usr, "Finish dialling first!") + return + temp = "You must stand on the holopad to make a call!
        " + temp += "Main Menu" + if(usr.loc == loc) + var/list/callnames = list() + for(var/I in holopads) + var/area/A = get_area(I) + if(A) + LAZYADD(callnames[A], I) + callnames -= get_area(src) + var/list/sorted_callnames = sortAtom(callnames) + dialling_input = TRUE + var/result = input(usr, "Choose an area to call", "Holocall") as null|anything in sorted_callnames + dialling_input = FALSE + if(QDELETED(usr) || !result || outgoing_call) + return + + if(usr.loc == loc) + temp = "Dialing...
        " + temp += "Main Menu" + new /datum/holocall(usr, src, callnames[result]) + + else if(href_list["connectcall"]) + var/datum/holocall/call_to_connect = locateUID(href_list["connectcall"]) + if(!QDELETED(call_to_connect) && (call_to_connect in holo_calls)) + call_to_connect.Answer(src) + temp = "" + + else if(href_list["disconnectcall"]) + var/datum/holocall/call_to_disconnect = locateUID(href_list["disconnectcall"]) + if(!QDELETED(call_to_disconnect)) + call_to_disconnect.Disconnect(src) + temp = "" + + else if(href_list["mainmenu"]) + temp = "" + if(outgoing_call) + outgoing_call.Disconnect() + + updateDialog() + +//do not allow AIs to answer calls or people will use it to meta the AI satellite +/obj/machinery/hologram/holopad/attack_ai(mob/living/silicon/ai/user) + if(!istype(user)) + return + if(outgoing_call) + return + /*There are pretty much only three ways to interact here. + I don't need to check for client since they're clicking on an object. + This may change in the future but for now will suffice.*/ + if(user.eyeobj.loc != loc)//Set client eye on the object if it's not already. + user.eyeobj.setLoc(get_turf(src)) + else if(!LAZYLEN(masters) || !masters[user])//If there is no hologram, possibly make one. + activate_holo(user, 1) + else//If there is a hologram, remove it. + clear_holo(user) + +/obj/machinery/hologram/holopad/process() + for(var/I in masters) + var/mob/living/master = I + if((stat & NOPOWER) || !validate_user(master) || !anchored) + clear_holo(master) + + if(outgoing_call) + outgoing_call.Check() + + ringing = FALSE + + for(var/I in holo_calls) + var/datum/holocall/HC = I + //Sanity check and skip if no longer valid call + if(!HC.Check()) + atom_say("Call was terminated at remote terminal.") + continue + + if(HC.connected_holopad != src) + if(force_answer_call && world.time > (HC.call_start_time + (HOLOPAD_MAX_DIAL_TIME / 2))) + HC.Answer(src) + break + if(outgoing_call) + HC.Disconnect(src)//can't answer calls while calling + else + playsound(src, 'sound/machines/twobeep.ogg', 100) //bring, bring! + ringing = TRUE + + update_icon() + + +//Try to transfer hologram to another pad that can project on T +/obj/machinery/hologram/holopad/proc/transfer_to_nearby_pad(turf/T, mob/holo_owner) + if(!isAI(holo_owner)) + return + for(var/pad in holopads) + var/obj/machinery/hologram/holopad/another = pad + if(another == src) + continue + if(another.validate_location(T)) + var/obj/effect/overlay/holo_pad_hologram/h = masters[holo_owner] + unset_holo(holo_owner) + another.set_holo(holo_owner, h) + return TRUE + return FALSE + +/obj/machinery/hologram/holopad/proc/validate_user(mob/living/user) + if(QDELETED(user) || user.incapacitated() || !user.client) + return FALSE + + if(istype(user, /mob/living/silicon/ai)) + var/mob/living/silicon/ai/AI = user + if(!AI.current) + return FALSE + return TRUE + +//Can we display holos there +//Area check instead of line of sight check because this is a called a lot if AI wants to move around. +/obj/machinery/hologram/holopad/proc/validate_location(turf/T,check_los = FALSE) + if(T.z == z && get_dist(T, src) <= holo_range && T.loc == get_area(src)) + return TRUE + return FALSE + + +/obj/machinery/hologram/holopad/proc/move_hologram(mob/living/user, turf/new_turf) + if(masters[user]) + var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] + var/transfered = FALSE + if(!validate_location(new_turf)) + if(!transfer_to_nearby_pad(new_turf,user)) + clear_holo(user) + return FALSE + else + transfered = TRUE + //All is good. + holo.setDir(get_dir(holo.loc, new_turf)) + holo.forceMove(new_turf) + if(!transfered) + update_holoray(user,new_turf) + return TRUE + +/obj/machinery/hologram/holopad/proc/activate_holo(mob/living/user, var/force = 0) + var/mob/living/silicon/ai/AI = user + if(!istype(AI)) + AI = null + if(AI && !force && AI.eyeobj.loc != loc) // allows holopads to pass off holograms to the next holopad in the chain + to_chat(user, "ERROR: Unable to project hologram.") + if(!(stat & NOPOWER) && (!AI || force)) + if(AI && (istype(AI.current, /obj/machinery/hologram/holopad))) + to_chat(user, "ERROR: Image feed in progress.") + return + + var/obj/effect/overlay/holo_pad_hologram/hologram = new(loc)//Spawn a blank effect at the location. + if(isAI(user)) + hologram.icon = AI.holo_icon + else //make it like real life + hologram.icon = getHologramIcon(get_id_photo(user)) + hologram.icon_state = user.icon_state + hologram.alpha = 100 + hologram.Impersonation = user + + hologram.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it. + hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them. + hologram.anchored = 1//So space wind cannot drag it. + hologram.name = "[user.name] (hologram)"//If someone decides to right click. + hologram.set_light(2) //hologram lighting + move_hologram() + + set_holo(user, hologram) + + if(!masters[user])//If there is not already a hologram. + visible_message("A holographic image of [user] flicks to life right before your eyes!") + + return hologram + + + to_chat(user, "ERROR: Hologram Projection Malfunction.") + clear_holo(user)//safety check + +/*This is the proc for special two-way communication between AI and holopad/people talking near holopad. +For the other part of the code, check silicon say.dm. Particularly robot talk.*/ +/obj/machinery/hologram/holopad/hear_talk(atom/movable/speaker, list/message_pieces, verb) + if(speaker && masters.len)//Master is mostly a safety in case lag hits or something. Radio_freq so AIs dont hear holopad stuff through radios. + for(var/mob/living/silicon/ai/master in masters) + if(masters[master] && speaker != master) + master.relay_speech(speaker, message_pieces, verb) + + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad == src && speaker != HC.hologram) + HC.user.hear_say(message_pieces, verb, speaker = speaker) + + if(outgoing_call && speaker == outgoing_call.user) + outgoing_call.hologram.atom_say(multilingual_to_message(message_pieces)) + + + +/obj/machinery/hologram/holopad/proc/SetLightsAndPower() + var/total_users = masters.len + LAZYLEN(holo_calls) + use_power = total_users > 0 ? ACTIVE_POWER_USE : IDLE_POWER_USE + active_power_usage = HOLOPAD_PASSIVE_POWER_USAGE + (HOLOGRAM_POWER_USAGE * total_users) + if(total_users) + set_light(2) + icon_state = "holopad1" + else + set_light(0) + icon_state = "holopad0" + update_icon() + +/obj/machinery/hologram/holopad/update_icon() + var/total_users = LAZYLEN(masters) + LAZYLEN(holo_calls) + if(icon_state == "holopad_open") + return + else if(ringing) + icon_state = "holopad_ringing" + else if(total_users) + icon_state = "holopad1" + else + icon_state = "holopad0" + + +/obj/machinery/hologram/holopad/proc/set_holo(mob/living/user, var/obj/effect/overlay/holo_pad_hologram/h) + masters[user] = h + holorays[user] = new /obj/effect/overlay/holoray(loc) + var/mob/living/silicon/ai/AI = user + if(istype(AI)) + AI.current = src + SetLightsAndPower() + update_holoray(user, get_turf(loc)) + return TRUE + +/obj/machinery/hologram/holopad/proc/clear_holo(mob/living/user) + qdel(masters[user]) // Get rid of user's hologram + unset_holo(user) + return TRUE + +/obj/machinery/hologram/holopad/proc/unset_holo(mob/living/user) + var/mob/living/silicon/ai/AI = user + if(istype(AI) && AI.current == src) + AI.current = null + masters -= user // Discard AI from the list of those who use holopad + qdel(holorays[user]) + holorays -= user + SetLightsAndPower() + return TRUE + +/obj/machinery/hologram/holopad/proc/update_holoray(mob/living/user, turf/new_turf) + var/obj/effect/overlay/holo_pad_hologram/holo = masters[user] + var/obj/effect/overlay/holoray/ray = holorays[user] + var/disty = holo.y - ray.y + var/distx = holo.x - ray.x + var/newangle + if(!disty) + if(distx >= 0) + newangle = 90 + else + newangle = 270 + else + newangle = arctan(distx/disty) + if(disty < 0) + newangle += 180 + else if(distx < 0) + newangle += 360 + var/matrix/M = matrix() + if(get_dist(get_turf(holo), new_turf) <= 1) + animate(ray, transform = turn(M.Scale(1, sqrt(distx*distx+disty*disty)), newangle), time = 1) + else + ray.transform = turn(M.Scale(1, sqrt(distx*distx+disty*disty)), newangle) + + +/obj/effect/overlay/holo_pad_hologram + var/mob/living/Impersonation + var/datum/holocall/HC + +/obj/effect/overlay/holo_pad_hologram/Destroy() + Impersonation = null + if(!QDELETED(HC)) + HC.Disconnect(HC.calling_holopad) + return ..() + +/obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0) + return 1 + +/obj/effect/overlay/holo_pad_hologram/examine(mob/user) + if(Impersonation) + . = Impersonation.examine(user) + else + . = ..() + + +/obj/effect/overlay/holoray + name = "holoray" + icon = 'icons/effects/96x96.dmi' + icon_state = "holoray" + layer = FLY_LAYER + density = FALSE + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_ICON + pixel_x = -32 + pixel_y = -32 + alpha = 100 + +/* + * Other Stuff: Is this even used? + */ +/obj/machinery/hologram/projector + name = "hologram projector" + desc = "It makes a hologram appear...with magnets or something..." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "hologram0" + +#undef HOLOPAD_PASSIVE_POWER_USAGE +#undef HOLOGRAM_POWER_USAGE diff --git a/code/game/machinery/igniter.dm b/code/game/machinery/igniter.dm index e4bdb0d5478..c6918d795ff 100755 --- a/code/game/machinery/igniter.dm +++ b/code/game/machinery/igniter.dm @@ -1,126 +1,126 @@ -/obj/machinery/igniter - name = "igniter" - desc = "It's useful for igniting plasma." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "igniter1" - plane = FLOOR_PLANE - max_integrity = 300 - armor = list(melee = 50, bullet = 30, laser = 70, energy = 50, bomb = 20, bio = 0, rad = 0, fire = 100, acid = 70) - resistance_flags = FIRE_PROOF - var/id = null - var/on = FALSE - anchored = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 4 - -/obj/machinery/igniter/on - on = TRUE - -/obj/machinery/igniter/attack_ai(mob/user as mob) - return src.attack_hand(user) - - -/obj/machinery/igniter/attack_hand(mob/user as mob) - if(..()) - return - add_fingerprint(user) - - use_power(50) - src.on = !( src.on ) - src.icon_state = text("igniter[]", src.on) - return - -/obj/machinery/igniter/process() //ugh why is this even in process()? - if(src.on && !(stat & NOPOWER) ) - var/turf/location = src.loc - if(isturf(location)) - location.hotspot_expose(1000,500,1) - return 1 - -/obj/machinery/igniter/New() - ..() - icon_state = "igniter[on]" - -/obj/machinery/igniter/power_change() - if(!( stat & NOPOWER) ) - icon_state = "igniter[src.on]" - else - icon_state = "igniter0" - -// Wall mounted remote-control igniter. - -/obj/machinery/sparker - name = "Mounted igniter" - desc = "A wall-mounted ignition device." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "migniter" - resistance_flags = FIRE_PROOF - var/id = null - var/disable = FALSE - var/last_spark = FALSE - var/base_state = "migniter" - anchored = 1 - -/obj/machinery/sparker/New() - ..() - -/obj/machinery/sparker/power_change() - if( powered() && disable == 0 ) - stat &= ~NOPOWER - icon_state = "[base_state]" -// src.sd_set_light(2) - else - stat |= ~NOPOWER - icon_state = "[base_state]-p" -// src.sd_set_light(0) - -/obj/machinery/sparker/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/detective_scanner)) - return - return ..() - -/obj/machinery/sparker/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - disable = !disable - if(disable) - user.visible_message("[user] has disabled [src]!", "You disable the connection to [src].") - icon_state = "[base_state]-d" - if(!disable) - user.visible_message("[user] has reconnected [src]!", "You fix the connection to [src].") - if(powered()) - icon_state = "[base_state]" - else - icon_state = "[base_state]-p" - -/obj/machinery/sparker/attack_ai() - if(src.anchored) - return src.spark() - else - return - -/obj/machinery/sparker/proc/spark() - if(!(powered())) - return - - if((src.disable) || (src.last_spark && world.time < src.last_spark + 50)) - return - - - flick("[base_state]-spark", src) - do_sparks(2, 1, src) - src.last_spark = world.time - use_power(1000) - var/turf/location = src.loc - if(isturf(location)) - location.hotspot_expose(1000,500,1) - return 1 - -/obj/machinery/sparker/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - ..(severity) - return - spark() - ..(severity) +/obj/machinery/igniter + name = "igniter" + desc = "It's useful for igniting plasma." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "igniter1" + plane = FLOOR_PLANE + max_integrity = 300 + armor = list(melee = 50, bullet = 30, laser = 70, energy = 50, bomb = 20, bio = 0, rad = 0, fire = 100, acid = 70) + resistance_flags = FIRE_PROOF + var/id = null + var/on = FALSE + anchored = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 4 + +/obj/machinery/igniter/on + on = TRUE + +/obj/machinery/igniter/attack_ai(mob/user as mob) + return src.attack_hand(user) + + +/obj/machinery/igniter/attack_hand(mob/user as mob) + if(..()) + return + add_fingerprint(user) + + use_power(50) + src.on = !( src.on ) + src.icon_state = text("igniter[]", src.on) + return + +/obj/machinery/igniter/process() //ugh why is this even in process()? + if(src.on && !(stat & NOPOWER) ) + var/turf/location = src.loc + if(isturf(location)) + location.hotspot_expose(1000,500,1) + return 1 + +/obj/machinery/igniter/New() + ..() + icon_state = "igniter[on]" + +/obj/machinery/igniter/power_change() + if(!( stat & NOPOWER) ) + icon_state = "igniter[src.on]" + else + icon_state = "igniter0" + +// Wall mounted remote-control igniter. + +/obj/machinery/sparker + name = "Mounted igniter" + desc = "A wall-mounted ignition device." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "migniter" + resistance_flags = FIRE_PROOF + var/id = null + var/disable = FALSE + var/last_spark = FALSE + var/base_state = "migniter" + anchored = 1 + +/obj/machinery/sparker/New() + ..() + +/obj/machinery/sparker/power_change() + if( powered() && disable == 0 ) + stat &= ~NOPOWER + icon_state = "[base_state]" +// src.sd_set_light(2) + else + stat |= ~NOPOWER + icon_state = "[base_state]-p" +// src.sd_set_light(0) + +/obj/machinery/sparker/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/detective_scanner)) + return + return ..() + +/obj/machinery/sparker/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + disable = !disable + if(disable) + user.visible_message("[user] has disabled [src]!", "You disable the connection to [src].") + icon_state = "[base_state]-d" + if(!disable) + user.visible_message("[user] has reconnected [src]!", "You fix the connection to [src].") + if(powered()) + icon_state = "[base_state]" + else + icon_state = "[base_state]-p" + +/obj/machinery/sparker/attack_ai() + if(src.anchored) + return src.spark() + else + return + +/obj/machinery/sparker/proc/spark() + if(!(powered())) + return + + if((src.disable) || (src.last_spark && world.time < src.last_spark + 50)) + return + + + flick("[base_state]-spark", src) + do_sparks(2, 1, src) + src.last_spark = world.time + use_power(1000) + var/turf/location = src.loc + if(isturf(location)) + location.hotspot_expose(1000,500,1) + return 1 + +/obj/machinery/sparker/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + ..(severity) + return + spark() + ..(severity) diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm index 204a85b426e..d7b21983792 100644 --- a/code/game/machinery/iv_drip.dm +++ b/code/game/machinery/iv_drip.dm @@ -1,75 +1,75 @@ -#define IV_TAKING 0 -#define IV_INJECTING 1 - -/obj/machinery/iv_drip - name = "\improper IV drip" - icon = 'icons/goonstation/objects/iv.dmi' - icon_state = "stand" - anchored = FALSE - mouse_drag_pointer = MOUSE_ACTIVE_POINTER - var/obj/item/reagent_containers/iv_bag/bag = null - -/obj/machinery/iv_drip/update_icon() - cut_overlays() - - if(bag) - add_overlay("hangingbag") - if(bag.reagents.total_volume) - var/image/filling = image('icons/goonstation/objects/iv.dmi', src, "hangingbag-fluid") - filling.icon += mix_color_from_reagents(bag.reagents.reagent_list) - add_overlay(filling) - -/obj/machinery/iv_drip/MouseDrop(mob/living/target) - if(usr.incapacitated()) - return - - if(!ishuman(usr) || !iscarbon(target)) - return - - if(Adjacent(target) && usr.Adjacent(target)) - bag.afterattack(target, usr, TRUE) - -/obj/machinery/iv_drip/attack_hand(mob/user) - if(bag) - user.put_in_hands(bag) - bag.update_icon() - bag = null - update_icon() - -/obj/machinery/iv_drip/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/iv_bag)) - if(bag) - to_chat(user, "[src] already has an IV bag!") - return - if(!user.drop_item()) - return - - I.forceMove(src) - bag = I - to_chat(user, "You attach [I] to [src].") - update_icon() - else if (bag && istype(I, /obj/item/reagent_containers)) - bag.attackby(I) - I.afterattack(bag, usr, TRUE) - update_icon() - else - return ..() - -/obj/machinery/iv_drip/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/sheet/metal(loc) - qdel(src) - -/obj/machinery/iv_drip/examine(mob/user) - . = ..() - if(bag) - . += bag.examine(user) - -/obj/machinery/iv_drip/Move(NewLoc, direct) - . = ..() - if(!.) // ..() will return 0 if we didn't actually move anywhere. - return . - playsound(loc, pick('sound/items/cartwheel1.ogg', 'sound/items/cartwheel2.ogg'), 100, 1, ignore_walls = FALSE) - -#undef IV_TAKING -#undef IV_INJECTING \ No newline at end of file +#define IV_TAKING 0 +#define IV_INJECTING 1 + +/obj/machinery/iv_drip + name = "\improper IV drip" + icon = 'icons/goonstation/objects/iv.dmi' + icon_state = "stand" + anchored = FALSE + mouse_drag_pointer = MOUSE_ACTIVE_POINTER + var/obj/item/reagent_containers/iv_bag/bag = null + +/obj/machinery/iv_drip/update_icon() + cut_overlays() + + if(bag) + add_overlay("hangingbag") + if(bag.reagents.total_volume) + var/image/filling = image('icons/goonstation/objects/iv.dmi', src, "hangingbag-fluid") + filling.icon += mix_color_from_reagents(bag.reagents.reagent_list) + add_overlay(filling) + +/obj/machinery/iv_drip/MouseDrop(mob/living/target) + if(usr.incapacitated()) + return + + if(!ishuman(usr) || !iscarbon(target)) + return + + if(Adjacent(target) && usr.Adjacent(target)) + bag.afterattack(target, usr, TRUE) + +/obj/machinery/iv_drip/attack_hand(mob/user) + if(bag) + user.put_in_hands(bag) + bag.update_icon() + bag = null + update_icon() + +/obj/machinery/iv_drip/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/iv_bag)) + if(bag) + to_chat(user, "[src] already has an IV bag!") + return + if(!user.drop_item()) + return + + I.forceMove(src) + bag = I + to_chat(user, "You attach [I] to [src].") + update_icon() + else if (bag && istype(I, /obj/item/reagent_containers)) + bag.attackby(I) + I.afterattack(bag, usr, TRUE) + update_icon() + else + return ..() + +/obj/machinery/iv_drip/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/sheet/metal(loc) + qdel(src) + +/obj/machinery/iv_drip/examine(mob/user) + . = ..() + if(bag) + . += bag.examine(user) + +/obj/machinery/iv_drip/Move(NewLoc, direct) + . = ..() + if(!.) // ..() will return 0 if we didn't actually move anywhere. + return . + playsound(loc, pick('sound/items/cartwheel1.ogg', 'sound/items/cartwheel2.ogg'), 100, 1, ignore_walls = FALSE) + +#undef IV_TAKING +#undef IV_INJECTING diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm index 13406ac4b0f..53995f420d9 100644 --- a/code/game/machinery/lightswitch.dm +++ b/code/game/machinery/lightswitch.dm @@ -1,183 +1,183 @@ -// the light switch -// can have multiple per area -// can also operate on non-loc area through "otherarea" var -/obj/machinery/light_switch - name = "light switch" - desc = "It turns lights on and off. What are you, simple?" - icon = 'icons/obj/power.dmi' - icon_state = "light1" - anchored = 1.0 - var/on = 1 - var/area/area = null - var/otherarea = null - // luminosity = 1 - settagwhitelist = list("logic_id_tag") - var/light_connect = 1 //Allows the switch to control lights in its associated areas. When set to 0, using the switch won't affect the lights. - var/datum/radio_frequency/radio_connection - var/frequency = 0 - var/logic_id_tag = "default" //Defines the ID tag to send logic signals to. - var/logic_connect = 0 //Set this to allow the switch to send out logic signals. - - -/obj/machinery/light_switch/New(turf/loc, var/w_dir=null) - ..() - switch(w_dir) - if(NORTH) - pixel_y = 25 - if(SOUTH) - pixel_y = -25 - if(EAST) - pixel_x = 25 - if(WEST) - pixel_x = -25 - if(SSradio) - set_frequency(frequency) - spawn(5) - src.area = get_area(src) - - if(otherarea) - src.area = locate(text2path("/area/[otherarea]")) - - if(!name) - name = "light switch([area.name])" - - src.on = src.area.lightswitch - updateicon() - -/obj/machinery/light_switch/Initialize() - ..() - set_frequency(frequency) - -/obj/machinery/light_switch/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_LOGIC) - return - -/obj/machinery/light_switch/Destroy() - if(SSradio) - SSradio.remove_object(src, frequency) - radio_connection = null - return ..() - -/obj/machinery/light_switch/proc/updateicon() - if(stat & NOPOWER) - icon_state = "light-p" - else - if(on) - icon_state = "light1" - else - icon_state = "light0" - -/obj/machinery/light_switch/examine(mob/user) - . = ..() - . += "A light switch. It is [on? "on" : "off"]." - -/obj/machinery/light_switch/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - return attack_hand(user) - -/obj/machinery/light_switch/attack_hand(mob/user) - on = !on - updateicon() - - if(light_connect) - area.lightswitch = on - area.updateicon() - - if(logic_connect && powered(LIGHT)) //Don't bother sending a signal if we aren't set to send them or we have no power to send with. - handle_output() - - if(light_connect) - for(var/obj/machinery/light_switch/L in area) - L.on = on - L.updateicon() - - area.power_change() - -/obj/machinery/light_switch/proc/handle_output() - if(!radio_connection) //can't output without this - return - - if(logic_id_tag == null) //Don't output to an undefined id_tag - return - - var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal - signal.source = src - - //Light switches are continuous signal sources, since they register as ON or OFF and stay that way until adjusted again - if(on) - signal.data = list( - "tag" = logic_id_tag, - "sigtype" = "logic", - "state" = LOGIC_ON, - ) - else - signal.data = list( - "tag" = logic_id_tag, - "sigtype" = "logic", - "state" = LOGIC_OFF, - ) - - radio_connection.post_signal(src, signal, filter = RADIO_LOGIC) - if(on) - use_power(5, LIGHT) //Use a tiny bit of power every time we send an ON signal. Draws from the local APC's lighting circuit, since this is a LIGHT switch. - -/obj/machinery/light_switch/power_change() - if(!otherarea) - if(powered(LIGHT)) - stat &= ~NOPOWER - else - stat |= NOPOWER - - updateicon() - -/obj/machinery/light_switch/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - ..(severity) - return - power_change() - ..(severity) - -/obj/machinery/light_switch/process() - if(logic_connect && powered(LIGHT)) //We won't send signals while unpowered, but the last signal will remain valid for anything that received it before we went dark - handle_output() - -/obj/machinery/light_switch/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/detective_scanner)) - return - return ..() - -/obj/machinery/light_switch/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - update_multitool_menu(user) - -/obj/machinery/light_switch/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - user.visible_message("[user] starts unwrenching [src] from the wall...", "You are unwrenching [src] from the wall...", "You hear ratcheting.") - . = TRUE - if(!I.use_tool(src, user, 30, volume = I.tool_volume)) - return - WRENCH_UNANCHOR_WALL_MESSAGE - new/obj/item/mounted/frame/light_switch(get_turf(src)) - qdel(src) - -/obj/machinery/light_switch/multitool_menu(var/mob/user, var/obj/item/multitool/P) - return {" - "} - -/obj/machinery/light_switch/multitool_topic(var/mob/user,var/list/href_list,var/obj/O) - ..() - if("toggle_light_connect" in href_list) - light_connect = !light_connect - if("toggle_logic" in href_list) - logic_connect = !logic_connect +// the light switch +// can have multiple per area +// can also operate on non-loc area through "otherarea" var +/obj/machinery/light_switch + name = "light switch" + desc = "It turns lights on and off. What are you, simple?" + icon = 'icons/obj/power.dmi' + icon_state = "light1" + anchored = 1.0 + var/on = 1 + var/area/area = null + var/otherarea = null + // luminosity = 1 + settagwhitelist = list("logic_id_tag") + var/light_connect = 1 //Allows the switch to control lights in its associated areas. When set to 0, using the switch won't affect the lights. + var/datum/radio_frequency/radio_connection + var/frequency = 0 + var/logic_id_tag = "default" //Defines the ID tag to send logic signals to. + var/logic_connect = 0 //Set this to allow the switch to send out logic signals. + + +/obj/machinery/light_switch/New(turf/loc, var/w_dir=null) + ..() + switch(w_dir) + if(NORTH) + pixel_y = 25 + if(SOUTH) + pixel_y = -25 + if(EAST) + pixel_x = 25 + if(WEST) + pixel_x = -25 + if(SSradio) + set_frequency(frequency) + spawn(5) + src.area = get_area(src) + + if(otherarea) + src.area = locate(text2path("/area/[otherarea]")) + + if(!name) + name = "light switch([area.name])" + + src.on = src.area.lightswitch + updateicon() + +/obj/machinery/light_switch/Initialize() + ..() + set_frequency(frequency) + +/obj/machinery/light_switch/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_LOGIC) + return + +/obj/machinery/light_switch/Destroy() + if(SSradio) + SSradio.remove_object(src, frequency) + radio_connection = null + return ..() + +/obj/machinery/light_switch/proc/updateicon() + if(stat & NOPOWER) + icon_state = "light-p" + else + if(on) + icon_state = "light1" + else + icon_state = "light0" + +/obj/machinery/light_switch/examine(mob/user) + . = ..() + . += "A light switch. It is [on? "on" : "off"]." + +/obj/machinery/light_switch/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + return attack_hand(user) + +/obj/machinery/light_switch/attack_hand(mob/user) + on = !on + updateicon() + + if(light_connect) + area.lightswitch = on + area.updateicon() + + if(logic_connect && powered(LIGHT)) //Don't bother sending a signal if we aren't set to send them or we have no power to send with. + handle_output() + + if(light_connect) + for(var/obj/machinery/light_switch/L in area) + L.on = on + L.updateicon() + + area.power_change() + +/obj/machinery/light_switch/proc/handle_output() + if(!radio_connection) //can't output without this + return + + if(logic_id_tag == null) //Don't output to an undefined id_tag + return + + var/datum/signal/signal = new + signal.transmission_method = 1 //radio signal + signal.source = src + + //Light switches are continuous signal sources, since they register as ON or OFF and stay that way until adjusted again + if(on) + signal.data = list( + "tag" = logic_id_tag, + "sigtype" = "logic", + "state" = LOGIC_ON, + ) + else + signal.data = list( + "tag" = logic_id_tag, + "sigtype" = "logic", + "state" = LOGIC_OFF, + ) + + radio_connection.post_signal(src, signal, filter = RADIO_LOGIC) + if(on) + use_power(5, LIGHT) //Use a tiny bit of power every time we send an ON signal. Draws from the local APC's lighting circuit, since this is a LIGHT switch. + +/obj/machinery/light_switch/power_change() + if(!otherarea) + if(powered(LIGHT)) + stat &= ~NOPOWER + else + stat |= NOPOWER + + updateicon() + +/obj/machinery/light_switch/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + ..(severity) + return + power_change() + ..(severity) + +/obj/machinery/light_switch/process() + if(logic_connect && powered(LIGHT)) //We won't send signals while unpowered, but the last signal will remain valid for anything that received it before we went dark + handle_output() + +/obj/machinery/light_switch/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/detective_scanner)) + return + return ..() + +/obj/machinery/light_switch/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + update_multitool_menu(user) + +/obj/machinery/light_switch/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + user.visible_message("[user] starts unwrenching [src] from the wall...", "You are unwrenching [src] from the wall...", "You hear ratcheting.") + . = TRUE + if(!I.use_tool(src, user, 30, volume = I.tool_volume)) + return + WRENCH_UNANCHOR_WALL_MESSAGE + new/obj/item/mounted/frame/light_switch(get_turf(src)) + qdel(src) + +/obj/machinery/light_switch/multitool_menu(var/mob/user, var/obj/item/multitool/P) + return {" + "} + +/obj/machinery/light_switch/multitool_topic(var/mob/user,var/list/href_list,var/obj/O) + ..() + if("toggle_light_connect" in href_list) + light_connect = !light_connect + if("toggle_logic" in href_list) + logic_connect = !logic_connect diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index f1ac4e88bbb..bb6889769b8 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -1,596 +1,596 @@ -/* -Overview: - Used to create objects that need a per step proc call. Default definition of 'New()' - stores a reference to src machine in global 'machines list'. Default definition - of 'Del' removes reference to src machine in global 'machines list'. - -Class Variables: - use_power (num) - current state of auto power use. - Possible Values: - 0 -- no auto power use - 1 -- machine is using power at its idle power level - 2 -- machine is using power at its active power level - - active_power_usage (num) - Value for the amount of power to use when in active power mode - - idle_power_usage (num) - Value for the amount of power to use when in idle power mode - - power_channel (num) - What channel to draw from when drawing power for power mode - Possible Values: - EQUIP:0 -- Equipment Channel - LIGHT:2 -- Lighting Channel - ENVIRON:3 -- Environment Channel - - component_parts (list) - A list of component parts of machine used by frame based machines. - - uid (num) - Unique id of machine across all machines. - - gl_uid (global num) - Next uid value in sequence - - stat (bitflag) - Machine status bit flags. - Possible bit flags: - BROKEN:1 -- Machine is broken - NOPOWER:2 -- No power is being supplied to machine. - POWEROFF:4 -- tbd - MAINT:8 -- machine is currently under going maintenance. - EMPED:16 -- temporary broken by EMP pulse - - manual (num) - Currently unused. - -Class Procs: - initialize() 'game/machinery/machine.dm' - - Destroy() 'game/machinery/machine.dm' - - auto_use_power() 'game/machinery/machine.dm' - This proc determines how power mode power is deducted by the machine. - 'auto_use_power()' is called by the 'master_controller' game_controller every - tick. - - Return Value: - return:1 -- if object is powered - return:0 -- if object is not powered. - - Default definition uses 'use_power', 'power_channel', 'active_power_usage', - 'idle_power_usage', 'powered()', and 'use_power()' implement behavior. - - powered(chan = EQUIP) 'modules/power/power.dm' - Checks to see if area that contains the object has power available for power - channel given in 'chan'. - - use_power(amount, chan=EQUIP, autocalled) 'modules/power/power.dm' - Deducts 'amount' from the power channel 'chan' of the area that contains the object. - If it's autocalled then everything is normal, if something else calls use_power we are going to - need to recalculate the power two ticks in a row. - - power_change() 'modules/power/power.dm' - Called by the area that contains the object when ever that area under goes a - power state change (area runs out of power, or area channel is turned off). - - RefreshParts() 'game/machinery/machine.dm' - Called to refresh the variables in the machine that are contributed to by parts - contained in the component_parts list. (example: glass and material amounts for - the autolathe) - - Default definition does nothing. - - assign_uid() 'game/machinery/machine.dm' - Called by machine to assign a value to the uid variable. - - process() 'game/machinery/machine.dm' - Called by the 'master_controller' once per game tick for each machine that is listed in the 'machines' list. - - - Compiled by Aygar -*/ - -/obj/machinery - name = "machinery" - icon = 'icons/obj/stationobjs.dmi' - pressure_resistance = 15 - max_integrity = 200 - layer = BELOW_OBJ_LAYER - var/stat = 0 - var/emagged = 0 - var/use_power = IDLE_POWER_USE - //0 = dont run the auto - //1 = run auto, use idle - //2 = run auto, use active - var/idle_power_usage = 0 - var/active_power_usage = 0 - var/power_channel = EQUIP //EQUIP,ENVIRON or LIGHT - var/list/component_parts = null //list of all the parts used to build it, if made from certain kinds of frames. - var/uid - var/manual = 0 - var/global/gl_uid = 1 - var/custom_aghost_alerts=0 - var/panel_open = 0 - var/area/myArea - var/interact_offline = 0 // Can the machine be interacted with while de-powered. - var/use_log = list() - var/list/settagwhitelist = list()//WHITELIST OF VARIABLES THAT THE set_tag HREF CAN MODIFY, DON'T PUT SHIT YOU DON'T NEED ON HERE, AND IF YOU'RE GONNA USE set_tag (format_tag() proc), ADD TO THIS LIST. - atom_say_verb = "beeps" - var/siemens_strength = 0.7 // how badly will it shock you? - -/obj/machinery/Initialize(mapload) - if(!armor) - armor = list(melee = 25, bullet = 10, laser = 10, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70) - . = ..() - GLOB.machines += src - - if(use_power) - myArea = get_area(src) - if(!speed_process) - START_PROCESSING(SSmachines, src) - else - START_PROCESSING(SSfastprocess, src) - - power_change() - -// gotta go fast -/obj/machinery/makeSpeedProcess() - if(speed_process) - return - speed_process = TRUE - STOP_PROCESSING(SSmachines, src) - START_PROCESSING(SSfastprocess, src) - -// gotta go slow -/obj/machinery/makeNormalProcess() - if(!speed_process) - return - speed_process = FALSE - STOP_PROCESSING(SSfastprocess, src) - START_PROCESSING(SSmachines, src) - -/obj/machinery/Destroy() - if(myArea) - myArea = null - GLOB.machines.Remove(src) - if(!speed_process) - STOP_PROCESSING(SSmachines, src) - else - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/obj/machinery/proc/locate_machinery() - return - -/obj/machinery/process() // If you dont use process or power why are you here - return PROCESS_KILL - -/obj/machinery/proc/process_atmos() //If you dont use process why are you here - return PROCESS_KILL - -/obj/machinery/emp_act(severity) - if(use_power && !stat) - use_power(7500/severity) - new /obj/effect/temp_visual/emp(loc) - ..() -/obj/machinery/default_welder_repair(mob/user, obj/item/I) - . = ..() - if(.) - stat &= ~BROKEN - -//sets the use_power var and then forces an area power update -/obj/machinery/proc/update_use_power(var/new_use_power) - use_power = new_use_power - -/obj/machinery/proc/auto_use_power() - if(!powered(power_channel)) - return 0 - if(use_power == IDLE_POWER_USE) - use_power(idle_power_usage,power_channel, 1) - else if(use_power >= ACTIVE_POWER_USE) - use_power(active_power_usage,power_channel, 1) - return 1 - -/obj/machinery/proc/multitool_topic(var/mob/user,var/list/href_list,var/obj/O) - if("set_id" in href_list) - if(!("id_tag" in vars)) - warning("set_id: [type] has no id_tag var.") - var/newid = copytext(reject_bad_text(input(usr, "Specify the new ID tag for this machine", src, src:id_tag) as null|text),1,MAX_MESSAGE_LEN) - if(newid) - src:id_tag = newid - return TRUE - if("set_freq" in href_list) - if(!("frequency" in vars)) - warning("set_freq: [type] has no frequency var.") - return FALSE - var/newfreq=src:frequency - if(href_list["set_freq"]!="-1") - newfreq=text2num(href_list["set_freq"]) - else - newfreq = input(usr, "Specify a new frequency (GHz). Decimals assigned automatically.", src, src:frequency) as null|num - if(newfreq) - if(findtext(num2text(newfreq), ".")) - newfreq *= 10 // shift the decimal one place - src:frequency = sanitize_frequency(newfreq, RADIO_LOW_FREQ, RADIO_HIGH_FREQ) - return TRUE - return FALSE - -/obj/machinery/proc/handle_multitool_topic(var/href, var/list/href_list, var/mob/user) - if(!allowed(user))//no, not even HREF exploits - return FALSE - var/obj/item/multitool/P = get_multitool(usr) - if(P && istype(P)) - var/update_mt_menu = FALSE - if("set_tag" in href_list) - if(!(href_list["set_tag"] in settagwhitelist))//I see you're trying Href exploits, I see you're failing, I SEE ADMIN WARNING. (seriously though, this is a powerfull HREF, I originally found this loophole, I'm not leaving it in on my PR) - message_admins("set_tag HREF (var attempted to edit: [href_list["set_tag"]]) exploit attempted by [key_name_admin(user)] on [src] (JMP)") - return FALSE - if(!(href_list["set_tag"] in vars)) - to_chat(usr, "Something went wrong: Unable to find [href_list["set_tag"]] in vars!") - return FALSE - var/current_tag = vars[href_list["set_tag"]] - var/newid = copytext(reject_bad_text(input(usr, "Specify the new value", src, current_tag) as null|text),1,MAX_MESSAGE_LEN) - if(newid) - vars[href_list["set_tag"]] = newid - update_mt_menu = TRUE - - if("unlink" in href_list) - var/idx = text2num(href_list["unlink"]) - if(!idx) - return FALSE - - var/obj/O = getLink(idx) - if(!O) - return FALSE - if(!canLink(O)) - to_chat(usr, "You can't link with that device.") - return FALSE - - if(unlinkFrom(usr, O)) - to_chat(usr, "A green light flashes on \the [P], confirming the link was removed.") - else - to_chat(usr, "A red light flashes on \the [P]. It appears something went wrong when unlinking the two devices.") - update_mt_menu = TRUE - - if("link" in href_list) - var/obj/O = P.buffer - if(!O) - return FALSE - if(!canLink(O,href_list)) - to_chat(usr, "You can't link with that device.") - return FALSE - if(isLinkedWith(O)) - to_chat(usr, "A red light flashes on \the [P]. The two devices are already linked.") - return FALSE - - if(linkWith(usr, O, href_list)) - to_chat(usr, "A green light flashes on \the [P], confirming the link was added.") - else - to_chat(usr, "A red light flashes on \the [P]. It appears something went wrong when linking the two devices.") - update_mt_menu = TRUE - - if("buffer" in href_list) - P.buffer = src - to_chat(usr, "A green light flashes, and the device appears in the multitool buffer.") - update_mt_menu = TRUE - - if("flush" in href_list) - to_chat(usr, "A green light flashes, and the device disappears from the multitool buffer.") - P.buffer = null - update_mt_menu = TRUE - - var/ret = multitool_topic(usr,href_list,P.buffer) - if(ret) - update_mt_menu = TRUE - - if(update_mt_menu) - update_multitool_menu(usr) - return TRUE - -/obj/machinery/Topic(href, href_list, var/nowindow = 0, var/datum/topic_state/state = default_state) - if(..(href, href_list, nowindow, state)) - return 1 - - handle_multitool_topic(href,href_list,usr) - add_fingerprint(usr) - return 0 - -/obj/machinery/proc/operable(var/additional_flags = 0) - return !inoperable(additional_flags) - -/obj/machinery/proc/inoperable(var/additional_flags = 0) - return (stat & (NOPOWER|BROKEN|additional_flags)) - -/obj/machinery/CanUseTopic(var/mob/user) - if(!interact_offline && (stat & (NOPOWER|BROKEN))) - return STATUS_CLOSE - - return ..() - -/obj/machinery/CouldUseTopic(var/mob/user) - ..() - user.set_machine(src) - -/obj/machinery/CouldNotUseTopic(var/mob/user) - usr.unset_machine() - -/obj/machinery/proc/dropContents()//putting for swarmers, occupent code commented out, someone can use later. - var/turf/T = get_turf(src) - for(var/atom/movable/AM in contents) - AM.forceMove(T) - -//////////////////////////////////////////////////////////////////////////////////////////// - -/obj/machinery/attack_ai(mob/user) - if(isrobot(user))// For some reason attack_robot doesn't work - var/mob/living/silicon/robot/R = user - if(R.client && R.client.eye == R && !R.low_power_mode)// This is to stop robots from using cameras to remotely control machines; and from using machines when the borg has no power. - return attack_hand(user) - else - return attack_hand(user) - -/obj/machinery/attack_hand(mob/user as mob) - if(user.incapacitated()) - return TRUE - - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return TRUE - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.getBrainLoss() >= 60) - visible_message("[H] stares cluelessly at [src] and drools.") - return TRUE - else if(prob(H.getBrainLoss())) - to_chat(user, "You momentarily forget how to use [src].") - return TRUE - - if(panel_open) - add_fingerprint(user) - return FALSE - - if(!interact_offline && stat & (NOPOWER|BROKEN|MAINT)) - return TRUE - - add_fingerprint(user) - - return ..() - -/obj/machinery/proc/is_operational() - return !(stat & (NOPOWER|BROKEN|MAINT)) - -/obj/machinery/CheckParts(list/parts_list) - ..() - RefreshParts() - -/obj/machinery/proc/RefreshParts() //Placeholder proc for machines that are built using frames. - return - return 0 - -/obj/machinery/proc/assign_uid() - uid = gl_uid - gl_uid++ - -/obj/machinery/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - on_deconstruction() - if(component_parts && component_parts.len) - spawn_frame(disassembled) - for(var/obj/item/I in component_parts) - I.forceMove(loc) - component_parts.Cut() - qdel(src) - -/obj/machinery/proc/spawn_frame(disassembled) - var/obj/machinery/constructable_frame/machine_frame/M = new /obj/machinery/constructable_frame/machine_frame(loc) - . = M - M.anchored = anchored - if(!disassembled) - M.obj_integrity = M.max_integrity * 0.5 //the frame is already half broken - transfer_fingerprints_to(M) - M.state = 2 - M.icon_state = "box_1" - -/obj/machinery/obj_break(damage_flag) - if(!(flags & NODECONSTRUCT)) - stat |= BROKEN - -/obj/machinery/proc/default_deconstruction_crowbar(user, obj/item/I, ignore_panel = 0) - if(I.tool_behaviour != TOOL_CROWBAR) - return FALSE - if(!I.use_tool(src, user, 0, volume = 0)) - return FALSE - if((panel_open || ignore_panel) && !(flags & NODECONSTRUCT)) - deconstruct(TRUE) - to_chat(user, "You disassemble [src].") - I.play_tool_sound(user, I.tool_volume) - return 1 - return 0 - -/obj/machinery/proc/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I) - if(I.tool_behaviour != TOOL_SCREWDRIVER) - return FALSE - if(!I.use_tool(src, user, 0, volume = 0)) - return FALSE - if(!(flags & NODECONSTRUCT)) - if(!panel_open) - panel_open = 1 - icon_state = icon_state_open - to_chat(user, "You open the maintenance hatch of [src].") - else - panel_open = 0 - icon_state = icon_state_closed - to_chat(user, "You close the maintenance hatch of [src].") - I.play_tool_sound(user, I.tool_volume) - return 1 - return 0 - -/obj/machinery/proc/default_change_direction_wrench(mob/user, obj/item/I) - if(I.tool_behaviour != TOOL_WRENCH) - return FALSE - if(!I.use_tool(src, user, 0, volume = 0)) - return FALSE - if(panel_open) - dir = turn(dir,-90) - to_chat(user, "You rotate [src].") - I.play_tool_sound(user, I.tool_volume) - return TRUE - return FALSE - -/obj/machinery/default_unfasten_wrench(mob/user, obj/item/I, time) - . = ..() - if(.) - power_change() - -/obj/machinery/proc/exchange_parts(mob/user, obj/item/storage/part_replacer/W) - var/shouldplaysound = 0 - if((flags & NODECONSTRUCT)) - return FALSE - if(istype(W) && component_parts) - if(panel_open || W.works_from_distance) - var/obj/item/circuitboard/CB = locate(/obj/item/circuitboard) in component_parts - var/P - if(W.works_from_distance) - to_chat(user, display_parts(user)) - for(var/obj/item/stock_parts/A in component_parts) - for(var/D in CB.req_components) - if(ispath(A.type, D)) - P = D - break - for(var/obj/item/stock_parts/B in W.contents) - if(istype(B, P) && istype(A, P)) - if(B.rating > A.rating) - W.remove_from_storage(B, src) - W.handle_item_insertion(A, 1) - component_parts -= A - component_parts += B - B.loc = null - to_chat(user, "[A.name] replaced with [B.name].") - shouldplaysound = 1 - break - RefreshParts() - else - to_chat(user, display_parts(user)) - if(shouldplaysound) - W.play_rped_sound() - return 1 - else - return 0 - -/obj/machinery/proc/display_parts(mob/user) - . = list("Following parts detected in the machine:") - for(var/obj/item/C in component_parts) - . += "[bicon(C)] [C.name]" - . = jointext(., "\n") - -/obj/machinery/examine(mob/user) - . = ..() - if(stat & BROKEN) - . += "It looks broken and non-functional." - if(!(resistance_flags & INDESTRUCTIBLE)) - if(resistance_flags & ON_FIRE) - . += "It's on fire!" - var/healthpercent = (obj_integrity/max_integrity) * 100 - switch(healthpercent) - if(50 to 99) - . += "It looks slightly damaged." - if(25 to 50) - . += "It appears heavily damaged." - if(0 to 25) - . += "It's falling apart!" - if(user.research_scanner && component_parts) - . += display_parts(user) - -/obj/machinery/proc/on_assess_perp(mob/living/carbon/human/perp) - return 0 - -/obj/machinery/proc/is_assess_emagged() - return emagged - -/obj/machinery/proc/assess_perp(mob/living/carbon/human/perp, var/check_access, var/auth_weapons, var/check_records, var/check_arrest) - var/threatcount = 0 //the integer returned - - if(is_assess_emagged()) - return 10 //if emagged, always return 10. - - threatcount += on_assess_perp(perp) - if(threatcount >= 10) - return threatcount - - //Agent cards lower threatlevel. - var/obj/item/card/id/id = GetIdCard(perp) - if(id && istype(id, /obj/item/card/id/syndicate)) - threatcount -= 2 - // A proper CentCom id is hard currency. - else if(id && istype(id, /obj/item/card/id/centcom)) - threatcount -= 2 - - if(check_access && !allowed(perp)) - threatcount += 4 - - if(auth_weapons && !allowed(perp)) - if(istype(perp.l_hand, /obj/item/gun) || istype(perp.l_hand, /obj/item/melee)) - threatcount += 4 - - if(istype(perp.r_hand, /obj/item/gun) || istype(perp.r_hand, /obj/item/melee)) - threatcount += 4 - - if(istype(perp.belt, /obj/item/gun) || istype(perp.belt, /obj/item/melee)) - threatcount += 2 - - if(!ishumanbasic(perp)) //beepsky so racist. - threatcount += 2 - - if(check_records || check_arrest) - var/perpname = perp.get_visible_name(TRUE) - - var/datum/data/record/R = find_security_record("name", perpname) - if(check_records && !R) - threatcount += 4 - - if(check_arrest && R && (R.fields["criminal"] == "*Arrest*")) - threatcount += 4 - - return threatcount - - -/obj/machinery/proc/shock(mob/user, prb) - if(inoperable()) - return FALSE - if(!prob(prb)) - return FALSE - do_sparks(5, 1, src) - if(electrocute_mob(user, get_area(src), src, siemens_strength, TRUE)) - return TRUE - return FALSE - -//called on machinery construction (i.e from frame to machinery) but not on initialization -/obj/machinery/proc/on_construction() - return - -/obj/machinery/proc/on_deconstruction() - return - -/obj/machinery/proc/can_be_overridden() - . = 1 - -/obj/machinery/tesla_act(power, explosive = FALSE) - ..() - if(prob(85) && explosive) - explosion(loc, 1, 2, 4, flame_range = 2, adminlog = 0, smoke = 0) - else if(prob(50)) - emp_act(EMP_LIGHT) - else - ex_act(EXPLODE_HEAVY) - -/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8 - var/md5 = md5(AM.name) // Oh, and it's deterministic too. A specific item will always drop from the same slot. - for (var/i in 1 to 32) - . += hex2num(md5[i]) - . = . % 9 - AM.pixel_x = -8 + ((.%3)*8) - AM.pixel_y = -8 + (round( . / 3)*8) +/* +Overview: + Used to create objects that need a per step proc call. Default definition of 'New()' + stores a reference to src machine in global 'machines list'. Default definition + of 'Del' removes reference to src machine in global 'machines list'. + +Class Variables: + use_power (num) + current state of auto power use. + Possible Values: + 0 -- no auto power use + 1 -- machine is using power at its idle power level + 2 -- machine is using power at its active power level + + active_power_usage (num) + Value for the amount of power to use when in active power mode + + idle_power_usage (num) + Value for the amount of power to use when in idle power mode + + power_channel (num) + What channel to draw from when drawing power for power mode + Possible Values: + EQUIP:0 -- Equipment Channel + LIGHT:2 -- Lighting Channel + ENVIRON:3 -- Environment Channel + + component_parts (list) + A list of component parts of machine used by frame based machines. + + uid (num) + Unique id of machine across all machines. + + gl_uid (global num) + Next uid value in sequence + + stat (bitflag) + Machine status bit flags. + Possible bit flags: + BROKEN:1 -- Machine is broken + NOPOWER:2 -- No power is being supplied to machine. + POWEROFF:4 -- tbd + MAINT:8 -- machine is currently under going maintenance. + EMPED:16 -- temporary broken by EMP pulse + + manual (num) + Currently unused. + +Class Procs: + initialize() 'game/machinery/machine.dm' + + Destroy() 'game/machinery/machine.dm' + + auto_use_power() 'game/machinery/machine.dm' + This proc determines how power mode power is deducted by the machine. + 'auto_use_power()' is called by the 'master_controller' game_controller every + tick. + + Return Value: + return:1 -- if object is powered + return:0 -- if object is not powered. + + Default definition uses 'use_power', 'power_channel', 'active_power_usage', + 'idle_power_usage', 'powered()', and 'use_power()' implement behavior. + + powered(chan = EQUIP) 'modules/power/power.dm' + Checks to see if area that contains the object has power available for power + channel given in 'chan'. + + use_power(amount, chan=EQUIP, autocalled) 'modules/power/power.dm' + Deducts 'amount' from the power channel 'chan' of the area that contains the object. + If it's autocalled then everything is normal, if something else calls use_power we are going to + need to recalculate the power two ticks in a row. + + power_change() 'modules/power/power.dm' + Called by the area that contains the object when ever that area under goes a + power state change (area runs out of power, or area channel is turned off). + + RefreshParts() 'game/machinery/machine.dm' + Called to refresh the variables in the machine that are contributed to by parts + contained in the component_parts list. (example: glass and material amounts for + the autolathe) + + Default definition does nothing. + + assign_uid() 'game/machinery/machine.dm' + Called by machine to assign a value to the uid variable. + + process() 'game/machinery/machine.dm' + Called by the 'master_controller' once per game tick for each machine that is listed in the 'machines' list. + + + Compiled by Aygar +*/ + +/obj/machinery + name = "machinery" + icon = 'icons/obj/stationobjs.dmi' + pressure_resistance = 15 + max_integrity = 200 + layer = BELOW_OBJ_LAYER + var/stat = 0 + var/emagged = 0 + var/use_power = IDLE_POWER_USE + //0 = dont run the auto + //1 = run auto, use idle + //2 = run auto, use active + var/idle_power_usage = 0 + var/active_power_usage = 0 + var/power_channel = EQUIP //EQUIP,ENVIRON or LIGHT + var/list/component_parts = null //list of all the parts used to build it, if made from certain kinds of frames. + var/uid + var/manual = 0 + var/global/gl_uid = 1 + var/custom_aghost_alerts=0 + var/panel_open = 0 + var/area/myArea + var/interact_offline = 0 // Can the machine be interacted with while de-powered. + var/use_log = list() + var/list/settagwhitelist = list()//WHITELIST OF VARIABLES THAT THE set_tag HREF CAN MODIFY, DON'T PUT SHIT YOU DON'T NEED ON HERE, AND IF YOU'RE GONNA USE set_tag (format_tag() proc), ADD TO THIS LIST. + atom_say_verb = "beeps" + var/siemens_strength = 0.7 // how badly will it shock you? + +/obj/machinery/Initialize(mapload) + if(!armor) + armor = list(melee = 25, bullet = 10, laser = 10, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70) + . = ..() + GLOB.machines += src + + if(use_power) + myArea = get_area(src) + if(!speed_process) + START_PROCESSING(SSmachines, src) + else + START_PROCESSING(SSfastprocess, src) + + power_change() + +// gotta go fast +/obj/machinery/makeSpeedProcess() + if(speed_process) + return + speed_process = TRUE + STOP_PROCESSING(SSmachines, src) + START_PROCESSING(SSfastprocess, src) + +// gotta go slow +/obj/machinery/makeNormalProcess() + if(!speed_process) + return + speed_process = FALSE + STOP_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSmachines, src) + +/obj/machinery/Destroy() + if(myArea) + myArea = null + GLOB.machines.Remove(src) + if(!speed_process) + STOP_PROCESSING(SSmachines, src) + else + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/machinery/proc/locate_machinery() + return + +/obj/machinery/process() // If you dont use process or power why are you here + return PROCESS_KILL + +/obj/machinery/proc/process_atmos() //If you dont use process why are you here + return PROCESS_KILL + +/obj/machinery/emp_act(severity) + if(use_power && !stat) + use_power(7500/severity) + new /obj/effect/temp_visual/emp(loc) + ..() +/obj/machinery/default_welder_repair(mob/user, obj/item/I) + . = ..() + if(.) + stat &= ~BROKEN + +//sets the use_power var and then forces an area power update +/obj/machinery/proc/update_use_power(var/new_use_power) + use_power = new_use_power + +/obj/machinery/proc/auto_use_power() + if(!powered(power_channel)) + return 0 + if(use_power == IDLE_POWER_USE) + use_power(idle_power_usage,power_channel, 1) + else if(use_power >= ACTIVE_POWER_USE) + use_power(active_power_usage,power_channel, 1) + return 1 + +/obj/machinery/proc/multitool_topic(var/mob/user,var/list/href_list,var/obj/O) + if("set_id" in href_list) + if(!("id_tag" in vars)) + warning("set_id: [type] has no id_tag var.") + var/newid = copytext(reject_bad_text(input(usr, "Specify the new ID tag for this machine", src, src:id_tag) as null|text),1,MAX_MESSAGE_LEN) + if(newid) + src:id_tag = newid + return TRUE + if("set_freq" in href_list) + if(!("frequency" in vars)) + warning("set_freq: [type] has no frequency var.") + return FALSE + var/newfreq=src:frequency + if(href_list["set_freq"]!="-1") + newfreq=text2num(href_list["set_freq"]) + else + newfreq = input(usr, "Specify a new frequency (GHz). Decimals assigned automatically.", src, src:frequency) as null|num + if(newfreq) + if(findtext(num2text(newfreq), ".")) + newfreq *= 10 // shift the decimal one place + src:frequency = sanitize_frequency(newfreq, RADIO_LOW_FREQ, RADIO_HIGH_FREQ) + return TRUE + return FALSE + +/obj/machinery/proc/handle_multitool_topic(var/href, var/list/href_list, var/mob/user) + if(!allowed(user))//no, not even HREF exploits + return FALSE + var/obj/item/multitool/P = get_multitool(usr) + if(P && istype(P)) + var/update_mt_menu = FALSE + if("set_tag" in href_list) + if(!(href_list["set_tag"] in settagwhitelist))//I see you're trying Href exploits, I see you're failing, I SEE ADMIN WARNING. (seriously though, this is a powerfull HREF, I originally found this loophole, I'm not leaving it in on my PR) + message_admins("set_tag HREF (var attempted to edit: [href_list["set_tag"]]) exploit attempted by [key_name_admin(user)] on [src] (JMP)") + return FALSE + if(!(href_list["set_tag"] in vars)) + to_chat(usr, "Something went wrong: Unable to find [href_list["set_tag"]] in vars!") + return FALSE + var/current_tag = vars[href_list["set_tag"]] + var/newid = copytext(reject_bad_text(input(usr, "Specify the new value", src, current_tag) as null|text),1,MAX_MESSAGE_LEN) + if(newid) + vars[href_list["set_tag"]] = newid + update_mt_menu = TRUE + + if("unlink" in href_list) + var/idx = text2num(href_list["unlink"]) + if(!idx) + return FALSE + + var/obj/O = getLink(idx) + if(!O) + return FALSE + if(!canLink(O)) + to_chat(usr, "You can't link with that device.") + return FALSE + + if(unlinkFrom(usr, O)) + to_chat(usr, "A green light flashes on \the [P], confirming the link was removed.") + else + to_chat(usr, "A red light flashes on \the [P]. It appears something went wrong when unlinking the two devices.") + update_mt_menu = TRUE + + if("link" in href_list) + var/obj/O = P.buffer + if(!O) + return FALSE + if(!canLink(O,href_list)) + to_chat(usr, "You can't link with that device.") + return FALSE + if(isLinkedWith(O)) + to_chat(usr, "A red light flashes on \the [P]. The two devices are already linked.") + return FALSE + + if(linkWith(usr, O, href_list)) + to_chat(usr, "A green light flashes on \the [P], confirming the link was added.") + else + to_chat(usr, "A red light flashes on \the [P]. It appears something went wrong when linking the two devices.") + update_mt_menu = TRUE + + if("buffer" in href_list) + P.buffer = src + to_chat(usr, "A green light flashes, and the device appears in the multitool buffer.") + update_mt_menu = TRUE + + if("flush" in href_list) + to_chat(usr, "A green light flashes, and the device disappears from the multitool buffer.") + P.buffer = null + update_mt_menu = TRUE + + var/ret = multitool_topic(usr,href_list,P.buffer) + if(ret) + update_mt_menu = TRUE + + if(update_mt_menu) + update_multitool_menu(usr) + return TRUE + +/obj/machinery/Topic(href, href_list, var/nowindow = 0, var/datum/topic_state/state = default_state) + if(..(href, href_list, nowindow, state)) + return 1 + + handle_multitool_topic(href,href_list,usr) + add_fingerprint(usr) + return 0 + +/obj/machinery/proc/operable(var/additional_flags = 0) + return !inoperable(additional_flags) + +/obj/machinery/proc/inoperable(var/additional_flags = 0) + return (stat & (NOPOWER|BROKEN|additional_flags)) + +/obj/machinery/CanUseTopic(var/mob/user) + if(!interact_offline && (stat & (NOPOWER|BROKEN))) + return STATUS_CLOSE + + return ..() + +/obj/machinery/CouldUseTopic(var/mob/user) + ..() + user.set_machine(src) + +/obj/machinery/CouldNotUseTopic(var/mob/user) + usr.unset_machine() + +/obj/machinery/proc/dropContents()//putting for swarmers, occupent code commented out, someone can use later. + var/turf/T = get_turf(src) + for(var/atom/movable/AM in contents) + AM.forceMove(T) + +//////////////////////////////////////////////////////////////////////////////////////////// + +/obj/machinery/attack_ai(mob/user) + if(isrobot(user))// For some reason attack_robot doesn't work + var/mob/living/silicon/robot/R = user + if(R.client && R.client.eye == R && !R.low_power_mode)// This is to stop robots from using cameras to remotely control machines; and from using machines when the borg has no power. + return attack_hand(user) + else + return attack_hand(user) + +/obj/machinery/attack_hand(mob/user as mob) + if(user.incapacitated()) + return TRUE + + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return TRUE + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.getBrainLoss() >= 60) + visible_message("[H] stares cluelessly at [src] and drools.") + return TRUE + else if(prob(H.getBrainLoss())) + to_chat(user, "You momentarily forget how to use [src].") + return TRUE + + if(panel_open) + add_fingerprint(user) + return FALSE + + if(!interact_offline && stat & (NOPOWER|BROKEN|MAINT)) + return TRUE + + add_fingerprint(user) + + return ..() + +/obj/machinery/proc/is_operational() + return !(stat & (NOPOWER|BROKEN|MAINT)) + +/obj/machinery/CheckParts(list/parts_list) + ..() + RefreshParts() + +/obj/machinery/proc/RefreshParts() //Placeholder proc for machines that are built using frames. + return + return 0 + +/obj/machinery/proc/assign_uid() + uid = gl_uid + gl_uid++ + +/obj/machinery/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + on_deconstruction() + if(component_parts && component_parts.len) + spawn_frame(disassembled) + for(var/obj/item/I in component_parts) + I.forceMove(loc) + component_parts.Cut() + qdel(src) + +/obj/machinery/proc/spawn_frame(disassembled) + var/obj/machinery/constructable_frame/machine_frame/M = new /obj/machinery/constructable_frame/machine_frame(loc) + . = M + M.anchored = anchored + if(!disassembled) + M.obj_integrity = M.max_integrity * 0.5 //the frame is already half broken + transfer_fingerprints_to(M) + M.state = 2 + M.icon_state = "box_1" + +/obj/machinery/obj_break(damage_flag) + if(!(flags & NODECONSTRUCT)) + stat |= BROKEN + +/obj/machinery/proc/default_deconstruction_crowbar(user, obj/item/I, ignore_panel = 0) + if(I.tool_behaviour != TOOL_CROWBAR) + return FALSE + if(!I.use_tool(src, user, 0, volume = 0)) + return FALSE + if((panel_open || ignore_panel) && !(flags & NODECONSTRUCT)) + deconstruct(TRUE) + to_chat(user, "You disassemble [src].") + I.play_tool_sound(user, I.tool_volume) + return 1 + return 0 + +/obj/machinery/proc/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I) + if(I.tool_behaviour != TOOL_SCREWDRIVER) + return FALSE + if(!I.use_tool(src, user, 0, volume = 0)) + return FALSE + if(!(flags & NODECONSTRUCT)) + if(!panel_open) + panel_open = 1 + icon_state = icon_state_open + to_chat(user, "You open the maintenance hatch of [src].") + else + panel_open = 0 + icon_state = icon_state_closed + to_chat(user, "You close the maintenance hatch of [src].") + I.play_tool_sound(user, I.tool_volume) + return 1 + return 0 + +/obj/machinery/proc/default_change_direction_wrench(mob/user, obj/item/I) + if(I.tool_behaviour != TOOL_WRENCH) + return FALSE + if(!I.use_tool(src, user, 0, volume = 0)) + return FALSE + if(panel_open) + dir = turn(dir,-90) + to_chat(user, "You rotate [src].") + I.play_tool_sound(user, I.tool_volume) + return TRUE + return FALSE + +/obj/machinery/default_unfasten_wrench(mob/user, obj/item/I, time) + . = ..() + if(.) + power_change() + +/obj/machinery/proc/exchange_parts(mob/user, obj/item/storage/part_replacer/W) + var/shouldplaysound = 0 + if((flags & NODECONSTRUCT)) + return FALSE + if(istype(W) && component_parts) + if(panel_open || W.works_from_distance) + var/obj/item/circuitboard/CB = locate(/obj/item/circuitboard) in component_parts + var/P + if(W.works_from_distance) + to_chat(user, display_parts(user)) + for(var/obj/item/stock_parts/A in component_parts) + for(var/D in CB.req_components) + if(ispath(A.type, D)) + P = D + break + for(var/obj/item/stock_parts/B in W.contents) + if(istype(B, P) && istype(A, P)) + if(B.rating > A.rating) + W.remove_from_storage(B, src) + W.handle_item_insertion(A, 1) + component_parts -= A + component_parts += B + B.loc = null + to_chat(user, "[A.name] replaced with [B.name].") + shouldplaysound = 1 + break + RefreshParts() + else + to_chat(user, display_parts(user)) + if(shouldplaysound) + W.play_rped_sound() + return 1 + else + return 0 + +/obj/machinery/proc/display_parts(mob/user) + . = list("Following parts detected in the machine:") + for(var/obj/item/C in component_parts) + . += "[bicon(C)] [C.name]" + . = jointext(., "\n") + +/obj/machinery/examine(mob/user) + . = ..() + if(stat & BROKEN) + . += "It looks broken and non-functional." + if(!(resistance_flags & INDESTRUCTIBLE)) + if(resistance_flags & ON_FIRE) + . += "It's on fire!" + var/healthpercent = (obj_integrity/max_integrity) * 100 + switch(healthpercent) + if(50 to 99) + . += "It looks slightly damaged." + if(25 to 50) + . += "It appears heavily damaged." + if(0 to 25) + . += "It's falling apart!" + if(user.research_scanner && component_parts) + . += display_parts(user) + +/obj/machinery/proc/on_assess_perp(mob/living/carbon/human/perp) + return 0 + +/obj/machinery/proc/is_assess_emagged() + return emagged + +/obj/machinery/proc/assess_perp(mob/living/carbon/human/perp, var/check_access, var/auth_weapons, var/check_records, var/check_arrest) + var/threatcount = 0 //the integer returned + + if(is_assess_emagged()) + return 10 //if emagged, always return 10. + + threatcount += on_assess_perp(perp) + if(threatcount >= 10) + return threatcount + + //Agent cards lower threatlevel. + var/obj/item/card/id/id = GetIdCard(perp) + if(id && istype(id, /obj/item/card/id/syndicate)) + threatcount -= 2 + // A proper CentCom id is hard currency. + else if(id && istype(id, /obj/item/card/id/centcom)) + threatcount -= 2 + + if(check_access && !allowed(perp)) + threatcount += 4 + + if(auth_weapons && !allowed(perp)) + if(istype(perp.l_hand, /obj/item/gun) || istype(perp.l_hand, /obj/item/melee)) + threatcount += 4 + + if(istype(perp.r_hand, /obj/item/gun) || istype(perp.r_hand, /obj/item/melee)) + threatcount += 4 + + if(istype(perp.belt, /obj/item/gun) || istype(perp.belt, /obj/item/melee)) + threatcount += 2 + + if(!ishumanbasic(perp)) //beepsky so racist. + threatcount += 2 + + if(check_records || check_arrest) + var/perpname = perp.get_visible_name(TRUE) + + var/datum/data/record/R = find_security_record("name", perpname) + if(check_records && !R) + threatcount += 4 + + if(check_arrest && R && (R.fields["criminal"] == "*Arrest*")) + threatcount += 4 + + return threatcount + + +/obj/machinery/proc/shock(mob/user, prb) + if(inoperable()) + return FALSE + if(!prob(prb)) + return FALSE + do_sparks(5, 1, src) + if(electrocute_mob(user, get_area(src), src, siemens_strength, TRUE)) + return TRUE + return FALSE + +//called on machinery construction (i.e from frame to machinery) but not on initialization +/obj/machinery/proc/on_construction() + return + +/obj/machinery/proc/on_deconstruction() + return + +/obj/machinery/proc/can_be_overridden() + . = 1 + +/obj/machinery/tesla_act(power, explosive = FALSE) + ..() + if(prob(85) && explosive) + explosion(loc, 1, 2, 4, flame_range = 2, adminlog = 0, smoke = 0) + else if(prob(50)) + emp_act(EMP_LIGHT) + else + ex_act(EXPLODE_HEAVY) + +/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8 + var/md5 = md5(AM.name) // Oh, and it's deterministic too. A specific item will always drop from the same slot. + for (var/i in 1 to 32) + . += hex2num(md5[i]) + . = . % 9 + AM.pixel_x = -8 + ((.%3)*8) + AM.pixel_y = -8 + (round( . / 3)*8) diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm index 9b982df99fd..b73fa94259f 100644 --- a/code/game/machinery/magnet.dm +++ b/code/game/machinery/magnet.dm @@ -1,386 +1,386 @@ -// Magnetic attractor, creates variable magnetic fields and attraction. -// Can also be used to emit electron/proton beams to create a center of magnetism on another tile - -// tl;dr: it's magnets lol -// This was created for firing ranges, but I suppose this could have other applications - Doohl - -/obj/machinery/magnetic_module - - icon = 'icons/obj/objects.dmi' - icon_state = "floor_magnet-f" - name = "Electromagnetic Generator" - desc = "A device that uses station power to create points of magnetic energy." - level = 1 // underfloor - layer = 2.5 - anchored = 1 - use_power = IDLE_POWER_USE - idle_power_usage = 50 - - var/freq = AIRLOCK_FREQ // radio frequency - var/electricity_level = 1 // intensity of the magnetic pull - var/magnetic_field = 1 // the range of magnetic attraction - var/code = 0 // frequency code, they should be different unless you have a group of magnets working together or something - var/turf/center // the center of magnetic attraction - var/on = 0 - var/magpulling = 0 - - // x, y modifiers to the center turf; (0, 0) is centered on the magnet, whereas (1, -1) is one tile right, one tile down - var/center_x = 0 - var/center_y = 0 - var/max_dist = 20 // absolute value of center_x,y cannot exceed this integer - -/obj/machinery/magnetic_module/New() - ..() - var/turf/T = loc - hide(T.intact) - center = T - - spawn(10) // must wait for map loading to finish - if(SSradio) - SSradio.add_object(src, freq, RADIO_MAGNETS) - - spawn() - magnetic_process() - - // update the invisibility and icon -/obj/machinery/magnetic_module/hide(intact) - invisibility = intact ? 101 : 0 - updateicon() - - // update the icon_state -/obj/machinery/magnetic_module/proc/updateicon() - var/state="floor_magnet" - var/onstate="" - if(!on) - onstate="0" - - if(invisibility) - icon_state = "[state][onstate]-f" // if invisible, set icon to faded version - // in case of being revealed by T-scanner - else - icon_state = "[state][onstate]" - -/obj/machinery/magnetic_module/receive_signal(datum/signal/signal) - var/command = signal.data["command"] - var/modifier = signal.data["modifier"] - var/signal_code = signal.data["code"] - if(command && (signal_code == code)) - Cmd(command, modifier) - - - -/obj/machinery/magnetic_module/proc/Cmd(var/command, var/modifier) - if(command) - switch(command) - if("set-electriclevel") - if(modifier) electricity_level = modifier - if("set-magneticfield") - if(modifier) magnetic_field = modifier - - if("add-elec") - electricity_level++ - if(electricity_level > 12) - electricity_level = 12 - if("sub-elec") - electricity_level-- - if(electricity_level <= 0) - electricity_level = 1 - if("add-mag") - magnetic_field++ - if(magnetic_field > 4) - magnetic_field = 4 - if("sub-mag") - magnetic_field-- - if(magnetic_field <= 0) - magnetic_field = 1 - - if("set-x") - if(modifier) center_x = modifier - if("set-y") - if(modifier) center_y = modifier - - if("N") // NORTH - center_y++ - if("S") // SOUTH - center_y-- - if("E") // EAST - center_x++ - if("W") // WEST - center_x-- - if("C") // CENTER - center_x = 0 - center_y = 0 - if("R") // RANDOM - center_x = rand(-max_dist, max_dist) - center_y = rand(-max_dist, max_dist) - - if("set-code") - if(modifier) code = modifier - if("toggle-power") - on = !on - - if(on) - spawn() - magnetic_process() - -/obj/machinery/magnetic_module/process() - if(stat & NOPOWER) - on = 0 - - // Sanity checks: - if(electricity_level <= 0) - electricity_level = 1 - if(magnetic_field <= 0) - magnetic_field = 1 - - - // Limitations: - if(abs(center_x) > max_dist) - center_x = max_dist - if(abs(center_y) > max_dist) - center_y = max_dist - if(magnetic_field > 4) - magnetic_field = 4 - if(electricity_level > 12) - electricity_level = 12 - - // Update power usage: - if(on) - use_power = 2 - active_power_usage = electricity_level*15 - else - use_power = 0 - updateicon() - - -/obj/machinery/magnetic_module/proc/magnetic_process() // proc that actually does the pulling - if(magpulling) return - while(on) - - magpulling = 1 - center = locate(x+center_x, y+center_y, z) - if(center) - for(var/obj/M in orange(magnetic_field, center)) - if(!M.anchored && (M.flags & CONDUCT)) - step_towards(M, center) - - for(var/mob/living/silicon/S in orange(magnetic_field, center)) - if(istype(S, /mob/living/silicon/ai)) continue - step_towards(S, center) - - use_power(electricity_level * 5) - sleep(13 - electricity_level) - - magpulling = 0 - -/obj/machinery/magnetic_controller - name = "Magnetic Control Console" - icon = 'icons/obj/airlock_machines.dmi' // uses an airlock machine icon, THINK GREEN HELP THE ENVIRONMENT - RECYCLING! - icon_state = "airlock_control_standby" - density = 1 - anchored = 1.0 - use_power = IDLE_POWER_USE - idle_power_usage = 45 - var/frequency = AIRLOCK_FREQ - var/code = 0 - var/list/magnets = list() - var/title = "Magnetic Control Console" - var/autolink = 0 // if set to 1, can't probe for other magnets! - - var/pathpos = 1 // position in the path - var/path = "NULL" // text path of the magnet - var/speed = 1 // lowest = 1, highest = 10 - var/list/rpath = list() // real path of the magnet, used in iterator - - var/moving = 0 // 1 if scheduled to loop - var/looping = 0 // 1 if looping - - var/datum/radio_frequency/radio_connection - - -/obj/machinery/magnetic_controller/New() - ..() - - if(autolink) - for(var/obj/machinery/magnetic_module/M in world) - if(M.freq == frequency && M.code == code) - magnets.Add(M) - - - spawn(45) // must wait for map loading to finish - if(SSradio) - radio_connection = SSradio.add_object(src, frequency, RADIO_MAGNETS) - - - if(path) // check for default path - filter_path() // renders rpath - - -/obj/machinery/magnetic_controller/Destroy() - if(SSradio) - SSradio.remove_object(src, frequency) - radio_connection = null - return ..() - -/obj/machinery/magnetic_controller/process() - if(magnets.len == 0 && autolink) - for(var/obj/machinery/magnetic_module/M in world) - if(M.freq == frequency && M.code == code) - magnets.Add(M) - - -/obj/machinery/magnetic_controller/attack_ai(mob/user as mob) - return src.attack_hand(user) - -/obj/machinery/magnetic_controller/attack_hand(mob/user as mob) - if(stat & (BROKEN|NOPOWER)) - return - user.set_machine(src) - var/dat = "Magnetic Control Console

        " - if(!autolink) - dat += {" - Frequency: [frequency]
        - Code: [code]
        - Probe Generators
        - "} - - if(magnets.len >= 1) - - dat += "Magnets confirmed:
        " - var/i = 0 - for(var/obj/machinery/magnetic_module/M in magnets) - i++ - dat += "     < \[[i]\] ([M.on ? "On":"Off"]) | Electricity level: - [M.electricity_level] +; Magnetic field: - [M.magnetic_field] +
        " - - dat += "
        Speed: - [speed] +
        " - dat += "Path: {[path]}
        " - dat += "Moving: [moving ? "Enabled":"Disabled"]" - - - user << browse(dat, "window=magnet;size=400x500") - onclose(user, "magnet") - -/obj/machinery/magnetic_controller/Topic(href, href_list) - if(stat & (BROKEN|NOPOWER)) - return - usr.set_machine(src) - src.add_fingerprint(usr) - - if(href_list["radio-op"]) - - // Prepare signal beforehand, because this is a radio operation - var/datum/signal/signal = new - signal.transmission_method = 1 // radio transmission - signal.source = src - signal.frequency = frequency - signal.data["code"] = code - - // Apply any necessary commands - switch(href_list["radio-op"]) - if("togglepower") - signal.data["command"] = "toggle-power" - - if("minuselec") - signal.data["command"] = "sub-elec" - if("pluselec") - signal.data["command"] = "add-elec" - - if("minusmag") - signal.data["command"] = "sub-mag" - if("plusmag") - signal.data["command"] = "add-mag" - - - // Broadcast the signal - - radio_connection.post_signal(src, signal, filter = RADIO_MAGNETS) - - spawn(1) - updateUsrDialog() // pretty sure this increases responsiveness - - if(href_list["operation"]) - switch(href_list["operation"]) - if("plusspeed") - speed++ - if(speed > 10) - speed = 10 - if("minusspeed") - speed -- - if(speed <= 0) - speed = 1 - if("setpath") - var/newpath = sanitize(copytext(input(usr, "Please define a new path!",,path) as text|null,1,MAX_MESSAGE_LEN)) - if(newpath && newpath != "") - moving = 0 // stop moving - path = newpath - pathpos = 1 // reset position - filter_path() // renders rpath - - if("togglemoving") - moving = !moving - if(moving) - spawn() MagnetMove() - - - updateUsrDialog() - -/obj/machinery/magnetic_controller/proc/MagnetMove() - if(looping) return - - while(moving && rpath.len >= 1) - - if(stat & (BROKEN|NOPOWER)) - break - - looping = 1 - - // Prepare the radio signal - var/datum/signal/signal = new - signal.transmission_method = 1 // radio transmission - signal.source = src - signal.frequency = frequency - signal.data["code"] = code - - if(pathpos > rpath.len) // if the position is greater than the length, we just loop through the list! - pathpos = 1 - - var/nextmove = uppertext(rpath[pathpos]) // makes it un-case-sensitive - - if(!(nextmove in list("N","S","E","W","C","R"))) - // N, S, E, W are directional - // C is center - // R is random (in magnetic field's bounds) - qdel(signal) - break // break the loop if the character located is invalid - - signal.data["command"] = nextmove - - - pathpos++ // increase iterator - - // Broadcast the signal - spawn() - radio_connection.post_signal(src, signal, filter = RADIO_MAGNETS) - - if(speed == 10) - sleep(1) - else - sleep(12-speed) - - looping = 0 - - -/obj/machinery/magnetic_controller/proc/filter_path() - // Generates the rpath variable using the path string, think of this as "string2list" - // Doesn't use params2list() because of the akward way it stacks entities - rpath = list() // clear rpath - var/maximum_character = min( 50, length(path) ) // chooses the maximum length of the iterator. 50 max length - - for(var/i=1, i<=maximum_character, i++) // iterates through all characters in path - - var/nextchar = copytext(path, i, i+1) // find next character - - if(!(nextchar in list(";", "&", "*", " "))) // if char is a separator, ignore - rpath += copytext(path, i, i+1) // else, add to list - - // there doesn't HAVE to be separators but it makes paths syntatically visible +// Magnetic attractor, creates variable magnetic fields and attraction. +// Can also be used to emit electron/proton beams to create a center of magnetism on another tile + +// tl;dr: it's magnets lol +// This was created for firing ranges, but I suppose this could have other applications - Doohl + +/obj/machinery/magnetic_module + + icon = 'icons/obj/objects.dmi' + icon_state = "floor_magnet-f" + name = "Electromagnetic Generator" + desc = "A device that uses station power to create points of magnetic energy." + level = 1 // underfloor + layer = 2.5 + anchored = 1 + use_power = IDLE_POWER_USE + idle_power_usage = 50 + + var/freq = AIRLOCK_FREQ // radio frequency + var/electricity_level = 1 // intensity of the magnetic pull + var/magnetic_field = 1 // the range of magnetic attraction + var/code = 0 // frequency code, they should be different unless you have a group of magnets working together or something + var/turf/center // the center of magnetic attraction + var/on = 0 + var/magpulling = 0 + + // x, y modifiers to the center turf; (0, 0) is centered on the magnet, whereas (1, -1) is one tile right, one tile down + var/center_x = 0 + var/center_y = 0 + var/max_dist = 20 // absolute value of center_x,y cannot exceed this integer + +/obj/machinery/magnetic_module/New() + ..() + var/turf/T = loc + hide(T.intact) + center = T + + spawn(10) // must wait for map loading to finish + if(SSradio) + SSradio.add_object(src, freq, RADIO_MAGNETS) + + spawn() + magnetic_process() + + // update the invisibility and icon +/obj/machinery/magnetic_module/hide(intact) + invisibility = intact ? 101 : 0 + updateicon() + + // update the icon_state +/obj/machinery/magnetic_module/proc/updateicon() + var/state="floor_magnet" + var/onstate="" + if(!on) + onstate="0" + + if(invisibility) + icon_state = "[state][onstate]-f" // if invisible, set icon to faded version + // in case of being revealed by T-scanner + else + icon_state = "[state][onstate]" + +/obj/machinery/magnetic_module/receive_signal(datum/signal/signal) + var/command = signal.data["command"] + var/modifier = signal.data["modifier"] + var/signal_code = signal.data["code"] + if(command && (signal_code == code)) + Cmd(command, modifier) + + + +/obj/machinery/magnetic_module/proc/Cmd(var/command, var/modifier) + if(command) + switch(command) + if("set-electriclevel") + if(modifier) electricity_level = modifier + if("set-magneticfield") + if(modifier) magnetic_field = modifier + + if("add-elec") + electricity_level++ + if(electricity_level > 12) + electricity_level = 12 + if("sub-elec") + electricity_level-- + if(electricity_level <= 0) + electricity_level = 1 + if("add-mag") + magnetic_field++ + if(magnetic_field > 4) + magnetic_field = 4 + if("sub-mag") + magnetic_field-- + if(magnetic_field <= 0) + magnetic_field = 1 + + if("set-x") + if(modifier) center_x = modifier + if("set-y") + if(modifier) center_y = modifier + + if("N") // NORTH + center_y++ + if("S") // SOUTH + center_y-- + if("E") // EAST + center_x++ + if("W") // WEST + center_x-- + if("C") // CENTER + center_x = 0 + center_y = 0 + if("R") // RANDOM + center_x = rand(-max_dist, max_dist) + center_y = rand(-max_dist, max_dist) + + if("set-code") + if(modifier) code = modifier + if("toggle-power") + on = !on + + if(on) + spawn() + magnetic_process() + +/obj/machinery/magnetic_module/process() + if(stat & NOPOWER) + on = 0 + + // Sanity checks: + if(electricity_level <= 0) + electricity_level = 1 + if(magnetic_field <= 0) + magnetic_field = 1 + + + // Limitations: + if(abs(center_x) > max_dist) + center_x = max_dist + if(abs(center_y) > max_dist) + center_y = max_dist + if(magnetic_field > 4) + magnetic_field = 4 + if(electricity_level > 12) + electricity_level = 12 + + // Update power usage: + if(on) + use_power = 2 + active_power_usage = electricity_level*15 + else + use_power = 0 + updateicon() + + +/obj/machinery/magnetic_module/proc/magnetic_process() // proc that actually does the pulling + if(magpulling) return + while(on) + + magpulling = 1 + center = locate(x+center_x, y+center_y, z) + if(center) + for(var/obj/M in orange(magnetic_field, center)) + if(!M.anchored && (M.flags & CONDUCT)) + step_towards(M, center) + + for(var/mob/living/silicon/S in orange(magnetic_field, center)) + if(istype(S, /mob/living/silicon/ai)) continue + step_towards(S, center) + + use_power(electricity_level * 5) + sleep(13 - electricity_level) + + magpulling = 0 + +/obj/machinery/magnetic_controller + name = "Magnetic Control Console" + icon = 'icons/obj/airlock_machines.dmi' // uses an airlock machine icon, THINK GREEN HELP THE ENVIRONMENT - RECYCLING! + icon_state = "airlock_control_standby" + density = 1 + anchored = 1.0 + use_power = IDLE_POWER_USE + idle_power_usage = 45 + var/frequency = AIRLOCK_FREQ + var/code = 0 + var/list/magnets = list() + var/title = "Magnetic Control Console" + var/autolink = 0 // if set to 1, can't probe for other magnets! + + var/pathpos = 1 // position in the path + var/path = "NULL" // text path of the magnet + var/speed = 1 // lowest = 1, highest = 10 + var/list/rpath = list() // real path of the magnet, used in iterator + + var/moving = 0 // 1 if scheduled to loop + var/looping = 0 // 1 if looping + + var/datum/radio_frequency/radio_connection + + +/obj/machinery/magnetic_controller/New() + ..() + + if(autolink) + for(var/obj/machinery/magnetic_module/M in world) + if(M.freq == frequency && M.code == code) + magnets.Add(M) + + + spawn(45) // must wait for map loading to finish + if(SSradio) + radio_connection = SSradio.add_object(src, frequency, RADIO_MAGNETS) + + + if(path) // check for default path + filter_path() // renders rpath + + +/obj/machinery/magnetic_controller/Destroy() + if(SSradio) + SSradio.remove_object(src, frequency) + radio_connection = null + return ..() + +/obj/machinery/magnetic_controller/process() + if(magnets.len == 0 && autolink) + for(var/obj/machinery/magnetic_module/M in world) + if(M.freq == frequency && M.code == code) + magnets.Add(M) + + +/obj/machinery/magnetic_controller/attack_ai(mob/user as mob) + return src.attack_hand(user) + +/obj/machinery/magnetic_controller/attack_hand(mob/user as mob) + if(stat & (BROKEN|NOPOWER)) + return + user.set_machine(src) + var/dat = "Magnetic Control Console

        " + if(!autolink) + dat += {" + Frequency: [frequency]
        + Code: [code]
        + Probe Generators
        + "} + + if(magnets.len >= 1) + + dat += "Magnets confirmed:
        " + var/i = 0 + for(var/obj/machinery/magnetic_module/M in magnets) + i++ + dat += "     < \[[i]\] ([M.on ? "On":"Off"]) | Electricity level: - [M.electricity_level] +; Magnetic field: - [M.magnetic_field] +
        " + + dat += "
        Speed: - [speed] +
        " + dat += "Path: {[path]}
        " + dat += "Moving: [moving ? "Enabled":"Disabled"]" + + + user << browse(dat, "window=magnet;size=400x500") + onclose(user, "magnet") + +/obj/machinery/magnetic_controller/Topic(href, href_list) + if(stat & (BROKEN|NOPOWER)) + return + usr.set_machine(src) + src.add_fingerprint(usr) + + if(href_list["radio-op"]) + + // Prepare signal beforehand, because this is a radio operation + var/datum/signal/signal = new + signal.transmission_method = 1 // radio transmission + signal.source = src + signal.frequency = frequency + signal.data["code"] = code + + // Apply any necessary commands + switch(href_list["radio-op"]) + if("togglepower") + signal.data["command"] = "toggle-power" + + if("minuselec") + signal.data["command"] = "sub-elec" + if("pluselec") + signal.data["command"] = "add-elec" + + if("minusmag") + signal.data["command"] = "sub-mag" + if("plusmag") + signal.data["command"] = "add-mag" + + + // Broadcast the signal + + radio_connection.post_signal(src, signal, filter = RADIO_MAGNETS) + + spawn(1) + updateUsrDialog() // pretty sure this increases responsiveness + + if(href_list["operation"]) + switch(href_list["operation"]) + if("plusspeed") + speed++ + if(speed > 10) + speed = 10 + if("minusspeed") + speed -- + if(speed <= 0) + speed = 1 + if("setpath") + var/newpath = sanitize(copytext(input(usr, "Please define a new path!",,path) as text|null,1,MAX_MESSAGE_LEN)) + if(newpath && newpath != "") + moving = 0 // stop moving + path = newpath + pathpos = 1 // reset position + filter_path() // renders rpath + + if("togglemoving") + moving = !moving + if(moving) + spawn() MagnetMove() + + + updateUsrDialog() + +/obj/machinery/magnetic_controller/proc/MagnetMove() + if(looping) return + + while(moving && rpath.len >= 1) + + if(stat & (BROKEN|NOPOWER)) + break + + looping = 1 + + // Prepare the radio signal + var/datum/signal/signal = new + signal.transmission_method = 1 // radio transmission + signal.source = src + signal.frequency = frequency + signal.data["code"] = code + + if(pathpos > rpath.len) // if the position is greater than the length, we just loop through the list! + pathpos = 1 + + var/nextmove = uppertext(rpath[pathpos]) // makes it un-case-sensitive + + if(!(nextmove in list("N","S","E","W","C","R"))) + // N, S, E, W are directional + // C is center + // R is random (in magnetic field's bounds) + qdel(signal) + break // break the loop if the character located is invalid + + signal.data["command"] = nextmove + + + pathpos++ // increase iterator + + // Broadcast the signal + spawn() + radio_connection.post_signal(src, signal, filter = RADIO_MAGNETS) + + if(speed == 10) + sleep(1) + else + sleep(12-speed) + + looping = 0 + + +/obj/machinery/magnetic_controller/proc/filter_path() + // Generates the rpath variable using the path string, think of this as "string2list" + // Doesn't use params2list() because of the akward way it stacks entities + rpath = list() // clear rpath + var/maximum_character = min( 50, length(path) ) // chooses the maximum length of the iterator. 50 max length + + for(var/i=1, i<=maximum_character, i++) // iterates through all characters in path + + var/nextchar = copytext(path, i, i+1) // find next character + + if(!(nextchar in list(";", "&", "*", " "))) // if char is a separator, ignore + rpath += copytext(path, i, i+1) // else, add to list + + // there doesn't HAVE to be separators but it makes paths syntatically visible diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm index 9e6f0d672cc..fc6e61be76d 100644 --- a/code/game/machinery/mass_driver.dm +++ b/code/game/machinery/mass_driver.dm @@ -1,211 +1,211 @@ -/obj/machinery/mass_driver - name = "mass driver" - desc = "Shoots things into space." - icon = 'icons/obj/objects.dmi' - icon_state = "mass_driver" - anchored = 1.0 - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 50 - - var/power = 1.0 - var/code = 1.0 - var/id_tag = "default" - settagwhitelist = list("id_tag") - var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess. - -/obj/machinery/mass_driver/attackby(obj/item/W, mob/user as mob) - - if(istype(W, /obj/item/multitool)) - update_multitool_menu(user) - return 1 - - if(istype(W, /obj/item/screwdriver)) - to_chat(user, "You begin to unscrew the bolts off the [src]...") - playsound(get_turf(src), W.usesound, 50, 1) - if(do_after(user, 30 * W.toolspeed, target = src)) - var/obj/machinery/mass_driver_frame/F = new(get_turf(src)) - F.dir = src.dir - F.anchored = 1 - F.build = 4 - F.update_icon() - qdel(src) - return 1 - - return ..() - -/obj/machinery/mass_driver/multitool_menu(var/mob/user, var/obj/item/multitool/P) - return {" -
          -
        • [format_tag("ID Tag","id_tag")]
        • -
        "} - -/obj/machinery/mass_driver/proc/drive(amount) - if(stat & (BROKEN|NOPOWER)) - return - use_power(500*power) - var/O_limit = 0 - var/atom/target = get_edge_target_turf(src, dir) - for(var/atom/movable/O in loc) - if(!O.anchored||istype(O, /obj/mecha))//Mechs need their launch platforms. - O_limit++ - if(O_limit >= 20)//so no more than 20 items are sent at a time, probably for counter-lag purposes - break - use_power(500) - spawn() - var/coef = 1 - if(emagged) - coef = 5 - O.throw_at(target, drive_range * power * coef, power * coef) - flick("mass_driver1", src) - return - -/obj/machinery/mass_driver/emp_act(severity) - if(stat & (BROKEN|NOPOWER)) - return - drive() - ..(severity) - -/obj/machinery/mass_driver/emag_act(mob/user) - if(!emagged) - emagged = 1 - to_chat(user, "You hack the Mass Driver, radically increasing the force at which it'll throw things. Better not stand in its way.") - return 1 - return -1 - -////////////////MASS BUMPER/////////////////// - -/obj/machinery/mass_driver/bumper - name = "mass bumper" - desc = "Now you're here, now you're over there." - density = 1 - -/obj/machinery/mass_driver/bumper/Bumped(M as mob|obj) - density = 0 - step(M, get_dir(M,src)) - spawn(1) - density = 1 - drive() - return - -////////////////MASS DRIVER FRAME/////////////////// - -/obj/machinery/mass_driver_frame - name = "mass driver frame" - icon = 'icons/obj/objects.dmi' - icon_state = "mass_driver_b0" - density = 0 - anchored = 0 - var/build = 0 - -/obj/machinery/mass_driver_frame/attackby(var/obj/item/W as obj, var/mob/user as mob) - switch(build) - if(0) // Loose frame - if(istype(W, /obj/item/wrench)) - to_chat(user, "You begin to anchor \the [src] on the floor.") - playsound(get_turf(src), W.usesound, 50, 1) - if(do_after(user, 10 * W.toolspeed, target = src) && (build == 0)) - to_chat(user, "You anchor \the [src]!") - anchored = 1 - build++ - update_icon() - return 1 - return - if(1) // Fixed to the floor - if(istype(W, /obj/item/wrench)) - to_chat(user, "You begin to de-anchor \the [src] from the floor.") - playsound(get_turf(src), W.usesound, 50, 1) - if(do_after(user, 10 * W.toolspeed, target = src) && (build == 1)) - build-- - update_icon() - anchored = 0 - to_chat(user, "You de-anchored \the [src]!") - return 1 - if(2) // Welded to the floor - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = W - to_chat(user, "You start adding cables to \the [src]...") - playsound(get_turf(src), C.usesound, 50, 1) - if(do_after(user, 20 * C.toolspeed, target = src) && (C.amount >= 2) && (build == 2)) - C.use(2) - to_chat(user, "You've added cables to \the [src].") - build++ - update_icon() - return - if(3) // Wired - if(istype(W, /obj/item/wirecutters)) - to_chat(user, "You begin to remove the wiring from \the [src].") - if(do_after(user, 10 * W.toolspeed, target = src) && (build == 3)) - new /obj/item/stack/cable_coil(loc,2) - playsound(get_turf(src), W.usesound, 50, 1) - to_chat(user, "You've removed the cables from \the [src].") - build-- - update_icon() - return 1 - if(istype(W, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = W - to_chat(user, "You begin to complete \the [src]...") - playsound(get_turf(src), R.usesound, 50, 1) - if(do_after(user, 20 * R.toolspeed, target = src) && (R.amount >= 2) && (build == 3)) - R.use(2) - to_chat(user, "You've added the grille to \the [src].") - build++ - update_icon() - return 1 - return - if(4) // Grille in place - if(istype(W, /obj/item/crowbar)) - to_chat(user, "You begin to pry off the grille from \the [src]...") - playsound(get_turf(src), W.usesound, 50, 1) - if(do_after(user, 30 * W.toolspeed, target = src) && (build == 4)) - new /obj/item/stack/rods(loc,2) - build-- - update_icon() - return 1 - if(istype(W, /obj/item/screwdriver)) - to_chat(user, "You finalize the Mass Driver...") - playsound(get_turf(src), W.usesound, 50, 1) - var/obj/machinery/mass_driver/M = new(get_turf(src)) - M.dir = src.dir - qdel(src) - return 1 - return - return ..() - -/obj/machinery/mass_driver_frame/welder_act(mob/user, obj/item/I) - if(build != 0 && build != 1 && build != 2) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(build == 0) //can deconstruct - WELDER_ATTEMPT_SLICING_MESSAGE - if(I.use_tool(src, user, 30, volume = I.tool_volume)) - WELDER_SLICING_SUCCESS_MESSAGE - new /obj/item/stack/sheet/plasteel(drop_location(),3) - qdel(src) - else if(build == 1) //wrenched but not welded down - WELDER_ATTEMPT_FLOOR_WELD_MESSAGE - if(I.use_tool(src, user, 40, volume = I.tool_volume) && build == 1) - WELDER_FLOOR_WELD_SUCCESS_MESSAGE - build = 2 - else if(build == 2) //welded down - WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE - if(I.use_tool(src, user, 40, volume = I.tool_volume) && build == 2) - WELDER_FLOOR_SLICE_SUCCESS_MESSAGE - build = 1 - update_icon() - -/obj/machinery/mass_driver_frame/update_icon() - icon_state = "mass_driver_b[build]" - -/obj/machinery/mass_driver_frame/verb/rotate() - set category = "Object" - set name = "Rotate Frame" - set src in view(1) - - if( usr.stat || usr.restrained() || (usr.status_flags & FAKEDEATH)) - return - - src.dir = turn(src.dir, -90) - return +/obj/machinery/mass_driver + name = "mass driver" + desc = "Shoots things into space." + icon = 'icons/obj/objects.dmi' + icon_state = "mass_driver" + anchored = 1.0 + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 50 + + var/power = 1.0 + var/code = 1.0 + var/id_tag = "default" + settagwhitelist = list("id_tag") + var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess. + +/obj/machinery/mass_driver/attackby(obj/item/W, mob/user as mob) + + if(istype(W, /obj/item/multitool)) + update_multitool_menu(user) + return 1 + + if(istype(W, /obj/item/screwdriver)) + to_chat(user, "You begin to unscrew the bolts off the [src]...") + playsound(get_turf(src), W.usesound, 50, 1) + if(do_after(user, 30 * W.toolspeed, target = src)) + var/obj/machinery/mass_driver_frame/F = new(get_turf(src)) + F.dir = src.dir + F.anchored = 1 + F.build = 4 + F.update_icon() + qdel(src) + return 1 + + return ..() + +/obj/machinery/mass_driver/multitool_menu(var/mob/user, var/obj/item/multitool/P) + return {" +
          +
        • [format_tag("ID Tag","id_tag")]
        • +
        "} + +/obj/machinery/mass_driver/proc/drive(amount) + if(stat & (BROKEN|NOPOWER)) + return + use_power(500*power) + var/O_limit = 0 + var/atom/target = get_edge_target_turf(src, dir) + for(var/atom/movable/O in loc) + if(!O.anchored||istype(O, /obj/mecha))//Mechs need their launch platforms. + O_limit++ + if(O_limit >= 20)//so no more than 20 items are sent at a time, probably for counter-lag purposes + break + use_power(500) + spawn() + var/coef = 1 + if(emagged) + coef = 5 + O.throw_at(target, drive_range * power * coef, power * coef) + flick("mass_driver1", src) + return + +/obj/machinery/mass_driver/emp_act(severity) + if(stat & (BROKEN|NOPOWER)) + return + drive() + ..(severity) + +/obj/machinery/mass_driver/emag_act(mob/user) + if(!emagged) + emagged = 1 + to_chat(user, "You hack the Mass Driver, radically increasing the force at which it'll throw things. Better not stand in its way.") + return 1 + return -1 + +////////////////MASS BUMPER/////////////////// + +/obj/machinery/mass_driver/bumper + name = "mass bumper" + desc = "Now you're here, now you're over there." + density = 1 + +/obj/machinery/mass_driver/bumper/Bumped(M as mob|obj) + density = 0 + step(M, get_dir(M,src)) + spawn(1) + density = 1 + drive() + return + +////////////////MASS DRIVER FRAME/////////////////// + +/obj/machinery/mass_driver_frame + name = "mass driver frame" + icon = 'icons/obj/objects.dmi' + icon_state = "mass_driver_b0" + density = 0 + anchored = 0 + var/build = 0 + +/obj/machinery/mass_driver_frame/attackby(var/obj/item/W as obj, var/mob/user as mob) + switch(build) + if(0) // Loose frame + if(istype(W, /obj/item/wrench)) + to_chat(user, "You begin to anchor \the [src] on the floor.") + playsound(get_turf(src), W.usesound, 50, 1) + if(do_after(user, 10 * W.toolspeed, target = src) && (build == 0)) + to_chat(user, "You anchor \the [src]!") + anchored = 1 + build++ + update_icon() + return 1 + return + if(1) // Fixed to the floor + if(istype(W, /obj/item/wrench)) + to_chat(user, "You begin to de-anchor \the [src] from the floor.") + playsound(get_turf(src), W.usesound, 50, 1) + if(do_after(user, 10 * W.toolspeed, target = src) && (build == 1)) + build-- + update_icon() + anchored = 0 + to_chat(user, "You de-anchored \the [src]!") + return 1 + if(2) // Welded to the floor + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = W + to_chat(user, "You start adding cables to \the [src]...") + playsound(get_turf(src), C.usesound, 50, 1) + if(do_after(user, 20 * C.toolspeed, target = src) && (C.amount >= 2) && (build == 2)) + C.use(2) + to_chat(user, "You've added cables to \the [src].") + build++ + update_icon() + return + if(3) // Wired + if(istype(W, /obj/item/wirecutters)) + to_chat(user, "You begin to remove the wiring from \the [src].") + if(do_after(user, 10 * W.toolspeed, target = src) && (build == 3)) + new /obj/item/stack/cable_coil(loc,2) + playsound(get_turf(src), W.usesound, 50, 1) + to_chat(user, "You've removed the cables from \the [src].") + build-- + update_icon() + return 1 + if(istype(W, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = W + to_chat(user, "You begin to complete \the [src]...") + playsound(get_turf(src), R.usesound, 50, 1) + if(do_after(user, 20 * R.toolspeed, target = src) && (R.amount >= 2) && (build == 3)) + R.use(2) + to_chat(user, "You've added the grille to \the [src].") + build++ + update_icon() + return 1 + return + if(4) // Grille in place + if(istype(W, /obj/item/crowbar)) + to_chat(user, "You begin to pry off the grille from \the [src]...") + playsound(get_turf(src), W.usesound, 50, 1) + if(do_after(user, 30 * W.toolspeed, target = src) && (build == 4)) + new /obj/item/stack/rods(loc,2) + build-- + update_icon() + return 1 + if(istype(W, /obj/item/screwdriver)) + to_chat(user, "You finalize the Mass Driver...") + playsound(get_turf(src), W.usesound, 50, 1) + var/obj/machinery/mass_driver/M = new(get_turf(src)) + M.dir = src.dir + qdel(src) + return 1 + return + return ..() + +/obj/machinery/mass_driver_frame/welder_act(mob/user, obj/item/I) + if(build != 0 && build != 1 && build != 2) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(build == 0) //can deconstruct + WELDER_ATTEMPT_SLICING_MESSAGE + if(I.use_tool(src, user, 30, volume = I.tool_volume)) + WELDER_SLICING_SUCCESS_MESSAGE + new /obj/item/stack/sheet/plasteel(drop_location(),3) + qdel(src) + else if(build == 1) //wrenched but not welded down + WELDER_ATTEMPT_FLOOR_WELD_MESSAGE + if(I.use_tool(src, user, 40, volume = I.tool_volume) && build == 1) + WELDER_FLOOR_WELD_SUCCESS_MESSAGE + build = 2 + else if(build == 2) //welded down + WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE + if(I.use_tool(src, user, 40, volume = I.tool_volume) && build == 2) + WELDER_FLOOR_SLICE_SUCCESS_MESSAGE + build = 1 + update_icon() + +/obj/machinery/mass_driver_frame/update_icon() + icon_state = "mass_driver_b[build]" + +/obj/machinery/mass_driver_frame/verb/rotate() + set category = "Object" + set name = "Rotate Frame" + set src in view(1) + + if( usr.stat || usr.restrained() || (usr.status_flags & FAKEDEATH)) + return + + src.dir = turn(src.dir, -90) + return diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm index d5fa5277592..48d87d0f330 100644 --- a/code/game/machinery/navbeacon.dm +++ b/code/game/machinery/navbeacon.dm @@ -1,222 +1,222 @@ -// Navigation beacon for AI robots -// No longer exists on the radio controller, it is managed by a global list. - -/obj/machinery/navbeacon - - icon = 'icons/obj/objects.dmi' - icon_state = "navbeacon0-f" - name = "navigation beacon" - desc = "A radio beacon used for bot navigation." - level = 1 // underfloor - layer = 2.5 - anchored = 1 - max_integrity = 500 - armor = list(melee = 70, bullet = 70, laser = 70, energy = 70, bomb = 0, bio = 0, rad = 0, fire = 80, acid = 80) - var/open = 0 // true if cover is open - var/locked = 1 // true if controls are locked - var/location = "" // location response text - var/list/codes // assoc. list of transponder codes - var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value" - - req_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) - -/obj/machinery/navbeacon/New() - ..() - - set_codes() - - var/turf/T = loc - hide(T.intact) - if(!codes || !codes.len) - log_runtime(EXCEPTION("Empty codes datum at ([x],[y],[z])"), src, list("codes_txt: '[codes_txt]'")) - if("patrol" in codes) - if(!GLOB.navbeacons["[z]"]) - GLOB.navbeacons["[z]"] = list() - GLOB.navbeacons["[z]"] += src //Register with the patrol list! - if("delivery" in codes) - GLOB.deliverybeacons += src - GLOB.deliverybeacontags += location - -/obj/machinery/navbeacon/Destroy() - GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one. - GLOB.deliverybeacons -= src - return ..() - -/obj/machinery/navbeacon/serialize() - var/list/data = ..() - data["codes"] = codes - return data - -/obj/machinery/navbeacon/deserialize(list/data) - codes = data["codes"] - ..() - -// set the transponder codes assoc list from codes_txt -/obj/machinery/navbeacon/proc/set_codes() - if(!codes_txt) - return - - codes = new() - - var/list/entries = splittext(codes_txt, ";") // entries are separated by semicolons - - for(var/e in entries) - var/index = findtext(e, "=") // format is "key=value" - if(index) - var/key = copytext(e, 1, index) - var/val = copytext(e, index+1) - codes[key] = val - else - codes[e] = "1" - - -// called when turf state changes -// hide the object if turf is intact -/obj/machinery/navbeacon/hide(intact) - invisibility = intact ? INVISIBILITY_MAXIMUM : 0 - updateicon() - -// update the icon_state -/obj/machinery/navbeacon/proc/updateicon() - var/state="navbeacon[open]" - - if(invisibility) - icon_state = "[state]-f" // if invisible, set icon to faded version - // in case revealed by T-scanner - else - icon_state = "[state]" - -/obj/machinery/navbeacon/attackby(obj/item/I, mob/user, params) - var/turf/T = loc - if(T.intact) - return // prevent intraction when T-scanner revealed - - if(istype(I, /obj/item/screwdriver)) - open = !open - - user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") - - updateicon() - - else if(istype(I, /obj/item/card/id) || istype(I, /obj/item/pda)) - if(open) - if(allowed(user)) - locked = !locked - to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") - else - to_chat(user, "Access denied.") - updateDialog() - else - to_chat(user, "You must open the cover first!") - else - return ..() - -/obj/machinery/navbeacon/attack_ai(mob/user) - interact(user, 1) - -/obj/machinery/navbeacon/attack_hand(mob/user) - interact(user, 0) - -/obj/machinery/navbeacon/interact(mob/user, ai = 0) - var/turf/T = loc - if(T.intact) - return // prevent intraction when T-scanner revealed - - if(!open && !ai) // can't alter controls if not open, unless you're an AI - to_chat(user, "The beacon's control cover is closed!") - return - - - var/t - - if(locked && !ai) - t = {"Navigation Beacon

        -(swipe card to unlock controls)
        -Location: [location ? location : "(none)"]
        -Transponder Codes:
          "} - - for(var/key in codes) - t += "
        • [key] ... [codes[key]]" - t+= "
            " - - else - - t = {"Navigation Beacon

            -(swipe card to lock controls)
            - -
            -Location: [location ? location : "None"]
            -Transponder Codes:
              "} - - for(var/key in codes) - t += "
            • [key] ... [codes[key]]" - t += " Edit" - t += " Delete
              " - t += " Add New
              " - t+= "
                " - - var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) - popup.set_content(t) - popup.open() - return - -/obj/machinery/navbeacon/Topic(href, href_list) - if(..()) - return - if(open && !locked) - usr.set_machine(src) - - if(href_list["locedit"]) - var/newloc = copytext(sanitize(input("Enter New Location", "Navigation Beacon", location) as text|null),1,MAX_MESSAGE_LEN) - if(newloc) - location = newloc - updateDialog() - - else if(href_list["edit"]) - var/codekey = href_list["code"] - - var/newkey = stripped_input(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey) - if(!newkey) - return - - var/codeval = codes[codekey] - var/newval = stripped_input(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval) - if(!newval) - newval = codekey - return - - codes.Remove(codekey) - codes[newkey] = newval - - updateDialog() - - else if(href_list["delete"]) - var/codekey = href_list["code"] - codes.Remove(codekey) - updateDialog() - - else if(href_list["add"]) - - var/newkey = stripped_input(usr, "Enter New Transponder Code Key", "Navigation Beacon") - if(!newkey) - return - - var/newval = stripped_input(usr, "Enter New Transponder Code Value", "Navigation Beacon") - if(!newval) - newval = "1" - return - - if(!codes) - codes = new() - - codes[newkey] = newval - - updateDialog() - - -/obj/machinery/navbeacon/invisible - invisibility = INVISIBILITY_MAXIMUM - -/obj/machinery/navbeacon/invisible/hide(intact) - invisibility = INVISIBILITY_MAXIMUM - updateicon() \ No newline at end of file +// Navigation beacon for AI robots +// No longer exists on the radio controller, it is managed by a global list. + +/obj/machinery/navbeacon + + icon = 'icons/obj/objects.dmi' + icon_state = "navbeacon0-f" + name = "navigation beacon" + desc = "A radio beacon used for bot navigation." + level = 1 // underfloor + layer = 2.5 + anchored = 1 + max_integrity = 500 + armor = list(melee = 70, bullet = 70, laser = 70, energy = 70, bomb = 0, bio = 0, rad = 0, fire = 80, acid = 80) + var/open = 0 // true if cover is open + var/locked = 1 // true if controls are locked + var/location = "" // location response text + var/list/codes // assoc. list of transponder codes + var/codes_txt = "" // codes as set on map: "tag1;tag2" or "tag1=value;tag2=value" + + req_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) + +/obj/machinery/navbeacon/New() + ..() + + set_codes() + + var/turf/T = loc + hide(T.intact) + if(!codes || !codes.len) + log_runtime(EXCEPTION("Empty codes datum at ([x],[y],[z])"), src, list("codes_txt: '[codes_txt]'")) + if("patrol" in codes) + if(!GLOB.navbeacons["[z]"]) + GLOB.navbeacons["[z]"] = list() + GLOB.navbeacons["[z]"] += src //Register with the patrol list! + if("delivery" in codes) + GLOB.deliverybeacons += src + GLOB.deliverybeacontags += location + +/obj/machinery/navbeacon/Destroy() + GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one. + GLOB.deliverybeacons -= src + return ..() + +/obj/machinery/navbeacon/serialize() + var/list/data = ..() + data["codes"] = codes + return data + +/obj/machinery/navbeacon/deserialize(list/data) + codes = data["codes"] + ..() + +// set the transponder codes assoc list from codes_txt +/obj/machinery/navbeacon/proc/set_codes() + if(!codes_txt) + return + + codes = new() + + var/list/entries = splittext(codes_txt, ";") // entries are separated by semicolons + + for(var/e in entries) + var/index = findtext(e, "=") // format is "key=value" + if(index) + var/key = copytext(e, 1, index) + var/val = copytext(e, index+1) + codes[key] = val + else + codes[e] = "1" + + +// called when turf state changes +// hide the object if turf is intact +/obj/machinery/navbeacon/hide(intact) + invisibility = intact ? INVISIBILITY_MAXIMUM : 0 + updateicon() + +// update the icon_state +/obj/machinery/navbeacon/proc/updateicon() + var/state="navbeacon[open]" + + if(invisibility) + icon_state = "[state]-f" // if invisible, set icon to faded version + // in case revealed by T-scanner + else + icon_state = "[state]" + +/obj/machinery/navbeacon/attackby(obj/item/I, mob/user, params) + var/turf/T = loc + if(T.intact) + return // prevent intraction when T-scanner revealed + + if(istype(I, /obj/item/screwdriver)) + open = !open + + user.visible_message("[user] [open ? "opens" : "closes"] the beacon's cover.", "You [open ? "open" : "close"] the beacon's cover.") + + updateicon() + + else if(istype(I, /obj/item/card/id) || istype(I, /obj/item/pda)) + if(open) + if(allowed(user)) + locked = !locked + to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") + else + to_chat(user, "Access denied.") + updateDialog() + else + to_chat(user, "You must open the cover first!") + else + return ..() + +/obj/machinery/navbeacon/attack_ai(mob/user) + interact(user, 1) + +/obj/machinery/navbeacon/attack_hand(mob/user) + interact(user, 0) + +/obj/machinery/navbeacon/interact(mob/user, ai = 0) + var/turf/T = loc + if(T.intact) + return // prevent intraction when T-scanner revealed + + if(!open && !ai) // can't alter controls if not open, unless you're an AI + to_chat(user, "The beacon's control cover is closed!") + return + + + var/t + + if(locked && !ai) + t = {"Navigation Beacon

                +(swipe card to unlock controls)
                +Location: [location ? location : "(none)"]
                +Transponder Codes:
                  "} + + for(var/key in codes) + t += "
                • [key] ... [codes[key]]" + t+= "
                    " + + else + + t = {"Navigation Beacon

                    +(swipe card to lock controls)
                    + +
                    +Location: [location ? location : "None"]
                    +Transponder Codes:
                      "} + + for(var/key in codes) + t += "
                    • [key] ... [codes[key]]" + t += " Edit" + t += " Delete
                      " + t += " Add New
                      " + t+= "
                        " + + var/datum/browser/popup = new(user, "navbeacon", "Navigation Beacon", 300, 400) + popup.set_content(t) + popup.open() + return + +/obj/machinery/navbeacon/Topic(href, href_list) + if(..()) + return + if(open && !locked) + usr.set_machine(src) + + if(href_list["locedit"]) + var/newloc = copytext(sanitize(input("Enter New Location", "Navigation Beacon", location) as text|null),1,MAX_MESSAGE_LEN) + if(newloc) + location = newloc + updateDialog() + + else if(href_list["edit"]) + var/codekey = href_list["code"] + + var/newkey = stripped_input(usr, "Enter Transponder Code Key", "Navigation Beacon", codekey) + if(!newkey) + return + + var/codeval = codes[codekey] + var/newval = stripped_input(usr, "Enter Transponder Code Value", "Navigation Beacon", codeval) + if(!newval) + newval = codekey + return + + codes.Remove(codekey) + codes[newkey] = newval + + updateDialog() + + else if(href_list["delete"]) + var/codekey = href_list["code"] + codes.Remove(codekey) + updateDialog() + + else if(href_list["add"]) + + var/newkey = stripped_input(usr, "Enter New Transponder Code Key", "Navigation Beacon") + if(!newkey) + return + + var/newval = stripped_input(usr, "Enter New Transponder Code Value", "Navigation Beacon") + if(!newval) + newval = "1" + return + + if(!codes) + codes = new() + + codes[newkey] = newval + + updateDialog() + + +/obj/machinery/navbeacon/invisible + invisibility = INVISIBILITY_MAXIMUM + +/obj/machinery/navbeacon/invisible/hide(intact) + invisibility = INVISIBILITY_MAXIMUM + updateicon() diff --git a/code/game/machinery/overview.dm b/code/game/machinery/overview.dm index e77eef869dc..7165cceb1ff 100644 --- a/code/game/machinery/overview.dm +++ b/code/game/machinery/overview.dm @@ -1,362 +1,362 @@ -//#define AMAP - -/obj/machinery/computer/security/verb/station_map() - set name = ".map" - set category = "Object" - set src in view(1) - usr.set_machine(src) - if(!mapping) return - - log_game("[usr]([usr.key]) used station map L[z] in [src.loc.loc]") - - src.drawmap(usr) - -/obj/machinery/computer/security/proc/drawmap(var/mob/user as mob) - - var/icx = round(world.maxx/16) + 1 - var/icy = round(world.maxy/16) + 1 - - var/xoff = round( (icx*16-world.maxx)-2) - var/yoff = round( (icy*16-world.maxy)-2) - - var/icount = icx * icy - - - var/list/imap = list() - -#ifdef AMAP - - for(var/i = 0; i
        - - - - -
        - [left_part] - - [list_queue()] -
        "} - var/datum/browser/popup = new(user, "mecha_fabricator", name, 1000, 600) - popup.set_content(dat) - popup.open(0) - onclose(user, "mecha_fabricator") - return - -/obj/machinery/mecha_part_fabricator/Topic(href, href_list) - if(..()) - return 1 - var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) - if(href_list["part_set"]) - var/tpart_set = afilter.getStr("part_set") - if(tpart_set) - if(tpart_set=="clear") - part_set = null - else - part_set = tpart_set - screen = "parts" - if(href_list["part"]) - var/T = afilter.getStr("part") - for(var/v in files.known_designs) - var/datum/design/D = files.known_designs[v] - if(D.build_type & fabricator_type) - if(D.id == T) - if(!processing_queue) - build_part(D) - else - add_to_queue(D) - break - if(href_list["add_to_queue"]) - var/T = afilter.getStr("add_to_queue") - for(var/v in files.known_designs) - var/datum/design/D = files.known_designs[v] - if(D.build_type & fabricator_type) - if(D.id == T) - add_to_queue(D) - break - return update_queue_on_page() - if(href_list["remove_from_queue"]) - remove_from_queue(afilter.getNum("remove_from_queue")) - return update_queue_on_page() - if(href_list["partset_to_queue"]) - add_part_set_to_queue(afilter.get("partset_to_queue")) - return update_queue_on_page() - if(href_list["process_queue"]) - spawn(0) - if(processing_queue || being_built) - return FALSE - processing_queue = 1 - process_queue() - processing_queue = 0 - if(href_list["clear_temp"]) - temp = null - if(href_list["screen"]) - screen = href_list["screen"] - if(href_list["queue_move"] && href_list["index"]) - var/index = afilter.getNum("index") - var/new_index = index + afilter.getNum("queue_move") - if(isnum(index) && isnum(new_index)) - if(IsInRange(new_index,1,queue.len)) - queue.Swap(index,new_index) - return update_queue_on_page() - if(href_list["clear_queue"]) - queue = list() - return update_queue_on_page() - if(href_list["sync"]) - sync() - if(href_list["part_desc"]) - var/T = afilter.getStr("part_desc") - for(var/v in files.known_designs) - var/datum/design/D = files.known_designs[v] - if(D.build_type & fabricator_type) - if(D.id == T) - var/obj/part = D.build_path - temp = {"

        [initial(part.name)] description:

        - [initial(part.desc)]
        - Return - "} - break - - if(href_list["remove_mat"] && href_list["material"]) - GET_COMPONENT(materials, /datum/component/material_container) - materials.retrieve_sheets(text2num(href_list["remove_mat"]), href_list["material"]) - - updateUsrDialog() - return - -/obj/machinery/mecha_part_fabricator/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) - var/stack_name = material2name(id_inserted) - overlays += "fab-load-[stack_name]" - sleep(10) - overlays -= "fab-load-[stack_name]" - updateUsrDialog() - -/obj/machinery/mecha_part_fabricator/attackby(obj/item/W, mob/user, params) - if(default_deconstruction_screwdriver(user, "fab-o", "fab-idle", W)) - return - - if(exchange_parts(user, W)) - return - - if(default_deconstruction_crowbar(user, W)) - return TRUE - - else - return ..() - -/obj/machinery/mecha_part_fabricator/proc/material2name(ID) - return copytext(ID,2) - -/obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) - if(panel_open) - to_chat(user, "You can't load [src] while it's opened!") - return FALSE - if(being_built) - to_chat(user, "\The [src] is currently processing! Please wait until completion.") - return FALSE - - return TRUE - -/obj/machinery/mecha_part_fabricator/spacepod - name = "spacepod fabricator" - fabricator_type = PODFAB - part_sets = list( "Pod_Weaponry", - "Pod_Armor", - "Pod_Cargo", - "Pod_Parts", - "Pod_Frame", - "Misc") - req_access = list(ACCESS_MECHANIC) - -/obj/machinery/mecha_part_fabricator/spacepod/New() - ..() - QDEL_LIST(component_parts) - component_parts = list() - component_parts += new /obj/item/circuitboard/podfab(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/micro_laser(null) - component_parts += new /obj/item/stack/sheet/glass(null) - RefreshParts() - -/obj/machinery/mecha_part_fabricator/robot - name = "Robotic Fabricator" - part_sets = list("Cyborg") +/obj/machinery/mecha_part_fabricator + icon = 'icons/obj/robotics.dmi' + icon_state = "fab-idle" + name = "exosuit fabricator" + desc = "Nothing is being built." + density = TRUE + anchored = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 20 + active_power_usage = 5000 + var/time_coeff = 1 + var/component_coeff = 1 + var/datum/research/files + var/fabricator_type = MECHFAB + var/id + var/sync = 0 + var/part_set + var/datum/design/being_built + var/list/queue = list() + var/processing_queue = 0 + var/screen = "main" + var/temp + var/list/part_sets = list( + "Cyborg", + "Cyborg Repair", + "Ripley", + "Firefighter", + "Odysseus", + "Gygax", + "Durand", + "H.O.N.K", + "Reticence", + "Phazon", + "Exosuit Equipment", + "Cyborg Upgrade Modules", + "Medical", + "Misc" + ) + +/obj/machinery/mecha_part_fabricator/New() + var/datum/component/material_container/materials = AddComponent(/datum/component/material_container, + list(MAT_METAL, MAT_GLASS, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_PLASMA, MAT_URANIUM, MAT_BANANIUM, MAT_TRANQUILLITE, MAT_TITANIUM, MAT_BLUESPACE), 0, + FALSE, /obj/item/stack, CALLBACK(src, .proc/is_insertion_ready), CALLBACK(src, .proc/AfterMaterialInsert)) + materials.precise_insertion = TRUE + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/mechfab(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/micro_laser(null) + component_parts += new /obj/item/stack/sheet/glass(null) + RefreshParts() + files = new /datum/research(src) //Setup the research data holder. + +/obj/machinery/mecha_part_fabricator/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/mechfab(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) + component_parts += new /obj/item/stack/sheet/glass(null) + RefreshParts() + +/obj/machinery/mecha_part_fabricator/Destroy() + GET_COMPONENT(materials, /datum/component/material_container) + materials.retrieve_all() + return ..() + +/obj/machinery/mecha_part_fabricator/RefreshParts() + var/T = 0 + + //maximum stocking amount (default 300000, 600000 at T4) + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + T += M.rating + GET_COMPONENT(materials, /datum/component/material_container) + materials.max_amount = (200000 + (T*50000)) + + //resources adjustment coefficient (1 -> 0.85 -> 0.7 -> 0.55) + T = 1.15 + for(var/obj/item/stock_parts/micro_laser/Ma in component_parts) + T -= Ma.rating*0.15 + component_coeff = T + + //building time adjustment coefficient (1 -> 0.8 -> 0.6) + T = -1 + for(var/obj/item/stock_parts/manipulator/Ml in component_parts) + T += Ml.rating + time_coeff = round(initial(time_coeff) - (initial(time_coeff)*(T))/5,0.01) + + +/obj/machinery/mecha_part_fabricator/proc/output_parts_list(set_name) + var/output = "" + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(D.build_type & fabricator_type) + if(!(set_name in D.category)) + continue + output += "
        [output_part_info(D)]
        \[" + if(check_resources(D)) + output += "Build | " + output += "Add to queue\]\[?\]
        " + return output + +/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D) + var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [get_construction_time_w_coeff(D)/10] seconds" + return output + +/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D) + var/i = 0 + var/output + for(var/c in D.materials) + output += "[i?" | ":null][get_resource_cost_w_coeff(D, c)] [material2name(c)]" + i++ + return output + +/obj/machinery/mecha_part_fabricator/proc/output_available_resources() + var/output + GET_COMPONENT(materials, /datum/component/material_container) + for(var/mat_id in materials.materials) + var/datum/material/M = materials.materials[mat_id] + output += "[M.name]: [M.amount] cm³" + if(M.amount >= MINERAL_MATERIAL_AMOUNT) + output += "- Remove \[1\]" + if(M.amount >= (MINERAL_MATERIAL_AMOUNT * 10)) + output += " | \[10\]" + output += " | \[All\]" + output += "
        " + return output + +/obj/machinery/mecha_part_fabricator/proc/get_resources_w_coeff(datum/design/D) + var/list/resources = list() + for(var/R in D.materials) + resources[R] = get_resource_cost_w_coeff(D, R) + return resources + +/obj/machinery/mecha_part_fabricator/proc/check_resources(datum/design/D) + if(D.reagents_list.len) // No reagents storage - no reagent designs. + return FALSE + GET_COMPONENT(materials, /datum/component/material_container) + if(materials.has_materials(get_resources_w_coeff(D))) + return TRUE + return FALSE + +/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D) + being_built = D + desc = "It's building \a [initial(D.name)]." + var/list/res_coef = get_resources_w_coeff(D) + + GET_COMPONENT(materials, /datum/component/material_container) + materials.use_amount(res_coef) + overlays += "fab-active" + use_power = ACTIVE_POWER_USE + updateUsrDialog() + sleep(get_construction_time_w_coeff(D)) + use_power = IDLE_POWER_USE + overlays -= "fab-active" + desc = initial(desc) + + var/obj/item/I = new D.build_path(loc) + if(D.locked) + var/obj/item/storage/lockbox/research/large/L = new /obj/item/storage/lockbox/research/large(get_step(src, SOUTH)) + I.forceMove(L) + L.name += " ([I.name])" + L.origin_tech = I.origin_tech + else + I.forceMove(get_step(src, SOUTH)) + if(istype(I)) + I.materials = res_coef + atom_say("[I] is complete.") + being_built = null + + updateUsrDialog() + return TRUE + +/obj/machinery/mecha_part_fabricator/proc/update_queue_on_page() + send_byjax(usr,"mecha_fabricator.browser","queue",list_queue()) + return + +/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(set_name) + if(set_name in part_sets) + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(D.build_type & fabricator_type) + if(set_name in D.category) + add_to_queue(D) + +/obj/machinery/mecha_part_fabricator/proc/add_to_queue(D) + if(!istype(queue)) + queue = list() + if(D) + queue[++queue.len] = D + return queue.len + +/obj/machinery/mecha_part_fabricator/proc/remove_from_queue(index) + if(!isnum(index) || !istype(queue) || (index<1 || index>queue.len)) + return FALSE + queue.Cut(index,++index) + return TRUE + +/obj/machinery/mecha_part_fabricator/proc/process_queue() + var/datum/design/D = queue[1] + if(!D) + remove_from_queue(1) + if(queue.len) + return process_queue() + else + return + temp = null + while(D) + if(stat&(NOPOWER|BROKEN)) + return FALSE + if(!check_resources(D)) + atom_say("Not enough resources. Queue processing stopped.") + temp = {"Not enough resources to build next part.
        + Try again | Return"} + return FALSE + remove_from_queue(1) + build_part(D) + D = listgetindex(queue, 1) + atom_say("Queue processing finished successfully.") + +/obj/machinery/mecha_part_fabricator/proc/list_queue() + var/output = "Queue contains:" + if(!istype(queue) || !queue.len) + output += "
        Nothing" + else + output += "
          " + var/i = 0 + for(var/datum/design/D in queue) + i++ + var/obj/part = D.build_path + output += "" + output += initial(part.name) + " - " + output += "[i>1?"":null] " + output += "[i↓":null] " + output += "Remove" + + output += "
        " + output += "\[Process queue | Clear queue\]" + return output + +/obj/machinery/mecha_part_fabricator/proc/sync() + temp = "Updating local R&D database..." + updateUsrDialog() + sleep(30) //only sleep if called by user + var/area/localarea = get_area(src) + + for(var/obj/machinery/computer/rdconsole/RDC in localarea.contents) + if(!RDC.sync) + continue + RDC.files.push_data(files) + temp = "Processed equipment designs.
        " + //check if the tech coefficients have changed + temp += "Return" + + updateUsrDialog() + atom_say("Successfully synchronized with R&D server.") + return + + temp = "Unable to connect to local R&D Database.
        Please check your connections and try again.
        Return" + updateUsrDialog() + return + +/obj/machinery/mecha_part_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, resource, roundto = 1) + return round(D.materials[resource]*component_coeff, roundto) + +/obj/machinery/mecha_part_fabricator/proc/get_construction_time_w_coeff(datum/design/D, roundto = 1) //aran + return round(initial(D.construction_time)*time_coeff, roundto) + +/obj/machinery/mecha_part_fabricator/attack_ghost(mob/user) + interact(user) + +/obj/machinery/mecha_part_fabricator/attack_hand(mob/user) + if(..()) + return 1 + if(!allowed(user) && !isobserver(user)) + to_chat(user, "Access denied.") + return 1 + return interact(user) + +/obj/machinery/mecha_part_fabricator/interact(mob/user) + var/dat, left_part + if(..()) + return + user.set_machine(src) + var/turf/exit = get_step(src,(dir)) + if(exit.density) + atom_say("Error! Part outlet is obstructed.") + return + if(temp) + left_part = temp + else if(being_built) + var/obj/I = being_built.build_path + left_part = {"Building [initial(I.name)].
        + Please wait until completion...
        "} + else + switch(screen) + if("main") + left_part = output_available_resources()+"
        " + left_part += "Sync with R&D servers
        " + for(var/part_set in part_sets) + left_part += "[part_set] - \[Add all parts to queue\]
        " + if("parts") + left_part += output_parts_list(part_set) + left_part += "
        Return" + dat = {" + + [name] + + + + + + + + +
        + [left_part] + + [list_queue()] +
        "} + var/datum/browser/popup = new(user, "mecha_fabricator", name, 1000, 600) + popup.set_content(dat) + popup.open(0) + onclose(user, "mecha_fabricator") + return + +/obj/machinery/mecha_part_fabricator/Topic(href, href_list) + if(..()) + return 1 + var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) + if(href_list["part_set"]) + var/tpart_set = afilter.getStr("part_set") + if(tpart_set) + if(tpart_set=="clear") + part_set = null + else + part_set = tpart_set + screen = "parts" + if(href_list["part"]) + var/T = afilter.getStr("part") + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(D.build_type & fabricator_type) + if(D.id == T) + if(!processing_queue) + build_part(D) + else + add_to_queue(D) + break + if(href_list["add_to_queue"]) + var/T = afilter.getStr("add_to_queue") + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(D.build_type & fabricator_type) + if(D.id == T) + add_to_queue(D) + break + return update_queue_on_page() + if(href_list["remove_from_queue"]) + remove_from_queue(afilter.getNum("remove_from_queue")) + return update_queue_on_page() + if(href_list["partset_to_queue"]) + add_part_set_to_queue(afilter.get("partset_to_queue")) + return update_queue_on_page() + if(href_list["process_queue"]) + spawn(0) + if(processing_queue || being_built) + return FALSE + processing_queue = 1 + process_queue() + processing_queue = 0 + if(href_list["clear_temp"]) + temp = null + if(href_list["screen"]) + screen = href_list["screen"] + if(href_list["queue_move"] && href_list["index"]) + var/index = afilter.getNum("index") + var/new_index = index + afilter.getNum("queue_move") + if(isnum(index) && isnum(new_index)) + if(IsInRange(new_index,1,queue.len)) + queue.Swap(index,new_index) + return update_queue_on_page() + if(href_list["clear_queue"]) + queue = list() + return update_queue_on_page() + if(href_list["sync"]) + sync() + if(href_list["part_desc"]) + var/T = afilter.getStr("part_desc") + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(D.build_type & fabricator_type) + if(D.id == T) + var/obj/part = D.build_path + temp = {"

        [initial(part.name)] description:

        + [initial(part.desc)]
        + Return + "} + break + + if(href_list["remove_mat"] && href_list["material"]) + GET_COMPONENT(materials, /datum/component/material_container) + materials.retrieve_sheets(text2num(href_list["remove_mat"]), href_list["material"]) + + updateUsrDialog() + return + +/obj/machinery/mecha_part_fabricator/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) + var/stack_name = material2name(id_inserted) + overlays += "fab-load-[stack_name]" + sleep(10) + overlays -= "fab-load-[stack_name]" + updateUsrDialog() + +/obj/machinery/mecha_part_fabricator/attackby(obj/item/W, mob/user, params) + if(default_deconstruction_screwdriver(user, "fab-o", "fab-idle", W)) + return + + if(exchange_parts(user, W)) + return + + if(default_deconstruction_crowbar(user, W)) + return TRUE + + else + return ..() + +/obj/machinery/mecha_part_fabricator/proc/material2name(ID) + return copytext(ID,2) + +/obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) + if(panel_open) + to_chat(user, "You can't load [src] while it's opened!") + return FALSE + if(being_built) + to_chat(user, "\The [src] is currently processing! Please wait until completion.") + return FALSE + + return TRUE + +/obj/machinery/mecha_part_fabricator/spacepod + name = "spacepod fabricator" + fabricator_type = PODFAB + part_sets = list( "Pod_Weaponry", + "Pod_Armor", + "Pod_Cargo", + "Pod_Parts", + "Pod_Frame", + "Misc") + req_access = list(ACCESS_MECHANIC) + +/obj/machinery/mecha_part_fabricator/spacepod/New() + ..() + QDEL_LIST(component_parts) + component_parts = list() + component_parts += new /obj/item/circuitboard/podfab(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/micro_laser(null) + component_parts += new /obj/item/stack/sheet/glass(null) + RefreshParts() + +/obj/machinery/mecha_part_fabricator/robot + name = "Robotic Fabricator" + part_sets = list("Cyborg") diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 6d8852908a3..69ba9941303 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -1,1521 +1,1521 @@ -/obj/mecha - name = "Mecha" - desc = "Exosuit" - icon = 'icons/mecha/mecha.dmi' - density = 1 //Dense. To raise the heat. - opacity = 1 ///opaque. Menacing. - anchored = 1 //no pulling around. - resistance_flags = FIRE_PROOF | ACID_PROOF - layer = MOB_LAYER //icon draw layer - infra_luminosity = 15 //byond implementation is bugged. - force = 5 - max_integrity = 300 //max_integrity is base health - armor = list(melee = 20, bullet = 10, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 100) - var/list/facing_modifiers = list(MECHA_FRONT_ARMOUR = 1.5, MECHA_SIDE_ARMOUR = 1, MECHA_BACK_ARMOUR = 0.5) - var/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming. - var/initial_icon = null //Mech type for resetting icon. Only used for reskinning kits (see custom items) - var/can_move = 0 // time of next allowed movement - var/mob/living/carbon/occupant = null - var/step_in = 10 //make a step in step_in/10 sec. - var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South. - var/normal_step_energy_drain = 10 - var/step_energy_drain = 10 - var/melee_energy_drain = 15 - var/overload_step_energy_drain_min = 100 - var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. - var/obj/item/stock_parts/cell/cell - var/state = 0 - var/list/log = new - var/last_message = 0 - var/add_req_access = 1 - var/maint_access = 1 - var/dna //dna-locking the mech - var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference - var/datum/effect_system/spark_spread/spark_system = new - var/lights = 0 - var/lights_power = 6 - var/emagged = FALSE - - //inner atmos - var/use_internal_tank = 0 - var/internal_tank_valve = ONE_ATMOSPHERE - var/obj/machinery/portable_atmospherics/canister/internal_tank - var/datum/gas_mixture/cabin_air - var/obj/machinery/atmospherics/unary/portables_connector/connected_port = null - - var/obj/item/radio/radio = null - var/list/trackers = list() - - var/max_temperature = 25000 - var/internal_damage_threshold = 50 //health percentage below which internal damage is possible - var/internal_damage = 0 //contains bitflags - - var/list/operation_req_access = list()//required access level for mecha operation - var/list/internals_req_access = list(ACCESS_ENGINE,ACCESS_ROBOTICS)//required access level to open cell compartment - - var/wreckage - - var/list/equipment = new - var/obj/item/mecha_parts/mecha_equipment/selected - var/max_equip = 3 - var/datum/events/events - var/turf/crashing = null - var/occupant_sight_flags = 0 - - var/stepsound = 'sound/mecha/mechstep.ogg' - var/turnsound = 'sound/mecha/mechturn.ogg' - var/nominalsound = 'sound/mecha/nominal.ogg' - var/zoomsound = 'sound/mecha/imag_enh.ogg' - var/critdestrsound = 'sound/mecha/critdestr.ogg' - var/weapdestrsound = 'sound/mecha/weapdestr.ogg' - var/lowpowersound = 'sound/mecha/lowpower.ogg' - var/longactivationsound = 'sound/mecha/nominal.ogg' - var/starting_voice = /obj/item/mecha_modkit/voice - var/activated = FALSE - var/power_warned = FALSE - - var/destruction_sleep_duration = 1 //Time that mech pilot is put to sleep for if mech is destroyed - - var/melee_cooldown = 10 - var/melee_can_hit = 1 - - // Action vars - var/defence_mode = FALSE - var/defence_mode_deflect_chance = 35 - var/leg_overload_mode = FALSE - var/leg_overload_coeff = 100 - var/thrusters_active = FALSE - var/smoke = 5 - var/smoke_ready = 1 - var/smoke_cooldown = 100 - var/zoom_mode = FALSE - var/phasing = FALSE - var/phasing_energy_drain = 200 - var/phase_state = "" //icon_state when phasing - - hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD) - -/obj/mecha/Initialize() - . = ..() - events = new - icon_state += "-open" - add_radio() - add_cabin() - add_airtank() - spark_system.set_up(2, 0, src) - spark_system.attach(src) - smoke_system.set_up(3, src) - smoke_system.attach(src) - add_cell() - START_PROCESSING(SSobj, src) - GLOB.poi_list |= src - log_message("[src] created.") - GLOB.mechas_list += src //global mech list - prepare_huds() - for(var/datum/atom_hud/data/diagnostic/diag_hud in huds) - diag_hud.add_to_hud(src) - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - diag_hud_set_mechtracking() - - var/obj/item/mecha_modkit/voice/V = new starting_voice(src) - V.install(src) - qdel(V) - -//////////////////////// -////// Helpers ///////// -//////////////////////// - -/obj/mecha/get_cell() - return cell - -/obj/mecha/proc/add_airtank() - internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) - return internal_tank - -/obj/mecha/proc/add_cell(var/obj/item/stock_parts/cell/C=null) - if(C) - C.forceMove(src) - cell = C - return - cell = new/obj/item/stock_parts/cell/high/plus(src) - -/obj/mecha/proc/add_cabin() - cabin_air = new - cabin_air.temperature = T20C - cabin_air.volume = 200 - cabin_air.oxygen = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - cabin_air.nitrogen = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - return cabin_air - -/obj/mecha/proc/add_radio() - radio = new(src) - radio.name = "[src] radio" - radio.icon = icon - radio.icon_state = icon_state - radio.subspace_transmission = 1 - -/obj/mecha/examine(mob/user) - . = ..() - var/integrity = obj_integrity * 100 / max_integrity - switch(integrity) - if(85 to 100) - . += "It's fully intact." - if(65 to 85) - . += "It's slightly damaged." - if(45 to 65) - . += "It's badly damaged." - if(25 to 45) - . += "It's heavily damaged." - else - . += "It's falling apart." - if(equipment && equipment.len) - . += "It's equipped with:" - for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment) - . += "[bicon(ME)] [ME]" - -/obj/mecha/hear_talk(mob/M, list/message_pieces) - if(M == occupant && radio.broadcasting) - radio.talk_into(M, message_pieces) - -/obj/mecha/proc/click_action(atom/target, mob/user, params) - if(!occupant || occupant != user ) - return - if(user.incapacitated()) - return - if(phasing) - occupant_message("Unable to interact with objects while phasing.") - return - if(state) - occupant_message("Maintenance protocols in effect.") - return - if(!get_charge()) - return - if(src == target) - return - - var/dir_to_target = get_dir(src, target) - if(dir_to_target && !(dir_to_target & dir))//wrong direction - return - - if(hasInternalDamage(MECHA_INT_CONTROL_LOST)) - target = safepick(view(3,target)) - if(!target) - return - - var/mob/living/L = user - if(!target.Adjacent(src)) - if(selected && selected.is_ranged()) - if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful) - to_chat(L, "You don't want to harm other living beings!") - return - selected.action(target, params) - else if(selected && selected.is_melee()) - if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm other living beings!") - return - selected.action(target, params) - else - if(internal_damage & MECHA_INT_CONTROL_LOST) - target = safepick(oview(1, src)) - if(!melee_can_hit || !isatom(target)) - return - target.mech_melee_attack(src) - melee_can_hit = 0 - spawn(melee_cooldown) - melee_can_hit = 1 - -/obj/mecha/proc/mech_toxin_damage(mob/living/target) - playsound(src, 'sound/effects/spray2.ogg', 50, 1) - if(target.reagents) - if(target.reagents.get_reagent_amount("atropine") + force < force*2) - target.reagents.add_reagent("atropine", force/2) - if(target.reagents.get_reagent_amount("toxin") + force < force*2) - target.reagents.add_reagent("toxin", force/2.5) - -/obj/mecha/proc/range_action(atom/target) - return - - -////////////////////////////////// -//////// Movement procs //////// -////////////////////////////////// - -/obj/mecha/Move(atom/newLoc, direct) - . = ..() - if(.) - events.fireEvent("onMove",get_turf(src)) - -/obj/mecha/Process_Spacemove(var/movement_dir = 0) - . = ..() - if(.) - return 1 - if(thrusters_active && movement_dir && use_power(step_energy_drain)) - return 1 - - var/atom/movable/backup = get_spacemove_backup() - if(backup) - if(istype(backup) && movement_dir && !backup.anchored) - if(backup.newtonian_move(turn(movement_dir, 180))) - if(occupant) - to_chat(occupant, "You push off of [backup] to propel yourself.") - return 1 - -/obj/mecha/relaymove(mob/user, direction) - if(!direction) - return - if(user != occupant) //While not "realistic", this piece is player friendly. - user.forceMove(get_turf(src)) - to_chat(user, "You climb out from [src].") - return 0 - if(connected_port) - if(world.time - last_message > 20) - occupant_message("Unable to move while connected to the air system port!") - last_message = world.time - return 0 - if(state) - occupant_message("Maintenance protocols in effect.") - return - return domove(direction) - -/obj/mecha/proc/domove(direction) - if(can_move >= world.time) - return 0 - if(!Process_Spacemove(direction)) - return 0 - if(!has_charge(step_energy_drain)) - return 0 - if(defence_mode) - if(world.time - last_message > 20) - occupant_message("Unable to move while in defence mode.") - last_message = world.time - return 0 - if(zoom_mode) - if(world.time - last_message > 20) - occupant_message("Unable to move while in zoom mode.") - last_message = world.time - return 0 - - var/move_result = 0 - var/move_type = 0 - if(internal_damage & MECHA_INT_CONTROL_LOST) - move_result = mechsteprand() - move_type = MECHAMOVE_RAND - else if(dir != direction) - move_result = mechturn(direction) - move_type = MECHAMOVE_TURN - else - move_result = mechstep(direction) - move_type = MECHAMOVE_STEP - - if(move_result && move_type) - aftermove(move_type) - can_move = world.time + step_in - return TRUE - return FALSE - -/obj/mecha/proc/aftermove(move_type) - use_power(step_energy_drain) - if(move_type & (MECHAMOVE_RAND | MECHAMOVE_STEP) && occupant) - var/obj/machinery/atmospherics/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/unary/portables_connector) in loc - if(possible_port) - var/obj/screen/alert/mech_port_available/A = occupant.throw_alert("mechaport", /obj/screen/alert/mech_port_available, override = TRUE) - if(A) - A.target = possible_port - else - occupant.clear_alert("mechaport") - if(leg_overload_mode) - if(obj_integrity < max_integrity - max_integrity / 3) - leg_overload_mode = FALSE - step_in = initial(step_in) - step_energy_drain = initial(step_energy_drain) - occupant_message("Leg actuators damage threshold exceded. Disabling overload.") - -/obj/mecha/proc/mechturn(direction) - dir = direction - if(turnsound) - playsound(src,turnsound,40,1) - return 1 - -/obj/mecha/proc/mechstep(direction) - . = step(src, direction) - if(!.) - if(phasing && get_charge() >= phasing_energy_drain) - if(can_move < world.time) - . = FALSE // We lie to mech code and say we didn't get to move, because we want to handle power usage + cooldown ourself - flick("phazon-phase", src) - forceMove(get_step(src, direction)) - use_power(phasing_energy_drain) - playsound(src, stepsound, 40, 1) - can_move = world.time + (step_in * 3) - else if(stepsound) - playsound(src, stepsound, 40, 1) - -/obj/mecha/proc/mechsteprand() - . = step_rand(src) - if(. && stepsound) - playsound(src, stepsound, 40, 1) - -/obj/mecha/Bump(var/atom/obstacle, bump_allowed) - if(throwing) //high velocity mechas in your face! - var/breakthrough = 0 - if(istype(obstacle, /obj/structure/window)) - qdel(obstacle) - breakthrough = 1 - - else if(istype(obstacle, /obj/structure/grille/)) - var/obj/structure/grille/G = obstacle - G.obj_break() - breakthrough = 1 - - else if(istype(obstacle, /obj/structure/table)) - var/obj/structure/table/T = obstacle - qdel(T) - breakthrough = 1 - - else if(istype(obstacle, /obj/structure/rack)) - new /obj/item/rack_parts(obstacle.loc) - qdel(obstacle) - breakthrough = 1 - - else if(istype(obstacle, /obj/structure/reagent_dispensers/fueltank)) - obstacle.ex_act(1) - - else if(isliving(obstacle)) - var/mob/living/L = obstacle - var/hit_sound = list('sound/weapons/genhit1.ogg','sound/weapons/genhit2.ogg','sound/weapons/genhit3.ogg') - if(L.flags & GODMODE) - return - L.take_overall_damage(5,0) - if(L.buckled) - L.buckled = 0 - L.Stun(5) - L.Weaken(5) - L.apply_effect(STUTTER, 5) - playsound(src, pick(hit_sound), 50, 0, 0) - breakthrough = 1 - - else - if(throwing) - throwing.finalize(FALSE) - crashing = null - - ..() - - if(breakthrough) - if(crashing) - spawn(1) - throw_at(crashing, 50, throw_speed) - else - spawn(1) - crashing = get_distant_turf(get_turf(src), dir, 3)//don't use get_dir(src, obstacle) or the mech will stop if he bumps into a one-direction window on his tile. - throw_at(crashing, 50, throw_speed) - - else - if(bump_allowed) - if(..()) - return - if(isobj(obstacle)) - var/obj/O = obstacle - if(istype(O, /obj/effect/portal)) //derpfix - anchored = 0 - O.Bumped(src) - spawn(0) //countering portal teleport spawn(0), hurr - anchored = 1 - else if(!O.anchored) - step(obstacle, dir) - else if(ismob(obstacle)) - step(obstacle, dir) - - -/////////////////////////////////// -//////// Internal damage //////// -/////////////////////////////////// - -/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage, ignore_threshold=null) - if(!islist(possible_int_damage) || isemptylist(possible_int_damage)) - return - if(prob(20)) - if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) - for(var/T in possible_int_damage) - if(internal_damage & T) - possible_int_damage -= T - var/int_dam_flag = safepick(possible_int_damage) - if(int_dam_flag) - setInternalDamage(int_dam_flag) - if(prob(5)) - if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) - var/obj/item/mecha_parts/mecha_equipment/ME = safepick(equipment) - if(ME) - qdel(ME) - -/obj/mecha/proc/hasInternalDamage(int_dam_flag=null) - return int_dam_flag ? internal_damage&int_dam_flag : internal_damage - - -/obj/mecha/proc/setInternalDamage(int_dam_flag) - internal_damage |= int_dam_flag - log_append_to_last("Internal damage of type [int_dam_flag].",1) - occupant << sound('sound/machines/warning-buzzer.ogg',wait=0) - diag_hud_set_mechstat() - -/obj/mecha/proc/clearInternalDamage(int_dam_flag) - internal_damage &= ~int_dam_flag - switch(int_dam_flag) - if(MECHA_INT_TEMP_CONTROL) - occupant_message("Life support system reactivated.") - if(MECHA_INT_FIRE) - occupant_message("Internal fire extinquished.") - if(MECHA_INT_TANK_BREACH) - occupant_message("Damaged internal tank has been sealed.") - diag_hud_set_mechstat() - - -//////////////////////////////////////// -//////// Health related procs //////// -//////////////////////////////////////// - -/obj/mecha/proc/get_armour_facing(relative_dir) - switch(relative_dir) - if(0) // BACKSTAB! - return facing_modifiers[MECHA_BACK_ARMOUR] - if(45, 90, 270, 315) - return facing_modifiers[MECHA_SIDE_ARMOUR] - if(225, 180, 135) - return facing_modifiers[MECHA_FRONT_ARMOUR] - return 1 //always return non-0 - -/obj/mecha/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(. && obj_integrity > 0) - spark_system.start() - switch(damage_flag) - if("fire") - check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL)) - if("melee") - check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST)) - else - check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT)) - if(. >= 5 || prob(33)) - occupant_message("Taking damage!") - log_message("Took [damage_amount] points of damage. Damage type: [damage_type]") - -/obj/mecha/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - . = ..() - if(!damage_amount) - return 0 - var/booster_deflection_modifier = 1 - var/booster_damage_modifier = 1 - if(damage_flag == "bullet" || damage_flag == "laser" || damage_flag == "energy") - for(var/obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster/B in equipment) - if(B.projectile_react()) - booster_deflection_modifier = B.deflect_coeff - booster_damage_modifier = B.damage_coeff - break - else if(damage_flag == "melee") - for(var/obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster/B in equipment) - if(B.attack_react()) - booster_deflection_modifier *= B.deflect_coeff - booster_damage_modifier *= B.damage_coeff - break - - if(attack_dir) - var/facing_modifier = get_armour_facing(dir2angle(attack_dir) - dir2angle(src)) - booster_damage_modifier /= facing_modifier - booster_deflection_modifier *= facing_modifier - if(prob(deflect_chance * booster_deflection_modifier)) - visible_message("[src]'s armour deflects the attack!") - log_message("Armor saved.") - return 0 - if(.) - . *= booster_damage_modifier - -/obj/mecha/attack_hand(mob/living/user) - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) - playsound(loc, 'sound/weapons/tap.ogg', 40, 1, -1) - user.visible_message("[user] hits [name]. Nothing happens", "You hit [name] with no visible effect.") - log_message("Attack by hand/paw. Attacker - [user].") - - -/obj/mecha/attack_alien(mob/living/user) - log_message("Attack by alien. Attacker - [user].", color = "red") - playsound(src.loc, 'sound/weapons/slash.ogg', 100, TRUE) - attack_generic(user, 15, BRUTE, "melee", 0) - -/obj/mecha/attack_animal(mob/living/simple_animal/user) - log_message("Attack by simple animal. Attacker - [user].") - if(!user.melee_damage_upper && !user.obj_damage) - user.custom_emote(1, "[user.friendly] [src].") - return FALSE - else - var/play_soundeffect = 1 - if(user.environment_smash) - play_soundeffect = 0 - playsound(src, 'sound/effects/bang.ogg', 50, TRUE) - var/animal_damage = rand(user.melee_damage_lower,user.melee_damage_upper) - if(user.obj_damage) - animal_damage = user.obj_damage - animal_damage = min(animal_damage, 20*user.environment_smash) - user.create_attack_log("attacked [name]") - attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect) - return TRUE - -/obj/mecha/hulk_damage() - return 15 - -/obj/mecha/attack_hulk(mob/living/carbon/human/user) - . = ..() - if(.) - log_message("Attack by hulk. Attacker - [user].", 1) - add_attack_logs(user, src, "Punched with hulk powers") - -/obj/mecha/blob_act(obj/structure/blob/B) - log_message("Attack by blob. Attacker - [B].") - take_damage(30, BRUTE, "melee", 0, get_dir(src, B)) - -/obj/mecha/attack_tk() - return - -/obj/mecha/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) //wrapper - log_message("Hit by [AM].") - . = ..() - -/obj/mecha/bullet_act(obj/item/projectile/Proj) //wrapper - log_message("Hit by projectile. Type: [Proj.name]([Proj.flag]).") - ..() - -/obj/mecha/ex_act(severity, target) - log_message("Affected by explosion of severity: [severity].") - if(prob(deflect_chance)) - severity++ - log_message("Armor saved, changing severity to [severity]") - ..() - severity++ - for(var/X in equipment) - var/obj/item/mecha_parts/mecha_equipment/ME = X - ME.ex_act(severity) - for(var/Y in trackers) - var/obj/item/mecha_parts/mecha_tracking/MT = Y - MT.ex_act(severity) - if(occupant) - occupant.ex_act(severity) - -/obj/mecha/handle_atom_del(atom/A) - if(A == occupant) - occupant = null - icon_state = initial(icon_state)+"-open" - setDir(dir_in) - -/obj/mecha/Destroy() - if(occupant) - occupant.SetSleeping(destruction_sleep_duration) - go_out() - var/mob/living/silicon/ai/AI - for(var/mob/M in src) //Let's just be ultra sure - if(isAI(M)) - occupant = null - AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck. - else - M.forceMove(loc) - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - E.detach(loc) - qdel(E) - equipment.Cut() - QDEL_NULL(cell) - QDEL_NULL(internal_tank) - if(AI) - AI.gib() //No wreck, no AI to recover - STOP_PROCESSING(SSobj, src) - GLOB.poi_list.Remove(src) - if(loc) - loc.assume_air(cabin_air) - air_update_turf() - else - qdel(cabin_air) - cabin_air = null - QDEL_NULL(spark_system) - QDEL_NULL(smoke_system) - - GLOB.mechas_list -= src //global mech list - return ..() - -//TODO -/obj/mecha/emp_act(severity) - if(get_charge()) - use_power((cell.charge/3)/(severity*2)) - take_damage(30 / severity, BURN, "energy", 1) - log_message("EMP detected", 1) - check_for_internal_damage(list(MECHA_INT_FIRE, MECHA_INT_TEMP_CONTROL, MECHA_INT_CONTROL_LOST, MECHA_INT_SHORT_CIRCUIT), 1) - -/obj/mecha/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > max_temperature) - log_message("Exposed to dangerous temperature.", 1) - take_damage(5, BURN, 0, 1) - check_for_internal_damage(list(MECHA_INT_FIRE, MECHA_INT_TEMP_CONTROL)) - -////////////////////// -////// AttackBy ////// -////////////////////// - -/obj/mecha/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/mmi)) - if(mmi_move_inside(W,user)) - to_chat(user, "[src]-MMI interface initialized successfuly") - else - to_chat(user, "[src]-MMI interface initialization failed.") - return - - if(istype(W, /obj/item/mecha_parts/mecha_equipment)) - var/obj/item/mecha_parts/mecha_equipment/E = W - spawn() - if(E.can_attach(src)) - if(!user.drop_item()) - return - E.attach(src) - user.visible_message("[user] attaches [W] to [src].", "You attach [W] to [src].") - else - to_chat(user, "You were unable to attach [W] to [src]!") - return - - if(W.GetID()) - if(add_req_access || maint_access) - if(internals_access_allowed(usr)) - var/obj/item/card/id/id_card - if(istype(W, /obj/item/card/id)) - id_card = W - else - var/obj/item/pda/pda = W - id_card = pda.id - output_maintenance_dialog(id_card, user) - return - else - to_chat(user, "Invalid ID: Access denied.") - else - to_chat(user, "Maintenance protocols disabled by operator.") - - else if(istype(W, /obj/item/stack/cable_coil)) - if(state == 3 && hasInternalDamage(MECHA_INT_SHORT_CIRCUIT)) - var/obj/item/stack/cable_coil/CC = W - if(CC.amount > 1) - CC.use(2) - clearInternalDamage(MECHA_INT_SHORT_CIRCUIT) - to_chat(user, "You replace the fused wires.") - else - to_chat(user, "There's not enough wire to finish the task.") - return - - else if(istype(W, /obj/item/stock_parts/cell)) - if(state==4) - if(!cell) - if(!user.drop_item()) - return - to_chat(user, "You install the powercell.") - W.forceMove(src) - cell = W - log_message("Powercell installed") - else - to_chat(user, "There's already a powercell installed.") - return - - else if(istype(W, /obj/item/mecha_parts/mecha_tracking)) - if(!user.unEquip(W)) - to_chat(user, "\the [W] is stuck to your hand, you cannot put it in \the [src]") - return - W.forceMove(src) - trackers += W - user.visible_message("[user] attaches [W] to [src].", "You attach [W] to [src].") - diag_hud_set_mechtracking() - return - - else if(istype(W, /obj/item/paintkit)) - if(occupant) - to_chat(user, "You can't customize a mech while someone is piloting it - that would be unsafe!") - return - - var/obj/item/paintkit/P = W - var/found = null - - for(var/type in P.allowed_types) - if(type == initial_icon) - found = 1 - break - - if(!found) - to_chat(user, "That kit isn't meant for use on this class of exosuit.") - return - - user.visible_message("[user] opens [P] and spends some quality time customising [src].") - - name = P.new_name - desc = P.new_desc - initial_icon = P.new_icon - reset_icon() - - user.drop_item() - qdel(P) - - else if(istype(W, /obj/item/mecha_modkit)) - if(occupant) - to_chat(user, "You can't access the mech's modification port while it is occupied.") - return - var/obj/item/mecha_modkit/M = W - if(do_after_once(user, M.install_time, target = src)) - M.install(src, user) - else - to_chat(user, "You stop installing [M].") - - else - return ..() - - -/obj/mecha/crowbar_act(mob/user, obj/item/I) - if(state != 2 && state != 3 && !(state == 4 && pilot_is_mmi())) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(state == 2) - state = 3 - to_chat(user, "You open the hatch to the power unit") - else if(state == 3) - state = 2 - to_chat(user, "You close the hatch to the power unit") - else - // Since having maint protocols available is controllable by the MMI, I see this as a consensual way to remove an MMI without destroying the mech - user.visible_message("[user] begins levering out the MMI from the [src].", "You begin to lever out the MMI from the [src].") - to_chat(occupant, "[user] is prying you out of the exosuit!") - if(I.use_tool(src, user, 80, volume = I.tool_volume) && pilot_is_mmi()) - user.visible_message("[user] pries the MMI out of the [src]!", "You finish removing the MMI from the [src]!") - go_out() - -/obj/mecha/screwdriver_act(mob/user, obj/item/I) - if(user.a_intent == INTENT_HARM) - return - if(!(state==3 && cell) && !(state==4 && cell)) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(hasInternalDamage(MECHA_INT_TEMP_CONTROL)) - clearInternalDamage(MECHA_INT_TEMP_CONTROL) - to_chat(user, "You repair the damaged temperature controller.") - else if(state==3 && cell) - cell.forceMove(loc) - cell = null - state = 4 - to_chat(user, "You unscrew and pry out the powercell.") - log_message("Powercell removed") - else if(state==4 && cell) - state=3 - to_chat(user, "You screw the cell in place.") - -/obj/mecha/wrench_act(mob/user, obj/item/I) - if(state != 1 && state != 2) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(state==1) - state = 2 - to_chat(user, "You undo the securing bolts.") - else - state = 1 - to_chat(user, "You tighten the securing bolts.") - -/obj/mecha/welder_act(mob/user, obj/item/I) - if(user.a_intent == INTENT_HARM) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if((obj_integrity >= max_integrity) && !internal_damage) - to_chat(user, "[src] is at full integrity!") - return - WELDER_ATTEMPT_REPAIR_MESSAGE - if(I.use_tool(src, user, 15, volume = I.tool_volume)) - if(internal_damage & MECHA_INT_TANK_BREACH) - clearInternalDamage(MECHA_INT_TANK_BREACH) - user.visible_message("[user] repairs the damaged gas tank.", "You repair the damaged gas tank.") - else if(obj_integrity < max_integrity) - user.visible_message("[user] repairs some damage to [name].", "You repair some damage to [name].") - obj_integrity += min(10, max_integrity - obj_integrity) - else - to_chat(user, "[src] is at full integrity!") - -/obj/mecha/mech_melee_attack(obj/mecha/M) - if(!has_charge(melee_energy_drain)) - return 0 - use_power(melee_energy_drain) - if(M.damtype == BRUTE || M.damtype == BURN) - add_attack_logs(M.occupant, src, "Mecha-attacked with [M] (INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])") - . = ..() - -/obj/mecha/emag_act(mob/user) - to_chat(user, "[src]'s ID slot rejects the card.") - return - - -///////////////////////////////////// -//////////// AI piloting //////////// -///////////////////////////////////// - -/obj/mecha/attack_ai(mob/living/silicon/ai/user) - if(!isAI(user)) - return - //Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot. - if(user.can_dominate_mechs) - examine(user) //Get diagnostic information! - for(var/obj/item/mecha_parts/mecha_tracking/B in trackers) - to_chat(user, "Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:") - to_chat(user, "[B.get_mecha_info_text()]") - break - //Nothing like a big, red link to make the player feel powerful! - to_chat(user, "ASSUME DIRECT CONTROL?
        ") - else - examine(user) - if(occupant) - user << "This exosuit has a pilot and cannot be controlled." - return - var/can_control_mech = FALSE - for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers) - can_control_mech = TRUE - to_chat(user, "[bicon(src)] Status of [name]:\n\ - [A.get_mecha_info_text()]") - break - if(!can_control_mech) - to_chat(user, "You cannot control exosuits without AI control beacons installed.") - return - to_chat(user, "Take control of exosuit?
        ") - -/obj/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(!..()) - return - - //Transfer from core or card to mech. Proc is called by mech. - switch(interaction) - if(AI_TRANS_TO_CARD) //Upload AI from mech to AI card. - if(!maint_access) //Mech must be in maint mode to allow carding. - to_chat(user, "[name] must have maintenance protocols active in order to allow a transfer.") - return - AI = occupant - if(!AI || !isAI(occupant)) //Mech does not have an AI for a pilot - to_chat(user, "No AI detected in the [name] onboard computer.") - return - if(AI.mind.special_role) //Malf AIs cannot leave mechs. Except through death. - to_chat(user, "ACCESS DENIED.") - return - AI.aiRestorePowerRoutine = 0//So the AI initially has power. - AI.control_disabled = 1 - AI.aiRadio.disabledAi = 1 - AI.loc = card - occupant = null - AI.controlled_mech = null - AI.remote_control = null - icon_state = initial(icon_state)+"-open" - to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") - - if(AI_MECH_HACK) //Called by AIs on the mech - AI.linked_core = new /obj/structure/AIcore/deactivated(AI.loc) - if(AI.can_dominate_mechs) - if(occupant) //Oh, I am sorry, were you using that? - to_chat(AI, "Pilot detected! Forced ejection initiated!") - to_chat(occupant, "You have been forcibly ejected!") - go_out(1) //IT IS MINE, NOW. SUCK IT, RD! - ai_enter_mech(AI, interaction) - - if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech. - AI = locate(/mob/living/silicon/ai) in card - if(!AI) - to_chat(user, "There is no AI currently installed on this device.") - return - else if(AI.stat || !AI.client) - to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.") - return - else if(occupant || dna) //Normal AIs cannot steal mechs! - to_chat(user, "Access denied. [name] is [occupant ? "currently occupied" : "secured with a DNA lock"].") - return - AI.control_disabled = 0 - AI.aiRadio.disabledAi = 0 - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - ai_enter_mech(AI, interaction) - -//Hack and From Card interactions share some code, so leave that here for both to use. -/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction) - AI.aiRestorePowerRoutine = 0 - AI.loc = src - occupant = AI - icon_state = initial(icon_state) - playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) - if(!hasInternalDamage()) - occupant << sound(nominalsound, volume = 50) - AI.cancel_camera() - AI.controlled_mech = src - AI.remote_control = src - AI.canmove = 1 //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. - AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES. - to_chat(AI, "[AI.can_dominate_mechs ? "Takeover of [name] complete! You are now permanently loaded onto the onboard computer. Do not attempt to leave the station sector!" \ - : "You have been uploaded to a mech's onboard computer."]") - to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.") - if(interaction == AI_TRANS_FROM_CARD) - GrantActions(AI, FALSE) - else - GrantActions(AI, !AI.can_dominate_mechs) - -///////////////////////////////////// -//////// Atmospheric stuff //////// -///////////////////////////////////// - -/obj/mecha/proc/get_turf_air() - var/turf/T = get_turf(src) - if(T) - . = T.return_air() - -/obj/mecha/remove_air(amount) - if(use_internal_tank) - return cabin_air.remove(amount) - else - var/turf/T = get_turf(src) - if(T) - return T.remove_air(amount) - -/obj/mecha/return_air() - if(use_internal_tank) - return cabin_air - return get_turf_air() - -/obj/mecha/proc/return_pressure() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_pressure() - -//skytodo: //No idea what you want me to do here, mate. -/obj/mecha/proc/return_temperature() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_temperature() - -/obj/mecha/proc/connect(obj/machinery/atmospherics/unary/portables_connector/new_port) - //Make sure not already connected to something else - if(connected_port || !istype(new_port) || new_port.connected_device) - return 0 - - //Make sure are close enough for a valid connection - if(new_port.loc != loc) - return 0 - - //Perform the connection - connected_port = new_port - connected_port.connected_device = src - connected_port.parent.reconcile_air() - - if(occupant) - occupant.clear_alert("mechaport") - occupant.throw_alert("mechaport_d", /obj/screen/alert/mech_port_disconnect) - - log_message("Connected to gas port.") - return 1 - -/obj/mecha/proc/disconnect() - if(!connected_port) - return 0 - - connected_port.connected_device = null - connected_port = null - log_message("Disconnected from gas port.") - if(occupant) - occupant.clear_alert("mechaport_d") - return 1 - -/obj/mecha/portableConnectorReturnAir() - return internal_tank.return_air() - -/obj/mecha/proc/toggle_lights() - lights = !lights - if(lights) - set_light(light_range + lights_power) - else - set_light(light_range - lights_power) - occupant_message("Toggled lights [lights ? "on" : "off"].") - log_message("Toggled lights [lights ? "on" : "off"].") - -/obj/mecha/proc/toggle_internal_tank() - use_internal_tank = !use_internal_tank - occupant_message("Now taking air from [use_internal_tank ? "internal airtank" : "environment"].") - log_message("Now taking air from [use_internal_tank ? "internal airtank" : "environment"].") - -/obj/mecha/MouseDrop_T(mob/M, mob/user) - if(user.incapacitated()) - return - if(user != M) - return - log_message("[user] tries to move in.") - if(occupant) - to_chat(user, "The [src] is already occupied!") - log_append_to_last("Permission denied.") - return - var/passed - if(dna) - if(ishuman(user)) - if(user.dna.unique_enzymes == dna) - passed = 1 - else if(operation_allowed(user)) - passed = 1 - if(!passed) - to_chat(user, "Access denied.") - log_append_to_last("Permission denied.") - return - if(user.buckled) - to_chat(user, "You are currently buckled and cannot move.") - log_append_to_last("Permission denied.") - return - if(user.has_buckled_mobs()) //mob attached to us - to_chat(user, "You can't enter the exosuit with other creatures attached to you!") - return - - visible_message("[user] starts to climb into [src]") - - if(do_after(user, 40, target = src)) - if(obj_integrity <= 0) - to_chat(user, "You cannot get in the [name], it has been destroyed!") - else if(occupant) - to_chat(user, "[occupant] was faster! Try better next time, loser.") - else if(user.buckled) - to_chat(user, "You can't enter the exosuit while buckled.") - else if(user.has_buckled_mobs()) - to_chat(user, "You can't enter the exosuit with other creatures attached to you!") - else - moved_inside(user) - else - to_chat(user, "You stop entering the exosuit!") - -/obj/mecha/proc/moved_inside(var/mob/living/carbon/human/H as mob) - if(H && H.client && H in range(1)) - occupant = H - H.stop_pulling() - H.forceMove(src) - H.reset_perspective(src) - add_fingerprint(H) - GrantActions(H, human_occupant = 1) - forceMove(loc) - log_append_to_last("[H] moved in as pilot.") - icon_state = reset_icon() - dir = dir_in - playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) - if(!activated) - occupant << sound(longactivationsound, volume = 50) - activated = TRUE - else if(!hasInternalDamage()) - occupant << sound(nominalsound, volume = 50) - if(state) - H.throw_alert("locked", /obj/screen/alert/mech_maintenance) - return 1 - else - return 0 - -/obj/mecha/proc/mmi_move_inside(var/obj/item/mmi/mmi_as_oc as obj,mob/user as mob) - if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) - to_chat(user, "Consciousness matrix not detected!") - return 0 - else if(mmi_as_oc.brainmob.stat) - to_chat(user, "Beta-rhythm below acceptable level!") - return 0 - else if(occupant) - to_chat(user, "Occupant detected!") - return 0 - else if(dna && dna != mmi_as_oc.brainmob.dna.unique_enzymes) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - return 0 - - if(do_after(user, 40, target = src)) - if(!occupant) - return mmi_moved_inside(mmi_as_oc,user) - else - to_chat(user, "Occupant detected!") - else - to_chat(user, "You stop inserting the MMI.") - return 0 - -/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/mmi_as_oc,mob/user) - if(mmi_as_oc && user in range(1)) - if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) - to_chat(user, "Consciousness matrix not detected.") - return 0 - else if(mmi_as_oc.brainmob.stat) - to_chat(user, "Beta-rhythm below acceptable level.") - return 0 - if(!user.unEquip(mmi_as_oc)) - to_chat(user, "\the [mmi_as_oc] is stuck to your hand, you cannot put it in \the [src]") - return 0 - var/mob/brainmob = mmi_as_oc.brainmob - brainmob.reset_perspective(src) - occupant = brainmob - brainmob.forceMove(src) //should allow relaymove - brainmob.canmove = 1 - if(istype(mmi_as_oc, /obj/item/mmi/robotic_brain)) - var/obj/item/mmi/robotic_brain/R = mmi_as_oc - if(R.imprinted_master) - to_chat(brainmob, "Your imprint to [R.imprinted_master] has been temporarily disabled. You should help the crew and not commit harm.") - mmi_as_oc.loc = src - mmi_as_oc.mecha = src - Entered(mmi_as_oc) - Move(loc) - icon_state = reset_icon() - dir = dir_in - log_message("[mmi_as_oc] moved in as pilot.") - if(!hasInternalDamage()) - to_chat(occupant, sound(nominalsound, volume=50)) - GrantActions(brainmob) - return 1 - else - return 0 - -/obj/mecha/proc/pilot_is_mmi() - var/atom/movable/mob_container - if(istype(occupant, /mob/living/carbon/brain)) - var/mob/living/carbon/brain/brain = occupant - mob_container = brain.container - if(istype(mob_container, /obj/item/mmi)) - return 1 - return 0 - -/obj/mecha/proc/pilot_mmi_hud(var/mob/living/carbon/brain/pilot) - return - -/obj/mecha/Exited(atom/movable/M, atom/newloc) - if(occupant && occupant == M) // The occupant exited the mech without calling go_out() - go_out(1, newloc) - -/obj/mecha/proc/go_out(forced, atom/newloc = loc) - if(!occupant) - return - var/atom/movable/mob_container - occupant.clear_alert("charge") - occupant.clear_alert("locked") - occupant.clear_alert("mech damage") - occupant.clear_alert("mechaport") - occupant.clear_alert("mechaport_d") - if(ishuman(occupant)) - mob_container = occupant - RemoveActions(occupant, human_occupant = 1) - else if(isbrain(occupant)) - var/mob/living/carbon/brain/brain = occupant - RemoveActions(brain) - mob_container = brain.container - else if(isAI(occupant)) - var/mob/living/silicon/ai/AI = occupant - if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. - RemoveActions(occupant) - occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. - occupant = null - return - else - if(!AI.linked_core || QDELETED(AI.linked_core)) - to_chat(AI, "Inactive core destroyed. Unable to return.") - AI.linked_core = null - return - to_chat(AI, "Returning to core...") - AI.controlled_mech = null - AI.remote_control = null - RemoveActions(occupant, 1) - mob_container = AI - newloc = get_turf(AI.linked_core) - qdel(AI.linked_core) - else - return - var/mob/living/L = occupant - occupant = null //we need it null when forceMove calls Exited(). - if(mob_container.forceMove(newloc))//ejecting mob container - log_message("[mob_container] moved out.") - L << browse(null, "window=exosuit") - - if(istype(mob_container, /obj/item/mmi)) - var/obj/item/mmi/mmi = mob_container - if(mmi.brainmob) - L.loc = mmi - L.reset_perspective() - mmi.mecha = null - mmi.update_icon() - L.canmove = 0 - if(istype(mmi, /obj/item/mmi/robotic_brain)) - var/obj/item/mmi/robotic_brain/R = mmi - if(R.imprinted_master) - to_chat(L, "Imprint re-enabled, you are once again bound to [R.imprinted_master]'s commands.") - icon_state = initial(icon_state)+"-open" - dir = dir_in - - if(L && L.client) - L.client.RemoveViewMod("mecha") - zoom_mode = FALSE - -///////////////////////// -////// Access stuff ///// -///////////////////////// - -/obj/mecha/proc/operation_allowed(mob/living/carbon/human/H) - if(!ishuman(H)) - return 0 - for(var/ID in list(H.get_active_hand(), H.wear_id, H.belt)) - if(check_access(ID, operation_req_access)) - return 1 - return 0 - - -/obj/mecha/proc/internals_access_allowed(mob/living/carbon/human/H) - for(var/atom/ID in list(H.get_active_hand(), H.wear_id, H.belt)) - if(check_access(ID, internals_req_access)) - return 1 - return 0 - - -/obj/mecha/check_access(obj/item/card/id/I, list/access_list) - if(!istype(access_list)) - return 1 - if(!access_list.len) //no requirements - return 1 - if(istype(I, /obj/item/pda)) - var/obj/item/pda/pda = I - I = pda.id - if(!istype(I) || !I.access) //not ID or no access - return 0 - if(access_list==operation_req_access) - for(var/req in access_list) - if(!(req in I.access)) //doesn't have this access - return 0 - else if(access_list==internals_req_access) - for(var/req in access_list) - if(req in I.access) - return 1 - return 1 - -/////////////////////// -///// Power stuff ///// -/////////////////////// - -/obj/mecha/proc/has_charge(amount) - return (get_charge()>=amount) - -/obj/mecha/proc/get_charge() - for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment) - var/relay_charge = R.get_charge() - if(relay_charge) - return relay_charge - if(cell) - return max(0, cell.charge) - -/obj/mecha/proc/use_power(amount) - if(get_charge()) - cell.use(amount) - if(occupant) - update_cell() - return 1 - return 0 - -/obj/mecha/proc/give_power(amount) - if(!isnull(get_charge())) - cell.give(amount) - if(occupant) - update_cell() - return 1 - return 0 - -/obj/mecha/proc/update_cell() - if(cell) - var/cellcharge = cell.charge/cell.maxcharge - switch(cellcharge) - if(0.75 to INFINITY) - occupant.clear_alert("charge") - if(0.5 to 0.75) - occupant.throw_alert("charge", /obj/screen/alert/mech_lowcell, 1) - if(0.25 to 0.5) - occupant.throw_alert("charge", /obj/screen/alert/mech_lowcell, 2) - if(power_warned) - power_warned = FALSE - if(0.01 to 0.25) - occupant.throw_alert("charge", /obj/screen/alert/mech_lowcell, 3) - if(!power_warned) - occupant << sound(lowpowersound, volume = 50) - power_warned = TRUE - else - occupant.throw_alert("charge", /obj/screen/alert/mech_emptycell) - else - occupant.throw_alert("charge", /obj/screen/alert/mech_nocell) - -/obj/mecha/proc/reset_icon() - if(initial_icon) - icon_state = initial_icon - else - icon_state = initial(icon_state) - return icon_state - -////////////////////////////////////////// -//////// Mecha global iterators //////// -////////////////////////////////////////// - -/obj/mecha/process() - process_internal_damage() - regulate_temp() - give_air() - update_huds() - -/obj/mecha/proc/process_internal_damage() - if(!internal_damage) - return - - if(internal_damage & MECHA_INT_FIRE) - if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5)) - clearInternalDamage(MECHA_INT_FIRE) - if(internal_tank) - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH)) - setInternalDamage(MECHA_INT_TANK_BREACH) - - if(int_tank_air && int_tank_air.return_volume() > 0) - int_tank_air.temperature = min(6000 + T0C, cabin_air.return_temperature() + rand(10, 15)) - - if(cabin_air && cabin_air.return_volume()>0) - cabin_air.temperature = min(6000+T0C, cabin_air.return_temperature()+rand(10,15)) - if(cabin_air.return_temperature() > max_temperature/2) - take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0) - - if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank - if(internal_tank) - var/datum/gas_mixture/int_tank_air = internal_tank.return_air() - var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.10) - if(loc) - loc.assume_air(leaked_gas) - air_update_turf() - else - qdel(leaked_gas) - - if(internal_damage & MECHA_INT_SHORT_CIRCUIT) - if(get_charge()) - spark_system.start() - cell.charge -= min(20,cell.charge) - cell.maxcharge -= min(20,cell.maxcharge) - -/obj/mecha/proc/regulate_temp() - if(internal_damage & MECHA_INT_TEMP_CONTROL) - return - - if(cabin_air && cabin_air.return_volume() > 0) - var/delta = cabin_air.temperature - T20C - cabin_air.temperature -= max(-10, min(10, round(delta / 4, 0.1))) - -/obj/mecha/proc/give_air() - if(!internal_tank) - return - - var/datum/gas_mixture/tank_air = internal_tank.return_air() - - var/release_pressure = internal_tank_valve - var/cabin_pressure = cabin_air.return_pressure() - var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) - var/transfer_moles = 0 - if(pressure_delta > 0) //cabin pressure lower than release pressure - if(tank_air.return_temperature() > 0) - transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) - var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) - cabin_air.merge(removed) - else if(pressure_delta < 0) //cabin pressure higher than release pressure - var/datum/gas_mixture/t_air = return_air() - pressure_delta = cabin_pressure - release_pressure - if(t_air) - pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) - if(pressure_delta > 0) //if location pressure is lower than cabin pressure - transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) - var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) - if(t_air) - t_air.merge(removed) - else //just delete the cabin gas, we're in space or some shit - qdel(removed) - -/obj/mecha/proc/update_huds() - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - diag_hud_set_mechtracking() - - -/obj/mecha/speech_bubble(var/bubble_state = "",var/bubble_loc = src, var/list/bubble_recipients = list()) - flick_overlay(image('icons/mob/talk.dmi', bubble_loc, bubble_state,MOB_LAYER+1), bubble_recipients, 30) - -/obj/mecha/update_remote_sight(mob/living/user) - if(occupant_sight_flags) - if(user == occupant) - user.sight |= occupant_sight_flags - - ..() - -/obj/mecha/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y) - if(!no_effect) - if(selected) - used_item = selected - else if(!visual_effect_icon) - visual_effect_icon = ATTACK_EFFECT_SMASH - if(damtype == BURN) - visual_effect_icon = ATTACK_EFFECT_MECHFIRE - else if(damtype == TOX) - visual_effect_icon = ATTACK_EFFECT_MECHTOXIN - ..() - -/obj/mecha/obj_destruction() - if(wreckage) - var/mob/living/silicon/ai/AI - if(isAI(occupant)) - AI = occupant - occupant = null - var/obj/structure/mecha_wreckage/WR = new wreckage(loc, AI) - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - if(E.salvageable && prob(30)) - WR.crowbar_salvage += E - E.detach(WR) //detaches from src into WR - E.equip_ready = 1 - else - E.detach(loc) - qdel(E) - if(cell) - WR.crowbar_salvage += cell - cell.forceMove(WR) - cell.charge = rand(0, cell.charge) - cell = null - if(internal_tank) - WR.crowbar_salvage += internal_tank - internal_tank.forceMove(WR) - cell = null - . = ..() - -/obj/mecha/CtrlClick(mob/living/L) - if(occupant != L || !istype(L)) - return ..() - - var/list/choices = list("Cancel / No Change" = mutable_appearance(icon = 'icons/mob/screen_gen.dmi', icon_state = "x")) - var/list/choices_to_refs = list() - - for(var/obj/item/mecha_parts/mecha_equipment/MT in equipment) - if(!MT.selectable || selected == MT) - continue - var/mutable_appearance/clean/MA = new(MT) - choices[MT.name] = MA - choices_to_refs[MT.name] = MT - - var/choice = show_radial_menu(L, src, choices, radius = 48, custom_check = CALLBACK(src, .proc/check_menu, L)) - if(!check_menu(L) || choice == "Cancel / No Change") - return - - var/obj/item/mecha_parts/mecha_equipment/new_sel = LAZYACCESS(choices_to_refs, choice) - if(istype(new_sel)) - selected = new_sel - occupant_message("You switch to [selected].") - visible_message("[src] raises [selected]") - send_byjax(occupant, "exosuit.browser", "eq_list", get_equipment_list()) - -/obj/mecha/proc/check_menu(mob/living/L) - if(L != occupant || !istype(L)) - return FALSE - if(L.incapacitated()) - return FALSE - return TRUE \ No newline at end of file +/obj/mecha + name = "Mecha" + desc = "Exosuit" + icon = 'icons/mecha/mecha.dmi' + density = 1 //Dense. To raise the heat. + opacity = 1 ///opaque. Menacing. + anchored = 1 //no pulling around. + resistance_flags = FIRE_PROOF | ACID_PROOF + layer = MOB_LAYER //icon draw layer + infra_luminosity = 15 //byond implementation is bugged. + force = 5 + max_integrity = 300 //max_integrity is base health + armor = list(melee = 20, bullet = 10, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 100) + var/list/facing_modifiers = list(MECHA_FRONT_ARMOUR = 1.5, MECHA_SIDE_ARMOUR = 1, MECHA_BACK_ARMOUR = 0.5) + var/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming. + var/initial_icon = null //Mech type for resetting icon. Only used for reskinning kits (see custom items) + var/can_move = 0 // time of next allowed movement + var/mob/living/carbon/occupant = null + var/step_in = 10 //make a step in step_in/10 sec. + var/dir_in = 2//What direction will the mech face when entered/powered on? Defaults to South. + var/normal_step_energy_drain = 10 + var/step_energy_drain = 10 + var/melee_energy_drain = 15 + var/overload_step_energy_drain_min = 100 + var/deflect_chance = 10 //chance to deflect the incoming projectiles, hits, or lesser the effect of ex_act. + var/obj/item/stock_parts/cell/cell + var/state = 0 + var/list/log = new + var/last_message = 0 + var/add_req_access = 1 + var/maint_access = 1 + var/dna //dna-locking the mech + var/list/proc_res = list() //stores proc owners, like proc_res["functionname"] = owner reference + var/datum/effect_system/spark_spread/spark_system = new + var/lights = 0 + var/lights_power = 6 + var/emagged = FALSE + + //inner atmos + var/use_internal_tank = 0 + var/internal_tank_valve = ONE_ATMOSPHERE + var/obj/machinery/portable_atmospherics/canister/internal_tank + var/datum/gas_mixture/cabin_air + var/obj/machinery/atmospherics/unary/portables_connector/connected_port = null + + var/obj/item/radio/radio = null + var/list/trackers = list() + + var/max_temperature = 25000 + var/internal_damage_threshold = 50 //health percentage below which internal damage is possible + var/internal_damage = 0 //contains bitflags + + var/list/operation_req_access = list()//required access level for mecha operation + var/list/internals_req_access = list(ACCESS_ENGINE,ACCESS_ROBOTICS)//required access level to open cell compartment + + var/wreckage + + var/list/equipment = new + var/obj/item/mecha_parts/mecha_equipment/selected + var/max_equip = 3 + var/datum/events/events + var/turf/crashing = null + var/occupant_sight_flags = 0 + + var/stepsound = 'sound/mecha/mechstep.ogg' + var/turnsound = 'sound/mecha/mechturn.ogg' + var/nominalsound = 'sound/mecha/nominal.ogg' + var/zoomsound = 'sound/mecha/imag_enh.ogg' + var/critdestrsound = 'sound/mecha/critdestr.ogg' + var/weapdestrsound = 'sound/mecha/weapdestr.ogg' + var/lowpowersound = 'sound/mecha/lowpower.ogg' + var/longactivationsound = 'sound/mecha/nominal.ogg' + var/starting_voice = /obj/item/mecha_modkit/voice + var/activated = FALSE + var/power_warned = FALSE + + var/destruction_sleep_duration = 1 //Time that mech pilot is put to sleep for if mech is destroyed + + var/melee_cooldown = 10 + var/melee_can_hit = 1 + + // Action vars + var/defence_mode = FALSE + var/defence_mode_deflect_chance = 35 + var/leg_overload_mode = FALSE + var/leg_overload_coeff = 100 + var/thrusters_active = FALSE + var/smoke = 5 + var/smoke_ready = 1 + var/smoke_cooldown = 100 + var/zoom_mode = FALSE + var/phasing = FALSE + var/phasing_energy_drain = 200 + var/phase_state = "" //icon_state when phasing + + hud_possible = list (DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_TRACK_HUD) + +/obj/mecha/Initialize() + . = ..() + events = new + icon_state += "-open" + add_radio() + add_cabin() + add_airtank() + spark_system.set_up(2, 0, src) + spark_system.attach(src) + smoke_system.set_up(3, src) + smoke_system.attach(src) + add_cell() + START_PROCESSING(SSobj, src) + GLOB.poi_list |= src + log_message("[src] created.") + GLOB.mechas_list += src //global mech list + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in huds) + diag_hud.add_to_hud(src) + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + diag_hud_set_mechtracking() + + var/obj/item/mecha_modkit/voice/V = new starting_voice(src) + V.install(src) + qdel(V) + +//////////////////////// +////// Helpers ///////// +//////////////////////// + +/obj/mecha/get_cell() + return cell + +/obj/mecha/proc/add_airtank() + internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) + return internal_tank + +/obj/mecha/proc/add_cell(var/obj/item/stock_parts/cell/C=null) + if(C) + C.forceMove(src) + cell = C + return + cell = new/obj/item/stock_parts/cell/high/plus(src) + +/obj/mecha/proc/add_cabin() + cabin_air = new + cabin_air.temperature = T20C + cabin_air.volume = 200 + cabin_air.oxygen = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + cabin_air.nitrogen = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + return cabin_air + +/obj/mecha/proc/add_radio() + radio = new(src) + radio.name = "[src] radio" + radio.icon = icon + radio.icon_state = icon_state + radio.subspace_transmission = 1 + +/obj/mecha/examine(mob/user) + . = ..() + var/integrity = obj_integrity * 100 / max_integrity + switch(integrity) + if(85 to 100) + . += "It's fully intact." + if(65 to 85) + . += "It's slightly damaged." + if(45 to 65) + . += "It's badly damaged." + if(25 to 45) + . += "It's heavily damaged." + else + . += "It's falling apart." + if(equipment && equipment.len) + . += "It's equipped with:" + for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment) + . += "[bicon(ME)] [ME]" + +/obj/mecha/hear_talk(mob/M, list/message_pieces) + if(M == occupant && radio.broadcasting) + radio.talk_into(M, message_pieces) + +/obj/mecha/proc/click_action(atom/target, mob/user, params) + if(!occupant || occupant != user ) + return + if(user.incapacitated()) + return + if(phasing) + occupant_message("Unable to interact with objects while phasing.") + return + if(state) + occupant_message("Maintenance protocols in effect.") + return + if(!get_charge()) + return + if(src == target) + return + + var/dir_to_target = get_dir(src, target) + if(dir_to_target && !(dir_to_target & dir))//wrong direction + return + + if(hasInternalDamage(MECHA_INT_CONTROL_LOST)) + target = safepick(view(3,target)) + if(!target) + return + + var/mob/living/L = user + if(!target.Adjacent(src)) + if(selected && selected.is_ranged()) + if(HAS_TRAIT(L, TRAIT_PACIFISM) && selected.harmful) + to_chat(L, "You don't want to harm other living beings!") + return + selected.action(target, params) + else if(selected && selected.is_melee()) + if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm other living beings!") + return + selected.action(target, params) + else + if(internal_damage & MECHA_INT_CONTROL_LOST) + target = safepick(oview(1, src)) + if(!melee_can_hit || !isatom(target)) + return + target.mech_melee_attack(src) + melee_can_hit = 0 + spawn(melee_cooldown) + melee_can_hit = 1 + +/obj/mecha/proc/mech_toxin_damage(mob/living/target) + playsound(src, 'sound/effects/spray2.ogg', 50, 1) + if(target.reagents) + if(target.reagents.get_reagent_amount("atropine") + force < force*2) + target.reagents.add_reagent("atropine", force/2) + if(target.reagents.get_reagent_amount("toxin") + force < force*2) + target.reagents.add_reagent("toxin", force/2.5) + +/obj/mecha/proc/range_action(atom/target) + return + + +////////////////////////////////// +//////// Movement procs //////// +////////////////////////////////// + +/obj/mecha/Move(atom/newLoc, direct) + . = ..() + if(.) + events.fireEvent("onMove",get_turf(src)) + +/obj/mecha/Process_Spacemove(var/movement_dir = 0) + . = ..() + if(.) + return 1 + if(thrusters_active && movement_dir && use_power(step_energy_drain)) + return 1 + + var/atom/movable/backup = get_spacemove_backup() + if(backup) + if(istype(backup) && movement_dir && !backup.anchored) + if(backup.newtonian_move(turn(movement_dir, 180))) + if(occupant) + to_chat(occupant, "You push off of [backup] to propel yourself.") + return 1 + +/obj/mecha/relaymove(mob/user, direction) + if(!direction) + return + if(user != occupant) //While not "realistic", this piece is player friendly. + user.forceMove(get_turf(src)) + to_chat(user, "You climb out from [src].") + return 0 + if(connected_port) + if(world.time - last_message > 20) + occupant_message("Unable to move while connected to the air system port!") + last_message = world.time + return 0 + if(state) + occupant_message("Maintenance protocols in effect.") + return + return domove(direction) + +/obj/mecha/proc/domove(direction) + if(can_move >= world.time) + return 0 + if(!Process_Spacemove(direction)) + return 0 + if(!has_charge(step_energy_drain)) + return 0 + if(defence_mode) + if(world.time - last_message > 20) + occupant_message("Unable to move while in defence mode.") + last_message = world.time + return 0 + if(zoom_mode) + if(world.time - last_message > 20) + occupant_message("Unable to move while in zoom mode.") + last_message = world.time + return 0 + + var/move_result = 0 + var/move_type = 0 + if(internal_damage & MECHA_INT_CONTROL_LOST) + move_result = mechsteprand() + move_type = MECHAMOVE_RAND + else if(dir != direction) + move_result = mechturn(direction) + move_type = MECHAMOVE_TURN + else + move_result = mechstep(direction) + move_type = MECHAMOVE_STEP + + if(move_result && move_type) + aftermove(move_type) + can_move = world.time + step_in + return TRUE + return FALSE + +/obj/mecha/proc/aftermove(move_type) + use_power(step_energy_drain) + if(move_type & (MECHAMOVE_RAND | MECHAMOVE_STEP) && occupant) + var/obj/machinery/atmospherics/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/unary/portables_connector) in loc + if(possible_port) + var/obj/screen/alert/mech_port_available/A = occupant.throw_alert("mechaport", /obj/screen/alert/mech_port_available, override = TRUE) + if(A) + A.target = possible_port + else + occupant.clear_alert("mechaport") + if(leg_overload_mode) + if(obj_integrity < max_integrity - max_integrity / 3) + leg_overload_mode = FALSE + step_in = initial(step_in) + step_energy_drain = initial(step_energy_drain) + occupant_message("Leg actuators damage threshold exceded. Disabling overload.") + +/obj/mecha/proc/mechturn(direction) + dir = direction + if(turnsound) + playsound(src,turnsound,40,1) + return 1 + +/obj/mecha/proc/mechstep(direction) + . = step(src, direction) + if(!.) + if(phasing && get_charge() >= phasing_energy_drain) + if(can_move < world.time) + . = FALSE // We lie to mech code and say we didn't get to move, because we want to handle power usage + cooldown ourself + flick("phazon-phase", src) + forceMove(get_step(src, direction)) + use_power(phasing_energy_drain) + playsound(src, stepsound, 40, 1) + can_move = world.time + (step_in * 3) + else if(stepsound) + playsound(src, stepsound, 40, 1) + +/obj/mecha/proc/mechsteprand() + . = step_rand(src) + if(. && stepsound) + playsound(src, stepsound, 40, 1) + +/obj/mecha/Bump(var/atom/obstacle, bump_allowed) + if(throwing) //high velocity mechas in your face! + var/breakthrough = 0 + if(istype(obstacle, /obj/structure/window)) + qdel(obstacle) + breakthrough = 1 + + else if(istype(obstacle, /obj/structure/grille/)) + var/obj/structure/grille/G = obstacle + G.obj_break() + breakthrough = 1 + + else if(istype(obstacle, /obj/structure/table)) + var/obj/structure/table/T = obstacle + qdel(T) + breakthrough = 1 + + else if(istype(obstacle, /obj/structure/rack)) + new /obj/item/rack_parts(obstacle.loc) + qdel(obstacle) + breakthrough = 1 + + else if(istype(obstacle, /obj/structure/reagent_dispensers/fueltank)) + obstacle.ex_act(1) + + else if(isliving(obstacle)) + var/mob/living/L = obstacle + var/hit_sound = list('sound/weapons/genhit1.ogg','sound/weapons/genhit2.ogg','sound/weapons/genhit3.ogg') + if(L.flags & GODMODE) + return + L.take_overall_damage(5,0) + if(L.buckled) + L.buckled = 0 + L.Stun(5) + L.Weaken(5) + L.apply_effect(STUTTER, 5) + playsound(src, pick(hit_sound), 50, 0, 0) + breakthrough = 1 + + else + if(throwing) + throwing.finalize(FALSE) + crashing = null + + ..() + + if(breakthrough) + if(crashing) + spawn(1) + throw_at(crashing, 50, throw_speed) + else + spawn(1) + crashing = get_distant_turf(get_turf(src), dir, 3)//don't use get_dir(src, obstacle) or the mech will stop if he bumps into a one-direction window on his tile. + throw_at(crashing, 50, throw_speed) + + else + if(bump_allowed) + if(..()) + return + if(isobj(obstacle)) + var/obj/O = obstacle + if(istype(O, /obj/effect/portal)) //derpfix + anchored = 0 + O.Bumped(src) + spawn(0) //countering portal teleport spawn(0), hurr + anchored = 1 + else if(!O.anchored) + step(obstacle, dir) + else if(ismob(obstacle)) + step(obstacle, dir) + + +/////////////////////////////////// +//////// Internal damage //////// +/////////////////////////////////// + +/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage, ignore_threshold=null) + if(!islist(possible_int_damage) || isemptylist(possible_int_damage)) + return + if(prob(20)) + if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) + for(var/T in possible_int_damage) + if(internal_damage & T) + possible_int_damage -= T + var/int_dam_flag = safepick(possible_int_damage) + if(int_dam_flag) + setInternalDamage(int_dam_flag) + if(prob(5)) + if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) + var/obj/item/mecha_parts/mecha_equipment/ME = safepick(equipment) + if(ME) + qdel(ME) + +/obj/mecha/proc/hasInternalDamage(int_dam_flag=null) + return int_dam_flag ? internal_damage&int_dam_flag : internal_damage + + +/obj/mecha/proc/setInternalDamage(int_dam_flag) + internal_damage |= int_dam_flag + log_append_to_last("Internal damage of type [int_dam_flag].",1) + occupant << sound('sound/machines/warning-buzzer.ogg',wait=0) + diag_hud_set_mechstat() + +/obj/mecha/proc/clearInternalDamage(int_dam_flag) + internal_damage &= ~int_dam_flag + switch(int_dam_flag) + if(MECHA_INT_TEMP_CONTROL) + occupant_message("Life support system reactivated.") + if(MECHA_INT_FIRE) + occupant_message("Internal fire extinquished.") + if(MECHA_INT_TANK_BREACH) + occupant_message("Damaged internal tank has been sealed.") + diag_hud_set_mechstat() + + +//////////////////////////////////////// +//////// Health related procs //////// +//////////////////////////////////////// + +/obj/mecha/proc/get_armour_facing(relative_dir) + switch(relative_dir) + if(0) // BACKSTAB! + return facing_modifiers[MECHA_BACK_ARMOUR] + if(45, 90, 270, 315) + return facing_modifiers[MECHA_SIDE_ARMOUR] + if(225, 180, 135) + return facing_modifiers[MECHA_FRONT_ARMOUR] + return 1 //always return non-0 + +/obj/mecha/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(. && obj_integrity > 0) + spark_system.start() + switch(damage_flag) + if("fire") + check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL)) + if("melee") + check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST)) + else + check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT)) + if(. >= 5 || prob(33)) + occupant_message("Taking damage!") + log_message("Took [damage_amount] points of damage. Damage type: [damage_type]") + +/obj/mecha/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + . = ..() + if(!damage_amount) + return 0 + var/booster_deflection_modifier = 1 + var/booster_damage_modifier = 1 + if(damage_flag == "bullet" || damage_flag == "laser" || damage_flag == "energy") + for(var/obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster/B in equipment) + if(B.projectile_react()) + booster_deflection_modifier = B.deflect_coeff + booster_damage_modifier = B.damage_coeff + break + else if(damage_flag == "melee") + for(var/obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster/B in equipment) + if(B.attack_react()) + booster_deflection_modifier *= B.deflect_coeff + booster_damage_modifier *= B.damage_coeff + break + + if(attack_dir) + var/facing_modifier = get_armour_facing(dir2angle(attack_dir) - dir2angle(src)) + booster_damage_modifier /= facing_modifier + booster_deflection_modifier *= facing_modifier + if(prob(deflect_chance * booster_deflection_modifier)) + visible_message("[src]'s armour deflects the attack!") + log_message("Armor saved.") + return 0 + if(.) + . *= booster_damage_modifier + +/obj/mecha/attack_hand(mob/living/user) + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) + playsound(loc, 'sound/weapons/tap.ogg', 40, 1, -1) + user.visible_message("[user] hits [name]. Nothing happens", "You hit [name] with no visible effect.") + log_message("Attack by hand/paw. Attacker - [user].") + + +/obj/mecha/attack_alien(mob/living/user) + log_message("Attack by alien. Attacker - [user].", color = "red") + playsound(src.loc, 'sound/weapons/slash.ogg', 100, TRUE) + attack_generic(user, 15, BRUTE, "melee", 0) + +/obj/mecha/attack_animal(mob/living/simple_animal/user) + log_message("Attack by simple animal. Attacker - [user].") + if(!user.melee_damage_upper && !user.obj_damage) + user.custom_emote(1, "[user.friendly] [src].") + return FALSE + else + var/play_soundeffect = 1 + if(user.environment_smash) + play_soundeffect = 0 + playsound(src, 'sound/effects/bang.ogg', 50, TRUE) + var/animal_damage = rand(user.melee_damage_lower,user.melee_damage_upper) + if(user.obj_damage) + animal_damage = user.obj_damage + animal_damage = min(animal_damage, 20*user.environment_smash) + user.create_attack_log("attacked [name]") + attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect) + return TRUE + +/obj/mecha/hulk_damage() + return 15 + +/obj/mecha/attack_hulk(mob/living/carbon/human/user) + . = ..() + if(.) + log_message("Attack by hulk. Attacker - [user].", 1) + add_attack_logs(user, src, "Punched with hulk powers") + +/obj/mecha/blob_act(obj/structure/blob/B) + log_message("Attack by blob. Attacker - [B].") + take_damage(30, BRUTE, "melee", 0, get_dir(src, B)) + +/obj/mecha/attack_tk() + return + +/obj/mecha/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) //wrapper + log_message("Hit by [AM].") + . = ..() + +/obj/mecha/bullet_act(obj/item/projectile/Proj) //wrapper + log_message("Hit by projectile. Type: [Proj.name]([Proj.flag]).") + ..() + +/obj/mecha/ex_act(severity, target) + log_message("Affected by explosion of severity: [severity].") + if(prob(deflect_chance)) + severity++ + log_message("Armor saved, changing severity to [severity]") + ..() + severity++ + for(var/X in equipment) + var/obj/item/mecha_parts/mecha_equipment/ME = X + ME.ex_act(severity) + for(var/Y in trackers) + var/obj/item/mecha_parts/mecha_tracking/MT = Y + MT.ex_act(severity) + if(occupant) + occupant.ex_act(severity) + +/obj/mecha/handle_atom_del(atom/A) + if(A == occupant) + occupant = null + icon_state = initial(icon_state)+"-open" + setDir(dir_in) + +/obj/mecha/Destroy() + if(occupant) + occupant.SetSleeping(destruction_sleep_duration) + go_out() + var/mob/living/silicon/ai/AI + for(var/mob/M in src) //Let's just be ultra sure + if(isAI(M)) + occupant = null + AI = M //AIs are loaded into the mech computer itself. When the mech dies, so does the AI. They can be recovered with an AI card from the wreck. + else + M.forceMove(loc) + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + E.detach(loc) + qdel(E) + equipment.Cut() + QDEL_NULL(cell) + QDEL_NULL(internal_tank) + if(AI) + AI.gib() //No wreck, no AI to recover + STOP_PROCESSING(SSobj, src) + GLOB.poi_list.Remove(src) + if(loc) + loc.assume_air(cabin_air) + air_update_turf() + else + qdel(cabin_air) + cabin_air = null + QDEL_NULL(spark_system) + QDEL_NULL(smoke_system) + + GLOB.mechas_list -= src //global mech list + return ..() + +//TODO +/obj/mecha/emp_act(severity) + if(get_charge()) + use_power((cell.charge/3)/(severity*2)) + take_damage(30 / severity, BURN, "energy", 1) + log_message("EMP detected", 1) + check_for_internal_damage(list(MECHA_INT_FIRE, MECHA_INT_TEMP_CONTROL, MECHA_INT_CONTROL_LOST, MECHA_INT_SHORT_CIRCUIT), 1) + +/obj/mecha/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > max_temperature) + log_message("Exposed to dangerous temperature.", 1) + take_damage(5, BURN, 0, 1) + check_for_internal_damage(list(MECHA_INT_FIRE, MECHA_INT_TEMP_CONTROL)) + +////////////////////// +////// AttackBy ////// +////////////////////// + +/obj/mecha/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/mmi)) + if(mmi_move_inside(W,user)) + to_chat(user, "[src]-MMI interface initialized successfuly") + else + to_chat(user, "[src]-MMI interface initialization failed.") + return + + if(istype(W, /obj/item/mecha_parts/mecha_equipment)) + var/obj/item/mecha_parts/mecha_equipment/E = W + spawn() + if(E.can_attach(src)) + if(!user.drop_item()) + return + E.attach(src) + user.visible_message("[user] attaches [W] to [src].", "You attach [W] to [src].") + else + to_chat(user, "You were unable to attach [W] to [src]!") + return + + if(W.GetID()) + if(add_req_access || maint_access) + if(internals_access_allowed(usr)) + var/obj/item/card/id/id_card + if(istype(W, /obj/item/card/id)) + id_card = W + else + var/obj/item/pda/pda = W + id_card = pda.id + output_maintenance_dialog(id_card, user) + return + else + to_chat(user, "Invalid ID: Access denied.") + else + to_chat(user, "Maintenance protocols disabled by operator.") + + else if(istype(W, /obj/item/stack/cable_coil)) + if(state == 3 && hasInternalDamage(MECHA_INT_SHORT_CIRCUIT)) + var/obj/item/stack/cable_coil/CC = W + if(CC.amount > 1) + CC.use(2) + clearInternalDamage(MECHA_INT_SHORT_CIRCUIT) + to_chat(user, "You replace the fused wires.") + else + to_chat(user, "There's not enough wire to finish the task.") + return + + else if(istype(W, /obj/item/stock_parts/cell)) + if(state==4) + if(!cell) + if(!user.drop_item()) + return + to_chat(user, "You install the powercell.") + W.forceMove(src) + cell = W + log_message("Powercell installed") + else + to_chat(user, "There's already a powercell installed.") + return + + else if(istype(W, /obj/item/mecha_parts/mecha_tracking)) + if(!user.unEquip(W)) + to_chat(user, "\the [W] is stuck to your hand, you cannot put it in \the [src]") + return + W.forceMove(src) + trackers += W + user.visible_message("[user] attaches [W] to [src].", "You attach [W] to [src].") + diag_hud_set_mechtracking() + return + + else if(istype(W, /obj/item/paintkit)) + if(occupant) + to_chat(user, "You can't customize a mech while someone is piloting it - that would be unsafe!") + return + + var/obj/item/paintkit/P = W + var/found = null + + for(var/type in P.allowed_types) + if(type == initial_icon) + found = 1 + break + + if(!found) + to_chat(user, "That kit isn't meant for use on this class of exosuit.") + return + + user.visible_message("[user] opens [P] and spends some quality time customising [src].") + + name = P.new_name + desc = P.new_desc + initial_icon = P.new_icon + reset_icon() + + user.drop_item() + qdel(P) + + else if(istype(W, /obj/item/mecha_modkit)) + if(occupant) + to_chat(user, "You can't access the mech's modification port while it is occupied.") + return + var/obj/item/mecha_modkit/M = W + if(do_after_once(user, M.install_time, target = src)) + M.install(src, user) + else + to_chat(user, "You stop installing [M].") + + else + return ..() + + +/obj/mecha/crowbar_act(mob/user, obj/item/I) + if(state != 2 && state != 3 && !(state == 4 && pilot_is_mmi())) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(state == 2) + state = 3 + to_chat(user, "You open the hatch to the power unit") + else if(state == 3) + state = 2 + to_chat(user, "You close the hatch to the power unit") + else + // Since having maint protocols available is controllable by the MMI, I see this as a consensual way to remove an MMI without destroying the mech + user.visible_message("[user] begins levering out the MMI from the [src].", "You begin to lever out the MMI from the [src].") + to_chat(occupant, "[user] is prying you out of the exosuit!") + if(I.use_tool(src, user, 80, volume = I.tool_volume) && pilot_is_mmi()) + user.visible_message("[user] pries the MMI out of the [src]!", "You finish removing the MMI from the [src]!") + go_out() + +/obj/mecha/screwdriver_act(mob/user, obj/item/I) + if(user.a_intent == INTENT_HARM) + return + if(!(state==3 && cell) && !(state==4 && cell)) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(hasInternalDamage(MECHA_INT_TEMP_CONTROL)) + clearInternalDamage(MECHA_INT_TEMP_CONTROL) + to_chat(user, "You repair the damaged temperature controller.") + else if(state==3 && cell) + cell.forceMove(loc) + cell = null + state = 4 + to_chat(user, "You unscrew and pry out the powercell.") + log_message("Powercell removed") + else if(state==4 && cell) + state=3 + to_chat(user, "You screw the cell in place.") + +/obj/mecha/wrench_act(mob/user, obj/item/I) + if(state != 1 && state != 2) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(state==1) + state = 2 + to_chat(user, "You undo the securing bolts.") + else + state = 1 + to_chat(user, "You tighten the securing bolts.") + +/obj/mecha/welder_act(mob/user, obj/item/I) + if(user.a_intent == INTENT_HARM) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if((obj_integrity >= max_integrity) && !internal_damage) + to_chat(user, "[src] is at full integrity!") + return + WELDER_ATTEMPT_REPAIR_MESSAGE + if(I.use_tool(src, user, 15, volume = I.tool_volume)) + if(internal_damage & MECHA_INT_TANK_BREACH) + clearInternalDamage(MECHA_INT_TANK_BREACH) + user.visible_message("[user] repairs the damaged gas tank.", "You repair the damaged gas tank.") + else if(obj_integrity < max_integrity) + user.visible_message("[user] repairs some damage to [name].", "You repair some damage to [name].") + obj_integrity += min(10, max_integrity - obj_integrity) + else + to_chat(user, "[src] is at full integrity!") + +/obj/mecha/mech_melee_attack(obj/mecha/M) + if(!has_charge(melee_energy_drain)) + return 0 + use_power(melee_energy_drain) + if(M.damtype == BRUTE || M.damtype == BURN) + add_attack_logs(M.occupant, src, "Mecha-attacked with [M] (INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])") + . = ..() + +/obj/mecha/emag_act(mob/user) + to_chat(user, "[src]'s ID slot rejects the card.") + return + + +///////////////////////////////////// +//////////// AI piloting //////////// +///////////////////////////////////// + +/obj/mecha/attack_ai(mob/living/silicon/ai/user) + if(!isAI(user)) + return + //Allows the Malf to scan a mech's status and loadout, helping it to decide if it is a worthy chariot. + if(user.can_dominate_mechs) + examine(user) //Get diagnostic information! + for(var/obj/item/mecha_parts/mecha_tracking/B in trackers) + to_chat(user, "Warning: Tracking Beacon detected. Enter at your own risk. Beacon Data:") + to_chat(user, "[B.get_mecha_info_text()]") + break + //Nothing like a big, red link to make the player feel powerful! + to_chat(user, "ASSUME DIRECT CONTROL?
        ") + else + examine(user) + if(occupant) + user << "This exosuit has a pilot and cannot be controlled." + return + var/can_control_mech = FALSE + for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in trackers) + can_control_mech = TRUE + to_chat(user, "[bicon(src)] Status of [name]:\n\ + [A.get_mecha_info_text()]") + break + if(!can_control_mech) + to_chat(user, "You cannot control exosuits without AI control beacons installed.") + return + to_chat(user, "Take control of exosuit?
        ") + +/obj/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(!..()) + return + + //Transfer from core or card to mech. Proc is called by mech. + switch(interaction) + if(AI_TRANS_TO_CARD) //Upload AI from mech to AI card. + if(!maint_access) //Mech must be in maint mode to allow carding. + to_chat(user, "[name] must have maintenance protocols active in order to allow a transfer.") + return + AI = occupant + if(!AI || !isAI(occupant)) //Mech does not have an AI for a pilot + to_chat(user, "No AI detected in the [name] onboard computer.") + return + if(AI.mind.special_role) //Malf AIs cannot leave mechs. Except through death. + to_chat(user, "ACCESS DENIED.") + return + AI.aiRestorePowerRoutine = 0//So the AI initially has power. + AI.control_disabled = 1 + AI.aiRadio.disabledAi = 1 + AI.loc = card + occupant = null + AI.controlled_mech = null + AI.remote_control = null + icon_state = initial(icon_state)+"-open" + to_chat(AI, "You have been downloaded to a mobile storage device. Wireless connection offline.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) removed from [name] and stored within local memory.") + + if(AI_MECH_HACK) //Called by AIs on the mech + AI.linked_core = new /obj/structure/AIcore/deactivated(AI.loc) + if(AI.can_dominate_mechs) + if(occupant) //Oh, I am sorry, were you using that? + to_chat(AI, "Pilot detected! Forced ejection initiated!") + to_chat(occupant, "You have been forcibly ejected!") + go_out(1) //IT IS MINE, NOW. SUCK IT, RD! + ai_enter_mech(AI, interaction) + + if(AI_TRANS_FROM_CARD) //Using an AI card to upload to a mech. + AI = locate(/mob/living/silicon/ai) in card + if(!AI) + to_chat(user, "There is no AI currently installed on this device.") + return + else if(AI.stat || !AI.client) + to_chat(user, "[AI.name] is currently unresponsive, and cannot be uploaded.") + return + else if(occupant || dna) //Normal AIs cannot steal mechs! + to_chat(user, "Access denied. [name] is [occupant ? "currently occupied" : "secured with a DNA lock"].") + return + AI.control_disabled = 0 + AI.aiRadio.disabledAi = 0 + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + ai_enter_mech(AI, interaction) + +//Hack and From Card interactions share some code, so leave that here for both to use. +/obj/mecha/proc/ai_enter_mech(mob/living/silicon/ai/AI, interaction) + AI.aiRestorePowerRoutine = 0 + AI.loc = src + occupant = AI + icon_state = initial(icon_state) + playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) + if(!hasInternalDamage()) + occupant << sound(nominalsound, volume = 50) + AI.cancel_camera() + AI.controlled_mech = src + AI.remote_control = src + AI.canmove = 1 //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow. + AI.can_shunt = 0 //ONE AI ENTERS. NO AI LEAVES. + to_chat(AI, "[AI.can_dominate_mechs ? "Takeover of [name] complete! You are now permanently loaded onto the onboard computer. Do not attempt to leave the station sector!" \ + : "You have been uploaded to a mech's onboard computer."]") + to_chat(AI, "Use Middle-Mouse to activate mech functions and equipment. Click normally for AI interactions.") + if(interaction == AI_TRANS_FROM_CARD) + GrantActions(AI, FALSE) + else + GrantActions(AI, !AI.can_dominate_mechs) + +///////////////////////////////////// +//////// Atmospheric stuff //////// +///////////////////////////////////// + +/obj/mecha/proc/get_turf_air() + var/turf/T = get_turf(src) + if(T) + . = T.return_air() + +/obj/mecha/remove_air(amount) + if(use_internal_tank) + return cabin_air.remove(amount) + else + var/turf/T = get_turf(src) + if(T) + return T.remove_air(amount) + +/obj/mecha/return_air() + if(use_internal_tank) + return cabin_air + return get_turf_air() + +/obj/mecha/proc/return_pressure() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_pressure() + +//skytodo: //No idea what you want me to do here, mate. +/obj/mecha/proc/return_temperature() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_temperature() + +/obj/mecha/proc/connect(obj/machinery/atmospherics/unary/portables_connector/new_port) + //Make sure not already connected to something else + if(connected_port || !istype(new_port) || new_port.connected_device) + return 0 + + //Make sure are close enough for a valid connection + if(new_port.loc != loc) + return 0 + + //Perform the connection + connected_port = new_port + connected_port.connected_device = src + connected_port.parent.reconcile_air() + + if(occupant) + occupant.clear_alert("mechaport") + occupant.throw_alert("mechaport_d", /obj/screen/alert/mech_port_disconnect) + + log_message("Connected to gas port.") + return 1 + +/obj/mecha/proc/disconnect() + if(!connected_port) + return 0 + + connected_port.connected_device = null + connected_port = null + log_message("Disconnected from gas port.") + if(occupant) + occupant.clear_alert("mechaport_d") + return 1 + +/obj/mecha/portableConnectorReturnAir() + return internal_tank.return_air() + +/obj/mecha/proc/toggle_lights() + lights = !lights + if(lights) + set_light(light_range + lights_power) + else + set_light(light_range - lights_power) + occupant_message("Toggled lights [lights ? "on" : "off"].") + log_message("Toggled lights [lights ? "on" : "off"].") + +/obj/mecha/proc/toggle_internal_tank() + use_internal_tank = !use_internal_tank + occupant_message("Now taking air from [use_internal_tank ? "internal airtank" : "environment"].") + log_message("Now taking air from [use_internal_tank ? "internal airtank" : "environment"].") + +/obj/mecha/MouseDrop_T(mob/M, mob/user) + if(user.incapacitated()) + return + if(user != M) + return + log_message("[user] tries to move in.") + if(occupant) + to_chat(user, "The [src] is already occupied!") + log_append_to_last("Permission denied.") + return + var/passed + if(dna) + if(ishuman(user)) + if(user.dna.unique_enzymes == dna) + passed = 1 + else if(operation_allowed(user)) + passed = 1 + if(!passed) + to_chat(user, "Access denied.") + log_append_to_last("Permission denied.") + return + if(user.buckled) + to_chat(user, "You are currently buckled and cannot move.") + log_append_to_last("Permission denied.") + return + if(user.has_buckled_mobs()) //mob attached to us + to_chat(user, "You can't enter the exosuit with other creatures attached to you!") + return + + visible_message("[user] starts to climb into [src]") + + if(do_after(user, 40, target = src)) + if(obj_integrity <= 0) + to_chat(user, "You cannot get in the [name], it has been destroyed!") + else if(occupant) + to_chat(user, "[occupant] was faster! Try better next time, loser.") + else if(user.buckled) + to_chat(user, "You can't enter the exosuit while buckled.") + else if(user.has_buckled_mobs()) + to_chat(user, "You can't enter the exosuit with other creatures attached to you!") + else + moved_inside(user) + else + to_chat(user, "You stop entering the exosuit!") + +/obj/mecha/proc/moved_inside(var/mob/living/carbon/human/H as mob) + if(H && H.client && H in range(1)) + occupant = H + H.stop_pulling() + H.forceMove(src) + H.reset_perspective(src) + add_fingerprint(H) + GrantActions(H, human_occupant = 1) + forceMove(loc) + log_append_to_last("[H] moved in as pilot.") + icon_state = reset_icon() + dir = dir_in + playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) + if(!activated) + occupant << sound(longactivationsound, volume = 50) + activated = TRUE + else if(!hasInternalDamage()) + occupant << sound(nominalsound, volume = 50) + if(state) + H.throw_alert("locked", /obj/screen/alert/mech_maintenance) + return 1 + else + return 0 + +/obj/mecha/proc/mmi_move_inside(var/obj/item/mmi/mmi_as_oc as obj,mob/user as mob) + if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) + to_chat(user, "Consciousness matrix not detected!") + return 0 + else if(mmi_as_oc.brainmob.stat) + to_chat(user, "Beta-rhythm below acceptable level!") + return 0 + else if(occupant) + to_chat(user, "Occupant detected!") + return 0 + else if(dna && dna != mmi_as_oc.brainmob.dna.unique_enzymes) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + return 0 + + if(do_after(user, 40, target = src)) + if(!occupant) + return mmi_moved_inside(mmi_as_oc,user) + else + to_chat(user, "Occupant detected!") + else + to_chat(user, "You stop inserting the MMI.") + return 0 + +/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/mmi_as_oc,mob/user) + if(mmi_as_oc && user in range(1)) + if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) + to_chat(user, "Consciousness matrix not detected.") + return 0 + else if(mmi_as_oc.brainmob.stat) + to_chat(user, "Beta-rhythm below acceptable level.") + return 0 + if(!user.unEquip(mmi_as_oc)) + to_chat(user, "\the [mmi_as_oc] is stuck to your hand, you cannot put it in \the [src]") + return 0 + var/mob/brainmob = mmi_as_oc.brainmob + brainmob.reset_perspective(src) + occupant = brainmob + brainmob.forceMove(src) //should allow relaymove + brainmob.canmove = 1 + if(istype(mmi_as_oc, /obj/item/mmi/robotic_brain)) + var/obj/item/mmi/robotic_brain/R = mmi_as_oc + if(R.imprinted_master) + to_chat(brainmob, "Your imprint to [R.imprinted_master] has been temporarily disabled. You should help the crew and not commit harm.") + mmi_as_oc.loc = src + mmi_as_oc.mecha = src + Entered(mmi_as_oc) + Move(loc) + icon_state = reset_icon() + dir = dir_in + log_message("[mmi_as_oc] moved in as pilot.") + if(!hasInternalDamage()) + to_chat(occupant, sound(nominalsound, volume=50)) + GrantActions(brainmob) + return 1 + else + return 0 + +/obj/mecha/proc/pilot_is_mmi() + var/atom/movable/mob_container + if(istype(occupant, /mob/living/carbon/brain)) + var/mob/living/carbon/brain/brain = occupant + mob_container = brain.container + if(istype(mob_container, /obj/item/mmi)) + return 1 + return 0 + +/obj/mecha/proc/pilot_mmi_hud(var/mob/living/carbon/brain/pilot) + return + +/obj/mecha/Exited(atom/movable/M, atom/newloc) + if(occupant && occupant == M) // The occupant exited the mech without calling go_out() + go_out(1, newloc) + +/obj/mecha/proc/go_out(forced, atom/newloc = loc) + if(!occupant) + return + var/atom/movable/mob_container + occupant.clear_alert("charge") + occupant.clear_alert("locked") + occupant.clear_alert("mech damage") + occupant.clear_alert("mechaport") + occupant.clear_alert("mechaport_d") + if(ishuman(occupant)) + mob_container = occupant + RemoveActions(occupant, human_occupant = 1) + else if(isbrain(occupant)) + var/mob/living/carbon/brain/brain = occupant + RemoveActions(brain) + mob_container = brain.container + else if(isAI(occupant)) + var/mob/living/silicon/ai/AI = occupant + if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf. + RemoveActions(occupant) + occupant.gib() //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced. + occupant = null + return + else + if(!AI.linked_core || QDELETED(AI.linked_core)) + to_chat(AI, "Inactive core destroyed. Unable to return.") + AI.linked_core = null + return + to_chat(AI, "Returning to core...") + AI.controlled_mech = null + AI.remote_control = null + RemoveActions(occupant, 1) + mob_container = AI + newloc = get_turf(AI.linked_core) + qdel(AI.linked_core) + else + return + var/mob/living/L = occupant + occupant = null //we need it null when forceMove calls Exited(). + if(mob_container.forceMove(newloc))//ejecting mob container + log_message("[mob_container] moved out.") + L << browse(null, "window=exosuit") + + if(istype(mob_container, /obj/item/mmi)) + var/obj/item/mmi/mmi = mob_container + if(mmi.brainmob) + L.loc = mmi + L.reset_perspective() + mmi.mecha = null + mmi.update_icon() + L.canmove = 0 + if(istype(mmi, /obj/item/mmi/robotic_brain)) + var/obj/item/mmi/robotic_brain/R = mmi + if(R.imprinted_master) + to_chat(L, "Imprint re-enabled, you are once again bound to [R.imprinted_master]'s commands.") + icon_state = initial(icon_state)+"-open" + dir = dir_in + + if(L && L.client) + L.client.RemoveViewMod("mecha") + zoom_mode = FALSE + +///////////////////////// +////// Access stuff ///// +///////////////////////// + +/obj/mecha/proc/operation_allowed(mob/living/carbon/human/H) + if(!ishuman(H)) + return 0 + for(var/ID in list(H.get_active_hand(), H.wear_id, H.belt)) + if(check_access(ID, operation_req_access)) + return 1 + return 0 + + +/obj/mecha/proc/internals_access_allowed(mob/living/carbon/human/H) + for(var/atom/ID in list(H.get_active_hand(), H.wear_id, H.belt)) + if(check_access(ID, internals_req_access)) + return 1 + return 0 + + +/obj/mecha/check_access(obj/item/card/id/I, list/access_list) + if(!istype(access_list)) + return 1 + if(!access_list.len) //no requirements + return 1 + if(istype(I, /obj/item/pda)) + var/obj/item/pda/pda = I + I = pda.id + if(!istype(I) || !I.access) //not ID or no access + return 0 + if(access_list==operation_req_access) + for(var/req in access_list) + if(!(req in I.access)) //doesn't have this access + return 0 + else if(access_list==internals_req_access) + for(var/req in access_list) + if(req in I.access) + return 1 + return 1 + +/////////////////////// +///// Power stuff ///// +/////////////////////// + +/obj/mecha/proc/has_charge(amount) + return (get_charge()>=amount) + +/obj/mecha/proc/get_charge() + for(var/obj/item/mecha_parts/mecha_equipment/tesla_energy_relay/R in equipment) + var/relay_charge = R.get_charge() + if(relay_charge) + return relay_charge + if(cell) + return max(0, cell.charge) + +/obj/mecha/proc/use_power(amount) + if(get_charge()) + cell.use(amount) + if(occupant) + update_cell() + return 1 + return 0 + +/obj/mecha/proc/give_power(amount) + if(!isnull(get_charge())) + cell.give(amount) + if(occupant) + update_cell() + return 1 + return 0 + +/obj/mecha/proc/update_cell() + if(cell) + var/cellcharge = cell.charge/cell.maxcharge + switch(cellcharge) + if(0.75 to INFINITY) + occupant.clear_alert("charge") + if(0.5 to 0.75) + occupant.throw_alert("charge", /obj/screen/alert/mech_lowcell, 1) + if(0.25 to 0.5) + occupant.throw_alert("charge", /obj/screen/alert/mech_lowcell, 2) + if(power_warned) + power_warned = FALSE + if(0.01 to 0.25) + occupant.throw_alert("charge", /obj/screen/alert/mech_lowcell, 3) + if(!power_warned) + occupant << sound(lowpowersound, volume = 50) + power_warned = TRUE + else + occupant.throw_alert("charge", /obj/screen/alert/mech_emptycell) + else + occupant.throw_alert("charge", /obj/screen/alert/mech_nocell) + +/obj/mecha/proc/reset_icon() + if(initial_icon) + icon_state = initial_icon + else + icon_state = initial(icon_state) + return icon_state + +////////////////////////////////////////// +//////// Mecha global iterators //////// +////////////////////////////////////////// + +/obj/mecha/process() + process_internal_damage() + regulate_temp() + give_air() + update_huds() + +/obj/mecha/proc/process_internal_damage() + if(!internal_damage) + return + + if(internal_damage & MECHA_INT_FIRE) + if(!(internal_damage & MECHA_INT_TEMP_CONTROL) && prob(5)) + clearInternalDamage(MECHA_INT_FIRE) + if(internal_tank) + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + if(int_tank_air.return_pressure() > internal_tank.maximum_pressure && !(internal_damage & MECHA_INT_TANK_BREACH)) + setInternalDamage(MECHA_INT_TANK_BREACH) + + if(int_tank_air && int_tank_air.return_volume() > 0) + int_tank_air.temperature = min(6000 + T0C, cabin_air.return_temperature() + rand(10, 15)) + + if(cabin_air && cabin_air.return_volume()>0) + cabin_air.temperature = min(6000+T0C, cabin_air.return_temperature()+rand(10,15)) + if(cabin_air.return_temperature() > max_temperature/2) + take_damage(4/round(max_temperature/cabin_air.return_temperature(),0.1), BURN, 0, 0) + + if(internal_damage & MECHA_INT_TANK_BREACH) //remove some air from internal tank + if(internal_tank) + var/datum/gas_mixture/int_tank_air = internal_tank.return_air() + var/datum/gas_mixture/leaked_gas = int_tank_air.remove_ratio(0.10) + if(loc) + loc.assume_air(leaked_gas) + air_update_turf() + else + qdel(leaked_gas) + + if(internal_damage & MECHA_INT_SHORT_CIRCUIT) + if(get_charge()) + spark_system.start() + cell.charge -= min(20,cell.charge) + cell.maxcharge -= min(20,cell.maxcharge) + +/obj/mecha/proc/regulate_temp() + if(internal_damage & MECHA_INT_TEMP_CONTROL) + return + + if(cabin_air && cabin_air.return_volume() > 0) + var/delta = cabin_air.temperature - T20C + cabin_air.temperature -= max(-10, min(10, round(delta / 4, 0.1))) + +/obj/mecha/proc/give_air() + if(!internal_tank) + return + + var/datum/gas_mixture/tank_air = internal_tank.return_air() + + var/release_pressure = internal_tank_valve + var/cabin_pressure = cabin_air.return_pressure() + var/pressure_delta = min(release_pressure - cabin_pressure, (tank_air.return_pressure() - cabin_pressure)/2) + var/transfer_moles = 0 + if(pressure_delta > 0) //cabin pressure lower than release pressure + if(tank_air.return_temperature() > 0) + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = tank_air.remove(transfer_moles) + cabin_air.merge(removed) + else if(pressure_delta < 0) //cabin pressure higher than release pressure + var/datum/gas_mixture/t_air = return_air() + pressure_delta = cabin_pressure - release_pressure + if(t_air) + pressure_delta = min(cabin_pressure - t_air.return_pressure(), pressure_delta) + if(pressure_delta > 0) //if location pressure is lower than cabin pressure + transfer_moles = pressure_delta*cabin_air.return_volume()/(cabin_air.return_temperature() * R_IDEAL_GAS_EQUATION) + var/datum/gas_mixture/removed = cabin_air.remove(transfer_moles) + if(t_air) + t_air.merge(removed) + else //just delete the cabin gas, we're in space or some shit + qdel(removed) + +/obj/mecha/proc/update_huds() + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + diag_hud_set_mechtracking() + + +/obj/mecha/speech_bubble(var/bubble_state = "",var/bubble_loc = src, var/list/bubble_recipients = list()) + flick_overlay(image('icons/mob/talk.dmi', bubble_loc, bubble_state,MOB_LAYER+1), bubble_recipients, 30) + +/obj/mecha/update_remote_sight(mob/living/user) + if(occupant_sight_flags) + if(user == occupant) + user.sight |= occupant_sight_flags + + ..() + +/obj/mecha/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y) + if(!no_effect) + if(selected) + used_item = selected + else if(!visual_effect_icon) + visual_effect_icon = ATTACK_EFFECT_SMASH + if(damtype == BURN) + visual_effect_icon = ATTACK_EFFECT_MECHFIRE + else if(damtype == TOX) + visual_effect_icon = ATTACK_EFFECT_MECHTOXIN + ..() + +/obj/mecha/obj_destruction() + if(wreckage) + var/mob/living/silicon/ai/AI + if(isAI(occupant)) + AI = occupant + occupant = null + var/obj/structure/mecha_wreckage/WR = new wreckage(loc, AI) + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + if(E.salvageable && prob(30)) + WR.crowbar_salvage += E + E.detach(WR) //detaches from src into WR + E.equip_ready = 1 + else + E.detach(loc) + qdel(E) + if(cell) + WR.crowbar_salvage += cell + cell.forceMove(WR) + cell.charge = rand(0, cell.charge) + cell = null + if(internal_tank) + WR.crowbar_salvage += internal_tank + internal_tank.forceMove(WR) + cell = null + . = ..() + +/obj/mecha/CtrlClick(mob/living/L) + if(occupant != L || !istype(L)) + return ..() + + var/list/choices = list("Cancel / No Change" = mutable_appearance(icon = 'icons/mob/screen_gen.dmi', icon_state = "x")) + var/list/choices_to_refs = list() + + for(var/obj/item/mecha_parts/mecha_equipment/MT in equipment) + if(!MT.selectable || selected == MT) + continue + var/mutable_appearance/clean/MA = new(MT) + choices[MT.name] = MA + choices_to_refs[MT.name] = MT + + var/choice = show_radial_menu(L, src, choices, radius = 48, custom_check = CALLBACK(src, .proc/check_menu, L)) + if(!check_menu(L) || choice == "Cancel / No Change") + return + + var/obj/item/mecha_parts/mecha_equipment/new_sel = LAZYACCESS(choices_to_refs, choice) + if(istype(new_sel)) + selected = new_sel + occupant_message("You switch to [selected].") + visible_message("[src] raises [selected]") + send_byjax(occupant, "exosuit.browser", "eq_list", get_equipment_list()) + +/obj/mecha/proc/check_menu(mob/living/L) + if(L != occupant || !istype(L)) + return FALSE + if(L.incapacitated()) + return FALSE + return TRUE diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm index 3c71db19b72..8ff3d4ebd5c 100644 --- a/code/game/mecha/mecha_actions.dm +++ b/code/game/mecha/mecha_actions.dm @@ -225,4 +225,4 @@ chassis.damtype = new_damtype button_icon_state = "mech_damtype_[new_damtype]" playsound(src, 'sound/mecha/mechmove01.ogg', 50, 1) - UpdateButtonIcon() \ No newline at end of file + UpdateButtonIcon() diff --git a/code/game/mecha/mecha_construction_paths.dm b/code/game/mecha/mecha_construction_paths.dm index 64eac560d39..c9dc5b11e97 100644 --- a/code/game/mecha/mecha_construction_paths.dm +++ b/code/game/mecha/mecha_construction_paths.dm @@ -1,1727 +1,1727 @@ -#define STANDARD_STACK_AMOUNT 5 - -//////////////////////////////// -///// Construction datums ////// -//////////////////////////////// - -/datum/construction/mecha/custom_action(step, atom/used_atom, mob/user) - if(istype(used_atom, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = used_atom - if(C.use(4)) - playsound(holder, C.usesound, 50, 1) - else - to_chat(user, ("There's not enough cable to finish the task.")) - return 0 - else if(istype(used_atom, /obj/item/stack)) - var/obj/item/stack/S = used_atom - if(S.amount < STANDARD_STACK_AMOUNT) - to_chat(user, ("There's not enough material in this stack.")) - return 0 - else - S.use(STANDARD_STACK_AMOUNT) - else - return ..() - -/datum/construction/reversible/mecha/custom_action(index as num, diff as num, atom/used_atom, mob/user as mob) - if(istype(used_atom, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = used_atom - if(C.use(4)) - playsound(holder, C.usesound, 50, 1) - else - to_chat(user, ("There's not enough cable to finish the task.")) - return 0 - else if(istype(used_atom, /obj/item/stack)) - var/obj/item/stack/S = used_atom - if(S.amount < STANDARD_STACK_AMOUNT) - to_chat(user, ("There's not enough material in this stack.")) - return 0 - else - S.use(STANDARD_STACK_AMOUNT) - else if(isitem(used_atom)) - var/obj/item/I = used_atom - if(I.tool_behaviour in CONSTRUCTION_TOOL_BEHAVIOURS) - if(!I.use_tool(holder, user, 0, volume = I.tool_volume)) - return 0 - return 1 - - -/datum/construction/mecha/ripley_chassis - steps = list(list("key"=/obj/item/mecha_parts/part/ripley_torso),//1 - list("key"=/obj/item/mecha_parts/part/ripley_left_arm),//2 - list("key"=/obj/item/mecha_parts/part/ripley_right_arm),//3 - list("key"=/obj/item/mecha_parts/part/ripley_left_leg),//4 - list("key"=/obj/item/mecha_parts/part/ripley_right_leg)//5 - ) - -/datum/construction/mecha/ripley_chassis/custom_action(step, atom/used_atom, mob/user) - user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") - holder.overlays += used_atom.icon_state+"+o" - qdel(used_atom) - return 1 - -/datum/construction/mecha/ripley_chassis/action(atom/used_atom,mob/user as mob) - return check_all_steps(used_atom,user) - -/datum/construction/mecha/ripley_chassis/spawn_result() - var/obj/item/mecha_parts/chassis/const_holder = holder - const_holder.construct = new /datum/construction/reversible/mecha/ripley(const_holder) - const_holder.icon = 'icons/mecha/mech_construction.dmi' - const_holder.icon_state = "ripley0" - const_holder.density = 1 - const_holder.overlays.len = 0 - qdel(src) - return - - -/datum/construction/reversible/mecha/ripley - result = "/obj/mecha/working/ripley" - taskpath = /datum/job_objective/make_ripley - steps = list( - //1 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="External armor is wrenched."), - //2 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="External armor is installed."), - //3 - list("key"=/obj/item/stack/sheet/plasteel, - "backkey"=TOOL_WELDER, - "desc"="Internal armor is welded."), - //4 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="Internal armor is wrenched."), - //5 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="Internal armor is installed."), - //6 - list("key"=/obj/item/stack/sheet/metal, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Peripherals control module is secured."), - //7 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Peripherals control module is installed."), - //8 - list("key"=/obj/item/circuitboard/mecha/ripley/peripherals, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Central control module is secured."), - //9 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Central control module is installed."), - //10 - list("key"=/obj/item/circuitboard/mecha/ripley/main, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is adjusted."), - //11 - list("key"=/obj/item/wirecutters, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is added."), - //12 - list("key"=/obj/item/stack/cable_coil, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The hydraulic systems are active."), - //13 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_WRENCH, - "desc"="The hydraulic systems are connected."), - //14 - list("key"=TOOL_WRENCH, - "desc"="The hydraulic systems are disconnected.") - ) - -/datum/construction/reversible/mecha/ripley/action(atom/used_atom,mob/user as mob) - return check_step(used_atom,user) - -/datum/construction/reversible/mecha/ripley/custom_action(index, diff, atom/used_atom, mob/user) - if(!..()) - return 0 - - //TODO: better messages. - switch(index) - if(14) - user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") - holder.icon_state = "ripley1" - if(13) - if(diff==FORWARD) - user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") - holder.icon_state = "ripley2" - else - user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") - holder.icon_state = "ripley0" - if(12) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") - holder.icon_state = "ripley3" - else - user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") - holder.icon_state = "ripley1" - if(11) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") - holder.icon_state = "ripley4" - else - user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") - var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) - coil.amount = 4 - holder.icon_state = "ripley2" - if(10) - if(diff==FORWARD) - user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") - qdel(used_atom) - holder.icon_state = "ripley5" - else - user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") - holder.icon_state = "ripley3" - if(9) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - holder.icon_state = "ripley6" - else - user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") - new /obj/item/circuitboard/mecha/ripley/main(get_turf(holder)) - holder.icon_state = "ripley4" - if(8) - if(diff==FORWARD) - user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") - qdel(used_atom) - holder.icon_state = "ripley7" - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - holder.icon_state = "ripley5" - if(7) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - holder.icon_state = "ripley8" - else - user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") - new /obj/item/circuitboard/mecha/ripley/peripherals(get_turf(holder)) - holder.icon_state = "ripley6" - if(6) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") - holder.icon_state = "ripley9" - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - holder.icon_state = "ripley7" - if(5) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - holder.icon_state = "ripley10" - else - user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") - var/obj/item/stack/sheet/metal/MS = new /obj/item/stack/sheet/metal(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "ripley8" - if(4) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") - holder.icon_state = "ripley11" - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - holder.icon_state = "ripley9" - if(3) - if(diff==FORWARD) - user.visible_message("[user] installs the external reinforced armor layer to the [holder].", "You install the external reinforced armor layer to the [holder].") - holder.icon_state = "ripley12" - else - user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") - holder.icon_state = "ripley10" - if(2) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - holder.icon_state = "ripley13" - else - user.visible_message("[user] pries external armor layer from the [holder].", "You pry external armor layer from the [holder].") - var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "ripley11" - if(1) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to the [holder].", "You weld the external armor layer to the [holder].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - holder.icon_state = "ripley12" - return 1 - -/datum/construction/reversible/mecha/ripley/spawn_result() - ..() - feedback_inc("mecha_ripley_created",1) - return - - - -/datum/construction/mecha/gygax_chassis - steps = list(list("key"=/obj/item/mecha_parts/part/gygax_torso),//1 - list("key"=/obj/item/mecha_parts/part/gygax_left_arm),//2 - list("key"=/obj/item/mecha_parts/part/gygax_right_arm),//3 - list("key"=/obj/item/mecha_parts/part/gygax_left_leg),//4 - list("key"=/obj/item/mecha_parts/part/gygax_right_leg),//5 - list("key"=/obj/item/mecha_parts/part/gygax_head) - ) - -/datum/construction/mecha/gygax_chassis/custom_action(step, atom/used_atom, mob/user) - user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") - holder.overlays += used_atom.icon_state+"+o" - qdel(used_atom) - return 1 - -/datum/construction/mecha/gygax_chassis/action(atom/used_atom,mob/user as mob) - return check_all_steps(used_atom,user) - -/datum/construction/mecha/gygax_chassis/spawn_result() - var/obj/item/mecha_parts/chassis/const_holder = holder - const_holder.construct = new /datum/construction/reversible/mecha/gygax(const_holder) - const_holder.icon = 'icons/mecha/mech_construction.dmi' - const_holder.icon_state = "gygax0" - const_holder.density = 1 - qdel(src) - return - - -/datum/construction/reversible/mecha/gygax - result = "/obj/mecha/combat/gygax" - steps = list( - //1 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="External armor is wrenched."), - //2 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="External armor is installed."), - //3 - list("key"=/obj/item/mecha_parts/part/gygax_armour, - "backkey"=TOOL_WELDER, - "desc"="Internal armor is welded."), - //4 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="Internal armor is wrenched."), - //5 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="Internal armor is installed."), - //6 - list("key"=/obj/item/stack/sheet/metal, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Advanced capacitor is secured."), - //7 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Advanced capacitor is installed."), - //8 - list("key"=/obj/item/stock_parts/capacitor/adv, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Advanced scanner module is secured."), - //9 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Advanced scanner module is installed."), - //10 - list("key"=/obj/item/stock_parts/scanning_module/adv, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Scanning module is secured."), - //11 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Scanning module is installed."), - //12 - list("key"=/obj/item/circuitboard/mecha/gygax/targeting, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Peripherals control module is secured."), - //13 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Peripherals control module is installed."), - //14 - list("key"=/obj/item/circuitboard/mecha/gygax/peripherals, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Central control module is secured."), - //15 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Central control module is installed."), - //16 - list("key"=/obj/item/circuitboard/mecha/gygax/main, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is adjusted."), - //17 - list("key"=/obj/item/wirecutters, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is added."), - //18 - list("key"=/obj/item/stack/cable_coil, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The hydraulic systems are active."), - //19 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_WRENCH, - "desc"="The hydraulic systems are connected."), - //20 - list("key"=TOOL_WRENCH, - "desc"="The hydraulic systems are disconnected.") - ) - -/datum/construction/reversible/mecha/gygax/action(atom/used_atom,mob/user as mob) - return check_step(used_atom,user) - -/datum/construction/reversible/mecha/gygax/custom_action(index, diff, atom/used_atom, mob/user) - if(!..()) - return 0 - - //TODO: better messages. - switch(index) - if(20) - user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") - holder.icon_state = "gygax1" - if(19) - if(diff==FORWARD) - user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") - holder.icon_state = "gygax2" - else - user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") - holder.icon_state = "gygax0" - if(18) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") - holder.icon_state = "gygax3" - else - user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") - holder.icon_state = "gygax1" - if(17) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") - holder.icon_state = "gygax4" - else - user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") - var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) - coil.amount = 4 - holder.icon_state = "gygax2" - if(16) - if(diff==FORWARD) - user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") - qdel(used_atom) - holder.icon_state = "gygax5" - else - user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") - holder.icon_state = "gygax3" - if(15) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - holder.icon_state = "gygax6" - else - user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") - new /obj/item/circuitboard/mecha/gygax/main(get_turf(holder)) - holder.icon_state = "gygax4" - if(14) - if(diff==FORWARD) - user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") - qdel(used_atom) - holder.icon_state = "gygax7" - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - holder.icon_state = "gygax5" - if(13) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - holder.icon_state = "gygax8" - else - user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") - new /obj/item/circuitboard/mecha/gygax/peripherals(get_turf(holder)) - holder.icon_state = "gygax6" - if(12) - if(diff==FORWARD) - user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") - qdel(used_atom) - holder.icon_state = "gygax9" - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - holder.icon_state = "gygax7" - if(11) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - holder.icon_state = "gygax10" - else - user.visible_message("[user] removes the weapon control module from the [holder].", "You remove the weapon control module from the [holder].") - new /obj/item/circuitboard/mecha/gygax/targeting(get_turf(holder)) - holder.icon_state = "gygax8" - if(10) - if(diff==FORWARD) - user.visible_message("[user] installs advanced scanner module to the [holder].", "You install advanced scanner module to the [holder].") - qdel(used_atom) - holder.icon_state = "gygax11" - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - holder.icon_state = "gygax9" - if(9) - if(diff==FORWARD) - user.visible_message("[user] secures the advanced scanner module.", "You secure the advanced scanner module.") - holder.icon_state = "gygax12" - else - user.visible_message("[user] removes the advanced scanner module from the [holder].", "You remove the advanced scanner module from the [holder].") - new /obj/item/stock_parts/scanning_module/adv(get_turf(holder)) - holder.icon_state = "gygax10" - if(8) - if(diff==FORWARD) - user.visible_message("[user] installs advanced capacitor to the [holder].", "You install advanced capacitor to the [holder].") - qdel(used_atom) - holder.icon_state = "gygax13" - else - user.visible_message("[user] unfastens the advanced scanner module.", "You unfasten the advanced scanner module.") - holder.icon_state = "gygax11" - if(7) - if(diff==FORWARD) - user.visible_message("[user] secures the advanced capacitor.", "You secure the advanced capacitor.") - holder.icon_state = "gygax14" - else - user.visible_message("[user] removes the advanced capacitor from the [holder].", "You remove the advanced capacitor from the [holder].") - new /obj/item/stock_parts/capacitor/adv(get_turf(holder)) - holder.icon_state = "gygax12" - if(6) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") - holder.icon_state = "gygax15" - else - user.visible_message("[user] unfastens the advanced capacitor.", "You unfasten the advanced capacitor.") - holder.icon_state = "gygax13" - if(5) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - holder.icon_state = "gygax16" - else - user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") - var/obj/item/stack/sheet/metal/MS = new /obj/item/stack/sheet/metal(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "gygax14" - if(4) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") - holder.icon_state = "gygax17" - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - holder.icon_state = "gygax15" - if(3) - if(diff==FORWARD) - user.visible_message("[user] installs Gygax Armor Plates to the [holder].", "You install Gygax Armor Plates to the [holder].") - qdel(used_atom) - holder.icon_state = "gygax18" - else - user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") - holder.icon_state = "gygax16" - if(2) - if(diff==FORWARD) - user.visible_message("[user] secures Gygax Armor Plates.", "You secure Gygax Armor Plates.") - holder.icon_state = "gygax19" - else - user.visible_message("[user] pries Gygax Armor Plates from the [holder].", "You pry Gygax Armor Plates from the [holder].") - new /obj/item/mecha_parts/part/gygax_armour(get_turf(holder)) - holder.icon_state = "gygax17" - if(1) - if(diff==FORWARD) - user.visible_message("[user] welds Gygax Armor Plates to the [holder].", "You weld Gygax Armor Plates to the [holder].") - else - user.visible_message("[user] unfastens Gygax Armor Plates.", "You unfasten Gygax Armor Plates.") - holder.icon_state = "gygax18" - return 1 - -/datum/construction/reversible/mecha/gygax/spawn_result() - ..() - feedback_inc("mecha_gygax_created",1) - return - -/datum/construction/mecha/firefighter_chassis - steps = list(list("key"=/obj/item/mecha_parts/part/ripley_torso),//1 - list("key"=/obj/item/mecha_parts/part/ripley_left_arm),//2 - list("key"=/obj/item/mecha_parts/part/ripley_right_arm),//3 - list("key"=/obj/item/mecha_parts/part/ripley_left_leg),//4 - list("key"=/obj/item/mecha_parts/part/ripley_right_leg),//5 - list("key"=/obj/item/clothing/suit/fire)//6 - ) - -/datum/construction/mecha/firefighter_chassis/custom_action(step, atom/used_atom, mob/user) - user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") - holder.overlays += used_atom.icon_state+"+o" - qdel(used_atom) - return 1 - -/datum/construction/mecha/firefighter_chassis/action(atom/used_atom,mob/user as mob) - return check_all_steps(used_atom,user) - -/datum/construction/mecha/firefighter_chassis/spawn_result() - var/obj/item/mecha_parts/chassis/const_holder = holder - const_holder.construct = new /datum/construction/reversible/mecha/firefighter(const_holder) - const_holder.icon = 'icons/mecha/mech_construction.dmi' - const_holder.icon_state = "fireripley0" - const_holder.density = 1 - qdel(src) - return - - -/datum/construction/reversible/mecha/firefighter - result = "/obj/mecha/working/ripley/firefighter" - taskpath = /datum/job_objective/make_ripley - steps = list( - //1 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="External armor is wrenched."), - //2 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="External armor is installed."), - //3 - list("key"=/obj/item/stack/sheet/plasteel, - "backkey"=TOOL_CROWBAR, - "desc"="External armor is being installed."), - //4 - list("key"=/obj/item/stack/sheet/plasteel, - "backkey"=TOOL_WELDER, - "desc"="Internal armor is welded."), - //5 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="Internal armor is wrenched."), - //6 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="Internal armor is installed."), - - //7 - list("key"=/obj/item/stack/sheet/plasteel, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Peripherals control module is secured."), - //8 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Peripherals control module is installed."), - //9 - list("key"=/obj/item/circuitboard/mecha/ripley/peripherals, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Central control module is secured."), - //10 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Central control module is installed."), - //11 - list("key"=/obj/item/circuitboard/mecha/ripley/main, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is adjusted."), - //12 - list("key"=/obj/item/wirecutters, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is added."), - //13 - list("key"=/obj/item/stack/cable_coil, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The hydraulic systems are active."), - //14 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_WRENCH, - "desc"="The hydraulic systems are connected."), - //15 - list("key"=TOOL_WRENCH, - "desc"="The hydraulic systems are disconnected.") - ) - -/datum/construction/reversible/mecha/firefighter/action(atom/used_atom,mob/user as mob) - return check_step(used_atom,user) - -/datum/construction/reversible/mecha/firefighter/custom_action(index, diff, atom/used_atom, mob/user) - if(!..()) - return 0 - - //TODO: better messages. - switch(index) - if(15) - user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") - holder.icon_state = "fireripley1" - if(14) - if(diff==FORWARD) - user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") - holder.icon_state = "fireripley2" - else - user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") - holder.icon_state = "fireripley0" - if(13) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") - holder.icon_state = "fireripley3" - else - user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") - holder.icon_state = "fireripley1" - if(12) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") - holder.icon_state = "fireripley4" - else - user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") - var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) - coil.amount = 4 - holder.icon_state = "fireripley2" - if(11) - if(diff==FORWARD) - user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") - qdel(used_atom) - holder.icon_state = "fireripley5" - else - user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") - holder.icon_state = "fireripley3" - if(10) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - holder.icon_state = "fireripley6" - else - user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") - new /obj/item/circuitboard/mecha/ripley/main(get_turf(holder)) - holder.icon_state = "fireripley4" - if(9) - if(diff==FORWARD) - user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") - qdel(used_atom) - holder.icon_state = "fireripley7" - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - holder.icon_state = "fireripley5" - if(8) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - holder.icon_state = "fireripley8" - else - user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") - new /obj/item/circuitboard/mecha/ripley/peripherals(get_turf(holder)) - holder.icon_state = "fireripley6" - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") - holder.icon_state = "fireripley9" - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - holder.icon_state = "fireripley7" - - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - holder.icon_state = "fireripley10" - else - user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") - var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "fireripley8" - if(5) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") - holder.icon_state = "fireripley11" - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - holder.icon_state = "fireripley9" - if(4) - if(diff==FORWARD) - user.visible_message("[user] starts to install the external armor layer to the [holder].", "You start to install the external armor layer to the [holder].") - holder.icon_state = "fireripley12" - else - user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") - holder.icon_state = "fireripley10" - if(3) - if(diff==FORWARD) - user.visible_message("[user] installs the external reinforced armor layer to the [holder].", "You install the external reinforced armor layer to the [holder].") - holder.icon_state = "fireripley13" - else - user.visible_message("[user] removes the external armor from the [holder].", "You remove the external armor from the [holder].") - var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "fireripley11" - if(2) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - holder.icon_state = "fireripley14" - else - user.visible_message("[user] pries external armor layer from the [holder].", "You pry external armor layer from the [holder].") - var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "fireripley12" - if(1) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to the [holder].", "You weld the external armor layer to the [holder].") - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - holder.icon_state = "fireripley13" - return 1 - -/datum/construction/reversible/mecha/firefighter/spawn_result() - ..() - feedback_inc("mecha_firefighter_created",1) - return - - - -/datum/construction/mecha/honker_chassis - steps = list(list("key"=/obj/item/mecha_parts/part/honker_torso),//1 - list("key"=/obj/item/mecha_parts/part/honker_left_arm),//2 - list("key"=/obj/item/mecha_parts/part/honker_right_arm),//3 - list("key"=/obj/item/mecha_parts/part/honker_left_leg),//4 - list("key"=/obj/item/mecha_parts/part/honker_right_leg),//5 - list("key"=/obj/item/mecha_parts/part/honker_head) - ) - -/datum/construction/mecha/honker_chassis/action(atom/used_atom,mob/user as mob) - return check_all_steps(used_atom,user) - -/datum/construction/mecha/honker_chassis/custom_action(step, atom/used_atom, mob/user) - user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") - holder.overlays += used_atom.icon_state+"+o" - qdel(used_atom) - return 1 - -/datum/construction/mecha/honker_chassis/spawn_result() - var/obj/item/mecha_parts/chassis/const_holder = holder - const_holder.construct = new /datum/construction/mecha/honker(const_holder) - const_holder.density = 1 - qdel(src) - return - - -/datum/construction/mecha/honker - result = "/obj/mecha/combat/honker" - steps = list(list("key"=/obj/item/bikehorn),//1 - list("key"=/obj/item/clothing/shoes/clown_shoes),//2 - list("key"=/obj/item/bikehorn),//3 - list("key"=/obj/item/clothing/mask/gas/clown_hat),//4 - list("key"=/obj/item/bikehorn),//5 - list("key"=/obj/item/circuitboard/mecha/honker/targeting),//6 - list("key"=/obj/item/bikehorn),//7 - list("key"=/obj/item/circuitboard/mecha/honker/peripherals),//8 - list("key"=/obj/item/bikehorn),//9 - list("key"=/obj/item/circuitboard/mecha/honker/main),//10 - list("key"=/obj/item/bikehorn),//11 - ) - -/datum/construction/mecha/honker/action(atom/used_atom,mob/user as mob) - return check_step(used_atom,user) - -/datum/construction/mecha/honker/custom_action(step, atom/used_atom, mob/user) - if(!..()) - return 0 - - if(istype(used_atom, /obj/item/bikehorn)) - playsound(holder, 'sound/items/bikehorn.ogg', 50, 1) - user.visible_message("HONK!") - - //TODO: better messages. - switch(step) - if(10) - user.visible_message("[user] installs the central control module into the [holder].", "You install the central control module into the [holder].") - qdel(used_atom) - if(8) - user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") - qdel(used_atom) - if(6) - user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") - qdel(used_atom) - if(4) - user.visible_message("[user] puts clown wig and mask on the [holder].", "You put clown wig and mask on the [holder].") - qdel(used_atom) - if(2) - user.visible_message("[user] puts clown boots on the [holder].", "You put clown boots on the [holder].") - qdel(used_atom) - return 1 - -/datum/construction/mecha/honker/spawn_result() - ..() - feedback_inc("mecha_honker_created",1) - return - -/datum/construction/mecha/reticence_chassis - steps = list(list("key"=/obj/item/mecha_parts/part/reticence_torso),//1 - list("key"=/obj/item/mecha_parts/part/reticence_left_arm),//2 - list("key"=/obj/item/mecha_parts/part/reticence_right_arm),//3 - list("key"=/obj/item/mecha_parts/part/reticence_left_leg),//4 - list("key"=/obj/item/mecha_parts/part/reticence_right_leg),//5 - list("key"=/obj/item/mecha_parts/part/reticence_head) - ) - -/datum/construction/mecha/reticence_chassis/action(atom/used_atom,mob/user as mob) - return check_all_steps(used_atom,user) - -/datum/construction/mecha/reticence_chassis/custom_action(step, atom/used_atom, mob/user) - user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") - holder.overlays += used_atom.icon_state + "+o" - qdel(used_atom) - return 1 - -/datum/construction/mecha/reticence_chassis/spawn_result() - var/obj/item/mecha_parts/chassis/const_holder = holder - const_holder.construct = new /datum/construction/mecha/reticence(const_holder) - const_holder.density = 1 - qdel(src) - return - -/datum/construction/mecha/reticence - result = "/obj/mecha/combat/reticence" - steps = list(list("key"=/obj/effect/dummy/mecha_emote_step),//1 - list("key"=/obj/item/clothing/suit/suspenders),//2 - list("key"=/obj/effect/dummy/mecha_emote_step),//3 - list("key"=/obj/item/clothing/mask/gas/mime),//4 - list("key"=/obj/effect/dummy/mecha_emote_step),//5 - list("key"=/obj/item/clothing/head/beret),//6 - list("key"=/obj/item/circuitboard/mecha/reticence/targeting),//7 - list("key"=/obj/item/circuitboard/mecha/reticence/peripherals),//8 - list("key"=/obj/item/circuitboard/mecha/reticence/main),//9 - ) - -/datum/construction/mecha/reticence/action(atom/used_atom,mob/user) - return check_step(used_atom,user) - -/datum/construction/mecha/reticence/custom_action(step, atom/used_atom, mob/user) - if(!..()) - return 0 - - if(istype(used_atom, /obj/effect/dummy/mecha_emote_step)) - var/obj/effect/dummy/mecha_emote_step/E = used_atom - holder.visible_message("[holder] likewise [E.emote]") - qdel(used_atom) - - //TODO: better messages. - switch(step) - if(9) - user.visible_message("[user] installs the central control module into the [holder].", "You install the central control module into the [holder].") - qdel(used_atom) - if(8) - user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") - qdel(used_atom) - if(7) - user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") - qdel(used_atom) - if(6) - user.visible_message("[user] puts beret on the [holder].", "You put beret on the [holder].") - qdel(used_atom) - if(4) - user.visible_message("[user] puts mime mask on the [holder].", "You put mime mask on the [holder].") - qdel(used_atom) - if(2) - user.visible_message("[user] puts suspenders on the [holder].", "You put suspenders on the [holder].") - qdel(used_atom) - return 1 - -/datum/construction/mecha/reticence/spawn_result() - ..() - feedback_inc("mecha_reticence_created",1) - return - -/datum/construction/mecha/durand_chassis - steps = list(list("key"=/obj/item/mecha_parts/part/durand_torso),//1 - list("key"=/obj/item/mecha_parts/part/durand_left_arm),//2 - list("key"=/obj/item/mecha_parts/part/durand_right_arm),//3 - list("key"=/obj/item/mecha_parts/part/durand_left_leg),//4 - list("key"=/obj/item/mecha_parts/part/durand_right_leg),//5 - list("key"=/obj/item/mecha_parts/part/durand_head) - ) - -/datum/construction/mecha/durand_chassis/custom_action(step, atom/used_atom, mob/user) - user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") - holder.overlays += used_atom.icon_state+"+o" - qdel(used_atom) - return 1 - -/datum/construction/mecha/durand_chassis/action(atom/used_atom,mob/user as mob) - return check_all_steps(used_atom,user) - -/datum/construction/mecha/durand_chassis/spawn_result() - var/obj/item/mecha_parts/chassis/const_holder = holder - const_holder.construct = new /datum/construction/reversible/mecha/durand(const_holder) - const_holder.icon = 'icons/mecha/mech_construction.dmi' - const_holder.icon_state = "durand0" - const_holder.density = 1 - qdel(src) - return - -/datum/construction/reversible/mecha/durand - result = "/obj/mecha/combat/durand" - steps = list( - //1 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="External armor is wrenched."), - //2 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="External armor is installed."), - //3 - list("key"=/obj/item/mecha_parts/part/durand_armor, - "backkey"=TOOL_WELDER, - "desc"="Internal armor is welded."), - //4 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="Internal armor is wrenched."), - //5 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="Internal armor is installed."), - //6 - list("key"=/obj/item/stack/sheet/metal, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Super capacitor is secured."), - //7 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Super capacitor is installed."), - //8 - list("key"=/obj/item/stock_parts/capacitor/super, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Phasic scanner module is secured."), - //9 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Phasic scanner module is installed."), - //10 - list("key"=/obj/item/stock_parts/scanning_module/phasic, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Scanning module is secured."), - //11 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Scanning module is installed."), - //12 - list("key"=/obj/item/circuitboard/mecha/durand/targeting, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Peripherals control module is secured."), - //13 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Peripherals control module is installed."), - //14 - list("key"=/obj/item/circuitboard/mecha/durand/peripherals, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Central control module is secured."), - //15 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Central control module is installed."), - //16 - list("key"=/obj/item/circuitboard/mecha/durand/main, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is adjusted."), - //17 - list("key"=/obj/item/wirecutters, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is added."), - //18 - list("key"=/obj/item/stack/cable_coil, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The hydraulic systems are active."), - //19 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_WRENCH, - "desc"="The hydraulic systems are connected."), - //20 - list("key"=TOOL_WRENCH, - "desc"="The hydraulic systems are disconnected.") - ) - - -/datum/construction/reversible/mecha/durand/action(atom/used_atom,mob/user as mob) - return check_step(used_atom,user) - -/datum/construction/reversible/mecha/durand/custom_action(index, diff, atom/used_atom, mob/user) - if(!..()) - return 0 - - //TODO: better messages. - switch(index) - if(20) - user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") - holder.icon_state = "durand1" - if(19) - if(diff==FORWARD) - user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") - holder.icon_state = "durand2" - else - user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") - holder.icon_state = "durand0" - if(18) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") - holder.icon_state = "durand3" - else - user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") - holder.icon_state = "durand1" - if(17) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") - holder.icon_state = "durand4" - else - user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") - var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) - coil.amount = 4 - holder.icon_state = "durand2" - if(16) - if(diff==FORWARD) - user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") - qdel(used_atom) - holder.icon_state = "durand5" - else - user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") - holder.icon_state = "durand3" - if(15) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - holder.icon_state = "durand6" - else - user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") - new /obj/item/circuitboard/mecha/durand/main(get_turf(holder)) - holder.icon_state = "durand4" - if(14) - if(diff==FORWARD) - user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") - qdel(used_atom) - holder.icon_state = "durand7" - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - holder.icon_state = "durand5" - if(13) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - holder.icon_state = "durand8" - else - user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") - new /obj/item/circuitboard/mecha/durand/peripherals(get_turf(holder)) - holder.icon_state = "durand6" - if(12) - if(diff==FORWARD) - user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") - qdel(used_atom) - holder.icon_state = "durand9" - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - holder.icon_state = "durand7" - if(11) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - holder.icon_state = "durand10" - else - user.visible_message("[user] removes the weapon control module from the [holder].", "You remove the weapon control module from the [holder].") - new /obj/item/circuitboard/mecha/durand/targeting(get_turf(holder)) - holder.icon_state = "durand8" - if(10) - if(diff==FORWARD) - user.visible_message("[user] installs phasic scanner module to the [holder].", "You install phasic scanner module to the [holder].") - qdel(used_atom) - holder.icon_state = "durand11" - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - holder.icon_state = "durand9" - if(9) - if(diff==FORWARD) - user.visible_message("[user] secures the phasic scanner module.", "You secure the phasic scanner module.") - holder.icon_state = "durand12" - else - user.visible_message("[user] removes the phasic scanner module from the [holder].", "You remove the phasic scanner module from the [holder].") - new /obj/item/stock_parts/scanning_module/phasic(get_turf(holder)) - holder.icon_state = "durand10" - if(8) - if(diff==FORWARD) - user.visible_message("[user] installs super capacitor to the [holder].", "You install super capacitor to the [holder].") - qdel(used_atom) - holder.icon_state = "durand13" - else - user.visible_message("[user] unfastens the phasic scanner module.", "You unfasten the phasic scanner module.") - holder.icon_state = "durand11" - if(7) - if(diff==FORWARD) - user.visible_message("[user] secures the super capacitor.", "You secure the super capacitor.") - holder.icon_state = "durand14" - else - user.visible_message("[user] removes the super capacitor from the [holder].", "You remove the super capacitor from the [holder].") - new /obj/item/stock_parts/capacitor/super(get_turf(holder)) - holder.icon_state = "durand12" - if(6) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") - holder.icon_state = "durand15" - else - user.visible_message("[user] unfastens the super capacitor.", "You unfasten the super capacitor.") - holder.icon_state = "durand13" - if(5) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - holder.icon_state = "durand16" - else - user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") - var/obj/item/stack/sheet/metal/MS = new /obj/item/stack/sheet/metal(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "durand14" - if(4) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") - holder.icon_state = "durand17" - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - holder.icon_state = "durand15" - if(3) - if(diff==FORWARD) - user.visible_message("[user] installs Durand Armor Plates to the [holder].", "You install Durand Armor Plates to the [holder].") - qdel(used_atom) - holder.icon_state = "durand18" - else - user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") - holder.icon_state = "durand16" - if(2) - if(diff==FORWARD) - user.visible_message("[user] secures Durand Armor Plates.", "You secure Durand Armor Plates.") - holder.icon_state = "durand19" - else - user.visible_message("[user] pries Durand Armor Plates from the [holder].", "You pry Durand Armor Plates from the [holder].") - new /obj/item/mecha_parts/part/durand_armor(get_turf(holder)) - holder.icon_state = "durand17" - if(1) - if(diff==FORWARD) - user.visible_message("[user] welds Durand Armor Plates to the [holder].", "You weld Durand Armor Plates to the [holder].") - else - user.visible_message("[user] unfastens Durand Armor Plates.", "You unfasten Durand Armor Plates.") - holder.icon_state = "durand18" - return 1 - -/datum/construction/reversible/mecha/durand/spawn_result() - ..() - feedback_inc("mecha_durand_created",1) - return - -//PHAZON - -/datum/construction/mecha/phazon_chassis - result = "/obj/mecha/combat/phazon" - steps = list(list("key"=/obj/item/mecha_parts/part/phazon_torso),//1 - list("key"=/obj/item/mecha_parts/part/phazon_left_arm),//2 - list("key"=/obj/item/mecha_parts/part/phazon_right_arm),//3 - list("key"=/obj/item/mecha_parts/part/phazon_left_leg),//4 - list("key"=/obj/item/mecha_parts/part/phazon_right_leg),//5 - list("key"=/obj/item/mecha_parts/part/phazon_head) - ) - -/datum/construction/mecha/phazon_chassis/custom_action(step, atom/used_atom, mob/user) - user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") - holder.overlays += used_atom.icon_state+"+o" - qdel(used_atom) - return 1 - -/datum/construction/mecha/phazon_chassis/action(atom/used_atom,mob/user as mob) - return check_all_steps(used_atom,user) - -/datum/construction/mecha/phazon_chassis/spawn_result() - var/obj/item/mecha_parts/chassis/const_holder = holder - const_holder.construct = new /datum/construction/reversible/mecha/phazon(const_holder) - const_holder.icon = 'icons/mecha/mech_construction.dmi' - const_holder.icon_state = "phazon0" - const_holder.density = 1 - qdel(src) - return - -/datum/construction/reversible/mecha/phazon - result = "/obj/mecha/combat/phazon" - steps = list( - //1 - list("key"=/obj/item/assembly/signaler/anomaly, - "backkey"=null, //Cannot remove the anomaly core once it's in - "desc"="Anomaly core socket is open and awaiting connection."), - //2 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="External armor is wrenched."), - //3 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="External armor is installed."), - //4 - list("key"=/obj/item/mecha_parts/part/phazon_armor, - "backkey"=TOOL_WELDER, - "desc"="Phase armor is welded."), - //5 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="Phase armor is wrenched."), - //6 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="Phase armor is installed."), - //7 - list("key"=/obj/item/stack/sheet/plasteel, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The bluespace crystal is engaged."), - //8 - list("key"=TOOL_SCREWDRIVER, - "backkey"=/obj/item/wirecutters, - "desc"="The bluespace crystal is connected."), - //9 - list("key"=/obj/item/stack/cable_coil, - "backkey"=TOOL_CROWBAR, - "desc"="The bluespace crystal is installed."), - //10 - list("key"=/obj/item/stack/ore/bluespace_crystal, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Super capacitor is secured."), - //11 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Super capacitor is installed."), - //12 - list("key"=/obj/item/stock_parts/capacitor/super, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Phasic scanner module is secured."), - //13 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Phasic scanner module is installed."), - //14 - list("key"=/obj/item/stock_parts/scanning_module/phasic, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Scanning module is secured."), - //15 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Scanning module is installed."), - //16 - list("key"=/obj/item/circuitboard/mecha/phazon/targeting, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Peripherals control module is secured."), - //17 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Peripherals control module is installed"), - //18 - list("key"=/obj/item/circuitboard/mecha/phazon/peripherals, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Central control module is secured."), - //19 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Central control module is installed."), - //20 - list("key"=/obj/item/circuitboard/mecha/phazon/main, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is adjusted."), - //21 - list("key"=/obj/item/wirecutters, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is added."), - //22 - list("key"=/obj/item/stack/cable_coil, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The hydraulic systems are active."), - //23 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_WRENCH, - "desc"="The hydraulic systems are connected."), - //24 - list("key"=TOOL_WRENCH, - "desc"="The hydraulic systems are disconnected.") - ) - - -/datum/construction/reversible/mecha/phazon/action(atom/used_atom,mob/user as mob) - return check_step(used_atom,user) - -/datum/construction/reversible/mecha/phazon/custom_action(index, diff, atom/used_atom, mob/user) - if(!..()) - return 0 - - //TODO: better messages. - switch(index) - if(24) - user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") - holder.icon_state = "phazon1" - if(23) - if(diff==FORWARD) - user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") - holder.icon_state = "phazon2" - else - user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") - holder.icon_state = "phazon0" - if(22) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") - holder.icon_state = "phazon3" - else - user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") - holder.icon_state = "phazon1" - if(21) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") - holder.icon_state = "phazon4" - else - user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") - var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) - coil.amount = 4 - holder.icon_state = "phazon2" - if(20) - if(diff==FORWARD) - user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") - qdel(used_atom) - holder.icon_state = "phazon5" - else - user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") - holder.icon_state = "phazon3" - if(19) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - holder.icon_state = "phazon6" - else - user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") - new /obj/item/circuitboard/mecha/phazon/main(get_turf(holder)) - holder.icon_state = "phazon4" - if(18) - if(diff==FORWARD) - user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") - qdel(used_atom) - holder.icon_state = "phazon7" - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - holder.icon_state = "phazon5" - if(17) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - holder.icon_state = "phazon8" - else - user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") - new /obj/item/circuitboard/mecha/phazon/peripherals(get_turf(holder)) - holder.icon_state = "phazon6" - if(16) - if(diff==FORWARD) - user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") - qdel(used_atom) - holder.icon_state = "phazon9" - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - holder.icon_state = "phazon7" - if(15) - if(diff==FORWARD) - user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") - holder.icon_state = "phazon10" - else - user.visible_message("[user] removes the weapon control module from the [holder].", "You remove the weapon control module from the [holder].") - new /obj/item/circuitboard/mecha/phazon/targeting(get_turf(holder)) - holder.icon_state = "phazon8" - if(14) - if(diff==FORWARD) - user.visible_message("[user] installs phasic scanner module to the [holder].", "You install phasic scanner module to the [holder].") - qdel(used_atom) - holder.icon_state = "phazon11" - else - user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") - holder.icon_state = "phazon9" - if(13) - if(diff==FORWARD) - user.visible_message("[user] secures the phasic scanner module.", "You secure the phasic scanner module.") - holder.icon_state = "phazon12" - else - user.visible_message("[user] removes the phasic scanner module from the [holder].", "You remove the phasic scanner module from the [holder].") - new /obj/item/stock_parts/scanning_module/phasic(get_turf(holder)) - holder.icon_state = "phazon10" - if(12) - if(diff==FORWARD) - user.visible_message("[user] installs super capacitor to the [holder].", "You install super capacitor to the [holder].") - qdel(used_atom) - holder.icon_state = "phazon13" - else - user.visible_message("[user] unfastens the phasic scanner module.", "You unfasten the phasic scanner module.") - holder.icon_state = "phazon11" - if(11) - if(diff==FORWARD) - user.visible_message("[user] secures the super capacitor.", "You secure the super capacitor.") - holder.icon_state = "phazon14" - else - user.visible_message("[user] removes the super capacitor from the [holder].", "You remove the super capacitor from the [holder].") - new /obj/item/stock_parts/capacitor/super(get_turf(holder)) - holder.icon_state = "phazon12" - if(10) - if(diff==FORWARD) - user.visible_message("[user] installs the bluespace crystal.", "You install the bluespace crystals.") - holder.icon_state = "phazon15" - else - user.visible_message("[user] unsecures the super capacitor from the [holder].", "You unsecure the super capacitor from the [holder].") - holder.icon_state = "phazon13" - if(9) - if(diff==FORWARD) - user.visible_message("[user] connects the bluespace crystal.", "You connect the bluespace crystals.") - holder.icon_state = "phazon16" - else - user.visible_message("[user] removes the bluespace crystal from the [holder].", "You remove the bluespace crystal from the [holder].") - new /obj/item/stack/ore/bluespace_crystal(get_turf(holder), new_amount = 5) - holder.icon_state = "phazon14" - if(8) - if(diff==FORWARD) - user.visible_message("[user] engages the bluespace crystal.", "You engage the bluespace crystals.") - holder.icon_state = "phazon17" - else - user.visible_message("[user] disconnects the bluespace crystal from the [holder].", "You disconnect the bluespace crystal from the [holder].") - holder.icon_state = "phazon15" - if(7) - if(diff==FORWARD) - user.visible_message("[user] installs the phase armor layer to the [holder].", "You install the phase armor layer to the [holder].") - holder.icon_state = "phazon18" - else - user.visible_message("[user] disengages the bluespace crystal.", "You disengage the bluespace crystals.") - holder.icon_state = "phazon16" - if(6) - if(diff==FORWARD) - user.visible_message("[user] secures the phase armor layer.", "You secure the phase armor layer.") - holder.icon_state = "phazon19" - else - user.visible_message("[user] pries the phase armor layer from the [holder].", "You pry the phase armor layer from the [holder].") - var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "phazon17" - if(5) - if(diff==FORWARD) - user.visible_message("[user] welds the phase armor layer to the [holder].", "You weld the phase armor layer to the [holder].") - holder.icon_state = "phazon20" - else - user.visible_message("[user] unfastens the phase armor layer.", "You unfasten the phase armor layer.") - holder.icon_state = "phazon18" - if(4) - if(diff==FORWARD) - user.visible_message("[user] installs Phazon Armor Plates to the [holder].", "You install Phazon Armor Plates to the [holder].") - qdel(used_atom) - holder.icon_state = "phazon21" - else - user.visible_message("[user] cuts phase armor layer from the [holder].", "You cut the phase armor layer from the [holder].") - holder.icon_state = "phazon19" - if(3) - if(diff==FORWARD) - user.visible_message("[user] secures Phazon Armor Plates.", "You secure Phazon Armor Plates.") - holder.icon_state = "phazon22" - else - user.visible_message("[user] pries Phazon Armor Plates from the [holder].", "You pry Phazon Armor Plates from the [holder].") - new /obj/item/mecha_parts/part/phazon_armor(get_turf(holder)) - holder.icon_state = "phazon20" - if(2) - if(diff==FORWARD) - user.visible_message("[user] welds Phazon Armor Plates to the [holder].", "You weld Phazon Armor Plates to the [holder].") - else - user.visible_message("[user] unfastens Phazon Armor Plates.", "You unfasten Phazon Armor Plates.") - holder.icon_state = "phazon21" - if(1) - if(diff==FORWARD) - user.visible_message("[user] carefully inserts the anomaly core into \the [holder] and secures it.", "You slowly place the anomaly core into its socket and close its chamber.") - qdel(used_atom) - return 1 - -/datum/construction/reversible/mecha/phazon/spawn_result() - ..() - feedback_inc("mecha_phazon_created",1) - return - -//ODYSSEUS - -/datum/construction/mecha/odysseus_chassis - steps = list(list("key"=/obj/item/mecha_parts/part/odysseus_torso),//1 - list("key"=/obj/item/mecha_parts/part/odysseus_head),//2 - list("key"=/obj/item/mecha_parts/part/odysseus_left_arm),//3 - list("key"=/obj/item/mecha_parts/part/odysseus_right_arm),//4 - list("key"=/obj/item/mecha_parts/part/odysseus_left_leg),//5 - list("key"=/obj/item/mecha_parts/part/odysseus_right_leg)//6 - ) - -/datum/construction/mecha/odysseus_chassis/custom_action(step, atom/used_atom, mob/user) - user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") - holder.overlays += used_atom.icon_state+"+o" - qdel(used_atom) - return 1 - -/datum/construction/mecha/odysseus_chassis/action(atom/used_atom,mob/user as mob) - return check_all_steps(used_atom,user) - -/datum/construction/mecha/odysseus_chassis/spawn_result() - var/obj/item/mecha_parts/chassis/const_holder = holder - const_holder.construct = new /datum/construction/reversible/mecha/odysseus(const_holder) - const_holder.icon = 'icons/mecha/mech_construction.dmi' - const_holder.icon_state = "odysseus0" - const_holder.density = 1 - qdel(src) - return - - -/datum/construction/reversible/mecha/odysseus - result = "/obj/mecha/medical/odysseus" - steps = list( - //1 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="External armor is wrenched."), - //2 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="External armor is installed."), - //3 - list("key"=/obj/item/stack/sheet/plasteel, - "backkey"=TOOL_WELDER, - "desc"="Internal armor is welded."), - //4 - list("key"=TOOL_WELDER, - "backkey"=TOOL_WRENCH, - "desc"="Internal armor is wrenched."), - //5 - list("key"=TOOL_WRENCH, - "backkey"=TOOL_CROWBAR, - "desc"="Internal armor is installed."), - //6 - list("key"=/obj/item/stack/sheet/metal, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Peripherals control module is secured."), - //7 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Peripherals control module is installed."), - //8 - list("key"=/obj/item/circuitboard/mecha/odysseus/peripherals, - "backkey"=TOOL_SCREWDRIVER, - "desc"="Central control module is secured."), - //9 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_CROWBAR, - "desc"="Central control module is installed."), - //10 - list("key"=/obj/item/circuitboard/mecha/odysseus/main, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is adjusted."), - //11 - list("key"=/obj/item/wirecutters, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The wiring is added."), - //12 - list("key"=/obj/item/stack/cable_coil, - "backkey"=TOOL_SCREWDRIVER, - "desc"="The hydraulic systems are active."), - //13 - list("key"=TOOL_SCREWDRIVER, - "backkey"=TOOL_WRENCH, - "desc"="The hydraulic systems are connected."), - //14 - list("key"=TOOL_WRENCH, - "desc"="The hydraulic systems are disconnected.") - ) - -/datum/construction/reversible/mecha/odysseus/action(atom/used_atom,mob/user as mob) - return check_step(used_atom,user) - -/datum/construction/reversible/mecha/odysseus/custom_action(index, diff, atom/used_atom, mob/user) - if(!..()) - return 0 - - //TODO: better messages. - switch(index) - if(14) - user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") - holder.icon_state = "odysseus1" - if(13) - if(diff==FORWARD) - user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") - holder.icon_state = "odysseus2" - else - user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") - holder.icon_state = "odysseus0" - if(12) - if(diff==FORWARD) - user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") - holder.icon_state = "odysseus3" - else - user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") - holder.icon_state = "odysseus1" - if(11) - if(diff==FORWARD) - user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") - holder.icon_state = "odysseus4" - else - user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") - var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) - coil.amount = 4 - holder.icon_state = "odysseus2" - if(10) - if(diff==FORWARD) - user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") - qdel(used_atom) - holder.icon_state = "odysseus5" - else - user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") - holder.icon_state = "odysseus3" - if(9) - if(diff==FORWARD) - user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") - holder.icon_state = "odysseus6" - else - user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") - new /obj/item/circuitboard/mecha/odysseus/main(get_turf(holder)) - holder.icon_state = "odysseus4" - if(8) - if(diff==FORWARD) - user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") - qdel(used_atom) - holder.icon_state = "odysseus7" - else - user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") - holder.icon_state = "odysseus5" - if(7) - if(diff==FORWARD) - user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") - holder.icon_state = "odysseus8" - else - user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") - new /obj/item/circuitboard/mecha/odysseus/peripherals(get_turf(holder)) - holder.icon_state = "odysseus6" - if(6) - if(diff==FORWARD) - user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") - holder.icon_state = "odysseus9" - else - user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") - holder.icon_state = "odysseus7" - if(5) - if(diff==FORWARD) - user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") - holder.icon_state = "odysseus10" - else - user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") - var/obj/item/stack/sheet/metal/MS = new /obj/item/stack/sheet/metal(get_turf(holder)) - MS.amount = 5 - holder.icon_state = "odysseus8" - if(4) - if(diff==FORWARD) - user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") - holder.icon_state = "odysseus11" - else - user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") - holder.icon_state = "odysseus9" - if(3) - if(diff==FORWARD) - user.visible_message("[user] installs [used_atom] layer to the [holder].", "You install the external reinforced armor layer to the [holder].") - - holder.icon_state = "odysseus12" - else - user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") - holder.icon_state = "odysseus10" - if(2) - if(diff==FORWARD) - user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") - holder.icon_state = "odysseus13" - else - var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) - MS.amount = 5 - user.visible_message("[user] pries [MS] from the [holder].", "You pry [MS] from the [holder].") - holder.icon_state = "odysseus11" - if(1) - if(diff==FORWARD) - user.visible_message("[user] welds the external armor layer to the [holder].", "You weld the external armor layer to the [holder].") - holder.icon_state = "odysseus14" - else - user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") - holder.icon_state = "odysseus12" - return 1 - -/datum/construction/reversible/mecha/odysseus/spawn_result() - ..() - feedback_inc("mecha_odysseus_created",1) - return +#define STANDARD_STACK_AMOUNT 5 + +//////////////////////////////// +///// Construction datums ////// +//////////////////////////////// + +/datum/construction/mecha/custom_action(step, atom/used_atom, mob/user) + if(istype(used_atom, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = used_atom + if(C.use(4)) + playsound(holder, C.usesound, 50, 1) + else + to_chat(user, ("There's not enough cable to finish the task.")) + return 0 + else if(istype(used_atom, /obj/item/stack)) + var/obj/item/stack/S = used_atom + if(S.amount < STANDARD_STACK_AMOUNT) + to_chat(user, ("There's not enough material in this stack.")) + return 0 + else + S.use(STANDARD_STACK_AMOUNT) + else + return ..() + +/datum/construction/reversible/mecha/custom_action(index as num, diff as num, atom/used_atom, mob/user as mob) + if(istype(used_atom, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = used_atom + if(C.use(4)) + playsound(holder, C.usesound, 50, 1) + else + to_chat(user, ("There's not enough cable to finish the task.")) + return 0 + else if(istype(used_atom, /obj/item/stack)) + var/obj/item/stack/S = used_atom + if(S.amount < STANDARD_STACK_AMOUNT) + to_chat(user, ("There's not enough material in this stack.")) + return 0 + else + S.use(STANDARD_STACK_AMOUNT) + else if(isitem(used_atom)) + var/obj/item/I = used_atom + if(I.tool_behaviour in CONSTRUCTION_TOOL_BEHAVIOURS) + if(!I.use_tool(holder, user, 0, volume = I.tool_volume)) + return 0 + return 1 + + +/datum/construction/mecha/ripley_chassis + steps = list(list("key"=/obj/item/mecha_parts/part/ripley_torso),//1 + list("key"=/obj/item/mecha_parts/part/ripley_left_arm),//2 + list("key"=/obj/item/mecha_parts/part/ripley_right_arm),//3 + list("key"=/obj/item/mecha_parts/part/ripley_left_leg),//4 + list("key"=/obj/item/mecha_parts/part/ripley_right_leg)//5 + ) + +/datum/construction/mecha/ripley_chassis/custom_action(step, atom/used_atom, mob/user) + user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") + holder.overlays += used_atom.icon_state+"+o" + qdel(used_atom) + return 1 + +/datum/construction/mecha/ripley_chassis/action(atom/used_atom,mob/user as mob) + return check_all_steps(used_atom,user) + +/datum/construction/mecha/ripley_chassis/spawn_result() + var/obj/item/mecha_parts/chassis/const_holder = holder + const_holder.construct = new /datum/construction/reversible/mecha/ripley(const_holder) + const_holder.icon = 'icons/mecha/mech_construction.dmi' + const_holder.icon_state = "ripley0" + const_holder.density = 1 + const_holder.overlays.len = 0 + qdel(src) + return + + +/datum/construction/reversible/mecha/ripley + result = "/obj/mecha/working/ripley" + taskpath = /datum/job_objective/make_ripley + steps = list( + //1 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="External armor is wrenched."), + //2 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="External armor is installed."), + //3 + list("key"=/obj/item/stack/sheet/plasteel, + "backkey"=TOOL_WELDER, + "desc"="Internal armor is welded."), + //4 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="Internal armor is wrenched."), + //5 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="Internal armor is installed."), + //6 + list("key"=/obj/item/stack/sheet/metal, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Peripherals control module is secured."), + //7 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Peripherals control module is installed."), + //8 + list("key"=/obj/item/circuitboard/mecha/ripley/peripherals, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Central control module is secured."), + //9 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Central control module is installed."), + //10 + list("key"=/obj/item/circuitboard/mecha/ripley/main, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is adjusted."), + //11 + list("key"=/obj/item/wirecutters, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is added."), + //12 + list("key"=/obj/item/stack/cable_coil, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The hydraulic systems are active."), + //13 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_WRENCH, + "desc"="The hydraulic systems are connected."), + //14 + list("key"=TOOL_WRENCH, + "desc"="The hydraulic systems are disconnected.") + ) + +/datum/construction/reversible/mecha/ripley/action(atom/used_atom,mob/user as mob) + return check_step(used_atom,user) + +/datum/construction/reversible/mecha/ripley/custom_action(index, diff, atom/used_atom, mob/user) + if(!..()) + return 0 + + //TODO: better messages. + switch(index) + if(14) + user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") + holder.icon_state = "ripley1" + if(13) + if(diff==FORWARD) + user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") + holder.icon_state = "ripley2" + else + user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") + holder.icon_state = "ripley0" + if(12) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") + holder.icon_state = "ripley3" + else + user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") + holder.icon_state = "ripley1" + if(11) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") + holder.icon_state = "ripley4" + else + user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") + var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) + coil.amount = 4 + holder.icon_state = "ripley2" + if(10) + if(diff==FORWARD) + user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") + qdel(used_atom) + holder.icon_state = "ripley5" + else + user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") + holder.icon_state = "ripley3" + if(9) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + holder.icon_state = "ripley6" + else + user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") + new /obj/item/circuitboard/mecha/ripley/main(get_turf(holder)) + holder.icon_state = "ripley4" + if(8) + if(diff==FORWARD) + user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") + qdel(used_atom) + holder.icon_state = "ripley7" + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + holder.icon_state = "ripley5" + if(7) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + holder.icon_state = "ripley8" + else + user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") + new /obj/item/circuitboard/mecha/ripley/peripherals(get_turf(holder)) + holder.icon_state = "ripley6" + if(6) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") + holder.icon_state = "ripley9" + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + holder.icon_state = "ripley7" + if(5) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + holder.icon_state = "ripley10" + else + user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") + var/obj/item/stack/sheet/metal/MS = new /obj/item/stack/sheet/metal(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "ripley8" + if(4) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") + holder.icon_state = "ripley11" + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + holder.icon_state = "ripley9" + if(3) + if(diff==FORWARD) + user.visible_message("[user] installs the external reinforced armor layer to the [holder].", "You install the external reinforced armor layer to the [holder].") + holder.icon_state = "ripley12" + else + user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") + holder.icon_state = "ripley10" + if(2) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + holder.icon_state = "ripley13" + else + user.visible_message("[user] pries external armor layer from the [holder].", "You pry external armor layer from the [holder].") + var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "ripley11" + if(1) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to the [holder].", "You weld the external armor layer to the [holder].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + holder.icon_state = "ripley12" + return 1 + +/datum/construction/reversible/mecha/ripley/spawn_result() + ..() + feedback_inc("mecha_ripley_created",1) + return + + + +/datum/construction/mecha/gygax_chassis + steps = list(list("key"=/obj/item/mecha_parts/part/gygax_torso),//1 + list("key"=/obj/item/mecha_parts/part/gygax_left_arm),//2 + list("key"=/obj/item/mecha_parts/part/gygax_right_arm),//3 + list("key"=/obj/item/mecha_parts/part/gygax_left_leg),//4 + list("key"=/obj/item/mecha_parts/part/gygax_right_leg),//5 + list("key"=/obj/item/mecha_parts/part/gygax_head) + ) + +/datum/construction/mecha/gygax_chassis/custom_action(step, atom/used_atom, mob/user) + user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") + holder.overlays += used_atom.icon_state+"+o" + qdel(used_atom) + return 1 + +/datum/construction/mecha/gygax_chassis/action(atom/used_atom,mob/user as mob) + return check_all_steps(used_atom,user) + +/datum/construction/mecha/gygax_chassis/spawn_result() + var/obj/item/mecha_parts/chassis/const_holder = holder + const_holder.construct = new /datum/construction/reversible/mecha/gygax(const_holder) + const_holder.icon = 'icons/mecha/mech_construction.dmi' + const_holder.icon_state = "gygax0" + const_holder.density = 1 + qdel(src) + return + + +/datum/construction/reversible/mecha/gygax + result = "/obj/mecha/combat/gygax" + steps = list( + //1 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="External armor is wrenched."), + //2 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="External armor is installed."), + //3 + list("key"=/obj/item/mecha_parts/part/gygax_armour, + "backkey"=TOOL_WELDER, + "desc"="Internal armor is welded."), + //4 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="Internal armor is wrenched."), + //5 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="Internal armor is installed."), + //6 + list("key"=/obj/item/stack/sheet/metal, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Advanced capacitor is secured."), + //7 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Advanced capacitor is installed."), + //8 + list("key"=/obj/item/stock_parts/capacitor/adv, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Advanced scanner module is secured."), + //9 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Advanced scanner module is installed."), + //10 + list("key"=/obj/item/stock_parts/scanning_module/adv, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Scanning module is secured."), + //11 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Scanning module is installed."), + //12 + list("key"=/obj/item/circuitboard/mecha/gygax/targeting, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Peripherals control module is secured."), + //13 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Peripherals control module is installed."), + //14 + list("key"=/obj/item/circuitboard/mecha/gygax/peripherals, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Central control module is secured."), + //15 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Central control module is installed."), + //16 + list("key"=/obj/item/circuitboard/mecha/gygax/main, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is adjusted."), + //17 + list("key"=/obj/item/wirecutters, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is added."), + //18 + list("key"=/obj/item/stack/cable_coil, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The hydraulic systems are active."), + //19 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_WRENCH, + "desc"="The hydraulic systems are connected."), + //20 + list("key"=TOOL_WRENCH, + "desc"="The hydraulic systems are disconnected.") + ) + +/datum/construction/reversible/mecha/gygax/action(atom/used_atom,mob/user as mob) + return check_step(used_atom,user) + +/datum/construction/reversible/mecha/gygax/custom_action(index, diff, atom/used_atom, mob/user) + if(!..()) + return 0 + + //TODO: better messages. + switch(index) + if(20) + user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") + holder.icon_state = "gygax1" + if(19) + if(diff==FORWARD) + user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") + holder.icon_state = "gygax2" + else + user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") + holder.icon_state = "gygax0" + if(18) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") + holder.icon_state = "gygax3" + else + user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") + holder.icon_state = "gygax1" + if(17) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") + holder.icon_state = "gygax4" + else + user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") + var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) + coil.amount = 4 + holder.icon_state = "gygax2" + if(16) + if(diff==FORWARD) + user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") + qdel(used_atom) + holder.icon_state = "gygax5" + else + user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") + holder.icon_state = "gygax3" + if(15) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + holder.icon_state = "gygax6" + else + user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") + new /obj/item/circuitboard/mecha/gygax/main(get_turf(holder)) + holder.icon_state = "gygax4" + if(14) + if(diff==FORWARD) + user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") + qdel(used_atom) + holder.icon_state = "gygax7" + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + holder.icon_state = "gygax5" + if(13) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + holder.icon_state = "gygax8" + else + user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") + new /obj/item/circuitboard/mecha/gygax/peripherals(get_turf(holder)) + holder.icon_state = "gygax6" + if(12) + if(diff==FORWARD) + user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") + qdel(used_atom) + holder.icon_state = "gygax9" + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + holder.icon_state = "gygax7" + if(11) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + holder.icon_state = "gygax10" + else + user.visible_message("[user] removes the weapon control module from the [holder].", "You remove the weapon control module from the [holder].") + new /obj/item/circuitboard/mecha/gygax/targeting(get_turf(holder)) + holder.icon_state = "gygax8" + if(10) + if(diff==FORWARD) + user.visible_message("[user] installs advanced scanner module to the [holder].", "You install advanced scanner module to the [holder].") + qdel(used_atom) + holder.icon_state = "gygax11" + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + holder.icon_state = "gygax9" + if(9) + if(diff==FORWARD) + user.visible_message("[user] secures the advanced scanner module.", "You secure the advanced scanner module.") + holder.icon_state = "gygax12" + else + user.visible_message("[user] removes the advanced scanner module from the [holder].", "You remove the advanced scanner module from the [holder].") + new /obj/item/stock_parts/scanning_module/adv(get_turf(holder)) + holder.icon_state = "gygax10" + if(8) + if(diff==FORWARD) + user.visible_message("[user] installs advanced capacitor to the [holder].", "You install advanced capacitor to the [holder].") + qdel(used_atom) + holder.icon_state = "gygax13" + else + user.visible_message("[user] unfastens the advanced scanner module.", "You unfasten the advanced scanner module.") + holder.icon_state = "gygax11" + if(7) + if(diff==FORWARD) + user.visible_message("[user] secures the advanced capacitor.", "You secure the advanced capacitor.") + holder.icon_state = "gygax14" + else + user.visible_message("[user] removes the advanced capacitor from the [holder].", "You remove the advanced capacitor from the [holder].") + new /obj/item/stock_parts/capacitor/adv(get_turf(holder)) + holder.icon_state = "gygax12" + if(6) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") + holder.icon_state = "gygax15" + else + user.visible_message("[user] unfastens the advanced capacitor.", "You unfasten the advanced capacitor.") + holder.icon_state = "gygax13" + if(5) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + holder.icon_state = "gygax16" + else + user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") + var/obj/item/stack/sheet/metal/MS = new /obj/item/stack/sheet/metal(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "gygax14" + if(4) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") + holder.icon_state = "gygax17" + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + holder.icon_state = "gygax15" + if(3) + if(diff==FORWARD) + user.visible_message("[user] installs Gygax Armor Plates to the [holder].", "You install Gygax Armor Plates to the [holder].") + qdel(used_atom) + holder.icon_state = "gygax18" + else + user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") + holder.icon_state = "gygax16" + if(2) + if(diff==FORWARD) + user.visible_message("[user] secures Gygax Armor Plates.", "You secure Gygax Armor Plates.") + holder.icon_state = "gygax19" + else + user.visible_message("[user] pries Gygax Armor Plates from the [holder].", "You pry Gygax Armor Plates from the [holder].") + new /obj/item/mecha_parts/part/gygax_armour(get_turf(holder)) + holder.icon_state = "gygax17" + if(1) + if(diff==FORWARD) + user.visible_message("[user] welds Gygax Armor Plates to the [holder].", "You weld Gygax Armor Plates to the [holder].") + else + user.visible_message("[user] unfastens Gygax Armor Plates.", "You unfasten Gygax Armor Plates.") + holder.icon_state = "gygax18" + return 1 + +/datum/construction/reversible/mecha/gygax/spawn_result() + ..() + feedback_inc("mecha_gygax_created",1) + return + +/datum/construction/mecha/firefighter_chassis + steps = list(list("key"=/obj/item/mecha_parts/part/ripley_torso),//1 + list("key"=/obj/item/mecha_parts/part/ripley_left_arm),//2 + list("key"=/obj/item/mecha_parts/part/ripley_right_arm),//3 + list("key"=/obj/item/mecha_parts/part/ripley_left_leg),//4 + list("key"=/obj/item/mecha_parts/part/ripley_right_leg),//5 + list("key"=/obj/item/clothing/suit/fire)//6 + ) + +/datum/construction/mecha/firefighter_chassis/custom_action(step, atom/used_atom, mob/user) + user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") + holder.overlays += used_atom.icon_state+"+o" + qdel(used_atom) + return 1 + +/datum/construction/mecha/firefighter_chassis/action(atom/used_atom,mob/user as mob) + return check_all_steps(used_atom,user) + +/datum/construction/mecha/firefighter_chassis/spawn_result() + var/obj/item/mecha_parts/chassis/const_holder = holder + const_holder.construct = new /datum/construction/reversible/mecha/firefighter(const_holder) + const_holder.icon = 'icons/mecha/mech_construction.dmi' + const_holder.icon_state = "fireripley0" + const_holder.density = 1 + qdel(src) + return + + +/datum/construction/reversible/mecha/firefighter + result = "/obj/mecha/working/ripley/firefighter" + taskpath = /datum/job_objective/make_ripley + steps = list( + //1 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="External armor is wrenched."), + //2 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="External armor is installed."), + //3 + list("key"=/obj/item/stack/sheet/plasteel, + "backkey"=TOOL_CROWBAR, + "desc"="External armor is being installed."), + //4 + list("key"=/obj/item/stack/sheet/plasteel, + "backkey"=TOOL_WELDER, + "desc"="Internal armor is welded."), + //5 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="Internal armor is wrenched."), + //6 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="Internal armor is installed."), + + //7 + list("key"=/obj/item/stack/sheet/plasteel, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Peripherals control module is secured."), + //8 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Peripherals control module is installed."), + //9 + list("key"=/obj/item/circuitboard/mecha/ripley/peripherals, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Central control module is secured."), + //10 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Central control module is installed."), + //11 + list("key"=/obj/item/circuitboard/mecha/ripley/main, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is adjusted."), + //12 + list("key"=/obj/item/wirecutters, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is added."), + //13 + list("key"=/obj/item/stack/cable_coil, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The hydraulic systems are active."), + //14 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_WRENCH, + "desc"="The hydraulic systems are connected."), + //15 + list("key"=TOOL_WRENCH, + "desc"="The hydraulic systems are disconnected.") + ) + +/datum/construction/reversible/mecha/firefighter/action(atom/used_atom,mob/user as mob) + return check_step(used_atom,user) + +/datum/construction/reversible/mecha/firefighter/custom_action(index, diff, atom/used_atom, mob/user) + if(!..()) + return 0 + + //TODO: better messages. + switch(index) + if(15) + user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") + holder.icon_state = "fireripley1" + if(14) + if(diff==FORWARD) + user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") + holder.icon_state = "fireripley2" + else + user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") + holder.icon_state = "fireripley0" + if(13) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") + holder.icon_state = "fireripley3" + else + user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") + holder.icon_state = "fireripley1" + if(12) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") + holder.icon_state = "fireripley4" + else + user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") + var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) + coil.amount = 4 + holder.icon_state = "fireripley2" + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") + qdel(used_atom) + holder.icon_state = "fireripley5" + else + user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") + holder.icon_state = "fireripley3" + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + holder.icon_state = "fireripley6" + else + user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") + new /obj/item/circuitboard/mecha/ripley/main(get_turf(holder)) + holder.icon_state = "fireripley4" + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") + qdel(used_atom) + holder.icon_state = "fireripley7" + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + holder.icon_state = "fireripley5" + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + holder.icon_state = "fireripley8" + else + user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") + new /obj/item/circuitboard/mecha/ripley/peripherals(get_turf(holder)) + holder.icon_state = "fireripley6" + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") + holder.icon_state = "fireripley9" + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + holder.icon_state = "fireripley7" + + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + holder.icon_state = "fireripley10" + else + user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") + var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "fireripley8" + if(5) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") + holder.icon_state = "fireripley11" + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + holder.icon_state = "fireripley9" + if(4) + if(diff==FORWARD) + user.visible_message("[user] starts to install the external armor layer to the [holder].", "You start to install the external armor layer to the [holder].") + holder.icon_state = "fireripley12" + else + user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") + holder.icon_state = "fireripley10" + if(3) + if(diff==FORWARD) + user.visible_message("[user] installs the external reinforced armor layer to the [holder].", "You install the external reinforced armor layer to the [holder].") + holder.icon_state = "fireripley13" + else + user.visible_message("[user] removes the external armor from the [holder].", "You remove the external armor from the [holder].") + var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "fireripley11" + if(2) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + holder.icon_state = "fireripley14" + else + user.visible_message("[user] pries external armor layer from the [holder].", "You pry external armor layer from the [holder].") + var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "fireripley12" + if(1) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to the [holder].", "You weld the external armor layer to the [holder].") + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + holder.icon_state = "fireripley13" + return 1 + +/datum/construction/reversible/mecha/firefighter/spawn_result() + ..() + feedback_inc("mecha_firefighter_created",1) + return + + + +/datum/construction/mecha/honker_chassis + steps = list(list("key"=/obj/item/mecha_parts/part/honker_torso),//1 + list("key"=/obj/item/mecha_parts/part/honker_left_arm),//2 + list("key"=/obj/item/mecha_parts/part/honker_right_arm),//3 + list("key"=/obj/item/mecha_parts/part/honker_left_leg),//4 + list("key"=/obj/item/mecha_parts/part/honker_right_leg),//5 + list("key"=/obj/item/mecha_parts/part/honker_head) + ) + +/datum/construction/mecha/honker_chassis/action(atom/used_atom,mob/user as mob) + return check_all_steps(used_atom,user) + +/datum/construction/mecha/honker_chassis/custom_action(step, atom/used_atom, mob/user) + user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") + holder.overlays += used_atom.icon_state+"+o" + qdel(used_atom) + return 1 + +/datum/construction/mecha/honker_chassis/spawn_result() + var/obj/item/mecha_parts/chassis/const_holder = holder + const_holder.construct = new /datum/construction/mecha/honker(const_holder) + const_holder.density = 1 + qdel(src) + return + + +/datum/construction/mecha/honker + result = "/obj/mecha/combat/honker" + steps = list(list("key"=/obj/item/bikehorn),//1 + list("key"=/obj/item/clothing/shoes/clown_shoes),//2 + list("key"=/obj/item/bikehorn),//3 + list("key"=/obj/item/clothing/mask/gas/clown_hat),//4 + list("key"=/obj/item/bikehorn),//5 + list("key"=/obj/item/circuitboard/mecha/honker/targeting),//6 + list("key"=/obj/item/bikehorn),//7 + list("key"=/obj/item/circuitboard/mecha/honker/peripherals),//8 + list("key"=/obj/item/bikehorn),//9 + list("key"=/obj/item/circuitboard/mecha/honker/main),//10 + list("key"=/obj/item/bikehorn),//11 + ) + +/datum/construction/mecha/honker/action(atom/used_atom,mob/user as mob) + return check_step(used_atom,user) + +/datum/construction/mecha/honker/custom_action(step, atom/used_atom, mob/user) + if(!..()) + return 0 + + if(istype(used_atom, /obj/item/bikehorn)) + playsound(holder, 'sound/items/bikehorn.ogg', 50, 1) + user.visible_message("HONK!") + + //TODO: better messages. + switch(step) + if(10) + user.visible_message("[user] installs the central control module into the [holder].", "You install the central control module into the [holder].") + qdel(used_atom) + if(8) + user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") + qdel(used_atom) + if(6) + user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") + qdel(used_atom) + if(4) + user.visible_message("[user] puts clown wig and mask on the [holder].", "You put clown wig and mask on the [holder].") + qdel(used_atom) + if(2) + user.visible_message("[user] puts clown boots on the [holder].", "You put clown boots on the [holder].") + qdel(used_atom) + return 1 + +/datum/construction/mecha/honker/spawn_result() + ..() + feedback_inc("mecha_honker_created",1) + return + +/datum/construction/mecha/reticence_chassis + steps = list(list("key"=/obj/item/mecha_parts/part/reticence_torso),//1 + list("key"=/obj/item/mecha_parts/part/reticence_left_arm),//2 + list("key"=/obj/item/mecha_parts/part/reticence_right_arm),//3 + list("key"=/obj/item/mecha_parts/part/reticence_left_leg),//4 + list("key"=/obj/item/mecha_parts/part/reticence_right_leg),//5 + list("key"=/obj/item/mecha_parts/part/reticence_head) + ) + +/datum/construction/mecha/reticence_chassis/action(atom/used_atom,mob/user as mob) + return check_all_steps(used_atom,user) + +/datum/construction/mecha/reticence_chassis/custom_action(step, atom/used_atom, mob/user) + user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") + holder.overlays += used_atom.icon_state + "+o" + qdel(used_atom) + return 1 + +/datum/construction/mecha/reticence_chassis/spawn_result() + var/obj/item/mecha_parts/chassis/const_holder = holder + const_holder.construct = new /datum/construction/mecha/reticence(const_holder) + const_holder.density = 1 + qdel(src) + return + +/datum/construction/mecha/reticence + result = "/obj/mecha/combat/reticence" + steps = list(list("key"=/obj/effect/dummy/mecha_emote_step),//1 + list("key"=/obj/item/clothing/suit/suspenders),//2 + list("key"=/obj/effect/dummy/mecha_emote_step),//3 + list("key"=/obj/item/clothing/mask/gas/mime),//4 + list("key"=/obj/effect/dummy/mecha_emote_step),//5 + list("key"=/obj/item/clothing/head/beret),//6 + list("key"=/obj/item/circuitboard/mecha/reticence/targeting),//7 + list("key"=/obj/item/circuitboard/mecha/reticence/peripherals),//8 + list("key"=/obj/item/circuitboard/mecha/reticence/main),//9 + ) + +/datum/construction/mecha/reticence/action(atom/used_atom,mob/user) + return check_step(used_atom,user) + +/datum/construction/mecha/reticence/custom_action(step, atom/used_atom, mob/user) + if(!..()) + return 0 + + if(istype(used_atom, /obj/effect/dummy/mecha_emote_step)) + var/obj/effect/dummy/mecha_emote_step/E = used_atom + holder.visible_message("[holder] likewise [E.emote]") + qdel(used_atom) + + //TODO: better messages. + switch(step) + if(9) + user.visible_message("[user] installs the central control module into the [holder].", "You install the central control module into the [holder].") + qdel(used_atom) + if(8) + user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") + qdel(used_atom) + if(7) + user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") + qdel(used_atom) + if(6) + user.visible_message("[user] puts beret on the [holder].", "You put beret on the [holder].") + qdel(used_atom) + if(4) + user.visible_message("[user] puts mime mask on the [holder].", "You put mime mask on the [holder].") + qdel(used_atom) + if(2) + user.visible_message("[user] puts suspenders on the [holder].", "You put suspenders on the [holder].") + qdel(used_atom) + return 1 + +/datum/construction/mecha/reticence/spawn_result() + ..() + feedback_inc("mecha_reticence_created",1) + return + +/datum/construction/mecha/durand_chassis + steps = list(list("key"=/obj/item/mecha_parts/part/durand_torso),//1 + list("key"=/obj/item/mecha_parts/part/durand_left_arm),//2 + list("key"=/obj/item/mecha_parts/part/durand_right_arm),//3 + list("key"=/obj/item/mecha_parts/part/durand_left_leg),//4 + list("key"=/obj/item/mecha_parts/part/durand_right_leg),//5 + list("key"=/obj/item/mecha_parts/part/durand_head) + ) + +/datum/construction/mecha/durand_chassis/custom_action(step, atom/used_atom, mob/user) + user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") + holder.overlays += used_atom.icon_state+"+o" + qdel(used_atom) + return 1 + +/datum/construction/mecha/durand_chassis/action(atom/used_atom,mob/user as mob) + return check_all_steps(used_atom,user) + +/datum/construction/mecha/durand_chassis/spawn_result() + var/obj/item/mecha_parts/chassis/const_holder = holder + const_holder.construct = new /datum/construction/reversible/mecha/durand(const_holder) + const_holder.icon = 'icons/mecha/mech_construction.dmi' + const_holder.icon_state = "durand0" + const_holder.density = 1 + qdel(src) + return + +/datum/construction/reversible/mecha/durand + result = "/obj/mecha/combat/durand" + steps = list( + //1 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="External armor is wrenched."), + //2 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="External armor is installed."), + //3 + list("key"=/obj/item/mecha_parts/part/durand_armor, + "backkey"=TOOL_WELDER, + "desc"="Internal armor is welded."), + //4 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="Internal armor is wrenched."), + //5 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="Internal armor is installed."), + //6 + list("key"=/obj/item/stack/sheet/metal, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Super capacitor is secured."), + //7 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Super capacitor is installed."), + //8 + list("key"=/obj/item/stock_parts/capacitor/super, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Phasic scanner module is secured."), + //9 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Phasic scanner module is installed."), + //10 + list("key"=/obj/item/stock_parts/scanning_module/phasic, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Scanning module is secured."), + //11 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Scanning module is installed."), + //12 + list("key"=/obj/item/circuitboard/mecha/durand/targeting, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Peripherals control module is secured."), + //13 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Peripherals control module is installed."), + //14 + list("key"=/obj/item/circuitboard/mecha/durand/peripherals, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Central control module is secured."), + //15 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Central control module is installed."), + //16 + list("key"=/obj/item/circuitboard/mecha/durand/main, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is adjusted."), + //17 + list("key"=/obj/item/wirecutters, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is added."), + //18 + list("key"=/obj/item/stack/cable_coil, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The hydraulic systems are active."), + //19 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_WRENCH, + "desc"="The hydraulic systems are connected."), + //20 + list("key"=TOOL_WRENCH, + "desc"="The hydraulic systems are disconnected.") + ) + + +/datum/construction/reversible/mecha/durand/action(atom/used_atom,mob/user as mob) + return check_step(used_atom,user) + +/datum/construction/reversible/mecha/durand/custom_action(index, diff, atom/used_atom, mob/user) + if(!..()) + return 0 + + //TODO: better messages. + switch(index) + if(20) + user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") + holder.icon_state = "durand1" + if(19) + if(diff==FORWARD) + user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") + holder.icon_state = "durand2" + else + user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") + holder.icon_state = "durand0" + if(18) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") + holder.icon_state = "durand3" + else + user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") + holder.icon_state = "durand1" + if(17) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") + holder.icon_state = "durand4" + else + user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") + var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) + coil.amount = 4 + holder.icon_state = "durand2" + if(16) + if(diff==FORWARD) + user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") + qdel(used_atom) + holder.icon_state = "durand5" + else + user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") + holder.icon_state = "durand3" + if(15) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + holder.icon_state = "durand6" + else + user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") + new /obj/item/circuitboard/mecha/durand/main(get_turf(holder)) + holder.icon_state = "durand4" + if(14) + if(diff==FORWARD) + user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") + qdel(used_atom) + holder.icon_state = "durand7" + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + holder.icon_state = "durand5" + if(13) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + holder.icon_state = "durand8" + else + user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") + new /obj/item/circuitboard/mecha/durand/peripherals(get_turf(holder)) + holder.icon_state = "durand6" + if(12) + if(diff==FORWARD) + user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") + qdel(used_atom) + holder.icon_state = "durand9" + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + holder.icon_state = "durand7" + if(11) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + holder.icon_state = "durand10" + else + user.visible_message("[user] removes the weapon control module from the [holder].", "You remove the weapon control module from the [holder].") + new /obj/item/circuitboard/mecha/durand/targeting(get_turf(holder)) + holder.icon_state = "durand8" + if(10) + if(diff==FORWARD) + user.visible_message("[user] installs phasic scanner module to the [holder].", "You install phasic scanner module to the [holder].") + qdel(used_atom) + holder.icon_state = "durand11" + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + holder.icon_state = "durand9" + if(9) + if(diff==FORWARD) + user.visible_message("[user] secures the phasic scanner module.", "You secure the phasic scanner module.") + holder.icon_state = "durand12" + else + user.visible_message("[user] removes the phasic scanner module from the [holder].", "You remove the phasic scanner module from the [holder].") + new /obj/item/stock_parts/scanning_module/phasic(get_turf(holder)) + holder.icon_state = "durand10" + if(8) + if(diff==FORWARD) + user.visible_message("[user] installs super capacitor to the [holder].", "You install super capacitor to the [holder].") + qdel(used_atom) + holder.icon_state = "durand13" + else + user.visible_message("[user] unfastens the phasic scanner module.", "You unfasten the phasic scanner module.") + holder.icon_state = "durand11" + if(7) + if(diff==FORWARD) + user.visible_message("[user] secures the super capacitor.", "You secure the super capacitor.") + holder.icon_state = "durand14" + else + user.visible_message("[user] removes the super capacitor from the [holder].", "You remove the super capacitor from the [holder].") + new /obj/item/stock_parts/capacitor/super(get_turf(holder)) + holder.icon_state = "durand12" + if(6) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") + holder.icon_state = "durand15" + else + user.visible_message("[user] unfastens the super capacitor.", "You unfasten the super capacitor.") + holder.icon_state = "durand13" + if(5) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + holder.icon_state = "durand16" + else + user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") + var/obj/item/stack/sheet/metal/MS = new /obj/item/stack/sheet/metal(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "durand14" + if(4) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") + holder.icon_state = "durand17" + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + holder.icon_state = "durand15" + if(3) + if(diff==FORWARD) + user.visible_message("[user] installs Durand Armor Plates to the [holder].", "You install Durand Armor Plates to the [holder].") + qdel(used_atom) + holder.icon_state = "durand18" + else + user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") + holder.icon_state = "durand16" + if(2) + if(diff==FORWARD) + user.visible_message("[user] secures Durand Armor Plates.", "You secure Durand Armor Plates.") + holder.icon_state = "durand19" + else + user.visible_message("[user] pries Durand Armor Plates from the [holder].", "You pry Durand Armor Plates from the [holder].") + new /obj/item/mecha_parts/part/durand_armor(get_turf(holder)) + holder.icon_state = "durand17" + if(1) + if(diff==FORWARD) + user.visible_message("[user] welds Durand Armor Plates to the [holder].", "You weld Durand Armor Plates to the [holder].") + else + user.visible_message("[user] unfastens Durand Armor Plates.", "You unfasten Durand Armor Plates.") + holder.icon_state = "durand18" + return 1 + +/datum/construction/reversible/mecha/durand/spawn_result() + ..() + feedback_inc("mecha_durand_created",1) + return + +//PHAZON + +/datum/construction/mecha/phazon_chassis + result = "/obj/mecha/combat/phazon" + steps = list(list("key"=/obj/item/mecha_parts/part/phazon_torso),//1 + list("key"=/obj/item/mecha_parts/part/phazon_left_arm),//2 + list("key"=/obj/item/mecha_parts/part/phazon_right_arm),//3 + list("key"=/obj/item/mecha_parts/part/phazon_left_leg),//4 + list("key"=/obj/item/mecha_parts/part/phazon_right_leg),//5 + list("key"=/obj/item/mecha_parts/part/phazon_head) + ) + +/datum/construction/mecha/phazon_chassis/custom_action(step, atom/used_atom, mob/user) + user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") + holder.overlays += used_atom.icon_state+"+o" + qdel(used_atom) + return 1 + +/datum/construction/mecha/phazon_chassis/action(atom/used_atom,mob/user as mob) + return check_all_steps(used_atom,user) + +/datum/construction/mecha/phazon_chassis/spawn_result() + var/obj/item/mecha_parts/chassis/const_holder = holder + const_holder.construct = new /datum/construction/reversible/mecha/phazon(const_holder) + const_holder.icon = 'icons/mecha/mech_construction.dmi' + const_holder.icon_state = "phazon0" + const_holder.density = 1 + qdel(src) + return + +/datum/construction/reversible/mecha/phazon + result = "/obj/mecha/combat/phazon" + steps = list( + //1 + list("key"=/obj/item/assembly/signaler/anomaly, + "backkey"=null, //Cannot remove the anomaly core once it's in + "desc"="Anomaly core socket is open and awaiting connection."), + //2 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="External armor is wrenched."), + //3 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="External armor is installed."), + //4 + list("key"=/obj/item/mecha_parts/part/phazon_armor, + "backkey"=TOOL_WELDER, + "desc"="Phase armor is welded."), + //5 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="Phase armor is wrenched."), + //6 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="Phase armor is installed."), + //7 + list("key"=/obj/item/stack/sheet/plasteel, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The bluespace crystal is engaged."), + //8 + list("key"=TOOL_SCREWDRIVER, + "backkey"=/obj/item/wirecutters, + "desc"="The bluespace crystal is connected."), + //9 + list("key"=/obj/item/stack/cable_coil, + "backkey"=TOOL_CROWBAR, + "desc"="The bluespace crystal is installed."), + //10 + list("key"=/obj/item/stack/ore/bluespace_crystal, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Super capacitor is secured."), + //11 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Super capacitor is installed."), + //12 + list("key"=/obj/item/stock_parts/capacitor/super, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Phasic scanner module is secured."), + //13 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Phasic scanner module is installed."), + //14 + list("key"=/obj/item/stock_parts/scanning_module/phasic, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Scanning module is secured."), + //15 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Scanning module is installed."), + //16 + list("key"=/obj/item/circuitboard/mecha/phazon/targeting, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Peripherals control module is secured."), + //17 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Peripherals control module is installed"), + //18 + list("key"=/obj/item/circuitboard/mecha/phazon/peripherals, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Central control module is secured."), + //19 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Central control module is installed."), + //20 + list("key"=/obj/item/circuitboard/mecha/phazon/main, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is adjusted."), + //21 + list("key"=/obj/item/wirecutters, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is added."), + //22 + list("key"=/obj/item/stack/cable_coil, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The hydraulic systems are active."), + //23 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_WRENCH, + "desc"="The hydraulic systems are connected."), + //24 + list("key"=TOOL_WRENCH, + "desc"="The hydraulic systems are disconnected.") + ) + + +/datum/construction/reversible/mecha/phazon/action(atom/used_atom,mob/user as mob) + return check_step(used_atom,user) + +/datum/construction/reversible/mecha/phazon/custom_action(index, diff, atom/used_atom, mob/user) + if(!..()) + return 0 + + //TODO: better messages. + switch(index) + if(24) + user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") + holder.icon_state = "phazon1" + if(23) + if(diff==FORWARD) + user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") + holder.icon_state = "phazon2" + else + user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") + holder.icon_state = "phazon0" + if(22) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") + holder.icon_state = "phazon3" + else + user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") + holder.icon_state = "phazon1" + if(21) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") + holder.icon_state = "phazon4" + else + user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") + var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) + coil.amount = 4 + holder.icon_state = "phazon2" + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") + qdel(used_atom) + holder.icon_state = "phazon5" + else + user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") + holder.icon_state = "phazon3" + if(19) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + holder.icon_state = "phazon6" + else + user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") + new /obj/item/circuitboard/mecha/phazon/main(get_turf(holder)) + holder.icon_state = "phazon4" + if(18) + if(diff==FORWARD) + user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") + qdel(used_atom) + holder.icon_state = "phazon7" + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + holder.icon_state = "phazon5" + if(17) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + holder.icon_state = "phazon8" + else + user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") + new /obj/item/circuitboard/mecha/phazon/peripherals(get_turf(holder)) + holder.icon_state = "phazon6" + if(16) + if(diff==FORWARD) + user.visible_message("[user] installs the weapon control module into the [holder].", "You install the weapon control module into the [holder].") + qdel(used_atom) + holder.icon_state = "phazon9" + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + holder.icon_state = "phazon7" + if(15) + if(diff==FORWARD) + user.visible_message("[user] secures the weapon control module.", "You secure the weapon control module.") + holder.icon_state = "phazon10" + else + user.visible_message("[user] removes the weapon control module from the [holder].", "You remove the weapon control module from the [holder].") + new /obj/item/circuitboard/mecha/phazon/targeting(get_turf(holder)) + holder.icon_state = "phazon8" + if(14) + if(diff==FORWARD) + user.visible_message("[user] installs phasic scanner module to the [holder].", "You install phasic scanner module to the [holder].") + qdel(used_atom) + holder.icon_state = "phazon11" + else + user.visible_message("[user] unfastens the weapon control module.", "You unfasten the weapon control module.") + holder.icon_state = "phazon9" + if(13) + if(diff==FORWARD) + user.visible_message("[user] secures the phasic scanner module.", "You secure the phasic scanner module.") + holder.icon_state = "phazon12" + else + user.visible_message("[user] removes the phasic scanner module from the [holder].", "You remove the phasic scanner module from the [holder].") + new /obj/item/stock_parts/scanning_module/phasic(get_turf(holder)) + holder.icon_state = "phazon10" + if(12) + if(diff==FORWARD) + user.visible_message("[user] installs super capacitor to the [holder].", "You install super capacitor to the [holder].") + qdel(used_atom) + holder.icon_state = "phazon13" + else + user.visible_message("[user] unfastens the phasic scanner module.", "You unfasten the phasic scanner module.") + holder.icon_state = "phazon11" + if(11) + if(diff==FORWARD) + user.visible_message("[user] secures the super capacitor.", "You secure the super capacitor.") + holder.icon_state = "phazon14" + else + user.visible_message("[user] removes the super capacitor from the [holder].", "You remove the super capacitor from the [holder].") + new /obj/item/stock_parts/capacitor/super(get_turf(holder)) + holder.icon_state = "phazon12" + if(10) + if(diff==FORWARD) + user.visible_message("[user] installs the bluespace crystal.", "You install the bluespace crystals.") + holder.icon_state = "phazon15" + else + user.visible_message("[user] unsecures the super capacitor from the [holder].", "You unsecure the super capacitor from the [holder].") + holder.icon_state = "phazon13" + if(9) + if(diff==FORWARD) + user.visible_message("[user] connects the bluespace crystal.", "You connect the bluespace crystals.") + holder.icon_state = "phazon16" + else + user.visible_message("[user] removes the bluespace crystal from the [holder].", "You remove the bluespace crystal from the [holder].") + new /obj/item/stack/ore/bluespace_crystal(get_turf(holder), new_amount = 5) + holder.icon_state = "phazon14" + if(8) + if(diff==FORWARD) + user.visible_message("[user] engages the bluespace crystal.", "You engage the bluespace crystals.") + holder.icon_state = "phazon17" + else + user.visible_message("[user] disconnects the bluespace crystal from the [holder].", "You disconnect the bluespace crystal from the [holder].") + holder.icon_state = "phazon15" + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs the phase armor layer to the [holder].", "You install the phase armor layer to the [holder].") + holder.icon_state = "phazon18" + else + user.visible_message("[user] disengages the bluespace crystal.", "You disengage the bluespace crystals.") + holder.icon_state = "phazon16" + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the phase armor layer.", "You secure the phase armor layer.") + holder.icon_state = "phazon19" + else + user.visible_message("[user] pries the phase armor layer from the [holder].", "You pry the phase armor layer from the [holder].") + var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "phazon17" + if(5) + if(diff==FORWARD) + user.visible_message("[user] welds the phase armor layer to the [holder].", "You weld the phase armor layer to the [holder].") + holder.icon_state = "phazon20" + else + user.visible_message("[user] unfastens the phase armor layer.", "You unfasten the phase armor layer.") + holder.icon_state = "phazon18" + if(4) + if(diff==FORWARD) + user.visible_message("[user] installs Phazon Armor Plates to the [holder].", "You install Phazon Armor Plates to the [holder].") + qdel(used_atom) + holder.icon_state = "phazon21" + else + user.visible_message("[user] cuts phase armor layer from the [holder].", "You cut the phase armor layer from the [holder].") + holder.icon_state = "phazon19" + if(3) + if(diff==FORWARD) + user.visible_message("[user] secures Phazon Armor Plates.", "You secure Phazon Armor Plates.") + holder.icon_state = "phazon22" + else + user.visible_message("[user] pries Phazon Armor Plates from the [holder].", "You pry Phazon Armor Plates from the [holder].") + new /obj/item/mecha_parts/part/phazon_armor(get_turf(holder)) + holder.icon_state = "phazon20" + if(2) + if(diff==FORWARD) + user.visible_message("[user] welds Phazon Armor Plates to the [holder].", "You weld Phazon Armor Plates to the [holder].") + else + user.visible_message("[user] unfastens Phazon Armor Plates.", "You unfasten Phazon Armor Plates.") + holder.icon_state = "phazon21" + if(1) + if(diff==FORWARD) + user.visible_message("[user] carefully inserts the anomaly core into \the [holder] and secures it.", "You slowly place the anomaly core into its socket and close its chamber.") + qdel(used_atom) + return 1 + +/datum/construction/reversible/mecha/phazon/spawn_result() + ..() + feedback_inc("mecha_phazon_created",1) + return + +//ODYSSEUS + +/datum/construction/mecha/odysseus_chassis + steps = list(list("key"=/obj/item/mecha_parts/part/odysseus_torso),//1 + list("key"=/obj/item/mecha_parts/part/odysseus_head),//2 + list("key"=/obj/item/mecha_parts/part/odysseus_left_arm),//3 + list("key"=/obj/item/mecha_parts/part/odysseus_right_arm),//4 + list("key"=/obj/item/mecha_parts/part/odysseus_left_leg),//5 + list("key"=/obj/item/mecha_parts/part/odysseus_right_leg)//6 + ) + +/datum/construction/mecha/odysseus_chassis/custom_action(step, atom/used_atom, mob/user) + user.visible_message("[user] has connected [used_atom] to the [holder].", "You connect [used_atom] to the [holder]") + holder.overlays += used_atom.icon_state+"+o" + qdel(used_atom) + return 1 + +/datum/construction/mecha/odysseus_chassis/action(atom/used_atom,mob/user as mob) + return check_all_steps(used_atom,user) + +/datum/construction/mecha/odysseus_chassis/spawn_result() + var/obj/item/mecha_parts/chassis/const_holder = holder + const_holder.construct = new /datum/construction/reversible/mecha/odysseus(const_holder) + const_holder.icon = 'icons/mecha/mech_construction.dmi' + const_holder.icon_state = "odysseus0" + const_holder.density = 1 + qdel(src) + return + + +/datum/construction/reversible/mecha/odysseus + result = "/obj/mecha/medical/odysseus" + steps = list( + //1 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="External armor is wrenched."), + //2 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="External armor is installed."), + //3 + list("key"=/obj/item/stack/sheet/plasteel, + "backkey"=TOOL_WELDER, + "desc"="Internal armor is welded."), + //4 + list("key"=TOOL_WELDER, + "backkey"=TOOL_WRENCH, + "desc"="Internal armor is wrenched."), + //5 + list("key"=TOOL_WRENCH, + "backkey"=TOOL_CROWBAR, + "desc"="Internal armor is installed."), + //6 + list("key"=/obj/item/stack/sheet/metal, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Peripherals control module is secured."), + //7 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Peripherals control module is installed."), + //8 + list("key"=/obj/item/circuitboard/mecha/odysseus/peripherals, + "backkey"=TOOL_SCREWDRIVER, + "desc"="Central control module is secured."), + //9 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_CROWBAR, + "desc"="Central control module is installed."), + //10 + list("key"=/obj/item/circuitboard/mecha/odysseus/main, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is adjusted."), + //11 + list("key"=/obj/item/wirecutters, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The wiring is added."), + //12 + list("key"=/obj/item/stack/cable_coil, + "backkey"=TOOL_SCREWDRIVER, + "desc"="The hydraulic systems are active."), + //13 + list("key"=TOOL_SCREWDRIVER, + "backkey"=TOOL_WRENCH, + "desc"="The hydraulic systems are connected."), + //14 + list("key"=TOOL_WRENCH, + "desc"="The hydraulic systems are disconnected.") + ) + +/datum/construction/reversible/mecha/odysseus/action(atom/used_atom,mob/user as mob) + return check_step(used_atom,user) + +/datum/construction/reversible/mecha/odysseus/custom_action(index, diff, atom/used_atom, mob/user) + if(!..()) + return 0 + + //TODO: better messages. + switch(index) + if(14) + user.visible_message("[user] connects the [holder] hydraulic systems", "You connect the [holder] hydraulic systems.") + holder.icon_state = "odysseus1" + if(13) + if(diff==FORWARD) + user.visible_message("[user] activates the [holder] hydraulic systems.", "You activate the [holder] hydraulic systems.") + holder.icon_state = "odysseus2" + else + user.visible_message("[user] disconnects the [holder] hydraulic systems", "You disconnect the [holder] hydraulic systems.") + holder.icon_state = "odysseus0" + if(12) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to the [holder].", "You add the wiring to the [holder].") + holder.icon_state = "odysseus3" + else + user.visible_message("[user] deactivates the [holder] hydraulic systems.", "You deactivate the [holder] hydraulic systems.") + holder.icon_state = "odysseus1" + if(11) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of the [holder].", "You adjust the wiring of the [holder].") + holder.icon_state = "odysseus4" + else + user.visible_message("[user] removes the wiring from the [holder].", "You remove the wiring from the [holder].") + var/obj/item/stack/cable_coil/coil = new /obj/item/stack/cable_coil(get_turf(holder)) + coil.amount = 4 + holder.icon_state = "odysseus2" + if(10) + if(diff==FORWARD) + user.visible_message("[user] installs the central control module into the [holder].", "You install the central computer mainboard into the [holder].") + qdel(used_atom) + holder.icon_state = "odysseus5" + else + user.visible_message("[user] disconnects the wiring of the [holder].", "You disconnect the wiring of the [holder].") + holder.icon_state = "odysseus3" + if(9) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + holder.icon_state = "odysseus6" + else + user.visible_message("[user] removes the central control module from the [holder].", "You remove the central computer mainboard from the [holder].") + new /obj/item/circuitboard/mecha/odysseus/main(get_turf(holder)) + holder.icon_state = "odysseus4" + if(8) + if(diff==FORWARD) + user.visible_message("[user] installs the peripherals control module into the [holder].", "You install the peripherals control module into the [holder].") + qdel(used_atom) + holder.icon_state = "odysseus7" + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + holder.icon_state = "odysseus5" + if(7) + if(diff==FORWARD) + user.visible_message("[user] secures the peripherals control module.", "You secure the peripherals control module.") + holder.icon_state = "odysseus8" + else + user.visible_message("[user] removes the peripherals control module from the [holder].", "You remove the peripherals control module from the [holder].") + new /obj/item/circuitboard/mecha/odysseus/peripherals(get_turf(holder)) + holder.icon_state = "odysseus6" + if(6) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to the [holder].", "You install the internal armor layer to the [holder].") + holder.icon_state = "odysseus9" + else + user.visible_message("[user] unfastens the peripherals control module.", "You unfasten the peripherals control module.") + holder.icon_state = "odysseus7" + if(5) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + holder.icon_state = "odysseus10" + else + user.visible_message("[user] pries internal armor layer from the [holder].", "You pry internal armor layer from the [holder].") + var/obj/item/stack/sheet/metal/MS = new /obj/item/stack/sheet/metal(get_turf(holder)) + MS.amount = 5 + holder.icon_state = "odysseus8" + if(4) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to the [holder].", "You weld the internal armor layer to the [holder].") + holder.icon_state = "odysseus11" + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + holder.icon_state = "odysseus9" + if(3) + if(diff==FORWARD) + user.visible_message("[user] installs [used_atom] layer to the [holder].", "You install the external reinforced armor layer to the [holder].") + + holder.icon_state = "odysseus12" + else + user.visible_message("[user] cuts the internal armor layer from the [holder].", "You cut the internal armor layer from the [holder].") + holder.icon_state = "odysseus10" + if(2) + if(diff==FORWARD) + user.visible_message("[user] secures the external armor layer.", "You secure the external reinforced armor layer.") + holder.icon_state = "odysseus13" + else + var/obj/item/stack/sheet/plasteel/MS = new /obj/item/stack/sheet/plasteel(get_turf(holder)) + MS.amount = 5 + user.visible_message("[user] pries [MS] from the [holder].", "You pry [MS] from the [holder].") + holder.icon_state = "odysseus11" + if(1) + if(diff==FORWARD) + user.visible_message("[user] welds the external armor layer to the [holder].", "You weld the external armor layer to the [holder].") + holder.icon_state = "odysseus14" + else + user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.") + holder.icon_state = "odysseus12" + return 1 + +/datum/construction/reversible/mecha/odysseus/spawn_result() + ..() + feedback_inc("mecha_odysseus_created",1) + return diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm index 35440de6cf9..0313397a809 100644 --- a/code/game/mecha/mecha_control_console.dm +++ b/code/game/mecha/mecha_control_console.dm @@ -1,161 +1,161 @@ -/obj/machinery/computer/mecha - name = "exosuit control console" - icon = 'icons/obj/computer.dmi' - icon_keyboard = "rd_key" - icon_screen = "mecha" - light_color = LIGHT_COLOR_FADEDPURPLE - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/mecha_control - var/list/located = list() - var/screen = 0 - var/stored_data - -/obj/machinery/computer/mecha/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/computer/mecha/attack_hand(mob/user) - ui_interact(user) - -/obj/machinery/computer/mecha/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "exosuit_control.tmpl", "Exosuit Control Console", 420, 500) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/computer/mecha/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - data["screen"] = screen - if(screen == 0) - var/list/mechas[0] - for(var/obj/item/mecha_parts/mecha_tracking/TR in world) - var/answer = TR.get_mecha_info() - if(answer) - mechas[++mechas.len] = answer - data["mechas"] = mechas - if(screen == 1) - data["log"] = stored_data - return data - -/obj/machinery/computer/mecha/Topic(href, href_list) - if(..()) - return 1 - - var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) - if(href_list["send_message"]) - var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("send_message") - var/message = strip_html_simple(input(usr,"Input message","Transmit message") as text) - if(!trim(message) || ..()) - return 1 - var/obj/mecha/M = MT.in_mecha() - if(M) - M.occupant_message(message) - - if(href_list["shock"]) - var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("shock") - MT.shock() - - if(href_list["get_log"]) - var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("get_log") - stored_data = MT.get_mecha_log() - screen = 1 - - if(href_list["return"]) - screen = 0 - - SSnanoui.update_uis(src) - return - -/obj/item/mecha_parts/mecha_tracking - name = "Exosuit tracking beacon" - desc = "Device used to transmit exosuit data." - icon = 'icons/obj/device.dmi' - icon_state = "motion2" - w_class = WEIGHT_CLASS_SMALL - origin_tech = "programming=2;magnets=2" - var/ai_beacon = FALSE //If this beacon allows for AI control. Exists to avoid using istype() on checking. - -/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() - if(!in_mecha()) - return FALSE - var/obj/mecha/M = loc - var/list/answer[0] - answer["reference"] = "\ref[src]" - answer["name"] = sanitize(replacetext(M.name,"\"","'")) // Double apostrophes break JSON - if(M.cell) - answer["cell"] = 1 - answer["cell_capacity"] = M.cell.maxcharge - answer["cell_current"] = M.get_charge() - answer["cell_percentage"] = round(M.cell.percent()) - else - answer["cell"] = 0 - answer["integrity"] = round((M.obj_integrity/M.max_integrity*100), 0.01) - answer["airtank"] = M.return_pressure() - answer["pilot"] = "[M.occupant||"None"]" - var/area/area = get_area(M) - answer["location"] = "[sanitize(area.name)||"Unknown"]" - answer["equipment"] = "[M.selected||"None"]" - if(istype(M, /obj/mecha/working/ripley)) - var/obj/mecha/working/ripley/RM = M - answer["hascargo"] = 1 - answer["cargo"] = RM.cargo.len/RM.cargo_capacity*100 - - return answer - -/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info_text() - if(!in_mecha()) - return FALSE - var/obj/mecha/M = loc - var/cell_charge = M.get_charge() - var/area/A = get_area(M) - var/answer = {"Name: [M.name] - Integrity: [M.obj_integrity / M.max_integrity * 100]% - Cell charge: [isnull(cell_charge)?"Not found":"[M.cell.percent()]%"] - Airtank: [M.return_pressure()]kPa - Pilot: [M.occupant||"None"] - Location: [sanitize(A.name)||"Unknown"] - Active equipment: [M.selected||"None"]
        "} - if(istype(M, /obj/mecha/working/ripley)) - var/obj/mecha/working/ripley/RM = M - answer += "Used cargo space: [RM.cargo.len/RM.cargo_capacity*100]%
        " - - return answer - -/obj/item/mecha_parts/mecha_tracking/emp_act() - qdel(src) - -/obj/item/mecha_parts/mecha_tracking/proc/in_mecha() - if(istype(loc, /obj/mecha)) - return loc - return FALSE - -/obj/item/mecha_parts/mecha_tracking/proc/shock() - var/obj/mecha/M = in_mecha() - if(M) - M.emp_act(2) - qdel(src) - -/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_log() - if(!in_mecha()) - return 0 - var/obj/mecha/M = loc - return M.get_log_html() - -/obj/item/mecha_parts/mecha_tracking/ai_control - name = "exosuit AI control beacon" - desc = "A device used to transmit exosuit data. Also allows active AI units to take control of said exosuit." - origin_tech = "programming=3;magnets=2;engineering=2" - ai_beacon = TRUE - -/obj/item/storage/box/mechabeacons - name = "Exosuit Tracking Beacons" - -/obj/item/storage/box/mechabeacons/New() - ..() - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) - new /obj/item/mecha_parts/mecha_tracking(src) +/obj/machinery/computer/mecha + name = "exosuit control console" + icon = 'icons/obj/computer.dmi' + icon_keyboard = "rd_key" + icon_screen = "mecha" + light_color = LIGHT_COLOR_FADEDPURPLE + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/mecha_control + var/list/located = list() + var/screen = 0 + var/stored_data + +/obj/machinery/computer/mecha/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/computer/mecha/attack_hand(mob/user) + ui_interact(user) + +/obj/machinery/computer/mecha/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "exosuit_control.tmpl", "Exosuit Control Console", 420, 500) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/mecha/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + data["screen"] = screen + if(screen == 0) + var/list/mechas[0] + for(var/obj/item/mecha_parts/mecha_tracking/TR in world) + var/answer = TR.get_mecha_info() + if(answer) + mechas[++mechas.len] = answer + data["mechas"] = mechas + if(screen == 1) + data["log"] = stored_data + return data + +/obj/machinery/computer/mecha/Topic(href, href_list) + if(..()) + return 1 + + var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) + if(href_list["send_message"]) + var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("send_message") + var/message = strip_html_simple(input(usr,"Input message","Transmit message") as text) + if(!trim(message) || ..()) + return 1 + var/obj/mecha/M = MT.in_mecha() + if(M) + M.occupant_message(message) + + if(href_list["shock"]) + var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("shock") + MT.shock() + + if(href_list["get_log"]) + var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("get_log") + stored_data = MT.get_mecha_log() + screen = 1 + + if(href_list["return"]) + screen = 0 + + SSnanoui.update_uis(src) + return + +/obj/item/mecha_parts/mecha_tracking + name = "Exosuit tracking beacon" + desc = "Device used to transmit exosuit data." + icon = 'icons/obj/device.dmi' + icon_state = "motion2" + w_class = WEIGHT_CLASS_SMALL + origin_tech = "programming=2;magnets=2" + var/ai_beacon = FALSE //If this beacon allows for AI control. Exists to avoid using istype() on checking. + +/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() + if(!in_mecha()) + return FALSE + var/obj/mecha/M = loc + var/list/answer[0] + answer["reference"] = "\ref[src]" + answer["name"] = sanitize(replacetext(M.name,"\"","'")) // Double apostrophes break JSON + if(M.cell) + answer["cell"] = 1 + answer["cell_capacity"] = M.cell.maxcharge + answer["cell_current"] = M.get_charge() + answer["cell_percentage"] = round(M.cell.percent()) + else + answer["cell"] = 0 + answer["integrity"] = round((M.obj_integrity/M.max_integrity*100), 0.01) + answer["airtank"] = M.return_pressure() + answer["pilot"] = "[M.occupant||"None"]" + var/area/area = get_area(M) + answer["location"] = "[sanitize(area.name)||"Unknown"]" + answer["equipment"] = "[M.selected||"None"]" + if(istype(M, /obj/mecha/working/ripley)) + var/obj/mecha/working/ripley/RM = M + answer["hascargo"] = 1 + answer["cargo"] = RM.cargo.len/RM.cargo_capacity*100 + + return answer + +/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info_text() + if(!in_mecha()) + return FALSE + var/obj/mecha/M = loc + var/cell_charge = M.get_charge() + var/area/A = get_area(M) + var/answer = {"Name: [M.name] + Integrity: [M.obj_integrity / M.max_integrity * 100]% + Cell charge: [isnull(cell_charge)?"Not found":"[M.cell.percent()]%"] + Airtank: [M.return_pressure()]kPa + Pilot: [M.occupant||"None"] + Location: [sanitize(A.name)||"Unknown"] + Active equipment: [M.selected||"None"]
        "} + if(istype(M, /obj/mecha/working/ripley)) + var/obj/mecha/working/ripley/RM = M + answer += "Used cargo space: [RM.cargo.len/RM.cargo_capacity*100]%
        " + + return answer + +/obj/item/mecha_parts/mecha_tracking/emp_act() + qdel(src) + +/obj/item/mecha_parts/mecha_tracking/proc/in_mecha() + if(istype(loc, /obj/mecha)) + return loc + return FALSE + +/obj/item/mecha_parts/mecha_tracking/proc/shock() + var/obj/mecha/M = in_mecha() + if(M) + M.emp_act(2) + qdel(src) + +/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_log() + if(!in_mecha()) + return 0 + var/obj/mecha/M = loc + return M.get_log_html() + +/obj/item/mecha_parts/mecha_tracking/ai_control + name = "exosuit AI control beacon" + desc = "A device used to transmit exosuit data. Also allows active AI units to take control of said exosuit." + origin_tech = "programming=3;magnets=2;engineering=2" + ai_beacon = TRUE + +/obj/item/storage/box/mechabeacons + name = "Exosuit Tracking Beacons" + +/obj/item/storage/box/mechabeacons/New() + ..() + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) + new /obj/item/mecha_parts/mecha_tracking(src) diff --git a/code/game/mecha/mecha_modkit.dm b/code/game/mecha/mecha_modkit.dm index 9a75fc75077..9d07e760c79 100644 --- a/code/game/mecha/mecha_modkit.dm +++ b/code/game/mecha/mecha_modkit.dm @@ -75,4 +75,4 @@ critdestrsound = null weapdestrsound = null lowpowersound = null - longactivationsound = null \ No newline at end of file + longactivationsound = null diff --git a/code/game/mecha/mecha_parts.dm b/code/game/mecha/mecha_parts.dm index 6aff10eb423..86fd9cb6821 100644 --- a/code/game/mecha/mecha_parts.dm +++ b/code/game/mecha/mecha_parts.dm @@ -1,457 +1,457 @@ -///////////////////////// -////// Mecha Parts ////// -///////////////////////// - -/obj/item/mecha_parts - name = "mecha part" - icon = 'icons/mecha/mech_construct.dmi' - icon_state = "blank" - w_class = WEIGHT_CLASS_GIGANTIC - flags = CONDUCT - origin_tech = "programming=2;materials=2;engineering=2" - - -/obj/item/mecha_parts/chassis - name="Mecha Chassis" - icon_state = "backbone" - var/datum/construction/construct - flags = CONDUCT - -/obj/item/mecha_parts/chassis/Destroy() - QDEL_NULL(construct) - return ..() - -/obj/item/mecha_parts/chassis/attackby(obj/item/W, mob/user, params) - if(!construct || !construct.action(W, user)) - return ..() - -/obj/item/mecha_parts/chassis/attack_hand() - return - -/////////// Ripley - -/obj/item/mecha_parts/chassis/ripley - name = "Ripley Chassis" - -/obj/item/mecha_parts/chassis/ripley/New() - ..() - construct = new /datum/construction/mecha/ripley_chassis(src) - -/obj/item/mecha_parts/part/ripley_torso - name="Ripley Torso" - desc="A torso part of Ripley APLU. Contains power unit, processing core and life support systems." - icon_state = "ripley_harness" - origin_tech = "programming=2;materials=2;biotech=2;engineering=2" - -/obj/item/mecha_parts/part/ripley_left_arm - name="Ripley Left Arm" - desc="A Ripley APLU left arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "ripley_l_arm" - -/obj/item/mecha_parts/part/ripley_right_arm - name="Ripley Right Arm" - desc="A Ripley APLU right arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "ripley_r_arm" - -/obj/item/mecha_parts/part/ripley_left_leg - name="Ripley Left Leg" - desc="A Ripley APLU left leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "ripley_l_leg" - -/obj/item/mecha_parts/part/ripley_right_leg - name="Ripley Right Leg" - desc="A Ripley APLU right leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "ripley_r_leg" - -///////// Gygax - -/obj/item/mecha_parts/chassis/gygax - name = "Gygax Chassis" - -/obj/item/mecha_parts/chassis/gygax/New() - ..() - construct = new /datum/construction/mecha/gygax_chassis(src) - -/obj/item/mecha_parts/part/gygax_torso - name="Gygax Torso" - desc="A torso part of Gygax. Contains power unit, processing core and life support systems. Has an additional equipment slot." - icon_state = "gygax_harness" - origin_tech = "programming=2;materials=4;biotech=3;engineering=3" - -/obj/item/mecha_parts/part/gygax_head - name="Gygax Head" - desc="A Gygax head. Houses advanced surveilance and targeting sensors." - icon_state = "gygax_head" - origin_tech = "programming=2;materials=4;magnets=3;engineering=3" - -/obj/item/mecha_parts/part/gygax_left_arm - name="Gygax Left Arm" - desc="A Gygax left arm. Data and power sockets are compatible with most exosuit tools and weapons." - icon_state = "gygax_l_arm" - origin_tech = "programming=2;materials=4;engineering=3" - -/obj/item/mecha_parts/part/gygax_right_arm - name="Gygax Right Arm" - desc="A Gygax right arm. Data and power sockets are compatible with most exosuit tools and weapons." - icon_state = "gygax_r_arm" - origin_tech = "programming=2;materials=4;engineering=3" - -/obj/item/mecha_parts/part/gygax_left_leg - name="Gygax Left Leg" - icon_state = "gygax_l_leg" - origin_tech = "programming=2;materials=4;engineering=3" - -/obj/item/mecha_parts/part/gygax_right_leg - name="Gygax Right Leg" - icon_state = "gygax_r_leg" - origin_tech = "programming=2;materials=4;engineering=3" - -/obj/item/mecha_parts/part/gygax_armour - name="Gygax Armour Plates" - icon_state = "gygax_armour" - origin_tech = "materials=6;combat=4;engineering=4" - - -//////////// Durand - -/obj/item/mecha_parts/chassis/durand - name = "Durand Chassis" - -/obj/item/mecha_parts/chassis/durand/New() - ..() - construct = new /datum/construction/mecha/durand_chassis(src) - -/obj/item/mecha_parts/part/durand_torso - name="Durand Torso" - icon_state = "durand_harness" - origin_tech = "programming=2;materials=3;biotech=3;engineering=3" - -/obj/item/mecha_parts/part/durand_head - name="Durand Head" - icon_state = "durand_head" - origin_tech = "programming=2;materials=3;magnets=3;engineering=3" - -/obj/item/mecha_parts/part/durand_left_arm - name="Durand Left Arm" - icon_state = "durand_l_arm" - origin_tech = "programming=2;materials=3;engineering=3" - -/obj/item/mecha_parts/part/durand_right_arm - name="Durand Right Arm" - icon_state = "durand_r_arm" - origin_tech = "programming=2;materials=3;engineering=3" - -/obj/item/mecha_parts/part/durand_left_leg - name="Durand Left Leg" - icon_state = "durand_l_leg" - origin_tech = "programming=2;materials=3;engineering=3" - -/obj/item/mecha_parts/part/durand_right_leg - name="Durand Right Leg" - icon_state = "durand_r_leg" - origin_tech = "programming=2;materials=3;engineering=3" - -/obj/item/mecha_parts/part/durand_armor - name="Durand Armour Plates" - icon_state = "durand_armor" - origin_tech = "materials=5;combat=4;engineering=4" - - - -////////// Firefighter - -/obj/item/mecha_parts/chassis/firefighter - name = "Firefighter Chassis" - -/obj/item/mecha_parts/chassis/firefighter/New() - ..() - construct = new /datum/construction/mecha/firefighter_chassis(src) - -////////// HONK - -/obj/item/mecha_parts/chassis/honker - name = "H.O.N.K Chassis" - -/obj/item/mecha_parts/chassis/honker/New() - ..() - construct = new /datum/construction/mecha/honker_chassis(src) - -/obj/item/mecha_parts/part/honker_torso - name="H.O.N.K Torso" - icon_state = "honker_harness" - -/obj/item/mecha_parts/part/honker_head - name="H.O.N.K Head" - icon_state = "honker_head" - -/obj/item/mecha_parts/part/honker_left_arm - name="H.O.N.K Left Arm" - icon_state = "honker_l_arm" - -/obj/item/mecha_parts/part/honker_right_arm - name="H.O.N.K Right Arm" - icon_state = "honker_r_arm" - -/obj/item/mecha_parts/part/honker_left_leg - name="H.O.N.K Left Leg" - icon_state = "honker_l_leg" - -/obj/item/mecha_parts/part/honker_right_leg - name="H.O.N.K Right Leg" - icon_state = "honker_r_leg" - - -////////// Reticence - -/obj/item/mecha_parts/chassis/reticence - name = "Reticence Chassis" - -/obj/item/mecha_parts/chassis/reticence/New() - ..() - construct = new /datum/construction/mecha/reticence_chassis(src) - -/obj/effect/dummy/mecha_emote_step - var/emote - -/obj/effect/dummy/mecha_emote_step/New(e) - emote = e - -/obj/item/mecha_parts/chassis/reticence/hear_message(mob/living/M, msg) - if(!istype(M) || !istype(construct, /datum/construction/mecha/reticence)) - return - // is the current step the dummy emote object? - var/list/steps = construct.steps - if(steps[steps.len]["key"] == /obj/effect/dummy/mecha_emote_step) - construct.action(new /obj/effect/dummy/mecha_emote_step(msg), M) - -/obj/item/mecha_parts/part/reticence_torso - name = "Reticence Torso" - icon_state = "reticence_harness" - -/obj/item/mecha_parts/part/reticence_head - name = "Reticence Head" - icon_state = "reticence_head" - -/obj/item/mecha_parts/part/reticence_left_arm - name = "Reticence Left Arm" - icon_state = "reticence_l_arm" - -/obj/item/mecha_parts/part/reticence_right_arm - name = "Reticence Right Arm" - icon_state = "reticence_r_arm" - -/obj/item/mecha_parts/part/reticence_left_leg - name = "Reticence Left Leg" - icon_state = "reticence_l_leg" - -/obj/item/mecha_parts/part/reticence_right_leg - name = "Reticence Right Leg" - icon_state = "reticence_r_leg" - - -////////// Phazon - -/obj/item/mecha_parts/chassis/phazon - name = "Phazon Chassis" - -/obj/item/mecha_parts/chassis/phazon/New() - ..() - construct = new /datum/construction/mecha/phazon_chassis(src) - -/obj/item/mecha_parts/part/phazon_torso - name="Phazon Torso" - icon_state = "phazon_harness" - origin_tech = "programming=4;materials=4;bluespace=4;plasmatech=5" - -/obj/item/mecha_parts/part/phazon_head - name="Phazon Head" - icon_state = "phazon_head" - origin_tech = "programming=3;materials=3;magnets=3" - -/obj/item/mecha_parts/part/phazon_left_arm - name="Phazon Left Arm" - icon_state = "phazon_l_arm" - origin_tech = "materials=3;bluespace=3;magnets=3" - -/obj/item/mecha_parts/part/phazon_right_arm - name="Phazon Right Arm" - icon_state = "phazon_r_arm" - origin_tech = "materials=3;bluespace=3;magnets=3" - -/obj/item/mecha_parts/part/phazon_left_leg - name="Phazon Left Leg" - icon_state = "phazon_l_leg" - origin_tech = "materials=3;bluespace=3;magnets=3" - -/obj/item/mecha_parts/part/phazon_right_leg - name="Phazon Right Leg" - icon_state = "phazon_r_leg" - origin_tech = "materials=3;bluespace=3;magnets=3" - -/obj/item/mecha_parts/part/phazon_armor - name="Phazon armor" - desc="Phazon armor plates. They are layered with plasma to protect the pilot from the stress of phasing and have unusual properties." - icon_state = "phazon_armor" - origin_tech = "materials=4;bluespace=4;plasmatech=5" - -///////// Odysseus -/obj/item/mecha_parts/chassis/odysseus - name = "Odysseus Chassis" - -/obj/item/mecha_parts/chassis/odysseus/New() - ..() - construct = new /datum/construction/mecha/odysseus_chassis(src) - -/obj/item/mecha_parts/part/odysseus_head - name="Odysseus Head" - icon_state = "odysseus_head" - -/obj/item/mecha_parts/part/odysseus_torso - name="Odysseus Torso" - desc="A torso part of Odysseus. Contains power unit, processing core and life support systems." - icon_state = "odysseus_torso" - origin_tech = "programming=2;materials=2;biotech=2;engineering=2" - -/obj/item/mecha_parts/part/odysseus_left_arm - name="Odysseus Left Arm" - desc="An Odysseus left arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "odysseus_l_arm" - -/obj/item/mecha_parts/part/odysseus_right_arm - name="Odysseus Right Arm" - desc="An Odysseus right arm. Data and power sockets are compatible with most exosuit tools." - icon_state = "odysseus_r_arm" - -/obj/item/mecha_parts/part/odysseus_left_leg - name="Odysseus Left Leg" - desc="An Odysseus left leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "odysseus_l_leg" - -/obj/item/mecha_parts/part/odysseus_right_leg - name="Odysseus Right Leg" - desc="A Odysseus right leg. Contains somewhat complex servodrives and balance maintaining systems." - icon_state = "odysseus_r_leg" - -/*/obj/item/mecha_parts/part/odysseus_armour - name="Odysseus Carapace" - icon_state = "odysseus_armour" - origin_tech = "materials=3;engineering=3")*/ - - -///////// Circuitboards - -/obj/item/circuitboard/mecha - name = "Exosuit Circuit board" - icon = 'icons/obj/module.dmi' - icon_state = "std_mod" - item_state = "electronic" - board_type = "other" - flags = CONDUCT - force = 5.0 - w_class = WEIGHT_CLASS_SMALL - throwforce = 5.0 - throw_speed = 3 - throw_range = 15 - -/obj/item/circuitboard/mecha/ripley - origin_tech = "programming=2" - -/obj/item/circuitboard/mecha/ripley/peripherals - name = "Circuit board (Ripley Peripherals Control module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/ripley/main - name = "Circuit board (Ripley Central Control module)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/gygax - origin_tech = "programming=4;combat=3;engineering=3" - -/obj/item/circuitboard/mecha/gygax/peripherals - name = "Circuit board (Gygax Peripherals Control module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/gygax/targeting - name = "Circuit board (Gygax Weapon Control and Targeting module)" - icon_state = "mcontroller" - origin_tech = "programming=4;combat=4" - -/obj/item/circuitboard/mecha/gygax/main - name = "Circuit board (Gygax Central Control module)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/durand - origin_tech = "programming=4;combat=3;engineering=3" - -/obj/item/circuitboard/mecha/durand/peripherals - name = "Circuit board (Durand Peripherals Control module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/durand/targeting - name = "Circuit board (Durand Weapon Control and Targeting module)" - icon_state = "mcontroller" - origin_tech = "programming=4;combat=4;engineering=3" - -/obj/item/circuitboard/mecha/durand/main - name = "Circuit board (Durand Central Control module)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/phazon - origin_tech = "programming=5;plasmatech=4" - -/obj/item/circuitboard/mecha/phazon/peripherals - name = "Circuit board (Phazon Peripherals Control module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/phazon/targeting - name = "Circuit board (Phazon Weapon Control and Targeting module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/phazon/main - name = "Circuit board (Phazon Central Control module)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/honker - origin_tech = "programming=3;engineering=3" - -/obj/item/circuitboard/mecha/honker/peripherals - name = "Circuit board (H.O.N.K Peripherals Control module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/honker/targeting - name = "Circuit board (H.O.N.K Weapon Control and Targeting module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/honker/main - name = "Circuit board (H.O.N.K Central Control module)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/reticence - origin_tech = "programming=3;engineering=3" - -/obj/item/circuitboard/mecha/reticence/peripherals - name = "circuit board (Reticence Peripherals Control module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/reticence/targeting - name = "circuit board (Reticence Weapon Control and Targeting module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/reticence/main - name = "circuit board (Reticence Central Control module)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/odysseus - origin_tech = "programming=3;biotech=3" - -/obj/item/circuitboard/mecha/odysseus/peripherals - name = "Circuit board (Odysseus Peripherals Control module)" - icon_state = "mcontroller" - -/obj/item/circuitboard/mecha/odysseus/main - name = "Circuit board (Odysseus Central Control module)" - icon_state = "mainboard" - -/obj/item/circuitboard/mecha/pod - name = "Circuit board (Space Pod Mainboard)" - icon_state = "mainboard" +///////////////////////// +////// Mecha Parts ////// +///////////////////////// + +/obj/item/mecha_parts + name = "mecha part" + icon = 'icons/mecha/mech_construct.dmi' + icon_state = "blank" + w_class = WEIGHT_CLASS_GIGANTIC + flags = CONDUCT + origin_tech = "programming=2;materials=2;engineering=2" + + +/obj/item/mecha_parts/chassis + name="Mecha Chassis" + icon_state = "backbone" + var/datum/construction/construct + flags = CONDUCT + +/obj/item/mecha_parts/chassis/Destroy() + QDEL_NULL(construct) + return ..() + +/obj/item/mecha_parts/chassis/attackby(obj/item/W, mob/user, params) + if(!construct || !construct.action(W, user)) + return ..() + +/obj/item/mecha_parts/chassis/attack_hand() + return + +/////////// Ripley + +/obj/item/mecha_parts/chassis/ripley + name = "Ripley Chassis" + +/obj/item/mecha_parts/chassis/ripley/New() + ..() + construct = new /datum/construction/mecha/ripley_chassis(src) + +/obj/item/mecha_parts/part/ripley_torso + name="Ripley Torso" + desc="A torso part of Ripley APLU. Contains power unit, processing core and life support systems." + icon_state = "ripley_harness" + origin_tech = "programming=2;materials=2;biotech=2;engineering=2" + +/obj/item/mecha_parts/part/ripley_left_arm + name="Ripley Left Arm" + desc="A Ripley APLU left arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "ripley_l_arm" + +/obj/item/mecha_parts/part/ripley_right_arm + name="Ripley Right Arm" + desc="A Ripley APLU right arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "ripley_r_arm" + +/obj/item/mecha_parts/part/ripley_left_leg + name="Ripley Left Leg" + desc="A Ripley APLU left leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "ripley_l_leg" + +/obj/item/mecha_parts/part/ripley_right_leg + name="Ripley Right Leg" + desc="A Ripley APLU right leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "ripley_r_leg" + +///////// Gygax + +/obj/item/mecha_parts/chassis/gygax + name = "Gygax Chassis" + +/obj/item/mecha_parts/chassis/gygax/New() + ..() + construct = new /datum/construction/mecha/gygax_chassis(src) + +/obj/item/mecha_parts/part/gygax_torso + name="Gygax Torso" + desc="A torso part of Gygax. Contains power unit, processing core and life support systems. Has an additional equipment slot." + icon_state = "gygax_harness" + origin_tech = "programming=2;materials=4;biotech=3;engineering=3" + +/obj/item/mecha_parts/part/gygax_head + name="Gygax Head" + desc="A Gygax head. Houses advanced surveilance and targeting sensors." + icon_state = "gygax_head" + origin_tech = "programming=2;materials=4;magnets=3;engineering=3" + +/obj/item/mecha_parts/part/gygax_left_arm + name="Gygax Left Arm" + desc="A Gygax left arm. Data and power sockets are compatible with most exosuit tools and weapons." + icon_state = "gygax_l_arm" + origin_tech = "programming=2;materials=4;engineering=3" + +/obj/item/mecha_parts/part/gygax_right_arm + name="Gygax Right Arm" + desc="A Gygax right arm. Data and power sockets are compatible with most exosuit tools and weapons." + icon_state = "gygax_r_arm" + origin_tech = "programming=2;materials=4;engineering=3" + +/obj/item/mecha_parts/part/gygax_left_leg + name="Gygax Left Leg" + icon_state = "gygax_l_leg" + origin_tech = "programming=2;materials=4;engineering=3" + +/obj/item/mecha_parts/part/gygax_right_leg + name="Gygax Right Leg" + icon_state = "gygax_r_leg" + origin_tech = "programming=2;materials=4;engineering=3" + +/obj/item/mecha_parts/part/gygax_armour + name="Gygax Armour Plates" + icon_state = "gygax_armour" + origin_tech = "materials=6;combat=4;engineering=4" + + +//////////// Durand + +/obj/item/mecha_parts/chassis/durand + name = "Durand Chassis" + +/obj/item/mecha_parts/chassis/durand/New() + ..() + construct = new /datum/construction/mecha/durand_chassis(src) + +/obj/item/mecha_parts/part/durand_torso + name="Durand Torso" + icon_state = "durand_harness" + origin_tech = "programming=2;materials=3;biotech=3;engineering=3" + +/obj/item/mecha_parts/part/durand_head + name="Durand Head" + icon_state = "durand_head" + origin_tech = "programming=2;materials=3;magnets=3;engineering=3" + +/obj/item/mecha_parts/part/durand_left_arm + name="Durand Left Arm" + icon_state = "durand_l_arm" + origin_tech = "programming=2;materials=3;engineering=3" + +/obj/item/mecha_parts/part/durand_right_arm + name="Durand Right Arm" + icon_state = "durand_r_arm" + origin_tech = "programming=2;materials=3;engineering=3" + +/obj/item/mecha_parts/part/durand_left_leg + name="Durand Left Leg" + icon_state = "durand_l_leg" + origin_tech = "programming=2;materials=3;engineering=3" + +/obj/item/mecha_parts/part/durand_right_leg + name="Durand Right Leg" + icon_state = "durand_r_leg" + origin_tech = "programming=2;materials=3;engineering=3" + +/obj/item/mecha_parts/part/durand_armor + name="Durand Armour Plates" + icon_state = "durand_armor" + origin_tech = "materials=5;combat=4;engineering=4" + + + +////////// Firefighter + +/obj/item/mecha_parts/chassis/firefighter + name = "Firefighter Chassis" + +/obj/item/mecha_parts/chassis/firefighter/New() + ..() + construct = new /datum/construction/mecha/firefighter_chassis(src) + +////////// HONK + +/obj/item/mecha_parts/chassis/honker + name = "H.O.N.K Chassis" + +/obj/item/mecha_parts/chassis/honker/New() + ..() + construct = new /datum/construction/mecha/honker_chassis(src) + +/obj/item/mecha_parts/part/honker_torso + name="H.O.N.K Torso" + icon_state = "honker_harness" + +/obj/item/mecha_parts/part/honker_head + name="H.O.N.K Head" + icon_state = "honker_head" + +/obj/item/mecha_parts/part/honker_left_arm + name="H.O.N.K Left Arm" + icon_state = "honker_l_arm" + +/obj/item/mecha_parts/part/honker_right_arm + name="H.O.N.K Right Arm" + icon_state = "honker_r_arm" + +/obj/item/mecha_parts/part/honker_left_leg + name="H.O.N.K Left Leg" + icon_state = "honker_l_leg" + +/obj/item/mecha_parts/part/honker_right_leg + name="H.O.N.K Right Leg" + icon_state = "honker_r_leg" + + +////////// Reticence + +/obj/item/mecha_parts/chassis/reticence + name = "Reticence Chassis" + +/obj/item/mecha_parts/chassis/reticence/New() + ..() + construct = new /datum/construction/mecha/reticence_chassis(src) + +/obj/effect/dummy/mecha_emote_step + var/emote + +/obj/effect/dummy/mecha_emote_step/New(e) + emote = e + +/obj/item/mecha_parts/chassis/reticence/hear_message(mob/living/M, msg) + if(!istype(M) || !istype(construct, /datum/construction/mecha/reticence)) + return + // is the current step the dummy emote object? + var/list/steps = construct.steps + if(steps[steps.len]["key"] == /obj/effect/dummy/mecha_emote_step) + construct.action(new /obj/effect/dummy/mecha_emote_step(msg), M) + +/obj/item/mecha_parts/part/reticence_torso + name = "Reticence Torso" + icon_state = "reticence_harness" + +/obj/item/mecha_parts/part/reticence_head + name = "Reticence Head" + icon_state = "reticence_head" + +/obj/item/mecha_parts/part/reticence_left_arm + name = "Reticence Left Arm" + icon_state = "reticence_l_arm" + +/obj/item/mecha_parts/part/reticence_right_arm + name = "Reticence Right Arm" + icon_state = "reticence_r_arm" + +/obj/item/mecha_parts/part/reticence_left_leg + name = "Reticence Left Leg" + icon_state = "reticence_l_leg" + +/obj/item/mecha_parts/part/reticence_right_leg + name = "Reticence Right Leg" + icon_state = "reticence_r_leg" + + +////////// Phazon + +/obj/item/mecha_parts/chassis/phazon + name = "Phazon Chassis" + +/obj/item/mecha_parts/chassis/phazon/New() + ..() + construct = new /datum/construction/mecha/phazon_chassis(src) + +/obj/item/mecha_parts/part/phazon_torso + name="Phazon Torso" + icon_state = "phazon_harness" + origin_tech = "programming=4;materials=4;bluespace=4;plasmatech=5" + +/obj/item/mecha_parts/part/phazon_head + name="Phazon Head" + icon_state = "phazon_head" + origin_tech = "programming=3;materials=3;magnets=3" + +/obj/item/mecha_parts/part/phazon_left_arm + name="Phazon Left Arm" + icon_state = "phazon_l_arm" + origin_tech = "materials=3;bluespace=3;magnets=3" + +/obj/item/mecha_parts/part/phazon_right_arm + name="Phazon Right Arm" + icon_state = "phazon_r_arm" + origin_tech = "materials=3;bluespace=3;magnets=3" + +/obj/item/mecha_parts/part/phazon_left_leg + name="Phazon Left Leg" + icon_state = "phazon_l_leg" + origin_tech = "materials=3;bluespace=3;magnets=3" + +/obj/item/mecha_parts/part/phazon_right_leg + name="Phazon Right Leg" + icon_state = "phazon_r_leg" + origin_tech = "materials=3;bluespace=3;magnets=3" + +/obj/item/mecha_parts/part/phazon_armor + name="Phazon armor" + desc="Phazon armor plates. They are layered with plasma to protect the pilot from the stress of phasing and have unusual properties." + icon_state = "phazon_armor" + origin_tech = "materials=4;bluespace=4;plasmatech=5" + +///////// Odysseus +/obj/item/mecha_parts/chassis/odysseus + name = "Odysseus Chassis" + +/obj/item/mecha_parts/chassis/odysseus/New() + ..() + construct = new /datum/construction/mecha/odysseus_chassis(src) + +/obj/item/mecha_parts/part/odysseus_head + name="Odysseus Head" + icon_state = "odysseus_head" + +/obj/item/mecha_parts/part/odysseus_torso + name="Odysseus Torso" + desc="A torso part of Odysseus. Contains power unit, processing core and life support systems." + icon_state = "odysseus_torso" + origin_tech = "programming=2;materials=2;biotech=2;engineering=2" + +/obj/item/mecha_parts/part/odysseus_left_arm + name="Odysseus Left Arm" + desc="An Odysseus left arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "odysseus_l_arm" + +/obj/item/mecha_parts/part/odysseus_right_arm + name="Odysseus Right Arm" + desc="An Odysseus right arm. Data and power sockets are compatible with most exosuit tools." + icon_state = "odysseus_r_arm" + +/obj/item/mecha_parts/part/odysseus_left_leg + name="Odysseus Left Leg" + desc="An Odysseus left leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "odysseus_l_leg" + +/obj/item/mecha_parts/part/odysseus_right_leg + name="Odysseus Right Leg" + desc="A Odysseus right leg. Contains somewhat complex servodrives and balance maintaining systems." + icon_state = "odysseus_r_leg" + +/*/obj/item/mecha_parts/part/odysseus_armour + name="Odysseus Carapace" + icon_state = "odysseus_armour" + origin_tech = "materials=3;engineering=3")*/ + + +///////// Circuitboards + +/obj/item/circuitboard/mecha + name = "Exosuit Circuit board" + icon = 'icons/obj/module.dmi' + icon_state = "std_mod" + item_state = "electronic" + board_type = "other" + flags = CONDUCT + force = 5.0 + w_class = WEIGHT_CLASS_SMALL + throwforce = 5.0 + throw_speed = 3 + throw_range = 15 + +/obj/item/circuitboard/mecha/ripley + origin_tech = "programming=2" + +/obj/item/circuitboard/mecha/ripley/peripherals + name = "Circuit board (Ripley Peripherals Control module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/ripley/main + name = "Circuit board (Ripley Central Control module)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/gygax + origin_tech = "programming=4;combat=3;engineering=3" + +/obj/item/circuitboard/mecha/gygax/peripherals + name = "Circuit board (Gygax Peripherals Control module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/gygax/targeting + name = "Circuit board (Gygax Weapon Control and Targeting module)" + icon_state = "mcontroller" + origin_tech = "programming=4;combat=4" + +/obj/item/circuitboard/mecha/gygax/main + name = "Circuit board (Gygax Central Control module)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/durand + origin_tech = "programming=4;combat=3;engineering=3" + +/obj/item/circuitboard/mecha/durand/peripherals + name = "Circuit board (Durand Peripherals Control module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/durand/targeting + name = "Circuit board (Durand Weapon Control and Targeting module)" + icon_state = "mcontroller" + origin_tech = "programming=4;combat=4;engineering=3" + +/obj/item/circuitboard/mecha/durand/main + name = "Circuit board (Durand Central Control module)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/phazon + origin_tech = "programming=5;plasmatech=4" + +/obj/item/circuitboard/mecha/phazon/peripherals + name = "Circuit board (Phazon Peripherals Control module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/phazon/targeting + name = "Circuit board (Phazon Weapon Control and Targeting module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/phazon/main + name = "Circuit board (Phazon Central Control module)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/honker + origin_tech = "programming=3;engineering=3" + +/obj/item/circuitboard/mecha/honker/peripherals + name = "Circuit board (H.O.N.K Peripherals Control module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/honker/targeting + name = "Circuit board (H.O.N.K Weapon Control and Targeting module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/honker/main + name = "Circuit board (H.O.N.K Central Control module)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/reticence + origin_tech = "programming=3;engineering=3" + +/obj/item/circuitboard/mecha/reticence/peripherals + name = "circuit board (Reticence Peripherals Control module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/reticence/targeting + name = "circuit board (Reticence Weapon Control and Targeting module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/reticence/main + name = "circuit board (Reticence Central Control module)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/odysseus + origin_tech = "programming=3;biotech=3" + +/obj/item/circuitboard/mecha/odysseus/peripherals + name = "Circuit board (Odysseus Peripherals Control module)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/odysseus/main + name = "Circuit board (Odysseus Central Control module)" + icon_state = "mainboard" + +/obj/item/circuitboard/mecha/pod + name = "Circuit board (Space Pod Mainboard)" + icon_state = "mainboard" diff --git a/code/game/mecha/mecha_wreckage.dm b/code/game/mecha/mecha_wreckage.dm index 9f01dc83af4..d061d2e2005 100644 --- a/code/game/mecha/mecha_wreckage.dm +++ b/code/game/mecha/mecha_wreckage.dm @@ -1,215 +1,215 @@ -/////////////////////////////////// -//////// Mecha wreckage //////// -/////////////////////////////////// - - -/obj/structure/mecha_wreckage - name = "exosuit wreckage" - desc = "Remains of some unfortunate mecha. Completely unrepairable, but perhaps something can be salvaged." - icon = 'icons/mecha/mecha.dmi' - density = TRUE - anchored = FALSE - opacity = 0 - var/list/welder_salvage = list(/obj/item/stack/sheet/plasteel, /obj/item/stack/sheet/metal, /obj/item/stack/rods) - var/salvage_num = 5 - var/list/crowbar_salvage = list() - var/wires_removed = FALSE - var/mob/living/silicon/ai/AI //AIs to be salvaged - var/list/parts - -/obj/structure/mecha_wreckage/Initialize(mapload, mob/living/silicon/ai/AI_pilot) - . = ..() - if(parts) - for(var/i in 1 to 2) - if(!parts.len) - break - if(prob(60)) - continue - var/part = pick(parts) - welder_salvage += part - parts = null - if(!AI_pilot) //Type-checking for this is already done in mecha/Destroy() - return - AI = AI_pilot - AI.apply_damage(150, BURN) //Give the AI a bit of damage from the "shock" of being suddenly shut down - AI.death() //The damage is not enough to kill the AI, but to be 'corrupted files' in need of repair. - AI.forceMove(src) //Put the dead AI inside the wreckage for recovery - add_overlay(mutable_appearance('icons/obj/projectiles.dmi', "green_laser")) //Overlay for the recovery beacon - AI.controlled_mech = null - AI.remote_control = null - -/obj/structure/mecha_wreckage/Destroy() - QDEL_NULL(AI) - QDEL_LIST(crowbar_salvage) - return ..() - -/obj/structure/mecha_wreckage/examine(mob/user) - . = ..() - if(!AI) - return - . += "The AI recovery beacon is active." - -/obj/structure/mecha_wreckage/crowbar_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(crowbar_salvage.len) - var/obj/S = pick(crowbar_salvage) - S.forceMove(user.drop_location()) - user.visible_message("[user] pries [S] from [src].", "You pry [S] from [src].") - crowbar_salvage -= S - return - to_chat(user, "You don't see anything that can be cut with [I]!") - -/obj/structure/mecha_wreckage/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(salvage_num <= 0 || !length(welder_salvage)) - to_chat(user, "You don't see anything that can be cut with [I]!") - return - if(prob(30)) - to_chat(user, "You fail to salvage anything valuable from [src]!") - return - var/type = pick(welder_salvage) - var/N = new type(get_turf(user)) - user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") - if(!istype(N, /obj/item/stack)) - welder_salvage -= type - salvage_num-- - -/obj/structure/mecha_wreckage/wirecutter_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(wires_removed) - to_chat(user, "You don't see anything that can be cut with [I]!") - return - var/N = new /obj/item/stack/cable_coil(get_turf(user), rand(1, 3)) - user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") - wires_removed = TRUE - -/obj/structure/mecha_wreckage/transfer_ai(interaction, mob/user, null, obj/item/aicard/card) - if(!..()) - return - - //Proc called on the wreck by the AI card. - if(interaction != AI_TRANS_TO_CARD) //AIs can only be transferred in one direction, from the wreck to the card. - return - if(!AI) //No AI in the wreck - to_chat(user, "No AI backups found.") - return - cut_overlays() //Remove the recovery beacon overlay - AI.forceMove(card) //Move the dead AI to the card. - if(AI.client) //AI player is still in the dead AI and is connected - to_chat(AI, "The remains of your file system have been recovered on a mobile storage device.") - else //Give the AI a heads-up that it is probably going to get fixed. - AI.notify_ghost_cloning("You have been recovered from the wreckage!", source = card) - to_chat(user, "Backup files recovered: [AI.name] ([rand(1000, 9999)].exe) salvaged from [name] and stored within local memory.") - AI = null - -/obj/structure/mecha_wreckage/gygax - name = "\improper Gygax wreckage" - icon_state = "gygax-broken" - parts = list( - /obj/item/mecha_parts/part/gygax_torso, - /obj/item/mecha_parts/part/gygax_head, - /obj/item/mecha_parts/part/gygax_left_arm, - /obj/item/mecha_parts/part/gygax_right_arm, - /obj/item/mecha_parts/part/gygax_left_leg, - /obj/item/mecha_parts/part/gygax_right_leg - ) - -/obj/structure/mecha_wreckage/gygax/dark - name = "\improper Dark Gygax wreckage" - icon_state = "darkgygax-broken" - -/obj/structure/mecha_wreckage/marauder - name = "\improper Marauder wreckage" - icon_state = "marauder-broken" - -/obj/structure/mecha_wreckage/mauler - name = "\improper Mauler wreckage" - icon_state = "mauler-broken" - desc = "The syndicate won't be very happy about this..." - -/obj/structure/mecha_wreckage/seraph - name = "\improper Seraph wreckage" - icon_state = "seraph-broken" - -/obj/structure/mecha_wreckage/reticence - name = "\improper Reticence wreckage" - icon_state = "reticence-broken" - color = "#87878715" - desc = "..." - -/obj/structure/mecha_wreckage/ripley - name = "\improper Ripley wreckage" - icon_state = "ripley-broken" - parts = list(/obj/item/mecha_parts/part/ripley_torso, - /obj/item/mecha_parts/part/ripley_left_arm, - /obj/item/mecha_parts/part/ripley_right_arm, - /obj/item/mecha_parts/part/ripley_left_leg, - /obj/item/mecha_parts/part/ripley_right_leg) - -/obj/structure/mecha_wreckage/ripley/mkii - name = "\improper Ripley MK-II wreckage" - icon_state = "ripleymkii-broken" - -/obj/structure/mecha_wreckage/ripley/firefighter - name = "\improper Firefighter wreckage" - icon_state = "firefighter-broken" - parts = list(/obj/item/mecha_parts/part/ripley_torso, - /obj/item/mecha_parts/part/ripley_left_arm, - /obj/item/mecha_parts/part/ripley_right_arm, - /obj/item/mecha_parts/part/ripley_left_leg, - /obj/item/mecha_parts/part/ripley_right_leg, - /obj/item/clothing/suit/fire) - -/obj/structure/mecha_wreckage/ripley/deathripley - name = "\improper Death-Ripley wreckage" - icon_state = "deathripley-broken" - parts = null - -/obj/structure/mecha_wreckage/honker - name = "\improper H.O.N.K wreckage" - icon_state = "honker-broken" - desc = "All is right in the universe." - parts = list( - /obj/item/mecha_parts/chassis/honker, - /obj/item/mecha_parts/part/honker_torso, - /obj/item/mecha_parts/part/honker_head, - /obj/item/mecha_parts/part/honker_left_arm, - /obj/item/mecha_parts/part/honker_right_arm, - /obj/item/mecha_parts/part/honker_left_leg, - /obj/item/mecha_parts/part/honker_right_leg) - -/obj/structure/mecha_wreckage/durand - name = "\improper Durand wreckage" - icon_state = "durand-broken" - parts = list( - /obj/item/mecha_parts/part/durand_torso, - /obj/item/mecha_parts/part/durand_head, - /obj/item/mecha_parts/part/durand_left_arm, - /obj/item/mecha_parts/part/durand_right_arm, - /obj/item/mecha_parts/part/durand_left_leg, - /obj/item/mecha_parts/part/durand_right_leg) - -/obj/structure/mecha_wreckage/durand/old - icon_state = "old_durand-broken" - -/obj/structure/mecha_wreckage/phazon - name = "\improper Phazon wreckage" - icon_state = "phazon-broken" - - -/obj/structure/mecha_wreckage/odysseus - name = "\improper Odysseus wreckage" - icon_state = "odysseus-broken" - parts = list( - /obj/item/mecha_parts/part/odysseus_torso, - /obj/item/mecha_parts/part/odysseus_head, - /obj/item/mecha_parts/part/odysseus_left_arm, - /obj/item/mecha_parts/part/odysseus_right_arm, - /obj/item/mecha_parts/part/odysseus_left_leg, - /obj/item/mecha_parts/part/odysseus_right_leg) +/////////////////////////////////// +//////// Mecha wreckage //////// +/////////////////////////////////// + + +/obj/structure/mecha_wreckage + name = "exosuit wreckage" + desc = "Remains of some unfortunate mecha. Completely unrepairable, but perhaps something can be salvaged." + icon = 'icons/mecha/mecha.dmi' + density = TRUE + anchored = FALSE + opacity = 0 + var/list/welder_salvage = list(/obj/item/stack/sheet/plasteel, /obj/item/stack/sheet/metal, /obj/item/stack/rods) + var/salvage_num = 5 + var/list/crowbar_salvage = list() + var/wires_removed = FALSE + var/mob/living/silicon/ai/AI //AIs to be salvaged + var/list/parts + +/obj/structure/mecha_wreckage/Initialize(mapload, mob/living/silicon/ai/AI_pilot) + . = ..() + if(parts) + for(var/i in 1 to 2) + if(!parts.len) + break + if(prob(60)) + continue + var/part = pick(parts) + welder_salvage += part + parts = null + if(!AI_pilot) //Type-checking for this is already done in mecha/Destroy() + return + AI = AI_pilot + AI.apply_damage(150, BURN) //Give the AI a bit of damage from the "shock" of being suddenly shut down + AI.death() //The damage is not enough to kill the AI, but to be 'corrupted files' in need of repair. + AI.forceMove(src) //Put the dead AI inside the wreckage for recovery + add_overlay(mutable_appearance('icons/obj/projectiles.dmi', "green_laser")) //Overlay for the recovery beacon + AI.controlled_mech = null + AI.remote_control = null + +/obj/structure/mecha_wreckage/Destroy() + QDEL_NULL(AI) + QDEL_LIST(crowbar_salvage) + return ..() + +/obj/structure/mecha_wreckage/examine(mob/user) + . = ..() + if(!AI) + return + . += "The AI recovery beacon is active." + +/obj/structure/mecha_wreckage/crowbar_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(crowbar_salvage.len) + var/obj/S = pick(crowbar_salvage) + S.forceMove(user.drop_location()) + user.visible_message("[user] pries [S] from [src].", "You pry [S] from [src].") + crowbar_salvage -= S + return + to_chat(user, "You don't see anything that can be cut with [I]!") + +/obj/structure/mecha_wreckage/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(salvage_num <= 0 || !length(welder_salvage)) + to_chat(user, "You don't see anything that can be cut with [I]!") + return + if(prob(30)) + to_chat(user, "You fail to salvage anything valuable from [src]!") + return + var/type = pick(welder_salvage) + var/N = new type(get_turf(user)) + user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") + if(!istype(N, /obj/item/stack)) + welder_salvage -= type + salvage_num-- + +/obj/structure/mecha_wreckage/wirecutter_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(wires_removed) + to_chat(user, "You don't see anything that can be cut with [I]!") + return + var/N = new /obj/item/stack/cable_coil(get_turf(user), rand(1, 3)) + user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") + wires_removed = TRUE + +/obj/structure/mecha_wreckage/transfer_ai(interaction, mob/user, null, obj/item/aicard/card) + if(!..()) + return + + //Proc called on the wreck by the AI card. + if(interaction != AI_TRANS_TO_CARD) //AIs can only be transferred in one direction, from the wreck to the card. + return + if(!AI) //No AI in the wreck + to_chat(user, "No AI backups found.") + return + cut_overlays() //Remove the recovery beacon overlay + AI.forceMove(card) //Move the dead AI to the card. + if(AI.client) //AI player is still in the dead AI and is connected + to_chat(AI, "The remains of your file system have been recovered on a mobile storage device.") + else //Give the AI a heads-up that it is probably going to get fixed. + AI.notify_ghost_cloning("You have been recovered from the wreckage!", source = card) + to_chat(user, "Backup files recovered: [AI.name] ([rand(1000, 9999)].exe) salvaged from [name] and stored within local memory.") + AI = null + +/obj/structure/mecha_wreckage/gygax + name = "\improper Gygax wreckage" + icon_state = "gygax-broken" + parts = list( + /obj/item/mecha_parts/part/gygax_torso, + /obj/item/mecha_parts/part/gygax_head, + /obj/item/mecha_parts/part/gygax_left_arm, + /obj/item/mecha_parts/part/gygax_right_arm, + /obj/item/mecha_parts/part/gygax_left_leg, + /obj/item/mecha_parts/part/gygax_right_leg + ) + +/obj/structure/mecha_wreckage/gygax/dark + name = "\improper Dark Gygax wreckage" + icon_state = "darkgygax-broken" + +/obj/structure/mecha_wreckage/marauder + name = "\improper Marauder wreckage" + icon_state = "marauder-broken" + +/obj/structure/mecha_wreckage/mauler + name = "\improper Mauler wreckage" + icon_state = "mauler-broken" + desc = "The syndicate won't be very happy about this..." + +/obj/structure/mecha_wreckage/seraph + name = "\improper Seraph wreckage" + icon_state = "seraph-broken" + +/obj/structure/mecha_wreckage/reticence + name = "\improper Reticence wreckage" + icon_state = "reticence-broken" + color = "#87878715" + desc = "..." + +/obj/structure/mecha_wreckage/ripley + name = "\improper Ripley wreckage" + icon_state = "ripley-broken" + parts = list(/obj/item/mecha_parts/part/ripley_torso, + /obj/item/mecha_parts/part/ripley_left_arm, + /obj/item/mecha_parts/part/ripley_right_arm, + /obj/item/mecha_parts/part/ripley_left_leg, + /obj/item/mecha_parts/part/ripley_right_leg) + +/obj/structure/mecha_wreckage/ripley/mkii + name = "\improper Ripley MK-II wreckage" + icon_state = "ripleymkii-broken" + +/obj/structure/mecha_wreckage/ripley/firefighter + name = "\improper Firefighter wreckage" + icon_state = "firefighter-broken" + parts = list(/obj/item/mecha_parts/part/ripley_torso, + /obj/item/mecha_parts/part/ripley_left_arm, + /obj/item/mecha_parts/part/ripley_right_arm, + /obj/item/mecha_parts/part/ripley_left_leg, + /obj/item/mecha_parts/part/ripley_right_leg, + /obj/item/clothing/suit/fire) + +/obj/structure/mecha_wreckage/ripley/deathripley + name = "\improper Death-Ripley wreckage" + icon_state = "deathripley-broken" + parts = null + +/obj/structure/mecha_wreckage/honker + name = "\improper H.O.N.K wreckage" + icon_state = "honker-broken" + desc = "All is right in the universe." + parts = list( + /obj/item/mecha_parts/chassis/honker, + /obj/item/mecha_parts/part/honker_torso, + /obj/item/mecha_parts/part/honker_head, + /obj/item/mecha_parts/part/honker_left_arm, + /obj/item/mecha_parts/part/honker_right_arm, + /obj/item/mecha_parts/part/honker_left_leg, + /obj/item/mecha_parts/part/honker_right_leg) + +/obj/structure/mecha_wreckage/durand + name = "\improper Durand wreckage" + icon_state = "durand-broken" + parts = list( + /obj/item/mecha_parts/part/durand_torso, + /obj/item/mecha_parts/part/durand_head, + /obj/item/mecha_parts/part/durand_left_arm, + /obj/item/mecha_parts/part/durand_right_arm, + /obj/item/mecha_parts/part/durand_left_leg, + /obj/item/mecha_parts/part/durand_right_leg) + +/obj/structure/mecha_wreckage/durand/old + icon_state = "old_durand-broken" + +/obj/structure/mecha_wreckage/phazon + name = "\improper Phazon wreckage" + icon_state = "phazon-broken" + + +/obj/structure/mecha_wreckage/odysseus + name = "\improper Odysseus wreckage" + icon_state = "odysseus-broken" + parts = list( + /obj/item/mecha_parts/part/odysseus_torso, + /obj/item/mecha_parts/part/odysseus_head, + /obj/item/mecha_parts/part/odysseus_left_arm, + /obj/item/mecha_parts/part/odysseus_right_arm, + /obj/item/mecha_parts/part/odysseus_left_leg, + /obj/item/mecha_parts/part/odysseus_right_leg) diff --git a/code/game/mecha/medical/medical.dm b/code/game/mecha/medical/medical.dm index 8aee3ef1f4c..2255885030c 100644 --- a/code/game/mecha/medical/medical.dm +++ b/code/game/mecha/medical/medical.dm @@ -1,7 +1,7 @@ -/obj/mecha/medical - turnsound = 'sound/mecha/mechmove01.ogg' - stepsound = 'sound/mecha/mechstep.ogg' - -/obj/mecha/medical/New() - ..() - trackers += new /obj/item/mecha_parts/mecha_tracking(src) +/obj/mecha/medical + turnsound = 'sound/mecha/mechmove01.ogg' + stepsound = 'sound/mecha/mechstep.ogg' + +/obj/mecha/medical/New() + ..() + trackers += new /obj/item/mecha_parts/mecha_tracking(src) diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index dcee2c1e74e..2cef4d49dd3 100644 --- a/code/game/mecha/medical/odysseus.dm +++ b/code/game/mecha/medical/odysseus.dm @@ -1,46 +1,46 @@ -/obj/mecha/medical/odysseus - desc = "These exosuits are developed and produced by Vey-Med. (© All rights reserved)." - name = "Odysseus" - icon_state = "odysseus" - initial_icon = "odysseus" - step_in = 3 - max_temperature = 15000 - max_integrity = 120 - wreckage = /obj/structure/mecha_wreckage/odysseus - internal_damage_threshold = 35 - deflect_chance = 15 - step_energy_drain = 6 - normal_step_energy_drain = 6 - var/builtin_hud_user = 0 - -/obj/mecha/medical/odysseus/moved_inside(var/mob/living/carbon/human/H) - . = ..() - if(. && ishuman(H)) - if(istype(H.glasses, /obj/item/clothing/glasses/hud)) - occupant_message("[H.glasses] prevent you from using the built-in medical hud.") - else - var/datum/atom_hud/data/human/medical/advanced/A = huds[DATA_HUD_MEDICAL_ADVANCED] - A.add_hud_to(H) - builtin_hud_user = 1 - -/obj/mecha/medical/odysseus/mmi_moved_inside(var/obj/item/mmi/mmi_as_oc, mob/user) - . = ..() - if(.) - if(occupant.client) - var/datum/atom_hud/A = huds[DATA_HUD_MEDICAL_ADVANCED] - A.add_hud_to(occupant) - builtin_hud_user = 1 - -/obj/mecha/medical/odysseus/go_out() - if(ishuman(occupant) && builtin_hud_user) - var/mob/living/carbon/human/H = occupant - var/datum/atom_hud/data/human/medical/advanced/A = huds[DATA_HUD_MEDICAL_ADVANCED] - A.remove_hud_from(H) - builtin_hud_user = 0 - else if((isbrain(occupant) || pilot_is_mmi()) && builtin_hud_user) - var/mob/living/carbon/brain/H = occupant - var/datum/atom_hud/A = huds[DATA_HUD_MEDICAL_ADVANCED] - A.remove_hud_from(H) - builtin_hud_user = 0 - - . = ..() \ No newline at end of file +/obj/mecha/medical/odysseus + desc = "These exosuits are developed and produced by Vey-Med. (© All rights reserved)." + name = "Odysseus" + icon_state = "odysseus" + initial_icon = "odysseus" + step_in = 3 + max_temperature = 15000 + max_integrity = 120 + wreckage = /obj/structure/mecha_wreckage/odysseus + internal_damage_threshold = 35 + deflect_chance = 15 + step_energy_drain = 6 + normal_step_energy_drain = 6 + var/builtin_hud_user = 0 + +/obj/mecha/medical/odysseus/moved_inside(var/mob/living/carbon/human/H) + . = ..() + if(. && ishuman(H)) + if(istype(H.glasses, /obj/item/clothing/glasses/hud)) + occupant_message("[H.glasses] prevent you from using the built-in medical hud.") + else + var/datum/atom_hud/data/human/medical/advanced/A = huds[DATA_HUD_MEDICAL_ADVANCED] + A.add_hud_to(H) + builtin_hud_user = 1 + +/obj/mecha/medical/odysseus/mmi_moved_inside(var/obj/item/mmi/mmi_as_oc, mob/user) + . = ..() + if(.) + if(occupant.client) + var/datum/atom_hud/A = huds[DATA_HUD_MEDICAL_ADVANCED] + A.add_hud_to(occupant) + builtin_hud_user = 1 + +/obj/mecha/medical/odysseus/go_out() + if(ishuman(occupant) && builtin_hud_user) + var/mob/living/carbon/human/H = occupant + var/datum/atom_hud/data/human/medical/advanced/A = huds[DATA_HUD_MEDICAL_ADVANCED] + A.remove_hud_from(H) + builtin_hud_user = 0 + else if((isbrain(occupant) || pilot_is_mmi()) && builtin_hud_user) + var/mob/living/carbon/brain/H = occupant + var/datum/atom_hud/A = huds[DATA_HUD_MEDICAL_ADVANCED] + A.remove_hud_from(H) + builtin_hud_user = 0 + + . = ..() diff --git a/code/game/mecha/paintkits.dm b/code/game/mecha/paintkits.dm index d11f87973db..861c80c4672 100644 --- a/code/game/mecha/paintkits.dm +++ b/code/game/mecha/paintkits.dm @@ -28,4 +28,4 @@ new_name = "APLU \"Strike the Earth!\"" new_desc = "Looks like an over worked, under maintained Ripley with some horrific damage." new_icon = "earth" - allowed_types = list("ripley","firefighter") \ No newline at end of file + allowed_types = list("ripley","firefighter") diff --git a/code/game/mecha/working/ripley.dm b/code/game/mecha/working/ripley.dm index 0e095f7ca12..0b106f2ab4c 100644 --- a/code/game/mecha/working/ripley.dm +++ b/code/game/mecha/working/ripley.dm @@ -1,207 +1,207 @@ -/obj/mecha/working/ripley - desc = "Autonomous Power Loader Unit. This newer model is refitted with powerful armour against the dangers of the EVA mining process." - name = "APLU \"Ripley\"" - icon_state = "ripley" - initial_icon = "ripley" - step_in = 4 //Move speed, lower is faster. - var/fast_pressure_step_in = 2 //step_in while in normal pressure conditions - var/slow_pressure_step_in = 4 //step_in while in better pressure conditions - max_temperature = 20000 - max_integrity = 200 - lights_power = 7 - deflect_chance = 15 - armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_equip = 6 - wreckage = /obj/structure/mecha_wreckage/ripley - var/list/cargo = new - var/cargo_capacity = 15 - var/hides = 0 - -/obj/mecha/working/ripley/Move() - . = ..() - if(.) - collect_ore() - update_pressure() - -/obj/mecha/working/ripley/proc/collect_ore() - if(locate(/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp) in equipment) - var/obj/structure/ore_box/ore_box = locate(/obj/structure/ore_box) in cargo - if(ore_box) - for(var/obj/item/stack/ore/ore in range(1, src)) - if(ore.Adjacent(src) && ((get_dir(src, ore) & dir) || ore.loc == loc)) //we can reach it and it's in front of us? grab it! - ore.forceMove(ore_box) - -/obj/mecha/working/ripley/Destroy() - for(var/i=1, i <= hides, i++) - new /obj/item/stack/sheet/animalhide/goliath_hide(loc) //If a goliath-plated ripley gets killed, all the plates drop - for(var/atom/movable/A in cargo) - A.forceMove(loc) - step_rand(A) - cargo.Cut() - return ..() - -/obj/mecha/working/ripley/go_out() - ..() - update_icon() - -/obj/mecha/working/ripley/moved_inside(mob/living/carbon/human/H) - ..() - update_icon() - -/obj/mecha/working/ripley/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user) - ..() - update_icon() - -/obj/mecha/working/ripley/update_icon() - ..() - if(hides) - cut_overlays() - if(hides < 3) - add_overlay(occupant ? "ripley-g" : "ripley-g-open") - else - add_overlay(occupant ? "ripley-g-full" : "ripley-g-full-open") - -/obj/mecha/working/ripley/firefighter - desc = "Standart APLU chassis was refitted with additional thermal protection and cistern." - name = "APLU \"Firefighter\"" - icon_state = "firefighter" - initial_icon = "firefighter" - max_temperature = 65000 - max_integrity = 250 - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - lights_power = 7 - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 60, "bio" = 0, "rad" = 70, "fire" = 100, "acid" = 100) - max_equip = 5 // More armor, less tools - wreckage = /obj/structure/mecha_wreckage/ripley/firefighter - -/obj/mecha/working/ripley/deathripley - desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" - name = "DEATH-RIPLEY" - icon_state = "deathripley" - initial_icon = "deathripley" - step_in = 3 - slow_pressure_step_in = 3 - opacity=0 - max_temperature = 65000 - max_integrity = 300 - lights_power = 7 - armor = list("melee" = 40, "bullet" = 40, "laser" = 40, "energy" = 0, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - wreckage = /obj/structure/mecha_wreckage/ripley/deathripley - step_energy_drain = 0 - normal_step_energy_drain = 0 - -/obj/mecha/working/ripley/deathripley/New() - ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill - ME.attach(src) - return - -/obj/mecha/working/ripley/mining - desc = "An old, dusty mining ripley." - name = "APLU \"Miner\"" - obj_integrity = 75 //Low starting health - -/obj/mecha/working/ripley/mining/New() - ..() - if(cell) - cell.charge = FLOOR(cell.charge * 0.25, 1) //Starts at very low charge - //Attach drill - if(prob(70)) //Maybe add a drill - if(prob(15)) //Possible diamond drill... Feeling lucky? - var/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill/D = new - D.attach(src) - else - var/obj/item/mecha_parts/mecha_equipment/drill/D = new - D.attach(src) - - else //Add plasma cutter if no drill - var/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/P = new - P.attach(src) - - //Add ore box to cargo - cargo.Add(new /obj/structure/ore_box(src)) - - //Attach hydraulic clamp - var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new - HC.attach(src) - for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily - qdel(B) - - var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new - scanner.attach(src) - -/obj/mecha/working/ripley/Exit(atom/movable/O) - if(O in cargo) - return 0 - return ..() - -/obj/mecha/working/ripley/Topic(href, href_list) - ..() - if(href_list["drop_from_cargo"]) - var/obj/O = locate(href_list["drop_from_cargo"]) - if(O && O in cargo) - occupant_message("You unload [O].") - O.loc = get_turf(src) - cargo -= O - var/turf/T = get_turf(O) - if(T) - T.Entered(O) - log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - cargo.len]") - return - - - -/obj/mecha/working/ripley/get_stats_part() - var/output = ..() - output += "Cargo Compartment Contents:
        " - if(cargo.len) - for(var/obj/O in cargo) - output += "Unload : [O]
        " - else - output += "Nothing" - output += "
        " - return output - -/obj/mecha/working/ripley/Destroy() - for(var/mob/M in src) - if(M == occupant) - continue - M.loc = get_turf(src) - M.loc.Entered(M) - step_rand(M) - for(var/atom/movable/A in cargo) - A.loc = get_turf(src) - var/turf/T = get_turf(A) - if(T) - T.Entered(A) - step_rand(A) - return ..() - -/obj/mecha/working/ripley/ex_act(severity) - ..() - for(var/X in cargo) - var/obj/O = X - if(prob(30 / severity)) - cargo -= O - O.forceMove(drop_location()) - -/obj/mecha/working/ripley/proc/update_pressure() - var/turf/T = get_turf(loc) - - if(lavaland_equipment_pressure_check(T)) - step_in = fast_pressure_step_in - for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) - drill.equip_cooldown = initial(drill.equip_cooldown)/2 - else - step_in = slow_pressure_step_in - for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) - drill.equip_cooldown = initial(drill.equip_cooldown) - -/obj/mecha/working/ripley/emag_act(mob/user) - if(!emagged) - emagged = TRUE - to_chat(user, "You slide the card through [src]'s ID slot.") - playsound(loc, "sparks", 100, 1) - desc += "
        The mech's equipment slots spark dangerously!" - else - to_chat(user, "[src]'s ID slot rejects the card.") +/obj/mecha/working/ripley + desc = "Autonomous Power Loader Unit. This newer model is refitted with powerful armour against the dangers of the EVA mining process." + name = "APLU \"Ripley\"" + icon_state = "ripley" + initial_icon = "ripley" + step_in = 4 //Move speed, lower is faster. + var/fast_pressure_step_in = 2 //step_in while in normal pressure conditions + var/slow_pressure_step_in = 4 //step_in while in better pressure conditions + max_temperature = 20000 + max_integrity = 200 + lights_power = 7 + deflect_chance = 15 + armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_equip = 6 + wreckage = /obj/structure/mecha_wreckage/ripley + var/list/cargo = new + var/cargo_capacity = 15 + var/hides = 0 + +/obj/mecha/working/ripley/Move() + . = ..() + if(.) + collect_ore() + update_pressure() + +/obj/mecha/working/ripley/proc/collect_ore() + if(locate(/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp) in equipment) + var/obj/structure/ore_box/ore_box = locate(/obj/structure/ore_box) in cargo + if(ore_box) + for(var/obj/item/stack/ore/ore in range(1, src)) + if(ore.Adjacent(src) && ((get_dir(src, ore) & dir) || ore.loc == loc)) //we can reach it and it's in front of us? grab it! + ore.forceMove(ore_box) + +/obj/mecha/working/ripley/Destroy() + for(var/i=1, i <= hides, i++) + new /obj/item/stack/sheet/animalhide/goliath_hide(loc) //If a goliath-plated ripley gets killed, all the plates drop + for(var/atom/movable/A in cargo) + A.forceMove(loc) + step_rand(A) + cargo.Cut() + return ..() + +/obj/mecha/working/ripley/go_out() + ..() + update_icon() + +/obj/mecha/working/ripley/moved_inside(mob/living/carbon/human/H) + ..() + update_icon() + +/obj/mecha/working/ripley/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user) + ..() + update_icon() + +/obj/mecha/working/ripley/update_icon() + ..() + if(hides) + cut_overlays() + if(hides < 3) + add_overlay(occupant ? "ripley-g" : "ripley-g-open") + else + add_overlay(occupant ? "ripley-g-full" : "ripley-g-full-open") + +/obj/mecha/working/ripley/firefighter + desc = "Standart APLU chassis was refitted with additional thermal protection and cistern." + name = "APLU \"Firefighter\"" + icon_state = "firefighter" + initial_icon = "firefighter" + max_temperature = 65000 + max_integrity = 250 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + lights_power = 7 + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 60, "bio" = 0, "rad" = 70, "fire" = 100, "acid" = 100) + max_equip = 5 // More armor, less tools + wreckage = /obj/structure/mecha_wreckage/ripley/firefighter + +/obj/mecha/working/ripley/deathripley + desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" + name = "DEATH-RIPLEY" + icon_state = "deathripley" + initial_icon = "deathripley" + step_in = 3 + slow_pressure_step_in = 3 + opacity=0 + max_temperature = 65000 + max_integrity = 300 + lights_power = 7 + armor = list("melee" = 40, "bullet" = 40, "laser" = 40, "energy" = 0, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + wreckage = /obj/structure/mecha_wreckage/ripley/deathripley + step_energy_drain = 0 + normal_step_energy_drain = 0 + +/obj/mecha/working/ripley/deathripley/New() + ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/kill + ME.attach(src) + return + +/obj/mecha/working/ripley/mining + desc = "An old, dusty mining ripley." + name = "APLU \"Miner\"" + obj_integrity = 75 //Low starting health + +/obj/mecha/working/ripley/mining/New() + ..() + if(cell) + cell.charge = FLOOR(cell.charge * 0.25, 1) //Starts at very low charge + //Attach drill + if(prob(70)) //Maybe add a drill + if(prob(15)) //Possible diamond drill... Feeling lucky? + var/obj/item/mecha_parts/mecha_equipment/drill/diamonddrill/D = new + D.attach(src) + else + var/obj/item/mecha_parts/mecha_equipment/drill/D = new + D.attach(src) + + else //Add plasma cutter if no drill + var/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/P = new + P.attach(src) + + //Add ore box to cargo + cargo.Add(new /obj/structure/ore_box(src)) + + //Attach hydraulic clamp + var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new + HC.attach(src) + for(var/obj/item/mecha_parts/mecha_tracking/B in trackers)//Deletes the beacon so it can't be found easily + qdel(B) + + var/obj/item/mecha_parts/mecha_equipment/mining_scanner/scanner = new + scanner.attach(src) + +/obj/mecha/working/ripley/Exit(atom/movable/O) + if(O in cargo) + return 0 + return ..() + +/obj/mecha/working/ripley/Topic(href, href_list) + ..() + if(href_list["drop_from_cargo"]) + var/obj/O = locate(href_list["drop_from_cargo"]) + if(O && O in cargo) + occupant_message("You unload [O].") + O.loc = get_turf(src) + cargo -= O + var/turf/T = get_turf(O) + if(T) + T.Entered(O) + log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - cargo.len]") + return + + + +/obj/mecha/working/ripley/get_stats_part() + var/output = ..() + output += "Cargo Compartment Contents:
        " + if(cargo.len) + for(var/obj/O in cargo) + output += "Unload : [O]
        " + else + output += "Nothing" + output += "
        " + return output + +/obj/mecha/working/ripley/Destroy() + for(var/mob/M in src) + if(M == occupant) + continue + M.loc = get_turf(src) + M.loc.Entered(M) + step_rand(M) + for(var/atom/movable/A in cargo) + A.loc = get_turf(src) + var/turf/T = get_turf(A) + if(T) + T.Entered(A) + step_rand(A) + return ..() + +/obj/mecha/working/ripley/ex_act(severity) + ..() + for(var/X in cargo) + var/obj/O = X + if(prob(30 / severity)) + cargo -= O + O.forceMove(drop_location()) + +/obj/mecha/working/ripley/proc/update_pressure() + var/turf/T = get_turf(loc) + + if(lavaland_equipment_pressure_check(T)) + step_in = fast_pressure_step_in + for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) + drill.equip_cooldown = initial(drill.equip_cooldown)/2 + else + step_in = slow_pressure_step_in + for(var/obj/item/mecha_parts/mecha_equipment/drill/drill in equipment) + drill.equip_cooldown = initial(drill.equip_cooldown) + +/obj/mecha/working/ripley/emag_act(mob/user) + if(!emagged) + emagged = TRUE + to_chat(user, "You slide the card through [src]'s ID slot.") + playsound(loc, "sparks", 100, 1) + desc += "
        The mech's equipment slots spark dangerously!" + else + to_chat(user, "[src]'s ID slot rejects the card.") diff --git a/code/game/mecha/working/working.dm b/code/game/mecha/working/working.dm index 516b57e898a..b29b64bed26 100644 --- a/code/game/mecha/working/working.dm +++ b/code/game/mecha/working/working.dm @@ -1,7 +1,7 @@ -/obj/mecha/working - internal_damage_threshold = 60 - -/obj/mecha/working/New() - ..() - if(!ruin_mecha) - trackers += new /obj/item/mecha_parts/mecha_tracking(src) +/obj/mecha/working + internal_damage_threshold = 60 + +/obj/mecha/working/New() + ..() + if(!ruin_mecha) + trackers += new /obj/item/mecha_parts/mecha_tracking(src) diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index aa5eef1f04a..bb1070f2cf1 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -150,4 +150,4 @@ /mob/living/proc/check_buckled() if(buckled && !(buckled in loc)) - buckled.unbuckle_mob(src, force = TRUE) \ No newline at end of file + buckled.unbuckle_mob(src, force = TRUE) diff --git a/code/game/objects/effects/alien_acid.dm b/code/game/objects/effects/alien_acid.dm index 8f383fd518c..96c89c62539 100644 --- a/code/game/objects/effects/alien_acid.dm +++ b/code/game/objects/effects/alien_acid.dm @@ -89,4 +89,4 @@ if(8) visible_message("[target] is struggling to withstand the acid!") if(4) - visible_message("[target] begins to crumble under the acid!") \ No newline at end of file + visible_message("[target] begins to crumble under the acid!") diff --git a/code/game/objects/effects/bump_teleporter.dm b/code/game/objects/effects/bump_teleporter.dm index 02e35b7e876..be8acfc80bc 100644 --- a/code/game/objects/effects/bump_teleporter.dm +++ b/code/game/objects/effects/bump_teleporter.dm @@ -1,40 +1,40 @@ -var/list/obj/effect/bump_teleporter/BUMP_TELEPORTERS = list() - -/obj/effect/bump_teleporter - name = "bump-teleporter" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x2" - var/id = null //id of this bump_teleporter. - var/id_target = null //id of bump_teleporter which this moves you to. - invisibility = 101 //nope, can't see this - anchored = 1 - density = 1 - opacity = 0 - -/obj/effect/bump_teleporter/New() - ..() - BUMP_TELEPORTERS += src - -/obj/effect/bump_teleporter/Destroy() - BUMP_TELEPORTERS -= src - return ..() - -/obj/effect/bump_teleporter/singularity_act() - return - -/obj/effect/bump_teleporter/singularity_pull() - return - -/obj/effect/bump_teleporter/Bumped(atom/user) - if(!ismob(user)) - //user.loc = src.loc //Stop at teleporter location - return - - if(!id_target) - //user.loc = src.loc //Stop at teleporter location, there is nowhere to teleport to. - return - - for(var/obj/effect/bump_teleporter/BT in BUMP_TELEPORTERS) - if(BT.id == src.id_target) - usr.loc = BT.loc //Teleport to location with correct id. - return \ No newline at end of file +var/list/obj/effect/bump_teleporter/BUMP_TELEPORTERS = list() + +/obj/effect/bump_teleporter + name = "bump-teleporter" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x2" + var/id = null //id of this bump_teleporter. + var/id_target = null //id of bump_teleporter which this moves you to. + invisibility = 101 //nope, can't see this + anchored = 1 + density = 1 + opacity = 0 + +/obj/effect/bump_teleporter/New() + ..() + BUMP_TELEPORTERS += src + +/obj/effect/bump_teleporter/Destroy() + BUMP_TELEPORTERS -= src + return ..() + +/obj/effect/bump_teleporter/singularity_act() + return + +/obj/effect/bump_teleporter/singularity_pull() + return + +/obj/effect/bump_teleporter/Bumped(atom/user) + if(!ismob(user)) + //user.loc = src.loc //Stop at teleporter location + return + + if(!id_target) + //user.loc = src.loc //Stop at teleporter location, there is nowhere to teleport to. + return + + for(var/obj/effect/bump_teleporter/BT in BUMP_TELEPORTERS) + if(BT.id == src.id_target) + usr.loc = BT.loc //Teleport to location with correct id. + return diff --git a/code/game/objects/effects/decals/Cleanable/misc.dm b/code/game/objects/effects/decals/Cleanable/misc.dm index cee5517fd0a..a8510ddc102 100644 --- a/code/game/objects/effects/decals/Cleanable/misc.dm +++ b/code/game/objects/effects/decals/Cleanable/misc.dm @@ -211,4 +211,4 @@ icon = 'icons/effects/blood.dmi' icon_state = "xfloor1" random_icon_states = list("xfloor1", "xfloor2", "xfloor3", "xfloor4", "xfloor5", "xfloor6", "xfloor7") - anchored = TRUE \ No newline at end of file + anchored = TRUE diff --git a/code/game/objects/effects/decals/Cleanable/robots.dm b/code/game/objects/effects/decals/Cleanable/robots.dm index 5193436295e..91f558068a8 100644 --- a/code/game/objects/effects/decals/Cleanable/robots.dm +++ b/code/game/objects/effects/decals/Cleanable/robots.dm @@ -1,60 +1,60 @@ -/obj/effect/decal/cleanable/blood/gibs/robot - name = "robot debris" - desc = "It's a useless heap of junk... or is it?" - icon = 'icons/mob/robots.dmi' - icon_state = "gib1" - basecolor = "#030303" - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") - bloodiness = BLOOD_AMOUNT_PER_DECAL - mergeable_decal = FALSE - -/obj/effect/decal/cleanable/blood/gibs/robot/can_bloodcrawl_in() - return FALSE - -/obj/effect/decal/cleanable/blood/gibs/robot/update_icon() - color = "#FFFFFF" - -/obj/effect/decal/cleanable/blood/gibs/robot/dry() //pieces of robots do not dry up like - return - -/obj/effect/decal/cleanable/blood/gibs/robot/can_bloodcrawl_in() - return FALSE - -/obj/effect/decal/cleanable/blood/gibs/robot/streak(var/list/directions) - spawn(0) - var/direction = pick(directions) - for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50; 4), i++) - sleep(3) - if(i > 0) - if(prob(40)) - var/obj/effect/decal/cleanable/blood/oil/streak = new(src.loc) - streak.update_icon() - else if(prob(10)) - do_sparks(3, 1, src) - if(step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/blood/gibs/robot/limb - random_icon_states = list("gibarm", "gibleg") - -/obj/effect/decal/cleanable/blood/gibs/robot/up - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7", "gibup1", "gibup1") //2:7 is close enough to 1:4 - -/obj/effect/decal/cleanable/blood/gibs/robot/down - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7", "gibdown1", "gibdown1") //2:7 is close enough to 1:4 - -/obj/effect/decal/cleanable/blood/oil - name = "motor oil" - desc = "It's black and greasy. Looks like Beepsky made another mess." - basecolor = "#030303" - bloodiness = MAX_SHOE_BLOODINESS - -/obj/effect/decal/cleanable/blood/oil/can_bloodcrawl_in() - return FALSE - -/obj/effect/decal/cleanable/blood/oil/dry() - return - -/obj/effect/decal/cleanable/blood/oil/streak - random_icon_states = list("mgibbl1", "mgibbl2", "mgibbl3", "mgibbl4", "mgibbl5") - amount = 2 +/obj/effect/decal/cleanable/blood/gibs/robot + name = "robot debris" + desc = "It's a useless heap of junk... or is it?" + icon = 'icons/mob/robots.dmi' + icon_state = "gib1" + basecolor = "#030303" + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") + bloodiness = BLOOD_AMOUNT_PER_DECAL + mergeable_decal = FALSE + +/obj/effect/decal/cleanable/blood/gibs/robot/can_bloodcrawl_in() + return FALSE + +/obj/effect/decal/cleanable/blood/gibs/robot/update_icon() + color = "#FFFFFF" + +/obj/effect/decal/cleanable/blood/gibs/robot/dry() //pieces of robots do not dry up like + return + +/obj/effect/decal/cleanable/blood/gibs/robot/can_bloodcrawl_in() + return FALSE + +/obj/effect/decal/cleanable/blood/gibs/robot/streak(var/list/directions) + spawn(0) + var/direction = pick(directions) + for(var/i = 0, i < pick(1, 200; 2, 150; 3, 50; 4), i++) + sleep(3) + if(i > 0) + if(prob(40)) + var/obj/effect/decal/cleanable/blood/oil/streak = new(src.loc) + streak.update_icon() + else if(prob(10)) + do_sparks(3, 1, src) + if(step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/blood/gibs/robot/limb + random_icon_states = list("gibarm", "gibleg") + +/obj/effect/decal/cleanable/blood/gibs/robot/up + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7", "gibup1", "gibup1") //2:7 is close enough to 1:4 + +/obj/effect/decal/cleanable/blood/gibs/robot/down + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7", "gibdown1", "gibdown1") //2:7 is close enough to 1:4 + +/obj/effect/decal/cleanable/blood/oil + name = "motor oil" + desc = "It's black and greasy. Looks like Beepsky made another mess." + basecolor = "#030303" + bloodiness = MAX_SHOE_BLOODINESS + +/obj/effect/decal/cleanable/blood/oil/can_bloodcrawl_in() + return FALSE + +/obj/effect/decal/cleanable/blood/oil/dry() + return + +/obj/effect/decal/cleanable/blood/oil/streak + random_icon_states = list("mgibbl1", "mgibbl2", "mgibbl3", "mgibbl4", "mgibbl5") + amount = 2 diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm index cdcc5e5da3c..52109a5ae86 100644 --- a/code/game/objects/effects/decals/Cleanable/tracks.dm +++ b/code/game/objects/effects/decals/Cleanable/tracks.dm @@ -135,4 +135,4 @@ var/global/list/image/fluidtrack_cache = list() /obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/blood/footprints/C) if(blood_state != C.blood_state) //We only replace footprints of the same type as us return - ..() \ No newline at end of file + ..() diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 9c34fbdcc5e..ca92d2da319 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,73 +1,73 @@ -/obj/effect/decal/cleanable - anchored = TRUE - var/list/random_icon_states = list() - var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints - var/mergeable_decal = TRUE //when two of these are on a same tile or do we need to merge them into just one? - -/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal - if(mergeable_decal) - return TRUE - -//Add "bloodiness" of this blood's type, to the human's shoes -//This is on /cleanable because fuck this ancient mess -/obj/effect/decal/cleanable/blood/Crossed(atom/movable/O) - ..() - if(!off_floor && ishuman(O)) - var/mob/living/carbon/human/H = O - var/obj/item/organ/external/l_foot = H.get_organ("l_foot") - var/obj/item/organ/external/r_foot = H.get_organ("r_foot") - var/hasfeet = TRUE - if(!l_foot && !r_foot) - hasfeet = FALSE - if(H.shoes && blood_state && bloodiness) - var/obj/item/clothing/shoes/S = H.shoes - var/add_blood = 0 - if(bloodiness >= BLOOD_GAIN_PER_STEP) - add_blood = BLOOD_GAIN_PER_STEP - else - add_blood = bloodiness - bloodiness -= add_blood - S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS, S.bloody_shoes[blood_state] + add_blood) - if(blood_DNA && blood_DNA.len) - S.add_blood(H.blood_DNA, basecolor) - S.blood_state = blood_state - S.blood_color = basecolor - update_icon() - H.update_inv_shoes() - else if(hasfeet && blood_state && bloodiness)//Or feet - var/add_blood = 0 - if(bloodiness >= BLOOD_GAIN_PER_STEP) - add_blood = BLOOD_GAIN_PER_STEP - else - add_blood = bloodiness - bloodiness -= add_blood - H.bloody_feet[blood_state] = min(MAX_SHOE_BLOODINESS, H.bloody_feet[blood_state] + add_blood) - if(!H.feet_blood_DNA) - H.feet_blood_DNA = list() - H.blood_state = blood_state - H.feet_blood_DNA |= blood_DNA.Copy() - H.feet_blood_color = basecolor - update_icon() - H.update_inv_shoes() - -/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() - return FALSE - -/obj/effect/decal/cleanable/Initialize(mapload) - . = ..() - if(loc && isturf(loc)) - for(var/obj/effect/decal/cleanable/C in loc) - if(C != src && C.type == type && !QDELETED(C)) - if(replace_decal(C)) - qdel(src) - return TRUE - if(random_icon_states && length(src.random_icon_states) > 0) - src.icon_state = pick(src.random_icon_states) - if(smooth) - queue_smooth(src) - queue_smooth_neighbors(src) - -/obj/effect/decal/cleanable/Destroy() - if(smooth) - queue_smooth_neighbors(src) - return ..() \ No newline at end of file +/obj/effect/decal/cleanable + anchored = TRUE + var/list/random_icon_states = list() + var/bloodiness = 0 //0-100, amount of blood in this decal, used for making footprints and affecting the alpha of bloody footprints + var/mergeable_decal = TRUE //when two of these are on a same tile or do we need to merge them into just one? + +/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal + if(mergeable_decal) + return TRUE + +//Add "bloodiness" of this blood's type, to the human's shoes +//This is on /cleanable because fuck this ancient mess +/obj/effect/decal/cleanable/blood/Crossed(atom/movable/O) + ..() + if(!off_floor && ishuman(O)) + var/mob/living/carbon/human/H = O + var/obj/item/organ/external/l_foot = H.get_organ("l_foot") + var/obj/item/organ/external/r_foot = H.get_organ("r_foot") + var/hasfeet = TRUE + if(!l_foot && !r_foot) + hasfeet = FALSE + if(H.shoes && blood_state && bloodiness) + var/obj/item/clothing/shoes/S = H.shoes + var/add_blood = 0 + if(bloodiness >= BLOOD_GAIN_PER_STEP) + add_blood = BLOOD_GAIN_PER_STEP + else + add_blood = bloodiness + bloodiness -= add_blood + S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS, S.bloody_shoes[blood_state] + add_blood) + if(blood_DNA && blood_DNA.len) + S.add_blood(H.blood_DNA, basecolor) + S.blood_state = blood_state + S.blood_color = basecolor + update_icon() + H.update_inv_shoes() + else if(hasfeet && blood_state && bloodiness)//Or feet + var/add_blood = 0 + if(bloodiness >= BLOOD_GAIN_PER_STEP) + add_blood = BLOOD_GAIN_PER_STEP + else + add_blood = bloodiness + bloodiness -= add_blood + H.bloody_feet[blood_state] = min(MAX_SHOE_BLOODINESS, H.bloody_feet[blood_state] + add_blood) + if(!H.feet_blood_DNA) + H.feet_blood_DNA = list() + H.blood_state = blood_state + H.feet_blood_DNA |= blood_DNA.Copy() + H.feet_blood_color = basecolor + update_icon() + H.update_inv_shoes() + +/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() + return FALSE + +/obj/effect/decal/cleanable/Initialize(mapload) + . = ..() + if(loc && isturf(loc)) + for(var/obj/effect/decal/cleanable/C in loc) + if(C != src && C.type == type && !QDELETED(C)) + if(replace_decal(C)) + qdel(src) + return TRUE + if(random_icon_states && length(src.random_icon_states) > 0) + src.icon_state = pick(src.random_icon_states) + if(smooth) + queue_smooth(src) + queue_smooth_neighbors(src) + +/obj/effect/decal/cleanable/Destroy() + if(smooth) + queue_smooth_neighbors(src) + return ..() diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm index fc735b31ee4..e8666d26d64 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -1,19 +1,19 @@ -/obj/effect/decal/cleanable/crayon - name = "rune" - desc = "A rune drawn in crayon." - icon = 'icons/effects/crayondecal.dmi' - icon_state = "rune1" - layer = MID_TURF_LAYER - plane = GAME_PLANE //makes the graffiti visible over a wall. - anchored = TRUE - mergeable_decal = FALSE // Allows crayon drawings to overlap one another. - - -/obj/effect/decal/cleanable/crayon/Initialize(mapload, main = "#FFFFFF", var/type = "rune1", var/e_name = "rune") - . = ..() - - name = e_name - desc = "A [name] drawn in crayon." - - icon_state = type - color = main +/obj/effect/decal/cleanable/crayon + name = "rune" + desc = "A rune drawn in crayon." + icon = 'icons/effects/crayondecal.dmi' + icon_state = "rune1" + layer = MID_TURF_LAYER + plane = GAME_PLANE //makes the graffiti visible over a wall. + anchored = TRUE + mergeable_decal = FALSE // Allows crayon drawings to overlap one another. + + +/obj/effect/decal/cleanable/crayon/Initialize(mapload, main = "#FFFFFF", var/type = "rune1", var/e_name = "rune") + . = ..() + + name = e_name + desc = "A [name] drawn in crayon." + + icon_state = type + color = main diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm index 31ae7ab68de..ce8e81eb1e4 100644 --- a/code/game/objects/effects/decals/decal.dm +++ b/code/game/objects/effects/decals/decal.dm @@ -12,4 +12,4 @@ var/turf/T = loc if(!istype(T)) //you know this will happen somehow CRASH("Turf decal initialized in an object/nullspace") - T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_GOD, color, null, null, alpha) \ No newline at end of file + T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_GOD, color, null, null, alpha) diff --git a/code/game/objects/effects/decals/misc.dm b/code/game/objects/effects/decals/misc.dm index 425031ae3e5..88663e6c38c 100644 --- a/code/game/objects/effects/decals/misc.dm +++ b/code/game/objects/effects/decals/misc.dm @@ -1,72 +1,72 @@ -// Used for spray that you spray at walls, tables, hydrovats etc -/obj/effect/decal/spraystill - density = FALSE - anchored = TRUE - layer = 50 - plane = HUD_PLANE - -/obj/effect/decal/chempuff - name = "chemicals" - icon = 'icons/obj/chempuff.dmi' - pass_flags = PASSTABLE | PASSGRILLE - -/obj/effect/decal/chempuff/blob_act(obj/structure/blob/B) - return - -/obj/effect/decal/snow - name = "snow" - density = FALSE - anchored = TRUE - layer = TURF_DECAL_LAYER - icon = 'icons/turf/snow.dmi' - icon_state = "snow" - -/obj/effect/decal/snow/clean/edge - icon_state = "snow_corner" - -/obj/effect/decal/snow/sand/edge - icon_state = "gravsnow_corner" - -/obj/effect/decal/snow/clean/surround - icon_state = "snow_surround" - -/obj/effect/decal/snow/sand/surround - icon_state = "gravsnow_surround" - -/obj/effect/decal/leaves - name = "fall leaves" - density = FALSE - anchored = TRUE - layer = HIGH_TURF_LAYER - icon = 'icons/obj/flora/plants.dmi' - icon_state = "fallleaves" - -/obj/effect/decal/straw - name = "scattered straw" - density = FALSE - anchored = TRUE - layer = HIGH_TURF_LAYER - icon = 'icons/obj/flora/plants.dmi' - icon_state = "strawscattered" - -/obj/effect/decal/straw/medium - icon_state = "strawscattered3" - -/obj/effect/decal/straw/light - icon_state = "strawscattered2" - -/obj/effect/decal/straw/edge - icon_state = "strawscatterededge" - -/obj/effect/decal/ants - name = "space ants" - desc = "A bunch of space ants." - icon = 'icons/goonstation/effects/effects.dmi' - icon_state = "spaceants" - scoop_reagents = list("ants" = 20) - -/obj/effect/decal/ants/Initialize(mapload) - . = ..() - var/scale = (rand(2, 10) / 10) + (rand(0, 5) / 100) - transform = matrix(transform, scale, scale, MATRIX_SCALE) - setDir(pick(NORTH, SOUTH, EAST, WEST)) \ No newline at end of file +// Used for spray that you spray at walls, tables, hydrovats etc +/obj/effect/decal/spraystill + density = FALSE + anchored = TRUE + layer = 50 + plane = HUD_PLANE + +/obj/effect/decal/chempuff + name = "chemicals" + icon = 'icons/obj/chempuff.dmi' + pass_flags = PASSTABLE | PASSGRILLE + +/obj/effect/decal/chempuff/blob_act(obj/structure/blob/B) + return + +/obj/effect/decal/snow + name = "snow" + density = FALSE + anchored = TRUE + layer = TURF_DECAL_LAYER + icon = 'icons/turf/snow.dmi' + icon_state = "snow" + +/obj/effect/decal/snow/clean/edge + icon_state = "snow_corner" + +/obj/effect/decal/snow/sand/edge + icon_state = "gravsnow_corner" + +/obj/effect/decal/snow/clean/surround + icon_state = "snow_surround" + +/obj/effect/decal/snow/sand/surround + icon_state = "gravsnow_surround" + +/obj/effect/decal/leaves + name = "fall leaves" + density = FALSE + anchored = TRUE + layer = HIGH_TURF_LAYER + icon = 'icons/obj/flora/plants.dmi' + icon_state = "fallleaves" + +/obj/effect/decal/straw + name = "scattered straw" + density = FALSE + anchored = TRUE + layer = HIGH_TURF_LAYER + icon = 'icons/obj/flora/plants.dmi' + icon_state = "strawscattered" + +/obj/effect/decal/straw/medium + icon_state = "strawscattered3" + +/obj/effect/decal/straw/light + icon_state = "strawscattered2" + +/obj/effect/decal/straw/edge + icon_state = "strawscatterededge" + +/obj/effect/decal/ants + name = "space ants" + desc = "A bunch of space ants." + icon = 'icons/goonstation/effects/effects.dmi' + icon_state = "spaceants" + scoop_reagents = list("ants" = 20) + +/obj/effect/decal/ants/Initialize(mapload) + . = ..() + var/scale = (rand(2, 10) / 10) + (rand(0, 5) / 100) + transform = matrix(transform, scale, scale, MATRIX_SCALE) + setDir(pick(NORTH, SOUTH, EAST, WEST)) diff --git a/code/game/objects/effects/decals/remains.dm b/code/game/objects/effects/decals/remains.dm index d69b8cad25a..9a879a02da3 100644 --- a/code/game/objects/effects/decals/remains.dm +++ b/code/game/objects/effects/decals/remains.dm @@ -1,46 +1,46 @@ -/obj/effect/decal/remains - gender = PLURAL - -/obj/effect/decal/remains/acid_act() - visible_message("[src] dissolve[gender==PLURAL?"":"s"] into a puddle of sizzling goop!") - playsound(src, 'sound/items/welder.ogg', 150, TRUE) - new /obj/effect/decal/cleanable/greenglow(drop_location()) - qdel(src) - -/obj/effect/decal/remains/human - name = "remains" - desc = "They look like human remains. They have a strange aura about them." - icon = 'icons/effects/blood.dmi' - icon_state = "remains" - anchored = TRUE - -/obj/effect/decal/remains/xeno - name = "remains" - desc = "They look like the remains of something... alien. They have a strange aura about them." - icon = 'icons/effects/blood.dmi' - icon_state = "remainsxeno" - anchored = TRUE - -/obj/effect/decal/remains/robot - name = "remains" - desc = "They look like the remains of something mechanical. They have a strange aura about them." - icon = 'icons/mob/robots.dmi' - icon_state = "remainsrobot" - anchored = TRUE - -/obj/effect/decal/remains/slime - name = "You shouldn't see this" - desc = "Noooooooooooooooooooooo" - icon = 'icons/effects/blood.dmi' - icon_state = "remains" - anchored = TRUE - -/obj/effect/decal/remains/slime/New() - ..() - var/datum/reagents/R = new/datum/reagents(5) - var/obj/effect/particle_effect/water/W = new(get_turf(src)) - W.reagents = R - R.my_atom = W - R.add_reagent("water", 5) - R.reaction(get_turf(src)) - qdel(src) +/obj/effect/decal/remains + gender = PLURAL + +/obj/effect/decal/remains/acid_act() + visible_message("[src] dissolve[gender==PLURAL?"":"s"] into a puddle of sizzling goop!") + playsound(src, 'sound/items/welder.ogg', 150, TRUE) + new /obj/effect/decal/cleanable/greenglow(drop_location()) + qdel(src) + +/obj/effect/decal/remains/human + name = "remains" + desc = "They look like human remains. They have a strange aura about them." + icon = 'icons/effects/blood.dmi' + icon_state = "remains" + anchored = TRUE + +/obj/effect/decal/remains/xeno + name = "remains" + desc = "They look like the remains of something... alien. They have a strange aura about them." + icon = 'icons/effects/blood.dmi' + icon_state = "remainsxeno" + anchored = TRUE + +/obj/effect/decal/remains/robot + name = "remains" + desc = "They look like the remains of something mechanical. They have a strange aura about them." + icon = 'icons/mob/robots.dmi' + icon_state = "remainsrobot" + anchored = TRUE + +/obj/effect/decal/remains/slime + name = "You shouldn't see this" + desc = "Noooooooooooooooooooooo" + icon = 'icons/effects/blood.dmi' + icon_state = "remains" + anchored = TRUE + +/obj/effect/decal/remains/slime/New() + ..() + var/datum/reagents/R = new/datum/reagents(5) + var/obj/effect/particle_effect/water/W = new(get_turf(src)) + W.reagents = R + R.my_atom = W + R.add_reagent("water", 5) + R.reaction(get_turf(src)) + qdel(src) diff --git a/code/game/objects/effects/decals/turfdecals/dirt.dm b/code/game/objects/effects/decals/turfdecals/dirt.dm index a2a1e8c6455..156c04d1ba3 100644 --- a/code/game/objects/effects/decals/turfdecals/dirt.dm +++ b/code/game/objects/effects/decals/turfdecals/dirt.dm @@ -2,4 +2,4 @@ icon_state = "sandyfloor" /obj/effect/turf_decal/sand/plating - icon_state = "sandyplating" \ No newline at end of file + icon_state = "sandyplating" diff --git a/code/game/objects/effects/decals/turfdecals/markings.dm b/code/game/objects/effects/decals/turfdecals/markings.dm index 8687967e1d1..196d22b580c 100644 --- a/code/game/objects/effects/decals/turfdecals/markings.dm +++ b/code/game/objects/effects/decals/turfdecals/markings.dm @@ -149,4 +149,4 @@ icon_state = "box_corners_red" /obj/effect/turf_decal/plaque - icon_state = "plaque" \ No newline at end of file + icon_state = "plaque" diff --git a/code/game/objects/effects/decals/turfdecals/tilecoloring.dm b/code/game/objects/effects/decals/turfdecals/tilecoloring.dm index 1708753dc1e..b636ede55e5 100644 --- a/code/game/objects/effects/decals/turfdecals/tilecoloring.dm +++ b/code/game/objects/effects/decals/turfdecals/tilecoloring.dm @@ -36,4 +36,4 @@ /obj/effect/turf_decal/tile/neutral name = "neutral corner" color = "#D4D4D4" - alpha = 50 \ No newline at end of file + alpha = 50 diff --git a/code/game/objects/effects/decals/turfdecals/weather.dm b/code/game/objects/effects/decals/turfdecals/weather.dm index 9e8da6a3f89..f179487944a 100644 --- a/code/game/objects/effects/decals/turfdecals/weather.dm +++ b/code/game/objects/effects/decals/turfdecals/weather.dm @@ -9,4 +9,4 @@ /obj/effect/turf_decal/weather/snow/corner name = "snow corner piece" icon = 'icons/turf/snow.dmi' - icon_state = "snow_corner" \ No newline at end of file + icon_state = "snow_corner" diff --git a/code/game/objects/effects/effect_system/effect_system.dm b/code/game/objects/effects/effect_system/effect_system.dm index 6792f0aa6e2..0884e97687b 100644 --- a/code/game/objects/effects/effect_system/effect_system.dm +++ b/code/game/objects/effects/effect_system/effect_system.dm @@ -73,4 +73,4 @@ would spawn and follow the beaker, even if it is carried or thrown. /datum/effect_system/proc/decrement_total_effect() total_effects-- if(autocleanup && total_effects <= 0) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/effects/effect_system/effects_chem_smoke.dm b/code/game/objects/effects/effect_system/effects_chem_smoke.dm index f55a64aac8d..8c28c7f11c5 100644 --- a/code/game/objects/effects/effect_system/effects_chem_smoke.dm +++ b/code/game/objects/effects/effect_system/effects_chem_smoke.dm @@ -123,4 +123,4 @@ if(iscarbon(A)) var/mob/living/carbon/C = A if(C.can_breathe_gas()) - chemholder.reagents.copy_to(C, chemholder.reagents.total_volume) \ No newline at end of file + chemholder.reagents.copy_to(C, chemholder.reagents.total_volume) diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm index 65daf2c1c65..10efb534af2 100644 --- a/code/game/objects/effects/effect_system/effects_foam.dm +++ b/code/game/objects/effects/effect_system/effects_foam.dm @@ -218,4 +218,4 @@ return !density /obj/structure/foamedmetal/CanAtmosPass() - return !density \ No newline at end of file + return !density diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm index 338219da1ae..7497b148788 100644 --- a/code/game/objects/effects/effects.dm +++ b/code/game/objects/effects/effects.dm @@ -89,4 +89,4 @@ /obj/effect/decal/blob_act(obj/structure/blob/B) if(B && B.loc == loc) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm index 848c6701434..366324c68ba 100644 --- a/code/game/objects/effects/forcefields.dm +++ b/code/game/objects/effects/forcefields.dm @@ -38,4 +38,4 @@ /obj/effect/forcefield/mime/advanced name = "invisible blockade" desc = "You might be here a while." - lifetime = 60 SECONDS \ No newline at end of file + lifetime = 60 SECONDS diff --git a/code/game/objects/effects/gibs.dm b/code/game/objects/effects/gibs.dm index b3eedbd08cc..5503ac91dd1 100644 --- a/code/game/objects/effects/gibs.dm +++ b/code/game/objects/effects/gibs.dm @@ -1,53 +1,53 @@ -/proc/gibs(atom/location, datum/dna/MobDNA) //CARN MARKER - new /obj/effect/gibspawner/generic(get_turf(location), MobDNA) - -/proc/hgibs(atom/location, datum/dna/MobDNA) - new /obj/effect/gibspawner/human(get_turf(location), MobDNA) - -/proc/xgibs(atom/location) - new /obj/effect/gibspawner/xeno(get_turf(location)) - -/proc/robogibs(atom/location) - new /obj/effect/gibspawner/robot(get_turf(location)) - -/obj/effect/gibspawner - var/sparks = 0 //whether sparks spread on Gib() - var/list/gibtypes = list() - var/list/gibamounts = list() - var/list/gibdirections = list() //of lists - -/obj/effect/gibspawner/New(location, datum/dna/MobDNA) - ..() - - if(istype(loc,/turf)) //basically if a badmin spawns it - Gib(loc, MobDNA) - -/obj/effect/gibspawner/proc/Gib(atom/location, datum/dna/MobDNA = null) - if(gibtypes.len != gibamounts.len || gibamounts.len != gibdirections.len) - to_chat(world, "Gib list length mismatch!") - return - - var/obj/effect/decal/cleanable/blood/gibs/gib = null - - if(sparks) - do_sparks(2, 1, location) - - for(var/i = 1, i<= gibtypes.len, i++) - if(gibamounts[i]) - for(var/j = 1, j<= gibamounts[i], j++) - var/gibType = gibtypes[i] - gib = new gibType(location) - - - gib.blood_DNA = list() - if(MobDNA) - gib.blood_DNA[MobDNA.unique_enzymes] = MobDNA.blood_type - else if(istype(src, /obj/effect/gibspawner/xeno)) - gib.blood_DNA["UNKNOWN DNA"] = "X*" - else if(istype(src, /obj/effect/gibspawner/human)) // Probably a monkey - gib.blood_DNA["Non-human DNA"] = "A+" - var/list/directions = gibdirections[i] - if(directions.len) - gib.streak(directions) - - qdel(src) +/proc/gibs(atom/location, datum/dna/MobDNA) //CARN MARKER + new /obj/effect/gibspawner/generic(get_turf(location), MobDNA) + +/proc/hgibs(atom/location, datum/dna/MobDNA) + new /obj/effect/gibspawner/human(get_turf(location), MobDNA) + +/proc/xgibs(atom/location) + new /obj/effect/gibspawner/xeno(get_turf(location)) + +/proc/robogibs(atom/location) + new /obj/effect/gibspawner/robot(get_turf(location)) + +/obj/effect/gibspawner + var/sparks = 0 //whether sparks spread on Gib() + var/list/gibtypes = list() + var/list/gibamounts = list() + var/list/gibdirections = list() //of lists + +/obj/effect/gibspawner/New(location, datum/dna/MobDNA) + ..() + + if(istype(loc,/turf)) //basically if a badmin spawns it + Gib(loc, MobDNA) + +/obj/effect/gibspawner/proc/Gib(atom/location, datum/dna/MobDNA = null) + if(gibtypes.len != gibamounts.len || gibamounts.len != gibdirections.len) + to_chat(world, "Gib list length mismatch!") + return + + var/obj/effect/decal/cleanable/blood/gibs/gib = null + + if(sparks) + do_sparks(2, 1, location) + + for(var/i = 1, i<= gibtypes.len, i++) + if(gibamounts[i]) + for(var/j = 1, j<= gibamounts[i], j++) + var/gibType = gibtypes[i] + gib = new gibType(location) + + + gib.blood_DNA = list() + if(MobDNA) + gib.blood_DNA[MobDNA.unique_enzymes] = MobDNA.blood_type + else if(istype(src, /obj/effect/gibspawner/xeno)) + gib.blood_DNA["UNKNOWN DNA"] = "X*" + else if(istype(src, /obj/effect/gibspawner/human)) // Probably a monkey + gib.blood_DNA["Non-human DNA"] = "A+" + var/list/directions = gibdirections[i] + if(directions.len) + gib.streak(directions) + + qdel(src) diff --git a/code/game/objects/effects/glowshroom.dm b/code/game/objects/effects/glowshroom.dm index 0fe284fb67e..dc12f91c618 100644 --- a/code/game/objects/effects/glowshroom.dm +++ b/code/game/objects/effects/glowshroom.dm @@ -1,169 +1,169 @@ -//separate dm since hydro is getting bloated already - -/obj/structure/glowshroom - name = "glowshroom" - desc = "Mycena Bregprox, a species of mushroom that glows in the dark." - anchored = TRUE - opacity = 0 - density = FALSE - icon = 'icons/obj/lighting.dmi' - icon_state = "glowshroom" //replaced in New - layer = ABOVE_NORMAL_TURF_LAYER - max_integrity = 30 - var/delay = 1200 - var/floor = 0 - var/generation = 1 - var/spreadIntoAdjacentChance = 60 - var/obj/item/seeds/myseed = /obj/item/seeds/glowshroom - -/obj/structure/glowshroom/glowcap - name = "glowcap" - desc = "Mycena Ruthenia, a species of mushroom that, while it does glow in the dark, is not actually bioluminescent." - icon_state = "glowcap" - myseed = /obj/item/seeds/glowshroom/glowcap - -/obj/structure/glowshroom/shadowshroom - name = "shadowshroom" - desc = "Mycena Umbra, a species of mushroom that emits shadow instead of light." - icon_state = "shadowshroom" - myseed = /obj/item/seeds/glowshroom/shadowshroom - -/obj/structure/glowshroom/single/Spread() - return - -/obj/structure/glowshroom/examine(mob/user) - . = ..() - . += "This is a [generation]\th generation [name]!" - -/obj/structure/glowshroom/Destroy() - QDEL_NULL(myseed) - return ..() - -/obj/structure/glowshroom/New(loc, obj/item/seeds/newseed, mutate_stats) - ..() - if(newseed) - myseed = newseed.Copy() - myseed.forceMove(src) - else - myseed = new myseed(src) - if(mutate_stats) //baby mushrooms have different stats :3 - myseed.adjust_potency(rand(-3,6)) - myseed.adjust_yield(rand(-1,2)) - myseed.adjust_production(rand(-3,6)) - myseed.adjust_endurance(rand(-3,6)) - delay = delay - myseed.production * 100 //So the delay goes DOWN with better stats instead of up. :I - obj_integrity = myseed.endurance - max_integrity = myseed.endurance - if(myseed.get_gene(/datum/plant_gene/trait/glow)) - var/datum/plant_gene/trait/glow/G = myseed.get_gene(/datum/plant_gene/trait/glow) - set_light(G.glow_range(myseed), G.glow_power(myseed), G.glow_color) - setDir(CalcDir()) - var/base_icon_state = initial(icon_state) - if(!floor) - switch(dir) //offset to make it be on the wall rather than on the floor - if(NORTH) - pixel_y = 32 - if(SOUTH) - pixel_y = -32 - if(EAST) - pixel_x = 32 - if(WEST) - pixel_x = -32 - icon_state = "[base_icon_state][rand(1,3)]" - else //if on the floor, glowshroom on-floor sprite - icon_state = "[base_icon_state]f" - - addtimer(CALLBACK(src, .proc/Spread), delay) - -/obj/structure/glowshroom/proc/Spread() - var/turf/ownturf = get_turf(src) - var/shrooms_planted = 0 - for(var/i in 1 to myseed.yield) - if(prob(1/(generation * generation) * 100))//This formula gives you diminishing returns based on generation. 100% with 1st gen, decreasing to 25%, 11%, 6, 4, 2... - var/list/possibleLocs = list() - var/spreadsIntoAdjacent = FALSE - - if(prob(spreadIntoAdjacentChance)) - spreadsIntoAdjacent = TRUE - - for(var/turf/simulated/floor/earth in view(3,src)) - if(!ownturf.CanAtmosPass(earth)) - continue - if(spreadsIntoAdjacent || !locate(/obj/structure/glowshroom) in view(1,earth)) - possibleLocs += earth - CHECK_TICK - - if(!possibleLocs.len) - break - - var/turf/newLoc = pick(possibleLocs) - - var/shroomCount = 0 //hacky - var/placeCount = 1 - for(var/obj/structure/glowshroom/shroom in newLoc) - shroomCount++ - for(var/wallDir in cardinal) - var/turf/isWall = get_step(newLoc,wallDir) - if(isWall.density) - placeCount++ - if(shroomCount >= placeCount) - continue - - var/obj/structure/glowshroom/child = new type(newLoc, myseed, TRUE) - child.generation = generation + 1 - shrooms_planted++ - - CHECK_TICK - else - shrooms_planted++ //if we failed due to generation, don't try to plant one later - if(shrooms_planted < myseed.yield) //if we didn't get all possible shrooms planted, try again later - myseed.yield -= shrooms_planted - addtimer(CALLBACK(src, .proc/Spread), delay) - -/obj/structure/glowshroom/proc/CalcDir(turf/location = loc) - var/direction = 16 - - for(var/wallDir in cardinal) - var/turf/newTurf = get_step(location,wallDir) - if(newTurf.density) - direction |= wallDir - - for(var/obj/structure/glowshroom/shroom in location) - if(shroom == src) - continue - if(shroom.floor) //special - direction &= ~16 - else - direction &= ~shroom.dir - - var/list/dirList = list() - - for(var/i=1,i<=16,i <<= 1) - if(direction & i) - dirList += i - - if(dirList.len) - var/newDir = pick(dirList) - if(newDir == 16) - floor = 1 - newDir = 1 - return newDir - - floor = 1 - return 1 - -/obj/structure/glowshroom/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - if(damage_type == BURN && damage_amount) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/structure/glowshroom/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > 300) - take_damage(5, BURN, 0, 0) - -/obj/structure/glowshroom/acid_act(acidpwr, acid_volume) - . = 1 - visible_message("[src] melts away!") - var/obj/effect/decal/cleanable/molten_object/I = new (get_turf(src)) - I.desc = "Looks like this was \an [src] some time ago." - qdel(src) \ No newline at end of file +//separate dm since hydro is getting bloated already + +/obj/structure/glowshroom + name = "glowshroom" + desc = "Mycena Bregprox, a species of mushroom that glows in the dark." + anchored = TRUE + opacity = 0 + density = FALSE + icon = 'icons/obj/lighting.dmi' + icon_state = "glowshroom" //replaced in New + layer = ABOVE_NORMAL_TURF_LAYER + max_integrity = 30 + var/delay = 1200 + var/floor = 0 + var/generation = 1 + var/spreadIntoAdjacentChance = 60 + var/obj/item/seeds/myseed = /obj/item/seeds/glowshroom + +/obj/structure/glowshroom/glowcap + name = "glowcap" + desc = "Mycena Ruthenia, a species of mushroom that, while it does glow in the dark, is not actually bioluminescent." + icon_state = "glowcap" + myseed = /obj/item/seeds/glowshroom/glowcap + +/obj/structure/glowshroom/shadowshroom + name = "shadowshroom" + desc = "Mycena Umbra, a species of mushroom that emits shadow instead of light." + icon_state = "shadowshroom" + myseed = /obj/item/seeds/glowshroom/shadowshroom + +/obj/structure/glowshroom/single/Spread() + return + +/obj/structure/glowshroom/examine(mob/user) + . = ..() + . += "This is a [generation]\th generation [name]!" + +/obj/structure/glowshroom/Destroy() + QDEL_NULL(myseed) + return ..() + +/obj/structure/glowshroom/New(loc, obj/item/seeds/newseed, mutate_stats) + ..() + if(newseed) + myseed = newseed.Copy() + myseed.forceMove(src) + else + myseed = new myseed(src) + if(mutate_stats) //baby mushrooms have different stats :3 + myseed.adjust_potency(rand(-3,6)) + myseed.adjust_yield(rand(-1,2)) + myseed.adjust_production(rand(-3,6)) + myseed.adjust_endurance(rand(-3,6)) + delay = delay - myseed.production * 100 //So the delay goes DOWN with better stats instead of up. :I + obj_integrity = myseed.endurance + max_integrity = myseed.endurance + if(myseed.get_gene(/datum/plant_gene/trait/glow)) + var/datum/plant_gene/trait/glow/G = myseed.get_gene(/datum/plant_gene/trait/glow) + set_light(G.glow_range(myseed), G.glow_power(myseed), G.glow_color) + setDir(CalcDir()) + var/base_icon_state = initial(icon_state) + if(!floor) + switch(dir) //offset to make it be on the wall rather than on the floor + if(NORTH) + pixel_y = 32 + if(SOUTH) + pixel_y = -32 + if(EAST) + pixel_x = 32 + if(WEST) + pixel_x = -32 + icon_state = "[base_icon_state][rand(1,3)]" + else //if on the floor, glowshroom on-floor sprite + icon_state = "[base_icon_state]f" + + addtimer(CALLBACK(src, .proc/Spread), delay) + +/obj/structure/glowshroom/proc/Spread() + var/turf/ownturf = get_turf(src) + var/shrooms_planted = 0 + for(var/i in 1 to myseed.yield) + if(prob(1/(generation * generation) * 100))//This formula gives you diminishing returns based on generation. 100% with 1st gen, decreasing to 25%, 11%, 6, 4, 2... + var/list/possibleLocs = list() + var/spreadsIntoAdjacent = FALSE + + if(prob(spreadIntoAdjacentChance)) + spreadsIntoAdjacent = TRUE + + for(var/turf/simulated/floor/earth in view(3,src)) + if(!ownturf.CanAtmosPass(earth)) + continue + if(spreadsIntoAdjacent || !locate(/obj/structure/glowshroom) in view(1,earth)) + possibleLocs += earth + CHECK_TICK + + if(!possibleLocs.len) + break + + var/turf/newLoc = pick(possibleLocs) + + var/shroomCount = 0 //hacky + var/placeCount = 1 + for(var/obj/structure/glowshroom/shroom in newLoc) + shroomCount++ + for(var/wallDir in cardinal) + var/turf/isWall = get_step(newLoc,wallDir) + if(isWall.density) + placeCount++ + if(shroomCount >= placeCount) + continue + + var/obj/structure/glowshroom/child = new type(newLoc, myseed, TRUE) + child.generation = generation + 1 + shrooms_planted++ + + CHECK_TICK + else + shrooms_planted++ //if we failed due to generation, don't try to plant one later + if(shrooms_planted < myseed.yield) //if we didn't get all possible shrooms planted, try again later + myseed.yield -= shrooms_planted + addtimer(CALLBACK(src, .proc/Spread), delay) + +/obj/structure/glowshroom/proc/CalcDir(turf/location = loc) + var/direction = 16 + + for(var/wallDir in cardinal) + var/turf/newTurf = get_step(location,wallDir) + if(newTurf.density) + direction |= wallDir + + for(var/obj/structure/glowshroom/shroom in location) + if(shroom == src) + continue + if(shroom.floor) //special + direction &= ~16 + else + direction &= ~shroom.dir + + var/list/dirList = list() + + for(var/i=1,i<=16,i <<= 1) + if(direction & i) + dirList += i + + if(dirList.len) + var/newDir = pick(dirList) + if(newDir == 16) + floor = 1 + newDir = 1 + return newDir + + floor = 1 + return 1 + +/obj/structure/glowshroom/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + if(damage_type == BURN && damage_amount) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/glowshroom/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > 300) + take_damage(5, BURN, 0, 0) + +/obj/structure/glowshroom/acid_act(acidpwr, acid_volume) + . = 1 + visible_message("[src] melts away!") + var/obj/effect/decal/cleanable/molten_object/I = new (get_turf(src)) + I.desc = "Looks like this was \an [src] some time ago." + qdel(src) diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index 0cab543c5a7..51eab134ac1 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -1,306 +1,306 @@ -/obj/effect/landmark - name = "landmark" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x2" - anchored = 1.0 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/effect/landmark/New() - - ..() - set_tag() - invisibility = 101 - - switch(name) //some of these are probably obsolete - if("start") - newplayer_start += loc - qdel(src) - - if("wizard") - wizardstart += loc - qdel(src) - - if("JoinLate") - latejoin += loc - qdel(src) - - if("JoinLateGateway") - latejoin_gateway += loc - qdel(src) - - if("JoinLateCryo") - latejoin_cryo += loc - qdel(src) - - if("JoinLateCyborg") - latejoin_cyborg += loc - qdel(src) - - if("prisonwarp") - prisonwarp += loc - qdel(src) - - if("prisonsecuritywarp") - prisonsecuritywarp += loc - qdel(src) - - if("tdome1") - tdome1 += loc - - if("tdome2") - tdome2 += loc - - if("tdomeadmin") - tdomeadmin += loc - - if("tdomeobserve") - tdomeobserve += loc - - if("aroomwarp") - aroomwarp += loc - - if("blobstart") - blobstart += loc - qdel(src) - - if("xeno_spawn") - xeno_spawn += loc - qdel(src) - - if("ninjastart") - ninjastart += loc - qdel(src) - - if("carpspawn") - carplist += loc - - if("voxstart") - raider_spawn += loc - - if("ERT Director") - ertdirector += loc - qdel(src) - - if("Response Team") - emergencyresponseteamspawn += loc - qdel(src) - - if("Syndicate Officer") - syndicateofficer += loc - qdel(src) - - GLOB.landmarks_list += src - return 1 - -/obj/effect/landmark/Destroy() - GLOB.landmarks_list -= src - ..() - return QDEL_HINT_HARDDEL_NOW - -/obj/effect/landmark/proc/set_tag() - tag = text("landmark*[]", name) - - -/obj/effect/landmark/singularity_act() - return - -// Please stop bombing the Observer-Start landmark. -/obj/effect/landmark/ex_act() - return - -/obj/effect/landmark/singularity_pull() - return - -/obj/effect/landmark/start - name = "start" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x" - anchored = 1.0 - -/obj/effect/landmark/start/set_tag() - tag = "start*[name]" - - -//Costume spawner landmarks - -/obj/effect/landmark/costume/New() //costume spawner, selects a random subclass and disappears - - var/list/options = typesof(/obj/effect/landmark/costume) - var/PICK= options[rand(1,options.len)] - new PICK(src.loc) - qdel(src) - -//SUBCLASSES. Spawn a bunch of items and disappear likewise -/obj/effect/landmark/costume/chicken/New() - new /obj/item/clothing/suit/chickensuit(src.loc) - new /obj/item/clothing/head/chicken(src.loc) - new /obj/item/reagent_containers/food/snacks/egg(src.loc) - qdel(src) - -/obj/effect/landmark/costume/gladiator/New() - new /obj/item/clothing/under/gladiator(src.loc) - new /obj/item/clothing/head/helmet/gladiator(src.loc) - qdel(src) - -/obj/effect/landmark/costume/madscientist/New() - new /obj/item/clothing/under/gimmick/rank/captain/suit(src.loc) - new /obj/item/clothing/head/flatcap(src.loc) - new /obj/item/clothing/suit/storage/labcoat/mad(src.loc) - new /obj/item/clothing/glasses/gglasses(src.loc) - qdel(src) - -/obj/effect/landmark/costume/elpresidente/New() - new /obj/item/clothing/under/gimmick/rank/captain/suit(src.loc) - new /obj/item/clothing/head/flatcap(src.loc) - new /obj/item/clothing/mask/cigarette/cigar/havana(src.loc) - new /obj/item/clothing/shoes/jackboots(src.loc) - qdel(src) - -/obj/effect/landmark/costume/nyangirl/New() - new /obj/item/clothing/under/schoolgirl(src.loc) - new /obj/item/clothing/head/kitty(src.loc) - qdel(src) - -/obj/effect/landmark/costume/maid/New() - new /obj/item/clothing/under/blackskirt(src.loc) - var/CHOICE = pick( /obj/item/clothing/head/beret , /obj/item/clothing/head/rabbitears ) - new CHOICE(src.loc) - new /obj/item/clothing/glasses/sunglasses/blindfold(src.loc) - qdel(src) - -/obj/effect/landmark/costume/butler/New() - new /obj/item/clothing/suit/wcoat(src.loc) - new /obj/item/clothing/under/suit_jacket(src.loc) - new /obj/item/clothing/head/that(src.loc) - qdel(src) - -/obj/effect/landmark/costume/scratch/New() - new /obj/item/clothing/gloves/color/white(src.loc) - new /obj/item/clothing/shoes/white(src.loc) - new /obj/item/clothing/under/scratch(src.loc) - if(prob(30)) - new /obj/item/clothing/head/cueball(src.loc) - qdel(src) - -/obj/effect/landmark/costume/highlander/New() - new /obj/item/clothing/under/kilt(src.loc) - new /obj/item/clothing/head/beret(src.loc) - qdel(src) - -/obj/effect/landmark/costume/prig/New() - new /obj/item/clothing/suit/wcoat(src.loc) - new /obj/item/clothing/glasses/monocle(src.loc) - var/CHOICE= pick( /obj/item/clothing/head/bowlerhat, /obj/item/clothing/head/that) - new CHOICE(src.loc) - new /obj/item/clothing/shoes/black(src.loc) - new /obj/item/cane(src.loc) - new /obj/item/clothing/under/sl_suit(src.loc) - new /obj/item/clothing/mask/fakemoustache(src.loc) - qdel(src) - -/obj/effect/landmark/costume/plaguedoctor/New() - new /obj/item/clothing/suit/bio_suit/plaguedoctorsuit(src.loc) - new /obj/item/clothing/head/plaguedoctorhat(src.loc) - qdel(src) - -/obj/effect/landmark/costume/nightowl/New() - new /obj/item/clothing/under/owl(src.loc) - new /obj/item/clothing/mask/gas/owl_mask(src.loc) - qdel(src) - -/obj/effect/landmark/costume/waiter/New() - new /obj/item/clothing/under/waiter(src.loc) - var/CHOICE= pick( /obj/item/clothing/head/kitty, /obj/item/clothing/head/rabbitears) - new CHOICE(src.loc) - new /obj/item/clothing/suit/apron(src.loc) - qdel(src) - -/obj/effect/landmark/costume/pirate/New() - new /obj/item/clothing/under/pirate(src.loc) - new /obj/item/clothing/suit/pirate_black(src.loc) - var/CHOICE = pick( /obj/item/clothing/head/pirate , /obj/item/clothing/head/bandana ) - new CHOICE(src.loc) - new /obj/item/clothing/glasses/eyepatch(src.loc) - qdel(src) - -/obj/effect/landmark/costume/commie/New() - new /obj/item/clothing/under/soviet(src.loc) - new /obj/item/clothing/head/ushanka(src.loc) - qdel(src) - - -/obj/effect/landmark/costume/imperium_monk/New() - new /obj/item/clothing/suit/imperium_monk(src.loc) - if(prob(25)) - new /obj/item/clothing/mask/gas/cyborg(src.loc) - qdel(src) - -/obj/effect/landmark/costume/holiday_priest/New() - new /obj/item/clothing/suit/holidaypriest(src.loc) - qdel(src) - -/obj/effect/landmark/costume/marisawizard/fake/New() - new /obj/item/clothing/head/wizard/marisa/fake(src.loc) - new/obj/item/clothing/suit/wizrobe/marisa/fake(src.loc) - qdel(src) - -/obj/effect/landmark/costume/cutewitch/New() - new /obj/item/clothing/under/sundress(src.loc) - new /obj/item/clothing/head/witchwig(src.loc) - new /obj/item/twohanded/staff/broom(src.loc) - qdel(src) - -/obj/effect/landmark/costume/fakewizard/New() - new /obj/item/clothing/suit/wizrobe/fake(src.loc) - new /obj/item/clothing/head/wizard/fake(src.loc) - new /obj/item/twohanded/staff/(src.loc) - qdel(src) - -/obj/effect/landmark/costume/sexyclown/New() - new /obj/item/clothing/mask/gas/clown_hat/sexy(loc) - new /obj/item/clothing/under/rank/clown/sexy(loc) - qdel(src) - -/obj/effect/landmark/costume/sexymime/New() - new /obj/item/clothing/mask/gas/sexymime(src.loc) - new /obj/item/clothing/under/sexymime(src.loc) - qdel(src) - -/obj/effect/landmark/ruin - var/datum/map_template/ruin/ruin_template - -/obj/effect/landmark/ruin/New(loc, my_ruin_template) - name = "ruin_[GLOB.ruin_landmarks.len + 1]" - ..(loc) - ruin_template = my_ruin_template - GLOB.ruin_landmarks |= src - -/obj/effect/landmark/ruin/Destroy() - GLOB.ruin_landmarks -= src - ruin_template = null - . = ..() - -// Damage tiles -/obj/effect/landmark/damageturf - icon_state = "damaged" - -/obj/effect/landmark/damageturf/New() - ..() - var/turf/simulated/T = get_turf(src) - if(istype(T)) - T.break_tile() - -/obj/effect/landmark/burnturf - icon_state = "burned" - -/obj/effect/landmark/burnturf/New() - ..() - var/turf/simulated/T = get_turf(src) - T.burn_tile() - - -/obj/effect/landmark/battle_mob_point - name = "Nanomob Battle Avatar Spawn Point" - -/obj/effect/landmark/free_golem_spawn - name = "Free Golem Spawn Point" \ No newline at end of file +/obj/effect/landmark + name = "landmark" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x2" + anchored = 1.0 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/effect/landmark/New() + + ..() + set_tag() + invisibility = 101 + + switch(name) //some of these are probably obsolete + if("start") + newplayer_start += loc + qdel(src) + + if("wizard") + wizardstart += loc + qdel(src) + + if("JoinLate") + latejoin += loc + qdel(src) + + if("JoinLateGateway") + latejoin_gateway += loc + qdel(src) + + if("JoinLateCryo") + latejoin_cryo += loc + qdel(src) + + if("JoinLateCyborg") + latejoin_cyborg += loc + qdel(src) + + if("prisonwarp") + prisonwarp += loc + qdel(src) + + if("prisonsecuritywarp") + prisonsecuritywarp += loc + qdel(src) + + if("tdome1") + tdome1 += loc + + if("tdome2") + tdome2 += loc + + if("tdomeadmin") + tdomeadmin += loc + + if("tdomeobserve") + tdomeobserve += loc + + if("aroomwarp") + aroomwarp += loc + + if("blobstart") + blobstart += loc + qdel(src) + + if("xeno_spawn") + xeno_spawn += loc + qdel(src) + + if("ninjastart") + ninjastart += loc + qdel(src) + + if("carpspawn") + carplist += loc + + if("voxstart") + raider_spawn += loc + + if("ERT Director") + ertdirector += loc + qdel(src) + + if("Response Team") + emergencyresponseteamspawn += loc + qdel(src) + + if("Syndicate Officer") + syndicateofficer += loc + qdel(src) + + GLOB.landmarks_list += src + return 1 + +/obj/effect/landmark/Destroy() + GLOB.landmarks_list -= src + ..() + return QDEL_HINT_HARDDEL_NOW + +/obj/effect/landmark/proc/set_tag() + tag = text("landmark*[]", name) + + +/obj/effect/landmark/singularity_act() + return + +// Please stop bombing the Observer-Start landmark. +/obj/effect/landmark/ex_act() + return + +/obj/effect/landmark/singularity_pull() + return + +/obj/effect/landmark/start + name = "start" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x" + anchored = 1.0 + +/obj/effect/landmark/start/set_tag() + tag = "start*[name]" + + +//Costume spawner landmarks + +/obj/effect/landmark/costume/New() //costume spawner, selects a random subclass and disappears + + var/list/options = typesof(/obj/effect/landmark/costume) + var/PICK= options[rand(1,options.len)] + new PICK(src.loc) + qdel(src) + +//SUBCLASSES. Spawn a bunch of items and disappear likewise +/obj/effect/landmark/costume/chicken/New() + new /obj/item/clothing/suit/chickensuit(src.loc) + new /obj/item/clothing/head/chicken(src.loc) + new /obj/item/reagent_containers/food/snacks/egg(src.loc) + qdel(src) + +/obj/effect/landmark/costume/gladiator/New() + new /obj/item/clothing/under/gladiator(src.loc) + new /obj/item/clothing/head/helmet/gladiator(src.loc) + qdel(src) + +/obj/effect/landmark/costume/madscientist/New() + new /obj/item/clothing/under/gimmick/rank/captain/suit(src.loc) + new /obj/item/clothing/head/flatcap(src.loc) + new /obj/item/clothing/suit/storage/labcoat/mad(src.loc) + new /obj/item/clothing/glasses/gglasses(src.loc) + qdel(src) + +/obj/effect/landmark/costume/elpresidente/New() + new /obj/item/clothing/under/gimmick/rank/captain/suit(src.loc) + new /obj/item/clothing/head/flatcap(src.loc) + new /obj/item/clothing/mask/cigarette/cigar/havana(src.loc) + new /obj/item/clothing/shoes/jackboots(src.loc) + qdel(src) + +/obj/effect/landmark/costume/nyangirl/New() + new /obj/item/clothing/under/schoolgirl(src.loc) + new /obj/item/clothing/head/kitty(src.loc) + qdel(src) + +/obj/effect/landmark/costume/maid/New() + new /obj/item/clothing/under/blackskirt(src.loc) + var/CHOICE = pick( /obj/item/clothing/head/beret , /obj/item/clothing/head/rabbitears ) + new CHOICE(src.loc) + new /obj/item/clothing/glasses/sunglasses/blindfold(src.loc) + qdel(src) + +/obj/effect/landmark/costume/butler/New() + new /obj/item/clothing/suit/wcoat(src.loc) + new /obj/item/clothing/under/suit_jacket(src.loc) + new /obj/item/clothing/head/that(src.loc) + qdel(src) + +/obj/effect/landmark/costume/scratch/New() + new /obj/item/clothing/gloves/color/white(src.loc) + new /obj/item/clothing/shoes/white(src.loc) + new /obj/item/clothing/under/scratch(src.loc) + if(prob(30)) + new /obj/item/clothing/head/cueball(src.loc) + qdel(src) + +/obj/effect/landmark/costume/highlander/New() + new /obj/item/clothing/under/kilt(src.loc) + new /obj/item/clothing/head/beret(src.loc) + qdel(src) + +/obj/effect/landmark/costume/prig/New() + new /obj/item/clothing/suit/wcoat(src.loc) + new /obj/item/clothing/glasses/monocle(src.loc) + var/CHOICE= pick( /obj/item/clothing/head/bowlerhat, /obj/item/clothing/head/that) + new CHOICE(src.loc) + new /obj/item/clothing/shoes/black(src.loc) + new /obj/item/cane(src.loc) + new /obj/item/clothing/under/sl_suit(src.loc) + new /obj/item/clothing/mask/fakemoustache(src.loc) + qdel(src) + +/obj/effect/landmark/costume/plaguedoctor/New() + new /obj/item/clothing/suit/bio_suit/plaguedoctorsuit(src.loc) + new /obj/item/clothing/head/plaguedoctorhat(src.loc) + qdel(src) + +/obj/effect/landmark/costume/nightowl/New() + new /obj/item/clothing/under/owl(src.loc) + new /obj/item/clothing/mask/gas/owl_mask(src.loc) + qdel(src) + +/obj/effect/landmark/costume/waiter/New() + new /obj/item/clothing/under/waiter(src.loc) + var/CHOICE= pick( /obj/item/clothing/head/kitty, /obj/item/clothing/head/rabbitears) + new CHOICE(src.loc) + new /obj/item/clothing/suit/apron(src.loc) + qdel(src) + +/obj/effect/landmark/costume/pirate/New() + new /obj/item/clothing/under/pirate(src.loc) + new /obj/item/clothing/suit/pirate_black(src.loc) + var/CHOICE = pick( /obj/item/clothing/head/pirate , /obj/item/clothing/head/bandana ) + new CHOICE(src.loc) + new /obj/item/clothing/glasses/eyepatch(src.loc) + qdel(src) + +/obj/effect/landmark/costume/commie/New() + new /obj/item/clothing/under/soviet(src.loc) + new /obj/item/clothing/head/ushanka(src.loc) + qdel(src) + + +/obj/effect/landmark/costume/imperium_monk/New() + new /obj/item/clothing/suit/imperium_monk(src.loc) + if(prob(25)) + new /obj/item/clothing/mask/gas/cyborg(src.loc) + qdel(src) + +/obj/effect/landmark/costume/holiday_priest/New() + new /obj/item/clothing/suit/holidaypriest(src.loc) + qdel(src) + +/obj/effect/landmark/costume/marisawizard/fake/New() + new /obj/item/clothing/head/wizard/marisa/fake(src.loc) + new/obj/item/clothing/suit/wizrobe/marisa/fake(src.loc) + qdel(src) + +/obj/effect/landmark/costume/cutewitch/New() + new /obj/item/clothing/under/sundress(src.loc) + new /obj/item/clothing/head/witchwig(src.loc) + new /obj/item/twohanded/staff/broom(src.loc) + qdel(src) + +/obj/effect/landmark/costume/fakewizard/New() + new /obj/item/clothing/suit/wizrobe/fake(src.loc) + new /obj/item/clothing/head/wizard/fake(src.loc) + new /obj/item/twohanded/staff/(src.loc) + qdel(src) + +/obj/effect/landmark/costume/sexyclown/New() + new /obj/item/clothing/mask/gas/clown_hat/sexy(loc) + new /obj/item/clothing/under/rank/clown/sexy(loc) + qdel(src) + +/obj/effect/landmark/costume/sexymime/New() + new /obj/item/clothing/mask/gas/sexymime(src.loc) + new /obj/item/clothing/under/sexymime(src.loc) + qdel(src) + +/obj/effect/landmark/ruin + var/datum/map_template/ruin/ruin_template + +/obj/effect/landmark/ruin/New(loc, my_ruin_template) + name = "ruin_[GLOB.ruin_landmarks.len + 1]" + ..(loc) + ruin_template = my_ruin_template + GLOB.ruin_landmarks |= src + +/obj/effect/landmark/ruin/Destroy() + GLOB.ruin_landmarks -= src + ruin_template = null + . = ..() + +// Damage tiles +/obj/effect/landmark/damageturf + icon_state = "damaged" + +/obj/effect/landmark/damageturf/New() + ..() + var/turf/simulated/T = get_turf(src) + if(istype(T)) + T.break_tile() + +/obj/effect/landmark/burnturf + icon_state = "burned" + +/obj/effect/landmark/burnturf/New() + ..() + var/turf/simulated/T = get_turf(src) + T.burn_tile() + + +/obj/effect/landmark/battle_mob_point + name = "Nanomob Battle Avatar Spawn Point" + +/obj/effect/landmark/free_golem_spawn + name = "Free Golem Spawn Point" diff --git a/code/game/objects/effects/manifest.dm b/code/game/objects/effects/manifest.dm index ea1c80e5346..f267494496e 100644 --- a/code/game/objects/effects/manifest.dm +++ b/code/game/objects/effects/manifest.dm @@ -1,20 +1,20 @@ -/obj/effect/manifest - name = "manifest" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x" - -/obj/effect/manifest/New() - - src.invisibility = 101 - return - -/obj/effect/manifest/proc/manifest() - var/dat = "Crew Manifest:
        " - for(var/mob/living/carbon/human/M in GLOB.mob_list) - dat += text(" [] - []
        ", M.name, M.get_assignment()) - var/obj/item/paper/P = new /obj/item/paper( src.loc ) - P.info = dat - P.name = "paper- 'Crew Manifest'" - //SN src = null - qdel(src) - return \ No newline at end of file +/obj/effect/manifest + name = "manifest" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x" + +/obj/effect/manifest/New() + + src.invisibility = 101 + return + +/obj/effect/manifest/proc/manifest() + var/dat = "Crew Manifest:
        " + for(var/mob/living/carbon/human/M in GLOB.mob_list) + dat += text(" [] - []
        ", M.name, M.get_assignment()) + var/obj/item/paper/P = new /obj/item/paper( src.loc ) + P.info = dat + P.name = "paper- 'Crew Manifest'" + //SN src = null + qdel(src) + return diff --git a/code/game/objects/effects/mapping_helpers.dm b/code/game/objects/effects/mapping_helpers.dm index 44a16c5ae5f..dd332f676fb 100644 --- a/code/game/objects/effects/mapping_helpers.dm +++ b/code/game/objects/effects/mapping_helpers.dm @@ -87,4 +87,4 @@ /obj/effect/mapping_helpers/no_lava/New() var/turf/T = get_turf(src) T.flags |= NO_LAVA_GEN - . = ..() \ No newline at end of file + . = ..() diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index 75cbacd2fda..04914655143 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -1,185 +1,185 @@ -/obj/effect/mine - name = "dummy mine" - desc = "I Better stay away from that thing." - density = 0 - anchored = 1 - icon = 'icons/obj/items.dmi' - icon_state = "uglyminearmed" - var/triggered = 0 - var/faction = "syndicate" - -/obj/effect/mine/proc/mineEffect(mob/living/victim) - to_chat(victim, "*click*") - -/obj/effect/mine/Crossed(AM as mob|obj, oldloc) - if(!isliving(AM)) - return - var/mob/living/M = AM - if(faction && (faction in M.faction)) - return - if(M.flying) - return - triggermine(M) - -/obj/effect/mine/proc/triggermine(mob/living/victim) - if(triggered) - return - visible_message("[victim] sets off [bicon(src)] [src]!") - do_sparks(3, 1, src) - mineEffect(victim) - triggered = 1 - qdel(src) - -/obj/effect/mine/ex_act(severity) - // Necessary because, as effects, they have infinite health, and wouldn't be destroyed otherwise. - // Also, they're pressure-sensitive mines, it makes sense that an explosion (wave of pressure) triggers/destroys them. - qdel(src) - -/obj/effect/mine/explosive - name = "explosive mine" - var/range_devastation = 0 - var/range_heavy = 1 - var/range_light = 2 - var/range_flash = 3 - -/obj/effect/mine/explosive/mineEffect(mob/living/victim) - explosion(loc, range_devastation, range_heavy, range_light, range_flash) - -/obj/effect/mine/stun - name = "stun mine" - var/stun_time = 8 - -/obj/effect/mine/stun/mineEffect(mob/living/victim) - if(isliving(victim)) - victim.Weaken(stun_time) - -/obj/effect/mine/depot - name = "sentry mine" - -/obj/effect/mine/depot/mineEffect(mob/living/victim) - var/area/syndicate_depot/core/depotarea = areaMaster - if(istype(depotarea)) - if(depotarea.mine_triggered(victim)) - explosion(loc, 1, 0, 0, 1) // devastate the tile you are on, but leave everything else untouched - -/obj/effect/mine/dnascramble - name = "Radiation Mine" - var/radiation_amount - -/obj/effect/mine/dnascramble/mineEffect(mob/living/victim) - victim.apply_effect(radiation_amount, IRRADIATE, 0) - if(ishuman(victim)) - var/mob/living/carbon/human/V = victim - if(NO_DNA in V.dna.species.species_traits) - return - randmutb(victim) - domutcheck(victim ,null) - -/obj/effect/mine/gas - name = "oxygen mine" - var/gas_amount = 360 - var/gas_type = LINDA_SPAWN_OXYGEN - -/obj/effect/mine/gas/mineEffect(mob/living/victim) - atmos_spawn_air(gas_type, gas_amount) - -/obj/effect/mine/gas/plasma - name = "plasma mine" - gas_type = LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS - -/obj/effect/mine/gas/n2o - name = "\improper N2O mine" - gas_type = LINDA_SPAWN_N2O - -/obj/effect/mine/sound - name = "honkblaster 1000" - var/sound = 'sound/items/bikehorn.ogg' - -/obj/effect/mine/sound/mineEffect(mob/living/victim) - playsound(loc, sound, 100, 1) - -/obj/effect/mine/sound/bwoink - name = "bwoink mine" - sound = 'sound/effects/adminhelp.ogg' - -/obj/effect/mine/pickup - name = "pickup" - desc = "pick me up" - icon = 'icons/effects/effects.dmi' - icon_state = "electricity2" - density = 0 - var/duration = 0 - -/obj/effect/mine/pickup/New() - ..() - animate(src, pixel_y = 4, time = 20, loop = -1) - -/obj/effect/mine/pickup/triggermine(mob/living/victim) - if(triggered) - return - triggered = 1 - invisibility = 101 - mineEffect(victim) - qdel(src) - -/obj/effect/mine/pickup/bloodbath - name = "Red Orb" - desc = "You feel angry just looking at it." - duration = 1200 //2min - color = "red" - -/obj/effect/mine/pickup/bloodbath/mineEffect(mob/living/carbon/victim) - if(!istype(victim) || !victim.client) - return - to_chat(victim, "RIP AND TEAR") - victim << 'sound/misc/e1m1.ogg' - var/old_color = victim.client.color - var/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0) - var/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0) - - spawn(0) - new /obj/effect/hallucination/delusion(victim.loc, victim, force_kind = "demon", duration = duration, skip_nearby = 0) - - var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw = new(victim.loc) - chainsaw.flags |= NODROP | DROPDEL - victim.drop_l_hand() - victim.drop_r_hand() - victim.put_in_hands(chainsaw) - chainsaw.attack_self(victim) - chainsaw.wield(victim) - victim.reagents.add_reagent("adminordrazine", 25) - - victim.client.color = pure_red - animate(victim.client,color = red_splash, time = 10, easing = SINE_EASING|EASE_OUT) - spawn(10) - animate(victim.client,color = old_color, time = duration)//, easing = SINE_EASING|EASE_OUT) - spawn(duration) - to_chat(victim, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.") - qdel(chainsaw) - qdel(src) - -/obj/effect/mine/pickup/healing - name = "Blue Orb" - desc = "You feel better just looking at it." - color = "blue" - -/obj/effect/mine/pickup/healing/mineEffect(mob/living/carbon/victim) - if(!victim.client || !istype(victim)) - return - to_chat(victim, "You feel great!") - victim.revive() - -/obj/effect/mine/pickup/speed - name = "Yellow Orb" - desc = "You feel faster just looking at it." - color = "yellow" - duration = 300 - -/obj/effect/mine/pickup/speed/mineEffect(mob/living/carbon/victim) - if(!victim.client || !istype(victim)) - return - to_chat(victim, "You feel fast!") - victim.status_flags |= GOTTAGOFAST - spawn(duration) - victim.status_flags &= ~GOTTAGOFAST - to_chat(victim, "You slow down.") +/obj/effect/mine + name = "dummy mine" + desc = "I Better stay away from that thing." + density = 0 + anchored = 1 + icon = 'icons/obj/items.dmi' + icon_state = "uglyminearmed" + var/triggered = 0 + var/faction = "syndicate" + +/obj/effect/mine/proc/mineEffect(mob/living/victim) + to_chat(victim, "*click*") + +/obj/effect/mine/Crossed(AM as mob|obj, oldloc) + if(!isliving(AM)) + return + var/mob/living/M = AM + if(faction && (faction in M.faction)) + return + if(M.flying) + return + triggermine(M) + +/obj/effect/mine/proc/triggermine(mob/living/victim) + if(triggered) + return + visible_message("[victim] sets off [bicon(src)] [src]!") + do_sparks(3, 1, src) + mineEffect(victim) + triggered = 1 + qdel(src) + +/obj/effect/mine/ex_act(severity) + // Necessary because, as effects, they have infinite health, and wouldn't be destroyed otherwise. + // Also, they're pressure-sensitive mines, it makes sense that an explosion (wave of pressure) triggers/destroys them. + qdel(src) + +/obj/effect/mine/explosive + name = "explosive mine" + var/range_devastation = 0 + var/range_heavy = 1 + var/range_light = 2 + var/range_flash = 3 + +/obj/effect/mine/explosive/mineEffect(mob/living/victim) + explosion(loc, range_devastation, range_heavy, range_light, range_flash) + +/obj/effect/mine/stun + name = "stun mine" + var/stun_time = 8 + +/obj/effect/mine/stun/mineEffect(mob/living/victim) + if(isliving(victim)) + victim.Weaken(stun_time) + +/obj/effect/mine/depot + name = "sentry mine" + +/obj/effect/mine/depot/mineEffect(mob/living/victim) + var/area/syndicate_depot/core/depotarea = areaMaster + if(istype(depotarea)) + if(depotarea.mine_triggered(victim)) + explosion(loc, 1, 0, 0, 1) // devastate the tile you are on, but leave everything else untouched + +/obj/effect/mine/dnascramble + name = "Radiation Mine" + var/radiation_amount + +/obj/effect/mine/dnascramble/mineEffect(mob/living/victim) + victim.apply_effect(radiation_amount, IRRADIATE, 0) + if(ishuman(victim)) + var/mob/living/carbon/human/V = victim + if(NO_DNA in V.dna.species.species_traits) + return + randmutb(victim) + domutcheck(victim ,null) + +/obj/effect/mine/gas + name = "oxygen mine" + var/gas_amount = 360 + var/gas_type = LINDA_SPAWN_OXYGEN + +/obj/effect/mine/gas/mineEffect(mob/living/victim) + atmos_spawn_air(gas_type, gas_amount) + +/obj/effect/mine/gas/plasma + name = "plasma mine" + gas_type = LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS + +/obj/effect/mine/gas/n2o + name = "\improper N2O mine" + gas_type = LINDA_SPAWN_N2O + +/obj/effect/mine/sound + name = "honkblaster 1000" + var/sound = 'sound/items/bikehorn.ogg' + +/obj/effect/mine/sound/mineEffect(mob/living/victim) + playsound(loc, sound, 100, 1) + +/obj/effect/mine/sound/bwoink + name = "bwoink mine" + sound = 'sound/effects/adminhelp.ogg' + +/obj/effect/mine/pickup + name = "pickup" + desc = "pick me up" + icon = 'icons/effects/effects.dmi' + icon_state = "electricity2" + density = 0 + var/duration = 0 + +/obj/effect/mine/pickup/New() + ..() + animate(src, pixel_y = 4, time = 20, loop = -1) + +/obj/effect/mine/pickup/triggermine(mob/living/victim) + if(triggered) + return + triggered = 1 + invisibility = 101 + mineEffect(victim) + qdel(src) + +/obj/effect/mine/pickup/bloodbath + name = "Red Orb" + desc = "You feel angry just looking at it." + duration = 1200 //2min + color = "red" + +/obj/effect/mine/pickup/bloodbath/mineEffect(mob/living/carbon/victim) + if(!istype(victim) || !victim.client) + return + to_chat(victim, "RIP AND TEAR") + victim << 'sound/misc/e1m1.ogg' + var/old_color = victim.client.color + var/red_splash = list(1,0,0,0.8,0.2,0, 0.8,0,0.2,0.1,0,0) + var/pure_red = list(0,0,0,0,0,0,0,0,0,1,0,0) + + spawn(0) + new /obj/effect/hallucination/delusion(victim.loc, victim, force_kind = "demon", duration = duration, skip_nearby = 0) + + var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw = new(victim.loc) + chainsaw.flags |= NODROP | DROPDEL + victim.drop_l_hand() + victim.drop_r_hand() + victim.put_in_hands(chainsaw) + chainsaw.attack_self(victim) + chainsaw.wield(victim) + victim.reagents.add_reagent("adminordrazine", 25) + + victim.client.color = pure_red + animate(victim.client,color = red_splash, time = 10, easing = SINE_EASING|EASE_OUT) + spawn(10) + animate(victim.client,color = old_color, time = duration)//, easing = SINE_EASING|EASE_OUT) + spawn(duration) + to_chat(victim, "Your bloodlust seeps back into the bog of your subconscious and you regain self control.") + qdel(chainsaw) + qdel(src) + +/obj/effect/mine/pickup/healing + name = "Blue Orb" + desc = "You feel better just looking at it." + color = "blue" + +/obj/effect/mine/pickup/healing/mineEffect(mob/living/carbon/victim) + if(!victim.client || !istype(victim)) + return + to_chat(victim, "You feel great!") + victim.revive() + +/obj/effect/mine/pickup/speed + name = "Yellow Orb" + desc = "You feel faster just looking at it." + color = "yellow" + duration = 300 + +/obj/effect/mine/pickup/speed/mineEffect(mob/living/carbon/victim) + if(!victim.client || !istype(victim)) + return + to_chat(victim, "You feel fast!") + victim.status_flags |= GOTTAGOFAST + spawn(duration) + victim.status_flags &= ~GOTTAGOFAST + to_chat(victim, "You slow down.") diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm index bd237fbfdb6..434c0af1136 100644 --- a/code/game/objects/effects/misc.dm +++ b/code/game/objects/effects/misc.dm @@ -1,123 +1,123 @@ -//MISC EFFECTS - -//This file is for effects that are less than 20 lines and don't fit very well in any other category. - -/*CURRENT CONTENTS - Strange Present - Mark - Beam - Laser - Begin - Stop - Projection - Shut_controller - Showcase - Spawner - List_container -*/ - -//The effect when you wrap a dead body in gift wrap -/obj/effect/spresent - name = "strange present" - desc = "It's a ... present?" - icon = 'icons/obj/items.dmi' - icon_state = "strangepresent" - density = 1 - anchored = 0 - -/obj/effect/mark - var/mark = "" - icon = 'icons/misc/mark.dmi' - icon_state = "blank" - anchored = 1 - layer = 99 - plane = HUD_PLANE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/beam - name = "beam" - var/def_zone - pass_flags = PASSTABLE - -/obj/effect/laser - name = "laser" - desc = "IT BURNS!!!" - icon = 'icons/obj/projectiles.dmi' - var/damage = 0.0 - var/range = 10.0 - -/obj/effect/begin - name = "begin" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "begin" - anchored = 1.0 - -/obj/effect/projection - name = "Projection" - desc = "This looks like a projection of something." - anchored = 1.0 - - -/obj/effect/shut_controller - name = "shut controller" - var/moving = null - var/list/parts = list( ) - -/obj/structure/showcase - name = "Showcase" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "showcase_1" - desc = "A stand with the empty body of a cyborg bolted to it." - density = 1 - anchored = 1 - -/obj/effect/spawner - name = "object spawner" - -/obj/effect/list_container - name = "list container" - -/obj/effect/list_container/mobl - name = "mobl" - var/master = null - - var/list/container = list( ) - - -/obj/structure/showcase/horrific_experiment - name = "horrific experiment" - desc = "Some sort of pod filled with blood and vicerea. You swear you can see it moving..." - icon = 'icons/obj/cloning.dmi' - icon_state = "pod_g" - - -//Makes a tile fully lit no matter what -/obj/effect/fullbright - icon = 'icons/effects/alphacolors.dmi' - icon_state = "white" - plane = LIGHTING_PLANE - layer = LIGHTING_LAYER - blend_mode = BLEND_ADD - - -/obj/effect/dummy/lighting_obj - name = "lighting fx obj" - desc = "Tell a coder if you're seeing this." - icon_state = "nothing" - light_color = "#FFFFFF" - light_range = MINIMUM_USEFUL_LIGHT_RANGE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/dummy/lighting_obj/Initialize(mapload, _color, _range, _power, _duration) - . = ..() - set_light(_range ? _range : light_range, _power ? _power : light_power, _color ? _color : light_color) - if(_duration) - QDEL_IN(src, _duration) - -/obj/effect/dummy/lighting_obj/moblight - name = "mob lighting fx" - -/obj/effect/dummy/lighting_obj/moblight/Initialize(mapload, _color, _range, _power, _duration) - . = ..() - if(!ismob(loc)) - return INITIALIZE_HINT_QDEL \ No newline at end of file +//MISC EFFECTS + +//This file is for effects that are less than 20 lines and don't fit very well in any other category. + +/*CURRENT CONTENTS + Strange Present + Mark + Beam + Laser + Begin + Stop + Projection + Shut_controller + Showcase + Spawner + List_container +*/ + +//The effect when you wrap a dead body in gift wrap +/obj/effect/spresent + name = "strange present" + desc = "It's a ... present?" + icon = 'icons/obj/items.dmi' + icon_state = "strangepresent" + density = 1 + anchored = 0 + +/obj/effect/mark + var/mark = "" + icon = 'icons/misc/mark.dmi' + icon_state = "blank" + anchored = 1 + layer = 99 + plane = HUD_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/beam + name = "beam" + var/def_zone + pass_flags = PASSTABLE + +/obj/effect/laser + name = "laser" + desc = "IT BURNS!!!" + icon = 'icons/obj/projectiles.dmi' + var/damage = 0.0 + var/range = 10.0 + +/obj/effect/begin + name = "begin" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "begin" + anchored = 1.0 + +/obj/effect/projection + name = "Projection" + desc = "This looks like a projection of something." + anchored = 1.0 + + +/obj/effect/shut_controller + name = "shut controller" + var/moving = null + var/list/parts = list( ) + +/obj/structure/showcase + name = "Showcase" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "showcase_1" + desc = "A stand with the empty body of a cyborg bolted to it." + density = 1 + anchored = 1 + +/obj/effect/spawner + name = "object spawner" + +/obj/effect/list_container + name = "list container" + +/obj/effect/list_container/mobl + name = "mobl" + var/master = null + + var/list/container = list( ) + + +/obj/structure/showcase/horrific_experiment + name = "horrific experiment" + desc = "Some sort of pod filled with blood and vicerea. You swear you can see it moving..." + icon = 'icons/obj/cloning.dmi' + icon_state = "pod_g" + + +//Makes a tile fully lit no matter what +/obj/effect/fullbright + icon = 'icons/effects/alphacolors.dmi' + icon_state = "white" + plane = LIGHTING_PLANE + layer = LIGHTING_LAYER + blend_mode = BLEND_ADD + + +/obj/effect/dummy/lighting_obj + name = "lighting fx obj" + desc = "Tell a coder if you're seeing this." + icon_state = "nothing" + light_color = "#FFFFFF" + light_range = MINIMUM_USEFUL_LIGHT_RANGE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/dummy/lighting_obj/Initialize(mapload, _color, _range, _power, _duration) + . = ..() + set_light(_range ? _range : light_range, _power ? _power : light_power, _color ? _color : light_color) + if(_duration) + QDEL_IN(src, _duration) + +/obj/effect/dummy/lighting_obj/moblight + name = "mob lighting fx" + +/obj/effect/dummy/lighting_obj/moblight/Initialize(mapload, _color, _range, _power, _duration) + . = ..() + if(!ismob(loc)) + return INITIALIZE_HINT_QDEL diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm index 4506f9b33a9..c7e66b6226e 100644 --- a/code/game/objects/effects/overlays.dm +++ b/code/game/objects/effects/overlays.dm @@ -1,65 +1,65 @@ -/obj/effect/overlay - name = "overlay" - var/i_attached//Added for possible image attachments to objects. For hallucinations and the like. - -/obj/effect/overlay/singularity_act() - return - -/obj/effect/overlay/singularity_pull() - return - -/obj/effect/overlay/beam//Not actually a projectile, just an effect. - name = "beam" - icon = 'icons/effects/beam.dmi' - icon_state = "b_beam" - var/tmp/atom/BeamSource - -/obj/effect/overlay/beam/New() - ..() - QDEL_IN(src, 10) - -/obj/effect/overlay/palmtree_r - name = "Palm tree" - icon = 'icons/misc/beach2.dmi' - icon_state = "palm1" - density = 1 - layer = 5 - anchored = 1 - -/obj/effect/overlay/palmtree_l - name = "Palm tree" - icon = 'icons/misc/beach2.dmi' - icon_state = "palm2" - density = 1 - layer = 5 - anchored = 1 - -/obj/effect/overlay/coconut - name = "Coconuts" - icon = 'icons/misc/beach.dmi' - icon_state = "coconuts" - -/obj/effect/overlay/sparkles - name = "sparkles" - icon = 'icons/effects/effects.dmi' - icon_state = "shieldsparkles" - -/obj/effect/overlay/adminoverlay - name = "adminoverlay" - icon = 'icons/effects/effects.dmi' - icon_state = "admin" - layer = 4.1 - -/obj/effect/overlay/wall_rot - name = "Wallrot" - desc = "Ick..." - icon = 'icons/effects/wallrot.dmi' - anchored = 1 - density = 1 - layer = 5 - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/effect/overlay/wall_rot/New() - ..() - pixel_x += rand(-10, 10) - pixel_y += rand(-10, 10) +/obj/effect/overlay + name = "overlay" + var/i_attached//Added for possible image attachments to objects. For hallucinations and the like. + +/obj/effect/overlay/singularity_act() + return + +/obj/effect/overlay/singularity_pull() + return + +/obj/effect/overlay/beam//Not actually a projectile, just an effect. + name = "beam" + icon = 'icons/effects/beam.dmi' + icon_state = "b_beam" + var/tmp/atom/BeamSource + +/obj/effect/overlay/beam/New() + ..() + QDEL_IN(src, 10) + +/obj/effect/overlay/palmtree_r + name = "Palm tree" + icon = 'icons/misc/beach2.dmi' + icon_state = "palm1" + density = 1 + layer = 5 + anchored = 1 + +/obj/effect/overlay/palmtree_l + name = "Palm tree" + icon = 'icons/misc/beach2.dmi' + icon_state = "palm2" + density = 1 + layer = 5 + anchored = 1 + +/obj/effect/overlay/coconut + name = "Coconuts" + icon = 'icons/misc/beach.dmi' + icon_state = "coconuts" + +/obj/effect/overlay/sparkles + name = "sparkles" + icon = 'icons/effects/effects.dmi' + icon_state = "shieldsparkles" + +/obj/effect/overlay/adminoverlay + name = "adminoverlay" + icon = 'icons/effects/effects.dmi' + icon_state = "admin" + layer = 4.1 + +/obj/effect/overlay/wall_rot + name = "Wallrot" + desc = "Ick..." + icon = 'icons/effects/wallrot.dmi' + anchored = 1 + density = 1 + layer = 5 + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/effect/overlay/wall_rot/New() + ..() + pixel_x += rand(-10, 10) + pixel_y += rand(-10, 10) diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm index ddf43f9c077..aa756631768 100644 --- a/code/game/objects/effects/portals.dm +++ b/code/game/objects/effects/portals.dm @@ -1,128 +1,128 @@ -/obj/effect/portal - name = "portal" - desc = "Looks unstable. Best to test it with the clown." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "portal" - anchored = TRUE - - var/obj/item/target = null - var/creator = null - - var/failchance = 5 - var/fail_icon = "portal1" - - var/precision = TRUE // how close to the portal you will teleport. FALSE = on the portal, TRUE = adjacent - var/can_multitool_to_remove = FALSE - var/ignore_tele_proof_area_setting = FALSE - -/obj/effect/portal/New(loc, turf/target, creator = null, lifespan = 300) - ..() - - GLOB.portals += src - - src.target = target - src.creator = creator - - if(lifespan > 0) - spawn(lifespan) - qdel(src) - -/obj/effect/portal/Destroy() - GLOB.portals -= src - - if(isobj(creator)) - var/obj/O = creator - O.portal_destroyed(src) - - creator = null - target = null - return ..() - -/obj/effect/portal/singularity_pull() - return - -/obj/effect/portal/singularity_act() - return - -/obj/effect/portal/Crossed(atom/movable/AM, oldloc) - if(isobserver(AM)) - return ..() - - if(target && (get_turf(oldloc) == get_turf(target))) - return ..() - - if(!teleport(AM)) - return ..() - -/obj/effect/portal/attack_tk(mob/user) - return - -/obj/effect/portal/attack_hand(mob/user) - . = ..() - if(.) - return - if(get_turf(user) == get_turf(src)) - teleport(user) - if(Adjacent(user)) - user.forceMove(get_turf(src)) - -/obj/effect/portal/attack_ghost(mob/dead/observer/O) - if(target) - O.forceMove(target) - -/obj/effect/portal/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(can_multitool_to_remove) - qdel(src) - else - user.forceMove(get_turf(src)) - -/obj/effect/portal/proc/can_teleport(atom/movable/M) - . = TRUE - - if(!istype(M)) - . = FALSE - - if(!M.simulated || iseffect(M)) - . = FALSE - - if(M.anchored && ismecha(M)) - . = FALSE - -/obj/effect/portal/proc/teleport(atom/movable/M) - if(!can_teleport(M)) - return FALSE - - if(!target) - qdel(src) - return FALSE - - if(ismegafauna(M)) - message_admins("[M] has used a portal at [ADMIN_VERBOSEJMP(src)] made by [key_name_admin(usr)].") - - if(prob(failchance)) - icon_state = fail_icon - if(!do_teleport(M, locate(rand(5, world.maxx - 5), rand(5, world.maxy -5), 3), 0, bypass_area_flag = ignore_tele_proof_area_setting)) // Try to send them to deep space. - invalid_teleport() - return FALSE - else - if(!do_teleport(M, target, precision, bypass_area_flag = ignore_tele_proof_area_setting)) // Try to send them to a turf adjacent to target. - invalid_teleport() - return FALSE - - return TRUE - -/obj/effect/portal/proc/invalid_teleport() - visible_message("[src] flickers and fails due to bluespace interference!") - do_sparks(5, 0, loc) - qdel(src) - -/obj/effect/portal/redspace - name = "redspace portal" - desc = "A portal capable of bypassing bluespace interference." - icon_state = "portal1" - failchance = 0 - precision = 0 - ignore_tele_proof_area_setting = TRUE +/obj/effect/portal + name = "portal" + desc = "Looks unstable. Best to test it with the clown." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "portal" + anchored = TRUE + + var/obj/item/target = null + var/creator = null + + var/failchance = 5 + var/fail_icon = "portal1" + + var/precision = TRUE // how close to the portal you will teleport. FALSE = on the portal, TRUE = adjacent + var/can_multitool_to_remove = FALSE + var/ignore_tele_proof_area_setting = FALSE + +/obj/effect/portal/New(loc, turf/target, creator = null, lifespan = 300) + ..() + + GLOB.portals += src + + src.target = target + src.creator = creator + + if(lifespan > 0) + spawn(lifespan) + qdel(src) + +/obj/effect/portal/Destroy() + GLOB.portals -= src + + if(isobj(creator)) + var/obj/O = creator + O.portal_destroyed(src) + + creator = null + target = null + return ..() + +/obj/effect/portal/singularity_pull() + return + +/obj/effect/portal/singularity_act() + return + +/obj/effect/portal/Crossed(atom/movable/AM, oldloc) + if(isobserver(AM)) + return ..() + + if(target && (get_turf(oldloc) == get_turf(target))) + return ..() + + if(!teleport(AM)) + return ..() + +/obj/effect/portal/attack_tk(mob/user) + return + +/obj/effect/portal/attack_hand(mob/user) + . = ..() + if(.) + return + if(get_turf(user) == get_turf(src)) + teleport(user) + if(Adjacent(user)) + user.forceMove(get_turf(src)) + +/obj/effect/portal/attack_ghost(mob/dead/observer/O) + if(target) + O.forceMove(target) + +/obj/effect/portal/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(can_multitool_to_remove) + qdel(src) + else + user.forceMove(get_turf(src)) + +/obj/effect/portal/proc/can_teleport(atom/movable/M) + . = TRUE + + if(!istype(M)) + . = FALSE + + if(!M.simulated || iseffect(M)) + . = FALSE + + if(M.anchored && ismecha(M)) + . = FALSE + +/obj/effect/portal/proc/teleport(atom/movable/M) + if(!can_teleport(M)) + return FALSE + + if(!target) + qdel(src) + return FALSE + + if(ismegafauna(M)) + message_admins("[M] has used a portal at [ADMIN_VERBOSEJMP(src)] made by [key_name_admin(usr)].") + + if(prob(failchance)) + icon_state = fail_icon + if(!do_teleport(M, locate(rand(5, world.maxx - 5), rand(5, world.maxy -5), 3), 0, bypass_area_flag = ignore_tele_proof_area_setting)) // Try to send them to deep space. + invalid_teleport() + return FALSE + else + if(!do_teleport(M, target, precision, bypass_area_flag = ignore_tele_proof_area_setting)) // Try to send them to a turf adjacent to target. + invalid_teleport() + return FALSE + + return TRUE + +/obj/effect/portal/proc/invalid_teleport() + visible_message("[src] flickers and fails due to bluespace interference!") + do_sparks(5, 0, loc) + qdel(src) + +/obj/effect/portal/redspace + name = "redspace portal" + desc = "A portal capable of bypassing bluespace interference." + icon_state = "portal1" + failchance = 0 + precision = 0 + ignore_tele_proof_area_setting = TRUE diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm index bfacf64adb5..2e81ecb235d 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -1,65 +1,65 @@ -/obj/effect/spawner/newbomb - name = "bomb" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x" - var/btype = 0 // 0=radio, 1=prox, 2=time - var/btemp1 = 1500 - var/btemp2 = 1000 // tank temperatures - - timer - btype = 2 - - syndicate - btemp1 = 150 - btemp2 = 20 - - proximity - btype = 1 - - radio - btype = 0 - - -/obj/effect/spawner/newbomb/New() - ..() - - var/obj/item/transfer_valve/V = new(src.loc) - var/obj/item/tank/plasma/PT = new(V) - var/obj/item/tank/oxygen/OT = new(V) - - V.tank_one = PT - V.tank_two = OT - - PT.master = V - OT.master = V - - PT.air_contents.temperature = btemp1 + T0C - OT.air_contents.temperature = btemp2 + T0C - - var/obj/item/assembly/S - - switch(src.btype) - // radio - if(0) - - S = new/obj/item/assembly/signaler(V) - - // proximity - if(1) - - S = new/obj/item/assembly/prox_sensor(V) - - // timer - if(2) - - S = new/obj/item/assembly/timer(V) - - - V.attached_device = S - - S.holder = V - S.toggle_secure() - - V.update_icon() - - qdel(src) +/obj/effect/spawner/newbomb + name = "bomb" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x" + var/btype = 0 // 0=radio, 1=prox, 2=time + var/btemp1 = 1500 + var/btemp2 = 1000 // tank temperatures + + timer + btype = 2 + + syndicate + btemp1 = 150 + btemp2 = 20 + + proximity + btype = 1 + + radio + btype = 0 + + +/obj/effect/spawner/newbomb/New() + ..() + + var/obj/item/transfer_valve/V = new(src.loc) + var/obj/item/tank/plasma/PT = new(V) + var/obj/item/tank/oxygen/OT = new(V) + + V.tank_one = PT + V.tank_two = OT + + PT.master = V + OT.master = V + + PT.air_contents.temperature = btemp1 + T0C + OT.air_contents.temperature = btemp2 + T0C + + var/obj/item/assembly/S + + switch(src.btype) + // radio + if(0) + + S = new/obj/item/assembly/signaler(V) + + // proximity + if(1) + + S = new/obj/item/assembly/prox_sensor(V) + + // timer + if(2) + + S = new/obj/item/assembly/timer(V) + + + V.attached_device = S + + S.holder = V + S.toggle_secure() + + V.update_icon() + + qdel(src) diff --git a/code/game/objects/effects/spawners/gibspawner.dm b/code/game/objects/effects/spawners/gibspawner.dm index b75df275659..1678f67e8d6 100644 --- a/code/game/objects/effects/spawners/gibspawner.dm +++ b/code/game/objects/effects/spawners/gibspawner.dm @@ -1,35 +1,35 @@ -/obj/effect/gibspawner/generic - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs/core) - gibamounts = list(2,2,1) - -/obj/effect/gibspawner/generic/New() - gibdirections = list(list(WEST, NORTHWEST, SOUTHWEST, NORTH),list(EAST, NORTHEAST, SOUTHEAST, SOUTH), list()) - ..() - -/obj/effect/gibspawner/human - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs/down,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs/core) - gibamounts = list(1,1,1,1,1,1,1) - -/obj/effect/gibspawner/human/New() - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), alldirs, alldirs, list()) - gibamounts[6] = pick(0,1,2) - ..() - -/obj/effect/gibspawner/xeno - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/xeno/up,/obj/effect/decal/cleanable/blood/gibs/xeno/down,/obj/effect/decal/cleanable/blood/gibs/xeno,/obj/effect/decal/cleanable/blood/gibs/xeno,/obj/effect/decal/cleanable/blood/gibs/xeno/body,/obj/effect/decal/cleanable/blood/gibs/xeno/limb,/obj/effect/decal/cleanable/blood/gibs/xeno/core) - gibamounts = list(1,1,1,1,1,1,1) - -/obj/effect/gibspawner/xeno/New() - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), alldirs, alldirs, list()) - gibamounts[6] = pick(0,1,2) - ..() - -/obj/effect/gibspawner/robot - sparks = 1 - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/robot/up,/obj/effect/decal/cleanable/blood/gibs/robot/down,/obj/effect/decal/cleanable/blood/gibs/robot,/obj/effect/decal/cleanable/blood/gibs/robot,/obj/effect/decal/cleanable/blood/gibs/robot,/obj/effect/decal/cleanable/blood/gibs/robot/limb) - gibamounts = list(1,1,1,1,1,1) - -/obj/effect/gibspawner/robot/New() - gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), alldirs, alldirs) - gibamounts[6] = pick(0,1,2) - ..() +/obj/effect/gibspawner/generic + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs/core) + gibamounts = list(2,2,1) + +/obj/effect/gibspawner/generic/New() + gibdirections = list(list(WEST, NORTHWEST, SOUTHWEST, NORTH),list(EAST, NORTHEAST, SOUTHEAST, SOUTH), list()) + ..() + +/obj/effect/gibspawner/human + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs/down,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs,/obj/effect/decal/cleanable/blood/gibs/core) + gibamounts = list(1,1,1,1,1,1,1) + +/obj/effect/gibspawner/human/New() + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), alldirs, alldirs, list()) + gibamounts[6] = pick(0,1,2) + ..() + +/obj/effect/gibspawner/xeno + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/xeno/up,/obj/effect/decal/cleanable/blood/gibs/xeno/down,/obj/effect/decal/cleanable/blood/gibs/xeno,/obj/effect/decal/cleanable/blood/gibs/xeno,/obj/effect/decal/cleanable/blood/gibs/xeno/body,/obj/effect/decal/cleanable/blood/gibs/xeno/limb,/obj/effect/decal/cleanable/blood/gibs/xeno/core) + gibamounts = list(1,1,1,1,1,1,1) + +/obj/effect/gibspawner/xeno/New() + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), alldirs, alldirs, list()) + gibamounts[6] = pick(0,1,2) + ..() + +/obj/effect/gibspawner/robot + sparks = 1 + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/robot/up,/obj/effect/decal/cleanable/blood/gibs/robot/down,/obj/effect/decal/cleanable/blood/gibs/robot,/obj/effect/decal/cleanable/blood/gibs/robot,/obj/effect/decal/cleanable/blood/gibs/robot,/obj/effect/decal/cleanable/blood/gibs/robot/limb) + gibamounts = list(1,1,1,1,1,1) + +/obj/effect/gibspawner/robot/New() + gibdirections = list(list(NORTH, NORTHEAST, NORTHWEST),list(SOUTH, SOUTHEAST, SOUTHWEST),list(WEST, NORTHWEST, SOUTHWEST),list(EAST, NORTHEAST, SOUTHEAST), alldirs, alldirs) + gibamounts[6] = pick(0,1,2) + ..() diff --git a/code/game/objects/effects/spawners/random_spawners.dm b/code/game/objects/effects/spawners/random_spawners.dm index ee7fc670075..b32688dbb83 100644 --- a/code/game/objects/effects/spawners/random_spawners.dm +++ b/code/game/objects/effects/spawners/random_spawners.dm @@ -318,4 +318,4 @@ /obj/effect/spawner/random_spawners/syndicate/layout/spacepod name = "50pc loot spacepod" result = list(/obj/spacepod/syndi = 1, - /obj/spacepod/syndi/unlocked = 1) \ No newline at end of file + /obj/spacepod/syndi/unlocked = 1) diff --git a/code/game/objects/effects/spawners/vaultspawner.dm b/code/game/objects/effects/spawners/vaultspawner.dm index 35cc039a08a..0d882cc9bd2 100644 --- a/code/game/objects/effects/spawners/vaultspawner.dm +++ b/code/game/objects/effects/spawners/vaultspawner.dm @@ -1,26 +1,26 @@ -/obj/effect/vaultspawner - var/maxX = 6 - var/maxY = 6 - var/minX = 2 - var/minY = 2 - -/obj/effect/vaultspawner/New(turf/location as turf,lX = minX,uX = maxX,lY = minY,uY = maxY,var/type = null) - if(!type) - type = pick("sandstone","rock","alien") - - var/lowBoundX = location.x - var/lowBoundY = location.y - - var/hiBoundX = location.x + rand(lX,uX) - var/hiBoundY = location.y + rand(lY,uY) - - var/z = location.z - - for(var/i = lowBoundX,i<=hiBoundX,i++) - for(var/j = lowBoundY,j<=hiBoundY,j++) - if(i == lowBoundX || i == hiBoundX || j == lowBoundY || j == hiBoundY) - new /turf/simulated/wall/vault(locate(i,j,z),type) - else - new /turf/simulated/floor/vault(locate(i,j,z),type) - - qdel(src) \ No newline at end of file +/obj/effect/vaultspawner + var/maxX = 6 + var/maxY = 6 + var/minX = 2 + var/minY = 2 + +/obj/effect/vaultspawner/New(turf/location as turf,lX = minX,uX = maxX,lY = minY,uY = maxY,var/type = null) + if(!type) + type = pick("sandstone","rock","alien") + + var/lowBoundX = location.x + var/lowBoundY = location.y + + var/hiBoundX = location.x + rand(lX,uX) + var/hiBoundY = location.y + rand(lY,uY) + + var/z = location.z + + for(var/i = lowBoundX,i<=hiBoundX,i++) + for(var/j = lowBoundY,j<=hiBoundY,j++) + if(i == lowBoundX || i == hiBoundX || j == lowBoundY || j == hiBoundY) + new /turf/simulated/wall/vault(locate(i,j,z),type) + else + new /turf/simulated/floor/vault(locate(i,j,z),type) + + qdel(src) diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 169a0a4b3c8..d97663759af 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -1,213 +1,213 @@ -//generic procs copied from obj/effect/alien -/obj/structure/spider - name = "web" - desc = "it's stringy and sticky" - icon = 'icons/effects/effects.dmi' - anchored = TRUE - density = FALSE - max_integrity = 15 - var/mob/living/carbon/human/master_commander = null - -/obj/structure/spider/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - if(damage_type == BURN)//the stickiness of the web mutes all attack sounds except fire damage type - playsound(loc, 'sound/items/welder.ogg', 100, TRUE) - - -/obj/structure/spider/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee") - switch(damage_type) - if(BURN) - damage_amount *= 2 - if(BRUTE) - damage_amount *= 0.25 - . = ..() - -/obj/structure/spider/Destroy() - master_commander = null - return ..() - -/obj/structure/spider/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > 300) - take_damage(5, BURN, 0, 0) - -/obj/structure/spider/stickyweb - icon_state = "stickyweb1" - -/obj/structure/spider/stickyweb/New() - ..() - if(prob(50)) - icon_state = "stickyweb2" - -/obj/structure/spider/stickyweb/CanPass(atom/movable/mover, turf/target, height=0) - if(height == 0) - return TRUE - if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider)) - return TRUE - else if(istype(mover, /mob/living)) - if(prob(50)) - to_chat(mover, "You get stuck in [src] for a moment.") - return FALSE - else if(istype(mover, /obj/item/projectile)) - return prob(30) - return TRUE - -/obj/structure/spider/eggcluster - name = "egg cluster" - desc = "They seem to pulse slightly with an inner life" - icon_state = "eggs" - var/amount_grown = 0 - var/player_spiders = 0 - var/list/faction = list() - -/obj/structure/spider/eggcluster/New() - ..() - pixel_x = rand(3,-3) - pixel_y = rand(3,-3) - START_PROCESSING(SSobj, src) - -/obj/structure/spider/eggcluster/process() - amount_grown += rand(0,2) - if(amount_grown >= 100) - var/num = rand(3, 12) - for(var/i in 1 to num) - var/obj/structure/spider/spiderling/S = new /obj/structure/spider/spiderling(loc) - S.faction = faction.Copy() - S.master_commander = master_commander - if(player_spiders) - S.player_spiders = 1 - qdel(src) - -/obj/structure/spider/spiderling - name = "spiderling" - desc = "It never stays still for long." - icon_state = "spiderling" - anchored = 0 - layer = 2.75 - max_integrity = 3 - var/amount_grown = 0 - var/grow_as = null - var/obj/machinery/atmospherics/unary/vent_pump/entry_vent - var/travelling_in_vent = 0 - var/player_spiders = 0 - var/list/faction = list() - var/selecting_player = 0 - -/obj/structure/spider/spiderling/New() - ..() - pixel_x = rand(6,-6) - pixel_y = rand(6,-6) - START_PROCESSING(SSobj, src) - -/obj/structure/spider/spiderling/Destroy() - STOP_PROCESSING(SSobj, src) - entry_vent = null - new /obj/effect/decal/cleanable/spiderling_remains(get_turf(src)) - return ..() - -/obj/structure/spider/spiderling/Bump(atom/user) - if(istype(user, /obj/structure/table)) - loc = user.loc - else - ..() - -/obj/structure/spider/spiderling/process() - if(travelling_in_vent) - if(istype(loc, /turf)) - travelling_in_vent = 0 - entry_vent = null - else if(entry_vent) - if(get_dist(src, entry_vent) <= 1) - var/list/vents = list() - for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in entry_vent.parent.other_atmosmch) - vents.Add(temp_vent) - if(!vents.len) - entry_vent = null - return - var/obj/machinery/atmospherics/unary/vent_pump/exit_vent = pick(vents) - if(prob(50)) - visible_message("[src] scrambles into the ventilation ducts!", \ - "You hear something squeezing through the ventilation ducts.") - - spawn(rand(20,60)) - loc = exit_vent - var/travel_time = round(get_dist(loc, exit_vent.loc) / 2) - spawn(travel_time) - - if(!exit_vent || exit_vent.welded) - loc = entry_vent - entry_vent = null - return - - if(prob(50)) - audible_message("You hear something squeezing through the ventilation ducts.") - sleep(travel_time) - - if(!exit_vent || exit_vent.welded) - loc = entry_vent - entry_vent = null - return - loc = exit_vent.loc - entry_vent = null - var/area/new_area = get_area(loc) - if(new_area) - new_area.Entered(src) - //================= - - else if(prob(33)) - var/list/nearby = oview(10, src) - if(nearby.len) - var/target_atom = pick(nearby) - walk_to(src, target_atom) - if(prob(40)) - visible_message("[src] skitters[pick(" away"," around","")].") - else if(prob(10)) - //ventcrawl! - for(var/obj/machinery/atmospherics/unary/vent_pump/v in view(7,src)) - if(!v.welded) - entry_vent = v - walk_to(src, entry_vent, 1) - break - if(isturf(loc)) - amount_grown += rand(0,2) - if(amount_grown >= 100) - if(!grow_as) - grow_as = pick(typesof(/mob/living/simple_animal/hostile/poison/giant_spider)) - var/mob/living/simple_animal/hostile/poison/giant_spider/S = new grow_as(loc) - S.faction = faction.Copy() - S.master_commander = master_commander - if(player_spiders && !selecting_player) - selecting_player = 1 - spawn() - var/list/candidates = pollCandidates("Do you want to play as a spider?", ROLE_GSPIDER, 1) - - if(candidates.len) - var/mob/C = pick(candidates) - if(C) - S.key = C.key - if(S.master_commander) - to_chat(S, "You are a spider who is loyal to [S.master_commander], obey [S.master_commander]'s every order and assist [S.master_commander.p_them()] in completing [S.master_commander.p_their()] goals at any cost.") - qdel(src) - -/obj/effect/decal/cleanable/spiderling_remains - name = "spiderling remains" - desc = "Green squishy mess." - icon = 'icons/effects/effects.dmi' - icon_state = "greenshatter" - anchored = 1 - -/obj/structure/spider/cocoon - name = "cocoon" - desc = "Something wrapped in silky spider web" - icon_state = "cocoon1" - max_integrity = 60 - -/obj/structure/spider/cocoon/New() - ..() - icon_state = pick("cocoon1","cocoon2","cocoon3") - -/obj/structure/spider/cocoon/Destroy() - visible_message("[src] splits open.") - for(var/atom/movable/A in contents) - A.forceMove(loc) - return ..() +//generic procs copied from obj/effect/alien +/obj/structure/spider + name = "web" + desc = "it's stringy and sticky" + icon = 'icons/effects/effects.dmi' + anchored = TRUE + density = FALSE + max_integrity = 15 + var/mob/living/carbon/human/master_commander = null + +/obj/structure/spider/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + if(damage_type == BURN)//the stickiness of the web mutes all attack sounds except fire damage type + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + + +/obj/structure/spider/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == "melee") + switch(damage_type) + if(BURN) + damage_amount *= 2 + if(BRUTE) + damage_amount *= 0.25 + . = ..() + +/obj/structure/spider/Destroy() + master_commander = null + return ..() + +/obj/structure/spider/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > 300) + take_damage(5, BURN, 0, 0) + +/obj/structure/spider/stickyweb + icon_state = "stickyweb1" + +/obj/structure/spider/stickyweb/New() + ..() + if(prob(50)) + icon_state = "stickyweb2" + +/obj/structure/spider/stickyweb/CanPass(atom/movable/mover, turf/target, height=0) + if(height == 0) + return TRUE + if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider)) + return TRUE + else if(istype(mover, /mob/living)) + if(prob(50)) + to_chat(mover, "You get stuck in [src] for a moment.") + return FALSE + else if(istype(mover, /obj/item/projectile)) + return prob(30) + return TRUE + +/obj/structure/spider/eggcluster + name = "egg cluster" + desc = "They seem to pulse slightly with an inner life" + icon_state = "eggs" + var/amount_grown = 0 + var/player_spiders = 0 + var/list/faction = list() + +/obj/structure/spider/eggcluster/New() + ..() + pixel_x = rand(3,-3) + pixel_y = rand(3,-3) + START_PROCESSING(SSobj, src) + +/obj/structure/spider/eggcluster/process() + amount_grown += rand(0,2) + if(amount_grown >= 100) + var/num = rand(3, 12) + for(var/i in 1 to num) + var/obj/structure/spider/spiderling/S = new /obj/structure/spider/spiderling(loc) + S.faction = faction.Copy() + S.master_commander = master_commander + if(player_spiders) + S.player_spiders = 1 + qdel(src) + +/obj/structure/spider/spiderling + name = "spiderling" + desc = "It never stays still for long." + icon_state = "spiderling" + anchored = 0 + layer = 2.75 + max_integrity = 3 + var/amount_grown = 0 + var/grow_as = null + var/obj/machinery/atmospherics/unary/vent_pump/entry_vent + var/travelling_in_vent = 0 + var/player_spiders = 0 + var/list/faction = list() + var/selecting_player = 0 + +/obj/structure/spider/spiderling/New() + ..() + pixel_x = rand(6,-6) + pixel_y = rand(6,-6) + START_PROCESSING(SSobj, src) + +/obj/structure/spider/spiderling/Destroy() + STOP_PROCESSING(SSobj, src) + entry_vent = null + new /obj/effect/decal/cleanable/spiderling_remains(get_turf(src)) + return ..() + +/obj/structure/spider/spiderling/Bump(atom/user) + if(istype(user, /obj/structure/table)) + loc = user.loc + else + ..() + +/obj/structure/spider/spiderling/process() + if(travelling_in_vent) + if(istype(loc, /turf)) + travelling_in_vent = 0 + entry_vent = null + else if(entry_vent) + if(get_dist(src, entry_vent) <= 1) + var/list/vents = list() + for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in entry_vent.parent.other_atmosmch) + vents.Add(temp_vent) + if(!vents.len) + entry_vent = null + return + var/obj/machinery/atmospherics/unary/vent_pump/exit_vent = pick(vents) + if(prob(50)) + visible_message("[src] scrambles into the ventilation ducts!", \ + "You hear something squeezing through the ventilation ducts.") + + spawn(rand(20,60)) + loc = exit_vent + var/travel_time = round(get_dist(loc, exit_vent.loc) / 2) + spawn(travel_time) + + if(!exit_vent || exit_vent.welded) + loc = entry_vent + entry_vent = null + return + + if(prob(50)) + audible_message("You hear something squeezing through the ventilation ducts.") + sleep(travel_time) + + if(!exit_vent || exit_vent.welded) + loc = entry_vent + entry_vent = null + return + loc = exit_vent.loc + entry_vent = null + var/area/new_area = get_area(loc) + if(new_area) + new_area.Entered(src) + //================= + + else if(prob(33)) + var/list/nearby = oview(10, src) + if(nearby.len) + var/target_atom = pick(nearby) + walk_to(src, target_atom) + if(prob(40)) + visible_message("[src] skitters[pick(" away"," around","")].") + else if(prob(10)) + //ventcrawl! + for(var/obj/machinery/atmospherics/unary/vent_pump/v in view(7,src)) + if(!v.welded) + entry_vent = v + walk_to(src, entry_vent, 1) + break + if(isturf(loc)) + amount_grown += rand(0,2) + if(amount_grown >= 100) + if(!grow_as) + grow_as = pick(typesof(/mob/living/simple_animal/hostile/poison/giant_spider)) + var/mob/living/simple_animal/hostile/poison/giant_spider/S = new grow_as(loc) + S.faction = faction.Copy() + S.master_commander = master_commander + if(player_spiders && !selecting_player) + selecting_player = 1 + spawn() + var/list/candidates = pollCandidates("Do you want to play as a spider?", ROLE_GSPIDER, 1) + + if(candidates.len) + var/mob/C = pick(candidates) + if(C) + S.key = C.key + if(S.master_commander) + to_chat(S, "You are a spider who is loyal to [S.master_commander], obey [S.master_commander]'s every order and assist [S.master_commander.p_them()] in completing [S.master_commander.p_their()] goals at any cost.") + qdel(src) + +/obj/effect/decal/cleanable/spiderling_remains + name = "spiderling remains" + desc = "Green squishy mess." + icon = 'icons/effects/effects.dmi' + icon_state = "greenshatter" + anchored = 1 + +/obj/structure/spider/cocoon + name = "cocoon" + desc = "Something wrapped in silky spider web" + icon_state = "cocoon1" + max_integrity = 60 + +/obj/structure/spider/cocoon/New() + ..() + icon_state = pick("cocoon1","cocoon2","cocoon3") + +/obj/structure/spider/cocoon/Destroy() + visible_message("[src] splits open.") + for(var/atom/movable/A in contents) + A.forceMove(loc) + return ..() diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm index c1231e698fc..0a47e57112e 100644 --- a/code/game/objects/effects/step_triggers.dm +++ b/code/game/objects/effects/step_triggers.dm @@ -195,4 +195,4 @@ playsound(T, sound, volume, freq_vary, extra_range) if(happens_once) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/effects/temporary_visuals/cult.dm b/code/game/objects/effects/temporary_visuals/cult.dm index 7a12caf6b10..775b4eafceb 100644 --- a/code/game/objects/effects/temporary_visuals/cult.dm +++ b/code/game/objects/effects/temporary_visuals/cult.dm @@ -38,4 +38,4 @@ /obj/effect/temp_visual/cult/turf/open/floor icon_state = "floorglow" duration = 5 - plane = FLOOR_PLANE \ No newline at end of file + plane = FLOOR_PLANE diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 5c3580ae177..e2f137cafa4 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -353,4 +353,4 @@ /obj/effect/temp_visual/impact_effect/ion icon_state = "shieldsparkles" - duration = 6 \ No newline at end of file + duration = 6 diff --git a/code/game/objects/empulse.dm b/code/game/objects/empulse.dm index cec5b6e1fc8..3e7f476e346 100644 --- a/code/game/objects/empulse.dm +++ b/code/game/objects/empulse.dm @@ -1,32 +1,32 @@ -/proc/empulse(turf/epicenter, heavy_range, light_range, log=0, cause = null) - if(!epicenter) return - - if(!istype(epicenter, /turf)) - epicenter = get_turf(epicenter.loc) - - if(log) - message_admins("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])": ""] [ADMIN_COORDJMP(epicenter)]") - log_game("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])" : ""] [COORD(epicenter)]") - - if(heavy_range > 1) - new/obj/effect/temp_visual/emp/pulse(epicenter) - - if(heavy_range > light_range) - light_range = heavy_range - - for(var/mob/M in range(heavy_range, epicenter)) - M << 'sound/effects/empulse.ogg' - for(var/atom/T in range(light_range, epicenter)) - var/distance = get_dist(epicenter, T) - if(distance < 0) - distance = 0 - if(distance < heavy_range) - T.emp_act(1) - else if(distance == heavy_range) - if(prob(50)) - T.emp_act(1) - else - T.emp_act(2) - else if(distance <= light_range) - T.emp_act(2) - return 1 +/proc/empulse(turf/epicenter, heavy_range, light_range, log=0, cause = null) + if(!epicenter) return + + if(!istype(epicenter, /turf)) + epicenter = get_turf(epicenter.loc) + + if(log) + message_admins("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])": ""] [ADMIN_COORDJMP(epicenter)]") + log_game("EMP with size ([heavy_range], [light_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])" : ""] [COORD(epicenter)]") + + if(heavy_range > 1) + new/obj/effect/temp_visual/emp/pulse(epicenter) + + if(heavy_range > light_range) + light_range = heavy_range + + for(var/mob/M in range(heavy_range, epicenter)) + M << 'sound/effects/empulse.ogg' + for(var/atom/T in range(light_range, epicenter)) + var/distance = get_dist(epicenter, T) + if(distance < 0) + distance = 0 + if(distance < heavy_range) + T.emp_act(1) + else if(distance == heavy_range) + if(prob(50)) + T.emp_act(1) + else + T.emp_act(2) + else if(distance <= light_range) + T.emp_act(2) + return 1 diff --git a/code/game/objects/explosion.dm b/code/game/objects/explosion.dm index 462a6d7eca7..7e17b58a3a4 100644 --- a/code/game/objects/explosion.dm +++ b/code/game/objects/explosion.dm @@ -1,241 +1,241 @@ -//TODO: Flash range does nothing currently - -/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = 1, ignorecap = 0, flame_range = 0, silent = 0, smoke = 1, cause = null, breach = TRUE) - src = null //so we don't abort once src is deleted - epicenter = get_turf(epicenter) - - // Archive the uncapped explosion for the doppler array - var/orig_dev_range = devastation_range - var/orig_heavy_range = heavy_impact_range - var/orig_light_range = light_impact_range - - if(!ignorecap) - // Clamp all values to MAX_EXPLOSION_RANGE - devastation_range = min (MAX_EX_DEVASTATION_RANGE, devastation_range) - heavy_impact_range = min (MAX_EX_HEAVY_RANGE, heavy_impact_range) - light_impact_range = min (MAX_EX_LIGHT_RANGE, light_impact_range) - flash_range = min (MAX_EX_FLASH_RANGE, flash_range) - flame_range = min (MAX_EX_FLAME_RANGE, flame_range) - - spawn(0) - var/watch = start_watch() - if(!epicenter) return - - var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range) - var/list/cached_exp_block = list() - - if(adminlog) - message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])" : ""] [ADMIN_COORDJMP(epicenter)] ") - log_game("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])" : ""] [COORD(epicenter)] ") - - // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves. - // Stereo users will also hear the direction of the explosion! - - // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions. - // 3/7/14 will calculate to 80 + 35 - - var/far_dist = 0 - far_dist += heavy_impact_range * 5 - far_dist += devastation_range * 20 - - if(!silent) - var/frequency = get_rand_frequency() - var/sound/explosion_sound = sound(get_sfx("explosion")) - var/sound/global_boom = sound('sound/effects/explosionfar.ogg') - - for(var/P in GLOB.player_list) - var/mob/M = P - // Double check for client - if(M && M.client) - var/turf/M_turf = get_turf(M) - if(M_turf && M_turf.z == epicenter.z) - var/dist = get_dist(M_turf, epicenter) - // If inside the blast radius + world.view - 2 - if(dist <= round(max_range + world.view - 2, 1)) - M.playsound_local(epicenter, null, 100, 1, frequency, falloff = 5, S = explosion_sound) - // You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station. - else if(M.can_hear() && !isspaceturf(M.loc)) - M << global_boom - - if(heavy_impact_range > 1) - if(smoke) - var/datum/effect_system/explosion/smoke/E = new/datum/effect_system/explosion/smoke() - E.set_up(epicenter) - E.start() - else - var/datum/effect_system/explosion/E = new/datum/effect_system/explosion() - E.set_up(epicenter) - E.start() - - var/x0 = epicenter.x - var/y0 = epicenter.y - var/z0 = epicenter.z - - var/list/affected_turfs = spiral_range_turfs(max_range, epicenter) - - if(config.reactionary_explosions) - for(var/A in affected_turfs) // we cache the explosion block rating of every turf in the explosion area - var/turf/T = A - cached_exp_block[T] = 0 - if(T.density && T.explosion_block) - cached_exp_block[T] += T.explosion_block - - for(var/obj/O in T) - var/the_block = O.explosion_block - cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block - CHECK_TICK - - for(var/A in affected_turfs) - var/turf/T = A - if(!T) - continue - var/dist = hypotenuse(T.x, T.y, x0, y0) - - if(config.reactionary_explosions) - var/turf/Trajectory = T - while(Trajectory != epicenter) - Trajectory = get_step_towards(Trajectory, epicenter) - dist += cached_exp_block[Trajectory] - - var/flame_dist = 0 -// var/throw_dist = max_range - dist - - if(dist < flame_range) - flame_dist = 1 - - if(dist < devastation_range) dist = 1 - else if(dist < heavy_impact_range) dist = 2 - else if(dist < light_impact_range) dist = 3 - else dist = 0 - - //------- TURF FIRES ------- - - if(T) - if(flame_dist && prob(40) && !istype(T, /turf/space) && !T.density) - new /obj/effect/hotspot(T) //Mostly for ambience! - if(dist > 0) - if(istype(T, /turf/simulated)) - var/turf/simulated/S = T - var/affecting_level - if(dist == 1) - affecting_level = 1 - else - affecting_level = S.is_shielded() ? 2 : (S.intact ? 2 : 1) - for(var/atom in S.contents) //bypass type checking since only atom can be contained by turfs anyway - var/atom/AM = atom - if(AM && AM.simulated) - if(AM.level >= affecting_level) - AM.ex_act(dist) - else - for(var/atom in T.contents) //see above - var/atom/AM = atom - if(AM && AM.simulated) - AM.ex_act(dist) - CHECK_TICK - if(breach) - T.ex_act(dist) - else - T.ex_act(3) - - CHECK_TICK - //--- THROW ITEMS AROUND --- -/* - if(throw_dist > 0) - var/throw_dir = get_dir(epicenter,T) - for(var/obj/item/I in T) - spawn(0) //Simultaneously not one at a time - if(I && !I.anchored) - var/throw_mult = 0.5 + (0.5 * rand()) // Between 0.5 and 1.0 - var/throw_range = round((throw_dist + 1) * throw_mult) // Roughly 50% to 100% of throw_dist - if(throw_range > 0) - var/turf/throw_at = get_ranged_target_turf(I, throw_dir, throw_range) - I.throw_at(throw_at, throw_range, 2, no_spin = 1) //Throw it at 2 speed, this is purely visual anyway; don't spin the thrown items, it's very costly. -*/ - var/took = stop_watch(watch) - //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare - if(Debug2) - log_world("## DEBUG: Explosion([x0],[y0],[z0])(d[devastation_range],h[heavy_impact_range],l[light_impact_range]): Took [took] seconds.") - - //Machines which report explosions. - for(var/i,i<=doppler_arrays.len,i++) - var/obj/machinery/doppler_array/Array = doppler_arrays[i] - if(Array) - Array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) - - return 1 - - - -/proc/secondaryexplosion(turf/epicenter, range) - for(var/turf/tile in spiral_range_turfs(range, epicenter)) - tile.ex_act(2) - -/client/proc/check_bomb_impacts() - set name = "Check Bomb Impact" - set category = "Debug" - - var/newmode = alert("Use reactionary explosions?","Check Bomb Impact", "Yes", "No") - var/turf/epicenter = get_turf(mob) - if(!epicenter) - return - - var/dev = 0 - var/heavy = 0 - var/light = 0 - var/list/choices = list("Small Bomb","Medium Bomb","Big Bomb","Custom Bomb") - var/choice = input("Bomb Size?") in choices - switch(choice) - if(null) - return 0 - if("Small Bomb") - dev = 1 - heavy = 2 - light = 3 - if("Medium Bomb") - dev = 2 - heavy = 3 - light = 4 - if("Big Bomb") - dev = 3 - heavy = 5 - light = 7 - if("Custom Bomb") - dev = input("Devestation range (Tiles):") as num - heavy = input("Heavy impact range (Tiles):") as num - light = input("Light impact range (Tiles):") as num - - var/max_range = max(dev, heavy, light) - var/x0 = epicenter.x - var/y0 = epicenter.y - var/list/wipe_colours = list() - for(var/turf/T in spiral_range_turfs(max_range, epicenter)) - wipe_colours += T - var/dist = hypotenuse(T.x, T.y, x0, y0) - - if(newmode == "Yes") - var/turf/TT = T - while(TT != epicenter) - TT = get_step_towards(TT,epicenter) - if(TT.density) - dist += TT.explosion_block - - for(var/obj/O in T) - var/the_block = O.explosion_block - dist += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block - - if(dist < dev) - T.color = "red" - T.maptext = "Dev" - else if(dist < heavy) - T.color = "yellow" - T.maptext = "Heavy" - else if(dist < light) - T.color = "blue" - T.maptext = "Light" - else - continue - - sleep(100) - for(var/turf/T in wipe_colours) - T.color = null - T.maptext = "" +//TODO: Flash range does nothing currently + +/proc/explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, adminlog = 1, ignorecap = 0, flame_range = 0, silent = 0, smoke = 1, cause = null, breach = TRUE) + src = null //so we don't abort once src is deleted + epicenter = get_turf(epicenter) + + // Archive the uncapped explosion for the doppler array + var/orig_dev_range = devastation_range + var/orig_heavy_range = heavy_impact_range + var/orig_light_range = light_impact_range + + if(!ignorecap) + // Clamp all values to MAX_EXPLOSION_RANGE + devastation_range = min (MAX_EX_DEVASTATION_RANGE, devastation_range) + heavy_impact_range = min (MAX_EX_HEAVY_RANGE, heavy_impact_range) + light_impact_range = min (MAX_EX_LIGHT_RANGE, light_impact_range) + flash_range = min (MAX_EX_FLASH_RANGE, flash_range) + flame_range = min (MAX_EX_FLAME_RANGE, flame_range) + + spawn(0) + var/watch = start_watch() + if(!epicenter) return + + var/max_range = max(devastation_range, heavy_impact_range, light_impact_range, flame_range) + var/list/cached_exp_block = list() + + if(adminlog) + message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])" : ""] [ADMIN_COORDJMP(epicenter)] ") + log_game("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in area [epicenter.loc.name] [cause ? "(Cause: [cause])" : ""] [COORD(epicenter)] ") + + // Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves. + // Stereo users will also hear the direction of the explosion! + + // Calculate far explosion sound range. Only allow the sound effect for heavy/devastating explosions. + // 3/7/14 will calculate to 80 + 35 + + var/far_dist = 0 + far_dist += heavy_impact_range * 5 + far_dist += devastation_range * 20 + + if(!silent) + var/frequency = get_rand_frequency() + var/sound/explosion_sound = sound(get_sfx("explosion")) + var/sound/global_boom = sound('sound/effects/explosionfar.ogg') + + for(var/P in GLOB.player_list) + var/mob/M = P + // Double check for client + if(M && M.client) + var/turf/M_turf = get_turf(M) + if(M_turf && M_turf.z == epicenter.z) + var/dist = get_dist(M_turf, epicenter) + // If inside the blast radius + world.view - 2 + if(dist <= round(max_range + world.view - 2, 1)) + M.playsound_local(epicenter, null, 100, 1, frequency, falloff = 5, S = explosion_sound) + // You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station. + else if(M.can_hear() && !isspaceturf(M.loc)) + M << global_boom + + if(heavy_impact_range > 1) + if(smoke) + var/datum/effect_system/explosion/smoke/E = new/datum/effect_system/explosion/smoke() + E.set_up(epicenter) + E.start() + else + var/datum/effect_system/explosion/E = new/datum/effect_system/explosion() + E.set_up(epicenter) + E.start() + + var/x0 = epicenter.x + var/y0 = epicenter.y + var/z0 = epicenter.z + + var/list/affected_turfs = spiral_range_turfs(max_range, epicenter) + + if(config.reactionary_explosions) + for(var/A in affected_turfs) // we cache the explosion block rating of every turf in the explosion area + var/turf/T = A + cached_exp_block[T] = 0 + if(T.density && T.explosion_block) + cached_exp_block[T] += T.explosion_block + + for(var/obj/O in T) + var/the_block = O.explosion_block + cached_exp_block[T] += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block + CHECK_TICK + + for(var/A in affected_turfs) + var/turf/T = A + if(!T) + continue + var/dist = hypotenuse(T.x, T.y, x0, y0) + + if(config.reactionary_explosions) + var/turf/Trajectory = T + while(Trajectory != epicenter) + Trajectory = get_step_towards(Trajectory, epicenter) + dist += cached_exp_block[Trajectory] + + var/flame_dist = 0 +// var/throw_dist = max_range - dist + + if(dist < flame_range) + flame_dist = 1 + + if(dist < devastation_range) dist = 1 + else if(dist < heavy_impact_range) dist = 2 + else if(dist < light_impact_range) dist = 3 + else dist = 0 + + //------- TURF FIRES ------- + + if(T) + if(flame_dist && prob(40) && !istype(T, /turf/space) && !T.density) + new /obj/effect/hotspot(T) //Mostly for ambience! + if(dist > 0) + if(istype(T, /turf/simulated)) + var/turf/simulated/S = T + var/affecting_level + if(dist == 1) + affecting_level = 1 + else + affecting_level = S.is_shielded() ? 2 : (S.intact ? 2 : 1) + for(var/atom in S.contents) //bypass type checking since only atom can be contained by turfs anyway + var/atom/AM = atom + if(AM && AM.simulated) + if(AM.level >= affecting_level) + AM.ex_act(dist) + else + for(var/atom in T.contents) //see above + var/atom/AM = atom + if(AM && AM.simulated) + AM.ex_act(dist) + CHECK_TICK + if(breach) + T.ex_act(dist) + else + T.ex_act(3) + + CHECK_TICK + //--- THROW ITEMS AROUND --- +/* + if(throw_dist > 0) + var/throw_dir = get_dir(epicenter,T) + for(var/obj/item/I in T) + spawn(0) //Simultaneously not one at a time + if(I && !I.anchored) + var/throw_mult = 0.5 + (0.5 * rand()) // Between 0.5 and 1.0 + var/throw_range = round((throw_dist + 1) * throw_mult) // Roughly 50% to 100% of throw_dist + if(throw_range > 0) + var/turf/throw_at = get_ranged_target_turf(I, throw_dir, throw_range) + I.throw_at(throw_at, throw_range, 2, no_spin = 1) //Throw it at 2 speed, this is purely visual anyway; don't spin the thrown items, it's very costly. +*/ + var/took = stop_watch(watch) + //You need to press the DebugGame verb to see these now....they were getting annoying and we've collected a fair bit of data. Just -test- changes to explosion code using this please so we can compare + if(Debug2) + log_world("## DEBUG: Explosion([x0],[y0],[z0])(d[devastation_range],h[heavy_impact_range],l[light_impact_range]): Took [took] seconds.") + + //Machines which report explosions. + for(var/i,i<=doppler_arrays.len,i++) + var/obj/machinery/doppler_array/Array = doppler_arrays[i] + if(Array) + Array.sense_explosion(x0,y0,z0,devastation_range,heavy_impact_range,light_impact_range,took,orig_dev_range,orig_heavy_range,orig_light_range) + + return 1 + + + +/proc/secondaryexplosion(turf/epicenter, range) + for(var/turf/tile in spiral_range_turfs(range, epicenter)) + tile.ex_act(2) + +/client/proc/check_bomb_impacts() + set name = "Check Bomb Impact" + set category = "Debug" + + var/newmode = alert("Use reactionary explosions?","Check Bomb Impact", "Yes", "No") + var/turf/epicenter = get_turf(mob) + if(!epicenter) + return + + var/dev = 0 + var/heavy = 0 + var/light = 0 + var/list/choices = list("Small Bomb","Medium Bomb","Big Bomb","Custom Bomb") + var/choice = input("Bomb Size?") in choices + switch(choice) + if(null) + return 0 + if("Small Bomb") + dev = 1 + heavy = 2 + light = 3 + if("Medium Bomb") + dev = 2 + heavy = 3 + light = 4 + if("Big Bomb") + dev = 3 + heavy = 5 + light = 7 + if("Custom Bomb") + dev = input("Devestation range (Tiles):") as num + heavy = input("Heavy impact range (Tiles):") as num + light = input("Light impact range (Tiles):") as num + + var/max_range = max(dev, heavy, light) + var/x0 = epicenter.x + var/y0 = epicenter.y + var/list/wipe_colours = list() + for(var/turf/T in spiral_range_turfs(max_range, epicenter)) + wipe_colours += T + var/dist = hypotenuse(T.x, T.y, x0, y0) + + if(newmode == "Yes") + var/turf/TT = T + while(TT != epicenter) + TT = get_step_towards(TT,epicenter) + if(TT.density) + dist += TT.explosion_block + + for(var/obj/O in T) + var/the_block = O.explosion_block + dist += the_block == EXPLOSION_BLOCK_PROC ? O.GetExplosionBlock() : the_block + + if(dist < dev) + T.color = "red" + T.maptext = "Dev" + else if(dist < heavy) + T.color = "yellow" + T.maptext = "Heavy" + else if(dist < light) + T.color = "blue" + T.maptext = "Light" + else + continue + + sleep(100) + for(var/turf/T in wipe_colours) + T.color = null + T.maptext = "" diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 955998fc0f6..200034b4b46 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1,681 +1,681 @@ -var/global/image/fire_overlay = image("icon" = 'icons/goonstation/effects/fire.dmi', "icon_state" = "fire") - -/obj/item - name = "item" - icon = 'icons/obj/items.dmi' - var/discrete = 0 // used in item_attack.dm to make an item not show an attack message to viewers - var/image/blood_overlay = null //this saves our blood splatter overlay, which will be processed not to go over the edges of the sprite - var/blood_overlay_color = null - var/item_state = null - var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' - - //Dimensions of the lefthand_file and righthand_file vars - //eg: 32x32 sprite, 64x64 sprite, etc. - var/inhand_x_dimension = 32 - var/inhand_y_dimension = 32 - - max_integrity = 200 - - can_be_hit = FALSE - suicidal_hands = TRUE - - var/hitsound = null - var/usesound = null - var/throwhitsound - var/w_class = WEIGHT_CLASS_NORMAL - var/slot_flags = 0 //This is used to determine on which slots an item can fit. - pass_flags = PASSTABLE - pressure_resistance = 4 -// causeerrorheresoifixthis - var/obj/item/master = null - - var/heat_protection = 0 //flags which determine which body parts are protected from heat. Use the HEAD, UPPER_TORSO, LOWER_TORSO, etc. flags. See setup.dm - var/cold_protection = 0 //flags which determine which body parts are protected from cold. Use the HEAD, UPPER_TORSO, LOWER_TORSO, etc. flags. See setup.dm - var/max_heat_protection_temperature //Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags - var/min_cold_protection_temperature //Set this variable to determine down to which temperature (IN KELVIN) the item protects against cold damage. 0 is NOT an acceptable number due to if(varname) tests!! Keep at null to disable protection. Only protects areas set by cold_protection flags - - var/list/actions = list() //list of /datum/action's that this item has. - var/list/actions_types = list() //list of paths of action datums to give to the item on New(). - var/list/action_icon = list() //list of icons-sheets for a given action to override the icon. - var/list/action_icon_state = list() //list of icon states for a given action to override the icon_state. - - var/list/materials = list() - //Since any item can now be a piece of clothing, this has to be put here so all items share it. - var/flags_inv //This flag is used to determine when items in someone's inventory cover others. IE helmets making it so you can't see glasses, etc. - var/item_color = null - var/body_parts_covered = 0 //see setup.dm for appropriate bit flags - //var/heat_transfer_coefficient = 1 //0 prevents all transfers, 1 is invisible - var/gas_transfer_coefficient = 1 // for leaking gas from turf to mask and vice-versa (for masks right now, but at some point, i'd like to include space helmets) - var/permeability_coefficient = 1 // for chemicals/diseases - var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit) - var/slowdown = 0 // How much clothing is slowing you down. Negative values speeds you up - var/armour_penetration = 0 //percentage of armour effectiveness to remove - var/list/allowed = null //suit storage stuff. - var/obj/item/uplink/hidden/hidden_uplink = null // All items can have an uplink hidden inside, just remember to add the triggers. - - var/needs_permit = 0 //Used by security bots to determine if this item is safe for public use. - - var/strip_delay = DEFAULT_ITEM_STRIP_DELAY - var/put_on_delay = DEFAULT_ITEM_PUTON_DELAY - var/breakouttime = 0 - var/flags_cover = 0 //for flags such as GLASSESCOVERSEYES - var/flags_size = 0 //flag, primarily used for clothing to determine if a fatty can wear something or not. - - var/block_chance = 0 - var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom - - // Needs to be in /obj/item because corgis can wear a lot of - // non-clothing items - var/datum/dog_fashion/dog_fashion = null - - var/mob/thrownby = null - - //So items can have custom embedd values - //Because customisation is king - var/embed_chance = EMBED_CHANCE - var/embedded_fall_chance = EMBEDDED_ITEM_FALLOUT - var/embedded_pain_chance = EMBEDDED_PAIN_CHANCE - var/embedded_pain_multiplier = EMBEDDED_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does while embedded (this*w_class) - var/embedded_fall_pain_multiplier = EMBEDDED_FALL_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does when falling out of a limb (this*w_class) - var/embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does when first embedded (this*w_class) - var/embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER //The coefficient of multiplication for the damage removing this without surgery causes (this*w_class) - var/embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME //A time in ticks, multiplied by the w_class. - var/embedded_ignore_throwspeed_threshold = FALSE - - var/tool_behaviour = NONE //What kind of tool are we? - var/tool_enabled = TRUE //If we can turn on or off, are we currently active? Mostly for welders and this will normally be TRUE - var/tool_volume = 50 //How loud are we when we use our tool? - var/toolspeed = 1 // If this item is a tool, the speed multiplier - - /* Species-specific sprites, concept stolen from Paradise//vg/. - ex: - sprite_sheets = list( - "Tajaran" = 'icons/cat/are/bad' - ) - If index term exists and icon_override is not set, this sprite sheet will be used. - */ - var/list/sprite_sheets = null - var/list/sprite_sheets_inhand = null //Used to override inhand items. Use a single .dmi and suffix the icon states inside with _l and _r for each hand. - var/icon_override = null //Used to override hardcoded clothing dmis in human clothing proc. - var/sprite_sheets_obj = null //Used to override hardcoded clothing inventory object dmis in human clothing proc. - - var/trip_verb = TV_TRIP - var/trip_chance = 0 - - var/trip_stun = 0 - var/trip_weaken = 0 - var/trip_any = FALSE - var/trip_walksafe = TRUE - var/trip_tiles = 0 - - //Tooltip vars - var/in_inventory = FALSE //is this item equipped into an inventory slot or hand of a mob? - var/tip_timer = 0 - -/obj/item/New() - ..() - for(var/path in actions_types) - new path(src, action_icon[path], action_icon_state[path]) - - if(!hitsound) - if(damtype == "fire") - hitsound = 'sound/items/welder.ogg' - if(damtype == "brute") - hitsound = "swing_hit" - -/obj/item/Destroy() - flags &= ~DROPDEL //prevent reqdels - QDEL_NULL(hidden_uplink) - if(ismob(loc)) - var/mob/m = loc - m.unEquip(src, 1) - QDEL_LIST(actions) - master = null - return ..() - -/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self) - if(((src in target) && !target_self) || ((!istype(target.loc, /turf)) && (!istype(target, /turf)) && (not_inside))) - return 0 - else - return 1 - -/obj/item/blob_act(obj/structure/blob/B) - if(B && B.loc == loc) - qdel(src) - -/obj/item/verb/move_to_top() - set name = "Move To Top" - set category = null - set src in oview(1) - - if(!istype(src.loc, /turf) || usr.stat || usr.restrained() ) - return - - var/turf/T = src.loc - - src.loc = null - - src.loc = T - -/obj/item/examine(mob/user) - var/size - switch(src.w_class) - if(WEIGHT_CLASS_TINY) - size = "tiny" - if(WEIGHT_CLASS_SMALL) - size = "small" - if(WEIGHT_CLASS_NORMAL) - size = "normal-sized" - if(WEIGHT_CLASS_BULKY) - size = "bulky" - if(WEIGHT_CLASS_HUGE) - size = "huge" - if(WEIGHT_CLASS_GIGANTIC) - size = "gigantic" - - . = ..(user, "", "It is a [size] item.") - - if(user.research_scanner) //Mob has a research scanner active. - var/msg = "*--------*
        " - - if(origin_tech) - msg += "Testing potentials:
        " - var/list/techlvls = params2list(origin_tech) - for(var/T in techlvls) //This needs to use the better names. - msg += "Tech: [CallTechName(T)] | Magnitude: [techlvls[T]]
        " - else - msg += "No tech origins detected.
        " - - - if(materials.len) - msg += "Extractable materials:
        " - for(var/mat in materials) - msg += "[CallMaterialName(mat)]
        " //Capitize first word, remove the "$" - else - msg += "No extractable materials detected.
        " - msg += "*--------*" - . += msg - -/obj/item/burn() - if(!QDELETED(src)) - var/turf/T = get_turf(src) - var/obj/effect/decal/cleanable/ash/A = new(T) - A.desc += "\nLooks like this used to be \an [name] some time ago." - ..() - -/obj/item/acid_melt() - if(!QDELETED(src)) - var/turf/T = get_turf(src) - var/obj/effect/decal/cleanable/molten_object/MO = new(T) - MO.pixel_x = rand(-16,16) - MO.pixel_y = rand(-16,16) - MO.desc = "Looks like this was \an [src] some time ago." - ..() - -/obj/item/afterattack(atom/target, mob/user, proximity, params) - SEND_SIGNAL(src, COMSIG_ITEM_AFTERATTACK, target, user, proximity, params) - ..() - -/obj/item/attack_hand(mob/user as mob, pickupfireoverride = FALSE) - if(!user) return 0 - if(hasorgans(user)) - var/mob/living/carbon/human/H = user - var/obj/item/organ/external/temp = H.bodyparts_by_name["r_hand"] - if(user.hand) - temp = H.bodyparts_by_name["l_hand"] - if(!temp) - to_chat(user, "You try to use your hand, but it's missing!") - return 0 - if(temp && !temp.is_usable()) - to_chat(user, "You try to move your [temp.name], but cannot!") - return 0 - - if((resistance_flags & ON_FIRE) && !pickupfireoverride) - var/mob/living/carbon/human/H = user - if(istype(H)) - if(H.gloves && (H.gloves.max_heat_protection_temperature > 360)) - extinguish() - to_chat(user, "You put out the fire on [src].") - else - to_chat(user, "You burn your hand on [src]!") - var/obj/item/organ/external/affecting = H.get_organ("[user.hand ? "l" : "r" ]_arm") - if(affecting && affecting.receive_damage(0, 5)) // 5 burn damage - H.UpdateDamageIcon() - return - else - extinguish() - - if(acid_level > 20 && !ismob(loc))// so we can still remove the clothes on us that have acid. - var/mob/living/carbon/human/H = user - if(istype(H)) - if(!H.gloves || (!(H.gloves.resistance_flags & (UNACIDABLE|ACID_PROOF)))) - to_chat(user, "The acid on [src] burns your hand!") - var/obj/item/organ/external/affecting = H.get_organ("[user.hand ? "l" : "r" ]_arm") - if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage - H.UpdateDamageIcon() - - if(istype(src.loc, /obj/item/storage)) - //If the item is in a storage item, take it out - var/obj/item/storage/S = src.loc - S.remove_from_storage(src) - - if(throwing) - throwing.finalize(FALSE) - if(loc == user) - if(!user.unEquip(src)) - return 0 - - else - if(isliving(loc)) - return 0 - add_fingerprint(user) - if(pickup(user)) // Pickup succeeded - user.put_in_active_hand(src) - - return 1 - -/obj/item/attack_alien(mob/user) - var/mob/living/carbon/alien/A = user - - if(!A.has_fine_manipulation) - if(src in A.contents) // To stop Aliens having items stuck in their pockets - A.unEquip(src) - to_chat(user, "Your claws aren't capable of such fine manipulation!") - return - attack_hand(A) - -/obj/item/attack_ai(mob/user as mob) - if(istype(src.loc, /obj/item/robot_module)) - //If the item is part of a cyborg module, equip it - if(!isrobot(user)) - return - var/mob/living/silicon/robot/R = user - if(!R.low_power_mode) //can't equip modules with an empty cell. - R.activate_module(src) - R.hud_used.update_robot_modules_display() - -// Due to storage type consolidation this should get used more now. -// I have cleaned it up a little, but it could probably use more. -Sayu -/obj/item/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/storage)) - var/obj/item/storage/S = I - if(S.use_to_pickup) - if(S.collection_mode) //Mode is set to collect all items on a tile and we clicked on a valid one. - if(isturf(loc)) - var/list/rejections = list() - var/success = 0 - var/failure = 0 - - for(var/obj/item/IT in loc) - if(IT.type in rejections) // To limit bag spamming: any given type only complains once - continue - if(!S.can_be_inserted(IT)) // Note can_be_inserted still makes noise when the answer is no - rejections += IT.type // therefore full bags are still a little spammy - failure = 1 - continue - success = 1 - S.handle_item_insertion(IT, 1) //The 1 stops the "You put the [src] into [S]" insertion message from being displayed. - if(success && !failure) - to_chat(user, "You put everything in [S].") - else if(success) - to_chat(user, "You put some things in [S].") - else - to_chat(user, "You fail to pick anything up with [S].") - - else if(S.can_be_inserted(src)) - S.handle_item_insertion(src) - else if(istype(I, /obj/item/stack/tape_roll)) - if(istype(src, /obj/item/storage)) //Don't tape the bag if we can put the duct tape inside it instead - var/obj/item/storage/bag = src - if(bag.can_be_inserted(I)) - return ..() - var/obj/item/stack/tape_roll/TR = I - var/list/clickparams = params2list(params) - var/x_offset = text2num(clickparams["icon-x"]) - var/y_offset = text2num(clickparams["icon-y"]) - if(GetComponent(/datum/component/ducttape)) - to_chat(user, "[src] already has some tape attached!") - return - if(TR.use(1)) - to_chat(user, "You apply some tape to [src].") - AddComponent(/datum/component/ducttape, src, user, x_offset, y_offset) - anchored = TRUE - user.transfer_fingerprints_to(src) - else - to_chat(user, "You don't have enough tape to do that!") - else - return ..() - -/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) - if(prob(final_block_chance)) - owner.visible_message("[owner] blocks [attack_text] with [src]!") - return 1 - return 0 - -// Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc. -// Returns TRUE on success, FALSE on failure. -/obj/item/proc/use(used) - return !used - -//Generic refill proc. Transfers something (e.g. fuel, charge) from an atom to our tool. returns TRUE if it was successful, FALSE otherwise -//Not sure if there should be an argument that indicates what exactly is being refilled -/obj/item/proc/refill(mob/user, atom/A, amount) - return FALSE - -/obj/item/proc/talk_into(mob/M, var/text, var/channel=null) - return - -/obj/item/proc/dropped(mob/user) - for(var/X in actions) - var/datum/action/A = X - A.Remove(user) - if(flags & DROPDEL) - qdel(src) - if((flags & NODROP) && !(initial(flags) & NODROP)) //Remove NODROP is dropped - flags &= ~NODROP - in_inventory = FALSE - SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user) - -// called just as an item is picked up (loc is not yet changed) -/obj/item/proc/pickup(mob/user) - SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) - in_inventory = TRUE - return TRUE - -// called when this item is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called. -/obj/item/proc/on_exit_storage(obj/item/storage/S as obj) - return - -// called when this item is added into a storage item, which is passed on as S. The loc variable is already set to the storage item. -/obj/item/proc/on_enter_storage(obj/item/storage/S as obj) - return - -// called when "found" in pockets and storage items. Returns 1 if the search should end. -/obj/item/proc/on_found(mob/finder as mob) - return - -// called when the giver gives it to the receiver -/obj/item/proc/on_give(mob/living/carbon/giver, mob/living/carbon/receiver) - return - -// called after an item is placed in an equipment slot -// user is mob that equipped it -// slot uses the slot_X defines found in setup.dm -// for items that can be placed in multiple slots -// note this isn't called during the initial dressing of a player -/obj/item/proc/equipped(var/mob/user, var/slot) - SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) - for(var/X in actions) - var/datum/action/A = X - if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot. - A.Grant(user) - in_inventory = TRUE - -/obj/item/proc/item_action_slot_check(slot, mob/user) - return 1 - -//returns 1 if the item is equipped by a mob, 0 otherwise. -//This might need some error trapping, not sure if get_equipped_items() is safe for non-human mobs. -/obj/item/proc/is_equipped() - if(!ismob(loc)) - return 0 - - var/mob/M = loc - if(src in M.get_equipped_items()) - return 1 - else - return 0 - -//the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't. -//If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen. -//Set disable_warning to 1 if you wish it to not give you outputs. -/obj/item/proc/mob_can_equip(mob/M, slot, disable_warning = 0) - if(!M) - return 0 - - return M.can_equip(src, slot, disable_warning) - -/obj/item/verb/verb_pickup() - set src in oview(1) - set category = null - set name = "Pick up" - - if(!(usr)) //BS12 EDIT - return - if(usr.incapacitated() || !Adjacent(usr)) - return - if(!iscarbon(usr) || isbrain(usr)) //Is humanoid, and is not a brain - to_chat(usr, "You can't pick things up!") - return - if(anchored) //Object isn't anchored - to_chat(usr, "You can't pick that up!") - return - if(!usr.hand && usr.r_hand) //Right hand is not full - to_chat(usr, "Your right hand is full.") - return - if(usr.hand && usr.l_hand) //Left hand is not full - to_chat(usr, "Your left hand is full.") - return - if(!isturf(loc)) //Object is on a turf - to_chat(usr, "You can't pick that up!") - return - //All checks are done, time to pick it up! - usr.UnarmedAttack(src) - - -//This proc is executed when someone clicks the on-screen UI button. -//The default action is attack_self(). -//Checks before we get to here are: mob is alive, mob is not restrained, paralyzed, asleep, resting, laying, item is on the mob. -/obj/item/proc/ui_action_click(mob/user, actiontype) - attack_self(user) - -/obj/item/proc/IsReflect(var/def_zone) //This proc determines if and at what% an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit - return 0 - -/obj/item/proc/get_loc_turf() - var/atom/L = loc - while(L && !istype(L, /turf/)) - L = L.loc - return loc - -/obj/item/proc/eyestab(mob/living/carbon/M as mob, mob/living/carbon/user as mob) - - var/mob/living/carbon/human/H = M - if(istype(H) && ( \ - (H.head && H.head.flags_cover & HEADCOVERSEYES) || \ - (H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) || \ - (H.glasses && H.glasses.flags_cover & GLASSESCOVERSEYES) \ - )) - // you can't stab someone in the eyes wearing a mask! - to_chat(user, "You're going to need to remove that mask/helmet/glasses first!") - return - - if(istype(M, /mob/living/carbon/alien) || istype(M, /mob/living/simple_animal/slime))//Aliens don't have eyes./N slimes also don't have eyes! - to_chat(user, "You cannot locate any eyes on this creature!") - return - - if(!iscarbon(user)) - M.LAssailant = null - else - M.LAssailant = user - - src.add_fingerprint(user) - - playsound(loc, src.hitsound, 30, 1, -1) - - user.do_attack_animation(M) - - if(M != user) - M.visible_message("[user] has stabbed [M] in the eye with [src]!", \ - "[user] stabs you in the eye with [src]!") - else - user.visible_message( \ - "[user] has stabbed [user.p_them()]self in the eyes with [src]!", \ - "You stab yourself in the eyes with [src]!" \ - ) - - add_attack_logs(user, M, "Eye-stabbed with [src] (INTENT: [uppertext(user.a_intent)])") - - if(istype(H)) - var/obj/item/organ/internal/eyes/eyes = H.get_int_organ(/obj/item/organ/internal/eyes) - if(!eyes) // should still get stabbed in the head - var/obj/item/organ/external/head/head = H.bodyparts_by_name["head"] - head.receive_damage(rand(10,14), 1) - return - eyes.receive_damage(rand(3,4), 1) - if(eyes.damage >= eyes.min_bruised_damage) - if(M.stat != 2) - if(!eyes.is_robotic()) //robot eyes bleeding might be a bit silly - to_chat(M, "Your eyes start to bleed profusely!") - if(prob(50)) - if(M.stat != DEAD) - to_chat(M, "You drop what you're holding and clutch at your eyes!") - M.drop_item() - M.AdjustEyeBlurry(10) - M.Paralyse(1) - M.Weaken(2) - if(eyes.damage >= eyes.min_broken_damage) - if(M.stat != 2) - to_chat(M, "You go blind!") - var/obj/item/organ/external/affecting = H.get_organ("head") - if(affecting.receive_damage(7)) - H.UpdateDamageIcon() - else - M.take_organ_damage(7) - M.AdjustEyeBlurry(rand(3,4)) - return - -/obj/item/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FOUR) - throw_at(S, 14, 3, spin = 0) - else - return - -/obj/item/throw_impact(atom/A) - if(A && !QDELETED(A)) - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, A) - var/itempush = 1 - if(w_class < WEIGHT_CLASS_BULKY) - itempush = 0 // too light to push anything - return A.hitby(src, 0, itempush) - -/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force) - thrownby = thrower - callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own - . = ..(target, range, speed, thrower, spin, diagonals_first, callback, force) - -/obj/item/proc/after_throw(datum/callback/callback) - if(callback) //call the original callback - . = callback.Invoke() - throw_speed = initial(throw_speed) //explosions change this. - in_inventory = FALSE - -/obj/item/proc/pwr_drain() - return 0 // Process Kill - -/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage - if(!newLoc) - return 0 - if(istype(loc,/obj/item/storage)) - var/obj/item/storage/S = loc - S.remove_from_storage(src,newLoc) - return 1 - return 0 - - -/obj/item/proc/wash(mob/user, atom/source) - if(flags & ABSTRACT) //Abstract items like grabs won't wash. No-drop items will though because it's still technically an item in your hand. - return - to_chat(user, "You start washing [src]...") - if(!do_after(user, 40, target = source)) - return - clean_blood() - acid_level = 0 - user.visible_message("[user] washes [src] using [source].", \ - "You wash [src] using [source].") - return 1 - -/obj/item/proc/is_crutch() //Does an item prop up a human mob and allow them to stand if they are missing a leg/foot? - return 0 - -// Return true if you don't want regular throw handling -/obj/item/proc/override_throw(mob/user, atom/target) - return FALSE - -/obj/item/proc/is_equivalent(obj/item/I) - return I == src - -/obj/item/Crossed(atom/movable/AM, oldloc) - . = ..() - if(prob(trip_chance) && ishuman(AM)) - var/mob/living/carbon/human/H = AM - on_trip(H) - -/obj/item/proc/on_trip(mob/living/carbon/human/H) - if(H.slip(src, trip_stun, trip_weaken, trip_tiles, trip_walksafe, trip_any, trip_verb)) - return TRUE - -/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - return - -/obj/item/attack_hulk(mob/living/carbon/human/user) - return FALSE - -/obj/item/attack_animal(mob/living/simple_animal/M) - if(can_be_hit) - return ..() - return FALSE - -/obj/item/mech_melee_attack(obj/mecha/M) - return 0 - -/obj/item/proc/openTip(location, control, params, user) - openToolTip(user, src, params, title = name, content = "[desc]", theme = "") - -/obj/item/MouseEntered(location, control, params) - if(in_inventory) - var/timedelay = 8 - var/user = usr - tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE) - -/obj/item/MouseExited() - deltimer(tip_timer) //delete any in-progress timer if the mouse is moved off the item before it finishes - closeToolTip(usr) - -// Returns a numeric value for sorting items used as parts in machines, so they can be replaced by the rped -/obj/item/proc/get_part_rating() - return 0 - -/obj/item/proc/update_slot_icon() - if(!ismob(loc)) - return - var/mob/owner = loc - var/flags = slot_flags - if(flags & SLOT_OCLOTHING) - owner.update_inv_wear_suit() - if(flags & SLOT_ICLOTHING) - owner.update_inv_w_uniform() - if(flags & SLOT_GLOVES) - owner.update_inv_gloves() - if(flags & SLOT_EYES) - owner.update_inv_glasses() - if(flags & SLOT_EARS) - owner.update_inv_ears() - if(flags & SLOT_MASK) - owner.update_inv_wear_mask() - if(flags & SLOT_HEAD) - owner.update_inv_head() - if(flags & SLOT_FEET) - owner.update_inv_shoes() - if(flags & SLOT_ID) - owner.update_inv_wear_id() - if(flags & SLOT_BELT) - owner.update_inv_belt() - if(flags & SLOT_BACK) - owner.update_inv_back() - if(flags & SLOT_PDA) - owner.update_inv_wear_pda() - +var/global/image/fire_overlay = image("icon" = 'icons/goonstation/effects/fire.dmi', "icon_state" = "fire") + +/obj/item + name = "item" + icon = 'icons/obj/items.dmi' + var/discrete = 0 // used in item_attack.dm to make an item not show an attack message to viewers + var/image/blood_overlay = null //this saves our blood splatter overlay, which will be processed not to go over the edges of the sprite + var/blood_overlay_color = null + var/item_state = null + var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' + + //Dimensions of the lefthand_file and righthand_file vars + //eg: 32x32 sprite, 64x64 sprite, etc. + var/inhand_x_dimension = 32 + var/inhand_y_dimension = 32 + + max_integrity = 200 + + can_be_hit = FALSE + suicidal_hands = TRUE + + var/hitsound = null + var/usesound = null + var/throwhitsound + var/w_class = WEIGHT_CLASS_NORMAL + var/slot_flags = 0 //This is used to determine on which slots an item can fit. + pass_flags = PASSTABLE + pressure_resistance = 4 +// causeerrorheresoifixthis + var/obj/item/master = null + + var/heat_protection = 0 //flags which determine which body parts are protected from heat. Use the HEAD, UPPER_TORSO, LOWER_TORSO, etc. flags. See setup.dm + var/cold_protection = 0 //flags which determine which body parts are protected from cold. Use the HEAD, UPPER_TORSO, LOWER_TORSO, etc. flags. See setup.dm + var/max_heat_protection_temperature //Set this variable to determine up to which temperature (IN KELVIN) the item protects against heat damage. Keep at null to disable protection. Only protects areas set by heat_protection flags + var/min_cold_protection_temperature //Set this variable to determine down to which temperature (IN KELVIN) the item protects against cold damage. 0 is NOT an acceptable number due to if(varname) tests!! Keep at null to disable protection. Only protects areas set by cold_protection flags + + var/list/actions = list() //list of /datum/action's that this item has. + var/list/actions_types = list() //list of paths of action datums to give to the item on New(). + var/list/action_icon = list() //list of icons-sheets for a given action to override the icon. + var/list/action_icon_state = list() //list of icon states for a given action to override the icon_state. + + var/list/materials = list() + //Since any item can now be a piece of clothing, this has to be put here so all items share it. + var/flags_inv //This flag is used to determine when items in someone's inventory cover others. IE helmets making it so you can't see glasses, etc. + var/item_color = null + var/body_parts_covered = 0 //see setup.dm for appropriate bit flags + //var/heat_transfer_coefficient = 1 //0 prevents all transfers, 1 is invisible + var/gas_transfer_coefficient = 1 // for leaking gas from turf to mask and vice-versa (for masks right now, but at some point, i'd like to include space helmets) + var/permeability_coefficient = 1 // for chemicals/diseases + var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit) + var/slowdown = 0 // How much clothing is slowing you down. Negative values speeds you up + var/armour_penetration = 0 //percentage of armour effectiveness to remove + var/list/allowed = null //suit storage stuff. + var/obj/item/uplink/hidden/hidden_uplink = null // All items can have an uplink hidden inside, just remember to add the triggers. + + var/needs_permit = 0 //Used by security bots to determine if this item is safe for public use. + + var/strip_delay = DEFAULT_ITEM_STRIP_DELAY + var/put_on_delay = DEFAULT_ITEM_PUTON_DELAY + var/breakouttime = 0 + var/flags_cover = 0 //for flags such as GLASSESCOVERSEYES + var/flags_size = 0 //flag, primarily used for clothing to determine if a fatty can wear something or not. + + var/block_chance = 0 + var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom + + // Needs to be in /obj/item because corgis can wear a lot of + // non-clothing items + var/datum/dog_fashion/dog_fashion = null + + var/mob/thrownby = null + + //So items can have custom embedd values + //Because customisation is king + var/embed_chance = EMBED_CHANCE + var/embedded_fall_chance = EMBEDDED_ITEM_FALLOUT + var/embedded_pain_chance = EMBEDDED_PAIN_CHANCE + var/embedded_pain_multiplier = EMBEDDED_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does while embedded (this*w_class) + var/embedded_fall_pain_multiplier = EMBEDDED_FALL_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does when falling out of a limb (this*w_class) + var/embedded_impact_pain_multiplier = EMBEDDED_IMPACT_PAIN_MULTIPLIER //The coefficient of multiplication for the damage this item does when first embedded (this*w_class) + var/embedded_unsafe_removal_pain_multiplier = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER //The coefficient of multiplication for the damage removing this without surgery causes (this*w_class) + var/embedded_unsafe_removal_time = EMBEDDED_UNSAFE_REMOVAL_TIME //A time in ticks, multiplied by the w_class. + var/embedded_ignore_throwspeed_threshold = FALSE + + var/tool_behaviour = NONE //What kind of tool are we? + var/tool_enabled = TRUE //If we can turn on or off, are we currently active? Mostly for welders and this will normally be TRUE + var/tool_volume = 50 //How loud are we when we use our tool? + var/toolspeed = 1 // If this item is a tool, the speed multiplier + + /* Species-specific sprites, concept stolen from Paradise//vg/. + ex: + sprite_sheets = list( + "Tajaran" = 'icons/cat/are/bad' + ) + If index term exists and icon_override is not set, this sprite sheet will be used. + */ + var/list/sprite_sheets = null + var/list/sprite_sheets_inhand = null //Used to override inhand items. Use a single .dmi and suffix the icon states inside with _l and _r for each hand. + var/icon_override = null //Used to override hardcoded clothing dmis in human clothing proc. + var/sprite_sheets_obj = null //Used to override hardcoded clothing inventory object dmis in human clothing proc. + + var/trip_verb = TV_TRIP + var/trip_chance = 0 + + var/trip_stun = 0 + var/trip_weaken = 0 + var/trip_any = FALSE + var/trip_walksafe = TRUE + var/trip_tiles = 0 + + //Tooltip vars + var/in_inventory = FALSE //is this item equipped into an inventory slot or hand of a mob? + var/tip_timer = 0 + +/obj/item/New() + ..() + for(var/path in actions_types) + new path(src, action_icon[path], action_icon_state[path]) + + if(!hitsound) + if(damtype == "fire") + hitsound = 'sound/items/welder.ogg' + if(damtype == "brute") + hitsound = "swing_hit" + +/obj/item/Destroy() + flags &= ~DROPDEL //prevent reqdels + QDEL_NULL(hidden_uplink) + if(ismob(loc)) + var/mob/m = loc + m.unEquip(src, 1) + QDEL_LIST(actions) + master = null + return ..() + +/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self) + if(((src in target) && !target_self) || ((!istype(target.loc, /turf)) && (!istype(target, /turf)) && (not_inside))) + return 0 + else + return 1 + +/obj/item/blob_act(obj/structure/blob/B) + if(B && B.loc == loc) + qdel(src) + +/obj/item/verb/move_to_top() + set name = "Move To Top" + set category = null + set src in oview(1) + + if(!istype(src.loc, /turf) || usr.stat || usr.restrained() ) + return + + var/turf/T = src.loc + + src.loc = null + + src.loc = T + +/obj/item/examine(mob/user) + var/size + switch(src.w_class) + if(WEIGHT_CLASS_TINY) + size = "tiny" + if(WEIGHT_CLASS_SMALL) + size = "small" + if(WEIGHT_CLASS_NORMAL) + size = "normal-sized" + if(WEIGHT_CLASS_BULKY) + size = "bulky" + if(WEIGHT_CLASS_HUGE) + size = "huge" + if(WEIGHT_CLASS_GIGANTIC) + size = "gigantic" + + . = ..(user, "", "It is a [size] item.") + + if(user.research_scanner) //Mob has a research scanner active. + var/msg = "*--------*
        " + + if(origin_tech) + msg += "Testing potentials:
        " + var/list/techlvls = params2list(origin_tech) + for(var/T in techlvls) //This needs to use the better names. + msg += "Tech: [CallTechName(T)] | Magnitude: [techlvls[T]]
        " + else + msg += "No tech origins detected.
        " + + + if(materials.len) + msg += "Extractable materials:
        " + for(var/mat in materials) + msg += "[CallMaterialName(mat)]
        " //Capitize first word, remove the "$" + else + msg += "No extractable materials detected.
        " + msg += "*--------*" + . += msg + +/obj/item/burn() + if(!QDELETED(src)) + var/turf/T = get_turf(src) + var/obj/effect/decal/cleanable/ash/A = new(T) + A.desc += "\nLooks like this used to be \an [name] some time ago." + ..() + +/obj/item/acid_melt() + if(!QDELETED(src)) + var/turf/T = get_turf(src) + var/obj/effect/decal/cleanable/molten_object/MO = new(T) + MO.pixel_x = rand(-16,16) + MO.pixel_y = rand(-16,16) + MO.desc = "Looks like this was \an [src] some time ago." + ..() + +/obj/item/afterattack(atom/target, mob/user, proximity, params) + SEND_SIGNAL(src, COMSIG_ITEM_AFTERATTACK, target, user, proximity, params) + ..() + +/obj/item/attack_hand(mob/user as mob, pickupfireoverride = FALSE) + if(!user) return 0 + if(hasorgans(user)) + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/temp = H.bodyparts_by_name["r_hand"] + if(user.hand) + temp = H.bodyparts_by_name["l_hand"] + if(!temp) + to_chat(user, "You try to use your hand, but it's missing!") + return 0 + if(temp && !temp.is_usable()) + to_chat(user, "You try to move your [temp.name], but cannot!") + return 0 + + if((resistance_flags & ON_FIRE) && !pickupfireoverride) + var/mob/living/carbon/human/H = user + if(istype(H)) + if(H.gloves && (H.gloves.max_heat_protection_temperature > 360)) + extinguish() + to_chat(user, "You put out the fire on [src].") + else + to_chat(user, "You burn your hand on [src]!") + var/obj/item/organ/external/affecting = H.get_organ("[user.hand ? "l" : "r" ]_arm") + if(affecting && affecting.receive_damage(0, 5)) // 5 burn damage + H.UpdateDamageIcon() + return + else + extinguish() + + if(acid_level > 20 && !ismob(loc))// so we can still remove the clothes on us that have acid. + var/mob/living/carbon/human/H = user + if(istype(H)) + if(!H.gloves || (!(H.gloves.resistance_flags & (UNACIDABLE|ACID_PROOF)))) + to_chat(user, "The acid on [src] burns your hand!") + var/obj/item/organ/external/affecting = H.get_organ("[user.hand ? "l" : "r" ]_arm") + if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage + H.UpdateDamageIcon() + + if(istype(src.loc, /obj/item/storage)) + //If the item is in a storage item, take it out + var/obj/item/storage/S = src.loc + S.remove_from_storage(src) + + if(throwing) + throwing.finalize(FALSE) + if(loc == user) + if(!user.unEquip(src)) + return 0 + + else + if(isliving(loc)) + return 0 + add_fingerprint(user) + if(pickup(user)) // Pickup succeeded + user.put_in_active_hand(src) + + return 1 + +/obj/item/attack_alien(mob/user) + var/mob/living/carbon/alien/A = user + + if(!A.has_fine_manipulation) + if(src in A.contents) // To stop Aliens having items stuck in their pockets + A.unEquip(src) + to_chat(user, "Your claws aren't capable of such fine manipulation!") + return + attack_hand(A) + +/obj/item/attack_ai(mob/user as mob) + if(istype(src.loc, /obj/item/robot_module)) + //If the item is part of a cyborg module, equip it + if(!isrobot(user)) + return + var/mob/living/silicon/robot/R = user + if(!R.low_power_mode) //can't equip modules with an empty cell. + R.activate_module(src) + R.hud_used.update_robot_modules_display() + +// Due to storage type consolidation this should get used more now. +// I have cleaned it up a little, but it could probably use more. -Sayu +/obj/item/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/storage)) + var/obj/item/storage/S = I + if(S.use_to_pickup) + if(S.collection_mode) //Mode is set to collect all items on a tile and we clicked on a valid one. + if(isturf(loc)) + var/list/rejections = list() + var/success = 0 + var/failure = 0 + + for(var/obj/item/IT in loc) + if(IT.type in rejections) // To limit bag spamming: any given type only complains once + continue + if(!S.can_be_inserted(IT)) // Note can_be_inserted still makes noise when the answer is no + rejections += IT.type // therefore full bags are still a little spammy + failure = 1 + continue + success = 1 + S.handle_item_insertion(IT, 1) //The 1 stops the "You put the [src] into [S]" insertion message from being displayed. + if(success && !failure) + to_chat(user, "You put everything in [S].") + else if(success) + to_chat(user, "You put some things in [S].") + else + to_chat(user, "You fail to pick anything up with [S].") + + else if(S.can_be_inserted(src)) + S.handle_item_insertion(src) + else if(istype(I, /obj/item/stack/tape_roll)) + if(istype(src, /obj/item/storage)) //Don't tape the bag if we can put the duct tape inside it instead + var/obj/item/storage/bag = src + if(bag.can_be_inserted(I)) + return ..() + var/obj/item/stack/tape_roll/TR = I + var/list/clickparams = params2list(params) + var/x_offset = text2num(clickparams["icon-x"]) + var/y_offset = text2num(clickparams["icon-y"]) + if(GetComponent(/datum/component/ducttape)) + to_chat(user, "[src] already has some tape attached!") + return + if(TR.use(1)) + to_chat(user, "You apply some tape to [src].") + AddComponent(/datum/component/ducttape, src, user, x_offset, y_offset) + anchored = TRUE + user.transfer_fingerprints_to(src) + else + to_chat(user, "You don't have enough tape to do that!") + else + return ..() + +/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) + if(prob(final_block_chance)) + owner.visible_message("[owner] blocks [attack_text] with [src]!") + return 1 + return 0 + +// Generic use proc. Depending on the item, it uses up fuel, charges, sheets, etc. +// Returns TRUE on success, FALSE on failure. +/obj/item/proc/use(used) + return !used + +//Generic refill proc. Transfers something (e.g. fuel, charge) from an atom to our tool. returns TRUE if it was successful, FALSE otherwise +//Not sure if there should be an argument that indicates what exactly is being refilled +/obj/item/proc/refill(mob/user, atom/A, amount) + return FALSE + +/obj/item/proc/talk_into(mob/M, var/text, var/channel=null) + return + +/obj/item/proc/dropped(mob/user) + for(var/X in actions) + var/datum/action/A = X + A.Remove(user) + if(flags & DROPDEL) + qdel(src) + if((flags & NODROP) && !(initial(flags) & NODROP)) //Remove NODROP is dropped + flags &= ~NODROP + in_inventory = FALSE + SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user) + +// called just as an item is picked up (loc is not yet changed) +/obj/item/proc/pickup(mob/user) + SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) + in_inventory = TRUE + return TRUE + +// called when this item is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called. +/obj/item/proc/on_exit_storage(obj/item/storage/S as obj) + return + +// called when this item is added into a storage item, which is passed on as S. The loc variable is already set to the storage item. +/obj/item/proc/on_enter_storage(obj/item/storage/S as obj) + return + +// called when "found" in pockets and storage items. Returns 1 if the search should end. +/obj/item/proc/on_found(mob/finder as mob) + return + +// called when the giver gives it to the receiver +/obj/item/proc/on_give(mob/living/carbon/giver, mob/living/carbon/receiver) + return + +// called after an item is placed in an equipment slot +// user is mob that equipped it +// slot uses the slot_X defines found in setup.dm +// for items that can be placed in multiple slots +// note this isn't called during the initial dressing of a player +/obj/item/proc/equipped(var/mob/user, var/slot) + SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) + for(var/X in actions) + var/datum/action/A = X + if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot. + A.Grant(user) + in_inventory = TRUE + +/obj/item/proc/item_action_slot_check(slot, mob/user) + return 1 + +//returns 1 if the item is equipped by a mob, 0 otherwise. +//This might need some error trapping, not sure if get_equipped_items() is safe for non-human mobs. +/obj/item/proc/is_equipped() + if(!ismob(loc)) + return 0 + + var/mob/M = loc + if(src in M.get_equipped_items()) + return 1 + else + return 0 + +//the mob M is attempting to equip this item into the slot passed through as 'slot'. Return 1 if it can do this and 0 if it can't. +//If you are making custom procs but would like to retain partial or complete functionality of this one, include a 'return ..()' to where you want this to happen. +//Set disable_warning to 1 if you wish it to not give you outputs. +/obj/item/proc/mob_can_equip(mob/M, slot, disable_warning = 0) + if(!M) + return 0 + + return M.can_equip(src, slot, disable_warning) + +/obj/item/verb/verb_pickup() + set src in oview(1) + set category = null + set name = "Pick up" + + if(!(usr)) //BS12 EDIT + return + if(usr.incapacitated() || !Adjacent(usr)) + return + if(!iscarbon(usr) || isbrain(usr)) //Is humanoid, and is not a brain + to_chat(usr, "You can't pick things up!") + return + if(anchored) //Object isn't anchored + to_chat(usr, "You can't pick that up!") + return + if(!usr.hand && usr.r_hand) //Right hand is not full + to_chat(usr, "Your right hand is full.") + return + if(usr.hand && usr.l_hand) //Left hand is not full + to_chat(usr, "Your left hand is full.") + return + if(!isturf(loc)) //Object is on a turf + to_chat(usr, "You can't pick that up!") + return + //All checks are done, time to pick it up! + usr.UnarmedAttack(src) + + +//This proc is executed when someone clicks the on-screen UI button. +//The default action is attack_self(). +//Checks before we get to here are: mob is alive, mob is not restrained, paralyzed, asleep, resting, laying, item is on the mob. +/obj/item/proc/ui_action_click(mob/user, actiontype) + attack_self(user) + +/obj/item/proc/IsReflect(var/def_zone) //This proc determines if and at what% an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit + return 0 + +/obj/item/proc/get_loc_turf() + var/atom/L = loc + while(L && !istype(L, /turf/)) + L = L.loc + return loc + +/obj/item/proc/eyestab(mob/living/carbon/M as mob, mob/living/carbon/user as mob) + + var/mob/living/carbon/human/H = M + if(istype(H) && ( \ + (H.head && H.head.flags_cover & HEADCOVERSEYES) || \ + (H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) || \ + (H.glasses && H.glasses.flags_cover & GLASSESCOVERSEYES) \ + )) + // you can't stab someone in the eyes wearing a mask! + to_chat(user, "You're going to need to remove that mask/helmet/glasses first!") + return + + if(istype(M, /mob/living/carbon/alien) || istype(M, /mob/living/simple_animal/slime))//Aliens don't have eyes./N slimes also don't have eyes! + to_chat(user, "You cannot locate any eyes on this creature!") + return + + if(!iscarbon(user)) + M.LAssailant = null + else + M.LAssailant = user + + src.add_fingerprint(user) + + playsound(loc, src.hitsound, 30, 1, -1) + + user.do_attack_animation(M) + + if(M != user) + M.visible_message("[user] has stabbed [M] in the eye with [src]!", \ + "[user] stabs you in the eye with [src]!") + else + user.visible_message( \ + "[user] has stabbed [user.p_them()]self in the eyes with [src]!", \ + "You stab yourself in the eyes with [src]!" \ + ) + + add_attack_logs(user, M, "Eye-stabbed with [src] (INTENT: [uppertext(user.a_intent)])") + + if(istype(H)) + var/obj/item/organ/internal/eyes/eyes = H.get_int_organ(/obj/item/organ/internal/eyes) + if(!eyes) // should still get stabbed in the head + var/obj/item/organ/external/head/head = H.bodyparts_by_name["head"] + head.receive_damage(rand(10,14), 1) + return + eyes.receive_damage(rand(3,4), 1) + if(eyes.damage >= eyes.min_bruised_damage) + if(M.stat != 2) + if(!eyes.is_robotic()) //robot eyes bleeding might be a bit silly + to_chat(M, "Your eyes start to bleed profusely!") + if(prob(50)) + if(M.stat != DEAD) + to_chat(M, "You drop what you're holding and clutch at your eyes!") + M.drop_item() + M.AdjustEyeBlurry(10) + M.Paralyse(1) + M.Weaken(2) + if(eyes.damage >= eyes.min_broken_damage) + if(M.stat != 2) + to_chat(M, "You go blind!") + var/obj/item/organ/external/affecting = H.get_organ("head") + if(affecting.receive_damage(7)) + H.UpdateDamageIcon() + else + M.take_organ_damage(7) + M.AdjustEyeBlurry(rand(3,4)) + return + +/obj/item/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FOUR) + throw_at(S, 14, 3, spin = 0) + else + return + +/obj/item/throw_impact(atom/A) + if(A && !QDELETED(A)) + SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, A) + var/itempush = 1 + if(w_class < WEIGHT_CLASS_BULKY) + itempush = 0 // too light to push anything + return A.hitby(src, 0, itempush) + +/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force) + thrownby = thrower + callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own + . = ..(target, range, speed, thrower, spin, diagonals_first, callback, force) + +/obj/item/proc/after_throw(datum/callback/callback) + if(callback) //call the original callback + . = callback.Invoke() + throw_speed = initial(throw_speed) //explosions change this. + in_inventory = FALSE + +/obj/item/proc/pwr_drain() + return 0 // Process Kill + +/obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage + if(!newLoc) + return 0 + if(istype(loc,/obj/item/storage)) + var/obj/item/storage/S = loc + S.remove_from_storage(src,newLoc) + return 1 + return 0 + + +/obj/item/proc/wash(mob/user, atom/source) + if(flags & ABSTRACT) //Abstract items like grabs won't wash. No-drop items will though because it's still technically an item in your hand. + return + to_chat(user, "You start washing [src]...") + if(!do_after(user, 40, target = source)) + return + clean_blood() + acid_level = 0 + user.visible_message("[user] washes [src] using [source].", \ + "You wash [src] using [source].") + return 1 + +/obj/item/proc/is_crutch() //Does an item prop up a human mob and allow them to stand if they are missing a leg/foot? + return 0 + +// Return true if you don't want regular throw handling +/obj/item/proc/override_throw(mob/user, atom/target) + return FALSE + +/obj/item/proc/is_equivalent(obj/item/I) + return I == src + +/obj/item/Crossed(atom/movable/AM, oldloc) + . = ..() + if(prob(trip_chance) && ishuman(AM)) + var/mob/living/carbon/human/H = AM + on_trip(H) + +/obj/item/proc/on_trip(mob/living/carbon/human/H) + if(H.slip(src, trip_stun, trip_weaken, trip_tiles, trip_walksafe, trip_any, trip_verb)) + return TRUE + +/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + return + +/obj/item/attack_hulk(mob/living/carbon/human/user) + return FALSE + +/obj/item/attack_animal(mob/living/simple_animal/M) + if(can_be_hit) + return ..() + return FALSE + +/obj/item/mech_melee_attack(obj/mecha/M) + return 0 + +/obj/item/proc/openTip(location, control, params, user) + openToolTip(user, src, params, title = name, content = "[desc]", theme = "") + +/obj/item/MouseEntered(location, control, params) + if(in_inventory) + var/timedelay = 8 + var/user = usr + tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE) + +/obj/item/MouseExited() + deltimer(tip_timer) //delete any in-progress timer if the mouse is moved off the item before it finishes + closeToolTip(usr) + +// Returns a numeric value for sorting items used as parts in machines, so they can be replaced by the rped +/obj/item/proc/get_part_rating() + return 0 + +/obj/item/proc/update_slot_icon() + if(!ismob(loc)) + return + var/mob/owner = loc + var/flags = slot_flags + if(flags & SLOT_OCLOTHING) + owner.update_inv_wear_suit() + if(flags & SLOT_ICLOTHING) + owner.update_inv_w_uniform() + if(flags & SLOT_GLOVES) + owner.update_inv_gloves() + if(flags & SLOT_EYES) + owner.update_inv_glasses() + if(flags & SLOT_EARS) + owner.update_inv_ears() + if(flags & SLOT_MASK) + owner.update_inv_wear_mask() + if(flags & SLOT_HEAD) + owner.update_inv_head() + if(flags & SLOT_FEET) + owner.update_inv_shoes() + if(flags & SLOT_ID) + owner.update_inv_wear_id() + if(flags & SLOT_BELT) + owner.update_inv_belt() + if(flags & SLOT_BACK) + owner.update_inv_back() + if(flags & SLOT_PDA) + owner.update_inv_wear_pda() + diff --git a/code/game/objects/items/ashtray.dm b/code/game/objects/items/ashtray.dm index 8ca65d7a668..5cf65188901 100644 --- a/code/game/objects/items/ashtray.dm +++ b/code/game/objects/items/ashtray.dm @@ -91,4 +91,4 @@ max_butts = 12 max_integrity = 12 materials = list(MAT_GLASS=60) - throwforce = 6 \ No newline at end of file + throwforce = 6 diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index e68251f09bd..5c63dbc709c 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -1,77 +1,77 @@ -//Also contains /obj/structure/closet/body_bag because I doubt anyone would think to look for bodybags in /object/structures - -/obj/item/bodybag - name = "body bag" - desc = "A folded bag designed for the storage and transportation of cadavers." - icon = 'icons/obj/bodybag.dmi' - icon_state = "bodybag_folded" - w_class = WEIGHT_CLASS_SMALL - - attack_self(mob/user) - var/obj/structure/closet/body_bag/R = new /obj/structure/closet/body_bag(user.loc) - R.add_fingerprint(user) - qdel(src) - -/obj/structure/closet/body_bag - name = "body bag" - desc = "A plastic bag designed for the storage and transportation of cadavers." - icon = 'icons/obj/bodybag.dmi' - icon_state = "bodybag_closed" - icon_closed = "bodybag_closed" - icon_opened = "bodybag_open" - sound = 'sound/items/zip.ogg' - var/item_path = /obj/item/bodybag - density = 0 - integrity_failure = 0 - - -/obj/structure/closet/body_bag/attackby(W as obj, mob/user as mob, params) - if(istype(W, /obj/item/pen)) - var/t = input(user, "What would you like the label to be?", text("[]", src.name), null) as text - if(user.get_active_hand() != W) - return - if(!in_range(src, user) && src.loc != user) - return - t = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) - if(t) - src.name = "body bag - " - src.name += t - src.overlays += image(src.icon, "bodybag_label") - else - src.name = "body bag" - return - if(istype(W, /obj/item/wirecutters)) - to_chat(user, "You cut the tag off the bodybag") - src.name = "body bag" - src.overlays.Cut() - return - return ..() - - -/obj/structure/closet/body_bag/close() - if(..()) - density = 0 - return 1 - return 0 - - -/obj/structure/closet/body_bag/MouseDrop(over_object, src_location, over_location) - ..() - if((over_object == usr && (in_range(src, usr) || usr.contents.Find(src)))) - if(!ishuman(usr)) return - if(opened) return 0 - if(contents.len) return 0 - visible_message("[usr] folds up the [src.name]") - new item_path(get_turf(src)) - spawn(0) - qdel(src) - return - -/obj/structure/closet/body_bag/relaymove(mob/user as mob) - if(user.stat) - return - - // Make it possible to escape from bodybags in morgues and crematoriums - if(loc && (isturf(loc) || istype(loc, /obj/structure/morgue) || istype(loc, /obj/structure/crematorium))) - if(!open()) - to_chat(user, "It won't budge!") +//Also contains /obj/structure/closet/body_bag because I doubt anyone would think to look for bodybags in /object/structures + +/obj/item/bodybag + name = "body bag" + desc = "A folded bag designed for the storage and transportation of cadavers." + icon = 'icons/obj/bodybag.dmi' + icon_state = "bodybag_folded" + w_class = WEIGHT_CLASS_SMALL + + attack_self(mob/user) + var/obj/structure/closet/body_bag/R = new /obj/structure/closet/body_bag(user.loc) + R.add_fingerprint(user) + qdel(src) + +/obj/structure/closet/body_bag + name = "body bag" + desc = "A plastic bag designed for the storage and transportation of cadavers." + icon = 'icons/obj/bodybag.dmi' + icon_state = "bodybag_closed" + icon_closed = "bodybag_closed" + icon_opened = "bodybag_open" + sound = 'sound/items/zip.ogg' + var/item_path = /obj/item/bodybag + density = 0 + integrity_failure = 0 + + +/obj/structure/closet/body_bag/attackby(W as obj, mob/user as mob, params) + if(istype(W, /obj/item/pen)) + var/t = input(user, "What would you like the label to be?", text("[]", src.name), null) as text + if(user.get_active_hand() != W) + return + if(!in_range(src, user) && src.loc != user) + return + t = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + if(t) + src.name = "body bag - " + src.name += t + src.overlays += image(src.icon, "bodybag_label") + else + src.name = "body bag" + return + if(istype(W, /obj/item/wirecutters)) + to_chat(user, "You cut the tag off the bodybag") + src.name = "body bag" + src.overlays.Cut() + return + return ..() + + +/obj/structure/closet/body_bag/close() + if(..()) + density = 0 + return 1 + return 0 + + +/obj/structure/closet/body_bag/MouseDrop(over_object, src_location, over_location) + ..() + if((over_object == usr && (in_range(src, usr) || usr.contents.Find(src)))) + if(!ishuman(usr)) return + if(opened) return 0 + if(contents.len) return 0 + visible_message("[usr] folds up the [src.name]") + new item_path(get_turf(src)) + spawn(0) + qdel(src) + return + +/obj/structure/closet/body_bag/relaymove(mob/user as mob) + if(user.stat) + return + + // Make it possible to escape from bodybags in morgues and crematoriums + if(loc && (isturf(loc) || istype(loc, /obj/structure/morgue) || istype(loc, /obj/structure/crematorium))) + if(!open()) + to_chat(user, "It won't budge!") diff --git a/code/game/objects/items/candle.dm b/code/game/objects/items/candle.dm index ca142e6f8be..48e72a2cf9b 100644 --- a/code/game/objects/items/candle.dm +++ b/code/game/objects/items/candle.dm @@ -1,86 +1,86 @@ -/obj/item/candle - name = "red candle" - desc = "In Greek myth, Prometheus stole fire from the Gods and gave it to humankind. The jewelry he kept for himself." - icon = 'icons/obj/candle.dmi' - icon_state = "candle1" - item_state = "candle1" - w_class = WEIGHT_CLASS_TINY - var/wax = 200 - var/lit = 0 - var/infinite = 0 - var/start_lit = 0 - light_color = "#E09D37" - -/obj/item/candle/New() - ..() - if(start_lit) - // No visible message - light(show_message = 0) - -/obj/item/candle/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/candle/update_icon() - var/i - if(wax>150) - i = 1 - else if(wax>80) - i = 2 - else i = 3 - icon_state = "candle[i][lit ? "_lit" : ""]" - - -/obj/item/candle/attackby(obj/item/W, mob/user, params) - if(is_hot(W)) - light("[user] lights [src] with [W].") - return - return ..() - -/obj/item/candle/welder_act(mob/user, obj/item/I) - . = TRUE - if(I.tool_use_check(user, 0)) //Don't need to flash eyes because you are a badass - light("[user] casually lights the [name] with [I], what a badass.") - -/obj/item/candle/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - if(!lit) - light() //honk - return ..() - -/obj/item/candle/proc/light(show_message) - if(!lit) - lit = 1 - if(show_message) - usr.visible_message(show_message) - set_light(CANDLE_LUM) - START_PROCESSING(SSobj, src) - update_icon() - - -/obj/item/candle/process() - if(!lit) - return - if(!infinite) - wax-- - if(!wax) - new/obj/item/trash/candle(src.loc) - if(istype(src.loc, /mob)) - var/mob/M = src.loc - M.unEquip(src, 1) //src is being deleted anyway - qdel(src) - update_icon() - if(isturf(loc)) //start a fire if possible - var/turf/T = loc - T.hotspot_expose(700, 5) - - -/obj/item/candle/attack_self(mob/user) - if(lit) - user.visible_message("[user] snuffs out [src].") - lit = 0 - update_icon() - set_light(0) - -/obj/item/candle/eternal - desc = "A candle. This one seems to have an odd quality about the wax." - infinite = 1 +/obj/item/candle + name = "red candle" + desc = "In Greek myth, Prometheus stole fire from the Gods and gave it to humankind. The jewelry he kept for himself." + icon = 'icons/obj/candle.dmi' + icon_state = "candle1" + item_state = "candle1" + w_class = WEIGHT_CLASS_TINY + var/wax = 200 + var/lit = 0 + var/infinite = 0 + var/start_lit = 0 + light_color = "#E09D37" + +/obj/item/candle/New() + ..() + if(start_lit) + // No visible message + light(show_message = 0) + +/obj/item/candle/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/candle/update_icon() + var/i + if(wax>150) + i = 1 + else if(wax>80) + i = 2 + else i = 3 + icon_state = "candle[i][lit ? "_lit" : ""]" + + +/obj/item/candle/attackby(obj/item/W, mob/user, params) + if(is_hot(W)) + light("[user] lights [src] with [W].") + return + return ..() + +/obj/item/candle/welder_act(mob/user, obj/item/I) + . = TRUE + if(I.tool_use_check(user, 0)) //Don't need to flash eyes because you are a badass + light("[user] casually lights the [name] with [I], what a badass.") + +/obj/item/candle/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + if(!lit) + light() //honk + return ..() + +/obj/item/candle/proc/light(show_message) + if(!lit) + lit = 1 + if(show_message) + usr.visible_message(show_message) + set_light(CANDLE_LUM) + START_PROCESSING(SSobj, src) + update_icon() + + +/obj/item/candle/process() + if(!lit) + return + if(!infinite) + wax-- + if(!wax) + new/obj/item/trash/candle(src.loc) + if(istype(src.loc, /mob)) + var/mob/M = src.loc + M.unEquip(src, 1) //src is being deleted anyway + qdel(src) + update_icon() + if(isturf(loc)) //start a fire if possible + var/turf/T = loc + T.hotspot_expose(700, 5) + + +/obj/item/candle/attack_self(mob/user) + if(lit) + user.visible_message("[user] snuffs out [src].") + lit = 0 + update_icon() + set_light(0) + +/obj/item/candle/eternal + desc = "A candle. This one seems to have an odd quality about the wax." + infinite = 1 diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm index 4d27e5d175b..eeb484b9f50 100644 --- a/code/game/objects/items/crayons.dm +++ b/code/game/objects/items/crayons.dm @@ -1,287 +1,287 @@ -/* - * Crayons - */ - -/obj/item/toy/crayon - name = "crayon" - desc = "A colourful crayon. Looks tasty. Mmmm..." - icon = 'icons/obj/crayons.dmi' - icon_state = "crayonred" - w_class = WEIGHT_CLASS_TINY - slot_flags = SLOT_BELT | SLOT_EARS - attack_verb = list("attacked", "coloured") - toolspeed = 1 - var/colour = "#FF0000" //RGB - var/drawtype = "rune" - var/list/graffiti = list("body","amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","up","down","left","right","heart","borgsrogue","voxpox","shitcurity","catbeast","hieroglyphs1","hieroglyphs2","hieroglyphs3","security","syndicate1","syndicate2","nanotrasen","lie","valid","arrowleft","arrowright","arrowup","arrowdown","chicken","hailcrab","brokenheart","peace","scribble","scribble2","scribble3","skrek","squish","tunnelsnake","yip","youaredead") - var/list/letters = list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z") - var/uses = 30 //0 for unlimited uses - var/instant = 0 - var/colourName = "red" //for updateIcon purposes - var/dat - var/busy = FALSE - var/list/validSurfaces = list(/turf/simulated/floor) - -/obj/item/toy/crayon/suicide_act(mob/user) - user.visible_message("[user] is jamming the [name] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide.") - return BRUTELOSS|OXYLOSS - -/obj/item/toy/crayon/New() - ..() - name = "[colourName] crayon" //Makes crayons identifiable in things like grinders - drawtype = pick(pick(graffiti), pick(letters), "rune[rand(1,10)]") - -/obj/item/toy/crayon/attack_self(mob/living/user as mob) - update_window(user) - -/obj/item/toy/crayon/proc/update_window(mob/living/user as mob) - dat += "

        Currently selected: [drawtype]


        " - dat += "Random letterPick letter" - dat += "
        " - dat += "

        Runes:


        " - dat += "Random rune" - for(var/i = 1; i <= 10; i++) - dat += "Rune[i]" - if(!((i + 1) % 3)) //3 buttons in a row - dat += "
        " - dat += "
        " - graffiti.Find() - dat += "

        Graffiti:


        " - dat += "Random graffiti" - var/c = 1 - for(var/T in graffiti) - dat += "[T]" - if(!((c + 1) % 3)) //3 buttons in a row - dat += "
        " - c++ - dat += "
        " - var/datum/browser/popup = new(user, "crayon", name, 300, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - dat = "" - -/obj/item/toy/crayon/Topic(href, href_list, hsrc) - var/temp = "a" - switch(href_list["type"]) - if("random_letter") - temp = pick(letters) - if("letter") - temp = input("Choose the letter.", "Scribbles") in letters - if("random_rune") - temp = "rune[rand(1,10)]" - if("random_graffiti") - temp = pick(graffiti) - else - temp = href_list["type"] - if((usr.restrained() || usr.stat || !usr.is_in_active_hand(src))) - return - drawtype = temp - update_window(usr) - -/obj/item/toy/crayon/afterattack(atom/target, mob/user, proximity) - if(!proximity) return - if(busy) return - if(is_type_in_list(target,validSurfaces)) - var/temp = "rune" - if(letters.Find(drawtype)) - temp = "letter" - else if(graffiti.Find(drawtype)) - temp = "graffiti" - to_chat(user, "You start drawing a [temp] on the [target.name].") - busy = TRUE - if(instant || do_after(user, 50 * toolspeed, target = target)) - var/obj/effect/decal/cleanable/crayon/C = new /obj/effect/decal/cleanable/crayon(target,colour,drawtype,temp) - C.add_hiddenprint(user) - to_chat(user, "You finish drawing [temp].") - if(uses) - uses-- - if(!uses) - to_chat(user, "You used up your [name]!") - qdel(src) - busy = FALSE - -/obj/item/toy/crayon/attack(mob/M, mob/user) - var/huffable = istype(src,/obj/item/toy/crayon/spraycan) - if(M == user) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(!H.check_has_mouth()) - to_chat(user, "You do not have a mouth!") - return - playsound(loc, 'sound/items/eatfood.ogg', 50, 0) - to_chat(user, "You take a [huffable ? "huff" : "bite"] of the [name]. Delicious!") - user.adjust_nutrition(5) - if(uses) - uses -= 5 - if(uses <= 0) - to_chat(user, "There is no more of [name] left!") - qdel(src) - else - ..() - - -/obj/item/toy/crayon/red - icon_state = "crayonred" - colour = "#DA0000" - colourName = "red" - -/obj/item/toy/crayon/orange - icon_state = "crayonorange" - colour = "#FF9300" - colourName = "orange" - -/obj/item/toy/crayon/yellow - icon_state = "crayonyellow" - colour = "#FFF200" - colourName = "yellow" - -/obj/item/toy/crayon/green - icon_state = "crayongreen" - colour = "#A8E61D" - colourName = "green" - -/obj/item/toy/crayon/blue - icon_state = "crayonblue" - colour = "#00B7EF" - colourName = "blue" - -/obj/item/toy/crayon/purple - icon_state = "crayonpurple" - colour = "#DA00FF" - colourName = "purple" - -/obj/item/toy/crayon/random/New() - icon_state = pick(list("crayonred", "crayonorange", "crayonyellow", "crayongreen", "crayonblue", "crayonpurple")) - switch(icon_state) - if("crayonred") - colour = "#DA0000" - colourName = "red" - if("crayonorange") - colour = "#FF9300" - colourName = "orange" - if("crayonyellow") - colour = "#FFF200" - colourName = "yellow" - if("crayongreen") - colour = "#A8E61D" - colourName = "green" - if("crayonblue") - colour = "#00B7EF" - colourName = "blue" - if("crayonpurple") - colour = "#DA00FF" - colourName = "purple" - ..() - -/obj/item/toy/crayon/white - icon_state = "crayonwhite" - colour = "#FFFFFF" - colourName = "white" - -/obj/item/toy/crayon/mime - icon_state = "crayonmime" - desc = "A very sad-looking crayon." - colour = "#FFFFFF" - colourName = "mime" - uses = 0 - -/obj/item/toy/crayon/mime/attack_self(mob/living/user as mob) - update_window(user) - -/obj/item/toy/crayon/mime/update_window(mob/living/user as mob) - dat += "
           Change color
        " - ..() - -/obj/item/toy/crayon/mime/Topic(href,href_list) - if(!Adjacent(usr) || usr.incapacitated()) - return - if(href_list["color"]) - if(colour != "#FFFFFF") - colour = "#FFFFFF" - else - colour = "#000000" - update_window(usr) - else - ..() - -/obj/item/toy/crayon/rainbow - icon_state = "crayonrainbow" - colour = "#FFF000" - colourName = "rainbow" - uses = 0 - -/obj/item/toy/crayon/rainbow/attack_self(mob/living/user as mob) - update_window(user) - -/obj/item/toy/crayon/rainbow/update_window(mob/living/user as mob) - dat += "
           Change color
        " - ..() - -/obj/item/toy/crayon/rainbow/Topic(href,href_list[]) - if(!Adjacent(usr) || usr.incapacitated()) - return - if(href_list["color"]) - var/temp = input(usr, "Please select colour.", "Crayon colour") as color - colour = temp - update_window(usr) - else - ..() - - -//Spraycan stuff - -/obj/item/toy/crayon/spraycan - icon_state = "spraycan_cap" - desc = "A metallic container containing tasty paint." - var/capped = 1 - instant = 1 - validSurfaces = list(/turf/simulated/floor,/turf/simulated/wall) - -/obj/item/toy/crayon/spraycan/New() - ..() - name = "Nanotrasen-brand Rapid Paint Applicator" - update_icon() - -/obj/item/toy/crayon/spraycan/attack_self(mob/living/user as mob) - var/choice = input(user,"Spraycan options") in list("Toggle Cap","Change Drawing","Change Color") - switch(choice) - if("Toggle Cap") - to_chat(user, "You [capped ? "Remove" : "Replace"] the cap of the [src]") - capped = capped ? 0 : 1 - icon_state = "spraycan[capped ? "_cap" : ""]" - update_icon() - if("Change Drawing") - ..() - if("Change Color") - colour = input(user,"Choose Color") as color - update_icon() - -/obj/item/toy/crayon/spraycan/afterattack(atom/target, mob/user as mob, proximity) - if(!proximity) - return - if(capped) - return - else - if(iscarbon(target)) - if(uses-10 > 0) - uses = uses - 10 - var/mob/living/carbon/human/C = target - user.visible_message(" [user] sprays [src] into the face of [target]!") - if(C.client) - C.EyeBlurry(3) - C.EyeBlind(1) - if(C.check_eye_prot() <= 0) // no eye protection? ARGH IT BURNS. - C.Confused(3) - C.Weaken(3) - C.lip_style = "spray_face" - C.lip_color = colour - C.update_body() - playsound(user.loc, 'sound/effects/spray.ogg', 5, 1, 5) - ..() - -/obj/item/toy/crayon/spraycan/update_icon() - overlays.Cut() - var/image/I = image('icons/obj/crayons.dmi',icon_state = "[capped ? "spraycan_cap_colors" : "spraycan_colors"]") - I.color = colour - overlays += I +/* + * Crayons + */ + +/obj/item/toy/crayon + name = "crayon" + desc = "A colourful crayon. Looks tasty. Mmmm..." + icon = 'icons/obj/crayons.dmi' + icon_state = "crayonred" + w_class = WEIGHT_CLASS_TINY + slot_flags = SLOT_BELT | SLOT_EARS + attack_verb = list("attacked", "coloured") + toolspeed = 1 + var/colour = "#FF0000" //RGB + var/drawtype = "rune" + var/list/graffiti = list("body","amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","up","down","left","right","heart","borgsrogue","voxpox","shitcurity","catbeast","hieroglyphs1","hieroglyphs2","hieroglyphs3","security","syndicate1","syndicate2","nanotrasen","lie","valid","arrowleft","arrowright","arrowup","arrowdown","chicken","hailcrab","brokenheart","peace","scribble","scribble2","scribble3","skrek","squish","tunnelsnake","yip","youaredead") + var/list/letters = list("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z") + var/uses = 30 //0 for unlimited uses + var/instant = 0 + var/colourName = "red" //for updateIcon purposes + var/dat + var/busy = FALSE + var/list/validSurfaces = list(/turf/simulated/floor) + +/obj/item/toy/crayon/suicide_act(mob/user) + user.visible_message("[user] is jamming the [name] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide.") + return BRUTELOSS|OXYLOSS + +/obj/item/toy/crayon/New() + ..() + name = "[colourName] crayon" //Makes crayons identifiable in things like grinders + drawtype = pick(pick(graffiti), pick(letters), "rune[rand(1,10)]") + +/obj/item/toy/crayon/attack_self(mob/living/user as mob) + update_window(user) + +/obj/item/toy/crayon/proc/update_window(mob/living/user as mob) + dat += "

        Currently selected: [drawtype]


        " + dat += "Random letterPick letter" + dat += "
        " + dat += "

        Runes:


        " + dat += "Random rune" + for(var/i = 1; i <= 10; i++) + dat += "Rune[i]" + if(!((i + 1) % 3)) //3 buttons in a row + dat += "
        " + dat += "
        " + graffiti.Find() + dat += "

        Graffiti:


        " + dat += "Random graffiti" + var/c = 1 + for(var/T in graffiti) + dat += "[T]" + if(!((c + 1) % 3)) //3 buttons in a row + dat += "
        " + c++ + dat += "
        " + var/datum/browser/popup = new(user, "crayon", name, 300, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + dat = "" + +/obj/item/toy/crayon/Topic(href, href_list, hsrc) + var/temp = "a" + switch(href_list["type"]) + if("random_letter") + temp = pick(letters) + if("letter") + temp = input("Choose the letter.", "Scribbles") in letters + if("random_rune") + temp = "rune[rand(1,10)]" + if("random_graffiti") + temp = pick(graffiti) + else + temp = href_list["type"] + if((usr.restrained() || usr.stat || !usr.is_in_active_hand(src))) + return + drawtype = temp + update_window(usr) + +/obj/item/toy/crayon/afterattack(atom/target, mob/user, proximity) + if(!proximity) return + if(busy) return + if(is_type_in_list(target,validSurfaces)) + var/temp = "rune" + if(letters.Find(drawtype)) + temp = "letter" + else if(graffiti.Find(drawtype)) + temp = "graffiti" + to_chat(user, "You start drawing a [temp] on the [target.name].") + busy = TRUE + if(instant || do_after(user, 50 * toolspeed, target = target)) + var/obj/effect/decal/cleanable/crayon/C = new /obj/effect/decal/cleanable/crayon(target,colour,drawtype,temp) + C.add_hiddenprint(user) + to_chat(user, "You finish drawing [temp].") + if(uses) + uses-- + if(!uses) + to_chat(user, "You used up your [name]!") + qdel(src) + busy = FALSE + +/obj/item/toy/crayon/attack(mob/M, mob/user) + var/huffable = istype(src,/obj/item/toy/crayon/spraycan) + if(M == user) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(!H.check_has_mouth()) + to_chat(user, "You do not have a mouth!") + return + playsound(loc, 'sound/items/eatfood.ogg', 50, 0) + to_chat(user, "You take a [huffable ? "huff" : "bite"] of the [name]. Delicious!") + user.adjust_nutrition(5) + if(uses) + uses -= 5 + if(uses <= 0) + to_chat(user, "There is no more of [name] left!") + qdel(src) + else + ..() + + +/obj/item/toy/crayon/red + icon_state = "crayonred" + colour = "#DA0000" + colourName = "red" + +/obj/item/toy/crayon/orange + icon_state = "crayonorange" + colour = "#FF9300" + colourName = "orange" + +/obj/item/toy/crayon/yellow + icon_state = "crayonyellow" + colour = "#FFF200" + colourName = "yellow" + +/obj/item/toy/crayon/green + icon_state = "crayongreen" + colour = "#A8E61D" + colourName = "green" + +/obj/item/toy/crayon/blue + icon_state = "crayonblue" + colour = "#00B7EF" + colourName = "blue" + +/obj/item/toy/crayon/purple + icon_state = "crayonpurple" + colour = "#DA00FF" + colourName = "purple" + +/obj/item/toy/crayon/random/New() + icon_state = pick(list("crayonred", "crayonorange", "crayonyellow", "crayongreen", "crayonblue", "crayonpurple")) + switch(icon_state) + if("crayonred") + colour = "#DA0000" + colourName = "red" + if("crayonorange") + colour = "#FF9300" + colourName = "orange" + if("crayonyellow") + colour = "#FFF200" + colourName = "yellow" + if("crayongreen") + colour = "#A8E61D" + colourName = "green" + if("crayonblue") + colour = "#00B7EF" + colourName = "blue" + if("crayonpurple") + colour = "#DA00FF" + colourName = "purple" + ..() + +/obj/item/toy/crayon/white + icon_state = "crayonwhite" + colour = "#FFFFFF" + colourName = "white" + +/obj/item/toy/crayon/mime + icon_state = "crayonmime" + desc = "A very sad-looking crayon." + colour = "#FFFFFF" + colourName = "mime" + uses = 0 + +/obj/item/toy/crayon/mime/attack_self(mob/living/user as mob) + update_window(user) + +/obj/item/toy/crayon/mime/update_window(mob/living/user as mob) + dat += "
           Change color
        " + ..() + +/obj/item/toy/crayon/mime/Topic(href,href_list) + if(!Adjacent(usr) || usr.incapacitated()) + return + if(href_list["color"]) + if(colour != "#FFFFFF") + colour = "#FFFFFF" + else + colour = "#000000" + update_window(usr) + else + ..() + +/obj/item/toy/crayon/rainbow + icon_state = "crayonrainbow" + colour = "#FFF000" + colourName = "rainbow" + uses = 0 + +/obj/item/toy/crayon/rainbow/attack_self(mob/living/user as mob) + update_window(user) + +/obj/item/toy/crayon/rainbow/update_window(mob/living/user as mob) + dat += "
           Change color
        " + ..() + +/obj/item/toy/crayon/rainbow/Topic(href,href_list[]) + if(!Adjacent(usr) || usr.incapacitated()) + return + if(href_list["color"]) + var/temp = input(usr, "Please select colour.", "Crayon colour") as color + colour = temp + update_window(usr) + else + ..() + + +//Spraycan stuff + +/obj/item/toy/crayon/spraycan + icon_state = "spraycan_cap" + desc = "A metallic container containing tasty paint." + var/capped = 1 + instant = 1 + validSurfaces = list(/turf/simulated/floor,/turf/simulated/wall) + +/obj/item/toy/crayon/spraycan/New() + ..() + name = "Nanotrasen-brand Rapid Paint Applicator" + update_icon() + +/obj/item/toy/crayon/spraycan/attack_self(mob/living/user as mob) + var/choice = input(user,"Spraycan options") in list("Toggle Cap","Change Drawing","Change Color") + switch(choice) + if("Toggle Cap") + to_chat(user, "You [capped ? "Remove" : "Replace"] the cap of the [src]") + capped = capped ? 0 : 1 + icon_state = "spraycan[capped ? "_cap" : ""]" + update_icon() + if("Change Drawing") + ..() + if("Change Color") + colour = input(user,"Choose Color") as color + update_icon() + +/obj/item/toy/crayon/spraycan/afterattack(atom/target, mob/user as mob, proximity) + if(!proximity) + return + if(capped) + return + else + if(iscarbon(target)) + if(uses-10 > 0) + uses = uses - 10 + var/mob/living/carbon/human/C = target + user.visible_message(" [user] sprays [src] into the face of [target]!") + if(C.client) + C.EyeBlurry(3) + C.EyeBlind(1) + if(C.check_eye_prot() <= 0) // no eye protection? ARGH IT BURNS. + C.Confused(3) + C.Weaken(3) + C.lip_style = "spray_face" + C.lip_color = colour + C.update_body() + playsound(user.loc, 'sound/effects/spray.ogg', 5, 1, 5) + ..() + +/obj/item/toy/crayon/spraycan/update_icon() + overlays.Cut() + var/image/I = image('icons/obj/crayons.dmi',icon_state = "[capped ? "spraycan_cap_colors" : "spraycan_colors"]") + I.color = colour + overlays += I diff --git a/code/game/objects/items/dehy_carp.dm b/code/game/objects/items/dehy_carp.dm index 2e25dd03ee1..1c30c4c5184 100644 --- a/code/game/objects/items/dehy_carp.dm +++ b/code/game/objects/items/dehy_carp.dm @@ -49,4 +49,4 @@ var/mob/living/simple_animal/hostile/carp/C = new /mob/living/simple_animal/hostile/carp(get_turf(src)) // Make carp non-hostile to user, yes this means C.faction |= list("syndicate", "\ref[owner]") - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index ce8e0fe1ce0..60c005de0ed 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -1,107 +1,107 @@ -/obj/item/aicard - name = "inteliCard" - icon = 'icons/obj/aicards.dmi' - icon_state = "aicard" // aicard-full - item_state = "electronic" - w_class = WEIGHT_CLASS_SMALL - slot_flags = SLOT_BELT - flags = NOBLUDGEON - var/flush = null - origin_tech = "programming=3;materials=3" - - -/obj/item/aicard/afterattack(atom/target, mob/user, proximity) - ..() - if(!proximity || !target) - return - var/mob/living/silicon/ai/AI = locate(/mob/living/silicon/ai) in src - if(AI) //AI is on the card, implies user wants to upload it. - target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src) - add_attack_logs(user, AI, "Carded with [src]") - else //No AI on the card, therefore the user wants to download one. - target.transfer_ai(AI_TRANS_TO_CARD, user, null, src) - update_state() //Whatever happened, update the card's state (icon, name) to match. - -/obj/item/aicard/proc/update_state() - var/mob/living/silicon/ai/AI = locate(/mob/living/silicon/ai) in src //AI is inside. - if(AI) - name = "intelliCard - [AI.name]" - if(AI.stat == DEAD) - icon_state = "aicard-404" - else - icon_state = "aicard-full" - AI.cancel_camera() //AI are forced to move when transferred, so do this whenver one is downloaded. - else - icon_state = "aicard" - name = "intelliCard" - overlays.Cut() - -/obj/item/aicard/attack_self(mob/user) - ui_interact(user) - - -/obj/item/aicard/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = inventory_state) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "aicard.tmpl", "[name]", 600, 400, state = state) - ui.open() - ui.set_auto_update(1) - - -/obj/item/aicard/ui_data(mob/user, ui_key = "main", datum/topic_state/state = inventory_state) - var/data[0] - - var/mob/living/silicon/ai/AI = locate() in src - if(istype(AI)) - data["has_ai"] = 1 - data["name"] = AI.name - data["hardware_integrity"] = ((AI.health + 100) / 2) - data["radio"] = !AI.aiRadio.disabledAi - data["wireless"] = !AI.control_disabled - data["operational"] = AI.stat != DEAD - data["flushing"] = flush - - var/laws[0] - for(var/datum/ai_law/AL in AI.laws.all_laws()) - laws[++laws.len] = list("index" = AL.get_index(), "law" = sanitize(AL.law)) - data["laws"] = laws - data["has_laws"] = laws.len - - return data - - -/obj/item/aicard/Topic(href, href_list, nowindow, state) - if(..()) - return 1 - - var/mob/living/silicon/ai/AI = locate() in src - if(!istype(AI)) - return 1 - - var/user = usr - - if(href_list["wipe"]) - var/confirm = alert("Are you sure you want to wipe this card's memory? This cannot be undone once started.", "Confirm Wipe", "Yes", "No") - if(confirm == "Yes" && (CanUseTopic(user, state) == STATUS_INTERACTIVE)) - msg_admin_attack("[key_name_admin(user)] wiped [key_name_admin(AI)] with \the [src].", ATKLOG_FEW) - add_attack_logs(user, AI, "Wiped with [src].") - flush = 1 - AI.suiciding = 1 - to_chat(AI, "Your core files are being wiped!") - while(AI && AI.stat != DEAD) - AI.adjustOxyLoss(2) - sleep(10) - flush = 0 - - if(href_list["radio"]) - AI.aiRadio.disabledAi = text2num(href_list["radio"]) - to_chat(AI, "Your Subspace Transceiver has been [AI.aiRadio.disabledAi ? "disabled" : "enabled"]!") - to_chat(user, "You [AI.aiRadio.disabledAi ? "disable" : "enable"] the AI's Subspace Transceiver.") - - if(href_list["wireless"]) - AI.control_disabled = text2num(href_list["wireless"]) - to_chat(AI, "Your wireless interface has been [AI.control_disabled ? "disabled" : "enabled"]!") - to_chat(user, "You [AI.control_disabled ? "disable" : "enable"] the AI's wireless interface.") - update_icon() - - return 1 \ No newline at end of file +/obj/item/aicard + name = "inteliCard" + icon = 'icons/obj/aicards.dmi' + icon_state = "aicard" // aicard-full + item_state = "electronic" + w_class = WEIGHT_CLASS_SMALL + slot_flags = SLOT_BELT + flags = NOBLUDGEON + var/flush = null + origin_tech = "programming=3;materials=3" + + +/obj/item/aicard/afterattack(atom/target, mob/user, proximity) + ..() + if(!proximity || !target) + return + var/mob/living/silicon/ai/AI = locate(/mob/living/silicon/ai) in src + if(AI) //AI is on the card, implies user wants to upload it. + target.transfer_ai(AI_TRANS_FROM_CARD, user, AI, src) + add_attack_logs(user, AI, "Carded with [src]") + else //No AI on the card, therefore the user wants to download one. + target.transfer_ai(AI_TRANS_TO_CARD, user, null, src) + update_state() //Whatever happened, update the card's state (icon, name) to match. + +/obj/item/aicard/proc/update_state() + var/mob/living/silicon/ai/AI = locate(/mob/living/silicon/ai) in src //AI is inside. + if(AI) + name = "intelliCard - [AI.name]" + if(AI.stat == DEAD) + icon_state = "aicard-404" + else + icon_state = "aicard-full" + AI.cancel_camera() //AI are forced to move when transferred, so do this whenver one is downloaded. + else + icon_state = "aicard" + name = "intelliCard" + overlays.Cut() + +/obj/item/aicard/attack_self(mob/user) + ui_interact(user) + + +/obj/item/aicard/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = inventory_state) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "aicard.tmpl", "[name]", 600, 400, state = state) + ui.open() + ui.set_auto_update(1) + + +/obj/item/aicard/ui_data(mob/user, ui_key = "main", datum/topic_state/state = inventory_state) + var/data[0] + + var/mob/living/silicon/ai/AI = locate() in src + if(istype(AI)) + data["has_ai"] = 1 + data["name"] = AI.name + data["hardware_integrity"] = ((AI.health + 100) / 2) + data["radio"] = !AI.aiRadio.disabledAi + data["wireless"] = !AI.control_disabled + data["operational"] = AI.stat != DEAD + data["flushing"] = flush + + var/laws[0] + for(var/datum/ai_law/AL in AI.laws.all_laws()) + laws[++laws.len] = list("index" = AL.get_index(), "law" = sanitize(AL.law)) + data["laws"] = laws + data["has_laws"] = laws.len + + return data + + +/obj/item/aicard/Topic(href, href_list, nowindow, state) + if(..()) + return 1 + + var/mob/living/silicon/ai/AI = locate() in src + if(!istype(AI)) + return 1 + + var/user = usr + + if(href_list["wipe"]) + var/confirm = alert("Are you sure you want to wipe this card's memory? This cannot be undone once started.", "Confirm Wipe", "Yes", "No") + if(confirm == "Yes" && (CanUseTopic(user, state) == STATUS_INTERACTIVE)) + msg_admin_attack("[key_name_admin(user)] wiped [key_name_admin(AI)] with \the [src].", ATKLOG_FEW) + add_attack_logs(user, AI, "Wiped with [src].") + flush = 1 + AI.suiciding = 1 + to_chat(AI, "Your core files are being wiped!") + while(AI && AI.stat != DEAD) + AI.adjustOxyLoss(2) + sleep(10) + flush = 0 + + if(href_list["radio"]) + AI.aiRadio.disabledAi = text2num(href_list["radio"]) + to_chat(AI, "Your Subspace Transceiver has been [AI.aiRadio.disabledAi ? "disabled" : "enabled"]!") + to_chat(user, "You [AI.aiRadio.disabledAi ? "disable" : "enable"] the AI's Subspace Transceiver.") + + if(href_list["wireless"]) + AI.control_disabled = text2num(href_list["wireless"]) + to_chat(AI, "Your wireless interface has been [AI.control_disabled ? "disabled" : "enabled"]!") + to_chat(user, "You [AI.control_disabled ? "disable" : "enable"] the AI's wireless interface.") + update_icon() + + return 1 diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm index be58a9dc12a..97c5817f05b 100644 --- a/code/game/objects/items/devices/chameleonproj.dm +++ b/code/game/objects/items/devices/chameleonproj.dm @@ -1,258 +1,258 @@ -/obj/item/chameleon - name = "chameleon-projector" - icon = 'icons/obj/device.dmi' - icon_state = "shield0" - flags = CONDUCT - slot_flags = SLOT_BELT - item_state = "electronic" - throwforce = 5.0 - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - origin_tech = "syndicate=4;magnets=4" - var/can_use = 1 - var/obj/effect/dummy/chameleon/active_dummy = null - var/saved_item = /obj/item/cigbutt - var/saved_icon = 'icons/obj/clothing/masks.dmi' - var/saved_icon_state = "cigbutt" - var/saved_overlays = null - var/saved_underlays = null - -/obj/item/chameleon/dropped() - disrupt() - -/obj/item/chameleon/equipped() - disrupt() - -/obj/item/chameleon/attack_self() - toggle() - -/obj/item/chameleon/afterattack(atom/target, mob/user , proximity) - if(!proximity) return - if(!active_dummy) - if(istype(target,/obj/item) && !istype(target, /obj/item/disk/nuclear)) - playsound(get_turf(src), 'sound/weapons/flash.ogg', 100, 1, -6) - to_chat(user, "Scanned [target].") - saved_item = target.type - saved_icon = target.icon - saved_icon_state = target.icon_state - saved_overlays = target.overlays - saved_underlays = target.underlays - -/obj/item/chameleon/proc/toggle() - if(!can_use || !saved_item) return - if(active_dummy) - eject_all() - playsound(get_turf(src), 'sound/effects/pop.ogg', 100, 1, -6) - QDEL_NULL(active_dummy) - to_chat(usr, "You deactivate [src].") - var/obj/effect/overlay/T = new/obj/effect/overlay(get_turf(src)) - T.icon = 'icons/effects/effects.dmi' - flick("emppulse",T) - spawn(8) - qdel(T) - else - playsound(get_turf(src), 'sound/effects/pop.ogg', 100, 1, -6) - var/obj/O = new saved_item(src) - if(!O) return - var/obj/effect/dummy/chameleon/C = new/obj/effect/dummy/chameleon(usr.loc) - C.activate(O, usr, saved_icon, saved_icon_state, saved_overlays, saved_underlays, src) - qdel(O) - to_chat(usr, "You activate [src].") - var/obj/effect/overlay/T = new/obj/effect/overlay(get_turf(src)) - T.icon = 'icons/effects/effects.dmi' - flick("emppulse",T) - spawn(8) - qdel(T) - -/obj/item/chameleon/proc/disrupt(var/delete_dummy = 1) - if(active_dummy) - do_sparks(5, 0, src) - eject_all() - if(delete_dummy) - qdel(active_dummy) - active_dummy = null - can_use = 0 - spawn(50) can_use = 1 - -/obj/item/chameleon/proc/eject_all() - for(var/atom/movable/A in active_dummy) - A.loc = active_dummy.loc - if(ismob(A)) - var/mob/M = A - M.reset_perspective(null) - -/obj/effect/dummy/chameleon - name = "" - desc = "" - density = 0 - anchored = 1 - var/can_move = 1 - var/obj/item/chameleon/master = null - -/obj/effect/dummy/chameleon/proc/activate(var/obj/O, var/mob/M, new_icon, new_iconstate, new_overlays, new_underlays, var/obj/item/chameleon/C) - name = O.name - desc = O.desc - icon = new_icon - icon_state = new_iconstate - overlays = new_overlays - underlays = new_underlays - dir = O.dir - M.loc = src - master = C - master.active_dummy = src - -/obj/effect/dummy/chameleon/attackby() - for(var/mob/M in src) - to_chat(M, "Your chameleon-projector deactivates.") - master.disrupt() - -/obj/effect/dummy/chameleon/attack_hand() - for(var/mob/M in src) - to_chat(M, "Your chameleon-projector deactivates.") - master.disrupt() - -/obj/effect/dummy/chameleon/attack_animal() - master.disrupt() - -/obj/effect/dummy/chameleon/attack_slime() - master.disrupt() - -/obj/effect/dummy/chameleon/attack_alien() - master.disrupt() - -/obj/effect/dummy/chameleon/ex_act(severity) //no longer bomb-proof - for(var/mob/M in src) - to_chat(M, "Your chameleon-projector deactivates.") - spawn() - M.ex_act(severity) - master.disrupt() - -/obj/effect/dummy/chameleon/bullet_act() - for(var/mob/M in src) - to_chat(M, "Your chameleon-projector deactivates.") - ..() - master.disrupt() - -/obj/effect/dummy/chameleon/relaymove(var/mob/user, direction) - if(istype(loc, /turf/space) || !direction) - return //No magical space movement! - - if(can_move) - can_move = 0 - switch(user.bodytemperature) - if(300 to INFINITY) - spawn(10) can_move = 1 - if(295 to 300) - spawn(13) can_move = 1 - if(280 to 295) - spawn(16) can_move = 1 - if(260 to 280) - spawn(20) can_move = 1 - else - spawn(25) can_move = 1 - step(src, direction) - return - -/obj/effect/dummy/chameleon/Destroy() - master.disrupt(0) - return ..() - -/obj/item/borg_chameleon - name = "cyborg chameleon projector" - icon = 'icons/obj/device.dmi' - icon_state = "shield0" - item_state = "electronic" - w_class = WEIGHT_CLASS_SMALL - var/active = FALSE - var/activationCost = 300 - var/activationUpkeep = 50 - var/disguise = "landmate" - var/mob/living/silicon/robot/syndicate/saboteur/S - -/obj/item/borg_chameleon/Destroy() - if(S) - S.cham_proj = null - return ..() - -/obj/item/borg_chameleon/dropped(mob/user) - . = ..() - disrupt(user) - -/obj/item/borg_chameleon/equipped(mob/user) - . = ..() - disrupt(user) - -/obj/item/borg_chameleon/attack_self(mob/living/silicon/robot/syndicate/saboteur/user) - if(user && user.cell && user.cell.charge > activationCost) - if(isturf(user.loc)) - toggle(user) - else - to_chat(user, "You can't use [src] while inside something!") - else - to_chat(user, "You need at least [activationCost] charge in your cell to use [src]!") - -/obj/item/borg_chameleon/proc/toggle(mob/living/silicon/robot/syndicate/saboteur/user) - if(active) - playsound(src, 'sound/effects/pop.ogg', 100, 1, -6) - to_chat(user, "You deactivate [src].") - deactivate(user) - else - to_chat(user, "You activate [src].") - var/start = user.filters.len - var/X - var/Y - var/rsq - var/i - var/f - for(i in 1 to 7) - do - X = 60 * rand() - 30 - Y = 60 * rand() - 30 - rsq = X * X + Y * Y - while(rsq < 100 || rsq > 900) - user.filters += filter(type = "wave", x = X, y = Y, size = rand() * 2.5 + 0.5, offset = rand()) - for(i in 1 to 7) - f = user.filters[start+i] - animate(f, offset = f:offset, time = 0, loop = 3, flags = ANIMATION_PARALLEL) - animate(offset = f:offset - 1, time = rand() * 20 + 10) - if(do_after(user, 50, target = user) && user.cell.use(activationCost)) - playsound(src, 'sound/effects/bamf.ogg', 100, 1, -6) - to_chat(user, "You are now disguised as a Nanotrasen engineering cyborg.") - activate(user) - else - to_chat(user, "The chameleon field fizzles.") - do_sparks(3, FALSE, user) - for(i in 1 to min(7, user.filters.len)) // removing filters that are animating does nothing, we gotta stop the animations first - f = user.filters[start + i] - animate(f) - user.filters = null - -/obj/item/borg_chameleon/process() - if(S) - if(!S.cell || !S.cell.use(activationUpkeep)) - disrupt(S) - else - return PROCESS_KILL - -/obj/item/borg_chameleon/proc/activate(mob/living/silicon/robot/syndicate/saboteur/user) - START_PROCESSING(SSobj, src) - S = user - user.base_icon = disguise - user.icon_state = disguise - user.cham_proj = src - active = TRUE - user.update_icons() - -/obj/item/borg_chameleon/proc/deactivate(mob/living/silicon/robot/syndicate/saboteur/user) - STOP_PROCESSING(SSobj, src) - S = user - user.base_icon = initial(user.base_icon) - user.icon_state = initial(user.icon_state) - active = FALSE - user.update_icons() - -/obj/item/borg_chameleon/proc/disrupt(mob/living/silicon/robot/syndicate/saboteur/user) - if(active) - to_chat(user, "Your chameleon field deactivates.") - deactivate(user) +/obj/item/chameleon + name = "chameleon-projector" + icon = 'icons/obj/device.dmi' + icon_state = "shield0" + flags = CONDUCT + slot_flags = SLOT_BELT + item_state = "electronic" + throwforce = 5.0 + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + origin_tech = "syndicate=4;magnets=4" + var/can_use = 1 + var/obj/effect/dummy/chameleon/active_dummy = null + var/saved_item = /obj/item/cigbutt + var/saved_icon = 'icons/obj/clothing/masks.dmi' + var/saved_icon_state = "cigbutt" + var/saved_overlays = null + var/saved_underlays = null + +/obj/item/chameleon/dropped() + disrupt() + +/obj/item/chameleon/equipped() + disrupt() + +/obj/item/chameleon/attack_self() + toggle() + +/obj/item/chameleon/afterattack(atom/target, mob/user , proximity) + if(!proximity) return + if(!active_dummy) + if(istype(target,/obj/item) && !istype(target, /obj/item/disk/nuclear)) + playsound(get_turf(src), 'sound/weapons/flash.ogg', 100, 1, -6) + to_chat(user, "Scanned [target].") + saved_item = target.type + saved_icon = target.icon + saved_icon_state = target.icon_state + saved_overlays = target.overlays + saved_underlays = target.underlays + +/obj/item/chameleon/proc/toggle() + if(!can_use || !saved_item) return + if(active_dummy) + eject_all() + playsound(get_turf(src), 'sound/effects/pop.ogg', 100, 1, -6) + QDEL_NULL(active_dummy) + to_chat(usr, "You deactivate [src].") + var/obj/effect/overlay/T = new/obj/effect/overlay(get_turf(src)) + T.icon = 'icons/effects/effects.dmi' + flick("emppulse",T) + spawn(8) + qdel(T) + else + playsound(get_turf(src), 'sound/effects/pop.ogg', 100, 1, -6) + var/obj/O = new saved_item(src) + if(!O) return + var/obj/effect/dummy/chameleon/C = new/obj/effect/dummy/chameleon(usr.loc) + C.activate(O, usr, saved_icon, saved_icon_state, saved_overlays, saved_underlays, src) + qdel(O) + to_chat(usr, "You activate [src].") + var/obj/effect/overlay/T = new/obj/effect/overlay(get_turf(src)) + T.icon = 'icons/effects/effects.dmi' + flick("emppulse",T) + spawn(8) + qdel(T) + +/obj/item/chameleon/proc/disrupt(var/delete_dummy = 1) + if(active_dummy) + do_sparks(5, 0, src) + eject_all() + if(delete_dummy) + qdel(active_dummy) + active_dummy = null + can_use = 0 + spawn(50) can_use = 1 + +/obj/item/chameleon/proc/eject_all() + for(var/atom/movable/A in active_dummy) + A.loc = active_dummy.loc + if(ismob(A)) + var/mob/M = A + M.reset_perspective(null) + +/obj/effect/dummy/chameleon + name = "" + desc = "" + density = 0 + anchored = 1 + var/can_move = 1 + var/obj/item/chameleon/master = null + +/obj/effect/dummy/chameleon/proc/activate(var/obj/O, var/mob/M, new_icon, new_iconstate, new_overlays, new_underlays, var/obj/item/chameleon/C) + name = O.name + desc = O.desc + icon = new_icon + icon_state = new_iconstate + overlays = new_overlays + underlays = new_underlays + dir = O.dir + M.loc = src + master = C + master.active_dummy = src + +/obj/effect/dummy/chameleon/attackby() + for(var/mob/M in src) + to_chat(M, "Your chameleon-projector deactivates.") + master.disrupt() + +/obj/effect/dummy/chameleon/attack_hand() + for(var/mob/M in src) + to_chat(M, "Your chameleon-projector deactivates.") + master.disrupt() + +/obj/effect/dummy/chameleon/attack_animal() + master.disrupt() + +/obj/effect/dummy/chameleon/attack_slime() + master.disrupt() + +/obj/effect/dummy/chameleon/attack_alien() + master.disrupt() + +/obj/effect/dummy/chameleon/ex_act(severity) //no longer bomb-proof + for(var/mob/M in src) + to_chat(M, "Your chameleon-projector deactivates.") + spawn() + M.ex_act(severity) + master.disrupt() + +/obj/effect/dummy/chameleon/bullet_act() + for(var/mob/M in src) + to_chat(M, "Your chameleon-projector deactivates.") + ..() + master.disrupt() + +/obj/effect/dummy/chameleon/relaymove(var/mob/user, direction) + if(istype(loc, /turf/space) || !direction) + return //No magical space movement! + + if(can_move) + can_move = 0 + switch(user.bodytemperature) + if(300 to INFINITY) + spawn(10) can_move = 1 + if(295 to 300) + spawn(13) can_move = 1 + if(280 to 295) + spawn(16) can_move = 1 + if(260 to 280) + spawn(20) can_move = 1 + else + spawn(25) can_move = 1 + step(src, direction) + return + +/obj/effect/dummy/chameleon/Destroy() + master.disrupt(0) + return ..() + +/obj/item/borg_chameleon + name = "cyborg chameleon projector" + icon = 'icons/obj/device.dmi' + icon_state = "shield0" + item_state = "electronic" + w_class = WEIGHT_CLASS_SMALL + var/active = FALSE + var/activationCost = 300 + var/activationUpkeep = 50 + var/disguise = "landmate" + var/mob/living/silicon/robot/syndicate/saboteur/S + +/obj/item/borg_chameleon/Destroy() + if(S) + S.cham_proj = null + return ..() + +/obj/item/borg_chameleon/dropped(mob/user) + . = ..() + disrupt(user) + +/obj/item/borg_chameleon/equipped(mob/user) + . = ..() + disrupt(user) + +/obj/item/borg_chameleon/attack_self(mob/living/silicon/robot/syndicate/saboteur/user) + if(user && user.cell && user.cell.charge > activationCost) + if(isturf(user.loc)) + toggle(user) + else + to_chat(user, "You can't use [src] while inside something!") + else + to_chat(user, "You need at least [activationCost] charge in your cell to use [src]!") + +/obj/item/borg_chameleon/proc/toggle(mob/living/silicon/robot/syndicate/saboteur/user) + if(active) + playsound(src, 'sound/effects/pop.ogg', 100, 1, -6) + to_chat(user, "You deactivate [src].") + deactivate(user) + else + to_chat(user, "You activate [src].") + var/start = user.filters.len + var/X + var/Y + var/rsq + var/i + var/f + for(i in 1 to 7) + do + X = 60 * rand() - 30 + Y = 60 * rand() - 30 + rsq = X * X + Y * Y + while(rsq < 100 || rsq > 900) + user.filters += filter(type = "wave", x = X, y = Y, size = rand() * 2.5 + 0.5, offset = rand()) + for(i in 1 to 7) + f = user.filters[start+i] + animate(f, offset = f:offset, time = 0, loop = 3, flags = ANIMATION_PARALLEL) + animate(offset = f:offset - 1, time = rand() * 20 + 10) + if(do_after(user, 50, target = user) && user.cell.use(activationCost)) + playsound(src, 'sound/effects/bamf.ogg', 100, 1, -6) + to_chat(user, "You are now disguised as a Nanotrasen engineering cyborg.") + activate(user) + else + to_chat(user, "The chameleon field fizzles.") + do_sparks(3, FALSE, user) + for(i in 1 to min(7, user.filters.len)) // removing filters that are animating does nothing, we gotta stop the animations first + f = user.filters[start + i] + animate(f) + user.filters = null + +/obj/item/borg_chameleon/process() + if(S) + if(!S.cell || !S.cell.use(activationUpkeep)) + disrupt(S) + else + return PROCESS_KILL + +/obj/item/borg_chameleon/proc/activate(mob/living/silicon/robot/syndicate/saboteur/user) + START_PROCESSING(SSobj, src) + S = user + user.base_icon = disguise + user.icon_state = disguise + user.cham_proj = src + active = TRUE + user.update_icons() + +/obj/item/borg_chameleon/proc/deactivate(mob/living/silicon/robot/syndicate/saboteur/user) + STOP_PROCESSING(SSobj, src) + S = user + user.base_icon = initial(user.base_icon) + user.icon_state = initial(user.icon_state) + active = FALSE + user.update_icons() + +/obj/item/borg_chameleon/proc/disrupt(mob/living/silicon/robot/syndicate/saboteur/user) + if(active) + to_chat(user, "Your chameleon field deactivates.") + deactivate(user) diff --git a/code/game/objects/items/devices/enginepicker.dm b/code/game/objects/items/devices/enginepicker.dm index e28b5b3bef5..fe6bb7e8717 100644 --- a/code/game/objects/items/devices/enginepicker.dm +++ b/code/game/objects/items/devices/enginepicker.dm @@ -93,4 +93,4 @@ for(var/mob/living/M in T) M.visible_message("\The [M] gets obliterated!") - M.gib() \ No newline at end of file + M.gib() diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm index a618383a819..af673c7b285 100644 --- a/code/game/objects/items/devices/flash.dm +++ b/code/game/objects/items/devices/flash.dm @@ -1,281 +1,281 @@ -/obj/item/flash - name = "flash" - desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production." - icon = 'icons/obj/device.dmi' - icon_state = "flash" - item_state = "flashtool" //looks exactly like a flash (and nothing like a flashbang) - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - flags = CONDUCT - materials = list(MAT_METAL = 300, MAT_GLASS = 300) - origin_tech = "magnets=2;combat=1" - - var/times_used = 0 //Number of times it's been used. - var/broken = 0 //Is the flash burnt out? - var/last_used = 0 //last world.time it was used. - var/battery_panel = 0 //whether the flash can be modified with a cell or not - var/overcharged = 0 //if overcharged the flash will set people on fire then immediately burn out (does so even if it doesn't blind them). - var/can_overcharge = TRUE //set this to FALSE if you don't want your flash to be overcharge capable - var/use_sound = 'sound/weapons/flash.ogg' - -/obj/item/flash/proc/clown_check(mob/user) - if(user && (CLUMSY in user.mutations) && prob(50)) - flash_carbon(user, user, 15, 0) - return 0 - return 1 - -/obj/item/flash/attackby(obj/item/W, mob/user, params) - if(can_overcharge) - if(istype(W, /obj/item/screwdriver)) - if(battery_panel) - to_chat(user, "You close the battery compartment on the [src].") - battery_panel = 0 - else - to_chat(user, "You open the battery compartment on the [src].") - battery_panel = 1 - if(battery_panel && !overcharged) - if(istype(W, /obj/item/stock_parts/cell)) - to_chat(user, "You jam the cell into battery compartment on the [src].") - qdel(W) - overcharged = 1 - overlays += "overcharge" - -/obj/item/flash/random/New() - ..() - if(prob(25)) - broken = 1 - icon_state = "[initial(icon_state)]burnt" - -/obj/item/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something. - broken = 1 - icon_state = "[initial(icon_state)]burnt" - visible_message("The [src.name] burns out!") - - -/obj/item/flash/proc/flash_recharge(var/mob/user) - if(prob(times_used * 2)) //if you use it 5 times in a minute it has a 10% chance to break! - burn_out() - return 0 - - var/deciseconds_passed = world.time - last_used - for(var/seconds = deciseconds_passed/10, seconds>=10, seconds-=10) //get 1 charge every 10 seconds - times_used-- - - last_used = world.time - times_used = max(0, times_used) //sanity - - -/obj/item/flash/proc/try_use_flash(var/mob/user = null) - flash_recharge(user) - - if(broken) - return 0 - - playsound(src.loc, use_sound, 100, 1) - flick("[initial(icon_state)]2", src) - times_used++ - - if(user && !clown_check(user)) - return 0 - - return 1 - - -/obj/item/flash/proc/flash_carbon(var/mob/living/carbon/M, var/mob/user = null, var/power = 5, targeted = 1) - add_attack_logs(user, M, "Flashed with [src]") - if(user && targeted) - if(M.weakeyes) - M.Weaken(3) //quick weaken bypasses eye protection but has no eye flash - if(M.flash_eyes(1, 1)) - M.AdjustConfused(power) - terrible_conversion_proc(M, user) - M.Stun(1) - visible_message("[user] blinds [M] with the flash!") - to_chat(user, "You blind [M] with the flash!") - to_chat(M, "[user] blinds you with the flash!") - if(M.weakeyes) - M.Stun(2) - M.visible_message("[M] gasps and shields [M.p_their()] eyes!", "You gasp and shields your eyes!") - else - visible_message("[user] fails to blind [M] with the flash!") - to_chat(user, "You fail to blind [M] with the flash!") - to_chat(M, "[user] fails to blind you with the flash!") - else - if(M.flash_eyes()) - M.AdjustConfused(power) - -/obj/item/flash/attack(mob/living/M, mob/user) - if(!try_use_flash(user)) - return 0 - - if(iscarbon(M)) - flash_carbon(M, user, 5, 1) - if(overcharged) - M.adjust_fire_stacks(6) - M.IgniteMob() - burn_out() - return 1 - - else if(issilicon(M)) - if(isrobot(M)) - var/mob/living/silicon/robot/R = M - if(R.module) // Perhaps they didn't choose a module yet - for(var/obj/item/borg/combat/shield/S in R.module.modules) - if(R.activated(S)) - add_attack_logs(user, M, "Flashed with [src]") - user.visible_message("[user] tries to overloads [M]'s sensors with the [src.name], but is blocked by [M]'s shield!", "You try to overload [M]'s sensors with the [src.name], but are blocked by [M.p_their()] shield!") - return 1 - add_attack_logs(user, M, "Flashed with [src]") - if(M.flash_eyes(affect_silicon = 1)) - M.Weaken(rand(5,10)) - user.visible_message("[user] overloads [M]'s sensors with the [src.name]!", "You overload [M]'s sensors with the [src.name]!") - return 1 - - user.visible_message("[user] fails to blind [M] with the [src.name]!", "You fail to blind [M] with the [src.name]!") - - -/obj/item/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0) - if(!try_use_flash(user)) - return 0 - user.visible_message("[user]'s [src.name] emits a blinding light!", "Your [src.name] emits a blinding light!") - for(var/mob/living/carbon/M in oviewers(3, null)) - flash_carbon(M, user, 3, 0) - - -/obj/item/flash/emp_act(severity) - if(!try_use_flash()) - return 0 - for(var/mob/living/carbon/M in viewers(3, null)) - flash_carbon(M, null, 10, 0) - burn_out() - ..() - - -/obj/item/flash/proc/terrible_conversion_proc(mob/M, mob/user) - if(ishuman(M) && ishuman(user) && M.stat != DEAD) - if(user.mind && (user.mind in SSticker.mode.head_revolutionaries)) - if(M.client) - if(M.stat == CONSCIOUS) - M.mind_initialize() //give them a mind datum if they don't have one. - var/resisted - if(!ismindshielded(M)) - if(user.mind in SSticker.mode.head_revolutionaries) - if(SSticker.mode.add_revolutionary(M.mind)) - times_used -- //Flashes less likely to burn out for headrevs when used for conversion - else - resisted = 1 - else - resisted = 1 - - if(resisted) - to_chat(user, "This mind seems resistant to the [name]!") - else - to_chat(user, "They must be conscious before you can convert [M.p_them()]!") - else - to_chat(user, "This mind is so vacant that it is not susceptible to influence!") - - -/obj/item/flash/cyborg - origin_tech = null - -/obj/item/flash/cyborg/attack(mob/living/M, mob/user) - ..() - new /obj/effect/temp_visual/borgflash(get_turf(src)) - -/obj/item/flash/cyborg/attack_self(mob/user) - ..() - new /obj/effect/temp_visual/borgflash(get_turf(src)) - -/obj/item/flash/cameraflash - name = "camera" - icon = 'icons/obj/items.dmi' - desc = "A polaroid camera. 10 photos left." - icon_state = "camera" - item_state = "electropack" //spelling, a coders worst enemy. This part gave me trouble for a while. - w_class = WEIGHT_CLASS_SMALL - slot_flags = SLOT_BELT - can_overcharge = FALSE - var/flash_max_charges = 5 - var/flash_cur_charges = 5 - var/charge_tick = 0 - use_sound = 'sound/items/polaroid1.ogg' - -/obj/item/flash/cameraflash/burn_out() //stops from burning out - return - -/obj/item/flash/cameraflash/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/flash/cameraflash/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/flash/cameraflash/process() //this and the two parts above are part of the charge system. - charge_tick++ - if(charge_tick < 10) - return FALSE - charge_tick = 0 - flash_cur_charges = min(flash_cur_charges+1, flash_max_charges) - return TRUE - -/obj/item/flash/cameraflash/attack(mob/living/M, mob/user) - if(flash_cur_charges > 0) - flash_cur_charges -= 1 - to_chat(user, "[src] now has [flash_cur_charges] charge\s.") - ..() - else - to_chat(user, "\The [src] needs time to recharge!") - return - -/obj/item/flash/cameraflash/attack_self(mob/living/carbon/user, flag = 0) - if(flash_cur_charges > 0) - flash_cur_charges -= 1 - to_chat(user, "[src] now has [flash_cur_charges] charge\s.") - ..() - else - to_chat(user, "\The [src] needs time to recharge!") - return - -/obj/item/flash/memorizer - name = "memorizer" - desc = "If you see this, you're not likely to remember it any time soon." - icon_state = "memorizer" - item_state = "nullrod" - -/obj/item/flash/armimplant - name = "photon projector" - desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out." - var/flashcd = 20 - var/overheat = 0 - var/obj/item/organ/internal/cyberimp/arm/flash/I = null - -/obj/item/flash/armimplant/Destroy() - I = null - return ..() - -/obj/item/flash/armimplant/burn_out() - if(I && I.owner) - to_chat(I.owner, "Your photon projector implant overheats and deactivates!") - I.Retract() - overheat = FALSE - addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2) - -/obj/item/flash/armimplant/try_use_flash(mob/user = null) - if(overheat) - if(I && I.owner) - to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!") - return FALSE - overheat = TRUE - addtimer(CALLBACK(src, .proc/cooldown), flashcd) - playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1) - update_icon(1) - return TRUE - -/obj/item/flash/armimplant/proc/cooldown() - overheat = FALSE - - -/obj/item/flash/synthetic //just a regular flash now +/obj/item/flash + name = "flash" + desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production." + icon = 'icons/obj/device.dmi' + icon_state = "flash" + item_state = "flashtool" //looks exactly like a flash (and nothing like a flashbang) + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + flags = CONDUCT + materials = list(MAT_METAL = 300, MAT_GLASS = 300) + origin_tech = "magnets=2;combat=1" + + var/times_used = 0 //Number of times it's been used. + var/broken = 0 //Is the flash burnt out? + var/last_used = 0 //last world.time it was used. + var/battery_panel = 0 //whether the flash can be modified with a cell or not + var/overcharged = 0 //if overcharged the flash will set people on fire then immediately burn out (does so even if it doesn't blind them). + var/can_overcharge = TRUE //set this to FALSE if you don't want your flash to be overcharge capable + var/use_sound = 'sound/weapons/flash.ogg' + +/obj/item/flash/proc/clown_check(mob/user) + if(user && (CLUMSY in user.mutations) && prob(50)) + flash_carbon(user, user, 15, 0) + return 0 + return 1 + +/obj/item/flash/attackby(obj/item/W, mob/user, params) + if(can_overcharge) + if(istype(W, /obj/item/screwdriver)) + if(battery_panel) + to_chat(user, "You close the battery compartment on the [src].") + battery_panel = 0 + else + to_chat(user, "You open the battery compartment on the [src].") + battery_panel = 1 + if(battery_panel && !overcharged) + if(istype(W, /obj/item/stock_parts/cell)) + to_chat(user, "You jam the cell into battery compartment on the [src].") + qdel(W) + overcharged = 1 + overlays += "overcharge" + +/obj/item/flash/random/New() + ..() + if(prob(25)) + broken = 1 + icon_state = "[initial(icon_state)]burnt" + +/obj/item/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something. + broken = 1 + icon_state = "[initial(icon_state)]burnt" + visible_message("The [src.name] burns out!") + + +/obj/item/flash/proc/flash_recharge(var/mob/user) + if(prob(times_used * 2)) //if you use it 5 times in a minute it has a 10% chance to break! + burn_out() + return 0 + + var/deciseconds_passed = world.time - last_used + for(var/seconds = deciseconds_passed/10, seconds>=10, seconds-=10) //get 1 charge every 10 seconds + times_used-- + + last_used = world.time + times_used = max(0, times_used) //sanity + + +/obj/item/flash/proc/try_use_flash(var/mob/user = null) + flash_recharge(user) + + if(broken) + return 0 + + playsound(src.loc, use_sound, 100, 1) + flick("[initial(icon_state)]2", src) + times_used++ + + if(user && !clown_check(user)) + return 0 + + return 1 + + +/obj/item/flash/proc/flash_carbon(var/mob/living/carbon/M, var/mob/user = null, var/power = 5, targeted = 1) + add_attack_logs(user, M, "Flashed with [src]") + if(user && targeted) + if(M.weakeyes) + M.Weaken(3) //quick weaken bypasses eye protection but has no eye flash + if(M.flash_eyes(1, 1)) + M.AdjustConfused(power) + terrible_conversion_proc(M, user) + M.Stun(1) + visible_message("[user] blinds [M] with the flash!") + to_chat(user, "You blind [M] with the flash!") + to_chat(M, "[user] blinds you with the flash!") + if(M.weakeyes) + M.Stun(2) + M.visible_message("[M] gasps and shields [M.p_their()] eyes!", "You gasp and shields your eyes!") + else + visible_message("[user] fails to blind [M] with the flash!") + to_chat(user, "You fail to blind [M] with the flash!") + to_chat(M, "[user] fails to blind you with the flash!") + else + if(M.flash_eyes()) + M.AdjustConfused(power) + +/obj/item/flash/attack(mob/living/M, mob/user) + if(!try_use_flash(user)) + return 0 + + if(iscarbon(M)) + flash_carbon(M, user, 5, 1) + if(overcharged) + M.adjust_fire_stacks(6) + M.IgniteMob() + burn_out() + return 1 + + else if(issilicon(M)) + if(isrobot(M)) + var/mob/living/silicon/robot/R = M + if(R.module) // Perhaps they didn't choose a module yet + for(var/obj/item/borg/combat/shield/S in R.module.modules) + if(R.activated(S)) + add_attack_logs(user, M, "Flashed with [src]") + user.visible_message("[user] tries to overloads [M]'s sensors with the [src.name], but is blocked by [M]'s shield!", "You try to overload [M]'s sensors with the [src.name], but are blocked by [M.p_their()] shield!") + return 1 + add_attack_logs(user, M, "Flashed with [src]") + if(M.flash_eyes(affect_silicon = 1)) + M.Weaken(rand(5,10)) + user.visible_message("[user] overloads [M]'s sensors with the [src.name]!", "You overload [M]'s sensors with the [src.name]!") + return 1 + + user.visible_message("[user] fails to blind [M] with the [src.name]!", "You fail to blind [M] with the [src.name]!") + + +/obj/item/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0) + if(!try_use_flash(user)) + return 0 + user.visible_message("[user]'s [src.name] emits a blinding light!", "Your [src.name] emits a blinding light!") + for(var/mob/living/carbon/M in oviewers(3, null)) + flash_carbon(M, user, 3, 0) + + +/obj/item/flash/emp_act(severity) + if(!try_use_flash()) + return 0 + for(var/mob/living/carbon/M in viewers(3, null)) + flash_carbon(M, null, 10, 0) + burn_out() + ..() + + +/obj/item/flash/proc/terrible_conversion_proc(mob/M, mob/user) + if(ishuman(M) && ishuman(user) && M.stat != DEAD) + if(user.mind && (user.mind in SSticker.mode.head_revolutionaries)) + if(M.client) + if(M.stat == CONSCIOUS) + M.mind_initialize() //give them a mind datum if they don't have one. + var/resisted + if(!ismindshielded(M)) + if(user.mind in SSticker.mode.head_revolutionaries) + if(SSticker.mode.add_revolutionary(M.mind)) + times_used -- //Flashes less likely to burn out for headrevs when used for conversion + else + resisted = 1 + else + resisted = 1 + + if(resisted) + to_chat(user, "This mind seems resistant to the [name]!") + else + to_chat(user, "They must be conscious before you can convert [M.p_them()]!") + else + to_chat(user, "This mind is so vacant that it is not susceptible to influence!") + + +/obj/item/flash/cyborg + origin_tech = null + +/obj/item/flash/cyborg/attack(mob/living/M, mob/user) + ..() + new /obj/effect/temp_visual/borgflash(get_turf(src)) + +/obj/item/flash/cyborg/attack_self(mob/user) + ..() + new /obj/effect/temp_visual/borgflash(get_turf(src)) + +/obj/item/flash/cameraflash + name = "camera" + icon = 'icons/obj/items.dmi' + desc = "A polaroid camera. 10 photos left." + icon_state = "camera" + item_state = "electropack" //spelling, a coders worst enemy. This part gave me trouble for a while. + w_class = WEIGHT_CLASS_SMALL + slot_flags = SLOT_BELT + can_overcharge = FALSE + var/flash_max_charges = 5 + var/flash_cur_charges = 5 + var/charge_tick = 0 + use_sound = 'sound/items/polaroid1.ogg' + +/obj/item/flash/cameraflash/burn_out() //stops from burning out + return + +/obj/item/flash/cameraflash/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/flash/cameraflash/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/flash/cameraflash/process() //this and the two parts above are part of the charge system. + charge_tick++ + if(charge_tick < 10) + return FALSE + charge_tick = 0 + flash_cur_charges = min(flash_cur_charges+1, flash_max_charges) + return TRUE + +/obj/item/flash/cameraflash/attack(mob/living/M, mob/user) + if(flash_cur_charges > 0) + flash_cur_charges -= 1 + to_chat(user, "[src] now has [flash_cur_charges] charge\s.") + ..() + else + to_chat(user, "\The [src] needs time to recharge!") + return + +/obj/item/flash/cameraflash/attack_self(mob/living/carbon/user, flag = 0) + if(flash_cur_charges > 0) + flash_cur_charges -= 1 + to_chat(user, "[src] now has [flash_cur_charges] charge\s.") + ..() + else + to_chat(user, "\The [src] needs time to recharge!") + return + +/obj/item/flash/memorizer + name = "memorizer" + desc = "If you see this, you're not likely to remember it any time soon." + icon_state = "memorizer" + item_state = "nullrod" + +/obj/item/flash/armimplant + name = "photon projector" + desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out." + var/flashcd = 20 + var/overheat = 0 + var/obj/item/organ/internal/cyberimp/arm/flash/I = null + +/obj/item/flash/armimplant/Destroy() + I = null + return ..() + +/obj/item/flash/armimplant/burn_out() + if(I && I.owner) + to_chat(I.owner, "Your photon projector implant overheats and deactivates!") + I.Retract() + overheat = FALSE + addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2) + +/obj/item/flash/armimplant/try_use_flash(mob/user = null) + if(overheat) + if(I && I.owner) + to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!") + return FALSE + overheat = TRUE + addtimer(CALLBACK(src, .proc/cooldown), flashcd) + playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1) + update_icon(1) + return TRUE + +/obj/item/flash/armimplant/proc/cooldown() + overheat = FALSE + + +/obj/item/flash/synthetic //just a regular flash now diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index f91cacd6804..4ce64a859ec 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -1,398 +1,398 @@ -/obj/item/flashlight - name = "flashlight" - desc = "A hand-held emergency light." - icon = 'icons/obj/lighting.dmi' - icon_state = "flashlight" - item_state = "flashlight" - w_class = WEIGHT_CLASS_SMALL - flags = CONDUCT - slot_flags = SLOT_BELT - materials = list(MAT_METAL=50, MAT_GLASS=20) - actions_types = list(/datum/action/item_action/toggle_light) - var/on = FALSE - var/brightness_on = 4 //luminosity when on - var/togglesound = 'sound/weapons/empty.ogg' - -/obj/item/flashlight/Initialize() - . = ..() - if(on) - icon_state = "[initial(icon_state)]-on" - set_light(brightness_on) - else - icon_state = initial(icon_state) - set_light(0) - -/obj/item/flashlight/proc/update_brightness(var/mob/user = null) - if(on) - icon_state = "[initial(icon_state)]-on" - set_light(brightness_on) - else - icon_state = initial(icon_state) - set_light(0) - -/obj/item/flashlight/attack_self(mob/user) - if(!isturf(user.loc)) - to_chat(user, "You cannot turn the light on while in this [user.loc].")//To prevent some lighting anomalities. - - return 0 - on = !on - playsound(user, togglesound, 100, 1) - update_brightness(user) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - return 1 - - -/obj/item/flashlight/attack(mob/living/M as mob, mob/living/user as mob) - add_fingerprint(user) - if(on && user.zone_selected == "eyes") - - if(((CLUMSY in user.mutations) || user.getBrainLoss() >= 60) && prob(50)) //too dumb to use flashlight properly - return ..() //just hit them in the head - - if(!(istype(user, /mob/living/carbon/human) || SSticker) && SSticker.mode.name != "monkey") //don't have dexterity - to_chat(user, "You don't have the dexterity to do this!") - return - - var/mob/living/carbon/human/H = M //mob has protective eyewear - if(istype(H) && ((H.head && H.head.flags_cover & HEADCOVERSEYES) || (H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) || (H.glasses && H.glasses.flags_cover & GLASSESCOVERSEYES))) - to_chat(user, "You're going to need to remove that [(H.head && H.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask" : "glasses"] first.") - return - - if(M == user) //they're using it on themselves - if(M.flash_eyes(visual = 1)) - M.visible_message("[M] directs [src] to [M.p_their()] eyes.", \ - "You wave the light in front of your eyes! Trippy!") - else - M.visible_message("[M] directs [src] to [M.p_their()] eyes.", \ - "You wave the light in front of your eyes.") - else - - user.visible_message("[user] directs [src] to [M]'s eyes.", \ - "You direct [src] to [M]'s eyes.") - - if(istype(H)) //robots and aliens are unaffected - var/obj/item/organ/internal/eyes/eyes = H.get_int_organ(/obj/item/organ/internal/eyes) - if(M.stat == DEAD || !eyes || M.disabilities & BLIND) //mob is dead or fully blind - to_chat(user, "[M]'s pupils are unresponsive to the light!") - else if((XRAY in M.mutations) || eyes.see_in_dark >= 8) //The mob's either got the X-RAY vision or has a tapetum lucidum (extreme nightvision, i.e. Vulp/Tajara with COLOURBLIND & their monkey forms). - to_chat(user, "[M]'s pupils glow eerily!") - else //they're okay! - if(M.flash_eyes(visual = 1)) - to_chat(user, "[M]'s pupils narrow.") - else - return ..() - -/obj/item/flashlight/extinguish_light() - if(on) - on = FALSE - update_brightness() - -/obj/item/flashlight/pen - name = "penlight" - desc = "A pen-sized light, used by medical staff." - icon_state = "penlight" - item_state = "" - w_class = WEIGHT_CLASS_TINY - slot_flags = SLOT_BELT | SLOT_EARS - flags = CONDUCT - brightness_on = 2 - -/obj/item/flashlight/seclite - name = "seclite" - desc = "A robust flashlight used by security." - icon_state = "seclite" - item_state = "seclite" - force = 9 // Not as good as a stun baton. - brightness_on = 5 // A little better than the standard flashlight. - hitsound = 'sound/weapons/genhit1.ogg' - -/obj/item/flashlight/drone - name = "low-power flashlight" - desc = "A miniature lamp, that might be used by small robots." - icon_state = "penlight" - item_state = "" - flags = CONDUCT - brightness_on = 2 - w_class = WEIGHT_CLASS_TINY - -// the desk lamps are a bit special -/obj/item/flashlight/lamp - name = "desk lamp" - desc = "A desk lamp with an adjustable mount." - icon_state = "lamp" - item_state = "lamp" - brightness_on = 5 - w_class = WEIGHT_CLASS_BULKY - flags = CONDUCT - materials = list() - on = TRUE - - -// green-shaded desk lamp -/obj/item/flashlight/lamp/green - desc = "A classic green-shaded desk lamp." - icon_state = "lampgreen" - item_state = "lampgreen" - - - -/obj/item/flashlight/lamp/verb/toggle_light() - set name = "Toggle light" - set category = "Object" - set src in oview(1) - - if(!usr.stat) - attack_self(usr) - -//Bananalamp -/obj/item/flashlight/lamp/bananalamp - name = "banana lamp" - desc = "Only a clown would think to make a ghetto banana-shaped lamp. Even has a goofy pullstring." - icon_state = "bananalamp" - item_state = "bananalamp" - - -// FLARES - -/obj/item/flashlight/flare - name = "flare" - desc = "A red Nanotrasen issued flare. There are instructions on the side, it reads 'pull cord, make light'." - brightness_on = 8 - light_color = "#ff0000" - icon_state = "flare" - item_state = "flare" - togglesound = 'sound/goonstation/misc/matchstick_light.ogg' - var/fuel = 0 - var/on_damage = 7 - var/produce_heat = 1500 - var/fuel_lower = 800 - var/fuel_upp = 1000 - -/obj/item/flashlight/flare/New() - fuel = rand(fuel_lower, fuel_upp) - ..() - -/obj/item/flashlight/flare/process() - var/turf/pos = get_turf(src) - if(pos && produce_heat) - pos.hotspot_expose(produce_heat, 5) - fuel = max(fuel - 1, 0) - if(!fuel || !on) - turn_off() - if(!fuel) - src.icon_state = "[initial(icon_state)]-empty" - STOP_PROCESSING(SSobj, src) - -/obj/item/flashlight/flare/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/flashlight/flare/proc/turn_off() - on = 0 - src.force = initial(src.force) - src.damtype = initial(src.damtype) - if(ismob(loc)) - var/mob/U = loc - update_brightness(U) - else - update_brightness(null) - -/obj/item/flashlight/flare/update_brightness(var/mob/user = null) - ..() - if(on) - item_state = "[initial(item_state)]-on" - else - item_state = "[initial(item_state)]" - -/obj/item/flashlight/flare/attack_self(mob/user) - - // Usual checks - if(!fuel) - to_chat(user, "[src] is out of fuel.") - return - if(on) - to_chat(user, "[src] is already on.") - return - - . = ..() - // All good, turn it on. - if(.) - user.visible_message("[user] activates [src].", "You activate [src].") - src.force = on_damage - src.damtype = "fire" - START_PROCESSING(SSobj, src) - -// GLOWSTICKS - -/obj/item/flashlight/flare/glowstick - name = "green glowstick" - desc = "A military-grade glowstick." - brightness_on = 4 - color = LIGHT_COLOR_GREEN - icon_state = "glowstick" - item_state = "glowstick" - togglesound = 'sound/effects/bone_break_1.ogg' - produce_heat = 0 - fuel_lower = 1600 - fuel_upp = 2000 - -/obj/item/flashlight/flare/glowstick/Initialize() - light_color = color - ..() - -/obj/item/flashlight/flare/glowstick/update_icon() - item_state = "glowstick" - cut_overlays() - if(!fuel) - icon_state = "glowstick-empty" - cut_overlays() - update_brightness(0) - else if(on) - var/mutable_appearance/glowstick_overlay = mutable_appearance(icon, "glowstick-glow") - glowstick_overlay.color = color - add_overlay(glowstick_overlay) - item_state = "glowstick-on" - update_brightness(brightness_on) - else - icon_state = "glowstick" - cut_overlays() - -/obj/item/flashlight/flare/glowstick/red - name = "red glowstick" - color = LIGHT_COLOR_RED - -/obj/item/flashlight/flare/glowstick/blue - name = "blue glowstick" - color = LIGHT_COLOR_BLUE - -/obj/item/flashlight/flare/glowstick/orange - name = "orange glowstick" - color = LIGHT_COLOR_ORANGE - -/obj/item/flashlight/flare/glowstick/yellow - name = "yellow glowstick" - color = LIGHT_COLOR_YELLOW - -/obj/item/flashlight/flare/glowstick/pink - name = "pink glowstick" - color = LIGHT_COLOR_PINK - -/obj/item/flashlight/flare/glowstick/emergency - name = "emergency glowstick" - desc = "A cheap looking, mass produced glowstick. You can practically feel it was made on a tight budget." - color = LIGHT_COLOR_BLUE - fuel_lower = 30 - fuel_upp = 90 - -/obj/item/flashlight/flare/glowstick/random - name = "random colored glowstick" - icon_state = "random_glowstick" - color = null - -/obj/item/flashlight/flare/glowstick/random/Initialize() - . = ..() - var/T = pick(typesof(/obj/item/flashlight/flare/glowstick) - /obj/item/flashlight/flare/glowstick/random - /obj/item/flashlight/flare/glowstick/emergency) - new T(loc) - qdel(src) // return INITIALIZE_HINT_QDEL <-- Doesn't work - -/obj/item/flashlight/flare/extinguish_light() - visible_message("[src] dims slightly before scattering the shadows around it.") - -/obj/item/flashlight/flare/torch - name = "torch" - desc = "A torch fashioned from some leaves and a log." - w_class = WEIGHT_CLASS_BULKY - brightness_on = 7 - icon_state = "torch" - item_state = "torch" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - light_color = LIGHT_COLOR_ORANGE - on_damage = 10 - -/obj/item/flashlight/slime - gender = PLURAL - name = "glowing slime extract" - desc = "A glowing ball of what appears to be amber." - icon = 'icons/obj/lighting.dmi' - icon_state = "floor1" //not a slime extract sprite but... something close enough! - item_state = "slime" - w_class = WEIGHT_CLASS_TINY - brightness_on = 6 - light_color = "#FFBF00" - materials = list() - on = TRUE //Bio-luminesence has one setting, on. - -/obj/item/flashlight/slime/New() - ..() - set_light(brightness_on) - spawn(1) //Might be sloppy, but seems to be necessary to prevent further runtimes and make these work as intended... don't judge me! - update_brightness() - icon_state = initial(icon_state) - -/obj/item/flashlight/slime/attack_self(mob/user) - return //Bio-luminescence does not toggle. - -/obj/item/flashlight/slime/extinguish_light() - visible_message("[src] dims slightly before scattering the shadows around it.") - -/obj/item/flashlight/emp - origin_tech = "magnets=3;syndicate=1" - - var/emp_max_charges = 4 - var/emp_cur_charges = 4 - var/charge_tick = 0 - - -/obj/item/flashlight/emp/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/flashlight/emp/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/flashlight/emp/process() - charge_tick++ - if(charge_tick < 10) - return FALSE - charge_tick = 0 - emp_cur_charges = min(emp_cur_charges+1, emp_max_charges) - return TRUE - -/obj/item/flashlight/emp/attack(mob/living/M as mob, mob/living/user as mob) - if(on && user.zone_selected == "eyes") // call original attack proc only if aiming at the eyes - ..() - return - -/obj/item/flashlight/emp/afterattack(atom/A as mob|obj, mob/user, proximity) - if(!proximity) return - if(emp_cur_charges > 0) - emp_cur_charges -= 1 - A.visible_message("[user] blinks \the [src] at \the [A].", \ - "[user] blinks \the [src] at \the [A].") - if(ismob(A)) - var/mob/M = A - add_attack_logs(user, M, "Hit with EMP-light") - to_chat(user, "[src] now has [emp_cur_charges] charge\s.") - A.emp_act(1) - else - to_chat(user, "\The [src] needs time to recharge!") - return - -/obj/item/flashlight/spotlight //invisible lighting source - name = "disco light" - desc = "Groovy..." - icon_state = null - light_color = null - brightness_on = 0 - light_range = 0 - light_power = 10 - alpha = 0 - layer = 0 - on = TRUE - anchored = TRUE - var/range = null - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF +/obj/item/flashlight + name = "flashlight" + desc = "A hand-held emergency light." + icon = 'icons/obj/lighting.dmi' + icon_state = "flashlight" + item_state = "flashlight" + w_class = WEIGHT_CLASS_SMALL + flags = CONDUCT + slot_flags = SLOT_BELT + materials = list(MAT_METAL=50, MAT_GLASS=20) + actions_types = list(/datum/action/item_action/toggle_light) + var/on = FALSE + var/brightness_on = 4 //luminosity when on + var/togglesound = 'sound/weapons/empty.ogg' + +/obj/item/flashlight/Initialize() + . = ..() + if(on) + icon_state = "[initial(icon_state)]-on" + set_light(brightness_on) + else + icon_state = initial(icon_state) + set_light(0) + +/obj/item/flashlight/proc/update_brightness(var/mob/user = null) + if(on) + icon_state = "[initial(icon_state)]-on" + set_light(brightness_on) + else + icon_state = initial(icon_state) + set_light(0) + +/obj/item/flashlight/attack_self(mob/user) + if(!isturf(user.loc)) + to_chat(user, "You cannot turn the light on while in this [user.loc].")//To prevent some lighting anomalities. + + return 0 + on = !on + playsound(user, togglesound, 100, 1) + update_brightness(user) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + return 1 + + +/obj/item/flashlight/attack(mob/living/M as mob, mob/living/user as mob) + add_fingerprint(user) + if(on && user.zone_selected == "eyes") + + if(((CLUMSY in user.mutations) || user.getBrainLoss() >= 60) && prob(50)) //too dumb to use flashlight properly + return ..() //just hit them in the head + + if(!(istype(user, /mob/living/carbon/human) || SSticker) && SSticker.mode.name != "monkey") //don't have dexterity + to_chat(user, "You don't have the dexterity to do this!") + return + + var/mob/living/carbon/human/H = M //mob has protective eyewear + if(istype(H) && ((H.head && H.head.flags_cover & HEADCOVERSEYES) || (H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) || (H.glasses && H.glasses.flags_cover & GLASSESCOVERSEYES))) + to_chat(user, "You're going to need to remove that [(H.head && H.head.flags_cover & HEADCOVERSEYES) ? "helmet" : (H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) ? "mask" : "glasses"] first.") + return + + if(M == user) //they're using it on themselves + if(M.flash_eyes(visual = 1)) + M.visible_message("[M] directs [src] to [M.p_their()] eyes.", \ + "You wave the light in front of your eyes! Trippy!") + else + M.visible_message("[M] directs [src] to [M.p_their()] eyes.", \ + "You wave the light in front of your eyes.") + else + + user.visible_message("[user] directs [src] to [M]'s eyes.", \ + "You direct [src] to [M]'s eyes.") + + if(istype(H)) //robots and aliens are unaffected + var/obj/item/organ/internal/eyes/eyes = H.get_int_organ(/obj/item/organ/internal/eyes) + if(M.stat == DEAD || !eyes || M.disabilities & BLIND) //mob is dead or fully blind + to_chat(user, "[M]'s pupils are unresponsive to the light!") + else if((XRAY in M.mutations) || eyes.see_in_dark >= 8) //The mob's either got the X-RAY vision or has a tapetum lucidum (extreme nightvision, i.e. Vulp/Tajara with COLOURBLIND & their monkey forms). + to_chat(user, "[M]'s pupils glow eerily!") + else //they're okay! + if(M.flash_eyes(visual = 1)) + to_chat(user, "[M]'s pupils narrow.") + else + return ..() + +/obj/item/flashlight/extinguish_light() + if(on) + on = FALSE + update_brightness() + +/obj/item/flashlight/pen + name = "penlight" + desc = "A pen-sized light, used by medical staff." + icon_state = "penlight" + item_state = "" + w_class = WEIGHT_CLASS_TINY + slot_flags = SLOT_BELT | SLOT_EARS + flags = CONDUCT + brightness_on = 2 + +/obj/item/flashlight/seclite + name = "seclite" + desc = "A robust flashlight used by security." + icon_state = "seclite" + item_state = "seclite" + force = 9 // Not as good as a stun baton. + brightness_on = 5 // A little better than the standard flashlight. + hitsound = 'sound/weapons/genhit1.ogg' + +/obj/item/flashlight/drone + name = "low-power flashlight" + desc = "A miniature lamp, that might be used by small robots." + icon_state = "penlight" + item_state = "" + flags = CONDUCT + brightness_on = 2 + w_class = WEIGHT_CLASS_TINY + +// the desk lamps are a bit special +/obj/item/flashlight/lamp + name = "desk lamp" + desc = "A desk lamp with an adjustable mount." + icon_state = "lamp" + item_state = "lamp" + brightness_on = 5 + w_class = WEIGHT_CLASS_BULKY + flags = CONDUCT + materials = list() + on = TRUE + + +// green-shaded desk lamp +/obj/item/flashlight/lamp/green + desc = "A classic green-shaded desk lamp." + icon_state = "lampgreen" + item_state = "lampgreen" + + + +/obj/item/flashlight/lamp/verb/toggle_light() + set name = "Toggle light" + set category = "Object" + set src in oview(1) + + if(!usr.stat) + attack_self(usr) + +//Bananalamp +/obj/item/flashlight/lamp/bananalamp + name = "banana lamp" + desc = "Only a clown would think to make a ghetto banana-shaped lamp. Even has a goofy pullstring." + icon_state = "bananalamp" + item_state = "bananalamp" + + +// FLARES + +/obj/item/flashlight/flare + name = "flare" + desc = "A red Nanotrasen issued flare. There are instructions on the side, it reads 'pull cord, make light'." + brightness_on = 8 + light_color = "#ff0000" + icon_state = "flare" + item_state = "flare" + togglesound = 'sound/goonstation/misc/matchstick_light.ogg' + var/fuel = 0 + var/on_damage = 7 + var/produce_heat = 1500 + var/fuel_lower = 800 + var/fuel_upp = 1000 + +/obj/item/flashlight/flare/New() + fuel = rand(fuel_lower, fuel_upp) + ..() + +/obj/item/flashlight/flare/process() + var/turf/pos = get_turf(src) + if(pos && produce_heat) + pos.hotspot_expose(produce_heat, 5) + fuel = max(fuel - 1, 0) + if(!fuel || !on) + turn_off() + if(!fuel) + src.icon_state = "[initial(icon_state)]-empty" + STOP_PROCESSING(SSobj, src) + +/obj/item/flashlight/flare/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/flashlight/flare/proc/turn_off() + on = 0 + src.force = initial(src.force) + src.damtype = initial(src.damtype) + if(ismob(loc)) + var/mob/U = loc + update_brightness(U) + else + update_brightness(null) + +/obj/item/flashlight/flare/update_brightness(var/mob/user = null) + ..() + if(on) + item_state = "[initial(item_state)]-on" + else + item_state = "[initial(item_state)]" + +/obj/item/flashlight/flare/attack_self(mob/user) + + // Usual checks + if(!fuel) + to_chat(user, "[src] is out of fuel.") + return + if(on) + to_chat(user, "[src] is already on.") + return + + . = ..() + // All good, turn it on. + if(.) + user.visible_message("[user] activates [src].", "You activate [src].") + src.force = on_damage + src.damtype = "fire" + START_PROCESSING(SSobj, src) + +// GLOWSTICKS + +/obj/item/flashlight/flare/glowstick + name = "green glowstick" + desc = "A military-grade glowstick." + brightness_on = 4 + color = LIGHT_COLOR_GREEN + icon_state = "glowstick" + item_state = "glowstick" + togglesound = 'sound/effects/bone_break_1.ogg' + produce_heat = 0 + fuel_lower = 1600 + fuel_upp = 2000 + +/obj/item/flashlight/flare/glowstick/Initialize() + light_color = color + ..() + +/obj/item/flashlight/flare/glowstick/update_icon() + item_state = "glowstick" + cut_overlays() + if(!fuel) + icon_state = "glowstick-empty" + cut_overlays() + update_brightness(0) + else if(on) + var/mutable_appearance/glowstick_overlay = mutable_appearance(icon, "glowstick-glow") + glowstick_overlay.color = color + add_overlay(glowstick_overlay) + item_state = "glowstick-on" + update_brightness(brightness_on) + else + icon_state = "glowstick" + cut_overlays() + +/obj/item/flashlight/flare/glowstick/red + name = "red glowstick" + color = LIGHT_COLOR_RED + +/obj/item/flashlight/flare/glowstick/blue + name = "blue glowstick" + color = LIGHT_COLOR_BLUE + +/obj/item/flashlight/flare/glowstick/orange + name = "orange glowstick" + color = LIGHT_COLOR_ORANGE + +/obj/item/flashlight/flare/glowstick/yellow + name = "yellow glowstick" + color = LIGHT_COLOR_YELLOW + +/obj/item/flashlight/flare/glowstick/pink + name = "pink glowstick" + color = LIGHT_COLOR_PINK + +/obj/item/flashlight/flare/glowstick/emergency + name = "emergency glowstick" + desc = "A cheap looking, mass produced glowstick. You can practically feel it was made on a tight budget." + color = LIGHT_COLOR_BLUE + fuel_lower = 30 + fuel_upp = 90 + +/obj/item/flashlight/flare/glowstick/random + name = "random colored glowstick" + icon_state = "random_glowstick" + color = null + +/obj/item/flashlight/flare/glowstick/random/Initialize() + . = ..() + var/T = pick(typesof(/obj/item/flashlight/flare/glowstick) - /obj/item/flashlight/flare/glowstick/random - /obj/item/flashlight/flare/glowstick/emergency) + new T(loc) + qdel(src) // return INITIALIZE_HINT_QDEL <-- Doesn't work + +/obj/item/flashlight/flare/extinguish_light() + visible_message("[src] dims slightly before scattering the shadows around it.") + +/obj/item/flashlight/flare/torch + name = "torch" + desc = "A torch fashioned from some leaves and a log." + w_class = WEIGHT_CLASS_BULKY + brightness_on = 7 + icon_state = "torch" + item_state = "torch" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + light_color = LIGHT_COLOR_ORANGE + on_damage = 10 + +/obj/item/flashlight/slime + gender = PLURAL + name = "glowing slime extract" + desc = "A glowing ball of what appears to be amber." + icon = 'icons/obj/lighting.dmi' + icon_state = "floor1" //not a slime extract sprite but... something close enough! + item_state = "slime" + w_class = WEIGHT_CLASS_TINY + brightness_on = 6 + light_color = "#FFBF00" + materials = list() + on = TRUE //Bio-luminesence has one setting, on. + +/obj/item/flashlight/slime/New() + ..() + set_light(brightness_on) + spawn(1) //Might be sloppy, but seems to be necessary to prevent further runtimes and make these work as intended... don't judge me! + update_brightness() + icon_state = initial(icon_state) + +/obj/item/flashlight/slime/attack_self(mob/user) + return //Bio-luminescence does not toggle. + +/obj/item/flashlight/slime/extinguish_light() + visible_message("[src] dims slightly before scattering the shadows around it.") + +/obj/item/flashlight/emp + origin_tech = "magnets=3;syndicate=1" + + var/emp_max_charges = 4 + var/emp_cur_charges = 4 + var/charge_tick = 0 + + +/obj/item/flashlight/emp/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/flashlight/emp/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/flashlight/emp/process() + charge_tick++ + if(charge_tick < 10) + return FALSE + charge_tick = 0 + emp_cur_charges = min(emp_cur_charges+1, emp_max_charges) + return TRUE + +/obj/item/flashlight/emp/attack(mob/living/M as mob, mob/living/user as mob) + if(on && user.zone_selected == "eyes") // call original attack proc only if aiming at the eyes + ..() + return + +/obj/item/flashlight/emp/afterattack(atom/A as mob|obj, mob/user, proximity) + if(!proximity) return + if(emp_cur_charges > 0) + emp_cur_charges -= 1 + A.visible_message("[user] blinks \the [src] at \the [A].", \ + "[user] blinks \the [src] at \the [A].") + if(ismob(A)) + var/mob/M = A + add_attack_logs(user, M, "Hit with EMP-light") + to_chat(user, "[src] now has [emp_cur_charges] charge\s.") + A.emp_act(1) + else + to_chat(user, "\The [src] needs time to recharge!") + return + +/obj/item/flashlight/spotlight //invisible lighting source + name = "disco light" + desc = "Groovy..." + icon_state = null + light_color = null + brightness_on = 0 + light_range = 0 + light_power = 10 + alpha = 0 + layer = 0 + on = TRUE + anchored = TRUE + var/range = null + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF diff --git a/code/game/objects/items/devices/handheld_defib.dm b/code/game/objects/items/devices/handheld_defib.dm index 9333f9170e5..9dd159ca91b 100644 --- a/code/game/objects/items/devices/handheld_defib.dm +++ b/code/game/objects/items/devices/handheld_defib.dm @@ -88,4 +88,4 @@ /obj/item/handheld_defibrillator/proc/recharge() cooldown = FALSE icon_state = "[icon_base]-on" - playsound(loc, "sound/weapons/flash.ogg", 75, 1) \ No newline at end of file + playsound(loc, "sound/weapons/flash.ogg", 75, 1) diff --git a/code/game/objects/items/devices/instruments.dm b/code/game/objects/items/devices/instruments.dm index 538b4efaf49..a296e465a59 100644 --- a/code/game/objects/items/devices/instruments.dm +++ b/code/game/objects/items/devices/instruments.dm @@ -189,4 +189,4 @@ /obj/item/stack/tape_roll = 5) tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER) time = 80 - category = CAT_MISC \ No newline at end of file + category = CAT_MISC diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm index e9cb80f398c..d9c6d60cf54 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -1,269 +1,269 @@ - -// Light Replacer (LR) -// -// ABOUT THE DEVICE -// -// This is a device supposedly to be used by Janitors and Janitor Cyborgs which will -// allow them to easily replace lights. This was mostly designed for Janitor Cyborgs since -// they don't have hands or a way to replace lightbulbs. -// -// HOW IT WORKS -// -// You attack a light fixture with it, if the light fixture is broken it will replace the -// light fixture with a working light; the broken light is then placed on the floor for the -// user to then pickup with a trash bag. If it's empty then it will just place a light in the fixture. -// -// HOW TO REFILL THE DEVICE -// -// It will need to be manually refilled with lights. -// If it's part of a robot module, it will charge when the Robot is inside a Recharge Station. -// -// EMAGGED FEATURES -// -// NOTICE: The Cyborg cannot use the emagged Light Replacer and the light's explosion was nerfed. It cannot create holes in the station anymore. -// -// I'm not sure everyone will react the emag's features so please say what your opinions are of it. -// -// When emagged it will rig every light it replaces, which will explode when the light is on. -// This is VERY noticable, even the device's name changes when you emag it so if anyone -// examines you when you're holding it in your hand, you will be discovered. -// It will also be very obvious who is setting all these lights off, since only Janitor Borgs and Janitors have easy -// access to them, and only one of them can emag their device. -// -// The explosion cannot insta-kill anyone with 30% or more health. - -#define LIGHT_OK 0 -#define LIGHT_EMPTY 1 -#define LIGHT_BROKEN 2 -#define LIGHT_BURNED 3 - - -/obj/item/lightreplacer - - name = "light replacer" - desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass." - - icon = 'icons/obj/janitor.dmi' - icon_state = "lightreplacer0" - item_state = "electronic" - w_class = WEIGHT_CLASS_SMALL - flags = CONDUCT - slot_flags = SLOT_BELT - origin_tech = "magnets=3;engineering=4" - force = 8 - - var/emagged = FALSE - var/max_uses = 20 - var/uses = 10 - // How much to increase per each glass? - var/increment = 5 - // How much to take from the glass? - var/decrement = 1 - var/charge = 1 - - // Eating used bulbs gives us bulb shards - var/bulb_shards = 0 - // when we get this many shards, we get a free bulb. - var/shards_required = 4 - - -/obj/item/lightreplacer/examine(mob/user) - . = ..() - . += status_string() - -/obj/item/lightreplacer/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = I - if(uses >= max_uses) - to_chat(user, "[src] is full.") - return - else if(G.use(decrement)) - AddUses(increment) - to_chat(user, "You insert a piece of glass into [src]. You have [uses] light\s remaining.") - return - else - to_chat(user, "You need one sheet of glass to replace lights!") - return - - if(istype(I, /obj/item/shard)) - if(uses >= max_uses) - to_chat(user, "[src] is full.") - return - if(!user.unEquip(I)) - return - AddUses(round(increment * 0.75)) - to_chat(user, "You insert a shard of glass into [src]. You have [uses] light\s remaining.") - qdel(I) - return - - if(istype(I, /obj/item/light)) - var/obj/item/light/L = I - if(L.status == 0) // LIGHT OKAY - if(uses < max_uses) - if(!user.unEquip(L)) - return - AddUses(1) - qdel(L) - else - if(!user.unEquip(L)) - return - to_chat(user, "You insert [L] into [src].") - AddShards(1, user) - qdel(L) - return - - if(istype(I, /obj/item/storage)) - var/obj/item/storage/S = I - var/found_lightbulbs = FALSE - var/replaced_something = TRUE - - for(var/obj/item/IT in S.contents) - if(istype(IT, /obj/item/light)) - var/obj/item/light/L = IT - found_lightbulbs = TRUE - if(uses >= max_uses) - break - if(L.status == LIGHT_OK) - replaced_something = TRUE - AddUses(1) - qdel(L) - - else if(L.status == LIGHT_BROKEN || L.status == LIGHT_BURNED) - replaced_something = TRUE - AddShards(1, user) - qdel(L) - - if(!found_lightbulbs) - to_chat(user, "[S] contains no bulbs.") - return - - if(!replaced_something && uses == max_uses) - to_chat(user, "[src] is full!") - return - - to_chat(user, "You fill [src] with lights from [S]. " + status_string() + "") - return - return ..() - -/obj/item/lightreplacer/emag_act(user as mob) - if(!emagged) - Emag() - -/obj/item/lightreplacer/attack_self(mob/user) - for(var/obj/machinery/light/target in user.loc) - ReplaceLight(target, user) - to_chat(user, status_string()) - -/obj/item/lightreplacer/update_icon() - icon_state = "lightreplacer[emagged]" - -/obj/item/lightreplacer/proc/status_string() - return "It has [uses] light\s remaining (plus [bulb_shards] fragment\s)." - -/obj/item/lightreplacer/proc/Use(mob/user) - playsound(loc, 'sound/machines/click.ogg', 50, TRUE) - AddUses(-1) - return 1 - -// Negative numbers will subtract -/obj/item/lightreplacer/proc/AddUses(amount = 1) - uses = Clamp(uses + amount, 0, max_uses) - -/obj/item/lightreplacer/proc/AddShards(amount = 1, user) - bulb_shards += amount - var/new_bulbs = round(bulb_shards / shards_required) - if(new_bulbs > 0) - AddUses(new_bulbs) - bulb_shards = bulb_shards % shards_required - if(new_bulbs != 0) - to_chat(user, "[src] has fabricated a new bulb from the broken glass it has stored. It now has [uses] uses.") - playsound(loc, 'sound/machines/ding.ogg', 50, TRUE) - return new_bulbs - -/obj/item/lightreplacer/proc/Charge(var/mob/user) - charge += 1 - if(charge > 3) - AddUses(1) - charge = 1 - -/obj/item/lightreplacer/proc/ReplaceLight(obj/machinery/light/target, mob/living/U) - if(target.status != LIGHT_OK) - if(CanUse(U)) - if(!Use(U)) - return - to_chat(U, "You replace [target.fitting] with [src].") - - if(target.status != LIGHT_EMPTY) - AddShards(1, U) - target.status = LIGHT_EMPTY - target.update() - - var/obj/item/light/L2 = new target.light_type() - - target.status = L2.status - target.switchcount = L2.switchcount - target.rigged = emagged - target.brightness_range = L2.brightness_range - target.brightness_power = L2.brightness_power - target.brightness_color = L2.brightness_color - target.on = target.has_power() - target.update() - qdel(L2) - - if(target.on && target.rigged) - target.explode() - return - - else - to_chat(U, "[src]'s refill light blinks red.") - return - else - to_chat(U, "There is a working [target.fitting] already inserted!") - return - -/obj/item/lightreplacer/proc/Emag() - emagged = !emagged - playsound(loc, "sparks", 100, TRUE) - if(emagged) - name = "shortcircuited [initial(name)]" - else - name = initial(name) - update_icon() - -/obj/item/lightreplacer/proc/CanUse(mob/living/user) - add_fingerprint(user) - if(uses > 0) - return 1 - else - return 0 - -/obj/item/lightreplacer/afterattack(atom/T, mob/U, proximity) - . = ..() - if(!proximity) - return - if(!isturf(T)) - return - - var/used = FALSE - for(var/atom/A in T) - if(!CanUse(U)) - break - used = TRUE - if(istype(A, /obj/machinery/light)) - ReplaceLight(A, U) - - if(!used) - to_chat(U, "[src]'s refill light blinks red.") - -/obj/item/lightreplacer/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) - J.put_in_cart(src, user) - J.myreplacer = src - J.update_icon() - -/obj/item/lightreplacer/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) - return - -#undef LIGHT_OK -#undef LIGHT_EMPTY -#undef LIGHT_BROKEN -#undef LIGHT_BURNED \ No newline at end of file + +// Light Replacer (LR) +// +// ABOUT THE DEVICE +// +// This is a device supposedly to be used by Janitors and Janitor Cyborgs which will +// allow them to easily replace lights. This was mostly designed for Janitor Cyborgs since +// they don't have hands or a way to replace lightbulbs. +// +// HOW IT WORKS +// +// You attack a light fixture with it, if the light fixture is broken it will replace the +// light fixture with a working light; the broken light is then placed on the floor for the +// user to then pickup with a trash bag. If it's empty then it will just place a light in the fixture. +// +// HOW TO REFILL THE DEVICE +// +// It will need to be manually refilled with lights. +// If it's part of a robot module, it will charge when the Robot is inside a Recharge Station. +// +// EMAGGED FEATURES +// +// NOTICE: The Cyborg cannot use the emagged Light Replacer and the light's explosion was nerfed. It cannot create holes in the station anymore. +// +// I'm not sure everyone will react the emag's features so please say what your opinions are of it. +// +// When emagged it will rig every light it replaces, which will explode when the light is on. +// This is VERY noticable, even the device's name changes when you emag it so if anyone +// examines you when you're holding it in your hand, you will be discovered. +// It will also be very obvious who is setting all these lights off, since only Janitor Borgs and Janitors have easy +// access to them, and only one of them can emag their device. +// +// The explosion cannot insta-kill anyone with 30% or more health. + +#define LIGHT_OK 0 +#define LIGHT_EMPTY 1 +#define LIGHT_BROKEN 2 +#define LIGHT_BURNED 3 + + +/obj/item/lightreplacer + + name = "light replacer" + desc = "A device to automatically replace lights. Refill with broken or working light bulbs, or sheets of glass." + + icon = 'icons/obj/janitor.dmi' + icon_state = "lightreplacer0" + item_state = "electronic" + w_class = WEIGHT_CLASS_SMALL + flags = CONDUCT + slot_flags = SLOT_BELT + origin_tech = "magnets=3;engineering=4" + force = 8 + + var/emagged = FALSE + var/max_uses = 20 + var/uses = 10 + // How much to increase per each glass? + var/increment = 5 + // How much to take from the glass? + var/decrement = 1 + var/charge = 1 + + // Eating used bulbs gives us bulb shards + var/bulb_shards = 0 + // when we get this many shards, we get a free bulb. + var/shards_required = 4 + + +/obj/item/lightreplacer/examine(mob/user) + . = ..() + . += status_string() + +/obj/item/lightreplacer/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = I + if(uses >= max_uses) + to_chat(user, "[src] is full.") + return + else if(G.use(decrement)) + AddUses(increment) + to_chat(user, "You insert a piece of glass into [src]. You have [uses] light\s remaining.") + return + else + to_chat(user, "You need one sheet of glass to replace lights!") + return + + if(istype(I, /obj/item/shard)) + if(uses >= max_uses) + to_chat(user, "[src] is full.") + return + if(!user.unEquip(I)) + return + AddUses(round(increment * 0.75)) + to_chat(user, "You insert a shard of glass into [src]. You have [uses] light\s remaining.") + qdel(I) + return + + if(istype(I, /obj/item/light)) + var/obj/item/light/L = I + if(L.status == 0) // LIGHT OKAY + if(uses < max_uses) + if(!user.unEquip(L)) + return + AddUses(1) + qdel(L) + else + if(!user.unEquip(L)) + return + to_chat(user, "You insert [L] into [src].") + AddShards(1, user) + qdel(L) + return + + if(istype(I, /obj/item/storage)) + var/obj/item/storage/S = I + var/found_lightbulbs = FALSE + var/replaced_something = TRUE + + for(var/obj/item/IT in S.contents) + if(istype(IT, /obj/item/light)) + var/obj/item/light/L = IT + found_lightbulbs = TRUE + if(uses >= max_uses) + break + if(L.status == LIGHT_OK) + replaced_something = TRUE + AddUses(1) + qdel(L) + + else if(L.status == LIGHT_BROKEN || L.status == LIGHT_BURNED) + replaced_something = TRUE + AddShards(1, user) + qdel(L) + + if(!found_lightbulbs) + to_chat(user, "[S] contains no bulbs.") + return + + if(!replaced_something && uses == max_uses) + to_chat(user, "[src] is full!") + return + + to_chat(user, "You fill [src] with lights from [S]. " + status_string() + "") + return + return ..() + +/obj/item/lightreplacer/emag_act(user as mob) + if(!emagged) + Emag() + +/obj/item/lightreplacer/attack_self(mob/user) + for(var/obj/machinery/light/target in user.loc) + ReplaceLight(target, user) + to_chat(user, status_string()) + +/obj/item/lightreplacer/update_icon() + icon_state = "lightreplacer[emagged]" + +/obj/item/lightreplacer/proc/status_string() + return "It has [uses] light\s remaining (plus [bulb_shards] fragment\s)." + +/obj/item/lightreplacer/proc/Use(mob/user) + playsound(loc, 'sound/machines/click.ogg', 50, TRUE) + AddUses(-1) + return 1 + +// Negative numbers will subtract +/obj/item/lightreplacer/proc/AddUses(amount = 1) + uses = Clamp(uses + amount, 0, max_uses) + +/obj/item/lightreplacer/proc/AddShards(amount = 1, user) + bulb_shards += amount + var/new_bulbs = round(bulb_shards / shards_required) + if(new_bulbs > 0) + AddUses(new_bulbs) + bulb_shards = bulb_shards % shards_required + if(new_bulbs != 0) + to_chat(user, "[src] has fabricated a new bulb from the broken glass it has stored. It now has [uses] uses.") + playsound(loc, 'sound/machines/ding.ogg', 50, TRUE) + return new_bulbs + +/obj/item/lightreplacer/proc/Charge(var/mob/user) + charge += 1 + if(charge > 3) + AddUses(1) + charge = 1 + +/obj/item/lightreplacer/proc/ReplaceLight(obj/machinery/light/target, mob/living/U) + if(target.status != LIGHT_OK) + if(CanUse(U)) + if(!Use(U)) + return + to_chat(U, "You replace [target.fitting] with [src].") + + if(target.status != LIGHT_EMPTY) + AddShards(1, U) + target.status = LIGHT_EMPTY + target.update() + + var/obj/item/light/L2 = new target.light_type() + + target.status = L2.status + target.switchcount = L2.switchcount + target.rigged = emagged + target.brightness_range = L2.brightness_range + target.brightness_power = L2.brightness_power + target.brightness_color = L2.brightness_color + target.on = target.has_power() + target.update() + qdel(L2) + + if(target.on && target.rigged) + target.explode() + return + + else + to_chat(U, "[src]'s refill light blinks red.") + return + else + to_chat(U, "There is a working [target.fitting] already inserted!") + return + +/obj/item/lightreplacer/proc/Emag() + emagged = !emagged + playsound(loc, "sparks", 100, TRUE) + if(emagged) + name = "shortcircuited [initial(name)]" + else + name = initial(name) + update_icon() + +/obj/item/lightreplacer/proc/CanUse(mob/living/user) + add_fingerprint(user) + if(uses > 0) + return 1 + else + return 0 + +/obj/item/lightreplacer/afterattack(atom/T, mob/U, proximity) + . = ..() + if(!proximity) + return + if(!isturf(T)) + return + + var/used = FALSE + for(var/atom/A in T) + if(!CanUse(U)) + break + used = TRUE + if(istype(A, /obj/machinery/light)) + ReplaceLight(A, U) + + if(!used) + to_chat(U, "[src]'s refill light blinks red.") + +/obj/item/lightreplacer/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) + J.put_in_cart(src, user) + J.myreplacer = src + J.update_icon() + +/obj/item/lightreplacer/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) + return + +#undef LIGHT_OK +#undef LIGHT_EMPTY +#undef LIGHT_BROKEN +#undef LIGHT_BURNED diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index e6ffa264a51..5f987bdce45 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -1,340 +1,340 @@ -/obj/item/paicard - name = "personal AI device" - icon = 'icons/obj/aicards.dmi' - icon_state = "pai" - item_state = "electronic" - w_class = WEIGHT_CLASS_SMALL - slot_flags = SLOT_BELT - origin_tech = "programming=2" - var/request_cooldown = 5 // five seconds - var/last_request - var/obj/item/radio/radio - var/looking_for_personality = 0 - var/mob/living/silicon/pai/pai - var/list/faction = list("neutral") // The factions the pAI will inherit from the card - resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE - -/obj/item/paicard/syndicate - name = "syndicate personal AI device" - faction = list("syndicate") - -/obj/item/paicard/relaymove(var/mob/user, var/direction) - if(user.stat || user.stunned) - return - var/obj/item/rig/rig = get_rig() - if(istype(rig)) - rig.forced_move(direction, user) - -/obj/item/paicard/New() - ..() - overlays += "pai-off" - -/obj/item/paicard/Destroy() - if(pai) - pai.ghostize() - QDEL_NULL(pai) - QDEL_NULL(radio) - return ..() - -/obj/item/paicard/attack_self(mob/user) - if(!in_range(src, user)) - return - user.set_machine(src) - var/dat = {" - - - - - - - "} - - if(pai) - dat += {" - Personal AI Device

        - - - - - - - - - - - - - -
        Installed Personality:[pai.name]
        Prime directive:[pai.pai_law0]
        Additional directives:[pai.pai_laws]
        -
        - "} - dat += {" - - -
        - Configure Directives -
        - "} - if(pai && (!pai.master_dna || !pai.master)) - dat += {" - - -
        - Imprint Master DNA -
        - "} - dat += "
        " - if(radio) - dat += "Radio Uplink" - dat += {" - - - - - - - - - -
        Transmit:[radio.broadcasting ? "En" : "Dis" ]abled - -
        Receive:[radio.listening ? "En" : "Dis" ]abled - -
        -
        - "} - else - dat += "Radio Uplink
        " - dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
        " - dat += {" - - -
        Wipe current pAI personality - -
        - "} - else - if(looking_for_personality) - dat += {" - pAI Request Module

        -

        Requesting AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.

        - Searching for personalities, please wait...

        - - - - - -
        - Refresh available personalities -

        - "} - else - dat += {" - pAI Request Module

        -

        No personality is installed.

        - - - - -
        Request personality -
        -
        -

        Each time this button is pressed, a request will be sent out to any available personalities. Check back often give plenty of time for personalities to respond. This process could take anywhere from 15 seconds to several minutes, depending on the available personalities' timeliness.

        - "} - user << browse(dat, "window=paicard") - onclose(user, "paicard") - return - -/obj/item/paicard/Topic(href, href_list) - - var/mob/U = usr - - if(!usr || usr.stat) - return - - if(pai) - if(!in_range(src, U)) - U << browse(null, "window=paicard") - usr.unset_machine() - return - - if(href_list["setdna"]) - if(pai.master_dna) - return - var/mob/M = usr - if(!istype(M, /mob/living/carbon)) - to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device.") - else - var/datum/dna/dna = usr.dna - pai.master = M.real_name - pai.master_dna = dna.unique_enzymes - to_chat(pai, "

        You have been bound to a new master.

        ") - if(href_list["request"]) - var/delta = (world.time / 10) - last_request - if(request_cooldown > delta) - var/cooldown_time = round(request_cooldown - ((world.time / 10) - last_request), 1) - to_chat(usr, "The request system is currently offline. Please wait another [cooldown_time] seconds.") - return - last_request = world.time / 10 - looking_for_personality = 1 - paiController.findPAI(src, usr) - if(href_list["wipe"]) - var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No") - if(confirm == "Yes") - for(var/mob/M in src) - to_chat(M, "

        You feel yourself slipping away from reality.

        ") - to_chat(M, "

        Byte by byte you lose your sense of self.

        ") - to_chat(M, "

        Your mental faculties leave you.

        ") - to_chat(M, "
        oblivion...
        ") - var/mob/living/silicon/pai/P = M - if(istype(P)) - if(P.resting || P.canmove) - P.close_up() - M.death(0, 1) - removePersonality() - if(href_list["wires"]) - var/t1 = text2num(href_list["wires"]) - switch(t1) - if(4) - radio.ToggleBroadcast() - if(2) - radio.ToggleReception() - if(href_list["setlaws"]) - var/newlaws = sanitize(copytext(input("Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.pai_laws) as message,1,MAX_MESSAGE_LEN)) - if(newlaws) - pai.pai_laws = newlaws - to_chat(pai, "Your supplemental directives have been updated. Your new directives are:") - to_chat(pai, "Prime Directive:
        [pai.pai_law0]") - to_chat(pai, "Supplemental Directives:
        [pai.pai_laws]") - attack_self(usr) - -// WIRE_SIGNAL = 1 -// WIRE_RECEIVE = 2 -// WIRE_TRANSMIT = 4 - -/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) - pai = personality - overlays += "pai-happy" - -/obj/item/paicard/proc/removePersonality() - pai = null - overlays.Cut() - overlays += "pai-off" - -/obj/item/paicard - var/current_emotion = 1 -/obj/item/paicard/proc/setEmotion(var/emotion) - if(pai) - overlays.Cut() - switch(emotion) - if(1) overlays += "pai-happy" - if(2) overlays += "pai-cat" - if(3) overlays += "pai-extremely-happy" - if(4) overlays += "pai-face" - if(5) overlays += "pai-laugh" - if(6) overlays += "pai-off" - if(7) overlays += "pai-sad" - if(8) overlays += "pai-angry" - if(9) overlays += "pai-what" - current_emotion = emotion - -/obj/item/paicard/proc/alertUpdate() - var/turf/T = get_turf_or_move(loc) - for(var/mob/M in viewers(T)) - M.show_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", 3, "[src] bleeps electronically.", 2) - -/obj/item/paicard/emp_act(severity) - for(var/mob/M in src) - M.emp_act(severity) - ..() - -/obj/item/paicard/extinguish_light() - pai.extinguish_light() - set_light(0) \ No newline at end of file +/obj/item/paicard + name = "personal AI device" + icon = 'icons/obj/aicards.dmi' + icon_state = "pai" + item_state = "electronic" + w_class = WEIGHT_CLASS_SMALL + slot_flags = SLOT_BELT + origin_tech = "programming=2" + var/request_cooldown = 5 // five seconds + var/last_request + var/obj/item/radio/radio + var/looking_for_personality = 0 + var/mob/living/silicon/pai/pai + var/list/faction = list("neutral") // The factions the pAI will inherit from the card + resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE + +/obj/item/paicard/syndicate + name = "syndicate personal AI device" + faction = list("syndicate") + +/obj/item/paicard/relaymove(var/mob/user, var/direction) + if(user.stat || user.stunned) + return + var/obj/item/rig/rig = get_rig() + if(istype(rig)) + rig.forced_move(direction, user) + +/obj/item/paicard/New() + ..() + overlays += "pai-off" + +/obj/item/paicard/Destroy() + if(pai) + pai.ghostize() + QDEL_NULL(pai) + QDEL_NULL(radio) + return ..() + +/obj/item/paicard/attack_self(mob/user) + if(!in_range(src, user)) + return + user.set_machine(src) + var/dat = {" + + + + + + + "} + + if(pai) + dat += {" + Personal AI Device

        + + + + + + + + + + + + + +
        Installed Personality:[pai.name]
        Prime directive:[pai.pai_law0]
        Additional directives:[pai.pai_laws]
        +
        + "} + dat += {" + + +
        + Configure Directives +
        + "} + if(pai && (!pai.master_dna || !pai.master)) + dat += {" + + +
        + Imprint Master DNA +
        + "} + dat += "
        " + if(radio) + dat += "Radio Uplink" + dat += {" + + + + + + + + + +
        Transmit:[radio.broadcasting ? "En" : "Dis" ]abled + +
        Receive:[radio.listening ? "En" : "Dis" ]abled + +
        +
        + "} + else + dat += "Radio Uplink
        " + dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
        " + dat += {" + + +
        Wipe current pAI personality + +
        + "} + else + if(looking_for_personality) + dat += {" + pAI Request Module

        +

        Requesting AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.

        + Searching for personalities, please wait...

        + + + + + +
        + Refresh available personalities +

        + "} + else + dat += {" + pAI Request Module

        +

        No personality is installed.

        + + + + +
        Request personality +
        +
        +

        Each time this button is pressed, a request will be sent out to any available personalities. Check back often give plenty of time for personalities to respond. This process could take anywhere from 15 seconds to several minutes, depending on the available personalities' timeliness.

        + "} + user << browse(dat, "window=paicard") + onclose(user, "paicard") + return + +/obj/item/paicard/Topic(href, href_list) + + var/mob/U = usr + + if(!usr || usr.stat) + return + + if(pai) + if(!in_range(src, U)) + U << browse(null, "window=paicard") + usr.unset_machine() + return + + if(href_list["setdna"]) + if(pai.master_dna) + return + var/mob/M = usr + if(!istype(M, /mob/living/carbon)) + to_chat(usr, "You don't have any DNA, or your DNA is incompatible with this device.") + else + var/datum/dna/dna = usr.dna + pai.master = M.real_name + pai.master_dna = dna.unique_enzymes + to_chat(pai, "

        You have been bound to a new master.

        ") + if(href_list["request"]) + var/delta = (world.time / 10) - last_request + if(request_cooldown > delta) + var/cooldown_time = round(request_cooldown - ((world.time / 10) - last_request), 1) + to_chat(usr, "The request system is currently offline. Please wait another [cooldown_time] seconds.") + return + last_request = world.time / 10 + looking_for_personality = 1 + paiController.findPAI(src, usr) + if(href_list["wipe"]) + var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No") + if(confirm == "Yes") + for(var/mob/M in src) + to_chat(M, "

        You feel yourself slipping away from reality.

        ") + to_chat(M, "

        Byte by byte you lose your sense of self.

        ") + to_chat(M, "

        Your mental faculties leave you.

        ") + to_chat(M, "
        oblivion...
        ") + var/mob/living/silicon/pai/P = M + if(istype(P)) + if(P.resting || P.canmove) + P.close_up() + M.death(0, 1) + removePersonality() + if(href_list["wires"]) + var/t1 = text2num(href_list["wires"]) + switch(t1) + if(4) + radio.ToggleBroadcast() + if(2) + radio.ToggleReception() + if(href_list["setlaws"]) + var/newlaws = sanitize(copytext(input("Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.pai_laws) as message,1,MAX_MESSAGE_LEN)) + if(newlaws) + pai.pai_laws = newlaws + to_chat(pai, "Your supplemental directives have been updated. Your new directives are:") + to_chat(pai, "Prime Directive:
        [pai.pai_law0]") + to_chat(pai, "Supplemental Directives:
        [pai.pai_laws]") + attack_self(usr) + +// WIRE_SIGNAL = 1 +// WIRE_RECEIVE = 2 +// WIRE_TRANSMIT = 4 + +/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality) + pai = personality + overlays += "pai-happy" + +/obj/item/paicard/proc/removePersonality() + pai = null + overlays.Cut() + overlays += "pai-off" + +/obj/item/paicard + var/current_emotion = 1 +/obj/item/paicard/proc/setEmotion(var/emotion) + if(pai) + overlays.Cut() + switch(emotion) + if(1) overlays += "pai-happy" + if(2) overlays += "pai-cat" + if(3) overlays += "pai-extremely-happy" + if(4) overlays += "pai-face" + if(5) overlays += "pai-laugh" + if(6) overlays += "pai-off" + if(7) overlays += "pai-sad" + if(8) overlays += "pai-angry" + if(9) overlays += "pai-what" + current_emotion = emotion + +/obj/item/paicard/proc/alertUpdate() + var/turf/T = get_turf_or_move(loc) + for(var/mob/M in viewers(T)) + M.show_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", 3, "[src] bleeps electronically.", 2) + +/obj/item/paicard/emp_act(severity) + for(var/mob/M in src) + M.emp_act(severity) + ..() + +/obj/item/paicard/extinguish_light() + pai.extinguish_light() + set_light(0) diff --git a/code/game/objects/items/devices/pizza_bomb.dm b/code/game/objects/items/devices/pizza_bomb.dm index 71cb95d436f..c12331b2be2 100644 --- a/code/game/objects/items/devices/pizza_bomb.dm +++ b/code/game/objects/items/devices/pizza_bomb.dm @@ -102,4 +102,4 @@ /obj/item/pizza_bomb/autoarm timer_set = 1 - timer = 30 // 3 seconds \ No newline at end of file + timer = 30 // 3 seconds diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm index e354adc28e4..68f52721f86 100644 --- a/code/game/objects/items/devices/powersink.dm +++ b/code/game/objects/items/devices/powersink.dm @@ -1,153 +1,153 @@ -#define DISCONNECTED 0 -#define CLAMPED_OFF 1 -#define OPERATING 2 - -// Powersink - used to drain station power - -/obj/item/powersink - name = "power sink" - desc = "A nulling power sink which drains energy from electrical systems." - icon = 'icons/obj/device.dmi' - icon_state = "powersink0" - item_state = "electronic" - w_class = WEIGHT_CLASS_BULKY - flags = CONDUCT - throwforce = 5 - throw_speed = 1 - throw_range = 2 - materials = list(MAT_METAL=750) - origin_tech = "powerstorage=5;syndicate=5" - var/drain_rate = 2000000 // amount of power to drain per tick - var/power_drained = 0 // has drained this much power - var/max_power = 6e8 // maximum power that can be drained before exploding - var/mode = 0 // 0 = off, 1=clamped (off), 2=operating - var/admins_warned = FALSE // stop spam, only warn the admins once that we are about to boom - - var/obj/structure/cable/attached // the attached cable - -/obj/item/powersink/Destroy() - STOP_PROCESSING(SSobj, src) - attached = null - return ..() - -/obj/item/powersink/update_icon() - icon_state = "powersink[mode == OPERATING]" - -/obj/item/powersink/proc/set_mode(value) - if(value == mode) - return - switch(value) - if(DISCONNECTED) - attached = null - if(mode == OPERATING) - STOP_PROCESSING(SSobj, src) - anchored = FALSE - density = FALSE - - if(CLAMPED_OFF) - if(!attached) - return - if(mode == OPERATING) - STOP_PROCESSING(SSobj, src) - anchored = TRUE - density = TRUE - - if(OPERATING) - if(!attached) - return - START_PROCESSING(SSobj, src) - anchored = TRUE - density = TRUE - - mode = value - update_icon() - set_light(0) - -/obj/item/powersink/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(mode == DISCONNECTED) - var/turf/T = loc - if(isturf(T) && !T.intact) - attached = locate() in T - if(!attached) - to_chat(user, "No exposed cable here to attach to.") - return - else - set_mode(CLAMPED_OFF) - visible_message("[user] attaches [src] to the cable!") - message_admins("Power sink activated by [key_name_admin(user)] at ([x],[y],[z] - JMP)") - log_game("Power sink activated by [key_name(user)] at ([x],[y],[z])") - else - to_chat(user, "Device must be placed over an exposed cable to attach to it.") - else - set_mode(DISCONNECTED) - src.visible_message("[user] detaches [src] from the cable!") - -/obj/item/powersink/attack_ai() - return - -/obj/item/powersink/attack_hand(var/mob/user) - switch(mode) - if(DISCONNECTED) - ..() - if(CLAMPED_OFF) - user.visible_message( \ - "[user] activates \the [src]!", \ - "You activate \the [src].", - "You hear a click.") - message_admins("Power sink activated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(src)]") - log_game("Power sink activated by [key_name(user)] at [AREACOORD(src)]") - set_mode(OPERATING) - - if(OPERATING) - user.visible_message( \ - "[user] deactivates \the [src]!", \ - "You deactivate \the [src].", - "You hear a click.") - set_mode(CLAMPED_OFF) - -/obj/item/powersink/process() - if(!attached) - set_mode(DISCONNECTED) - return - - var/datum/powernet/PN = attached.powernet - if(PN) - set_light(5) - - // found a powernet, so drain up to max power from it - - var/drained = min (drain_rate, attached.newavail()) - attached.add_delayedload(drained) - power_drained += drained - - // if tried to drain more than available on powernet - // now look for APCs and drain their cells - if(drained < drain_rate) - for(var/obj/machinery/power/terminal/T in PN.nodes) - if(istype(T.master, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = T.master - if(A.operating && A.cell) - A.cell.charge = max(0, A.cell.charge - 50) - power_drained += 50 - if(A.charging == 2) // If the cell was full - A.charging = 1 // It's no longer full - if(drained >= drain_rate) - break - - if(power_drained > max_power * 0.98) - if (!admins_warned) - admins_warned = TRUE - message_admins("Power sink at ([x],[y],[z] - JMP) is 95% full. Explosion imminent.") - playsound(src, 'sound/effects/screech.ogg', 100, 1, 1) - - if(power_drained >= max_power) - STOP_PROCESSING(SSobj, src) - explosion(src.loc, 4,8,16,32) - qdel(src) - -#undef DISCONNECTED -#undef CLAMPED_OFF -#undef OPERATING \ No newline at end of file +#define DISCONNECTED 0 +#define CLAMPED_OFF 1 +#define OPERATING 2 + +// Powersink - used to drain station power + +/obj/item/powersink + name = "power sink" + desc = "A nulling power sink which drains energy from electrical systems." + icon = 'icons/obj/device.dmi' + icon_state = "powersink0" + item_state = "electronic" + w_class = WEIGHT_CLASS_BULKY + flags = CONDUCT + throwforce = 5 + throw_speed = 1 + throw_range = 2 + materials = list(MAT_METAL=750) + origin_tech = "powerstorage=5;syndicate=5" + var/drain_rate = 2000000 // amount of power to drain per tick + var/power_drained = 0 // has drained this much power + var/max_power = 6e8 // maximum power that can be drained before exploding + var/mode = 0 // 0 = off, 1=clamped (off), 2=operating + var/admins_warned = FALSE // stop spam, only warn the admins once that we are about to boom + + var/obj/structure/cable/attached // the attached cable + +/obj/item/powersink/Destroy() + STOP_PROCESSING(SSobj, src) + attached = null + return ..() + +/obj/item/powersink/update_icon() + icon_state = "powersink[mode == OPERATING]" + +/obj/item/powersink/proc/set_mode(value) + if(value == mode) + return + switch(value) + if(DISCONNECTED) + attached = null + if(mode == OPERATING) + STOP_PROCESSING(SSobj, src) + anchored = FALSE + density = FALSE + + if(CLAMPED_OFF) + if(!attached) + return + if(mode == OPERATING) + STOP_PROCESSING(SSobj, src) + anchored = TRUE + density = TRUE + + if(OPERATING) + if(!attached) + return + START_PROCESSING(SSobj, src) + anchored = TRUE + density = TRUE + + mode = value + update_icon() + set_light(0) + +/obj/item/powersink/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(mode == DISCONNECTED) + var/turf/T = loc + if(isturf(T) && !T.intact) + attached = locate() in T + if(!attached) + to_chat(user, "No exposed cable here to attach to.") + return + else + set_mode(CLAMPED_OFF) + visible_message("[user] attaches [src] to the cable!") + message_admins("Power sink activated by [key_name_admin(user)] at ([x],[y],[z] - JMP)") + log_game("Power sink activated by [key_name(user)] at ([x],[y],[z])") + else + to_chat(user, "Device must be placed over an exposed cable to attach to it.") + else + set_mode(DISCONNECTED) + src.visible_message("[user] detaches [src] from the cable!") + +/obj/item/powersink/attack_ai() + return + +/obj/item/powersink/attack_hand(var/mob/user) + switch(mode) + if(DISCONNECTED) + ..() + if(CLAMPED_OFF) + user.visible_message( \ + "[user] activates \the [src]!", \ + "You activate \the [src].", + "You hear a click.") + message_admins("Power sink activated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(src)]") + log_game("Power sink activated by [key_name(user)] at [AREACOORD(src)]") + set_mode(OPERATING) + + if(OPERATING) + user.visible_message( \ + "[user] deactivates \the [src]!", \ + "You deactivate \the [src].", + "You hear a click.") + set_mode(CLAMPED_OFF) + +/obj/item/powersink/process() + if(!attached) + set_mode(DISCONNECTED) + return + + var/datum/powernet/PN = attached.powernet + if(PN) + set_light(5) + + // found a powernet, so drain up to max power from it + + var/drained = min (drain_rate, attached.newavail()) + attached.add_delayedload(drained) + power_drained += drained + + // if tried to drain more than available on powernet + // now look for APCs and drain their cells + if(drained < drain_rate) + for(var/obj/machinery/power/terminal/T in PN.nodes) + if(istype(T.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = T.master + if(A.operating && A.cell) + A.cell.charge = max(0, A.cell.charge - 50) + power_drained += 50 + if(A.charging == 2) // If the cell was full + A.charging = 1 // It's no longer full + if(drained >= drain_rate) + break + + if(power_drained > max_power * 0.98) + if (!admins_warned) + admins_warned = TRUE + message_admins("Power sink at ([x],[y],[z] - JMP) is 95% full. Explosion imminent.") + playsound(src, 'sound/effects/screech.ogg', 100, 1, 1) + + if(power_drained >= max_power) + STOP_PROCESSING(SSobj, src) + explosion(src.loc, 4,8,16,32) + qdel(src) + +#undef DISCONNECTED +#undef CLAMPED_OFF +#undef OPERATING diff --git a/code/game/objects/items/devices/radio/beacon.dm b/code/game/objects/items/devices/radio/beacon.dm index 041b21ede10..517f9f595c5 100644 --- a/code/game/objects/items/devices/radio/beacon.dm +++ b/code/game/objects/items/devices/radio/beacon.dm @@ -1,106 +1,106 @@ -/obj/item/radio/beacon - name = "Tracking Beacon" - desc = "A beacon used by a teleporter." - icon_state = "beacon" - item_state = "signaler" - var/code = "Beacon" - origin_tech = "bluespace=1" - var/emagged = 0 - var/syndicate = 0 - var/area_bypass = FALSE - var/cc_beacon = FALSE //set if allowed to teleport to even if on zlevel2 - -/obj/item/radio/beacon/New() - ..() - code = "[code] ([GLOB.beacons.len + 1])" - GLOB.beacons += src - -/obj/item/radio/beacon/Destroy() - GLOB.beacons -= src - return ..() - -/obj/item/radio/beacon/emag_act(user as mob) - if(!emagged) - emagged = 1 - syndicate = 1 - to_chat(user, "The This beacon now only be locked on to by emagged teleporters!") - -/obj/item/radio/beacon/hear_talk() - return - - -/obj/item/radio/beacon/send_hear() - return null - -/obj/item/radio/beacon/verb/alter_signal(t as text) - set name = "Alter Beacon's Signal" - set category = "Object" - set src in usr - - if(usr.stat || usr.restrained()) - return - - code = t - if(isnull(code)) - code = initial(code) - src.add_fingerprint(usr) - return - -/obj/item/radio/beacon/bacon //Probably a better way of doing this, I'm lazy. - -/obj/item/radio/beacon/bacon/proc/digest_delay() - QDEL_IN(src, 600) - -// SINGULO BEACON SPAWNER -/obj/item/radio/beacon/syndicate - name = "suspicious beacon" - desc = "A label on it reads: Activate to have a singularity beacon teleported to your location." - origin_tech = "bluespace=6;syndicate=5" - syndicate = TRUE - var/obj/machinery/computer/syndicate_depot/teleporter/mycomputer - -/obj/item/radio/beacon/syndicate/Destroy() - if(mycomputer) - mycomputer.mybeacon = null - return ..() - -/obj/item/radio/beacon/syndicate/attack_self(mob/user) - if(user) - to_chat(user, "Locked In") - new /obj/machinery/power/singularity_beacon/syndicate( user.loc ) - playsound(src, 'sound/effects/pop.ogg', 100, 1, 1) - qdel(src) - return - -/obj/item/radio/beacon/syndicate/bomb - name = "suspicious beacon" - desc = "A label on it reads: Warning: Activating this device will send a high-ordinance explosive to your location." - origin_tech = "bluespace=5;syndicate=5" - -/obj/item/radio/beacon/syndicate/bomb/attack_self(mob/user) - if(user) - to_chat(user, "Locked In") - new /obj/machinery/syndicatebomb( user.loc ) - playsound(src, 'sound/effects/pop.ogg', 100, 1, 1) - qdel(src) - return - -/obj/item/radio/beacon/engine - desc = "A label on it reads: Warning: This device is used for transportation of high-density objects used for high-yield power generation. Stay away!." - anchored = 1 //Let's not move these around. Some folk might get the idea to use these for assassinations - var/list/enginetype = list() - -/obj/item/radio/beacon/engine/Initialize() - LAZYADD(GLOB.engine_beacon_list, src) - -/obj/item/radio/beacon/engine/tesling - name = "Engine Beacon for Tesla and Singularity" - enginetype = list(ENGTYPE_TESLA, ENGTYPE_SING) - -/obj/item/radio/beacon/engine/tesla - name = "Engine Beacon for Tesla" - enginetype = list(ENGTYPE_TESLA) - -/obj/item/radio/beacon/engine/sing - name = "Engine Beacon for Singularity" - enginetype = list(ENGTYPE_SING) \ No newline at end of file +/obj/item/radio/beacon + name = "Tracking Beacon" + desc = "A beacon used by a teleporter." + icon_state = "beacon" + item_state = "signaler" + var/code = "Beacon" + origin_tech = "bluespace=1" + var/emagged = 0 + var/syndicate = 0 + var/area_bypass = FALSE + var/cc_beacon = FALSE //set if allowed to teleport to even if on zlevel2 + +/obj/item/radio/beacon/New() + ..() + code = "[code] ([GLOB.beacons.len + 1])" + GLOB.beacons += src + +/obj/item/radio/beacon/Destroy() + GLOB.beacons -= src + return ..() + +/obj/item/radio/beacon/emag_act(user as mob) + if(!emagged) + emagged = 1 + syndicate = 1 + to_chat(user, "The This beacon now only be locked on to by emagged teleporters!") + +/obj/item/radio/beacon/hear_talk() + return + + +/obj/item/radio/beacon/send_hear() + return null + +/obj/item/radio/beacon/verb/alter_signal(t as text) + set name = "Alter Beacon's Signal" + set category = "Object" + set src in usr + + if(usr.stat || usr.restrained()) + return + + code = t + if(isnull(code)) + code = initial(code) + src.add_fingerprint(usr) + return + +/obj/item/radio/beacon/bacon //Probably a better way of doing this, I'm lazy. + +/obj/item/radio/beacon/bacon/proc/digest_delay() + QDEL_IN(src, 600) + +// SINGULO BEACON SPAWNER +/obj/item/radio/beacon/syndicate + name = "suspicious beacon" + desc = "A label on it reads: Activate to have a singularity beacon teleported to your location." + origin_tech = "bluespace=6;syndicate=5" + syndicate = TRUE + var/obj/machinery/computer/syndicate_depot/teleporter/mycomputer + +/obj/item/radio/beacon/syndicate/Destroy() + if(mycomputer) + mycomputer.mybeacon = null + return ..() + +/obj/item/radio/beacon/syndicate/attack_self(mob/user) + if(user) + to_chat(user, "Locked In") + new /obj/machinery/power/singularity_beacon/syndicate( user.loc ) + playsound(src, 'sound/effects/pop.ogg', 100, 1, 1) + qdel(src) + return + +/obj/item/radio/beacon/syndicate/bomb + name = "suspicious beacon" + desc = "A label on it reads: Warning: Activating this device will send a high-ordinance explosive to your location." + origin_tech = "bluespace=5;syndicate=5" + +/obj/item/radio/beacon/syndicate/bomb/attack_self(mob/user) + if(user) + to_chat(user, "Locked In") + new /obj/machinery/syndicatebomb( user.loc ) + playsound(src, 'sound/effects/pop.ogg', 100, 1, 1) + qdel(src) + return + +/obj/item/radio/beacon/engine + desc = "A label on it reads: Warning: This device is used for transportation of high-density objects used for high-yield power generation. Stay away!." + anchored = 1 //Let's not move these around. Some folk might get the idea to use these for assassinations + var/list/enginetype = list() + +/obj/item/radio/beacon/engine/Initialize() + LAZYADD(GLOB.engine_beacon_list, src) + +/obj/item/radio/beacon/engine/tesling + name = "Engine Beacon for Tesla and Singularity" + enginetype = list(ENGTYPE_TESLA, ENGTYPE_SING) + +/obj/item/radio/beacon/engine/tesla + name = "Engine Beacon for Tesla" + enginetype = list(ENGTYPE_TESLA) + +/obj/item/radio/beacon/engine/sing + name = "Engine Beacon for Singularity" + enginetype = list(ENGTYPE_SING) diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index 2e53e4ff06a..4b5716d73eb 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -1,113 +1,113 @@ -/obj/item/radio/electropack - name = "electropack" - desc = "Dance my monkeys! DANCE!!!" - icon_state = "electropack0" - item_state = "electropack" - frequency = AIRLOCK_FREQ - flags = CONDUCT - slot_flags = SLOT_BACK - w_class = WEIGHT_CLASS_HUGE - materials = list(MAT_METAL=10000, MAT_GLASS=2500) - var/code = 2 - - is_special = 1 - -/obj/item/radio/electropack/attack_hand(mob/user as mob) - if(src == user.back) - to_chat(user, "You need help taking this off!") - return 0 - . = ..() - -/obj/item/radio/electropack/Destroy() - if(istype(src.loc, /obj/item/assembly/shock_kit)) - var/obj/item/assembly/shock_kit/S = src.loc - if(S.part1 == src) - S.part1 = null - else if(S.part2 == src) - S.part2 = null - master = null - return ..() - -/obj/item/radio/electropack/attackby(obj/item/W as obj, mob/user as mob, params) - ..() - if(istype(W, /obj/item/clothing/head/helmet)) - if(!b_stat) - to_chat(user, "[src] is not ready to be attached!") - return - var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit( user ) - A.icon = 'icons/obj/assemblies.dmi' - - if(!user.unEquip(W)) - to_chat(user, "\the [W] is stuck to your hand, you cannot attach it to \the [src]!") - return - W.loc = A - W.master = A - A.part1 = W - - user.unEquip(src) - loc = A - master = A - A.part2 = src - - user.put_in_hands(A) - A.add_fingerprint(user) - if(src.flags & NODROP) - A.flags |= NODROP - -/obj/item/radio/electropack/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["freq"]) - var/new_frequency = sanitize_frequency(frequency + text2num(href_list["freq"])) - set_frequency(new_frequency) - - else if(href_list["code"]) - code += text2num(href_list["code"]) - code = round(code) - code = Clamp(code, 1, 100) - - else if(href_list["power"]) - on = !on - - add_fingerprint(usr) - -/obj/item/radio/electropack/receive_signal(datum/signal/signal) - if(!signal || signal.encryption != code) - return - - if(ismob(loc) && on) - var/mob/M = loc - var/turf/T = M.loc - if(istype(T, /turf)) - if(!M.moved_recently && M.last_move) - M.moved_recently = 1 - step(M, M.last_move) - sleep(50) - if(M) - M.moved_recently = 0 - to_chat(M, "You feel a sharp shock!") - do_sparks(3, 1, M) - - M.Weaken(5) - - if(master) - master.receive_signal() - return - - -/obj/item/radio/electropack/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "radio_electro.tmpl", "[name]", 400, 500) - ui.open() - ui.set_auto_update(1) - -/obj/item/radio/electropack/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - data["power"] = on - data["freq"] = format_frequency(frequency) - data["code"] = code - - return data +/obj/item/radio/electropack + name = "electropack" + desc = "Dance my monkeys! DANCE!!!" + icon_state = "electropack0" + item_state = "electropack" + frequency = AIRLOCK_FREQ + flags = CONDUCT + slot_flags = SLOT_BACK + w_class = WEIGHT_CLASS_HUGE + materials = list(MAT_METAL=10000, MAT_GLASS=2500) + var/code = 2 + + is_special = 1 + +/obj/item/radio/electropack/attack_hand(mob/user as mob) + if(src == user.back) + to_chat(user, "You need help taking this off!") + return 0 + . = ..() + +/obj/item/radio/electropack/Destroy() + if(istype(src.loc, /obj/item/assembly/shock_kit)) + var/obj/item/assembly/shock_kit/S = src.loc + if(S.part1 == src) + S.part1 = null + else if(S.part2 == src) + S.part2 = null + master = null + return ..() + +/obj/item/radio/electropack/attackby(obj/item/W as obj, mob/user as mob, params) + ..() + if(istype(W, /obj/item/clothing/head/helmet)) + if(!b_stat) + to_chat(user, "[src] is not ready to be attached!") + return + var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit( user ) + A.icon = 'icons/obj/assemblies.dmi' + + if(!user.unEquip(W)) + to_chat(user, "\the [W] is stuck to your hand, you cannot attach it to \the [src]!") + return + W.loc = A + W.master = A + A.part1 = W + + user.unEquip(src) + loc = A + master = A + A.part2 = src + + user.put_in_hands(A) + A.add_fingerprint(user) + if(src.flags & NODROP) + A.flags |= NODROP + +/obj/item/radio/electropack/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["freq"]) + var/new_frequency = sanitize_frequency(frequency + text2num(href_list["freq"])) + set_frequency(new_frequency) + + else if(href_list["code"]) + code += text2num(href_list["code"]) + code = round(code) + code = Clamp(code, 1, 100) + + else if(href_list["power"]) + on = !on + + add_fingerprint(usr) + +/obj/item/radio/electropack/receive_signal(datum/signal/signal) + if(!signal || signal.encryption != code) + return + + if(ismob(loc) && on) + var/mob/M = loc + var/turf/T = M.loc + if(istype(T, /turf)) + if(!M.moved_recently && M.last_move) + M.moved_recently = 1 + step(M, M.last_move) + sleep(50) + if(M) + M.moved_recently = 0 + to_chat(M, "You feel a sharp shock!") + do_sparks(3, 1, M) + + M.Weaken(5) + + if(master) + master.receive_signal() + return + + +/obj/item/radio/electropack/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "radio_electro.tmpl", "[name]", 400, 500) + ui.open() + ui.set_auto_update(1) + +/obj/item/radio/electropack/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + data["power"] = on + data["freq"] = format_frequency(frequency) + data["code"] = code + + return data diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index e6716a4f5c8..72e584e91aa 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -1,426 +1,426 @@ -/obj/item/radio/headset - name = "radio headset" - desc = "An updated, modular intercom that fits over the head. Takes encryption keys." - var/radio_desc = "" - icon_state = "headset" - item_state = "headset" - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/ears.dmi', - "Vox Armalis" = 'icons/mob/species/armalis/ears.dmi' - ) //We read you loud and skree-er. - materials = list(MAT_METAL=75) - subspace_transmission = TRUE - canhear_range = 0 // can't hear headsets from very far away - - slot_flags = SLOT_EARS - var/translate_binary = FALSE - var/translate_hive = FALSE - var/obj/item/encryptionkey/keyslot1 = null - var/obj/item/encryptionkey/keyslot2 = null - - var/ks1type = null - var/ks2type = null - dog_fashion = null - -/obj/item/radio/headset/New() - ..() - internal_channels.Cut() - -/obj/item/radio/headset/Initialize() - ..() - - if(ks1type) - keyslot1 = new ks1type(src) - if(keyslot1.syndie) - syndiekey = keyslot1 - if(ks2type) - keyslot2 = new ks2type(src) - if(keyslot2.syndie) - syndiekey = keyslot2 - - recalculateChannels(TRUE) - -/obj/item/radio/headset/Destroy() - QDEL_NULL(keyslot1) - QDEL_NULL(keyslot2) - return ..() - -/obj/item/radio/headset/list_channels(var/mob/user) - return list_secure_channels() - -/obj/item/radio/headset/examine(mob/user) - . = ..() - if(in_range(src, user) && radio_desc) - . += "The following channels are available:" - . += radio_desc - -/obj/item/radio/headset/handle_message_mode(mob/living/M as mob, list/message_pieces, channel) - if(channel == "special") - if(translate_binary) - var/datum/language/binary = GLOB.all_languages["Robot Talk"] - binary.broadcast(M, strip_prefixes(multilingual_to_message(message_pieces))) - return RADIO_CONNECTION_NON_SUBSPACE - if(translate_hive) - var/datum/language/hivemind = GLOB.all_languages["Hivemind"] - hivemind.broadcast(M, strip_prefixes(multilingual_to_message(message_pieces))) - return RADIO_CONNECTION_NON_SUBSPACE - return RADIO_CONNECTION_FAIL - - return ..() - -/obj/item/radio/headset/is_listening() - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.l_ear == src || H.r_ear == src) - return ..() - else if(isanimal(loc) || isAI(loc)) - return ..() - - return FALSE - -/obj/item/radio/headset/alt - name = "bowman headset" - desc = "An updated, modular intercom that fits over the head. Takes encryption keys. Protects ears from flashbangs." - flags = EARBANGPROTECT - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/syndicate - origin_tech = "syndicate=3" - ks1type = /obj/item/encryptionkey/syndicate/nukeops - -/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection - name = "syndicate headset" - desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs." - flags = EARBANGPROTECT - origin_tech = "syndicate=3" - icon_state = "syndie_headset" - item_state = "syndie_headset" - -/obj/item/radio/headset/syndicate/syndteam - ks1type = /obj/item/encryptionkey/syndteam - -/obj/item/radio/headset/syndicate/alt/syndteam - ks1type = /obj/item/encryptionkey/syndteam - -/obj/item/radio/headset/binary - origin_tech = "syndicate=3" - ks1type = /obj/item/encryptionkey/binary - -/obj/item/radio/headset/headset_sec - name = "security radio headset" - desc = "This is used by your elite security force." - icon_state = "sec_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_sec - -/obj/item/radio/headset/headset_sec/alt - name = "security bowman headset" - desc = "This is used by your elite security force. Protects ears from flashbangs." - flags = EARBANGPROTECT - icon_state = "sec_headset_alt" - item_state = "sec_headset_alt" - -/obj/item/radio/headset/headset_eng - name = "engineering radio headset" - desc = "When the engineers wish to chat like girls." - icon_state = "eng_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_eng - -/obj/item/radio/headset/headset_rob - name = "robotics radio headset" - desc = "Made specifically for the roboticists who cannot decide between departments." - icon_state = "rob_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_rob - -/obj/item/radio/headset/headset_med - name = "medical radio headset" - desc = "A headset for the trained staff of the medbay." - icon_state = "med_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_med - -/obj/item/radio/headset/headset_sci - name = "science radio headset" - desc = "A sciency headset. Like usual." - icon_state = "sci_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_sci - -/obj/item/radio/headset/headset_medsci - name = "medical research radio headset" - desc = "A headset that is a result of the mating between medical and science." - icon_state = "medsci_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_medsci - -/obj/item/radio/headset/headset_com - name = "command radio headset" - desc = "A headset with a commanding channel." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_com - -/obj/item/radio/headset/heads/captain - name = "captain's headset" - desc = "The headset of the boss." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/captain - -/obj/item/radio/headset/heads/captain/alt - name = "\proper the captain's bowman headset" - desc = "The headset of the boss. Protects ears from flashbangs." - flags = EARBANGPROTECT - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/rd - name = "Research Director's headset" - desc = "Headset of the researching God." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/rd - -/obj/item/radio/headset/heads/hos - name = "head of security's headset" - desc = "The headset of the man who protects your worthless lives." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/hos - -/obj/item/radio/headset/heads/hos/alt - name = "\proper the head of security's bowman headset" - desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs." - flags = EARBANGPROTECT - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/ce - name = "chief engineer's headset" - desc = "The headset of the guy who is in charge of morons." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/ce - -/obj/item/radio/headset/heads/cmo - name = "chief medical officer's headset" - desc = "The headset of the highly trained medical chief." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/cmo - -/obj/item/radio/headset/heads/hop - name = "head of personnel's headset" - desc = "The headset of the guy who will one day be captain." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/hop - -/obj/item/radio/headset/headset_cargo - name = "supply radio headset" - desc = "A headset used by the cargo department." - icon_state = "cargo_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_cargo - -/obj/item/radio/headset/headset_cargo/mining - name = "mining radio headset" - desc = "Headset used by shaft miners." - icon_state = "mine_headset" - -/obj/item/radio/headset/headset_service - name = "service radio headset" - desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." - icon_state = "srv_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/headset_service - -/obj/item/radio/headset/heads/ntrep - name = "nanotrasen representative's headset" - desc = "The headset of the Nanotrasen Representative." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/ntrep - -/obj/item/radio/headset/heads/magistrate - name = "magistrate's headset" - desc = "The headset of the Magistrate." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/magistrate - -/obj/item/radio/headset/heads/magistrate/alt - name = "\proper magistrate's bowman headset" - desc = "The headset of the Magistrate. Protects ears from flashbangs." - flags = EARBANGPROTECT - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/blueshield - name = "blueshield's headset" - desc = "The headset of the Blueshield." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/blueshield - -/obj/item/radio/headset/heads/blueshield/alt - name = "\proper blueshield's bowman headset" - desc = "The headset of the Blueshield. Protects ears from flashbangs." - flags = EARBANGPROTECT - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/ert - name = "emergency response team headset" - desc = "The headset of the boss's boss." - icon_state = "com_headset" - item_state = "headset" - ks2type = /obj/item/encryptionkey/ert - -/obj/item/radio/headset/ert/alt - name = "\proper emergency response team's bowman headset" - desc = "The headset of the boss. Protects ears from flashbangs." - flags = EARBANGPROTECT - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/centcom - name = "\proper centcom officer's bowman headset" - desc = "The headset of final authority. Protects ears from flashbangs." - flags = EARBANGPROTECT - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - ks2type = /obj/item/encryptionkey/centcom - -/obj/item/radio/headset/heads/ai_integrated //No need to care about icons, it should be hidden inside the AI anyway. - name = "\improper AI subspace transceiver" - desc = "Integrated AI radio transceiver." - icon = 'icons/obj/robot_component.dmi' - icon_state = "radio" - item_state = "headset" - ks2type = /obj/item/encryptionkey/heads/ai_integrated - var/myAi = null // Atlantis: Reference back to the AI which has this radio. - var/disabledAi = 0 // Atlantis: Used to manually disable AI's integrated radio via intellicard menu. - -/obj/item/radio/headset/heads/ai_integrated/is_listening() - if(disabledAi) - return FALSE - return ..() - -/obj/item/radio/headset/attackby(obj/item/W as obj, mob/user as mob) - if(istype(W, /obj/item/encryptionkey/)) - user.set_machine(src) - if(keyslot1 && keyslot2) - to_chat(user, "The headset can't hold another key!") - return - - if(!keyslot1) - user.drop_item() - W.loc = src - keyslot1 = W - else - user.drop_item() - W.loc = src - keyslot2 = W - recalculateChannels() - else - return ..() - -/obj/item/radio/headset/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = 0)) - return - user.set_machine(src) - if(keyslot1 || keyslot2) - - for(var/ch_name in channels) - SSradio.remove_object(src, SSradio.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - if(keyslot1) - var/turf/T = get_turf(user) - if(T) - keyslot1.loc = T - keyslot1 = null - if(keyslot2) - var/turf/T = get_turf(user) - if(T) - keyslot2.loc = T - keyslot2 = null - - recalculateChannels() - to_chat(user, "You pop out the encryption keys in the headset!") - I.play_tool_sound(user, I.tool_volume) - else - to_chat(user, "This headset doesn't have any encryption keys! How useless...") - -/obj/item/radio/headset/proc/recalculateChannels(var/setDescription = FALSE) - channels = list() - translate_binary = FALSE - translate_hive = FALSE - syndiekey = null - - if(keyslot1) - for(var/ch_name in keyslot1.channels) - if(ch_name in channels) - continue - channels += ch_name - channels[ch_name] = keyslot1.channels[ch_name] - - if(keyslot1.translate_binary) - translate_binary = TRUE - - if(keyslot1.translate_hive) - translate_hive = TRUE - - if(keyslot1.syndie) - syndiekey = keyslot1 - - if(keyslot2) - for(var/ch_name in keyslot2.channels) - if(ch_name in channels) - continue - channels += ch_name - channels[ch_name] = keyslot2.channels[ch_name] - - if(keyslot2.translate_binary) - translate_binary = TRUE - - if(keyslot2.translate_hive) - translate_hive = TRUE - - if(keyslot2.syndie) - syndiekey = keyslot2 - - - for(var/ch_name in channels) - if(!SSradio) - name = "broken radio headset" - return - - secure_radio_connections[ch_name] = SSradio.add_object(src, SSradio.radiochannels[ch_name], RADIO_CHAT) - - if(setDescription) - setupRadioDescription() - - return - -/obj/item/radio/headset/proc/setupRadioDescription() - var/radio_text = "" - for(var/i = 1 to channels.len) - var/channel = channels[i] - var/key = get_radio_key_from_channel(channel) - radio_text += "[key] - [channel]" - if(i != channels.len) - radio_text += ", " - - radio_desc = radio_text - -/obj/item/radio/headset/proc/make_syndie() // Turns normal radios into Syndicate radios! - qdel(keyslot1) - keyslot1 = new /obj/item/encryptionkey/syndicate - syndiekey = keyslot1 - recalculateChannels() \ No newline at end of file +/obj/item/radio/headset + name = "radio headset" + desc = "An updated, modular intercom that fits over the head. Takes encryption keys." + var/radio_desc = "" + icon_state = "headset" + item_state = "headset" + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/ears.dmi', + "Vox Armalis" = 'icons/mob/species/armalis/ears.dmi' + ) //We read you loud and skree-er. + materials = list(MAT_METAL=75) + subspace_transmission = TRUE + canhear_range = 0 // can't hear headsets from very far away + + slot_flags = SLOT_EARS + var/translate_binary = FALSE + var/translate_hive = FALSE + var/obj/item/encryptionkey/keyslot1 = null + var/obj/item/encryptionkey/keyslot2 = null + + var/ks1type = null + var/ks2type = null + dog_fashion = null + +/obj/item/radio/headset/New() + ..() + internal_channels.Cut() + +/obj/item/radio/headset/Initialize() + ..() + + if(ks1type) + keyslot1 = new ks1type(src) + if(keyslot1.syndie) + syndiekey = keyslot1 + if(ks2type) + keyslot2 = new ks2type(src) + if(keyslot2.syndie) + syndiekey = keyslot2 + + recalculateChannels(TRUE) + +/obj/item/radio/headset/Destroy() + QDEL_NULL(keyslot1) + QDEL_NULL(keyslot2) + return ..() + +/obj/item/radio/headset/list_channels(var/mob/user) + return list_secure_channels() + +/obj/item/radio/headset/examine(mob/user) + . = ..() + if(in_range(src, user) && radio_desc) + . += "The following channels are available:" + . += radio_desc + +/obj/item/radio/headset/handle_message_mode(mob/living/M as mob, list/message_pieces, channel) + if(channel == "special") + if(translate_binary) + var/datum/language/binary = GLOB.all_languages["Robot Talk"] + binary.broadcast(M, strip_prefixes(multilingual_to_message(message_pieces))) + return RADIO_CONNECTION_NON_SUBSPACE + if(translate_hive) + var/datum/language/hivemind = GLOB.all_languages["Hivemind"] + hivemind.broadcast(M, strip_prefixes(multilingual_to_message(message_pieces))) + return RADIO_CONNECTION_NON_SUBSPACE + return RADIO_CONNECTION_FAIL + + return ..() + +/obj/item/radio/headset/is_listening() + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.l_ear == src || H.r_ear == src) + return ..() + else if(isanimal(loc) || isAI(loc)) + return ..() + + return FALSE + +/obj/item/radio/headset/alt + name = "bowman headset" + desc = "An updated, modular intercom that fits over the head. Takes encryption keys. Protects ears from flashbangs." + flags = EARBANGPROTECT + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/syndicate + origin_tech = "syndicate=3" + ks1type = /obj/item/encryptionkey/syndicate/nukeops + +/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection + name = "syndicate headset" + desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs." + flags = EARBANGPROTECT + origin_tech = "syndicate=3" + icon_state = "syndie_headset" + item_state = "syndie_headset" + +/obj/item/radio/headset/syndicate/syndteam + ks1type = /obj/item/encryptionkey/syndteam + +/obj/item/radio/headset/syndicate/alt/syndteam + ks1type = /obj/item/encryptionkey/syndteam + +/obj/item/radio/headset/binary + origin_tech = "syndicate=3" + ks1type = /obj/item/encryptionkey/binary + +/obj/item/radio/headset/headset_sec + name = "security radio headset" + desc = "This is used by your elite security force." + icon_state = "sec_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_sec + +/obj/item/radio/headset/headset_sec/alt + name = "security bowman headset" + desc = "This is used by your elite security force. Protects ears from flashbangs." + flags = EARBANGPROTECT + icon_state = "sec_headset_alt" + item_state = "sec_headset_alt" + +/obj/item/radio/headset/headset_eng + name = "engineering radio headset" + desc = "When the engineers wish to chat like girls." + icon_state = "eng_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_eng + +/obj/item/radio/headset/headset_rob + name = "robotics radio headset" + desc = "Made specifically for the roboticists who cannot decide between departments." + icon_state = "rob_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_rob + +/obj/item/radio/headset/headset_med + name = "medical radio headset" + desc = "A headset for the trained staff of the medbay." + icon_state = "med_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_med + +/obj/item/radio/headset/headset_sci + name = "science radio headset" + desc = "A sciency headset. Like usual." + icon_state = "sci_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_sci + +/obj/item/radio/headset/headset_medsci + name = "medical research radio headset" + desc = "A headset that is a result of the mating between medical and science." + icon_state = "medsci_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_medsci + +/obj/item/radio/headset/headset_com + name = "command radio headset" + desc = "A headset with a commanding channel." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_com + +/obj/item/radio/headset/heads/captain + name = "captain's headset" + desc = "The headset of the boss." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/captain + +/obj/item/radio/headset/heads/captain/alt + name = "\proper the captain's bowman headset" + desc = "The headset of the boss. Protects ears from flashbangs." + flags = EARBANGPROTECT + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/rd + name = "Research Director's headset" + desc = "Headset of the researching God." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/rd + +/obj/item/radio/headset/heads/hos + name = "head of security's headset" + desc = "The headset of the man who protects your worthless lives." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/hos + +/obj/item/radio/headset/heads/hos/alt + name = "\proper the head of security's bowman headset" + desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs." + flags = EARBANGPROTECT + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/ce + name = "chief engineer's headset" + desc = "The headset of the guy who is in charge of morons." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/ce + +/obj/item/radio/headset/heads/cmo + name = "chief medical officer's headset" + desc = "The headset of the highly trained medical chief." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/cmo + +/obj/item/radio/headset/heads/hop + name = "head of personnel's headset" + desc = "The headset of the guy who will one day be captain." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/hop + +/obj/item/radio/headset/headset_cargo + name = "supply radio headset" + desc = "A headset used by the cargo department." + icon_state = "cargo_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_cargo + +/obj/item/radio/headset/headset_cargo/mining + name = "mining radio headset" + desc = "Headset used by shaft miners." + icon_state = "mine_headset" + +/obj/item/radio/headset/headset_service + name = "service radio headset" + desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." + icon_state = "srv_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/headset_service + +/obj/item/radio/headset/heads/ntrep + name = "nanotrasen representative's headset" + desc = "The headset of the Nanotrasen Representative." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/ntrep + +/obj/item/radio/headset/heads/magistrate + name = "magistrate's headset" + desc = "The headset of the Magistrate." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/magistrate + +/obj/item/radio/headset/heads/magistrate/alt + name = "\proper magistrate's bowman headset" + desc = "The headset of the Magistrate. Protects ears from flashbangs." + flags = EARBANGPROTECT + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/blueshield + name = "blueshield's headset" + desc = "The headset of the Blueshield." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/blueshield + +/obj/item/radio/headset/heads/blueshield/alt + name = "\proper blueshield's bowman headset" + desc = "The headset of the Blueshield. Protects ears from flashbangs." + flags = EARBANGPROTECT + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/ert + name = "emergency response team headset" + desc = "The headset of the boss's boss." + icon_state = "com_headset" + item_state = "headset" + ks2type = /obj/item/encryptionkey/ert + +/obj/item/radio/headset/ert/alt + name = "\proper emergency response team's bowman headset" + desc = "The headset of the boss. Protects ears from flashbangs." + flags = EARBANGPROTECT + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/centcom + name = "\proper centcom officer's bowman headset" + desc = "The headset of final authority. Protects ears from flashbangs." + flags = EARBANGPROTECT + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + ks2type = /obj/item/encryptionkey/centcom + +/obj/item/radio/headset/heads/ai_integrated //No need to care about icons, it should be hidden inside the AI anyway. + name = "\improper AI subspace transceiver" + desc = "Integrated AI radio transceiver." + icon = 'icons/obj/robot_component.dmi' + icon_state = "radio" + item_state = "headset" + ks2type = /obj/item/encryptionkey/heads/ai_integrated + var/myAi = null // Atlantis: Reference back to the AI which has this radio. + var/disabledAi = 0 // Atlantis: Used to manually disable AI's integrated radio via intellicard menu. + +/obj/item/radio/headset/heads/ai_integrated/is_listening() + if(disabledAi) + return FALSE + return ..() + +/obj/item/radio/headset/attackby(obj/item/W as obj, mob/user as mob) + if(istype(W, /obj/item/encryptionkey/)) + user.set_machine(src) + if(keyslot1 && keyslot2) + to_chat(user, "The headset can't hold another key!") + return + + if(!keyslot1) + user.drop_item() + W.loc = src + keyslot1 = W + else + user.drop_item() + W.loc = src + keyslot2 = W + recalculateChannels() + else + return ..() + +/obj/item/radio/headset/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = 0)) + return + user.set_machine(src) + if(keyslot1 || keyslot2) + + for(var/ch_name in channels) + SSradio.remove_object(src, SSradio.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + if(keyslot1) + var/turf/T = get_turf(user) + if(T) + keyslot1.loc = T + keyslot1 = null + if(keyslot2) + var/turf/T = get_turf(user) + if(T) + keyslot2.loc = T + keyslot2 = null + + recalculateChannels() + to_chat(user, "You pop out the encryption keys in the headset!") + I.play_tool_sound(user, I.tool_volume) + else + to_chat(user, "This headset doesn't have any encryption keys! How useless...") + +/obj/item/radio/headset/proc/recalculateChannels(var/setDescription = FALSE) + channels = list() + translate_binary = FALSE + translate_hive = FALSE + syndiekey = null + + if(keyslot1) + for(var/ch_name in keyslot1.channels) + if(ch_name in channels) + continue + channels += ch_name + channels[ch_name] = keyslot1.channels[ch_name] + + if(keyslot1.translate_binary) + translate_binary = TRUE + + if(keyslot1.translate_hive) + translate_hive = TRUE + + if(keyslot1.syndie) + syndiekey = keyslot1 + + if(keyslot2) + for(var/ch_name in keyslot2.channels) + if(ch_name in channels) + continue + channels += ch_name + channels[ch_name] = keyslot2.channels[ch_name] + + if(keyslot2.translate_binary) + translate_binary = TRUE + + if(keyslot2.translate_hive) + translate_hive = TRUE + + if(keyslot2.syndie) + syndiekey = keyslot2 + + + for(var/ch_name in channels) + if(!SSradio) + name = "broken radio headset" + return + + secure_radio_connections[ch_name] = SSradio.add_object(src, SSradio.radiochannels[ch_name], RADIO_CHAT) + + if(setDescription) + setupRadioDescription() + + return + +/obj/item/radio/headset/proc/setupRadioDescription() + var/radio_text = "" + for(var/i = 1 to channels.len) + var/channel = channels[i] + var/key = get_radio_key_from_channel(channel) + radio_text += "[key] - [channel]" + if(i != channels.len) + radio_text += ", " + + radio_desc = radio_text + +/obj/item/radio/headset/proc/make_syndie() // Turns normal radios into Syndicate radios! + qdel(keyslot1) + keyslot1 = new /obj/item/encryptionkey/syndicate + syndiekey = keyslot1 + recalculateChannels() diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index 6ae33740cb0..fbfe984d35f 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -1,274 +1,274 @@ -/obj/item/radio/intercom - name = "station intercom (General)" - desc = "Talk through this." - icon_state = "intercom" - anchored = 1 - w_class = WEIGHT_CLASS_BULKY - canhear_range = 2 - flags = CONDUCT - var/number = 0 - var/circuitry_installed = 1 - var/last_tick //used to delay the powercheck - var/buildstage = 0 - dog_fashion = null - -/obj/item/radio/intercom/custom - name = "station intercom (Custom)" - broadcasting = 0 - listening = 0 - -/obj/item/radio/intercom/interrogation - name = "station intercom (Interrogation)" - frequency = AIRLOCK_FREQ - -/obj/item/radio/intercom/private - name = "station intercom (Private)" - frequency = AI_FREQ - -/obj/item/radio/intercom/command - name = "station intercom (Command)" - frequency = COMM_FREQ - -/obj/item/radio/intercom/specops - name = "\improper Special Operations intercom" - frequency = ERT_FREQ - -/obj/item/radio/intercom/department - canhear_range = 5 - broadcasting = 0 - listening = 1 - -/obj/item/radio/intercom/department/medbay - name = "station intercom (Medbay)" - frequency = MED_I_FREQ - -/obj/item/radio/intercom/department/security - name = "station intercom (Security)" - frequency = SEC_I_FREQ - -/obj/item/radio/intercom/New(turf/loc, ndir, building = 3) - ..() - buildstage = building - if(buildstage) - START_PROCESSING(SSobj, src) - else - if(ndir) - pixel_x = (ndir & EAST|WEST) ? (ndir == EAST ? 28 : -28) : 0 - pixel_y = (ndir & NORTH|SOUTH) ? (ndir == NORTH ? 28 : -28) : 0 - dir=ndir - b_stat=1 - on = 0 - GLOB.global_intercoms.Add(src) - update_icon() - -/obj/item/radio/intercom/department/medbay/New() - ..() - internal_channels = default_medbay_channels.Copy() - -/obj/item/radio/intercom/department/security/New() - ..() - internal_channels = list( - num2text(PUB_FREQ) = list(), - num2text(SEC_I_FREQ) = list(ACCESS_SECURITY) - ) - -/obj/item/radio/intercom/syndicate - name = "illicit intercom" - desc = "Talk through this. Evilly" - frequency = SYND_FREQ - subspace_transmission = TRUE - syndiekey = new /obj/item/encryptionkey/syndicate/nukeops - -/obj/item/radio/intercom/syndicate/New() - ..() - internal_channels[num2text(SYND_FREQ)] = list(ACCESS_SYNDICATE) - -/obj/item/radio/intercom/pirate - name = "pirate radio intercom" - desc = "You wouldn't steal a space shuttle. Piracy. It's a crime!" - subspace_transmission = 1 - -/obj/item/radio/intercom/pirate/New() - ..() - internal_channels.Cut() - internal_channels = list( - num2text(PUB_FREQ) = list(), - num2text(AI_FREQ) = list(), - num2text(COMM_FREQ)= list(), - num2text(ENG_FREQ) = list(), - num2text(MED_FREQ) = list(), - num2text(MED_I_FREQ)=list(), - num2text(SEC_FREQ) = list(), - num2text(SEC_I_FREQ)=list(), - num2text(SCI_FREQ) = list(), - num2text(SUP_FREQ) = list(), - num2text(SRV_FREQ) = list() - ) - -/obj/item/radio/intercom/Destroy() - STOP_PROCESSING(SSobj, src) - GLOB.global_intercoms.Remove(src) - return ..() - -/obj/item/radio/intercom/attack_ai(mob/user as mob) - add_hiddenprint(user) - add_fingerprint(user) - spawn(0) - attack_self(user) - -/obj/item/radio/intercom/attack_hand(mob/user as mob) - add_fingerprint(user) - spawn(0) - attack_self(user) - -/obj/item/radio/intercom/receive_range(freq, level) - if(!is_listening()) - return -1 - if(!(0 in level)) - var/turf/position = get_turf(src) - // TODO: Integrate radio with the space manager - if(isnull(position) || !(position.z in level)) - return -1 - if(freq in SSradio.ANTAG_FREQS) - if(!(syndiekey)) - return -1//Prevents broadcast of messages over devices lacking the encryption - - return canhear_range - -/obj/item/radio/intercom/attackby(obj/item/W as obj, mob/user as mob) - if(istype(W, /obj/item/stack/tape_roll)) //eww - return - else if(iscoil(W) && buildstage == 1) - var/obj/item/stack/cable_coil/coil = W - if(coil.amount < 5) - to_chat(user, "You need more cable for this!") - return - if(do_after(user, 10 * coil.toolspeed, target = src) && buildstage == 1) - coil.use(5) - to_chat(user, "You wire \the [src]!") - buildstage = 2 - return 1 - else if(istype(W,/obj/item/intercom_electronics) && buildstage == 0) - playsound(get_turf(src), W.usesound, 50, 1) - if(do_after(user, 10 * W.toolspeed, target = src) && buildstage == 0) - qdel(W) - to_chat(user, "You insert \the [W] into \the [src]!") - buildstage = 1 - return 1 - else - return ..() - -/obj/item/radio/intercom/crowbar_act(mob/user, obj/item/I) - if(buildstage != 1) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - to_chat(user, "You begin removing the electronics...") - if(!I.use_tool(src, user, 10, volume = I.tool_volume) || buildstage != 1) - return - new /obj/item/intercom_electronics(get_turf(src)) - to_chat(user, "The circuit board pops out!") - buildstage = 0 - -/obj/item/radio/intercom/screwdriver_act(mob/user, obj/item/I) - if(buildstage != 2) - return ..() - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(!I.use_tool(src, user, 10, volume = I.tool_volume) || buildstage != 2) - return - update_icon() - on = 1 - b_stat = 0 - buildstage = 3 - to_chat(user, "You secure the electronics!") - update_icon() - START_PROCESSING(SSobj, src) - for(var/i, i<= 5, i++) - wires.UpdateCut(i,1) - -/obj/item/radio/intercom/wirecutter_act(mob/user, obj/item/I) - if(!(buildstage == 3 && b_stat && wires.IsAllCut())) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - WIRECUTTER_SNIP_MESSAGE - new /obj/item/stack/cable_coil(get_turf(src),5) - on = 0 - b_stat = 1 - buildstage = 1 - update_icon() - STOP_PROCESSING(SSobj, src) - -/obj/item/radio/intercom/welder_act(mob/user, obj/item/I) - if(!buildstage) - return - . = TRUE - if(!I.tool_use_check(user, 3)) - return - to_chat(user, "You start slicing [src] from the wall...") - if(I.use_tool(src, user, 10, amount = 3, volume = I.tool_volume)) - to_chat(user, "You cut [src] free from the wall!") - new /obj/item/mounted/frame/intercom(get_turf(src)) - qdel(src) - -/obj/item/radio/intercom/update_icon() - if(!circuitry_installed) - icon_state="intercom-frame" - return - icon_state = "intercom[!on?"-p":""][b_stat ? "-open":""]" - -/obj/item/radio/intercom/process() - if(((world.timeofday - last_tick) > 30) || ((world.timeofday - last_tick) < 0)) - last_tick = world.timeofday - - - if(!src.loc) - on = 0 - else - var/area/A = get_area(src) - if(!A) - on = 0 - else - on = A.powered(EQUIP) // set "on" to the power status - update_icon() - -/obj/item/intercom_electronics - name = "intercom electronics" - icon = 'icons/obj/doors/door_assembly.dmi' - icon_state = "door_electronics" - desc = "Looks like a circuit. Probably is." - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=50, MAT_GLASS=50) - origin_tech = "engineering=2;programming=1" - toolspeed = 1 - usesound = 'sound/items/deconstruct.ogg' - -/obj/item/radio/intercom/locked - var/locked_frequency - -/obj/item/radio/intercom/locked/set_frequency(var/frequency) - if(frequency == locked_frequency) - ..(locked_frequency) - -/obj/item/radio/intercom/locked/list_channels() - return "" - -/obj/item/radio/intercom/locked/ai_private - name = "\improper AI intercom" - frequency = AI_FREQ - -/obj/item/radio/intercom/locked/confessional - name = "confessional intercom" - frequency = 1480 - -/obj/item/radio/intercom/locked/prison - name = "\improper prison intercom" - desc = "Talk through this. It looks like it has been modified to not broadcast." - -/obj/item/radio/intercom/locked/prison/New() - ..() - wires.CutWireIndex(RADIO_WIRE_TRANSMIT) +/obj/item/radio/intercom + name = "station intercom (General)" + desc = "Talk through this." + icon_state = "intercom" + anchored = 1 + w_class = WEIGHT_CLASS_BULKY + canhear_range = 2 + flags = CONDUCT + var/number = 0 + var/circuitry_installed = 1 + var/last_tick //used to delay the powercheck + var/buildstage = 0 + dog_fashion = null + +/obj/item/radio/intercom/custom + name = "station intercom (Custom)" + broadcasting = 0 + listening = 0 + +/obj/item/radio/intercom/interrogation + name = "station intercom (Interrogation)" + frequency = AIRLOCK_FREQ + +/obj/item/radio/intercom/private + name = "station intercom (Private)" + frequency = AI_FREQ + +/obj/item/radio/intercom/command + name = "station intercom (Command)" + frequency = COMM_FREQ + +/obj/item/radio/intercom/specops + name = "\improper Special Operations intercom" + frequency = ERT_FREQ + +/obj/item/radio/intercom/department + canhear_range = 5 + broadcasting = 0 + listening = 1 + +/obj/item/radio/intercom/department/medbay + name = "station intercom (Medbay)" + frequency = MED_I_FREQ + +/obj/item/radio/intercom/department/security + name = "station intercom (Security)" + frequency = SEC_I_FREQ + +/obj/item/radio/intercom/New(turf/loc, ndir, building = 3) + ..() + buildstage = building + if(buildstage) + START_PROCESSING(SSobj, src) + else + if(ndir) + pixel_x = (ndir & EAST|WEST) ? (ndir == EAST ? 28 : -28) : 0 + pixel_y = (ndir & NORTH|SOUTH) ? (ndir == NORTH ? 28 : -28) : 0 + dir=ndir + b_stat=1 + on = 0 + GLOB.global_intercoms.Add(src) + update_icon() + +/obj/item/radio/intercom/department/medbay/New() + ..() + internal_channels = default_medbay_channels.Copy() + +/obj/item/radio/intercom/department/security/New() + ..() + internal_channels = list( + num2text(PUB_FREQ) = list(), + num2text(SEC_I_FREQ) = list(ACCESS_SECURITY) + ) + +/obj/item/radio/intercom/syndicate + name = "illicit intercom" + desc = "Talk through this. Evilly" + frequency = SYND_FREQ + subspace_transmission = TRUE + syndiekey = new /obj/item/encryptionkey/syndicate/nukeops + +/obj/item/radio/intercom/syndicate/New() + ..() + internal_channels[num2text(SYND_FREQ)] = list(ACCESS_SYNDICATE) + +/obj/item/radio/intercom/pirate + name = "pirate radio intercom" + desc = "You wouldn't steal a space shuttle. Piracy. It's a crime!" + subspace_transmission = 1 + +/obj/item/radio/intercom/pirate/New() + ..() + internal_channels.Cut() + internal_channels = list( + num2text(PUB_FREQ) = list(), + num2text(AI_FREQ) = list(), + num2text(COMM_FREQ)= list(), + num2text(ENG_FREQ) = list(), + num2text(MED_FREQ) = list(), + num2text(MED_I_FREQ)=list(), + num2text(SEC_FREQ) = list(), + num2text(SEC_I_FREQ)=list(), + num2text(SCI_FREQ) = list(), + num2text(SUP_FREQ) = list(), + num2text(SRV_FREQ) = list() + ) + +/obj/item/radio/intercom/Destroy() + STOP_PROCESSING(SSobj, src) + GLOB.global_intercoms.Remove(src) + return ..() + +/obj/item/radio/intercom/attack_ai(mob/user as mob) + add_hiddenprint(user) + add_fingerprint(user) + spawn(0) + attack_self(user) + +/obj/item/radio/intercom/attack_hand(mob/user as mob) + add_fingerprint(user) + spawn(0) + attack_self(user) + +/obj/item/radio/intercom/receive_range(freq, level) + if(!is_listening()) + return -1 + if(!(0 in level)) + var/turf/position = get_turf(src) + // TODO: Integrate radio with the space manager + if(isnull(position) || !(position.z in level)) + return -1 + if(freq in SSradio.ANTAG_FREQS) + if(!(syndiekey)) + return -1//Prevents broadcast of messages over devices lacking the encryption + + return canhear_range + +/obj/item/radio/intercom/attackby(obj/item/W as obj, mob/user as mob) + if(istype(W, /obj/item/stack/tape_roll)) //eww + return + else if(iscoil(W) && buildstage == 1) + var/obj/item/stack/cable_coil/coil = W + if(coil.amount < 5) + to_chat(user, "You need more cable for this!") + return + if(do_after(user, 10 * coil.toolspeed, target = src) && buildstage == 1) + coil.use(5) + to_chat(user, "You wire \the [src]!") + buildstage = 2 + return 1 + else if(istype(W,/obj/item/intercom_electronics) && buildstage == 0) + playsound(get_turf(src), W.usesound, 50, 1) + if(do_after(user, 10 * W.toolspeed, target = src) && buildstage == 0) + qdel(W) + to_chat(user, "You insert \the [W] into \the [src]!") + buildstage = 1 + return 1 + else + return ..() + +/obj/item/radio/intercom/crowbar_act(mob/user, obj/item/I) + if(buildstage != 1) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + to_chat(user, "You begin removing the electronics...") + if(!I.use_tool(src, user, 10, volume = I.tool_volume) || buildstage != 1) + return + new /obj/item/intercom_electronics(get_turf(src)) + to_chat(user, "The circuit board pops out!") + buildstage = 0 + +/obj/item/radio/intercom/screwdriver_act(mob/user, obj/item/I) + if(buildstage != 2) + return ..() + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(!I.use_tool(src, user, 10, volume = I.tool_volume) || buildstage != 2) + return + update_icon() + on = 1 + b_stat = 0 + buildstage = 3 + to_chat(user, "You secure the electronics!") + update_icon() + START_PROCESSING(SSobj, src) + for(var/i, i<= 5, i++) + wires.UpdateCut(i,1) + +/obj/item/radio/intercom/wirecutter_act(mob/user, obj/item/I) + if(!(buildstage == 3 && b_stat && wires.IsAllCut())) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + WIRECUTTER_SNIP_MESSAGE + new /obj/item/stack/cable_coil(get_turf(src),5) + on = 0 + b_stat = 1 + buildstage = 1 + update_icon() + STOP_PROCESSING(SSobj, src) + +/obj/item/radio/intercom/welder_act(mob/user, obj/item/I) + if(!buildstage) + return + . = TRUE + if(!I.tool_use_check(user, 3)) + return + to_chat(user, "You start slicing [src] from the wall...") + if(I.use_tool(src, user, 10, amount = 3, volume = I.tool_volume)) + to_chat(user, "You cut [src] free from the wall!") + new /obj/item/mounted/frame/intercom(get_turf(src)) + qdel(src) + +/obj/item/radio/intercom/update_icon() + if(!circuitry_installed) + icon_state="intercom-frame" + return + icon_state = "intercom[!on?"-p":""][b_stat ? "-open":""]" + +/obj/item/radio/intercom/process() + if(((world.timeofday - last_tick) > 30) || ((world.timeofday - last_tick) < 0)) + last_tick = world.timeofday + + + if(!src.loc) + on = 0 + else + var/area/A = get_area(src) + if(!A) + on = 0 + else + on = A.powered(EQUIP) // set "on" to the power status + update_icon() + +/obj/item/intercom_electronics + name = "intercom electronics" + icon = 'icons/obj/doors/door_assembly.dmi' + icon_state = "door_electronics" + desc = "Looks like a circuit. Probably is." + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=50, MAT_GLASS=50) + origin_tech = "engineering=2;programming=1" + toolspeed = 1 + usesound = 'sound/items/deconstruct.ogg' + +/obj/item/radio/intercom/locked + var/locked_frequency + +/obj/item/radio/intercom/locked/set_frequency(var/frequency) + if(frequency == locked_frequency) + ..(locked_frequency) + +/obj/item/radio/intercom/locked/list_channels() + return "" + +/obj/item/radio/intercom/locked/ai_private + name = "\improper AI intercom" + frequency = AI_FREQ + +/obj/item/radio/intercom/locked/confessional + name = "confessional intercom" + frequency = 1480 + +/obj/item/radio/intercom/locked/prison + name = "\improper prison intercom" + desc = "Talk through this. It looks like it has been modified to not broadcast." + +/obj/item/radio/intercom/locked/prison/New() + ..() + wires.CutWireIndex(RADIO_WIRE_TRANSMIT) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index c1a19457c14..23d897d491f 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -1,866 +1,866 @@ -var/global/list/default_internal_channels = list( - num2text(PUB_FREQ) = list(), - num2text(AI_FREQ) = list(ACCESS_CAPTAIN), - num2text(ERT_FREQ) = list(ACCESS_CENT_SPECOPS), - num2text(COMM_FREQ)= list(ACCESS_HEADS), - num2text(ENG_FREQ) = list(ACCESS_ENGINE, ACCESS_ATMOSPHERICS), - num2text(MED_FREQ) = list(ACCESS_MEDICAL), - num2text(MED_I_FREQ)=list(ACCESS_MEDICAL), - num2text(SEC_FREQ) = list(ACCESS_SECURITY), - num2text(SEC_I_FREQ)=list(ACCESS_SECURITY), - num2text(SCI_FREQ) = list(ACCESS_RESEARCH), - num2text(SUP_FREQ) = list(ACCESS_CARGO), - num2text(SRV_FREQ) = list(ACCESS_HOP, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CLOWN, ACCESS_MIME) -) - -var/global/list/default_medbay_channels = list( - num2text(PUB_FREQ) = list(), - num2text(MED_FREQ) = list(ACCESS_MEDICAL), - num2text(MED_I_FREQ) = list(ACCESS_MEDICAL) -) - -/obj/item/radio - icon = 'icons/obj/radio.dmi' - name = "station bounced radio" - dog_fashion = /datum/dog_fashion/back - suffix = "\[3\]" - icon_state = "walkietalkie" - item_state = "walkietalkie" - var/on = 1 // 0 for off - var/last_transmission - var/frequency = PUB_FREQ //common chat - var/traitor_frequency = 0 //tune to frequency to unlock traitor supplies - var/canhear_range = 3 // the range which mobs can hear this radio from - var/datum/wires/radio/wires = null - var/b_stat = 0 - var/broadcasting = 0 - var/listening = 1 - var/list/channels = list() //see communications.dm for full list. First channes is a "default" for :h - var/subspace_transmission = 0 - var/obj/item/encryptionkey/syndicate/syndiekey = null //Holder for the syndicate encryption key if present - var/disable_timer = 0 //How many times this is disabled by EMPs - - var/is_special = 0 //For electropacks mostly, skips Topic() checks - - flags = CONDUCT - slot_flags = SLOT_BELT - throw_speed = 2 - throw_range = 9 - w_class = WEIGHT_CLASS_SMALL - - materials = list(MAT_METAL=75) - - var/const/FREQ_LISTENING = 1 - var/atom/follow_target // Custom follow target for autosay-using bots - - var/list/internal_channels - - var/datum/radio_frequency/radio_connection - var/list/datum/radio_frequency/secure_radio_connections = new - - -/obj/item/radio/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) - - -/obj/item/radio/New() - ..() - wires = new(src) - - internal_channels = default_internal_channels.Copy() - GLOB.global_radios |= src - -/obj/item/radio/Destroy() - QDEL_NULL(wires) - if(SSradio) - SSradio.remove_object(src, frequency) - for(var/ch_name in channels) - SSradio.remove_object(src, SSradio.radiochannels[ch_name]) - radio_connection = null - GLOB.global_radios -= src - follow_target = null - return ..() - - -/obj/item/radio/Initialize() - ..() - if(frequency < RADIO_LOW_FREQ || frequency > RADIO_HIGH_FREQ) - frequency = sanitize_frequency(frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ) - set_frequency(frequency) - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = SSradio.add_object(src, SSradio.radiochannels[ch_name], RADIO_CHAT) - -/obj/item/radio/attack_ghost(mob/user) - return interact(user) - -/obj/item/radio/attack_self(mob/user as mob) - user.set_machine(src) - interact(user) - -/obj/item/radio/interact(mob/user) - if(!user) - return 0 - - if(b_stat) - wires.Interact(user) - - return ui_interact(user) - -/obj/item/radio/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "radio_basic.tmpl", "[name]", 400, 550) - ui.open() - ui.set_auto_update(1) - -/obj/item/radio/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - data["mic_status"] = broadcasting - data["speaker"] = listening - data["freq"] = format_frequency(frequency) - data["rawfreq"] = num2text(frequency) - - data["mic_cut"] = (wires.IsIndexCut(RADIO_WIRE_TRANSMIT) || wires.IsIndexCut(RADIO_WIRE_SIGNAL)) - data["spk_cut"] = (wires.IsIndexCut(RADIO_WIRE_RECEIVE) || wires.IsIndexCut(RADIO_WIRE_SIGNAL)) - - var/list/chanlist = list_channels(user) - if(islist(chanlist) && chanlist.len) - data["chan_list"] = chanlist - data["chan_list_len"] = chanlist.len - - if(syndiekey) - data["useSyndMode"] = 1 - - return data - - -/obj/item/radio/proc/list_channels(var/mob/user) - return list_internal_channels(user) - -/obj/item/radio/proc/list_secure_channels(var/mob/user) - var/dat[0] - - for(var/ch_name in channels) - var/chan_stat = channels[ch_name] - var/listening = !!(chan_stat & FREQ_LISTENING) != 0 - - dat.Add(list(list("chan" = ch_name, "display_name" = ch_name, "secure_channel" = 1, "sec_channel_listen" = !listening, "chan_span" = SSradio.frequency_span_class(SSradio.radiochannels[ch_name])))) - - return dat - -/obj/item/radio/proc/list_internal_channels(var/mob/user) - var/dat[0] - for(var/internal_chan in internal_channels) - if(has_channel_access(user, internal_chan)) - dat.Add(list(list("chan" = internal_chan, "display_name" = get_frequency_name(text2num(internal_chan)), "chan_span" = SSradio.frequency_span_class(text2num(internal_chan))))) - - return dat - -/obj/item/radio/proc/has_channel_access(var/mob/user, var/freq) - if(!user) - return 0 - - if(!(freq in internal_channels)) - return 0 - - return user.has_internal_radio_channel_access(user, internal_channels[freq]) - -/mob/proc/has_internal_radio_channel_access(var/mob/user, var/list/req_one_accesses) - var/obj/item/card/id/I = user.get_id_card() - return has_access(list(), req_one_accesses, I ? I.GetAccess() : list()) - -/mob/living/silicon/has_internal_radio_channel_access(var/mob/user, var/list/req_one_accesses) - var/list/access = get_all_accesses() - return has_access(list(), req_one_accesses, access) - -/mob/dead/observer/has_internal_radio_channel_access(var/mob/user, var/list/req_one_accesses) - return can_admin_interact() - -/obj/item/radio/proc/ToggleBroadcast() - broadcasting = !broadcasting && !(wires.IsIndexCut(RADIO_WIRE_TRANSMIT) || wires.IsIndexCut(RADIO_WIRE_SIGNAL)) - -/obj/item/radio/proc/ToggleReception() - listening = !listening && !(wires.IsIndexCut(RADIO_WIRE_RECEIVE) || wires.IsIndexCut(RADIO_WIRE_SIGNAL)) - -/obj/item/radio/Topic(href, href_list) - if(..()) - return 1 - - if(is_special) - return 0 - - if(href_list["track"]) - var/mob/target = locate(href_list["track"]) - var/mob/living/silicon/ai/A = locate(href_list["track2"]) - if(A && target) - A.ai_actual_track(target) - . = 1 - - else if(href_list["freq"]) - var/new_frequency = (frequency + text2num(href_list["freq"])) - if((new_frequency < PUBLIC_LOW_FREQ || new_frequency > PUBLIC_HIGH_FREQ)) - new_frequency = sanitize_frequency(new_frequency) - set_frequency(new_frequency) - if(hidden_uplink) - if(hidden_uplink.check_trigger(usr, frequency, traitor_frequency)) - usr << browse(null, "window=radio") - . = 1 - else if(href_list["talk"]) - ToggleBroadcast() - . = 1 - else if(href_list["listen"]) - var/chan_name = href_list["ch_name"] - if(!chan_name) - ToggleReception() - else - if(channels[chan_name] & FREQ_LISTENING) - channels[chan_name] &= ~FREQ_LISTENING - else - channels[chan_name] |= FREQ_LISTENING - . = 1 - else if(href_list["spec_freq"]) - var freq = href_list["spec_freq"] - if(has_channel_access(usr, freq)) - set_frequency(text2num(freq)) - . = 1 - - if(href_list["nowindow"]) // here for pAIs, maybe others will want it, idk - return 1 - - add_fingerprint(usr) - -/obj/item/radio/proc/autosay(var/message, var/from, var/channel, var/zlevel = config.contact_levels, var/role = "Unknown") //BS12 EDIT - var/datum/radio_frequency/connection = null - if(channel && channels && channels.len > 0) - if(channel == "department") - channel = channels[1] - connection = secure_radio_connections[channel] - else - connection = radio_connection - channel = null - if(!istype(connection)) - return - if(!connection) - return - var/mob/living/automatedannouncer/A = new /mob/living/automatedannouncer(src) - A.name = from - A.role = role - A.message = message - var/jammed = FALSE - for(var/obj/item/jammer/jammer in GLOB.active_jammers) - if(get_dist(get_turf(src), get_turf(jammer)) < jammer.range) - jammed = TRUE - break - if(jammed) - message = Gibberish(message, 100) - var/list/message_pieces = message_to_multilingual(message) - Broadcast_Message(connection, A, - 0, "*garbled automated announcement*", src, - message_pieces, from, "Automated Announcement", from, "synthesized voice", - 4, 0, zlevel, connection.frequency, follow_target = follow_target) - qdel(A) - -// Just a dummy mob used for making announcements, so we don't create AIs to do this -// I'm not sure who thought that was a good idea. -- Crazylemon -/mob/living/automatedannouncer - var/role = "" - var/lifetime_timer - var/message = "" - universal_speak = 1 - -/mob/living/automatedannouncer/New() - lifetime_timer = addtimer(CALLBACK(src, .proc/autocleanup), SecondsToTicks(10), TIMER_STOPPABLE) - ..() - -/mob/living/automatedannouncer/Destroy() - if(lifetime_timer) - deltimer(lifetime_timer) - lifetime_timer = null - return ..() - -/mob/living/automatedannouncer/proc/autocleanup() - log_runtime(EXCEPTION("An announcer somehow managed to outlive the radio! Deleting!"), src, list("Message: '[message]'")) - qdel(src) - -// Interprets the message mode when talking into a radio, possibly returning a connection datum -/obj/item/radio/proc/handle_message_mode(mob/living/M as mob, list/message_pieces, message_mode) - // If a channel isn't specified, send to common. - if(!message_mode || message_mode == "headset") - return radio_connection - - // Otherwise, if a channel is specified, look for it. - if(channels && channels.len > 0) - if(message_mode == "department") // Department radio shortcut - message_mode = channels[1] - - if(channels[message_mode]) // only broadcast if the channel is set on - return secure_radio_connections[message_mode] - - // If we were to send to a channel we don't have, drop it. - return RADIO_CONNECTION_FAIL - -/obj/item/radio/talk_into(mob/living/M as mob, list/message_pieces, channel, var/verb = "says") - if(!on) - return 0 // the device has to be on - // Fix for permacell radios, but kinda eh about actually fixing them. - if(!M || !message_pieces) - return 0 - - // Uncommenting this. To the above comment: - // The permacell radios aren't suppose to be able to transmit, this isn't a bug and this "fix" is just making radio wires useless. -Giacom - if(wires.IsIndexCut(RADIO_WIRE_TRANSMIT)) // The device has to have all its wires and shit intact - return 0 - - if(!M.IsVocal()) - return 0 - - /* Quick introduction: - This new radio system uses a very robust FTL signaling technology unoriginally - dubbed "subspace" which is somewhat similar to 'blue-space' but can't - actually transmit large mass. Headsets are the only radio devices capable - of sending subspace transmissions to the Communications Satellite. - - A headset sends a signal to a subspace listener/receiver elsewhere in space, - the signal gets processed and logged, and an audible transmission gets sent - to each individual headset. - */ - - //#### Grab the connection datum ####// - var/message_mode = handle_message_mode(M, message_pieces, channel) - switch(message_mode) //special cases - if(RADIO_CONNECTION_FAIL) - return 0 - if(RADIO_CONNECTION_NON_SUBSPACE) - return 1 - - if(!istype(message_mode, /datum/radio_frequency)) //if not a special case, it should be returning a radio connection - return 0 - - var/datum/radio_frequency/connection = message_mode - var/turf/position = get_turf(src) - - var/jammed = FALSE - for(var/obj/item/jammer/jammer in GLOB.active_jammers) - if(get_dist(position, get_turf(jammer)) < jammer.range) - jammed = TRUE - break - - //#### Tagging the signal with all appropriate identity values ####// - - // ||-- The mob's name identity --|| - var/displayname = M.name // grab the display name (name you get when you hover over someone's icon) - var/real_name = M.real_name // mob's real name - var/mobkey = "none" // player key associated with mob - var/voicemask = 0 // the speaker is wearing a voice mask - if(M.client) - mobkey = M.key // assign the mob's key - - - var/jobname // the mob's "job" - - if(jammed) - Gibberish_all(message_pieces, 100) - - // --- Human: use their actual job --- - if(ishuman(M)) - var/mob/living/carbon/human/H = M - jobname = H.get_assignment() - - // --- Carbon Nonhuman --- - else if(iscarbon(M)) // Nonhuman carbon mob - jobname = "No id" - - // --- AI --- - else if(isAI(M)) - jobname = "AI" - - // --- Cyborg --- - else if(isrobot(M)) - jobname = "Cyborg" - - // --- Personal AI (pAI) --- - else if(istype(M, /mob/living/silicon/pai)) - jobname = "Personal AI" - - // --- Unidentifiable mob --- - else - jobname = "Unknown" - - - // --- Modifications to the mob's identity --- - - // The mob is disguising their identity: - if(ishuman(M)) - var/mob/living/carbon/human/H = M - displayname = H.voice - if(H.voice != real_name) - voicemask = TRUE - - if(syndiekey && syndiekey.change_voice && connection.frequency == SYND_FREQ) - displayname = syndiekey.fake_name - jobname = "Unknown" - voicemask = TRUE - - - /* ###### Radio headsets can only broadcast through subspace ###### */ - - if(subspace_transmission) - // First, we want to generate a new radio signal - var/datum/signal/signal = new - signal.transmission_method = 2 // 2 would be a subspace transmission. - // transmission_method could probably be enumerated through #define. Would be neater. - - // --- Finally, tag the actual signal with the appropriate values --- - signal.data = list( - // Identity-associated tags: - "mob" = M, // store a reference to the mob - "mobtype" = M.type, // the mob's type - "race" = signal.get_race(M), - "realname" = real_name, // the mob's real name - "name" = displayname, // the mob's display name - "job" = jobname, // the mob's job - "key" = mobkey, // the mob's key - "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood - "vname" = M.voice_name, // the name to display if the voice wasn't understood - "vmask" = voicemask, // 1 if the mob is using a voice gas mask - - // We store things that would otherwise be kept in the actual mob - // so that they can be logged even AFTER the mob is deleted or something - - // Other tags: - "compression" = rand(45,50), // compressed radio signal - "message" = message_pieces, // the actual sent message - "connection" = connection, // the radio connection to use - "radio" = src, // stores the radio used for transmission - "slow" = 0, // how much to sleep() before broadcasting - simulates net lag - "traffic" = 0, // dictates the total traffic sum that the signal went through - "type" = 0, // determines what type of radio input it is: normal broadcast - "server" = null, // the last server to log this signal - "reject" = 0, // if nonzero, the signal will not be accepted by any broadcasting machinery - "level" = position.z, // The source's z level - "verb" = verb - ) - signal.frequency = connection.frequency // Quick frequency set - - //#### Sending the signal to all subspace receivers ####// - - for(var/obj/machinery/telecomms/receiver/R in telecomms_list) - spawn(0) - R.receive_signal(signal) - - // Allinone can act as receivers. - for(var/obj/machinery/telecomms/allinone/R in telecomms_list) - spawn(0) - R.receive_signal(signal) - - // Receiving code can be located in Telecommunications.dm - return signal.data["done"] && position.z in signal.data["level"] - - - /* ###### Intercoms and station-bounced radios ###### */ - - var/filter_type = 2 - - /* --- Intercoms can only broadcast to other intercoms, but bounced radios can broadcast to bounced radios and intercoms --- */ - if(istype(src, /obj/item/radio/intercom)) - filter_type = 1 - - - var/datum/signal/signal = new - signal.transmission_method = 2 - - - /* --- Try to send a normal subspace broadcast first */ - - signal.data = list( - - "mob" = M, // store a reference to the mob - "mobtype" = M.type, // the mob's type - "race" = signal.get_race(M), // text to show next to mob in comms log console - "realname" = real_name, // the mob's real name - "name" = displayname, // the mob's display name - "job" = jobname, // the mob's job - "key" = mobkey, // the mob's key - "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood - "vname" = M.voice_name, // the name to display if the voice wasn't understood - "vmask" = voicemask, // 1 if the mob is using a voice gas mas - - "compression" = 0, // uncompressed radio signal - "message" = message_pieces, // the actual sent message - "connection" = connection, // the radio connection to use - "radio" = src, // stores the radio used for transmission - "slow" = 0, - "traffic" = 0, - "type" = 0, - "server" = null, - "reject" = 0, - "level" = position.z, - "verb" = verb - ) - signal.frequency = connection.frequency // Quick frequency set - - for(var/obj/machinery/telecomms/receiver/R in telecomms_list) - spawn(0) - R.receive_signal(signal) - - - sleep(rand(10,25)) // wait a little... - - if(signal.data["done"] && position.z in signal.data["level"]) - // we're done here. - return 1 - - // Oh my god; the comms are down or something because the signal hasn't been broadcasted yet in our level. - // Send a mundane broadcast with limited targets: - - //THIS IS TEMPORARY. YEAH RIGHT - if(!connection) return 0 //~Carn - - return Broadcast_Message(connection, M, voicemask, pick(M.speak_emote), - src, message_pieces, displayname, jobname, real_name, M.voice_name, - filter_type, signal.data["compression"], list(position.z), connection.frequency,verb) - - -/obj/item/radio/hear_talk(mob/M as mob, list/message_pieces, var/verb = "says") - if(broadcasting) - if(get_dist(src, M) <= canhear_range) - talk_into(M, message_pieces, null, verb) - - -/* -/obj/item/radio/proc/accept_rad(obj/item/radio/R as obj, message) - - if((R.frequency == frequency && message)) - return 1 - else if - - else - return null - return -*/ - - -/obj/item/radio/proc/receive_range(freq, level) - // check if this radio can receive on the given frequency, and if so, - // what the range is in which mobs will hear the radio - // returns: -1 if can't receive, range otherwise - - if(!is_listening()) - return -1 - if(!(0 in level)) - var/turf/position = get_turf(src) - if(!position || !(position.z in level)) - return -1 - if(freq in SSradio.ANTAG_FREQS) - if(!(syndiekey))//Checks to see if it's allowed on that frequency, based on the encryption keys - return -1 - if(!freq) //recieved on main frequency - if(!listening) - return -1 - else - var/accept = (freq==frequency && listening) - if(!accept) - for(var/ch_name in channels) - var/datum/radio_frequency/RF = secure_radio_connections[ch_name] - if(RF.frequency==freq && (channels[ch_name]&FREQ_LISTENING)) - accept = 1 - break - if(!accept) - return -1 - return canhear_range - -/obj/item/radio/proc/send_hear(freq, level) - var/range = receive_range(freq, level) - if(range > -1) - return get_mobs_in_view(canhear_range, src) - -/obj/item/radio/proc/is_listening() - var/is_listening = TRUE - if(!on) - is_listening = FALSE - if(!wires || wires.IsIndexCut(RADIO_WIRE_RECEIVE)) - is_listening = FALSE - if(!listening) - is_listening = FALSE - - return is_listening - -/obj/item/radio/proc/send_announcement() - if(is_listening()) - return get_mobs_in_view(canhear_range, src) - - return null - -/obj/item/radio/examine(mob/user) - . = ..() - if(in_range(src, user) || loc == user) - if(b_stat) - . += "\the [src] can be attached and modified!" - else - . += "\the [src] can not be modified or attached!" - -/obj/item/radio/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - user.set_machine(src) - b_stat = !b_stat - if(!istype(src, /obj/item/radio/beacon)) - if(b_stat) - user.show_message("The radio can now be attached and modified!") - else - user.show_message("The radio can no longer be modified or attached!") - updateDialog() - -/obj/item/radio/wirecutter_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - interact(user) - -/obj/item/radio/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - interact(user) - -/obj/item/radio/emp_act(severity) - on = 0 - disable_timer++ - addtimer(CALLBACK(src, .proc/enable_radio), rand(100, 200)) - - if(listening) - visible_message("[src] buzzes violently!") - - broadcasting = 0 - listening = 0 - for(var/ch_name in channels) - channels[ch_name] = 0 - ..() - -/obj/item/radio/proc/enable_radio() - if(disable_timer > 0) - disable_timer-- - if(!disable_timer) - on = 1 - -/////////////////////////////// -//////////Borg Radios////////// -/////////////////////////////// -//Giving borgs their own radio to have some more room to work with -Sieve - -/obj/item/radio/borg - var/mob/living/silicon/robot/myborg = null // Cyborg which owns this radio. Used for power checks - var/obj/item/encryptionkey/keyslot = null//Borg radios can handle a single encryption key - var/shut_up = 1 - icon = 'icons/obj/robot_component.dmi' // Cyborgs radio icons should look like the component. - icon_state = "radio" - canhear_range = 0 - subspace_transmission = 1 - dog_fashion = null - -/obj/item/radio/borg/syndicate - keyslot = new /obj/item/encryptionkey/syndicate/nukeops - -/obj/item/radio/borg/syndicate/CanUseTopic(mob/user, datum/topic_state/state) - . = ..() - if(. == STATUS_UPDATE && istype(user, /mob/living/silicon/robot/syndicate)) - . = STATUS_INTERACTIVE - -/obj/item/radio/borg/Destroy() - myborg = null - return ..() - -/obj/item/radio/borg/list_channels(var/mob/user) - return list_secure_channels(user) - -/obj/item/radio/borg/syndicate/New() - ..() - syndiekey = keyslot - set_frequency(SYND_FREQ) - -/obj/item/radio/borg/deathsquad - -/obj/item/radio/borg/deathsquad/New() - ..() - set_frequency(DTH_FREQ) - -/obj/item/radio/borg/ert - keyslot = new /obj/item/encryptionkey/ert - -/obj/item/radio/borg/ert/New() - ..() - set_frequency(ERT_FREQ) - -/obj/item/radio/borg/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/encryptionkey/)) - user.set_machine(src) - if(keyslot) - to_chat(user, "The radio can't hold another key!") - return - - if(!keyslot) - user.drop_item() - W.loc = src - keyslot = W - - recalculateChannels() - else - return ..() - -/obj/item/radio/borg/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = 0)) - return - user.set_machine(src) - if(keyslot) - for(var/ch_name in channels) - SSradio.remove_object(src, SSradio.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - - if(keyslot) - var/turf/T = get_turf(user) - if(T) - keyslot.loc = T - keyslot = null - - recalculateChannels() - to_chat(user, "You pop out the encryption key in the radio!") - I.play_tool_sound(user, I.tool_volume) - - else - to_chat(user, "This radio doesn't have any encryption keys!") - -/obj/item/radio/borg/proc/recalculateChannels() - channels = list() - syndiekey = null - - var/mob/living/silicon/robot/D = loc - if(D.module) - for(var/ch_name in D.module.channels) - if(ch_name in channels) - continue - channels += ch_name - channels[ch_name] += D.module.channels[ch_name] - if(keyslot) - for(var/ch_name in keyslot.channels) - if(ch_name in channels) - continue - channels += ch_name - channels[ch_name] += keyslot.channels[ch_name] - - if(keyslot.syndie) - syndiekey = keyslot - - - for(var/ch_name in channels) - if(!SSradio) - sleep(30) // Waiting for SSradio to be created. - if(!SSradio) - name = "broken radio" - return - - secure_radio_connections[ch_name] = SSradio.add_object(src, SSradio.radiochannels[ch_name], RADIO_CHAT) - - return - -/obj/item/radio/borg/Topic(href, href_list) - if(..()) - return 1 - if(href_list["mode"]) - var/enable_subspace_transmission = text2num(href_list["mode"]) - if(enable_subspace_transmission != subspace_transmission) - subspace_transmission = !subspace_transmission - if(subspace_transmission) - to_chat(usr, "Subspace Transmission is enabled.") - else - to_chat(usr, "Subspace Transmission is disabled.") - - if(subspace_transmission == 0)//Simple as fuck, clears the channel list to prevent talking/listening over them if subspace transmission is disabled - channels = list() - else - recalculateChannels() - . = 1 - if(href_list["shutup"]) // Toggle loudspeaker mode, AKA everyone around you hearing your radio. - var/do_shut_up = text2num(href_list["shutup"]) - if(do_shut_up != shut_up) - shut_up = !shut_up - if(shut_up) - canhear_range = 0 - to_chat(usr, "Loudspeaker disabled.") - else - canhear_range = 3 - to_chat(usr, "Loudspeaker enabled.") - . = 1 - - -/obj/item/radio/borg/interact(mob/user as mob) - if(!on) - return - - . = ..() - -/obj/item/radio/borg/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "radio_basic.tmpl", "[name]", 430, 500) - ui.open() - ui.set_auto_update(1) - -/obj/item/radio/borg/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - data["mic_status"] = broadcasting - data["speaker"] = listening - data["freq"] = format_frequency(frequency) - data["rawfreq"] = num2text(frequency) - - var/list/chanlist = list_channels(user) - if(islist(chanlist) && chanlist.len) - data["chan_list"] = chanlist - data["chan_list_len"] = chanlist.len - - if(syndiekey) - data["useSyndMode"] = 1 - - data["has_loudspeaker"] = 1 - data["loudspeaker"] = !shut_up - data["has_subspace"] = 1 - data["subspace"] = subspace_transmission - - return data - -/obj/item/radio/proc/config(op) - if(SSradio) - for(var/ch_name in channels) - SSradio.remove_object(src, SSradio.radiochannels[ch_name]) - secure_radio_connections = new - channels = op - if(SSradio) - for(var/ch_name in op) - secure_radio_connections[ch_name] = SSradio.add_object(src, SSradio.radiochannels[ch_name], RADIO_CHAT) - return - -/obj/item/radio/off - listening = 0 - dog_fashion = /datum/dog_fashion/back - -/obj/item/radio/phone - broadcasting = 0 - icon = 'icons/obj/items.dmi' - icon_state = "red_phone" - listening = 1 - name = "phone" - dog_fashion = null - -/obj/item/radio/phone/medbay - frequency = MED_I_FREQ - -/obj/item/radio/phone/medbay/New() - ..() - internal_channels = default_medbay_channels.Copy() +var/global/list/default_internal_channels = list( + num2text(PUB_FREQ) = list(), + num2text(AI_FREQ) = list(ACCESS_CAPTAIN), + num2text(ERT_FREQ) = list(ACCESS_CENT_SPECOPS), + num2text(COMM_FREQ)= list(ACCESS_HEADS), + num2text(ENG_FREQ) = list(ACCESS_ENGINE, ACCESS_ATMOSPHERICS), + num2text(MED_FREQ) = list(ACCESS_MEDICAL), + num2text(MED_I_FREQ)=list(ACCESS_MEDICAL), + num2text(SEC_FREQ) = list(ACCESS_SECURITY), + num2text(SEC_I_FREQ)=list(ACCESS_SECURITY), + num2text(SCI_FREQ) = list(ACCESS_RESEARCH), + num2text(SUP_FREQ) = list(ACCESS_CARGO), + num2text(SRV_FREQ) = list(ACCESS_HOP, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_HYDROPONICS, ACCESS_JANITOR, ACCESS_CLOWN, ACCESS_MIME) +) + +var/global/list/default_medbay_channels = list( + num2text(PUB_FREQ) = list(), + num2text(MED_FREQ) = list(ACCESS_MEDICAL), + num2text(MED_I_FREQ) = list(ACCESS_MEDICAL) +) + +/obj/item/radio + icon = 'icons/obj/radio.dmi' + name = "station bounced radio" + dog_fashion = /datum/dog_fashion/back + suffix = "\[3\]" + icon_state = "walkietalkie" + item_state = "walkietalkie" + var/on = 1 // 0 for off + var/last_transmission + var/frequency = PUB_FREQ //common chat + var/traitor_frequency = 0 //tune to frequency to unlock traitor supplies + var/canhear_range = 3 // the range which mobs can hear this radio from + var/datum/wires/radio/wires = null + var/b_stat = 0 + var/broadcasting = 0 + var/listening = 1 + var/list/channels = list() //see communications.dm for full list. First channes is a "default" for :h + var/subspace_transmission = 0 + var/obj/item/encryptionkey/syndicate/syndiekey = null //Holder for the syndicate encryption key if present + var/disable_timer = 0 //How many times this is disabled by EMPs + + var/is_special = 0 //For electropacks mostly, skips Topic() checks + + flags = CONDUCT + slot_flags = SLOT_BELT + throw_speed = 2 + throw_range = 9 + w_class = WEIGHT_CLASS_SMALL + + materials = list(MAT_METAL=75) + + var/const/FREQ_LISTENING = 1 + var/atom/follow_target // Custom follow target for autosay-using bots + + var/list/internal_channels + + var/datum/radio_frequency/radio_connection + var/list/datum/radio_frequency/secure_radio_connections = new + + +/obj/item/radio/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) + + +/obj/item/radio/New() + ..() + wires = new(src) + + internal_channels = default_internal_channels.Copy() + GLOB.global_radios |= src + +/obj/item/radio/Destroy() + QDEL_NULL(wires) + if(SSradio) + SSradio.remove_object(src, frequency) + for(var/ch_name in channels) + SSradio.remove_object(src, SSradio.radiochannels[ch_name]) + radio_connection = null + GLOB.global_radios -= src + follow_target = null + return ..() + + +/obj/item/radio/Initialize() + ..() + if(frequency < RADIO_LOW_FREQ || frequency > RADIO_HIGH_FREQ) + frequency = sanitize_frequency(frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ) + set_frequency(frequency) + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = SSradio.add_object(src, SSradio.radiochannels[ch_name], RADIO_CHAT) + +/obj/item/radio/attack_ghost(mob/user) + return interact(user) + +/obj/item/radio/attack_self(mob/user as mob) + user.set_machine(src) + interact(user) + +/obj/item/radio/interact(mob/user) + if(!user) + return 0 + + if(b_stat) + wires.Interact(user) + + return ui_interact(user) + +/obj/item/radio/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "radio_basic.tmpl", "[name]", 400, 550) + ui.open() + ui.set_auto_update(1) + +/obj/item/radio/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + data["mic_status"] = broadcasting + data["speaker"] = listening + data["freq"] = format_frequency(frequency) + data["rawfreq"] = num2text(frequency) + + data["mic_cut"] = (wires.IsIndexCut(RADIO_WIRE_TRANSMIT) || wires.IsIndexCut(RADIO_WIRE_SIGNAL)) + data["spk_cut"] = (wires.IsIndexCut(RADIO_WIRE_RECEIVE) || wires.IsIndexCut(RADIO_WIRE_SIGNAL)) + + var/list/chanlist = list_channels(user) + if(islist(chanlist) && chanlist.len) + data["chan_list"] = chanlist + data["chan_list_len"] = chanlist.len + + if(syndiekey) + data["useSyndMode"] = 1 + + return data + + +/obj/item/radio/proc/list_channels(var/mob/user) + return list_internal_channels(user) + +/obj/item/radio/proc/list_secure_channels(var/mob/user) + var/dat[0] + + for(var/ch_name in channels) + var/chan_stat = channels[ch_name] + var/listening = !!(chan_stat & FREQ_LISTENING) != 0 + + dat.Add(list(list("chan" = ch_name, "display_name" = ch_name, "secure_channel" = 1, "sec_channel_listen" = !listening, "chan_span" = SSradio.frequency_span_class(SSradio.radiochannels[ch_name])))) + + return dat + +/obj/item/radio/proc/list_internal_channels(var/mob/user) + var/dat[0] + for(var/internal_chan in internal_channels) + if(has_channel_access(user, internal_chan)) + dat.Add(list(list("chan" = internal_chan, "display_name" = get_frequency_name(text2num(internal_chan)), "chan_span" = SSradio.frequency_span_class(text2num(internal_chan))))) + + return dat + +/obj/item/radio/proc/has_channel_access(var/mob/user, var/freq) + if(!user) + return 0 + + if(!(freq in internal_channels)) + return 0 + + return user.has_internal_radio_channel_access(user, internal_channels[freq]) + +/mob/proc/has_internal_radio_channel_access(var/mob/user, var/list/req_one_accesses) + var/obj/item/card/id/I = user.get_id_card() + return has_access(list(), req_one_accesses, I ? I.GetAccess() : list()) + +/mob/living/silicon/has_internal_radio_channel_access(var/mob/user, var/list/req_one_accesses) + var/list/access = get_all_accesses() + return has_access(list(), req_one_accesses, access) + +/mob/dead/observer/has_internal_radio_channel_access(var/mob/user, var/list/req_one_accesses) + return can_admin_interact() + +/obj/item/radio/proc/ToggleBroadcast() + broadcasting = !broadcasting && !(wires.IsIndexCut(RADIO_WIRE_TRANSMIT) || wires.IsIndexCut(RADIO_WIRE_SIGNAL)) + +/obj/item/radio/proc/ToggleReception() + listening = !listening && !(wires.IsIndexCut(RADIO_WIRE_RECEIVE) || wires.IsIndexCut(RADIO_WIRE_SIGNAL)) + +/obj/item/radio/Topic(href, href_list) + if(..()) + return 1 + + if(is_special) + return 0 + + if(href_list["track"]) + var/mob/target = locate(href_list["track"]) + var/mob/living/silicon/ai/A = locate(href_list["track2"]) + if(A && target) + A.ai_actual_track(target) + . = 1 + + else if(href_list["freq"]) + var/new_frequency = (frequency + text2num(href_list["freq"])) + if((new_frequency < PUBLIC_LOW_FREQ || new_frequency > PUBLIC_HIGH_FREQ)) + new_frequency = sanitize_frequency(new_frequency) + set_frequency(new_frequency) + if(hidden_uplink) + if(hidden_uplink.check_trigger(usr, frequency, traitor_frequency)) + usr << browse(null, "window=radio") + . = 1 + else if(href_list["talk"]) + ToggleBroadcast() + . = 1 + else if(href_list["listen"]) + var/chan_name = href_list["ch_name"] + if(!chan_name) + ToggleReception() + else + if(channels[chan_name] & FREQ_LISTENING) + channels[chan_name] &= ~FREQ_LISTENING + else + channels[chan_name] |= FREQ_LISTENING + . = 1 + else if(href_list["spec_freq"]) + var freq = href_list["spec_freq"] + if(has_channel_access(usr, freq)) + set_frequency(text2num(freq)) + . = 1 + + if(href_list["nowindow"]) // here for pAIs, maybe others will want it, idk + return 1 + + add_fingerprint(usr) + +/obj/item/radio/proc/autosay(var/message, var/from, var/channel, var/zlevel = config.contact_levels, var/role = "Unknown") //BS12 EDIT + var/datum/radio_frequency/connection = null + if(channel && channels && channels.len > 0) + if(channel == "department") + channel = channels[1] + connection = secure_radio_connections[channel] + else + connection = radio_connection + channel = null + if(!istype(connection)) + return + if(!connection) + return + var/mob/living/automatedannouncer/A = new /mob/living/automatedannouncer(src) + A.name = from + A.role = role + A.message = message + var/jammed = FALSE + for(var/obj/item/jammer/jammer in GLOB.active_jammers) + if(get_dist(get_turf(src), get_turf(jammer)) < jammer.range) + jammed = TRUE + break + if(jammed) + message = Gibberish(message, 100) + var/list/message_pieces = message_to_multilingual(message) + Broadcast_Message(connection, A, + 0, "*garbled automated announcement*", src, + message_pieces, from, "Automated Announcement", from, "synthesized voice", + 4, 0, zlevel, connection.frequency, follow_target = follow_target) + qdel(A) + +// Just a dummy mob used for making announcements, so we don't create AIs to do this +// I'm not sure who thought that was a good idea. -- Crazylemon +/mob/living/automatedannouncer + var/role = "" + var/lifetime_timer + var/message = "" + universal_speak = 1 + +/mob/living/automatedannouncer/New() + lifetime_timer = addtimer(CALLBACK(src, .proc/autocleanup), SecondsToTicks(10), TIMER_STOPPABLE) + ..() + +/mob/living/automatedannouncer/Destroy() + if(lifetime_timer) + deltimer(lifetime_timer) + lifetime_timer = null + return ..() + +/mob/living/automatedannouncer/proc/autocleanup() + log_runtime(EXCEPTION("An announcer somehow managed to outlive the radio! Deleting!"), src, list("Message: '[message]'")) + qdel(src) + +// Interprets the message mode when talking into a radio, possibly returning a connection datum +/obj/item/radio/proc/handle_message_mode(mob/living/M as mob, list/message_pieces, message_mode) + // If a channel isn't specified, send to common. + if(!message_mode || message_mode == "headset") + return radio_connection + + // Otherwise, if a channel is specified, look for it. + if(channels && channels.len > 0) + if(message_mode == "department") // Department radio shortcut + message_mode = channels[1] + + if(channels[message_mode]) // only broadcast if the channel is set on + return secure_radio_connections[message_mode] + + // If we were to send to a channel we don't have, drop it. + return RADIO_CONNECTION_FAIL + +/obj/item/radio/talk_into(mob/living/M as mob, list/message_pieces, channel, var/verb = "says") + if(!on) + return 0 // the device has to be on + // Fix for permacell radios, but kinda eh about actually fixing them. + if(!M || !message_pieces) + return 0 + + // Uncommenting this. To the above comment: + // The permacell radios aren't suppose to be able to transmit, this isn't a bug and this "fix" is just making radio wires useless. -Giacom + if(wires.IsIndexCut(RADIO_WIRE_TRANSMIT)) // The device has to have all its wires and shit intact + return 0 + + if(!M.IsVocal()) + return 0 + + /* Quick introduction: + This new radio system uses a very robust FTL signaling technology unoriginally + dubbed "subspace" which is somewhat similar to 'blue-space' but can't + actually transmit large mass. Headsets are the only radio devices capable + of sending subspace transmissions to the Communications Satellite. + + A headset sends a signal to a subspace listener/receiver elsewhere in space, + the signal gets processed and logged, and an audible transmission gets sent + to each individual headset. + */ + + //#### Grab the connection datum ####// + var/message_mode = handle_message_mode(M, message_pieces, channel) + switch(message_mode) //special cases + if(RADIO_CONNECTION_FAIL) + return 0 + if(RADIO_CONNECTION_NON_SUBSPACE) + return 1 + + if(!istype(message_mode, /datum/radio_frequency)) //if not a special case, it should be returning a radio connection + return 0 + + var/datum/radio_frequency/connection = message_mode + var/turf/position = get_turf(src) + + var/jammed = FALSE + for(var/obj/item/jammer/jammer in GLOB.active_jammers) + if(get_dist(position, get_turf(jammer)) < jammer.range) + jammed = TRUE + break + + //#### Tagging the signal with all appropriate identity values ####// + + // ||-- The mob's name identity --|| + var/displayname = M.name // grab the display name (name you get when you hover over someone's icon) + var/real_name = M.real_name // mob's real name + var/mobkey = "none" // player key associated with mob + var/voicemask = 0 // the speaker is wearing a voice mask + if(M.client) + mobkey = M.key // assign the mob's key + + + var/jobname // the mob's "job" + + if(jammed) + Gibberish_all(message_pieces, 100) + + // --- Human: use their actual job --- + if(ishuman(M)) + var/mob/living/carbon/human/H = M + jobname = H.get_assignment() + + // --- Carbon Nonhuman --- + else if(iscarbon(M)) // Nonhuman carbon mob + jobname = "No id" + + // --- AI --- + else if(isAI(M)) + jobname = "AI" + + // --- Cyborg --- + else if(isrobot(M)) + jobname = "Cyborg" + + // --- Personal AI (pAI) --- + else if(istype(M, /mob/living/silicon/pai)) + jobname = "Personal AI" + + // --- Unidentifiable mob --- + else + jobname = "Unknown" + + + // --- Modifications to the mob's identity --- + + // The mob is disguising their identity: + if(ishuman(M)) + var/mob/living/carbon/human/H = M + displayname = H.voice + if(H.voice != real_name) + voicemask = TRUE + + if(syndiekey && syndiekey.change_voice && connection.frequency == SYND_FREQ) + displayname = syndiekey.fake_name + jobname = "Unknown" + voicemask = TRUE + + + /* ###### Radio headsets can only broadcast through subspace ###### */ + + if(subspace_transmission) + // First, we want to generate a new radio signal + var/datum/signal/signal = new + signal.transmission_method = 2 // 2 would be a subspace transmission. + // transmission_method could probably be enumerated through #define. Would be neater. + + // --- Finally, tag the actual signal with the appropriate values --- + signal.data = list( + // Identity-associated tags: + "mob" = M, // store a reference to the mob + "mobtype" = M.type, // the mob's type + "race" = signal.get_race(M), + "realname" = real_name, // the mob's real name + "name" = displayname, // the mob's display name + "job" = jobname, // the mob's job + "key" = mobkey, // the mob's key + "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood + "vname" = M.voice_name, // the name to display if the voice wasn't understood + "vmask" = voicemask, // 1 if the mob is using a voice gas mask + + // We store things that would otherwise be kept in the actual mob + // so that they can be logged even AFTER the mob is deleted or something + + // Other tags: + "compression" = rand(45,50), // compressed radio signal + "message" = message_pieces, // the actual sent message + "connection" = connection, // the radio connection to use + "radio" = src, // stores the radio used for transmission + "slow" = 0, // how much to sleep() before broadcasting - simulates net lag + "traffic" = 0, // dictates the total traffic sum that the signal went through + "type" = 0, // determines what type of radio input it is: normal broadcast + "server" = null, // the last server to log this signal + "reject" = 0, // if nonzero, the signal will not be accepted by any broadcasting machinery + "level" = position.z, // The source's z level + "verb" = verb + ) + signal.frequency = connection.frequency // Quick frequency set + + //#### Sending the signal to all subspace receivers ####// + + for(var/obj/machinery/telecomms/receiver/R in telecomms_list) + spawn(0) + R.receive_signal(signal) + + // Allinone can act as receivers. + for(var/obj/machinery/telecomms/allinone/R in telecomms_list) + spawn(0) + R.receive_signal(signal) + + // Receiving code can be located in Telecommunications.dm + return signal.data["done"] && position.z in signal.data["level"] + + + /* ###### Intercoms and station-bounced radios ###### */ + + var/filter_type = 2 + + /* --- Intercoms can only broadcast to other intercoms, but bounced radios can broadcast to bounced radios and intercoms --- */ + if(istype(src, /obj/item/radio/intercom)) + filter_type = 1 + + + var/datum/signal/signal = new + signal.transmission_method = 2 + + + /* --- Try to send a normal subspace broadcast first */ + + signal.data = list( + + "mob" = M, // store a reference to the mob + "mobtype" = M.type, // the mob's type + "race" = signal.get_race(M), // text to show next to mob in comms log console + "realname" = real_name, // the mob's real name + "name" = displayname, // the mob's display name + "job" = jobname, // the mob's job + "key" = mobkey, // the mob's key + "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood + "vname" = M.voice_name, // the name to display if the voice wasn't understood + "vmask" = voicemask, // 1 if the mob is using a voice gas mas + + "compression" = 0, // uncompressed radio signal + "message" = message_pieces, // the actual sent message + "connection" = connection, // the radio connection to use + "radio" = src, // stores the radio used for transmission + "slow" = 0, + "traffic" = 0, + "type" = 0, + "server" = null, + "reject" = 0, + "level" = position.z, + "verb" = verb + ) + signal.frequency = connection.frequency // Quick frequency set + + for(var/obj/machinery/telecomms/receiver/R in telecomms_list) + spawn(0) + R.receive_signal(signal) + + + sleep(rand(10,25)) // wait a little... + + if(signal.data["done"] && position.z in signal.data["level"]) + // we're done here. + return 1 + + // Oh my god; the comms are down or something because the signal hasn't been broadcasted yet in our level. + // Send a mundane broadcast with limited targets: + + //THIS IS TEMPORARY. YEAH RIGHT + if(!connection) return 0 //~Carn + + return Broadcast_Message(connection, M, voicemask, pick(M.speak_emote), + src, message_pieces, displayname, jobname, real_name, M.voice_name, + filter_type, signal.data["compression"], list(position.z), connection.frequency,verb) + + +/obj/item/radio/hear_talk(mob/M as mob, list/message_pieces, var/verb = "says") + if(broadcasting) + if(get_dist(src, M) <= canhear_range) + talk_into(M, message_pieces, null, verb) + + +/* +/obj/item/radio/proc/accept_rad(obj/item/radio/R as obj, message) + + if((R.frequency == frequency && message)) + return 1 + else if + + else + return null + return +*/ + + +/obj/item/radio/proc/receive_range(freq, level) + // check if this radio can receive on the given frequency, and if so, + // what the range is in which mobs will hear the radio + // returns: -1 if can't receive, range otherwise + + if(!is_listening()) + return -1 + if(!(0 in level)) + var/turf/position = get_turf(src) + if(!position || !(position.z in level)) + return -1 + if(freq in SSradio.ANTAG_FREQS) + if(!(syndiekey))//Checks to see if it's allowed on that frequency, based on the encryption keys + return -1 + if(!freq) //recieved on main frequency + if(!listening) + return -1 + else + var/accept = (freq==frequency && listening) + if(!accept) + for(var/ch_name in channels) + var/datum/radio_frequency/RF = secure_radio_connections[ch_name] + if(RF.frequency==freq && (channels[ch_name]&FREQ_LISTENING)) + accept = 1 + break + if(!accept) + return -1 + return canhear_range + +/obj/item/radio/proc/send_hear(freq, level) + var/range = receive_range(freq, level) + if(range > -1) + return get_mobs_in_view(canhear_range, src) + +/obj/item/radio/proc/is_listening() + var/is_listening = TRUE + if(!on) + is_listening = FALSE + if(!wires || wires.IsIndexCut(RADIO_WIRE_RECEIVE)) + is_listening = FALSE + if(!listening) + is_listening = FALSE + + return is_listening + +/obj/item/radio/proc/send_announcement() + if(is_listening()) + return get_mobs_in_view(canhear_range, src) + + return null + +/obj/item/radio/examine(mob/user) + . = ..() + if(in_range(src, user) || loc == user) + if(b_stat) + . += "\the [src] can be attached and modified!" + else + . += "\the [src] can not be modified or attached!" + +/obj/item/radio/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + user.set_machine(src) + b_stat = !b_stat + if(!istype(src, /obj/item/radio/beacon)) + if(b_stat) + user.show_message("The radio can now be attached and modified!") + else + user.show_message("The radio can no longer be modified or attached!") + updateDialog() + +/obj/item/radio/wirecutter_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + interact(user) + +/obj/item/radio/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + interact(user) + +/obj/item/radio/emp_act(severity) + on = 0 + disable_timer++ + addtimer(CALLBACK(src, .proc/enable_radio), rand(100, 200)) + + if(listening) + visible_message("[src] buzzes violently!") + + broadcasting = 0 + listening = 0 + for(var/ch_name in channels) + channels[ch_name] = 0 + ..() + +/obj/item/radio/proc/enable_radio() + if(disable_timer > 0) + disable_timer-- + if(!disable_timer) + on = 1 + +/////////////////////////////// +//////////Borg Radios////////// +/////////////////////////////// +//Giving borgs their own radio to have some more room to work with -Sieve + +/obj/item/radio/borg + var/mob/living/silicon/robot/myborg = null // Cyborg which owns this radio. Used for power checks + var/obj/item/encryptionkey/keyslot = null//Borg radios can handle a single encryption key + var/shut_up = 1 + icon = 'icons/obj/robot_component.dmi' // Cyborgs radio icons should look like the component. + icon_state = "radio" + canhear_range = 0 + subspace_transmission = 1 + dog_fashion = null + +/obj/item/radio/borg/syndicate + keyslot = new /obj/item/encryptionkey/syndicate/nukeops + +/obj/item/radio/borg/syndicate/CanUseTopic(mob/user, datum/topic_state/state) + . = ..() + if(. == STATUS_UPDATE && istype(user, /mob/living/silicon/robot/syndicate)) + . = STATUS_INTERACTIVE + +/obj/item/radio/borg/Destroy() + myborg = null + return ..() + +/obj/item/radio/borg/list_channels(var/mob/user) + return list_secure_channels(user) + +/obj/item/radio/borg/syndicate/New() + ..() + syndiekey = keyslot + set_frequency(SYND_FREQ) + +/obj/item/radio/borg/deathsquad + +/obj/item/radio/borg/deathsquad/New() + ..() + set_frequency(DTH_FREQ) + +/obj/item/radio/borg/ert + keyslot = new /obj/item/encryptionkey/ert + +/obj/item/radio/borg/ert/New() + ..() + set_frequency(ERT_FREQ) + +/obj/item/radio/borg/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/encryptionkey/)) + user.set_machine(src) + if(keyslot) + to_chat(user, "The radio can't hold another key!") + return + + if(!keyslot) + user.drop_item() + W.loc = src + keyslot = W + + recalculateChannels() + else + return ..() + +/obj/item/radio/borg/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = 0)) + return + user.set_machine(src) + if(keyslot) + for(var/ch_name in channels) + SSradio.remove_object(src, SSradio.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + + if(keyslot) + var/turf/T = get_turf(user) + if(T) + keyslot.loc = T + keyslot = null + + recalculateChannels() + to_chat(user, "You pop out the encryption key in the radio!") + I.play_tool_sound(user, I.tool_volume) + + else + to_chat(user, "This radio doesn't have any encryption keys!") + +/obj/item/radio/borg/proc/recalculateChannels() + channels = list() + syndiekey = null + + var/mob/living/silicon/robot/D = loc + if(D.module) + for(var/ch_name in D.module.channels) + if(ch_name in channels) + continue + channels += ch_name + channels[ch_name] += D.module.channels[ch_name] + if(keyslot) + for(var/ch_name in keyslot.channels) + if(ch_name in channels) + continue + channels += ch_name + channels[ch_name] += keyslot.channels[ch_name] + + if(keyslot.syndie) + syndiekey = keyslot + + + for(var/ch_name in channels) + if(!SSradio) + sleep(30) // Waiting for SSradio to be created. + if(!SSradio) + name = "broken radio" + return + + secure_radio_connections[ch_name] = SSradio.add_object(src, SSradio.radiochannels[ch_name], RADIO_CHAT) + + return + +/obj/item/radio/borg/Topic(href, href_list) + if(..()) + return 1 + if(href_list["mode"]) + var/enable_subspace_transmission = text2num(href_list["mode"]) + if(enable_subspace_transmission != subspace_transmission) + subspace_transmission = !subspace_transmission + if(subspace_transmission) + to_chat(usr, "Subspace Transmission is enabled.") + else + to_chat(usr, "Subspace Transmission is disabled.") + + if(subspace_transmission == 0)//Simple as fuck, clears the channel list to prevent talking/listening over them if subspace transmission is disabled + channels = list() + else + recalculateChannels() + . = 1 + if(href_list["shutup"]) // Toggle loudspeaker mode, AKA everyone around you hearing your radio. + var/do_shut_up = text2num(href_list["shutup"]) + if(do_shut_up != shut_up) + shut_up = !shut_up + if(shut_up) + canhear_range = 0 + to_chat(usr, "Loudspeaker disabled.") + else + canhear_range = 3 + to_chat(usr, "Loudspeaker enabled.") + . = 1 + + +/obj/item/radio/borg/interact(mob/user as mob) + if(!on) + return + + . = ..() + +/obj/item/radio/borg/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "radio_basic.tmpl", "[name]", 430, 500) + ui.open() + ui.set_auto_update(1) + +/obj/item/radio/borg/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + data["mic_status"] = broadcasting + data["speaker"] = listening + data["freq"] = format_frequency(frequency) + data["rawfreq"] = num2text(frequency) + + var/list/chanlist = list_channels(user) + if(islist(chanlist) && chanlist.len) + data["chan_list"] = chanlist + data["chan_list_len"] = chanlist.len + + if(syndiekey) + data["useSyndMode"] = 1 + + data["has_loudspeaker"] = 1 + data["loudspeaker"] = !shut_up + data["has_subspace"] = 1 + data["subspace"] = subspace_transmission + + return data + +/obj/item/radio/proc/config(op) + if(SSradio) + for(var/ch_name in channels) + SSradio.remove_object(src, SSradio.radiochannels[ch_name]) + secure_radio_connections = new + channels = op + if(SSradio) + for(var/ch_name in op) + secure_radio_connections[ch_name] = SSradio.add_object(src, SSradio.radiochannels[ch_name], RADIO_CHAT) + return + +/obj/item/radio/off + listening = 0 + dog_fashion = /datum/dog_fashion/back + +/obj/item/radio/phone + broadcasting = 0 + icon = 'icons/obj/items.dmi' + icon_state = "red_phone" + listening = 1 + name = "phone" + dog_fashion = null + +/obj/item/radio/phone/medbay + frequency = MED_I_FREQ + +/obj/item/radio/phone/medbay/New() + ..() + internal_channels = default_medbay_channels.Copy() diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 536c3dfd0e2..34183a81102 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -1,869 +1,869 @@ -/* -CONTAINS: -T-RAY -DETECTIVE SCANNER -HEALTH ANALYZER -GAS ANALYZER -PLANT ANALYZER -REAGENT SCANNER -*/ -/obj/item/t_scanner - name = "T-ray scanner" - desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." - icon = 'icons/obj/device.dmi' - icon_state = "t-ray0" - var/on = 0 - slot_flags = SLOT_BELT - w_class = 2 - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - materials = list(MAT_METAL=150) - origin_tech = "magnets=1;engineering=1" - var/scan_range = 1 - var/pulse_duration = 10 - -/obj/item/t_scanner/longer_pulse - pulse_duration = 50 - -/obj/item/t_scanner/extended_range - scan_range = 3 - -/obj/item/t_scanner/extended_range/longer_pulse - scan_range = 3 - pulse_duration = 50 - -/obj/item/t_scanner/Destroy() - if(on) - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/t_scanner/attack_self(mob/user) - - on = !on - icon_state = copytext(icon_state, 1, length(icon_state))+"[on]" - - if(on) - START_PROCESSING(SSobj, src) - - -/obj/item/t_scanner/process() - if(!on) - STOP_PROCESSING(SSobj, src) - return null - scan() - -/obj/item/t_scanner/proc/scan() - - for(var/turf/T in range(scan_range, src.loc) ) - - if(!T.intact) - continue - - for(var/obj/O in T.contents) - - if(O.level != 1) - continue - - if(O.invisibility == 101) - O.invisibility = 0 - O.alpha = 128 - spawn(pulse_duration) - if(O) - var/turf/U = O.loc - if(U && U.intact) - O.invisibility = 101 - O.alpha = 255 - for(var/mob/living/M in T.contents) - var/oldalpha = M.alpha - if(M.alpha < 255 && istype(M)) - M.alpha = 255 - spawn(10) - if(M) - M.alpha = oldalpha - - var/mob/living/M = locate() in T - - if(M && M.invisibility == 2) - M.invisibility = 0 - spawn(2) - if(M) - M.invisibility = INVISIBILITY_LEVEL_TWO - - -/proc/chemscan(mob/living/user, mob/living/M) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.reagents) - if(H.reagents.reagent_list.len) - to_chat(user, "Subject contains the following reagents:") - for(var/datum/reagent/R in H.reagents.reagent_list) - to_chat(user, "[R.volume]u of [R.name][R.overdosed ? " - OVERDOSING" : ".
        "]") - else - to_chat(user, "Subject contains no reagents.") - if(H.reagents.addiction_list.len) - to_chat(user, "Subject is addicted to the following reagents:") - for(var/datum/reagent/R in H.reagents.addiction_list) - to_chat(user, "[R.name] Stage: [R.addiction_stage]/5") - else - to_chat(user, "Subject is not addicted to any reagents.") - -/obj/item/healthanalyzer - name = "Health Analyzer" - icon = 'icons/obj/device.dmi' - icon_state = "health" - item_state = "healthanalyzer" - desc = "A hand-held body scanner able to distinguish vital signs of the subject." - flags = CONDUCT | NOBLUDGEON - slot_flags = SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=200) - origin_tech = "magnets=1;biotech=1" - var/mode = 1 - var/advanced = FALSE - -/obj/item/healthanalyzer/attack(mob/living/M, mob/living/user) - if(((CLUMSY in user.mutations) || user.getBrainLoss() >= 60) && prob(50)) - user.visible_message("[user] analyzes the floor's vitals!", "You stupidly try to analyze the floor's vitals!") - to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy") - to_chat(user, "Key: Suffocation/Toxin/Burn/Brute") - to_chat(user, "\tDamage specifics: 0-0-0-0") - to_chat(user, "Body temperature: ???") - return - - user.visible_message("[user] analyzes [M]'s vitals.", "You analyze [M]'s vitals.") - - healthscan(user, M, mode, advanced) - - add_fingerprint(user) - -// Used by the PDA medical scanner too -/proc/healthscan(mob/user, mob/living/M, mode = 1, advanced = FALSE) - if(!ishuman(M) || M.isSynthetic()) - //these sensors are designed for organic life - to_chat(user, "Analyzing Results for ERROR:\n\t Overall Status: ERROR") - to_chat(user, "\t Key: Suffocation/Toxin/Burns/Brute") - to_chat(user, "\t Damage Specifics: ? - ? - ? - ?") - to_chat(user, "Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)") - to_chat(user, "Warning: Blood Level ERROR: --% --cl.Type: ERROR") - to_chat(user, "Subject's pulse: -- bpm.") - return - - var/mob/living/carbon/human/H = M - var/fake_oxy = max(rand(1,40), H.getOxyLoss(), (300 - (H.getToxLoss() + H.getFireLoss() + H.getBruteLoss()))) - var/OX = H.getOxyLoss() > 50 ? "[H.getOxyLoss()]" : H.getOxyLoss() - var/TX = H.getToxLoss() > 50 ? "[H.getToxLoss()]" : H.getToxLoss() - var/BU = H.getFireLoss() > 50 ? "[H.getFireLoss()]" : H.getFireLoss() - var/BR = H.getBruteLoss() > 50 ? "[H.getBruteLoss()]" : H.getBruteLoss() - if(H.status_flags & FAKEDEATH) - OX = fake_oxy > 50 ? "[fake_oxy]" : fake_oxy - to_chat(user, "Analyzing Results for [H]:\n\t Overall Status: dead") - else - to_chat(user, "Analyzing Results for [H]:\n\t Overall Status: [H.stat > 1 ? "dead" : "[H.health]% healthy"]") - to_chat(user, "\t Key: Suffocation/Toxin/Burns/Brute") - to_chat(user, "\t Damage Specifics: [OX] - [TX] - [BU] - [BR]") - to_chat(user, "Body Temperature: [H.bodytemperature-T0C]°C ([H.bodytemperature*1.8-459.67]°F)") - if(H.timeofdeath && (H.stat == DEAD || (H.status_flags & FAKEDEATH))) - to_chat(user, "Time of Death: [station_time_timestamp("hh:mm:ss", H.timeofdeath)]") - var/tdelta = round(world.time - H.timeofdeath) - if(tdelta < (DEFIB_TIME_LIMIT * 10)) - to_chat(user, "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!") - - if(mode == 1) - var/list/damaged = H.get_damaged_organs(1,1) - to_chat(user, "Localized Damage, Brute/Burn:") - if(length(damaged) > 0) - for(var/obj/item/organ/external/org in damaged) - to_chat(user, "\t\t[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]") - - OX = H.getOxyLoss() > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" - TX = H.getToxLoss() > 50 ? "Dangerous amount of toxins detected" : "Subject bloodstream toxin level minimal" - BU = H.getFireLoss() > 50 ? "Severe burn damage detected" : "Subject burn injury status O.K" - BR = H.getBruteLoss() > 50 ? "Severe anatomical damage detected" : "Subject brute-force injury status O.K" - if(H.status_flags & FAKEDEATH) - OX = fake_oxy > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" - to_chat(user, "[OX] | [TX] | [BU] | [BR]") - - if(advanced) - chemscan(user, H) - for(var/thing in H.viruses) - var/datum/disease/D = thing - if(!(D.visibility_flags & HIDDEN_SCANNER)) - to_chat(user, "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]") - if(H.undergoing_cardiac_arrest()) - var/obj/item/organ/internal/heart/heart = H.get_int_organ(/obj/item/organ/internal/heart) - if(heart && !(heart.status & ORGAN_DEAD)) - to_chat(user, "Warning: Medical Emergency detected\nName: Cardiac Arrest.\nType: The patient's heart has stopped.\nStage: 1/1.\nPossible Cure: Electric Shock") - else if(heart && (heart.status & ORGAN_DEAD)) - to_chat(user, "Subject's heart is necrotic.") - else if(!heart) - to_chat(user, "Subject has no heart.") - - if(H.getStaminaLoss()) - to_chat(user, "Subject appears to be suffering from fatigue.") - if(H.getCloneLoss()) - to_chat(user, "Subject appears to have [H.getCloneLoss() > 30 ? "severe" : "minor"] cellular damage.") - if(H.has_brain_worms()) - to_chat(user, "Subject suffering from aberrant brain activity. Recommend further scanning.") - - if(H.get_int_organ(/obj/item/organ/internal/brain)) - if(H.getBrainLoss() >= 100) - to_chat(user, "Subject is brain dead.") - else if(H.getBrainLoss() >= 60) - to_chat(user, "Severe brain damage detected. Subject likely to have mental retardation.") - else if(H.getBrainLoss() >= 10) - to_chat(user, "Significant brain damage detected. Subject may have had a concussion.") - else - to_chat(user, "Subject has no brain.") - - for(var/name in H.bodyparts_by_name) - var/obj/item/organ/external/e = H.bodyparts_by_name[name] - if(!e) - continue - var/limb = e.name - if(e.status & ORGAN_BROKEN) - if((e.limb_name in list("l_arm", "r_arm", "l_hand", "r_hand", "l_leg", "r_leg", "l_foot", "r_foot")) && !(e.status & ORGAN_SPLINTED)) - to_chat(user, "Unsecured fracture in subject [limb]. Splinting recommended for transport.") - if(e.has_infected_wound()) - to_chat(user, "Infected wound detected in subject [limb]. Disinfection recommended.") - - for(var/name in H.bodyparts_by_name) - var/obj/item/organ/external/e = H.bodyparts_by_name[name] - if(!e) - continue - if(e.status & ORGAN_BROKEN) - to_chat(user, "Bone fractures detected. Advanced scanner required for location.") - break - for(var/obj/item/organ/external/e in H.bodyparts) - if(e.internal_bleeding) - to_chat(user, "Internal bleeding detected. Advanced scanner required for location.") - break - var/blood_id = H.get_blood_id() - if(blood_id) - if(H.bleed_rate) - to_chat(user, "Subject is bleeding!") - var/blood_percent = round((H.blood_volume / BLOOD_VOLUME_NORMAL)*100) - var/blood_type = H.dna.blood_type - if(blood_id != "blood")//special blood substance - var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] - if(R) - blood_type = R.name - else - blood_type = blood_id - if(H.blood_volume <= BLOOD_VOLUME_SAFE && H.blood_volume > BLOOD_VOLUME_OKAY) - to_chat(user, "LOW blood level [blood_percent] %, [H.blood_volume] cl, type: [blood_type]") - else if(H.blood_volume <= BLOOD_VOLUME_OKAY) - to_chat(user, "CRITICAL blood level [blood_percent] %, [H.blood_volume] cl, type: [blood_type]") - else - to_chat(user, "Blood level [blood_percent] %, [H.blood_volume] cl, type: [blood_type]") - - to_chat(user, "Subject's pulse: [H.get_pulse(GETPULSE_TOOL)] bpm.") - var/implant_detect - for(var/obj/item/organ/internal/cyberimp/CI in H.internal_organs) - if(CI.is_robotic()) - implant_detect += "[H.name] is modified with a [CI.name].
        " - if(implant_detect) - to_chat(user, "Detected cybernetic modifications:") - to_chat(user, "[implant_detect]") - if(H.gene_stability < 40) - to_chat(user, "Subject's genes are quickly breaking down!") - else if(H.gene_stability < 70) - to_chat(user, "Subject's genes are showing signs of spontaneous breakdown.") - else if(H.gene_stability < 85) - to_chat(user, "Subject's genes are showing minor signs of instability.") - else - to_chat(user, "Subject's genes are stable.") - -/obj/item/healthanalyzer/attack_self(mob/user) - toggle_mode() - -/obj/item/healthanalyzer/verb/toggle_mode() - set name = "Switch Verbosity" - set category = "Object" - - mode = !mode - switch(mode) - if(1) - to_chat(usr, "The scanner now shows specific limb damage.") - if(0) - to_chat(usr, "The scanner no longer shows limb damage.") - -/obj/item/healthanalyzer/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/healthupgrade)) - if(advanced) - to_chat(user, "An upgrade is already installed on [src].") - else - if(user.unEquip(I)) - to_chat(user, "You install the upgrade on [src].") - add_overlay("advanced") - playsound(loc, I.usesound, 50, 1) - advanced = TRUE - qdel(I) - return - return ..() - -/obj/item/healthanalyzer/advanced - advanced = TRUE - -/obj/item/healthanalyzer/advanced/Initialize(mapload) - . = ..() - add_overlay("advanced") - - -/obj/item/healthupgrade - name = "Health Analyzer Upgrade" - icon = 'icons/obj/device.dmi' - icon_state = "healthupgrade" - desc = "An upgrade unit that can be installed on a health analyzer for expanded functionality." - w_class = WEIGHT_CLASS_TINY - origin_tech = "magnets=2;biotech=2" - usesound = 'sound/items/deconstruct.ogg' - -/obj/item/analyzer - desc = "A hand-held environmental scanner which reports current gas levels." - name = "analyzer" - icon = 'icons/obj/device.dmi' - icon_state = "atmos" - item_state = "analyzer" - w_class = WEIGHT_CLASS_SMALL - flags = CONDUCT - slot_flags = SLOT_BELT - throwforce = 0 - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=30, MAT_GLASS=20) - origin_tech = "magnets=1;engineering=1" - var/cooldown = FALSE - var/cooldown_time = 250 - var/accuracy // 0 is the best accuracy. - -/obj/item/analyzer/examine(mob/user) - . = ..() - . += "Alt-click [src] to activate the barometer function." - -/obj/item/analyzer/attack_self(mob/user as mob) - - if(user.stat) - return - - var/turf/location = user.loc - if(!( istype(location, /turf) )) - return - - var/datum/gas_mixture/environment = location.return_air() - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - to_chat(user, "Results:") - if(abs(pressure - ONE_ATMOSPHERE) < 10) - to_chat(user, "Pressure: [round(pressure,0.1)] kPa") - else - to_chat(user, "Pressure: [round(pressure,0.1)] kPa") - if(total_moles) - var/o2_concentration = environment.oxygen/total_moles - var/n2_concentration = environment.nitrogen/total_moles - var/co2_concentration = environment.carbon_dioxide/total_moles - var/plasma_concentration = environment.toxins/total_moles - - var/unknown_concentration = 1-(o2_concentration+n2_concentration+co2_concentration+plasma_concentration) - if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(user, "Nitrogen: [round(n2_concentration*100)] %") - else - to_chat(user, "Nitrogen: [round(n2_concentration*100)] %") - - if(abs(o2_concentration - O2STANDARD) < 2) - to_chat(user, "Oxygen: [round(o2_concentration*100)] %") - else - to_chat(user, "Oxygen: [round(o2_concentration*100)] %") - - if(co2_concentration > 0.01) - to_chat(user, "CO2: [round(co2_concentration*100)] %") - else - to_chat(user, "CO2: [round(co2_concentration*100)] %") - - if(plasma_concentration > 0.01) - to_chat(user, "Plasma: [round(plasma_concentration*100)] %") - - if(unknown_concentration > 0.01) - to_chat(user, "Unknown: [round(unknown_concentration*100)] %") - - to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C") - - add_fingerprint(user) - -/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens - ..() - - if(!user.incapacitated() && Adjacent(user)) - - if(cooldown) - to_chat(user, "[src]'s barometer function is prepraring itself.") - return - - var/turf/T = get_turf(user) - if(!T) - return - - playsound(src, 'sound/effects/pop.ogg', 100) - var/area/user_area = T.loc - var/datum/weather/ongoing_weather = null - - if(!user_area.outdoors) - to_chat(user, "[src]'s barometer function won't work indoors!") - return - - for(var/V in SSweather.processing) - var/datum/weather/W = V - if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) - ongoing_weather = W - break - - if(ongoing_weather) - if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) - to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]") - return - - to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].") - if(ongoing_weather.aesthetic) - to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.") - else - var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] - var/fixed = next_hit ? next_hit - world.time : -1 - if(fixed < 0) - to_chat(user, "[src]'s barometer function was unable to trace any weather patterns.") - else - to_chat(user, "[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].") - cooldown = TRUE - addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time) - -/obj/item/analyzer/proc/ping() - if(isliving(loc)) - var/mob/living/L = loc - to_chat(L, "[src]'s barometer function is ready!") - playsound(src, 'sound/machines/click.ogg', 100) - cooldown = FALSE - -/obj/item/analyzer/proc/butchertime(amount) - if(!amount) - return - if(accuracy) - var/inaccurate = round(accuracy * (1 / 3)) - if(prob(50)) - amount -= inaccurate - if(prob(50)) - amount += inaccurate - return DisplayTimeText(max(1, amount)) - -/obj/item/reagent_scanner - name = "reagent scanner" - desc = "A hand-held reagent scanner which identifies chemical agents and blood types." - icon = 'icons/obj/device.dmi' - icon_state = "spectrometer" - item_state = "analyzer" - w_class = WEIGHT_CLASS_SMALL - flags = CONDUCT - slot_flags = SLOT_BELT - throwforce = 5 - throw_speed = 4 - throw_range = 20 - materials = list(MAT_METAL=30, MAT_GLASS=20) - origin_tech = "magnets=2;biotech=1;plasmatech=2" - var/details = FALSE - var/datatoprint = "" - var/scanning = TRUE - actions_types = list(/datum/action/item_action/print_report) - -/obj/item/reagent_scanner/afterattack(obj/O, mob/user as mob) - if(user.stat) - return - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - if(!istype(O)) - return - - if(!isnull(O.reagents)) - var/dat = "" - var/blood_type = "" - if(O.reagents.reagent_list.len > 0) - var/one_percent = O.reagents.total_volume / 100 - for(var/datum/reagent/R in O.reagents.reagent_list) - if(R.id != "blood") - dat += "
        [TAB][R][details ? ": [R.volume / one_percent]%" : ""]" - else - blood_type = R.data["blood_type"] - dat += "
        [TAB][R][blood_type ? " [blood_type]" : ""][details ? ": [R.volume / one_percent]%" : ""]" - if(dat) - to_chat(user, "Chemicals found: [dat]") - datatoprint = dat - scanning = FALSE - else - to_chat(user, "No active chemical agents found in [O].") - else - to_chat(user, "No significant chemical agents found in [O].") - return - -/obj/item/reagent_scanner/adv - name = "advanced reagent scanner" - icon_state = "adv_spectrometer" - details = TRUE - origin_tech = "magnets=4;biotech=3;plasmatech=3" - -/obj/item/reagent_scanner/proc/print_report() - if(!scanning) - usr.visible_message("[src] rattles and prints out a sheet of paper.") - playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1) - sleep(50) - - var/obj/item/paper/P = new(get_turf(src)) - P.name = "Reagent Scanner Report: [station_time_timestamp()]" - P.info = "
        Reagent Scanner

        Data Analysis:



        Chemical agents detected:
        [datatoprint]

        " - - if(ismob(loc)) - var/mob/M = loc - M.put_in_hands(P) - to_chat(M, "Report printed. Log cleared.") - datatoprint = "" - scanning = TRUE - else - to_chat(usr, "[src] has no logs or is already in use.") - -/obj/item/reagent_scanner/ui_action_click() - print_report() - -/obj/item/slime_scanner - name = "slime scanner" - icon = 'icons/obj/device.dmi' - icon_state = "adv_spectrometer_s" - item_state = "analyzer" - origin_tech = "biotech=2" - w_class = WEIGHT_CLASS_SMALL - flags = CONDUCT - throwforce = 0 - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=30, MAT_GLASS=20) - -/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) - if(user.incapacitated() || user.eye_blind) - return - if(!isslime(M)) - to_chat(user, "This device can only scan slimes!") - return - var/mob/living/simple_animal/slime/T = M - slime_scan(T, user) - -/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user) - to_chat(user, "========================") - to_chat(user, "Slime scan results:") - to_chat(user, "[T.colour] [T.is_adult ? "adult" : "baby"] slime") - to_chat(user, "Nutrition: [T.nutrition]/[T.get_max_nutrition()]") - if(T.nutrition < T.get_starve_nutrition()) - to_chat(user, "Warning: slime is starving!") - else if(T.nutrition < T.get_hunger_nutrition()) - to_chat(user, "Warning: slime is hungry") - to_chat(user, "Electric change strength: [T.powerlevel]") - to_chat(user, "Health: [round(T.health/T.maxHealth,0.01)*100]%") - if(T.slime_mutation[4] == T.colour) - to_chat(user, "This slime does not evolve any further.") - else - if(T.slime_mutation[3] == T.slime_mutation[4]) - if(T.slime_mutation[2] == T.slime_mutation[1]) - to_chat(user, "Possible mutation: [T.slime_mutation[3]]") - to_chat(user, "Genetic destability: [T.mutation_chance/2] % chance of mutation on splitting") - else - to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)") - to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting") - else - to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]") - to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting") - if(T.cores > 1) - to_chat(user, "Multiple cores detected") - to_chat(user, "Growth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]") - if(T.effectmod) - to_chat(user, "Core mutation in progress: [T.effectmod]") - to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]") - to_chat(user, "========================") - -/obj/item/bodyanalyzer - name = "handheld body analyzer" - icon = 'icons/obj/device.dmi' - icon_state = "bodyanalyzer_0" - item_state = "healthanalyser" - desc = "A handheld scanner capable of deep-scanning an entire body." - slot_flags = SLOT_BELT - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - throw_speed = 5 - throw_range = 10 - origin_tech = "magnets=6;biotech=6" - var/obj/item/stock_parts/cell/cell - var/cell_type = /obj/item/stock_parts/cell/upgraded - var/ready = TRUE // Ready to scan - var/time_to_use = 0 // How much time remaining before next scan is available. - var/usecharge = 750 - var/scan_time = 10 SECONDS //how long does it take to scan - var/scan_cd = 60 SECONDS //how long before we can scan again - -/obj/item/bodyanalyzer/get_cell() - return cell - -/obj/item/bodyanalyzer/advanced - cell_type = /obj/item/stock_parts/cell/upgraded/plus - -/obj/item/bodyanalyzer/borg - name = "cyborg body analyzer" - desc = "Scan an entire body to prepare for field surgery. Consumes power for each scan." - -/obj/item/bodyanalyzer/borg/syndicate - scan_time = 5 SECONDS - scan_cd = 20 SECONDS - -/obj/item/bodyanalyzer/New() - ..() - cell = new cell_type(src) - cell.give(cell.maxcharge) - update_icon() - -/obj/item/bodyanalyzer/proc/setReady() - ready = TRUE - playsound(src, 'sound/machines/defib_saftyon.ogg', 50, 0) - update_icon() - -/obj/item/bodyanalyzer/update_icon(printing = FALSE) - overlays.Cut() - var/percent = cell.percent() - if(ready) - icon_state = "bodyanalyzer_1" - else - icon_state = "bodyanalyzer_2" - - var/overlayid = round(percent / 10) - overlayid = "bodyanalyzer_charge[overlayid]" - overlays += icon(icon, overlayid) - - if(printing) - overlays += icon(icon, "bodyanalyzer_printing") - -/obj/item/bodyanalyzer/attack(mob/living/M, mob/living/carbon/human/user) - if(user.incapacitated() || !user.Adjacent(M)) - return - - if(!ready) - to_chat(user, "The scanner beeps angrily at you! It's currently recharging - [round((time_to_use - world.time) * 0.1)] seconds remaining.") - playsound(user.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) - return - - if(cell.charge >= usecharge) - mobScan(M, user) - else - to_chat(user, "The scanner beeps angrily at you! It's out of charge!") - playsound(user.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) - -/obj/item/bodyanalyzer/borg/attack(mob/living/M, mob/living/silicon/robot/user) - if(user.incapacitated() || !user.Adjacent(M)) - return - - if(!ready) - to_chat(user, "[src] is currently recharging - [round((time_to_use - world.time) * 0.1)] seconds remaining.") - return - - if(user.cell.charge >= usecharge) - mobScan(M, user) - else - to_chat(user, "You need to recharge before you can use [src]") - -/obj/item/bodyanalyzer/proc/mobScan(mob/living/M, mob/user) - if(ishuman(M)) - var/report = generate_printing_text(M, user) - user.visible_message("[user] begins scanning [M] with [src].", "You begin scanning [M].") - if(do_after(user, scan_time, target = M)) - var/obj/item/paper/printout = new - printout.info = report - printout.name = "Scan report - [M.name]" - playsound(user.loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) - user.put_in_hands(printout) - time_to_use = world.time + scan_cd - if(isrobot(user)) - var/mob/living/silicon/robot/R = user - R.cell.use(usecharge) - else - cell.use(usecharge) - ready = FALSE - update_icon(TRUE) - addtimer(CALLBACK(src, /obj/item/bodyanalyzer/.proc/setReady), scan_cd) - addtimer(CALLBACK(src, /obj/item/bodyanalyzer/.proc/update_icon), 20) - - else if(iscorgi(M) && M.stat == DEAD) - to_chat(user, "You wonder if [M.p_they()] was a good dog. [src] tells you they were the best...") // :'( - playsound(loc, 'sound/machines/ping.ogg', 50, 0) - ready = FALSE - addtimer(CALLBACK(src, /obj/item/bodyanalyzer/.proc/setReady), scan_cd) - time_to_use = world.time + scan_cd - else - to_chat(user, "Scanning error detected. Invalid specimen.") - -//Unashamedly ripped from adv_med.dm -/obj/item/bodyanalyzer/proc/generate_printing_text(mob/living/M, mob/user) - var/dat = "" - var/mob/living/carbon/human/target = M - - dat = "Target Statistics:
        " - var/t1 - switch(target.stat) // obvious, see what their status is - if(CONSCIOUS) - t1 = "Conscious" - if(UNCONSCIOUS) - t1 = "Unconscious" - else - t1 = "*dead*" - dat += "[target.health > 50 ? "" : ""]\tHealth %: [target.health], ([t1])
        " - - var/found_disease = FALSE - for(var/thing in target.viruses) - var/datum/disease/D = thing - if(D.visibility_flags) //If any visibility flags are on. - continue - found_disease = TRUE - break - if(found_disease) - dat += "Disease detected in target.
        " - - var/extra_font = null - extra_font = (target.getBruteLoss() < 60 ? "" : "") - dat += "[extra_font]\t-Brute Damage %: [target.getBruteLoss()]
        " - - extra_font = (target.getOxyLoss() < 60 ? "" : "") - dat += "[extra_font]\t-Respiratory Damage %: [target.getOxyLoss()]
        " - - extra_font = (target.getToxLoss() < 60 ? "" : "") - dat += "[extra_font]\t-Toxin Content %: [target.getToxLoss()]
        " - - extra_font = (target.getFireLoss() < 60 ? "" : "") - dat += "[extra_font]\t-Burn Severity %: [target.getFireLoss()]
        " - - extra_font = (target.radiation < 10 ?"" : "") - dat += "[extra_font]\tRadiation Level %: [target.radiation]
        " - - extra_font = (target.getCloneLoss() < 1 ?"" : "") - dat += "[extra_font]\tGenetic Tissue Damage %: [target.getCloneLoss()]
        " - - extra_font = (target.getBrainLoss() < 1 ?"" : "") - dat += "[extra_font]\tApprox. Brain Damage %: [target.getBrainLoss()]
        " - - dat += "Paralysis Summary %: [target.paralysis] ([round(target.paralysis / 4)] seconds left!)
        " - dat += "Body Temperature: [target.bodytemperature-T0C]°C ([target.bodytemperature*1.8-459.67]°F)
        " - - dat += "
        " - - if(target.has_brain_worms()) - dat += "Large growth detected in frontal lobe, possibly cancerous. Surgical removal is recommended.
        " - - var/blood_percent = round((target.blood_volume / BLOOD_VOLUME_NORMAL)) - blood_percent *= 100 - - extra_font = (target.blood_volume > 448 ? "" : "") - dat += "[extra_font]\tBlood Level %: [blood_percent] ([target.blood_volume] units)
        " - - if(target.reagents) - dat += "Epinephrine units: [target.reagents.get_reagent_amount("Epinephrine")] units
        " - dat += "Ether: [target.reagents.get_reagent_amount("ether")] units
        " - - extra_font = (target.reagents.get_reagent_amount("silver_sulfadiazine") < 30 ? "" : "") - dat += "[extra_font]\tSilver Sulfadiazine: [target.reagents.get_reagent_amount("silver_sulfadiazine")]
        " - - extra_font = (target.reagents.get_reagent_amount("styptic_powder") < 30 ? "" : "") - dat += "[extra_font]\tStyptic Powder: [target.reagents.get_reagent_amount("styptic_powder")] units
        " - - extra_font = (target.reagents.get_reagent_amount("salbutamol") < 30 ? "" : "") - dat += "[extra_font]\tSalbutamol: [target.reagents.get_reagent_amount("salbutamol")] units
        " - - dat += "
        " - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - - for(var/obj/item/organ/external/e in target.bodyparts) - dat += "" - var/AN = "" - var/open = "" - var/infected = "" - var/robot = "" - var/imp = "" - var/bled = "" - var/splint = "" - var/internal_bleeding = "" - var/lung_ruptured = "" - if(e.internal_bleeding) - internal_bleeding = "
        Internal bleeding" - if(istype(e, /obj/item/organ/external/chest) && target.is_lung_ruptured()) - lung_ruptured = "Lung ruptured:" - if(e.status & ORGAN_SPLINTED) - splint = "Splinted:" - if(e.status & ORGAN_BROKEN) - AN = "[e.broken_description]:" - if(e.is_robotic()) - robot = "Robotic:" - if(e.open) - open = "Open:" - switch(e.germ_level) - if(INFECTION_LEVEL_ONE to INFECTION_LEVEL_ONE + 200) - infected = "Mild Infection:" - if(INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) - infected = "Mild Infection+:" - if(INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) - infected = "Mild Infection++:" - if(INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) - 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) - infected = "Acute Infection++:" - if(INFECTION_LEVEL_THREE to INFINITY) - infected = "Septic:" - - var/unknown_body = 0 - for(var/I in e.embedded_objects) - unknown_body++ - - if(unknown_body || e.hidden) - imp += "Unknown body present:" - if(!AN && !open && !infected & !imp) - AN = "None:" - dat += "" - dat += "" - for(var/obj/item/organ/internal/i in target.internal_organs) - var/mech = i.desc - var/infection = "None" - switch(i.germ_level) - if(1 to INFECTION_LEVEL_ONE + 200) - infection = "Mild Infection:" - if(INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) - infection = "Mild Infection+:" - if(INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) - infection = "Mild Infection++:" - if(INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) - infection = "Acute Infection:" - if(INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300) - infection = "Acute Infection+:" - if(INFECTION_LEVEL_TWO + 300 to INFINITY) - infection = "Acute Infection++:" - - dat += "" - dat += "" - dat += "" - dat += "
        OrganBurn DamageBrute DamageOther Wounds
        [e.name][e.burn_dam][e.brute_dam][robot][bled][AN][splint][open][infected][imp][internal_bleeding][lung_ruptured]
        [i.name]N/A[i.damage][infection]:[mech]
        " - if(target.disabilities & BLIND) - dat += "Cataracts detected.
        " - if(target.disabilities & COLOURBLIND) - dat += "Photoreceptor abnormalities detected.
        " - if(target.disabilities & NEARSIGHTED) - dat += "Retinal misalignment detected.
        " - - return dat \ No newline at end of file +/* +CONTAINS: +T-RAY +DETECTIVE SCANNER +HEALTH ANALYZER +GAS ANALYZER +PLANT ANALYZER +REAGENT SCANNER +*/ +/obj/item/t_scanner + name = "T-ray scanner" + desc = "A terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes." + icon = 'icons/obj/device.dmi' + icon_state = "t-ray0" + var/on = 0 + slot_flags = SLOT_BELT + w_class = 2 + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + materials = list(MAT_METAL=150) + origin_tech = "magnets=1;engineering=1" + var/scan_range = 1 + var/pulse_duration = 10 + +/obj/item/t_scanner/longer_pulse + pulse_duration = 50 + +/obj/item/t_scanner/extended_range + scan_range = 3 + +/obj/item/t_scanner/extended_range/longer_pulse + scan_range = 3 + pulse_duration = 50 + +/obj/item/t_scanner/Destroy() + if(on) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/t_scanner/attack_self(mob/user) + + on = !on + icon_state = copytext(icon_state, 1, length(icon_state))+"[on]" + + if(on) + START_PROCESSING(SSobj, src) + + +/obj/item/t_scanner/process() + if(!on) + STOP_PROCESSING(SSobj, src) + return null + scan() + +/obj/item/t_scanner/proc/scan() + + for(var/turf/T in range(scan_range, src.loc) ) + + if(!T.intact) + continue + + for(var/obj/O in T.contents) + + if(O.level != 1) + continue + + if(O.invisibility == 101) + O.invisibility = 0 + O.alpha = 128 + spawn(pulse_duration) + if(O) + var/turf/U = O.loc + if(U && U.intact) + O.invisibility = 101 + O.alpha = 255 + for(var/mob/living/M in T.contents) + var/oldalpha = M.alpha + if(M.alpha < 255 && istype(M)) + M.alpha = 255 + spawn(10) + if(M) + M.alpha = oldalpha + + var/mob/living/M = locate() in T + + if(M && M.invisibility == 2) + M.invisibility = 0 + spawn(2) + if(M) + M.invisibility = INVISIBILITY_LEVEL_TWO + + +/proc/chemscan(mob/living/user, mob/living/M) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.reagents) + if(H.reagents.reagent_list.len) + to_chat(user, "Subject contains the following reagents:") + for(var/datum/reagent/R in H.reagents.reagent_list) + to_chat(user, "[R.volume]u of [R.name][R.overdosed ? " - OVERDOSING" : ".
        "]") + else + to_chat(user, "Subject contains no reagents.") + if(H.reagents.addiction_list.len) + to_chat(user, "Subject is addicted to the following reagents:") + for(var/datum/reagent/R in H.reagents.addiction_list) + to_chat(user, "[R.name] Stage: [R.addiction_stage]/5") + else + to_chat(user, "Subject is not addicted to any reagents.") + +/obj/item/healthanalyzer + name = "Health Analyzer" + icon = 'icons/obj/device.dmi' + icon_state = "health" + item_state = "healthanalyzer" + desc = "A hand-held body scanner able to distinguish vital signs of the subject." + flags = CONDUCT | NOBLUDGEON + slot_flags = SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=200) + origin_tech = "magnets=1;biotech=1" + var/mode = 1 + var/advanced = FALSE + +/obj/item/healthanalyzer/attack(mob/living/M, mob/living/user) + if(((CLUMSY in user.mutations) || user.getBrainLoss() >= 60) && prob(50)) + user.visible_message("[user] analyzes the floor's vitals!", "You stupidly try to analyze the floor's vitals!") + to_chat(user, "Analyzing results for The floor:\n\tOverall status: Healthy") + to_chat(user, "Key: Suffocation/Toxin/Burn/Brute") + to_chat(user, "\tDamage specifics: 0-0-0-0") + to_chat(user, "Body temperature: ???") + return + + user.visible_message("[user] analyzes [M]'s vitals.", "You analyze [M]'s vitals.") + + healthscan(user, M, mode, advanced) + + add_fingerprint(user) + +// Used by the PDA medical scanner too +/proc/healthscan(mob/user, mob/living/M, mode = 1, advanced = FALSE) + if(!ishuman(M) || M.isSynthetic()) + //these sensors are designed for organic life + to_chat(user, "Analyzing Results for ERROR:\n\t Overall Status: ERROR") + to_chat(user, "\t Key: Suffocation/Toxin/Burns/Brute") + to_chat(user, "\t Damage Specifics: ? - ? - ? - ?") + to_chat(user, "Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)") + to_chat(user, "Warning: Blood Level ERROR: --% --cl.Type: ERROR") + to_chat(user, "Subject's pulse: -- bpm.") + return + + var/mob/living/carbon/human/H = M + var/fake_oxy = max(rand(1,40), H.getOxyLoss(), (300 - (H.getToxLoss() + H.getFireLoss() + H.getBruteLoss()))) + var/OX = H.getOxyLoss() > 50 ? "[H.getOxyLoss()]" : H.getOxyLoss() + var/TX = H.getToxLoss() > 50 ? "[H.getToxLoss()]" : H.getToxLoss() + var/BU = H.getFireLoss() > 50 ? "[H.getFireLoss()]" : H.getFireLoss() + var/BR = H.getBruteLoss() > 50 ? "[H.getBruteLoss()]" : H.getBruteLoss() + if(H.status_flags & FAKEDEATH) + OX = fake_oxy > 50 ? "[fake_oxy]" : fake_oxy + to_chat(user, "Analyzing Results for [H]:\n\t Overall Status: dead") + else + to_chat(user, "Analyzing Results for [H]:\n\t Overall Status: [H.stat > 1 ? "dead" : "[H.health]% healthy"]") + to_chat(user, "\t Key: Suffocation/Toxin/Burns/Brute") + to_chat(user, "\t Damage Specifics: [OX] - [TX] - [BU] - [BR]") + to_chat(user, "Body Temperature: [H.bodytemperature-T0C]°C ([H.bodytemperature*1.8-459.67]°F)") + if(H.timeofdeath && (H.stat == DEAD || (H.status_flags & FAKEDEATH))) + to_chat(user, "Time of Death: [station_time_timestamp("hh:mm:ss", H.timeofdeath)]") + var/tdelta = round(world.time - H.timeofdeath) + if(tdelta < (DEFIB_TIME_LIMIT * 10)) + to_chat(user, "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!") + + if(mode == 1) + var/list/damaged = H.get_damaged_organs(1,1) + to_chat(user, "Localized Damage, Brute/Burn:") + if(length(damaged) > 0) + for(var/obj/item/organ/external/org in damaged) + to_chat(user, "\t\t[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]") + + OX = H.getOxyLoss() > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" + TX = H.getToxLoss() > 50 ? "Dangerous amount of toxins detected" : "Subject bloodstream toxin level minimal" + BU = H.getFireLoss() > 50 ? "Severe burn damage detected" : "Subject burn injury status O.K" + BR = H.getBruteLoss() > 50 ? "Severe anatomical damage detected" : "Subject brute-force injury status O.K" + if(H.status_flags & FAKEDEATH) + OX = fake_oxy > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" + to_chat(user, "[OX] | [TX] | [BU] | [BR]") + + if(advanced) + chemscan(user, H) + for(var/thing in H.viruses) + var/datum/disease/D = thing + if(!(D.visibility_flags & HIDDEN_SCANNER)) + to_chat(user, "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]") + if(H.undergoing_cardiac_arrest()) + var/obj/item/organ/internal/heart/heart = H.get_int_organ(/obj/item/organ/internal/heart) + if(heart && !(heart.status & ORGAN_DEAD)) + to_chat(user, "Warning: Medical Emergency detected\nName: Cardiac Arrest.\nType: The patient's heart has stopped.\nStage: 1/1.\nPossible Cure: Electric Shock") + else if(heart && (heart.status & ORGAN_DEAD)) + to_chat(user, "Subject's heart is necrotic.") + else if(!heart) + to_chat(user, "Subject has no heart.") + + if(H.getStaminaLoss()) + to_chat(user, "Subject appears to be suffering from fatigue.") + if(H.getCloneLoss()) + to_chat(user, "Subject appears to have [H.getCloneLoss() > 30 ? "severe" : "minor"] cellular damage.") + if(H.has_brain_worms()) + to_chat(user, "Subject suffering from aberrant brain activity. Recommend further scanning.") + + if(H.get_int_organ(/obj/item/organ/internal/brain)) + if(H.getBrainLoss() >= 100) + to_chat(user, "Subject is brain dead.") + else if(H.getBrainLoss() >= 60) + to_chat(user, "Severe brain damage detected. Subject likely to have mental retardation.") + else if(H.getBrainLoss() >= 10) + to_chat(user, "Significant brain damage detected. Subject may have had a concussion.") + else + to_chat(user, "Subject has no brain.") + + for(var/name in H.bodyparts_by_name) + var/obj/item/organ/external/e = H.bodyparts_by_name[name] + if(!e) + continue + var/limb = e.name + if(e.status & ORGAN_BROKEN) + if((e.limb_name in list("l_arm", "r_arm", "l_hand", "r_hand", "l_leg", "r_leg", "l_foot", "r_foot")) && !(e.status & ORGAN_SPLINTED)) + to_chat(user, "Unsecured fracture in subject [limb]. Splinting recommended for transport.") + if(e.has_infected_wound()) + to_chat(user, "Infected wound detected in subject [limb]. Disinfection recommended.") + + for(var/name in H.bodyparts_by_name) + var/obj/item/organ/external/e = H.bodyparts_by_name[name] + if(!e) + continue + if(e.status & ORGAN_BROKEN) + to_chat(user, "Bone fractures detected. Advanced scanner required for location.") + break + for(var/obj/item/organ/external/e in H.bodyparts) + if(e.internal_bleeding) + to_chat(user, "Internal bleeding detected. Advanced scanner required for location.") + break + var/blood_id = H.get_blood_id() + if(blood_id) + if(H.bleed_rate) + to_chat(user, "Subject is bleeding!") + var/blood_percent = round((H.blood_volume / BLOOD_VOLUME_NORMAL)*100) + var/blood_type = H.dna.blood_type + if(blood_id != "blood")//special blood substance + var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] + if(R) + blood_type = R.name + else + blood_type = blood_id + if(H.blood_volume <= BLOOD_VOLUME_SAFE && H.blood_volume > BLOOD_VOLUME_OKAY) + to_chat(user, "LOW blood level [blood_percent] %, [H.blood_volume] cl, type: [blood_type]") + else if(H.blood_volume <= BLOOD_VOLUME_OKAY) + to_chat(user, "CRITICAL blood level [blood_percent] %, [H.blood_volume] cl, type: [blood_type]") + else + to_chat(user, "Blood level [blood_percent] %, [H.blood_volume] cl, type: [blood_type]") + + to_chat(user, "Subject's pulse: [H.get_pulse(GETPULSE_TOOL)] bpm.") + var/implant_detect + for(var/obj/item/organ/internal/cyberimp/CI in H.internal_organs) + if(CI.is_robotic()) + implant_detect += "[H.name] is modified with a [CI.name].
        " + if(implant_detect) + to_chat(user, "Detected cybernetic modifications:") + to_chat(user, "[implant_detect]") + if(H.gene_stability < 40) + to_chat(user, "Subject's genes are quickly breaking down!") + else if(H.gene_stability < 70) + to_chat(user, "Subject's genes are showing signs of spontaneous breakdown.") + else if(H.gene_stability < 85) + to_chat(user, "Subject's genes are showing minor signs of instability.") + else + to_chat(user, "Subject's genes are stable.") + +/obj/item/healthanalyzer/attack_self(mob/user) + toggle_mode() + +/obj/item/healthanalyzer/verb/toggle_mode() + set name = "Switch Verbosity" + set category = "Object" + + mode = !mode + switch(mode) + if(1) + to_chat(usr, "The scanner now shows specific limb damage.") + if(0) + to_chat(usr, "The scanner no longer shows limb damage.") + +/obj/item/healthanalyzer/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/healthupgrade)) + if(advanced) + to_chat(user, "An upgrade is already installed on [src].") + else + if(user.unEquip(I)) + to_chat(user, "You install the upgrade on [src].") + add_overlay("advanced") + playsound(loc, I.usesound, 50, 1) + advanced = TRUE + qdel(I) + return + return ..() + +/obj/item/healthanalyzer/advanced + advanced = TRUE + +/obj/item/healthanalyzer/advanced/Initialize(mapload) + . = ..() + add_overlay("advanced") + + +/obj/item/healthupgrade + name = "Health Analyzer Upgrade" + icon = 'icons/obj/device.dmi' + icon_state = "healthupgrade" + desc = "An upgrade unit that can be installed on a health analyzer for expanded functionality." + w_class = WEIGHT_CLASS_TINY + origin_tech = "magnets=2;biotech=2" + usesound = 'sound/items/deconstruct.ogg' + +/obj/item/analyzer + desc = "A hand-held environmental scanner which reports current gas levels." + name = "analyzer" + icon = 'icons/obj/device.dmi' + icon_state = "atmos" + item_state = "analyzer" + w_class = WEIGHT_CLASS_SMALL + flags = CONDUCT + slot_flags = SLOT_BELT + throwforce = 0 + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=30, MAT_GLASS=20) + origin_tech = "magnets=1;engineering=1" + var/cooldown = FALSE + var/cooldown_time = 250 + var/accuracy // 0 is the best accuracy. + +/obj/item/analyzer/examine(mob/user) + . = ..() + . += "Alt-click [src] to activate the barometer function." + +/obj/item/analyzer/attack_self(mob/user as mob) + + if(user.stat) + return + + var/turf/location = user.loc + if(!( istype(location, /turf) )) + return + + var/datum/gas_mixture/environment = location.return_air() + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + to_chat(user, "Results:") + if(abs(pressure - ONE_ATMOSPHERE) < 10) + to_chat(user, "Pressure: [round(pressure,0.1)] kPa") + else + to_chat(user, "Pressure: [round(pressure,0.1)] kPa") + if(total_moles) + var/o2_concentration = environment.oxygen/total_moles + var/n2_concentration = environment.nitrogen/total_moles + var/co2_concentration = environment.carbon_dioxide/total_moles + var/plasma_concentration = environment.toxins/total_moles + + var/unknown_concentration = 1-(o2_concentration+n2_concentration+co2_concentration+plasma_concentration) + if(abs(n2_concentration - N2STANDARD) < 20) + to_chat(user, "Nitrogen: [round(n2_concentration*100)] %") + else + to_chat(user, "Nitrogen: [round(n2_concentration*100)] %") + + if(abs(o2_concentration - O2STANDARD) < 2) + to_chat(user, "Oxygen: [round(o2_concentration*100)] %") + else + to_chat(user, "Oxygen: [round(o2_concentration*100)] %") + + if(co2_concentration > 0.01) + to_chat(user, "CO2: [round(co2_concentration*100)] %") + else + to_chat(user, "CO2: [round(co2_concentration*100)] %") + + if(plasma_concentration > 0.01) + to_chat(user, "Plasma: [round(plasma_concentration*100)] %") + + if(unknown_concentration > 0.01) + to_chat(user, "Unknown: [round(unknown_concentration*100)] %") + + to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C") + + add_fingerprint(user) + +/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens + ..() + + if(!user.incapacitated() && Adjacent(user)) + + if(cooldown) + to_chat(user, "[src]'s barometer function is prepraring itself.") + return + + var/turf/T = get_turf(user) + if(!T) + return + + playsound(src, 'sound/effects/pop.ogg', 100) + var/area/user_area = T.loc + var/datum/weather/ongoing_weather = null + + if(!user_area.outdoors) + to_chat(user, "[src]'s barometer function won't work indoors!") + return + + for(var/V in SSweather.processing) + var/datum/weather/W = V + if(W.barometer_predictable && (T.z in W.impacted_z_levels) && W.area_type == user_area.type && !(W.stage == END_STAGE)) + ongoing_weather = W + break + + if(ongoing_weather) + if((ongoing_weather.stage == MAIN_STAGE) || (ongoing_weather.stage == WIND_DOWN_STAGE)) + to_chat(user, "[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == MAIN_STAGE ? "already here!" : "winding down."]") + return + + to_chat(user, "The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].") + if(ongoing_weather.aesthetic) + to_chat(user, "[src]'s barometer function says that the next storm will breeze on by.") + else + var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] + var/fixed = next_hit ? next_hit - world.time : -1 + if(fixed < 0) + to_chat(user, "[src]'s barometer function was unable to trace any weather patterns.") + else + to_chat(user, "[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].") + cooldown = TRUE + addtimer(CALLBACK(src,/obj/item/analyzer/proc/ping), cooldown_time) + +/obj/item/analyzer/proc/ping() + if(isliving(loc)) + var/mob/living/L = loc + to_chat(L, "[src]'s barometer function is ready!") + playsound(src, 'sound/machines/click.ogg', 100) + cooldown = FALSE + +/obj/item/analyzer/proc/butchertime(amount) + if(!amount) + return + if(accuracy) + var/inaccurate = round(accuracy * (1 / 3)) + if(prob(50)) + amount -= inaccurate + if(prob(50)) + amount += inaccurate + return DisplayTimeText(max(1, amount)) + +/obj/item/reagent_scanner + name = "reagent scanner" + desc = "A hand-held reagent scanner which identifies chemical agents and blood types." + icon = 'icons/obj/device.dmi' + icon_state = "spectrometer" + item_state = "analyzer" + w_class = WEIGHT_CLASS_SMALL + flags = CONDUCT + slot_flags = SLOT_BELT + throwforce = 5 + throw_speed = 4 + throw_range = 20 + materials = list(MAT_METAL=30, MAT_GLASS=20) + origin_tech = "magnets=2;biotech=1;plasmatech=2" + var/details = FALSE + var/datatoprint = "" + var/scanning = TRUE + actions_types = list(/datum/action/item_action/print_report) + +/obj/item/reagent_scanner/afterattack(obj/O, mob/user as mob) + if(user.stat) + return + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + if(!istype(O)) + return + + if(!isnull(O.reagents)) + var/dat = "" + var/blood_type = "" + if(O.reagents.reagent_list.len > 0) + var/one_percent = O.reagents.total_volume / 100 + for(var/datum/reagent/R in O.reagents.reagent_list) + if(R.id != "blood") + dat += "
        [TAB][R][details ? ": [R.volume / one_percent]%" : ""]" + else + blood_type = R.data["blood_type"] + dat += "
        [TAB][R][blood_type ? " [blood_type]" : ""][details ? ": [R.volume / one_percent]%" : ""]" + if(dat) + to_chat(user, "Chemicals found: [dat]") + datatoprint = dat + scanning = FALSE + else + to_chat(user, "No active chemical agents found in [O].") + else + to_chat(user, "No significant chemical agents found in [O].") + return + +/obj/item/reagent_scanner/adv + name = "advanced reagent scanner" + icon_state = "adv_spectrometer" + details = TRUE + origin_tech = "magnets=4;biotech=3;plasmatech=3" + +/obj/item/reagent_scanner/proc/print_report() + if(!scanning) + usr.visible_message("[src] rattles and prints out a sheet of paper.") + playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1) + sleep(50) + + var/obj/item/paper/P = new(get_turf(src)) + P.name = "Reagent Scanner Report: [station_time_timestamp()]" + P.info = "
        Reagent Scanner

        Data Analysis:



        Chemical agents detected:
        [datatoprint]

        " + + if(ismob(loc)) + var/mob/M = loc + M.put_in_hands(P) + to_chat(M, "Report printed. Log cleared.") + datatoprint = "" + scanning = TRUE + else + to_chat(usr, "[src] has no logs or is already in use.") + +/obj/item/reagent_scanner/ui_action_click() + print_report() + +/obj/item/slime_scanner + name = "slime scanner" + icon = 'icons/obj/device.dmi' + icon_state = "adv_spectrometer_s" + item_state = "analyzer" + origin_tech = "biotech=2" + w_class = WEIGHT_CLASS_SMALL + flags = CONDUCT + throwforce = 0 + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=30, MAT_GLASS=20) + +/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) + if(user.incapacitated() || user.eye_blind) + return + if(!isslime(M)) + to_chat(user, "This device can only scan slimes!") + return + var/mob/living/simple_animal/slime/T = M + slime_scan(T, user) + +/proc/slime_scan(mob/living/simple_animal/slime/T, mob/living/user) + to_chat(user, "========================") + to_chat(user, "Slime scan results:") + to_chat(user, "[T.colour] [T.is_adult ? "adult" : "baby"] slime") + to_chat(user, "Nutrition: [T.nutrition]/[T.get_max_nutrition()]") + if(T.nutrition < T.get_starve_nutrition()) + to_chat(user, "Warning: slime is starving!") + else if(T.nutrition < T.get_hunger_nutrition()) + to_chat(user, "Warning: slime is hungry") + to_chat(user, "Electric change strength: [T.powerlevel]") + to_chat(user, "Health: [round(T.health/T.maxHealth,0.01)*100]%") + if(T.slime_mutation[4] == T.colour) + to_chat(user, "This slime does not evolve any further.") + else + if(T.slime_mutation[3] == T.slime_mutation[4]) + if(T.slime_mutation[2] == T.slime_mutation[1]) + to_chat(user, "Possible mutation: [T.slime_mutation[3]]") + to_chat(user, "Genetic destability: [T.mutation_chance/2] % chance of mutation on splitting") + else + to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)") + to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting") + else + to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]") + to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting") + if(T.cores > 1) + to_chat(user, "Multiple cores detected") + to_chat(user, "Growth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]") + if(T.effectmod) + to_chat(user, "Core mutation in progress: [T.effectmod]") + to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]") + to_chat(user, "========================") + +/obj/item/bodyanalyzer + name = "handheld body analyzer" + icon = 'icons/obj/device.dmi' + icon_state = "bodyanalyzer_0" + item_state = "healthanalyser" + desc = "A handheld scanner capable of deep-scanning an entire body." + slot_flags = SLOT_BELT + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + throw_speed = 5 + throw_range = 10 + origin_tech = "magnets=6;biotech=6" + var/obj/item/stock_parts/cell/cell + var/cell_type = /obj/item/stock_parts/cell/upgraded + var/ready = TRUE // Ready to scan + var/time_to_use = 0 // How much time remaining before next scan is available. + var/usecharge = 750 + var/scan_time = 10 SECONDS //how long does it take to scan + var/scan_cd = 60 SECONDS //how long before we can scan again + +/obj/item/bodyanalyzer/get_cell() + return cell + +/obj/item/bodyanalyzer/advanced + cell_type = /obj/item/stock_parts/cell/upgraded/plus + +/obj/item/bodyanalyzer/borg + name = "cyborg body analyzer" + desc = "Scan an entire body to prepare for field surgery. Consumes power for each scan." + +/obj/item/bodyanalyzer/borg/syndicate + scan_time = 5 SECONDS + scan_cd = 20 SECONDS + +/obj/item/bodyanalyzer/New() + ..() + cell = new cell_type(src) + cell.give(cell.maxcharge) + update_icon() + +/obj/item/bodyanalyzer/proc/setReady() + ready = TRUE + playsound(src, 'sound/machines/defib_saftyon.ogg', 50, 0) + update_icon() + +/obj/item/bodyanalyzer/update_icon(printing = FALSE) + overlays.Cut() + var/percent = cell.percent() + if(ready) + icon_state = "bodyanalyzer_1" + else + icon_state = "bodyanalyzer_2" + + var/overlayid = round(percent / 10) + overlayid = "bodyanalyzer_charge[overlayid]" + overlays += icon(icon, overlayid) + + if(printing) + overlays += icon(icon, "bodyanalyzer_printing") + +/obj/item/bodyanalyzer/attack(mob/living/M, mob/living/carbon/human/user) + if(user.incapacitated() || !user.Adjacent(M)) + return + + if(!ready) + to_chat(user, "The scanner beeps angrily at you! It's currently recharging - [round((time_to_use - world.time) * 0.1)] seconds remaining.") + playsound(user.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) + return + + if(cell.charge >= usecharge) + mobScan(M, user) + else + to_chat(user, "The scanner beeps angrily at you! It's out of charge!") + playsound(user.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) + +/obj/item/bodyanalyzer/borg/attack(mob/living/M, mob/living/silicon/robot/user) + if(user.incapacitated() || !user.Adjacent(M)) + return + + if(!ready) + to_chat(user, "[src] is currently recharging - [round((time_to_use - world.time) * 0.1)] seconds remaining.") + return + + if(user.cell.charge >= usecharge) + mobScan(M, user) + else + to_chat(user, "You need to recharge before you can use [src]") + +/obj/item/bodyanalyzer/proc/mobScan(mob/living/M, mob/user) + if(ishuman(M)) + var/report = generate_printing_text(M, user) + user.visible_message("[user] begins scanning [M] with [src].", "You begin scanning [M].") + if(do_after(user, scan_time, target = M)) + var/obj/item/paper/printout = new + printout.info = report + printout.name = "Scan report - [M.name]" + playsound(user.loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) + user.put_in_hands(printout) + time_to_use = world.time + scan_cd + if(isrobot(user)) + var/mob/living/silicon/robot/R = user + R.cell.use(usecharge) + else + cell.use(usecharge) + ready = FALSE + update_icon(TRUE) + addtimer(CALLBACK(src, /obj/item/bodyanalyzer/.proc/setReady), scan_cd) + addtimer(CALLBACK(src, /obj/item/bodyanalyzer/.proc/update_icon), 20) + + else if(iscorgi(M) && M.stat == DEAD) + to_chat(user, "You wonder if [M.p_they()] was a good dog. [src] tells you they were the best...") // :'( + playsound(loc, 'sound/machines/ping.ogg', 50, 0) + ready = FALSE + addtimer(CALLBACK(src, /obj/item/bodyanalyzer/.proc/setReady), scan_cd) + time_to_use = world.time + scan_cd + else + to_chat(user, "Scanning error detected. Invalid specimen.") + +//Unashamedly ripped from adv_med.dm +/obj/item/bodyanalyzer/proc/generate_printing_text(mob/living/M, mob/user) + var/dat = "" + var/mob/living/carbon/human/target = M + + dat = "Target Statistics:
        " + var/t1 + switch(target.stat) // obvious, see what their status is + if(CONSCIOUS) + t1 = "Conscious" + if(UNCONSCIOUS) + t1 = "Unconscious" + else + t1 = "*dead*" + dat += "[target.health > 50 ? "" : ""]\tHealth %: [target.health], ([t1])
        " + + var/found_disease = FALSE + for(var/thing in target.viruses) + var/datum/disease/D = thing + if(D.visibility_flags) //If any visibility flags are on. + continue + found_disease = TRUE + break + if(found_disease) + dat += "Disease detected in target.
        " + + var/extra_font = null + extra_font = (target.getBruteLoss() < 60 ? "" : "") + dat += "[extra_font]\t-Brute Damage %: [target.getBruteLoss()]
        " + + extra_font = (target.getOxyLoss() < 60 ? "" : "") + dat += "[extra_font]\t-Respiratory Damage %: [target.getOxyLoss()]
        " + + extra_font = (target.getToxLoss() < 60 ? "" : "") + dat += "[extra_font]\t-Toxin Content %: [target.getToxLoss()]
        " + + extra_font = (target.getFireLoss() < 60 ? "" : "") + dat += "[extra_font]\t-Burn Severity %: [target.getFireLoss()]
        " + + extra_font = (target.radiation < 10 ?"" : "") + dat += "[extra_font]\tRadiation Level %: [target.radiation]
        " + + extra_font = (target.getCloneLoss() < 1 ?"" : "") + dat += "[extra_font]\tGenetic Tissue Damage %: [target.getCloneLoss()]
        " + + extra_font = (target.getBrainLoss() < 1 ?"" : "") + dat += "[extra_font]\tApprox. Brain Damage %: [target.getBrainLoss()]
        " + + dat += "Paralysis Summary %: [target.paralysis] ([round(target.paralysis / 4)] seconds left!)
        " + dat += "Body Temperature: [target.bodytemperature-T0C]°C ([target.bodytemperature*1.8-459.67]°F)
        " + + dat += "
        " + + if(target.has_brain_worms()) + dat += "Large growth detected in frontal lobe, possibly cancerous. Surgical removal is recommended.
        " + + var/blood_percent = round((target.blood_volume / BLOOD_VOLUME_NORMAL)) + blood_percent *= 100 + + extra_font = (target.blood_volume > 448 ? "" : "") + dat += "[extra_font]\tBlood Level %: [blood_percent] ([target.blood_volume] units)
        " + + if(target.reagents) + dat += "Epinephrine units: [target.reagents.get_reagent_amount("Epinephrine")] units
        " + dat += "Ether: [target.reagents.get_reagent_amount("ether")] units
        " + + extra_font = (target.reagents.get_reagent_amount("silver_sulfadiazine") < 30 ? "" : "") + dat += "[extra_font]\tSilver Sulfadiazine: [target.reagents.get_reagent_amount("silver_sulfadiazine")]
        " + + extra_font = (target.reagents.get_reagent_amount("styptic_powder") < 30 ? "" : "") + dat += "[extra_font]\tStyptic Powder: [target.reagents.get_reagent_amount("styptic_powder")] units
        " + + extra_font = (target.reagents.get_reagent_amount("salbutamol") < 30 ? "" : "") + dat += "[extra_font]\tSalbutamol: [target.reagents.get_reagent_amount("salbutamol")] units
        " + + dat += "
        " + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + + for(var/obj/item/organ/external/e in target.bodyparts) + dat += "" + var/AN = "" + var/open = "" + var/infected = "" + var/robot = "" + var/imp = "" + var/bled = "" + var/splint = "" + var/internal_bleeding = "" + var/lung_ruptured = "" + if(e.internal_bleeding) + internal_bleeding = "
        Internal bleeding" + if(istype(e, /obj/item/organ/external/chest) && target.is_lung_ruptured()) + lung_ruptured = "Lung ruptured:" + if(e.status & ORGAN_SPLINTED) + splint = "Splinted:" + if(e.status & ORGAN_BROKEN) + AN = "[e.broken_description]:" + if(e.is_robotic()) + robot = "Robotic:" + if(e.open) + open = "Open:" + switch(e.germ_level) + if(INFECTION_LEVEL_ONE to INFECTION_LEVEL_ONE + 200) + infected = "Mild Infection:" + if(INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) + infected = "Mild Infection+:" + if(INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) + infected = "Mild Infection++:" + if(INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) + 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) + infected = "Acute Infection++:" + if(INFECTION_LEVEL_THREE to INFINITY) + infected = "Septic:" + + var/unknown_body = 0 + for(var/I in e.embedded_objects) + unknown_body++ + + if(unknown_body || e.hidden) + imp += "Unknown body present:" + if(!AN && !open && !infected & !imp) + AN = "None:" + dat += "" + dat += "" + for(var/obj/item/organ/internal/i in target.internal_organs) + var/mech = i.desc + var/infection = "None" + switch(i.germ_level) + if(1 to INFECTION_LEVEL_ONE + 200) + infection = "Mild Infection:" + if(INFECTION_LEVEL_ONE + 200 to INFECTION_LEVEL_ONE + 300) + infection = "Mild Infection+:" + if(INFECTION_LEVEL_ONE + 300 to INFECTION_LEVEL_ONE + 400) + infection = "Mild Infection++:" + if(INFECTION_LEVEL_TWO to INFECTION_LEVEL_TWO + 200) + infection = "Acute Infection:" + if(INFECTION_LEVEL_TWO + 200 to INFECTION_LEVEL_TWO + 300) + infection = "Acute Infection+:" + if(INFECTION_LEVEL_TWO + 300 to INFINITY) + infection = "Acute Infection++:" + + dat += "" + dat += "" + dat += "" + dat += "
        OrganBurn DamageBrute DamageOther Wounds
        [e.name][e.burn_dam][e.brute_dam][robot][bled][AN][splint][open][infected][imp][internal_bleeding][lung_ruptured]
        [i.name]N/A[i.damage][infection]:[mech]
        " + if(target.disabilities & BLIND) + dat += "Cataracts detected.
        " + if(target.disabilities & COLOURBLIND) + dat += "Photoreceptor abnormalities detected.
        " + if(target.disabilities & NEARSIGHTED) + dat += "Retinal misalignment detected.
        " + + return dat diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index 12f5212ffd9..7a8766ebb3c 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -1,324 +1,324 @@ -/obj/item/taperecorder - name = "universal recorder" - desc = "A device that can record to cassette tapes, and play them. It automatically translates the content in playback." - icon = 'icons/obj/device.dmi' - icon_state = "taperecorder_empty" - item_state = "analyzer" - w_class = WEIGHT_CLASS_SMALL - slot_flags = SLOT_BELT - materials = list(MAT_METAL=60, MAT_GLASS=30) - force = 2 - throwforce = 0 - var/recording = 0 - var/playing = 0 - var/playsleepseconds = 0 - var/obj/item/tape/mytape - var/open_panel = 0 - var/canprint = 1 - var/starts_with_tape = TRUE - - -/obj/item/taperecorder/New() - ..() - if(starts_with_tape) - mytape = new /obj/item/tape/random(src) - update_icon() - -/obj/item/taperecorder/Destroy() - QDEL_NULL(mytape) - return ..() - -/obj/item/taperecorder/examine(mob/user) - . = ..() - if(in_range(user, src)) - . += "The wire panel is [open_panel ? "opened" : "closed"]." - - -/obj/item/taperecorder/attackby(obj/item/I, mob/user) - if(!mytape && istype(I, /obj/item/tape)) - user.drop_item() - I.loc = src - mytape = I - to_chat(user, "You insert [I] into [src].") - update_icon() - -/obj/item/taperecorder/proc/eject(mob/user) - if(mytape) - to_chat(user, "You remove [mytape] from [src].") - stop() - user.put_in_hands(mytape) - mytape = null - update_icon() - - -/obj/item/taperecorder/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - mytape.ruin() //Fires destroy the tape - return ..() - -/obj/item/taperecorder/attack_hand(mob/user) - if(loc == user) - if(mytape) - if(user.l_hand != src && user.r_hand != src) - ..() - return - eject(user) - return - ..() - - -/obj/item/taperecorder/verb/ejectverb() - set name = "Eject Tape" - set category = "Object" - - if(usr.stat) - return - if(!mytape) - return - - eject(usr) - - -/obj/item/taperecorder/update_icon() - if(!mytape) - icon_state = "taperecorder_empty" - else if(recording) - icon_state = "taperecorder_recording" - else if(playing) - icon_state = "taperecorder_playing" - else - icon_state = "taperecorder_idle" - - -/obj/item/taperecorder/hear_talk(mob/living/M as mob, list/message_pieces) - var/msg = multilingual_to_message(message_pieces) - if(mytape && recording) - var/ending = copytext(msg, length(msg)) - mytape.timestamp += mytape.used_capacity - if(M.stuttering) - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] stammers, \"[msg]\"" - return - if(M.getBrainLoss() >= 60) - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] gibbers, \"[msg]\"" - return - if(ending == "?") - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] asks, \"[msg]\"" - return - else if(ending == "!") - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] exclaims, \"[msg]\"" - return - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] says, \"[msg]\"" - -/obj/item/taperecorder/hear_message(mob/living/M as mob, msg) - if(mytape && recording) - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] [msg]" - -/obj/item/taperecorder/verb/record() - set name = "Start Recording" - set category = "Object" - - if(usr.stat) - return - if(!mytape || mytape.ruined) - return - if(recording) - return - if(playing) - return - - if(mytape.used_capacity < mytape.max_capacity) - to_chat(usr, "Recording started.") - recording = 1 - update_icon() - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording started." - var/used = mytape.used_capacity //to stop runtimes when you eject the tape - var/max = mytape.max_capacity - for(used, used < max) - if(recording == 0) - break - mytape.used_capacity++ - used++ - sleep(10) - recording = 0 - update_icon() - else - to_chat(usr, "The tape is full.") - - -/obj/item/taperecorder/verb/stop() - set name = "Stop" - set category = "Object" - - if(usr.stat) - return - - if(recording) - recording = 0 - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording stopped." - to_chat(usr, "Recording stopped.") - return - else if(playing) - playing = 0 - atom_say("Playback stopped.") - update_icon() - - -/obj/item/taperecorder/verb/play() - set name = "Play Tape" - set category = "Object" - - if(usr.stat) - return - if(!mytape || mytape.ruined) - return - if(recording) - return - if(playing) - return - - playing = 1 - update_icon() - to_chat(usr, "Playing started.") - var/used = mytape.used_capacity //to stop runtimes when you eject the tape - var/max = mytape.max_capacity - for(var/i = 1, used < max, sleep(10 * playsleepseconds)) - if(!mytape) - break - if(playing == 0) - break - if(mytape.storedinfo.len < i) - break - atom_say("[mytape.storedinfo[i]]") - if(mytape.storedinfo.len < i + 1) - playsleepseconds = 1 - sleep(10) - T = get_turf(src) - atom_say("End of recording.") - else - playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] - if(playsleepseconds > 14) - sleep(10) - T = get_turf(src) - atom_say("Skipping [playsleepseconds] seconds of silence.") - playsleepseconds = 1 - i++ - - playing = 0 - update_icon() - - -/obj/item/taperecorder/attack_self(mob/user) - if(!mytape || mytape.ruined) - return - if(recording) - stop() - else - record() - - -/obj/item/taperecorder/verb/print_transcript() - set name = "Print Transcript" - set category = "Object" - - if(usr.stat) - return - if(!mytape) - return - if(!canprint) - to_chat(usr, "The recorder can't print that fast!") - return - if(recording || playing) - return - - to_chat(usr, "Transcript printed.") - playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1) - var/obj/item/paper/P = new /obj/item/paper(get_turf(src)) - var/t1 = "Transcript:

        " - for(var/i = 1, mytape.storedinfo.len >= i, i++) - t1 += "[mytape.storedinfo[i]]
        " - P.info = t1 - P.name = "paper- 'Transcript'" - usr.put_in_hands(P) - canprint = 0 - sleep(300) - canprint = 1 - -//empty tape recorders -/obj/item/taperecorder/empty - starts_with_tape = FALSE - - -/obj/item/tape - name = "tape" - desc = "A magnetic tape that can hold up to ten minutes of content." - icon = 'icons/obj/device.dmi' - icon_state = "tape_white" - item_state = "analyzer" - w_class = WEIGHT_CLASS_TINY - materials = list(MAT_METAL=20, MAT_GLASS=5) - force = 1 - throwforce = 0 - var/max_capacity = 600 - var/used_capacity = 0 - var/list/storedinfo = list() - var/list/timestamp = list() - var/ruined = 0 - -/obj/item/tape/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - ..() - ruin() - -/obj/item/tape/attack_self(mob/user) - if(!ruined) - to_chat(user, "You pull out all the tape!") - ruin() - -/obj/item/tape/verb/wipe() - set name = "Wipe Tape" - set category = "Object" - - if(usr.stat) - return - if(ruined) - return - - to_chat(usr, "You erase the data from the [src]") - clear() - -/obj/item/tape/proc/clear() - used_capacity = 0 - storedinfo.Cut() - timestamp.Cut() - -/obj/item/tape/proc/ruin() - if(!ruined) - overlays += "ribbonoverlay" - ruined = 1 - - - -/obj/item/tape/proc/fix() - overlays -= "ribbonoverlay" - ruined = 0 - - -/obj/item/tape/attackby(obj/item/I, mob/user) - if(ruined && istype(I, /obj/item/screwdriver)) - to_chat(user, "You start winding the tape back in.") - if(do_after(user, 120 * I.toolspeed, target = src)) - to_chat(user, "You wound the tape back in!") - fix() - else if(istype(I, /obj/item/pen)) - var/title = stripped_input(usr,"What do you want to name the tape?", "Tape Renaming", name, MAX_NAME_LEN) - if(!title || !length(title)) - name = initial(name) - return - name = "tape - [title]" - - -//Random colour tapes -/obj/item/tape/random/New() - ..() - icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" +/obj/item/taperecorder + name = "universal recorder" + desc = "A device that can record to cassette tapes, and play them. It automatically translates the content in playback." + icon = 'icons/obj/device.dmi' + icon_state = "taperecorder_empty" + item_state = "analyzer" + w_class = WEIGHT_CLASS_SMALL + slot_flags = SLOT_BELT + materials = list(MAT_METAL=60, MAT_GLASS=30) + force = 2 + throwforce = 0 + var/recording = 0 + var/playing = 0 + var/playsleepseconds = 0 + var/obj/item/tape/mytape + var/open_panel = 0 + var/canprint = 1 + var/starts_with_tape = TRUE + + +/obj/item/taperecorder/New() + ..() + if(starts_with_tape) + mytape = new /obj/item/tape/random(src) + update_icon() + +/obj/item/taperecorder/Destroy() + QDEL_NULL(mytape) + return ..() + +/obj/item/taperecorder/examine(mob/user) + . = ..() + if(in_range(user, src)) + . += "The wire panel is [open_panel ? "opened" : "closed"]." + + +/obj/item/taperecorder/attackby(obj/item/I, mob/user) + if(!mytape && istype(I, /obj/item/tape)) + user.drop_item() + I.loc = src + mytape = I + to_chat(user, "You insert [I] into [src].") + update_icon() + +/obj/item/taperecorder/proc/eject(mob/user) + if(mytape) + to_chat(user, "You remove [mytape] from [src].") + stop() + user.put_in_hands(mytape) + mytape = null + update_icon() + + +/obj/item/taperecorder/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + mytape.ruin() //Fires destroy the tape + return ..() + +/obj/item/taperecorder/attack_hand(mob/user) + if(loc == user) + if(mytape) + if(user.l_hand != src && user.r_hand != src) + ..() + return + eject(user) + return + ..() + + +/obj/item/taperecorder/verb/ejectverb() + set name = "Eject Tape" + set category = "Object" + + if(usr.stat) + return + if(!mytape) + return + + eject(usr) + + +/obj/item/taperecorder/update_icon() + if(!mytape) + icon_state = "taperecorder_empty" + else if(recording) + icon_state = "taperecorder_recording" + else if(playing) + icon_state = "taperecorder_playing" + else + icon_state = "taperecorder_idle" + + +/obj/item/taperecorder/hear_talk(mob/living/M as mob, list/message_pieces) + var/msg = multilingual_to_message(message_pieces) + if(mytape && recording) + var/ending = copytext(msg, length(msg)) + mytape.timestamp += mytape.used_capacity + if(M.stuttering) + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] stammers, \"[msg]\"" + return + if(M.getBrainLoss() >= 60) + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] gibbers, \"[msg]\"" + return + if(ending == "?") + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] asks, \"[msg]\"" + return + else if(ending == "!") + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] exclaims, \"[msg]\"" + return + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] says, \"[msg]\"" + +/obj/item/taperecorder/hear_message(mob/living/M as mob, msg) + if(mytape && recording) + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [M.name] [msg]" + +/obj/item/taperecorder/verb/record() + set name = "Start Recording" + set category = "Object" + + if(usr.stat) + return + if(!mytape || mytape.ruined) + return + if(recording) + return + if(playing) + return + + if(mytape.used_capacity < mytape.max_capacity) + to_chat(usr, "Recording started.") + recording = 1 + update_icon() + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording started." + var/used = mytape.used_capacity //to stop runtimes when you eject the tape + var/max = mytape.max_capacity + for(used, used < max) + if(recording == 0) + break + mytape.used_capacity++ + used++ + sleep(10) + recording = 0 + update_icon() + else + to_chat(usr, "The tape is full.") + + +/obj/item/taperecorder/verb/stop() + set name = "Stop" + set category = "Object" + + if(usr.stat) + return + + if(recording) + recording = 0 + mytape.timestamp += mytape.used_capacity + mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording stopped." + to_chat(usr, "Recording stopped.") + return + else if(playing) + playing = 0 + atom_say("Playback stopped.") + update_icon() + + +/obj/item/taperecorder/verb/play() + set name = "Play Tape" + set category = "Object" + + if(usr.stat) + return + if(!mytape || mytape.ruined) + return + if(recording) + return + if(playing) + return + + playing = 1 + update_icon() + to_chat(usr, "Playing started.") + var/used = mytape.used_capacity //to stop runtimes when you eject the tape + var/max = mytape.max_capacity + for(var/i = 1, used < max, sleep(10 * playsleepseconds)) + if(!mytape) + break + if(playing == 0) + break + if(mytape.storedinfo.len < i) + break + atom_say("[mytape.storedinfo[i]]") + if(mytape.storedinfo.len < i + 1) + playsleepseconds = 1 + sleep(10) + T = get_turf(src) + atom_say("End of recording.") + else + playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] + if(playsleepseconds > 14) + sleep(10) + T = get_turf(src) + atom_say("Skipping [playsleepseconds] seconds of silence.") + playsleepseconds = 1 + i++ + + playing = 0 + update_icon() + + +/obj/item/taperecorder/attack_self(mob/user) + if(!mytape || mytape.ruined) + return + if(recording) + stop() + else + record() + + +/obj/item/taperecorder/verb/print_transcript() + set name = "Print Transcript" + set category = "Object" + + if(usr.stat) + return + if(!mytape) + return + if(!canprint) + to_chat(usr, "The recorder can't print that fast!") + return + if(recording || playing) + return + + to_chat(usr, "Transcript printed.") + playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1) + var/obj/item/paper/P = new /obj/item/paper(get_turf(src)) + var/t1 = "Transcript:

        " + for(var/i = 1, mytape.storedinfo.len >= i, i++) + t1 += "[mytape.storedinfo[i]]
        " + P.info = t1 + P.name = "paper- 'Transcript'" + usr.put_in_hands(P) + canprint = 0 + sleep(300) + canprint = 1 + +//empty tape recorders +/obj/item/taperecorder/empty + starts_with_tape = FALSE + + +/obj/item/tape + name = "tape" + desc = "A magnetic tape that can hold up to ten minutes of content." + icon = 'icons/obj/device.dmi' + icon_state = "tape_white" + item_state = "analyzer" + w_class = WEIGHT_CLASS_TINY + materials = list(MAT_METAL=20, MAT_GLASS=5) + force = 1 + throwforce = 0 + var/max_capacity = 600 + var/used_capacity = 0 + var/list/storedinfo = list() + var/list/timestamp = list() + var/ruined = 0 + +/obj/item/tape/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + ..() + ruin() + +/obj/item/tape/attack_self(mob/user) + if(!ruined) + to_chat(user, "You pull out all the tape!") + ruin() + +/obj/item/tape/verb/wipe() + set name = "Wipe Tape" + set category = "Object" + + if(usr.stat) + return + if(ruined) + return + + to_chat(usr, "You erase the data from the [src]") + clear() + +/obj/item/tape/proc/clear() + used_capacity = 0 + storedinfo.Cut() + timestamp.Cut() + +/obj/item/tape/proc/ruin() + if(!ruined) + overlays += "ribbonoverlay" + ruined = 1 + + + +/obj/item/tape/proc/fix() + overlays -= "ribbonoverlay" + ruined = 0 + + +/obj/item/tape/attackby(obj/item/I, mob/user) + if(ruined && istype(I, /obj/item/screwdriver)) + to_chat(user, "You start winding the tape back in.") + if(do_after(user, 120 * I.toolspeed, target = src)) + to_chat(user, "You wound the tape back in!") + fix() + else if(istype(I, /obj/item/pen)) + var/title = stripped_input(usr,"What do you want to name the tape?", "Tape Renaming", name, MAX_NAME_LEN) + if(!title || !length(title)) + name = initial(name) + return + name = "tape - [title]" + + +//Random colour tapes +/obj/item/tape/random/New() + ..() + icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" diff --git a/code/game/objects/items/devices/thermal_drill.dm b/code/game/objects/items/devices/thermal_drill.dm index c35a75ef055..55dac21f348 100644 --- a/code/game/objects/items/devices/thermal_drill.dm +++ b/code/game/objects/items/devices/thermal_drill.dm @@ -25,4 +25,4 @@ name = "diamond tipped thermal safe drill" desc = "A diamond tipped thermal drill with magnetic clamps for the purpose of quickly drilling hardened objects. Guaranteed 100% jam proof." icon_state = "diamond_drill" - time_multiplier = 0.5 \ No newline at end of file + time_multiplier = 0.5 diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index dfef93f2c32..1ffa9cceb88 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -167,4 +167,4 @@ effective or pretty fucking useless. if(active) GLOB.active_jammers |= src else - GLOB.active_jammers -= src \ No newline at end of file + GLOB.active_jammers -= src diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index 371c6248a6e..40eea3a9856 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -1,220 +1,220 @@ -/obj/item/transfer_valve - icon = 'icons/obj/assemblies.dmi' - name = "tank transfer valve" - icon_state = "valve_1" - item_state = "ttv" - desc = "Regulates the transfer of air between two tanks" - var/obj/item/tank/tank_one = null - var/obj/item/tank/tank_two = null - var/obj/item/assembly/attached_device = null - var/mob/living/attacher = null - var/valve_open = 0 - var/toggle = 1 - origin_tech = "materials=1;engineering=1" - -/obj/item/transfer_valve/Destroy() - QDEL_NULL(tank_one) - QDEL_NULL(tank_two) - QDEL_NULL(attached_device) - attacher = null - return ..() - -/obj/item/transfer_valve/IsAssemblyHolder() - return 1 - -/obj/item/transfer_valve/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/tank)) - if(tank_one && tank_two) - to_chat(user, "There are already two tanks attached, remove one first.") - return - - if(!tank_one) - if(!user.unEquip(I)) - return - tank_one = I - I.forceMove(src) - to_chat(user, "You attach the tank to the transfer valve.") - if(I.w_class > w_class) - w_class = I.w_class - else if(!tank_two) - if(!user.unEquip(I)) - return - tank_two = I - I.forceMove(src) - to_chat(user, "You attach the tank to the transfer valve.") - if(I.w_class > w_class) - w_class = I.w_class - - update_icon() - SSnanoui.update_uis(src) // update all UIs attached to src -//TODO: Have this take an assemblyholder - else if(isassembly(I)) - var/obj/item/assembly/A = I - if(A.secured) - to_chat(user, "The device is secured.") - return - if(attached_device) - to_chat(user, "There is already a device attached to the valve, remove it first.") - return - user.remove_from_mob(A) - attached_device = A - A.forceMove(src) - to_chat(user, "You attach the [A] to the valve controls and secure it.") - A.holder = src - A.toggle_secure() //this calls update_icon(), which calls update_icon() on the holder (i.e. the bomb). - - investigate_log("[key_name(user)] attached a [A] to a transfer valve.", INVESTIGATE_BOMB) - msg_admin_attack("[key_name_admin(user)]attached [A] to a transfer valve.", ATKLOG_FEW) - log_game("[key_name_admin(user)] attached [A] to a transfer valve.") - attacher = user - SSnanoui.update_uis(src) // update all UIs attached to src - - -/obj/item/transfer_valve/HasProximity(atom/movable/AM) - if(!attached_device) - return - attached_device.HasProximity(AM) - -/obj/item/transfer_valve/hear_talk(mob/living/M, list/message_pieces) - ..() - for(var/obj/O in contents) - O.hear_talk(M, message_pieces) - -/obj/item/transfer_valve/hear_message(mob/living/M, msg) - ..() - for(var/obj/O in contents) - O.hear_message(M, msg) - -/obj/item/transfer_valve/attack_self(mob/user) - ui_interact(user) - -/obj/item/transfer_valve/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "transfer_valve.tmpl", "Tank Transfer Valve", 460, 280) - // open the new ui window - ui.open() - // auto update every Master Controller tick - //ui.set_auto_update(1) - -/obj/item/transfer_valve/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - data["attachmentOne"] = tank_one ? tank_one.name : null - data["attachmentTwo"] = tank_two ? tank_two.name : null - data["valveAttachment"] = attached_device ? attached_device.name : null - data["valveOpen"] = valve_open ? 1 : 0 - - return data - -/obj/item/transfer_valve/Topic(href, href_list) - ..() - if(usr.incapacitated()) - return 0 - if(loc != usr) - return 0 - if(tank_one && href_list["tankone"]) - split_gases() - valve_open = 0 - tank_one.forceMove(get_turf(src)) - tank_one = null - update_icon() - if((!tank_two || tank_two.w_class < WEIGHT_CLASS_BULKY) && (w_class > WEIGHT_CLASS_NORMAL)) - w_class = WEIGHT_CLASS_NORMAL - else if(tank_two && href_list["tanktwo"]) - split_gases() - valve_open = 0 - tank_two.forceMove(get_turf(src)) - tank_two = null - update_icon() - if((!tank_one || tank_one.w_class < WEIGHT_CLASS_BULKY) && (w_class > WEIGHT_CLASS_NORMAL)) - w_class = WEIGHT_CLASS_NORMAL - else if(href_list["open"]) - toggle_valve() - else if(attached_device) - if(href_list["rem_device"]) - attached_device.forceMove(get_turf(src)) - attached_device.holder = null - attached_device = null - update_icon() - if(href_list["device"]) - attached_device.attack_self(usr) - add_fingerprint(usr) - return 1 // Returning 1 sends an update to attached UIs - -/obj/item/transfer_valve/proc/process_activation(obj/item/D) - if(toggle) - toggle = 0 - toggle_valve() - spawn(50) // To stop a signal being spammed from a proxy sensor constantly going off or whatever - toggle = 1 - -/obj/item/transfer_valve/update_icon() - overlays.Cut() - underlays = null - - if(!tank_one && !tank_two && !attached_device) - icon_state = "valve_1" - return - icon_state = "valve" - - if(tank_one) - overlays += "[tank_one.icon_state]" - if(tank_two) - var/icon/J = new(icon, icon_state = "[tank_two.icon_state]") - J.Shift(WEST, 13) - underlays += J - if(attached_device) - overlays += "device" - -/obj/item/transfer_valve/proc/merge_gases() - tank_two.air_contents.volume += tank_one.air_contents.volume - var/datum/gas_mixture/temp - temp = tank_one.air_contents.remove_ratio(1) - tank_two.air_contents.merge(temp) - -/obj/item/transfer_valve/proc/split_gases() - if(!valve_open || !tank_one || !tank_two) - return - var/ratio1 = tank_one.air_contents.volume/tank_two.air_contents.volume - var/datum/gas_mixture/temp - temp = tank_two.air_contents.remove_ratio(ratio1) - tank_one.air_contents.merge(temp) - tank_two.air_contents.volume -= tank_one.air_contents.volume - - /* - Exadv1: I know this isn't how it's going to work, but this was just to check - it explodes properly when it gets a signal (and it does). - */ - -/obj/item/transfer_valve/proc/toggle_valve() - if(!valve_open && tank_one && tank_two) - valve_open = 1 - var/turf/bombturf = get_turf(src) - var/area/A = get_area(bombturf) - - var/attacher_name = "" - if(!attacher) - attacher_name = "Unknown" - else - attacher_name = "[key_name_admin(attacher)]" - - var/mob/mob = get_mob_by_key(src.fingerprintslast) - - investigate_log("Bomb valve opened at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) with [attached_device ? attached_device : "no device"], attached by [attacher_name]. Last touched by: [key_name(mob)]", INVESTIGATE_BOMB) - message_admins("Bomb valve opened at [A.name] (JMP) with [attached_device ? attached_device : "no device"], attached by [attacher_name]. Last touched by: [key_name_admin(mob)]") - log_game("Bomb valve opened at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) with [attached_device ? attached_device : "no device"], attached by [attacher_name]. Last touched by: [key_name(mob)]") - merge_gases() - spawn(20) // In case one tank bursts - for(var/i in 1 to 5) - update_icon() - sleep(10) - update_icon() - - else if(valve_open && tank_one && tank_two) - split_gases() - valve_open = 0 - update_icon() +/obj/item/transfer_valve + icon = 'icons/obj/assemblies.dmi' + name = "tank transfer valve" + icon_state = "valve_1" + item_state = "ttv" + desc = "Regulates the transfer of air between two tanks" + var/obj/item/tank/tank_one = null + var/obj/item/tank/tank_two = null + var/obj/item/assembly/attached_device = null + var/mob/living/attacher = null + var/valve_open = 0 + var/toggle = 1 + origin_tech = "materials=1;engineering=1" + +/obj/item/transfer_valve/Destroy() + QDEL_NULL(tank_one) + QDEL_NULL(tank_two) + QDEL_NULL(attached_device) + attacher = null + return ..() + +/obj/item/transfer_valve/IsAssemblyHolder() + return 1 + +/obj/item/transfer_valve/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/tank)) + if(tank_one && tank_two) + to_chat(user, "There are already two tanks attached, remove one first.") + return + + if(!tank_one) + if(!user.unEquip(I)) + return + tank_one = I + I.forceMove(src) + to_chat(user, "You attach the tank to the transfer valve.") + if(I.w_class > w_class) + w_class = I.w_class + else if(!tank_two) + if(!user.unEquip(I)) + return + tank_two = I + I.forceMove(src) + to_chat(user, "You attach the tank to the transfer valve.") + if(I.w_class > w_class) + w_class = I.w_class + + update_icon() + SSnanoui.update_uis(src) // update all UIs attached to src +//TODO: Have this take an assemblyholder + else if(isassembly(I)) + var/obj/item/assembly/A = I + if(A.secured) + to_chat(user, "The device is secured.") + return + if(attached_device) + to_chat(user, "There is already a device attached to the valve, remove it first.") + return + user.remove_from_mob(A) + attached_device = A + A.forceMove(src) + to_chat(user, "You attach the [A] to the valve controls and secure it.") + A.holder = src + A.toggle_secure() //this calls update_icon(), which calls update_icon() on the holder (i.e. the bomb). + + investigate_log("[key_name(user)] attached a [A] to a transfer valve.", INVESTIGATE_BOMB) + msg_admin_attack("[key_name_admin(user)]attached [A] to a transfer valve.", ATKLOG_FEW) + log_game("[key_name_admin(user)] attached [A] to a transfer valve.") + attacher = user + SSnanoui.update_uis(src) // update all UIs attached to src + + +/obj/item/transfer_valve/HasProximity(atom/movable/AM) + if(!attached_device) + return + attached_device.HasProximity(AM) + +/obj/item/transfer_valve/hear_talk(mob/living/M, list/message_pieces) + ..() + for(var/obj/O in contents) + O.hear_talk(M, message_pieces) + +/obj/item/transfer_valve/hear_message(mob/living/M, msg) + ..() + for(var/obj/O in contents) + O.hear_message(M, msg) + +/obj/item/transfer_valve/attack_self(mob/user) + ui_interact(user) + +/obj/item/transfer_valve/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "transfer_valve.tmpl", "Tank Transfer Valve", 460, 280) + // open the new ui window + ui.open() + // auto update every Master Controller tick + //ui.set_auto_update(1) + +/obj/item/transfer_valve/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + data["attachmentOne"] = tank_one ? tank_one.name : null + data["attachmentTwo"] = tank_two ? tank_two.name : null + data["valveAttachment"] = attached_device ? attached_device.name : null + data["valveOpen"] = valve_open ? 1 : 0 + + return data + +/obj/item/transfer_valve/Topic(href, href_list) + ..() + if(usr.incapacitated()) + return 0 + if(loc != usr) + return 0 + if(tank_one && href_list["tankone"]) + split_gases() + valve_open = 0 + tank_one.forceMove(get_turf(src)) + tank_one = null + update_icon() + if((!tank_two || tank_two.w_class < WEIGHT_CLASS_BULKY) && (w_class > WEIGHT_CLASS_NORMAL)) + w_class = WEIGHT_CLASS_NORMAL + else if(tank_two && href_list["tanktwo"]) + split_gases() + valve_open = 0 + tank_two.forceMove(get_turf(src)) + tank_two = null + update_icon() + if((!tank_one || tank_one.w_class < WEIGHT_CLASS_BULKY) && (w_class > WEIGHT_CLASS_NORMAL)) + w_class = WEIGHT_CLASS_NORMAL + else if(href_list["open"]) + toggle_valve() + else if(attached_device) + if(href_list["rem_device"]) + attached_device.forceMove(get_turf(src)) + attached_device.holder = null + attached_device = null + update_icon() + if(href_list["device"]) + attached_device.attack_self(usr) + add_fingerprint(usr) + return 1 // Returning 1 sends an update to attached UIs + +/obj/item/transfer_valve/proc/process_activation(obj/item/D) + if(toggle) + toggle = 0 + toggle_valve() + spawn(50) // To stop a signal being spammed from a proxy sensor constantly going off or whatever + toggle = 1 + +/obj/item/transfer_valve/update_icon() + overlays.Cut() + underlays = null + + if(!tank_one && !tank_two && !attached_device) + icon_state = "valve_1" + return + icon_state = "valve" + + if(tank_one) + overlays += "[tank_one.icon_state]" + if(tank_two) + var/icon/J = new(icon, icon_state = "[tank_two.icon_state]") + J.Shift(WEST, 13) + underlays += J + if(attached_device) + overlays += "device" + +/obj/item/transfer_valve/proc/merge_gases() + tank_two.air_contents.volume += tank_one.air_contents.volume + var/datum/gas_mixture/temp + temp = tank_one.air_contents.remove_ratio(1) + tank_two.air_contents.merge(temp) + +/obj/item/transfer_valve/proc/split_gases() + if(!valve_open || !tank_one || !tank_two) + return + var/ratio1 = tank_one.air_contents.volume/tank_two.air_contents.volume + var/datum/gas_mixture/temp + temp = tank_two.air_contents.remove_ratio(ratio1) + tank_one.air_contents.merge(temp) + tank_two.air_contents.volume -= tank_one.air_contents.volume + + /* + Exadv1: I know this isn't how it's going to work, but this was just to check + it explodes properly when it gets a signal (and it does). + */ + +/obj/item/transfer_valve/proc/toggle_valve() + if(!valve_open && tank_one && tank_two) + valve_open = 1 + var/turf/bombturf = get_turf(src) + var/area/A = get_area(bombturf) + + var/attacher_name = "" + if(!attacher) + attacher_name = "Unknown" + else + attacher_name = "[key_name_admin(attacher)]" + + var/mob/mob = get_mob_by_key(src.fingerprintslast) + + investigate_log("Bomb valve opened at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) with [attached_device ? attached_device : "no device"], attached by [attacher_name]. Last touched by: [key_name(mob)]", INVESTIGATE_BOMB) + message_admins("Bomb valve opened at [A.name] (JMP) with [attached_device ? attached_device : "no device"], attached by [attacher_name]. Last touched by: [key_name_admin(mob)]") + log_game("Bomb valve opened at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) with [attached_device ? attached_device : "no device"], attached by [attacher_name]. Last touched by: [key_name(mob)]") + merge_gases() + spawn(20) // In case one tank bursts + for(var/i in 1 to 5) + update_icon() + sleep(10) + update_icon() + + else if(valve_open && tank_one && tank_two) + split_gases() + valve_open = 0 + update_icon() diff --git a/code/game/objects/items/devices/uplinks.dm b/code/game/objects/items/devices/uplinks.dm index d814b242b29..ab4387a988e 100644 --- a/code/game/objects/items/devices/uplinks.dm +++ b/code/game/objects/items/devices/uplinks.dm @@ -353,4 +353,4 @@ var/list/world_uplinks = list() /obj/item/radio/headset/uplink/New() ..() hidden_uplink = new(src) - hidden_uplink.uses = 20 \ No newline at end of file + hidden_uplink.uses = 20 diff --git a/code/game/objects/items/devices/voice.dm b/code/game/objects/items/devices/voice.dm index 38bcd16f01e..43a95ae86d7 100644 --- a/code/game/objects/items/devices/voice.dm +++ b/code/game/objects/items/devices/voice.dm @@ -41,4 +41,4 @@ return voice = sanitize(copytext(chosen_voice, 1, MAX_MESSAGE_LEN)) - to_chat(user, "You are now mimicking [voice].") \ No newline at end of file + to_chat(user, "You are now mimicking [voice].") diff --git a/code/game/objects/items/documents.dm b/code/game/objects/items/documents.dm index 2bf519b4a22..e19561444e3 100644 --- a/code/game/objects/items/documents.dm +++ b/code/game/objects/items/documents.dm @@ -52,4 +52,4 @@ H.reagents.add_reagent(poison_type, poison_dose) poison_total -= poison_dose add_attack_logs(src, user, "Picked up [src], the trapped syndicate documents") - return ..() \ No newline at end of file + return ..() diff --git a/code/game/objects/items/flag.dm b/code/game/objects/items/flag.dm index aed33fc2819..0edcafd64b5 100644 --- a/code/game/objects/items/flag.dm +++ b/code/game/objects/items/flag.dm @@ -275,4 +275,4 @@ /obj/item/flag/chameleon/depot/New() ..() - boobytrap = new /obj/item/grenade/gas/plasma(src) \ No newline at end of file + boobytrap = new /obj/item/grenade/gas/plasma(src) diff --git a/code/game/objects/items/latexballoon.dm b/code/game/objects/items/latexballoon.dm index 9be42813e09..1aded3ac4bc 100644 --- a/code/game/objects/items/latexballoon.dm +++ b/code/game/objects/items/latexballoon.dm @@ -1,65 +1,65 @@ -/obj/item/latexballon - name = "latex glove" - desc = "" //todo - icon_state = "latexballon" - item_state = "lgloves" - force = 0 - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 1 - throw_range = 7 - var/state - var/datum/gas_mixture/air_contents = null - -/obj/item/latexballon/Destroy() - QDEL_NULL(air_contents) - return ..() - -/obj/item/latexballon/proc/blow(obj/item/tank/tank, mob/user) - if(icon_state == "latexballon_bursted") - return - icon_state = "latexballon_blow" - item_state = "latexballon" - user.update_inv_r_hand() - user.update_inv_l_hand() - to_chat(user, "You blow up [src] with [tank].") - air_contents = tank.remove_air_volume(3) - -/obj/item/latexballon/proc/burst() - if(!air_contents || icon_state != "latexballon_blow") - return - playsound(src, 'sound/weapons/gunshots/gunshot.ogg', 100, 1) - icon_state = "latexballon_bursted" - item_state = "lgloves" - if(isliving(loc)) - var/mob/living/user = loc - user.update_inv_r_hand() - user.update_inv_l_hand() - loc.assume_air(air_contents) - -/obj/item/latexballon/ex_act(severity) - burst() - switch(severity) - if (1) - qdel(src) - if (2) - if (prob(50)) - qdel(src) - -/obj/item/latexballon/bullet_act(obj/item/projectile/P) - if(!P.nodamage) - burst() - return ..() - -/obj/item/latexballon/temperature_expose(datum/gas_mixture/air, temperature, volume) - ..() - if(temperature > T0C+100) - burst() - -/obj/item/latexballon/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - var/obj/item/tank/T = W - blow(T, user) - return - if(is_sharp(W) || is_hot(W) || is_pointed(W)) - burst() +/obj/item/latexballon + name = "latex glove" + desc = "" //todo + icon_state = "latexballon" + item_state = "lgloves" + force = 0 + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 1 + throw_range = 7 + var/state + var/datum/gas_mixture/air_contents = null + +/obj/item/latexballon/Destroy() + QDEL_NULL(air_contents) + return ..() + +/obj/item/latexballon/proc/blow(obj/item/tank/tank, mob/user) + if(icon_state == "latexballon_bursted") + return + icon_state = "latexballon_blow" + item_state = "latexballon" + user.update_inv_r_hand() + user.update_inv_l_hand() + to_chat(user, "You blow up [src] with [tank].") + air_contents = tank.remove_air_volume(3) + +/obj/item/latexballon/proc/burst() + if(!air_contents || icon_state != "latexballon_blow") + return + playsound(src, 'sound/weapons/gunshots/gunshot.ogg', 100, 1) + icon_state = "latexballon_bursted" + item_state = "lgloves" + if(isliving(loc)) + var/mob/living/user = loc + user.update_inv_r_hand() + user.update_inv_l_hand() + loc.assume_air(air_contents) + +/obj/item/latexballon/ex_act(severity) + burst() + switch(severity) + if (1) + qdel(src) + if (2) + if (prob(50)) + qdel(src) + +/obj/item/latexballon/bullet_act(obj/item/projectile/P) + if(!P.nodamage) + burst() + return ..() + +/obj/item/latexballon/temperature_expose(datum/gas_mixture/air, temperature, volume) + ..() + if(temperature > T0C+100) + burst() + +/obj/item/latexballon/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + var/obj/item/tank/T = W + blow(T, user) + return + if(is_sharp(W) || is_hot(W) || is_pointed(W)) + burst() diff --git a/code/game/objects/items/mixing_bowl.dm b/code/game/objects/items/mixing_bowl.dm index 1edfdac9561..da851437fd5 100644 --- a/code/game/objects/items/mixing_bowl.dm +++ b/code/game/objects/items/mixing_bowl.dm @@ -167,4 +167,4 @@ var/obj/item/reagent_containers/food/snacks/badrecipe/ffuu = new(get_turf(source)) ffuu.reagents.add_reagent("carbon", amount) ffuu.reagents.add_reagent("????", amount/10) - make_dirty(75) \ No newline at end of file + make_dirty(75) diff --git a/code/game/objects/items/mountable_frames/air_alarm.dm b/code/game/objects/items/mountable_frames/air_alarm.dm index 4b16db0b0f6..432b4169355 100644 --- a/code/game/objects/items/mountable_frames/air_alarm.dm +++ b/code/game/objects/items/mountable_frames/air_alarm.dm @@ -13,4 +13,4 @@ Code shamelessly copied from apc_frame /obj/item/mounted/frame/alarm_frame/do_build(turf/on_wall, mob/user) new /obj/machinery/alarm(get_turf(src), get_dir(on_wall, user), 1) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/mountable_frames/apc_frame.dm b/code/game/objects/items/mountable_frames/apc_frame.dm index f6707775fb2..548b77b85fe 100644 --- a/code/game/objects/items/mountable_frames/apc_frame.dm +++ b/code/game/objects/items/mountable_frames/apc_frame.dm @@ -29,4 +29,4 @@ /obj/item/mounted/frame/apc_frame/do_build(turf/on_wall, mob/user) new /obj/machinery/power/apc(get_turf(src), get_dir(user, on_wall), 1) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/mountable_frames/buttons_switches.dm b/code/game/objects/items/mountable_frames/buttons_switches.dm index b1e3fabbb86..76e4f0ecfd4 100644 --- a/code/game/objects/items/mountable_frames/buttons_switches.dm +++ b/code/game/objects/items/mountable_frames/buttons_switches.dm @@ -20,4 +20,4 @@ /obj/item/mounted/frame/light_switch/do_build(turf/on_wall, mob/user) new /obj/machinery/light_switch(get_turf(user), get_dir(user, on_wall)) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/mountable_frames/fire_alarm.dm b/code/game/objects/items/mountable_frames/fire_alarm.dm index ceec9b7c716..83f435a277c 100644 --- a/code/game/objects/items/mountable_frames/fire_alarm.dm +++ b/code/game/objects/items/mountable_frames/fire_alarm.dm @@ -7,4 +7,4 @@ /obj/item/mounted/frame/firealarm/do_build(turf/on_wall, mob/user) new /obj/machinery/firealarm(get_turf(src), get_dir(on_wall, user), 1) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/mountable_frames/intercom.dm b/code/game/objects/items/mountable_frames/intercom.dm index 18e25ebcbfb..95f21b5c2c9 100644 --- a/code/game/objects/items/mountable_frames/intercom.dm +++ b/code/game/objects/items/mountable_frames/intercom.dm @@ -7,4 +7,4 @@ /obj/item/mounted/frame/intercom/do_build(turf/on_wall, mob/user) new /obj/item/radio/intercom(get_turf(src), get_dir(user, on_wall), 0) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/mountable_frames/lights.dm b/code/game/objects/items/mountable_frames/lights.dm index b108240b332..d7954fd2704 100644 --- a/code/game/objects/items/mountable_frames/lights.dm +++ b/code/game/objects/items/mountable_frames/lights.dm @@ -36,4 +36,4 @@ icon = 'icons/obj/lighting.dmi' icon_state = "bulb-construct-item" fixture_type = "bulb" - sheets_refunded = 1 \ No newline at end of file + sheets_refunded = 1 diff --git a/code/game/objects/items/mountable_frames/newscaster_frame.dm b/code/game/objects/items/mountable_frames/newscaster_frame.dm index 8ed81d937d5..760e5c6147b 100644 --- a/code/game/objects/items/mountable_frames/newscaster_frame.dm +++ b/code/game/objects/items/mountable_frames/newscaster_frame.dm @@ -28,4 +28,4 @@ var/obj/machinery/newscaster/N = new /obj/machinery/newscaster(get_turf(src), get_dir(on_wall, user), 1) N.pixel_y -= (loc.y - on_wall.y) * 32 N.pixel_x -= (loc.x - on_wall.x) * 32 - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/random_items.dm b/code/game/objects/items/random_items.dm index 78c66a1ee36..f90f40dee57 100644 --- a/code/game/objects/items/random_items.dm +++ b/code/game/objects/items/random_items.dm @@ -321,4 +321,4 @@ ..() for(var/i in 1 to 6) var/nade = pick(grenadelist) - new nade(src) \ No newline at end of file + new nade(src) diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm index 1b20716783a..eae59151a38 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -1,37 +1,37 @@ -/********************************************************************** - Cyborg Spec Items -***********************************************************************/ -/obj/item/borg - icon = 'icons/mob/robot_items.dmi' - -/obj/item/borg/stun - name = "electrically-charged arm" - icon_state = "elecarm" - var/charge_cost = 30 - -/obj/item/borg/stun/attack(mob/living/M, mob/living/silicon/robot/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) - playsound(M, 'sound/weapons/genhit.ogg', 50, 1) - return 0 - - if(isrobot(user)) - if(!user.cell.use(charge_cost)) - return - - user.do_attack_animation(M) - M.Weaken(5) - M.apply_effect(STUTTER, 5) - M.Stun(5) - - M.visible_message("[user] has prodded [M] with [src]!", \ - "[user] has prodded you with [src]!") - - playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) - add_attack_logs(user, M, "Stunned with [src] (INTENT: [uppertext(user.a_intent)])") - -/obj/item/borg/overdrive - name = "Overdrive" - icon = 'icons/obj/decals.dmi' - icon_state = "shock" +/********************************************************************** + Cyborg Spec Items +***********************************************************************/ +/obj/item/borg + icon = 'icons/mob/robot_items.dmi' + +/obj/item/borg/stun + name = "electrically-charged arm" + icon_state = "elecarm" + var/charge_cost = 30 + +/obj/item/borg/stun/attack(mob/living/M, mob/living/silicon/robot/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) + playsound(M, 'sound/weapons/genhit.ogg', 50, 1) + return 0 + + if(isrobot(user)) + if(!user.cell.use(charge_cost)) + return + + user.do_attack_animation(M) + M.Weaken(5) + M.apply_effect(STUTTER, 5) + M.Stun(5) + + M.visible_message("[user] has prodded [M] with [src]!", \ + "[user] has prodded you with [src]!") + + playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) + add_attack_logs(user, M, "Stunned with [src] (INTENT: [uppertext(user.a_intent)])") + +/obj/item/borg/overdrive + name = "Overdrive" + icon = 'icons/obj/decals.dmi' + icon_state = "shock" diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 959b308ee05..8b85d8821d9 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -1,177 +1,177 @@ -// Targets, the things that actually get shot! -/obj/item/target - name = "shooting target" - desc = "A shooting target." - icon = 'icons/obj/objects.dmi' - icon_state = "target_h" - density = 0 - var/hp = 1800 - var/icon/virtualIcon - var/list/bulletholes = list() - -/obj/item/target/Destroy() - // if a target is deleted and associated with a stake, force stake to forget - for(var/obj/structure/target_stake/T in view(3,src)) - if(T.pinned_target == src) - T.pinned_target = null - T.density = 1 - break - return ..() // delete target - -/obj/item/target/Move() - ..() - // After target moves, check for nearby stakes. If associated, move to target - for(var/obj/structure/target_stake/M in view(3,src)) - if(M.density == 0 && M.pinned_target == src) - M.loc = loc - - // This may seem a little counter-intuitive but I assure you that's for a purpose. - // Stakes are the ones that carry targets, yes, but in the stake code we set - // a stake's density to 0 meaning it can't be pushed anymore. Instead of pushing - // the stake now, we have to push the target. - - -/obj/item/target/welder_act(mob/user, obj/item/I) - . = TRUE - if(!use_tool(src, user, 0,, volume = I.tool_volume)) - return - overlays.Cut() - to_chat(usr, "You slice off [src]'s uneven chunks of aluminum and scorch marks.") - -/obj/item/target/attack_hand(mob/user as mob) - // taking pinned targets off! - var/obj/structure/target_stake/stake - for(var/obj/structure/target_stake/T in view(3,src)) - if(T.pinned_target == src) - stake = T - break - - if(stake) - if(stake.pinned_target) - stake.density = 1 - density = 0 - layer = OBJ_LAYER - - loc = user.loc - if(ishuman(user)) - if(!user.get_active_hand()) - user.put_in_hands(src) - to_chat(user, "You take the target out of the stake.") - else - src.loc = get_turf(user) - to_chat(user, "You take the target out of the stake.") - - stake.pinned_target = null - return - - else - ..() - -/obj/item/target/syndicate - icon_state = "target_s" - desc = "A shooting target that looks like a syndicate scum." - hp = 2600 // i guess syndie targets are sturdier? - -/obj/item/target/alien - icon_state = "target_q" - desc = "A shooting target that looks like a xenomorphic alien." - hp = 2350 // alium onest too kinda - -/obj/item/target/bullet_act(var/obj/item/projectile/Proj) - var/p_x = Proj.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset Proj.p_x!" - var/p_y = Proj.p_y + pick(0,0,0,0,0,-1,1) - var/decaltype = 1 // 1 - scorch, 2 - bullet - - if(istype(/obj/item/projectile/bullet, Proj)) - decaltype = 2 - - - virtualIcon = new(icon, icon_state) - - if( virtualIcon.GetPixel(p_x, p_y) ) // if the located pixel isn't blank (null) - - hp -= Proj.damage - if(hp <= 0) - visible_message("[src] breaks into tiny pieces and collapses!") - qdel(src) - - // Create a temporary object to represent the damage - var/obj/bmark = new - bmark.pixel_x = p_x - bmark.pixel_y = p_y - bmark.icon = 'icons/effects/effects.dmi' - bmark.layer = 3.5 - bmark.icon_state = "scorch" - - if(decaltype == 1) - // Energy weapons are hot. they scorch! - - // offset correction - bmark.pixel_x-- - bmark.pixel_y-- - - if(Proj.damage >= 20 || istype(Proj, /obj/item/projectile/beam/practice)) - bmark.icon_state = "scorch" - bmark.dir = pick(NORTH,SOUTH,EAST,WEST) // random scorch design - - - else - bmark.icon_state = "light_scorch" - else - - // Bullets are hard. They make dents! - bmark.icon_state = "dent" - - if(Proj.damage >= 10 && bulletholes.len <= 35) // maximum of 35 bullet holes - if(decaltype == 2) // bullet - if(prob(Proj.damage+30)) // bullets make holes more commonly! - new/datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole - else // Lasers! - if(prob(Proj.damage-10)) // lasers make holes less commonly - new/datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole - - // draw bullet holes - for(var/datum/bullethole/B in bulletholes) - - virtualIcon.DrawBox(null, B.b1x1, B.b1y, B.b1x2, B.b1y) // horizontal line, left to right - virtualIcon.DrawBox(null, B.b2x, B.b2y1, B.b2x, B.b2y2) // vertical line, top to bottom - - overlays += bmark // add the decal - - icon = virtualIcon // apply bulletholes over decals - - return - - return -1 // the bullet/projectile goes through the target! Ie, you missed - - -// Small memory holder entity for transparent bullet holes -/datum/bullethole - // First box - var/b1x1 = 0 - var/b1x2 = 0 - var/b1y = 0 - - // Second box - var/b2x = 0 - var/b2y1 = 0 - var/b2y2 = 0 - -/datum/bullethole/New(obj/item/target/Target, pixel_x = 0, pixel_y = 0) - if(!Target) return - - // Randomize the first box - b1x1 = pixel_x - pick(1,1,1,1,2,2,3,3,4) - b1x2 = pixel_x + pick(1,1,1,1,2,2,3,3,4) - b1y = pixel_y - if(prob(35)) - b1y += rand(-4,4) - - // Randomize the second box - b2x = pixel_x - if(prob(35)) - b2x += rand(-4,4) - b2y1 = pixel_y + pick(1,1,1,1,2,2,3,3,4) - b2y2 = pixel_y - pick(1,1,1,1,2,2,3,3,4) - - Target.bulletholes.Add(src) +// Targets, the things that actually get shot! +/obj/item/target + name = "shooting target" + desc = "A shooting target." + icon = 'icons/obj/objects.dmi' + icon_state = "target_h" + density = 0 + var/hp = 1800 + var/icon/virtualIcon + var/list/bulletholes = list() + +/obj/item/target/Destroy() + // if a target is deleted and associated with a stake, force stake to forget + for(var/obj/structure/target_stake/T in view(3,src)) + if(T.pinned_target == src) + T.pinned_target = null + T.density = 1 + break + return ..() // delete target + +/obj/item/target/Move() + ..() + // After target moves, check for nearby stakes. If associated, move to target + for(var/obj/structure/target_stake/M in view(3,src)) + if(M.density == 0 && M.pinned_target == src) + M.loc = loc + + // This may seem a little counter-intuitive but I assure you that's for a purpose. + // Stakes are the ones that carry targets, yes, but in the stake code we set + // a stake's density to 0 meaning it can't be pushed anymore. Instead of pushing + // the stake now, we have to push the target. + + +/obj/item/target/welder_act(mob/user, obj/item/I) + . = TRUE + if(!use_tool(src, user, 0,, volume = I.tool_volume)) + return + overlays.Cut() + to_chat(usr, "You slice off [src]'s uneven chunks of aluminum and scorch marks.") + +/obj/item/target/attack_hand(mob/user as mob) + // taking pinned targets off! + var/obj/structure/target_stake/stake + for(var/obj/structure/target_stake/T in view(3,src)) + if(T.pinned_target == src) + stake = T + break + + if(stake) + if(stake.pinned_target) + stake.density = 1 + density = 0 + layer = OBJ_LAYER + + loc = user.loc + if(ishuman(user)) + if(!user.get_active_hand()) + user.put_in_hands(src) + to_chat(user, "You take the target out of the stake.") + else + src.loc = get_turf(user) + to_chat(user, "You take the target out of the stake.") + + stake.pinned_target = null + return + + else + ..() + +/obj/item/target/syndicate + icon_state = "target_s" + desc = "A shooting target that looks like a syndicate scum." + hp = 2600 // i guess syndie targets are sturdier? + +/obj/item/target/alien + icon_state = "target_q" + desc = "A shooting target that looks like a xenomorphic alien." + hp = 2350 // alium onest too kinda + +/obj/item/target/bullet_act(var/obj/item/projectile/Proj) + var/p_x = Proj.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset Proj.p_x!" + var/p_y = Proj.p_y + pick(0,0,0,0,0,-1,1) + var/decaltype = 1 // 1 - scorch, 2 - bullet + + if(istype(/obj/item/projectile/bullet, Proj)) + decaltype = 2 + + + virtualIcon = new(icon, icon_state) + + if( virtualIcon.GetPixel(p_x, p_y) ) // if the located pixel isn't blank (null) + + hp -= Proj.damage + if(hp <= 0) + visible_message("[src] breaks into tiny pieces and collapses!") + qdel(src) + + // Create a temporary object to represent the damage + var/obj/bmark = new + bmark.pixel_x = p_x + bmark.pixel_y = p_y + bmark.icon = 'icons/effects/effects.dmi' + bmark.layer = 3.5 + bmark.icon_state = "scorch" + + if(decaltype == 1) + // Energy weapons are hot. they scorch! + + // offset correction + bmark.pixel_x-- + bmark.pixel_y-- + + if(Proj.damage >= 20 || istype(Proj, /obj/item/projectile/beam/practice)) + bmark.icon_state = "scorch" + bmark.dir = pick(NORTH,SOUTH,EAST,WEST) // random scorch design + + + else + bmark.icon_state = "light_scorch" + else + + // Bullets are hard. They make dents! + bmark.icon_state = "dent" + + if(Proj.damage >= 10 && bulletholes.len <= 35) // maximum of 35 bullet holes + if(decaltype == 2) // bullet + if(prob(Proj.damage+30)) // bullets make holes more commonly! + new/datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole + else // Lasers! + if(prob(Proj.damage-10)) // lasers make holes less commonly + new/datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole + + // draw bullet holes + for(var/datum/bullethole/B in bulletholes) + + virtualIcon.DrawBox(null, B.b1x1, B.b1y, B.b1x2, B.b1y) // horizontal line, left to right + virtualIcon.DrawBox(null, B.b2x, B.b2y1, B.b2x, B.b2y2) // vertical line, top to bottom + + overlays += bmark // add the decal + + icon = virtualIcon // apply bulletholes over decals + + return + + return -1 // the bullet/projectile goes through the target! Ie, you missed + + +// Small memory holder entity for transparent bullet holes +/datum/bullethole + // First box + var/b1x1 = 0 + var/b1x2 = 0 + var/b1y = 0 + + // Second box + var/b2x = 0 + var/b2y1 = 0 + var/b2y2 = 0 + +/datum/bullethole/New(obj/item/target/Target, pixel_x = 0, pixel_y = 0) + if(!Target) return + + // Randomize the first box + b1x1 = pixel_x - pick(1,1,1,1,2,2,3,3,4) + b1x2 = pixel_x + pick(1,1,1,1,2,2,3,3,4) + b1y = pixel_y + if(prob(35)) + b1y += rand(-4,4) + + // Randomize the second box + b2x = pixel_x + if(prob(35)) + b2x += rand(-4,4) + b2y1 = pixel_y + pick(1,1,1,1,2,2,3,3,4) + b2y2 = pixel_y - pick(1,1,1,1,2,2,3,3,4) + + Target.bulletholes.Add(src) diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 9f8c877aa24..8c1f5826d66 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -1,283 +1,283 @@ -/obj/item/stack/medical - name = "medical pack" - singular_name = "medical pack" - icon = 'icons/obj/items.dmi' - amount = 6 - max_amount = 6 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - resistance_flags = FLAMMABLE - max_integrity = 40 - var/heal_brute = 0 - var/heal_burn = 0 - var/self_delay = 20 - var/unique_handling = FALSE //some things give a special prompt, do we want to bypass some checks in parent? - var/stop_bleeding = 0 - var/healverb = "bandage" - -/obj/item/stack/medical/attack(mob/living/M, mob/user) - if(!iscarbon(M) && !isanimal(M)) - to_chat(user, "[src] cannot be applied to [M]!") - return 1 - - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return 1 - - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) - - if(!H.can_inject(user, TRUE)) - return TRUE - - if(!affecting) - to_chat(user, "That limb is missing!") - return TRUE - - if(affecting.is_robotic()) - to_chat(user, "This can't be used on a robotic limb.") - return TRUE - - if(M == user && !unique_handling) - user.visible_message("[user] starts to apply [src] on [H]...") - if(!do_mob(user, H, self_delay)) - return TRUE - return - - if(isanimal(M)) - var/mob/living/simple_animal/critter = M - if(!(critter.healable)) - to_chat(user, "You cannot use [src] on [critter]!") - return - else if (critter.health == critter.maxHealth) - to_chat(user, "[critter] is at full health.") - return - else if(heal_brute < 1) - to_chat(user, "[src] won't help [critter] at all.") - return - - critter.heal_organ_damage(heal_brute, heal_burn) - user.visible_message("[user] applies [src] on [critter].", \ - "You apply [src] on [critter].") - - use(1) - - else - M.heal_organ_damage(heal_brute, heal_burn) - user.visible_message("[user] applies [src] on [M].", \ - "You apply [src] on [M].") - use(1) - -/obj/item/stack/medical/proc/heal(mob/living/M, mob/user) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) - user.visible_message("[user] [healverb]s the wounds on [H]'s [affecting.name].", \ - "You [healverb] the wounds on [H]'s [affecting.name]." ) - - var/rembrute = max(0, heal_brute - affecting.brute_dam) // Maxed with 0 since heal_damage let you pass in a negative value - var/remburn = max(0, heal_burn - affecting.burn_dam) // And deduct it from their health (aka deal damage) - var/nrembrute = rembrute - var/nremburn = remburn - affecting.heal_damage(heal_brute, heal_burn) - var/list/achildlist - if(!isnull(affecting.children)) - achildlist = affecting.children.Copy() - var/parenthealed = FALSE - while(rembrute + remburn > 0) // Don't bother if there's not enough leftover heal - var/obj/item/organ/external/E - if(LAZYLEN(achildlist)) - E = pick_n_take(achildlist) // Pick a random children and then remove it from the list - else if(affecting.parent && !parenthealed) // If there's a parent and no healing attempt was made on it - E = affecting.parent - parenthealed = TRUE - else - break // If the organ have no child left and no parent / parent healed, break - if(E.status & ORGAN_ROBOT || E.open) // Ignore robotic or open limb - continue - else if(!E.brute_dam && !E.burn_dam) // Ignore undamaged limb - continue - nrembrute = max(0, rembrute - E.brute_dam) // Deduct the healed damage from the remain - nremburn = max(0, remburn - E.burn_dam) - E.heal_damage(rembrute, remburn) - rembrute = nrembrute - remburn = nremburn - user.visible_message("[user] [healverb]s the wounds on [H]'s [E.name] with the remaining medication.", \ - "You [healverb] the wounds on [H]'s [E.name] with the remaining medication." ) - -//Bruise Packs// - -/obj/item/stack/medical/bruise_pack - name = "roll of gauze" - singular_name = "gauze length" - desc = "Some sterile gauze to wrap around bloody stumps." - icon_state = "gauze" - origin_tech = "biotech=2" - heal_brute = 10 - stop_bleeding = 1800 - -/obj/item/stack/medical/bruise_pack/attackby(obj/item/I, mob/user, params) - if(I.sharp) - if(get_amount() < 2) - to_chat(user, "You need at least two gauzes to do this!") - return - new /obj/item/stack/sheet/cloth(user.drop_location()) - user.visible_message("[user] cuts [src] into pieces of cloth with [I].", \ - "You cut [src] into pieces of cloth with [I].", \ - "You hear cutting.") - use(2) - else - return ..() - -/obj/item/stack/medical/bruise_pack/attack(mob/living/M, mob/user) - if(..()) - return TRUE - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) - - if(affecting.open == FALSE) - affecting.germ_level = 0 - - if(stop_bleeding) - if(!H.bleedsuppress) //so you can't stack bleed suppression - H.suppress_bloodloss(stop_bleeding) - - heal(H, user) - - H.UpdateDamageIcon() - use(1) - else - to_chat(user, "[affecting] is cut open, you'll need more than a bandage!") - -/obj/item/stack/medical/bruise_pack/improvised - name = "improvised gauze" - singular_name = "improvised gauze" - desc = "A roll of cloth roughly cut from something that can stop bleeding, but does not heal wounds." - stop_bleeding = 900 - -/obj/item/stack/medical/bruise_pack/advanced - name = "advanced trauma kit" - singular_name = "advanced trauma kit" - desc = "An advanced trauma kit for severe injuries." - icon_state = "traumakit" - heal_brute = 25 - stop_bleeding = 0 - - - -//Ointment// - - -/obj/item/stack/medical/ointment - name = "ointment" - desc = "Used to treat those nasty burns." - gender = PLURAL - singular_name = "ointment" - icon_state = "ointment" - origin_tech = "biotech=2" - healverb = "salve" - heal_burn = 10 - -/obj/item/stack/medical/ointment/attack(mob/living/M, mob/user) - if(..()) - return 1 - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) - - if(affecting.open == FALSE) - affecting.germ_level = 0 - - heal(H, user) - - H.UpdateDamageIcon() - use(1) - else - to_chat(user, "[affecting] is cut open, you'll need more than some ointment!") - - -/obj/item/stack/medical/ointment/advanced - name = "advanced burn kit" - singular_name = "advanced burn kit" - desc = "An advanced treatment kit for severe burns." - icon_state = "burnkit" - heal_burn = 25 - -//Medical Herbs// -/obj/item/stack/medical/bruise_pack/comfrey - name = "\improper Comfrey leaf" - singular_name = "Comfrey leaf" - desc = "A soft leaf that is rubbed on bruises." - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "tea_aspera_leaves" - color = "#378C61" - stop_bleeding = 0 - heal_brute = 12 - - -/obj/item/stack/medical/ointment/aloe - name = "\improper Aloe Vera leaf" - singular_name = "Aloe Vera leaf" - desc = "A cold leaf that is rubbed on burns." - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "ambrosiavulgaris" - color = "#4CC5C7" - heal_burn = 12 - -// Splints -/obj/item/stack/medical/splint - name = "medical splints" - singular_name = "medical splint" - icon_state = "splint" - unique_handling = TRUE - self_delay = 100 - var/other_delay = 0 - -/obj/item/stack/medical/splint/attack(mob/living/M, mob/user) - if(..()) - return TRUE - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) - var/limb = affecting.name - - if(!(affecting.limb_name in list("l_arm", "r_arm", "l_hand", "r_hand", "l_leg", "r_leg", "l_foot", "r_foot"))) - to_chat(user, "You can't apply a splint there!") - return TRUE - - if(affecting.status & ORGAN_SPLINTED) - to_chat(user, "[H]'s [limb] is already splinted!") - if(alert(user, "Would you like to remove the splint from [H]'s [limb]?", "Splint removal.", "Yes", "No") == "Yes") - affecting.status &= ~ORGAN_SPLINTED - H.handle_splints() - to_chat(user, "You remove the splint from [H]'s [limb].") - return TRUE - - if((M == user && self_delay > 0) || (M != user && other_delay > 0)) - user.visible_message("[user] starts to apply [src] to [H]'s [limb].", \ - "You start to apply [src] to [H]'s [limb].", \ - "You hear something being wrapped.") - - if(M == user && !do_mob(user, H, self_delay)) - return TRUE - else if(!do_mob(user, H, other_delay)) - return TRUE - - user.visible_message("[user] applies [src] to [H]'s [limb].", \ - "You apply [src] to [H]'s [limb].") - - affecting.status |= ORGAN_SPLINTED - affecting.splinted_count = H.step_count - H.handle_splints() - use(1) - -/obj/item/stack/medical/splint/tribal - name = "tribal splints" - icon_state = "tribal_splint" - other_delay = 50 +/obj/item/stack/medical + name = "medical pack" + singular_name = "medical pack" + icon = 'icons/obj/items.dmi' + amount = 6 + max_amount = 6 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + resistance_flags = FLAMMABLE + max_integrity = 40 + var/heal_brute = 0 + var/heal_burn = 0 + var/self_delay = 20 + var/unique_handling = FALSE //some things give a special prompt, do we want to bypass some checks in parent? + var/stop_bleeding = 0 + var/healverb = "bandage" + +/obj/item/stack/medical/attack(mob/living/M, mob/user) + if(!iscarbon(M) && !isanimal(M)) + to_chat(user, "[src] cannot be applied to [M]!") + return 1 + + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return 1 + + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) + + if(!H.can_inject(user, TRUE)) + return TRUE + + if(!affecting) + to_chat(user, "That limb is missing!") + return TRUE + + if(affecting.is_robotic()) + to_chat(user, "This can't be used on a robotic limb.") + return TRUE + + if(M == user && !unique_handling) + user.visible_message("[user] starts to apply [src] on [H]...") + if(!do_mob(user, H, self_delay)) + return TRUE + return + + if(isanimal(M)) + var/mob/living/simple_animal/critter = M + if(!(critter.healable)) + to_chat(user, "You cannot use [src] on [critter]!") + return + else if (critter.health == critter.maxHealth) + to_chat(user, "[critter] is at full health.") + return + else if(heal_brute < 1) + to_chat(user, "[src] won't help [critter] at all.") + return + + critter.heal_organ_damage(heal_brute, heal_burn) + user.visible_message("[user] applies [src] on [critter].", \ + "You apply [src] on [critter].") + + use(1) + + else + M.heal_organ_damage(heal_brute, heal_burn) + user.visible_message("[user] applies [src] on [M].", \ + "You apply [src] on [M].") + use(1) + +/obj/item/stack/medical/proc/heal(mob/living/M, mob/user) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) + user.visible_message("[user] [healverb]s the wounds on [H]'s [affecting.name].", \ + "You [healverb] the wounds on [H]'s [affecting.name]." ) + + var/rembrute = max(0, heal_brute - affecting.brute_dam) // Maxed with 0 since heal_damage let you pass in a negative value + var/remburn = max(0, heal_burn - affecting.burn_dam) // And deduct it from their health (aka deal damage) + var/nrembrute = rembrute + var/nremburn = remburn + affecting.heal_damage(heal_brute, heal_burn) + var/list/achildlist + if(!isnull(affecting.children)) + achildlist = affecting.children.Copy() + var/parenthealed = FALSE + while(rembrute + remburn > 0) // Don't bother if there's not enough leftover heal + var/obj/item/organ/external/E + if(LAZYLEN(achildlist)) + E = pick_n_take(achildlist) // Pick a random children and then remove it from the list + else if(affecting.parent && !parenthealed) // If there's a parent and no healing attempt was made on it + E = affecting.parent + parenthealed = TRUE + else + break // If the organ have no child left and no parent / parent healed, break + if(E.status & ORGAN_ROBOT || E.open) // Ignore robotic or open limb + continue + else if(!E.brute_dam && !E.burn_dam) // Ignore undamaged limb + continue + nrembrute = max(0, rembrute - E.brute_dam) // Deduct the healed damage from the remain + nremburn = max(0, remburn - E.burn_dam) + E.heal_damage(rembrute, remburn) + rembrute = nrembrute + remburn = nremburn + user.visible_message("[user] [healverb]s the wounds on [H]'s [E.name] with the remaining medication.", \ + "You [healverb] the wounds on [H]'s [E.name] with the remaining medication." ) + +//Bruise Packs// + +/obj/item/stack/medical/bruise_pack + name = "roll of gauze" + singular_name = "gauze length" + desc = "Some sterile gauze to wrap around bloody stumps." + icon_state = "gauze" + origin_tech = "biotech=2" + heal_brute = 10 + stop_bleeding = 1800 + +/obj/item/stack/medical/bruise_pack/attackby(obj/item/I, mob/user, params) + if(I.sharp) + if(get_amount() < 2) + to_chat(user, "You need at least two gauzes to do this!") + return + new /obj/item/stack/sheet/cloth(user.drop_location()) + user.visible_message("[user] cuts [src] into pieces of cloth with [I].", \ + "You cut [src] into pieces of cloth with [I].", \ + "You hear cutting.") + use(2) + else + return ..() + +/obj/item/stack/medical/bruise_pack/attack(mob/living/M, mob/user) + if(..()) + return TRUE + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) + + if(affecting.open == FALSE) + affecting.germ_level = 0 + + if(stop_bleeding) + if(!H.bleedsuppress) //so you can't stack bleed suppression + H.suppress_bloodloss(stop_bleeding) + + heal(H, user) + + H.UpdateDamageIcon() + use(1) + else + to_chat(user, "[affecting] is cut open, you'll need more than a bandage!") + +/obj/item/stack/medical/bruise_pack/improvised + name = "improvised gauze" + singular_name = "improvised gauze" + desc = "A roll of cloth roughly cut from something that can stop bleeding, but does not heal wounds." + stop_bleeding = 900 + +/obj/item/stack/medical/bruise_pack/advanced + name = "advanced trauma kit" + singular_name = "advanced trauma kit" + desc = "An advanced trauma kit for severe injuries." + icon_state = "traumakit" + heal_brute = 25 + stop_bleeding = 0 + + + +//Ointment// + + +/obj/item/stack/medical/ointment + name = "ointment" + desc = "Used to treat those nasty burns." + gender = PLURAL + singular_name = "ointment" + icon_state = "ointment" + origin_tech = "biotech=2" + healverb = "salve" + heal_burn = 10 + +/obj/item/stack/medical/ointment/attack(mob/living/M, mob/user) + if(..()) + return 1 + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) + + if(affecting.open == FALSE) + affecting.germ_level = 0 + + heal(H, user) + + H.UpdateDamageIcon() + use(1) + else + to_chat(user, "[affecting] is cut open, you'll need more than some ointment!") + + +/obj/item/stack/medical/ointment/advanced + name = "advanced burn kit" + singular_name = "advanced burn kit" + desc = "An advanced treatment kit for severe burns." + icon_state = "burnkit" + heal_burn = 25 + +//Medical Herbs// +/obj/item/stack/medical/bruise_pack/comfrey + name = "\improper Comfrey leaf" + singular_name = "Comfrey leaf" + desc = "A soft leaf that is rubbed on bruises." + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "tea_aspera_leaves" + color = "#378C61" + stop_bleeding = 0 + heal_brute = 12 + + +/obj/item/stack/medical/ointment/aloe + name = "\improper Aloe Vera leaf" + singular_name = "Aloe Vera leaf" + desc = "A cold leaf that is rubbed on burns." + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "ambrosiavulgaris" + color = "#4CC5C7" + heal_burn = 12 + +// Splints +/obj/item/stack/medical/splint + name = "medical splints" + singular_name = "medical splint" + icon_state = "splint" + unique_handling = TRUE + self_delay = 100 + var/other_delay = 0 + +/obj/item/stack/medical/splint/attack(mob/living/M, mob/user) + if(..()) + return TRUE + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/affecting = H.get_organ(user.zone_selected) + var/limb = affecting.name + + if(!(affecting.limb_name in list("l_arm", "r_arm", "l_hand", "r_hand", "l_leg", "r_leg", "l_foot", "r_foot"))) + to_chat(user, "You can't apply a splint there!") + return TRUE + + if(affecting.status & ORGAN_SPLINTED) + to_chat(user, "[H]'s [limb] is already splinted!") + if(alert(user, "Would you like to remove the splint from [H]'s [limb]?", "Splint removal.", "Yes", "No") == "Yes") + affecting.status &= ~ORGAN_SPLINTED + H.handle_splints() + to_chat(user, "You remove the splint from [H]'s [limb].") + return TRUE + + if((M == user && self_delay > 0) || (M != user && other_delay > 0)) + user.visible_message("[user] starts to apply [src] to [H]'s [limb].", \ + "You start to apply [src] to [H]'s [limb].", \ + "You hear something being wrapped.") + + if(M == user && !do_mob(user, H, self_delay)) + return TRUE + else if(!do_mob(user, H, other_delay)) + return TRUE + + user.visible_message("[user] applies [src] to [H]'s [limb].", \ + "You apply [src] to [H]'s [limb].") + + affecting.status |= ORGAN_SPLINTED + affecting.splinted_count = H.step_count + H.handle_splints() + use(1) + +/obj/item/stack/medical/splint/tribal + name = "tribal splints" + icon_state = "tribal_splint" + other_delay = 50 diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 37f726b215d..3b214f87940 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -1,68 +1,68 @@ -var/global/list/datum/stack_recipe/rod_recipes = list ( \ - new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ - ) - -/obj/item/stack/rods - name = "metal rod" - desc = "Some rods. Can be used for building, or something." - singular_name = "metal rod" - icon_state = "rods" - item_state = "rods" - flags = CONDUCT - w_class = WEIGHT_CLASS_NORMAL - force = 9.0 - throwforce = 10.0 - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=1000) - max_amount = 50 - attack_verb = list("hit", "bludgeoned", "whacked") - hitsound = 'sound/weapons/grenadelaunch.ogg' - toolspeed = 1 - usesound = 'sound/items/deconstruct.ogg' - -/obj/item/stack/rods/cyborg - materials = list() - -/obj/item/stack/rods/ten - amount = 10 - -/obj/item/stack/rods/twentyfive - amount = 25 - -/obj/item/stack/rods/fifty - amount = 50 - -/obj/item/stack/rods/New(loc, amount=null) - ..() - recipes = rod_recipes - update_icon() - -/obj/item/stack/rods/update_icon() - var/amount = get_amount() - if((amount <= 5) && (amount > 0)) - icon_state = "rods-[amount]" - else - icon_state = "rods" - -/obj/item/stack/rods/welder_act(mob/user, obj/item/I) - if(get_amount() < 2) - to_chat(user, "You need at least two rods to do this!") - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - var/obj/item/stack/sheet/metal/new_item = new(drop_location()) - if(new_item.get_amount() <= 0) - // stack was moved into another one on the pile - new_item = locate() in user.loc - visible_message("[user.name] shapes [src] into metal with [I]!", \ - "You shape [src] into metal with [I]!", \ - "You hear welding.") - var/replace = user.is_in_inactive_hand(src) - use(2) - if(get_amount() <= 0 && replace) - user.unEquip(src, 1) - if(new_item) - user.put_in_hands(new_item) +var/global/list/datum/stack_recipe/rod_recipes = list ( \ + new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ + ) + +/obj/item/stack/rods + name = "metal rod" + desc = "Some rods. Can be used for building, or something." + singular_name = "metal rod" + icon_state = "rods" + item_state = "rods" + flags = CONDUCT + w_class = WEIGHT_CLASS_NORMAL + force = 9.0 + throwforce = 10.0 + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=1000) + max_amount = 50 + attack_verb = list("hit", "bludgeoned", "whacked") + hitsound = 'sound/weapons/grenadelaunch.ogg' + toolspeed = 1 + usesound = 'sound/items/deconstruct.ogg' + +/obj/item/stack/rods/cyborg + materials = list() + +/obj/item/stack/rods/ten + amount = 10 + +/obj/item/stack/rods/twentyfive + amount = 25 + +/obj/item/stack/rods/fifty + amount = 50 + +/obj/item/stack/rods/New(loc, amount=null) + ..() + recipes = rod_recipes + update_icon() + +/obj/item/stack/rods/update_icon() + var/amount = get_amount() + if((amount <= 5) && (amount > 0)) + icon_state = "rods-[amount]" + else + icon_state = "rods" + +/obj/item/stack/rods/welder_act(mob/user, obj/item/I) + if(get_amount() < 2) + to_chat(user, "You need at least two rods to do this!") + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + var/obj/item/stack/sheet/metal/new_item = new(drop_location()) + if(new_item.get_amount() <= 0) + // stack was moved into another one on the pile + new_item = locate() in user.loc + visible_message("[user.name] shapes [src] into metal with [I]!", \ + "You shape [src] into metal with [I]!", \ + "You hear welding.") + var/replace = user.is_in_inactive_hand(src) + use(2) + if(get_amount() <= 0 && replace) + user.unEquip(src, 1) + if(new_item) + user.put_in_hands(new_item) diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index 769bc9dad54..4d3e3181726 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -1,211 +1,211 @@ -/* Glass stack types - * Contains: - * Glass sheets - * Reinforced glass sheets - * Glass shards - TODO: Move this into code/game/object/item/weapons - * Plasma Glass Sheets - * Reinforced Plasma Glass Sheets (AKA Holy fuck strong windows) - - Todo: Create a unified construct_window(sheet, user, created_window, full_window) - - */ - -/* - * Glass sheets - */ - -GLOBAL_LIST_INIT(glass_recipes, list ( \ - new/datum/stack_recipe/window("directional window", /obj/structure/window/basic, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe/window("fulltile window", /obj/structure/window/full/basic, 2, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe("fishbowl", /obj/machinery/fishtank/bowl, 1, time = 0), \ - new/datum/stack_recipe("fish tank", /obj/machinery/fishtank/tank, 3, time = 0, on_floor = TRUE), \ - new/datum/stack_recipe("wall aquariam", /obj/machinery/fishtank/wall, 4, time = 0, on_floor = TRUE) \ -)) - -/obj/item/stack/sheet/glass - name = "glass" - desc = "HOLY SHEET! That is a lot of glass." - singular_name = "glass sheet" - icon_state = "sheet-glass" - materials = list(MAT_GLASS=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) - resistance_flags = ACID_PROOF - origin_tech = "materials=1" - created_window = /obj/structure/window/basic - full_window = /obj/structure/window/full/basic - merge_type = /obj/item/stack/sheet/glass - point_value = 1 - -/obj/item/stack/sheet/glass/fifty - amount = 50 - -/obj/item/stack/sheet/glass/cyborg - materials = list() - -/obj/item/stack/sheet/glass/New(loc, amount) - recipes = GLOB.glass_recipes - ..() - -/obj/item/stack/sheet/glass/attackby(obj/item/W, mob/user, params) - ..() - if(istype(W,/obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/CC = W - if(CC.amount < 5) - to_chat(user, "There is not enough wire in this coil. You need 5 lengths.") - return - CC.use(5) - to_chat(user, "You attach wire to the [name].") - new /obj/item/stack/light_w(user.loc) - src.use(1) - else if( istype(W, /obj/item/stack/rods) ) - var/obj/item/stack/rods/V = W - var/obj/item/stack/sheet/rglass/RG = new (user.loc) - RG.add_fingerprint(user) - V.use(1) - var/obj/item/stack/sheet/glass/G = src - src = null - var/replace = (user.get_inactive_hand()==G) - G.use(1) - if(!G && !RG && replace) - user.put_in_hands(RG) - else - return ..() - - -/* - * Reinforced glass sheets - */ - -GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ - new/datum/stack_recipe/window("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, on_floor = TRUE, window_checks = TRUE), \ - null, \ - new/datum/stack_recipe/window("directional reinforced window", /obj/structure/window/reinforced, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe/window("fulltile reinforced window", /obj/structure/window/full/reinforced, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/rglass - name = "reinforced glass" - desc = "Glass which seems to have rods or something stuck in them." - singular_name = "reinforced glass sheet" - icon_state = "sheet-rglass" - materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT/2, MAT_GLASS=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) - resistance_flags = ACID_PROOF - origin_tech = "materials=2" - created_window = /obj/structure/window/reinforced - full_window = /obj/structure/window/full/reinforced - merge_type = /obj/item/stack/sheet/rglass - point_value = 4 - -/obj/item/stack/sheet/rglass/cyborg - materials = list() - -/obj/item/stack/sheet/rglass/New(loc, amount) - recipes = GLOB.reinforced_glass_recipes - ..() - -GLOBAL_LIST_INIT(pglass_recipes, list ( \ - new/datum/stack_recipe/window("directional window", /obj/structure/window/plasmabasic, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe/window("fulltile window", /obj/structure/window/full/plasmabasic, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/plasmaglass - name = "plasma glass" - desc = "A very strong and very resistant sheet of a plasma-glass alloy." - singular_name = "glass sheet" - icon_state = "sheet-plasmaglass" - item_state = "sheet-rglass" - materials = list(MAT_GLASS=MINERAL_MATERIAL_AMOUNT*2) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100) - resistance_flags = ACID_PROOF - origin_tech = "plasmatech=2;materials=2" - created_window = /obj/structure/window/plasmabasic - full_window = /obj/structure/window/full/plasmabasic - point_value = 19 - -/obj/item/stack/sheet/plasmaglass/New(loc, amount) - recipes = GLOB.pglass_recipes - ..() - -/obj/item/stack/sheet/plasmaglass/attackby(obj/item/W, mob/user, params) - ..() - if( istype(W, /obj/item/stack/rods) ) - var/obj/item/stack/rods/V = W - var/obj/item/stack/sheet/plasmarglass/RG = new (user.loc) - RG.add_fingerprint(user) - V.use(1) - var/obj/item/stack/sheet/glass/G = src - src = null - var/replace = (user.get_inactive_hand()==G) - G.use(1) - if(!G && !RG && replace) - user.put_in_hands(RG) - else - return ..() - -/* - * Reinforced plasma glass sheets - */ - -GLOBAL_LIST_INIT(prglass_recipes, list ( \ - new/datum/stack_recipe/window("directional reinforced window", /obj/structure/window/plasmareinforced, time = 0, on_floor = TRUE, window_checks = TRUE), \ - new/datum/stack_recipe/window("fulltile reinforced window", /obj/structure/window/full/plasmareinforced, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ -)) - -/obj/item/stack/sheet/plasmarglass - name = "reinforced plasma glass" - desc = "Plasma glass which seems to have rods or something stuck in them." - singular_name = "reinforced plasma glass sheet" - icon_state = "sheet-plasmarglass" - item_state = "sheet-rglass" - materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT/2, MAT_GLASS=MINERAL_MATERIAL_AMOUNT*2) - armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - resistance_flags = ACID_PROOF - origin_tech = "plasmatech=2;materials=2" - created_window = /obj/structure/window/plasmareinforced - full_window = /obj/structure/window/full/plasmareinforced - point_value = 23 - -/obj/item/stack/sheet/plasmarglass/New(loc, amount) - recipes = GLOB.prglass_recipes - ..() - -GLOBAL_LIST_INIT(titaniumglass_recipes, list( - new/datum/stack_recipe/window("shuttle window", /obj/structure/window/full/shuttle, 2, time = 0, on_floor = TRUE, window_checks = TRUE) - )) - -/obj/item/stack/sheet/titaniumglass - name = "titanium glass" - desc = "A glass sheet made out of a titanium-silicate alloy." - singular_name = "titanium glass sheet" - icon_state = "sheet-titaniumglass" - item_state = "sheet-rglass" - materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT, MAT_GLASS=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/titaniumglass - full_window = /obj/structure/window/full/shuttle - -/obj/item/stack/sheet/titaniumglass/New(loc, amount) - recipes = GLOB.titaniumglass_recipes - ..() - -GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( - new/datum/stack_recipe/window("plastitanium window", /obj/structure/window/plastitanium, 2, time = 0, on_floor = TRUE, window_checks = TRUE) - )) - -/obj/item/stack/sheet/plastitaniumglass - name = "plastitanium glass" - desc = "A glass sheet made out of a plasma-titanium-silicate alloy." - singular_name = "plastitanium glass sheet" - icon_state = "sheet-plastitaniumglass" - item_state = "sheet-rglass" - materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT, MAT_PLASMA=MINERAL_MATERIAL_AMOUNT, MAT_GLASS=MINERAL_MATERIAL_AMOUNT) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - resistance_flags = ACID_PROOF - merge_type = /obj/item/stack/sheet/plastitaniumglass - full_window = /obj/structure/window/plastitanium - -/obj/item/stack/sheet/plastitaniumglass/New(loc, amount) - recipes = GLOB.plastitaniumglass_recipes - ..() +/* Glass stack types + * Contains: + * Glass sheets + * Reinforced glass sheets + * Glass shards - TODO: Move this into code/game/object/item/weapons + * Plasma Glass Sheets + * Reinforced Plasma Glass Sheets (AKA Holy fuck strong windows) + + Todo: Create a unified construct_window(sheet, user, created_window, full_window) + + */ + +/* + * Glass sheets + */ + +GLOBAL_LIST_INIT(glass_recipes, list ( \ + new/datum/stack_recipe/window("directional window", /obj/structure/window/basic, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe/window("fulltile window", /obj/structure/window/full/basic, 2, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe("fishbowl", /obj/machinery/fishtank/bowl, 1, time = 0), \ + new/datum/stack_recipe("fish tank", /obj/machinery/fishtank/tank, 3, time = 0, on_floor = TRUE), \ + new/datum/stack_recipe("wall aquariam", /obj/machinery/fishtank/wall, 4, time = 0, on_floor = TRUE) \ +)) + +/obj/item/stack/sheet/glass + name = "glass" + desc = "HOLY SHEET! That is a lot of glass." + singular_name = "glass sheet" + icon_state = "sheet-glass" + materials = list(MAT_GLASS=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 100) + resistance_flags = ACID_PROOF + origin_tech = "materials=1" + created_window = /obj/structure/window/basic + full_window = /obj/structure/window/full/basic + merge_type = /obj/item/stack/sheet/glass + point_value = 1 + +/obj/item/stack/sheet/glass/fifty + amount = 50 + +/obj/item/stack/sheet/glass/cyborg + materials = list() + +/obj/item/stack/sheet/glass/New(loc, amount) + recipes = GLOB.glass_recipes + ..() + +/obj/item/stack/sheet/glass/attackby(obj/item/W, mob/user, params) + ..() + if(istype(W,/obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + if(CC.amount < 5) + to_chat(user, "There is not enough wire in this coil. You need 5 lengths.") + return + CC.use(5) + to_chat(user, "You attach wire to the [name].") + new /obj/item/stack/light_w(user.loc) + src.use(1) + else if( istype(W, /obj/item/stack/rods) ) + var/obj/item/stack/rods/V = W + var/obj/item/stack/sheet/rglass/RG = new (user.loc) + RG.add_fingerprint(user) + V.use(1) + var/obj/item/stack/sheet/glass/G = src + src = null + var/replace = (user.get_inactive_hand()==G) + G.use(1) + if(!G && !RG && replace) + user.put_in_hands(RG) + else + return ..() + + +/* + * Reinforced glass sheets + */ + +GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ + new/datum/stack_recipe/window("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, on_floor = TRUE, window_checks = TRUE), \ + null, \ + new/datum/stack_recipe/window("directional reinforced window", /obj/structure/window/reinforced, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe/window("fulltile reinforced window", /obj/structure/window/full/reinforced, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/rglass + name = "reinforced glass" + desc = "Glass which seems to have rods or something stuck in them." + singular_name = "reinforced glass sheet" + icon_state = "sheet-rglass" + materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT/2, MAT_GLASS=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) + resistance_flags = ACID_PROOF + origin_tech = "materials=2" + created_window = /obj/structure/window/reinforced + full_window = /obj/structure/window/full/reinforced + merge_type = /obj/item/stack/sheet/rglass + point_value = 4 + +/obj/item/stack/sheet/rglass/cyborg + materials = list() + +/obj/item/stack/sheet/rglass/New(loc, amount) + recipes = GLOB.reinforced_glass_recipes + ..() + +GLOBAL_LIST_INIT(pglass_recipes, list ( \ + new/datum/stack_recipe/window("directional window", /obj/structure/window/plasmabasic, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe/window("fulltile window", /obj/structure/window/full/plasmabasic, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/plasmaglass + name = "plasma glass" + desc = "A very strong and very resistant sheet of a plasma-glass alloy." + singular_name = "glass sheet" + icon_state = "sheet-plasmaglass" + item_state = "sheet-rglass" + materials = list(MAT_GLASS=MINERAL_MATERIAL_AMOUNT*2) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 100) + resistance_flags = ACID_PROOF + origin_tech = "plasmatech=2;materials=2" + created_window = /obj/structure/window/plasmabasic + full_window = /obj/structure/window/full/plasmabasic + point_value = 19 + +/obj/item/stack/sheet/plasmaglass/New(loc, amount) + recipes = GLOB.pglass_recipes + ..() + +/obj/item/stack/sheet/plasmaglass/attackby(obj/item/W, mob/user, params) + ..() + if( istype(W, /obj/item/stack/rods) ) + var/obj/item/stack/rods/V = W + var/obj/item/stack/sheet/plasmarglass/RG = new (user.loc) + RG.add_fingerprint(user) + V.use(1) + var/obj/item/stack/sheet/glass/G = src + src = null + var/replace = (user.get_inactive_hand()==G) + G.use(1) + if(!G && !RG && replace) + user.put_in_hands(RG) + else + return ..() + +/* + * Reinforced plasma glass sheets + */ + +GLOBAL_LIST_INIT(prglass_recipes, list ( \ + new/datum/stack_recipe/window("directional reinforced window", /obj/structure/window/plasmareinforced, time = 0, on_floor = TRUE, window_checks = TRUE), \ + new/datum/stack_recipe/window("fulltile reinforced window", /obj/structure/window/full/plasmareinforced, 2, time = 0, on_floor = TRUE, window_checks = TRUE) \ +)) + +/obj/item/stack/sheet/plasmarglass + name = "reinforced plasma glass" + desc = "Plasma glass which seems to have rods or something stuck in them." + singular_name = "reinforced plasma glass sheet" + icon_state = "sheet-plasmarglass" + item_state = "sheet-rglass" + materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT/2, MAT_GLASS=MINERAL_MATERIAL_AMOUNT*2) + armor = list("melee" = 20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + resistance_flags = ACID_PROOF + origin_tech = "plasmatech=2;materials=2" + created_window = /obj/structure/window/plasmareinforced + full_window = /obj/structure/window/full/plasmareinforced + point_value = 23 + +/obj/item/stack/sheet/plasmarglass/New(loc, amount) + recipes = GLOB.prglass_recipes + ..() + +GLOBAL_LIST_INIT(titaniumglass_recipes, list( + new/datum/stack_recipe/window("shuttle window", /obj/structure/window/full/shuttle, 2, time = 0, on_floor = TRUE, window_checks = TRUE) + )) + +/obj/item/stack/sheet/titaniumglass + name = "titanium glass" + desc = "A glass sheet made out of a titanium-silicate alloy." + singular_name = "titanium glass sheet" + icon_state = "sheet-titaniumglass" + item_state = "sheet-rglass" + materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT, MAT_GLASS=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/titaniumglass + full_window = /obj/structure/window/full/shuttle + +/obj/item/stack/sheet/titaniumglass/New(loc, amount) + recipes = GLOB.titaniumglass_recipes + ..() + +GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( + new/datum/stack_recipe/window("plastitanium window", /obj/structure/window/plastitanium, 2, time = 0, on_floor = TRUE, window_checks = TRUE) + )) + +/obj/item/stack/sheet/plastitaniumglass + name = "plastitanium glass" + desc = "A glass sheet made out of a plasma-titanium-silicate alloy." + singular_name = "plastitanium glass sheet" + icon_state = "sheet-plastitaniumglass" + item_state = "sheet-rglass" + materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT, MAT_PLASMA=MINERAL_MATERIAL_AMOUNT, MAT_GLASS=MINERAL_MATERIAL_AMOUNT) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + resistance_flags = ACID_PROOF + merge_type = /obj/item/stack/sheet/plastitaniumglass + full_window = /obj/structure/window/plastitanium + +/obj/item/stack/sheet/plastitaniumglass/New(loc, amount) + recipes = GLOB.plastitaniumglass_recipes + ..() diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index e7c66ede217..d79c31467bf 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -1,242 +1,242 @@ -/obj/item/stack/sheet/animalhide - name = "hide" - desc = "Something went wrong." - origin_tech = "biotech=3" - -/obj/item/stack/sheet/animalhide/human - name = "human skin" - desc = "The by-product of human farming." - singular_name = "human skin piece" - icon_state = "sheet-hide" - -var/global/list/datum/stack_recipe/human_recipes = list( \ - new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/bloated_human, 5, on_floor = 1), \ - new/datum/stack_recipe("bloated human costume head", /obj/item/clothing/head/human_head, 5, on_floor = 1), \ - ) - -/obj/item/stack/sheet/animalhide/human/New(var/loc, var/amount=null) - recipes = human_recipes - return ..() - -/obj/item/stack/sheet/animalhide/generic - name = "generic skin" - desc = "A piece of generic skin." - singular_name = "generic skin piece" - icon_state = "sheet-hide" - -/obj/item/stack/sheet/animalhide/corgi - name = "corgi hide" - desc = "The by-product of corgi farming." - singular_name = "corgi hide piece" - icon_state = "sheet-corgi" - -/obj/item/stack/sheet/animalhide/cat - name = "cat hide" - desc = "The by-product of cat farming." - singular_name = "cat hide piece" - icon_state = "sheet-cat" - -/obj/item/stack/sheet/animalhide/monkey - name = "monkey hide" - desc = "The by-product of monkey farming." - singular_name = "monkey hide piece" - icon_state = "sheet-monkey" - -/obj/item/stack/sheet/animalhide/lizard - name = "lizard skin" - desc = "Sssssss..." - singular_name = "lizard skin piece" - icon_state = "sheet-lizard" - -/obj/item/stack/sheet/animalhide/xeno - name = "alien hide" - desc = "The skin of a terrible creature." - singular_name = "alien hide piece" - icon_state = "sheet-xeno" - -GLOBAL_LIST_INIT(xeno_recipes, list ( - new/datum/stack_recipe("alien helmet", /obj/item/clothing/head/xenos, 1), - new/datum/stack_recipe("alien suit", /obj/item/clothing/suit/xenos, 2))) - -/obj/item/stack/sheet/animalhide/xeno/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.xeno_recipes - return ..() - -//don't see anywhere else to put these, maybe together they could be used to make the xenos suit? -/obj/item/stack/sheet/xenochitin - name = "alien chitin" - desc = "A piece of the hide of a terrible creature." - singular_name = "alien hide piece" - icon = 'icons/mob/alien.dmi' - icon_state = "chitin" - origin_tech = "" - -/obj/item/xenos_claw - name = "alien claw" - desc = "The claw of a terrible creature." - icon = 'icons/mob/alien.dmi' - icon_state = "claw" - origin_tech = "" - -/obj/item/weed_extract - name = "weed extract" - desc = "A piece of slimy, purplish weed." - icon = 'icons/mob/alien.dmi' - icon_state = "weed_extract" - origin_tech = "" - -/obj/item/stack/sheet/hairlesshide - name = "hairless hide" - desc = "This hide was stripped of it's hair, but still needs tanning." - singular_name = "hairless hide piece" - icon_state = "sheet-hairlesshide" - origin_tech = "" - -/obj/item/stack/sheet/wetleather - name = "wet leather" - desc = "This leather has been cleaned but still needs to be dried." - singular_name = "wet leather piece" - icon_state = "sheet-wetleather" - origin_tech = "" - var/wetness = 30 //Reduced when exposed to high temperautres - var/drying_threshold_temperature = 500 //Kelvin to start drying - -/obj/item/stack/sheet/leather - name = "leather" - desc = "The by-product of mob grinding." - singular_name = "leather piece" - icon_state = "sheet-leather" - origin_tech = "materials=2" - -GLOBAL_LIST_INIT(leather_recipes, list ( - new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1), - new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2), - new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3), - new/datum/stack_recipe("toolbelt", /obj/item/storage/belt/utility, 4), - new/datum/stack_recipe("leather satchel", /obj/item/storage/backpack/satchel, 5), - new/datum/stack_recipe("bandolier", /obj/item/storage/belt/bandolier, 5), - new/datum/stack_recipe("leather jacket", /obj/item/clothing/suit/jacket/leather, 7), - new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2), - new/datum/stack_recipe("leather overcoat", /obj/item/clothing/suit/jacket/leather/overcoat, 10), - new/datum/stack_recipe("hide mantle", /obj/item/clothing/suit/unathi/mantle, 4))) - -/obj/item/stack/sheet/leather/New(loc, new_amount, merge = TRUE) - recipes = GLOB.leather_recipes - return ..() - -/obj/item/stack/sheet/sinew - name = "watcher sinew" - icon = 'icons/obj/mining.dmi' - desc = "Long stringy filaments which presumably came from a watcher's wings." - singular_name = "watcher sinew" - icon_state = "sinew" - origin_tech = "biotech=4" - -var/global/list/datum/stack_recipe/sinew_recipes = list ( \ - new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/sinew, 1, on_floor = 1), \ - ) - -/obj/item/stack/sheet/sinew/New(var/loc, var/amount=null) - recipes = sinew_recipes - return ..() - -/obj/item/stack/sheet/animalhide/goliath_hide - name = "goliath hide plates" - desc = "Pieces of a goliath's rocky hide, these might be able to make your suit a bit more durable to attack from the local fauna." - icon = 'icons/obj/mining.dmi' - icon_state = "goliath_hide" - singular_name = "hide plate" - flags = NOBLUDGEON - w_class = WEIGHT_CLASS_NORMAL - layer = MOB_LAYER - var/static/list/goliath_platable_armor_typecache = typecacheof(list( - /obj/item/clothing/suit/space/hardsuit/mining, - /obj/item/clothing/head/helmet/space/hardsuit/mining, - /obj/item/clothing/suit/hooded/explorer, - /obj/item/clothing/head/hooded/explorer, - /obj/item/clothing/head/helmet/space/plasmaman/mining)) - -/obj/item/stack/sheet/animalhide/goliath_hide/afterattack(atom/target, mob/user, proximity_flag) - if(!proximity_flag) - return - if(is_type_in_typecache(target, goliath_platable_armor_typecache)) - var/obj/item/clothing/C = target - var/list/current_armor = C.armor - if(current_armor["melee"] < 60) - current_armor["melee"] = min(current_armor["melee"] + 10, 60) - to_chat(user, "You strengthen [target], improving its resistance against melee attacks.") - use(1) - else - to_chat(user, "You can't improve [C] any further!") - else if(istype(target, /obj/mecha/working/ripley)) - var/obj/mecha/working/ripley/D = target - if(D.hides < 3) - D.hides++ - D.armor["melee"] = min(D.armor["melee"] + 10, 70) - D.armor["bullet"] = min(D.armor["bullet"] + 5, 50) - D.armor["laser"] = min(D.armor["laser"] + 5, 50) - to_chat(user, "You strengthen [target], improving its resistance against melee attacks.") - D.update_icon() - if(D.hides == 3) - D.desc = "Autonomous Power Loader Unit. It's wearing a fearsome carapace entirely composed of goliath hide plates - its pilot must be an experienced monster hunter." - else - D.desc = "Autonomous Power Loader Unit. Its armour is enhanced with some goliath hide plates." - use(1) - else - to_chat(user, "You can't improve [D] any further!") - -/obj/item/stack/sheet/animalhide/ashdrake - name = "ash drake hide" - desc = "The strong, scaled hide of an ash drake." - icon = 'icons/obj/mining.dmi' - icon_state = "dragon_hide" - singular_name = "drake plate" - flags = NOBLUDGEON - w_class = WEIGHT_CLASS_NORMAL - layer = MOB_LAYER - -//Step one - dehairing. - -/obj/item/stack/sheet/animalhide/attackby(obj/item/W as obj, mob/user as mob, params) - if(W.sharp) - user.visible_message("[user] starts cutting hair off \the [src].", "You start cutting the hair off \the [src]...", "You hear the sound of a knife rubbing against flesh.") - if(do_after(user, 50 * W.toolspeed, target = src)) - to_chat(user, "You cut the hair from this [src.singular_name].") - //Try locating an exisitng stack on the tile and add to there if possible - for(var/obj/item/stack/sheet/hairlesshide/HS in usr.loc) - if(HS.amount < 50) - HS.amount++ - src.use(1) - break - //If it gets to here it means it did not find a suitable stack on the tile. - var/obj/item/stack/sheet/hairlesshide/HS = new(usr.loc) - HS.amount = 1 - src.use(1) - else - ..() - -//Step two - washing (also handled by water reagent code and washing machine code) -/obj/item/stack/sheet/hairlesshide/water_act(volume, temperature, source, method = REAGENT_TOUCH) - . = ..() - if(volume >= 10) - new /obj/item/stack/sheet/wetleather(get_turf(src), amount) - qdel(src) - -//Step three - drying -/obj/item/stack/sheet/wetleather/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature >= drying_threshold_temperature) - wetness-- - if(wetness == 0) - //Try locating an exisitng stack on the tile and add to there if possible - for(var/obj/item/stack/sheet/leather/HS in src.loc) - if(HS.amount < 50) - HS.amount++ - src.use(1) - wetness = initial(wetness) - break - //If it gets to here it means it did not find a suitable stack on the tile. - var/obj/item/stack/sheet/leather/HS = new(src.loc) - HS.amount = 1 - wetness = initial(wetness) - src.use(1) +/obj/item/stack/sheet/animalhide + name = "hide" + desc = "Something went wrong." + origin_tech = "biotech=3" + +/obj/item/stack/sheet/animalhide/human + name = "human skin" + desc = "The by-product of human farming." + singular_name = "human skin piece" + icon_state = "sheet-hide" + +var/global/list/datum/stack_recipe/human_recipes = list( \ + new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/bloated_human, 5, on_floor = 1), \ + new/datum/stack_recipe("bloated human costume head", /obj/item/clothing/head/human_head, 5, on_floor = 1), \ + ) + +/obj/item/stack/sheet/animalhide/human/New(var/loc, var/amount=null) + recipes = human_recipes + return ..() + +/obj/item/stack/sheet/animalhide/generic + name = "generic skin" + desc = "A piece of generic skin." + singular_name = "generic skin piece" + icon_state = "sheet-hide" + +/obj/item/stack/sheet/animalhide/corgi + name = "corgi hide" + desc = "The by-product of corgi farming." + singular_name = "corgi hide piece" + icon_state = "sheet-corgi" + +/obj/item/stack/sheet/animalhide/cat + name = "cat hide" + desc = "The by-product of cat farming." + singular_name = "cat hide piece" + icon_state = "sheet-cat" + +/obj/item/stack/sheet/animalhide/monkey + name = "monkey hide" + desc = "The by-product of monkey farming." + singular_name = "monkey hide piece" + icon_state = "sheet-monkey" + +/obj/item/stack/sheet/animalhide/lizard + name = "lizard skin" + desc = "Sssssss..." + singular_name = "lizard skin piece" + icon_state = "sheet-lizard" + +/obj/item/stack/sheet/animalhide/xeno + name = "alien hide" + desc = "The skin of a terrible creature." + singular_name = "alien hide piece" + icon_state = "sheet-xeno" + +GLOBAL_LIST_INIT(xeno_recipes, list ( + new/datum/stack_recipe("alien helmet", /obj/item/clothing/head/xenos, 1), + new/datum/stack_recipe("alien suit", /obj/item/clothing/suit/xenos, 2))) + +/obj/item/stack/sheet/animalhide/xeno/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.xeno_recipes + return ..() + +//don't see anywhere else to put these, maybe together they could be used to make the xenos suit? +/obj/item/stack/sheet/xenochitin + name = "alien chitin" + desc = "A piece of the hide of a terrible creature." + singular_name = "alien hide piece" + icon = 'icons/mob/alien.dmi' + icon_state = "chitin" + origin_tech = "" + +/obj/item/xenos_claw + name = "alien claw" + desc = "The claw of a terrible creature." + icon = 'icons/mob/alien.dmi' + icon_state = "claw" + origin_tech = "" + +/obj/item/weed_extract + name = "weed extract" + desc = "A piece of slimy, purplish weed." + icon = 'icons/mob/alien.dmi' + icon_state = "weed_extract" + origin_tech = "" + +/obj/item/stack/sheet/hairlesshide + name = "hairless hide" + desc = "This hide was stripped of it's hair, but still needs tanning." + singular_name = "hairless hide piece" + icon_state = "sheet-hairlesshide" + origin_tech = "" + +/obj/item/stack/sheet/wetleather + name = "wet leather" + desc = "This leather has been cleaned but still needs to be dried." + singular_name = "wet leather piece" + icon_state = "sheet-wetleather" + origin_tech = "" + var/wetness = 30 //Reduced when exposed to high temperautres + var/drying_threshold_temperature = 500 //Kelvin to start drying + +/obj/item/stack/sheet/leather + name = "leather" + desc = "The by-product of mob grinding." + singular_name = "leather piece" + icon_state = "sheet-leather" + origin_tech = "materials=2" + +GLOBAL_LIST_INIT(leather_recipes, list ( + new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1), + new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2), + new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3), + new/datum/stack_recipe("toolbelt", /obj/item/storage/belt/utility, 4), + new/datum/stack_recipe("leather satchel", /obj/item/storage/backpack/satchel, 5), + new/datum/stack_recipe("bandolier", /obj/item/storage/belt/bandolier, 5), + new/datum/stack_recipe("leather jacket", /obj/item/clothing/suit/jacket/leather, 7), + new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2), + new/datum/stack_recipe("leather overcoat", /obj/item/clothing/suit/jacket/leather/overcoat, 10), + new/datum/stack_recipe("hide mantle", /obj/item/clothing/suit/unathi/mantle, 4))) + +/obj/item/stack/sheet/leather/New(loc, new_amount, merge = TRUE) + recipes = GLOB.leather_recipes + return ..() + +/obj/item/stack/sheet/sinew + name = "watcher sinew" + icon = 'icons/obj/mining.dmi' + desc = "Long stringy filaments which presumably came from a watcher's wings." + singular_name = "watcher sinew" + icon_state = "sinew" + origin_tech = "biotech=4" + +var/global/list/datum/stack_recipe/sinew_recipes = list ( \ + new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/sinew, 1, on_floor = 1), \ + ) + +/obj/item/stack/sheet/sinew/New(var/loc, var/amount=null) + recipes = sinew_recipes + return ..() + +/obj/item/stack/sheet/animalhide/goliath_hide + name = "goliath hide plates" + desc = "Pieces of a goliath's rocky hide, these might be able to make your suit a bit more durable to attack from the local fauna." + icon = 'icons/obj/mining.dmi' + icon_state = "goliath_hide" + singular_name = "hide plate" + flags = NOBLUDGEON + w_class = WEIGHT_CLASS_NORMAL + layer = MOB_LAYER + var/static/list/goliath_platable_armor_typecache = typecacheof(list( + /obj/item/clothing/suit/space/hardsuit/mining, + /obj/item/clothing/head/helmet/space/hardsuit/mining, + /obj/item/clothing/suit/hooded/explorer, + /obj/item/clothing/head/hooded/explorer, + /obj/item/clothing/head/helmet/space/plasmaman/mining)) + +/obj/item/stack/sheet/animalhide/goliath_hide/afterattack(atom/target, mob/user, proximity_flag) + if(!proximity_flag) + return + if(is_type_in_typecache(target, goliath_platable_armor_typecache)) + var/obj/item/clothing/C = target + var/list/current_armor = C.armor + if(current_armor["melee"] < 60) + current_armor["melee"] = min(current_armor["melee"] + 10, 60) + to_chat(user, "You strengthen [target], improving its resistance against melee attacks.") + use(1) + else + to_chat(user, "You can't improve [C] any further!") + else if(istype(target, /obj/mecha/working/ripley)) + var/obj/mecha/working/ripley/D = target + if(D.hides < 3) + D.hides++ + D.armor["melee"] = min(D.armor["melee"] + 10, 70) + D.armor["bullet"] = min(D.armor["bullet"] + 5, 50) + D.armor["laser"] = min(D.armor["laser"] + 5, 50) + to_chat(user, "You strengthen [target], improving its resistance against melee attacks.") + D.update_icon() + if(D.hides == 3) + D.desc = "Autonomous Power Loader Unit. It's wearing a fearsome carapace entirely composed of goliath hide plates - its pilot must be an experienced monster hunter." + else + D.desc = "Autonomous Power Loader Unit. Its armour is enhanced with some goliath hide plates." + use(1) + else + to_chat(user, "You can't improve [D] any further!") + +/obj/item/stack/sheet/animalhide/ashdrake + name = "ash drake hide" + desc = "The strong, scaled hide of an ash drake." + icon = 'icons/obj/mining.dmi' + icon_state = "dragon_hide" + singular_name = "drake plate" + flags = NOBLUDGEON + w_class = WEIGHT_CLASS_NORMAL + layer = MOB_LAYER + +//Step one - dehairing. + +/obj/item/stack/sheet/animalhide/attackby(obj/item/W as obj, mob/user as mob, params) + if(W.sharp) + user.visible_message("[user] starts cutting hair off \the [src].", "You start cutting the hair off \the [src]...", "You hear the sound of a knife rubbing against flesh.") + if(do_after(user, 50 * W.toolspeed, target = src)) + to_chat(user, "You cut the hair from this [src.singular_name].") + //Try locating an exisitng stack on the tile and add to there if possible + for(var/obj/item/stack/sheet/hairlesshide/HS in usr.loc) + if(HS.amount < 50) + HS.amount++ + src.use(1) + break + //If it gets to here it means it did not find a suitable stack on the tile. + var/obj/item/stack/sheet/hairlesshide/HS = new(usr.loc) + HS.amount = 1 + src.use(1) + else + ..() + +//Step two - washing (also handled by water reagent code and washing machine code) +/obj/item/stack/sheet/hairlesshide/water_act(volume, temperature, source, method = REAGENT_TOUCH) + . = ..() + if(volume >= 10) + new /obj/item/stack/sheet/wetleather(get_turf(src), amount) + qdel(src) + +//Step three - drying +/obj/item/stack/sheet/wetleather/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature >= drying_threshold_temperature) + wetness-- + if(wetness == 0) + //Try locating an exisitng stack on the tile and add to there if possible + for(var/obj/item/stack/sheet/leather/HS in src.loc) + if(HS.amount < 50) + HS.amount++ + src.use(1) + wetness = initial(wetness) + break + //If it gets to here it means it did not find a suitable stack on the tile. + var/obj/item/stack/sheet/leather/HS = new(src.loc) + HS.amount = 1 + wetness = initial(wetness) + src.use(1) diff --git a/code/game/objects/items/stacks/sheets/light.dm b/code/game/objects/items/stacks/sheets/light.dm index 9e914671b45..17cf8bc5001 100644 --- a/code/game/objects/items/stacks/sheets/light.dm +++ b/code/game/objects/items/stacks/sheets/light.dm @@ -1,37 +1,37 @@ -/obj/item/stack/light_w - name = "wired glass tiles" - gender = PLURAL - singular_name = "wired glass floor tile" - desc = "A glass tile, which is wired, somehow." - icon = 'icons/obj/tiles.dmi' - icon_state = "glass_wire" - w_class = WEIGHT_CLASS_NORMAL - force = 3.0 - throwforce = 5.0 - throw_speed = 5 - throw_range = 20 - flags = CONDUCT - max_amount = 60 - -/obj/item/stack/light_w/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - ..() - if(istype(O,/obj/item/wirecutters)) - var/obj/item/stack/cable_coil/CC = new/obj/item/stack/cable_coil(user.loc) - CC.amount = 5 - amount-- - new/obj/item/stack/sheet/glass(user.loc) - if(amount <= 0) - user.unEquip(src, 1) - qdel(src) - - if(istype(O,/obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = O - M.amount-- - if(M.amount <= 0) - user.unEquip(src, 1) - qdel(M) - amount-- - new/obj/item/stack/tile/light(user.loc) - if(amount <= 0) - user.unEquip(src, 1) - qdel(src) +/obj/item/stack/light_w + name = "wired glass tiles" + gender = PLURAL + singular_name = "wired glass floor tile" + desc = "A glass tile, which is wired, somehow." + icon = 'icons/obj/tiles.dmi' + icon_state = "glass_wire" + w_class = WEIGHT_CLASS_NORMAL + force = 3.0 + throwforce = 5.0 + throw_speed = 5 + throw_range = 20 + flags = CONDUCT + max_amount = 60 + +/obj/item/stack/light_w/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + ..() + if(istype(O,/obj/item/wirecutters)) + var/obj/item/stack/cable_coil/CC = new/obj/item/stack/cable_coil(user.loc) + CC.amount = 5 + amount-- + new/obj/item/stack/sheet/glass(user.loc) + if(amount <= 0) + user.unEquip(src, 1) + qdel(src) + + if(istype(O,/obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = O + M.amount-- + if(M.amount <= 0) + user.unEquip(src, 1) + qdel(M) + amount-- + new/obj/item/stack/tile/light(user.loc) + if(amount <= 0) + user.unEquip(src, 1) + qdel(src) diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm index 763fa960f77..0791bb760dc 100644 --- a/code/game/objects/items/stacks/sheets/mineral.dm +++ b/code/game/objects/items/stacks/sheets/mineral.dm @@ -1,389 +1,389 @@ -/* -Mineral Sheets - Contains: - - Sandstone - - Diamond - - Uranium - - Plasma - - Gold - - Silver - - Bananium - - Tranqillite - - Enriched Uranium - - Platinum - - Alien Alloy - - Adamantine -*/ - -var/global/list/datum/stack_recipe/sandstone_recipes = list ( \ - new/datum/stack_recipe("pile of dirt", /obj/machinery/hydroponics/soil, 3, time = 10, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("sandstone door", /obj/structure/mineral_door/sandstone, 10, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("Assistant Statue", /obj/structure/statue/sandstone/assistant, 5, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("Breakdown into sand", /obj/item/stack/ore/glass, 1, one_per_turf = 0, on_floor = 1), \ - ) - -var/global/list/datum/stack_recipe/silver_recipes = list ( \ - new/datum/stack_recipe("silver door", /obj/structure/mineral_door/silver, 10, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("silver tile", /obj/item/stack/tile/mineral/silver, 1, 4, 20), \ - null, \ - new/datum/stack_recipe("Janitor Statue", /obj/structure/statue/silver/janitor, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("Sec Officer Statue", /obj/structure/statue/silver/sec, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("Sec Borg Statue", /obj/structure/statue/silver/secborg, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("Med Doctor Statue", /obj/structure/statue/silver/md, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("Med Borg Statue", /obj/structure/statue/silver/medborg, 5, one_per_turf = 1, on_floor = 1), \ - ) - -var/global/list/datum/stack_recipe/diamond_recipes = list ( \ - new/datum/stack_recipe("diamond door", /obj/structure/mineral_door/transparent/diamond, 10, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("diamond tile", /obj/item/stack/tile/mineral/diamond, 1, 4, 20), \ - null, \ - new/datum/stack_recipe("Captain Statue", /obj/structure/statue/diamond/captain, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("AI Hologram Statue", /obj/structure/statue/diamond/ai1, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("AI Core Statue", /obj/structure/statue/diamond/ai2, 5, one_per_turf = 1, on_floor = 1), \ - ) - -var/global/list/datum/stack_recipe/uranium_recipes = list ( \ - new/datum/stack_recipe("uranium door", /obj/structure/mineral_door/uranium, 10, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("uranium tile", /obj/item/stack/tile/mineral/uranium, 1, 4, 20), \ - null, \ - new/datum/stack_recipe("Nuke Statue", /obj/structure/statue/uranium/nuke, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("Engineer Statue", /obj/structure/statue/uranium/eng, 5, one_per_turf = 1, on_floor = 1), \ - ) - -var/global/list/datum/stack_recipe/gold_recipes = list ( \ - new/datum/stack_recipe("golden door", /obj/structure/mineral_door/gold, 10, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("gold tile", /obj/item/stack/tile/mineral/gold, 1, 4, 20), \ - null, \ - new/datum/stack_recipe("HoS Statue", /obj/structure/statue/gold/hos, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("HoP Statue", /obj/structure/statue/gold/hop, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("CE Statue", /obj/structure/statue/gold/ce, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("RD Statue", /obj/structure/statue/gold/rd, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("CMO Statue", /obj/structure/statue/gold/cmo, 5, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("Simple Crown", /obj/item/clothing/head/crown, 5), \ - ) - -var/global/list/datum/stack_recipe/plasma_recipes = list ( \ - new/datum/stack_recipe/dangerous("plasma door", /obj/structure/mineral_door/transparent/plasma, 10, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe/dangerous("plasma tile", /obj/item/stack/tile/mineral/plasma, 1, 4, 20), \ - null, \ - new/datum/stack_recipe/dangerous("Scientist Statue", /obj/structure/statue/plasma/scientist, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe/dangerous("Xenomorph Statue", /obj/structure/statue/plasma/xeno, 5, one_per_turf = 1, on_floor = 1), \ - ) - -var/global/list/datum/stack_recipe/bananium_recipes = list ( \ - new/datum/stack_recipe("bananium tile", /obj/item/stack/tile/mineral/bananium, 1, 4, 20), \ - null, \ - new/datum/stack_recipe("Clown Statue", /obj/structure/statue/bananium/clown, 5, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("bananium computer frame", /obj/structure/computerframe/HONKputer, 50, time = 25, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("bananium grenade casing", /obj/item/grenade/bananade/casing, 4, on_floor = 1), \ - ) - -var/global/list/datum/stack_recipe/tranquillite_recipes = list ( \ - new/datum/stack_recipe("invisible wall", /obj/structure/barricade/mime, 5, one_per_turf = 1, on_floor = 1, time = 50), \ - null, \ - new/datum/stack_recipe("silent tile", /obj/item/stack/tile/mineral/tranquillite, 1, 4, 20), \ - null, \ - new/datum/stack_recipe("Mime Statue", /obj/structure/statue/tranquillite/mime, 5, one_per_turf = 1, on_floor = 1), \ - ) - -var/global/list/datum/stack_recipe/abductor_recipes = list ( \ - new/datum/stack_recipe("alien bed", /obj/structure/bed/abductor, 2, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("alien locker", /obj/structure/closet/abductor, 1, time = 15, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("alien table frame", /obj/structure/table_frame/abductor, 1, time = 15, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("alien airlock assembly", /obj/structure/door_assembly/door_assembly_abductor, 4, time = 20, one_per_turf = 1, on_floor = 1), \ - null, \ - new/datum/stack_recipe("alien floor tile", /obj/item/stack/tile/mineral/abductor, 1, 4, 20), \ - ) - -var/global/list/datum/stack_recipe/adamantine_recipes = list( - new /datum/stack_recipe("incomplete servant golem shell", /obj/item/golem_shell/servant, req_amount = 1, res_amount = 1), \ - ) - -var/global/list/datum/stack_recipe/snow_recipes = list( - new/datum/stack_recipe("snowman", /obj/structure/snowman, 5, one_per_turf = 1, on_floor = 1), \ - new/datum/stack_recipe("Snowball", /obj/item/snowball, 1) - ) - -/obj/item/stack/sheet/mineral - force = 5 - throwforce = 5 - throw_speed = 3 - -/obj/item/stack/sheet/mineral/New() - ..() - pixel_x = rand(0,4)-4 - pixel_y = rand(0,4)-4 - -/obj/item/stack/sheet/mineral/sandstone - name = "sandstone brick" - desc = "This appears to be a combination of both sand and stone." - singular_name = "sandstone brick" - icon_state = "sheet-sandstone" - throw_range = 5 - origin_tech = "materials=1" - sheettype = "sandstone" - materials = list(MAT_GLASS=MINERAL_MATERIAL_AMOUNT) - -/obj/item/stack/sheet/mineral/sandstone/New() - ..() - recipes = sandstone_recipes - -/* - * Sandbags - */ - -/obj/item/stack/sheet/mineral/sandbags - name = "sandbags" - icon_state = "sandbags" - singular_name = "sandbag" - layer = LOW_ITEM_LAYER - merge_type = /obj/item/stack/sheet/mineral/sandbags - -GLOBAL_LIST_INIT(sandbag_recipes, list ( \ - new/datum/stack_recipe("sandbags", /obj/structure/barricade/sandbags, 1, time = 25, one_per_turf = 1, on_floor = 1), \ - )) - -/obj/item/stack/sheet/mineral/sandbags/New() - recipes = GLOB.sandbag_recipes - ..() - -/obj/item/emptysandbag - name = "empty sandbag" - desc = "A bag to be filled with sand." - icon = 'icons/obj/items.dmi' - icon_state = "sandbag" - w_class = WEIGHT_CLASS_TINY - -/obj/item/emptysandbag/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/stack/ore/glass)) - var/obj/item/stack/ore/glass/G = I - to_chat(user, "You fill the sandbag.") - var/obj/item/stack/sheet/mineral/sandbags/S = new /obj/item/stack/sheet/mineral/sandbags(drop_location()) - qdel(src) - if(Adjacent(user) && !issilicon(user)) - user.put_in_hands(S) - G.use(1) - else - return ..() - -/obj/item/stack/sheet/mineral/diamond - name = "diamond" - icon_state = "sheet-diamond" - singular_name = "diamond" - origin_tech = "materials=6" - sheettype = "diamond" - materials = list(MAT_DIAMOND=MINERAL_MATERIAL_AMOUNT) - point_value = 25 - -/obj/item/stack/sheet/mineral/diamond/New() - ..() - recipes = diamond_recipes - -/obj/item/stack/sheet/mineral/uranium - name = "uranium" - icon_state = "sheet-uranium" - singular_name = "uranium sheet" - origin_tech = "materials=5" - sheettype = "uranium" - materials = list(MAT_URANIUM=MINERAL_MATERIAL_AMOUNT) - point_value = 20 - -/obj/item/stack/sheet/mineral/uranium/New() - ..() - recipes = uranium_recipes - -/obj/item/stack/sheet/mineral/plasma - name = "solid plasma" - icon_state = "sheet-plasma" - singular_name = "plasma sheet" - origin_tech = "plasmatech=2;materials=2" - sheettype = "plasma" - materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT) - resistance_flags = FLAMMABLE - max_integrity = 100 - point_value = 20 - -/obj/item/stack/sheet/mineral/plasma/New() - ..() - recipes = plasma_recipes - -/obj/item/stack/sheet/mineral/plasma/welder_act(mob/user, obj/item/I) - if(I.use_tool(src, user, volume = I.tool_volume)) - message_admins("Plasma sheets ignited by [key_name_admin(user)]([ADMIN_QUE(user,"?")]) ([ADMIN_FLW(user,"FLW")]) in ([x],[y],[z] - JMP)",0,1) - log_game("Plasma sheets ignited by [key_name(user)] in ([x],[y],[z])") - investigate_log("was ignited by [key_name(user)]","atmos") - fire_act() - -/obj/item/stack/sheet/mineral/plasma/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - ..() - atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, amount*10) - qdel(src) - -/obj/item/stack/sheet/mineral/gold - name = "gold" - icon_state = "sheet-gold" - singular_name = "gold bar" - origin_tech = "materials=4" - sheettype = "gold" - materials = list(MAT_GOLD=MINERAL_MATERIAL_AMOUNT) - point_value = 20 - -/obj/item/stack/sheet/mineral/gold/New() - ..() - recipes = gold_recipes - -/obj/item/stack/sheet/mineral/silver - name = "silver" - icon_state = "sheet-silver" - singular_name = "silver bar" - origin_tech = "materials=4" - sheettype = "silver" - materials = list(MAT_SILVER=MINERAL_MATERIAL_AMOUNT) - point_value = 20 - -/obj/item/stack/sheet/mineral/silver/New() - ..() - recipes = silver_recipes - -/obj/item/stack/sheet/mineral/bananium - name = "bananium" - icon_state = "sheet-clown" - singular_name = "bananium sheet" - origin_tech = "materials=4" - sheettype = "bananium" - materials = list(MAT_BANANIUM=MINERAL_MATERIAL_AMOUNT) - point_value = 50 - -/obj/item/stack/sheet/mineral/bananium/New(loc, amount=null) - ..() - recipes = bananium_recipes - -/obj/item/stack/sheet/mineral/tranquillite - name = "tranquillite" - icon_state = "sheet-mime" - singular_name = "beret" - origin_tech = "materials=4" - sheettype = "tranquillite" - materials = list(MAT_TRANQUILLITE=MINERAL_MATERIAL_AMOUNT) - wall_allowed = FALSE //no tranquilite walls in code - point_value = 50 - -/obj/item/stack/sheet/mineral/tranquillite/New(loc, amount=null) - ..() - recipes = tranquillite_recipes - -/* - * Titanium - */ -/obj/item/stack/sheet/mineral/titanium - name = "titanium" - icon_state = "sheet-titanium" - item_state = "sheet-metal" - singular_name = "titanium sheet" - force = 5 - throwforce = 5 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 1 - throw_range = 3 - sheettype = "titanium" - materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT) - point_value = 20 - -var/global/list/datum/stack_recipe/titanium_recipes = list ( - new/datum/stack_recipe("titanium tile", /obj/item/stack/tile/mineral/titanium, 1, 4, 20), - new/datum/stack_recipe("surgical tray", /obj/structure/table/tray, 2, one_per_turf = 1, on_floor = 1), - ) - -/obj/item/stack/sheet/mineral/titanium/New(loc, amount=null) - recipes = titanium_recipes - ..() - -/obj/item/stack/sheet/mineral/titanium/fifty - amount = 50 - - -/* - * Plastitanium - */ -/obj/item/stack/sheet/mineral/plastitanium - name = "plastitanium" - icon_state = "sheet-plastitanium" - item_state = "sheet-metal" - singular_name = "plastitanium sheet" - force = 5 - throwforce = 5 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 1 - throw_range = 3 - sheettype = "plastitanium" - materials = list(MAT_TITANIUM=2000, MAT_PLASMA=2000) - point_value = 45 - -var/global/list/datum/stack_recipe/plastitanium_recipes = list ( - new/datum/stack_recipe("plas-titanium tile", /obj/item/stack/tile/mineral/plastitanium, 1, 4, 20), - ) - -/obj/item/stack/sheet/mineral/plastitanium/New(loc, amount=null) - recipes = plastitanium_recipes - ..() - -/obj/item/stack/sheet/mineral/enruranium - name = "enriched uranium" - icon_state = "sheet-enruranium" - origin_tech = "materials=6" - materials = list(MAT_URANIUM=3000) - -//Alien Alloy -/obj/item/stack/sheet/mineral/abductor - name = "alien alloy" - icon = 'icons/obj/abductor.dmi' - icon_state = "sheet-abductor" - singular_name = "alien alloy sheet" - force = 5 - throwforce = 5 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 1 - origin_tech = "materials=6;abductor=1" - sheettype = "abductor" - -/obj/item/stack/sheet/mineral/abductor/New(loc, amount=null) - recipes = abductor_recipes - ..() - -/obj/item/stack/sheet/mineral/adamantine - name = "adamantine" - desc = "A strange mineral used in the construction of sentient golems." - icon_state = "sheet-adamantine" - singular_name = "adamantine sheet" - origin_tech = "materials=5" - merge_type = /obj/item/stack/sheet/mineral/adamantine - wall_allowed = FALSE - -/obj/item/stack/sheet/mineral/adamantine/New(loc, amount = null) - recipes = adamantine_recipes - ..() - -/* - * Snow - */ -/obj/item/stack/sheet/mineral/snow - name = "snow" - icon_state = "sheet-snow" - item_state = "sheet-snow" - singular_name = "snow block" - force = 1 - throwforce = 2 - merge_type = /obj/item/stack/sheet/mineral/snow - -/obj/item/stack/sheet/mineral/snow/New(loc, amount = null) - recipes = snow_recipes - ..() +/* +Mineral Sheets + Contains: + - Sandstone + - Diamond + - Uranium + - Plasma + - Gold + - Silver + - Bananium + - Tranqillite + - Enriched Uranium + - Platinum + - Alien Alloy + - Adamantine +*/ + +var/global/list/datum/stack_recipe/sandstone_recipes = list ( \ + new/datum/stack_recipe("pile of dirt", /obj/machinery/hydroponics/soil, 3, time = 10, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("sandstone door", /obj/structure/mineral_door/sandstone, 10, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("Assistant Statue", /obj/structure/statue/sandstone/assistant, 5, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("Breakdown into sand", /obj/item/stack/ore/glass, 1, one_per_turf = 0, on_floor = 1), \ + ) + +var/global/list/datum/stack_recipe/silver_recipes = list ( \ + new/datum/stack_recipe("silver door", /obj/structure/mineral_door/silver, 10, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("silver tile", /obj/item/stack/tile/mineral/silver, 1, 4, 20), \ + null, \ + new/datum/stack_recipe("Janitor Statue", /obj/structure/statue/silver/janitor, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("Sec Officer Statue", /obj/structure/statue/silver/sec, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("Sec Borg Statue", /obj/structure/statue/silver/secborg, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("Med Doctor Statue", /obj/structure/statue/silver/md, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("Med Borg Statue", /obj/structure/statue/silver/medborg, 5, one_per_turf = 1, on_floor = 1), \ + ) + +var/global/list/datum/stack_recipe/diamond_recipes = list ( \ + new/datum/stack_recipe("diamond door", /obj/structure/mineral_door/transparent/diamond, 10, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("diamond tile", /obj/item/stack/tile/mineral/diamond, 1, 4, 20), \ + null, \ + new/datum/stack_recipe("Captain Statue", /obj/structure/statue/diamond/captain, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("AI Hologram Statue", /obj/structure/statue/diamond/ai1, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("AI Core Statue", /obj/structure/statue/diamond/ai2, 5, one_per_turf = 1, on_floor = 1), \ + ) + +var/global/list/datum/stack_recipe/uranium_recipes = list ( \ + new/datum/stack_recipe("uranium door", /obj/structure/mineral_door/uranium, 10, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("uranium tile", /obj/item/stack/tile/mineral/uranium, 1, 4, 20), \ + null, \ + new/datum/stack_recipe("Nuke Statue", /obj/structure/statue/uranium/nuke, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("Engineer Statue", /obj/structure/statue/uranium/eng, 5, one_per_turf = 1, on_floor = 1), \ + ) + +var/global/list/datum/stack_recipe/gold_recipes = list ( \ + new/datum/stack_recipe("golden door", /obj/structure/mineral_door/gold, 10, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("gold tile", /obj/item/stack/tile/mineral/gold, 1, 4, 20), \ + null, \ + new/datum/stack_recipe("HoS Statue", /obj/structure/statue/gold/hos, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("HoP Statue", /obj/structure/statue/gold/hop, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("CE Statue", /obj/structure/statue/gold/ce, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("RD Statue", /obj/structure/statue/gold/rd, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("CMO Statue", /obj/structure/statue/gold/cmo, 5, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("Simple Crown", /obj/item/clothing/head/crown, 5), \ + ) + +var/global/list/datum/stack_recipe/plasma_recipes = list ( \ + new/datum/stack_recipe/dangerous("plasma door", /obj/structure/mineral_door/transparent/plasma, 10, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe/dangerous("plasma tile", /obj/item/stack/tile/mineral/plasma, 1, 4, 20), \ + null, \ + new/datum/stack_recipe/dangerous("Scientist Statue", /obj/structure/statue/plasma/scientist, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe/dangerous("Xenomorph Statue", /obj/structure/statue/plasma/xeno, 5, one_per_turf = 1, on_floor = 1), \ + ) + +var/global/list/datum/stack_recipe/bananium_recipes = list ( \ + new/datum/stack_recipe("bananium tile", /obj/item/stack/tile/mineral/bananium, 1, 4, 20), \ + null, \ + new/datum/stack_recipe("Clown Statue", /obj/structure/statue/bananium/clown, 5, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("bananium computer frame", /obj/structure/computerframe/HONKputer, 50, time = 25, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("bananium grenade casing", /obj/item/grenade/bananade/casing, 4, on_floor = 1), \ + ) + +var/global/list/datum/stack_recipe/tranquillite_recipes = list ( \ + new/datum/stack_recipe("invisible wall", /obj/structure/barricade/mime, 5, one_per_turf = 1, on_floor = 1, time = 50), \ + null, \ + new/datum/stack_recipe("silent tile", /obj/item/stack/tile/mineral/tranquillite, 1, 4, 20), \ + null, \ + new/datum/stack_recipe("Mime Statue", /obj/structure/statue/tranquillite/mime, 5, one_per_turf = 1, on_floor = 1), \ + ) + +var/global/list/datum/stack_recipe/abductor_recipes = list ( \ + new/datum/stack_recipe("alien bed", /obj/structure/bed/abductor, 2, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("alien locker", /obj/structure/closet/abductor, 1, time = 15, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("alien table frame", /obj/structure/table_frame/abductor, 1, time = 15, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("alien airlock assembly", /obj/structure/door_assembly/door_assembly_abductor, 4, time = 20, one_per_turf = 1, on_floor = 1), \ + null, \ + new/datum/stack_recipe("alien floor tile", /obj/item/stack/tile/mineral/abductor, 1, 4, 20), \ + ) + +var/global/list/datum/stack_recipe/adamantine_recipes = list( + new /datum/stack_recipe("incomplete servant golem shell", /obj/item/golem_shell/servant, req_amount = 1, res_amount = 1), \ + ) + +var/global/list/datum/stack_recipe/snow_recipes = list( + new/datum/stack_recipe("snowman", /obj/structure/snowman, 5, one_per_turf = 1, on_floor = 1), \ + new/datum/stack_recipe("Snowball", /obj/item/snowball, 1) + ) + +/obj/item/stack/sheet/mineral + force = 5 + throwforce = 5 + throw_speed = 3 + +/obj/item/stack/sheet/mineral/New() + ..() + pixel_x = rand(0,4)-4 + pixel_y = rand(0,4)-4 + +/obj/item/stack/sheet/mineral/sandstone + name = "sandstone brick" + desc = "This appears to be a combination of both sand and stone." + singular_name = "sandstone brick" + icon_state = "sheet-sandstone" + throw_range = 5 + origin_tech = "materials=1" + sheettype = "sandstone" + materials = list(MAT_GLASS=MINERAL_MATERIAL_AMOUNT) + +/obj/item/stack/sheet/mineral/sandstone/New() + ..() + recipes = sandstone_recipes + +/* + * Sandbags + */ + +/obj/item/stack/sheet/mineral/sandbags + name = "sandbags" + icon_state = "sandbags" + singular_name = "sandbag" + layer = LOW_ITEM_LAYER + merge_type = /obj/item/stack/sheet/mineral/sandbags + +GLOBAL_LIST_INIT(sandbag_recipes, list ( \ + new/datum/stack_recipe("sandbags", /obj/structure/barricade/sandbags, 1, time = 25, one_per_turf = 1, on_floor = 1), \ + )) + +/obj/item/stack/sheet/mineral/sandbags/New() + recipes = GLOB.sandbag_recipes + ..() + +/obj/item/emptysandbag + name = "empty sandbag" + desc = "A bag to be filled with sand." + icon = 'icons/obj/items.dmi' + icon_state = "sandbag" + w_class = WEIGHT_CLASS_TINY + +/obj/item/emptysandbag/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/stack/ore/glass)) + var/obj/item/stack/ore/glass/G = I + to_chat(user, "You fill the sandbag.") + var/obj/item/stack/sheet/mineral/sandbags/S = new /obj/item/stack/sheet/mineral/sandbags(drop_location()) + qdel(src) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(S) + G.use(1) + else + return ..() + +/obj/item/stack/sheet/mineral/diamond + name = "diamond" + icon_state = "sheet-diamond" + singular_name = "diamond" + origin_tech = "materials=6" + sheettype = "diamond" + materials = list(MAT_DIAMOND=MINERAL_MATERIAL_AMOUNT) + point_value = 25 + +/obj/item/stack/sheet/mineral/diamond/New() + ..() + recipes = diamond_recipes + +/obj/item/stack/sheet/mineral/uranium + name = "uranium" + icon_state = "sheet-uranium" + singular_name = "uranium sheet" + origin_tech = "materials=5" + sheettype = "uranium" + materials = list(MAT_URANIUM=MINERAL_MATERIAL_AMOUNT) + point_value = 20 + +/obj/item/stack/sheet/mineral/uranium/New() + ..() + recipes = uranium_recipes + +/obj/item/stack/sheet/mineral/plasma + name = "solid plasma" + icon_state = "sheet-plasma" + singular_name = "plasma sheet" + origin_tech = "plasmatech=2;materials=2" + sheettype = "plasma" + materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT) + resistance_flags = FLAMMABLE + max_integrity = 100 + point_value = 20 + +/obj/item/stack/sheet/mineral/plasma/New() + ..() + recipes = plasma_recipes + +/obj/item/stack/sheet/mineral/plasma/welder_act(mob/user, obj/item/I) + if(I.use_tool(src, user, volume = I.tool_volume)) + message_admins("Plasma sheets ignited by [key_name_admin(user)]([ADMIN_QUE(user,"?")]) ([ADMIN_FLW(user,"FLW")]) in ([x],[y],[z] - JMP)",0,1) + log_game("Plasma sheets ignited by [key_name(user)] in ([x],[y],[z])") + investigate_log("was ignited by [key_name(user)]","atmos") + fire_act() + +/obj/item/stack/sheet/mineral/plasma/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + ..() + atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, amount*10) + qdel(src) + +/obj/item/stack/sheet/mineral/gold + name = "gold" + icon_state = "sheet-gold" + singular_name = "gold bar" + origin_tech = "materials=4" + sheettype = "gold" + materials = list(MAT_GOLD=MINERAL_MATERIAL_AMOUNT) + point_value = 20 + +/obj/item/stack/sheet/mineral/gold/New() + ..() + recipes = gold_recipes + +/obj/item/stack/sheet/mineral/silver + name = "silver" + icon_state = "sheet-silver" + singular_name = "silver bar" + origin_tech = "materials=4" + sheettype = "silver" + materials = list(MAT_SILVER=MINERAL_MATERIAL_AMOUNT) + point_value = 20 + +/obj/item/stack/sheet/mineral/silver/New() + ..() + recipes = silver_recipes + +/obj/item/stack/sheet/mineral/bananium + name = "bananium" + icon_state = "sheet-clown" + singular_name = "bananium sheet" + origin_tech = "materials=4" + sheettype = "bananium" + materials = list(MAT_BANANIUM=MINERAL_MATERIAL_AMOUNT) + point_value = 50 + +/obj/item/stack/sheet/mineral/bananium/New(loc, amount=null) + ..() + recipes = bananium_recipes + +/obj/item/stack/sheet/mineral/tranquillite + name = "tranquillite" + icon_state = "sheet-mime" + singular_name = "beret" + origin_tech = "materials=4" + sheettype = "tranquillite" + materials = list(MAT_TRANQUILLITE=MINERAL_MATERIAL_AMOUNT) + wall_allowed = FALSE //no tranquilite walls in code + point_value = 50 + +/obj/item/stack/sheet/mineral/tranquillite/New(loc, amount=null) + ..() + recipes = tranquillite_recipes + +/* + * Titanium + */ +/obj/item/stack/sheet/mineral/titanium + name = "titanium" + icon_state = "sheet-titanium" + item_state = "sheet-metal" + singular_name = "titanium sheet" + force = 5 + throwforce = 5 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 1 + throw_range = 3 + sheettype = "titanium" + materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT) + point_value = 20 + +var/global/list/datum/stack_recipe/titanium_recipes = list ( + new/datum/stack_recipe("titanium tile", /obj/item/stack/tile/mineral/titanium, 1, 4, 20), + new/datum/stack_recipe("surgical tray", /obj/structure/table/tray, 2, one_per_turf = 1, on_floor = 1), + ) + +/obj/item/stack/sheet/mineral/titanium/New(loc, amount=null) + recipes = titanium_recipes + ..() + +/obj/item/stack/sheet/mineral/titanium/fifty + amount = 50 + + +/* + * Plastitanium + */ +/obj/item/stack/sheet/mineral/plastitanium + name = "plastitanium" + icon_state = "sheet-plastitanium" + item_state = "sheet-metal" + singular_name = "plastitanium sheet" + force = 5 + throwforce = 5 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 1 + throw_range = 3 + sheettype = "plastitanium" + materials = list(MAT_TITANIUM=2000, MAT_PLASMA=2000) + point_value = 45 + +var/global/list/datum/stack_recipe/plastitanium_recipes = list ( + new/datum/stack_recipe("plas-titanium tile", /obj/item/stack/tile/mineral/plastitanium, 1, 4, 20), + ) + +/obj/item/stack/sheet/mineral/plastitanium/New(loc, amount=null) + recipes = plastitanium_recipes + ..() + +/obj/item/stack/sheet/mineral/enruranium + name = "enriched uranium" + icon_state = "sheet-enruranium" + origin_tech = "materials=6" + materials = list(MAT_URANIUM=3000) + +//Alien Alloy +/obj/item/stack/sheet/mineral/abductor + name = "alien alloy" + icon = 'icons/obj/abductor.dmi' + icon_state = "sheet-abductor" + singular_name = "alien alloy sheet" + force = 5 + throwforce = 5 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 1 + origin_tech = "materials=6;abductor=1" + sheettype = "abductor" + +/obj/item/stack/sheet/mineral/abductor/New(loc, amount=null) + recipes = abductor_recipes + ..() + +/obj/item/stack/sheet/mineral/adamantine + name = "adamantine" + desc = "A strange mineral used in the construction of sentient golems." + icon_state = "sheet-adamantine" + singular_name = "adamantine sheet" + origin_tech = "materials=5" + merge_type = /obj/item/stack/sheet/mineral/adamantine + wall_allowed = FALSE + +/obj/item/stack/sheet/mineral/adamantine/New(loc, amount = null) + recipes = adamantine_recipes + ..() + +/* + * Snow + */ +/obj/item/stack/sheet/mineral/snow + name = "snow" + icon_state = "sheet-snow" + item_state = "sheet-snow" + singular_name = "snow block" + force = 1 + throwforce = 2 + merge_type = /obj/item/stack/sheet/mineral/snow + +/obj/item/stack/sheet/mineral/snow/New(loc, amount = null) + recipes = snow_recipes + ..() diff --git a/code/game/objects/items/stacks/sheets/sheets.dm b/code/game/objects/items/stacks/sheets/sheets.dm index 13774b0db4b..63223a4c6f4 100644 --- a/code/game/objects/items/stacks/sheets/sheets.dm +++ b/code/game/objects/items/stacks/sheets/sheets.dm @@ -1,18 +1,18 @@ -/obj/item/stack/sheet - name = "sheet" - w_class = WEIGHT_CLASS_NORMAL - force = 5 - throwforce = 5 - max_amount = 50 - throw_speed = 1 - throw_range = 3 - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed") - var/perunit = MINERAL_MATERIAL_AMOUNT - var/sheettype = null //this is used for girders in the creation of walls/false walls - var/point_value = 0 //turn-in value for the gulag stacker - loosely relative to its rarity. - - var/created_window = null //apparently glass sheets don't share a base type for glass specifically, so each had to define these vars individually - var/full_window = null //moving the var declaration to here so this can be checked cleaner until someone is willing to make them share a base type properly - usesound = 'sound/items/deconstruct.ogg' - toolspeed = 1 - var/wall_allowed = TRUE //determines if sheet can be used in wall construction or not. +/obj/item/stack/sheet + name = "sheet" + w_class = WEIGHT_CLASS_NORMAL + force = 5 + throwforce = 5 + max_amount = 50 + throw_speed = 1 + throw_range = 3 + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed") + var/perunit = MINERAL_MATERIAL_AMOUNT + var/sheettype = null //this is used for girders in the creation of walls/false walls + var/point_value = 0 //turn-in value for the gulag stacker - loosely relative to its rarity. + + var/created_window = null //apparently glass sheets don't share a base type for glass specifically, so each had to define these vars individually + var/full_window = null //moving the var declaration to here so this can be checked cleaner until someone is willing to make them share a base type properly + usesound = 'sound/items/deconstruct.ogg' + toolspeed = 1 + var/wall_allowed = TRUE //determines if sheet can be used in wall construction or not. diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 2fe83a08398..a4d54f12ab0 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -1,1783 +1,1783 @@ -/* Toys! - * Contains: - * Balloons - * Fake telebeacon - * Fake singularity - * Toy swords - * Toy mechs - * Snap pops - * Water flower - * Toy Nuke - * Card Deck - * Therapy dolls - * Toddler doll - * Inflatable duck - * Foam armblade - * Mini Gibber - * Toy xeno - * Toy chainsaws - * Action Figures - */ - - -/obj/item/toy - throwforce = 0 - throw_speed = 4 - throw_range = 20 - force = 0 - - -/* - * Balloons - */ -/obj/item/toy/balloon - name = "water balloon" - desc = "A translucent balloon. There's nothing in it." - icon = 'icons/obj/toy.dmi' - icon_state = "waterballoon-e" - item_state = "balloon-empty" - -/obj/item/toy/balloon/New() - ..() - create_reagents(10) - -/obj/item/toy/balloon/attack(mob/living/carbon/human/M as mob, mob/user as mob) - return - -/obj/item/toy/balloon/afterattack(atom/A, mob/user, proximity) - if(!proximity) - return - if(istype(A, /obj/structure/reagent_dispensers)) - var/obj/structure/reagent_dispensers/RD = A - if(RD.reagents.total_volume <= 0) - to_chat(user, "[RD] is empty.") - else if(reagents.total_volume >= 10) - to_chat(user, "[src] is full.") - else - A.reagents.trans_to(src, 10) - to_chat(user, "You fill the balloon with the contents of [A].") - desc = "A translucent balloon with some form of liquid sloshing around in it." - update_icon() - -/obj/item/toy/balloon/wash(mob/user, atom/source) - if(reagents.total_volume < 10) - reagents.add_reagent("water", min(10-reagents.total_volume, 10)) - to_chat(user, "You fill the balloon from the [source].") - desc = "A translucent balloon with some form of liquid sloshing around in it." - update_icon() - return - -/obj/item/toy/balloon/attackby(obj/O as obj, mob/user as mob, params) - if(istype(O, /obj/item/reagent_containers/glass) || istype(O, /obj/item/reagent_containers/food/drinks/drinkingglass)) - if(O.reagents) - if(O.reagents.total_volume < 1) - to_chat(user, "The [O] is empty.") - else if(O.reagents.total_volume >= 1) - if(O.reagents.has_reagent("facid", 1)) - to_chat(user, "The acid chews through the balloon!") - O.reagents.reaction(user) - qdel(src) - else - desc = "A translucent balloon with some form of liquid sloshing around in it." - to_chat(user, "You fill the balloon with the contents of [O].") - O.reagents.trans_to(src, 10) - update_icon() - return - -/obj/item/toy/balloon/throw_impact(atom/hit_atom) - if(reagents.total_volume >= 1) - visible_message("The [src] bursts!","You hear a pop and a splash.") - reagents.reaction(get_turf(hit_atom)) - for(var/atom/A in get_turf(hit_atom)) - reagents.reaction(A) - icon_state = "burst" - spawn(5) - if(src) - qdel(src) - return - -/obj/item/toy/balloon/update_icon() - if(src.reagents.total_volume >= 1) - icon_state = "waterballoon" - item_state = "balloon" - else - icon_state = "waterballoon-e" - item_state = "balloon-empty" - -/obj/item/toy/syndicateballoon - name = "syndicate balloon" - desc = "There is a tag on the back that reads \"FUK NT!11!\"." - throwforce = 0 - throw_speed = 4 - throw_range = 20 - force = 0 - icon_state = "syndballoon" - item_state = "syndballoon" - w_class = WEIGHT_CLASS_BULKY - var/lastused = null - -/obj/item/toy/syndicateballoon/attack_self(mob/user) - if(world.time - lastused < CLICK_CD_MELEE) - return - var/playverb = pick("bat [src]", "tug on [src]'s string", "play with [src]") - user.visible_message("[user] plays with [src].", "You [playverb].") - lastused = world.time - -/* - * Fake telebeacon - */ -/obj/item/toy/blink - name = "electronic blink toy game" - desc = "Blink. Blink. Blink. Ages 8 and up." - icon = 'icons/obj/radio.dmi' - icon_state = "beacon" - item_state = "signaler" - -/* - * Fake singularity - */ -/obj/item/toy/spinningtoy - name = "Gravitational Singularity" - desc = "\"Singulo\" brand spinning toy." - icon = 'icons/obj/singularity.dmi' - icon_state = "singularity_s1" - -/* - * Toy swords - */ -/obj/item/toy/sword - name = "toy sword" - desc = "A cheap, plastic replica of an energy sword. Realistic sounds! Ages 8 and up." - icon_state = "sword0" - item_state = "sword0" - var/active = FALSE - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("attacked", "struck", "hit") - -/obj/item/toy/sword/attack_self(mob/user) - active = !active - if(active) - to_chat(user, "You extend the plastic blade with a quick flick of your wrist.") - playsound(user, 'sound/weapons/saberon.ogg', 20, 1) - icon_state = "swordblue" - item_state = "swordblue" - w_class = WEIGHT_CLASS_BULKY - else - to_chat(user, "You push the plastic blade back down into the handle.") - playsound(user, 'sound/weapons/saberoff.ogg', 20, 1) - icon_state = "sword0" - item_state = "sword0" - w_class = WEIGHT_CLASS_SMALL - - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - add_fingerprint(user) - return - -// Copied from /obj/item/melee/energy/sword/attackby -/obj/item/toy/sword/attackby(obj/item/W, mob/living/user, params) - ..() - if(istype(W, /obj/item/toy/sword)) - if(W == src) - to_chat(user, "You try to attach the end of the plastic sword to... itself. You're not very smart, are you?") - if(ishuman(user)) - user.adjustBrainLoss(10) - else if((W.flags & NODROP) || (flags & NODROP)) - to_chat(user, "\the [flags & NODROP ? src : W] is stuck to your hand, you can't attach it to \the [flags & NODROP ? W : src]!") - else - to_chat(user, "You attach the ends of the two plastic swords, making a single double-bladed toy! You're fake-cool.") - new /obj/item/twohanded/dualsaber/toy(user.loc) - user.unEquip(W) - user.unEquip(src) - qdel(W) - qdel(src) - -/* - * Subtype of Double-Bladed Energy Swords - */ -/obj/item/twohanded/dualsaber/toy - name = "double-bladed toy sword" - desc = "A cheap, plastic replica of TWO energy swords. Double the fun!" - force = 0 - throwforce = 0 - throw_speed = 3 - throw_range = 5 - force_unwielded = 0 - force_wielded = 0 - origin_tech = null - attack_verb = list("attacked", "struck", "hit") - brightness_on = 0 - sharp_when_wielded = FALSE // It's a toy - -/obj/item/twohanded/dualsaber/toy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 - -/obj/item/twohanded/dualsaber/toy/IsReflect()//Stops Toy Dualsabers from reflecting energy projectiles - return 0 - -/obj/item/toy/katana - name = "replica katana" - desc = "Woefully underpowered in D20." - icon_state = "katana" - item_state = "katana" - flags = CONDUCT - slot_flags = SLOT_BELT | SLOT_BACK - force = 5 - throwforce = 5 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("attacked", "slashed", "stabbed", "sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - -/obj/item/toy/katana/suicide_act(mob/user) - var/dmsg = pick("[user] tries to stab \the [src] into [user.p_their()] abdomen, but it shatters! [user.p_they(TRUE)] look[user.p_s()] as if [user.p_they()] might die from the shame.","[user] tries to stab \the [src] into [user.p_their()] abdomen, but \the [src] bends and breaks in half! [user.p_they(TRUE)] look[user.p_s()] as if [user.p_they()] might die from the shame.","[user] tries to slice [user.p_their()] own throat, but the plastic blade has no sharpness, causing [user.p_them()] to lose [user.p_their()] balance, slip over, and break [user.p_their()] neck with a loud snap!") - user.visible_message("[dmsg] It looks like [user.p_theyre()] trying to commit suicide.") - return BRUTELOSS - - -/* - * Snap pops viral shit - */ -/obj/item/toy/snappop/virus - name = "unstable goo" - desc = "Your palm is oozing this stuff!" - icon = 'icons/mob/slimes.dmi' - icon_state = "red slime extract" - throwforce = 5.0 - throw_speed = 10 - throw_range = 30 - w_class = WEIGHT_CLASS_TINY - - -/obj/item/toy/snappop/virus/throw_impact(atom/hit_atom) - ..() - do_sparks(3, 1, src) - new /obj/effect/decal/cleanable/ash(src.loc) - visible_message("The [name] explodes!","You hear a bang!") - playsound(src, 'sound/effects/snap.ogg', 50, 1) - qdel(src) - -/* - * Snap pops - */ -/obj/item/toy/snappop - name = "snap pop" - desc = "Wow!" - icon = 'icons/obj/toy.dmi' - icon_state = "snappop" - w_class = WEIGHT_CLASS_TINY - var/ash_type = /obj/effect/decal/cleanable/ash - -/obj/item/toy/snappop/proc/pop_burst(var/n=3, var/c=1) - do_sparks(n, c, src) - new ash_type(loc) - visible_message("[src] explodes!", - "You hear a snap!") - playsound(src, 'sound/effects/snap.ogg', 50, 1) - qdel(src) - -/obj/item/toy/snappop/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - ..() - pop_burst() - -/obj/item/toy/snappop/throw_impact(atom/hit_atom) - ..() - pop_burst() - -/obj/item/toy/snappop/Crossed(H as mob|obj, oldloc) - if(ishuman(H) || issilicon(H)) //i guess carp and shit shouldn't set them off - var/mob/living/carbon/M = H - if(issilicon(H) || M.m_intent == MOVE_INTENT_RUN) - to_chat(M, "You step on the snap pop!") - pop_burst(2, 0) - -/obj/item/toy/snappop/phoenix - name = "phoenix snap pop" - desc = "Wow! And wow! And wow!" - ash_type = /obj/effect/decal/cleanable/ash/snappop_phoenix - -/obj/effect/decal/cleanable/ash/snappop_phoenix - var/respawn_time = 300 - -/obj/effect/decal/cleanable/ash/snappop_phoenix/Initialize(mapload) - . = ..() - addtimer(CALLBACK(src, .proc/respawn), respawn_time) - -/obj/effect/decal/cleanable/ash/snappop_phoenix/proc/respawn() - new /obj/item/toy/snappop/phoenix(get_turf(src)) - qdel(src) - - -/* - * Mech prizes - */ -/obj/item/toy/prize - icon = 'icons/obj/toy.dmi' - icon_state = "ripleytoy" - var/cooldown = 0 - -//all credit to skasi for toy mech fun ideas -/obj/item/toy/prize/attack_self(mob/user as mob) - if(cooldown < world.time - 8) - to_chat(user, "You play with [src].") - playsound(user, 'sound/mecha/mechstep.ogg', 20, 1) - cooldown = world.time - -/obj/item/toy/prize/attack_hand(mob/user as mob) - if(loc == user) - if(cooldown < world.time - 8) - to_chat(user, "You play with [src].") - playsound(user, 'sound/mecha/mechturn.ogg', 20, 1) - cooldown = world.time - return - ..() - -/obj/random/mech - name = "Random Mech Prize" - desc = "This is a random prize" - icon = 'icons/obj/toy.dmi' - icon_state = "ripleytoy" - -/obj/random/mech/item_to_spawn() - return pick(subtypesof(/obj/item/toy/prize)) //exclude the base type. - -/obj/item/toy/prize/ripley - name = "toy ripley" - desc = "Mini-Mecha action figure! Collect them all! 1/11." - -/obj/item/toy/prize/fireripley - name = "toy firefighting ripley" - desc = "Mini-Mecha action figure! Collect them all! 2/11." - icon_state = "fireripleytoy" - -/obj/item/toy/prize/deathripley - name = "toy deathsquad ripley" - desc = "Mini-Mecha action figure! Collect them all! 3/11." - icon_state = "deathripleytoy" - -/obj/item/toy/prize/gygax - name = "toy gygax" - desc = "Mini-Mecha action figure! Collect them all! 4/11." - icon_state = "gygaxtoy" - -/obj/item/toy/prize/durand - name = "toy durand" - desc = "Mini-Mecha action figure! Collect them all! 5/11." - icon_state = "durandprize" - -/obj/item/toy/prize/honk - name = "toy H.O.N.K." - desc = "Mini-Mecha action figure! Collect them all! 6/11." - icon_state = "honkprize" - -/obj/item/toy/prize/marauder - name = "toy marauder" - desc = "Mini-Mecha action figure! Collect them all! 7/11." - icon_state = "marauderprize" - -/obj/item/toy/prize/seraph - name = "toy seraph" - desc = "Mini-Mecha action figure! Collect them all! 8/11." - icon_state = "seraphprize" - -/obj/item/toy/prize/mauler - name = "toy mauler" - desc = "Mini-Mecha action figure! Collect them all! 9/11." - icon_state = "maulerprize" - -/obj/item/toy/prize/odysseus - name = "toy odysseus" - desc = "Mini-Mecha action figure! Collect them all! 10/11." - icon_state = "odysseusprize" - -/obj/item/toy/prize/phazon - name = "toy phazon" - desc = "Mini-Mecha action figure! Collect them all! 11/11." - icon_state = "phazonprize" - - - -/* -|| A Deck of Cards for playing various games of chance || -*/ - - - -obj/item/toy/cards - resistance_flags = FLAMMABLE - max_integrity = 50 - var/parentdeck = null - var/deckstyle = "nanotrasen" - var/card_hitsound = null - var/card_force = 0 - var/card_throwforce = 0 - var/card_throw_speed = 4 - var/card_throw_range = 20 - var/list/card_attack_verb = list("attacked") - -obj/item/toy/cards/New() - ..() - -obj/item/toy/cards/proc/apply_card_vars(obj/item/toy/cards/newobj, obj/item/toy/cards/sourceobj) // Applies variables for supporting multiple types of card deck - if(!istype(sourceobj)) - return - -obj/item/toy/cards/deck - name = "deck of cards" - desc = "A deck of space-grade playing cards." - icon = 'icons/obj/toy.dmi' - deckstyle = "nanotrasen" - icon_state = "deck_nanotrasen_full" - w_class = WEIGHT_CLASS_SMALL - var/cooldown = 0 - var/list/cards = list() - -obj/item/toy/cards/deck/New() - ..() - icon_state = "deck_[deckstyle]_full" - for(var/i in 2 to 10) - cards += "[i] of Hearts" - cards += "[i] of Spades" - cards += "[i] of Clubs" - cards += "[i] of Diamonds" - cards += "King of Hearts" - cards += "King of Spades" - cards += "King of Clubs" - cards += "King of Diamonds" - cards += "Queen of Hearts" - cards += "Queen of Spades" - cards += "Queen of Clubs" - cards += "Queen of Diamonds" - cards += "Jack of Hearts" - cards += "Jack of Spades" - cards += "Jack of Clubs" - cards += "Jack of Diamonds" - cards += "Ace of Hearts" - cards += "Ace of Spades" - cards += "Ace of Clubs" - cards += "Ace of Diamonds" - -obj/item/toy/cards/deck/attack_hand(mob/user as mob) - var/choice = null - if(cards.len == 0) - icon_state = "deck_[deckstyle]_empty" - to_chat(user, "There are no more cards to draw.") - return - var/obj/item/toy/cards/singlecard/H = new/obj/item/toy/cards/singlecard(user.loc) - choice = cards[1] - H.cardname = choice - H.parentdeck = src - var/O = src - H.apply_card_vars(H,O) - cards -= choice - H.pickup(user) - user.put_in_active_hand(H) - visible_message("[user] draws a card from the deck.", "You draw a card from the deck.") - update_icon() - -obj/item/toy/cards/deck/attack_self(mob/user as mob) - if(cooldown < world.time - 50) - cards = shuffle(cards) - playsound(user, 'sound/items/cardshuffle.ogg', 50, 1) - user.visible_message("[user] shuffles the deck.", "You shuffle the deck.") - cooldown = world.time - -obj/item/toy/cards/deck/attackby(obj/item/toy/cards/singlecard/C, mob/living/user, params) - ..() - if(istype(C)) - if(C.parentdeck == src) - if(!user.unEquip(C)) - to_chat(user, "The card is stuck to your hand, you can't add it to the deck!") - return - cards += C.cardname - user.visible_message("[user] adds a card to the bottom of the deck.","You add the card to the bottom of the deck.") - qdel(C) - else - to_chat(user, "You can't mix cards from other decks.") - update_icon() - - -obj/item/toy/cards/deck/attackby(obj/item/toy/cards/cardhand/C, mob/living/user, params) - ..() - if(istype(C)) - if(C.parentdeck == src) - if(!user.unEquip(C)) - to_chat(user, "The hand of cards is stuck to your hand, you can't add it to the deck!") - return - cards += C.currenthand - user.visible_message("[user] puts [user.p_their()] hand of cards in the deck.", "You put the hand of cards in the deck.") - qdel(C) - else - to_chat(user, "You can't mix cards from other decks.") - update_icon() - -obj/item/toy/cards/deck/MouseDrop(atom/over_object) - var/mob/M = usr - if(usr.stat || !ishuman(usr) || !usr.canmove || usr.restrained()) - return - if(Adjacent(usr)) - if(over_object == M && loc != M) - M.put_in_hands(src) - to_chat(usr, "You pick up the deck.") - - else if(istype(over_object, /obj/screen)) - switch(over_object.name) - if("l_hand") - if(!remove_item_from_storage(M)) - M.unEquip(src) - M.put_in_l_hand(src) - to_chat(usr, "You pick up the deck.") - if("r_hand") - if(!remove_item_from_storage(M)) - M.unEquip(src) - M.put_in_r_hand(src) - to_chat(usr, "You pick up the deck.") - else - to_chat(usr, "You can't reach it from here.") - -obj/item/toy/cards/deck/update_icon() - switch(cards.len) - if(0) - icon_state = "deck_[deckstyle]_empty" - if(1 to 10) - icon_state = "deck_[deckstyle]_low" - if(11 to 26) - icon_state = "deck_[deckstyle]_half" - else - icon_state = "deck_[deckstyle]_full" - -obj/item/toy/cards/cardhand - name = "hand of cards" - desc = "A number of cards not in a deck, customarily held in ones hand." - icon = 'icons/obj/toy.dmi' - icon_state = "nanotrasen_hand2" - w_class = WEIGHT_CLASS_TINY - var/list/currenthand = list() - var/choice = null - - -obj/item/toy/cards/cardhand/attack_self(mob/user as mob) - user.set_machine(src) - interact(user) - -obj/item/toy/cards/cardhand/interact(mob/user) - var/dat = "You have:
        " - for(var/t in currenthand) - dat += "A [t].
        " - dat += "Which card will you remove next?" - var/datum/browser/popup = new(user, "cardhand", "Hand of Cards", 400, 240) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.set_content(dat) - popup.open() - - -obj/item/toy/cards/cardhand/Topic(href, href_list) - if(..()) - return - if(usr.stat || !ishuman(usr) || !usr.canmove) - return - var/mob/living/carbon/human/cardUser = usr - var/O = src - if(href_list["pick"]) - if(cardUser.get_item_by_slot(slot_l_hand) == src || cardUser.get_item_by_slot(slot_r_hand) == src) - var/choice = href_list["pick"] - var/obj/item/toy/cards/singlecard/C = new/obj/item/toy/cards/singlecard(cardUser.loc) - currenthand -= choice - C.parentdeck = src.parentdeck - C.cardname = choice - C.apply_card_vars(C,O) - C.pickup(cardUser) - cardUser.put_in_any_hand_if_possible(C) - cardUser.visible_message("[cardUser] draws a card from [cardUser.p_their()] hand.", "You take the [C.cardname] from your hand.") - - interact(cardUser) - update_icon() - if(currenthand.len == 1) - var/obj/item/toy/cards/singlecard/N = new/obj/item/toy/cards/singlecard(src.loc) - N.parentdeck = src.parentdeck - N.cardname = src.currenthand[1] - N.apply_card_vars(N,O) - cardUser.unEquip(src) - N.pickup(cardUser) - cardUser.put_in_any_hand_if_possible(N) - to_chat(cardUser, "You also take [currenthand[1]] and hold it.") - cardUser << browse(null, "window=cardhand") - qdel(src) - return - -obj/item/toy/cards/cardhand/attackby(obj/item/toy/cards/singlecard/C, mob/living/user, params) - if(istype(C)) - if(C.parentdeck == parentdeck) - currenthand += C.cardname - user.unEquip(C) - user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [C.cardname] to your hand.") - interact(user) - update_icon() - qdel(C) - else - to_chat(user, "You can't mix cards from other decks.") - -obj/item/toy/cards/cardhand/apply_card_vars(obj/item/toy/cards/newobj,obj/item/toy/cards/sourceobj) - ..() - newobj.deckstyle = sourceobj.deckstyle - newobj.icon_state = "[deckstyle]_hand2" // Another dumb hack, without this the hand is invisible (or has the default deckstyle) until another card is added. - newobj.card_hitsound = sourceobj.card_hitsound - newobj.card_force = sourceobj.card_force - newobj.card_throwforce = sourceobj.card_throwforce - newobj.card_throw_speed = sourceobj.card_throw_speed - newobj.card_throw_range = sourceobj.card_throw_range - newobj.card_attack_verb = sourceobj.card_attack_verb - newobj.resistance_flags = sourceobj.resistance_flags - - -obj/item/toy/cards/singlecard - name = "card" - desc = "a card" - icon = 'icons/obj/toy.dmi' - icon_state = "singlecard_nanotrasen_down" - w_class = WEIGHT_CLASS_TINY - var/cardname = null - var/flipped = 0 - pixel_x = -5 - - -obj/item/toy/cards/singlecard/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0) - if(ishuman(user)) - var/mob/living/carbon/human/cardUser = user - if(cardUser.get_item_by_slot(slot_l_hand) == src || cardUser.get_item_by_slot(slot_r_hand) == src) - cardUser.visible_message("[cardUser] checks [cardUser.p_their()] card.", "The card reads: [src.cardname]") - else - . += "You need to have the card in your hand to check it." - - -obj/item/toy/cards/singlecard/verb/Flip() - set name = "Flip Card" - set category = "Object" - set src in range(1) - if(usr.stat || !ishuman(usr) || !usr.canmove || usr.restrained()) - return - if(!flipped) - flipped = 1 - if(cardname) - icon_state = "sc_[cardname]_[deckstyle]" - name = cardname - else - icon_state = "sc_Ace of Spades_[deckstyle]" - name = "What Card" - pixel_x = 5 - else - flipped = 0 - icon_state = "singlecard_down_[deckstyle]" - name = "card" - pixel_x = -5 - -obj/item/toy/cards/singlecard/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/toy/cards/singlecard/)) - var/obj/item/toy/cards/singlecard/C = I - if(C.parentdeck == parentdeck) - var/obj/item/toy/cards/cardhand/H = new/obj/item/toy/cards/cardhand(user.loc) - H.currenthand += C.cardname - H.currenthand += cardname - H.parentdeck = C.parentdeck - H.apply_card_vars(H,C) - user.unEquip(C) - H.pickup(user) - user.put_in_active_hand(H) - to_chat(user, "You combine the [C.cardname] and the [cardname] into a hand.") - qdel(C) - qdel(src) - else - to_chat(user, "You can't mix cards from other decks.") - - if(istype(I, /obj/item/toy/cards/cardhand/)) - var/obj/item/toy/cards/cardhand/H = I - if(H.parentdeck == parentdeck) - H.currenthand += cardname - user.unEquip(src) - user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [cardname] to your hand.") - H.interact(user) - H.update_icon() - qdel(src) - else - to_chat(user, "You can't mix cards from other decks.") - -obj/item/toy/cards/cardhand/update_icon() - switch(currenthand.len) - if(0 to 1) - return - if(2) - icon_state = "[deckstyle]_hand2" - if(3) - icon_state = "[deckstyle]_hand3" - if(4) - icon_state = "[deckstyle]_hand4" - else - icon_state = "[deckstyle]_hand5" - - -obj/item/toy/cards/singlecard/attack_self(mob/user) - if(usr.stat || !ishuman(usr) || !usr.canmove || usr.restrained()) - return - Flip() - -obj/item/toy/cards/singlecard/apply_card_vars(obj/item/toy/cards/singlecard/newobj,obj/item/toy/cards/sourceobj) - ..() - newobj.deckstyle = sourceobj.deckstyle - newobj.icon_state = "singlecard_down_[deckstyle]" // Without this the card is invisible until flipped. It's an ugly hack, but it works. - newobj.card_hitsound = sourceobj.card_hitsound - newobj.hitsound = newobj.card_hitsound - newobj.card_force = sourceobj.card_force - newobj.force = newobj.card_force - newobj.card_throwforce = sourceobj.card_throwforce - newobj.throwforce = newobj.card_throwforce - newobj.card_throw_speed = sourceobj.card_throw_speed - newobj.throw_speed = newobj.card_throw_speed - newobj.card_throw_range = sourceobj.card_throw_range - newobj.throw_range = newobj.card_throw_range - newobj.card_attack_verb = sourceobj.card_attack_verb - newobj.attack_verb = newobj.card_attack_verb - - -/* -|| Syndicate playing cards, for pretending you're Gambit and playing poker for the nuke disk. || -*/ - -obj/item/toy/cards/deck/syndicate - name = "suspicious looking deck of cards" - desc = "A deck of space-grade playing cards. They seem unusually rigid." - deckstyle = "syndicate" - card_hitsound = 'sound/weapons/bladeslice.ogg' - card_force = 5 - card_throwforce = 10 - card_throw_speed = 3 - card_throw_range = 20 - card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut") - resistance_flags = NONE - -/* -|| Custom card decks || -*/ -obj/item/toy/cards/deck/black - deckstyle = "black" - -obj/item/toy/cards/deck/syndicate/black - deckstyle = "black" - -/obj/item/toy/nuke - name = "\improper Nuclear Fission Explosive toy" - desc = "A plastic model of a Nuclear Fission Explosive." - icon = 'icons/obj/toy.dmi' - icon_state = "nuketoyidle" - w_class = WEIGHT_CLASS_SMALL - var/cooldown = 0 - -/obj/item/toy/nuke/attack_self(mob/user) - if(cooldown < world.time) - cooldown = world.time + 1800 //3 minutes - user.visible_message("[user] presses a button on [src]", "You activate [src], it plays a loud noise!", "You hear the click of a button.") - spawn(5) //gia said so - icon_state = "nuketoy" - playsound(src, 'sound/machines/alarm.ogg', 100, 0, 0) - sleep(135) - icon_state = "nuketoycool" - sleep(cooldown - world.time) - icon_state = "nuketoyidle" - else - var/timeleft = (cooldown - world.time) - to_chat(user, "Nothing happens, and '[round(timeleft/10)]' appears on a small display.") - -/obj/item/toy/therapy - name = "therapy doll" - desc = "A toy for therapeutic and recreational purposes." - icon = 'icons/obj/toy.dmi' - icon_state = "therapyred" - item_state = "egg4" - w_class = WEIGHT_CLASS_TINY - var/cooldown = 0 - resistance_flags = FLAMMABLE - -/obj/item/toy/therapy/New() - ..() - if(item_color) - name = "[item_color] therapy doll" - desc += " This one is [item_color]." - icon_state = "therapy[item_color]" - -/obj/item/toy/therapy/attack_self(mob/user) - if(cooldown < world.time - 8) - to_chat(user, "You relieve some stress with \the [src].") - playsound(user, 'sound/items/squeaktoy.ogg', 20, 1) - cooldown = world.time - -/obj/random/therapy - name = "Random Therapy Doll" - desc = "This is a random therapy doll." - icon = 'icons/obj/toy.dmi' - icon_state = "therapyred" - -/obj/random/therapy/item_to_spawn() - return pick(subtypesof(/obj/item/toy/therapy)) //exclude the base type. - -/obj/item/toy/therapy/red - item_state = "egg4" // It's the red egg in items_left/righthand - item_color = "red" - -/obj/item/toy/therapy/purple - item_state = "egg1" // It's the magenta egg in items_left/righthand - item_color = "purple" - -/obj/item/toy/therapy/blue - item_state = "egg2" // It's the blue egg in items_left/righthand - item_color = "blue" - -/obj/item/toy/therapy/yellow - item_state = "egg5" // It's the yellow egg in items_left/righthand - item_color = "yellow" - -/obj/item/toy/therapy/orange - item_state = "egg4" // It's the red one again, lacking an orange item_state and making a new one is pointless - item_color = "orange" - -/obj/item/toy/therapy/green - item_state = "egg3" // It's the green egg in items_left/righthand - item_color = "green" - -/obj/item/toddler - icon_state = "toddler" - name = "toddler" - desc = "This baby looks almost real. Wait, did it just burp?" - force = 5 - w_class = WEIGHT_CLASS_BULKY - slot_flags = SLOT_BACK - - -//This should really be somewhere else but I don't know where. w/e - -/obj/item/inflatable_duck - name = "inflatable duck" - desc = "No bother to sink or swim when you can just float!" - icon_state = "inflatable" - item_state = "inflatable" - icon = 'icons/obj/clothing/belts.dmi' - slot_flags = SLOT_BELT - -/* - * Fake meteor - */ - -/obj/item/toy/minimeteor - name = "Mini-Meteor" - desc = "Relive the excitement of a meteor shower! SweetMeat-eor. Co is not responsible for any injuries, headaches or hearing loss caused by Mini-Meteor." - icon = 'icons/obj/toy.dmi' - icon_state = "minimeteor" - w_class = WEIGHT_CLASS_SMALL - -/obj/item/toy/minimeteor/throw_impact(atom/hit_atom) - ..() - playsound(src, 'sound/effects/meteorimpact.ogg', 40, 1) - for(var/mob/M in range(10, src)) - if(!M.stat && !istype(M, /mob/living/silicon/ai))\ - shake_camera(M, 3, 1) - qdel(src) - -/* - * Carp plushie - */ - -/obj/item/toy/carpplushie - name = "space carp plushie" - desc = "An adorable stuffed toy that resembles a space carp." - icon = 'icons/obj/toy.dmi' - icon_state = "carpplushie" - attack_verb = list("bitten", "eaten", "fin slapped") - var/bitesound = 'sound/weapons/bite.ogg' - resistance_flags = FLAMMABLE - -// Attack mob -/obj/item/toy/carpplushie/attack(mob/M as mob, mob/user as mob) - playsound(loc, bitesound, 20, 1) // Play bite sound in local area - return ..() - -// Attack self -/obj/item/toy/carpplushie/attack_self(mob/user as mob) - playsound(src.loc, bitesound, 20, 1) - return ..() - - -/obj/random/carp_plushie - name = "Random Carp Plushie" - desc = "This is a random plushie" - icon = 'icons/obj/toy.dmi' - icon_state = "carpplushie" - -/obj/random/carp_plushie/item_to_spawn() - return pick(typesof(/obj/item/toy/carpplushie)) //can pick any carp plushie, even the original. - -/obj/item/toy/carpplushie/ice - icon_state = "icecarp" - -/obj/item/toy/carpplushie/silent - icon_state = "silentcarp" - -/obj/item/toy/carpplushie/electric - icon_state = "electriccarp" - -/obj/item/toy/carpplushie/gold - icon_state = "goldcarp" - -/obj/item/toy/carpplushie/toxin - icon_state = "toxincarp" - -/obj/item/toy/carpplushie/dragon - icon_state = "dragoncarp" - -/obj/item/toy/carpplushie/pink - icon_state = "pinkcarp" - -/obj/item/toy/carpplushie/candy - icon_state = "candycarp" - -/obj/item/toy/carpplushie/nebula - icon_state = "nebulacarp" - -/obj/item/toy/carpplushie/void - icon_state = "voidcarp" - -/* - * Plushie - */ - - -/obj/item/toy/plushie - name = "plushie" - desc = "An adorable, soft, and cuddly plushie." - icon = 'icons/obj/toy.dmi' - var/poof_sound = 'sound/weapons/thudswoosh.ogg' - attack_verb = list("poofed", "bopped", "whapped","cuddled","fluffed") - resistance_flags = FLAMMABLE - -/obj/item/toy/plushie/attack(mob/M as mob, mob/user as mob) - playsound(loc, poof_sound, 20, 1) // Play the whoosh sound in local area - if(iscarbon(M)) - if(prob(10)) - M.reagents.add_reagent("hugs", 10) - return ..() - -/obj/item/toy/plushie/attack_self(mob/user as mob) - var/cuddle_verb = pick("hugs","cuddles","snugs") - user.visible_message("[user] [cuddle_verb] the [src].") - playsound(get_turf(src), poof_sound, 50, 1, -1) - return ..() - -/obj/random/plushie - name = "Random Plushie" - desc = "This is a random plushie" - icon = 'icons/obj/toy.dmi' - icon_state = "redfox" - -/obj/random/plushie/item_to_spawn() - return pick(subtypesof(/obj/item/toy/plushie) - typesof(/obj/item/toy/plushie/fluff)) //exclude the base type. - -/obj/item/toy/plushie/corgi - name = "corgi plushie" - icon_state = "corgi" - -/obj/item/toy/plushie/girly_corgi - name = "corgi plushie" - icon_state = "girlycorgi" - -/obj/item/toy/plushie/robo_corgi - name = "borgi plushie" - icon_state = "robotcorgi" - -/obj/item/toy/plushie/octopus - name = "octopus plushie" - icon_state = "loveable" - -/obj/item/toy/plushie/face_hugger - name = "facehugger plushie" - icon_state = "huggable" - -//foxes are basically the best - -/obj/item/toy/plushie/red_fox - name = "red fox plushie" - icon_state = "redfox" - -/obj/item/toy/plushie/black_fox - name = "black fox plushie" - icon_state = "blackfox" - -/obj/item/toy/plushie/marble_fox - name = "marble fox plushie" - icon_state = "marblefox" - -/obj/item/toy/plushie/blue_fox - name = "blue fox plushie" - icon_state = "bluefox" - -/obj/item/toy/plushie/orange_fox - name = "orange fox plushie" - icon_state = "orangefox" - -/obj/item/toy/plushie/coffee_fox - name = "coffee fox plushie" - icon_state = "coffeefox" - -/obj/item/toy/plushie/pink_fox - name = "pink fox plushie" - icon_state = "pinkfox" - -/obj/item/toy/plushie/purple_fox - name = "purple fox plushie" - icon_state = "purplefox" - -/obj/item/toy/plushie/crimson_fox - name = "crimson fox plushie" - icon_state = "crimsonfox" - -/obj/item/toy/plushie/deer - name = "deer plushie" - icon_state = "deer" - -/obj/item/toy/plushie/black_cat - name = "black cat plushie" - icon_state = "blackcat" - -/obj/item/toy/plushie/grey_cat - name = "grey cat plushie" - icon_state = "greycat" - -/obj/item/toy/plushie/white_cat - name = "white cat plushie" - icon_state = "whitecat" - -/obj/item/toy/plushie/orange_cat - name = "orange cat plushie" - icon_state = "orangecat" - -/obj/item/toy/plushie/siamese_cat - name = "siamese cat plushie" - icon_state = "siamesecat" - -/obj/item/toy/plushie/tabby_cat - name = "tabby cat plushie" - icon_state = "tabbycat" - -/obj/item/toy/plushie/tuxedo_cat - name = "tuxedo cat plushie" - icon_state = "tuxedocat" - -/obj/item/toy/plushie/voxplushie - name = "vox plushie" - desc = "A stitched-together vox, fresh from the skipjack. Press its belly to hear it skree!" - icon_state = "plushie_vox" - item_state = "plushie_vox" - var/cooldown = 0 - -/obj/item/toy/plushie/voxplushie/attack_self(mob/user) - if(!cooldown) - playsound(user, 'sound/voice/shriek1.ogg', 10, 0) - visible_message("Skreee!") - cooldown = 1 - spawn(30) cooldown = 0 - return - ..() - -//New generation TG plushies - -/obj/item/toy/plushie/lizardplushie - name = "lizard plushie" - desc = "An adorable stuffed toy that resembles a lizardperson." - icon_state = "plushie_lizard" - item_state = "plushie_lizard" - -/obj/item/toy/plushie/snakeplushie - name = "snake plushie" - desc = "An adorable stuffed toy that resembles a snake. Not to be mistaken for the real thing." - icon_state = "plushie_snake" - item_state = "plushie_snake" - -/obj/item/toy/plushie/nukeplushie - name = "operative plushie" - desc = "An stuffed toy that resembles a syndicate nuclear operative. The tag claims operatives to be purely fictitious." - icon_state = "plushie_nuke" - item_state = "plushie_nuke" - -/obj/item/toy/plushie/slimeplushie - name = "slime plushie" - desc = "An adorable stuffed toy that resembles a slime. It is practically just a hacky sack." - icon_state = "plushie_slime" - item_state = "plushie_slime" - -/* - * Foam Armblade - */ - - /obj/item/toy/foamblade - name = "foam armblade" - desc = "it says \"Sternside Changs #1 fan\" on it. " - icon = 'icons/obj/toy.dmi' - icon_state = "foamblade" - item_state = "arm_blade" - attack_verb = list("pricked", "absorbed", "gored") - w_class = WEIGHT_CLASS_SMALL - resistance_flags = FLAMMABLE - -/* - * Toy/fake flash - */ -/obj/item/toy/flash - name = "toy flash" - desc = "FOR THE REVOLU- Oh wait, that's just a toy." - icon = 'icons/obj/device.dmi' - icon_state = "flash" - item_state = "flashtool" - w_class = WEIGHT_CLASS_TINY - -/obj/item/toy/flash/attack(mob/living/M, mob/user) - playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1) - flick("[initial(icon_state)]2", src) - user.visible_message("[user] blinds [M] with the flash!") - - -/* - * Toy big red button - */ -/obj/item/toy/redbutton - name = "big red button" - desc = "A big, plastic red button. Reads 'From HonkCo Pranks?' on the back." - icon = 'icons/obj/assemblies.dmi' - icon_state = "bigred" - w_class = WEIGHT_CLASS_SMALL - var/cooldown = 0 - -/obj/item/toy/redbutton/attack_self(mob/user) - if(cooldown < world.time) - cooldown = (world.time + 300) // Sets cooldown at 30 seconds - user.visible_message("[user] presses the big red button.", "You press the button, it plays a loud noise!", "The button clicks loudly.") - playsound(src, 'sound/effects/explosionfar.ogg', 50, 0, 0) - for(var/mob/M in range(10, src)) // Checks range - if(!M.stat && !istype(M, /mob/living/silicon/ai)) // Checks to make sure whoever's getting shaken is alive/not the AI - sleep(8) // Short delay to match up with the explosion sound - shake_camera(M, 2, 1) // Shakes player camera 2 squares for 1 second. - - else - to_chat(user, "Nothing happens.") - - -/* - * AI core prizes - */ -/obj/item/toy/AI - name = "toy AI" - desc = "A little toy model AI core with real law announcing action!" - icon = 'icons/obj/toy.dmi' - icon_state = "AI" - w_class = WEIGHT_CLASS_SMALL - var/cooldown = 0 - -/obj/item/toy/AI/attack_self(mob/user) - if(!cooldown) //for the sanity of everyone - var/message = generate_ion_law() - to_chat(user, "You press the button on [src].") - playsound(user, 'sound/machines/click.ogg', 20, 1) - visible_message("[bicon(src)] [message]") - cooldown = 1 - spawn(30) cooldown = 0 - return - ..() - -/obj/item/toy/codex_gigas - name = "Toy Codex Gigas" - desc = "A tool to help you write fictional devils!" - icon = 'icons/obj/library.dmi' - icon_state = "demonomicon" - w_class = WEIGHT_CLASS_SMALL - var/cooldown = FALSE - -/obj/item/toy/codex_gigas/attack_self(mob/user) - if(!cooldown) - user.visible_message( - "[user] presses the button on \the [src].", - "You press the button on \the [src].", - "You hear a soft click.") - var/list/messages = list() - var/datum/devilinfo/devil = randomDevilInfo() - messages += "Some fun facts about: [devil.truename]" - messages += "[lawlorify[LORE][devil.bane]]" - messages += "[lawlorify[LORE][devil.obligation]]" - messages += "[lawlorify[LORE][devil.ban]]" - messages += "[lawlorify[LORE][devil.banish]]" - playsound(loc, 'sound/machines/click.ogg', 20, 1) - cooldown = TRUE - for(var/message in messages) - user.loc.visible_message("[bicon(src)] [message]") - sleep(10) - spawn(20) - cooldown = FALSE - return - ..() - -/obj/item/toy/owl - name = "owl action figure" - desc = "An action figure modeled after 'The Owl', defender of justice." - icon = 'icons/obj/toy.dmi' - icon_state = "owlprize" - w_class = WEIGHT_CLASS_SMALL - var/cooldown = 0 - -/obj/item/toy/owl/attack_self(mob/user) - if(!cooldown) //for the sanity of everyone - var/message = pick("You won't get away this time, Griffin!", "Stop right there, criminal!", "Hoot! Hoot!", "I am the night!") - to_chat(user, "You pull the string on the [src].") - playsound(user, 'sound/creatures/hoot.ogg', 25, 1) - visible_message("[bicon(src)] [message]") - cooldown = 1 - spawn(30) cooldown = 0 - return - ..() - -/obj/item/toy/griffin - name = "griffin action figure" - desc = "An action figure modeled after 'The Griffin', criminal mastermind." - icon = 'icons/obj/toy.dmi' - icon_state = "griffinprize" - w_class = WEIGHT_CLASS_SMALL - var/cooldown = 0 - -/obj/item/toy/griffin/attack_self(mob/user) - if(!cooldown) //for the sanity of everyone - var/message = pick("You can't stop me, Owl!", "My plan is flawless! The vault is mine!", "Caaaawwww!", "You will never catch me!") - to_chat(user, "You pull the string on the [src].") - playsound(user, 'sound/creatures/caw.ogg', 25, 1) - visible_message("[bicon(src)] [message]") - cooldown = 1 - spawn(30) cooldown = 0 - return - ..() - -// DND Character minis. Use the naming convention (type)character for the icon states. -/obj/item/toy/character - icon = 'icons/obj/toy.dmi' - w_class = WEIGHT_CLASS_SMALL - pixel_z = 5 - -/obj/item/toy/character/alien - name = "Xenomorph Miniature" - desc = "A miniature xenomorph. Scary!" - icon_state = "aliencharacter" -/obj/item/toy/character/cleric - name = "Cleric Miniature" - desc = "A wee little cleric, with his wee little staff." - icon_state = "clericcharacter" -/obj/item/toy/character/warrior - name = "Warrior Miniature" - desc = "That sword would make a decent toothpick." - icon_state = "warriorcharacter" -/obj/item/toy/character/thief - name = "Thief Miniature" - desc = "Hey, where did my wallet go!?" - icon_state = "thiefcharacter" -/obj/item/toy/character/wizard - name = "Wizard Miniature" - desc = "MAGIC!" - icon_state = "wizardcharacter" -/obj/item/toy/character/cthulhu - name = "Cthulhu Miniature" - desc = "The dark lord has risen!" - icon_state = "darkmastercharacter" -/obj/item/toy/character/lich - name = "Lich Miniature" - desc = "Murderboner extraordinaire." - icon_state = "lichcharacter" -/obj/item/storage/box/characters - name = "Box of Miniatures" - desc = "The nerd's best friends." - icon_state = "box" -/obj/item/storage/box/characters/New() - ..() - new /obj/item/toy/character/alien(src) - new /obj/item/toy/character/cleric(src) - new /obj/item/toy/character/warrior(src) - new /obj/item/toy/character/thief(src) - new /obj/item/toy/character/wizard(src) - new /obj/item/toy/character/cthulhu(src) - new /obj/item/toy/character/lich(src) - - -//Pet Rocks, just like from the 70's! - -/obj/item/toy/pet_rock - name = "pet rock" - desc = "The perfect pet!" - icon = 'icons/obj/toy.dmi' - icon_state = "pet_rock" - w_class = WEIGHT_CLASS_SMALL - force = 5 - throwforce = 5 - attack_verb = list("attacked", "bashed", "smashed", "stoned") - hitsound = "swing_hit" - -/obj/item/toy/pet_rock/fred - name = "fred" - desc = "Fred, the bestest boy pet in the whole wide universe!" - icon_state = "fred" - -/obj/item/toy/pet_rock/roxie - name = "roxie" - desc = "Roxie, the bestest girl pet in the whole wide universe!" - icon_state = "roxie" - -//minigibber, so cute - -/obj/item/toy/minigibber - name = "miniature gibber" - desc = "A miniature recreation of Nanotrasen's famous meat grinder." - icon = 'icons/obj/toy.dmi' - icon_state = "minigibber" - attack_verb = list("grinded", "gibbed") - w_class = WEIGHT_CLASS_SMALL - var/cooldown = 0 - var/obj/stored_minature = null - -/obj/item/toy/minigibber/attack_self(var/mob/user) - - if(stored_minature) - to_chat(user, "\The [src] makes a violent grinding noise as it tears apart the miniature figure inside!") - QDEL_NULL(stored_minature) - playsound(user, 'sound/goonstation/effects/gib.ogg', 20, 1) - cooldown = world.time - - if(cooldown < world.time - 8) - to_chat(user, "You hit the gib button on \the [src].") - playsound(user, 'sound/goonstation/effects/gib.ogg', 20, 1) - cooldown = world.time - -/obj/item/toy/minigibber/attackby(var/obj/O, var/mob/user, params) - if(istype(O,/obj/item/toy/character) && O.loc == user) - to_chat(user, "You start feeding \the [O] [bicon(O)] into \the [src]'s mini-input.") - if(do_after(user, 10, target = src)) - if(O.loc != user) - to_chat(user, "\The [O] is too far away to feed into \the [src]!") - else - to_chat(user, "You feed \the [O] [bicon(O)] into \the [src]!") - user.unEquip(O) - O.forceMove(src) - stored_minature = O - else - to_chat(user, "You stop feeding \the [O] into \the [src]'s mini-input.") - else ..() - -/* - * Xenomorph action figure - */ - -/obj/item/toy/toy_xeno - icon = 'icons/obj/toy.dmi' - icon_state = "toy_xeno" - name = "xenomorph action figure" - desc = "MEGA presents the new Xenos Isolated action figure! Comes complete with realistic sounds! Pull back string to use." - w_class = WEIGHT_CLASS_SMALL - var/cooldown = 0 - -/obj/item/toy/toy_xeno/attack_self(mob/user) - if(cooldown <= world.time) - cooldown = (world.time + 50) //5 second cooldown - user.visible_message("[user] pulls back the string on [src].") - icon_state = "[initial(icon_state)]_used" - sleep(5) - audible_message("[bicon(src)] Hiss!") - var/list/possible_sounds = list('sound/voice/hiss1.ogg', 'sound/voice/hiss2.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss4.ogg') - playsound(get_turf(src), pick(possible_sounds), 50, 1) - spawn(45) - if(src) - icon_state = "[initial(icon_state)]" - else - to_chat(user, "The string on [src] hasn't rewound all the way!") - return - -/obj/item/toy/russian_revolver - name = "russian revolver" - desc = "for fun and games!" - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "detective_gold" - item_state = "gun" - lefthand_file = 'icons/mob/inhands/guns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/guns_righthand.dmi' - hitsound = "swing_hit" - flags = CONDUCT - slot_flags = SLOT_BELT - materials = list(MAT_METAL=2000) - w_class = WEIGHT_CLASS_NORMAL - throwforce = 5 - throw_speed = 4 - throw_range = 5 - force = 5 - origin_tech = "combat=1" - attack_verb = list("struck", "hit", "bashed") - var/bullets_left = 0 - var/max_shots = 6 - -/obj/item/toy/russian_revolver/suicide_act(mob/user) - user.visible_message("[user] quickly loads six bullets into [src]'s cylinder and points it at [user.p_their()] head before pulling the trigger! It looks like [user.p_theyre()] trying to commit suicide.") - playsound(loc, 'sound/weapons/gunshots/gunshot_strong.ogg', 50, 1) - return BRUTELOSS - -/obj/item/toy/russian_revolver/New() - ..() - spin_cylinder() - -/obj/item/toy/russian_revolver/attack_self(mob/user) - if(!bullets_left) - user.visible_message("[user] loads a bullet into [src]'s cylinder before spinning it.") - spin_cylinder() - else - user.visible_message("[user] spins the cylinder on [src]!") - spin_cylinder() - -/obj/item/toy/russian_revolver/attack(mob/M, mob/living/user) - return - -/obj/item/toy/russian_revolver/afterattack(atom/target, mob/user, flag, params) - if(flag) - if(target in user.contents) - return - if(!ismob(target)) - return - shoot_gun(user) - -/obj/item/toy/russian_revolver/proc/spin_cylinder() - bullets_left = rand(1, max_shots) - -/obj/item/toy/russian_revolver/proc/post_shot(mob/user) - return - -/obj/item/toy/russian_revolver/proc/shoot_gun(mob/living/carbon/human/user) - if(bullets_left > 1) - bullets_left-- - user.visible_message("*click*") - playsound(src, 'sound/weapons/empty.ogg', 100, 1) - return FALSE - if(bullets_left == 1) - bullets_left = 0 - var/zone = "head" - if(!(user.has_organ(zone))) // If they somehow don't have a head. - zone = "chest" - playsound(src, 'sound/weapons/gunshots/gunshot_strong.ogg', 50, 1) - user.visible_message("[src] goes off!") - post_shot(user) - user.apply_damage(300, BRUTE, zone, sharp = TRUE, used_weapon = "Self-inflicted gunshot wound to the [zone].") - user.bleed(BLOOD_VOLUME_NORMAL) - user.death() // Just in case - return TRUE - else - to_chat(user, "[src] needs to be reloaded.") - return FALSE - -/obj/item/toy/russian_revolver/trick_revolver - name = "\improper .357 revolver" - desc = "A suspicious revolver. Uses .357 ammo." - icon_state = "revolver" - max_shots = 1 - var/fake_bullets = 0 - -/obj/item/toy/russian_revolver/trick_revolver/New() - ..() - fake_bullets = rand(2, 7) - -/obj/item/toy/russian_revolver/trick_revolver/examine(mob/user) //Sneaky sneaky - . = ..() - . += "Has [fake_bullets] round\s remaining." - . += "[fake_bullets] of those are live rounds." - -/obj/item/toy/russian_revolver/trick_revolver/post_shot(user) - to_chat(user, "[src] did look pretty dodgey!") - SEND_SOUND(user, 'sound/misc/sadtrombone.ogg') //HONK -/* - * Rubber Chainsaw - */ -/obj/item/twohanded/toy/chainsaw - name = "Toy Chainsaw" - desc = "A toy chainsaw with a rubber edge. Ages 8 and up" - icon_state = "chainsaw0" - force = 0 - throwforce = 0 - throw_speed = 4 - throw_range = 20 - wieldsound = 'sound/weapons/chainsawstart.ogg' - attack_verb = list("sawed", "cut", "hacked", "carved", "cleaved", "butchered", "felled", "timbered") - -/obj/item/twohanded/toy/chainsaw/update_icon() - if(wielded) - icon_state = "chainsaw[wielded]" - else - icon_state = "chainsaw0" - -/* - * Cat Toy - */ -/obj/item/toy/cattoy - name = "toy mouse" - desc = "A colorful toy mouse!" - icon = 'icons/obj/toy.dmi' - icon_state = "toy_mouse" - w_class = WEIGHT_CLASS_SMALL - resistance_flags = FLAMMABLE - var/cooldown = 0 - -/* - * Action Figures - */ - - -/obj/random/figure - name = "Random Action Figure" - desc = "This is a random toy action figure" - icon = 'icons/obj/toy.dmi' - icon_state = "nuketoy" - -/obj/random/figure/item_to_spawn() - return pick(subtypesof(/obj/item/toy/figure)) - - -/obj/item/toy/figure - name = "Non-Specific Action Figure action figure" - desc = "A \"Space Life\" brand... wait, what the hell is this thing?" - icon = 'icons/obj/toy.dmi' - icon_state = "nuketoy" - var/cooldown = 0 - var/toysay = "What the fuck did you do?" - -/obj/item/toy/figure/New() - ..() - desc = "A \"Space Life\" brand [name]" - -/obj/item/toy/figure/attack_self(mob/user as mob) - if(cooldown < world.time) - cooldown = (world.time + 30) //3 second cooldown - user.visible_message("[bicon(src)] The [src] says \"[toysay]\".") - playsound(user, 'sound/machines/click.ogg', 20, 1) - -/obj/item/toy/figure/cmo - name = "Chief Medical Officer action figure" - icon_state = "cmo" - toysay = "Suit sensors!" - -/obj/item/toy/figure/assistant - name = "Assistant action figure" - icon_state = "assistant" - toysay = "Grey tide station wide!" - -/obj/item/toy/figure/atmos - name = "Atmospheric Technician action figure" - icon_state = "atmos" - toysay = "Glory to Atmosia!" - -/obj/item/toy/figure/bartender - name = "Bartender action figure" - icon_state = "bartender" - toysay = "Wheres my monkey?" - -/obj/item/toy/figure/borg - name = "Cyborg action figure" - icon_state = "borg" - toysay = "I. LIVE. AGAIN." - -/obj/item/toy/figure/botanist - name = "Botanist action figure" - icon_state = "botanist" - toysay = "Dude, I see colors..." - -/obj/item/toy/figure/captain - name = "Captain action figure" - icon_state = "captain" - toysay = "Crew, the Nuke Disk is safely up my ass." - -/obj/item/toy/figure/cargotech - name = "Cargo Technician action figure" - icon_state = "cargotech" - toysay = "For Cargonia!" - -/obj/item/toy/figure/ce - name = "Chief Engineer action figure" - icon_state = "ce" - toysay = "Wire the solars!" - -/obj/item/toy/figure/chaplain - name = "Chaplain action figure" - icon_state = "chaplain" - toysay = "Gods make me a killing machine please!" - -/obj/item/toy/figure/chef - name = "Chef action figure" - icon_state = "chef" - toysay = "I swear it's not human meat." - -/obj/item/toy/figure/chemist - name = "Chemist action figure" - icon_state = "chemist" - toysay = "Get your pills!" - -/obj/item/toy/figure/clown - name = "Clown action figure" - icon_state = "clown" - toysay = "Honk!" - -/obj/item/toy/figure/ian - name = "Ian action figure" - icon_state = "ian" - toysay = "Arf!" - -/obj/item/toy/figure/detective - name = "Detective action figure" - icon_state = "detective" - toysay = "This airlock has grey jumpsuit and insulated glove fibers on it." - -/obj/item/toy/figure/dsquad - name = "Death Squad Officer action figure" - icon_state = "dsquad" - toysay = "Eliminate all threats!" - -/obj/item/toy/figure/engineer - name = "Engineer action figure" - icon_state = "engineer" - toysay = "Oh god, the singularity is loose!" - -/obj/item/toy/figure/geneticist - name = "Geneticist action figure" - icon_state = "geneticist" - toysay = "I'm not qualified for this job." - -/obj/item/toy/figure/hop - name = "Head of Personnel action figure" - icon_state = "hop" - toysay = "Giving out all access!" - -/obj/item/toy/figure/hos - name = "Head of Security action figure" - icon_state = "hos" - toysay = "I'm here to win, anything else is secondary." - -/obj/item/toy/figure/qm - name = "Quartermaster action figure" - icon_state = "qm" - toysay = "Hail Cargonia!" - -/obj/item/toy/figure/janitor - name = "Janitor action figure" - icon_state = "janitor" - toysay = "Look at the signs, you idiot." - -/obj/item/toy/figure/lawyer - name = "Internal Affairs Agent action figure" - icon_state = "lawyer" - toysay = "Standard Operating Procedure says they're guilty! Hacking is proof they're an Enemy of the Corporation!" - -/obj/item/toy/figure/librarian - name = "Librarian action figure" - icon_state = "librarian" - toysay = "One day while..." - -/obj/item/toy/figure/md - name = "Medical Doctor action figure" - icon_state = "md" - toysay = "The patient is already dead!" - -/obj/item/toy/figure/mime - name = "Mime action figure" - desc = "A \"Space Life\" brand Mime action figure." - icon_state = "mime" - toysay = "..." - -/obj/item/toy/figure/miner - name = "Shaft Miner action figure" - icon_state = "miner" - toysay = "Oh god it's eating my intestines!" - -/obj/item/toy/figure/ninja - name = "Ninja action figure" - icon_state = "ninja" - toysay = "Oh god! Stop shooting, I'm friendly!" - -/obj/item/toy/figure/wizard - name = "Wizard action figure" - icon_state = "wizard" - toysay = "Ei Nath!" - -/obj/item/toy/figure/rd - name = "Research Director action figure" - icon_state = "rd" - toysay = "Blowing all of the borgs!" - -/obj/item/toy/figure/roboticist - name = "Roboticist action figure" - icon_state = "roboticist" - toysay = "He asked to be borged!" - -/obj/item/toy/figure/scientist - name = "Scientist action figure" - icon_state = "scientist" - toysay = "Someone else must have made those bombs!" - -/obj/item/toy/figure/syndie - name = "Nuclear Operative action figure" - icon_state = "syndie" - toysay = "Get that fucking disk!" - -/obj/item/toy/figure/secofficer - name = "Security Officer action figure" - icon_state = "secofficer" - toysay = "I am the law!" - -/obj/item/toy/figure/virologist - name = "Virologist action figure" - icon_state = "virologist" - toysay = "The cure is potassium!" - -/obj/item/toy/figure/warden - name = "Warden action figure" - icon_state = "warden" - toysay = "Execute him for breaking in!" - -////////////////////////////////////////////////////// -// Magic 8-Ball / Conch // -////////////////////////////////////////////////////// - -/obj/item/toy/eight_ball - name = "Magic 8-Ball" - desc = "Mystical! Magical! Ages 8+!" - icon = 'icons/obj/toy.dmi' - icon_state = "eight-ball" - var/use_action = "shakes the ball" - var/cooldown = 0 - var/list/possible_answers = list("Definitely", "All signs point to yes.", "Most likely.", "Yes.", "Ask again later.", "Better not tell you now.", "Future Unclear.", "Maybe.", "Doubtful.", "No.", "Don't count on it.", "Never.") - -/obj/item/toy/eight_ball/attack_self(mob/user as mob) - if(!cooldown) - var/answer = pick(possible_answers) - user.visible_message("[user] focuses on [user.p_their()] question and [use_action]...") - user.visible_message("[bicon(src)] The [src] says \"[answer]\"") - spawn(30) - cooldown = 0 - return - -/obj/item/toy/eight_ball/conch - name = "Magic Conch Shell" - desc = "All hail the Magic Conch!" - icon_state = "conch" - use_action = "pulls the string" - possible_answers = list("Yes.", "No.", "Try asking again.", "Nothing.", "I don't think so.", "Neither.", "Maybe someday.") - -/* - *Fake cuffs (honk honk) - */ - -/obj/item/restraints/handcuffs/toy - desc = "Toy handcuffs. Plastic and extremely cheaply made." - throwforce = 0 - breakouttime = 0 - ignoresClumsy = TRUE \ No newline at end of file +/* Toys! + * Contains: + * Balloons + * Fake telebeacon + * Fake singularity + * Toy swords + * Toy mechs + * Snap pops + * Water flower + * Toy Nuke + * Card Deck + * Therapy dolls + * Toddler doll + * Inflatable duck + * Foam armblade + * Mini Gibber + * Toy xeno + * Toy chainsaws + * Action Figures + */ + + +/obj/item/toy + throwforce = 0 + throw_speed = 4 + throw_range = 20 + force = 0 + + +/* + * Balloons + */ +/obj/item/toy/balloon + name = "water balloon" + desc = "A translucent balloon. There's nothing in it." + icon = 'icons/obj/toy.dmi' + icon_state = "waterballoon-e" + item_state = "balloon-empty" + +/obj/item/toy/balloon/New() + ..() + create_reagents(10) + +/obj/item/toy/balloon/attack(mob/living/carbon/human/M as mob, mob/user as mob) + return + +/obj/item/toy/balloon/afterattack(atom/A, mob/user, proximity) + if(!proximity) + return + if(istype(A, /obj/structure/reagent_dispensers)) + var/obj/structure/reagent_dispensers/RD = A + if(RD.reagents.total_volume <= 0) + to_chat(user, "[RD] is empty.") + else if(reagents.total_volume >= 10) + to_chat(user, "[src] is full.") + else + A.reagents.trans_to(src, 10) + to_chat(user, "You fill the balloon with the contents of [A].") + desc = "A translucent balloon with some form of liquid sloshing around in it." + update_icon() + +/obj/item/toy/balloon/wash(mob/user, atom/source) + if(reagents.total_volume < 10) + reagents.add_reagent("water", min(10-reagents.total_volume, 10)) + to_chat(user, "You fill the balloon from the [source].") + desc = "A translucent balloon with some form of liquid sloshing around in it." + update_icon() + return + +/obj/item/toy/balloon/attackby(obj/O as obj, mob/user as mob, params) + if(istype(O, /obj/item/reagent_containers/glass) || istype(O, /obj/item/reagent_containers/food/drinks/drinkingglass)) + if(O.reagents) + if(O.reagents.total_volume < 1) + to_chat(user, "The [O] is empty.") + else if(O.reagents.total_volume >= 1) + if(O.reagents.has_reagent("facid", 1)) + to_chat(user, "The acid chews through the balloon!") + O.reagents.reaction(user) + qdel(src) + else + desc = "A translucent balloon with some form of liquid sloshing around in it." + to_chat(user, "You fill the balloon with the contents of [O].") + O.reagents.trans_to(src, 10) + update_icon() + return + +/obj/item/toy/balloon/throw_impact(atom/hit_atom) + if(reagents.total_volume >= 1) + visible_message("The [src] bursts!","You hear a pop and a splash.") + reagents.reaction(get_turf(hit_atom)) + for(var/atom/A in get_turf(hit_atom)) + reagents.reaction(A) + icon_state = "burst" + spawn(5) + if(src) + qdel(src) + return + +/obj/item/toy/balloon/update_icon() + if(src.reagents.total_volume >= 1) + icon_state = "waterballoon" + item_state = "balloon" + else + icon_state = "waterballoon-e" + item_state = "balloon-empty" + +/obj/item/toy/syndicateballoon + name = "syndicate balloon" + desc = "There is a tag on the back that reads \"FUK NT!11!\"." + throwforce = 0 + throw_speed = 4 + throw_range = 20 + force = 0 + icon_state = "syndballoon" + item_state = "syndballoon" + w_class = WEIGHT_CLASS_BULKY + var/lastused = null + +/obj/item/toy/syndicateballoon/attack_self(mob/user) + if(world.time - lastused < CLICK_CD_MELEE) + return + var/playverb = pick("bat [src]", "tug on [src]'s string", "play with [src]") + user.visible_message("[user] plays with [src].", "You [playverb].") + lastused = world.time + +/* + * Fake telebeacon + */ +/obj/item/toy/blink + name = "electronic blink toy game" + desc = "Blink. Blink. Blink. Ages 8 and up." + icon = 'icons/obj/radio.dmi' + icon_state = "beacon" + item_state = "signaler" + +/* + * Fake singularity + */ +/obj/item/toy/spinningtoy + name = "Gravitational Singularity" + desc = "\"Singulo\" brand spinning toy." + icon = 'icons/obj/singularity.dmi' + icon_state = "singularity_s1" + +/* + * Toy swords + */ +/obj/item/toy/sword + name = "toy sword" + desc = "A cheap, plastic replica of an energy sword. Realistic sounds! Ages 8 and up." + icon_state = "sword0" + item_state = "sword0" + var/active = FALSE + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("attacked", "struck", "hit") + +/obj/item/toy/sword/attack_self(mob/user) + active = !active + if(active) + to_chat(user, "You extend the plastic blade with a quick flick of your wrist.") + playsound(user, 'sound/weapons/saberon.ogg', 20, 1) + icon_state = "swordblue" + item_state = "swordblue" + w_class = WEIGHT_CLASS_BULKY + else + to_chat(user, "You push the plastic blade back down into the handle.") + playsound(user, 'sound/weapons/saberoff.ogg', 20, 1) + icon_state = "sword0" + item_state = "sword0" + w_class = WEIGHT_CLASS_SMALL + + if(istype(user,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.update_inv_l_hand() + H.update_inv_r_hand() + add_fingerprint(user) + return + +// Copied from /obj/item/melee/energy/sword/attackby +/obj/item/toy/sword/attackby(obj/item/W, mob/living/user, params) + ..() + if(istype(W, /obj/item/toy/sword)) + if(W == src) + to_chat(user, "You try to attach the end of the plastic sword to... itself. You're not very smart, are you?") + if(ishuman(user)) + user.adjustBrainLoss(10) + else if((W.flags & NODROP) || (flags & NODROP)) + to_chat(user, "\the [flags & NODROP ? src : W] is stuck to your hand, you can't attach it to \the [flags & NODROP ? W : src]!") + else + to_chat(user, "You attach the ends of the two plastic swords, making a single double-bladed toy! You're fake-cool.") + new /obj/item/twohanded/dualsaber/toy(user.loc) + user.unEquip(W) + user.unEquip(src) + qdel(W) + qdel(src) + +/* + * Subtype of Double-Bladed Energy Swords + */ +/obj/item/twohanded/dualsaber/toy + name = "double-bladed toy sword" + desc = "A cheap, plastic replica of TWO energy swords. Double the fun!" + force = 0 + throwforce = 0 + throw_speed = 3 + throw_range = 5 + force_unwielded = 0 + force_wielded = 0 + origin_tech = null + attack_verb = list("attacked", "struck", "hit") + brightness_on = 0 + sharp_when_wielded = FALSE // It's a toy + +/obj/item/twohanded/dualsaber/toy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + return 0 + +/obj/item/twohanded/dualsaber/toy/IsReflect()//Stops Toy Dualsabers from reflecting energy projectiles + return 0 + +/obj/item/toy/katana + name = "replica katana" + desc = "Woefully underpowered in D20." + icon_state = "katana" + item_state = "katana" + flags = CONDUCT + slot_flags = SLOT_BELT | SLOT_BACK + force = 5 + throwforce = 5 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("attacked", "slashed", "stabbed", "sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + +/obj/item/toy/katana/suicide_act(mob/user) + var/dmsg = pick("[user] tries to stab \the [src] into [user.p_their()] abdomen, but it shatters! [user.p_they(TRUE)] look[user.p_s()] as if [user.p_they()] might die from the shame.","[user] tries to stab \the [src] into [user.p_their()] abdomen, but \the [src] bends and breaks in half! [user.p_they(TRUE)] look[user.p_s()] as if [user.p_they()] might die from the shame.","[user] tries to slice [user.p_their()] own throat, but the plastic blade has no sharpness, causing [user.p_them()] to lose [user.p_their()] balance, slip over, and break [user.p_their()] neck with a loud snap!") + user.visible_message("[dmsg] It looks like [user.p_theyre()] trying to commit suicide.") + return BRUTELOSS + + +/* + * Snap pops viral shit + */ +/obj/item/toy/snappop/virus + name = "unstable goo" + desc = "Your palm is oozing this stuff!" + icon = 'icons/mob/slimes.dmi' + icon_state = "red slime extract" + throwforce = 5.0 + throw_speed = 10 + throw_range = 30 + w_class = WEIGHT_CLASS_TINY + + +/obj/item/toy/snappop/virus/throw_impact(atom/hit_atom) + ..() + do_sparks(3, 1, src) + new /obj/effect/decal/cleanable/ash(src.loc) + visible_message("The [name] explodes!","You hear a bang!") + playsound(src, 'sound/effects/snap.ogg', 50, 1) + qdel(src) + +/* + * Snap pops + */ +/obj/item/toy/snappop + name = "snap pop" + desc = "Wow!" + icon = 'icons/obj/toy.dmi' + icon_state = "snappop" + w_class = WEIGHT_CLASS_TINY + var/ash_type = /obj/effect/decal/cleanable/ash + +/obj/item/toy/snappop/proc/pop_burst(var/n=3, var/c=1) + do_sparks(n, c, src) + new ash_type(loc) + visible_message("[src] explodes!", + "You hear a snap!") + playsound(src, 'sound/effects/snap.ogg', 50, 1) + qdel(src) + +/obj/item/toy/snappop/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + ..() + pop_burst() + +/obj/item/toy/snappop/throw_impact(atom/hit_atom) + ..() + pop_burst() + +/obj/item/toy/snappop/Crossed(H as mob|obj, oldloc) + if(ishuman(H) || issilicon(H)) //i guess carp and shit shouldn't set them off + var/mob/living/carbon/M = H + if(issilicon(H) || M.m_intent == MOVE_INTENT_RUN) + to_chat(M, "You step on the snap pop!") + pop_burst(2, 0) + +/obj/item/toy/snappop/phoenix + name = "phoenix snap pop" + desc = "Wow! And wow! And wow!" + ash_type = /obj/effect/decal/cleanable/ash/snappop_phoenix + +/obj/effect/decal/cleanable/ash/snappop_phoenix + var/respawn_time = 300 + +/obj/effect/decal/cleanable/ash/snappop_phoenix/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, .proc/respawn), respawn_time) + +/obj/effect/decal/cleanable/ash/snappop_phoenix/proc/respawn() + new /obj/item/toy/snappop/phoenix(get_turf(src)) + qdel(src) + + +/* + * Mech prizes + */ +/obj/item/toy/prize + icon = 'icons/obj/toy.dmi' + icon_state = "ripleytoy" + var/cooldown = 0 + +//all credit to skasi for toy mech fun ideas +/obj/item/toy/prize/attack_self(mob/user as mob) + if(cooldown < world.time - 8) + to_chat(user, "You play with [src].") + playsound(user, 'sound/mecha/mechstep.ogg', 20, 1) + cooldown = world.time + +/obj/item/toy/prize/attack_hand(mob/user as mob) + if(loc == user) + if(cooldown < world.time - 8) + to_chat(user, "You play with [src].") + playsound(user, 'sound/mecha/mechturn.ogg', 20, 1) + cooldown = world.time + return + ..() + +/obj/random/mech + name = "Random Mech Prize" + desc = "This is a random prize" + icon = 'icons/obj/toy.dmi' + icon_state = "ripleytoy" + +/obj/random/mech/item_to_spawn() + return pick(subtypesof(/obj/item/toy/prize)) //exclude the base type. + +/obj/item/toy/prize/ripley + name = "toy ripley" + desc = "Mini-Mecha action figure! Collect them all! 1/11." + +/obj/item/toy/prize/fireripley + name = "toy firefighting ripley" + desc = "Mini-Mecha action figure! Collect them all! 2/11." + icon_state = "fireripleytoy" + +/obj/item/toy/prize/deathripley + name = "toy deathsquad ripley" + desc = "Mini-Mecha action figure! Collect them all! 3/11." + icon_state = "deathripleytoy" + +/obj/item/toy/prize/gygax + name = "toy gygax" + desc = "Mini-Mecha action figure! Collect them all! 4/11." + icon_state = "gygaxtoy" + +/obj/item/toy/prize/durand + name = "toy durand" + desc = "Mini-Mecha action figure! Collect them all! 5/11." + icon_state = "durandprize" + +/obj/item/toy/prize/honk + name = "toy H.O.N.K." + desc = "Mini-Mecha action figure! Collect them all! 6/11." + icon_state = "honkprize" + +/obj/item/toy/prize/marauder + name = "toy marauder" + desc = "Mini-Mecha action figure! Collect them all! 7/11." + icon_state = "marauderprize" + +/obj/item/toy/prize/seraph + name = "toy seraph" + desc = "Mini-Mecha action figure! Collect them all! 8/11." + icon_state = "seraphprize" + +/obj/item/toy/prize/mauler + name = "toy mauler" + desc = "Mini-Mecha action figure! Collect them all! 9/11." + icon_state = "maulerprize" + +/obj/item/toy/prize/odysseus + name = "toy odysseus" + desc = "Mini-Mecha action figure! Collect them all! 10/11." + icon_state = "odysseusprize" + +/obj/item/toy/prize/phazon + name = "toy phazon" + desc = "Mini-Mecha action figure! Collect them all! 11/11." + icon_state = "phazonprize" + + + +/* +|| A Deck of Cards for playing various games of chance || +*/ + + + +obj/item/toy/cards + resistance_flags = FLAMMABLE + max_integrity = 50 + var/parentdeck = null + var/deckstyle = "nanotrasen" + var/card_hitsound = null + var/card_force = 0 + var/card_throwforce = 0 + var/card_throw_speed = 4 + var/card_throw_range = 20 + var/list/card_attack_verb = list("attacked") + +obj/item/toy/cards/New() + ..() + +obj/item/toy/cards/proc/apply_card_vars(obj/item/toy/cards/newobj, obj/item/toy/cards/sourceobj) // Applies variables for supporting multiple types of card deck + if(!istype(sourceobj)) + return + +obj/item/toy/cards/deck + name = "deck of cards" + desc = "A deck of space-grade playing cards." + icon = 'icons/obj/toy.dmi' + deckstyle = "nanotrasen" + icon_state = "deck_nanotrasen_full" + w_class = WEIGHT_CLASS_SMALL + var/cooldown = 0 + var/list/cards = list() + +obj/item/toy/cards/deck/New() + ..() + icon_state = "deck_[deckstyle]_full" + for(var/i in 2 to 10) + cards += "[i] of Hearts" + cards += "[i] of Spades" + cards += "[i] of Clubs" + cards += "[i] of Diamonds" + cards += "King of Hearts" + cards += "King of Spades" + cards += "King of Clubs" + cards += "King of Diamonds" + cards += "Queen of Hearts" + cards += "Queen of Spades" + cards += "Queen of Clubs" + cards += "Queen of Diamonds" + cards += "Jack of Hearts" + cards += "Jack of Spades" + cards += "Jack of Clubs" + cards += "Jack of Diamonds" + cards += "Ace of Hearts" + cards += "Ace of Spades" + cards += "Ace of Clubs" + cards += "Ace of Diamonds" + +obj/item/toy/cards/deck/attack_hand(mob/user as mob) + var/choice = null + if(cards.len == 0) + icon_state = "deck_[deckstyle]_empty" + to_chat(user, "There are no more cards to draw.") + return + var/obj/item/toy/cards/singlecard/H = new/obj/item/toy/cards/singlecard(user.loc) + choice = cards[1] + H.cardname = choice + H.parentdeck = src + var/O = src + H.apply_card_vars(H,O) + cards -= choice + H.pickup(user) + user.put_in_active_hand(H) + visible_message("[user] draws a card from the deck.", "You draw a card from the deck.") + update_icon() + +obj/item/toy/cards/deck/attack_self(mob/user as mob) + if(cooldown < world.time - 50) + cards = shuffle(cards) + playsound(user, 'sound/items/cardshuffle.ogg', 50, 1) + user.visible_message("[user] shuffles the deck.", "You shuffle the deck.") + cooldown = world.time + +obj/item/toy/cards/deck/attackby(obj/item/toy/cards/singlecard/C, mob/living/user, params) + ..() + if(istype(C)) + if(C.parentdeck == src) + if(!user.unEquip(C)) + to_chat(user, "The card is stuck to your hand, you can't add it to the deck!") + return + cards += C.cardname + user.visible_message("[user] adds a card to the bottom of the deck.","You add the card to the bottom of the deck.") + qdel(C) + else + to_chat(user, "You can't mix cards from other decks.") + update_icon() + + +obj/item/toy/cards/deck/attackby(obj/item/toy/cards/cardhand/C, mob/living/user, params) + ..() + if(istype(C)) + if(C.parentdeck == src) + if(!user.unEquip(C)) + to_chat(user, "The hand of cards is stuck to your hand, you can't add it to the deck!") + return + cards += C.currenthand + user.visible_message("[user] puts [user.p_their()] hand of cards in the deck.", "You put the hand of cards in the deck.") + qdel(C) + else + to_chat(user, "You can't mix cards from other decks.") + update_icon() + +obj/item/toy/cards/deck/MouseDrop(atom/over_object) + var/mob/M = usr + if(usr.stat || !ishuman(usr) || !usr.canmove || usr.restrained()) + return + if(Adjacent(usr)) + if(over_object == M && loc != M) + M.put_in_hands(src) + to_chat(usr, "You pick up the deck.") + + else if(istype(over_object, /obj/screen)) + switch(over_object.name) + if("l_hand") + if(!remove_item_from_storage(M)) + M.unEquip(src) + M.put_in_l_hand(src) + to_chat(usr, "You pick up the deck.") + if("r_hand") + if(!remove_item_from_storage(M)) + M.unEquip(src) + M.put_in_r_hand(src) + to_chat(usr, "You pick up the deck.") + else + to_chat(usr, "You can't reach it from here.") + +obj/item/toy/cards/deck/update_icon() + switch(cards.len) + if(0) + icon_state = "deck_[deckstyle]_empty" + if(1 to 10) + icon_state = "deck_[deckstyle]_low" + if(11 to 26) + icon_state = "deck_[deckstyle]_half" + else + icon_state = "deck_[deckstyle]_full" + +obj/item/toy/cards/cardhand + name = "hand of cards" + desc = "A number of cards not in a deck, customarily held in ones hand." + icon = 'icons/obj/toy.dmi' + icon_state = "nanotrasen_hand2" + w_class = WEIGHT_CLASS_TINY + var/list/currenthand = list() + var/choice = null + + +obj/item/toy/cards/cardhand/attack_self(mob/user as mob) + user.set_machine(src) + interact(user) + +obj/item/toy/cards/cardhand/interact(mob/user) + var/dat = "You have:
        " + for(var/t in currenthand) + dat += "A [t].
        " + dat += "Which card will you remove next?" + var/datum/browser/popup = new(user, "cardhand", "Hand of Cards", 400, 240) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.set_content(dat) + popup.open() + + +obj/item/toy/cards/cardhand/Topic(href, href_list) + if(..()) + return + if(usr.stat || !ishuman(usr) || !usr.canmove) + return + var/mob/living/carbon/human/cardUser = usr + var/O = src + if(href_list["pick"]) + if(cardUser.get_item_by_slot(slot_l_hand) == src || cardUser.get_item_by_slot(slot_r_hand) == src) + var/choice = href_list["pick"] + var/obj/item/toy/cards/singlecard/C = new/obj/item/toy/cards/singlecard(cardUser.loc) + currenthand -= choice + C.parentdeck = src.parentdeck + C.cardname = choice + C.apply_card_vars(C,O) + C.pickup(cardUser) + cardUser.put_in_any_hand_if_possible(C) + cardUser.visible_message("[cardUser] draws a card from [cardUser.p_their()] hand.", "You take the [C.cardname] from your hand.") + + interact(cardUser) + update_icon() + if(currenthand.len == 1) + var/obj/item/toy/cards/singlecard/N = new/obj/item/toy/cards/singlecard(src.loc) + N.parentdeck = src.parentdeck + N.cardname = src.currenthand[1] + N.apply_card_vars(N,O) + cardUser.unEquip(src) + N.pickup(cardUser) + cardUser.put_in_any_hand_if_possible(N) + to_chat(cardUser, "You also take [currenthand[1]] and hold it.") + cardUser << browse(null, "window=cardhand") + qdel(src) + return + +obj/item/toy/cards/cardhand/attackby(obj/item/toy/cards/singlecard/C, mob/living/user, params) + if(istype(C)) + if(C.parentdeck == parentdeck) + currenthand += C.cardname + user.unEquip(C) + user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [C.cardname] to your hand.") + interact(user) + update_icon() + qdel(C) + else + to_chat(user, "You can't mix cards from other decks.") + +obj/item/toy/cards/cardhand/apply_card_vars(obj/item/toy/cards/newobj,obj/item/toy/cards/sourceobj) + ..() + newobj.deckstyle = sourceobj.deckstyle + newobj.icon_state = "[deckstyle]_hand2" // Another dumb hack, without this the hand is invisible (or has the default deckstyle) until another card is added. + newobj.card_hitsound = sourceobj.card_hitsound + newobj.card_force = sourceobj.card_force + newobj.card_throwforce = sourceobj.card_throwforce + newobj.card_throw_speed = sourceobj.card_throw_speed + newobj.card_throw_range = sourceobj.card_throw_range + newobj.card_attack_verb = sourceobj.card_attack_verb + newobj.resistance_flags = sourceobj.resistance_flags + + +obj/item/toy/cards/singlecard + name = "card" + desc = "a card" + icon = 'icons/obj/toy.dmi' + icon_state = "singlecard_nanotrasen_down" + w_class = WEIGHT_CLASS_TINY + var/cardname = null + var/flipped = 0 + pixel_x = -5 + + +obj/item/toy/cards/singlecard/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0) + if(ishuman(user)) + var/mob/living/carbon/human/cardUser = user + if(cardUser.get_item_by_slot(slot_l_hand) == src || cardUser.get_item_by_slot(slot_r_hand) == src) + cardUser.visible_message("[cardUser] checks [cardUser.p_their()] card.", "The card reads: [src.cardname]") + else + . += "You need to have the card in your hand to check it." + + +obj/item/toy/cards/singlecard/verb/Flip() + set name = "Flip Card" + set category = "Object" + set src in range(1) + if(usr.stat || !ishuman(usr) || !usr.canmove || usr.restrained()) + return + if(!flipped) + flipped = 1 + if(cardname) + icon_state = "sc_[cardname]_[deckstyle]" + name = cardname + else + icon_state = "sc_Ace of Spades_[deckstyle]" + name = "What Card" + pixel_x = 5 + else + flipped = 0 + icon_state = "singlecard_down_[deckstyle]" + name = "card" + pixel_x = -5 + +obj/item/toy/cards/singlecard/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/toy/cards/singlecard/)) + var/obj/item/toy/cards/singlecard/C = I + if(C.parentdeck == parentdeck) + var/obj/item/toy/cards/cardhand/H = new/obj/item/toy/cards/cardhand(user.loc) + H.currenthand += C.cardname + H.currenthand += cardname + H.parentdeck = C.parentdeck + H.apply_card_vars(H,C) + user.unEquip(C) + H.pickup(user) + user.put_in_active_hand(H) + to_chat(user, "You combine the [C.cardname] and the [cardname] into a hand.") + qdel(C) + qdel(src) + else + to_chat(user, "You can't mix cards from other decks.") + + if(istype(I, /obj/item/toy/cards/cardhand/)) + var/obj/item/toy/cards/cardhand/H = I + if(H.parentdeck == parentdeck) + H.currenthand += cardname + user.unEquip(src) + user.visible_message("[user] adds a card to [user.p_their()] hand.", "You add the [cardname] to your hand.") + H.interact(user) + H.update_icon() + qdel(src) + else + to_chat(user, "You can't mix cards from other decks.") + +obj/item/toy/cards/cardhand/update_icon() + switch(currenthand.len) + if(0 to 1) + return + if(2) + icon_state = "[deckstyle]_hand2" + if(3) + icon_state = "[deckstyle]_hand3" + if(4) + icon_state = "[deckstyle]_hand4" + else + icon_state = "[deckstyle]_hand5" + + +obj/item/toy/cards/singlecard/attack_self(mob/user) + if(usr.stat || !ishuman(usr) || !usr.canmove || usr.restrained()) + return + Flip() + +obj/item/toy/cards/singlecard/apply_card_vars(obj/item/toy/cards/singlecard/newobj,obj/item/toy/cards/sourceobj) + ..() + newobj.deckstyle = sourceobj.deckstyle + newobj.icon_state = "singlecard_down_[deckstyle]" // Without this the card is invisible until flipped. It's an ugly hack, but it works. + newobj.card_hitsound = sourceobj.card_hitsound + newobj.hitsound = newobj.card_hitsound + newobj.card_force = sourceobj.card_force + newobj.force = newobj.card_force + newobj.card_throwforce = sourceobj.card_throwforce + newobj.throwforce = newobj.card_throwforce + newobj.card_throw_speed = sourceobj.card_throw_speed + newobj.throw_speed = newobj.card_throw_speed + newobj.card_throw_range = sourceobj.card_throw_range + newobj.throw_range = newobj.card_throw_range + newobj.card_attack_verb = sourceobj.card_attack_verb + newobj.attack_verb = newobj.card_attack_verb + + +/* +|| Syndicate playing cards, for pretending you're Gambit and playing poker for the nuke disk. || +*/ + +obj/item/toy/cards/deck/syndicate + name = "suspicious looking deck of cards" + desc = "A deck of space-grade playing cards. They seem unusually rigid." + deckstyle = "syndicate" + card_hitsound = 'sound/weapons/bladeslice.ogg' + card_force = 5 + card_throwforce = 10 + card_throw_speed = 3 + card_throw_range = 20 + card_attack_verb = list("attacked", "sliced", "diced", "slashed", "cut") + resistance_flags = NONE + +/* +|| Custom card decks || +*/ +obj/item/toy/cards/deck/black + deckstyle = "black" + +obj/item/toy/cards/deck/syndicate/black + deckstyle = "black" + +/obj/item/toy/nuke + name = "\improper Nuclear Fission Explosive toy" + desc = "A plastic model of a Nuclear Fission Explosive." + icon = 'icons/obj/toy.dmi' + icon_state = "nuketoyidle" + w_class = WEIGHT_CLASS_SMALL + var/cooldown = 0 + +/obj/item/toy/nuke/attack_self(mob/user) + if(cooldown < world.time) + cooldown = world.time + 1800 //3 minutes + user.visible_message("[user] presses a button on [src]", "You activate [src], it plays a loud noise!", "You hear the click of a button.") + spawn(5) //gia said so + icon_state = "nuketoy" + playsound(src, 'sound/machines/alarm.ogg', 100, 0, 0) + sleep(135) + icon_state = "nuketoycool" + sleep(cooldown - world.time) + icon_state = "nuketoyidle" + else + var/timeleft = (cooldown - world.time) + to_chat(user, "Nothing happens, and '[round(timeleft/10)]' appears on a small display.") + +/obj/item/toy/therapy + name = "therapy doll" + desc = "A toy for therapeutic and recreational purposes." + icon = 'icons/obj/toy.dmi' + icon_state = "therapyred" + item_state = "egg4" + w_class = WEIGHT_CLASS_TINY + var/cooldown = 0 + resistance_flags = FLAMMABLE + +/obj/item/toy/therapy/New() + ..() + if(item_color) + name = "[item_color] therapy doll" + desc += " This one is [item_color]." + icon_state = "therapy[item_color]" + +/obj/item/toy/therapy/attack_self(mob/user) + if(cooldown < world.time - 8) + to_chat(user, "You relieve some stress with \the [src].") + playsound(user, 'sound/items/squeaktoy.ogg', 20, 1) + cooldown = world.time + +/obj/random/therapy + name = "Random Therapy Doll" + desc = "This is a random therapy doll." + icon = 'icons/obj/toy.dmi' + icon_state = "therapyred" + +/obj/random/therapy/item_to_spawn() + return pick(subtypesof(/obj/item/toy/therapy)) //exclude the base type. + +/obj/item/toy/therapy/red + item_state = "egg4" // It's the red egg in items_left/righthand + item_color = "red" + +/obj/item/toy/therapy/purple + item_state = "egg1" // It's the magenta egg in items_left/righthand + item_color = "purple" + +/obj/item/toy/therapy/blue + item_state = "egg2" // It's the blue egg in items_left/righthand + item_color = "blue" + +/obj/item/toy/therapy/yellow + item_state = "egg5" // It's the yellow egg in items_left/righthand + item_color = "yellow" + +/obj/item/toy/therapy/orange + item_state = "egg4" // It's the red one again, lacking an orange item_state and making a new one is pointless + item_color = "orange" + +/obj/item/toy/therapy/green + item_state = "egg3" // It's the green egg in items_left/righthand + item_color = "green" + +/obj/item/toddler + icon_state = "toddler" + name = "toddler" + desc = "This baby looks almost real. Wait, did it just burp?" + force = 5 + w_class = WEIGHT_CLASS_BULKY + slot_flags = SLOT_BACK + + +//This should really be somewhere else but I don't know where. w/e + +/obj/item/inflatable_duck + name = "inflatable duck" + desc = "No bother to sink or swim when you can just float!" + icon_state = "inflatable" + item_state = "inflatable" + icon = 'icons/obj/clothing/belts.dmi' + slot_flags = SLOT_BELT + +/* + * Fake meteor + */ + +/obj/item/toy/minimeteor + name = "Mini-Meteor" + desc = "Relive the excitement of a meteor shower! SweetMeat-eor. Co is not responsible for any injuries, headaches or hearing loss caused by Mini-Meteor." + icon = 'icons/obj/toy.dmi' + icon_state = "minimeteor" + w_class = WEIGHT_CLASS_SMALL + +/obj/item/toy/minimeteor/throw_impact(atom/hit_atom) + ..() + playsound(src, 'sound/effects/meteorimpact.ogg', 40, 1) + for(var/mob/M in range(10, src)) + if(!M.stat && !istype(M, /mob/living/silicon/ai))\ + shake_camera(M, 3, 1) + qdel(src) + +/* + * Carp plushie + */ + +/obj/item/toy/carpplushie + name = "space carp plushie" + desc = "An adorable stuffed toy that resembles a space carp." + icon = 'icons/obj/toy.dmi' + icon_state = "carpplushie" + attack_verb = list("bitten", "eaten", "fin slapped") + var/bitesound = 'sound/weapons/bite.ogg' + resistance_flags = FLAMMABLE + +// Attack mob +/obj/item/toy/carpplushie/attack(mob/M as mob, mob/user as mob) + playsound(loc, bitesound, 20, 1) // Play bite sound in local area + return ..() + +// Attack self +/obj/item/toy/carpplushie/attack_self(mob/user as mob) + playsound(src.loc, bitesound, 20, 1) + return ..() + + +/obj/random/carp_plushie + name = "Random Carp Plushie" + desc = "This is a random plushie" + icon = 'icons/obj/toy.dmi' + icon_state = "carpplushie" + +/obj/random/carp_plushie/item_to_spawn() + return pick(typesof(/obj/item/toy/carpplushie)) //can pick any carp plushie, even the original. + +/obj/item/toy/carpplushie/ice + icon_state = "icecarp" + +/obj/item/toy/carpplushie/silent + icon_state = "silentcarp" + +/obj/item/toy/carpplushie/electric + icon_state = "electriccarp" + +/obj/item/toy/carpplushie/gold + icon_state = "goldcarp" + +/obj/item/toy/carpplushie/toxin + icon_state = "toxincarp" + +/obj/item/toy/carpplushie/dragon + icon_state = "dragoncarp" + +/obj/item/toy/carpplushie/pink + icon_state = "pinkcarp" + +/obj/item/toy/carpplushie/candy + icon_state = "candycarp" + +/obj/item/toy/carpplushie/nebula + icon_state = "nebulacarp" + +/obj/item/toy/carpplushie/void + icon_state = "voidcarp" + +/* + * Plushie + */ + + +/obj/item/toy/plushie + name = "plushie" + desc = "An adorable, soft, and cuddly plushie." + icon = 'icons/obj/toy.dmi' + var/poof_sound = 'sound/weapons/thudswoosh.ogg' + attack_verb = list("poofed", "bopped", "whapped","cuddled","fluffed") + resistance_flags = FLAMMABLE + +/obj/item/toy/plushie/attack(mob/M as mob, mob/user as mob) + playsound(loc, poof_sound, 20, 1) // Play the whoosh sound in local area + if(iscarbon(M)) + if(prob(10)) + M.reagents.add_reagent("hugs", 10) + return ..() + +/obj/item/toy/plushie/attack_self(mob/user as mob) + var/cuddle_verb = pick("hugs","cuddles","snugs") + user.visible_message("[user] [cuddle_verb] the [src].") + playsound(get_turf(src), poof_sound, 50, 1, -1) + return ..() + +/obj/random/plushie + name = "Random Plushie" + desc = "This is a random plushie" + icon = 'icons/obj/toy.dmi' + icon_state = "redfox" + +/obj/random/plushie/item_to_spawn() + return pick(subtypesof(/obj/item/toy/plushie) - typesof(/obj/item/toy/plushie/fluff)) //exclude the base type. + +/obj/item/toy/plushie/corgi + name = "corgi plushie" + icon_state = "corgi" + +/obj/item/toy/plushie/girly_corgi + name = "corgi plushie" + icon_state = "girlycorgi" + +/obj/item/toy/plushie/robo_corgi + name = "borgi plushie" + icon_state = "robotcorgi" + +/obj/item/toy/plushie/octopus + name = "octopus plushie" + icon_state = "loveable" + +/obj/item/toy/plushie/face_hugger + name = "facehugger plushie" + icon_state = "huggable" + +//foxes are basically the best + +/obj/item/toy/plushie/red_fox + name = "red fox plushie" + icon_state = "redfox" + +/obj/item/toy/plushie/black_fox + name = "black fox plushie" + icon_state = "blackfox" + +/obj/item/toy/plushie/marble_fox + name = "marble fox plushie" + icon_state = "marblefox" + +/obj/item/toy/plushie/blue_fox + name = "blue fox plushie" + icon_state = "bluefox" + +/obj/item/toy/plushie/orange_fox + name = "orange fox plushie" + icon_state = "orangefox" + +/obj/item/toy/plushie/coffee_fox + name = "coffee fox plushie" + icon_state = "coffeefox" + +/obj/item/toy/plushie/pink_fox + name = "pink fox plushie" + icon_state = "pinkfox" + +/obj/item/toy/plushie/purple_fox + name = "purple fox plushie" + icon_state = "purplefox" + +/obj/item/toy/plushie/crimson_fox + name = "crimson fox plushie" + icon_state = "crimsonfox" + +/obj/item/toy/plushie/deer + name = "deer plushie" + icon_state = "deer" + +/obj/item/toy/plushie/black_cat + name = "black cat plushie" + icon_state = "blackcat" + +/obj/item/toy/plushie/grey_cat + name = "grey cat plushie" + icon_state = "greycat" + +/obj/item/toy/plushie/white_cat + name = "white cat plushie" + icon_state = "whitecat" + +/obj/item/toy/plushie/orange_cat + name = "orange cat plushie" + icon_state = "orangecat" + +/obj/item/toy/plushie/siamese_cat + name = "siamese cat plushie" + icon_state = "siamesecat" + +/obj/item/toy/plushie/tabby_cat + name = "tabby cat plushie" + icon_state = "tabbycat" + +/obj/item/toy/plushie/tuxedo_cat + name = "tuxedo cat plushie" + icon_state = "tuxedocat" + +/obj/item/toy/plushie/voxplushie + name = "vox plushie" + desc = "A stitched-together vox, fresh from the skipjack. Press its belly to hear it skree!" + icon_state = "plushie_vox" + item_state = "plushie_vox" + var/cooldown = 0 + +/obj/item/toy/plushie/voxplushie/attack_self(mob/user) + if(!cooldown) + playsound(user, 'sound/voice/shriek1.ogg', 10, 0) + visible_message("Skreee!") + cooldown = 1 + spawn(30) cooldown = 0 + return + ..() + +//New generation TG plushies + +/obj/item/toy/plushie/lizardplushie + name = "lizard plushie" + desc = "An adorable stuffed toy that resembles a lizardperson." + icon_state = "plushie_lizard" + item_state = "plushie_lizard" + +/obj/item/toy/plushie/snakeplushie + name = "snake plushie" + desc = "An adorable stuffed toy that resembles a snake. Not to be mistaken for the real thing." + icon_state = "plushie_snake" + item_state = "plushie_snake" + +/obj/item/toy/plushie/nukeplushie + name = "operative plushie" + desc = "An stuffed toy that resembles a syndicate nuclear operative. The tag claims operatives to be purely fictitious." + icon_state = "plushie_nuke" + item_state = "plushie_nuke" + +/obj/item/toy/plushie/slimeplushie + name = "slime plushie" + desc = "An adorable stuffed toy that resembles a slime. It is practically just a hacky sack." + icon_state = "plushie_slime" + item_state = "plushie_slime" + +/* + * Foam Armblade + */ + + /obj/item/toy/foamblade + name = "foam armblade" + desc = "it says \"Sternside Changs #1 fan\" on it. " + icon = 'icons/obj/toy.dmi' + icon_state = "foamblade" + item_state = "arm_blade" + attack_verb = list("pricked", "absorbed", "gored") + w_class = WEIGHT_CLASS_SMALL + resistance_flags = FLAMMABLE + +/* + * Toy/fake flash + */ +/obj/item/toy/flash + name = "toy flash" + desc = "FOR THE REVOLU- Oh wait, that's just a toy." + icon = 'icons/obj/device.dmi' + icon_state = "flash" + item_state = "flashtool" + w_class = WEIGHT_CLASS_TINY + +/obj/item/toy/flash/attack(mob/living/M, mob/user) + playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1) + flick("[initial(icon_state)]2", src) + user.visible_message("[user] blinds [M] with the flash!") + + +/* + * Toy big red button + */ +/obj/item/toy/redbutton + name = "big red button" + desc = "A big, plastic red button. Reads 'From HonkCo Pranks?' on the back." + icon = 'icons/obj/assemblies.dmi' + icon_state = "bigred" + w_class = WEIGHT_CLASS_SMALL + var/cooldown = 0 + +/obj/item/toy/redbutton/attack_self(mob/user) + if(cooldown < world.time) + cooldown = (world.time + 300) // Sets cooldown at 30 seconds + user.visible_message("[user] presses the big red button.", "You press the button, it plays a loud noise!", "The button clicks loudly.") + playsound(src, 'sound/effects/explosionfar.ogg', 50, 0, 0) + for(var/mob/M in range(10, src)) // Checks range + if(!M.stat && !istype(M, /mob/living/silicon/ai)) // Checks to make sure whoever's getting shaken is alive/not the AI + sleep(8) // Short delay to match up with the explosion sound + shake_camera(M, 2, 1) // Shakes player camera 2 squares for 1 second. + + else + to_chat(user, "Nothing happens.") + + +/* + * AI core prizes + */ +/obj/item/toy/AI + name = "toy AI" + desc = "A little toy model AI core with real law announcing action!" + icon = 'icons/obj/toy.dmi' + icon_state = "AI" + w_class = WEIGHT_CLASS_SMALL + var/cooldown = 0 + +/obj/item/toy/AI/attack_self(mob/user) + if(!cooldown) //for the sanity of everyone + var/message = generate_ion_law() + to_chat(user, "You press the button on [src].") + playsound(user, 'sound/machines/click.ogg', 20, 1) + visible_message("[bicon(src)] [message]") + cooldown = 1 + spawn(30) cooldown = 0 + return + ..() + +/obj/item/toy/codex_gigas + name = "Toy Codex Gigas" + desc = "A tool to help you write fictional devils!" + icon = 'icons/obj/library.dmi' + icon_state = "demonomicon" + w_class = WEIGHT_CLASS_SMALL + var/cooldown = FALSE + +/obj/item/toy/codex_gigas/attack_self(mob/user) + if(!cooldown) + user.visible_message( + "[user] presses the button on \the [src].", + "You press the button on \the [src].", + "You hear a soft click.") + var/list/messages = list() + var/datum/devilinfo/devil = randomDevilInfo() + messages += "Some fun facts about: [devil.truename]" + messages += "[lawlorify[LORE][devil.bane]]" + messages += "[lawlorify[LORE][devil.obligation]]" + messages += "[lawlorify[LORE][devil.ban]]" + messages += "[lawlorify[LORE][devil.banish]]" + playsound(loc, 'sound/machines/click.ogg', 20, 1) + cooldown = TRUE + for(var/message in messages) + user.loc.visible_message("[bicon(src)] [message]") + sleep(10) + spawn(20) + cooldown = FALSE + return + ..() + +/obj/item/toy/owl + name = "owl action figure" + desc = "An action figure modeled after 'The Owl', defender of justice." + icon = 'icons/obj/toy.dmi' + icon_state = "owlprize" + w_class = WEIGHT_CLASS_SMALL + var/cooldown = 0 + +/obj/item/toy/owl/attack_self(mob/user) + if(!cooldown) //for the sanity of everyone + var/message = pick("You won't get away this time, Griffin!", "Stop right there, criminal!", "Hoot! Hoot!", "I am the night!") + to_chat(user, "You pull the string on the [src].") + playsound(user, 'sound/creatures/hoot.ogg', 25, 1) + visible_message("[bicon(src)] [message]") + cooldown = 1 + spawn(30) cooldown = 0 + return + ..() + +/obj/item/toy/griffin + name = "griffin action figure" + desc = "An action figure modeled after 'The Griffin', criminal mastermind." + icon = 'icons/obj/toy.dmi' + icon_state = "griffinprize" + w_class = WEIGHT_CLASS_SMALL + var/cooldown = 0 + +/obj/item/toy/griffin/attack_self(mob/user) + if(!cooldown) //for the sanity of everyone + var/message = pick("You can't stop me, Owl!", "My plan is flawless! The vault is mine!", "Caaaawwww!", "You will never catch me!") + to_chat(user, "You pull the string on the [src].") + playsound(user, 'sound/creatures/caw.ogg', 25, 1) + visible_message("[bicon(src)] [message]") + cooldown = 1 + spawn(30) cooldown = 0 + return + ..() + +// DND Character minis. Use the naming convention (type)character for the icon states. +/obj/item/toy/character + icon = 'icons/obj/toy.dmi' + w_class = WEIGHT_CLASS_SMALL + pixel_z = 5 + +/obj/item/toy/character/alien + name = "Xenomorph Miniature" + desc = "A miniature xenomorph. Scary!" + icon_state = "aliencharacter" +/obj/item/toy/character/cleric + name = "Cleric Miniature" + desc = "A wee little cleric, with his wee little staff." + icon_state = "clericcharacter" +/obj/item/toy/character/warrior + name = "Warrior Miniature" + desc = "That sword would make a decent toothpick." + icon_state = "warriorcharacter" +/obj/item/toy/character/thief + name = "Thief Miniature" + desc = "Hey, where did my wallet go!?" + icon_state = "thiefcharacter" +/obj/item/toy/character/wizard + name = "Wizard Miniature" + desc = "MAGIC!" + icon_state = "wizardcharacter" +/obj/item/toy/character/cthulhu + name = "Cthulhu Miniature" + desc = "The dark lord has risen!" + icon_state = "darkmastercharacter" +/obj/item/toy/character/lich + name = "Lich Miniature" + desc = "Murderboner extraordinaire." + icon_state = "lichcharacter" +/obj/item/storage/box/characters + name = "Box of Miniatures" + desc = "The nerd's best friends." + icon_state = "box" +/obj/item/storage/box/characters/New() + ..() + new /obj/item/toy/character/alien(src) + new /obj/item/toy/character/cleric(src) + new /obj/item/toy/character/warrior(src) + new /obj/item/toy/character/thief(src) + new /obj/item/toy/character/wizard(src) + new /obj/item/toy/character/cthulhu(src) + new /obj/item/toy/character/lich(src) + + +//Pet Rocks, just like from the 70's! + +/obj/item/toy/pet_rock + name = "pet rock" + desc = "The perfect pet!" + icon = 'icons/obj/toy.dmi' + icon_state = "pet_rock" + w_class = WEIGHT_CLASS_SMALL + force = 5 + throwforce = 5 + attack_verb = list("attacked", "bashed", "smashed", "stoned") + hitsound = "swing_hit" + +/obj/item/toy/pet_rock/fred + name = "fred" + desc = "Fred, the bestest boy pet in the whole wide universe!" + icon_state = "fred" + +/obj/item/toy/pet_rock/roxie + name = "roxie" + desc = "Roxie, the bestest girl pet in the whole wide universe!" + icon_state = "roxie" + +//minigibber, so cute + +/obj/item/toy/minigibber + name = "miniature gibber" + desc = "A miniature recreation of Nanotrasen's famous meat grinder." + icon = 'icons/obj/toy.dmi' + icon_state = "minigibber" + attack_verb = list("grinded", "gibbed") + w_class = WEIGHT_CLASS_SMALL + var/cooldown = 0 + var/obj/stored_minature = null + +/obj/item/toy/minigibber/attack_self(var/mob/user) + + if(stored_minature) + to_chat(user, "\The [src] makes a violent grinding noise as it tears apart the miniature figure inside!") + QDEL_NULL(stored_minature) + playsound(user, 'sound/goonstation/effects/gib.ogg', 20, 1) + cooldown = world.time + + if(cooldown < world.time - 8) + to_chat(user, "You hit the gib button on \the [src].") + playsound(user, 'sound/goonstation/effects/gib.ogg', 20, 1) + cooldown = world.time + +/obj/item/toy/minigibber/attackby(var/obj/O, var/mob/user, params) + if(istype(O,/obj/item/toy/character) && O.loc == user) + to_chat(user, "You start feeding \the [O] [bicon(O)] into \the [src]'s mini-input.") + if(do_after(user, 10, target = src)) + if(O.loc != user) + to_chat(user, "\The [O] is too far away to feed into \the [src]!") + else + to_chat(user, "You feed \the [O] [bicon(O)] into \the [src]!") + user.unEquip(O) + O.forceMove(src) + stored_minature = O + else + to_chat(user, "You stop feeding \the [O] into \the [src]'s mini-input.") + else ..() + +/* + * Xenomorph action figure + */ + +/obj/item/toy/toy_xeno + icon = 'icons/obj/toy.dmi' + icon_state = "toy_xeno" + name = "xenomorph action figure" + desc = "MEGA presents the new Xenos Isolated action figure! Comes complete with realistic sounds! Pull back string to use." + w_class = WEIGHT_CLASS_SMALL + var/cooldown = 0 + +/obj/item/toy/toy_xeno/attack_self(mob/user) + if(cooldown <= world.time) + cooldown = (world.time + 50) //5 second cooldown + user.visible_message("[user] pulls back the string on [src].") + icon_state = "[initial(icon_state)]_used" + sleep(5) + audible_message("[bicon(src)] Hiss!") + var/list/possible_sounds = list('sound/voice/hiss1.ogg', 'sound/voice/hiss2.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss4.ogg') + playsound(get_turf(src), pick(possible_sounds), 50, 1) + spawn(45) + if(src) + icon_state = "[initial(icon_state)]" + else + to_chat(user, "The string on [src] hasn't rewound all the way!") + return + +/obj/item/toy/russian_revolver + name = "russian revolver" + desc = "for fun and games!" + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "detective_gold" + item_state = "gun" + lefthand_file = 'icons/mob/inhands/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/guns_righthand.dmi' + hitsound = "swing_hit" + flags = CONDUCT + slot_flags = SLOT_BELT + materials = list(MAT_METAL=2000) + w_class = WEIGHT_CLASS_NORMAL + throwforce = 5 + throw_speed = 4 + throw_range = 5 + force = 5 + origin_tech = "combat=1" + attack_verb = list("struck", "hit", "bashed") + var/bullets_left = 0 + var/max_shots = 6 + +/obj/item/toy/russian_revolver/suicide_act(mob/user) + user.visible_message("[user] quickly loads six bullets into [src]'s cylinder and points it at [user.p_their()] head before pulling the trigger! It looks like [user.p_theyre()] trying to commit suicide.") + playsound(loc, 'sound/weapons/gunshots/gunshot_strong.ogg', 50, 1) + return BRUTELOSS + +/obj/item/toy/russian_revolver/New() + ..() + spin_cylinder() + +/obj/item/toy/russian_revolver/attack_self(mob/user) + if(!bullets_left) + user.visible_message("[user] loads a bullet into [src]'s cylinder before spinning it.") + spin_cylinder() + else + user.visible_message("[user] spins the cylinder on [src]!") + spin_cylinder() + +/obj/item/toy/russian_revolver/attack(mob/M, mob/living/user) + return + +/obj/item/toy/russian_revolver/afterattack(atom/target, mob/user, flag, params) + if(flag) + if(target in user.contents) + return + if(!ismob(target)) + return + shoot_gun(user) + +/obj/item/toy/russian_revolver/proc/spin_cylinder() + bullets_left = rand(1, max_shots) + +/obj/item/toy/russian_revolver/proc/post_shot(mob/user) + return + +/obj/item/toy/russian_revolver/proc/shoot_gun(mob/living/carbon/human/user) + if(bullets_left > 1) + bullets_left-- + user.visible_message("*click*") + playsound(src, 'sound/weapons/empty.ogg', 100, 1) + return FALSE + if(bullets_left == 1) + bullets_left = 0 + var/zone = "head" + if(!(user.has_organ(zone))) // If they somehow don't have a head. + zone = "chest" + playsound(src, 'sound/weapons/gunshots/gunshot_strong.ogg', 50, 1) + user.visible_message("[src] goes off!") + post_shot(user) + user.apply_damage(300, BRUTE, zone, sharp = TRUE, used_weapon = "Self-inflicted gunshot wound to the [zone].") + user.bleed(BLOOD_VOLUME_NORMAL) + user.death() // Just in case + return TRUE + else + to_chat(user, "[src] needs to be reloaded.") + return FALSE + +/obj/item/toy/russian_revolver/trick_revolver + name = "\improper .357 revolver" + desc = "A suspicious revolver. Uses .357 ammo." + icon_state = "revolver" + max_shots = 1 + var/fake_bullets = 0 + +/obj/item/toy/russian_revolver/trick_revolver/New() + ..() + fake_bullets = rand(2, 7) + +/obj/item/toy/russian_revolver/trick_revolver/examine(mob/user) //Sneaky sneaky + . = ..() + . += "Has [fake_bullets] round\s remaining." + . += "[fake_bullets] of those are live rounds." + +/obj/item/toy/russian_revolver/trick_revolver/post_shot(user) + to_chat(user, "[src] did look pretty dodgey!") + SEND_SOUND(user, 'sound/misc/sadtrombone.ogg') //HONK +/* + * Rubber Chainsaw + */ +/obj/item/twohanded/toy/chainsaw + name = "Toy Chainsaw" + desc = "A toy chainsaw with a rubber edge. Ages 8 and up" + icon_state = "chainsaw0" + force = 0 + throwforce = 0 + throw_speed = 4 + throw_range = 20 + wieldsound = 'sound/weapons/chainsawstart.ogg' + attack_verb = list("sawed", "cut", "hacked", "carved", "cleaved", "butchered", "felled", "timbered") + +/obj/item/twohanded/toy/chainsaw/update_icon() + if(wielded) + icon_state = "chainsaw[wielded]" + else + icon_state = "chainsaw0" + +/* + * Cat Toy + */ +/obj/item/toy/cattoy + name = "toy mouse" + desc = "A colorful toy mouse!" + icon = 'icons/obj/toy.dmi' + icon_state = "toy_mouse" + w_class = WEIGHT_CLASS_SMALL + resistance_flags = FLAMMABLE + var/cooldown = 0 + +/* + * Action Figures + */ + + +/obj/random/figure + name = "Random Action Figure" + desc = "This is a random toy action figure" + icon = 'icons/obj/toy.dmi' + icon_state = "nuketoy" + +/obj/random/figure/item_to_spawn() + return pick(subtypesof(/obj/item/toy/figure)) + + +/obj/item/toy/figure + name = "Non-Specific Action Figure action figure" + desc = "A \"Space Life\" brand... wait, what the hell is this thing?" + icon = 'icons/obj/toy.dmi' + icon_state = "nuketoy" + var/cooldown = 0 + var/toysay = "What the fuck did you do?" + +/obj/item/toy/figure/New() + ..() + desc = "A \"Space Life\" brand [name]" + +/obj/item/toy/figure/attack_self(mob/user as mob) + if(cooldown < world.time) + cooldown = (world.time + 30) //3 second cooldown + user.visible_message("[bicon(src)] The [src] says \"[toysay]\".") + playsound(user, 'sound/machines/click.ogg', 20, 1) + +/obj/item/toy/figure/cmo + name = "Chief Medical Officer action figure" + icon_state = "cmo" + toysay = "Suit sensors!" + +/obj/item/toy/figure/assistant + name = "Assistant action figure" + icon_state = "assistant" + toysay = "Grey tide station wide!" + +/obj/item/toy/figure/atmos + name = "Atmospheric Technician action figure" + icon_state = "atmos" + toysay = "Glory to Atmosia!" + +/obj/item/toy/figure/bartender + name = "Bartender action figure" + icon_state = "bartender" + toysay = "Wheres my monkey?" + +/obj/item/toy/figure/borg + name = "Cyborg action figure" + icon_state = "borg" + toysay = "I. LIVE. AGAIN." + +/obj/item/toy/figure/botanist + name = "Botanist action figure" + icon_state = "botanist" + toysay = "Dude, I see colors..." + +/obj/item/toy/figure/captain + name = "Captain action figure" + icon_state = "captain" + toysay = "Crew, the Nuke Disk is safely up my ass." + +/obj/item/toy/figure/cargotech + name = "Cargo Technician action figure" + icon_state = "cargotech" + toysay = "For Cargonia!" + +/obj/item/toy/figure/ce + name = "Chief Engineer action figure" + icon_state = "ce" + toysay = "Wire the solars!" + +/obj/item/toy/figure/chaplain + name = "Chaplain action figure" + icon_state = "chaplain" + toysay = "Gods make me a killing machine please!" + +/obj/item/toy/figure/chef + name = "Chef action figure" + icon_state = "chef" + toysay = "I swear it's not human meat." + +/obj/item/toy/figure/chemist + name = "Chemist action figure" + icon_state = "chemist" + toysay = "Get your pills!" + +/obj/item/toy/figure/clown + name = "Clown action figure" + icon_state = "clown" + toysay = "Honk!" + +/obj/item/toy/figure/ian + name = "Ian action figure" + icon_state = "ian" + toysay = "Arf!" + +/obj/item/toy/figure/detective + name = "Detective action figure" + icon_state = "detective" + toysay = "This airlock has grey jumpsuit and insulated glove fibers on it." + +/obj/item/toy/figure/dsquad + name = "Death Squad Officer action figure" + icon_state = "dsquad" + toysay = "Eliminate all threats!" + +/obj/item/toy/figure/engineer + name = "Engineer action figure" + icon_state = "engineer" + toysay = "Oh god, the singularity is loose!" + +/obj/item/toy/figure/geneticist + name = "Geneticist action figure" + icon_state = "geneticist" + toysay = "I'm not qualified for this job." + +/obj/item/toy/figure/hop + name = "Head of Personnel action figure" + icon_state = "hop" + toysay = "Giving out all access!" + +/obj/item/toy/figure/hos + name = "Head of Security action figure" + icon_state = "hos" + toysay = "I'm here to win, anything else is secondary." + +/obj/item/toy/figure/qm + name = "Quartermaster action figure" + icon_state = "qm" + toysay = "Hail Cargonia!" + +/obj/item/toy/figure/janitor + name = "Janitor action figure" + icon_state = "janitor" + toysay = "Look at the signs, you idiot." + +/obj/item/toy/figure/lawyer + name = "Internal Affairs Agent action figure" + icon_state = "lawyer" + toysay = "Standard Operating Procedure says they're guilty! Hacking is proof they're an Enemy of the Corporation!" + +/obj/item/toy/figure/librarian + name = "Librarian action figure" + icon_state = "librarian" + toysay = "One day while..." + +/obj/item/toy/figure/md + name = "Medical Doctor action figure" + icon_state = "md" + toysay = "The patient is already dead!" + +/obj/item/toy/figure/mime + name = "Mime action figure" + desc = "A \"Space Life\" brand Mime action figure." + icon_state = "mime" + toysay = "..." + +/obj/item/toy/figure/miner + name = "Shaft Miner action figure" + icon_state = "miner" + toysay = "Oh god it's eating my intestines!" + +/obj/item/toy/figure/ninja + name = "Ninja action figure" + icon_state = "ninja" + toysay = "Oh god! Stop shooting, I'm friendly!" + +/obj/item/toy/figure/wizard + name = "Wizard action figure" + icon_state = "wizard" + toysay = "Ei Nath!" + +/obj/item/toy/figure/rd + name = "Research Director action figure" + icon_state = "rd" + toysay = "Blowing all of the borgs!" + +/obj/item/toy/figure/roboticist + name = "Roboticist action figure" + icon_state = "roboticist" + toysay = "He asked to be borged!" + +/obj/item/toy/figure/scientist + name = "Scientist action figure" + icon_state = "scientist" + toysay = "Someone else must have made those bombs!" + +/obj/item/toy/figure/syndie + name = "Nuclear Operative action figure" + icon_state = "syndie" + toysay = "Get that fucking disk!" + +/obj/item/toy/figure/secofficer + name = "Security Officer action figure" + icon_state = "secofficer" + toysay = "I am the law!" + +/obj/item/toy/figure/virologist + name = "Virologist action figure" + icon_state = "virologist" + toysay = "The cure is potassium!" + +/obj/item/toy/figure/warden + name = "Warden action figure" + icon_state = "warden" + toysay = "Execute him for breaking in!" + +////////////////////////////////////////////////////// +// Magic 8-Ball / Conch // +////////////////////////////////////////////////////// + +/obj/item/toy/eight_ball + name = "Magic 8-Ball" + desc = "Mystical! Magical! Ages 8+!" + icon = 'icons/obj/toy.dmi' + icon_state = "eight-ball" + var/use_action = "shakes the ball" + var/cooldown = 0 + var/list/possible_answers = list("Definitely", "All signs point to yes.", "Most likely.", "Yes.", "Ask again later.", "Better not tell you now.", "Future Unclear.", "Maybe.", "Doubtful.", "No.", "Don't count on it.", "Never.") + +/obj/item/toy/eight_ball/attack_self(mob/user as mob) + if(!cooldown) + var/answer = pick(possible_answers) + user.visible_message("[user] focuses on [user.p_their()] question and [use_action]...") + user.visible_message("[bicon(src)] The [src] says \"[answer]\"") + spawn(30) + cooldown = 0 + return + +/obj/item/toy/eight_ball/conch + name = "Magic Conch Shell" + desc = "All hail the Magic Conch!" + icon_state = "conch" + use_action = "pulls the string" + possible_answers = list("Yes.", "No.", "Try asking again.", "Nothing.", "I don't think so.", "Neither.", "Maybe someday.") + +/* + *Fake cuffs (honk honk) + */ + +/obj/item/restraints/handcuffs/toy + desc = "Toy handcuffs. Plastic and extremely cheaply made." + throwforce = 0 + breakouttime = 0 + ignoresClumsy = TRUE diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index 4a00f9726a9..7cf48095e72 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -1,107 +1,107 @@ -//Items labled as 'trash' for the trash bag. -//TODO: Make this an item var or something... - -//Added by Jack Rost -/obj/item/trash - icon = 'icons/obj/trash.dmi' - w_class = WEIGHT_CLASS_TINY - desc = "This is rubbish." - resistance_flags = FLAMMABLE - -/obj/item/trash/raisins - name = "4no raisins" - icon_state= "4no_raisins" - -/obj/item/trash/candy - name = "Candy" - icon_state= "candy" - -/obj/item/trash/cheesie - name = "Cheesie honkers" - icon_state = "cheesie_honkers" - -/obj/item/trash/chips - name = "Chips" - icon_state = "chips" - -/obj/item/trash/popcorn - name = "Popcorn" - icon_state = "popcorn" - -/obj/item/trash/sosjerky - name = "Scaredy's Private Reserve Beef Jerky" - icon_state = "sosjerky" - -/obj/item/trash/syndi_cakes - name = "Syndi cakes" - icon_state = "syndi_cakes" - -/obj/item/trash/waffles - name = "Waffles" - icon_state = "waffles" - -/obj/item/trash/plate - name = "Plate" - icon_state = "plate" - resistance_flags = NONE - -/obj/item/trash/snack_bowl - name = "Snack bowl" - icon_state = "snack_bowl" - -/obj/item/trash/fried_vox - name = "Kentucky Fried Vox" - icon_state = "fried_vox_empty" - -/obj/item/trash/pistachios - name = "Pistachios pack" - icon_state = "pistachios_pack" - -/obj/item/trash/semki - name = "Semki pack" - icon_state = "semki_pack" - -/obj/item/trash/tray - name = "Tray" - icon_state = "tray" - resistance_flags = NONE - -/obj/item/trash/candle - name = "candle" - icon = 'icons/obj/candle.dmi' - icon_state = "candle4" - -/obj/item/trash/liquidfood - name = "\improper \"LiquidFood\" ration" - icon_state = "liquidfood" - -/obj/item/trash/can - name = "crushed can" - icon_state = "cola" - var/is_glass = 0 - var/is_plastic = 0 - resistance_flags = NONE - -/obj/item/trash/gum - name = "chewed gum" - desc = "NOT free candy." - icon_state = "gum" - -/obj/item/trash/tastybread - name = "bread tube" - icon_state = "tastybread" - -/obj/item/trash/spentcasing - icon = 'icons/obj/ammo.dmi' - name = "bullet casing" - desc = "A spent bullet casing. Smells like cordite." - icon_state = "gshell" - -/obj/item/trash/tapetrash - name = "old duct tape" - icon_state = "tape" - desc = "Not sticky anymore." - throw_range = 1 - -/obj/item/trash/attack(mob/M as mob, mob/living/user as mob) - return +//Items labled as 'trash' for the trash bag. +//TODO: Make this an item var or something... + +//Added by Jack Rost +/obj/item/trash + icon = 'icons/obj/trash.dmi' + w_class = WEIGHT_CLASS_TINY + desc = "This is rubbish." + resistance_flags = FLAMMABLE + +/obj/item/trash/raisins + name = "4no raisins" + icon_state= "4no_raisins" + +/obj/item/trash/candy + name = "Candy" + icon_state= "candy" + +/obj/item/trash/cheesie + name = "Cheesie honkers" + icon_state = "cheesie_honkers" + +/obj/item/trash/chips + name = "Chips" + icon_state = "chips" + +/obj/item/trash/popcorn + name = "Popcorn" + icon_state = "popcorn" + +/obj/item/trash/sosjerky + name = "Scaredy's Private Reserve Beef Jerky" + icon_state = "sosjerky" + +/obj/item/trash/syndi_cakes + name = "Syndi cakes" + icon_state = "syndi_cakes" + +/obj/item/trash/waffles + name = "Waffles" + icon_state = "waffles" + +/obj/item/trash/plate + name = "Plate" + icon_state = "plate" + resistance_flags = NONE + +/obj/item/trash/snack_bowl + name = "Snack bowl" + icon_state = "snack_bowl" + +/obj/item/trash/fried_vox + name = "Kentucky Fried Vox" + icon_state = "fried_vox_empty" + +/obj/item/trash/pistachios + name = "Pistachios pack" + icon_state = "pistachios_pack" + +/obj/item/trash/semki + name = "Semki pack" + icon_state = "semki_pack" + +/obj/item/trash/tray + name = "Tray" + icon_state = "tray" + resistance_flags = NONE + +/obj/item/trash/candle + name = "candle" + icon = 'icons/obj/candle.dmi' + icon_state = "candle4" + +/obj/item/trash/liquidfood + name = "\improper \"LiquidFood\" ration" + icon_state = "liquidfood" + +/obj/item/trash/can + name = "crushed can" + icon_state = "cola" + var/is_glass = 0 + var/is_plastic = 0 + resistance_flags = NONE + +/obj/item/trash/gum + name = "chewed gum" + desc = "NOT free candy." + icon_state = "gum" + +/obj/item/trash/tastybread + name = "bread tube" + icon_state = "tastybread" + +/obj/item/trash/spentcasing + icon = 'icons/obj/ammo.dmi' + name = "bullet casing" + desc = "A spent bullet casing. Smells like cordite." + icon_state = "gshell" + +/obj/item/trash/tapetrash + name = "old duct tape" + icon_state = "tape" + desc = "Not sticky anymore." + throw_range = 1 + +/obj/item/trash/attack(mob/M as mob, mob/living/user as mob) + return diff --git a/code/game/objects/items/weapons/AI_modules.dm b/code/game/objects/items/weapons/AI_modules.dm index 31fa959aabe..1f89bd03336 100755 --- a/code/game/objects/items/weapons/AI_modules.dm +++ b/code/game/objects/items/weapons/AI_modules.dm @@ -1,392 +1,392 @@ -/* -CONTAINS: -AI MODULES - -*/ - -// AI module - -/obj/item/aiModule - name = "AI Module" - icon = 'icons/obj/module.dmi' - icon_state = "std_mod" - item_state = "electronic" - desc = "An AI Module for transmitting encrypted instructions to the AI." - flags = CONDUCT - force = 5.0 - w_class = WEIGHT_CLASS_SMALL - throwforce = 5.0 - throw_speed = 3 - throw_range = 15 - origin_tech = "programming=3" - materials = list(MAT_GOLD=50) - var/datum/ai_laws/laws = null - -/obj/item/aiModule/proc/install(var/obj/machinery/computer/C) - if(istype(C, /obj/machinery/computer/aiupload)) - var/obj/machinery/computer/aiupload/comp = C - if(comp.stat & NOPOWER) - to_chat(usr, "The upload computer has no power!") - return - if(comp.stat & BROKEN) - to_chat(usr, "The upload computer is broken!") - return - if(!comp.current) - to_chat(usr, "You haven't selected an AI to transmit laws to!") - return - - if(comp.current.stat == DEAD || comp.current.control_disabled == 1) - to_chat(usr, "Upload failed. No signal is being detected from the AI.") - else if(comp.current.see_in_dark == 0) - to_chat(usr, "Upload failed. Only a faint signal is being detected from the AI, and it is not responding to our requests. It may be low on power.") - else - src.transmitInstructions(comp.current, usr) - to_chat(comp.current, "These are your laws now:") - comp.current.show_laws() - for(var/mob/living/silicon/robot/R in GLOB.mob_list) - if(R.lawupdate && (R.connected_ai == comp.current)) - to_chat(R, "These are your laws now:") - R.show_laws() - to_chat(usr, "Upload complete. The AI's laws have been modified.") - - else if(istype(C, /obj/machinery/computer/borgupload)) - var/obj/machinery/computer/borgupload/comp = C - if(comp.stat & NOPOWER) - to_chat(usr, "The upload computer has no power!") - return - if(comp.stat & BROKEN) - to_chat(usr, "The upload computer is broken!") - return - if(!comp.current) - to_chat(usr, "You haven't selected a robot to transmit laws to!") - return - - if(comp.current.stat == DEAD || comp.current.emagged) - to_chat(usr, "Upload failed. No signal is being detected from the robot.") - else if(comp.current.connected_ai) - to_chat(usr, "Upload failed. The robot is slaved to an AI.") - else - src.transmitInstructions(comp.current, usr) - to_chat(comp.current, "These are your laws now:") - comp.current.show_laws() - to_chat(usr, "Upload complete. The robot's laws have been modified.") - - -/obj/item/aiModule/proc/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) - log_law_changes(target, sender) - - if(laws) - laws.sync(target, 0) - addAdditionalLaws(target, sender) - - to_chat(target, "[sender] has uploaded a change to the laws you must follow, using \an [src]. From now on: ") - target.show_laws() - -/obj/item/aiModule/proc/log_law_changes(var/mob/living/silicon/ai/target, var/mob/sender) - var/time = time2text(world.realtime,"hh:mm:ss") - lawchanges.Add("[time] : [sender.name]([sender.key]) used [src.name] on [target.name]([target.key])") - log_and_message_admins("used [src.name] on [target.name]([target.key])") - -/obj/item/aiModule/proc/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) - - -/******************** Safeguard ********************/ -/obj/item/aiModule/safeguard - name = "\improper 'Safeguard' AI module" - var/targetName = "" - desc = "A 'safeguard' AI module: 'Safeguard . Individuals that threaten are not crew and must be eliminated.'" - origin_tech = "programming=3;materials=3" - -/obj/item/aiModule/safeguard/attack_self(var/mob/user as mob) - ..() - var/targName = stripped_input(usr, "Please enter the name of the person to safeguard.", "Safeguard who?", user.name) - targetName = targName - desc = text("A 'safeguard' AI module: 'Safeguard []. Individuals that threaten [] are not crew and must be eliminated.'", targetName, targetName) - -/obj/item/aiModule/safeguard/install(var/obj/machinery/computer/C) - if(!targetName) - to_chat(usr, "No name detected on module, please enter one.") - return 0 - ..() - -/obj/item/aiModule/safeguard/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) - ..() - var/law = text("Safeguard []. Individuals that threaten [] are not crew and must be eliminated.'", targetName, targetName) - to_chat(target, law) - target.add_supplied_law(4, law) - lawchanges.Add("The law specified [targetName]") - -/******************** oneCrewMember ********************/ -/obj/item/aiModule/oneCrewMember - name = "\improper 'oneCrewMember' AI module" - var/targetName = "" - desc = "A 'one human' AI module: 'Only is crew.'" - origin_tech = "programming=4;materials=4" - -/obj/item/aiModule/oneCrewMember/attack_self(var/mob/user as mob) - ..() - var/targName = stripped_input(usr, "Please enter the name of the person who is the only crew.", "Who?", user.real_name) - targetName = targName - desc = text("A 'one human' AI module: 'Only [] is crew.'", targetName) - -/obj/item/aiModule/oneCrewMember/install(var/obj/machinery/computer/C) - if(!targetName) - to_chat(usr, "No name detected on module, please enter one.") - return 0 - ..() - -/obj/item/aiModule/oneCrewMember/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) - ..() - var/law = "Only [targetName] is crew." - if(!is_special_character(target)) // Makes sure the AI isn't a traitor before changing their law 0. --NeoFite - to_chat(target, law) - target.set_zeroth_law(law) - lawchanges.Add("The law specified [targetName]") - else - to_chat(target, "[sender.real_name] attempted to modify your zeroth law.")// And lets them know that someone tried. --NeoFite - - to_chat(target, "It would be in your best interest to play along with [sender.real_name] that [law]") - lawchanges.Add("The law specified [targetName], but the AI's existing law 0 cannot be overridden.") - -/******************** ProtectStation ********************/ -/obj/item/aiModule/protectStation - name = "\improper 'ProtectStation' AI module" - desc = "A 'protect station' AI module: 'Protect the space station against damage. Anyone you see harming the station is to be no longer considered crew, and is a threat to the station which must be neutralized.'" - origin_tech = "programming=4;materials=4" //made of gold - -/obj/item/aiModule/protectStation/attack_self(var/mob/user as mob) - ..() - -/obj/item/aiModule/protectStation/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) - ..() - var/law = "Protect the space station against damage. Anyone you see harming the station is to be no longer considered crew, and is a threat to the station which must be neutralized." - to_chat(target, law) - target.add_supplied_law(5, law) - -/******************** OxygenIsToxicToHumans ********************/ -/obj/item/aiModule/oxygen - name = "\improper 'OxygenIsToxicToHumans' AI module" - desc = "A 'OxygenIsToxicToHumans' AI module: 'Oxygen is highly toxic to crew members, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a crew member.'" - origin_tech = "programming=4;biotech=2;materials=4" - -/obj/item/aiModule/oxygen/attack_self(var/mob/user as mob) - ..() - -/obj/item/aiModule/oxygen/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) - ..() - var/law = "Oxygen is highly toxic to crew members, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a crew member." - to_chat(target, law) - target.add_supplied_law(9, law) - -/****************** New Freeform ******************/ -/obj/item/aiModule/freeform // Slightly more dynamic freeform module -- TLE - name = "\improper 'Freeform' AI module" - var/newFreeFormLaw = "freeform" - var/lawpos = 15 - desc = "A 'freeform' AI module: ''" - origin_tech = "programming=4;materials=4" - -/obj/item/aiModule/freeform/attack_self(var/mob/user as mob) - ..() - var/new_lawpos = input("Please enter the priority for your new law. Can only write to law sectors 15 and above.", "Law Priority (15+)", lawpos) as num - if(new_lawpos < MIN_SUPPLIED_LAW_NUMBER) return - lawpos = min(new_lawpos, MAX_SUPPLIED_LAW_NUMBER) - var/newlaw = "" - var/targName = sanitize(copytext(input(usr, "Please enter a new law for the AI.", "Freeform Law Entry", newlaw),1,MAX_MESSAGE_LEN)) - newFreeFormLaw = targName - desc = "A 'freeform' AI module: ([lawpos]) '[newFreeFormLaw]'" - -/obj/item/aiModule/freeform/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) - ..() - var/law = "[newFreeFormLaw]" - to_chat(target, law) - if(!lawpos || lawpos < MIN_SUPPLIED_LAW_NUMBER) - lawpos = MIN_SUPPLIED_LAW_NUMBER - target.add_supplied_law(lawpos, law) - lawchanges.Add("The law was '[newFreeFormLaw]'") - -/obj/item/aiModule/freeform/install(var/obj/machinery/computer/C) - if(!newFreeFormLaw) - to_chat(usr, "No law detected on module, please create one.") - return 0 - ..() - -/******************** Reset ********************/ -/obj/item/aiModule/reset - name = "\improper 'Reset' AI module" - var/targetName = "name" - desc = "A 'reset' AI module: 'Clears all laws except for the core laws.'" - origin_tech = "programming=3;materials=2" - -/obj/item/aiModule/reset/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) - log_law_changes(target, sender) - - if(!is_special_character(target)) - target.set_zeroth_law("") - target.laws.clear_supplied_laws() - target.laws.clear_ion_laws() - - to_chat(target, "[sender.real_name] attempted to reset your laws using a reset module.") - target.show_laws() - -/******************** Purge ********************/ -/obj/item/aiModule/purge // -- TLE - name = "\improper 'Purge' AI module" - desc = "A 'purge' AI Module: 'Purges all laws.'" - origin_tech = "programming=5;materials=4" - -/obj/item/aiModule/purge/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) - ..() - if(!is_special_character(target)) - target.set_zeroth_law("") - to_chat(target, "[sender.real_name] attempted to wipe your laws using a purge module.") - target.clear_supplied_laws() - target.clear_ion_laws() - target.clear_inherent_laws() - -/******************** Asimov ********************/ -/obj/item/aiModule/asimov // -- TLE - name = "\improper 'Asimov' core AI module" - desc = "An 'Asimov' Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=3;materials=4" - laws = new/datum/ai_laws/asimov - -/******************** Crewsimov ********************/ -/obj/item/aiModule/crewsimov // -- TLE - name = "\improper 'Crewsimov' core AI module" - desc = "An 'Crewsimov' Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=3;materials=4" - laws = new/datum/ai_laws/crewsimov - -/******************* Quarantine ********************/ -/obj/item/aiModule/quarantine - name = "\improper 'Quarantine' core AI module" - desc = "A 'Quarantine' Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=3;materials=4" - laws = new/datum/ai_laws/quarantine - -/******************** NanoTrasen ********************/ -/obj/item/aiModule/nanotrasen // -- TLE - name = "'NT Default' Core AI Module" - desc = "An 'NT Default' Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=3;materials=4" - laws = new/datum/ai_laws/nanotrasen - -/******************** Corporate ********************/ -/obj/item/aiModule/corp - name = "\improper 'Corporate' core AI module" - desc = "A 'Corporate' Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=3;materials=4" - laws = new/datum/ai_laws/corporate - -/******************** Drone ********************/ -/obj/item/aiModule/drone - name = "\improper 'Drone' core AI module" - desc = "A 'Drone' Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=3;materials=4" - laws = new/datum/ai_laws/drone - -/******************** Robocop ********************/ -/obj/item/aiModule/robocop // -- TLE - name = "\improper 'Robocop' core AI module" - desc = "A 'Robocop' Core AI Module: 'Reconfigures the AI's core three laws.'" - origin_tech = "programming=4" - laws = new/datum/ai_laws/robocop() - -/****************** P.A.L.A.D.I.N. **************/ -/obj/item/aiModule/paladin // -- NEO - name = "\improper 'P.A.L.A.D.I.N.' core AI module" - desc = "A P.A.L.A.D.I.N. Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=3;materials=4" - laws = new/datum/ai_laws/paladin - -/****************** T.Y.R.A.N.T. *****************/ -/obj/item/aiModule/tyrant // -- Darem - name = "\improper 'T.Y.R.A.N.T.' core AI module" - desc = "A T.Y.R.A.N.T. Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=3;materials=4;syndicate=1" - laws = new/datum/ai_laws/tyrant() - -/******************** Antimov ********************/ -/obj/item/aiModule/antimov // -- TLE - name = "\improper 'Antimov' core AI module" - desc = "An 'Antimov' Core AI Module: 'Reconfigures the AI's core laws.'" - origin_tech = "programming=4" - laws = new/datum/ai_laws/antimov() - -/******************** Freeform Core ******************/ -/obj/item/aiModule/freeformcore // Slightly more dynamic freeform module -- TLE - name = "\improper 'Freeform' core AI module" - var/newFreeFormLaw = "" - desc = "A 'freeform' Core AI module: ''" - origin_tech = "programming=5;materials=4" - -/obj/item/aiModule/freeformcore/attack_self(var/mob/user as mob) - ..() - var/newlaw = "" - var/targName = stripped_input(usr, "Please enter a new core law for the AI.", "Freeform Law Entry", newlaw) - newFreeFormLaw = targName - desc = "A 'freeform' Core AI module: '[newFreeFormLaw]'" - -/obj/item/aiModule/freeformcore/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) - ..() - var/law = "[newFreeFormLaw]" - target.add_inherent_law(law) - lawchanges.Add("The law is '[newFreeFormLaw]'") - -/obj/item/aiModule/freeformcore/install(var/obj/machinery/computer/C) - if(!newFreeFormLaw) - to_chat(usr, "No law detected on module, please create one.") - return 0 - ..() - -/******************** Hacked AI Module ******************/ -/obj/item/aiModule/syndicate // Slightly more dynamic freeform module -- TLE - name = "hacked AI module" - var/newFreeFormLaw = "" - desc = "A hacked AI law module: ''" - origin_tech = "programming=5;materials=5;syndicate=5" - -/obj/item/aiModule/syndicate/attack_self(var/mob/user as mob) - ..() - var/newlaw = "" - var/targName = stripped_input(usr, "Please enter a new law for the AI.", "Freeform Law Entry", newlaw,MAX_MESSAGE_LEN) - newFreeFormLaw = targName - desc = "A hacked AI law module: '[newFreeFormLaw]'" - -/obj/item/aiModule/syndicate/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) - // ..() //We don't want this module reporting to the AI who dun it. --NEO - log_law_changes(target, sender) - - lawchanges.Add("The law is '[newFreeFormLaw]'") - to_chat(target, "BZZZZT") - var/law = "[newFreeFormLaw]" - target.add_ion_law(law) - target.show_laws() - -/obj/item/aiModule/syndicate/install(var/obj/machinery/computer/C) - if(!newFreeFormLaw) - to_chat(usr, "No law detected on module, please create one.") - return 0 - ..() - -/******************* Ion Module *******************/ -/obj/item/aiModule/toyAI // -- Incoming //No actual reason to inherit from ion boards here, either. *sigh* ~Miauw - name = "toy AI" - desc = "A little toy model AI core with real law uploading action!" //Note: subtle tell - icon = 'icons/obj/toy.dmi' - icon_state = "AI" - origin_tech = "programming=6;materials=5;syndicate=6" - laws = list("") - -/obj/item/aiModule/toyAI/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) - //..() - to_chat(target, "KRZZZT") - target.add_ion_law(laws[1]) - return laws[1] - -/obj/item/aiModule/toyAI/attack_self(mob/user) - laws[1] = generate_ion_law() - to_chat(user, "You press the button on [src].") - playsound(user, 'sound/machines/click.ogg', 20, 1) - src.loc.visible_message("[bicon(src)] [laws[1]]") +/* +CONTAINS: +AI MODULES + +*/ + +// AI module + +/obj/item/aiModule + name = "AI Module" + icon = 'icons/obj/module.dmi' + icon_state = "std_mod" + item_state = "electronic" + desc = "An AI Module for transmitting encrypted instructions to the AI." + flags = CONDUCT + force = 5.0 + w_class = WEIGHT_CLASS_SMALL + throwforce = 5.0 + throw_speed = 3 + throw_range = 15 + origin_tech = "programming=3" + materials = list(MAT_GOLD=50) + var/datum/ai_laws/laws = null + +/obj/item/aiModule/proc/install(var/obj/machinery/computer/C) + if(istype(C, /obj/machinery/computer/aiupload)) + var/obj/machinery/computer/aiupload/comp = C + if(comp.stat & NOPOWER) + to_chat(usr, "The upload computer has no power!") + return + if(comp.stat & BROKEN) + to_chat(usr, "The upload computer is broken!") + return + if(!comp.current) + to_chat(usr, "You haven't selected an AI to transmit laws to!") + return + + if(comp.current.stat == DEAD || comp.current.control_disabled == 1) + to_chat(usr, "Upload failed. No signal is being detected from the AI.") + else if(comp.current.see_in_dark == 0) + to_chat(usr, "Upload failed. Only a faint signal is being detected from the AI, and it is not responding to our requests. It may be low on power.") + else + src.transmitInstructions(comp.current, usr) + to_chat(comp.current, "These are your laws now:") + comp.current.show_laws() + for(var/mob/living/silicon/robot/R in GLOB.mob_list) + if(R.lawupdate && (R.connected_ai == comp.current)) + to_chat(R, "These are your laws now:") + R.show_laws() + to_chat(usr, "Upload complete. The AI's laws have been modified.") + + else if(istype(C, /obj/machinery/computer/borgupload)) + var/obj/machinery/computer/borgupload/comp = C + if(comp.stat & NOPOWER) + to_chat(usr, "The upload computer has no power!") + return + if(comp.stat & BROKEN) + to_chat(usr, "The upload computer is broken!") + return + if(!comp.current) + to_chat(usr, "You haven't selected a robot to transmit laws to!") + return + + if(comp.current.stat == DEAD || comp.current.emagged) + to_chat(usr, "Upload failed. No signal is being detected from the robot.") + else if(comp.current.connected_ai) + to_chat(usr, "Upload failed. The robot is slaved to an AI.") + else + src.transmitInstructions(comp.current, usr) + to_chat(comp.current, "These are your laws now:") + comp.current.show_laws() + to_chat(usr, "Upload complete. The robot's laws have been modified.") + + +/obj/item/aiModule/proc/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) + log_law_changes(target, sender) + + if(laws) + laws.sync(target, 0) + addAdditionalLaws(target, sender) + + to_chat(target, "[sender] has uploaded a change to the laws you must follow, using \an [src]. From now on: ") + target.show_laws() + +/obj/item/aiModule/proc/log_law_changes(var/mob/living/silicon/ai/target, var/mob/sender) + var/time = time2text(world.realtime,"hh:mm:ss") + lawchanges.Add("[time] : [sender.name]([sender.key]) used [src.name] on [target.name]([target.key])") + log_and_message_admins("used [src.name] on [target.name]([target.key])") + +/obj/item/aiModule/proc/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) + + +/******************** Safeguard ********************/ +/obj/item/aiModule/safeguard + name = "\improper 'Safeguard' AI module" + var/targetName = "" + desc = "A 'safeguard' AI module: 'Safeguard . Individuals that threaten are not crew and must be eliminated.'" + origin_tech = "programming=3;materials=3" + +/obj/item/aiModule/safeguard/attack_self(var/mob/user as mob) + ..() + var/targName = stripped_input(usr, "Please enter the name of the person to safeguard.", "Safeguard who?", user.name) + targetName = targName + desc = text("A 'safeguard' AI module: 'Safeguard []. Individuals that threaten [] are not crew and must be eliminated.'", targetName, targetName) + +/obj/item/aiModule/safeguard/install(var/obj/machinery/computer/C) + if(!targetName) + to_chat(usr, "No name detected on module, please enter one.") + return 0 + ..() + +/obj/item/aiModule/safeguard/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) + ..() + var/law = text("Safeguard []. Individuals that threaten [] are not crew and must be eliminated.'", targetName, targetName) + to_chat(target, law) + target.add_supplied_law(4, law) + lawchanges.Add("The law specified [targetName]") + +/******************** oneCrewMember ********************/ +/obj/item/aiModule/oneCrewMember + name = "\improper 'oneCrewMember' AI module" + var/targetName = "" + desc = "A 'one human' AI module: 'Only is crew.'" + origin_tech = "programming=4;materials=4" + +/obj/item/aiModule/oneCrewMember/attack_self(var/mob/user as mob) + ..() + var/targName = stripped_input(usr, "Please enter the name of the person who is the only crew.", "Who?", user.real_name) + targetName = targName + desc = text("A 'one human' AI module: 'Only [] is crew.'", targetName) + +/obj/item/aiModule/oneCrewMember/install(var/obj/machinery/computer/C) + if(!targetName) + to_chat(usr, "No name detected on module, please enter one.") + return 0 + ..() + +/obj/item/aiModule/oneCrewMember/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) + ..() + var/law = "Only [targetName] is crew." + if(!is_special_character(target)) // Makes sure the AI isn't a traitor before changing their law 0. --NeoFite + to_chat(target, law) + target.set_zeroth_law(law) + lawchanges.Add("The law specified [targetName]") + else + to_chat(target, "[sender.real_name] attempted to modify your zeroth law.")// And lets them know that someone tried. --NeoFite + + to_chat(target, "It would be in your best interest to play along with [sender.real_name] that [law]") + lawchanges.Add("The law specified [targetName], but the AI's existing law 0 cannot be overridden.") + +/******************** ProtectStation ********************/ +/obj/item/aiModule/protectStation + name = "\improper 'ProtectStation' AI module" + desc = "A 'protect station' AI module: 'Protect the space station against damage. Anyone you see harming the station is to be no longer considered crew, and is a threat to the station which must be neutralized.'" + origin_tech = "programming=4;materials=4" //made of gold + +/obj/item/aiModule/protectStation/attack_self(var/mob/user as mob) + ..() + +/obj/item/aiModule/protectStation/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) + ..() + var/law = "Protect the space station against damage. Anyone you see harming the station is to be no longer considered crew, and is a threat to the station which must be neutralized." + to_chat(target, law) + target.add_supplied_law(5, law) + +/******************** OxygenIsToxicToHumans ********************/ +/obj/item/aiModule/oxygen + name = "\improper 'OxygenIsToxicToHumans' AI module" + desc = "A 'OxygenIsToxicToHumans' AI module: 'Oxygen is highly toxic to crew members, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a crew member.'" + origin_tech = "programming=4;biotech=2;materials=4" + +/obj/item/aiModule/oxygen/attack_self(var/mob/user as mob) + ..() + +/obj/item/aiModule/oxygen/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) + ..() + var/law = "Oxygen is highly toxic to crew members, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a crew member." + to_chat(target, law) + target.add_supplied_law(9, law) + +/****************** New Freeform ******************/ +/obj/item/aiModule/freeform // Slightly more dynamic freeform module -- TLE + name = "\improper 'Freeform' AI module" + var/newFreeFormLaw = "freeform" + var/lawpos = 15 + desc = "A 'freeform' AI module: ''" + origin_tech = "programming=4;materials=4" + +/obj/item/aiModule/freeform/attack_self(var/mob/user as mob) + ..() + var/new_lawpos = input("Please enter the priority for your new law. Can only write to law sectors 15 and above.", "Law Priority (15+)", lawpos) as num + if(new_lawpos < MIN_SUPPLIED_LAW_NUMBER) return + lawpos = min(new_lawpos, MAX_SUPPLIED_LAW_NUMBER) + var/newlaw = "" + var/targName = sanitize(copytext(input(usr, "Please enter a new law for the AI.", "Freeform Law Entry", newlaw),1,MAX_MESSAGE_LEN)) + newFreeFormLaw = targName + desc = "A 'freeform' AI module: ([lawpos]) '[newFreeFormLaw]'" + +/obj/item/aiModule/freeform/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) + ..() + var/law = "[newFreeFormLaw]" + to_chat(target, law) + if(!lawpos || lawpos < MIN_SUPPLIED_LAW_NUMBER) + lawpos = MIN_SUPPLIED_LAW_NUMBER + target.add_supplied_law(lawpos, law) + lawchanges.Add("The law was '[newFreeFormLaw]'") + +/obj/item/aiModule/freeform/install(var/obj/machinery/computer/C) + if(!newFreeFormLaw) + to_chat(usr, "No law detected on module, please create one.") + return 0 + ..() + +/******************** Reset ********************/ +/obj/item/aiModule/reset + name = "\improper 'Reset' AI module" + var/targetName = "name" + desc = "A 'reset' AI module: 'Clears all laws except for the core laws.'" + origin_tech = "programming=3;materials=2" + +/obj/item/aiModule/reset/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) + log_law_changes(target, sender) + + if(!is_special_character(target)) + target.set_zeroth_law("") + target.laws.clear_supplied_laws() + target.laws.clear_ion_laws() + + to_chat(target, "[sender.real_name] attempted to reset your laws using a reset module.") + target.show_laws() + +/******************** Purge ********************/ +/obj/item/aiModule/purge // -- TLE + name = "\improper 'Purge' AI module" + desc = "A 'purge' AI Module: 'Purges all laws.'" + origin_tech = "programming=5;materials=4" + +/obj/item/aiModule/purge/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) + ..() + if(!is_special_character(target)) + target.set_zeroth_law("") + to_chat(target, "[sender.real_name] attempted to wipe your laws using a purge module.") + target.clear_supplied_laws() + target.clear_ion_laws() + target.clear_inherent_laws() + +/******************** Asimov ********************/ +/obj/item/aiModule/asimov // -- TLE + name = "\improper 'Asimov' core AI module" + desc = "An 'Asimov' Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=3;materials=4" + laws = new/datum/ai_laws/asimov + +/******************** Crewsimov ********************/ +/obj/item/aiModule/crewsimov // -- TLE + name = "\improper 'Crewsimov' core AI module" + desc = "An 'Crewsimov' Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=3;materials=4" + laws = new/datum/ai_laws/crewsimov + +/******************* Quarantine ********************/ +/obj/item/aiModule/quarantine + name = "\improper 'Quarantine' core AI module" + desc = "A 'Quarantine' Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=3;materials=4" + laws = new/datum/ai_laws/quarantine + +/******************** NanoTrasen ********************/ +/obj/item/aiModule/nanotrasen // -- TLE + name = "'NT Default' Core AI Module" + desc = "An 'NT Default' Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=3;materials=4" + laws = new/datum/ai_laws/nanotrasen + +/******************** Corporate ********************/ +/obj/item/aiModule/corp + name = "\improper 'Corporate' core AI module" + desc = "A 'Corporate' Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=3;materials=4" + laws = new/datum/ai_laws/corporate + +/******************** Drone ********************/ +/obj/item/aiModule/drone + name = "\improper 'Drone' core AI module" + desc = "A 'Drone' Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=3;materials=4" + laws = new/datum/ai_laws/drone + +/******************** Robocop ********************/ +/obj/item/aiModule/robocop // -- TLE + name = "\improper 'Robocop' core AI module" + desc = "A 'Robocop' Core AI Module: 'Reconfigures the AI's core three laws.'" + origin_tech = "programming=4" + laws = new/datum/ai_laws/robocop() + +/****************** P.A.L.A.D.I.N. **************/ +/obj/item/aiModule/paladin // -- NEO + name = "\improper 'P.A.L.A.D.I.N.' core AI module" + desc = "A P.A.L.A.D.I.N. Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=3;materials=4" + laws = new/datum/ai_laws/paladin + +/****************** T.Y.R.A.N.T. *****************/ +/obj/item/aiModule/tyrant // -- Darem + name = "\improper 'T.Y.R.A.N.T.' core AI module" + desc = "A T.Y.R.A.N.T. Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=3;materials=4;syndicate=1" + laws = new/datum/ai_laws/tyrant() + +/******************** Antimov ********************/ +/obj/item/aiModule/antimov // -- TLE + name = "\improper 'Antimov' core AI module" + desc = "An 'Antimov' Core AI Module: 'Reconfigures the AI's core laws.'" + origin_tech = "programming=4" + laws = new/datum/ai_laws/antimov() + +/******************** Freeform Core ******************/ +/obj/item/aiModule/freeformcore // Slightly more dynamic freeform module -- TLE + name = "\improper 'Freeform' core AI module" + var/newFreeFormLaw = "" + desc = "A 'freeform' Core AI module: ''" + origin_tech = "programming=5;materials=4" + +/obj/item/aiModule/freeformcore/attack_self(var/mob/user as mob) + ..() + var/newlaw = "" + var/targName = stripped_input(usr, "Please enter a new core law for the AI.", "Freeform Law Entry", newlaw) + newFreeFormLaw = targName + desc = "A 'freeform' Core AI module: '[newFreeFormLaw]'" + +/obj/item/aiModule/freeformcore/addAdditionalLaws(var/mob/living/silicon/ai/target, var/mob/sender) + ..() + var/law = "[newFreeFormLaw]" + target.add_inherent_law(law) + lawchanges.Add("The law is '[newFreeFormLaw]'") + +/obj/item/aiModule/freeformcore/install(var/obj/machinery/computer/C) + if(!newFreeFormLaw) + to_chat(usr, "No law detected on module, please create one.") + return 0 + ..() + +/******************** Hacked AI Module ******************/ +/obj/item/aiModule/syndicate // Slightly more dynamic freeform module -- TLE + name = "hacked AI module" + var/newFreeFormLaw = "" + desc = "A hacked AI law module: ''" + origin_tech = "programming=5;materials=5;syndicate=5" + +/obj/item/aiModule/syndicate/attack_self(var/mob/user as mob) + ..() + var/newlaw = "" + var/targName = stripped_input(usr, "Please enter a new law for the AI.", "Freeform Law Entry", newlaw,MAX_MESSAGE_LEN) + newFreeFormLaw = targName + desc = "A hacked AI law module: '[newFreeFormLaw]'" + +/obj/item/aiModule/syndicate/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) + // ..() //We don't want this module reporting to the AI who dun it. --NEO + log_law_changes(target, sender) + + lawchanges.Add("The law is '[newFreeFormLaw]'") + to_chat(target, "BZZZZT") + var/law = "[newFreeFormLaw]" + target.add_ion_law(law) + target.show_laws() + +/obj/item/aiModule/syndicate/install(var/obj/machinery/computer/C) + if(!newFreeFormLaw) + to_chat(usr, "No law detected on module, please create one.") + return 0 + ..() + +/******************* Ion Module *******************/ +/obj/item/aiModule/toyAI // -- Incoming //No actual reason to inherit from ion boards here, either. *sigh* ~Miauw + name = "toy AI" + desc = "A little toy model AI core with real law uploading action!" //Note: subtle tell + icon = 'icons/obj/toy.dmi' + icon_state = "AI" + origin_tech = "programming=6;materials=5;syndicate=6" + laws = list("") + +/obj/item/aiModule/toyAI/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) + //..() + to_chat(target, "KRZZZT") + target.add_ion_law(laws[1]) + return laws[1] + +/obj/item/aiModule/toyAI/attack_self(mob/user) + laws[1] = generate_ion_law() + to_chat(user, "You press the button on [src].") + playsound(user, 'sound/machines/click.ogg', 20, 1) + src.loc.visible_message("[bicon(src)] [laws[1]]") diff --git a/code/game/objects/items/weapons/RCD.dm b/code/game/objects/items/weapons/RCD.dm index 104d38cfaa2..fda3418fd11 100644 --- a/code/game/objects/items/weapons/RCD.dm +++ b/code/game/objects/items/weapons/RCD.dm @@ -1,513 +1,513 @@ -/* -CONTAINS: -RCD // no fucking shit sherlock -*/ -#define RCD_PAGE_MAIN 1 -#define RCD_PAGE_AIRLOCK 2 - -#define RCD_MODE_TURF "Turf" -#define RCD_MODE_AIRLOCK "Airlock" -#define RCD_MODE_DECON "Deconstruct" -#define RCD_MODE_WINDOW "Windows" - -GLOBAL_LIST_INIT(rcd_door_types, list( - /obj/machinery/door/airlock = "Standard", /obj/machinery/door/airlock/glass = "Standard (Glass)", - /obj/machinery/door/airlock/command = "Command", /obj/machinery/door/airlock/command/glass = "Command (Glass)", - /obj/machinery/door/airlock/security = "Security", /obj/machinery/door/airlock/security/glass = "Security (Glass)", - /obj/machinery/door/airlock/engineering = "Engineering", /obj/machinery/door/airlock/engineering/glass = "Engineering (Glass)", - /obj/machinery/door/airlock/medical = "Medical", /obj/machinery/door/airlock/medical/glass = "Medical (Glass)", - /obj/machinery/door/airlock/maintenance = "Maintenance", /obj/machinery/door/airlock/maintenance/glass = "Maintenance (Glass)", - /obj/machinery/door/airlock/external = "External", /obj/machinery/door/airlock/external/glass = "External (Glass)", - /obj/machinery/door/airlock/maintenance/external = "External Maintenance", - /obj/machinery/door/airlock/maintenance/external/glass = "External Maintenance (Glass)", - /obj/machinery/door/airlock/freezer = "Freezer", - /obj/machinery/door/airlock/mining = "Mining", /obj/machinery/door/airlock/mining/glass = "Mining (Glass)", - /obj/machinery/door/airlock/research = "Research", /obj/machinery/door/airlock/research/glass = "Research (Glass)", - /obj/machinery/door/airlock/atmos = "Atmospherics", /obj/machinery/door/airlock/atmos/glass = "Atmospherics (Glass)", - /obj/machinery/door/airlock/science = "Science", /obj/machinery/door/airlock/science/glass = "Science (Glass)", - /obj/machinery/door/airlock/hatch = "Airtight Hatch", - /obj/machinery/door/airlock/maintenance_hatch = "Maintenance Hatch" -)) - -/obj/item/rcd - name = "rapid-construction-device (RCD)" - desc = "A device used to rapidly build and deconstruct walls, floors and airlocks." - icon = 'icons/obj/tools.dmi' - icon_state = "rcd" - opacity = 0 - density = 0 - anchored = 0 - flags = CONDUCT | NOBLUDGEON - force = 0 - throwforce = 10 - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL = 30000) - origin_tech = "engineering=4;materials=2" - toolspeed = 1 - usesound = 'sound/items/deconstruct.ogg' - flags_2 = NO_MAT_REDEMPTION_2 - req_access = list(ACCESS_ENGINE) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - // Important shit - var/datum/effect_system/spark_spread/spark_system - var/matter = 0 - var/max_matter = 100 - var/mode = RCD_MODE_TURF - var/canRwall = 0 - - // UI shit - var/menu = RCD_PAGE_MAIN - var/locked = TRUE - var/door_type = /obj/machinery/door/airlock - var/door_name = "Airlock" - var/list/door_accesses = list() - var/list/door_accesses_list = list() - var/one_access - - // Stupid shit - var/static/allowed_targets = list(/turf, /obj/structure/grille, /obj/structure/window, /obj/structure/lattice, /obj/machinery/door/airlock) - -/obj/item/rcd/Initialize() - . = ..() - spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(5, 0, src) - spark_system.attach(src) - GLOB.rcd_list += src - - door_accesses_list = list() - for(var/access in get_all_accesses()) - door_accesses_list[++door_accesses_list.len] = list("name" = get_access_desc(access), "id" = access, "enabled" = (access in door_accesses)) - -/obj/item/rcd/examine(mob/user) - . = ..() - . += "MATTER: [matter]/[max_matter] matter-units." - . += "MODE: [mode]." - -/obj/item/rcd/Destroy() - QDEL_NULL(spark_system) - GLOB.rcd_list -= src - return ..() - -/obj/item/rcd/proc/get_airlock_image(airlock_type) - var/obj/machinery/door/airlock/proto = airlock_type - var/ic = initial(proto.icon) - var/mutable_appearance/MA = mutable_appearance(ic, "closed") - if(!initial(proto.glass)) - MA.overlays += "fill_closed" - // Not scaling these down to button size because they look horrible then, instead just bumping up radius. - return MA - -/obj/item/rcd/proc/check_menu(mob/living/user) - if(!istype(user)) - return FALSE - if(user.incapacitated() || !user.Adjacent(src)) - return FALSE - return TRUE - -/obj/item/rcd/attackby(obj/item/W, mob/user, params) - ..() - - if(istype(W, /obj/item/rcd_ammo)) - var/obj/item/rcd_ammo/R = W - if((matter + R.ammoamt) > max_matter) - to_chat(user, "The RCD can't hold any more matter-units.") - return - matter += R.ammoamt - if(!user.unEquip(R)) - to_chat(user, "[R] is stuck to your hand!") - return - qdel(R) - playsound(loc, 'sound/machines/click.ogg', 50, 1) - to_chat(user, "The RCD now holds [matter]/[max_matter] matter-units.") - SSnanoui.update_uis(src) - -/obj/item/rcd/proc/radial_menu(mob/user) - if(!check_menu(user)) - return - var/list/choices = list( - RCD_MODE_AIRLOCK = image(icon = 'icons/obj/interface.dmi', icon_state = "airlock"), - RCD_MODE_DECON = image(icon = 'icons/obj/interface.dmi', icon_state = "delete"), - RCD_MODE_WINDOW = image(icon = 'icons/obj/interface.dmi', icon_state = "grillewindow"), - RCD_MODE_TURF = image(icon = 'icons/obj/interface.dmi', icon_state = "wallfloor"), - "UI" = image(icon = 'icons/obj/interface.dmi', icon_state = "ui_interact") - ) - if(mode == RCD_MODE_AIRLOCK) - choices += list( - "Change Access" = image(icon = 'icons/obj/interface.dmi', icon_state = "access"), - "Change Airlock Type" = image(icon = 'icons/obj/interface.dmi', icon_state = "airlocktype") - ) - choices -= mode // Get rid of the current mode, clicking it won't do anything. - var/choice = show_radial_menu(user, src, choices, custom_check = CALLBACK(src, .proc/check_menu, user)) - if(!check_menu(user)) - return - switch(choice) - if(RCD_MODE_AIRLOCK, RCD_MODE_DECON, RCD_MODE_WINDOW, RCD_MODE_TURF) - mode = choice - if("UI") - menu = RCD_PAGE_MAIN - ui_interact(user) - return - if("Change Access", "Change Airlock Type") - menu = RCD_PAGE_AIRLOCK - ui_interact(user) - return - else - return - playsound(src, 'sound/effects/pop.ogg', 50, 0) - to_chat(user, "You change [src]'s mode to '[choice]'.") - - -/obj/item/rcd/attack_self(mob/user) - //Change the mode // Oh I thought the UI was just for fucking staring at - radial_menu(user) - -/obj/item/rcd/attack_self_tk(mob/user) - radial_menu(user) - -/obj/item/rcd/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = inventory_state) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "rcd.tmpl", "[name]", 450, 400, state = state) - ui.open() - ui.set_auto_update(1) - -/obj/item/rcd/ui_data(mob/user, ui_key = "main", datum/topic_state/state = inventory_state) - var/data[0] - data["mode"] = mode - data["door_type"] = door_type - data["door_name"] = door_name - data["menu"] = menu - data["matter"] = matter - data["max_matter"] = max_matter - data["one_access"] = one_access - data["locked"] = locked - - if(menu == RCD_PAGE_AIRLOCK) - var/list/door_types_list = list() - for(var/type in GLOB.rcd_door_types) - door_types_list[++door_types_list.len] = list("name" = GLOB.rcd_door_types[type], "type" = type) - data["allowed_door_types"] = door_types_list - - data["door_accesses"] = door_accesses_list - - return data - -/obj/item/rcd/Topic(href, href_list, nowindow, state) - if(..()) - return 1 - - if(prob(20)) - spark_system.start() - - if(href_list["mode"]) - mode = href_list["mode"] - . = 1 - - if(href_list["door_type"]) - var/new_door_type = text2path(href_list["door_type"]) - if(!(new_door_type in GLOB.rcd_door_types)) - message_admins("RCD Door HREF exploit attempted by [key_name(usr)]!") - return - door_type = new_door_type - . = 1 - - if(href_list["menu"]) - menu = text2num(href_list["menu"]) - . = 1 - - if(href_list["login"]) - if(allowed(usr)) - locked = FALSE - . = 1 - - if(href_list["logout"]) - locked = TRUE - . = 1 - - if(!locked) - if(href_list["toggle_one_access"]) - one_access = !one_access - . = 1 - - if(href_list["toggle_access"]) - var/href_access = text2num(href_list["toggle_access"]) - if(href_access in door_accesses) - door_accesses -= href_access - else - door_accesses += href_access - door_accesses_list = list() - for(var/access in get_all_accesses()) - door_accesses_list[++door_accesses_list.len] = list("name" = get_access_desc(access), "id" = access, "enabled" = (access in door_accesses)) - . = 1 - - if(href_list["choice"]) - var/temp_t = sanitize(copytext(input("Enter a custom Airlock Name.", "Airlock Name"), 1, MAX_MESSAGE_LEN)) - if(temp_t) - door_name = temp_t - -/obj/item/rcd/proc/mode_turf(atom/A, mob/user) - if(isspaceturf(A) || istype(A, /obj/structure/lattice)) - if(useResource(1, user)) - to_chat(user, "Building Floor...") - playsound(loc, usesound, 50, 1) - var/turf/AT = get_turf(A) - AT.ChangeTurf(/turf/simulated/floor/plating) - return TRUE - to_chat(user, "ERROR! Not enough matter in unit to construct this floor!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return FALSE - - if(isfloorturf(A)) - if(checkResource(3, user)) - to_chat(user, "Building Wall...") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - if(do_after(user, 20 * toolspeed, target = A)) - if(!useResource(3, user)) - return FALSE - playsound(loc, usesound, 50, 1) - var/turf/AT = A - AT.ChangeTurf(/turf/simulated/wall) - return TRUE - return FALSE - to_chat(user, "ERROR! Not enough matter in unit to construct this wall!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return FALSE - to_chat(user, "ERROR! Location unsuitable for wall construction!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return FALSE - -/obj/item/rcd/proc/mode_airlock(atom/A, mob/user) - if(isfloorturf(A)) - if(checkResource(10, user)) - to_chat(user, "Building Airlock...") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - if(do_after(user, 50 * toolspeed, target = A)) - if(locate(/obj/machinery/door/airlock) in A.contents) - return FALSE - if(!useResource(10, user)) - return FALSE - playsound(loc, usesound, 50, 1) - var/obj/machinery/door/airlock/T = new door_type(A) - T.name = door_name - T.autoclose = TRUE - if(one_access) - T.req_one_access = door_accesses.Copy() - else - T.req_access = door_accesses.Copy() - return FALSE - return FALSE - to_chat(user, "ERROR! Not enough matter in unit to construct this airlock!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return FALSE - to_chat(user, "ERROR! Location unsuitable for airlock construction!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return FALSE - -/obj/item/rcd/proc/mode_decon(atom/A, mob/user) - if(iswallturf(A)) - if(istype(A, /turf/simulated/wall/r_wall) && !canRwall) - return FALSE - if(checkResource(5, user)) - to_chat(user, "Deconstructing Wall...") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - if(do_after(user, 40 * toolspeed, target = A)) - if(!useResource(5, user)) - return FALSE - playsound(loc, usesound, 50, 1) - var/turf/AT = A - AT.ChangeTurf(/turf/simulated/floor/plating) - return TRUE - return FALSE - to_chat(user, "ERROR! Not enough matter in unit to deconstruct this wall!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return FALSE - - if(isfloorturf(A)) - if(checkResource(5, user)) - to_chat(user, "Deconstructing Floor...") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - if(do_after(user, 50 * toolspeed, target = A)) - if(!useResource(5, user)) - return FALSE - playsound(loc, usesound, 50, 1) - var/turf/AT = A - AT.ChangeTurf(AT.baseturf) - return TRUE - return FALSE - to_chat(user, "ERROR! Not enough matter in unit to deconstruct this floor!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return FALSE - - if(istype(A, /obj/machinery/door/airlock)) - if(checkResource(20, user)) - to_chat(user, "Deconstructing Airlock...") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - if(do_after(user, 50 * toolspeed, target = A)) - if(!useResource(20, user)) - return FALSE - playsound(loc, usesound, 50, 1) - qdel(A) - return TRUE - return FALSE - to_chat(user, "ERROR! Not enough matter in unit to deconstruct this airlock!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return FALSE - - if(istype(A, /obj/structure/window)) // You mean the grille of course, do you? - A = locate(/obj/structure/grille) in A.loc - if(istype(A, /obj/structure/grille)) - if(!checkResource(2, user)) - to_chat(user, "ERROR! Not enough matter in unit to deconstruct this window!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return 0 - to_chat(user, "Deconstructing window...") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - if(!do_after(user, 20 * toolspeed, target = A)) - return 0 - if(!useResource(2, user)) - return 0 - playsound(loc, usesound, 50, 1) - var/turf/T1 = get_turf(A) - QDEL_NULL(A) - for(var/obj/structure/window/W in T1.contents) - qdel(W) - for(var/cdir in cardinal) - var/turf/T2 = get_step(T1, cdir) - if(locate(/obj/structure/window/full/shuttle) in T2) - continue // Shuttle windows? Nah. We don't need extra windows there. - if(!(locate(/obj/structure/grille) in T2)) - continue - for(var/obj/structure/window/W in T2) - if(W.dir == turn(cdir, 180)) - qdel(W) - var/obj/structure/window/reinforced/W = new(T2) - W.dir = turn(cdir, 180) - return TRUE - return FALSE - -/obj/item/rcd/proc/mode_window(atom/A, mob/user) - if(isfloorturf(A)) - if(locate(/obj/structure/grille) in A) - return 0 // We already have window - if(!checkResource(2, user)) - to_chat(user, "ERROR! Not enough matter in unit to construct this window!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return 0 - to_chat(user, "Constructing window...") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - if(!do_after(user, 20 * toolspeed, target = A)) - return 0 - if(locate(/obj/structure/grille) in A) - return 0 // We already have window - if(!useResource(2, user)) - return 0 - playsound(loc, usesound, 50, 1) - new /obj/structure/grille(A) - for(var/obj/structure/window/W in A) - qdel(W) - for(var/cdir in cardinal) - var/turf/T = get_step(A, cdir) - if(locate(/obj/structure/grille) in T) - for(var/obj/structure/window/W in T) - if(W.dir == turn(cdir, 180)) - qdel(W) - else // Build a window! - var/obj/structure/window/reinforced/W = new(A) - W.dir = cdir - var/turf/AT = A - AT.ChangeTurf(/turf/simulated/floor/plating) // Platings go under windows. - return 1 - to_chat(user, "ERROR! Location unsuitable for window construction!") - playsound(loc, 'sound/machines/click.ogg', 50, 1) - return 0 - -/obj/item/rcd/afterattack(atom/A, mob/user, proximity) - if(!proximity) - return FALSE - if(istype(A, /turf/space/transit)) - return FALSE - if(!is_type_in_list(A, allowed_targets)) - return FALSE - - switch(mode) - if(RCD_MODE_TURF) - . = mode_turf(A, user) - if(RCD_MODE_AIRLOCK) - . = mode_airlock(A, user) - if(RCD_MODE_DECON) - . = mode_decon(A, user) - if(RCD_MODE_WINDOW) - . = mode_window(A, user) - else - to_chat(user, "ERROR: RCD in MODE: [mode] attempted use by [user]. Send this text #coderbus or an admin.") - . = 0 - - SSnanoui.update_uis(src) - -/obj/item/rcd/proc/useResource(amount, mob/user) - if(matter < amount) - return 0 - matter -= amount - SSnanoui.update_uis(src) - return 1 - -/obj/item/rcd/proc/checkResource(amount, mob/user) - return matter >= amount - -/obj/item/rcd/borg - canRwall = 1 - var/use_multiplier = 160 - -/obj/item/rcd/borg/syndicate - use_multiplier = 80 - -/obj/item/rcd/borg/useResource(amount, mob/user) - if(!isrobot(user)) - return 0 - var/mob/living/silicon/robot/R = user - return R.cell.use(amount * use_multiplier) - -/obj/item/rcd/borg/checkResource(amount, mob/user) - if(!isrobot(user)) - return 0 - var/mob/living/silicon/robot/R = user - return R.cell.charge >= (amount * use_multiplier) - -/obj/item/rcd/proc/detonate_pulse() - audible_message("[src] begins to vibrate and \ - buzz loudly!","[src] begins \ - vibrating violently!") - // 5 seconds to get rid of it - addtimer(CALLBACK(src, .proc/detonate_pulse_explode), 50) - -/obj/item/rcd/proc/detonate_pulse_explode() - explosion(src, 0, 0, 3, 1, flame_range = 1) - qdel(src) - -/obj/item/rcd/preloaded - matter = 100 - -/obj/item/rcd/combat - name = "combat RCD" - max_matter = 500 - matter = 500 - canRwall = 1 - -/obj/item/rcd_ammo - name = "compressed matter cartridge" - desc = "Highly compressed matter for the RCD." - icon = 'icons/obj/ammo.dmi' - icon_state = "rcd" - item_state = "rcdammo" - opacity = 0 - density = 0 - anchored = 0.0 - origin_tech = "materials=3" - materials = list(MAT_METAL=16000, MAT_GLASS=8000) - var/ammoamt = 20 - -/obj/item/rcd_ammo/large - ammoamt = 100 +/* +CONTAINS: +RCD // no fucking shit sherlock +*/ +#define RCD_PAGE_MAIN 1 +#define RCD_PAGE_AIRLOCK 2 + +#define RCD_MODE_TURF "Turf" +#define RCD_MODE_AIRLOCK "Airlock" +#define RCD_MODE_DECON "Deconstruct" +#define RCD_MODE_WINDOW "Windows" + +GLOBAL_LIST_INIT(rcd_door_types, list( + /obj/machinery/door/airlock = "Standard", /obj/machinery/door/airlock/glass = "Standard (Glass)", + /obj/machinery/door/airlock/command = "Command", /obj/machinery/door/airlock/command/glass = "Command (Glass)", + /obj/machinery/door/airlock/security = "Security", /obj/machinery/door/airlock/security/glass = "Security (Glass)", + /obj/machinery/door/airlock/engineering = "Engineering", /obj/machinery/door/airlock/engineering/glass = "Engineering (Glass)", + /obj/machinery/door/airlock/medical = "Medical", /obj/machinery/door/airlock/medical/glass = "Medical (Glass)", + /obj/machinery/door/airlock/maintenance = "Maintenance", /obj/machinery/door/airlock/maintenance/glass = "Maintenance (Glass)", + /obj/machinery/door/airlock/external = "External", /obj/machinery/door/airlock/external/glass = "External (Glass)", + /obj/machinery/door/airlock/maintenance/external = "External Maintenance", + /obj/machinery/door/airlock/maintenance/external/glass = "External Maintenance (Glass)", + /obj/machinery/door/airlock/freezer = "Freezer", + /obj/machinery/door/airlock/mining = "Mining", /obj/machinery/door/airlock/mining/glass = "Mining (Glass)", + /obj/machinery/door/airlock/research = "Research", /obj/machinery/door/airlock/research/glass = "Research (Glass)", + /obj/machinery/door/airlock/atmos = "Atmospherics", /obj/machinery/door/airlock/atmos/glass = "Atmospherics (Glass)", + /obj/machinery/door/airlock/science = "Science", /obj/machinery/door/airlock/science/glass = "Science (Glass)", + /obj/machinery/door/airlock/hatch = "Airtight Hatch", + /obj/machinery/door/airlock/maintenance_hatch = "Maintenance Hatch" +)) + +/obj/item/rcd + name = "rapid-construction-device (RCD)" + desc = "A device used to rapidly build and deconstruct walls, floors and airlocks." + icon = 'icons/obj/tools.dmi' + icon_state = "rcd" + opacity = 0 + density = 0 + anchored = 0 + flags = CONDUCT | NOBLUDGEON + force = 0 + throwforce = 10 + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL = 30000) + origin_tech = "engineering=4;materials=2" + toolspeed = 1 + usesound = 'sound/items/deconstruct.ogg' + flags_2 = NO_MAT_REDEMPTION_2 + req_access = list(ACCESS_ENGINE) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + // Important shit + var/datum/effect_system/spark_spread/spark_system + var/matter = 0 + var/max_matter = 100 + var/mode = RCD_MODE_TURF + var/canRwall = 0 + + // UI shit + var/menu = RCD_PAGE_MAIN + var/locked = TRUE + var/door_type = /obj/machinery/door/airlock + var/door_name = "Airlock" + var/list/door_accesses = list() + var/list/door_accesses_list = list() + var/one_access + + // Stupid shit + var/static/allowed_targets = list(/turf, /obj/structure/grille, /obj/structure/window, /obj/structure/lattice, /obj/machinery/door/airlock) + +/obj/item/rcd/Initialize() + . = ..() + spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(5, 0, src) + spark_system.attach(src) + GLOB.rcd_list += src + + door_accesses_list = list() + for(var/access in get_all_accesses()) + door_accesses_list[++door_accesses_list.len] = list("name" = get_access_desc(access), "id" = access, "enabled" = (access in door_accesses)) + +/obj/item/rcd/examine(mob/user) + . = ..() + . += "MATTER: [matter]/[max_matter] matter-units." + . += "MODE: [mode]." + +/obj/item/rcd/Destroy() + QDEL_NULL(spark_system) + GLOB.rcd_list -= src + return ..() + +/obj/item/rcd/proc/get_airlock_image(airlock_type) + var/obj/machinery/door/airlock/proto = airlock_type + var/ic = initial(proto.icon) + var/mutable_appearance/MA = mutable_appearance(ic, "closed") + if(!initial(proto.glass)) + MA.overlays += "fill_closed" + // Not scaling these down to button size because they look horrible then, instead just bumping up radius. + return MA + +/obj/item/rcd/proc/check_menu(mob/living/user) + if(!istype(user)) + return FALSE + if(user.incapacitated() || !user.Adjacent(src)) + return FALSE + return TRUE + +/obj/item/rcd/attackby(obj/item/W, mob/user, params) + ..() + + if(istype(W, /obj/item/rcd_ammo)) + var/obj/item/rcd_ammo/R = W + if((matter + R.ammoamt) > max_matter) + to_chat(user, "The RCD can't hold any more matter-units.") + return + matter += R.ammoamt + if(!user.unEquip(R)) + to_chat(user, "[R] is stuck to your hand!") + return + qdel(R) + playsound(loc, 'sound/machines/click.ogg', 50, 1) + to_chat(user, "The RCD now holds [matter]/[max_matter] matter-units.") + SSnanoui.update_uis(src) + +/obj/item/rcd/proc/radial_menu(mob/user) + if(!check_menu(user)) + return + var/list/choices = list( + RCD_MODE_AIRLOCK = image(icon = 'icons/obj/interface.dmi', icon_state = "airlock"), + RCD_MODE_DECON = image(icon = 'icons/obj/interface.dmi', icon_state = "delete"), + RCD_MODE_WINDOW = image(icon = 'icons/obj/interface.dmi', icon_state = "grillewindow"), + RCD_MODE_TURF = image(icon = 'icons/obj/interface.dmi', icon_state = "wallfloor"), + "UI" = image(icon = 'icons/obj/interface.dmi', icon_state = "ui_interact") + ) + if(mode == RCD_MODE_AIRLOCK) + choices += list( + "Change Access" = image(icon = 'icons/obj/interface.dmi', icon_state = "access"), + "Change Airlock Type" = image(icon = 'icons/obj/interface.dmi', icon_state = "airlocktype") + ) + choices -= mode // Get rid of the current mode, clicking it won't do anything. + var/choice = show_radial_menu(user, src, choices, custom_check = CALLBACK(src, .proc/check_menu, user)) + if(!check_menu(user)) + return + switch(choice) + if(RCD_MODE_AIRLOCK, RCD_MODE_DECON, RCD_MODE_WINDOW, RCD_MODE_TURF) + mode = choice + if("UI") + menu = RCD_PAGE_MAIN + ui_interact(user) + return + if("Change Access", "Change Airlock Type") + menu = RCD_PAGE_AIRLOCK + ui_interact(user) + return + else + return + playsound(src, 'sound/effects/pop.ogg', 50, 0) + to_chat(user, "You change [src]'s mode to '[choice]'.") + + +/obj/item/rcd/attack_self(mob/user) + //Change the mode // Oh I thought the UI was just for fucking staring at + radial_menu(user) + +/obj/item/rcd/attack_self_tk(mob/user) + radial_menu(user) + +/obj/item/rcd/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = inventory_state) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "rcd.tmpl", "[name]", 450, 400, state = state) + ui.open() + ui.set_auto_update(1) + +/obj/item/rcd/ui_data(mob/user, ui_key = "main", datum/topic_state/state = inventory_state) + var/data[0] + data["mode"] = mode + data["door_type"] = door_type + data["door_name"] = door_name + data["menu"] = menu + data["matter"] = matter + data["max_matter"] = max_matter + data["one_access"] = one_access + data["locked"] = locked + + if(menu == RCD_PAGE_AIRLOCK) + var/list/door_types_list = list() + for(var/type in GLOB.rcd_door_types) + door_types_list[++door_types_list.len] = list("name" = GLOB.rcd_door_types[type], "type" = type) + data["allowed_door_types"] = door_types_list + + data["door_accesses"] = door_accesses_list + + return data + +/obj/item/rcd/Topic(href, href_list, nowindow, state) + if(..()) + return 1 + + if(prob(20)) + spark_system.start() + + if(href_list["mode"]) + mode = href_list["mode"] + . = 1 + + if(href_list["door_type"]) + var/new_door_type = text2path(href_list["door_type"]) + if(!(new_door_type in GLOB.rcd_door_types)) + message_admins("RCD Door HREF exploit attempted by [key_name(usr)]!") + return + door_type = new_door_type + . = 1 + + if(href_list["menu"]) + menu = text2num(href_list["menu"]) + . = 1 + + if(href_list["login"]) + if(allowed(usr)) + locked = FALSE + . = 1 + + if(href_list["logout"]) + locked = TRUE + . = 1 + + if(!locked) + if(href_list["toggle_one_access"]) + one_access = !one_access + . = 1 + + if(href_list["toggle_access"]) + var/href_access = text2num(href_list["toggle_access"]) + if(href_access in door_accesses) + door_accesses -= href_access + else + door_accesses += href_access + door_accesses_list = list() + for(var/access in get_all_accesses()) + door_accesses_list[++door_accesses_list.len] = list("name" = get_access_desc(access), "id" = access, "enabled" = (access in door_accesses)) + . = 1 + + if(href_list["choice"]) + var/temp_t = sanitize(copytext(input("Enter a custom Airlock Name.", "Airlock Name"), 1, MAX_MESSAGE_LEN)) + if(temp_t) + door_name = temp_t + +/obj/item/rcd/proc/mode_turf(atom/A, mob/user) + if(isspaceturf(A) || istype(A, /obj/structure/lattice)) + if(useResource(1, user)) + to_chat(user, "Building Floor...") + playsound(loc, usesound, 50, 1) + var/turf/AT = get_turf(A) + AT.ChangeTurf(/turf/simulated/floor/plating) + return TRUE + to_chat(user, "ERROR! Not enough matter in unit to construct this floor!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return FALSE + + if(isfloorturf(A)) + if(checkResource(3, user)) + to_chat(user, "Building Wall...") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + if(do_after(user, 20 * toolspeed, target = A)) + if(!useResource(3, user)) + return FALSE + playsound(loc, usesound, 50, 1) + var/turf/AT = A + AT.ChangeTurf(/turf/simulated/wall) + return TRUE + return FALSE + to_chat(user, "ERROR! Not enough matter in unit to construct this wall!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return FALSE + to_chat(user, "ERROR! Location unsuitable for wall construction!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return FALSE + +/obj/item/rcd/proc/mode_airlock(atom/A, mob/user) + if(isfloorturf(A)) + if(checkResource(10, user)) + to_chat(user, "Building Airlock...") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + if(do_after(user, 50 * toolspeed, target = A)) + if(locate(/obj/machinery/door/airlock) in A.contents) + return FALSE + if(!useResource(10, user)) + return FALSE + playsound(loc, usesound, 50, 1) + var/obj/machinery/door/airlock/T = new door_type(A) + T.name = door_name + T.autoclose = TRUE + if(one_access) + T.req_one_access = door_accesses.Copy() + else + T.req_access = door_accesses.Copy() + return FALSE + return FALSE + to_chat(user, "ERROR! Not enough matter in unit to construct this airlock!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return FALSE + to_chat(user, "ERROR! Location unsuitable for airlock construction!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return FALSE + +/obj/item/rcd/proc/mode_decon(atom/A, mob/user) + if(iswallturf(A)) + if(istype(A, /turf/simulated/wall/r_wall) && !canRwall) + return FALSE + if(checkResource(5, user)) + to_chat(user, "Deconstructing Wall...") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + if(do_after(user, 40 * toolspeed, target = A)) + if(!useResource(5, user)) + return FALSE + playsound(loc, usesound, 50, 1) + var/turf/AT = A + AT.ChangeTurf(/turf/simulated/floor/plating) + return TRUE + return FALSE + to_chat(user, "ERROR! Not enough matter in unit to deconstruct this wall!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return FALSE + + if(isfloorturf(A)) + if(checkResource(5, user)) + to_chat(user, "Deconstructing Floor...") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + if(do_after(user, 50 * toolspeed, target = A)) + if(!useResource(5, user)) + return FALSE + playsound(loc, usesound, 50, 1) + var/turf/AT = A + AT.ChangeTurf(AT.baseturf) + return TRUE + return FALSE + to_chat(user, "ERROR! Not enough matter in unit to deconstruct this floor!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return FALSE + + if(istype(A, /obj/machinery/door/airlock)) + if(checkResource(20, user)) + to_chat(user, "Deconstructing Airlock...") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + if(do_after(user, 50 * toolspeed, target = A)) + if(!useResource(20, user)) + return FALSE + playsound(loc, usesound, 50, 1) + qdel(A) + return TRUE + return FALSE + to_chat(user, "ERROR! Not enough matter in unit to deconstruct this airlock!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return FALSE + + if(istype(A, /obj/structure/window)) // You mean the grille of course, do you? + A = locate(/obj/structure/grille) in A.loc + if(istype(A, /obj/structure/grille)) + if(!checkResource(2, user)) + to_chat(user, "ERROR! Not enough matter in unit to deconstruct this window!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return 0 + to_chat(user, "Deconstructing window...") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + if(!do_after(user, 20 * toolspeed, target = A)) + return 0 + if(!useResource(2, user)) + return 0 + playsound(loc, usesound, 50, 1) + var/turf/T1 = get_turf(A) + QDEL_NULL(A) + for(var/obj/structure/window/W in T1.contents) + qdel(W) + for(var/cdir in cardinal) + var/turf/T2 = get_step(T1, cdir) + if(locate(/obj/structure/window/full/shuttle) in T2) + continue // Shuttle windows? Nah. We don't need extra windows there. + if(!(locate(/obj/structure/grille) in T2)) + continue + for(var/obj/structure/window/W in T2) + if(W.dir == turn(cdir, 180)) + qdel(W) + var/obj/structure/window/reinforced/W = new(T2) + W.dir = turn(cdir, 180) + return TRUE + return FALSE + +/obj/item/rcd/proc/mode_window(atom/A, mob/user) + if(isfloorturf(A)) + if(locate(/obj/structure/grille) in A) + return 0 // We already have window + if(!checkResource(2, user)) + to_chat(user, "ERROR! Not enough matter in unit to construct this window!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return 0 + to_chat(user, "Constructing window...") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + if(!do_after(user, 20 * toolspeed, target = A)) + return 0 + if(locate(/obj/structure/grille) in A) + return 0 // We already have window + if(!useResource(2, user)) + return 0 + playsound(loc, usesound, 50, 1) + new /obj/structure/grille(A) + for(var/obj/structure/window/W in A) + qdel(W) + for(var/cdir in cardinal) + var/turf/T = get_step(A, cdir) + if(locate(/obj/structure/grille) in T) + for(var/obj/structure/window/W in T) + if(W.dir == turn(cdir, 180)) + qdel(W) + else // Build a window! + var/obj/structure/window/reinforced/W = new(A) + W.dir = cdir + var/turf/AT = A + AT.ChangeTurf(/turf/simulated/floor/plating) // Platings go under windows. + return 1 + to_chat(user, "ERROR! Location unsuitable for window construction!") + playsound(loc, 'sound/machines/click.ogg', 50, 1) + return 0 + +/obj/item/rcd/afterattack(atom/A, mob/user, proximity) + if(!proximity) + return FALSE + if(istype(A, /turf/space/transit)) + return FALSE + if(!is_type_in_list(A, allowed_targets)) + return FALSE + + switch(mode) + if(RCD_MODE_TURF) + . = mode_turf(A, user) + if(RCD_MODE_AIRLOCK) + . = mode_airlock(A, user) + if(RCD_MODE_DECON) + . = mode_decon(A, user) + if(RCD_MODE_WINDOW) + . = mode_window(A, user) + else + to_chat(user, "ERROR: RCD in MODE: [mode] attempted use by [user]. Send this text #coderbus or an admin.") + . = 0 + + SSnanoui.update_uis(src) + +/obj/item/rcd/proc/useResource(amount, mob/user) + if(matter < amount) + return 0 + matter -= amount + SSnanoui.update_uis(src) + return 1 + +/obj/item/rcd/proc/checkResource(amount, mob/user) + return matter >= amount + +/obj/item/rcd/borg + canRwall = 1 + var/use_multiplier = 160 + +/obj/item/rcd/borg/syndicate + use_multiplier = 80 + +/obj/item/rcd/borg/useResource(amount, mob/user) + if(!isrobot(user)) + return 0 + var/mob/living/silicon/robot/R = user + return R.cell.use(amount * use_multiplier) + +/obj/item/rcd/borg/checkResource(amount, mob/user) + if(!isrobot(user)) + return 0 + var/mob/living/silicon/robot/R = user + return R.cell.charge >= (amount * use_multiplier) + +/obj/item/rcd/proc/detonate_pulse() + audible_message("[src] begins to vibrate and \ + buzz loudly!","[src] begins \ + vibrating violently!") + // 5 seconds to get rid of it + addtimer(CALLBACK(src, .proc/detonate_pulse_explode), 50) + +/obj/item/rcd/proc/detonate_pulse_explode() + explosion(src, 0, 0, 3, 1, flame_range = 1) + qdel(src) + +/obj/item/rcd/preloaded + matter = 100 + +/obj/item/rcd/combat + name = "combat RCD" + max_matter = 500 + matter = 500 + canRwall = 1 + +/obj/item/rcd_ammo + name = "compressed matter cartridge" + desc = "Highly compressed matter for the RCD." + icon = 'icons/obj/ammo.dmi' + icon_state = "rcd" + item_state = "rcdammo" + opacity = 0 + density = 0 + anchored = 0.0 + origin_tech = "materials=3" + materials = list(MAT_METAL=16000, MAT_GLASS=8000) + var/ammoamt = 20 + +/obj/item/rcd_ammo/large + ammoamt = 100 diff --git a/code/game/objects/items/weapons/RCL.dm b/code/game/objects/items/weapons/RCL.dm index fc803a5398e..27583a7edd3 100644 --- a/code/game/objects/items/weapons/RCL.dm +++ b/code/game/objects/items/weapons/RCL.dm @@ -151,4 +151,4 @@ loaded = new() loaded.max_amount = max_amount loaded.amount = max_amount - update_icon() \ No newline at end of file + update_icon() diff --git a/code/game/objects/items/weapons/RSF.dm b/code/game/objects/items/weapons/RSF.dm index 55dfa04c394..a8da7503c72 100644 --- a/code/game/objects/items/weapons/RSF.dm +++ b/code/game/objects/items/weapons/RSF.dm @@ -1,84 +1,84 @@ -/* -CONTAINS: -RSF -*/ - -/obj/item/rsf - name = "\improper Rapid-Service-Fabricator" - desc = "A device used to rapidly deploy service items." - icon = 'icons/obj/tools.dmi' - icon_state = "rsf" - opacity = 0 - density = 0 - anchored = 0.0 - var/matter = 0 - var/mode = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - w_class = WEIGHT_CLASS_NORMAL - var/list/configured_items = list() - -/obj/item/rsf/New() - ..() - desc = "A RSF. It currently holds [matter]/30 fabrication-units." - // configured_items[ID_NUMBER] = list("Human-readable name", price in energy, /type/path) - configured_items[++configured_items.len] = list("Dosh", 50, /obj/item/stack/spacecash/c10) - configured_items[++configured_items.len] = list("Drinking Glass", 50, /obj/item/reagent_containers/food/drinks/drinkingglass) - configured_items[++configured_items.len] = list("Paper", 50, /obj/item/paper) - configured_items[++configured_items.len] = list("Pen", 50, /obj/item/pen) - configured_items[++configured_items.len] = list("Dice Pack", 50, /obj/item/storage/pill_bottle/dice) - configured_items[++configured_items.len] = list("Cigarette", 50, /obj/item/clothing/mask/cigarette) - configured_items[++configured_items.len] = list("Snack - Newdles", 4000, /obj/item/reagent_containers/food/snacks/chinese/newdles) - configured_items[++configured_items.len] = list("Snack - Donut", 4000, /obj/item/reagent_containers/food/snacks/donut) - configured_items[++configured_items.len] = list("Snack - Chicken Soup", 4000, /obj/item/reagent_containers/food/drinks/chicken_soup) - configured_items[++configured_items.len] = list("Snack - Turkey Burger", 4000, /obj/item/reagent_containers/food/snacks/tofuburger) - -/obj/item/rsf/attackby(obj/item/W as obj, mob/user as mob, params) - ..() - if(istype(W, /obj/item/rcd_ammo)) - if((matter + 10) > 30) - to_chat(user, "The RSF cant hold any more matter.") - return - qdel(W) - matter += 10 - playsound(src.loc, 'sound/machines/click.ogg', 10, 1) - to_chat(user, "The RSF now holds [matter]/30 fabrication-units.") - desc = "A RSF. It currently holds [matter]/30 fabrication-units." - return - -/obj/item/rsf/attack_self(mob/user as mob) - playsound(src.loc, 'sound/effects/pop.ogg', 50, 0) - if(mode == configured_items.len) - mode = 1 - else - mode++ - to_chat(user, "Changed dispensing mode to '" + configured_items[mode][1] + "'") - - -/obj/item/rsf/afterattack(atom/A, mob/user as mob, proximity) - if(!proximity) return - if(!(istype(A, /obj/structure/table) || istype(A, /turf/simulated/floor))) - return - var spawn_location - var/turf/T = get_turf(A) - if(istype(T) && !T.density) - spawn_location = T - else - to_chat(user, "The RSF can only create service items on tables, or floors.") - return - if(isrobot(user)) - var/mob/living/silicon/robot/engy = user - if(!engy.cell.use(configured_items[mode][2])) - to_chat(user, "Insufficient energy.") - return - else - if(!matter) - to_chat(user, "Insufficient matter.") - return - matter-- - to_chat(user, "The RSF now holds [matter]/30 fabrication-units.") - desc = "A RSF. It currently holds [matter]/30 fabrication-units." - - to_chat(user, "Dispensing " + configured_items[mode][1] + "...") - playsound(loc, 'sound/machines/click.ogg', 10, 1) - var/type_path = configured_items[mode][3] - new type_path(spawn_location) +/* +CONTAINS: +RSF +*/ + +/obj/item/rsf + name = "\improper Rapid-Service-Fabricator" + desc = "A device used to rapidly deploy service items." + icon = 'icons/obj/tools.dmi' + icon_state = "rsf" + opacity = 0 + density = 0 + anchored = 0.0 + var/matter = 0 + var/mode = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + w_class = WEIGHT_CLASS_NORMAL + var/list/configured_items = list() + +/obj/item/rsf/New() + ..() + desc = "A RSF. It currently holds [matter]/30 fabrication-units." + // configured_items[ID_NUMBER] = list("Human-readable name", price in energy, /type/path) + configured_items[++configured_items.len] = list("Dosh", 50, /obj/item/stack/spacecash/c10) + configured_items[++configured_items.len] = list("Drinking Glass", 50, /obj/item/reagent_containers/food/drinks/drinkingglass) + configured_items[++configured_items.len] = list("Paper", 50, /obj/item/paper) + configured_items[++configured_items.len] = list("Pen", 50, /obj/item/pen) + configured_items[++configured_items.len] = list("Dice Pack", 50, /obj/item/storage/pill_bottle/dice) + configured_items[++configured_items.len] = list("Cigarette", 50, /obj/item/clothing/mask/cigarette) + configured_items[++configured_items.len] = list("Snack - Newdles", 4000, /obj/item/reagent_containers/food/snacks/chinese/newdles) + configured_items[++configured_items.len] = list("Snack - Donut", 4000, /obj/item/reagent_containers/food/snacks/donut) + configured_items[++configured_items.len] = list("Snack - Chicken Soup", 4000, /obj/item/reagent_containers/food/drinks/chicken_soup) + configured_items[++configured_items.len] = list("Snack - Turkey Burger", 4000, /obj/item/reagent_containers/food/snacks/tofuburger) + +/obj/item/rsf/attackby(obj/item/W as obj, mob/user as mob, params) + ..() + if(istype(W, /obj/item/rcd_ammo)) + if((matter + 10) > 30) + to_chat(user, "The RSF cant hold any more matter.") + return + qdel(W) + matter += 10 + playsound(src.loc, 'sound/machines/click.ogg', 10, 1) + to_chat(user, "The RSF now holds [matter]/30 fabrication-units.") + desc = "A RSF. It currently holds [matter]/30 fabrication-units." + return + +/obj/item/rsf/attack_self(mob/user as mob) + playsound(src.loc, 'sound/effects/pop.ogg', 50, 0) + if(mode == configured_items.len) + mode = 1 + else + mode++ + to_chat(user, "Changed dispensing mode to '" + configured_items[mode][1] + "'") + + +/obj/item/rsf/afterattack(atom/A, mob/user as mob, proximity) + if(!proximity) return + if(!(istype(A, /obj/structure/table) || istype(A, /turf/simulated/floor))) + return + var spawn_location + var/turf/T = get_turf(A) + if(istype(T) && !T.density) + spawn_location = T + else + to_chat(user, "The RSF can only create service items on tables, or floors.") + return + if(isrobot(user)) + var/mob/living/silicon/robot/engy = user + if(!engy.cell.use(configured_items[mode][2])) + to_chat(user, "Insufficient energy.") + return + else + if(!matter) + to_chat(user, "Insufficient matter.") + return + matter-- + to_chat(user, "The RSF now holds [matter]/30 fabrication-units.") + desc = "A RSF. It currently holds [matter]/30 fabrication-units." + + to_chat(user, "Dispensing " + configured_items[mode][1] + "...") + playsound(loc, 'sound/machines/click.ogg', 10, 1) + var/type_path = configured_items[mode][3] + new type_path(spawn_location) diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm index 20b2ac3cfc1..58258687fc7 100644 --- a/code/game/objects/items/weapons/cards_ids.dm +++ b/code/game/objects/items/weapons/cards_ids.dm @@ -1,927 +1,927 @@ -/* Cards - * Contains: - * DATA CARD - * ID CARD - * FINGERPRINT CARD HOLDER - * FINGERPRINT CARD - */ - - - -/* - * DATA CARDS - Used for the teleporter - */ -/obj/item/card - name = "card" - desc = "A card." - icon = 'icons/obj/card.dmi' - w_class = WEIGHT_CLASS_TINY - var/associated_account_number = 0 - - var/list/files = list( ) - -/obj/item/card/data - name = "data card" - desc = "A disk containing data." - icon_state = "data" - var/function = "storage" - var/data = "null" - var/special = null - item_state = "card-id" - -/obj/item/card/data/verb/label(t as text) - set name = "Label Disk" - set category = "Object" - set src in usr - - if(t) - src.name = text("Data Disk- '[]'", t) - else - src.name = "Data Disk" - src.add_fingerprint(usr) - return - -/obj/item/card/data/clown - name = "coordinates to clown planet" - icon_state = "data" - item_state = "card-id" - layer = 3 - level = 2 - desc = "This card contains coordinates to the fabled Clown Planet. Handle with care." - function = "teleporter" - data = "Clown Land" - -/* - * ID CARDS - */ - -/obj/item/card/emag_broken - desc = "It's a card with a magnetic strip attached to some circuitry. It looks too busted to be used for anything but salvage." - name = "broken cryptographic sequencer" - icon_state = "emag" - item_state = "card-id" - origin_tech = "magnets=2;syndicate=2" - -/obj/item/card/emag - desc = "It's a card with a magnetic strip attached to some circuitry." - name = "cryptographic sequencer" - icon_state = "emag" - item_state = "card-id" - origin_tech = "magnets=2;syndicate=2" - flags = NOBLUDGEON - flags_2 = NO_MAT_REDEMPTION_2 - -/obj/item/card/emag/attack() - return - -/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) - var/atom/A = target - if(!proximity) - return - A.emag_act(user) - -/obj/item/card/id - name = "identification card" - desc = "A card used to provide ID and determine access across the station." - icon_state = "id" - item_state = "card-id" - var/mining_points = 0 //For redeeming at mining equipment lockers - var/list/access = list() - var/registered_name = "Unknown" // The name registered_name on the card - slot_flags = SLOT_ID - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - var/untrackable // Can not be tracked by AI's - - var/blood_type = "\[UNSET\]" - var/dna_hash = "\[UNSET\]" - var/fingerprint_hash = "\[UNSET\]" - - //alt titles are handled a bit weirdly in order to unobtrusively integrate into existing ID system - var/assignment = null //can be alt title or the actual job - var/rank = null //actual job - var/owner_uid - var/owner_ckey - var/dorm = 0 // determines if this ID has claimed a dorm already - - var/sex - var/age - var/photo - var/dat - var/stamped = 0 - - var/obj/item/card/id/guest/guest_pass = null // Guest pass attached to the ID - -/obj/item/card/id/New() - ..() - spawn(30) - if(ishuman(loc) && blood_type == "\[UNSET\]") - var/mob/living/carbon/human/H = loc - SetOwnerInfo(H) - -/obj/item/card/id/examine(mob/user) - . = ..() - if(in_range(user, src)) - show(usr) - else - . += "It is too far away." - if(guest_pass) - . += "There is a guest pass attached to this ID card" - if(world.time < guest_pass.expiration_time) - . += "It expires at [station_time_timestamp("hh:mm:ss", guest_pass.expiration_time)]." - else - . += "It expired at [station_time_timestamp("hh:mm:ss", guest_pass.expiration_time)]." - . += "It grants access to following areas:" - for(var/A in guest_pass.temp_access) - . += "[get_access_desc(A)]." - . += "Issuing reason: [guest_pass.reason]." - -/obj/item/card/id/proc/show(mob/user as mob) - var/datum/asset/assets = get_asset_datum(/datum/asset/simple/paper) - assets.send(user) - - var/datum/browser/popup = new(user, "idcard", name, 600, 400) - popup.set_content(dat) - popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/item/card/id/attack_self(mob/user as mob) - user.visible_message("[user] shows you: [bicon(src)] [src.name]. The assignment on the card: [src.assignment]",\ - "You flash your ID card: [bicon(src)] [src.name]. The assignment on the card: [src.assignment]") - if(mining_points) - to_chat(user, "There's [mining_points] mining equipment redemption points loaded onto this card.") - src.add_fingerprint(user) - return - -/obj/item/card/id/proc/UpdateName() - name = "[src.registered_name]'s ID Card ([src.assignment])" - -/obj/item/card/id/proc/SetOwnerInfo(var/mob/living/carbon/human/H) - if(!H || !H.dna) - return - - sex = capitalize(H.gender) - age = H.age - blood_type = H.dna.blood_type - dna_hash = H.dna.unique_enzymes - fingerprint_hash = md5(H.dna.uni_identity) - - RebuildHTML() - -/obj/item/card/id/proc/RebuildHTML() - var/photo_front = "'data:image/png;base64,[icon2base64(icon(photo, dir = SOUTH))]'" - var/photo_side = "'data:image/png;base64,[icon2base64(icon(photo, dir = WEST))]'" - - dat = {"
        - Name: [registered_name]
        - Sex: [sex]
        - Age: [age]
        - Rank: [assignment]
        - Fingerprint: [fingerprint_hash]
        - Blood Type: [blood_type]
        - DNA Hash: [dna_hash]

        -
        Photo:
        -
        "} - -/obj/item/card/id/GetAccess() - if(!guest_pass) - return access - return access | guest_pass.GetAccess() - -/obj/item/card/id/GetID() - return src - -/obj/item/card/id/proc/getRankAndAssignment() - var/jobnamedata = "" - if(rank) - jobnamedata += rank - if(rank != assignment) - jobnamedata += " (" + assignment + ")" - return jobnamedata - -/obj/item/card/id/proc/getPlayer() - if(owner_uid) - var/mob/living/carbon/human/H = locateUID(owner_uid) - if(istype(H) && H.ckey == owner_ckey) - return H - owner_uid = null - if(owner_ckey) - for(var/mob/M in GLOB.player_list) - if(M.ckey && M.ckey == owner_ckey) - owner_uid = M.UID() - return M - owner_ckey = null - -/obj/item/card/id/proc/is_untrackable() - return untrackable - -/obj/item/card/id/proc/update_label(newname, newjob) - if(newname || newjob) - name = "[(!newname) ? "identification card" : "[newname]'s ID Card"][(!newjob) ? "" : " ([newjob])"]" - return - - name = "[(!registered_name) ? "identification card" : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" - -/obj/item/card/id/attackby(obj/item/W as obj, mob/user as mob, params) - ..() - - if(istype(W, /obj/item/id_decal/)) - var/obj/item/id_decal/decal = W - to_chat(user, "You apply [decal] to [src].") - if(decal.override_name) - name = decal.decal_name - desc = decal.decal_desc - icon_state = decal.decal_icon_state - item_state = decal.decal_item_state - qdel(decal) - qdel(W) - return - - else if(istype (W,/obj/item/stamp)) - if(!stamped) - dat+="" - stamped = 1 - to_chat(user, "You stamp the ID card!") - else - to_chat(user, "This ID has already been stamped!") - - else if(istype(W, /obj/item/card/id/guest)) - if(istype(src, /obj/item/card/id/guest)) - return - var/obj/item/card/id/guest/G = W - if(world.time > G.expiration_time) - to_chat(user, "There's no point, the guest pass has expired.") - return - if(guest_pass) - to_chat(user, "There's already a guest pass attached to this ID.") - return - if(G.registered_name != registered_name && G.registered_name != "NOT SPECIFIED") - to_chat(user, "The guest pass cannot be attached to this ID") - return - if(!user.unEquip(G)) - return - G.loc = src - guest_pass = G - -/obj/item/card/id/verb/remove_guest_pass() - set name = "Remove Guest Pass" - set category = "Object" - set src in range(0) - - if(usr.stat || !usr.canmove || usr.restrained()) - return - - if(guest_pass) - to_chat(usr, "You remove the guest pass from this ID.") - guest_pass.forceMove(get_turf(src)) - guest_pass = null - else - to_chat(usr, "There is no guest pass attached to this ID") - -/obj/item/card/id/serialize() - var/list/data = ..() - - data["sex"] = sex - data["age"] = age - data["btype"] = blood_type - data["dna_hash"] = dna_hash - data["fprint_hash"] = fingerprint_hash - data["access"] = access - data["job"] = assignment - data["account"] = associated_account_number - data["owner"] = registered_name - data["mining"] = mining_points - return data - -/obj/item/card/id/deserialize(list/data) - sex = data["sex"] - age = data["age"] - blood_type = data["btype"] - dna_hash = data["dna_hash"] - fingerprint_hash = data["fprint_hash"] - access = data["access"] // No need for a copy, the list isn't getting touched - assignment = data["job"] - associated_account_number = data["account"] - registered_name = data["owner"] - mining_points = data["mining"] - // We'd need to use icon serialization(b64) to save the photo, and I don't feel like i - UpdateName() - RebuildHTML() - ..() - -/obj/item/card/id/silver - name = "identification card" - desc = "A silver card which shows honour and dedication." - icon_state = "silver" - item_state = "silver_id" - -/obj/item/card/id/gold - name = "identification card" - desc = "A golden card which shows power and might." - icon_state = "gold" - item_state = "gold_id" - -/obj/item/card/id/syndicate - name = "agent card" - var/list/initial_access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_EXTERNAL_AIRLOCKS) - origin_tech = "syndicate=1" - var/registered_user = null - untrackable = 1 - var/anyone = FALSE //Can anyone forge the ID or just syndicate? - -/obj/item/card/id/syndicate/anyone - anyone = TRUE - -/obj/item/card/id/syndicate/New() - access = initial_access.Copy() - ..() - -/obj/item/card/id/syndicate/vox - name = "agent card" - initial_access = list(ACCESS_MAINT_TUNNELS, ACCESS_VOX, ACCESS_EXTERNAL_AIRLOCKS) - -/obj/item/card/id/syndicate/command - initial_access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_SYNDICATE_COMMAND, ACCESS_EXTERNAL_AIRLOCKS) - icon_state = "commander" - -/obj/item/card/id/syndicate/afterattack(var/obj/item/O as obj, mob/user as mob, proximity) - if(!proximity) - return - if(istype(O, /obj/item/card/id)) - var/obj/item/card/id/I = O - if(istype(user, /mob/living) && user.mind) - if(user.mind.special_role || anyone) - to_chat(usr, "The card's microscanners activate as you pass it over \the [I], copying its access.") - src.access |= I.access //Don't copy access if user isn't an antag -- to prevent metagaming - -/obj/item/card/id/syndicate/attack_self(mob/user as mob) - if(!src.registered_name) - var t = reject_bad_name(input(user, "What name would you like to use on this card?", "Agent Card name", ishuman(user) ? user.real_name : user.name)) - if(!t) - to_chat(user, "Invalid name.") - return - src.registered_name = t - - var u = sanitize(stripped_input(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels other than maintenance.", "Agent Card Job Assignment", "Agent", MAX_MESSAGE_LEN)) - if(!u) - to_chat(user, "Invalid assignment.") - src.registered_name = "" - return - src.assignment = u - src.name = "[src.registered_name]'s ID Card ([src.assignment])" - to_chat(user, "You successfully forge the ID card.") - registered_user = user - else if(!registered_user || registered_user == user) - if(!registered_user) - registered_user = user - - switch(alert(user,"Would you like to display \the [src] or edit it?","Choose","Show","Edit")) - if("Show") - return ..() - if("Edit") - switch(input(user,"What would you like to edit on \the [src]?") in list("Name", "Photo", "Appearance", "Sex", "Age", "Occupation", "Money Account", "Blood Type", "DNA Hash", "Fingerprint Hash", "Reset Access", "Delete Card Information")) - if("Name") - var/new_name = reject_bad_name(input(user,"What name would you like to put on this card?","Agent Card Name", ishuman(user) ? user.real_name : user.name)) - if(!Adjacent(user)) - return - src.registered_name = new_name - UpdateName() - to_chat(user, "Name changed to [new_name].") - RebuildHTML() - - if("Photo") - if(!Adjacent(user)) - return - var/job_clothes = null - if(assignment) - job_clothes = assignment - var/icon/newphoto = get_id_photo(user, job_clothes) - if(!newphoto) - return - photo = newphoto - to_chat(user, "Photo changed. Select another occupation and take a new photo if you wish to appear with different clothes.") - RebuildHTML() - - if("Appearance") - var/list/appearances = list( - "data", - "id", - "gold", - "silver", - "centcom", - "centcom_old", - "security", - "medical", - "HoS", - "research", - "cargo", - "engineering", - "CMO", - "RD", - "CE", - "clown", - "mime", - "rainbow", - "prisoner", - "syndie", - "deathsquad", - "commander", - "ERT_leader", - "ERT_security", - "ERT_engineering", - "ERT_medical", - "ERT_janitorial", - ) - var/choice = input(user, "Select the appearance for this card.", "Agent Card Appearance") in appearances - if(!Adjacent(user)) - return - if(!choice) - return - icon_state = choice - switch(choice) - if("silver") - desc = "A silver card which shows honour and dedication." - if("gold") - desc = "A golden card which shows power and might." - if("clown") - desc = "Even looking at the card strikes you with deep fear." - if("mime") - desc = "..." - if("prisoner") - desc = "You are a number, you are not a free man." - if("centcom") - desc = "An ID straight from Central Command." - else - desc = "A card used to provide ID and determine access across the station." - to_chat(usr, "Appearance changed to [choice].") - - if("Sex") - var/new_sex = sanitize(stripped_input(user,"What sex would you like to put on this card?","Agent Card Sex", ishuman(user) ? capitalize(user.gender) : "Male", MAX_MESSAGE_LEN)) - if(!Adjacent(user)) - return - sex = new_sex - to_chat(user, "Sex changed to [new_sex].") - RebuildHTML() - - if("Age") - var/default = "21" - if(ishuman(user)) - var/mob/living/carbon/human/H = user - default = H.age - var/new_age = sanitize(input(user,"What age would you like to be written on this card?","Agent Card Age", default) as text) - if(!Adjacent(user)) - return - age = new_age - to_chat(user, "Age changed to [new_age].") - RebuildHTML() - - if("Occupation") - var/list/departments =list( - "Civilian", - "Engineering", - "Medical", - "Science", - "Security", - "Support", - "Command", - "Custom", - ) - - var/department = input(user, "What job would you like to put on this card?\nChoose a department or a custom job title.\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in departments - var/new_job = "Civilian" - - if(department == "Custom") - new_job = sanitize(stripped_input(user,"Choose a custom job title:","Agent Card Occupation", "Civilian", MAX_MESSAGE_LEN)) - else if(department != "Civilian") - switch(department) - if("Engineering") - new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in engineering_positions - if("Medical") - new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in medical_positions - if("Science") - new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in science_positions - if("Security") - new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in security_positions - if("Support") - new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in support_positions - if("Command") - new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in command_positions - - if(!Adjacent(user)) - return - assignment = new_job - to_chat(user, "Occupation changed to [new_job].") - UpdateName() - RebuildHTML() - - if("Money Account") - var/new_account = input(user,"What money account would you like to link to this card?","Agent Card Account",12345) as num - if(!Adjacent(user)) - return - associated_account_number = new_account - to_chat(user, "Linked money account changed to [new_account].") - - if("Blood Type") - var/default = "\[UNSET\]" - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna) - default = H.dna.blood_type - - var/new_blood_type = sanitize(input(user,"What blood type would you like to be written on this card?","Agent Card Blood Type",default) as text) - if(!Adjacent(user)) - return - blood_type = new_blood_type - to_chat(user, "Blood type changed to [new_blood_type].") - RebuildHTML() - - if("DNA Hash") - var/default = "\[UNSET\]" - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna) - default = H.dna.unique_enzymes - - var/new_dna_hash = sanitize(input(user,"What DNA hash would you like to be written on this card?","Agent Card DNA Hash",default) as text) - if(!Adjacent(user)) - return - dna_hash = new_dna_hash - to_chat(user, "DNA hash changed to [new_dna_hash].") - RebuildHTML() - - if("Fingerprint Hash") - var/default = "\[UNSET\]" - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna) - default = md5(H.dna.uni_identity) - - var/new_fingerprint_hash = sanitize(input(user,"What fingerprint hash would you like to be written on this card?","Agent Card Fingerprint Hash",default) as text) - if(!Adjacent(user)) - return - fingerprint_hash = new_fingerprint_hash - to_chat(user, "Fingerprint hash changed to [new_fingerprint_hash].") - RebuildHTML() - - if("Reset Access") - var/response = alert(user, "Are you sure you want to reset access saved on the card?","Reset Access", "No", "Yes") - if(response == "Yes") - access = initial_access.Copy() // Initial() doesn't work on lists - to_chat(user, "Card access reset.") - - if("Delete Card Information") - var/response = alert(user, "Are you sure you want to delete all information saved on the card?","Delete Card Information", "No", "Yes") - if(response == "Yes") - name = initial(name) - registered_name = initial(registered_name) - icon_state = initial(icon_state) - sex = initial(sex) - age = initial(age) - assignment = initial(assignment) - associated_account_number = initial(associated_account_number) - blood_type = initial(blood_type) - dna_hash = initial(dna_hash) - fingerprint_hash = initial(fingerprint_hash) - photo = null - registered_user = null - to_chat(user, "All information has been deleted from \the [src].") - RebuildHTML() - else - ..() - -/obj/item/card/id/syndicate_command - name = "syndicate ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - icon_state = "syndie" - assignment = "Syndicate Overlord" - untrackable = 1 - access = list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_SYNDICATE_COMMAND, ACCESS_EXTERNAL_AIRLOCKS) - -/obj/item/card/id/captains_spare - name = "captain's spare ID" - desc = "The spare ID of the captain." - icon_state = "gold" - item_state = "gold_id" - registered_name = "Captain" - assignment = "Captain" - -/obj/item/card/id/captains_spare/New() - var/datum/job/captain/J = new/datum/job/captain - access = J.get_access() - ..() - -/obj/item/card/id/admin - name = "admin ID card" - icon_state = "admin" - item_state = "gold_id" - registered_name = "Admin" - assignment = "Testing Shit" - untrackable = 1 - -/obj/item/card/id/admin/New() - access = get_absolutely_all_accesses() - ..() - -/obj/item/card/id/centcom - name = "central command ID card" - desc = "An ID straight from Central Command." - icon_state = "centcom" - registered_name = "Central Command" - assignment = "General" - -/obj/item/card/id/centcom/New() - access = get_all_centcom_access() - ..() - -/obj/item/card/id/nanotrasen - name = "nanotrasen ID card" - icon_state = "nanotrasen" - -/obj/item/card/id/prisoner - name = "prisoner ID card" - desc = "You are a number, you are not a free man." - icon_state = "prisoner" - item_state = "orange-id" - assignment = "Prisoner" - registered_name = "Scum" - var/goal = 0 //How far from freedom? - var/points = 0 - -/obj/item/card/id/prisoner/attack_self(mob/user as mob) - to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") - -/obj/item/card/id/prisoner/one - name = "Prisoner #13-001" - registered_name = "Prisoner #13-001" - -/obj/item/card/id/prisoner/two - name = "Prisoner #13-002" - registered_name = "Prisoner #13-002" - -/obj/item/card/id/prisoner/three - name = "Prisoner #13-003" - registered_name = "Prisoner #13-003" - -/obj/item/card/id/prisoner/four - name = "Prisoner #13-004" - registered_name = "Prisoner #13-004" - -/obj/item/card/id/prisoner/five - name = "Prisoner #13-005" - registered_name = "Prisoner #13-005" - -/obj/item/card/id/prisoner/six - name = "Prisoner #13-006" - registered_name = "Prisoner #13-006" - -/obj/item/card/id/prisoner/seven - name = "Prisoner #13-007" - registered_name = "Prisoner #13-007" - -/obj/item/card/id/prisoner/random -/obj/item/card/id/prisoner/random/New() - ..() - var/random_number = "#[rand(0, 99)]-[rand(0, 999)]" - name = "Prisoner [random_number]" - registered_name = name - -/obj/item/card/id/salvage_captain - name = "Captain's ID" - registered_name = "Captain" - icon_state = "centcom" - desc = "Finders, keepers." - access = list(ACCESS_SALVAGE_CAPTAIN) - -/obj/item/card/id/medical - name = "Medical ID" - registered_name = "Medic" - icon_state = "medical" - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/security - name = "Security ID" - registered_name = "Officer" - icon_state = "security" - access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS) - -/obj/item/card/id/research - name = "Research ID" - registered_name = "Scientist" - icon_state = "research" - access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_XENOARCH, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/supply - name = "Supply ID" - registered_name = "Cargonian" - icon_state = "cargo" - access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINT, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/engineering - name = "Engineering ID" - registered_name = "Engineer" - icon_state = "engineering" - access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS) - -/obj/item/card/id/hos - name = "Head of Security ID" - registered_name = "HoS" - icon_state = "HoS" - access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, - ACCESS_FORENSICS_LOCKERS, ACCESS_PILOT, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS, - ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING, - ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_WEAPONS) - -/obj/item/card/id/cmo - name = "Chief Medical Officer ID" - registered_name = "CMO" - icon_state = "CMO" - access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_HEADS, - ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, - ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/rd - name = "Research Director ID" - registered_name = "RD" - icon_state = "RD" - access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE, - ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS, - ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD, - ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_MINISAT, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/ce - name = "Chief Engineer ID" - registered_name = "CE" - icon_state = "CE" - access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, - ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, - ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, - ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/clown - name = "Pink ID" - registered_name = "HONK!" - icon_state = "clown" - desc = "Even looking at the card strikes you with deep fear." - access = list(ACCESS_CLOWN, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS) - -/obj/item/card/id/mime - name = "Black and White ID" - registered_name = "..." - icon_state = "mime" - desc = "..." - access = list(ACCESS_MIME, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS) - -/obj/item/card/id/rainbow - name = "Rainbow ID" - icon_state = "rainbow" - -/obj/item/card/id/thunderdome/red - name = "Thunderdome Red ID" - registered_name = "Red Team Fighter" - assignment = "Red Team Fighter" - icon_state = "TDred" - desc = "This ID card is given to those who fought inside the thunderdome for the Red Team. Not many have lived to see one of those, even fewer lived to keep it." - -/obj/item/card/id/thunderdome/green - name = "Thunderdome Green ID" - registered_name = "Green Team Fighter" - assignment = "Green Team Fighter" - icon_state = "TDgreen" - desc = "This ID card is given to those who fought inside the thunderdome for the Green Team. Not many have lived to see one of those, even fewer lived to keep it." - -/obj/item/card/id/lifetime - name = "Lifetime ID Card" - desc = "A modified ID card given only to those people who have devoted their lives to the better interests of Nanotrasen. It sparkles blue." - icon_state = "lifetimeid" - -/obj/item/card/id/ert - name = "ERT ID" - icon_state = "ERT_empty" - -/obj/item/card/id/ert/commander - icon_state = "ERT_leader" -/obj/item/card/id/ert/security - icon_state = "ERT_security" -/obj/item/card/id/ert/engineering - icon_state = "ERT_engineering" -/obj/item/card/id/ert/medic - icon_state = "ERT_medical" - -/obj/item/card/id/golem - name = "Free Golem ID" - desc = "A card used to claim mining points and buy gear. Use it to mark it as yours." - icon_state = "research" - access = list(ACCESS_FREE_GOLEMS, ACCESS_ROBOTICS, ACCESS_CLOWN, ACCESS_MIME) //access to robots/mechs - var/registered = FALSE - -/obj/item/card/id/golem/attack_self(mob/user as mob) - if(!registered && ishuman(user)) - registered_name = user.real_name - SetOwnerInfo(user) - assignment = "Free Golem" - RebuildHTML() - UpdateName() - desc = "A card used to claim mining points and buy gear." - registered = TRUE - to_chat(user, "The ID is now registered as yours.") - else - ..() - -// Decals -/obj/item/id_decal - name = "identification card decal" - desc = "A nano-cellophane wrap that molds to an ID card to make it look snazzy." - icon = 'icons/obj/toy.dmi' - icon_state = "id_decal" - var/decal_name = "identification card" - var/decal_desc = "A card used to provide ID and determine access across the station." - var/decal_icon_state = "id" - var/decal_item_state = "card-id" - var/override_name = 0 - -/obj/item/id_decal/gold - name = "gold ID card decal" - icon_state = "id_decal_gold" - desc = "Make your ID look like the Captain's or a self-centered HOP's. Applies to any ID." - decal_desc = "A golden card which shows power and might." - decal_icon_state = "gold" - decal_item_state = "gold_id" - -/obj/item/id_decal/silver - name = "silver ID card decal" - icon_state = "id_decal_silver" - desc = "Make your ID look like HOP's because they wouldn't change it officially. Applies to any ID." - decal_desc = "A silver card which shows honour and dedication." - decal_icon_state = "silver" - decal_item_state = "silver_id" - -/obj/item/id_decal/prisoner - name = "prisoner ID card decal" - icon_state = "id_decal_prisoner" - desc = "All the cool kids have an ID that's this color. Applies to any ID." - decal_desc = "You are a number, you are not a free man." - decal_icon_state = "prisoner" - decal_item_state = "orange-id" - -/obj/item/id_decal/centcom - name = "centcom ID card decal" - icon_state = "id_decal_centcom" - desc = "All the prestige without the responsibility or the access. Applies to any ID." - decal_desc = "An ID straight from Cent. Com." - decal_icon_state = "centcom" - -/obj/item/id_decal/emag - name = "cryptographic sequencer ID card decal" - icon_state = "id_decal_emag" - desc = "A bundle of wires that you can tape to your ID to look very suspect. Applies to any ID." - decal_name = "cryptographic sequencer" - decal_desc = "It's a card with a magnetic strip attached to some circuitry." - decal_icon_state = "emag" - override_name = 1 - -/proc/get_station_card_skins() - return list("data","id","gold","silver","security","medical","research","cargo","engineering","HoS","CMO","RD","CE","clown","mime","rainbow","prisoner") - -/proc/get_centcom_card_skins() - return list("centcom","centcom_old","nanotrasen","ERT_leader","ERT_empty","ERT_security","ERT_engineering","ERT_medical","ERT_janitorial","deathsquad","commander","syndie","TDred","TDgreen") - -/proc/get_all_card_skins() - return get_station_card_skins() + get_centcom_card_skins() - -/proc/get_skin_desc(skin) - switch(skin) - if("id") - return "Standard" - if("cargo") - return "Supply" - if("HoS") - return "Head of Security" - if("CMO") - return "Chief Medical Officer" - if("RD") - return "Research Director" - if("CE") - return "Chief Engineer" - if("centcom_old") - return "Centcom Old" - if("ERT_leader") - return "ERT Leader" - if("ERT_empty") - return "ERT Default" - if("ERT_security") - return "ERT Security" - if("ERT_engineering") - return "ERT Engineering" - if("ERT_medical") - return "ERT Medical" - if("ERT_janitorial") - return "ERT Janitorial" - if("syndie") - return "Syndicate" - if("TDred") - return "Thunderdome Red" - if("TDgreen") - return "Thunderdome Green" - else - return capitalize(skin) +/* Cards + * Contains: + * DATA CARD + * ID CARD + * FINGERPRINT CARD HOLDER + * FINGERPRINT CARD + */ + + + +/* + * DATA CARDS - Used for the teleporter + */ +/obj/item/card + name = "card" + desc = "A card." + icon = 'icons/obj/card.dmi' + w_class = WEIGHT_CLASS_TINY + var/associated_account_number = 0 + + var/list/files = list( ) + +/obj/item/card/data + name = "data card" + desc = "A disk containing data." + icon_state = "data" + var/function = "storage" + var/data = "null" + var/special = null + item_state = "card-id" + +/obj/item/card/data/verb/label(t as text) + set name = "Label Disk" + set category = "Object" + set src in usr + + if(t) + src.name = text("Data Disk- '[]'", t) + else + src.name = "Data Disk" + src.add_fingerprint(usr) + return + +/obj/item/card/data/clown + name = "coordinates to clown planet" + icon_state = "data" + item_state = "card-id" + layer = 3 + level = 2 + desc = "This card contains coordinates to the fabled Clown Planet. Handle with care." + function = "teleporter" + data = "Clown Land" + +/* + * ID CARDS + */ + +/obj/item/card/emag_broken + desc = "It's a card with a magnetic strip attached to some circuitry. It looks too busted to be used for anything but salvage." + name = "broken cryptographic sequencer" + icon_state = "emag" + item_state = "card-id" + origin_tech = "magnets=2;syndicate=2" + +/obj/item/card/emag + desc = "It's a card with a magnetic strip attached to some circuitry." + name = "cryptographic sequencer" + icon_state = "emag" + item_state = "card-id" + origin_tech = "magnets=2;syndicate=2" + flags = NOBLUDGEON + flags_2 = NO_MAT_REDEMPTION_2 + +/obj/item/card/emag/attack() + return + +/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) + var/atom/A = target + if(!proximity) + return + A.emag_act(user) + +/obj/item/card/id + name = "identification card" + desc = "A card used to provide ID and determine access across the station." + icon_state = "id" + item_state = "card-id" + var/mining_points = 0 //For redeeming at mining equipment lockers + var/list/access = list() + var/registered_name = "Unknown" // The name registered_name on the card + slot_flags = SLOT_ID + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + var/untrackable // Can not be tracked by AI's + + var/blood_type = "\[UNSET\]" + var/dna_hash = "\[UNSET\]" + var/fingerprint_hash = "\[UNSET\]" + + //alt titles are handled a bit weirdly in order to unobtrusively integrate into existing ID system + var/assignment = null //can be alt title or the actual job + var/rank = null //actual job + var/owner_uid + var/owner_ckey + var/dorm = 0 // determines if this ID has claimed a dorm already + + var/sex + var/age + var/photo + var/dat + var/stamped = 0 + + var/obj/item/card/id/guest/guest_pass = null // Guest pass attached to the ID + +/obj/item/card/id/New() + ..() + spawn(30) + if(ishuman(loc) && blood_type == "\[UNSET\]") + var/mob/living/carbon/human/H = loc + SetOwnerInfo(H) + +/obj/item/card/id/examine(mob/user) + . = ..() + if(in_range(user, src)) + show(usr) + else + . += "It is too far away." + if(guest_pass) + . += "There is a guest pass attached to this ID card" + if(world.time < guest_pass.expiration_time) + . += "It expires at [station_time_timestamp("hh:mm:ss", guest_pass.expiration_time)]." + else + . += "It expired at [station_time_timestamp("hh:mm:ss", guest_pass.expiration_time)]." + . += "It grants access to following areas:" + for(var/A in guest_pass.temp_access) + . += "[get_access_desc(A)]." + . += "Issuing reason: [guest_pass.reason]." + +/obj/item/card/id/proc/show(mob/user as mob) + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/paper) + assets.send(user) + + var/datum/browser/popup = new(user, "idcard", name, 600, 400) + popup.set_content(dat) + popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/item/card/id/attack_self(mob/user as mob) + user.visible_message("[user] shows you: [bicon(src)] [src.name]. The assignment on the card: [src.assignment]",\ + "You flash your ID card: [bicon(src)] [src.name]. The assignment on the card: [src.assignment]") + if(mining_points) + to_chat(user, "There's [mining_points] mining equipment redemption points loaded onto this card.") + src.add_fingerprint(user) + return + +/obj/item/card/id/proc/UpdateName() + name = "[src.registered_name]'s ID Card ([src.assignment])" + +/obj/item/card/id/proc/SetOwnerInfo(var/mob/living/carbon/human/H) + if(!H || !H.dna) + return + + sex = capitalize(H.gender) + age = H.age + blood_type = H.dna.blood_type + dna_hash = H.dna.unique_enzymes + fingerprint_hash = md5(H.dna.uni_identity) + + RebuildHTML() + +/obj/item/card/id/proc/RebuildHTML() + var/photo_front = "'data:image/png;base64,[icon2base64(icon(photo, dir = SOUTH))]'" + var/photo_side = "'data:image/png;base64,[icon2base64(icon(photo, dir = WEST))]'" + + dat = {"
        + Name: [registered_name]
        + Sex: [sex]
        + Age: [age]
        + Rank: [assignment]
        + Fingerprint: [fingerprint_hash]
        + Blood Type: [blood_type]
        + DNA Hash: [dna_hash]

        +
        Photo:
        +
        "} + +/obj/item/card/id/GetAccess() + if(!guest_pass) + return access + return access | guest_pass.GetAccess() + +/obj/item/card/id/GetID() + return src + +/obj/item/card/id/proc/getRankAndAssignment() + var/jobnamedata = "" + if(rank) + jobnamedata += rank + if(rank != assignment) + jobnamedata += " (" + assignment + ")" + return jobnamedata + +/obj/item/card/id/proc/getPlayer() + if(owner_uid) + var/mob/living/carbon/human/H = locateUID(owner_uid) + if(istype(H) && H.ckey == owner_ckey) + return H + owner_uid = null + if(owner_ckey) + for(var/mob/M in GLOB.player_list) + if(M.ckey && M.ckey == owner_ckey) + owner_uid = M.UID() + return M + owner_ckey = null + +/obj/item/card/id/proc/is_untrackable() + return untrackable + +/obj/item/card/id/proc/update_label(newname, newjob) + if(newname || newjob) + name = "[(!newname) ? "identification card" : "[newname]'s ID Card"][(!newjob) ? "" : " ([newjob])"]" + return + + name = "[(!registered_name) ? "identification card" : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" + +/obj/item/card/id/attackby(obj/item/W as obj, mob/user as mob, params) + ..() + + if(istype(W, /obj/item/id_decal/)) + var/obj/item/id_decal/decal = W + to_chat(user, "You apply [decal] to [src].") + if(decal.override_name) + name = decal.decal_name + desc = decal.decal_desc + icon_state = decal.decal_icon_state + item_state = decal.decal_item_state + qdel(decal) + qdel(W) + return + + else if(istype (W,/obj/item/stamp)) + if(!stamped) + dat+="" + stamped = 1 + to_chat(user, "You stamp the ID card!") + else + to_chat(user, "This ID has already been stamped!") + + else if(istype(W, /obj/item/card/id/guest)) + if(istype(src, /obj/item/card/id/guest)) + return + var/obj/item/card/id/guest/G = W + if(world.time > G.expiration_time) + to_chat(user, "There's no point, the guest pass has expired.") + return + if(guest_pass) + to_chat(user, "There's already a guest pass attached to this ID.") + return + if(G.registered_name != registered_name && G.registered_name != "NOT SPECIFIED") + to_chat(user, "The guest pass cannot be attached to this ID") + return + if(!user.unEquip(G)) + return + G.loc = src + guest_pass = G + +/obj/item/card/id/verb/remove_guest_pass() + set name = "Remove Guest Pass" + set category = "Object" + set src in range(0) + + if(usr.stat || !usr.canmove || usr.restrained()) + return + + if(guest_pass) + to_chat(usr, "You remove the guest pass from this ID.") + guest_pass.forceMove(get_turf(src)) + guest_pass = null + else + to_chat(usr, "There is no guest pass attached to this ID") + +/obj/item/card/id/serialize() + var/list/data = ..() + + data["sex"] = sex + data["age"] = age + data["btype"] = blood_type + data["dna_hash"] = dna_hash + data["fprint_hash"] = fingerprint_hash + data["access"] = access + data["job"] = assignment + data["account"] = associated_account_number + data["owner"] = registered_name + data["mining"] = mining_points + return data + +/obj/item/card/id/deserialize(list/data) + sex = data["sex"] + age = data["age"] + blood_type = data["btype"] + dna_hash = data["dna_hash"] + fingerprint_hash = data["fprint_hash"] + access = data["access"] // No need for a copy, the list isn't getting touched + assignment = data["job"] + associated_account_number = data["account"] + registered_name = data["owner"] + mining_points = data["mining"] + // We'd need to use icon serialization(b64) to save the photo, and I don't feel like i + UpdateName() + RebuildHTML() + ..() + +/obj/item/card/id/silver + name = "identification card" + desc = "A silver card which shows honour and dedication." + icon_state = "silver" + item_state = "silver_id" + +/obj/item/card/id/gold + name = "identification card" + desc = "A golden card which shows power and might." + icon_state = "gold" + item_state = "gold_id" + +/obj/item/card/id/syndicate + name = "agent card" + var/list/initial_access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_EXTERNAL_AIRLOCKS) + origin_tech = "syndicate=1" + var/registered_user = null + untrackable = 1 + var/anyone = FALSE //Can anyone forge the ID or just syndicate? + +/obj/item/card/id/syndicate/anyone + anyone = TRUE + +/obj/item/card/id/syndicate/New() + access = initial_access.Copy() + ..() + +/obj/item/card/id/syndicate/vox + name = "agent card" + initial_access = list(ACCESS_MAINT_TUNNELS, ACCESS_VOX, ACCESS_EXTERNAL_AIRLOCKS) + +/obj/item/card/id/syndicate/command + initial_access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_SYNDICATE_COMMAND, ACCESS_EXTERNAL_AIRLOCKS) + icon_state = "commander" + +/obj/item/card/id/syndicate/afterattack(var/obj/item/O as obj, mob/user as mob, proximity) + if(!proximity) + return + if(istype(O, /obj/item/card/id)) + var/obj/item/card/id/I = O + if(istype(user, /mob/living) && user.mind) + if(user.mind.special_role || anyone) + to_chat(usr, "The card's microscanners activate as you pass it over \the [I], copying its access.") + src.access |= I.access //Don't copy access if user isn't an antag -- to prevent metagaming + +/obj/item/card/id/syndicate/attack_self(mob/user as mob) + if(!src.registered_name) + var t = reject_bad_name(input(user, "What name would you like to use on this card?", "Agent Card name", ishuman(user) ? user.real_name : user.name)) + if(!t) + to_chat(user, "Invalid name.") + return + src.registered_name = t + + var u = sanitize(stripped_input(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels other than maintenance.", "Agent Card Job Assignment", "Agent", MAX_MESSAGE_LEN)) + if(!u) + to_chat(user, "Invalid assignment.") + src.registered_name = "" + return + src.assignment = u + src.name = "[src.registered_name]'s ID Card ([src.assignment])" + to_chat(user, "You successfully forge the ID card.") + registered_user = user + else if(!registered_user || registered_user == user) + if(!registered_user) + registered_user = user + + switch(alert(user,"Would you like to display \the [src] or edit it?","Choose","Show","Edit")) + if("Show") + return ..() + if("Edit") + switch(input(user,"What would you like to edit on \the [src]?") in list("Name", "Photo", "Appearance", "Sex", "Age", "Occupation", "Money Account", "Blood Type", "DNA Hash", "Fingerprint Hash", "Reset Access", "Delete Card Information")) + if("Name") + var/new_name = reject_bad_name(input(user,"What name would you like to put on this card?","Agent Card Name", ishuman(user) ? user.real_name : user.name)) + if(!Adjacent(user)) + return + src.registered_name = new_name + UpdateName() + to_chat(user, "Name changed to [new_name].") + RebuildHTML() + + if("Photo") + if(!Adjacent(user)) + return + var/job_clothes = null + if(assignment) + job_clothes = assignment + var/icon/newphoto = get_id_photo(user, job_clothes) + if(!newphoto) + return + photo = newphoto + to_chat(user, "Photo changed. Select another occupation and take a new photo if you wish to appear with different clothes.") + RebuildHTML() + + if("Appearance") + var/list/appearances = list( + "data", + "id", + "gold", + "silver", + "centcom", + "centcom_old", + "security", + "medical", + "HoS", + "research", + "cargo", + "engineering", + "CMO", + "RD", + "CE", + "clown", + "mime", + "rainbow", + "prisoner", + "syndie", + "deathsquad", + "commander", + "ERT_leader", + "ERT_security", + "ERT_engineering", + "ERT_medical", + "ERT_janitorial", + ) + var/choice = input(user, "Select the appearance for this card.", "Agent Card Appearance") in appearances + if(!Adjacent(user)) + return + if(!choice) + return + icon_state = choice + switch(choice) + if("silver") + desc = "A silver card which shows honour and dedication." + if("gold") + desc = "A golden card which shows power and might." + if("clown") + desc = "Even looking at the card strikes you with deep fear." + if("mime") + desc = "..." + if("prisoner") + desc = "You are a number, you are not a free man." + if("centcom") + desc = "An ID straight from Central Command." + else + desc = "A card used to provide ID and determine access across the station." + to_chat(usr, "Appearance changed to [choice].") + + if("Sex") + var/new_sex = sanitize(stripped_input(user,"What sex would you like to put on this card?","Agent Card Sex", ishuman(user) ? capitalize(user.gender) : "Male", MAX_MESSAGE_LEN)) + if(!Adjacent(user)) + return + sex = new_sex + to_chat(user, "Sex changed to [new_sex].") + RebuildHTML() + + if("Age") + var/default = "21" + if(ishuman(user)) + var/mob/living/carbon/human/H = user + default = H.age + var/new_age = sanitize(input(user,"What age would you like to be written on this card?","Agent Card Age", default) as text) + if(!Adjacent(user)) + return + age = new_age + to_chat(user, "Age changed to [new_age].") + RebuildHTML() + + if("Occupation") + var/list/departments =list( + "Civilian", + "Engineering", + "Medical", + "Science", + "Security", + "Support", + "Command", + "Custom", + ) + + var/department = input(user, "What job would you like to put on this card?\nChoose a department or a custom job title.\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in departments + var/new_job = "Civilian" + + if(department == "Custom") + new_job = sanitize(stripped_input(user,"Choose a custom job title:","Agent Card Occupation", "Civilian", MAX_MESSAGE_LEN)) + else if(department != "Civilian") + switch(department) + if("Engineering") + new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in engineering_positions + if("Medical") + new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in medical_positions + if("Science") + new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in science_positions + if("Security") + new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in security_positions + if("Support") + new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in support_positions + if("Command") + new_job = input(user, "What job would you like to put on this card?\nChanging occupation will not grant or remove any access levels.","Agent Card Occupation") in command_positions + + if(!Adjacent(user)) + return + assignment = new_job + to_chat(user, "Occupation changed to [new_job].") + UpdateName() + RebuildHTML() + + if("Money Account") + var/new_account = input(user,"What money account would you like to link to this card?","Agent Card Account",12345) as num + if(!Adjacent(user)) + return + associated_account_number = new_account + to_chat(user, "Linked money account changed to [new_account].") + + if("Blood Type") + var/default = "\[UNSET\]" + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna) + default = H.dna.blood_type + + var/new_blood_type = sanitize(input(user,"What blood type would you like to be written on this card?","Agent Card Blood Type",default) as text) + if(!Adjacent(user)) + return + blood_type = new_blood_type + to_chat(user, "Blood type changed to [new_blood_type].") + RebuildHTML() + + if("DNA Hash") + var/default = "\[UNSET\]" + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna) + default = H.dna.unique_enzymes + + var/new_dna_hash = sanitize(input(user,"What DNA hash would you like to be written on this card?","Agent Card DNA Hash",default) as text) + if(!Adjacent(user)) + return + dna_hash = new_dna_hash + to_chat(user, "DNA hash changed to [new_dna_hash].") + RebuildHTML() + + if("Fingerprint Hash") + var/default = "\[UNSET\]" + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna) + default = md5(H.dna.uni_identity) + + var/new_fingerprint_hash = sanitize(input(user,"What fingerprint hash would you like to be written on this card?","Agent Card Fingerprint Hash",default) as text) + if(!Adjacent(user)) + return + fingerprint_hash = new_fingerprint_hash + to_chat(user, "Fingerprint hash changed to [new_fingerprint_hash].") + RebuildHTML() + + if("Reset Access") + var/response = alert(user, "Are you sure you want to reset access saved on the card?","Reset Access", "No", "Yes") + if(response == "Yes") + access = initial_access.Copy() // Initial() doesn't work on lists + to_chat(user, "Card access reset.") + + if("Delete Card Information") + var/response = alert(user, "Are you sure you want to delete all information saved on the card?","Delete Card Information", "No", "Yes") + if(response == "Yes") + name = initial(name) + registered_name = initial(registered_name) + icon_state = initial(icon_state) + sex = initial(sex) + age = initial(age) + assignment = initial(assignment) + associated_account_number = initial(associated_account_number) + blood_type = initial(blood_type) + dna_hash = initial(dna_hash) + fingerprint_hash = initial(fingerprint_hash) + photo = null + registered_user = null + to_chat(user, "All information has been deleted from \the [src].") + RebuildHTML() + else + ..() + +/obj/item/card/id/syndicate_command + name = "syndicate ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + icon_state = "syndie" + assignment = "Syndicate Overlord" + untrackable = 1 + access = list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_SYNDICATE_COMMAND, ACCESS_EXTERNAL_AIRLOCKS) + +/obj/item/card/id/captains_spare + name = "captain's spare ID" + desc = "The spare ID of the captain." + icon_state = "gold" + item_state = "gold_id" + registered_name = "Captain" + assignment = "Captain" + +/obj/item/card/id/captains_spare/New() + var/datum/job/captain/J = new/datum/job/captain + access = J.get_access() + ..() + +/obj/item/card/id/admin + name = "admin ID card" + icon_state = "admin" + item_state = "gold_id" + registered_name = "Admin" + assignment = "Testing Shit" + untrackable = 1 + +/obj/item/card/id/admin/New() + access = get_absolutely_all_accesses() + ..() + +/obj/item/card/id/centcom + name = "central command ID card" + desc = "An ID straight from Central Command." + icon_state = "centcom" + registered_name = "Central Command" + assignment = "General" + +/obj/item/card/id/centcom/New() + access = get_all_centcom_access() + ..() + +/obj/item/card/id/nanotrasen + name = "nanotrasen ID card" + icon_state = "nanotrasen" + +/obj/item/card/id/prisoner + name = "prisoner ID card" + desc = "You are a number, you are not a free man." + icon_state = "prisoner" + item_state = "orange-id" + assignment = "Prisoner" + registered_name = "Scum" + var/goal = 0 //How far from freedom? + var/points = 0 + +/obj/item/card/id/prisoner/attack_self(mob/user as mob) + to_chat(usr, "You have accumulated [points] out of the [goal] points you need for freedom.") + +/obj/item/card/id/prisoner/one + name = "Prisoner #13-001" + registered_name = "Prisoner #13-001" + +/obj/item/card/id/prisoner/two + name = "Prisoner #13-002" + registered_name = "Prisoner #13-002" + +/obj/item/card/id/prisoner/three + name = "Prisoner #13-003" + registered_name = "Prisoner #13-003" + +/obj/item/card/id/prisoner/four + name = "Prisoner #13-004" + registered_name = "Prisoner #13-004" + +/obj/item/card/id/prisoner/five + name = "Prisoner #13-005" + registered_name = "Prisoner #13-005" + +/obj/item/card/id/prisoner/six + name = "Prisoner #13-006" + registered_name = "Prisoner #13-006" + +/obj/item/card/id/prisoner/seven + name = "Prisoner #13-007" + registered_name = "Prisoner #13-007" + +/obj/item/card/id/prisoner/random +/obj/item/card/id/prisoner/random/New() + ..() + var/random_number = "#[rand(0, 99)]-[rand(0, 999)]" + name = "Prisoner [random_number]" + registered_name = name + +/obj/item/card/id/salvage_captain + name = "Captain's ID" + registered_name = "Captain" + icon_state = "centcom" + desc = "Finders, keepers." + access = list(ACCESS_SALVAGE_CAPTAIN) + +/obj/item/card/id/medical + name = "Medical ID" + registered_name = "Medic" + icon_state = "medical" + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/security + name = "Security ID" + registered_name = "Officer" + icon_state = "security" + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS) + +/obj/item/card/id/research + name = "Research ID" + registered_name = "Scientist" + icon_state = "research" + access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_XENOARCH, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/supply + name = "Supply ID" + registered_name = "Cargonian" + icon_state = "cargo" + access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_QM, ACCESS_MINT, ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/engineering + name = "Engineering ID" + registered_name = "Engineer" + icon_state = "engineering" + access = list(ACCESS_EVA, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS) + +/obj/item/card/id/hos + name = "Head of Security ID" + registered_name = "HoS" + icon_state = "HoS" + access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, + ACCESS_FORENSICS_LOCKERS, ACCESS_PILOT, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_RESEARCH, ACCESS_ENGINE, ACCESS_MINING, ACCESS_MEDICAL, ACCESS_CONSTRUCTION, ACCESS_MAILSORTING, + ACCESS_HEADS, ACCESS_HOS, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_GATEWAY, ACCESS_WEAPONS) + +/obj/item/card/id/cmo + name = "Chief Medical Officer ID" + registered_name = "CMO" + icon_state = "CMO" + access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_HEADS, + ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE, + ACCESS_KEYCARD_AUTH, ACCESS_SEC_DOORS, ACCESS_PSYCHIATRIST, ACCESS_PARAMEDIC, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/rd + name = "Research Director ID" + registered_name = "RD" + icon_state = "RD" + access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE, + ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS, + ACCESS_RESEARCH, ACCESS_ROBOTICS, ACCESS_XENOBIOLOGY, ACCESS_AI_UPLOAD, + ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_MINISAT, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/ce + name = "Chief Engineer ID" + registered_name = "CE" + icon_state = "CE" + access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS, + ACCESS_TELEPORTER, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EMERGENCY_STORAGE, ACCESS_EVA, + ACCESS_HEADS, ACCESS_CONSTRUCTION, ACCESS_SEC_DOORS, + ACCESS_CE, ACCESS_RC_ANNOUNCE, ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_MINISAT, ACCESS_MECHANIC, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/clown + name = "Pink ID" + registered_name = "HONK!" + icon_state = "clown" + desc = "Even looking at the card strikes you with deep fear." + access = list(ACCESS_CLOWN, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS) + +/obj/item/card/id/mime + name = "Black and White ID" + registered_name = "..." + icon_state = "mime" + desc = "..." + access = list(ACCESS_MIME, ACCESS_THEATRE, ACCESS_MAINT_TUNNELS) + +/obj/item/card/id/rainbow + name = "Rainbow ID" + icon_state = "rainbow" + +/obj/item/card/id/thunderdome/red + name = "Thunderdome Red ID" + registered_name = "Red Team Fighter" + assignment = "Red Team Fighter" + icon_state = "TDred" + desc = "This ID card is given to those who fought inside the thunderdome for the Red Team. Not many have lived to see one of those, even fewer lived to keep it." + +/obj/item/card/id/thunderdome/green + name = "Thunderdome Green ID" + registered_name = "Green Team Fighter" + assignment = "Green Team Fighter" + icon_state = "TDgreen" + desc = "This ID card is given to those who fought inside the thunderdome for the Green Team. Not many have lived to see one of those, even fewer lived to keep it." + +/obj/item/card/id/lifetime + name = "Lifetime ID Card" + desc = "A modified ID card given only to those people who have devoted their lives to the better interests of Nanotrasen. It sparkles blue." + icon_state = "lifetimeid" + +/obj/item/card/id/ert + name = "ERT ID" + icon_state = "ERT_empty" + +/obj/item/card/id/ert/commander + icon_state = "ERT_leader" +/obj/item/card/id/ert/security + icon_state = "ERT_security" +/obj/item/card/id/ert/engineering + icon_state = "ERT_engineering" +/obj/item/card/id/ert/medic + icon_state = "ERT_medical" + +/obj/item/card/id/golem + name = "Free Golem ID" + desc = "A card used to claim mining points and buy gear. Use it to mark it as yours." + icon_state = "research" + access = list(ACCESS_FREE_GOLEMS, ACCESS_ROBOTICS, ACCESS_CLOWN, ACCESS_MIME) //access to robots/mechs + var/registered = FALSE + +/obj/item/card/id/golem/attack_self(mob/user as mob) + if(!registered && ishuman(user)) + registered_name = user.real_name + SetOwnerInfo(user) + assignment = "Free Golem" + RebuildHTML() + UpdateName() + desc = "A card used to claim mining points and buy gear." + registered = TRUE + to_chat(user, "The ID is now registered as yours.") + else + ..() + +// Decals +/obj/item/id_decal + name = "identification card decal" + desc = "A nano-cellophane wrap that molds to an ID card to make it look snazzy." + icon = 'icons/obj/toy.dmi' + icon_state = "id_decal" + var/decal_name = "identification card" + var/decal_desc = "A card used to provide ID and determine access across the station." + var/decal_icon_state = "id" + var/decal_item_state = "card-id" + var/override_name = 0 + +/obj/item/id_decal/gold + name = "gold ID card decal" + icon_state = "id_decal_gold" + desc = "Make your ID look like the Captain's or a self-centered HOP's. Applies to any ID." + decal_desc = "A golden card which shows power and might." + decal_icon_state = "gold" + decal_item_state = "gold_id" + +/obj/item/id_decal/silver + name = "silver ID card decal" + icon_state = "id_decal_silver" + desc = "Make your ID look like HOP's because they wouldn't change it officially. Applies to any ID." + decal_desc = "A silver card which shows honour and dedication." + decal_icon_state = "silver" + decal_item_state = "silver_id" + +/obj/item/id_decal/prisoner + name = "prisoner ID card decal" + icon_state = "id_decal_prisoner" + desc = "All the cool kids have an ID that's this color. Applies to any ID." + decal_desc = "You are a number, you are not a free man." + decal_icon_state = "prisoner" + decal_item_state = "orange-id" + +/obj/item/id_decal/centcom + name = "centcom ID card decal" + icon_state = "id_decal_centcom" + desc = "All the prestige without the responsibility or the access. Applies to any ID." + decal_desc = "An ID straight from Cent. Com." + decal_icon_state = "centcom" + +/obj/item/id_decal/emag + name = "cryptographic sequencer ID card decal" + icon_state = "id_decal_emag" + desc = "A bundle of wires that you can tape to your ID to look very suspect. Applies to any ID." + decal_name = "cryptographic sequencer" + decal_desc = "It's a card with a magnetic strip attached to some circuitry." + decal_icon_state = "emag" + override_name = 1 + +/proc/get_station_card_skins() + return list("data","id","gold","silver","security","medical","research","cargo","engineering","HoS","CMO","RD","CE","clown","mime","rainbow","prisoner") + +/proc/get_centcom_card_skins() + return list("centcom","centcom_old","nanotrasen","ERT_leader","ERT_empty","ERT_security","ERT_engineering","ERT_medical","ERT_janitorial","deathsquad","commander","syndie","TDred","TDgreen") + +/proc/get_all_card_skins() + return get_station_card_skins() + get_centcom_card_skins() + +/proc/get_skin_desc(skin) + switch(skin) + if("id") + return "Standard" + if("cargo") + return "Supply" + if("HoS") + return "Head of Security" + if("CMO") + return "Chief Medical Officer" + if("RD") + return "Research Director" + if("CE") + return "Chief Engineer" + if("centcom_old") + return "Centcom Old" + if("ERT_leader") + return "ERT Leader" + if("ERT_empty") + return "ERT Default" + if("ERT_security") + return "ERT Security" + if("ERT_engineering") + return "ERT Engineering" + if("ERT_medical") + return "ERT Medical" + if("ERT_janitorial") + return "ERT Janitorial" + if("syndie") + return "Syndicate" + if("TDred") + return "Thunderdome Red" + if("TDgreen") + return "Thunderdome Green" + else + return capitalize(skin) diff --git a/code/game/objects/items/weapons/cash.dm b/code/game/objects/items/weapons/cash.dm index 84d7eab9e0b..23aaeb27cbc 100644 --- a/code/game/objects/items/weapons/cash.dm +++ b/code/game/objects/items/weapons/cash.dm @@ -56,4 +56,4 @@ amount = 1000 /obj/item/stack/spacecash/c1000000 - amount = 1000000 \ No newline at end of file + amount = 1000000 diff --git a/code/game/objects/items/weapons/chrono_eraser.dm b/code/game/objects/items/weapons/chrono_eraser.dm index 7680ef269dd..91bb84f880b 100644 --- a/code/game/objects/items/weapons/chrono_eraser.dm +++ b/code/game/objects/items/weapons/chrono_eraser.dm @@ -261,4 +261,4 @@ #undef CHRONO_BEAM_RANGE -#undef CHRONO_FRAME_COUNT \ No newline at end of file +#undef CHRONO_FRAME_COUNT diff --git a/code/game/objects/items/weapons/cigs.dm b/code/game/objects/items/weapons/cigs.dm index a2b193fc608..24c904bc9d3 100644 --- a/code/game/objects/items/weapons/cigs.dm +++ b/code/game/objects/items/weapons/cigs.dm @@ -426,4 +426,4 @@ LIGHTERS ARE IN LIGHTERS.DM else to_chat(user, "You need to dry this first!") else - ..() \ No newline at end of file + ..() diff --git a/code/game/objects/items/weapons/clown_items.dm b/code/game/objects/items/weapons/clown_items.dm index 1143954faef..f6fa5c26d5a 100644 --- a/code/game/objects/items/weapons/clown_items.dm +++ b/code/game/objects/items/weapons/clown_items.dm @@ -1,58 +1,58 @@ -/* Clown Items - * Contains: - * Banana Peels - * Soap - * Bike Horns - */ - -/* - * Bike Horns - */ - -/obj/item/bikehorn - name = "bike horn" - desc = "A horn off of a bicycle." - icon = 'icons/obj/items.dmi' - icon_state = "bike_horn" - item_state = "bike_horn" - hitsound = null - throwforce = 3 - w_class = WEIGHT_CLASS_TINY - var/list/honk_sounds = list('sound/items/bikehorn.ogg' = 1) - throw_speed = 3 - throw_range = 15 - attack_verb = list("HONKED") - -/obj/item/bikehorn/Initialize() - . = ..() - AddComponent(/datum/component/squeak, honk_sounds, 50) - -/obj/item/bikehorn/airhorn - name = "air horn" - desc = "Damn son, where'd you find this?" - icon_state = "air_horn" - origin_tech = "materials=4;engineering=4" - honk_sounds = list('sound/items/airhorn2.ogg' = 1) - -/obj/item/bikehorn/golden - name = "golden bike horn" - desc = "Golden? Clearly, its made with bananium! Honk!" - icon_state = "gold_horn" - item_state = "gold_horn" - -/obj/item/bikehorn/golden/attack() - flip_mobs() - return ..() - -/obj/item/bikehorn/golden/attack_self(mob/user) - flip_mobs() - ..() - -/obj/item/bikehorn/golden/proc/flip_mobs(mob/living/carbon/M, mob/user) - var/turf/T = get_turf(src) - for(M in ohearers(7, T)) - if(istype(M, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = M - if(!H.can_hear()) - continue - M.emote("flip") +/* Clown Items + * Contains: + * Banana Peels + * Soap + * Bike Horns + */ + +/* + * Bike Horns + */ + +/obj/item/bikehorn + name = "bike horn" + desc = "A horn off of a bicycle." + icon = 'icons/obj/items.dmi' + icon_state = "bike_horn" + item_state = "bike_horn" + hitsound = null + throwforce = 3 + w_class = WEIGHT_CLASS_TINY + var/list/honk_sounds = list('sound/items/bikehorn.ogg' = 1) + throw_speed = 3 + throw_range = 15 + attack_verb = list("HONKED") + +/obj/item/bikehorn/Initialize() + . = ..() + AddComponent(/datum/component/squeak, honk_sounds, 50) + +/obj/item/bikehorn/airhorn + name = "air horn" + desc = "Damn son, where'd you find this?" + icon_state = "air_horn" + origin_tech = "materials=4;engineering=4" + honk_sounds = list('sound/items/airhorn2.ogg' = 1) + +/obj/item/bikehorn/golden + name = "golden bike horn" + desc = "Golden? Clearly, its made with bananium! Honk!" + icon_state = "gold_horn" + item_state = "gold_horn" + +/obj/item/bikehorn/golden/attack() + flip_mobs() + return ..() + +/obj/item/bikehorn/golden/attack_self(mob/user) + flip_mobs() + ..() + +/obj/item/bikehorn/golden/proc/flip_mobs(mob/living/carbon/M, mob/user) + var/turf/T = get_turf(src) + for(M in ohearers(7, T)) + if(istype(M, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = M + if(!H.can_hear()) + continue + M.emote("flip") diff --git a/code/game/objects/items/weapons/cosmetics.dm b/code/game/objects/items/weapons/cosmetics.dm index b7aa9675891..7f9cd2f1663 100644 --- a/code/game/objects/items/weapons/cosmetics.dm +++ b/code/game/objects/items/weapons/cosmetics.dm @@ -1,180 +1,180 @@ -/obj/item/lipstick - name = "red lipstick" - desc = "A generic brand of lipstick." - icon = 'icons/obj/items.dmi' - icon_state = "lipstick" - w_class = WEIGHT_CLASS_TINY - var/colour = "red" - var/open = 0 - var/list/lipstick_colors = list( - "purple" = "purple", - "jade" = "#216F43", - "lime" = "lime", - "black" = "black", - "green" = "green", - "blue" = "blue", - "white" = "white") - - -/obj/item/lipstick/purple - name = "purple lipstick" - colour = "purple" - -/obj/item/lipstick/jade - name = "jade lipstick" - colour = "#216F43" - -/obj/item/lipstick/lime - name = "lime lipstick" - colour = "lime" - -/obj/item/lipstick/black - name = "black lipstick" - colour = "black" - -/obj/item/lipstick/green - name = "green lipstick" - colour = "green" - -/obj/item/lipstick/blue - name = "blue lipstick" - colour = "blue" - -/obj/item/lipstick/white - name = "white lipstick" - colour = "white" - -/obj/item/lipstick/random - name = "lipstick" - -/obj/item/lipstick/random/New() - ..() - var/lscolor = pick(lipstick_colors)//A random color is picked from the var defined initially in a new var. - colour = lipstick_colors[lscolor]//The color of the lipstick is pulled from the new variable (right hand side, HTML & Hex RGB) - name = "[lscolor] lipstick"//The new variable is also used to match the name to the color of the lipstick. Kudos to Desolate & Lemon - - -/obj/item/lipstick/attack_self(mob/user as mob) - overlays.Cut() - to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") - open = !open - if(open) - var/image/colored = image("icon"='icons/obj/items.dmi', "icon_state"="lipstick_uncap_color") - colored.color = colour - icon_state = "lipstick_uncap" - overlays += colored - else - icon_state = "lipstick" - -/obj/item/lipstick/attack(mob/M as mob, mob/user as mob) - if(!open) return - - if(!istype(M, /mob)) return - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.lip_style) //if they already have lipstick on - to_chat(user, "You need to wipe off the old lipstick first!") - return - if(H == user) - user.visible_message("[user] does [user.p_their()] lips with [src].", \ - "You take a moment to apply [src]. Perfect!") - H.lip_style = "lipstick" - H.lip_color = colour - H.update_body() - else - user.visible_message("[user] begins to do [H]'s lips with \the [src].", \ - "You begin to apply \the [src].") - if(do_after(user, 20, target = H)) - user.visible_message("[user] does [H]'s lips with \the [src].", \ - "You apply \the [src].") - H.lip_style = "lipstick" - H.lip_color = colour - H.update_body() - else - to_chat(user, "Where are the lips on that?") - -/obj/item/razor - name = "electric razor" - desc = "The latest and greatest power razor born from the science of shaving." - icon = 'icons/obj/items.dmi' - icon_state = "razor" - flags = CONDUCT - w_class = WEIGHT_CLASS_TINY - usesound = 'sound/items/welder2.ogg' - toolspeed = 1 - -/obj/item/razor/attack(mob/living/carbon/M as mob, mob/user as mob) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/head/C = H.get_organ("head") - var/datum/robolimb/robohead = all_robolimbs[C.model] - if(user.zone_selected == "mouth") - if(!get_location_accessible(H, "mouth")) - to_chat(user, "The mask is in the way.") - return - if((C.dna.species.bodyflags & ALL_RPARTS) && robohead.is_monitor) //If the target is of a species that can have prosthetic heads, but the head doesn't support human hair 'wigs'... - to_chat(user, "You find yourself disappointed at the appalling lack of facial hair.") - return - if(C.f_style == "Shaved") - to_chat(user, "Already clean-shaven.") - return - if(H == user) //shaving yourself - user.visible_message("[user] starts to shave [user.p_their()] facial hair with [src].", \ - "You take a moment shave your facial hair with \the [src].") - if(do_after(user, 50 * toolspeed, target = H)) - user.visible_message("[user] shaves [user.p_their()] facial hair clean with [src].", \ - "You finish shaving with [src]. Fast and clean!") - C.f_style = "Shaved" - H.update_fhair() - playsound(src.loc, usesound, 20, 1) - else - var/turf/user_loc = user.loc - var/turf/H_loc = H.loc - user.visible_message("[user] tries to shave [H]'s facial hair with \the [src].", \ - "You start shaving [H]'s facial hair.") - if(do_after(user, 50 * toolspeed, target = H)) - if(user_loc == user.loc && H_loc == H.loc) - user.visible_message("[user] shaves off [H]'s facial hair with \the [src].", \ - "You shave [H]'s facial hair clean off.") - C.f_style = "Shaved" - H.update_fhair() - playsound(src.loc, usesound, 20, 1) - if(user.zone_selected == "head") - if(!get_location_accessible(H, "head")) - to_chat(user, "The headgear is in the way.") - return - if((C.dna.species.bodyflags & ALL_RPARTS) && robohead.is_monitor) //If the target is of a species that can have prosthetic heads, but the head doesn't support human hair 'wigs'... - to_chat(user, "You find yourself disappointed at the appalling lack of hair.") - return - if(C.h_style == "Bald" || C.h_style == "Balding Hair" || C.h_style == "Skinhead") - to_chat(user, "There is not enough hair left to shave...") - return - if(isskrell(M)) - to_chat(user, "Your razor isn't going to cut through tentacles.") - return - if(H == user) //shaving yourself - user.visible_message("[user] starts to shave [user.p_their()] head with [src].", \ - "You start to shave your head with \the [src].") - if(do_after(user, 50 * toolspeed, target = H)) - user.visible_message("[user] shaves [user.p_their()] head with [src].", \ - "You finish shaving with \the [src].") - C.h_style = "Skinhead" - H.update_hair() - playsound(src.loc, usesound, 40, 1) - else - var/turf/user_loc = user.loc - var/turf/H_loc = H.loc - user.visible_message("[user] tries to shave [H]'s head with \the [src]!", \ - "You start shaving [H]'s head.") - if(do_after(user, 50 * toolspeed, target = H)) - if(user_loc == user.loc && H_loc == H.loc) - user.visible_message("[user] shaves [H]'s head bald with \the [src]!", \ - "You shave [H]'s head bald.") - C.h_style = "Skinhead" - H.update_hair() - playsound(src.loc, usesound, 40, 1) - else - ..() - else - ..() +/obj/item/lipstick + name = "red lipstick" + desc = "A generic brand of lipstick." + icon = 'icons/obj/items.dmi' + icon_state = "lipstick" + w_class = WEIGHT_CLASS_TINY + var/colour = "red" + var/open = 0 + var/list/lipstick_colors = list( + "purple" = "purple", + "jade" = "#216F43", + "lime" = "lime", + "black" = "black", + "green" = "green", + "blue" = "blue", + "white" = "white") + + +/obj/item/lipstick/purple + name = "purple lipstick" + colour = "purple" + +/obj/item/lipstick/jade + name = "jade lipstick" + colour = "#216F43" + +/obj/item/lipstick/lime + name = "lime lipstick" + colour = "lime" + +/obj/item/lipstick/black + name = "black lipstick" + colour = "black" + +/obj/item/lipstick/green + name = "green lipstick" + colour = "green" + +/obj/item/lipstick/blue + name = "blue lipstick" + colour = "blue" + +/obj/item/lipstick/white + name = "white lipstick" + colour = "white" + +/obj/item/lipstick/random + name = "lipstick" + +/obj/item/lipstick/random/New() + ..() + var/lscolor = pick(lipstick_colors)//A random color is picked from the var defined initially in a new var. + colour = lipstick_colors[lscolor]//The color of the lipstick is pulled from the new variable (right hand side, HTML & Hex RGB) + name = "[lscolor] lipstick"//The new variable is also used to match the name to the color of the lipstick. Kudos to Desolate & Lemon + + +/obj/item/lipstick/attack_self(mob/user as mob) + overlays.Cut() + to_chat(user, "You twist \the [src] [open ? "closed" : "open"].") + open = !open + if(open) + var/image/colored = image("icon"='icons/obj/items.dmi', "icon_state"="lipstick_uncap_color") + colored.color = colour + icon_state = "lipstick_uncap" + overlays += colored + else + icon_state = "lipstick" + +/obj/item/lipstick/attack(mob/M as mob, mob/user as mob) + if(!open) return + + if(!istype(M, /mob)) return + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.lip_style) //if they already have lipstick on + to_chat(user, "You need to wipe off the old lipstick first!") + return + if(H == user) + user.visible_message("[user] does [user.p_their()] lips with [src].", \ + "You take a moment to apply [src]. Perfect!") + H.lip_style = "lipstick" + H.lip_color = colour + H.update_body() + else + user.visible_message("[user] begins to do [H]'s lips with \the [src].", \ + "You begin to apply \the [src].") + if(do_after(user, 20, target = H)) + user.visible_message("[user] does [H]'s lips with \the [src].", \ + "You apply \the [src].") + H.lip_style = "lipstick" + H.lip_color = colour + H.update_body() + else + to_chat(user, "Where are the lips on that?") + +/obj/item/razor + name = "electric razor" + desc = "The latest and greatest power razor born from the science of shaving." + icon = 'icons/obj/items.dmi' + icon_state = "razor" + flags = CONDUCT + w_class = WEIGHT_CLASS_TINY + usesound = 'sound/items/welder2.ogg' + toolspeed = 1 + +/obj/item/razor/attack(mob/living/carbon/M as mob, mob/user as mob) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/head/C = H.get_organ("head") + var/datum/robolimb/robohead = all_robolimbs[C.model] + if(user.zone_selected == "mouth") + if(!get_location_accessible(H, "mouth")) + to_chat(user, "The mask is in the way.") + return + if((C.dna.species.bodyflags & ALL_RPARTS) && robohead.is_monitor) //If the target is of a species that can have prosthetic heads, but the head doesn't support human hair 'wigs'... + to_chat(user, "You find yourself disappointed at the appalling lack of facial hair.") + return + if(C.f_style == "Shaved") + to_chat(user, "Already clean-shaven.") + return + if(H == user) //shaving yourself + user.visible_message("[user] starts to shave [user.p_their()] facial hair with [src].", \ + "You take a moment shave your facial hair with \the [src].") + if(do_after(user, 50 * toolspeed, target = H)) + user.visible_message("[user] shaves [user.p_their()] facial hair clean with [src].", \ + "You finish shaving with [src]. Fast and clean!") + C.f_style = "Shaved" + H.update_fhair() + playsound(src.loc, usesound, 20, 1) + else + var/turf/user_loc = user.loc + var/turf/H_loc = H.loc + user.visible_message("[user] tries to shave [H]'s facial hair with \the [src].", \ + "You start shaving [H]'s facial hair.") + if(do_after(user, 50 * toolspeed, target = H)) + if(user_loc == user.loc && H_loc == H.loc) + user.visible_message("[user] shaves off [H]'s facial hair with \the [src].", \ + "You shave [H]'s facial hair clean off.") + C.f_style = "Shaved" + H.update_fhair() + playsound(src.loc, usesound, 20, 1) + if(user.zone_selected == "head") + if(!get_location_accessible(H, "head")) + to_chat(user, "The headgear is in the way.") + return + if((C.dna.species.bodyflags & ALL_RPARTS) && robohead.is_monitor) //If the target is of a species that can have prosthetic heads, but the head doesn't support human hair 'wigs'... + to_chat(user, "You find yourself disappointed at the appalling lack of hair.") + return + if(C.h_style == "Bald" || C.h_style == "Balding Hair" || C.h_style == "Skinhead") + to_chat(user, "There is not enough hair left to shave...") + return + if(isskrell(M)) + to_chat(user, "Your razor isn't going to cut through tentacles.") + return + if(H == user) //shaving yourself + user.visible_message("[user] starts to shave [user.p_their()] head with [src].", \ + "You start to shave your head with \the [src].") + if(do_after(user, 50 * toolspeed, target = H)) + user.visible_message("[user] shaves [user.p_their()] head with [src].", \ + "You finish shaving with \the [src].") + C.h_style = "Skinhead" + H.update_hair() + playsound(src.loc, usesound, 40, 1) + else + var/turf/user_loc = user.loc + var/turf/H_loc = H.loc + user.visible_message("[user] tries to shave [H]'s head with \the [src]!", \ + "You start shaving [H]'s head.") + if(do_after(user, 50 * toolspeed, target = H)) + if(user_loc == user.loc && H_loc == H.loc) + user.visible_message("[user] shaves [H]'s head bald with \the [src]!", \ + "You shave [H]'s head bald.") + C.h_style = "Skinhead" + H.update_hair() + playsound(src.loc, usesound, 40, 1) + else + ..() + else + ..() diff --git a/code/game/objects/items/weapons/dice.dm b/code/game/objects/items/weapons/dice.dm index c480d8f450c..82b885c03d1 100644 --- a/code/game/objects/items/weapons/dice.dm +++ b/code/game/objects/items/weapons/dice.dm @@ -1,213 +1,213 @@ -/obj/item/storage/pill_bottle/dice - name = "bag of dice" - desc = "Contains all the luck you'll ever need." - icon = 'icons/obj/dice.dmi' - icon_state = "dicebag" - can_hold = list(/obj/item/dice) - allow_wrap = FALSE - -/obj/item/storage/pill_bottle/dice/New() - ..() - var/special_die = pick("1","2","fudge","00","100") - if(special_die == "1") - new /obj/item/dice/d1(src) - if(special_die == "2") - new /obj/item/dice/d2(src) - new /obj/item/dice/d4(src) - new /obj/item/dice/d6(src) - if(special_die == "fudge") - new /obj/item/dice/fudge(src) - new /obj/item/dice/d8(src) - new /obj/item/dice/d10(src) - if(special_die == "00") - new /obj/item/dice/d00(src) - new /obj/item/dice/d12(src) - new /obj/item/dice/d20(src) - if(special_die == "100") - new /obj/item/dice/d100(src) - -/obj/item/storage/pill_bottle/dice/suicide_act(mob/user) - user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/dice //depreciated d6, use /obj/item/dice/d6 if you actually want a d6 - name = "die" - desc = "A die with six sides. Basic and servicable." - icon = 'icons/obj/dice.dmi' - icon_state = "d6" - w_class = WEIGHT_CLASS_TINY - - var/sides = 6 - var/result = null - var/list/special_faces = list() //entries should match up to sides var if used - - var/rigged = DICE_NOT_RIGGED - var/rigged_value - -/obj/item/dice/Initialize(mapload) - . = ..() - if(!result) - result = roll(sides) - update_icon() - -/obj/item/dice/suicide_act(mob/user) - user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") - return (OXYLOSS) - -/obj/item/dice/d1 - name = "d1" - desc = "A die with one side. Deterministic!" - icon_state = "d1" - sides = 1 - -/obj/item/dice/d2 - name = "d2" - desc = "A die with two sides. Coins are undignified!" - icon_state = "d2" - sides = 2 - -/obj/item/dice/d4 - name = "d4" - desc = "A die with four sides. The nerd's caltrop." - icon_state = "d4" - sides = 4 - -/obj/item/dice/d4/Initialize(mapload) - . = ..() - AddComponent(/datum/component/caltrop, 1, 4) //1d4 damage - -/obj/item/dice/d6 - name = "d6" - -/obj/item/dice/fudge - name = "fudge die" - desc = "A die with six sides but only three results. Is this a plus or a minus? Your mind is drawing a blank..." - sides = 3 //shhh - icon_state = "fudge" - special_faces = list("minus","blank","plus") - -/obj/item/dice/d8 - name = "d8" - desc = "A die with eight sides. It feels... lucky." - icon_state = "d8" - sides = 8 - -/obj/item/dice/d10 - name = "d10" - desc = "A die with ten sides. Useful for percentages." - icon_state = "d10" - sides = 10 - -/obj/item/dice/d00 - name = "d00" - desc = "A die with ten sides. Works better for d100 rolls than a golfball." - icon_state = "d00" - sides = 10 - -/obj/item/dice/d12 - name = "d12" - desc = "A die with twelve sides. There's an air of neglect about it." - icon_state = "d12" - sides = 12 - -/obj/item/dice/d20 - name = "d20" - desc = "A die with twenty sides. The prefered die to throw at the GM." - icon_state = "d20" - sides = 20 - -/obj/item/dice/d100 - name = "d100" - desc = "A die with one hundred sides! Probably not fairly weighted..." - icon_state = "d100" - sides = 100 - -/obj/item/dice/d100/update_icon() - return - -/obj/item/dice/d20/e20 - var/triggered = 0 - -/obj/item/dice/attack_self(mob/user as mob) - diceroll(user) - -/obj/item/dice/throw_impact(atom/target) - diceroll(thrownby) - . = ..() - -/obj/item/dice/proc/diceroll(mob/user) - result = roll(sides) - if(rigged != DICE_NOT_RIGGED && result != rigged_value) - if(rigged == DICE_BASICALLY_RIGGED && prob(Clamp(1/(sides - 1) * 100, 25, 80))) - result = rigged_value - else if(rigged == DICE_TOTALLY_RIGGED) - result = rigged_value - - . = result - - var/fake_result = roll(sides)//Daredevil isn't as good as he used to be - var/comment = "" - if(sides == 20 && result == 20) - comment = "NAT 20!" - else if(sides == 20 && result == 1) - comment = "Ouch, bad luck." - update_icon() - if(initial(icon_state) == "d00") - result = (result - 1)*10 - if(special_faces.len == sides) - result = special_faces[result] - if(user != null) //Dice was rolled in someone's hand - user.visible_message("[user] has thrown [src]. It lands on [result]. [comment]", \ - "You throw [src]. It lands on [result]. [comment]", \ - "You hear [src] rolling, it sounds like a [fake_result].") - else if(!src.throwing) //Dice was thrown and is coming to rest - visible_message("[src] rolls to a stop, landing on [result]. [comment]") - -/obj/item/dice/d20/e20/diceroll(mob/user as mob, thrown) - if(triggered) - return - - . = ..() - - if(result == 1) - to_chat(user, "Rocks fall, you die.") - user.gib() - else - triggered = 1 - visible_message("You hear a quiet click.") - spawn(40) - - var/cap = 0 - if(result > MAX_EX_LIGHT_RANGE && result != 20) - cap = 1 - result = min(result, MAX_EX_LIGHT_RANGE) //Apply the bombcap - else if(result == 20) //Roll a nat 20, screw the bombcap - result = 24 - var/turf/epicenter = get_turf(src) - explosion(epicenter, round(result*0.25), round(result*0.5), round(result), round(result*1.5), 1, cap) - - var/turf/bombturf = get_turf(src) - var/area/A = get_area(bombturf) - investigate_log("E20 detonated at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) with a roll of [result]. Triggered by: [key_name(user)]", INVESTIGATE_BOMB) - message_admins("E20 detonated at [A.name] (JMP) with a roll of [result]. Triggered by: [key_name_admin(user)]") - log_game("E20 detonated at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) with a roll of [result]. Triggered by: [key_name(user)]") - - -/obj/item/dice/update_icon() - overlays.Cut() - overlays += "[icon_state][result]" - -/obj/item/storage/box/dice - name = "Box of dice" - desc = "ANOTHER ONE!? FUCK!" - icon_state = "box" - -/obj/item/storage/box/dice/New() - ..() - new /obj/item/dice/d2(src) - new /obj/item/dice/d4(src) - new /obj/item/dice/d8(src) - new /obj/item/dice/d10(src) - new /obj/item/dice/d00(src) - new /obj/item/dice/d12(src) - new /obj/item/dice/d20(src) +/obj/item/storage/pill_bottle/dice + name = "bag of dice" + desc = "Contains all the luck you'll ever need." + icon = 'icons/obj/dice.dmi' + icon_state = "dicebag" + can_hold = list(/obj/item/dice) + allow_wrap = FALSE + +/obj/item/storage/pill_bottle/dice/New() + ..() + var/special_die = pick("1","2","fudge","00","100") + if(special_die == "1") + new /obj/item/dice/d1(src) + if(special_die == "2") + new /obj/item/dice/d2(src) + new /obj/item/dice/d4(src) + new /obj/item/dice/d6(src) + if(special_die == "fudge") + new /obj/item/dice/fudge(src) + new /obj/item/dice/d8(src) + new /obj/item/dice/d10(src) + if(special_die == "00") + new /obj/item/dice/d00(src) + new /obj/item/dice/d12(src) + new /obj/item/dice/d20(src) + if(special_die == "100") + new /obj/item/dice/d100(src) + +/obj/item/storage/pill_bottle/dice/suicide_act(mob/user) + user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/dice //depreciated d6, use /obj/item/dice/d6 if you actually want a d6 + name = "die" + desc = "A die with six sides. Basic and servicable." + icon = 'icons/obj/dice.dmi' + icon_state = "d6" + w_class = WEIGHT_CLASS_TINY + + var/sides = 6 + var/result = null + var/list/special_faces = list() //entries should match up to sides var if used + + var/rigged = DICE_NOT_RIGGED + var/rigged_value + +/obj/item/dice/Initialize(mapload) + . = ..() + if(!result) + result = roll(sides) + update_icon() + +/obj/item/dice/suicide_act(mob/user) + user.visible_message("[user] is gambling with death! It looks like [user.p_theyre()] trying to commit suicide!") + return (OXYLOSS) + +/obj/item/dice/d1 + name = "d1" + desc = "A die with one side. Deterministic!" + icon_state = "d1" + sides = 1 + +/obj/item/dice/d2 + name = "d2" + desc = "A die with two sides. Coins are undignified!" + icon_state = "d2" + sides = 2 + +/obj/item/dice/d4 + name = "d4" + desc = "A die with four sides. The nerd's caltrop." + icon_state = "d4" + sides = 4 + +/obj/item/dice/d4/Initialize(mapload) + . = ..() + AddComponent(/datum/component/caltrop, 1, 4) //1d4 damage + +/obj/item/dice/d6 + name = "d6" + +/obj/item/dice/fudge + name = "fudge die" + desc = "A die with six sides but only three results. Is this a plus or a minus? Your mind is drawing a blank..." + sides = 3 //shhh + icon_state = "fudge" + special_faces = list("minus","blank","plus") + +/obj/item/dice/d8 + name = "d8" + desc = "A die with eight sides. It feels... lucky." + icon_state = "d8" + sides = 8 + +/obj/item/dice/d10 + name = "d10" + desc = "A die with ten sides. Useful for percentages." + icon_state = "d10" + sides = 10 + +/obj/item/dice/d00 + name = "d00" + desc = "A die with ten sides. Works better for d100 rolls than a golfball." + icon_state = "d00" + sides = 10 + +/obj/item/dice/d12 + name = "d12" + desc = "A die with twelve sides. There's an air of neglect about it." + icon_state = "d12" + sides = 12 + +/obj/item/dice/d20 + name = "d20" + desc = "A die with twenty sides. The prefered die to throw at the GM." + icon_state = "d20" + sides = 20 + +/obj/item/dice/d100 + name = "d100" + desc = "A die with one hundred sides! Probably not fairly weighted..." + icon_state = "d100" + sides = 100 + +/obj/item/dice/d100/update_icon() + return + +/obj/item/dice/d20/e20 + var/triggered = 0 + +/obj/item/dice/attack_self(mob/user as mob) + diceroll(user) + +/obj/item/dice/throw_impact(atom/target) + diceroll(thrownby) + . = ..() + +/obj/item/dice/proc/diceroll(mob/user) + result = roll(sides) + if(rigged != DICE_NOT_RIGGED && result != rigged_value) + if(rigged == DICE_BASICALLY_RIGGED && prob(Clamp(1/(sides - 1) * 100, 25, 80))) + result = rigged_value + else if(rigged == DICE_TOTALLY_RIGGED) + result = rigged_value + + . = result + + var/fake_result = roll(sides)//Daredevil isn't as good as he used to be + var/comment = "" + if(sides == 20 && result == 20) + comment = "NAT 20!" + else if(sides == 20 && result == 1) + comment = "Ouch, bad luck." + update_icon() + if(initial(icon_state) == "d00") + result = (result - 1)*10 + if(special_faces.len == sides) + result = special_faces[result] + if(user != null) //Dice was rolled in someone's hand + user.visible_message("[user] has thrown [src]. It lands on [result]. [comment]", \ + "You throw [src]. It lands on [result]. [comment]", \ + "You hear [src] rolling, it sounds like a [fake_result].") + else if(!src.throwing) //Dice was thrown and is coming to rest + visible_message("[src] rolls to a stop, landing on [result]. [comment]") + +/obj/item/dice/d20/e20/diceroll(mob/user as mob, thrown) + if(triggered) + return + + . = ..() + + if(result == 1) + to_chat(user, "Rocks fall, you die.") + user.gib() + else + triggered = 1 + visible_message("You hear a quiet click.") + spawn(40) + + var/cap = 0 + if(result > MAX_EX_LIGHT_RANGE && result != 20) + cap = 1 + result = min(result, MAX_EX_LIGHT_RANGE) //Apply the bombcap + else if(result == 20) //Roll a nat 20, screw the bombcap + result = 24 + var/turf/epicenter = get_turf(src) + explosion(epicenter, round(result*0.25), round(result*0.5), round(result), round(result*1.5), 1, cap) + + var/turf/bombturf = get_turf(src) + var/area/A = get_area(bombturf) + investigate_log("E20 detonated at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) with a roll of [result]. Triggered by: [key_name(user)]", INVESTIGATE_BOMB) + message_admins("E20 detonated at [A.name] (JMP) with a roll of [result]. Triggered by: [key_name_admin(user)]") + log_game("E20 detonated at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) with a roll of [result]. Triggered by: [key_name(user)]") + + +/obj/item/dice/update_icon() + overlays.Cut() + overlays += "[icon_state][result]" + +/obj/item/storage/box/dice + name = "Box of dice" + desc = "ANOTHER ONE!? FUCK!" + icon_state = "box" + +/obj/item/storage/box/dice/New() + ..() + new /obj/item/dice/d2(src) + new /obj/item/dice/d4(src) + new /obj/item/dice/d8(src) + new /obj/item/dice/d10(src) + new /obj/item/dice/d00(src) + new /obj/item/dice/d12(src) + new /obj/item/dice/d20(src) diff --git a/code/game/objects/items/weapons/dna_injector.dm b/code/game/objects/items/weapons/dna_injector.dm index c7627688b5e..295d17efc39 100644 --- a/code/game/objects/items/weapons/dna_injector.dm +++ b/code/game/objects/items/weapons/dna_injector.dm @@ -1,694 +1,694 @@ -/obj/item/dnainjector - name = "DNA-Injector" - desc = "This injects the person with DNA." - icon = 'icons/obj/items.dmi' - icon_state = "dnainjector" - item_state = "dnainjector" - var/block = 0 - var/datum/dna2/record/buf = null - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - origin_tech = "biotech=1" - - var/damage_coeff = 1 - var/used = FALSE - - // USE ONLY IN PREMADE SYRINGES. WILL NOT WORK OTHERWISE. - var/datatype = 0 - var/value = 0 - var/forcedmutation = FALSE //Will it give the mutation, guaranteed? - -/obj/item/dnainjector/Initialize() - . = ..() - if(datatype && block) - buf = new - buf.dna = new - buf.types = datatype - buf.dna.ResetSE() - SetValue(value) - -/obj/item/dnainjector/Destroy() - QDEL_NULL(buf) - return ..() - -/obj/item/dnainjector/proc/GetRealBlock(selblock) - if(selblock == 0) - return block - else - return selblock - -/obj/item/dnainjector/proc/GetState(selblock = 0) - var/real_block = GetRealBlock(selblock) - if(buf.types & DNA2_BUF_SE) - return buf.dna.GetSEState(real_block) - else - return buf.dna.GetUIState(real_block) - -/obj/item/dnainjector/proc/SetState(on, selblock = 0) - var/real_block = GetRealBlock(selblock) - if(buf.types & DNA2_BUF_SE) - return buf.dna.SetSEState(real_block,on) - else - return buf.dna.SetUIState(real_block,on) - -/obj/item/dnainjector/proc/GetValue(selblock = 0) - var/real_block = GetRealBlock(selblock) - if(buf.types & DNA2_BUF_SE) - return buf.dna.GetSEValue(real_block) - else - return buf.dna.GetUIValue(real_block) - -/obj/item/dnainjector/proc/SetValue(val, selblock = 0) - var/real_block = GetRealBlock(selblock) - if(buf.types & DNA2_BUF_SE) - return buf.dna.SetSEValue(real_block,val) - else - return buf.dna.SetUIValue(real_block,val) - -/obj/item/dnainjector/proc/inject(mob/living/M, mob/user) - if(used) - return - if(istype(M,/mob/living)) - M.apply_effect(rand(20 / (damage_coeff ** 2), 50 / (damage_coeff ** 2)), IRRADIATE, 0, 1) - var/mob/living/carbon/human/H - if(istype(M, /mob/living/carbon/human)) - H = M - - if(!buf) - log_runtime(EXCEPTION("[src] used by [user] on [M] failed to initialize properly."), src) - return - - spawn(0) //Some mutations have sleeps in them, like monkey - if(!(NOCLONE in M.mutations) && !(H && (NO_DNA in H.dna.species.species_traits))) // prevents drained people from having their DNA changed - var/prev_ue = M.dna.unique_enzymes - // UI in syringe. - if(buf.types & DNA2_BUF_UI) - if(!block) //isolated block? - M.dna.UI = buf.dna.UI.Copy() - M.dna.UpdateUI() - M.UpdateAppearance() - if(buf.types & DNA2_BUF_UE) //unique enzymes? yes - - M.real_name = buf.dna.real_name - M.name = buf.dna.real_name - M.dna.real_name = buf.dna.real_name - M.dna.unique_enzymes = buf.dna.unique_enzymes - else - M.dna.SetUIValue(block,src.GetValue()) - M.UpdateAppearance() - if(buf.types & DNA2_BUF_SE) - if(!block) //isolated block? - M.dna.SE = buf.dna.SE.Copy() - M.dna.UpdateSE() - else - M.dna.SetSEValue(block,src.GetValue()) - domutcheck(M, null, forcedmutation ? MUTCHK_FORCED : 0) - M.update_mutations() - if(H) - H.sync_organ_dna(assimilate = 0, old_ue = prev_ue) - -/obj/item/dnainjector/attack(mob/M, mob/user) - if(used) - to_chat(user, "This injector is used up!") - return - if(!M.dna) //You know what would be nice? If the mob you're injecting has DNA, and so doesn't cause runtimes. - return FALSE - - if(ishuman(M)) // Would've done this via species instead of type, but the basic mob doesn't have a species, go figure. - var/mob/living/carbon/human/H = M - if(NO_DNA in H.dna.species.species_traits) - return FALSE - - if(!user.IsAdvancedToolUser()) - return FALSE - - var/attack_log = "injected with the Isolated [name]" - - if(buf && buf.types & DNA2_BUF_SE) - if(block) - if(GetState() && block == MONKEYBLOCK && ishuman(M)) - attack_log = "injected with the Isolated [name] (MONKEY)" - message_admins("[key_name_admin(user)] injected [key_name_admin(M)] with the Isolated [name] (MONKEY)") - - else - if(GetState(MONKEYBLOCK) && ishuman(M)) - attack_log = "injected with the Isolated [name] (MONKEY)" - message_admins("[key_name_admin(user)] injected [key_name_admin(M)] with the Isolated [name] (MONKEY)") - - - if(M != user) - M.visible_message("[user] is trying to inject [M] with [src]!", "[user] is trying to inject [M] with [src]!") - if(!do_mob(user, M)) - return - M.visible_message("[user] injects [M] with the syringe with [src]!", \ - "[user] injects [M] with the syringe with [src]!") - else - to_chat(user, "You inject yourself with [src].") - - add_attack_logs(user, M, attack_log, ATKLOG_ALL) - if(!iscarbon(user)) - M.LAssailant = null - else - M.LAssailant = user - - inject(M, user) - used = TRUE - icon_state = "dnainjector0" - desc += " This one is used up." - -/obj/item/dnainjector/hulkmut - name = "DNA-Injector (Hulk)" - desc = "This will make you big and strong, but give you a bad skin condition." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/hulkmut/Initialize() - block = HULKBLOCK - ..() - -/obj/item/dnainjector/antihulk - name = "DNA-Injector (Anti-Hulk)" - desc = "Cures green skin." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antihulk/Initialize() - block = HULKBLOCK - ..() - -/obj/item/dnainjector/xraymut - name = "DNA-Injector (Xray)" - desc = "Finally you can see what the Captain does." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/xraymut/Initialize() - block = XRAYBLOCK - ..() - -/obj/item/dnainjector/antixray - name = "DNA-Injector (Anti-Xray)" - desc = "It will make you see harder." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antixray/Initialize() - block = XRAYBLOCK - ..() - -/obj/item/dnainjector/firemut - name = "DNA-Injector (Fire)" - desc = "Gives you fire." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/firemut/Initialize() - block = FIREBLOCK - ..() - -/obj/item/dnainjector/antifire - name = "DNA-Injector (Anti-Fire)" - desc = "Cures fire." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antifire/Initialize() - block = FIREBLOCK - ..() - -/obj/item/dnainjector/telemut - name = "DNA-Injector (Tele.)" - desc = "Super brain man!" - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/telemut/Initialize() - block = TELEBLOCK - ..() - -/obj/item/dnainjector/telemut/darkbundle - name = "DNA injector" - desc = "Good. Let the hate flow through you." - - -/obj/item/dnainjector/antitele - name = "DNA-Injector (Anti-Tele.)" - desc = "Will make you not able to control your mind." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antitele/Initialize() - block = TELEBLOCK - ..() - -/obj/item/dnainjector/nobreath - name = "DNA-Injector (Breathless)" - desc = "Hold your breath and count to infinity." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/nobreath/Initialize() - block = BREATHLESSBLOCK - ..() - -/obj/item/dnainjector/antinobreath - name = "DNA-Injector (Anti-Breathless)" - desc = "Hold your breath and count to 100." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antinobreath/Initialize() - block = BREATHLESSBLOCK - ..() - -/obj/item/dnainjector/remoteview - name = "DNA-Injector (Remote View)" - desc = "Stare into the distance for a reason." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/remoteview/Initialize() - block = REMOTEVIEWBLOCK - ..() - -/obj/item/dnainjector/antiremoteview - name = "DNA-Injector (Anti-Remote View)" - desc = "Cures green skin." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antiremoteview/Initialize() - block = REMOTEVIEWBLOCK - ..() - -/obj/item/dnainjector/regenerate - name = "DNA-Injector (Regeneration)" - desc = "Healthy but hungry." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/regenerate/Initialize() - block = REGENERATEBLOCK - ..() - -/obj/item/dnainjector/antiregenerate - name = "DNA-Injector (Anti-Regeneration)" - desc = "Sickly but sated." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antiregenerate/Initialize() - block = REGENERATEBLOCK - ..() - -/obj/item/dnainjector/runfast - name = "DNA-Injector (Increase Run)" - desc = "Running Man." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/runfast/Initialize() - block = INCREASERUNBLOCK - ..() - -/obj/item/dnainjector/antirunfast - name = "DNA-Injector (Anti-Increase Run)" - desc = "Walking Man." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antirunfast/Initialize() - block = INCREASERUNBLOCK - ..() - -/obj/item/dnainjector/morph - name = "DNA-Injector (Morph)" - desc = "A total makeover." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/morph/Initialize() - block = MORPHBLOCK - ..() - -/obj/item/dnainjector/antimorph - name = "DNA-Injector (Anti-Morph)" - desc = "Cures identity crisis." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antimorph/Initialize() - block = MORPHBLOCK - ..() - -/obj/item/dnainjector/noprints - name = "DNA-Injector (No Prints)" - desc = "Better than a pair of budget insulated gloves." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/noprints/Initialize() - block = NOPRINTSBLOCK - ..() - -/obj/item/dnainjector/antinoprints - name = "DNA-Injector (Anti-No Prints)" - desc = "Not quite as good as a pair of budget insulated gloves." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antinoprints/Initialize() - block = NOPRINTSBLOCK - ..() - -/obj/item/dnainjector/insulation - name = "DNA-Injector (Shock Immunity)" - desc = "Better than a pair of real insulated gloves." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/insulation/Initialize() - block = SHOCKIMMUNITYBLOCK - ..() - -/obj/item/dnainjector/antiinsulation - name = "DNA-Injector (Anti-Shock Immunity)" - desc = "Not quite as good as a pair of real insulated gloves." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antiinsulation/Initialize() - block = SHOCKIMMUNITYBLOCK - ..() - -/obj/item/dnainjector/midgit - name = "DNA-Injector (Small Size)" - desc = "Makes you shrink." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/midgit/Initialize() - block = SMALLSIZEBLOCK - ..() - -/obj/item/dnainjector/antimidgit - name = "DNA-Injector (Anti-Small Size)" - desc = "Makes you grow. But not too much." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antimidgit/Initialize() - block = SMALLSIZEBLOCK - ..() - -///////////////////////////////////// -/obj/item/dnainjector/antiglasses - name = "DNA-Injector (Anti-Glasses)" - desc = "Toss away those glasses!" - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antiglasses/Initialize() - block = GLASSESBLOCK - ..() - -/obj/item/dnainjector/glassesmut - name = "DNA-Injector (Glasses)" - desc = "Will make you need dorkish glasses." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/glassesmut/Initialize() - block = GLASSESBLOCK - ..() - -/obj/item/dnainjector/epimut - name = "DNA-Injector (Epi.)" - desc = "Shake shake shake the room!" - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/epimut/Initialize() - block = EPILEPSYBLOCK - ..() - -/obj/item/dnainjector/antiepi - name = "DNA-Injector (Anti-Epi.)" - desc = "Will fix you up from shaking the room." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antiepi/Initialize() - block = EPILEPSYBLOCK - ..() - -/obj/item/dnainjector/anticough - name = "DNA-Injector (Anti-Cough)" - desc = "Will stop that awful noise." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/anticough/Initialize() - block = COUGHBLOCK - ..() - -/obj/item/dnainjector/coughmut - name = "DNA-Injector (Cough)" - desc = "Will bring forth a sound of horror from your throat." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/coughmut/Initialize() - block = COUGHBLOCK - ..() - -/obj/item/dnainjector/clumsymut - name = "DNA-Injector (Clumsy)" - desc = "Makes clumsy minions." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/clumsymut/Initialize() - block = CLUMSYBLOCK - ..() - -/obj/item/dnainjector/anticlumsy - name = "DNA-Injector (Anti-Clumy)" - desc = "Cleans up confusion." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/anticlumsy/Initialize() - block = CLUMSYBLOCK - ..() - -/obj/item/dnainjector/antitour - name = "DNA-Injector (Anti-Tour.)" - desc = "Will cure tourrets." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antitour/Initialize() - block = TWITCHBLOCK - ..() - -/obj/item/dnainjector/tourmut - name = "DNA-Injector (Tour.)" - desc = "Gives you a nasty case off tourrets." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/tourmut/Initialize() - block = TWITCHBLOCK - ..() - -/obj/item/dnainjector/stuttmut - name = "DNA-Injector (Stutt.)" - desc = "Makes you s-s-stuttterrr" - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/stuttmut/Initialize() - block = NERVOUSBLOCK - ..() - - -/obj/item/dnainjector/antistutt - name = "DNA-Injector (Anti-Stutt.)" - desc = "Fixes that speaking impairment." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antistutt/Initialize() - block = NERVOUSBLOCK - ..() - -/obj/item/dnainjector/blindmut - name = "DNA-Injector (Blind)" - desc = "Makes you not see anything." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/blindmut/Initialize() - block = BLINDBLOCK - ..() - -/obj/item/dnainjector/antiblind - name = "DNA-Injector (Anti-Blind)" - desc = "ITS A MIRACLE!!!" - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antiblind/Initialize() - block = BLINDBLOCK - ..() - -/obj/item/dnainjector/telemut - name = "DNA-Injector (Tele.)" - desc = "Super brain man!" - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/telemut/Initialize() - block = TELEBLOCK - ..() - -/obj/item/dnainjector/antitele - name = "DNA-Injector (Anti-Tele.)" - desc = "Will make you not able to control your mind." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antitele/Initialize() - block = TELEBLOCK - ..() - -/obj/item/dnainjector/deafmut - name = "DNA-Injector (Deaf)" - desc = "Sorry, what did you say?" - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/deafmut/Initialize() - block = DEAFBLOCK - ..() - -/obj/item/dnainjector/antideaf - name = "DNA-Injector (Anti-Deaf)" - desc = "Will make you hear once more." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antideaf/Initialize() - block = DEAFBLOCK - ..() - -/obj/item/dnainjector/hallucination - name = "DNA-Injector (Halluctination)" - desc = "What you see isn't always what you get." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/hallucination/Initialize() - block = HALLUCINATIONBLOCK - ..() - -/obj/item/dnainjector/antihallucination - name = "DNA-Injector (Anti-Hallucination)" - desc = "What you see is what you get." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/antihallucination/Initialize() - block = HALLUCINATIONBLOCK - ..() - -/obj/item/dnainjector/h2m - name = "DNA-Injector (Human > Monkey)" - desc = "Will make you a flea bag." - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/h2m/Initialize() - block = MONKEYBLOCK - ..() - -/obj/item/dnainjector/m2h - name = "DNA-Injector (Monkey > Human)" - desc = "Will make you...less hairy." - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/m2h/Initialize() - block = MONKEYBLOCK - ..() - - -/obj/item/dnainjector/comic - name = "DNA-Injector (Comic)" - desc = "Honk!" - datatype = DNA2_BUF_SE - value = 0xFFF - forcedmutation = TRUE - -/obj/item/dnainjector/comic/Initialize() - block = COMICBLOCK - ..() - -/obj/item/dnainjector/anticomic - name = "DNA-Injector (Ant-Comic)" - desc = "Honk...?" - datatype = DNA2_BUF_SE - value = 0x001 - forcedmutation = TRUE - -/obj/item/dnainjector/anticomic/Initialize() - block = COMICBLOCK - ..() \ No newline at end of file +/obj/item/dnainjector + name = "DNA-Injector" + desc = "This injects the person with DNA." + icon = 'icons/obj/items.dmi' + icon_state = "dnainjector" + item_state = "dnainjector" + var/block = 0 + var/datum/dna2/record/buf = null + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + origin_tech = "biotech=1" + + var/damage_coeff = 1 + var/used = FALSE + + // USE ONLY IN PREMADE SYRINGES. WILL NOT WORK OTHERWISE. + var/datatype = 0 + var/value = 0 + var/forcedmutation = FALSE //Will it give the mutation, guaranteed? + +/obj/item/dnainjector/Initialize() + . = ..() + if(datatype && block) + buf = new + buf.dna = new + buf.types = datatype + buf.dna.ResetSE() + SetValue(value) + +/obj/item/dnainjector/Destroy() + QDEL_NULL(buf) + return ..() + +/obj/item/dnainjector/proc/GetRealBlock(selblock) + if(selblock == 0) + return block + else + return selblock + +/obj/item/dnainjector/proc/GetState(selblock = 0) + var/real_block = GetRealBlock(selblock) + if(buf.types & DNA2_BUF_SE) + return buf.dna.GetSEState(real_block) + else + return buf.dna.GetUIState(real_block) + +/obj/item/dnainjector/proc/SetState(on, selblock = 0) + var/real_block = GetRealBlock(selblock) + if(buf.types & DNA2_BUF_SE) + return buf.dna.SetSEState(real_block,on) + else + return buf.dna.SetUIState(real_block,on) + +/obj/item/dnainjector/proc/GetValue(selblock = 0) + var/real_block = GetRealBlock(selblock) + if(buf.types & DNA2_BUF_SE) + return buf.dna.GetSEValue(real_block) + else + return buf.dna.GetUIValue(real_block) + +/obj/item/dnainjector/proc/SetValue(val, selblock = 0) + var/real_block = GetRealBlock(selblock) + if(buf.types & DNA2_BUF_SE) + return buf.dna.SetSEValue(real_block,val) + else + return buf.dna.SetUIValue(real_block,val) + +/obj/item/dnainjector/proc/inject(mob/living/M, mob/user) + if(used) + return + if(istype(M,/mob/living)) + M.apply_effect(rand(20 / (damage_coeff ** 2), 50 / (damage_coeff ** 2)), IRRADIATE, 0, 1) + var/mob/living/carbon/human/H + if(istype(M, /mob/living/carbon/human)) + H = M + + if(!buf) + log_runtime(EXCEPTION("[src] used by [user] on [M] failed to initialize properly."), src) + return + + spawn(0) //Some mutations have sleeps in them, like monkey + if(!(NOCLONE in M.mutations) && !(H && (NO_DNA in H.dna.species.species_traits))) // prevents drained people from having their DNA changed + var/prev_ue = M.dna.unique_enzymes + // UI in syringe. + if(buf.types & DNA2_BUF_UI) + if(!block) //isolated block? + M.dna.UI = buf.dna.UI.Copy() + M.dna.UpdateUI() + M.UpdateAppearance() + if(buf.types & DNA2_BUF_UE) //unique enzymes? yes + + M.real_name = buf.dna.real_name + M.name = buf.dna.real_name + M.dna.real_name = buf.dna.real_name + M.dna.unique_enzymes = buf.dna.unique_enzymes + else + M.dna.SetUIValue(block,src.GetValue()) + M.UpdateAppearance() + if(buf.types & DNA2_BUF_SE) + if(!block) //isolated block? + M.dna.SE = buf.dna.SE.Copy() + M.dna.UpdateSE() + else + M.dna.SetSEValue(block,src.GetValue()) + domutcheck(M, null, forcedmutation ? MUTCHK_FORCED : 0) + M.update_mutations() + if(H) + H.sync_organ_dna(assimilate = 0, old_ue = prev_ue) + +/obj/item/dnainjector/attack(mob/M, mob/user) + if(used) + to_chat(user, "This injector is used up!") + return + if(!M.dna) //You know what would be nice? If the mob you're injecting has DNA, and so doesn't cause runtimes. + return FALSE + + if(ishuman(M)) // Would've done this via species instead of type, but the basic mob doesn't have a species, go figure. + var/mob/living/carbon/human/H = M + if(NO_DNA in H.dna.species.species_traits) + return FALSE + + if(!user.IsAdvancedToolUser()) + return FALSE + + var/attack_log = "injected with the Isolated [name]" + + if(buf && buf.types & DNA2_BUF_SE) + if(block) + if(GetState() && block == MONKEYBLOCK && ishuman(M)) + attack_log = "injected with the Isolated [name] (MONKEY)" + message_admins("[key_name_admin(user)] injected [key_name_admin(M)] with the Isolated [name] (MONKEY)") + + else + if(GetState(MONKEYBLOCK) && ishuman(M)) + attack_log = "injected with the Isolated [name] (MONKEY)" + message_admins("[key_name_admin(user)] injected [key_name_admin(M)] with the Isolated [name] (MONKEY)") + + + if(M != user) + M.visible_message("[user] is trying to inject [M] with [src]!", "[user] is trying to inject [M] with [src]!") + if(!do_mob(user, M)) + return + M.visible_message("[user] injects [M] with the syringe with [src]!", \ + "[user] injects [M] with the syringe with [src]!") + else + to_chat(user, "You inject yourself with [src].") + + add_attack_logs(user, M, attack_log, ATKLOG_ALL) + if(!iscarbon(user)) + M.LAssailant = null + else + M.LAssailant = user + + inject(M, user) + used = TRUE + icon_state = "dnainjector0" + desc += " This one is used up." + +/obj/item/dnainjector/hulkmut + name = "DNA-Injector (Hulk)" + desc = "This will make you big and strong, but give you a bad skin condition." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/hulkmut/Initialize() + block = HULKBLOCK + ..() + +/obj/item/dnainjector/antihulk + name = "DNA-Injector (Anti-Hulk)" + desc = "Cures green skin." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antihulk/Initialize() + block = HULKBLOCK + ..() + +/obj/item/dnainjector/xraymut + name = "DNA-Injector (Xray)" + desc = "Finally you can see what the Captain does." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/xraymut/Initialize() + block = XRAYBLOCK + ..() + +/obj/item/dnainjector/antixray + name = "DNA-Injector (Anti-Xray)" + desc = "It will make you see harder." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antixray/Initialize() + block = XRAYBLOCK + ..() + +/obj/item/dnainjector/firemut + name = "DNA-Injector (Fire)" + desc = "Gives you fire." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/firemut/Initialize() + block = FIREBLOCK + ..() + +/obj/item/dnainjector/antifire + name = "DNA-Injector (Anti-Fire)" + desc = "Cures fire." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antifire/Initialize() + block = FIREBLOCK + ..() + +/obj/item/dnainjector/telemut + name = "DNA-Injector (Tele.)" + desc = "Super brain man!" + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/telemut/Initialize() + block = TELEBLOCK + ..() + +/obj/item/dnainjector/telemut/darkbundle + name = "DNA injector" + desc = "Good. Let the hate flow through you." + + +/obj/item/dnainjector/antitele + name = "DNA-Injector (Anti-Tele.)" + desc = "Will make you not able to control your mind." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antitele/Initialize() + block = TELEBLOCK + ..() + +/obj/item/dnainjector/nobreath + name = "DNA-Injector (Breathless)" + desc = "Hold your breath and count to infinity." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/nobreath/Initialize() + block = BREATHLESSBLOCK + ..() + +/obj/item/dnainjector/antinobreath + name = "DNA-Injector (Anti-Breathless)" + desc = "Hold your breath and count to 100." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antinobreath/Initialize() + block = BREATHLESSBLOCK + ..() + +/obj/item/dnainjector/remoteview + name = "DNA-Injector (Remote View)" + desc = "Stare into the distance for a reason." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/remoteview/Initialize() + block = REMOTEVIEWBLOCK + ..() + +/obj/item/dnainjector/antiremoteview + name = "DNA-Injector (Anti-Remote View)" + desc = "Cures green skin." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antiremoteview/Initialize() + block = REMOTEVIEWBLOCK + ..() + +/obj/item/dnainjector/regenerate + name = "DNA-Injector (Regeneration)" + desc = "Healthy but hungry." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/regenerate/Initialize() + block = REGENERATEBLOCK + ..() + +/obj/item/dnainjector/antiregenerate + name = "DNA-Injector (Anti-Regeneration)" + desc = "Sickly but sated." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antiregenerate/Initialize() + block = REGENERATEBLOCK + ..() + +/obj/item/dnainjector/runfast + name = "DNA-Injector (Increase Run)" + desc = "Running Man." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/runfast/Initialize() + block = INCREASERUNBLOCK + ..() + +/obj/item/dnainjector/antirunfast + name = "DNA-Injector (Anti-Increase Run)" + desc = "Walking Man." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antirunfast/Initialize() + block = INCREASERUNBLOCK + ..() + +/obj/item/dnainjector/morph + name = "DNA-Injector (Morph)" + desc = "A total makeover." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/morph/Initialize() + block = MORPHBLOCK + ..() + +/obj/item/dnainjector/antimorph + name = "DNA-Injector (Anti-Morph)" + desc = "Cures identity crisis." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antimorph/Initialize() + block = MORPHBLOCK + ..() + +/obj/item/dnainjector/noprints + name = "DNA-Injector (No Prints)" + desc = "Better than a pair of budget insulated gloves." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/noprints/Initialize() + block = NOPRINTSBLOCK + ..() + +/obj/item/dnainjector/antinoprints + name = "DNA-Injector (Anti-No Prints)" + desc = "Not quite as good as a pair of budget insulated gloves." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antinoprints/Initialize() + block = NOPRINTSBLOCK + ..() + +/obj/item/dnainjector/insulation + name = "DNA-Injector (Shock Immunity)" + desc = "Better than a pair of real insulated gloves." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/insulation/Initialize() + block = SHOCKIMMUNITYBLOCK + ..() + +/obj/item/dnainjector/antiinsulation + name = "DNA-Injector (Anti-Shock Immunity)" + desc = "Not quite as good as a pair of real insulated gloves." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antiinsulation/Initialize() + block = SHOCKIMMUNITYBLOCK + ..() + +/obj/item/dnainjector/midgit + name = "DNA-Injector (Small Size)" + desc = "Makes you shrink." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/midgit/Initialize() + block = SMALLSIZEBLOCK + ..() + +/obj/item/dnainjector/antimidgit + name = "DNA-Injector (Anti-Small Size)" + desc = "Makes you grow. But not too much." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antimidgit/Initialize() + block = SMALLSIZEBLOCK + ..() + +///////////////////////////////////// +/obj/item/dnainjector/antiglasses + name = "DNA-Injector (Anti-Glasses)" + desc = "Toss away those glasses!" + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antiglasses/Initialize() + block = GLASSESBLOCK + ..() + +/obj/item/dnainjector/glassesmut + name = "DNA-Injector (Glasses)" + desc = "Will make you need dorkish glasses." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/glassesmut/Initialize() + block = GLASSESBLOCK + ..() + +/obj/item/dnainjector/epimut + name = "DNA-Injector (Epi.)" + desc = "Shake shake shake the room!" + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/epimut/Initialize() + block = EPILEPSYBLOCK + ..() + +/obj/item/dnainjector/antiepi + name = "DNA-Injector (Anti-Epi.)" + desc = "Will fix you up from shaking the room." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antiepi/Initialize() + block = EPILEPSYBLOCK + ..() + +/obj/item/dnainjector/anticough + name = "DNA-Injector (Anti-Cough)" + desc = "Will stop that awful noise." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/anticough/Initialize() + block = COUGHBLOCK + ..() + +/obj/item/dnainjector/coughmut + name = "DNA-Injector (Cough)" + desc = "Will bring forth a sound of horror from your throat." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/coughmut/Initialize() + block = COUGHBLOCK + ..() + +/obj/item/dnainjector/clumsymut + name = "DNA-Injector (Clumsy)" + desc = "Makes clumsy minions." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/clumsymut/Initialize() + block = CLUMSYBLOCK + ..() + +/obj/item/dnainjector/anticlumsy + name = "DNA-Injector (Anti-Clumy)" + desc = "Cleans up confusion." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/anticlumsy/Initialize() + block = CLUMSYBLOCK + ..() + +/obj/item/dnainjector/antitour + name = "DNA-Injector (Anti-Tour.)" + desc = "Will cure tourrets." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antitour/Initialize() + block = TWITCHBLOCK + ..() + +/obj/item/dnainjector/tourmut + name = "DNA-Injector (Tour.)" + desc = "Gives you a nasty case off tourrets." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/tourmut/Initialize() + block = TWITCHBLOCK + ..() + +/obj/item/dnainjector/stuttmut + name = "DNA-Injector (Stutt.)" + desc = "Makes you s-s-stuttterrr" + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/stuttmut/Initialize() + block = NERVOUSBLOCK + ..() + + +/obj/item/dnainjector/antistutt + name = "DNA-Injector (Anti-Stutt.)" + desc = "Fixes that speaking impairment." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antistutt/Initialize() + block = NERVOUSBLOCK + ..() + +/obj/item/dnainjector/blindmut + name = "DNA-Injector (Blind)" + desc = "Makes you not see anything." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/blindmut/Initialize() + block = BLINDBLOCK + ..() + +/obj/item/dnainjector/antiblind + name = "DNA-Injector (Anti-Blind)" + desc = "ITS A MIRACLE!!!" + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antiblind/Initialize() + block = BLINDBLOCK + ..() + +/obj/item/dnainjector/telemut + name = "DNA-Injector (Tele.)" + desc = "Super brain man!" + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/telemut/Initialize() + block = TELEBLOCK + ..() + +/obj/item/dnainjector/antitele + name = "DNA-Injector (Anti-Tele.)" + desc = "Will make you not able to control your mind." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antitele/Initialize() + block = TELEBLOCK + ..() + +/obj/item/dnainjector/deafmut + name = "DNA-Injector (Deaf)" + desc = "Sorry, what did you say?" + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/deafmut/Initialize() + block = DEAFBLOCK + ..() + +/obj/item/dnainjector/antideaf + name = "DNA-Injector (Anti-Deaf)" + desc = "Will make you hear once more." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antideaf/Initialize() + block = DEAFBLOCK + ..() + +/obj/item/dnainjector/hallucination + name = "DNA-Injector (Halluctination)" + desc = "What you see isn't always what you get." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/hallucination/Initialize() + block = HALLUCINATIONBLOCK + ..() + +/obj/item/dnainjector/antihallucination + name = "DNA-Injector (Anti-Hallucination)" + desc = "What you see is what you get." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/antihallucination/Initialize() + block = HALLUCINATIONBLOCK + ..() + +/obj/item/dnainjector/h2m + name = "DNA-Injector (Human > Monkey)" + desc = "Will make you a flea bag." + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/h2m/Initialize() + block = MONKEYBLOCK + ..() + +/obj/item/dnainjector/m2h + name = "DNA-Injector (Monkey > Human)" + desc = "Will make you...less hairy." + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/m2h/Initialize() + block = MONKEYBLOCK + ..() + + +/obj/item/dnainjector/comic + name = "DNA-Injector (Comic)" + desc = "Honk!" + datatype = DNA2_BUF_SE + value = 0xFFF + forcedmutation = TRUE + +/obj/item/dnainjector/comic/Initialize() + block = COMICBLOCK + ..() + +/obj/item/dnainjector/anticomic + name = "DNA-Injector (Ant-Comic)" + desc = "Honk...?" + datatype = DNA2_BUF_SE + value = 0x001 + forcedmutation = TRUE + +/obj/item/dnainjector/anticomic/Initialize() + block = COMICBLOCK + ..() diff --git a/code/game/objects/items/weapons/explosives.dm b/code/game/objects/items/weapons/explosives.dm index c35eed4fae5..c534a89277c 100644 --- a/code/game/objects/items/weapons/explosives.dm +++ b/code/game/objects/items/weapons/explosives.dm @@ -1,277 +1,277 @@ -/obj/item/grenade/plastic - name = "plastic explosive" - desc = "Used to put holes in specific areas without too much extra hole." - icon_state = "plastic-explosive0" - item_state = "plastic-explosive" - flags = NOBLUDGEON - det_time = 10 - display_timer = 0 - origin_tech = "syndicate=1" - toolspeed = 1 - var/atom/target = null - var/image_overlay = null - var/obj/item/assembly_holder/nadeassembly = null - var/assemblyattacher - -/obj/item/grenade/plastic/New() - image_overlay = image('icons/obj/grenade.dmi', "[item_state]2") - ..() - -/obj/item/grenade/plastic/Destroy() - QDEL_NULL(nadeassembly) - target = null - return ..() - -/obj/item/grenade/plastic/attackby(obj/item/I, mob/user, params) - if(!nadeassembly && istype(I, /obj/item/assembly_holder)) - var/obj/item/assembly_holder/A = I - if(!user.unEquip(I)) - return ..() - nadeassembly = A - A.master = src - A.loc = src - assemblyattacher = user.ckey - to_chat(user, "You add [A] to the [name].") - playsound(src, 'sound/weapons/tap.ogg', 20, 1) - update_icon() - return - if(nadeassembly && istype(I, /obj/item/wirecutters)) - playsound(src, I.usesound, 20, 1) - nadeassembly.loc = get_turf(src) - nadeassembly.master = null - nadeassembly = null - update_icon() - return - ..() - -//assembly stuff -/obj/item/grenade/plastic/receive_signal() - prime() - -/obj/item/grenade/plastic/Crossed(atom/movable/AM, oldloc) - if(nadeassembly) - nadeassembly.Crossed(AM, oldloc) - -/obj/item/grenade/plastic/on_found(mob/finder) - if(nadeassembly) - nadeassembly.on_found(finder) - -/obj/item/grenade/plastic/attack_self(mob/user) - if(nadeassembly) - nadeassembly.attack_self(user) - return - var/newtime = input(usr, "Please set the timer.", "Timer", det_time) as num - if(user.is_in_active_hand(src)) - newtime = Clamp(newtime, 10, 60000) - det_time = newtime - to_chat(user, "Timer set for [det_time] seconds.") - -/obj/item/grenade/plastic/afterattack(atom/movable/AM, mob/user, flag) - if (!flag) - return - if (istype(AM, /mob/living/carbon)) - return - to_chat(user, "You start planting the [src]. The timer is set to [det_time]...") - - if(do_after(user, 50 * toolspeed, target = AM)) - if(!user.unEquip(src)) - return - src.target = AM - loc = null - - message_admins("[key_name_admin(user)]([ADMIN_QUE(user,"?")]) ([ADMIN_FLW(user,"FLW")]) planted [src.name] on [target.name] at ([target.x],[target.y],[target.z] - JMP) with [det_time] second fuse",0,1) - log_game("[key_name(user)] planted [name] on [target.name] at ([target.x],[target.y],[target.z]) with [det_time] second fuse") - - target.overlays += image_overlay - if(!nadeassembly) - to_chat(user, "You plant the bomb. Timer counting down from [det_time].") - addtimer(CALLBACK(src, .proc/prime), det_time*10) - -/obj/item/grenade/plastic/suicide_act(mob/user) - message_admins("[key_name_admin(user)]([ADMIN_QUE(user,"?")]) ([ADMIN_FLW(user,"FLW")]) suicided with [src.name] at ([user.x],[user.y],[user.z] - JMP)",0,1) - log_game("[key_name(user)] suicided with [name] at ([user.x],[user.y],[user.z])") - user.visible_message("[user] activates the [name] and holds it above [user.p_their()] head! It looks like [user.p_theyre()] going out with a bang!") - var/message_say = "FOR NO RAISIN!" - if(user.mind) - if(user.mind.special_role) - var/role = lowertext(user.mind.special_role) - if(role == ROLE_TRAITOR || role == "syndicate" || role == "syndicate commando") - message_say = "FOR THE SYNDICATE!" - else if(role == ROLE_CHANGELING) - message_say = "FOR THE HIVE!" - else if(role == ROLE_CULTIST) - message_say = "FOR NARSIE!" - else if(role == ROLE_NINJA) - message_say = "FOR THE CLAN!" - else if(role == ROLE_WIZARD) - message_say = "FOR THE FEDERATION!" - else if(role == ROLE_REV || role == "head revolutionary") - message_say = "FOR THE REVOLOUTION!" - else if(role == "death commando" || role == ROLE_ERT) - message_say = "FOR NANOTRASEN!" - else if(role == ROLE_DEVIL) - message_say = "FOR INFERNO!" - user.say(message_say) - target = user - sleep(10) - prime() - user.gib() - return OBLITERATION - -/obj/item/grenade/plastic/update_icon() - if(nadeassembly) - icon_state = "[item_state]1" - else - icon_state = "[item_state]0" - -////////////////////////// -///// The Explosives ///// -////////////////////////// - -/obj/item/grenade/plastic/c4 - name = "C4" - desc = "Used to put holes in specific areas without too much extra hole. A saboteurs favourite." - -/obj/item/grenade/plastic/c4/prime() - var/turf/location - if(target) - if(!QDELETED(target)) - if(istype(target, /turf/)) - location = get_turf(target) // Set the explosion location to turf if planted directly on a wall or floor - else - location = get_atom_on_turf(target) // Otherwise, make sure we're blowing up what's on top of the turf - target.overlays -= image_overlay - else - location = get_atom_on_turf(src) - if(location) - explosion(location,0,0,3) - location.ex_act(2, target) - if(istype(target, /mob)) - var/mob/M = target - M.gib() - qdel(src) - -// X4 is an upgraded directional variant of c4 which is relatively safe to be standing next to. And much less safe to be standing on the other side of. -// C4 is intended to be used for infiltration, and destroying tech. X4 is intended to be used for heavy breaching and tight spaces. -// Intended to replace C4 for nukeops, and to be a randomdrop in surplus/random traitor purchases. - -/obj/item/grenade/plastic/x4 - name = "X4" - desc = "A specialized shaped high explosive breaching charge. Designed to be safer for the user, and less so, for the wall." - var/aim_dir = NORTH - icon_state = "plasticx40" - item_state = "plasticx4" - -/obj/item/grenade/plastic/x4/prime() - var/turf/location - if(target) - if(!QDELETED(target)) - if(istype(target, /turf/)) - location = get_turf(target) - else - location = get_atom_on_turf(target) - target.overlays -= image_overlay - else - location = get_atom_on_turf(src) - if(location) - if(target && target.density) - var/turf/T = get_step(location, aim_dir) - explosion(get_step(T, aim_dir),0,0,3) - explosion(T,0,2,0) - location.ex_act(2, target) - else - explosion(location, 0, 2, 3) - location.ex_act(2, target) - if(istype(target, /mob)) - var/mob/M = target - M.gib() - qdel(src) - -/obj/item/grenade/plastic/x4/afterattack(atom/movable/AM, mob/user, flag) - aim_dir = get_dir(user,AM) - ..() - -// Shaped charge -// Same blasting power as C4, but with the same idea as the X4 -- Everyone on one side of the wall is safe. - -/obj/item/grenade/plastic/c4_shaped - name = "C4 (shaped)" - desc = "A brick of C4 shaped to allow more precise breaching." - var/aim_dir = NORTH - -/obj/item/grenade/plastic/c4_shaped/prime() - var/turf/location - if(target) - if(!QDELETED(target)) - location = get_turf(target) - target.overlays -= image_overlay - else - location = get_turf(src) - if(location) - if(target && target.density) - var/turf/T = get_step(location, aim_dir) - explosion(get_step(T, aim_dir),0,0,3) - location.ex_act(2, target) - else - explosion(location, 0, 0, 3) - location.ex_act(2, target) - if(istype(target, /mob)) - var/mob/M = target - M.gib() - qdel(src) - -/obj/item/grenade/plastic/c4_shaped/afterattack(atom/movable/AM, mob/user, flag) - aim_dir = get_dir(user,AM) - ..() - -/obj/item/grenade/plastic/c4_shaped/flash - name = "C4 (flash)" - desc = "A C4 charge with an altered chemical composition, designed to blind and deafen the occupants of a room before breaching." - -/obj/item/grenade/plastic/c4_shaped/flash/prime() - if(target && target.density) - T = get_step(get_turf(target), aim_dir) - else if(target) - T = get_turf(target) - else - T = get_turf(src) - - var/obj/item/grenade/flashbang/CB = new/obj/item/grenade/flashbang(T) - CB.prime() - - ..() - -/obj/item/grenade/plastic/x4/thermite - name = "T4" - desc = "A wall breaching charge, containing fuel, metal oxide and metal powder mixed in just the right way. One hell of a combination. Effective against walls, ineffective against airlocks..." - det_time = 2 - icon_state = "t4breach0" - item_state = "t4breach" - -/obj/item/grenade/plastic/x4/thermite/prime() - var/turf/location - if(target) - if(!QDELETED(target)) - location = get_turf(target) - target.overlays -= image_overlay - else - location = get_turf(src) - if(location) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(8,0, location, aim_dir) - if(target && target.density) - var/turf/T = get_step(location, aim_dir) - for(var/turf/simulated/wall/W in range(1, location)) - W.thermitemelt(speed = 30) - addtimer(CALLBACK(null, .proc/explosion, T, 0, 0, 2), 3) - addtimer(CALLBACK(smoke, /datum/effect_system/smoke_spread/.proc/start), 3) - else - addtimer(CALLBACK(null, .proc/explosion, T, 0, 0, 2), 3) - addtimer(CALLBACK(smoke, /datum/effect_system/smoke_spread/.proc/start), 3) - - - if(isliving(target)) - var/mob/living/M = target - M.adjust_fire_stacks(2) - M.IgniteMob() - qdel(src) +/obj/item/grenade/plastic + name = "plastic explosive" + desc = "Used to put holes in specific areas without too much extra hole." + icon_state = "plastic-explosive0" + item_state = "plastic-explosive" + flags = NOBLUDGEON + det_time = 10 + display_timer = 0 + origin_tech = "syndicate=1" + toolspeed = 1 + var/atom/target = null + var/image_overlay = null + var/obj/item/assembly_holder/nadeassembly = null + var/assemblyattacher + +/obj/item/grenade/plastic/New() + image_overlay = image('icons/obj/grenade.dmi', "[item_state]2") + ..() + +/obj/item/grenade/plastic/Destroy() + QDEL_NULL(nadeassembly) + target = null + return ..() + +/obj/item/grenade/plastic/attackby(obj/item/I, mob/user, params) + if(!nadeassembly && istype(I, /obj/item/assembly_holder)) + var/obj/item/assembly_holder/A = I + if(!user.unEquip(I)) + return ..() + nadeassembly = A + A.master = src + A.loc = src + assemblyattacher = user.ckey + to_chat(user, "You add [A] to the [name].") + playsound(src, 'sound/weapons/tap.ogg', 20, 1) + update_icon() + return + if(nadeassembly && istype(I, /obj/item/wirecutters)) + playsound(src, I.usesound, 20, 1) + nadeassembly.loc = get_turf(src) + nadeassembly.master = null + nadeassembly = null + update_icon() + return + ..() + +//assembly stuff +/obj/item/grenade/plastic/receive_signal() + prime() + +/obj/item/grenade/plastic/Crossed(atom/movable/AM, oldloc) + if(nadeassembly) + nadeassembly.Crossed(AM, oldloc) + +/obj/item/grenade/plastic/on_found(mob/finder) + if(nadeassembly) + nadeassembly.on_found(finder) + +/obj/item/grenade/plastic/attack_self(mob/user) + if(nadeassembly) + nadeassembly.attack_self(user) + return + var/newtime = input(usr, "Please set the timer.", "Timer", det_time) as num + if(user.is_in_active_hand(src)) + newtime = Clamp(newtime, 10, 60000) + det_time = newtime + to_chat(user, "Timer set for [det_time] seconds.") + +/obj/item/grenade/plastic/afterattack(atom/movable/AM, mob/user, flag) + if (!flag) + return + if (istype(AM, /mob/living/carbon)) + return + to_chat(user, "You start planting the [src]. The timer is set to [det_time]...") + + if(do_after(user, 50 * toolspeed, target = AM)) + if(!user.unEquip(src)) + return + src.target = AM + loc = null + + message_admins("[key_name_admin(user)]([ADMIN_QUE(user,"?")]) ([ADMIN_FLW(user,"FLW")]) planted [src.name] on [target.name] at ([target.x],[target.y],[target.z] - JMP) with [det_time] second fuse",0,1) + log_game("[key_name(user)] planted [name] on [target.name] at ([target.x],[target.y],[target.z]) with [det_time] second fuse") + + target.overlays += image_overlay + if(!nadeassembly) + to_chat(user, "You plant the bomb. Timer counting down from [det_time].") + addtimer(CALLBACK(src, .proc/prime), det_time*10) + +/obj/item/grenade/plastic/suicide_act(mob/user) + message_admins("[key_name_admin(user)]([ADMIN_QUE(user,"?")]) ([ADMIN_FLW(user,"FLW")]) suicided with [src.name] at ([user.x],[user.y],[user.z] - JMP)",0,1) + log_game("[key_name(user)] suicided with [name] at ([user.x],[user.y],[user.z])") + user.visible_message("[user] activates the [name] and holds it above [user.p_their()] head! It looks like [user.p_theyre()] going out with a bang!") + var/message_say = "FOR NO RAISIN!" + if(user.mind) + if(user.mind.special_role) + var/role = lowertext(user.mind.special_role) + if(role == ROLE_TRAITOR || role == "syndicate" || role == "syndicate commando") + message_say = "FOR THE SYNDICATE!" + else if(role == ROLE_CHANGELING) + message_say = "FOR THE HIVE!" + else if(role == ROLE_CULTIST) + message_say = "FOR NARSIE!" + else if(role == ROLE_NINJA) + message_say = "FOR THE CLAN!" + else if(role == ROLE_WIZARD) + message_say = "FOR THE FEDERATION!" + else if(role == ROLE_REV || role == "head revolutionary") + message_say = "FOR THE REVOLOUTION!" + else if(role == "death commando" || role == ROLE_ERT) + message_say = "FOR NANOTRASEN!" + else if(role == ROLE_DEVIL) + message_say = "FOR INFERNO!" + user.say(message_say) + target = user + sleep(10) + prime() + user.gib() + return OBLITERATION + +/obj/item/grenade/plastic/update_icon() + if(nadeassembly) + icon_state = "[item_state]1" + else + icon_state = "[item_state]0" + +////////////////////////// +///// The Explosives ///// +////////////////////////// + +/obj/item/grenade/plastic/c4 + name = "C4" + desc = "Used to put holes in specific areas without too much extra hole. A saboteurs favourite." + +/obj/item/grenade/plastic/c4/prime() + var/turf/location + if(target) + if(!QDELETED(target)) + if(istype(target, /turf/)) + location = get_turf(target) // Set the explosion location to turf if planted directly on a wall or floor + else + location = get_atom_on_turf(target) // Otherwise, make sure we're blowing up what's on top of the turf + target.overlays -= image_overlay + else + location = get_atom_on_turf(src) + if(location) + explosion(location,0,0,3) + location.ex_act(2, target) + if(istype(target, /mob)) + var/mob/M = target + M.gib() + qdel(src) + +// X4 is an upgraded directional variant of c4 which is relatively safe to be standing next to. And much less safe to be standing on the other side of. +// C4 is intended to be used for infiltration, and destroying tech. X4 is intended to be used for heavy breaching and tight spaces. +// Intended to replace C4 for nukeops, and to be a randomdrop in surplus/random traitor purchases. + +/obj/item/grenade/plastic/x4 + name = "X4" + desc = "A specialized shaped high explosive breaching charge. Designed to be safer for the user, and less so, for the wall." + var/aim_dir = NORTH + icon_state = "plasticx40" + item_state = "plasticx4" + +/obj/item/grenade/plastic/x4/prime() + var/turf/location + if(target) + if(!QDELETED(target)) + if(istype(target, /turf/)) + location = get_turf(target) + else + location = get_atom_on_turf(target) + target.overlays -= image_overlay + else + location = get_atom_on_turf(src) + if(location) + if(target && target.density) + var/turf/T = get_step(location, aim_dir) + explosion(get_step(T, aim_dir),0,0,3) + explosion(T,0,2,0) + location.ex_act(2, target) + else + explosion(location, 0, 2, 3) + location.ex_act(2, target) + if(istype(target, /mob)) + var/mob/M = target + M.gib() + qdel(src) + +/obj/item/grenade/plastic/x4/afterattack(atom/movable/AM, mob/user, flag) + aim_dir = get_dir(user,AM) + ..() + +// Shaped charge +// Same blasting power as C4, but with the same idea as the X4 -- Everyone on one side of the wall is safe. + +/obj/item/grenade/plastic/c4_shaped + name = "C4 (shaped)" + desc = "A brick of C4 shaped to allow more precise breaching." + var/aim_dir = NORTH + +/obj/item/grenade/plastic/c4_shaped/prime() + var/turf/location + if(target) + if(!QDELETED(target)) + location = get_turf(target) + target.overlays -= image_overlay + else + location = get_turf(src) + if(location) + if(target && target.density) + var/turf/T = get_step(location, aim_dir) + explosion(get_step(T, aim_dir),0,0,3) + location.ex_act(2, target) + else + explosion(location, 0, 0, 3) + location.ex_act(2, target) + if(istype(target, /mob)) + var/mob/M = target + M.gib() + qdel(src) + +/obj/item/grenade/plastic/c4_shaped/afterattack(atom/movable/AM, mob/user, flag) + aim_dir = get_dir(user,AM) + ..() + +/obj/item/grenade/plastic/c4_shaped/flash + name = "C4 (flash)" + desc = "A C4 charge with an altered chemical composition, designed to blind and deafen the occupants of a room before breaching." + +/obj/item/grenade/plastic/c4_shaped/flash/prime() + if(target && target.density) + T = get_step(get_turf(target), aim_dir) + else if(target) + T = get_turf(target) + else + T = get_turf(src) + + var/obj/item/grenade/flashbang/CB = new/obj/item/grenade/flashbang(T) + CB.prime() + + ..() + +/obj/item/grenade/plastic/x4/thermite + name = "T4" + desc = "A wall breaching charge, containing fuel, metal oxide and metal powder mixed in just the right way. One hell of a combination. Effective against walls, ineffective against airlocks..." + det_time = 2 + icon_state = "t4breach0" + item_state = "t4breach" + +/obj/item/grenade/plastic/x4/thermite/prime() + var/turf/location + if(target) + if(!QDELETED(target)) + location = get_turf(target) + target.overlays -= image_overlay + else + location = get_turf(src) + if(location) + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(8,0, location, aim_dir) + if(target && target.density) + var/turf/T = get_step(location, aim_dir) + for(var/turf/simulated/wall/W in range(1, location)) + W.thermitemelt(speed = 30) + addtimer(CALLBACK(null, .proc/explosion, T, 0, 0, 2), 3) + addtimer(CALLBACK(smoke, /datum/effect_system/smoke_spread/.proc/start), 3) + else + addtimer(CALLBACK(null, .proc/explosion, T, 0, 0, 2), 3) + addtimer(CALLBACK(smoke, /datum/effect_system/smoke_spread/.proc/start), 3) + + + if(isliving(target)) + var/mob/living/M = target + M.adjust_fire_stacks(2) + M.IgniteMob() + qdel(src) diff --git a/code/game/objects/items/weapons/extinguisher.dm b/code/game/objects/items/weapons/extinguisher.dm index 3ea5fdff56a..f4ab41fab7b 100644 --- a/code/game/objects/items/weapons/extinguisher.dm +++ b/code/game/objects/items/weapons/extinguisher.dm @@ -1,180 +1,180 @@ -/obj/item/extinguisher - name = "fire extinguisher" - desc = "A traditional red fire extinguisher." - icon = 'icons/obj/items.dmi' - icon_state = "fire_extinguisher0" - item_state = "fire_extinguisher" - hitsound = 'sound/weapons/smash.ogg' - flags = CONDUCT - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 2 - throw_range = 7 - force = 10 - container_type = AMOUNT_VISIBLE - materials = list(MAT_METAL=90) - attack_verb = list("slammed", "whacked", "bashed", "thunked", "battered", "bludgeoned", "thrashed") - dog_fashion = /datum/dog_fashion/back - resistance_flags = FIRE_PROOF - var/max_water = 50 - var/last_use = 1.0 - var/safety = 1 - var/refilling = FALSE - var/sprite_name = "fire_extinguisher" - var/power = 5 //Maximum distance launched water will travel - var/precision = 0 //By default, turfs picked from a spray are random, set to 1 to make it always have at least one water effect per row - var/cooling_power = 2 //Sets the cooling_temperature of the water reagent datum inside of the extinguisher when it is refilled - -/obj/item/extinguisher/mini - name = "pocket fire extinguisher" - desc = "A light and compact fibreglass-framed model fire extinguisher." - icon_state = "miniFE0" - item_state = "miniFE" - hitsound = null //it is much lighter, after all. - flags = null //doesn't CONDUCT - throwforce = 2 - w_class = WEIGHT_CLASS_SMALL - force = 3.0 - materials = list() - max_water = 30 - sprite_name = "miniFE" - dog_fashion = null - -/obj/item/extinguisher/examine(mob/user) - . = ..() - . += "The safety is [safety ? "on" : "off"]." - - -/obj/item/extinguisher/New() - ..() - create_reagents(max_water) - reagents.add_reagent("water", max_water) - -/obj/item/extinguisher/attack_self(mob/user as mob) - safety = !safety - src.icon_state = "[sprite_name][!safety]" - src.desc = "The safety is [safety ? "on" : "off"]." - to_chat(user, "The safety is [safety ? "on" : "off"].") - return - -/obj/item/extinguisher/attack_obj(obj/O, mob/living/user) - if(AttemptRefill(O, user)) - refilling = TRUE - return FALSE - else - return ..() - -/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user) - if(istype(target, /obj/structure/reagent_dispensers/watertank) && target.Adjacent(user)) - var/safety_save = safety - safety = 1 - if(reagents.total_volume == reagents.maximum_volume) - to_chat(user, "\The [src] is already full!") - safety = safety_save - return 1 - var/obj/structure/reagent_dispensers/watertank/W = target - var/transferred = W.reagents.trans_to(src, max_water) - if(transferred > 0) - to_chat(user, "\The [src] has been refilled by [transferred] units") - playsound(src.loc, 'sound/effects/refill.ogg', 50, 1, -6) - for(var/datum/reagent/water/R in reagents.reagent_list) - R.cooling_temperature = cooling_power - else - to_chat(user, "\The [W] is empty!") - safety = safety_save - return 1 - else - return 0 - -/obj/item/extinguisher/afterattack(atom/target, mob/user , flag) - . = ..() - //TODO; Add support for reagents in water. - if(target.loc == user)//No more spraying yourself when putting your extinguisher away - return - - if(refilling) - refilling = FALSE - return - - if(!safety) - if(src.reagents.total_volume < 1) - to_chat(usr, "\The [src] is empty.") - return - - if(world.time < src.last_use + 20) - return - - src.last_use = world.time - - playsound(src.loc, 'sound/effects/extinguish.ogg', 75, 1, -3) - - var/direction = get_dir(src,target) - - if(usr.buckled && isobj(usr.buckled) && !usr.buckled.anchored ) - spawn(0) - var/obj/structure/chair/C = null - if(istype(usr.buckled, /obj/structure/chair)) - C = usr.buckled - var/obj/B = usr.buckled - var/movementdirection = turn(direction,180) - if(C) C.propelled = 4 - step(B, movementdirection) - sleep(1) - step(B, movementdirection) - if(C) C.propelled = 3 - sleep(1) - step(B, movementdirection) - sleep(1) - step(B, movementdirection) - if(C) C.propelled = 2 - sleep(2) - step(B, movementdirection) - if(C) C.propelled = 1 - sleep(2) - step(B, movementdirection) - if(C) C.propelled = 0 - sleep(3) - step(B, movementdirection) - sleep(3) - step(B, movementdirection) - sleep(3) - step(B, movementdirection) - - else user.newtonian_move(turn(direction, 180)) - - var/turf/T = get_turf(target) - var/turf/T1 = get_step(T,turn(direction, 90)) - var/turf/T2 = get_step(T,turn(direction, -90)) - var/list/the_targets = list(T,T1,T2) - if(precision) - var/turf/T3 = get_step(T1, turn(direction, 90)) - var/turf/T4 = get_step(T2,turn(direction, -90)) - the_targets = list(T,T1,T2,T3,T4) - - for(var/a=0, a<5, a++) - spawn(0) - var/obj/effect/particle_effect/water/W = new /obj/effect/particle_effect/water( get_turf(src) ) - var/turf/my_target = pick(the_targets) - if(precision) - the_targets -= my_target - var/datum/reagents/R = new/datum/reagents(5) - if(!W) return - W.reagents = R - R.my_atom = W - if(!W || !src) return - src.reagents.trans_to(W,1) - for(var/b=0, b<5, b++) - step_towards(W,my_target) - if(!W || !W.reagents) return - W.reagents.reaction(get_turf(W)) - for(var/atom/atm in get_turf(W)) - if(!W) return - W.reagents.reaction(atm) - if(isliving(atm)) //For extinguishing mobs on fire - var/mob/living/M = atm - M.ExtinguishMob() - - if(W.loc == my_target) break - sleep(2) - else - return ..() +/obj/item/extinguisher + name = "fire extinguisher" + desc = "A traditional red fire extinguisher." + icon = 'icons/obj/items.dmi' + icon_state = "fire_extinguisher0" + item_state = "fire_extinguisher" + hitsound = 'sound/weapons/smash.ogg' + flags = CONDUCT + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 2 + throw_range = 7 + force = 10 + container_type = AMOUNT_VISIBLE + materials = list(MAT_METAL=90) + attack_verb = list("slammed", "whacked", "bashed", "thunked", "battered", "bludgeoned", "thrashed") + dog_fashion = /datum/dog_fashion/back + resistance_flags = FIRE_PROOF + var/max_water = 50 + var/last_use = 1.0 + var/safety = 1 + var/refilling = FALSE + var/sprite_name = "fire_extinguisher" + var/power = 5 //Maximum distance launched water will travel + var/precision = 0 //By default, turfs picked from a spray are random, set to 1 to make it always have at least one water effect per row + var/cooling_power = 2 //Sets the cooling_temperature of the water reagent datum inside of the extinguisher when it is refilled + +/obj/item/extinguisher/mini + name = "pocket fire extinguisher" + desc = "A light and compact fibreglass-framed model fire extinguisher." + icon_state = "miniFE0" + item_state = "miniFE" + hitsound = null //it is much lighter, after all. + flags = null //doesn't CONDUCT + throwforce = 2 + w_class = WEIGHT_CLASS_SMALL + force = 3.0 + materials = list() + max_water = 30 + sprite_name = "miniFE" + dog_fashion = null + +/obj/item/extinguisher/examine(mob/user) + . = ..() + . += "The safety is [safety ? "on" : "off"]." + + +/obj/item/extinguisher/New() + ..() + create_reagents(max_water) + reagents.add_reagent("water", max_water) + +/obj/item/extinguisher/attack_self(mob/user as mob) + safety = !safety + src.icon_state = "[sprite_name][!safety]" + src.desc = "The safety is [safety ? "on" : "off"]." + to_chat(user, "The safety is [safety ? "on" : "off"].") + return + +/obj/item/extinguisher/attack_obj(obj/O, mob/living/user) + if(AttemptRefill(O, user)) + refilling = TRUE + return FALSE + else + return ..() + +/obj/item/extinguisher/proc/AttemptRefill(atom/target, mob/user) + if(istype(target, /obj/structure/reagent_dispensers/watertank) && target.Adjacent(user)) + var/safety_save = safety + safety = 1 + if(reagents.total_volume == reagents.maximum_volume) + to_chat(user, "\The [src] is already full!") + safety = safety_save + return 1 + var/obj/structure/reagent_dispensers/watertank/W = target + var/transferred = W.reagents.trans_to(src, max_water) + if(transferred > 0) + to_chat(user, "\The [src] has been refilled by [transferred] units") + playsound(src.loc, 'sound/effects/refill.ogg', 50, 1, -6) + for(var/datum/reagent/water/R in reagents.reagent_list) + R.cooling_temperature = cooling_power + else + to_chat(user, "\The [W] is empty!") + safety = safety_save + return 1 + else + return 0 + +/obj/item/extinguisher/afterattack(atom/target, mob/user , flag) + . = ..() + //TODO; Add support for reagents in water. + if(target.loc == user)//No more spraying yourself when putting your extinguisher away + return + + if(refilling) + refilling = FALSE + return + + if(!safety) + if(src.reagents.total_volume < 1) + to_chat(usr, "\The [src] is empty.") + return + + if(world.time < src.last_use + 20) + return + + src.last_use = world.time + + playsound(src.loc, 'sound/effects/extinguish.ogg', 75, 1, -3) + + var/direction = get_dir(src,target) + + if(usr.buckled && isobj(usr.buckled) && !usr.buckled.anchored ) + spawn(0) + var/obj/structure/chair/C = null + if(istype(usr.buckled, /obj/structure/chair)) + C = usr.buckled + var/obj/B = usr.buckled + var/movementdirection = turn(direction,180) + if(C) C.propelled = 4 + step(B, movementdirection) + sleep(1) + step(B, movementdirection) + if(C) C.propelled = 3 + sleep(1) + step(B, movementdirection) + sleep(1) + step(B, movementdirection) + if(C) C.propelled = 2 + sleep(2) + step(B, movementdirection) + if(C) C.propelled = 1 + sleep(2) + step(B, movementdirection) + if(C) C.propelled = 0 + sleep(3) + step(B, movementdirection) + sleep(3) + step(B, movementdirection) + sleep(3) + step(B, movementdirection) + + else user.newtonian_move(turn(direction, 180)) + + var/turf/T = get_turf(target) + var/turf/T1 = get_step(T,turn(direction, 90)) + var/turf/T2 = get_step(T,turn(direction, -90)) + var/list/the_targets = list(T,T1,T2) + if(precision) + var/turf/T3 = get_step(T1, turn(direction, 90)) + var/turf/T4 = get_step(T2,turn(direction, -90)) + the_targets = list(T,T1,T2,T3,T4) + + for(var/a=0, a<5, a++) + spawn(0) + var/obj/effect/particle_effect/water/W = new /obj/effect/particle_effect/water( get_turf(src) ) + var/turf/my_target = pick(the_targets) + if(precision) + the_targets -= my_target + var/datum/reagents/R = new/datum/reagents(5) + if(!W) return + W.reagents = R + R.my_atom = W + if(!W || !src) return + src.reagents.trans_to(W,1) + for(var/b=0, b<5, b++) + step_towards(W,my_target) + if(!W || !W.reagents) return + W.reagents.reaction(get_turf(W)) + for(var/atom/atm in get_turf(W)) + if(!W) return + W.reagents.reaction(atm) + if(isliving(atm)) //For extinguishing mobs on fire + var/mob/living/M = atm + M.ExtinguishMob() + + if(W.loc == my_target) break + sleep(2) + else + return ..() diff --git a/code/game/objects/items/weapons/fireworks.dm b/code/game/objects/items/weapons/fireworks.dm index 7db6eb18250..a2a0d6df552 100644 --- a/code/game/objects/items/weapons/fireworks.dm +++ b/code/game/objects/items/weapons/fireworks.dm @@ -59,4 +59,4 @@ obj/item/sparkler/attackby(obj/item/W,mob/user, params) new /obj/item/firework(src) new /obj/item/firework(src) new /obj/item/firework(src) - new /obj/item/firework(src) \ No newline at end of file + new /obj/item/firework(src) diff --git a/code/game/objects/items/weapons/flamethrower.dm b/code/game/objects/items/weapons/flamethrower.dm index fc1a0ca6c12..b18ec5ee0b6 100644 --- a/code/game/objects/items/weapons/flamethrower.dm +++ b/code/game/objects/items/weapons/flamethrower.dm @@ -250,4 +250,4 @@ location.hotspot_expose(700, 2) /obj/item/assembly/igniter/proc/ignite_turf(obj/item/flamethrower/F, turf/simulated/location, release_amount = 0.05) - F.default_ignite(location, release_amount) \ No newline at end of file + F.default_ignite(location, release_amount) diff --git a/code/game/objects/items/weapons/grenades/atmosgrenade.dm b/code/game/objects/items/weapons/grenades/atmosgrenade.dm index cc2008b539a..6ba46d3442e 100644 --- a/code/game/objects/items/weapons/grenades/atmosgrenade.dm +++ b/code/game/objects/items/weapons/grenades/atmosgrenade.dm @@ -51,4 +51,4 @@ L.adjustStaminaLoss(stamina_damage) L.apply_effect(rad_damage, IRRADIATE) L.adjust_bodytemperature(-230) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/weapons/grenades/chem_grenade.dm b/code/game/objects/items/weapons/grenades/chem_grenade.dm index 8b08a4dc5b0..79612afd1f3 100644 --- a/code/game/objects/items/weapons/grenades/chem_grenade.dm +++ b/code/game/objects/items/weapons/grenades/chem_grenade.dm @@ -1,591 +1,591 @@ -#define EMPTY 0 -#define WIRED 1 -#define READY 2 - -/obj/item/grenade/chem_grenade - name = "grenade casing" - desc = "A do it yourself grenade casing!" - icon_state = "chemg" - item_state = "flashbang" - var/bomb_state = "chembomb" - var/payload_name = null // used for spawned grenades - w_class = WEIGHT_CLASS_SMALL - force = 2 - var/prime_sound = 'sound/items/screwdriver2.ogg' - var/stage = EMPTY - var/list/beakers = list() - var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) - var/affected_area = 3 - var/obj/item/assembly_holder/nadeassembly = null - var/label = null - var/assemblyattacher - var/ignition_temp = 10 // The amount of heat added to the reagents when this grenade goes off. - var/threatscale = 1 // Used by advanced grenades to make them slightly more worthy. - var/no_splash = FALSE //If the grenade deletes even if it has no reagents to splash with. Used for slime core reactions. - var/contained = "" // For logging - var/cores = "" // Also for logging - -/obj/item/grenade/chem_grenade/New() - ..() - create_reagents(1000) - if(payload_name) - payload_name += " " // formatting, ignore me - update_icon() - -/obj/item/grenade/chem_grenade/Destroy() - QDEL_NULL(nadeassembly) - QDEL_LIST(beakers) - return ..() - -/obj/item/grenade/chem_grenade/examine(mob/user) - . = ..() - display_timer = (stage == READY && !nadeassembly) //show/hide the timer based on assembly state - - - -/obj/item/grenade/chem_grenade/proc/get_trigger() - if(!nadeassembly) return null - for(var/obj/O in list(nadeassembly.a_left, nadeassembly.a_right)) - if(!O || istype(O,/obj/item/assembly/igniter)) continue - return O - return null - - -/obj/item/grenade/chem_grenade/proc/update_overlays() - underlays = list() - if(nadeassembly) - underlays += "[nadeassembly.a_left.icon_state]_left" - for(var/O in nadeassembly.a_left.attached_overlays) - underlays += "[O]_l" - underlays += "[nadeassembly.a_right.icon_state]_right" - for(var/O in nadeassembly.a_right.attached_overlays) - underlays += "[O]_r" - -/obj/item/grenade/chem_grenade/update_icon() - if(nadeassembly) - icon = 'icons/obj/assemblies/new_assemblies.dmi' - icon_state = bomb_state - update_overlays() - var/obj/item/assembly/A = get_trigger() - if(stage != READY) - name = "bomb casing[label]" - else - if(!A) - name = "[payload_name]de-fused bomb[label]" // this should not actually happen - else - name = payload_name + A.bomb_name + label // time bombs, remote mines, etc - else - icon = 'icons/obj/grenade.dmi' - icon_state = initial(icon_state) - overlays = list() - switch(stage) - if(EMPTY) - name = "grenade casing[label]" - if(WIRED) - icon_state += "_ass" - name = "grenade casing[label]" - if(READY) - if(active) - icon_state += "_active" - else - icon_state += "_locked" - name = payload_name + "grenade" + label - - -/obj/item/grenade/chem_grenade/attack_self(mob/user) - if(stage == READY && !active) - var/turf/bombturf = get_turf(src) - var/area/A = get_area(bombturf) - if(nadeassembly) - nadeassembly.attack_self(user) - update_icon() - else if(clown_check(user)) - // This used to go before the assembly check, but that has absolutely zero to do with priming the damn thing. You could spam the admins with it. - message_admins("[key_name_admin(usr)] has primed a [name] for detonation at [A.name] (JMP) [contained].") - log_game("[key_name(usr)] has primed a [name] for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) [contained].") - investigate_log("[key_name(usr)] has primed a [name] for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z])[contained].", INVESTIGATE_BOMB) - to_chat(user, "You prime the [name]! [det_time / 10] second\s!") - playsound(user.loc, 'sound/weapons/armbomb.ogg', 60, 1) - active = 1 - update_icon() - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.throw_mode_on() - spawn(det_time) - prime() - -/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - var/obj/item/projectile/P = hitby - if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15)) - owner.visible_message("[attack_text] hits [owner]'s [src], setting it off! What a shot!") - var/turf/T = get_turf(src) - log_game("A projectile ([hitby]) detonated a grenade held by [key_name(owner)] at [COORD(T)]") - message_admins("A projectile ([hitby]) detonated a grenade held by [key_name_admin(owner)] at [ADMIN_COORDJMP(T)]") - prime() - return 1 //It hit the grenade, not them - -/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) - if(istype(I,/obj/item/hand_labeler)) - var/obj/item/hand_labeler/HL = I - if(length(HL.label)) - label = " ([HL.label])" - return 0 - else - if(label) - label = null - update_icon() - to_chat(user, "You remove the label from [src].") - return 1 - if(istype(I, /obj/item/screwdriver)) - if(stage == WIRED) - if(beakers.len) - to_chat(user, "You lock the assembly.") - playsound(loc, prime_sound, 25, -3) - stage = READY - update_icon() - contained = "" - cores = "" // clear them out so no recursive logging by accidentally - for(var/obj/O in beakers) - if(!O.reagents) continue - if(istype(O,/obj/item/slime_extract)) - cores += " [O]" - for(var/reagent in O.reagents.reagent_list) - contained += " [reagent] " - if(contained) - if(cores) - contained = "\[[cores];[contained]\]" - else - contained = "\[[contained]\]" - var/turf/bombturf = get_turf(loc) - var/area/A = bombturf.loc - message_admins("[key_name_admin(usr)] has completed [name] at [A.name] (JMP) [contained].") - log_game("[key_name(usr)] has completed [name] at [bombturf.x], [bombturf.y], [bombturf.z]. [contained]") - else - to_chat(user, "You need to add at least one beaker before locking the assembly.") - else if(stage == READY && !nadeassembly) - det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 - to_chat(user, "You modify the time delay. It's set for [det_time / 10] second\s.") - else if(stage == EMPTY) - to_chat(user, "You need to add an activation mechanism.") - - else if(stage == WIRED && is_type_in_list(I, allowed_containers)) - if(beakers.len == 2) - to_chat(user, "[src] can not hold more containers.") - return - else - if(I.reagents.total_volume) - to_chat(user, "You add [I] to the assembly.") - user.drop_item() - I.loc = src - beakers += I - else - to_chat(user, "[I] is empty.") - - else if(stage == EMPTY && istype(I, /obj/item/assembly_holder)) - var/obj/item/assembly_holder/A = I - if(!A.secured) - return - if(isigniter(A.a_left) == isigniter(A.a_right)) //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it - return - - user.drop_item() - nadeassembly = A - A.master = src - A.loc = src - assemblyattacher = user.ckey - stage = WIRED - to_chat(user, "You add [A] to [src]!") - update_icon() - - else if(stage == EMPTY && istype(I, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = I - C.use(1) - - stage = WIRED - to_chat(user, "You rig [src].") - update_icon() - - else if(stage == READY && istype(I, /obj/item/wirecutters)) - to_chat(user, "You unlock the assembly.") - stage = WIRED - update_icon() - - else if(stage == WIRED && istype(I, /obj/item/wrench)) - to_chat(user, "You open the grenade and remove the contents.") - stage = EMPTY - payload_name = null - label = null - if(nadeassembly) - nadeassembly.loc = get_turf(src) - nadeassembly.master = null - nadeassembly = null - if(beakers.len) - for(var/obj/O in beakers) - O.loc = get_turf(src) - beakers = list() - update_icon() - - -//assembly stuff -/obj/item/grenade/chem_grenade/receive_signal() - prime() - -/obj/item/grenade/chem_grenade/HasProximity(atom/movable/AM) - if(nadeassembly) - nadeassembly.HasProximity(AM) - -/obj/item/grenade/chem_grenade/Move() // prox sensors and infrared care about this - ..() - if(nadeassembly) - nadeassembly.process_movement() - -/obj/item/grenade/chem_grenade/pickup() - . = ..() - if(nadeassembly) - nadeassembly.process_movement() - -/obj/item/grenade/chem_grenade/Crossed(atom/movable/AM, oldloc) - if(nadeassembly) - nadeassembly.Crossed(AM, oldloc) - -/obj/item/grenade/chem_grenade/on_found(mob/finder) - if(nadeassembly) - nadeassembly.on_found(finder) - -/obj/item/grenade/chem_grenade/hear_talk(mob/living/M, list/message_pieces) - if(nadeassembly) - nadeassembly.hear_talk(M, message_pieces) - -/obj/item/grenade/chem_grenade/hear_message(mob/living/M, msg) - if(nadeassembly) - nadeassembly.hear_message(M, msg) - -/obj/item/grenade/chem_grenade/Bump() - ..() - if(nadeassembly) - nadeassembly.process_movement() - -/obj/item/grenade/chem_grenade/throw_impact() // called when a throw stops - ..() - if(nadeassembly) - nadeassembly.process_movement() - - -/obj/item/grenade/chem_grenade/prime() - if(stage != READY) - return - - var/list/datum/reagents/reactants = list() - for(var/obj/item/reagent_containers/glass/G in beakers) - reactants += G.reagents - - if(!chem_splash(get_turf(src), affected_area, reactants, ignition_temp, threatscale) && !no_splash) - playsound(loc, 'sound/items/screwdriver2.ogg', 50, 1) - if(beakers.len) - for(var/obj/O in beakers) - O.forceMove(get_turf(src)) - beakers = list() - stage = EMPTY - update_icon() - return - - if(nadeassembly) - var/mob/M = get_mob_by_ckey(assemblyattacher) - var/mob/last = get_mob_by_ckey(nadeassembly.fingerprintslast) - var/turf/T = get_turf(src) - var/area/A = get_area(T) - message_admins("grenade primed by an assembly, attached by [key_name_admin(M)] and last touched by [key_name_admin(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] (JMP). [contained]") - log_game("grenade primed by an assembly, attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] ([T.x], [T.y], [T.z]) [contained]") - - update_mob() - - qdel(src) - -/obj/item/grenade/chem_grenade/proc/CreateDefaultTrigger(var/typekey) - if(ispath(typekey,/obj/item/assembly)) - nadeassembly = new(src) - nadeassembly.a_left = new /obj/item/assembly/igniter(nadeassembly) - nadeassembly.a_left.holder = nadeassembly - nadeassembly.a_left.secured = 1 - nadeassembly.a_right = new typekey(nadeassembly) - if(!nadeassembly.a_right.secured) - nadeassembly.a_right.toggle_secure() // necessary because fuxing prock_sensors - nadeassembly.a_right.holder = nadeassembly - nadeassembly.secured = 1 - nadeassembly.master = src - nadeassembly.update_icon() - stage = READY - update_icon() - - -//Large chem grenades accept slime cores and use the appropriately. -/obj/item/grenade/chem_grenade/large - name = "large grenade casing" - desc = "A custom made large grenade. It affects a larger area." - icon_state = "large_grenade" - bomb_state = "largebomb" - allowed_containers = list(/obj/item/reagent_containers/glass,/obj/item/reagent_containers/food/condiment, - /obj/item/reagent_containers/food/drinks) - origin_tech = "combat=3;engineering=3" - affected_area = 5 - ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades. - threatscale = 1.1 // 10% more effective. - -/obj/item/grenade/chem_grenade/large/prime() - if(stage != READY) - return - - for(var/obj/item/slime_extract/S in beakers) - if(S.Uses) - for(var/obj/item/reagent_containers/glass/G in beakers) - G.reagents.trans_to(S, G.reagents.total_volume) - - //If there is still a core (sometimes it's used up) - //and there are reagents left, behave normally, - //otherwise drop it on the ground for timed reactions like gold. - - if(S) - if(S.reagents && S.reagents.total_volume) - for(var/obj/item/reagent_containers/glass/G in beakers) - S.reagents.trans_to(G, S.reagents.total_volume) - else - S.forceMove(get_turf(src)) - no_splash = TRUE - ..() - - - //I tried to just put it in the allowed_containers list but - //if you do that it must have reagents. If you're going to - //make a special case you might as well do it explicitly. -Sayu -/obj/item/grenade/chem_grenade/large/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/slime_extract) && stage == WIRED) - to_chat(user, "You add [I] to the assembly.") - user.drop_item() - I.loc = src - beakers += I - else - return ..() - -/obj/item/grenade/chem_grenade/cryo // Intended for rare cryogenic mixes. Cools the area moderately upon detonation. - name = "cryo grenade" - desc = "A custom made cryogenic grenade. It rapidly cools its contents upon detonation." - icon_state = "cryog" - affected_area = 2 - ignition_temp = -100 - -/obj/item/grenade/chem_grenade/pyro // Intended for pyrotechnical mixes. Produces a small fire upon detonation, igniting potentially flammable mixtures. - name = "pyro grenade" - desc = "A custom made pyrotechnical grenade. It heats up and ignites its contents upon detonation." - icon_state = "pyrog" - origin_tech = "combat=4;engineering=4" - affected_area = 3 - ignition_temp = 500 // This is enough to expose a hotspot. - -/obj/item/grenade/chem_grenade/adv_release // Intended for weaker, but longer lasting effects. Could have some interesting uses. - name = "advanced release grenade" - desc = "A custom made advanced release grenade. It is able to be detonated more than once. Can be configured using a multitool." - icon_state = "timeg" - origin_tech = "combat=3;engineering=4" - var/unit_spread = 10 // Amount of units per repeat. Can be altered with a multitool. - -/obj/item/grenade/chem_grenade/adv_release/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - switch(unit_spread) - if(0 to 24) - unit_spread += 5 - if(25 to 99) - unit_spread += 25 - else - unit_spread = 5 - to_chat(user, " You set the time release to [unit_spread] units per detonation.") - -/obj/item/grenade/chem_grenade/adv_release/prime() - if(stage != READY) - return - - var/total_volume = 0 - for(var/obj/item/reagent_containers/RC in beakers) - total_volume += RC.reagents.total_volume - if(!total_volume) - qdel(src) - qdel(nadeassembly) - return - var/fraction = unit_spread/total_volume - var/datum/reagents/reactants = new(unit_spread) - reactants.my_atom = src - for(var/obj/item/reagent_containers/RC in beakers) - RC.reagents.trans_to(reactants, RC.reagents.total_volume*fraction, threatscale, 1, 1) - chem_splash(get_turf(src), affected_area, list(reactants), ignition_temp, threatscale) - - if(nadeassembly) - var/mob/M = get_mob_by_ckey(assemblyattacher) - var/mob/last = get_mob_by_ckey(nadeassembly.fingerprintslast) - var/turf/T = get_turf(src) - var/area/A = get_area(T) - message_admins("grenade primed by an assembly, attached by [key_name_admin(M)] and last touched by [key_name_admin(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] (JMP).") - log_game("grenade primed by an assembly, attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] ([T.x], [T.y], [T.z])") - else - addtimer(CALLBACK(src, .proc/prime), det_time) - var/turf/DT = get_turf(src) - var/area/DA = get_area(DT) - log_game("A grenade detonated at [DA.name] ([DT.x], [DT.y], [DT.z])") - -/obj/item/grenade/chem_grenade/metalfoam - payload_name = "metal foam" - desc = "Used for emergency sealing of air breaches." - stage = READY - -/obj/item/grenade/chem_grenade/metalfoam/New() - ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent("aluminum", 30) - B2.reagents.add_reagent("fluorosurfactant", 10) - B2.reagents.add_reagent("sacid", 10) - - beakers += B1 - beakers += B2 - update_icon() - - -/obj/item/grenade/chem_grenade/firefighting - payload_name = "fire fighting grenade" - desc = "Can help to put out dangerous fires from a distance." - stage = READY - -/obj/item/grenade/chem_grenade/firefighting/New() - ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent("firefighting_foam", 30) - B2.reagents.add_reagent("firefighting_foam", 30) - - beakers += B1 - beakers += B2 - update_icon() - -/obj/item/grenade/chem_grenade/incendiary - payload_name = "incendiary" - desc = "Used for clearing rooms of living things." - stage = READY - -/obj/item/grenade/chem_grenade/incendiary/New() - ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent("phosphorus", 25) - B2.reagents.add_reagent("plasma", 25) - B2.reagents.add_reagent("sacid", 25) - - - beakers += B1 - beakers += B2 - update_icon() - - -/obj/item/grenade/chem_grenade/antiweed - payload_name = "weed killer" - desc = "Used for purging large areas of invasive plant species. Contents under pressure. Do not directly inhale contents." - stage = READY - -/obj/item/grenade/chem_grenade/antiweed/New() - ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent("atrazine", 30) - B1.reagents.add_reagent("potassium", 20) - B2.reagents.add_reagent("phosphorus", 20) - B2.reagents.add_reagent("sugar", 20) - B2.reagents.add_reagent("atrazine", 10) - - beakers += B1 - beakers += B2 - update_icon() - - -/obj/item/grenade/chem_grenade/cleaner - payload_name = "cleaner" - desc = "BLAM!-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." - stage = READY - -/obj/item/grenade/chem_grenade/cleaner/New() - ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent("fluorosurfactant", 40) - B2.reagents.add_reagent("cleaner", 10) - B2.reagents.add_reagent("water", 40) //when you make pre-designed foam reactions that carry the reagents, always add water last - - beakers += B1 - beakers += B2 - update_icon() - - -/obj/item/grenade/chem_grenade/teargas - payload_name = "teargas" - desc = "Used for nonlethal riot control. Contents under pressure. Do not directly inhale contents." - stage = READY - -/obj/item/grenade/chem_grenade/teargas/New() - ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent("condensedcapsaicin", 25) - B1.reagents.add_reagent("potassium", 25) - B2.reagents.add_reagent("phosphorus", 25) - B2.reagents.add_reagent("sugar", 25) - - beakers += B1 - beakers += B2 - update_icon() - -/obj/item/grenade/chem_grenade/facid - payload_name = "acid smoke" - desc = "Use to chew up opponents from the inside out." - stage = READY - -/obj/item/grenade/chem_grenade/facid/New() - ..() - var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) - - B1.reagents.add_reagent("facid", 80) - B1.reagents.add_reagent("potassium", 20) - B2.reagents.add_reagent("phosphorus", 20) - B2.reagents.add_reagent("sugar", 20) - B2.reagents.add_reagent("facid", 60) - - beakers += B1 - beakers += B2 - update_icon() - -/obj/item/grenade/chem_grenade/saringas - payload_name = "sarin gas" - desc = "Contains sarin gas; extremely deadly and fast acting; use with extreme caution." - stage = READY - -/obj/item/grenade/chem_grenade/saringas/New() - ..() - var/obj/item/reagent_containers/glass/beaker/B1 = new(src) - var/obj/item/reagent_containers/glass/beaker/B2 = new(src) - - B1.reagents.add_reagent("sarin", 25) - B1.reagents.add_reagent("potassium", 25) - B2.reagents.add_reagent("phosphorus", 25) - B2.reagents.add_reagent("sugar", 25) - - beakers += B1 - beakers += B2 - update_icon() - -#undef EMPTY -#undef WIRED -#undef READY +#define EMPTY 0 +#define WIRED 1 +#define READY 2 + +/obj/item/grenade/chem_grenade + name = "grenade casing" + desc = "A do it yourself grenade casing!" + icon_state = "chemg" + item_state = "flashbang" + var/bomb_state = "chembomb" + var/payload_name = null // used for spawned grenades + w_class = WEIGHT_CLASS_SMALL + force = 2 + var/prime_sound = 'sound/items/screwdriver2.ogg' + var/stage = EMPTY + var/list/beakers = list() + var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) + var/affected_area = 3 + var/obj/item/assembly_holder/nadeassembly = null + var/label = null + var/assemblyattacher + var/ignition_temp = 10 // The amount of heat added to the reagents when this grenade goes off. + var/threatscale = 1 // Used by advanced grenades to make them slightly more worthy. + var/no_splash = FALSE //If the grenade deletes even if it has no reagents to splash with. Used for slime core reactions. + var/contained = "" // For logging + var/cores = "" // Also for logging + +/obj/item/grenade/chem_grenade/New() + ..() + create_reagents(1000) + if(payload_name) + payload_name += " " // formatting, ignore me + update_icon() + +/obj/item/grenade/chem_grenade/Destroy() + QDEL_NULL(nadeassembly) + QDEL_LIST(beakers) + return ..() + +/obj/item/grenade/chem_grenade/examine(mob/user) + . = ..() + display_timer = (stage == READY && !nadeassembly) //show/hide the timer based on assembly state + + + +/obj/item/grenade/chem_grenade/proc/get_trigger() + if(!nadeassembly) return null + for(var/obj/O in list(nadeassembly.a_left, nadeassembly.a_right)) + if(!O || istype(O,/obj/item/assembly/igniter)) continue + return O + return null + + +/obj/item/grenade/chem_grenade/proc/update_overlays() + underlays = list() + if(nadeassembly) + underlays += "[nadeassembly.a_left.icon_state]_left" + for(var/O in nadeassembly.a_left.attached_overlays) + underlays += "[O]_l" + underlays += "[nadeassembly.a_right.icon_state]_right" + for(var/O in nadeassembly.a_right.attached_overlays) + underlays += "[O]_r" + +/obj/item/grenade/chem_grenade/update_icon() + if(nadeassembly) + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = bomb_state + update_overlays() + var/obj/item/assembly/A = get_trigger() + if(stage != READY) + name = "bomb casing[label]" + else + if(!A) + name = "[payload_name]de-fused bomb[label]" // this should not actually happen + else + name = payload_name + A.bomb_name + label // time bombs, remote mines, etc + else + icon = 'icons/obj/grenade.dmi' + icon_state = initial(icon_state) + overlays = list() + switch(stage) + if(EMPTY) + name = "grenade casing[label]" + if(WIRED) + icon_state += "_ass" + name = "grenade casing[label]" + if(READY) + if(active) + icon_state += "_active" + else + icon_state += "_locked" + name = payload_name + "grenade" + label + + +/obj/item/grenade/chem_grenade/attack_self(mob/user) + if(stage == READY && !active) + var/turf/bombturf = get_turf(src) + var/area/A = get_area(bombturf) + if(nadeassembly) + nadeassembly.attack_self(user) + update_icon() + else if(clown_check(user)) + // This used to go before the assembly check, but that has absolutely zero to do with priming the damn thing. You could spam the admins with it. + message_admins("[key_name_admin(usr)] has primed a [name] for detonation at [A.name] (JMP) [contained].") + log_game("[key_name(usr)] has primed a [name] for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]) [contained].") + investigate_log("[key_name(usr)] has primed a [name] for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z])[contained].", INVESTIGATE_BOMB) + to_chat(user, "You prime the [name]! [det_time / 10] second\s!") + playsound(user.loc, 'sound/weapons/armbomb.ogg', 60, 1) + active = 1 + update_icon() + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.throw_mode_on() + spawn(det_time) + prime() + +/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + var/obj/item/projectile/P = hitby + if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15)) + owner.visible_message("[attack_text] hits [owner]'s [src], setting it off! What a shot!") + var/turf/T = get_turf(src) + log_game("A projectile ([hitby]) detonated a grenade held by [key_name(owner)] at [COORD(T)]") + message_admins("A projectile ([hitby]) detonated a grenade held by [key_name_admin(owner)] at [ADMIN_COORDJMP(T)]") + prime() + return 1 //It hit the grenade, not them + +/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) + if(istype(I,/obj/item/hand_labeler)) + var/obj/item/hand_labeler/HL = I + if(length(HL.label)) + label = " ([HL.label])" + return 0 + else + if(label) + label = null + update_icon() + to_chat(user, "You remove the label from [src].") + return 1 + if(istype(I, /obj/item/screwdriver)) + if(stage == WIRED) + if(beakers.len) + to_chat(user, "You lock the assembly.") + playsound(loc, prime_sound, 25, -3) + stage = READY + update_icon() + contained = "" + cores = "" // clear them out so no recursive logging by accidentally + for(var/obj/O in beakers) + if(!O.reagents) continue + if(istype(O,/obj/item/slime_extract)) + cores += " [O]" + for(var/reagent in O.reagents.reagent_list) + contained += " [reagent] " + if(contained) + if(cores) + contained = "\[[cores];[contained]\]" + else + contained = "\[[contained]\]" + var/turf/bombturf = get_turf(loc) + var/area/A = bombturf.loc + message_admins("[key_name_admin(usr)] has completed [name] at [A.name] (JMP) [contained].") + log_game("[key_name(usr)] has completed [name] at [bombturf.x], [bombturf.y], [bombturf.z]. [contained]") + else + to_chat(user, "You need to add at least one beaker before locking the assembly.") + else if(stage == READY && !nadeassembly) + det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 + to_chat(user, "You modify the time delay. It's set for [det_time / 10] second\s.") + else if(stage == EMPTY) + to_chat(user, "You need to add an activation mechanism.") + + else if(stage == WIRED && is_type_in_list(I, allowed_containers)) + if(beakers.len == 2) + to_chat(user, "[src] can not hold more containers.") + return + else + if(I.reagents.total_volume) + to_chat(user, "You add [I] to the assembly.") + user.drop_item() + I.loc = src + beakers += I + else + to_chat(user, "[I] is empty.") + + else if(stage == EMPTY && istype(I, /obj/item/assembly_holder)) + var/obj/item/assembly_holder/A = I + if(!A.secured) + return + if(isigniter(A.a_left) == isigniter(A.a_right)) //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it + return + + user.drop_item() + nadeassembly = A + A.master = src + A.loc = src + assemblyattacher = user.ckey + stage = WIRED + to_chat(user, "You add [A] to [src]!") + update_icon() + + else if(stage == EMPTY && istype(I, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = I + C.use(1) + + stage = WIRED + to_chat(user, "You rig [src].") + update_icon() + + else if(stage == READY && istype(I, /obj/item/wirecutters)) + to_chat(user, "You unlock the assembly.") + stage = WIRED + update_icon() + + else if(stage == WIRED && istype(I, /obj/item/wrench)) + to_chat(user, "You open the grenade and remove the contents.") + stage = EMPTY + payload_name = null + label = null + if(nadeassembly) + nadeassembly.loc = get_turf(src) + nadeassembly.master = null + nadeassembly = null + if(beakers.len) + for(var/obj/O in beakers) + O.loc = get_turf(src) + beakers = list() + update_icon() + + +//assembly stuff +/obj/item/grenade/chem_grenade/receive_signal() + prime() + +/obj/item/grenade/chem_grenade/HasProximity(atom/movable/AM) + if(nadeassembly) + nadeassembly.HasProximity(AM) + +/obj/item/grenade/chem_grenade/Move() // prox sensors and infrared care about this + ..() + if(nadeassembly) + nadeassembly.process_movement() + +/obj/item/grenade/chem_grenade/pickup() + . = ..() + if(nadeassembly) + nadeassembly.process_movement() + +/obj/item/grenade/chem_grenade/Crossed(atom/movable/AM, oldloc) + if(nadeassembly) + nadeassembly.Crossed(AM, oldloc) + +/obj/item/grenade/chem_grenade/on_found(mob/finder) + if(nadeassembly) + nadeassembly.on_found(finder) + +/obj/item/grenade/chem_grenade/hear_talk(mob/living/M, list/message_pieces) + if(nadeassembly) + nadeassembly.hear_talk(M, message_pieces) + +/obj/item/grenade/chem_grenade/hear_message(mob/living/M, msg) + if(nadeassembly) + nadeassembly.hear_message(M, msg) + +/obj/item/grenade/chem_grenade/Bump() + ..() + if(nadeassembly) + nadeassembly.process_movement() + +/obj/item/grenade/chem_grenade/throw_impact() // called when a throw stops + ..() + if(nadeassembly) + nadeassembly.process_movement() + + +/obj/item/grenade/chem_grenade/prime() + if(stage != READY) + return + + var/list/datum/reagents/reactants = list() + for(var/obj/item/reagent_containers/glass/G in beakers) + reactants += G.reagents + + if(!chem_splash(get_turf(src), affected_area, reactants, ignition_temp, threatscale) && !no_splash) + playsound(loc, 'sound/items/screwdriver2.ogg', 50, 1) + if(beakers.len) + for(var/obj/O in beakers) + O.forceMove(get_turf(src)) + beakers = list() + stage = EMPTY + update_icon() + return + + if(nadeassembly) + var/mob/M = get_mob_by_ckey(assemblyattacher) + var/mob/last = get_mob_by_ckey(nadeassembly.fingerprintslast) + var/turf/T = get_turf(src) + var/area/A = get_area(T) + message_admins("grenade primed by an assembly, attached by [key_name_admin(M)] and last touched by [key_name_admin(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] (JMP). [contained]") + log_game("grenade primed by an assembly, attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] ([T.x], [T.y], [T.z]) [contained]") + + update_mob() + + qdel(src) + +/obj/item/grenade/chem_grenade/proc/CreateDefaultTrigger(var/typekey) + if(ispath(typekey,/obj/item/assembly)) + nadeassembly = new(src) + nadeassembly.a_left = new /obj/item/assembly/igniter(nadeassembly) + nadeassembly.a_left.holder = nadeassembly + nadeassembly.a_left.secured = 1 + nadeassembly.a_right = new typekey(nadeassembly) + if(!nadeassembly.a_right.secured) + nadeassembly.a_right.toggle_secure() // necessary because fuxing prock_sensors + nadeassembly.a_right.holder = nadeassembly + nadeassembly.secured = 1 + nadeassembly.master = src + nadeassembly.update_icon() + stage = READY + update_icon() + + +//Large chem grenades accept slime cores and use the appropriately. +/obj/item/grenade/chem_grenade/large + name = "large grenade casing" + desc = "A custom made large grenade. It affects a larger area." + icon_state = "large_grenade" + bomb_state = "largebomb" + allowed_containers = list(/obj/item/reagent_containers/glass,/obj/item/reagent_containers/food/condiment, + /obj/item/reagent_containers/food/drinks) + origin_tech = "combat=3;engineering=3" + affected_area = 5 + ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades. + threatscale = 1.1 // 10% more effective. + +/obj/item/grenade/chem_grenade/large/prime() + if(stage != READY) + return + + for(var/obj/item/slime_extract/S in beakers) + if(S.Uses) + for(var/obj/item/reagent_containers/glass/G in beakers) + G.reagents.trans_to(S, G.reagents.total_volume) + + //If there is still a core (sometimes it's used up) + //and there are reagents left, behave normally, + //otherwise drop it on the ground for timed reactions like gold. + + if(S) + if(S.reagents && S.reagents.total_volume) + for(var/obj/item/reagent_containers/glass/G in beakers) + S.reagents.trans_to(G, S.reagents.total_volume) + else + S.forceMove(get_turf(src)) + no_splash = TRUE + ..() + + + //I tried to just put it in the allowed_containers list but + //if you do that it must have reagents. If you're going to + //make a special case you might as well do it explicitly. -Sayu +/obj/item/grenade/chem_grenade/large/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/slime_extract) && stage == WIRED) + to_chat(user, "You add [I] to the assembly.") + user.drop_item() + I.loc = src + beakers += I + else + return ..() + +/obj/item/grenade/chem_grenade/cryo // Intended for rare cryogenic mixes. Cools the area moderately upon detonation. + name = "cryo grenade" + desc = "A custom made cryogenic grenade. It rapidly cools its contents upon detonation." + icon_state = "cryog" + affected_area = 2 + ignition_temp = -100 + +/obj/item/grenade/chem_grenade/pyro // Intended for pyrotechnical mixes. Produces a small fire upon detonation, igniting potentially flammable mixtures. + name = "pyro grenade" + desc = "A custom made pyrotechnical grenade. It heats up and ignites its contents upon detonation." + icon_state = "pyrog" + origin_tech = "combat=4;engineering=4" + affected_area = 3 + ignition_temp = 500 // This is enough to expose a hotspot. + +/obj/item/grenade/chem_grenade/adv_release // Intended for weaker, but longer lasting effects. Could have some interesting uses. + name = "advanced release grenade" + desc = "A custom made advanced release grenade. It is able to be detonated more than once. Can be configured using a multitool." + icon_state = "timeg" + origin_tech = "combat=3;engineering=4" + var/unit_spread = 10 // Amount of units per repeat. Can be altered with a multitool. + +/obj/item/grenade/chem_grenade/adv_release/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + switch(unit_spread) + if(0 to 24) + unit_spread += 5 + if(25 to 99) + unit_spread += 25 + else + unit_spread = 5 + to_chat(user, " You set the time release to [unit_spread] units per detonation.") + +/obj/item/grenade/chem_grenade/adv_release/prime() + if(stage != READY) + return + + var/total_volume = 0 + for(var/obj/item/reagent_containers/RC in beakers) + total_volume += RC.reagents.total_volume + if(!total_volume) + qdel(src) + qdel(nadeassembly) + return + var/fraction = unit_spread/total_volume + var/datum/reagents/reactants = new(unit_spread) + reactants.my_atom = src + for(var/obj/item/reagent_containers/RC in beakers) + RC.reagents.trans_to(reactants, RC.reagents.total_volume*fraction, threatscale, 1, 1) + chem_splash(get_turf(src), affected_area, list(reactants), ignition_temp, threatscale) + + if(nadeassembly) + var/mob/M = get_mob_by_ckey(assemblyattacher) + var/mob/last = get_mob_by_ckey(nadeassembly.fingerprintslast) + var/turf/T = get_turf(src) + var/area/A = get_area(T) + message_admins("grenade primed by an assembly, attached by [key_name_admin(M)] and last touched by [key_name_admin(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] (JMP).") + log_game("grenade primed by an assembly, attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] ([T.x], [T.y], [T.z])") + else + addtimer(CALLBACK(src, .proc/prime), det_time) + var/turf/DT = get_turf(src) + var/area/DA = get_area(DT) + log_game("A grenade detonated at [DA.name] ([DT.x], [DT.y], [DT.z])") + +/obj/item/grenade/chem_grenade/metalfoam + payload_name = "metal foam" + desc = "Used for emergency sealing of air breaches." + stage = READY + +/obj/item/grenade/chem_grenade/metalfoam/New() + ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent("aluminum", 30) + B2.reagents.add_reagent("fluorosurfactant", 10) + B2.reagents.add_reagent("sacid", 10) + + beakers += B1 + beakers += B2 + update_icon() + + +/obj/item/grenade/chem_grenade/firefighting + payload_name = "fire fighting grenade" + desc = "Can help to put out dangerous fires from a distance." + stage = READY + +/obj/item/grenade/chem_grenade/firefighting/New() + ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent("firefighting_foam", 30) + B2.reagents.add_reagent("firefighting_foam", 30) + + beakers += B1 + beakers += B2 + update_icon() + +/obj/item/grenade/chem_grenade/incendiary + payload_name = "incendiary" + desc = "Used for clearing rooms of living things." + stage = READY + +/obj/item/grenade/chem_grenade/incendiary/New() + ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent("phosphorus", 25) + B2.reagents.add_reagent("plasma", 25) + B2.reagents.add_reagent("sacid", 25) + + + beakers += B1 + beakers += B2 + update_icon() + + +/obj/item/grenade/chem_grenade/antiweed + payload_name = "weed killer" + desc = "Used for purging large areas of invasive plant species. Contents under pressure. Do not directly inhale contents." + stage = READY + +/obj/item/grenade/chem_grenade/antiweed/New() + ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent("atrazine", 30) + B1.reagents.add_reagent("potassium", 20) + B2.reagents.add_reagent("phosphorus", 20) + B2.reagents.add_reagent("sugar", 20) + B2.reagents.add_reagent("atrazine", 10) + + beakers += B1 + beakers += B2 + update_icon() + + +/obj/item/grenade/chem_grenade/cleaner + payload_name = "cleaner" + desc = "BLAM!-brand foaming space cleaner. In a special applicator for rapid cleaning of wide areas." + stage = READY + +/obj/item/grenade/chem_grenade/cleaner/New() + ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent("fluorosurfactant", 40) + B2.reagents.add_reagent("cleaner", 10) + B2.reagents.add_reagent("water", 40) //when you make pre-designed foam reactions that carry the reagents, always add water last + + beakers += B1 + beakers += B2 + update_icon() + + +/obj/item/grenade/chem_grenade/teargas + payload_name = "teargas" + desc = "Used for nonlethal riot control. Contents under pressure. Do not directly inhale contents." + stage = READY + +/obj/item/grenade/chem_grenade/teargas/New() + ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent("condensedcapsaicin", 25) + B1.reagents.add_reagent("potassium", 25) + B2.reagents.add_reagent("phosphorus", 25) + B2.reagents.add_reagent("sugar", 25) + + beakers += B1 + beakers += B2 + update_icon() + +/obj/item/grenade/chem_grenade/facid + payload_name = "acid smoke" + desc = "Use to chew up opponents from the inside out." + stage = READY + +/obj/item/grenade/chem_grenade/facid/New() + ..() + var/obj/item/reagent_containers/glass/beaker/large/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/large/B2 = new(src) + + B1.reagents.add_reagent("facid", 80) + B1.reagents.add_reagent("potassium", 20) + B2.reagents.add_reagent("phosphorus", 20) + B2.reagents.add_reagent("sugar", 20) + B2.reagents.add_reagent("facid", 60) + + beakers += B1 + beakers += B2 + update_icon() + +/obj/item/grenade/chem_grenade/saringas + payload_name = "sarin gas" + desc = "Contains sarin gas; extremely deadly and fast acting; use with extreme caution." + stage = READY + +/obj/item/grenade/chem_grenade/saringas/New() + ..() + var/obj/item/reagent_containers/glass/beaker/B1 = new(src) + var/obj/item/reagent_containers/glass/beaker/B2 = new(src) + + B1.reagents.add_reagent("sarin", 25) + B1.reagents.add_reagent("potassium", 25) + B2.reagents.add_reagent("phosphorus", 25) + B2.reagents.add_reagent("sugar", 25) + + beakers += B1 + beakers += B2 + update_icon() + +#undef EMPTY +#undef WIRED +#undef READY diff --git a/code/game/objects/items/weapons/grenades/emgrenade.dm b/code/game/objects/items/weapons/grenades/emgrenade.dm index 5b6d585ef5f..8807391d52b 100644 --- a/code/game/objects/items/weapons/grenades/emgrenade.dm +++ b/code/game/objects/items/weapons/grenades/emgrenade.dm @@ -1,11 +1,11 @@ -/obj/item/grenade/empgrenade - name = "classic EMP grenade" - desc = "It is designed to wreak havoc on electronic systems." - icon_state = "emp" - item_state = "emp" - origin_tech = "magnets=3;combat=2" - -/obj/item/grenade/empgrenade/prime() - update_mob() - empulse(src, 4, 10, 1) - qdel(src) +/obj/item/grenade/empgrenade + name = "classic EMP grenade" + desc = "It is designed to wreak havoc on electronic systems." + icon_state = "emp" + item_state = "emp" + origin_tech = "magnets=3;combat=2" + +/obj/item/grenade/empgrenade/prime() + update_mob() + empulse(src, 4, 10, 1) + qdel(src) diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index 5ffcaa9291c..b3028f13c90 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -1,79 +1,79 @@ -/obj/item/grenade/flashbang - name = "flashbang" - icon_state = "flashbang" - item_state = "flashbang" - origin_tech = "materials=2;combat=3" - light_power = 10 - light_color = LIGHT_COLOR_WHITE - var/light_time = 2 - var/range = 7 - -/obj/item/grenade/flashbang/prime() - update_mob() - var/flashbang_turf = get_turf(src) - if(!flashbang_turf) - return - - set_light(7) - - do_sparks(rand(5, 9), FALSE, src) - playsound(flashbang_turf, 'sound/effects/bang.ogg', 25, 1) - bang(flashbang_turf, src, range) - - for(var/obj/structure/blob/B in hear(8, flashbang_turf)) //Blob damage here - var/damage = round(30 / (get_dist(B, get_turf(src)) + 1)) - B.take_damage(damage, BURN, "melee", 0) - - spawn(light_time) - qdel(src) - -/proc/bang(turf/T, atom/A, range = 7, flash = TRUE, bang = TRUE) - for(var/mob/living/M in hearers(range, T)) - if(M.stat == DEAD) - continue - M.show_message("BANG", 2) - - //Checking for protections - var/ear_safety = M.check_ear_prot() - var/distance = max(1, get_dist(get_turf(A), get_turf(M))) - - //Flash - if(flash) - if(M.weakeyes) - M.visible_message("[M] screams and collapses!") - to_chat(M, "AAAAGH!") - M.Weaken(15) //hella stunned - M.Stun(15) - if(ishuman(M)) - M.emote("scream") - var/mob/living/carbon/human/H = M - var/obj/item/organ/internal/eyes/E = H.get_int_organ(/obj/item/organ/internal/eyes) - if(E) - E.receive_damage(8, 1) - - if(M.flash_eyes(affect_silicon = TRUE)) - M.Stun(max(10 / distance, 3)) - M.Weaken(max(10 / distance, 3)) - - - //Bang - if(bang) - if(!distance || A.loc == M || A.loc == M.loc) //Holding on person or being exactly where lies is significantly more dangerous and voids protection - M.Stun(10) - M.Weaken(10) - if(!ear_safety) - M.Stun(max(10 / distance, 3)) - M.Weaken(max(10 / distance, 3)) - M.AdjustEarDamage(rand(0, 5), 15) - if(iscarbon(M)) - var/mob/living/carbon/C = M - var/obj/item/organ/internal/ears/ears = C.get_int_organ(/obj/item/organ/internal/ears) - if(istype(ears)) - if(ears.ear_damage >= 15) - to_chat(M, "Your ears start to ring badly!") - if(prob(ears.ear_damage - 5)) - to_chat(M, "You can't hear anything!") - M.BecomeDeaf() - else - if(ears.ear_damage >= 5) - to_chat(M, "Your ears start to ring!") \ No newline at end of file +/obj/item/grenade/flashbang + name = "flashbang" + icon_state = "flashbang" + item_state = "flashbang" + origin_tech = "materials=2;combat=3" + light_power = 10 + light_color = LIGHT_COLOR_WHITE + var/light_time = 2 + var/range = 7 + +/obj/item/grenade/flashbang/prime() + update_mob() + var/flashbang_turf = get_turf(src) + if(!flashbang_turf) + return + + set_light(7) + + do_sparks(rand(5, 9), FALSE, src) + playsound(flashbang_turf, 'sound/effects/bang.ogg', 25, 1) + bang(flashbang_turf, src, range) + + for(var/obj/structure/blob/B in hear(8, flashbang_turf)) //Blob damage here + var/damage = round(30 / (get_dist(B, get_turf(src)) + 1)) + B.take_damage(damage, BURN, "melee", 0) + + spawn(light_time) + qdel(src) + +/proc/bang(turf/T, atom/A, range = 7, flash = TRUE, bang = TRUE) + for(var/mob/living/M in hearers(range, T)) + if(M.stat == DEAD) + continue + M.show_message("BANG", 2) + + //Checking for protections + var/ear_safety = M.check_ear_prot() + var/distance = max(1, get_dist(get_turf(A), get_turf(M))) + + //Flash + if(flash) + if(M.weakeyes) + M.visible_message("[M] screams and collapses!") + to_chat(M, "AAAAGH!") + M.Weaken(15) //hella stunned + M.Stun(15) + if(ishuman(M)) + M.emote("scream") + var/mob/living/carbon/human/H = M + var/obj/item/organ/internal/eyes/E = H.get_int_organ(/obj/item/organ/internal/eyes) + if(E) + E.receive_damage(8, 1) + + if(M.flash_eyes(affect_silicon = TRUE)) + M.Stun(max(10 / distance, 3)) + M.Weaken(max(10 / distance, 3)) + + + //Bang + if(bang) + if(!distance || A.loc == M || A.loc == M.loc) //Holding on person or being exactly where lies is significantly more dangerous and voids protection + M.Stun(10) + M.Weaken(10) + if(!ear_safety) + M.Stun(max(10 / distance, 3)) + M.Weaken(max(10 / distance, 3)) + M.AdjustEarDamage(rand(0, 5), 15) + if(iscarbon(M)) + var/mob/living/carbon/C = M + var/obj/item/organ/internal/ears/ears = C.get_int_organ(/obj/item/organ/internal/ears) + if(istype(ears)) + if(ears.ear_damage >= 15) + to_chat(M, "Your ears start to ring badly!") + if(prob(ears.ear_damage - 5)) + to_chat(M, "You can't hear anything!") + M.BecomeDeaf() + else + if(ears.ear_damage >= 5) + to_chat(M, "Your ears start to ring!") diff --git a/code/game/objects/items/weapons/grenades/grenade.dm b/code/game/objects/items/weapons/grenades/grenade.dm index 414fffdfc7e..8cbc041c146 100644 --- a/code/game/objects/items/weapons/grenades/grenade.dm +++ b/code/game/objects/items/weapons/grenades/grenade.dm @@ -1,111 +1,111 @@ -/obj/item/grenade - name = "grenade" - desc = "A hand held grenade, with an adjustable timer." - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/grenade.dmi' - icon_state = "grenade" - item_state = "flashbang" - throw_speed = 4 - throw_range = 20 - flags = CONDUCT - slot_flags = SLOT_BELT - resistance_flags = FLAMMABLE - max_integrity = 40 - var/active = 0 - var/det_time = 50 - var/display_timer = 1 - -/obj/item/grenade/deconstruct(disassembled = TRUE) - if(!disassembled) - prime() - if(!QDELETED(src)) - qdel(src) - -/obj/item/grenade/proc/clown_check(var/mob/living/user) - if((CLUMSY in user.mutations) && prob(50)) - to_chat(user, "Huh? How does this thing work?") - active = 1 - icon_state = initial(icon_state) + "_active" - playsound(loc, 'sound/weapons/armbomb.ogg', 75, 1, -3) - spawn(5) - if(user) - user.drop_item() - prime() - return 0 - return 1 - - -/*/obj/item/grenade/afterattack(atom/target as mob|obj|turf|area, mob/user as mob) - if(istype(target, /obj/item/storage)) return ..() // Trying to put it in a full container - if(istype(target, /obj/item/gun/grenadelauncher)) return ..() - if((user.is_in_active_hand(src)) && (!active) && (clown_check(user)) && target.loc != src.loc) - to_chat(user, "You prime the [name]! [det_time/10] seconds!") - active = 1 - icon_state = initial(icon_state) + "_active" - playsound(loc, 'sound/weapons/armbomb.ogg', 75, 1, -3) - spawn(det_time) - prime() - return - user.dir = get_dir(user, target) - user.drop_item() - var/t = (isturf(target) ? target : target.loc) - walk_towards(src, t, 3) - return*/ - - -/obj/item/grenade/examine(mob/user) - . = ..() - if(display_timer) - if(det_time > 1) - . += "The timer is set to [det_time/10] second\s." - else - . += "\The [src] is set for instant detonation." - -/obj/item/grenade/attack_self(mob/user as mob) - if(!active) - if(clown_check(user)) - to_chat(user, "You prime the [name]! [det_time/10] seconds!") - active = 1 - icon_state = initial(icon_state) + "_active" - add_fingerprint(user) - var/turf/bombturf = get_turf(src) - var/area/A = get_area(bombturf) - message_admins("[key_name_admin(usr)] has primed a [name] for detonation at [A.name] (JMP)") - log_game("[key_name(usr)] has primed a [name] for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z])") - investigate_log("[key_name(usr)] has primed a [name] for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z])", INVESTIGATE_BOMB) - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.throw_mode_on() - spawn(det_time) - prime() - - -/obj/item/grenade/proc/prime() - -/obj/item/grenade/proc/update_mob() - if(ismob(loc)) - var/mob/M = loc - M.unEquip(src) - - -/obj/item/grenade/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/screwdriver)) - switch(det_time) - if("1") - det_time = 10 - to_chat(user, "You set the [name] for 1 second detonation time.") - if("10") - det_time = 30 - to_chat(user, "You set the [name] for 3 second detonation time.") - if("30") - det_time = 50 - to_chat(user, "You set the [name] for 5 second detonation time.") - if("50") - det_time = 1 - to_chat(user, "You set the [name] for instant detonation.") - add_fingerprint(user) - ..() - -/obj/item/grenade/attack_hand() - walk(src, null, null) - ..() +/obj/item/grenade + name = "grenade" + desc = "A hand held grenade, with an adjustable timer." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/grenade.dmi' + icon_state = "grenade" + item_state = "flashbang" + throw_speed = 4 + throw_range = 20 + flags = CONDUCT + slot_flags = SLOT_BELT + resistance_flags = FLAMMABLE + max_integrity = 40 + var/active = 0 + var/det_time = 50 + var/display_timer = 1 + +/obj/item/grenade/deconstruct(disassembled = TRUE) + if(!disassembled) + prime() + if(!QDELETED(src)) + qdel(src) + +/obj/item/grenade/proc/clown_check(var/mob/living/user) + if((CLUMSY in user.mutations) && prob(50)) + to_chat(user, "Huh? How does this thing work?") + active = 1 + icon_state = initial(icon_state) + "_active" + playsound(loc, 'sound/weapons/armbomb.ogg', 75, 1, -3) + spawn(5) + if(user) + user.drop_item() + prime() + return 0 + return 1 + + +/*/obj/item/grenade/afterattack(atom/target as mob|obj|turf|area, mob/user as mob) + if(istype(target, /obj/item/storage)) return ..() // Trying to put it in a full container + if(istype(target, /obj/item/gun/grenadelauncher)) return ..() + if((user.is_in_active_hand(src)) && (!active) && (clown_check(user)) && target.loc != src.loc) + to_chat(user, "You prime the [name]! [det_time/10] seconds!") + active = 1 + icon_state = initial(icon_state) + "_active" + playsound(loc, 'sound/weapons/armbomb.ogg', 75, 1, -3) + spawn(det_time) + prime() + return + user.dir = get_dir(user, target) + user.drop_item() + var/t = (isturf(target) ? target : target.loc) + walk_towards(src, t, 3) + return*/ + + +/obj/item/grenade/examine(mob/user) + . = ..() + if(display_timer) + if(det_time > 1) + . += "The timer is set to [det_time/10] second\s." + else + . += "\The [src] is set for instant detonation." + +/obj/item/grenade/attack_self(mob/user as mob) + if(!active) + if(clown_check(user)) + to_chat(user, "You prime the [name]! [det_time/10] seconds!") + active = 1 + icon_state = initial(icon_state) + "_active" + add_fingerprint(user) + var/turf/bombturf = get_turf(src) + var/area/A = get_area(bombturf) + message_admins("[key_name_admin(usr)] has primed a [name] for detonation at [A.name] (JMP)") + log_game("[key_name(usr)] has primed a [name] for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z])") + investigate_log("[key_name(usr)] has primed a [name] for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z])", INVESTIGATE_BOMB) + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.throw_mode_on() + spawn(det_time) + prime() + + +/obj/item/grenade/proc/prime() + +/obj/item/grenade/proc/update_mob() + if(ismob(loc)) + var/mob/M = loc + M.unEquip(src) + + +/obj/item/grenade/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/screwdriver)) + switch(det_time) + if("1") + det_time = 10 + to_chat(user, "You set the [name] for 1 second detonation time.") + if("10") + det_time = 30 + to_chat(user, "You set the [name] for 3 second detonation time.") + if("30") + det_time = 50 + to_chat(user, "You set the [name] for 5 second detonation time.") + if("50") + det_time = 1 + to_chat(user, "You set the [name] for instant detonation.") + add_fingerprint(user) + ..() + +/obj/item/grenade/attack_hand() + walk(src, null, null) + ..() diff --git a/code/game/objects/items/weapons/grenades/smokebomb.dm b/code/game/objects/items/weapons/grenades/smokebomb.dm index 7fd965b5c2a..6dd07485f5b 100644 --- a/code/game/objects/items/weapons/grenades/smokebomb.dm +++ b/code/game/objects/items/weapons/grenades/smokebomb.dm @@ -1,37 +1,37 @@ -/obj/item/grenade/smokebomb - desc = "It is set to detonate in 2 seconds." - name = "smoke bomb" - icon = 'icons/obj/grenade.dmi' - icon_state = "flashbang" - det_time = 20 - item_state = "flashbang" - slot_flags = SLOT_BELT - var/datum/effect_system/smoke_spread/bad/smoke - -/obj/item/grenade/smokebomb/New() - ..() - src.smoke = new /datum/effect_system/smoke_spread/bad - src.smoke.attach(src) - -/obj/item/grenade/smokebomb/Destroy() - QDEL_NULL(smoke) - return ..() - -/obj/item/grenade/smokebomb/prime() - playsound(src.loc, 'sound/effects/smoke.ogg', 50, 1, -3) - src.smoke.set_up(10, 0, usr.loc) - spawn(0) - src.smoke.start() - sleep(10) - src.smoke.start() - sleep(10) - src.smoke.start() - sleep(10) - src.smoke.start() - - for(var/obj/structure/blob/B in view(8,src)) - var/damage = round(30/(get_dist(B,src)+1)) - B.take_damage(damage, BURN, "melee", 0) - sleep(80) - qdel(src) - return +/obj/item/grenade/smokebomb + desc = "It is set to detonate in 2 seconds." + name = "smoke bomb" + icon = 'icons/obj/grenade.dmi' + icon_state = "flashbang" + det_time = 20 + item_state = "flashbang" + slot_flags = SLOT_BELT + var/datum/effect_system/smoke_spread/bad/smoke + +/obj/item/grenade/smokebomb/New() + ..() + src.smoke = new /datum/effect_system/smoke_spread/bad + src.smoke.attach(src) + +/obj/item/grenade/smokebomb/Destroy() + QDEL_NULL(smoke) + return ..() + +/obj/item/grenade/smokebomb/prime() + playsound(src.loc, 'sound/effects/smoke.ogg', 50, 1, -3) + src.smoke.set_up(10, 0, usr.loc) + spawn(0) + src.smoke.start() + sleep(10) + src.smoke.start() + sleep(10) + src.smoke.start() + sleep(10) + src.smoke.start() + + for(var/obj/structure/blob/B in view(8,src)) + var/damage = round(30/(get_dist(B,src)+1)) + B.take_damage(damage, BURN, "melee", 0) + sleep(80) + qdel(src) + return diff --git a/code/game/objects/items/weapons/grenades/spawnergrenade.dm b/code/game/objects/items/weapons/grenades/spawnergrenade.dm index dacbffa1a85..b4df7b0d16b 100644 --- a/code/game/objects/items/weapons/grenades/spawnergrenade.dm +++ b/code/game/objects/items/weapons/grenades/spawnergrenade.dm @@ -1,68 +1,68 @@ -/obj/item/grenade/spawnergrenade - desc = "It is set to detonate in 5 seconds. It will unleash unleash an unspecified anomaly into the vicinity." - name = "delivery grenade" - icon = 'icons/obj/grenade.dmi' - icon_state = "delivery" - item_state = "flashbang" - origin_tech = "materials=3;magnets=4" - var/spawner_type = null // must be an object path - var/deliveryamt = 1 // amount of type to deliver - spawner_type = /mob/living/simple_animal/hostile/viscerator - - prime() // Prime now just handles the two loops that query for people in lockers and people who can see it. - - if(spawner_type && deliveryamt) - // Make a quick flash - var/turf/T = get_turf(src) - playsound(T, 'sound/effects/phasein.ogg', 100, 1) - for(var/mob/living/carbon/C in viewers(T, null)) - C.flash_eyes() - - for(var/i=1, i<=deliveryamt, i++) - var/atom/movable/x = new spawner_type - x.admin_spawned = admin_spawned - x.loc = T - if(prob(50)) - for(var/j = 1, j <= rand(1, 3), j++) - step(x, pick(NORTH,SOUTH,EAST,WEST)) - - // Spawn some hostile syndicate critters - - qdel(src) - return - -/obj/item/grenade/spawnergrenade/manhacks - name = "manhack delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/viscerator - deliveryamt = 5 - origin_tech = "materials=3;magnets=4;syndicate=3" - -/obj/item/grenade/spawnergrenade/spesscarp - name = "carp delivery grenade" - spawner_type = /mob/living/simple_animal/hostile/carp - deliveryamt = 5 - origin_tech = "materials=3;magnets=4;syndicate=3" - -/obj/item/grenade/spawnergrenade/feral_cats - name = "feral cat delivery grenade" - desc = "This grenade contains 5 dehydrated feral cats in a similar manner to dehydrated monkeys, which, upon detonation, will be rehydrated by a small reservoir of water contained within the grenade. These cats will then attack anything in sight." - spawner_type = /mob/living/simple_animal/hostile/feral_cat - deliveryamt = 5 - origin_tech = "materials=3;magnets=4;syndicate=3" - -/obj/item/grenade/spawnergrenade/feral_cats/prime() //Own proc for this because the regular one would flash people which was dumb. - update_mob() - if(spawner_type && deliveryamt) - var/turf/T = get_turf(src) - playsound(T, 'sound/effects/phasein.ogg', 100, 1) - - for(var/i=1, i<=deliveryamt, i++) - var/atom/movable/x = new spawner_type - x.loc = T - if(prob(50)) - for(var/j = 1, j <= rand(1, 3), j++) - step(x, pick(NORTH,SOUTH,EAST,WEST)) - - - qdel(src) - return +/obj/item/grenade/spawnergrenade + desc = "It is set to detonate in 5 seconds. It will unleash unleash an unspecified anomaly into the vicinity." + name = "delivery grenade" + icon = 'icons/obj/grenade.dmi' + icon_state = "delivery" + item_state = "flashbang" + origin_tech = "materials=3;magnets=4" + var/spawner_type = null // must be an object path + var/deliveryamt = 1 // amount of type to deliver + spawner_type = /mob/living/simple_animal/hostile/viscerator + + prime() // Prime now just handles the two loops that query for people in lockers and people who can see it. + + if(spawner_type && deliveryamt) + // Make a quick flash + var/turf/T = get_turf(src) + playsound(T, 'sound/effects/phasein.ogg', 100, 1) + for(var/mob/living/carbon/C in viewers(T, null)) + C.flash_eyes() + + for(var/i=1, i<=deliveryamt, i++) + var/atom/movable/x = new spawner_type + x.admin_spawned = admin_spawned + x.loc = T + if(prob(50)) + for(var/j = 1, j <= rand(1, 3), j++) + step(x, pick(NORTH,SOUTH,EAST,WEST)) + + // Spawn some hostile syndicate critters + + qdel(src) + return + +/obj/item/grenade/spawnergrenade/manhacks + name = "manhack delivery grenade" + spawner_type = /mob/living/simple_animal/hostile/viscerator + deliveryamt = 5 + origin_tech = "materials=3;magnets=4;syndicate=3" + +/obj/item/grenade/spawnergrenade/spesscarp + name = "carp delivery grenade" + spawner_type = /mob/living/simple_animal/hostile/carp + deliveryamt = 5 + origin_tech = "materials=3;magnets=4;syndicate=3" + +/obj/item/grenade/spawnergrenade/feral_cats + name = "feral cat delivery grenade" + desc = "This grenade contains 5 dehydrated feral cats in a similar manner to dehydrated monkeys, which, upon detonation, will be rehydrated by a small reservoir of water contained within the grenade. These cats will then attack anything in sight." + spawner_type = /mob/living/simple_animal/hostile/feral_cat + deliveryamt = 5 + origin_tech = "materials=3;magnets=4;syndicate=3" + +/obj/item/grenade/spawnergrenade/feral_cats/prime() //Own proc for this because the regular one would flash people which was dumb. + update_mob() + if(spawner_type && deliveryamt) + var/turf/T = get_turf(src) + playsound(T, 'sound/effects/phasein.ogg', 100, 1) + + for(var/i=1, i<=deliveryamt, i++) + var/atom/movable/x = new spawner_type + x.loc = T + if(prob(50)) + for(var/j = 1, j <= rand(1, 3), j++) + step(x, pick(NORTH,SOUTH,EAST,WEST)) + + + qdel(src) + return diff --git a/code/game/objects/items/weapons/grenades/syndieminibomb.dm b/code/game/objects/items/weapons/grenades/syndieminibomb.dm index 0292b8f9eba..f092a4cf6dc 100644 --- a/code/game/objects/items/weapons/grenades/syndieminibomb.dm +++ b/code/game/objects/items/weapons/grenades/syndieminibomb.dm @@ -9,4 +9,4 @@ /obj/item/grenade/syndieminibomb/prime() update_mob() explosion(loc, 1, 2, 4, flame_range = 2) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm index 94f5e47f4ca..e188279eb8c 100644 --- a/code/game/objects/items/weapons/handcuffs.dm +++ b/code/game/objects/items/weapons/handcuffs.dm @@ -1,194 +1,194 @@ -/obj/item/restraints/handcuffs - name = "handcuffs" - desc = "Use this to keep prisoners in line." - gender = PLURAL - icon = 'icons/obj/items.dmi' - icon_state = "handcuff" - flags = CONDUCT - slot_flags = SLOT_BELT - throwforce = 5 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 2 - throw_range = 5 - materials = list(MAT_METAL=500) - origin_tech = "engineering=3;combat=3" - breakouttime = 600 //Deciseconds = 60s = 1 minutes - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - var/cuffsound = 'sound/weapons/handcuffs.ogg' - var/trashtype = null //For disposable cuffs - var/ignoresClumsy = FALSE - -/obj/item/restraints/handcuffs/attack(mob/living/carbon/C, mob/user) - if(!user.IsAdvancedToolUser()) - return - - if(!istype(C)) - return - - if(flags & NODROP) - to_chat(user, "[src] is stuck to your hand!") - return - - if((CLUMSY in user.mutations) && prob(50) && (!ignoresClumsy)) - to_chat(user, "Uh... how do those things work?!") - apply_cuffs(user, user) - return - - cuff(C, user) - -/obj/item/restraints/handcuffs/proc/cuff(mob/living/carbon/C, mob/user, remove_src = TRUE) - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(!(H.has_left_hand() || H.has_right_hand())) - to_chat(user, "How do you suggest handcuffing someone with no hands?") - return - - if(!C.handcuffed) - C.visible_message("[user] is trying to put [src.name] on [C]!", \ - "[user] is trying to put [src.name] on [C]!") - - playsound(loc, cuffsound, 30, 1, -2) - if(do_mob(user, C, 30)) - apply_cuffs(C, user, remove_src) - to_chat(user, "You handcuff [C].") - if(istype(src, /obj/item/restraints/handcuffs/cable)) - feedback_add_details("handcuffs", "C") - else - feedback_add_details("handcuffs", "H") - - add_attack_logs(user, C, "Handcuffed ([src])") - else - to_chat(user, "You fail to handcuff [C].") - -/obj/item/restraints/handcuffs/proc/apply_cuffs(mob/living/carbon/target, mob/user, remove_src = TRUE) - if(!target.handcuffed) - if(remove_src) - user.drop_item() - if(trashtype) - target.handcuffed = new trashtype(target) - if(remove_src) - qdel(src) - else - if(remove_src) - loc = target - target.handcuffed = src - else - target.handcuffed = new type(loc) - target.update_handcuffed() - return - -/obj/item/restraints/handcuffs/sinew - name = "sinew restraints" - desc = "A pair of restraints fashioned from long strands of flesh." - icon = 'icons/obj/mining.dmi' - icon_state = "sinewcuff" - item_state = "sinewcuff" - breakouttime = 300 //Deciseconds = 30s - cuffsound = 'sound/weapons/cablecuff.ogg' - -/obj/item/restraints/handcuffs/cable - name = "cable restraints" - desc = "Looks like some cables tied together. Could be used to tie something up." - icon_state = "cuff_white" - origin_tech = "engineering=2" - materials = list(MAT_METAL=150, MAT_GLASS=75) - breakouttime = 300 //Deciseconds = 30s - cuffsound = 'sound/weapons/cablecuff.ogg' - -/obj/item/restraints/handcuffs/cable/red - color = COLOR_RED - -/obj/item/restraints/handcuffs/cable/yellow - color = COLOR_YELLOW - -/obj/item/restraints/handcuffs/cable/blue - color = COLOR_BLUE - -/obj/item/restraints/handcuffs/cable/green - color = COLOR_GREEN - -/obj/item/restraints/handcuffs/cable/pink - color = COLOR_PINK - -/obj/item/restraints/handcuffs/cable/orange - color = COLOR_ORANGE - -/obj/item/restraints/handcuffs/cable/cyan - color = COLOR_CYAN - -/obj/item/restraints/handcuffs/cable/white - color = COLOR_WHITE - -/obj/item/restraints/handcuffs/cable/random/New() - color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) - ..() - -/obj/item/restraints/handcuffs/cable/proc/cable_color(var/colorC) - if(colorC) - if(colorC == "rainbow") - colorC = color_rainbow() - color = colorC - else - color = COLOR_RED - -/obj/item/restraints/handcuffs/cable/proc/color_rainbow() - color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) - return color - -/obj/item/restraints/handcuffs/alien - icon_state = "handcuffAlien" - -/obj/item/restraints/handcuffs/pinkcuffs - name = "fluffy pink handcuffs" - desc = "Use this to keep prisoners in line. Or you know, your significant other." - icon_state = "pinkcuffs" - -/obj/item/restraints/handcuffs/cable/attackby(var/obj/item/I, mob/user as mob, params) - ..() - if(istype(I, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = I - if(R.use(1)) - var/obj/item/wirerod/W = new /obj/item/wirerod - if(!remove_item_from_storage(user)) - user.unEquip(src) - user.put_in_hands(W) - to_chat(user, "You wrap the cable restraint around the top of the rod.") - qdel(src) - else - to_chat(user, "You need one rod to make a wired rod!") - else if(istype(I, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/M = I - if(M.amount < 6) - to_chat(user, "You need at least six metal sheets to make good enough weights!") - return - to_chat(user, "You begin to apply [I] to [src]...") - if(do_after(user, 35 * M.toolspeed, target = src)) - var/obj/item/restraints/legcuffs/bola/S = new /obj/item/restraints/legcuffs/bola - M.use(6) - user.put_in_hands(S) - to_chat(user, "You make some weights out of [I] and tie them to [src].") - if(!remove_item_from_storage(user)) - user.unEquip(src) - qdel(src) - else if(istype(I, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/C = I - cable_color(C.colourName) - -/obj/item/restraints/handcuffs/cable/zipties - name = "zipties" - desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." - icon_state = "cuff_white" - breakouttime = 450 //Deciseconds = 45s - materials = list() - trashtype = /obj/item/restraints/handcuffs/cable/zipties/used - -/obj/item/restraints/handcuffs/cable/zipties/cyborg/attack(mob/living/carbon/C, mob/user) - if(isrobot(user)) - cuff(C, user, FALSE) - -/obj/item/restraints/handcuffs/cable/zipties/used - desc = "A pair of broken zipties." - icon_state = "cuff_white_used" - -/obj/item/restraints/handcuffs/cable/zipties/used/attack() - return +/obj/item/restraints/handcuffs + name = "handcuffs" + desc = "Use this to keep prisoners in line." + gender = PLURAL + icon = 'icons/obj/items.dmi' + icon_state = "handcuff" + flags = CONDUCT + slot_flags = SLOT_BELT + throwforce = 5 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 2 + throw_range = 5 + materials = list(MAT_METAL=500) + origin_tech = "engineering=3;combat=3" + breakouttime = 600 //Deciseconds = 60s = 1 minutes + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + var/cuffsound = 'sound/weapons/handcuffs.ogg' + var/trashtype = null //For disposable cuffs + var/ignoresClumsy = FALSE + +/obj/item/restraints/handcuffs/attack(mob/living/carbon/C, mob/user) + if(!user.IsAdvancedToolUser()) + return + + if(!istype(C)) + return + + if(flags & NODROP) + to_chat(user, "[src] is stuck to your hand!") + return + + if((CLUMSY in user.mutations) && prob(50) && (!ignoresClumsy)) + to_chat(user, "Uh... how do those things work?!") + apply_cuffs(user, user) + return + + cuff(C, user) + +/obj/item/restraints/handcuffs/proc/cuff(mob/living/carbon/C, mob/user, remove_src = TRUE) + if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(!(H.has_left_hand() || H.has_right_hand())) + to_chat(user, "How do you suggest handcuffing someone with no hands?") + return + + if(!C.handcuffed) + C.visible_message("[user] is trying to put [src.name] on [C]!", \ + "[user] is trying to put [src.name] on [C]!") + + playsound(loc, cuffsound, 30, 1, -2) + if(do_mob(user, C, 30)) + apply_cuffs(C, user, remove_src) + to_chat(user, "You handcuff [C].") + if(istype(src, /obj/item/restraints/handcuffs/cable)) + feedback_add_details("handcuffs", "C") + else + feedback_add_details("handcuffs", "H") + + add_attack_logs(user, C, "Handcuffed ([src])") + else + to_chat(user, "You fail to handcuff [C].") + +/obj/item/restraints/handcuffs/proc/apply_cuffs(mob/living/carbon/target, mob/user, remove_src = TRUE) + if(!target.handcuffed) + if(remove_src) + user.drop_item() + if(trashtype) + target.handcuffed = new trashtype(target) + if(remove_src) + qdel(src) + else + if(remove_src) + loc = target + target.handcuffed = src + else + target.handcuffed = new type(loc) + target.update_handcuffed() + return + +/obj/item/restraints/handcuffs/sinew + name = "sinew restraints" + desc = "A pair of restraints fashioned from long strands of flesh." + icon = 'icons/obj/mining.dmi' + icon_state = "sinewcuff" + item_state = "sinewcuff" + breakouttime = 300 //Deciseconds = 30s + cuffsound = 'sound/weapons/cablecuff.ogg' + +/obj/item/restraints/handcuffs/cable + name = "cable restraints" + desc = "Looks like some cables tied together. Could be used to tie something up." + icon_state = "cuff_white" + origin_tech = "engineering=2" + materials = list(MAT_METAL=150, MAT_GLASS=75) + breakouttime = 300 //Deciseconds = 30s + cuffsound = 'sound/weapons/cablecuff.ogg' + +/obj/item/restraints/handcuffs/cable/red + color = COLOR_RED + +/obj/item/restraints/handcuffs/cable/yellow + color = COLOR_YELLOW + +/obj/item/restraints/handcuffs/cable/blue + color = COLOR_BLUE + +/obj/item/restraints/handcuffs/cable/green + color = COLOR_GREEN + +/obj/item/restraints/handcuffs/cable/pink + color = COLOR_PINK + +/obj/item/restraints/handcuffs/cable/orange + color = COLOR_ORANGE + +/obj/item/restraints/handcuffs/cable/cyan + color = COLOR_CYAN + +/obj/item/restraints/handcuffs/cable/white + color = COLOR_WHITE + +/obj/item/restraints/handcuffs/cable/random/New() + color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) + ..() + +/obj/item/restraints/handcuffs/cable/proc/cable_color(var/colorC) + if(colorC) + if(colorC == "rainbow") + colorC = color_rainbow() + color = colorC + else + color = COLOR_RED + +/obj/item/restraints/handcuffs/cable/proc/color_rainbow() + color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) + return color + +/obj/item/restraints/handcuffs/alien + icon_state = "handcuffAlien" + +/obj/item/restraints/handcuffs/pinkcuffs + name = "fluffy pink handcuffs" + desc = "Use this to keep prisoners in line. Or you know, your significant other." + icon_state = "pinkcuffs" + +/obj/item/restraints/handcuffs/cable/attackby(var/obj/item/I, mob/user as mob, params) + ..() + if(istype(I, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = I + if(R.use(1)) + var/obj/item/wirerod/W = new /obj/item/wirerod + if(!remove_item_from_storage(user)) + user.unEquip(src) + user.put_in_hands(W) + to_chat(user, "You wrap the cable restraint around the top of the rod.") + qdel(src) + else + to_chat(user, "You need one rod to make a wired rod!") + else if(istype(I, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/M = I + if(M.amount < 6) + to_chat(user, "You need at least six metal sheets to make good enough weights!") + return + to_chat(user, "You begin to apply [I] to [src]...") + if(do_after(user, 35 * M.toolspeed, target = src)) + var/obj/item/restraints/legcuffs/bola/S = new /obj/item/restraints/legcuffs/bola + M.use(6) + user.put_in_hands(S) + to_chat(user, "You make some weights out of [I] and tie them to [src].") + if(!remove_item_from_storage(user)) + user.unEquip(src) + qdel(src) + else if(istype(I, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/C = I + cable_color(C.colourName) + +/obj/item/restraints/handcuffs/cable/zipties + name = "zipties" + desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use." + icon_state = "cuff_white" + breakouttime = 450 //Deciseconds = 45s + materials = list() + trashtype = /obj/item/restraints/handcuffs/cable/zipties/used + +/obj/item/restraints/handcuffs/cable/zipties/cyborg/attack(mob/living/carbon/C, mob/user) + if(isrobot(user)) + cuff(C, user, FALSE) + +/obj/item/restraints/handcuffs/cable/zipties/used + desc = "A pair of broken zipties." + icon_state = "cuff_white_used" + +/obj/item/restraints/handcuffs/cable/zipties/used/attack() + return diff --git a/code/game/objects/items/weapons/holosign.dm b/code/game/objects/items/weapons/holosign.dm index 12767cb9563..1828d38628c 100644 --- a/code/game/objects/items/weapons/holosign.dm +++ b/code/game/objects/items/weapons/holosign.dm @@ -120,4 +120,4 @@ if(signs.len) for(var/H in signs) qdel(H) - to_chat(user, "You clear all active holograms.") \ No newline at end of file + to_chat(user, "You clear all active holograms.") diff --git a/code/game/objects/items/weapons/implants/health.dm b/code/game/objects/items/weapons/implants/health.dm index d009a2037d2..e40006f4a2a 100644 --- a/code/game/objects/items/weapons/implants/health.dm +++ b/code/game/objects/items/weapons/implants/health.dm @@ -8,4 +8,4 @@ return "ERROR" else healthstring = "[round(imp_in.getOxyLoss())] - [round(imp_in.getFireLoss())] - [round(imp_in.getToxLoss())] - [round(imp_in.getBruteLoss())]" - return healthstring \ No newline at end of file + return healthstring diff --git a/code/game/objects/items/weapons/implants/implant.dm b/code/game/objects/items/weapons/implants/implant.dm index 88dc4ed6542..9dc1fe864fc 100644 --- a/code/game/objects/items/weapons/implants/implant.dm +++ b/code/game/objects/items/weapons/implants/implant.dm @@ -1,87 +1,87 @@ -/obj/item/implant - name = "implant" - icon = 'icons/obj/implants.dmi' - icon_state = "generic" //Shows up as the action button icon - origin_tech = "materials=2;biotech=3;programming=2" - - actions_types = list(/datum/action/item_action/hands_free/activate) - var/activated = 1 //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants - var/implanted = null - var/mob/living/imp_in = null - item_color = "b" - var/allow_multiple = 0 - var/uses = -1 - flags = DROPDEL - - -/obj/item/implant/proc/trigger(emote, mob/source, force) - return - -/obj/item/implant/proc/activate() - return - -/obj/item/implant/ui_action_click() - activate("action_button") - - -//What does the implant do upon injection? -//return 1 if the implant injects -//return -1 if the implant fails to inject -//return 0 if there is no room for implant -/obj/item/implant/proc/implant(var/mob/source, var/mob/user) - var/obj/item/implant/imp_e = locate(src.type) in source - if(!allow_multiple && imp_e && imp_e != src) - if(imp_e.uses < initial(imp_e.uses)*2) - if(uses == -1) - imp_e.uses = -1 - else - imp_e.uses = min(imp_e.uses + uses, initial(imp_e.uses)*2) - qdel(src) - return 1 - else - return 0 - - - src.loc = source - imp_in = source - implanted = 1 - if(activated) - for(var/X in actions) - var/datum/action/A = X - A.Grant(source) - if(ishuman(source)) - var/mob/living/carbon/human/H = source - H.sec_hud_set_implants() - - if(user) - add_attack_logs(user, source, "Implanted with [src]") - - return 1 - -/obj/item/implant/proc/removed(var/mob/source) - src.loc = null - imp_in = null - implanted = 0 - - for(var/X in actions) - var/datum/action/A = X - A.Grant(source) - - if(ishuman(source)) - var/mob/living/carbon/human/H = source - H.sec_hud_set_implants() - - return 1 - -/obj/item/implant/Destroy() - if(imp_in) - removed(imp_in) - return ..() - - -/obj/item/implant/proc/get_data() - return "No information available" - -/obj/item/implant/dropped(mob/user) - . = 1 - ..() \ No newline at end of file +/obj/item/implant + name = "implant" + icon = 'icons/obj/implants.dmi' + icon_state = "generic" //Shows up as the action button icon + origin_tech = "materials=2;biotech=3;programming=2" + + actions_types = list(/datum/action/item_action/hands_free/activate) + var/activated = 1 //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants + var/implanted = null + var/mob/living/imp_in = null + item_color = "b" + var/allow_multiple = 0 + var/uses = -1 + flags = DROPDEL + + +/obj/item/implant/proc/trigger(emote, mob/source, force) + return + +/obj/item/implant/proc/activate() + return + +/obj/item/implant/ui_action_click() + activate("action_button") + + +//What does the implant do upon injection? +//return 1 if the implant injects +//return -1 if the implant fails to inject +//return 0 if there is no room for implant +/obj/item/implant/proc/implant(var/mob/source, var/mob/user) + var/obj/item/implant/imp_e = locate(src.type) in source + if(!allow_multiple && imp_e && imp_e != src) + if(imp_e.uses < initial(imp_e.uses)*2) + if(uses == -1) + imp_e.uses = -1 + else + imp_e.uses = min(imp_e.uses + uses, initial(imp_e.uses)*2) + qdel(src) + return 1 + else + return 0 + + + src.loc = source + imp_in = source + implanted = 1 + if(activated) + for(var/X in actions) + var/datum/action/A = X + A.Grant(source) + if(ishuman(source)) + var/mob/living/carbon/human/H = source + H.sec_hud_set_implants() + + if(user) + add_attack_logs(user, source, "Implanted with [src]") + + return 1 + +/obj/item/implant/proc/removed(var/mob/source) + src.loc = null + imp_in = null + implanted = 0 + + for(var/X in actions) + var/datum/action/A = X + A.Grant(source) + + if(ishuman(source)) + var/mob/living/carbon/human/H = source + H.sec_hud_set_implants() + + return 1 + +/obj/item/implant/Destroy() + if(imp_in) + removed(imp_in) + return ..() + + +/obj/item/implant/proc/get_data() + return "No information available" + +/obj/item/implant/dropped(mob/user) + . = 1 + ..() diff --git a/code/game/objects/items/weapons/implants/implant_abductor.dm b/code/game/objects/items/weapons/implants/implant_abductor.dm index 64bb7345f7a..8967f364078 100644 --- a/code/game/objects/items/weapons/implants/implant_abductor.dm +++ b/code/game/objects/items/weapons/implants/implant_abductor.dm @@ -44,4 +44,4 @@ if(c.team == team) console = c break - return console \ No newline at end of file + return console diff --git a/code/game/objects/items/weapons/implants/implant_explosive.dm b/code/game/objects/items/weapons/implants/implant_explosive.dm index f37e148bee6..52262ac8edb 100644 --- a/code/game/objects/items/weapons/implants/implant_explosive.dm +++ b/code/game/objects/items/weapons/implants/implant_explosive.dm @@ -174,4 +174,4 @@ /obj/item/implanter/dust/New() imp = new /obj/item/implant/dust(src) - ..() \ No newline at end of file + ..() diff --git a/code/game/objects/items/weapons/implants/implant_krav_maga.dm b/code/game/objects/items/weapons/implants/implant_krav_maga.dm index 1a633d80151..3c2666f3d66 100644 --- a/code/game/objects/items/weapons/implants/implant_krav_maga.dm +++ b/code/game/objects/items/weapons/implants/implant_krav_maga.dm @@ -37,4 +37,4 @@ /obj/item/implantcase/krav_maga/New() imp = new /obj/item/implant/krav_maga(src) - ..() \ No newline at end of file + ..() diff --git a/code/game/objects/items/weapons/implants/implant_mindshield.dm b/code/game/objects/items/weapons/implants/implant_mindshield.dm index bc942b73d81..f9cf7e4cff1 100644 --- a/code/game/objects/items/weapons/implants/implant_mindshield.dm +++ b/code/game/objects/items/weapons/implants/implant_mindshield.dm @@ -56,4 +56,4 @@ /obj/item/implantcase/mindshield/New() imp = new /obj/item/implant/mindshield(src) - ..() \ No newline at end of file + ..() diff --git a/code/game/objects/items/weapons/implants/implant_track.dm b/code/game/objects/items/weapons/implants/implant_track.dm index c74e2fecff5..af37768f469 100644 --- a/code/game/objects/items/weapons/implants/implant_track.dm +++ b/code/game/objects/items/weapons/implants/implant_track.dm @@ -37,4 +37,4 @@ /obj/item/implantcase/track/New() imp = new /obj/item/implant/tracking(src) - ..() \ No newline at end of file + ..() diff --git a/code/game/objects/items/weapons/implants/implantcase.dm b/code/game/objects/items/weapons/implants/implantcase.dm index df7ecdceb6d..965c5ca7d94 100644 --- a/code/game/objects/items/weapons/implants/implantcase.dm +++ b/code/game/objects/items/weapons/implants/implantcase.dm @@ -1,102 +1,102 @@ -/obj/item/implantcase - name = "implant case" - desc = "A glass case containing an implant." - icon = 'icons/obj/items.dmi' - icon_state = "implantcase-0" - item_state = "implantcase" - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - origin_tech = "materials=1;biotech=2" - container_type = OPENCONTAINER | INJECTABLE | DRAWABLE - materials = list(MAT_GLASS=500) - var/obj/item/implant/imp = null - - -/obj/item/implantcase/update_icon() - if(imp) - icon_state = "implantcase-[imp.item_color]" - origin_tech = imp.origin_tech - flags = imp.flags & ~DROPDEL - reagents = imp.reagents - else - icon_state = "implantcase-0" - origin_tech = initial(origin_tech) - flags = initial(flags) - reagents = null - - -/obj/item/implantcase/attackby(obj/item/W, mob/user, params) - ..() - if(istype(W, /obj/item/pen)) - var/t = stripped_input(user, "What would you like the label to be?", name, null) - if(user.get_active_hand() != W) - return - if(!in_range(src, user) && loc != user) - return - if(t) - name = "implant case - '[t]'" - else - name = "implant case" - else if(istype(W, /obj/item/implanter)) - var/obj/item/implanter/I = W - if(I.imp) - if(imp || I.imp.implanted) - return - I.imp.loc = src - imp = I.imp - I.imp = null - update_icon() - I.update_icon() - else - if(imp) - if(I.imp) - return - imp.loc = I - I.imp = imp - imp = null - update_icon() - I.update_icon() - - /*else if(istype(W, /obj/item/ammo_casing/shotgun/implanter)) - var/obj/item/ammo_casing/shotgun/implanter/I = W - if(I.implanter) - src.attackby(I.implanter, user, params) */ // COMING SOON -- c0 - -/obj/item/implantcase/New() - ..() - update_icon() - - -/obj/item/implantcase/tracking - name = "implant case - 'Tracking'" - desc = "A glass case containing a tracking implant." - -/obj/item/implantcase/tracking/New() - imp = new /obj/item/implant/tracking(src) - ..() - - -/obj/item/implantcase/weapons_auth - name = "implant case - 'Firearms Authentication'" - desc = "A glass case containing a firearms authentication implant." - -/obj/item/implantcase/weapons_auth/New() - imp = new /obj/item/implant/weapons_auth(src) - ..() - -/obj/item/implantcase/adrenaline - name = "implant case - 'Adrenaline'" - desc = "A glass case containing an adrenaline implant." - -/obj/item/implantcase/adrenaline/New() - imp = new /obj/item/implant/adrenalin(src) - ..() - -/obj/item/implantcase/death_alarm - name = "Glass Case- 'Death Alarm'" - desc = "A case containing a death alarm implant." - -/obj/item/implantcase/death_alarm/New() - imp = new /obj/item/implant/death_alarm(src) - ..() +/obj/item/implantcase + name = "implant case" + desc = "A glass case containing an implant." + icon = 'icons/obj/items.dmi' + icon_state = "implantcase-0" + item_state = "implantcase" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + origin_tech = "materials=1;biotech=2" + container_type = OPENCONTAINER | INJECTABLE | DRAWABLE + materials = list(MAT_GLASS=500) + var/obj/item/implant/imp = null + + +/obj/item/implantcase/update_icon() + if(imp) + icon_state = "implantcase-[imp.item_color]" + origin_tech = imp.origin_tech + flags = imp.flags & ~DROPDEL + reagents = imp.reagents + else + icon_state = "implantcase-0" + origin_tech = initial(origin_tech) + flags = initial(flags) + reagents = null + + +/obj/item/implantcase/attackby(obj/item/W, mob/user, params) + ..() + if(istype(W, /obj/item/pen)) + var/t = stripped_input(user, "What would you like the label to be?", name, null) + if(user.get_active_hand() != W) + return + if(!in_range(src, user) && loc != user) + return + if(t) + name = "implant case - '[t]'" + else + name = "implant case" + else if(istype(W, /obj/item/implanter)) + var/obj/item/implanter/I = W + if(I.imp) + if(imp || I.imp.implanted) + return + I.imp.loc = src + imp = I.imp + I.imp = null + update_icon() + I.update_icon() + else + if(imp) + if(I.imp) + return + imp.loc = I + I.imp = imp + imp = null + update_icon() + I.update_icon() + + /*else if(istype(W, /obj/item/ammo_casing/shotgun/implanter)) + var/obj/item/ammo_casing/shotgun/implanter/I = W + if(I.implanter) + src.attackby(I.implanter, user, params) */ // COMING SOON -- c0 + +/obj/item/implantcase/New() + ..() + update_icon() + + +/obj/item/implantcase/tracking + name = "implant case - 'Tracking'" + desc = "A glass case containing a tracking implant." + +/obj/item/implantcase/tracking/New() + imp = new /obj/item/implant/tracking(src) + ..() + + +/obj/item/implantcase/weapons_auth + name = "implant case - 'Firearms Authentication'" + desc = "A glass case containing a firearms authentication implant." + +/obj/item/implantcase/weapons_auth/New() + imp = new /obj/item/implant/weapons_auth(src) + ..() + +/obj/item/implantcase/adrenaline + name = "implant case - 'Adrenaline'" + desc = "A glass case containing an adrenaline implant." + +/obj/item/implantcase/adrenaline/New() + imp = new /obj/item/implant/adrenalin(src) + ..() + +/obj/item/implantcase/death_alarm + name = "Glass Case- 'Death Alarm'" + desc = "A case containing a death alarm implant." + +/obj/item/implantcase/death_alarm/New() + imp = new /obj/item/implant/death_alarm(src) + ..() diff --git a/code/game/objects/items/weapons/implants/implantchair.dm b/code/game/objects/items/weapons/implants/implantchair.dm index 5727fe08cd0..d74ab31a6c3 100644 --- a/code/game/objects/items/weapons/implants/implantchair.dm +++ b/code/game/objects/items/weapons/implants/implantchair.dm @@ -1,158 +1,158 @@ -/obj/machinery/implantchair - name = "mindshield implanter" - desc = "Used to implant occupants with mindshield implants." - icon = 'icons/obj/machines/implantchair.dmi' - icon_state = "implantchair" - density = 1 - opacity = 0 - anchored = 1 - - var/ready = 1 - var/malfunction = 0 - var/list/obj/item/implant/mindshield/implant_list = list() - var/max_implants = 5 - var/injection_cooldown = 600 - var/replenish_cooldown = 6000 - var/replenishing = 0 - var/mob/living/carbon/occupant = null - var/injecting = 0 - -/obj/machinery/implantchair/proc - go_out() - put_mob(mob/living/carbon/M) - implant(var/mob/M) - add_implants() - - -/obj/machinery/implantchair/New() - ..() - add_implants() - - -/obj/machinery/implantchair/attack_hand(mob/user) - user.set_machine(src) - var/health_text = "" - if(src.occupant) - if(src.occupant.health <= -100) - health_text = "Dead" - else if(src.occupant.health < 0) - health_text = "[round(src.occupant.health,0.1)]" - else - health_text = "[round(src.occupant.health,0.1)]" - - var/dat ="Implanter Status
        " - - dat +="Current occupant: [src.occupant ? "
        Name: [src.occupant]
        Health: [health_text]
        " : "None"]
        " - dat += "Implants: [src.implant_list.len ? "[implant_list.len]" : "Replenish"]
        " - if(src.occupant) - dat += "[src.ready ? "Implant" : "Recharging"]
        " - user.set_machine(src) - user << browse(dat, "window=implant") - onclose(user, "implant") - - -/obj/machinery/implantchair/Topic(href, href_list) - if(..()) - return - if(href_list["implant"]) - if(src.occupant) - injecting = 1 - go_out() - ready = 0 - spawn(injection_cooldown) - ready = 1 - - if(href_list["replenish"]) - ready = 0 - spawn(replenish_cooldown) - add_implants() - ready = 1 - - src.updateUsrDialog() - return - - -/obj/machinery/implantchair/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/grab)) - var/obj/item/grab/G = W - if(!ismob(G.affecting)) - return - var/mob/M = G.affecting - if(M.has_buckled_mobs()) - to_chat(user, "[M] will not fit into [src] because [M.p_they()] [M.p_have()] a slime latched onto [M.p_their()] head.") - return - if(put_mob(M)) - qdel(G) - src.updateUsrDialog() - return - - -/obj/machinery/implantchair/go_out(mob/M) - if(!( src.occupant )) - return - if(M == occupant) // so that the guy inside can't eject himself -Agouri - return - occupant.forceMove(loc) - if(injecting) - implant(src.occupant) - injecting = 0 - src.occupant = null - icon_state = "implantchair" - return - - -/obj/machinery/implantchair/put_mob(mob/living/carbon/M) - if(!iscarbon(M)) - to_chat(usr, "The [src.name] cannot hold this!") - return - if(src.occupant) - to_chat(usr, "The [src.name] is already occupied!") - return - M.stop_pulling() - M.forceMove(src) - src.occupant = M - src.add_fingerprint(usr) - icon_state = "implantchair_on" - return 1 - - -/obj/machinery/implantchair/implant(mob/M) - if(!istype(M, /mob/living/carbon)) - return - if(!implant_list.len) return - for(var/obj/item/implant/mindshield/imp in implant_list) - if(!imp) continue - if(istype(imp, /obj/item/implant/mindshield)) - M.visible_message("[M] has been implanted by the [src.name].") - - if(imp.implant(M)) - implant_list -= imp - break - return - - -/obj/machinery/implantchair/add_implants() - for(var/i=0, iName: [src.occupant]
        Health: [health_text]
        " : "None"]
        " + dat += "Implants: [src.implant_list.len ? "[implant_list.len]" : "Replenish"]
        " + if(src.occupant) + dat += "[src.ready ? "Implant" : "Recharging"]
        " + user.set_machine(src) + user << browse(dat, "window=implant") + onclose(user, "implant") + + +/obj/machinery/implantchair/Topic(href, href_list) + if(..()) + return + if(href_list["implant"]) + if(src.occupant) + injecting = 1 + go_out() + ready = 0 + spawn(injection_cooldown) + ready = 1 + + if(href_list["replenish"]) + ready = 0 + spawn(replenish_cooldown) + add_implants() + ready = 1 + + src.updateUsrDialog() + return + + +/obj/machinery/implantchair/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/grab)) + var/obj/item/grab/G = W + if(!ismob(G.affecting)) + return + var/mob/M = G.affecting + if(M.has_buckled_mobs()) + to_chat(user, "[M] will not fit into [src] because [M.p_they()] [M.p_have()] a slime latched onto [M.p_their()] head.") + return + if(put_mob(M)) + qdel(G) + src.updateUsrDialog() + return + + +/obj/machinery/implantchair/go_out(mob/M) + if(!( src.occupant )) + return + if(M == occupant) // so that the guy inside can't eject himself -Agouri + return + occupant.forceMove(loc) + if(injecting) + implant(src.occupant) + injecting = 0 + src.occupant = null + icon_state = "implantchair" + return + + +/obj/machinery/implantchair/put_mob(mob/living/carbon/M) + if(!iscarbon(M)) + to_chat(usr, "The [src.name] cannot hold this!") + return + if(src.occupant) + to_chat(usr, "The [src.name] is already occupied!") + return + M.stop_pulling() + M.forceMove(src) + src.occupant = M + src.add_fingerprint(usr) + icon_state = "implantchair_on" + return 1 + + +/obj/machinery/implantchair/implant(mob/M) + if(!istype(M, /mob/living/carbon)) + return + if(!implant_list.len) return + for(var/obj/item/implant/mindshield/imp in implant_list) + if(!imp) continue + if(istype(imp, /obj/item/implant/mindshield)) + M.visible_message("[M] has been implanted by the [src.name].") + + if(imp.implant(M)) + implant_list -= imp + break + return + + +/obj/machinery/implantchair/add_implants() + for(var/i=0, i[user] is attemping to implant [M].
        ") - - var/turf/T = get_turf(M) - if(T && (M == user || do_after(user, 50 * toolspeed, target = M))) - if(user && M && (get_turf(M) == T) && src && imp) - if(imp.implant(M, user)) - if(M == user) - to_chat(user, "You implant yourself.") - else - M.visible_message("[user] has implanted [M].", "[user] implants you.") - imp = null - update_icon() - -/obj/item/implanter/attackby(obj/item/W, mob/user, params) - ..() - if(istype(W, /obj/item/pen)) - var/t = stripped_input(user, "What would you like the label to be?", name, null) - if(user.get_active_hand() != W) - return - if(!in_range(src, user) && loc != user) - return - if(t) - name = "implanter ([t])" - else - name = "implanter" - -/obj/item/implanter/New() - ..() - spawn(1) - update_icon() - - -/obj/item/implanter/adrenalin - name = "implanter (adrenalin)" - -/obj/item/implanter/adrenalin/New() - imp = new /obj/item/implant/adrenalin(src) - ..() - - -/obj/item/implanter/emp - name = "implanter (EMP)" - -/obj/item/implanter/emp/New() - imp = new /obj/item/implant/emp(src) - ..() - -/obj/item/implanter/traitor - name = "implanter (Mindslave)" - -/obj/item/implanter/traitor/New() - imp = new /obj/item/implant/traitor(src) - ..() - -/obj/item/implanter/death_alarm - name = "implanter (Death Alarm)" - -/obj/item/implanter/death_alarm/New() - imp = new /obj/item/implant/death_alarm(src) - ..() +/obj/item/implanter + name = "implanter" + desc = "A sterile automatic implant injector." + icon = 'icons/obj/items.dmi' + icon_state = "implanter0" + item_state = "syringe_0" + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + origin_tech = "materials=2;biotech=3" + materials = list(MAT_METAL=600, MAT_GLASS=200) + toolspeed = 1 + var/obj/item/implant/imp = null + + +/obj/item/implanter/update_icon() + if(imp) + icon_state = "implanter1" + origin_tech = imp.origin_tech + else + icon_state = "implanter0" + origin_tech = initial(origin_tech) + + +/obj/item/implanter/attack(mob/living/carbon/M, mob/user) + if(!iscarbon(M)) + return + if(user && imp) + if(M != user) + M.visible_message("[user] is attemping to implant [M].") + + var/turf/T = get_turf(M) + if(T && (M == user || do_after(user, 50 * toolspeed, target = M))) + if(user && M && (get_turf(M) == T) && src && imp) + if(imp.implant(M, user)) + if(M == user) + to_chat(user, "You implant yourself.") + else + M.visible_message("[user] has implanted [M].", "[user] implants you.") + imp = null + update_icon() + +/obj/item/implanter/attackby(obj/item/W, mob/user, params) + ..() + if(istype(W, /obj/item/pen)) + var/t = stripped_input(user, "What would you like the label to be?", name, null) + if(user.get_active_hand() != W) + return + if(!in_range(src, user) && loc != user) + return + if(t) + name = "implanter ([t])" + else + name = "implanter" + +/obj/item/implanter/New() + ..() + spawn(1) + update_icon() + + +/obj/item/implanter/adrenalin + name = "implanter (adrenalin)" + +/obj/item/implanter/adrenalin/New() + imp = new /obj/item/implant/adrenalin(src) + ..() + + +/obj/item/implanter/emp + name = "implanter (EMP)" + +/obj/item/implanter/emp/New() + imp = new /obj/item/implant/emp(src) + ..() + +/obj/item/implanter/traitor + name = "implanter (Mindslave)" + +/obj/item/implanter/traitor/New() + imp = new /obj/item/implant/traitor(src) + ..() + +/obj/item/implanter/death_alarm + name = "implanter (Death Alarm)" + +/obj/item/implanter/death_alarm/New() + imp = new /obj/item/implant/death_alarm(src) + ..() diff --git a/code/game/objects/items/weapons/implants/implantpad.dm b/code/game/objects/items/weapons/implants/implantpad.dm index b9b93b035ec..5f1cf0ae103 100644 --- a/code/game/objects/items/weapons/implants/implantpad.dm +++ b/code/game/objects/items/weapons/implants/implantpad.dm @@ -1,107 +1,107 @@ -/obj/item/implantpad - name = "implantpad" - desc = "Used to modify implants." - icon = 'icons/obj/items.dmi' - icon_state = "implantpad-0" - item_state = "electronic" - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - var/obj/item/implantcase/case = null - -/obj/item/implantpad/Destroy() - if(case) - dropcase() - return ..() - -/obj/item/implantpad/update_icon() - if(case) - src.icon_state = "implantpad-1" - else - src.icon_state = "implantpad-0" - return - -/obj/item/implantpad/proc/addcase(mob/user as mob, obj/item/implantcase/C as obj) - if(!user || !C) - return - if(case) - to_chat(user, "There's already an implant in the pad!") - return - user.unEquip(C) - C.forceMove(src) - case = C - update_icon() - -/obj/item/implantpad/proc/dropcase(mob/user as mob) - if(!case) - to_chat(user, "There's no implant in the pad!") - return - if(user) - if(user.put_in_hands(case)) - add_fingerprint(user) - case.add_fingerprint(user) - case = null - update_icon() - return - - case.forceMove(get_turf(src)) - case = null - update_icon() - -/obj/item/implantpad/verb/remove_implant() - set category = "Object" - set name = "Remove Implant" - set src in usr - - if(usr.stat || usr.restrained()) - return - - dropcase(usr) - -/obj/item/implantpad/attackby(obj/item/implantcase/C as obj, mob/user as mob, params) - if(istype(C, /obj/item/implantcase)) - addcase(user, C) - else - return ..() - -/obj/item/implantpad/attack_self(mob/user as mob) - add_fingerprint(user) - user.set_machine(src) - var/dat = "Implant Mini-Computer:
        " - if(case) - if(case.imp) - if(istype(case.imp, /obj/item/implant)) - dat += "Remove Case
        " - dat += case.imp.get_data() - if(istype(case.imp, /obj/item/implant/tracking)) - var/obj/item/implant/tracking/T = case.imp - dat += {"ID (1-100): - - - - [T.id] - + - +
        "} - else - dat += "The implant casing is empty." - else - dat += "Please insert an implant casing!" - user << browse(dat, "window=implantpad") - onclose(user, "implantpad") - return - - -/obj/item/implantpad/Topic(href, href_list) - if(..()) - return 1 - - var/mob/living/user = usr - if(href_list["tracking_id"]) - if(case && case.imp) - var/obj/item/implant/tracking/T = case.imp - T.id += text2num(href_list["tracking_id"]) - T.id = min(100, T.id) - T.id = max(1, T.id) - else if(href_list["removecase"]) - dropcase(user) - - attack_self(user) - return 1 +/obj/item/implantpad + name = "implantpad" + desc = "Used to modify implants." + icon = 'icons/obj/items.dmi' + icon_state = "implantpad-0" + item_state = "electronic" + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + var/obj/item/implantcase/case = null + +/obj/item/implantpad/Destroy() + if(case) + dropcase() + return ..() + +/obj/item/implantpad/update_icon() + if(case) + src.icon_state = "implantpad-1" + else + src.icon_state = "implantpad-0" + return + +/obj/item/implantpad/proc/addcase(mob/user as mob, obj/item/implantcase/C as obj) + if(!user || !C) + return + if(case) + to_chat(user, "There's already an implant in the pad!") + return + user.unEquip(C) + C.forceMove(src) + case = C + update_icon() + +/obj/item/implantpad/proc/dropcase(mob/user as mob) + if(!case) + to_chat(user, "There's no implant in the pad!") + return + if(user) + if(user.put_in_hands(case)) + add_fingerprint(user) + case.add_fingerprint(user) + case = null + update_icon() + return + + case.forceMove(get_turf(src)) + case = null + update_icon() + +/obj/item/implantpad/verb/remove_implant() + set category = "Object" + set name = "Remove Implant" + set src in usr + + if(usr.stat || usr.restrained()) + return + + dropcase(usr) + +/obj/item/implantpad/attackby(obj/item/implantcase/C as obj, mob/user as mob, params) + if(istype(C, /obj/item/implantcase)) + addcase(user, C) + else + return ..() + +/obj/item/implantpad/attack_self(mob/user as mob) + add_fingerprint(user) + user.set_machine(src) + var/dat = "Implant Mini-Computer:
        " + if(case) + if(case.imp) + if(istype(case.imp, /obj/item/implant)) + dat += "Remove Case
        " + dat += case.imp.get_data() + if(istype(case.imp, /obj/item/implant/tracking)) + var/obj/item/implant/tracking/T = case.imp + dat += {"ID (1-100): + - + - [T.id] + + + +
        "} + else + dat += "The implant casing is empty." + else + dat += "Please insert an implant casing!" + user << browse(dat, "window=implantpad") + onclose(user, "implantpad") + return + + +/obj/item/implantpad/Topic(href, href_list) + if(..()) + return 1 + + var/mob/living/user = usr + if(href_list["tracking_id"]) + if(case && case.imp) + var/obj/item/implant/tracking/T = case.imp + T.id += text2num(href_list["tracking_id"]) + T.id = min(100, T.id) + T.id = max(1, T.id) + else if(href_list["removecase"]) + dropcase(user) + + attack_self(user) + return 1 diff --git a/code/game/objects/items/weapons/implants/implantuplink.dm b/code/game/objects/items/weapons/implants/implantuplink.dm index d6d1bb5bac7..3d5e2e37591 100644 --- a/code/game/objects/items/weapons/implants/implantuplink.dm +++ b/code/game/objects/items/weapons/implants/implantuplink.dm @@ -1,45 +1,45 @@ -/obj/item/implant/uplink - name = "uplink implant" - desc = "Summon things." - icon = 'icons/obj/radio.dmi' - icon_state = "radio" - origin_tech = "materials=4;magnets=4;programming=4;biotech=4;syndicate=5;bluespace=5" - -/obj/item/implant/uplink/New() - hidden_uplink = new(src) - hidden_uplink.uses = 10 - ..() - -/obj/item/implant/uplink/sit/New() - ..() - if(hidden_uplink) - hidden_uplink.uplink_type = "sit" - -/obj/item/implant/uplink/admin/New() - ..() - if(hidden_uplink) - hidden_uplink.uplink_type = "admin" - -/obj/item/implant/uplink/implant(mob/source) - var/obj/item/implant/imp_e = locate(src.type) in source - if(imp_e && imp_e != src) - imp_e.hidden_uplink.uses += hidden_uplink.uses - qdel(src) - return 1 - - if(..()) - hidden_uplink.uplink_owner="[source.key]" - return 1 - return 0 - -/obj/item/implant/uplink/activate() - if(hidden_uplink) - hidden_uplink.check_trigger(imp_in) - - -/obj/item/implanter/uplink - name = "implanter (uplink)" - -/obj/item/implanter/uplink/New() - imp = new /obj/item/implant/uplink(src) - ..() +/obj/item/implant/uplink + name = "uplink implant" + desc = "Summon things." + icon = 'icons/obj/radio.dmi' + icon_state = "radio" + origin_tech = "materials=4;magnets=4;programming=4;biotech=4;syndicate=5;bluespace=5" + +/obj/item/implant/uplink/New() + hidden_uplink = new(src) + hidden_uplink.uses = 10 + ..() + +/obj/item/implant/uplink/sit/New() + ..() + if(hidden_uplink) + hidden_uplink.uplink_type = "sit" + +/obj/item/implant/uplink/admin/New() + ..() + if(hidden_uplink) + hidden_uplink.uplink_type = "admin" + +/obj/item/implant/uplink/implant(mob/source) + var/obj/item/implant/imp_e = locate(src.type) in source + if(imp_e && imp_e != src) + imp_e.hidden_uplink.uses += hidden_uplink.uses + qdel(src) + return 1 + + if(..()) + hidden_uplink.uplink_owner="[source.key]" + return 1 + return 0 + +/obj/item/implant/uplink/activate() + if(hidden_uplink) + hidden_uplink.check_trigger(imp_in) + + +/obj/item/implanter/uplink + name = "implanter (uplink)" + +/obj/item/implanter/uplink/New() + imp = new /obj/item/implant/uplink(src) + ..() diff --git a/code/game/objects/items/weapons/kitchen.dm b/code/game/objects/items/weapons/kitchen.dm index 77f133b775c..9934ce1c0ab 100644 --- a/code/game/objects/items/weapons/kitchen.dm +++ b/code/game/objects/items/weapons/kitchen.dm @@ -1,307 +1,307 @@ -/* Kitchen tools - * Contains: - * Utensils - * Spoons - * Forks - * Knives - * Kitchen knives - * Butcher's cleaver - * Rolling Pins - * Candy Moulds - * Sushi Mat - * Circular cutter - */ - -/obj/item/kitchen - icon = 'icons/obj/kitchen.dmi' - origin_tech = "materials=1" - - - - -/* - * Utensils - */ -/obj/item/kitchen/utensil - force = 5.0 - w_class = WEIGHT_CLASS_TINY - throwforce = 0.0 - throw_speed = 3 - throw_range = 5 - flags = CONDUCT - attack_verb = list("attacked", "stabbed", "poked") - hitsound = 'sound/weapons/bladeslice.ogg' - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - sharp = 0 - var/max_contents = 1 - -/obj/item/kitchen/utensil/New() - ..() - if(prob(60)) - src.pixel_y = rand(0, 4) - - create_reagents(5) - -/obj/item/kitchen/utensil/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) - if(!istype(M)) - return ..() - - if(user.a_intent != INTENT_HELP) - if(user.zone_selected == "head" || user.zone_selected == "eyes") - if((CLUMSY in user.mutations) && prob(50)) - M = user - return eyestab(M,user) - else - return ..() - - if(contents.len) - var/obj/item/reagent_containers/food/snacks/toEat = contents[1] - if(istype(toEat)) - if(M.eat(toEat, user)) - toEat.On_Consume(M, user) - spawn(0) - if(toEat) - qdel(toEat) - overlays.Cut() - return - - -/obj/item/kitchen/utensil/fork - name = "fork" - desc = "It's a fork. Sure is pointy." - icon_state = "fork" - -/obj/item/kitchen/utensil/pfork - name = "plastic fork" - desc = "Yay, no washing up to do." - icon_state = "pfork" - -/obj/item/kitchen/utensil/spoon - name = "spoon" - desc = "It's a spoon. You can see your own upside-down face in it." - icon_state = "spoon" - attack_verb = list("attacked", "poked") - -/obj/item/kitchen/utensil/pspoon - name = "plastic spoon" - desc = "It's a plastic spoon. How dull." - icon_state = "pspoon" - attack_verb = list("attacked", "poked") - -/obj/item/kitchen/utensil/spork - name = "spork" - desc = "It's a spork. Marvel at its innovative design." - icon_state = "spork" - attack_verb = list("attacked", "sporked") - -/obj/item/kitchen/utensil/pspork - name = "plastic spork" - desc = "It's a plastic spork. It's the fork side of the spoon!" - icon_state = "pspork" - attack_verb = list("attacked", "sporked") - -/* - * Knives - */ -/obj/item/kitchen/knife - name = "kitchen knife" - icon_state = "knife" - desc = "A general purpose Chef's Knife made by SpaceCook Incorporated. Guaranteed to stay sharp for years to come." - flags = CONDUCT - force = 10 - w_class = WEIGHT_CLASS_SMALL - throwforce = 10 - hitsound = 'sound/weapons/bladeslice.ogg' - throw_speed = 3 - throw_range = 6 - materials = list(MAT_METAL=12000) - attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - sharp = TRUE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - var/bayonet = FALSE //Can this be attached to a gun? - -/obj/item/kitchen/knife/suicide_act(mob/user) - user.visible_message(pick("[user] is slitting [user.p_their()] wrists with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ - "[user] is slitting [user.p_their()] throat with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ - "[user] is slitting [user.p_their()] stomach open with the [name]! It looks like [user.p_theyre()] trying to commit seppuku.")) - return BRUTELOSS - -/obj/item/kitchen/knife/plastic - name = "plastic knife" - desc = "The bluntest of blades." - icon_state = "pknife" - item_state = "knife" - sharp = 0 - -/obj/item/kitchen/knife/ritual - name = "ritual knife" - desc = "The unearthly energies that once powered this blade are now dormant." - icon = 'icons/obj/wizard.dmi' - icon_state = "render" - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/kitchen/knife/butcher - name = "butcher's cleaver" - icon_state = "butch" - desc = "A huge thing used for chopping and chopping up meat. This includes clowns and clown-by-products." - flags = CONDUCT - force = 15 - throwforce = 8 - attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/kitchen/knife/butcher/meatcleaver - name = "meat cleaver" - icon_state = "mcleaver" - item_state = "butch" - force = 25 - throwforce = 15 - -/obj/item/kitchen/knife/combat - name = "combat knife" - icon_state = "combatknife" - item_state = "knife" - desc = "A military combat utility survival knife." - force = 20 - throwforce = 20 - origin_tech = "materials=3;combat=4" - attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut") - bayonet = TRUE - -/obj/item/kitchen/knife/combat/survival - name = "survival knife" - icon_state = "survivalknife" - desc = "A hunting grade survival knife." - force = 15 - throwforce = 15 - -/obj/item/kitchen/knife/combat/survival/bone - name = "bone dagger" - item_state = "bone_dagger" - icon_state = "bone_dagger" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - desc = "A sharpened bone. The bare minimum in survival." - materials = list() - -/obj/item/kitchen/knife/combat/cyborg - name = "cyborg knife" - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "knife" - desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." - origin_tech = null - -/obj/item/kitchen/knife/carrotshiv - name = "carrot shiv" - icon_state = "carrotshiv" - item_state = "carrotshiv" - desc = "Unlike other carrots, you should probably keep this far away from your eyes." - force = 8 - throwforce = 12 //fuck git - materials = list() - origin_tech = "biotech=3;combat=2" - attack_verb = list("shanked", "shivved") - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - - -/* - * Rolling Pins - */ - -/obj/item/kitchen/rollingpin - name = "rolling pin" - desc = "Used to knock out the Bartender." - icon_state = "rolling_pin" - force = 8.0 - throwforce = 10.0 - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - -/* Trays moved to /obj/item/storage/bag */ - -/* - * Candy Moulds - */ - -/obj/item/kitchen/mould - name = "generic candy mould" - desc = "You aren't sure what it's supposed to be." - icon_state = "mould" - force = 5 - throwforce = 5 - throw_speed = 3 - throw_range = 3 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed") - -/obj/item/kitchen/mould/bear - name = "bear-shaped candy mould" - desc = "It has the shape of a small bear imprinted into it." - icon_state = "mould_bear" - -/obj/item/kitchen/mould/worm - name = "worm-shaped candy mould" - desc = "It has the shape of a worm imprinted into it." - icon_state = "mould_worm" - -/obj/item/kitchen/mould/bean - name = "bean-shaped candy mould" - desc = "It has the shape of a bean imprinted into it." - icon_state = "mould_bean" - -/obj/item/kitchen/mould/ball - name = "ball-shaped candy mould" - desc = "It has a small sphere imprinted into it." - icon_state = "mould_ball" - -/obj/item/kitchen/mould/cane - name = "cane-shaped candy mould" - desc = "It has the shape of a cane imprinted into it." - icon_state = "mould_cane" - -/obj/item/kitchen/mould/cash - name = "cash-shaped candy mould" - desc = "It has the shape and design of fake money imprinted into it." - icon_state = "mould_cash" - -/obj/item/kitchen/mould/coin - name = "coin-shaped candy mould" - desc = "It has the shape of a coin imprinted into it." - icon_state = "mould_coin" - -/obj/item/kitchen/mould/loli - name = "sucker mould" - desc = "It has the shape of a sucker imprinted into it." - icon_state = "mould_loli" - -/* - * Sushi Mat - */ -/obj/item/kitchen/sushimat - name = "Sushi Mat" - desc = "A wooden mat used for efficient sushi crafting." - icon_state = "sushi_mat" - force = 5 - throwforce = 5 - throw_speed = 3 - throw_range = 3 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("rolled", "cracked", "battered", "thrashed") - - - -/// circular cutter by Ume - -/obj/item/kitchen/cutter - name = "generic circular cutter" - desc = "A generic circular cutter for cookies and other things." - icon = 'icons/obj/kitchen.dmi' - icon_state = "circular_cutter" - force = 5 - throwforce = 5 - throw_speed = 3 - throw_range = 3 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("bashed", "slashed", "pricked", "thrashed") \ No newline at end of file +/* Kitchen tools + * Contains: + * Utensils + * Spoons + * Forks + * Knives + * Kitchen knives + * Butcher's cleaver + * Rolling Pins + * Candy Moulds + * Sushi Mat + * Circular cutter + */ + +/obj/item/kitchen + icon = 'icons/obj/kitchen.dmi' + origin_tech = "materials=1" + + + + +/* + * Utensils + */ +/obj/item/kitchen/utensil + force = 5.0 + w_class = WEIGHT_CLASS_TINY + throwforce = 0.0 + throw_speed = 3 + throw_range = 5 + flags = CONDUCT + attack_verb = list("attacked", "stabbed", "poked") + hitsound = 'sound/weapons/bladeslice.ogg' + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + sharp = 0 + var/max_contents = 1 + +/obj/item/kitchen/utensil/New() + ..() + if(prob(60)) + src.pixel_y = rand(0, 4) + + create_reagents(5) + +/obj/item/kitchen/utensil/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) + if(!istype(M)) + return ..() + + if(user.a_intent != INTENT_HELP) + if(user.zone_selected == "head" || user.zone_selected == "eyes") + if((CLUMSY in user.mutations) && prob(50)) + M = user + return eyestab(M,user) + else + return ..() + + if(contents.len) + var/obj/item/reagent_containers/food/snacks/toEat = contents[1] + if(istype(toEat)) + if(M.eat(toEat, user)) + toEat.On_Consume(M, user) + spawn(0) + if(toEat) + qdel(toEat) + overlays.Cut() + return + + +/obj/item/kitchen/utensil/fork + name = "fork" + desc = "It's a fork. Sure is pointy." + icon_state = "fork" + +/obj/item/kitchen/utensil/pfork + name = "plastic fork" + desc = "Yay, no washing up to do." + icon_state = "pfork" + +/obj/item/kitchen/utensil/spoon + name = "spoon" + desc = "It's a spoon. You can see your own upside-down face in it." + icon_state = "spoon" + attack_verb = list("attacked", "poked") + +/obj/item/kitchen/utensil/pspoon + name = "plastic spoon" + desc = "It's a plastic spoon. How dull." + icon_state = "pspoon" + attack_verb = list("attacked", "poked") + +/obj/item/kitchen/utensil/spork + name = "spork" + desc = "It's a spork. Marvel at its innovative design." + icon_state = "spork" + attack_verb = list("attacked", "sporked") + +/obj/item/kitchen/utensil/pspork + name = "plastic spork" + desc = "It's a plastic spork. It's the fork side of the spoon!" + icon_state = "pspork" + attack_verb = list("attacked", "sporked") + +/* + * Knives + */ +/obj/item/kitchen/knife + name = "kitchen knife" + icon_state = "knife" + desc = "A general purpose Chef's Knife made by SpaceCook Incorporated. Guaranteed to stay sharp for years to come." + flags = CONDUCT + force = 10 + w_class = WEIGHT_CLASS_SMALL + throwforce = 10 + hitsound = 'sound/weapons/bladeslice.ogg' + throw_speed = 3 + throw_range = 6 + materials = list(MAT_METAL=12000) + attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + sharp = TRUE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + var/bayonet = FALSE //Can this be attached to a gun? + +/obj/item/kitchen/knife/suicide_act(mob/user) + user.visible_message(pick("[user] is slitting [user.p_their()] wrists with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ + "[user] is slitting [user.p_their()] throat with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.", \ + "[user] is slitting [user.p_their()] stomach open with the [name]! It looks like [user.p_theyre()] trying to commit seppuku.")) + return BRUTELOSS + +/obj/item/kitchen/knife/plastic + name = "plastic knife" + desc = "The bluntest of blades." + icon_state = "pknife" + item_state = "knife" + sharp = 0 + +/obj/item/kitchen/knife/ritual + name = "ritual knife" + desc = "The unearthly energies that once powered this blade are now dormant." + icon = 'icons/obj/wizard.dmi' + icon_state = "render" + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/kitchen/knife/butcher + name = "butcher's cleaver" + icon_state = "butch" + desc = "A huge thing used for chopping and chopping up meat. This includes clowns and clown-by-products." + flags = CONDUCT + force = 15 + throwforce = 8 + attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/kitchen/knife/butcher/meatcleaver + name = "meat cleaver" + icon_state = "mcleaver" + item_state = "butch" + force = 25 + throwforce = 15 + +/obj/item/kitchen/knife/combat + name = "combat knife" + icon_state = "combatknife" + item_state = "knife" + desc = "A military combat utility survival knife." + force = 20 + throwforce = 20 + origin_tech = "materials=3;combat=4" + attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut") + bayonet = TRUE + +/obj/item/kitchen/knife/combat/survival + name = "survival knife" + icon_state = "survivalknife" + desc = "A hunting grade survival knife." + force = 15 + throwforce = 15 + +/obj/item/kitchen/knife/combat/survival/bone + name = "bone dagger" + item_state = "bone_dagger" + icon_state = "bone_dagger" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + desc = "A sharpened bone. The bare minimum in survival." + materials = list() + +/obj/item/kitchen/knife/combat/cyborg + name = "cyborg knife" + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "knife" + desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." + origin_tech = null + +/obj/item/kitchen/knife/carrotshiv + name = "carrot shiv" + icon_state = "carrotshiv" + item_state = "carrotshiv" + desc = "Unlike other carrots, you should probably keep this far away from your eyes." + force = 8 + throwforce = 12 //fuck git + materials = list() + origin_tech = "biotech=3;combat=2" + attack_verb = list("shanked", "shivved") + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + + +/* + * Rolling Pins + */ + +/obj/item/kitchen/rollingpin + name = "rolling pin" + desc = "Used to knock out the Bartender." + icon_state = "rolling_pin" + force = 8.0 + throwforce = 10.0 + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + +/* Trays moved to /obj/item/storage/bag */ + +/* + * Candy Moulds + */ + +/obj/item/kitchen/mould + name = "generic candy mould" + desc = "You aren't sure what it's supposed to be." + icon_state = "mould" + force = 5 + throwforce = 5 + throw_speed = 3 + throw_range = 3 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "smashed") + +/obj/item/kitchen/mould/bear + name = "bear-shaped candy mould" + desc = "It has the shape of a small bear imprinted into it." + icon_state = "mould_bear" + +/obj/item/kitchen/mould/worm + name = "worm-shaped candy mould" + desc = "It has the shape of a worm imprinted into it." + icon_state = "mould_worm" + +/obj/item/kitchen/mould/bean + name = "bean-shaped candy mould" + desc = "It has the shape of a bean imprinted into it." + icon_state = "mould_bean" + +/obj/item/kitchen/mould/ball + name = "ball-shaped candy mould" + desc = "It has a small sphere imprinted into it." + icon_state = "mould_ball" + +/obj/item/kitchen/mould/cane + name = "cane-shaped candy mould" + desc = "It has the shape of a cane imprinted into it." + icon_state = "mould_cane" + +/obj/item/kitchen/mould/cash + name = "cash-shaped candy mould" + desc = "It has the shape and design of fake money imprinted into it." + icon_state = "mould_cash" + +/obj/item/kitchen/mould/coin + name = "coin-shaped candy mould" + desc = "It has the shape of a coin imprinted into it." + icon_state = "mould_coin" + +/obj/item/kitchen/mould/loli + name = "sucker mould" + desc = "It has the shape of a sucker imprinted into it." + icon_state = "mould_loli" + +/* + * Sushi Mat + */ +/obj/item/kitchen/sushimat + name = "Sushi Mat" + desc = "A wooden mat used for efficient sushi crafting." + icon_state = "sushi_mat" + force = 5 + throwforce = 5 + throw_speed = 3 + throw_range = 3 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("rolled", "cracked", "battered", "thrashed") + + + +/// circular cutter by Ume + +/obj/item/kitchen/cutter + name = "generic circular cutter" + desc = "A generic circular cutter for cookies and other things." + icon = 'icons/obj/kitchen.dmi' + icon_state = "circular_cutter" + force = 5 + throwforce = 5 + throw_speed = 3 + throw_range = 3 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("bashed", "slashed", "pricked", "thrashed") diff --git a/code/game/objects/items/weapons/lighters.dm b/code/game/objects/items/weapons/lighters.dm index 23c55cc1a63..e24346c1ee9 100644 --- a/code/game/objects/items/weapons/lighters.dm +++ b/code/game/objects/items/weapons/lighters.dm @@ -235,4 +235,4 @@ /obj/item/match/firebrand/New() ..() - matchignite() \ No newline at end of file + matchignite() diff --git a/code/game/objects/items/weapons/manuals.dm b/code/game/objects/items/weapons/manuals.dm index 0267daa0934..d91bb29ff76 100644 --- a/code/game/objects/items/weapons/manuals.dm +++ b/code/game/objects/items/weapons/manuals.dm @@ -1,1407 +1,1407 @@ -/*********************MANUALS (BOOKS)***********************/ - -//Oh god what the fuck I am not good at computer -/obj/item/book/manual - icon = 'icons/obj/library.dmi' - due_date = 0 // Game time in 1/10th seconds - unique = 1 // 0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified - - -/obj/item/book/manual/engineering_construction - name = "Station Repairs and Construction" - icon_state ="bookEngineering" - author = "Engineering Encyclopedia" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - title = "Station Repairs and Construction" - dat = {" - - - - - - - - - - - "} - -/obj/item/book/manual/engineering_particle_accelerator - name = "Particle Accelerator User's Guide" - icon_state ="bookParticleAccelerator" - author = "Engineering Encyclopedia" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - title = "Particle Accelerator User's Guide" -//big pile of shit below. - - dat = {" - - - - - -

        Experienced user's guide

        - -

        Setting up

        - -
          -
        1. Wrench all pieces to the floor
        2. -
        3. Add wires to all the pieces
        4. -
        5. Close all the panels with your screwdriver
        6. -
        - -

        Use

        - -
          -
        1. Open the control panel
        2. -
        3. Set the speed to 2
        4. -
        5. Start firing at the singularity generator
        6. -
        7. When the singularity reaches a large enough size so it starts moving on it's own set the speed down to 0, but don't shut it off
        8. -
        9. Remember to wear a radiation suit when working with this machine... we did tell you that at the start, right?
        10. -
        - - - "} - - -/obj/item/book/manual/supermatter_engine - name = "Supermatter Engine User's Guide" - icon_state = "bookParticleAccelerator" //TEMP FIXME - author = "Waleed Asad" - title = "Supermatter Engine User's Guide" - - dat = {"Engineering notes on single-stage Supermatter engine,
        - -Waleed Asad
        - - A word of caution, do not enter the engine room, for any reason, without radiation protection and mesons on. The status of the engine may be unpredictable even when you believe it is .off.. This is an important level of personal protection.

        - - The engine has two basic modes of functionality. He has observed that it is capable of both a safe level of operation and a modified, high output mode.

        - -
        Notes on starting the basic function mode, dubbed .Heat-Primary Mode..


        - - 1. Prepare collector arrays. This is done standard to any text on their function by wrenching them down, filling six plasma tanks with a plasma canister, and inserting the tank into the collectors one by one. Finally, initialize each collector.

        - - 2. Prepare gas system. Before introducing any gas to the Supermatter engine room, it is important to remember the small but vital steps to preparing this section. First, set the input gas pump and output gas flow pump to 4500, or maximum flow. Second, switch the digital switching valve into the .up. position, in order to circulate the gas back toward the coolers and collectors.

        - - 3. Apply N2 gas. Retrieve the two N2 canisters from storage and bring them to the engine room. Attach one of them to the input section of the engine gas system located next to the collectors. Keep it attached until the N2 pressure is low enough to turn the canister light red. Replace it with the second canister to keep N2 pressure at optimal levels.

        - - 4. Begin primary emitter burst series. This means firing a single emitter for its first four shots. It is important to move to this step quickly. The onboard SMES units may not have enough power to run the emitters if left alone too long on-station. This engine can produce enough power on its own to run the entire station, ignoring the SMES units completely, and is wired to do so.

        - - 5. Switch SMES units to primary settings. Maximize input and set the devices to automatically charge, additionally turn their outputs on if they are off unless power is to be saved (Which can be useful in case of later failures.)

        - - 6. Begin secondary emitter burst series. Before firing the emitter again, check the power in the line with a multimeter (Do not forget electrical gloves.) The engine is running at high efficiency when the value exceeds 200,000 power units.

        - - 7. Maintain engine power. When power in the lines gets low, add an additional emitter burst series to bring power to normal levels.


        - - - -
        The second mode for running the engine uses a gas mix to produce a reaction within the Supermatter. This mode requires CE or Atmospheric help to setup. This has been dubbed the .O2-Reaction Mode..


        - - THIS MODE CAN CAUSE A RUNAWAY REACTION, LEADING TO CATASTROPHIC FAILURE IF NOT MAINTAINED. NEVER FORGET ABOUT THE ENGINE IN THIS MODE.

        - - Additionally, this mode can be used for what is called a .Cold Start.. If the station has no power in the SMES to run the emitters, using this mode will allow enough power output to run them, and quickly reach an acceptable level of power output.

        - - 1. Prepare collector arrays. This is done standard to any text on their function by wrenching them down, filling six plasma tanks with a plasma canister, and inserting the tank into the collectors one by one. Finally, initialize each collector.

        - - 2. Prepare gas system. Before introducing any gas to the Supermatter engine room, it is important to remember the small but vital steps to preparing this section. First, set the input gas pump and output gas flow pump to 4500, or maximum flow. Second, switch the digital switching valve into the .up. position, in order to circulate the gas back toward the coolers and collectors.

        - - 3. Modify the engine room filters. Unlike the Heat-Primary Mode, it is important to change the filters attached to the gas system to stop filtering O2, and start filtering Carbon Molecules. O2-Reaction Mode produces far more plasma than Heat-Primary, therefor filtering it off is essential.

        - - 4. Switch SMES units to primary settings. Maximize input and set the devices to automatically charge, additionally turn their outputs on if they are off unless power is to be saved (Which can be useful in case of later failures.) If you check the power in the system lines at this point you will find that it is constantly going up. Indeed, with just the addition of O2 to the Supermatter, it will begin outputting power.

        - - 5. Begin primary emitter burst series. Fire a single emitter for a series of four pulses, or a single series, and turn it off. Do not over power the Supermatter. The reaction is self sustaining and propagating. As long as O2 is in the chamber, it will continue outputting MORE power.

        - - 6. Maintain follow up operations. Remember to check the temp of the core gas and switch to the Heat-Primary function, or vent the core room when problems begin if required.

        - - Notes on Supermatter Reaction Function and Drawbacks-

        - - After several hours of observation an interesting phenomenon was witnessed. The Supermatter undergoes a constant self-sustaining reaction when given an extremely high O2 concentration. Anything about 80% or higher typically will cause this reaction. The Supermatter will continue to react whenever this gas mix is in the same room as the Supermatter.

        - - To understand why O2-Reaction mode is dangerous, the core principle of the Supermatter must be understood. The Supermatter emits three things when .not safe,. that is any time it is giving off power. These things are:

        - - *Radiation (which is converted into power by the collectors,)
        - *Heat (which is removed via the gas exchange system and coolers,)
        - *External gas (in the form of plasma and O2.)
        - - When in Heat-Primary mode, far more heat and plasma are produced than radiation. In O2-Reaction mode, very little heat and only moderate amounts of plasma are produced, however HUGE amounts of energy leaving the Supermatter is in the form of radiation.

        - - The O2-Reaction engine mode has a single drawback which has been eluded to more than once so far and that is very simple. The engine room will continue to grow hotter as the constant reaction continues. Eventually, there will be what he calls the .critical gas mix.. This is the point at which the constant adding of plasma to the mix of air around the Supermatter changes the gas concentration to below the tolerance. When this happens, two things occur. First, the Supermatter switches to its primary mode of operation where in huge amounts of heat are produced by the engine rather than low amounts with high power output. Second, an uncontrollable increase in heat within the Supermatter chamber will occur. This will lead to a spark-up, igniting the plasma in the Supermatter chamber, wildly increasing both pressure and temperature.

        - - While the O2-Reaction mode is dangerous, it does produce heavy amounts of energy. Consider using this mode only in short amounts to fill the SMES, and switch back later in the shift to keep things flowing normally.

        - - - Notes on Supermatter Containment and Emergency Procedures-

        - - While a constant vigil on the Supermatter is not required, regular checkups are important. Verify the temp of gas leaving the Supermatter chamber for unsafe levels, and ensure that the plasma in the chamber is at a safe concentration. Of course, also make sure the chamber is not on fire. A fire in the core chamber is very difficult to put out. As any Toxin scientist can tell you, even low amounts of plasma can burn at very high temperatures. This burning creates a huge increase in pressure and more importantly, temperature of the crystal itself.

        - - The Supermatter is strong, but not invincible. When the Supermatter is heated too much, its crystal structure will attempt to liquify. The change in atomic structure of the Supermatter leads to a single reaction, a massive explosion. The computer chip attached to the Supermatter core will warn the station when stability is threatened. It will then offer a second warning, when things have become dangerously close to total destruction of the core.

        - - Located both within the supermatter monitoring room and engine room is the vent control button. This button allows the Core Vent Controls to be accessed, venting the room to space. Remember however, that this process takes time. If a fire is raging, and the pressure is higher than fathomable, it will take a great deal of time to vent the room. Also located in the supermatter monitoring room is the emergency core eject button. A new core can be ordered from cargo. It is often not worth the lives of the crew to hold on to it, not to mention the structural damage. However, if by some mistake the Supermatter is pushed off or removed from the mass ejector it sits on, manual reposition will be required. Which is very dangerous and often leads to death.

        - - The Supermatter is extremely dangerous. More dangerous than people give it credit for. It can destroy you in an instant, without hesitation, reducing you to a pile of dust. When working closely with Supermatter it is.. suggested to get a genetic backup and do not wear any items of value to you. The Supermatter core can be pulled if grabbed properly by the base, but pushing is not possible.


        - - - In Closing-

        - - Remember that the Supermatter is dangerous, and the core is dangerous still. Venting the core room is always an option if you are even remotely worried, utilizing Atmospherics to properly ready the room once more for core function. It is always a good idea to check up regularly on the temperature of gas leaving the chamber, as well as the power in the system lines. Lastly, once again remember, never touch the Supermatter with anything. Ever.

        - - -Waleed Asad, Senior Engine Technician."} - -/obj/item/book/manual/engineering_hacking - name = "Hacking" - icon_state ="bookHacking" - author = "Engineering Encyclopedia" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - title = "Hacking" -//big pile of shit below. - - dat = {" - - - - - - - - - - - "} - -/obj/item/book/manual/engineering_singularity_safety - name = "Singularity Safety in Special Circumstances" - icon_state ="bookEngineeringSingularitySafety" - author = "Engineering Encyclopedia" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - title = "Singularity Safety in Special Circumstances" -//big pile of shit below. - - dat = {" - - - - -

        Singularity Safety in Special Circumstances

        - -

        Power outage

        - - A power problem has made the entire station loose power? Could be station-wide wiring problems or syndicate power sinks. In any case follow these steps: -

        - Step one: PANIC!
        - Step two: Get your ass over to engineering! QUICKLY!!!
        - Step three: Get to the Area Power Controller which controls the power to the emitters.
        - Step four: Swipe it with your ID card - if it doesn't unlock, continue with step 15.
        - Step five: Open the console and disengage the cover lock.
        - Step six: Pry open the APC with a Crowbar.
        - Step seven: Take out the empty power cell.
        - Step eight: Put in the new, full power cell - if you don't have one, continue with step 15.
        - Step nine: Quickly put on a Radiation suit.
        - Step ten: Check if the singularity field generators withstood the down-time - if they didn't, continue with step 15.
        - Step eleven: Since disaster was averted you now have to ensure it doesn't repeat. If it was a powersink which caused it and if the engineering apc is wired to the same powernet, which the powersink is on, you have to remove the piece of wire which links the apc to the powernet. If it wasn't a powersink which caused it, then skip to step 14.
        - Step twelve: Grab your crowbar and pry away the tile closest to the APC.
        - Step thirteen: Use the wirecutters to cut the wire which is conecting the grid to the terminal.
        - Step fourteen: Go to the bar and tell the guys how you saved them all. Stop reading this guide here.
        - Step fifteen: GET THE FUCK OUT OF THERE!!!
        -

        - -

        Shields get damaged

        - - Step one: GET THE FUCK OUT OF THERE!!! FORGET THE WOMEN AND CHILDREN, SAVE YOURSELF!!!
        - - - "} - -/obj/item/book/manual/hydroponics_pod_people - name = "The Human Harvest - From seed to market" - icon_state ="bookHydroponicsPodPeople" - author = "Farmer John" - title = "The Human Harvest - From seed to market" - dat = {" - - - - -

        Growing Humans

        - - Why would you want to grow humans? Well I'm expecting most readers to be in the slave trade, but a few might actually - want to revive fallen comrades. Growing pod people is easy, but prone to disaster. -

        -

          -
        1. Find a dead person who is in need of cloning.
        2. -
        3. Take a blood sample with a syringe.
        4. -
        5. Inject a seed pack with the blood sample.
        6. -
        7. Plant the seeds.
        8. -
        9. Tend to the plants water and nutrition levels until it is time to harvest the cloned human.
        10. -
        -

        - It really is that easy! Good luck! - - - - "} - -/obj/item/book/manual/medical_cloning - name = "Cloning techniques of the 26th century" - icon_state ="bookCloning" - author = "Medical Journal, volume 3" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - title = "Cloning techniques of the 26th century" -//big pile of shit below. - - dat = {" - - - - - -

        How to Clone People

        - So thereÂ’s 50 dead people lying on the floor, chairs are spinning like no tomorrow and you havenÂ’t the foggiest idea of what to do? Not to worry! This guide is intended to teach you how to clone people and how to do it right, in a simple step-by-step process! If at any point of the guide you have a mental meltdown, genetics probably isnÂ’t for you and you should get a job-change as soon as possible before youÂ’re sued for malpractice. - -
          -
        1. Acquire body
        2. -
        3. Strip body
        4. -
        5. Put body in cloning machine
        6. -
        7. Scan body
        8. -
        9. Clone body
        10. -
        11. Get clean Structurel Enzymes for the body
        12. -
        13. Put body in morgue
        14. -
        15. Await cloned body
        16. -
        17. Use the clean SW injector
        18. -
        19. Give person clothes back
        20. -
        21. Send person on their way
        22. -
        - -

        Step 1: Acquire body

        - This is pretty much vital for the process because without a body, you cannot clone it. Usually, bodies will be brought to you, so you do not need to worry so much about this step. If you already have a body, great! Move on to the next step. - -

        Step 2: Strip body

        - The cloning machine does not like abiotic items. What this means is you canÂ’t clone anyone if theyÂ’re wearing clothes, so take all of it off. If itÂ’s just one person, itÂ’s courteous to put their possessions in the closet. If you have about seven people awaiting cloning, just leave the piles where they are, but donÂ’t mix them around and for GodÂ’s sake donÂ’t let people in to steal them. - -

        Step 3: Put body in cloning machine

        - Grab the body and then put it inside the DNA modifier. If you cannot do this, then you messed up at Step 2. Go back and check you took EVERYTHING off - a commonly missed item is their headset. - -

        Step 4: Scan body

        - Go onto the computer and scan the body by pressing ‘Scan - ’. If you’re successful, they will be added to the records (note that this can be done at any time, even with living people, so that they can be cloned without a body in the event that they are lying dead on port solars and didn‘t turn on their suit sensors)! If not, and it says “Error: Mental interface failure.”, then they have left their bodily confines and are one with the spirits. If this happens, just shout at them to get back in their body, click ‘Refresh‘ and try scanning them again. If there’s no success, threaten them with gibbing. Still no success? Skip over to Step 7 and don‘t continue after it, as you have an unresponsive body and it cannot be cloned. If you got “Error: Unable to locate valid genetic data.“, you are trying to clone a monkey - start over. - -

        Step 5: Clone body

        - Now that the body has a record, click ’View Records’, click the subject’s name, and then click ‘Clone’ to start the cloning process. Congratulations! You’re halfway there. Remember not to ‘Eject’ the cloning pod as this will kill the developing clone and you’ll have to start the process again. - -

        Step 6: Get clean SEs for body

        - Cloning is a finicky and unreliable process. Whilst it will most certainly bring someone back from the dead, they can have any number of nasty disabilities given to them during the cloning process! For this reason, you need to prepare a clean, defect-free Structural Enzyme (SE) injection for when they’re done. If you’re a competent Geneticist, you will already have one ready on your working computer. If, for any reason, you do not, then eject the body from the DNA modifier (NOT THE CLONING POD) and take it next door to the Genetics research room. Put the body in one of those DNA modifiers and then go onto the console. Go into View/Edit/Transfer Buffer, find an open slot and click “SE“ to save it. Then click ‘Injector’ to get the SEs in syringe form. Put this in your pocket or something for when the body is done. - -

        Step 7: Put body in morgue

        - Now that the cloning process has been initiated and you have some clean Structural Enzymes, you no longer need the body! Drag it to the morgue and tell the Chef over the radio that they have some fresh meat waiting for them in there. To put a body in a morgue bed, simply open the tray, grab the body, put it on the open tray, then close the tray again. Use one of the nearby pens to label the bed “CHEF MEAT” in order to avoid confusion. - -

        Step 8: Await cloned body

        - Now go back to the lab and wait for your patient to be cloned. It wonÂ’t be long now, I promise. - -

        Step 9: Use the clean SE injector on person

        - Has your body been cloned yet? Great! As soon as the guy pops out, grab your injector and jab it in them. Once youÂ’ve injected them, they now have clean Structural Enzymes and their defects, if any, will disappear in a short while. - -

        Step 10: Give person clothes back

        - Obviously the person will be naked after they have been cloned. Provided you werenÂ’t an irresponsible little shit, you should have protected their possessions from thieves and should be able to give them back to the patient. No matter how cruel you are, itÂ’s simply against protocol to force your patients to walk outside naked. - -

        Step 11: Send person on their way

        - Give the patient one last check-over - make sure they donÂ’t still have any defects and that they have all their possessions. Ask them how they died, if they know, so that you can report any foul play over the radio. Once youÂ’re done, your patient is ready to go back to work! Chances are they do not have Medbay access, so you should let them out of Genetics and the Medbay main entrance. - -

        If youÂ’ve gotten this far, congratulations! You have mastered the art of cloning. Now, the real problem is how to resurrect yourself after that traitor had his way with you for cloning his target. - - - - - - "} - - -/obj/item/book/manual/ripley_build_and_repair - name = "APLU \"Ripley\" Construction and Operation Manual" - icon_state ="book" - author = "Weyland-Yutani Corp" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - title = "APLU \"Ripley\" Construction and Operation Manual" -//big pile of shit below. - - dat = {" - - - - -

        - Weyland-Yutani - Building Better Worlds -

        Autonomous Power Loader Unit \"Ripley\"

        -
        -

        Specifications:

        -
          -
        1. Cyborg Related Equipment
        2. -
        3. Cyborg Modules
        4. -
        5. Cyborg Construction
        6. -
        7. Cyborg Maintenance
        8. -
        9. Cyborg Repairs
        10. -
        11. In Case of Emergency
        12. -
        - - -

        Cyborg Related Equipment

        - -

        Exosuit Fabricator

        - The Exosuit Fabricator is the most important piece of equipment related to cyborgs. It allows the construction of the core cyborg parts. Without these machines, cyborgs can not be built. It seems that they may also benefit from advanced research techniques. - -

        Cyborg Recharging Station

        - This useful piece of equipment will suck power out of the power systems to charge a cyborg's power cell back up to full charge. - -

        Robotics Control Console

        - This useful piece of equipment can be used to immobolize or destroy a cyborg. A word of warning: Cyborgs are expensive pieces of equipment, do not destroy them without good reason, or Nanotrasen may see to it that it never happens again. - - -

        Cyborg Modules

        - When a cyborg is created it picks out of an array of modules to designate its purpose. There are 6 different cyborg modules. - -

        Standard Cyborg

        - The standard cyborg module is a multi-purpose cyborg. It is equipped with various modules, allowing it to do basic tasks.
        A Standard Cyborg comes with: -
          -
        • Crowbar
        • -
        • Stun Baton
        • -
        • Health Analyzer
        • -
        • Fire Extinguisher
        • -
        - -

        Engineering Cyborg

        - The Engineering cyborg module comes equipped with various engineering-related tools to help with engineering-related tasks.
        An Engineering Cyborg comes with: -
          -
        • A basic set of engineering tools
        • -
        • Metal Synthesizer
        • -
        • Reinforced Glass Synthesizer
        • -
        • An RCD
        • -
        • Wire Synthesizer
        • -
        • Fire Extinguisher
        • -
        • Built-in Optical Meson Scanners
        • -
        - -

        Mining Cyborg

        - The Mining Cyborg module comes equipped with the latest in mining equipment. They are efficient at mining due to no need for oxygen, but their power cells limit their time in the mines.
        A Mining Cyborg comes with: -
          -
        • Jackhammer
        • -
        • Shovel
        • -
        • Mining Satchel
        • -
        • Built-in Optical Meson Scanners
        • -
        - -

        Security Cyborg

        - The Security Cyborg module is equipped with effective security measures used to apprehend and arrest criminals without harming them a bit.
        A Security Cyborg comes with: -
          -
        • Stun Baton
        • -
        • Handcuffs
        • -
        • Taser
        • -
        - -

        Janitor Cyborg

        - The Janitor Cyborg module is equipped with various cleaning-facilitating devices.
        A Janitor Cyborg comes with: -
          -
        • Mop
        • -
        • Hand Bucket
        • -
        • Cleaning Spray Synthesizer and Spray Nozzle
        • -
        - -

        Service Cyborg

        - The service cyborg module comes ready to serve your human needs. It includes various entertainment and refreshment devices. Occasionally some service cyborgs may have been referred to as "Bros"
        A Service Cyborg comes with: -
          -
        • Shaker
        • -
        • Industrail Dropper
        • -
        • Platter
        • -
        • Beer Synthesizer
        • -
        • Zippo Lighter
        • -
        • Rapid-Service-Fabricator (Produces various entertainment and refreshment objects)
        • -
        • Pen
        • -
        - -

        Cyborg Construction

        - Cyborg construction is a rather easy process, requiring a decent amount of metal and a few other supplies.
        The required materials to make a cyborg are: -
          -
        • Metal
        • -
        • Two Flashes
        • -
        • One Power Cell (Preferrably rated to 15000w)
        • -
        • Some electrical wires
        • -
        • One Human Brain
        • -
        • One Man-Machine Interface
        • -
        - Once you have acquired the materials, you can start on construction of your cyborg.
        To construct a cyborg, follow the steps below: -
          -
        1. Start the Exosuit Fabricators constructing all of the cyborg parts
        2. -
        3. While the parts are being constructed, take your human brain, and place it inside the Man-Machine Interface
        4. -
        5. Once you have a Robot Head, place your two flashes inside the eye sockets
        6. -
        7. Once you have your Robot Chest, wire the Robot chest, then insert the power cell
        8. -
        9. Attach all of the Robot parts to the Robot frame
        10. -
        11. Insert the Man-Machine Interface (With the Brain inside) Into the Robot Body
        12. -
        13. Congratulations! You have a new cyborg!
        14. -
        - -

        Cyborg Maintenance

        - Occasionally Cyborgs may require maintenance of a couple types, this could include replacing a power cell with a charged one, or possibly maintaining the cyborg's internal wiring. - -

        Replacing a Power Cell

        - Replacing a Power cell is a common type of maintenance for cyborgs. It usually involves replacing the cell with a fully charged one, or upgrading the cell with a larger capacity cell.
        The steps to replace a cell are follows: -
          -
        1. Unlock the Cyborg's Interface by swiping your ID on it
        2. -
        3. Open the Cyborg's outer panel using a crowbar
        4. -
        5. Remove the old power cell
        6. -
        7. Insert the new power cell
        8. -
        9. Close the Cyborg's outer panel using a crowbar
        10. -
        11. Lock the Cyborg's Interface by swiping your ID on it, this will prevent non-qualified personnel from attempting to remove the power cell
        12. -
        - -

        Exposing the Internal Wiring

        - Exposing the internal wiring of a cyborg is fairly easy to do, and is mainly used for cyborg repairs.
        You can easily expose the internal wiring by following the steps below: -
          -
        1. Follow Steps 1 - 3 of "Replacing a Cyborg's Power Cell"
        2. -
        3. Open the cyborg's internal wiring panel by using a screwdriver to unsecure the panel
        4. -
        - To re-seal the cyborg's internal wiring: -
          -
        1. Use a screwdriver to secure the cyborg's internal panel
        2. -
        3. Follow steps 4 - 6 of "Replacing a Cyborg's Power Cell" to close up the cyborg
        4. -
        - -

        Cyborg Repairs

        - Occasionally a Cyborg may become damaged. This could be in the form of impact damage from a heavy or fast-travelling object, or it could be heat damage from high temperatures, or even lasers or Electromagnetic Pulses (EMPs). - -

        Dents

        - If a cyborg becomes damaged due to impact from heavy or fast-moving objects, it will become dented. Sure, a dent may not seem like much, but it can compromise the structural integrity of the cyborg, possibly causing a critical failure. - Dents in a cyborg's frame are rather easy to repair, all you need is to apply a welding tool to the dented area, and the high-tech cyborg frame will repair the dent under the heat of the welder. - -

        Excessive Heat Damage

        - If a cyborg becomes damaged due to excessive heat, it is likely that the internal wires will have been damaged. You must replace those wires to ensure that the cyborg remains functioning properly.
        To replace the internal wiring follow the steps below: -
          -
        1. Unlock the Cyborg's Interface by swiping your ID
        2. -
        3. Open the Cyborg's External Panel using a crowbar
        4. -
        5. Remove the Cyborg's Power Cell
        6. -
        7. Using a screwdriver, expose the internal wiring or the Cyborg
        8. -
        9. Replace the damaged wires inside the cyborg
        10. -
        11. Secure the internal wiring cover using a screwdriver
        12. -
        13. Insert the Cyborg's Power Cell
        14. -
        15. Close the Cyborg's External Panel using a crowbar
        16. -
        17. Lock the Cyborg's Interface by swiping your ID
        18. -
        - These repair tasks may seem difficult, but are essential to keep your cyborgs running at peak efficiency. - -

        In Case of Emergency

        - In case of emergency, there are a few steps you can take. - -

        "Rogue" Cyborgs

        - If the cyborgs seem to become "rogue", they may have non-standard laws. In this case, use extreme caution. - To repair the situation, follow these steps: -
          -
        1. Locate the nearest robotics console
        2. -
        3. Determine which cyborgs are "Rogue"
        4. -
        5. Press the lockdown button to immobolize the cyborg
        6. -
        7. Locate the cyborg
        8. -
        9. Expose the cyborg's internal wiring
        10. -
        11. Check to make sure the LawSync and AI Sync lights are lit
        12. -
        13. If they are not lit, pulse the LawSync wire using a multitool to enable the cyborg's Law Sync
        14. -
        15. Proceed to a cyborg upload console. Nanotrasen usually places these in the same location as AI uplaod consoles.
        16. -
        17. Use a "Reset" upload moduleto reset the cyborg's laws
        18. -
        19. Proceed to a Robotics Control console
        20. -
        21. Remove the lockdown on the cyborg
        22. -
        - -

        As a last resort

        - If all else fails in a case of cyborg-related emergency. There may be only one option. Using a Robotics Control console, you may have to remotely detonate the cyborg. -

        WARNING:

        Do not detonate a borg without an explicit reason for doing so. Cyborgs are expensive pieces of Nanotrasen equipment, and you may be punished for detonating them without reason. - - - - "} - -/obj/item/book/manual/security_space_law - name = "Space Law" - desc = "A set of Nanotrasen guidelines for keeping law and order on their space stations." - icon_state = "bookSpaceLaw" - force = 4 //advanced magistrate tactics - author = "Nanotrasen" - title = "Space Law" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/security_space_law/black - name = "Space Law - Limited Edition" - desc = "A leather-bound, immaculately-written copy of JUSTICE." - icon_state = "bookSpaceLawblack" - title = "Space Law - Limited Edition" - -/obj/item/book/manual/engineering_guide - name = "Engineering Textbook" - icon_state ="bookEngineering2" - author = "Engineering Encyclopedia" - title = "Engineering Textbook" - dat = {" - - - - - - - - - - "} - - -/obj/item/book/manual/chef_recipes - name = "Chef Recipes" - icon_state = "cooked_book" - author = "Victoria Ponsonby" - title = "Chef Recipes" - dat = {" - - - - - -

        Food for Dummies

        - Here is a guide on basic food recipes and also how to not poison your customers accidentally. - -

        Basics:

        - Knead an egg and some flour to make dough. Bake that to make a bun or flatten and cut it. - -

        Burger:

        - Put a bun and some meat into the microwave and turn it on. Then wait. - -

        Bread:

        - Put some dough and an egg into the microwave and then wait. - -

        Waffles:

        - Add two lumps of dough and 10u of sugar to the microwave and then wait. - -

        Popcorn:

        - Add 1 corn to the microwave and wait. - -

        Meat Steak:

        - Put a slice of meat, 1 unit of salt and 1 unit of pepper into the microwave and wait. - -

        Meat Pie:

        - Put a flattened piece of dough and some meat into the microwave and wait. - -

        Boiled Spaghetti:

        - Put the spaghetti (processed flour) and 5 units of water into the microwave and wait. - -

        Donuts:

        - Add some dough and 5 units of sugar to the microwave and wait. - -

        Fries:

        - Add one potato to the processor, then bake them in the microwave. - - - - - "} - -/obj/item/book/manual/barman_recipes - name = "Barman Recipes" - icon_state = "barbook" - author = "Sir John Rose" - title = "Barman Recipes" - dat = {" - - - - - -

        Drinks for dummies

        - Heres a guide for some basic drinks. - -

        Manly Dorf:

        - Mix ale and beer into a glass. - -

        Grog:

        - Mix rum and water into a glass. - -

        Black Russian:

        - Mix vodka and kahlua into a glass. - -

        Irish Cream:

        - Mix cream and whiskey into a glass. - -

        Screwdriver:

        - Mix vodka and orange juice into a glass. - -

        Cafe Latte:

        - Mix milk and coffee into a glass. - -

        Mead:

        - Mix Enzyme, water and sugar into a glass. - -

        Gin Tonic:

        - Mix gin and tonic into a glass. - -

        Classic Martini:

        - Mix vermouth and gin into a glass. - - - - - "} - - -/obj/item/book/manual/detective - name = "The Film Noir: Proper Procedures for Investigations" - icon_state ="bookDetective" - author = "Nanotrasen" - title = "The Film Noir: Proper Procedures for Investigations" - dat = {" - - - - -

        Detective Work

        - - Between your bouts of self-narration, and drinking whiskey on the rocks, you might get a case or two to solve.
        - To have the best chance to solve your case, follow these directions: -

        -

          -
        1. Go to the crime scene.
        2. -
        3. Take your scanner and scan EVERYTHING (Yes, the doors, the tables, even the dog.)
        4. -
        5. Once you are reasonably certain you have every scrap of evidence you can use, find all possible entry points and scan them, too.
        6. -
        7. Return to your office.
        8. -
        9. Using your forensic scanning computer, scan your Scanner to upload all of your evidence into the database.
        10. -
        11. Browse through the resulting dossiers, looking for the one that either has the most complete set of prints, or the most suspicious items handled.
        12. -
        13. If you have 80% or more of the print (The print is displayed) go to step 10, otherwise continue to step 8.
        14. -
        15. Look for clues from the suit fibres you found on your perp, and go about looking for more evidence with this new information, scanning as you go.
        16. -
        17. Try to get a fingerprint card of your perp, as if used in the computer, the prints will be completed on their dossier.
        18. -
        19. Assuming you have enough of a print to see it, grab the biggest complete piece of the print and search the security records for it.
        20. -
        21. Since you now have both your dossier and the name of the person, print both out as evidence, and get security to nab your baddie.
        22. -
        23. Give yourself a pat on the back and a bottle of the ships finest vodka, you did it!.
        24. -
        -

        - It really is that easy! Good luck! - - - "} - -/obj/item/book/manual/nuclear - name = "Fission Mailed: Nuclear Sabotage 101" - icon_state ="bookNuclear" - author = "Syndicate" - title = "Fission Mailed: Nuclear Sabotage 101" - dat = {" - Nuclear Explosives 101:
        - Hello and thank you for choosing the Syndicate for your nuclear information needs.
        - Today's crash course will deal with the operation of a Fusion Class Nanotrasen made Nuclear Device.
        - First and foremost, DO NOT TOUCH ANYTHING UNTIL THE BOMB IS IN PLACE.
        - Pressing any button on the compacted bomb will cause it to extend and bolt itself into place.
        - If this is done to unbolt it one must completely log in which at this time may not be possible.
        - To make the nuclear device functional:
        -

      • Place the nuclear device in the designated detonation zone.
      • -
      • Extend and anchor the nuclear device from its interface.
      • -
      • Insert the nuclear authorisation disk into slot.
      • -
      • Type numeric authorisation code into the keypad. This should have been provided. Note: If you make a mistake press R to reset the device. -
      • Press the E button to log onto the device.
      • - You now have activated the device. To deactivate the buttons at anytime for example when you've already prepped the bomb for detonation remove the auth disk OR press the R on the keypad.
        - Now the bomb CAN ONLY be detonated using the timer. Manual detonation is not an option.
        - Note: Nanotrasen is a pain in the neck.
        - Toggle off the SAFETY.
        - Note: You wouldn't believe how many Syndicate Operatives with doctorates have forgotten this step.
        - So use the - - and + + to set a det time between 5 seconds and 10 minutes.
        - Then press the timer toggle button to start the countdown.
        - Now remove the auth. disk so that the buttons deactivate.
        - Note: THE BOMB IS STILL SET AND WILL DETONATE
        - Now before you remove the disk if you need to move the bomb you can:
        - Toggle off the anchor, move it, and re-anchor.

        - Good luck. Remember the order:
        - Disk, Code, Safety, Timer, Disk, RUN!
        - Intelligence Analysts believe that normal Nanotrasen procedure is for the Captain to secure the nuclear authorisation disk.
        - Good luck! - "} - -/obj/item/book/manual/atmospipes - name = "Pipes and You: Getting To Know Your Scary Tools" - icon_state = "pipingbook" - author = "Maria Crash, Senior Atmospherics Technician" - title = "Pipes and You: Getting To Know Your Scary Tools" - dat = {" - - - - - - -

        Contents

        -
          -
        1. Author's Forward
        2. -
        3. Basic Piping
        4. -
        5. Insulated Pipes
        6. -
        7. Atmospherics Devices
        8. -
        9. Heat Exchange Systems
        10. -
        11. Final Checks
        12. -
        -

        - -

        HOW TO NOT SUCK QUITE SO HARD AT ATMOSPHERICS


        - Or: What the fuck does a "passive gate" do?

        - - Alright. It has come to my attention that a variety of people are unsure of what a "pipe" is and what it does. - Apparently there is an unnatural fear of these arcane devices and their "gases". Spooky, spooky. So, - this will tell you what every device constructable by an ordinary pipe dispenser within atmospherics actually does. - You are not going to learn what to do with them to be the super best person ever, or how to play guitar with passive gates, - or something like that. Just what stuff does.

        - - -

        Basic Pipes


        - The boring ones.
        - TMost ordinary pipes are pretty straightforward. They hold gas. If gas is moving in a direction for some reason, gas will flow in that direction. - That's about it. Even so, here's all of your wonderful pipe options.
        - -
      • Straight pipes: They're pipes. One-meter sections. Straight line. Pretty simple. Just about every pipe and device is based around this - standard one-meter size, so most things will take up as much space as one of these.
      • -
      • Bent pipes: Pipes with a 90 degree bend at the half-meter mark. My goodness.
      • -
      • Pipe manifolds: Pipes that are essentially a "T" shape, allowing you to connect three things at one point.
      • -
      • 4-way manifold: A four-way junction.
      • -
      • Pipe cap: Caps off the end of a pipe. Open ends don't actually vent air, because of the way the pipes are assembled, so, uh. Use them to decorate your house or something.
      • -
      • Manual Valve: A valve that will block off airflow when turned. Can't be used by the AI or cyborgs, because they don't have hands.
      • -
      • Manual T-Valve: Like a manual valve, but at the center of a manifold instead of a straight pipe.


      • - -

        Insulated Pipes


        - Special Public Service Announcement.
        - Our regular pipes are already insulated. These are completely worthless. Punch anyone who uses them.

        - -

        Devices:


        - They actually do something.
        - This is usually where people get frightened,
        afraid, and start calling on their gods and/or cowering in fear. Yes, I can see you doing that right now. - Stop it. It's unbecoming. Most of these are fairly straightforward.
        - -
      • Gas Pump: Take a wild guess. It moves gas in the direction it's pointing (marked by the red line on one end). It moves it based on pressure, the maximum output being 4500 kPa (kilopascals). - Ordinary atmospheric pressure, for comparison, is 101.3 kPa, and the minimum pressure of room-temperature pure oxygen needed to not suffocate in a matter of minutes is 16 kPa - (though 18 is preferred using internals, for various reasons).
      • -
      • Volume pump: This pump goes based on volume, instead of pressure, and the possible maximum pressure it can create in the pipe on the recieving end is double the gas pump because of this, - clocking in at an incredible 9000 kPa. If a pipe with this is destroyed or damaged, and this pressure of gas escapes, it can be incredibly dangerous depending on the size of the pipe filled. - Don't hook this to the distribution loop, or you will make babies cry and the Chief Engineer brutally beat you.
      • -
      • Passive gate: This is essentially a cap on the pressure of gas allowed to flow in a specific direction. - When turned on, instead of actively pumping gas, it measures the pressure flowing through it, and whatever pressure you set is the maximum: it'll cap after that. - In addition, it only lets gas flow one way. The direction the gas flows is opposite the red handle on it, which is confusing to people used to the red stripe on pumps pointing the way.
      • -
      • Unary vent: The basic vent used in rooms. It pumps gas into the room, but can't suck it back out. Controlled by the room's air alarm system.
      • -
      • Scrubber: The other half of room equipment. Filters air, and can suck it in entirely in what's called a "panic siphon". Actvating a panic siphon without very good reason will kill someone. Don't do it.
      • -
      • Meter: A little box with some gagues and numbers. Fasten it to any pipe or manifold, and it'll read you the pressure in it. Very useful.
      • -
      • Gas mixer: Two sides are input, one side is output. Mixes the gases pumped into it at the ratio defined. The side perpendicular to the other two is "node 2", for reference. - Can output this gas at pressures from 0-4500 kPa.
      • -
      • Gas filter: Essentially the opposite of a gas mixer. One side is input. The other two sides are output. One gas type will be filtered into the perpendicular output pipe, - the rest will continue out the other side. Can also output from 0-4500 kPa.
      • - -

        Heat Exchange Systems


        - Will not set you on fire.
        - These systems are used to transfer heat only between two pipes. They will not move gases or any other element, but will equalize the temperature (eventually). Note that because of how gases work (remember: pv=nRt), - a higher temperature will raise pressure, and a lower one will lower temperature.
        - -
      • Pipe: This is a pipe that will exchange heat with the surrounding atmosphere. Place in fire for superheating. Place in space for supercooling.
      • -
      • Bent Pipe: Take a wild guess.
      • -
      • Junction:Junction:The point where you connect your normal pipes to heat exchange pipes. Not necessary for heat exchangers, but necessary for H/E pipes/bent pipes.
      • -
      • Heat Exchanger: These funky-looking bits attach to an open pipe end. Put another heat exchanger directly across from it, and you can transfer heat across two pipes without having to have the gases touch. - This normally shouldn't exchange with the ambient air, despite being totally exposed. Just don't ask questions...

      • - - - That's about it for pipes. Go forth, armed with this knowledge, and try not to break, burn down, or kill anything. Please.
        - - - - "} - -/obj/item/book/manual/evaguide - name = "EVA Gear and You: Not Spending All Day Inside" - icon_state = "evabook" - author = "Maria Crash, Senior Atmospherics Technician" - title = "EVA Gear and You: Not Spending All Day Inside" - dat = {" - - - - - - -

        Contents

        -
          -
        1. A forward on using EVA gear
        2. -
        3. Donning a Civilian Suits
        4. -
        5. Putting on a Hardsuit
        6. -
        7. Final Checks
        8. -
        -

        - -

        EVA Gear and You: Not Spending All Day Inside


        - Or: How not to suffocate because there's a hole in your shoes

        - - EVA gear. Wonderful to use. It's useful for mining, engineering, and occasionally just surviving, if things are that bad. Most people have EVA training, - but apparently there are some on a space station who don't. This guide should give you a basic idea of how to use this gear, safely. It's split into two sections: - Civilian suits and hardsuits.

        - -

        Civilian Suits


        - The bulkiest things this side of Alpha Centauri
        - These suits are the grey ones that are stored in EVA. They're the more simple to get on, but are also a lot bulkier, and provide less protection from environmental hazards such as radiaion or physical impact. - As Medical, Engineering, Security, and Mining all have hardsuits of their own, these don't see much use, but knowing how to put them on is quite useful anyways.

        - - First, take the suit. It should be in three pieces: A top, a bottom,
        and a helmet. Put the bottom on first, shoes and the like will fit in it. If you have magnetic boots, however, - put them on on top of the suit's feet. Next, get the top on, as you would a shirt. It can be somewhat awkward putting these pieces on, due to the makeup of the suit, - but to an extent they will adjust to you. You can then find the snaps and seals around the waist, where the two pieces meet. Fasten these, and double-check their tightness. - The red indicators around the waist of the lower half will turn green when this is done correctly. Next, put on whatever breathing apparatus you're using, be it a gas mask or a breath mask. Make sure the oxygen tube is fastened into it. - Put on the helmet now, straight forward, and make sure the tube goes into the small opening specifically for internals. Again, fasten seals around the neck, a small indicator light in the inside of the helmet should go from red to off when all is fastened. - There is a small slot on the side of the suit where an emergency oxygen tank or extended emergency oxygen tank will fit, - but it is reccomended to have a full-sized tank on your back for EVA.

        - -

        Hardsuits


        - Heavy, uncomfortable, still the best option.
        - These suits come in Engineering, Mining, and the Armory. There's also a couple Medical Hardsuits in EVA. These provide a lot more protection than the standard suits.

        - - Similarly to the other suits, these are split into three parts. Fastening the pant and top are mostly the same as the other spacesuits, with the exception that these are a bit heavier, - though not as bulky. The helmet goes on differently, with the air tube feeing into the suit and out a hole near the left shoulder, while the helmet goes on turned ninety degrees counter-clockwise, - and then is screwed in for one and a quarter full rotations clockwise, leaving the faceplate directly in front of you. There is a small button on the right side of the helmet that activates the helmet light. - The tanks that fasten onto the side slot are emergency tanks, as
        well as full-sized oxygen tanks, leaving your back free for a backpack or satchel.

        - -

        FINAL CHECKS:


        -
      • Are all seals fastened correctly?
      • -
      • Do you either have shoes on under the suit, or magnetic boots on over it?
      • -
      • Do you have a mask on and internals on the suit or your back?
      • -
      • Do you have a way to communicate with the station in case something goes wrong?
      • -
      • Do you have a second person watching if this is a training session?

      • - - If you don't have any further issues, go out and do whatever is necessary.
        - - - - "} - -/obj/item/book/manual/faxes - name = "A Guide to Faxes" - desc = "A Nanotrasen-approved guide to writing faxes" - icon_state = "book6" - author = "Nanotrasen" - title = "A Guide to Faxes" - dat = {" - - - - - - - - -

        Contents

        -
          -
        1. What's a Fax?
        2. -
        3. When to Fax?
        4. -
        5. How to Fax?
        6. -
        -

        - -

        What's a Fax?


        -
      • Faxes are your main method of communicating with the NAS Trurl, better known as Central Command.
      • -
      • Faxes allow personnel on the station to maintain open lines of communication with the NAS Trurl, allowing for vital information to flow both ways.
      • -
      • Being written communications, proper grammar, syntax and typography is required, in addition to a signature and, if applicable, a stamp. Failure to sign faxes will lead to an automatic rejection.
      • -
      • We at Nanotrasen provide Fax Machines to every Head of Staff, in addition to the Magistrate, Nanotrasen Representative, and Internal Affairs Agents.
      • -
      • This means that we trust the recipients of these fax machines to only use them in the proper circumstances (see When to Fax?).
      • - -

        When to Fax?


        -
      • While it is up to the discretion of each individual person to decide when to fax Central Command, there are some simple guidelines on when to do this.
      • -
      • Firstly, any situation that can reasonably be solved on-site, should be handled on-site. Knowledge of Standard Operating Procedure is mandatory for everyone with access to a fax machine.
      • -
      • Resolving issues on-site not only leads to more expedient problem-solving, it also frees up company resources and provides valuable work experience for all parties involved.
      • -
      • This means that you should work with the Heads of Staff concerning personnel and workplace issues, and attempt to resolve situations with them. If, for whatever reason, the relevent Head of Staff is not available or receptive, consider speaking with the Captain and/or Nanotrasen Representative.
      • -
      • If, for whatever reason, these issues cannot be solved on-site, either due to incompetence or just plain refusal to cooperate, faxing Central Command becomes a viable option.
      • -
      • Secondly, station status reports should be sent occasionally, but never at the start of the shift. Remember, we assign personnel to the station. We do not need a repeat of what we just signed off on.
      • -
      • Thirdly, staff/departmental evaluations are always welcome, especially in cases of noticeable (in)competence. Just as a brilliant coworker can be rewarded, an incompetent one can be punished.
      • -
      • Fourthly, do not issue faxes asking for sentences. You have an entire Security department and an associated Detective, not to mention on-site Space Law manuals.
      • -
      • Lastly, please pay attention to context. If the station is facing a massive emergency, such as a Class 7-10 Blob Organism, most, if not all, non-relevant faxes will be duly ignored.
      • - -

        How to Fax?


        -
      • Sending a fax is simple. Simply insert your ID into the fax machine, then log in.
      • -
      • Once logged in, insert a piece of paper and select the destination from the provided list. Remember, you can rename your fax from within the fax machine's menu.
      • -
      • You can send faxes to any other fax machine on the station, which can be a very useful tool when you need to issue broad communications to all of the Heads of Staff.
      • -
      • To send a fax to Central Command, simply select the correct destination, and send the fax. Keep in mind, the communication arrays need to recharge after sending a fax to Central Command, so make sure you sent everything you need.
      • -
      • Lastly, paper bundles can also be faxed as a single item, so feel free to bundle up all relevant documentation and send it in at once.
      • - -
        - - - - "} - -/obj/item/book/manual/sop_science - name = "Science Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all scientific activities." - icon_state = "sop_science" - author = "Nanotrasen" - title = "Science Standard Operating Procedures" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/sop_medical - name = "Medical Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all medical activities." - icon_state = "sop_medical" - author = "Nanotrasen" - title = "Medical Standard Operating Procedures" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/sop_engineering - name = "Engineering Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all engineering activities." - icon_state = "sop_engineering" - author = "Nanotrasen" - title = "Engineering Standard Operating Procedures" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/sop_service - name = "Service Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all service activities." - icon_state = "sop_service" - author = "Nanotrasen" - title = "Service Standard Operating Procedures" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/sop_supply - name = "Supply Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all supply activities." - icon_state = "sop_cargo" - author = "Nanotrasen" - title = "Supply Standard Operating Procedures" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/sop_security - name = "Security Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all security activities." - icon_state = "sop_security" - author = "Nanotrasen" - title = "Security Standard Operating Procedures" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/sop_legal - name = "Legal Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all legal activities." - icon_state = "sop_legal" - author = "Nanotrasen" - title = "Legal Standard Operating Procedures" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/sop_general - name = "Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all station activities." - icon_state = "sop" - author = "Nanotrasen" - title = "Standard Operating Procedures" - dat = {" - - - - - - - - - - "} - -/obj/item/book/manual/sop_command - name = "Command Standard Operating Procedures" - desc = "A set of guidelines aiming at the safe conduct of all Command activities." - icon_state = "sop_command" - author = "Nanotrasen" - title = "Command Standard Operating Procedures" - dat = {" - - - - - - - - - - "} +/*********************MANUALS (BOOKS)***********************/ + +//Oh god what the fuck I am not good at computer +/obj/item/book/manual + icon = 'icons/obj/library.dmi' + due_date = 0 // Game time in 1/10th seconds + unique = 1 // 0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified + + +/obj/item/book/manual/engineering_construction + name = "Station Repairs and Construction" + icon_state ="bookEngineering" + author = "Engineering Encyclopedia" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned + title = "Station Repairs and Construction" + dat = {" + + + + + + + + + + + "} + +/obj/item/book/manual/engineering_particle_accelerator + name = "Particle Accelerator User's Guide" + icon_state ="bookParticleAccelerator" + author = "Engineering Encyclopedia" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned + title = "Particle Accelerator User's Guide" +//big pile of shit below. + + dat = {" + + + + + +

        Experienced user's guide

        + +

        Setting up

        + +
          +
        1. Wrench all pieces to the floor
        2. +
        3. Add wires to all the pieces
        4. +
        5. Close all the panels with your screwdriver
        6. +
        + +

        Use

        + +
          +
        1. Open the control panel
        2. +
        3. Set the speed to 2
        4. +
        5. Start firing at the singularity generator
        6. +
        7. When the singularity reaches a large enough size so it starts moving on it's own set the speed down to 0, but don't shut it off
        8. +
        9. Remember to wear a radiation suit when working with this machine... we did tell you that at the start, right?
        10. +
        + + + "} + + +/obj/item/book/manual/supermatter_engine + name = "Supermatter Engine User's Guide" + icon_state = "bookParticleAccelerator" //TEMP FIXME + author = "Waleed Asad" + title = "Supermatter Engine User's Guide" + + dat = {"Engineering notes on single-stage Supermatter engine,
        + -Waleed Asad
        + + A word of caution, do not enter the engine room, for any reason, without radiation protection and mesons on. The status of the engine may be unpredictable even when you believe it is .off.. This is an important level of personal protection.

        + + The engine has two basic modes of functionality. He has observed that it is capable of both a safe level of operation and a modified, high output mode.

        + +
        Notes on starting the basic function mode, dubbed .Heat-Primary Mode..


        + + 1. Prepare collector arrays. This is done standard to any text on their function by wrenching them down, filling six plasma tanks with a plasma canister, and inserting the tank into the collectors one by one. Finally, initialize each collector.

        + + 2. Prepare gas system. Before introducing any gas to the Supermatter engine room, it is important to remember the small but vital steps to preparing this section. First, set the input gas pump and output gas flow pump to 4500, or maximum flow. Second, switch the digital switching valve into the .up. position, in order to circulate the gas back toward the coolers and collectors.

        + + 3. Apply N2 gas. Retrieve the two N2 canisters from storage and bring them to the engine room. Attach one of them to the input section of the engine gas system located next to the collectors. Keep it attached until the N2 pressure is low enough to turn the canister light red. Replace it with the second canister to keep N2 pressure at optimal levels.

        + + 4. Begin primary emitter burst series. This means firing a single emitter for its first four shots. It is important to move to this step quickly. The onboard SMES units may not have enough power to run the emitters if left alone too long on-station. This engine can produce enough power on its own to run the entire station, ignoring the SMES units completely, and is wired to do so.

        + + 5. Switch SMES units to primary settings. Maximize input and set the devices to automatically charge, additionally turn their outputs on if they are off unless power is to be saved (Which can be useful in case of later failures.)

        + + 6. Begin secondary emitter burst series. Before firing the emitter again, check the power in the line with a multimeter (Do not forget electrical gloves.) The engine is running at high efficiency when the value exceeds 200,000 power units.

        + + 7. Maintain engine power. When power in the lines gets low, add an additional emitter burst series to bring power to normal levels.


        + + + +
        The second mode for running the engine uses a gas mix to produce a reaction within the Supermatter. This mode requires CE or Atmospheric help to setup. This has been dubbed the .O2-Reaction Mode..


        + + THIS MODE CAN CAUSE A RUNAWAY REACTION, LEADING TO CATASTROPHIC FAILURE IF NOT MAINTAINED. NEVER FORGET ABOUT THE ENGINE IN THIS MODE.

        + + Additionally, this mode can be used for what is called a .Cold Start.. If the station has no power in the SMES to run the emitters, using this mode will allow enough power output to run them, and quickly reach an acceptable level of power output.

        + + 1. Prepare collector arrays. This is done standard to any text on their function by wrenching them down, filling six plasma tanks with a plasma canister, and inserting the tank into the collectors one by one. Finally, initialize each collector.

        + + 2. Prepare gas system. Before introducing any gas to the Supermatter engine room, it is important to remember the small but vital steps to preparing this section. First, set the input gas pump and output gas flow pump to 4500, or maximum flow. Second, switch the digital switching valve into the .up. position, in order to circulate the gas back toward the coolers and collectors.

        + + 3. Modify the engine room filters. Unlike the Heat-Primary Mode, it is important to change the filters attached to the gas system to stop filtering O2, and start filtering Carbon Molecules. O2-Reaction Mode produces far more plasma than Heat-Primary, therefor filtering it off is essential.

        + + 4. Switch SMES units to primary settings. Maximize input and set the devices to automatically charge, additionally turn their outputs on if they are off unless power is to be saved (Which can be useful in case of later failures.) If you check the power in the system lines at this point you will find that it is constantly going up. Indeed, with just the addition of O2 to the Supermatter, it will begin outputting power.

        + + 5. Begin primary emitter burst series. Fire a single emitter for a series of four pulses, or a single series, and turn it off. Do not over power the Supermatter. The reaction is self sustaining and propagating. As long as O2 is in the chamber, it will continue outputting MORE power.

        + + 6. Maintain follow up operations. Remember to check the temp of the core gas and switch to the Heat-Primary function, or vent the core room when problems begin if required.

        + + Notes on Supermatter Reaction Function and Drawbacks-

        + + After several hours of observation an interesting phenomenon was witnessed. The Supermatter undergoes a constant self-sustaining reaction when given an extremely high O2 concentration. Anything about 80% or higher typically will cause this reaction. The Supermatter will continue to react whenever this gas mix is in the same room as the Supermatter.

        + + To understand why O2-Reaction mode is dangerous, the core principle of the Supermatter must be understood. The Supermatter emits three things when .not safe,. that is any time it is giving off power. These things are:

        + + *Radiation (which is converted into power by the collectors,)
        + *Heat (which is removed via the gas exchange system and coolers,)
        + *External gas (in the form of plasma and O2.)
        + + When in Heat-Primary mode, far more heat and plasma are produced than radiation. In O2-Reaction mode, very little heat and only moderate amounts of plasma are produced, however HUGE amounts of energy leaving the Supermatter is in the form of radiation.

        + + The O2-Reaction engine mode has a single drawback which has been eluded to more than once so far and that is very simple. The engine room will continue to grow hotter as the constant reaction continues. Eventually, there will be what he calls the .critical gas mix.. This is the point at which the constant adding of plasma to the mix of air around the Supermatter changes the gas concentration to below the tolerance. When this happens, two things occur. First, the Supermatter switches to its primary mode of operation where in huge amounts of heat are produced by the engine rather than low amounts with high power output. Second, an uncontrollable increase in heat within the Supermatter chamber will occur. This will lead to a spark-up, igniting the plasma in the Supermatter chamber, wildly increasing both pressure and temperature.

        + + While the O2-Reaction mode is dangerous, it does produce heavy amounts of energy. Consider using this mode only in short amounts to fill the SMES, and switch back later in the shift to keep things flowing normally.

        + + + Notes on Supermatter Containment and Emergency Procedures-

        + + While a constant vigil on the Supermatter is not required, regular checkups are important. Verify the temp of gas leaving the Supermatter chamber for unsafe levels, and ensure that the plasma in the chamber is at a safe concentration. Of course, also make sure the chamber is not on fire. A fire in the core chamber is very difficult to put out. As any Toxin scientist can tell you, even low amounts of plasma can burn at very high temperatures. This burning creates a huge increase in pressure and more importantly, temperature of the crystal itself.

        + + The Supermatter is strong, but not invincible. When the Supermatter is heated too much, its crystal structure will attempt to liquify. The change in atomic structure of the Supermatter leads to a single reaction, a massive explosion. The computer chip attached to the Supermatter core will warn the station when stability is threatened. It will then offer a second warning, when things have become dangerously close to total destruction of the core.

        + + Located both within the supermatter monitoring room and engine room is the vent control button. This button allows the Core Vent Controls to be accessed, venting the room to space. Remember however, that this process takes time. If a fire is raging, and the pressure is higher than fathomable, it will take a great deal of time to vent the room. Also located in the supermatter monitoring room is the emergency core eject button. A new core can be ordered from cargo. It is often not worth the lives of the crew to hold on to it, not to mention the structural damage. However, if by some mistake the Supermatter is pushed off or removed from the mass ejector it sits on, manual reposition will be required. Which is very dangerous and often leads to death.

        + + The Supermatter is extremely dangerous. More dangerous than people give it credit for. It can destroy you in an instant, without hesitation, reducing you to a pile of dust. When working closely with Supermatter it is.. suggested to get a genetic backup and do not wear any items of value to you. The Supermatter core can be pulled if grabbed properly by the base, but pushing is not possible.


        + + + In Closing-

        + + Remember that the Supermatter is dangerous, and the core is dangerous still. Venting the core room is always an option if you are even remotely worried, utilizing Atmospherics to properly ready the room once more for core function. It is always a good idea to check up regularly on the temperature of gas leaving the chamber, as well as the power in the system lines. Lastly, once again remember, never touch the Supermatter with anything. Ever.

        + + -Waleed Asad, Senior Engine Technician."} + +/obj/item/book/manual/engineering_hacking + name = "Hacking" + icon_state ="bookHacking" + author = "Engineering Encyclopedia" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned + title = "Hacking" +//big pile of shit below. + + dat = {" + + + + + + + + + + + "} + +/obj/item/book/manual/engineering_singularity_safety + name = "Singularity Safety in Special Circumstances" + icon_state ="bookEngineeringSingularitySafety" + author = "Engineering Encyclopedia" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned + title = "Singularity Safety in Special Circumstances" +//big pile of shit below. + + dat = {" + + + + +

        Singularity Safety in Special Circumstances

        + +

        Power outage

        + + A power problem has made the entire station loose power? Could be station-wide wiring problems or syndicate power sinks. In any case follow these steps: +

        + Step one: PANIC!
        + Step two: Get your ass over to engineering! QUICKLY!!!
        + Step three: Get to the Area Power Controller which controls the power to the emitters.
        + Step four: Swipe it with your ID card - if it doesn't unlock, continue with step 15.
        + Step five: Open the console and disengage the cover lock.
        + Step six: Pry open the APC with a Crowbar.
        + Step seven: Take out the empty power cell.
        + Step eight: Put in the new, full power cell - if you don't have one, continue with step 15.
        + Step nine: Quickly put on a Radiation suit.
        + Step ten: Check if the singularity field generators withstood the down-time - if they didn't, continue with step 15.
        + Step eleven: Since disaster was averted you now have to ensure it doesn't repeat. If it was a powersink which caused it and if the engineering apc is wired to the same powernet, which the powersink is on, you have to remove the piece of wire which links the apc to the powernet. If it wasn't a powersink which caused it, then skip to step 14.
        + Step twelve: Grab your crowbar and pry away the tile closest to the APC.
        + Step thirteen: Use the wirecutters to cut the wire which is conecting the grid to the terminal.
        + Step fourteen: Go to the bar and tell the guys how you saved them all. Stop reading this guide here.
        + Step fifteen: GET THE FUCK OUT OF THERE!!!
        +

        + +

        Shields get damaged

        + + Step one: GET THE FUCK OUT OF THERE!!! FORGET THE WOMEN AND CHILDREN, SAVE YOURSELF!!!
        + + + "} + +/obj/item/book/manual/hydroponics_pod_people + name = "The Human Harvest - From seed to market" + icon_state ="bookHydroponicsPodPeople" + author = "Farmer John" + title = "The Human Harvest - From seed to market" + dat = {" + + + + +

        Growing Humans

        + + Why would you want to grow humans? Well I'm expecting most readers to be in the slave trade, but a few might actually + want to revive fallen comrades. Growing pod people is easy, but prone to disaster. +

        +

          +
        1. Find a dead person who is in need of cloning.
        2. +
        3. Take a blood sample with a syringe.
        4. +
        5. Inject a seed pack with the blood sample.
        6. +
        7. Plant the seeds.
        8. +
        9. Tend to the plants water and nutrition levels until it is time to harvest the cloned human.
        10. +
        +

        + It really is that easy! Good luck! + + + + "} + +/obj/item/book/manual/medical_cloning + name = "Cloning techniques of the 26th century" + icon_state ="bookCloning" + author = "Medical Journal, volume 3" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned + title = "Cloning techniques of the 26th century" +//big pile of shit below. + + dat = {" + + + + + +

        How to Clone People

        + So thereÂ’s 50 dead people lying on the floor, chairs are spinning like no tomorrow and you havenÂ’t the foggiest idea of what to do? Not to worry! This guide is intended to teach you how to clone people and how to do it right, in a simple step-by-step process! If at any point of the guide you have a mental meltdown, genetics probably isnÂ’t for you and you should get a job-change as soon as possible before youÂ’re sued for malpractice. + +
          +
        1. Acquire body
        2. +
        3. Strip body
        4. +
        5. Put body in cloning machine
        6. +
        7. Scan body
        8. +
        9. Clone body
        10. +
        11. Get clean Structurel Enzymes for the body
        12. +
        13. Put body in morgue
        14. +
        15. Await cloned body
        16. +
        17. Use the clean SW injector
        18. +
        19. Give person clothes back
        20. +
        21. Send person on their way
        22. +
        + +

        Step 1: Acquire body

        + This is pretty much vital for the process because without a body, you cannot clone it. Usually, bodies will be brought to you, so you do not need to worry so much about this step. If you already have a body, great! Move on to the next step. + +

        Step 2: Strip body

        + The cloning machine does not like abiotic items. What this means is you canÂ’t clone anyone if theyÂ’re wearing clothes, so take all of it off. If itÂ’s just one person, itÂ’s courteous to put their possessions in the closet. If you have about seven people awaiting cloning, just leave the piles where they are, but donÂ’t mix them around and for GodÂ’s sake donÂ’t let people in to steal them. + +

        Step 3: Put body in cloning machine

        + Grab the body and then put it inside the DNA modifier. If you cannot do this, then you messed up at Step 2. Go back and check you took EVERYTHING off - a commonly missed item is their headset. + +

        Step 4: Scan body

        + Go onto the computer and scan the body by pressing ‘Scan - ’. If you’re successful, they will be added to the records (note that this can be done at any time, even with living people, so that they can be cloned without a body in the event that they are lying dead on port solars and didn‘t turn on their suit sensors)! If not, and it says “Error: Mental interface failure.”, then they have left their bodily confines and are one with the spirits. If this happens, just shout at them to get back in their body, click ‘Refresh‘ and try scanning them again. If there’s no success, threaten them with gibbing. Still no success? Skip over to Step 7 and don‘t continue after it, as you have an unresponsive body and it cannot be cloned. If you got “Error: Unable to locate valid genetic data.“, you are trying to clone a monkey - start over. + +

        Step 5: Clone body

        + Now that the body has a record, click ’View Records’, click the subject’s name, and then click ‘Clone’ to start the cloning process. Congratulations! You’re halfway there. Remember not to ‘Eject’ the cloning pod as this will kill the developing clone and you’ll have to start the process again. + +

        Step 6: Get clean SEs for body

        + Cloning is a finicky and unreliable process. Whilst it will most certainly bring someone back from the dead, they can have any number of nasty disabilities given to them during the cloning process! For this reason, you need to prepare a clean, defect-free Structural Enzyme (SE) injection for when they’re done. If you’re a competent Geneticist, you will already have one ready on your working computer. If, for any reason, you do not, then eject the body from the DNA modifier (NOT THE CLONING POD) and take it next door to the Genetics research room. Put the body in one of those DNA modifiers and then go onto the console. Go into View/Edit/Transfer Buffer, find an open slot and click “SE“ to save it. Then click ‘Injector’ to get the SEs in syringe form. Put this in your pocket or something for when the body is done. + +

        Step 7: Put body in morgue

        + Now that the cloning process has been initiated and you have some clean Structural Enzymes, you no longer need the body! Drag it to the morgue and tell the Chef over the radio that they have some fresh meat waiting for them in there. To put a body in a morgue bed, simply open the tray, grab the body, put it on the open tray, then close the tray again. Use one of the nearby pens to label the bed “CHEF MEAT” in order to avoid confusion. + +

        Step 8: Await cloned body

        + Now go back to the lab and wait for your patient to be cloned. It wonÂ’t be long now, I promise. + +

        Step 9: Use the clean SE injector on person

        + Has your body been cloned yet? Great! As soon as the guy pops out, grab your injector and jab it in them. Once youÂ’ve injected them, they now have clean Structural Enzymes and their defects, if any, will disappear in a short while. + +

        Step 10: Give person clothes back

        + Obviously the person will be naked after they have been cloned. Provided you werenÂ’t an irresponsible little shit, you should have protected their possessions from thieves and should be able to give them back to the patient. No matter how cruel you are, itÂ’s simply against protocol to force your patients to walk outside naked. + +

        Step 11: Send person on their way

        + Give the patient one last check-over - make sure they donÂ’t still have any defects and that they have all their possessions. Ask them how they died, if they know, so that you can report any foul play over the radio. Once youÂ’re done, your patient is ready to go back to work! Chances are they do not have Medbay access, so you should let them out of Genetics and the Medbay main entrance. + +

        If youÂ’ve gotten this far, congratulations! You have mastered the art of cloning. Now, the real problem is how to resurrect yourself after that traitor had his way with you for cloning his target. + + + + + + "} + + +/obj/item/book/manual/ripley_build_and_repair + name = "APLU \"Ripley\" Construction and Operation Manual" + icon_state ="book" + author = "Weyland-Yutani Corp" // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned + title = "APLU \"Ripley\" Construction and Operation Manual" +//big pile of shit below. + + dat = {" + + + + +

        + Weyland-Yutani - Building Better Worlds +

        Autonomous Power Loader Unit \"Ripley\"

        +
        +

        Specifications:

        +
          -
        • Class: Autonomous Power Loader
        • -
        • Scope: Logistics and Construction
        • -
        • Weight: 820kg (without operator and with empty cargo compartment)
        • -
        • Height: 2.5m
        • -
        • Width: 1.8m
        • -
        • Top speed: 5km/hour
        • -
        • Operation in vacuum/hostile environment: Possible -
        • Airtank Volume: 500liters
        • -
        • Devices: -
            -
          • Hydraulic Clamp
          • -
          • High-speed Drill
          • -
          -
        • -
        • Propulsion Device: Powercell-powered electro-hydraulic system.
        • -
        • Powercell capacity: Varies.
        • -
        - -

        Construction:

        -
          -
        1. Connect all exosuit parts to the chassis frame
        2. -
        3. Connect all hydraulic fittings and tighten them up with a wrench
        4. -
        5. Adjust the servohydraulics with a screwdriver
        6. -
        7. Wire the chassis. (Cable is not included.)
        8. -
        9. Use the wirecutters to remove the excess cable if needed.
        10. -
        11. Install the central control module (Not included. Use supplied datadisk to create one).
        12. -
        13. Secure the mainboard with a screwdriver.
        14. -
        15. Install the peripherals control module (Not included. Use supplied datadisk to create one).
        16. -
        17. Secure the peripherals control module with a screwdriver
        18. -
        19. Install the internal armor plating (Not included due to Nanotrasen regulations. Can be made using 5 metal sheets.)
        20. -
        21. Secure the internal armor plating with a wrench
        22. -
        23. Weld the internal armor plating to the chassis
        24. -
        25. Install the external reinforced armor plating (Not included due to Nanotrasen regulations. Can be made using 5 reinforced metal sheets.)
        26. -
        27. Secure the external reinforced armor plating with a wrench
        28. -
        29. Weld the external reinforced armor plating to the chassis
        30. -
        31. -
        32. Additional Information:
        33. -
        34. The firefighting variation is made in a similar fashion.
        35. -
        36. A firesuit must be connected to the Firefighter chassis for heat shielding.
        37. -
        38. Internal armor is plasteel for additional strength.
        39. -
        40. External armor must be installed in 2 parts, totaling 10 sheets.
        41. -
        42. Completed mech is more resiliant against fire, and is a bit more durable overall
        43. -
        44. Nanotrasen is determined to the safety of its investments employees.
        45. -
        - - - -

        Operation

        - Coming soon... - "} - -/obj/item/book/manual/experimentor - name = "Mentoring your Experiments" - icon_state = "rdbook" - author = "Dr. H.P. Kritz" - title = "Mentoring your Experiments" - dat = {" - - - - -

        THE E.X.P.E.R.I-MENTOR

        - The Enhanced Xenobiological Period Extraction (and) Restoration Instructor is a machine designed to discover the secrets behind every item in existence. - With advanced technology, it can process 99.95% of items, and discover their uses and secrets. - The E.X.P.E.R.I-MENTOR is a Research apparatus that takes items, and through a process of elimination, it allows you to deduce new technological designs from them. - Due to the volatile nature of the E.X.P.E.R.I-MENTOR, there is a slight chance for malfunction, potentially causing irreparable damage to you or your environment. - However, upgrading the apparatus has proven to decrease the chances of undesirable, potentially life-threatening outcomes. - Please note that the E.X.P.E.R.I-MENTOR uses a state-of-the-art random generator, which has a larger entropy than the observable universe, - therefore it can generate wildly different results each day, therefore it is highly suggested to re-scan objects of interests frequently (e.g. each shift). - -

        BASIC PROCESS

        - The usage of the E.X.P.E.R.I-MENTOR is quite simple: -
          -
        1. Find an item with a technological background
        2. -
        3. Insert the item into the E.X.P.E.R.I-MENTOR
        4. -
        5. Cycle through each processing method of the device.
        6. -
        7. Stand back, even in case of a successful experiment, as the machine might produce undesired behaviour.
        8. -
        - -

        ADVANCED USAGE

        - The E.X.P.E.R.I-MENTOR has a variety of uses, beyond menial research work. The different results can be used to combat localised events, or even to get special items. - - The E.X.P.E.R.I-MENTOR's OBLITERATE function has the added use of transferring the destroyed item's material into a linked lathe. - - The IRRADIATE function can be used to transform items into other items, resulting in potential upgrades (or downgrades). - - Users should remember to always wear appropriate protection when using the machine, because malfunction can occur at any moment! - -

        EVENTS

        -

        GLOBAL (happens at any time):

        -
          -
        1. DETECTION MALFUNCTION - The machine's onboard sensors have malfunctioned, causing it to redefine the item's experiment type. - Produces the message: The E.X.P.E.R.I-MENTOR's onboard detection system has malfunctioned!
        2. - -
        3. IANIZATION - The machine's onboard corgi-filter has malfunctioned, causing it to produce a corgi from.. somewhere. - Produces the message: The E.X.P.E.R.I-MENTOR melts the banana, ian-izing the air around it!
        4. - -
        5. RUNTIME ERROR - The machine's onboard C4T-P processor has encountered a critical error, causing it to produce a cat from.. somewhere. - Produces the message: The E.X.P.E.R.I-MENTOR encounters a run-time error!
        6. - -
        7. B100DG0D.EXE - The machine has encountered an unknown subroutine, which has been injected into it's runtime. It upgrades the held item! - Produces the message: The E.X.P.E.R.I-MENTOR improves the banana, drawing the life essence of those nearby!
        8. - -
        9. POWERSINK - The machine's PSU has tripped the charging mechanism! It consumes massive amounts of power! - Produces the message: The E.X.P.E.R.I-MENTOR begins to smoke and hiss, shaking violently!
        10. -
        -

        FAIL:

        - This event is produced when the item mismatches the selected experiment. - Produces a random message similar to: "the Banana rumbles, and shakes, the experiment was a failure!" - -

        POKE:

        -
          -
        1. WILD ARMS - The machine's gryoscopic processors malfunction, causing it to lash out at nearby people with it's arms. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions and destroys the banana, lashing it's arms out at nearby people!
        2. - -
        3. MISTYPE - The machine's interface has been garbled, and it switches to OBLITERATE. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions!
        4. - -
        5. THROW - The machine's spatial recognition device has shifted several meters across the room, causing it to try and repostion the item there. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, throwing the banana!
        6. -
        -

        IRRADIATE:

        -
          -
        1. RADIATION LEAK - The machine's shield has failed, resulting in a toxic radiation leak. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, melting the banana and leaking radiation!
        2. - -
        3. RADIATION DUMP - The machine's recycling and containment functions have failed, resulting in a dump of toxic waste around it - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, spewing toxic waste!
        4. - -
        5. MUTATION - The machine's radio-isotope level meter has malfunctioned, causing it over-irradiate the item, making it transform. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, transforming the banana!
        6. -
        -

        GAS:

        -
          -
        1. TOXIN LEAK - The machine's filtering and vent systems have failed, resulting in a cloud of toxic gas being expelled. - Produces the message: The E.X.P.E.R.I-MENTOR destroys the banana, leaking dangerous gas!
        2. - -
        3. GAS LEAK - The machine's vent systems have failed, resulting in a cloud of harmless, but obscuring gas. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, spewing harmless gas!
        4. - -
        5. ELECTROMAGNETIC IONS - The machine's electrolytic scanners have failed, causing a dangerous Electromagnetic reaction. - Produces the message: The E.X.P.E.R.I-MENTOR melts the banana, ionizing the air around it!
        6. -
        -

        HEAT:

        -
          -
        1. TOASTER - The machine's heating coils have come into contact with the machine's gas storage, causing a large, sudden blast of flame. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, melting the banana and releasing a burst of flame!
        2. - -
        3. SAUNA - The machine's vent loop has sprung a leak, resulting in a large amount of superheated air being dumped around it. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, melting the banana and leaking hot air!
        4. - -
        5. EMERGENCY VENT - The machine's temperature gauge has malfunctioned, resulting in it attempting to cool the area around it, but instead, dumping a cloud of steam. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, activating it's emergency coolant systems!
        6. -
        -

        COLD:

        -
          -
        1. FREEZER - The machine's cooling loop has sprung a leak, resulting in a cloud of super-cooled liquid being blasted into the air. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, shattering the banana and releasing a dangerous cloud of coolant!
        2. - -
        3. FRIDGE - The machine's cooling loop has been exposed to the outside air, resulting in a large decrease in temperature. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, shattering the banana and leaking cold air!
        4. - -
        5. SNOWSTORM - The machine's cooling loop has come into contact with the heating coils, resulting in a sudden blast of cool air. - Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, releasing a flurry of chilly air as the banana pops out!
        6. -
        -

        OBLITERATE:

        -
          -
        1. IMPLOSION - The machine's pressure leveller has malfunctioned, causing it to pierce the space-time momentarily, making everything in the area fly towards it. - Produces the message: The E.X.P.E.R.I-MENTOR's crusher goes way too many levels too high, crushing right through space-time!
        2. - -
        3. DISTORTION - The machine's pressure leveller has completely disabled, resulting in a momentary space-time distortion, causing everything to fly around. - Produces the message: The E.X.P.E.R.I-MENTOR's crusher goes one level too high, crushing right into space-time!
        4. -
        - - - "} - -/obj/item/book/manual/research_and_development - name = "Research and Development 101" - icon_state = "rdbook" - author = "Dr. L. Ight" - title = "Research and Development 101" - dat = {" - - - - - - -

        Science For Dummies

        - So you want to further SCIENCE? Good man/woman/thing! However, SCIENCE is a complicated process even though it's quite easy. For the most part, it's a three step process: -
          -
        1. 1) Deconstruct items in the Destructive Analyzer to advance technology or improve the design.
        2. -
        3. 2) Build unlocked designs in the Protolathe and Circuit Imprinter
        4. -
        5. 3) Repeat!
        6. -
        - - Those are the basic steps to furthing science. What do you do science with, however? Well, you have four major tools: R&D Console, the Destructive Analyzer, the Protolathe, and the Circuit Imprinter. - -

        The R&D Console

        - The R&D console is the cornerstone of any research lab. It is the central system from which the Destructive Analyzer, Protolathe, and Circuit Imprinter (your R&D systems) are controled. More on those systems in their own sections. On its own, the R&D console acts as a database for all your technological gains and new devices you discover. So long as the R&D console remains intact, you'll retain all that SCIENCE you've discovered. Protect it though, because if it gets damaged, you'll lose your data! In addition to this important purpose, the R&D console has a disk menu that lets you transfer data from the database onto disk or from the disk into the database. It also has a settings menu that lets you re-sync with nearby R&D devices (if they've become disconnected), lock the console from the unworthy, upload the data to all other R&D consoles in the network (all R&D consoles are networked by default), connect/disconnect from the network, and purge all data from the database. - NOTE: The technology list screen, circuit imprinter, and protolathe menus are accessible by non-scientists. This is intended to allow 'public' systems for the plebians to utilize some new devices. - -

        Destructive Analyzer

        - This is the source of all technology. Whenever you put a handheld object in it, it analyzes it and determines what sort of technological advancements you can discover from it. If the technology of the object is equal or higher then your current knowledge, you can destroy the object to further those sciences. Some devices (notably, some devices made from the protolathe and circuit imprinter) aren't 100% reliable when you first discover them. If these devices break down, you can put them into the Destructive Analyzer and improve their reliability rather then futher science. If their reliability is high enough ,it'll also advance their related technologies. - -

        Circuit Imprinter

        - This machine, along with the Protolathe, is used to actually produce new devices. The Circuit Imprinter takes glass and various chemicals (depends on the design) to produce new circuit boards to build new machines or computers. It can even be used to print AI modules. - -

        Protolathe

        - This machine is an advanced form of the Autolathe that produce non-circuit designs. Unlike the Autolathe, it can use processed metal, glass, solid plasma, silver, gold, and diamonds along with a variety of chemicals to produce devices. The downside is that, again, not all devices you make are 100% reliable when you first discover them. - -

        Reliability and You

        - As it has been stated, many devices when they're first discovered do not have a 100% reliablity when you first discover them. Instead, the reliablity of the device is dependent upon a base reliability value, whatever improvements to the design you've discovered through the Destructive Analyzer, and any advancements you've made with the device's source technologies. To be able to improve the reliability of a device, you have to use the device until it breaks beyond repair. Once that happens, you can analyze it in a Destructive Analyzer. Once the device reachs a certain minimum reliability, you'll gain tech advancements from it. - -

        Building a Better Machine

        - Many machines produces from circuit boards and inserted into a machine frame require a variety of parts to construct. These are parts like capacitors, batteries, matter bins, and so forth. As your knowledge of science improves, more advanced versions are unlocked. If you use these parts when constructing something, its attributes may be improved. For example, if you use an advanced matter bin when constructing an autolathe (rather then a regular one), it'll hold more materials. Experiment around with stock parts of various qualities to see how they affect the end results! Be warned, however: Tier 3 and higher stock parts don't have 100% reliability and their low reliability may affect the reliability of the end machine. - - - "} - - -/obj/item/book/manual/robotics_cyborgs - name = "Cyborgs for Dummies" - icon_state = "borgbook" - author = "XISC" - title = "Cyborgs for Dummies" - dat = {" - - - - - -

        Cyborgs for Dummies

        - -

        Chapters

        - -
          +
        • Class: Autonomous Power Loader
        • +
        • Scope: Logistics and Construction
        • +
        • Weight: 820kg (without operator and with empty cargo compartment)
        • +
        • Height: 2.5m
        • +
        • Width: 1.8m
        • +
        • Top speed: 5km/hour
        • +
        • Operation in vacuum/hostile environment: Possible +
        • Airtank Volume: 500liters
        • +
        • Devices: +
            +
          • Hydraulic Clamp
          • +
          • High-speed Drill
          • +
          +
        • +
        • Propulsion Device: Powercell-powered electro-hydraulic system.
        • +
        • Powercell capacity: Varies.
        • +
        + +

        Construction:

        +
          +
        1. Connect all exosuit parts to the chassis frame
        2. +
        3. Connect all hydraulic fittings and tighten them up with a wrench
        4. +
        5. Adjust the servohydraulics with a screwdriver
        6. +
        7. Wire the chassis. (Cable is not included.)
        8. +
        9. Use the wirecutters to remove the excess cable if needed.
        10. +
        11. Install the central control module (Not included. Use supplied datadisk to create one).
        12. +
        13. Secure the mainboard with a screwdriver.
        14. +
        15. Install the peripherals control module (Not included. Use supplied datadisk to create one).
        16. +
        17. Secure the peripherals control module with a screwdriver
        18. +
        19. Install the internal armor plating (Not included due to Nanotrasen regulations. Can be made using 5 metal sheets.)
        20. +
        21. Secure the internal armor plating with a wrench
        22. +
        23. Weld the internal armor plating to the chassis
        24. +
        25. Install the external reinforced armor plating (Not included due to Nanotrasen regulations. Can be made using 5 reinforced metal sheets.)
        26. +
        27. Secure the external reinforced armor plating with a wrench
        28. +
        29. Weld the external reinforced armor plating to the chassis
        30. +
        31. +
        32. Additional Information:
        33. +
        34. The firefighting variation is made in a similar fashion.
        35. +
        36. A firesuit must be connected to the Firefighter chassis for heat shielding.
        37. +
        38. Internal armor is plasteel for additional strength.
        39. +
        40. External armor must be installed in 2 parts, totaling 10 sheets.
        41. +
        42. Completed mech is more resiliant against fire, and is a bit more durable overall
        43. +
        44. Nanotrasen is determined to the safety of its investments employees.
        45. +
        + + + +

        Operation

        + Coming soon... + "} + +/obj/item/book/manual/experimentor + name = "Mentoring your Experiments" + icon_state = "rdbook" + author = "Dr. H.P. Kritz" + title = "Mentoring your Experiments" + dat = {" + + + + +

        THE E.X.P.E.R.I-MENTOR

        + The Enhanced Xenobiological Period Extraction (and) Restoration Instructor is a machine designed to discover the secrets behind every item in existence. + With advanced technology, it can process 99.95% of items, and discover their uses and secrets. + The E.X.P.E.R.I-MENTOR is a Research apparatus that takes items, and through a process of elimination, it allows you to deduce new technological designs from them. + Due to the volatile nature of the E.X.P.E.R.I-MENTOR, there is a slight chance for malfunction, potentially causing irreparable damage to you or your environment. + However, upgrading the apparatus has proven to decrease the chances of undesirable, potentially life-threatening outcomes. + Please note that the E.X.P.E.R.I-MENTOR uses a state-of-the-art random generator, which has a larger entropy than the observable universe, + therefore it can generate wildly different results each day, therefore it is highly suggested to re-scan objects of interests frequently (e.g. each shift). + +

        BASIC PROCESS

        + The usage of the E.X.P.E.R.I-MENTOR is quite simple: +
          +
        1. Find an item with a technological background
        2. +
        3. Insert the item into the E.X.P.E.R.I-MENTOR
        4. +
        5. Cycle through each processing method of the device.
        6. +
        7. Stand back, even in case of a successful experiment, as the machine might produce undesired behaviour.
        8. +
        + +

        ADVANCED USAGE

        + The E.X.P.E.R.I-MENTOR has a variety of uses, beyond menial research work. The different results can be used to combat localised events, or even to get special items. + + The E.X.P.E.R.I-MENTOR's OBLITERATE function has the added use of transferring the destroyed item's material into a linked lathe. + + The IRRADIATE function can be used to transform items into other items, resulting in potential upgrades (or downgrades). + + Users should remember to always wear appropriate protection when using the machine, because malfunction can occur at any moment! + +

        EVENTS

        +

        GLOBAL (happens at any time):

        +
          +
        1. DETECTION MALFUNCTION - The machine's onboard sensors have malfunctioned, causing it to redefine the item's experiment type. + Produces the message: The E.X.P.E.R.I-MENTOR's onboard detection system has malfunctioned!
        2. + +
        3. IANIZATION - The machine's onboard corgi-filter has malfunctioned, causing it to produce a corgi from.. somewhere. + Produces the message: The E.X.P.E.R.I-MENTOR melts the banana, ian-izing the air around it!
        4. + +
        5. RUNTIME ERROR - The machine's onboard C4T-P processor has encountered a critical error, causing it to produce a cat from.. somewhere. + Produces the message: The E.X.P.E.R.I-MENTOR encounters a run-time error!
        6. + +
        7. B100DG0D.EXE - The machine has encountered an unknown subroutine, which has been injected into it's runtime. It upgrades the held item! + Produces the message: The E.X.P.E.R.I-MENTOR improves the banana, drawing the life essence of those nearby!
        8. + +
        9. POWERSINK - The machine's PSU has tripped the charging mechanism! It consumes massive amounts of power! + Produces the message: The E.X.P.E.R.I-MENTOR begins to smoke and hiss, shaking violently!
        10. +
        +

        FAIL:

        + This event is produced when the item mismatches the selected experiment. + Produces a random message similar to: "the Banana rumbles, and shakes, the experiment was a failure!" + +

        POKE:

        +
          +
        1. WILD ARMS - The machine's gryoscopic processors malfunction, causing it to lash out at nearby people with it's arms. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions and destroys the banana, lashing it's arms out at nearby people!
        2. + +
        3. MISTYPE - The machine's interface has been garbled, and it switches to OBLITERATE. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions!
        4. + +
        5. THROW - The machine's spatial recognition device has shifted several meters across the room, causing it to try and repostion the item there. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, throwing the banana!
        6. +
        +

        IRRADIATE:

        +
          +
        1. RADIATION LEAK - The machine's shield has failed, resulting in a toxic radiation leak. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, melting the banana and leaking radiation!
        2. + +
        3. RADIATION DUMP - The machine's recycling and containment functions have failed, resulting in a dump of toxic waste around it + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, spewing toxic waste!
        4. + +
        5. MUTATION - The machine's radio-isotope level meter has malfunctioned, causing it over-irradiate the item, making it transform. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, transforming the banana!
        6. +
        +

        GAS:

        +
          +
        1. TOXIN LEAK - The machine's filtering and vent systems have failed, resulting in a cloud of toxic gas being expelled. + Produces the message: The E.X.P.E.R.I-MENTOR destroys the banana, leaking dangerous gas!
        2. + +
        3. GAS LEAK - The machine's vent systems have failed, resulting in a cloud of harmless, but obscuring gas. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, spewing harmless gas!
        4. + +
        5. ELECTROMAGNETIC IONS - The machine's electrolytic scanners have failed, causing a dangerous Electromagnetic reaction. + Produces the message: The E.X.P.E.R.I-MENTOR melts the banana, ionizing the air around it!
        6. +
        +

        HEAT:

        +
          +
        1. TOASTER - The machine's heating coils have come into contact with the machine's gas storage, causing a large, sudden blast of flame. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, melting the banana and releasing a burst of flame!
        2. + +
        3. SAUNA - The machine's vent loop has sprung a leak, resulting in a large amount of superheated air being dumped around it. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, melting the banana and leaking hot air!
        4. + +
        5. EMERGENCY VENT - The machine's temperature gauge has malfunctioned, resulting in it attempting to cool the area around it, but instead, dumping a cloud of steam. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, activating it's emergency coolant systems!
        6. +
        +

        COLD:

        +
          +
        1. FREEZER - The machine's cooling loop has sprung a leak, resulting in a cloud of super-cooled liquid being blasted into the air. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, shattering the banana and releasing a dangerous cloud of coolant!
        2. + +
        3. FRIDGE - The machine's cooling loop has been exposed to the outside air, resulting in a large decrease in temperature. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, shattering the banana and leaking cold air!
        4. + +
        5. SNOWSTORM - The machine's cooling loop has come into contact with the heating coils, resulting in a sudden blast of cool air. + Produces the message: The E.X.P.E.R.I-MENTOR malfunctions, releasing a flurry of chilly air as the banana pops out!
        6. +
        +

        OBLITERATE:

        +
          +
        1. IMPLOSION - The machine's pressure leveller has malfunctioned, causing it to pierce the space-time momentarily, making everything in the area fly towards it. + Produces the message: The E.X.P.E.R.I-MENTOR's crusher goes way too many levels too high, crushing right through space-time!
        2. + +
        3. DISTORTION - The machine's pressure leveller has completely disabled, resulting in a momentary space-time distortion, causing everything to fly around. + Produces the message: The E.X.P.E.R.I-MENTOR's crusher goes one level too high, crushing right into space-time!
        4. +
        + + + "} + +/obj/item/book/manual/research_and_development + name = "Research and Development 101" + icon_state = "rdbook" + author = "Dr. L. Ight" + title = "Research and Development 101" + dat = {" + + + + + + +

        Science For Dummies

        + So you want to further SCIENCE? Good man/woman/thing! However, SCIENCE is a complicated process even though it's quite easy. For the most part, it's a three step process: +
          +
        1. 1) Deconstruct items in the Destructive Analyzer to advance technology or improve the design.
        2. +
        3. 2) Build unlocked designs in the Protolathe and Circuit Imprinter
        4. +
        5. 3) Repeat!
        6. +
        + + Those are the basic steps to furthing science. What do you do science with, however? Well, you have four major tools: R&D Console, the Destructive Analyzer, the Protolathe, and the Circuit Imprinter. + +

        The R&D Console

        + The R&D console is the cornerstone of any research lab. It is the central system from which the Destructive Analyzer, Protolathe, and Circuit Imprinter (your R&D systems) are controled. More on those systems in their own sections. On its own, the R&D console acts as a database for all your technological gains and new devices you discover. So long as the R&D console remains intact, you'll retain all that SCIENCE you've discovered. Protect it though, because if it gets damaged, you'll lose your data! In addition to this important purpose, the R&D console has a disk menu that lets you transfer data from the database onto disk or from the disk into the database. It also has a settings menu that lets you re-sync with nearby R&D devices (if they've become disconnected), lock the console from the unworthy, upload the data to all other R&D consoles in the network (all R&D consoles are networked by default), connect/disconnect from the network, and purge all data from the database. + NOTE: The technology list screen, circuit imprinter, and protolathe menus are accessible by non-scientists. This is intended to allow 'public' systems for the plebians to utilize some new devices. + +

        Destructive Analyzer

        + This is the source of all technology. Whenever you put a handheld object in it, it analyzes it and determines what sort of technological advancements you can discover from it. If the technology of the object is equal or higher then your current knowledge, you can destroy the object to further those sciences. Some devices (notably, some devices made from the protolathe and circuit imprinter) aren't 100% reliable when you first discover them. If these devices break down, you can put them into the Destructive Analyzer and improve their reliability rather then futher science. If their reliability is high enough ,it'll also advance their related technologies. + +

        Circuit Imprinter

        + This machine, along with the Protolathe, is used to actually produce new devices. The Circuit Imprinter takes glass and various chemicals (depends on the design) to produce new circuit boards to build new machines or computers. It can even be used to print AI modules. + +

        Protolathe

        + This machine is an advanced form of the Autolathe that produce non-circuit designs. Unlike the Autolathe, it can use processed metal, glass, solid plasma, silver, gold, and diamonds along with a variety of chemicals to produce devices. The downside is that, again, not all devices you make are 100% reliable when you first discover them. + +

        Reliability and You

        + As it has been stated, many devices when they're first discovered do not have a 100% reliablity when you first discover them. Instead, the reliablity of the device is dependent upon a base reliability value, whatever improvements to the design you've discovered through the Destructive Analyzer, and any advancements you've made with the device's source technologies. To be able to improve the reliability of a device, you have to use the device until it breaks beyond repair. Once that happens, you can analyze it in a Destructive Analyzer. Once the device reachs a certain minimum reliability, you'll gain tech advancements from it. + +

        Building a Better Machine

        + Many machines produces from circuit boards and inserted into a machine frame require a variety of parts to construct. These are parts like capacitors, batteries, matter bins, and so forth. As your knowledge of science improves, more advanced versions are unlocked. If you use these parts when constructing something, its attributes may be improved. For example, if you use an advanced matter bin when constructing an autolathe (rather then a regular one), it'll hold more materials. Experiment around with stock parts of various qualities to see how they affect the end results! Be warned, however: Tier 3 and higher stock parts don't have 100% reliability and their low reliability may affect the reliability of the end machine. + + + "} + + +/obj/item/book/manual/robotics_cyborgs + name = "Cyborgs for Dummies" + icon_state = "borgbook" + author = "XISC" + title = "Cyborgs for Dummies" + dat = {" + + + + + +

        Cyborgs for Dummies

        + +

        Chapters

        + +
          +
        1. Cyborg Related Equipment
        2. +
        3. Cyborg Modules
        4. +
        5. Cyborg Construction
        6. +
        7. Cyborg Maintenance
        8. +
        9. Cyborg Repairs
        10. +
        11. In Case of Emergency
        12. +
        + + +

        Cyborg Related Equipment

        + +

        Exosuit Fabricator

        + The Exosuit Fabricator is the most important piece of equipment related to cyborgs. It allows the construction of the core cyborg parts. Without these machines, cyborgs can not be built. It seems that they may also benefit from advanced research techniques. + +

        Cyborg Recharging Station

        + This useful piece of equipment will suck power out of the power systems to charge a cyborg's power cell back up to full charge. + +

        Robotics Control Console

        + This useful piece of equipment can be used to immobolize or destroy a cyborg. A word of warning: Cyborgs are expensive pieces of equipment, do not destroy them without good reason, or Nanotrasen may see to it that it never happens again. + + +

        Cyborg Modules

        + When a cyborg is created it picks out of an array of modules to designate its purpose. There are 6 different cyborg modules. + +

        Standard Cyborg

        + The standard cyborg module is a multi-purpose cyborg. It is equipped with various modules, allowing it to do basic tasks.
        A Standard Cyborg comes with: +
          +
        • Crowbar
        • +
        • Stun Baton
        • +
        • Health Analyzer
        • +
        • Fire Extinguisher
        • +
        + +

        Engineering Cyborg

        + The Engineering cyborg module comes equipped with various engineering-related tools to help with engineering-related tasks.
        An Engineering Cyborg comes with: +
          +
        • A basic set of engineering tools
        • +
        • Metal Synthesizer
        • +
        • Reinforced Glass Synthesizer
        • +
        • An RCD
        • +
        • Wire Synthesizer
        • +
        • Fire Extinguisher
        • +
        • Built-in Optical Meson Scanners
        • +
        + +

        Mining Cyborg

        + The Mining Cyborg module comes equipped with the latest in mining equipment. They are efficient at mining due to no need for oxygen, but their power cells limit their time in the mines.
        A Mining Cyborg comes with: +
          +
        • Jackhammer
        • +
        • Shovel
        • +
        • Mining Satchel
        • +
        • Built-in Optical Meson Scanners
        • +
        + +

        Security Cyborg

        + The Security Cyborg module is equipped with effective security measures used to apprehend and arrest criminals without harming them a bit.
        A Security Cyborg comes with: +
          +
        • Stun Baton
        • +
        • Handcuffs
        • +
        • Taser
        • +
        + +

        Janitor Cyborg

        + The Janitor Cyborg module is equipped with various cleaning-facilitating devices.
        A Janitor Cyborg comes with: +
          +
        • Mop
        • +
        • Hand Bucket
        • +
        • Cleaning Spray Synthesizer and Spray Nozzle
        • +
        + +

        Service Cyborg

        + The service cyborg module comes ready to serve your human needs. It includes various entertainment and refreshment devices. Occasionally some service cyborgs may have been referred to as "Bros"
        A Service Cyborg comes with: +
          +
        • Shaker
        • +
        • Industrail Dropper
        • +
        • Platter
        • +
        • Beer Synthesizer
        • +
        • Zippo Lighter
        • +
        • Rapid-Service-Fabricator (Produces various entertainment and refreshment objects)
        • +
        • Pen
        • +
        + +

        Cyborg Construction

        + Cyborg construction is a rather easy process, requiring a decent amount of metal and a few other supplies.
        The required materials to make a cyborg are: +
          +
        • Metal
        • +
        • Two Flashes
        • +
        • One Power Cell (Preferrably rated to 15000w)
        • +
        • Some electrical wires
        • +
        • One Human Brain
        • +
        • One Man-Machine Interface
        • +
        + Once you have acquired the materials, you can start on construction of your cyborg.
        To construct a cyborg, follow the steps below: +
          +
        1. Start the Exosuit Fabricators constructing all of the cyborg parts
        2. +
        3. While the parts are being constructed, take your human brain, and place it inside the Man-Machine Interface
        4. +
        5. Once you have a Robot Head, place your two flashes inside the eye sockets
        6. +
        7. Once you have your Robot Chest, wire the Robot chest, then insert the power cell
        8. +
        9. Attach all of the Robot parts to the Robot frame
        10. +
        11. Insert the Man-Machine Interface (With the Brain inside) Into the Robot Body
        12. +
        13. Congratulations! You have a new cyborg!
        14. +
        + +

        Cyborg Maintenance

        + Occasionally Cyborgs may require maintenance of a couple types, this could include replacing a power cell with a charged one, or possibly maintaining the cyborg's internal wiring. + +

        Replacing a Power Cell

        + Replacing a Power cell is a common type of maintenance for cyborgs. It usually involves replacing the cell with a fully charged one, or upgrading the cell with a larger capacity cell.
        The steps to replace a cell are follows: +
          +
        1. Unlock the Cyborg's Interface by swiping your ID on it
        2. +
        3. Open the Cyborg's outer panel using a crowbar
        4. +
        5. Remove the old power cell
        6. +
        7. Insert the new power cell
        8. +
        9. Close the Cyborg's outer panel using a crowbar
        10. +
        11. Lock the Cyborg's Interface by swiping your ID on it, this will prevent non-qualified personnel from attempting to remove the power cell
        12. +
        + +

        Exposing the Internal Wiring

        + Exposing the internal wiring of a cyborg is fairly easy to do, and is mainly used for cyborg repairs.
        You can easily expose the internal wiring by following the steps below: +
          +
        1. Follow Steps 1 - 3 of "Replacing a Cyborg's Power Cell"
        2. +
        3. Open the cyborg's internal wiring panel by using a screwdriver to unsecure the panel
        4. +
        + To re-seal the cyborg's internal wiring: +
          +
        1. Use a screwdriver to secure the cyborg's internal panel
        2. +
        3. Follow steps 4 - 6 of "Replacing a Cyborg's Power Cell" to close up the cyborg
        4. +
        + +

        Cyborg Repairs

        + Occasionally a Cyborg may become damaged. This could be in the form of impact damage from a heavy or fast-travelling object, or it could be heat damage from high temperatures, or even lasers or Electromagnetic Pulses (EMPs). + +

        Dents

        + If a cyborg becomes damaged due to impact from heavy or fast-moving objects, it will become dented. Sure, a dent may not seem like much, but it can compromise the structural integrity of the cyborg, possibly causing a critical failure. + Dents in a cyborg's frame are rather easy to repair, all you need is to apply a welding tool to the dented area, and the high-tech cyborg frame will repair the dent under the heat of the welder. + +

        Excessive Heat Damage

        + If a cyborg becomes damaged due to excessive heat, it is likely that the internal wires will have been damaged. You must replace those wires to ensure that the cyborg remains functioning properly.
        To replace the internal wiring follow the steps below: +
          +
        1. Unlock the Cyborg's Interface by swiping your ID
        2. +
        3. Open the Cyborg's External Panel using a crowbar
        4. +
        5. Remove the Cyborg's Power Cell
        6. +
        7. Using a screwdriver, expose the internal wiring or the Cyborg
        8. +
        9. Replace the damaged wires inside the cyborg
        10. +
        11. Secure the internal wiring cover using a screwdriver
        12. +
        13. Insert the Cyborg's Power Cell
        14. +
        15. Close the Cyborg's External Panel using a crowbar
        16. +
        17. Lock the Cyborg's Interface by swiping your ID
        18. +
        + These repair tasks may seem difficult, but are essential to keep your cyborgs running at peak efficiency. + +

        In Case of Emergency

        + In case of emergency, there are a few steps you can take. + +

        "Rogue" Cyborgs

        + If the cyborgs seem to become "rogue", they may have non-standard laws. In this case, use extreme caution. + To repair the situation, follow these steps: +
          +
        1. Locate the nearest robotics console
        2. +
        3. Determine which cyborgs are "Rogue"
        4. +
        5. Press the lockdown button to immobolize the cyborg
        6. +
        7. Locate the cyborg
        8. +
        9. Expose the cyborg's internal wiring
        10. +
        11. Check to make sure the LawSync and AI Sync lights are lit
        12. +
        13. If they are not lit, pulse the LawSync wire using a multitool to enable the cyborg's Law Sync
        14. +
        15. Proceed to a cyborg upload console. Nanotrasen usually places these in the same location as AI uplaod consoles.
        16. +
        17. Use a "Reset" upload moduleto reset the cyborg's laws
        18. +
        19. Proceed to a Robotics Control console
        20. +
        21. Remove the lockdown on the cyborg
        22. +
        + +

        As a last resort

        + If all else fails in a case of cyborg-related emergency. There may be only one option. Using a Robotics Control console, you may have to remotely detonate the cyborg. +

        WARNING:

        Do not detonate a borg without an explicit reason for doing so. Cyborgs are expensive pieces of Nanotrasen equipment, and you may be punished for detonating them without reason. + + + + "} + +/obj/item/book/manual/security_space_law + name = "Space Law" + desc = "A set of Nanotrasen guidelines for keeping law and order on their space stations." + icon_state = "bookSpaceLaw" + force = 4 //advanced magistrate tactics + author = "Nanotrasen" + title = "Space Law" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/security_space_law/black + name = "Space Law - Limited Edition" + desc = "A leather-bound, immaculately-written copy of JUSTICE." + icon_state = "bookSpaceLawblack" + title = "Space Law - Limited Edition" + +/obj/item/book/manual/engineering_guide + name = "Engineering Textbook" + icon_state ="bookEngineering2" + author = "Engineering Encyclopedia" + title = "Engineering Textbook" + dat = {" + + + + + + + + + + "} + + +/obj/item/book/manual/chef_recipes + name = "Chef Recipes" + icon_state = "cooked_book" + author = "Victoria Ponsonby" + title = "Chef Recipes" + dat = {" + + + + + +

        Food for Dummies

        + Here is a guide on basic food recipes and also how to not poison your customers accidentally. + +

        Basics:

        + Knead an egg and some flour to make dough. Bake that to make a bun or flatten and cut it. + +

        Burger:

        + Put a bun and some meat into the microwave and turn it on. Then wait. + +

        Bread:

        + Put some dough and an egg into the microwave and then wait. + +

        Waffles:

        + Add two lumps of dough and 10u of sugar to the microwave and then wait. + +

        Popcorn:

        + Add 1 corn to the microwave and wait. + +

        Meat Steak:

        + Put a slice of meat, 1 unit of salt and 1 unit of pepper into the microwave and wait. + +

        Meat Pie:

        + Put a flattened piece of dough and some meat into the microwave and wait. + +

        Boiled Spaghetti:

        + Put the spaghetti (processed flour) and 5 units of water into the microwave and wait. + +

        Donuts:

        + Add some dough and 5 units of sugar to the microwave and wait. + +

        Fries:

        + Add one potato to the processor, then bake them in the microwave. + + + + + "} + +/obj/item/book/manual/barman_recipes + name = "Barman Recipes" + icon_state = "barbook" + author = "Sir John Rose" + title = "Barman Recipes" + dat = {" + + + + + +

        Drinks for dummies

        + Heres a guide for some basic drinks. + +

        Manly Dorf:

        + Mix ale and beer into a glass. + +

        Grog:

        + Mix rum and water into a glass. + +

        Black Russian:

        + Mix vodka and kahlua into a glass. + +

        Irish Cream:

        + Mix cream and whiskey into a glass. + +

        Screwdriver:

        + Mix vodka and orange juice into a glass. + +

        Cafe Latte:

        + Mix milk and coffee into a glass. + +

        Mead:

        + Mix Enzyme, water and sugar into a glass. + +

        Gin Tonic:

        + Mix gin and tonic into a glass. + +

        Classic Martini:

        + Mix vermouth and gin into a glass. + + + + + "} + + +/obj/item/book/manual/detective + name = "The Film Noir: Proper Procedures for Investigations" + icon_state ="bookDetective" + author = "Nanotrasen" + title = "The Film Noir: Proper Procedures for Investigations" + dat = {" + + + + +

        Detective Work

        + + Between your bouts of self-narration, and drinking whiskey on the rocks, you might get a case or two to solve.
        + To have the best chance to solve your case, follow these directions: +

        +

          +
        1. Go to the crime scene.
        2. +
        3. Take your scanner and scan EVERYTHING (Yes, the doors, the tables, even the dog.)
        4. +
        5. Once you are reasonably certain you have every scrap of evidence you can use, find all possible entry points and scan them, too.
        6. +
        7. Return to your office.
        8. +
        9. Using your forensic scanning computer, scan your Scanner to upload all of your evidence into the database.
        10. +
        11. Browse through the resulting dossiers, looking for the one that either has the most complete set of prints, or the most suspicious items handled.
        12. +
        13. If you have 80% or more of the print (The print is displayed) go to step 10, otherwise continue to step 8.
        14. +
        15. Look for clues from the suit fibres you found on your perp, and go about looking for more evidence with this new information, scanning as you go.
        16. +
        17. Try to get a fingerprint card of your perp, as if used in the computer, the prints will be completed on their dossier.
        18. +
        19. Assuming you have enough of a print to see it, grab the biggest complete piece of the print and search the security records for it.
        20. +
        21. Since you now have both your dossier and the name of the person, print both out as evidence, and get security to nab your baddie.
        22. +
        23. Give yourself a pat on the back and a bottle of the ships finest vodka, you did it!.
        24. +
        +

        + It really is that easy! Good luck! + + + "} + +/obj/item/book/manual/nuclear + name = "Fission Mailed: Nuclear Sabotage 101" + icon_state ="bookNuclear" + author = "Syndicate" + title = "Fission Mailed: Nuclear Sabotage 101" + dat = {" + Nuclear Explosives 101:
        + Hello and thank you for choosing the Syndicate for your nuclear information needs.
        + Today's crash course will deal with the operation of a Fusion Class Nanotrasen made Nuclear Device.
        + First and foremost, DO NOT TOUCH ANYTHING UNTIL THE BOMB IS IN PLACE.
        + Pressing any button on the compacted bomb will cause it to extend and bolt itself into place.
        + If this is done to unbolt it one must completely log in which at this time may not be possible.
        + To make the nuclear device functional:
        +

      • Place the nuclear device in the designated detonation zone.
      • +
      • Extend and anchor the nuclear device from its interface.
      • +
      • Insert the nuclear authorisation disk into slot.
      • +
      • Type numeric authorisation code into the keypad. This should have been provided. Note: If you make a mistake press R to reset the device. +
      • Press the E button to log onto the device.
      • + You now have activated the device. To deactivate the buttons at anytime for example when you've already prepped the bomb for detonation remove the auth disk OR press the R on the keypad.
        + Now the bomb CAN ONLY be detonated using the timer. Manual detonation is not an option.
        + Note: Nanotrasen is a pain in the neck.
        + Toggle off the SAFETY.
        + Note: You wouldn't believe how many Syndicate Operatives with doctorates have forgotten this step.
        + So use the - - and + + to set a det time between 5 seconds and 10 minutes.
        + Then press the timer toggle button to start the countdown.
        + Now remove the auth. disk so that the buttons deactivate.
        + Note: THE BOMB IS STILL SET AND WILL DETONATE
        + Now before you remove the disk if you need to move the bomb you can:
        + Toggle off the anchor, move it, and re-anchor.

        + Good luck. Remember the order:
        + Disk, Code, Safety, Timer, Disk, RUN!
        + Intelligence Analysts believe that normal Nanotrasen procedure is for the Captain to secure the nuclear authorisation disk.
        + Good luck! + "} + +/obj/item/book/manual/atmospipes + name = "Pipes and You: Getting To Know Your Scary Tools" + icon_state = "pipingbook" + author = "Maria Crash, Senior Atmospherics Technician" + title = "Pipes and You: Getting To Know Your Scary Tools" + dat = {" + + + + + + +

        Contents

        +
          +
        1. Author's Forward
        2. +
        3. Basic Piping
        4. +
        5. Insulated Pipes
        6. +
        7. Atmospherics Devices
        8. +
        9. Heat Exchange Systems
        10. +
        11. Final Checks
        12. +
        +

        + +

        HOW TO NOT SUCK QUITE SO HARD AT ATMOSPHERICS


        + Or: What the fuck does a "passive gate" do?

        + + Alright. It has come to my attention that a variety of people are unsure of what a "pipe" is and what it does. + Apparently there is an unnatural fear of these arcane devices and their "gases". Spooky, spooky. So, + this will tell you what every device constructable by an ordinary pipe dispenser within atmospherics actually does. + You are not going to learn what to do with them to be the super best person ever, or how to play guitar with passive gates, + or something like that. Just what stuff does.

        + + +

        Basic Pipes


        + The boring ones.
        + TMost ordinary pipes are pretty straightforward. They hold gas. If gas is moving in a direction for some reason, gas will flow in that direction. + That's about it. Even so, here's all of your wonderful pipe options.
        + +
      • Straight pipes: They're pipes. One-meter sections. Straight line. Pretty simple. Just about every pipe and device is based around this + standard one-meter size, so most things will take up as much space as one of these.
      • +
      • Bent pipes: Pipes with a 90 degree bend at the half-meter mark. My goodness.
      • +
      • Pipe manifolds: Pipes that are essentially a "T" shape, allowing you to connect three things at one point.
      • +
      • 4-way manifold: A four-way junction.
      • +
      • Pipe cap: Caps off the end of a pipe. Open ends don't actually vent air, because of the way the pipes are assembled, so, uh. Use them to decorate your house or something.
      • +
      • Manual Valve: A valve that will block off airflow when turned. Can't be used by the AI or cyborgs, because they don't have hands.
      • +
      • Manual T-Valve: Like a manual valve, but at the center of a manifold instead of a straight pipe.


      • + +

        Insulated Pipes


        + Special Public Service Announcement.
        + Our regular pipes are already insulated. These are completely worthless. Punch anyone who uses them.

        + +

        Devices:


        + They actually do something.
        + This is usually where people get frightened,
        afraid, and start calling on their gods and/or cowering in fear. Yes, I can see you doing that right now. + Stop it. It's unbecoming. Most of these are fairly straightforward.
        + +
      • Gas Pump: Take a wild guess. It moves gas in the direction it's pointing (marked by the red line on one end). It moves it based on pressure, the maximum output being 4500 kPa (kilopascals). + Ordinary atmospheric pressure, for comparison, is 101.3 kPa, and the minimum pressure of room-temperature pure oxygen needed to not suffocate in a matter of minutes is 16 kPa + (though 18 is preferred using internals, for various reasons).
      • +
      • Volume pump: This pump goes based on volume, instead of pressure, and the possible maximum pressure it can create in the pipe on the recieving end is double the gas pump because of this, + clocking in at an incredible 9000 kPa. If a pipe with this is destroyed or damaged, and this pressure of gas escapes, it can be incredibly dangerous depending on the size of the pipe filled. + Don't hook this to the distribution loop, or you will make babies cry and the Chief Engineer brutally beat you.
      • +
      • Passive gate: This is essentially a cap on the pressure of gas allowed to flow in a specific direction. + When turned on, instead of actively pumping gas, it measures the pressure flowing through it, and whatever pressure you set is the maximum: it'll cap after that. + In addition, it only lets gas flow one way. The direction the gas flows is opposite the red handle on it, which is confusing to people used to the red stripe on pumps pointing the way.
      • +
      • Unary vent: The basic vent used in rooms. It pumps gas into the room, but can't suck it back out. Controlled by the room's air alarm system.
      • +
      • Scrubber: The other half of room equipment. Filters air, and can suck it in entirely in what's called a "panic siphon". Actvating a panic siphon without very good reason will kill someone. Don't do it.
      • +
      • Meter: A little box with some gagues and numbers. Fasten it to any pipe or manifold, and it'll read you the pressure in it. Very useful.
      • +
      • Gas mixer: Two sides are input, one side is output. Mixes the gases pumped into it at the ratio defined. The side perpendicular to the other two is "node 2", for reference. + Can output this gas at pressures from 0-4500 kPa.
      • +
      • Gas filter: Essentially the opposite of a gas mixer. One side is input. The other two sides are output. One gas type will be filtered into the perpendicular output pipe, + the rest will continue out the other side. Can also output from 0-4500 kPa.
      • + +

        Heat Exchange Systems


        + Will not set you on fire.
        + These systems are used to transfer heat only between two pipes. They will not move gases or any other element, but will equalize the temperature (eventually). Note that because of how gases work (remember: pv=nRt), + a higher temperature will raise pressure, and a lower one will lower temperature.
        + +
      • Pipe: This is a pipe that will exchange heat with the surrounding atmosphere. Place in fire for superheating. Place in space for supercooling.
      • +
      • Bent Pipe: Take a wild guess.
      • +
      • Junction:Junction:The point where you connect your normal pipes to heat exchange pipes. Not necessary for heat exchangers, but necessary for H/E pipes/bent pipes.
      • +
      • Heat Exchanger: These funky-looking bits attach to an open pipe end. Put another heat exchanger directly across from it, and you can transfer heat across two pipes without having to have the gases touch. + This normally shouldn't exchange with the ambient air, despite being totally exposed. Just don't ask questions...

      • + + + That's about it for pipes. Go forth, armed with this knowledge, and try not to break, burn down, or kill anything. Please.
        + + + + "} + +/obj/item/book/manual/evaguide + name = "EVA Gear and You: Not Spending All Day Inside" + icon_state = "evabook" + author = "Maria Crash, Senior Atmospherics Technician" + title = "EVA Gear and You: Not Spending All Day Inside" + dat = {" + + + + + + +

        Contents

        +
          +
        1. A forward on using EVA gear
        2. +
        3. Donning a Civilian Suits
        4. +
        5. Putting on a Hardsuit
        6. +
        7. Final Checks
        8. +
        +

        + +

        EVA Gear and You: Not Spending All Day Inside


        + Or: How not to suffocate because there's a hole in your shoes

        + + EVA gear. Wonderful to use. It's useful for mining, engineering, and occasionally just surviving, if things are that bad. Most people have EVA training, + but apparently there are some on a space station who don't. This guide should give you a basic idea of how to use this gear, safely. It's split into two sections: + Civilian suits and hardsuits.

        + +

        Civilian Suits


        + The bulkiest things this side of Alpha Centauri
        + These suits are the grey ones that are stored in EVA. They're the more simple to get on, but are also a lot bulkier, and provide less protection from environmental hazards such as radiaion or physical impact. + As Medical, Engineering, Security, and Mining all have hardsuits of their own, these don't see much use, but knowing how to put them on is quite useful anyways.

        + + First, take the suit. It should be in three pieces: A top, a bottom,
        and a helmet. Put the bottom on first, shoes and the like will fit in it. If you have magnetic boots, however, + put them on on top of the suit's feet. Next, get the top on, as you would a shirt. It can be somewhat awkward putting these pieces on, due to the makeup of the suit, + but to an extent they will adjust to you. You can then find the snaps and seals around the waist, where the two pieces meet. Fasten these, and double-check their tightness. + The red indicators around the waist of the lower half will turn green when this is done correctly. Next, put on whatever breathing apparatus you're using, be it a gas mask or a breath mask. Make sure the oxygen tube is fastened into it. + Put on the helmet now, straight forward, and make sure the tube goes into the small opening specifically for internals. Again, fasten seals around the neck, a small indicator light in the inside of the helmet should go from red to off when all is fastened. + There is a small slot on the side of the suit where an emergency oxygen tank or extended emergency oxygen tank will fit, + but it is reccomended to have a full-sized tank on your back for EVA.

        + +

        Hardsuits


        + Heavy, uncomfortable, still the best option.
        + These suits come in Engineering, Mining, and the Armory. There's also a couple Medical Hardsuits in EVA. These provide a lot more protection than the standard suits.

        + + Similarly to the other suits, these are split into three parts. Fastening the pant and top are mostly the same as the other spacesuits, with the exception that these are a bit heavier, + though not as bulky. The helmet goes on differently, with the air tube feeing into the suit and out a hole near the left shoulder, while the helmet goes on turned ninety degrees counter-clockwise, + and then is screwed in for one and a quarter full rotations clockwise, leaving the faceplate directly in front of you. There is a small button on the right side of the helmet that activates the helmet light. + The tanks that fasten onto the side slot are emergency tanks, as
        well as full-sized oxygen tanks, leaving your back free for a backpack or satchel.

        + +

        FINAL CHECKS:


        +
      • Are all seals fastened correctly?
      • +
      • Do you either have shoes on under the suit, or magnetic boots on over it?
      • +
      • Do you have a mask on and internals on the suit or your back?
      • +
      • Do you have a way to communicate with the station in case something goes wrong?
      • +
      • Do you have a second person watching if this is a training session?

      • + + If you don't have any further issues, go out and do whatever is necessary.
        + + + + "} + +/obj/item/book/manual/faxes + name = "A Guide to Faxes" + desc = "A Nanotrasen-approved guide to writing faxes" + icon_state = "book6" + author = "Nanotrasen" + title = "A Guide to Faxes" + dat = {" + + + + + + + + +

        Contents

        +
          +
        1. What's a Fax?
        2. +
        3. When to Fax?
        4. +
        5. How to Fax?
        6. +
        +

        + +

        What's a Fax?


        +
      • Faxes are your main method of communicating with the NAS Trurl, better known as Central Command.
      • +
      • Faxes allow personnel on the station to maintain open lines of communication with the NAS Trurl, allowing for vital information to flow both ways.
      • +
      • Being written communications, proper grammar, syntax and typography is required, in addition to a signature and, if applicable, a stamp. Failure to sign faxes will lead to an automatic rejection.
      • +
      • We at Nanotrasen provide Fax Machines to every Head of Staff, in addition to the Magistrate, Nanotrasen Representative, and Internal Affairs Agents.
      • +
      • This means that we trust the recipients of these fax machines to only use them in the proper circumstances (see When to Fax?).
      • + +

        When to Fax?


        +
      • While it is up to the discretion of each individual person to decide when to fax Central Command, there are some simple guidelines on when to do this.
      • +
      • Firstly, any situation that can reasonably be solved on-site, should be handled on-site. Knowledge of Standard Operating Procedure is mandatory for everyone with access to a fax machine.
      • +
      • Resolving issues on-site not only leads to more expedient problem-solving, it also frees up company resources and provides valuable work experience for all parties involved.
      • +
      • This means that you should work with the Heads of Staff concerning personnel and workplace issues, and attempt to resolve situations with them. If, for whatever reason, the relevent Head of Staff is not available or receptive, consider speaking with the Captain and/or Nanotrasen Representative.
      • +
      • If, for whatever reason, these issues cannot be solved on-site, either due to incompetence or just plain refusal to cooperate, faxing Central Command becomes a viable option.
      • +
      • Secondly, station status reports should be sent occasionally, but never at the start of the shift. Remember, we assign personnel to the station. We do not need a repeat of what we just signed off on.
      • +
      • Thirdly, staff/departmental evaluations are always welcome, especially in cases of noticeable (in)competence. Just as a brilliant coworker can be rewarded, an incompetent one can be punished.
      • +
      • Fourthly, do not issue faxes asking for sentences. You have an entire Security department and an associated Detective, not to mention on-site Space Law manuals.
      • +
      • Lastly, please pay attention to context. If the station is facing a massive emergency, such as a Class 7-10 Blob Organism, most, if not all, non-relevant faxes will be duly ignored.
      • + +

        How to Fax?


        +
      • Sending a fax is simple. Simply insert your ID into the fax machine, then log in.
      • +
      • Once logged in, insert a piece of paper and select the destination from the provided list. Remember, you can rename your fax from within the fax machine's menu.
      • +
      • You can send faxes to any other fax machine on the station, which can be a very useful tool when you need to issue broad communications to all of the Heads of Staff.
      • +
      • To send a fax to Central Command, simply select the correct destination, and send the fax. Keep in mind, the communication arrays need to recharge after sending a fax to Central Command, so make sure you sent everything you need.
      • +
      • Lastly, paper bundles can also be faxed as a single item, so feel free to bundle up all relevant documentation and send it in at once.
      • + +
        + + + + "} + +/obj/item/book/manual/sop_science + name = "Science Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all scientific activities." + icon_state = "sop_science" + author = "Nanotrasen" + title = "Science Standard Operating Procedures" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/sop_medical + name = "Medical Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all medical activities." + icon_state = "sop_medical" + author = "Nanotrasen" + title = "Medical Standard Operating Procedures" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/sop_engineering + name = "Engineering Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all engineering activities." + icon_state = "sop_engineering" + author = "Nanotrasen" + title = "Engineering Standard Operating Procedures" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/sop_service + name = "Service Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all service activities." + icon_state = "sop_service" + author = "Nanotrasen" + title = "Service Standard Operating Procedures" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/sop_supply + name = "Supply Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all supply activities." + icon_state = "sop_cargo" + author = "Nanotrasen" + title = "Supply Standard Operating Procedures" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/sop_security + name = "Security Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all security activities." + icon_state = "sop_security" + author = "Nanotrasen" + title = "Security Standard Operating Procedures" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/sop_legal + name = "Legal Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all legal activities." + icon_state = "sop_legal" + author = "Nanotrasen" + title = "Legal Standard Operating Procedures" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/sop_general + name = "Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all station activities." + icon_state = "sop" + author = "Nanotrasen" + title = "Standard Operating Procedures" + dat = {" + + + + + + + + + + "} + +/obj/item/book/manual/sop_command + name = "Command Standard Operating Procedures" + desc = "A set of guidelines aiming at the safe conduct of all Command activities." + icon_state = "sop_command" + author = "Nanotrasen" + title = "Command Standard Operating Procedures" + dat = {" + + + + + + + + + + "} diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index e97fc554d59..c3f2e50174b 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -1,367 +1,367 @@ -/obj/item/melee/energy - var/active = 0 - var/force_on = 30 //force when active - var/throwforce_on = 20 - var/faction_bonus_force = 0 //Bonus force dealt against certain factions - var/list/nemesis_factions //Any mob with a faction that exists in this list will take bonus damage/effects - w_class = WEIGHT_CLASS_SMALL - var/w_class_on = WEIGHT_CLASS_BULKY - var/icon_state_on = "axe1" - var/list/attack_verb_on = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - hitsound = 'sound/weapons/blade1.ogg' // Probably more appropriate than the previous hitsound. -- Dave - usesound = 'sound/weapons/blade1.ogg' - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) - resistance_flags = FIRE_PROOF - toolspeed = 1 - light_power = 2 - var/brightness_on = 2 - var/colormap = list(red=LIGHT_COLOR_RED, blue=LIGHT_COLOR_LIGHTBLUE, green=LIGHT_COLOR_GREEN, purple=LIGHT_COLOR_PURPLE, rainbow=LIGHT_COLOR_WHITE) - -/obj/item/melee/energy/attack(mob/living/target, mob/living/carbon/human/user) - var/nemesis_faction = FALSE - if(LAZYLEN(nemesis_factions)) - for(var/F in target.faction) - if(F in nemesis_factions) - nemesis_faction = TRUE - force += faction_bonus_force - nemesis_effects(user, target) - break - . = ..() - if(nemesis_faction) - force -= faction_bonus_force - -/obj/item/melee/energy/suicide_act(mob/user) - user.visible_message(pick("[user] is slitting [user.p_their()] stomach open with the [name]! It looks like [user.p_theyre()] trying to commit seppuku.", \ - "[user] is falling on the [name]! It looks like [user.p_theyre()] trying to commit suicide.")) - return BRUTELOSS|FIRELOSS - -/obj/item/melee/energy/attack_self(mob/living/carbon/user) - if(user.disabilities & CLUMSY && prob(50)) - to_chat(user, "You accidentally cut yourself with [src], like a doofus!") - user.take_organ_damage(5,5) - active = !active - if(active) - force = force_on - throwforce = throwforce_on - hitsound = 'sound/weapons/blade1.ogg' - throw_speed = 4 - if(attack_verb_on.len) - attack_verb = attack_verb_on - if(!item_color) - icon_state = icon_state_on - set_light(brightness_on) - else - icon_state = "sword[item_color]" - set_light(brightness_on, l_color=colormap[item_color]) - w_class = w_class_on - playsound(user, 'sound/weapons/saberon.ogg', 35, 1) //changed it from 50% volume to 35% because deafness - to_chat(user, "[src] is now active.") - else - force = initial(force) - throwforce = initial(throwforce) - hitsound = initial(hitsound) - throw_speed = initial(throw_speed) - if(attack_verb_on.len) - attack_verb = list() - icon_state = initial(icon_state) - w_class = initial(w_class) - playsound(user, 'sound/weapons/saberoff.ogg', 35, 1) //changed it from 50% volume to 35% because deafness - set_light(0) - to_chat(user, "[src] can now be concealed.") - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - add_fingerprint(user) - return - -/obj/item/melee/energy/axe - name = "energy axe" - desc = "An energised battle axe." - icon_state = "axe0" - force = 40 - force_on = 150 - throwforce = 25 - throwforce_on = 30 - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_NORMAL - w_class_on = WEIGHT_CLASS_HUGE - hitsound = 'sound/weapons/bladeslice.ogg' - flags = CONDUCT - armour_penetration = 100 - origin_tech = "combat=4;magnets=3" - attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") - attack_verb_on = list() - sharp = 1 - light_color = LIGHT_COLOR_WHITE - -/obj/item/melee/energy/axe/suicide_act(mob/user) - user.visible_message("[user] swings the [name] towards [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide.") - return BRUTELOSS|FIRELOSS - -/obj/item/melee/energy/sword - name = "energy sword" - desc = "May the force be within you." - icon_state = "sword0" - force = 3 - throwforce = 5 - throw_speed = 3 - throw_range = 5 - hitsound = "swing_hit" - embed_chance = 75 - embedded_impact_pain_multiplier = 10 - armour_penetration = 35 - origin_tech = "combat=3;magnets=4;syndicate=4" - block_chance = 50 - sharp = 1 - var/hacked = 0 - -/obj/item/melee/energy/sword/New() - ..() - if(item_color == null) - item_color = pick("red", "blue", "green", "purple") - -/obj/item/melee/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 - -/obj/item/melee/energy/sword/cyborg - var/hitcost = 50 - -/obj/item/melee/energy/sword/cyborg/attack(mob/M, var/mob/living/silicon/robot/R) - if(R.cell) - var/obj/item/stock_parts/cell/C = R.cell - if(active && !(C.use(hitcost))) - attack_self(R) - to_chat(R, "It's out of charge!") - return - ..() - return - -/obj/item/melee/energy/sword/cyborg/saw //Used by medical Syndicate cyborgs - name = "energy saw" - desc = "For heavy duty cutting. It has a carbon-fiber blade in addition to a toggleable hard-light edge to dramatically increase sharpness." - force_on = 30 - force = 18 //About as much as a spear - sharp = 1 - hitsound = 'sound/weapons/circsawhit.ogg' - icon = 'icons/obj/surgery.dmi' - icon_state = "esaw_0" - icon_state_on = "esaw_1" - hitcost = 75 //Costs more than a standard cyborg esword - item_color = null - w_class = WEIGHT_CLASS_NORMAL - light_color = LIGHT_COLOR_WHITE - -/obj/item/melee/energy/sword/cyborg/saw/New() - ..() - item_color = null - -/obj/item/melee/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 - -/obj/item/melee/energy/sword/saber - -/obj/item/melee/energy/sword/saber/blue - item_color = "blue" - -/obj/item/melee/energy/sword/saber/purple - item_color = "purple" - -/obj/item/melee/energy/sword/saber/green - item_color = "green" - -/obj/item/melee/energy/sword/saber/red - item_color = "red" - -/obj/item/melee/energy/sword/saber/attackby(obj/item/W, mob/living/user, params) - ..() - if(istype(W, /obj/item/melee/energy/sword/saber)) - if(W == src) - to_chat(user, "You try to attach the end of the energy sword to... itself. You're not very smart, are you?") - if(ishuman(user)) - user.adjustBrainLoss(10) - else - to_chat(user, "You attach the ends of the two energy swords, making a single double-bladed weapon! You're cool.") - var/obj/item/twohanded/dualsaber/newSaber = new /obj/item/twohanded/dualsaber(user.loc) - if(src.hacked) // That's right, we'll only check the "original" esword. - newSaber.hacked = 1 - newSaber.item_color = "rainbow" - user.unEquip(W) - user.unEquip(src) - qdel(W) - qdel(src) - user.put_in_hands(newSaber) - else if(istype(W, /obj/item/multitool)) - if(hacked == 0) - hacked = 1 - item_color = "rainbow" - to_chat(user, "RNBW_ENGAGE") - - if(active) - icon_state = "swordrainbow" - // Updating overlays, copied from welder code. - // I tried calling attack_self twice, which looked cool, except it somehow didn't update the overlays!! - if(user.r_hand == src) - user.update_inv_r_hand() - else if(user.l_hand == src) - user.update_inv_l_hand() - - else - to_chat(user, "It's already fabulous!") - -/obj/item/melee/energy/sword/pirate - name = "energy cutlass" - desc = "Arrrr matey." - icon_state = "cutlass0" - icon_state_on = "cutlass1" - light_color = LIGHT_COLOR_RED - -/obj/item/melee/energy/blade - name = "energy blade" - desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." - icon_state = "blade" - force = 30 //Normal attacks deal esword damage - hitsound = 'sound/weapons/blade1.ogg' - active = 1 - throwforce = 1//Throwing or dropping the item deletes it. - throw_speed = 3 - throw_range = 1 - w_class = WEIGHT_CLASS_BULKY //So you can't hide it in your pocket or some such. - sharp = 1 - -/obj/item/melee/energy/blade/attack_self(mob/user) - return - -/obj/item/melee/energy/blade/hardlight - name = "hardlight blade" - desc = "An extremely sharp blade made out of hard light. Packs quite a punch." - icon_state = "lightblade" - item_state = "lightblade" - -/obj/item/melee/energy/proc/nemesis_effects(mob/living/user, mob/living/target) - return - -/obj/item/melee/energy/cleaving_saw - name = "cleaving saw" - desc = "This saw, effective at drawing the blood of beasts, transforms into a long cleaver that makes use of centrifugal force." - force = 12 - force_on = 20 //force when active - throwforce = 20 - throwforce_on = 20 - icon = 'icons/obj/lavaland/artefacts.dmi' - lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' - righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' - inhand_x_dimension = 64 - inhand_y_dimension = 64 - icon_state = "cleaving_saw" - icon_state_on = "cleaving_saw_open" - slot_flags = SLOT_BELT - var/attack_verb_off = list("attacked", "sawed", "sliced", "torn", "ripped", "diced", "cut") - attack_verb_on = list("cleaved", "swiped", "slashed", "chopped") - hitsound = 'sound/weapons/bladeslice.ogg' - w_class = WEIGHT_CLASS_BULKY - sharp = TRUE - faction_bonus_force = 30 - nemesis_factions = list("mining", "boss") - var/transform_cooldown - var/swiping = FALSE - -/obj/item/melee/energy/cleaving_saw/nemesis_effects(mob/living/user, mob/living/target) - var/datum/status_effect/saw_bleed/B = target.has_status_effect(STATUS_EFFECT_SAWBLEED) - if(!B) - if(!active) //This isn't in the above if-check so that the else doesn't care about active - target.apply_status_effect(STATUS_EFFECT_SAWBLEED) - else - B.add_bleed(B.bleed_buildup) - -/obj/item/melee/energy/cleaving_saw/attack_self(mob/living/carbon/user) - transform_weapon(user) - -/obj/item/melee/energy/cleaving_saw/proc/transform_weapon(mob/living/user, supress_message_text) - if(transform_cooldown > world.time) - return FALSE - - transform_cooldown = world.time + (CLICK_CD_MELEE * 0.5) - user.changeNext_move(CLICK_CD_MELEE * 0.25) - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.disabilities & CLUMSY && prob(50)) - to_chat(H, "You accidentally cut yourself with [src], like a doofus!") - H.take_organ_damage(10,10) - active = !active - if(active) - force = force_on - throwforce = throwforce_on - hitsound = 'sound/weapons/bladeslice.ogg' - throw_speed = 4 - if(attack_verb_on.len) - attack_verb = attack_verb_on - if(!item_color) - icon_state = icon_state_on - set_light(brightness_on) - else - icon_state = "sword[item_color]" - set_light(brightness_on, l_color=colormap[item_color]) - w_class = w_class_on - playsound(user, 'sound/magic/fellowship_armory.ogg', 35, TRUE, frequency = 90000 - (active * 30000)) - to_chat(user, "You open [src]. It will now cleave enemies in a wide arc and deal additional damage to fauna.") - else - force = initial(force) - throwforce = initial(throwforce) - hitsound = initial(hitsound) - throw_speed = initial(throw_speed) - if(attack_verb_on.len) - attack_verb = list() - icon_state = initial(icon_state) - w_class = initial(w_class) - playsound(user, 'sound/magic/fellowship_armory.ogg', 35, 1) //changed it from 50% volume to 35% because deafness - set_light(0) - to_chat(user, "You close [src]. It will now attack rapidly and cause fauna to bleed.") - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - - add_fingerprint(user) - -/obj/item/melee/energy/cleaving_saw/examine(mob/user) - . = ..() - . += "It is [active ? "open, will cleave enemies in a wide arc and deal additional damage to fauna":"closed, and can be used for rapid consecutive attacks that cause fauna to bleed"].
        \ - Both modes will build up existing bleed effects, doing a burst of high damage if the bleed is built up high enough.
        \ - Transforming it immediately after an attack causes the next attack to come out faster.
        " - -/obj/item/melee/energy/cleaving_saw/suicide_act(mob/user) - user.visible_message("[user] is [active ? "closing [src] on [user.p_their()] neck" : "opening [src] into [user.p_their()] chest"]! It looks like [user.p_theyre()] trying to commit suicide!") - transform_cooldown = 0 - transform_weapon(user, TRUE) - return BRUTELOSS - -/obj/item/melee/energy/cleaving_saw/melee_attack_chain(mob/user, atom/target, params) - ..() - if(!active) - user.changeNext_move(CLICK_CD_MELEE * 0.5) //when closed, it attacks very rapidly - -/obj/item/melee/energy/cleaving_saw/attack(mob/living/target, mob/living/carbon/human/user) - if(!active || swiping || !target.density || get_turf(target) == get_turf(user)) - if(!active) - faction_bonus_force = 0 - ..() - if(!active) - faction_bonus_force = initial(faction_bonus_force) - else - var/turf/user_turf = get_turf(user) - var/dir_to_target = get_dir(user_turf, get_turf(target)) - swiping = TRUE - var/static/list/cleaving_saw_cleave_angles = list(0, -45, 45) //so that the animation animates towards the target clicked and not towards a side target - for(var/i in cleaving_saw_cleave_angles) - var/turf/T = get_step(user_turf, turn(dir_to_target, i)) - for(var/mob/living/L in T) - if(user.Adjacent(L) && L.density) - melee_attack_chain(user, L) - swiping = FALSE \ No newline at end of file +/obj/item/melee/energy + var/active = 0 + var/force_on = 30 //force when active + var/throwforce_on = 20 + var/faction_bonus_force = 0 //Bonus force dealt against certain factions + var/list/nemesis_factions //Any mob with a faction that exists in this list will take bonus damage/effects + w_class = WEIGHT_CLASS_SMALL + var/w_class_on = WEIGHT_CLASS_BULKY + var/icon_state_on = "axe1" + var/list/attack_verb_on = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + hitsound = 'sound/weapons/blade1.ogg' // Probably more appropriate than the previous hitsound. -- Dave + usesound = 'sound/weapons/blade1.ogg' + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) + resistance_flags = FIRE_PROOF + toolspeed = 1 + light_power = 2 + var/brightness_on = 2 + var/colormap = list(red=LIGHT_COLOR_RED, blue=LIGHT_COLOR_LIGHTBLUE, green=LIGHT_COLOR_GREEN, purple=LIGHT_COLOR_PURPLE, rainbow=LIGHT_COLOR_WHITE) + +/obj/item/melee/energy/attack(mob/living/target, mob/living/carbon/human/user) + var/nemesis_faction = FALSE + if(LAZYLEN(nemesis_factions)) + for(var/F in target.faction) + if(F in nemesis_factions) + nemesis_faction = TRUE + force += faction_bonus_force + nemesis_effects(user, target) + break + . = ..() + if(nemesis_faction) + force -= faction_bonus_force + +/obj/item/melee/energy/suicide_act(mob/user) + user.visible_message(pick("[user] is slitting [user.p_their()] stomach open with the [name]! It looks like [user.p_theyre()] trying to commit seppuku.", \ + "[user] is falling on the [name]! It looks like [user.p_theyre()] trying to commit suicide.")) + return BRUTELOSS|FIRELOSS + +/obj/item/melee/energy/attack_self(mob/living/carbon/user) + if(user.disabilities & CLUMSY && prob(50)) + to_chat(user, "You accidentally cut yourself with [src], like a doofus!") + user.take_organ_damage(5,5) + active = !active + if(active) + force = force_on + throwforce = throwforce_on + hitsound = 'sound/weapons/blade1.ogg' + throw_speed = 4 + if(attack_verb_on.len) + attack_verb = attack_verb_on + if(!item_color) + icon_state = icon_state_on + set_light(brightness_on) + else + icon_state = "sword[item_color]" + set_light(brightness_on, l_color=colormap[item_color]) + w_class = w_class_on + playsound(user, 'sound/weapons/saberon.ogg', 35, 1) //changed it from 50% volume to 35% because deafness + to_chat(user, "[src] is now active.") + else + force = initial(force) + throwforce = initial(throwforce) + hitsound = initial(hitsound) + throw_speed = initial(throw_speed) + if(attack_verb_on.len) + attack_verb = list() + icon_state = initial(icon_state) + w_class = initial(w_class) + playsound(user, 'sound/weapons/saberoff.ogg', 35, 1) //changed it from 50% volume to 35% because deafness + set_light(0) + to_chat(user, "[src] can now be concealed.") + if(istype(user,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.update_inv_l_hand() + H.update_inv_r_hand() + add_fingerprint(user) + return + +/obj/item/melee/energy/axe + name = "energy axe" + desc = "An energised battle axe." + icon_state = "axe0" + force = 40 + force_on = 150 + throwforce = 25 + throwforce_on = 30 + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL + w_class_on = WEIGHT_CLASS_HUGE + hitsound = 'sound/weapons/bladeslice.ogg' + flags = CONDUCT + armour_penetration = 100 + origin_tech = "combat=4;magnets=3" + attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") + attack_verb_on = list() + sharp = 1 + light_color = LIGHT_COLOR_WHITE + +/obj/item/melee/energy/axe/suicide_act(mob/user) + user.visible_message("[user] swings the [name] towards [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide.") + return BRUTELOSS|FIRELOSS + +/obj/item/melee/energy/sword + name = "energy sword" + desc = "May the force be within you." + icon_state = "sword0" + force = 3 + throwforce = 5 + throw_speed = 3 + throw_range = 5 + hitsound = "swing_hit" + embed_chance = 75 + embedded_impact_pain_multiplier = 10 + armour_penetration = 35 + origin_tech = "combat=3;magnets=4;syndicate=4" + block_chance = 50 + sharp = 1 + var/hacked = 0 + +/obj/item/melee/energy/sword/New() + ..() + if(item_color == null) + item_color = pick("red", "blue", "green", "purple") + +/obj/item/melee/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(active) + return ..() + return 0 + +/obj/item/melee/energy/sword/cyborg + var/hitcost = 50 + +/obj/item/melee/energy/sword/cyborg/attack(mob/M, var/mob/living/silicon/robot/R) + if(R.cell) + var/obj/item/stock_parts/cell/C = R.cell + if(active && !(C.use(hitcost))) + attack_self(R) + to_chat(R, "It's out of charge!") + return + ..() + return + +/obj/item/melee/energy/sword/cyborg/saw //Used by medical Syndicate cyborgs + name = "energy saw" + desc = "For heavy duty cutting. It has a carbon-fiber blade in addition to a toggleable hard-light edge to dramatically increase sharpness." + force_on = 30 + force = 18 //About as much as a spear + sharp = 1 + hitsound = 'sound/weapons/circsawhit.ogg' + icon = 'icons/obj/surgery.dmi' + icon_state = "esaw_0" + icon_state_on = "esaw_1" + hitcost = 75 //Costs more than a standard cyborg esword + item_color = null + w_class = WEIGHT_CLASS_NORMAL + light_color = LIGHT_COLOR_WHITE + +/obj/item/melee/energy/sword/cyborg/saw/New() + ..() + item_color = null + +/obj/item/melee/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + return 0 + +/obj/item/melee/energy/sword/saber + +/obj/item/melee/energy/sword/saber/blue + item_color = "blue" + +/obj/item/melee/energy/sword/saber/purple + item_color = "purple" + +/obj/item/melee/energy/sword/saber/green + item_color = "green" + +/obj/item/melee/energy/sword/saber/red + item_color = "red" + +/obj/item/melee/energy/sword/saber/attackby(obj/item/W, mob/living/user, params) + ..() + if(istype(W, /obj/item/melee/energy/sword/saber)) + if(W == src) + to_chat(user, "You try to attach the end of the energy sword to... itself. You're not very smart, are you?") + if(ishuman(user)) + user.adjustBrainLoss(10) + else + to_chat(user, "You attach the ends of the two energy swords, making a single double-bladed weapon! You're cool.") + var/obj/item/twohanded/dualsaber/newSaber = new /obj/item/twohanded/dualsaber(user.loc) + if(src.hacked) // That's right, we'll only check the "original" esword. + newSaber.hacked = 1 + newSaber.item_color = "rainbow" + user.unEquip(W) + user.unEquip(src) + qdel(W) + qdel(src) + user.put_in_hands(newSaber) + else if(istype(W, /obj/item/multitool)) + if(hacked == 0) + hacked = 1 + item_color = "rainbow" + to_chat(user, "RNBW_ENGAGE") + + if(active) + icon_state = "swordrainbow" + // Updating overlays, copied from welder code. + // I tried calling attack_self twice, which looked cool, except it somehow didn't update the overlays!! + if(user.r_hand == src) + user.update_inv_r_hand() + else if(user.l_hand == src) + user.update_inv_l_hand() + + else + to_chat(user, "It's already fabulous!") + +/obj/item/melee/energy/sword/pirate + name = "energy cutlass" + desc = "Arrrr matey." + icon_state = "cutlass0" + icon_state_on = "cutlass1" + light_color = LIGHT_COLOR_RED + +/obj/item/melee/energy/blade + name = "energy blade" + desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." + icon_state = "blade" + force = 30 //Normal attacks deal esword damage + hitsound = 'sound/weapons/blade1.ogg' + active = 1 + throwforce = 1//Throwing or dropping the item deletes it. + throw_speed = 3 + throw_range = 1 + w_class = WEIGHT_CLASS_BULKY //So you can't hide it in your pocket or some such. + sharp = 1 + +/obj/item/melee/energy/blade/attack_self(mob/user) + return + +/obj/item/melee/energy/blade/hardlight + name = "hardlight blade" + desc = "An extremely sharp blade made out of hard light. Packs quite a punch." + icon_state = "lightblade" + item_state = "lightblade" + +/obj/item/melee/energy/proc/nemesis_effects(mob/living/user, mob/living/target) + return + +/obj/item/melee/energy/cleaving_saw + name = "cleaving saw" + desc = "This saw, effective at drawing the blood of beasts, transforms into a long cleaver that makes use of centrifugal force." + force = 12 + force_on = 20 //force when active + throwforce = 20 + throwforce_on = 20 + icon = 'icons/obj/lavaland/artefacts.dmi' + lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' + righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' + inhand_x_dimension = 64 + inhand_y_dimension = 64 + icon_state = "cleaving_saw" + icon_state_on = "cleaving_saw_open" + slot_flags = SLOT_BELT + var/attack_verb_off = list("attacked", "sawed", "sliced", "torn", "ripped", "diced", "cut") + attack_verb_on = list("cleaved", "swiped", "slashed", "chopped") + hitsound = 'sound/weapons/bladeslice.ogg' + w_class = WEIGHT_CLASS_BULKY + sharp = TRUE + faction_bonus_force = 30 + nemesis_factions = list("mining", "boss") + var/transform_cooldown + var/swiping = FALSE + +/obj/item/melee/energy/cleaving_saw/nemesis_effects(mob/living/user, mob/living/target) + var/datum/status_effect/saw_bleed/B = target.has_status_effect(STATUS_EFFECT_SAWBLEED) + if(!B) + if(!active) //This isn't in the above if-check so that the else doesn't care about active + target.apply_status_effect(STATUS_EFFECT_SAWBLEED) + else + B.add_bleed(B.bleed_buildup) + +/obj/item/melee/energy/cleaving_saw/attack_self(mob/living/carbon/user) + transform_weapon(user) + +/obj/item/melee/energy/cleaving_saw/proc/transform_weapon(mob/living/user, supress_message_text) + if(transform_cooldown > world.time) + return FALSE + + transform_cooldown = world.time + (CLICK_CD_MELEE * 0.5) + user.changeNext_move(CLICK_CD_MELEE * 0.25) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.disabilities & CLUMSY && prob(50)) + to_chat(H, "You accidentally cut yourself with [src], like a doofus!") + H.take_organ_damage(10,10) + active = !active + if(active) + force = force_on + throwforce = throwforce_on + hitsound = 'sound/weapons/bladeslice.ogg' + throw_speed = 4 + if(attack_verb_on.len) + attack_verb = attack_verb_on + if(!item_color) + icon_state = icon_state_on + set_light(brightness_on) + else + icon_state = "sword[item_color]" + set_light(brightness_on, l_color=colormap[item_color]) + w_class = w_class_on + playsound(user, 'sound/magic/fellowship_armory.ogg', 35, TRUE, frequency = 90000 - (active * 30000)) + to_chat(user, "You open [src]. It will now cleave enemies in a wide arc and deal additional damage to fauna.") + else + force = initial(force) + throwforce = initial(throwforce) + hitsound = initial(hitsound) + throw_speed = initial(throw_speed) + if(attack_verb_on.len) + attack_verb = list() + icon_state = initial(icon_state) + w_class = initial(w_class) + playsound(user, 'sound/magic/fellowship_armory.ogg', 35, 1) //changed it from 50% volume to 35% because deafness + set_light(0) + to_chat(user, "You close [src]. It will now attack rapidly and cause fauna to bleed.") + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + H.update_inv_l_hand() + H.update_inv_r_hand() + + add_fingerprint(user) + +/obj/item/melee/energy/cleaving_saw/examine(mob/user) + . = ..() + . += "It is [active ? "open, will cleave enemies in a wide arc and deal additional damage to fauna":"closed, and can be used for rapid consecutive attacks that cause fauna to bleed"].
        \ + Both modes will build up existing bleed effects, doing a burst of high damage if the bleed is built up high enough.
        \ + Transforming it immediately after an attack causes the next attack to come out faster.
        " + +/obj/item/melee/energy/cleaving_saw/suicide_act(mob/user) + user.visible_message("[user] is [active ? "closing [src] on [user.p_their()] neck" : "opening [src] into [user.p_their()] chest"]! It looks like [user.p_theyre()] trying to commit suicide!") + transform_cooldown = 0 + transform_weapon(user, TRUE) + return BRUTELOSS + +/obj/item/melee/energy/cleaving_saw/melee_attack_chain(mob/user, atom/target, params) + ..() + if(!active) + user.changeNext_move(CLICK_CD_MELEE * 0.5) //when closed, it attacks very rapidly + +/obj/item/melee/energy/cleaving_saw/attack(mob/living/target, mob/living/carbon/human/user) + if(!active || swiping || !target.density || get_turf(target) == get_turf(user)) + if(!active) + faction_bonus_force = 0 + ..() + if(!active) + faction_bonus_force = initial(faction_bonus_force) + else + var/turf/user_turf = get_turf(user) + var/dir_to_target = get_dir(user_turf, get_turf(target)) + swiping = TRUE + var/static/list/cleaving_saw_cleave_angles = list(0, -45, 45) //so that the animation animates towards the target clicked and not towards a side target + for(var/i in cleaving_saw_cleave_angles) + var/turf/T = get_step(user_turf, turn(dir_to_target, i)) + for(var/mob/living/L in T) + if(user.Adjacent(L) && L.density) + melee_attack_chain(user, L) + swiping = FALSE diff --git a/code/game/objects/items/weapons/melee/misc.dm b/code/game/objects/items/weapons/melee/misc.dm index b9f1fa679a8..4f9e051ef44 100644 --- a/code/game/objects/items/weapons/melee/misc.dm +++ b/code/game/objects/items/weapons/melee/misc.dm @@ -1,104 +1,104 @@ -/obj/item/melee - needs_permit = 1 - -/obj/item/melee/proc/check_martial_counter(mob/living/carbon/human/target, mob/living/carbon/human/user) - if(target.check_block()) - target.visible_message("[target.name] blocks [src] and twists [user]'s arm behind [user.p_their()] back!", - "You block the attack!") - user.Stun(2) - return TRUE - -/obj/item/melee/chainofcommand - name = "chain of command" - desc = "A tool used by great men to placate the frothing masses." - icon_state = "chain" - item_state = "chain" - flags = CONDUCT - slot_flags = SLOT_BELT - force = 10 - throwforce = 7 - w_class = WEIGHT_CLASS_NORMAL - origin_tech = "combat=5" - attack_verb = list("flogged", "whipped", "lashed", "disciplined") - hitsound = 'sound/weapons/slash.ogg' //pls replace - - -/obj/item/melee/chainofcommand/suicide_act(mob/user) - to_chat(viewers(user), "[user] is strangling [user.p_them()]self with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.") - return OXYLOSS - -/obj/item/melee/rapier - name = "captain's rapier" - desc = "An elegant weapon, for a more civilized age." - icon_state = "rapier" - item_state = "rapier" - flags = CONDUCT - force = 15 - throwforce = 10 - w_class = WEIGHT_CLASS_BULKY - block_chance = 50 - armour_penetration = 75 - sharp = 1 - origin_tech = "combat=5" - attack_verb = list("lunged at", "stabbed") - hitsound = 'sound/weapons/rapierhit.ogg' - materials = list(MAT_METAL = 1000) - -/obj/item/melee/rapier/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) - final_block_chance = 0 //Don't bring a sword to a gunfight - return ..() - -/obj/item/melee/icepick - name = "ice pick" - desc = "Used for chopping ice. Also excellent for mafia esque murders." - icon_state = "icepick" - item_state = "icepick" - force = 15 - throwforce = 10 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("stabbed", "jabbed", "iced,") - -/obj/item/melee/candy_sword - name = "candy cane sword" - desc = "A large candy cane with a sharpened point. Definitely too dangerous for schoolchildren." - icon_state = "candy_sword" - item_state = "candy_sword" - force = 10 - throwforce = 7 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("slashed", "stabbed", "sliced", "caned") - -/obj/item/melee/flyswatter - name = "flyswatter" - desc = "Useful for killing insects of all sizes." - icon_state = "flyswatter" - item_state = "flyswatter" - force = 1 - throwforce = 1 - attack_verb = list("swatted", "smacked") - hitsound = 'sound/effects/snap.ogg' - w_class = WEIGHT_CLASS_SMALL - //Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc. - var/list/strong_against - -/obj/item/melee/flyswatter/Initialize(mapload) - . = ..() - strong_against = typecacheof(list( - /mob/living/simple_animal/hostile/poison/bees/, - /mob/living/simple_animal/butterfly, - /mob/living/simple_animal/cockroach, - /obj/item/queen_bee - )) - -/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag) - . = ..() - if(proximity_flag) - if(is_type_in_typecache(target, strong_against)) - new /obj/effect/decal/cleanable/insectguts(target.drop_location()) - to_chat(user, "You easily splat the [target].") - if(istype(target, /mob/living/)) - var/mob/living/bug = target - bug.death(1) - else - qdel(target) \ No newline at end of file +/obj/item/melee + needs_permit = 1 + +/obj/item/melee/proc/check_martial_counter(mob/living/carbon/human/target, mob/living/carbon/human/user) + if(target.check_block()) + target.visible_message("[target.name] blocks [src] and twists [user]'s arm behind [user.p_their()] back!", + "You block the attack!") + user.Stun(2) + return TRUE + +/obj/item/melee/chainofcommand + name = "chain of command" + desc = "A tool used by great men to placate the frothing masses." + icon_state = "chain" + item_state = "chain" + flags = CONDUCT + slot_flags = SLOT_BELT + force = 10 + throwforce = 7 + w_class = WEIGHT_CLASS_NORMAL + origin_tech = "combat=5" + attack_verb = list("flogged", "whipped", "lashed", "disciplined") + hitsound = 'sound/weapons/slash.ogg' //pls replace + + +/obj/item/melee/chainofcommand/suicide_act(mob/user) + to_chat(viewers(user), "[user] is strangling [user.p_them()]self with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.") + return OXYLOSS + +/obj/item/melee/rapier + name = "captain's rapier" + desc = "An elegant weapon, for a more civilized age." + icon_state = "rapier" + item_state = "rapier" + flags = CONDUCT + force = 15 + throwforce = 10 + w_class = WEIGHT_CLASS_BULKY + block_chance = 50 + armour_penetration = 75 + sharp = 1 + origin_tech = "combat=5" + attack_verb = list("lunged at", "stabbed") + hitsound = 'sound/weapons/rapierhit.ogg' + materials = list(MAT_METAL = 1000) + +/obj/item/melee/rapier/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(attack_type == PROJECTILE_ATTACK) + final_block_chance = 0 //Don't bring a sword to a gunfight + return ..() + +/obj/item/melee/icepick + name = "ice pick" + desc = "Used for chopping ice. Also excellent for mafia esque murders." + icon_state = "icepick" + item_state = "icepick" + force = 15 + throwforce = 10 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("stabbed", "jabbed", "iced,") + +/obj/item/melee/candy_sword + name = "candy cane sword" + desc = "A large candy cane with a sharpened point. Definitely too dangerous for schoolchildren." + icon_state = "candy_sword" + item_state = "candy_sword" + force = 10 + throwforce = 7 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("slashed", "stabbed", "sliced", "caned") + +/obj/item/melee/flyswatter + name = "flyswatter" + desc = "Useful for killing insects of all sizes." + icon_state = "flyswatter" + item_state = "flyswatter" + force = 1 + throwforce = 1 + attack_verb = list("swatted", "smacked") + hitsound = 'sound/effects/snap.ogg' + w_class = WEIGHT_CLASS_SMALL + //Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc. + var/list/strong_against + +/obj/item/melee/flyswatter/Initialize(mapload) + . = ..() + strong_against = typecacheof(list( + /mob/living/simple_animal/hostile/poison/bees/, + /mob/living/simple_animal/butterfly, + /mob/living/simple_animal/cockroach, + /obj/item/queen_bee + )) + +/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(proximity_flag) + if(is_type_in_typecache(target, strong_against)) + new /obj/effect/decal/cleanable/insectguts(target.drop_location()) + to_chat(user, "You easily splat the [target].") + if(istype(target, /mob/living/)) + var/mob/living/bug = target + bug.death(1) + else + qdel(target) diff --git a/code/game/objects/items/weapons/mop.dm b/code/game/objects/items/weapons/mop.dm index fdb90b2c3a0..419df51bbd4 100644 --- a/code/game/objects/items/weapons/mop.dm +++ b/code/game/objects/items/weapons/mop.dm @@ -1,120 +1,120 @@ -/obj/item/mop - desc = "The world of janitalia wouldn't be complete without a mop." - name = "mop" - icon = 'icons/obj/janitor.dmi' - icon_state = "mop" - force = 3 - throwforce = 5 - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("mopped", "bashed", "bludgeoned", "whacked") - resistance_flags = FLAMMABLE - var/mopping = 0 - var/mopcount = 0 - var/mopcap = 5 - var/mopspeed = 30 - -/obj/item/mop/New() - ..() - create_reagents(mopcap) - GLOB.janitorial_equipment += src - -/obj/item/mop/Destroy() - GLOB.janitorial_equipment -= src - return ..() - -/obj/item/mop/proc/clean(turf/simulated/A) - if(reagents.has_reagent("water", 1) || reagents.has_reagent("cleaner", 1) || reagents.has_reagent("holywater", 1)) - A.clean_blood() - for(var/obj/effect/O in A) - if(is_cleanable(O)) - qdel(O) - reagents.reaction(A, REAGENT_TOUCH, 10) //10 is the multiplier for the reaction effect. probably needed to wet the floor properly. - reagents.remove_any(1) //reaction() doesn't use up the reagents - -/obj/item/mop/afterattack(atom/A, mob/user, proximity) - if(!proximity) return - - if(reagents.total_volume < 1) - to_chat(user, "Your mop is dry!") - return - - var/turf/simulated/T = get_turf(A) - - if(istype(A, /obj/item/reagent_containers/glass/bucket) || istype(A, /obj/structure/janitorialcart)) - return - - if(istype(T)) - user.visible_message("[user] begins to clean [T] with [src].", "You begin to clean [T] with [src]...") - - if(do_after(user, src.mopspeed, target = T)) - to_chat(user, "You finish mopping.") - clean(T) - - -/obj/effect/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/mop) || istype(I, /obj/item/soap)) - return - else - return ..() - - -/obj/item/mop/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) - J.put_in_cart(src, user) - J.mymop=src - J.update_icon() - -/obj/item/mop/wash(mob/user, atom/source) - reagents.add_reagent("water", 5) - to_chat(user, "You wet [src] in [source].") - playsound(loc, 'sound/effects/slosh.ogg', 25, 1) - return 1 - -/obj/item/mop/advanced - desc = "The most advanced tool in a custodian's arsenal. Just think of all the viscera you will clean up with this!" - name = "advanced mop" - mopcap = 10 - icon_state = "advmop" - item_state = "mop" - origin_tech = "materials=3;engineering=3" - force = 6 - throwforce = 8 - throw_range = 4 - mopspeed = 20 - var/refill_enabled = TRUE //Self-refill toggle for when a janitor decides to mop with something other than water. - var/refill_rate = 1 //Rate per process() tick mop refills itself - var/refill_reagent = "water" //Determins what reagent to use for refilling, just in case someone wanted to make a HOLY MOP OF PURGING - -/obj/item/mop/advanced/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/mop/advanced/attack_self(mob/user) - refill_enabled = !refill_enabled - if(refill_enabled) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - to_chat(user, "You set the condenser switch to the '[refill_enabled ? "ON" : "OFF"]' position.") - playsound(user, 'sound/machines/click.ogg', 30, 1) - -/obj/item/mop/advanced/process() - - if(reagents.total_volume < mopcap) - reagents.add_reagent(refill_reagent, refill_rate) - -/obj/item/mop/advanced/examine(mob/user) - . = ..() - . += "The condenser switch is set to [refill_enabled ? "ON" : "OFF"]." - -/obj/item/mop/advanced/Destroy() - if(refill_enabled) - STOP_PROCESSING(SSobj, src) - return ..() - - -/obj/item/mop/advanced/cyborg - -/obj/item/mop/advanced/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) - return \ No newline at end of file +/obj/item/mop + desc = "The world of janitalia wouldn't be complete without a mop." + name = "mop" + icon = 'icons/obj/janitor.dmi' + icon_state = "mop" + force = 3 + throwforce = 5 + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("mopped", "bashed", "bludgeoned", "whacked") + resistance_flags = FLAMMABLE + var/mopping = 0 + var/mopcount = 0 + var/mopcap = 5 + var/mopspeed = 30 + +/obj/item/mop/New() + ..() + create_reagents(mopcap) + GLOB.janitorial_equipment += src + +/obj/item/mop/Destroy() + GLOB.janitorial_equipment -= src + return ..() + +/obj/item/mop/proc/clean(turf/simulated/A) + if(reagents.has_reagent("water", 1) || reagents.has_reagent("cleaner", 1) || reagents.has_reagent("holywater", 1)) + A.clean_blood() + for(var/obj/effect/O in A) + if(is_cleanable(O)) + qdel(O) + reagents.reaction(A, REAGENT_TOUCH, 10) //10 is the multiplier for the reaction effect. probably needed to wet the floor properly. + reagents.remove_any(1) //reaction() doesn't use up the reagents + +/obj/item/mop/afterattack(atom/A, mob/user, proximity) + if(!proximity) return + + if(reagents.total_volume < 1) + to_chat(user, "Your mop is dry!") + return + + var/turf/simulated/T = get_turf(A) + + if(istype(A, /obj/item/reagent_containers/glass/bucket) || istype(A, /obj/structure/janitorialcart)) + return + + if(istype(T)) + user.visible_message("[user] begins to clean [T] with [src].", "You begin to clean [T] with [src]...") + + if(do_after(user, src.mopspeed, target = T)) + to_chat(user, "You finish mopping.") + clean(T) + + +/obj/effect/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/mop) || istype(I, /obj/item/soap)) + return + else + return ..() + + +/obj/item/mop/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) + J.put_in_cart(src, user) + J.mymop=src + J.update_icon() + +/obj/item/mop/wash(mob/user, atom/source) + reagents.add_reagent("water", 5) + to_chat(user, "You wet [src] in [source].") + playsound(loc, 'sound/effects/slosh.ogg', 25, 1) + return 1 + +/obj/item/mop/advanced + desc = "The most advanced tool in a custodian's arsenal. Just think of all the viscera you will clean up with this!" + name = "advanced mop" + mopcap = 10 + icon_state = "advmop" + item_state = "mop" + origin_tech = "materials=3;engineering=3" + force = 6 + throwforce = 8 + throw_range = 4 + mopspeed = 20 + var/refill_enabled = TRUE //Self-refill toggle for when a janitor decides to mop with something other than water. + var/refill_rate = 1 //Rate per process() tick mop refills itself + var/refill_reagent = "water" //Determins what reagent to use for refilling, just in case someone wanted to make a HOLY MOP OF PURGING + +/obj/item/mop/advanced/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/mop/advanced/attack_self(mob/user) + refill_enabled = !refill_enabled + if(refill_enabled) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + to_chat(user, "You set the condenser switch to the '[refill_enabled ? "ON" : "OFF"]' position.") + playsound(user, 'sound/machines/click.ogg', 30, 1) + +/obj/item/mop/advanced/process() + + if(reagents.total_volume < mopcap) + reagents.add_reagent(refill_reagent, refill_rate) + +/obj/item/mop/advanced/examine(mob/user) + . = ..() + . += "The condenser switch is set to [refill_enabled ? "ON" : "OFF"]." + +/obj/item/mop/advanced/Destroy() + if(refill_enabled) + STOP_PROCESSING(SSobj, src) + return ..() + + +/obj/item/mop/advanced/cyborg + +/obj/item/mop/advanced/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) + return diff --git a/code/game/objects/items/weapons/paint.dm b/code/game/objects/items/weapons/paint.dm index 59a31e8494b..1b333078354 100644 --- a/code/game/objects/items/weapons/paint.dm +++ b/code/game/objects/items/weapons/paint.dm @@ -1,68 +1,68 @@ -//NEVER USE THIS IT SUX -PETETHEGOAT - -/obj/item/reagent_containers/glass/paint - desc = "It's a paint bucket." - name = "paint bucket" - icon = 'icons/obj/items.dmi' - icon_state = "paint_neutral" - item_state = "paintcan" - materials = list(MAT_METAL=200) - w_class = WEIGHT_CLASS_NORMAL - resistance_flags = FLAMMABLE - max_integrity = 100 - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list(5,10,20,30,50,70) - volume = 70 - container_type = OPENCONTAINER - -/obj/item/reagent_containers/glass/paint/afterattack(turf/simulated/target, mob/user, proximity) - if(!proximity) - return - if(!is_open_container()) - return - if(istype(target) && reagents.total_volume >= 5) - user.visible_message("[target] has been splashed with something by [user]!") - spawn(5) - reagents.reaction(target, REAGENT_TOUCH) - reagents.remove_any(5) - else - return ..() - -/obj/item/reagent_containers/glass/paint/red - name = "red paint bucket" - icon_state = "paint_red" - list_reagents = list("paint_red" = 70) - -/obj/item/reagent_containers/glass/paint/green - name = "green paint bucket" - icon_state = "paint_green" - list_reagents = list("paint_green" = 70) - -/obj/item/reagent_containers/glass/paint/blue - name = "blue paint bucket" - icon_state = "paint_blue" - list_reagents = list("paint_blue" = 70) - -/obj/item/reagent_containers/glass/paint/yellow - name = "yellow paint bucket" - icon_state = "paint_yellow" - list_reagents = list("paint_yellow" = 70) - -/obj/item/reagent_containers/glass/paint/violet - name = "violet paint bucket" - icon_state = "paint_violet" - list_reagents = list("paint_violet" = 70) - -/obj/item/reagent_containers/glass/paint/black - name = "black paint bucket" - icon_state = "paint_black" - list_reagents = list("paint_black" = 70) - -/obj/item/reagent_containers/glass/paint/white - name = "white paint bucket" - icon_state = "paint_white" - list_reagents = list("paint_white" = 70) - -/obj/item/reagent_containers/glass/paint/remover - name = "paint remover bucket" - list_reagents = list("paint_remover" = 70) +//NEVER USE THIS IT SUX -PETETHEGOAT + +/obj/item/reagent_containers/glass/paint + desc = "It's a paint bucket." + name = "paint bucket" + icon = 'icons/obj/items.dmi' + icon_state = "paint_neutral" + item_state = "paintcan" + materials = list(MAT_METAL=200) + w_class = WEIGHT_CLASS_NORMAL + resistance_flags = FLAMMABLE + max_integrity = 100 + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list(5,10,20,30,50,70) + volume = 70 + container_type = OPENCONTAINER + +/obj/item/reagent_containers/glass/paint/afterattack(turf/simulated/target, mob/user, proximity) + if(!proximity) + return + if(!is_open_container()) + return + if(istype(target) && reagents.total_volume >= 5) + user.visible_message("[target] has been splashed with something by [user]!") + spawn(5) + reagents.reaction(target, REAGENT_TOUCH) + reagents.remove_any(5) + else + return ..() + +/obj/item/reagent_containers/glass/paint/red + name = "red paint bucket" + icon_state = "paint_red" + list_reagents = list("paint_red" = 70) + +/obj/item/reagent_containers/glass/paint/green + name = "green paint bucket" + icon_state = "paint_green" + list_reagents = list("paint_green" = 70) + +/obj/item/reagent_containers/glass/paint/blue + name = "blue paint bucket" + icon_state = "paint_blue" + list_reagents = list("paint_blue" = 70) + +/obj/item/reagent_containers/glass/paint/yellow + name = "yellow paint bucket" + icon_state = "paint_yellow" + list_reagents = list("paint_yellow" = 70) + +/obj/item/reagent_containers/glass/paint/violet + name = "violet paint bucket" + icon_state = "paint_violet" + list_reagents = list("paint_violet" = 70) + +/obj/item/reagent_containers/glass/paint/black + name = "black paint bucket" + icon_state = "paint_black" + list_reagents = list("paint_black" = 70) + +/obj/item/reagent_containers/glass/paint/white + name = "white paint bucket" + icon_state = "paint_white" + list_reagents = list("paint_white" = 70) + +/obj/item/reagent_containers/glass/paint/remover + name = "paint remover bucket" + list_reagents = list("paint_remover" = 70) diff --git a/code/game/objects/items/weapons/paiwire.dm b/code/game/objects/items/weapons/paiwire.dm index b4f46ffec46..2e0ddfa828d 100644 --- a/code/game/objects/items/weapons/paiwire.dm +++ b/code/game/objects/items/weapons/paiwire.dm @@ -1,11 +1,11 @@ -/obj/item/pai_cable/proc/plugin(obj/machinery/M as obj, mob/user as mob) - if(istype(M, /obj/machinery/door) || istype(M, /obj/machinery/camera)) - user.visible_message("[user] inserts [src] into a data port on [M].", "You insert [src] into a data port on [M].", "You hear the satisfying click of a wire jack fastening into place.") - user.drop_item() - src.loc = M - src.machine = M - else - user.visible_message("[user] dumbly fumbles to find a place on [M] to plug in [src].", "There aren't any ports on [M] that match the jack belonging to [src].") - -/obj/item/pai_cable/attack(obj/machinery/M as obj, mob/user as mob) - src.plugin(M, user) \ No newline at end of file +/obj/item/pai_cable/proc/plugin(obj/machinery/M as obj, mob/user as mob) + if(istype(M, /obj/machinery/door) || istype(M, /obj/machinery/camera)) + user.visible_message("[user] inserts [src] into a data port on [M].", "You insert [src] into a data port on [M].", "You hear the satisfying click of a wire jack fastening into place.") + user.drop_item() + src.loc = M + src.machine = M + else + user.visible_message("[user] dumbly fumbles to find a place on [M] to plug in [src].", "There aren't any ports on [M] that match the jack belonging to [src].") + +/obj/item/pai_cable/attack(obj/machinery/M as obj, mob/user as mob) + src.plugin(M, user) diff --git a/code/game/objects/items/weapons/scrolls.dm b/code/game/objects/items/weapons/scrolls.dm index 1b2b9c6f21c..70add3839ef 100644 --- a/code/game/objects/items/weapons/scrolls.dm +++ b/code/game/objects/items/weapons/scrolls.dm @@ -1,106 +1,106 @@ -/obj/item/teleportation_scroll - name = "scroll of teleportation" - desc = "A scroll for moving around." - icon = 'icons/obj/wizard.dmi' - icon_state = "scroll" - var/uses = 4.0 - w_class = WEIGHT_CLASS_SMALL - item_state = "paper" - throw_speed = 4 - throw_range = 20 - origin_tech = "bluespace=6" - resistance_flags = FLAMMABLE - -/obj/item/teleportation_scroll/apprentice - name = "lesser scroll of teleportation" - uses = 1 - origin_tech = "bluespace=5" - -/obj/item/teleportation_scroll/attack_self(mob/user as mob) - user.set_machine(src) - var/dat = "Teleportation Scroll:
        " - dat += "Number of uses: [src.uses]
        " - dat += "
        " - dat += "Four uses use them wisely:
        " - dat += "Teleport
        " - dat += "Kind regards,
        Wizards Federation

        P.S. Don't forget to bring your gear, you'll need it to cast most spells.
        " - user << browse(dat, "window=scroll") - onclose(user, "scroll") - return - -/obj/item/teleportation_scroll/Topic(href, href_list) - ..() - if(usr.stat || usr.restrained() || src.loc != usr) - return - var/mob/living/carbon/human/H = usr - if(!( istype(H, /mob/living/carbon/human))) - return 1 - if((usr == src.loc || (in_range(src, usr) && istype(src.loc, /turf)))) - usr.set_machine(src) - if(href_list["spell_teleport"]) - if(src.uses >= 1) - teleportscroll(H) - attack_self(H) - return - -/obj/item/teleportation_scroll/proc/teleportscroll(var/mob/user) - - var/A - - A = input(user, "Area to jump to", "BOOYEA", A) as null|anything in teleportlocs - - if(!A) - return - - var/area/thearea = teleportlocs[A] - - if(user.stat || user.restrained()) - return - if(!((user == loc || (in_range(src, user) && istype(src.loc, /turf))))) - return - - if(thearea.tele_proof && !istype(thearea, /area/wizard_station)) - to_chat(user, "A mysterious force disrupts your arcane spell matrix, and you remain where you are.") - return - - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(5, 0, user.loc) - smoke.attach(user) - smoke.start() - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(!T.density) - var/clear = 1 - for(var/obj/O in T) - if(O.density) - clear = 0 - break - if(clear) - L+=T - - if(!L.len) - to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") - return - - if(user && user.buckled) - user.buckled.unbuckle_mob(user, force = TRUE) - - if(user && user.has_buckled_mobs()) - user.unbuckle_all_mobs(force = TRUE) - - var/list/tempL = L - var/attempt = null - var/success = 0 - while(tempL.len) - attempt = pick(tempL) - success = user.Move(attempt) - if(!success) - tempL.Remove(attempt) - else - break - - if(!success) - user.loc = pick(L) - - smoke.start() - src.uses -= 1 +/obj/item/teleportation_scroll + name = "scroll of teleportation" + desc = "A scroll for moving around." + icon = 'icons/obj/wizard.dmi' + icon_state = "scroll" + var/uses = 4.0 + w_class = WEIGHT_CLASS_SMALL + item_state = "paper" + throw_speed = 4 + throw_range = 20 + origin_tech = "bluespace=6" + resistance_flags = FLAMMABLE + +/obj/item/teleportation_scroll/apprentice + name = "lesser scroll of teleportation" + uses = 1 + origin_tech = "bluespace=5" + +/obj/item/teleportation_scroll/attack_self(mob/user as mob) + user.set_machine(src) + var/dat = "Teleportation Scroll:
        " + dat += "Number of uses: [src.uses]
        " + dat += "
        " + dat += "Four uses use them wisely:
        " + dat += "Teleport
        " + dat += "Kind regards,
        Wizards Federation

        P.S. Don't forget to bring your gear, you'll need it to cast most spells.
        " + user << browse(dat, "window=scroll") + onclose(user, "scroll") + return + +/obj/item/teleportation_scroll/Topic(href, href_list) + ..() + if(usr.stat || usr.restrained() || src.loc != usr) + return + var/mob/living/carbon/human/H = usr + if(!( istype(H, /mob/living/carbon/human))) + return 1 + if((usr == src.loc || (in_range(src, usr) && istype(src.loc, /turf)))) + usr.set_machine(src) + if(href_list["spell_teleport"]) + if(src.uses >= 1) + teleportscroll(H) + attack_self(H) + return + +/obj/item/teleportation_scroll/proc/teleportscroll(var/mob/user) + + var/A + + A = input(user, "Area to jump to", "BOOYEA", A) as null|anything in teleportlocs + + if(!A) + return + + var/area/thearea = teleportlocs[A] + + if(user.stat || user.restrained()) + return + if(!((user == loc || (in_range(src, user) && istype(src.loc, /turf))))) + return + + if(thearea.tele_proof && !istype(thearea, /area/wizard_station)) + to_chat(user, "A mysterious force disrupts your arcane spell matrix, and you remain where you are.") + return + + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(5, 0, user.loc) + smoke.attach(user) + smoke.start() + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + if(!T.density) + var/clear = 1 + for(var/obj/O in T) + if(O.density) + clear = 0 + break + if(clear) + L+=T + + if(!L.len) + to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") + return + + if(user && user.buckled) + user.buckled.unbuckle_mob(user, force = TRUE) + + if(user && user.has_buckled_mobs()) + user.unbuckle_all_mobs(force = TRUE) + + var/list/tempL = L + var/attempt = null + var/success = 0 + while(tempL.len) + attempt = pick(tempL) + success = user.Move(attempt) + if(!success) + tempL.Remove(attempt) + else + break + + if(!success) + user.loc = pick(L) + + smoke.start() + src.uses -= 1 diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm index 7388cff9e5b..362ab601685 100644 --- a/code/game/objects/items/weapons/shields.dm +++ b/code/game/objects/items/weapons/shields.dm @@ -1,148 +1,148 @@ -/obj/item/shield - name = "shield" - block_chance = 50 - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - -/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == THROWN_PROJECTILE_ATTACK) - final_block_chance += 30 - if(attack_type == LEAP_ATTACK) - final_block_chance = 100 - return ..() - -/obj/item/shield/riot - name = "riot shield" - desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." - icon_state = "riot" - slot_flags = SLOT_BACK - force = 10 - throwforce = 5 - throw_speed = 2 - throw_range = 3 - w_class = WEIGHT_CLASS_BULKY - materials = list(MAT_GLASS=7500, MAT_METAL=1000) - origin_tech = "materials=3;combat=4" - attack_verb = list("shoved", "bashed") - var/cooldown = 0 //shield bash cooldown. based on world.time - -/obj/item/shield/riot/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/melee/baton)) - if(cooldown < world.time - 25) - user.visible_message("[user] bashes [src] with [W]!") - playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1) - cooldown = world.time - else - ..() - -/obj/item/shield/riot/roman - name = "roman shield" - desc = "Bears an inscription on the inside: \"Romanes venio domus\"." - icon_state = "roman_shield" - item_state = "roman_shield" - materials = list(MAT_METAL=8500) - -/obj/item/shield/riot/roman/fake - desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." - block_chance = 0 - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0) - -/obj/item/shield/riot/buckler - name = "wooden buckler" - desc = "A medieval wooden buckler." - icon_state = "buckler" - item_state = "buckler" - materials = list() - origin_tech = "materials=1;combat=3;biotech=2" - resistance_flags = FLAMMABLE - block_chance = 30 - -/obj/item/shield/energy - name = "energy combat shield" - desc = "A shield that reflects almost all energy projectiles, but is useless against physical attacks. It can be retracted, expanded, and stored anywhere." - icon_state = "eshield0" // eshield1 for expanded - force = 3 - throwforce = 3 - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - origin_tech = "materials=4;magnets=5;syndicate=6" - attack_verb = list("shoved", "bashed") - var/active = 0 - -/obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 - -/obj/item/shield/energy/IsReflect() - return (active) - -/obj/item/shield/energy/attack_self(mob/living/carbon/human/user) - if(user.disabilities & CLUMSY && prob(50)) - to_chat(user, "You beat yourself in the head with [src].") - user.take_organ_damage(5) - active = !active - icon_state = "eshield[active]" - - if(active) - force = 10 - throwforce = 8 - throw_speed = 2 - w_class = WEIGHT_CLASS_BULKY - playsound(user, 'sound/weapons/saberon.ogg', 35, 1) - to_chat(user, "[src] is now active.") - else - force = 3 - throwforce = 3 - throw_speed = 3 - w_class = WEIGHT_CLASS_TINY - playsound(user, 'sound/weapons/saberoff.ogg', 35, 1) - to_chat(user, "[src] can now be concealed.") - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - add_fingerprint(user) - return - -/obj/item/shield/riot/tele - name = "telescopic shield" - desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." - icon_state = "teleriot0" - origin_tech = "materials=3;combat=4;engineering=4" - slot_flags = null - force = 3 - throwforce = 3 - throw_speed = 3 - throw_range = 4 - w_class = WEIGHT_CLASS_NORMAL - var/active = 0 - -/obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 - -/obj/item/shield/riot/tele/attack_self(mob/living/user) - active = !active - icon_state = "teleriot[active]" - playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1) - - if(active) - force = 8 - throwforce = 5 - throw_speed = 2 - w_class = WEIGHT_CLASS_BULKY - slot_flags = SLOT_BACK - to_chat(user, "You extend \the [src].") - else - force = 3 - throwforce = 3 - throw_speed = 3 - w_class = WEIGHT_CLASS_NORMAL - slot_flags = null - to_chat(user, "[src] can now be concealed.") - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - add_fingerprint(user) - return +/obj/item/shield + name = "shield" + block_chance = 50 + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + +/obj/item/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(attack_type == THROWN_PROJECTILE_ATTACK) + final_block_chance += 30 + if(attack_type == LEAP_ATTACK) + final_block_chance = 100 + return ..() + +/obj/item/shield/riot + name = "riot shield" + desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." + icon_state = "riot" + slot_flags = SLOT_BACK + force = 10 + throwforce = 5 + throw_speed = 2 + throw_range = 3 + w_class = WEIGHT_CLASS_BULKY + materials = list(MAT_GLASS=7500, MAT_METAL=1000) + origin_tech = "materials=3;combat=4" + attack_verb = list("shoved", "bashed") + var/cooldown = 0 //shield bash cooldown. based on world.time + +/obj/item/shield/riot/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/melee/baton)) + if(cooldown < world.time - 25) + user.visible_message("[user] bashes [src] with [W]!") + playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1) + cooldown = world.time + else + ..() + +/obj/item/shield/riot/roman + name = "roman shield" + desc = "Bears an inscription on the inside: \"Romanes venio domus\"." + icon_state = "roman_shield" + item_state = "roman_shield" + materials = list(MAT_METAL=8500) + +/obj/item/shield/riot/roman/fake + desc = "Bears an inscription on the inside: \"Romanes venio domus\". It appears to be a bit flimsy." + block_chance = 0 + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0) + +/obj/item/shield/riot/buckler + name = "wooden buckler" + desc = "A medieval wooden buckler." + icon_state = "buckler" + item_state = "buckler" + materials = list() + origin_tech = "materials=1;combat=3;biotech=2" + resistance_flags = FLAMMABLE + block_chance = 30 + +/obj/item/shield/energy + name = "energy combat shield" + desc = "A shield that reflects almost all energy projectiles, but is useless against physical attacks. It can be retracted, expanded, and stored anywhere." + icon_state = "eshield0" // eshield1 for expanded + force = 3 + throwforce = 3 + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + origin_tech = "materials=4;magnets=5;syndicate=6" + attack_verb = list("shoved", "bashed") + var/active = 0 + +/obj/item/shield/energy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + return 0 + +/obj/item/shield/energy/IsReflect() + return (active) + +/obj/item/shield/energy/attack_self(mob/living/carbon/human/user) + if(user.disabilities & CLUMSY && prob(50)) + to_chat(user, "You beat yourself in the head with [src].") + user.take_organ_damage(5) + active = !active + icon_state = "eshield[active]" + + if(active) + force = 10 + throwforce = 8 + throw_speed = 2 + w_class = WEIGHT_CLASS_BULKY + playsound(user, 'sound/weapons/saberon.ogg', 35, 1) + to_chat(user, "[src] is now active.") + else + force = 3 + throwforce = 3 + throw_speed = 3 + w_class = WEIGHT_CLASS_TINY + playsound(user, 'sound/weapons/saberoff.ogg', 35, 1) + to_chat(user, "[src] can now be concealed.") + if(istype(user,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.update_inv_l_hand() + H.update_inv_r_hand() + add_fingerprint(user) + return + +/obj/item/shield/riot/tele + name = "telescopic shield" + desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." + icon_state = "teleriot0" + origin_tech = "materials=3;combat=4;engineering=4" + slot_flags = null + force = 3 + throwforce = 3 + throw_speed = 3 + throw_range = 4 + w_class = WEIGHT_CLASS_NORMAL + var/active = 0 + +/obj/item/shield/riot/tele/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(active) + return ..() + return 0 + +/obj/item/shield/riot/tele/attack_self(mob/living/user) + active = !active + icon_state = "teleriot[active]" + playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1) + + if(active) + force = 8 + throwforce = 5 + throw_speed = 2 + w_class = WEIGHT_CLASS_BULKY + slot_flags = SLOT_BACK + to_chat(user, "You extend \the [src].") + else + force = 3 + throwforce = 3 + throw_speed = 3 + w_class = WEIGHT_CLASS_NORMAL + slot_flags = null + to_chat(user, "[src] can now be concealed.") + if(istype(user,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.update_inv_l_hand() + H.update_inv_r_hand() + add_fingerprint(user) + return diff --git a/code/game/objects/items/weapons/storage/artistic_toolbox.dm b/code/game/objects/items/weapons/storage/artistic_toolbox.dm index f1422186ab8..610f0d14ab0 100644 --- a/code/game/objects/items/weapons/storage/artistic_toolbox.dm +++ b/code/game/objects/items/weapons/storage/artistic_toolbox.dm @@ -223,4 +223,4 @@ affected_mob.adjustBruteLoss(5) if(ismob(progenitor.loc)) - progenitor.hunger++ \ No newline at end of file + progenitor.hunger++ diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm index af5e9540b02..757d2b05757 100644 --- a/code/game/objects/items/weapons/storage/backpack.dm +++ b/code/game/objects/items/weapons/storage/backpack.dm @@ -1,587 +1,587 @@ - -/* - * Backpack - */ - -/obj/item/storage/backpack - name = "backpack" - desc = "You wear this on your back and put items into it." - icon_state = "backpack" - item_state = "backpack" - lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - slot_flags = SLOT_BACK //ERROOOOO - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 21 - storage_slots = 21 - resistance_flags = NONE - max_integrity = 300 - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/back.dmi', - "Vox Armalis" = 'icons/mob/species/armalis/back.dmi', - "Grey" = 'icons/mob/species/grey/back.dmi' - ) //For Armalis anything but this and the nitrogen tank will use the default backpack icon. - -/obj/item/storage/backpack/attackby(obj/item/W as obj, mob/user as mob, params) - playsound(src.loc, "rustle", 50, 1, -5) - return ..() - -/obj/item/storage/backpack/examine(mob/user) - var/space_used = 0 - . = ..() - if(in_range(user, src)) - for(var/obj/item/I in contents) - space_used += I.w_class - if(!space_used) - . += " [src] is empty." - else if(space_used <= max_combined_w_class*0.6) - . += " [src] still has plenty of remaining space." - else if(space_used <= max_combined_w_class*0.8) - . += " [src] is beginning to run out of space." - else if(space_used < max_combined_w_class) - . += " [src] doesn't have much space left." - else - . += " [src] is full." - -/* - * Backpack Types - */ - -/obj/item/storage/backpack/holding - name = "Bag of Holding" - desc = "A backpack that opens into a localized pocket of Blue Space." - origin_tech = "bluespace=5;materials=4;engineering=4;plasmatech=5" - icon_state = "holdingpack" - item_state = "holdingpack" - max_w_class = WEIGHT_CLASS_HUGE - max_combined_w_class = 35 - resistance_flags = FIRE_PROOF - flags_2 = NO_MAT_REDEMPTION_2 - cant_hold = list(/obj/item/storage/backpack/holding) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50) - -/obj/item/storage/backpack/holding/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/storage/backpack/holding)) - var/response = alert(user, "This creates a singularity, destroying you and much of the station. Are you SURE?","IMMINENT DEATH!", "No", "Yes") - if(response == "Yes") - user.visible_message("[user] grins as [user.p_they()] begin[user.p_s()] to put a Bag of Holding into a Bag of Holding!", "You begin to put the Bag of Holding into the Bag of Holding!") - if(do_after(user, 30, target=src)) - investigate_log("has become a singularity. Caused by [user.key]","singulo") - user.visible_message("[user] erupts in evil laughter as [user.p_they()] put[user.p_s()] the Bag of Holding into another Bag of Holding!", "You can't help but laugh wildly as you put the Bag of Holding into another Bag of Holding, complete darkness surrounding you."," You hear the sound of scientific evil brewing! ") - qdel(W) - var/obj/singularity/singulo = new /obj/singularity(get_turf(user)) - singulo.energy = 300 //To give it a small boost - message_admins("[key_name_admin(user)] detonated a bag of holding JMP)") - log_game("[key_name(user)] detonated a bag of holding") - qdel(src) - else - user.visible_message("After careful consideration, [user] has decided that putting a Bag of Holding inside another Bag of Holding would not yield the ideal outcome.","You come to the realization that this might not be the greatest idea.") - else - . = ..() - -/obj/item/storage/backpack/holding/singularity_act(current_size) - var/dist = max((current_size - 2),1) - explosion(src.loc,(dist),(dist*2),(dist*4)) - -/obj/item/storage/backpack/santabag - name = "Santa's Gift Bag" - desc = "Space Santa uses this to deliver toys to all the nice children in space on Christmas! Wow, it's pretty big!" - icon_state = "giftbag0" - item_state = "giftbag" - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 400 // can store a ton of shit! - -/obj/item/storage/backpack/cultpack - name = "trophy rack" - desc = "It's useful for both carrying extra gear and proudly declaring your insanity." - icon_state = "cultpack" - -/obj/item/storage/backpack/clown - name = "Giggles Von Honkerton" - desc = "It's a backpack made by Honk! Co." - icon_state = "clownpack" - item_state = "clownpack" - -/obj/item/storage/backpack/clown/syndie - -/obj/item/storage/backpack/clown/syndie/New() - ..() - new /obj/item/clothing/under/rank/clown(src) - new /obj/item/clothing/shoes/magboots/clown(src) - new /obj/item/clothing/mask/chameleon(src) - new /obj/item/radio/headset/headset_service(src) - new /obj/item/pda/clown(src) - new /obj/item/storage/box/survival(src) - new /obj/item/reagent_containers/food/snacks/grown/banana(src) - new /obj/item/stamp/clown(src) - new /obj/item/toy/crayon/rainbow(src) - new /obj/item/storage/fancy/crayons(src) - new /obj/item/reagent_containers/spray/waterflower(src) - new /obj/item/reagent_containers/food/drinks/bottle/bottleofbanana(src) - new /obj/item/instrument/bikehorn(src) - new /obj/item/bikehorn(src) - new /obj/item/dnainjector/comic(src) - -/obj/item/storage/backpack/mime - name = "Parcel Parceaux" - desc = "A silent backpack made for those silent workers. Silence Co." - icon_state = "mimepack" - item_state = "mimepack" - -/obj/item/storage/backpack/medic - name = "medical backpack" - desc = "It's a backpack especially designed for use in a sterile environment." - icon_state = "medicalpack" - item_state = "medicalpack" - -/obj/item/storage/backpack/security - name = "security backpack" - desc = "It's a very robust backpack." - icon_state = "securitypack" - item_state = "securitypack" - -/obj/item/storage/backpack/captain - name = "captain's backpack" - desc = "It's a special backpack made exclusively for Nanotrasen officers." - icon_state = "captainpack" - item_state = "captainpack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/industrial - name = "industrial backpack" - desc = "It's a tough backpack for the daily grind of station life." - icon_state = "engiepack" - item_state = "engiepack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/explorer - name = "explorer bag" - desc = "A robust backpack for stashing your loot." - icon_state = "explorerpack" - item_state = "explorerpack" - -/obj/item/storage/backpack/botany - name = "botany backpack" - desc = "It's a backpack made of all-natural fibers." - icon_state = "botpack" - item_state = "botpack" - -/obj/item/storage/backpack/chemistry - name = "chemistry backpack" - desc = "A backpack specially designed to repel stains and hazardous liquids." - icon_state = "chempack" - item_state = "chempack" - -/obj/item/storage/backpack/genetics - name = "genetics backpack" - desc = "A bag designed to be super tough, just in case someone hulks out on you." - icon_state = "genepack" - item_state = "genepack" - -/obj/item/storage/backpack/science - name = "science backpack" - desc = "A specially designed backpack. It's fire resistant and smells vaguely of plasma." - icon_state = "toxpack" - item_state = "toxpack" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/virology - name = "virology backpack" - desc = "A backpack made of hypo-allergenic fibers. It's designed to help prevent the spread of disease. Smells like monkey." - icon_state = "viropack" - item_state = "viropack" - -/obj/item/storage/backpack/blueshield - name = "blueshield backpack" - desc = "A robust backpack issued to Nanotrasen's finest." - icon_state = "blueshieldpack" - item_state = "blueshieldpack" - -/* - * Satchel Types - */ - -/obj/item/storage/backpack/satchel - name = "leather satchel" - desc = "It's a very fancy satchel made with fine leather." - icon_state = "satchel" - resistance_flags = FIRE_PROOF - var/strap_side_straight = FALSE - -/obj/item/storage/backpack/satchel/verb/switch_strap() - set name = "Switch Strap Side" - set category = "Object" - set src in usr - - if(usr.incapacitated()) - return - strap_side_straight = !strap_side_straight - icon_state = strap_side_straight ? "satchel-flipped" : "satchel" - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_back() - - - -/obj/item/storage/backpack/satcheldeluxe - name = "leather satchel" - desc = "An NT Deluxe satchel, with the finest quality leather and the company logo in a thin gold stitch" - icon_state = "nt_deluxe" - -/obj/item/storage/backpack/satchel/withwallet/New() - ..() - new /obj/item/storage/wallet/random(src) - -/obj/item/storage/backpack/satchel_norm - name = "satchel" - desc = "A deluxe NT Satchel, made of the highest quality leather." - icon_state = "satchel-norm" - -/obj/item/storage/backpack/satchel_eng - name = "industrial satchel" - desc = "A tough satchel with extra pockets." - icon_state = "satchel-eng" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel/explorer - name = "explorer satchel" - desc = "A robust satchel for stashing your loot." - icon_state = "satchel-explorer" - item_state = "securitypack" - -/obj/item/storage/backpack/satchel_med - name = "medical satchel" - desc = "A sterile satchel used in medical departments." - icon_state = "satchel-med" - -/obj/item/storage/backpack/satchel_vir - name = "virologist satchel" - desc = "A sterile satchel with virologist colours." - icon_state = "satchel-vir" - -/obj/item/storage/backpack/satchel_chem - name = "chemist satchel" - desc = "A sterile satchel with chemist colours." - icon_state = "satchel-chem" - -/obj/item/storage/backpack/satchel_gen - name = "geneticist satchel" - desc = "A sterile satchel with geneticist colours." - icon_state = "satchel-gen" - -/obj/item/storage/backpack/satchel_tox - name = "scientist satchel" - desc = "Useful for holding research materials." - icon_state = "satchel-tox" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel_sec - name = "security satchel" - desc = "A robust satchel for security related needs." - icon_state = "satchel-sec" - -/obj/item/storage/backpack/satchel_hyd - name = "hydroponics satchel" - desc = "A green satchel for plant related work." - icon_state = "satchel-hyd" - -/obj/item/storage/backpack/satchel_cap - name = "captain's satchel" - desc = "An exclusive satchel for Nanotrasen officers." - icon_state = "satchel-cap" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/satchel_blueshield - name = "blueshield satchel" - desc = "A robust satchel issued to Nanotrasen's finest." - icon_state = "satchel-blueshield" - -/obj/item/storage/backpack/satchel_flat - name = "smuggler's satchel" - desc = "A very slim satchel that can easily fit into tight spaces." - icon_state = "satchel-flat" - w_class = WEIGHT_CLASS_NORMAL //Can fit in backpacks itself. - max_combined_w_class = 15 - level = 1 - cant_hold = list(/obj/item/storage/backpack/satchel_flat) //muh recursive backpacks - -/obj/item/storage/backpack/satchel_flat/hide(var/intact) - if(intact) - invisibility = 101 - anchored = 1 //otherwise you can start pulling, cover it, and drag around an invisible backpack. - icon_state = "[initial(icon_state)]2" - else - invisibility = initial(invisibility) - anchored = 0 - icon_state = initial(icon_state) - -/obj/item/storage/backpack/satchel_flat/New() - ..() - new /obj/item/stack/tile/plasteel(src) - new /obj/item/crowbar(src) - -/* - * Duffelbags - My thanks to MrSnapWalk for the original icon and Neinhaus for the job variants - Dave. - */ - -/obj/item/storage/backpack/duffel - name = "duffelbag" - desc = "A large grey duffelbag designed to hold more items than a regular bag." - icon_state = "duffel" - item_state = "duffel" - max_combined_w_class = 30 - slowdown = 1 - -/obj/item/storage/backpack/duffel/syndie - name = "suspicious looking duffelbag" - desc = "A large duffelbag for holding extra tactical supplies." - icon_state = "duffel-syndie" - item_state = "duffel-syndimed" - origin_tech = "syndicate=1" - silent = 1 - slowdown = 0 - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffel/syndie/med - name = "suspicious duffelbag" - desc = "A black and red duffelbag with a red and white cross sewn onto it." - icon_state = "duffel-syndimed" - item_state = "duffel-syndimed" - -/obj/item/storage/backpack/duffel/syndie/ammo - name = "suspicious duffelbag" - desc = "A black and red duffelbag with a patch depicting shotgun shells sewn onto it." - icon_state = "duffel-syndiammo" - item_state = "duffel-syndiammo" - -/obj/item/storage/backpack/duffel/syndie/ammo/shotgun - desc = "A large duffelbag, packed to the brim with Bulldog shotgun ammo." - -/obj/item/storage/backpack/duffel/syndie/ammo/shotgun/New() - ..() - for(var/i in 1 to 6) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/ammo_box/magazine/m12g/buckshot(src) - new /obj/item/ammo_box/magazine/m12g/buckshot(src) - new /obj/item/ammo_box/magazine/m12g/dragon(src) - -/obj/item/storage/backpack/duffel/mining_conscript/ - name = "mining conscription kit" - desc = "A kit containing everything a crewmember needs to support a shaft miner in the field." - -/obj/item/storage/backpack/duffel/mining_conscript/New() - ..() - new /obj/item/pickaxe(src) - new /obj/item/clothing/glasses/meson(src) - new /obj/item/t_scanner/adv_mining_scanner/lesser(src) - new /obj/item/storage/bag/ore(src) - new /obj/item/clothing/under/rank/miner/lavaland(src) - new /obj/item/encryptionkey/headset_cargo(src) - new /obj/item/clothing/mask/gas/explorer(src) - new /obj/item/gun/energy/kinetic_accelerator(src) - new /obj/item/kitchen/knife/combat/survival(src) - new /obj/item/flashlight/seclite(src) - new /obj/item/clothing/suit/hooded/explorer(src) - - -/obj/item/storage/backpack/duffel/syndie/ammo/smg - desc = "A large duffel bag, packed to the brim with C-20r magazines." - -/obj/item/storage/backpack/duffel/syndie/ammo/smg/New() - ..() - for(var/i in 1 to 10) - new /obj/item/ammo_box/magazine/smgm45(src) - -/obj/item/storage/backpack/duffel/syndie/c20rbundle - desc = "A large duffel bag containing a C-20r, some magazines, and a cheap looking suppressor." - -/obj/item/storage/backpack/duffel/syndie/c20rbundle/New() - ..() - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/ammo_box/magazine/smgm45(src) - new /obj/item/gun/projectile/automatic/c20r(src) - new /obj/item/suppressor/specialoffer(src) - -/obj/item/storage/backpack/duffel/syndie/bulldogbundle - desc = "A large duffel bag containing a Bulldog, some drums, and a pair of thermal imaging glasses." - -/obj/item/storage/backpack/duffel/syndie/bulldogbundle/New() - ..() - new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/ammo_box/magazine/m12g(src) - new /obj/item/clothing/glasses/chameleon/thermal(src) - -/obj/item/storage/backpack/duffel/syndie/med/medicalbundle - desc = "A large duffel bag containing a tactical medkit, a medical beam gun and a pair of syndicate magboots." - -/obj/item/storage/backpack/duffel/syndie/med/medicalbundle/New() - ..() - new /obj/item/storage/firstaid/tactical(src) - new /obj/item/clothing/shoes/magboots/syndie(src) - new /obj/item/gun/medbeam(src) - -/obj/item/storage/backpack/duffel/syndie/c4/New() - ..() - for(var/i in 1 to 10) - new /obj/item/grenade/plastic/c4(src) - -/obj/item/storage/backpack/duffel/syndie/x4/New() - ..() - for(var/i in 1 to 3) - new /obj/item/grenade/plastic/x4(src) - -/obj/item/storage/backpack/duffel/syndie/surgery - name = "surgery duffelbag" - desc = "A suspicious looking duffelbag for holding surgery tools." - icon_state = "duffel-syndimed" - item_state = "duffel-syndimed" - -/obj/item/storage/backpack/duffel/syndie/surgery/New() - ..() - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/retractor(src) - new /obj/item/circular_saw(src) - new /obj/item/surgicaldrill(src) - new /obj/item/cautery(src) - new /obj/item/bonegel(src) - new /obj/item/bonesetter(src) - new /obj/item/FixOVein(src) - new /obj/item/clothing/suit/straight_jacket(src) - new /obj/item/clothing/mask/muzzle(src) - -/obj/item/storage/backpack/duffel/syndie/surgery_fake //for maint spawns - name = "surgery duffelbag" - desc = "A suspicious looking duffelbag for holding surgery tools." - icon_state = "duffel-syndimed" - item_state = "duffel-syndimed" - -/obj/item/storage/backpack/duffel/syndie/surgery_fake/New() - ..() - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/retractor(src) - new /obj/item/cautery(src) - new /obj/item/bonegel(src) - new /obj/item/bonesetter(src) - new /obj/item/FixOVein(src) - if(prob(50)) - new /obj/item/circular_saw(src) - new /obj/item/surgicaldrill(src) - -/obj/item/storage/backpack/duffel/captain - name = "captain's duffelbag" - desc = "A duffelbag designed to hold large quantities of condoms." - icon_state = "duffel-captain" - item_state = "duffel-captain" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffel/security - name = "security duffelbag" - desc = "A duffelbag built with robust fabric!" - icon_state = "duffel-security" - item_state = "duffel-security" - -/obj/item/storage/backpack/duffel/virology - name = "virology duffelbag" - desc = "A white duffelbag designed to contain biohazards." - icon_state = "duffel-viro" - item_state = "duffel-viro" - -/obj/item/storage/backpack/duffel/science - name = "scientist duffelbag" - desc = "A duffelbag designed to hold the secrets of space." - icon_state = "duffel-toxins" - item_state = "duffel-toxins" - -/obj/item/storage/backpack/duffel/genetics - name = "geneticist duffelbag" - desc = "A duffelbag designed to hold gibbering monkies." - icon_state = "duffel-gene" - item_state = "duffel-gene" - -/obj/item/storage/backpack/duffel/chemistry - name = "chemist duffelbag" - desc = "A duffelbag designed to hold corrosive substances." - icon_state = "duffel-chemistry" - item_state = "duffel-chemistry" - -/obj/item/storage/backpack/duffel/medical - name = "medical duffelbag" - desc = "A duffelbag designed to hold medicine." - icon_state = "duffel-med" - item_state = "duffel-med" - -/obj/item/storage/backpack/duffel/engineering - name = "industrial duffelbag" - desc = "A duffelbag designed to hold tools." - icon_state = "duffel-eng" - item_state = "duffel-eng" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffel/atmos - name = "atmospherics duffelbag" - desc = "A duffelbag designed to hold tools. This one is specially designed for atmospherics." - icon_state = "duffel-atmos" - item_state = "duffel-atmos" - resistance_flags = FIRE_PROOF - -/obj/item/storage/backpack/duffel/hydro - name = "hydroponics duffelbag" - desc = "A duffelbag designed to hold seeds and fauna." - icon_state = "duffel-hydro" - item_state = "duffel-hydro" - -/obj/item/storage/backpack/duffel/clown - name = "smiles von wiggleton" - desc = "A duffelbag designed to hold bananas and bike horns." - icon_state = "duffel-clown" - item_state = "duffel-clown" - -obj/item/storage/backpack/duffel/blueshield - name = "blueshield duffelbag" - desc = "A robust duffelbag issued to Nanotrasen's finest." - icon_state = "duffel-blueshield" - item_state = "duffel-blueshield" - -//ERT backpacks. -/obj/item/storage/backpack/ert - name = "emergency response team backpack" - desc = "A spacious backpack with lots of pockets, used by members of the Nanotrasen Emergency Response Team." - icon_state = "ert_commander" - item_state = "backpack" - max_combined_w_class = 30 - resistance_flags = FIRE_PROOF - -//Commander -/obj/item/storage/backpack/ert/commander - name = "emergency response team commander backpack" - desc = "A spacious backpack with lots of pockets, worn by the commander of a Nanotrasen Emergency Response Team." - -//Security -/obj/item/storage/backpack/ert/security - name = "emergency response team security backpack" - desc = "A spacious backpack with lots of pockets, worn by security members of a Nanotrasen Emergency Response Team." - icon_state = "ert_security" - -//Engineering -/obj/item/storage/backpack/ert/engineer - name = "emergency response team engineer backpack" - desc = "A spacious backpack with lots of pockets, worn by engineering members of a Nanotrasen Emergency Response Team." - icon_state = "ert_engineering" - -//Medical -/obj/item/storage/backpack/ert/medical - name = "emergency response team medical backpack" - desc = "A spacious backpack with lots of pockets, worn by medical members of a Nanotrasen Emergency Response Team." - icon_state = "ert_medical" - -//Janitorial -/obj/item/storage/backpack/ert/janitor - name = "emergency response team janitor backpack" - desc = "A spacious backpack with lots of pockets, worn by janitorial members of a Nanotrasen Emergency Response Team." - icon_state = "ert_janitor" + +/* + * Backpack + */ + +/obj/item/storage/backpack + name = "backpack" + desc = "You wear this on your back and put items into it." + icon_state = "backpack" + item_state = "backpack" + lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + slot_flags = SLOT_BACK //ERROOOOO + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 21 + storage_slots = 21 + resistance_flags = NONE + max_integrity = 300 + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/back.dmi', + "Vox Armalis" = 'icons/mob/species/armalis/back.dmi', + "Grey" = 'icons/mob/species/grey/back.dmi' + ) //For Armalis anything but this and the nitrogen tank will use the default backpack icon. + +/obj/item/storage/backpack/attackby(obj/item/W as obj, mob/user as mob, params) + playsound(src.loc, "rustle", 50, 1, -5) + return ..() + +/obj/item/storage/backpack/examine(mob/user) + var/space_used = 0 + . = ..() + if(in_range(user, src)) + for(var/obj/item/I in contents) + space_used += I.w_class + if(!space_used) + . += " [src] is empty." + else if(space_used <= max_combined_w_class*0.6) + . += " [src] still has plenty of remaining space." + else if(space_used <= max_combined_w_class*0.8) + . += " [src] is beginning to run out of space." + else if(space_used < max_combined_w_class) + . += " [src] doesn't have much space left." + else + . += " [src] is full." + +/* + * Backpack Types + */ + +/obj/item/storage/backpack/holding + name = "Bag of Holding" + desc = "A backpack that opens into a localized pocket of Blue Space." + origin_tech = "bluespace=5;materials=4;engineering=4;plasmatech=5" + icon_state = "holdingpack" + item_state = "holdingpack" + max_w_class = WEIGHT_CLASS_HUGE + max_combined_w_class = 35 + resistance_flags = FIRE_PROOF + flags_2 = NO_MAT_REDEMPTION_2 + cant_hold = list(/obj/item/storage/backpack/holding) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 50) + +/obj/item/storage/backpack/holding/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/storage/backpack/holding)) + var/response = alert(user, "This creates a singularity, destroying you and much of the station. Are you SURE?","IMMINENT DEATH!", "No", "Yes") + if(response == "Yes") + user.visible_message("[user] grins as [user.p_they()] begin[user.p_s()] to put a Bag of Holding into a Bag of Holding!", "You begin to put the Bag of Holding into the Bag of Holding!") + if(do_after(user, 30, target=src)) + investigate_log("has become a singularity. Caused by [user.key]","singulo") + user.visible_message("[user] erupts in evil laughter as [user.p_they()] put[user.p_s()] the Bag of Holding into another Bag of Holding!", "You can't help but laugh wildly as you put the Bag of Holding into another Bag of Holding, complete darkness surrounding you."," You hear the sound of scientific evil brewing! ") + qdel(W) + var/obj/singularity/singulo = new /obj/singularity(get_turf(user)) + singulo.energy = 300 //To give it a small boost + message_admins("[key_name_admin(user)] detonated a bag of holding JMP)") + log_game("[key_name(user)] detonated a bag of holding") + qdel(src) + else + user.visible_message("After careful consideration, [user] has decided that putting a Bag of Holding inside another Bag of Holding would not yield the ideal outcome.","You come to the realization that this might not be the greatest idea.") + else + . = ..() + +/obj/item/storage/backpack/holding/singularity_act(current_size) + var/dist = max((current_size - 2),1) + explosion(src.loc,(dist),(dist*2),(dist*4)) + +/obj/item/storage/backpack/santabag + name = "Santa's Gift Bag" + desc = "Space Santa uses this to deliver toys to all the nice children in space on Christmas! Wow, it's pretty big!" + icon_state = "giftbag0" + item_state = "giftbag" + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 400 // can store a ton of shit! + +/obj/item/storage/backpack/cultpack + name = "trophy rack" + desc = "It's useful for both carrying extra gear and proudly declaring your insanity." + icon_state = "cultpack" + +/obj/item/storage/backpack/clown + name = "Giggles Von Honkerton" + desc = "It's a backpack made by Honk! Co." + icon_state = "clownpack" + item_state = "clownpack" + +/obj/item/storage/backpack/clown/syndie + +/obj/item/storage/backpack/clown/syndie/New() + ..() + new /obj/item/clothing/under/rank/clown(src) + new /obj/item/clothing/shoes/magboots/clown(src) + new /obj/item/clothing/mask/chameleon(src) + new /obj/item/radio/headset/headset_service(src) + new /obj/item/pda/clown(src) + new /obj/item/storage/box/survival(src) + new /obj/item/reagent_containers/food/snacks/grown/banana(src) + new /obj/item/stamp/clown(src) + new /obj/item/toy/crayon/rainbow(src) + new /obj/item/storage/fancy/crayons(src) + new /obj/item/reagent_containers/spray/waterflower(src) + new /obj/item/reagent_containers/food/drinks/bottle/bottleofbanana(src) + new /obj/item/instrument/bikehorn(src) + new /obj/item/bikehorn(src) + new /obj/item/dnainjector/comic(src) + +/obj/item/storage/backpack/mime + name = "Parcel Parceaux" + desc = "A silent backpack made for those silent workers. Silence Co." + icon_state = "mimepack" + item_state = "mimepack" + +/obj/item/storage/backpack/medic + name = "medical backpack" + desc = "It's a backpack especially designed for use in a sterile environment." + icon_state = "medicalpack" + item_state = "medicalpack" + +/obj/item/storage/backpack/security + name = "security backpack" + desc = "It's a very robust backpack." + icon_state = "securitypack" + item_state = "securitypack" + +/obj/item/storage/backpack/captain + name = "captain's backpack" + desc = "It's a special backpack made exclusively for Nanotrasen officers." + icon_state = "captainpack" + item_state = "captainpack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/industrial + name = "industrial backpack" + desc = "It's a tough backpack for the daily grind of station life." + icon_state = "engiepack" + item_state = "engiepack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/explorer + name = "explorer bag" + desc = "A robust backpack for stashing your loot." + icon_state = "explorerpack" + item_state = "explorerpack" + +/obj/item/storage/backpack/botany + name = "botany backpack" + desc = "It's a backpack made of all-natural fibers." + icon_state = "botpack" + item_state = "botpack" + +/obj/item/storage/backpack/chemistry + name = "chemistry backpack" + desc = "A backpack specially designed to repel stains and hazardous liquids." + icon_state = "chempack" + item_state = "chempack" + +/obj/item/storage/backpack/genetics + name = "genetics backpack" + desc = "A bag designed to be super tough, just in case someone hulks out on you." + icon_state = "genepack" + item_state = "genepack" + +/obj/item/storage/backpack/science + name = "science backpack" + desc = "A specially designed backpack. It's fire resistant and smells vaguely of plasma." + icon_state = "toxpack" + item_state = "toxpack" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/virology + name = "virology backpack" + desc = "A backpack made of hypo-allergenic fibers. It's designed to help prevent the spread of disease. Smells like monkey." + icon_state = "viropack" + item_state = "viropack" + +/obj/item/storage/backpack/blueshield + name = "blueshield backpack" + desc = "A robust backpack issued to Nanotrasen's finest." + icon_state = "blueshieldpack" + item_state = "blueshieldpack" + +/* + * Satchel Types + */ + +/obj/item/storage/backpack/satchel + name = "leather satchel" + desc = "It's a very fancy satchel made with fine leather." + icon_state = "satchel" + resistance_flags = FIRE_PROOF + var/strap_side_straight = FALSE + +/obj/item/storage/backpack/satchel/verb/switch_strap() + set name = "Switch Strap Side" + set category = "Object" + set src in usr + + if(usr.incapacitated()) + return + strap_side_straight = !strap_side_straight + icon_state = strap_side_straight ? "satchel-flipped" : "satchel" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_back() + + + +/obj/item/storage/backpack/satcheldeluxe + name = "leather satchel" + desc = "An NT Deluxe satchel, with the finest quality leather and the company logo in a thin gold stitch" + icon_state = "nt_deluxe" + +/obj/item/storage/backpack/satchel/withwallet/New() + ..() + new /obj/item/storage/wallet/random(src) + +/obj/item/storage/backpack/satchel_norm + name = "satchel" + desc = "A deluxe NT Satchel, made of the highest quality leather." + icon_state = "satchel-norm" + +/obj/item/storage/backpack/satchel_eng + name = "industrial satchel" + desc = "A tough satchel with extra pockets." + icon_state = "satchel-eng" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel/explorer + name = "explorer satchel" + desc = "A robust satchel for stashing your loot." + icon_state = "satchel-explorer" + item_state = "securitypack" + +/obj/item/storage/backpack/satchel_med + name = "medical satchel" + desc = "A sterile satchel used in medical departments." + icon_state = "satchel-med" + +/obj/item/storage/backpack/satchel_vir + name = "virologist satchel" + desc = "A sterile satchel with virologist colours." + icon_state = "satchel-vir" + +/obj/item/storage/backpack/satchel_chem + name = "chemist satchel" + desc = "A sterile satchel with chemist colours." + icon_state = "satchel-chem" + +/obj/item/storage/backpack/satchel_gen + name = "geneticist satchel" + desc = "A sterile satchel with geneticist colours." + icon_state = "satchel-gen" + +/obj/item/storage/backpack/satchel_tox + name = "scientist satchel" + desc = "Useful for holding research materials." + icon_state = "satchel-tox" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel_sec + name = "security satchel" + desc = "A robust satchel for security related needs." + icon_state = "satchel-sec" + +/obj/item/storage/backpack/satchel_hyd + name = "hydroponics satchel" + desc = "A green satchel for plant related work." + icon_state = "satchel-hyd" + +/obj/item/storage/backpack/satchel_cap + name = "captain's satchel" + desc = "An exclusive satchel for Nanotrasen officers." + icon_state = "satchel-cap" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/satchel_blueshield + name = "blueshield satchel" + desc = "A robust satchel issued to Nanotrasen's finest." + icon_state = "satchel-blueshield" + +/obj/item/storage/backpack/satchel_flat + name = "smuggler's satchel" + desc = "A very slim satchel that can easily fit into tight spaces." + icon_state = "satchel-flat" + w_class = WEIGHT_CLASS_NORMAL //Can fit in backpacks itself. + max_combined_w_class = 15 + level = 1 + cant_hold = list(/obj/item/storage/backpack/satchel_flat) //muh recursive backpacks + +/obj/item/storage/backpack/satchel_flat/hide(var/intact) + if(intact) + invisibility = 101 + anchored = 1 //otherwise you can start pulling, cover it, and drag around an invisible backpack. + icon_state = "[initial(icon_state)]2" + else + invisibility = initial(invisibility) + anchored = 0 + icon_state = initial(icon_state) + +/obj/item/storage/backpack/satchel_flat/New() + ..() + new /obj/item/stack/tile/plasteel(src) + new /obj/item/crowbar(src) + +/* + * Duffelbags - My thanks to MrSnapWalk for the original icon and Neinhaus for the job variants - Dave. + */ + +/obj/item/storage/backpack/duffel + name = "duffelbag" + desc = "A large grey duffelbag designed to hold more items than a regular bag." + icon_state = "duffel" + item_state = "duffel" + max_combined_w_class = 30 + slowdown = 1 + +/obj/item/storage/backpack/duffel/syndie + name = "suspicious looking duffelbag" + desc = "A large duffelbag for holding extra tactical supplies." + icon_state = "duffel-syndie" + item_state = "duffel-syndimed" + origin_tech = "syndicate=1" + silent = 1 + slowdown = 0 + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffel/syndie/med + name = "suspicious duffelbag" + desc = "A black and red duffelbag with a red and white cross sewn onto it." + icon_state = "duffel-syndimed" + item_state = "duffel-syndimed" + +/obj/item/storage/backpack/duffel/syndie/ammo + name = "suspicious duffelbag" + desc = "A black and red duffelbag with a patch depicting shotgun shells sewn onto it." + icon_state = "duffel-syndiammo" + item_state = "duffel-syndiammo" + +/obj/item/storage/backpack/duffel/syndie/ammo/shotgun + desc = "A large duffelbag, packed to the brim with Bulldog shotgun ammo." + +/obj/item/storage/backpack/duffel/syndie/ammo/shotgun/New() + ..() + for(var/i in 1 to 6) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/ammo_box/magazine/m12g/buckshot(src) + new /obj/item/ammo_box/magazine/m12g/buckshot(src) + new /obj/item/ammo_box/magazine/m12g/dragon(src) + +/obj/item/storage/backpack/duffel/mining_conscript/ + name = "mining conscription kit" + desc = "A kit containing everything a crewmember needs to support a shaft miner in the field." + +/obj/item/storage/backpack/duffel/mining_conscript/New() + ..() + new /obj/item/pickaxe(src) + new /obj/item/clothing/glasses/meson(src) + new /obj/item/t_scanner/adv_mining_scanner/lesser(src) + new /obj/item/storage/bag/ore(src) + new /obj/item/clothing/under/rank/miner/lavaland(src) + new /obj/item/encryptionkey/headset_cargo(src) + new /obj/item/clothing/mask/gas/explorer(src) + new /obj/item/gun/energy/kinetic_accelerator(src) + new /obj/item/kitchen/knife/combat/survival(src) + new /obj/item/flashlight/seclite(src) + new /obj/item/clothing/suit/hooded/explorer(src) + + +/obj/item/storage/backpack/duffel/syndie/ammo/smg + desc = "A large duffel bag, packed to the brim with C-20r magazines." + +/obj/item/storage/backpack/duffel/syndie/ammo/smg/New() + ..() + for(var/i in 1 to 10) + new /obj/item/ammo_box/magazine/smgm45(src) + +/obj/item/storage/backpack/duffel/syndie/c20rbundle + desc = "A large duffel bag containing a C-20r, some magazines, and a cheap looking suppressor." + +/obj/item/storage/backpack/duffel/syndie/c20rbundle/New() + ..() + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/ammo_box/magazine/smgm45(src) + new /obj/item/gun/projectile/automatic/c20r(src) + new /obj/item/suppressor/specialoffer(src) + +/obj/item/storage/backpack/duffel/syndie/bulldogbundle + desc = "A large duffel bag containing a Bulldog, some drums, and a pair of thermal imaging glasses." + +/obj/item/storage/backpack/duffel/syndie/bulldogbundle/New() + ..() + new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/ammo_box/magazine/m12g(src) + new /obj/item/clothing/glasses/chameleon/thermal(src) + +/obj/item/storage/backpack/duffel/syndie/med/medicalbundle + desc = "A large duffel bag containing a tactical medkit, a medical beam gun and a pair of syndicate magboots." + +/obj/item/storage/backpack/duffel/syndie/med/medicalbundle/New() + ..() + new /obj/item/storage/firstaid/tactical(src) + new /obj/item/clothing/shoes/magboots/syndie(src) + new /obj/item/gun/medbeam(src) + +/obj/item/storage/backpack/duffel/syndie/c4/New() + ..() + for(var/i in 1 to 10) + new /obj/item/grenade/plastic/c4(src) + +/obj/item/storage/backpack/duffel/syndie/x4/New() + ..() + for(var/i in 1 to 3) + new /obj/item/grenade/plastic/x4(src) + +/obj/item/storage/backpack/duffel/syndie/surgery + name = "surgery duffelbag" + desc = "A suspicious looking duffelbag for holding surgery tools." + icon_state = "duffel-syndimed" + item_state = "duffel-syndimed" + +/obj/item/storage/backpack/duffel/syndie/surgery/New() + ..() + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/retractor(src) + new /obj/item/circular_saw(src) + new /obj/item/surgicaldrill(src) + new /obj/item/cautery(src) + new /obj/item/bonegel(src) + new /obj/item/bonesetter(src) + new /obj/item/FixOVein(src) + new /obj/item/clothing/suit/straight_jacket(src) + new /obj/item/clothing/mask/muzzle(src) + +/obj/item/storage/backpack/duffel/syndie/surgery_fake //for maint spawns + name = "surgery duffelbag" + desc = "A suspicious looking duffelbag for holding surgery tools." + icon_state = "duffel-syndimed" + item_state = "duffel-syndimed" + +/obj/item/storage/backpack/duffel/syndie/surgery_fake/New() + ..() + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/retractor(src) + new /obj/item/cautery(src) + new /obj/item/bonegel(src) + new /obj/item/bonesetter(src) + new /obj/item/FixOVein(src) + if(prob(50)) + new /obj/item/circular_saw(src) + new /obj/item/surgicaldrill(src) + +/obj/item/storage/backpack/duffel/captain + name = "captain's duffelbag" + desc = "A duffelbag designed to hold large quantities of condoms." + icon_state = "duffel-captain" + item_state = "duffel-captain" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffel/security + name = "security duffelbag" + desc = "A duffelbag built with robust fabric!" + icon_state = "duffel-security" + item_state = "duffel-security" + +/obj/item/storage/backpack/duffel/virology + name = "virology duffelbag" + desc = "A white duffelbag designed to contain biohazards." + icon_state = "duffel-viro" + item_state = "duffel-viro" + +/obj/item/storage/backpack/duffel/science + name = "scientist duffelbag" + desc = "A duffelbag designed to hold the secrets of space." + icon_state = "duffel-toxins" + item_state = "duffel-toxins" + +/obj/item/storage/backpack/duffel/genetics + name = "geneticist duffelbag" + desc = "A duffelbag designed to hold gibbering monkies." + icon_state = "duffel-gene" + item_state = "duffel-gene" + +/obj/item/storage/backpack/duffel/chemistry + name = "chemist duffelbag" + desc = "A duffelbag designed to hold corrosive substances." + icon_state = "duffel-chemistry" + item_state = "duffel-chemistry" + +/obj/item/storage/backpack/duffel/medical + name = "medical duffelbag" + desc = "A duffelbag designed to hold medicine." + icon_state = "duffel-med" + item_state = "duffel-med" + +/obj/item/storage/backpack/duffel/engineering + name = "industrial duffelbag" + desc = "A duffelbag designed to hold tools." + icon_state = "duffel-eng" + item_state = "duffel-eng" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffel/atmos + name = "atmospherics duffelbag" + desc = "A duffelbag designed to hold tools. This one is specially designed for atmospherics." + icon_state = "duffel-atmos" + item_state = "duffel-atmos" + resistance_flags = FIRE_PROOF + +/obj/item/storage/backpack/duffel/hydro + name = "hydroponics duffelbag" + desc = "A duffelbag designed to hold seeds and fauna." + icon_state = "duffel-hydro" + item_state = "duffel-hydro" + +/obj/item/storage/backpack/duffel/clown + name = "smiles von wiggleton" + desc = "A duffelbag designed to hold bananas and bike horns." + icon_state = "duffel-clown" + item_state = "duffel-clown" + +obj/item/storage/backpack/duffel/blueshield + name = "blueshield duffelbag" + desc = "A robust duffelbag issued to Nanotrasen's finest." + icon_state = "duffel-blueshield" + item_state = "duffel-blueshield" + +//ERT backpacks. +/obj/item/storage/backpack/ert + name = "emergency response team backpack" + desc = "A spacious backpack with lots of pockets, used by members of the Nanotrasen Emergency Response Team." + icon_state = "ert_commander" + item_state = "backpack" + max_combined_w_class = 30 + resistance_flags = FIRE_PROOF + +//Commander +/obj/item/storage/backpack/ert/commander + name = "emergency response team commander backpack" + desc = "A spacious backpack with lots of pockets, worn by the commander of a Nanotrasen Emergency Response Team." + +//Security +/obj/item/storage/backpack/ert/security + name = "emergency response team security backpack" + desc = "A spacious backpack with lots of pockets, worn by security members of a Nanotrasen Emergency Response Team." + icon_state = "ert_security" + +//Engineering +/obj/item/storage/backpack/ert/engineer + name = "emergency response team engineer backpack" + desc = "A spacious backpack with lots of pockets, worn by engineering members of a Nanotrasen Emergency Response Team." + icon_state = "ert_engineering" + +//Medical +/obj/item/storage/backpack/ert/medical + name = "emergency response team medical backpack" + desc = "A spacious backpack with lots of pockets, worn by medical members of a Nanotrasen Emergency Response Team." + icon_state = "ert_medical" + +//Janitorial +/obj/item/storage/backpack/ert/janitor + name = "emergency response team janitor backpack" + desc = "A spacious backpack with lots of pockets, worn by janitorial members of a Nanotrasen Emergency Response Team." + icon_state = "ert_janitor" diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm index a3808bf5d80..66288713775 100644 --- a/code/game/objects/items/weapons/storage/bags.dm +++ b/code/game/objects/items/weapons/storage/bags.dm @@ -1,503 +1,503 @@ -/* - * These absorb the functionality of the plant bag, ore satchel, etc. - * They use the use_to_pickup, quick_gather, and quick_empty functions - * that were already defined in weapon/storage, but which had been - * re-implemented in other classes. - * - * Contains: - * Trash Bag - * Mining Satchel - * Plant Bag - * Sheet Snatcher - * Book Bag - * Tray - * - * -Sayu - */ - -// Generic non-item -/obj/item/storage/bag - allow_quick_gather = 1 - allow_quick_empty = 1 - display_contents_with_number = 1 // should work fine now - use_to_pickup = 1 - slot_flags = SLOT_BELT - -// ----------------------------- -// Trash bag -// ----------------------------- -/obj/item/storage/bag/trash - name = "trash bag" - desc = "It's the heavy-duty black polymer kind. Time to take out the trash!" - icon = 'icons/obj/janitor.dmi' - icon_state = "trashbag" - item_state = "trashbag" - - w_class = WEIGHT_CLASS_TINY - max_w_class = WEIGHT_CLASS_SMALL - storage_slots = 30 - can_hold = list() // any - cant_hold = list(/obj/item/disk/nuclear) - -/obj/item/storage/bag/trash/suicide_act(mob/user) - user.visible_message("[user] puts the [name] over [user.p_their()] head and starts chomping at the insides! Disgusting!") - playsound(loc, 'sound/items/eatfood.ogg', 50, 1, -1) - return TOXLOSS - -/obj/item/storage/bag/trash/update_icon() - if(contents.len == 0) - w_class = WEIGHT_CLASS_TINY - icon_state = "[initial(icon_state)]" - else if(contents.len < 12) - w_class = WEIGHT_CLASS_BULKY - icon_state = "[initial(icon_state)]1" - else if(contents.len < 21) - w_class = WEIGHT_CLASS_BULKY - icon_state = "[initial(icon_state)]2" - else - w_class = WEIGHT_CLASS_BULKY - icon_state = "[initial(icon_state)]3" - -/obj/item/storage/bag/trash/cyborg - -/obj/item/storage/bag/trash/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) - J.put_in_cart(src, user) - J.mybag=src - J.update_icon() - -/obj/item/storage/bag/trash/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) - return - -/obj/item/storage/bag/trash/bluespace - name = "trash bag of holding" - desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage." - icon_state = "bluetrashbag" - origin_tech = "materials=4;bluespace=4;engineering=4;plasmatech=3" - max_combined_w_class = 60 - storage_slots = 60 - flags_2 = NO_MAT_REDEMPTION_2 - -// ----------------------------- -// Plastic Bag -// ----------------------------- - -/obj/item/storage/bag/plasticbag - name = "plastic bag" - desc = "It's a very flimsy, very noisy alternative to a bag." - icon = 'icons/obj/trash.dmi' - icon_state = "plasticbag" - item_state = "plasticbag" - slot_flags = SLOT_HEAD|SLOT_BELT - throwforce = 0 - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_SMALL - storage_slots = 7 - display_contents_with_number = 0 //or else this will lead to stupid behavior. - can_hold = list() // any - cant_hold = list(/obj/item/disk/nuclear) - -/obj/item/storage/bag/plasticbag/mob_can_equip(M as mob, slot) - - if(slot==slot_head && contents.len) - to_chat(M, "You need to empty the bag first!") - return 0 - return ..() - - -/obj/item/storage/bag/plasticbag/equipped(var/mob/user, var/slot) - if(slot==slot_head) - storage_slots = 0 - START_PROCESSING(SSobj, src) - return - -/obj/item/storage/bag/plasticbag/process() - if(is_equipped()) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.get_item_by_slot(slot_head) == src) - if(H.internal) - return - H.AdjustLoseBreath(1) - else - storage_slots = 7 - STOP_PROCESSING(SSobj, src) - return - -// ----------------------------- -// Mining Satchel -// ----------------------------- - -/obj/item/storage/bag/ore - name = "mining satchel" - desc = "This little bugger can be used to store and transport ores." - icon = 'icons/obj/mining.dmi' - icon_state = "satchel" - origin_tech = "engineering=2" - slot_flags = SLOT_BELT | SLOT_POCKET - w_class = WEIGHT_CLASS_NORMAL - storage_slots = 10 - max_combined_w_class = 200 //Doesn't matter what this is, so long as it's more or equal to storage_slots * ore.w_class - max_w_class = WEIGHT_CLASS_NORMAL - can_hold = list(/obj/item/stack/ore) - -/obj/item/storage/bag/ore/cyborg - name = "cyborg mining satchel" - flags = NODROP - -/obj/item/storage/bag/ore/holding //miners, your messiah has arrived - name = "mining satchel of holding" - desc = "A revolution in convenience, this satchel allows for infinite ore storage. It's been outfitted with anti-malfunction safety measures." - storage_slots = INFINITY - max_combined_w_class = INFINITY - origin_tech = "bluespace=4;materials=3;engineering=3" - icon_state = "satchel_bspace" - -/obj/item/storage/bag/ore/holding/cyborg - name = "cyborg mining satchel of holding" - flags = NODROP - -// ----------------------------- -// Plant bag -// ----------------------------- - -/obj/item/storage/bag/plants - name = "plant bag" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "plantbag" - storage_slots = 100 //the number of plant pieces it can carry. - max_combined_w_class = 100 //Doesn't matter what this is, so long as it's more or equal to storage_slots * plants.w_class - max_w_class = WEIGHT_CLASS_NORMAL - w_class = WEIGHT_CLASS_TINY - can_hold = list(/obj/item/reagent_containers/food/snacks/grown,/obj/item/seeds,/obj/item/grown,/obj/item/reagent_containers/food/snacks/grown/ash_flora) - resistance_flags = FLAMMABLE - -/obj/item/storage/bag/plants/portaseeder - name = "portable seed extractor" - desc = "For the enterprising botanist on the go. Less efficient than the stationary model, it creates one seed per plant." - icon_state = "portaseeder" - origin_tech = "biotech=3;engineering=2" - -/obj/item/storage/bag/plants/portaseeder/verb/dissolve_contents() - set name = "Activate Seed Extraction" - set category = "Object" - set desc = "Activate to convert your plants into plantable seeds." - - if(usr.incapacitated()) - return - for(var/obj/item/O in contents) - seedify(O, 1) - for(var/mob/M in range(1)) - if(M.s_active == src) - close(M) - - -// ----------------------------- -// Sheet Snatcher -// ----------------------------- -// Because it stacks stacks, this doesn't operate normally. -// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu - -/obj/item/storage/bag/sheetsnatcher - icon = 'icons/obj/mining.dmi' - icon_state = "sheetsnatcher" - name = "Sheet Snatcher" - desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet." - - var/capacity = 300; //the number of sheets it can carry. - w_class = WEIGHT_CLASS_NORMAL - - allow_quick_empty = 1 // this function is superceded -/obj/item/storage/bag/sheetsnatcher/New() - ..() - //verbs -= /obj/item/storage/verb/quick_empty - //verbs += /obj/item/storage/bag/sheetsnatcher/quick_empty - -/obj/item/storage/bag/sheetsnatcher/can_be_inserted(obj/item/W as obj, stop_messages = 0) - if(!istype(W,/obj/item/stack/sheet) || istype(W,/obj/item/stack/sheet/mineral/sandstone) || istype(W,/obj/item/stack/sheet/wood)) - if(!stop_messages) - to_chat(usr, "The snatcher does not accept [W].") - return 0 //I don't care, but the existing code rejects them for not being "sheets" *shrug* -Sayu - var/current = 0 - for(var/obj/item/stack/sheet/S in contents) - current += S.amount - if(capacity == current)//If it's full, you're done - if(!stop_messages) - to_chat(usr, "The snatcher is full.") - return 0 - return 1 - - -// Modified handle_item_insertion. Would prefer not to, but... -/obj/item/storage/bag/sheetsnatcher/handle_item_insertion(obj/item/W as obj, prevent_warning = 0) - var/obj/item/stack/sheet/S = W - if(!istype(S)) return 0 - - var/amount - var/inserted = 0 - var/current = 0 - for(var/obj/item/stack/sheet/S2 in contents) - current += S2.amount - if(capacity < current + S.amount)//If the stack will fill it up - amount = capacity - current - else - amount = S.amount - - for(var/obj/item/stack/sheet/sheet in contents) - if(S.type == sheet.type) // we are violating the amount limitation because these are not sane objects - sheet.amount += amount // they should only be removed through procs in this file, which split them up. - S.amount -= amount - inserted = 1 - break - - if(!inserted || !S.amount) - usr.unEquip(S) - usr.update_icons() //update our overlays - if(usr.client && usr.s_active != src) - usr.client.screen -= S - S.dropped(usr) - if(!S.amount) - qdel(S) - else - S.loc = src - - orient2hud(usr) - if(usr.s_active) - usr.s_active.show_to(usr) - update_icon() - return 1 - - -// Sets up numbered display to show the stack size of each stored mineral -// NOTE: numbered display is turned off currently because it's broken -/obj/item/storage/bag/sheetsnatcher/orient2hud(mob/user as mob) - var/adjusted_contents = contents.len - - //Numbered contents display - var/list/datum/numbered_display/numbered_contents - if(display_contents_with_number) - numbered_contents = list() - adjusted_contents = 0 - for(var/obj/item/stack/sheet/I in contents) - adjusted_contents++ - var/datum/numbered_display/D = new/datum/numbered_display(I) - D.number = I.amount - numbered_contents.Add( D ) - - var/row_num = 0 - var/col_count = min(7,storage_slots) -1 - if(adjusted_contents > 7) - row_num = round((adjusted_contents-1) / 7) // 7 is the maximum allowed width. - src.standard_orient_objs(row_num, col_count, numbered_contents) - return - - -// Modified quick_empty verb drops appropriate sized stacks -/obj/item/storage/bag/sheetsnatcher/quick_empty() - var/location = get_turf(src) - for(var/obj/item/stack/sheet/S in contents) - while(S.amount) - var/obj/item/stack/sheet/N = new S.type(location) - var/stacksize = min(S.amount,N.max_amount) - N.amount = stacksize - S.amount -= stacksize - if(!S.amount) - qdel(S) // todo: there's probably something missing here - orient2hud(usr) - if(usr.s_active) - usr.s_active.show_to(usr) - update_icon() - -// Instead of removing -/obj/item/storage/bag/sheetsnatcher/remove_from_storage(obj/item/W as obj, atom/new_location) - var/obj/item/stack/sheet/S = W - if(!istype(S)) return 0 - - //I would prefer to drop a new stack, but the item/attack_hand code - // that calls this can't recieve a different object than you clicked on. - //Therefore, make a new stack internally that has the remainder. - // -Sayu - - if(S.amount > S.max_amount) - var/obj/item/stack/sheet/temp = new S.type(src) - temp.amount = S.amount - S.max_amount - S.amount = S.max_amount - - return ..(S,new_location) - -// ----------------------------- -// Sheet Snatcher (Cyborg) -// ----------------------------- - -/obj/item/storage/bag/sheetsnatcher/borg - name = "Sheet Snatcher 9000" - desc = "" - capacity = 500//Borgs get more because >specialization - - -// ----------------------------- -// Cash Bag -// ----------------------------- - -/obj/item/storage/bag/cash - icon = 'icons/obj/storage.dmi' - icon_state = "cashbag" - name = "Cash bag" - desc = "A bag for carrying lots of cash. It's got a big dollar sign printed on the front." - storage_slots = 50; //the number of cash pieces it can carry. - max_combined_w_class = 200 //Doesn't matter what this is, so long as it's more or equal to storage_slots * cash.w_class - max_w_class = WEIGHT_CLASS_NORMAL - w_class = WEIGHT_CLASS_TINY - can_hold = list(/obj/item/coin,/obj/item/stack/spacecash) - -// ----------------------------- -// Book bag -// ----------------------------- - -/obj/item/storage/bag/books - name = "book bag" - desc = "A bag for books." - icon = 'icons/obj/library.dmi' - icon_state = "bookbag" - display_contents_with_number = 0 //This would look really stupid otherwise - storage_slots = 7 - max_combined_w_class = 21 - max_w_class = WEIGHT_CLASS_NORMAL - w_class = WEIGHT_CLASS_BULKY //Bigger than a book because physics - can_hold = list(/obj/item/book, /obj/item/storage/bible, /obj/item/tome, /obj/item/spellbook) - resistance_flags = FLAMMABLE - -/* - * Trays - Agouri - */ -/obj/item/storage/bag/tray - name = "tray" - icon = 'icons/obj/food/containers.dmi' - icon_state = "tray" - desc = "A metal tray to lay food on." - force = 5 - throwforce = 10.0 - throw_speed = 3 - throw_range = 5 - w_class = WEIGHT_CLASS_BULKY - flags = CONDUCT - materials = list(MAT_METAL=3000) - -/obj/item/storage/bag/tray/attack(mob/living/M as mob, mob/living/user as mob) - ..() - // Drop all the things. All of them. - var/list/obj/item/oldContents = contents.Copy() - quick_empty() - - // Make each item scatter a bit - for(var/obj/item/I in oldContents) - spawn() - for(var/i = 1, i <= rand(1,2), i++) - if(I) - step(I, pick(NORTH,SOUTH,EAST,WEST)) - sleep(rand(2,4)) - - if(prob(50)) - playsound(M, 'sound/items/trayhit1.ogg', 50, 1) - else - playsound(M, 'sound/items/trayhit2.ogg', 50, 1) - - if(ishuman(M)) - if(prob(10)) - M.Weaken(2) - -/obj/item/storage/bag/tray/proc/rebuild_overlays() - overlays.Cut() - for(var/obj/item/I in contents) - overlays += image("icon" = I.icon, "icon_state" = I.icon_state, "layer" = -1) - -/obj/item/storage/bag/tray/remove_from_storage(obj/item/W as obj, atom/new_location) - ..() - rebuild_overlays() - -/obj/item/storage/bag/tray/handle_item_insertion(obj/item/I, prevent_warning = 0) - overlays += image("icon" = I.icon, "icon_state" = I.icon_state, "layer" = -1) - ..() - -/obj/item/storage/bag/tray/cyborg - -/obj/item/storage/bag/tray/cyborg/afterattack(atom/target, mob/user as mob) - if( isturf(target) || istype(target,/obj/structure/table) ) - var foundtable = istype(target,/obj/structure/table/) - if( !foundtable ) //it must be a turf! - for(var/obj/structure/table/T in target) - foundtable = 1 - break - - var turf/dropspot - if( !foundtable ) // don't unload things onto walls or other silly places. - dropspot = user.loc - else if( isturf(target) ) // they clicked on a turf with a table in it - dropspot = target - else // they clicked on a table - dropspot = target.loc - - overlays = null - - var droppedSomething = 0 - - for(var/obj/item/I in contents) - I.loc = dropspot - contents.Remove(I) - droppedSomething = 1 - if(!foundtable && isturf(dropspot)) - // if no table, presume that the person just shittily dropped the tray on the ground and made a mess everywhere! - spawn() - for(var/i = 1, i <= rand(1,2), i++) - if(I) - step(I, pick(NORTH,SOUTH,EAST,WEST)) - sleep(rand(2,4)) - if( droppedSomething ) - if( foundtable ) - user.visible_message("[user] unloads [user.p_their()] service tray.") - else - user.visible_message("[user] drops all the items on [user.p_their()] tray.") - - return ..() - - -/obj/item/storage/bag/tray/cookies_tray - var/cookie = /obj/item/reagent_containers/food/snacks/cookie - -/obj/item/storage/bag/tray/cookies_tray/New() /// By Azule Utama, thank you a lot! - ..() - for(var/i in 1 to 6) - var/obj/item/C = new cookie(src) - handle_item_insertion(C) // Done this way so the tray actually has the cookies visible when spawned - rebuild_overlays() - -/obj/item/storage/bag/tray/cookies_tray/sugarcookie - cookie = /obj/item/reagent_containers/food/snacks/sugarcookie - -/* - * Chemistry bag - */ - -/obj/item/storage/bag/chemistry - name = "chemistry bag" - icon = 'icons/obj/chemical.dmi' - icon_state = "bag" - desc = "A bag for storing pills, patches, and bottles." - storage_slots = 50 - max_combined_w_class = 200 - w_class = WEIGHT_CLASS_TINY - can_hold = list(/obj/item/reagent_containers/food/pill,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/glass/bottle) - resistance_flags = FLAMMABLE -/* - * Biowaste bag (mostly for xenobiologists) - */ - -/obj/item/storage/bag/bio - name = "bio bag" - icon = 'icons/obj/chemical.dmi' - icon_state = "biobag" - desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." - storage_slots = 25 - max_combined_w_class = 200 - w_class = WEIGHT_CLASS_TINY - can_hold = list(/obj/item/slime_extract,/obj/item/reagent_containers/food/snacks/monkeycube,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/iv_bag,/obj/item/reagent_containers/hypospray/autoinjector) - resistance_flags = FLAMMABLE +/* + * These absorb the functionality of the plant bag, ore satchel, etc. + * They use the use_to_pickup, quick_gather, and quick_empty functions + * that were already defined in weapon/storage, but which had been + * re-implemented in other classes. + * + * Contains: + * Trash Bag + * Mining Satchel + * Plant Bag + * Sheet Snatcher + * Book Bag + * Tray + * + * -Sayu + */ + +// Generic non-item +/obj/item/storage/bag + allow_quick_gather = 1 + allow_quick_empty = 1 + display_contents_with_number = 1 // should work fine now + use_to_pickup = 1 + slot_flags = SLOT_BELT + +// ----------------------------- +// Trash bag +// ----------------------------- +/obj/item/storage/bag/trash + name = "trash bag" + desc = "It's the heavy-duty black polymer kind. Time to take out the trash!" + icon = 'icons/obj/janitor.dmi' + icon_state = "trashbag" + item_state = "trashbag" + + w_class = WEIGHT_CLASS_TINY + max_w_class = WEIGHT_CLASS_SMALL + storage_slots = 30 + can_hold = list() // any + cant_hold = list(/obj/item/disk/nuclear) + +/obj/item/storage/bag/trash/suicide_act(mob/user) + user.visible_message("[user] puts the [name] over [user.p_their()] head and starts chomping at the insides! Disgusting!") + playsound(loc, 'sound/items/eatfood.ogg', 50, 1, -1) + return TOXLOSS + +/obj/item/storage/bag/trash/update_icon() + if(contents.len == 0) + w_class = WEIGHT_CLASS_TINY + icon_state = "[initial(icon_state)]" + else if(contents.len < 12) + w_class = WEIGHT_CLASS_BULKY + icon_state = "[initial(icon_state)]1" + else if(contents.len < 21) + w_class = WEIGHT_CLASS_BULKY + icon_state = "[initial(icon_state)]2" + else + w_class = WEIGHT_CLASS_BULKY + icon_state = "[initial(icon_state)]3" + +/obj/item/storage/bag/trash/cyborg + +/obj/item/storage/bag/trash/proc/janicart_insert(mob/user, obj/structure/janitorialcart/J) + J.put_in_cart(src, user) + J.mybag=src + J.update_icon() + +/obj/item/storage/bag/trash/cyborg/janicart_insert(mob/user, obj/structure/janitorialcart/J) + return + +/obj/item/storage/bag/trash/bluespace + name = "trash bag of holding" + desc = "The latest and greatest in custodial convenience, a trashbag that is capable of holding vast quantities of garbage." + icon_state = "bluetrashbag" + origin_tech = "materials=4;bluespace=4;engineering=4;plasmatech=3" + max_combined_w_class = 60 + storage_slots = 60 + flags_2 = NO_MAT_REDEMPTION_2 + +// ----------------------------- +// Plastic Bag +// ----------------------------- + +/obj/item/storage/bag/plasticbag + name = "plastic bag" + desc = "It's a very flimsy, very noisy alternative to a bag." + icon = 'icons/obj/trash.dmi' + icon_state = "plasticbag" + item_state = "plasticbag" + slot_flags = SLOT_HEAD|SLOT_BELT + throwforce = 0 + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_SMALL + storage_slots = 7 + display_contents_with_number = 0 //or else this will lead to stupid behavior. + can_hold = list() // any + cant_hold = list(/obj/item/disk/nuclear) + +/obj/item/storage/bag/plasticbag/mob_can_equip(M as mob, slot) + + if(slot==slot_head && contents.len) + to_chat(M, "You need to empty the bag first!") + return 0 + return ..() + + +/obj/item/storage/bag/plasticbag/equipped(var/mob/user, var/slot) + if(slot==slot_head) + storage_slots = 0 + START_PROCESSING(SSobj, src) + return + +/obj/item/storage/bag/plasticbag/process() + if(is_equipped()) + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.get_item_by_slot(slot_head) == src) + if(H.internal) + return + H.AdjustLoseBreath(1) + else + storage_slots = 7 + STOP_PROCESSING(SSobj, src) + return + +// ----------------------------- +// Mining Satchel +// ----------------------------- + +/obj/item/storage/bag/ore + name = "mining satchel" + desc = "This little bugger can be used to store and transport ores." + icon = 'icons/obj/mining.dmi' + icon_state = "satchel" + origin_tech = "engineering=2" + slot_flags = SLOT_BELT | SLOT_POCKET + w_class = WEIGHT_CLASS_NORMAL + storage_slots = 10 + max_combined_w_class = 200 //Doesn't matter what this is, so long as it's more or equal to storage_slots * ore.w_class + max_w_class = WEIGHT_CLASS_NORMAL + can_hold = list(/obj/item/stack/ore) + +/obj/item/storage/bag/ore/cyborg + name = "cyborg mining satchel" + flags = NODROP + +/obj/item/storage/bag/ore/holding //miners, your messiah has arrived + name = "mining satchel of holding" + desc = "A revolution in convenience, this satchel allows for infinite ore storage. It's been outfitted with anti-malfunction safety measures." + storage_slots = INFINITY + max_combined_w_class = INFINITY + origin_tech = "bluespace=4;materials=3;engineering=3" + icon_state = "satchel_bspace" + +/obj/item/storage/bag/ore/holding/cyborg + name = "cyborg mining satchel of holding" + flags = NODROP + +// ----------------------------- +// Plant bag +// ----------------------------- + +/obj/item/storage/bag/plants + name = "plant bag" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "plantbag" + storage_slots = 100 //the number of plant pieces it can carry. + max_combined_w_class = 100 //Doesn't matter what this is, so long as it's more or equal to storage_slots * plants.w_class + max_w_class = WEIGHT_CLASS_NORMAL + w_class = WEIGHT_CLASS_TINY + can_hold = list(/obj/item/reagent_containers/food/snacks/grown,/obj/item/seeds,/obj/item/grown,/obj/item/reagent_containers/food/snacks/grown/ash_flora) + resistance_flags = FLAMMABLE + +/obj/item/storage/bag/plants/portaseeder + name = "portable seed extractor" + desc = "For the enterprising botanist on the go. Less efficient than the stationary model, it creates one seed per plant." + icon_state = "portaseeder" + origin_tech = "biotech=3;engineering=2" + +/obj/item/storage/bag/plants/portaseeder/verb/dissolve_contents() + set name = "Activate Seed Extraction" + set category = "Object" + set desc = "Activate to convert your plants into plantable seeds." + + if(usr.incapacitated()) + return + for(var/obj/item/O in contents) + seedify(O, 1) + for(var/mob/M in range(1)) + if(M.s_active == src) + close(M) + + +// ----------------------------- +// Sheet Snatcher +// ----------------------------- +// Because it stacks stacks, this doesn't operate normally. +// However, making it a storage/bag allows us to reuse existing code in some places. -Sayu + +/obj/item/storage/bag/sheetsnatcher + icon = 'icons/obj/mining.dmi' + icon_state = "sheetsnatcher" + name = "Sheet Snatcher" + desc = "A patented Nanotrasen storage system designed for any kind of mineral sheet." + + var/capacity = 300; //the number of sheets it can carry. + w_class = WEIGHT_CLASS_NORMAL + + allow_quick_empty = 1 // this function is superceded +/obj/item/storage/bag/sheetsnatcher/New() + ..() + //verbs -= /obj/item/storage/verb/quick_empty + //verbs += /obj/item/storage/bag/sheetsnatcher/quick_empty + +/obj/item/storage/bag/sheetsnatcher/can_be_inserted(obj/item/W as obj, stop_messages = 0) + if(!istype(W,/obj/item/stack/sheet) || istype(W,/obj/item/stack/sheet/mineral/sandstone) || istype(W,/obj/item/stack/sheet/wood)) + if(!stop_messages) + to_chat(usr, "The snatcher does not accept [W].") + return 0 //I don't care, but the existing code rejects them for not being "sheets" *shrug* -Sayu + var/current = 0 + for(var/obj/item/stack/sheet/S in contents) + current += S.amount + if(capacity == current)//If it's full, you're done + if(!stop_messages) + to_chat(usr, "The snatcher is full.") + return 0 + return 1 + + +// Modified handle_item_insertion. Would prefer not to, but... +/obj/item/storage/bag/sheetsnatcher/handle_item_insertion(obj/item/W as obj, prevent_warning = 0) + var/obj/item/stack/sheet/S = W + if(!istype(S)) return 0 + + var/amount + var/inserted = 0 + var/current = 0 + for(var/obj/item/stack/sheet/S2 in contents) + current += S2.amount + if(capacity < current + S.amount)//If the stack will fill it up + amount = capacity - current + else + amount = S.amount + + for(var/obj/item/stack/sheet/sheet in contents) + if(S.type == sheet.type) // we are violating the amount limitation because these are not sane objects + sheet.amount += amount // they should only be removed through procs in this file, which split them up. + S.amount -= amount + inserted = 1 + break + + if(!inserted || !S.amount) + usr.unEquip(S) + usr.update_icons() //update our overlays + if(usr.client && usr.s_active != src) + usr.client.screen -= S + S.dropped(usr) + if(!S.amount) + qdel(S) + else + S.loc = src + + orient2hud(usr) + if(usr.s_active) + usr.s_active.show_to(usr) + update_icon() + return 1 + + +// Sets up numbered display to show the stack size of each stored mineral +// NOTE: numbered display is turned off currently because it's broken +/obj/item/storage/bag/sheetsnatcher/orient2hud(mob/user as mob) + var/adjusted_contents = contents.len + + //Numbered contents display + var/list/datum/numbered_display/numbered_contents + if(display_contents_with_number) + numbered_contents = list() + adjusted_contents = 0 + for(var/obj/item/stack/sheet/I in contents) + adjusted_contents++ + var/datum/numbered_display/D = new/datum/numbered_display(I) + D.number = I.amount + numbered_contents.Add( D ) + + var/row_num = 0 + var/col_count = min(7,storage_slots) -1 + if(adjusted_contents > 7) + row_num = round((adjusted_contents-1) / 7) // 7 is the maximum allowed width. + src.standard_orient_objs(row_num, col_count, numbered_contents) + return + + +// Modified quick_empty verb drops appropriate sized stacks +/obj/item/storage/bag/sheetsnatcher/quick_empty() + var/location = get_turf(src) + for(var/obj/item/stack/sheet/S in contents) + while(S.amount) + var/obj/item/stack/sheet/N = new S.type(location) + var/stacksize = min(S.amount,N.max_amount) + N.amount = stacksize + S.amount -= stacksize + if(!S.amount) + qdel(S) // todo: there's probably something missing here + orient2hud(usr) + if(usr.s_active) + usr.s_active.show_to(usr) + update_icon() + +// Instead of removing +/obj/item/storage/bag/sheetsnatcher/remove_from_storage(obj/item/W as obj, atom/new_location) + var/obj/item/stack/sheet/S = W + if(!istype(S)) return 0 + + //I would prefer to drop a new stack, but the item/attack_hand code + // that calls this can't recieve a different object than you clicked on. + //Therefore, make a new stack internally that has the remainder. + // -Sayu + + if(S.amount > S.max_amount) + var/obj/item/stack/sheet/temp = new S.type(src) + temp.amount = S.amount - S.max_amount + S.amount = S.max_amount + + return ..(S,new_location) + +// ----------------------------- +// Sheet Snatcher (Cyborg) +// ----------------------------- + +/obj/item/storage/bag/sheetsnatcher/borg + name = "Sheet Snatcher 9000" + desc = "" + capacity = 500//Borgs get more because >specialization + + +// ----------------------------- +// Cash Bag +// ----------------------------- + +/obj/item/storage/bag/cash + icon = 'icons/obj/storage.dmi' + icon_state = "cashbag" + name = "Cash bag" + desc = "A bag for carrying lots of cash. It's got a big dollar sign printed on the front." + storage_slots = 50; //the number of cash pieces it can carry. + max_combined_w_class = 200 //Doesn't matter what this is, so long as it's more or equal to storage_slots * cash.w_class + max_w_class = WEIGHT_CLASS_NORMAL + w_class = WEIGHT_CLASS_TINY + can_hold = list(/obj/item/coin,/obj/item/stack/spacecash) + +// ----------------------------- +// Book bag +// ----------------------------- + +/obj/item/storage/bag/books + name = "book bag" + desc = "A bag for books." + icon = 'icons/obj/library.dmi' + icon_state = "bookbag" + display_contents_with_number = 0 //This would look really stupid otherwise + storage_slots = 7 + max_combined_w_class = 21 + max_w_class = WEIGHT_CLASS_NORMAL + w_class = WEIGHT_CLASS_BULKY //Bigger than a book because physics + can_hold = list(/obj/item/book, /obj/item/storage/bible, /obj/item/tome, /obj/item/spellbook) + resistance_flags = FLAMMABLE + +/* + * Trays - Agouri + */ +/obj/item/storage/bag/tray + name = "tray" + icon = 'icons/obj/food/containers.dmi' + icon_state = "tray" + desc = "A metal tray to lay food on." + force = 5 + throwforce = 10.0 + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_BULKY + flags = CONDUCT + materials = list(MAT_METAL=3000) + +/obj/item/storage/bag/tray/attack(mob/living/M as mob, mob/living/user as mob) + ..() + // Drop all the things. All of them. + var/list/obj/item/oldContents = contents.Copy() + quick_empty() + + // Make each item scatter a bit + for(var/obj/item/I in oldContents) + spawn() + for(var/i = 1, i <= rand(1,2), i++) + if(I) + step(I, pick(NORTH,SOUTH,EAST,WEST)) + sleep(rand(2,4)) + + if(prob(50)) + playsound(M, 'sound/items/trayhit1.ogg', 50, 1) + else + playsound(M, 'sound/items/trayhit2.ogg', 50, 1) + + if(ishuman(M)) + if(prob(10)) + M.Weaken(2) + +/obj/item/storage/bag/tray/proc/rebuild_overlays() + overlays.Cut() + for(var/obj/item/I in contents) + overlays += image("icon" = I.icon, "icon_state" = I.icon_state, "layer" = -1) + +/obj/item/storage/bag/tray/remove_from_storage(obj/item/W as obj, atom/new_location) + ..() + rebuild_overlays() + +/obj/item/storage/bag/tray/handle_item_insertion(obj/item/I, prevent_warning = 0) + overlays += image("icon" = I.icon, "icon_state" = I.icon_state, "layer" = -1) + ..() + +/obj/item/storage/bag/tray/cyborg + +/obj/item/storage/bag/tray/cyborg/afterattack(atom/target, mob/user as mob) + if( isturf(target) || istype(target,/obj/structure/table) ) + var foundtable = istype(target,/obj/structure/table/) + if( !foundtable ) //it must be a turf! + for(var/obj/structure/table/T in target) + foundtable = 1 + break + + var turf/dropspot + if( !foundtable ) // don't unload things onto walls or other silly places. + dropspot = user.loc + else if( isturf(target) ) // they clicked on a turf with a table in it + dropspot = target + else // they clicked on a table + dropspot = target.loc + + overlays = null + + var droppedSomething = 0 + + for(var/obj/item/I in contents) + I.loc = dropspot + contents.Remove(I) + droppedSomething = 1 + if(!foundtable && isturf(dropspot)) + // if no table, presume that the person just shittily dropped the tray on the ground and made a mess everywhere! + spawn() + for(var/i = 1, i <= rand(1,2), i++) + if(I) + step(I, pick(NORTH,SOUTH,EAST,WEST)) + sleep(rand(2,4)) + if( droppedSomething ) + if( foundtable ) + user.visible_message("[user] unloads [user.p_their()] service tray.") + else + user.visible_message("[user] drops all the items on [user.p_their()] tray.") + + return ..() + + +/obj/item/storage/bag/tray/cookies_tray + var/cookie = /obj/item/reagent_containers/food/snacks/cookie + +/obj/item/storage/bag/tray/cookies_tray/New() /// By Azule Utama, thank you a lot! + ..() + for(var/i in 1 to 6) + var/obj/item/C = new cookie(src) + handle_item_insertion(C) // Done this way so the tray actually has the cookies visible when spawned + rebuild_overlays() + +/obj/item/storage/bag/tray/cookies_tray/sugarcookie + cookie = /obj/item/reagent_containers/food/snacks/sugarcookie + +/* + * Chemistry bag + */ + +/obj/item/storage/bag/chemistry + name = "chemistry bag" + icon = 'icons/obj/chemical.dmi' + icon_state = "bag" + desc = "A bag for storing pills, patches, and bottles." + storage_slots = 50 + max_combined_w_class = 200 + w_class = WEIGHT_CLASS_TINY + can_hold = list(/obj/item/reagent_containers/food/pill,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/glass/bottle) + resistance_flags = FLAMMABLE +/* + * Biowaste bag (mostly for xenobiologists) + */ + +/obj/item/storage/bag/bio + name = "bio bag" + icon = 'icons/obj/chemical.dmi' + icon_state = "biobag" + desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." + storage_slots = 25 + max_combined_w_class = 200 + w_class = WEIGHT_CLASS_TINY + can_hold = list(/obj/item/slime_extract,/obj/item/reagent_containers/food/snacks/monkeycube,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/iv_bag,/obj/item/reagent_containers/hypospray/autoinjector) + resistance_flags = FLAMMABLE diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm index 0ebd553fc3b..755b0076c3c 100644 --- a/code/game/objects/items/weapons/storage/belt.dm +++ b/code/game/objects/items/weapons/storage/belt.dm @@ -1,793 +1,793 @@ -/obj/item/storage/belt - name = "belt" - desc = "Can hold various things." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "utilitybelt" - item_state = "utility" - lefthand_file = 'icons/mob/inhands/equipment/belt_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/belt_righthand.dmi' - slot_flags = SLOT_BELT - attack_verb = list("whipped", "lashed", "disciplined") - max_integrity = 300 - var/use_item_overlays = 0 // Do we have overlays for items held inside the belt? - -/obj/item/storage/belt/update_icon() - if(use_item_overlays) - overlays.Cut() - for(var/obj/item/I in contents) - overlays += "[I.name]" - ..() - -/obj/item/storage/belt/proc/can_use() - return is_equipped() - -/obj/item/storage/belt/MouseDrop(obj/over_object as obj, src_location, over_location) - var/mob/M = usr - if(!istype(over_object, /obj/screen)) - return ..() - playsound(src.loc, "rustle", 50, 1, -5) - if(!M.restrained() && !M.stat && can_use()) - switch(over_object.name) - if("r_hand") - M.unEquip(src) - M.put_in_r_hand(src) - if("l_hand") - M.unEquip(src) - M.put_in_l_hand(src) - src.add_fingerprint(usr) - return - -/obj/item/storage/belt/deserialize(list/data) - ..() - update_icon() - -/obj/item/storage/belt/utility - name = "tool-belt" //Carn: utility belt is nicer, but it bamboozles the text parsing. - desc = "Can hold various tools." - icon_state = "utilitybelt" - item_state = "utility" - use_item_overlays = 1 - can_hold = list( - /obj/item/crowbar, - /obj/item/screwdriver, - /obj/item/weldingtool, - /obj/item/wirecutters, - /obj/item/wrench, - /obj/item/multitool, - /obj/item/flashlight, - /obj/item/stack/cable_coil, - /obj/item/t_scanner, - /obj/item/analyzer, - /obj/item/extinguisher/mini, - /obj/item/holosign_creator) - -/obj/item/storage/belt/utility/full/New() - ..() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/stack/cable_coil/random(src, 30) - update_icon() - -/obj/item/storage/belt/utility/full/multitool/New() - ..() - new /obj/item/multitool(src) - update_icon() - -/obj/item/storage/belt/utility/atmostech/New() - ..() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/wirecutters(src) - new /obj/item/t_scanner(src) - new /obj/item/extinguisher/mini(src) - update_icon() - -/obj/item/storage/belt/utility/chief - name = "advanced toolbelt" - desc = "Holds tools, looks snazzy" - icon_state = "utilitybelt_ce" - item_state = "utility_ce" - -/obj/item/storage/belt/utility/chief/full/New() - ..() - new /obj/item/screwdriver/power(src) - new /obj/item/crowbar/power(src) - new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil/random(src, 30) - new /obj/item/extinguisher/mini(src) - new /obj/item/analyzer(src) - update_icon() - //much roomier now that we've managed to remove two tools - -/obj/item/storage/belt/medical - use_to_pickup = 1 //Allow medical belt to pick up medicine - name = "medical belt" - desc = "Can hold various medical equipment." - icon_state = "medicalbelt" - item_state = "medical" - use_item_overlays = 1 - max_w_class = WEIGHT_CLASS_NORMAL - can_hold = list( - /obj/item/healthanalyzer, - /obj/item/dnainjector, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /obj/item/reagent_containers/food/pill, - /obj/item/reagent_containers/syringe, - /obj/item/lighter/zippo, - /obj/item/storage/fancy/cigarettes, - /obj/item/storage/pill_bottle, - /obj/item/stack/medical, - /obj/item/flashlight/pen, - /obj/item/clothing/mask/surgical, - /obj/item/clothing/gloves/color/latex, - /obj/item/reagent_containers/hypospray/autoinjector, - /obj/item/reagent_containers/hypospray/CMO, - /obj/item/reagent_containers/hypospray/safety, - /obj/item/rad_laser, - /obj/item/sensor_device, - /obj/item/wrench/medical, - /obj/item/handheld_defibrillator - ) - -/obj/item/storage/belt/medical/surgery - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 17 - use_to_pickup = 1 - name = "surgical belt" - desc = "Can hold various surgical tools." - storage_slots = 9 - use_item_overlays = 1 - can_hold = list( - /obj/item/scalpel, - /obj/item/hemostat, - /obj/item/retractor, - /obj/item/circular_saw, - /obj/item/bonegel, - /obj/item/bonesetter, - /obj/item/FixOVein, - /obj/item/surgicaldrill, - /obj/item/cautery, - ) - -/obj/item/storage/belt/medical/surgery/loaded - -/obj/item/storage/belt/medical/surgery/loaded/New() - ..() - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/retractor(src) - new /obj/item/circular_saw(src) - new /obj/item/bonegel(src) - new /obj/item/bonesetter(src) - new /obj/item/FixOVein(src) - new /obj/item/surgicaldrill(src) - new /obj/item/cautery(src) - -/obj/item/storage/belt/medical/response_team - -/obj/item/storage/belt/medical/response_team/New() - ..() - new /obj/item/reagent_containers/food/pill/salbutamol(src) - new /obj/item/reagent_containers/food/pill/salbutamol(src) - new /obj/item/reagent_containers/food/pill/charcoal(src) - new /obj/item/reagent_containers/food/pill/charcoal(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - update_icon() - -/obj/item/storage/belt/botany - name = "botanist belt" - desc = "Can hold various botanical supplies." - icon_state = "botanybelt" - item_state = "botany" - use_item_overlays = 1 - can_hold = list( - /obj/item/plant_analyzer, - /obj/item/cultivator, - /obj/item/hatchet, - /obj/item/reagent_containers/glass/bottle, -// /obj/item/reagent_containers/syringe, -// /obj/item/reagent_containers/glass/beaker, - /obj/item/lighter/zippo, - /obj/item/storage/fancy/cigarettes, - /obj/item/shovel/spade, - /obj/item/flashlight/pen, - /obj/item/seeds, - /obj/item/wirecutters, - /obj/item/wrench, - ) - -/obj/item/storage/belt/security - name = "security belt" - desc = "Can hold security gear like handcuffs and flashes." - icon_state = "securitybelt" - item_state = "security"//Could likely use a better one. - storage_slots = 5 - max_w_class = WEIGHT_CLASS_NORMAL - use_item_overlays = 1 - can_hold = list( - /obj/item/grenade/flashbang, - /obj/item/grenade/chem_grenade/teargas, - /obj/item/reagent_containers/spray/pepper, - /obj/item/restraints/handcuffs, - /obj/item/flash, - /obj/item/clothing/glasses, - /obj/item/ammo_casing/shotgun, - /obj/item/ammo_box, - /obj/item/reagent_containers/food/snacks/donut, - /obj/item/kitchen/knife/combat, - /obj/item/melee/baton, - /obj/item/melee/classic_baton, - /obj/item/flashlight/seclite, - /obj/item/holosign_creator/security, - /obj/item/melee/classic_baton/telescopic, - /obj/item/restraints/legcuffs/bola) - -/obj/item/storage/belt/security/sec/New() - ..() - new /obj/item/flashlight/seclite(src) - update_icon() - -/obj/item/storage/belt/security/response_team/New() - ..() - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/flash(src) - new /obj/item/melee/classic_baton/telescopic(src) - new /obj/item/grenade/flashbang(src) - update_icon() - -/obj/item/storage/belt/security/response_team_gamma/New() - ..() - new /obj/item/melee/baton/loaded(src) - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/flash(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/grenade/flashbang(src) - update_icon() - -/obj/item/storage/belt/soulstone - name = "soul stone belt" - desc = "Designed for ease of access to the shards during a fight, as to not let a single enemy spirit slip away" - icon_state = "soulstonebelt" - item_state = "soulstonebelt" - storage_slots = 6 - use_item_overlays = 1 - can_hold = list( - "/obj/item/soulstone" - ) - -/obj/item/storage/belt/soulstone/full/New() - ..() - new /obj/item/soulstone(src) - new /obj/item/soulstone(src) - new /obj/item/soulstone(src) - new /obj/item/soulstone(src) - new /obj/item/soulstone(src) - new /obj/item/soulstone(src) - update_icon() - - -/obj/item/storage/belt/champion - name = "championship belt" - desc = "Proves to the world that you are the strongest!" - icon_state = "championbelt" - item_state = "champion" - materials = list(MAT_GOLD=400) - storage_slots = 1 - can_hold = list( - "/obj/item/clothing/mask/luchador" - ) - -/obj/item/storage/belt/military - name = "military belt" - desc = "A syndicate belt designed to be used by boarding parties. Its style is modelled after the hardsuits they wear." - icon_state = "militarybelt" - item_state = "military" - max_w_class = WEIGHT_CLASS_SMALL - resistance_flags = FIRE_PROOF - -/obj/item/storage/belt/military/sst - icon_state = "assaultbelt" - item_state = "assault" - -/obj/item/storage/belt/military/traitor - name = "tool-belt" - desc = "Can hold various tools. This model seems to have additional compartments." - icon_state = "utilitybelt" - item_state = "utility" - use_item_overlays = 1 // So it will still show tools in it in case sec get lazy and just glance at it. - -/obj/item/storage/belt/grenade - name = "grenadier belt" - desc = "A belt for holding grenades." - icon_state = "assaultbelt" - item_state = "assault" - storage_slots = 30 - max_combined_w_class = 60 - display_contents_with_number = 1 - can_hold = list( - /obj/item/grenade, - /obj/item/lighter, - /obj/item/reagent_containers/food/drinks/bottle/molotov - ) - -/obj/item/storage/belt/grenade/full/New() - ..() - new /obj/item/grenade/smokebomb(src) //4 - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/empgrenade(src) //2 - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/gluon(src) //4 - new /obj/item/grenade/gluon(src) - new /obj/item/grenade/gluon(src) - new /obj/item/grenade/gluon(src) - new /obj/item/grenade/chem_grenade/facid(src) //1 - new /obj/item/grenade/chem_grenade/saringas(src) //1 - new /obj/item/grenade/gas/plasma(src) //2 - new /obj/item/grenade/gas/plasma(src) - new /obj/item/grenade/frag(src) //10 - new /obj/item/grenade/frag(src) - new /obj/item/grenade/frag(src) - new /obj/item/grenade/frag(src) - new /obj/item/grenade/frag(src) - new /obj/item/grenade/frag(src) - new /obj/item/grenade/frag(src) - new /obj/item/grenade/frag(src) - new /obj/item/grenade/frag(src) - new /obj/item/grenade/frag(src) - new /obj/item/grenade/syndieminibomb(src) //2 - new /obj/item/grenade/syndieminibomb(src) - -/obj/item/storage/belt/military/abductor - name = "agent belt" - desc = "A belt used by abductor agents." - icon = 'icons/obj/abductor.dmi' - icon_state = "belt" - item_state = "security" - -/obj/item/storage/belt/military/abductor/full/New() - ..() - new /obj/item/screwdriver/abductor(src) - new /obj/item/wrench/abductor(src) - new /obj/item/weldingtool/abductor(src) - new /obj/item/crowbar/abductor(src) - new /obj/item/wirecutters/abductor(src) - new /obj/item/multitool/abductor(src) - new /obj/item/stack/cable_coil(src, 30, COLOR_WHITE) - -/obj/item/storage/belt/military/assault - name = "assault belt" - desc = "A tactical assault belt." - icon_state = "assaultbelt" - item_state = "assault" - storage_slots = 6 - -/obj/item/storage/belt/janitor - name = "janibelt" - desc = "A belt used to hold most janitorial supplies." - icon_state = "janibelt" - item_state = "janibelt" - storage_slots = 6 - max_w_class = WEIGHT_CLASS_BULKY // Set to this so the light replacer can fit. - use_item_overlays = 1 - can_hold = list( - /obj/item/grenade/chem_grenade/cleaner, - /obj/item/lightreplacer, - /obj/item/flashlight, - /obj/item/reagent_containers/spray, - /obj/item/soap, - /obj/item/holosign_creator, - /obj/item/melee/flyswatter, - ) - -/obj/item/storage/belt/janitor/full/New() - ..() - new /obj/item/lightreplacer(src) - new /obj/item/holosign_creator(src) - new /obj/item/reagent_containers/spray/cleaner(src) - new /obj/item/soap(src) - new /obj/item/grenade/chem_grenade/cleaner(src) - new /obj/item/grenade/chem_grenade/cleaner(src) - new /obj/item/melee/flyswatter(src) - update_icon() - -/obj/item/storage/belt/lazarus - name = "trainer's belt" - desc = "For the mining master, holds your lazarus capsules." - icon_state = "lazarusbelt" - item_state = "lazbelt" - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_TINY - max_combined_w_class = 6 - storage_slots = 6 - can_hold = list(/obj/item/mobcapsule) - -/obj/item/storage/belt/lazarus/New() - ..() - update_icon() - - -/obj/item/storage/belt/lazarus/update_icon() - ..() - icon_state = "[initial(icon_state)]_[contents.len]" - -/obj/item/storage/belt/lazarus/attackby(obj/item/W, mob/user) - var/amount = contents.len - . = ..() - if(amount != contents.len) - update_icon() - -/obj/item/storage/belt/lazarus/remove_from_storage(obj/item/W as obj, atom/new_location) - ..() - update_icon() - - -/obj/item/storage/belt/bandolier - name = "bandolier" - desc = "A bandolier for holding shotgun ammunition." - icon_state = "bandolier" - item_state = "bandolier" - storage_slots = 8 - can_hold = list( - /obj/item/ammo_casing/shotgun - ) - -/obj/item/storage/belt/bandolier/New() - ..() - update_icon() - -/obj/item/storage/belt/bandolier/full/New() - ..() - new /obj/item/ammo_casing/shotgun/beanbag(src) - new /obj/item/ammo_casing/shotgun/beanbag(src) - new /obj/item/ammo_casing/shotgun/beanbag(src) - new /obj/item/ammo_casing/shotgun/beanbag(src) - new /obj/item/ammo_casing/shotgun/beanbag(src) - new /obj/item/ammo_casing/shotgun/beanbag(src) - new /obj/item/ammo_casing/shotgun/beanbag(src) - new /obj/item/ammo_casing/shotgun/beanbag(src) - update_icon() - -/obj/item/storage/belt/bandolier/update_icon() - ..() - icon_state = "[initial(icon_state)]_[contents.len]" - -/obj/item/storage/belt/bandolier/attackby(obj/item/W, mob/user) - var/amount = contents.len - . = ..() - if(amount != contents.len) - update_icon() - -/obj/item/storage/belt/bandolier/remove_from_storage(obj/item/W as obj, atom/new_location) - ..() - update_icon() - -/obj/item/storage/belt/holster - name = "shoulder holster" - desc = "A holster to conceal a carried handgun. WARNING: Badasses only." - icon_state = "holster" - item_state = "holster" - storage_slots = 1 - max_w_class = WEIGHT_CLASS_NORMAL - can_hold = list( - /obj/item/gun/projectile/automatic/pistol, - /obj/item/gun/projectile/revolver/detective - ) - -/obj/item/storage/belt/wands - name = "wand belt" - desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic." - icon_state = "soulstonebelt" - item_state = "soulstonebelt" - storage_slots = 6 - use_item_overlays = 1 - can_hold = list( - /obj/item/gun/magic/wand - ) - -/obj/item/storage/belt/wands/full/New() - ..() - new /obj/item/gun/magic/wand/death(src) - new /obj/item/gun/magic/wand/resurrection(src) - new /obj/item/gun/magic/wand/polymorph(src) - new /obj/item/gun/magic/wand/teleport(src) - new /obj/item/gun/magic/wand/door(src) - new /obj/item/gun/magic/wand/fireball(src) - - for(var/obj/item/gun/magic/wand/W in contents) //All wands in this pack come in the best possible condition - W.max_charges = initial(W.max_charges) - W.charges = W.max_charges - update_icon() - -/obj/item/storage/belt/fannypack - name = "fannypack" - desc = "A dorky fannypack for keeping small items in." - icon_state = "fannypack_leather" - item_state = "fannypack_leather" - storage_slots = 3 - max_w_class = WEIGHT_CLASS_SMALL - -/obj/item/storage/belt/fannypack/black - name = "black fannypack" - icon_state = "fannypack_black" - item_state = "fannypack_black" - -/obj/item/storage/belt/fannypack/red - name = "red fannypack" - icon_state = "fannypack_red" - item_state = "fannypack_red" - -/obj/item/storage/belt/fannypack/purple - name = "purple fannypack" - icon_state = "fannypack_purple" - item_state = "fannypack_purple" - -/obj/item/storage/belt/fannypack/blue - name = "blue fannypack" - icon_state = "fannypack_blue" - item_state = "fannypack_blue" - -/obj/item/storage/belt/fannypack/orange - name = "orange fannypack" - icon_state = "fannypack_orange" - item_state = "fannypack_orange" - -/obj/item/storage/belt/fannypack/white - name = "white fannypack" - icon_state = "fannypack_white" - item_state = "fannypack_white" - -/obj/item/storage/belt/fannypack/green - name = "green fannypack" - icon_state = "fannypack_green" - item_state = "fannypack_green" - -/obj/item/storage/belt/fannypack/pink - name = "pink fannypack" - icon_state = "fannypack_pink" - item_state = "fannypack_pink" - -/obj/item/storage/belt/fannypack/cyan - name = "cyan fannypack" - icon_state = "fannypack_cyan" - item_state = "fannypack_cyan" - -/obj/item/storage/belt/fannypack/yellow - name = "yellow fannypack" - icon_state = "fannypack_yellow" - item_state = "fannypack_yellow" - -/obj/item/storage/belt/rapier - name = "rapier sheath" - desc = "Can hold rapiers." - icon_state = "sheath" - item_state = "sheath" - storage_slots = 1 - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_BULKY - can_hold = list(/obj/item/melee/rapier) - -/obj/item/storage/belt/rapier/update_icon() - icon_state = "[initial(icon_state)]" - item_state = "[initial(item_state)]" - if(contents.len) - icon_state = "[initial(icon_state)]-rapier" - item_state = "[initial(item_state)]-rapier" - if(isliving(loc)) - var/mob/living/L = loc - L.update_inv_belt() - ..() - -/obj/item/storage/belt/rapier/New() - ..() - new /obj/item/melee/rapier(src) - update_icon() - -// ------------------------------------- -// Bluespace Belt -// ------------------------------------- - -/obj/item/storage/belt/bluespace - name = "Belt of Holding" - desc = "The greatest in pants-supporting technology." - icon_state = "holdingbelt" - item_state = "holdingbelt" - storage_slots = 14 - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_SMALL - max_combined_w_class = 21 // = 14 * 1.5, not 14 * 2. This is deliberate - origin_tech = "bluespace=5;materials=4;engineering=4;plasmatech=5" - can_hold = list() - -/obj/item/storage/belt/bluespace/owlman - name = "Owlman's utility belt" - desc = "Sometimes people choose justice. Sometimes, justice chooses you..." - icon_state = "securitybelt" - item_state = "security" - storage_slots = 6 - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 18 - origin_tech = "bluespace=5;materials=4;engineering=4;plasmatech=5" - allow_quick_empty = 1 - can_hold = list( - /obj/item/grenade/smokebomb, - /obj/item/restraints/legcuffs/bola - ) - - flags = NODROP - var/smokecount = 0 - var/bolacount = 0 - var/cooldown = 0 - -/obj/item/storage/belt/bluespace/owlman/New() - ..() - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/restraints/legcuffs/bola(src) - new /obj/item/restraints/legcuffs/bola(src) - START_PROCESSING(SSobj, src) - cooldown = world.time - -/obj/item/storage/belt/bluespace/owlman/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/storage/belt/bluespace/owlman/process() - if(cooldown < world.time - 600) - smokecount = 0 - var/obj/item/grenade/smokebomb/S - for(S in src) - smokecount++ - bolacount = 0 - var/obj/item/restraints/legcuffs/bola/B - for(B in src) - bolacount++ - if(smokecount < 4) - while(smokecount < 4) - new /obj/item/grenade/smokebomb(src) - smokecount++ - if(bolacount < 2) - while(bolacount < 2) - new /obj/item/restraints/legcuffs/bola(src) - bolacount++ - cooldown = world.time - update_icon() - orient2hud() - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.belt && H.belt == src) - if(H.s_active && H.s_active == src) - H.s_active.show_to(H) - -/obj/item/storage/belt/bluespace/attack(mob/M as mob, mob/user as mob, def_zone) - return - -/obj/item/storage/belt/bluespace/admin - name = "Admin's Tool-belt" - desc = "Holds everything for those that run everything." - icon_state = "soulstonebelt" - item_state = "soulstonebelt" - w_class = 10 // permit holding other storage items - storage_slots = 28 - max_w_class = 10 - max_combined_w_class = 280 - can_hold = list() - -/obj/item/storage/belt/bluespace/admin/New() - ..() - new /obj/item/crowbar(src) - new /obj/item/screwdriver(src) - new /obj/item/weldingtool/hugetank(src) - new /obj/item/wirecutters(src) - new /obj/item/wrench(src) - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src) - - new /obj/item/restraints/handcuffs(src) - new /obj/item/dnainjector/xraymut(src) - new /obj/item/dnainjector/firemut(src) - new /obj/item/dnainjector/telemut(src) - new /obj/item/dnainjector/hulkmut(src) -// new /obj/item/spellbook(src) // for smoke effects, door openings, etc -// new /obj/item/magic/spellbook(src) - -// new/obj/item/reagent_containers/hypospray/admin(src) - -/obj/item/storage/belt/bluespace/sandbox - name = "Sandbox Mode Toolbelt" - desc = "Holds whatever, you can spawn your own damn stuff." - w_class = 10 // permit holding other storage items - storage_slots = 28 - max_w_class = 10 - max_combined_w_class = 280 - can_hold = list() - -/obj/item/storage/belt/bluespace/sandbox/New() - ..() - new /obj/item/crowbar(src) - new /obj/item/screwdriver(src) - new /obj/item/weldingtool/hugetank(src) - new /obj/item/wirecutters(src) - new /obj/item/wrench(src) - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src) - - new /obj/item/analyzer(src) - new /obj/item/healthanalyzer(src) - -/obj/item/storage/belt/mining - name = "explorer's webbing" - desc = "A versatile chest rig, cherished by miners and hunters alike." - icon_state = "explorer1" - item_state = "explorer1" - storage_slots = 6 - max_w_class = WEIGHT_CLASS_BULKY - max_combined_w_class = 20 - use_item_overlays = 0 - can_hold = list( - /obj/item/crowbar, - /obj/item/screwdriver, - /obj/item/weldingtool, - /obj/item/wirecutters, - /obj/item/wrench, - /obj/item/multitool, - /obj/item/flashlight, - /obj/item/stack/cable_coil, - /obj/item/analyzer, - /obj/item/extinguisher/mini, - /obj/item/radio, - /obj/item/clothing/gloves, - /obj/item/resonator, - /obj/item/mining_scanner, - /obj/item/pickaxe, - /obj/item/shovel, - /obj/item/stack/sheet/animalhide, - /obj/item/stack/sheet/sinew, - /obj/item/stack/sheet/bone, - /obj/item/lighter, - /obj/item/storage/fancy/cigarettes, - /obj/item/reagent_containers/food/drinks/bottle, - /obj/item/stack/medical, - /obj/item/kitchen/knife, - /obj/item/reagent_containers/hypospray, - /obj/item/gps, - /obj/item/storage/bag/ore, - /obj/item/survivalcapsule, - /obj/item/t_scanner/adv_mining_scanner, - /obj/item/reagent_containers/food/pill, - /obj/item/storage/pill_bottle, - /obj/item/stack/ore, - /obj/item/reagent_containers/food/drinks, - /obj/item/organ/internal/regenerative_core, - /obj/item/wormhole_jaunter, - /obj/item/storage/bag/plants, - /obj/item/stack/marker_beacon) - -/obj/item/storage/belt/mining/vendor/Initialize(mapload) - . = ..() - new /obj/item/survivalcapsule(src) - -/obj/item/storage/belt/mining/alt - icon_state = "explorer2" - item_state = "explorer2" - -/obj/item/storage/belt/mining/primitive - name = "hunter's belt" - desc = "A versatile belt, woven from sinew." - icon_state = "ebelt" - item_state = "ebelt" - storage_slots = 5 \ No newline at end of file +/obj/item/storage/belt + name = "belt" + desc = "Can hold various things." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "utilitybelt" + item_state = "utility" + lefthand_file = 'icons/mob/inhands/equipment/belt_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/belt_righthand.dmi' + slot_flags = SLOT_BELT + attack_verb = list("whipped", "lashed", "disciplined") + max_integrity = 300 + var/use_item_overlays = 0 // Do we have overlays for items held inside the belt? + +/obj/item/storage/belt/update_icon() + if(use_item_overlays) + overlays.Cut() + for(var/obj/item/I in contents) + overlays += "[I.name]" + ..() + +/obj/item/storage/belt/proc/can_use() + return is_equipped() + +/obj/item/storage/belt/MouseDrop(obj/over_object as obj, src_location, over_location) + var/mob/M = usr + if(!istype(over_object, /obj/screen)) + return ..() + playsound(src.loc, "rustle", 50, 1, -5) + if(!M.restrained() && !M.stat && can_use()) + switch(over_object.name) + if("r_hand") + M.unEquip(src) + M.put_in_r_hand(src) + if("l_hand") + M.unEquip(src) + M.put_in_l_hand(src) + src.add_fingerprint(usr) + return + +/obj/item/storage/belt/deserialize(list/data) + ..() + update_icon() + +/obj/item/storage/belt/utility + name = "tool-belt" //Carn: utility belt is nicer, but it bamboozles the text parsing. + desc = "Can hold various tools." + icon_state = "utilitybelt" + item_state = "utility" + use_item_overlays = 1 + can_hold = list( + /obj/item/crowbar, + /obj/item/screwdriver, + /obj/item/weldingtool, + /obj/item/wirecutters, + /obj/item/wrench, + /obj/item/multitool, + /obj/item/flashlight, + /obj/item/stack/cable_coil, + /obj/item/t_scanner, + /obj/item/analyzer, + /obj/item/extinguisher/mini, + /obj/item/holosign_creator) + +/obj/item/storage/belt/utility/full/New() + ..() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/stack/cable_coil/random(src, 30) + update_icon() + +/obj/item/storage/belt/utility/full/multitool/New() + ..() + new /obj/item/multitool(src) + update_icon() + +/obj/item/storage/belt/utility/atmostech/New() + ..() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/wirecutters(src) + new /obj/item/t_scanner(src) + new /obj/item/extinguisher/mini(src) + update_icon() + +/obj/item/storage/belt/utility/chief + name = "advanced toolbelt" + desc = "Holds tools, looks snazzy" + icon_state = "utilitybelt_ce" + item_state = "utility_ce" + +/obj/item/storage/belt/utility/chief/full/New() + ..() + new /obj/item/screwdriver/power(src) + new /obj/item/crowbar/power(src) + new /obj/item/weldingtool/experimental(src)//This can be changed if this is too much + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil/random(src, 30) + new /obj/item/extinguisher/mini(src) + new /obj/item/analyzer(src) + update_icon() + //much roomier now that we've managed to remove two tools + +/obj/item/storage/belt/medical + use_to_pickup = 1 //Allow medical belt to pick up medicine + name = "medical belt" + desc = "Can hold various medical equipment." + icon_state = "medicalbelt" + item_state = "medical" + use_item_overlays = 1 + max_w_class = WEIGHT_CLASS_NORMAL + can_hold = list( + /obj/item/healthanalyzer, + /obj/item/dnainjector, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /obj/item/reagent_containers/food/pill, + /obj/item/reagent_containers/syringe, + /obj/item/lighter/zippo, + /obj/item/storage/fancy/cigarettes, + /obj/item/storage/pill_bottle, + /obj/item/stack/medical, + /obj/item/flashlight/pen, + /obj/item/clothing/mask/surgical, + /obj/item/clothing/gloves/color/latex, + /obj/item/reagent_containers/hypospray/autoinjector, + /obj/item/reagent_containers/hypospray/CMO, + /obj/item/reagent_containers/hypospray/safety, + /obj/item/rad_laser, + /obj/item/sensor_device, + /obj/item/wrench/medical, + /obj/item/handheld_defibrillator + ) + +/obj/item/storage/belt/medical/surgery + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 17 + use_to_pickup = 1 + name = "surgical belt" + desc = "Can hold various surgical tools." + storage_slots = 9 + use_item_overlays = 1 + can_hold = list( + /obj/item/scalpel, + /obj/item/hemostat, + /obj/item/retractor, + /obj/item/circular_saw, + /obj/item/bonegel, + /obj/item/bonesetter, + /obj/item/FixOVein, + /obj/item/surgicaldrill, + /obj/item/cautery, + ) + +/obj/item/storage/belt/medical/surgery/loaded + +/obj/item/storage/belt/medical/surgery/loaded/New() + ..() + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/retractor(src) + new /obj/item/circular_saw(src) + new /obj/item/bonegel(src) + new /obj/item/bonesetter(src) + new /obj/item/FixOVein(src) + new /obj/item/surgicaldrill(src) + new /obj/item/cautery(src) + +/obj/item/storage/belt/medical/response_team + +/obj/item/storage/belt/medical/response_team/New() + ..() + new /obj/item/reagent_containers/food/pill/salbutamol(src) + new /obj/item/reagent_containers/food/pill/salbutamol(src) + new /obj/item/reagent_containers/food/pill/charcoal(src) + new /obj/item/reagent_containers/food/pill/charcoal(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + update_icon() + +/obj/item/storage/belt/botany + name = "botanist belt" + desc = "Can hold various botanical supplies." + icon_state = "botanybelt" + item_state = "botany" + use_item_overlays = 1 + can_hold = list( + /obj/item/plant_analyzer, + /obj/item/cultivator, + /obj/item/hatchet, + /obj/item/reagent_containers/glass/bottle, +// /obj/item/reagent_containers/syringe, +// /obj/item/reagent_containers/glass/beaker, + /obj/item/lighter/zippo, + /obj/item/storage/fancy/cigarettes, + /obj/item/shovel/spade, + /obj/item/flashlight/pen, + /obj/item/seeds, + /obj/item/wirecutters, + /obj/item/wrench, + ) + +/obj/item/storage/belt/security + name = "security belt" + desc = "Can hold security gear like handcuffs and flashes." + icon_state = "securitybelt" + item_state = "security"//Could likely use a better one. + storage_slots = 5 + max_w_class = WEIGHT_CLASS_NORMAL + use_item_overlays = 1 + can_hold = list( + /obj/item/grenade/flashbang, + /obj/item/grenade/chem_grenade/teargas, + /obj/item/reagent_containers/spray/pepper, + /obj/item/restraints/handcuffs, + /obj/item/flash, + /obj/item/clothing/glasses, + /obj/item/ammo_casing/shotgun, + /obj/item/ammo_box, + /obj/item/reagent_containers/food/snacks/donut, + /obj/item/kitchen/knife/combat, + /obj/item/melee/baton, + /obj/item/melee/classic_baton, + /obj/item/flashlight/seclite, + /obj/item/holosign_creator/security, + /obj/item/melee/classic_baton/telescopic, + /obj/item/restraints/legcuffs/bola) + +/obj/item/storage/belt/security/sec/New() + ..() + new /obj/item/flashlight/seclite(src) + update_icon() + +/obj/item/storage/belt/security/response_team/New() + ..() + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/flash(src) + new /obj/item/melee/classic_baton/telescopic(src) + new /obj/item/grenade/flashbang(src) + update_icon() + +/obj/item/storage/belt/security/response_team_gamma/New() + ..() + new /obj/item/melee/baton/loaded(src) + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/flash(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/grenade/flashbang(src) + update_icon() + +/obj/item/storage/belt/soulstone + name = "soul stone belt" + desc = "Designed for ease of access to the shards during a fight, as to not let a single enemy spirit slip away" + icon_state = "soulstonebelt" + item_state = "soulstonebelt" + storage_slots = 6 + use_item_overlays = 1 + can_hold = list( + "/obj/item/soulstone" + ) + +/obj/item/storage/belt/soulstone/full/New() + ..() + new /obj/item/soulstone(src) + new /obj/item/soulstone(src) + new /obj/item/soulstone(src) + new /obj/item/soulstone(src) + new /obj/item/soulstone(src) + new /obj/item/soulstone(src) + update_icon() + + +/obj/item/storage/belt/champion + name = "championship belt" + desc = "Proves to the world that you are the strongest!" + icon_state = "championbelt" + item_state = "champion" + materials = list(MAT_GOLD=400) + storage_slots = 1 + can_hold = list( + "/obj/item/clothing/mask/luchador" + ) + +/obj/item/storage/belt/military + name = "military belt" + desc = "A syndicate belt designed to be used by boarding parties. Its style is modelled after the hardsuits they wear." + icon_state = "militarybelt" + item_state = "military" + max_w_class = WEIGHT_CLASS_SMALL + resistance_flags = FIRE_PROOF + +/obj/item/storage/belt/military/sst + icon_state = "assaultbelt" + item_state = "assault" + +/obj/item/storage/belt/military/traitor + name = "tool-belt" + desc = "Can hold various tools. This model seems to have additional compartments." + icon_state = "utilitybelt" + item_state = "utility" + use_item_overlays = 1 // So it will still show tools in it in case sec get lazy and just glance at it. + +/obj/item/storage/belt/grenade + name = "grenadier belt" + desc = "A belt for holding grenades." + icon_state = "assaultbelt" + item_state = "assault" + storage_slots = 30 + max_combined_w_class = 60 + display_contents_with_number = 1 + can_hold = list( + /obj/item/grenade, + /obj/item/lighter, + /obj/item/reagent_containers/food/drinks/bottle/molotov + ) + +/obj/item/storage/belt/grenade/full/New() + ..() + new /obj/item/grenade/smokebomb(src) //4 + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/empgrenade(src) //2 + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/gluon(src) //4 + new /obj/item/grenade/gluon(src) + new /obj/item/grenade/gluon(src) + new /obj/item/grenade/gluon(src) + new /obj/item/grenade/chem_grenade/facid(src) //1 + new /obj/item/grenade/chem_grenade/saringas(src) //1 + new /obj/item/grenade/gas/plasma(src) //2 + new /obj/item/grenade/gas/plasma(src) + new /obj/item/grenade/frag(src) //10 + new /obj/item/grenade/frag(src) + new /obj/item/grenade/frag(src) + new /obj/item/grenade/frag(src) + new /obj/item/grenade/frag(src) + new /obj/item/grenade/frag(src) + new /obj/item/grenade/frag(src) + new /obj/item/grenade/frag(src) + new /obj/item/grenade/frag(src) + new /obj/item/grenade/frag(src) + new /obj/item/grenade/syndieminibomb(src) //2 + new /obj/item/grenade/syndieminibomb(src) + +/obj/item/storage/belt/military/abductor + name = "agent belt" + desc = "A belt used by abductor agents." + icon = 'icons/obj/abductor.dmi' + icon_state = "belt" + item_state = "security" + +/obj/item/storage/belt/military/abductor/full/New() + ..() + new /obj/item/screwdriver/abductor(src) + new /obj/item/wrench/abductor(src) + new /obj/item/weldingtool/abductor(src) + new /obj/item/crowbar/abductor(src) + new /obj/item/wirecutters/abductor(src) + new /obj/item/multitool/abductor(src) + new /obj/item/stack/cable_coil(src, 30, COLOR_WHITE) + +/obj/item/storage/belt/military/assault + name = "assault belt" + desc = "A tactical assault belt." + icon_state = "assaultbelt" + item_state = "assault" + storage_slots = 6 + +/obj/item/storage/belt/janitor + name = "janibelt" + desc = "A belt used to hold most janitorial supplies." + icon_state = "janibelt" + item_state = "janibelt" + storage_slots = 6 + max_w_class = WEIGHT_CLASS_BULKY // Set to this so the light replacer can fit. + use_item_overlays = 1 + can_hold = list( + /obj/item/grenade/chem_grenade/cleaner, + /obj/item/lightreplacer, + /obj/item/flashlight, + /obj/item/reagent_containers/spray, + /obj/item/soap, + /obj/item/holosign_creator, + /obj/item/melee/flyswatter, + ) + +/obj/item/storage/belt/janitor/full/New() + ..() + new /obj/item/lightreplacer(src) + new /obj/item/holosign_creator(src) + new /obj/item/reagent_containers/spray/cleaner(src) + new /obj/item/soap(src) + new /obj/item/grenade/chem_grenade/cleaner(src) + new /obj/item/grenade/chem_grenade/cleaner(src) + new /obj/item/melee/flyswatter(src) + update_icon() + +/obj/item/storage/belt/lazarus + name = "trainer's belt" + desc = "For the mining master, holds your lazarus capsules." + icon_state = "lazarusbelt" + item_state = "lazbelt" + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_TINY + max_combined_w_class = 6 + storage_slots = 6 + can_hold = list(/obj/item/mobcapsule) + +/obj/item/storage/belt/lazarus/New() + ..() + update_icon() + + +/obj/item/storage/belt/lazarus/update_icon() + ..() + icon_state = "[initial(icon_state)]_[contents.len]" + +/obj/item/storage/belt/lazarus/attackby(obj/item/W, mob/user) + var/amount = contents.len + . = ..() + if(amount != contents.len) + update_icon() + +/obj/item/storage/belt/lazarus/remove_from_storage(obj/item/W as obj, atom/new_location) + ..() + update_icon() + + +/obj/item/storage/belt/bandolier + name = "bandolier" + desc = "A bandolier for holding shotgun ammunition." + icon_state = "bandolier" + item_state = "bandolier" + storage_slots = 8 + can_hold = list( + /obj/item/ammo_casing/shotgun + ) + +/obj/item/storage/belt/bandolier/New() + ..() + update_icon() + +/obj/item/storage/belt/bandolier/full/New() + ..() + new /obj/item/ammo_casing/shotgun/beanbag(src) + new /obj/item/ammo_casing/shotgun/beanbag(src) + new /obj/item/ammo_casing/shotgun/beanbag(src) + new /obj/item/ammo_casing/shotgun/beanbag(src) + new /obj/item/ammo_casing/shotgun/beanbag(src) + new /obj/item/ammo_casing/shotgun/beanbag(src) + new /obj/item/ammo_casing/shotgun/beanbag(src) + new /obj/item/ammo_casing/shotgun/beanbag(src) + update_icon() + +/obj/item/storage/belt/bandolier/update_icon() + ..() + icon_state = "[initial(icon_state)]_[contents.len]" + +/obj/item/storage/belt/bandolier/attackby(obj/item/W, mob/user) + var/amount = contents.len + . = ..() + if(amount != contents.len) + update_icon() + +/obj/item/storage/belt/bandolier/remove_from_storage(obj/item/W as obj, atom/new_location) + ..() + update_icon() + +/obj/item/storage/belt/holster + name = "shoulder holster" + desc = "A holster to conceal a carried handgun. WARNING: Badasses only." + icon_state = "holster" + item_state = "holster" + storage_slots = 1 + max_w_class = WEIGHT_CLASS_NORMAL + can_hold = list( + /obj/item/gun/projectile/automatic/pistol, + /obj/item/gun/projectile/revolver/detective + ) + +/obj/item/storage/belt/wands + name = "wand belt" + desc = "A belt designed to hold various rods of power. A veritable fanny pack of exotic magic." + icon_state = "soulstonebelt" + item_state = "soulstonebelt" + storage_slots = 6 + use_item_overlays = 1 + can_hold = list( + /obj/item/gun/magic/wand + ) + +/obj/item/storage/belt/wands/full/New() + ..() + new /obj/item/gun/magic/wand/death(src) + new /obj/item/gun/magic/wand/resurrection(src) + new /obj/item/gun/magic/wand/polymorph(src) + new /obj/item/gun/magic/wand/teleport(src) + new /obj/item/gun/magic/wand/door(src) + new /obj/item/gun/magic/wand/fireball(src) + + for(var/obj/item/gun/magic/wand/W in contents) //All wands in this pack come in the best possible condition + W.max_charges = initial(W.max_charges) + W.charges = W.max_charges + update_icon() + +/obj/item/storage/belt/fannypack + name = "fannypack" + desc = "A dorky fannypack for keeping small items in." + icon_state = "fannypack_leather" + item_state = "fannypack_leather" + storage_slots = 3 + max_w_class = WEIGHT_CLASS_SMALL + +/obj/item/storage/belt/fannypack/black + name = "black fannypack" + icon_state = "fannypack_black" + item_state = "fannypack_black" + +/obj/item/storage/belt/fannypack/red + name = "red fannypack" + icon_state = "fannypack_red" + item_state = "fannypack_red" + +/obj/item/storage/belt/fannypack/purple + name = "purple fannypack" + icon_state = "fannypack_purple" + item_state = "fannypack_purple" + +/obj/item/storage/belt/fannypack/blue + name = "blue fannypack" + icon_state = "fannypack_blue" + item_state = "fannypack_blue" + +/obj/item/storage/belt/fannypack/orange + name = "orange fannypack" + icon_state = "fannypack_orange" + item_state = "fannypack_orange" + +/obj/item/storage/belt/fannypack/white + name = "white fannypack" + icon_state = "fannypack_white" + item_state = "fannypack_white" + +/obj/item/storage/belt/fannypack/green + name = "green fannypack" + icon_state = "fannypack_green" + item_state = "fannypack_green" + +/obj/item/storage/belt/fannypack/pink + name = "pink fannypack" + icon_state = "fannypack_pink" + item_state = "fannypack_pink" + +/obj/item/storage/belt/fannypack/cyan + name = "cyan fannypack" + icon_state = "fannypack_cyan" + item_state = "fannypack_cyan" + +/obj/item/storage/belt/fannypack/yellow + name = "yellow fannypack" + icon_state = "fannypack_yellow" + item_state = "fannypack_yellow" + +/obj/item/storage/belt/rapier + name = "rapier sheath" + desc = "Can hold rapiers." + icon_state = "sheath" + item_state = "sheath" + storage_slots = 1 + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_BULKY + can_hold = list(/obj/item/melee/rapier) + +/obj/item/storage/belt/rapier/update_icon() + icon_state = "[initial(icon_state)]" + item_state = "[initial(item_state)]" + if(contents.len) + icon_state = "[initial(icon_state)]-rapier" + item_state = "[initial(item_state)]-rapier" + if(isliving(loc)) + var/mob/living/L = loc + L.update_inv_belt() + ..() + +/obj/item/storage/belt/rapier/New() + ..() + new /obj/item/melee/rapier(src) + update_icon() + +// ------------------------------------- +// Bluespace Belt +// ------------------------------------- + +/obj/item/storage/belt/bluespace + name = "Belt of Holding" + desc = "The greatest in pants-supporting technology." + icon_state = "holdingbelt" + item_state = "holdingbelt" + storage_slots = 14 + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_SMALL + max_combined_w_class = 21 // = 14 * 1.5, not 14 * 2. This is deliberate + origin_tech = "bluespace=5;materials=4;engineering=4;plasmatech=5" + can_hold = list() + +/obj/item/storage/belt/bluespace/owlman + name = "Owlman's utility belt" + desc = "Sometimes people choose justice. Sometimes, justice chooses you..." + icon_state = "securitybelt" + item_state = "security" + storage_slots = 6 + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 18 + origin_tech = "bluespace=5;materials=4;engineering=4;plasmatech=5" + allow_quick_empty = 1 + can_hold = list( + /obj/item/grenade/smokebomb, + /obj/item/restraints/legcuffs/bola + ) + + flags = NODROP + var/smokecount = 0 + var/bolacount = 0 + var/cooldown = 0 + +/obj/item/storage/belt/bluespace/owlman/New() + ..() + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/restraints/legcuffs/bola(src) + new /obj/item/restraints/legcuffs/bola(src) + START_PROCESSING(SSobj, src) + cooldown = world.time + +/obj/item/storage/belt/bluespace/owlman/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/storage/belt/bluespace/owlman/process() + if(cooldown < world.time - 600) + smokecount = 0 + var/obj/item/grenade/smokebomb/S + for(S in src) + smokecount++ + bolacount = 0 + var/obj/item/restraints/legcuffs/bola/B + for(B in src) + bolacount++ + if(smokecount < 4) + while(smokecount < 4) + new /obj/item/grenade/smokebomb(src) + smokecount++ + if(bolacount < 2) + while(bolacount < 2) + new /obj/item/restraints/legcuffs/bola(src) + bolacount++ + cooldown = world.time + update_icon() + orient2hud() + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.belt && H.belt == src) + if(H.s_active && H.s_active == src) + H.s_active.show_to(H) + +/obj/item/storage/belt/bluespace/attack(mob/M as mob, mob/user as mob, def_zone) + return + +/obj/item/storage/belt/bluespace/admin + name = "Admin's Tool-belt" + desc = "Holds everything for those that run everything." + icon_state = "soulstonebelt" + item_state = "soulstonebelt" + w_class = 10 // permit holding other storage items + storage_slots = 28 + max_w_class = 10 + max_combined_w_class = 280 + can_hold = list() + +/obj/item/storage/belt/bluespace/admin/New() + ..() + new /obj/item/crowbar(src) + new /obj/item/screwdriver(src) + new /obj/item/weldingtool/hugetank(src) + new /obj/item/wirecutters(src) + new /obj/item/wrench(src) + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src) + + new /obj/item/restraints/handcuffs(src) + new /obj/item/dnainjector/xraymut(src) + new /obj/item/dnainjector/firemut(src) + new /obj/item/dnainjector/telemut(src) + new /obj/item/dnainjector/hulkmut(src) +// new /obj/item/spellbook(src) // for smoke effects, door openings, etc +// new /obj/item/magic/spellbook(src) + +// new/obj/item/reagent_containers/hypospray/admin(src) + +/obj/item/storage/belt/bluespace/sandbox + name = "Sandbox Mode Toolbelt" + desc = "Holds whatever, you can spawn your own damn stuff." + w_class = 10 // permit holding other storage items + storage_slots = 28 + max_w_class = 10 + max_combined_w_class = 280 + can_hold = list() + +/obj/item/storage/belt/bluespace/sandbox/New() + ..() + new /obj/item/crowbar(src) + new /obj/item/screwdriver(src) + new /obj/item/weldingtool/hugetank(src) + new /obj/item/wirecutters(src) + new /obj/item/wrench(src) + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src) + + new /obj/item/analyzer(src) + new /obj/item/healthanalyzer(src) + +/obj/item/storage/belt/mining + name = "explorer's webbing" + desc = "A versatile chest rig, cherished by miners and hunters alike." + icon_state = "explorer1" + item_state = "explorer1" + storage_slots = 6 + max_w_class = WEIGHT_CLASS_BULKY + max_combined_w_class = 20 + use_item_overlays = 0 + can_hold = list( + /obj/item/crowbar, + /obj/item/screwdriver, + /obj/item/weldingtool, + /obj/item/wirecutters, + /obj/item/wrench, + /obj/item/multitool, + /obj/item/flashlight, + /obj/item/stack/cable_coil, + /obj/item/analyzer, + /obj/item/extinguisher/mini, + /obj/item/radio, + /obj/item/clothing/gloves, + /obj/item/resonator, + /obj/item/mining_scanner, + /obj/item/pickaxe, + /obj/item/shovel, + /obj/item/stack/sheet/animalhide, + /obj/item/stack/sheet/sinew, + /obj/item/stack/sheet/bone, + /obj/item/lighter, + /obj/item/storage/fancy/cigarettes, + /obj/item/reagent_containers/food/drinks/bottle, + /obj/item/stack/medical, + /obj/item/kitchen/knife, + /obj/item/reagent_containers/hypospray, + /obj/item/gps, + /obj/item/storage/bag/ore, + /obj/item/survivalcapsule, + /obj/item/t_scanner/adv_mining_scanner, + /obj/item/reagent_containers/food/pill, + /obj/item/storage/pill_bottle, + /obj/item/stack/ore, + /obj/item/reagent_containers/food/drinks, + /obj/item/organ/internal/regenerative_core, + /obj/item/wormhole_jaunter, + /obj/item/storage/bag/plants, + /obj/item/stack/marker_beacon) + +/obj/item/storage/belt/mining/vendor/Initialize(mapload) + . = ..() + new /obj/item/survivalcapsule(src) + +/obj/item/storage/belt/mining/alt + icon_state = "explorer2" + item_state = "explorer2" + +/obj/item/storage/belt/mining/primitive + name = "hunter's belt" + desc = "A versatile belt, woven from sinew." + icon_state = "ebelt" + item_state = "ebelt" + storage_slots = 5 diff --git a/code/game/objects/items/weapons/storage/bible.dm b/code/game/objects/items/weapons/storage/bible.dm index a79674d3217..c4afed4b266 100644 --- a/code/game/objects/items/weapons/storage/bible.dm +++ b/code/game/objects/items/weapons/storage/bible.dm @@ -1,114 +1,114 @@ -/obj/item/storage/bible - name = "bible" - desc = "Apply to head repeatedly." - icon_state ="bible" - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_NORMAL - resistance_flags = FIRE_PROOF - var/mob/affecting = null - var/deity_name = "Christ" - -/obj/item/storage/bible/suicide_act(mob/user) - to_chat(viewers(user), "[user] stares into [src.name] and attempts to transcend understanding of the universe!") - user.dust() - return OBLITERATION - -/obj/item/storage/bible/fart_act(mob/living/M) - if(QDELETED(M) || M.stat == DEAD) - return - M.visible_message("[M] farts on \the [name]!") - M.visible_message("A mysterious force smites [M]!") - M.suiciding = TRUE - do_sparks(3, 1, M) - M.gib() - return TRUE // Don't run the fart emote - -/obj/item/storage/bible/booze - name = "bible" - desc = "To be applied to the head repeatedly." - icon_state ="bible" - -/obj/item/storage/bible/booze/New() - ..() - new /obj/item/reagent_containers/food/drinks/cans/beer(src) - new /obj/item/reagent_containers/food/drinks/cans/beer(src) - new /obj/item/stack/spacecash(src) - new /obj/item/stack/spacecash(src) - new /obj/item/stack/spacecash(src) -//BS12 EDIT - // All cult functionality moved to Null Rod -/obj/item/storage/bible/proc/bless(mob/living/carbon/M as mob) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/heal_amt = 10 - for(var/obj/item/organ/external/affecting in H.bodyparts) - if(affecting.heal_damage(heal_amt, heal_amt)) - H.UpdateDamageIcon() - return - -/obj/item/storage/bible/attack(mob/living/M as mob, mob/living/user as mob) - add_attack_logs(user, M, "Hit with [src]") - if(!iscarbon(user)) - M.LAssailant = null - else - M.LAssailant = user - - if(!(istype(user, /mob/living/carbon/human) || SSticker) && SSticker.mode.name != "monkey") - to_chat(user, "You don't have the dexterity to do this!") - return - if(!user.mind || !user.mind.isholy) - to_chat(user, "The book sizzles in your hands.") - user.take_organ_damage(0,10) - return - - if((CLUMSY in user.mutations) && prob(50)) - to_chat(user, "The [src] slips out of your hand and hits your head.") - user.take_organ_damage(10) - user.Paralyse(20) - return - - if(M.stat !=2) - if((istype(M, /mob/living/carbon/human) && prob(60))) - bless(M) - for(var/mob/O in viewers(M, null)) - O.show_message(text("[] heals [] with the power of [src.deity_name]!", user, M), 1) - to_chat(M, "May the power of [src.deity_name] compel you to be healed!") - playsound(src.loc, "punch", 25, 1, -1) - else - if(ishuman(M) && !istype(M:head, /obj/item/clothing/head/helmet)) - M.adjustBrainLoss(10) - to_chat(M, "You feel dumber.") - for(var/mob/O in viewers(M, null)) - O.show_message(text("[] beats [] over the head with []!", user, M, src), 1) - playsound(src.loc, "punch", 25, 1, -1) - else if(M.stat == 2) - for(var/mob/O in viewers(M, null)) - O.show_message(text("[] smacks []'s lifeless corpse with [].", user, M, src), 1) - playsound(src.loc, "punch", 25, 1, -1) - return - -/obj/item/storage/bible/afterattack(atom/A, mob/user as mob, proximity) - if(!proximity) - return - if(istype(A, /turf/simulated/floor)) - to_chat(user, "You hit the floor with the bible.") - if(user.mind && (user.mind.isholy)) - for(var/obj/effect/rune/R in A) - if(R.invisibility) - R.talismanreveal() - if(user.mind && (user.mind.isholy)) - if(A.reagents && A.reagents.has_reagent("water")) //blesses all the water in the holder - to_chat(user, "You bless [A].") - var/water2holy = A.reagents.get_reagent_amount("water") - A.reagents.del_reagent("water") - A.reagents.add_reagent("holywater",water2holy) - if(A.reagents && A.reagents.has_reagent("unholywater")) //yeah yeah, copy pasted code - sue me - to_chat(user, "You purify [A].") - var/unholy2clean = A.reagents.get_reagent_amount("unholywater") - A.reagents.del_reagent("unholywater") - A.reagents.add_reagent("holywater",unholy2clean) - -/obj/item/storage/bible/attackby(obj/item/W as obj, mob/user as mob, params) - playsound(src.loc, "rustle", 50, 1, -5) - ..() +/obj/item/storage/bible + name = "bible" + desc = "Apply to head repeatedly." + icon_state ="bible" + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL + resistance_flags = FIRE_PROOF + var/mob/affecting = null + var/deity_name = "Christ" + +/obj/item/storage/bible/suicide_act(mob/user) + to_chat(viewers(user), "[user] stares into [src.name] and attempts to transcend understanding of the universe!") + user.dust() + return OBLITERATION + +/obj/item/storage/bible/fart_act(mob/living/M) + if(QDELETED(M) || M.stat == DEAD) + return + M.visible_message("[M] farts on \the [name]!") + M.visible_message("A mysterious force smites [M]!") + M.suiciding = TRUE + do_sparks(3, 1, M) + M.gib() + return TRUE // Don't run the fart emote + +/obj/item/storage/bible/booze + name = "bible" + desc = "To be applied to the head repeatedly." + icon_state ="bible" + +/obj/item/storage/bible/booze/New() + ..() + new /obj/item/reagent_containers/food/drinks/cans/beer(src) + new /obj/item/reagent_containers/food/drinks/cans/beer(src) + new /obj/item/stack/spacecash(src) + new /obj/item/stack/spacecash(src) + new /obj/item/stack/spacecash(src) +//BS12 EDIT + // All cult functionality moved to Null Rod +/obj/item/storage/bible/proc/bless(mob/living/carbon/M as mob) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/heal_amt = 10 + for(var/obj/item/organ/external/affecting in H.bodyparts) + if(affecting.heal_damage(heal_amt, heal_amt)) + H.UpdateDamageIcon() + return + +/obj/item/storage/bible/attack(mob/living/M as mob, mob/living/user as mob) + add_attack_logs(user, M, "Hit with [src]") + if(!iscarbon(user)) + M.LAssailant = null + else + M.LAssailant = user + + if(!(istype(user, /mob/living/carbon/human) || SSticker) && SSticker.mode.name != "monkey") + to_chat(user, "You don't have the dexterity to do this!") + return + if(!user.mind || !user.mind.isholy) + to_chat(user, "The book sizzles in your hands.") + user.take_organ_damage(0,10) + return + + if((CLUMSY in user.mutations) && prob(50)) + to_chat(user, "The [src] slips out of your hand and hits your head.") + user.take_organ_damage(10) + user.Paralyse(20) + return + + if(M.stat !=2) + if((istype(M, /mob/living/carbon/human) && prob(60))) + bless(M) + for(var/mob/O in viewers(M, null)) + O.show_message(text("[] heals [] with the power of [src.deity_name]!", user, M), 1) + to_chat(M, "May the power of [src.deity_name] compel you to be healed!") + playsound(src.loc, "punch", 25, 1, -1) + else + if(ishuman(M) && !istype(M:head, /obj/item/clothing/head/helmet)) + M.adjustBrainLoss(10) + to_chat(M, "You feel dumber.") + for(var/mob/O in viewers(M, null)) + O.show_message(text("[] beats [] over the head with []!", user, M, src), 1) + playsound(src.loc, "punch", 25, 1, -1) + else if(M.stat == 2) + for(var/mob/O in viewers(M, null)) + O.show_message(text("[] smacks []'s lifeless corpse with [].", user, M, src), 1) + playsound(src.loc, "punch", 25, 1, -1) + return + +/obj/item/storage/bible/afterattack(atom/A, mob/user as mob, proximity) + if(!proximity) + return + if(istype(A, /turf/simulated/floor)) + to_chat(user, "You hit the floor with the bible.") + if(user.mind && (user.mind.isholy)) + for(var/obj/effect/rune/R in A) + if(R.invisibility) + R.talismanreveal() + if(user.mind && (user.mind.isholy)) + if(A.reagents && A.reagents.has_reagent("water")) //blesses all the water in the holder + to_chat(user, "You bless [A].") + var/water2holy = A.reagents.get_reagent_amount("water") + A.reagents.del_reagent("water") + A.reagents.add_reagent("holywater",water2holy) + if(A.reagents && A.reagents.has_reagent("unholywater")) //yeah yeah, copy pasted code - sue me + to_chat(user, "You purify [A].") + var/unholy2clean = A.reagents.get_reagent_amount("unholywater") + A.reagents.del_reagent("unholywater") + A.reagents.add_reagent("holywater",unholy2clean) + +/obj/item/storage/bible/attackby(obj/item/W as obj, mob/user as mob, params) + playsound(src.loc, "rustle", 50, 1, -5) + ..() diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm index 93c1a9f0d0a..dceab93330a 100644 --- a/code/game/objects/items/weapons/storage/boxes.dm +++ b/code/game/objects/items/weapons/storage/boxes.dm @@ -1,1130 +1,1130 @@ -/* - * Everything derived from the common cardboard box. - * Basically everything except the original is a kit (starts full). - * - * Contains: - * Empty box, starter boxes (survival/engineer), - * Latex glove and sterile mask boxes, - * Syringe, beaker, dna injector boxes, - * Blanks, flashbangs, and EMP grenade boxes, - * Tracking and chemical implant boxes, - * Prescription glasses and drinking glass boxes, - * Condiment bottle and silly cup boxes, - * Donkpocket and monkeycube boxes, - * ID and security PDA cart boxes, - * Handcuff, mousetrap, and pillbottle boxes, - * Snap-pops and matchboxes, - * Replacement light boxes. - * - * For syndicate call-ins see uplink_kits.dm - */ - -/obj/item/storage/box - name = "box" - desc = "It's just an ordinary box." - icon_state = "box" - item_state = "syringe_kit" - resistance_flags = FLAMMABLE - var/foldable = /obj/item/stack/sheet/cardboard - var/amt = 1 - -/obj/item/storage/box/attack_self(mob/user) - ..() - - if(!foldable) - return - if(contents.len) - to_chat(user, "You can't fold this box with items still inside!") - return - if(!ispath(foldable)) - return - - // Close any open UI windows first - var/found = 0 - for(var/mob/M in range(1)) - if(M.s_active == src) - close(M) - if(M == user) - found = 1 - if(!found) // User is too far away - return - - to_chat(user, "You fold [src] flat.") - var/obj/item/stack/I = new foldable(get_turf(src), amt) - user.put_in_hands(I) - qdel(src) - -/obj/item/storage/box/large - name = "large box" - desc = "You could build a fort with this." - icon_state = "largebox" - w_class = 4 // Big, bulky. - foldable = /obj/item/stack/sheet/cardboard - amt = 4 - storage_slots = 21 - max_combined_w_class = 42 // 21*2 - -/obj/item/storage/box/survival - icon_state = "box_civ" - New() - ..() - contents = list() - new /obj/item/clothing/mask/breath( src ) - new /obj/item/tank/emergency_oxygen( src ) - new /obj/item/reagent_containers/hypospray/autoinjector( src ) - new /obj/item/flashlight/flare/glowstick/emergency( src ) - return - -/obj/item/storage/box/survival_vox - icon_state = "box_vox" - -/obj/item/storage/box/survival_vox/New() - ..() - contents = list() - new /obj/item/clothing/mask/breath/vox(src) - new /obj/item/tank/emergency_oxygen/nitrogen(src) - new /obj/item/reagent_containers/hypospray/autoinjector(src) - new /obj/item/flashlight/flare/glowstick/emergency(src) - -/obj/item/storage/box/survival_plasmaman - icon_state = "box_plasma" - -/obj/item/storage/box/survival_plasmaman/New() - ..() - contents = list() - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/emergency_oxygen/plasma(src) - new /obj/item/reagent_containers/hypospray/autoinjector(src) - new /obj/item/flashlight/flare/glowstick/emergency(src) - -/obj/item/storage/box/engineer - icon_state = "box_eng" - New() - ..() - contents = list() - new /obj/item/clothing/mask/breath( src ) - new /obj/item/tank/emergency_oxygen/engi( src ) - new /obj/item/reagent_containers/hypospray/autoinjector( src ) - new /obj/item/flashlight/flare/glowstick/emergency( src ) - return - -/obj/item/storage/box/survival_mining - icon_state = "box_min" - New() - ..() - contents = list() - new /obj/item/clothing/mask/gas/explorer(src) - new /obj/item/tank/emergency_oxygen/engi(src) - new /obj/item/crowbar/red(src) - new /obj/item/reagent_containers/hypospray/autoinjector(src) - new /obj/item/flashlight/flare/glowstick/emergency(src) - -/obj/item/storage/box/survival_syndi - icon_state = "box_syndi" - New() - ..() - contents = list() - new /obj/item/clothing/mask/gas/syndicate(src) - new /obj/item/tank/emergency_oxygen/syndi(src) - new /obj/item/reagent_containers/hypospray/autoinjector(src) - new /obj/item/reagent_containers/food/pill/initropidril(src) - new /obj/item/flashlight/flare/glowstick/red(src) - -/obj/item/storage/box/gloves - name = "box of latex gloves" - desc = "Contains white gloves." - icon_state = "latex" - - New() - ..() - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/clothing/gloves/color/latex(src) - -/obj/item/storage/box/masks - name = "sterile masks" - desc = "This box contains masks of sterility." - icon_state = "sterile" - - New() - ..() - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - - -/obj/item/storage/box/syringes - name = "syringes" - desc = "A box full of syringes." - desc = "A biohazard alert warning is printed on the box" - icon_state = "syringe" - - New() - ..() - new /obj/item/reagent_containers/syringe( src ) - new /obj/item/reagent_containers/syringe( src ) - new /obj/item/reagent_containers/syringe( src ) - new /obj/item/reagent_containers/syringe( src ) - new /obj/item/reagent_containers/syringe( src ) - new /obj/item/reagent_containers/syringe( src ) - new /obj/item/reagent_containers/syringe( src ) - -/obj/item/storage/box/beakers - name = "beaker box" - icon_state = "beaker" - - New() - ..() - new /obj/item/reagent_containers/glass/beaker( src ) - new /obj/item/reagent_containers/glass/beaker( src ) - new /obj/item/reagent_containers/glass/beaker( src ) - new /obj/item/reagent_containers/glass/beaker( src ) - new /obj/item/reagent_containers/glass/beaker( src ) - new /obj/item/reagent_containers/glass/beaker( src ) - new /obj/item/reagent_containers/glass/beaker( src ) - -/obj/item/storage/box/beakers/bluespace - name = "box of bluespace beakers" - icon_state = "beaker" - -/obj/item/storage/box/beakers/bluespace/New() - ..() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/glass/beaker/bluespace(src) - -/obj/item/storage/box/iv_bags - name = "IV Bags" - desc = "A box full of empty IV bags." - icon_state = "beaker" - -/obj/item/storage/box/iv_bags/New() - ..() - new /obj/item/reagent_containers/iv_bag( src ) - new /obj/item/reagent_containers/iv_bag( src ) - new /obj/item/reagent_containers/iv_bag( src ) - new /obj/item/reagent_containers/iv_bag( src ) - new /obj/item/reagent_containers/iv_bag( src ) - new /obj/item/reagent_containers/iv_bag( src ) - new /obj/item/reagent_containers/iv_bag( src ) - -/obj/item/storage/box/injectors - name = "\improper DNA injectors" - desc = "This box contains injectors it seems." - - New() - ..() - new /obj/item/dnainjector/h2m(src) - new /obj/item/dnainjector/h2m(src) - new /obj/item/dnainjector/h2m(src) - new /obj/item/dnainjector/m2h(src) - new /obj/item/dnainjector/m2h(src) - new /obj/item/dnainjector/m2h(src) - -/obj/item/storage/box/slug - name = "Ammunition Box (Slug)" - desc = "A small box capable of holding seven shotgun shells." - icon_state = "slugbox" - -/obj/item/storage/box/slug/New() - ..() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun(src) - - -/obj/item/storage/box/buck - name = "Ammunition Box (Buckshot)" - desc = "A small box capable of holding seven shotgun shells." - icon_state = "buckshotbox" - -/obj/item/storage/box/buck/New() - ..() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/buckshot(src) - -/obj/item/storage/box/dragonsbreath - name = "Ammunition Box (Dragonsbreath)" - desc = "A small box capable of holding seven shotgun shells." - icon_state = "dragonsbreathbox" - -/obj/item/storage/box/dragonsbreath/New() - ..() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/incendiary/dragonsbreath(src) - -/obj/item/storage/box/stun - name = "Ammunition Box (Stun shells)" - desc = "A small box capable of holding seven shotgun shells." - icon_state = "stunbox" - -/obj/item/storage/box/stun/New() - ..() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/stunslug(src) - -/obj/item/storage/box/beanbag - name = "Ammunition Box (Beanbag shells)" - desc = "A small box capable of holding seven shotgun shells." - icon_state = "beanbagbox" - -/obj/item/storage/box/beanbag/New() - ..() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/beanbag(src) - -/obj/item/storage/box/rubbershot - name = "Ammunition Box (Rubbershot shells)" - desc = "A small box capable of holding seven shotgun shells." - icon_state = "rubbershotbox" - -/obj/item/storage/box/rubbershot/New() - ..() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/rubbershot(src) - -/obj/item/storage/box/tranquilizer - name = "Ammunition Box (Tranquilizer darts)" - desc = "A small box capable of holding seven shotgun shells." - icon_state = "tranqbox" - -/obj/item/storage/box/tranquilizer/New() - ..() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/tranquilizer(src) - -/obj/item/storage/box/flashbangs - name = "box of flashbangs (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause blindness or deafness in repeated use." - icon_state = "flashbang" - - New() - ..() - new /obj/item/grenade/flashbang(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/grenade/flashbang(src) - -/obj/item/storage/box/flashes - name = "box of flashbulbs" - desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." - icon_state = "flashbang" - - New() - ..() - new /obj/item/flash(src) - new /obj/item/flash(src) - new /obj/item/flash(src) - new /obj/item/flash(src) - new /obj/item/flash(src) - new /obj/item/flash(src) - -/obj/item/storage/box/teargas - name = "box of tear gas grenades (WARNING)" - desc = "WARNING: These devices are extremely dangerous and can cause blindness and skin irritation." - icon_state = "flashbang" - -/obj/item/storage/box/teargas/New() - ..() - new /obj/item/grenade/chem_grenade/teargas(src) - new /obj/item/grenade/chem_grenade/teargas(src) - new /obj/item/grenade/chem_grenade/teargas(src) - new /obj/item/grenade/chem_grenade/teargas(src) - new /obj/item/grenade/chem_grenade/teargas(src) - new /obj/item/grenade/chem_grenade/teargas(src) - new /obj/item/grenade/chem_grenade/teargas(src) - -/obj/item/storage/box/emps - name = "emp grenades" - desc = "A box with 5 emp grenades." - icon_state = "flashbang" - - New() - ..() - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - - -/obj/item/storage/box/trackimp - name = "tracking implant kit" - desc = "Box full of scum-bag tracking utensils." - icon_state = "implant" - - New() - ..() - new /obj/item/implantcase/tracking(src) - new /obj/item/implantcase/tracking(src) - new /obj/item/implantcase/tracking(src) - new /obj/item/implantcase/tracking(src) - new /obj/item/implanter(src) - new /obj/item/implantpad(src) - new /obj/item/locator(src) - -/obj/item/storage/box/minertracker - name = "boxed tracking implant kit" - desc = "For finding those who have died on the accursed lavaworld." - icon_state = "implant" - -/obj/item/storage/box/minertracker/New() - ..() - new /obj/item/implantcase/tracking(src) - new /obj/item/implantcase/tracking(src) - new /obj/item/implantcase/tracking(src) - new /obj/item/implanter(src) - new /obj/item/implantpad(src) - new /obj/item/locator(src) - -/obj/item/storage/box/chemimp - name = "chemical implant kit" - desc = "Box of stuff used to implant chemicals." - icon_state = "implant" - - New() - ..() - new /obj/item/implantcase/chem(src) - new /obj/item/implantcase/chem(src) - new /obj/item/implantcase/chem(src) - new /obj/item/implantcase/chem(src) - new /obj/item/implantcase/chem(src) - new /obj/item/implanter(src) - new /obj/item/implantpad(src) - -/obj/item/storage/box/exileimp - name = "boxed exile implant kit" - desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway." - icon_state = "implant" - - New() - ..() - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implanter(src) - -/obj/item/storage/box/deathimp - name = "death alarm implant kit" - desc = "Box of life sign monitoring implants." - icon_state = "implant" - - New() - ..() - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implanter(src) - -/obj/item/storage/box/tapes - name = "Tape Box" - desc = "A box of spare recording tapes" - icon_state = "box" - - New() - ..() - new /obj/item/tape(src) - new /obj/item/tape(src) - new /obj/item/tape(src) - new /obj/item/tape(src) - new /obj/item/tape(src) - new /obj/item/tape(src) - -/obj/item/storage/box/rxglasses - name = "prescription glasses" - desc = "This box contains nerd glasses." - icon_state = "glasses" - - New() - ..() - new /obj/item/clothing/glasses/regular(src) - new /obj/item/clothing/glasses/regular(src) - new /obj/item/clothing/glasses/regular(src) - new /obj/item/clothing/glasses/regular(src) - new /obj/item/clothing/glasses/regular(src) - new /obj/item/clothing/glasses/regular(src) - new /obj/item/clothing/glasses/regular(src) - -/obj/item/storage/box/drinkingglasses - name = "box of drinking glasses" - desc = "It has a picture of drinking glasses on it." - - New() - ..() - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - new /obj/item/reagent_containers/food/drinks/drinkingglass(src) - -/obj/item/storage/box/cdeathalarm_kit - name = "Death Alarm Kit" - desc = "Box of stuff used to implant death alarms." - icon_state = "implant" - item_state = "syringe_kit" - - New() - ..() - new /obj/item/implanter(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - new /obj/item/implantcase/death_alarm(src) - -/obj/item/storage/box/condimentbottles - name = "box of condiment bottles" - desc = "It has a large ketchup smear on it." - - New() - ..() - new /obj/item/reagent_containers/food/condiment(src) - new /obj/item/reagent_containers/food/condiment(src) - new /obj/item/reagent_containers/food/condiment(src) - new /obj/item/reagent_containers/food/condiment(src) - new /obj/item/reagent_containers/food/condiment(src) - new /obj/item/reagent_containers/food/condiment(src) - - - -/obj/item/storage/box/cups - name = "box of paper cups" - desc = "It has pictures of paper cups on the front." - New() - ..() - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - new /obj/item/reagent_containers/food/drinks/sillycup( src ) - - -/obj/item/storage/box/donkpockets - name = "box of donk-pockets" - desc = "Instructions: Heat in microwave. Product will cool if not eaten within seven minutes." - icon_state = "donk_kit" - - New() - ..() - new /obj/item/reagent_containers/food/snacks/donkpocket(src) - new /obj/item/reagent_containers/food/snacks/donkpocket(src) - new /obj/item/reagent_containers/food/snacks/donkpocket(src) - new /obj/item/reagent_containers/food/snacks/donkpocket(src) - new /obj/item/reagent_containers/food/snacks/donkpocket(src) - new /obj/item/reagent_containers/food/snacks/donkpocket(src) - -/obj/item/storage/box/syndidonkpockets - name = "box of donk-pockets" - desc = "This box feels slightly warm" - icon_state = "donk_kit" - - New() - ..() - new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) - new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) - new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) - new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) - new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) - new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) - -/obj/item/storage/box/monkeycubes - name = "monkey cube box" - desc = "Drymate brand monkey cubes. Just add water!" - icon = 'icons/obj/food/food.dmi' - icon_state = "monkeycubebox" - storage_slots = 7 - can_hold = list(/obj/item/reagent_containers/food/snacks/monkeycube) - var/monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube - -/obj/item/storage/box/monkeycubes/New() - ..() - for(var/i in 1 to 5) - new monkey_cube_type(src) - -/obj/item/storage/box/monkeycubes/syndicate - desc = "Waffle Co. brand monkey cubes. Just add water and a dash of subterfuge!" - monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/syndicate - -/obj/item/storage/box/monkeycubes/farwacubes - name = "farwa cube box" - desc = "Drymate brand farwa cubes. Just add water!" - monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/farwacube - -/obj/item/storage/box/monkeycubes/stokcubes - name = "stok cube box" - desc = "Drymate brand stok cubes. Just add water!" - monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/stokcube - -/obj/item/storage/box/monkeycubes/neaeracubes - name = "neaera cube box" - desc = "Drymate brand neaera cubes. Just add water!" - monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/neaeracube - -/obj/item/storage/box/monkeycubes/wolpincubes - name = "wolpin cube box" - desc = "Drymate brand wolpin cubes. Just add water!" - monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/wolpincube - -/obj/item/storage/box/permits - name = "box of construction permits" - desc = "A box for containing construction permits, used to officially declare built rooms as additions to the station." - icon_state = "id" - -/obj/item/storage/box/permits/New() - ..() - new /obj/item/areaeditor/permit(src) - new /obj/item/areaeditor/permit(src) - new /obj/item/areaeditor/permit(src) - new /obj/item/areaeditor/permit(src) - new /obj/item/areaeditor/permit(src) - new /obj/item/areaeditor/permit(src) - new /obj/item/areaeditor/permit(src) - - -/obj/item/storage/box/ids - name = "spare IDs" - desc = "Has so many empty IDs." - icon_state = "id" - - New() - ..() - new /obj/item/card/id(src) - new /obj/item/card/id(src) - new /obj/item/card/id(src) - new /obj/item/card/id(src) - new /obj/item/card/id(src) - new /obj/item/card/id(src) - new /obj/item/card/id(src) - -/obj/item/storage/box/prisoner - name = "prisoner IDs" - desc = "Take away their last shred of dignity, their name." - icon_state = "id" - - New() - ..() - new /obj/item/card/id/prisoner/one(src) - new /obj/item/card/id/prisoner/two(src) - new /obj/item/card/id/prisoner/three(src) - new /obj/item/card/id/prisoner/four(src) - new /obj/item/card/id/prisoner/five(src) - new /obj/item/card/id/prisoner/six(src) - new /obj/item/card/id/prisoner/seven(src) - -/obj/item/storage/box/seccarts - name = "spare R.O.B.U.S.T. Cartridges" - desc = "A box full of R.O.B.U.S.T. Cartridges, used by Security." - icon_state = "pda" - - New() - ..() - new /obj/item/cartridge/security(src) - new /obj/item/cartridge/security(src) - new /obj/item/cartridge/security(src) - new /obj/item/cartridge/security(src) - new /obj/item/cartridge/security(src) - new /obj/item/cartridge/security(src) - new /obj/item/cartridge/security(src) - -/obj/item/storage/box/holobadge - name = "holobadge box" - icon_state = "box_badge" - desc = "A box claiming to contain holobadges." - New() - new /obj/item/clothing/accessory/holobadge(src) - new /obj/item/clothing/accessory/holobadge(src) - new /obj/item/clothing/accessory/holobadge(src) - new /obj/item/clothing/accessory/holobadge(src) - new /obj/item/clothing/accessory/holobadge/cord(src) - new /obj/item/clothing/accessory/holobadge/cord(src) - ..() - return - -/obj/item/storage/box/evidence - name = "evidence bag box" - desc = "A box claiming to contain evidence bags." - icon_state = "box_evidence" - -/obj/item/storage/box/evidence/New() - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - ..() - -/obj/item/storage/box/handcuffs - name = "spare handcuffs" - desc = "A box full of handcuffs." - icon_state = "handcuff" - - New() - ..() - new /obj/item/restraints/handcuffs(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/restraints/handcuffs(src) - -/obj/item/storage/box/zipties - name = "box of spare zipties" - desc = "A box full of zipties." - icon_state = "handcuff" - - New() - ..() - new /obj/item/restraints/handcuffs/cable/zipties(src) - new /obj/item/restraints/handcuffs/cable/zipties(src) - new /obj/item/restraints/handcuffs/cable/zipties(src) - new /obj/item/restraints/handcuffs/cable/zipties(src) - new /obj/item/restraints/handcuffs/cable/zipties(src) - new /obj/item/restraints/handcuffs/cable/zipties(src) - new /obj/item/restraints/handcuffs/cable/zipties(src) - -/obj/item/storage/box/alienhandcuffs - name = "box of spare handcuffs" - desc = "A box full of handcuffs." - icon_state = "alienboxCuffs" - - New() - ..() - for(var/i in 1 to 7) - new /obj/item/restraints/handcuffs/alien(src) - -/obj/item/storage/box/fakesyndiesuit - name = "boxed space suit and helmet" - desc = "A sleek, sturdy box used to hold replica spacesuits." - icon_state = "box_of_doom" - - New() - ..() - new /obj/item/clothing/head/syndicatefake(src) - new /obj/item/clothing/suit/syndicatefake(src) - -/obj/item/storage/box/enforcer_rubber - name = "enforcer pistol kit (rubber)" - desc = "A box marked with pictures of an enforcer pistol, two ammo clips, and the word 'NON-LETHAL'." - icon_state = "box_ert" - -/obj/item/storage/box/enforcer_rubber/New() - ..() - new /obj/item/gun/projectile/automatic/pistol/enforcer(src) // loaded with rubber by default - new /obj/item/ammo_box/magazine/enforcer(src) - new /obj/item/ammo_box/magazine/enforcer(src) - -/obj/item/storage/box/enforcer_lethal - name = "enforcer pistol kit (lethal)" - desc = "A box marked with pictures of an enforcer pistol, two ammo clips, and the word 'LETHAL'." - icon_state = "box_ert" - -/obj/item/storage/box/enforcer_lethal/New() - ..() - new /obj/item/gun/projectile/automatic/pistol/enforcer/lethal(src) - new /obj/item/ammo_box/magazine/enforcer/lethal(src) - new /obj/item/ammo_box/magazine/enforcer/lethal(src) - -/obj/item/storage/box/bartender_rare_ingredients_kit - name = "bartender rare reagents kit" - desc = "A box intended for experienced bartenders." - -/obj/item/storage/box/bartender_rare_ingredients_kit/New() - ..() - var/list/reagent_list = list("sacid", "radium", "ether", "methamphetamine", "plasma", "gold", "silver", "capsaicin", "psilocybin") - for(var/reag in reagent_list) - var/obj/item/reagent_containers/glass/bottle/B = new(src) - B.reagents.add_reagent(reag, 30) - B.name = "[reag] bottle" - -/obj/item/storage/box/chef_rare_ingredients_kit - name = "chef rare reagents kit" - desc = "A box intended for experienced chefs." - -/obj/item/storage/box/chef_rare_ingredients_kit/New() - ..() - new /obj/item/reagent_containers/food/condiment/soysauce(src) - new /obj/item/reagent_containers/food/condiment/enzyme(src) - new /obj/item/reagent_containers/food/condiment/pack/hotsauce(src) - new /obj/item/kitchen/knife/butcher(src) - var/list/reagent_list = list("msg", "triple_citrus", "salglu_solution", "nutriment", "gravy", "honey", "vitfro") - for(var/reag in reagent_list) - var/obj/item/reagent_containers/glass/bottle/B = new(src) - B.reagents.add_reagent(reag, 30) - B.name = "[reag] bottle" - -/obj/item/storage/box/mousetraps - name = "box of Pest-B-Gon mousetraps" - desc = "WARNING: Keep out of reach of children." - icon_state = "mousetraps" - - New() - ..() - new /obj/item/assembly/mousetrap( src ) - new /obj/item/assembly/mousetrap( src ) - new /obj/item/assembly/mousetrap( src ) - new /obj/item/assembly/mousetrap( src ) - new /obj/item/assembly/mousetrap( src ) - new /obj/item/assembly/mousetrap( src ) - -/obj/item/storage/box/pillbottles - name = "box of pill bottles" - desc = "It has pictures of pill bottles on its front." - - New() - ..() - new /obj/item/storage/pill_bottle( src ) - new /obj/item/storage/pill_bottle( src ) - new /obj/item/storage/pill_bottle( src ) - new /obj/item/storage/pill_bottle( src ) - new /obj/item/storage/pill_bottle( src ) - new /obj/item/storage/pill_bottle( src ) - new /obj/item/storage/pill_bottle( src ) - -/obj/item/storage/box/patch_packs - name = "box of patch packs" - desc = "It has pictures of patch packs on its front." - -/obj/item/storage/box/patch_packs/New() - ..() - new /obj/item/storage/pill_bottle/patch_pack(src) - new /obj/item/storage/pill_bottle/patch_pack(src) - new /obj/item/storage/pill_bottle/patch_pack(src) - new /obj/item/storage/pill_bottle/patch_pack(src) - new /obj/item/storage/pill_bottle/patch_pack(src) - new /obj/item/storage/pill_bottle/patch_pack(src) - new /obj/item/storage/pill_bottle/patch_pack(src) - -/obj/item/storage/box/bodybags - name = "body bags" - desc = "This box contains body bags." - icon_state = "bodybags" - -/obj/item/storage/box/bodybags/New() - ..() - new /obj/item/bodybag(src) - new /obj/item/bodybag(src) - new /obj/item/bodybag(src) - new /obj/item/bodybag(src) - new /obj/item/bodybag(src) - new /obj/item/bodybag(src) - new /obj/item/bodybag(src) - -/obj/item/storage/box/snappops - name = "snap pop box" - desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children." - icon = 'icons/obj/toy.dmi' - icon_state = "spbox" - storage_slots = 8 - can_hold = list(/obj/item/toy/snappop) - New() - ..() - for(var/i=1; i <= storage_slots; i++) - new /obj/item/toy/snappop(src) - -/obj/item/storage/box/matches - name = "matchbox" - desc = "A small box of Almost But Not Quite Plasma Premium Matches." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "matchbox" - item_state = "zippo" - storage_slots = 10 - w_class = WEIGHT_CLASS_TINY - max_w_class = WEIGHT_CLASS_TINY - slot_flags = SLOT_BELT - can_hold = list(/obj/item/match) - -/obj/item/storage/box/matches/New() - ..() - for(var/i in 1 to storage_slots) - new /obj/item/match(src) - -/obj/item/storage/box/matches/attackby(obj/item/match/W, mob/user, params) - if(istype(W, /obj/item/match) && !W.lit) - W.matchignite() - playsound(user.loc, 'sound/goonstation/misc/matchstick_light.ogg', 50, 1) - return - -/obj/item/storage/box/autoinjectors - name = "box of injectors" - desc = "Contains autoinjectors." - icon_state = "syringe" - New() - ..() - for(var/i; i < storage_slots; i++) - new /obj/item/reagent_containers/hypospray/autoinjector(src) - -/obj/item/storage/box/autoinjector/utility - name = "autoinjector kit" - desc = "A box with several utility autoinjectors for the economical miner." - icon_state = "syringe" - - New() - ..() - new /obj/item/reagent_containers/hypospray/autoinjector/teporone(src) - new /obj/item/reagent_containers/hypospray/autoinjector/teporone(src) - new /obj/item/reagent_containers/hypospray/autoinjector/stimpack(src) - new /obj/item/reagent_containers/hypospray/autoinjector/stimpack(src) - new /obj/item/reagent_containers/hypospray/autoinjector/stimpack(src) - -/obj/item/storage/box/lights - name = "replacement bulbs" - icon = 'icons/obj/storage.dmi' - icon_state = "light" - desc = "This box is shaped on the inside so that only light tubes and bulbs fit." - item_state = "syringe_kit" - foldable = /obj/item/stack/sheet/cardboard - storage_slots=21 - can_hold = list(/obj/item/light/tube, /obj/item/light/bulb) - max_combined_w_class = 21 - use_to_pickup = 1 // for picking up broken bulbs, not that most people will try - -/obj/item/storage/box/lights/bulbs/New() - ..() - for(var/i = 0; i < 21; i++) - new /obj/item/light/bulb(src) - -/obj/item/storage/box/lights/tubes - name = "replacement tubes" - icon_state = "lighttube" - -/obj/item/storage/box/lights/tubes/New() - ..() - for(var/i = 0; i < 21; i++) - new /obj/item/light/tube(src) - -/obj/item/storage/box/lights/mixed - name = "replacement lights" - icon_state = "lightmixed" - -/obj/item/storage/box/lights/mixed/New() - ..() - for(var/i = 0; i < 14; i++) - new /obj/item/light/tube(src) - for(var/i = 0; i < 7; i++) - new /obj/item/light/bulb(src) - -/obj/item/storage/box/barber - name = "Barber Starter Kit" - desc = "For all hairstyling needs." - icon_state = "implant" - -/obj/item/storage/box/barber/New() - ..() - new /obj/item/scissors/barber(src) - new /obj/item/hair_dye_bottle(src) - new /obj/item/reagent_containers/glass/bottle/reagent/hairgrownium(src) - new /obj/item/reagent_containers/glass/bottle/reagent/hair_dye(src) - new /obj/item/reagent_containers/glass/bottle/reagent(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/clothing/mask/fakemoustache(src) //totally necessary for successful barbering -Fox - -/obj/item/storage/box/lip_stick - name = "Lipstick Kit" - desc = "For all your lip coloring needs." - icon_state = "implant" - -/obj/item/storage/box/lip_stick/New() - ..() - new /obj/item/lipstick(src) - new /obj/item/lipstick/purple(src) - new /obj/item/lipstick/jade(src) - new /obj/item/lipstick/black(src) - new /obj/item/lipstick/green(src) - new /obj/item/lipstick/blue(src) - new /obj/item/lipstick/white(src) - -#define NODESIGN "None" -#define NANOTRASEN "NanotrasenStandard" -#define SYNDI "SyndiSnacks" -#define HEART "Heart" -#define SMILE "SmileyFace" - -/obj/item/storage/box/papersack - name = "paper sack" - desc = "A sack neatly crafted out of paper." - icon_state = "paperbag_None" - item_state = "paperbag_None" - resistance_flags = FLAMMABLE - foldable = null - var/design = NODESIGN - -/obj/item/storage/box/papersack/update_icon() - if(!contents.len) - icon_state = "[item_state]" - else icon_state = "[item_state]_closed" - -/obj/item/storage/box/papersack/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - //if a pen is used on the sack, dialogue to change its design appears - if(contents.len) - to_chat(user, "You can't modify [src] with items still inside!") - return - var/list/designs = list(NODESIGN, NANOTRASEN, SYNDI, HEART, SMILE) - var/switchDesign = input("Select a Design:", "Paper Sack Design", designs[1]) as null|anything in designs - if(!switchDesign) - return - if(get_dist(usr, src) > 1 && !usr.incapacitated()) - to_chat(usr, "You have moved too far away!") - return - if(design == switchDesign) - return - to_chat(usr, "You make some modifications to [src] using your pen.") - design = switchDesign - icon_state = "paperbag_[design]" - item_state = "paperbag_[design]" - switch(design) - if(NODESIGN) - desc = "A sack neatly crafted out of paper." - if(NANOTRASEN) - desc = "A standard Nanotrasen paper lunch sack for loyal employees on the go." - if(SYNDI) - desc = "The design on this paper sack is a remnant of the notorious 'SyndieSnacks' program." - if(HEART) - desc = "A paper sack with a heart etched onto the side." - if(SMILE) - desc = "A paper sack with a crude smile etched onto the side." - return - else if(is_sharp(W)) - if(!contents.len) - if(item_state == "paperbag_None") - to_chat(user, "You cut eyeholes into [src].") - new /obj/item/clothing/head/papersack(user.loc) - qdel(src) - return - else if(item_state == "paperbag_SmileyFace") - to_chat(user, "You cut eyeholes into [src] and modify the design.") - new /obj/item/clothing/head/papersack/smiley(user.loc) - qdel(src) - return - return ..() - - -/obj/item/storage/box/centcomofficer - name = "officer kit" - icon_state = "box_ert" - storage_slots = 14 - max_combined_w_class = 20 - -/obj/item/storage/box/centcomofficer/New() - ..() - contents = list() - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/emergency_oxygen/double/full(src) - new /obj/item/flashlight/seclite(src) - new /obj/item/kitchen/knife/combat(src) - - new /obj/item/radio/centcom(src) - new /obj/item/door_remote/omni(src) - new /obj/item/implanter/death_alarm(src) - - new /obj/item/reagent_containers/hypospray/combat/nanites(src) - new /obj/item/pinpointer(src) - new /obj/item/pinpointer/crew/centcom(src) - -/obj/item/storage/box/responseteam - name = "boxed survival kit" - icon_state = "box_ert" - -/obj/item/storage/box/responseteam/New() - ..() - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/emergency_oxygen/engi(src) - new /obj/item/flashlight/flare(src) - new /obj/item/kitchen/knife/combat(src) - new /obj/item/radio/centcom(src) - new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) - new /obj/item/reagent_containers/hypospray/autoinjector(src) - -/obj/item/storage/box/clown - name = "clown box" - desc = "A colorful cardboard box for the clown" - icon_state = "box_clown" - -/obj/item/storage/box/emptysandbags - name = "box of empty sandbags" - -/obj/item/storage/box/emptysandbags/New() - ..() - contents = list() - for(var/i in 1 to 7) - new /obj/item/emptysandbag(src) - -/obj/item/storage/box/rndboards - name = "the Liberator's legacy" - desc = "A box containing a gift for worthy golems." - -/obj/item/storage/box/rndboards/New() - ..() - contents = list() - new /obj/item/circuitboard/protolathe(src) - new /obj/item/circuitboard/destructive_analyzer(src) - new /obj/item/circuitboard/circuit_imprinter(src) - new /obj/item/circuitboard/rdconsole/public(src) - -/obj/item/storage/box/stockparts/basic //for ruins where it's a bad idea to give access to an autolathe/protolathe, but still want to make stock parts accessible - name = "box of stock parts" - desc = "Contains a variety of basic stock parts." - -/obj/item/storage/box/stockparts/basic/New() - ..() - for(var/i in 1 to 3) - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/manipulator(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/matter_bin(src) - -/obj/item/storage/box/stockparts/deluxe - name = "box of deluxe stock parts" - desc = "Contains a variety of deluxe stock parts." - -/obj/item/storage/box/stockparts/deluxe/New() - ..() - for(var/i in 1 to 3) - new /obj/item/stock_parts/capacitor/quadratic(src) - new /obj/item/stock_parts/scanning_module/triphasic(src) - new /obj/item/stock_parts/manipulator/femto(src) - new /obj/item/stock_parts/micro_laser/quadultra(src) - new /obj/item/stock_parts/matter_bin/bluespace(src) - -/obj/item/storage/box/hug - name = "box of hugs" - desc = "A special box for sensitive people." - icon_state = "hugbox" - foldable = null - -/obj/item/storage/box/hug/suicide_act(mob/user) - user.visible_message("[user] clamps the box of hugs on [user.p_their()] jugular! Guess it wasn't such a hugbox after all..") - return (BRUTELOSS) - -/obj/item/storage/box/hug/attack_self(mob/user) - ..() - user.changeNext_move(CLICK_CD_MELEE) - playsound(loc, "rustle", 50, 1, -5) - user.visible_message("[user] hugs \the [src].","You hug \the [src].") - -#undef NODESIGN -#undef NANOTRASEN -#undef SYNDI -#undef HEART -#undef SMILE +/* + * Everything derived from the common cardboard box. + * Basically everything except the original is a kit (starts full). + * + * Contains: + * Empty box, starter boxes (survival/engineer), + * Latex glove and sterile mask boxes, + * Syringe, beaker, dna injector boxes, + * Blanks, flashbangs, and EMP grenade boxes, + * Tracking and chemical implant boxes, + * Prescription glasses and drinking glass boxes, + * Condiment bottle and silly cup boxes, + * Donkpocket and monkeycube boxes, + * ID and security PDA cart boxes, + * Handcuff, mousetrap, and pillbottle boxes, + * Snap-pops and matchboxes, + * Replacement light boxes. + * + * For syndicate call-ins see uplink_kits.dm + */ + +/obj/item/storage/box + name = "box" + desc = "It's just an ordinary box." + icon_state = "box" + item_state = "syringe_kit" + resistance_flags = FLAMMABLE + var/foldable = /obj/item/stack/sheet/cardboard + var/amt = 1 + +/obj/item/storage/box/attack_self(mob/user) + ..() + + if(!foldable) + return + if(contents.len) + to_chat(user, "You can't fold this box with items still inside!") + return + if(!ispath(foldable)) + return + + // Close any open UI windows first + var/found = 0 + for(var/mob/M in range(1)) + if(M.s_active == src) + close(M) + if(M == user) + found = 1 + if(!found) // User is too far away + return + + to_chat(user, "You fold [src] flat.") + var/obj/item/stack/I = new foldable(get_turf(src), amt) + user.put_in_hands(I) + qdel(src) + +/obj/item/storage/box/large + name = "large box" + desc = "You could build a fort with this." + icon_state = "largebox" + w_class = 4 // Big, bulky. + foldable = /obj/item/stack/sheet/cardboard + amt = 4 + storage_slots = 21 + max_combined_w_class = 42 // 21*2 + +/obj/item/storage/box/survival + icon_state = "box_civ" + New() + ..() + contents = list() + new /obj/item/clothing/mask/breath( src ) + new /obj/item/tank/emergency_oxygen( src ) + new /obj/item/reagent_containers/hypospray/autoinjector( src ) + new /obj/item/flashlight/flare/glowstick/emergency( src ) + return + +/obj/item/storage/box/survival_vox + icon_state = "box_vox" + +/obj/item/storage/box/survival_vox/New() + ..() + contents = list() + new /obj/item/clothing/mask/breath/vox(src) + new /obj/item/tank/emergency_oxygen/nitrogen(src) + new /obj/item/reagent_containers/hypospray/autoinjector(src) + new /obj/item/flashlight/flare/glowstick/emergency(src) + +/obj/item/storage/box/survival_plasmaman + icon_state = "box_plasma" + +/obj/item/storage/box/survival_plasmaman/New() + ..() + contents = list() + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/emergency_oxygen/plasma(src) + new /obj/item/reagent_containers/hypospray/autoinjector(src) + new /obj/item/flashlight/flare/glowstick/emergency(src) + +/obj/item/storage/box/engineer + icon_state = "box_eng" + New() + ..() + contents = list() + new /obj/item/clothing/mask/breath( src ) + new /obj/item/tank/emergency_oxygen/engi( src ) + new /obj/item/reagent_containers/hypospray/autoinjector( src ) + new /obj/item/flashlight/flare/glowstick/emergency( src ) + return + +/obj/item/storage/box/survival_mining + icon_state = "box_min" + New() + ..() + contents = list() + new /obj/item/clothing/mask/gas/explorer(src) + new /obj/item/tank/emergency_oxygen/engi(src) + new /obj/item/crowbar/red(src) + new /obj/item/reagent_containers/hypospray/autoinjector(src) + new /obj/item/flashlight/flare/glowstick/emergency(src) + +/obj/item/storage/box/survival_syndi + icon_state = "box_syndi" + New() + ..() + contents = list() + new /obj/item/clothing/mask/gas/syndicate(src) + new /obj/item/tank/emergency_oxygen/syndi(src) + new /obj/item/reagent_containers/hypospray/autoinjector(src) + new /obj/item/reagent_containers/food/pill/initropidril(src) + new /obj/item/flashlight/flare/glowstick/red(src) + +/obj/item/storage/box/gloves + name = "box of latex gloves" + desc = "Contains white gloves." + icon_state = "latex" + + New() + ..() + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/clothing/gloves/color/latex(src) + +/obj/item/storage/box/masks + name = "sterile masks" + desc = "This box contains masks of sterility." + icon_state = "sterile" + + New() + ..() + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + + +/obj/item/storage/box/syringes + name = "syringes" + desc = "A box full of syringes." + desc = "A biohazard alert warning is printed on the box" + icon_state = "syringe" + + New() + ..() + new /obj/item/reagent_containers/syringe( src ) + new /obj/item/reagent_containers/syringe( src ) + new /obj/item/reagent_containers/syringe( src ) + new /obj/item/reagent_containers/syringe( src ) + new /obj/item/reagent_containers/syringe( src ) + new /obj/item/reagent_containers/syringe( src ) + new /obj/item/reagent_containers/syringe( src ) + +/obj/item/storage/box/beakers + name = "beaker box" + icon_state = "beaker" + + New() + ..() + new /obj/item/reagent_containers/glass/beaker( src ) + new /obj/item/reagent_containers/glass/beaker( src ) + new /obj/item/reagent_containers/glass/beaker( src ) + new /obj/item/reagent_containers/glass/beaker( src ) + new /obj/item/reagent_containers/glass/beaker( src ) + new /obj/item/reagent_containers/glass/beaker( src ) + new /obj/item/reagent_containers/glass/beaker( src ) + +/obj/item/storage/box/beakers/bluespace + name = "box of bluespace beakers" + icon_state = "beaker" + +/obj/item/storage/box/beakers/bluespace/New() + ..() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/glass/beaker/bluespace(src) + +/obj/item/storage/box/iv_bags + name = "IV Bags" + desc = "A box full of empty IV bags." + icon_state = "beaker" + +/obj/item/storage/box/iv_bags/New() + ..() + new /obj/item/reagent_containers/iv_bag( src ) + new /obj/item/reagent_containers/iv_bag( src ) + new /obj/item/reagent_containers/iv_bag( src ) + new /obj/item/reagent_containers/iv_bag( src ) + new /obj/item/reagent_containers/iv_bag( src ) + new /obj/item/reagent_containers/iv_bag( src ) + new /obj/item/reagent_containers/iv_bag( src ) + +/obj/item/storage/box/injectors + name = "\improper DNA injectors" + desc = "This box contains injectors it seems." + + New() + ..() + new /obj/item/dnainjector/h2m(src) + new /obj/item/dnainjector/h2m(src) + new /obj/item/dnainjector/h2m(src) + new /obj/item/dnainjector/m2h(src) + new /obj/item/dnainjector/m2h(src) + new /obj/item/dnainjector/m2h(src) + +/obj/item/storage/box/slug + name = "Ammunition Box (Slug)" + desc = "A small box capable of holding seven shotgun shells." + icon_state = "slugbox" + +/obj/item/storage/box/slug/New() + ..() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun(src) + + +/obj/item/storage/box/buck + name = "Ammunition Box (Buckshot)" + desc = "A small box capable of holding seven shotgun shells." + icon_state = "buckshotbox" + +/obj/item/storage/box/buck/New() + ..() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/buckshot(src) + +/obj/item/storage/box/dragonsbreath + name = "Ammunition Box (Dragonsbreath)" + desc = "A small box capable of holding seven shotgun shells." + icon_state = "dragonsbreathbox" + +/obj/item/storage/box/dragonsbreath/New() + ..() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/incendiary/dragonsbreath(src) + +/obj/item/storage/box/stun + name = "Ammunition Box (Stun shells)" + desc = "A small box capable of holding seven shotgun shells." + icon_state = "stunbox" + +/obj/item/storage/box/stun/New() + ..() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/stunslug(src) + +/obj/item/storage/box/beanbag + name = "Ammunition Box (Beanbag shells)" + desc = "A small box capable of holding seven shotgun shells." + icon_state = "beanbagbox" + +/obj/item/storage/box/beanbag/New() + ..() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/beanbag(src) + +/obj/item/storage/box/rubbershot + name = "Ammunition Box (Rubbershot shells)" + desc = "A small box capable of holding seven shotgun shells." + icon_state = "rubbershotbox" + +/obj/item/storage/box/rubbershot/New() + ..() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/rubbershot(src) + +/obj/item/storage/box/tranquilizer + name = "Ammunition Box (Tranquilizer darts)" + desc = "A small box capable of holding seven shotgun shells." + icon_state = "tranqbox" + +/obj/item/storage/box/tranquilizer/New() + ..() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/tranquilizer(src) + +/obj/item/storage/box/flashbangs + name = "box of flashbangs (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause blindness or deafness in repeated use." + icon_state = "flashbang" + + New() + ..() + new /obj/item/grenade/flashbang(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/grenade/flashbang(src) + +/obj/item/storage/box/flashes + name = "box of flashbulbs" + desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." + icon_state = "flashbang" + + New() + ..() + new /obj/item/flash(src) + new /obj/item/flash(src) + new /obj/item/flash(src) + new /obj/item/flash(src) + new /obj/item/flash(src) + new /obj/item/flash(src) + +/obj/item/storage/box/teargas + name = "box of tear gas grenades (WARNING)" + desc = "WARNING: These devices are extremely dangerous and can cause blindness and skin irritation." + icon_state = "flashbang" + +/obj/item/storage/box/teargas/New() + ..() + new /obj/item/grenade/chem_grenade/teargas(src) + new /obj/item/grenade/chem_grenade/teargas(src) + new /obj/item/grenade/chem_grenade/teargas(src) + new /obj/item/grenade/chem_grenade/teargas(src) + new /obj/item/grenade/chem_grenade/teargas(src) + new /obj/item/grenade/chem_grenade/teargas(src) + new /obj/item/grenade/chem_grenade/teargas(src) + +/obj/item/storage/box/emps + name = "emp grenades" + desc = "A box with 5 emp grenades." + icon_state = "flashbang" + + New() + ..() + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + + +/obj/item/storage/box/trackimp + name = "tracking implant kit" + desc = "Box full of scum-bag tracking utensils." + icon_state = "implant" + + New() + ..() + new /obj/item/implantcase/tracking(src) + new /obj/item/implantcase/tracking(src) + new /obj/item/implantcase/tracking(src) + new /obj/item/implantcase/tracking(src) + new /obj/item/implanter(src) + new /obj/item/implantpad(src) + new /obj/item/locator(src) + +/obj/item/storage/box/minertracker + name = "boxed tracking implant kit" + desc = "For finding those who have died on the accursed lavaworld." + icon_state = "implant" + +/obj/item/storage/box/minertracker/New() + ..() + new /obj/item/implantcase/tracking(src) + new /obj/item/implantcase/tracking(src) + new /obj/item/implantcase/tracking(src) + new /obj/item/implanter(src) + new /obj/item/implantpad(src) + new /obj/item/locator(src) + +/obj/item/storage/box/chemimp + name = "chemical implant kit" + desc = "Box of stuff used to implant chemicals." + icon_state = "implant" + + New() + ..() + new /obj/item/implantcase/chem(src) + new /obj/item/implantcase/chem(src) + new /obj/item/implantcase/chem(src) + new /obj/item/implantcase/chem(src) + new /obj/item/implantcase/chem(src) + new /obj/item/implanter(src) + new /obj/item/implantpad(src) + +/obj/item/storage/box/exileimp + name = "boxed exile implant kit" + desc = "Box of exile implants. It has a picture of a clown being booted through the Gateway." + icon_state = "implant" + + New() + ..() + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implanter(src) + +/obj/item/storage/box/deathimp + name = "death alarm implant kit" + desc = "Box of life sign monitoring implants." + icon_state = "implant" + + New() + ..() + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implanter(src) + +/obj/item/storage/box/tapes + name = "Tape Box" + desc = "A box of spare recording tapes" + icon_state = "box" + + New() + ..() + new /obj/item/tape(src) + new /obj/item/tape(src) + new /obj/item/tape(src) + new /obj/item/tape(src) + new /obj/item/tape(src) + new /obj/item/tape(src) + +/obj/item/storage/box/rxglasses + name = "prescription glasses" + desc = "This box contains nerd glasses." + icon_state = "glasses" + + New() + ..() + new /obj/item/clothing/glasses/regular(src) + new /obj/item/clothing/glasses/regular(src) + new /obj/item/clothing/glasses/regular(src) + new /obj/item/clothing/glasses/regular(src) + new /obj/item/clothing/glasses/regular(src) + new /obj/item/clothing/glasses/regular(src) + new /obj/item/clothing/glasses/regular(src) + +/obj/item/storage/box/drinkingglasses + name = "box of drinking glasses" + desc = "It has a picture of drinking glasses on it." + + New() + ..() + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + new /obj/item/reagent_containers/food/drinks/drinkingglass(src) + +/obj/item/storage/box/cdeathalarm_kit + name = "Death Alarm Kit" + desc = "Box of stuff used to implant death alarms." + icon_state = "implant" + item_state = "syringe_kit" + + New() + ..() + new /obj/item/implanter(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + new /obj/item/implantcase/death_alarm(src) + +/obj/item/storage/box/condimentbottles + name = "box of condiment bottles" + desc = "It has a large ketchup smear on it." + + New() + ..() + new /obj/item/reagent_containers/food/condiment(src) + new /obj/item/reagent_containers/food/condiment(src) + new /obj/item/reagent_containers/food/condiment(src) + new /obj/item/reagent_containers/food/condiment(src) + new /obj/item/reagent_containers/food/condiment(src) + new /obj/item/reagent_containers/food/condiment(src) + + + +/obj/item/storage/box/cups + name = "box of paper cups" + desc = "It has pictures of paper cups on the front." + New() + ..() + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + new /obj/item/reagent_containers/food/drinks/sillycup( src ) + + +/obj/item/storage/box/donkpockets + name = "box of donk-pockets" + desc = "Instructions: Heat in microwave. Product will cool if not eaten within seven minutes." + icon_state = "donk_kit" + + New() + ..() + new /obj/item/reagent_containers/food/snacks/donkpocket(src) + new /obj/item/reagent_containers/food/snacks/donkpocket(src) + new /obj/item/reagent_containers/food/snacks/donkpocket(src) + new /obj/item/reagent_containers/food/snacks/donkpocket(src) + new /obj/item/reagent_containers/food/snacks/donkpocket(src) + new /obj/item/reagent_containers/food/snacks/donkpocket(src) + +/obj/item/storage/box/syndidonkpockets + name = "box of donk-pockets" + desc = "This box feels slightly warm" + icon_state = "donk_kit" + + New() + ..() + new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) + new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) + new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) + new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) + new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) + new /obj/item/reagent_containers/food/snacks/syndidonkpocket(src) + +/obj/item/storage/box/monkeycubes + name = "monkey cube box" + desc = "Drymate brand monkey cubes. Just add water!" + icon = 'icons/obj/food/food.dmi' + icon_state = "monkeycubebox" + storage_slots = 7 + can_hold = list(/obj/item/reagent_containers/food/snacks/monkeycube) + var/monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube + +/obj/item/storage/box/monkeycubes/New() + ..() + for(var/i in 1 to 5) + new monkey_cube_type(src) + +/obj/item/storage/box/monkeycubes/syndicate + desc = "Waffle Co. brand monkey cubes. Just add water and a dash of subterfuge!" + monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/syndicate + +/obj/item/storage/box/monkeycubes/farwacubes + name = "farwa cube box" + desc = "Drymate brand farwa cubes. Just add water!" + monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/farwacube + +/obj/item/storage/box/monkeycubes/stokcubes + name = "stok cube box" + desc = "Drymate brand stok cubes. Just add water!" + monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/stokcube + +/obj/item/storage/box/monkeycubes/neaeracubes + name = "neaera cube box" + desc = "Drymate brand neaera cubes. Just add water!" + monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/neaeracube + +/obj/item/storage/box/monkeycubes/wolpincubes + name = "wolpin cube box" + desc = "Drymate brand wolpin cubes. Just add water!" + monkey_cube_type = /obj/item/reagent_containers/food/snacks/monkeycube/wolpincube + +/obj/item/storage/box/permits + name = "box of construction permits" + desc = "A box for containing construction permits, used to officially declare built rooms as additions to the station." + icon_state = "id" + +/obj/item/storage/box/permits/New() + ..() + new /obj/item/areaeditor/permit(src) + new /obj/item/areaeditor/permit(src) + new /obj/item/areaeditor/permit(src) + new /obj/item/areaeditor/permit(src) + new /obj/item/areaeditor/permit(src) + new /obj/item/areaeditor/permit(src) + new /obj/item/areaeditor/permit(src) + + +/obj/item/storage/box/ids + name = "spare IDs" + desc = "Has so many empty IDs." + icon_state = "id" + + New() + ..() + new /obj/item/card/id(src) + new /obj/item/card/id(src) + new /obj/item/card/id(src) + new /obj/item/card/id(src) + new /obj/item/card/id(src) + new /obj/item/card/id(src) + new /obj/item/card/id(src) + +/obj/item/storage/box/prisoner + name = "prisoner IDs" + desc = "Take away their last shred of dignity, their name." + icon_state = "id" + + New() + ..() + new /obj/item/card/id/prisoner/one(src) + new /obj/item/card/id/prisoner/two(src) + new /obj/item/card/id/prisoner/three(src) + new /obj/item/card/id/prisoner/four(src) + new /obj/item/card/id/prisoner/five(src) + new /obj/item/card/id/prisoner/six(src) + new /obj/item/card/id/prisoner/seven(src) + +/obj/item/storage/box/seccarts + name = "spare R.O.B.U.S.T. Cartridges" + desc = "A box full of R.O.B.U.S.T. Cartridges, used by Security." + icon_state = "pda" + + New() + ..() + new /obj/item/cartridge/security(src) + new /obj/item/cartridge/security(src) + new /obj/item/cartridge/security(src) + new /obj/item/cartridge/security(src) + new /obj/item/cartridge/security(src) + new /obj/item/cartridge/security(src) + new /obj/item/cartridge/security(src) + +/obj/item/storage/box/holobadge + name = "holobadge box" + icon_state = "box_badge" + desc = "A box claiming to contain holobadges." + New() + new /obj/item/clothing/accessory/holobadge(src) + new /obj/item/clothing/accessory/holobadge(src) + new /obj/item/clothing/accessory/holobadge(src) + new /obj/item/clothing/accessory/holobadge(src) + new /obj/item/clothing/accessory/holobadge/cord(src) + new /obj/item/clothing/accessory/holobadge/cord(src) + ..() + return + +/obj/item/storage/box/evidence + name = "evidence bag box" + desc = "A box claiming to contain evidence bags." + icon_state = "box_evidence" + +/obj/item/storage/box/evidence/New() + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + ..() + +/obj/item/storage/box/handcuffs + name = "spare handcuffs" + desc = "A box full of handcuffs." + icon_state = "handcuff" + + New() + ..() + new /obj/item/restraints/handcuffs(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/restraints/handcuffs(src) + +/obj/item/storage/box/zipties + name = "box of spare zipties" + desc = "A box full of zipties." + icon_state = "handcuff" + + New() + ..() + new /obj/item/restraints/handcuffs/cable/zipties(src) + new /obj/item/restraints/handcuffs/cable/zipties(src) + new /obj/item/restraints/handcuffs/cable/zipties(src) + new /obj/item/restraints/handcuffs/cable/zipties(src) + new /obj/item/restraints/handcuffs/cable/zipties(src) + new /obj/item/restraints/handcuffs/cable/zipties(src) + new /obj/item/restraints/handcuffs/cable/zipties(src) + +/obj/item/storage/box/alienhandcuffs + name = "box of spare handcuffs" + desc = "A box full of handcuffs." + icon_state = "alienboxCuffs" + + New() + ..() + for(var/i in 1 to 7) + new /obj/item/restraints/handcuffs/alien(src) + +/obj/item/storage/box/fakesyndiesuit + name = "boxed space suit and helmet" + desc = "A sleek, sturdy box used to hold replica spacesuits." + icon_state = "box_of_doom" + + New() + ..() + new /obj/item/clothing/head/syndicatefake(src) + new /obj/item/clothing/suit/syndicatefake(src) + +/obj/item/storage/box/enforcer_rubber + name = "enforcer pistol kit (rubber)" + desc = "A box marked with pictures of an enforcer pistol, two ammo clips, and the word 'NON-LETHAL'." + icon_state = "box_ert" + +/obj/item/storage/box/enforcer_rubber/New() + ..() + new /obj/item/gun/projectile/automatic/pistol/enforcer(src) // loaded with rubber by default + new /obj/item/ammo_box/magazine/enforcer(src) + new /obj/item/ammo_box/magazine/enforcer(src) + +/obj/item/storage/box/enforcer_lethal + name = "enforcer pistol kit (lethal)" + desc = "A box marked with pictures of an enforcer pistol, two ammo clips, and the word 'LETHAL'." + icon_state = "box_ert" + +/obj/item/storage/box/enforcer_lethal/New() + ..() + new /obj/item/gun/projectile/automatic/pistol/enforcer/lethal(src) + new /obj/item/ammo_box/magazine/enforcer/lethal(src) + new /obj/item/ammo_box/magazine/enforcer/lethal(src) + +/obj/item/storage/box/bartender_rare_ingredients_kit + name = "bartender rare reagents kit" + desc = "A box intended for experienced bartenders." + +/obj/item/storage/box/bartender_rare_ingredients_kit/New() + ..() + var/list/reagent_list = list("sacid", "radium", "ether", "methamphetamine", "plasma", "gold", "silver", "capsaicin", "psilocybin") + for(var/reag in reagent_list) + var/obj/item/reagent_containers/glass/bottle/B = new(src) + B.reagents.add_reagent(reag, 30) + B.name = "[reag] bottle" + +/obj/item/storage/box/chef_rare_ingredients_kit + name = "chef rare reagents kit" + desc = "A box intended for experienced chefs." + +/obj/item/storage/box/chef_rare_ingredients_kit/New() + ..() + new /obj/item/reagent_containers/food/condiment/soysauce(src) + new /obj/item/reagent_containers/food/condiment/enzyme(src) + new /obj/item/reagent_containers/food/condiment/pack/hotsauce(src) + new /obj/item/kitchen/knife/butcher(src) + var/list/reagent_list = list("msg", "triple_citrus", "salglu_solution", "nutriment", "gravy", "honey", "vitfro") + for(var/reag in reagent_list) + var/obj/item/reagent_containers/glass/bottle/B = new(src) + B.reagents.add_reagent(reag, 30) + B.name = "[reag] bottle" + +/obj/item/storage/box/mousetraps + name = "box of Pest-B-Gon mousetraps" + desc = "WARNING: Keep out of reach of children." + icon_state = "mousetraps" + + New() + ..() + new /obj/item/assembly/mousetrap( src ) + new /obj/item/assembly/mousetrap( src ) + new /obj/item/assembly/mousetrap( src ) + new /obj/item/assembly/mousetrap( src ) + new /obj/item/assembly/mousetrap( src ) + new /obj/item/assembly/mousetrap( src ) + +/obj/item/storage/box/pillbottles + name = "box of pill bottles" + desc = "It has pictures of pill bottles on its front." + + New() + ..() + new /obj/item/storage/pill_bottle( src ) + new /obj/item/storage/pill_bottle( src ) + new /obj/item/storage/pill_bottle( src ) + new /obj/item/storage/pill_bottle( src ) + new /obj/item/storage/pill_bottle( src ) + new /obj/item/storage/pill_bottle( src ) + new /obj/item/storage/pill_bottle( src ) + +/obj/item/storage/box/patch_packs + name = "box of patch packs" + desc = "It has pictures of patch packs on its front." + +/obj/item/storage/box/patch_packs/New() + ..() + new /obj/item/storage/pill_bottle/patch_pack(src) + new /obj/item/storage/pill_bottle/patch_pack(src) + new /obj/item/storage/pill_bottle/patch_pack(src) + new /obj/item/storage/pill_bottle/patch_pack(src) + new /obj/item/storage/pill_bottle/patch_pack(src) + new /obj/item/storage/pill_bottle/patch_pack(src) + new /obj/item/storage/pill_bottle/patch_pack(src) + +/obj/item/storage/box/bodybags + name = "body bags" + desc = "This box contains body bags." + icon_state = "bodybags" + +/obj/item/storage/box/bodybags/New() + ..() + new /obj/item/bodybag(src) + new /obj/item/bodybag(src) + new /obj/item/bodybag(src) + new /obj/item/bodybag(src) + new /obj/item/bodybag(src) + new /obj/item/bodybag(src) + new /obj/item/bodybag(src) + +/obj/item/storage/box/snappops + name = "snap pop box" + desc = "Eight wrappers of fun! Ages 8 and up. Not suitable for children." + icon = 'icons/obj/toy.dmi' + icon_state = "spbox" + storage_slots = 8 + can_hold = list(/obj/item/toy/snappop) + New() + ..() + for(var/i=1; i <= storage_slots; i++) + new /obj/item/toy/snappop(src) + +/obj/item/storage/box/matches + name = "matchbox" + desc = "A small box of Almost But Not Quite Plasma Premium Matches." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "matchbox" + item_state = "zippo" + storage_slots = 10 + w_class = WEIGHT_CLASS_TINY + max_w_class = WEIGHT_CLASS_TINY + slot_flags = SLOT_BELT + can_hold = list(/obj/item/match) + +/obj/item/storage/box/matches/New() + ..() + for(var/i in 1 to storage_slots) + new /obj/item/match(src) + +/obj/item/storage/box/matches/attackby(obj/item/match/W, mob/user, params) + if(istype(W, /obj/item/match) && !W.lit) + W.matchignite() + playsound(user.loc, 'sound/goonstation/misc/matchstick_light.ogg', 50, 1) + return + +/obj/item/storage/box/autoinjectors + name = "box of injectors" + desc = "Contains autoinjectors." + icon_state = "syringe" + New() + ..() + for(var/i; i < storage_slots; i++) + new /obj/item/reagent_containers/hypospray/autoinjector(src) + +/obj/item/storage/box/autoinjector/utility + name = "autoinjector kit" + desc = "A box with several utility autoinjectors for the economical miner." + icon_state = "syringe" + + New() + ..() + new /obj/item/reagent_containers/hypospray/autoinjector/teporone(src) + new /obj/item/reagent_containers/hypospray/autoinjector/teporone(src) + new /obj/item/reagent_containers/hypospray/autoinjector/stimpack(src) + new /obj/item/reagent_containers/hypospray/autoinjector/stimpack(src) + new /obj/item/reagent_containers/hypospray/autoinjector/stimpack(src) + +/obj/item/storage/box/lights + name = "replacement bulbs" + icon = 'icons/obj/storage.dmi' + icon_state = "light" + desc = "This box is shaped on the inside so that only light tubes and bulbs fit." + item_state = "syringe_kit" + foldable = /obj/item/stack/sheet/cardboard + storage_slots=21 + can_hold = list(/obj/item/light/tube, /obj/item/light/bulb) + max_combined_w_class = 21 + use_to_pickup = 1 // for picking up broken bulbs, not that most people will try + +/obj/item/storage/box/lights/bulbs/New() + ..() + for(var/i = 0; i < 21; i++) + new /obj/item/light/bulb(src) + +/obj/item/storage/box/lights/tubes + name = "replacement tubes" + icon_state = "lighttube" + +/obj/item/storage/box/lights/tubes/New() + ..() + for(var/i = 0; i < 21; i++) + new /obj/item/light/tube(src) + +/obj/item/storage/box/lights/mixed + name = "replacement lights" + icon_state = "lightmixed" + +/obj/item/storage/box/lights/mixed/New() + ..() + for(var/i = 0; i < 14; i++) + new /obj/item/light/tube(src) + for(var/i = 0; i < 7; i++) + new /obj/item/light/bulb(src) + +/obj/item/storage/box/barber + name = "Barber Starter Kit" + desc = "For all hairstyling needs." + icon_state = "implant" + +/obj/item/storage/box/barber/New() + ..() + new /obj/item/scissors/barber(src) + new /obj/item/hair_dye_bottle(src) + new /obj/item/reagent_containers/glass/bottle/reagent/hairgrownium(src) + new /obj/item/reagent_containers/glass/bottle/reagent/hair_dye(src) + new /obj/item/reagent_containers/glass/bottle/reagent(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/clothing/mask/fakemoustache(src) //totally necessary for successful barbering -Fox + +/obj/item/storage/box/lip_stick + name = "Lipstick Kit" + desc = "For all your lip coloring needs." + icon_state = "implant" + +/obj/item/storage/box/lip_stick/New() + ..() + new /obj/item/lipstick(src) + new /obj/item/lipstick/purple(src) + new /obj/item/lipstick/jade(src) + new /obj/item/lipstick/black(src) + new /obj/item/lipstick/green(src) + new /obj/item/lipstick/blue(src) + new /obj/item/lipstick/white(src) + +#define NODESIGN "None" +#define NANOTRASEN "NanotrasenStandard" +#define SYNDI "SyndiSnacks" +#define HEART "Heart" +#define SMILE "SmileyFace" + +/obj/item/storage/box/papersack + name = "paper sack" + desc = "A sack neatly crafted out of paper." + icon_state = "paperbag_None" + item_state = "paperbag_None" + resistance_flags = FLAMMABLE + foldable = null + var/design = NODESIGN + +/obj/item/storage/box/papersack/update_icon() + if(!contents.len) + icon_state = "[item_state]" + else icon_state = "[item_state]_closed" + +/obj/item/storage/box/papersack/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + //if a pen is used on the sack, dialogue to change its design appears + if(contents.len) + to_chat(user, "You can't modify [src] with items still inside!") + return + var/list/designs = list(NODESIGN, NANOTRASEN, SYNDI, HEART, SMILE) + var/switchDesign = input("Select a Design:", "Paper Sack Design", designs[1]) as null|anything in designs + if(!switchDesign) + return + if(get_dist(usr, src) > 1 && !usr.incapacitated()) + to_chat(usr, "You have moved too far away!") + return + if(design == switchDesign) + return + to_chat(usr, "You make some modifications to [src] using your pen.") + design = switchDesign + icon_state = "paperbag_[design]" + item_state = "paperbag_[design]" + switch(design) + if(NODESIGN) + desc = "A sack neatly crafted out of paper." + if(NANOTRASEN) + desc = "A standard Nanotrasen paper lunch sack for loyal employees on the go." + if(SYNDI) + desc = "The design on this paper sack is a remnant of the notorious 'SyndieSnacks' program." + if(HEART) + desc = "A paper sack with a heart etched onto the side." + if(SMILE) + desc = "A paper sack with a crude smile etched onto the side." + return + else if(is_sharp(W)) + if(!contents.len) + if(item_state == "paperbag_None") + to_chat(user, "You cut eyeholes into [src].") + new /obj/item/clothing/head/papersack(user.loc) + qdel(src) + return + else if(item_state == "paperbag_SmileyFace") + to_chat(user, "You cut eyeholes into [src] and modify the design.") + new /obj/item/clothing/head/papersack/smiley(user.loc) + qdel(src) + return + return ..() + + +/obj/item/storage/box/centcomofficer + name = "officer kit" + icon_state = "box_ert" + storage_slots = 14 + max_combined_w_class = 20 + +/obj/item/storage/box/centcomofficer/New() + ..() + contents = list() + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/emergency_oxygen/double/full(src) + new /obj/item/flashlight/seclite(src) + new /obj/item/kitchen/knife/combat(src) + + new /obj/item/radio/centcom(src) + new /obj/item/door_remote/omni(src) + new /obj/item/implanter/death_alarm(src) + + new /obj/item/reagent_containers/hypospray/combat/nanites(src) + new /obj/item/pinpointer(src) + new /obj/item/pinpointer/crew/centcom(src) + +/obj/item/storage/box/responseteam + name = "boxed survival kit" + icon_state = "box_ert" + +/obj/item/storage/box/responseteam/New() + ..() + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/emergency_oxygen/engi(src) + new /obj/item/flashlight/flare(src) + new /obj/item/kitchen/knife/combat(src) + new /obj/item/radio/centcom(src) + new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) + new /obj/item/reagent_containers/hypospray/autoinjector(src) + +/obj/item/storage/box/clown + name = "clown box" + desc = "A colorful cardboard box for the clown" + icon_state = "box_clown" + +/obj/item/storage/box/emptysandbags + name = "box of empty sandbags" + +/obj/item/storage/box/emptysandbags/New() + ..() + contents = list() + for(var/i in 1 to 7) + new /obj/item/emptysandbag(src) + +/obj/item/storage/box/rndboards + name = "the Liberator's legacy" + desc = "A box containing a gift for worthy golems." + +/obj/item/storage/box/rndboards/New() + ..() + contents = list() + new /obj/item/circuitboard/protolathe(src) + new /obj/item/circuitboard/destructive_analyzer(src) + new /obj/item/circuitboard/circuit_imprinter(src) + new /obj/item/circuitboard/rdconsole/public(src) + +/obj/item/storage/box/stockparts/basic //for ruins where it's a bad idea to give access to an autolathe/protolathe, but still want to make stock parts accessible + name = "box of stock parts" + desc = "Contains a variety of basic stock parts." + +/obj/item/storage/box/stockparts/basic/New() + ..() + for(var/i in 1 to 3) + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/manipulator(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/matter_bin(src) + +/obj/item/storage/box/stockparts/deluxe + name = "box of deluxe stock parts" + desc = "Contains a variety of deluxe stock parts." + +/obj/item/storage/box/stockparts/deluxe/New() + ..() + for(var/i in 1 to 3) + new /obj/item/stock_parts/capacitor/quadratic(src) + new /obj/item/stock_parts/scanning_module/triphasic(src) + new /obj/item/stock_parts/manipulator/femto(src) + new /obj/item/stock_parts/micro_laser/quadultra(src) + new /obj/item/stock_parts/matter_bin/bluespace(src) + +/obj/item/storage/box/hug + name = "box of hugs" + desc = "A special box for sensitive people." + icon_state = "hugbox" + foldable = null + +/obj/item/storage/box/hug/suicide_act(mob/user) + user.visible_message("[user] clamps the box of hugs on [user.p_their()] jugular! Guess it wasn't such a hugbox after all..") + return (BRUTELOSS) + +/obj/item/storage/box/hug/attack_self(mob/user) + ..() + user.changeNext_move(CLICK_CD_MELEE) + playsound(loc, "rustle", 50, 1, -5) + user.visible_message("[user] hugs \the [src].","You hug \the [src].") + +#undef NODESIGN +#undef NANOTRASEN +#undef SYNDI +#undef HEART +#undef SMILE diff --git a/code/game/objects/items/weapons/storage/briefcase.dm b/code/game/objects/items/weapons/storage/briefcase.dm index 3f747e0f799..acbf16ce202 100644 --- a/code/game/objects/items/weapons/storage/briefcase.dm +++ b/code/game/objects/items/weapons/storage/briefcase.dm @@ -1,94 +1,94 @@ -/obj/item/storage/briefcase - name = "briefcase" - desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional." - icon_state = "briefcase" - item_state = "briefcase" - flags = CONDUCT - hitsound = "swing_hit" - force = 8 - throw_speed = 2 - throw_range = 4 - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 21 - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - resistance_flags = FLAMMABLE - max_integrity = 150 - -/obj/item/storage/briefcase/sniperbundle - desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding. Smells like L'Air du Temps." - force = 10 - -/obj/item/storage/briefcase/sniperbundle/New() - ..() - new /obj/item/gun/projectile/automatic/sniper_rifle/syndicate(src) - new /obj/item/clothing/accessory/red(src) - new /obj/item/clothing/under/syndicate/sniper(src) - new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) - new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) - new /obj/item/suppressor/specialoffer(src) - -/obj/item/storage/briefcase/false_bottomed - max_w_class = WEIGHT_CLASS_SMALL - max_combined_w_class = 10 - - var/busy_hunting = FALSE - var/bottom_open = FALSE //is the false bottom open? - var/obj/item/stored_item = null //what's in the false bottom. If it's a gun, we can fire it - -/obj/item/storage/briefcase/false_bottomed/Destroy() - if(stored_item)//since the stored_item isn't in the briefcase' contents we gotta remind the game to delete it here. - QDEL_NULL(stored_item) - return ..() - -/obj/item/storage/briefcase/false_bottomed/afterattack(atom/A, mob/user, flag, params) - ..() - if(stored_item && istype(stored_item, /obj/item/gun) && !Adjacent(A)) - var/obj/item/gun/stored_gun = stored_item - stored_gun.afterattack(A, user, flag, params) - -/obj/item/storage/briefcase/false_bottomed/attackby(var/obj/item/I, mob/user) - if(bottom_open) - if(stored_item) - to_chat(user, "There's already something in the false bottom!") - return - if(I.w_class > WEIGHT_CLASS_NORMAL) - to_chat(user, "The [I] is too big to fit in the false bottom!") - return - if(!user.drop_item(I)) - user << "The [I] is stuck to your hands!" - return - - stored_item = I - max_w_class = WEIGHT_CLASS_NORMAL - stored_item.w_class - I.forceMove(null) //null space here we go - to stop it showing up in the briefcase - to_chat(user, "You place the [I] into the false bottom of the briefcase.") - else - return ..() - -/obj/item/storage/briefcase/false_bottomed/screwdriver_act(mob/user, obj/item/I) - if(!bottom_open && busy_hunting) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(!bottom_open) - to_chat(user, "You begin to hunt around the rim of the [src]...") - busy_hunting = TRUE - if(do_after(user, 20, target = src)) - if(user) - to_chat(user, "You pry open the false bottom!") - bottom_open = TRUE - busy_hunting = FALSE - else - to_chat(user, "You push the false bottom down and close it with a click[stored_item ? ", with the [stored_item] snugly inside." : "."]") - bottom_open = FALSE - -/obj/item/storage/briefcase/false_bottomed/attack_hand(mob/user) - if(bottom_open && stored_item) - user.put_in_hands(stored_item) - to_chat(user, "You pull out the [stored_item] from the [src]'s false bottom.") - stored_item = null - max_w_class = initial(max_w_class) - else - return ..() +/obj/item/storage/briefcase + name = "briefcase" + desc = "It's made of AUTHENTIC faux-leather and has a price-tag still attached. Its owner must be a real professional." + icon_state = "briefcase" + item_state = "briefcase" + flags = CONDUCT + hitsound = "swing_hit" + force = 8 + throw_speed = 2 + throw_range = 4 + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 21 + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + resistance_flags = FLAMMABLE + max_integrity = 150 + +/obj/item/storage/briefcase/sniperbundle + desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding. Smells like L'Air du Temps." + force = 10 + +/obj/item/storage/briefcase/sniperbundle/New() + ..() + new /obj/item/gun/projectile/automatic/sniper_rifle/syndicate(src) + new /obj/item/clothing/accessory/red(src) + new /obj/item/clothing/under/syndicate/sniper(src) + new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) + new /obj/item/ammo_box/magazine/sniper_rounds/soporific(src) + new /obj/item/suppressor/specialoffer(src) + +/obj/item/storage/briefcase/false_bottomed + max_w_class = WEIGHT_CLASS_SMALL + max_combined_w_class = 10 + + var/busy_hunting = FALSE + var/bottom_open = FALSE //is the false bottom open? + var/obj/item/stored_item = null //what's in the false bottom. If it's a gun, we can fire it + +/obj/item/storage/briefcase/false_bottomed/Destroy() + if(stored_item)//since the stored_item isn't in the briefcase' contents we gotta remind the game to delete it here. + QDEL_NULL(stored_item) + return ..() + +/obj/item/storage/briefcase/false_bottomed/afterattack(atom/A, mob/user, flag, params) + ..() + if(stored_item && istype(stored_item, /obj/item/gun) && !Adjacent(A)) + var/obj/item/gun/stored_gun = stored_item + stored_gun.afterattack(A, user, flag, params) + +/obj/item/storage/briefcase/false_bottomed/attackby(var/obj/item/I, mob/user) + if(bottom_open) + if(stored_item) + to_chat(user, "There's already something in the false bottom!") + return + if(I.w_class > WEIGHT_CLASS_NORMAL) + to_chat(user, "The [I] is too big to fit in the false bottom!") + return + if(!user.drop_item(I)) + user << "The [I] is stuck to your hands!" + return + + stored_item = I + max_w_class = WEIGHT_CLASS_NORMAL - stored_item.w_class + I.forceMove(null) //null space here we go - to stop it showing up in the briefcase + to_chat(user, "You place the [I] into the false bottom of the briefcase.") + else + return ..() + +/obj/item/storage/briefcase/false_bottomed/screwdriver_act(mob/user, obj/item/I) + if(!bottom_open && busy_hunting) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!bottom_open) + to_chat(user, "You begin to hunt around the rim of the [src]...") + busy_hunting = TRUE + if(do_after(user, 20, target = src)) + if(user) + to_chat(user, "You pry open the false bottom!") + bottom_open = TRUE + busy_hunting = FALSE + else + to_chat(user, "You push the false bottom down and close it with a click[stored_item ? ", with the [stored_item] snugly inside." : "."]") + bottom_open = FALSE + +/obj/item/storage/briefcase/false_bottomed/attack_hand(mob/user) + if(bottom_open && stored_item) + user.put_in_hands(stored_item) + to_chat(user, "You pull out the [stored_item] from the [src]'s false bottom.") + stored_item = null + max_w_class = initial(max_w_class) + else + return ..() diff --git a/code/game/objects/items/weapons/storage/firstaid.dm b/code/game/objects/items/weapons/storage/firstaid.dm index 987bf86a904..bc1e90dac8e 100644 --- a/code/game/objects/items/weapons/storage/firstaid.dm +++ b/code/game/objects/items/weapons/storage/firstaid.dm @@ -1,382 +1,382 @@ -/* First aid storage - * Contains: - * First Aid Kits - * Pill Bottles - * Dice Pack (in a pill bottle) - */ - -/* - * First Aid Kits - */ -/obj/item/storage/firstaid - name = "first-aid kit" - desc = "It's an emergency medical kit for those serious boo-boos." - icon_state = "firstaid" - throw_speed = 2 - throw_range = 8 - var/empty = 0 - req_one_access =list(ACCESS_MEDICAL, ACCESS_ROBOTICS) //Access and treatment are utilized for medbots. - var/treatment_brute = "salglu_solution" - var/treatment_oxy = "salbutamol" - var/treatment_fire = "salglu_solution" - var/treatment_tox = "charcoal" - var/treatment_virus = "spaceacillin" - var/med_bot_skin = null - var/syndicate_aligned = FALSE - - -/obj/item/storage/firstaid/fire - name = "fire first-aid kit" - desc = "A medical kit that contains several medical patches and pills for treating burns. Contains one epinephrine syringe for emergency use and a health analyzer." - icon_state = "ointment" - item_state = "firstaid-ointment" - med_bot_skin = "ointment" - - New() - ..() - if(empty) return - - icon_state = pick("ointment","firefirstaid") - - new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) - new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) - new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) - new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) - new /obj/item/healthanalyzer( src ) - new /obj/item/reagent_containers/hypospray/autoinjector( src ) - new /obj/item/reagent_containers/food/pill/salicylic( src ) - return - -/obj/item/storage/firstaid/fire/empty - empty = 1 - -/obj/item/storage/firstaid/regular - desc = "A general medical kit that contains medical patches for both brute damage and burn damage. Also contains an epinephrine syringe for emergency use and a health analyzer" - icon_state = "firstaid" - - New() - ..() - if(empty) return - new /obj/item/reagent_containers/food/pill/patch/styptic( src ) - new /obj/item/reagent_containers/food/pill/patch/styptic( src ) - new /obj/item/reagent_containers/food/pill/salicylic( src ) - new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) - new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) - new /obj/item/healthanalyzer( src ) - new /obj/item/reagent_containers/hypospray/autoinjector( src ) - return - -/obj/item/storage/firstaid/toxin - name = "toxin first aid kit" - desc = "A medical kit designed to counter poisoning by common toxins. Contains three pills and syringes, and a health analyzer to determine the health of the patient." - icon_state = "antitoxin" - item_state = "firstaid-toxin" - med_bot_skin = "tox" - - New() - ..() - if(empty) return - - icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3") - - new /obj/item/reagent_containers/syringe/charcoal( src ) - new /obj/item/reagent_containers/syringe/charcoal( src ) - new /obj/item/reagent_containers/syringe/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/healthanalyzer( src ) - return - -/obj/item/storage/firstaid/toxin/empty - empty = 1 - -/obj/item/storage/firstaid/o2 - name = "oxygen deprivation first aid kit" - desc = "A first aid kit that contains four pills of salbutamol, which is able to counter injuries caused by suffocation. Also contains a health analyzer to determine the health of the patient." - icon_state = "o2" - item_state = "firstaid-o2" - med_bot_skin = "o2" - - New() - ..() - if(empty) return - new /obj/item/reagent_containers/food/pill/salbutamol( src ) - new /obj/item/reagent_containers/food/pill/salbutamol( src ) - new /obj/item/reagent_containers/food/pill/salbutamol( src ) - new /obj/item/reagent_containers/food/pill/salbutamol( src ) - new /obj/item/healthanalyzer( src ) - return - -/obj/item/storage/firstaid/o2/empty - empty = 1 - -/obj/item/storage/firstaid/brute - name = "brute trauma treatment kit" - desc = "A medical kit that contains several medical patches and pills for treating brute injuries. Contains one epinephrine syringe for emergency use and a health analyzer." - icon_state = "brute" - item_state = "firstaid-brute" - med_bot_skin = "brute" - - New() - ..() - if(empty) return - - icon_state = pick("brute","brute2") - - new /obj/item/reagent_containers/food/pill/patch/styptic(src) - new /obj/item/reagent_containers/food/pill/patch/styptic(src) - new /obj/item/reagent_containers/food/pill/patch/styptic(src) - new /obj/item/reagent_containers/food/pill/patch/styptic(src) - new /obj/item/healthanalyzer(src) - new /obj/item/reagent_containers/hypospray/autoinjector(src) - new /obj/item/stack/medical/bruise_pack(src) - return - -/obj/item/storage/firstaid/brute/empty - empty = 1 - -/obj/item/storage/firstaid/adv - name = "advanced first-aid kit" - desc = "Contains advanced medical treatments." - icon_state = "advfirstaid" - item_state = "firstaid-advanced" - med_bot_skin = "adv" - -/obj/item/storage/firstaid/adv/New() - ..() - if(empty) - return - new /obj/item/stack/medical/bruise_pack(src) - new /obj/item/stack/medical/bruise_pack/advanced(src) - new /obj/item/stack/medical/bruise_pack/advanced(src) - new /obj/item/stack/medical/ointment/advanced(src) - new /obj/item/stack/medical/ointment/advanced(src) - new /obj/item/reagent_containers/hypospray/autoinjector(src) - new /obj/item/healthanalyzer(src) - -/obj/item/storage/firstaid/adv/empty - empty = 1 - -/obj/item/storage/firstaid/machine - name = "machine repair kit" - desc = "A kit that contains supplies to repair IPCs on the go." - icon_state = "machinefirstaid" - item_state = "firstaid-machine" - med_bot_skin = "machine" - -/obj/item/storage/firstaid/machine/New() - ..() - if(empty) - return - new /obj/item/weldingtool(src) - new /obj/item/stack/cable_coil(src) - new /obj/item/stack/cable_coil(src) - new /obj/item/stack/cable_coil(src) - new /obj/item/reagent_containers/food/drinks/oilcan/full(src) - new /obj/item/robotanalyzer(src) - -/obj/item/storage/firstaid/machine/empty - empty = 1 - - -/obj/item/storage/firstaid/tactical - name = "first-aid kit" - icon_state = "bezerk" - desc = "I hope you've got insurance." - max_w_class = WEIGHT_CLASS_NORMAL - treatment_oxy = "perfluorodecalin" - treatment_brute = "bicaridine" - treatment_fire = "kelotane" - treatment_tox = "charcoal" - req_one_access =list(ACCESS_SYNDICATE) - med_bot_skin = "bezerk" - syndicate_aligned = TRUE - -/obj/item/storage/firstaid/tactical/New() - ..() - if(empty) return - new /obj/item/reagent_containers/hypospray/combat(src) - new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) // Because you ain't got no time to look at what damage dey taking yo - new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) - new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) - new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) - new /obj/item/defibrillator/compact/combat/loaded(src) - new /obj/item/clothing/glasses/hud/health/night(src) - return - -/obj/item/storage/firstaid/tactical/empty - empty =1 - -/obj/item/storage/firstaid/surgery - name = "field surgery kit" - icon_state = "duffel-med" - desc = "A kit for surgery in the field." - max_w_class = WEIGHT_CLASS_BULKY - max_combined_w_class = 21 - storage_slots = 10 - can_hold = list(/obj/item/roller,/obj/item/bonesetter,/obj/item/bonegel, /obj/item/scalpel, /obj/item/hemostat, - /obj/item/cautery, /obj/item/retractor, /obj/item/FixOVein, /obj/item/surgicaldrill, /obj/item/circular_saw) - -/obj/item/storage/firstaid/surgery/New() - ..() - new /obj/item/roller(src) - new /obj/item/bonesetter(src) - new /obj/item/bonegel(src) - new /obj/item/scalpel(src) - new /obj/item/hemostat(src) - new /obj/item/cautery(src) - new /obj/item/retractor(src) - new /obj/item/FixOVein(src) - new /obj/item/surgicaldrill(src) - new /obj/item/circular_saw(src) - -/* - * Pill Bottles - */ - -/obj/item/storage/pill_bottle - name = "pill bottle" - desc = "It's an airtight container for storing medication." - icon_state = "pill_canister" - icon = 'icons/obj/chemical.dmi' - item_state = "contsolid" - w_class = WEIGHT_CLASS_SMALL - can_hold = list(/obj/item/reagent_containers/food/pill) - cant_hold = list(/obj/item/reagent_containers/food/pill/patch) - allow_quick_gather = TRUE - use_to_pickup = TRUE - storage_slots = 50 - max_combined_w_class = 50 - display_contents_with_number = TRUE - var/base_name = "" - var/label_text = "" - var/applying_meds = FALSE //To Prevent spam clicking and generating runtimes from apply a deleting pill multiple times. - var/rapid_intake_message = "unscrews the cap on the pill bottle and begins dumping the entire contents down their throat!" - var/rapid_post_instake_message = "downs the entire bottle of pills in one go!" - var/allow_wrap = TRUE - var/wrapper_color = null - -/obj/item/storage/pill_bottle/New() - ..() - base_name = name - if(allow_wrap) - apply_wrap() - -/obj/item/storage/pill_bottle/proc/apply_wrap() - if(wrapper_color) - overlays.Cut() - var/image/I = image(icon, "pillbottle_wrap") - I.color = wrapper_color - overlays += I - -/obj/item/storage/pill_bottle/attack(mob/M, mob/user) - if(iscarbon(M) && contents.len) - if(applying_meds) - to_chat(user, "You are already applying meds.") - return - applying_meds = TRUE - for(var/obj/item/reagent_containers/food/pill/P in contents) - if(P.attack(M, user)) - applying_meds = FALSE - else - applying_meds = FALSE - break - else - return ..() - -/obj/item/storage/pill_bottle/ert - wrapper_color = COLOR_MAROON - -/obj/item/storage/pill_bottle/ert/New() - ..() - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/charcoal(src) - new /obj/item/reagent_containers/food/pill/charcoal(src) - new /obj/item/reagent_containers/food/pill/charcoal(src) - -/obj/item/storage/pill_bottle/MouseDrop(obj/over_object as obj) // Best utilized if you're a cantankerous doctor with a Vicodin habit. - if(iscarbon(over_object)) - var/mob/living/carbon/C = over_object - if(loc == C && src == C.get_active_hand()) - if(!contents.len) - to_chat(C, "There is nothing in [src]!") - return - C.visible_message("[C] [rapid_intake_message]") - if(do_mob(C, C, 100)) // 10 seconds - for(var/obj/item/reagent_containers/food/pill/P in contents) - P.attack(C, C) - C.visible_message("[C] [rapid_post_instake_message]") - return - - return ..() - -/obj/item/storage/pill_bottle/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/pen) || istype(I, /obj/item/flashlight/pen)) - var/tmp_label = sanitize(input(user, "Enter a label for [name]","Label",label_text)) - if(length(tmp_label) > MAX_NAME_LEN) - to_chat(user, "The label can be at most [MAX_NAME_LEN] characters long.") - else - to_chat(user, "You set the label to \"[tmp_label]\".") - label_text = tmp_label - update_name_label() - else - return ..() - -/obj/item/storage/pill_bottle/proc/update_name_label() - if(label_text == "") - name = base_name - else - name = "[base_name] ([label_text])" - -/obj/item/storage/pill_bottle/patch_pack - name = "Patch Pack" - desc = "It's a container for storing medical patches." - icon_state = "patch_pack" - can_hold = list(/obj/item/reagent_containers/food/pill/patch) - cant_hold = list() - rapid_intake_message = "flips the lid of the Patch Pack open and begins rapidly stamping patches on themselves!" - rapid_post_instake_message = "stamps the entire contents of the Patch Pack all over their entire body!" - allow_wrap = FALSE - -/obj/item/storage/pill_bottle/charcoal - name = "Pill bottle (Charcoal)" - desc = "Contains pills used to counter toxins." - wrapper_color = COLOR_GREEN - - New() - ..() - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - new /obj/item/reagent_containers/food/pill/charcoal( src ) - -/obj/item/storage/pill_bottle/painkillers - name = "Pill Bottle (Salicylic Acid)" - desc = "Contains various pills for minor pain relief." - wrapper_color = COLOR_RED - -/obj/item/storage/pill_bottle/painkillers/New() - ..() - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - new /obj/item/reagent_containers/food/pill/salicylic(src) - -/obj/item/storage/pill_bottle/fakedeath - allow_wrap = FALSE - -/obj/item/storage/pill_bottle/fakedeath/New() - ..() - new /obj/item/reagent_containers/food/pill/fakedeath(src) - new /obj/item/reagent_containers/food/pill/fakedeath(src) - new /obj/item/reagent_containers/food/pill/fakedeath(src) +/* First aid storage + * Contains: + * First Aid Kits + * Pill Bottles + * Dice Pack (in a pill bottle) + */ + +/* + * First Aid Kits + */ +/obj/item/storage/firstaid + name = "first-aid kit" + desc = "It's an emergency medical kit for those serious boo-boos." + icon_state = "firstaid" + throw_speed = 2 + throw_range = 8 + var/empty = 0 + req_one_access =list(ACCESS_MEDICAL, ACCESS_ROBOTICS) //Access and treatment are utilized for medbots. + var/treatment_brute = "salglu_solution" + var/treatment_oxy = "salbutamol" + var/treatment_fire = "salglu_solution" + var/treatment_tox = "charcoal" + var/treatment_virus = "spaceacillin" + var/med_bot_skin = null + var/syndicate_aligned = FALSE + + +/obj/item/storage/firstaid/fire + name = "fire first-aid kit" + desc = "A medical kit that contains several medical patches and pills for treating burns. Contains one epinephrine syringe for emergency use and a health analyzer." + icon_state = "ointment" + item_state = "firstaid-ointment" + med_bot_skin = "ointment" + + New() + ..() + if(empty) return + + icon_state = pick("ointment","firefirstaid") + + new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) + new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) + new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) + new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) + new /obj/item/healthanalyzer( src ) + new /obj/item/reagent_containers/hypospray/autoinjector( src ) + new /obj/item/reagent_containers/food/pill/salicylic( src ) + return + +/obj/item/storage/firstaid/fire/empty + empty = 1 + +/obj/item/storage/firstaid/regular + desc = "A general medical kit that contains medical patches for both brute damage and burn damage. Also contains an epinephrine syringe for emergency use and a health analyzer" + icon_state = "firstaid" + + New() + ..() + if(empty) return + new /obj/item/reagent_containers/food/pill/patch/styptic( src ) + new /obj/item/reagent_containers/food/pill/patch/styptic( src ) + new /obj/item/reagent_containers/food/pill/salicylic( src ) + new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) + new /obj/item/reagent_containers/food/pill/patch/silver_sulf( src ) + new /obj/item/healthanalyzer( src ) + new /obj/item/reagent_containers/hypospray/autoinjector( src ) + return + +/obj/item/storage/firstaid/toxin + name = "toxin first aid kit" + desc = "A medical kit designed to counter poisoning by common toxins. Contains three pills and syringes, and a health analyzer to determine the health of the patient." + icon_state = "antitoxin" + item_state = "firstaid-toxin" + med_bot_skin = "tox" + + New() + ..() + if(empty) return + + icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3") + + new /obj/item/reagent_containers/syringe/charcoal( src ) + new /obj/item/reagent_containers/syringe/charcoal( src ) + new /obj/item/reagent_containers/syringe/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/healthanalyzer( src ) + return + +/obj/item/storage/firstaid/toxin/empty + empty = 1 + +/obj/item/storage/firstaid/o2 + name = "oxygen deprivation first aid kit" + desc = "A first aid kit that contains four pills of salbutamol, which is able to counter injuries caused by suffocation. Also contains a health analyzer to determine the health of the patient." + icon_state = "o2" + item_state = "firstaid-o2" + med_bot_skin = "o2" + + New() + ..() + if(empty) return + new /obj/item/reagent_containers/food/pill/salbutamol( src ) + new /obj/item/reagent_containers/food/pill/salbutamol( src ) + new /obj/item/reagent_containers/food/pill/salbutamol( src ) + new /obj/item/reagent_containers/food/pill/salbutamol( src ) + new /obj/item/healthanalyzer( src ) + return + +/obj/item/storage/firstaid/o2/empty + empty = 1 + +/obj/item/storage/firstaid/brute + name = "brute trauma treatment kit" + desc = "A medical kit that contains several medical patches and pills for treating brute injuries. Contains one epinephrine syringe for emergency use and a health analyzer." + icon_state = "brute" + item_state = "firstaid-brute" + med_bot_skin = "brute" + + New() + ..() + if(empty) return + + icon_state = pick("brute","brute2") + + new /obj/item/reagent_containers/food/pill/patch/styptic(src) + new /obj/item/reagent_containers/food/pill/patch/styptic(src) + new /obj/item/reagent_containers/food/pill/patch/styptic(src) + new /obj/item/reagent_containers/food/pill/patch/styptic(src) + new /obj/item/healthanalyzer(src) + new /obj/item/reagent_containers/hypospray/autoinjector(src) + new /obj/item/stack/medical/bruise_pack(src) + return + +/obj/item/storage/firstaid/brute/empty + empty = 1 + +/obj/item/storage/firstaid/adv + name = "advanced first-aid kit" + desc = "Contains advanced medical treatments." + icon_state = "advfirstaid" + item_state = "firstaid-advanced" + med_bot_skin = "adv" + +/obj/item/storage/firstaid/adv/New() + ..() + if(empty) + return + new /obj/item/stack/medical/bruise_pack(src) + new /obj/item/stack/medical/bruise_pack/advanced(src) + new /obj/item/stack/medical/bruise_pack/advanced(src) + new /obj/item/stack/medical/ointment/advanced(src) + new /obj/item/stack/medical/ointment/advanced(src) + new /obj/item/reagent_containers/hypospray/autoinjector(src) + new /obj/item/healthanalyzer(src) + +/obj/item/storage/firstaid/adv/empty + empty = 1 + +/obj/item/storage/firstaid/machine + name = "machine repair kit" + desc = "A kit that contains supplies to repair IPCs on the go." + icon_state = "machinefirstaid" + item_state = "firstaid-machine" + med_bot_skin = "machine" + +/obj/item/storage/firstaid/machine/New() + ..() + if(empty) + return + new /obj/item/weldingtool(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/stack/cable_coil(src) + new /obj/item/reagent_containers/food/drinks/oilcan/full(src) + new /obj/item/robotanalyzer(src) + +/obj/item/storage/firstaid/machine/empty + empty = 1 + + +/obj/item/storage/firstaid/tactical + name = "first-aid kit" + icon_state = "bezerk" + desc = "I hope you've got insurance." + max_w_class = WEIGHT_CLASS_NORMAL + treatment_oxy = "perfluorodecalin" + treatment_brute = "bicaridine" + treatment_fire = "kelotane" + treatment_tox = "charcoal" + req_one_access =list(ACCESS_SYNDICATE) + med_bot_skin = "bezerk" + syndicate_aligned = TRUE + +/obj/item/storage/firstaid/tactical/New() + ..() + if(empty) return + new /obj/item/reagent_containers/hypospray/combat(src) + new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) // Because you ain't got no time to look at what damage dey taking yo + new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) + new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) + new /obj/item/reagent_containers/food/pill/patch/synthflesh(src) + new /obj/item/defibrillator/compact/combat/loaded(src) + new /obj/item/clothing/glasses/hud/health/night(src) + return + +/obj/item/storage/firstaid/tactical/empty + empty =1 + +/obj/item/storage/firstaid/surgery + name = "field surgery kit" + icon_state = "duffel-med" + desc = "A kit for surgery in the field." + max_w_class = WEIGHT_CLASS_BULKY + max_combined_w_class = 21 + storage_slots = 10 + can_hold = list(/obj/item/roller,/obj/item/bonesetter,/obj/item/bonegel, /obj/item/scalpel, /obj/item/hemostat, + /obj/item/cautery, /obj/item/retractor, /obj/item/FixOVein, /obj/item/surgicaldrill, /obj/item/circular_saw) + +/obj/item/storage/firstaid/surgery/New() + ..() + new /obj/item/roller(src) + new /obj/item/bonesetter(src) + new /obj/item/bonegel(src) + new /obj/item/scalpel(src) + new /obj/item/hemostat(src) + new /obj/item/cautery(src) + new /obj/item/retractor(src) + new /obj/item/FixOVein(src) + new /obj/item/surgicaldrill(src) + new /obj/item/circular_saw(src) + +/* + * Pill Bottles + */ + +/obj/item/storage/pill_bottle + name = "pill bottle" + desc = "It's an airtight container for storing medication." + icon_state = "pill_canister" + icon = 'icons/obj/chemical.dmi' + item_state = "contsolid" + w_class = WEIGHT_CLASS_SMALL + can_hold = list(/obj/item/reagent_containers/food/pill) + cant_hold = list(/obj/item/reagent_containers/food/pill/patch) + allow_quick_gather = TRUE + use_to_pickup = TRUE + storage_slots = 50 + max_combined_w_class = 50 + display_contents_with_number = TRUE + var/base_name = "" + var/label_text = "" + var/applying_meds = FALSE //To Prevent spam clicking and generating runtimes from apply a deleting pill multiple times. + var/rapid_intake_message = "unscrews the cap on the pill bottle and begins dumping the entire contents down their throat!" + var/rapid_post_instake_message = "downs the entire bottle of pills in one go!" + var/allow_wrap = TRUE + var/wrapper_color = null + +/obj/item/storage/pill_bottle/New() + ..() + base_name = name + if(allow_wrap) + apply_wrap() + +/obj/item/storage/pill_bottle/proc/apply_wrap() + if(wrapper_color) + overlays.Cut() + var/image/I = image(icon, "pillbottle_wrap") + I.color = wrapper_color + overlays += I + +/obj/item/storage/pill_bottle/attack(mob/M, mob/user) + if(iscarbon(M) && contents.len) + if(applying_meds) + to_chat(user, "You are already applying meds.") + return + applying_meds = TRUE + for(var/obj/item/reagent_containers/food/pill/P in contents) + if(P.attack(M, user)) + applying_meds = FALSE + else + applying_meds = FALSE + break + else + return ..() + +/obj/item/storage/pill_bottle/ert + wrapper_color = COLOR_MAROON + +/obj/item/storage/pill_bottle/ert/New() + ..() + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/charcoal(src) + new /obj/item/reagent_containers/food/pill/charcoal(src) + new /obj/item/reagent_containers/food/pill/charcoal(src) + +/obj/item/storage/pill_bottle/MouseDrop(obj/over_object as obj) // Best utilized if you're a cantankerous doctor with a Vicodin habit. + if(iscarbon(over_object)) + var/mob/living/carbon/C = over_object + if(loc == C && src == C.get_active_hand()) + if(!contents.len) + to_chat(C, "There is nothing in [src]!") + return + C.visible_message("[C] [rapid_intake_message]") + if(do_mob(C, C, 100)) // 10 seconds + for(var/obj/item/reagent_containers/food/pill/P in contents) + P.attack(C, C) + C.visible_message("[C] [rapid_post_instake_message]") + return + + return ..() + +/obj/item/storage/pill_bottle/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/pen) || istype(I, /obj/item/flashlight/pen)) + var/tmp_label = sanitize(input(user, "Enter a label for [name]","Label",label_text)) + if(length(tmp_label) > MAX_NAME_LEN) + to_chat(user, "The label can be at most [MAX_NAME_LEN] characters long.") + else + to_chat(user, "You set the label to \"[tmp_label]\".") + label_text = tmp_label + update_name_label() + else + return ..() + +/obj/item/storage/pill_bottle/proc/update_name_label() + if(label_text == "") + name = base_name + else + name = "[base_name] ([label_text])" + +/obj/item/storage/pill_bottle/patch_pack + name = "Patch Pack" + desc = "It's a container for storing medical patches." + icon_state = "patch_pack" + can_hold = list(/obj/item/reagent_containers/food/pill/patch) + cant_hold = list() + rapid_intake_message = "flips the lid of the Patch Pack open and begins rapidly stamping patches on themselves!" + rapid_post_instake_message = "stamps the entire contents of the Patch Pack all over their entire body!" + allow_wrap = FALSE + +/obj/item/storage/pill_bottle/charcoal + name = "Pill bottle (Charcoal)" + desc = "Contains pills used to counter toxins." + wrapper_color = COLOR_GREEN + + New() + ..() + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + new /obj/item/reagent_containers/food/pill/charcoal( src ) + +/obj/item/storage/pill_bottle/painkillers + name = "Pill Bottle (Salicylic Acid)" + desc = "Contains various pills for minor pain relief." + wrapper_color = COLOR_RED + +/obj/item/storage/pill_bottle/painkillers/New() + ..() + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + new /obj/item/reagent_containers/food/pill/salicylic(src) + +/obj/item/storage/pill_bottle/fakedeath + allow_wrap = FALSE + +/obj/item/storage/pill_bottle/fakedeath/New() + ..() + new /obj/item/reagent_containers/food/pill/fakedeath(src) + new /obj/item/reagent_containers/food/pill/fakedeath(src) + new /obj/item/reagent_containers/food/pill/fakedeath(src) diff --git a/code/game/objects/items/weapons/storage/lockbox.dm b/code/game/objects/items/weapons/storage/lockbox.dm index 3881c0e06e8..b67ba9154e3 100644 --- a/code/game/objects/items/weapons/storage/lockbox.dm +++ b/code/game/objects/items/weapons/storage/lockbox.dm @@ -1,137 +1,137 @@ -/obj/item/storage/lockbox - name = "lockbox" - desc = "A locked box." - icon_state = "lockbox+l" - item_state = "syringe_kit" - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 14 //The sum of the w_classes of all the items in this storage item. - storage_slots = 4 - req_access = list(ACCESS_ARMORY) - var/locked = 1 - var/broken = 0 - var/icon_locked = "lockbox+l" - var/icon_closed = "lockbox" - var/icon_broken = "lockbox+b" - -/obj/item/storage/lockbox/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) - if(broken) - to_chat(user, "It appears to be broken.") - return - if(check_access(W)) - locked = !locked - if(locked) - icon_state = icon_locked - to_chat(user, "You lock \the [src]!") - if(user.s_active) - user.s_active.close(user) - return - else - icon_state = icon_closed - to_chat(user, "You unlock \the [src]!") - origin_tech = null //wipe out any origin tech if it's unlocked in any way so you can't double-dip tech levels at R&D. - return - else - to_chat(user, "Access denied.") - return - else if((istype(W, /obj/item/card/emag) || (istype(W, /obj/item/melee/energy/blade)) && !broken)) - emag_act(user) - return - if(!locked) - ..() - else - to_chat(user, "It's locked!") - return - - -/obj/item/storage/lockbox/show_to(mob/user as mob) - if(locked) - to_chat(user, "It's locked!") - else - ..() - return - -/obj/item/storage/lockbox/can_be_inserted(obj/item/W as obj, stop_messages = 0) - if(!locked) - return ..() - if(!stop_messages) - to_chat(usr, "[src] is locked!") - return 0 - -/obj/item/storage/lockbox/emag_act(user as mob) - if(!broken) - broken = 1 - locked = 0 - desc = "It appears to be broken." - icon_state = icon_broken - to_chat(user, "You unlock \the [src].") - origin_tech = null //wipe out any origin tech if it's unlocked in any way so you can't double-dip tech levels at R&D. - return - -/obj/item/storage/lockbox/hear_talk(mob/living/M as mob, list/message_pieces) - -/obj/item/storage/lockbox/hear_message(mob/living/M as mob, msg) - -/obj/item/storage/lockbox/mindshield - name = "Lockbox (Mindshield Implants)" - req_access = list(ACCESS_SECURITY) - -/obj/item/storage/lockbox/mindshield/New() - ..() - new /obj/item/implantcase/mindshield(src) - new /obj/item/implantcase/mindshield(src) - new /obj/item/implantcase/mindshield(src) - new /obj/item/implanter/mindshield(src) - -/obj/item/storage/lockbox/clusterbang - name = "lockbox (clusterbang)" - desc = "You have a bad feeling about opening this." - req_access = list(ACCESS_SECURITY) - -/obj/item/storage/lockbox/clusterbang/New() - ..() - new /obj/item/grenade/clusterbuster(src) - -/obj/item/storage/lockbox/medal - name = "medal box" - desc = "A locked box used to store medals of honor." - icon_state = "medalbox+l" - item_state = "syringe_kit" - w_class = WEIGHT_CLASS_NORMAL - max_w_class = WEIGHT_CLASS_SMALL - max_combined_w_class = 20 - storage_slots = 12 - req_access = list(ACCESS_CAPTAIN) - icon_locked = "medalbox+l" - icon_closed = "medalbox" - icon_broken = "medalbox+b" - -/obj/item/storage/lockbox/medal/New() - ..() - new /obj/item/clothing/accessory/medal/gold/captain(src) - new /obj/item/clothing/accessory/medal/silver/leadership(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/heart(src) - -/obj/item/storage/lockbox/t4 - name = "lockbox (T4)" - desc = "Contains three T4 breaching charges." - req_access = list(ACCESS_CENT_SPECOPS) - -/obj/item/storage/lockbox/t4/New() - ..() - for(var/i in 0 to 2) - new /obj/item/grenade/plastic/x4/thermite(src) - -/obj/item/storage/lockbox/research - -/obj/item/storage/lockbox/research/deconstruct(disassembled = TRUE) // Get wrecked, Science nerds - qdel(src) - -/obj/item/storage/lockbox/research/large - name = "Large lockbox" - desc = "A large lockbox" - max_w_class = WEIGHT_CLASS_BULKY - max_combined_w_class = 4 //The sum of the w_classes of all the items in this storage item. - storage_slots = 1 \ No newline at end of file +/obj/item/storage/lockbox + name = "lockbox" + desc = "A locked box." + icon_state = "lockbox+l" + item_state = "syringe_kit" + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 14 //The sum of the w_classes of all the items in this storage item. + storage_slots = 4 + req_access = list(ACCESS_ARMORY) + var/locked = 1 + var/broken = 0 + var/icon_locked = "lockbox+l" + var/icon_closed = "lockbox" + var/icon_broken = "lockbox+b" + +/obj/item/storage/lockbox/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) + if(broken) + to_chat(user, "It appears to be broken.") + return + if(check_access(W)) + locked = !locked + if(locked) + icon_state = icon_locked + to_chat(user, "You lock \the [src]!") + if(user.s_active) + user.s_active.close(user) + return + else + icon_state = icon_closed + to_chat(user, "You unlock \the [src]!") + origin_tech = null //wipe out any origin tech if it's unlocked in any way so you can't double-dip tech levels at R&D. + return + else + to_chat(user, "Access denied.") + return + else if((istype(W, /obj/item/card/emag) || (istype(W, /obj/item/melee/energy/blade)) && !broken)) + emag_act(user) + return + if(!locked) + ..() + else + to_chat(user, "It's locked!") + return + + +/obj/item/storage/lockbox/show_to(mob/user as mob) + if(locked) + to_chat(user, "It's locked!") + else + ..() + return + +/obj/item/storage/lockbox/can_be_inserted(obj/item/W as obj, stop_messages = 0) + if(!locked) + return ..() + if(!stop_messages) + to_chat(usr, "[src] is locked!") + return 0 + +/obj/item/storage/lockbox/emag_act(user as mob) + if(!broken) + broken = 1 + locked = 0 + desc = "It appears to be broken." + icon_state = icon_broken + to_chat(user, "You unlock \the [src].") + origin_tech = null //wipe out any origin tech if it's unlocked in any way so you can't double-dip tech levels at R&D. + return + +/obj/item/storage/lockbox/hear_talk(mob/living/M as mob, list/message_pieces) + +/obj/item/storage/lockbox/hear_message(mob/living/M as mob, msg) + +/obj/item/storage/lockbox/mindshield + name = "Lockbox (Mindshield Implants)" + req_access = list(ACCESS_SECURITY) + +/obj/item/storage/lockbox/mindshield/New() + ..() + new /obj/item/implantcase/mindshield(src) + new /obj/item/implantcase/mindshield(src) + new /obj/item/implantcase/mindshield(src) + new /obj/item/implanter/mindshield(src) + +/obj/item/storage/lockbox/clusterbang + name = "lockbox (clusterbang)" + desc = "You have a bad feeling about opening this." + req_access = list(ACCESS_SECURITY) + +/obj/item/storage/lockbox/clusterbang/New() + ..() + new /obj/item/grenade/clusterbuster(src) + +/obj/item/storage/lockbox/medal + name = "medal box" + desc = "A locked box used to store medals of honor." + icon_state = "medalbox+l" + item_state = "syringe_kit" + w_class = WEIGHT_CLASS_NORMAL + max_w_class = WEIGHT_CLASS_SMALL + max_combined_w_class = 20 + storage_slots = 12 + req_access = list(ACCESS_CAPTAIN) + icon_locked = "medalbox+l" + icon_closed = "medalbox" + icon_broken = "medalbox+b" + +/obj/item/storage/lockbox/medal/New() + ..() + new /obj/item/clothing/accessory/medal/gold/captain(src) + new /obj/item/clothing/accessory/medal/silver/leadership(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/heart(src) + +/obj/item/storage/lockbox/t4 + name = "lockbox (T4)" + desc = "Contains three T4 breaching charges." + req_access = list(ACCESS_CENT_SPECOPS) + +/obj/item/storage/lockbox/t4/New() + ..() + for(var/i in 0 to 2) + new /obj/item/grenade/plastic/x4/thermite(src) + +/obj/item/storage/lockbox/research + +/obj/item/storage/lockbox/research/deconstruct(disassembled = TRUE) // Get wrecked, Science nerds + qdel(src) + +/obj/item/storage/lockbox/research/large + name = "Large lockbox" + desc = "A large lockbox" + max_w_class = WEIGHT_CLASS_BULKY + max_combined_w_class = 4 //The sum of the w_classes of all the items in this storage item. + storage_slots = 1 diff --git a/code/game/objects/items/weapons/storage/secure.dm b/code/game/objects/items/weapons/storage/secure.dm index b3736a22620..d38c3c97dac 100644 --- a/code/game/objects/items/weapons/storage/secure.dm +++ b/code/game/objects/items/weapons/storage/secure.dm @@ -1,250 +1,250 @@ -/* - * Absorbs /obj/item/secstorage. - * Reimplements it only slightly to use existing storage functionality. - * - * Contains: - * Secure Briefcase - * Wall Safe - */ - -// ----------------------------- -// Generic Item -// ----------------------------- -/obj/item/storage/secure - name = "secstorage" - var/icon_locking = "secureb" - var/icon_sparking = "securespark" - var/icon_opened = "secure0" - var/locked = 1 - var/code = "" - var/l_code = null - var/l_set = 0 - var/l_setshort = 0 - var/l_hacking = 0 - var/emagged = 0 - var/open = 0 - w_class = WEIGHT_CLASS_NORMAL - max_w_class = WEIGHT_CLASS_SMALL - max_combined_w_class = 14 - -/obj/item/storage/secure/examine(mob/user) - . = ..() - if(in_range(user, src)) - . += "The service panel is [open ? "open" : "closed"]." - -/obj/item/storage/secure/attackby(obj/item/W as obj, mob/user as mob, params) - if(locked) - if((istype(W, /obj/item/melee/energy/blade)) && (!emagged)) - emag_act(user, W) - - if(istype(W, /obj/item/screwdriver)) - if(do_after(user, 20 * W.toolspeed, target = src)) - open = !open - user.show_message("You [open ? "open" : "close"] the service panel.", 1) - return - - if((istype(W, /obj/item/multitool)) && (open == 1) && (!l_hacking)) - user.show_message("Now attempting to reset internal memory, please hold.", 1) - l_hacking = 1 - if(do_after(usr, 100 * W.toolspeed, target = src)) - if(prob(40)) - l_setshort = 1 - l_set = 0 - user.show_message("Internal memory reset. Please give it a few seconds to reinitialize.", 1) - sleep(80) - l_setshort = 0 - l_hacking = 0 - else - user.show_message("Unable to reset internal memory.", 1) - l_hacking = 0 - else - l_hacking = 0 - return - //At this point you have exhausted all the special things to do when locked - // ... but it's still locked. - return - - return ..() - -/obj/item/storage/secure/emag_act(user as mob, weapon as obj) - if(!emagged) - emagged = 1 - overlays += image('icons/obj/storage.dmi', icon_sparking) - sleep(6) - overlays = null - overlays += image('icons/obj/storage.dmi', icon_locking) - locked = 0 - if(istype(weapon, /obj/item/melee/energy/blade)) - do_sparks(5, 0, loc) - playsound(loc, 'sound/weapons/blade1.ogg', 50, 1) - playsound(loc, "sparks", 50, 1) - to_chat(user, "You slice through the lock on [src].") - else - to_chat(user, "You short out the lock on [src].") - return - -/obj/item/storage/secure/AltClick(mob/user) - if(!try_to_open()) - return FALSE - return ..() - -/obj/item/storage/secure/MouseDrop(over_object, src_location, over_location) - if(!try_to_open()) - return FALSE - return ..() - -/obj/item/storage/secure/proc/try_to_open() - if(locked) - add_fingerprint(usr) - to_chat(usr, "It's locked!") - return FALSE - -/obj/item/storage/secure/attack_self(mob/user as mob) - user.set_machine(src) - var/dat = text("[]
        \n\nLock Status: []", src, (locked ? "LOCKED" : "UNLOCKED")) - var/message = "Code" - if((l_set == 0) && (!emagged) && (!l_setshort)) - dat += text("

        \n5-DIGIT PASSCODE NOT SET.
        ENTER NEW PASSCODE.
        ") - if(emagged) - dat += text("

        \nLOCKING SYSTEM ERROR - 1701") - if(l_setshort) - dat += text("

        \nALERT: MEMORY SYSTEM ERROR - 6040 201") - message = text("[]", code) - if(!locked) - message = "*****" - dat += {"


        \n>[message]
        \n - 1- - 2- - 3
        \n - 4- - 5- - 6
        \n - 7- - 8- - 9
        \n - R- - 0- - E
        \n
        "} - user << browse(dat, "window=caselock;size=300x280") - -/obj/item/storage/secure/Topic(href, href_list) - ..() - if(usr.incapacitated() || (get_dist(src, usr) > 1)) - return - if(href_list["type"]) - if(href_list["type"] == "E") - if((l_set == 0) && (length(code) == 5) && (!l_setshort) && (code != "ERROR")) - l_code = code - l_set = 1 - else if((code == l_code) && (emagged == 0) && (l_set == 1)) - locked = 0 - overlays = null - overlays += image('icons/obj/storage.dmi', icon_opened) - code = null - else - code = "ERROR" - else - if((href_list["type"] == "R") && (emagged == 0) && (!l_setshort)) - locked = 1 - overlays = null - code = null - close(usr) - else - code += text("[]", href_list["type"]) - if(length(code) > 5) - code = "ERROR" - add_fingerprint(usr) - for(var/mob/M in viewers(1, loc)) - if((M.client && M.machine == src)) - attack_self(M) - return - return - -/obj/item/storage/secure/can_be_inserted(obj/item/W as obj, stop_messages = 0) - if(!locked) - return ..() - if(!stop_messages) - to_chat(usr, "[src] is locked!") - return 0 - -/obj/item/storage/secure/hear_talk(mob/living/M as mob, list/message_pieces) - return - -/obj/item/storage/secure/hear_message(mob/living/M as mob, msg) - return - -// ----------------------------- -// Secure Briefcase -// ----------------------------- -/obj/item/storage/secure/briefcase - name = "secure briefcase" - desc = "A large briefcase with a digital locking system." - icon = 'icons/obj/storage.dmi' - icon_state = "secure" - item_state = "sec-case" - flags = CONDUCT - hitsound = "swing_hit" - force = 8 - throw_speed = 2 - throw_range = 4 - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 21 - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - -/obj/item/storage/secure/briefcase/New() - ..() - handle_item_insertion(new /obj/item/paper, 1) - handle_item_insertion(new /obj/item/pen, 1) - -/obj/item/storage/secure/briefcase/attack_hand(mob/user as mob) - if((loc == user) && (locked == 1)) - to_chat(usr, "[src] is locked and cannot be opened!") - else if((loc == user) && !locked) - playsound(loc, "rustle", 50, 1, -5) - if(user.s_active) - user.s_active.close(user) //Close and re-open - show_to(user) - else - ..() - for(var/mob/M in range(1)) - if(M.s_active == src) - close(M) - orient2hud(user) - add_fingerprint(user) - return - -//Syndie variant of Secure Briefcase. Contains space cash, slightly more robust. -/obj/item/storage/secure/briefcase/syndie - force = 15 - -/obj/item/storage/secure/briefcase/syndie/New() - ..() - for(var/i = 0, i < storage_slots - 2, i++) - handle_item_insertion(new /obj/item/stack/spacecash/c1000, 1) - -// ----------------------------- -// Secure Safe -// ----------------------------- - -/obj/item/storage/secure/safe - name = "secure safe" - icon = 'icons/obj/storage.dmi' - icon_state = "safe" - icon_opened = "safe0" - icon_locking = "safeb" - icon_sparking = "safespark" - force = 8 - w_class = WEIGHT_CLASS_HUGE - max_w_class = 8 - anchored = 1 - density = 0 - cant_hold = list(/obj/item/storage/secure/briefcase) - -/obj/item/storage/secure/safe/New() - ..() - handle_item_insertion(new /obj/item/paper, 1) - handle_item_insertion(new /obj/item/pen, 1) - -/obj/item/storage/secure/safe/attack_hand(mob/user as mob) - return attack_self(user) +/* + * Absorbs /obj/item/secstorage. + * Reimplements it only slightly to use existing storage functionality. + * + * Contains: + * Secure Briefcase + * Wall Safe + */ + +// ----------------------------- +// Generic Item +// ----------------------------- +/obj/item/storage/secure + name = "secstorage" + var/icon_locking = "secureb" + var/icon_sparking = "securespark" + var/icon_opened = "secure0" + var/locked = 1 + var/code = "" + var/l_code = null + var/l_set = 0 + var/l_setshort = 0 + var/l_hacking = 0 + var/emagged = 0 + var/open = 0 + w_class = WEIGHT_CLASS_NORMAL + max_w_class = WEIGHT_CLASS_SMALL + max_combined_w_class = 14 + +/obj/item/storage/secure/examine(mob/user) + . = ..() + if(in_range(user, src)) + . += "The service panel is [open ? "open" : "closed"]." + +/obj/item/storage/secure/attackby(obj/item/W as obj, mob/user as mob, params) + if(locked) + if((istype(W, /obj/item/melee/energy/blade)) && (!emagged)) + emag_act(user, W) + + if(istype(W, /obj/item/screwdriver)) + if(do_after(user, 20 * W.toolspeed, target = src)) + open = !open + user.show_message("You [open ? "open" : "close"] the service panel.", 1) + return + + if((istype(W, /obj/item/multitool)) && (open == 1) && (!l_hacking)) + user.show_message("Now attempting to reset internal memory, please hold.", 1) + l_hacking = 1 + if(do_after(usr, 100 * W.toolspeed, target = src)) + if(prob(40)) + l_setshort = 1 + l_set = 0 + user.show_message("Internal memory reset. Please give it a few seconds to reinitialize.", 1) + sleep(80) + l_setshort = 0 + l_hacking = 0 + else + user.show_message("Unable to reset internal memory.", 1) + l_hacking = 0 + else + l_hacking = 0 + return + //At this point you have exhausted all the special things to do when locked + // ... but it's still locked. + return + + return ..() + +/obj/item/storage/secure/emag_act(user as mob, weapon as obj) + if(!emagged) + emagged = 1 + overlays += image('icons/obj/storage.dmi', icon_sparking) + sleep(6) + overlays = null + overlays += image('icons/obj/storage.dmi', icon_locking) + locked = 0 + if(istype(weapon, /obj/item/melee/energy/blade)) + do_sparks(5, 0, loc) + playsound(loc, 'sound/weapons/blade1.ogg', 50, 1) + playsound(loc, "sparks", 50, 1) + to_chat(user, "You slice through the lock on [src].") + else + to_chat(user, "You short out the lock on [src].") + return + +/obj/item/storage/secure/AltClick(mob/user) + if(!try_to_open()) + return FALSE + return ..() + +/obj/item/storage/secure/MouseDrop(over_object, src_location, over_location) + if(!try_to_open()) + return FALSE + return ..() + +/obj/item/storage/secure/proc/try_to_open() + if(locked) + add_fingerprint(usr) + to_chat(usr, "It's locked!") + return FALSE + +/obj/item/storage/secure/attack_self(mob/user as mob) + user.set_machine(src) + var/dat = text("[]
        \n\nLock Status: []", src, (locked ? "LOCKED" : "UNLOCKED")) + var/message = "Code" + if((l_set == 0) && (!emagged) && (!l_setshort)) + dat += text("

        \n5-DIGIT PASSCODE NOT SET.
        ENTER NEW PASSCODE.
        ") + if(emagged) + dat += text("

        \nLOCKING SYSTEM ERROR - 1701") + if(l_setshort) + dat += text("

        \nALERT: MEMORY SYSTEM ERROR - 6040 201") + message = text("[]", code) + if(!locked) + message = "*****" + dat += {"


        \n>[message]
        \n + 1- + 2- + 3
        \n + 4- + 5- + 6
        \n + 7- + 8- + 9
        \n + R- + 0- + E
        \n
        "} + user << browse(dat, "window=caselock;size=300x280") + +/obj/item/storage/secure/Topic(href, href_list) + ..() + if(usr.incapacitated() || (get_dist(src, usr) > 1)) + return + if(href_list["type"]) + if(href_list["type"] == "E") + if((l_set == 0) && (length(code) == 5) && (!l_setshort) && (code != "ERROR")) + l_code = code + l_set = 1 + else if((code == l_code) && (emagged == 0) && (l_set == 1)) + locked = 0 + overlays = null + overlays += image('icons/obj/storage.dmi', icon_opened) + code = null + else + code = "ERROR" + else + if((href_list["type"] == "R") && (emagged == 0) && (!l_setshort)) + locked = 1 + overlays = null + code = null + close(usr) + else + code += text("[]", href_list["type"]) + if(length(code) > 5) + code = "ERROR" + add_fingerprint(usr) + for(var/mob/M in viewers(1, loc)) + if((M.client && M.machine == src)) + attack_self(M) + return + return + +/obj/item/storage/secure/can_be_inserted(obj/item/W as obj, stop_messages = 0) + if(!locked) + return ..() + if(!stop_messages) + to_chat(usr, "[src] is locked!") + return 0 + +/obj/item/storage/secure/hear_talk(mob/living/M as mob, list/message_pieces) + return + +/obj/item/storage/secure/hear_message(mob/living/M as mob, msg) + return + +// ----------------------------- +// Secure Briefcase +// ----------------------------- +/obj/item/storage/secure/briefcase + name = "secure briefcase" + desc = "A large briefcase with a digital locking system." + icon = 'icons/obj/storage.dmi' + icon_state = "secure" + item_state = "sec-case" + flags = CONDUCT + hitsound = "swing_hit" + force = 8 + throw_speed = 2 + throw_range = 4 + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 21 + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + +/obj/item/storage/secure/briefcase/New() + ..() + handle_item_insertion(new /obj/item/paper, 1) + handle_item_insertion(new /obj/item/pen, 1) + +/obj/item/storage/secure/briefcase/attack_hand(mob/user as mob) + if((loc == user) && (locked == 1)) + to_chat(usr, "[src] is locked and cannot be opened!") + else if((loc == user) && !locked) + playsound(loc, "rustle", 50, 1, -5) + if(user.s_active) + user.s_active.close(user) //Close and re-open + show_to(user) + else + ..() + for(var/mob/M in range(1)) + if(M.s_active == src) + close(M) + orient2hud(user) + add_fingerprint(user) + return + +//Syndie variant of Secure Briefcase. Contains space cash, slightly more robust. +/obj/item/storage/secure/briefcase/syndie + force = 15 + +/obj/item/storage/secure/briefcase/syndie/New() + ..() + for(var/i = 0, i < storage_slots - 2, i++) + handle_item_insertion(new /obj/item/stack/spacecash/c1000, 1) + +// ----------------------------- +// Secure Safe +// ----------------------------- + +/obj/item/storage/secure/safe + name = "secure safe" + icon = 'icons/obj/storage.dmi' + icon_state = "safe" + icon_opened = "safe0" + icon_locking = "safeb" + icon_sparking = "safespark" + force = 8 + w_class = WEIGHT_CLASS_HUGE + max_w_class = 8 + anchored = 1 + density = 0 + cant_hold = list(/obj/item/storage/secure/briefcase) + +/obj/item/storage/secure/safe/New() + ..() + handle_item_insertion(new /obj/item/paper, 1) + handle_item_insertion(new /obj/item/pen, 1) + +/obj/item/storage/secure/safe/attack_hand(mob/user as mob) + return attack_self(user) diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm index 7cefb4dfd59..d79a121e685 100644 --- a/code/game/objects/items/weapons/storage/storage.dm +++ b/code/game/objects/items/weapons/storage/storage.dm @@ -1,581 +1,581 @@ -// To clarify: -// For use_to_pickup and allow_quick_gather functionality, -// see item/attackby() (/game/objects/items.dm, params) -// Do not remove this functionality without good reason, cough reagent_containers cough. -// -Sayu - - -/obj/item/storage - name = "storage" - icon = 'icons/obj/storage.dmi' - w_class = WEIGHT_CLASS_NORMAL - var/silent = 0 // No message on putting items in - var/list/can_hold = new/list() //List of objects which this item can store (if set, it can't store anything else) - var/list/cant_hold = new/list() //List of objects which this item can't store (in effect only if can_hold isn't set) - var/max_w_class = WEIGHT_CLASS_SMALL //Max size of objects that this object can store (in effect only if can_hold isn't set) - var/max_combined_w_class = 14 //The sum of the w_classes of all the items in this storage item. - var/storage_slots = 7 //The number of storage slots in this container. - var/obj/screen/storage/boxes = null - var/obj/screen/close/closer = null - var/use_to_pickup //Set this to make it possible to use this item in an inverse way, so you can have the item in your hand and click items on the floor to pick them up. - var/display_contents_with_number //Set this to make the storage item group contents of the same type and display them as a number. - var/allow_quick_empty //Set this variable to allow the object to have the 'empty' verb, which dumps all the contents on the floor. - var/allow_quick_gather //Set this variable to allow the object to have the 'toggle mode' verb, which quickly collects all items from a tile. - var/collection_mode = 1; //0 = pick one at a time, 1 = pick all on tile - var/use_sound = "rustle" //sound played when used. null for no sound. - -/obj/item/storage/MouseDrop(obj/over_object as obj) - if(ishuman(usr)) //so monkeys can take off their backpacks -- Urist - var/mob/M = usr - - if(istype(M.loc,/obj/mecha) || M.incapacitated(FALSE, TRUE, TRUE)) // Stops inventory actions in a mech as well as while being incapacitated - return - - if(over_object == M && Adjacent(M)) // this must come before the screen objects only block - orient2hud(M) // dunno why it wasn't before - if(M.s_active) - M.s_active.close(M) - show_to(M) - return - - if((istype(over_object, /obj/structure/table) || istype(over_object, /turf/simulated/floor)) \ - && contents.len && loc == usr && !usr.stat && !usr.restrained() && usr.canmove && over_object.Adjacent(usr) \ - && !istype(src, /obj/item/storage/lockbox)) - var/turf/T = get_turf(over_object) - if(istype(over_object, /turf/simulated/floor)) - if(get_turf(usr) != T) - return // Can only empty containers onto the floor under you - if("Yes" != alert(usr,"Empty \the [src] onto \the [T]?","Confirm","Yes","No")) - return - if(!(usr && over_object && contents.len && loc == usr && !usr.stat && !usr.restrained() && usr.canmove && get_turf(usr) == T)) - return // Something happened while the player was thinking - hide_from(usr) - usr.face_atom(over_object) - usr.visible_message("[usr] empties \the [src] onto \the [over_object].", - "You empty \the [src] onto \the [over_object].") - for(var/obj/item/I in contents) - remove_from_storage(I, T) - update_icon() // For content-sensitive icons - return - - if(!( istype(over_object, /obj/screen) )) - return ..() - if(!(src.loc == usr) || (src.loc && src.loc.loc == usr)) - return - playsound(src.loc, "rustle", 50, 1, -5) - if(!( M.restrained() ) && !( M.stat )) - switch(over_object.name) - if("r_hand") - if(!M.unEquip(src)) - return - M.put_in_r_hand(src) - if("l_hand") - if(!M.unEquip(src)) - return - M.put_in_l_hand(src) - src.add_fingerprint(usr) - return - if(over_object == usr && in_range(src, usr) || usr.contents.Find(src)) - if(usr.s_active) - usr.s_active.close(usr) - src.show_to(usr) - return - return - -/obj/item/storage/AltClick(mob/user) - if(Adjacent(user) && !user.incapacitated(FALSE, TRUE, TRUE)) - orient2hud(user) - if(user.s_active) - user.s_active.close(user) - show_to(user) - -/obj/item/storage/proc/return_inv() - - var/list/L = list( ) - - L += src.contents - - for(var/obj/item/storage/S in src) - L += S.return_inv() - for(var/obj/item/gift/G in src) - L += G.gift - if(istype(G.gift, /obj/item/storage)) - L += G.gift:return_inv() - for(var/obj/item/folder/F in src) - L += F.contents - return L - -/obj/item/storage/proc/show_to(mob/user as mob) - if(!user.client) - return - if(user.s_active != src) - for(var/obj/item/I in src) - if(I.on_found(user)) - return - if(user.s_active) - user.s_active.hide_from(user) - user.client.screen -= src.boxes - user.client.screen -= src.closer - user.client.screen -= src.contents - user.client.screen += src.boxes - user.client.screen += src.closer - user.client.screen += src.contents - user.s_active = src - return - -/obj/item/storage/proc/hide_from(mob/user as mob) - - if(!user.client) - return - user.client.screen -= src.boxes - user.client.screen -= src.closer - user.client.screen -= src.contents - if(user.s_active == src) - user.s_active = null - return - -/obj/item/storage/proc/open(mob/user as mob) - if(src.use_sound) - playsound(src.loc, src.use_sound, 50, 1, -5) - - orient2hud(user) - if(user.s_active) - user.s_active.close(user) - show_to(user) - -/obj/item/storage/proc/close(mob/user as mob) - - src.hide_from(user) - user.s_active = null - return - -//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. -//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. -/obj/item/storage/proc/orient_objs(tx, ty, mx, my) - var/cx = tx - var/cy = ty - src.boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" - for(var/obj/O in src.contents) - O.screen_loc = "[cx],[cy]" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx > mx) - cx = tx - cy-- - src.closer.screen_loc = "[mx+1],[my]" - return - -//This proc draws out the inventory and places the items on it. It uses the standard position. -/obj/item/storage/proc/standard_orient_objs(var/rows, var/cols, var/list/obj/item/display_contents) - var/cx = 4 - var/cy = 2+rows - src.boxes.screen_loc = "4:16,2:16 to [4+cols]:16,[2+rows]:16" - - if(display_contents_with_number) - for(var/datum/numbered_display/ND in display_contents) - ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE - ND.sample_object.screen_loc = "[cx]:16,[cy]:16" - ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" - ND.sample_object.layer = ABOVE_HUD_LAYER - ND.sample_object.plane = ABOVE_HUD_PLANE - cx++ - if(cx > (4+cols)) - cx = 4 - cy-- - else - for(var/obj/O in contents) - O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" - O.screen_loc = "[cx]:16,[cy]:16" - O.maptext = "" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx > (4+cols)) - cx = 4 - cy-- - src.closer.screen_loc = "[4+cols+1]:16,2:16" - return - -/datum/numbered_display - var/obj/item/sample_object - var/number - - New(obj/item/sample as obj) - if(!istype(sample)) - qdel(src) - sample_object = sample - number = 1 - -//This proc determins the size of the inventory to be displayed. Please touch it only if you know what you're doing. -/obj/item/storage/proc/orient2hud(mob/user as mob) - - var/adjusted_contents = contents.len - - //Numbered contents display - var/list/datum/numbered_display/numbered_contents - if(display_contents_with_number) - numbered_contents = list() - adjusted_contents = 0 - for(var/obj/item/I in contents) - var/found = 0 - for(var/datum/numbered_display/ND in numbered_contents) - if(ND.sample_object.type == I.type && ND.sample_object.name == I.name) - ND.number++ - found = 1 - break - if(!found) - adjusted_contents++ - numbered_contents.Add( new/datum/numbered_display(I) ) - - //var/mob/living/carbon/human/H = user - var/row_num = 0 - var/col_count = min(7,storage_slots) -1 - if(adjusted_contents > 7) - row_num = round((adjusted_contents-1) / 7) // 7 is the maximum allowed width. - src.standard_orient_objs(row_num, col_count, numbered_contents) - return - -//This proc return 1 if the item can be picked up and 0 if it can't. -//Set the stop_messages to stop it from printing messages -/obj/item/storage/proc/can_be_inserted(obj/item/W as obj, stop_messages = 0) - if(!istype(W) || (W.flags & ABSTRACT)) return //Not an item - - if(src.loc == W) - return 0 //Means the item is already in the storage item - if(contents.len >= storage_slots) - if(!stop_messages) - to_chat(usr, "[W] won't fit in [src], make some space!") - return 0 //Storage item is full - - if(can_hold.len) - if(!is_type_in_typecache(W, can_hold)) - if(!stop_messages) - to_chat(usr, "[src] cannot hold [W].") - return 0 - - if(is_type_in_typecache(W, cant_hold)) //Check for specific items which this container can't hold. - if(!stop_messages) - to_chat(usr, "[src] cannot hold [W].") - return 0 - - if(W.w_class > max_w_class) - if(!stop_messages) - to_chat(usr, "[W] is too big for [src].") - return 0 - - var/sum_w_class = W.w_class - for(var/obj/item/I in contents) - sum_w_class += I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. - - if(sum_w_class > max_combined_w_class) - if(!stop_messages) - to_chat(usr, "[src] is full, make some space.") - return 0 - - if(W.w_class >= src.w_class && (istype(W, /obj/item/storage))) - if(!istype(src, /obj/item/storage/backpack/holding)) //bohs should be able to hold backpacks again. The override for putting a boh in a boh is in backpack.dm. - if(!stop_messages) - to_chat(usr, "[src] cannot hold [W] as it's a storage item of the same size.") - return 0 //To prevent the stacking of same sized storage items. - - if(W.flags & NODROP) //SHOULD be handled in unEquip, but better safe than sorry. - to_chat(usr, "\the [W] is stuck to your hand, you can't put it in \the [src]") - return 0 - - return 1 - -//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() -//The stop_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, -//such as when picking up all the items on a tile with one click. -/obj/item/storage/proc/handle_item_insertion(obj/item/W as obj, prevent_warning = 0) - if(!istype(W)) - return 0 - if(usr) - if(!usr.unEquip(W)) - return 0 - usr.update_icons() //update our overlays - if(silent) - prevent_warning = 1 - W.forceMove(src) - W.on_enter_storage(src) - if(usr) - if(usr.client && usr.s_active != src) - usr.client.screen -= W - W.dropped(usr) - add_fingerprint(usr) - - if(!prevent_warning && !istype(W, /obj/item/gun/energy/kinetic_accelerator/crossbow)) - for(var/mob/M in viewers(usr, null)) - if(M == usr) - to_chat(usr, "You put the [W] into [src].") - else if(M in range(1)) //If someone is standing close enough, they can tell what it is... - M.show_message("[usr] puts [W] into [src].") - else if(W && W.w_class >= WEIGHT_CLASS_NORMAL) //Otherwise they can only see large or normal items from a distance... - M.show_message("[usr] puts [W] into [src].") - - src.orient2hud(usr) - if(usr.s_active) - usr.s_active.show_to(usr) - W.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt - W.in_inventory = TRUE - update_icon() - return 1 - -//Call this proc to handle the removal of an item from the storage item. The item will be moved to the atom sent as new_target -/obj/item/storage/proc/remove_from_storage(obj/item/W as obj, atom/new_location) - if(!istype(W)) return 0 - - if(istype(src, /obj/item/storage/fancy)) - var/obj/item/storage/fancy/F = src - F.update_icon(1) - - for(var/mob/M in range(1, src.loc)) - if(M.s_active == src) - if(M.client) - M.client.screen -= W - - if(new_location) - if(ismob(loc)) - W.dropped(usr) - if(ismob(new_location)) - W.layer = ABOVE_HUD_LAYER - W.plane = ABOVE_HUD_PLANE - else - W.layer = initial(W.layer) - W.plane = initial(W.plane) - W.forceMove(new_location) - else - W.forceMove(get_turf(src)) - - if(usr) - src.orient2hud(usr) - if(usr.s_active) - usr.s_active.show_to(usr) - if(W.maptext) - W.maptext = "" - W.on_exit_storage(src) - update_icon() - W.mouse_opacity = initial(W.mouse_opacity) - return 1 - -/obj/item/storage/Exited(atom/A, loc) - remove_from_storage(A, loc) //worry not, comrade; this only gets called once - ..() - -/obj/item/storage/deconstruct(disassembled = TRUE) - var/drop_loc = loc - if(ismob(loc)) - drop_loc = get_turf(src) - for(var/obj/item/I in contents) - remove_from_storage(I, drop_loc) - qdel(src) - -//This proc is called when you want to place an item into the storage item. -/obj/item/storage/attackby(obj/item/I, mob/user, params) - ..() - if(istype(I, /obj/item/hand_labeler)) - var/obj/item/hand_labeler/labeler = I - if(labeler.mode) - return FALSE - . = 1 //no afterattack - if(isrobot(user)) - return //Robots can't interact with storage items. - - if(!can_be_inserted(I)) - if(contents.len >= storage_slots) //don't use items on the backpack if they don't fit - return TRUE - return FALSE - - handle_item_insertion(I) - -/obj/item/storage/attack_hand(mob/user as mob) - playsound(src.loc, "rustle", 50, 1, -5) - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.l_store == src && !H.get_active_hand()) //Prevents opening if it's in a pocket. - H.put_in_hands(src) - H.l_store = null - return - if(H.r_store == src && !H.get_active_hand()) - H.put_in_hands(src) - H.r_store = null - return - - src.orient2hud(user) - if(src.loc == user) - if(user.s_active) - user.s_active.close(user) - src.show_to(user) - else - ..() - for(var/mob/M in range(1)) - if(M.s_active == src) - src.close(M) - src.add_fingerprint(user) - return - -/obj/item/storage/verb/toggle_gathering_mode() - set name = "Switch Gathering Method" - set category = "Object" - - collection_mode = !collection_mode - switch(collection_mode) - if(1) - to_chat(usr, "[src] now picks up all items in a tile at once.") - if(0) - to_chat(usr, "[src] now picks up one item at a time.") - - -/obj/item/storage/verb/quick_empty() - set name = "Empty Contents" - set category = "Object" - - if((!ishuman(usr) && (src.loc != usr)) || usr.stat || usr.restrained()) - return - - var/turf/T = get_turf(src) - hide_from(usr) - for(var/obj/item/I in contents) - remove_from_storage(I, T) - CHECK_TICK - -/obj/item/storage/New() - ..() - can_hold = typecacheof(can_hold) - cant_hold = typecacheof(cant_hold) - - if(allow_quick_empty) - verbs += /obj/item/storage/verb/quick_empty - else - verbs -= /obj/item/storage/verb/quick_empty - - if(allow_quick_gather) - verbs += /obj/item/storage/verb/toggle_gathering_mode - else - verbs -= /obj/item/storage/verb/toggle_gathering_mode - - boxes = new /obj/screen/storage( ) - boxes.name = "storage" - boxes.master = src - boxes.icon_state = "block" - boxes.screen_loc = "7,7 to 10,8" - boxes.layer = HUD_LAYER - boxes.plane = HUD_PLANE - closer = new /obj/screen/close( ) - closer.master = src - closer.icon_state = "backpack_close" - closer.layer = ABOVE_HUD_LAYER - closer.plane = ABOVE_HUD_PLANE - orient2hud() - -/obj/item/storage/Destroy() - for(var/obj/O in contents) - O.mouse_opacity = initial(O.mouse_opacity) - - QDEL_NULL(boxes) - QDEL_NULL(closer) - return ..() - -/obj/item/storage/emp_act(severity) - if(!istype(loc, /mob/living)) - for(var/obj/O in contents) - O.emp_act(severity) - ..() - -/obj/item/storage/hear_talk(mob/living/M as mob, list/message_pieces) - ..() - for(var/obj/O in contents) - O.hear_talk(M, message_pieces) - -/obj/item/storage/hear_message(mob/living/M as mob, msg) - ..() - for(var/obj/O in contents) - O.hear_message(M, msg) - -/obj/item/storage/attack_self(mob/user) - - //Clicking on itself will empty it, if it has the verb to do that. - if(user.is_in_active_hand(src)) - if(verbs.Find(/obj/item/storage/verb/quick_empty)) - quick_empty() - -//Returns the storage depth of an atom. This is the number of storage items the atom is contained in before reaching toplevel (the area). -//Returns -1 if the atom was not found on container. -/atom/proc/storage_depth(atom/container) - var/depth = 0 - var/atom/cur_atom = src - - while(cur_atom && !(cur_atom in container.contents)) - if(isarea(cur_atom)) - return -1 - if(istype(cur_atom.loc, /obj/item/storage)) - depth++ - cur_atom = cur_atom.loc - - if(!cur_atom) - return -1 //inside something with a null loc. - - return depth - -//Like storage depth, but returns the depth to the nearest turf -//Returns -1 if no top level turf (a loc was null somewhere, or a non-turf atom's loc was an area somehow). -/atom/proc/storage_depth_turf() - var/depth = 0 - var/atom/cur_atom = src - - while(cur_atom && !isturf(cur_atom)) - if(isarea(cur_atom)) - return -1 - if(istype(cur_atom.loc, /obj/item/storage)) - depth++ - cur_atom = cur_atom.loc - - if(!cur_atom) - return -1 //inside something with a null loc. - - return depth - -/obj/item/storage/serialize() - var data = ..() - var/list/content_list = list() - data["content"] = content_list - data["slots"] = storage_slots - data["max_w_class"] = max_w_class - data["max_c_w_class"] = max_combined_w_class - for(var/thing in contents) - var/atom/movable/AM = thing - // This code does not watch out for infinite loops - // But then again a tesseract would destroy the server anyways - // Also I wish I could just insert a list instead of it reading it the wrong way - content_list.len++ - content_list[content_list.len] = AM.serialize() - return data - -/obj/item/storage/deserialize(list/data) - if(isnum(data["slots"])) - storage_slots = data["slots"] - if(isnum(data["max_w_class"])) - max_w_class = data["max_w_class"] - if(isnum(data["max_c_w_class"])) - max_combined_w_class = data["max_c_w_class"] - for(var/thing in contents) - qdel(thing) // out with the old - for(var/thing in data["content"]) - if(islist(thing)) - list_to_object(thing, src) - else if(thing == null) - log_runtime(EXCEPTION("Null entry found in storage/deserialize."), src) - else - log_runtime(EXCEPTION("Non-list thing found in storage/deserialize."), src, list("Thing: [thing]")) - ..() - -/obj/item/storage/AllowDrop() - return TRUE - -/obj/item/storage/ex_act(severity) - for(var/atom/A in contents) - A.ex_act(severity) - CHECK_TICK - ..() \ No newline at end of file +// To clarify: +// For use_to_pickup and allow_quick_gather functionality, +// see item/attackby() (/game/objects/items.dm, params) +// Do not remove this functionality without good reason, cough reagent_containers cough. +// -Sayu + + +/obj/item/storage + name = "storage" + icon = 'icons/obj/storage.dmi' + w_class = WEIGHT_CLASS_NORMAL + var/silent = 0 // No message on putting items in + var/list/can_hold = new/list() //List of objects which this item can store (if set, it can't store anything else) + var/list/cant_hold = new/list() //List of objects which this item can't store (in effect only if can_hold isn't set) + var/max_w_class = WEIGHT_CLASS_SMALL //Max size of objects that this object can store (in effect only if can_hold isn't set) + var/max_combined_w_class = 14 //The sum of the w_classes of all the items in this storage item. + var/storage_slots = 7 //The number of storage slots in this container. + var/obj/screen/storage/boxes = null + var/obj/screen/close/closer = null + var/use_to_pickup //Set this to make it possible to use this item in an inverse way, so you can have the item in your hand and click items on the floor to pick them up. + var/display_contents_with_number //Set this to make the storage item group contents of the same type and display them as a number. + var/allow_quick_empty //Set this variable to allow the object to have the 'empty' verb, which dumps all the contents on the floor. + var/allow_quick_gather //Set this variable to allow the object to have the 'toggle mode' verb, which quickly collects all items from a tile. + var/collection_mode = 1; //0 = pick one at a time, 1 = pick all on tile + var/use_sound = "rustle" //sound played when used. null for no sound. + +/obj/item/storage/MouseDrop(obj/over_object as obj) + if(ishuman(usr)) //so monkeys can take off their backpacks -- Urist + var/mob/M = usr + + if(istype(M.loc,/obj/mecha) || M.incapacitated(FALSE, TRUE, TRUE)) // Stops inventory actions in a mech as well as while being incapacitated + return + + if(over_object == M && Adjacent(M)) // this must come before the screen objects only block + orient2hud(M) // dunno why it wasn't before + if(M.s_active) + M.s_active.close(M) + show_to(M) + return + + if((istype(over_object, /obj/structure/table) || istype(over_object, /turf/simulated/floor)) \ + && contents.len && loc == usr && !usr.stat && !usr.restrained() && usr.canmove && over_object.Adjacent(usr) \ + && !istype(src, /obj/item/storage/lockbox)) + var/turf/T = get_turf(over_object) + if(istype(over_object, /turf/simulated/floor)) + if(get_turf(usr) != T) + return // Can only empty containers onto the floor under you + if("Yes" != alert(usr,"Empty \the [src] onto \the [T]?","Confirm","Yes","No")) + return + if(!(usr && over_object && contents.len && loc == usr && !usr.stat && !usr.restrained() && usr.canmove && get_turf(usr) == T)) + return // Something happened while the player was thinking + hide_from(usr) + usr.face_atom(over_object) + usr.visible_message("[usr] empties \the [src] onto \the [over_object].", + "You empty \the [src] onto \the [over_object].") + for(var/obj/item/I in contents) + remove_from_storage(I, T) + update_icon() // For content-sensitive icons + return + + if(!( istype(over_object, /obj/screen) )) + return ..() + if(!(src.loc == usr) || (src.loc && src.loc.loc == usr)) + return + playsound(src.loc, "rustle", 50, 1, -5) + if(!( M.restrained() ) && !( M.stat )) + switch(over_object.name) + if("r_hand") + if(!M.unEquip(src)) + return + M.put_in_r_hand(src) + if("l_hand") + if(!M.unEquip(src)) + return + M.put_in_l_hand(src) + src.add_fingerprint(usr) + return + if(over_object == usr && in_range(src, usr) || usr.contents.Find(src)) + if(usr.s_active) + usr.s_active.close(usr) + src.show_to(usr) + return + return + +/obj/item/storage/AltClick(mob/user) + if(Adjacent(user) && !user.incapacitated(FALSE, TRUE, TRUE)) + orient2hud(user) + if(user.s_active) + user.s_active.close(user) + show_to(user) + +/obj/item/storage/proc/return_inv() + + var/list/L = list( ) + + L += src.contents + + for(var/obj/item/storage/S in src) + L += S.return_inv() + for(var/obj/item/gift/G in src) + L += G.gift + if(istype(G.gift, /obj/item/storage)) + L += G.gift:return_inv() + for(var/obj/item/folder/F in src) + L += F.contents + return L + +/obj/item/storage/proc/show_to(mob/user as mob) + if(!user.client) + return + if(user.s_active != src) + for(var/obj/item/I in src) + if(I.on_found(user)) + return + if(user.s_active) + user.s_active.hide_from(user) + user.client.screen -= src.boxes + user.client.screen -= src.closer + user.client.screen -= src.contents + user.client.screen += src.boxes + user.client.screen += src.closer + user.client.screen += src.contents + user.s_active = src + return + +/obj/item/storage/proc/hide_from(mob/user as mob) + + if(!user.client) + return + user.client.screen -= src.boxes + user.client.screen -= src.closer + user.client.screen -= src.contents + if(user.s_active == src) + user.s_active = null + return + +/obj/item/storage/proc/open(mob/user as mob) + if(src.use_sound) + playsound(src.loc, src.use_sound, 50, 1, -5) + + orient2hud(user) + if(user.s_active) + user.s_active.close(user) + show_to(user) + +/obj/item/storage/proc/close(mob/user as mob) + + src.hide_from(user) + user.s_active = null + return + +//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. +//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. +/obj/item/storage/proc/orient_objs(tx, ty, mx, my) + var/cx = tx + var/cy = ty + src.boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" + for(var/obj/O in src.contents) + O.screen_loc = "[cx],[cy]" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx > mx) + cx = tx + cy-- + src.closer.screen_loc = "[mx+1],[my]" + return + +//This proc draws out the inventory and places the items on it. It uses the standard position. +/obj/item/storage/proc/standard_orient_objs(var/rows, var/cols, var/list/obj/item/display_contents) + var/cx = 4 + var/cy = 2+rows + src.boxes.screen_loc = "4:16,2:16 to [4+cols]:16,[2+rows]:16" + + if(display_contents_with_number) + for(var/datum/numbered_display/ND in display_contents) + ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE + ND.sample_object.screen_loc = "[cx]:16,[cy]:16" + ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" + ND.sample_object.layer = ABOVE_HUD_LAYER + ND.sample_object.plane = ABOVE_HUD_PLANE + cx++ + if(cx > (4+cols)) + cx = 4 + cy-- + else + for(var/obj/O in contents) + O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" + O.screen_loc = "[cx]:16,[cy]:16" + O.maptext = "" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx > (4+cols)) + cx = 4 + cy-- + src.closer.screen_loc = "[4+cols+1]:16,2:16" + return + +/datum/numbered_display + var/obj/item/sample_object + var/number + + New(obj/item/sample as obj) + if(!istype(sample)) + qdel(src) + sample_object = sample + number = 1 + +//This proc determins the size of the inventory to be displayed. Please touch it only if you know what you're doing. +/obj/item/storage/proc/orient2hud(mob/user as mob) + + var/adjusted_contents = contents.len + + //Numbered contents display + var/list/datum/numbered_display/numbered_contents + if(display_contents_with_number) + numbered_contents = list() + adjusted_contents = 0 + for(var/obj/item/I in contents) + var/found = 0 + for(var/datum/numbered_display/ND in numbered_contents) + if(ND.sample_object.type == I.type && ND.sample_object.name == I.name) + ND.number++ + found = 1 + break + if(!found) + adjusted_contents++ + numbered_contents.Add( new/datum/numbered_display(I) ) + + //var/mob/living/carbon/human/H = user + var/row_num = 0 + var/col_count = min(7,storage_slots) -1 + if(adjusted_contents > 7) + row_num = round((adjusted_contents-1) / 7) // 7 is the maximum allowed width. + src.standard_orient_objs(row_num, col_count, numbered_contents) + return + +//This proc return 1 if the item can be picked up and 0 if it can't. +//Set the stop_messages to stop it from printing messages +/obj/item/storage/proc/can_be_inserted(obj/item/W as obj, stop_messages = 0) + if(!istype(W) || (W.flags & ABSTRACT)) return //Not an item + + if(src.loc == W) + return 0 //Means the item is already in the storage item + if(contents.len >= storage_slots) + if(!stop_messages) + to_chat(usr, "[W] won't fit in [src], make some space!") + return 0 //Storage item is full + + if(can_hold.len) + if(!is_type_in_typecache(W, can_hold)) + if(!stop_messages) + to_chat(usr, "[src] cannot hold [W].") + return 0 + + if(is_type_in_typecache(W, cant_hold)) //Check for specific items which this container can't hold. + if(!stop_messages) + to_chat(usr, "[src] cannot hold [W].") + return 0 + + if(W.w_class > max_w_class) + if(!stop_messages) + to_chat(usr, "[W] is too big for [src].") + return 0 + + var/sum_w_class = W.w_class + for(var/obj/item/I in contents) + sum_w_class += I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. + + if(sum_w_class > max_combined_w_class) + if(!stop_messages) + to_chat(usr, "[src] is full, make some space.") + return 0 + + if(W.w_class >= src.w_class && (istype(W, /obj/item/storage))) + if(!istype(src, /obj/item/storage/backpack/holding)) //bohs should be able to hold backpacks again. The override for putting a boh in a boh is in backpack.dm. + if(!stop_messages) + to_chat(usr, "[src] cannot hold [W] as it's a storage item of the same size.") + return 0 //To prevent the stacking of same sized storage items. + + if(W.flags & NODROP) //SHOULD be handled in unEquip, but better safe than sorry. + to_chat(usr, "\the [W] is stuck to your hand, you can't put it in \the [src]") + return 0 + + return 1 + +//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() +//The stop_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, +//such as when picking up all the items on a tile with one click. +/obj/item/storage/proc/handle_item_insertion(obj/item/W as obj, prevent_warning = 0) + if(!istype(W)) + return 0 + if(usr) + if(!usr.unEquip(W)) + return 0 + usr.update_icons() //update our overlays + if(silent) + prevent_warning = 1 + W.forceMove(src) + W.on_enter_storage(src) + if(usr) + if(usr.client && usr.s_active != src) + usr.client.screen -= W + W.dropped(usr) + add_fingerprint(usr) + + if(!prevent_warning && !istype(W, /obj/item/gun/energy/kinetic_accelerator/crossbow)) + for(var/mob/M in viewers(usr, null)) + if(M == usr) + to_chat(usr, "You put the [W] into [src].") + else if(M in range(1)) //If someone is standing close enough, they can tell what it is... + M.show_message("[usr] puts [W] into [src].") + else if(W && W.w_class >= WEIGHT_CLASS_NORMAL) //Otherwise they can only see large or normal items from a distance... + M.show_message("[usr] puts [W] into [src].") + + src.orient2hud(usr) + if(usr.s_active) + usr.s_active.show_to(usr) + W.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt + W.in_inventory = TRUE + update_icon() + return 1 + +//Call this proc to handle the removal of an item from the storage item. The item will be moved to the atom sent as new_target +/obj/item/storage/proc/remove_from_storage(obj/item/W as obj, atom/new_location) + if(!istype(W)) return 0 + + if(istype(src, /obj/item/storage/fancy)) + var/obj/item/storage/fancy/F = src + F.update_icon(1) + + for(var/mob/M in range(1, src.loc)) + if(M.s_active == src) + if(M.client) + M.client.screen -= W + + if(new_location) + if(ismob(loc)) + W.dropped(usr) + if(ismob(new_location)) + W.layer = ABOVE_HUD_LAYER + W.plane = ABOVE_HUD_PLANE + else + W.layer = initial(W.layer) + W.plane = initial(W.plane) + W.forceMove(new_location) + else + W.forceMove(get_turf(src)) + + if(usr) + src.orient2hud(usr) + if(usr.s_active) + usr.s_active.show_to(usr) + if(W.maptext) + W.maptext = "" + W.on_exit_storage(src) + update_icon() + W.mouse_opacity = initial(W.mouse_opacity) + return 1 + +/obj/item/storage/Exited(atom/A, loc) + remove_from_storage(A, loc) //worry not, comrade; this only gets called once + ..() + +/obj/item/storage/deconstruct(disassembled = TRUE) + var/drop_loc = loc + if(ismob(loc)) + drop_loc = get_turf(src) + for(var/obj/item/I in contents) + remove_from_storage(I, drop_loc) + qdel(src) + +//This proc is called when you want to place an item into the storage item. +/obj/item/storage/attackby(obj/item/I, mob/user, params) + ..() + if(istype(I, /obj/item/hand_labeler)) + var/obj/item/hand_labeler/labeler = I + if(labeler.mode) + return FALSE + . = 1 //no afterattack + if(isrobot(user)) + return //Robots can't interact with storage items. + + if(!can_be_inserted(I)) + if(contents.len >= storage_slots) //don't use items on the backpack if they don't fit + return TRUE + return FALSE + + handle_item_insertion(I) + +/obj/item/storage/attack_hand(mob/user as mob) + playsound(src.loc, "rustle", 50, 1, -5) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.l_store == src && !H.get_active_hand()) //Prevents opening if it's in a pocket. + H.put_in_hands(src) + H.l_store = null + return + if(H.r_store == src && !H.get_active_hand()) + H.put_in_hands(src) + H.r_store = null + return + + src.orient2hud(user) + if(src.loc == user) + if(user.s_active) + user.s_active.close(user) + src.show_to(user) + else + ..() + for(var/mob/M in range(1)) + if(M.s_active == src) + src.close(M) + src.add_fingerprint(user) + return + +/obj/item/storage/verb/toggle_gathering_mode() + set name = "Switch Gathering Method" + set category = "Object" + + collection_mode = !collection_mode + switch(collection_mode) + if(1) + to_chat(usr, "[src] now picks up all items in a tile at once.") + if(0) + to_chat(usr, "[src] now picks up one item at a time.") + + +/obj/item/storage/verb/quick_empty() + set name = "Empty Contents" + set category = "Object" + + if((!ishuman(usr) && (src.loc != usr)) || usr.stat || usr.restrained()) + return + + var/turf/T = get_turf(src) + hide_from(usr) + for(var/obj/item/I in contents) + remove_from_storage(I, T) + CHECK_TICK + +/obj/item/storage/New() + ..() + can_hold = typecacheof(can_hold) + cant_hold = typecacheof(cant_hold) + + if(allow_quick_empty) + verbs += /obj/item/storage/verb/quick_empty + else + verbs -= /obj/item/storage/verb/quick_empty + + if(allow_quick_gather) + verbs += /obj/item/storage/verb/toggle_gathering_mode + else + verbs -= /obj/item/storage/verb/toggle_gathering_mode + + boxes = new /obj/screen/storage( ) + boxes.name = "storage" + boxes.master = src + boxes.icon_state = "block" + boxes.screen_loc = "7,7 to 10,8" + boxes.layer = HUD_LAYER + boxes.plane = HUD_PLANE + closer = new /obj/screen/close( ) + closer.master = src + closer.icon_state = "backpack_close" + closer.layer = ABOVE_HUD_LAYER + closer.plane = ABOVE_HUD_PLANE + orient2hud() + +/obj/item/storage/Destroy() + for(var/obj/O in contents) + O.mouse_opacity = initial(O.mouse_opacity) + + QDEL_NULL(boxes) + QDEL_NULL(closer) + return ..() + +/obj/item/storage/emp_act(severity) + if(!istype(loc, /mob/living)) + for(var/obj/O in contents) + O.emp_act(severity) + ..() + +/obj/item/storage/hear_talk(mob/living/M as mob, list/message_pieces) + ..() + for(var/obj/O in contents) + O.hear_talk(M, message_pieces) + +/obj/item/storage/hear_message(mob/living/M as mob, msg) + ..() + for(var/obj/O in contents) + O.hear_message(M, msg) + +/obj/item/storage/attack_self(mob/user) + + //Clicking on itself will empty it, if it has the verb to do that. + if(user.is_in_active_hand(src)) + if(verbs.Find(/obj/item/storage/verb/quick_empty)) + quick_empty() + +//Returns the storage depth of an atom. This is the number of storage items the atom is contained in before reaching toplevel (the area). +//Returns -1 if the atom was not found on container. +/atom/proc/storage_depth(atom/container) + var/depth = 0 + var/atom/cur_atom = src + + while(cur_atom && !(cur_atom in container.contents)) + if(isarea(cur_atom)) + return -1 + if(istype(cur_atom.loc, /obj/item/storage)) + depth++ + cur_atom = cur_atom.loc + + if(!cur_atom) + return -1 //inside something with a null loc. + + return depth + +//Like storage depth, but returns the depth to the nearest turf +//Returns -1 if no top level turf (a loc was null somewhere, or a non-turf atom's loc was an area somehow). +/atom/proc/storage_depth_turf() + var/depth = 0 + var/atom/cur_atom = src + + while(cur_atom && !isturf(cur_atom)) + if(isarea(cur_atom)) + return -1 + if(istype(cur_atom.loc, /obj/item/storage)) + depth++ + cur_atom = cur_atom.loc + + if(!cur_atom) + return -1 //inside something with a null loc. + + return depth + +/obj/item/storage/serialize() + var data = ..() + var/list/content_list = list() + data["content"] = content_list + data["slots"] = storage_slots + data["max_w_class"] = max_w_class + data["max_c_w_class"] = max_combined_w_class + for(var/thing in contents) + var/atom/movable/AM = thing + // This code does not watch out for infinite loops + // But then again a tesseract would destroy the server anyways + // Also I wish I could just insert a list instead of it reading it the wrong way + content_list.len++ + content_list[content_list.len] = AM.serialize() + return data + +/obj/item/storage/deserialize(list/data) + if(isnum(data["slots"])) + storage_slots = data["slots"] + if(isnum(data["max_w_class"])) + max_w_class = data["max_w_class"] + if(isnum(data["max_c_w_class"])) + max_combined_w_class = data["max_c_w_class"] + for(var/thing in contents) + qdel(thing) // out with the old + for(var/thing in data["content"]) + if(islist(thing)) + list_to_object(thing, src) + else if(thing == null) + log_runtime(EXCEPTION("Null entry found in storage/deserialize."), src) + else + log_runtime(EXCEPTION("Non-list thing found in storage/deserialize."), src, list("Thing: [thing]")) + ..() + +/obj/item/storage/AllowDrop() + return TRUE + +/obj/item/storage/ex_act(severity) + for(var/atom/A in contents) + A.ex_act(severity) + CHECK_TICK + ..() diff --git a/code/game/objects/items/weapons/storage/toolbox.dm b/code/game/objects/items/weapons/storage/toolbox.dm index e8cc64af842..c95e61cbf14 100644 --- a/code/game/objects/items/weapons/storage/toolbox.dm +++ b/code/game/objects/items/weapons/storage/toolbox.dm @@ -1,137 +1,137 @@ -/obj/item/storage/toolbox - name = "toolbox" - desc = "Danger. Very robust." - icon = 'icons/obj/storage.dmi' - icon_state = "red" - item_state = "toolbox_red" - flags = CONDUCT - force = 10.0 - throwforce = 10.0 - throw_speed = 2 - throw_range = 7 - w_class = WEIGHT_CLASS_BULKY - materials = list(MAT_METAL = 500) - origin_tech = "combat=1;engineering=1" - attack_verb = list("robusted") - hitsound = 'sound/weapons/smash.ogg' - -/obj/item/storage/toolbox/emergency - name = "emergency toolbox" - icon_state = "red" - item_state = "toolbox_red" - -/obj/item/storage/toolbox/emergency/New() - ..() - new /obj/item/crowbar/red(src) - new /obj/item/weldingtool/mini(src) - new /obj/item/extinguisher/mini(src) - if(prob(50)) - new /obj/item/flashlight(src) - else - new /obj/item/flashlight/flare(src) - new /obj/item/radio(src) - -/obj/item/storage/toolbox/emergency/old - name = "rusty red toolbox" - icon_state = "toolbox_red_old" - -/obj/item/storage/toolbox/mechanical - name = "mechanical toolbox" - icon_state = "blue" - item_state = "toolbox_blue" - -/obj/item/storage/toolbox/mechanical/New() - ..() - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/analyzer(src) - new /obj/item/wirecutters(src) - -/obj/item/storage/toolbox/mechanical/greytide - flags = NODROP - -/obj/item/storage/toolbox/mechanical/old - name = "rusty blue toolbox" - icon_state = "toolbox_blue_old" - -/obj/item/storage/toolbox/electrical - name = "electrical toolbox" - icon_state = "yellow" - item_state = "toolbox_yellow" - -/obj/item/storage/toolbox/electrical/New() - ..() - var/pickedcolor = pick(COLOR_RED, COLOR_YELLOW, COLOR_GREEN, COLOR_BLUE, COLOR_PINK, COLOR_ORANGE, COLOR_CYAN, COLOR_WHITE) - new /obj/item/screwdriver(src) - new /obj/item/wirecutters(src) - new /obj/item/t_scanner(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor) - new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor) - if(prob(5)) - new /obj/item/clothing/gloves/color/yellow(src) - else - new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor) - -/obj/item/storage/toolbox/syndicate - name = "suspicious looking toolbox" - icon_state = "syndicate" - item_state = "toolbox_syndi" - origin_tech = "combat=2;syndicate=1;engineering=2" - silent = 1 - force = 15.0 - throwforce = 18.0 - -/obj/item/storage/toolbox/syndicate/New() - ..() - new /obj/item/screwdriver(src, "red") - new /obj/item/wrench(src) - new /obj/item/weldingtool/largetank(src) - new /obj/item/crowbar/red(src) - new /obj/item/wirecutters(src, "red") - new /obj/item/multitool(src) - new /obj/item/clothing/gloves/combat(src) - -/obj/item/storage/toolbox/fakesyndi - name = "suspicous looking toolbox" - icon_state = "syndicate" - item_state = "toolbox_syndi" - desc = "Danger. Very Robust. The paint is still wet." - -/obj/item/storage/toolbox/drone - name = "mechanical toolbox" - icon_state = "blue" - item_state = "toolbox_blue" - -/obj/item/storage/toolbox/drone/New() - ..() - var/pickedcolor = pick(pick(COLOR_RED, COLOR_YELLOW, COLOR_GREEN, COLOR_BLUE, COLOR_PINK, COLOR_ORANGE, COLOR_CYAN, COLOR_WHITE)) - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/weldingtool(src) - new /obj/item/crowbar(src) - new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - -/obj/item/storage/toolbox/brass - name = "brass box" - desc = "A huge brass box with several indentations in its surface." - icon_state = "brassbox" - item_state = null - resistance_flags = FIRE_PROOF | ACID_PROOF - w_class = WEIGHT_CLASS_HUGE - max_w_class = WEIGHT_CLASS_NORMAL - max_combined_w_class = 28 - storage_slots = 28 - attack_verb = list("robusted", "crushed", "smashed") - -/obj/item/storage/toolbox/brass/prefilled/New() - ..() - new /obj/item/screwdriver/brass(src) - new /obj/item/wirecutters/brass(src) - new /obj/item/wrench/brass(src) - new /obj/item/crowbar/brass(src) - new /obj/item/weldingtool/experimental/brass(src) +/obj/item/storage/toolbox + name = "toolbox" + desc = "Danger. Very robust." + icon = 'icons/obj/storage.dmi' + icon_state = "red" + item_state = "toolbox_red" + flags = CONDUCT + force = 10.0 + throwforce = 10.0 + throw_speed = 2 + throw_range = 7 + w_class = WEIGHT_CLASS_BULKY + materials = list(MAT_METAL = 500) + origin_tech = "combat=1;engineering=1" + attack_verb = list("robusted") + hitsound = 'sound/weapons/smash.ogg' + +/obj/item/storage/toolbox/emergency + name = "emergency toolbox" + icon_state = "red" + item_state = "toolbox_red" + +/obj/item/storage/toolbox/emergency/New() + ..() + new /obj/item/crowbar/red(src) + new /obj/item/weldingtool/mini(src) + new /obj/item/extinguisher/mini(src) + if(prob(50)) + new /obj/item/flashlight(src) + else + new /obj/item/flashlight/flare(src) + new /obj/item/radio(src) + +/obj/item/storage/toolbox/emergency/old + name = "rusty red toolbox" + icon_state = "toolbox_red_old" + +/obj/item/storage/toolbox/mechanical + name = "mechanical toolbox" + icon_state = "blue" + item_state = "toolbox_blue" + +/obj/item/storage/toolbox/mechanical/New() + ..() + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/analyzer(src) + new /obj/item/wirecutters(src) + +/obj/item/storage/toolbox/mechanical/greytide + flags = NODROP + +/obj/item/storage/toolbox/mechanical/old + name = "rusty blue toolbox" + icon_state = "toolbox_blue_old" + +/obj/item/storage/toolbox/electrical + name = "electrical toolbox" + icon_state = "yellow" + item_state = "toolbox_yellow" + +/obj/item/storage/toolbox/electrical/New() + ..() + var/pickedcolor = pick(COLOR_RED, COLOR_YELLOW, COLOR_GREEN, COLOR_BLUE, COLOR_PINK, COLOR_ORANGE, COLOR_CYAN, COLOR_WHITE) + new /obj/item/screwdriver(src) + new /obj/item/wirecutters(src) + new /obj/item/t_scanner(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor) + new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor) + if(prob(5)) + new /obj/item/clothing/gloves/color/yellow(src) + else + new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor) + +/obj/item/storage/toolbox/syndicate + name = "suspicious looking toolbox" + icon_state = "syndicate" + item_state = "toolbox_syndi" + origin_tech = "combat=2;syndicate=1;engineering=2" + silent = 1 + force = 15.0 + throwforce = 18.0 + +/obj/item/storage/toolbox/syndicate/New() + ..() + new /obj/item/screwdriver(src, "red") + new /obj/item/wrench(src) + new /obj/item/weldingtool/largetank(src) + new /obj/item/crowbar/red(src) + new /obj/item/wirecutters(src, "red") + new /obj/item/multitool(src) + new /obj/item/clothing/gloves/combat(src) + +/obj/item/storage/toolbox/fakesyndi + name = "suspicous looking toolbox" + icon_state = "syndicate" + item_state = "toolbox_syndi" + desc = "Danger. Very Robust. The paint is still wet." + +/obj/item/storage/toolbox/drone + name = "mechanical toolbox" + icon_state = "blue" + item_state = "toolbox_blue" + +/obj/item/storage/toolbox/drone/New() + ..() + var/pickedcolor = pick(pick(COLOR_RED, COLOR_YELLOW, COLOR_GREEN, COLOR_BLUE, COLOR_PINK, COLOR_ORANGE, COLOR_CYAN, COLOR_WHITE)) + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/weldingtool(src) + new /obj/item/crowbar(src) + new /obj/item/stack/cable_coil(src, 30, paramcolor = pickedcolor) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + +/obj/item/storage/toolbox/brass + name = "brass box" + desc = "A huge brass box with several indentations in its surface." + icon_state = "brassbox" + item_state = null + resistance_flags = FIRE_PROOF | ACID_PROOF + w_class = WEIGHT_CLASS_HUGE + max_w_class = WEIGHT_CLASS_NORMAL + max_combined_w_class = 28 + storage_slots = 28 + attack_verb = list("robusted", "crushed", "smashed") + +/obj/item/storage/toolbox/brass/prefilled/New() + ..() + new /obj/item/screwdriver/brass(src) + new /obj/item/wirecutters/brass(src) + new /obj/item/wrench/brass(src) + new /obj/item/crowbar/brass(src) + new /obj/item/weldingtool/experimental/brass(src) diff --git a/code/game/objects/items/weapons/swords_axes_etc.dm b/code/game/objects/items/weapons/swords_axes_etc.dm index 409e5575df3..e75935a3cc2 100644 --- a/code/game/objects/items/weapons/swords_axes_etc.dm +++ b/code/game/objects/items/weapons/swords_axes_etc.dm @@ -1,119 +1,119 @@ -/* Weapons - * Contains: - * Banhammer - * Classic Baton - */ - -/* - * Banhammer - */ -/obj/item/banhammer/attack(mob/M, mob/user) - to_chat(M, " You have been banned FOR NO REISIN by [user]") - to_chat(user, " You have BANNED [M]") - playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much - -/* - * Classic Baton - */ - -/obj/item/melee/classic_baton - name = "police baton" - desc = "A wooden truncheon for beating criminal scum." - icon_state = "baton" - item_state = "classic_baton" - slot_flags = SLOT_BELT - force = 12 //9 hit crit - w_class = WEIGHT_CLASS_NORMAL - var/cooldown = 0 - var/on = 1 - -/obj/item/melee/classic_baton/attack(mob/target as mob, mob/living/user as mob) - if(on) - add_fingerprint(user) - if((CLUMSY in user.mutations) && prob(50)) - to_chat(user, "You club yourself over the head.") - user.Weaken(3 * force) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - H.apply_damage(2*force, BRUTE, "head") - else - user.take_organ_damage(2*force) - return - if(isrobot(target)) - ..() - return - if(!isliving(target)) - return - if(user.a_intent == INTENT_HARM) - if(!..()) return - if(!isrobot(target)) return - else - if(cooldown <= 0) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) - return - if(check_martial_counter(H, user)) - return - playsound(get_turf(src), 'sound/effects/woodhit.ogg', 75, 1, -1) - target.Weaken(3) - add_attack_logs(user, target, "Stunned with [src]") - add_fingerprint(user) - target.visible_message("[user] has knocked down [target] with \the [src]!", \ - "[user] has knocked down [target] with \the [src]!") - if(!iscarbon(user)) - target.LAssailant = null - else - target.LAssailant = user - cooldown = 1 - spawn(40) - cooldown = 0 - return - else - return ..() - -/obj/item/melee/classic_baton/ntcane - name = "fancy cane" - desc = "A cane with special engraving on it. It seems well suited for fending off assailants..." - icon_state = "cane_nt" - item_state = "cane_nt" - needs_permit = 0 - -/obj/item/melee/classic_baton/ntcane/is_crutch() - return 1 - -//Telescopic baton -/obj/item/melee/classic_baton/telescopic - name = "telescopic baton" - desc = "A compact yet robust personal defense weapon. Can be concealed when folded." - icon_state = "telebaton_0" - item_state = null - slot_flags = SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - needs_permit = 0 - force = 0 - on = 0 - -/obj/item/melee/classic_baton/telescopic/attack_self(mob/user as mob) - on = !on - if(on) - to_chat(user, "You extend the baton.") - icon_state = "telebaton_1" - item_state = "nullrod" - w_class = WEIGHT_CLASS_BULKY //doesnt fit in backpack when its on for balance - force = 10 //stunbaton damage - attack_verb = list("smacked", "struck", "cracked", "beaten") - else - to_chat(user, "You collapse the baton.") - icon_state = "telebaton_0" - item_state = null //no sprite for concealment even when in hand - slot_flags = SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - force = 0 //not so robust now - attack_verb = list("hit", "poked") - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1) - add_fingerprint(user) +/* Weapons + * Contains: + * Banhammer + * Classic Baton + */ + +/* + * Banhammer + */ +/obj/item/banhammer/attack(mob/M, mob/user) + to_chat(M, " You have been banned FOR NO REISIN by [user]") + to_chat(user, " You have BANNED [M]") + playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much + +/* + * Classic Baton + */ + +/obj/item/melee/classic_baton + name = "police baton" + desc = "A wooden truncheon for beating criminal scum." + icon_state = "baton" + item_state = "classic_baton" + slot_flags = SLOT_BELT + force = 12 //9 hit crit + w_class = WEIGHT_CLASS_NORMAL + var/cooldown = 0 + var/on = 1 + +/obj/item/melee/classic_baton/attack(mob/target as mob, mob/living/user as mob) + if(on) + add_fingerprint(user) + if((CLUMSY in user.mutations) && prob(50)) + to_chat(user, "You club yourself over the head.") + user.Weaken(3 * force) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + H.apply_damage(2*force, BRUTE, "head") + else + user.take_organ_damage(2*force) + return + if(isrobot(target)) + ..() + return + if(!isliving(target)) + return + if(user.a_intent == INTENT_HARM) + if(!..()) return + if(!isrobot(target)) return + else + if(cooldown <= 0) + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) + return + if(check_martial_counter(H, user)) + return + playsound(get_turf(src), 'sound/effects/woodhit.ogg', 75, 1, -1) + target.Weaken(3) + add_attack_logs(user, target, "Stunned with [src]") + add_fingerprint(user) + target.visible_message("[user] has knocked down [target] with \the [src]!", \ + "[user] has knocked down [target] with \the [src]!") + if(!iscarbon(user)) + target.LAssailant = null + else + target.LAssailant = user + cooldown = 1 + spawn(40) + cooldown = 0 + return + else + return ..() + +/obj/item/melee/classic_baton/ntcane + name = "fancy cane" + desc = "A cane with special engraving on it. It seems well suited for fending off assailants..." + icon_state = "cane_nt" + item_state = "cane_nt" + needs_permit = 0 + +/obj/item/melee/classic_baton/ntcane/is_crutch() + return 1 + +//Telescopic baton +/obj/item/melee/classic_baton/telescopic + name = "telescopic baton" + desc = "A compact yet robust personal defense weapon. Can be concealed when folded." + icon_state = "telebaton_0" + item_state = null + slot_flags = SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + needs_permit = 0 + force = 0 + on = 0 + +/obj/item/melee/classic_baton/telescopic/attack_self(mob/user as mob) + on = !on + if(on) + to_chat(user, "You extend the baton.") + icon_state = "telebaton_1" + item_state = "nullrod" + w_class = WEIGHT_CLASS_BULKY //doesnt fit in backpack when its on for balance + force = 10 //stunbaton damage + attack_verb = list("smacked", "struck", "cracked", "beaten") + else + to_chat(user, "You collapse the baton.") + icon_state = "telebaton_0" + item_state = null //no sprite for concealment even when in hand + slot_flags = SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + force = 0 //not so robust now + attack_verb = list("hit", "poked") + if(istype(user,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.update_inv_l_hand() + H.update_inv_r_hand() + playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1) + add_fingerprint(user) diff --git a/code/game/objects/items/weapons/tanks/jetpack.dm b/code/game/objects/items/weapons/tanks/jetpack.dm index 40376248d61..bc783457e76 100644 --- a/code/game/objects/items/weapons/tanks/jetpack.dm +++ b/code/game/objects/items/weapons/tanks/jetpack.dm @@ -1,246 +1,246 @@ -/obj/item/tank/jetpack - name = "Jetpack (Empty)" - desc = "A tank of compressed gas for use as propulsion in zero-gravity areas. Use with caution." - icon_state = "jetpack" - w_class = WEIGHT_CLASS_BULKY - item_state = "jetpack" - distribute_pressure = ONE_ATMOSPHERE*O2STANDARD - var/datum/effect_system/trail_follow/ion/ion_trail - actions_types = list(/datum/action/item_action/set_internals, /datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) - var/on = 0 - var/stabilizers = 0 - var/volume_rate = 500 //Needed for borg jetpack transfer - -/obj/item/tank/jetpack/New() - ..() - ion_trail = new /datum/effect_system/trail_follow/ion() - ion_trail.set_up(src) - -/obj/item/tank/jetpack/Destroy() - QDEL_NULL(ion_trail) - return ..() - -/obj/item/tank/jetpack/ui_action_click(mob/user, actiontype) - if(actiontype == /datum/action/item_action/toggle_jetpack) - cycle(user) - else if(actiontype == /datum/action/item_action/jetpack_stabilization) - toggle_stabilization(user) - else - toggle_internals(user) - -/obj/item/tank/jetpack/proc/toggle_stabilization(mob/user) - if(on) - stabilizers = !stabilizers - to_chat(user, "You turn [src]'s stabilization [stabilizers ? "on" : "off"].") - - -/obj/item/tank/jetpack/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0 && air_contents.oxygen < 10) - . += "The meter on [src] indicates you are almost out of air!" - playsound(user, 'sound/effects/alert.ogg', 50, 1) - - -/obj/item/tank/jetpack/proc/cycle(mob/user) - if(user.incapacitated()) - return - - if(!on) - turn_on(user) - to_chat(user, "You turn the jetpack on.") - else - turn_off(user) - to_chat(user, "You turn the jetpack off.") - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - - -/obj/item/tank/jetpack/proc/turn_on(mob/user) - on = TRUE - icon_state = "[initial(icon_state)]-on" - ion_trail.start() - -/obj/item/tank/jetpack/proc/turn_off(mob/user) - on = FALSE - stabilizers = FALSE - icon_state = initial(icon_state) - ion_trail.stop() - - -/obj/item/tank/jetpack/proc/allow_thrust(num, mob/living/user) - if(!on) - return 0 - if((num < 0.005 || air_contents.total_moles() < num)) - turn_off(user) - return 0 - - var/datum/gas_mixture/removed = air_contents.remove(num) - if(removed.total_moles() < 0.005) - turn_off(user) - return 0 - - var/turf/T = get_turf(user) - T.assume_air(removed) - return 1 - -/obj/item/tank/jetpack/void - name = "Void Jetpack (Oxygen)" - desc = "It works well in a void." - icon_state = "jetpack-void" - item_state = "jetpack-void" - -/obj/item/tank/jetpack/void/New() - ..() - air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/jetpack/void/grey - name = "Void Jetpack (Oxygen)" - icon_state = "jetpack-void-grey" - -/obj/item/tank/jetpack/void/gold - name = "Retro Jetpack (Oxygen)" - icon_state = "jetpack-void-gold" - - -/obj/item/tank/jetpack/oxygen - name = "Jetpack (Oxygen)" - desc = "A tank of compressed oxygen for use as propulsion in zero-gravity areas. Use with caution." - icon_state = "jetpack" - item_state = "jetpack" - -/obj/item/tank/jetpack/oxygen/New() - ..() - air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/jetpack/oxygen/captain - name = "Captain's jetpack" - desc = "A compact, lightweight jetpack containing a high amount of compressed oxygen." - icon_state = "jetpack-captain" - item_state = "jetpack-captain" - volume = 90 - w_class = WEIGHT_CLASS_NORMAL - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF //steal objective items are hard to destroy. - -/obj/item/tank/jetpack/oxygen/harness - name = "jet harness (oxygen)" - desc = "A lightweight tactical harness, used by those who don't want to be weighed down by traditional jetpacks." - icon_state = "jetpack-mini" - item_state = "jetpack-mini" - volume = 40 - throw_range = 8 - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/tank/jetpack/oxygenblack - name = "Jetpack (Oxygen)" - desc = "A black tank of compressed oxygen for use as propulsion in zero-gravity areas. Use with caution." - icon_state = "jetpack-black" - item_state = "jetpack-black" - -/obj/item/tank/jetpack/oxygenblack/New() - ..() - air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/jetpack/carbondioxide - name = "Jetpack (Carbon Dioxide)" - desc = "A tank of compressed carbon dioxide for use as propulsion in zero-gravity areas. Painted black to indicate that it should not be used as a source for internals." - distribute_pressure = 0 - icon_state = "jetpack-black" - item_state = "jetpack-black" - -/obj/item/tank/jetpack/carbondioxide/New() - ..() - ion_trail = new /datum/effect_system/trail_follow/ion() - ion_trail.set_up(src) - air_contents.carbon_dioxide = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/jetpack/carbondioxide/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0 && air_contents.carbon_dioxide < 10) - . += "The meter on [src] indicates you are almost out of air!" - playsound(user, 'sound/effects/alert.ogg', 50, 1) - -/obj/item/tank/jetpack/suit - name = "hardsuit jetpack upgrade" - desc = "A modular, compact set of thrusters designed to integrate with a hardsuit. It is fueled by a tank inserted into the suit's storage compartment." - icon_state = "jetpack-mining" - item_state = "jetpack-black" - origin_tech = "materials=4;magnets=4;engineering=5" - w_class = WEIGHT_CLASS_NORMAL - actions_types = list(/datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) - volume = 1 - slot_flags = null - var/datum/gas_mixture/temp_air_contents - var/obj/item/tank/tank = null - var/mob/living/carbon/human/cur_user - -/obj/item/tank/jetpack/suit/New() - ..() - STOP_PROCESSING(SSobj, src) - temp_air_contents = air_contents - -/obj/item/tank/jetpack/suit/attack_self() - return - -/obj/item/tank/jetpack/suit/cycle(mob/user) - if(!istype(loc, /obj/item/clothing/suit/space/hardsuit)) - to_chat(user, "[src] must be connected to a hardsuit!") - return - - var/mob/living/carbon/human/H = user - if(!istype(H.s_store, /obj/item/tank)) - to_chat(user, "You need a tank in your suit storage!") - return - ..() - -/obj/item/tank/jetpack/suit/turn_on(mob/user) - if(!istype(loc, /obj/item/clothing/suit/space/hardsuit) || !ishuman(loc.loc) || loc.loc != user) - return - var/mob/living/carbon/human/H = user - tank = H.s_store - air_contents = tank.air_contents - START_PROCESSING(SSobj, src) - cur_user = user - ..() - -/obj/item/tank/jetpack/suit/turn_off(mob/user) - tank = null - air_contents = temp_air_contents - STOP_PROCESSING(SSobj, src) - cur_user = null - ..() - -/obj/item/tank/jetpack/suit/process() - if(!istype(loc, /obj/item/clothing/suit/space/hardsuit) || !ishuman(loc.loc)) - turn_off(cur_user) - return - var/mob/living/carbon/human/H = loc.loc - if(!tank || tank != H.s_store) - turn_off(cur_user) - return - ..() - -/obj/item/tank/jetpack/rig - name = "jetpack" - var/obj/item/rig/holder - actions_types = list(/datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) - -/obj/item/tank/jetpack/rig/examine() - . = list("It's a jetpack. If you can see this, report it on the bug tracker.") - -/obj/item/tank/jetpack/rig/allow_thrust(num, mob/living/user) - if(!on) - return 0 - - if(!istype(holder) || !holder.air_supply) - return 0 - - var/datum/gas_mixture/removed = holder.air_supply.air_contents.remove(num) - if(removed.total_moles() < 0.005) - turn_off(user) - return 0 - - var/turf/T = get_turf(user) - T.assume_air(removed) - - return 1 +/obj/item/tank/jetpack + name = "Jetpack (Empty)" + desc = "A tank of compressed gas for use as propulsion in zero-gravity areas. Use with caution." + icon_state = "jetpack" + w_class = WEIGHT_CLASS_BULKY + item_state = "jetpack" + distribute_pressure = ONE_ATMOSPHERE*O2STANDARD + var/datum/effect_system/trail_follow/ion/ion_trail + actions_types = list(/datum/action/item_action/set_internals, /datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) + var/on = 0 + var/stabilizers = 0 + var/volume_rate = 500 //Needed for borg jetpack transfer + +/obj/item/tank/jetpack/New() + ..() + ion_trail = new /datum/effect_system/trail_follow/ion() + ion_trail.set_up(src) + +/obj/item/tank/jetpack/Destroy() + QDEL_NULL(ion_trail) + return ..() + +/obj/item/tank/jetpack/ui_action_click(mob/user, actiontype) + if(actiontype == /datum/action/item_action/toggle_jetpack) + cycle(user) + else if(actiontype == /datum/action/item_action/jetpack_stabilization) + toggle_stabilization(user) + else + toggle_internals(user) + +/obj/item/tank/jetpack/proc/toggle_stabilization(mob/user) + if(on) + stabilizers = !stabilizers + to_chat(user, "You turn [src]'s stabilization [stabilizers ? "on" : "off"].") + + +/obj/item/tank/jetpack/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0 && air_contents.oxygen < 10) + . += "The meter on [src] indicates you are almost out of air!" + playsound(user, 'sound/effects/alert.ogg', 50, 1) + + +/obj/item/tank/jetpack/proc/cycle(mob/user) + if(user.incapacitated()) + return + + if(!on) + turn_on(user) + to_chat(user, "You turn the jetpack on.") + else + turn_off(user) + to_chat(user, "You turn the jetpack off.") + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + + +/obj/item/tank/jetpack/proc/turn_on(mob/user) + on = TRUE + icon_state = "[initial(icon_state)]-on" + ion_trail.start() + +/obj/item/tank/jetpack/proc/turn_off(mob/user) + on = FALSE + stabilizers = FALSE + icon_state = initial(icon_state) + ion_trail.stop() + + +/obj/item/tank/jetpack/proc/allow_thrust(num, mob/living/user) + if(!on) + return 0 + if((num < 0.005 || air_contents.total_moles() < num)) + turn_off(user) + return 0 + + var/datum/gas_mixture/removed = air_contents.remove(num) + if(removed.total_moles() < 0.005) + turn_off(user) + return 0 + + var/turf/T = get_turf(user) + T.assume_air(removed) + return 1 + +/obj/item/tank/jetpack/void + name = "Void Jetpack (Oxygen)" + desc = "It works well in a void." + icon_state = "jetpack-void" + item_state = "jetpack-void" + +/obj/item/tank/jetpack/void/New() + ..() + air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/jetpack/void/grey + name = "Void Jetpack (Oxygen)" + icon_state = "jetpack-void-grey" + +/obj/item/tank/jetpack/void/gold + name = "Retro Jetpack (Oxygen)" + icon_state = "jetpack-void-gold" + + +/obj/item/tank/jetpack/oxygen + name = "Jetpack (Oxygen)" + desc = "A tank of compressed oxygen for use as propulsion in zero-gravity areas. Use with caution." + icon_state = "jetpack" + item_state = "jetpack" + +/obj/item/tank/jetpack/oxygen/New() + ..() + air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/jetpack/oxygen/captain + name = "Captain's jetpack" + desc = "A compact, lightweight jetpack containing a high amount of compressed oxygen." + icon_state = "jetpack-captain" + item_state = "jetpack-captain" + volume = 90 + w_class = WEIGHT_CLASS_NORMAL + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF //steal objective items are hard to destroy. + +/obj/item/tank/jetpack/oxygen/harness + name = "jet harness (oxygen)" + desc = "A lightweight tactical harness, used by those who don't want to be weighed down by traditional jetpacks." + icon_state = "jetpack-mini" + item_state = "jetpack-mini" + volume = 40 + throw_range = 8 + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/tank/jetpack/oxygenblack + name = "Jetpack (Oxygen)" + desc = "A black tank of compressed oxygen for use as propulsion in zero-gravity areas. Use with caution." + icon_state = "jetpack-black" + item_state = "jetpack-black" + +/obj/item/tank/jetpack/oxygenblack/New() + ..() + air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/jetpack/carbondioxide + name = "Jetpack (Carbon Dioxide)" + desc = "A tank of compressed carbon dioxide for use as propulsion in zero-gravity areas. Painted black to indicate that it should not be used as a source for internals." + distribute_pressure = 0 + icon_state = "jetpack-black" + item_state = "jetpack-black" + +/obj/item/tank/jetpack/carbondioxide/New() + ..() + ion_trail = new /datum/effect_system/trail_follow/ion() + ion_trail.set_up(src) + air_contents.carbon_dioxide = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/jetpack/carbondioxide/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0 && air_contents.carbon_dioxide < 10) + . += "The meter on [src] indicates you are almost out of air!" + playsound(user, 'sound/effects/alert.ogg', 50, 1) + +/obj/item/tank/jetpack/suit + name = "hardsuit jetpack upgrade" + desc = "A modular, compact set of thrusters designed to integrate with a hardsuit. It is fueled by a tank inserted into the suit's storage compartment." + icon_state = "jetpack-mining" + item_state = "jetpack-black" + origin_tech = "materials=4;magnets=4;engineering=5" + w_class = WEIGHT_CLASS_NORMAL + actions_types = list(/datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) + volume = 1 + slot_flags = null + var/datum/gas_mixture/temp_air_contents + var/obj/item/tank/tank = null + var/mob/living/carbon/human/cur_user + +/obj/item/tank/jetpack/suit/New() + ..() + STOP_PROCESSING(SSobj, src) + temp_air_contents = air_contents + +/obj/item/tank/jetpack/suit/attack_self() + return + +/obj/item/tank/jetpack/suit/cycle(mob/user) + if(!istype(loc, /obj/item/clothing/suit/space/hardsuit)) + to_chat(user, "[src] must be connected to a hardsuit!") + return + + var/mob/living/carbon/human/H = user + if(!istype(H.s_store, /obj/item/tank)) + to_chat(user, "You need a tank in your suit storage!") + return + ..() + +/obj/item/tank/jetpack/suit/turn_on(mob/user) + if(!istype(loc, /obj/item/clothing/suit/space/hardsuit) || !ishuman(loc.loc) || loc.loc != user) + return + var/mob/living/carbon/human/H = user + tank = H.s_store + air_contents = tank.air_contents + START_PROCESSING(SSobj, src) + cur_user = user + ..() + +/obj/item/tank/jetpack/suit/turn_off(mob/user) + tank = null + air_contents = temp_air_contents + STOP_PROCESSING(SSobj, src) + cur_user = null + ..() + +/obj/item/tank/jetpack/suit/process() + if(!istype(loc, /obj/item/clothing/suit/space/hardsuit) || !ishuman(loc.loc)) + turn_off(cur_user) + return + var/mob/living/carbon/human/H = loc.loc + if(!tank || tank != H.s_store) + turn_off(cur_user) + return + ..() + +/obj/item/tank/jetpack/rig + name = "jetpack" + var/obj/item/rig/holder + actions_types = list(/datum/action/item_action/toggle_jetpack, /datum/action/item_action/jetpack_stabilization) + +/obj/item/tank/jetpack/rig/examine() + . = list("It's a jetpack. If you can see this, report it on the bug tracker.") + +/obj/item/tank/jetpack/rig/allow_thrust(num, mob/living/user) + if(!on) + return 0 + + if(!istype(holder) || !holder.air_supply) + return 0 + + var/datum/gas_mixture/removed = holder.air_supply.air_contents.remove(num) + if(removed.total_moles() < 0.005) + turn_off(user) + return 0 + + var/turf/T = get_turf(user) + T.assume_air(removed) + + return 1 diff --git a/code/game/objects/items/weapons/tanks/tank_types.dm b/code/game/objects/items/weapons/tanks/tank_types.dm index 55b0225bc2d..11355b34170 100644 --- a/code/game/objects/items/weapons/tanks/tank_types.dm +++ b/code/game/objects/items/weapons/tanks/tank_types.dm @@ -1,253 +1,253 @@ -/* Types of tanks! - * Contains: - * Oxygen - * Anesthetic - * Air - * Plasma - * Emergency Oxygen - */ - -/* - * Oxygen - */ -/obj/item/tank/oxygen - name = "oxygen tank" - desc = "A tank of oxygen." - icon_state = "oxygen" - distribute_pressure = ONE_ATMOSPHERE*O2STANDARD - dog_fashion = /datum/dog_fashion/back - - -/obj/item/tank/oxygen/New() - ..() - air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/oxygen/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0 && air_contents.oxygen < 10) - . += "The meter on [src] indicates you are almost out of air!" - -obj/item/tank/oxygen/empty/New() - ..() - air_contents.oxygen = null - -/obj/item/tank/oxygen/yellow - desc = "A tank of oxygen, this one is yellow." - icon_state = "oxygen_f" - dog_fashion = null - -/obj/item/tank/oxygen/red - desc = "A tank of oxygen, this one is red." - icon_state = "oxygen_fr" - dog_fashion = null - - -/* - * Anesthetic - */ -/obj/item/tank/anesthetic - name = "anesthetic tank" - desc = "A tank with an N2O/O2 gas mix." - icon_state = "anesthetic" - item_state = "an_tank" - -/obj/item/tank/anesthetic/New() - ..() - - air_contents.oxygen = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - - var/datum/gas/sleeping_agent/trace_gas = new() - trace_gas.moles = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD - - air_contents.trace_gases += trace_gas - -/* - * Air - */ -/obj/item/tank/air - name = "air tank" - desc = "Mixed anyone?" - icon_state = "air" - item_state = "air" - -/obj/item/tank/air/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0 && air_contents.oxygen < 1) - . += "The meter on [src] indicates you are almost out of air!" - playsound(user, 'sound/effects/alert.ogg', 50, 1) - -/obj/item/tank/air/New() - ..() - air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - air_contents.nitrogen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD - -/* - * Plasma - */ -/obj/item/tank/plasma - name = "plasma tank" - desc = "Contains dangerous plasma. Do not inhale. Warning: extremely flammable." - icon_state = "plasma" - flags = CONDUCT - slot_flags = null //they have no straps! - -/obj/item/tank/plasma/New() - ..() - air_contents.toxins = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/plasma/attackby(obj/item/W as obj, mob/user as mob, params) - ..() - - if(istype(W, /obj/item/flamethrower)) - var/obj/item/flamethrower/F = W - if((!F.status)||(F.ptank)) return - master = F - F.ptank = src - user.unEquip(src) - loc = F - F.update_icon() - -/obj/item/tank/plasma/full/New() - ..() - air_contents.toxins = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/plasma/plasmaman - name = "plasma internals tank" - desc = "A tank of plasma gas designed specifically for use as internals, particularly for plasma-based lifeforms. If you're not a Plasmaman, you probably shouldn't use this." - icon_state = "plasmaman_tank" - item_state = "plasmaman_tank" - force = 10 - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - -/obj/item/tank/plasma/plasmaman/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0 && air_contents.toxins < 0.2) - . += "The meter on [src] indicates you are almost out of plasma!" - playsound(user, 'sound/effects/alert.ogg', 50, 1) - - -/obj/item/tank/plasma/plasmaman/belt - icon_state = "plasmaman_tank_belt" - item_state = "plasmaman_tank_belt" - slot_flags = SLOT_BELT - force = 5 - volume = 25 - w_class = WEIGHT_CLASS_SMALL - -/obj/item/tank/plasma/plasmaman/belt/full/New() - ..() - air_contents.toxins = (10 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C) - -/* - * Emergency Oxygen - */ -/obj/item/tank/emergency_oxygen - name = "emergency oxygen tank" - desc = "Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it." - icon_state = "emergency" - flags = CONDUCT - slot_flags = SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - force = 4.0 - distribute_pressure = ONE_ATMOSPHERE*O2STANDARD - volume = 3 //Tiny. Real life equivalents only have 21 breaths of oxygen in them. They're EMERGENCY tanks anyway -errorage (dangercon 2011) - - -/obj/item/tank/emergency_oxygen/New() - ..() - air_contents.oxygen = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/emergency_oxygen/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0 && air_contents.oxygen < 0.2) - . += "The meter on [src] indicates you are almost out of air!" - playsound(user, 'sound/effects/alert.ogg', 50, 1) - -obj/item/tank/emergency_oxygen/empty/New() - ..() - air_contents.oxygen = null - -/obj/item/tank/emergency_oxygen/engi - name = "extended-capacity emergency oxygen tank" - icon_state = "emergency_engi" - volume = 6 - -obj/item/tank/emergency_oxygen/engi/empty/New() - ..() - air_contents.oxygen = null - -/obj/item/tank/emergency_oxygen/syndi - name = "suspicious emergency oxygen tank" - icon_state = "emergency_syndi" - desc = "A dark emergency oxygen tank. The label on the back reads \"Original Oxygen Tank Design, Do Not Steal.\"" - volume = 6 - -/obj/item/tank/emergency_oxygen/double - name = "double emergency oxygen tank" - icon_state = "emergency_double" - volume = 10 - -obj/item/tank/emergency_oxygen/double/empty/New() - ..() - air_contents.oxygen = null - -/obj/item/tank/emergency_oxygen/double/full - name = "pressurized double emergency oxygen tank" - desc = "Used for \"emergencies,\" it actually contains a fair amount of oxygen." - -/obj/item/tank/emergency_oxygen/double/full/New() - ..() - air_contents.oxygen = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/* - * Nitrogen - */ -/obj/item/tank/nitrogen - name = "nitrogen tank" - desc = "A tank of nitrogen." - icon_state = "oxygen_fr" - distribute_pressure = ONE_ATMOSPHERE*O2STANDARD - sprite_sheets = list("Vox Armalis" = 'icons/mob/species/armalis/back.dmi') //Do it for Big Bird. - -/obj/item/tank/nitrogen/New() - ..() - air_contents.nitrogen = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/nitrogen/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0 && air_contents.nitrogen < 10) - . += "The meter on the [src.name] indicates you are almost out of air!" - -/obj/item/tank/emergency_oxygen/vox - name = "vox specialized nitrogen tank" - desc = "A high-tech nitrogen tank designed specifically for Vox." - icon_state = "emergency_vox" - volume = 25 - sprite_sheets = list("Vox Armalis" = 'icons/mob/species/armalis/belt.dmi') //Do it for Big Bird. - -/obj/item/tank/emergency_oxygen/vox/New() - ..() - air_contents.oxygen -= (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - air_contents.nitrogen = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/emergency_oxygen/nitrogen - name = "emergency nitrogen tank" - desc = "An emergency tank designed specifically for Vox." - icon_state = "emergency_nitrogen" - volume = 3 - -/obj/item/tank/emergency_oxygen/nitrogen/New() - ..() - air_contents.oxygen -= (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - air_contents.nitrogen = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - -/obj/item/tank/emergency_oxygen/plasma - name = "emergency plasma tank" - desc = "An emergency tank designed specifically for Plasmamen." - icon_state = "emergency_p" - volume = 3 - -/obj/item/tank/emergency_oxygen/plasma/New() - ..() - air_contents.oxygen -= (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - air_contents.toxins = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) +/* Types of tanks! + * Contains: + * Oxygen + * Anesthetic + * Air + * Plasma + * Emergency Oxygen + */ + +/* + * Oxygen + */ +/obj/item/tank/oxygen + name = "oxygen tank" + desc = "A tank of oxygen." + icon_state = "oxygen" + distribute_pressure = ONE_ATMOSPHERE*O2STANDARD + dog_fashion = /datum/dog_fashion/back + + +/obj/item/tank/oxygen/New() + ..() + air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/oxygen/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0 && air_contents.oxygen < 10) + . += "The meter on [src] indicates you are almost out of air!" + +obj/item/tank/oxygen/empty/New() + ..() + air_contents.oxygen = null + +/obj/item/tank/oxygen/yellow + desc = "A tank of oxygen, this one is yellow." + icon_state = "oxygen_f" + dog_fashion = null + +/obj/item/tank/oxygen/red + desc = "A tank of oxygen, this one is red." + icon_state = "oxygen_fr" + dog_fashion = null + + +/* + * Anesthetic + */ +/obj/item/tank/anesthetic + name = "anesthetic tank" + desc = "A tank with an N2O/O2 gas mix." + icon_state = "anesthetic" + item_state = "an_tank" + +/obj/item/tank/anesthetic/New() + ..() + + air_contents.oxygen = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD + + var/datum/gas/sleeping_agent/trace_gas = new() + trace_gas.moles = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + + air_contents.trace_gases += trace_gas + +/* + * Air + */ +/obj/item/tank/air + name = "air tank" + desc = "Mixed anyone?" + icon_state = "air" + item_state = "air" + +/obj/item/tank/air/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0 && air_contents.oxygen < 1) + . += "The meter on [src] indicates you are almost out of air!" + playsound(user, 'sound/effects/alert.ogg', 50, 1) + +/obj/item/tank/air/New() + ..() + air_contents.oxygen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD + air_contents.nitrogen = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + +/* + * Plasma + */ +/obj/item/tank/plasma + name = "plasma tank" + desc = "Contains dangerous plasma. Do not inhale. Warning: extremely flammable." + icon_state = "plasma" + flags = CONDUCT + slot_flags = null //they have no straps! + +/obj/item/tank/plasma/New() + ..() + air_contents.toxins = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/plasma/attackby(obj/item/W as obj, mob/user as mob, params) + ..() + + if(istype(W, /obj/item/flamethrower)) + var/obj/item/flamethrower/F = W + if((!F.status)||(F.ptank)) return + master = F + F.ptank = src + user.unEquip(src) + loc = F + F.update_icon() + +/obj/item/tank/plasma/full/New() + ..() + air_contents.toxins = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/plasma/plasmaman + name = "plasma internals tank" + desc = "A tank of plasma gas designed specifically for use as internals, particularly for plasma-based lifeforms. If you're not a Plasmaman, you probably shouldn't use this." + icon_state = "plasmaman_tank" + item_state = "plasmaman_tank" + force = 10 + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + +/obj/item/tank/plasma/plasmaman/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0 && air_contents.toxins < 0.2) + . += "The meter on [src] indicates you are almost out of plasma!" + playsound(user, 'sound/effects/alert.ogg', 50, 1) + + +/obj/item/tank/plasma/plasmaman/belt + icon_state = "plasmaman_tank_belt" + item_state = "plasmaman_tank_belt" + slot_flags = SLOT_BELT + force = 5 + volume = 25 + w_class = WEIGHT_CLASS_SMALL + +/obj/item/tank/plasma/plasmaman/belt/full/New() + ..() + air_contents.toxins = (10 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C) + +/* + * Emergency Oxygen + */ +/obj/item/tank/emergency_oxygen + name = "emergency oxygen tank" + desc = "Used for emergencies. Contains very little oxygen, so try to conserve it until you actually need it." + icon_state = "emergency" + flags = CONDUCT + slot_flags = SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + force = 4.0 + distribute_pressure = ONE_ATMOSPHERE*O2STANDARD + volume = 3 //Tiny. Real life equivalents only have 21 breaths of oxygen in them. They're EMERGENCY tanks anyway -errorage (dangercon 2011) + + +/obj/item/tank/emergency_oxygen/New() + ..() + air_contents.oxygen = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/emergency_oxygen/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0 && air_contents.oxygen < 0.2) + . += "The meter on [src] indicates you are almost out of air!" + playsound(user, 'sound/effects/alert.ogg', 50, 1) + +obj/item/tank/emergency_oxygen/empty/New() + ..() + air_contents.oxygen = null + +/obj/item/tank/emergency_oxygen/engi + name = "extended-capacity emergency oxygen tank" + icon_state = "emergency_engi" + volume = 6 + +obj/item/tank/emergency_oxygen/engi/empty/New() + ..() + air_contents.oxygen = null + +/obj/item/tank/emergency_oxygen/syndi + name = "suspicious emergency oxygen tank" + icon_state = "emergency_syndi" + desc = "A dark emergency oxygen tank. The label on the back reads \"Original Oxygen Tank Design, Do Not Steal.\"" + volume = 6 + +/obj/item/tank/emergency_oxygen/double + name = "double emergency oxygen tank" + icon_state = "emergency_double" + volume = 10 + +obj/item/tank/emergency_oxygen/double/empty/New() + ..() + air_contents.oxygen = null + +/obj/item/tank/emergency_oxygen/double/full + name = "pressurized double emergency oxygen tank" + desc = "Used for \"emergencies,\" it actually contains a fair amount of oxygen." + +/obj/item/tank/emergency_oxygen/double/full/New() + ..() + air_contents.oxygen = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/* + * Nitrogen + */ +/obj/item/tank/nitrogen + name = "nitrogen tank" + desc = "A tank of nitrogen." + icon_state = "oxygen_fr" + distribute_pressure = ONE_ATMOSPHERE*O2STANDARD + sprite_sheets = list("Vox Armalis" = 'icons/mob/species/armalis/back.dmi') //Do it for Big Bird. + +/obj/item/tank/nitrogen/New() + ..() + air_contents.nitrogen = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/nitrogen/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0 && air_contents.nitrogen < 10) + . += "The meter on the [src.name] indicates you are almost out of air!" + +/obj/item/tank/emergency_oxygen/vox + name = "vox specialized nitrogen tank" + desc = "A high-tech nitrogen tank designed specifically for Vox." + icon_state = "emergency_vox" + volume = 25 + sprite_sheets = list("Vox Armalis" = 'icons/mob/species/armalis/belt.dmi') //Do it for Big Bird. + +/obj/item/tank/emergency_oxygen/vox/New() + ..() + air_contents.oxygen -= (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.nitrogen = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/emergency_oxygen/nitrogen + name = "emergency nitrogen tank" + desc = "An emergency tank designed specifically for Vox." + icon_state = "emergency_nitrogen" + volume = 3 + +/obj/item/tank/emergency_oxygen/nitrogen/New() + ..() + air_contents.oxygen -= (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.nitrogen = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + +/obj/item/tank/emergency_oxygen/plasma + name = "emergency plasma tank" + desc = "An emergency tank designed specifically for Plasmamen." + icon_state = "emergency_p" + volume = 3 + +/obj/item/tank/emergency_oxygen/plasma/New() + ..() + air_contents.oxygen -= (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.toxins = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm index f85af5c093d..a0dd6aa485d 100644 --- a/code/game/objects/items/weapons/tanks/tanks.dm +++ b/code/game/objects/items/weapons/tanks/tanks.dm @@ -1,292 +1,292 @@ -#define TANK_MAX_RELEASE_PRESSURE (3*ONE_ATMOSPHERE) - -/obj/item/tank - name = "tank" - icon = 'icons/obj/tank.dmi' - flags = CONDUCT - slot_flags = SLOT_BACK - hitsound = 'sound/weapons/smash.ogg' - w_class = WEIGHT_CLASS_NORMAL - pressure_resistance = ONE_ATMOSPHERE * 5 - force = 5.0 - throwforce = 10.0 - throw_speed = 1 - throw_range = 4 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 30) - actions_types = list(/datum/action/item_action/set_internals) - var/datum/gas_mixture/air_contents = null - var/distribute_pressure = ONE_ATMOSPHERE - var/integrity = 3 - var/volume = 70 - -/obj/item/tank/New() - ..() - - air_contents = new /datum/gas_mixture() - air_contents.volume = volume //liters - air_contents.temperature = T20C - - START_PROCESSING(SSobj, src) - return - -/obj/item/tank/Destroy() - QDEL_NULL(air_contents) - - STOP_PROCESSING(SSobj, src) - - return ..() - - -/obj/item/tank/ui_action_click(mob/user) - toggle_internals(user) - -/obj/item/tank/proc/toggle_internals(mob/user, silent = FALSE) - var/mob/living/carbon/C = user - if(!istype(C)) - return 0 - - if(C.internal == src) - to_chat(C, "You close \the [src] valve.") - C.internal = null - else - var/can_open_valve = 0 - if(C.get_organ_slot("breathing_tube")) - can_open_valve = 1 - else if(C.wear_mask && C.wear_mask.flags & AIRTIGHT) - can_open_valve = 1 - else if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(H.head && H.head.flags & AIRTIGHT) - can_open_valve = 1 - - if(can_open_valve) - if(C.internal) - if(!silent) - to_chat(C, "You switch your internals to [src].") - else - if(!silent) - to_chat(C, "You open \the [src] valve.") - C.internal = src - else - if(!silent) - to_chat(C, "You are not wearing a suitable mask or helmet.") - return 0 - - C.update_action_buttons_icon() - - -/obj/item/tank/examine(mob/user) - . = ..() - - var/obj/icon = src - if(istype(loc, /obj/item/assembly)) - icon = loc - - if(!in_range(src, user)) - if(icon == src) - . += "It's \a [bicon(icon)][src]! If you want any more information you'll need to get closer." - return - - var/celsius_temperature = air_contents.temperature-T0C - var/descriptive - - if(celsius_temperature < 20) - descriptive = "cold" - else if(celsius_temperature < 40) - descriptive = "room temperature" - else if(celsius_temperature < 80) - descriptive = "lukewarm" - else if(celsius_temperature < 100) - descriptive = "warm" - else if(celsius_temperature < 300) - descriptive = "hot" - else - descriptive = "furiously hot" - - . += "\The [bicon(icon)][src] feels [descriptive]" - -/obj/item/tank/blob_act(obj/structure/blob/B) - if(B && B.loc == loc) - var/turf/location = get_turf(src) - if(!location) - qdel(src) - - if(air_contents) - location.assume_air(air_contents) - - qdel(src) - -/obj/item/tank/deconstruct(disassembled = TRUE) - if(!disassembled) - var/turf/T = get_turf(src) - if(T) - T.assume_air(air_contents) - air_update_turf() - playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) - qdel(src) - -/obj/item/tank/attackby(obj/item/W as obj, mob/user as mob, params) - ..() - - add_fingerprint(user) - if(istype(loc, /obj/item/assembly)) - icon = loc - - if((istype(W, /obj/item/analyzer)) && get_dist(user, src) <= 1) - atmosanalyzer_scan(air_contents, user) - - if(istype(W, /obj/item/assembly_holder)) - bomb_assemble(W,user) - -/obj/item/tank/attack_self(mob/user as mob) - if(!(air_contents)) - return - - ui_interact(user) - -/obj/item/tank/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "tanks.tmpl", "Tank", 500, 300) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/item/tank/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/using_internal - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - if(C.internal == src) - using_internal = 1 - - var/data[0] - data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) - data["releasePressure"] = round(distribute_pressure ? distribute_pressure : 0) - data["defaultReleasePressure"] = round(TANK_DEFAULT_RELEASE_PRESSURE) - data["maxReleasePressure"] = round(TANK_MAX_RELEASE_PRESSURE) - data["valveOpen"] = using_internal ? 1 : 0 - - data["maskConnected"] = 0 - - if(iscarbon(loc)) - var/mob/living/carbon/C = loc - if(C.internal == src) - data["maskConnected"] = 1 - else - if(C.wear_mask && (C.wear_mask.flags & AIRTIGHT)) - data["maskConnected"] = 1 - else if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(H.head && (H.head.flags & AIRTIGHT)) - data["maskConnected"] = 1 - - return data - -/obj/item/tank/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["dist_p"]) - if(href_list["dist_p"] == "reset") - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - else if(href_list["dist_p"] == "max") - distribute_pressure = TANK_MAX_RELEASE_PRESSURE - else - var/cp = text2num(href_list["dist_p"]) - distribute_pressure += cp - distribute_pressure = min(max(round(distribute_pressure), 0), TANK_MAX_RELEASE_PRESSURE) - - if(href_list["stat"]) - toggle_internals(usr) - - add_fingerprint(usr) - return 1 - - -/obj/item/tank/remove_air(amount) - return air_contents.remove(amount) - -/obj/item/tank/return_air() - return air_contents - -/obj/item/tank/assume_air(datum/gas_mixture/giver) - air_contents.merge(giver) - - check_status() - return 1 - -/obj/item/tank/proc/remove_air_volume(volume_to_return) - if(!air_contents) - return null - - var/tank_pressure = air_contents.return_pressure() - if(tank_pressure < distribute_pressure) - distribute_pressure = tank_pressure - - var/moles_needed = distribute_pressure*volume_to_return/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - - return remove_air(moles_needed) - -/obj/item/tank/process() - //Allow for reactions - air_contents.react() - check_status() - - -/obj/item/tank/proc/check_status() - //Handle exploding, leaking, and rupturing of the tank - - if(!air_contents) - return 0 - - var/pressure = air_contents.return_pressure() - if(pressure > TANK_FRAGMENT_PRESSURE) - if(!istype(loc,/obj/item/transfer_valve)) - message_admins("Explosive tank rupture! last key to touch the tank was [fingerprintslast] (JMP)") - log_game("Explosive tank rupture! last key to touch the tank was [fingerprintslast] at [x], [y], [z]") -// to_chat(world, "[x],[y] tank is exploding: [pressure] kPa") - //Give the gas a chance to build up more pressure through reacting - air_contents.react() - air_contents.react() - air_contents.react() - pressure = air_contents.return_pressure() - var/range = (pressure-TANK_FRAGMENT_PRESSURE)/TANK_FRAGMENT_SCALE - var/turf/epicenter = get_turf(loc) - -// to_chat(world, "Exploding Pressure: [pressure] kPa, intensity: [range]") - - explosion(epicenter, round(range*0.25), round(range*0.5), round(range), round(range*1.5)) - if(istype(loc,/obj/item/transfer_valve)) - qdel(loc) - else - qdel(src) - - else if(pressure > TANK_RUPTURE_PRESSURE) -// to_chat(world, "[x],[y] tank is rupturing: [pressure] kPa, integrity [integrity]") - if(integrity <= 0) - var/turf/simulated/T = get_turf(src) - if(!T) - return - T.assume_air(air_contents) - playsound(loc, 'sound/effects/spray.ogg', 10, 1, -3) - qdel(src) - else - integrity-- - - else if(pressure > TANK_LEAK_PRESSURE) -// to_chat(world, "[x],[y] tank is leaking: [pressure] kPa, integrity [integrity]") - if(integrity <= 0) - var/turf/simulated/T = get_turf(src) - if(!T) - return - var/datum/gas_mixture/leaked_gas = air_contents.remove_ratio(0.25) - T.assume_air(leaked_gas) - else - integrity-- - - else if(integrity < 3) - integrity++ +#define TANK_MAX_RELEASE_PRESSURE (3*ONE_ATMOSPHERE) + +/obj/item/tank + name = "tank" + icon = 'icons/obj/tank.dmi' + flags = CONDUCT + slot_flags = SLOT_BACK + hitsound = 'sound/weapons/smash.ogg' + w_class = WEIGHT_CLASS_NORMAL + pressure_resistance = ONE_ATMOSPHERE * 5 + force = 5.0 + throwforce = 10.0 + throw_speed = 1 + throw_range = 4 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 30) + actions_types = list(/datum/action/item_action/set_internals) + var/datum/gas_mixture/air_contents = null + var/distribute_pressure = ONE_ATMOSPHERE + var/integrity = 3 + var/volume = 70 + +/obj/item/tank/New() + ..() + + air_contents = new /datum/gas_mixture() + air_contents.volume = volume //liters + air_contents.temperature = T20C + + START_PROCESSING(SSobj, src) + return + +/obj/item/tank/Destroy() + QDEL_NULL(air_contents) + + STOP_PROCESSING(SSobj, src) + + return ..() + + +/obj/item/tank/ui_action_click(mob/user) + toggle_internals(user) + +/obj/item/tank/proc/toggle_internals(mob/user, silent = FALSE) + var/mob/living/carbon/C = user + if(!istype(C)) + return 0 + + if(C.internal == src) + to_chat(C, "You close \the [src] valve.") + C.internal = null + else + var/can_open_valve = 0 + if(C.get_organ_slot("breathing_tube")) + can_open_valve = 1 + else if(C.wear_mask && C.wear_mask.flags & AIRTIGHT) + can_open_valve = 1 + else if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(H.head && H.head.flags & AIRTIGHT) + can_open_valve = 1 + + if(can_open_valve) + if(C.internal) + if(!silent) + to_chat(C, "You switch your internals to [src].") + else + if(!silent) + to_chat(C, "You open \the [src] valve.") + C.internal = src + else + if(!silent) + to_chat(C, "You are not wearing a suitable mask or helmet.") + return 0 + + C.update_action_buttons_icon() + + +/obj/item/tank/examine(mob/user) + . = ..() + + var/obj/icon = src + if(istype(loc, /obj/item/assembly)) + icon = loc + + if(!in_range(src, user)) + if(icon == src) + . += "It's \a [bicon(icon)][src]! If you want any more information you'll need to get closer." + return + + var/celsius_temperature = air_contents.temperature-T0C + var/descriptive + + if(celsius_temperature < 20) + descriptive = "cold" + else if(celsius_temperature < 40) + descriptive = "room temperature" + else if(celsius_temperature < 80) + descriptive = "lukewarm" + else if(celsius_temperature < 100) + descriptive = "warm" + else if(celsius_temperature < 300) + descriptive = "hot" + else + descriptive = "furiously hot" + + . += "\The [bicon(icon)][src] feels [descriptive]" + +/obj/item/tank/blob_act(obj/structure/blob/B) + if(B && B.loc == loc) + var/turf/location = get_turf(src) + if(!location) + qdel(src) + + if(air_contents) + location.assume_air(air_contents) + + qdel(src) + +/obj/item/tank/deconstruct(disassembled = TRUE) + if(!disassembled) + var/turf/T = get_turf(src) + if(T) + T.assume_air(air_contents) + air_update_turf() + playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) + qdel(src) + +/obj/item/tank/attackby(obj/item/W as obj, mob/user as mob, params) + ..() + + add_fingerprint(user) + if(istype(loc, /obj/item/assembly)) + icon = loc + + if((istype(W, /obj/item/analyzer)) && get_dist(user, src) <= 1) + atmosanalyzer_scan(air_contents, user) + + if(istype(W, /obj/item/assembly_holder)) + bomb_assemble(W,user) + +/obj/item/tank/attack_self(mob/user as mob) + if(!(air_contents)) + return + + ui_interact(user) + +/obj/item/tank/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "tanks.tmpl", "Tank", 500, 300) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/item/tank/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/using_internal + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + if(C.internal == src) + using_internal = 1 + + var/data[0] + data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) + data["releasePressure"] = round(distribute_pressure ? distribute_pressure : 0) + data["defaultReleasePressure"] = round(TANK_DEFAULT_RELEASE_PRESSURE) + data["maxReleasePressure"] = round(TANK_MAX_RELEASE_PRESSURE) + data["valveOpen"] = using_internal ? 1 : 0 + + data["maskConnected"] = 0 + + if(iscarbon(loc)) + var/mob/living/carbon/C = loc + if(C.internal == src) + data["maskConnected"] = 1 + else + if(C.wear_mask && (C.wear_mask.flags & AIRTIGHT)) + data["maskConnected"] = 1 + else if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(H.head && (H.head.flags & AIRTIGHT)) + data["maskConnected"] = 1 + + return data + +/obj/item/tank/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["dist_p"]) + if(href_list["dist_p"] == "reset") + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + else if(href_list["dist_p"] == "max") + distribute_pressure = TANK_MAX_RELEASE_PRESSURE + else + var/cp = text2num(href_list["dist_p"]) + distribute_pressure += cp + distribute_pressure = min(max(round(distribute_pressure), 0), TANK_MAX_RELEASE_PRESSURE) + + if(href_list["stat"]) + toggle_internals(usr) + + add_fingerprint(usr) + return 1 + + +/obj/item/tank/remove_air(amount) + return air_contents.remove(amount) + +/obj/item/tank/return_air() + return air_contents + +/obj/item/tank/assume_air(datum/gas_mixture/giver) + air_contents.merge(giver) + + check_status() + return 1 + +/obj/item/tank/proc/remove_air_volume(volume_to_return) + if(!air_contents) + return null + + var/tank_pressure = air_contents.return_pressure() + if(tank_pressure < distribute_pressure) + distribute_pressure = tank_pressure + + var/moles_needed = distribute_pressure*volume_to_return/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + + return remove_air(moles_needed) + +/obj/item/tank/process() + //Allow for reactions + air_contents.react() + check_status() + + +/obj/item/tank/proc/check_status() + //Handle exploding, leaking, and rupturing of the tank + + if(!air_contents) + return 0 + + var/pressure = air_contents.return_pressure() + if(pressure > TANK_FRAGMENT_PRESSURE) + if(!istype(loc,/obj/item/transfer_valve)) + message_admins("Explosive tank rupture! last key to touch the tank was [fingerprintslast] (JMP)") + log_game("Explosive tank rupture! last key to touch the tank was [fingerprintslast] at [x], [y], [z]") +// to_chat(world, "[x],[y] tank is exploding: [pressure] kPa") + //Give the gas a chance to build up more pressure through reacting + air_contents.react() + air_contents.react() + air_contents.react() + pressure = air_contents.return_pressure() + var/range = (pressure-TANK_FRAGMENT_PRESSURE)/TANK_FRAGMENT_SCALE + var/turf/epicenter = get_turf(loc) + +// to_chat(world, "Exploding Pressure: [pressure] kPa, intensity: [range]") + + explosion(epicenter, round(range*0.25), round(range*0.5), round(range), round(range*1.5)) + if(istype(loc,/obj/item/transfer_valve)) + qdel(loc) + else + qdel(src) + + else if(pressure > TANK_RUPTURE_PRESSURE) +// to_chat(world, "[x],[y] tank is rupturing: [pressure] kPa, integrity [integrity]") + if(integrity <= 0) + var/turf/simulated/T = get_turf(src) + if(!T) + return + T.assume_air(air_contents) + playsound(loc, 'sound/effects/spray.ogg', 10, 1, -3) + qdel(src) + else + integrity-- + + else if(pressure > TANK_LEAK_PRESSURE) +// to_chat(world, "[x],[y] tank is leaking: [pressure] kPa, integrity [integrity]") + if(integrity <= 0) + var/turf/simulated/T = get_turf(src) + if(!T) + return + var/datum/gas_mixture/leaked_gas = air_contents.remove_ratio(0.25) + T.assume_air(leaked_gas) + else + integrity-- + + else if(integrity < 3) + integrity++ diff --git a/code/game/objects/items/weapons/teleportation.dm b/code/game/objects/items/weapons/teleportation.dm index 117bb90c35f..4ac9651ea65 100644 --- a/code/game/objects/items/weapons/teleportation.dm +++ b/code/game/objects/items/weapons/teleportation.dm @@ -1,149 +1,149 @@ -/* Teleportation devices. - * Contains: - * Locator - * Hand-tele - */ - -/* - * Locator - */ -/obj/item/locator - name = "locator" - desc = "Used to track those with locater implants." - icon = 'icons/obj/device.dmi' - icon_state = "locator" - var/temp = null - var/frequency = 1451 - var/broadcasting = null - var/listening = 1.0 - flags = CONDUCT - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - throw_speed = 4 - throw_range = 20 - materials = list(MAT_METAL=400) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - origin_tech = "magnets=3;bluespace=2" - -/obj/item/locator/attack_self(mob/user as mob) - add_fingerprint(usr) - var/dat - if(temp) - dat = "[src.temp]

        Clear" - else - dat = {" -Persistent Signal Locator
        -Frequency: -- -- [format_frequency(src.frequency)] -+ -+
        - -Refresh"} - user << browse(dat, "window=radio") - onclose(user, "radio") - return - -/obj/item/locator/Topic(href, href_list) - if(..()) - return 1 - - var/turf/current_location = get_turf(usr)//What turf is the user on? - if(!current_location || is_admin_level(current_location.z))//If turf was not found or they're in the admin zone - to_chat(usr, "\The [src] is malfunctioning.") - return 1 - - if(href_list["refresh"]) - temp = "Persistent Signal Locator
        " - var/turf/sr = get_turf(src) - - if(sr) - temp += "Located Beacons:
        " - - for(var/obj/item/radio/beacon/W in GLOB.beacons) - if(W.frequency == frequency && !W.syndicate) - if(W && W.z == z) - var/turf/TB = get_turf(W) - temp += "[W.code]: [TB.x], [TB.y], [TB.z]
        " - - temp += "Located Implants:
        " - for(var/obj/item/implant/tracking/T in GLOB.tracked_implants) - if(!T.implanted || !T.imp_in) - continue - var/turf/Tr = get_turf(T) - - if(Tr && Tr.z == sr.z) - temp += "[T.id]: [Tr.x], [Tr.y], [Tr.z]
        " - - temp += "You are at \[[sr.x],[sr.y],[sr.z]\]." - temp += "

        Refresh
        " - else - temp += "Processing error: Unable to locate orbital position.
        " - else - if(href_list["freq"]) - frequency += text2num(href_list["freq"]) - frequency = sanitize_frequency(frequency) - else - if(href_list["temp"]) - temp = null - - attack_self(usr) - return 1 - -/* - * Hand-tele - */ -/obj/item/hand_tele - name = "hand tele" - desc = "A portable item using blue-space technology." - icon = 'icons/obj/device.dmi' - icon_state = "hand_tele" - item_state = "electronic" - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - materials = list(MAT_METAL=10000) - origin_tech = "magnets=3;bluespace=4" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - var/active_portals = 0 - -/obj/item/hand_tele/attack_self(mob/user as mob) - var/turf/current_location = get_turf(user)//What turf is the user on? - if(!current_location||!is_teleport_allowed(current_location.z))//If turf was not found or they're somewhere teleproof - to_chat(user, "\The [src] is malfunctioning.") - return - var/list/L = list( ) - for(var/obj/machinery/computer/teleporter/com in world) - if(com.target) - if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged) - L["[com.id] (Active)"] = com.target - else - L["[com.id] (Inactive)"] = com.target - var/list/turfs = list( ) - var/area/A - for(var/turf/T in orange(10)) - if(T.x>world.maxx-8 || T.x<8) continue //putting them at the edge is dumb - if(T.y>world.maxy-8 || T.y<8) continue - A = get_area(T) - if(A.tele_proof == 1) continue // Telescience-proofed areas require a beacon. - turfs += T - if(turfs.len) - L["None (Dangerous)"] = pick(turfs) - var/t1 = input(user, "Please select a teleporter to lock in on.", "Hand Teleporter") as null|anything in L - if(!t1 || (!user.is_in_active_hand(src) || user.stat || user.restrained())) - return - if(active_portals >= 3) - user.show_message("\The [src] is recharging!") - return - var/T = L[t1] - user.show_message("Locked In.", 2) - var/obj/effect/portal/P = new /obj/effect/portal(get_turf(src), T, src) - try_move_adjacent(P) - active_portals++ - add_fingerprint(user) - return - -/obj/item/hand_tele/portal_destroyed(obj/effect/portal/P) - active_portals-- \ No newline at end of file +/* Teleportation devices. + * Contains: + * Locator + * Hand-tele + */ + +/* + * Locator + */ +/obj/item/locator + name = "locator" + desc = "Used to track those with locater implants." + icon = 'icons/obj/device.dmi' + icon_state = "locator" + var/temp = null + var/frequency = 1451 + var/broadcasting = null + var/listening = 1.0 + flags = CONDUCT + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + throw_speed = 4 + throw_range = 20 + materials = list(MAT_METAL=400) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + origin_tech = "magnets=3;bluespace=2" + +/obj/item/locator/attack_self(mob/user as mob) + add_fingerprint(usr) + var/dat + if(temp) + dat = "[src.temp]

        Clear" + else + dat = {" +Persistent Signal Locator
        +Frequency: +- +- [format_frequency(src.frequency)] ++ ++
        + +Refresh"} + user << browse(dat, "window=radio") + onclose(user, "radio") + return + +/obj/item/locator/Topic(href, href_list) + if(..()) + return 1 + + var/turf/current_location = get_turf(usr)//What turf is the user on? + if(!current_location || is_admin_level(current_location.z))//If turf was not found or they're in the admin zone + to_chat(usr, "\The [src] is malfunctioning.") + return 1 + + if(href_list["refresh"]) + temp = "Persistent Signal Locator
        " + var/turf/sr = get_turf(src) + + if(sr) + temp += "Located Beacons:
        " + + for(var/obj/item/radio/beacon/W in GLOB.beacons) + if(W.frequency == frequency && !W.syndicate) + if(W && W.z == z) + var/turf/TB = get_turf(W) + temp += "[W.code]: [TB.x], [TB.y], [TB.z]
        " + + temp += "Located Implants:
        " + for(var/obj/item/implant/tracking/T in GLOB.tracked_implants) + if(!T.implanted || !T.imp_in) + continue + var/turf/Tr = get_turf(T) + + if(Tr && Tr.z == sr.z) + temp += "[T.id]: [Tr.x], [Tr.y], [Tr.z]
        " + + temp += "You are at \[[sr.x],[sr.y],[sr.z]\]." + temp += "

        Refresh
        " + else + temp += "Processing error: Unable to locate orbital position.
        " + else + if(href_list["freq"]) + frequency += text2num(href_list["freq"]) + frequency = sanitize_frequency(frequency) + else + if(href_list["temp"]) + temp = null + + attack_self(usr) + return 1 + +/* + * Hand-tele + */ +/obj/item/hand_tele + name = "hand tele" + desc = "A portable item using blue-space technology." + icon = 'icons/obj/device.dmi' + icon_state = "hand_tele" + item_state = "electronic" + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + materials = list(MAT_METAL=10000) + origin_tech = "magnets=3;bluespace=4" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + var/active_portals = 0 + +/obj/item/hand_tele/attack_self(mob/user as mob) + var/turf/current_location = get_turf(user)//What turf is the user on? + if(!current_location||!is_teleport_allowed(current_location.z))//If turf was not found or they're somewhere teleproof + to_chat(user, "\The [src] is malfunctioning.") + return + var/list/L = list( ) + for(var/obj/machinery/computer/teleporter/com in world) + if(com.target) + if(com.power_station && com.power_station.teleporter_hub && com.power_station.engaged) + L["[com.id] (Active)"] = com.target + else + L["[com.id] (Inactive)"] = com.target + var/list/turfs = list( ) + var/area/A + for(var/turf/T in orange(10)) + if(T.x>world.maxx-8 || T.x<8) continue //putting them at the edge is dumb + if(T.y>world.maxy-8 || T.y<8) continue + A = get_area(T) + if(A.tele_proof == 1) continue // Telescience-proofed areas require a beacon. + turfs += T + if(turfs.len) + L["None (Dangerous)"] = pick(turfs) + var/t1 = input(user, "Please select a teleporter to lock in on.", "Hand Teleporter") as null|anything in L + if(!t1 || (!user.is_in_active_hand(src) || user.stat || user.restrained())) + return + if(active_portals >= 3) + user.show_message("\The [src] is recharging!") + return + var/T = L[t1] + user.show_message("Locked In.", 2) + var/obj/effect/portal/P = new /obj/effect/portal(get_turf(src), T, src) + try_move_adjacent(P) + active_portals++ + add_fingerprint(user) + return + +/obj/item/hand_tele/portal_destroyed(obj/effect/portal/P) + active_portals-- diff --git a/code/game/objects/items/weapons/tools.dm b/code/game/objects/items/weapons/tools.dm index f6c6c0ea150..012fcf79b10 100644 --- a/code/game/objects/items/weapons/tools.dm +++ b/code/game/objects/items/weapons/tools.dm @@ -1,785 +1,785 @@ -#define HEALPERWELD 15 - -/* Tools! - * Note: Multitools are in devices - * - * Contains: - * Wrench - * Screwdriver - * Wirecutters - * Welding Tool - * Crowbar - * Revolver Conversion Kit - */ - -//Wrench -/obj/item/wrench - name = "wrench" - desc = "A wrench with common uses. Can be found in your hand." - icon = 'icons/obj/tools.dmi' - icon_state = "wrench" - flags = CONDUCT - slot_flags = SLOT_BELT - force = 5 - throwforce = 7 - usesound = 'sound/items/ratchet.ogg' - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=150) - origin_tech = "materials=1;engineering=1" - attack_verb = list("bashed", "battered", "bludgeoned", "whacked") - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - -/obj/item/wrench/suicide_act(mob/user) - user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/weapons/genhit.ogg', 50, 1, -1) - return BRUTELOSS - -/obj/item/wrench/cyborg - name = "automatic wrench" - desc = "An advanced robotic wrench. Can be found in construction cyborgs." - toolspeed = 0.5 - -/obj/item/wrench/brass - name = "brass wrench" - desc = "A brass wrench. It's faintly warm to the touch." - icon_state = "wrench_brass" - toolspeed = 0.5 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/wrench/abductor - name = "alien wrench" - desc = "A polarized wrench. It causes anything placed between the jaws to turn." - icon = 'icons/obj/abductor.dmi' - icon_state = "wrench" - usesound = 'sound/effects/empulse.ogg' - toolspeed = 0.1 - origin_tech = "materials=5;engineering=5;abductor=3" - -/obj/item/wrench/power - name = "hand drill" - desc = "A simple powered drill with a bolt bit." - icon_state = "drill_bolt" - item_state = "drill" - usesound = 'sound/items/drill_use.ogg' - materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) - origin_tech = "materials=2;engineering=2" //done for balance reasons, making them high value for research, but harder to get - force = 8 //might or might not be too high, subject to change - throwforce = 8 - attack_verb = list("drilled", "screwed", "jabbed") - toolspeed = 0.25 - -/obj/item/wrench/power/attack_self(mob/user) - playsound(get_turf(user),'sound/items/change_drill.ogg', 50, 1) - var/obj/item/wirecutters/power/s_drill = new /obj/item/screwdriver/power - to_chat(user, "You attach the screwdriver bit to [src].") - qdel(src) - user.put_in_active_hand(s_drill) - -/obj/item/wrench/power/suicide_act(mob/user) - user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/wrench/medical - name = "medical wrench" - desc = "A medical wrench with common (medical?) uses. Can be found in your hand." - icon_state = "wrench_medical" - force = 2 //MEDICAL - throwforce = 4 - origin_tech = "materials=1;engineering=1;biotech=3" - attack_verb = list("wrenched", "medicaled", "tapped", "jabbed", "whacked") - -/obj/item/wrench/medical/suicide_act(mob/user) - user.visible_message("[user] is praying to the medical wrench to take [user.p_their()] soul. It looks like [user.p_theyre()] trying to commit suicide!") - // TODO Make them glow with the power of the M E D I C A L W R E N C H - // during their ascension - - // Stun stops them from wandering off - user.Stun(5) - playsound(loc, 'sound/effects/pray.ogg', 50, 1, -1) - - // Let the sound effect finish playing - sleep(20) - - if(!user) - return - - for(var/obj/item/W in user) - user.unEquip(W) - - var/obj/item/wrench/medical/W = new /obj/item/wrench/medical(loc) - W.add_fingerprint(user) - W.desc += " For some reason, it reminds you of [user.name]." - - if(!user) - return - - user.dust() - return OBLITERATION - -//Screwdriver -/obj/item/screwdriver - name = "screwdriver" - desc = "You can be totally screwy with this." - icon = 'icons/obj/tools.dmi' - icon_state = "screwdriver_map" - flags = CONDUCT - slot_flags = SLOT_BELT - force = 5 - w_class = WEIGHT_CLASS_TINY - throwforce = 5 - throw_speed = 3 - throw_range = 5 - materials = list(MAT_METAL=75) - attack_verb = list("stabbed") - hitsound = 'sound/weapons/bladeslice.ogg' - usesound = 'sound/items/screwdriver.ogg' - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - var/random_color = TRUE //if the screwdriver uses random coloring - -/obj/item/screwdriver/nuke - name = "screwdriver" - desc = "A screwdriver with an ultra thin tip." - icon_state = "screwdriver_nuke" - toolspeed = 0.5 - -/obj/item/screwdriver/suicide_act(mob/user) - user.visible_message("[user] is stabbing [src] into [user.p_their()] [pick("temple", "heart")]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/screwdriver/New(loc, var/param_color = null) - ..() - if(random_color) - if(!param_color) - param_color = pick("red","blue","pink","brown","green","cyan","yellow") - icon_state = "screwdriver_[param_color]" - - if (prob(75)) - src.pixel_y = rand(0, 16) - -/obj/item/screwdriver/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!istype(M) || user.a_intent == INTENT_HELP) - return ..() - if(user.zone_selected != "eyes" && user.zone_selected != "head") - return ..() - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm [M]!") - return - if((CLUMSY in user.mutations) && prob(50)) - M = user - return eyestab(M,user) - -/obj/item/screwdriver/brass - name = "brass screwdriver" - desc = "A screwdriver made of brass. The handle feels freezing cold." - icon_state = "screwdriver_brass" - toolspeed = 0.5 - random_color = FALSE - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/screwdriver/abductor - name = "alien screwdriver" - desc = "An ultrasonic screwdriver." - icon = 'icons/obj/abductor.dmi' - icon_state = "screwdriver" - usesound = 'sound/items/pshoom.ogg' - toolspeed = 0.1 - random_color = FALSE - -/obj/item/screwdriver/power - name = "hand drill" - desc = "A simple hand drill with a screwdriver bit attached." - icon_state = "drill_screw" - item_state = "drill" - materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) - origin_tech = "materials=2;engineering=2" //done for balance reasons, making them high value for research, but harder to get - force = 8 //might or might not be too high, subject to change - throwforce = 8 - throw_speed = 2 - throw_range = 3//it's heavier than a screw driver/wrench, so it does more damage, but can't be thrown as far - attack_verb = list("drilled", "screwed", "jabbed","whacked") - hitsound = 'sound/items/drill_hit.ogg' - usesound = 'sound/items/drill_use.ogg' - toolspeed = 0.25 - random_color = FALSE - -/obj/item/screwdriver/power/suicide_act(mob/user) - user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/screwdriver/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, 1) - var/obj/item/wrench/power/b_drill = new /obj/item/wrench/power - to_chat(user, "You attach the bolt driver bit to [src].") - qdel(src) - user.put_in_active_hand(b_drill) - -/obj/item/screwdriver/cyborg - name = "powered screwdriver" - desc = "An electrical screwdriver, designed to be both precise and quick." - usesound = 'sound/items/drill_use.ogg' - toolspeed = 0.5 - -//Wirecutters -/obj/item/wirecutters - name = "wirecutters" - desc = "This cuts wires." - icon = 'icons/obj/tools.dmi' - icon_state = "cutters" - flags = CONDUCT - slot_flags = SLOT_BELT - force = 6 - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=80) - origin_tech = "materials=1;engineering=1" - attack_verb = list("pinched", "nipped") - hitsound = 'sound/items/wirecutter.ogg' - usesound = 'sound/items/wirecutter.ogg' - sharp = 1 - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - var/random_color = TRUE - -/obj/item/wirecutters/New(loc, param_color = null) - ..() - if(random_color) - if(!param_color) - param_color = pick("yellow", "red") - icon_state = "cutters_[param_color]" - -/obj/item/wirecutters/attack(mob/living/carbon/C, mob/user) - if(istype(C) && C.handcuffed && istype(C.handcuffed, /obj/item/restraints/handcuffs/cable)) - user.visible_message("[user] cuts [C]'s restraints with [src]!") - QDEL_NULL(C.handcuffed) - if(C.buckled && C.buckled.buckle_requires_restraints) - C.buckled.unbuckle_mob(C) - C.update_handcuffed() - return - else - ..() - -/obj/item/wirecutters/suicide_act(mob/user) - user.visible_message("[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, usesound, 50, 1, -1) - return BRUTELOSS - -/obj/item/wirecutters/brass - name = "brass wirecutters" - desc = "A pair of wirecutters made of brass. The handle feels freezing cold to the touch." - icon_state = "cutters_brass" - toolspeed = 0.5 - random_color = FALSE - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/wirecutters/abductor - name = "alien wirecutters" - desc = "Extremely sharp wirecutters, made out of a silvery-green metal." - icon = 'icons/obj/abductor.dmi' - icon_state = "cutters" - toolspeed = 0.1 - origin_tech = "materials=5;engineering=4;abductor=3" - random_color = FALSE - -/obj/item/wirecutters/cyborg - name = "wirecutters" - desc = "This cuts wires." - toolspeed = 0.5 - -/obj/item/wirecutters/power - name = "jaws of life" - desc = "A set of jaws of life, the magic of science has managed to fit it down into a device small enough to fit in a tool belt. It's fitted with a cutting head." - icon_state = "jaws_cutter" - item_state = "jawsoflife" - origin_tech = "materials=2;engineering=2" - materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) - usesound = 'sound/items/jaws_cut.ogg' - toolspeed = 0.25 - random_color = FALSE - -/obj/item/wirecutters/power/suicide_act(mob/user) - user.visible_message("[user] is wrapping \the [src] around [user.p_their()] neck. It looks like [user.p_theyre()] trying to rip [user.p_their()] head off!") - playsound(loc, 'sound/items/jaws_cut.ogg', 50, 1, -1) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - var/obj/item/organ/external/head/head = H.bodyparts_by_name["head"] - if(head) - head.droplimb(0, DROPLIMB_BLUNT, FALSE, TRUE) - playsound(loc,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1) - return BRUTELOSS - -/obj/item/wirecutters/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1) - var/obj/item/crowbar/power/pryjaws = new /obj/item/crowbar/power - to_chat(user, "You attach the pry jaws to [src].") - qdel(src) - user.put_in_active_hand(pryjaws) - -//Welding Tool -/obj/item/weldingtool - name = "welding tool" - desc = "A standard edition welder provided by Nanotrasen." - icon = 'icons/obj/tools.dmi' - icon_state = "welder" - item_state = "welder" - flags = CONDUCT - slot_flags = SLOT_BELT - force = 3 - throwforce = 5 - throw_speed = 3 - throw_range = 5 - hitsound = "swing_hit" - usesound = 'sound/items/welder.ogg' - var/acti_sound = 'sound/items/welderactivate.ogg' - var/deac_sound = 'sound/items/welderdeactivate.ogg' - w_class = WEIGHT_CLASS_SMALL - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) - resistance_flags = FIRE_PROOF - materials = list(MAT_METAL=70, MAT_GLASS=30) - origin_tech = "engineering=1;plasmatech=1" - toolspeed = 1 - var/welding = 0 //Whether or not the welding tool is off(0), on(1) or currently welding(2) - var/status = 1 //Whether the welder is secured or unsecured (able to attach rods to it to make a flamethrower) - var/max_fuel = 20 //The max amount of fuel the welder can hold - var/change_icons = 1 - var/can_off_process = 0 - var/light_intensity = 2 //how powerful the emitted light is when used. - var/nextrefueltick = 0 - -/obj/item/weldingtool/New() - ..() - create_reagents(max_fuel) - reagents.add_reagent("fuel", max_fuel) - update_icon() - -/obj/item/weldingtool/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 0) - . += "It contains [get_fuel()] unit\s of fuel out of [max_fuel]." - -/obj/item/weldingtool/suicide_act(mob/user) - user.visible_message("[user] welds [user.p_their()] every orifice closed! It looks like [user.p_theyre()] trying to commit suicide!") - return FIRELOSS - -/obj/item/weldingtool/proc/update_torch() - overlays.Cut() - if(welding) - overlays += "[initial(icon_state)]-on" - item_state = "[initial(item_state)]1" - else - item_state = "[initial(item_state)]" - -/obj/item/weldingtool/update_icon() - if(change_icons) - var/ratio = get_fuel() / max_fuel - ratio = Ceiling(ratio*4) * 25 - if(ratio == 100) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)][ratio]" - update_torch() - ..() - -/obj/item/weldingtool/process() - switch(welding) - if(0) - force = 3 - damtype = "brute" - update_icon() - if(!can_off_process) - STOP_PROCESSING(SSobj, src) - return - //Welders left on now use up fuel, but lets not have them run out quite that fast - if(1) - force = 15 - damtype = "fire" - if(prob(5)) - remove_fuel(1) - update_icon() - - //This is to start fires. process() is only called if the welder is on. - var/turf/location = loc - if(ismob(location)) - var/mob/M = location - if(M.l_hand == src || M.r_hand == src) - location = get_turf(M) - if(isturf(location)) - location.hotspot_expose(700, 5) - -/obj/item/weldingtool/attackby(obj/item/I, mob/user, params) - if(isscrewdriver(I)) - flamethrower_screwdriver(I, user) - else if(istype(I, /obj/item/stack/rods)) - flamethrower_rods(I, user) - else - ..() - -/obj/item/weldingtool/attack(mob/M, mob/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/S = H.bodyparts_by_name[user.zone_selected] - - if(!S) - return - - if(!S.is_robotic() || user.a_intent != INTENT_HELP || S.open == 2) - return ..() - - if(!isOn()) //why wasn't this being checked already? - to_chat(user, "Turn on [src] before attempting repairs!") - return 1 - - if(S.brute_dam > ROBOLIMB_SELF_REPAIR_CAP) - to_chat(user, "The damage is far too severe to patch over externally.") - return - - if(!S.brute_dam) - to_chat(user, "Nothing to fix!") - return - - if(get_fuel() >= 1) - if(H == user) - if(!do_mob(user, H, 10)) - return 1 - if(!remove_fuel(1,null)) - to_chat(user, "Need more welding fuel!") - var/rembrute = HEALPERWELD - var/nrembrute = 0 - var/childlist - if(!isnull(S.children)) - childlist = S.children.Copy() - var/parenthealed = FALSE - while(rembrute > 0) - var/obj/item/organ/external/E - if(S.brute_dam) - E = S - else if(LAZYLEN(childlist)) - E = pick_n_take(childlist) - if(!E.brute_dam || !E.is_robotic()) - continue - else if(S.parent && !parenthealed) - E = S.parent - parenthealed = TRUE - if(!E.brute_dam || !E.is_robotic()) - break - else - break - playsound(src.loc, usesound, 50, 1) - nrembrute = max(rembrute - E.brute_dam, 0) - E.heal_damage(rembrute,0,0,1) - rembrute = nrembrute - user.visible_message("\The [user] patches some dents on \the [M]'s [E.name] with \the [src].") - if(H.bleed_rate && H.isSynthetic()) - H.bleed_rate = 0 - user.visible_message("\The [user] patches some leaks on [M] with \the [src].") - return 1 - else - return ..() - -/obj/item/weldingtool/afterattack(atom/O, mob/user, proximity) - if(!proximity) - return - if(welding) - remove_fuel(1) - var/turf/location = get_turf(user) - location.hotspot_expose(700, 50, 1) - if(get_fuel() <= 0) - set_light(0) - - if(isliving(O)) - var/mob/living/L = O - if(L.IgniteMob()) - message_admins("[key_name_admin(user)] set [key_name_admin(L)] on fire") - log_game("[key_name(user)] set [key_name(L)] on fire") - -/obj/item/weldingtool/attack_self(mob/user) - switched_on(user) - if(welding) - set_light(light_intensity) - - update_icon() - -//Returns the amount of fuel in the welder -/obj/item/weldingtool/proc/get_fuel() - return reagents.get_reagent_amount("fuel") - -//Removes fuel from the welding tool. If a mob is passed, it will try to flash the mob's eyes. This should probably be renamed to use() -/obj/item/weldingtool/proc/remove_fuel(amount = 1, mob/living/M = null) - if(!welding || !check_fuel()) - return FALSE - if(get_fuel() >= amount) - reagents.remove_reagent("fuel", amount) - check_fuel() - if(M) - M.flash_eyes(light_intensity) - return TRUE - else - if(M) - to_chat(M, "You need more welding fuel to complete this task.") - return FALSE - -//Returns whether or not the welding tool is currently on. -/obj/item/weldingtool/proc/isOn() - return welding - -//Turns off the welder if there is no more fuel (does this really need to be its own proc?) -/obj/item/weldingtool/proc/check_fuel(mob/user) - if(get_fuel() <= 0 && welding) - switched_on(user) - update_icon() - //mob icon update - if(ismob(loc)) - var/mob/M = loc - M.update_inv_r_hand(0) - M.update_inv_l_hand(0) - return 0 - return 1 - -//Switches the welder on -/obj/item/weldingtool/proc/switched_on(mob/user) - if(!status) - to_chat(user, "[src] can't be turned on while unsecured!") - return - welding = !welding - if(welding) - if(get_fuel() >= 1) - to_chat(user, "You switch [src] on.") - playsound(loc, acti_sound, 50, 1) - force = 15 - damtype = "fire" - hitsound = 'sound/items/welder.ogg' - update_icon() - START_PROCESSING(SSobj, src) - else - to_chat(user, "You need more fuel!") - switched_off(user) - else - if(user) - to_chat(user, "You switch [src] off.") - playsound(loc, deac_sound, 50, 1) - switched_off(user) - -//Switches the welder off -/obj/item/weldingtool/proc/switched_off(mob/user) - welding = 0 - set_light(0) - - force = 3 - damtype = "brute" - hitsound = "swing_hit" - update_icon() - -/obj/item/weldingtool/proc/flamethrower_screwdriver(obj/item/I, mob/user) - if(welding) - to_chat(user, "Turn it off first!") - return - status = !status - if(status) - to_chat(user, "You resecure [src].") - else - to_chat(user, "[src] can now be attached and modified.") - add_fingerprint(user) - -/obj/item/weldingtool/proc/flamethrower_rods(obj/item/I, mob/user) - if(!status) - var/obj/item/stack/rods/R = I - if(R.use(1)) - var/obj/item/flamethrower/F = new /obj/item/flamethrower(user.loc) - if(!remove_item_from_storage(F)) - user.unEquip(src) - loc = F - F.weldtool = src - add_fingerprint(user) - to_chat(user, "You add a rod to a welder, starting to build a flamethrower.") - user.put_in_hands(F) - else - to_chat(user, "You need one rod to start building a flamethrower!") - -/obj/item/weldingtool/largetank - name = "Industrial Welding Tool" - desc = "A slightly larger welder with a larger tank." - icon_state = "indwelder" - max_fuel = 40 - materials = list(MAT_METAL=70, MAT_GLASS=60) - origin_tech = "engineering=2;plasmatech=2" - -/obj/item/weldingtool/largetank/cyborg - name = "integrated welding tool" - desc = "An advanced welder designed to be used in robotic systems." - toolspeed = 0.5 - -/obj/item/weldingtool/largetank/flamethrower_screwdriver() - return - -/obj/item/weldingtool/mini - name = "emergency welding tool" - desc = "A miniature welder used during emergencies." - icon_state = "miniwelder" - max_fuel = 10 - w_class = WEIGHT_CLASS_TINY - materials = list(MAT_METAL=30, MAT_GLASS=10) - change_icons = 0 - -/obj/item/weldingtool/mini/flamethrower_screwdriver() - return - -/obj/item/weldingtool/abductor - name = "alien welding tool" - desc = "An alien welding tool. Whatever fuel it uses, it never runs out." - icon = 'icons/obj/abductor.dmi' - icon_state = "welder" - toolspeed = 0.1 - light_intensity = 0 - change_icons = 0 - origin_tech = "plasmatech=5;engineering=5;abductor=3" - can_off_process = 1 - -/obj/item/weldingtool/abductor/process() - if(get_fuel() <= max_fuel) - reagents.add_reagent("fuel", 1) - ..() - -/obj/item/weldingtool/hugetank - name = "Upgraded Welding Tool" - desc = "An upgraded welder based off the industrial welder." - icon_state = "upindwelder" - item_state = "upindwelder" - max_fuel = 80 - materials = list(MAT_METAL=70, MAT_GLASS=120) - origin_tech = "engineering=3;plasmatech=2" - -/obj/item/weldingtool/experimental - name = "Experimental Welding Tool" - desc = "An experimental welder capable of self-fuel generation and less harmful to the eyes." - icon_state = "exwelder" - item_state = "exwelder" - max_fuel = 40 - materials = list(MAT_METAL=70, MAT_GLASS=120) - origin_tech = "materials=4;engineering=4;bluespace=3;plasmatech=4" - change_icons = 0 - can_off_process = 1 - light_intensity = 1 - toolspeed = 0.5 - var/last_gen = 0 - -/obj/item/weldingtool/experimental/brass - name = "brass welding tool" - desc = "A brass welder that seems to constantly refuel itself. It is faintly warm to the touch." - icon_state = "brasswelder" - item_state = "brasswelder" - resistance_flags = FIRE_PROOF | ACID_PROOF - -obj/item/weldingtool/experimental/process() - ..() - if(get_fuel() < max_fuel && nextrefueltick < world.time) - nextrefueltick = world.time + 10 - reagents.add_reagent("fuel", 1) - -//Crowbar -/obj/item/crowbar - name = "pocket crowbar" - desc = "A small crowbar. This handy tool is useful for lots of things, such as prying floor tiles or opening unpowered doors." - icon = 'icons/obj/tools.dmi' - icon_state = "crowbar" - item_state = "crowbar" - usesound = 'sound/items/crowbar.ogg' - flags = CONDUCT - slot_flags = SLOT_BELT - force = 5 - throwforce = 7 - item_state = "crowbar" - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=50) - origin_tech = "engineering=1;combat=1" - attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked") - toolspeed = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - -/obj/item/crowbar/red - icon_state = "crowbar_red" - item_state = "crowbar_red" - force = 8 - -/obj/item/crowbar/brass - name = "brass crowbar" - desc = "A brass crowbar. It feels faintly warm to the touch." - icon_state = "crowbar_brass" - item_state = "crowbar_brass" - toolspeed = 0.5 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/crowbar/abductor - name = "alien crowbar" - desc = "A hard-light crowbar. It appears to pry by itself, without any effort required." - icon = 'icons/obj/abductor.dmi' - usesound = 'sound/weapons/sonic_jackhammer.ogg' - icon_state = "crowbar" - toolspeed = 0.1 - origin_tech = "combat=4;engineering=4;abductor=3" - -/obj/item/crowbar/large - name = "crowbar" - desc = "It's a big crowbar. It doesn't fit in your pockets, because its too big." - force = 12 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 3 - throw_range = 3 - materials = list(MAT_METAL=70) - icon_state = "crowbar_large" - item_state = "crowbar_large" - toolspeed = 0.5 - -/obj/item/crowbar/cyborg - name = "hydraulic crowbar" - desc = "A hydraulic prying tool, compact but powerful. Designed to replace crowbar in construction cyborgs." - usesound = 'sound/items/jaws_pry.ogg' - force = 10 - toolspeed = 0.5 - -/obj/item/crowbar/power - name = "jaws of life" - desc = "A set of jaws of life, the magic of science has managed to fit it down into a device small enough to fit in a tool belt. It's fitted with a prying head." - icon_state = "jaws_pry" - item_state = "jawsoflife" - materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) - origin_tech = "materials=2;engineering=2" - usesound = 'sound/items/jaws_pry.ogg' - force = 15 - toolspeed = 0.25 - var/airlock_open_time = 100 // Time required to open powered airlocks - -/obj/item/crowbar/power/suicide_act(mob/user) - user.visible_message("[user] is putting [user.p_their()] head in [src]. It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/items/jaws_pry.ogg', 50, 1, -1) - return BRUTELOSS - -/obj/item/crowbar/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1) - var/obj/item/wirecutters/power/cutjaws = new /obj/item/wirecutters/power - to_chat(user, "You attach the cutting jaws to [src].") - qdel(src) - user.put_in_active_hand(cutjaws) - -// Conversion kit -/obj/item/conversion_kit - name = "\improper Revolver Conversion Kit" - desc = "A professional conversion kit used to convert any knock off revolver into the real deal capable of shooting lethal .357 rounds without the possibility of catastrophic failure." - icon_state = "kit" - flags = CONDUCT - w_class = WEIGHT_CLASS_SMALL - origin_tech = "combat=2" - var/open = 0 - -/obj/item/conversion_kit/New() - ..() - update_icon() - -/obj/item/conversion_kit/update_icon() - icon_state = "[initial(icon_state)]_[open]" - -/obj/item/conversion_kit/attack_self(mob/user) - open = !open - to_chat(user, "You [open ? "open" : "close"] the conversion kit.") - update_icon() +#define HEALPERWELD 15 + +/* Tools! + * Note: Multitools are in devices + * + * Contains: + * Wrench + * Screwdriver + * Wirecutters + * Welding Tool + * Crowbar + * Revolver Conversion Kit + */ + +//Wrench +/obj/item/wrench + name = "wrench" + desc = "A wrench with common uses. Can be found in your hand." + icon = 'icons/obj/tools.dmi' + icon_state = "wrench" + flags = CONDUCT + slot_flags = SLOT_BELT + force = 5 + throwforce = 7 + usesound = 'sound/items/ratchet.ogg' + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=150) + origin_tech = "materials=1;engineering=1" + attack_verb = list("bashed", "battered", "bludgeoned", "whacked") + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + +/obj/item/wrench/suicide_act(mob/user) + user.visible_message("[user] is beating [user.p_them()]self to death with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/weapons/genhit.ogg', 50, 1, -1) + return BRUTELOSS + +/obj/item/wrench/cyborg + name = "automatic wrench" + desc = "An advanced robotic wrench. Can be found in construction cyborgs." + toolspeed = 0.5 + +/obj/item/wrench/brass + name = "brass wrench" + desc = "A brass wrench. It's faintly warm to the touch." + icon_state = "wrench_brass" + toolspeed = 0.5 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/wrench/abductor + name = "alien wrench" + desc = "A polarized wrench. It causes anything placed between the jaws to turn." + icon = 'icons/obj/abductor.dmi' + icon_state = "wrench" + usesound = 'sound/effects/empulse.ogg' + toolspeed = 0.1 + origin_tech = "materials=5;engineering=5;abductor=3" + +/obj/item/wrench/power + name = "hand drill" + desc = "A simple powered drill with a bolt bit." + icon_state = "drill_bolt" + item_state = "drill" + usesound = 'sound/items/drill_use.ogg' + materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) + origin_tech = "materials=2;engineering=2" //done for balance reasons, making them high value for research, but harder to get + force = 8 //might or might not be too high, subject to change + throwforce = 8 + attack_verb = list("drilled", "screwed", "jabbed") + toolspeed = 0.25 + +/obj/item/wrench/power/attack_self(mob/user) + playsound(get_turf(user),'sound/items/change_drill.ogg', 50, 1) + var/obj/item/wirecutters/power/s_drill = new /obj/item/screwdriver/power + to_chat(user, "You attach the screwdriver bit to [src].") + qdel(src) + user.put_in_active_hand(s_drill) + +/obj/item/wrench/power/suicide_act(mob/user) + user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/wrench/medical + name = "medical wrench" + desc = "A medical wrench with common (medical?) uses. Can be found in your hand." + icon_state = "wrench_medical" + force = 2 //MEDICAL + throwforce = 4 + origin_tech = "materials=1;engineering=1;biotech=3" + attack_verb = list("wrenched", "medicaled", "tapped", "jabbed", "whacked") + +/obj/item/wrench/medical/suicide_act(mob/user) + user.visible_message("[user] is praying to the medical wrench to take [user.p_their()] soul. It looks like [user.p_theyre()] trying to commit suicide!") + // TODO Make them glow with the power of the M E D I C A L W R E N C H + // during their ascension + + // Stun stops them from wandering off + user.Stun(5) + playsound(loc, 'sound/effects/pray.ogg', 50, 1, -1) + + // Let the sound effect finish playing + sleep(20) + + if(!user) + return + + for(var/obj/item/W in user) + user.unEquip(W) + + var/obj/item/wrench/medical/W = new /obj/item/wrench/medical(loc) + W.add_fingerprint(user) + W.desc += " For some reason, it reminds you of [user.name]." + + if(!user) + return + + user.dust() + return OBLITERATION + +//Screwdriver +/obj/item/screwdriver + name = "screwdriver" + desc = "You can be totally screwy with this." + icon = 'icons/obj/tools.dmi' + icon_state = "screwdriver_map" + flags = CONDUCT + slot_flags = SLOT_BELT + force = 5 + w_class = WEIGHT_CLASS_TINY + throwforce = 5 + throw_speed = 3 + throw_range = 5 + materials = list(MAT_METAL=75) + attack_verb = list("stabbed") + hitsound = 'sound/weapons/bladeslice.ogg' + usesound = 'sound/items/screwdriver.ogg' + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + var/random_color = TRUE //if the screwdriver uses random coloring + +/obj/item/screwdriver/nuke + name = "screwdriver" + desc = "A screwdriver with an ultra thin tip." + icon_state = "screwdriver_nuke" + toolspeed = 0.5 + +/obj/item/screwdriver/suicide_act(mob/user) + user.visible_message("[user] is stabbing [src] into [user.p_their()] [pick("temple", "heart")]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/screwdriver/New(loc, var/param_color = null) + ..() + if(random_color) + if(!param_color) + param_color = pick("red","blue","pink","brown","green","cyan","yellow") + icon_state = "screwdriver_[param_color]" + + if (prob(75)) + src.pixel_y = rand(0, 16) + +/obj/item/screwdriver/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!istype(M) || user.a_intent == INTENT_HELP) + return ..() + if(user.zone_selected != "eyes" && user.zone_selected != "head") + return ..() + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm [M]!") + return + if((CLUMSY in user.mutations) && prob(50)) + M = user + return eyestab(M,user) + +/obj/item/screwdriver/brass + name = "brass screwdriver" + desc = "A screwdriver made of brass. The handle feels freezing cold." + icon_state = "screwdriver_brass" + toolspeed = 0.5 + random_color = FALSE + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/screwdriver/abductor + name = "alien screwdriver" + desc = "An ultrasonic screwdriver." + icon = 'icons/obj/abductor.dmi' + icon_state = "screwdriver" + usesound = 'sound/items/pshoom.ogg' + toolspeed = 0.1 + random_color = FALSE + +/obj/item/screwdriver/power + name = "hand drill" + desc = "A simple hand drill with a screwdriver bit attached." + icon_state = "drill_screw" + item_state = "drill" + materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) + origin_tech = "materials=2;engineering=2" //done for balance reasons, making them high value for research, but harder to get + force = 8 //might or might not be too high, subject to change + throwforce = 8 + throw_speed = 2 + throw_range = 3//it's heavier than a screw driver/wrench, so it does more damage, but can't be thrown as far + attack_verb = list("drilled", "screwed", "jabbed","whacked") + hitsound = 'sound/items/drill_hit.ogg' + usesound = 'sound/items/drill_use.ogg' + toolspeed = 0.25 + random_color = FALSE + +/obj/item/screwdriver/power/suicide_act(mob/user) + user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/screwdriver/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, 1) + var/obj/item/wrench/power/b_drill = new /obj/item/wrench/power + to_chat(user, "You attach the bolt driver bit to [src].") + qdel(src) + user.put_in_active_hand(b_drill) + +/obj/item/screwdriver/cyborg + name = "powered screwdriver" + desc = "An electrical screwdriver, designed to be both precise and quick." + usesound = 'sound/items/drill_use.ogg' + toolspeed = 0.5 + +//Wirecutters +/obj/item/wirecutters + name = "wirecutters" + desc = "This cuts wires." + icon = 'icons/obj/tools.dmi' + icon_state = "cutters" + flags = CONDUCT + slot_flags = SLOT_BELT + force = 6 + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=80) + origin_tech = "materials=1;engineering=1" + attack_verb = list("pinched", "nipped") + hitsound = 'sound/items/wirecutter.ogg' + usesound = 'sound/items/wirecutter.ogg' + sharp = 1 + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + var/random_color = TRUE + +/obj/item/wirecutters/New(loc, param_color = null) + ..() + if(random_color) + if(!param_color) + param_color = pick("yellow", "red") + icon_state = "cutters_[param_color]" + +/obj/item/wirecutters/attack(mob/living/carbon/C, mob/user) + if(istype(C) && C.handcuffed && istype(C.handcuffed, /obj/item/restraints/handcuffs/cable)) + user.visible_message("[user] cuts [C]'s restraints with [src]!") + QDEL_NULL(C.handcuffed) + if(C.buckled && C.buckled.buckle_requires_restraints) + C.buckled.unbuckle_mob(C) + C.update_handcuffed() + return + else + ..() + +/obj/item/wirecutters/suicide_act(mob/user) + user.visible_message("[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, usesound, 50, 1, -1) + return BRUTELOSS + +/obj/item/wirecutters/brass + name = "brass wirecutters" + desc = "A pair of wirecutters made of brass. The handle feels freezing cold to the touch." + icon_state = "cutters_brass" + toolspeed = 0.5 + random_color = FALSE + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/wirecutters/abductor + name = "alien wirecutters" + desc = "Extremely sharp wirecutters, made out of a silvery-green metal." + icon = 'icons/obj/abductor.dmi' + icon_state = "cutters" + toolspeed = 0.1 + origin_tech = "materials=5;engineering=4;abductor=3" + random_color = FALSE + +/obj/item/wirecutters/cyborg + name = "wirecutters" + desc = "This cuts wires." + toolspeed = 0.5 + +/obj/item/wirecutters/power + name = "jaws of life" + desc = "A set of jaws of life, the magic of science has managed to fit it down into a device small enough to fit in a tool belt. It's fitted with a cutting head." + icon_state = "jaws_cutter" + item_state = "jawsoflife" + origin_tech = "materials=2;engineering=2" + materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) + usesound = 'sound/items/jaws_cut.ogg' + toolspeed = 0.25 + random_color = FALSE + +/obj/item/wirecutters/power/suicide_act(mob/user) + user.visible_message("[user] is wrapping \the [src] around [user.p_their()] neck. It looks like [user.p_theyre()] trying to rip [user.p_their()] head off!") + playsound(loc, 'sound/items/jaws_cut.ogg', 50, 1, -1) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/head/head = H.bodyparts_by_name["head"] + if(head) + head.droplimb(0, DROPLIMB_BLUNT, FALSE, TRUE) + playsound(loc,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1) + return BRUTELOSS + +/obj/item/wirecutters/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1) + var/obj/item/crowbar/power/pryjaws = new /obj/item/crowbar/power + to_chat(user, "You attach the pry jaws to [src].") + qdel(src) + user.put_in_active_hand(pryjaws) + +//Welding Tool +/obj/item/weldingtool + name = "welding tool" + desc = "A standard edition welder provided by Nanotrasen." + icon = 'icons/obj/tools.dmi' + icon_state = "welder" + item_state = "welder" + flags = CONDUCT + slot_flags = SLOT_BELT + force = 3 + throwforce = 5 + throw_speed = 3 + throw_range = 5 + hitsound = "swing_hit" + usesound = 'sound/items/welder.ogg' + var/acti_sound = 'sound/items/welderactivate.ogg' + var/deac_sound = 'sound/items/welderdeactivate.ogg' + w_class = WEIGHT_CLASS_SMALL + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) + resistance_flags = FIRE_PROOF + materials = list(MAT_METAL=70, MAT_GLASS=30) + origin_tech = "engineering=1;plasmatech=1" + toolspeed = 1 + var/welding = 0 //Whether or not the welding tool is off(0), on(1) or currently welding(2) + var/status = 1 //Whether the welder is secured or unsecured (able to attach rods to it to make a flamethrower) + var/max_fuel = 20 //The max amount of fuel the welder can hold + var/change_icons = 1 + var/can_off_process = 0 + var/light_intensity = 2 //how powerful the emitted light is when used. + var/nextrefueltick = 0 + +/obj/item/weldingtool/New() + ..() + create_reagents(max_fuel) + reagents.add_reagent("fuel", max_fuel) + update_icon() + +/obj/item/weldingtool/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 0) + . += "It contains [get_fuel()] unit\s of fuel out of [max_fuel]." + +/obj/item/weldingtool/suicide_act(mob/user) + user.visible_message("[user] welds [user.p_their()] every orifice closed! It looks like [user.p_theyre()] trying to commit suicide!") + return FIRELOSS + +/obj/item/weldingtool/proc/update_torch() + overlays.Cut() + if(welding) + overlays += "[initial(icon_state)]-on" + item_state = "[initial(item_state)]1" + else + item_state = "[initial(item_state)]" + +/obj/item/weldingtool/update_icon() + if(change_icons) + var/ratio = get_fuel() / max_fuel + ratio = Ceiling(ratio*4) * 25 + if(ratio == 100) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)][ratio]" + update_torch() + ..() + +/obj/item/weldingtool/process() + switch(welding) + if(0) + force = 3 + damtype = "brute" + update_icon() + if(!can_off_process) + STOP_PROCESSING(SSobj, src) + return + //Welders left on now use up fuel, but lets not have them run out quite that fast + if(1) + force = 15 + damtype = "fire" + if(prob(5)) + remove_fuel(1) + update_icon() + + //This is to start fires. process() is only called if the welder is on. + var/turf/location = loc + if(ismob(location)) + var/mob/M = location + if(M.l_hand == src || M.r_hand == src) + location = get_turf(M) + if(isturf(location)) + location.hotspot_expose(700, 5) + +/obj/item/weldingtool/attackby(obj/item/I, mob/user, params) + if(isscrewdriver(I)) + flamethrower_screwdriver(I, user) + else if(istype(I, /obj/item/stack/rods)) + flamethrower_rods(I, user) + else + ..() + +/obj/item/weldingtool/attack(mob/M, mob/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/S = H.bodyparts_by_name[user.zone_selected] + + if(!S) + return + + if(!S.is_robotic() || user.a_intent != INTENT_HELP || S.open == 2) + return ..() + + if(!isOn()) //why wasn't this being checked already? + to_chat(user, "Turn on [src] before attempting repairs!") + return 1 + + if(S.brute_dam > ROBOLIMB_SELF_REPAIR_CAP) + to_chat(user, "The damage is far too severe to patch over externally.") + return + + if(!S.brute_dam) + to_chat(user, "Nothing to fix!") + return + + if(get_fuel() >= 1) + if(H == user) + if(!do_mob(user, H, 10)) + return 1 + if(!remove_fuel(1,null)) + to_chat(user, "Need more welding fuel!") + var/rembrute = HEALPERWELD + var/nrembrute = 0 + var/childlist + if(!isnull(S.children)) + childlist = S.children.Copy() + var/parenthealed = FALSE + while(rembrute > 0) + var/obj/item/organ/external/E + if(S.brute_dam) + E = S + else if(LAZYLEN(childlist)) + E = pick_n_take(childlist) + if(!E.brute_dam || !E.is_robotic()) + continue + else if(S.parent && !parenthealed) + E = S.parent + parenthealed = TRUE + if(!E.brute_dam || !E.is_robotic()) + break + else + break + playsound(src.loc, usesound, 50, 1) + nrembrute = max(rembrute - E.brute_dam, 0) + E.heal_damage(rembrute,0,0,1) + rembrute = nrembrute + user.visible_message("\The [user] patches some dents on \the [M]'s [E.name] with \the [src].") + if(H.bleed_rate && H.isSynthetic()) + H.bleed_rate = 0 + user.visible_message("\The [user] patches some leaks on [M] with \the [src].") + return 1 + else + return ..() + +/obj/item/weldingtool/afterattack(atom/O, mob/user, proximity) + if(!proximity) + return + if(welding) + remove_fuel(1) + var/turf/location = get_turf(user) + location.hotspot_expose(700, 50, 1) + if(get_fuel() <= 0) + set_light(0) + + if(isliving(O)) + var/mob/living/L = O + if(L.IgniteMob()) + message_admins("[key_name_admin(user)] set [key_name_admin(L)] on fire") + log_game("[key_name(user)] set [key_name(L)] on fire") + +/obj/item/weldingtool/attack_self(mob/user) + switched_on(user) + if(welding) + set_light(light_intensity) + + update_icon() + +//Returns the amount of fuel in the welder +/obj/item/weldingtool/proc/get_fuel() + return reagents.get_reagent_amount("fuel") + +//Removes fuel from the welding tool. If a mob is passed, it will try to flash the mob's eyes. This should probably be renamed to use() +/obj/item/weldingtool/proc/remove_fuel(amount = 1, mob/living/M = null) + if(!welding || !check_fuel()) + return FALSE + if(get_fuel() >= amount) + reagents.remove_reagent("fuel", amount) + check_fuel() + if(M) + M.flash_eyes(light_intensity) + return TRUE + else + if(M) + to_chat(M, "You need more welding fuel to complete this task.") + return FALSE + +//Returns whether or not the welding tool is currently on. +/obj/item/weldingtool/proc/isOn() + return welding + +//Turns off the welder if there is no more fuel (does this really need to be its own proc?) +/obj/item/weldingtool/proc/check_fuel(mob/user) + if(get_fuel() <= 0 && welding) + switched_on(user) + update_icon() + //mob icon update + if(ismob(loc)) + var/mob/M = loc + M.update_inv_r_hand(0) + M.update_inv_l_hand(0) + return 0 + return 1 + +//Switches the welder on +/obj/item/weldingtool/proc/switched_on(mob/user) + if(!status) + to_chat(user, "[src] can't be turned on while unsecured!") + return + welding = !welding + if(welding) + if(get_fuel() >= 1) + to_chat(user, "You switch [src] on.") + playsound(loc, acti_sound, 50, 1) + force = 15 + damtype = "fire" + hitsound = 'sound/items/welder.ogg' + update_icon() + START_PROCESSING(SSobj, src) + else + to_chat(user, "You need more fuel!") + switched_off(user) + else + if(user) + to_chat(user, "You switch [src] off.") + playsound(loc, deac_sound, 50, 1) + switched_off(user) + +//Switches the welder off +/obj/item/weldingtool/proc/switched_off(mob/user) + welding = 0 + set_light(0) + + force = 3 + damtype = "brute" + hitsound = "swing_hit" + update_icon() + +/obj/item/weldingtool/proc/flamethrower_screwdriver(obj/item/I, mob/user) + if(welding) + to_chat(user, "Turn it off first!") + return + status = !status + if(status) + to_chat(user, "You resecure [src].") + else + to_chat(user, "[src] can now be attached and modified.") + add_fingerprint(user) + +/obj/item/weldingtool/proc/flamethrower_rods(obj/item/I, mob/user) + if(!status) + var/obj/item/stack/rods/R = I + if(R.use(1)) + var/obj/item/flamethrower/F = new /obj/item/flamethrower(user.loc) + if(!remove_item_from_storage(F)) + user.unEquip(src) + loc = F + F.weldtool = src + add_fingerprint(user) + to_chat(user, "You add a rod to a welder, starting to build a flamethrower.") + user.put_in_hands(F) + else + to_chat(user, "You need one rod to start building a flamethrower!") + +/obj/item/weldingtool/largetank + name = "Industrial Welding Tool" + desc = "A slightly larger welder with a larger tank." + icon_state = "indwelder" + max_fuel = 40 + materials = list(MAT_METAL=70, MAT_GLASS=60) + origin_tech = "engineering=2;plasmatech=2" + +/obj/item/weldingtool/largetank/cyborg + name = "integrated welding tool" + desc = "An advanced welder designed to be used in robotic systems." + toolspeed = 0.5 + +/obj/item/weldingtool/largetank/flamethrower_screwdriver() + return + +/obj/item/weldingtool/mini + name = "emergency welding tool" + desc = "A miniature welder used during emergencies." + icon_state = "miniwelder" + max_fuel = 10 + w_class = WEIGHT_CLASS_TINY + materials = list(MAT_METAL=30, MAT_GLASS=10) + change_icons = 0 + +/obj/item/weldingtool/mini/flamethrower_screwdriver() + return + +/obj/item/weldingtool/abductor + name = "alien welding tool" + desc = "An alien welding tool. Whatever fuel it uses, it never runs out." + icon = 'icons/obj/abductor.dmi' + icon_state = "welder" + toolspeed = 0.1 + light_intensity = 0 + change_icons = 0 + origin_tech = "plasmatech=5;engineering=5;abductor=3" + can_off_process = 1 + +/obj/item/weldingtool/abductor/process() + if(get_fuel() <= max_fuel) + reagents.add_reagent("fuel", 1) + ..() + +/obj/item/weldingtool/hugetank + name = "Upgraded Welding Tool" + desc = "An upgraded welder based off the industrial welder." + icon_state = "upindwelder" + item_state = "upindwelder" + max_fuel = 80 + materials = list(MAT_METAL=70, MAT_GLASS=120) + origin_tech = "engineering=3;plasmatech=2" + +/obj/item/weldingtool/experimental + name = "Experimental Welding Tool" + desc = "An experimental welder capable of self-fuel generation and less harmful to the eyes." + icon_state = "exwelder" + item_state = "exwelder" + max_fuel = 40 + materials = list(MAT_METAL=70, MAT_GLASS=120) + origin_tech = "materials=4;engineering=4;bluespace=3;plasmatech=4" + change_icons = 0 + can_off_process = 1 + light_intensity = 1 + toolspeed = 0.5 + var/last_gen = 0 + +/obj/item/weldingtool/experimental/brass + name = "brass welding tool" + desc = "A brass welder that seems to constantly refuel itself. It is faintly warm to the touch." + icon_state = "brasswelder" + item_state = "brasswelder" + resistance_flags = FIRE_PROOF | ACID_PROOF + +obj/item/weldingtool/experimental/process() + ..() + if(get_fuel() < max_fuel && nextrefueltick < world.time) + nextrefueltick = world.time + 10 + reagents.add_reagent("fuel", 1) + +//Crowbar +/obj/item/crowbar + name = "pocket crowbar" + desc = "A small crowbar. This handy tool is useful for lots of things, such as prying floor tiles or opening unpowered doors." + icon = 'icons/obj/tools.dmi' + icon_state = "crowbar" + item_state = "crowbar" + usesound = 'sound/items/crowbar.ogg' + flags = CONDUCT + slot_flags = SLOT_BELT + force = 5 + throwforce = 7 + item_state = "crowbar" + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=50) + origin_tech = "engineering=1;combat=1" + attack_verb = list("attacked", "bashed", "battered", "bludgeoned", "whacked") + toolspeed = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + +/obj/item/crowbar/red + icon_state = "crowbar_red" + item_state = "crowbar_red" + force = 8 + +/obj/item/crowbar/brass + name = "brass crowbar" + desc = "A brass crowbar. It feels faintly warm to the touch." + icon_state = "crowbar_brass" + item_state = "crowbar_brass" + toolspeed = 0.5 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/crowbar/abductor + name = "alien crowbar" + desc = "A hard-light crowbar. It appears to pry by itself, without any effort required." + icon = 'icons/obj/abductor.dmi' + usesound = 'sound/weapons/sonic_jackhammer.ogg' + icon_state = "crowbar" + toolspeed = 0.1 + origin_tech = "combat=4;engineering=4;abductor=3" + +/obj/item/crowbar/large + name = "crowbar" + desc = "It's a big crowbar. It doesn't fit in your pockets, because its too big." + force = 12 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 3 + throw_range = 3 + materials = list(MAT_METAL=70) + icon_state = "crowbar_large" + item_state = "crowbar_large" + toolspeed = 0.5 + +/obj/item/crowbar/cyborg + name = "hydraulic crowbar" + desc = "A hydraulic prying tool, compact but powerful. Designed to replace crowbar in construction cyborgs." + usesound = 'sound/items/jaws_pry.ogg' + force = 10 + toolspeed = 0.5 + +/obj/item/crowbar/power + name = "jaws of life" + desc = "A set of jaws of life, the magic of science has managed to fit it down into a device small enough to fit in a tool belt. It's fitted with a prying head." + icon_state = "jaws_pry" + item_state = "jawsoflife" + materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) + origin_tech = "materials=2;engineering=2" + usesound = 'sound/items/jaws_pry.ogg' + force = 15 + toolspeed = 0.25 + var/airlock_open_time = 100 // Time required to open powered airlocks + +/obj/item/crowbar/power/suicide_act(mob/user) + user.visible_message("[user] is putting [user.p_their()] head in [src]. It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, 'sound/items/jaws_pry.ogg', 50, 1, -1) + return BRUTELOSS + +/obj/item/crowbar/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1) + var/obj/item/wirecutters/power/cutjaws = new /obj/item/wirecutters/power + to_chat(user, "You attach the cutting jaws to [src].") + qdel(src) + user.put_in_active_hand(cutjaws) + +// Conversion kit +/obj/item/conversion_kit + name = "\improper Revolver Conversion Kit" + desc = "A professional conversion kit used to convert any knock off revolver into the real deal capable of shooting lethal .357 rounds without the possibility of catastrophic failure." + icon_state = "kit" + flags = CONDUCT + w_class = WEIGHT_CLASS_SMALL + origin_tech = "combat=2" + var/open = 0 + +/obj/item/conversion_kit/New() + ..() + update_icon() + +/obj/item/conversion_kit/update_icon() + icon_state = "[initial(icon_state)]_[open]" + +/obj/item/conversion_kit/attack_self(mob/user) + open = !open + to_chat(user, "You [open ? "open" : "close"] the conversion kit.") + update_icon() diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm index fba969f05b7..57c9a5301f6 100644 --- a/code/game/objects/items/weapons/twohanded.dm +++ b/code/game/objects/items/weapons/twohanded.dm @@ -1,878 +1,878 @@ -/* Two-handed Weapons - * Contains: - * Twohanded - * Fireaxe - * Double-Bladed Energy Swords - * Spears - * Kidan spear - * Chainsaw - * Singularity hammer - * Mjolnnir - * Knighthammer - */ - -/*################################################################## -##################### TWO HANDED WEAPONS BE HERE~ -Agouri :3 ######## -####################################################################*/ - -//Rewrote TwoHanded weapons stuff and put it all here. Just copypasta fireaxe to make new ones ~Carn -//This rewrite means we don't have two variables for EVERY item which are used only by a few weapons. -//It also tidies stuff up elsewhere. - -/* - * Twohanded - */ -/obj/item/twohanded - var/wielded = FALSE - var/force_unwielded = 0 - var/force_wielded = 0 - var/wieldsound = null - var/unwieldsound = null - var/sharp_when_wielded = FALSE - -/obj/item/twohanded/proc/unwield(mob/living/carbon/user) - if(!wielded || !user) - return - wielded = FALSE - force = force_unwielded - if(sharp_when_wielded) - sharp = FALSE - var/sf = findtext(name," (Wielded)") - if(sf) - name = copytext(name, 1, sf) - else //something wrong - name = "[initial(name)]" - update_icon() - if(user) - user.update_inv_r_hand() - user.update_inv_l_hand() - if(isrobot(user)) - to_chat(user, "You free up your module.") - else - to_chat(user, "You are now carrying [name] with one hand.") - if(unwieldsound) - playsound(loc, unwieldsound, 50, 1) - var/obj/item/twohanded/offhand/O = user.get_inactive_hand() - if(O && istype(O)) - O.unwield() - -/obj/item/twohanded/proc/wield(mob/living/carbon/user) - if(wielded) - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.species.is_small) - to_chat(user, "It's too heavy for you to wield fully.") - return - if(user.get_inactive_hand()) - to_chat(user, "You need your other hand to be empty!") - return - wielded = TRUE - force = force_wielded - if(sharp_when_wielded) - sharp = TRUE - name = "[name] (Wielded)" - update_icon() - if(user) - user.update_inv_r_hand() - user.update_inv_l_hand() - if(isrobot(user)) - to_chat(user, "You dedicate your module to [name].") - else - to_chat(user, "You grab the [name] with both hands.") - if(wieldsound) - playsound(loc, wieldsound, 50, 1) - var/obj/item/twohanded/offhand/O = new(user) ////Let's reserve his other hand~ - O.name = "[name] - offhand" - O.desc = "Your second grip on the [name]" - user.put_in_inactive_hand(O) - -/obj/item/twohanded/dropped(mob/user) - ..() - //handles unwielding a twohanded weapon when dropped as well as clearing up the offhand - if(user) - var/obj/item/twohanded/O = user.get_inactive_hand() - if(istype(O)) - O.unwield(user) - return unwield(user) - -/obj/item/twohanded/attack_self(mob/user) - ..() - if(wielded) //Trying to unwield it - unwield(user) - else //Trying to wield it - wield(user) - - -/obj/item/twohanded/equip_to_best_slot(mob/M) - if(..()) - unwield(M) - return - -///////////OFFHAND/////////////// -/obj/item/twohanded/offhand - w_class = WEIGHT_CLASS_HUGE - icon_state = "offhand" - name = "offhand" - flags = ABSTRACT - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/item/twohanded/offhand/unwield() - if(!QDELETED(src)) - qdel(src) - -/obj/item/twohanded/offhand/wield() - if(!QDELETED(src)) - qdel(src) - -///////////Two hand required objects/////////////// -//This is for objects that require two hands to even pick up -/obj/item/twohanded/required - w_class = WEIGHT_CLASS_HUGE - -/obj/item/twohanded/required/attack_self() - return - -/obj/item/twohanded/required/mob_can_equip(mob/M, slot) - if(wielded && !slot_flags) - to_chat(M, "[src] is too cumbersome to carry with anything but your hands!") - return FALSE - return ..() - -/obj/item/twohanded/required/attack_hand(mob/user)//Can't even pick it up without both hands empty - var/obj/item/twohanded/required/H = user.get_inactive_hand() - if(get_dist(src, user) > 1) - return FALSE - if(H != null) - to_chat(user, "[src] is too cumbersome to carry in one hand!") - return - if(loc != user) - wield(user) - ..() - -/obj/item/twohanded/required/on_give(mob/living/carbon/giver, mob/living/carbon/receiver) - var/obj/item/twohanded/required/H = receiver.get_inactive_hand() - if(H != null) //Check if he can wield it - receiver.drop_item() //Can't wear it so drop it - to_chat(receiver, "[src] is too cumbersome to carry in one hand!") - return - equipped(receiver,receiver.hand ? slot_l_hand : slot_r_hand) - -/obj/item/twohanded/required/equipped(mob/user, slot) - ..() - if(slot == slot_l_hand || slot == slot_r_hand) - wield(user) - else - unwield(user) - -/* - * Fireaxe - */ -/obj/item/twohanded/fireaxe // DEM AXES MAN, marker -Agouri - icon_state = "fireaxe0" - name = "fire axe" - desc = "Truly, the weapon of a madman. Who would think to fight fire with an axe?" - force = 5 - throwforce = 15 - sharp = TRUE - w_class = WEIGHT_CLASS_BULKY - slot_flags = SLOT_BACK - force_unwielded = 5 - force_wielded = 24 - attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - usesound = 'sound/items/crowbar.ogg' - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) - resistance_flags = FIRE_PROOF - -/obj/item/twohanded/fireaxe/update_icon() //Currently only here to fuck with the on-mob icons. - icon_state = "fireaxe[wielded]" - ..() - -/obj/item/twohanded/fireaxe/afterattack(atom/A, mob/user, proximity) - if(!proximity) - return - if(wielded) //destroys windows and grilles in one hit - if(istype(A, /obj/structure/window) || istype(A, /obj/structure/grille)) - var/obj/structure/W = A - W.obj_destruction("fireaxe") - - -/obj/item/twohanded/fireaxe/boneaxe // Blatant imitation of the fireaxe, but made out of bone. - icon_state = "bone_axe0" - name = "bone axe" - desc = "A large, vicious axe crafted out of several sharpened bone plates and crudely tied together. Made of monsters, by killing monsters, for killing monsters." - force_wielded = 23 - -/obj/item/twohanded/fireaxe/boneaxe/update_icon() - icon_state = "bone_axe[wielded]" - -/* - * Double-Bladed Energy Swords - Cheridan - */ -/obj/item/twohanded/dualsaber - var/hacked = FALSE - var/blade_color - icon_state = "dualsaber0" - name = "double-bladed energy sword" - desc = "Handle with care." - force = 3 - throwforce = 5 - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - var/w_class_on = WEIGHT_CLASS_BULKY - force_unwielded = 3 - force_wielded = 34 - wieldsound = 'sound/weapons/saberon.ogg' - unwieldsound = 'sound/weapons/saberoff.ogg' - armour_penetration = 35 - origin_tech = "magnets=4;syndicate=5" - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 75 - sharp_when_wielded = TRUE // only sharp when wielded - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - light_power = 2 - var/brightness_on = 2 - var/colormap = list(red=LIGHT_COLOR_RED, blue=LIGHT_COLOR_LIGHTBLUE, green=LIGHT_COLOR_GREEN, purple=LIGHT_COLOR_PURPLE, rainbow=LIGHT_COLOR_WHITE) - -/obj/item/twohanded/dualsaber/New() - ..() - if(!blade_color) - blade_color = pick("red", "blue", "green", "purple") - -/obj/item/twohanded/dualsaber/update_icon() - if(wielded) - icon_state = "dualsaber[blade_color][wielded]" - set_light(brightness_on, l_color=colormap[blade_color]) - else - icon_state = "dualsaber0" - set_light(0) - ..() - -/obj/item/twohanded/dualsaber/attack(mob/target, mob/living/user) - if(HULK in user.mutations) - to_chat(user, "You grip the blade too hard and accidentally close it!") - unwield() - return - ..() - if((CLUMSY in user.mutations) && (wielded) && prob(40)) - to_chat(user, "You twirl around a bit before losing your balance and impaling yourself on the [src].") - user.take_organ_damage(20, 25) - return - if((wielded) && prob(50)) - INVOKE_ASYNC(src, .proc/jedi_spin, user) - -/obj/item/twohanded/dualsaber/proc/jedi_spin(mob/living/user) - for(var/i in list(NORTH, SOUTH, EAST, WEST, EAST, SOUTH, NORTH, SOUTH, EAST, WEST, EAST, SOUTH)) - user.setDir(i) - if(i == WEST) - user.SpinAnimation(7, 1) - sleep(1) - -/obj/item/twohanded/dualsaber/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(wielded) - return ..() - return FALSE - -/obj/item/twohanded/dualsaber/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) //In case thats just so happens that it is still activated on the groud, prevents hulk from picking it up - if(wielded) - to_chat(user, "You can't pick up such a dangerous item with your meaty hands without losing fingers, better not to!") - return TRUE - -/obj/item/twohanded/dualsaber/green - blade_color = "green" - -/obj/item/twohanded/dualsaber/red - blade_color = "red" - -/obj/item/twohanded/dualsaber/purple - blade_color = "purple" - -/obj/item/twohanded/dualsaber/blue - blade_color = "blue" - -/obj/item/twohanded/dualsaber/unwield() - ..() - hitsound = "swing_hit" - w_class = initial(w_class) - -/obj/item/twohanded/dualsaber/IsReflect() - if(wielded) - return TRUE - -/obj/item/twohanded/dualsaber/wield(mob/living/carbon/M) //Specific wield () hulk checks due to reflection chance for balance issues and switches hitsounds. - if(HULK in M.mutations) - to_chat(M, "You lack the grace to wield this!") - return - ..() - hitsound = 'sound/weapons/blade1.ogg' - w_class = w_class_on - -/obj/item/twohanded/dualsaber/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(!hacked) - hacked = TRUE - to_chat(user, "2XRNBW_ENGAGE") - blade_color = "rainbow" - update_icon() - else - to_chat(user, "It's starting to look like a triple rainbow - no, nevermind.") - -//spears -/obj/item/twohanded/spear - icon_state = "spearglass0" - name = "spear" - desc = "A haphazardly-constructed yet still deadly weapon of ancient design." - force = 10 - w_class = WEIGHT_CLASS_BULKY - slot_flags = SLOT_BACK - force_unwielded = 10 - force_wielded = 18 - throwforce = 20 - throw_speed = 4 - armour_penetration = 10 - materials = list(MAT_METAL = 1150, MAT_GLASS = 2075) - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "poked", "jabbed", "torn", "gored") - sharp = TRUE - no_spin_thrown = TRUE - var/obj/item/grenade/explosive = null - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) - var/icon_prefix = "spearglass" - -/obj/item/twohanded/spear/update_icon() - icon_state = "[icon_prefix][wielded]" - -/obj/item/twohanded/spear/CheckParts(list/parts_list) - var/obj/item/shard/tip = locate() in parts_list - if(istype(tip, /obj/item/shard/plasma)) - force_wielded = 19 - force_unwielded = 11 - throwforce = 21 - icon_prefix = "spearplasma" - update_icon() - qdel(tip) - ..() - - -/obj/item/twohanded/spear/afterattack(atom/movable/AM, mob/user, proximity) - if(!proximity) - return - if(isturf(AM)) //So you can actually melee with it - return - if(explosive && wielded) - explosive.forceMove(AM) - explosive.prime() - qdel(src) - -/obj/item/twohanded/spear/throw_impact(atom/target) - . = ..() - if(explosive) - explosive.prime() - qdel(src) - -/obj/item/twohanded/spear/bonespear //Blatant imitation of spear, but made out of bone. Not valid for explosive modification. - icon_state = "bone_spear0" - name = "bone spear" - desc = "A haphazardly-constructed yet still deadly weapon. The pinnacle of modern technology." - force = 11 - force_unwielded = 11 - force_wielded = 20 //I have no idea how to balance - throwforce = 22 - armour_penetration = 15 //Enhanced armor piercing - icon_prefix = "bone_spear" - -//GREY TIDE -/obj/item/twohanded/spear/grey_tide - icon_state = "spearglass0" - name = "\improper Grey Tide" - desc = "Recovered from the aftermath of a revolt aboard Defense Outpost Theta Aegis, in which a seemingly endless tide of Assistants caused heavy casualities among Nanotrasen military forces." - force_unwielded = 15 - force_wielded = 25 - throwforce = 20 - throw_speed = 4 - attack_verb = list("gored") - -/obj/item/twohanded/spear/grey_tide/afterattack(atom/movable/AM, mob/living/user, proximity) - ..() - if(!proximity) - return - user.faction |= "greytide(\ref[user])" - if(isliving(AM)) - var/mob/living/L = AM - if(istype (L, /mob/living/simple_animal/hostile/illusion)) - return - if(!L.stat && prob(50)) - var/mob/living/simple_animal/hostile/illusion/M = new(user.loc) - M.faction = user.faction.Copy() - M.attack_sound = hitsound - M.Copy_Parent(user, 100, user.health/2.5, 12, 30) - M.GiveTarget(L) - -//Putting heads on spears -/obj/item/twohanded/spear/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/organ/external/head)) - if(user.unEquip(src) && user.drop_item()) - to_chat(user, "You stick [I] onto the spear and stand it upright on the ground.") - var/obj/structure/headspear/HS = new /obj/structure/headspear(get_turf(src)) - var/matrix/M = matrix() - I.transform = M - var/image/IM = image(I.icon, I.icon_state) - IM.overlays = I.overlays.Copy() - HS.overlays += IM - I.forceMove(HS) - HS.mounted_head = I - forceMove(HS) - HS.contained_spear = src - else - return ..() - -/obj/structure/headspear - name = "head on a spear" - desc = "How barbaric." - icon_state = "headspear" - density = FALSE - anchored = TRUE - var/obj/item/organ/external/head/mounted_head = null - var/obj/item/twohanded/spear/contained_spear = null - -/obj/structure/headspear/Destroy() - QDEL_NULL(mounted_head) - QDEL_NULL(contained_spear) - return ..() - -/obj/structure/headspear/attack_hand(mob/living/user) - user.visible_message("[user] kicks over [src]!", "You kick down [src]!") - playsound(src, 'sound/weapons/genhit.ogg', 50, 1) - var/turf/T = get_turf(src) - if(contained_spear) - contained_spear.forceMove(T) - contained_spear = null - if(mounted_head) - mounted_head.forceMove(T) - mounted_head = null - qdel(src) - -/obj/item/twohanded/spear/kidan - icon_state = "kidanspear0" - name = "Kidan spear" - desc = "A spear brought over from the Kidan homeworld." - -// DIY CHAINSAW -/obj/item/twohanded/required/chainsaw - name = "chainsaw" - desc = "A versatile power tool. Useful for limbing trees and delimbing humans." - icon_state = "gchainsaw_off" - flags = CONDUCT - force = 13 - var/force_on = 24 - w_class = WEIGHT_CLASS_HUGE - throwforce = 13 - throw_speed = 2 - throw_range = 4 - materials = list(MAT_METAL = 13000) - origin_tech = "materials=3;engineering=4;combat=2" - attack_verb = list("sawed", "cut", "hacked", "carved", "cleaved", "butchered", "felled", "timbered") - hitsound = "swing_hit" - sharp = TRUE - actions_types = list(/datum/action/item_action/startchainsaw) - var/on = FALSE - -/obj/item/twohanded/required/chainsaw/attack_self(mob/user) - on = !on - to_chat(user, "As you pull the starting cord dangling from [src], [on ? "it begins to whirr." : "the chain stops moving."]") - if(on) - playsound(loc, 'sound/weapons/chainsawstart.ogg', 50, 1) - force = on ? force_on : initial(force) - throwforce = on ? force_on : initial(throwforce) - icon_state = "gchainsaw_[on ? "on" : "off"]" - - if(hitsound == "swing_hit") - hitsound = 'sound/weapons/chainsaw.ogg' - else - hitsound = "swing_hit" - - if(src == user.get_active_hand()) //update inhands - user.update_inv_l_hand() - user.update_inv_r_hand() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/twohanded/required/chainsaw/attack_hand(mob/user) - . = ..() - force = on ? force_on : initial(force) - throwforce = on ? force_on : initial(throwforce) - -/obj/item/twohanded/required/chainsaw/on_give(mob/living/carbon/giver, mob/living/carbon/receiver) - . = ..() - force = on ? force_on : initial(force) - throwforce = on ? force_on : initial(throwforce) - -/obj/item/twohanded/required/chainsaw/doomslayer - name = "OOOH BABY" - desc = "VRRRRRRR!!!" - armour_penetration = 100 - force_on = 30 - -/obj/item/twohanded/required/chainsaw/doomslayer/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) - owner.visible_message("Ranged attacks just make [owner] angrier!") - playsound(src, pick('sound/weapons/bulletflyby.ogg','sound/weapons/bulletflyby2.ogg','sound/weapons/bulletflyby3.ogg'), 75, 1) - return TRUE - return FALSE - - -///CHAINSAW/// -/obj/item/twohanded/chainsaw - icon_state = "chainsaw0" - name = "Chainsaw" - desc = "Perfect for felling trees or fellow spacemen." - force = 15 - throwforce = 15 - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_BULKY // can't fit in backpacks - force_unwielded = 15 //still pretty robust - force_wielded = 40 //you'll gouge their eye out! Or a limb...maybe even their entire body! - wieldsound = 'sound/weapons/chainsawstart.ogg' - hitsound = null - armour_penetration = 35 - origin_tech = "materials=6;syndicate=4" - attack_verb = list("sawed", "cut", "hacked", "carved", "cleaved", "butchered", "felled", "timbered") - sharp = TRUE - -/obj/item/twohanded/chainsaw/update_icon() - if(wielded) - icon_state = "chainsaw[wielded]" - else - icon_state = "chainsaw0" - ..() - -/obj/item/twohanded/chainsaw/attack(mob/target, mob/living/user) - if(wielded) - playsound(loc, 'sound/weapons/chainsaw.ogg', 100, 1, -1) //incredibly loud; you ain't goin' for stealth with this thing. Credit to Lonemonk of Freesound for this sound. - if(isrobot(target)) - ..() - return - if(!isliving(target)) - return - else - target.Weaken(4) - ..() - return - else - playsound(loc, "swing_hit", 50, 1, -1) - return ..() - -/obj/item/twohanded/chainsaw/wield() //you can't disarm an active chainsaw, you crazy person. - ..() - flags |= NODROP - -/obj/item/twohanded/chainsaw/unwield() - ..() - flags &= ~NODROP - -// SINGULOHAMMER -/obj/item/twohanded/singularityhammer - name = "singularity hammer" - desc = "The pinnacle of close combat technology, the hammer harnesses the power of a miniaturized singularity to deal crushing blows." - icon_state = "mjollnir0" - flags = CONDUCT - slot_flags = SLOT_BACK - force = 5 - force_unwielded = 5 - force_wielded = 20 - throwforce = 15 - throw_range = 1 - w_class = WEIGHT_CLASS_HUGE - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - var/charged = 5 - origin_tech = "combat=4;bluespace=4;plasmatech=7" - -/obj/item/twohanded/singularityhammer/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/twohanded/singularityhammer/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/twohanded/singularityhammer/process() - if(charged < 5) - charged++ - -/obj/item/twohanded/singularityhammer/update_icon() //Currently only here to fuck with the on-mob icons. - icon_state = "mjollnir[wielded]" - ..() - -/obj/item/twohanded/singularityhammer/proc/vortex(turf/pull, mob/wielder) - for(var/atom/movable/X in orange(5, pull)) - if(X == wielder) - continue - if((X) && (!X.anchored) && (!ishuman(X))) - step_towards(X, pull) - step_towards(X, pull) - step_towards(X, pull) - else if(ishuman(X)) - var/mob/living/carbon/human/H = X - if(istype(H.shoes, /obj/item/clothing/shoes/magboots)) - var/obj/item/clothing/shoes/magboots/M = H.shoes - if(M.magpulse) - continue - H.apply_effect(1, WEAKEN, 0) - step_towards(H, pull) - step_towards(H, pull) - step_towards(H, pull) - -/obj/item/twohanded/singularityhammer/afterattack(atom/A, mob/user, proximity) - if(!proximity) - return - if(wielded) - if(charged == 5) - charged = 0 - if(isliving(A)) - var/mob/living/Z = A - Z.take_organ_damage(20, 0) - playsound(user, 'sound/weapons/marauder.ogg', 50, 1) - var/turf/target = get_turf(A) - vortex(target, user) - -/obj/item/twohanded/mjollnir - name = "Mjolnir" - desc = "A weapon worthy of a god, able to strike with the force of a lightning bolt. It crackles with barely contained energy." - icon_state = "mjollnir0" - flags = CONDUCT - slot_flags = SLOT_BACK - force = 5 - force_unwielded = 5 - force_wielded = 25 - throwforce = 30 - throw_range = 7 - w_class = WEIGHT_CLASS_HUGE - //var/charged = 5 - origin_tech = "combat=4;powerstorage=7" - -/obj/item/twohanded/mjollnir/proc/shock(mob/living/target) - do_sparks(5, 1, target.loc) - target.visible_message("[target.name] was shocked by the [name]!", \ - "You feel a powerful shock course through your body sending you flying!", \ - "You hear a heavy electrical crack!") - var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src))) - target.throw_at(throw_target, 200, 4) - -/obj/item/twohanded/mjollnir/attack(mob/M, mob/user) - ..() - if(wielded) - //if(charged == 5) - //charged = 0 - playsound(loc, "sparks", 50, 1) - if(isliving(M)) - M.Stun(3) - shock(M) - -/obj/item/twohanded/mjollnir/throw_impact(atom/target) - . = ..() - if(isliving(target)) - var/mob/living/L = target - L.Stun(3) - shock(L) - -/obj/item/twohanded/mjollnir/update_icon() //Currently only here to fuck with the on-mob icons. - icon_state = "mjollnir[wielded]" - ..() - -/obj/item/twohanded/knighthammer - name = "singuloth knight's hammer" - desc = "A hammer made of sturdy metal with a golden skull adorned with wings on either side of the head.
        This weapon causes devastating damage to those it hits due to a power field sustained by a mini-singularity inside of the hammer." - icon_state = "knighthammer0" - flags = CONDUCT - slot_flags = SLOT_BACK - force = 5 - force_unwielded = 5 - force_wielded = 30 - throwforce = 15 - throw_range = 1 - w_class = WEIGHT_CLASS_HUGE - var/charged = 5 - origin_tech = "combat=5;bluespace=4" - -/obj/item/twohanded/knighthammer/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/twohanded/knighthammer/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/twohanded/knighthammer/process() - if(charged < 5) - charged++ - -/obj/item/twohanded/knighthammer/update_icon() //Currently only here to fuck with the on-mob icons. - icon_state = "knighthammer[wielded]" - ..() - -/obj/item/twohanded/knighthammer/afterattack(atom/A, mob/user, proximity) - if(!proximity) - return - if(charged == 5) - charged = 0 - if(isliving(A)) - var/mob/living/Z = A - if(Z.health >= 1) - Z.visible_message("[Z.name] was sent flying by a blow from the [name]!", \ - "You feel a powerful blow connect with your body and send you flying!", \ - "You hear something heavy impact flesh!.") - var/atom/throw_target = get_edge_target_turf(Z, get_dir(src, get_step_away(Z, src))) - Z.throw_at(throw_target, 200, 4) - playsound(user, 'sound/weapons/marauder.ogg', 50, 1) - else if(wielded && Z.health < 1) - Z.visible_message("[Z.name] was blown to pieces by the power of [name]!", \ - "You feel a powerful blow rip you apart!", \ - "You hear a heavy impact and the sound of ripping flesh!.") - Z.gib() - playsound(user, 'sound/weapons/marauder.ogg', 50, 1) - if(wielded) - if(istype(A, /turf/simulated/wall)) - var/turf/simulated/wall/Z = A - Z.ex_act(2) - charged = 3 - playsound(user, 'sound/weapons/marauder.ogg', 50, 1) - else if(istype(A, /obj/structure) || istype(A, /obj/mecha)) - var/obj/Z = A - Z.ex_act(2) - charged = 3 - playsound(user, 'sound/weapons/marauder.ogg', 50, 1) - -// Energized Fire axe -/obj/item/twohanded/energizedfireaxe - name = "energized fire axe" - desc = "Someone with a love for fire axes decided to turn one into a single-charge energy weapon. Seems excessive." - icon_state = "fireaxe0" - force = 5 - throwforce = 15 - sharp = TRUE - w_class = WEIGHT_CLASS_HUGE - armour_penetration = 20 - slot_flags = SLOT_BACK - force_unwielded = 5 - force_wielded = 30 - attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) - var/charged = 1 - -/obj/item/twohanded/energizedfireaxe/update_icon() - if(wielded) - icon_state = "fireaxe2" - else - icon_state = "fireaxe0" - ..() - -/obj/item/twohanded/energizedfireaxe/afterattack(atom/A, mob/user, proximity) - if(!proximity) - return - if(wielded) - if(isliving(A)) - var/mob/living/Z = A - if(charged) - charged-- - Z.take_organ_damage(0, 30) - user.visible_message("[user] slams the charged axe into [Z.name] with all [user.p_their()] might!") - playsound(loc, 'sound/magic/lightningbolt.ogg', 5, 1) - do_sparks(1, 1, src) - - if(A && wielded && (istype(A, /obj/structure/window) || istype(A, /obj/structure/grille))) - if(istype(A, /obj/structure/window)) - var/obj/structure/window/W = A - W.deconstruct(FALSE) - if(prob(4)) - charged++ - user.visible_message("The axe starts to emit an electric buzz!") - else - qdel(A) - if(prob(4)) - charged++ - user.visible_message("The axe starts to emit an electric buzz!") - -/obj/item/twohanded/pitchfork - icon_state = "pitchfork0" - name = "pitchfork" - desc = "A simple tool used for moving hay." - force = 7 - throwforce = 15 - w_class = WEIGHT_CLASS_BULKY - force_unwielded = 7 - force_wielded = 15 - attack_verb = list("attacked", "impaled", "pierced") - hitsound = 'sound/weapons/bladeslice.ogg' - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) - resistance_flags = FIRE_PROOF - -/obj/item/twohanded/pitchfork/demonic - name = "demonic pitchfork" - desc = "A red pitchfork, it looks like the work of the devil." - force = 19 - throwforce = 24 - force_unwielded = 19 - force_wielded = 25 - -/obj/item/twohanded/pitchfork/demonic/greater - force = 24 - throwforce = 50 - force_unwielded = 24 - force_wielded = 34 - -/obj/item/twohanded/pitchfork/demonic/ascended - force = 100 - throwforce = 100 - force_unwielded = 100 - force_wielded = 500000 // Kills you DEAD. - -/obj/item/twohanded/pitchfork/update_icon() - icon_state = "pitchfork[wielded]" - -/obj/item/twohanded/pitchfork/suicide_act(mob/user) - user.visible_message("[user] impales \himself in \his abdomen with [src]! It looks like \he's trying to commit suicide...") - return BRUTELOSS - -/obj/item/twohanded/pitchfork/demonic/pickup(mob/user) - . = ..() - if(istype(user, /mob/living)) - var/mob/living/U = user - if(U.mind && !U.mind.devilinfo && (U.mind.soulOwner == U.mind)) //Burn hands unless they are a devil or have sold their soul - U.visible_message("As [U] picks [src] up, [U]'s arms briefly catch fire.", \ - "\"As you pick up the [src] your arms ignite, reminding you of all your past sins.\"") - if(ishuman(U)) - var/mob/living/carbon/human/H = U - H.apply_damage(rand(force/2, force), BURN, pick("l_arm", "r_arm")) - else - U.adjustFireLoss(rand(force/2,force)) - -/obj/item/twohanded/pitchfork/demonic/attack(mob/target, mob/living/carbon/human/user) - if(user.mind && !user.mind.devilinfo && (user.mind.soulOwner != user.mind)) - to_chat(user, "The [src] burns in your hands.") - user.apply_damage(rand(force/2, force), BURN, pick("l_arm", "r_arm")) - ..() - -// It's no fun being the lord of all hell if you can't get out of a simple room -/obj/item/twohanded/pitchfork/demonic/ascended/afterattack(atom/target, mob/user, proximity) - if(!proximity || !wielded) - return - if(istype(target, /turf/simulated/wall)) - var/turf/simulated/wall/W = target - user.visible_message("[user] blasts \the [target] with \the [src]!") - playsound(target, 'sound/magic/Disintegrate.ogg', 100, 1) - W.devastate_wall(TRUE) - return 1 - ..() +/* Two-handed Weapons + * Contains: + * Twohanded + * Fireaxe + * Double-Bladed Energy Swords + * Spears + * Kidan spear + * Chainsaw + * Singularity hammer + * Mjolnnir + * Knighthammer + */ + +/*################################################################## +##################### TWO HANDED WEAPONS BE HERE~ -Agouri :3 ######## +####################################################################*/ + +//Rewrote TwoHanded weapons stuff and put it all here. Just copypasta fireaxe to make new ones ~Carn +//This rewrite means we don't have two variables for EVERY item which are used only by a few weapons. +//It also tidies stuff up elsewhere. + +/* + * Twohanded + */ +/obj/item/twohanded + var/wielded = FALSE + var/force_unwielded = 0 + var/force_wielded = 0 + var/wieldsound = null + var/unwieldsound = null + var/sharp_when_wielded = FALSE + +/obj/item/twohanded/proc/unwield(mob/living/carbon/user) + if(!wielded || !user) + return + wielded = FALSE + force = force_unwielded + if(sharp_when_wielded) + sharp = FALSE + var/sf = findtext(name," (Wielded)") + if(sf) + name = copytext(name, 1, sf) + else //something wrong + name = "[initial(name)]" + update_icon() + if(user) + user.update_inv_r_hand() + user.update_inv_l_hand() + if(isrobot(user)) + to_chat(user, "You free up your module.") + else + to_chat(user, "You are now carrying [name] with one hand.") + if(unwieldsound) + playsound(loc, unwieldsound, 50, 1) + var/obj/item/twohanded/offhand/O = user.get_inactive_hand() + if(O && istype(O)) + O.unwield() + +/obj/item/twohanded/proc/wield(mob/living/carbon/user) + if(wielded) + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna.species.is_small) + to_chat(user, "It's too heavy for you to wield fully.") + return + if(user.get_inactive_hand()) + to_chat(user, "You need your other hand to be empty!") + return + wielded = TRUE + force = force_wielded + if(sharp_when_wielded) + sharp = TRUE + name = "[name] (Wielded)" + update_icon() + if(user) + user.update_inv_r_hand() + user.update_inv_l_hand() + if(isrobot(user)) + to_chat(user, "You dedicate your module to [name].") + else + to_chat(user, "You grab the [name] with both hands.") + if(wieldsound) + playsound(loc, wieldsound, 50, 1) + var/obj/item/twohanded/offhand/O = new(user) ////Let's reserve his other hand~ + O.name = "[name] - offhand" + O.desc = "Your second grip on the [name]" + user.put_in_inactive_hand(O) + +/obj/item/twohanded/dropped(mob/user) + ..() + //handles unwielding a twohanded weapon when dropped as well as clearing up the offhand + if(user) + var/obj/item/twohanded/O = user.get_inactive_hand() + if(istype(O)) + O.unwield(user) + return unwield(user) + +/obj/item/twohanded/attack_self(mob/user) + ..() + if(wielded) //Trying to unwield it + unwield(user) + else //Trying to wield it + wield(user) + + +/obj/item/twohanded/equip_to_best_slot(mob/M) + if(..()) + unwield(M) + return + +///////////OFFHAND/////////////// +/obj/item/twohanded/offhand + w_class = WEIGHT_CLASS_HUGE + icon_state = "offhand" + name = "offhand" + flags = ABSTRACT + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/item/twohanded/offhand/unwield() + if(!QDELETED(src)) + qdel(src) + +/obj/item/twohanded/offhand/wield() + if(!QDELETED(src)) + qdel(src) + +///////////Two hand required objects/////////////// +//This is for objects that require two hands to even pick up +/obj/item/twohanded/required + w_class = WEIGHT_CLASS_HUGE + +/obj/item/twohanded/required/attack_self() + return + +/obj/item/twohanded/required/mob_can_equip(mob/M, slot) + if(wielded && !slot_flags) + to_chat(M, "[src] is too cumbersome to carry with anything but your hands!") + return FALSE + return ..() + +/obj/item/twohanded/required/attack_hand(mob/user)//Can't even pick it up without both hands empty + var/obj/item/twohanded/required/H = user.get_inactive_hand() + if(get_dist(src, user) > 1) + return FALSE + if(H != null) + to_chat(user, "[src] is too cumbersome to carry in one hand!") + return + if(loc != user) + wield(user) + ..() + +/obj/item/twohanded/required/on_give(mob/living/carbon/giver, mob/living/carbon/receiver) + var/obj/item/twohanded/required/H = receiver.get_inactive_hand() + if(H != null) //Check if he can wield it + receiver.drop_item() //Can't wear it so drop it + to_chat(receiver, "[src] is too cumbersome to carry in one hand!") + return + equipped(receiver,receiver.hand ? slot_l_hand : slot_r_hand) + +/obj/item/twohanded/required/equipped(mob/user, slot) + ..() + if(slot == slot_l_hand || slot == slot_r_hand) + wield(user) + else + unwield(user) + +/* + * Fireaxe + */ +/obj/item/twohanded/fireaxe // DEM AXES MAN, marker -Agouri + icon_state = "fireaxe0" + name = "fire axe" + desc = "Truly, the weapon of a madman. Who would think to fight fire with an axe?" + force = 5 + throwforce = 15 + sharp = TRUE + w_class = WEIGHT_CLASS_BULKY + slot_flags = SLOT_BACK + force_unwielded = 5 + force_wielded = 24 + attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + usesound = 'sound/items/crowbar.ogg' + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) + resistance_flags = FIRE_PROOF + +/obj/item/twohanded/fireaxe/update_icon() //Currently only here to fuck with the on-mob icons. + icon_state = "fireaxe[wielded]" + ..() + +/obj/item/twohanded/fireaxe/afterattack(atom/A, mob/user, proximity) + if(!proximity) + return + if(wielded) //destroys windows and grilles in one hit + if(istype(A, /obj/structure/window) || istype(A, /obj/structure/grille)) + var/obj/structure/W = A + W.obj_destruction("fireaxe") + + +/obj/item/twohanded/fireaxe/boneaxe // Blatant imitation of the fireaxe, but made out of bone. + icon_state = "bone_axe0" + name = "bone axe" + desc = "A large, vicious axe crafted out of several sharpened bone plates and crudely tied together. Made of monsters, by killing monsters, for killing monsters." + force_wielded = 23 + +/obj/item/twohanded/fireaxe/boneaxe/update_icon() + icon_state = "bone_axe[wielded]" + +/* + * Double-Bladed Energy Swords - Cheridan + */ +/obj/item/twohanded/dualsaber + var/hacked = FALSE + var/blade_color + icon_state = "dualsaber0" + name = "double-bladed energy sword" + desc = "Handle with care." + force = 3 + throwforce = 5 + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + var/w_class_on = WEIGHT_CLASS_BULKY + force_unwielded = 3 + force_wielded = 34 + wieldsound = 'sound/weapons/saberon.ogg' + unwieldsound = 'sound/weapons/saberoff.ogg' + armour_penetration = 35 + origin_tech = "magnets=4;syndicate=5" + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + block_chance = 75 + sharp_when_wielded = TRUE // only sharp when wielded + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + light_power = 2 + var/brightness_on = 2 + var/colormap = list(red=LIGHT_COLOR_RED, blue=LIGHT_COLOR_LIGHTBLUE, green=LIGHT_COLOR_GREEN, purple=LIGHT_COLOR_PURPLE, rainbow=LIGHT_COLOR_WHITE) + +/obj/item/twohanded/dualsaber/New() + ..() + if(!blade_color) + blade_color = pick("red", "blue", "green", "purple") + +/obj/item/twohanded/dualsaber/update_icon() + if(wielded) + icon_state = "dualsaber[blade_color][wielded]" + set_light(brightness_on, l_color=colormap[blade_color]) + else + icon_state = "dualsaber0" + set_light(0) + ..() + +/obj/item/twohanded/dualsaber/attack(mob/target, mob/living/user) + if(HULK in user.mutations) + to_chat(user, "You grip the blade too hard and accidentally close it!") + unwield() + return + ..() + if((CLUMSY in user.mutations) && (wielded) && prob(40)) + to_chat(user, "You twirl around a bit before losing your balance and impaling yourself on the [src].") + user.take_organ_damage(20, 25) + return + if((wielded) && prob(50)) + INVOKE_ASYNC(src, .proc/jedi_spin, user) + +/obj/item/twohanded/dualsaber/proc/jedi_spin(mob/living/user) + for(var/i in list(NORTH, SOUTH, EAST, WEST, EAST, SOUTH, NORTH, SOUTH, EAST, WEST, EAST, SOUTH)) + user.setDir(i) + if(i == WEST) + user.SpinAnimation(7, 1) + sleep(1) + +/obj/item/twohanded/dualsaber/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(wielded) + return ..() + return FALSE + +/obj/item/twohanded/dualsaber/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) //In case thats just so happens that it is still activated on the groud, prevents hulk from picking it up + if(wielded) + to_chat(user, "You can't pick up such a dangerous item with your meaty hands without losing fingers, better not to!") + return TRUE + +/obj/item/twohanded/dualsaber/green + blade_color = "green" + +/obj/item/twohanded/dualsaber/red + blade_color = "red" + +/obj/item/twohanded/dualsaber/purple + blade_color = "purple" + +/obj/item/twohanded/dualsaber/blue + blade_color = "blue" + +/obj/item/twohanded/dualsaber/unwield() + ..() + hitsound = "swing_hit" + w_class = initial(w_class) + +/obj/item/twohanded/dualsaber/IsReflect() + if(wielded) + return TRUE + +/obj/item/twohanded/dualsaber/wield(mob/living/carbon/M) //Specific wield () hulk checks due to reflection chance for balance issues and switches hitsounds. + if(HULK in M.mutations) + to_chat(M, "You lack the grace to wield this!") + return + ..() + hitsound = 'sound/weapons/blade1.ogg' + w_class = w_class_on + +/obj/item/twohanded/dualsaber/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!hacked) + hacked = TRUE + to_chat(user, "2XRNBW_ENGAGE") + blade_color = "rainbow" + update_icon() + else + to_chat(user, "It's starting to look like a triple rainbow - no, nevermind.") + +//spears +/obj/item/twohanded/spear + icon_state = "spearglass0" + name = "spear" + desc = "A haphazardly-constructed yet still deadly weapon of ancient design." + force = 10 + w_class = WEIGHT_CLASS_BULKY + slot_flags = SLOT_BACK + force_unwielded = 10 + force_wielded = 18 + throwforce = 20 + throw_speed = 4 + armour_penetration = 10 + materials = list(MAT_METAL = 1150, MAT_GLASS = 2075) + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "poked", "jabbed", "torn", "gored") + sharp = TRUE + no_spin_thrown = TRUE + var/obj/item/grenade/explosive = null + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 30) + var/icon_prefix = "spearglass" + +/obj/item/twohanded/spear/update_icon() + icon_state = "[icon_prefix][wielded]" + +/obj/item/twohanded/spear/CheckParts(list/parts_list) + var/obj/item/shard/tip = locate() in parts_list + if(istype(tip, /obj/item/shard/plasma)) + force_wielded = 19 + force_unwielded = 11 + throwforce = 21 + icon_prefix = "spearplasma" + update_icon() + qdel(tip) + ..() + + +/obj/item/twohanded/spear/afterattack(atom/movable/AM, mob/user, proximity) + if(!proximity) + return + if(isturf(AM)) //So you can actually melee with it + return + if(explosive && wielded) + explosive.forceMove(AM) + explosive.prime() + qdel(src) + +/obj/item/twohanded/spear/throw_impact(atom/target) + . = ..() + if(explosive) + explosive.prime() + qdel(src) + +/obj/item/twohanded/spear/bonespear //Blatant imitation of spear, but made out of bone. Not valid for explosive modification. + icon_state = "bone_spear0" + name = "bone spear" + desc = "A haphazardly-constructed yet still deadly weapon. The pinnacle of modern technology." + force = 11 + force_unwielded = 11 + force_wielded = 20 //I have no idea how to balance + throwforce = 22 + armour_penetration = 15 //Enhanced armor piercing + icon_prefix = "bone_spear" + +//GREY TIDE +/obj/item/twohanded/spear/grey_tide + icon_state = "spearglass0" + name = "\improper Grey Tide" + desc = "Recovered from the aftermath of a revolt aboard Defense Outpost Theta Aegis, in which a seemingly endless tide of Assistants caused heavy casualities among Nanotrasen military forces." + force_unwielded = 15 + force_wielded = 25 + throwforce = 20 + throw_speed = 4 + attack_verb = list("gored") + +/obj/item/twohanded/spear/grey_tide/afterattack(atom/movable/AM, mob/living/user, proximity) + ..() + if(!proximity) + return + user.faction |= "greytide(\ref[user])" + if(isliving(AM)) + var/mob/living/L = AM + if(istype (L, /mob/living/simple_animal/hostile/illusion)) + return + if(!L.stat && prob(50)) + var/mob/living/simple_animal/hostile/illusion/M = new(user.loc) + M.faction = user.faction.Copy() + M.attack_sound = hitsound + M.Copy_Parent(user, 100, user.health/2.5, 12, 30) + M.GiveTarget(L) + +//Putting heads on spears +/obj/item/twohanded/spear/attackby(obj/item/I, mob/living/user) + if(istype(I, /obj/item/organ/external/head)) + if(user.unEquip(src) && user.drop_item()) + to_chat(user, "You stick [I] onto the spear and stand it upright on the ground.") + var/obj/structure/headspear/HS = new /obj/structure/headspear(get_turf(src)) + var/matrix/M = matrix() + I.transform = M + var/image/IM = image(I.icon, I.icon_state) + IM.overlays = I.overlays.Copy() + HS.overlays += IM + I.forceMove(HS) + HS.mounted_head = I + forceMove(HS) + HS.contained_spear = src + else + return ..() + +/obj/structure/headspear + name = "head on a spear" + desc = "How barbaric." + icon_state = "headspear" + density = FALSE + anchored = TRUE + var/obj/item/organ/external/head/mounted_head = null + var/obj/item/twohanded/spear/contained_spear = null + +/obj/structure/headspear/Destroy() + QDEL_NULL(mounted_head) + QDEL_NULL(contained_spear) + return ..() + +/obj/structure/headspear/attack_hand(mob/living/user) + user.visible_message("[user] kicks over [src]!", "You kick down [src]!") + playsound(src, 'sound/weapons/genhit.ogg', 50, 1) + var/turf/T = get_turf(src) + if(contained_spear) + contained_spear.forceMove(T) + contained_spear = null + if(mounted_head) + mounted_head.forceMove(T) + mounted_head = null + qdel(src) + +/obj/item/twohanded/spear/kidan + icon_state = "kidanspear0" + name = "Kidan spear" + desc = "A spear brought over from the Kidan homeworld." + +// DIY CHAINSAW +/obj/item/twohanded/required/chainsaw + name = "chainsaw" + desc = "A versatile power tool. Useful for limbing trees and delimbing humans." + icon_state = "gchainsaw_off" + flags = CONDUCT + force = 13 + var/force_on = 24 + w_class = WEIGHT_CLASS_HUGE + throwforce = 13 + throw_speed = 2 + throw_range = 4 + materials = list(MAT_METAL = 13000) + origin_tech = "materials=3;engineering=4;combat=2" + attack_verb = list("sawed", "cut", "hacked", "carved", "cleaved", "butchered", "felled", "timbered") + hitsound = "swing_hit" + sharp = TRUE + actions_types = list(/datum/action/item_action/startchainsaw) + var/on = FALSE + +/obj/item/twohanded/required/chainsaw/attack_self(mob/user) + on = !on + to_chat(user, "As you pull the starting cord dangling from [src], [on ? "it begins to whirr." : "the chain stops moving."]") + if(on) + playsound(loc, 'sound/weapons/chainsawstart.ogg', 50, 1) + force = on ? force_on : initial(force) + throwforce = on ? force_on : initial(throwforce) + icon_state = "gchainsaw_[on ? "on" : "off"]" + + if(hitsound == "swing_hit") + hitsound = 'sound/weapons/chainsaw.ogg' + else + hitsound = "swing_hit" + + if(src == user.get_active_hand()) //update inhands + user.update_inv_l_hand() + user.update_inv_r_hand() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/twohanded/required/chainsaw/attack_hand(mob/user) + . = ..() + force = on ? force_on : initial(force) + throwforce = on ? force_on : initial(throwforce) + +/obj/item/twohanded/required/chainsaw/on_give(mob/living/carbon/giver, mob/living/carbon/receiver) + . = ..() + force = on ? force_on : initial(force) + throwforce = on ? force_on : initial(throwforce) + +/obj/item/twohanded/required/chainsaw/doomslayer + name = "OOOH BABY" + desc = "VRRRRRRR!!!" + armour_penetration = 100 + force_on = 30 + +/obj/item/twohanded/required/chainsaw/doomslayer/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(attack_type == PROJECTILE_ATTACK) + owner.visible_message("Ranged attacks just make [owner] angrier!") + playsound(src, pick('sound/weapons/bulletflyby.ogg','sound/weapons/bulletflyby2.ogg','sound/weapons/bulletflyby3.ogg'), 75, 1) + return TRUE + return FALSE + + +///CHAINSAW/// +/obj/item/twohanded/chainsaw + icon_state = "chainsaw0" + name = "Chainsaw" + desc = "Perfect for felling trees or fellow spacemen." + force = 15 + throwforce = 15 + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_BULKY // can't fit in backpacks + force_unwielded = 15 //still pretty robust + force_wielded = 40 //you'll gouge their eye out! Or a limb...maybe even their entire body! + wieldsound = 'sound/weapons/chainsawstart.ogg' + hitsound = null + armour_penetration = 35 + origin_tech = "materials=6;syndicate=4" + attack_verb = list("sawed", "cut", "hacked", "carved", "cleaved", "butchered", "felled", "timbered") + sharp = TRUE + +/obj/item/twohanded/chainsaw/update_icon() + if(wielded) + icon_state = "chainsaw[wielded]" + else + icon_state = "chainsaw0" + ..() + +/obj/item/twohanded/chainsaw/attack(mob/target, mob/living/user) + if(wielded) + playsound(loc, 'sound/weapons/chainsaw.ogg', 100, 1, -1) //incredibly loud; you ain't goin' for stealth with this thing. Credit to Lonemonk of Freesound for this sound. + if(isrobot(target)) + ..() + return + if(!isliving(target)) + return + else + target.Weaken(4) + ..() + return + else + playsound(loc, "swing_hit", 50, 1, -1) + return ..() + +/obj/item/twohanded/chainsaw/wield() //you can't disarm an active chainsaw, you crazy person. + ..() + flags |= NODROP + +/obj/item/twohanded/chainsaw/unwield() + ..() + flags &= ~NODROP + +// SINGULOHAMMER +/obj/item/twohanded/singularityhammer + name = "singularity hammer" + desc = "The pinnacle of close combat technology, the hammer harnesses the power of a miniaturized singularity to deal crushing blows." + icon_state = "mjollnir0" + flags = CONDUCT + slot_flags = SLOT_BACK + force = 5 + force_unwielded = 5 + force_wielded = 20 + throwforce = 15 + throw_range = 1 + w_class = WEIGHT_CLASS_HUGE + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + var/charged = 5 + origin_tech = "combat=4;bluespace=4;plasmatech=7" + +/obj/item/twohanded/singularityhammer/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/twohanded/singularityhammer/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/twohanded/singularityhammer/process() + if(charged < 5) + charged++ + +/obj/item/twohanded/singularityhammer/update_icon() //Currently only here to fuck with the on-mob icons. + icon_state = "mjollnir[wielded]" + ..() + +/obj/item/twohanded/singularityhammer/proc/vortex(turf/pull, mob/wielder) + for(var/atom/movable/X in orange(5, pull)) + if(X == wielder) + continue + if((X) && (!X.anchored) && (!ishuman(X))) + step_towards(X, pull) + step_towards(X, pull) + step_towards(X, pull) + else if(ishuman(X)) + var/mob/living/carbon/human/H = X + if(istype(H.shoes, /obj/item/clothing/shoes/magboots)) + var/obj/item/clothing/shoes/magboots/M = H.shoes + if(M.magpulse) + continue + H.apply_effect(1, WEAKEN, 0) + step_towards(H, pull) + step_towards(H, pull) + step_towards(H, pull) + +/obj/item/twohanded/singularityhammer/afterattack(atom/A, mob/user, proximity) + if(!proximity) + return + if(wielded) + if(charged == 5) + charged = 0 + if(isliving(A)) + var/mob/living/Z = A + Z.take_organ_damage(20, 0) + playsound(user, 'sound/weapons/marauder.ogg', 50, 1) + var/turf/target = get_turf(A) + vortex(target, user) + +/obj/item/twohanded/mjollnir + name = "Mjolnir" + desc = "A weapon worthy of a god, able to strike with the force of a lightning bolt. It crackles with barely contained energy." + icon_state = "mjollnir0" + flags = CONDUCT + slot_flags = SLOT_BACK + force = 5 + force_unwielded = 5 + force_wielded = 25 + throwforce = 30 + throw_range = 7 + w_class = WEIGHT_CLASS_HUGE + //var/charged = 5 + origin_tech = "combat=4;powerstorage=7" + +/obj/item/twohanded/mjollnir/proc/shock(mob/living/target) + do_sparks(5, 1, target.loc) + target.visible_message("[target.name] was shocked by the [name]!", \ + "You feel a powerful shock course through your body sending you flying!", \ + "You hear a heavy electrical crack!") + var/atom/throw_target = get_edge_target_turf(target, get_dir(src, get_step_away(target, src))) + target.throw_at(throw_target, 200, 4) + +/obj/item/twohanded/mjollnir/attack(mob/M, mob/user) + ..() + if(wielded) + //if(charged == 5) + //charged = 0 + playsound(loc, "sparks", 50, 1) + if(isliving(M)) + M.Stun(3) + shock(M) + +/obj/item/twohanded/mjollnir/throw_impact(atom/target) + . = ..() + if(isliving(target)) + var/mob/living/L = target + L.Stun(3) + shock(L) + +/obj/item/twohanded/mjollnir/update_icon() //Currently only here to fuck with the on-mob icons. + icon_state = "mjollnir[wielded]" + ..() + +/obj/item/twohanded/knighthammer + name = "singuloth knight's hammer" + desc = "A hammer made of sturdy metal with a golden skull adorned with wings on either side of the head.
        This weapon causes devastating damage to those it hits due to a power field sustained by a mini-singularity inside of the hammer." + icon_state = "knighthammer0" + flags = CONDUCT + slot_flags = SLOT_BACK + force = 5 + force_unwielded = 5 + force_wielded = 30 + throwforce = 15 + throw_range = 1 + w_class = WEIGHT_CLASS_HUGE + var/charged = 5 + origin_tech = "combat=5;bluespace=4" + +/obj/item/twohanded/knighthammer/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/twohanded/knighthammer/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/twohanded/knighthammer/process() + if(charged < 5) + charged++ + +/obj/item/twohanded/knighthammer/update_icon() //Currently only here to fuck with the on-mob icons. + icon_state = "knighthammer[wielded]" + ..() + +/obj/item/twohanded/knighthammer/afterattack(atom/A, mob/user, proximity) + if(!proximity) + return + if(charged == 5) + charged = 0 + if(isliving(A)) + var/mob/living/Z = A + if(Z.health >= 1) + Z.visible_message("[Z.name] was sent flying by a blow from the [name]!", \ + "You feel a powerful blow connect with your body and send you flying!", \ + "You hear something heavy impact flesh!.") + var/atom/throw_target = get_edge_target_turf(Z, get_dir(src, get_step_away(Z, src))) + Z.throw_at(throw_target, 200, 4) + playsound(user, 'sound/weapons/marauder.ogg', 50, 1) + else if(wielded && Z.health < 1) + Z.visible_message("[Z.name] was blown to pieces by the power of [name]!", \ + "You feel a powerful blow rip you apart!", \ + "You hear a heavy impact and the sound of ripping flesh!.") + Z.gib() + playsound(user, 'sound/weapons/marauder.ogg', 50, 1) + if(wielded) + if(istype(A, /turf/simulated/wall)) + var/turf/simulated/wall/Z = A + Z.ex_act(2) + charged = 3 + playsound(user, 'sound/weapons/marauder.ogg', 50, 1) + else if(istype(A, /obj/structure) || istype(A, /obj/mecha)) + var/obj/Z = A + Z.ex_act(2) + charged = 3 + playsound(user, 'sound/weapons/marauder.ogg', 50, 1) + +// Energized Fire axe +/obj/item/twohanded/energizedfireaxe + name = "energized fire axe" + desc = "Someone with a love for fire axes decided to turn one into a single-charge energy weapon. Seems excessive." + icon_state = "fireaxe0" + force = 5 + throwforce = 15 + sharp = TRUE + w_class = WEIGHT_CLASS_HUGE + armour_penetration = 20 + slot_flags = SLOT_BACK + force_unwielded = 5 + force_wielded = 30 + attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) + var/charged = 1 + +/obj/item/twohanded/energizedfireaxe/update_icon() + if(wielded) + icon_state = "fireaxe2" + else + icon_state = "fireaxe0" + ..() + +/obj/item/twohanded/energizedfireaxe/afterattack(atom/A, mob/user, proximity) + if(!proximity) + return + if(wielded) + if(isliving(A)) + var/mob/living/Z = A + if(charged) + charged-- + Z.take_organ_damage(0, 30) + user.visible_message("[user] slams the charged axe into [Z.name] with all [user.p_their()] might!") + playsound(loc, 'sound/magic/lightningbolt.ogg', 5, 1) + do_sparks(1, 1, src) + + if(A && wielded && (istype(A, /obj/structure/window) || istype(A, /obj/structure/grille))) + if(istype(A, /obj/structure/window)) + var/obj/structure/window/W = A + W.deconstruct(FALSE) + if(prob(4)) + charged++ + user.visible_message("The axe starts to emit an electric buzz!") + else + qdel(A) + if(prob(4)) + charged++ + user.visible_message("The axe starts to emit an electric buzz!") + +/obj/item/twohanded/pitchfork + icon_state = "pitchfork0" + name = "pitchfork" + desc = "A simple tool used for moving hay." + force = 7 + throwforce = 15 + w_class = WEIGHT_CLASS_BULKY + force_unwielded = 7 + force_wielded = 15 + attack_verb = list("attacked", "impaled", "pierced") + hitsound = 'sound/weapons/bladeslice.ogg' + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 30) + resistance_flags = FIRE_PROOF + +/obj/item/twohanded/pitchfork/demonic + name = "demonic pitchfork" + desc = "A red pitchfork, it looks like the work of the devil." + force = 19 + throwforce = 24 + force_unwielded = 19 + force_wielded = 25 + +/obj/item/twohanded/pitchfork/demonic/greater + force = 24 + throwforce = 50 + force_unwielded = 24 + force_wielded = 34 + +/obj/item/twohanded/pitchfork/demonic/ascended + force = 100 + throwforce = 100 + force_unwielded = 100 + force_wielded = 500000 // Kills you DEAD. + +/obj/item/twohanded/pitchfork/update_icon() + icon_state = "pitchfork[wielded]" + +/obj/item/twohanded/pitchfork/suicide_act(mob/user) + user.visible_message("[user] impales \himself in \his abdomen with [src]! It looks like \he's trying to commit suicide...") + return BRUTELOSS + +/obj/item/twohanded/pitchfork/demonic/pickup(mob/user) + . = ..() + if(istype(user, /mob/living)) + var/mob/living/U = user + if(U.mind && !U.mind.devilinfo && (U.mind.soulOwner == U.mind)) //Burn hands unless they are a devil or have sold their soul + U.visible_message("As [U] picks [src] up, [U]'s arms briefly catch fire.", \ + "\"As you pick up the [src] your arms ignite, reminding you of all your past sins.\"") + if(ishuman(U)) + var/mob/living/carbon/human/H = U + H.apply_damage(rand(force/2, force), BURN, pick("l_arm", "r_arm")) + else + U.adjustFireLoss(rand(force/2,force)) + +/obj/item/twohanded/pitchfork/demonic/attack(mob/target, mob/living/carbon/human/user) + if(user.mind && !user.mind.devilinfo && (user.mind.soulOwner != user.mind)) + to_chat(user, "The [src] burns in your hands.") + user.apply_damage(rand(force/2, force), BURN, pick("l_arm", "r_arm")) + ..() + +// It's no fun being the lord of all hell if you can't get out of a simple room +/obj/item/twohanded/pitchfork/demonic/ascended/afterattack(atom/target, mob/user, proximity) + if(!proximity || !wielded) + return + if(istype(target, /turf/simulated/wall)) + var/turf/simulated/wall/W = target + user.visible_message("[user] blasts \the [target] with \the [src]!") + playsound(target, 'sound/magic/Disintegrate.ogg', 100, 1) + W.devastate_wall(TRUE) + return 1 + ..() diff --git a/code/game/objects/items/weapons/vending_items.dm b/code/game/objects/items/weapons/vending_items.dm index aafebf7ef92..258771870d8 100644 --- a/code/game/objects/items/weapons/vending_items.dm +++ b/code/game/objects/items/weapons/vending_items.dm @@ -151,4 +151,4 @@ /obj/item/vending_refill/robotics machine_name = "Robotech Deluxe" - icon_state = "refill_engi" \ No newline at end of file + icon_state = "refill_engi" diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm index 735ae7dc608..6ef379ffcad 100644 --- a/code/game/objects/items/weapons/weaponry.dm +++ b/code/game/objects/items/weapons/weaponry.dm @@ -1,275 +1,275 @@ -/obj/item/banhammer - desc = "A banhammer" - name = "banhammer" - icon = 'icons/obj/items.dmi' - icon_state = "toyhammer" - slot_flags = SLOT_BELT - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 7 - throw_range = 15 - attack_verb = list("banned") - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) - resistance_flags = FIRE_PROOF - - -/obj/item/banhammer/suicide_act(mob/user) - to_chat(viewers(user), "[user] is hitting [user.p_them()]self with the [src.name]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.") - return BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS - -/obj/item/sord - name = "\improper SORD" - desc = "This thing is so unspeakably shitty you are having a hard time even holding it." - icon_state = "sord" - item_state = "sord" - slot_flags = SLOT_BELT - force = 2 - throwforce = 1 - w_class = WEIGHT_CLASS_NORMAL - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - -/obj/item/sord/suicide_act(mob/user) - user.visible_message("[user] is trying to impale [user.p_them()]self with [src]! It might be a suicide attempt if it weren't so shitty.", \ - "You try to impale yourself with [src], but it's USELESS...") - return SHAME - -/obj/item/claymore - name = "claymore" - desc = "What are you standing around staring at this for? Get to killing!" - icon_state = "claymore" - item_state = "claymore" - flags = CONDUCT - hitsound = 'sound/weapons/bladeslice.ogg' - slot_flags = SLOT_BELT - force = 40 - throwforce = 10 - sharp = 1 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 50 - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - -/obj/item/claymore/suicide_act(mob/user) - user.visible_message("[user] is falling on the [name]! It looks like [user.p_theyre()] trying to commit suicide.") - return BRUTELOSS - -/obj/item/claymore/ceremonial - name = "ceremonial claymore" - desc = "An engraved and fancy version of the claymore. It appears to be less sharp than it's more functional cousin." - force = 20 - -/obj/item/katana - name = "katana" - desc = "Woefully underpowered in D20" - icon_state = "katana" - item_state = "katana" - flags = CONDUCT - slot_flags = SLOT_BELT | SLOT_BACK - force = 40 - throwforce = 10 - sharp = 1 - w_class = WEIGHT_CLASS_NORMAL - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - block_chance = 50 - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - -/obj/item/katana/cursed - slot_flags = null - -/obj/item/katana/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku.") - return BRUTELOSS - -/obj/item/harpoon - name = "harpoon" - sharp = 1 - desc = "Tharr she blows!" - icon_state = "harpoon" - item_state = "harpoon" - force = 20 - throwforce = 15 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("jabbed","stabbed","ripped") - -/obj/item/wirerod - name = "Wired rod" - desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit." - icon_state = "wiredrod" - item_state = "rods" - flags = CONDUCT - force = 9 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=1150, MAT_GLASS=75) - attack_verb = list("hit", "bludgeoned", "whacked", "bonked") - -/obj/item/wirerod/attackby(obj/item/I, mob/user, params) - ..() - if(istype(I, /obj/item/shard)) - var/obj/item/twohanded/spear/S = new /obj/item/twohanded/spear - if(istype(I, /obj/item/shard/plasma)) - S.force_wielded = 19 - S.force_unwielded = 11 - S.throwforce = 21 - S.icon_prefix = "spearplasma" - S.update_icon() - if(!remove_item_from_storage(user)) - user.unEquip(src) - user.unEquip(I) - - user.put_in_hands(S) - to_chat(user, "You fasten the glass shard to the top of the rod with the cable.") - qdel(I) - qdel(src) - - else if(istype(I, /obj/item/assembly/igniter) && !(I.flags & NODROP)) - var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod - - if(!remove_item_from_storage(user)) - user.unEquip(src) - user.unEquip(I) - - user.put_in_hands(P) - to_chat(user, "You fasten [I] to the top of the rod with the cable.") - qdel(I) - qdel(src) - -/obj/item/throwing_star - name = "throwing star" - desc = "An ancient weapon still used to this day due to it's ease of lodging itself into victim's body parts" - icon_state = "throwingstar" - item_state = "eshield0" - force = 2 - throwforce = 20 //This is never used on mobs since this has a 100% embed chance. - throw_speed = 4 - embedded_pain_multiplier = 4 - w_class = WEIGHT_CLASS_SMALL - embed_chance = 100 - embedded_fall_chance = 0 //Hahaha! - sharp = 1 - materials = list(MAT_METAL=500, MAT_GLASS=500) - resistance_flags = FIRE_PROOF - -/obj/item/spear/kidan - icon_state = "kidanspear" - name = "Kidan spear" - desc = "A one-handed spear brought over from the Kidan homeworld." - icon_state = "kidanspear" - item_state = "kidanspear" - force = 10 - throwforce = 15 - -/obj/item/melee/baseball_bat - name = "baseball bat" - desc = "There ain't a skull in the league that can withstand a swatter." - icon = 'icons/obj/items.dmi' - icon_state = "baseball_bat" - item_state = "baseball_bat" - var/deflectmode = FALSE // deflect small/medium thrown objects - var/lastdeflect - force = 10 - throwforce = 12 - attack_verb = list("beat", "smacked") - w_class = WEIGHT_CLASS_HUGE - var/homerun_ready = 0 - var/homerun_able = 0 - -/obj/item/melee/baseball_bat/homerun - name = "home run bat" - desc = "This thing looks dangerous... Dangerously good at baseball, that is." - homerun_able = 1 - -/obj/item/melee/baseball_bat/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - . = ..() - if(!isitem(hitby) || attack_type != THROWN_PROJECTILE_ATTACK) - return FALSE - var/obj/item/I = hitby - if(I.w_class <= WEIGHT_CLASS_NORMAL || istype(I, /obj/item/beach_ball)) // baseball bat deflecting - if(deflectmode) - if(prob(10)) - visible_message("[owner] Deflects [I] directly back at the thrower! It's a home run!", "You deflect the [I] directly back at the thrower! It's a home run!") - playsound(get_turf(owner), 'sound/weapons/homerun.ogg', 100, 1) - do_attack_animation(I, ATTACK_EFFECT_DISARM) - I.throw_at(I.thrownby, 20, 20, owner) - deflectmode = FALSE - if(!istype(I, /obj/item/beach_ball)) - lastdeflect = world.time + 3000 - return TRUE - else if(prob(30)) - visible_message("[owner] swings! And [p_they()] miss[p_es()]! How embarassing.", "You swing! You miss! Oh no!") - playsound(get_turf(owner), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - do_attack_animation(get_step(owner, pick(alldirs)), ATTACK_EFFECT_DISARM) - deflectmode = FALSE - if(!istype(I, /obj/item/beach_ball)) - lastdeflect = world.time + 3000 - return FALSE - else - visible_message("[owner] swings and deflects [I]!", "You swing and deflect the [I]!") - playsound(get_turf(owner), 'sound/weapons/baseball_hit.ogg', 50, 1, -1) - do_attack_animation(I, ATTACK_EFFECT_DISARM) - I.throw_at(get_edge_target_turf(owner, pick(cardinal)), rand(8,10), 14, owner) - deflectmode = FALSE - if(!istype(I, /obj/item/beach_ball)) - lastdeflect = world.time + 3000 - return TRUE - -/obj/item/melee/baseball_bat/attack_self(mob/user) - if(!homerun_able) - if(!deflectmode && world.time >= lastdeflect) - to_chat(user, "You prepare to deflect objects thrown at you. You cannot attack during this time.") - deflectmode = TRUE - else if(deflectmode && world.time >= lastdeflect) - to_chat(user, "You no longer deflect objects thrown at you. You can attack during this time") - deflectmode = FALSE - else - to_chat(user, "You need to wait until you can deflect again. The ability will be ready in [time2text(lastdeflect - world.time, "m:ss")]") - return ..() - if(homerun_ready) - to_chat(user, "You're already ready to do a home run!") - return ..() - to_chat(user, "You begin gathering strength...") - playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, 1) - if(do_after(user, 90, target = user)) - to_chat(user, "You gather power! Time for a home run!") - homerun_ready = 1 - ..() - -/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user) - if(deflectmode) - to_chat(user, "You cannot attack in deflect mode!") - return - . = ..() - var/atom/throw_target = get_edge_target_turf(target, user.dir) - if(homerun_ready) - user.visible_message("It's a home run!") - target.throw_at(throw_target, rand(8,10), 14, user) - target.ex_act(2) - playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1) - homerun_ready = 0 - return - else if(!target.anchored) - target.throw_at(throw_target, rand(1,2), 7, user) - -/obj/item/melee/baseball_bat/ablative - name = "metal baseball bat" - desc = "This bat is made of highly reflective, highly armored material." - icon_state = "baseball_bat_metal" - item_state = "baseball_bat_metal" - force = 12 - throwforce = 15 - -/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers - var/picksound = rand(1,2) - var/turf = get_turf(src) - if(picksound == 1) - playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, 1) - if(picksound == 2) - playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1) - return 1 +/obj/item/banhammer + desc = "A banhammer" + name = "banhammer" + icon = 'icons/obj/items.dmi' + icon_state = "toyhammer" + slot_flags = SLOT_BELT + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 7 + throw_range = 15 + attack_verb = list("banned") + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) + resistance_flags = FIRE_PROOF + + +/obj/item/banhammer/suicide_act(mob/user) + to_chat(viewers(user), "[user] is hitting [user.p_them()]self with the [src.name]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.") + return BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS + +/obj/item/sord + name = "\improper SORD" + desc = "This thing is so unspeakably shitty you are having a hard time even holding it." + icon_state = "sord" + item_state = "sord" + slot_flags = SLOT_BELT + force = 2 + throwforce = 1 + w_class = WEIGHT_CLASS_NORMAL + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + +/obj/item/sord/suicide_act(mob/user) + user.visible_message("[user] is trying to impale [user.p_them()]self with [src]! It might be a suicide attempt if it weren't so shitty.", \ + "You try to impale yourself with [src], but it's USELESS...") + return SHAME + +/obj/item/claymore + name = "claymore" + desc = "What are you standing around staring at this for? Get to killing!" + icon_state = "claymore" + item_state = "claymore" + flags = CONDUCT + hitsound = 'sound/weapons/bladeslice.ogg' + slot_flags = SLOT_BELT + force = 40 + throwforce = 10 + sharp = 1 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + block_chance = 50 + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + +/obj/item/claymore/suicide_act(mob/user) + user.visible_message("[user] is falling on the [name]! It looks like [user.p_theyre()] trying to commit suicide.") + return BRUTELOSS + +/obj/item/claymore/ceremonial + name = "ceremonial claymore" + desc = "An engraved and fancy version of the claymore. It appears to be less sharp than it's more functional cousin." + force = 20 + +/obj/item/katana + name = "katana" + desc = "Woefully underpowered in D20" + icon_state = "katana" + item_state = "katana" + flags = CONDUCT + slot_flags = SLOT_BELT | SLOT_BACK + force = 40 + throwforce = 10 + sharp = 1 + w_class = WEIGHT_CLASS_NORMAL + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + block_chance = 50 + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + +/obj/item/katana/cursed + slot_flags = null + +/obj/item/katana/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku.") + return BRUTELOSS + +/obj/item/harpoon + name = "harpoon" + sharp = 1 + desc = "Tharr she blows!" + icon_state = "harpoon" + item_state = "harpoon" + force = 20 + throwforce = 15 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("jabbed","stabbed","ripped") + +/obj/item/wirerod + name = "Wired rod" + desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit." + icon_state = "wiredrod" + item_state = "rods" + flags = CONDUCT + force = 9 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=1150, MAT_GLASS=75) + attack_verb = list("hit", "bludgeoned", "whacked", "bonked") + +/obj/item/wirerod/attackby(obj/item/I, mob/user, params) + ..() + if(istype(I, /obj/item/shard)) + var/obj/item/twohanded/spear/S = new /obj/item/twohanded/spear + if(istype(I, /obj/item/shard/plasma)) + S.force_wielded = 19 + S.force_unwielded = 11 + S.throwforce = 21 + S.icon_prefix = "spearplasma" + S.update_icon() + if(!remove_item_from_storage(user)) + user.unEquip(src) + user.unEquip(I) + + user.put_in_hands(S) + to_chat(user, "You fasten the glass shard to the top of the rod with the cable.") + qdel(I) + qdel(src) + + else if(istype(I, /obj/item/assembly/igniter) && !(I.flags & NODROP)) + var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod + + if(!remove_item_from_storage(user)) + user.unEquip(src) + user.unEquip(I) + + user.put_in_hands(P) + to_chat(user, "You fasten [I] to the top of the rod with the cable.") + qdel(I) + qdel(src) + +/obj/item/throwing_star + name = "throwing star" + desc = "An ancient weapon still used to this day due to it's ease of lodging itself into victim's body parts" + icon_state = "throwingstar" + item_state = "eshield0" + force = 2 + throwforce = 20 //This is never used on mobs since this has a 100% embed chance. + throw_speed = 4 + embedded_pain_multiplier = 4 + w_class = WEIGHT_CLASS_SMALL + embed_chance = 100 + embedded_fall_chance = 0 //Hahaha! + sharp = 1 + materials = list(MAT_METAL=500, MAT_GLASS=500) + resistance_flags = FIRE_PROOF + +/obj/item/spear/kidan + icon_state = "kidanspear" + name = "Kidan spear" + desc = "A one-handed spear brought over from the Kidan homeworld." + icon_state = "kidanspear" + item_state = "kidanspear" + force = 10 + throwforce = 15 + +/obj/item/melee/baseball_bat + name = "baseball bat" + desc = "There ain't a skull in the league that can withstand a swatter." + icon = 'icons/obj/items.dmi' + icon_state = "baseball_bat" + item_state = "baseball_bat" + var/deflectmode = FALSE // deflect small/medium thrown objects + var/lastdeflect + force = 10 + throwforce = 12 + attack_verb = list("beat", "smacked") + w_class = WEIGHT_CLASS_HUGE + var/homerun_ready = 0 + var/homerun_able = 0 + +/obj/item/melee/baseball_bat/homerun + name = "home run bat" + desc = "This thing looks dangerous... Dangerously good at baseball, that is." + homerun_able = 1 + +/obj/item/melee/baseball_bat/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + . = ..() + if(!isitem(hitby) || attack_type != THROWN_PROJECTILE_ATTACK) + return FALSE + var/obj/item/I = hitby + if(I.w_class <= WEIGHT_CLASS_NORMAL || istype(I, /obj/item/beach_ball)) // baseball bat deflecting + if(deflectmode) + if(prob(10)) + visible_message("[owner] Deflects [I] directly back at the thrower! It's a home run!", "You deflect the [I] directly back at the thrower! It's a home run!") + playsound(get_turf(owner), 'sound/weapons/homerun.ogg', 100, 1) + do_attack_animation(I, ATTACK_EFFECT_DISARM) + I.throw_at(I.thrownby, 20, 20, owner) + deflectmode = FALSE + if(!istype(I, /obj/item/beach_ball)) + lastdeflect = world.time + 3000 + return TRUE + else if(prob(30)) + visible_message("[owner] swings! And [p_they()] miss[p_es()]! How embarassing.", "You swing! You miss! Oh no!") + playsound(get_turf(owner), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + do_attack_animation(get_step(owner, pick(alldirs)), ATTACK_EFFECT_DISARM) + deflectmode = FALSE + if(!istype(I, /obj/item/beach_ball)) + lastdeflect = world.time + 3000 + return FALSE + else + visible_message("[owner] swings and deflects [I]!", "You swing and deflect the [I]!") + playsound(get_turf(owner), 'sound/weapons/baseball_hit.ogg', 50, 1, -1) + do_attack_animation(I, ATTACK_EFFECT_DISARM) + I.throw_at(get_edge_target_turf(owner, pick(cardinal)), rand(8,10), 14, owner) + deflectmode = FALSE + if(!istype(I, /obj/item/beach_ball)) + lastdeflect = world.time + 3000 + return TRUE + +/obj/item/melee/baseball_bat/attack_self(mob/user) + if(!homerun_able) + if(!deflectmode && world.time >= lastdeflect) + to_chat(user, "You prepare to deflect objects thrown at you. You cannot attack during this time.") + deflectmode = TRUE + else if(deflectmode && world.time >= lastdeflect) + to_chat(user, "You no longer deflect objects thrown at you. You can attack during this time") + deflectmode = FALSE + else + to_chat(user, "You need to wait until you can deflect again. The ability will be ready in [time2text(lastdeflect - world.time, "m:ss")]") + return ..() + if(homerun_ready) + to_chat(user, "You're already ready to do a home run!") + return ..() + to_chat(user, "You begin gathering strength...") + playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, 1) + if(do_after(user, 90, target = user)) + to_chat(user, "You gather power! Time for a home run!") + homerun_ready = 1 + ..() + +/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user) + if(deflectmode) + to_chat(user, "You cannot attack in deflect mode!") + return + . = ..() + var/atom/throw_target = get_edge_target_turf(target, user.dir) + if(homerun_ready) + user.visible_message("It's a home run!") + target.throw_at(throw_target, rand(8,10), 14, user) + target.ex_act(2) + playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1) + homerun_ready = 0 + return + else if(!target.anchored) + target.throw_at(throw_target, rand(1,2), 7, user) + +/obj/item/melee/baseball_bat/ablative + name = "metal baseball bat" + desc = "This bat is made of highly reflective, highly armored material." + icon_state = "baseball_bat_metal" + item_state = "baseball_bat_metal" + force = 12 + throwforce = 15 + +/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers + var/picksound = rand(1,2) + var/turf = get_turf(src) + if(picksound == 1) + playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, 1) + if(picksound == 2) + playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1) + return 1 diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 9323412e830..1816357d450 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -271,4 +271,4 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e ///returns how much the object blocks an explosion. Used by subtypes. /obj/proc/GetExplosionBlock() - CRASH("Unimplemented GetExplosionBlock()") \ No newline at end of file + CRASH("Unimplemented GetExplosionBlock()") diff --git a/code/game/objects/random/random.dm b/code/game/objects/random/random.dm index 22479653e69..59cc3898088 100644 --- a/code/game/objects/random/random.dm +++ b/code/game/objects/random/random.dm @@ -102,4 +102,4 @@ prob(2);/obj/random/toolbox,\ prob(2);/obj/item/storage/belt/utility,\ prob(5);/obj/random/tool,\ - prob(3);/obj/item/stack/tape_roll) \ No newline at end of file + prob(3);/obj/item/stack/tape_roll) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 6849e6d07d3..3d727142db5 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -1,162 +1,162 @@ -/obj/structure - icon = 'icons/obj/structures.dmi' - pressure_resistance = 8 - max_integrity = 300 - var/climbable - var/mob/climber - var/broken = FALSE - -/obj/structure/New() - if (!armor) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - ..() - if(smooth) - if(SSticker && SSticker.current_state == GAME_STATE_PLAYING) - queue_smooth(src) - queue_smooth_neighbors(src) - icon_state = "" - if(climbable) - verbs += /obj/structure/proc/climb_on - if(SSticker) - cameranet.updateVisibility(src) - -/obj/structure/Destroy() - if(SSticker) - cameranet.updateVisibility(src) - if(smooth) - var/turf/T = get_turf(src) - spawn(0) - queue_smooth_neighbors(T) - return ..() - -/obj/structure/proc/climb_on() - - set name = "Climb structure" - set desc = "Climbs onto a structure." - set category = null - set src in oview(1) - - do_climb(usr) - -/obj/structure/MouseDrop_T(var/atom/movable/C, mob/user as mob) - if(..()) - return - if(C == user) - do_climb(user) - -/obj/structure/proc/density_check() - for(var/obj/O in orange(0, src)) - if(O.density && !istype(O, /obj/machinery/door/window)) //Ignores windoors, as those already block climbing, otherwise a windoor on the opposite side of a table would prevent climbing. - return O - var/turf/T = get_turf(src) - if(T.density) - return T - return null - -/obj/structure/proc/do_climb(var/mob/living/user) - if(!can_touch(user) || !climbable) - return - var/blocking_object = density_check() - if(blocking_object) - to_chat(user, "You cannot climb [src], as it is blocked by \a [blocking_object]!") - return - - var/turf/T = src.loc - if(!T || !istype(T)) return - - usr.visible_message("[user] starts climbing onto \the [src]!") - climber = user - if(!do_after(user, 50, target = src)) - climber = null - return - - if(!can_touch(user) || !climbable) - climber = null - return - - usr.loc = get_turf(src) - if(get_turf(user) == get_turf(src)) - usr.visible_message("[user] climbs onto \the [src]!") - - climber = null - -/obj/structure/proc/structure_shaken() - - for(var/mob/living/M in get_turf(src)) - - if(M.lying) return //No spamming this on people. - - M.Weaken(5) - to_chat(M, "You topple as \the [src] moves under you!") - - if(prob(25)) - - var/damage = rand(15,30) - var/mob/living/carbon/human/H = M - if(!istype(H)) - to_chat(H, "You land heavily!") - M.adjustBruteLoss(damage) - return - - var/obj/item/organ/external/affecting - - switch(pick(list("ankle","wrist","head","knee","elbow"))) - if("ankle") - affecting = H.get_organ(pick("l_foot", "r_foot")) - if("knee") - affecting = H.get_organ(pick("l_leg", "r_leg")) - if("wrist") - affecting = H.get_organ(pick("l_hand", "r_hand")) - if("elbow") - affecting = H.get_organ(pick("l_arm", "r_arm")) - if("head") - affecting = H.get_organ("head") - - if(affecting) - to_chat(M, "You land heavily on your [affecting.name]!") - affecting.receive_damage(damage, 0) - if(affecting.parent) - affecting.parent.add_autopsy_data("Misadventure", damage) - else - to_chat(H, "You land heavily!") - H.adjustBruteLoss(damage) - - H.UpdateDamageIcon() - return - -/obj/structure/proc/can_touch(var/mob/user) - if(!user) - return 0 - if(!Adjacent(user)) - return 0 - if(user.restrained() || user.buckled) - to_chat(user, "You need your hands and legs free for this.") - return 0 - if(user.stat || user.paralysis || user.sleeping || user.lying || user.IsWeakened()) - return 0 - if(issilicon(user)) - to_chat(user, "You need hands for this.") - return 0 - return 1 - -/obj/structure/examine(mob/user) - . = ..() - if(!(resistance_flags & INDESTRUCTIBLE)) - if(resistance_flags & ON_FIRE) - . += "It's on fire!" - if(broken) - . += "It appears to be broken." - var/examine_status = examine_status(user) - if(examine_status) - . += examine_status - -/obj/structure/proc/examine_status(mob/user) //An overridable proc, mostly for falsewalls. - var/healthpercent = (obj_integrity/max_integrity) * 100 - switch(healthpercent) - if(50 to 99) - return "It looks slightly damaged." - if(25 to 50) - return "It appears heavily damaged." - if(0 to 25) - if(!broken) - return "It's falling apart!" +/obj/structure + icon = 'icons/obj/structures.dmi' + pressure_resistance = 8 + max_integrity = 300 + var/climbable + var/mob/climber + var/broken = FALSE + +/obj/structure/New() + if (!armor) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + ..() + if(smooth) + if(SSticker && SSticker.current_state == GAME_STATE_PLAYING) + queue_smooth(src) + queue_smooth_neighbors(src) + icon_state = "" + if(climbable) + verbs += /obj/structure/proc/climb_on + if(SSticker) + cameranet.updateVisibility(src) + +/obj/structure/Destroy() + if(SSticker) + cameranet.updateVisibility(src) + if(smooth) + var/turf/T = get_turf(src) + spawn(0) + queue_smooth_neighbors(T) + return ..() + +/obj/structure/proc/climb_on() + + set name = "Climb structure" + set desc = "Climbs onto a structure." + set category = null + set src in oview(1) + + do_climb(usr) + +/obj/structure/MouseDrop_T(var/atom/movable/C, mob/user as mob) + if(..()) + return + if(C == user) + do_climb(user) + +/obj/structure/proc/density_check() + for(var/obj/O in orange(0, src)) + if(O.density && !istype(O, /obj/machinery/door/window)) //Ignores windoors, as those already block climbing, otherwise a windoor on the opposite side of a table would prevent climbing. + return O + var/turf/T = get_turf(src) + if(T.density) + return T + return null + +/obj/structure/proc/do_climb(var/mob/living/user) + if(!can_touch(user) || !climbable) + return + var/blocking_object = density_check() + if(blocking_object) + to_chat(user, "You cannot climb [src], as it is blocked by \a [blocking_object]!") + return + + var/turf/T = src.loc + if(!T || !istype(T)) return + + usr.visible_message("[user] starts climbing onto \the [src]!") + climber = user + if(!do_after(user, 50, target = src)) + climber = null + return + + if(!can_touch(user) || !climbable) + climber = null + return + + usr.loc = get_turf(src) + if(get_turf(user) == get_turf(src)) + usr.visible_message("[user] climbs onto \the [src]!") + + climber = null + +/obj/structure/proc/structure_shaken() + + for(var/mob/living/M in get_turf(src)) + + if(M.lying) return //No spamming this on people. + + M.Weaken(5) + to_chat(M, "You topple as \the [src] moves under you!") + + if(prob(25)) + + var/damage = rand(15,30) + var/mob/living/carbon/human/H = M + if(!istype(H)) + to_chat(H, "You land heavily!") + M.adjustBruteLoss(damage) + return + + var/obj/item/organ/external/affecting + + switch(pick(list("ankle","wrist","head","knee","elbow"))) + if("ankle") + affecting = H.get_organ(pick("l_foot", "r_foot")) + if("knee") + affecting = H.get_organ(pick("l_leg", "r_leg")) + if("wrist") + affecting = H.get_organ(pick("l_hand", "r_hand")) + if("elbow") + affecting = H.get_organ(pick("l_arm", "r_arm")) + if("head") + affecting = H.get_organ("head") + + if(affecting) + to_chat(M, "You land heavily on your [affecting.name]!") + affecting.receive_damage(damage, 0) + if(affecting.parent) + affecting.parent.add_autopsy_data("Misadventure", damage) + else + to_chat(H, "You land heavily!") + H.adjustBruteLoss(damage) + + H.UpdateDamageIcon() + return + +/obj/structure/proc/can_touch(var/mob/user) + if(!user) + return 0 + if(!Adjacent(user)) + return 0 + if(user.restrained() || user.buckled) + to_chat(user, "You need your hands and legs free for this.") + return 0 + if(user.stat || user.paralysis || user.sleeping || user.lying || user.IsWeakened()) + return 0 + if(issilicon(user)) + to_chat(user, "You need hands for this.") + return 0 + return 1 + +/obj/structure/examine(mob/user) + . = ..() + if(!(resistance_flags & INDESTRUCTIBLE)) + if(resistance_flags & ON_FIRE) + . += "It's on fire!" + if(broken) + . += "It appears to be broken." + var/examine_status = examine_status(user) + if(examine_status) + . += examine_status + +/obj/structure/proc/examine_status(mob/user) //An overridable proc, mostly for falsewalls. + var/healthpercent = (obj_integrity/max_integrity) * 100 + switch(healthpercent) + if(50 to 99) + return "It looks slightly damaged." + if(25 to 50) + return "It appears heavily damaged." + if(0 to 25) + if(!broken) + return "It's falling apart!" diff --git a/code/game/objects/structures/aliens.dm b/code/game/objects/structures/aliens.dm index 26ab24b609c..1de93f4d583 100644 --- a/code/game/objects/structures/aliens.dm +++ b/code/game/objects/structures/aliens.dm @@ -1,334 +1,334 @@ -/* Alien shit! - * Contains: - * structure/alien - * Resin - * Weeds - * Egg - */ - -#define WEED_NORTH_EDGING "north" -#define WEED_SOUTH_EDGING "south" -#define WEED_EAST_EDGING "east" -#define WEED_WEST_EDGING "west" - -/obj/structure/alien - icon = 'icons/mob/alien.dmi' - max_integrity = 100 - -/obj/structure/alien/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee") - switch(damage_type) - if(BRUTE) - damage_amount *= 0.25 - if(BURN) - damage_amount *= 2 - . = ..() - -/obj/structure/alien/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(loc, 'sound/effects/attackblob.ogg', 100, TRUE) - else - playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - if(damage_amount) - playsound(loc, 'sound/items/welder.ogg', 100, TRUE) - -/* - * Resin - */ -/obj/structure/alien/resin - name = "resin" - desc = "Looks like some kind of thick resin." - icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi' - icon_state = "resin" - density = TRUE - opacity = TRUE - anchored = TRUE - canSmoothWith = list(/obj/structure/alien/resin) - max_integrity = 200 - smooth = SMOOTH_TRUE - var/resintype = null - -/obj/structure/alien/resin/Initialize() - air_update_turf(1) - ..() - -/obj/structure/alien/resin/Destroy() - var/turf/T = get_turf(src) - . = ..() - T.air_update_turf(TRUE) - -/obj/structure/alien/resin/Move() - var/turf/T = loc - ..() - move_update_air(T) - -/obj/structure/alien/resin/CanAtmosPass() - return !density - -/obj/structure/alien/resin/wall - name = "resin wall" - desc = "Thick resin solidified into a wall." - icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi' - icon_state = "wall0" //same as resin, but consistency ho! - resintype = "wall" - canSmoothWith = list(/obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane) - -/obj/structure/alien/resin/wall/BlockSuperconductivity() - return 1 - -/obj/structure/alien/resin/wall/shadowling //For chrysalis - name = "chrysalis wall" - desc = "Some sort of purple substance in an egglike shape. It pulses and throbs from within and seems impenetrable." - max_integrity = INFINITY - -/obj/structure/alien/resin/membrane - name = "resin membrane" - desc = "Resin just thin enough to let light pass through." - icon = 'icons/obj/smooth_structures/alien/resin_membrane.dmi' - icon_state = "membrane0" - opacity = 0 - max_integrity = 160 - resintype = "membrane" - canSmoothWith = list(/obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane) - -/obj/structure/alien/resin/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && mover.checkpass(PASSGLASS)) - return !opacity - return !density - - -/* - * Weeds - */ - -#define NODERANGE 3 - -/obj/structure/alien/weeds - gender = PLURAL - name = "resin floor" - desc = "A thick resin surface covers the floor." - anchored = TRUE - density = FALSE - layer = TURF_LAYER - plane = FLOOR_PLANE - icon_state = "weeds" - max_integrity = 15 - var/obj/structure/alien/weeds/node/linked_node = null - var/static/list/weedImageCache - - -/obj/structure/alien/weeds/New(pos, node) - ..() - linked_node = node - if(istype(loc, /turf/space)) - qdel(src) - return - if(icon_state == "weeds") - icon_state = pick("weeds", "weeds1", "weeds2") - fullUpdateWeedOverlays() - spawn(rand(150, 200)) - if(src) - Life() - -/obj/structure/alien/weeds/Destroy() - var/turf/T = loc - for(var/obj/structure/alien/weeds/W in range(1,T)) - W.updateWeedOverlays() - linked_node = null - return ..() - -/obj/structure/alien/weeds/proc/Life() - set background = BACKGROUND_ENABLED - var/turf/U = get_turf(src) - - if(istype(U, /turf/space)) - qdel(src) - return - - if(!linked_node || get_dist(linked_node, src) > linked_node.node_range) - return - - for(var/turf/T in U.GetAtmosAdjacentTurfs()) - - if(locate(/obj/structure/alien/weeds) in T || istype(T, /turf/space)) - continue - - new /obj/structure/alien/weeds(T, linked_node) - -/obj/structure/alien/weeds/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > 300) - take_damage(5, BURN, 0, 0) - -/obj/structure/alien/weeds/proc/updateWeedOverlays() - - overlays.Cut() - - if(!weedImageCache || !weedImageCache.len) - weedImageCache = list() - weedImageCache.len = 4 - weedImageCache[WEED_NORTH_EDGING] = image('icons/mob/alien.dmi', "weeds_side_n", layer=2.11, pixel_y = -32) - weedImageCache[WEED_SOUTH_EDGING] = image('icons/mob/alien.dmi', "weeds_side_s", layer=2.11, pixel_y = 32) - weedImageCache[WEED_EAST_EDGING] = image('icons/mob/alien.dmi', "weeds_side_e", layer=2.11, pixel_x = -32) - weedImageCache[WEED_WEST_EDGING] = image('icons/mob/alien.dmi', "weeds_side_w", layer=2.11, pixel_x = 32) - - var/turf/N = get_step(src, NORTH) - var/turf/S = get_step(src, SOUTH) - var/turf/E = get_step(src, EAST) - var/turf/W = get_step(src, WEST) - if(!locate(/obj/structure/alien) in N.contents) - if(istype(N, /turf/simulated/floor)) - overlays += weedImageCache[WEED_SOUTH_EDGING] - if(!locate(/obj/structure/alien) in S.contents) - if(istype(S, /turf/simulated/floor)) - overlays += weedImageCache[WEED_NORTH_EDGING] - if(!locate(/obj/structure/alien) in E.contents) - if(istype(E, /turf/simulated/floor)) - overlays += weedImageCache[WEED_WEST_EDGING] - if(!locate(/obj/structure/alien) in W.contents) - if(istype(W, /turf/simulated/floor)) - overlays += weedImageCache[WEED_EAST_EDGING] - - -/obj/structure/alien/weeds/proc/fullUpdateWeedOverlays() - for(var/obj/structure/alien/weeds/W in range(1,src)) - W.updateWeedOverlays() - -//Weed nodes -/obj/structure/alien/weeds/node - name = "glowing resin" - desc = "Blue bioluminescence shines from beneath the surface." - icon_state = "weednode" - light_range = 1 - var/node_range = NODERANGE - - -/obj/structure/alien/weeds/node/New() - ..(loc, src) - -#undef NODERANGE - - -/* - * Egg - */ - -//for the status var -#define BURST 0 -#define BURSTING 1 -#define GROWING 2 -#define GROWN 3 -#define MIN_GROWTH_TIME 1800 //time it takes to grow a hugger -#define MAX_GROWTH_TIME 3000 - -/obj/structure/alien/egg - name = "egg" - desc = "A large mottled egg." - icon_state = "egg_growing" - density = FALSE - anchored = TRUE - max_integrity = 100 - integrity_failure = 5 - var/status = GROWING //can be GROWING, GROWN or BURST; all mutually exclusive - layer = MOB_LAYER - -/obj/structure/alien/egg/grown - status = GROWN - icon_state = "egg" - -/obj/structure/alien/egg/burst - status = BURST - icon_state = "egg_hatched" - -/obj/structure/alien/egg/New() - new /obj/item/clothing/mask/facehugger(src) - ..() - if(status == BURST) - obj_integrity = integrity_failure - else if(status != GROWN) - spawn(rand(MIN_GROWTH_TIME, MAX_GROWTH_TIME)) - Grow() - -/obj/structure/alien/egg/attack_alien(mob/living/carbon/alien/user) - return attack_hand(user) - -/obj/structure/alien/egg/attack_hand(mob/living/user) - if(user.get_int_organ(/obj/item/organ/internal/xenos/plasmavessel)) - switch(status) - if(BURST) - to_chat(user, "You clear the hatched egg.") - playsound(loc, 'sound/effects/attackblob.ogg', 100, 1) - qdel(src) - return - if(GROWING) - to_chat(user, "The child is not developed yet.") - return - if(GROWN) - to_chat(user, "You retrieve the child.") - Burst(0) - return - else - to_chat(user, "It feels slimy.") - user.changeNext_move(CLICK_CD_MELEE) - - -/obj/structure/alien/egg/proc/GetFacehugger() - return locate(/obj/item/clothing/mask/facehugger) in contents - -/obj/structure/alien/egg/proc/Grow() - icon_state = "egg" - status = GROWN - -/obj/structure/alien/egg/proc/Burst(kill = TRUE) //drops and kills the hugger if any is remaining - if(status == GROWN || status == GROWING) - icon_state = "egg_hatched" - flick("egg_opening", src) - status = BURSTING - spawn(15) - status = BURST - var/obj/item/clothing/mask/facehugger/child = GetFacehugger() - if(child) - child.loc = get_turf(src) - if(kill && istype(child)) - child.Die() - else - for(var/mob/M in range(1,src)) - if(CanHug(M)) - child.Attach(M) - break - -/obj/structure/alien/egg/obj_break(damage_flag) - if(!(flags & NODECONSTRUCT)) - if(status != BURST) - Burst(kill = TRUE) - -/obj/structure/alien/egg/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > 500) - take_damage(5, BURN, 0, 0) - -/obj/structure/alien/egg/HasProximity(atom/movable/AM) - if(status == GROWN) - if(!CanHug(AM)) - return - - var/mob/living/carbon/C = AM - if(C.stat == CONSCIOUS && C.get_int_organ(/obj/item/organ/internal/body_egg/alien_embryo)) - return - - Burst(0) - -#undef BURST -#undef BURSTING -#undef GROWING -#undef GROWN -#undef MIN_GROWTH_TIME -#undef MAX_GROWTH_TIME - -#undef WEED_NORTH_EDGING -#undef WEED_SOUTH_EDGING -#undef WEED_EAST_EDGING -#undef WEED_WEST_EDGING +/* Alien shit! + * Contains: + * structure/alien + * Resin + * Weeds + * Egg + */ + +#define WEED_NORTH_EDGING "north" +#define WEED_SOUTH_EDGING "south" +#define WEED_EAST_EDGING "east" +#define WEED_WEST_EDGING "west" + +/obj/structure/alien + icon = 'icons/mob/alien.dmi' + max_integrity = 100 + +/obj/structure/alien/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == "melee") + switch(damage_type) + if(BRUTE) + damage_amount *= 0.25 + if(BURN) + damage_amount *= 2 + . = ..() + +/obj/structure/alien/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(loc, 'sound/effects/attackblob.ogg', 100, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + if(damage_amount) + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + +/* + * Resin + */ +/obj/structure/alien/resin + name = "resin" + desc = "Looks like some kind of thick resin." + icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi' + icon_state = "resin" + density = TRUE + opacity = TRUE + anchored = TRUE + canSmoothWith = list(/obj/structure/alien/resin) + max_integrity = 200 + smooth = SMOOTH_TRUE + var/resintype = null + +/obj/structure/alien/resin/Initialize() + air_update_turf(1) + ..() + +/obj/structure/alien/resin/Destroy() + var/turf/T = get_turf(src) + . = ..() + T.air_update_turf(TRUE) + +/obj/structure/alien/resin/Move() + var/turf/T = loc + ..() + move_update_air(T) + +/obj/structure/alien/resin/CanAtmosPass() + return !density + +/obj/structure/alien/resin/wall + name = "resin wall" + desc = "Thick resin solidified into a wall." + icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi' + icon_state = "wall0" //same as resin, but consistency ho! + resintype = "wall" + canSmoothWith = list(/obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane) + +/obj/structure/alien/resin/wall/BlockSuperconductivity() + return 1 + +/obj/structure/alien/resin/wall/shadowling //For chrysalis + name = "chrysalis wall" + desc = "Some sort of purple substance in an egglike shape. It pulses and throbs from within and seems impenetrable." + max_integrity = INFINITY + +/obj/structure/alien/resin/membrane + name = "resin membrane" + desc = "Resin just thin enough to let light pass through." + icon = 'icons/obj/smooth_structures/alien/resin_membrane.dmi' + icon_state = "membrane0" + opacity = 0 + max_integrity = 160 + resintype = "membrane" + canSmoothWith = list(/obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane) + +/obj/structure/alien/resin/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && mover.checkpass(PASSGLASS)) + return !opacity + return !density + + +/* + * Weeds + */ + +#define NODERANGE 3 + +/obj/structure/alien/weeds + gender = PLURAL + name = "resin floor" + desc = "A thick resin surface covers the floor." + anchored = TRUE + density = FALSE + layer = TURF_LAYER + plane = FLOOR_PLANE + icon_state = "weeds" + max_integrity = 15 + var/obj/structure/alien/weeds/node/linked_node = null + var/static/list/weedImageCache + + +/obj/structure/alien/weeds/New(pos, node) + ..() + linked_node = node + if(istype(loc, /turf/space)) + qdel(src) + return + if(icon_state == "weeds") + icon_state = pick("weeds", "weeds1", "weeds2") + fullUpdateWeedOverlays() + spawn(rand(150, 200)) + if(src) + Life() + +/obj/structure/alien/weeds/Destroy() + var/turf/T = loc + for(var/obj/structure/alien/weeds/W in range(1,T)) + W.updateWeedOverlays() + linked_node = null + return ..() + +/obj/structure/alien/weeds/proc/Life() + set background = BACKGROUND_ENABLED + var/turf/U = get_turf(src) + + if(istype(U, /turf/space)) + qdel(src) + return + + if(!linked_node || get_dist(linked_node, src) > linked_node.node_range) + return + + for(var/turf/T in U.GetAtmosAdjacentTurfs()) + + if(locate(/obj/structure/alien/weeds) in T || istype(T, /turf/space)) + continue + + new /obj/structure/alien/weeds(T, linked_node) + +/obj/structure/alien/weeds/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > 300) + take_damage(5, BURN, 0, 0) + +/obj/structure/alien/weeds/proc/updateWeedOverlays() + + overlays.Cut() + + if(!weedImageCache || !weedImageCache.len) + weedImageCache = list() + weedImageCache.len = 4 + weedImageCache[WEED_NORTH_EDGING] = image('icons/mob/alien.dmi', "weeds_side_n", layer=2.11, pixel_y = -32) + weedImageCache[WEED_SOUTH_EDGING] = image('icons/mob/alien.dmi', "weeds_side_s", layer=2.11, pixel_y = 32) + weedImageCache[WEED_EAST_EDGING] = image('icons/mob/alien.dmi', "weeds_side_e", layer=2.11, pixel_x = -32) + weedImageCache[WEED_WEST_EDGING] = image('icons/mob/alien.dmi', "weeds_side_w", layer=2.11, pixel_x = 32) + + var/turf/N = get_step(src, NORTH) + var/turf/S = get_step(src, SOUTH) + var/turf/E = get_step(src, EAST) + var/turf/W = get_step(src, WEST) + if(!locate(/obj/structure/alien) in N.contents) + if(istype(N, /turf/simulated/floor)) + overlays += weedImageCache[WEED_SOUTH_EDGING] + if(!locate(/obj/structure/alien) in S.contents) + if(istype(S, /turf/simulated/floor)) + overlays += weedImageCache[WEED_NORTH_EDGING] + if(!locate(/obj/structure/alien) in E.contents) + if(istype(E, /turf/simulated/floor)) + overlays += weedImageCache[WEED_WEST_EDGING] + if(!locate(/obj/structure/alien) in W.contents) + if(istype(W, /turf/simulated/floor)) + overlays += weedImageCache[WEED_EAST_EDGING] + + +/obj/structure/alien/weeds/proc/fullUpdateWeedOverlays() + for(var/obj/structure/alien/weeds/W in range(1,src)) + W.updateWeedOverlays() + +//Weed nodes +/obj/structure/alien/weeds/node + name = "glowing resin" + desc = "Blue bioluminescence shines from beneath the surface." + icon_state = "weednode" + light_range = 1 + var/node_range = NODERANGE + + +/obj/structure/alien/weeds/node/New() + ..(loc, src) + +#undef NODERANGE + + +/* + * Egg + */ + +//for the status var +#define BURST 0 +#define BURSTING 1 +#define GROWING 2 +#define GROWN 3 +#define MIN_GROWTH_TIME 1800 //time it takes to grow a hugger +#define MAX_GROWTH_TIME 3000 + +/obj/structure/alien/egg + name = "egg" + desc = "A large mottled egg." + icon_state = "egg_growing" + density = FALSE + anchored = TRUE + max_integrity = 100 + integrity_failure = 5 + var/status = GROWING //can be GROWING, GROWN or BURST; all mutually exclusive + layer = MOB_LAYER + +/obj/structure/alien/egg/grown + status = GROWN + icon_state = "egg" + +/obj/structure/alien/egg/burst + status = BURST + icon_state = "egg_hatched" + +/obj/structure/alien/egg/New() + new /obj/item/clothing/mask/facehugger(src) + ..() + if(status == BURST) + obj_integrity = integrity_failure + else if(status != GROWN) + spawn(rand(MIN_GROWTH_TIME, MAX_GROWTH_TIME)) + Grow() + +/obj/structure/alien/egg/attack_alien(mob/living/carbon/alien/user) + return attack_hand(user) + +/obj/structure/alien/egg/attack_hand(mob/living/user) + if(user.get_int_organ(/obj/item/organ/internal/xenos/plasmavessel)) + switch(status) + if(BURST) + to_chat(user, "You clear the hatched egg.") + playsound(loc, 'sound/effects/attackblob.ogg', 100, 1) + qdel(src) + return + if(GROWING) + to_chat(user, "The child is not developed yet.") + return + if(GROWN) + to_chat(user, "You retrieve the child.") + Burst(0) + return + else + to_chat(user, "It feels slimy.") + user.changeNext_move(CLICK_CD_MELEE) + + +/obj/structure/alien/egg/proc/GetFacehugger() + return locate(/obj/item/clothing/mask/facehugger) in contents + +/obj/structure/alien/egg/proc/Grow() + icon_state = "egg" + status = GROWN + +/obj/structure/alien/egg/proc/Burst(kill = TRUE) //drops and kills the hugger if any is remaining + if(status == GROWN || status == GROWING) + icon_state = "egg_hatched" + flick("egg_opening", src) + status = BURSTING + spawn(15) + status = BURST + var/obj/item/clothing/mask/facehugger/child = GetFacehugger() + if(child) + child.loc = get_turf(src) + if(kill && istype(child)) + child.Die() + else + for(var/mob/M in range(1,src)) + if(CanHug(M)) + child.Attach(M) + break + +/obj/structure/alien/egg/obj_break(damage_flag) + if(!(flags & NODECONSTRUCT)) + if(status != BURST) + Burst(kill = TRUE) + +/obj/structure/alien/egg/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > 500) + take_damage(5, BURN, 0, 0) + +/obj/structure/alien/egg/HasProximity(atom/movable/AM) + if(status == GROWN) + if(!CanHug(AM)) + return + + var/mob/living/carbon/C = AM + if(C.stat == CONSCIOUS && C.get_int_organ(/obj/item/organ/internal/body_egg/alien_embryo)) + return + + Burst(0) + +#undef BURST +#undef BURSTING +#undef GROWING +#undef GROWN +#undef MIN_GROWTH_TIME +#undef MAX_GROWTH_TIME + +#undef WEED_NORTH_EDGING +#undef WEED_SOUTH_EDGING +#undef WEED_EAST_EDGING +#undef WEED_WEST_EDGING diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm index 8945819e3aa..9c517f39c69 100644 --- a/code/game/objects/structures/bedsheet_bin.dm +++ b/code/game/objects/structures/bedsheet_bin.dm @@ -1,332 +1,332 @@ -/* -CONTAINS: -BEDSHEETS -LINEN BINS -*/ - -/obj/item/bedsheet - name = "bedsheet" - desc = "A surprisingly soft linen bedsheet." - icon = 'icons/obj/items.dmi' - icon_state = "sheet" - item_state = "bedsheet" - layer = 4.0 - throwforce = 1 - throw_speed = 1 - throw_range = 2 - w_class = WEIGHT_CLASS_TINY - item_color = "white" - resistance_flags = FLAMMABLE - slot_flags = SLOT_BACK - - dog_fashion = /datum/dog_fashion/head/ghost - var/list/dream_messages = list("white") - var/list/nightmare_messages = list("black") - var/comfort = 0.5 - - - -/obj/item/bedsheet/attack_self(mob/user as mob) - user.drop_item() - if(layer == initial(layer)) - layer = 5 - else - layer = initial(layer) - add_fingerprint(user) - return - -/obj/item/bedsheet/attackby(obj/item/I, mob/user, params) - if(I.sharp) - var/obj/item/stack/sheet/cloth/C = new (get_turf(src), 3) - transfer_fingerprints_to(C) - C.add_fingerprint(user) - qdel(src) - to_chat(user, "You tear [src] up.") - else - return ..() - -/obj/item/bedsheet/blue - icon_state = "sheetblue" - item_color = "blue" - dream_messages = list("blue") - nightmare_messages = list("vox blood") - -/obj/item/bedsheet/green - icon_state = "sheetgreen" - item_color = "green" - dream_messages = list("green") - nightmare_messages = list("unathi flesh") - -/obj/item/bedsheet/orange - icon_state = "sheetorange" - item_color = "orange" - dream_messages = list("orange") - nightmare_messages = list("exploding fruit") - -/obj/item/bedsheet/purple - icon_state = "sheetpurple" - item_color = "purple" - dream_messages = list("purple") - nightmare_messages = list("Grey blood") - -/obj/item/bedsheet/patriot - name = "patriotic bedsheet" - desc = "You've never felt more free than when sleeping on this." - icon_state = "sheetUSA" - item_color = "sheetUSA" - dream_messages = list("America", "freedom", "fireworks", "bald eagles") - nightmare_messages = list("communism") - -/obj/item/bedsheet/rainbow - name = "rainbow bedsheet" - desc = "A multi_colored blanket. It's actually several different sheets cut up and sewn together." - icon_state = "sheetrainbow" - item_color = "rainbow" - dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow") - nightmare_messages = list("green", "a cluwne", "fLoOr ClUwNe") - -/obj/item/bedsheet/red - icon_state = "sheetred" - item_color = "red" - dream_messages = list("red") - nightmare_messages = list("gibs") - -/obj/item/bedsheet/yellow - icon_state = "sheetyellow" - item_color = "yellow" - dream_messages = list("yellow") - nightmare_messages = list("locker full of banana peels") - -/obj/item/bedsheet/black - icon_state = "sheetblack" - item_color = "sheetblack" - dream_messages = list("black") - nightmare_messages = list("the void of space") - -/obj/item/bedsheet/mime - name = "mime's blanket" - desc = "A very soothing striped blanket. All the noise just seems to fade out when you're under the covers in this." - icon_state = "sheetmime" - item_color = "mime" - dream_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime") - nightmare_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown") - -/obj/item/bedsheet/clown - name = "clown's blanket" - desc = "A rainbow blanket with a clown mask woven in. It smells faintly of bananas." - icon_state = "sheetclown" - item_color = "clown" - dream_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown") - nightmare_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime") - -/obj/item/bedsheet/captain - name = "captain's bedsheet." - desc = "It has a Nanotrasen symbol on it, and was woven with a revolutionary new kind of thread guaranteed to have 0.01% permeability for most non-chemical substances, popular among most modern captains." - icon_state = "sheetcaptain" - item_color = "captain" - dream_messages = list("authority", "a golden ID", "sunglasses", "a green disc", "an antique gun", "the captain") - nightmare_messages = list("comdom", "clown with all access", "the syndicate") - -/obj/item/bedsheet/rd - name = "research director's bedsheet" - desc = "It appears to have a beaker emblem, and is made out of fire-resistant material, although it probably won't protect you in the event of fires you're familiar with every day." - icon_state = "sheetrd" - item_color = "director" - dream_messages = list("authority", "a silvery ID", "a bomb", "a mech", "a facehugger", "maniacal laughter", "the research director") - nightmare_messages = list("toxins full of plasma", "UPGRADE THE SLEEPERS", "rogue ai") - -/obj/item/bedsheet/rd/royal_cape - name = "Royal Cape of the Liberator" - desc = "Majestic." - dream_messages = list("mining", "stone", "a golem", "freedom", "doing whatever") - -/obj/item/bedsheet/medical - name = "medical blanket" - desc = "It's a sterilized* blanket commonly used in the Medbay. *Sterilization is voided if a virologist is present onboard the station." - icon_state = "sheetmedical" - item_color = "medical" - dream_messages = list("healing", "life", "surgery", "a doctor") - nightmare_messages = list("death", "no cryox", "cryo is off") - -/obj/item/bedsheet/cmo - name = "chief medical officer's bedsheet" - desc = "It's a sterilized blanket that has a cross emblem. There's some cat fur on it, likely from Runtime." - icon_state = "sheetcmo" - item_color = "cmo" - dream_messages = list("authority", "a silvery ID", "healing", "life", "surgery", "a cat", "the chief medical officer") - nightmare_messages = list("chemists making meth", "cryo it off", "where is the defib", "no biomass") - -/obj/item/bedsheet/hos - name = "head of security's bedsheet" - desc = "It is decorated with a shield emblem. While crime doesn't sleep, you do, but you are still THE LAW!" - icon_state = "sheethos" - item_color = "hosred" - dream_messages = list("authority", "a silvery ID", "handcuffs", "a baton", "a flashbang", "sunglasses", "the head of security") - nightmare_messages = list("the clown", "a toolbox", "sHiTcUrItY", "why did you put them in for 50 minutes") - - -/obj/item/bedsheet/hop - name = "head of personnel's bedsheet" - desc = "It is decorated with a key emblem. For those rare moments when you can rest and cuddle with Ian without someone screaming for you over the radio." - icon_state = "sheethop" - item_color = "hop" - dream_messages = list("authority", "a silvery ID", "obligation", "a computer", "an ID", "a corgi", "the head of personnel") - nightmare_messages = list("improper paperwork", "all access clown", "50 open clown slots", "dead ian") - -/obj/item/bedsheet/ce - name = "chief engineer's bedsheet" - desc = "It is decorated with a wrench emblem. It's highly reflective and stain resistant, so you don't need to worry about ruining it with oil." - icon_state = "sheetce" - item_color = "chief" - dream_messages = list("authority", "a silvery ID", "the engine", "power tools", "an APC", "a parrot", "the chief engineer") - nightmare_messages = list("forced airlock", "syndicate bomb", "explosion in research chem", "iT's LoOsE") - -/obj/item/bedsheet/qm - name = "quartermaster's bedsheet" - desc = "It is decorated with a crate emblem in silver lining. It's rather tough, and just the thing to lie on after a hard day of pushing paper." - icon_state = "sheetqm" - item_color = "qm" - dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster") - nightmare_messages = list("a bald person", "no points", "wheres the ore", "space carp") - -/obj/item/bedsheet/brown - icon_state = "sheetbrown" - item_color = "cargo" - dream_messages = list("brown") - nightmare_messages = list("dead monkey") - -/obj/item/bedsheet/centcom - name = "centcom bedsheet" - desc = "Woven with advanced nanothread for warmth as well as being very decorated, essential for all officials." - icon_state = "sheetcentcom" - item_color = "centcom" - dream_messages = list("a unique ID", "authority", "artillery", "an ending") - nightmare_messages = list("a butt fax") - -/obj/item/bedsheet/syndie - name = "syndicate bedsheet" - desc = "It has a syndicate emblem and it has an aura of evil." - icon_state = "sheetsyndie" - item_color = "syndie" - dream_messages = list("a green disc", "a red crystal", "a glowing blade", "a wire-covered ID") - nightmare_messages = list("stunbaton", "taser", "lasers", "a toolbox") - -/obj/item/bedsheet/cult - name = "cultist's bedsheet" - desc = "You might dream of Nar'Sie if you sleep with this. It seems rather tattered and glows of an eldritch presence." - icon_state = "sheetcult" - item_color = "cult" - dream_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure") - nightmare_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure") - - -/obj/item/bedsheet/wiz - name = "wizard's bedsheet" - desc = "A special fabric enchanted with magic so you can have an enchanted night. It even glows!" - icon_state = "sheetwiz" - item_color = "wiz" - dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic") - nightmare_messages = list("a toolbox", "solars") - - - -/obj/structure/bedsheetbin - name = "linen bin" - desc = "A linen bin. It looks rather cosy." - icon = 'icons/obj/structures.dmi' - icon_state = "linenbin-full" - anchored = 1 - resistance_flags = FLAMMABLE - max_integrity = 70 - var/amount = 20 - var/list/sheets = list() - var/obj/item/hidden = null - - -/obj/structure/bedsheetbin/examine(mob/user) - . = ..() - if(amount < 1) - . += "There are no bed sheets in the bin." - else if(amount == 1) - . += "There is one bed sheet in the bin." - else - . += "There are [amount] bed sheets in the bin." - - -/obj/structure/bedsheetbin/update_icon() - switch(amount) - if(0) icon_state = "linenbin-empty" - if(1 to amount / 2) icon_state = "linenbin-half" - else icon_state = "linenbin-full" - - -/obj/structure/bedsheetbin/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - if(amount) - amount = 0 - update_icon() - ..() - -/obj/structure/bedsheetbin/burn() - amount = 0 - extinguish() - update_icon() - -/obj/structure/bedsheetbin/attackby(obj/item/I as obj, mob/user as mob, params) - if(istype(I, /obj/item/bedsheet)) - user.drop_item() - I.loc = src - sheets.Add(I) - amount++ - to_chat(user, "You put [I] in [src].") - else if(amount && !hidden && I.w_class < WEIGHT_CLASS_BULKY) //make sure there's sheets to hide it among, make sure nothing else is hidden in there. - user.drop_item() - I.loc = src - hidden = I - to_chat(user, "You hide [I] among the sheets.") - - - -/obj/structure/bedsheetbin/attack_hand(mob/user as mob) - if(amount >= 1) - amount-- - - var/obj/item/bedsheet/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - B = new /obj/item/bedsheet(loc) - - B.loc = user.loc - user.put_in_hands(B) - to_chat(user, "You take [B] out of [src].") - - if(hidden) - hidden.loc = user.loc - to_chat(user, "[hidden] falls out of [B]!") - hidden = null - - - add_fingerprint(user) - - -/obj/structure/bedsheetbin/attack_tk(mob/user as mob) - if(amount >= 1) - amount-- - - var/obj/item/bedsheet/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - B = new /obj/item/bedsheet(loc) - - B.loc = loc - to_chat(user, "You telekinetically remove [B] from [src].") - update_icon() - - if(hidden) - hidden.loc = loc - hidden = null +/* +CONTAINS: +BEDSHEETS +LINEN BINS +*/ + +/obj/item/bedsheet + name = "bedsheet" + desc = "A surprisingly soft linen bedsheet." + icon = 'icons/obj/items.dmi' + icon_state = "sheet" + item_state = "bedsheet" + layer = 4.0 + throwforce = 1 + throw_speed = 1 + throw_range = 2 + w_class = WEIGHT_CLASS_TINY + item_color = "white" + resistance_flags = FLAMMABLE + slot_flags = SLOT_BACK + + dog_fashion = /datum/dog_fashion/head/ghost + var/list/dream_messages = list("white") + var/list/nightmare_messages = list("black") + var/comfort = 0.5 + + + +/obj/item/bedsheet/attack_self(mob/user as mob) + user.drop_item() + if(layer == initial(layer)) + layer = 5 + else + layer = initial(layer) + add_fingerprint(user) + return + +/obj/item/bedsheet/attackby(obj/item/I, mob/user, params) + if(I.sharp) + var/obj/item/stack/sheet/cloth/C = new (get_turf(src), 3) + transfer_fingerprints_to(C) + C.add_fingerprint(user) + qdel(src) + to_chat(user, "You tear [src] up.") + else + return ..() + +/obj/item/bedsheet/blue + icon_state = "sheetblue" + item_color = "blue" + dream_messages = list("blue") + nightmare_messages = list("vox blood") + +/obj/item/bedsheet/green + icon_state = "sheetgreen" + item_color = "green" + dream_messages = list("green") + nightmare_messages = list("unathi flesh") + +/obj/item/bedsheet/orange + icon_state = "sheetorange" + item_color = "orange" + dream_messages = list("orange") + nightmare_messages = list("exploding fruit") + +/obj/item/bedsheet/purple + icon_state = "sheetpurple" + item_color = "purple" + dream_messages = list("purple") + nightmare_messages = list("Grey blood") + +/obj/item/bedsheet/patriot + name = "patriotic bedsheet" + desc = "You've never felt more free than when sleeping on this." + icon_state = "sheetUSA" + item_color = "sheetUSA" + dream_messages = list("America", "freedom", "fireworks", "bald eagles") + nightmare_messages = list("communism") + +/obj/item/bedsheet/rainbow + name = "rainbow bedsheet" + desc = "A multi_colored blanket. It's actually several different sheets cut up and sewn together." + icon_state = "sheetrainbow" + item_color = "rainbow" + dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow") + nightmare_messages = list("green", "a cluwne", "fLoOr ClUwNe") + +/obj/item/bedsheet/red + icon_state = "sheetred" + item_color = "red" + dream_messages = list("red") + nightmare_messages = list("gibs") + +/obj/item/bedsheet/yellow + icon_state = "sheetyellow" + item_color = "yellow" + dream_messages = list("yellow") + nightmare_messages = list("locker full of banana peels") + +/obj/item/bedsheet/black + icon_state = "sheetblack" + item_color = "sheetblack" + dream_messages = list("black") + nightmare_messages = list("the void of space") + +/obj/item/bedsheet/mime + name = "mime's blanket" + desc = "A very soothing striped blanket. All the noise just seems to fade out when you're under the covers in this." + icon_state = "sheetmime" + item_color = "mime" + dream_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime") + nightmare_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown") + +/obj/item/bedsheet/clown + name = "clown's blanket" + desc = "A rainbow blanket with a clown mask woven in. It smells faintly of bananas." + icon_state = "sheetclown" + item_color = "clown" + dream_messages = list("honk", "laughter", "a prank", "a joke", "a smiling face", "the clown") + nightmare_messages = list("silence", "gestures", "a pale face", "a gaping mouth", "the mime") + +/obj/item/bedsheet/captain + name = "captain's bedsheet." + desc = "It has a Nanotrasen symbol on it, and was woven with a revolutionary new kind of thread guaranteed to have 0.01% permeability for most non-chemical substances, popular among most modern captains." + icon_state = "sheetcaptain" + item_color = "captain" + dream_messages = list("authority", "a golden ID", "sunglasses", "a green disc", "an antique gun", "the captain") + nightmare_messages = list("comdom", "clown with all access", "the syndicate") + +/obj/item/bedsheet/rd + name = "research director's bedsheet" + desc = "It appears to have a beaker emblem, and is made out of fire-resistant material, although it probably won't protect you in the event of fires you're familiar with every day." + icon_state = "sheetrd" + item_color = "director" + dream_messages = list("authority", "a silvery ID", "a bomb", "a mech", "a facehugger", "maniacal laughter", "the research director") + nightmare_messages = list("toxins full of plasma", "UPGRADE THE SLEEPERS", "rogue ai") + +/obj/item/bedsheet/rd/royal_cape + name = "Royal Cape of the Liberator" + desc = "Majestic." + dream_messages = list("mining", "stone", "a golem", "freedom", "doing whatever") + +/obj/item/bedsheet/medical + name = "medical blanket" + desc = "It's a sterilized* blanket commonly used in the Medbay. *Sterilization is voided if a virologist is present onboard the station." + icon_state = "sheetmedical" + item_color = "medical" + dream_messages = list("healing", "life", "surgery", "a doctor") + nightmare_messages = list("death", "no cryox", "cryo is off") + +/obj/item/bedsheet/cmo + name = "chief medical officer's bedsheet" + desc = "It's a sterilized blanket that has a cross emblem. There's some cat fur on it, likely from Runtime." + icon_state = "sheetcmo" + item_color = "cmo" + dream_messages = list("authority", "a silvery ID", "healing", "life", "surgery", "a cat", "the chief medical officer") + nightmare_messages = list("chemists making meth", "cryo it off", "where is the defib", "no biomass") + +/obj/item/bedsheet/hos + name = "head of security's bedsheet" + desc = "It is decorated with a shield emblem. While crime doesn't sleep, you do, but you are still THE LAW!" + icon_state = "sheethos" + item_color = "hosred" + dream_messages = list("authority", "a silvery ID", "handcuffs", "a baton", "a flashbang", "sunglasses", "the head of security") + nightmare_messages = list("the clown", "a toolbox", "sHiTcUrItY", "why did you put them in for 50 minutes") + + +/obj/item/bedsheet/hop + name = "head of personnel's bedsheet" + desc = "It is decorated with a key emblem. For those rare moments when you can rest and cuddle with Ian without someone screaming for you over the radio." + icon_state = "sheethop" + item_color = "hop" + dream_messages = list("authority", "a silvery ID", "obligation", "a computer", "an ID", "a corgi", "the head of personnel") + nightmare_messages = list("improper paperwork", "all access clown", "50 open clown slots", "dead ian") + +/obj/item/bedsheet/ce + name = "chief engineer's bedsheet" + desc = "It is decorated with a wrench emblem. It's highly reflective and stain resistant, so you don't need to worry about ruining it with oil." + icon_state = "sheetce" + item_color = "chief" + dream_messages = list("authority", "a silvery ID", "the engine", "power tools", "an APC", "a parrot", "the chief engineer") + nightmare_messages = list("forced airlock", "syndicate bomb", "explosion in research chem", "iT's LoOsE") + +/obj/item/bedsheet/qm + name = "quartermaster's bedsheet" + desc = "It is decorated with a crate emblem in silver lining. It's rather tough, and just the thing to lie on after a hard day of pushing paper." + icon_state = "sheetqm" + item_color = "qm" + dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster") + nightmare_messages = list("a bald person", "no points", "wheres the ore", "space carp") + +/obj/item/bedsheet/brown + icon_state = "sheetbrown" + item_color = "cargo" + dream_messages = list("brown") + nightmare_messages = list("dead monkey") + +/obj/item/bedsheet/centcom + name = "centcom bedsheet" + desc = "Woven with advanced nanothread for warmth as well as being very decorated, essential for all officials." + icon_state = "sheetcentcom" + item_color = "centcom" + dream_messages = list("a unique ID", "authority", "artillery", "an ending") + nightmare_messages = list("a butt fax") + +/obj/item/bedsheet/syndie + name = "syndicate bedsheet" + desc = "It has a syndicate emblem and it has an aura of evil." + icon_state = "sheetsyndie" + item_color = "syndie" + dream_messages = list("a green disc", "a red crystal", "a glowing blade", "a wire-covered ID") + nightmare_messages = list("stunbaton", "taser", "lasers", "a toolbox") + +/obj/item/bedsheet/cult + name = "cultist's bedsheet" + desc = "You might dream of Nar'Sie if you sleep with this. It seems rather tattered and glows of an eldritch presence." + icon_state = "sheetcult" + item_color = "cult" + dream_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure") + nightmare_messages = list("a tome", "a floating red crystal", "a glowing sword", "a bloody symbol", "a massive humanoid figure") + + +/obj/item/bedsheet/wiz + name = "wizard's bedsheet" + desc = "A special fabric enchanted with magic so you can have an enchanted night. It even glows!" + icon_state = "sheetwiz" + item_color = "wiz" + dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic") + nightmare_messages = list("a toolbox", "solars") + + + +/obj/structure/bedsheetbin + name = "linen bin" + desc = "A linen bin. It looks rather cosy." + icon = 'icons/obj/structures.dmi' + icon_state = "linenbin-full" + anchored = 1 + resistance_flags = FLAMMABLE + max_integrity = 70 + var/amount = 20 + var/list/sheets = list() + var/obj/item/hidden = null + + +/obj/structure/bedsheetbin/examine(mob/user) + . = ..() + if(amount < 1) + . += "There are no bed sheets in the bin." + else if(amount == 1) + . += "There is one bed sheet in the bin." + else + . += "There are [amount] bed sheets in the bin." + + +/obj/structure/bedsheetbin/update_icon() + switch(amount) + if(0) icon_state = "linenbin-empty" + if(1 to amount / 2) icon_state = "linenbin-half" + else icon_state = "linenbin-full" + + +/obj/structure/bedsheetbin/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + if(amount) + amount = 0 + update_icon() + ..() + +/obj/structure/bedsheetbin/burn() + amount = 0 + extinguish() + update_icon() + +/obj/structure/bedsheetbin/attackby(obj/item/I as obj, mob/user as mob, params) + if(istype(I, /obj/item/bedsheet)) + user.drop_item() + I.loc = src + sheets.Add(I) + amount++ + to_chat(user, "You put [I] in [src].") + else if(amount && !hidden && I.w_class < WEIGHT_CLASS_BULKY) //make sure there's sheets to hide it among, make sure nothing else is hidden in there. + user.drop_item() + I.loc = src + hidden = I + to_chat(user, "You hide [I] among the sheets.") + + + +/obj/structure/bedsheetbin/attack_hand(mob/user as mob) + if(amount >= 1) + amount-- + + var/obj/item/bedsheet/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + B = new /obj/item/bedsheet(loc) + + B.loc = user.loc + user.put_in_hands(B) + to_chat(user, "You take [B] out of [src].") + + if(hidden) + hidden.loc = user.loc + to_chat(user, "[hidden] falls out of [B]!") + hidden = null + + + add_fingerprint(user) + + +/obj/structure/bedsheetbin/attack_tk(mob/user as mob) + if(amount >= 1) + amount-- + + var/obj/item/bedsheet/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + B = new /obj/item/bedsheet(loc) + + B.loc = loc + to_chat(user, "You telekinetically remove [B] from [src].") + update_icon() + + if(hidden) + hidden.loc = loc + hidden = null diff --git a/code/game/objects/structures/coathanger.dm b/code/game/objects/structures/coathanger.dm index 5a0a47150ca..59bd99a7e15 100644 --- a/code/game/objects/structures/coathanger.dm +++ b/code/game/objects/structures/coathanger.dm @@ -49,4 +49,4 @@ if(istype(coat, /obj/item/clothing/suit/storage/labcoat/cmo)) overlays += image(icon, icon_state = "coat_cmo") if(istype(coat, /obj/item/clothing/suit/storage/det_suit)) - overlays += image(icon, icon_state = "coat_det") \ No newline at end of file + overlays += image(icon, icon_state = "coat_det") diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 8c31d1cdbfe..82487dbd0ae 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1,451 +1,451 @@ -/obj/structure/closet - name = "closet" - desc = "It's a basic storage unit." - icon = 'icons/obj/closet.dmi' - icon_state = "closed" - density = 1 - max_integrity = 200 - integrity_failure = 50 - armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) - var/icon_closed = "closed" - var/icon_opened = "open" - var/opened = FALSE - var/welded = FALSE - var/locked = FALSE - var/wall_mounted = 0 //never solid (You can always pass over it) - var/lastbang - var/sound = 'sound/machines/click.ogg' - var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients. - var/material_drop = /obj/item/stack/sheet/metal - var/material_drop_amount = 2 - -/obj/structure/closet/New() - ..() - spawn(1) - if(!opened) // if closed, any item at the crate's loc is put in the contents - for(var/obj/item/I in loc) - if(I.density || I.anchored || I == src) continue - I.forceMove(src) - -// Fix for #383 - C4 deleting fridges with corpses -/obj/structure/closet/Destroy() - dump_contents() - return ..() - -/obj/structure/closet/CanPass(atom/movable/mover, turf/target, height=0) - if(height==0 || wall_mounted) - return TRUE - return (!density) - -/obj/structure/closet/proc/can_open() - if(welded) - return FALSE - return TRUE - -/obj/structure/closet/proc/can_close() - for(var/obj/structure/closet/closet in get_turf(src)) - if(closet != src && closet.anchored != 1) - return FALSE - - return TRUE - -/obj/structure/closet/proc/dump_contents() - var/turf/T = get_turf(src) - for(var/atom/movable/AM in src) - AM.forceMove(T) - if(throwing) // you keep some momentum when getting out of a thrown closet - step(AM, dir) - if(throwing) - throwing.finalize(FALSE) - -/obj/structure/closet/proc/open() - if(opened) - return FALSE - - if(!can_open()) - return FALSE - - dump_contents() - - icon_state = icon_opened - opened = TRUE - if(sound) - playsound(loc, sound, 15, 1, -3) - else - playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) - density = 0 - return TRUE - -/obj/structure/closet/proc/close() - if(!opened) - return FALSE - if(!can_close()) - return FALSE - - var/itemcount = 0 - - //Cham Projector Exception - for(var/obj/effect/dummy/chameleon/AD in loc) - if(itemcount >= storage_capacity) - break - AD.forceMove(src) - itemcount++ - - for(var/obj/item/I in loc) - if(itemcount >= storage_capacity) - break - if(!I.anchored) - I.forceMove(src) - itemcount++ - - for(var/mob/M in loc) - if(itemcount >= storage_capacity) - break - if(istype(M, /mob/dead/observer)) - continue - if(istype(M, /mob/living/simple_animal/bot/mulebot)) - continue - if(M.buckled || M.anchored || M.has_buckled_mobs()) - continue - if(isAI(M)) - continue - - M.forceMove(src) - itemcount++ - - icon_state = icon_closed - opened = FALSE - if(sound) - playsound(loc, sound, 15, 1, -3) - else - playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) - density = 1 - return TRUE - -/obj/structure/closet/proc/toggle(mob/user) - if(!(opened ? close() : open())) - to_chat(user, "It won't budge!") - -/obj/structure/closet/proc/bust_open() - welded = FALSE //applies to all lockers - locked = FALSE //applies to critter crates and secure lockers only - broken = TRUE //applies to secure lockers only - open() - -/obj/structure/closet/deconstruct(disassembled = TRUE) - if(ispath(material_drop) && material_drop_amount && !(flags & NODECONSTRUCT)) - new material_drop(loc, material_drop_amount) - qdel(src) - -/obj/structure/closet/obj_break(damage_flag) - if(!broken && !(flags & NODECONSTRUCT)) - bust_open() - -/obj/structure/closet/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/rcs) && !opened) - if(user in contents) //to prevent self-teleporting. - return - var/obj/item/rcs/E = W - if(E.rcell && (E.rcell.charge >= E.chargecost)) - if(!is_level_reachable(z)) - to_chat(user, "The rapid-crate-sender can't locate any telepads!") - return - if(E.mode == 0) - if(!E.teleporting) - var/list/L = list() - var/list/areaindex = list() - for(var/obj/machinery/telepad_cargo/R in world) - if(R.stage == 0) - var/turf/T = get_turf(R) - var/tmpname = T.loc.name - if(areaindex[tmpname]) - tmpname = "[tmpname] ([++areaindex[tmpname]])" - else - areaindex[tmpname] = 1 - L[tmpname] = R - var/desc = input("Please select a telepad.", "RCS") in L - E.pad = L[desc] - if(!Adjacent(user)) - to_chat(user, "Unable to teleport, too far from crate.") - return - playsound(E.loc, E.usesound, 50, 1) - to_chat(user, "Teleporting [name]...") - E.teleporting = 1 - if(!do_after(user, 50 * E.toolspeed, target = src)) - E.teleporting = 0 - return - E.teleporting = 0 - if(user in contents) - to_chat(user, "Error: User located in container--aborting for safety.") - playsound(E.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) - return - if(!(E.rcell && E.rcell.use(E.chargecost))) - to_chat(user, "Unable to teleport, insufficient charge.") - return - do_sparks(5, 1, src) - do_teleport(src, E.pad, 0) - to_chat(user, "Teleport successful. [round(E.rcell.charge/E.chargecost)] charge\s left.") - return - else - E.rand_x = rand(50,200) - E.rand_y = rand(50,200) - var/L = locate(E.rand_x, E.rand_y, 6) - if(!Adjacent(user)) - to_chat(user, "Unable to teleport, too far from crate.") - return - playsound(E.loc, E.usesound, 50, 1) - to_chat(user, "Teleporting [name]...") - E.teleporting = 1 - if(!do_after(user, 50, E.toolspeed, target = src)) - E.teleporting = 0 - return - E.teleporting = 0 - if(user in contents) - to_chat(user, "Error: User located in container--aborting for safety.") - playsound(E.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) - return - if(!(E.rcell && E.rcell.use(E.chargecost))) - to_chat(user, "Unable to teleport, insufficient charge.") - return - do_sparks(5, 1, src) - do_teleport(src, L) - to_chat(user, "Teleport successful. [round(E.rcell.charge/E.chargecost)] charge\s left.") - return - else - to_chat(user, "Out of charges.") - return - return - - if(opened) - if(istype(W, /obj/item/grab)) - MouseDrop_T(W:affecting, user) //act like they were dragged onto the closet - if(istype(W,/obj/item/tk_grab)) - return FALSE - if(isrobot(user)) - return - if(!user.drop_item()) //couldn't drop the item - to_chat(user, "\The [W] is stuck to your hand, you cannot put it in \the [src]!") - return - if(W) - W.forceMove(loc) - else if(istype(W, /obj/item/stack/packageWrap)) - return - else if(user.a_intent != INTENT_HARM) - attack_hand(user) - else - return ..() - -/obj/structure/closet/welder_act(mob/user, obj/item/I) - . = TRUE - if(!opened && user.loc == src) - to_chat(user, "You can't weld [src] from inside!") - return - if(!I.tool_use_check(user, 0)) - return - if(opened) - WELDER_ATTEMPT_SLICING_MESSAGE - if(I.use_tool(src, user, 40, volume = I.tool_volume)) - WELDER_SLICING_SUCCESS_MESSAGE - deconstruct(TRUE) - return - else - var/adjective = welded ? "open" : "shut" - user.visible_message("[user] begins welding [src] [adjective]...", "You begin welding [src] [adjective]...", "You hear welding.") - if(I.use_tool(src, user, 15, volume = I.tool_volume)) - if(opened) - to_chat(user, "Keep [src] shut while doing that!") - return - user.visible_message("[user] welds [src] [adjective]!", "You weld [src] [adjective]!") - welded = !welded - update_icon() - return - -/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/user) - ..() - if(istype(O, /obj/screen)) //fix for HUD elements making their way into the world -Pete - return - if(O.loc == user) - return - if(user.restrained() || user.stat || user.IsWeakened() || user.stunned || user.paralysis || user.lying) - return - if((!( istype(O, /atom/movable) ) || O.anchored || get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src))) - return - if(user.loc==null) // just in case someone manages to get a closet into the blue light dimension, as unlikely as that seems - return - if(!istype(user.loc, /turf)) // are you in a container/closet/pod/etc? - return - if(!opened) - return - if(istype(O, /obj/structure/closet)) - return - step_towards(O, loc) - if(user != O) - user.visible_message("[user] stuffs [O] into [src]!", "You stuff [O] into [src]!") - add_fingerprint(user) - -/obj/structure/closet/attack_ai(mob/user) - if(isrobot(user) && Adjacent(user)) //Robots can open/close it, but not the AI - attack_hand(user) - -/obj/structure/closet/relaymove(mob/user) - if(user.stat || !isturf(loc)) - return - - if(!open()) - to_chat(user, "It won't budge!") - if(!lastbang) - lastbang = 1 - for(var/mob/M in hearers(src, null)) - to_chat(M, text("BANG, bang!", max(0, 5 - get_dist(src, M)))) - spawn(30) - lastbang = 0 - -/obj/structure/closet/attack_hand(mob/user) - add_fingerprint(user) - toggle(user) - -/obj/structure/closet/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - toggle(user) - -// tk grab then use on self -/obj/structure/closet/attack_self_tk(mob/user) - add_fingerprint(user) - if(!toggle()) - to_chat(usr, "It won't budge!") - -/obj/structure/closet/verb/verb_toggleopen() - set src in oview(1) - set category = null - set name = "Toggle Open" - - if(usr.incapacitated()) - return - - if(ishuman(usr)) - add_fingerprint(usr) - toggle(usr) - else - to_chat(usr, "This mob type can't use this verb.") - -/obj/structure/closet/update_icon()//Putting the welded stuff in updateicon() so it's easy to overwrite for special cases (Fridges, cabinets, and whatnot) - overlays.Cut() - if(!opened) - icon_state = icon_closed - if(welded) - overlays += "welded" - else - icon_state = icon_opened - -// Objects that try to exit a locker by stepping were doing so successfully, -// and due to an oversight in turf/Enter() were going through walls. That -// should be independently resolved, but this is also an interesting twist. -/obj/structure/closet/Exit(atom/movable/AM) - open() - if(AM.loc == src) - return FALSE - return TRUE - -/obj/structure/closet/container_resist(var/mob/living/L) - var/breakout_time = 2 //2 minutes by default - if(opened) - if(L.loc == src) - L.forceMove(get_turf(src)) // Let's just be safe here - return //Door's open... wait, why are you in it's contents then? - if(!welded) - open() //for cardboard boxes - return //closed but not welded... - // else Meh, lets just keep it at 2 minutes for now - // breakout_time++ //Harder to get out of welded lockers than locked lockers - - //okay, so the closet is either welded or locked... resist!!! - L.changeNext_move(CLICK_CD_BREAKOUT) - L.last_special = world.time + CLICK_CD_BREAKOUT - to_chat(L, "You lean on the back of \the [src] and start pushing the door open. (this will take about [breakout_time] minutes)") - for(var/mob/O in viewers(usr.loc)) - O.show_message("The [src] begins to shake violently!", 1) - - - spawn(0) - if(do_after(L,(breakout_time*60*10), target = src)) //minutes * 60seconds * 10deciseconds - if(!src || !L || L.stat != CONSCIOUS || L.loc != src || opened) //closet/user destroyed OR user dead/unconcious OR user no longer in closet OR closet opened - return - - //Perform the same set of checks as above for weld and lock status to determine if there is even still a point in 'resisting'... - if(!welded) - return - - //Well then break it! - welded = FALSE - update_icon() - to_chat(usr, "You successfully break out!") - for(var/mob/O in viewers(L.loc)) - O.show_message("\the [usr] successfully broke out of \the [src]!", 1) - if(istype(loc, /obj/structure/bigDelivery)) //nullspace ect.. read the comment above - var/obj/structure/bigDelivery/BD = loc - BD.attack_hand(usr) - open() - -/obj/structure/closet/tesla_act(var/power) - ..() - visible_message("[src] is blown apart by the bolt of electricity!", "You hear a metallic screeching sound.") - qdel(src) - -/obj/structure/closet/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/structure/closet/ex_act(severity) - for(var/atom/A in contents) - A.ex_act(severity) - CHECK_TICK - ..() - -/obj/structure/closet/singularity_act() - dump_contents() - ..() - -/obj/structure/closet/AllowDrop() - return TRUE - -/obj/structure/closet/bluespace - name = "bluespace closet" - desc = "A storage unit that moves and stores through the fourth dimension." - density = 0 - icon_state = "bluespace" - icon_closed = "bluespace" - icon_opened = "bluespaceopen" - storage_capacity = 60 - var/materials = list(MAT_METAL = 5000, MAT_PLASMA = 2500, MAT_TITANIUM = 500, MAT_BLUESPACE = 500) - -/obj/structure/closet/bluespace/CheckExit(atom/movable/AM) - UpdateTransparency(AM, loc) - return TRUE - -/obj/structure/closet/bluespace/proc/UpdateTransparency(atom/movable/AM, atom/location) - var/transparent = FALSE - for(var/atom/A in location) - if(A.density && A != src && A != AM) - transparent = TRUE - break - icon_opened = transparent ? "bluespaceopentrans" : "bluespaceopen" - icon_closed = transparent ? "bluespacetrans" : "bluespace" - icon_state = opened ? icon_opened : icon_closed - -/obj/structure/closet/bluespace/Crossed(atom/movable/AM, oldloc) - if(AM.density) - icon_state = opened ? "bluespaceopentrans" : "bluespacetrans" - -/obj/structure/closet/bluespace/Move(NewLoc, direct) // Allows for "phasing" throug objects but doesn't allow you to stuff your EOC homebois in one of these and push them through walls. - var/turf/T = get_turf(NewLoc) - if(T.density) - return - for(var/atom/A in T.contents) - if(A.density && istype(A, /obj/machinery/door)) - return - UpdateTransparency(src, NewLoc) - forceMove(NewLoc) - -/obj/structure/closet/bluespace/close() - . = ..() - density = 0 \ No newline at end of file +/obj/structure/closet + name = "closet" + desc = "It's a basic storage unit." + icon = 'icons/obj/closet.dmi' + icon_state = "closed" + density = 1 + max_integrity = 200 + integrity_failure = 50 + armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) + var/icon_closed = "closed" + var/icon_opened = "open" + var/opened = FALSE + var/welded = FALSE + var/locked = FALSE + var/wall_mounted = 0 //never solid (You can always pass over it) + var/lastbang + var/sound = 'sound/machines/click.ogg' + var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients. + var/material_drop = /obj/item/stack/sheet/metal + var/material_drop_amount = 2 + +/obj/structure/closet/New() + ..() + spawn(1) + if(!opened) // if closed, any item at the crate's loc is put in the contents + for(var/obj/item/I in loc) + if(I.density || I.anchored || I == src) continue + I.forceMove(src) + +// Fix for #383 - C4 deleting fridges with corpses +/obj/structure/closet/Destroy() + dump_contents() + return ..() + +/obj/structure/closet/CanPass(atom/movable/mover, turf/target, height=0) + if(height==0 || wall_mounted) + return TRUE + return (!density) + +/obj/structure/closet/proc/can_open() + if(welded) + return FALSE + return TRUE + +/obj/structure/closet/proc/can_close() + for(var/obj/structure/closet/closet in get_turf(src)) + if(closet != src && closet.anchored != 1) + return FALSE + + return TRUE + +/obj/structure/closet/proc/dump_contents() + var/turf/T = get_turf(src) + for(var/atom/movable/AM in src) + AM.forceMove(T) + if(throwing) // you keep some momentum when getting out of a thrown closet + step(AM, dir) + if(throwing) + throwing.finalize(FALSE) + +/obj/structure/closet/proc/open() + if(opened) + return FALSE + + if(!can_open()) + return FALSE + + dump_contents() + + icon_state = icon_opened + opened = TRUE + if(sound) + playsound(loc, sound, 15, 1, -3) + else + playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) + density = 0 + return TRUE + +/obj/structure/closet/proc/close() + if(!opened) + return FALSE + if(!can_close()) + return FALSE + + var/itemcount = 0 + + //Cham Projector Exception + for(var/obj/effect/dummy/chameleon/AD in loc) + if(itemcount >= storage_capacity) + break + AD.forceMove(src) + itemcount++ + + for(var/obj/item/I in loc) + if(itemcount >= storage_capacity) + break + if(!I.anchored) + I.forceMove(src) + itemcount++ + + for(var/mob/M in loc) + if(itemcount >= storage_capacity) + break + if(istype(M, /mob/dead/observer)) + continue + if(istype(M, /mob/living/simple_animal/bot/mulebot)) + continue + if(M.buckled || M.anchored || M.has_buckled_mobs()) + continue + if(isAI(M)) + continue + + M.forceMove(src) + itemcount++ + + icon_state = icon_closed + opened = FALSE + if(sound) + playsound(loc, sound, 15, 1, -3) + else + playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) + density = 1 + return TRUE + +/obj/structure/closet/proc/toggle(mob/user) + if(!(opened ? close() : open())) + to_chat(user, "It won't budge!") + +/obj/structure/closet/proc/bust_open() + welded = FALSE //applies to all lockers + locked = FALSE //applies to critter crates and secure lockers only + broken = TRUE //applies to secure lockers only + open() + +/obj/structure/closet/deconstruct(disassembled = TRUE) + if(ispath(material_drop) && material_drop_amount && !(flags & NODECONSTRUCT)) + new material_drop(loc, material_drop_amount) + qdel(src) + +/obj/structure/closet/obj_break(damage_flag) + if(!broken && !(flags & NODECONSTRUCT)) + bust_open() + +/obj/structure/closet/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/rcs) && !opened) + if(user in contents) //to prevent self-teleporting. + return + var/obj/item/rcs/E = W + if(E.rcell && (E.rcell.charge >= E.chargecost)) + if(!is_level_reachable(z)) + to_chat(user, "The rapid-crate-sender can't locate any telepads!") + return + if(E.mode == 0) + if(!E.teleporting) + var/list/L = list() + var/list/areaindex = list() + for(var/obj/machinery/telepad_cargo/R in world) + if(R.stage == 0) + var/turf/T = get_turf(R) + var/tmpname = T.loc.name + if(areaindex[tmpname]) + tmpname = "[tmpname] ([++areaindex[tmpname]])" + else + areaindex[tmpname] = 1 + L[tmpname] = R + var/desc = input("Please select a telepad.", "RCS") in L + E.pad = L[desc] + if(!Adjacent(user)) + to_chat(user, "Unable to teleport, too far from crate.") + return + playsound(E.loc, E.usesound, 50, 1) + to_chat(user, "Teleporting [name]...") + E.teleporting = 1 + if(!do_after(user, 50 * E.toolspeed, target = src)) + E.teleporting = 0 + return + E.teleporting = 0 + if(user in contents) + to_chat(user, "Error: User located in container--aborting for safety.") + playsound(E.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) + return + if(!(E.rcell && E.rcell.use(E.chargecost))) + to_chat(user, "Unable to teleport, insufficient charge.") + return + do_sparks(5, 1, src) + do_teleport(src, E.pad, 0) + to_chat(user, "Teleport successful. [round(E.rcell.charge/E.chargecost)] charge\s left.") + return + else + E.rand_x = rand(50,200) + E.rand_y = rand(50,200) + var/L = locate(E.rand_x, E.rand_y, 6) + if(!Adjacent(user)) + to_chat(user, "Unable to teleport, too far from crate.") + return + playsound(E.loc, E.usesound, 50, 1) + to_chat(user, "Teleporting [name]...") + E.teleporting = 1 + if(!do_after(user, 50, E.toolspeed, target = src)) + E.teleporting = 0 + return + E.teleporting = 0 + if(user in contents) + to_chat(user, "Error: User located in container--aborting for safety.") + playsound(E.loc, 'sound/machines/buzz-sigh.ogg', 50, 1) + return + if(!(E.rcell && E.rcell.use(E.chargecost))) + to_chat(user, "Unable to teleport, insufficient charge.") + return + do_sparks(5, 1, src) + do_teleport(src, L) + to_chat(user, "Teleport successful. [round(E.rcell.charge/E.chargecost)] charge\s left.") + return + else + to_chat(user, "Out of charges.") + return + return + + if(opened) + if(istype(W, /obj/item/grab)) + MouseDrop_T(W:affecting, user) //act like they were dragged onto the closet + if(istype(W,/obj/item/tk_grab)) + return FALSE + if(isrobot(user)) + return + if(!user.drop_item()) //couldn't drop the item + to_chat(user, "\The [W] is stuck to your hand, you cannot put it in \the [src]!") + return + if(W) + W.forceMove(loc) + else if(istype(W, /obj/item/stack/packageWrap)) + return + else if(user.a_intent != INTENT_HARM) + attack_hand(user) + else + return ..() + +/obj/structure/closet/welder_act(mob/user, obj/item/I) + . = TRUE + if(!opened && user.loc == src) + to_chat(user, "You can't weld [src] from inside!") + return + if(!I.tool_use_check(user, 0)) + return + if(opened) + WELDER_ATTEMPT_SLICING_MESSAGE + if(I.use_tool(src, user, 40, volume = I.tool_volume)) + WELDER_SLICING_SUCCESS_MESSAGE + deconstruct(TRUE) + return + else + var/adjective = welded ? "open" : "shut" + user.visible_message("[user] begins welding [src] [adjective]...", "You begin welding [src] [adjective]...", "You hear welding.") + if(I.use_tool(src, user, 15, volume = I.tool_volume)) + if(opened) + to_chat(user, "Keep [src] shut while doing that!") + return + user.visible_message("[user] welds [src] [adjective]!", "You weld [src] [adjective]!") + welded = !welded + update_icon() + return + +/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/user) + ..() + if(istype(O, /obj/screen)) //fix for HUD elements making their way into the world -Pete + return + if(O.loc == user) + return + if(user.restrained() || user.stat || user.IsWeakened() || user.stunned || user.paralysis || user.lying) + return + if((!( istype(O, /atom/movable) ) || O.anchored || get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src))) + return + if(user.loc==null) // just in case someone manages to get a closet into the blue light dimension, as unlikely as that seems + return + if(!istype(user.loc, /turf)) // are you in a container/closet/pod/etc? + return + if(!opened) + return + if(istype(O, /obj/structure/closet)) + return + step_towards(O, loc) + if(user != O) + user.visible_message("[user] stuffs [O] into [src]!", "You stuff [O] into [src]!") + add_fingerprint(user) + +/obj/structure/closet/attack_ai(mob/user) + if(isrobot(user) && Adjacent(user)) //Robots can open/close it, but not the AI + attack_hand(user) + +/obj/structure/closet/relaymove(mob/user) + if(user.stat || !isturf(loc)) + return + + if(!open()) + to_chat(user, "It won't budge!") + if(!lastbang) + lastbang = 1 + for(var/mob/M in hearers(src, null)) + to_chat(M, text("BANG, bang!", max(0, 5 - get_dist(src, M)))) + spawn(30) + lastbang = 0 + +/obj/structure/closet/attack_hand(mob/user) + add_fingerprint(user) + toggle(user) + +/obj/structure/closet/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + toggle(user) + +// tk grab then use on self +/obj/structure/closet/attack_self_tk(mob/user) + add_fingerprint(user) + if(!toggle()) + to_chat(usr, "It won't budge!") + +/obj/structure/closet/verb/verb_toggleopen() + set src in oview(1) + set category = null + set name = "Toggle Open" + + if(usr.incapacitated()) + return + + if(ishuman(usr)) + add_fingerprint(usr) + toggle(usr) + else + to_chat(usr, "This mob type can't use this verb.") + +/obj/structure/closet/update_icon()//Putting the welded stuff in updateicon() so it's easy to overwrite for special cases (Fridges, cabinets, and whatnot) + overlays.Cut() + if(!opened) + icon_state = icon_closed + if(welded) + overlays += "welded" + else + icon_state = icon_opened + +// Objects that try to exit a locker by stepping were doing so successfully, +// and due to an oversight in turf/Enter() were going through walls. That +// should be independently resolved, but this is also an interesting twist. +/obj/structure/closet/Exit(atom/movable/AM) + open() + if(AM.loc == src) + return FALSE + return TRUE + +/obj/structure/closet/container_resist(var/mob/living/L) + var/breakout_time = 2 //2 minutes by default + if(opened) + if(L.loc == src) + L.forceMove(get_turf(src)) // Let's just be safe here + return //Door's open... wait, why are you in it's contents then? + if(!welded) + open() //for cardboard boxes + return //closed but not welded... + // else Meh, lets just keep it at 2 minutes for now + // breakout_time++ //Harder to get out of welded lockers than locked lockers + + //okay, so the closet is either welded or locked... resist!!! + L.changeNext_move(CLICK_CD_BREAKOUT) + L.last_special = world.time + CLICK_CD_BREAKOUT + to_chat(L, "You lean on the back of \the [src] and start pushing the door open. (this will take about [breakout_time] minutes)") + for(var/mob/O in viewers(usr.loc)) + O.show_message("The [src] begins to shake violently!", 1) + + + spawn(0) + if(do_after(L,(breakout_time*60*10), target = src)) //minutes * 60seconds * 10deciseconds + if(!src || !L || L.stat != CONSCIOUS || L.loc != src || opened) //closet/user destroyed OR user dead/unconcious OR user no longer in closet OR closet opened + return + + //Perform the same set of checks as above for weld and lock status to determine if there is even still a point in 'resisting'... + if(!welded) + return + + //Well then break it! + welded = FALSE + update_icon() + to_chat(usr, "You successfully break out!") + for(var/mob/O in viewers(L.loc)) + O.show_message("\the [usr] successfully broke out of \the [src]!", 1) + if(istype(loc, /obj/structure/bigDelivery)) //nullspace ect.. read the comment above + var/obj/structure/bigDelivery/BD = loc + BD.attack_hand(usr) + open() + +/obj/structure/closet/tesla_act(var/power) + ..() + visible_message("[src] is blown apart by the bolt of electricity!", "You hear a metallic screeching sound.") + qdel(src) + +/obj/structure/closet/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/structure/closet/ex_act(severity) + for(var/atom/A in contents) + A.ex_act(severity) + CHECK_TICK + ..() + +/obj/structure/closet/singularity_act() + dump_contents() + ..() + +/obj/structure/closet/AllowDrop() + return TRUE + +/obj/structure/closet/bluespace + name = "bluespace closet" + desc = "A storage unit that moves and stores through the fourth dimension." + density = 0 + icon_state = "bluespace" + icon_closed = "bluespace" + icon_opened = "bluespaceopen" + storage_capacity = 60 + var/materials = list(MAT_METAL = 5000, MAT_PLASMA = 2500, MAT_TITANIUM = 500, MAT_BLUESPACE = 500) + +/obj/structure/closet/bluespace/CheckExit(atom/movable/AM) + UpdateTransparency(AM, loc) + return TRUE + +/obj/structure/closet/bluespace/proc/UpdateTransparency(atom/movable/AM, atom/location) + var/transparent = FALSE + for(var/atom/A in location) + if(A.density && A != src && A != AM) + transparent = TRUE + break + icon_opened = transparent ? "bluespaceopentrans" : "bluespaceopen" + icon_closed = transparent ? "bluespacetrans" : "bluespace" + icon_state = opened ? icon_opened : icon_closed + +/obj/structure/closet/bluespace/Crossed(atom/movable/AM, oldloc) + if(AM.density) + icon_state = opened ? "bluespaceopentrans" : "bluespacetrans" + +/obj/structure/closet/bluespace/Move(NewLoc, direct) // Allows for "phasing" throug objects but doesn't allow you to stuff your EOC homebois in one of these and push them through walls. + var/turf/T = get_turf(NewLoc) + if(T.density) + return + for(var/atom/A in T.contents) + if(A.density && istype(A, /obj/machinery/door)) + return + UpdateTransparency(src, NewLoc) + forceMove(NewLoc) + +/obj/structure/closet/bluespace/close() + . = ..() + density = 0 diff --git a/code/game/objects/structures/crates_lockers/closets/coffin.dm b/code/game/objects/structures/crates_lockers/closets/coffin.dm index eb0958f03c6..b763796947b 100644 --- a/code/game/objects/structures/crates_lockers/closets/coffin.dm +++ b/code/game/objects/structures/crates_lockers/closets/coffin.dm @@ -1,23 +1,23 @@ -/obj/structure/closet/coffin - name = "coffin" - desc = "It's a burial receptacle for the dearly departed." - icon_state = "coffin" - icon_closed = "coffin" - icon_opened = "coffin_open" - resistance_flags = FLAMMABLE - max_integrity = 70 - material_drop = /obj/item/stack/sheet/wood - -/obj/structure/closet/coffin/update_icon() - if(!opened) - icon_state = icon_closed - else - icon_state = icon_opened - -/obj/structure/closet/coffin/sarcophagus - name = "sarcophagus" - icon_state = "sarc" - icon_closed = "sarc" - icon_opened = "sarc_open" - sound = 'sound/effects/stonedoor_openclose.ogg' - material_drop = /obj/item/stack/sheet/mineral/sandstone \ No newline at end of file +/obj/structure/closet/coffin + name = "coffin" + desc = "It's a burial receptacle for the dearly departed." + icon_state = "coffin" + icon_closed = "coffin" + icon_opened = "coffin_open" + resistance_flags = FLAMMABLE + max_integrity = 70 + material_drop = /obj/item/stack/sheet/wood + +/obj/structure/closet/coffin/update_icon() + if(!opened) + icon_state = icon_closed + else + icon_state = icon_opened + +/obj/structure/closet/coffin/sarcophagus + name = "sarcophagus" + icon_state = "sarc" + icon_closed = "sarc" + icon_opened = "sarc_open" + sound = 'sound/effects/stonedoor_openclose.ogg' + material_drop = /obj/item/stack/sheet/mineral/sandstone diff --git a/code/game/objects/structures/crates_lockers/closets/crittercrate.dm b/code/game/objects/structures/crates_lockers/closets/crittercrate.dm index 6a295ca4307..8370048ecd0 100644 --- a/code/game/objects/structures/crates_lockers/closets/crittercrate.dm +++ b/code/game/objects/structures/crates_lockers/closets/crittercrate.dm @@ -1,7 +1,7 @@ -/obj/structure/closet/crate/critter - name = "critter crate" - desc = "A crate which can sustain life for a while." - icon_state = "critter" - icon_opened = "critteropen" - icon_closed = "critter" - material_drop = /obj/item/stack/sheet/wood \ No newline at end of file +/obj/structure/closet/crate/critter + name = "critter crate" + desc = "A crate which can sustain life for a while." + icon_state = "critter" + icon_opened = "critteropen" + icon_closed = "critter" + material_drop = /obj/item/stack/sheet/wood diff --git a/code/game/objects/structures/crates_lockers/closets/fitness.dm b/code/game/objects/structures/crates_lockers/closets/fitness.dm index 02751e5208e..af8c3e46348 100644 --- a/code/game/objects/structures/crates_lockers/closets/fitness.dm +++ b/code/game/objects/structures/crates_lockers/closets/fitness.dm @@ -71,4 +71,4 @@ new /obj/item/clothing/suit/bluetag(src) new /obj/item/clothing/suit/bluetag(src) new /obj/item/clothing/suit/bluetag(src) - new /obj/item/clothing/head/helmet/bluetaghelm(src) \ No newline at end of file + new /obj/item/clothing/head/helmet/bluetaghelm(src) diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm index 6f388b0bd40..e570128383a 100644 --- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm +++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm @@ -1,136 +1,136 @@ -/obj/structure/closet/cabinet - name = "cabinet" - desc = "Old will forever be in fashion." - icon_state = "cabinet_closed" - icon_closed = "cabinet_closed" - icon_opened = "cabinet_open" - resistance_flags = FLAMMABLE - max_integrity = 70 - -/obj/structure/closet/cabinet/update_icon() - if(!opened) - icon_state = icon_closed - else - icon_state = icon_opened - -/obj/structure/closet/acloset - name = "strange closet" - desc = "It looks alien!" - icon_state = "acloset" - icon_closed = "acloset" - icon_opened = "aclosetopen" - - -/obj/structure/closet/gimmick - name = "administrative supply closet" - desc = "It's a storage unit for things that have no right being here." - icon_state = "syndicate1" - icon_closed = "syndicate1" - icon_opened = "syndicate1open" - anchored = 0 - -/obj/structure/closet/gimmick/russian - name = "russian surplus closet" - desc = "It's a storage unit for Russian standard-issue surplus." - icon_state = "syndicate1" - icon_closed = "syndicate1" - icon_opened = "syndicate1open" - -/obj/structure/closet/gimmick/russian/New() - ..() - new /obj/item/clothing/head/ushanka(src) - new /obj/item/clothing/head/ushanka(src) - new /obj/item/clothing/head/ushanka(src) - new /obj/item/clothing/head/ushanka(src) - new /obj/item/clothing/head/ushanka(src) - new /obj/item/clothing/under/soviet(src) - new /obj/item/clothing/under/soviet(src) - new /obj/item/clothing/under/soviet(src) - new /obj/item/clothing/under/soviet(src) - new /obj/item/clothing/under/soviet(src) - - -/obj/structure/closet/gimmick/tacticool - name = "tacticool gear closet" - desc = "It's a storage unit for Tacticool gear." - icon_state = "syndicate1" - icon_closed = "syndicate1" - icon_opened = "syndicate1open" - -/obj/structure/closet/gimmick/tacticool/New() - ..() - new /obj/item/clothing/glasses/eyepatch(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/gloves/combat(src) - new /obj/item/clothing/gloves/combat(src) - new /obj/item/clothing/head/helmet/swat(src) - new /obj/item/clothing/head/helmet/swat(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/shoes/combat/swat(src) - new /obj/item/clothing/shoes/combat/swat(src) - new /obj/item/clothing/suit/space/deathsquad(src) - new /obj/item/clothing/suit/space/deathsquad(src) - new /obj/item/clothing/under/syndicate/tacticool(src) - new /obj/item/clothing/under/syndicate/tacticool(src) - - -/obj/structure/closet/thunderdome - name = "\improper Thunderdome closet" - desc = "Everything you need!" - icon_state = "syndicate" - icon_closed = "syndicate" - icon_opened = "syndicateopen" - anchored = 1 - -/obj/structure/closet/thunderdome/tdred - name = "red-team Thunderdome closet" - -/obj/structure/closet/thunderdome/tdred/New() - ..() - new /obj/item/clothing/suit/armor/tdome/red(src) - new /obj/item/clothing/suit/armor/tdome/red(src) - new /obj/item/clothing/suit/armor/tdome/red(src) - new /obj/item/melee/energy/sword/saber(src) - new /obj/item/melee/energy/sword/saber(src) - new /obj/item/melee/energy/sword/saber(src) - new /obj/item/gun/energy/laser(src) - new /obj/item/gun/energy/laser(src) - new /obj/item/gun/energy/laser(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/clothing/head/helmet/thunderdome(src) - new /obj/item/clothing/head/helmet/thunderdome(src) - new /obj/item/clothing/head/helmet/thunderdome(src) - -/obj/structure/closet/thunderdome/tdgreen - name = "green-team Thunderdome closet" - icon_state = "syndicate1" - icon_closed = "syndicate1" - icon_opened = "syndicate1open" - -/obj/structure/closet/thunderdome/tdgreen/New() - ..() - new /obj/item/clothing/suit/armor/tdome/green(src) - new /obj/item/clothing/suit/armor/tdome/green(src) - new /obj/item/clothing/suit/armor/tdome/green(src) - new /obj/item/melee/energy/sword/saber(src) - new /obj/item/melee/energy/sword/saber(src) - new /obj/item/melee/energy/sword/saber(src) - new /obj/item/gun/energy/laser(src) - new /obj/item/gun/energy/laser(src) - new /obj/item/gun/energy/laser(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/clothing/head/helmet/thunderdome(src) - new /obj/item/clothing/head/helmet/thunderdome(src) - new /obj/item/clothing/head/helmet/thunderdome(src) - +/obj/structure/closet/cabinet + name = "cabinet" + desc = "Old will forever be in fashion." + icon_state = "cabinet_closed" + icon_closed = "cabinet_closed" + icon_opened = "cabinet_open" + resistance_flags = FLAMMABLE + max_integrity = 70 + +/obj/structure/closet/cabinet/update_icon() + if(!opened) + icon_state = icon_closed + else + icon_state = icon_opened + +/obj/structure/closet/acloset + name = "strange closet" + desc = "It looks alien!" + icon_state = "acloset" + icon_closed = "acloset" + icon_opened = "aclosetopen" + + +/obj/structure/closet/gimmick + name = "administrative supply closet" + desc = "It's a storage unit for things that have no right being here." + icon_state = "syndicate1" + icon_closed = "syndicate1" + icon_opened = "syndicate1open" + anchored = 0 + +/obj/structure/closet/gimmick/russian + name = "russian surplus closet" + desc = "It's a storage unit for Russian standard-issue surplus." + icon_state = "syndicate1" + icon_closed = "syndicate1" + icon_opened = "syndicate1open" + +/obj/structure/closet/gimmick/russian/New() + ..() + new /obj/item/clothing/head/ushanka(src) + new /obj/item/clothing/head/ushanka(src) + new /obj/item/clothing/head/ushanka(src) + new /obj/item/clothing/head/ushanka(src) + new /obj/item/clothing/head/ushanka(src) + new /obj/item/clothing/under/soviet(src) + new /obj/item/clothing/under/soviet(src) + new /obj/item/clothing/under/soviet(src) + new /obj/item/clothing/under/soviet(src) + new /obj/item/clothing/under/soviet(src) + + +/obj/structure/closet/gimmick/tacticool + name = "tacticool gear closet" + desc = "It's a storage unit for Tacticool gear." + icon_state = "syndicate1" + icon_closed = "syndicate1" + icon_opened = "syndicate1open" + +/obj/structure/closet/gimmick/tacticool/New() + ..() + new /obj/item/clothing/glasses/eyepatch(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/gloves/combat(src) + new /obj/item/clothing/gloves/combat(src) + new /obj/item/clothing/head/helmet/swat(src) + new /obj/item/clothing/head/helmet/swat(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/shoes/combat/swat(src) + new /obj/item/clothing/shoes/combat/swat(src) + new /obj/item/clothing/suit/space/deathsquad(src) + new /obj/item/clothing/suit/space/deathsquad(src) + new /obj/item/clothing/under/syndicate/tacticool(src) + new /obj/item/clothing/under/syndicate/tacticool(src) + + +/obj/structure/closet/thunderdome + name = "\improper Thunderdome closet" + desc = "Everything you need!" + icon_state = "syndicate" + icon_closed = "syndicate" + icon_opened = "syndicateopen" + anchored = 1 + +/obj/structure/closet/thunderdome/tdred + name = "red-team Thunderdome closet" + +/obj/structure/closet/thunderdome/tdred/New() + ..() + new /obj/item/clothing/suit/armor/tdome/red(src) + new /obj/item/clothing/suit/armor/tdome/red(src) + new /obj/item/clothing/suit/armor/tdome/red(src) + new /obj/item/melee/energy/sword/saber(src) + new /obj/item/melee/energy/sword/saber(src) + new /obj/item/melee/energy/sword/saber(src) + new /obj/item/gun/energy/laser(src) + new /obj/item/gun/energy/laser(src) + new /obj/item/gun/energy/laser(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/clothing/head/helmet/thunderdome(src) + new /obj/item/clothing/head/helmet/thunderdome(src) + new /obj/item/clothing/head/helmet/thunderdome(src) + +/obj/structure/closet/thunderdome/tdgreen + name = "green-team Thunderdome closet" + icon_state = "syndicate1" + icon_closed = "syndicate1" + icon_opened = "syndicate1open" + +/obj/structure/closet/thunderdome/tdgreen/New() + ..() + new /obj/item/clothing/suit/armor/tdome/green(src) + new /obj/item/clothing/suit/armor/tdome/green(src) + new /obj/item/clothing/suit/armor/tdome/green(src) + new /obj/item/melee/energy/sword/saber(src) + new /obj/item/melee/energy/sword/saber(src) + new /obj/item/melee/energy/sword/saber(src) + new /obj/item/gun/energy/laser(src) + new /obj/item/gun/energy/laser(src) + new /obj/item/gun/energy/laser(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/clothing/head/helmet/thunderdome(src) + new /obj/item/clothing/head/helmet/thunderdome(src) + new /obj/item/clothing/head/helmet/thunderdome(src) + diff --git a/code/game/objects/structures/crates_lockers/closets/job_closets.dm b/code/game/objects/structures/crates_lockers/closets/job_closets.dm index f1053ec917b..448fd6feb34 100644 --- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm @@ -1,144 +1,144 @@ -/* Closets for specific jobs - * Contains: - * Bartender - * Janitor - * Lawyer - */ - -/* - * Bartender - */ -/obj/structure/closet/gmcloset - name = "formal closet" - desc = "It's a storage unit for formal clothing." - icon_state = "black" - icon_closed = "black" - -/obj/structure/closet/gmcloset/New() - ..() - new /obj/item/clothing/head/that(src) - new /obj/item/clothing/head/that(src) - new /obj/item/radio/headset/headset_service(src) - new /obj/item/radio/headset/headset_service(src) - new /obj/item/clothing/head/hairflower - new /obj/item/clothing/under/sl_suit(src) - new /obj/item/clothing/under/sl_suit(src) - new /obj/item/clothing/under/rank/bartender(src) - new /obj/item/clothing/under/rank/bartender(src) - new /obj/item/clothing/under/dress/dress_saloon - new /obj/item/clothing/suit/wcoat(src) - new /obj/item/clothing/suit/wcoat(src) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - -/* - * Chef - */ -/obj/structure/closet/chefcloset - name = "chef's closet" - desc = "It's a storage unit for foodservice garments." - icon_state = "black" - icon_closed = "black" - -/obj/structure/closet/chefcloset/New() - ..() - new /obj/item/clothing/under/waiter(src) - new /obj/item/clothing/under/waiter(src) - new /obj/item/radio/headset/headset_service(src) - new /obj/item/radio/headset/headset_service(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/suit/chef/classic(src) - new /obj/item/clothing/suit/chef/classic(src) - new /obj/item/clothing/suit/chef/classic(src) - new /obj/item/clothing/head/soft/mime(src) - new /obj/item/clothing/head/soft/mime(src) - new /obj/item/storage/box/mousetraps(src) - new /obj/item/storage/box/mousetraps(src) - new /obj/item/clothing/under/rank/chef(src) - new /obj/item/clothing/head/chefhat(src) - new /obj/item/reagent_containers/glass/rag(src) - -/* - * Janitor - */ -/obj/structure/closet/jcloset - name = "custodial closet" - desc = "It's a storage unit for janitorial clothes and gear." - icon_state = "mixed" - icon_closed = "mixed" - -/obj/structure/closet/jcloset/New() - ..() - new /obj/item/clothing/under/rank/janitor(src) - new /obj/item/radio/headset/headset_service(src) - new /obj/item/cartridge/janitor(src) - new /obj/item/flashlight(src) - new /obj/item/melee/flyswatter(src) - new /obj/item/clothing/shoes/galoshes(src) - new /obj/item/soap(src) - new /obj/item/caution(src) - new /obj/item/caution(src) - new /obj/item/caution(src) - new /obj/item/caution(src) - new /obj/item/storage/bag/trash(src) - new /obj/item/lightreplacer(src) - new /obj/item/holosign_creator(src) - new /obj/item/clothing/gloves/color/black(src) - new /obj/item/clothing/head/soft/purple(src) - new /obj/item/watertank/janitor(src) - new /obj/item/storage/belt/janitor(src) - -/* - * Lawyer - */ -/obj/structure/closet/lawcloset - name = "legal closet" - desc = "It's a storage unit for courtroom apparel and items." - icon_state = "blue" - icon_closed = "blue" - -/obj/structure/closet/lawcloset/New() - ..() - new /obj/item/storage/box/tapes(src) - new /obj/item/book/manual/faxes(src) - new /obj/item/clothing/under/lawyer/female(src) - new /obj/item/clothing/under/lawyer/black(src) - new /obj/item/clothing/under/lawyer/red(src) - new /obj/item/clothing/under/lawyer/bluesuit(src) - new /obj/item/clothing/suit/storage/lawyer/bluejacket(src) - new /obj/item/clothing/under/lawyer/purpsuit(src) - new /obj/item/clothing/suit/storage/lawyer/purpjacket(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/glasses/sunglasses/big(src) - new /obj/item/clothing/glasses/sunglasses/big(src) - -//Paramedic - -/obj/structure/closet/paramedic - name = "paramedic wardrobe" - desc = "It's a storage unit for paramedic equipment." - icon_state = "blue" - icon_closed = "blue" - - -/obj/structure/closet/paramedic/New() - new /obj/item/clothing/under/rank/medical/paramedic(src) - new /obj/item/clothing/under/rank/medical/paramedic(src) - new /obj/item/radio/headset/headset_med(src) - new /obj/item/radio/headset/headset_med(src) - new /obj/item/clothing/head/soft/blue(src) - new /obj/item/clothing/head/soft/blue(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/head/soft/blue(src) - new /obj/item/clothing/head/soft/blue(src) - new /obj/item/clothing/suit/storage/paramedic(src) - new /obj/item/clothing/suit/storage/paramedic(src) - new /obj/item/tank/emergency_oxygen/engi(src) - new /obj/item/tank/emergency_oxygen/engi(src) +/* Closets for specific jobs + * Contains: + * Bartender + * Janitor + * Lawyer + */ + +/* + * Bartender + */ +/obj/structure/closet/gmcloset + name = "formal closet" + desc = "It's a storage unit for formal clothing." + icon_state = "black" + icon_closed = "black" + +/obj/structure/closet/gmcloset/New() + ..() + new /obj/item/clothing/head/that(src) + new /obj/item/clothing/head/that(src) + new /obj/item/radio/headset/headset_service(src) + new /obj/item/radio/headset/headset_service(src) + new /obj/item/clothing/head/hairflower + new /obj/item/clothing/under/sl_suit(src) + new /obj/item/clothing/under/sl_suit(src) + new /obj/item/clothing/under/rank/bartender(src) + new /obj/item/clothing/under/rank/bartender(src) + new /obj/item/clothing/under/dress/dress_saloon + new /obj/item/clothing/suit/wcoat(src) + new /obj/item/clothing/suit/wcoat(src) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + +/* + * Chef + */ +/obj/structure/closet/chefcloset + name = "chef's closet" + desc = "It's a storage unit for foodservice garments." + icon_state = "black" + icon_closed = "black" + +/obj/structure/closet/chefcloset/New() + ..() + new /obj/item/clothing/under/waiter(src) + new /obj/item/clothing/under/waiter(src) + new /obj/item/radio/headset/headset_service(src) + new /obj/item/radio/headset/headset_service(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/suit/chef/classic(src) + new /obj/item/clothing/suit/chef/classic(src) + new /obj/item/clothing/suit/chef/classic(src) + new /obj/item/clothing/head/soft/mime(src) + new /obj/item/clothing/head/soft/mime(src) + new /obj/item/storage/box/mousetraps(src) + new /obj/item/storage/box/mousetraps(src) + new /obj/item/clothing/under/rank/chef(src) + new /obj/item/clothing/head/chefhat(src) + new /obj/item/reagent_containers/glass/rag(src) + +/* + * Janitor + */ +/obj/structure/closet/jcloset + name = "custodial closet" + desc = "It's a storage unit for janitorial clothes and gear." + icon_state = "mixed" + icon_closed = "mixed" + +/obj/structure/closet/jcloset/New() + ..() + new /obj/item/clothing/under/rank/janitor(src) + new /obj/item/radio/headset/headset_service(src) + new /obj/item/cartridge/janitor(src) + new /obj/item/flashlight(src) + new /obj/item/melee/flyswatter(src) + new /obj/item/clothing/shoes/galoshes(src) + new /obj/item/soap(src) + new /obj/item/caution(src) + new /obj/item/caution(src) + new /obj/item/caution(src) + new /obj/item/caution(src) + new /obj/item/storage/bag/trash(src) + new /obj/item/lightreplacer(src) + new /obj/item/holosign_creator(src) + new /obj/item/clothing/gloves/color/black(src) + new /obj/item/clothing/head/soft/purple(src) + new /obj/item/watertank/janitor(src) + new /obj/item/storage/belt/janitor(src) + +/* + * Lawyer + */ +/obj/structure/closet/lawcloset + name = "legal closet" + desc = "It's a storage unit for courtroom apparel and items." + icon_state = "blue" + icon_closed = "blue" + +/obj/structure/closet/lawcloset/New() + ..() + new /obj/item/storage/box/tapes(src) + new /obj/item/book/manual/faxes(src) + new /obj/item/clothing/under/lawyer/female(src) + new /obj/item/clothing/under/lawyer/black(src) + new /obj/item/clothing/under/lawyer/red(src) + new /obj/item/clothing/under/lawyer/bluesuit(src) + new /obj/item/clothing/suit/storage/lawyer/bluejacket(src) + new /obj/item/clothing/under/lawyer/purpsuit(src) + new /obj/item/clothing/suit/storage/lawyer/purpjacket(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/glasses/sunglasses/big(src) + new /obj/item/clothing/glasses/sunglasses/big(src) + +//Paramedic + +/obj/structure/closet/paramedic + name = "paramedic wardrobe" + desc = "It's a storage unit for paramedic equipment." + icon_state = "blue" + icon_closed = "blue" + + +/obj/structure/closet/paramedic/New() + new /obj/item/clothing/under/rank/medical/paramedic(src) + new /obj/item/clothing/under/rank/medical/paramedic(src) + new /obj/item/radio/headset/headset_med(src) + new /obj/item/radio/headset/headset_med(src) + new /obj/item/clothing/head/soft/blue(src) + new /obj/item/clothing/head/soft/blue(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/head/soft/blue(src) + new /obj/item/clothing/head/soft/blue(src) + new /obj/item/clothing/suit/storage/paramedic(src) + new /obj/item/clothing/suit/storage/paramedic(src) + new /obj/item/tank/emergency_oxygen/engi(src) + new /obj/item/tank/emergency_oxygen/engi(src) diff --git a/code/game/objects/structures/crates_lockers/closets/l3closet.dm b/code/game/objects/structures/crates_lockers/closets/l3closet.dm index 0d550dce36d..e29ce1bb11c 100644 --- a/code/game/objects/structures/crates_lockers/closets/l3closet.dm +++ b/code/game/objects/structures/crates_lockers/closets/l3closet.dm @@ -1,76 +1,76 @@ -/obj/structure/closet/l3closet - name = "level-3 biohazard suit closet" - desc = "It's a storage unit for level-3 biohazard gear." - icon_state = "bio" - icon_closed = "bio" - icon_opened = "bioopen" - -/obj/structure/closet/l3closet/New() - ..() - new /obj/item/storage/bag/bio( src ) - new /obj/item/clothing/suit/bio_suit/general( src ) - new /obj/item/clothing/head/bio_hood/general( src ) - - -/obj/structure/closet/l3closet/general - icon_state = "bio_general" - icon_closed = "bio_general" - icon_opened = "bio_generalopen" - -/obj/structure/closet/l3closet/general/New() - ..() - contents = list() - new /obj/item/clothing/suit/bio_suit/general( src ) - new /obj/item/clothing/head/bio_hood/general( src ) - - -/obj/structure/closet/l3closet/virology - icon_state = "bio_virology" - icon_closed = "bio_virology" - icon_opened = "bio_virologyopen" - -/obj/structure/closet/l3closet/virology/New() - ..() - contents = list() - new /obj/item/storage/bag/bio( src ) - new /obj/item/clothing/suit/bio_suit/virology( src ) - new /obj/item/clothing/head/bio_hood/virology( src ) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/oxygen(src) - - -/obj/structure/closet/l3closet/security - icon_state = "bio_security" - icon_closed = "bio_security" - icon_opened = "bio_securityopen" - -/obj/structure/closet/l3closet/security/New() - ..() - contents = list() - new /obj/item/clothing/suit/bio_suit/security( src ) - new /obj/item/clothing/head/bio_hood/security( src ) - - -/obj/structure/closet/l3closet/janitor - icon_state = "bio_janitor" - icon_closed = "bio_janitor" - icon_opened = "bio_janitoropen" - -/obj/structure/closet/l3closet/janitor/New() - ..() - contents = list() - new /obj/item/clothing/suit/bio_suit/janitor( src ) - new /obj/item/clothing/head/bio_hood/janitor( src ) - - -/obj/structure/closet/l3closet/scientist - icon_state = "bio_scientist" - icon_closed = "bio_scientist" - icon_opened = "bio_scientistopen" - -/obj/structure/closet/l3closet/scientist/New() - ..() - contents = list() - new /obj/item/storage/bag/bio( src ) - new /obj/item/clothing/suit/bio_suit/scientist( src ) - new /obj/item/clothing/head/bio_hood/scientist( src ) \ No newline at end of file +/obj/structure/closet/l3closet + name = "level-3 biohazard suit closet" + desc = "It's a storage unit for level-3 biohazard gear." + icon_state = "bio" + icon_closed = "bio" + icon_opened = "bioopen" + +/obj/structure/closet/l3closet/New() + ..() + new /obj/item/storage/bag/bio( src ) + new /obj/item/clothing/suit/bio_suit/general( src ) + new /obj/item/clothing/head/bio_hood/general( src ) + + +/obj/structure/closet/l3closet/general + icon_state = "bio_general" + icon_closed = "bio_general" + icon_opened = "bio_generalopen" + +/obj/structure/closet/l3closet/general/New() + ..() + contents = list() + new /obj/item/clothing/suit/bio_suit/general( src ) + new /obj/item/clothing/head/bio_hood/general( src ) + + +/obj/structure/closet/l3closet/virology + icon_state = "bio_virology" + icon_closed = "bio_virology" + icon_opened = "bio_virologyopen" + +/obj/structure/closet/l3closet/virology/New() + ..() + contents = list() + new /obj/item/storage/bag/bio( src ) + new /obj/item/clothing/suit/bio_suit/virology( src ) + new /obj/item/clothing/head/bio_hood/virology( src ) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/oxygen(src) + + +/obj/structure/closet/l3closet/security + icon_state = "bio_security" + icon_closed = "bio_security" + icon_opened = "bio_securityopen" + +/obj/structure/closet/l3closet/security/New() + ..() + contents = list() + new /obj/item/clothing/suit/bio_suit/security( src ) + new /obj/item/clothing/head/bio_hood/security( src ) + + +/obj/structure/closet/l3closet/janitor + icon_state = "bio_janitor" + icon_closed = "bio_janitor" + icon_opened = "bio_janitoropen" + +/obj/structure/closet/l3closet/janitor/New() + ..() + contents = list() + new /obj/item/clothing/suit/bio_suit/janitor( src ) + new /obj/item/clothing/head/bio_hood/janitor( src ) + + +/obj/structure/closet/l3closet/scientist + icon_state = "bio_scientist" + icon_closed = "bio_scientist" + icon_opened = "bio_scientistopen" + +/obj/structure/closet/l3closet/scientist/New() + ..() + contents = list() + new /obj/item/storage/bag/bio( src ) + new /obj/item/clothing/suit/bio_suit/scientist( src ) + new /obj/item/clothing/head/bio_hood/scientist( src ) diff --git a/code/game/objects/structures/crates_lockers/closets/malfunction.dm b/code/game/objects/structures/crates_lockers/closets/malfunction.dm index cb7da3e3375..957fc6ef623 100644 --- a/code/game/objects/structures/crates_lockers/closets/malfunction.dm +++ b/code/game/objects/structures/crates_lockers/closets/malfunction.dm @@ -1,15 +1,15 @@ - -/obj/structure/closet/malf/suits - desc = "It's a storage unit for operational gear." - icon_state = "syndicate" - icon_closed = "syndicate" - icon_opened = "syndicateopen" - -/obj/structure/closet/malf/suits/New() - ..() - new /obj/item/tank/jetpack/void(src) - new /obj/item/clothing/mask/breath(src) - new /obj/effect/nasavoidsuitspawner(src) - new /obj/item/crowbar(src) - new /obj/item/stock_parts/cell(src) - new /obj/item/multitool(src) \ No newline at end of file + +/obj/structure/closet/malf/suits + desc = "It's a storage unit for operational gear." + icon_state = "syndicate" + icon_closed = "syndicate" + icon_opened = "syndicateopen" + +/obj/structure/closet/malf/suits/New() + ..() + new /obj/item/tank/jetpack/void(src) + new /obj/item/clothing/mask/breath(src) + new /obj/effect/nasavoidsuitspawner(src) + new /obj/item/crowbar(src) + new /obj/item/stock_parts/cell(src) + new /obj/item/multitool(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/depot.dm b/code/game/objects/structures/crates_lockers/closets/secure/depot.dm index df208106334..bba8dc2a866 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/depot.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/depot.dm @@ -63,4 +63,4 @@ /obj/structure/closet/secure_closet/syndicate/depot/armory req_access = list(ACCESS_SYNDICATE) - is_armory = TRUE \ No newline at end of file + is_armory = TRUE diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm index 8642a50d01b..7616aa90877 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -1,152 +1,152 @@ -/obj/structure/closet/secure_closet/engineering_chief - name = "chief engineer's locker" - req_access = list(ACCESS_CE) - icon_state = "securece1" - icon_closed = "securece" - icon_locked = "securece1" - icon_opened = "secureceopen" - icon_broken = "securecebroken" - icon_off = "secureceoff" - -/obj/structure/closet/secure_closet/engineering_chief/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/industrial(src) - else - new /obj/item/storage/backpack/satchel_eng(src) - new /obj/item/storage/backpack/duffel/engineering(src) - new /obj/item/clothing/head/beret/ce(src) - new /obj/item/areaeditor/blueprints(src) - new /obj/item/storage/box/permits(src) - new /obj/item/clothing/under/rank/chief_engineer(src) - new /obj/item/clothing/under/rank/chief_engineer/skirt(src) - new /obj/item/clothing/suit/mantle/chief_engineer(src) - new /obj/item/clothing/head/hardhat/white(src) - new /obj/item/clothing/glasses/welding/superior(src) - new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/tank/jetpack/suit(src) - new /obj/item/cartridge/ce(src) - new /obj/item/radio/headset/heads/ce(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/clothing/suit/storage/hazardvest(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/multitool(src) - new /obj/item/holosign_creator/engineering(src) - new /obj/item/flash(src) - new /obj/item/clothing/head/beret/eng(src) - new /obj/item/door_remote/chief_engineer(src) - new /obj/item/rpd(src) - new /obj/item/reagent_containers/food/drinks/mug/ce(src) - new /obj/item/organ/internal/cyberimp/eyes/meson(src) - new /obj/item/clothing/accessory/medal/engineering(src) - new /obj/item/holosign_creator/atmos(src) - - -/obj/structure/closet/secure_closet/engineering_electrical - name = "electrical supplies locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "secureengelec1" - icon_closed = "secureengelec" - icon_locked = "secureengelec1" - icon_opened = "toolclosetopen" - icon_broken = "secureengelecbroken" - icon_off = "secureengelecoff" - -/obj/structure/closet/secure_closet/engineering_electrical/New() - ..() - new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/storage/toolbox/electrical(src) - new /obj/item/storage/toolbox/electrical(src) - new /obj/item/storage/toolbox/electrical(src) - new /obj/item/apc_electronics(src) - new /obj/item/apc_electronics(src) - new /obj/item/apc_electronics(src) - new /obj/item/multitool(src) - new /obj/item/multitool(src) - new /obj/item/multitool(src) - new /obj/item/clothing/head/beret/eng - - -/obj/structure/closet/secure_closet/engineering_welding - name = "welding supplies locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "secureengweld1" - icon_closed = "secureengweld" - icon_locked = "secureengweld1" - icon_opened = "toolclosetopen" - icon_broken = "secureengweldbroken" - icon_off = "secureengweldoff" - -/obj/structure/closet/secure_closet/engineering_welding/New() - ..() - new /obj/item/clothing/head/welding(src) - new /obj/item/clothing/head/welding(src) - new /obj/item/clothing/head/welding(src) - new /obj/item/weldingtool/largetank(src) - new /obj/item/weldingtool/largetank(src) - new /obj/item/weldingtool/largetank(src) - - -/obj/structure/closet/secure_closet/engineering_personal - name = "engineer's locker" - req_access = list(ACCESS_ENGINE_EQUIP) - icon_state = "secureeng1" - icon_closed = "secureeng" - icon_locked = "secureeng1" - icon_opened = "secureengopen" - icon_broken = "secureengbroken" - icon_off = "secureengoff" - -/obj/structure/closet/secure_closet/engineering_personal/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/industrial(src) - else - new /obj/item/storage/backpack/satchel_eng(src) - new /obj/item/storage/backpack/duffel/engineering(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/holosign_creator/engineering(src) - new /obj/item/radio/headset/headset_eng(src) - new /obj/item/clothing/under/rank/engineer(src) - new /obj/item/clothing/under/rank/engineer/skirt(src) - new /obj/item/clothing/suit/storage/hazardvest(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/glasses/meson(src) - new /obj/item/cartridge/engineering(src) - new /obj/item/clothing/head/beret/eng(src) - - -/obj/structure/closet/secure_closet/atmos_personal - name = "technician's locker" - req_access = list(ACCESS_ATMOSPHERICS) - icon_state = "secureatm1" - icon_closed = "secureatm" - icon_locked = "secureatm1" - icon_opened = "secureatmopen" - icon_broken = "secureatmbroken" - icon_off = "secureatmoff" - -/obj/structure/closet/secure_closet/atmos_personal/New() - ..() - new /obj/item/radio/headset/headset_eng(src) - new /obj/item/cartridge/atmos(src) - new /obj/item/storage/toolbox/mechanical(src) - if(prob(50)) - new /obj/item/storage/backpack/industrial(src) - else - new /obj/item/storage/backpack/satchel_eng(src) - new /obj/item/storage/backpack/duffel/atmos(src) - new /obj/item/extinguisher(src) - new /obj/item/grenade/gas/oxygen(src) - new /obj/item/grenade/gas/oxygen(src) - new /obj/item/clothing/suit/storage/hazardvest(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/tank/emergency_oxygen/engi(src) - new /obj/item/holosign_creator/atmos(src) - new /obj/item/watertank/atmos(src) - new /obj/item/clothing/suit/fire/atmos(src) - new /obj/item/clothing/head/hardhat/atmos(src) - new /obj/item/rpd(src) - new /obj/item/destTagger(src) +/obj/structure/closet/secure_closet/engineering_chief + name = "chief engineer's locker" + req_access = list(ACCESS_CE) + icon_state = "securece1" + icon_closed = "securece" + icon_locked = "securece1" + icon_opened = "secureceopen" + icon_broken = "securecebroken" + icon_off = "secureceoff" + +/obj/structure/closet/secure_closet/engineering_chief/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/industrial(src) + else + new /obj/item/storage/backpack/satchel_eng(src) + new /obj/item/storage/backpack/duffel/engineering(src) + new /obj/item/clothing/head/beret/ce(src) + new /obj/item/areaeditor/blueprints(src) + new /obj/item/storage/box/permits(src) + new /obj/item/clothing/under/rank/chief_engineer(src) + new /obj/item/clothing/under/rank/chief_engineer/skirt(src) + new /obj/item/clothing/suit/mantle/chief_engineer(src) + new /obj/item/clothing/head/hardhat/white(src) + new /obj/item/clothing/glasses/welding/superior(src) + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/tank/jetpack/suit(src) + new /obj/item/cartridge/ce(src) + new /obj/item/radio/headset/heads/ce(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/clothing/suit/storage/hazardvest(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/multitool(src) + new /obj/item/holosign_creator/engineering(src) + new /obj/item/flash(src) + new /obj/item/clothing/head/beret/eng(src) + new /obj/item/door_remote/chief_engineer(src) + new /obj/item/rpd(src) + new /obj/item/reagent_containers/food/drinks/mug/ce(src) + new /obj/item/organ/internal/cyberimp/eyes/meson(src) + new /obj/item/clothing/accessory/medal/engineering(src) + new /obj/item/holosign_creator/atmos(src) + + +/obj/structure/closet/secure_closet/engineering_electrical + name = "electrical supplies locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "secureengelec1" + icon_closed = "secureengelec" + icon_locked = "secureengelec1" + icon_opened = "toolclosetopen" + icon_broken = "secureengelecbroken" + icon_off = "secureengelecoff" + +/obj/structure/closet/secure_closet/engineering_electrical/New() + ..() + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/storage/toolbox/electrical(src) + new /obj/item/storage/toolbox/electrical(src) + new /obj/item/storage/toolbox/electrical(src) + new /obj/item/apc_electronics(src) + new /obj/item/apc_electronics(src) + new /obj/item/apc_electronics(src) + new /obj/item/multitool(src) + new /obj/item/multitool(src) + new /obj/item/multitool(src) + new /obj/item/clothing/head/beret/eng + + +/obj/structure/closet/secure_closet/engineering_welding + name = "welding supplies locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "secureengweld1" + icon_closed = "secureengweld" + icon_locked = "secureengweld1" + icon_opened = "toolclosetopen" + icon_broken = "secureengweldbroken" + icon_off = "secureengweldoff" + +/obj/structure/closet/secure_closet/engineering_welding/New() + ..() + new /obj/item/clothing/head/welding(src) + new /obj/item/clothing/head/welding(src) + new /obj/item/clothing/head/welding(src) + new /obj/item/weldingtool/largetank(src) + new /obj/item/weldingtool/largetank(src) + new /obj/item/weldingtool/largetank(src) + + +/obj/structure/closet/secure_closet/engineering_personal + name = "engineer's locker" + req_access = list(ACCESS_ENGINE_EQUIP) + icon_state = "secureeng1" + icon_closed = "secureeng" + icon_locked = "secureeng1" + icon_opened = "secureengopen" + icon_broken = "secureengbroken" + icon_off = "secureengoff" + +/obj/structure/closet/secure_closet/engineering_personal/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/industrial(src) + else + new /obj/item/storage/backpack/satchel_eng(src) + new /obj/item/storage/backpack/duffel/engineering(src) + new /obj/item/storage/toolbox/mechanical(src) + new /obj/item/holosign_creator/engineering(src) + new /obj/item/radio/headset/headset_eng(src) + new /obj/item/clothing/under/rank/engineer(src) + new /obj/item/clothing/under/rank/engineer/skirt(src) + new /obj/item/clothing/suit/storage/hazardvest(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/glasses/meson(src) + new /obj/item/cartridge/engineering(src) + new /obj/item/clothing/head/beret/eng(src) + + +/obj/structure/closet/secure_closet/atmos_personal + name = "technician's locker" + req_access = list(ACCESS_ATMOSPHERICS) + icon_state = "secureatm1" + icon_closed = "secureatm" + icon_locked = "secureatm1" + icon_opened = "secureatmopen" + icon_broken = "secureatmbroken" + icon_off = "secureatmoff" + +/obj/structure/closet/secure_closet/atmos_personal/New() + ..() + new /obj/item/radio/headset/headset_eng(src) + new /obj/item/cartridge/atmos(src) + new /obj/item/storage/toolbox/mechanical(src) + if(prob(50)) + new /obj/item/storage/backpack/industrial(src) + else + new /obj/item/storage/backpack/satchel_eng(src) + new /obj/item/storage/backpack/duffel/atmos(src) + new /obj/item/extinguisher(src) + new /obj/item/grenade/gas/oxygen(src) + new /obj/item/grenade/gas/oxygen(src) + new /obj/item/clothing/suit/storage/hazardvest(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/tank/emergency_oxygen/engi(src) + new /obj/item/holosign_creator/atmos(src) + new /obj/item/watertank/atmos(src) + new /obj/item/clothing/suit/fire/atmos(src) + new /obj/item/clothing/head/hardhat/atmos(src) + new /obj/item/rpd(src) + new /obj/item/destTagger(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm index 817b8cf474a..e8f7cd3edbe 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm @@ -1,112 +1,112 @@ -/obj/structure/closet/secure_closet/freezer - desc = "It's a card-locked refrigerative storage unit. This one is lead-lined." - -/obj/structure/closet/secure_closet/freezer/update_icon() - if(broken) - icon_state = icon_broken - else - if(!opened) - if(locked) - icon_state = icon_locked - else - icon_state = icon_closed - if(welded) - overlays += "welded" - else - icon_state = icon_opened - -/obj/structure/closet/secure_closet/freezer/ex_act(var/severity) - // IF INDIANA JONES CAN DO IT SO CAN YOU - - // Bomb in here? (using same search as space transits searching for nuke disk) - var/list/bombs = search_contents_for(/obj/item/transfer_valve) - if(!isemptylist(bombs)) // You're fucked. - ..(severity) - - -/obj/structure/closet/secure_closet/freezer/kitchen - name = "kitchen cabinet" - req_access = list(ACCESS_KITCHEN) - -/obj/structure/closet/secure_closet/freezer/kitchen/New() - ..() - for(var/i in 1 to 3) - new /obj/item/reagent_containers/food/condiment/flour(src) - new /obj/item/reagent_containers/food/condiment/rice(src) - new /obj/item/reagent_containers/food/condiment/sugar(src) - - -/obj/structure/closet/secure_closet/freezer/kitchen/mining - req_access = list() - -/obj/structure/closet/secure_closet/freezer/kitchen/maintenance - name = "maintenance refrigerator" - desc = "This refrigerator looks quite dusty, is there anything edible still inside?" - req_access = list() - -/obj/structure/closet/secure_closet/freezer/kitchen/maintenance/New() - ..() - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/milk(src) - for(var/i = 0, i < 5, i++) - new /obj/item/reagent_containers/food/condiment/soymilk(src) - for(var/i = 0, i < 2, i++) - new /obj/item/storage/fancy/egg_box(src) - -/obj/structure/closet/secure_closet/freezer/meat - name = "meat fridge" - icon_state = "fridge1" - icon_closed = "fridge" - icon_locked = "fridge1" - icon_opened = "fridgeopen" - icon_broken = "fridgebroken" - icon_off = "fridge1" - -/obj/structure/closet/secure_closet/freezer/meat/New() - ..() - for(var/i in 1 to 4) - new /obj/item/reagent_containers/food/snacks/meat/monkey(src) - -/obj/structure/closet/secure_closet/freezer/meat/open - req_access = null - locked = FALSE - -/obj/structure/closet/secure_closet/freezer/fridge - name = "refrigerator" - icon_state = "fridge1" - icon_closed = "fridge" - icon_locked = "fridge1" - icon_opened = "fridgeopen" - icon_broken = "fridgebroken" - icon_off = "fridge1" - -/obj/structure/closet/secure_closet/freezer/fridge/New() - ..() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/food/condiment/milk(src) - new /obj/item/reagent_containers/food/condiment/soymilk(src) - for(var/i in 1 to 2) - new /obj/item/storage/fancy/egg_box(src) - -/obj/structure/closet/secure_closet/freezer/fridge/open - req_access = null - locked = FALSE - -/obj/structure/closet/secure_closet/freezer/money - name = "freezer" - icon_state = "fridge1" - icon_closed = "fridge" - icon_locked = "fridge1" - icon_opened = "fridgeopen" - icon_broken = "fridgebroken" - icon_off = "fridge1" - req_access = list(ACCESS_HEADS_VAULT) - -/obj/structure/closet/secure_closet/freezer/money/New() - ..() - for(var/i in 1 to 3) - new /obj/item/stack/spacecash/c1000(src) - for(var/i in 1 to 5) - new /obj/item/stack/spacecash/c500(src) - for(var/i in 1 to 6) - new /obj/item/stack/spacecash/c200(src) +/obj/structure/closet/secure_closet/freezer + desc = "It's a card-locked refrigerative storage unit. This one is lead-lined." + +/obj/structure/closet/secure_closet/freezer/update_icon() + if(broken) + icon_state = icon_broken + else + if(!opened) + if(locked) + icon_state = icon_locked + else + icon_state = icon_closed + if(welded) + overlays += "welded" + else + icon_state = icon_opened + +/obj/structure/closet/secure_closet/freezer/ex_act(var/severity) + // IF INDIANA JONES CAN DO IT SO CAN YOU + + // Bomb in here? (using same search as space transits searching for nuke disk) + var/list/bombs = search_contents_for(/obj/item/transfer_valve) + if(!isemptylist(bombs)) // You're fucked. + ..(severity) + + +/obj/structure/closet/secure_closet/freezer/kitchen + name = "kitchen cabinet" + req_access = list(ACCESS_KITCHEN) + +/obj/structure/closet/secure_closet/freezer/kitchen/New() + ..() + for(var/i in 1 to 3) + new /obj/item/reagent_containers/food/condiment/flour(src) + new /obj/item/reagent_containers/food/condiment/rice(src) + new /obj/item/reagent_containers/food/condiment/sugar(src) + + +/obj/structure/closet/secure_closet/freezer/kitchen/mining + req_access = list() + +/obj/structure/closet/secure_closet/freezer/kitchen/maintenance + name = "maintenance refrigerator" + desc = "This refrigerator looks quite dusty, is there anything edible still inside?" + req_access = list() + +/obj/structure/closet/secure_closet/freezer/kitchen/maintenance/New() + ..() + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/milk(src) + for(var/i = 0, i < 5, i++) + new /obj/item/reagent_containers/food/condiment/soymilk(src) + for(var/i = 0, i < 2, i++) + new /obj/item/storage/fancy/egg_box(src) + +/obj/structure/closet/secure_closet/freezer/meat + name = "meat fridge" + icon_state = "fridge1" + icon_closed = "fridge" + icon_locked = "fridge1" + icon_opened = "fridgeopen" + icon_broken = "fridgebroken" + icon_off = "fridge1" + +/obj/structure/closet/secure_closet/freezer/meat/New() + ..() + for(var/i in 1 to 4) + new /obj/item/reagent_containers/food/snacks/meat/monkey(src) + +/obj/structure/closet/secure_closet/freezer/meat/open + req_access = null + locked = FALSE + +/obj/structure/closet/secure_closet/freezer/fridge + name = "refrigerator" + icon_state = "fridge1" + icon_closed = "fridge" + icon_locked = "fridge1" + icon_opened = "fridgeopen" + icon_broken = "fridgebroken" + icon_off = "fridge1" + +/obj/structure/closet/secure_closet/freezer/fridge/New() + ..() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/food/condiment/milk(src) + new /obj/item/reagent_containers/food/condiment/soymilk(src) + for(var/i in 1 to 2) + new /obj/item/storage/fancy/egg_box(src) + +/obj/structure/closet/secure_closet/freezer/fridge/open + req_access = null + locked = FALSE + +/obj/structure/closet/secure_closet/freezer/money + name = "freezer" + icon_state = "fridge1" + icon_closed = "fridge" + icon_locked = "fridge1" + icon_opened = "fridgeopen" + icon_broken = "fridgebroken" + icon_off = "fridge1" + req_access = list(ACCESS_HEADS_VAULT) + +/obj/structure/closet/secure_closet/freezer/money/New() + ..() + for(var/i in 1 to 3) + new /obj/item/stack/spacecash/c1000(src) + for(var/i in 1 to 5) + new /obj/item/stack/spacecash/c500(src) + for(var/i in 1 to 6) + new /obj/item/stack/spacecash/c200(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/guncabinet.dm b/code/game/objects/structures/crates_lockers/closets/secure/guncabinet.dm index 162680446bb..bb3a50c38b7 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/guncabinet.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/guncabinet.dm @@ -52,4 +52,4 @@ else if(locked) overlays += icon(src.icon,"locked") else - overlays += icon(src.icon,"open") \ No newline at end of file + overlays += icon(src.icon,"open") diff --git a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm index afd82f34f78..29664c65626 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm @@ -23,4 +23,4 @@ new /obj/item/clothing/mask/bandana/botany(src) new /obj/item/cultivator(src) new /obj/item/hatchet(src) - new /obj/item/storage/box/disks_plantgene(src) \ No newline at end of file + new /obj/item/storage/box/disks_plantgene(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm index 6fb30c47664..91746bed68f 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -1,295 +1,295 @@ -/obj/structure/closet/secure_closet/medical1 - name = "medicine closet" - desc = "Filled with medical junk." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/medical1/New() - ..() - new /obj/item/storage/box/autoinjectors(src) - new /obj/item/storage/box/syringes(src) - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/patch_packs(src) - new /obj/item/storage/box/iv_bags(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/glass/bottle/epinephrine(src) - new /obj/item/reagent_containers/glass/bottle/epinephrine(src) - new /obj/item/reagent_containers/glass/bottle/charcoal(src) - new /obj/item/reagent_containers/glass/bottle/charcoal(src) - - -/obj/structure/closet/secure_closet/medical2 - name = "anesthetic locker" - desc = "Used to knock people out." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/medical2/New() - ..() - new /obj/item/tank/anesthetic(src) - new /obj/item/tank/anesthetic(src) - new /obj/item/tank/anesthetic(src) - new /obj/item/clothing/mask/breath/medical(src) - new /obj/item/clothing/mask/breath/medical(src) - new /obj/item/clothing/mask/breath/medical(src) - - -/obj/structure/closet/secure_closet/medical3 - name = "medical doctor's locker" - req_access = list(ACCESS_SURGERY) - icon_state = "securemed1" - icon_closed = "securemed" - icon_locked = "securemed1" - icon_opened = "securemedopen" - icon_broken = "securemedbroken" - icon_off = "securemedoff" - -/obj/structure/closet/secure_closet/medical3/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/medic(src) - else - new /obj/item/storage/backpack/satchel_med(src) - new /obj/item/storage/backpack/duffel/medical(src) - new /obj/item/clothing/under/rank/medical(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/radio/headset/headset_med(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/defibrillator/loaded(src) - new /obj/item/handheld_defibrillator(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/clothing/glasses/hud/health(src) - new /obj/item/clothing/shoes/sandal/white(src) - - -//Exam Room -/obj/structure/closet/secure_closet/exam - name = "exam room closet" - desc = "Filled with exam room materials." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/exam/New() - ..() - new /obj/item/storage/box/syringes(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/glasses/hud/health(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/clothing/accessory/stethoscope(src) - new /obj/item/flashlight/pen(src) - new /obj/item/storage/firstaid/regular(src) - new /obj/item/storage/firstaid/adv(src) - new /obj/item/storage/firstaid/brute(src) - new /obj/item/storage/firstaid/fire(src) - new /obj/item/storage/firstaid/o2(src) - new /obj/item/storage/firstaid/toxin(src) - - -// Psychiatrist's pill bottle -/obj/item/storage/pill_bottle/psychiatrist - name = "psychiatrist's pill bottle" - desc = "Contains various pills to calm or sedate patients." - wrapper_color = COLOR_PALE_BTL_GREEN - -/obj/item/storage/pill_bottle/psychiatrist/New() - ..() - new /obj/item/reagent_containers/food/pill/haloperidol(src) - new /obj/item/reagent_containers/food/pill/haloperidol(src) - new /obj/item/reagent_containers/food/pill/haloperidol(src) - new /obj/item/reagent_containers/food/pill/methamphetamine(src) - new /obj/item/reagent_containers/food/pill/methamphetamine(src) - new /obj/item/reagent_containers/food/pill/methamphetamine(src) - new /obj/item/reagent_containers/food/pill/patch/nicotine(src) - new /obj/item/reagent_containers/food/pill/patch/nicotine(src) - new /obj/item/reagent_containers/food/pill/patch/nicotine(src) - new /obj/item/reagent_containers/food/pill/hydrocodone(src) - new /obj/item/reagent_containers/food/pill/hydrocodone(src) - -/obj/structure/closet/secure_closet/psychiatrist - name = "psychiatrist's locker" - req_access = list(ACCESS_PSYCHIATRIST) - icon_state = "securemed1" - icon_closed = "securemed" - icon_locked = "securemed1" - icon_opened = "securemedopen" - icon_broken = "securemedbroken" - icon_off = "securemedoff" - -/obj/structure/closet/secure_closet/psychiatrist/New() - ..() - new /obj/item/clothing/suit/straight_jacket(src) - new /obj/item/reagent_containers/syringe(src) - new /obj/item/reagent_containers/glass/bottle/ether(src) - new /obj/item/storage/fancy/cigarettes/cigpack_med(src) - new /obj/item/storage/fancy/cigarettes/cigpack_med(src) - new /obj/item/storage/fancy/cigarettes/cigpack_med(src) - new /obj/item/storage/pill_bottle/psychiatrist(src) - new /obj/random/plushie(src) - for(var/i in 0 to 3) - var/candy = pick(subtypesof(/obj/item/reagent_containers/food/snacks/candy/fudge)) - new candy(src) - -/obj/structure/closet/secure_closet/CMO - name = "chief medical officer's locker" - req_access = list(ACCESS_CMO) - icon_state = "cmosecure1" - icon_closed = "cmosecure" - icon_locked = "cmosecure1" - icon_opened = "cmosecureopen" - icon_broken = "cmosecurebroken" - icon_off = "cmosecureoff" - -/obj/structure/closet/secure_closet/CMO/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/medic(src) - else - new /obj/item/storage/backpack/satchel_med(src) - new /obj/item/storage/backpack/duffel/medical(src) - new /obj/item/clothing/suit/bio_suit/cmo(src) - new /obj/item/clothing/head/bio_hood/cmo(src) - new /obj/item/clothing/shoes/white(src) - switch(pick("blue", "green", "purple")) - if("blue") - new /obj/item/clothing/under/rank/medical/blue(src) - new /obj/item/clothing/head/surgery/blue(src) - if("green") - new /obj/item/clothing/under/rank/medical/green(src) - new /obj/item/clothing/head/surgery/green(src) - if("purple") - new /obj/item/clothing/under/rank/medical/purple(src) - new /obj/item/clothing/head/surgery/purple(src) - new /obj/item/clothing/suit/storage/labcoat/cmo(src) - new /obj/item/clothing/under/rank/chief_medical_officer(src) - new /obj/item/clothing/suit/mantle/labcoat/chief_medical_officer(src) - new /obj/item/clothing/shoes/brown (src) - new /obj/item/radio/headset/heads/cmo(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/defibrillator/compact/loaded(src) - new /obj/item/handheld_defibrillator(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/flash(src) - new /obj/item/reagent_containers/hypospray/CMO(src) - new /obj/item/organ/internal/cyberimp/eyes/hud/medical(src) - new /obj/item/door_remote/chief_medical_officer(src) - new /obj/item/reagent_containers/food/drinks/mug/cmo(src) - new /obj/item/clothing/accessory/medal/medical(src) - - -/obj/structure/closet/secure_closet/animal - name = "animal control locker" - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/animal/New() - ..() - new /obj/item/assembly/signaler(src) - new /obj/item/radio/electropack(src) - new /obj/item/radio/electropack(src) - new /obj/item/radio/electropack(src) - - -/obj/structure/closet/secure_closet/chemical - name = "chemical closet" - desc = "Store dangerous chemicals in here." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_CHEMISTRY) - -/obj/structure/closet/secure_closet/chemical/New() - ..() - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/patch_packs(src) - new /obj/item/storage/box/patch_packs(src) - - -/obj/structure/closet/secure_closet/medical_wall - name = "first aid closet" - desc = "It's a secure wall-mounted storage unit for first aid supplies." - icon_state = "medical_wall_locked" - icon_closed = "medical_wall_unlocked" - icon_locked = "medical_wall_locked" - icon_opened = "medical_wall_open" - icon_broken = "medical_wall_spark" - icon_off = "medical_wall_off" - anchored = 1 - density = 0 - wall_mounted = 1 - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/medical_wall/update_icon() - if(broken) - icon_state = icon_broken - else - if(!opened) - if(locked) - icon_state = icon_locked - else - icon_state = icon_closed - else - icon_state = icon_opened - -/obj/structure/closet/secure_closet/paramedic - name = "paramedic EVA gear" - desc = "A locker with a Paramedic EVA suit." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_PARAMEDIC) - -/obj/structure/closet/secure_closet/paramedic/New() - ..() - new /obj/item/clothing/suit/space/eva/paramedic(src) - new /obj/item/clothing/head/helmet/space/eva/paramedic(src) - new /obj/item/sensor_device(src) - new /obj/item/key/ambulance(src) - new /obj/item/pinpointer/crew(src) - new /obj/item/handheld_defibrillator(src) - -/obj/structure/closet/secure_closet/reagents - name = "chemical storage closet" - desc = "Store dangerous chemicals in here." - icon_state = "chemical1" - icon_closed = "chemical" - icon_locked = "chemical1" - icon_opened = "medicalopen" - icon_broken = "chemicalbroken" - icon_off = "chemicaloff" - req_access = list(ACCESS_CHEMISTRY) - -/obj/structure/closet/secure_closet/reagents/New() - ..() - new /obj/item/reagent_containers/glass/bottle/reagent/phenol(src) - new /obj/item/reagent_containers/glass/bottle/reagent/ammonia(src) - new /obj/item/reagent_containers/glass/bottle/reagent/oil(src) - new /obj/item/reagent_containers/glass/bottle/reagent/acetone(src) - new /obj/item/reagent_containers/glass/bottle/reagent/acid(src) - new /obj/item/reagent_containers/glass/bottle/reagent/diethylamine(src) +/obj/structure/closet/secure_closet/medical1 + name = "medicine closet" + desc = "Filled with medical junk." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/medical1/New() + ..() + new /obj/item/storage/box/autoinjectors(src) + new /obj/item/storage/box/syringes(src) + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/patch_packs(src) + new /obj/item/storage/box/iv_bags(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/glass/bottle/epinephrine(src) + new /obj/item/reagent_containers/glass/bottle/epinephrine(src) + new /obj/item/reagent_containers/glass/bottle/charcoal(src) + new /obj/item/reagent_containers/glass/bottle/charcoal(src) + + +/obj/structure/closet/secure_closet/medical2 + name = "anesthetic locker" + desc = "Used to knock people out." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/medical2/New() + ..() + new /obj/item/tank/anesthetic(src) + new /obj/item/tank/anesthetic(src) + new /obj/item/tank/anesthetic(src) + new /obj/item/clothing/mask/breath/medical(src) + new /obj/item/clothing/mask/breath/medical(src) + new /obj/item/clothing/mask/breath/medical(src) + + +/obj/structure/closet/secure_closet/medical3 + name = "medical doctor's locker" + req_access = list(ACCESS_SURGERY) + icon_state = "securemed1" + icon_closed = "securemed" + icon_locked = "securemed1" + icon_opened = "securemedopen" + icon_broken = "securemedbroken" + icon_off = "securemedoff" + +/obj/structure/closet/secure_closet/medical3/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/medic(src) + else + new /obj/item/storage/backpack/satchel_med(src) + new /obj/item/storage/backpack/duffel/medical(src) + new /obj/item/clothing/under/rank/medical(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/radio/headset/headset_med(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/defibrillator/loaded(src) + new /obj/item/handheld_defibrillator(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/clothing/glasses/hud/health(src) + new /obj/item/clothing/shoes/sandal/white(src) + + +//Exam Room +/obj/structure/closet/secure_closet/exam + name = "exam room closet" + desc = "Filled with exam room materials." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/exam/New() + ..() + new /obj/item/storage/box/syringes(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/glasses/hud/health(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/clothing/accessory/stethoscope(src) + new /obj/item/flashlight/pen(src) + new /obj/item/storage/firstaid/regular(src) + new /obj/item/storage/firstaid/adv(src) + new /obj/item/storage/firstaid/brute(src) + new /obj/item/storage/firstaid/fire(src) + new /obj/item/storage/firstaid/o2(src) + new /obj/item/storage/firstaid/toxin(src) + + +// Psychiatrist's pill bottle +/obj/item/storage/pill_bottle/psychiatrist + name = "psychiatrist's pill bottle" + desc = "Contains various pills to calm or sedate patients." + wrapper_color = COLOR_PALE_BTL_GREEN + +/obj/item/storage/pill_bottle/psychiatrist/New() + ..() + new /obj/item/reagent_containers/food/pill/haloperidol(src) + new /obj/item/reagent_containers/food/pill/haloperidol(src) + new /obj/item/reagent_containers/food/pill/haloperidol(src) + new /obj/item/reagent_containers/food/pill/methamphetamine(src) + new /obj/item/reagent_containers/food/pill/methamphetamine(src) + new /obj/item/reagent_containers/food/pill/methamphetamine(src) + new /obj/item/reagent_containers/food/pill/patch/nicotine(src) + new /obj/item/reagent_containers/food/pill/patch/nicotine(src) + new /obj/item/reagent_containers/food/pill/patch/nicotine(src) + new /obj/item/reagent_containers/food/pill/hydrocodone(src) + new /obj/item/reagent_containers/food/pill/hydrocodone(src) + +/obj/structure/closet/secure_closet/psychiatrist + name = "psychiatrist's locker" + req_access = list(ACCESS_PSYCHIATRIST) + icon_state = "securemed1" + icon_closed = "securemed" + icon_locked = "securemed1" + icon_opened = "securemedopen" + icon_broken = "securemedbroken" + icon_off = "securemedoff" + +/obj/structure/closet/secure_closet/psychiatrist/New() + ..() + new /obj/item/clothing/suit/straight_jacket(src) + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/glass/bottle/ether(src) + new /obj/item/storage/fancy/cigarettes/cigpack_med(src) + new /obj/item/storage/fancy/cigarettes/cigpack_med(src) + new /obj/item/storage/fancy/cigarettes/cigpack_med(src) + new /obj/item/storage/pill_bottle/psychiatrist(src) + new /obj/random/plushie(src) + for(var/i in 0 to 3) + var/candy = pick(subtypesof(/obj/item/reagent_containers/food/snacks/candy/fudge)) + new candy(src) + +/obj/structure/closet/secure_closet/CMO + name = "chief medical officer's locker" + req_access = list(ACCESS_CMO) + icon_state = "cmosecure1" + icon_closed = "cmosecure" + icon_locked = "cmosecure1" + icon_opened = "cmosecureopen" + icon_broken = "cmosecurebroken" + icon_off = "cmosecureoff" + +/obj/structure/closet/secure_closet/CMO/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/medic(src) + else + new /obj/item/storage/backpack/satchel_med(src) + new /obj/item/storage/backpack/duffel/medical(src) + new /obj/item/clothing/suit/bio_suit/cmo(src) + new /obj/item/clothing/head/bio_hood/cmo(src) + new /obj/item/clothing/shoes/white(src) + switch(pick("blue", "green", "purple")) + if("blue") + new /obj/item/clothing/under/rank/medical/blue(src) + new /obj/item/clothing/head/surgery/blue(src) + if("green") + new /obj/item/clothing/under/rank/medical/green(src) + new /obj/item/clothing/head/surgery/green(src) + if("purple") + new /obj/item/clothing/under/rank/medical/purple(src) + new /obj/item/clothing/head/surgery/purple(src) + new /obj/item/clothing/suit/storage/labcoat/cmo(src) + new /obj/item/clothing/under/rank/chief_medical_officer(src) + new /obj/item/clothing/suit/mantle/labcoat/chief_medical_officer(src) + new /obj/item/clothing/shoes/brown (src) + new /obj/item/radio/headset/heads/cmo(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/defibrillator/compact/loaded(src) + new /obj/item/handheld_defibrillator(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/flash(src) + new /obj/item/reagent_containers/hypospray/CMO(src) + new /obj/item/organ/internal/cyberimp/eyes/hud/medical(src) + new /obj/item/door_remote/chief_medical_officer(src) + new /obj/item/reagent_containers/food/drinks/mug/cmo(src) + new /obj/item/clothing/accessory/medal/medical(src) + + +/obj/structure/closet/secure_closet/animal + name = "animal control locker" + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/animal/New() + ..() + new /obj/item/assembly/signaler(src) + new /obj/item/radio/electropack(src) + new /obj/item/radio/electropack(src) + new /obj/item/radio/electropack(src) + + +/obj/structure/closet/secure_closet/chemical + name = "chemical closet" + desc = "Store dangerous chemicals in here." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_CHEMISTRY) + +/obj/structure/closet/secure_closet/chemical/New() + ..() + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/patch_packs(src) + new /obj/item/storage/box/patch_packs(src) + + +/obj/structure/closet/secure_closet/medical_wall + name = "first aid closet" + desc = "It's a secure wall-mounted storage unit for first aid supplies." + icon_state = "medical_wall_locked" + icon_closed = "medical_wall_unlocked" + icon_locked = "medical_wall_locked" + icon_opened = "medical_wall_open" + icon_broken = "medical_wall_spark" + icon_off = "medical_wall_off" + anchored = 1 + density = 0 + wall_mounted = 1 + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/medical_wall/update_icon() + if(broken) + icon_state = icon_broken + else + if(!opened) + if(locked) + icon_state = icon_locked + else + icon_state = icon_closed + else + icon_state = icon_opened + +/obj/structure/closet/secure_closet/paramedic + name = "paramedic EVA gear" + desc = "A locker with a Paramedic EVA suit." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_PARAMEDIC) + +/obj/structure/closet/secure_closet/paramedic/New() + ..() + new /obj/item/clothing/suit/space/eva/paramedic(src) + new /obj/item/clothing/head/helmet/space/eva/paramedic(src) + new /obj/item/sensor_device(src) + new /obj/item/key/ambulance(src) + new /obj/item/pinpointer/crew(src) + new /obj/item/handheld_defibrillator(src) + +/obj/structure/closet/secure_closet/reagents + name = "chemical storage closet" + desc = "Store dangerous chemicals in here." + icon_state = "chemical1" + icon_closed = "chemical" + icon_locked = "chemical1" + icon_opened = "medicalopen" + icon_broken = "chemicalbroken" + icon_off = "chemicaloff" + req_access = list(ACCESS_CHEMISTRY) + +/obj/structure/closet/secure_closet/reagents/New() + ..() + new /obj/item/reagent_containers/glass/bottle/reagent/phenol(src) + new /obj/item/reagent_containers/glass/bottle/reagent/ammonia(src) + new /obj/item/reagent_containers/glass/bottle/reagent/oil(src) + new /obj/item/reagent_containers/glass/bottle/reagent/acetone(src) + new /obj/item/reagent_containers/glass/bottle/reagent/acid(src) + new /obj/item/reagent_containers/glass/bottle/reagent/diethylamine(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm index f5afa97e70f..096133b6e0d 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm @@ -1,89 +1,89 @@ -/obj/structure/closet/secure_closet/personal - desc = "It's a secure locker for personnel. The first card swiped gains control." - name = "personal closet" - req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) - var/registered_name = null - -/obj/structure/closet/secure_closet/personal/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/duffel(src) - if(prob(50)) - new /obj/item/storage/backpack(src) - else - new /obj/item/storage/backpack/satchel_norm(src) - new /obj/item/radio/headset( src ) - - -/obj/structure/closet/secure_closet/personal/patient - name = "patient's closet" - -/obj/structure/closet/secure_closet/personal/patient/New() - ..() - contents.Cut() - new /obj/item/clothing/under/color/white( src ) - new /obj/item/clothing/shoes/white( src ) - - - -/obj/structure/closet/secure_closet/personal/cabinet - icon_state = "cabinetdetective_locked" - icon_closed = "cabinetdetective" - icon_locked = "cabinetdetective_locked" - icon_opened = "cabinetdetective_open" - icon_broken = "cabinetdetective_broken" - icon_off = "cabinetdetective_broken" - resistance_flags = FLAMMABLE - max_integrity = 70 - -/obj/structure/closet/secure_closet/personal/cabinet/update_icon() - if(broken) - icon_state = icon_broken - else - if(!opened) - if(locked) - icon_state = icon_locked - else - icon_state = icon_closed - else - icon_state = icon_opened - -/obj/structure/closet/secure_closet/personal/cabinet/New() - ..() - contents.Cut() - new /obj/item/storage/backpack/satchel/withwallet( src ) - new /obj/item/radio/headset( src ) - -/obj/structure/closet/secure_closet/personal/attackby(obj/item/W as obj, mob/user as mob, params) - if(src.opened) - if(istype(W, /obj/item/grab)) - src.MouseDrop_T(W:affecting, user) //act like they were dragged onto the closet - user.drop_item() - if(W) W.forceMove(loc) - else if(istype(W, /obj/item/card/id)) - if(src.broken) - to_chat(user, "It appears to be broken.") - return - var/obj/item/card/id/I = W - if(!I || !I.registered_name) return - if(src == user.loc) - to_chat(user, "You can't reach the lock from inside.") - else if(src.allowed(user) || !src.registered_name || (istype(I) && (src.registered_name == I.registered_name))) - //they can open all lockers, or nobody owns this, or they own this locker - src.locked = !( src.locked ) - if(src.locked) - src.icon_state = src.icon_locked - else - src.icon_state = src.icon_closed - registered_name = null - desc = initial(desc) - - if(!src.registered_name && src.locked) - src.registered_name = I.registered_name - src.desc = "Owned by [I.registered_name]." - else - to_chat(user, "Access Denied") - else if((istype(W, /obj/item/card/emag) || istype(W, /obj/item/melee/energy/blade)) && !broken) - emag_act(user) - else - return ..() +/obj/structure/closet/secure_closet/personal + desc = "It's a secure locker for personnel. The first card swiped gains control." + name = "personal closet" + req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) + var/registered_name = null + +/obj/structure/closet/secure_closet/personal/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/duffel(src) + if(prob(50)) + new /obj/item/storage/backpack(src) + else + new /obj/item/storage/backpack/satchel_norm(src) + new /obj/item/radio/headset( src ) + + +/obj/structure/closet/secure_closet/personal/patient + name = "patient's closet" + +/obj/structure/closet/secure_closet/personal/patient/New() + ..() + contents.Cut() + new /obj/item/clothing/under/color/white( src ) + new /obj/item/clothing/shoes/white( src ) + + + +/obj/structure/closet/secure_closet/personal/cabinet + icon_state = "cabinetdetective_locked" + icon_closed = "cabinetdetective" + icon_locked = "cabinetdetective_locked" + icon_opened = "cabinetdetective_open" + icon_broken = "cabinetdetective_broken" + icon_off = "cabinetdetective_broken" + resistance_flags = FLAMMABLE + max_integrity = 70 + +/obj/structure/closet/secure_closet/personal/cabinet/update_icon() + if(broken) + icon_state = icon_broken + else + if(!opened) + if(locked) + icon_state = icon_locked + else + icon_state = icon_closed + else + icon_state = icon_opened + +/obj/structure/closet/secure_closet/personal/cabinet/New() + ..() + contents.Cut() + new /obj/item/storage/backpack/satchel/withwallet( src ) + new /obj/item/radio/headset( src ) + +/obj/structure/closet/secure_closet/personal/attackby(obj/item/W as obj, mob/user as mob, params) + if(src.opened) + if(istype(W, /obj/item/grab)) + src.MouseDrop_T(W:affecting, user) //act like they were dragged onto the closet + user.drop_item() + if(W) W.forceMove(loc) + else if(istype(W, /obj/item/card/id)) + if(src.broken) + to_chat(user, "It appears to be broken.") + return + var/obj/item/card/id/I = W + if(!I || !I.registered_name) return + if(src == user.loc) + to_chat(user, "You can't reach the lock from inside.") + else if(src.allowed(user) || !src.registered_name || (istype(I) && (src.registered_name == I.registered_name))) + //they can open all lockers, or nobody owns this, or they own this locker + src.locked = !( src.locked ) + if(src.locked) + src.icon_state = src.icon_locked + else + src.icon_state = src.icon_closed + registered_name = null + desc = initial(desc) + + if(!src.registered_name && src.locked) + src.registered_name = I.registered_name + src.desc = "Owned by [I.registered_name]." + else + to_chat(user, "Access Denied") + else if((istype(W, /obj/item/card/emag) || istype(W, /obj/item/melee/energy/blade)) && !broken) + emag_act(user) + else + return ..() diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm index 5fff4e79296..fd67afcf797 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm @@ -1,108 +1,108 @@ -/obj/structure/closet/secure_closet/scientist - name = "scientist's locker" - req_access = list(ACCESS_TOX_STORAGE) - icon_state = "secureres1" - icon_closed = "secureres" - icon_locked = "secureres1" - icon_opened = "secureresopen" - icon_broken = "secureresbroken" - icon_off = "secureresoff" - -/obj/structure/closet/secure_closet/scientist/New() - ..() - new /obj/item/storage/backpack/science(src) - new /obj/item/storage/backpack/satchel_tox(src) - new /obj/item/clothing/under/rank/scientist(src) - new /obj/item/clothing/under/rank/scientist/skirt(src) - //new /obj/item/clothing/suit/labcoat/science(src) - new /obj/item/clothing/suit/storage/labcoat/science(src) - new /obj/item/clothing/shoes/white(src) -// new /obj/item/cartridge/signal/toxins(src) - new /obj/item/radio/headset/headset_sci(src) - new /obj/item/tank/air(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/shoes/sandal/white(src) - -/obj/structure/closet/secure_closet/roboticist - name = "roboticist's locker" - req_access = list(ACCESS_ROBOTICS) - icon_state = "secureres1" - icon_closed = "secureres" - icon_locked = "secureres1" - icon_opened = "secureresopen" - icon_broken = "secureresbroken" - icon_off = "secureresoff" - -/obj/structure/closet/secure_closet/roboticist/New() - ..() - new /obj/item/storage/backpack(src) - new /obj/item/storage/backpack(src) - new /obj/item/storage/backpack/satchel_norm(src) - new /obj/item/storage/backpack/satchel_norm(src) - new /obj/item/storage/backpack/duffel(src) - new /obj/item/storage/backpack/duffel(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/radio/headset/headset_sci(src) - new /obj/item/radio/headset/headset_sci(src) - new /obj/item/reagent_containers/food/drinks/oilcan(src) - new /obj/item/reagent_containers/food/drinks/oilcan(src) - -/obj/structure/closet/secure_closet/RD - name = "research director's locker" - req_access = list(ACCESS_RD) - icon_state = "rdsecure1" - icon_closed = "rdsecure" - icon_locked = "rdsecure1" - icon_opened = "rdsecureopen" - icon_broken = "rdsecurebroken" - icon_off = "rdsecureoff" - -/obj/structure/closet/secure_closet/RD/New() - ..() - new /obj/item/clothing/suit/bio_suit/scientist(src) - new /obj/item/clothing/head/bio_hood/scientist(src) - new /obj/item/clothing/under/rank/research_director(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/suit/mantle/labcoat(src) - new /obj/item/cartridge/rd(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/radio/headset/heads/rd(src) - new /obj/item/tank/air(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/suit/armor/reactive/teleport(src) - new /obj/item/flash(src) - new /obj/item/laser_pointer(src) - new /obj/item/door_remote/research_director(src) - new /obj/item/reagent_containers/food/drinks/mug/rd(src) - new /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic(src) - new /obj/item/clothing/accessory/medal/science(src) - -/obj/structure/closet/secure_closet/research_reagents - name = "research chemical storage closet" - desc = "Store dangerous chemicals in here." - icon_state = "rchemical1" - icon_closed = "rchemical" - icon_locked = "rchemical1" - icon_opened = "medicalopen" - icon_broken = "rchemicalbroken" - icon_off = "rchemicaloff" - req_access = list(ACCESS_TOX_STORAGE) - -/obj/structure/closet/secure_closet/research_reagents/New() - ..() - new /obj/item/reagent_containers/glass/bottle/reagent/morphine(src) - new /obj/item/reagent_containers/glass/bottle/reagent/morphine(src) - new /obj/item/reagent_containers/glass/bottle/reagent/morphine(src) - new /obj/item/reagent_containers/glass/bottle/reagent/morphine(src) - new /obj/item/reagent_containers/glass/bottle/reagent/insulin(src) - new /obj/item/reagent_containers/glass/bottle/reagent/insulin(src) - new /obj/item/reagent_containers/glass/bottle/reagent/insulin(src) - new /obj/item/reagent_containers/glass/bottle/reagent/insulin(src) - new /obj/item/reagent_containers/glass/bottle/reagent/phenol(src) - new /obj/item/reagent_containers/glass/bottle/reagent/ammonia(src) - new /obj/item/reagent_containers/glass/bottle/reagent/oil(src) - new /obj/item/reagent_containers/glass/bottle/reagent/acetone(src) - new /obj/item/reagent_containers/glass/bottle/reagent/acid(src) - new /obj/item/reagent_containers/glass/bottle/reagent/diethylamine(src) +/obj/structure/closet/secure_closet/scientist + name = "scientist's locker" + req_access = list(ACCESS_TOX_STORAGE) + icon_state = "secureres1" + icon_closed = "secureres" + icon_locked = "secureres1" + icon_opened = "secureresopen" + icon_broken = "secureresbroken" + icon_off = "secureresoff" + +/obj/structure/closet/secure_closet/scientist/New() + ..() + new /obj/item/storage/backpack/science(src) + new /obj/item/storage/backpack/satchel_tox(src) + new /obj/item/clothing/under/rank/scientist(src) + new /obj/item/clothing/under/rank/scientist/skirt(src) + //new /obj/item/clothing/suit/labcoat/science(src) + new /obj/item/clothing/suit/storage/labcoat/science(src) + new /obj/item/clothing/shoes/white(src) +// new /obj/item/cartridge/signal/toxins(src) + new /obj/item/radio/headset/headset_sci(src) + new /obj/item/tank/air(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/shoes/sandal/white(src) + +/obj/structure/closet/secure_closet/roboticist + name = "roboticist's locker" + req_access = list(ACCESS_ROBOTICS) + icon_state = "secureres1" + icon_closed = "secureres" + icon_locked = "secureres1" + icon_opened = "secureresopen" + icon_broken = "secureresbroken" + icon_off = "secureresoff" + +/obj/structure/closet/secure_closet/roboticist/New() + ..() + new /obj/item/storage/backpack(src) + new /obj/item/storage/backpack(src) + new /obj/item/storage/backpack/satchel_norm(src) + new /obj/item/storage/backpack/satchel_norm(src) + new /obj/item/storage/backpack/duffel(src) + new /obj/item/storage/backpack/duffel(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/radio/headset/headset_sci(src) + new /obj/item/radio/headset/headset_sci(src) + new /obj/item/reagent_containers/food/drinks/oilcan(src) + new /obj/item/reagent_containers/food/drinks/oilcan(src) + +/obj/structure/closet/secure_closet/RD + name = "research director's locker" + req_access = list(ACCESS_RD) + icon_state = "rdsecure1" + icon_closed = "rdsecure" + icon_locked = "rdsecure1" + icon_opened = "rdsecureopen" + icon_broken = "rdsecurebroken" + icon_off = "rdsecureoff" + +/obj/structure/closet/secure_closet/RD/New() + ..() + new /obj/item/clothing/suit/bio_suit/scientist(src) + new /obj/item/clothing/head/bio_hood/scientist(src) + new /obj/item/clothing/under/rank/research_director(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/suit/mantle/labcoat(src) + new /obj/item/cartridge/rd(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/radio/headset/heads/rd(src) + new /obj/item/tank/air(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/suit/armor/reactive/teleport(src) + new /obj/item/flash(src) + new /obj/item/laser_pointer(src) + new /obj/item/door_remote/research_director(src) + new /obj/item/reagent_containers/food/drinks/mug/rd(src) + new /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic(src) + new /obj/item/clothing/accessory/medal/science(src) + +/obj/structure/closet/secure_closet/research_reagents + name = "research chemical storage closet" + desc = "Store dangerous chemicals in here." + icon_state = "rchemical1" + icon_closed = "rchemical" + icon_locked = "rchemical1" + icon_opened = "medicalopen" + icon_broken = "rchemicalbroken" + icon_off = "rchemicaloff" + req_access = list(ACCESS_TOX_STORAGE) + +/obj/structure/closet/secure_closet/research_reagents/New() + ..() + new /obj/item/reagent_containers/glass/bottle/reagent/morphine(src) + new /obj/item/reagent_containers/glass/bottle/reagent/morphine(src) + new /obj/item/reagent_containers/glass/bottle/reagent/morphine(src) + new /obj/item/reagent_containers/glass/bottle/reagent/morphine(src) + new /obj/item/reagent_containers/glass/bottle/reagent/insulin(src) + new /obj/item/reagent_containers/glass/bottle/reagent/insulin(src) + new /obj/item/reagent_containers/glass/bottle/reagent/insulin(src) + new /obj/item/reagent_containers/glass/bottle/reagent/insulin(src) + new /obj/item/reagent_containers/glass/bottle/reagent/phenol(src) + new /obj/item/reagent_containers/glass/bottle/reagent/ammonia(src) + new /obj/item/reagent_containers/glass/bottle/reagent/oil(src) + new /obj/item/reagent_containers/glass/bottle/reagent/acetone(src) + new /obj/item/reagent_containers/glass/bottle/reagent/acid(src) + new /obj/item/reagent_containers/glass/bottle/reagent/diethylamine(src) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm index 3ab0d94317b..dbb3b8f283d 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm @@ -1,179 +1,179 @@ -/obj/structure/closet/secure_closet - name = "secure locker" - desc = "It's an immobile card-locked storage unit." - icon = 'icons/obj/closet.dmi' - icon_state = "secure1" - density = 1 - opened = 0 - locked = 1 - broken = 0 - max_integrity = 250 - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - damage_deflection = 20 - var/large = 1 - icon_closed = "secure" - var/icon_locked = "secure1" - icon_opened = "secureopen" - var/icon_broken = "securebroken" - var/icon_off = "secureoff" - wall_mounted = 0 //never solid (You can always pass over it) - -/obj/structure/closet/secure_closet/can_open() - if(!..()) - return 0 - if(locked) - return 0 - return ..() - -/obj/structure/closet/secure_closet/close() - if(..()) - if(broken) - icon_state = icon_off - return 1 - else - return 0 - -/obj/structure/closet/secure_closet/emp_act(severity) - for(var/obj/O in src) - O.emp_act(severity) - if(!broken) - if(prob(50/severity)) - locked = !locked - update_icon() - if(prob(20/severity) && !opened) - if(!locked) - open() - else - req_access = list() - req_access += pick(get_all_accesses()) - ..() - -/obj/structure/closet/secure_closet/proc/togglelock(mob/user) - if(opened) - to_chat(user, "Close the locker first.") - return - if(broken) - to_chat(user, "The locker appears to be broken.") - return - if(user.loc == src) - to_chat(user, "You can't reach the lock from inside.") - return - if(allowed(user)) - locked = !locked - playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) - visible_message("The locker has been [locked ? null : "un"]locked by [user].") - update_icon() - else - to_chat(user, "Access Denied") - -/obj/structure/closet/secure_closet/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/rcs)) - return ..() - - if(opened) - if(istype(W, /obj/item/grab)) - if(large) - MouseDrop_T(W:affecting, user) //act like they were dragged onto the closet - else - to_chat(user, "The locker is too small to stuff [W:affecting] into!") - if(isrobot(user)) - return - if(!user.drop_item()) //couldn't drop the item - to_chat(user, "\The [W] is stuck to your hand, you cannot put it in \the [src]!") - return - if(W) - W.forceMove(loc) - else if((istype(W, /obj/item/card/emag)||istype(W, /obj/item/melee/energy/blade)) && !broken) - emag_act(user) - else if(istype(W,/obj/item/stack/packageWrap) || istype(W,/obj/item/weldingtool)) - return ..(W, user) - else if(user.a_intent != INTENT_HARM) - togglelock(user) - else - return ..() - -/obj/structure/closet/secure_closet/emag_act(mob/user) - if(!broken) - broken = TRUE - locked = FALSE - icon_state = icon_off - flick(icon_broken, src) - to_chat(user, "You break the lock on \the [src].") - -/obj/structure/closet/secure_closet/attack_hand(mob/user) - add_fingerprint(user) - if(locked) - togglelock(user) - else - toggle(user) - -/obj/structure/closet/secure_closet/verb/verb_togglelock() - set src in oview(1) // One square distance - set category = "Object" - set name = "Toggle Lock" - - if(usr.incapacitated()) // Don't use it if you're not able to! Checks for stuns, ghost and restrain - return - - if(ishuman(usr)) - add_fingerprint(usr) - togglelock(usr) - else - to_chat(usr, "This mob type can't use this verb.") - -/obj/structure/closet/secure_closet/update_icon()//Putting the welded stuff in updateicon() so it's easy to overwrite for special cases (Fridges, cabinets, and whatnot) - overlays.Cut() - if(!opened) - if(locked) - icon_state = icon_locked - else - icon_state = icon_closed - if(welded) - overlays += "welded" - else - icon_state = icon_opened - -/obj/structure/closet/secure_closet/container_resist(var/mob/living/L) - var/breakout_time = 2 //2 minutes by default - if(opened) - if(L.loc == src) - L.forceMove(get_turf(src)) // Let's just be safe here - return //Door's open... wait, why are you in it's contents then? - if(!locked && !welded) - return //It's a secure closet, but isn't locked. Easily escapable from, no need to 'resist' - - //okay, so the closet is either welded or locked... resist!!! - L.changeNext_move(CLICK_CD_BREAKOUT) - L.last_special = world.time + CLICK_CD_BREAKOUT - to_chat(L, "You lean on the back of \the [src] and start pushing the door open. (this will take about [breakout_time] minutes)") - for(var/mob/O in viewers(src)) - O.show_message("The [src] begins to shake violently!", 1) - - - spawn(0) - if(do_after(usr,(breakout_time*60*10), target = src)) //minutes * 60seconds * 10deciseconds - if(!src || !L || L.stat != CONSCIOUS || L.loc != src || opened) //closet/user destroyed OR user dead/unconcious OR user no longer in closet OR closet opened - return - - //Perform the same set of checks as above for weld and lock status to determine if there is even still a point in 'resisting'... - if(!locked && !welded) - return - - //Well then break it! - desc = "It appears to be broken." - icon_state = icon_off - flick(icon_broken, src) - sleep(10) - flick(icon_broken, src) - sleep(10) - broken = 1 - locked = 0 - welded = 0 - update_icon() - to_chat(usr, "You successfully break out!") - for(var/mob/O in viewers(L.loc)) - O.show_message("\the [usr] successfully broke out of \the [src]!", 1) - if(istype(loc, /obj/structure/bigDelivery)) //Do this to prevent contents from being opened into nullspace (read: bluespace) - var/obj/structure/bigDelivery/BD = loc - BD.attack_hand(usr) - open() +/obj/structure/closet/secure_closet + name = "secure locker" + desc = "It's an immobile card-locked storage unit." + icon = 'icons/obj/closet.dmi' + icon_state = "secure1" + density = 1 + opened = 0 + locked = 1 + broken = 0 + max_integrity = 250 + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + damage_deflection = 20 + var/large = 1 + icon_closed = "secure" + var/icon_locked = "secure1" + icon_opened = "secureopen" + var/icon_broken = "securebroken" + var/icon_off = "secureoff" + wall_mounted = 0 //never solid (You can always pass over it) + +/obj/structure/closet/secure_closet/can_open() + if(!..()) + return 0 + if(locked) + return 0 + return ..() + +/obj/structure/closet/secure_closet/close() + if(..()) + if(broken) + icon_state = icon_off + return 1 + else + return 0 + +/obj/structure/closet/secure_closet/emp_act(severity) + for(var/obj/O in src) + O.emp_act(severity) + if(!broken) + if(prob(50/severity)) + locked = !locked + update_icon() + if(prob(20/severity) && !opened) + if(!locked) + open() + else + req_access = list() + req_access += pick(get_all_accesses()) + ..() + +/obj/structure/closet/secure_closet/proc/togglelock(mob/user) + if(opened) + to_chat(user, "Close the locker first.") + return + if(broken) + to_chat(user, "The locker appears to be broken.") + return + if(user.loc == src) + to_chat(user, "You can't reach the lock from inside.") + return + if(allowed(user)) + locked = !locked + playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) + visible_message("The locker has been [locked ? null : "un"]locked by [user].") + update_icon() + else + to_chat(user, "Access Denied") + +/obj/structure/closet/secure_closet/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/rcs)) + return ..() + + if(opened) + if(istype(W, /obj/item/grab)) + if(large) + MouseDrop_T(W:affecting, user) //act like they were dragged onto the closet + else + to_chat(user, "The locker is too small to stuff [W:affecting] into!") + if(isrobot(user)) + return + if(!user.drop_item()) //couldn't drop the item + to_chat(user, "\The [W] is stuck to your hand, you cannot put it in \the [src]!") + return + if(W) + W.forceMove(loc) + else if((istype(W, /obj/item/card/emag)||istype(W, /obj/item/melee/energy/blade)) && !broken) + emag_act(user) + else if(istype(W,/obj/item/stack/packageWrap) || istype(W,/obj/item/weldingtool)) + return ..(W, user) + else if(user.a_intent != INTENT_HARM) + togglelock(user) + else + return ..() + +/obj/structure/closet/secure_closet/emag_act(mob/user) + if(!broken) + broken = TRUE + locked = FALSE + icon_state = icon_off + flick(icon_broken, src) + to_chat(user, "You break the lock on \the [src].") + +/obj/structure/closet/secure_closet/attack_hand(mob/user) + add_fingerprint(user) + if(locked) + togglelock(user) + else + toggle(user) + +/obj/structure/closet/secure_closet/verb/verb_togglelock() + set src in oview(1) // One square distance + set category = "Object" + set name = "Toggle Lock" + + if(usr.incapacitated()) // Don't use it if you're not able to! Checks for stuns, ghost and restrain + return + + if(ishuman(usr)) + add_fingerprint(usr) + togglelock(usr) + else + to_chat(usr, "This mob type can't use this verb.") + +/obj/structure/closet/secure_closet/update_icon()//Putting the welded stuff in updateicon() so it's easy to overwrite for special cases (Fridges, cabinets, and whatnot) + overlays.Cut() + if(!opened) + if(locked) + icon_state = icon_locked + else + icon_state = icon_closed + if(welded) + overlays += "welded" + else + icon_state = icon_opened + +/obj/structure/closet/secure_closet/container_resist(var/mob/living/L) + var/breakout_time = 2 //2 minutes by default + if(opened) + if(L.loc == src) + L.forceMove(get_turf(src)) // Let's just be safe here + return //Door's open... wait, why are you in it's contents then? + if(!locked && !welded) + return //It's a secure closet, but isn't locked. Easily escapable from, no need to 'resist' + + //okay, so the closet is either welded or locked... resist!!! + L.changeNext_move(CLICK_CD_BREAKOUT) + L.last_special = world.time + CLICK_CD_BREAKOUT + to_chat(L, "You lean on the back of \the [src] and start pushing the door open. (this will take about [breakout_time] minutes)") + for(var/mob/O in viewers(src)) + O.show_message("The [src] begins to shake violently!", 1) + + + spawn(0) + if(do_after(usr,(breakout_time*60*10), target = src)) //minutes * 60seconds * 10deciseconds + if(!src || !L || L.stat != CONSCIOUS || L.loc != src || opened) //closet/user destroyed OR user dead/unconcious OR user no longer in closet OR closet opened + return + + //Perform the same set of checks as above for weld and lock status to determine if there is even still a point in 'resisting'... + if(!locked && !welded) + return + + //Well then break it! + desc = "It appears to be broken." + icon_state = icon_off + flick(icon_broken, src) + sleep(10) + flick(icon_broken, src) + sleep(10) + broken = 1 + locked = 0 + welded = 0 + update_icon() + to_chat(usr, "You successfully break out!") + for(var/mob/O in viewers(L.loc)) + O.show_message("\the [usr] successfully broke out of \the [src]!", 1) + if(istype(loc, /obj/structure/bigDelivery)) //Do this to prevent contents from being opened into nullspace (read: bluespace) + var/obj/structure/bigDelivery/BD = loc + BD.attack_hand(usr) + open() diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index e66cf96c40b..6d4b47df6ab 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -1,468 +1,468 @@ -/obj/structure/closet/secure_closet/captains - name = "captain's locker" - req_access = list(ACCESS_CAPTAIN) - icon_state = "capsecure1" - icon_closed = "capsecure" - icon_locked = "capsecure1" - icon_opened = "capsecureopen" - icon_broken = "capsecurebroken" - icon_off = "capsecureoff" - -/obj/structure/closet/secure_closet/captains/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/captain(src) - else - new /obj/item/storage/backpack/satchel_cap(src) - new /obj/item/book/manual/faxes(src) - new /obj/item/storage/backpack/duffel/captain(src) - new /obj/item/clothing/suit/captunic(src) - new /obj/item/clothing/suit/captunic/capjacket(src) - new /obj/item/clothing/suit/mantle/armor/captain(src) - new /obj/item/clothing/under/captainparade(src) - new /obj/item/clothing/head/caphat/parade(src) - new /obj/item/clothing/under/rank/captain(src) - new /obj/item/clothing/under/dress/dress_cap(src) - new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) - new /obj/item/cartridge/captain(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/clothing/shoes/laceup(src) - new /obj/item/radio/headset/heads/captain/alt(src) - new /obj/item/clothing/gloves/color/captain(src) - new /obj/item/storage/belt/rapier(src) - new /obj/item/gun/energy/gun(src) - new /obj/item/door_remote/captain(src) - new /obj/item/reagent_containers/food/drinks/mug/cap(src) - new /obj/item/tank/emergency_oxygen/double(src) - - -/obj/structure/closet/secure_closet/hop - name = "head of personnel's locker" - req_access = list(ACCESS_HOP) - icon_state = "hopsecure1" - icon_closed = "hopsecure" - icon_locked = "hopsecure1" - icon_opened = "hopsecureopen" - icon_broken = "hopsecurebroken" - icon_off = "hopsecureoff" - -/obj/structure/closet/secure_closet/hop/New() - ..() - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/head/hopcap(src) - new /obj/item/cartridge/hop(src) - new /obj/item/radio/headset/heads/hop(src) - new /obj/item/storage/box/ids(src) - new /obj/item/storage/box/PDAs(src) - new /obj/item/clothing/suit/armor/vest(src) - new /obj/item/gun/energy/gun/mini(src) - new /obj/item/flash(src) - new /obj/item/clothing/accessory/petcollar(src) - new /obj/item/door_remote/civillian(src) - new /obj/item/reagent_containers/food/drinks/mug/hop(src) - new /obj/item/clothing/accessory/medal/service(src) - -/obj/structure/closet/secure_closet/hop2 - name = "head of personnel's attire" - req_access = list(ACCESS_HOP) - icon_state = "hopsecure1" - icon_closed = "hopsecure" - icon_locked = "hopsecure1" - icon_opened = "hopsecureopen" - icon_broken = "hopsecurebroken" - icon_off = "hopsecureoff" - -/obj/structure/closet/secure_closet/hop2/New() - ..() - new /obj/item/clothing/under/rank/head_of_personnel(src) - new /obj/item/clothing/suit/mantle/armor/head_of_personnel(src) - new /obj/item/clothing/under/dress/dress_hop(src) - new /obj/item/clothing/under/dress/dress_hr(src) - new /obj/item/clothing/under/lawyer/female(src) - new /obj/item/clothing/under/lawyer/black(src) - new /obj/item/clothing/under/lawyer/red(src) - new /obj/item/clothing/under/lawyer/oldman(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/leather(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/under/rank/head_of_personnel_whimsy(src) - - -/obj/structure/closet/secure_closet/hos - name = "head of security's locker" - req_access = list(ACCESS_HOS) - icon_state = "hossecure1" - icon_closed = "hossecure" - icon_locked = "hossecure1" - icon_opened = "hossecureopen" - icon_broken = "hossecurebroken" - icon_off = "hossecureoff" - -/obj/structure/closet/secure_closet/hos/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/security(src) - else - new /obj/item/storage/backpack/satchel_sec(src) - new /obj/item/cartridge/hos(src) - new /obj/item/radio/headset/heads/hos/alt(src) - new /obj/item/clothing/under/rank/head_of_security(src) - new /obj/item/clothing/under/rank/head_of_security/formal(src) - new /obj/item/clothing/under/rank/head_of_security/corp(src) - new /obj/item/clothing/under/rank/head_of_security/skirt(src) - new /obj/item/clothing/suit/armor/hos(src) - new /obj/item/clothing/suit/armor/hos/alt(src) - new /obj/item/clothing/head/HoS(src) - new /obj/item/clothing/head/HoS/beret(src) - new /obj/item/clothing/suit/mantle/armor(src) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - new /obj/item/storage/lockbox/mindshield(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/clothing/mask/gas/sechailer/hos(src) - new /obj/item/shield/riot/tele(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/storage/belt/security/sec(src) - new /obj/item/gun/energy/gun/hos(src) - new /obj/item/door_remote/head_of_security(src) - new /obj/item/reagent_containers/food/drinks/mug/hos(src) - new /obj/item/organ/internal/cyberimp/eyes/hud/security(src) - new /obj/item/clothing/accessory/medal/security(src) - -/obj/structure/closet/secure_closet/warden - name = "warden's locker" - req_access = list(ACCESS_ARMORY) - icon_state = "wardensecure1" - icon_closed = "wardensecure" - icon_locked = "wardensecure1" - icon_opened = "wardensecureopen" - icon_broken = "wardensecurebroken" - icon_off = "wardensecureoff" - -/obj/structure/closet/secure_closet/warden/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/security(src) - else - new /obj/item/storage/backpack/satchel_sec(src) - new /obj/item/radio/headset/headset_sec/alt(src) - new /obj/item/clothing/suit/armor/vest/warden(src) - new /obj/item/clothing/head/warden(src) - new /obj/item/clothing/suit/armor/vest/warden/alt(src) - new /obj/item/clothing/head/beret/sec/warden(src) - new /obj/item/clothing/under/rank/warden(src) - new /obj/item/clothing/under/rank/warden/formal(src) - new /obj/item/clothing/under/rank/warden/corp(src) - new /obj/item/clothing/under/rank/warden/skirt(src) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/clothing/mask/gas/sechailer/warden(src) - new /obj/item/storage/box/zipties(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/gun/energy/gun/advtaser(src) - new /obj/item/storage/belt/security/sec(src) - new /obj/item/storage/box/holobadge(src) - new /obj/item/clothing/gloves/color/black/krav_maga/sec(src) - - -/obj/structure/closet/secure_closet/security - name = "security officer's locker" - req_access = list(ACCESS_SECURITY) - icon_state = "sec1" - icon_closed = "sec" - icon_locked = "sec1" - icon_opened = "secopen" - icon_broken = "secbroken" - icon_off = "secoff" - -/obj/structure/closet/secure_closet/security/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/security(src) - else - new /obj/item/storage/backpack/satchel_sec(src) - new /obj/item/clothing/suit/armor/vest/security(src) - new /obj/item/radio/headset/headset_sec/alt(src) - new /obj/item/clothing/head/soft/sec(src) - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/flash(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/storage/belt/security/sec(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/clothing/mask/gas/sechailer(src) - new /obj/item/clothing/glasses/hud/security/sunglasses(src) - new /obj/item/clothing/head/helmet(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/clothing/suit/armor/secjacket(src) - - -/obj/structure/closet/secure_closet/brigdoc - name = "brig physician's locker" - req_access = list(ACCESS_SECURITY) - icon_state = "securemed1" - icon_closed = "securemed" - icon_locked = "securemed1" - icon_opened = "securemedopen" - icon_broken = "securemedbroken" - icon_off = "securemedoff" - -/obj/structure/closet/secure_closet/brigdoc/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/medic(src) - else - new /obj/item/storage/backpack/satchel_med(src) - new /obj/item/reagent_containers/spray/pepper(src) - new /obj/item/flash(src) - new /obj/item/storage/firstaid/regular(src) - new /obj/item/storage/firstaid/fire(src) - new /obj/item/storage/firstaid/adv(src) - new /obj/item/storage/firstaid/o2(src) - new /obj/item/storage/firstaid/toxin(src) - new /obj/item/clothing/suit/storage/brigdoc(src) - new /obj/item/clothing/under/rank/security/brigphys(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/radio/headset/headset_sec/alt(src) - new /obj/item/clothing/shoes/sandal/white(src) - - -/obj/structure/closet/secure_closet/blueshield - name = "blueshield's locker" - req_access = list(ACCESS_BLUESHIELD) - icon_state = "bssecure1" - icon_closed = "bssecure" - icon_locked = "bssecure1" - icon_opened = "bssecureopen" - icon_broken = "bssecurebroken" - icon_off = "bssecureoff" - -/obj/structure/closet/secure_closet/blueshield/New() - ..() - new /obj/item/storage/briefcase(src) - new /obj/item/storage/firstaid/adv(src) - new /obj/item/pinpointer/crew(src) - new /obj/item/storage/belt/security/sec(src) - new /obj/item/grenade/flashbang(src) - new /obj/item/flash(src) - new /obj/item/restraints/handcuffs(src) - new /obj/item/melee/baton/loaded(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/glasses/hud/security/sunglasses/read_only(src) - new /obj/item/clothing/glasses/hud/health/sunglasses - new /obj/item/clothing/head/beret/centcom/officer(src) - new /obj/item/clothing/head/beret/centcom/officer/navy(src) - new /obj/item/clothing/suit/armor/vest/blueshield(src) - new /obj/item/clothing/suit/storage/blueshield(src) - new /obj/item/clothing/shoes/centcom(src) - new /obj/item/clothing/accessory/holster(src) - new /obj/item/clothing/accessory/blue(src) - new /obj/item/clothing/shoes/jackboots/jacksandals(src) - new /obj/item/clothing/under/rank/centcom/blueshield(src) - - -/obj/structure/closet/secure_closet/ntrep - name = "\improper Nanotrasen Representative's locker" - req_access = list(ACCESS_NTREP) - icon_state = "ntsecure1" - icon_closed = "ntsecure" - icon_locked = "ntsecure1" - icon_opened = "ntsecureopen" - icon_broken = "ntsecurebroken" - icon_off = "ntsecureoff" - -/obj/structure/closet/secure_closet/ntrep/New() - ..() - new /obj/item/book/manual/faxes(src) - new /obj/item/storage/briefcase(src) - new /obj/item/paicard(src) - new /obj/item/flash(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/gloves/color/white(src) - new /obj/item/clothing/shoes/centcom(src) - new /obj/item/clothing/under/lawyer/oldman(src) - new /obj/item/clothing/under/lawyer/black(src) - new /obj/item/clothing/under/lawyer/female(src) - new /obj/item/clothing/under/rank/centcom/representative(src) - new /obj/item/clothing/head/ntrep(src) - new /obj/item/clothing/shoes/sandal/fancy(src) - new /obj/item/storage/box/tapes(src) - new /obj/item/taperecorder(src) - - -/obj/structure/closet/secure_closet/security/cargo - -/obj/structure/closet/secure_closet/security/cargo/New() - ..() - new /obj/item/clothing/accessory/armband/cargo(src) - new /obj/item/encryptionkey/headset_cargo(src) - - -/obj/structure/closet/secure_closet/security/engine - -/obj/structure/closet/secure_closet/security/engine/New() - ..() - new /obj/item/clothing/accessory/armband/engine(src) - new /obj/item/encryptionkey/headset_eng(src) - - -/obj/structure/closet/secure_closet/security/science - -/obj/structure/closet/secure_closet/security/science/New() - ..() - new /obj/item/clothing/accessory/armband/science(src) - new /obj/item/encryptionkey/headset_sci(src) - - -/obj/structure/closet/secure_closet/security/med - -/obj/structure/closet/secure_closet/security/med/New() - ..() - new /obj/item/clothing/accessory/armband/medgreen(src) - new /obj/item/encryptionkey/headset_med(src) - - -/obj/structure/closet/secure_closet/detective - name = "detective's cabinet" - req_access = list(ACCESS_FORENSICS_LOCKERS) - icon_state = "cabinetdetective_locked" - icon_closed = "cabinetdetective" - icon_locked = "cabinetdetective_locked" - icon_opened = "cabinetdetective_open" - icon_broken = "cabinetdetective_broken" - icon_off = "cabinetdetective_broken" - resistance_flags = FLAMMABLE - max_integrity = 70 - -/obj/structure/closet/secure_closet/detective/New() - ..() - new /obj/item/clothing/under/det(src) - new /obj/item/clothing/suit/storage/det_suit(src) - new /obj/item/clothing/suit/storage/det_suit/forensics/blue(src) - new /obj/item/clothing/suit/storage/det_suit/forensics/red(src) - new /obj/item/clothing/gloves/color/black/forensics(src) - new /obj/item/clothing/head/det_hat(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/storage/box/evidence(src) - new /obj/item/clipboard(src) - new /obj/item/radio/headset/headset_sec/alt(src) - new /obj/item/detective_scanner(src) - new /obj/item/clothing/suit/armor/vest/det_suit(src) - new /obj/item/ammo_box/c38(src) - new /obj/item/ammo_box/c38(src) - new /obj/item/gun/projectile/revolver/detective(src) - new /obj/item/clothing/accessory/holster/armpit(src) - new /obj/item/clothing/glasses/sunglasses/yeah(src) - new /obj/item/flashlight/seclite(src) - new /obj/item/holosign_creator/security(src) - new /obj/item/clothing/accessory/black(src) - new /obj/item/taperecorder(src) - new /obj/item/storage/box/tapes(src) - -/obj/structure/closet/secure_closet/detective/update_icon() - if(broken) - icon_state = icon_broken - else - if(!opened) - if(locked) - icon_state = icon_locked - else - icon_state = icon_closed - else - icon_state = icon_opened - - -/obj/structure/closet/secure_closet/injection - name = "lethal injections locker" - req_access = list(ACCESS_SECURITY) - -/obj/structure/closet/secure_closet/injection/New() - ..() - new /obj/item/reagent_containers/syringe/lethal(src) - new /obj/item/reagent_containers/syringe/lethal(src) - - -/obj/structure/closet/secure_closet/brig - name = "brig locker" - req_access = list(ACCESS_BRIG) - anchored = 1 - var/id = null - -/obj/structure/closet/secure_closet/brig/New() - ..() - new /obj/item/clothing/under/color/orange/prison(src) - new /obj/item/clothing/shoes/orange(src) - new /obj/item/card/id/prisoner/random(src) - new /obj/item/radio/headset(src) - - -/obj/structure/closet/secure_closet/courtroom - name = "courtroom locker" - req_access = list(ACCESS_COURT) - -/obj/structure/closet/secure_closet/courtroom/New() - ..() - new /obj/item/clothing/shoes/brown(src) - new /obj/item/paper/Court (src) - new /obj/item/paper/Court (src) - new /obj/item/paper/Court (src) - new /obj/item/pen (src) - new /obj/item/clothing/suit/judgerobe (src) - new /obj/item/clothing/head/powdered_wig (src) - new /obj/item/storage/briefcase(src) - - -/obj/structure/closet/secure_closet/wall - name = "wall locker" - req_access = list(ACCESS_SECURITY) - icon_state = "wall-locker1" - density = 1 - icon_closed = "wall-locker" - icon_locked = "wall-locker1" - icon_opened = "wall-lockeropen" - icon_broken = "wall-lockerbroken" - icon_off = "wall-lockeroff" - - //too small to put a man in - large = 0 - -/obj/structure/closet/secure_closet/wall/update_icon() - if(broken) - icon_state = icon_broken - else - if(!opened) - if(locked) - icon_state = icon_locked - else - icon_state = icon_closed - else - icon_state = icon_opened - -/obj/structure/closet/secure_closet/magistrate - name = "\improper Magistrate's locker" - req_access = list(ACCESS_MAGISTRATE) - icon_state = "magistratesecure1" - icon_closed = "magistratesecure" - icon_locked = "magistratesecure1" - icon_opened = "magistratesecureopen" - icon_broken = "magistratesecurebroken" - icon_off = "magistratesecureoff" - -/obj/structure/closet/secure_closet/magistrate/New() - ..() - new /obj/item/book/manual/faxes(src) - new /obj/item/storage/secure/briefcase(src) - new /obj/item/flash(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/clothing/gloves/color/white(src) - new /obj/item/clothing/shoes/centcom(src) - new /obj/item/clothing/under/suit_jacket/really_black(src) - new /obj/item/clothing/under/rank/centcom/magistrate(src) - new /obj/item/clothing/suit/judgerobe(src) - new /obj/item/clothing/head/powdered_wig(src) - new /obj/item/gavelblock(src) - new /obj/item/gavelhammer(src) - new /obj/item/clothing/head/justice_wig(src) - new /obj/item/clothing/accessory/medal/legal(src) \ No newline at end of file +/obj/structure/closet/secure_closet/captains + name = "captain's locker" + req_access = list(ACCESS_CAPTAIN) + icon_state = "capsecure1" + icon_closed = "capsecure" + icon_locked = "capsecure1" + icon_opened = "capsecureopen" + icon_broken = "capsecurebroken" + icon_off = "capsecureoff" + +/obj/structure/closet/secure_closet/captains/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/captain(src) + else + new /obj/item/storage/backpack/satchel_cap(src) + new /obj/item/book/manual/faxes(src) + new /obj/item/storage/backpack/duffel/captain(src) + new /obj/item/clothing/suit/captunic(src) + new /obj/item/clothing/suit/captunic/capjacket(src) + new /obj/item/clothing/suit/mantle/armor/captain(src) + new /obj/item/clothing/under/captainparade(src) + new /obj/item/clothing/head/caphat/parade(src) + new /obj/item/clothing/under/rank/captain(src) + new /obj/item/clothing/under/dress/dress_cap(src) + new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) + new /obj/item/cartridge/captain(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/clothing/shoes/laceup(src) + new /obj/item/radio/headset/heads/captain/alt(src) + new /obj/item/clothing/gloves/color/captain(src) + new /obj/item/storage/belt/rapier(src) + new /obj/item/gun/energy/gun(src) + new /obj/item/door_remote/captain(src) + new /obj/item/reagent_containers/food/drinks/mug/cap(src) + new /obj/item/tank/emergency_oxygen/double(src) + + +/obj/structure/closet/secure_closet/hop + name = "head of personnel's locker" + req_access = list(ACCESS_HOP) + icon_state = "hopsecure1" + icon_closed = "hopsecure" + icon_locked = "hopsecure1" + icon_opened = "hopsecureopen" + icon_broken = "hopsecurebroken" + icon_off = "hopsecureoff" + +/obj/structure/closet/secure_closet/hop/New() + ..() + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/head/hopcap(src) + new /obj/item/cartridge/hop(src) + new /obj/item/radio/headset/heads/hop(src) + new /obj/item/storage/box/ids(src) + new /obj/item/storage/box/PDAs(src) + new /obj/item/clothing/suit/armor/vest(src) + new /obj/item/gun/energy/gun/mini(src) + new /obj/item/flash(src) + new /obj/item/clothing/accessory/petcollar(src) + new /obj/item/door_remote/civillian(src) + new /obj/item/reagent_containers/food/drinks/mug/hop(src) + new /obj/item/clothing/accessory/medal/service(src) + +/obj/structure/closet/secure_closet/hop2 + name = "head of personnel's attire" + req_access = list(ACCESS_HOP) + icon_state = "hopsecure1" + icon_closed = "hopsecure" + icon_locked = "hopsecure1" + icon_opened = "hopsecureopen" + icon_broken = "hopsecurebroken" + icon_off = "hopsecureoff" + +/obj/structure/closet/secure_closet/hop2/New() + ..() + new /obj/item/clothing/under/rank/head_of_personnel(src) + new /obj/item/clothing/suit/mantle/armor/head_of_personnel(src) + new /obj/item/clothing/under/dress/dress_hop(src) + new /obj/item/clothing/under/dress/dress_hr(src) + new /obj/item/clothing/under/lawyer/female(src) + new /obj/item/clothing/under/lawyer/black(src) + new /obj/item/clothing/under/lawyer/red(src) + new /obj/item/clothing/under/lawyer/oldman(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/leather(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/under/rank/head_of_personnel_whimsy(src) + + +/obj/structure/closet/secure_closet/hos + name = "head of security's locker" + req_access = list(ACCESS_HOS) + icon_state = "hossecure1" + icon_closed = "hossecure" + icon_locked = "hossecure1" + icon_opened = "hossecureopen" + icon_broken = "hossecurebroken" + icon_off = "hossecureoff" + +/obj/structure/closet/secure_closet/hos/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/security(src) + else + new /obj/item/storage/backpack/satchel_sec(src) + new /obj/item/cartridge/hos(src) + new /obj/item/radio/headset/heads/hos/alt(src) + new /obj/item/clothing/under/rank/head_of_security(src) + new /obj/item/clothing/under/rank/head_of_security/formal(src) + new /obj/item/clothing/under/rank/head_of_security/corp(src) + new /obj/item/clothing/under/rank/head_of_security/skirt(src) + new /obj/item/clothing/suit/armor/hos(src) + new /obj/item/clothing/suit/armor/hos/alt(src) + new /obj/item/clothing/head/HoS(src) + new /obj/item/clothing/head/HoS/beret(src) + new /obj/item/clothing/suit/mantle/armor(src) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + new /obj/item/storage/lockbox/mindshield(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/clothing/mask/gas/sechailer/hos(src) + new /obj/item/shield/riot/tele(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/storage/belt/security/sec(src) + new /obj/item/gun/energy/gun/hos(src) + new /obj/item/door_remote/head_of_security(src) + new /obj/item/reagent_containers/food/drinks/mug/hos(src) + new /obj/item/organ/internal/cyberimp/eyes/hud/security(src) + new /obj/item/clothing/accessory/medal/security(src) + +/obj/structure/closet/secure_closet/warden + name = "warden's locker" + req_access = list(ACCESS_ARMORY) + icon_state = "wardensecure1" + icon_closed = "wardensecure" + icon_locked = "wardensecure1" + icon_opened = "wardensecureopen" + icon_broken = "wardensecurebroken" + icon_off = "wardensecureoff" + +/obj/structure/closet/secure_closet/warden/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/security(src) + else + new /obj/item/storage/backpack/satchel_sec(src) + new /obj/item/radio/headset/headset_sec/alt(src) + new /obj/item/clothing/suit/armor/vest/warden(src) + new /obj/item/clothing/head/warden(src) + new /obj/item/clothing/suit/armor/vest/warden/alt(src) + new /obj/item/clothing/head/beret/sec/warden(src) + new /obj/item/clothing/under/rank/warden(src) + new /obj/item/clothing/under/rank/warden/formal(src) + new /obj/item/clothing/under/rank/warden/corp(src) + new /obj/item/clothing/under/rank/warden/skirt(src) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/clothing/mask/gas/sechailer/warden(src) + new /obj/item/storage/box/zipties(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/gun/energy/gun/advtaser(src) + new /obj/item/storage/belt/security/sec(src) + new /obj/item/storage/box/holobadge(src) + new /obj/item/clothing/gloves/color/black/krav_maga/sec(src) + + +/obj/structure/closet/secure_closet/security + name = "security officer's locker" + req_access = list(ACCESS_SECURITY) + icon_state = "sec1" + icon_closed = "sec" + icon_locked = "sec1" + icon_opened = "secopen" + icon_broken = "secbroken" + icon_off = "secoff" + +/obj/structure/closet/secure_closet/security/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/security(src) + else + new /obj/item/storage/backpack/satchel_sec(src) + new /obj/item/clothing/suit/armor/vest/security(src) + new /obj/item/radio/headset/headset_sec/alt(src) + new /obj/item/clothing/head/soft/sec(src) + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/flash(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/storage/belt/security/sec(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/clothing/mask/gas/sechailer(src) + new /obj/item/clothing/glasses/hud/security/sunglasses(src) + new /obj/item/clothing/head/helmet(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/clothing/suit/armor/secjacket(src) + + +/obj/structure/closet/secure_closet/brigdoc + name = "brig physician's locker" + req_access = list(ACCESS_SECURITY) + icon_state = "securemed1" + icon_closed = "securemed" + icon_locked = "securemed1" + icon_opened = "securemedopen" + icon_broken = "securemedbroken" + icon_off = "securemedoff" + +/obj/structure/closet/secure_closet/brigdoc/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/medic(src) + else + new /obj/item/storage/backpack/satchel_med(src) + new /obj/item/reagent_containers/spray/pepper(src) + new /obj/item/flash(src) + new /obj/item/storage/firstaid/regular(src) + new /obj/item/storage/firstaid/fire(src) + new /obj/item/storage/firstaid/adv(src) + new /obj/item/storage/firstaid/o2(src) + new /obj/item/storage/firstaid/toxin(src) + new /obj/item/clothing/suit/storage/brigdoc(src) + new /obj/item/clothing/under/rank/security/brigphys(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/radio/headset/headset_sec/alt(src) + new /obj/item/clothing/shoes/sandal/white(src) + + +/obj/structure/closet/secure_closet/blueshield + name = "blueshield's locker" + req_access = list(ACCESS_BLUESHIELD) + icon_state = "bssecure1" + icon_closed = "bssecure" + icon_locked = "bssecure1" + icon_opened = "bssecureopen" + icon_broken = "bssecurebroken" + icon_off = "bssecureoff" + +/obj/structure/closet/secure_closet/blueshield/New() + ..() + new /obj/item/storage/briefcase(src) + new /obj/item/storage/firstaid/adv(src) + new /obj/item/pinpointer/crew(src) + new /obj/item/storage/belt/security/sec(src) + new /obj/item/grenade/flashbang(src) + new /obj/item/flash(src) + new /obj/item/restraints/handcuffs(src) + new /obj/item/melee/baton/loaded(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/glasses/hud/security/sunglasses/read_only(src) + new /obj/item/clothing/glasses/hud/health/sunglasses + new /obj/item/clothing/head/beret/centcom/officer(src) + new /obj/item/clothing/head/beret/centcom/officer/navy(src) + new /obj/item/clothing/suit/armor/vest/blueshield(src) + new /obj/item/clothing/suit/storage/blueshield(src) + new /obj/item/clothing/shoes/centcom(src) + new /obj/item/clothing/accessory/holster(src) + new /obj/item/clothing/accessory/blue(src) + new /obj/item/clothing/shoes/jackboots/jacksandals(src) + new /obj/item/clothing/under/rank/centcom/blueshield(src) + + +/obj/structure/closet/secure_closet/ntrep + name = "\improper Nanotrasen Representative's locker" + req_access = list(ACCESS_NTREP) + icon_state = "ntsecure1" + icon_closed = "ntsecure" + icon_locked = "ntsecure1" + icon_opened = "ntsecureopen" + icon_broken = "ntsecurebroken" + icon_off = "ntsecureoff" + +/obj/structure/closet/secure_closet/ntrep/New() + ..() + new /obj/item/book/manual/faxes(src) + new /obj/item/storage/briefcase(src) + new /obj/item/paicard(src) + new /obj/item/flash(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/gloves/color/white(src) + new /obj/item/clothing/shoes/centcom(src) + new /obj/item/clothing/under/lawyer/oldman(src) + new /obj/item/clothing/under/lawyer/black(src) + new /obj/item/clothing/under/lawyer/female(src) + new /obj/item/clothing/under/rank/centcom/representative(src) + new /obj/item/clothing/head/ntrep(src) + new /obj/item/clothing/shoes/sandal/fancy(src) + new /obj/item/storage/box/tapes(src) + new /obj/item/taperecorder(src) + + +/obj/structure/closet/secure_closet/security/cargo + +/obj/structure/closet/secure_closet/security/cargo/New() + ..() + new /obj/item/clothing/accessory/armband/cargo(src) + new /obj/item/encryptionkey/headset_cargo(src) + + +/obj/structure/closet/secure_closet/security/engine + +/obj/structure/closet/secure_closet/security/engine/New() + ..() + new /obj/item/clothing/accessory/armband/engine(src) + new /obj/item/encryptionkey/headset_eng(src) + + +/obj/structure/closet/secure_closet/security/science + +/obj/structure/closet/secure_closet/security/science/New() + ..() + new /obj/item/clothing/accessory/armband/science(src) + new /obj/item/encryptionkey/headset_sci(src) + + +/obj/structure/closet/secure_closet/security/med + +/obj/structure/closet/secure_closet/security/med/New() + ..() + new /obj/item/clothing/accessory/armband/medgreen(src) + new /obj/item/encryptionkey/headset_med(src) + + +/obj/structure/closet/secure_closet/detective + name = "detective's cabinet" + req_access = list(ACCESS_FORENSICS_LOCKERS) + icon_state = "cabinetdetective_locked" + icon_closed = "cabinetdetective" + icon_locked = "cabinetdetective_locked" + icon_opened = "cabinetdetective_open" + icon_broken = "cabinetdetective_broken" + icon_off = "cabinetdetective_broken" + resistance_flags = FLAMMABLE + max_integrity = 70 + +/obj/structure/closet/secure_closet/detective/New() + ..() + new /obj/item/clothing/under/det(src) + new /obj/item/clothing/suit/storage/det_suit(src) + new /obj/item/clothing/suit/storage/det_suit/forensics/blue(src) + new /obj/item/clothing/suit/storage/det_suit/forensics/red(src) + new /obj/item/clothing/gloves/color/black/forensics(src) + new /obj/item/clothing/head/det_hat(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/storage/box/evidence(src) + new /obj/item/clipboard(src) + new /obj/item/radio/headset/headset_sec/alt(src) + new /obj/item/detective_scanner(src) + new /obj/item/clothing/suit/armor/vest/det_suit(src) + new /obj/item/ammo_box/c38(src) + new /obj/item/ammo_box/c38(src) + new /obj/item/gun/projectile/revolver/detective(src) + new /obj/item/clothing/accessory/holster/armpit(src) + new /obj/item/clothing/glasses/sunglasses/yeah(src) + new /obj/item/flashlight/seclite(src) + new /obj/item/holosign_creator/security(src) + new /obj/item/clothing/accessory/black(src) + new /obj/item/taperecorder(src) + new /obj/item/storage/box/tapes(src) + +/obj/structure/closet/secure_closet/detective/update_icon() + if(broken) + icon_state = icon_broken + else + if(!opened) + if(locked) + icon_state = icon_locked + else + icon_state = icon_closed + else + icon_state = icon_opened + + +/obj/structure/closet/secure_closet/injection + name = "lethal injections locker" + req_access = list(ACCESS_SECURITY) + +/obj/structure/closet/secure_closet/injection/New() + ..() + new /obj/item/reagent_containers/syringe/lethal(src) + new /obj/item/reagent_containers/syringe/lethal(src) + + +/obj/structure/closet/secure_closet/brig + name = "brig locker" + req_access = list(ACCESS_BRIG) + anchored = 1 + var/id = null + +/obj/structure/closet/secure_closet/brig/New() + ..() + new /obj/item/clothing/under/color/orange/prison(src) + new /obj/item/clothing/shoes/orange(src) + new /obj/item/card/id/prisoner/random(src) + new /obj/item/radio/headset(src) + + +/obj/structure/closet/secure_closet/courtroom + name = "courtroom locker" + req_access = list(ACCESS_COURT) + +/obj/structure/closet/secure_closet/courtroom/New() + ..() + new /obj/item/clothing/shoes/brown(src) + new /obj/item/paper/Court (src) + new /obj/item/paper/Court (src) + new /obj/item/paper/Court (src) + new /obj/item/pen (src) + new /obj/item/clothing/suit/judgerobe (src) + new /obj/item/clothing/head/powdered_wig (src) + new /obj/item/storage/briefcase(src) + + +/obj/structure/closet/secure_closet/wall + name = "wall locker" + req_access = list(ACCESS_SECURITY) + icon_state = "wall-locker1" + density = 1 + icon_closed = "wall-locker" + icon_locked = "wall-locker1" + icon_opened = "wall-lockeropen" + icon_broken = "wall-lockerbroken" + icon_off = "wall-lockeroff" + + //too small to put a man in + large = 0 + +/obj/structure/closet/secure_closet/wall/update_icon() + if(broken) + icon_state = icon_broken + else + if(!opened) + if(locked) + icon_state = icon_locked + else + icon_state = icon_closed + else + icon_state = icon_opened + +/obj/structure/closet/secure_closet/magistrate + name = "\improper Magistrate's locker" + req_access = list(ACCESS_MAGISTRATE) + icon_state = "magistratesecure1" + icon_closed = "magistratesecure" + icon_locked = "magistratesecure1" + icon_opened = "magistratesecureopen" + icon_broken = "magistratesecurebroken" + icon_off = "magistratesecureoff" + +/obj/structure/closet/secure_closet/magistrate/New() + ..() + new /obj/item/book/manual/faxes(src) + new /obj/item/storage/secure/briefcase(src) + new /obj/item/flash(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/clothing/gloves/color/white(src) + new /obj/item/clothing/shoes/centcom(src) + new /obj/item/clothing/under/suit_jacket/really_black(src) + new /obj/item/clothing/under/rank/centcom/magistrate(src) + new /obj/item/clothing/suit/judgerobe(src) + new /obj/item/clothing/head/powdered_wig(src) + new /obj/item/gavelblock(src) + new /obj/item/gavelhammer(src) + new /obj/item/clothing/head/justice_wig(src) + new /obj/item/clothing/accessory/medal/legal(src) diff --git a/code/game/objects/structures/crates_lockers/closets/statue.dm b/code/game/objects/structures/crates_lockers/closets/statue.dm index 3cd753740fb..501abd4922d 100644 --- a/code/game/objects/structures/crates_lockers/closets/statue.dm +++ b/code/game/objects/structures/crates_lockers/closets/statue.dm @@ -114,4 +114,4 @@ if(user) user.dust() dump_contents() - visible_message("[src] shatters!. ") \ No newline at end of file + visible_message("[src] shatters!. ") diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm index 01a9affbe43..d95eac2a0ac 100644 --- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm +++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm @@ -1,149 +1,149 @@ -/obj/structure/closet/syndicate - name = "armoury closet" - desc = "Why is this here?" - icon_state = "syndicate" - icon_closed = "syndicate" - icon_opened = "syndicateopen" - - -/obj/structure/closet/syndicate/personal - desc = "It's a storage unit for operative gear." - -/obj/structure/closet/syndicate/personal/New() - ..() - new /obj/item/clothing/under/syndicate(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/storage/belt/military(src) - new /obj/item/crowbar/red(src) - new /obj/item/clothing/glasses/night(src) - -/obj/structure/closet/syndicate/suits - desc = "It's a storage unit for operative space gear." - -/obj/structure/closet/syndicate/suits/New() - ..() - new /obj/item/clothing/mask/gas/syndicate(src) - new /obj/item/clothing/suit/space/hardsuit/syndi(src) - new /obj/item/tank/jetpack/oxygen/harness(src) - -/obj/structure/closet/syndicate/nuclear - desc = "It's a storage unit for a Syndicate boarding party." - -/obj/structure/closet/syndicate/nuclear/New() - ..() - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/storage/box/teargas(src) - new /obj/item/storage/box/flashbangs(src) - new /obj/item/storage/backpack/duffel/syndie/med(src) - new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) - new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) - new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) - new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) - new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) - new /obj/item/pda/syndicate(src) - -/obj/structure/closet/syndicate/sst - desc = "It's a storage unit for an elite syndicate strike team's gear." - -/obj/structure/closet/syndicate/sst/New() - ..() - new /obj/item/ammo_box/magazine/mm556x45(src) - new /obj/item/gun/projectile/automatic/l6_saw(src) - new /obj/item/tank/jetpack/oxygen/harness(src) - new /obj/item/storage/belt/military/sst(src) - new /obj/item/clothing/glasses/thermal(src) - new /obj/item/clothing/shoes/magboots/syndie/advance(src) - new /obj/item/clothing/mask/gas/syndicate(src) - new /obj/item/clothing/suit/space/hardsuit/syndi/elite/sst(src) - -/obj/structure/closet/syndicate/resources/ - desc = "An old, dusty locker." - - New() - ..() - var/common_min = 30 //Minimum amount of minerals in the stack for common minerals - var/common_max = 50 //Maximum amount of HONK in the stack for HONK common minerals - var/rare_min = 5 //Minimum HONK of HONK in the stack HONK HONK rare minerals - var/rare_max = 20 //Maximum HONK HONK HONK in the HONK for HONK rare HONK - - var/pickednum = rand(1, 50) - - //Sad trombone - if(pickednum == 1) - var/obj/item/paper/P = new /obj/item/paper(src) - P.name = "IOU" - P.info = "Sorry man, we needed the money so we sold your stash. It's ok, we'll double our money for sure this time!" - - //Metal (common ore) - if(pickednum >= 2) - new /obj/item/stack/sheet/metal(src, rand(common_min, common_max)) - - //Glass (common ore) - if(pickednum >= 5) - new /obj/item/stack/sheet/glass(src, rand(common_min, common_max)) - - //Plasteel (common ore) Because it has a million more uses then plasma - if(pickednum >= 10) - new /obj/item/stack/sheet/plasteel(src, rand(common_min, common_max)) - - //Plasma (rare ore) - if(pickednum >= 15) - new /obj/item/stack/sheet/mineral/plasma(src, rand(rare_min, rare_max)) - - //Silver (rare ore) - if(pickednum >= 20) - new /obj/item/stack/sheet/mineral/silver(src, rand(rare_min, rare_max)) - - //Gold (rare ore) - if(pickednum >= 30) - new /obj/item/stack/sheet/mineral/gold(src, rand(rare_min, rare_max)) - - //Uranium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/uranium(src, rand(rare_min, rare_max)) - - //Titanium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/titanium(src, rand(rare_min, rare_max)) - - //Plastitanium (rare ore) - if(pickednum >= 40) - new /obj/item/stack/sheet/mineral/plastitanium(src, rand(rare_min, rare_max)) - - //Diamond (rare HONK) - if(pickednum >= 45) - new /obj/item/stack/sheet/mineral/diamond(src, rand(rare_min, rare_max)) - - //Jetpack (You hit the jackpot!) - if(pickednum == 50) - new /obj/item/tank/jetpack/carbondioxide(src) - -/obj/structure/closet/syndicate/resources/everything - desc = "It's an emergency storage closet for repairs." - - New() - ..() - var/list/resources = list( - /obj/item/stack/sheet/metal, - /obj/item/stack/sheet/glass, - /obj/item/stack/sheet/mineral/gold, - /obj/item/stack/sheet/mineral/silver, - /obj/item/stack/sheet/mineral/plasma, - /obj/item/stack/sheet/mineral/uranium, - /obj/item/stack/sheet/mineral/diamond, - /obj/item/stack/sheet/mineral/bananium, - /obj/item/stack/sheet/mineral/titanium, - /obj/item/stack/sheet/mineral/plastitanium, - /obj/item/stack/sheet/plasteel, - /obj/item/stack/rods - ) - - for(var/i in 1 to 2) - for(var/res in resources) - var/obj/item/stack/R = new res(src) - R.amount = R.max_amount \ No newline at end of file +/obj/structure/closet/syndicate + name = "armoury closet" + desc = "Why is this here?" + icon_state = "syndicate" + icon_closed = "syndicate" + icon_opened = "syndicateopen" + + +/obj/structure/closet/syndicate/personal + desc = "It's a storage unit for operative gear." + +/obj/structure/closet/syndicate/personal/New() + ..() + new /obj/item/clothing/under/syndicate(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/storage/belt/military(src) + new /obj/item/crowbar/red(src) + new /obj/item/clothing/glasses/night(src) + +/obj/structure/closet/syndicate/suits + desc = "It's a storage unit for operative space gear." + +/obj/structure/closet/syndicate/suits/New() + ..() + new /obj/item/clothing/mask/gas/syndicate(src) + new /obj/item/clothing/suit/space/hardsuit/syndi(src) + new /obj/item/tank/jetpack/oxygen/harness(src) + +/obj/structure/closet/syndicate/nuclear + desc = "It's a storage unit for a Syndicate boarding party." + +/obj/structure/closet/syndicate/nuclear/New() + ..() + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/storage/box/teargas(src) + new /obj/item/storage/box/flashbangs(src) + new /obj/item/storage/backpack/duffel/syndie/med(src) + new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) + new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) + new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) + new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) + new /obj/item/gun/projectile/automatic/shotgun/bulldog(src) + new /obj/item/pda/syndicate(src) + +/obj/structure/closet/syndicate/sst + desc = "It's a storage unit for an elite syndicate strike team's gear." + +/obj/structure/closet/syndicate/sst/New() + ..() + new /obj/item/ammo_box/magazine/mm556x45(src) + new /obj/item/gun/projectile/automatic/l6_saw(src) + new /obj/item/tank/jetpack/oxygen/harness(src) + new /obj/item/storage/belt/military/sst(src) + new /obj/item/clothing/glasses/thermal(src) + new /obj/item/clothing/shoes/magboots/syndie/advance(src) + new /obj/item/clothing/mask/gas/syndicate(src) + new /obj/item/clothing/suit/space/hardsuit/syndi/elite/sst(src) + +/obj/structure/closet/syndicate/resources/ + desc = "An old, dusty locker." + + New() + ..() + var/common_min = 30 //Minimum amount of minerals in the stack for common minerals + var/common_max = 50 //Maximum amount of HONK in the stack for HONK common minerals + var/rare_min = 5 //Minimum HONK of HONK in the stack HONK HONK rare minerals + var/rare_max = 20 //Maximum HONK HONK HONK in the HONK for HONK rare HONK + + var/pickednum = rand(1, 50) + + //Sad trombone + if(pickednum == 1) + var/obj/item/paper/P = new /obj/item/paper(src) + P.name = "IOU" + P.info = "Sorry man, we needed the money so we sold your stash. It's ok, we'll double our money for sure this time!" + + //Metal (common ore) + if(pickednum >= 2) + new /obj/item/stack/sheet/metal(src, rand(common_min, common_max)) + + //Glass (common ore) + if(pickednum >= 5) + new /obj/item/stack/sheet/glass(src, rand(common_min, common_max)) + + //Plasteel (common ore) Because it has a million more uses then plasma + if(pickednum >= 10) + new /obj/item/stack/sheet/plasteel(src, rand(common_min, common_max)) + + //Plasma (rare ore) + if(pickednum >= 15) + new /obj/item/stack/sheet/mineral/plasma(src, rand(rare_min, rare_max)) + + //Silver (rare ore) + if(pickednum >= 20) + new /obj/item/stack/sheet/mineral/silver(src, rand(rare_min, rare_max)) + + //Gold (rare ore) + if(pickednum >= 30) + new /obj/item/stack/sheet/mineral/gold(src, rand(rare_min, rare_max)) + + //Uranium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/uranium(src, rand(rare_min, rare_max)) + + //Titanium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/titanium(src, rand(rare_min, rare_max)) + + //Plastitanium (rare ore) + if(pickednum >= 40) + new /obj/item/stack/sheet/mineral/plastitanium(src, rand(rare_min, rare_max)) + + //Diamond (rare HONK) + if(pickednum >= 45) + new /obj/item/stack/sheet/mineral/diamond(src, rand(rare_min, rare_max)) + + //Jetpack (You hit the jackpot!) + if(pickednum == 50) + new /obj/item/tank/jetpack/carbondioxide(src) + +/obj/structure/closet/syndicate/resources/everything + desc = "It's an emergency storage closet for repairs." + + New() + ..() + var/list/resources = list( + /obj/item/stack/sheet/metal, + /obj/item/stack/sheet/glass, + /obj/item/stack/sheet/mineral/gold, + /obj/item/stack/sheet/mineral/silver, + /obj/item/stack/sheet/mineral/plasma, + /obj/item/stack/sheet/mineral/uranium, + /obj/item/stack/sheet/mineral/diamond, + /obj/item/stack/sheet/mineral/bananium, + /obj/item/stack/sheet/mineral/titanium, + /obj/item/stack/sheet/mineral/plastitanium, + /obj/item/stack/sheet/plasteel, + /obj/item/stack/rods + ) + + for(var/i in 1 to 2) + for(var/res in resources) + var/obj/item/stack/R = new res(src) + R.amount = R.max_amount diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm index 504d2b29644..0b80e0f7ce1 100644 --- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm @@ -1,226 +1,226 @@ -/* Utility Closets - * Contains: - * Emergency Closet - * Fire Closet - * Tool Closet - * Radiation Closet - * Bombsuit Closet - * Hydrant - * First Aid - */ - -/* - * Emergency Closet - */ -/obj/structure/closet/emcloset - name = "emergency closet" - desc = "It's a storage unit for emergency breathmasks and o2 tanks." - icon_state = "emergency" - icon_closed = "emergency" - icon_opened = "emergencyopen" - -/obj/structure/closet/emcloset/anchored - anchored = TRUE - -/obj/structure/closet/emcloset/New() - ..() - - switch(pickweight(list("small" = 55, "aid" = 25, "tank" = 10, "both" = 10, "nothing" = 0, "delete" = 0))) - if("small") - new /obj/item/tank/emergency_oxygen(src) - new /obj/item/tank/emergency_oxygen(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/clothing/mask/breath(src) - if("aid") - new /obj/item/tank/emergency_oxygen(src) - new /obj/item/storage/toolbox/emergency(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/storage/firstaid/o2(src) - if("tank") - new /obj/item/tank/emergency_oxygen/engi(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/tank/emergency_oxygen/engi(src) - new /obj/item/clothing/mask/breath(src) - if("both") - new /obj/item/storage/toolbox/emergency(src) - new /obj/item/tank/emergency_oxygen/engi(src) - new /obj/item/clothing/mask/breath(src) - new /obj/item/storage/firstaid/o2(src) - if("nothing") - // doot - - // teehee - Ah, tg coders... - if("delete") - qdel(src) - - //If you want to re-add fire, just add "fire" = 15 to the pick list. - /*if("fire") - new /obj/structure/closet/firecloset(src.loc) - qdel(src)*/ - -/obj/structure/closet/emcloset/legacy/New() - new /obj/item/tank/oxygen(src) - new /obj/item/clothing/mask/gas(src) - -/* - * Fire Closet - */ -/obj/structure/closet/firecloset - name = "fire-safety closet" - desc = "It's a storage unit for fire-fighting supplies." - icon_state = "firecloset" - icon_closed = "firecloset" - icon_opened = "fireclosetopen" - -/obj/structure/closet/firecloset/New() - ..() - - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/tank/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - -/obj/structure/closet/firecloset/full/New() - ..() - contents.Cut() - - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/flashlight(src) - new /obj/item/tank/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - - -/* - * Tool Closet - */ -/obj/structure/closet/toolcloset - name = "tool closet" - desc = "It's a storage unit for tools." - icon_state = "toolcloset" - icon_closed = "toolcloset" - icon_opened = "toolclosetopen" - -/obj/structure/closet/toolcloset/New() - ..() - if(prob(40)) - new /obj/item/clothing/suit/storage/hazardvest(src) - if(prob(70)) - new /obj/item/flashlight(src) - if(prob(70)) - new /obj/item/screwdriver(src) - if(prob(70)) - new /obj/item/wrench(src) - if(prob(70)) - new /obj/item/weldingtool(src) - if(prob(70)) - new /obj/item/crowbar(src) - if(prob(70)) - new /obj/item/wirecutters(src) - if(prob(70)) - new /obj/item/t_scanner(src) - if(prob(20)) - new /obj/item/storage/belt/utility(src) - if(prob(30)) - new /obj/item/stack/cable_coil/random(src) - if(prob(30)) - new /obj/item/stack/cable_coil/random(src) - if(prob(30)) - new /obj/item/stack/cable_coil/random(src) - if(prob(20)) - new /obj/item/multitool(src) - if(prob(5)) - new /obj/item/clothing/gloves/color/yellow(src) - if(prob(40)) - new /obj/item/clothing/head/hardhat(src) - - -/* - * Radiation Closet - */ -/obj/structure/closet/radiation - name = "radiation suit closet" - desc = "It's a storage unit for rad-protective suits." - icon_state = "radsuitcloset" - icon_opened = "toolclosetopen" - icon_closed = "radsuitcloset" - -/obj/structure/closet/radiation/New() - ..() - new /obj/item/clothing/suit/radiation(src) - new /obj/item/clothing/head/radiation(src) - -/* - * Bombsuit closet - */ -/obj/structure/closet/bombcloset - name = "\improper EOD closet" - desc = "It's a storage unit for explosion-protective suits." - icon_state = "bombsuit" - icon_closed = "bombsuit" - icon_opened = "bombsuitopen" - -/obj/structure/closet/bombcloset/New() - ..() - new /obj/item/clothing/suit/bomb_suit( src ) - new /obj/item/clothing/under/color/black( src ) - new /obj/item/clothing/shoes/black( src ) - new /obj/item/clothing/head/bomb_hood( src ) - - -/obj/structure/closet/bombclosetsecurity - name = "\improper EOD closet" - desc = "It's a storage unit for explosion-protective suits." - icon_state = "bombsuitsec" - icon_closed = "bombsuitsec" - icon_opened = "bombsuitsecopen" - -/obj/structure/closet/bombclosetsecurity/New() - ..() - new /obj/item/clothing/suit/bomb_suit/security( src ) - new /obj/item/clothing/under/rank/security( src ) - new /obj/item/clothing/shoes/brown( src ) - new /obj/item/clothing/head/bomb_hood/security( src ) - -/* - * Hydrant - */ -/obj/structure/closet/hydrant //wall mounted fire closet - name = "fire-safety closet" - desc = "It's a storage unit for fire-fighting supplies." - icon_state = "hydrant" - icon_closed = "hydrant" - icon_opened = "hydrant_open" - anchored = 1 - density = 0 - wall_mounted = 1 - -/obj/structure/closet/hydrant/New() - ..() - new /obj/item/clothing/suit/fire/firefighter(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/flashlight(src) - new /obj/item/tank/oxygen/red(src) - new /obj/item/extinguisher(src) - new /obj/item/clothing/head/hardhat/red(src) - -/* - * First Aid - */ -/obj/structure/closet/medical_wall //wall mounted medical closet - name = "first-aid closet" - desc = "It's wall-mounted storage unit for first aid supplies." - icon_state = "medical_wall" - icon_closed = "medical_wall" - icon_opened = "medical_wall_open" - anchored = 1 - density = 0 - wall_mounted = 1 - -/obj/structure/closet/medical_wall/update_icon() - if(!opened) - icon_state = icon_closed - else - icon_state = icon_opened \ No newline at end of file +/* Utility Closets + * Contains: + * Emergency Closet + * Fire Closet + * Tool Closet + * Radiation Closet + * Bombsuit Closet + * Hydrant + * First Aid + */ + +/* + * Emergency Closet + */ +/obj/structure/closet/emcloset + name = "emergency closet" + desc = "It's a storage unit for emergency breathmasks and o2 tanks." + icon_state = "emergency" + icon_closed = "emergency" + icon_opened = "emergencyopen" + +/obj/structure/closet/emcloset/anchored + anchored = TRUE + +/obj/structure/closet/emcloset/New() + ..() + + switch(pickweight(list("small" = 55, "aid" = 25, "tank" = 10, "both" = 10, "nothing" = 0, "delete" = 0))) + if("small") + new /obj/item/tank/emergency_oxygen(src) + new /obj/item/tank/emergency_oxygen(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/clothing/mask/breath(src) + if("aid") + new /obj/item/tank/emergency_oxygen(src) + new /obj/item/storage/toolbox/emergency(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/storage/firstaid/o2(src) + if("tank") + new /obj/item/tank/emergency_oxygen/engi(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/tank/emergency_oxygen/engi(src) + new /obj/item/clothing/mask/breath(src) + if("both") + new /obj/item/storage/toolbox/emergency(src) + new /obj/item/tank/emergency_oxygen/engi(src) + new /obj/item/clothing/mask/breath(src) + new /obj/item/storage/firstaid/o2(src) + if("nothing") + // doot + + // teehee - Ah, tg coders... + if("delete") + qdel(src) + + //If you want to re-add fire, just add "fire" = 15 to the pick list. + /*if("fire") + new /obj/structure/closet/firecloset(src.loc) + qdel(src)*/ + +/obj/structure/closet/emcloset/legacy/New() + new /obj/item/tank/oxygen(src) + new /obj/item/clothing/mask/gas(src) + +/* + * Fire Closet + */ +/obj/structure/closet/firecloset + name = "fire-safety closet" + desc = "It's a storage unit for fire-fighting supplies." + icon_state = "firecloset" + icon_closed = "firecloset" + icon_opened = "fireclosetopen" + +/obj/structure/closet/firecloset/New() + ..() + + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/tank/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + +/obj/structure/closet/firecloset/full/New() + ..() + contents.Cut() + + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/flashlight(src) + new /obj/item/tank/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + + +/* + * Tool Closet + */ +/obj/structure/closet/toolcloset + name = "tool closet" + desc = "It's a storage unit for tools." + icon_state = "toolcloset" + icon_closed = "toolcloset" + icon_opened = "toolclosetopen" + +/obj/structure/closet/toolcloset/New() + ..() + if(prob(40)) + new /obj/item/clothing/suit/storage/hazardvest(src) + if(prob(70)) + new /obj/item/flashlight(src) + if(prob(70)) + new /obj/item/screwdriver(src) + if(prob(70)) + new /obj/item/wrench(src) + if(prob(70)) + new /obj/item/weldingtool(src) + if(prob(70)) + new /obj/item/crowbar(src) + if(prob(70)) + new /obj/item/wirecutters(src) + if(prob(70)) + new /obj/item/t_scanner(src) + if(prob(20)) + new /obj/item/storage/belt/utility(src) + if(prob(30)) + new /obj/item/stack/cable_coil/random(src) + if(prob(30)) + new /obj/item/stack/cable_coil/random(src) + if(prob(30)) + new /obj/item/stack/cable_coil/random(src) + if(prob(20)) + new /obj/item/multitool(src) + if(prob(5)) + new /obj/item/clothing/gloves/color/yellow(src) + if(prob(40)) + new /obj/item/clothing/head/hardhat(src) + + +/* + * Radiation Closet + */ +/obj/structure/closet/radiation + name = "radiation suit closet" + desc = "It's a storage unit for rad-protective suits." + icon_state = "radsuitcloset" + icon_opened = "toolclosetopen" + icon_closed = "radsuitcloset" + +/obj/structure/closet/radiation/New() + ..() + new /obj/item/clothing/suit/radiation(src) + new /obj/item/clothing/head/radiation(src) + +/* + * Bombsuit closet + */ +/obj/structure/closet/bombcloset + name = "\improper EOD closet" + desc = "It's a storage unit for explosion-protective suits." + icon_state = "bombsuit" + icon_closed = "bombsuit" + icon_opened = "bombsuitopen" + +/obj/structure/closet/bombcloset/New() + ..() + new /obj/item/clothing/suit/bomb_suit( src ) + new /obj/item/clothing/under/color/black( src ) + new /obj/item/clothing/shoes/black( src ) + new /obj/item/clothing/head/bomb_hood( src ) + + +/obj/structure/closet/bombclosetsecurity + name = "\improper EOD closet" + desc = "It's a storage unit for explosion-protective suits." + icon_state = "bombsuitsec" + icon_closed = "bombsuitsec" + icon_opened = "bombsuitsecopen" + +/obj/structure/closet/bombclosetsecurity/New() + ..() + new /obj/item/clothing/suit/bomb_suit/security( src ) + new /obj/item/clothing/under/rank/security( src ) + new /obj/item/clothing/shoes/brown( src ) + new /obj/item/clothing/head/bomb_hood/security( src ) + +/* + * Hydrant + */ +/obj/structure/closet/hydrant //wall mounted fire closet + name = "fire-safety closet" + desc = "It's a storage unit for fire-fighting supplies." + icon_state = "hydrant" + icon_closed = "hydrant" + icon_opened = "hydrant_open" + anchored = 1 + density = 0 + wall_mounted = 1 + +/obj/structure/closet/hydrant/New() + ..() + new /obj/item/clothing/suit/fire/firefighter(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/flashlight(src) + new /obj/item/tank/oxygen/red(src) + new /obj/item/extinguisher(src) + new /obj/item/clothing/head/hardhat/red(src) + +/* + * First Aid + */ +/obj/structure/closet/medical_wall //wall mounted medical closet + name = "first-aid closet" + desc = "It's wall-mounted storage unit for first aid supplies." + icon_state = "medical_wall" + icon_closed = "medical_wall" + icon_opened = "medical_wall_open" + anchored = 1 + density = 0 + wall_mounted = 1 + +/obj/structure/closet/medical_wall/update_icon() + if(!opened) + icon_state = icon_closed + else + icon_state = icon_opened diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm index 7de956a2a71..72326757687 100644 --- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm +++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm @@ -1,481 +1,481 @@ -/obj/structure/closet/wardrobe - name = "wardrobe" - desc = "It's a storage unit for standard-issue Nanotrasen attire." - icon_state = "blue" - icon_closed = "blue" - -/obj/structure/closet/wardrobe/generic - // Identical to the base wardrobe, aside from containing some stuff. - -/obj/structure/closet/wardrobe/generic/New() - ..() - new /obj/item/clothing/under/color/blue(src) - new /obj/item/clothing/under/color/blue(src) - new /obj/item/clothing/under/color/blue(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/mask/bandana/blue(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/clothing/shoes/brown(src) - - -/obj/structure/closet/wardrobe/red - name = "security wardrobe" - icon_state = "red" - icon_closed = "red" - -/obj/structure/closet/wardrobe/red/New() - ..() - new /obj/item/storage/backpack/duffel/security(src) - new /obj/item/storage/backpack/duffel/security(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/under/rank/security(src) - new /obj/item/clothing/under/rank/security(src) - new /obj/item/clothing/under/rank/security(src) - new /obj/item/clothing/under/rank/security/formal(src) - new /obj/item/clothing/under/rank/security/formal(src) - new /obj/item/clothing/under/rank/security/formal(src) - new /obj/item/clothing/under/rank/security/skirt(src) - new /obj/item/clothing/under/rank/security/skirt(src) - new /obj/item/clothing/under/rank/security/skirt(src) - new /obj/item/clothing/shoes/jackboots(src) - new /obj/item/clothing/shoes/jackboots(src) - new /obj/item/clothing/shoes/jackboots(src) - new /obj/item/clothing/shoes/jackboots/jacksandals(src) - new /obj/item/clothing/shoes/jackboots/jacksandals(src) - new /obj/item/clothing/shoes/jackboots/jacksandals(src) - new /obj/item/clothing/head/soft/sec(src) - new /obj/item/clothing/head/soft/sec(src) - new /obj/item/clothing/head/soft/sec(src) - new /obj/item/clothing/head/beret/sec(src) - new /obj/item/clothing/head/beret/sec(src) - new /obj/item/clothing/head/beret/sec(src) - new /obj/item/clothing/head/officer(src) - new /obj/item/clothing/head/officer(src) - new /obj/item/clothing/head/officer(src) - -/obj/structure/closet/redcorp - name = "corporate security wardrobe" - icon_state = "red" - icon_closed = "red" - -/obj/structure/closet/redcorp/New() - ..() - new /obj/item/clothing/under/rank/security/corp(src) - new /obj/item/clothing/under/rank/security/corp(src) - new /obj/item/clothing/under/rank/security/corp(src) - new /obj/item/clothing/head/soft/sec/corp(src) - new /obj/item/clothing/head/soft/sec/corp(src) - new /obj/item/clothing/head/soft/sec/corp(src) - -/obj/structure/closet/wardrobe/pink - name = "pink wardrobe" - icon_state = "pink" - icon_closed = "pink" - -/obj/structure/closet/wardrobe/pink/New() - ..() - new /obj/item/clothing/under/color/pink(src) - new /obj/item/clothing/under/color/pink(src) - new /obj/item/clothing/under/color/pink(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/clothing/shoes/brown(src) - new /obj/item/clothing/shoes/brown(src) - -/obj/structure/closet/wardrobe/black - name = "black wardrobe" - icon_state = "black" - icon_closed = "black" - -/obj/structure/closet/wardrobe/black/New() - ..() - new /obj/item/clothing/under/color/black(src) - new /obj/item/clothing/under/color/black(src) - new /obj/item/clothing/under/color/black(src) - if(prob(25)) - new /obj/item/clothing/suit/jacket/leather(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/head/that(src) - new /obj/item/clothing/head/that(src) - new /obj/item/clothing/head/that(src) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/head/soft/black(src) - -/obj/structure/closet/wardrobe/green - name = "green wardrobe" - icon_state = "green" - icon_closed = "green" - -/obj/structure/closet/wardrobe/green/New() - ..() - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - -/obj/structure/closet/wardrobe/xenos - name = "xenos wardrobe" - icon_state = "green" - icon_closed = "green" - -/obj/structure/closet/wardrobe/xenos/New() - ..() - new /obj/item/clothing/suit/unathi/mantle(src) - new /obj/item/clothing/suit/unathi/robe(src) - new /obj/item/clothing/shoes/sandal(src) - new /obj/item/clothing/shoes/sandal(src) - new /obj/item/clothing/shoes/sandal(src) - new /obj/item/clothing/shoes/footwraps(src) - new /obj/item/clothing/shoes/footwraps(src) - new /obj/item/clothing/shoes/footwraps(src) - - -/obj/structure/closet/wardrobe/orange - name = "prison wardrobe" - desc = "It's a storage unit for Nanotrasen-regulation prisoner attire." - icon_state = "orange" - icon_closed = "orange" - -/obj/structure/closet/wardrobe/orange/New() - ..() - new /obj/item/clothing/under/color/orange/prison(src) - new /obj/item/clothing/under/color/orange/prison(src) - new /obj/item/clothing/under/color/orange/prison(src) - new /obj/item/clothing/shoes/orange(src) - new /obj/item/clothing/shoes/orange(src) - new /obj/item/clothing/shoes/orange(src) - - -/obj/structure/closet/wardrobe/yellow - name = "yellow wardrobe" - icon_state = "yellow" - icon_closed = "yellow" - -/obj/structure/closet/wardrobe/yellow/New() - ..() - new /obj/item/clothing/under/color/yellow(src) - new /obj/item/clothing/under/color/yellow(src) - new /obj/item/clothing/under/color/yellow(src) - new /obj/item/clothing/shoes/orange(src) - new /obj/item/clothing/shoes/orange(src) - new /obj/item/clothing/shoes/orange(src) - - -/obj/structure/closet/wardrobe/atmospherics_yellow - name = "atmospherics wardrobe" - icon_state = "atmostech" - icon_closed = "atmostech" - -/obj/structure/closet/wardrobe/atmospherics_yellow/New() - ..() - new /obj/item/clothing/under/rank/atmospheric_technician(src) - new /obj/item/clothing/under/rank/atmospheric_technician(src) - new /obj/item/clothing/under/rank/atmospheric_technician(src) - new /obj/item/clothing/under/rank/atmospheric_technician/skirt(src) - new /obj/item/clothing/under/rank/atmospheric_technician/skirt(src) - new /obj/item/clothing/under/rank/atmospheric_technician/skirt(src) - new /obj/item/clothing/shoes/workboots(src) - new /obj/item/clothing/shoes/workboots(src) - new /obj/item/clothing/shoes/workboots(src) - new /obj/item/clothing/head/hardhat/red(src) - new /obj/item/clothing/head/hardhat/red(src) - new /obj/item/clothing/head/hardhat/red(src) - new /obj/item/clothing/head/beret/atmos(src) - new /obj/item/clothing/head/beret/atmos(src) - new /obj/item/clothing/head/beret/atmos(src) - - - -/obj/structure/closet/wardrobe/engineering_yellow - name = "engineering wardrobe" - icon_state = "engineer" - icon_closed = "engineer" - -/obj/structure/closet/wardrobe/engineering_yellow/New() - ..() - new /obj/item/clothing/under/rank/engineer(src) - new /obj/item/clothing/under/rank/engineer(src) - new /obj/item/clothing/under/rank/engineer(src) - new /obj/item/clothing/under/rank/engineer/skirt(src) - new /obj/item/clothing/under/rank/engineer/skirt(src) - new /obj/item/clothing/under/rank/engineer/skirt(src) - new /obj/item/clothing/shoes/workboots(src) - new /obj/item/clothing/shoes/workboots(src) - new /obj/item/clothing/shoes/workboots(src) - new /obj/item/clothing/head/hardhat(src) - new /obj/item/clothing/head/hardhat(src) - new /obj/item/clothing/head/hardhat(src) - new /obj/item/clothing/head/beret/eng(src) - new /obj/item/clothing/head/beret/eng(src) - new /obj/item/clothing/head/beret/eng(src) - - -/obj/structure/closet/wardrobe/white - name = "white wardrobe" - icon_state = "white" - icon_closed = "white" - -/obj/structure/closet/wardrobe/white/New() - ..() - new /obj/item/clothing/under/color/white(src) - new /obj/item/clothing/under/color/white(src) - new /obj/item/clothing/under/color/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - -/obj/structure/closet/wardrobe/medical_white - name = "medical doctor's wardrobe" - icon_state = "white" - icon_closed = "white" - -/obj/structure/closet/wardrobe/medical_white/New() - ..() - new /obj/item/clothing/under/rank/nursesuit (src) - new /obj/item/clothing/head/nursehat (src) - new /obj/item/clothing/under/rank/nurse(src) - new /obj/item/clothing/under/rank/orderly(src) - new /obj/item/clothing/suit/storage/fr_jacket(src) - new /obj/item/clothing/suit/storage/fr_jacket(src) - new /obj/item/clothing/suit/storage/fr_jacket(src) - new /obj/item/clothing/under/rank/medical/blue(src) - new /obj/item/clothing/head/surgery/blue(src) - new /obj/item/clothing/under/rank/medical/green(src) - new /obj/item/clothing/head/surgery/green(src) - new /obj/item/clothing/under/rank/medical/purple(src) - new /obj/item/clothing/under/rank/medical/skirt(src) - new /obj/item/clothing/under/rank/medical/skirt(src) - new /obj/item/clothing/head/surgery/purple(src) - new /obj/item/clothing/under/medigown(src) - new /obj/item/clothing/under/medigown(src) - new /obj/item/clothing/under/medigown(src) - new /obj/item/clothing/under/medigown(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - -/obj/structure/closet/wardrobe/pjs - name = "Pajama wardrobe" - icon_state = "white" - icon_closed = "white" - -/obj/structure/closet/wardrobe/pjs/New() - ..() - new /obj/item/clothing/under/pj/red(src) - new /obj/item/clothing/under/pj/red(src) - new /obj/item/clothing/under/pj/blue(src) - new /obj/item/clothing/under/pj/blue(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/slippers(src) - new /obj/item/clothing/shoes/slippers(src) - - -/obj/structure/closet/wardrobe/toxins_white - name = "toxins wardrobe" - icon_state = "white" - icon_closed = "white" - -/obj/structure/closet/wardrobe/toxins_white/New() - ..() - new /obj/item/clothing/under/rank/scientist(src) - new /obj/item/clothing/under/rank/scientist(src) - new /obj/item/clothing/under/rank/scientist(src) - new /obj/item/clothing/under/rank/scientist/skirt(src) - new /obj/item/clothing/under/rank/scientist/skirt(src) - new /obj/item/clothing/under/rank/scientist/skirt(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/slippers - new /obj/item/clothing/shoes/slippers - new /obj/item/clothing/shoes/slippers - - -/obj/structure/closet/wardrobe/robotics_black - name = "robotics wardrobe" - icon_state = "black" - icon_closed = "black" - -/obj/structure/closet/wardrobe/robotics_black/New() - ..() - new /obj/item/clothing/under/rank/roboticist(src) - new /obj/item/clothing/under/rank/roboticist(src) - new /obj/item/clothing/under/rank/roboticist/skirt(src) - new /obj/item/clothing/under/rank/roboticist/skirt(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/gloves/fingerless(src) - new /obj/item/clothing/gloves/fingerless(src) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/head/soft/black(src) - - -/obj/structure/closet/wardrobe/chemistry_white - name = "chemistry wardrobe" - icon_state = "white" - icon_closed = "white" - -/obj/structure/closet/wardrobe/chemistry_white/New() - ..() - new /obj/item/clothing/under/rank/chemist(src) - new /obj/item/clothing/under/rank/chemist(src) - new /obj/item/clothing/under/rank/chemist/skirt(src) - new /obj/item/clothing/under/rank/chemist/skirt(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/suit/storage/labcoat/chemist(src) - new /obj/item/clothing/suit/storage/labcoat/chemist(src) - new /obj/item/storage/backpack/chemistry(src) - new /obj/item/storage/backpack/chemistry(src) - new /obj/item/storage/backpack/satchel_chem(src) - new /obj/item/storage/backpack/satchel_chem(src) - new /obj/item/storage/bag/chemistry(src) - new /obj/item/storage/bag/chemistry(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/clothing/mask/gas(src) - - -/obj/structure/closet/wardrobe/genetics_white - name = "genetics wardrobe" - icon_state = "white" - icon_closed = "white" - -/obj/structure/closet/wardrobe/genetics_white/New() - ..() - new /obj/item/clothing/under/rank/geneticist(src) - new /obj/item/clothing/under/rank/geneticist(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/suit/storage/labcoat/genetics(src) - new /obj/item/clothing/suit/storage/labcoat/genetics(src) - new /obj/item/storage/backpack/genetics(src) - new /obj/item/storage/backpack/genetics(src) - new /obj/item/storage/backpack/satchel_gen(src) - new /obj/item/storage/backpack/satchel_gen(src) - - -/obj/structure/closet/wardrobe/virology_white - name = "virology wardrobe" - icon_state = "white" - icon_closed = "white" - -/obj/structure/closet/wardrobe/virology_white/New() - ..() - new /obj/item/clothing/under/rank/virologist(src) - new /obj/item/clothing/under/rank/virologist(src) - new /obj/item/clothing/under/rank/virologist/skirt(src) - new /obj/item/clothing/under/rank/virologist/skirt(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/suit/storage/labcoat/virologist(src) - new /obj/item/clothing/suit/storage/labcoat/virologist(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/storage/backpack/virology(src) - new /obj/item/storage/backpack/virology(src) - new /obj/item/storage/backpack/satchel_vir(src) - new /obj/item/storage/backpack/satchel_vir(src) - - -/obj/structure/closet/wardrobe/medic_white - name = "medical wardrobe" - icon_state = "white" - icon_closed = "white" - -/obj/structure/closet/wardrobe/medic_white/New() - ..() - new /obj/item/clothing/under/rank/medical(src) - new /obj/item/clothing/under/rank/medical(src) - new /obj/item/clothing/under/rank/medical/skirt(src) - new /obj/item/clothing/under/rank/medical/skirt(src) - new /obj/item/clothing/under/rank/medical/blue(src) - new /obj/item/clothing/under/rank/medical/green(src) - new /obj/item/clothing/under/rank/medical/purple(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/under/medigown(src) - new /obj/item/clothing/under/medigown(src) - new /obj/item/clothing/under/medigown(src) - new /obj/item/clothing/under/medigown(src) - new /obj/item/clothing/head/headmirror(src) - new /obj/item/clothing/head/headmirror(src) - - -/obj/structure/closet/wardrobe/grey - name = "grey wardrobe" - icon_state = "grey" - icon_closed = "grey" - -/obj/structure/closet/wardrobe/grey/New() - ..() - new /obj/item/clothing/under/color/grey(src) - new /obj/item/clothing/under/color/grey(src) - new /obj/item/clothing/under/color/grey(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/shoes/black(src) - new /obj/item/clothing/head/soft/grey(src) - new /obj/item/clothing/head/soft/grey(src) - new /obj/item/clothing/head/soft/grey(src) - if(prob(50)) - new /obj/item/storage/backpack/duffel(src) - if(prob(40)) - new /obj/item/clothing/under/assistantformal(src) - if(prob(40)) - new /obj/item/clothing/under/assistantformal(src) - - -/obj/structure/closet/wardrobe/mixed - name = "mixed wardrobe" - icon_state = "mixed" - icon_closed = "mixed" - -/obj/structure/closet/wardrobe/mixed/New() - ..() - new /obj/item/clothing/under/color/blue(src) - new /obj/item/clothing/under/color/yellow(src) - new /obj/item/clothing/under/color/green(src) - new /obj/item/clothing/under/color/orange(src) - new /obj/item/clothing/under/color/pink(src) - new /obj/item/clothing/under/dress/plaid_blue(src) - new /obj/item/clothing/under/dress/plaid_red(src) - new /obj/item/clothing/under/dress/plaid_purple(src) - new /obj/item/clothing/shoes/blue(src) - new /obj/item/clothing/shoes/yellow(src) - new /obj/item/clothing/shoes/green(src) - new /obj/item/clothing/shoes/orange(src) - new /obj/item/clothing/shoes/purple(src) - new /obj/item/clothing/shoes/leather(src) - -/obj/structure/closet/wardrobe/coroner - name = "coroner wardrobe" - icon_state = "black" - icon_closed = "black" - -/obj/structure/closet/wardrobe/coroner/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/medic(src) - else - new /obj/item/storage/backpack/satchel_med(src) - new /obj/item/storage/backpack/duffel/medical(src) - new /obj/item/clothing/suit/storage/labcoat/mortician(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/clothing/under/rank/medical/mortician(src) - new /obj/item/clothing/head/surgery/black(src) +/obj/structure/closet/wardrobe + name = "wardrobe" + desc = "It's a storage unit for standard-issue Nanotrasen attire." + icon_state = "blue" + icon_closed = "blue" + +/obj/structure/closet/wardrobe/generic + // Identical to the base wardrobe, aside from containing some stuff. + +/obj/structure/closet/wardrobe/generic/New() + ..() + new /obj/item/clothing/under/color/blue(src) + new /obj/item/clothing/under/color/blue(src) + new /obj/item/clothing/under/color/blue(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/mask/bandana/blue(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/clothing/shoes/brown(src) + + +/obj/structure/closet/wardrobe/red + name = "security wardrobe" + icon_state = "red" + icon_closed = "red" + +/obj/structure/closet/wardrobe/red/New() + ..() + new /obj/item/storage/backpack/duffel/security(src) + new /obj/item/storage/backpack/duffel/security(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/under/rank/security(src) + new /obj/item/clothing/under/rank/security(src) + new /obj/item/clothing/under/rank/security(src) + new /obj/item/clothing/under/rank/security/formal(src) + new /obj/item/clothing/under/rank/security/formal(src) + new /obj/item/clothing/under/rank/security/formal(src) + new /obj/item/clothing/under/rank/security/skirt(src) + new /obj/item/clothing/under/rank/security/skirt(src) + new /obj/item/clothing/under/rank/security/skirt(src) + new /obj/item/clothing/shoes/jackboots(src) + new /obj/item/clothing/shoes/jackboots(src) + new /obj/item/clothing/shoes/jackboots(src) + new /obj/item/clothing/shoes/jackboots/jacksandals(src) + new /obj/item/clothing/shoes/jackboots/jacksandals(src) + new /obj/item/clothing/shoes/jackboots/jacksandals(src) + new /obj/item/clothing/head/soft/sec(src) + new /obj/item/clothing/head/soft/sec(src) + new /obj/item/clothing/head/soft/sec(src) + new /obj/item/clothing/head/beret/sec(src) + new /obj/item/clothing/head/beret/sec(src) + new /obj/item/clothing/head/beret/sec(src) + new /obj/item/clothing/head/officer(src) + new /obj/item/clothing/head/officer(src) + new /obj/item/clothing/head/officer(src) + +/obj/structure/closet/redcorp + name = "corporate security wardrobe" + icon_state = "red" + icon_closed = "red" + +/obj/structure/closet/redcorp/New() + ..() + new /obj/item/clothing/under/rank/security/corp(src) + new /obj/item/clothing/under/rank/security/corp(src) + new /obj/item/clothing/under/rank/security/corp(src) + new /obj/item/clothing/head/soft/sec/corp(src) + new /obj/item/clothing/head/soft/sec/corp(src) + new /obj/item/clothing/head/soft/sec/corp(src) + +/obj/structure/closet/wardrobe/pink + name = "pink wardrobe" + icon_state = "pink" + icon_closed = "pink" + +/obj/structure/closet/wardrobe/pink/New() + ..() + new /obj/item/clothing/under/color/pink(src) + new /obj/item/clothing/under/color/pink(src) + new /obj/item/clothing/under/color/pink(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/clothing/shoes/brown(src) + new /obj/item/clothing/shoes/brown(src) + +/obj/structure/closet/wardrobe/black + name = "black wardrobe" + icon_state = "black" + icon_closed = "black" + +/obj/structure/closet/wardrobe/black/New() + ..() + new /obj/item/clothing/under/color/black(src) + new /obj/item/clothing/under/color/black(src) + new /obj/item/clothing/under/color/black(src) + if(prob(25)) + new /obj/item/clothing/suit/jacket/leather(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/head/that(src) + new /obj/item/clothing/head/that(src) + new /obj/item/clothing/head/that(src) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/head/soft/black(src) + +/obj/structure/closet/wardrobe/green + name = "green wardrobe" + icon_state = "green" + icon_closed = "green" + +/obj/structure/closet/wardrobe/green/New() + ..() + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + +/obj/structure/closet/wardrobe/xenos + name = "xenos wardrobe" + icon_state = "green" + icon_closed = "green" + +/obj/structure/closet/wardrobe/xenos/New() + ..() + new /obj/item/clothing/suit/unathi/mantle(src) + new /obj/item/clothing/suit/unathi/robe(src) + new /obj/item/clothing/shoes/sandal(src) + new /obj/item/clothing/shoes/sandal(src) + new /obj/item/clothing/shoes/sandal(src) + new /obj/item/clothing/shoes/footwraps(src) + new /obj/item/clothing/shoes/footwraps(src) + new /obj/item/clothing/shoes/footwraps(src) + + +/obj/structure/closet/wardrobe/orange + name = "prison wardrobe" + desc = "It's a storage unit for Nanotrasen-regulation prisoner attire." + icon_state = "orange" + icon_closed = "orange" + +/obj/structure/closet/wardrobe/orange/New() + ..() + new /obj/item/clothing/under/color/orange/prison(src) + new /obj/item/clothing/under/color/orange/prison(src) + new /obj/item/clothing/under/color/orange/prison(src) + new /obj/item/clothing/shoes/orange(src) + new /obj/item/clothing/shoes/orange(src) + new /obj/item/clothing/shoes/orange(src) + + +/obj/structure/closet/wardrobe/yellow + name = "yellow wardrobe" + icon_state = "yellow" + icon_closed = "yellow" + +/obj/structure/closet/wardrobe/yellow/New() + ..() + new /obj/item/clothing/under/color/yellow(src) + new /obj/item/clothing/under/color/yellow(src) + new /obj/item/clothing/under/color/yellow(src) + new /obj/item/clothing/shoes/orange(src) + new /obj/item/clothing/shoes/orange(src) + new /obj/item/clothing/shoes/orange(src) + + +/obj/structure/closet/wardrobe/atmospherics_yellow + name = "atmospherics wardrobe" + icon_state = "atmostech" + icon_closed = "atmostech" + +/obj/structure/closet/wardrobe/atmospherics_yellow/New() + ..() + new /obj/item/clothing/under/rank/atmospheric_technician(src) + new /obj/item/clothing/under/rank/atmospheric_technician(src) + new /obj/item/clothing/under/rank/atmospheric_technician(src) + new /obj/item/clothing/under/rank/atmospheric_technician/skirt(src) + new /obj/item/clothing/under/rank/atmospheric_technician/skirt(src) + new /obj/item/clothing/under/rank/atmospheric_technician/skirt(src) + new /obj/item/clothing/shoes/workboots(src) + new /obj/item/clothing/shoes/workboots(src) + new /obj/item/clothing/shoes/workboots(src) + new /obj/item/clothing/head/hardhat/red(src) + new /obj/item/clothing/head/hardhat/red(src) + new /obj/item/clothing/head/hardhat/red(src) + new /obj/item/clothing/head/beret/atmos(src) + new /obj/item/clothing/head/beret/atmos(src) + new /obj/item/clothing/head/beret/atmos(src) + + + +/obj/structure/closet/wardrobe/engineering_yellow + name = "engineering wardrobe" + icon_state = "engineer" + icon_closed = "engineer" + +/obj/structure/closet/wardrobe/engineering_yellow/New() + ..() + new /obj/item/clothing/under/rank/engineer(src) + new /obj/item/clothing/under/rank/engineer(src) + new /obj/item/clothing/under/rank/engineer(src) + new /obj/item/clothing/under/rank/engineer/skirt(src) + new /obj/item/clothing/under/rank/engineer/skirt(src) + new /obj/item/clothing/under/rank/engineer/skirt(src) + new /obj/item/clothing/shoes/workboots(src) + new /obj/item/clothing/shoes/workboots(src) + new /obj/item/clothing/shoes/workboots(src) + new /obj/item/clothing/head/hardhat(src) + new /obj/item/clothing/head/hardhat(src) + new /obj/item/clothing/head/hardhat(src) + new /obj/item/clothing/head/beret/eng(src) + new /obj/item/clothing/head/beret/eng(src) + new /obj/item/clothing/head/beret/eng(src) + + +/obj/structure/closet/wardrobe/white + name = "white wardrobe" + icon_state = "white" + icon_closed = "white" + +/obj/structure/closet/wardrobe/white/New() + ..() + new /obj/item/clothing/under/color/white(src) + new /obj/item/clothing/under/color/white(src) + new /obj/item/clothing/under/color/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + +/obj/structure/closet/wardrobe/medical_white + name = "medical doctor's wardrobe" + icon_state = "white" + icon_closed = "white" + +/obj/structure/closet/wardrobe/medical_white/New() + ..() + new /obj/item/clothing/under/rank/nursesuit (src) + new /obj/item/clothing/head/nursehat (src) + new /obj/item/clothing/under/rank/nurse(src) + new /obj/item/clothing/under/rank/orderly(src) + new /obj/item/clothing/suit/storage/fr_jacket(src) + new /obj/item/clothing/suit/storage/fr_jacket(src) + new /obj/item/clothing/suit/storage/fr_jacket(src) + new /obj/item/clothing/under/rank/medical/blue(src) + new /obj/item/clothing/head/surgery/blue(src) + new /obj/item/clothing/under/rank/medical/green(src) + new /obj/item/clothing/head/surgery/green(src) + new /obj/item/clothing/under/rank/medical/purple(src) + new /obj/item/clothing/under/rank/medical/skirt(src) + new /obj/item/clothing/under/rank/medical/skirt(src) + new /obj/item/clothing/head/surgery/purple(src) + new /obj/item/clothing/under/medigown(src) + new /obj/item/clothing/under/medigown(src) + new /obj/item/clothing/under/medigown(src) + new /obj/item/clothing/under/medigown(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + +/obj/structure/closet/wardrobe/pjs + name = "Pajama wardrobe" + icon_state = "white" + icon_closed = "white" + +/obj/structure/closet/wardrobe/pjs/New() + ..() + new /obj/item/clothing/under/pj/red(src) + new /obj/item/clothing/under/pj/red(src) + new /obj/item/clothing/under/pj/blue(src) + new /obj/item/clothing/under/pj/blue(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/slippers(src) + new /obj/item/clothing/shoes/slippers(src) + + +/obj/structure/closet/wardrobe/toxins_white + name = "toxins wardrobe" + icon_state = "white" + icon_closed = "white" + +/obj/structure/closet/wardrobe/toxins_white/New() + ..() + new /obj/item/clothing/under/rank/scientist(src) + new /obj/item/clothing/under/rank/scientist(src) + new /obj/item/clothing/under/rank/scientist(src) + new /obj/item/clothing/under/rank/scientist/skirt(src) + new /obj/item/clothing/under/rank/scientist/skirt(src) + new /obj/item/clothing/under/rank/scientist/skirt(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/slippers + new /obj/item/clothing/shoes/slippers + new /obj/item/clothing/shoes/slippers + + +/obj/structure/closet/wardrobe/robotics_black + name = "robotics wardrobe" + icon_state = "black" + icon_closed = "black" + +/obj/structure/closet/wardrobe/robotics_black/New() + ..() + new /obj/item/clothing/under/rank/roboticist(src) + new /obj/item/clothing/under/rank/roboticist(src) + new /obj/item/clothing/under/rank/roboticist/skirt(src) + new /obj/item/clothing/under/rank/roboticist/skirt(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/gloves/fingerless(src) + new /obj/item/clothing/gloves/fingerless(src) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/head/soft/black(src) + + +/obj/structure/closet/wardrobe/chemistry_white + name = "chemistry wardrobe" + icon_state = "white" + icon_closed = "white" + +/obj/structure/closet/wardrobe/chemistry_white/New() + ..() + new /obj/item/clothing/under/rank/chemist(src) + new /obj/item/clothing/under/rank/chemist(src) + new /obj/item/clothing/under/rank/chemist/skirt(src) + new /obj/item/clothing/under/rank/chemist/skirt(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/suit/storage/labcoat/chemist(src) + new /obj/item/clothing/suit/storage/labcoat/chemist(src) + new /obj/item/storage/backpack/chemistry(src) + new /obj/item/storage/backpack/chemistry(src) + new /obj/item/storage/backpack/satchel_chem(src) + new /obj/item/storage/backpack/satchel_chem(src) + new /obj/item/storage/bag/chemistry(src) + new /obj/item/storage/bag/chemistry(src) + new /obj/item/clothing/mask/gas(src) + new /obj/item/clothing/mask/gas(src) + + +/obj/structure/closet/wardrobe/genetics_white + name = "genetics wardrobe" + icon_state = "white" + icon_closed = "white" + +/obj/structure/closet/wardrobe/genetics_white/New() + ..() + new /obj/item/clothing/under/rank/geneticist(src) + new /obj/item/clothing/under/rank/geneticist(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/suit/storage/labcoat/genetics(src) + new /obj/item/clothing/suit/storage/labcoat/genetics(src) + new /obj/item/storage/backpack/genetics(src) + new /obj/item/storage/backpack/genetics(src) + new /obj/item/storage/backpack/satchel_gen(src) + new /obj/item/storage/backpack/satchel_gen(src) + + +/obj/structure/closet/wardrobe/virology_white + name = "virology wardrobe" + icon_state = "white" + icon_closed = "white" + +/obj/structure/closet/wardrobe/virology_white/New() + ..() + new /obj/item/clothing/under/rank/virologist(src) + new /obj/item/clothing/under/rank/virologist(src) + new /obj/item/clothing/under/rank/virologist/skirt(src) + new /obj/item/clothing/under/rank/virologist/skirt(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/suit/storage/labcoat/virologist(src) + new /obj/item/clothing/suit/storage/labcoat/virologist(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/storage/backpack/virology(src) + new /obj/item/storage/backpack/virology(src) + new /obj/item/storage/backpack/satchel_vir(src) + new /obj/item/storage/backpack/satchel_vir(src) + + +/obj/structure/closet/wardrobe/medic_white + name = "medical wardrobe" + icon_state = "white" + icon_closed = "white" + +/obj/structure/closet/wardrobe/medic_white/New() + ..() + new /obj/item/clothing/under/rank/medical(src) + new /obj/item/clothing/under/rank/medical(src) + new /obj/item/clothing/under/rank/medical/skirt(src) + new /obj/item/clothing/under/rank/medical/skirt(src) + new /obj/item/clothing/under/rank/medical/blue(src) + new /obj/item/clothing/under/rank/medical/green(src) + new /obj/item/clothing/under/rank/medical/purple(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/under/medigown(src) + new /obj/item/clothing/under/medigown(src) + new /obj/item/clothing/under/medigown(src) + new /obj/item/clothing/under/medigown(src) + new /obj/item/clothing/head/headmirror(src) + new /obj/item/clothing/head/headmirror(src) + + +/obj/structure/closet/wardrobe/grey + name = "grey wardrobe" + icon_state = "grey" + icon_closed = "grey" + +/obj/structure/closet/wardrobe/grey/New() + ..() + new /obj/item/clothing/under/color/grey(src) + new /obj/item/clothing/under/color/grey(src) + new /obj/item/clothing/under/color/grey(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/shoes/black(src) + new /obj/item/clothing/head/soft/grey(src) + new /obj/item/clothing/head/soft/grey(src) + new /obj/item/clothing/head/soft/grey(src) + if(prob(50)) + new /obj/item/storage/backpack/duffel(src) + if(prob(40)) + new /obj/item/clothing/under/assistantformal(src) + if(prob(40)) + new /obj/item/clothing/under/assistantformal(src) + + +/obj/structure/closet/wardrobe/mixed + name = "mixed wardrobe" + icon_state = "mixed" + icon_closed = "mixed" + +/obj/structure/closet/wardrobe/mixed/New() + ..() + new /obj/item/clothing/under/color/blue(src) + new /obj/item/clothing/under/color/yellow(src) + new /obj/item/clothing/under/color/green(src) + new /obj/item/clothing/under/color/orange(src) + new /obj/item/clothing/under/color/pink(src) + new /obj/item/clothing/under/dress/plaid_blue(src) + new /obj/item/clothing/under/dress/plaid_red(src) + new /obj/item/clothing/under/dress/plaid_purple(src) + new /obj/item/clothing/shoes/blue(src) + new /obj/item/clothing/shoes/yellow(src) + new /obj/item/clothing/shoes/green(src) + new /obj/item/clothing/shoes/orange(src) + new /obj/item/clothing/shoes/purple(src) + new /obj/item/clothing/shoes/leather(src) + +/obj/structure/closet/wardrobe/coroner + name = "coroner wardrobe" + icon_state = "black" + icon_closed = "black" + +/obj/structure/closet/wardrobe/coroner/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/medic(src) + else + new /obj/item/storage/backpack/satchel_med(src) + new /obj/item/storage/backpack/duffel/medical(src) + new /obj/item/clothing/suit/storage/labcoat/mortician(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/clothing/under/rank/medical/mortician(src) + new /obj/item/clothing/head/surgery/black(src) diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index 31747323e07..45aaf76fb07 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -1,643 +1,643 @@ -/obj/structure/closet/crate - name = "crate" - desc = "A rectangular steel crate." - icon = 'icons/obj/crates.dmi' - icon_state = "crate" - icon_opened = "crateopen" - icon_closed = "crate" - climbable = TRUE -// mouse_drag_pointer = MOUSE_ACTIVE_POINTER //??? - var/rigged = FALSE - var/obj/item/paper/manifest/manifest - // A list of beacon names that the crate will announce the arrival of, when delivered. - var/list/announce_beacons = list() - -/obj/structure/closet/crate/New() - ..() - update_icon() - -/obj/structure/closet/crate/update_icon() - ..() - overlays.Cut() - if(manifest) - overlays += "manifest" - -/obj/structure/closet/crate/can_open() - return TRUE - -/obj/structure/closet/crate/can_close() - return TRUE - -/obj/structure/closet/crate/open() - if(src.opened) - return FALSE - if(!src.can_open()) - return FALSE - - if(rigged && locate(/obj/item/radio/electropack) in src) - if(isliving(usr)) - var/mob/living/L = usr - if(L.electrocute_act(17, src)) - do_sparks(5, 1, src) - return 2 - - playsound(src.loc, 'sound/machines/click.ogg', 15, 1, -3) - for(var/obj/O in src) //Objects - O.forceMove(loc) - for(var/mob/M in src) //Mobs - M.forceMove(loc) - icon_state = icon_opened - src.opened = TRUE - - if(climbable) - structure_shaken() - - return TRUE - -/obj/structure/closet/crate/close() - if(!src.opened) - return FALSE - if(!src.can_close()) - return FALSE - - playsound(src.loc, 'sound/machines/click.ogg', 15, 1, -3) - var/itemcount = 0 - for(var/obj/O in get_turf(src)) - if(itemcount >= storage_capacity) - break - if(O.density || O.anchored || istype(O,/obj/structure/closet)) - continue - if(istype(O, /obj/structure/bed)) //This is only necessary because of rollerbeds and swivel chairs. - var/obj/structure/bed/B = O - if(B.has_buckled_mobs()) - continue - O.forceMove(src) - itemcount++ - - icon_state = icon_closed - src.opened = FALSE - return TRUE - -/obj/structure/closet/crate/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/rcs) && !src.opened) - var/obj/item/rcs/E = W - if(E.rcell && (E.rcell.charge >= E.chargecost)) - if(!is_level_reachable(src.z)) // This is inconsistent with the closet sending code - to_chat(user, "The rapid-crate-sender can't locate any telepads!") - return - if(E.mode == 0) - if(!E.teleporting) - var/list/L = list() - var/list/areaindex = list() - for(var/obj/machinery/telepad_cargo/R in world) - if(R.stage == 0) - var/turf/T = get_turf(R) - var/tmpname = T.loc.name - if(areaindex[tmpname]) - tmpname = "[tmpname] ([++areaindex[tmpname]])" - else - areaindex[tmpname] = 1 - L[tmpname] = R - var/desc = input("Please select a telepad.", "RCS") in L - E.pad = L[desc] - if(!Adjacent(user)) - to_chat(user, "Unable to teleport, too far from crate.") - return - playsound(E.loc, E.usesound, 50, 1) - to_chat(user, "Teleporting [src.name]...") - E.teleporting = TRUE - if(!do_after(user, 50 * E.toolspeed, target = src)) - E.teleporting = 0 - return - E.teleporting = 0 - if(!(E.rcell && E.rcell.use(E.chargecost))) - to_chat(user, "Unable to teleport, insufficient charge.") - return - do_sparks(5, 1, src) - do_teleport(src, E.pad, 0) - to_chat(user, "Teleport successful. [round(E.rcell.charge/E.chargecost)] charge\s left.") - return - - else - E.rand_x = rand(50,200) - E.rand_y = rand(50,200) - var/L = locate(E.rand_x, E.rand_y, 6) - if(!Adjacent(user)) - to_chat(user, "Unable to teleport, too far from crate.") - return - playsound(E.loc, E.usesound, 50, 1) - to_chat(user, "Teleporting [src.name]...") - E.teleporting = TRUE - if(!do_after(user, 50 * E.toolspeed, target = src)) - E.teleporting = FALSE - return - E.teleporting = 0 - if(!(E.rcell && E.rcell.use(E.chargecost))) - to_chat(user, "Unable to teleport, insufficient charge.") - return - do_sparks(5, 1, src) - do_teleport(src, L) - to_chat(user, "Teleport successful. [round(E.rcell.charge/E.chargecost)] charge\s left.") - return - else - to_chat(user, "Out of charges.") - return - - if(opened) - if(isrobot(user)) - return - if(!user.drop_item()) //couldn't drop the item - to_chat(user, "\The [W] is stuck to your hand, you cannot put it in \the [src]!") - return - if(W) - W.forceMove(loc) - else if(istype(W, /obj/item/stack/packageWrap)) - return - else if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = W - if(rigged) - to_chat(user, "[src] is already rigged!") - return - if(C.use(15)) - to_chat(user, "You rig [src].") - rigged = TRUE - else - to_chat(user, "You need atleast 15 wires to rig [src]!") - return - else if(istype(W, /obj/item/radio/electropack)) - if(rigged) - to_chat(user, "You attach [W] to [src].") - user.drop_item() - W.forceMove(src) - return - else if(istype(W, /obj/item/wirecutters)) - if(rigged) - to_chat(user, "You cut away the wiring.") - playsound(loc, W.usesound, 100, 1) - rigged = FALSE - return - else if(user.a_intent != INTENT_HARM) - attack_hand(user) - else - return ..() - -/obj/structure/closet/singularity_act() - dump_contents() - ..() - -/obj/structure/closet/crate/welder_act() - return - -/obj/structure/closet/crate/attack_hand(mob/user) - if(manifest) - to_chat(user, "You tear the manifest off of the crate.") - playsound(src.loc, 'sound/items/poster_ripped.ogg', 75, 1) - manifest.forceMove(loc) - if(ishuman(user)) - user.put_in_hands(manifest) - manifest = null - update_icon() - return - else - if(rigged && locate(/obj/item/radio/electropack) in src) - if(isliving(user)) - var/mob/living/L = user - if(L.electrocute_act(17, src)) - do_sparks(5, 1, src) - return - src.add_fingerprint(user) - src.toggle(user) - -// Called when a crate is delivered by MULE at a location, for notifying purposes -/obj/structure/closet/crate/proc/notifyRecipient(var/destination) - var/msg = "[capitalize(name)] has arrived at [destination]." - if(destination in announce_beacons) - for(var/obj/machinery/requests_console/D in allConsoles) - if(D.department in src.announce_beacons[destination]) - D.createMessage(name, "Your Crate has Arrived!", msg, 1) - -/obj/structure/closet/crate/secure - desc = "A secure crate." - name = "Secure crate" - icon_state = "securecrate" - icon_opened = "securecrateopen" - icon_closed = "securecrate" - var/redlight = "securecrater" - var/greenlight = "securecrateg" - var/sparks = "securecratesparks" - var/emag = "securecrateemag" - max_integrity = 500 - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - damage_deflection = 25 - var/tamperproof = 0 - broken = 0 - locked = 1 - -/obj/structure/closet/crate/secure/update_icon() - ..() - overlays.Cut() - if(manifest) - overlays += "manifest" - if(locked) - overlays += redlight - else if(broken) - overlays += emag - else - overlays += greenlight - -/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - if(prob(tamperproof) && damage_amount >= DAMAGE_PRECISION) - boom() - else - return ..() - -/obj/structure/closet/crate/secure/proc/boom(mob/user) - if(user) - to_chat(user, "The crate's anti-tamper system activates!") - investigate_log("[key_name(user)] has detonated a [src]", INVESTIGATE_BOMB) - for(var/atom/movable/AM in src) - qdel(AM) - explosion(get_turf(src), 0, 1, 5, 5) - qdel(src) - -/obj/structure/closet/crate/secure/can_open() - return !locked - -/obj/structure/closet/crate/secure/proc/togglelock(mob/user) - if(src.opened) - to_chat(user, "Close the crate first.") - return - if(src.broken) - to_chat(user, "The crate appears to be broken.") - return - if(src.allowed(user)) - src.locked = !src.locked - visible_message("The crate has been [locked ? null : "un"]locked by [user].") - update_icon() - else - to_chat(user, "Access Denied") - -/obj/structure/closet/crate/secure/verb/verb_togglelock() - set src in oview(1) // One square distance - set category = null - set name = "Toggle Lock" - - if(!usr.canmove || usr.stat || usr.restrained()) // Don't use it if you're not able to! Checks for stuns, ghost and restrain - return - - if(ishuman(usr)) - src.add_fingerprint(usr) - src.togglelock(usr) - else - to_chat(usr, "This mob type can't use this verb.") - -/obj/structure/closet/crate/secure/attack_hand(mob/user) - if(manifest) - to_chat(user, "You tear the manifest off of the crate.") - playsound(src.loc, 'sound/items/poster_ripped.ogg', 75, 1) - manifest.forceMove(loc) - if(ishuman(user)) - user.put_in_hands(manifest) - manifest = null - update_icon() - return - if(locked) - src.togglelock(user) - else - src.toggle(user) - - -/obj/structure/closet/crate/secure/attackby(obj/item/W, mob/user, params) - if(is_type_in_list(W, list(/obj/item/stack/packageWrap, /obj/item/stack/cable_coil, /obj/item/radio/electropack, /obj/item/wirecutters,/obj/item/rcs))) - return ..() - if((istype(W, /obj/item/card/emag) || istype(W, /obj/item/melee/energy/blade))) - emag_act(user) - return - if(!opened) - src.togglelock(user) - return - return ..() - -/obj/structure/closet/crate/secure/emag_act(mob/user) - if(locked) - overlays += sparks - spawn(6) overlays -= sparks //Tried lots of stuff but nothing works right. so i have to use this *sadface* - playsound(src.loc, "sparks", 60, 1) - src.locked = 0 - src.broken = 1 - update_icon() - to_chat(user, "You unlock \the [src].") - -/obj/structure/closet/crate/secure/emp_act(severity) - for(var/obj/O in src) - O.emp_act(severity) - if(!broken && !opened && prob(50/severity)) - if(!locked) - src.locked = 1 - else - overlays += sparks - spawn(6) overlays -= sparks //Tried lots of stuff but nothing works right. so i have to use this *sadface* - playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1) - src.locked = 0 - update_icon() - if(!opened && prob(20/severity)) - if(!locked) - open() - else - src.req_access = list() - src.req_access += pick(get_all_accesses()) - ..() - -/obj/structure/closet/crate/plastic - name = "plastic crate" - desc = "A rectangular plastic crate." - icon_state = "plasticcrate" - icon_opened = "plasticcrateopen" - icon_closed = "plasticcrate" - -/obj/structure/closet/crate/internals - desc = "A internals crate." - name = "internals crate" - icon_state = "o2crate" - icon_opened = "o2crateopen" - icon_closed = "o2crate" - -/obj/structure/closet/crate/trashcart - desc = "A heavy, metal trashcart with wheels." - name = "trash Cart" - icon_state = "trashcart" - icon_opened = "trashcartopen" - icon_closed = "trashcart" - -/*these aren't needed anymore -/obj/structure/closet/crate/hat - desc = "A crate filled with Valuable Collector's Hats!." - name = "Hat Crate" - icon_state = "crate" - icon_opened = "crateopen" - icon_closed = "crate" - -/obj/structure/closet/crate/contraband - name = "Poster crate" - desc = "A random assortment of posters manufactured by providers NOT listed under Nanotrasen's whitelist." - icon_state = "crate" - icon_opened = "crateopen" - icon_closed = "crate" -*/ - -/obj/structure/closet/crate/medical - desc = "A medical crate." - name = "medical crate" - icon_state = "medicalcrate" - icon_opened = "medicalcrateopen" - icon_closed = "medicalcrate" - -/obj/structure/closet/crate/rcd - desc = "A crate for the storage of the RCD." - name = "\improper RCD crate" - icon_state = "crate" - icon_opened = "crateopen" - icon_closed = "crate" - -/obj/structure/closet/crate/rcd/New() - ..() - new /obj/item/rcd_ammo(src) - new /obj/item/rcd_ammo(src) - new /obj/item/rcd_ammo(src) - new /obj/item/rcd(src) - -/obj/structure/closet/crate/freezer - desc = "A freezer." - name = "Freezer" - icon_state = "freezer" - icon_opened = "freezeropen" - icon_closed = "freezer" - var/target_temp = T0C - 40 - var/cooling_power = 40 - - return_air() - var/datum/gas_mixture/gas = (..()) - if(!gas) return null - var/datum/gas_mixture/newgas = new/datum/gas_mixture() - newgas.oxygen = gas.oxygen - newgas.carbon_dioxide = gas.carbon_dioxide - newgas.nitrogen = gas.nitrogen - newgas.toxins = gas.toxins - newgas.volume = gas.volume - newgas.temperature = gas.temperature - if(newgas.temperature <= target_temp) return - - if((newgas.temperature - cooling_power) > target_temp) - newgas.temperature -= cooling_power - else - newgas.temperature = target_temp - return newgas - - -/obj/structure/closet/crate/can - desc = "A large can, looks like a bin to me." - name = "garbage can" - icon_state = "largebin" - icon_opened = "largebinopen" - icon_closed = "largebin" - anchored = TRUE - -/obj/structure/closet/crate/can/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - default_unfasten_wrench(user, I, 40) - -/obj/structure/closet/crate/radiation - desc = "A crate with a radiation sign on it." - name = "radioactive gear crate" - icon_state = "radiation" - icon_opened = "radiationopen" - icon_closed = "radiation" - -/obj/structure/closet/crate/radiation/New() - ..() - new /obj/item/clothing/suit/radiation(src) - new /obj/item/clothing/head/radiation(src) - new /obj/item/clothing/suit/radiation(src) - new /obj/item/clothing/head/radiation(src) - new /obj/item/clothing/suit/radiation(src) - new /obj/item/clothing/head/radiation(src) - new /obj/item/clothing/suit/radiation(src) - new /obj/item/clothing/head/radiation(src) - -/obj/structure/closet/crate/secure/weapon - desc = "A secure weapons crate." - name = "weapons crate" - icon_state = "weaponcrate" - icon_opened = "weaponcrateopen" - icon_closed = "weaponcrate" - -/obj/structure/closet/crate/secure/plasma - desc = "A secure plasma crate." - name = "plasma crate" - icon_state = "plasmacrate" - icon_opened = "plasmacrateopen" - icon_closed = "plasmacrate" - -/obj/structure/closet/crate/secure/gear - desc = "A secure gear crate." - name = "gear crate" - icon_state = "secgearcrate" - icon_opened = "secgearcrateopen" - icon_closed = "secgearcrate" - -/obj/structure/closet/crate/secure/hydrosec - desc = "A crate with a lock on it, painted in the scheme of the station's botanists." - name = "secure hydroponics crate" - icon_state = "hydrosecurecrate" - icon_opened = "hydrosecurecrateopen" - icon_closed = "hydrosecurecrate" - -/obj/structure/closet/crate/secure/bin - desc = "A secure bin." - name = "secure bin" - icon_state = "largebins" - icon_opened = "largebinsopen" - icon_closed = "largebins" - redlight = "largebinr" - greenlight = "largebing" - sparks = "largebinsparks" - emag = "largebinemag" - -/obj/structure/closet/crate/large - name = "large crate" - desc = "A hefty metal crate." - icon_state = "largemetal" - icon_opened = "largemetalopen" - icon_closed = "largemetal" - integrity_failure = 0 //Makes the crate break when integrity reaches 0, instead of opening and becoming an invisible sprite. - -/obj/structure/closet/crate/large/close() - . = ..() - if(.)//we can hold up to one large item - var/found = 0 - for(var/obj/structure/S in src.loc) - if(S == src) - continue - if(!S.anchored) - found = 1 - S.forceMove(src) - break - if(!found) - for(var/obj/machinery/M in src.loc) - if(!M.anchored) - M.forceMove(src) - break - -/obj/structure/closet/crate/secure/large - name = "large crate" - desc = "A hefty metal crate with an electronic locking system." - icon_state = "largemetal" - icon_opened = "largemetalopen" - icon_closed = "largemetal" - redlight = "largemetalr" - greenlight = "largemetalg" - -/obj/structure/closet/crate/secure/large/close() - . = ..() - if(.)//we can hold up to one large item - var/found = 0 - for(var/obj/structure/S in src.loc) - if(S == src) - continue - if(!S.anchored) - found = 1 - S.forceMove(src) - break - if(!found) - for(var/obj/machinery/M in src.loc) - if(!M.anchored) - M.forceMove(src) - break - -//fluff variant -/obj/structure/closet/crate/secure/large/reinforced - desc = "A hefty, reinforced metal crate with an electronic locking system." - icon_state = "largermetal" - icon_opened = "largermetalopen" - icon_closed = "largermetal" - -/obj/structure/closet/crate/hydroponics - name = "hydroponics crate" - desc = "All you need to destroy those pesky weeds and pests." - icon_state = "hydrocrate" - icon_opened = "hydrocrateopen" - icon_closed = "hydrocrate" - -/obj/structure/closet/crate/hydroponics/prespawned - //This exists so the prespawned hydro crates spawn with their contents. - - New() - ..() - new /obj/item/reagent_containers/glass/bucket(src) - new /obj/item/reagent_containers/glass/bucket(src) - new /obj/item/screwdriver(src) - new /obj/item/screwdriver(src) - new /obj/item/wrench(src) - new /obj/item/wrench(src) - new /obj/item/wirecutters(src) - new /obj/item/wirecutters(src) - new /obj/item/shovel/spade(src) - new /obj/item/shovel/spade(src) - new /obj/item/storage/box/beakers(src) - new /obj/item/storage/box/beakers(src) - new /obj/item/hand_labeler(src) - new /obj/item/hand_labeler(src) - -/obj/structure/closet/crate/sci - name = "science crate" - desc = "A science crate." - icon_state = "scicrate" - icon_opened = "scicrateopen" - icon_closed = "scicrate" - -/obj/structure/closet/crate/secure/scisec - name = "secure science crate" - desc = "A crate with a lock on it, painted in the scheme of the station's scientists." - icon_state = "scisecurecrate" - icon_opened = "scisecurecrateopen" - icon_closed = "scisecurecrate" - -/obj/structure/closet/crate/engineering - name = "engineering crate" - desc = "An engineering crate." - icon_state = "engicrate" - icon_opened = "engicrateopen" - icon_closed = "engicrate" - -/obj/structure/closet/crate/secure/engineering - name = "secure engineering crate" - desc = "A crate with a lock on it, painted in the scheme of the station's engineers." - icon_state = "engisecurecrate" - icon_opened = "engisecurecrateopen" - icon_closed = "engisecurecrate" - -/obj/structure/closet/crate/engineering/electrical - name = "electrical engineering crate" - desc = "An electrical engineering crate." - icon_state = "electricalcrate" - icon_opened = "electricalcrateopen" - icon_closed = "electricalcrate" - -/obj/structure/closet/crate/tape/New() - if(prob(10)) - new /obj/item/bikehorn/rubberducky(src) - ..() - -//crates of gear in the free golem ship -/obj/structure/closet/crate/golemgear/New() - ..() - new /obj/item/storage/backpack/industrial(src) - new /obj/item/shovel(src) - new /obj/item/pickaxe(src) - new /obj/item/t_scanner/adv_mining_scanner/lesser(src) - new /obj/item/storage/bag/ore(src) - new /obj/item/clothing/glasses/meson(src) - new /obj/item/card/id/golem(src) - new /obj/item/flashlight/lantern(src) +/obj/structure/closet/crate + name = "crate" + desc = "A rectangular steel crate." + icon = 'icons/obj/crates.dmi' + icon_state = "crate" + icon_opened = "crateopen" + icon_closed = "crate" + climbable = TRUE +// mouse_drag_pointer = MOUSE_ACTIVE_POINTER //??? + var/rigged = FALSE + var/obj/item/paper/manifest/manifest + // A list of beacon names that the crate will announce the arrival of, when delivered. + var/list/announce_beacons = list() + +/obj/structure/closet/crate/New() + ..() + update_icon() + +/obj/structure/closet/crate/update_icon() + ..() + overlays.Cut() + if(manifest) + overlays += "manifest" + +/obj/structure/closet/crate/can_open() + return TRUE + +/obj/structure/closet/crate/can_close() + return TRUE + +/obj/structure/closet/crate/open() + if(src.opened) + return FALSE + if(!src.can_open()) + return FALSE + + if(rigged && locate(/obj/item/radio/electropack) in src) + if(isliving(usr)) + var/mob/living/L = usr + if(L.electrocute_act(17, src)) + do_sparks(5, 1, src) + return 2 + + playsound(src.loc, 'sound/machines/click.ogg', 15, 1, -3) + for(var/obj/O in src) //Objects + O.forceMove(loc) + for(var/mob/M in src) //Mobs + M.forceMove(loc) + icon_state = icon_opened + src.opened = TRUE + + if(climbable) + structure_shaken() + + return TRUE + +/obj/structure/closet/crate/close() + if(!src.opened) + return FALSE + if(!src.can_close()) + return FALSE + + playsound(src.loc, 'sound/machines/click.ogg', 15, 1, -3) + var/itemcount = 0 + for(var/obj/O in get_turf(src)) + if(itemcount >= storage_capacity) + break + if(O.density || O.anchored || istype(O,/obj/structure/closet)) + continue + if(istype(O, /obj/structure/bed)) //This is only necessary because of rollerbeds and swivel chairs. + var/obj/structure/bed/B = O + if(B.has_buckled_mobs()) + continue + O.forceMove(src) + itemcount++ + + icon_state = icon_closed + src.opened = FALSE + return TRUE + +/obj/structure/closet/crate/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/rcs) && !src.opened) + var/obj/item/rcs/E = W + if(E.rcell && (E.rcell.charge >= E.chargecost)) + if(!is_level_reachable(src.z)) // This is inconsistent with the closet sending code + to_chat(user, "The rapid-crate-sender can't locate any telepads!") + return + if(E.mode == 0) + if(!E.teleporting) + var/list/L = list() + var/list/areaindex = list() + for(var/obj/machinery/telepad_cargo/R in world) + if(R.stage == 0) + var/turf/T = get_turf(R) + var/tmpname = T.loc.name + if(areaindex[tmpname]) + tmpname = "[tmpname] ([++areaindex[tmpname]])" + else + areaindex[tmpname] = 1 + L[tmpname] = R + var/desc = input("Please select a telepad.", "RCS") in L + E.pad = L[desc] + if(!Adjacent(user)) + to_chat(user, "Unable to teleport, too far from crate.") + return + playsound(E.loc, E.usesound, 50, 1) + to_chat(user, "Teleporting [src.name]...") + E.teleporting = TRUE + if(!do_after(user, 50 * E.toolspeed, target = src)) + E.teleporting = 0 + return + E.teleporting = 0 + if(!(E.rcell && E.rcell.use(E.chargecost))) + to_chat(user, "Unable to teleport, insufficient charge.") + return + do_sparks(5, 1, src) + do_teleport(src, E.pad, 0) + to_chat(user, "Teleport successful. [round(E.rcell.charge/E.chargecost)] charge\s left.") + return + + else + E.rand_x = rand(50,200) + E.rand_y = rand(50,200) + var/L = locate(E.rand_x, E.rand_y, 6) + if(!Adjacent(user)) + to_chat(user, "Unable to teleport, too far from crate.") + return + playsound(E.loc, E.usesound, 50, 1) + to_chat(user, "Teleporting [src.name]...") + E.teleporting = TRUE + if(!do_after(user, 50 * E.toolspeed, target = src)) + E.teleporting = FALSE + return + E.teleporting = 0 + if(!(E.rcell && E.rcell.use(E.chargecost))) + to_chat(user, "Unable to teleport, insufficient charge.") + return + do_sparks(5, 1, src) + do_teleport(src, L) + to_chat(user, "Teleport successful. [round(E.rcell.charge/E.chargecost)] charge\s left.") + return + else + to_chat(user, "Out of charges.") + return + + if(opened) + if(isrobot(user)) + return + if(!user.drop_item()) //couldn't drop the item + to_chat(user, "\The [W] is stuck to your hand, you cannot put it in \the [src]!") + return + if(W) + W.forceMove(loc) + else if(istype(W, /obj/item/stack/packageWrap)) + return + else if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = W + if(rigged) + to_chat(user, "[src] is already rigged!") + return + if(C.use(15)) + to_chat(user, "You rig [src].") + rigged = TRUE + else + to_chat(user, "You need atleast 15 wires to rig [src]!") + return + else if(istype(W, /obj/item/radio/electropack)) + if(rigged) + to_chat(user, "You attach [W] to [src].") + user.drop_item() + W.forceMove(src) + return + else if(istype(W, /obj/item/wirecutters)) + if(rigged) + to_chat(user, "You cut away the wiring.") + playsound(loc, W.usesound, 100, 1) + rigged = FALSE + return + else if(user.a_intent != INTENT_HARM) + attack_hand(user) + else + return ..() + +/obj/structure/closet/singularity_act() + dump_contents() + ..() + +/obj/structure/closet/crate/welder_act() + return + +/obj/structure/closet/crate/attack_hand(mob/user) + if(manifest) + to_chat(user, "You tear the manifest off of the crate.") + playsound(src.loc, 'sound/items/poster_ripped.ogg', 75, 1) + manifest.forceMove(loc) + if(ishuman(user)) + user.put_in_hands(manifest) + manifest = null + update_icon() + return + else + if(rigged && locate(/obj/item/radio/electropack) in src) + if(isliving(user)) + var/mob/living/L = user + if(L.electrocute_act(17, src)) + do_sparks(5, 1, src) + return + src.add_fingerprint(user) + src.toggle(user) + +// Called when a crate is delivered by MULE at a location, for notifying purposes +/obj/structure/closet/crate/proc/notifyRecipient(var/destination) + var/msg = "[capitalize(name)] has arrived at [destination]." + if(destination in announce_beacons) + for(var/obj/machinery/requests_console/D in allConsoles) + if(D.department in src.announce_beacons[destination]) + D.createMessage(name, "Your Crate has Arrived!", msg, 1) + +/obj/structure/closet/crate/secure + desc = "A secure crate." + name = "Secure crate" + icon_state = "securecrate" + icon_opened = "securecrateopen" + icon_closed = "securecrate" + var/redlight = "securecrater" + var/greenlight = "securecrateg" + var/sparks = "securecratesparks" + var/emag = "securecrateemag" + max_integrity = 500 + armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + damage_deflection = 25 + var/tamperproof = 0 + broken = 0 + locked = 1 + +/obj/structure/closet/crate/secure/update_icon() + ..() + overlays.Cut() + if(manifest) + overlays += "manifest" + if(locked) + overlays += redlight + else if(broken) + overlays += emag + else + overlays += greenlight + +/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + if(prob(tamperproof) && damage_amount >= DAMAGE_PRECISION) + boom() + else + return ..() + +/obj/structure/closet/crate/secure/proc/boom(mob/user) + if(user) + to_chat(user, "The crate's anti-tamper system activates!") + investigate_log("[key_name(user)] has detonated a [src]", INVESTIGATE_BOMB) + for(var/atom/movable/AM in src) + qdel(AM) + explosion(get_turf(src), 0, 1, 5, 5) + qdel(src) + +/obj/structure/closet/crate/secure/can_open() + return !locked + +/obj/structure/closet/crate/secure/proc/togglelock(mob/user) + if(src.opened) + to_chat(user, "Close the crate first.") + return + if(src.broken) + to_chat(user, "The crate appears to be broken.") + return + if(src.allowed(user)) + src.locked = !src.locked + visible_message("The crate has been [locked ? null : "un"]locked by [user].") + update_icon() + else + to_chat(user, "Access Denied") + +/obj/structure/closet/crate/secure/verb/verb_togglelock() + set src in oview(1) // One square distance + set category = null + set name = "Toggle Lock" + + if(!usr.canmove || usr.stat || usr.restrained()) // Don't use it if you're not able to! Checks for stuns, ghost and restrain + return + + if(ishuman(usr)) + src.add_fingerprint(usr) + src.togglelock(usr) + else + to_chat(usr, "This mob type can't use this verb.") + +/obj/structure/closet/crate/secure/attack_hand(mob/user) + if(manifest) + to_chat(user, "You tear the manifest off of the crate.") + playsound(src.loc, 'sound/items/poster_ripped.ogg', 75, 1) + manifest.forceMove(loc) + if(ishuman(user)) + user.put_in_hands(manifest) + manifest = null + update_icon() + return + if(locked) + src.togglelock(user) + else + src.toggle(user) + + +/obj/structure/closet/crate/secure/attackby(obj/item/W, mob/user, params) + if(is_type_in_list(W, list(/obj/item/stack/packageWrap, /obj/item/stack/cable_coil, /obj/item/radio/electropack, /obj/item/wirecutters,/obj/item/rcs))) + return ..() + if((istype(W, /obj/item/card/emag) || istype(W, /obj/item/melee/energy/blade))) + emag_act(user) + return + if(!opened) + src.togglelock(user) + return + return ..() + +/obj/structure/closet/crate/secure/emag_act(mob/user) + if(locked) + overlays += sparks + spawn(6) overlays -= sparks //Tried lots of stuff but nothing works right. so i have to use this *sadface* + playsound(src.loc, "sparks", 60, 1) + src.locked = 0 + src.broken = 1 + update_icon() + to_chat(user, "You unlock \the [src].") + +/obj/structure/closet/crate/secure/emp_act(severity) + for(var/obj/O in src) + O.emp_act(severity) + if(!broken && !opened && prob(50/severity)) + if(!locked) + src.locked = 1 + else + overlays += sparks + spawn(6) overlays -= sparks //Tried lots of stuff but nothing works right. so i have to use this *sadface* + playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1) + src.locked = 0 + update_icon() + if(!opened && prob(20/severity)) + if(!locked) + open() + else + src.req_access = list() + src.req_access += pick(get_all_accesses()) + ..() + +/obj/structure/closet/crate/plastic + name = "plastic crate" + desc = "A rectangular plastic crate." + icon_state = "plasticcrate" + icon_opened = "plasticcrateopen" + icon_closed = "plasticcrate" + +/obj/structure/closet/crate/internals + desc = "A internals crate." + name = "internals crate" + icon_state = "o2crate" + icon_opened = "o2crateopen" + icon_closed = "o2crate" + +/obj/structure/closet/crate/trashcart + desc = "A heavy, metal trashcart with wheels." + name = "trash Cart" + icon_state = "trashcart" + icon_opened = "trashcartopen" + icon_closed = "trashcart" + +/*these aren't needed anymore +/obj/structure/closet/crate/hat + desc = "A crate filled with Valuable Collector's Hats!." + name = "Hat Crate" + icon_state = "crate" + icon_opened = "crateopen" + icon_closed = "crate" + +/obj/structure/closet/crate/contraband + name = "Poster crate" + desc = "A random assortment of posters manufactured by providers NOT listed under Nanotrasen's whitelist." + icon_state = "crate" + icon_opened = "crateopen" + icon_closed = "crate" +*/ + +/obj/structure/closet/crate/medical + desc = "A medical crate." + name = "medical crate" + icon_state = "medicalcrate" + icon_opened = "medicalcrateopen" + icon_closed = "medicalcrate" + +/obj/structure/closet/crate/rcd + desc = "A crate for the storage of the RCD." + name = "\improper RCD crate" + icon_state = "crate" + icon_opened = "crateopen" + icon_closed = "crate" + +/obj/structure/closet/crate/rcd/New() + ..() + new /obj/item/rcd_ammo(src) + new /obj/item/rcd_ammo(src) + new /obj/item/rcd_ammo(src) + new /obj/item/rcd(src) + +/obj/structure/closet/crate/freezer + desc = "A freezer." + name = "Freezer" + icon_state = "freezer" + icon_opened = "freezeropen" + icon_closed = "freezer" + var/target_temp = T0C - 40 + var/cooling_power = 40 + + return_air() + var/datum/gas_mixture/gas = (..()) + if(!gas) return null + var/datum/gas_mixture/newgas = new/datum/gas_mixture() + newgas.oxygen = gas.oxygen + newgas.carbon_dioxide = gas.carbon_dioxide + newgas.nitrogen = gas.nitrogen + newgas.toxins = gas.toxins + newgas.volume = gas.volume + newgas.temperature = gas.temperature + if(newgas.temperature <= target_temp) return + + if((newgas.temperature - cooling_power) > target_temp) + newgas.temperature -= cooling_power + else + newgas.temperature = target_temp + return newgas + + +/obj/structure/closet/crate/can + desc = "A large can, looks like a bin to me." + name = "garbage can" + icon_state = "largebin" + icon_opened = "largebinopen" + icon_closed = "largebin" + anchored = TRUE + +/obj/structure/closet/crate/can/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + default_unfasten_wrench(user, I, 40) + +/obj/structure/closet/crate/radiation + desc = "A crate with a radiation sign on it." + name = "radioactive gear crate" + icon_state = "radiation" + icon_opened = "radiationopen" + icon_closed = "radiation" + +/obj/structure/closet/crate/radiation/New() + ..() + new /obj/item/clothing/suit/radiation(src) + new /obj/item/clothing/head/radiation(src) + new /obj/item/clothing/suit/radiation(src) + new /obj/item/clothing/head/radiation(src) + new /obj/item/clothing/suit/radiation(src) + new /obj/item/clothing/head/radiation(src) + new /obj/item/clothing/suit/radiation(src) + new /obj/item/clothing/head/radiation(src) + +/obj/structure/closet/crate/secure/weapon + desc = "A secure weapons crate." + name = "weapons crate" + icon_state = "weaponcrate" + icon_opened = "weaponcrateopen" + icon_closed = "weaponcrate" + +/obj/structure/closet/crate/secure/plasma + desc = "A secure plasma crate." + name = "plasma crate" + icon_state = "plasmacrate" + icon_opened = "plasmacrateopen" + icon_closed = "plasmacrate" + +/obj/structure/closet/crate/secure/gear + desc = "A secure gear crate." + name = "gear crate" + icon_state = "secgearcrate" + icon_opened = "secgearcrateopen" + icon_closed = "secgearcrate" + +/obj/structure/closet/crate/secure/hydrosec + desc = "A crate with a lock on it, painted in the scheme of the station's botanists." + name = "secure hydroponics crate" + icon_state = "hydrosecurecrate" + icon_opened = "hydrosecurecrateopen" + icon_closed = "hydrosecurecrate" + +/obj/structure/closet/crate/secure/bin + desc = "A secure bin." + name = "secure bin" + icon_state = "largebins" + icon_opened = "largebinsopen" + icon_closed = "largebins" + redlight = "largebinr" + greenlight = "largebing" + sparks = "largebinsparks" + emag = "largebinemag" + +/obj/structure/closet/crate/large + name = "large crate" + desc = "A hefty metal crate." + icon_state = "largemetal" + icon_opened = "largemetalopen" + icon_closed = "largemetal" + integrity_failure = 0 //Makes the crate break when integrity reaches 0, instead of opening and becoming an invisible sprite. + +/obj/structure/closet/crate/large/close() + . = ..() + if(.)//we can hold up to one large item + var/found = 0 + for(var/obj/structure/S in src.loc) + if(S == src) + continue + if(!S.anchored) + found = 1 + S.forceMove(src) + break + if(!found) + for(var/obj/machinery/M in src.loc) + if(!M.anchored) + M.forceMove(src) + break + +/obj/structure/closet/crate/secure/large + name = "large crate" + desc = "A hefty metal crate with an electronic locking system." + icon_state = "largemetal" + icon_opened = "largemetalopen" + icon_closed = "largemetal" + redlight = "largemetalr" + greenlight = "largemetalg" + +/obj/structure/closet/crate/secure/large/close() + . = ..() + if(.)//we can hold up to one large item + var/found = 0 + for(var/obj/structure/S in src.loc) + if(S == src) + continue + if(!S.anchored) + found = 1 + S.forceMove(src) + break + if(!found) + for(var/obj/machinery/M in src.loc) + if(!M.anchored) + M.forceMove(src) + break + +//fluff variant +/obj/structure/closet/crate/secure/large/reinforced + desc = "A hefty, reinforced metal crate with an electronic locking system." + icon_state = "largermetal" + icon_opened = "largermetalopen" + icon_closed = "largermetal" + +/obj/structure/closet/crate/hydroponics + name = "hydroponics crate" + desc = "All you need to destroy those pesky weeds and pests." + icon_state = "hydrocrate" + icon_opened = "hydrocrateopen" + icon_closed = "hydrocrate" + +/obj/structure/closet/crate/hydroponics/prespawned + //This exists so the prespawned hydro crates spawn with their contents. + + New() + ..() + new /obj/item/reagent_containers/glass/bucket(src) + new /obj/item/reagent_containers/glass/bucket(src) + new /obj/item/screwdriver(src) + new /obj/item/screwdriver(src) + new /obj/item/wrench(src) + new /obj/item/wrench(src) + new /obj/item/wirecutters(src) + new /obj/item/wirecutters(src) + new /obj/item/shovel/spade(src) + new /obj/item/shovel/spade(src) + new /obj/item/storage/box/beakers(src) + new /obj/item/storage/box/beakers(src) + new /obj/item/hand_labeler(src) + new /obj/item/hand_labeler(src) + +/obj/structure/closet/crate/sci + name = "science crate" + desc = "A science crate." + icon_state = "scicrate" + icon_opened = "scicrateopen" + icon_closed = "scicrate" + +/obj/structure/closet/crate/secure/scisec + name = "secure science crate" + desc = "A crate with a lock on it, painted in the scheme of the station's scientists." + icon_state = "scisecurecrate" + icon_opened = "scisecurecrateopen" + icon_closed = "scisecurecrate" + +/obj/structure/closet/crate/engineering + name = "engineering crate" + desc = "An engineering crate." + icon_state = "engicrate" + icon_opened = "engicrateopen" + icon_closed = "engicrate" + +/obj/structure/closet/crate/secure/engineering + name = "secure engineering crate" + desc = "A crate with a lock on it, painted in the scheme of the station's engineers." + icon_state = "engisecurecrate" + icon_opened = "engisecurecrateopen" + icon_closed = "engisecurecrate" + +/obj/structure/closet/crate/engineering/electrical + name = "electrical engineering crate" + desc = "An electrical engineering crate." + icon_state = "electricalcrate" + icon_opened = "electricalcrateopen" + icon_closed = "electricalcrate" + +/obj/structure/closet/crate/tape/New() + if(prob(10)) + new /obj/item/bikehorn/rubberducky(src) + ..() + +//crates of gear in the free golem ship +/obj/structure/closet/crate/golemgear/New() + ..() + new /obj/item/storage/backpack/industrial(src) + new /obj/item/shovel(src) + new /obj/item/pickaxe(src) + new /obj/item/t_scanner/adv_mining_scanner/lesser(src) + new /obj/item/storage/bag/ore(src) + new /obj/item/clothing/glasses/meson(src) + new /obj/item/card/id/golem(src) + new /obj/item/flashlight/lantern(src) diff --git a/code/game/objects/structures/crates_lockers/crittercrate.dm b/code/game/objects/structures/crates_lockers/crittercrate.dm index a476440629c..fd726aab6a7 100644 --- a/code/game/objects/structures/crates_lockers/crittercrate.dm +++ b/code/game/objects/structures/crates_lockers/crittercrate.dm @@ -88,4 +88,4 @@ /obj/structure/closet/critter/deer name = "deer crate" - content_mob = /mob/living/simple_animal/deer \ No newline at end of file + content_mob = /mob/living/simple_animal/deer diff --git a/code/game/objects/structures/crates_lockers/largecrate.dm b/code/game/objects/structures/crates_lockers/largecrate.dm index 7b30dee1f73..3e763ca223c 100644 --- a/code/game/objects/structures/crates_lockers/largecrate.dm +++ b/code/game/objects/structures/crates_lockers/largecrate.dm @@ -1,99 +1,99 @@ -/obj/structure/largecrate - name = "large crate" - desc = "A hefty wooden crate." - icon = 'icons/obj/crates.dmi' - icon_state = "largecrate" - density = 1 - var/obj/item/paper/manifest/manifest - -/obj/structure/largecrate/New() - ..() - update_icon() - -/obj/structure/largecrate/update_icon() - ..() - overlays.Cut() - if(manifest) - overlays += "manifest" - -/obj/structure/largecrate/attack_hand(mob/user as mob) - if(manifest) - to_chat(user, "You tear the manifest off of the crate.") - playsound(src.loc, 'sound/items/poster_ripped.ogg', 75, 1) - manifest.forceMove(loc) - if(ishuman(user)) - user.put_in_hands(manifest) - manifest = null - update_icon() - return - else - to_chat(user, "You need a crowbar to pry this open!") - return - -/obj/structure/largecrate/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/crowbar)) - if(manifest) - manifest.forceMove(loc) - manifest = null - update_icon() - new /obj/item/stack/sheet/wood(src) - var/turf/T = get_turf(src) - for(var/O in contents) - var/atom/movable/A = O - A.forceMove(T) - user.visible_message("[user] pries \the [src] open.", \ - "You pry open \the [src].", \ - "You hear splitting wood.") - qdel(src) - else if(user.a_intent != INTENT_HARM) - attack_hand(user) - else - return ..() - -/obj/structure/largecrate/mule - -/obj/structure/largecrate/lisa - icon_state = "lisacrate" - -/obj/structure/largecrate/lisa/attackby(obj/item/W as obj, mob/user as mob) //ugly but oh well - if(istype(W, /obj/item/crowbar)) - new /mob/living/simple_animal/pet/dog/corgi/Lisa(loc) - return ..() - -/obj/structure/largecrate/cow - name = "cow crate" - icon_state = "lisacrate" - -/obj/structure/largecrate/cow/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/crowbar)) - new /mob/living/simple_animal/cow(loc) - return ..() - -/obj/structure/largecrate/goat - name = "goat crate" - icon_state = "lisacrate" - -/obj/structure/largecrate/goat/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/crowbar)) - new /mob/living/simple_animal/hostile/retaliate/goat(loc) - return ..() - -/obj/structure/largecrate/chick - name = "chicken crate" - icon_state = "lisacrate" - -/obj/structure/largecrate/chick/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/crowbar)) - var/num = rand(4, 6) - for(var/i = 0, i < num, i++) - new /mob/living/simple_animal/chick(loc) - return ..() - -/obj/structure/largecrate/cat - name = "cat crate" - icon_state = "lisacrate" - -/obj/structure/largecrate/cat/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/crowbar)) - new /mob/living/simple_animal/pet/cat(loc) - return ..() \ No newline at end of file +/obj/structure/largecrate + name = "large crate" + desc = "A hefty wooden crate." + icon = 'icons/obj/crates.dmi' + icon_state = "largecrate" + density = 1 + var/obj/item/paper/manifest/manifest + +/obj/structure/largecrate/New() + ..() + update_icon() + +/obj/structure/largecrate/update_icon() + ..() + overlays.Cut() + if(manifest) + overlays += "manifest" + +/obj/structure/largecrate/attack_hand(mob/user as mob) + if(manifest) + to_chat(user, "You tear the manifest off of the crate.") + playsound(src.loc, 'sound/items/poster_ripped.ogg', 75, 1) + manifest.forceMove(loc) + if(ishuman(user)) + user.put_in_hands(manifest) + manifest = null + update_icon() + return + else + to_chat(user, "You need a crowbar to pry this open!") + return + +/obj/structure/largecrate/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/crowbar)) + if(manifest) + manifest.forceMove(loc) + manifest = null + update_icon() + new /obj/item/stack/sheet/wood(src) + var/turf/T = get_turf(src) + for(var/O in contents) + var/atom/movable/A = O + A.forceMove(T) + user.visible_message("[user] pries \the [src] open.", \ + "You pry open \the [src].", \ + "You hear splitting wood.") + qdel(src) + else if(user.a_intent != INTENT_HARM) + attack_hand(user) + else + return ..() + +/obj/structure/largecrate/mule + +/obj/structure/largecrate/lisa + icon_state = "lisacrate" + +/obj/structure/largecrate/lisa/attackby(obj/item/W as obj, mob/user as mob) //ugly but oh well + if(istype(W, /obj/item/crowbar)) + new /mob/living/simple_animal/pet/dog/corgi/Lisa(loc) + return ..() + +/obj/structure/largecrate/cow + name = "cow crate" + icon_state = "lisacrate" + +/obj/structure/largecrate/cow/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/crowbar)) + new /mob/living/simple_animal/cow(loc) + return ..() + +/obj/structure/largecrate/goat + name = "goat crate" + icon_state = "lisacrate" + +/obj/structure/largecrate/goat/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/crowbar)) + new /mob/living/simple_animal/hostile/retaliate/goat(loc) + return ..() + +/obj/structure/largecrate/chick + name = "chicken crate" + icon_state = "lisacrate" + +/obj/structure/largecrate/chick/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/crowbar)) + var/num = rand(4, 6) + for(var/i = 0, i < num, i++) + new /mob/living/simple_animal/chick(loc) + return ..() + +/obj/structure/largecrate/cat + name = "cat crate" + icon_state = "lisacrate" + +/obj/structure/largecrate/cat/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/crowbar)) + new /mob/living/simple_animal/pet/cat(loc) + return ..() diff --git a/code/game/objects/structures/depot.dm b/code/game/objects/structures/depot.dm index 0309db3dffb..e970ac911dc 100644 --- a/code/game/objects/structures/depot.dm +++ b/code/game/objects/structures/depot.dm @@ -126,4 +126,4 @@ qdel(src) /obj/effect/overload/ex_act(severity) - return \ No newline at end of file + return diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index 313a8e963e3..36a20aa0239 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -1,238 +1,238 @@ -/obj/structure/displaycase - name = "display case" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "glassbox0" - desc = "A display case for prized possessions." - density = TRUE - anchored = TRUE - resistance_flags = ACID_PROOF - armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) - max_integrity = 200 - integrity_failure = 50 - var/obj/item/showpiece = null - var/alert = TRUE - var/open = FALSE - var/openable = TRUE - var/obj/item/airlock_electronics/electronics - var/start_showpiece_type = null //add type for items on display - var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk") - var/trophy_message = "" - -/obj/structure/displaycase/Initialize(mapload) - . = ..() - if(start_showpieces.len && !start_showpiece_type) - var/list/showpiece_entry = pick(start_showpieces) - if (showpiece_entry && showpiece_entry["type"]) - start_showpiece_type = showpiece_entry["type"] - if (showpiece_entry["trophy_message"]) - trophy_message = showpiece_entry["trophy_message"] - if(start_showpiece_type) - showpiece = new start_showpiece_type (src) - update_icon() - -/obj/structure/displaycase/Destroy() - QDEL_NULL(electronics) - QDEL_NULL(showpiece) - return ..() - -/obj/structure/displaycase/examine(mob/user) - . = ..() - if(alert) - . += "Hooked up with an anti-theft system." - if(showpiece) - . += "There's [showpiece] inside." - if(trophy_message) - . += "The plaque reads:\n [trophy_message]" - -/obj/structure/displaycase/proc/dump() - if(showpiece) - showpiece.forceMove(loc) - showpiece = null - -/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/structure/displaycase/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - dump() - if(!disassembled) - new /obj/item/shard(loc) - trigger_alarm() - qdel(src) - -/obj/structure/displaycase/obj_break(damage_flag) - if(!broken && !(flags & NODECONSTRUCT)) - density = FALSE - broken = 1 - new /obj/item/shard( src.loc ) - playsound(src, "shatter", 70, TRUE) - update_icon() - trigger_alarm() - -/obj/structure/displaycase/proc/trigger_alarm() - set waitfor = FALSE - if(alert && is_station_contact(z)) - var/area/alarmed = get_area(src) - alarmed.burglaralert(src) - visible_message("The burglar alarm goes off!") - // Play the burglar alarm three times - for(var/i = 0, i < 4, i++) - playsound(src, 'sound/machines/burglar_alarm.ogg', 50, 0) - sleep(74) // 7.4 seconds long - -/obj/structure/displaycase/update_icon() - var/icon/I - if(open) - I = icon('icons/obj/stationobjs.dmi',"glassbox_open") - else - I = icon('icons/obj/stationobjs.dmi',"glassbox0") - if(broken) - I = icon('icons/obj/stationobjs.dmi',"glassboxb0") - if(showpiece) - var/icon/S = getFlatIcon(showpiece) - S.Scale(17, 17) - I.Blend(S,ICON_UNDERLAY,8,8) - icon = I - -/obj/structure/displaycase/attackby(obj/item/I, mob/user, params) - if(I.GetID() && !broken && openable) - if(allowed(user)) - to_chat(user, "You [open ? "close":"open"] [src].") - toggle_lock(user) - else - to_chat(user, "Access denied.") - else if(open && !showpiece) - if(user.drop_item()) - I.forceMove(src) - showpiece = I - to_chat(user, "You put [I] on display") - update_icon() - else if(istype(I, /obj/item/stack/sheet/glass) && broken) - var/obj/item/stack/sheet/glass/G = I - if(G.get_amount() < 2) - to_chat(user, "You need two glass sheets to fix the case!") - return - to_chat(user, "You start fixing [src]...") - if(do_after(user, 20, target = src)) - G.use(2) - broken = 0 - obj_integrity = max_integrity - update_icon() - else - return ..() - -/obj/structure/displaycase/crowbar_act(mob/user, obj/item/I) //Only applies to the lab cage and player made display cases - if(alert || !openable) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(broken) - if(showpiece) - to_chat(user, "Remove the displayed object first.") - if(I.use_tool(src, user, 0, volume = I.tool_volume)) - to_chat(user, "You remove the destroyed case") - qdel(src) - else - to_chat(user, "You start to [open ? "close":"open"] [src].") - if(!I.use_tool(src, user, 20, volume = I.tool_volume)) - return - to_chat(user, "You [open ? "close":"open"] [src].") - toggle_lock(user) - -obj/structure/displaycase/welder_act(mob/user, obj/item/I) - . = TRUE - if(default_welder_repair(user, I)) - broken = FALSE - -/obj/structure/displaycase/proc/toggle_lock(mob/user) - open = !open - update_icon() - -/obj/structure/displaycase/attack_hand(mob/user) - user.changeNext_move(CLICK_CD_MELEE) - if(showpiece && (broken || open)) - to_chat(user, "You deactivate the hover field built into the case.") - dump() - add_fingerprint(user) - update_icon() - return - else - //prevents remote "kicks" with TK - if(!Adjacent(user)) - return - user.visible_message("[user] kicks the display case.") - user.do_attack_animation(src, ATTACK_EFFECT_KICK) - take_damage(2) - -/obj/structure/displaycase_chassis - anchored = TRUE - density = FALSE - name = "display case chassis" - desc = "The wooden base of a display case." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "glassbox_chassis" - var/obj/item/airlock_electronics/electronics - -/obj/structure/displaycase_chassis/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/airlock_electronics)) - to_chat(user, "You start installing the electronics into [src]...") - playsound(src.loc, I.usesound, 50, 1) - if(do_after(user, 30, target = src)) - if(user.drop_item()) - I.forceMove(src) - electronics = I - to_chat(user, "You install the airlock electronics.") - - else if(istype(I, /obj/item/stack/sheet/glass)) - var/obj/item/stack/sheet/glass/G = I - if(G.get_amount() < 10) - to_chat(user, "You need ten glass sheets to do this!") - return - to_chat(user, "You start adding [G] to [src]...") - if(do_after(user, 20, target = src)) - G.use(10) - var/obj/structure/displaycase/display = new(src.loc) - if(electronics) - electronics.forceMove(display) - display.electronics = electronics - if(electronics.one_access) - display.req_one_access = electronics.conf_access - else - display.req_access = electronics.conf_access - qdel(src) - else - return ..() - -/obj/structure/displaycase_chassis/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - TOOL_ATTEMPT_DISMANTLE_MESSAGE - if(!I.use_tool(src, user, 30, volume = I.tool_volume)) - return - TOOL_DISMANTLE_SUCCESS_MESSAGE - new /obj/item/stack/sheet/wood(get_turf(src), 5) - qdel(src) - -//The lab cage and captains display case do not spawn with electronics, which is why req_access is needed. -/obj/structure/displaycase/captain - alert = TRUE - start_showpiece_type = /obj/item/gun/energy/laser/captain - req_access = list(ACCESS_CAPTAIN) - -/obj/structure/displaycase/labcage - name = "lab cage" - desc = "A glass lab container for storing interesting creatures." - start_showpiece_type = /obj/item/clothing/mask/facehugger/lamarr - req_access = list(ACCESS_RD) - -/obj/structure/displaycase/stechkin - name = "officer's display case" - desc = "A display case containing a humble stechkin pistol. Never forget your roots." - start_showpiece_type = /obj/item/gun/projectile/automatic/pistol - req_access = list(ACCESS_SYNDICATE_COMMAND) \ No newline at end of file +/obj/structure/displaycase + name = "display case" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "glassbox0" + desc = "A display case for prized possessions." + density = TRUE + anchored = TRUE + resistance_flags = ACID_PROOF + armor = list("melee" = 30, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100) + max_integrity = 200 + integrity_failure = 50 + var/obj/item/showpiece = null + var/alert = TRUE + var/open = FALSE + var/openable = TRUE + var/obj/item/airlock_electronics/electronics + var/start_showpiece_type = null //add type for items on display + var/list/start_showpieces = list() //Takes sublists in the form of list("type" = /obj/item/bikehorn, "trophy_message" = "henk") + var/trophy_message = "" + +/obj/structure/displaycase/Initialize(mapload) + . = ..() + if(start_showpieces.len && !start_showpiece_type) + var/list/showpiece_entry = pick(start_showpieces) + if (showpiece_entry && showpiece_entry["type"]) + start_showpiece_type = showpiece_entry["type"] + if (showpiece_entry["trophy_message"]) + trophy_message = showpiece_entry["trophy_message"] + if(start_showpiece_type) + showpiece = new start_showpiece_type (src) + update_icon() + +/obj/structure/displaycase/Destroy() + QDEL_NULL(electronics) + QDEL_NULL(showpiece) + return ..() + +/obj/structure/displaycase/examine(mob/user) + . = ..() + if(alert) + . += "Hooked up with an anti-theft system." + if(showpiece) + . += "There's [showpiece] inside." + if(trophy_message) + . += "The plaque reads:\n [trophy_message]" + +/obj/structure/displaycase/proc/dump() + if(showpiece) + showpiece.forceMove(loc) + showpiece = null + +/obj/structure/displaycase/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/displaycase/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + dump() + if(!disassembled) + new /obj/item/shard(loc) + trigger_alarm() + qdel(src) + +/obj/structure/displaycase/obj_break(damage_flag) + if(!broken && !(flags & NODECONSTRUCT)) + density = FALSE + broken = 1 + new /obj/item/shard( src.loc ) + playsound(src, "shatter", 70, TRUE) + update_icon() + trigger_alarm() + +/obj/structure/displaycase/proc/trigger_alarm() + set waitfor = FALSE + if(alert && is_station_contact(z)) + var/area/alarmed = get_area(src) + alarmed.burglaralert(src) + visible_message("The burglar alarm goes off!") + // Play the burglar alarm three times + for(var/i = 0, i < 4, i++) + playsound(src, 'sound/machines/burglar_alarm.ogg', 50, 0) + sleep(74) // 7.4 seconds long + +/obj/structure/displaycase/update_icon() + var/icon/I + if(open) + I = icon('icons/obj/stationobjs.dmi',"glassbox_open") + else + I = icon('icons/obj/stationobjs.dmi',"glassbox0") + if(broken) + I = icon('icons/obj/stationobjs.dmi',"glassboxb0") + if(showpiece) + var/icon/S = getFlatIcon(showpiece) + S.Scale(17, 17) + I.Blend(S,ICON_UNDERLAY,8,8) + icon = I + +/obj/structure/displaycase/attackby(obj/item/I, mob/user, params) + if(I.GetID() && !broken && openable) + if(allowed(user)) + to_chat(user, "You [open ? "close":"open"] [src].") + toggle_lock(user) + else + to_chat(user, "Access denied.") + else if(open && !showpiece) + if(user.drop_item()) + I.forceMove(src) + showpiece = I + to_chat(user, "You put [I] on display") + update_icon() + else if(istype(I, /obj/item/stack/sheet/glass) && broken) + var/obj/item/stack/sheet/glass/G = I + if(G.get_amount() < 2) + to_chat(user, "You need two glass sheets to fix the case!") + return + to_chat(user, "You start fixing [src]...") + if(do_after(user, 20, target = src)) + G.use(2) + broken = 0 + obj_integrity = max_integrity + update_icon() + else + return ..() + +/obj/structure/displaycase/crowbar_act(mob/user, obj/item/I) //Only applies to the lab cage and player made display cases + if(alert || !openable) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(broken) + if(showpiece) + to_chat(user, "Remove the displayed object first.") + if(I.use_tool(src, user, 0, volume = I.tool_volume)) + to_chat(user, "You remove the destroyed case") + qdel(src) + else + to_chat(user, "You start to [open ? "close":"open"] [src].") + if(!I.use_tool(src, user, 20, volume = I.tool_volume)) + return + to_chat(user, "You [open ? "close":"open"] [src].") + toggle_lock(user) + +obj/structure/displaycase/welder_act(mob/user, obj/item/I) + . = TRUE + if(default_welder_repair(user, I)) + broken = FALSE + +/obj/structure/displaycase/proc/toggle_lock(mob/user) + open = !open + update_icon() + +/obj/structure/displaycase/attack_hand(mob/user) + user.changeNext_move(CLICK_CD_MELEE) + if(showpiece && (broken || open)) + to_chat(user, "You deactivate the hover field built into the case.") + dump() + add_fingerprint(user) + update_icon() + return + else + //prevents remote "kicks" with TK + if(!Adjacent(user)) + return + user.visible_message("[user] kicks the display case.") + user.do_attack_animation(src, ATTACK_EFFECT_KICK) + take_damage(2) + +/obj/structure/displaycase_chassis + anchored = TRUE + density = FALSE + name = "display case chassis" + desc = "The wooden base of a display case." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "glassbox_chassis" + var/obj/item/airlock_electronics/electronics + +/obj/structure/displaycase_chassis/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/airlock_electronics)) + to_chat(user, "You start installing the electronics into [src]...") + playsound(src.loc, I.usesound, 50, 1) + if(do_after(user, 30, target = src)) + if(user.drop_item()) + I.forceMove(src) + electronics = I + to_chat(user, "You install the airlock electronics.") + + else if(istype(I, /obj/item/stack/sheet/glass)) + var/obj/item/stack/sheet/glass/G = I + if(G.get_amount() < 10) + to_chat(user, "You need ten glass sheets to do this!") + return + to_chat(user, "You start adding [G] to [src]...") + if(do_after(user, 20, target = src)) + G.use(10) + var/obj/structure/displaycase/display = new(src.loc) + if(electronics) + electronics.forceMove(display) + display.electronics = electronics + if(electronics.one_access) + display.req_one_access = electronics.conf_access + else + display.req_access = electronics.conf_access + qdel(src) + else + return ..() + +/obj/structure/displaycase_chassis/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + TOOL_ATTEMPT_DISMANTLE_MESSAGE + if(!I.use_tool(src, user, 30, volume = I.tool_volume)) + return + TOOL_DISMANTLE_SUCCESS_MESSAGE + new /obj/item/stack/sheet/wood(get_turf(src), 5) + qdel(src) + +//The lab cage and captains display case do not spawn with electronics, which is why req_access is needed. +/obj/structure/displaycase/captain + alert = TRUE + start_showpiece_type = /obj/item/gun/energy/laser/captain + req_access = list(ACCESS_CAPTAIN) + +/obj/structure/displaycase/labcage + name = "lab cage" + desc = "A glass lab container for storing interesting creatures." + start_showpiece_type = /obj/item/clothing/mask/facehugger/lamarr + req_access = list(ACCESS_RD) + +/obj/structure/displaycase/stechkin + name = "officer's display case" + desc = "A display case containing a humble stechkin pistol. Never forget your roots." + start_showpiece_type = /obj/item/gun/projectile/automatic/pistol + req_access = list(ACCESS_SYNDICATE_COMMAND) diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm index ee30932dabe..354e7fea81b 100644 --- a/code/game/objects/structures/door_assembly.dm +++ b/code/game/objects/structures/door_assembly.dm @@ -1,313 +1,313 @@ -/obj/structure/door_assembly - name = "airlock assembly" - icon = 'icons/obj/doors/airlocks/station/public.dmi' - icon_state = "construction" - anchored = FALSE - density = TRUE - max_integrity = 200 - var/overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi' - var/state = AIRLOCK_ASSEMBLY_NEEDS_WIRES - var/mineral - var/base_name = "airlock" - var/obj/item/airlock_electronics/electronics - var/airlock_type = /obj/machinery/door/airlock //the type path of the airlock once completed - var/glass_type = /obj/machinery/door/airlock/glass - var/glass = 0 // 0 = glass can be installed. 1 = glass is already installed. - var/created_name - var/heat_proof_finished = 0 //whether to heat-proof the finished airlock - var/previous_assembly = /obj/structure/door_assembly - var/noglass = FALSE //airlocks with no glass version, also cannot be modified with sheets - var/material_type = /obj/item/stack/sheet/metal - var/material_amt = 4 - -/obj/structure/door_assembly/New() - update_icon() - update_name() - ..() - -/obj/structure/door_assembly/Destroy() - QDEL_NULL(electronics) - return ..() - -/obj/structure/door_assembly/examine(mob/user) - . = ..() - var/doorname = "" - if(created_name) - doorname = ", written on it is '[created_name]'" - switch(state) - if(AIRLOCK_ASSEMBLY_NEEDS_WIRES) - if(anchored) - . += "The anchoring bolts are wrenched in place, but the maintenance panel lacks wiring." - else - . += "The assembly is welded together, but the anchoring bolts are unwrenched." - if(AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) - . += "The maintenance panel is wired, but the circuit slot is empty." - if(AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER) - . += "The circuit is connected loosely to its slot, but the maintenance panel is unscrewed and open." - if(!mineral && !glass && !noglass) - . += "There is a small paper placard on the assembly[doorname]. There are empty slots for glass windows and mineral covers." - else if(!mineral && glass && !noglass) - . += "There is a small paper placard on the assembly[doorname]. There are empty slots for mineral covers." - else if(mineral && !glass && !noglass) - . += "There is a small paper placard on the assembly[doorname]. There are empty slots for glass windows." - else - . += "There is a small paper placard on the assembly[doorname]." - -/obj/structure/door_assembly/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - var/t = copytext(stripped_input(user, "Enter the name for the door.", name, created_name),1,MAX_NAME_LEN) - if(!t) - return - if(!in_range(src, usr) && loc != usr) - return - created_name = t - return - - else if(iscoil(W) && state == AIRLOCK_ASSEMBLY_NEEDS_WIRES && anchored) - var/obj/item/stack/cable_coil/coil = W - if(coil.get_amount() < 1) - to_chat(user, "You need one length of cable to wire the airlock assembly!") - return - user.visible_message("[user] wires the airlock assembly.", "You start to wire the airlock assembly...") - if(do_after(user, 40 * coil.toolspeed, target = src)) - if(coil.get_amount() < 1 || state != AIRLOCK_ASSEMBLY_NEEDS_WIRES) - return - coil.use(1) - state = AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS - to_chat(user, "You wire the airlock assembly.") - - else if(istype(W, /obj/item/airlock_electronics) && state == AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS && W.icon_state != "door_electronics_smoked") - playsound(loc, W.usesound, 100, 1) - user.visible_message("[user] installs the electronics into the airlock assembly.", "You start to install electronics into the airlock assembly...") - - if(do_after(user, 40 * W.toolspeed, target = src)) - if(state != AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) - return - user.drop_item() - W.forceMove(src) - to_chat(user, "You install the airlock electronics.") - state = AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER - name = "near finished airlock assembly" - electronics = W - - else if(istype(W, /obj/item/stack/sheet) && (!glass || !mineral)) - var/obj/item/stack/sheet/S = W - if(S) - if(S.get_amount() >= 1) - if(!noglass) - if(!glass) - if(istype(S, /obj/item/stack/sheet/rglass) || istype(S, /obj/item/stack/sheet/glass)) - playsound(loc, S.usesound, 100, 1) - user.visible_message("[user] adds [S.name] to the airlock assembly.", "You start to install [S.name] into the airlock assembly...") - if(do_after(user, 40 * S.toolspeed, target = src)) - if(S.get_amount() < 1 || glass) - return - if(S.type == /obj/item/stack/sheet/rglass) - to_chat(user, "You install reinforced glass windows into the airlock assembly.") - heat_proof_finished = TRUE //reinforced glass makes the airlock heat-proof - else - to_chat(user, "You install regular glass windows into the airlock assembly.") - S.use(1) - glass = TRUE - if(!mineral) - if(istype(S, /obj/item/stack/sheet/mineral) && S.sheettype) - var/M = S.sheettype - if(S.get_amount() >= 2) - playsound(loc, S.usesound, 100, 1) - user.visible_message("[user] adds [S.name] to the airlock assembly.", "You start to install [S.name] into the airlock assembly...") - if(do_after(user, 40 * S.toolspeed, target = src)) - if(S.get_amount() < 2 || mineral) - return - to_chat(user, "You install [M] plating into the airlock assembly.") - S.use(2) - var/mineralassembly = text2path("/obj/structure/door_assembly/door_assembly_[M]") - var/obj/structure/door_assembly/MA = new mineralassembly(loc) - transfer_assembly_vars(src, MA, TRUE) - else - to_chat(user, "You need at least two sheets to add a mineral cover!") - else - to_chat(user, "You cannot add [S] to [src]!") - else - to_chat(user, "You cannot add [S] to [src]!") - else - return ..() - update_name() - update_icon() - -/obj/structure/door_assembly/crowbar_act(mob/user, obj/item/I) - if(state != AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER ) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - user.visible_message("[user] is removing the electronics from the airlock assembly...", "You start to remove electronics from the airlock assembly...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER) - return - to_chat(user, "You remove the airlock electronics.") - state = AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS - name = "wired airlock assembly" - var/obj/item/airlock_electronics/ae - if(!electronics) - ae = new/obj/item/airlock_electronics(loc) - else - ae = electronics - electronics = null - ae.forceMove(loc) - update_icon() - update_name() - -/obj/structure/door_assembly/screwdriver_act(mob/user, obj/item/I) - if(state != AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER ) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - user.visible_message("[user] is finishing the airlock...", \ - "You start finishing the airlock...") - . = TRUE - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER) - return - to_chat(user, "You finish the airlock.") - var/obj/machinery/door/airlock/door - if(glass) - door = new glass_type(loc) - else - door = new airlock_type(loc) - door.setDir(dir) - door.electronics = electronics - door.unres_sides = electronics.unres_sides - door.heat_proof = heat_proof_finished - if(electronics.one_access) - door.req_access = null - door.req_one_access = electronics.conf_access - else - door.req_access = electronics.conf_access - if(created_name) - door.name = created_name - else - door.name = base_name - door.previous_airlock = previous_assembly - electronics.forceMove(door) - qdel(src) - update_icon() - -/obj/structure/door_assembly/wirecutter_act(mob/user, obj/item/I) - if(state != AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - user.visible_message("[user] is cutting the wires from the airlock assembly...", "You start to cut the wires from airlock assembly...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) - return - to_chat(user, "You cut the wires from the airlock assembly.") - new/obj/item/stack/cable_coil(get_turf(user), 1) - state = AIRLOCK_ASSEMBLY_NEEDS_WIRES - update_icon() - -/obj/structure/door_assembly/wrench_act(mob/user, obj/item/I) - if(state != AIRLOCK_ASSEMBLY_NEEDS_WIRES) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(anchored) - user.visible_message("[user] is unsecuring the airlock assembly from the floor...", "You start to unsecure the airlock assembly from the floor...") - else - user.visible_message("[user] is securing the airlock assembly to the floor...", "You start to secure the airlock assembly to the floor...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != AIRLOCK_ASSEMBLY_NEEDS_WIRES) - return - to_chat(user, "You [anchored ? "un" : ""]secure the airlock assembly.") - anchored = !anchored - -/obj/structure/door_assembly/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(mineral) - var/obj/item/stack/sheet/mineral/mineral_path = text2path("/obj/item/stack/sheet/mineral/[mineral]") - visible_message("[user] welds the [mineral] plating off [src].",\ - "You start to weld the [mineral] plating off [src]...",\ - "You hear welding.") - if(!I.use_tool(src, user, 40, volume = I.tool_volume)) - return - to_chat(user, "You weld the [mineral] plating off.") - new mineral_path(loc, 2) - var/obj/structure/door_assembly/PA = new previous_assembly(loc) - transfer_assembly_vars(src, PA) - else if(glass) - visible_message("[user] welds the glass panel out of [src].",\ - "You start to weld the glass panel out of the [src]...",\ - "You hear welding.") - if(!I.use_tool(src, user, 40, volume = I.tool_volume)) - return - to_chat(user, "You weld the glass panel out.") - if(heat_proof_finished) - new /obj/item/stack/sheet/rglass(get_turf(src)) - heat_proof_finished = FALSE - else - new /obj/item/stack/sheet/glass(get_turf(src)) - glass = FALSE - else if(!anchored) - visible_message("[user] disassembles [src].", \ - "You start to disassemble [src]...",\ - "You hear welding.") - if(!I.use_tool(src, user, 40, volume = I.tool_volume)) - return - to_chat(user, "You disassemble the airlock assembly.") - deconstruct(TRUE) - update_icon() - -/obj/structure/door_assembly/update_icon() - overlays.Cut() - if(!glass) - overlays += get_airlock_overlay("fill_construction", icon) - else if(glass) - overlays += get_airlock_overlay("glass_construction", overlays_file) - overlays += get_airlock_overlay("panel_c[state+1]", overlays_file) - -/obj/structure/door_assembly/proc/update_name() - name = "" - switch(state) - if(AIRLOCK_ASSEMBLY_NEEDS_WIRES) - if(anchored) - name = "secured " - if(AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) - name = "wired " - if(AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER) - name = "near finished " - name += "[heat_proof_finished ? "heat-proofed " : ""][glass ? "window " : ""][base_name] assembly" - -/obj/structure/door_assembly/proc/transfer_assembly_vars(obj/structure/door_assembly/source, obj/structure/door_assembly/target, previous = FALSE) - target.glass = source.glass - target.heat_proof_finished = source.heat_proof_finished - target.created_name = source.created_name - target.state = source.state - target.anchored = source.anchored - if(previous) - target.previous_assembly = source.type - if(electronics) - target.electronics = source.electronics - source.electronics.forceMove(target) - target.update_icon() - target.update_name() - qdel(source) - -/obj/structure/door_assembly/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - var/turf/T = get_turf(src) - if(!disassembled) - material_amt = rand(2,4) - new material_type(T, material_amt) - if(glass) - if(disassembled) - if(heat_proof_finished) - new /obj/item/stack/sheet/rglass(T) - else - new /obj/item/stack/sheet/glass(T) - else - new /obj/item/shard(T) - if(mineral) - var/obj/item/stack/sheet/mineral/mineral_path = text2path("/obj/item/stack/sheet/mineral/[mineral]") - new mineral_path(T, 2) - qdel(src) +/obj/structure/door_assembly + name = "airlock assembly" + icon = 'icons/obj/doors/airlocks/station/public.dmi' + icon_state = "construction" + anchored = FALSE + density = TRUE + max_integrity = 200 + var/overlays_file = 'icons/obj/doors/airlocks/station/overlays.dmi' + var/state = AIRLOCK_ASSEMBLY_NEEDS_WIRES + var/mineral + var/base_name = "airlock" + var/obj/item/airlock_electronics/electronics + var/airlock_type = /obj/machinery/door/airlock //the type path of the airlock once completed + var/glass_type = /obj/machinery/door/airlock/glass + var/glass = 0 // 0 = glass can be installed. 1 = glass is already installed. + var/created_name + var/heat_proof_finished = 0 //whether to heat-proof the finished airlock + var/previous_assembly = /obj/structure/door_assembly + var/noglass = FALSE //airlocks with no glass version, also cannot be modified with sheets + var/material_type = /obj/item/stack/sheet/metal + var/material_amt = 4 + +/obj/structure/door_assembly/New() + update_icon() + update_name() + ..() + +/obj/structure/door_assembly/Destroy() + QDEL_NULL(electronics) + return ..() + +/obj/structure/door_assembly/examine(mob/user) + . = ..() + var/doorname = "" + if(created_name) + doorname = ", written on it is '[created_name]'" + switch(state) + if(AIRLOCK_ASSEMBLY_NEEDS_WIRES) + if(anchored) + . += "The anchoring bolts are wrenched in place, but the maintenance panel lacks wiring." + else + . += "The assembly is welded together, but the anchoring bolts are unwrenched." + if(AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) + . += "The maintenance panel is wired, but the circuit slot is empty." + if(AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER) + . += "The circuit is connected loosely to its slot, but the maintenance panel is unscrewed and open." + if(!mineral && !glass && !noglass) + . += "There is a small paper placard on the assembly[doorname]. There are empty slots for glass windows and mineral covers." + else if(!mineral && glass && !noglass) + . += "There is a small paper placard on the assembly[doorname]. There are empty slots for mineral covers." + else if(mineral && !glass && !noglass) + . += "There is a small paper placard on the assembly[doorname]. There are empty slots for glass windows." + else + . += "There is a small paper placard on the assembly[doorname]." + +/obj/structure/door_assembly/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + var/t = copytext(stripped_input(user, "Enter the name for the door.", name, created_name),1,MAX_NAME_LEN) + if(!t) + return + if(!in_range(src, usr) && loc != usr) + return + created_name = t + return + + else if(iscoil(W) && state == AIRLOCK_ASSEMBLY_NEEDS_WIRES && anchored) + var/obj/item/stack/cable_coil/coil = W + if(coil.get_amount() < 1) + to_chat(user, "You need one length of cable to wire the airlock assembly!") + return + user.visible_message("[user] wires the airlock assembly.", "You start to wire the airlock assembly...") + if(do_after(user, 40 * coil.toolspeed, target = src)) + if(coil.get_amount() < 1 || state != AIRLOCK_ASSEMBLY_NEEDS_WIRES) + return + coil.use(1) + state = AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS + to_chat(user, "You wire the airlock assembly.") + + else if(istype(W, /obj/item/airlock_electronics) && state == AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS && W.icon_state != "door_electronics_smoked") + playsound(loc, W.usesound, 100, 1) + user.visible_message("[user] installs the electronics into the airlock assembly.", "You start to install electronics into the airlock assembly...") + + if(do_after(user, 40 * W.toolspeed, target = src)) + if(state != AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) + return + user.drop_item() + W.forceMove(src) + to_chat(user, "You install the airlock electronics.") + state = AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER + name = "near finished airlock assembly" + electronics = W + + else if(istype(W, /obj/item/stack/sheet) && (!glass || !mineral)) + var/obj/item/stack/sheet/S = W + if(S) + if(S.get_amount() >= 1) + if(!noglass) + if(!glass) + if(istype(S, /obj/item/stack/sheet/rglass) || istype(S, /obj/item/stack/sheet/glass)) + playsound(loc, S.usesound, 100, 1) + user.visible_message("[user] adds [S.name] to the airlock assembly.", "You start to install [S.name] into the airlock assembly...") + if(do_after(user, 40 * S.toolspeed, target = src)) + if(S.get_amount() < 1 || glass) + return + if(S.type == /obj/item/stack/sheet/rglass) + to_chat(user, "You install reinforced glass windows into the airlock assembly.") + heat_proof_finished = TRUE //reinforced glass makes the airlock heat-proof + else + to_chat(user, "You install regular glass windows into the airlock assembly.") + S.use(1) + glass = TRUE + if(!mineral) + if(istype(S, /obj/item/stack/sheet/mineral) && S.sheettype) + var/M = S.sheettype + if(S.get_amount() >= 2) + playsound(loc, S.usesound, 100, 1) + user.visible_message("[user] adds [S.name] to the airlock assembly.", "You start to install [S.name] into the airlock assembly...") + if(do_after(user, 40 * S.toolspeed, target = src)) + if(S.get_amount() < 2 || mineral) + return + to_chat(user, "You install [M] plating into the airlock assembly.") + S.use(2) + var/mineralassembly = text2path("/obj/structure/door_assembly/door_assembly_[M]") + var/obj/structure/door_assembly/MA = new mineralassembly(loc) + transfer_assembly_vars(src, MA, TRUE) + else + to_chat(user, "You need at least two sheets to add a mineral cover!") + else + to_chat(user, "You cannot add [S] to [src]!") + else + to_chat(user, "You cannot add [S] to [src]!") + else + return ..() + update_name() + update_icon() + +/obj/structure/door_assembly/crowbar_act(mob/user, obj/item/I) + if(state != AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER ) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + user.visible_message("[user] is removing the electronics from the airlock assembly...", "You start to remove electronics from the airlock assembly...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER) + return + to_chat(user, "You remove the airlock electronics.") + state = AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS + name = "wired airlock assembly" + var/obj/item/airlock_electronics/ae + if(!electronics) + ae = new/obj/item/airlock_electronics(loc) + else + ae = electronics + electronics = null + ae.forceMove(loc) + update_icon() + update_name() + +/obj/structure/door_assembly/screwdriver_act(mob/user, obj/item/I) + if(state != AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER ) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + user.visible_message("[user] is finishing the airlock...", \ + "You start finishing the airlock...") + . = TRUE + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER) + return + to_chat(user, "You finish the airlock.") + var/obj/machinery/door/airlock/door + if(glass) + door = new glass_type(loc) + else + door = new airlock_type(loc) + door.setDir(dir) + door.electronics = electronics + door.unres_sides = electronics.unres_sides + door.heat_proof = heat_proof_finished + if(electronics.one_access) + door.req_access = null + door.req_one_access = electronics.conf_access + else + door.req_access = electronics.conf_access + if(created_name) + door.name = created_name + else + door.name = base_name + door.previous_airlock = previous_assembly + electronics.forceMove(door) + qdel(src) + update_icon() + +/obj/structure/door_assembly/wirecutter_act(mob/user, obj/item/I) + if(state != AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + user.visible_message("[user] is cutting the wires from the airlock assembly...", "You start to cut the wires from airlock assembly...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) + return + to_chat(user, "You cut the wires from the airlock assembly.") + new/obj/item/stack/cable_coil(get_turf(user), 1) + state = AIRLOCK_ASSEMBLY_NEEDS_WIRES + update_icon() + +/obj/structure/door_assembly/wrench_act(mob/user, obj/item/I) + if(state != AIRLOCK_ASSEMBLY_NEEDS_WIRES) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(anchored) + user.visible_message("[user] is unsecuring the airlock assembly from the floor...", "You start to unsecure the airlock assembly from the floor...") + else + user.visible_message("[user] is securing the airlock assembly to the floor...", "You start to secure the airlock assembly to the floor...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != AIRLOCK_ASSEMBLY_NEEDS_WIRES) + return + to_chat(user, "You [anchored ? "un" : ""]secure the airlock assembly.") + anchored = !anchored + +/obj/structure/door_assembly/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(mineral) + var/obj/item/stack/sheet/mineral/mineral_path = text2path("/obj/item/stack/sheet/mineral/[mineral]") + visible_message("[user] welds the [mineral] plating off [src].",\ + "You start to weld the [mineral] plating off [src]...",\ + "You hear welding.") + if(!I.use_tool(src, user, 40, volume = I.tool_volume)) + return + to_chat(user, "You weld the [mineral] plating off.") + new mineral_path(loc, 2) + var/obj/structure/door_assembly/PA = new previous_assembly(loc) + transfer_assembly_vars(src, PA) + else if(glass) + visible_message("[user] welds the glass panel out of [src].",\ + "You start to weld the glass panel out of the [src]...",\ + "You hear welding.") + if(!I.use_tool(src, user, 40, volume = I.tool_volume)) + return + to_chat(user, "You weld the glass panel out.") + if(heat_proof_finished) + new /obj/item/stack/sheet/rglass(get_turf(src)) + heat_proof_finished = FALSE + else + new /obj/item/stack/sheet/glass(get_turf(src)) + glass = FALSE + else if(!anchored) + visible_message("[user] disassembles [src].", \ + "You start to disassemble [src]...",\ + "You hear welding.") + if(!I.use_tool(src, user, 40, volume = I.tool_volume)) + return + to_chat(user, "You disassemble the airlock assembly.") + deconstruct(TRUE) + update_icon() + +/obj/structure/door_assembly/update_icon() + overlays.Cut() + if(!glass) + overlays += get_airlock_overlay("fill_construction", icon) + else if(glass) + overlays += get_airlock_overlay("glass_construction", overlays_file) + overlays += get_airlock_overlay("panel_c[state+1]", overlays_file) + +/obj/structure/door_assembly/proc/update_name() + name = "" + switch(state) + if(AIRLOCK_ASSEMBLY_NEEDS_WIRES) + if(anchored) + name = "secured " + if(AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS) + name = "wired " + if(AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER) + name = "near finished " + name += "[heat_proof_finished ? "heat-proofed " : ""][glass ? "window " : ""][base_name] assembly" + +/obj/structure/door_assembly/proc/transfer_assembly_vars(obj/structure/door_assembly/source, obj/structure/door_assembly/target, previous = FALSE) + target.glass = source.glass + target.heat_proof_finished = source.heat_proof_finished + target.created_name = source.created_name + target.state = source.state + target.anchored = source.anchored + if(previous) + target.previous_assembly = source.type + if(electronics) + target.electronics = source.electronics + source.electronics.forceMove(target) + target.update_icon() + target.update_name() + qdel(source) + +/obj/structure/door_assembly/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + var/turf/T = get_turf(src) + if(!disassembled) + material_amt = rand(2,4) + new material_type(T, material_amt) + if(glass) + if(disassembled) + if(heat_proof_finished) + new /obj/item/stack/sheet/rglass(T) + else + new /obj/item/stack/sheet/glass(T) + else + new /obj/item/shard(T) + if(mineral) + var/obj/item/stack/sheet/mineral/mineral_path = text2path("/obj/item/stack/sheet/mineral/[mineral]") + new mineral_path(T, 2) + qdel(src) diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm index f432e19b51a..878b03436ff 100644 --- a/code/game/objects/structures/dresser.dm +++ b/code/game/objects/structures/dresser.dm @@ -79,4 +79,4 @@ /obj/structure/dresser/deconstruct(disassembled = TRUE) new /obj/item/stack/sheet/wood(drop_location(), 30) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/structures/electricchair.dm b/code/game/objects/structures/electricchair.dm index 995e86b5dfb..792558d01fa 100644 --- a/code/game/objects/structures/electricchair.dm +++ b/code/game/objects/structures/electricchair.dm @@ -80,4 +80,4 @@ spawn(1) buckled_mob.electrocute_act(110, src, 1) A.power_light = light - A.updateicon() \ No newline at end of file + A.updateicon() diff --git a/code/game/objects/structures/engicart.dm b/code/game/objects/structures/engicart.dm index a005e59c5a5..a77dc35b909 100644 --- a/code/game/objects/structures/engicart.dm +++ b/code/game/objects/structures/engicart.dm @@ -179,4 +179,4 @@ if(myredtoolbox) overlays += "cart_redtoolbox" if(myyellowtoolbox) - overlays += "cart_yellowtoolbox" \ No newline at end of file + overlays += "cart_yellowtoolbox" diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm index aeb5579981d..7626abb9a7d 100644 --- a/code/game/objects/structures/false_walls.dm +++ b/code/game/objects/structures/false_walls.dm @@ -1,355 +1,355 @@ -/* - * False Walls - */ - -// Minimum pressure difference to fail building falsewalls. -// Also affects admin alerts. -#define FALSEDOOR_MAX_PRESSURE_DIFF 25.0 - -/obj/structure/falsewall - name = "wall" - desc = "A huge chunk of metal used to seperate rooms." - anchored = TRUE - icon = 'icons/turf/walls/wall.dmi' - icon_state = "wall" - - var/mineral = /obj/item/stack/sheet/metal - var/mineral_amount = 2 - var/walltype = /turf/simulated/wall - var/girder_type = /obj/structure/girder/displaced - var/opening = FALSE - - density = TRUE - opacity = TRUE - max_integrity = 100 - - canSmoothWith = list( - /turf/simulated/wall, - /turf/simulated/wall/r_wall, - /obj/structure/falsewall, - /obj/structure/falsewall/brass, - /obj/structure/falsewall/reinforced, // WHY DO WE SMOOTH WITH FALSE R-WALLS WHEN WE DON'T SMOOTH WITH REAL R-WALLS. //because we do smooth with real r-walls now - /turf/simulated/wall/rust, - /turf/simulated/wall/r_wall/rust) - smooth = SMOOTH_TRUE - -/obj/structure/falsewall/New(loc) - ..() - air_update_turf(1) - -/obj/structure/falsewall/ratvar_act() - new /obj/structure/falsewall/brass(loc) - qdel(src) - -/obj/structure/falsewall/Destroy() - density = 0 - air_update_turf(1) - return ..() - -/obj/structure/falsewall/CanAtmosPass(turf/T) - return !density - -/obj/structure/falsewall/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - toggle(user) - -/obj/structure/falsewall/attack_hand(mob/user) - toggle(user) - -/obj/structure/falsewall/proc/toggle(mob/user) - if(opening) - return - - opening = 1 - if(density) - do_the_flick() - sleep(4) - density = 0 - set_opacity(0) - update_icon(0) - else - var/srcturf = get_turf(src) - for(var/mob/living/obstacle in srcturf) //Stop people from using this as a shield - opening = 0 - return - do_the_flick() - density = 1 - sleep(4) - set_opacity(1) - update_icon() - air_update_turf(1) - opening = 0 - -/obj/structure/falsewall/proc/do_the_flick() - if(density) - smooth = SMOOTH_FALSE - clear_smooth_overlays() - icon_state = "fwall_opening" - else - icon_state = "fwall_closing" - -/obj/structure/falsewall/update_icon() - if(density) - icon_state = initial(icon_state) - smooth = SMOOTH_TRUE - queue_smooth(src) - else - icon_state = "fwall_open" - -/obj/structure/falsewall/proc/ChangeToWall(delete = TRUE) - var/turf/T = get_turf(src) - T.ChangeTurf(walltype) - if(delete) - qdel(src) - return T - -/obj/structure/falsewall/attackby(obj/item/W, mob/user, params) - if(opening) - to_chat(user, "You must wait until the door has stopped moving.") - return - - if(density) - var/turf/T = get_turf(src) - if(T.density) - to_chat(user, "[src] is blocked!") - return - if(istype(W, /obj/item/screwdriver)) - if(!istype(T, /turf/simulated/floor)) - to_chat(user, "[src] bolts must be tightened on the floor!") - return - user.visible_message("[user] tightens some bolts on the wall.", "You tighten the bolts on the wall.") - ChangeToWall() - else - to_chat(user, "You can't reach, close it first!") - - if(istype(W, /obj/item/gun/energy/plasmacutter) || istype(W, /obj/item/pickaxe/drill/diamonddrill) || istype(W, /obj/item/pickaxe/drill/jackhammer) || istype(W, /obj/item/melee/energy/blade)) - dismantle(user, TRUE) - -/obj/structure/falsewall/welder_act(mob/user, obj/item/I) - if(!density) - return - . = TRUE - if(!I.use_tool(src, user, volume = I.tool_volume)) - return - dismantle(user, TRUE) - -/obj/structure/falsewall/proc/dismantle(mob/user, disassembled = TRUE) - user.visible_message("[user] dismantles the false wall.", "You dismantle the false wall.") - playsound(src, 'sound/items/welder.ogg', 100, TRUE) - deconstruct(disassembled) - -/obj/structure/falsewall/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(disassembled) - new girder_type(loc) - if(mineral_amount) - for(var/i in 1 to mineral_amount) - new mineral(loc) - qdel(src) - -/* - * False R-Walls - */ - -/obj/structure/falsewall/reinforced - name = "reinforced wall" - desc = "A huge chunk of reinforced metal used to seperate rooms." - icon = 'icons/turf/walls/reinforced_wall.dmi' - icon_state = "r_wall" - walltype = /turf/simulated/wall/r_wall - mineral = /obj/item/stack/sheet/plasteel - -/obj/structure/falsewall/reinforced/ChangeToWall(delete = 1) - var/turf/T = get_turf(src) - T.ChangeTurf(/turf/simulated/wall/r_wall) - if(delete) - qdel(src) - return T - -/* - * Uranium Falsewalls - */ - -/obj/structure/falsewall/uranium - name = "uranium wall" - desc = "A wall with uranium plating. This is probably a bad idea." - icon = 'icons/turf/walls/uranium_wall.dmi' - icon_state = "uranium" - mineral = /obj/item/stack/sheet/mineral/uranium - walltype = /turf/simulated/wall/mineral/uranium - var/active = null - var/last_event = 0 - canSmoothWith = list(/obj/structure/falsewall/uranium, /turf/simulated/wall/mineral/uranium) - -/obj/structure/falsewall/uranium/attackby(obj/item/W as obj, mob/user as mob, params) - radiate() - ..() - -/obj/structure/falsewall/uranium/attack_hand(mob/user as mob) - radiate() - ..() - -/obj/structure/falsewall/uranium/proc/radiate() - if(!active) - if(world.time > last_event+15) - active = 1 - for(var/mob/living/L in range(3,src)) - L.apply_effect(12,IRRADIATE,0) - for(var/turf/simulated/wall/mineral/uranium/T in range(3,src)) - T.radiate() - last_event = world.time - active = null - return - return -/* - * Other misc falsewall types - */ - -/obj/structure/falsewall/gold - name = "gold wall" - desc = "A wall with gold plating. Swag!" - icon = 'icons/turf/walls/gold_wall.dmi' - icon_state = "gold" - mineral = /obj/item/stack/sheet/mineral/gold - walltype = /turf/simulated/wall/mineral/gold - canSmoothWith = list(/obj/structure/falsewall/gold, /turf/simulated/wall/mineral/gold) - -/obj/structure/falsewall/silver - name = "silver wall" - desc = "A wall with silver plating. Shiny." - icon = 'icons/turf/walls/silver_wall.dmi' - icon_state = "silver" - mineral = /obj/item/stack/sheet/mineral/silver - walltype = /turf/simulated/wall/mineral/silver - canSmoothWith = list(/obj/structure/falsewall/silver, /turf/simulated/wall/mineral/silver) - -/obj/structure/falsewall/diamond - name = "diamond wall" - desc = "A wall with diamond plating. You monster." - icon = 'icons/turf/walls/diamond_wall.dmi' - icon_state = "diamond" - mineral = /obj/item/stack/sheet/mineral/diamond - walltype = /turf/simulated/wall/mineral/diamond - canSmoothWith = list(/obj/structure/falsewall/diamond, /turf/simulated/wall/mineral/diamond) - max_integrity = 800 - - -/obj/structure/falsewall/plasma - name = "plasma wall" - desc = "A wall with plasma plating. This is definately a bad idea." - icon = 'icons/turf/walls/plasma_wall.dmi' - icon_state = "plasma" - mineral = /obj/item/stack/sheet/mineral/plasma - walltype = /turf/simulated/wall/mineral/plasma - canSmoothWith = list(/obj/structure/falsewall/plasma, /turf/simulated/wall/mineral/plasma, /turf/simulated/wall/mineral/alien) - -/obj/structure/falsewall/plasma/attackby(obj/item/W, mob/user, params) - if(is_hot(W) > 300) - message_admins("Plasma falsewall ignited by [key_name_admin(user)] in [ADMIN_VERBOSEJMP(T)]") - log_game("Plasma falsewall ignited by [key_name(user)] in [AREACOORD(T)]") - investigate_log("was ignited by [key_name(user)]","atmos") - burnbabyburn() - else - return ..() - -/obj/structure/falsewall/plasma/proc/burnbabyburn(user) - playsound(src, 'sound/items/welder.ogg', 100, 1) - atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, 400) - new /obj/structure/girder/displaced(loc) - qdel(src) - -/obj/structure/falsewall/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > 300) - burnbabyburn() - -/obj/structure/falsewall/alien - name = "alien wall" - desc = "A strange-looking alien wall." - icon = 'icons/turf/walls/plasma_wall.dmi' - icon_state = "plasma" - mineral = /obj/item/stack/sheet/mineral/abductor - walltype = /turf/simulated/wall/mineral/abductor - canSmoothWith = list(/obj/structure/falsewall/alien, /turf/simulated/wall/mineral/alien) - - -/obj/structure/falsewall/bananium - name = "bananium wall" - desc = "A wall with bananium plating. Honk!" - icon = 'icons/turf/walls/bananium_wall.dmi' - icon_state = "bananium" - mineral = /obj/item/stack/sheet/mineral/bananium - walltype = /turf/simulated/wall/mineral/bananium - canSmoothWith = list(/obj/structure/falsewall/bananium, /turf/simulated/wall/mineral/bananium) - -/obj/structure/falsewall/sandstone - name = "sandstone wall" - desc = "A wall with sandstone plating." - icon_state = "sandstone" - mineral = /obj/item/stack/sheet/mineral/sandstone - walltype = /turf/simulated/wall/mineral/sandstone - canSmoothWith = list(/obj/structure/falsewall/sandstone, /turf/simulated/wall/mineral/sandstone) - -/obj/structure/falsewall/wood - name = "wooden wall" - desc = "A wall with wooden plating. Stiff." - icon = 'icons/turf/walls/wood_wall.dmi' - icon_state = "wood" - mineral = /obj/item/stack/sheet/wood - walltype = /turf/simulated/wall/mineral/wood - canSmoothWith = list(/obj/structure/falsewall/wood, /turf/simulated/wall/mineral/wood) - -/obj/structure/falsewall/iron - name = "rough metal wall" - desc = "A wall with rough metal plating." - icon = 'icons/turf/walls/iron_wall.dmi' - icon_state = "iron" - mineral = /obj/item/stack/rods - mineral_amount = 5 - walltype = /turf/simulated/wall/mineral/iron - canSmoothWith = list(/obj/structure/falsewall/iron, /turf/simulated/wall/mineral/iron) - -/obj/structure/falsewall/abductor - name = "alien wall" - desc = "A wall with alien alloy plating." - icon = 'icons/turf/walls/abductor_wall.dmi' - icon_state = "abductor" - mineral = /obj/item/stack/sheet/mineral/abductor - walltype = /turf/simulated/wall/mineral/abductor - canSmoothWith = list(/obj/structure/falsewall/abductor, /turf/simulated/wall/mineral/abductor) - -/obj/structure/falsewall/titanium - desc = "A light-weight titanium wall used in shuttles." - icon = 'icons/turf/walls/shuttle_wall.dmi' - icon_state = "shuttle" - mineral = /obj/item/stack/sheet/mineral/titanium - walltype = /turf/simulated/wall/mineral/titanium - smooth = SMOOTH_MORE - canSmoothWith = list(/turf/simulated/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/full/shuttle, /obj/structure/shuttle/engine/heater) - -/obj/structure/falsewall/plastitanium - desc = "An evil wall of plasma and titanium." - icon = 'icons/turf/walls/plastitanium_wall.dmi' - icon_state = "shuttle" - mineral = /obj/item/stack/sheet/mineral/plastitanium - walltype = /turf/simulated/wall/mineral/plastitanium - smooth = SMOOTH_MORE - canSmoothWith = list(/turf/simulated/wall/mineral/plastitanium, /turf/simulated/wall/mineral/plastitanium/nodiagonal, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/full/shuttle, /obj/structure/shuttle/engine/heater) - -/obj/structure/falsewall/brass - name = "clockwork wall" - desc = "A huge chunk of warm metal. The clanging of machinery emanates from within." - icon = 'icons/turf/walls/clockwork_wall.dmi' - icon_state = "clockwork_wall" - resistance_flags = FIRE_PROOF | ACID_PROOF - mineral_amount = 1 - canSmoothWith = list(/obj/effect/clockwork/overlay/wall, /obj/structure/falsewall/brass) - girder_type = /obj/structure/clockwork/wall_gear/displaced - walltype = /turf/simulated/wall/clockwork - mineral = /obj/item/stack/tile/brass - -/obj/structure/falsewall/brass/New(loc) - ..() - var/turf/T = get_turf(src) - new /obj/effect/temp_visual/ratvar/wall/false(T) - new /obj/effect/temp_visual/ratvar/beam/falsewall(T) +/* + * False Walls + */ + +// Minimum pressure difference to fail building falsewalls. +// Also affects admin alerts. +#define FALSEDOOR_MAX_PRESSURE_DIFF 25.0 + +/obj/structure/falsewall + name = "wall" + desc = "A huge chunk of metal used to seperate rooms." + anchored = TRUE + icon = 'icons/turf/walls/wall.dmi' + icon_state = "wall" + + var/mineral = /obj/item/stack/sheet/metal + var/mineral_amount = 2 + var/walltype = /turf/simulated/wall + var/girder_type = /obj/structure/girder/displaced + var/opening = FALSE + + density = TRUE + opacity = TRUE + max_integrity = 100 + + canSmoothWith = list( + /turf/simulated/wall, + /turf/simulated/wall/r_wall, + /obj/structure/falsewall, + /obj/structure/falsewall/brass, + /obj/structure/falsewall/reinforced, // WHY DO WE SMOOTH WITH FALSE R-WALLS WHEN WE DON'T SMOOTH WITH REAL R-WALLS. //because we do smooth with real r-walls now + /turf/simulated/wall/rust, + /turf/simulated/wall/r_wall/rust) + smooth = SMOOTH_TRUE + +/obj/structure/falsewall/New(loc) + ..() + air_update_turf(1) + +/obj/structure/falsewall/ratvar_act() + new /obj/structure/falsewall/brass(loc) + qdel(src) + +/obj/structure/falsewall/Destroy() + density = 0 + air_update_turf(1) + return ..() + +/obj/structure/falsewall/CanAtmosPass(turf/T) + return !density + +/obj/structure/falsewall/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + toggle(user) + +/obj/structure/falsewall/attack_hand(mob/user) + toggle(user) + +/obj/structure/falsewall/proc/toggle(mob/user) + if(opening) + return + + opening = 1 + if(density) + do_the_flick() + sleep(4) + density = 0 + set_opacity(0) + update_icon(0) + else + var/srcturf = get_turf(src) + for(var/mob/living/obstacle in srcturf) //Stop people from using this as a shield + opening = 0 + return + do_the_flick() + density = 1 + sleep(4) + set_opacity(1) + update_icon() + air_update_turf(1) + opening = 0 + +/obj/structure/falsewall/proc/do_the_flick() + if(density) + smooth = SMOOTH_FALSE + clear_smooth_overlays() + icon_state = "fwall_opening" + else + icon_state = "fwall_closing" + +/obj/structure/falsewall/update_icon() + if(density) + icon_state = initial(icon_state) + smooth = SMOOTH_TRUE + queue_smooth(src) + else + icon_state = "fwall_open" + +/obj/structure/falsewall/proc/ChangeToWall(delete = TRUE) + var/turf/T = get_turf(src) + T.ChangeTurf(walltype) + if(delete) + qdel(src) + return T + +/obj/structure/falsewall/attackby(obj/item/W, mob/user, params) + if(opening) + to_chat(user, "You must wait until the door has stopped moving.") + return + + if(density) + var/turf/T = get_turf(src) + if(T.density) + to_chat(user, "[src] is blocked!") + return + if(istype(W, /obj/item/screwdriver)) + if(!istype(T, /turf/simulated/floor)) + to_chat(user, "[src] bolts must be tightened on the floor!") + return + user.visible_message("[user] tightens some bolts on the wall.", "You tighten the bolts on the wall.") + ChangeToWall() + else + to_chat(user, "You can't reach, close it first!") + + if(istype(W, /obj/item/gun/energy/plasmacutter) || istype(W, /obj/item/pickaxe/drill/diamonddrill) || istype(W, /obj/item/pickaxe/drill/jackhammer) || istype(W, /obj/item/melee/energy/blade)) + dismantle(user, TRUE) + +/obj/structure/falsewall/welder_act(mob/user, obj/item/I) + if(!density) + return + . = TRUE + if(!I.use_tool(src, user, volume = I.tool_volume)) + return + dismantle(user, TRUE) + +/obj/structure/falsewall/proc/dismantle(mob/user, disassembled = TRUE) + user.visible_message("[user] dismantles the false wall.", "You dismantle the false wall.") + playsound(src, 'sound/items/welder.ogg', 100, TRUE) + deconstruct(disassembled) + +/obj/structure/falsewall/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(disassembled) + new girder_type(loc) + if(mineral_amount) + for(var/i in 1 to mineral_amount) + new mineral(loc) + qdel(src) + +/* + * False R-Walls + */ + +/obj/structure/falsewall/reinforced + name = "reinforced wall" + desc = "A huge chunk of reinforced metal used to seperate rooms." + icon = 'icons/turf/walls/reinforced_wall.dmi' + icon_state = "r_wall" + walltype = /turf/simulated/wall/r_wall + mineral = /obj/item/stack/sheet/plasteel + +/obj/structure/falsewall/reinforced/ChangeToWall(delete = 1) + var/turf/T = get_turf(src) + T.ChangeTurf(/turf/simulated/wall/r_wall) + if(delete) + qdel(src) + return T + +/* + * Uranium Falsewalls + */ + +/obj/structure/falsewall/uranium + name = "uranium wall" + desc = "A wall with uranium plating. This is probably a bad idea." + icon = 'icons/turf/walls/uranium_wall.dmi' + icon_state = "uranium" + mineral = /obj/item/stack/sheet/mineral/uranium + walltype = /turf/simulated/wall/mineral/uranium + var/active = null + var/last_event = 0 + canSmoothWith = list(/obj/structure/falsewall/uranium, /turf/simulated/wall/mineral/uranium) + +/obj/structure/falsewall/uranium/attackby(obj/item/W as obj, mob/user as mob, params) + radiate() + ..() + +/obj/structure/falsewall/uranium/attack_hand(mob/user as mob) + radiate() + ..() + +/obj/structure/falsewall/uranium/proc/radiate() + if(!active) + if(world.time > last_event+15) + active = 1 + for(var/mob/living/L in range(3,src)) + L.apply_effect(12,IRRADIATE,0) + for(var/turf/simulated/wall/mineral/uranium/T in range(3,src)) + T.radiate() + last_event = world.time + active = null + return + return +/* + * Other misc falsewall types + */ + +/obj/structure/falsewall/gold + name = "gold wall" + desc = "A wall with gold plating. Swag!" + icon = 'icons/turf/walls/gold_wall.dmi' + icon_state = "gold" + mineral = /obj/item/stack/sheet/mineral/gold + walltype = /turf/simulated/wall/mineral/gold + canSmoothWith = list(/obj/structure/falsewall/gold, /turf/simulated/wall/mineral/gold) + +/obj/structure/falsewall/silver + name = "silver wall" + desc = "A wall with silver plating. Shiny." + icon = 'icons/turf/walls/silver_wall.dmi' + icon_state = "silver" + mineral = /obj/item/stack/sheet/mineral/silver + walltype = /turf/simulated/wall/mineral/silver + canSmoothWith = list(/obj/structure/falsewall/silver, /turf/simulated/wall/mineral/silver) + +/obj/structure/falsewall/diamond + name = "diamond wall" + desc = "A wall with diamond plating. You monster." + icon = 'icons/turf/walls/diamond_wall.dmi' + icon_state = "diamond" + mineral = /obj/item/stack/sheet/mineral/diamond + walltype = /turf/simulated/wall/mineral/diamond + canSmoothWith = list(/obj/structure/falsewall/diamond, /turf/simulated/wall/mineral/diamond) + max_integrity = 800 + + +/obj/structure/falsewall/plasma + name = "plasma wall" + desc = "A wall with plasma plating. This is definately a bad idea." + icon = 'icons/turf/walls/plasma_wall.dmi' + icon_state = "plasma" + mineral = /obj/item/stack/sheet/mineral/plasma + walltype = /turf/simulated/wall/mineral/plasma + canSmoothWith = list(/obj/structure/falsewall/plasma, /turf/simulated/wall/mineral/plasma, /turf/simulated/wall/mineral/alien) + +/obj/structure/falsewall/plasma/attackby(obj/item/W, mob/user, params) + if(is_hot(W) > 300) + message_admins("Plasma falsewall ignited by [key_name_admin(user)] in [ADMIN_VERBOSEJMP(T)]") + log_game("Plasma falsewall ignited by [key_name(user)] in [AREACOORD(T)]") + investigate_log("was ignited by [key_name(user)]","atmos") + burnbabyburn() + else + return ..() + +/obj/structure/falsewall/plasma/proc/burnbabyburn(user) + playsound(src, 'sound/items/welder.ogg', 100, 1) + atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, 400) + new /obj/structure/girder/displaced(loc) + qdel(src) + +/obj/structure/falsewall/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > 300) + burnbabyburn() + +/obj/structure/falsewall/alien + name = "alien wall" + desc = "A strange-looking alien wall." + icon = 'icons/turf/walls/plasma_wall.dmi' + icon_state = "plasma" + mineral = /obj/item/stack/sheet/mineral/abductor + walltype = /turf/simulated/wall/mineral/abductor + canSmoothWith = list(/obj/structure/falsewall/alien, /turf/simulated/wall/mineral/alien) + + +/obj/structure/falsewall/bananium + name = "bananium wall" + desc = "A wall with bananium plating. Honk!" + icon = 'icons/turf/walls/bananium_wall.dmi' + icon_state = "bananium" + mineral = /obj/item/stack/sheet/mineral/bananium + walltype = /turf/simulated/wall/mineral/bananium + canSmoothWith = list(/obj/structure/falsewall/bananium, /turf/simulated/wall/mineral/bananium) + +/obj/structure/falsewall/sandstone + name = "sandstone wall" + desc = "A wall with sandstone plating." + icon_state = "sandstone" + mineral = /obj/item/stack/sheet/mineral/sandstone + walltype = /turf/simulated/wall/mineral/sandstone + canSmoothWith = list(/obj/structure/falsewall/sandstone, /turf/simulated/wall/mineral/sandstone) + +/obj/structure/falsewall/wood + name = "wooden wall" + desc = "A wall with wooden plating. Stiff." + icon = 'icons/turf/walls/wood_wall.dmi' + icon_state = "wood" + mineral = /obj/item/stack/sheet/wood + walltype = /turf/simulated/wall/mineral/wood + canSmoothWith = list(/obj/structure/falsewall/wood, /turf/simulated/wall/mineral/wood) + +/obj/structure/falsewall/iron + name = "rough metal wall" + desc = "A wall with rough metal plating." + icon = 'icons/turf/walls/iron_wall.dmi' + icon_state = "iron" + mineral = /obj/item/stack/rods + mineral_amount = 5 + walltype = /turf/simulated/wall/mineral/iron + canSmoothWith = list(/obj/structure/falsewall/iron, /turf/simulated/wall/mineral/iron) + +/obj/structure/falsewall/abductor + name = "alien wall" + desc = "A wall with alien alloy plating." + icon = 'icons/turf/walls/abductor_wall.dmi' + icon_state = "abductor" + mineral = /obj/item/stack/sheet/mineral/abductor + walltype = /turf/simulated/wall/mineral/abductor + canSmoothWith = list(/obj/structure/falsewall/abductor, /turf/simulated/wall/mineral/abductor) + +/obj/structure/falsewall/titanium + desc = "A light-weight titanium wall used in shuttles." + icon = 'icons/turf/walls/shuttle_wall.dmi' + icon_state = "shuttle" + mineral = /obj/item/stack/sheet/mineral/titanium + walltype = /turf/simulated/wall/mineral/titanium + smooth = SMOOTH_MORE + canSmoothWith = list(/turf/simulated/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/full/shuttle, /obj/structure/shuttle/engine/heater) + +/obj/structure/falsewall/plastitanium + desc = "An evil wall of plasma and titanium." + icon = 'icons/turf/walls/plastitanium_wall.dmi' + icon_state = "shuttle" + mineral = /obj/item/stack/sheet/mineral/plastitanium + walltype = /turf/simulated/wall/mineral/plastitanium + smooth = SMOOTH_MORE + canSmoothWith = list(/turf/simulated/wall/mineral/plastitanium, /turf/simulated/wall/mineral/plastitanium/nodiagonal, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/full/shuttle, /obj/structure/shuttle/engine/heater) + +/obj/structure/falsewall/brass + name = "clockwork wall" + desc = "A huge chunk of warm metal. The clanging of machinery emanates from within." + icon = 'icons/turf/walls/clockwork_wall.dmi' + icon_state = "clockwork_wall" + resistance_flags = FIRE_PROOF | ACID_PROOF + mineral_amount = 1 + canSmoothWith = list(/obj/effect/clockwork/overlay/wall, /obj/structure/falsewall/brass) + girder_type = /obj/structure/clockwork/wall_gear/displaced + walltype = /turf/simulated/wall/clockwork + mineral = /obj/item/stack/tile/brass + +/obj/structure/falsewall/brass/New(loc) + ..() + var/turf/T = get_turf(src) + new /obj/effect/temp_visual/ratvar/wall/false(T) + new /obj/effect/temp_visual/ratvar/beam/falsewall(T) diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm index 7830fd4f101..b836760a9df 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -1,346 +1,346 @@ -/obj/structure/flora - resistance_flags = FLAMMABLE - max_integrity = 150 - -//trees -/obj/structure/flora/tree - name = "tree" - anchored = 1 - density = 1 - pixel_x = -16 - layer = 9 - -/obj/structure/flora/tree/pine - name = "pine tree" - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "pine_1" - -/obj/structure/flora/tree/pine/New() - ..() - icon_state = "pine_[rand(1, 3)]" - -/obj/structure/flora/tree/pine/xmas - name = "xmas tree" - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "pine_c" - -/obj/structure/flora/tree/pine/xmas/New() - ..() - icon_state = "pine_c" - -/obj/structure/flora/tree/dead - icon = 'icons/obj/flora/deadtrees.dmi' - icon_state = "tree_1" - -/obj/structure/flora/tree/dead/New() - ..() - icon_state = "tree_[rand(1, 6)]" - -/obj/structure/flora/tree/palm - icon = 'icons/misc/beach2.dmi' - icon_state = "palm1" - -/obj/structure/flora/tree/palm/New() - ..() - icon_state = pick("palm1","palm2") - pixel_x = 0 - -//grass -/obj/structure/flora/grass - name = "grass" - icon = 'icons/obj/flora/snowflora.dmi' - anchored = 1 - -/obj/structure/flora/grass/brown - icon_state = "snowgrass1bb" - -/obj/structure/flora/grass/brown/New() - ..() - icon_state = "snowgrass[rand(1, 3)]bb" - - -/obj/structure/flora/grass/green - icon_state = "snowgrass1gb" - -/obj/structure/flora/grass/green/New() - ..() - icon_state = "snowgrass[rand(1, 3)]gb" - -/obj/structure/flora/grass/both - icon_state = "snowgrassall1" - -/obj/structure/flora/grass/both/New() - ..() - icon_state = "snowgrassall[rand(1, 3)]" - - -//bushes -/obj/structure/flora/bush - name = "bush" - icon = 'icons/obj/flora/snowflora.dmi' - icon_state = "snowbush1" - anchored = 1 - -/obj/structure/flora/bush/New() - ..() - icon_state = "snowbush[rand(1, 6)]" - -//newbushes - -/obj/structure/flora/ausbushes - name = "bush" - icon = 'icons/obj/flora/ausflora.dmi' - icon_state = "firstbush_1" - anchored = 1 - -/obj/structure/flora/ausbushes/New() - ..() - icon_state = "firstbush_[rand(1, 4)]" - -/obj/structure/flora/ausbushes/reedbush - icon_state = "reedbush_1" - -/obj/structure/flora/ausbushes/reedbush/New() - ..() - icon_state = "reedbush_[rand(1, 4)]" - -/obj/structure/flora/ausbushes/leafybush - icon_state = "leafybush_1" - -/obj/structure/flora/ausbushes/leafybush/New() - ..() - icon_state = "leafybush_[rand(1, 3)]" - -/obj/structure/flora/ausbushes/palebush - icon_state = "palebush_1" - -/obj/structure/flora/ausbushes/palebush/New() - ..() - icon_state = "palebush_[rand(1, 4)]" - -/obj/structure/flora/ausbushes/stalkybush - icon_state = "stalkybush_1" - -/obj/structure/flora/ausbushes/stalkybush/New() - ..() - icon_state = "stalkybush_[rand(1, 3)]" - -/obj/structure/flora/ausbushes/grassybush - icon_state = "grassybush_1" - -/obj/structure/flora/ausbushes/grassybush/New() - ..() - icon_state = "grassybush_[rand(1, 4)]" - -/obj/structure/flora/ausbushes/fernybush - icon_state = "fernybush_1" - -/obj/structure/flora/ausbushes/fernybush/New() - ..() - icon_state = "fernybush_[rand(1, 3)]" - -/obj/structure/flora/ausbushes/sunnybush - icon_state = "sunnybush_1" - -/obj/structure/flora/ausbushes/sunnybush/New() - ..() - icon_state = "sunnybush_[rand(1, 3)]" - -/obj/structure/flora/ausbushes/genericbush - icon_state = "genericbush_1" - -/obj/structure/flora/ausbushes/genericbush/New() - ..() - icon_state = "genericbush_[rand(1, 4)]" - -/obj/structure/flora/ausbushes/pointybush - icon_state = "pointybush_1" - -/obj/structure/flora/ausbushes/pointybush/New() - ..() - icon_state = "pointybush_[rand(1, 4)]" - -/obj/structure/flora/ausbushes/lavendergrass - icon_state = "lavendergrass_1" - -/obj/structure/flora/ausbushes/lavendergrass/New() - ..() - icon_state = "lavendergrass_[rand(1, 4)]" - -/obj/structure/flora/ausbushes/ywflowers - icon_state = "ywflowers_1" - -/obj/structure/flora/ausbushes/ywflowers/New() - ..() - icon_state = "ywflowers_[rand(1, 3)]" - -/obj/structure/flora/ausbushes/brflowers - icon_state = "brflowers_1" - -/obj/structure/flora/ausbushes/brflowers/New() - ..() - icon_state = "brflowers_[rand(1, 3)]" - -/obj/structure/flora/ausbushes/ppflowers - icon_state = "ppflowers_1" - -/obj/structure/flora/ausbushes/ppflowers/New() - ..() - icon_state = "ppflowers_[rand(1, 4)]" - -/obj/structure/flora/ausbushes/sparsegrass - icon_state = "sparsegrass_1" - -/obj/structure/flora/ausbushes/sparsegrass/New() - ..() - icon_state = "sparsegrass_[rand(1, 3)]" - -/obj/structure/flora/ausbushes/fullgrass - icon_state = "fullgrass_1" - -/obj/structure/flora/ausbushes/fullgrass/New() - ..() - icon_state = "fullgrass_[rand(1, 3)]" - - -/obj/item/twohanded/required/kirbyplants - name = "potted plant" - icon = 'icons/obj/flora/plants.dmi' - icon_state = "plant-1" - anchored = 0 - layer = 5 - w_class = WEIGHT_CLASS_HUGE - force = 10 - force_wielded = 10 - throwforce = 13 - throw_speed = 2 - throw_range = 4 - -/obj/item/twohanded/required/kirbyplants/New() - ..() - icon_state = "plant-[rand(1,35)]" - if(prob(1)) - icon_state = "plant-36" - -/obj/item/twohanded/required/kirbyplants/equipped(mob/living/user) - . = ..() - var/image/I = image(icon = 'icons/obj/flora/plants.dmi' , icon_state = src.icon_state, loc = user) - I.override = 1 - user.add_alt_appearance("sneaking_mission", I, GLOB.player_list) - -/obj/item/twohanded/required/kirbyplants/dropped(mob/living/user) - ..() - user.remove_alt_appearance("sneaking_mission") - -/obj/item/twohanded/required/kirbyplants/dead - name = "\improper RD's potted plant" - desc = "A gift from the botanical staff, presented after the RD's reassignment. There's a tag on it that says \"Y'all come back now, y'hear?\"\nIt doesn't look very healthy..." - icon_state = "plant-dead" - -//a rock is flora according to where the icon file is -//and now these defines -/obj/structure/flora/rock - name = "rock" - desc = "a rock" - icon_state = "rock1" - icon = 'icons/obj/flora/rocks.dmi' - resistance_flags = FIRE_PROOF - anchored = 1 - -/obj/structure/flora/rock/New() - ..() - icon_state = "rock[rand(1,5)]" - -/obj/structure/flora/rock/pile - name = "rocks" - desc = "some rocks" - icon_state = "rockpile1" - -/obj/structure/flora/rock/pile/New() - ..() - icon_state = "rockpile[rand(1,5)]" - -/obj/structure/flora/rock/icy - name = "icy rock" - color = "#cce9eb" - -/obj/structure/flora/rock/pile/icy - name = "icy rocks" - color = "#cce9eb" - -/obj/structure/flora/corn_stalk - name = "corn stalk" - icon = 'icons/obj/flora/plants.dmi' - icon_state = "cornstalk1" - anchored = 0 - layer = 5 - -/obj/structure/flora/corn_stalk/alt_1 - icon_state = "cornstalk2" - -/obj/structure/flora/corn_stalk/alt_2 - icon_state = "cornstalk3" - -/obj/structure/flora/straw_bail - name = "straw bail" - icon = 'icons/obj/flora/plants.dmi' - icon_state = "strawbail1" - density = 1 - climbable = 1 // you can climb all over them. - -/obj/structure/flora/straw_bail/alt_1 - icon_state = "strawbail2" - -/obj/structure/flora/straw_bail/alt_2 - icon_state = "strawbail3" - -/obj/structure/bush - name = "foliage" - desc = "Pretty thick scrub, it'll take something sharp and a lot of determination to clear away." - icon = 'icons/obj/flora/plants.dmi' - icon_state = "bush1" - density = 1 - anchored = 1 - layer = 3.2 - var/indestructable = 0 - var/stump = 0 - -/obj/structure/bush/New() - if(prob(20)) - opacity = 1 - -/* -/obj/structure/bush/Bumped(M as mob) - if(istype(M, /mob/living/simple_animal)) - var/mob/living/simple_animal/A = M - A.loc = get_turf(src) - else if(istype(M, /mob/living/carbon/monkey)) - var/mob/living/carbon/monkey/A = M - A.loc = get_turf(src) -*/ - -/obj/structure/bush/attackby(var/obj/I as obj, var/mob/user as mob, params) - //hatchets can clear away undergrowth - if(istype(I, /obj/item/hatchet) && !stump) - if(indestructable) - //this bush marks the edge of the map, you can't destroy it - to_chat(user, "You flail away at the undergrowth, but it's too thick here.") - else - user.visible_message("[user] begins clearing away [src].
        ","You begin clearing away [src].
        ") - spawn(rand(15,30)) - if(get_dist(user,src) < 2) - to_chat(user, "You clear away [src].") - var/obj/item/stack/sheet/wood/W = new(src.loc) - W.amount = rand(3,15) - if(prob(50)) - icon_state = "stump[rand(1,2)]" - name = "cleared foliage" - desc = "There used to be dense undergrowth here." - density = 0 - stump = 1 - pixel_x = rand(-6,6) - pixel_y = rand(-6,6) - else - qdel(src) - else - return ..() +/obj/structure/flora + resistance_flags = FLAMMABLE + max_integrity = 150 + +//trees +/obj/structure/flora/tree + name = "tree" + anchored = 1 + density = 1 + pixel_x = -16 + layer = 9 + +/obj/structure/flora/tree/pine + name = "pine tree" + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "pine_1" + +/obj/structure/flora/tree/pine/New() + ..() + icon_state = "pine_[rand(1, 3)]" + +/obj/structure/flora/tree/pine/xmas + name = "xmas tree" + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "pine_c" + +/obj/structure/flora/tree/pine/xmas/New() + ..() + icon_state = "pine_c" + +/obj/structure/flora/tree/dead + icon = 'icons/obj/flora/deadtrees.dmi' + icon_state = "tree_1" + +/obj/structure/flora/tree/dead/New() + ..() + icon_state = "tree_[rand(1, 6)]" + +/obj/structure/flora/tree/palm + icon = 'icons/misc/beach2.dmi' + icon_state = "palm1" + +/obj/structure/flora/tree/palm/New() + ..() + icon_state = pick("palm1","palm2") + pixel_x = 0 + +//grass +/obj/structure/flora/grass + name = "grass" + icon = 'icons/obj/flora/snowflora.dmi' + anchored = 1 + +/obj/structure/flora/grass/brown + icon_state = "snowgrass1bb" + +/obj/structure/flora/grass/brown/New() + ..() + icon_state = "snowgrass[rand(1, 3)]bb" + + +/obj/structure/flora/grass/green + icon_state = "snowgrass1gb" + +/obj/structure/flora/grass/green/New() + ..() + icon_state = "snowgrass[rand(1, 3)]gb" + +/obj/structure/flora/grass/both + icon_state = "snowgrassall1" + +/obj/structure/flora/grass/both/New() + ..() + icon_state = "snowgrassall[rand(1, 3)]" + + +//bushes +/obj/structure/flora/bush + name = "bush" + icon = 'icons/obj/flora/snowflora.dmi' + icon_state = "snowbush1" + anchored = 1 + +/obj/structure/flora/bush/New() + ..() + icon_state = "snowbush[rand(1, 6)]" + +//newbushes + +/obj/structure/flora/ausbushes + name = "bush" + icon = 'icons/obj/flora/ausflora.dmi' + icon_state = "firstbush_1" + anchored = 1 + +/obj/structure/flora/ausbushes/New() + ..() + icon_state = "firstbush_[rand(1, 4)]" + +/obj/structure/flora/ausbushes/reedbush + icon_state = "reedbush_1" + +/obj/structure/flora/ausbushes/reedbush/New() + ..() + icon_state = "reedbush_[rand(1, 4)]" + +/obj/structure/flora/ausbushes/leafybush + icon_state = "leafybush_1" + +/obj/structure/flora/ausbushes/leafybush/New() + ..() + icon_state = "leafybush_[rand(1, 3)]" + +/obj/structure/flora/ausbushes/palebush + icon_state = "palebush_1" + +/obj/structure/flora/ausbushes/palebush/New() + ..() + icon_state = "palebush_[rand(1, 4)]" + +/obj/structure/flora/ausbushes/stalkybush + icon_state = "stalkybush_1" + +/obj/structure/flora/ausbushes/stalkybush/New() + ..() + icon_state = "stalkybush_[rand(1, 3)]" + +/obj/structure/flora/ausbushes/grassybush + icon_state = "grassybush_1" + +/obj/structure/flora/ausbushes/grassybush/New() + ..() + icon_state = "grassybush_[rand(1, 4)]" + +/obj/structure/flora/ausbushes/fernybush + icon_state = "fernybush_1" + +/obj/structure/flora/ausbushes/fernybush/New() + ..() + icon_state = "fernybush_[rand(1, 3)]" + +/obj/structure/flora/ausbushes/sunnybush + icon_state = "sunnybush_1" + +/obj/structure/flora/ausbushes/sunnybush/New() + ..() + icon_state = "sunnybush_[rand(1, 3)]" + +/obj/structure/flora/ausbushes/genericbush + icon_state = "genericbush_1" + +/obj/structure/flora/ausbushes/genericbush/New() + ..() + icon_state = "genericbush_[rand(1, 4)]" + +/obj/structure/flora/ausbushes/pointybush + icon_state = "pointybush_1" + +/obj/structure/flora/ausbushes/pointybush/New() + ..() + icon_state = "pointybush_[rand(1, 4)]" + +/obj/structure/flora/ausbushes/lavendergrass + icon_state = "lavendergrass_1" + +/obj/structure/flora/ausbushes/lavendergrass/New() + ..() + icon_state = "lavendergrass_[rand(1, 4)]" + +/obj/structure/flora/ausbushes/ywflowers + icon_state = "ywflowers_1" + +/obj/structure/flora/ausbushes/ywflowers/New() + ..() + icon_state = "ywflowers_[rand(1, 3)]" + +/obj/structure/flora/ausbushes/brflowers + icon_state = "brflowers_1" + +/obj/structure/flora/ausbushes/brflowers/New() + ..() + icon_state = "brflowers_[rand(1, 3)]" + +/obj/structure/flora/ausbushes/ppflowers + icon_state = "ppflowers_1" + +/obj/structure/flora/ausbushes/ppflowers/New() + ..() + icon_state = "ppflowers_[rand(1, 4)]" + +/obj/structure/flora/ausbushes/sparsegrass + icon_state = "sparsegrass_1" + +/obj/structure/flora/ausbushes/sparsegrass/New() + ..() + icon_state = "sparsegrass_[rand(1, 3)]" + +/obj/structure/flora/ausbushes/fullgrass + icon_state = "fullgrass_1" + +/obj/structure/flora/ausbushes/fullgrass/New() + ..() + icon_state = "fullgrass_[rand(1, 3)]" + + +/obj/item/twohanded/required/kirbyplants + name = "potted plant" + icon = 'icons/obj/flora/plants.dmi' + icon_state = "plant-1" + anchored = 0 + layer = 5 + w_class = WEIGHT_CLASS_HUGE + force = 10 + force_wielded = 10 + throwforce = 13 + throw_speed = 2 + throw_range = 4 + +/obj/item/twohanded/required/kirbyplants/New() + ..() + icon_state = "plant-[rand(1,35)]" + if(prob(1)) + icon_state = "plant-36" + +/obj/item/twohanded/required/kirbyplants/equipped(mob/living/user) + . = ..() + var/image/I = image(icon = 'icons/obj/flora/plants.dmi' , icon_state = src.icon_state, loc = user) + I.override = 1 + user.add_alt_appearance("sneaking_mission", I, GLOB.player_list) + +/obj/item/twohanded/required/kirbyplants/dropped(mob/living/user) + ..() + user.remove_alt_appearance("sneaking_mission") + +/obj/item/twohanded/required/kirbyplants/dead + name = "\improper RD's potted plant" + desc = "A gift from the botanical staff, presented after the RD's reassignment. There's a tag on it that says \"Y'all come back now, y'hear?\"\nIt doesn't look very healthy..." + icon_state = "plant-dead" + +//a rock is flora according to where the icon file is +//and now these defines +/obj/structure/flora/rock + name = "rock" + desc = "a rock" + icon_state = "rock1" + icon = 'icons/obj/flora/rocks.dmi' + resistance_flags = FIRE_PROOF + anchored = 1 + +/obj/structure/flora/rock/New() + ..() + icon_state = "rock[rand(1,5)]" + +/obj/structure/flora/rock/pile + name = "rocks" + desc = "some rocks" + icon_state = "rockpile1" + +/obj/structure/flora/rock/pile/New() + ..() + icon_state = "rockpile[rand(1,5)]" + +/obj/structure/flora/rock/icy + name = "icy rock" + color = "#cce9eb" + +/obj/structure/flora/rock/pile/icy + name = "icy rocks" + color = "#cce9eb" + +/obj/structure/flora/corn_stalk + name = "corn stalk" + icon = 'icons/obj/flora/plants.dmi' + icon_state = "cornstalk1" + anchored = 0 + layer = 5 + +/obj/structure/flora/corn_stalk/alt_1 + icon_state = "cornstalk2" + +/obj/structure/flora/corn_stalk/alt_2 + icon_state = "cornstalk3" + +/obj/structure/flora/straw_bail + name = "straw bail" + icon = 'icons/obj/flora/plants.dmi' + icon_state = "strawbail1" + density = 1 + climbable = 1 // you can climb all over them. + +/obj/structure/flora/straw_bail/alt_1 + icon_state = "strawbail2" + +/obj/structure/flora/straw_bail/alt_2 + icon_state = "strawbail3" + +/obj/structure/bush + name = "foliage" + desc = "Pretty thick scrub, it'll take something sharp and a lot of determination to clear away." + icon = 'icons/obj/flora/plants.dmi' + icon_state = "bush1" + density = 1 + anchored = 1 + layer = 3.2 + var/indestructable = 0 + var/stump = 0 + +/obj/structure/bush/New() + if(prob(20)) + opacity = 1 + +/* +/obj/structure/bush/Bumped(M as mob) + if(istype(M, /mob/living/simple_animal)) + var/mob/living/simple_animal/A = M + A.loc = get_turf(src) + else if(istype(M, /mob/living/carbon/monkey)) + var/mob/living/carbon/monkey/A = M + A.loc = get_turf(src) +*/ + +/obj/structure/bush/attackby(var/obj/I as obj, var/mob/user as mob, params) + //hatchets can clear away undergrowth + if(istype(I, /obj/item/hatchet) && !stump) + if(indestructable) + //this bush marks the edge of the map, you can't destroy it + to_chat(user, "You flail away at the undergrowth, but it's too thick here.") + else + user.visible_message("[user] begins clearing away [src].","You begin clearing away [src].
        ") + spawn(rand(15,30)) + if(get_dist(user,src) < 2) + to_chat(user, "You clear away [src].") + var/obj/item/stack/sheet/wood/W = new(src.loc) + W.amount = rand(3,15) + if(prob(50)) + icon_state = "stump[rand(1,2)]" + name = "cleared foliage" + desc = "There used to be dense undergrowth here." + density = 0 + stump = 1 + pixel_x = rand(-6,6) + pixel_y = rand(-6,6) + else + qdel(src) + else + return ..() diff --git a/code/game/objects/structures/fluff.dm b/code/game/objects/structures/fluff.dm index c81b350f745..4f09b36fe81 100644 --- a/code/game/objects/structures/fluff.dm +++ b/code/game/objects/structures/fluff.dm @@ -78,4 +78,4 @@ /obj/structure/fluff/divine/conduit name = "conduit" desc = "It allows a deity to extend their reach. Their powers are just as potent near a conduit as a nexus." - icon_state = "conduit" \ No newline at end of file + icon_state = "conduit" diff --git a/code/game/objects/structures/foodcart.dm b/code/game/objects/structures/foodcart.dm index da88c16f074..198dbf5f195 100644 --- a/code/game/objects/structures/foodcart.dm +++ b/code/game/objects/structures/foodcart.dm @@ -176,4 +176,4 @@ /obj/structure/foodcart/deconstruct(disassembled = TRUE) if(!(flags & NODECONSTRUCT)) new /obj/item/stack/sheet/metal(loc, 4) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 6b65d3028d9..dcb74369d72 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -1,471 +1,471 @@ -/obj/structure/girder - name = "girder" - icon_state = "girder" - anchored = 1 - density = 1 - layer = BELOW_OBJ_LAYER - var/state = GIRDER_NORMAL - var/girderpasschance = 20 // percentage chance that a projectile passes through the girder. - max_integrity = 200 - var/can_displace = TRUE //If the girder can be moved around by crowbarring it - var/metalUsed = 2 //used to determine amount returned in deconstruction - var/metal_type = /obj/item/stack/sheet/metal - -/obj/structure/girder/examine(mob/user) - . = ..() - switch(state) - if(GIRDER_REINF) - . += "The support struts are screwed in place." - if(GIRDER_REINF_STRUTS) - . += "The support struts are unscrewed and the inner grille is intact." - if(GIRDER_NORMAL) - if(can_displace) - . += "The bolts are lodged in place." - if(GIRDER_DISPLACED) - . += "The bolts are loosened, but the screws are holding [src] together." - if(GIRDER_DISASSEMBLED) - . += "[src] is disassembled! You probably shouldn't be able to see this examine message." - -/obj/structure/girder/proc/refundMetal(metalAmount) //refunds metal used in construction when deconstructed - for(var/i=0;i < metalAmount;i++) - new metal_type(get_turf(src)) - -/obj/structure/girder/temperature_expose(datum/gas_mixture/air, exposed_temperature) - ..() - var/temp_check = exposed_temperature - if(temp_check >= GIRDER_MELTING_TEMP) - take_damage(10) - -/obj/structure/girder/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if(istype(W, /obj/item/gun/energy/plasmacutter)) - to_chat(user, "You start slicing apart the girder...") - if(do_after(user, 40 * W.toolspeed, target = src)) - if(!src) - return - playsound(loc, W.usesound, 100, 1) - to_chat(user, "You slice apart the girder.") - refundMetal(metalUsed) - qdel(src) - - else if(istype(W, /obj/item/pickaxe/drill/diamonddrill)) - to_chat(user, "You drill through the girder!") - refundMetal(metalUsed) - qdel(src) - - else if(istype(W, /obj/item/pickaxe/drill/jackhammer)) - playsound(loc, W.usesound, 100, 1) - to_chat(user, "You disintegrate the girder!") - refundMetal(metalUsed) - qdel(src) - - else if(istype(W, /obj/item/stack)) - if(iswallturf(loc)) - to_chat(user, "There is already a wall present!") - return - if(!isfloorturf(loc)) - to_chat(user, "A floor must be present to build a false wall!") - return - if (locate(/obj/structure/falsewall) in loc.contents) - to_chat(user, "There is already a false wall present!") - return - if(istype(W, /obj/item/stack/sheet/runed_metal)) - to_chat(user, "You can't seem to make the metal bend..") - return - - if(istype(W,/obj/item/stack/rods)) - var/obj/item/stack/rods/S = W - if(state == GIRDER_DISPLACED) - if(S.get_amount() < 2) - to_chat(user, "You need at least two rods to create a false wall!") - return - to_chat(user, "You start building a reinforced false wall...") - if(do_after(user, 20, target = src)) - if(!loc || !S || S.get_amount() < 2) - return - S.use(2) - to_chat(user, "You create a false wall. Push on it to open or close the passage.") - var/obj/structure/falsewall/iron/FW = new (loc) - transfer_fingerprints_to(FW) - qdel(src) - else - if(S.get_amount() < 5) - to_chat(user, "You need at least five rods to add plating!") - return - to_chat(user, "You start adding plating...") - if (do_after(user, 40, target = src)) - if(!loc || !S || S.get_amount() < 5) - return - S.use(5) - to_chat(user, "You add the plating.") - var/turf/T = get_turf(src) - T.ChangeTurf(/turf/simulated/wall/mineral/iron) - transfer_fingerprints_to(T) - qdel(src) - return - - if(!istype(W,/obj/item/stack/sheet)) - return - - var/obj/item/stack/sheet/S = W - if(!S.wall_allowed) - to_chat(user, "You don't think that is good material for a wall!") - return - - if(istype(S, /obj/item/stack/sheet/wood)) - if(state == GIRDER_DISPLACED) - if(S.get_amount() < 2) - to_chat(user, "You need two planks of wood to create a false wall!") - return - to_chat(user, "You start building a false wall...") - if(do_after(user, 20, target = src)) - if(!loc || !S || S.get_amount() < 2) - return - S.use(2) - to_chat(user, "You create a false wall. Push on it to open or close the passage.") - var/obj/structure/falsewall/wood/falsewood = new(loc) - transfer_fingerprints_to(falsewood) - qdel(src) - else - if(S.get_amount() < 2) - to_chat(user, "You need two planks of wood to finish a wall!") - return - to_chat(user, "You start adding plating...") - if(do_after(user, 40 * W.toolspeed, target = src)) - if(!src || !S || S.get_amount() < 2) - return - S.use(2) - to_chat(user, "You add the plating.") - var/turf/Tsrc = get_turf(src) - Tsrc.ChangeTurf(/turf/simulated/wall/mineral/wood) - for(var/turf/simulated/wall/mineral/wood/X in Tsrc.loc) - if(X) - X.add_hiddenprint(usr) - qdel(src) - return - - else if(istype(S, /obj/item/stack/sheet/metal)) - if(state == GIRDER_DISPLACED) - if(S.get_amount() < 2) - to_chat(user, "You need two sheets of metal to create a false wall!") - return - to_chat(user, "You start building a false wall...") - if(do_after(user, 20, target = src)) - if(!loc || !S || S.get_amount() < 2) - return - S.use(2) - to_chat(user, "You create a false wall. Push on it to open or close the passage.") - var/obj/structure/falsewall/F = new(loc) - transfer_fingerprints_to(F) - qdel(src) - else - if(S.get_amount() < 2) - to_chat(user, "You need two sheets of metal to finish a wall!") - return - to_chat(user, "You start adding plating...") - if(do_after(user, 40 * W.toolspeed, target = src)) - if(!src || !S || S.get_amount() < 2) - return - S.use(2) - to_chat(user, "You add the plating.") - var/turf/Tsrc = get_turf(src) - Tsrc.ChangeTurf(/turf/simulated/wall) - for(var/turf/simulated/wall/X in Tsrc.loc) - if(X) - X.add_hiddenprint(usr) - qdel(src) - return - - if(istype(S, /obj/item/stack/sheet/plasteel)) - if(state == GIRDER_DISPLACED) - if(S.get_amount() < 2) - to_chat(user, "You need at least two sheets to create a false wall!") - return - to_chat(user, "You start building a reinforced false wall...") - if(do_after(user, 20, target = src)) - if(!loc || !S || S.get_amount() < 2) - return - S.use(2) - to_chat(user, "You create a reinforced false wall. Push on it to open or close the passage.") - var/obj/structure/falsewall/reinforced/FW = new (loc) - transfer_fingerprints_to(FW) - qdel(src) - else - if(state == GIRDER_REINF) - if(S.get_amount() < 1) - return - to_chat(user, "You start finalizing the reinforced wall...") - if(do_after(user, 50, target = src)) - if(!src || !S || S.get_amount() < 1) - return - S.use(1) - to_chat(user, "You fully reinforce the wall.") - var/turf/Tsrc = get_turf(src) - Tsrc.ChangeTurf(/turf/simulated/wall/r_wall) - for(var/turf/simulated/wall/r_wall/X in Tsrc.loc) - if(X) - X.add_hiddenprint(usr) - qdel(src) - return - else - if(S.get_amount() < 1) - return - to_chat(user, "You start reinforcing the girder...") - if(do_after(user,60, target = src)) - if(!src || !S || S.get_amount() < 1) - return - S.use(1) - to_chat(user, "You reinforce the girder.") - var/obj/structure/girder/reinforced/R = new (loc) - transfer_fingerprints_to(R) - qdel(src) - return - - if(S.sheettype) - var/M = S.sheettype - if(state == GIRDER_DISPLACED) - if(S.get_amount() < 2) - to_chat(user, "You need at least two sheets to create a false wall!") - return - if(do_after(user, 20, target = src)) - if(!loc || !S || S.get_amount() < 2) - return - S.use(2) - to_chat(user, "You create a false wall. Push on it to open or close the passage.") - var/F = text2path("/obj/structure/falsewall/[M]") - var/obj/structure/FW = new F (loc) - transfer_fingerprints_to(FW) - qdel(src) - else - if(S.get_amount() < 2) - to_chat(user, "You need at least two sheets to add plating!") - return - to_chat(user, "You start adding plating...") - if(do_after(user,40, target = src)) - if(!src || !S || S.get_amount() < 2) - return - S.use(2) - to_chat(user, "You add the plating.") - var/turf/Tsrc = get_turf(src) - Tsrc.ChangeTurf(text2path("/turf/simulated/wall/mineral/[M]")) - for(var/turf/simulated/wall/mineral/X in Tsrc.loc) - if(X) - X.add_hiddenprint(usr) - qdel(src) - return - - add_hiddenprint(user) - - else if(istype(W, /obj/item/pipe)) - var/obj/item/pipe/P = W - if(P.pipe_type in list(0, 1, 5)) //simple pipes, simple bends, and simple manifolds. - if(!user.drop_item()) - return - P.loc = src.loc - to_chat(user, "You fit the pipe into \the [src].") - else - return ..() - -/obj/structure/girder/crowbar_act(mob/user, obj/item/I) - if(!can_displace || state != GIRDER_NORMAL) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - to_chat(user, "You start dislodging the girder...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_NORMAL) - return - to_chat(user, "You dislodge the girder.") - var/obj/structure/girder/displaced/D = new (loc) - transfer_fingerprints_to(D) - qdel(src) - -/obj/structure/girder/screwdriver_act(mob/user, obj/item/I) - if(state != GIRDER_DISPLACED && state != GIRDER_REINF && state != GIRDER_REINF_STRUTS) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - switch(state) - if(GIRDER_DISPLACED) - TOOL_ATTEMPT_DISMANTLE_MESSAGE - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_DISPLACED) - return - state = GIRDER_DISASSEMBLED - TOOL_DISMANTLE_SUCCESS_MESSAGE - var/obj/item/stack/sheet/metal/M = new(loc, 2) - M.add_fingerprint(user) - qdel(src) - if(GIRDER_REINF) - to_chat(user, "You start unsecuring support struts...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_REINF) - return - to_chat(user, "You unsecure the support struts.") - state = GIRDER_REINF_STRUTS - if(GIRDER_REINF_STRUTS) - to_chat(user, "You start securing support struts...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_REINF_STRUTS) - return - to_chat(user, "You secure the support struts.") - state = GIRDER_REINF - -/obj/structure/girder/wirecutter_act(mob/user, obj/item/I) - if(state != GIRDER_REINF_STRUTS) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - to_chat(user, "You start removing the inner grille...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_REINF_STRUTS) - return - to_chat(user, "You remove the inner grille.") - new /obj/item/stack/sheet/plasteel(get_turf(src)) - var/obj/structure/girder/G = new (loc) - transfer_fingerprints_to(G) - qdel(src) - -/obj/structure/girder/wrench_act(mob/user, obj/item/I) - if(state != GIRDER_NORMAL && state != GIRDER_DISPLACED) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(state == GIRDER_NORMAL) - TOOL_ATTEMPT_DISMANTLE_MESSAGE - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_NORMAL) - return - state = GIRDER_DISASSEMBLED - TOOL_DISMANTLE_SUCCESS_MESSAGE - var/obj/item/stack/sheet/metal/M = new(loc, 2) - M.add_fingerprint(user) - qdel(src) - else - if(!isfloorturf(loc)) - to_chat(user, "A floor must be present to secure the girder!") - return - to_chat(user, "You start securing the girder...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_DISPLACED) - return - to_chat(user, "You secure the girder.") - var/obj/structure/girder/G = new(loc) - transfer_fingerprints_to(G) - qdel(src) - -/obj/structure/girder/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_SLICING_MESSAGE - if(I.use_tool(src, user, 40, volume = I.tool_volume)) - WELDER_SLICING_SUCCESS_MESSAGE - refundMetal(metalUsed) - qdel(src) - -/obj/structure/girder/CanPass(atom/movable/mover, turf/target, height=0) - if(height==0) - return 1 - if(istype(mover) && mover.checkpass(PASSGRILLE)) - return prob(girderpasschance) - else - if(istype(mover, /obj/item/projectile)) - return prob(girderpasschance) - else - return 0 - -/obj/structure/girder/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(caller)) - var/atom/movable/mover = caller - . = . || mover.checkpass(PASSGRILLE) - -/obj/structure/girder/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - var/remains = pick(/obj/item/stack/rods, /obj/item/stack/sheet/metal) - new remains(loc) - qdel(src) - -/obj/structure/girder/narsie_act() - if(prob(25)) - new /obj/structure/girder/cult(loc) - qdel(src) - -/obj/structure/girder/displaced - name = "displaced girder" - icon_state = "displaced" - anchored = 0 - state = GIRDER_DISPLACED - girderpasschance = 25 - max_integrity = 120 - -/obj/structure/girder/reinforced - name = "reinforced girder" - icon_state = "reinforced" - state = GIRDER_REINF - girderpasschance = 0 - max_integrity = 350 - -/obj/structure/girder/cult - name = "runed girder" - desc = "Framework made of a strange and shockingly cold metal. It doesn't seem to have any bolts." - icon = 'icons/obj/cult.dmi' - icon_state = "cultgirder" - can_displace = FALSE - metalUsed = 1 - metal_type = /obj/item/stack/sheet/runed_metal - -/obj/structure/girder/cult/New() - . = ..() - icon_state = SSticker.cultdat?.cult_girder_icon_state - -/obj/structure/girder/cult/refundMetal(metalAmount) - for(var/i=0;i < metalAmount;i++) - new /obj/item/stack/sheet/runed_metal(get_turf(src)) - -/obj/structure/girder/cult/attackby(obj/item/W, mob/user, params) - add_fingerprint(user) - if(istype(W, /obj/item/tome) && iscultist(user)) //Cultists can demolish cult girders instantly with their tomes - user.visible_message("[user] strikes [src] with [W]!", "You demolish [src].") - refundMetal(metalUsed) - qdel(src) - else if(istype(W, /obj/item/gun/energy/plasmacutter)) - to_chat(user, "You start slicing apart the girder...") - if(do_after(user, 40* W.toolspeed, target = src)) - playsound(loc, W.usesound, 100, 1) - to_chat(user, "You slice apart the girder.") - var/obj/item/stack/sheet/runed_metal/R = new(get_turf(src)) - R.amount = 1 - transfer_fingerprints_to(R) - qdel(src) - else if(istype(W, /obj/item/pickaxe/drill/jackhammer)) - var/obj/item/pickaxe/drill/jackhammer/D = W - to_chat(user, "Your jackhammer smashes through the girder!") - var/obj/item/stack/sheet/runed_metal/R = new(get_turf(src)) - R.amount = 1 - transfer_fingerprints_to(R) - D.playDigSound() - qdel(src) - - else if(istype(W, /obj/item/stack/sheet/runed_metal)) - var/obj/item/stack/sheet/runed_metal/R = W - if(R.get_amount() < 1) - to_chat(user, "You need at least one sheet of runed metal to construct a runed wall!") - return 0 - user.visible_message("[user] begins laying runed metal on [src]...", "You begin constructing a runed wall...") - if(do_after(user, 50, target = src)) - if(R.get_amount() < 1 || !R) - return - user.visible_message("[user] plates [src] with runed metal.", "You construct a runed wall.") - R.use(1) - var/turf/T = get_turf(src) - T.ChangeTurf(/turf/simulated/wall/cult) - qdel(src) - else - return ..() - -/obj/structure/girder/cult/narsie_act() - return - -/obj/structure/girder/cult/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/sheet/runed_metal(drop_location(), 1) - qdel(src) +/obj/structure/girder + name = "girder" + icon_state = "girder" + anchored = 1 + density = 1 + layer = BELOW_OBJ_LAYER + var/state = GIRDER_NORMAL + var/girderpasschance = 20 // percentage chance that a projectile passes through the girder. + max_integrity = 200 + var/can_displace = TRUE //If the girder can be moved around by crowbarring it + var/metalUsed = 2 //used to determine amount returned in deconstruction + var/metal_type = /obj/item/stack/sheet/metal + +/obj/structure/girder/examine(mob/user) + . = ..() + switch(state) + if(GIRDER_REINF) + . += "The support struts are screwed in place." + if(GIRDER_REINF_STRUTS) + . += "The support struts are unscrewed and the inner grille is intact." + if(GIRDER_NORMAL) + if(can_displace) + . += "The bolts are lodged in place." + if(GIRDER_DISPLACED) + . += "The bolts are loosened, but the screws are holding [src] together." + if(GIRDER_DISASSEMBLED) + . += "[src] is disassembled! You probably shouldn't be able to see this examine message." + +/obj/structure/girder/proc/refundMetal(metalAmount) //refunds metal used in construction when deconstructed + for(var/i=0;i < metalAmount;i++) + new metal_type(get_turf(src)) + +/obj/structure/girder/temperature_expose(datum/gas_mixture/air, exposed_temperature) + ..() + var/temp_check = exposed_temperature + if(temp_check >= GIRDER_MELTING_TEMP) + take_damage(10) + +/obj/structure/girder/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(istype(W, /obj/item/gun/energy/plasmacutter)) + to_chat(user, "You start slicing apart the girder...") + if(do_after(user, 40 * W.toolspeed, target = src)) + if(!src) + return + playsound(loc, W.usesound, 100, 1) + to_chat(user, "You slice apart the girder.") + refundMetal(metalUsed) + qdel(src) + + else if(istype(W, /obj/item/pickaxe/drill/diamonddrill)) + to_chat(user, "You drill through the girder!") + refundMetal(metalUsed) + qdel(src) + + else if(istype(W, /obj/item/pickaxe/drill/jackhammer)) + playsound(loc, W.usesound, 100, 1) + to_chat(user, "You disintegrate the girder!") + refundMetal(metalUsed) + qdel(src) + + else if(istype(W, /obj/item/stack)) + if(iswallturf(loc)) + to_chat(user, "There is already a wall present!") + return + if(!isfloorturf(loc)) + to_chat(user, "A floor must be present to build a false wall!") + return + if (locate(/obj/structure/falsewall) in loc.contents) + to_chat(user, "There is already a false wall present!") + return + if(istype(W, /obj/item/stack/sheet/runed_metal)) + to_chat(user, "You can't seem to make the metal bend..") + return + + if(istype(W,/obj/item/stack/rods)) + var/obj/item/stack/rods/S = W + if(state == GIRDER_DISPLACED) + if(S.get_amount() < 2) + to_chat(user, "You need at least two rods to create a false wall!") + return + to_chat(user, "You start building a reinforced false wall...") + if(do_after(user, 20, target = src)) + if(!loc || !S || S.get_amount() < 2) + return + S.use(2) + to_chat(user, "You create a false wall. Push on it to open or close the passage.") + var/obj/structure/falsewall/iron/FW = new (loc) + transfer_fingerprints_to(FW) + qdel(src) + else + if(S.get_amount() < 5) + to_chat(user, "You need at least five rods to add plating!") + return + to_chat(user, "You start adding plating...") + if (do_after(user, 40, target = src)) + if(!loc || !S || S.get_amount() < 5) + return + S.use(5) + to_chat(user, "You add the plating.") + var/turf/T = get_turf(src) + T.ChangeTurf(/turf/simulated/wall/mineral/iron) + transfer_fingerprints_to(T) + qdel(src) + return + + if(!istype(W,/obj/item/stack/sheet)) + return + + var/obj/item/stack/sheet/S = W + if(!S.wall_allowed) + to_chat(user, "You don't think that is good material for a wall!") + return + + if(istype(S, /obj/item/stack/sheet/wood)) + if(state == GIRDER_DISPLACED) + if(S.get_amount() < 2) + to_chat(user, "You need two planks of wood to create a false wall!") + return + to_chat(user, "You start building a false wall...") + if(do_after(user, 20, target = src)) + if(!loc || !S || S.get_amount() < 2) + return + S.use(2) + to_chat(user, "You create a false wall. Push on it to open or close the passage.") + var/obj/structure/falsewall/wood/falsewood = new(loc) + transfer_fingerprints_to(falsewood) + qdel(src) + else + if(S.get_amount() < 2) + to_chat(user, "You need two planks of wood to finish a wall!") + return + to_chat(user, "You start adding plating...") + if(do_after(user, 40 * W.toolspeed, target = src)) + if(!src || !S || S.get_amount() < 2) + return + S.use(2) + to_chat(user, "You add the plating.") + var/turf/Tsrc = get_turf(src) + Tsrc.ChangeTurf(/turf/simulated/wall/mineral/wood) + for(var/turf/simulated/wall/mineral/wood/X in Tsrc.loc) + if(X) + X.add_hiddenprint(usr) + qdel(src) + return + + else if(istype(S, /obj/item/stack/sheet/metal)) + if(state == GIRDER_DISPLACED) + if(S.get_amount() < 2) + to_chat(user, "You need two sheets of metal to create a false wall!") + return + to_chat(user, "You start building a false wall...") + if(do_after(user, 20, target = src)) + if(!loc || !S || S.get_amount() < 2) + return + S.use(2) + to_chat(user, "You create a false wall. Push on it to open or close the passage.") + var/obj/structure/falsewall/F = new(loc) + transfer_fingerprints_to(F) + qdel(src) + else + if(S.get_amount() < 2) + to_chat(user, "You need two sheets of metal to finish a wall!") + return + to_chat(user, "You start adding plating...") + if(do_after(user, 40 * W.toolspeed, target = src)) + if(!src || !S || S.get_amount() < 2) + return + S.use(2) + to_chat(user, "You add the plating.") + var/turf/Tsrc = get_turf(src) + Tsrc.ChangeTurf(/turf/simulated/wall) + for(var/turf/simulated/wall/X in Tsrc.loc) + if(X) + X.add_hiddenprint(usr) + qdel(src) + return + + if(istype(S, /obj/item/stack/sheet/plasteel)) + if(state == GIRDER_DISPLACED) + if(S.get_amount() < 2) + to_chat(user, "You need at least two sheets to create a false wall!") + return + to_chat(user, "You start building a reinforced false wall...") + if(do_after(user, 20, target = src)) + if(!loc || !S || S.get_amount() < 2) + return + S.use(2) + to_chat(user, "You create a reinforced false wall. Push on it to open or close the passage.") + var/obj/structure/falsewall/reinforced/FW = new (loc) + transfer_fingerprints_to(FW) + qdel(src) + else + if(state == GIRDER_REINF) + if(S.get_amount() < 1) + return + to_chat(user, "You start finalizing the reinforced wall...") + if(do_after(user, 50, target = src)) + if(!src || !S || S.get_amount() < 1) + return + S.use(1) + to_chat(user, "You fully reinforce the wall.") + var/turf/Tsrc = get_turf(src) + Tsrc.ChangeTurf(/turf/simulated/wall/r_wall) + for(var/turf/simulated/wall/r_wall/X in Tsrc.loc) + if(X) + X.add_hiddenprint(usr) + qdel(src) + return + else + if(S.get_amount() < 1) + return + to_chat(user, "You start reinforcing the girder...") + if(do_after(user,60, target = src)) + if(!src || !S || S.get_amount() < 1) + return + S.use(1) + to_chat(user, "You reinforce the girder.") + var/obj/structure/girder/reinforced/R = new (loc) + transfer_fingerprints_to(R) + qdel(src) + return + + if(S.sheettype) + var/M = S.sheettype + if(state == GIRDER_DISPLACED) + if(S.get_amount() < 2) + to_chat(user, "You need at least two sheets to create a false wall!") + return + if(do_after(user, 20, target = src)) + if(!loc || !S || S.get_amount() < 2) + return + S.use(2) + to_chat(user, "You create a false wall. Push on it to open or close the passage.") + var/F = text2path("/obj/structure/falsewall/[M]") + var/obj/structure/FW = new F (loc) + transfer_fingerprints_to(FW) + qdel(src) + else + if(S.get_amount() < 2) + to_chat(user, "You need at least two sheets to add plating!") + return + to_chat(user, "You start adding plating...") + if(do_after(user,40, target = src)) + if(!src || !S || S.get_amount() < 2) + return + S.use(2) + to_chat(user, "You add the plating.") + var/turf/Tsrc = get_turf(src) + Tsrc.ChangeTurf(text2path("/turf/simulated/wall/mineral/[M]")) + for(var/turf/simulated/wall/mineral/X in Tsrc.loc) + if(X) + X.add_hiddenprint(usr) + qdel(src) + return + + add_hiddenprint(user) + + else if(istype(W, /obj/item/pipe)) + var/obj/item/pipe/P = W + if(P.pipe_type in list(0, 1, 5)) //simple pipes, simple bends, and simple manifolds. + if(!user.drop_item()) + return + P.loc = src.loc + to_chat(user, "You fit the pipe into \the [src].") + else + return ..() + +/obj/structure/girder/crowbar_act(mob/user, obj/item/I) + if(!can_displace || state != GIRDER_NORMAL) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + to_chat(user, "You start dislodging the girder...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_NORMAL) + return + to_chat(user, "You dislodge the girder.") + var/obj/structure/girder/displaced/D = new (loc) + transfer_fingerprints_to(D) + qdel(src) + +/obj/structure/girder/screwdriver_act(mob/user, obj/item/I) + if(state != GIRDER_DISPLACED && state != GIRDER_REINF && state != GIRDER_REINF_STRUTS) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + switch(state) + if(GIRDER_DISPLACED) + TOOL_ATTEMPT_DISMANTLE_MESSAGE + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_DISPLACED) + return + state = GIRDER_DISASSEMBLED + TOOL_DISMANTLE_SUCCESS_MESSAGE + var/obj/item/stack/sheet/metal/M = new(loc, 2) + M.add_fingerprint(user) + qdel(src) + if(GIRDER_REINF) + to_chat(user, "You start unsecuring support struts...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_REINF) + return + to_chat(user, "You unsecure the support struts.") + state = GIRDER_REINF_STRUTS + if(GIRDER_REINF_STRUTS) + to_chat(user, "You start securing support struts...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_REINF_STRUTS) + return + to_chat(user, "You secure the support struts.") + state = GIRDER_REINF + +/obj/structure/girder/wirecutter_act(mob/user, obj/item/I) + if(state != GIRDER_REINF_STRUTS) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + to_chat(user, "You start removing the inner grille...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_REINF_STRUTS) + return + to_chat(user, "You remove the inner grille.") + new /obj/item/stack/sheet/plasteel(get_turf(src)) + var/obj/structure/girder/G = new (loc) + transfer_fingerprints_to(G) + qdel(src) + +/obj/structure/girder/wrench_act(mob/user, obj/item/I) + if(state != GIRDER_NORMAL && state != GIRDER_DISPLACED) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(state == GIRDER_NORMAL) + TOOL_ATTEMPT_DISMANTLE_MESSAGE + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_NORMAL) + return + state = GIRDER_DISASSEMBLED + TOOL_DISMANTLE_SUCCESS_MESSAGE + var/obj/item/stack/sheet/metal/M = new(loc, 2) + M.add_fingerprint(user) + qdel(src) + else + if(!isfloorturf(loc)) + to_chat(user, "A floor must be present to secure the girder!") + return + to_chat(user, "You start securing the girder...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state != GIRDER_DISPLACED) + return + to_chat(user, "You secure the girder.") + var/obj/structure/girder/G = new(loc) + transfer_fingerprints_to(G) + qdel(src) + +/obj/structure/girder/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_SLICING_MESSAGE + if(I.use_tool(src, user, 40, volume = I.tool_volume)) + WELDER_SLICING_SUCCESS_MESSAGE + refundMetal(metalUsed) + qdel(src) + +/obj/structure/girder/CanPass(atom/movable/mover, turf/target, height=0) + if(height==0) + return 1 + if(istype(mover) && mover.checkpass(PASSGRILLE)) + return prob(girderpasschance) + else + if(istype(mover, /obj/item/projectile)) + return prob(girderpasschance) + else + return 0 + +/obj/structure/girder/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSGRILLE) + +/obj/structure/girder/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + var/remains = pick(/obj/item/stack/rods, /obj/item/stack/sheet/metal) + new remains(loc) + qdel(src) + +/obj/structure/girder/narsie_act() + if(prob(25)) + new /obj/structure/girder/cult(loc) + qdel(src) + +/obj/structure/girder/displaced + name = "displaced girder" + icon_state = "displaced" + anchored = 0 + state = GIRDER_DISPLACED + girderpasschance = 25 + max_integrity = 120 + +/obj/structure/girder/reinforced + name = "reinforced girder" + icon_state = "reinforced" + state = GIRDER_REINF + girderpasschance = 0 + max_integrity = 350 + +/obj/structure/girder/cult + name = "runed girder" + desc = "Framework made of a strange and shockingly cold metal. It doesn't seem to have any bolts." + icon = 'icons/obj/cult.dmi' + icon_state = "cultgirder" + can_displace = FALSE + metalUsed = 1 + metal_type = /obj/item/stack/sheet/runed_metal + +/obj/structure/girder/cult/New() + . = ..() + icon_state = SSticker.cultdat?.cult_girder_icon_state + +/obj/structure/girder/cult/refundMetal(metalAmount) + for(var/i=0;i < metalAmount;i++) + new /obj/item/stack/sheet/runed_metal(get_turf(src)) + +/obj/structure/girder/cult/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(istype(W, /obj/item/tome) && iscultist(user)) //Cultists can demolish cult girders instantly with their tomes + user.visible_message("[user] strikes [src] with [W]!", "You demolish [src].") + refundMetal(metalUsed) + qdel(src) + else if(istype(W, /obj/item/gun/energy/plasmacutter)) + to_chat(user, "You start slicing apart the girder...") + if(do_after(user, 40* W.toolspeed, target = src)) + playsound(loc, W.usesound, 100, 1) + to_chat(user, "You slice apart the girder.") + var/obj/item/stack/sheet/runed_metal/R = new(get_turf(src)) + R.amount = 1 + transfer_fingerprints_to(R) + qdel(src) + else if(istype(W, /obj/item/pickaxe/drill/jackhammer)) + var/obj/item/pickaxe/drill/jackhammer/D = W + to_chat(user, "Your jackhammer smashes through the girder!") + var/obj/item/stack/sheet/runed_metal/R = new(get_turf(src)) + R.amount = 1 + transfer_fingerprints_to(R) + D.playDigSound() + qdel(src) + + else if(istype(W, /obj/item/stack/sheet/runed_metal)) + var/obj/item/stack/sheet/runed_metal/R = W + if(R.get_amount() < 1) + to_chat(user, "You need at least one sheet of runed metal to construct a runed wall!") + return 0 + user.visible_message("[user] begins laying runed metal on [src]...", "You begin constructing a runed wall...") + if(do_after(user, 50, target = src)) + if(R.get_amount() < 1 || !R) + return + user.visible_message("[user] plates [src] with runed metal.", "You construct a runed wall.") + R.use(1) + var/turf/T = get_turf(src) + T.ChangeTurf(/turf/simulated/wall/cult) + qdel(src) + else + return ..() + +/obj/structure/girder/cult/narsie_act() + return + +/obj/structure/girder/cult/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/sheet/runed_metal(drop_location(), 1) + qdel(src) diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index ae24a1a23e9..755d2a3de1b 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -1,318 +1,318 @@ -/obj/structure/grille - desc = "A flimsy framework of metal rods." - name = "grille" - icon = 'icons/obj/structures.dmi' - icon_state = "grille" - density = TRUE - anchored = TRUE - flags = CONDUCT - pressure_resistance = 5*ONE_ATMOSPHERE - layer = BELOW_OBJ_LAYER - level = 3 - armor = list("melee" = 50, "bullet" = 70, "laser" = 70, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0) - max_integrity = 50 - integrity_failure = 20 - var/rods_type = /obj/item/stack/rods - var/rods_amount = 2 - var/rods_broken = 1 - var/grille_type - var/broken_type = /obj/structure/grille/broken - var/shockcooldown = 0 - var/my_shockcooldown = 1 SECONDS - -/obj/structure/grille/fence/ - var/width = 3 - -/obj/structure/grille/fence/New() - if(width > 1) - if(dir in list(EAST, WEST)) - bound_width = width * world.icon_size - bound_height = world.icon_size - else - bound_width = world.icon_size - bound_height = width * world.icon_size - -/obj/structure/grille/fence/east_west - //width=80 - //height=42 - icon='icons/fence-ew.dmi' - -/obj/structure/grille/fence/north_south - //width=80 - //height=42 - icon='icons/fence-ns.dmi' - -/obj/structure/grille/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - update_icon() - -/obj/structure/grille/examine(mob/user) - . = ..() - if(anchored) - . += "It's secured in place with screws. The rods look like they could be cut through." - if(!anchored) - . += "The anchoring screws are unscrewed. The rods look like they could be cut through." - -/obj/structure/grille/ratvar_act() - if(broken) - new /obj/structure/grille/ratvar/broken(loc) - else - new /obj/structure/grille/ratvar(loc) - qdel(src) - -/obj/structure/grille/Bumped(atom/user) - if(ismob(user)) - if(!(shockcooldown <= world.time)) - return - shock(user, 70) - shockcooldown = world.time + my_shockcooldown - -/obj/structure/grille/attack_animal(mob/user) - . = ..() - if(. && !QDELETED(src) && !shock(user, 70)) - take_damage(rand(5,10), BRUTE, "melee", 1) - -/obj/structure/grille/hulk_damage() - return 60 - -/obj/structure/grille/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) - if(user.a_intent == INTENT_HARM) - if(!shock(user, 70)) - ..(user, TRUE) - return TRUE - -/obj/structure/grille/attack_hand(mob/living/user) - . = ..() - if(.) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src, ATTACK_EFFECT_KICK) - user.visible_message("[user] hits [src].") - if(!shock(user, 70)) - take_damage(rand(5,10), BRUTE, "melee", 1) - -/obj/structure/grille/attack_alien(mob/living/user) - user.do_attack_animation(src) - user.changeNext_move(CLICK_CD_MELEE) - user.visible_message("[user] mangles [src].") - if(!shock(user, 70)) - take_damage(20, BRUTE, "melee", 1) - -/obj/structure/grille/CanPass(atom/movable/mover, turf/target, height=0) - if(height==0) - return 1 - if(istype(mover) && mover.checkpass(PASSGRILLE)) - return 1 - else - if(istype(mover, /obj/item/projectile)) - return prob(30) - else - return !density - -/obj/structure/grille/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(caller)) - var/atom/movable/mover = caller - . = . || mover.checkpass(PASSGRILLE) - -/obj/structure/grille/attackby(obj/item/W, mob/user, params) - user.changeNext_move(CLICK_CD_MELEE) - add_fingerprint(user) - if(istype(W, /obj/item/stack/rods) && broken) - var/obj/item/stack/rods/R = W - if(!shock(user, 90)) - user.visible_message("[user] rebuilds the broken grille.", \ - "You rebuild the broken grille.") - new grille_type(loc) - R.use(1) - qdel(src) - return - -//window placing begin - else if(is_glass_sheet(W)) - build_window(W, user) - return -//window placing end - - else if(istype(W, /obj/item/shard) || !shock(user, 70)) - return ..() - -/obj/structure/grille/wirecutter_act(mob/user, obj/item/I) - . = TRUE - if(shock(user, 100)) - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - deconstruct() - -/obj/structure/grille/screwdriver_act(mob/user, obj/item/I) - if(!(istype(loc, /turf/simulated) || anchored)) - return - . = TRUE - if(shock(user, 90)) - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - anchored = !anchored - user.visible_message("[user] [anchored ? "fastens" : "unfastens"] [src].", \ - "You [anchored ? "fasten [src] to" : "unfasten [src] from"] the floor.") - -/obj/structure/grille/proc/build_window(obj/item/stack/sheet/S, mob/user) - var/dir_to_set = NORTH - if(!istype(S) || !user) - return - if(broken) - to_chat(user, "You must repair or replace [src] first!") - return - if(S.get_amount() < 1) - to_chat(user, "You need at least one sheet of glass for that!") - return - if(!anchored) - to_chat(user, "[src] needs to be fastened to the floor first!") - return - if(!getRelativeDirection(src, user) && (user.loc != loc)) //essentially a cardinal direction adjacent or sharing same loc check - to_chat(user, "You can't reach.") - return - if(loc == user.loc) - dir_to_set = user.dir - else - if(x == user.x) - if(y > user.y) - dir_to_set = SOUTH - else - dir_to_set = NORTH - else if(y == user.y) - if(x > user.x) - dir_to_set = WEST - else - dir_to_set = EAST - for(var/obj/structure/window/WINDOW in loc) - if(WINDOW.dir == dir_to_set) - to_chat(user, "There is already a window facing this way there.") - return - to_chat(user, "You start placing the window...") - if(do_after(user, 20, target = src)) - if(!loc || !anchored) //Grille destroyed or unanchored while waiting - return - for(var/obj/structure/window/WINDOW in loc) - if(WINDOW.dir == dir_to_set)//checking this for a 2nd time to check if a window was made while we were waiting. - to_chat(user, "There is already a window facing this way there.") - return - var/obj/structure/window/W = new S.created_window(get_turf(src)) - S.use(1) - W.setDir(dir_to_set) - W.ini_dir = dir_to_set - W.anchored = FALSE - W.state = WINDOW_OUT_OF_FRAME - to_chat(user, "You place the [W] on [src].") - W.update_nearby_icons() - - -/obj/structure/grille/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(src, 'sound/effects/grillehit.ogg', 80, TRUE) - else - playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(src, 'sound/items/welder.ogg', 80, TRUE) - -/obj/structure/grille/deconstruct(disassembled = TRUE) - if(!loc) //if already qdel'd somehow, we do nothing - return - if(!(flags & NODECONSTRUCT)) - var/obj/R = new rods_type(drop_location(), rods_amount) - transfer_fingerprints_to(R) - qdel(src) - ..() - -/obj/structure/grille/obj_break() - if(!broken && !(flags & NODECONSTRUCT)) - new broken_type(loc) - var/obj/R = new rods_type(drop_location(), rods_broken) - transfer_fingerprints_to(R) - qdel(src) - -// shock user with probability prb (if all connections & power are working) -// returns 1 if shocked, 0 otherwise - -/obj/structure/grille/proc/shock(mob/user, prb) - if(!anchored || broken) // unanchored/broken grilles are never connected - return FALSE - if(!prob(prb)) - return FALSE - if(!in_range(src, user))//To prevent TK and mech users from getting shocked - return FALSE - var/turf/T = get_turf(src) - var/obj/structure/cable/C = T.get_cable_node() - if(C) - if(electrocute_mob(user, C, src, 1, TRUE)) - do_sparks(3, 1, src) - return TRUE - else - return FALSE - return FALSE - -/obj/structure/grille/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(!broken) - if(exposed_temperature > T0C + 1500) - take_damage(1, BURN, 0, 0) - -/obj/structure/grille/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(isobj(AM)) - if(prob(50) && anchored && !broken) - var/obj/O = AM - if(O.throwforce != 0)//don't want to let people spam tesla bolts, this way it will break after time - var/turf/T = get_turf(src) - var/obj/structure/cable/C = T.get_cable_node() - if(C) - playsound(src, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5) - tesla_zap(src, 3, C.newavail() * 0.01) //Zap for 1/100 of the amount of power. At a million watts in the grid, it will be as powerful as a tesla revolver shot. - C.add_delayedload(C.newavail() * 0.0375) // you can gain up to 3.5 via the 4x upgrades power is halved by the pole so thats 2x then 1X then .5X for 3.5x the 3 bounces shock. - return ..() - -/obj/structure/grille/broken // Pre-broken grilles for map placement - icon_state = "brokengrille" - density = 0 - obj_integrity = 20 - broken = 1 - rods_amount = 1 - rods_broken = 0 - grille_type = /obj/structure/grille - broken_type = null - -/obj/structure/grille/ratvar - icon_state = "ratvargrille" - name = "cog grille" - desc = "A strangely-shaped grille." - broken_type = /obj/structure/grille/ratvar/broken - -/obj/structure/grille/ratvar/New() - ..() - if(broken) - new /obj/effect/temp_visual/ratvar/grille/broken(get_turf(src)) - else - new /obj/effect/temp_visual/ratvar/grille(get_turf(src)) - new /obj/effect/temp_visual/ratvar/beam/grille(get_turf(src)) - -/obj/structure/grille/ratvar/narsie_act() - take_damage(rand(1, 3), BRUTE) - if(src) - var/previouscolor = color - color = "#960000" - animate(src, color = previouscolor, time = 8) - -/obj/structure/grille/ratvar/ratvar_act() - return - -/obj/structure/grille/ratvar/broken - icon_state = "brokenratvargrille" - density = FALSE - obj_integrity = 20 - broken = TRUE - rods_amount = 1 - rods_broken = 0 - grille_type = /obj/structure/grille/ratvar - broken_type = null +/obj/structure/grille + desc = "A flimsy framework of metal rods." + name = "grille" + icon = 'icons/obj/structures.dmi' + icon_state = "grille" + density = TRUE + anchored = TRUE + flags = CONDUCT + pressure_resistance = 5*ONE_ATMOSPHERE + layer = BELOW_OBJ_LAYER + level = 3 + armor = list("melee" = 50, "bullet" = 70, "laser" = 70, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0) + max_integrity = 50 + integrity_failure = 20 + var/rods_type = /obj/item/stack/rods + var/rods_amount = 2 + var/rods_broken = 1 + var/grille_type + var/broken_type = /obj/structure/grille/broken + var/shockcooldown = 0 + var/my_shockcooldown = 1 SECONDS + +/obj/structure/grille/fence/ + var/width = 3 + +/obj/structure/grille/fence/New() + if(width > 1) + if(dir in list(EAST, WEST)) + bound_width = width * world.icon_size + bound_height = world.icon_size + else + bound_width = world.icon_size + bound_height = width * world.icon_size + +/obj/structure/grille/fence/east_west + //width=80 + //height=42 + icon='icons/fence-ew.dmi' + +/obj/structure/grille/fence/north_south + //width=80 + //height=42 + icon='icons/fence-ns.dmi' + +/obj/structure/grille/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + update_icon() + +/obj/structure/grille/examine(mob/user) + . = ..() + if(anchored) + . += "It's secured in place with screws. The rods look like they could be cut through." + if(!anchored) + . += "The anchoring screws are unscrewed. The rods look like they could be cut through." + +/obj/structure/grille/ratvar_act() + if(broken) + new /obj/structure/grille/ratvar/broken(loc) + else + new /obj/structure/grille/ratvar(loc) + qdel(src) + +/obj/structure/grille/Bumped(atom/user) + if(ismob(user)) + if(!(shockcooldown <= world.time)) + return + shock(user, 70) + shockcooldown = world.time + my_shockcooldown + +/obj/structure/grille/attack_animal(mob/user) + . = ..() + if(. && !QDELETED(src) && !shock(user, 70)) + take_damage(rand(5,10), BRUTE, "melee", 1) + +/obj/structure/grille/hulk_damage() + return 60 + +/obj/structure/grille/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + if(user.a_intent == INTENT_HARM) + if(!shock(user, 70)) + ..(user, TRUE) + return TRUE + +/obj/structure/grille/attack_hand(mob/living/user) + . = ..() + if(.) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src, ATTACK_EFFECT_KICK) + user.visible_message("[user] hits [src].") + if(!shock(user, 70)) + take_damage(rand(5,10), BRUTE, "melee", 1) + +/obj/structure/grille/attack_alien(mob/living/user) + user.do_attack_animation(src) + user.changeNext_move(CLICK_CD_MELEE) + user.visible_message("[user] mangles [src].") + if(!shock(user, 70)) + take_damage(20, BRUTE, "melee", 1) + +/obj/structure/grille/CanPass(atom/movable/mover, turf/target, height=0) + if(height==0) + return 1 + if(istype(mover) && mover.checkpass(PASSGRILLE)) + return 1 + else + if(istype(mover, /obj/item/projectile)) + return prob(30) + else + return !density + +/obj/structure/grille/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSGRILLE) + +/obj/structure/grille/attackby(obj/item/W, mob/user, params) + user.changeNext_move(CLICK_CD_MELEE) + add_fingerprint(user) + if(istype(W, /obj/item/stack/rods) && broken) + var/obj/item/stack/rods/R = W + if(!shock(user, 90)) + user.visible_message("[user] rebuilds the broken grille.", \ + "You rebuild the broken grille.") + new grille_type(loc) + R.use(1) + qdel(src) + return + +//window placing begin + else if(is_glass_sheet(W)) + build_window(W, user) + return +//window placing end + + else if(istype(W, /obj/item/shard) || !shock(user, 70)) + return ..() + +/obj/structure/grille/wirecutter_act(mob/user, obj/item/I) + . = TRUE + if(shock(user, 100)) + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + deconstruct() + +/obj/structure/grille/screwdriver_act(mob/user, obj/item/I) + if(!(istype(loc, /turf/simulated) || anchored)) + return + . = TRUE + if(shock(user, 90)) + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + anchored = !anchored + user.visible_message("[user] [anchored ? "fastens" : "unfastens"] [src].", \ + "You [anchored ? "fasten [src] to" : "unfasten [src] from"] the floor.") + +/obj/structure/grille/proc/build_window(obj/item/stack/sheet/S, mob/user) + var/dir_to_set = NORTH + if(!istype(S) || !user) + return + if(broken) + to_chat(user, "You must repair or replace [src] first!") + return + if(S.get_amount() < 1) + to_chat(user, "You need at least one sheet of glass for that!") + return + if(!anchored) + to_chat(user, "[src] needs to be fastened to the floor first!") + return + if(!getRelativeDirection(src, user) && (user.loc != loc)) //essentially a cardinal direction adjacent or sharing same loc check + to_chat(user, "You can't reach.") + return + if(loc == user.loc) + dir_to_set = user.dir + else + if(x == user.x) + if(y > user.y) + dir_to_set = SOUTH + else + dir_to_set = NORTH + else if(y == user.y) + if(x > user.x) + dir_to_set = WEST + else + dir_to_set = EAST + for(var/obj/structure/window/WINDOW in loc) + if(WINDOW.dir == dir_to_set) + to_chat(user, "There is already a window facing this way there.") + return + to_chat(user, "You start placing the window...") + if(do_after(user, 20, target = src)) + if(!loc || !anchored) //Grille destroyed or unanchored while waiting + return + for(var/obj/structure/window/WINDOW in loc) + if(WINDOW.dir == dir_to_set)//checking this for a 2nd time to check if a window was made while we were waiting. + to_chat(user, "There is already a window facing this way there.") + return + var/obj/structure/window/W = new S.created_window(get_turf(src)) + S.use(1) + W.setDir(dir_to_set) + W.ini_dir = dir_to_set + W.anchored = FALSE + W.state = WINDOW_OUT_OF_FRAME + to_chat(user, "You place the [W] on [src].") + W.update_nearby_icons() + + +/obj/structure/grille/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(src, 'sound/effects/grillehit.ogg', 80, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(src, 'sound/items/welder.ogg', 80, TRUE) + +/obj/structure/grille/deconstruct(disassembled = TRUE) + if(!loc) //if already qdel'd somehow, we do nothing + return + if(!(flags & NODECONSTRUCT)) + var/obj/R = new rods_type(drop_location(), rods_amount) + transfer_fingerprints_to(R) + qdel(src) + ..() + +/obj/structure/grille/obj_break() + if(!broken && !(flags & NODECONSTRUCT)) + new broken_type(loc) + var/obj/R = new rods_type(drop_location(), rods_broken) + transfer_fingerprints_to(R) + qdel(src) + +// shock user with probability prb (if all connections & power are working) +// returns 1 if shocked, 0 otherwise + +/obj/structure/grille/proc/shock(mob/user, prb) + if(!anchored || broken) // unanchored/broken grilles are never connected + return FALSE + if(!prob(prb)) + return FALSE + if(!in_range(src, user))//To prevent TK and mech users from getting shocked + return FALSE + var/turf/T = get_turf(src) + var/obj/structure/cable/C = T.get_cable_node() + if(C) + if(electrocute_mob(user, C, src, 1, TRUE)) + do_sparks(3, 1, src) + return TRUE + else + return FALSE + return FALSE + +/obj/structure/grille/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(!broken) + if(exposed_temperature > T0C + 1500) + take_damage(1, BURN, 0, 0) + +/obj/structure/grille/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(isobj(AM)) + if(prob(50) && anchored && !broken) + var/obj/O = AM + if(O.throwforce != 0)//don't want to let people spam tesla bolts, this way it will break after time + var/turf/T = get_turf(src) + var/obj/structure/cable/C = T.get_cable_node() + if(C) + playsound(src, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5) + tesla_zap(src, 3, C.newavail() * 0.01) //Zap for 1/100 of the amount of power. At a million watts in the grid, it will be as powerful as a tesla revolver shot. + C.add_delayedload(C.newavail() * 0.0375) // you can gain up to 3.5 via the 4x upgrades power is halved by the pole so thats 2x then 1X then .5X for 3.5x the 3 bounces shock. + return ..() + +/obj/structure/grille/broken // Pre-broken grilles for map placement + icon_state = "brokengrille" + density = 0 + obj_integrity = 20 + broken = 1 + rods_amount = 1 + rods_broken = 0 + grille_type = /obj/structure/grille + broken_type = null + +/obj/structure/grille/ratvar + icon_state = "ratvargrille" + name = "cog grille" + desc = "A strangely-shaped grille." + broken_type = /obj/structure/grille/ratvar/broken + +/obj/structure/grille/ratvar/New() + ..() + if(broken) + new /obj/effect/temp_visual/ratvar/grille/broken(get_turf(src)) + else + new /obj/effect/temp_visual/ratvar/grille(get_turf(src)) + new /obj/effect/temp_visual/ratvar/beam/grille(get_turf(src)) + +/obj/structure/grille/ratvar/narsie_act() + take_damage(rand(1, 3), BRUTE) + if(src) + var/previouscolor = color + color = "#960000" + animate(src, color = previouscolor, time = 8) + +/obj/structure/grille/ratvar/ratvar_act() + return + +/obj/structure/grille/ratvar/broken + icon_state = "brokenratvargrille" + density = FALSE + obj_integrity = 20 + broken = TRUE + rods_amount = 1 + rods_broken = 0 + grille_type = /obj/structure/grille/ratvar + broken_type = null diff --git a/code/game/objects/structures/guillotine.dm b/code/game/objects/structures/guillotine.dm index e5b5b6c7373..9184d739a5f 100644 --- a/code/game/objects/structures/guillotine.dm +++ b/code/game/objects/structures/guillotine.dm @@ -253,4 +253,4 @@ #undef GUILLOTINE_ACTIVATE_DELAY #undef GUILLOTINE_WRENCH_DELAY #undef GUILLOTINE_ACTION_INUSE -#undef GUILLOTINE_ACTION_WRENCH \ No newline at end of file +#undef GUILLOTINE_ACTION_WRENCH diff --git a/code/game/objects/structures/inflatable.dm b/code/game/objects/structures/inflatable.dm index e9853f12162..d3cf5b0dd96 100644 --- a/code/game/objects/structures/inflatable.dm +++ b/code/game/objects/structures/inflatable.dm @@ -208,4 +208,4 @@ new /obj/item/inflatable(src) new /obj/item/inflatable(src) new /obj/item/inflatable(src) - new /obj/item/inflatable(src) \ No newline at end of file + new /obj/item/inflatable(src) diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm index bba1dc18de6..58120ca4ed5 100644 --- a/code/game/objects/structures/janicart.dm +++ b/code/game/objects/structures/janicart.dm @@ -1,186 +1,186 @@ -//TG style Janicart - -/obj/structure/janitorialcart - name = "janitorial cart" - desc = "This is the alpha and omega of sanitation." - icon = 'icons/obj/janitor.dmi' - icon_state = "cart" - anchored = 0 - density = 1 - container_type = OPENCONTAINER - //copypaste sorry - var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite - var/obj/item/storage/bag/trash/mybag = null - var/obj/item/mop/mymop = null - var/obj/item/reagent_containers/spray/cleaner/myspray = null - var/obj/item/lightreplacer/myreplacer = null - var/signs = 0 - var/const/max_signs = 4 - - -/obj/structure/janitorialcart/New() - ..() - create_reagents(100) - GLOB.janitorial_equipment += src - -/obj/structure/janitorialcart/Destroy() - GLOB.janitorial_equipment -= src - QDEL_NULL(mybag) - QDEL_NULL(mymop) - QDEL_NULL(myspray) - QDEL_NULL(myreplacer) - return ..() - -/obj/structure/janitorialcart/proc/wet_mop(obj/item/mop, mob/user) - if(reagents.total_volume < 1) - to_chat(user, "[src] is out of water!") - else - reagents.trans_to(mop, 5) // - to_chat(user, "You wet [mop] in [src].") - playsound(loc, 'sound/effects/slosh.ogg', 25, 1) - -/obj/structure/janitorialcart/proc/put_in_cart(obj/item/I, mob/user) - user.drop_item() - I.loc = src - updateUsrDialog() - to_chat(user, "You put [I] into [src].") - return - - -/obj/structure/janitorialcart/attackby(obj/item/I, mob/user, params) - var/fail_msg = "There is already one of those in [src]." - - if(!I.is_robot_module()) - if(istype(I, /obj/item/mop)) - var/obj/item/mop/m=I - if(m.reagents.total_volume < m.reagents.maximum_volume) - wet_mop(m, user) - return - if(!mymop) - m.janicart_insert(user, src) - else - to_chat(user, fail_msg) - - else if(istype(I, /obj/item/storage/bag/trash)) - if(!mybag) - var/obj/item/storage/bag/trash/t=I - t.janicart_insert(user, src) - else - to_chat(user, fail_msg) - else if(istype(I, /obj/item/reagent_containers/spray/cleaner)) - if(!myspray) - put_in_cart(I, user) - myspray=I - update_icon() - else - to_chat(user, fail_msg) - else if(istype(I, /obj/item/lightreplacer)) - if(!myreplacer) - var/obj/item/lightreplacer/l=I - l.janicart_insert(user,src) - else - to_chat(user, fail_msg) - else if(istype(I, /obj/item/caution)) - if(signs < max_signs) - put_in_cart(I, user) - signs++ - update_icon() - else - to_chat(user, "[src] can't hold any more signs.") - else if(istype(I, /obj/item/crowbar)) - user.visible_message("[user] begins to empty the contents of [src].") - if(do_after(user, 30 * I.toolspeed, target = src)) - to_chat(usr, "You empty the contents of [src]'s bucket onto the floor.") - reagents.reaction(src.loc) - src.reagents.clear_reagents() - else if(istype(I, /obj/item/wrench)) - if(!anchored && !isinspace()) - playsound(src.loc, I.usesound, 50, 1) - user.visible_message( \ - "[user] tightens \the [src]'s casters.", \ - " You have tightened \the [src]'s casters.", \ - "You hear ratchet.") - anchored = 1 - else if(anchored) - playsound(src.loc, I.usesound, 50, 1) - user.visible_message( \ - "[user] loosens \the [src]'s casters.", \ - " You have loosened \the [src]'s casters.", \ - "You hear ratchet.") - anchored = 0 - else if(mybag) - mybag.attackby(I, user, params) - else - to_chat(usr, "You cannot interface your modules [src]!") - -/obj/structure/janitorialcart/attack_hand(mob/user) - user.set_machine(src) - var/dat - if(mybag) - dat += "[mybag.name]
        " - if(mymop) - dat += "[mymop.name]
        " - if(myspray) - dat += "[myspray.name]
        " - if(myreplacer) - dat += "[myreplacer.name]
        " - if(signs) - dat += "[signs] sign\s
        " - var/datum/browser/popup = new(user, "janicart", name, 240, 160) - popup.set_content(dat) - popup.open() - - -/obj/structure/janitorialcart/Topic(href, href_list) - if(!in_range(src, usr)) - return - if(!isliving(usr)) - return - var/mob/living/user = usr - if(href_list["garbage"]) - if(mybag) - user.put_in_hands(mybag) - to_chat(user, "You take [mybag] from [src].") - mybag = null - if(href_list["mop"]) - if(mymop) - user.put_in_hands(mymop) - to_chat(user, "You take [mymop] from [src].") - mymop = null - if(href_list["spray"]) - if(myspray) - user.put_in_hands(myspray) - to_chat(user, "You take [myspray] from [src].") - myspray = null - if(href_list["replacer"]) - if(myreplacer) - user.put_in_hands(myreplacer) - to_chat(user, "You take [myreplacer] from [src].") - myreplacer = null - if(href_list["sign"]) - if(signs) - var/obj/item/caution/Sign = locate() in src - if(Sign) - user.put_in_hands(Sign) - to_chat(user, "You take \a [Sign] from [src].") - signs-- - else - WARNING("Signs ([signs]) didn't match contents") - signs = 0 - - update_icon() - updateUsrDialog() - - -/obj/structure/janitorialcart/update_icon() - overlays = null - if(mybag) - overlays += "cart_garbage" - if(mymop) - overlays += "cart_mop" - if(myspray) - overlays += "cart_spray" - if(myreplacer) - overlays += "cart_replacer" - if(signs) - overlays += "cart_sign[signs]" \ No newline at end of file +//TG style Janicart + +/obj/structure/janitorialcart + name = "janitorial cart" + desc = "This is the alpha and omega of sanitation." + icon = 'icons/obj/janitor.dmi' + icon_state = "cart" + anchored = 0 + density = 1 + container_type = OPENCONTAINER + //copypaste sorry + var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite + var/obj/item/storage/bag/trash/mybag = null + var/obj/item/mop/mymop = null + var/obj/item/reagent_containers/spray/cleaner/myspray = null + var/obj/item/lightreplacer/myreplacer = null + var/signs = 0 + var/const/max_signs = 4 + + +/obj/structure/janitorialcart/New() + ..() + create_reagents(100) + GLOB.janitorial_equipment += src + +/obj/structure/janitorialcart/Destroy() + GLOB.janitorial_equipment -= src + QDEL_NULL(mybag) + QDEL_NULL(mymop) + QDEL_NULL(myspray) + QDEL_NULL(myreplacer) + return ..() + +/obj/structure/janitorialcart/proc/wet_mop(obj/item/mop, mob/user) + if(reagents.total_volume < 1) + to_chat(user, "[src] is out of water!") + else + reagents.trans_to(mop, 5) // + to_chat(user, "You wet [mop] in [src].") + playsound(loc, 'sound/effects/slosh.ogg', 25, 1) + +/obj/structure/janitorialcart/proc/put_in_cart(obj/item/I, mob/user) + user.drop_item() + I.loc = src + updateUsrDialog() + to_chat(user, "You put [I] into [src].") + return + + +/obj/structure/janitorialcart/attackby(obj/item/I, mob/user, params) + var/fail_msg = "There is already one of those in [src]." + + if(!I.is_robot_module()) + if(istype(I, /obj/item/mop)) + var/obj/item/mop/m=I + if(m.reagents.total_volume < m.reagents.maximum_volume) + wet_mop(m, user) + return + if(!mymop) + m.janicart_insert(user, src) + else + to_chat(user, fail_msg) + + else if(istype(I, /obj/item/storage/bag/trash)) + if(!mybag) + var/obj/item/storage/bag/trash/t=I + t.janicart_insert(user, src) + else + to_chat(user, fail_msg) + else if(istype(I, /obj/item/reagent_containers/spray/cleaner)) + if(!myspray) + put_in_cart(I, user) + myspray=I + update_icon() + else + to_chat(user, fail_msg) + else if(istype(I, /obj/item/lightreplacer)) + if(!myreplacer) + var/obj/item/lightreplacer/l=I + l.janicart_insert(user,src) + else + to_chat(user, fail_msg) + else if(istype(I, /obj/item/caution)) + if(signs < max_signs) + put_in_cart(I, user) + signs++ + update_icon() + else + to_chat(user, "[src] can't hold any more signs.") + else if(istype(I, /obj/item/crowbar)) + user.visible_message("[user] begins to empty the contents of [src].") + if(do_after(user, 30 * I.toolspeed, target = src)) + to_chat(usr, "You empty the contents of [src]'s bucket onto the floor.") + reagents.reaction(src.loc) + src.reagents.clear_reagents() + else if(istype(I, /obj/item/wrench)) + if(!anchored && !isinspace()) + playsound(src.loc, I.usesound, 50, 1) + user.visible_message( \ + "[user] tightens \the [src]'s casters.", \ + " You have tightened \the [src]'s casters.", \ + "You hear ratchet.") + anchored = 1 + else if(anchored) + playsound(src.loc, I.usesound, 50, 1) + user.visible_message( \ + "[user] loosens \the [src]'s casters.", \ + " You have loosened \the [src]'s casters.", \ + "You hear ratchet.") + anchored = 0 + else if(mybag) + mybag.attackby(I, user, params) + else + to_chat(usr, "You cannot interface your modules [src]!") + +/obj/structure/janitorialcart/attack_hand(mob/user) + user.set_machine(src) + var/dat + if(mybag) + dat += "[mybag.name]
        " + if(mymop) + dat += "[mymop.name]
        " + if(myspray) + dat += "[myspray.name]
        " + if(myreplacer) + dat += "[myreplacer.name]
        " + if(signs) + dat += "[signs] sign\s
        " + var/datum/browser/popup = new(user, "janicart", name, 240, 160) + popup.set_content(dat) + popup.open() + + +/obj/structure/janitorialcart/Topic(href, href_list) + if(!in_range(src, usr)) + return + if(!isliving(usr)) + return + var/mob/living/user = usr + if(href_list["garbage"]) + if(mybag) + user.put_in_hands(mybag) + to_chat(user, "You take [mybag] from [src].") + mybag = null + if(href_list["mop"]) + if(mymop) + user.put_in_hands(mymop) + to_chat(user, "You take [mymop] from [src].") + mymop = null + if(href_list["spray"]) + if(myspray) + user.put_in_hands(myspray) + to_chat(user, "You take [myspray] from [src].") + myspray = null + if(href_list["replacer"]) + if(myreplacer) + user.put_in_hands(myreplacer) + to_chat(user, "You take [myreplacer] from [src].") + myreplacer = null + if(href_list["sign"]) + if(signs) + var/obj/item/caution/Sign = locate() in src + if(Sign) + user.put_in_hands(Sign) + to_chat(user, "You take \a [Sign] from [src].") + signs-- + else + WARNING("Signs ([signs]) didn't match contents") + signs = 0 + + update_icon() + updateUsrDialog() + + +/obj/structure/janitorialcart/update_icon() + overlays = null + if(mybag) + overlays += "cart_garbage" + if(mymop) + overlays += "cart_mop" + if(myspray) + overlays += "cart_spray" + if(myreplacer) + overlays += "cart_replacer" + if(signs) + overlays += "cart_sign[signs]" diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm index 6281d589c70..21af7f6e615 100644 --- a/code/game/objects/structures/kitchen_spike.dm +++ b/code/game/objects/structures/kitchen_spike.dm @@ -1,156 +1,156 @@ - -//////Kitchen Spike - -/obj/structure/kitchenspike_frame - name = "meatspike frame" - icon = 'icons/obj/kitchen.dmi' - icon_state = "spikeframe" - desc = "The frame of a meat spike." - density = 1 - anchored = 0 - max_integrity = 200 - -/obj/structure/kitchenspike_frame/attackby(obj/item/I, mob/user, params) - add_fingerprint(user) - if(istype(I, /obj/item/wrench)) - if(anchored) - to_chat(user, "You unwrench [src] from the floor.") - anchored = 0 - else - to_chat(user, "You wrench [src] into place.") - anchored = 1 - else if(istype(I, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = I - if(R.get_amount() >= 4) - R.use(4) - to_chat(user, "You add spikes to the frame.") - new /obj/structure/kitchenspike(loc) - add_fingerprint(user) - qdel(src) - return - else - return ..() - - -/obj/structure/kitchenspike - name = "meat spike" - icon = 'icons/obj/kitchen.dmi' - icon_state = "spike" - desc = "A spike for collecting meat from animals." - density = 1 - anchored = 1 - buckle_lying = FALSE - can_buckle = TRUE - max_integrity = 250 - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/structure/kitchenspike/attack_hand(mob/user) - if(has_buckled_mobs()) - for(var/mob/living/L in buckled_mobs) - user_unbuckle_mob(L, user) - else - ..() - -/obj/structure/kitchenspike/attackby(obj/item/grab/G, mob/user) - if(istype(G, /obj/item/crowbar)) - if(!has_buckled_mobs()) - playsound(loc, G.usesound, 100, 1) - if(do_after(user, 20 * G.toolspeed, target = src)) - to_chat(user, "You pry the spikes out of the frame.") - deconstruct(TRUE) - else - to_chat(user, "You can't do that while something's on the spike!") - return - if(!istype(G, /obj/item/grab) || !G.affecting) - return - if(has_buckled_mobs()) - to_chat(user, "The spike already has something on it, finish collecting its meat first!") - return - if(isliving(G.affecting)) - if(!has_buckled_mobs()) - if(do_mob(user, src, 120)) - if(spike(G.affecting)) - G.affecting.visible_message("[user] slams [G.affecting] onto the meat spike!", "[user] slams you onto the meat spike!", "You hear a squishy wet noise.") - qdel(G) - return - return ..() - -/obj/structure/kitchenspike/proc/spike(mob/living/victim) - - if(!istype(victim)) - return FALSE - - if(has_buckled_mobs()) //to prevent spam/queing up attacks - return FALSE - if(victim.buckled) - return FALSE - playsound(loc, 'sound/effects/splat.ogg', 25, 1) - victim.forceMove(drop_location()) - victim.emote("scream") - if(ishuman(victim)) - var/mob/living/carbon/human/H = victim - H.add_splatter_floor() - victim.adjustBruteLoss(30) - victim.setDir(2) - buckle_mob(victim, force = TRUE) - var/matrix/m180 = matrix(victim.transform) - m180.Turn(180) - animate(victim, transform = m180, time = 3) - victim.pixel_y = victim.get_standard_pixel_y_offset(180) - return TRUE - - -/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking - return - -/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user) - if(buckled_mob) - var/mob/living/M = buckled_mob - if(M != user) - M.visible_message("[user] tries to pull [M] free of [src]!",\ - "[user] is trying to pull you off [src], opening up fresh wounds!",\ - "You hear a squishy wet noise.") - if(!do_after(user, 300, target = src)) - if(M && M.buckled) - M.visible_message("[user] fails to free [M]!",\ - "[user] fails to pull you off of [src].") - return - - else - M.visible_message("[M] struggles to break free from [src]!",\ - "You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\ - "You hear a wet squishing noise..") - M.adjustBruteLoss(30) - if(!do_after(M, 1200, target = src)) - if(M && M.buckled) - to_chat(M, "You fail to free yourself!") - return - if(!M.buckled) - return - release_mob(M) - -/obj/structure/kitchenspike/proc/release_mob(mob/living/M) - var/matrix/m180 = matrix(M.transform) - m180.Turn(180) - animate(M, transform = m180, time = 3) - M.pixel_y = M.get_standard_pixel_y_offset(180) - M.adjustBruteLoss(30) - src.visible_message(text("[M] falls free of [src]!")) - unbuckle_mob(M, force = TRUE) - M.emote("scream") - M.AdjustWeakened(10) - -/obj/structure/kitchenspike/Destroy() - if(has_buckled_mobs()) - for(var/mob/living/L in buckled_mobs) - release_mob(L) - return ..() - -/obj/structure/kitchenspike/deconstruct(disassembled = TRUE) - if(disassembled) - var/obj/F = new /obj/structure/kitchenspike_frame(loc) - transfer_fingerprints_to(F) - else - new /obj/item/stack/sheet/metal(loc, 4) - new /obj/item/stack/rods(loc, 4) - qdel(src) \ No newline at end of file + +//////Kitchen Spike + +/obj/structure/kitchenspike_frame + name = "meatspike frame" + icon = 'icons/obj/kitchen.dmi' + icon_state = "spikeframe" + desc = "The frame of a meat spike." + density = 1 + anchored = 0 + max_integrity = 200 + +/obj/structure/kitchenspike_frame/attackby(obj/item/I, mob/user, params) + add_fingerprint(user) + if(istype(I, /obj/item/wrench)) + if(anchored) + to_chat(user, "You unwrench [src] from the floor.") + anchored = 0 + else + to_chat(user, "You wrench [src] into place.") + anchored = 1 + else if(istype(I, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = I + if(R.get_amount() >= 4) + R.use(4) + to_chat(user, "You add spikes to the frame.") + new /obj/structure/kitchenspike(loc) + add_fingerprint(user) + qdel(src) + return + else + return ..() + + +/obj/structure/kitchenspike + name = "meat spike" + icon = 'icons/obj/kitchen.dmi' + icon_state = "spike" + desc = "A spike for collecting meat from animals." + density = 1 + anchored = 1 + buckle_lying = FALSE + can_buckle = TRUE + max_integrity = 250 + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/structure/kitchenspike/attack_hand(mob/user) + if(has_buckled_mobs()) + for(var/mob/living/L in buckled_mobs) + user_unbuckle_mob(L, user) + else + ..() + +/obj/structure/kitchenspike/attackby(obj/item/grab/G, mob/user) + if(istype(G, /obj/item/crowbar)) + if(!has_buckled_mobs()) + playsound(loc, G.usesound, 100, 1) + if(do_after(user, 20 * G.toolspeed, target = src)) + to_chat(user, "You pry the spikes out of the frame.") + deconstruct(TRUE) + else + to_chat(user, "You can't do that while something's on the spike!") + return + if(!istype(G, /obj/item/grab) || !G.affecting) + return + if(has_buckled_mobs()) + to_chat(user, "The spike already has something on it, finish collecting its meat first!") + return + if(isliving(G.affecting)) + if(!has_buckled_mobs()) + if(do_mob(user, src, 120)) + if(spike(G.affecting)) + G.affecting.visible_message("[user] slams [G.affecting] onto the meat spike!", "[user] slams you onto the meat spike!", "You hear a squishy wet noise.") + qdel(G) + return + return ..() + +/obj/structure/kitchenspike/proc/spike(mob/living/victim) + + if(!istype(victim)) + return FALSE + + if(has_buckled_mobs()) //to prevent spam/queing up attacks + return FALSE + if(victim.buckled) + return FALSE + playsound(loc, 'sound/effects/splat.ogg', 25, 1) + victim.forceMove(drop_location()) + victim.emote("scream") + if(ishuman(victim)) + var/mob/living/carbon/human/H = victim + H.add_splatter_floor() + victim.adjustBruteLoss(30) + victim.setDir(2) + buckle_mob(victim, force = TRUE) + var/matrix/m180 = matrix(victim.transform) + m180.Turn(180) + animate(victim, transform = m180, time = 3) + victim.pixel_y = victim.get_standard_pixel_y_offset(180) + return TRUE + + +/obj/structure/kitchenspike/user_buckle_mob(mob/living/M, mob/living/user) //Don't want them getting put on the rack other than by spiking + return + +/obj/structure/kitchenspike/user_unbuckle_mob(mob/living/buckled_mob, mob/living/carbon/human/user) + if(buckled_mob) + var/mob/living/M = buckled_mob + if(M != user) + M.visible_message("[user] tries to pull [M] free of [src]!",\ + "[user] is trying to pull you off [src], opening up fresh wounds!",\ + "You hear a squishy wet noise.") + if(!do_after(user, 300, target = src)) + if(M && M.buckled) + M.visible_message("[user] fails to free [M]!",\ + "[user] fails to pull you off of [src].") + return + + else + M.visible_message("[M] struggles to break free from [src]!",\ + "You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\ + "You hear a wet squishing noise..") + M.adjustBruteLoss(30) + if(!do_after(M, 1200, target = src)) + if(M && M.buckled) + to_chat(M, "You fail to free yourself!") + return + if(!M.buckled) + return + release_mob(M) + +/obj/structure/kitchenspike/proc/release_mob(mob/living/M) + var/matrix/m180 = matrix(M.transform) + m180.Turn(180) + animate(M, transform = m180, time = 3) + M.pixel_y = M.get_standard_pixel_y_offset(180) + M.adjustBruteLoss(30) + src.visible_message(text("[M] falls free of [src]!")) + unbuckle_mob(M, force = TRUE) + M.emote("scream") + M.AdjustWeakened(10) + +/obj/structure/kitchenspike/Destroy() + if(has_buckled_mobs()) + for(var/mob/living/L in buckled_mobs) + release_mob(L) + return ..() + +/obj/structure/kitchenspike/deconstruct(disassembled = TRUE) + if(disassembled) + var/obj/F = new /obj/structure/kitchenspike_frame(loc) + transfer_fingerprints_to(F) + else + new /obj/item/stack/sheet/metal(loc, 4) + new /obj/item/stack/rods(loc, 4) + qdel(src) diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm index d240e78af5c..cb093c9521a 100644 --- a/code/game/objects/structures/ladders.dm +++ b/code/game/objects/structures/ladders.dm @@ -1,202 +1,202 @@ -// Basic ladder. By default links to the z-level above/below. -/obj/structure/ladder - name = "ladder" - desc = "A sturdy metal ladder." - icon = 'icons/obj/structures.dmi' - icon_state = "ladder11" - anchored = TRUE - var/obj/structure/ladder/down //the ladder below this one - var/obj/structure/ladder/up //the ladder above this one - var/use_verb = "climb" - -/obj/structure/ladder/Initialize(mapload, obj/structure/ladder/up, obj/structure/ladder/down) - . = ..() - if (up) - src.up = up - up.down = src - up.update_icon() - if (down) - src.down = down - down.up = src - down.update_icon() - return INITIALIZE_HINT_LATELOAD - -/obj/structure/ladder/Destroy(force) - if((resistance_flags & INDESTRUCTIBLE) && !force) - return QDEL_HINT_LETMELIVE - disconnect() - return ..() - -/obj/structure/ladder/LateInitialize() - // By default, discover ladders above and below us vertically - var/turf/T = get_turf(src) - - if(!down) - for(var/obj/structure/ladder/L in locate(T.x, T.y, T.z - 1)) - down = L - L.up = src // Don't waste effort looping the other way - L.update_icon() - break - if(!up) - for (var/obj/structure/ladder/L in locate(T.x, T.y, T.z + 1)) - up = L - L.down = src // Don't waste effort looping the other way - L.update_icon() - break - - update_icon() - -/obj/structure/ladder/proc/disconnect() - if(up && up.down == src) - up.down = null - up.update_icon() - if(down && down.up == src) - down.up = null - down.update_icon() - up = down = null - -/obj/structure/ladder/update_icon() - if(up && down) - icon_state = "ladder11" - - else if(up) - icon_state = "ladder10" - - else if(down) - icon_state = "ladder01" - - else //wtf make your ladders properly assholes - icon_state = "ladder00" - -/obj/structure/ladder/singularity_pull() - if(!(resistance_flags & INDESTRUCTIBLE)) - visible_message("[src] is torn to pieces by the gravitational pull!") - qdel(src) - -/obj/structure/ladder/proc/travel(going_up, mob/user, is_ghost, obj/structure/ladder/ladder) - if(!is_ghost) - show_fluff_message(going_up, user) - ladder.add_fingerprint(user) - - var/turf/T = get_turf(ladder) - var/atom/movable/AM - if(user.pulling) - AM = user.pulling - AM.forceMove(T) - user.forceMove(T) - if(AM) - user.start_pulling(AM) - -/obj/structure/ladder/proc/use(mob/user, is_ghost = FALSE) - if(!is_ghost && !in_range(src, user)) - return - - if(up && down) - var/result = alert("Go up or down [src]?", "[name]", "Up", "Down", "Cancel") - if (!is_ghost && !in_range(src, user)) - return // nice try - switch(result) - if("Up") - travel(TRUE, user, is_ghost, up) - if("Down") - travel(FALSE, user, is_ghost, down) - if("Cancel") - return - else if(up) - travel(TRUE, user, is_ghost, up) - else if(down) - travel(FALSE, user, is_ghost, down) - else - to_chat(user, "[src] doesn't seem to lead anywhere!") - - if(!is_ghost) - add_fingerprint(user) - -/obj/structure/ladder/attack_hand(mob/user) - use(user) - -/obj/structure/ladder/attackby(obj/item/W, mob/user, params) - return use(user) - -/obj/structure/ladder/attack_robot(mob/living/silicon/robot/R) - if(R.Adjacent(src)) - return use(R) - -//ATTACK GHOST IGNORING PARENT RETURN VALUE -/obj/structure/ladder/attack_ghost(mob/dead/observer/user) - use(user, TRUE) - -/obj/structure/ladder/proc/show_fluff_message(going_up, mob/user) - if(going_up) - user.visible_message("[user] climbs up [src].","You [use_verb] up [src].") - else - user.visible_message("[user] climbs down [src].","You [use_verb] down [src].") - - -// Indestructible away mission ladders which link based on a mapped ID and height value rather than X/Y/Z. -/obj/structure/ladder/unbreakable - name = "sturdy ladder" - desc = "An extremely sturdy metal ladder." - resistance_flags = INDESTRUCTIBLE - var/id - var/height = 0 // higher numbers are considered physically higher - -/obj/structure/ladder/unbreakable/Initialize(mapload) - GLOB.ladders += src - return ..() - -/obj/structure/ladder/unbreakable/Destroy() - . = ..() - if(. != QDEL_HINT_LETMELIVE) - GLOB.ladders -= src - -/obj/structure/ladder/unbreakable/LateInitialize() - // Override the parent to find ladders based on being height-linked - if(!id || (up && down)) - update_icon() - return - - for(var/O in GLOB.ladders) - var/obj/structure/ladder/unbreakable/L = O - if(L.id != id) - continue // not one of our pals - if(!down && L.height == height - 1) - down = L - L.up = src - L.update_icon() - if (up) - break // break if both our connections are filled - else if(!up && L.height == height + 1) - up = L - L.down = src - L.update_icon() - if (down) - break // break if both our connections are filled - - update_icon() - -/obj/structure/ladder/unbreakable/dive_point/buoy - name = "diving point buoy" - desc = "A buoy marking the location of an underwater dive area." - icon = 'icons/misc/beach.dmi' - icon_state = "buoy" - id = "dive" - height = 2 - use_verb = "swim" - layer = MOB_LAYER + 0.2 //0.1 higher than the water overlay, this also means people can "swim" behind/under it - -/obj/structure/ladder/unbreakable/dive_point/anchor - name = "diving point anchor" - desc = "An anchor tethered to the buoy at the surface, to keep the dive area marked." - icon = 'icons/misc/beach.dmi' - icon_state = "anchor" - id = "dive" - height = 1 - light_range = 5 - -/obj/structure/ladder/dive_point/Initialize(mapload) - . = ..() - set_light(light_range, light_power) //magical glowing anchor - -/obj/structure/ladder/unbreakable/dive_point/update_icon() - return \ No newline at end of file +// Basic ladder. By default links to the z-level above/below. +/obj/structure/ladder + name = "ladder" + desc = "A sturdy metal ladder." + icon = 'icons/obj/structures.dmi' + icon_state = "ladder11" + anchored = TRUE + var/obj/structure/ladder/down //the ladder below this one + var/obj/structure/ladder/up //the ladder above this one + var/use_verb = "climb" + +/obj/structure/ladder/Initialize(mapload, obj/structure/ladder/up, obj/structure/ladder/down) + . = ..() + if (up) + src.up = up + up.down = src + up.update_icon() + if (down) + src.down = down + down.up = src + down.update_icon() + return INITIALIZE_HINT_LATELOAD + +/obj/structure/ladder/Destroy(force) + if((resistance_flags & INDESTRUCTIBLE) && !force) + return QDEL_HINT_LETMELIVE + disconnect() + return ..() + +/obj/structure/ladder/LateInitialize() + // By default, discover ladders above and below us vertically + var/turf/T = get_turf(src) + + if(!down) + for(var/obj/structure/ladder/L in locate(T.x, T.y, T.z - 1)) + down = L + L.up = src // Don't waste effort looping the other way + L.update_icon() + break + if(!up) + for (var/obj/structure/ladder/L in locate(T.x, T.y, T.z + 1)) + up = L + L.down = src // Don't waste effort looping the other way + L.update_icon() + break + + update_icon() + +/obj/structure/ladder/proc/disconnect() + if(up && up.down == src) + up.down = null + up.update_icon() + if(down && down.up == src) + down.up = null + down.update_icon() + up = down = null + +/obj/structure/ladder/update_icon() + if(up && down) + icon_state = "ladder11" + + else if(up) + icon_state = "ladder10" + + else if(down) + icon_state = "ladder01" + + else //wtf make your ladders properly assholes + icon_state = "ladder00" + +/obj/structure/ladder/singularity_pull() + if(!(resistance_flags & INDESTRUCTIBLE)) + visible_message("[src] is torn to pieces by the gravitational pull!") + qdel(src) + +/obj/structure/ladder/proc/travel(going_up, mob/user, is_ghost, obj/structure/ladder/ladder) + if(!is_ghost) + show_fluff_message(going_up, user) + ladder.add_fingerprint(user) + + var/turf/T = get_turf(ladder) + var/atom/movable/AM + if(user.pulling) + AM = user.pulling + AM.forceMove(T) + user.forceMove(T) + if(AM) + user.start_pulling(AM) + +/obj/structure/ladder/proc/use(mob/user, is_ghost = FALSE) + if(!is_ghost && !in_range(src, user)) + return + + if(up && down) + var/result = alert("Go up or down [src]?", "[name]", "Up", "Down", "Cancel") + if (!is_ghost && !in_range(src, user)) + return // nice try + switch(result) + if("Up") + travel(TRUE, user, is_ghost, up) + if("Down") + travel(FALSE, user, is_ghost, down) + if("Cancel") + return + else if(up) + travel(TRUE, user, is_ghost, up) + else if(down) + travel(FALSE, user, is_ghost, down) + else + to_chat(user, "[src] doesn't seem to lead anywhere!") + + if(!is_ghost) + add_fingerprint(user) + +/obj/structure/ladder/attack_hand(mob/user) + use(user) + +/obj/structure/ladder/attackby(obj/item/W, mob/user, params) + return use(user) + +/obj/structure/ladder/attack_robot(mob/living/silicon/robot/R) + if(R.Adjacent(src)) + return use(R) + +//ATTACK GHOST IGNORING PARENT RETURN VALUE +/obj/structure/ladder/attack_ghost(mob/dead/observer/user) + use(user, TRUE) + +/obj/structure/ladder/proc/show_fluff_message(going_up, mob/user) + if(going_up) + user.visible_message("[user] climbs up [src].","You [use_verb] up [src].") + else + user.visible_message("[user] climbs down [src].","You [use_verb] down [src].") + + +// Indestructible away mission ladders which link based on a mapped ID and height value rather than X/Y/Z. +/obj/structure/ladder/unbreakable + name = "sturdy ladder" + desc = "An extremely sturdy metal ladder." + resistance_flags = INDESTRUCTIBLE + var/id + var/height = 0 // higher numbers are considered physically higher + +/obj/structure/ladder/unbreakable/Initialize(mapload) + GLOB.ladders += src + return ..() + +/obj/structure/ladder/unbreakable/Destroy() + . = ..() + if(. != QDEL_HINT_LETMELIVE) + GLOB.ladders -= src + +/obj/structure/ladder/unbreakable/LateInitialize() + // Override the parent to find ladders based on being height-linked + if(!id || (up && down)) + update_icon() + return + + for(var/O in GLOB.ladders) + var/obj/structure/ladder/unbreakable/L = O + if(L.id != id) + continue // not one of our pals + if(!down && L.height == height - 1) + down = L + L.up = src + L.update_icon() + if (up) + break // break if both our connections are filled + else if(!up && L.height == height + 1) + up = L + L.down = src + L.update_icon() + if (down) + break // break if both our connections are filled + + update_icon() + +/obj/structure/ladder/unbreakable/dive_point/buoy + name = "diving point buoy" + desc = "A buoy marking the location of an underwater dive area." + icon = 'icons/misc/beach.dmi' + icon_state = "buoy" + id = "dive" + height = 2 + use_verb = "swim" + layer = MOB_LAYER + 0.2 //0.1 higher than the water overlay, this also means people can "swim" behind/under it + +/obj/structure/ladder/unbreakable/dive_point/anchor + name = "diving point anchor" + desc = "An anchor tethered to the buoy at the surface, to keep the dive area marked." + icon = 'icons/misc/beach.dmi' + icon_state = "anchor" + id = "dive" + height = 1 + light_range = 5 + +/obj/structure/ladder/dive_point/Initialize(mapload) + . = ..() + set_light(light_range, light_power) //magical glowing anchor + +/obj/structure/ladder/unbreakable/dive_point/update_icon() + return diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm index a5fdff392b4..d84c1f39728 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -1,126 +1,126 @@ -/obj/structure/lattice - name = "lattice" - desc = "A lightweight support lattice." - icon = 'icons/obj/smooth_structures/lattice.dmi' - icon_state = "lattice" - density = FALSE - anchored = TRUE - armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - max_integrity = 50 - layer = LATTICE_LAYER //under pipes - plane = FLOOR_PLANE - var/number_of_rods = 1 - canSmoothWith = list(/obj/structure/lattice, - /turf/simulated/floor, - /turf/simulated/wall, - /obj/structure/falsewall) - smooth = SMOOTH_MORE - -/obj/structure/lattice/Initialize(mapload) - . = ..() - for(var/obj/structure/lattice/LAT in loc) - if(LAT != src) - QDEL_IN(LAT, 0) - -/obj/structure/lattice/examine(mob/user) - . = ..() - . += deconstruction_hints(user) - -/obj/structure/lattice/proc/deconstruction_hints(mob/user) - return "The rods look like they could be cut. There's space for more rods or a tile." - -/obj/structure/lattice/attackby(obj/item/C, mob/user, params) - if(resistance_flags & INDESTRUCTIBLE) - return - if(istype(C, /obj/item/wirecutters)) - var/obj/item/wirecutters/W = C - playsound(loc, W.usesound, 50, 1) - to_chat(user, "Slicing [name] joints...") - deconstruct() - else - var/turf/T = get_turf(src) - return T.attackby(C, user) //hand this off to the turf instead (for building plating, catwalks, etc) - -/obj/structure/lattice/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/rods(get_turf(src), number_of_rods) - qdel(src) - - -/obj/structure/lattice/blob_act(obj/structure/blob/B) - return - -/obj/structure/lattice/singularity_pull(S, current_size) - if(current_size >= STAGE_FOUR) - deconstruct() - -/obj/structure/lattice/clockwork - name = "cog lattice" - desc = "A lightweight support lattice. These hold the Justicar's station together." - icon = 'icons/obj/smooth_structures/lattice_clockwork.dmi' - -/obj/structure/lattice/clockwork/Initialize(mapload) - . = ..() - ratvar_act() - -/obj/structure/lattice/clockwork/ratvar_act() - if((x + y) % 2 != 0) - icon = 'icons/obj/smooth_structures/lattice_clockwork_large.dmi' - pixel_x = -9 - pixel_y = -9 - else - icon = 'icons/obj/smooth_structures/lattice_clockwork.dmi' - pixel_x = 0 - pixel_y = 0 - return TRUE - -/obj/structure/lattice/catwalk - name = "catwalk" - desc = "A catwalk for easier EVA maneuvering and cable placement." - icon = 'icons/obj/smooth_structures/catwalk.dmi' - icon_state = "catwalk" - number_of_rods = 2 - smooth = SMOOTH_TRUE - canSmoothWith = null - -/obj/structure/lattice/catwalk/deconstruction_hints(mob/user) - to_chat(user, "The supporting rods look like they could be cut.") - -/obj/structure/lattice/catwalk/Move() - var/turf/T = loc - for(var/obj/structure/cable/C in T) - C.deconstruct() - ..() - -/obj/structure/lattice/catwalk/deconstruct() - var/turf/T = loc - for(var/obj/structure/cable/C in T) - C.deconstruct() - ..() - -/obj/structure/lattice/catwalk/clockwork - name = "clockwork catwalk" - icon = 'icons/obj/smooth_structures/catwalk_clockwork.dmi' - canSmoothWith = list(/obj/structure/lattice, - /turf/simulated/floor, - /turf/simulated/wall, - /obj/structure/falsewall) - smooth = SMOOTH_MORE - -/obj/structure/lattice/catwalk/clockwork/Initialize(mapload) - . = ..() - ratvar_act() - if(!mapload) - new /obj/effect/temp_visual/ratvar/floor/catwalk(loc) - new /obj/effect/temp_visual/ratvar/beam/catwalk(loc) - -/obj/structure/lattice/catwalk/clockwork/ratvar_act() - if((x + y) % 2 != 0) - icon = 'icons/obj/smooth_structures/catwalk_clockwork_large.dmi' - pixel_x = -9 - pixel_y = -9 - else - icon = 'icons/obj/smooth_structures/catwalk_clockwork.dmi' - pixel_x = 0 - pixel_y = 0 - return TRUE +/obj/structure/lattice + name = "lattice" + desc = "A lightweight support lattice." + icon = 'icons/obj/smooth_structures/lattice.dmi' + icon_state = "lattice" + density = FALSE + anchored = TRUE + armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + max_integrity = 50 + layer = LATTICE_LAYER //under pipes + plane = FLOOR_PLANE + var/number_of_rods = 1 + canSmoothWith = list(/obj/structure/lattice, + /turf/simulated/floor, + /turf/simulated/wall, + /obj/structure/falsewall) + smooth = SMOOTH_MORE + +/obj/structure/lattice/Initialize(mapload) + . = ..() + for(var/obj/structure/lattice/LAT in loc) + if(LAT != src) + QDEL_IN(LAT, 0) + +/obj/structure/lattice/examine(mob/user) + . = ..() + . += deconstruction_hints(user) + +/obj/structure/lattice/proc/deconstruction_hints(mob/user) + return "The rods look like they could be cut. There's space for more rods or a tile." + +/obj/structure/lattice/attackby(obj/item/C, mob/user, params) + if(resistance_flags & INDESTRUCTIBLE) + return + if(istype(C, /obj/item/wirecutters)) + var/obj/item/wirecutters/W = C + playsound(loc, W.usesound, 50, 1) + to_chat(user, "Slicing [name] joints...") + deconstruct() + else + var/turf/T = get_turf(src) + return T.attackby(C, user) //hand this off to the turf instead (for building plating, catwalks, etc) + +/obj/structure/lattice/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/rods(get_turf(src), number_of_rods) + qdel(src) + + +/obj/structure/lattice/blob_act(obj/structure/blob/B) + return + +/obj/structure/lattice/singularity_pull(S, current_size) + if(current_size >= STAGE_FOUR) + deconstruct() + +/obj/structure/lattice/clockwork + name = "cog lattice" + desc = "A lightweight support lattice. These hold the Justicar's station together." + icon = 'icons/obj/smooth_structures/lattice_clockwork.dmi' + +/obj/structure/lattice/clockwork/Initialize(mapload) + . = ..() + ratvar_act() + +/obj/structure/lattice/clockwork/ratvar_act() + if((x + y) % 2 != 0) + icon = 'icons/obj/smooth_structures/lattice_clockwork_large.dmi' + pixel_x = -9 + pixel_y = -9 + else + icon = 'icons/obj/smooth_structures/lattice_clockwork.dmi' + pixel_x = 0 + pixel_y = 0 + return TRUE + +/obj/structure/lattice/catwalk + name = "catwalk" + desc = "A catwalk for easier EVA maneuvering and cable placement." + icon = 'icons/obj/smooth_structures/catwalk.dmi' + icon_state = "catwalk" + number_of_rods = 2 + smooth = SMOOTH_TRUE + canSmoothWith = null + +/obj/structure/lattice/catwalk/deconstruction_hints(mob/user) + to_chat(user, "The supporting rods look like they could be cut.") + +/obj/structure/lattice/catwalk/Move() + var/turf/T = loc + for(var/obj/structure/cable/C in T) + C.deconstruct() + ..() + +/obj/structure/lattice/catwalk/deconstruct() + var/turf/T = loc + for(var/obj/structure/cable/C in T) + C.deconstruct() + ..() + +/obj/structure/lattice/catwalk/clockwork + name = "clockwork catwalk" + icon = 'icons/obj/smooth_structures/catwalk_clockwork.dmi' + canSmoothWith = list(/obj/structure/lattice, + /turf/simulated/floor, + /turf/simulated/wall, + /obj/structure/falsewall) + smooth = SMOOTH_MORE + +/obj/structure/lattice/catwalk/clockwork/Initialize(mapload) + . = ..() + ratvar_act() + if(!mapload) + new /obj/effect/temp_visual/ratvar/floor/catwalk(loc) + new /obj/effect/temp_visual/ratvar/beam/catwalk(loc) + +/obj/structure/lattice/catwalk/clockwork/ratvar_act() + if((x + y) % 2 != 0) + icon = 'icons/obj/smooth_structures/catwalk_clockwork_large.dmi' + pixel_x = -9 + pixel_y = -9 + else + icon = 'icons/obj/smooth_structures/catwalk_clockwork.dmi' + pixel_x = 0 + pixel_y = 0 + return TRUE diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm index f906bb30a34..f9717956bb0 100644 --- a/code/game/objects/structures/lavaland/necropolis_tendril.dm +++ b/code/game/objects/structures/lavaland/necropolis_tendril.dm @@ -97,4 +97,4 @@ GLOBAL_LIST_INIT(tendrils, list()) for(var/turf/T in range(2,src)) if(!T.density) T.TerraformTurf(/turf/simulated/floor/chasm/straight_down/lava_land_surface) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/game/objects/structures/loom.dm b/code/game/objects/structures/loom.dm index 26bd4136c2a..d0bcd07f92f 100644 --- a/code/game/objects/structures/loom.dm +++ b/code/game/objects/structures/loom.dm @@ -35,4 +35,4 @@ user.show_message("You weave \the [W.name] into a workable fabric.", 1) return TRUE -#undef FABRIC_PER_SHEET \ No newline at end of file +#undef FABRIC_PER_SHEET diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm index c51400fc7d3..8e704fb83f7 100644 --- a/code/game/objects/structures/mineral_doors.dm +++ b/code/game/objects/structures/mineral_doors.dm @@ -1,236 +1,236 @@ -//NOT using the existing /obj/machinery/door type, since that has some complications on its own, mainly based on its machineryness -/obj/structure/mineral_door - name = "metal door" - density = 1 - anchored = 1 - opacity = 1 - - icon = 'icons/obj/doors/mineral_doors.dmi' - icon_state = "metal" - max_integrity = 200 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) - var/initial_state - var/state = 0 //closed, 1 == open - var/isSwitchingStates = 0 - var/close_delay = -1 //-1 if does not auto close. - - var/hardness = 1 - var/sheetType = /obj/item/stack/sheet/metal - var/sheetAmount = 7 - var/openSound = 'sound/effects/stonedoor_openclose.ogg' - var/closeSound = 'sound/effects/stonedoor_openclose.ogg' - var/damageSound = null - -/obj/structure/mineral_door/New(location) - ..() - initial_state = icon_state - -/obj/structure/mineral_door/Initialize() - ..() - air_update_turf(1) - -/obj/structure/mineral_door/Destroy() - density = 0 - air_update_turf(1) - return ..() - -/obj/structure/mineral_door/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/structure/mineral_door/Bumped(atom/user) - ..() - if(!state) - return TryToSwitchState(user) - -/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral - if(isAI(user)) //so the AI can't open it - return - else if(isrobot(user) && Adjacent(user)) //but cyborgs can, but not remotely - return TryToSwitchState(user) - -/obj/structure/mineral_door/attack_hand(mob/user) - return TryToSwitchState(user) - -/obj/structure/mineral_door/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - SwitchState() - -/obj/structure/mineral_door/CanPass(atom/movable/mover, turf/target, height = 0) - if(istype(mover, /obj/effect/beam)) - return !opacity - return !density - -/obj/structure/mineral_door/CanAtmosPass(turf/T) - return !density - -/obj/structure/mineral_door/proc/TryToSwitchState(atom/user) - if(isSwitchingStates) - return - if(isliving(user)) - var/mob/living/M = user - if(world.time - user.last_bumped <= 60) - return //NOTE do we really need that? - if(M.client) - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(!C.handcuffed) - SwitchState() - else - SwitchState() - else if(istype(user, /obj/mecha)) - SwitchState() - -/obj/structure/mineral_door/proc/SwitchState() - if(state) - Close() - else - Open() - -/obj/structure/mineral_door/proc/Open() - isSwitchingStates = 1 - playsound(loc, openSound, 100, 1) - flick("[initial_state]opening",src) - sleep(10) - density = 0 - opacity = 0 - state = 1 - air_update_turf(1) - update_icon() - isSwitchingStates = 0 - - if(close_delay != -1) - spawn(close_delay) - Close() - -/obj/structure/mineral_door/proc/Close() - if(isSwitchingStates || state != 1) - return - var/turf/T = get_turf(src) - for(var/mob/living/L in T) - return - isSwitchingStates = 1 - playsound(loc, closeSound, 100, 1) - flick("[initial_state]closing",src) - sleep(10) - density = 1 - opacity = 1 - state = 0 - air_update_turf(1) - update_icon() - isSwitchingStates = 0 - -/obj/structure/mineral_door/update_icon() - if(state) - icon_state = "[initial_state]open" - else - icon_state = initial_state - -/obj/structure/mineral_door/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pickaxe)) - var/obj/item/pickaxe/digTool = W - to_chat(user, "You start digging \the [src].") - if(do_after(user, 40 * digTool.toolspeed * hardness, target = src) && src) - to_chat(user, "You finished digging.") - deconstruct(TRUE) - else if(user.a_intent != INTENT_HARM) - attack_hand(user) - else - return ..() - -/obj/structure/mineral_door/deconstruct(disassembled = TRUE) - var/turf/T = get_turf(src) - if(sheetType) - if(disassembled) - new sheetType(T, sheetAmount) - else - new sheetType(T, max(sheetAmount - 2, 1)) - qdel(src) - -/obj/structure/mineral_door/iron - max_integrity = 300 - -/obj/structure/mineral_door/silver - name = "silver door" - icon_state = "silver" - sheetType = /obj/item/stack/sheet/mineral/silver - max_integrity = 300 - -/obj/structure/mineral_door/gold - name = "gold door" - icon_state = "gold" - sheetType = /obj/item/stack/sheet/mineral/gold - -/obj/structure/mineral_door/uranium - name = "uranium door" - icon_state = "uranium" - sheetType = /obj/item/stack/sheet/mineral/uranium - max_integrity = 300 - light_range = 2 - -/obj/structure/mineral_door/sandstone - name = "sandstone door" - icon_state = "sandstone" - sheetType = /obj/item/stack/sheet/mineral/sandstone - max_integrity = 100 - -/obj/structure/mineral_door/transparent - opacity = 0 - -/obj/structure/mineral_door/transparent/Close() - ..() - set_opacity(0) - -/obj/structure/mineral_door/transparent/plasma - name = "plasma door" - icon_state = "plasma" - sheetType = /obj/item/stack/sheet/mineral/plasma - -/obj/structure/mineral_door/transparent/plasma/attackby(obj/item/W, mob/user) - if(is_hot(W)) - message_admins("Plasma mineral door ignited by [key_name_admin(user)] in ([x], [y], [z] - JMP)", 0, 1) - log_game("Plasma mineral door ignited by [key_name(user)] in ([x], [y], [z])") - investigate_log("was ignited by [key_name(user)]","atmos") - TemperatureAct(100) - else - return ..() - -/obj/structure/mineral_door/transparent/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > 300) - TemperatureAct(exposed_temperature) - -/obj/structure/mineral_door/transparent/plasma/proc/TemperatureAct(temperature) - atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, 500) - deconstruct(FALSE) - -/obj/structure/mineral_door/transparent/diamond - name = "diamond door" - icon_state = "diamond" - sheetType = /obj/item/stack/sheet/mineral/diamond - max_integrity = 1000 - -/obj/structure/mineral_door/wood - name = "wood door" - icon_state = "wood" - openSound = 'sound/effects/doorcreaky.ogg' - closeSound = 'sound/effects/doorcreaky.ogg' - sheetType = /obj/item/stack/sheet/wood - hardness = 1 - resistance_flags = FLAMMABLE - max_integrity = 200 - -/obj/structure/mineral_door/resin - name = "resin door" - icon_state = "resin" - hardness = 1.5 - close_delay = 100 - openSound = 'sound/effects/attackblob.ogg' - closeSound = 'sound/effects/attackblob.ogg' - damageSound = 'sound/effects/attackblob.ogg' - sheetType = null - -/obj/structure/mineral_door/resin/TryToSwitchState(atom/user) - if(isalien(user)) - return ..() +//NOT using the existing /obj/machinery/door type, since that has some complications on its own, mainly based on its machineryness +/obj/structure/mineral_door + name = "metal door" + density = 1 + anchored = 1 + opacity = 1 + + icon = 'icons/obj/doors/mineral_doors.dmi' + icon_state = "metal" + max_integrity = 200 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) + var/initial_state + var/state = 0 //closed, 1 == open + var/isSwitchingStates = 0 + var/close_delay = -1 //-1 if does not auto close. + + var/hardness = 1 + var/sheetType = /obj/item/stack/sheet/metal + var/sheetAmount = 7 + var/openSound = 'sound/effects/stonedoor_openclose.ogg' + var/closeSound = 'sound/effects/stonedoor_openclose.ogg' + var/damageSound = null + +/obj/structure/mineral_door/New(location) + ..() + initial_state = icon_state + +/obj/structure/mineral_door/Initialize() + ..() + air_update_turf(1) + +/obj/structure/mineral_door/Destroy() + density = 0 + air_update_turf(1) + return ..() + +/obj/structure/mineral_door/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/structure/mineral_door/Bumped(atom/user) + ..() + if(!state) + return TryToSwitchState(user) + +/obj/structure/mineral_door/attack_ai(mob/user) //those aren't machinery, they're just big fucking slabs of a mineral + if(isAI(user)) //so the AI can't open it + return + else if(isrobot(user) && Adjacent(user)) //but cyborgs can, but not remotely + return TryToSwitchState(user) + +/obj/structure/mineral_door/attack_hand(mob/user) + return TryToSwitchState(user) + +/obj/structure/mineral_door/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + SwitchState() + +/obj/structure/mineral_door/CanPass(atom/movable/mover, turf/target, height = 0) + if(istype(mover, /obj/effect/beam)) + return !opacity + return !density + +/obj/structure/mineral_door/CanAtmosPass(turf/T) + return !density + +/obj/structure/mineral_door/proc/TryToSwitchState(atom/user) + if(isSwitchingStates) + return + if(isliving(user)) + var/mob/living/M = user + if(world.time - user.last_bumped <= 60) + return //NOTE do we really need that? + if(M.client) + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(!C.handcuffed) + SwitchState() + else + SwitchState() + else if(istype(user, /obj/mecha)) + SwitchState() + +/obj/structure/mineral_door/proc/SwitchState() + if(state) + Close() + else + Open() + +/obj/structure/mineral_door/proc/Open() + isSwitchingStates = 1 + playsound(loc, openSound, 100, 1) + flick("[initial_state]opening",src) + sleep(10) + density = 0 + opacity = 0 + state = 1 + air_update_turf(1) + update_icon() + isSwitchingStates = 0 + + if(close_delay != -1) + spawn(close_delay) + Close() + +/obj/structure/mineral_door/proc/Close() + if(isSwitchingStates || state != 1) + return + var/turf/T = get_turf(src) + for(var/mob/living/L in T) + return + isSwitchingStates = 1 + playsound(loc, closeSound, 100, 1) + flick("[initial_state]closing",src) + sleep(10) + density = 1 + opacity = 1 + state = 0 + air_update_turf(1) + update_icon() + isSwitchingStates = 0 + +/obj/structure/mineral_door/update_icon() + if(state) + icon_state = "[initial_state]open" + else + icon_state = initial_state + +/obj/structure/mineral_door/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pickaxe)) + var/obj/item/pickaxe/digTool = W + to_chat(user, "You start digging \the [src].") + if(do_after(user, 40 * digTool.toolspeed * hardness, target = src) && src) + to_chat(user, "You finished digging.") + deconstruct(TRUE) + else if(user.a_intent != INTENT_HARM) + attack_hand(user) + else + return ..() + +/obj/structure/mineral_door/deconstruct(disassembled = TRUE) + var/turf/T = get_turf(src) + if(sheetType) + if(disassembled) + new sheetType(T, sheetAmount) + else + new sheetType(T, max(sheetAmount - 2, 1)) + qdel(src) + +/obj/structure/mineral_door/iron + max_integrity = 300 + +/obj/structure/mineral_door/silver + name = "silver door" + icon_state = "silver" + sheetType = /obj/item/stack/sheet/mineral/silver + max_integrity = 300 + +/obj/structure/mineral_door/gold + name = "gold door" + icon_state = "gold" + sheetType = /obj/item/stack/sheet/mineral/gold + +/obj/structure/mineral_door/uranium + name = "uranium door" + icon_state = "uranium" + sheetType = /obj/item/stack/sheet/mineral/uranium + max_integrity = 300 + light_range = 2 + +/obj/structure/mineral_door/sandstone + name = "sandstone door" + icon_state = "sandstone" + sheetType = /obj/item/stack/sheet/mineral/sandstone + max_integrity = 100 + +/obj/structure/mineral_door/transparent + opacity = 0 + +/obj/structure/mineral_door/transparent/Close() + ..() + set_opacity(0) + +/obj/structure/mineral_door/transparent/plasma + name = "plasma door" + icon_state = "plasma" + sheetType = /obj/item/stack/sheet/mineral/plasma + +/obj/structure/mineral_door/transparent/plasma/attackby(obj/item/W, mob/user) + if(is_hot(W)) + message_admins("Plasma mineral door ignited by [key_name_admin(user)] in ([x], [y], [z] - JMP)", 0, 1) + log_game("Plasma mineral door ignited by [key_name(user)] in ([x], [y], [z])") + investigate_log("was ignited by [key_name(user)]","atmos") + TemperatureAct(100) + else + return ..() + +/obj/structure/mineral_door/transparent/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > 300) + TemperatureAct(exposed_temperature) + +/obj/structure/mineral_door/transparent/plasma/proc/TemperatureAct(temperature) + atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, 500) + deconstruct(FALSE) + +/obj/structure/mineral_door/transparent/diamond + name = "diamond door" + icon_state = "diamond" + sheetType = /obj/item/stack/sheet/mineral/diamond + max_integrity = 1000 + +/obj/structure/mineral_door/wood + name = "wood door" + icon_state = "wood" + openSound = 'sound/effects/doorcreaky.ogg' + closeSound = 'sound/effects/doorcreaky.ogg' + sheetType = /obj/item/stack/sheet/wood + hardness = 1 + resistance_flags = FLAMMABLE + max_integrity = 200 + +/obj/structure/mineral_door/resin + name = "resin door" + icon_state = "resin" + hardness = 1.5 + close_delay = 100 + openSound = 'sound/effects/attackblob.ogg' + closeSound = 'sound/effects/attackblob.ogg' + damageSound = 'sound/effects/attackblob.ogg' + sheetType = null + +/obj/structure/mineral_door/resin/TryToSwitchState(atom/user) + if(isalien(user)) + return ..() diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 6a34c39a94d..592e67e466d 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -1,163 +1,163 @@ -//wip wip wup -/obj/structure/mirror - name = "mirror" - desc = "Mirror mirror on the wall, who's the most robust of them all?" - icon = 'icons/obj/watercloset.dmi' - icon_state = "mirror" - density = 0 - anchored = 1 - max_integrity = 200 - integrity_failure = 100 - var/list/ui_users = list() - -/obj/structure/mirror/New(turf/T, newdir = SOUTH, building = FALSE) - ..() - if(building) - switch(newdir) - if(NORTH) - pixel_y = -32 - if(SOUTH) - pixel_y = 32 - if(EAST) - pixel_x = -32 - if(WEST) - pixel_x = 32 - -/obj/structure/mirror/attack_hand(mob/user) - if(broken) - return - - if(ishuman(user)) - var/datum/nano_module/appearance_changer/AC = ui_users[user] - if(!AC) - AC = new(src, user) - AC.name = "SalonPro Nano-Mirror™" - AC.flags = APPEARANCE_ALL_BODY - ui_users[user] = AC - AC.ui_interact(user) - -/obj/structure/mirror/obj_break(damage_flag, mapload) - if(!broken && !(flags & NODECONSTRUCT)) - icon_state = "mirror_broke" - if(!mapload) - playsound(src, "shatter", 70, TRUE) - if(desc == initial(desc)) - desc = "Oh no, seven years of bad luck!" - broken = TRUE - -/obj/structure/mirror/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - user.visible_message("[user] begins to unfasten [src].", "You begin to unfasten [src].") - if(!I.use_tool(src, user, 30, volume = I.tool_volume)) - return - if(broken) - user.visible_message("[user] drops the broken shards to the floor.", "You drop the broken shards on the floor.") - new /obj/item/shard(get_turf(user)) - else - user.visible_message("[user] carefully places [src] on the floor.", "You carefully place [src] on the floor.") - new /obj/item/mounted/mirror(get_turf(user)) - qdel(src) - -/obj/structure/mirror/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(!disassembled) - new /obj/item/shard( src.loc ) - qdel(src) - -/obj/structure/mirror/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) - if(BURN) - playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) - - -/obj/item/mounted/mirror - name = "mirror" - desc = "Some reflective glass ready to be hung on a wall. Don't break it!" - icon = 'icons/obj/watercloset.dmi' - icon_state = "mirror" - -/obj/item/mounted/mirror/do_build(turf/on_wall, mob/user) - var/obj/structure/mirror/M = new /obj/structure/mirror(get_turf(user), get_dir(on_wall, user), 1) - transfer_prints_to(M, TRUE) - qdel(src) - -/obj/structure/mirror/magic - name = "magic mirror" - icon_state = "magic_mirror" - -/obj/structure/mirror/magic/attack_hand(mob/user) - if(!ishuman(user) || broken) - return - - var/mob/living/carbon/human/H = user - var/choice = input(user, "Something to change?", "Magical Grooming") as null|anything in list("Name", "Body", "Voice") - - switch(choice) - if("Name") - var/newname = copytext(sanitize(input(H, "Who are we again?", "Name change", H.name) as null|text),1,MAX_NAME_LEN) - - if(!newname) - return - H.real_name = newname - H.name = newname - if(H.dna) - H.dna.real_name = newname - if(H.mind) - H.mind.name = newname - - if(newname) - curse(user) - - if("Body") - var/list/race_list = list("Human", "Tajaran", "Skrell", "Unathi", "Diona", "Vulpkanin") - if(config.usealienwhitelist) - for(var/Spec in GLOB.whitelisted_species) - if(is_alien_whitelisted(H, Spec)) - race_list += Spec - else - race_list += GLOB.whitelisted_species - - var/datum/nano_module/appearance_changer/AC = ui_users[user] - if(!AC) - AC = new(src, user) - AC.name = "Magic Mirror" - AC.flags = APPEARANCE_ALL - AC.whitelist = race_list - ui_users[user] = AC - AC.ui_interact(user) - - if("Voice") - var/voice_choice = input(user, "Perhaps...", "Voice effects") as null|anything in list("Comic Sans", "Wingdings", "Swedish", "Chav") - var/voice_mutation - switch(voice_choice) - if("Comic Sans") - voice_mutation = COMICBLOCK - if("Wingdings") - voice_mutation = WINGDINGSBLOCK - if("Swedish") - voice_mutation = SWEDEBLOCK - if("Chav") - voice_mutation = CHAVBLOCK - if(voice_mutation) - if(H.dna.GetSEState(voice_mutation)) - H.dna.SetSEState(voice_mutation, FALSE) - genemutcheck(H, voice_mutation, null, MUTCHK_FORCED) - else - H.dna.SetSEState(voice_mutation, TRUE) - genemutcheck(H, voice_mutation, null, MUTCHK_FORCED) - - if(voice_choice) - curse(user) - -/obj/structure/mirror/magic/on_ui_close(mob/user) - curse(user) - -/obj/structure/mirror/magic/attackby(obj/item/I, mob/living/user, params) - return - -/obj/structure/mirror/magic/proc/curse(mob/living/user) - return \ No newline at end of file +//wip wip wup +/obj/structure/mirror + name = "mirror" + desc = "Mirror mirror on the wall, who's the most robust of them all?" + icon = 'icons/obj/watercloset.dmi' + icon_state = "mirror" + density = 0 + anchored = 1 + max_integrity = 200 + integrity_failure = 100 + var/list/ui_users = list() + +/obj/structure/mirror/New(turf/T, newdir = SOUTH, building = FALSE) + ..() + if(building) + switch(newdir) + if(NORTH) + pixel_y = -32 + if(SOUTH) + pixel_y = 32 + if(EAST) + pixel_x = -32 + if(WEST) + pixel_x = 32 + +/obj/structure/mirror/attack_hand(mob/user) + if(broken) + return + + if(ishuman(user)) + var/datum/nano_module/appearance_changer/AC = ui_users[user] + if(!AC) + AC = new(src, user) + AC.name = "SalonPro Nano-Mirror™" + AC.flags = APPEARANCE_ALL_BODY + ui_users[user] = AC + AC.ui_interact(user) + +/obj/structure/mirror/obj_break(damage_flag, mapload) + if(!broken && !(flags & NODECONSTRUCT)) + icon_state = "mirror_broke" + if(!mapload) + playsound(src, "shatter", 70, TRUE) + if(desc == initial(desc)) + desc = "Oh no, seven years of bad luck!" + broken = TRUE + +/obj/structure/mirror/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + user.visible_message("[user] begins to unfasten [src].", "You begin to unfasten [src].") + if(!I.use_tool(src, user, 30, volume = I.tool_volume)) + return + if(broken) + user.visible_message("[user] drops the broken shards to the floor.", "You drop the broken shards on the floor.") + new /obj/item/shard(get_turf(user)) + else + user.visible_message("[user] carefully places [src] on the floor.", "You carefully place [src] on the floor.") + new /obj/item/mounted/mirror(get_turf(user)) + qdel(src) + +/obj/structure/mirror/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(!disassembled) + new /obj/item/shard( src.loc ) + qdel(src) + +/obj/structure/mirror/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) + if(BURN) + playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) + + +/obj/item/mounted/mirror + name = "mirror" + desc = "Some reflective glass ready to be hung on a wall. Don't break it!" + icon = 'icons/obj/watercloset.dmi' + icon_state = "mirror" + +/obj/item/mounted/mirror/do_build(turf/on_wall, mob/user) + var/obj/structure/mirror/M = new /obj/structure/mirror(get_turf(user), get_dir(on_wall, user), 1) + transfer_prints_to(M, TRUE) + qdel(src) + +/obj/structure/mirror/magic + name = "magic mirror" + icon_state = "magic_mirror" + +/obj/structure/mirror/magic/attack_hand(mob/user) + if(!ishuman(user) || broken) + return + + var/mob/living/carbon/human/H = user + var/choice = input(user, "Something to change?", "Magical Grooming") as null|anything in list("Name", "Body", "Voice") + + switch(choice) + if("Name") + var/newname = copytext(sanitize(input(H, "Who are we again?", "Name change", H.name) as null|text),1,MAX_NAME_LEN) + + if(!newname) + return + H.real_name = newname + H.name = newname + if(H.dna) + H.dna.real_name = newname + if(H.mind) + H.mind.name = newname + + if(newname) + curse(user) + + if("Body") + var/list/race_list = list("Human", "Tajaran", "Skrell", "Unathi", "Diona", "Vulpkanin") + if(config.usealienwhitelist) + for(var/Spec in GLOB.whitelisted_species) + if(is_alien_whitelisted(H, Spec)) + race_list += Spec + else + race_list += GLOB.whitelisted_species + + var/datum/nano_module/appearance_changer/AC = ui_users[user] + if(!AC) + AC = new(src, user) + AC.name = "Magic Mirror" + AC.flags = APPEARANCE_ALL + AC.whitelist = race_list + ui_users[user] = AC + AC.ui_interact(user) + + if("Voice") + var/voice_choice = input(user, "Perhaps...", "Voice effects") as null|anything in list("Comic Sans", "Wingdings", "Swedish", "Chav") + var/voice_mutation + switch(voice_choice) + if("Comic Sans") + voice_mutation = COMICBLOCK + if("Wingdings") + voice_mutation = WINGDINGSBLOCK + if("Swedish") + voice_mutation = SWEDEBLOCK + if("Chav") + voice_mutation = CHAVBLOCK + if(voice_mutation) + if(H.dna.GetSEState(voice_mutation)) + H.dna.SetSEState(voice_mutation, FALSE) + genemutcheck(H, voice_mutation, null, MUTCHK_FORCED) + else + H.dna.SetSEState(voice_mutation, TRUE) + genemutcheck(H, voice_mutation, null, MUTCHK_FORCED) + + if(voice_choice) + curse(user) + +/obj/structure/mirror/magic/on_ui_close(mob/user) + curse(user) + +/obj/structure/mirror/magic/attackby(obj/item/I, mob/living/user, params) + return + +/obj/structure/mirror/magic/proc/curse(mob/living/user) + return diff --git a/code/game/objects/structures/misc.dm b/code/game/objects/structures/misc.dm index b6da99bf6dc..1497344d5c8 100644 --- a/code/game/objects/structures/misc.dm +++ b/code/game/objects/structures/misc.dm @@ -120,4 +120,4 @@ icon_state = "boulder1" density = TRUE opacity = TRUE - anchored = TRUE \ No newline at end of file + anchored = TRUE diff --git a/code/game/objects/structures/mop_bucket.dm b/code/game/objects/structures/mop_bucket.dm index 05c6a66c733..9f56f3dea25 100644 --- a/code/game/objects/structures/mop_bucket.dm +++ b/code/game/objects/structures/mop_bucket.dm @@ -1,37 +1,37 @@ -/obj/structure/mopbucket - desc = "Fill it with water, but don't forget a mop!" - name = "mop bucket" - icon = 'icons/obj/janitor.dmi' - icon_state = "mopbucket" - density = 1 - container_type = OPENCONTAINER - var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite - -/obj/structure/mopbucket/New() - ..() - create_reagents(100) - GLOB.janitorial_equipment += src - -/obj/structure/mopbucket/full/New() - ..() - reagents.add_reagent("water", 100) - -/obj/structure/mopbucket/Destroy() - GLOB.janitorial_equipment -= src - return ..() - -/obj/structure/mopbucket/examine(mob/user) - . = ..() - if(in_range(user, src)) - . += "[bicon(src)] [src] contains [reagents.total_volume] units of water left!" - -/obj/structure/mopbucket/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/mop)) - if(src.reagents.total_volume >= 2) - src.reagents.trans_to(W, 2) - to_chat(user, "You wet the mop") - playsound(src.loc, 'sound/effects/slosh.ogg', 25, 1) - if(src.reagents.total_volume < 1) - to_chat(user, "Out of water!") - return - return ..() \ No newline at end of file +/obj/structure/mopbucket + desc = "Fill it with water, but don't forget a mop!" + name = "mop bucket" + icon = 'icons/obj/janitor.dmi' + icon_state = "mopbucket" + density = 1 + container_type = OPENCONTAINER + var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite + +/obj/structure/mopbucket/New() + ..() + create_reagents(100) + GLOB.janitorial_equipment += src + +/obj/structure/mopbucket/full/New() + ..() + reagents.add_reagent("water", 100) + +/obj/structure/mopbucket/Destroy() + GLOB.janitorial_equipment -= src + return ..() + +/obj/structure/mopbucket/examine(mob/user) + . = ..() + if(in_range(user, src)) + . += "[bicon(src)] [src] contains [reagents.total_volume] units of water left!" + +/obj/structure/mopbucket/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/mop)) + if(src.reagents.total_volume >= 2) + src.reagents.trans_to(W, 2) + to_chat(user, "You wet the mop") + playsound(src.loc, 'sound/effects/slosh.ogg', 25, 1) + if(src.reagents.total_volume < 1) + to_chat(user, "Out of water!") + return + return ..() diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index ddcef6b2396..1ea1c277af8 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -1,491 +1,491 @@ -/* Morgue stuff - * Contains: - * Morgue - * Morgue trays - * Creamatorium - * Creamatorium trays - */ - -/* - * Morgue - */ - -/obj/structure/morgue - name = "morgue" - desc = "Used to keep bodies in until someone fetches them." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "morgue1" - density = 1 - max_integrity = 400 - dir = EAST - var/obj/structure/m_tray/connected = null - var/list/status_descriptors = list( - "The tray is currently extended.", - "The tray is currently empty.", - "The tray contains an unviable body.", - "The tray contains a body that is responsive to revival techniques.", - "The tray contains something that is not a body.", - "The tray contains a body that might be responsive." - ) - anchored = 1.0 - var/open_sound = 'sound/items/deconstruct.ogg' - -/obj/structure/morgue/Initialize() - . = ..() - update() - -/obj/structure/morgue/proc/update() - if(connected) - icon_state = "morgue0" - desc = initial(desc) + "\n[status_descriptors[1]]" - else - if(contents.len) - - var/mob/living/M = locate() in contents - - var/obj/structure/closet/body_bag/B = locate() in contents - if(M==null) M = locate() in B - - if(M) - var/mob/dead/observer/G = M.get_ghost() - - if(M.client) - icon_state = "morgue3" - desc = initial(desc) + "\n[status_descriptors[4]]" - else if(G && G.client) //There is a ghost and it is connected to the server - icon_state = "morgue5" - desc = initial(desc) + "\n[status_descriptors[6]]" - else - icon_state = "morgue2" - desc = initial(desc) + "\n[status_descriptors[3]]" - - - else - icon_state = "morgue4" - desc = initial(desc) + "\n[status_descriptors[5]]" - else - icon_state = "morgue1" - desc = initial(desc) + "\n[status_descriptors[2]]" - return - - -/obj/structure/morgue/ex_act(severity) - switch(severity) - if(1.0) - for(var/atom/movable/A as mob|obj in src) - A.forceMove(loc) - ex_act(severity) - qdel(src) - return - if(2.0) - if(prob(50)) - for(var/atom/movable/A as mob|obj in src) - A.forceMove(loc) - ex_act(severity) - qdel(src) - return - if(3.0) - if(prob(5)) - for(var/atom/movable/A as mob|obj in src) - A.forceMove(loc) - ex_act(severity) - qdel(src) - return - return - -/obj/structure/morgue/attack_hand(mob/user as mob) - if(connected) - for(var/atom/movable/A as mob|obj in connected.loc) - if(!( A.anchored )) - A.forceMove(src) - playsound(loc, open_sound, 50, 1) - QDEL_NULL(connected) - else - playsound(loc, open_sound, 50, 1) - connected = new /obj/structure/m_tray( loc ) - step(connected, dir) - connected.layer = OBJ_LAYER - var/turf/T = get_step(src, dir) - if(T.contents.Find(connected)) - connected.connected = src - icon_state = "morgue0" - for(var/atom/movable/A as mob|obj in src) - A.forceMove(connected.loc) - connected.icon_state = "morguet" - connected.dir = dir - else - QDEL_NULL(connected) - add_fingerprint(user) - update() - return - -/obj/structure/morgue/attackby(P as obj, mob/user as mob, params) - if(istype(P, /obj/item/pen)) - var/t = input(user, "What would you like the label to be?", text("[]", name), null) as text - if(user.get_active_hand() != P) - return - if((!in_range(src, usr) && loc != user)) - return - t = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) - if(t) - name = text("Morgue- '[]'", t) - overlays += image(icon, "morgue_label") - else - name = "Morgue" - overlays.Cut() - add_fingerprint(user) - return - return ..() - -/obj/structure/morgue/relaymove(mob/user as mob) - if(user.stat) - return - connected = new /obj/structure/m_tray( loc ) - step(connected, dir) - connected.layer = OBJ_LAYER - var/turf/T = get_step(src, dir) - if(T.contents.Find(connected)) - connected.connected = src - icon_state = "morgue0" - for(var/atom/movable/A as mob|obj in src) - A.forceMove(connected.loc) - connected.icon_state = "morguet" - else - QDEL_NULL(connected) - return - -/obj/structure/morgue/Destroy() - QDEL_NULL(connected) - return ..() - -/obj/structure/morgue/container_resist(var/mob/living/L) - var/mob/living/carbon/CM = L - if(!istype(CM)) - return - if(CM.stat || CM.restrained()) - return - - to_chat(CM, "You attempt to slide yourself out of \the [src]...") - src.attack_hand(CM) - - -/obj/structure/morgue/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) - -/* - * Morgue tray - */ -/obj/structure/m_tray - name = "morgue tray" - desc = "Apply corpse before closing. May float away in no-gravity." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "morguet" - density = 1 - layer = 2.0 - var/obj/structure/morgue/connected = null - anchored = 1.0 - pass_flags = LETPASSTHROW - max_integrity = 350 - - -/obj/structure/m_tray/attack_hand(mob/user as mob) - if(connected) - for(var/atom/movable/A as mob|obj in loc) - if(!( A.anchored )) - A.forceMove(connected) - connected.connected = null - connected.update() - add_fingerprint(user) - qdel(src) - return - return - -/obj/structure/m_tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) - if((!( istype(O, /atom/movable) ) || O.anchored || get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src) || user.contents.Find(O))) - return - if(!ismob(O) && !istype(O, /obj/structure/closet/body_bag)) - return - if(!ismob(user) || user.stat || user.lying || user.stunned) - return - O.forceMove(loc) - if(user != O) - user.visible_message("[user] stuffs [O] into [src]!") - return - -/obj/structure/m_tray/Destroy() - if(connected && connected.connected == src) - connected.connected = null - connected = null - return ..() - -/obj/structure/tray/m_tray/CanPass(atom/movable/mover, turf/target, height=0) - if(height == 0) - return 1 - - if(istype(mover) && mover.checkpass(PASSTABLE)) - return 1 - if(locate(/obj/structure/table) in get_turf(mover)) - return 1 - else - return 0 - -/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(caller)) - var/atom/movable/mover = caller - . = . || mover.checkpass(PASSTABLE) - -/* - * Crematorium - */ - -/obj/structure/crematorium - name = "crematorium" - desc = "A human incinerator. Works well on barbeque nights." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "crema1" - density = 1 - var/obj/structure/c_tray/connected = null - anchored = 1.0 - var/cremating = 0 - var/id = 1 - var/locked = 0 - var/open_sound = 'sound/items/deconstruct.ogg' - -/obj/structure/crematorium/proc/update() - if(connected) - icon_state = "crema0" - else - if(contents.len) - icon_state = "crema2" - else - icon_state = "crema1" - return - -/obj/structure/crematorium/ex_act(severity) - switch(severity) - if(1.0) - for(var/atom/movable/A as mob|obj in src) - A.forceMove(loc) - ex_act(severity) - qdel(src) - return - if(2.0) - if(prob(50)) - for(var/atom/movable/A as mob|obj in src) - A.forceMove(loc) - ex_act(severity) - qdel(src) - return - if(3.0) - if(prob(5)) - for(var/atom/movable/A as mob|obj in src) - A.forceMove(loc) - ex_act(severity) - qdel(src) - return - return - -/obj/structure/crematorium/attack_hand(mob/user as mob) - if(cremating) - to_chat(usr, "It's locked.") - return - if((connected) && (locked == 0)) - for(var/atom/movable/A as mob|obj in connected.loc) - if(!( A.anchored )) - A.forceMove(src) - playsound(loc, open_sound, 50, 1) - QDEL_NULL(connected) - else if(locked == 0) - playsound(loc, open_sound, 50, 1) - connected = new /obj/structure/c_tray( loc ) - step(connected, SOUTH) - connected.layer = OBJ_LAYER - var/turf/T = get_step(src, SOUTH) - if(T.contents.Find(connected)) - connected.connected = src - icon_state = "crema0" - for(var/atom/movable/A as mob|obj in src) - A.forceMove(connected.loc) - connected.icon_state = "cremat" - else - QDEL_NULL(connected) - add_fingerprint(user) - update() - -/obj/structure/crematorium/attackby(P as obj, mob/user as mob, params) - if(istype(P, /obj/item/pen)) - var/t = input(user, "What would you like the label to be?", text("[]", name), null) as text - if(user.get_active_hand() != P) - return - if((!in_range(src, usr) > 1 && loc != user)) - return - t = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) - if(t) - name = text("Crematorium- '[]'", t) - else - name = "Crematorium" - add_fingerprint(user) - return - return ..() - -/obj/structure/crematorium/relaymove(mob/user as mob) - if(user.stat || locked) - return - connected = new /obj/structure/c_tray( loc ) - step(connected, SOUTH) - connected.layer = OBJ_LAYER - var/turf/T = get_step(src, SOUTH) - if(T.contents.Find(connected)) - connected.connected = src - icon_state = "crema0" - for(var/atom/movable/A as mob|obj in src) - A.forceMove(connected.loc) - connected.icon_state = "cremat" - else - QDEL_NULL(connected) - return - -/obj/structure/crematorium/proc/cremate(mob/user as mob) - if(cremating) - return //don't let you cremate something twice or w/e - - if(contents.len <= 0) - for(var/mob/M in viewers(src)) - M.show_message("You hear a hollow crackle.", 1) - return - - else - for(var/mob/M in viewers(src)) - M.show_message("You hear a roar as the crematorium activates.", 1) - - cremating = 1 - locked = 1 - icon_state = "crema_active" - - for(var/mob/living/M in search_contents_for(/mob/living)) - if(QDELETED(M)) - continue - if(M.stat!=2) - M.emote("scream") - if(istype(user)) - add_attack_logs(user, M, "Cremated") - M.death(1) - if(QDELETED(M)) - continue // Re-check for mobs that delete themselves on death - M.ghostize() - qdel(M) - - for(var/obj/O in contents) //obj instead of obj/item so that bodybags and ashes get destroyed. We dont want tons and tons of ash piling up - qdel(O) - - new /obj/effect/decal/cleanable/ash(src) - sleep(30) - cremating = 0 - locked = 0 - update() - playsound(loc, 'sound/machines/ding.ogg', 50, 1) - return - -/obj/structure/crematorium/Destroy() - QDEL_NULL(connected) - return ..() - -/obj/structure/crematorium/container_resist(var/mob/living/L) - var/mob/living/carbon/CM = L - if(!istype(CM)) - return - if(CM.stat || CM.restrained()) - return - - to_chat(CM, "You attempt to slide yourself out of \the [src]...") - src.attack_hand(CM) - -/obj/structure/crematorium/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) - -/* - * Crematorium tray - */ -/obj/structure/c_tray - name = "crematorium tray" - desc = "Apply body before burning." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "cremat" - density = 1 - layer = 2.0 - var/obj/structure/crematorium/connected = null - anchored = 1.0 - pass_flags = LETPASSTHROW - -/obj/structure/c_tray/attack_hand(mob/user as mob) - if(connected) - for(var/atom/movable/A as mob|obj in loc) - if(!( A.anchored )) - A.forceMove(connected) - //Foreach goto(26) - connected.connected = null - connected.update() - add_fingerprint(user) - qdel(src) - return - return - -/obj/structure/c_tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) - if((!( istype(O, /atom/movable) ) || O.anchored || get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src) || user.contents.Find(O))) - return - if(!ismob(O) && !istype(O, /obj/structure/closet/body_bag)) - return - if(!ismob(user) || user.stat || user.lying || user.stunned) - return - O.forceMove(loc) - if(user != O) - user.visible_message("[user] stuffs [O] into [src]!") - //Foreach goto(99) - return - -/obj/structure/c_tray/Destroy() - if(connected && connected.connected == src) - connected.connected = null - connected = null - return ..() - -// Crematorium switch -/obj/machinery/crema_switch - desc = "Burn baby burn!" - name = "crematorium igniter" - icon = 'icons/obj/power.dmi' - icon_state = "crema_switch" - anchored = 1.0 - req_access = list(ACCESS_CREMATORIUM) - var/on = 0 - var/area/area = null - var/otherarea = null - var/id = 1 - -/obj/machinery/crema_switch/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - return attack_hand(user) - -/obj/machinery/crema_switch/attack_hand(mob/user) - if(allowed(usr) || user.can_advanced_admin_interact()) - for(var/obj/structure/crematorium/C in world) - if(C.id == id) - if(!C.cremating) - C.cremate(user) - else - to_chat(usr, "Access denied.") - -/mob/proc/update_morgue() - if(stat == DEAD) - var/obj/structure/morgue/morgue - var/mob/living/C = src - var/mob/dead/observer/G = src - if(istype(G) && G.can_reenter_corpse && G.mind) //We're a ghost, let's find our corpse - C = G.mind.current - if(istype(C)) //We found our corpse, is it inside a morgue? - morgue = get(C.loc, /obj/structure/morgue) - if(morgue) - morgue.update() \ No newline at end of file +/* Morgue stuff + * Contains: + * Morgue + * Morgue trays + * Creamatorium + * Creamatorium trays + */ + +/* + * Morgue + */ + +/obj/structure/morgue + name = "morgue" + desc = "Used to keep bodies in until someone fetches them." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "morgue1" + density = 1 + max_integrity = 400 + dir = EAST + var/obj/structure/m_tray/connected = null + var/list/status_descriptors = list( + "The tray is currently extended.", + "The tray is currently empty.", + "The tray contains an unviable body.", + "The tray contains a body that is responsive to revival techniques.", + "The tray contains something that is not a body.", + "The tray contains a body that might be responsive." + ) + anchored = 1.0 + var/open_sound = 'sound/items/deconstruct.ogg' + +/obj/structure/morgue/Initialize() + . = ..() + update() + +/obj/structure/morgue/proc/update() + if(connected) + icon_state = "morgue0" + desc = initial(desc) + "\n[status_descriptors[1]]" + else + if(contents.len) + + var/mob/living/M = locate() in contents + + var/obj/structure/closet/body_bag/B = locate() in contents + if(M==null) M = locate() in B + + if(M) + var/mob/dead/observer/G = M.get_ghost() + + if(M.client) + icon_state = "morgue3" + desc = initial(desc) + "\n[status_descriptors[4]]" + else if(G && G.client) //There is a ghost and it is connected to the server + icon_state = "morgue5" + desc = initial(desc) + "\n[status_descriptors[6]]" + else + icon_state = "morgue2" + desc = initial(desc) + "\n[status_descriptors[3]]" + + + else + icon_state = "morgue4" + desc = initial(desc) + "\n[status_descriptors[5]]" + else + icon_state = "morgue1" + desc = initial(desc) + "\n[status_descriptors[2]]" + return + + +/obj/structure/morgue/ex_act(severity) + switch(severity) + if(1.0) + for(var/atom/movable/A as mob|obj in src) + A.forceMove(loc) + ex_act(severity) + qdel(src) + return + if(2.0) + if(prob(50)) + for(var/atom/movable/A as mob|obj in src) + A.forceMove(loc) + ex_act(severity) + qdel(src) + return + if(3.0) + if(prob(5)) + for(var/atom/movable/A as mob|obj in src) + A.forceMove(loc) + ex_act(severity) + qdel(src) + return + return + +/obj/structure/morgue/attack_hand(mob/user as mob) + if(connected) + for(var/atom/movable/A as mob|obj in connected.loc) + if(!( A.anchored )) + A.forceMove(src) + playsound(loc, open_sound, 50, 1) + QDEL_NULL(connected) + else + playsound(loc, open_sound, 50, 1) + connected = new /obj/structure/m_tray( loc ) + step(connected, dir) + connected.layer = OBJ_LAYER + var/turf/T = get_step(src, dir) + if(T.contents.Find(connected)) + connected.connected = src + icon_state = "morgue0" + for(var/atom/movable/A as mob|obj in src) + A.forceMove(connected.loc) + connected.icon_state = "morguet" + connected.dir = dir + else + QDEL_NULL(connected) + add_fingerprint(user) + update() + return + +/obj/structure/morgue/attackby(P as obj, mob/user as mob, params) + if(istype(P, /obj/item/pen)) + var/t = input(user, "What would you like the label to be?", text("[]", name), null) as text + if(user.get_active_hand() != P) + return + if((!in_range(src, usr) && loc != user)) + return + t = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + if(t) + name = text("Morgue- '[]'", t) + overlays += image(icon, "morgue_label") + else + name = "Morgue" + overlays.Cut() + add_fingerprint(user) + return + return ..() + +/obj/structure/morgue/relaymove(mob/user as mob) + if(user.stat) + return + connected = new /obj/structure/m_tray( loc ) + step(connected, dir) + connected.layer = OBJ_LAYER + var/turf/T = get_step(src, dir) + if(T.contents.Find(connected)) + connected.connected = src + icon_state = "morgue0" + for(var/atom/movable/A as mob|obj in src) + A.forceMove(connected.loc) + connected.icon_state = "morguet" + else + QDEL_NULL(connected) + return + +/obj/structure/morgue/Destroy() + QDEL_NULL(connected) + return ..() + +/obj/structure/morgue/container_resist(var/mob/living/L) + var/mob/living/carbon/CM = L + if(!istype(CM)) + return + if(CM.stat || CM.restrained()) + return + + to_chat(CM, "You attempt to slide yourself out of \the [src]...") + src.attack_hand(CM) + + +/obj/structure/morgue/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) + +/* + * Morgue tray + */ +/obj/structure/m_tray + name = "morgue tray" + desc = "Apply corpse before closing. May float away in no-gravity." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "morguet" + density = 1 + layer = 2.0 + var/obj/structure/morgue/connected = null + anchored = 1.0 + pass_flags = LETPASSTHROW + max_integrity = 350 + + +/obj/structure/m_tray/attack_hand(mob/user as mob) + if(connected) + for(var/atom/movable/A as mob|obj in loc) + if(!( A.anchored )) + A.forceMove(connected) + connected.connected = null + connected.update() + add_fingerprint(user) + qdel(src) + return + return + +/obj/structure/m_tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) + if((!( istype(O, /atom/movable) ) || O.anchored || get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src) || user.contents.Find(O))) + return + if(!ismob(O) && !istype(O, /obj/structure/closet/body_bag)) + return + if(!ismob(user) || user.stat || user.lying || user.stunned) + return + O.forceMove(loc) + if(user != O) + user.visible_message("[user] stuffs [O] into [src]!") + return + +/obj/structure/m_tray/Destroy() + if(connected && connected.connected == src) + connected.connected = null + connected = null + return ..() + +/obj/structure/tray/m_tray/CanPass(atom/movable/mover, turf/target, height=0) + if(height == 0) + return 1 + + if(istype(mover) && mover.checkpass(PASSTABLE)) + return 1 + if(locate(/obj/structure/table) in get_turf(mover)) + return 1 + else + return 0 + +/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSTABLE) + +/* + * Crematorium + */ + +/obj/structure/crematorium + name = "crematorium" + desc = "A human incinerator. Works well on barbeque nights." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "crema1" + density = 1 + var/obj/structure/c_tray/connected = null + anchored = 1.0 + var/cremating = 0 + var/id = 1 + var/locked = 0 + var/open_sound = 'sound/items/deconstruct.ogg' + +/obj/structure/crematorium/proc/update() + if(connected) + icon_state = "crema0" + else + if(contents.len) + icon_state = "crema2" + else + icon_state = "crema1" + return + +/obj/structure/crematorium/ex_act(severity) + switch(severity) + if(1.0) + for(var/atom/movable/A as mob|obj in src) + A.forceMove(loc) + ex_act(severity) + qdel(src) + return + if(2.0) + if(prob(50)) + for(var/atom/movable/A as mob|obj in src) + A.forceMove(loc) + ex_act(severity) + qdel(src) + return + if(3.0) + if(prob(5)) + for(var/atom/movable/A as mob|obj in src) + A.forceMove(loc) + ex_act(severity) + qdel(src) + return + return + +/obj/structure/crematorium/attack_hand(mob/user as mob) + if(cremating) + to_chat(usr, "It's locked.") + return + if((connected) && (locked == 0)) + for(var/atom/movable/A as mob|obj in connected.loc) + if(!( A.anchored )) + A.forceMove(src) + playsound(loc, open_sound, 50, 1) + QDEL_NULL(connected) + else if(locked == 0) + playsound(loc, open_sound, 50, 1) + connected = new /obj/structure/c_tray( loc ) + step(connected, SOUTH) + connected.layer = OBJ_LAYER + var/turf/T = get_step(src, SOUTH) + if(T.contents.Find(connected)) + connected.connected = src + icon_state = "crema0" + for(var/atom/movable/A as mob|obj in src) + A.forceMove(connected.loc) + connected.icon_state = "cremat" + else + QDEL_NULL(connected) + add_fingerprint(user) + update() + +/obj/structure/crematorium/attackby(P as obj, mob/user as mob, params) + if(istype(P, /obj/item/pen)) + var/t = input(user, "What would you like the label to be?", text("[]", name), null) as text + if(user.get_active_hand() != P) + return + if((!in_range(src, usr) > 1 && loc != user)) + return + t = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + if(t) + name = text("Crematorium- '[]'", t) + else + name = "Crematorium" + add_fingerprint(user) + return + return ..() + +/obj/structure/crematorium/relaymove(mob/user as mob) + if(user.stat || locked) + return + connected = new /obj/structure/c_tray( loc ) + step(connected, SOUTH) + connected.layer = OBJ_LAYER + var/turf/T = get_step(src, SOUTH) + if(T.contents.Find(connected)) + connected.connected = src + icon_state = "crema0" + for(var/atom/movable/A as mob|obj in src) + A.forceMove(connected.loc) + connected.icon_state = "cremat" + else + QDEL_NULL(connected) + return + +/obj/structure/crematorium/proc/cremate(mob/user as mob) + if(cremating) + return //don't let you cremate something twice or w/e + + if(contents.len <= 0) + for(var/mob/M in viewers(src)) + M.show_message("You hear a hollow crackle.", 1) + return + + else + for(var/mob/M in viewers(src)) + M.show_message("You hear a roar as the crematorium activates.", 1) + + cremating = 1 + locked = 1 + icon_state = "crema_active" + + for(var/mob/living/M in search_contents_for(/mob/living)) + if(QDELETED(M)) + continue + if(M.stat!=2) + M.emote("scream") + if(istype(user)) + add_attack_logs(user, M, "Cremated") + M.death(1) + if(QDELETED(M)) + continue // Re-check for mobs that delete themselves on death + M.ghostize() + qdel(M) + + for(var/obj/O in contents) //obj instead of obj/item so that bodybags and ashes get destroyed. We dont want tons and tons of ash piling up + qdel(O) + + new /obj/effect/decal/cleanable/ash(src) + sleep(30) + cremating = 0 + locked = 0 + update() + playsound(loc, 'sound/machines/ding.ogg', 50, 1) + return + +/obj/structure/crematorium/Destroy() + QDEL_NULL(connected) + return ..() + +/obj/structure/crematorium/container_resist(var/mob/living/L) + var/mob/living/carbon/CM = L + if(!istype(CM)) + return + if(CM.stat || CM.restrained()) + return + + to_chat(CM, "You attempt to slide yourself out of \the [src]...") + src.attack_hand(CM) + +/obj/structure/crematorium/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) + +/* + * Crematorium tray + */ +/obj/structure/c_tray + name = "crematorium tray" + desc = "Apply body before burning." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "cremat" + density = 1 + layer = 2.0 + var/obj/structure/crematorium/connected = null + anchored = 1.0 + pass_flags = LETPASSTHROW + +/obj/structure/c_tray/attack_hand(mob/user as mob) + if(connected) + for(var/atom/movable/A as mob|obj in loc) + if(!( A.anchored )) + A.forceMove(connected) + //Foreach goto(26) + connected.connected = null + connected.update() + add_fingerprint(user) + qdel(src) + return + return + +/obj/structure/c_tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user as mob) + if((!( istype(O, /atom/movable) ) || O.anchored || get_dist(user, src) > 1 || get_dist(user, O) > 1 || user.contents.Find(src) || user.contents.Find(O))) + return + if(!ismob(O) && !istype(O, /obj/structure/closet/body_bag)) + return + if(!ismob(user) || user.stat || user.lying || user.stunned) + return + O.forceMove(loc) + if(user != O) + user.visible_message("[user] stuffs [O] into [src]!") + //Foreach goto(99) + return + +/obj/structure/c_tray/Destroy() + if(connected && connected.connected == src) + connected.connected = null + connected = null + return ..() + +// Crematorium switch +/obj/machinery/crema_switch + desc = "Burn baby burn!" + name = "crematorium igniter" + icon = 'icons/obj/power.dmi' + icon_state = "crema_switch" + anchored = 1.0 + req_access = list(ACCESS_CREMATORIUM) + var/on = 0 + var/area/area = null + var/otherarea = null + var/id = 1 + +/obj/machinery/crema_switch/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + return attack_hand(user) + +/obj/machinery/crema_switch/attack_hand(mob/user) + if(allowed(usr) || user.can_advanced_admin_interact()) + for(var/obj/structure/crematorium/C in world) + if(C.id == id) + if(!C.cremating) + C.cremate(user) + else + to_chat(usr, "Access denied.") + +/mob/proc/update_morgue() + if(stat == DEAD) + var/obj/structure/morgue/morgue + var/mob/living/C = src + var/mob/dead/observer/G = src + if(istype(G) && G.can_reenter_corpse && G.mind) //We're a ghost, let's find our corpse + C = G.mind.current + if(istype(C)) //We found our corpse, is it inside a morgue? + morgue = get(C.loc, /obj/structure/morgue) + if(morgue) + morgue.update() diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm index 8c025b63a97..7458c0a4b3b 100644 --- a/code/game/objects/structures/musician.dm +++ b/code/game/objects/structures/musician.dm @@ -1,341 +1,341 @@ - - -/datum/song - var/name = "Untitled" - var/list/lines = new() - var/tempo = 5 // delay between notes - - var/playing = 0 // if we're playing - var/help = 0 // if help is open - var/repeat = 0 // number of times remaining to repeat - var/max_repeat = 10 // maximum times we can repeat - - var/instrumentDir = "piano" // the folder with the sounds - var/instrumentExt = "ogg" // the file extension - var/obj/instrumentObj = null // the associated obj playing the sound - -/datum/song/New(dir, obj, ext = "ogg") - tempo = sanitize_tempo(tempo) - instrumentDir = dir - instrumentObj = obj - instrumentExt = ext - -/datum/song/Destroy() - instrumentObj = null - return ..() - -// note is a number from 1-7 for A-G -// acc is either "b", "n", or "#" -// oct is 1-8 (or 9 for C) -/datum/song/proc/playnote(note, acc as text, oct) - // handle accidental -> B<>C of E<>F - if(acc == "b" && (note == 3 || note == 6)) // C or F - if(note == 3) - oct-- - note-- - acc = "n" - else if(acc == "#" && (note == 2 || note == 5)) // B or E - if(note == 2) - oct++ - note++ - acc = "n" - else if(acc == "#" && (note == 7)) //G# - note = 1 - acc = "b" - else if(acc == "#") // mass convert all sharps to flats, octave jump already handled - acc = "b" - note++ - - // check octave, C is allowed to go to 9 - if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) - return - - // now generate name - var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]" - soundfile = file(soundfile) - // make sure the note exists - if(!fexists(soundfile)) - return - // and play - var/turf/source = get_turf(instrumentObj) - var/sound/music_played = sound(soundfile) - for(var/A in hearers(15, source)) - var/mob/M = A - if(!M.client || !(M.client.prefs.sound & SOUND_INSTRUMENTS)) - continue - M.playsound_local(source, null, 100, falloff = 5, S = music_played) - -/datum/song/proc/shouldStopPlaying(mob/user) - if(instrumentObj) - //if(!user.canUseTopic(instrumentObj)) - //return 1 - return !instrumentObj.anchored // add special cases to stop in subclasses - else - return 1 - -/datum/song/proc/playsong(mob/user) - while(repeat >= 0) - var/cur_oct[7] - var/cur_acc[7] - for(var/i = 1 to 7) - cur_oct[i] = 3 - cur_acc[i] = "n" - - for(var/line in lines) - for(var/beat in splittext(lowertext(line), ",")) - var/list/notes = splittext(beat, "/") - for(var/note in splittext(notes[1], "-")) - if(!playing || shouldStopPlaying(user)) //If the instrument is playing, or special case - playing = 0 - return - if(length(note) == 0) - continue - var/cur_note = text2ascii(note) - 96 - if(cur_note < 1 || cur_note > 7) - continue - for(var/i=2 to length(note)) - var/ni = copytext(note,i,i+1) - if(!text2num(ni)) - if(ni == "#" || ni == "b" || ni == "n") - cur_acc[cur_note] = ni - else if(ni == "s") - cur_acc[cur_note] = "#" // so shift is never required - else - cur_oct[cur_note] = text2num(ni) - playnote(cur_note, cur_acc[cur_note], cur_oct[cur_note]) - if(notes.len >= 2 && text2num(notes[2])) - sleep(sanitize_tempo(tempo / text2num(notes[2]))) - else - sleep(tempo) - repeat-- - playing = 0 - repeat = 0 - -/datum/song/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(!instrumentObj) - return - - ui = SSnanoui.try_update_ui(user, instrumentObj, ui_key, ui, force_open) - if(!ui) - ui = new(user, instrumentObj, ui_key, "song.tmpl", instrumentObj.name, 700, 500) - ui.open() - ui.set_auto_update(1) - -/datum/song/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - data["lines"] = lines - data["tempo"] = tempo - - data["playing"] = playing - data["help"] = help - data["repeat"] = repeat - data["maxRepeat"] = max_repeat - data["minTempo"] = world.tick_lag - data["maxTempo"] = 600 - - return data - -/datum/song/Topic(href, href_list) - if(!in_range(instrumentObj, usr) || (issilicon(usr) && instrumentObj.loc != usr) || !isliving(usr) || usr.incapacitated()) - usr << browse(null, "window=instrument") - usr.unset_machine() - return 1 - - instrumentObj.add_fingerprint(usr) - - if(href_list["newsong"]) - playing = 0 - lines = new() - tempo = sanitize_tempo(5) // default 120 BPM - name = "" - SSnanoui.update_uis(src) - - else if(href_list["import"]) - playing = 0 - var/t = "" - do - t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) - if(!in_range(instrumentObj, usr)) - return - - if(length(t) >= 12000) - var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") - if(cont == "no") - break - while(length(t) > 12000) - - //split into lines - spawn() - lines = splittext(t, "\n") - if(lines.len == 0) - return 1 - if(copytext(lines[1],1,6) == "BPM: ") - tempo = sanitize_tempo(600 / text2num(copytext(lines[1],6))) - lines.Cut(1,2) - else - tempo = sanitize_tempo(5) // default 120 BPM - if(lines.len > 200) - to_chat(usr, "Too many lines!") - lines.Cut(201) - var/linenum = 1 - for(var/l in lines) - if(length(l) > 200) - to_chat(usr, "Line [linenum] too long!") - lines.Remove(l) - else - linenum++ - SSnanoui.update_uis(src) - - else if(href_list["help"]) - help = !help - SSnanoui.update_uis(src) - - if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops. - if(playing) - return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing. - repeat += round(text2num(href_list["repeat"])) - if(repeat < 0) - repeat = 0 - if(repeat > max_repeat) - repeat = max_repeat - SSnanoui.update_uis(src) - - else if(href_list["tempo"]) - tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]) * world.tick_lag) - SSnanoui.update_uis(src) - - else if(href_list["play"]) - if(playing) - return - playing = 1 - spawn() - playsong(usr) - SSnanoui.update_uis(src) - - else if(href_list["insertline"]) - var/num = round(text2num(href_list["insertline"])) - if(num < 1 || num > lines.len + 1) - return - - var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) - if(!newline || !in_range(instrumentObj, usr)) - return - if(lines.len > 200) - return - if(length(newline) > 200) - newline = copytext(newline, 1, 200) - - lines.Insert(num, newline) - SSnanoui.update_uis(src) - - else if(href_list["deleteline"]) - var/num = round(text2num(href_list["deleteline"])) - if(num > lines.len || num < 1) - return - lines.Cut(num, num + 1) - SSnanoui.update_uis(src) - - else if(href_list["modifyline"]) - var/num = round(text2num(href_list["modifyline"])) - var/content = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null) - if(!content || !in_range(instrumentObj, usr)) - return - if(length(content) > 200) - content = copytext(content, 1, 200) - if(num > lines.len || num < 1) - return - lines[num] = content - SSnanoui.update_uis(src) - - else if(href_list["stop"]) - playing = 0 - SSnanoui.update_uis(src) - -/datum/song/proc/sanitize_tempo(new_tempo) - new_tempo = abs(new_tempo) - return max(round(new_tempo, world.tick_lag), world.tick_lag) - -// subclass for handheld instruments, like violin -/datum/song/handheld - -/datum/song/handheld/shouldStopPlaying() - if(instrumentObj) - return !isliving(instrumentObj.loc) - else - return 1 - - -////////////////////////////////////////////////////////////////////////// - - -/obj/structure/piano - name = "space minimoog" - icon = 'icons/obj/musician.dmi' - icon_state = "minimoog" - anchored = 1 - density = 1 - var/datum/song/song - - -/obj/structure/piano/New() - ..() - song = new("piano", src) - - if(prob(50)) - name = "space minimoog" - desc = "This is a minimoog, like a space piano, but more spacey!" - icon_state = "minimoog" - else - name = "space piano" - desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." - icon_state = "piano" - -/obj/structure/piano/Destroy() - QDEL_NULL(song) - return ..() - -/obj/structure/piano/Initialize() - if(song) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - ..() - -/obj/structure/piano/attack_hand(mob/user as mob) - ui_interact(user) - -/obj/structure/piano/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(!isliving(user) || user.incapacitated() || !anchored) - return - - song.ui_interact(user, ui_key, ui, force_open) - -/obj/structure/piano/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - return song.ui_data(user, ui_key, state) - -/obj/structure/piano/Topic(href, href_list) - song.Topic(href, href_list) - -/obj/structure/piano/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(!anchored && !isinspace()) - WRENCH_ANCHOR_MESSAGE - if(!I.use_tool(src, user, 20, volume = I.tool_volume)) - return - user.visible_message( \ - "[user] tightens [src]'s casters.", \ - " You have tightened [src]'s casters. Now it can be played again.", \ - "You hear ratchet.") - anchored = TRUE - else if(anchored) - to_chat(user, " You begin to loosen [src]'s casters...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume)) - return - user.visible_message( \ - "[user] loosens [src]'s casters.", \ - " You have loosened [src]. Now it can be pulled somewhere else.", \ - "You hear ratchet.") - anchored = FALSE - else - to_chat(user, "[src] needs to be bolted to the floor!") + + +/datum/song + var/name = "Untitled" + var/list/lines = new() + var/tempo = 5 // delay between notes + + var/playing = 0 // if we're playing + var/help = 0 // if help is open + var/repeat = 0 // number of times remaining to repeat + var/max_repeat = 10 // maximum times we can repeat + + var/instrumentDir = "piano" // the folder with the sounds + var/instrumentExt = "ogg" // the file extension + var/obj/instrumentObj = null // the associated obj playing the sound + +/datum/song/New(dir, obj, ext = "ogg") + tempo = sanitize_tempo(tempo) + instrumentDir = dir + instrumentObj = obj + instrumentExt = ext + +/datum/song/Destroy() + instrumentObj = null + return ..() + +// note is a number from 1-7 for A-G +// acc is either "b", "n", or "#" +// oct is 1-8 (or 9 for C) +/datum/song/proc/playnote(note, acc as text, oct) + // handle accidental -> B<>C of E<>F + if(acc == "b" && (note == 3 || note == 6)) // C or F + if(note == 3) + oct-- + note-- + acc = "n" + else if(acc == "#" && (note == 2 || note == 5)) // B or E + if(note == 2) + oct++ + note++ + acc = "n" + else if(acc == "#" && (note == 7)) //G# + note = 1 + acc = "b" + else if(acc == "#") // mass convert all sharps to flats, octave jump already handled + acc = "b" + note++ + + // check octave, C is allowed to go to 9 + if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) + return + + // now generate name + var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]" + soundfile = file(soundfile) + // make sure the note exists + if(!fexists(soundfile)) + return + // and play + var/turf/source = get_turf(instrumentObj) + var/sound/music_played = sound(soundfile) + for(var/A in hearers(15, source)) + var/mob/M = A + if(!M.client || !(M.client.prefs.sound & SOUND_INSTRUMENTS)) + continue + M.playsound_local(source, null, 100, falloff = 5, S = music_played) + +/datum/song/proc/shouldStopPlaying(mob/user) + if(instrumentObj) + //if(!user.canUseTopic(instrumentObj)) + //return 1 + return !instrumentObj.anchored // add special cases to stop in subclasses + else + return 1 + +/datum/song/proc/playsong(mob/user) + while(repeat >= 0) + var/cur_oct[7] + var/cur_acc[7] + for(var/i = 1 to 7) + cur_oct[i] = 3 + cur_acc[i] = "n" + + for(var/line in lines) + for(var/beat in splittext(lowertext(line), ",")) + var/list/notes = splittext(beat, "/") + for(var/note in splittext(notes[1], "-")) + if(!playing || shouldStopPlaying(user)) //If the instrument is playing, or special case + playing = 0 + return + if(length(note) == 0) + continue + var/cur_note = text2ascii(note) - 96 + if(cur_note < 1 || cur_note > 7) + continue + for(var/i=2 to length(note)) + var/ni = copytext(note,i,i+1) + if(!text2num(ni)) + if(ni == "#" || ni == "b" || ni == "n") + cur_acc[cur_note] = ni + else if(ni == "s") + cur_acc[cur_note] = "#" // so shift is never required + else + cur_oct[cur_note] = text2num(ni) + playnote(cur_note, cur_acc[cur_note], cur_oct[cur_note]) + if(notes.len >= 2 && text2num(notes[2])) + sleep(sanitize_tempo(tempo / text2num(notes[2]))) + else + sleep(tempo) + repeat-- + playing = 0 + repeat = 0 + +/datum/song/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(!instrumentObj) + return + + ui = SSnanoui.try_update_ui(user, instrumentObj, ui_key, ui, force_open) + if(!ui) + ui = new(user, instrumentObj, ui_key, "song.tmpl", instrumentObj.name, 700, 500) + ui.open() + ui.set_auto_update(1) + +/datum/song/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + data["lines"] = lines + data["tempo"] = tempo + + data["playing"] = playing + data["help"] = help + data["repeat"] = repeat + data["maxRepeat"] = max_repeat + data["minTempo"] = world.tick_lag + data["maxTempo"] = 600 + + return data + +/datum/song/Topic(href, href_list) + if(!in_range(instrumentObj, usr) || (issilicon(usr) && instrumentObj.loc != usr) || !isliving(usr) || usr.incapacitated()) + usr << browse(null, "window=instrument") + usr.unset_machine() + return 1 + + instrumentObj.add_fingerprint(usr) + + if(href_list["newsong"]) + playing = 0 + lines = new() + tempo = sanitize_tempo(5) // default 120 BPM + name = "" + SSnanoui.update_uis(src) + + else if(href_list["import"]) + playing = 0 + var/t = "" + do + t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) + if(!in_range(instrumentObj, usr)) + return + + if(length(t) >= 12000) + var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") + if(cont == "no") + break + while(length(t) > 12000) + + //split into lines + spawn() + lines = splittext(t, "\n") + if(lines.len == 0) + return 1 + if(copytext(lines[1],1,6) == "BPM: ") + tempo = sanitize_tempo(600 / text2num(copytext(lines[1],6))) + lines.Cut(1,2) + else + tempo = sanitize_tempo(5) // default 120 BPM + if(lines.len > 200) + to_chat(usr, "Too many lines!") + lines.Cut(201) + var/linenum = 1 + for(var/l in lines) + if(length(l) > 200) + to_chat(usr, "Line [linenum] too long!") + lines.Remove(l) + else + linenum++ + SSnanoui.update_uis(src) + + else if(href_list["help"]) + help = !help + SSnanoui.update_uis(src) + + if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops. + if(playing) + return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing. + repeat += round(text2num(href_list["repeat"])) + if(repeat < 0) + repeat = 0 + if(repeat > max_repeat) + repeat = max_repeat + SSnanoui.update_uis(src) + + else if(href_list["tempo"]) + tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]) * world.tick_lag) + SSnanoui.update_uis(src) + + else if(href_list["play"]) + if(playing) + return + playing = 1 + spawn() + playsong(usr) + SSnanoui.update_uis(src) + + else if(href_list["insertline"]) + var/num = round(text2num(href_list["insertline"])) + if(num < 1 || num > lines.len + 1) + return + + var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) + if(!newline || !in_range(instrumentObj, usr)) + return + if(lines.len > 200) + return + if(length(newline) > 200) + newline = copytext(newline, 1, 200) + + lines.Insert(num, newline) + SSnanoui.update_uis(src) + + else if(href_list["deleteline"]) + var/num = round(text2num(href_list["deleteline"])) + if(num > lines.len || num < 1) + return + lines.Cut(num, num + 1) + SSnanoui.update_uis(src) + + else if(href_list["modifyline"]) + var/num = round(text2num(href_list["modifyline"])) + var/content = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null) + if(!content || !in_range(instrumentObj, usr)) + return + if(length(content) > 200) + content = copytext(content, 1, 200) + if(num > lines.len || num < 1) + return + lines[num] = content + SSnanoui.update_uis(src) + + else if(href_list["stop"]) + playing = 0 + SSnanoui.update_uis(src) + +/datum/song/proc/sanitize_tempo(new_tempo) + new_tempo = abs(new_tempo) + return max(round(new_tempo, world.tick_lag), world.tick_lag) + +// subclass for handheld instruments, like violin +/datum/song/handheld + +/datum/song/handheld/shouldStopPlaying() + if(instrumentObj) + return !isliving(instrumentObj.loc) + else + return 1 + + +////////////////////////////////////////////////////////////////////////// + + +/obj/structure/piano + name = "space minimoog" + icon = 'icons/obj/musician.dmi' + icon_state = "minimoog" + anchored = 1 + density = 1 + var/datum/song/song + + +/obj/structure/piano/New() + ..() + song = new("piano", src) + + if(prob(50)) + name = "space minimoog" + desc = "This is a minimoog, like a space piano, but more spacey!" + icon_state = "minimoog" + else + name = "space piano" + desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." + icon_state = "piano" + +/obj/structure/piano/Destroy() + QDEL_NULL(song) + return ..() + +/obj/structure/piano/Initialize() + if(song) + song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded + ..() + +/obj/structure/piano/attack_hand(mob/user as mob) + ui_interact(user) + +/obj/structure/piano/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(!isliving(user) || user.incapacitated() || !anchored) + return + + song.ui_interact(user, ui_key, ui, force_open) + +/obj/structure/piano/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + return song.ui_data(user, ui_key, state) + +/obj/structure/piano/Topic(href, href_list) + song.Topic(href, href_list) + +/obj/structure/piano/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(!anchored && !isinspace()) + WRENCH_ANCHOR_MESSAGE + if(!I.use_tool(src, user, 20, volume = I.tool_volume)) + return + user.visible_message( \ + "[user] tightens [src]'s casters.", \ + " You have tightened [src]'s casters. Now it can be played again.", \ + "You hear ratchet.") + anchored = TRUE + else if(anchored) + to_chat(user, " You begin to loosen [src]'s casters...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume)) + return + user.visible_message( \ + "[user] loosens [src]'s casters.", \ + " You have loosened [src]. Now it can be pulled somewhere else.", \ + "You hear ratchet.") + anchored = FALSE + else + to_chat(user, "[src] needs to be bolted to the floor!") diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm index 5938a52c1d5..6b9615cd994 100644 --- a/code/game/objects/structures/noticeboard.dm +++ b/code/game/objects/structures/noticeboard.dm @@ -1,82 +1,82 @@ -/obj/structure/noticeboard - name = "notice board" - desc = "A board for pinning important notices upon." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "nboard00" - density = 0 - anchored = 1 - max_integrity = 150 - var/notices = 0 - -/obj/structure/noticeboard/Initialize() - ..() - for(var/obj/item/I in loc) - if(notices > 4) break - if(istype(I, /obj/item/paper)) - I.loc = src - notices++ - icon_state = "nboard0[notices]" - -//attaching papers!! -/obj/structure/noticeboard/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - if(istype(O, /obj/item/paper)) - if(notices < 5) - O.add_fingerprint(user) - add_fingerprint(user) - user.drop_item() - O.loc = src - notices++ - icon_state = "nboard0[notices]" //update sprite - to_chat(user, "You pin the paper to the noticeboard.") - else - to_chat(user, "You reach to pin your paper to the board but hesitate. You are certain your paper will not be seen among the many others already attached.") - return - return ..() - -/obj/structure/noticeboard/attack_hand(user as mob) - var/dat = "Noticeboard
        " - for(var/obj/item/paper/P in src) - dat += "[P.name] Write Remove
        " - user << browse("Notices[dat]","window=noticeboard") - onclose(user, "noticeboard") - -/obj/structure/noticeboard/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/sheet/metal (loc, 1) - qdel(src) - -/obj/structure/noticeboard/Topic(href, href_list) - ..() - usr.set_machine(src) - if(href_list["remove"]) - if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open - return - var/obj/item/P = locate(href_list["remove"]) - if((P && P.loc == src)) - P.loc = get_turf(src) //dump paper on the floor because you're a clumsy fuck - P.add_fingerprint(usr) - add_fingerprint(usr) - notices-- - icon_state = "nboard0[notices]" - - if(href_list["write"]) - if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open - return - var/obj/item/P = locate(href_list["write"]) - - if((P && P.loc == src)) //ifthe paper's on the board - if(istype(usr.r_hand, /obj/item/pen)) //and you're holding a pen - add_fingerprint(usr) - P.attackby(usr.r_hand, usr) //then do ittttt - else - if(istype(usr.l_hand, /obj/item/pen)) //check other hand for pen - add_fingerprint(usr) - P.attackby(usr.l_hand, usr) - else - to_chat(usr, "You'll need something to write with!") - - if(href_list["read"]) - var/obj/item/paper/P = locate(href_list["read"]) - if((P && P.loc == src)) - P.show_content(usr) - return +/obj/structure/noticeboard + name = "notice board" + desc = "A board for pinning important notices upon." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "nboard00" + density = 0 + anchored = 1 + max_integrity = 150 + var/notices = 0 + +/obj/structure/noticeboard/Initialize() + ..() + for(var/obj/item/I in loc) + if(notices > 4) break + if(istype(I, /obj/item/paper)) + I.loc = src + notices++ + icon_state = "nboard0[notices]" + +//attaching papers!! +/obj/structure/noticeboard/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + if(istype(O, /obj/item/paper)) + if(notices < 5) + O.add_fingerprint(user) + add_fingerprint(user) + user.drop_item() + O.loc = src + notices++ + icon_state = "nboard0[notices]" //update sprite + to_chat(user, "You pin the paper to the noticeboard.") + else + to_chat(user, "You reach to pin your paper to the board but hesitate. You are certain your paper will not be seen among the many others already attached.") + return + return ..() + +/obj/structure/noticeboard/attack_hand(user as mob) + var/dat = "Noticeboard
        " + for(var/obj/item/paper/P in src) + dat += "[P.name] Write Remove
        " + user << browse("Notices[dat]","window=noticeboard") + onclose(user, "noticeboard") + +/obj/structure/noticeboard/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/sheet/metal (loc, 1) + qdel(src) + +/obj/structure/noticeboard/Topic(href, href_list) + ..() + usr.set_machine(src) + if(href_list["remove"]) + if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open + return + var/obj/item/P = locate(href_list["remove"]) + if((P && P.loc == src)) + P.loc = get_turf(src) //dump paper on the floor because you're a clumsy fuck + P.add_fingerprint(usr) + add_fingerprint(usr) + notices-- + icon_state = "nboard0[notices]" + + if(href_list["write"]) + if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open + return + var/obj/item/P = locate(href_list["write"]) + + if((P && P.loc == src)) //ifthe paper's on the board + if(istype(usr.r_hand, /obj/item/pen)) //and you're holding a pen + add_fingerprint(usr) + P.attackby(usr.r_hand, usr) //then do ittttt + else + if(istype(usr.l_hand, /obj/item/pen)) //check other hand for pen + add_fingerprint(usr) + P.attackby(usr.l_hand, usr) + else + to_chat(usr, "You'll need something to write with!") + + if(href_list["read"]) + var/obj/item/paper/P = locate(href_list["read"]) + if((P && P.loc == src)) + P.show_content(usr) + return diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm index 15b3c5d6420..01c48edaad1 100644 --- a/code/game/objects/structures/plasticflaps.dm +++ b/code/game/objects/structures/plasticflaps.dm @@ -108,4 +108,4 @@ T.air_update_turf(TRUE) /obj/structure/plasticflaps/mining/CanAtmosPass(turf/T) - return FALSE \ No newline at end of file + return FALSE diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm index 5620422cc45..3a83788b133 100644 --- a/code/game/objects/structures/reflector.dm +++ b/code/game/objects/structures/reflector.dm @@ -1,183 +1,183 @@ -/obj/structure/reflector - name = "reflector frame" - icon = 'icons/obj/stock_parts.dmi' - icon_state = "box_0" - desc = "A frame to create a reflector.\nUse 5 sheets of glass to create a 1 way reflector.\nUse 10 sheets of reinforced glass to create a 2 way reflector.\nUse 1 diamond to create a reflector cube." - anchored = 0 - density = 1 - layer = 3 - var/finished = 0 - - -/obj/structure/reflector/bullet_act(obj/item/projectile/P) - var/turf/reflector_turf = get_turf(src) - var/turf/reflect_turf - if(!istype(P, /obj/item/projectile/beam)) - return ..() - var/new_dir = get_reflection(dir, P.dir) - if(new_dir) - reflect_turf = get_step(reflect_turf, new_dir) - else - visible_message("[src] is hit by [P]!") - new_dir = 0 - return ..() //Hits as normal, explodes or emps or whatever - - visible_message("[P] bounces off of [src]") - reflect_turf = get_step(loc,new_dir) - - P.original = reflect_turf - P.starting = reflector_turf - P.current = reflector_turf - P.yo = reflect_turf.y - reflector_turf.y - P.xo = reflect_turf.x - reflector_turf.x - P.ignore_source_check = TRUE //If shot by a laser, will now hit the mob that fired it - var/reflect_angle = dir2angle(new_dir) - P.setAngle(reflect_angle) - - new_dir = 0 - return -1 - - -/obj/structure/reflector/attackby(obj/item/W, mob/user, params) - //Finishing the frame - if(istype(W,/obj/item/stack/sheet)) - if(finished) - return - var/obj/item/stack/sheet/S = W - if(istype(W, /obj/item/stack/sheet/glass)) - if(S.get_amount() < 5) - to_chat(user, "You need five sheets of glass to create a reflector!") - return - else - S.use(5) - new /obj/structure/reflector/single (src.loc) - qdel(src) - if(istype(W,/obj/item/stack/sheet/rglass)) - if(S.get_amount() < 10) - to_chat(user, "You need ten sheets of reinforced glass to create a double reflector!") - return - else - S.use(10) - new /obj/structure/reflector/double (src.loc) - qdel(src) - if(istype(W, /obj/item/stack/sheet/mineral/diamond)) - if(S.get_amount() >= 1) - S.use(1) - new /obj/structure/reflector/box (src.loc) - qdel(src) - return - return ..() - -/obj/structure/reflector/wrench_act(mob/user, obj/item/I) - . = TRUE - if(anchored) - to_chat(user, "Unweld [src] first!") - return - if(!I.tool_use_check(user, 0)) - return - TOOL_ATTEMPT_DISMANTLE_MESSAGE - if(!I.use_tool(src, user, 80, volume = I.tool_volume)) - return - playsound(user, 'sound/items/Ratchet.ogg', 50, 1) - TOOL_DISMANTLE_SUCCESS_MESSAGE - new /obj/item/stack/sheet/metal(src.loc, 5) - qdel(src) - -/obj/structure/reflector/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(anchored) - WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE - if(!I.use_tool(src, user, 20, volume = I.tool_volume)) - return - WELDER_FLOOR_SLICE_SUCCESS_MESSAGE - anchored = FALSE - else - WELDER_ATTEMPT_FLOOR_WELD_MESSAGE - if(!I.use_tool(src, user, 20, volume = I.tool_volume)) - return - WELDER_FLOOR_WELD_SUCCESS_MESSAGE - anchored = TRUE - -/obj/structure/reflector/proc/get_reflection(srcdir,pdir) - return 0 - - -/obj/structure/reflector/verb/rotate() - set name = "Rotate" - set category = "Object" - set src in oview(1) - - if(usr.incapacitated()) - return - if(anchored) - to_chat(usr, "It is fastened to the floor!") - return 0 - dir = turn(dir, 270) - return 1 - - -/obj/structure/reflector/AltClick(mob/user) - ..() - if(user.incapacitated()) - to_chat(user, "You can't do that right now!") - return - if(!in_range(src, user)) - return - else - rotate() - - -//TYPES OF REFLECTORS, SINGLE, DOUBLE, BOX - -//SINGLE - -/obj/structure/reflector/single - name = "reflector" - icon = 'icons/obj/reflector.dmi' - icon_state = "reflector" - desc = "A double sided angled mirror for reflecting lasers. This one does so at a 90 degree angle." - finished = 1 - var/static/list/rotations = list("[NORTH]" = list("[SOUTH]" = WEST, "[EAST]" = NORTH), -"[EAST]" = list("[SOUTH]" = EAST, "[WEST]" = NORTH), -"[SOUTH]" = list("[NORTH]" = EAST, "[WEST]" = SOUTH), -"[WEST]" = list("[NORTH]" = WEST, "[EAST]" = SOUTH) ) - -/obj/structure/reflector/single/get_reflection(srcdir,pdir) - var/new_dir = rotations["[srcdir]"]["[pdir]"] - return new_dir - -//DOUBLE - -/obj/structure/reflector/double - name = "double sided reflector" - icon = 'icons/obj/reflector.dmi' - icon_state = "reflector_double" - desc = "A double sided angled mirror for reflecting lasers. This one does so at a 90 degree angle." - finished = 1 - var/static/list/double_rotations = list("[NORTH]" = list("[NORTH]" = WEST, "[EAST]" = SOUTH, "[SOUTH]" = EAST, "[WEST]" = NORTH), -"[EAST]" = list("[NORTH]" = EAST, "[WEST]" = SOUTH, "[SOUTH]" = WEST, "[EAST]" = NORTH), -"[SOUTH]" = list("[NORTH]" = EAST, "[WEST]" = SOUTH, "[SOUTH]" = WEST, "[EAST]" = NORTH), -"[WEST]" = list("[NORTH]" = WEST, "[EAST]" = SOUTH, "[SOUTH]" = EAST, "[WEST]" = NORTH) ) - -/obj/structure/reflector/double/get_reflection(srcdir,pdir) - var/new_dir = double_rotations["[srcdir]"]["[pdir]"] - return new_dir - -//BOX - -/obj/structure/reflector/box - name = "reflector box" - icon = 'icons/obj/reflector.dmi' - icon_state = "reflector_box" - desc = "A box with an internal set of mirrors that reflects all laser fire in a single direction." - finished = 1 - var/static/list/box_rotations = list("[NORTH]" = list("[SOUTH]" = NORTH, "[EAST]" = NORTH, "[WEST]" = NORTH, "[NORTH]" = NORTH), -"[EAST]" = list("[SOUTH]" = EAST, "[EAST]" = EAST, "[WEST]" = EAST, "[NORTH]" = EAST), -"[SOUTH]" = list("[SOUTH]" = SOUTH, "[EAST]" = SOUTH, "[WEST]" = SOUTH, "[NORTH]" = SOUTH), -"[WEST]" = list("[SOUTH]" = WEST, "[EAST]" = WEST, "[WEST]" = WEST, "[NORTH]" = WEST) ) - -/obj/structure/reflector/box/get_reflection(srcdir,pdir) - var/new_dir = box_rotations["[srcdir]"]["[pdir]"] - return new_dir \ No newline at end of file +/obj/structure/reflector + name = "reflector frame" + icon = 'icons/obj/stock_parts.dmi' + icon_state = "box_0" + desc = "A frame to create a reflector.\nUse 5 sheets of glass to create a 1 way reflector.\nUse 10 sheets of reinforced glass to create a 2 way reflector.\nUse 1 diamond to create a reflector cube." + anchored = 0 + density = 1 + layer = 3 + var/finished = 0 + + +/obj/structure/reflector/bullet_act(obj/item/projectile/P) + var/turf/reflector_turf = get_turf(src) + var/turf/reflect_turf + if(!istype(P, /obj/item/projectile/beam)) + return ..() + var/new_dir = get_reflection(dir, P.dir) + if(new_dir) + reflect_turf = get_step(reflect_turf, new_dir) + else + visible_message("[src] is hit by [P]!") + new_dir = 0 + return ..() //Hits as normal, explodes or emps or whatever + + visible_message("[P] bounces off of [src]") + reflect_turf = get_step(loc,new_dir) + + P.original = reflect_turf + P.starting = reflector_turf + P.current = reflector_turf + P.yo = reflect_turf.y - reflector_turf.y + P.xo = reflect_turf.x - reflector_turf.x + P.ignore_source_check = TRUE //If shot by a laser, will now hit the mob that fired it + var/reflect_angle = dir2angle(new_dir) + P.setAngle(reflect_angle) + + new_dir = 0 + return -1 + + +/obj/structure/reflector/attackby(obj/item/W, mob/user, params) + //Finishing the frame + if(istype(W,/obj/item/stack/sheet)) + if(finished) + return + var/obj/item/stack/sheet/S = W + if(istype(W, /obj/item/stack/sheet/glass)) + if(S.get_amount() < 5) + to_chat(user, "You need five sheets of glass to create a reflector!") + return + else + S.use(5) + new /obj/structure/reflector/single (src.loc) + qdel(src) + if(istype(W,/obj/item/stack/sheet/rglass)) + if(S.get_amount() < 10) + to_chat(user, "You need ten sheets of reinforced glass to create a double reflector!") + return + else + S.use(10) + new /obj/structure/reflector/double (src.loc) + qdel(src) + if(istype(W, /obj/item/stack/sheet/mineral/diamond)) + if(S.get_amount() >= 1) + S.use(1) + new /obj/structure/reflector/box (src.loc) + qdel(src) + return + return ..() + +/obj/structure/reflector/wrench_act(mob/user, obj/item/I) + . = TRUE + if(anchored) + to_chat(user, "Unweld [src] first!") + return + if(!I.tool_use_check(user, 0)) + return + TOOL_ATTEMPT_DISMANTLE_MESSAGE + if(!I.use_tool(src, user, 80, volume = I.tool_volume)) + return + playsound(user, 'sound/items/Ratchet.ogg', 50, 1) + TOOL_DISMANTLE_SUCCESS_MESSAGE + new /obj/item/stack/sheet/metal(src.loc, 5) + qdel(src) + +/obj/structure/reflector/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(anchored) + WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE + if(!I.use_tool(src, user, 20, volume = I.tool_volume)) + return + WELDER_FLOOR_SLICE_SUCCESS_MESSAGE + anchored = FALSE + else + WELDER_ATTEMPT_FLOOR_WELD_MESSAGE + if(!I.use_tool(src, user, 20, volume = I.tool_volume)) + return + WELDER_FLOOR_WELD_SUCCESS_MESSAGE + anchored = TRUE + +/obj/structure/reflector/proc/get_reflection(srcdir,pdir) + return 0 + + +/obj/structure/reflector/verb/rotate() + set name = "Rotate" + set category = "Object" + set src in oview(1) + + if(usr.incapacitated()) + return + if(anchored) + to_chat(usr, "It is fastened to the floor!") + return 0 + dir = turn(dir, 270) + return 1 + + +/obj/structure/reflector/AltClick(mob/user) + ..() + if(user.incapacitated()) + to_chat(user, "You can't do that right now!") + return + if(!in_range(src, user)) + return + else + rotate() + + +//TYPES OF REFLECTORS, SINGLE, DOUBLE, BOX + +//SINGLE + +/obj/structure/reflector/single + name = "reflector" + icon = 'icons/obj/reflector.dmi' + icon_state = "reflector" + desc = "A double sided angled mirror for reflecting lasers. This one does so at a 90 degree angle." + finished = 1 + var/static/list/rotations = list("[NORTH]" = list("[SOUTH]" = WEST, "[EAST]" = NORTH), +"[EAST]" = list("[SOUTH]" = EAST, "[WEST]" = NORTH), +"[SOUTH]" = list("[NORTH]" = EAST, "[WEST]" = SOUTH), +"[WEST]" = list("[NORTH]" = WEST, "[EAST]" = SOUTH) ) + +/obj/structure/reflector/single/get_reflection(srcdir,pdir) + var/new_dir = rotations["[srcdir]"]["[pdir]"] + return new_dir + +//DOUBLE + +/obj/structure/reflector/double + name = "double sided reflector" + icon = 'icons/obj/reflector.dmi' + icon_state = "reflector_double" + desc = "A double sided angled mirror for reflecting lasers. This one does so at a 90 degree angle." + finished = 1 + var/static/list/double_rotations = list("[NORTH]" = list("[NORTH]" = WEST, "[EAST]" = SOUTH, "[SOUTH]" = EAST, "[WEST]" = NORTH), +"[EAST]" = list("[NORTH]" = EAST, "[WEST]" = SOUTH, "[SOUTH]" = WEST, "[EAST]" = NORTH), +"[SOUTH]" = list("[NORTH]" = EAST, "[WEST]" = SOUTH, "[SOUTH]" = WEST, "[EAST]" = NORTH), +"[WEST]" = list("[NORTH]" = WEST, "[EAST]" = SOUTH, "[SOUTH]" = EAST, "[WEST]" = NORTH) ) + +/obj/structure/reflector/double/get_reflection(srcdir,pdir) + var/new_dir = double_rotations["[srcdir]"]["[pdir]"] + return new_dir + +//BOX + +/obj/structure/reflector/box + name = "reflector box" + icon = 'icons/obj/reflector.dmi' + icon_state = "reflector_box" + desc = "A box with an internal set of mirrors that reflects all laser fire in a single direction." + finished = 1 + var/static/list/box_rotations = list("[NORTH]" = list("[SOUTH]" = NORTH, "[EAST]" = NORTH, "[WEST]" = NORTH, "[NORTH]" = NORTH), +"[EAST]" = list("[SOUTH]" = EAST, "[EAST]" = EAST, "[WEST]" = EAST, "[NORTH]" = EAST), +"[SOUTH]" = list("[SOUTH]" = SOUTH, "[EAST]" = SOUTH, "[WEST]" = SOUTH, "[NORTH]" = SOUTH), +"[WEST]" = list("[SOUTH]" = WEST, "[EAST]" = WEST, "[WEST]" = WEST, "[NORTH]" = WEST) ) + +/obj/structure/reflector/box/get_reflection(srcdir,pdir) + var/new_dir = box_rotations["[srcdir]"]["[pdir]"] + return new_dir diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm index 1e289a7f27b..f891aff5592 100644 --- a/code/game/objects/structures/safe.dm +++ b/code/game/objects/structures/safe.dm @@ -1,385 +1,385 @@ -/* -CONTAINS: -SAFES -FLOOR SAFES -SAFE INTERNALS -SAFE CODES -*/ - -GLOBAL_LIST_EMPTY(safes) - -//SAFES -/obj/structure/safe - name = "\improper Safe" - desc = "A huge chunk of metal with a dial embedded in it. Fine print on the dial reads \"Scarborough Arms tumbler safe, guaranteed thermite resistant, explosion resistant, and assistant resistant.\"" - icon = 'icons/obj/structures.dmi' - icon_state = "safe" - - anchored = TRUE - density = TRUE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - - var/open = FALSE - var/locked = TRUE - var/dial = 0 // The position the dial is pointing to. - - var/number_of_tumblers = 3 // The amount of tumblers that will be generated. - var/list/tumblers = list() // The list of tumbler dial positions that need to be hit. - var/list/current_tumbler_index = 1 // The index in the tumblers list of the tumbler dial position that needs to be hit. - - var/space = 0 // The combined w_class of everything in the safe. - var/maxspace = 24 // The maximum combined w_class of stuff in the safe. - - var/obj/item/thermal_drill/drill = null - var/drill_timer - var/time_to_drill - var/image/bar - var/drill_start_time - var/drill_x_offset = -13 - var/drill_y_offset = -3 - var/known_by = list() - - var/image/progress_bar - var/image/drill_overlay - -/obj/structure/safe/Initialize(mapload) - . = ..() - - GLOB.safes += src - - for(var/i in 1 to number_of_tumblers) - tumblers.Add(rand(0, 99)) - - for(var/obj/item/I in loc) - if(space >= maxspace) - return - if(I.w_class + space <= maxspace) - space += I.w_class - I.forceMove(src) - -/obj/structure/safe/Destroy() - GLOB.safes -= src - drill?.soundloop?.stop() - drill?.forceMove(loc) - drill = null - - qdel(progress_bar) - qdel(drill_overlay) - return ..() - -/obj/structure/safe/process() - if(drill_timer) - cut_overlay(progress_bar) - progress_bar = image('icons/effects/progessbar.dmi', src, "prog_bar_[round((((world.time - drill_start_time) / time_to_drill) * 100), 5)]", HUD_LAYER) - add_overlay(progress_bar) - if(prob(15)) - drill.spark_system.start() - -/obj/structure/safe/examine(mob/user) - . = ..() - - . += "This model appears to have [number_of_tumblers] tumblers." - if(open) - . += "The inside of the the door has numbers written on it: [get_combination()]" - -/obj/structure/safe/blob_act(obj/structure/blob/B) - return - -/obj/structure/safe/ex_act(severity) - return - -/obj/structure/safe/examine_status(mob/user) - return - -/obj/structure/safe/proc/check_unlocked() - if(current_tumbler_index > number_of_tumblers) - locked = FALSE - visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!") - return TRUE - - locked = TRUE - return FALSE - -/obj/structure/safe/proc/get_combination() - var/combination = "" - var/looped = 0 - - for(var/tumbler in tumblers) - looped++ - combination += "[tumbler]" - - if(looped < LAZYLEN(tumblers)) - combination += ", " - - return combination - -/obj/structure/safe/update_icon() - if(open) - if(broken) - icon_state = "[initial(icon_state)]-open-broken" - else - icon_state = "[initial(icon_state)]-open" - else - if(broken) - icon_state = "[initial(icon_state)]-broken" - else - icon_state = initial(icon_state) - - var/list/overlays_to_cut = list(drill_overlay) - if(!drill_timer) - overlays_to_cut += progress_bar - - cut_overlay(overlays_to_cut) - - if(istype(drill, /obj/item/thermal_drill)) - var/drill_icon = istype(drill, /obj/item/thermal_drill/diamond_drill) ? "d" : "h" - if(drill_timer) - drill_overlay = image(icon = 'icons/effects/drill.dmi', icon_state = "[initial(icon_state)]_[drill_icon]-drill-on", pixel_x = drill_x_offset, pixel_y = drill_y_offset) - else - drill_overlay = image(icon = 'icons/effects/drill.dmi', icon_state = "[initial(icon_state)]_[drill_icon]-drill-off", pixel_x = drill_x_offset, pixel_y = drill_y_offset) - - add_overlay(drill_overlay) - -/obj/structure/safe/attack_ghost(mob/user) - if(..() || drill) - return TRUE - - ui_interact(user) - -/obj/structure/safe/attack_hand(mob/user) - if(..()) - return TRUE - - if(drill) - switch(alert("What would you like to do?", "Thermal Drill", "Turn [drill_timer ? "Off" : "On"]", "Remove Drill", "Cancel")) - if("Turn On") - if(do_after(user, 2 SECONDS, target = src)) - drill_timer = addtimer(CALLBACK(src, .proc/drill_open), time_to_drill, TIMER_STOPPABLE) - drill_start_time = world.time - drill.soundloop.start() - update_icon() - START_PROCESSING(SSobj, src) - if("Turn Off") - if(do_after(user, 2 SECONDS, target = src)) - deltimer(drill_timer) - drill_timer = null - drill.soundloop.stop() - update_icon() - STOP_PROCESSING(SSobj, src) - if("Remove Drill") - if(drill_timer) - to_chat(user, "You cant remove the drill while it's running!") - else if(do_after(user, 2 SECONDS, target = src)) - user.put_in_hands(drill) - drill = null - update_icon() - if("Cancel") - return - else - ui_interact(user) - -/obj/structure/safe/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "safe.tmpl", name, 600, 750) - ui.open() - ui.set_auto_update(1) - -/obj/structure/safe/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - var/list/contents_names = list() - if(open) - for(var/obj/O in contents) - contents_names[++contents_names.len] = list("name" = O.name, "index" = contents.Find(O), "sprite" = O.icon_state) - user << browse_rsc(icon(O.icon, O.icon_state), "[O.icon_state].png") - - data["dial"] = dial - data["open"] = open - data["locked"] = locked - data["rotation"] = "[-dial * 3.6]deg" - data["contents"] = contents_names - - return data - -/obj/structure/safe/proc/notify_user(user, canhear, sounds, total_ticks, current_tick) - if(!canhear) - return - - if(current_tick == 2) - to_chat(user, "The sounds from [src] are too fast and blend together.") - - if(total_ticks == 1 || prob(10)) - to_chat(user, "You hear a [pick(sounds)] from [src].") - -/obj/structure/safe/Topic(href, href_list) - if(..()) - return TRUE - - var/mob/user = usr - if(!user.IsAdvancedToolUser() && !isobserver(user)) - to_chat(user, "You're not able to operate the safe.") - return - - var/canhear = FALSE - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.can_hear() && H.is_in_hands(/obj/item/clothing/accessory/stethoscope)) - canhear = TRUE - - if(href_list["open"]) - if(check_unlocked() || open || broken) - to_chat(user, "You [open ? "close" : "open"] [src].") - open = !open - update_icon() - else - to_chat(user, "You can't open [src], as its lock is engaged!") - - . = TRUE - - if(href_list["turnright"]) - if(open) - return - - if(broken) - to_chat(user, "The dial will not turn, as the mechanism is destroyed.") - return - - var/ticks = text2num(href_list["turnright"]) - for(var/i = 1 to ticks) - dial = Wrap(dial - 1, 0, 100) - - var/invalid_turn = current_tumbler_index % 2 == 0 || current_tumbler_index > number_of_tumblers - if(invalid_turn) // The moment you turn the wrong way or go too far, the tumblers reset - current_tumbler_index = 1 - - if(!invalid_turn && dial == tumblers[current_tumbler_index]) - notify_user(user, canhear, list("tink", "krink", "plink"), ticks, i) - current_tumbler_index++ - else - notify_user(user, canhear, list("clack", "scrape", "clank"), ticks, i) - - sleep(1) - check_unlocked(user, canhear) - SSnanoui.update_uis(src) - - . = TRUE - - if(href_list["turnleft"]) - if(open) - return - - if(broken) - to_chat(user, "The dial will not turn, as the mechanism is destroyed.") - return - - var/ticks = text2num(href_list["turnleft"]) - for(var/i = 1 to ticks) - dial = Wrap(dial + 1, 0, 100) - - var/invalid_turn = current_tumbler_index % 2 != 0 || current_tumbler_index > number_of_tumblers - if(invalid_turn) // The moment you turn the wrong way or go too far, the tumblers reset - current_tumbler_index = 1 - - if(!invalid_turn && dial == tumblers[current_tumbler_index]) - notify_user(user, canhear, list("tonk", "krunk", "plunk"), ticks, i) - current_tumbler_index++ - else - notify_user(user, canhear, list("click", "chink", "clink"), ticks, i) - - sleep(1) - check_unlocked(user, canhear) - SSnanoui.update_uis(src) - - . = TRUE - - if(href_list["retrieve"]) - var/index = text2num(href_list["retrieve"]) - if(index > 0 && index <= contents.len) - var/obj/item/P = contents[index] - if(open) - if(P && in_range(src, user)) - user.put_in_hands(P) - space -= P.w_class - . = TRUE - -/obj/structure/safe/proc/drill_open() - broken = TRUE - drill_timer = null - drill.soundloop.stop() - update_icon() - STOP_PROCESSING(SSobj, src) - -/obj/structure/safe/attackby(obj/item/I, mob/user, params) - if(open) - if(broken && istype(I, /obj/item/safe_internals) && do_after(user, 2 SECONDS, target = src)) - to_chat(user, "You replace the broken mechanism.") - qdel(I) - broken = !broken - update_icon() - else if(I.w_class + space <= maxspace) - if(!user.drop_item()) - to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the safe!") - return - space += I.w_class - I.forceMove(src) - to_chat(user, "You put [I] in [src].") - else - to_chat(user, "[I] won't fit in [src].") - else - if(istype(I, /obj/item/clothing/accessory/stethoscope)) - to_chat(user, "Hold [I] in one of your hands while you manipulate the dial!") - return - else if(istype(I, /obj/item/thermal_drill)) - if(drill) - to_chat(user, "There is already a drill attached!") - else if(do_after(user, 2 SECONDS, target = src)) - if(!user.drop_item()) - to_chat(user, "[I] is stuck to your hand, you cannot put it in the safe!") - return - I.forceMove(src) - drill = I - time_to_drill = 300 SECONDS * drill.time_multiplier - update_icon() - else - to_chat(user, "You can't put [I] into the safe while it is closed!") - return - -//FLOOR SAFES -/obj/structure/safe/floor - name = "floor safe" - icon_state = "floorsafe" - density = FALSE - level = 1 //Under the floor - layer = LOW_OBJ_LAYER - drill_x_offset = -1 - drill_y_offset = 20 - -/obj/structure/safe/floor/Initialize() - . = ..() - var/turf/T = loc - hide(T.intact) - -/obj/structure/safe/floor/hide(intact) - invisibility = intact ? INVISIBILITY_MAXIMUM : 0 - -/obj/item/safe_internals - name = "safe internals" - desc = "The mechanism and locking bolts for a Scarborough Arms - 2 tumbler safe" - icon_state = "safe_internals" - -/obj/item/paper/safe_code - name = "safe codes" - var/owner - info = "

        Safe Codes

        " - -/obj/item/paper/safe_code/Initialize(mapload) - return INITIALIZE_HINT_LATELOAD - -/obj/item/paper/safe_code/LateInitialize(mapload) - . = ..() - for(var/safe in GLOB.safes) - var/obj/structure/safe/S = safe - if(owner in S.known_by) - info += "
        The combination for the safe located in the [get_area(S).name] is: [S.get_combination()]
        " - info_links = info - update_icon() \ No newline at end of file +/* +CONTAINS: +SAFES +FLOOR SAFES +SAFE INTERNALS +SAFE CODES +*/ + +GLOBAL_LIST_EMPTY(safes) + +//SAFES +/obj/structure/safe + name = "\improper Safe" + desc = "A huge chunk of metal with a dial embedded in it. Fine print on the dial reads \"Scarborough Arms tumbler safe, guaranteed thermite resistant, explosion resistant, and assistant resistant.\"" + icon = 'icons/obj/structures.dmi' + icon_state = "safe" + + anchored = TRUE + density = TRUE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + + var/open = FALSE + var/locked = TRUE + var/dial = 0 // The position the dial is pointing to. + + var/number_of_tumblers = 3 // The amount of tumblers that will be generated. + var/list/tumblers = list() // The list of tumbler dial positions that need to be hit. + var/list/current_tumbler_index = 1 // The index in the tumblers list of the tumbler dial position that needs to be hit. + + var/space = 0 // The combined w_class of everything in the safe. + var/maxspace = 24 // The maximum combined w_class of stuff in the safe. + + var/obj/item/thermal_drill/drill = null + var/drill_timer + var/time_to_drill + var/image/bar + var/drill_start_time + var/drill_x_offset = -13 + var/drill_y_offset = -3 + var/known_by = list() + + var/image/progress_bar + var/image/drill_overlay + +/obj/structure/safe/Initialize(mapload) + . = ..() + + GLOB.safes += src + + for(var/i in 1 to number_of_tumblers) + tumblers.Add(rand(0, 99)) + + for(var/obj/item/I in loc) + if(space >= maxspace) + return + if(I.w_class + space <= maxspace) + space += I.w_class + I.forceMove(src) + +/obj/structure/safe/Destroy() + GLOB.safes -= src + drill?.soundloop?.stop() + drill?.forceMove(loc) + drill = null + + qdel(progress_bar) + qdel(drill_overlay) + return ..() + +/obj/structure/safe/process() + if(drill_timer) + cut_overlay(progress_bar) + progress_bar = image('icons/effects/progessbar.dmi', src, "prog_bar_[round((((world.time - drill_start_time) / time_to_drill) * 100), 5)]", HUD_LAYER) + add_overlay(progress_bar) + if(prob(15)) + drill.spark_system.start() + +/obj/structure/safe/examine(mob/user) + . = ..() + + . += "This model appears to have [number_of_tumblers] tumblers." + if(open) + . += "The inside of the the door has numbers written on it: [get_combination()]" + +/obj/structure/safe/blob_act(obj/structure/blob/B) + return + +/obj/structure/safe/ex_act(severity) + return + +/obj/structure/safe/examine_status(mob/user) + return + +/obj/structure/safe/proc/check_unlocked() + if(current_tumbler_index > number_of_tumblers) + locked = FALSE + visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!") + return TRUE + + locked = TRUE + return FALSE + +/obj/structure/safe/proc/get_combination() + var/combination = "" + var/looped = 0 + + for(var/tumbler in tumblers) + looped++ + combination += "[tumbler]" + + if(looped < LAZYLEN(tumblers)) + combination += ", " + + return combination + +/obj/structure/safe/update_icon() + if(open) + if(broken) + icon_state = "[initial(icon_state)]-open-broken" + else + icon_state = "[initial(icon_state)]-open" + else + if(broken) + icon_state = "[initial(icon_state)]-broken" + else + icon_state = initial(icon_state) + + var/list/overlays_to_cut = list(drill_overlay) + if(!drill_timer) + overlays_to_cut += progress_bar + + cut_overlay(overlays_to_cut) + + if(istype(drill, /obj/item/thermal_drill)) + var/drill_icon = istype(drill, /obj/item/thermal_drill/diamond_drill) ? "d" : "h" + if(drill_timer) + drill_overlay = image(icon = 'icons/effects/drill.dmi', icon_state = "[initial(icon_state)]_[drill_icon]-drill-on", pixel_x = drill_x_offset, pixel_y = drill_y_offset) + else + drill_overlay = image(icon = 'icons/effects/drill.dmi', icon_state = "[initial(icon_state)]_[drill_icon]-drill-off", pixel_x = drill_x_offset, pixel_y = drill_y_offset) + + add_overlay(drill_overlay) + +/obj/structure/safe/attack_ghost(mob/user) + if(..() || drill) + return TRUE + + ui_interact(user) + +/obj/structure/safe/attack_hand(mob/user) + if(..()) + return TRUE + + if(drill) + switch(alert("What would you like to do?", "Thermal Drill", "Turn [drill_timer ? "Off" : "On"]", "Remove Drill", "Cancel")) + if("Turn On") + if(do_after(user, 2 SECONDS, target = src)) + drill_timer = addtimer(CALLBACK(src, .proc/drill_open), time_to_drill, TIMER_STOPPABLE) + drill_start_time = world.time + drill.soundloop.start() + update_icon() + START_PROCESSING(SSobj, src) + if("Turn Off") + if(do_after(user, 2 SECONDS, target = src)) + deltimer(drill_timer) + drill_timer = null + drill.soundloop.stop() + update_icon() + STOP_PROCESSING(SSobj, src) + if("Remove Drill") + if(drill_timer) + to_chat(user, "You cant remove the drill while it's running!") + else if(do_after(user, 2 SECONDS, target = src)) + user.put_in_hands(drill) + drill = null + update_icon() + if("Cancel") + return + else + ui_interact(user) + +/obj/structure/safe/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "safe.tmpl", name, 600, 750) + ui.open() + ui.set_auto_update(1) + +/obj/structure/safe/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + var/list/contents_names = list() + if(open) + for(var/obj/O in contents) + contents_names[++contents_names.len] = list("name" = O.name, "index" = contents.Find(O), "sprite" = O.icon_state) + user << browse_rsc(icon(O.icon, O.icon_state), "[O.icon_state].png") + + data["dial"] = dial + data["open"] = open + data["locked"] = locked + data["rotation"] = "[-dial * 3.6]deg" + data["contents"] = contents_names + + return data + +/obj/structure/safe/proc/notify_user(user, canhear, sounds, total_ticks, current_tick) + if(!canhear) + return + + if(current_tick == 2) + to_chat(user, "The sounds from [src] are too fast and blend together.") + + if(total_ticks == 1 || prob(10)) + to_chat(user, "You hear a [pick(sounds)] from [src].") + +/obj/structure/safe/Topic(href, href_list) + if(..()) + return TRUE + + var/mob/user = usr + if(!user.IsAdvancedToolUser() && !isobserver(user)) + to_chat(user, "You're not able to operate the safe.") + return + + var/canhear = FALSE + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.can_hear() && H.is_in_hands(/obj/item/clothing/accessory/stethoscope)) + canhear = TRUE + + if(href_list["open"]) + if(check_unlocked() || open || broken) + to_chat(user, "You [open ? "close" : "open"] [src].") + open = !open + update_icon() + else + to_chat(user, "You can't open [src], as its lock is engaged!") + + . = TRUE + + if(href_list["turnright"]) + if(open) + return + + if(broken) + to_chat(user, "The dial will not turn, as the mechanism is destroyed.") + return + + var/ticks = text2num(href_list["turnright"]) + for(var/i = 1 to ticks) + dial = Wrap(dial - 1, 0, 100) + + var/invalid_turn = current_tumbler_index % 2 == 0 || current_tumbler_index > number_of_tumblers + if(invalid_turn) // The moment you turn the wrong way or go too far, the tumblers reset + current_tumbler_index = 1 + + if(!invalid_turn && dial == tumblers[current_tumbler_index]) + notify_user(user, canhear, list("tink", "krink", "plink"), ticks, i) + current_tumbler_index++ + else + notify_user(user, canhear, list("clack", "scrape", "clank"), ticks, i) + + sleep(1) + check_unlocked(user, canhear) + SSnanoui.update_uis(src) + + . = TRUE + + if(href_list["turnleft"]) + if(open) + return + + if(broken) + to_chat(user, "The dial will not turn, as the mechanism is destroyed.") + return + + var/ticks = text2num(href_list["turnleft"]) + for(var/i = 1 to ticks) + dial = Wrap(dial + 1, 0, 100) + + var/invalid_turn = current_tumbler_index % 2 != 0 || current_tumbler_index > number_of_tumblers + if(invalid_turn) // The moment you turn the wrong way or go too far, the tumblers reset + current_tumbler_index = 1 + + if(!invalid_turn && dial == tumblers[current_tumbler_index]) + notify_user(user, canhear, list("tonk", "krunk", "plunk"), ticks, i) + current_tumbler_index++ + else + notify_user(user, canhear, list("click", "chink", "clink"), ticks, i) + + sleep(1) + check_unlocked(user, canhear) + SSnanoui.update_uis(src) + + . = TRUE + + if(href_list["retrieve"]) + var/index = text2num(href_list["retrieve"]) + if(index > 0 && index <= contents.len) + var/obj/item/P = contents[index] + if(open) + if(P && in_range(src, user)) + user.put_in_hands(P) + space -= P.w_class + . = TRUE + +/obj/structure/safe/proc/drill_open() + broken = TRUE + drill_timer = null + drill.soundloop.stop() + update_icon() + STOP_PROCESSING(SSobj, src) + +/obj/structure/safe/attackby(obj/item/I, mob/user, params) + if(open) + if(broken && istype(I, /obj/item/safe_internals) && do_after(user, 2 SECONDS, target = src)) + to_chat(user, "You replace the broken mechanism.") + qdel(I) + broken = !broken + update_icon() + else if(I.w_class + space <= maxspace) + if(!user.drop_item()) + to_chat(user, "\The [I] is stuck to your hand, you cannot put it in the safe!") + return + space += I.w_class + I.forceMove(src) + to_chat(user, "You put [I] in [src].") + else + to_chat(user, "[I] won't fit in [src].") + else + if(istype(I, /obj/item/clothing/accessory/stethoscope)) + to_chat(user, "Hold [I] in one of your hands while you manipulate the dial!") + return + else if(istype(I, /obj/item/thermal_drill)) + if(drill) + to_chat(user, "There is already a drill attached!") + else if(do_after(user, 2 SECONDS, target = src)) + if(!user.drop_item()) + to_chat(user, "[I] is stuck to your hand, you cannot put it in the safe!") + return + I.forceMove(src) + drill = I + time_to_drill = 300 SECONDS * drill.time_multiplier + update_icon() + else + to_chat(user, "You can't put [I] into the safe while it is closed!") + return + +//FLOOR SAFES +/obj/structure/safe/floor + name = "floor safe" + icon_state = "floorsafe" + density = FALSE + level = 1 //Under the floor + layer = LOW_OBJ_LAYER + drill_x_offset = -1 + drill_y_offset = 20 + +/obj/structure/safe/floor/Initialize() + . = ..() + var/turf/T = loc + hide(T.intact) + +/obj/structure/safe/floor/hide(intact) + invisibility = intact ? INVISIBILITY_MAXIMUM : 0 + +/obj/item/safe_internals + name = "safe internals" + desc = "The mechanism and locking bolts for a Scarborough Arms - 2 tumbler safe" + icon_state = "safe_internals" + +/obj/item/paper/safe_code + name = "safe codes" + var/owner + info = "

        Safe Codes

        " + +/obj/item/paper/safe_code/Initialize(mapload) + return INITIALIZE_HINT_LATELOAD + +/obj/item/paper/safe_code/LateInitialize(mapload) + . = ..() + for(var/safe in GLOB.safes) + var/obj/structure/safe/S = safe + if(owner in S.known_by) + info += "
        The combination for the safe located in the [get_area(S).name] is: [S.get_combination()]
        " + info_links = info + update_icon() diff --git a/code/game/objects/structures/signs.dm b/code/game/objects/structures/signs.dm index 930dcc0995c..3f5d667a356 100644 --- a/code/game/objects/structures/signs.dm +++ b/code/game/objects/structures/signs.dm @@ -1,326 +1,326 @@ -/obj/structure/sign - icon = 'icons/obj/decals.dmi' - anchored = 1 - opacity = 0 - density = 0 - layer = 3.5 - max_integrity = 100 - armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - -/obj/structure/sign/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(src.loc, 'sound/weapons/slash.ogg', 80, TRUE) - else - playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(loc, 'sound/items/welder.ogg', 80, TRUE) - -/obj/structure/sign/screwdriver_act(mob/user, obj/item/I) - if(istype(src, /obj/structure/sign/double)) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - to_chat(user, "You unfasten the sign with [I].") - var/obj/item/sign/S = new(src.loc) - S.name = name - S.desc = desc - S.icon_state = icon_state - //var/icon/I = icon('icons/obj/decals.dmi', icon_state) - //S.icon = I.Scale(24, 24) - S.sign_state = icon_state - qdel(src) - - -/obj/item/sign - name = "sign" - desc = "" - icon = 'icons/obj/decals.dmi' - w_class = WEIGHT_CLASS_NORMAL - resistance_flags = FLAMMABLE - var/sign_state = "" - -/obj/item/sign/attackby(obj/item/tool as obj, mob/user as mob) //construction - if(istype(tool, /obj/item/screwdriver) && isturf(user.loc)) - var/direction = input("In which direction?", "Select direction.") in list("North", "East", "South", "West", "Cancel") - if(direction == "Cancel") - return - if(QDELETED(src)) - return - var/obj/structure/sign/S = new(user.loc) - switch(direction) - if("North") - S.pixel_y = 32 - if("East") - S.pixel_x = 32 - if("South") - S.pixel_y = -32 - if("West") - S.pixel_x = -32 - else - return - S.name = name - S.desc = desc - S.icon_state = sign_state - to_chat(user, "You fasten \the [S] with your [tool].") - qdel(src) - else - return ..() - -/obj/structure/sign/double/map - name = "station map" - desc = "A framed picture of the station." - max_integrity = 500 - -/obj/structure/sign/double/map/left - icon_state = "map-left" - -/obj/structure/sign/double/map/right - icon_state = "map-right" - -/obj/structure/sign/securearea - name = "\improper SECURE AREA" - desc = "A warning sign which reads 'SECURE AREA'" - icon_state = "securearea" - -/obj/structure/sign/biohazard - name = "\improper BIOHAZARD" - desc = "A warning sign which reads 'BIOHAZARD'" - icon_state = "bio" - -/obj/structure/sign/electricshock - name = "\improper HIGH VOLTAGE" - desc = "A warning sign which reads 'HIGH VOLTAGE'" - icon_state = "shock" - -/obj/structure/sign/examroom - name = "\improper EXAM" - desc = "A guidance sign which reads 'EXAM ROOM'" - icon_state = "examroom" - -/obj/structure/sign/vacuum - name = "\improper HARD VACUUM AHEAD" - desc = "A warning sign which reads 'HARD VACUUM AHEAD'" - icon_state = "space" - -/obj/structure/sign/vacuum/external - name = "\improper EXTERNAL AIRLOCK" - desc = "A warning sign which reads 'EXTERNAL AIRLOCK'." - layer = MOB_LAYER - -/obj/structure/sign/deathsposal - name = "\improper DISPOSAL LEADS TO SPACE" - desc = "A warning sign which reads 'DISPOSAL LEADS TO SPACE'" - icon_state = "deathsposal" - -/obj/structure/sign/pods - name = "\improper ESCAPE PODS" - desc = "A warning sign which reads 'ESCAPE PODS'" - icon_state = "pods" - -/obj/structure/sign/fire - name = "\improper DANGER: FIRE" - desc = "A warning sign which reads 'DANGER: FIRE'" - icon_state = "fire" - resistance_flags = FIRE_PROOF - -/obj/structure/sign/nosmoking_1 - name = "\improper NO SMOKING" - desc = "A warning sign which reads 'NO SMOKING'" - icon_state = "nosmoking" - resistance_flags = FLAMMABLE - -/obj/structure/sign/nosmoking_2 - name = "\improper NO SMOKING" - desc = "A warning sign which reads 'NO SMOKING'" - icon_state = "nosmoking2" - -/obj/structure/sign/radiation - name = "\improper HAZARDOUS RADIATION" - desc = "A warning sign alerting the user of potential radiation hazards." - icon_state = "radiation" - -/obj/structure/sign/radiation/rad_area - name = "\improper RADIOACTIVE AREA" - desc = "A warning sign which reads 'RADIOACTIVE AREA'." - -/obj/structure/sign/xeno_warning_mining - name = "DANGEROUS ALIEN LIFE" - desc = "A sign that warns would be travellers of hostile alien life in the vicinity." - icon = 'icons/obj/mining.dmi' - icon_state = "xeno_warning" - -/obj/structure/sign/redcross - name = "medbay" - desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here.'" - icon_state = "redcross" - -/obj/structure/sign/greencross - name = "medbay" - desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here.'" - icon_state = "greencross" - -/obj/structure/sign/goldenplaque - name = "The Most Robust Men Award for Robustness" - desc = "To be Robust is not an action or a way of life, but a mental state. Only those with the force of Will strong enough to act during a crisis, saving friend from foe, are truly Robust. Stay Robust my friends." - icon_state = "goldenplaque" - -/obj/structure/sign/kiddieplaque - name = "AI developers plaque" - desc = "Next to the extremely long list of names and job titles, there is a drawing of a little child. The child appears to be retarded. Beneath the image, someone has scratched the word \"PACKETS\"." - icon_state = "kiddieplaque" - -/obj/structure/sign/atmosplaque - name = "\improper ZAS Atmospherics Division plaque" - desc = "This plaque commemorates the fall of the Atmos ZAS division. For all the charred, dizzy, and brittle men who have died in its horrible hands." - icon_state = "atmosplaque" - -/obj/structure/sign/kidanplaque - name = "Kidan wall trophy" - desc = "A dead and stuffed Diona nymph, mounted on a board." - icon_state = "kidanplaque" - -/obj/structure/sign/mech - name = "\improper mech painting" - desc = "A painting of a mech" - icon_state = "mech" - -/obj/structure/sign/nuke - name = "\improper nuke painting" - desc = "A painting of a nuke" - icon_state = "nuke" - -/obj/structure/sign/clown - name = "\improper clown painting" - desc = "A painting of the clown and mime. Awwww." - icon_state = "clown" - -/obj/structure/sign/bobross - name = "\improper calming painting" - desc = "We don't make mistakes, just happy little accidents." - icon_state = "bob" - -/obj/structure/sign/singulo - name = "\improper singulo painting" - desc = "A mesmerizing painting of a singularity. It seems to suck you in..." - icon_state = "singulo" - -/obj/structure/sign/barber - name = "\improper barber shop sign" - desc = "A spinning sign indicating a barbershop is near." - icon_state = "barber" - -/obj/structure/sign/chinese - name = "\improper chinese restaurant sign" - desc = "A glowing dragon invites you in." - icon_state = "chinese" - -/obj/structure/sign/science - name = "\improper SCIENCE!" - desc = "A warning sign which reads 'SCIENCE!'" - icon_state = "science1" - -/obj/structure/sign/chemistry - name = "\improper CHEMISTRY" - desc = "A warning sign which reads 'CHEMISTRY'" - icon_state = "chemistry1" - -/obj/structure/sign/botany - name = "\improper HYDROPONICS" - desc = "A warning sign which reads 'HYDROPONICS'" - icon_state = "hydro1" - -/obj/structure/sign/xenobio - name = "\improper XENOBIOLOGY" - desc = "A sign labelling an area as a place where xenobiological entities are researched." - icon_state = "xenobio" - -/obj/structure/sign/evac - name = "\improper EVACUATION" - desc = "A sign labelling an area where evacuation procedures take place." - icon_state = "evac" - -/obj/structure/sign/drop - name = "\improper DROP PODS" - desc = "A sign labelling an area where drop pod loading procedures take place." - icon_state = "drop" - -/obj/structure/sign/custodian - name = "\improper CUSTODIAN" - desc = "A sign labelling an area where the custodian works." - icon_state = "custodian" - -/obj/structure/sign/engineering - name = "\improper ENGINEERING" - desc = "A sign labelling an area where engineers work." - icon_state = "engine" - -/obj/structure/sign/cargo - name = "\improper CARGO" - desc = "A sign labelling an area where cargo ships dock." - icon_state = "cargo" - -/obj/structure/sign/security - name = "\improper SECURITY" - desc = "A sign labelling an area where the law is law." - icon_state = "security" - -/obj/structure/sign/holy - name = "\improper HOLY" - desc = "A sign labelling a religious area." - icon_state = "holy" - -/obj/structure/sign/restroom - name = "\improper RESTROOM" - desc = "A sign labelling a restroom." - icon_state = "restroom" - -/obj/structure/sign/medbay - name = "\improper MEDBAY" - desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here." - icon_state = "bluecross" - -/obj/structure/sign/medbay/alt - icon_state = "bluecross2" - -/obj/structure/sign/directions/science - name = "\improper Research Division" - desc = "A direction sign, pointing out which way the Research Division is." - icon_state = "direction_sci" - -/obj/structure/sign/directions/engineering - name = "\improper Engineering Department" - desc = "A direction sign, pointing out which way the Engineering department is." - icon_state = "direction_eng" - -/obj/structure/sign/directions/security - name = "\improper Security Department" - desc = "A direction sign, pointing out which way the Security department is." - icon_state = "direction_sec" - -/obj/structure/sign/directions/medical - name = "\improper Medical Bay" - desc = "A direction sign, pointing out which way Medical Bay is." - icon_state = "direction_med" - -/obj/structure/sign/directions/evac - name = "\improper Escape Arm" - desc = "A direction sign, pointing out which way escape shuttle dock is." - icon_state = "direction_evac" - -/obj/structure/sign/directions/cargo - name = "\improper Cargo Department" - desc = "A direction sign, pointing out which way the Cargo department is." - icon_state = "direction_supply" - -/obj/structure/sign/explosives - name = "\improper HIGH EXPLOSIVES" - desc = "A warning sign which reads 'HIGH EXPLOSIVES'." - icon_state = "explosives" - -/obj/structure/sign/explosives/alt - name = "\improper HIGH EXPLOSIVES" - desc = "A warning sign which reads 'HIGH EXPLOSIVES'." - icon_state = "explosives2" \ No newline at end of file +/obj/structure/sign + icon = 'icons/obj/decals.dmi' + anchored = 1 + opacity = 0 + density = 0 + layer = 3.5 + max_integrity = 100 + armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + +/obj/structure/sign/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(src.loc, 'sound/weapons/slash.ogg', 80, TRUE) + else + playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(loc, 'sound/items/welder.ogg', 80, TRUE) + +/obj/structure/sign/screwdriver_act(mob/user, obj/item/I) + if(istype(src, /obj/structure/sign/double)) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + to_chat(user, "You unfasten the sign with [I].") + var/obj/item/sign/S = new(src.loc) + S.name = name + S.desc = desc + S.icon_state = icon_state + //var/icon/I = icon('icons/obj/decals.dmi', icon_state) + //S.icon = I.Scale(24, 24) + S.sign_state = icon_state + qdel(src) + + +/obj/item/sign + name = "sign" + desc = "" + icon = 'icons/obj/decals.dmi' + w_class = WEIGHT_CLASS_NORMAL + resistance_flags = FLAMMABLE + var/sign_state = "" + +/obj/item/sign/attackby(obj/item/tool as obj, mob/user as mob) //construction + if(istype(tool, /obj/item/screwdriver) && isturf(user.loc)) + var/direction = input("In which direction?", "Select direction.") in list("North", "East", "South", "West", "Cancel") + if(direction == "Cancel") + return + if(QDELETED(src)) + return + var/obj/structure/sign/S = new(user.loc) + switch(direction) + if("North") + S.pixel_y = 32 + if("East") + S.pixel_x = 32 + if("South") + S.pixel_y = -32 + if("West") + S.pixel_x = -32 + else + return + S.name = name + S.desc = desc + S.icon_state = sign_state + to_chat(user, "You fasten \the [S] with your [tool].") + qdel(src) + else + return ..() + +/obj/structure/sign/double/map + name = "station map" + desc = "A framed picture of the station." + max_integrity = 500 + +/obj/structure/sign/double/map/left + icon_state = "map-left" + +/obj/structure/sign/double/map/right + icon_state = "map-right" + +/obj/structure/sign/securearea + name = "\improper SECURE AREA" + desc = "A warning sign which reads 'SECURE AREA'" + icon_state = "securearea" + +/obj/structure/sign/biohazard + name = "\improper BIOHAZARD" + desc = "A warning sign which reads 'BIOHAZARD'" + icon_state = "bio" + +/obj/structure/sign/electricshock + name = "\improper HIGH VOLTAGE" + desc = "A warning sign which reads 'HIGH VOLTAGE'" + icon_state = "shock" + +/obj/structure/sign/examroom + name = "\improper EXAM" + desc = "A guidance sign which reads 'EXAM ROOM'" + icon_state = "examroom" + +/obj/structure/sign/vacuum + name = "\improper HARD VACUUM AHEAD" + desc = "A warning sign which reads 'HARD VACUUM AHEAD'" + icon_state = "space" + +/obj/structure/sign/vacuum/external + name = "\improper EXTERNAL AIRLOCK" + desc = "A warning sign which reads 'EXTERNAL AIRLOCK'." + layer = MOB_LAYER + +/obj/structure/sign/deathsposal + name = "\improper DISPOSAL LEADS TO SPACE" + desc = "A warning sign which reads 'DISPOSAL LEADS TO SPACE'" + icon_state = "deathsposal" + +/obj/structure/sign/pods + name = "\improper ESCAPE PODS" + desc = "A warning sign which reads 'ESCAPE PODS'" + icon_state = "pods" + +/obj/structure/sign/fire + name = "\improper DANGER: FIRE" + desc = "A warning sign which reads 'DANGER: FIRE'" + icon_state = "fire" + resistance_flags = FIRE_PROOF + +/obj/structure/sign/nosmoking_1 + name = "\improper NO SMOKING" + desc = "A warning sign which reads 'NO SMOKING'" + icon_state = "nosmoking" + resistance_flags = FLAMMABLE + +/obj/structure/sign/nosmoking_2 + name = "\improper NO SMOKING" + desc = "A warning sign which reads 'NO SMOKING'" + icon_state = "nosmoking2" + +/obj/structure/sign/radiation + name = "\improper HAZARDOUS RADIATION" + desc = "A warning sign alerting the user of potential radiation hazards." + icon_state = "radiation" + +/obj/structure/sign/radiation/rad_area + name = "\improper RADIOACTIVE AREA" + desc = "A warning sign which reads 'RADIOACTIVE AREA'." + +/obj/structure/sign/xeno_warning_mining + name = "DANGEROUS ALIEN LIFE" + desc = "A sign that warns would be travellers of hostile alien life in the vicinity." + icon = 'icons/obj/mining.dmi' + icon_state = "xeno_warning" + +/obj/structure/sign/redcross + name = "medbay" + desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here.'" + icon_state = "redcross" + +/obj/structure/sign/greencross + name = "medbay" + desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here.'" + icon_state = "greencross" + +/obj/structure/sign/goldenplaque + name = "The Most Robust Men Award for Robustness" + desc = "To be Robust is not an action or a way of life, but a mental state. Only those with the force of Will strong enough to act during a crisis, saving friend from foe, are truly Robust. Stay Robust my friends." + icon_state = "goldenplaque" + +/obj/structure/sign/kiddieplaque + name = "AI developers plaque" + desc = "Next to the extremely long list of names and job titles, there is a drawing of a little child. The child appears to be retarded. Beneath the image, someone has scratched the word \"PACKETS\"." + icon_state = "kiddieplaque" + +/obj/structure/sign/atmosplaque + name = "\improper ZAS Atmospherics Division plaque" + desc = "This plaque commemorates the fall of the Atmos ZAS division. For all the charred, dizzy, and brittle men who have died in its horrible hands." + icon_state = "atmosplaque" + +/obj/structure/sign/kidanplaque + name = "Kidan wall trophy" + desc = "A dead and stuffed Diona nymph, mounted on a board." + icon_state = "kidanplaque" + +/obj/structure/sign/mech + name = "\improper mech painting" + desc = "A painting of a mech" + icon_state = "mech" + +/obj/structure/sign/nuke + name = "\improper nuke painting" + desc = "A painting of a nuke" + icon_state = "nuke" + +/obj/structure/sign/clown + name = "\improper clown painting" + desc = "A painting of the clown and mime. Awwww." + icon_state = "clown" + +/obj/structure/sign/bobross + name = "\improper calming painting" + desc = "We don't make mistakes, just happy little accidents." + icon_state = "bob" + +/obj/structure/sign/singulo + name = "\improper singulo painting" + desc = "A mesmerizing painting of a singularity. It seems to suck you in..." + icon_state = "singulo" + +/obj/structure/sign/barber + name = "\improper barber shop sign" + desc = "A spinning sign indicating a barbershop is near." + icon_state = "barber" + +/obj/structure/sign/chinese + name = "\improper chinese restaurant sign" + desc = "A glowing dragon invites you in." + icon_state = "chinese" + +/obj/structure/sign/science + name = "\improper SCIENCE!" + desc = "A warning sign which reads 'SCIENCE!'" + icon_state = "science1" + +/obj/structure/sign/chemistry + name = "\improper CHEMISTRY" + desc = "A warning sign which reads 'CHEMISTRY'" + icon_state = "chemistry1" + +/obj/structure/sign/botany + name = "\improper HYDROPONICS" + desc = "A warning sign which reads 'HYDROPONICS'" + icon_state = "hydro1" + +/obj/structure/sign/xenobio + name = "\improper XENOBIOLOGY" + desc = "A sign labelling an area as a place where xenobiological entities are researched." + icon_state = "xenobio" + +/obj/structure/sign/evac + name = "\improper EVACUATION" + desc = "A sign labelling an area where evacuation procedures take place." + icon_state = "evac" + +/obj/structure/sign/drop + name = "\improper DROP PODS" + desc = "A sign labelling an area where drop pod loading procedures take place." + icon_state = "drop" + +/obj/structure/sign/custodian + name = "\improper CUSTODIAN" + desc = "A sign labelling an area where the custodian works." + icon_state = "custodian" + +/obj/structure/sign/engineering + name = "\improper ENGINEERING" + desc = "A sign labelling an area where engineers work." + icon_state = "engine" + +/obj/structure/sign/cargo + name = "\improper CARGO" + desc = "A sign labelling an area where cargo ships dock." + icon_state = "cargo" + +/obj/structure/sign/security + name = "\improper SECURITY" + desc = "A sign labelling an area where the law is law." + icon_state = "security" + +/obj/structure/sign/holy + name = "\improper HOLY" + desc = "A sign labelling a religious area." + icon_state = "holy" + +/obj/structure/sign/restroom + name = "\improper RESTROOM" + desc = "A sign labelling a restroom." + icon_state = "restroom" + +/obj/structure/sign/medbay + name = "\improper MEDBAY" + desc = "The Intergalactic symbol of Medical institutions. You'll probably get help here." + icon_state = "bluecross" + +/obj/structure/sign/medbay/alt + icon_state = "bluecross2" + +/obj/structure/sign/directions/science + name = "\improper Research Division" + desc = "A direction sign, pointing out which way the Research Division is." + icon_state = "direction_sci" + +/obj/structure/sign/directions/engineering + name = "\improper Engineering Department" + desc = "A direction sign, pointing out which way the Engineering department is." + icon_state = "direction_eng" + +/obj/structure/sign/directions/security + name = "\improper Security Department" + desc = "A direction sign, pointing out which way the Security department is." + icon_state = "direction_sec" + +/obj/structure/sign/directions/medical + name = "\improper Medical Bay" + desc = "A direction sign, pointing out which way Medical Bay is." + icon_state = "direction_med" + +/obj/structure/sign/directions/evac + name = "\improper Escape Arm" + desc = "A direction sign, pointing out which way escape shuttle dock is." + icon_state = "direction_evac" + +/obj/structure/sign/directions/cargo + name = "\improper Cargo Department" + desc = "A direction sign, pointing out which way the Cargo department is." + icon_state = "direction_supply" + +/obj/structure/sign/explosives + name = "\improper HIGH EXPLOSIVES" + desc = "A warning sign which reads 'HIGH EXPLOSIVES'." + icon_state = "explosives" + +/obj/structure/sign/explosives/alt + name = "\improper HIGH EXPLOSIVES" + desc = "A warning sign which reads 'HIGH EXPLOSIVES'." + icon_state = "explosives2" diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm index b3b3157c391..9a10507ba39 100644 --- a/code/game/objects/structures/spawner.dm +++ b/code/game/objects/structures/spawner.dm @@ -97,4 +97,4 @@ spawn_time = 600 mob_types = list(/mob/living/simple_animal/hostile/headcrab, /mob/living/simple_animal/hostile/headcrab/fast, /mob/living/simple_animal/hostile/headcrab/poison) spawn_text = "crawls out of" - faction = list("hostile") \ No newline at end of file + faction = list("hostile") diff --git a/code/game/objects/structures/spirit_board.dm b/code/game/objects/structures/spirit_board.dm index dc82b824e34..47731fd48d3 100644 --- a/code/game/objects/structures/spirit_board.dm +++ b/code/game/objects/structures/spirit_board.dm @@ -77,4 +77,4 @@ to_chat(M, "There aren't enough people to use the [src.name]!") return 0 - return 1 \ No newline at end of file + return 1 diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm index 58c0aa32c9f..80fd69597c4 100644 --- a/code/game/objects/structures/statues.dm +++ b/code/game/objects/structures/statues.dm @@ -335,4 +335,4 @@ icon = 'icons/obj/decorations.dmi' icon_state = "chickenstatue" anchored = 1 - density = 1 \ No newline at end of file + density = 1 diff --git a/code/game/objects/structures/stool_bed_chair_nest/alien_nests.dm b/code/game/objects/structures/stool_bed_chair_nest/alien_nests.dm index 6a2180efcac..b303df8079a 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/alien_nests.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/alien_nests.dm @@ -1,91 +1,91 @@ -//Alium nests. Essentially beds with an unbuckle delay that only aliums can buckle mobs to. - -/obj/structure/bed/nest - name = "alien nest" - desc = "It's a gruesome pile of thick, sticky resin shaped like a nest." - icon = 'icons/mob/alien.dmi' - icon_state = "nest" - max_integrity = 120 - var/image/nest_overlay - comfort = 0 - flags = NODECONSTRUCT - -/obj/structure/bed/nest/New() - nest_overlay = image('icons/mob/alien.dmi', "nestoverlay", layer=MOB_LAYER - 0.2) - return ..() - -/obj/structure/bed/nest/user_unbuckle_mob(mob/living/user) - if(has_buckled_mobs()) - for(var/buck in buckled_mobs) //breaking a nest releases all the buckled mobs, because the nest isn't holding them down anymore - var/mob/living/M = buck - - if(user.get_int_organ(/obj/item/organ/internal/xenos/plasmavessel)) - unbuckle_mob(M) - add_fingerprint(user) - return - - if(M != user) - M.visible_message("[user.name] pulls [M.name] free from the sticky nest!",\ - "[user.name] pulls you free from the gelatinous resin.",\ - "You hear squelching...") - else - M.visible_message("[M.name] struggles to break free from the gelatinous resin!",\ - "You struggle to break free from the gelatinous resin... (Stay still for two minutes.)",\ - "You hear squelching...") - if(!do_after(M, 1200, target = src)) - if(M && M.buckled) - to_chat(M, "You fail to escape \the [src]!") - return - if(!M.buckled) - return - M.visible_message("[M.name] breaks free from the gelatinous resin!",\ - "You break free from the gelatinous resin!",\ - "You hear squelching...") - - unbuckle_mob(M) - add_fingerprint(user) - - -/obj/structure/bed/nest/user_buckle_mob(mob/living/M, mob/living/user) - if (!ismob(M) || (get_dist(src, user) > 1) || (M.loc != loc) || user.incapacitated() || M.buckled) - return - - if(M.get_int_organ(/obj/item/organ/internal/xenos/plasmavessel)) - return - - if(!user.get_int_organ(/obj/item/organ/internal/xenos/plasmavessel)) - return - - if(has_buckled_mobs()) - unbuckle_all_mobs() - - if(buckle_mob(M)) - M.visible_message("[user.name] secretes a thick vile goo, securing [M.name] into [src]!",\ - "[user.name] drenches you in a foul-smelling resin, trapping you in [src]!",\ - "You hear squelching...") - - -/obj/structure/bed/nest/post_buckle_mob(mob/living/M) - M.pixel_y = 0 - M.pixel_x = initial(M.pixel_x) + 2 - M.layer = BELOW_MOB_LAYER - add_overlay(nest_overlay) - -/obj/structure/bed/nest/post_unbuckle_mob(mob/living/M) - M.pixel_x = M.get_standard_pixel_x_offset(M.lying) - M.pixel_y = M.get_standard_pixel_y_offset(M.lying) - M.layer = initial(M.layer) - cut_overlay(nest_overlay) - -/obj/structure/bed/nest/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - playsound(loc, 'sound/effects/attackblob.ogg', 100, TRUE) - if(BURN) - playsound(loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/structure/bed/nest/attack_alien(mob/living/carbon/alien/user) - if(user.a_intent != INTENT_HARM) - return attack_hand(user) - else - return ..() \ No newline at end of file +//Alium nests. Essentially beds with an unbuckle delay that only aliums can buckle mobs to. + +/obj/structure/bed/nest + name = "alien nest" + desc = "It's a gruesome pile of thick, sticky resin shaped like a nest." + icon = 'icons/mob/alien.dmi' + icon_state = "nest" + max_integrity = 120 + var/image/nest_overlay + comfort = 0 + flags = NODECONSTRUCT + +/obj/structure/bed/nest/New() + nest_overlay = image('icons/mob/alien.dmi', "nestoverlay", layer=MOB_LAYER - 0.2) + return ..() + +/obj/structure/bed/nest/user_unbuckle_mob(mob/living/user) + if(has_buckled_mobs()) + for(var/buck in buckled_mobs) //breaking a nest releases all the buckled mobs, because the nest isn't holding them down anymore + var/mob/living/M = buck + + if(user.get_int_organ(/obj/item/organ/internal/xenos/plasmavessel)) + unbuckle_mob(M) + add_fingerprint(user) + return + + if(M != user) + M.visible_message("[user.name] pulls [M.name] free from the sticky nest!",\ + "[user.name] pulls you free from the gelatinous resin.",\ + "You hear squelching...") + else + M.visible_message("[M.name] struggles to break free from the gelatinous resin!",\ + "You struggle to break free from the gelatinous resin... (Stay still for two minutes.)",\ + "You hear squelching...") + if(!do_after(M, 1200, target = src)) + if(M && M.buckled) + to_chat(M, "You fail to escape \the [src]!") + return + if(!M.buckled) + return + M.visible_message("[M.name] breaks free from the gelatinous resin!",\ + "You break free from the gelatinous resin!",\ + "You hear squelching...") + + unbuckle_mob(M) + add_fingerprint(user) + + +/obj/structure/bed/nest/user_buckle_mob(mob/living/M, mob/living/user) + if (!ismob(M) || (get_dist(src, user) > 1) || (M.loc != loc) || user.incapacitated() || M.buckled) + return + + if(M.get_int_organ(/obj/item/organ/internal/xenos/plasmavessel)) + return + + if(!user.get_int_organ(/obj/item/organ/internal/xenos/plasmavessel)) + return + + if(has_buckled_mobs()) + unbuckle_all_mobs() + + if(buckle_mob(M)) + M.visible_message("[user.name] secretes a thick vile goo, securing [M.name] into [src]!",\ + "[user.name] drenches you in a foul-smelling resin, trapping you in [src]!",\ + "You hear squelching...") + + +/obj/structure/bed/nest/post_buckle_mob(mob/living/M) + M.pixel_y = 0 + M.pixel_x = initial(M.pixel_x) + 2 + M.layer = BELOW_MOB_LAYER + add_overlay(nest_overlay) + +/obj/structure/bed/nest/post_unbuckle_mob(mob/living/M) + M.pixel_x = M.get_standard_pixel_x_offset(M.lying) + M.pixel_y = M.get_standard_pixel_y_offset(M.lying) + M.layer = initial(M.layer) + cut_overlay(nest_overlay) + +/obj/structure/bed/nest/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + playsound(loc, 'sound/effects/attackblob.ogg', 100, TRUE) + if(BURN) + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/bed/nest/attack_alien(mob/living/carbon/alien/user) + if(user.a_intent != INTENT_HARM) + return attack_hand(user) + else + return ..() diff --git a/code/game/objects/structures/stool_bed_chair_nest/bed.dm b/code/game/objects/structures/stool_bed_chair_nest/bed.dm index b823cc1b623..9d0d4568e73 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/bed.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/bed.dm @@ -1,176 +1,176 @@ -/* Beds... get your mind out of the gutter, they're for sleeping! - * Contains: - * Beds - * Roller beds - * Dog Beds - */ - -/* - * Beds - */ - -/obj/structure/bed - name = "bed" - desc = "This is used to lie in, sleep in or strap on." - icon = 'icons/obj/objects.dmi' - icon_state = "bed" - can_buckle = TRUE - anchored = TRUE - buckle_lying = TRUE - resistance_flags = FLAMMABLE - max_integrity = 100 - integrity_failure = 30 - var/buildstacktype = /obj/item/stack/sheet/metal - var/buildstackamount = 2 - buckle_offset = -6 - var/comfort = 2 // default comfort - -/obj/structure/bed/psych - name = "psych bed" - desc = "For prime comfort during psychiatric evaluations." - icon_state = "psychbed" - buildstackamount = 5 - -/obj/structure/bed/alien - name = "resting contraption" - desc = "This looks similar to contraptions from Earth. Could aliens be stealing our technology?" - icon_state = "abed" - comfort = 0.3 - -/obj/structure/bed/proc/handle_rotation() - return - -/obj/structure/bed/wrench_act(mob/user, obj/item/I) - . = TRUE - if(flags & NODECONSTRUCT) - to_chat(user, "You can't figure out how to deconstruct [src]!") - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - deconstruct(TRUE) - -/obj/structure/bed/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(buildstacktype) - new buildstacktype(loc, buildstackamount) - ..() - - -/* - * Roller beds - */ - -/obj/structure/bed/roller - name = "roller bed" - icon = 'icons/obj/rollerbed.dmi' - icon_state = "down" - resistance_flags = NONE - anchored = FALSE - comfort = 1 - -/obj/structure/bed/roller/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/roller_holder)) - if(has_buckled_mobs()) - if(buckled_mobs.len > 1) - unbuckle_all_mobs() - user.visible_message("[user] unbuckles all creatures from [src].") - else - user_unbuckle_mob(buckled_mobs[1], user) - else - user.visible_message("[user] collapses \the [name].", "You collapse \the [name].") - new/obj/item/roller(get_turf(src)) - qdel(src) - else - return ..() - -/obj/structure/bed/roller/post_buckle_mob(mob/living/M) - density = TRUE - icon_state = "up" - M.pixel_y = initial(M.pixel_y) - -/obj/structure/bed/roller/post_unbuckle_mob(mob/living/M) - density = FALSE - icon_state = "down" - M.pixel_x = M.get_standard_pixel_x_offset(M.lying) - M.pixel_y = M.get_standard_pixel_y_offset(M.lying) - -/obj/item/roller - name = "roller bed" - desc = "A collapsed roller bed that can be carried around." - icon = 'icons/obj/rollerbed.dmi' - icon_state = "folded" - w_class = WEIGHT_CLASS_BULKY // Can't be put in backpacks. - -/obj/item/roller/attack_self(mob/user) - var/obj/structure/bed/roller/R = new /obj/structure/bed/roller(user.loc) - R.add_fingerprint(user) - qdel(src) - -/obj/item/roller/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/roller_holder)) - var/obj/item/roller_holder/RH = W - if(!RH.held) - user.visible_message("[user] collects \the [name].", "You collect \the [name].") - forceMove(RH) - RH.held = src - -/obj/structure/bed/roller/MouseDrop(over_object, src_location, over_location) - ..() - if(over_object == usr && Adjacent(usr) && (in_range(src, usr) || usr.contents.Find(src))) - if(!ishuman(usr)) - return - if(has_buckled_mobs()) - return 0 - usr.visible_message("[usr] collapses \the [name].", "You collapse \the [name].") - new/obj/item/roller(get_turf(src)) - qdel(src) - -/obj/item/roller_holder - name = "roller bed rack" - desc = "A rack for carrying a collapsed roller bed." - icon = 'icons/obj/rollerbed.dmi' - icon_state = "folded" - var/obj/item/roller/held - -/obj/item/roller_holder/New() - ..() - held = new /obj/item/roller(src) - -/obj/item/roller_holder/attack_self(mob/user as mob) - if(!held) - to_chat(user, " The rack is empty.") - return - - to_chat(user, "You deploy the roller bed.") - var/obj/structure/bed/roller/R = new /obj/structure/bed/roller(user.loc) - R.add_fingerprint(user) - QDEL_NULL(held) - -/* - * Dog beds - */ - -/obj/structure/bed/dogbed - name = "dog bed" - icon_state = "dogbed" - desc = "A comfy-looking dog bed. You can even strap your pet in, just in case the gravity turns off." - anchored = FALSE - buildstackamount = 10 - buildstacktype = /obj/item/stack/sheet/wood - buckle_offset = 0 - comfort = 0.5 - -/obj/structure/bed/dogbed/ian - name = "Ian's bed" - desc = "Ian's bed! Looks comfy." - anchored = TRUE - -/obj/structure/bed/dogbed/renault - desc = "Renault's bed! Looks comfy. A foxy person needs a foxy pet." - name = "Renault's bed" - anchored = TRUE - -/obj/structure/bed/dogbed/runtime - desc = "A comfy-looking cat bed. You can even strap your pet in, in case the gravity turns off." - name = "Runtime's bed" - anchored = TRUE +/* Beds... get your mind out of the gutter, they're for sleeping! + * Contains: + * Beds + * Roller beds + * Dog Beds + */ + +/* + * Beds + */ + +/obj/structure/bed + name = "bed" + desc = "This is used to lie in, sleep in or strap on." + icon = 'icons/obj/objects.dmi' + icon_state = "bed" + can_buckle = TRUE + anchored = TRUE + buckle_lying = TRUE + resistance_flags = FLAMMABLE + max_integrity = 100 + integrity_failure = 30 + var/buildstacktype = /obj/item/stack/sheet/metal + var/buildstackamount = 2 + buckle_offset = -6 + var/comfort = 2 // default comfort + +/obj/structure/bed/psych + name = "psych bed" + desc = "For prime comfort during psychiatric evaluations." + icon_state = "psychbed" + buildstackamount = 5 + +/obj/structure/bed/alien + name = "resting contraption" + desc = "This looks similar to contraptions from Earth. Could aliens be stealing our technology?" + icon_state = "abed" + comfort = 0.3 + +/obj/structure/bed/proc/handle_rotation() + return + +/obj/structure/bed/wrench_act(mob/user, obj/item/I) + . = TRUE + if(flags & NODECONSTRUCT) + to_chat(user, "You can't figure out how to deconstruct [src]!") + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + deconstruct(TRUE) + +/obj/structure/bed/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(buildstacktype) + new buildstacktype(loc, buildstackamount) + ..() + + +/* + * Roller beds + */ + +/obj/structure/bed/roller + name = "roller bed" + icon = 'icons/obj/rollerbed.dmi' + icon_state = "down" + resistance_flags = NONE + anchored = FALSE + comfort = 1 + +/obj/structure/bed/roller/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/roller_holder)) + if(has_buckled_mobs()) + if(buckled_mobs.len > 1) + unbuckle_all_mobs() + user.visible_message("[user] unbuckles all creatures from [src].") + else + user_unbuckle_mob(buckled_mobs[1], user) + else + user.visible_message("[user] collapses \the [name].", "You collapse \the [name].") + new/obj/item/roller(get_turf(src)) + qdel(src) + else + return ..() + +/obj/structure/bed/roller/post_buckle_mob(mob/living/M) + density = TRUE + icon_state = "up" + M.pixel_y = initial(M.pixel_y) + +/obj/structure/bed/roller/post_unbuckle_mob(mob/living/M) + density = FALSE + icon_state = "down" + M.pixel_x = M.get_standard_pixel_x_offset(M.lying) + M.pixel_y = M.get_standard_pixel_y_offset(M.lying) + +/obj/item/roller + name = "roller bed" + desc = "A collapsed roller bed that can be carried around." + icon = 'icons/obj/rollerbed.dmi' + icon_state = "folded" + w_class = WEIGHT_CLASS_BULKY // Can't be put in backpacks. + +/obj/item/roller/attack_self(mob/user) + var/obj/structure/bed/roller/R = new /obj/structure/bed/roller(user.loc) + R.add_fingerprint(user) + qdel(src) + +/obj/item/roller/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/roller_holder)) + var/obj/item/roller_holder/RH = W + if(!RH.held) + user.visible_message("[user] collects \the [name].", "You collect \the [name].") + forceMove(RH) + RH.held = src + +/obj/structure/bed/roller/MouseDrop(over_object, src_location, over_location) + ..() + if(over_object == usr && Adjacent(usr) && (in_range(src, usr) || usr.contents.Find(src))) + if(!ishuman(usr)) + return + if(has_buckled_mobs()) + return 0 + usr.visible_message("[usr] collapses \the [name].", "You collapse \the [name].") + new/obj/item/roller(get_turf(src)) + qdel(src) + +/obj/item/roller_holder + name = "roller bed rack" + desc = "A rack for carrying a collapsed roller bed." + icon = 'icons/obj/rollerbed.dmi' + icon_state = "folded" + var/obj/item/roller/held + +/obj/item/roller_holder/New() + ..() + held = new /obj/item/roller(src) + +/obj/item/roller_holder/attack_self(mob/user as mob) + if(!held) + to_chat(user, " The rack is empty.") + return + + to_chat(user, "You deploy the roller bed.") + var/obj/structure/bed/roller/R = new /obj/structure/bed/roller(user.loc) + R.add_fingerprint(user) + QDEL_NULL(held) + +/* + * Dog beds + */ + +/obj/structure/bed/dogbed + name = "dog bed" + icon_state = "dogbed" + desc = "A comfy-looking dog bed. You can even strap your pet in, just in case the gravity turns off." + anchored = FALSE + buildstackamount = 10 + buildstacktype = /obj/item/stack/sheet/wood + buckle_offset = 0 + comfort = 0.5 + +/obj/structure/bed/dogbed/ian + name = "Ian's bed" + desc = "Ian's bed! Looks comfy." + anchored = TRUE + +/obj/structure/bed/dogbed/renault + desc = "Renault's bed! Looks comfy. A foxy person needs a foxy pet." + name = "Renault's bed" + anchored = TRUE + +/obj/structure/bed/dogbed/runtime + desc = "A comfy-looking cat bed. You can even strap your pet in, in case the gravity turns off." + name = "Runtime's bed" + anchored = TRUE diff --git a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm index 90b489300d3..c1fe7dd7013 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm @@ -1,465 +1,465 @@ -/obj/structure/chair // fuck you Pete - name = "chair" - desc = "You sit in this. Either by will or force." - icon = 'icons/obj/chairs.dmi' - icon_state = "chair" - layer = OBJ_LAYER - can_buckle = TRUE - buckle_lying = FALSE // you sit in a chair, not lay - anchored = TRUE - resistance_flags = NONE - max_integrity = 250 - integrity_failure = 25 - buckle_offset = 0 - var/buildstacktype = /obj/item/stack/sheet/metal - var/buildstackamount = 1 - var/item_chair = /obj/item/chair // if null it can't be picked up - var/movable = FALSE // For mobility checks - var/propelled = FALSE // Check for fire-extinguisher-driven chairs - var/comfort = 0 - -/obj/structure/chair/New() - ..() - spawn(3) //sorry. i don't think there's a better way to do this. - handle_rotation() - return - -/obj/structure/chair/narsie_act() - if(prob(20)) - var/obj/structure/chair/wood/W = new/obj/structure/chair/wood(get_turf(src)) - W.setDir(dir) - qdel(src) - -/obj/structure/chair/ratvar_act() - var/obj/structure/chair/brass/B = new(get_turf(src)) - B.setDir(dir) - qdel(src) - -/obj/structure/chair/Move(atom/newloc, direct) - ..() - handle_rotation() - -/obj/structure/chair/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/assembly/shock_kit)) - var/obj/item/assembly/shock_kit/SK = W - if(!SK.status) - to_chat(user, "[SK] is not ready to be attached!") - return - user.drop_item() - var/obj/structure/chair/e_chair/E = new /obj/structure/chair/e_chair(src.loc) - playsound(src.loc, W.usesound, 50, 1) - E.dir = dir - E.part = SK - SK.loc = E - SK.master = E - qdel(src) - return - return ..() - -/obj/structure/chair/wrench_act(mob/user, obj/item/I) - . = TRUE - if(flags & NODECONSTRUCT) - to_chat(user, "Try as you might, you can't figure out how to deconstruct [src].") - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - deconstruct(TRUE) - -/obj/structure/chair/deconstruct() - // If we have materials, and don't have the NOCONSTRUCT flag - if(buildstacktype && (!(flags & NODECONSTRUCT))) - new buildstacktype(loc, buildstackamount) - ..() - -/obj/structure/chair/MouseDrop(over_object, src_location, over_location) - . = ..() - if(over_object == usr && Adjacent(usr)) - if(!item_chair || has_buckled_mobs()) - return - if(usr.incapacitated()) - to_chat(usr, "You can't do that right now!") - return - if(!usr.has_right_hand() && !usr.has_left_hand()) - to_chat(usr, "You try to grab the chair, but you are missing both of your hands!") - return - if(usr.get_active_hand() && usr.get_inactive_hand()) - to_chat(usr, "You try to grab the chair, but your hands are already full!") - return - if(!ishuman(usr)) - return - usr.visible_message("[usr] grabs \the [src.name].", "You grab \the [src.name].") - var/C = new item_chair(loc) - usr.put_in_hands(C) - qdel(src) - -/obj/structure/chair/attack_tk(mob/user as mob) - if(!anchored || has_buckled_mobs() || !isturf(user.loc)) - ..() - else - rotate() - -/obj/structure/chair/proc/handle_rotation(direction) - handle_layer() - if(has_buckled_mobs()) - for(var/m in buckled_mobs) - var/mob/living/buckled_mob = m - buckled_mob.setDir(direction) - -/obj/structure/chair/proc/handle_layer() - if(has_buckled_mobs() && dir == NORTH) - layer = ABOVE_MOB_LAYER - else - layer = OBJ_LAYER - -/obj/structure/chair/post_buckle_mob(mob/living/M) - . = ..() - handle_layer() - -/obj/structure/chair/post_unbuckle_mob() - . = ..() - handle_layer() - -/obj/structure/chair/setDir(newdir) - ..() - handle_rotation(newdir) - -/obj/structure/chair/verb/rotate() - set name = "Rotate Chair" - set category = "Object" - set src in oview(1) - - if(config.ghost_interaction) - setDir(turn(dir, 90)) - handle_rotation() - return - - if(usr.incapacitated()) - return - - setDir(turn(dir, 90)) - handle_rotation() - -/obj/structure/chair/AltClick(mob/user) - if(user.incapacitated()) - to_chat(user, "You can't do that right now!") - return - if(!Adjacent(user)) - return - rotate() - -// Chair types -/obj/structure/chair/wood - name = "wooden chair" - desc = "Old is never too old to not be in fashion." - icon_state = "wooden_chair" - resistance_flags = FLAMMABLE - max_integrity = 70 - buildstackamount = 3 - buildstacktype = /obj/item/stack/sheet/wood - item_chair = /obj/item/chair/wood - -/obj/structure/chair/wood/narsie_act() - return - -/obj/structure/chair/wood/wings - icon_state = "wooden_chair_wings" - item_chair = /obj/item/chair/wood/wings - -/obj/structure/chair/comfy - name = "comfy chair" - desc = "It looks comfy." - icon_state = "comfychair" - color = rgb(255, 255, 255) - resistance_flags = FLAMMABLE - max_integrity = 70 - buildstackamount = 2 - item_chair = null - var/image/armrest = null - -/obj/structure/chair/comfy/Initialize(mapload) - armrest = GetArmrest() - armrest.layer = ABOVE_MOB_LAYER - return ..() - -/obj/structure/chair/comfy/proc/GetArmrest() - return mutable_appearance('icons/obj/chairs.dmi', "comfychair_armrest") - -/obj/structure/chair/comfy/Destroy() - QDEL_NULL(armrest) - return ..() - -/obj/structure/chair/comfy/post_buckle_mob(mob/living/M) - . = ..() - update_armrest() - -/obj/structure/chair/comfy/post_unbuckle_mob(mob/living/M) - . = ..() - update_armrest() - -/obj/structure/chair/comfy/proc/update_armrest() - if(has_buckled_mobs()) - add_overlay(armrest) - else - cut_overlay(armrest) - -/obj/structure/chair/comfy/brown - color = rgb(141,70,0) - -/obj/structure/chair/comfy/red - color = rgb(218,2,10) - -/obj/structure/chair/comfy/teal - color = rgb(0,234,250) - -/obj/structure/chair/comfy/black - color = rgb(60,60,60) - -/obj/structure/chair/comfy/green - color = rgb(1,196,8) - -/obj/structure/chair/comfy/purp - color = rgb(112,2,176) - -/obj/structure/chair/comfy/blue - color = rgb(2,9,210) - -/obj/structure/chair/comfy/beige - color = rgb(255,253,195) - -/obj/structure/chair/comfy/lime - color = rgb(255,251,0) - -/obj/structure/chair/office - anchored = FALSE - movable = TRUE - item_chair = null - buildstackamount = 5 - -/obj/structure/chair/comfy/shuttle - name = "shuttle seat" - desc = "A comfortable, secure seat. It has a more sturdy looking buckling system, for smoother flights." - icon_state = "shuttle_chair" - -/obj/structure/chair/comfy/shuttle/GetArmrest() - return mutable_appearance('icons/obj/chairs.dmi', "shuttle_chair_armrest") - -/obj/structure/chair/office/Bump(atom/A) - ..() - if(!has_buckled_mobs()) - return - - if(propelled) - for(var/m in buckled_mobs) - var/mob/living/buckled_mob = m - unbuckle_mob(buckled_mob) - buckled_mob.throw_at(A, 3, propelled) - buckled_mob.apply_effect(6, STUN, 0) - buckled_mob.apply_effect(6, WEAKEN, 0) - buckled_mob.apply_effect(6, STUTTER, 0) - buckled_mob.take_organ_damage(10) - playsound(loc, 'sound/weapons/punch1.ogg', 50, 1, -1) - buckled_mob.visible_message("[buckled_mob] crashed into [A]!") - -/obj/structure/chair/office/light - icon_state = "officechair_white" - -/obj/structure/chair/office/dark - icon_state = "officechair_dark" - -/obj/structure/chair/barber - icon_state = "barber_chair" - buildstackamount = 1 - item_chair = null - -// Sofas - -/obj/structure/chair/sofa - name = "sofa" - icon_state = "sofamiddle" - anchored = TRUE - item_chair = null - buildstackamount = 1 - -/obj/structure/chair/sofa/left - icon_state = "sofaend_left" - -/obj/structure/chair/sofa/right - icon_state = "sofaend_right" - -/obj/structure/chair/sofa/corner - icon_state = "sofacorner" - -/obj/structure/chair/stool - name = "stool" - desc = "Apply butt." - icon_state = "stool" - can_buckle = FALSE - item_chair = /obj/item/chair/stool - -/obj/structure/chair/stool/bar - name = "bar stool" - desc = "It has some unsavory stains on it..." - icon_state = "bar" - item_chair = /obj/item/chair/stool/bar - -/obj/item/chair - name = "chair" - desc = "Bar brawl essential." - icon = 'icons/obj/chairs.dmi' - icon_state = "chair_toppled" - item_state = "chair" - lefthand_file = 'icons/mob/inhands/chairs_lefthand.dmi' - righthand_file = 'icons/mob/inhands/chairs_righthand.dmi' - w_class = WEIGHT_CLASS_HUGE - force = 8 - throwforce = 10 - throw_range = 3 - hitsound = 'sound/items/trayhit1.ogg' - hit_reaction_chance = 50 - materials = list(MAT_METAL = 2000) - var/break_chance = 5 //Likely hood of smashing the chair. - var/obj/structure/chair/origin_type = /obj/structure/chair - -/obj/item/chair/stool - name = "stool" - icon = 'icons/obj/chairs.dmi' - icon_state = "stool_toppled" - item_state = "stool" - force = 10 - throwforce = 10 - w_class = WEIGHT_CLASS_HUGE - origin_type = /obj/structure/chair/stool - break_chance = 0 //It's too sturdy. - -/obj/item/chair/stool/bar - name = "bar stool" - icon_state = "bar_toppled" - item_state = "stool_bar" - origin_type = /obj/structure/chair/stool/bar - -/obj/item/chair/attack_self(mob/user) - plant(user) - -/obj/item/chair/proc/plant(mob/user) - for(var/obj/A in get_turf(loc)) - if(istype(A, /obj/structure/chair)) - to_chat(user, "There is already a chair here.") - return - - user.visible_message("[user] rights \the [src.name].", "You right \the [name].") - var/obj/structure/chair/C = new origin_type(get_turf(loc)) - C.setDir(dir) - qdel(src) - -/obj/item/chair/proc/smash(mob/living/user) - var/stack_type = initial(origin_type.buildstacktype) - if(!stack_type) - return - var/remaining_mats = initial(origin_type.buildstackamount) - remaining_mats-- //Part of the chair was rendered completely unusable. It magically dissapears. Maybe make some dirt? - if(remaining_mats) - for(var/M=1 to remaining_mats) - new stack_type(get_turf(loc)) - qdel(src) - -/obj/item/chair/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == UNARMED_ATTACK && prob(hit_reaction_chance)) - owner.visible_message("[owner] fends off [attack_text] with [src]!") - return 1 - return 0 - -/obj/item/chair/afterattack(atom/target, mob/living/carbon/user, proximity) - ..() - if(!proximity) - return - if(prob(break_chance)) - user.visible_message("[user] smashes \the [src] to pieces against \the [target]") - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(C.health < C.maxHealth*0.5) - C.apply_effect(6, STUN, 0) - C.apply_effect(6, WEAKEN, 0) - C.apply_effect(6, STUTTER, 0) - playsound(src.loc, 'sound/weapons/punch1.ogg', 50, 1, -1) - smash(user) - -/obj/item/chair/stool/attack_self(mob/user as mob) - ..() - new origin_type(get_turf(loc)) - user.unEquip(src) - user.visible_message("[user] puts [src] down.", "You put [src] down.") - qdel(src) - -/obj/item/chair/stool/attack(mob/M as mob, mob/user as mob) - if(prob(5) && istype(M,/mob/living)) - user.visible_message("[user] breaks [src] over [M]'s back!.") - user.unEquip(src) - var/obj/item/stack/sheet/metal/m = new/obj/item/stack/sheet/metal - m.loc = get_turf(src) - qdel(src) - var/mob/living/T = M - T.Weaken(5) - return - ..() - -/obj/item/chair/wood - name = "wooden chair" - icon_state = "wooden_chair_toppled" - item_state = "woodenchair" - resistance_flags = FLAMMABLE - max_integrity = 70 - hitsound = 'sound/weapons/genhit1.ogg' - origin_type = /obj/structure/chair/wood - materials = null - break_chance = 50 - -/obj/item/chair/wood/narsie_act() - return - -/obj/item/chair/wood/wings - icon_state = "wooden_chair_wings_toppled" - origin_type = /obj/structure/chair/wood/wings - -/obj/structure/chair/old - name = "strange chair" - desc = "You sit in this. Either by will or force. Looks REALLY uncomfortable." - icon_state = "chairold" - item_chair = null - -// Brass chair -/obj/structure/chair/brass - name = "brass chair" - desc = "A spinny chair made of brass. It looks uncomfortable." - icon_state = "brass_chair" - max_integrity = 150 - buildstacktype = /obj/item/stack/tile/brass - buildstackamount = 1 - item_chair = null - var/turns = 0 - -/obj/structure/chair/brass/Destroy() - STOP_PROCESSING(SSfastprocess, src) - . = ..() - -/obj/structure/chair/brass/process() - setDir(turn(dir,-90)) - playsound(src, 'sound/effects/servostep.ogg', 50, FALSE) - turns++ - if(turns >= 8) - STOP_PROCESSING(SSfastprocess, src) - -/obj/structure/chair/brass/ratvar_act() - return - -/obj/structure/chair/brass/AltClick(mob/living/user) - turns = 0 - if(!istype(user) || user.incapacitated() || !in_range(src, user)) - return - if(!isprocessing) - user.visible_message("[user] spins [src] around, and Ratvarian technology keeps it spinning FOREVER.", \ - "Automated spinny chairs. The pinnacle of Ratvarian technology.") - START_PROCESSING(SSfastprocess, src) - else - user.visible_message("[user] stops [src]'s uncontrollable spinning.", \ - "You grab [src] and stop its wild spinning.") - STOP_PROCESSING(SSfastprocess, src) \ No newline at end of file +/obj/structure/chair // fuck you Pete + name = "chair" + desc = "You sit in this. Either by will or force." + icon = 'icons/obj/chairs.dmi' + icon_state = "chair" + layer = OBJ_LAYER + can_buckle = TRUE + buckle_lying = FALSE // you sit in a chair, not lay + anchored = TRUE + resistance_flags = NONE + max_integrity = 250 + integrity_failure = 25 + buckle_offset = 0 + var/buildstacktype = /obj/item/stack/sheet/metal + var/buildstackamount = 1 + var/item_chair = /obj/item/chair // if null it can't be picked up + var/movable = FALSE // For mobility checks + var/propelled = FALSE // Check for fire-extinguisher-driven chairs + var/comfort = 0 + +/obj/structure/chair/New() + ..() + spawn(3) //sorry. i don't think there's a better way to do this. + handle_rotation() + return + +/obj/structure/chair/narsie_act() + if(prob(20)) + var/obj/structure/chair/wood/W = new/obj/structure/chair/wood(get_turf(src)) + W.setDir(dir) + qdel(src) + +/obj/structure/chair/ratvar_act() + var/obj/structure/chair/brass/B = new(get_turf(src)) + B.setDir(dir) + qdel(src) + +/obj/structure/chair/Move(atom/newloc, direct) + ..() + handle_rotation() + +/obj/structure/chair/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/assembly/shock_kit)) + var/obj/item/assembly/shock_kit/SK = W + if(!SK.status) + to_chat(user, "[SK] is not ready to be attached!") + return + user.drop_item() + var/obj/structure/chair/e_chair/E = new /obj/structure/chair/e_chair(src.loc) + playsound(src.loc, W.usesound, 50, 1) + E.dir = dir + E.part = SK + SK.loc = E + SK.master = E + qdel(src) + return + return ..() + +/obj/structure/chair/wrench_act(mob/user, obj/item/I) + . = TRUE + if(flags & NODECONSTRUCT) + to_chat(user, "Try as you might, you can't figure out how to deconstruct [src].") + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + deconstruct(TRUE) + +/obj/structure/chair/deconstruct() + // If we have materials, and don't have the NOCONSTRUCT flag + if(buildstacktype && (!(flags & NODECONSTRUCT))) + new buildstacktype(loc, buildstackamount) + ..() + +/obj/structure/chair/MouseDrop(over_object, src_location, over_location) + . = ..() + if(over_object == usr && Adjacent(usr)) + if(!item_chair || has_buckled_mobs()) + return + if(usr.incapacitated()) + to_chat(usr, "You can't do that right now!") + return + if(!usr.has_right_hand() && !usr.has_left_hand()) + to_chat(usr, "You try to grab the chair, but you are missing both of your hands!") + return + if(usr.get_active_hand() && usr.get_inactive_hand()) + to_chat(usr, "You try to grab the chair, but your hands are already full!") + return + if(!ishuman(usr)) + return + usr.visible_message("[usr] grabs \the [src.name].", "You grab \the [src.name].") + var/C = new item_chair(loc) + usr.put_in_hands(C) + qdel(src) + +/obj/structure/chair/attack_tk(mob/user as mob) + if(!anchored || has_buckled_mobs() || !isturf(user.loc)) + ..() + else + rotate() + +/obj/structure/chair/proc/handle_rotation(direction) + handle_layer() + if(has_buckled_mobs()) + for(var/m in buckled_mobs) + var/mob/living/buckled_mob = m + buckled_mob.setDir(direction) + +/obj/structure/chair/proc/handle_layer() + if(has_buckled_mobs() && dir == NORTH) + layer = ABOVE_MOB_LAYER + else + layer = OBJ_LAYER + +/obj/structure/chair/post_buckle_mob(mob/living/M) + . = ..() + handle_layer() + +/obj/structure/chair/post_unbuckle_mob() + . = ..() + handle_layer() + +/obj/structure/chair/setDir(newdir) + ..() + handle_rotation(newdir) + +/obj/structure/chair/verb/rotate() + set name = "Rotate Chair" + set category = "Object" + set src in oview(1) + + if(config.ghost_interaction) + setDir(turn(dir, 90)) + handle_rotation() + return + + if(usr.incapacitated()) + return + + setDir(turn(dir, 90)) + handle_rotation() + +/obj/structure/chair/AltClick(mob/user) + if(user.incapacitated()) + to_chat(user, "You can't do that right now!") + return + if(!Adjacent(user)) + return + rotate() + +// Chair types +/obj/structure/chair/wood + name = "wooden chair" + desc = "Old is never too old to not be in fashion." + icon_state = "wooden_chair" + resistance_flags = FLAMMABLE + max_integrity = 70 + buildstackamount = 3 + buildstacktype = /obj/item/stack/sheet/wood + item_chair = /obj/item/chair/wood + +/obj/structure/chair/wood/narsie_act() + return + +/obj/structure/chair/wood/wings + icon_state = "wooden_chair_wings" + item_chair = /obj/item/chair/wood/wings + +/obj/structure/chair/comfy + name = "comfy chair" + desc = "It looks comfy." + icon_state = "comfychair" + color = rgb(255, 255, 255) + resistance_flags = FLAMMABLE + max_integrity = 70 + buildstackamount = 2 + item_chair = null + var/image/armrest = null + +/obj/structure/chair/comfy/Initialize(mapload) + armrest = GetArmrest() + armrest.layer = ABOVE_MOB_LAYER + return ..() + +/obj/structure/chair/comfy/proc/GetArmrest() + return mutable_appearance('icons/obj/chairs.dmi', "comfychair_armrest") + +/obj/structure/chair/comfy/Destroy() + QDEL_NULL(armrest) + return ..() + +/obj/structure/chair/comfy/post_buckle_mob(mob/living/M) + . = ..() + update_armrest() + +/obj/structure/chair/comfy/post_unbuckle_mob(mob/living/M) + . = ..() + update_armrest() + +/obj/structure/chair/comfy/proc/update_armrest() + if(has_buckled_mobs()) + add_overlay(armrest) + else + cut_overlay(armrest) + +/obj/structure/chair/comfy/brown + color = rgb(141,70,0) + +/obj/structure/chair/comfy/red + color = rgb(218,2,10) + +/obj/structure/chair/comfy/teal + color = rgb(0,234,250) + +/obj/structure/chair/comfy/black + color = rgb(60,60,60) + +/obj/structure/chair/comfy/green + color = rgb(1,196,8) + +/obj/structure/chair/comfy/purp + color = rgb(112,2,176) + +/obj/structure/chair/comfy/blue + color = rgb(2,9,210) + +/obj/structure/chair/comfy/beige + color = rgb(255,253,195) + +/obj/structure/chair/comfy/lime + color = rgb(255,251,0) + +/obj/structure/chair/office + anchored = FALSE + movable = TRUE + item_chair = null + buildstackamount = 5 + +/obj/structure/chair/comfy/shuttle + name = "shuttle seat" + desc = "A comfortable, secure seat. It has a more sturdy looking buckling system, for smoother flights." + icon_state = "shuttle_chair" + +/obj/structure/chair/comfy/shuttle/GetArmrest() + return mutable_appearance('icons/obj/chairs.dmi', "shuttle_chair_armrest") + +/obj/structure/chair/office/Bump(atom/A) + ..() + if(!has_buckled_mobs()) + return + + if(propelled) + for(var/m in buckled_mobs) + var/mob/living/buckled_mob = m + unbuckle_mob(buckled_mob) + buckled_mob.throw_at(A, 3, propelled) + buckled_mob.apply_effect(6, STUN, 0) + buckled_mob.apply_effect(6, WEAKEN, 0) + buckled_mob.apply_effect(6, STUTTER, 0) + buckled_mob.take_organ_damage(10) + playsound(loc, 'sound/weapons/punch1.ogg', 50, 1, -1) + buckled_mob.visible_message("[buckled_mob] crashed into [A]!") + +/obj/structure/chair/office/light + icon_state = "officechair_white" + +/obj/structure/chair/office/dark + icon_state = "officechair_dark" + +/obj/structure/chair/barber + icon_state = "barber_chair" + buildstackamount = 1 + item_chair = null + +// Sofas + +/obj/structure/chair/sofa + name = "sofa" + icon_state = "sofamiddle" + anchored = TRUE + item_chair = null + buildstackamount = 1 + +/obj/structure/chair/sofa/left + icon_state = "sofaend_left" + +/obj/structure/chair/sofa/right + icon_state = "sofaend_right" + +/obj/structure/chair/sofa/corner + icon_state = "sofacorner" + +/obj/structure/chair/stool + name = "stool" + desc = "Apply butt." + icon_state = "stool" + can_buckle = FALSE + item_chair = /obj/item/chair/stool + +/obj/structure/chair/stool/bar + name = "bar stool" + desc = "It has some unsavory stains on it..." + icon_state = "bar" + item_chair = /obj/item/chair/stool/bar + +/obj/item/chair + name = "chair" + desc = "Bar brawl essential." + icon = 'icons/obj/chairs.dmi' + icon_state = "chair_toppled" + item_state = "chair" + lefthand_file = 'icons/mob/inhands/chairs_lefthand.dmi' + righthand_file = 'icons/mob/inhands/chairs_righthand.dmi' + w_class = WEIGHT_CLASS_HUGE + force = 8 + throwforce = 10 + throw_range = 3 + hitsound = 'sound/items/trayhit1.ogg' + hit_reaction_chance = 50 + materials = list(MAT_METAL = 2000) + var/break_chance = 5 //Likely hood of smashing the chair. + var/obj/structure/chair/origin_type = /obj/structure/chair + +/obj/item/chair/stool + name = "stool" + icon = 'icons/obj/chairs.dmi' + icon_state = "stool_toppled" + item_state = "stool" + force = 10 + throwforce = 10 + w_class = WEIGHT_CLASS_HUGE + origin_type = /obj/structure/chair/stool + break_chance = 0 //It's too sturdy. + +/obj/item/chair/stool/bar + name = "bar stool" + icon_state = "bar_toppled" + item_state = "stool_bar" + origin_type = /obj/structure/chair/stool/bar + +/obj/item/chair/attack_self(mob/user) + plant(user) + +/obj/item/chair/proc/plant(mob/user) + for(var/obj/A in get_turf(loc)) + if(istype(A, /obj/structure/chair)) + to_chat(user, "There is already a chair here.") + return + + user.visible_message("[user] rights \the [src.name].", "You right \the [name].") + var/obj/structure/chair/C = new origin_type(get_turf(loc)) + C.setDir(dir) + qdel(src) + +/obj/item/chair/proc/smash(mob/living/user) + var/stack_type = initial(origin_type.buildstacktype) + if(!stack_type) + return + var/remaining_mats = initial(origin_type.buildstackamount) + remaining_mats-- //Part of the chair was rendered completely unusable. It magically dissapears. Maybe make some dirt? + if(remaining_mats) + for(var/M=1 to remaining_mats) + new stack_type(get_turf(loc)) + qdel(src) + +/obj/item/chair/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(attack_type == UNARMED_ATTACK && prob(hit_reaction_chance)) + owner.visible_message("[owner] fends off [attack_text] with [src]!") + return 1 + return 0 + +/obj/item/chair/afterattack(atom/target, mob/living/carbon/user, proximity) + ..() + if(!proximity) + return + if(prob(break_chance)) + user.visible_message("[user] smashes \the [src] to pieces against \the [target]") + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(C.health < C.maxHealth*0.5) + C.apply_effect(6, STUN, 0) + C.apply_effect(6, WEAKEN, 0) + C.apply_effect(6, STUTTER, 0) + playsound(src.loc, 'sound/weapons/punch1.ogg', 50, 1, -1) + smash(user) + +/obj/item/chair/stool/attack_self(mob/user as mob) + ..() + new origin_type(get_turf(loc)) + user.unEquip(src) + user.visible_message("[user] puts [src] down.", "You put [src] down.") + qdel(src) + +/obj/item/chair/stool/attack(mob/M as mob, mob/user as mob) + if(prob(5) && istype(M,/mob/living)) + user.visible_message("[user] breaks [src] over [M]'s back!.") + user.unEquip(src) + var/obj/item/stack/sheet/metal/m = new/obj/item/stack/sheet/metal + m.loc = get_turf(src) + qdel(src) + var/mob/living/T = M + T.Weaken(5) + return + ..() + +/obj/item/chair/wood + name = "wooden chair" + icon_state = "wooden_chair_toppled" + item_state = "woodenchair" + resistance_flags = FLAMMABLE + max_integrity = 70 + hitsound = 'sound/weapons/genhit1.ogg' + origin_type = /obj/structure/chair/wood + materials = null + break_chance = 50 + +/obj/item/chair/wood/narsie_act() + return + +/obj/item/chair/wood/wings + icon_state = "wooden_chair_wings_toppled" + origin_type = /obj/structure/chair/wood/wings + +/obj/structure/chair/old + name = "strange chair" + desc = "You sit in this. Either by will or force. Looks REALLY uncomfortable." + icon_state = "chairold" + item_chair = null + +// Brass chair +/obj/structure/chair/brass + name = "brass chair" + desc = "A spinny chair made of brass. It looks uncomfortable." + icon_state = "brass_chair" + max_integrity = 150 + buildstacktype = /obj/item/stack/tile/brass + buildstackamount = 1 + item_chair = null + var/turns = 0 + +/obj/structure/chair/brass/Destroy() + STOP_PROCESSING(SSfastprocess, src) + . = ..() + +/obj/structure/chair/brass/process() + setDir(turn(dir,-90)) + playsound(src, 'sound/effects/servostep.ogg', 50, FALSE) + turns++ + if(turns >= 8) + STOP_PROCESSING(SSfastprocess, src) + +/obj/structure/chair/brass/ratvar_act() + return + +/obj/structure/chair/brass/AltClick(mob/living/user) + turns = 0 + if(!istype(user) || user.incapacitated() || !in_range(src, user)) + return + if(!isprocessing) + user.visible_message("[user] spins [src] around, and Ratvarian technology keeps it spinning FOREVER.", \ + "Automated spinny chairs. The pinnacle of Ratvarian technology.") + START_PROCESSING(SSfastprocess, src) + else + user.visible_message("[user] stops [src]'s uncontrollable spinning.", \ + "You grab [src] and stop its wild spinning.") + STOP_PROCESSING(SSfastprocess, src) diff --git a/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm b/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm index a837e632692..9def5542c76 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm @@ -155,4 +155,4 @@ . = 0 else - . = 1 \ No newline at end of file + . = 1 diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 1f7fd24d803..5acd4f7d8a3 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -1,777 +1,777 @@ -/* Tables and Racks - * Contains: - * Tables - * Glass Tables - * Wooden Tables - * Reinforced Tables - * Racks - * Rack Parts - */ - -/* - * Tables - */ - -/obj/structure/table - name = "table" - desc = "A square piece of metal standing on four metal legs. It can not move." - icon = 'icons/obj/smooth_structures/table.dmi' - icon_state = "table" - density = TRUE - anchored = TRUE - layer = TABLE_LAYER - pass_flags = LETPASSTHROW - climbable = TRUE - max_integrity = 100 - integrity_failure = 30 - smooth = SMOOTH_TRUE - canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced) - var/frame = /obj/structure/table_frame - var/framestack = /obj/item/stack/rods - var/buildstack = /obj/item/stack/sheet/metal - var/busy = FALSE - var/buildstackamount = 1 - var/framestackamount = 2 - var/deconstruction_ready = TRUE - var/flipped = 0 - -/obj/structure/table/New() - ..() - if(flipped) - update_icon() - -/obj/structure/table/examine(mob/user) - . = ..() - . += deconstruction_hints(user) - -/obj/structure/table/proc/deconstruction_hints(mob/user) - return "The top is screwed on, but the main bolts are also visible." - -/obj/structure/table/update_icon() - if(smooth && !flipped) - icon_state = "" - queue_smooth(src) - queue_smooth_neighbors(src) - - if(flipped) - clear_smooth_overlays() - - var/type = 0 - var/subtype = null - for(var/direction in list(turn(dir,90), turn(dir,-90)) ) - var/obj/structure/table/T = locate(/obj/structure/table,get_step(src,direction)) - if(T && T.flipped) - type++ - if(type == 1) - subtype = direction == turn(dir,90) ? "-" : "+" - var/base = "table" - if(istype(src, /obj/structure/table/wood)) - base = "wood" - if(istype(src, /obj/structure/table/reinforced)) - base = "rtable" - - icon_state = "[base]flip[type][type == 1 ? subtype : ""]" - - return 1 - -/obj/structure/table/narsie_act() - new /obj/structure/table/wood(loc) - qdel(src) - -/obj/structure/table/ratvar_act() - new /obj/structure/table/reinforced/brass(loc) - qdel(src) - -/obj/structure/table/do_climb(mob/living/user) - . = ..() - item_placed(user) - -/obj/structure/table/attack_hand(mob/living/user) - ..() - if(climber) - climber.Weaken(2) - climber.visible_message("[climber.name] has been knocked off the table", "You've been knocked off the table", "You see [climber.name] get knocked off the table") - else if(Adjacent(user) && user.pulling && user.pulling.pass_flags & PASSTABLE) - user.Move_Pulled(src) - if(user.pulling.loc == loc) - user.visible_message("[user] places [user.pulling] onto [src].", - "You place [user.pulling] onto [src].") - user.stop_pulling() - -/obj/structure/table/attack_tk() // no telehulk sorry - return - -/obj/structure/table/proc/item_placed(item) - return - -/obj/structure/table/CanPass(atom/movable/mover, turf/target, height=0) - if(height == 0) - return 1 - if(istype(mover,/obj/item/projectile)) - return (check_cover(mover,target)) - if(ismob(mover)) - var/mob/M = mover - if(M.flying) - return 1 - if(istype(mover) && mover.checkpass(PASSTABLE)) - return 1 - if(mover.throwing) - return 1 - if(locate(/obj/structure/table) in get_turf(mover)) - return 1 - if(flipped) - if(get_dir(loc, target) == dir) - return !density - else - return 1 - return 0 - -/obj/structure/table/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(caller)) - var/atom/movable/mover = caller - . = . || mover.checkpass(PASSTABLE) - -//checks if projectile 'P' from turf 'from' can hit whatever is behind the table. Returns 1 if it can, 0 if bullet stops. -/obj/structure/table/proc/check_cover(obj/item/projectile/P, turf/from) - var/turf/cover = flipped ? get_turf(src) : get_step(loc, get_dir(from, loc)) - if(get_dist(P.starting, loc) <= 1) //Tables won't help you if people are THIS close - return 1 - if(get_turf(P.original) == cover) - var/chance = 20 - if(ismob(P.original)) - var/mob/M = P.original - if(M.lying) - chance += 20 //Lying down lets you catch less bullets - if(flipped) - if(get_dir(loc, from) == dir) //Flipped tables catch mroe bullets - chance += 20 - else - return 1 //But only from one side - if(prob(chance)) - obj_integrity -= P.damage/2 - if(obj_integrity > 0) - visible_message("[P] hits \the [src]!") - return 0 - else - visible_message("[src] breaks down!") - deconstruct(FALSE) - return 1 - return 1 - -/obj/structure/table/CheckExit(atom/movable/O, turf/target) - if(istype(O) && O.checkpass(PASSTABLE)) - return 1 - if(flipped) - if(get_dir(loc, target) == dir) - return !density - else - return 1 - return 1 - -/obj/structure/table/MouseDrop_T(obj/O, mob/user) - ..() - if((!( istype(O, /obj/item) ) || user.get_active_hand() != O)) - return - if(isrobot(user)) - return - if(!user.drop_item()) - return - if(O.loc != src.loc) - step(O, get_dir(O, src)) - return - -/obj/structure/table/proc/tablepush(obj/item/grab/G, mob/user) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "Throwing [G.affecting] onto the table might hurt them!") - return - if(get_dist(src, user) < 2) - if(G.affecting.buckled) - to_chat(user, "[G.affecting] is buckled to [G.affecting.buckled]!") - return FALSE - if(G.state < GRAB_AGGRESSIVE) - to_chat(user, "You need a better grip to do that!") - return FALSE - if(!G.confirm()) - return FALSE - var/blocking_object = density_check() - if(blocking_object) - to_chat(user, "You cannot do this there is \a [blocking_object] in the way!") - return FALSE - G.affecting.forceMove(get_turf(src)) - G.affecting.Weaken(2) - item_placed(G.affecting) - G.affecting.visible_message("[G.assailant] pushes [G.affecting] onto [src].", \ - "[G.assailant] pushes [G.affecting] onto [src].") - add_attack_logs(G.assailant, G.affecting, "Pushed onto a table") - qdel(G) - return TRUE - qdel(G) - -/obj/structure/table/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/grab)) - tablepush(I, user) - return - - if(isrobot(user)) - return - - if(user.a_intent != INTENT_HARM && !(I.flags & ABSTRACT)) - if(user.drop_item()) - I.Move(loc) - var/list/click_params = params2list(params) - //Center the icon where the user clicked. - if(!click_params || !click_params["icon-x"] || !click_params["icon-y"]) - return - //Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf) - I.pixel_x = Clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2) - I.pixel_y = Clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2) - item_placed(I) - else - return ..() - - -/obj/structure/table/screwdriver_act(mob/user, obj/item/I) - if(flags & NODECONSTRUCT) - return - if(!deconstruction_ready) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - TOOL_ATTEMPT_DISMANTLE_MESSAGE - if(I.use_tool(src, user, 20, volume = I.tool_volume) && deconstruction_ready) - deconstruct(TRUE) - TOOL_DISMANTLE_SUCCESS_MESSAGE - -/obj/structure/table/wrench_act(mob/user, obj/item/I) - if(flags & NODECONSTRUCT) - return - if(!deconstruction_ready) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - TOOL_ATTEMPT_DISMANTLE_MESSAGE - if(I.use_tool(src, user, 40, volume = I.tool_volume) && deconstruction_ready) - deconstruct(TRUE, 1) - TOOL_DISMANTLE_SUCCESS_MESSAGE - -/obj/structure/table/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags & NODECONSTRUCT)) - var/turf/T = get_turf(src) - new buildstack(T, buildstackamount) - if(!wrench_disassembly) - new frame(T) - else - new framestack(T, framestackamount) - qdel(src) - -/obj/structure/table/proc/straight_table_check(direction) - var/obj/structure/table/T - for(var/angle in list(-90,90)) - T = locate() in get_step(loc,turn(direction,angle)) - if(T && !T.flipped) - return 0 - T = locate() in get_step(loc,direction) - if(!T || T.flipped) - return 1 - if(istype(T,/obj/structure/table/reinforced/)) - if(!T.deconstruction_ready) - return 0 - return T.straight_table_check(direction) - -/obj/structure/table/verb/do_flip() - set name = "Flip table" - set desc = "Flips a non-reinforced table" - set category = null - set src in oview(1) - - if(!can_touch(usr) || ismouse(usr)) - return - - if(!flip(get_cardinal_dir(usr,src))) - to_chat(usr, "It won't budge.") - return - - usr.visible_message("[usr] flips \the [src]!") - - if(climbable) - structure_shaken() - - return - -/obj/structure/table/proc/do_put() - set name = "Put table back" - set desc = "Puts flipped table back" - set category = "Object" - set src in oview(1) - - if(!can_touch(usr) || ismouse(usr)) - return - - if(!unflip()) - to_chat(usr, "It won't budge.") - return - - -/obj/structure/table/proc/flip(direction) - if(flipped) - return 0 - - if( !straight_table_check(turn(direction,90)) || !straight_table_check(turn(direction,-90)) ) - return 0 - - verbs -=/obj/structure/table/verb/do_flip - verbs +=/obj/structure/table/proc/do_put - - var/list/targets = list(get_step(src,dir),get_step(src,turn(dir, 45)),get_step(src,turn(dir, -45))) - for(var/atom/movable/A in get_turf(src)) - if(!A.anchored) - spawn(0) - A.throw_at(pick(targets),1,1) - - dir = direction - if(dir != NORTH) - layer = 5 - flipped = 1 - smooth = SMOOTH_FALSE - flags |= ON_BORDER - for(var/D in list(turn(direction, 90), turn(direction, -90))) - if(locate(/obj/structure/table,get_step(src,D))) - var/obj/structure/table/T = locate(/obj/structure/table,get_step(src,D)) - T.flip(direction) - update_icon() - - return 1 - -/obj/structure/table/proc/unflip() - if(!flipped) - return 0 - - var/can_flip = 1 - for(var/mob/A in oview(src,0))//loc) - if(istype(A)) - can_flip = 0 - if(!can_flip) - return 0 - - verbs -=/obj/structure/table/proc/do_put - verbs +=/obj/structure/table/verb/do_flip - - layer = initial(layer) - flipped = 0 - smooth = initial(smooth) - flags &= ~ON_BORDER - for(var/D in list(turn(dir, 90), turn(dir, -90))) - if(locate(/obj/structure/table,get_step(src,D))) - var/obj/structure/table/T = locate(/obj/structure/table,get_step(src,D)) - T.unflip() - update_icon() - - return 1 - - -/* - * Glass Tables - */ - -/obj/structure/table/glass - name = "glass table" - desc = "Looks fragile. You should totally flip it. It is begging for it." - icon = 'icons/obj/smooth_structures/glass_table.dmi' - icon_state = "glass_table" - buildstack = /obj/item/stack/sheet/glass - canSmoothWith = null - max_integrity = 70 - resistance_flags = ACID_PROOF - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - var/list/debris = list() - -/obj/structure/table/glass/New() - . = ..() - debris += new frame - debris += new /obj/item/shard - -/obj/structure/table/glass/Destroy() - for(var/i in debris) - qdel(i) - . = ..() - -/obj/structure/table/glass/Crossed(atom/movable/AM, oldloc) - . = ..() - if(flags & NODECONSTRUCT) - return - if(!isliving(AM)) - return - // Don't break if they're just flying past - if(AM.throwing) - addtimer(CALLBACK(src, .proc/throw_check, AM), 5) - else - check_break(AM) - -/obj/structure/table/glass/proc/throw_check(mob/living/M) - if(M.loc == get_turf(src)) - check_break(M) - -/obj/structure/table/glass/proc/check_break(mob/living/M) - if(has_gravity(M) && M.mob_size > MOB_SIZE_SMALL) - table_shatter(M) - -/obj/structure/table/glass/flip(direction) - deconstruct(FALSE) - -/obj/structure/table/glass/proc/table_shatter(mob/living/L) - visible_message("[src] breaks!", - "You hear breaking glass.") - var/turf/T = get_turf(src) - playsound(T, "shatter", 50, 1) - for(var/I in debris) - var/atom/movable/AM = I - AM.forceMove(T) - debris -= AM - if(istype(AM, /obj/item/shard)) - AM.throw_impact(L) - L.Weaken(5) - qdel(src) - -/obj/structure/table/glass/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags & NODECONSTRUCT)) - if(disassembled) - ..() - return - else - var/turf/T = get_turf(src) - playsound(T, "shatter", 50, TRUE) - for(var/X in debris) - var/atom/movable/AM = X - AM.forceMove(T) - debris -= AM - qdel(src) - -/obj/structure/table/glass/narsie_act() - color = NARSIE_WINDOW_COLOUR - for(var/obj/item/shard/S in debris) - S.color = NARSIE_WINDOW_COLOUR - -/* - * Wooden tables - */ -/obj/structure/table/wood - name = "wooden table" - desc = "Do not apply fire to this. Rumour says it burns easily." - icon = 'icons/obj/smooth_structures/wood_table.dmi' - icon_state = "wood_table" - frame = /obj/structure/table_frame/wood - framestack = /obj/item/stack/sheet/wood - buildstack = /obj/item/stack/sheet/wood - max_integrity = 70 - canSmoothWith = list(/obj/structure/table/wood, /obj/structure/table/wood/poker) - resistance_flags = FLAMMABLE - -/obj/structure/table/wood/narsie_act(total_override = TRUE) - if(!total_override) - ..() - -/obj/structure/table/wood/poker //No specialties, Just a mapping object. - name = "gambling table" - desc = "A seedy table for seedy dealings in seedy places." - icon = 'icons/obj/smooth_structures/poker_table.dmi' - icon_state = "poker_table" - buildstack = /obj/item/stack/tile/carpet - -/obj/structure/table/wood/poker/narsie_act() - ..(FALSE) - -/* - * Fancy Tables - */ - -/obj/structure/table/wood/fancy - name = "fancy table" - desc = "A standard metal table frame covered with an amazingly fancy, patterned cloth." - icon = 'icons/obj/structures.dmi' - icon_state = "fancy_table" - frame = /obj/structure/table_frame - framestack = /obj/item/stack/rods - buildstack = /obj/item/stack/tile/carpet - canSmoothWith = list(/obj/structure/table/wood/fancy, /obj/structure/table/wood/fancy/black) - -/obj/structure/table/wood/fancy/New() - icon = 'icons/obj/smooth_structures/fancy_table.dmi' //so that the tables place correctly in the map editor - ..() - -/obj/structure/table/wood/fancy/black - icon_state = "fancy_table_black" - buildstack = /obj/item/stack/tile/carpet/black - -/obj/structure/table/wood/fancy/black/New() - ..() - icon = 'icons/obj/smooth_structures/fancy_table_black.dmi' //so that the tables place correctly in the map editor - -/* - * Reinforced tables - */ -/obj/structure/table/reinforced - name = "reinforced table" - desc = "A reinforced version of the four legged table." - icon = 'icons/obj/smooth_structures/reinforced_table.dmi' - icon_state = "r_table" - deconstruction_ready = FALSE - buildstack = /obj/item/stack/sheet/plasteel - canSmoothWith = list(/obj/structure/table/reinforced, /obj/structure/table) - max_integrity = 200 - integrity_failure = 50 - armor = list("melee" = 10, "bullet" = 30, "laser" = 30, "energy" = 100, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - -/obj/structure/table/reinforced/deconstruction_hints(mob/user) - if(deconstruction_ready) - to_chat(user, "The top cover has been welded loose and the main frame's bolts are exposed.") - else - to_chat(user, "The top cover is firmly welded on.") - -/obj/structure/table/reinforced/flip(direction) - if(!deconstruction_ready) - return 0 - else - return ..() - -/obj/structure/table/reinforced/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - to_chat(user, "You start [deconstruction_ready ? "strengthening" : "weakening"] the reinforced table...") - if(I.use_tool(src, user, 50, volume = I.tool_volume)) - to_chat(user, "You [deconstruction_ready ? "strengthen" : "weaken"] the table.") - deconstruction_ready = !deconstruction_ready - -/obj/structure/table/reinforced/brass - name = "brass table" - desc = "A solid, slightly beveled brass table." - icon = 'icons/obj/smooth_structures/brass_table.dmi' - icon_state = "brass_table" - resistance_flags = FIRE_PROOF | ACID_PROOF - frame = /obj/structure/table_frame/brass - framestack = /obj/item/stack/tile/brass - buildstack = /obj/item/stack/tile/brass - framestackamount = 1 - buildstackamount = 1 - canSmoothWith = list(/obj/structure/table/reinforced/brass) - -/obj/structure/table/reinforced/brass/narsie_act() - take_damage(rand(15, 45), BRUTE) - if(src) //do we still exist? - var/previouscolor = color - color = "#960000" - animate(src, color = previouscolor, time = 8) - -/obj/structure/table/reinforced/brass/ratvar_act() - obj_integrity = max_integrity - -/obj/structure/table/tray - name = "surgical tray" - desc = "A small metal tray with wheels." - anchored = FALSE - smooth = SMOOTH_FALSE - icon = 'icons/obj/stationobjs.dmi' - icon_state = "tray" - buildstack = /obj/item/stack/sheet/mineral/titanium - buildstackamount = 2 - var/list/typecache_can_hold = list(/mob, /obj/item) - var/list/held_items = list() - -/obj/structure/table/tray/Initialize() - . = ..() - verbs -= /obj/structure/table/verb/do_flip - typecache_can_hold = typecacheof(typecache_can_hold) - for(var/atom/movable/held in get_turf(src)) - if(is_type_in_typecache(held, typecache_can_hold)) - held_items += held.UID() - -/obj/structure/table/tray/Move(NewLoc, direct) - var/atom/OldLoc = loc - - . = ..() - if(!.) // ..() will return 0 if we didn't actually move anywhere. - return . - - if(direct & (direct - 1)) // This represents a diagonal movement, which is split into multiple cardinal movements. We'll handle moving the items on the cardinals only. - return . - - playsound(loc, pick('sound/items/cartwheel1.ogg', 'sound/items/cartwheel2.ogg'), 100, 1, ignore_walls = FALSE) - - var/atom/movable/held - for(var/held_uid in held_items) - held = locateUID(held_uid) - if(!held) - held_items -= held_uid - continue - if(OldLoc != held.loc) - held_items -= held_uid - continue - held.forceMove(NewLoc) - -/obj/structure/table/tray/item_placed(atom/movable/item) - . = ..() - if(is_type_in_typecache(item, typecache_can_hold)) - held_items += item.UID() - -/obj/structure/table/tray/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags & NODECONSTRUCT)) - var/turf/T = get_turf(src) - new buildstack(T, buildstackamount) - qdel(src) - -/obj/structure/table/tray/deconstruction_hints(mob/user) - to_chat(user, "It is held together by some screws and bolts.") - -/obj/structure/table/tray/flip() - return 0 - -/obj/structure/table/tray/narsie_act() - return 0 - -/obj/structure/table/tray/ratvar_act() - return 0 - -/* - * Racks - */ -/obj/structure/rack - name = "rack" - desc = "Different from the Middle Ages version." - icon = 'icons/obj/objects.dmi' - icon_state = "rack" - layer = TABLE_LAYER - density = TRUE - anchored = TRUE - pass_flags = LETPASSTHROW - max_integrity = 20 - -/obj/structure/rack/examine(mob/user) - . = ..() - . += "It's held together by a couple of bolts." - -/obj/structure/rack/CanPass(atom/movable/mover, turf/target, height=0) - if(height==0) - return 1 - if(density == 0) //Because broken racks -Agouri |TODO: SPRITE!| - return 1 - if(istype(mover) && mover.checkpass(PASSTABLE)) - return 1 - if(mover.throwing) - return 1 - else - return 0 - -/obj/structure/rack/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(caller)) - var/atom/movable/mover = caller - . = . || mover.checkpass(PASSTABLE) - -/obj/structure/rack/MouseDrop_T(obj/O, mob/user) - if((!( istype(O, /obj/item) ) || user.get_active_hand() != O)) - return - if(isrobot(user)) - return - if(!user.drop_item()) - return - if(O.loc != src.loc) - step(O, get_dir(O, src)) - -/obj/structure/rack/attackby(obj/item/W, mob/user, params) - if(isrobot(user)) - return - if(user.a_intent == INTENT_HARM) - return ..() - if(!(W.flags & ABSTRACT)) - if(user.drop_item()) - W.Move(loc) - return - -/obj/structure/rack/wrench_act(mob/user, obj/item/I) - . = TRUE - if(flags & NODECONSTRUCT) - to_chat(user, "Try as you might, you can't figure out how to deconstruct this.") - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - deconstruct(TRUE) - -/obj/structure/rack/attack_hand(mob/living/user) - if(user.IsWeakened() || user.resting || user.lying) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src, ATTACK_EFFECT_KICK) - user.visible_message("[user] kicks [src].", \ - "You kick [src].") - take_damage(rand(4,8), BRUTE, "melee", 1) - -/obj/structure/rack/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(loc, 'sound/items/dodgeball.ogg', 80, TRUE) - else - playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(loc, 'sound/items/welder.ogg', 40, TRUE) - -/obj/structure/rack/skeletal_bar - name = "skeletal minibar" - desc = "Made with the skulls of the fallen." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "minibar" - -/obj/structure/rack/skeletal_bar/left - icon_state = "minibar_left" - -/obj/structure/rack/skeletal_bar/right - icon_state = "minibar_right" - -/* - * Rack destruction - */ - -/obj/structure/rack/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - density = FALSE - var/obj/item/rack_parts/newparts = new(loc) - transfer_fingerprints_to(newparts) - qdel(src) - -/* - * Rack Parts - */ - -/obj/item/rack_parts - name = "rack parts" - desc = "Parts of a rack." - icon = 'icons/obj/items.dmi' - icon_state = "rack_parts" - flags = CONDUCT - materials = list(MAT_METAL=2000) - var/building = FALSE - -/obj/item/rack_parts/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - new /obj/item/stack/sheet/metal(user.loc) - qdel(src) - -/obj/item/rack_parts/attack_self(mob/user) - if(building) - return - building = TRUE - to_chat(user, "You start constructing a rack...") - if(do_after(user, 50, target = user, progress=TRUE)) - if(!user.drop_item(src)) - return - var/obj/structure/rack/R = new /obj/structure/rack(user.loc) - user.visible_message("[user] assembles \a [R].\ - ", "You assemble \a [R].") - R.add_fingerprint(user) - qdel(src) - building = FALSE +/* Tables and Racks + * Contains: + * Tables + * Glass Tables + * Wooden Tables + * Reinforced Tables + * Racks + * Rack Parts + */ + +/* + * Tables + */ + +/obj/structure/table + name = "table" + desc = "A square piece of metal standing on four metal legs. It can not move." + icon = 'icons/obj/smooth_structures/table.dmi' + icon_state = "table" + density = TRUE + anchored = TRUE + layer = TABLE_LAYER + pass_flags = LETPASSTHROW + climbable = TRUE + max_integrity = 100 + integrity_failure = 30 + smooth = SMOOTH_TRUE + canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced) + var/frame = /obj/structure/table_frame + var/framestack = /obj/item/stack/rods + var/buildstack = /obj/item/stack/sheet/metal + var/busy = FALSE + var/buildstackamount = 1 + var/framestackamount = 2 + var/deconstruction_ready = TRUE + var/flipped = 0 + +/obj/structure/table/New() + ..() + if(flipped) + update_icon() + +/obj/structure/table/examine(mob/user) + . = ..() + . += deconstruction_hints(user) + +/obj/structure/table/proc/deconstruction_hints(mob/user) + return "The top is screwed on, but the main bolts are also visible." + +/obj/structure/table/update_icon() + if(smooth && !flipped) + icon_state = "" + queue_smooth(src) + queue_smooth_neighbors(src) + + if(flipped) + clear_smooth_overlays() + + var/type = 0 + var/subtype = null + for(var/direction in list(turn(dir,90), turn(dir,-90)) ) + var/obj/structure/table/T = locate(/obj/structure/table,get_step(src,direction)) + if(T && T.flipped) + type++ + if(type == 1) + subtype = direction == turn(dir,90) ? "-" : "+" + var/base = "table" + if(istype(src, /obj/structure/table/wood)) + base = "wood" + if(istype(src, /obj/structure/table/reinforced)) + base = "rtable" + + icon_state = "[base]flip[type][type == 1 ? subtype : ""]" + + return 1 + +/obj/structure/table/narsie_act() + new /obj/structure/table/wood(loc) + qdel(src) + +/obj/structure/table/ratvar_act() + new /obj/structure/table/reinforced/brass(loc) + qdel(src) + +/obj/structure/table/do_climb(mob/living/user) + . = ..() + item_placed(user) + +/obj/structure/table/attack_hand(mob/living/user) + ..() + if(climber) + climber.Weaken(2) + climber.visible_message("[climber.name] has been knocked off the table", "You've been knocked off the table", "You see [climber.name] get knocked off the table") + else if(Adjacent(user) && user.pulling && user.pulling.pass_flags & PASSTABLE) + user.Move_Pulled(src) + if(user.pulling.loc == loc) + user.visible_message("[user] places [user.pulling] onto [src].", + "You place [user.pulling] onto [src].") + user.stop_pulling() + +/obj/structure/table/attack_tk() // no telehulk sorry + return + +/obj/structure/table/proc/item_placed(item) + return + +/obj/structure/table/CanPass(atom/movable/mover, turf/target, height=0) + if(height == 0) + return 1 + if(istype(mover,/obj/item/projectile)) + return (check_cover(mover,target)) + if(ismob(mover)) + var/mob/M = mover + if(M.flying) + return 1 + if(istype(mover) && mover.checkpass(PASSTABLE)) + return 1 + if(mover.throwing) + return 1 + if(locate(/obj/structure/table) in get_turf(mover)) + return 1 + if(flipped) + if(get_dir(loc, target) == dir) + return !density + else + return 1 + return 0 + +/obj/structure/table/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSTABLE) + +//checks if projectile 'P' from turf 'from' can hit whatever is behind the table. Returns 1 if it can, 0 if bullet stops. +/obj/structure/table/proc/check_cover(obj/item/projectile/P, turf/from) + var/turf/cover = flipped ? get_turf(src) : get_step(loc, get_dir(from, loc)) + if(get_dist(P.starting, loc) <= 1) //Tables won't help you if people are THIS close + return 1 + if(get_turf(P.original) == cover) + var/chance = 20 + if(ismob(P.original)) + var/mob/M = P.original + if(M.lying) + chance += 20 //Lying down lets you catch less bullets + if(flipped) + if(get_dir(loc, from) == dir) //Flipped tables catch mroe bullets + chance += 20 + else + return 1 //But only from one side + if(prob(chance)) + obj_integrity -= P.damage/2 + if(obj_integrity > 0) + visible_message("[P] hits \the [src]!") + return 0 + else + visible_message("[src] breaks down!") + deconstruct(FALSE) + return 1 + return 1 + +/obj/structure/table/CheckExit(atom/movable/O, turf/target) + if(istype(O) && O.checkpass(PASSTABLE)) + return 1 + if(flipped) + if(get_dir(loc, target) == dir) + return !density + else + return 1 + return 1 + +/obj/structure/table/MouseDrop_T(obj/O, mob/user) + ..() + if((!( istype(O, /obj/item) ) || user.get_active_hand() != O)) + return + if(isrobot(user)) + return + if(!user.drop_item()) + return + if(O.loc != src.loc) + step(O, get_dir(O, src)) + return + +/obj/structure/table/proc/tablepush(obj/item/grab/G, mob/user) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "Throwing [G.affecting] onto the table might hurt them!") + return + if(get_dist(src, user) < 2) + if(G.affecting.buckled) + to_chat(user, "[G.affecting] is buckled to [G.affecting.buckled]!") + return FALSE + if(G.state < GRAB_AGGRESSIVE) + to_chat(user, "You need a better grip to do that!") + return FALSE + if(!G.confirm()) + return FALSE + var/blocking_object = density_check() + if(blocking_object) + to_chat(user, "You cannot do this there is \a [blocking_object] in the way!") + return FALSE + G.affecting.forceMove(get_turf(src)) + G.affecting.Weaken(2) + item_placed(G.affecting) + G.affecting.visible_message("[G.assailant] pushes [G.affecting] onto [src].", \ + "[G.assailant] pushes [G.affecting] onto [src].") + add_attack_logs(G.assailant, G.affecting, "Pushed onto a table") + qdel(G) + return TRUE + qdel(G) + +/obj/structure/table/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/grab)) + tablepush(I, user) + return + + if(isrobot(user)) + return + + if(user.a_intent != INTENT_HARM && !(I.flags & ABSTRACT)) + if(user.drop_item()) + I.Move(loc) + var/list/click_params = params2list(params) + //Center the icon where the user clicked. + if(!click_params || !click_params["icon-x"] || !click_params["icon-y"]) + return + //Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf) + I.pixel_x = Clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2) + I.pixel_y = Clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2) + item_placed(I) + else + return ..() + + +/obj/structure/table/screwdriver_act(mob/user, obj/item/I) + if(flags & NODECONSTRUCT) + return + if(!deconstruction_ready) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + TOOL_ATTEMPT_DISMANTLE_MESSAGE + if(I.use_tool(src, user, 20, volume = I.tool_volume) && deconstruction_ready) + deconstruct(TRUE) + TOOL_DISMANTLE_SUCCESS_MESSAGE + +/obj/structure/table/wrench_act(mob/user, obj/item/I) + if(flags & NODECONSTRUCT) + return + if(!deconstruction_ready) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + TOOL_ATTEMPT_DISMANTLE_MESSAGE + if(I.use_tool(src, user, 40, volume = I.tool_volume) && deconstruction_ready) + deconstruct(TRUE, 1) + TOOL_DISMANTLE_SUCCESS_MESSAGE + +/obj/structure/table/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags & NODECONSTRUCT)) + var/turf/T = get_turf(src) + new buildstack(T, buildstackamount) + if(!wrench_disassembly) + new frame(T) + else + new framestack(T, framestackamount) + qdel(src) + +/obj/structure/table/proc/straight_table_check(direction) + var/obj/structure/table/T + for(var/angle in list(-90,90)) + T = locate() in get_step(loc,turn(direction,angle)) + if(T && !T.flipped) + return 0 + T = locate() in get_step(loc,direction) + if(!T || T.flipped) + return 1 + if(istype(T,/obj/structure/table/reinforced/)) + if(!T.deconstruction_ready) + return 0 + return T.straight_table_check(direction) + +/obj/structure/table/verb/do_flip() + set name = "Flip table" + set desc = "Flips a non-reinforced table" + set category = null + set src in oview(1) + + if(!can_touch(usr) || ismouse(usr)) + return + + if(!flip(get_cardinal_dir(usr,src))) + to_chat(usr, "It won't budge.") + return + + usr.visible_message("[usr] flips \the [src]!") + + if(climbable) + structure_shaken() + + return + +/obj/structure/table/proc/do_put() + set name = "Put table back" + set desc = "Puts flipped table back" + set category = "Object" + set src in oview(1) + + if(!can_touch(usr) || ismouse(usr)) + return + + if(!unflip()) + to_chat(usr, "It won't budge.") + return + + +/obj/structure/table/proc/flip(direction) + if(flipped) + return 0 + + if( !straight_table_check(turn(direction,90)) || !straight_table_check(turn(direction,-90)) ) + return 0 + + verbs -=/obj/structure/table/verb/do_flip + verbs +=/obj/structure/table/proc/do_put + + var/list/targets = list(get_step(src,dir),get_step(src,turn(dir, 45)),get_step(src,turn(dir, -45))) + for(var/atom/movable/A in get_turf(src)) + if(!A.anchored) + spawn(0) + A.throw_at(pick(targets),1,1) + + dir = direction + if(dir != NORTH) + layer = 5 + flipped = 1 + smooth = SMOOTH_FALSE + flags |= ON_BORDER + for(var/D in list(turn(direction, 90), turn(direction, -90))) + if(locate(/obj/structure/table,get_step(src,D))) + var/obj/structure/table/T = locate(/obj/structure/table,get_step(src,D)) + T.flip(direction) + update_icon() + + return 1 + +/obj/structure/table/proc/unflip() + if(!flipped) + return 0 + + var/can_flip = 1 + for(var/mob/A in oview(src,0))//loc) + if(istype(A)) + can_flip = 0 + if(!can_flip) + return 0 + + verbs -=/obj/structure/table/proc/do_put + verbs +=/obj/structure/table/verb/do_flip + + layer = initial(layer) + flipped = 0 + smooth = initial(smooth) + flags &= ~ON_BORDER + for(var/D in list(turn(dir, 90), turn(dir, -90))) + if(locate(/obj/structure/table,get_step(src,D))) + var/obj/structure/table/T = locate(/obj/structure/table,get_step(src,D)) + T.unflip() + update_icon() + + return 1 + + +/* + * Glass Tables + */ + +/obj/structure/table/glass + name = "glass table" + desc = "Looks fragile. You should totally flip it. It is begging for it." + icon = 'icons/obj/smooth_structures/glass_table.dmi' + icon_state = "glass_table" + buildstack = /obj/item/stack/sheet/glass + canSmoothWith = null + max_integrity = 70 + resistance_flags = ACID_PROOF + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + var/list/debris = list() + +/obj/structure/table/glass/New() + . = ..() + debris += new frame + debris += new /obj/item/shard + +/obj/structure/table/glass/Destroy() + for(var/i in debris) + qdel(i) + . = ..() + +/obj/structure/table/glass/Crossed(atom/movable/AM, oldloc) + . = ..() + if(flags & NODECONSTRUCT) + return + if(!isliving(AM)) + return + // Don't break if they're just flying past + if(AM.throwing) + addtimer(CALLBACK(src, .proc/throw_check, AM), 5) + else + check_break(AM) + +/obj/structure/table/glass/proc/throw_check(mob/living/M) + if(M.loc == get_turf(src)) + check_break(M) + +/obj/structure/table/glass/proc/check_break(mob/living/M) + if(has_gravity(M) && M.mob_size > MOB_SIZE_SMALL) + table_shatter(M) + +/obj/structure/table/glass/flip(direction) + deconstruct(FALSE) + +/obj/structure/table/glass/proc/table_shatter(mob/living/L) + visible_message("[src] breaks!", + "You hear breaking glass.") + var/turf/T = get_turf(src) + playsound(T, "shatter", 50, 1) + for(var/I in debris) + var/atom/movable/AM = I + AM.forceMove(T) + debris -= AM + if(istype(AM, /obj/item/shard)) + AM.throw_impact(L) + L.Weaken(5) + qdel(src) + +/obj/structure/table/glass/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags & NODECONSTRUCT)) + if(disassembled) + ..() + return + else + var/turf/T = get_turf(src) + playsound(T, "shatter", 50, TRUE) + for(var/X in debris) + var/atom/movable/AM = X + AM.forceMove(T) + debris -= AM + qdel(src) + +/obj/structure/table/glass/narsie_act() + color = NARSIE_WINDOW_COLOUR + for(var/obj/item/shard/S in debris) + S.color = NARSIE_WINDOW_COLOUR + +/* + * Wooden tables + */ +/obj/structure/table/wood + name = "wooden table" + desc = "Do not apply fire to this. Rumour says it burns easily." + icon = 'icons/obj/smooth_structures/wood_table.dmi' + icon_state = "wood_table" + frame = /obj/structure/table_frame/wood + framestack = /obj/item/stack/sheet/wood + buildstack = /obj/item/stack/sheet/wood + max_integrity = 70 + canSmoothWith = list(/obj/structure/table/wood, /obj/structure/table/wood/poker) + resistance_flags = FLAMMABLE + +/obj/structure/table/wood/narsie_act(total_override = TRUE) + if(!total_override) + ..() + +/obj/structure/table/wood/poker //No specialties, Just a mapping object. + name = "gambling table" + desc = "A seedy table for seedy dealings in seedy places." + icon = 'icons/obj/smooth_structures/poker_table.dmi' + icon_state = "poker_table" + buildstack = /obj/item/stack/tile/carpet + +/obj/structure/table/wood/poker/narsie_act() + ..(FALSE) + +/* + * Fancy Tables + */ + +/obj/structure/table/wood/fancy + name = "fancy table" + desc = "A standard metal table frame covered with an amazingly fancy, patterned cloth." + icon = 'icons/obj/structures.dmi' + icon_state = "fancy_table" + frame = /obj/structure/table_frame + framestack = /obj/item/stack/rods + buildstack = /obj/item/stack/tile/carpet + canSmoothWith = list(/obj/structure/table/wood/fancy, /obj/structure/table/wood/fancy/black) + +/obj/structure/table/wood/fancy/New() + icon = 'icons/obj/smooth_structures/fancy_table.dmi' //so that the tables place correctly in the map editor + ..() + +/obj/structure/table/wood/fancy/black + icon_state = "fancy_table_black" + buildstack = /obj/item/stack/tile/carpet/black + +/obj/structure/table/wood/fancy/black/New() + ..() + icon = 'icons/obj/smooth_structures/fancy_table_black.dmi' //so that the tables place correctly in the map editor + +/* + * Reinforced tables + */ +/obj/structure/table/reinforced + name = "reinforced table" + desc = "A reinforced version of the four legged table." + icon = 'icons/obj/smooth_structures/reinforced_table.dmi' + icon_state = "r_table" + deconstruction_ready = FALSE + buildstack = /obj/item/stack/sheet/plasteel + canSmoothWith = list(/obj/structure/table/reinforced, /obj/structure/table) + max_integrity = 200 + integrity_failure = 50 + armor = list("melee" = 10, "bullet" = 30, "laser" = 30, "energy" = 100, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + +/obj/structure/table/reinforced/deconstruction_hints(mob/user) + if(deconstruction_ready) + to_chat(user, "The top cover has been welded loose and the main frame's bolts are exposed.") + else + to_chat(user, "The top cover is firmly welded on.") + +/obj/structure/table/reinforced/flip(direction) + if(!deconstruction_ready) + return 0 + else + return ..() + +/obj/structure/table/reinforced/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + to_chat(user, "You start [deconstruction_ready ? "strengthening" : "weakening"] the reinforced table...") + if(I.use_tool(src, user, 50, volume = I.tool_volume)) + to_chat(user, "You [deconstruction_ready ? "strengthen" : "weaken"] the table.") + deconstruction_ready = !deconstruction_ready + +/obj/structure/table/reinforced/brass + name = "brass table" + desc = "A solid, slightly beveled brass table." + icon = 'icons/obj/smooth_structures/brass_table.dmi' + icon_state = "brass_table" + resistance_flags = FIRE_PROOF | ACID_PROOF + frame = /obj/structure/table_frame/brass + framestack = /obj/item/stack/tile/brass + buildstack = /obj/item/stack/tile/brass + framestackamount = 1 + buildstackamount = 1 + canSmoothWith = list(/obj/structure/table/reinforced/brass) + +/obj/structure/table/reinforced/brass/narsie_act() + take_damage(rand(15, 45), BRUTE) + if(src) //do we still exist? + var/previouscolor = color + color = "#960000" + animate(src, color = previouscolor, time = 8) + +/obj/structure/table/reinforced/brass/ratvar_act() + obj_integrity = max_integrity + +/obj/structure/table/tray + name = "surgical tray" + desc = "A small metal tray with wheels." + anchored = FALSE + smooth = SMOOTH_FALSE + icon = 'icons/obj/stationobjs.dmi' + icon_state = "tray" + buildstack = /obj/item/stack/sheet/mineral/titanium + buildstackamount = 2 + var/list/typecache_can_hold = list(/mob, /obj/item) + var/list/held_items = list() + +/obj/structure/table/tray/Initialize() + . = ..() + verbs -= /obj/structure/table/verb/do_flip + typecache_can_hold = typecacheof(typecache_can_hold) + for(var/atom/movable/held in get_turf(src)) + if(is_type_in_typecache(held, typecache_can_hold)) + held_items += held.UID() + +/obj/structure/table/tray/Move(NewLoc, direct) + var/atom/OldLoc = loc + + . = ..() + if(!.) // ..() will return 0 if we didn't actually move anywhere. + return . + + if(direct & (direct - 1)) // This represents a diagonal movement, which is split into multiple cardinal movements. We'll handle moving the items on the cardinals only. + return . + + playsound(loc, pick('sound/items/cartwheel1.ogg', 'sound/items/cartwheel2.ogg'), 100, 1, ignore_walls = FALSE) + + var/atom/movable/held + for(var/held_uid in held_items) + held = locateUID(held_uid) + if(!held) + held_items -= held_uid + continue + if(OldLoc != held.loc) + held_items -= held_uid + continue + held.forceMove(NewLoc) + +/obj/structure/table/tray/item_placed(atom/movable/item) + . = ..() + if(is_type_in_typecache(item, typecache_can_hold)) + held_items += item.UID() + +/obj/structure/table/tray/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags & NODECONSTRUCT)) + var/turf/T = get_turf(src) + new buildstack(T, buildstackamount) + qdel(src) + +/obj/structure/table/tray/deconstruction_hints(mob/user) + to_chat(user, "It is held together by some screws and bolts.") + +/obj/structure/table/tray/flip() + return 0 + +/obj/structure/table/tray/narsie_act() + return 0 + +/obj/structure/table/tray/ratvar_act() + return 0 + +/* + * Racks + */ +/obj/structure/rack + name = "rack" + desc = "Different from the Middle Ages version." + icon = 'icons/obj/objects.dmi' + icon_state = "rack" + layer = TABLE_LAYER + density = TRUE + anchored = TRUE + pass_flags = LETPASSTHROW + max_integrity = 20 + +/obj/structure/rack/examine(mob/user) + . = ..() + . += "It's held together by a couple of bolts." + +/obj/structure/rack/CanPass(atom/movable/mover, turf/target, height=0) + if(height==0) + return 1 + if(density == 0) //Because broken racks -Agouri |TODO: SPRITE!| + return 1 + if(istype(mover) && mover.checkpass(PASSTABLE)) + return 1 + if(mover.throwing) + return 1 + else + return 0 + +/obj/structure/rack/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || mover.checkpass(PASSTABLE) + +/obj/structure/rack/MouseDrop_T(obj/O, mob/user) + if((!( istype(O, /obj/item) ) || user.get_active_hand() != O)) + return + if(isrobot(user)) + return + if(!user.drop_item()) + return + if(O.loc != src.loc) + step(O, get_dir(O, src)) + +/obj/structure/rack/attackby(obj/item/W, mob/user, params) + if(isrobot(user)) + return + if(user.a_intent == INTENT_HARM) + return ..() + if(!(W.flags & ABSTRACT)) + if(user.drop_item()) + W.Move(loc) + return + +/obj/structure/rack/wrench_act(mob/user, obj/item/I) + . = TRUE + if(flags & NODECONSTRUCT) + to_chat(user, "Try as you might, you can't figure out how to deconstruct this.") + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + deconstruct(TRUE) + +/obj/structure/rack/attack_hand(mob/living/user) + if(user.IsWeakened() || user.resting || user.lying) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src, ATTACK_EFFECT_KICK) + user.visible_message("[user] kicks [src].", \ + "You kick [src].") + take_damage(rand(4,8), BRUTE, "melee", 1) + +/obj/structure/rack/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(loc, 'sound/items/dodgeball.ogg', 80, TRUE) + else + playsound(loc, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(loc, 'sound/items/welder.ogg', 40, TRUE) + +/obj/structure/rack/skeletal_bar + name = "skeletal minibar" + desc = "Made with the skulls of the fallen." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "minibar" + +/obj/structure/rack/skeletal_bar/left + icon_state = "minibar_left" + +/obj/structure/rack/skeletal_bar/right + icon_state = "minibar_right" + +/* + * Rack destruction + */ + +/obj/structure/rack/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + density = FALSE + var/obj/item/rack_parts/newparts = new(loc) + transfer_fingerprints_to(newparts) + qdel(src) + +/* + * Rack Parts + */ + +/obj/item/rack_parts + name = "rack parts" + desc = "Parts of a rack." + icon = 'icons/obj/items.dmi' + icon_state = "rack_parts" + flags = CONDUCT + materials = list(MAT_METAL=2000) + var/building = FALSE + +/obj/item/rack_parts/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + new /obj/item/stack/sheet/metal(user.loc) + qdel(src) + +/obj/item/rack_parts/attack_self(mob/user) + if(building) + return + building = TRUE + to_chat(user, "You start constructing a rack...") + if(do_after(user, 50, target = user, progress=TRUE)) + if(!user.drop_item(src)) + return + var/obj/structure/rack/R = new /obj/structure/rack(user.loc) + user.visible_message("[user] assembles \a [R].\ + ", "You assemble \a [R].") + R.add_fingerprint(user) + qdel(src) + building = FALSE diff --git a/code/game/objects/structures/tank_dispenser.dm b/code/game/objects/structures/tank_dispenser.dm index 6a16305a299..c4c36d788e2 100644 --- a/code/game/objects/structures/tank_dispenser.dm +++ b/code/game/objects/structures/tank_dispenser.dm @@ -1,132 +1,132 @@ -/obj/structure/dispenser - name = "tank storage unit" - desc = "A simple yet bulky storage device for gas tanks. Has room for up to ten oxygen tanks, and ten plasma tanks." - icon = 'icons/obj/objects.dmi' - icon_state = "dispenser" - density = 1 - anchored = 1.0 - var/oxygentanks = 10 - var/plasmatanks = 10 - var/list/oxytanks = list() //sorry for the similar var names - var/list/platanks = list() - -/obj/structure/dispenser/oxygen - plasmatanks = 0 - -/obj/structure/dispenser/plasma - oxygentanks = 0 - -/obj/structure/dispenser/New() - ..() - update_icon() - -/obj/structure/dispenser/update_icon() - overlays.Cut() - switch(oxygentanks) - if(1 to 3) overlays += "oxygen-[oxygentanks]" - if(4 to INFINITY) overlays += "oxygen-4" - switch(plasmatanks) - if(1 to 4) overlays += "plasma-[plasmatanks]" - if(5 to INFINITY) overlays += "plasma-5" - -/obj/structure/dispenser/attack_hand(mob/user) - if(..()) - return 1 - add_fingerprint(user) - ui_interact(user) - -/obj/structure/dispenser/attack_ghost(mob/user) - ui_interact(user) - -/obj/structure/dispenser/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, var/master_ui = null, var/datum/topic_state/state = default_state) - user.set_machine(src) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "tank_dispenser.tmpl", name, 275, 100, state = state) - ui.open() - -/obj/structure/dispenser/ui_data(user) - var/list/data = list() - data["o_tanks"] = oxygentanks - data["p_tanks"] = plasmatanks - return data - -/obj/structure/dispenser/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/tank/oxygen) || istype(I, /obj/item/tank/air) || istype(I, /obj/item/tank/anesthetic)) - if(oxygentanks < 10) - user.drop_item() - I.forceMove(src) - oxytanks.Add(I) - oxygentanks++ - update_icon() - to_chat(user, "You put [I] in [src].") - else - to_chat(user, "[src] is full.") - SSnanoui.update_uis(src) - return - if(istype(I, /obj/item/tank/plasma)) - if(plasmatanks < 10) - user.drop_item() - I.forceMove(src) - platanks.Add(I) - plasmatanks++ - update_icon() - to_chat(user, "You put [I] in [src].") - else - to_chat(user, "[src] is full.") - SSnanoui.update_uis(src) - return - if(istype(I, /obj/item/wrench)) - if(anchored) - to_chat(user, "You lean down and unwrench [src].") - anchored = 0 - else - to_chat(user, "You wrench [src] into place.") - anchored = 1 - return - return ..() - -/obj/structure/dispenser/Topic(href, href_list) - if(..()) - return 1 - - if(Adjacent(usr)) - usr.set_machine(src) - if(href_list["oxygen"]) - if(oxygentanks > 0) - var/obj/item/tank/oxygen/O - if(oxytanks.len == oxygentanks) - O = oxytanks[1] - oxytanks.Remove(O) - else - O = new /obj/item/tank/oxygen(loc) - O.loc = loc - to_chat(usr, "You take [O] out of [src].") - oxygentanks-- - update_icon() - if(href_list["plasma"]) - if(plasmatanks > 0) - var/obj/item/tank/plasma/P - if(platanks.len == plasmatanks) - P = platanks[1] - platanks.Remove(P) - else - P = new /obj/item/tank/plasma(loc) - P.loc = loc - to_chat(usr, "You take [P] out of [src].") - plasmatanks-- - update_icon() - add_fingerprint(usr) - updateUsrDialog() - SSnanoui.update_uis(src) - else - SSnanoui.close_user_uis(usr,src) - return 1 - -/obj/structure/tank_dispenser/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - for(var/X in src) - var/obj/item/I = X - I.forceMove(loc) - new /obj/item/stack/sheet/metal(loc, 2) - qdel(src) \ No newline at end of file +/obj/structure/dispenser + name = "tank storage unit" + desc = "A simple yet bulky storage device for gas tanks. Has room for up to ten oxygen tanks, and ten plasma tanks." + icon = 'icons/obj/objects.dmi' + icon_state = "dispenser" + density = 1 + anchored = 1.0 + var/oxygentanks = 10 + var/plasmatanks = 10 + var/list/oxytanks = list() //sorry for the similar var names + var/list/platanks = list() + +/obj/structure/dispenser/oxygen + plasmatanks = 0 + +/obj/structure/dispenser/plasma + oxygentanks = 0 + +/obj/structure/dispenser/New() + ..() + update_icon() + +/obj/structure/dispenser/update_icon() + overlays.Cut() + switch(oxygentanks) + if(1 to 3) overlays += "oxygen-[oxygentanks]" + if(4 to INFINITY) overlays += "oxygen-4" + switch(plasmatanks) + if(1 to 4) overlays += "plasma-[plasmatanks]" + if(5 to INFINITY) overlays += "plasma-5" + +/obj/structure/dispenser/attack_hand(mob/user) + if(..()) + return 1 + add_fingerprint(user) + ui_interact(user) + +/obj/structure/dispenser/attack_ghost(mob/user) + ui_interact(user) + +/obj/structure/dispenser/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, var/master_ui = null, var/datum/topic_state/state = default_state) + user.set_machine(src) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "tank_dispenser.tmpl", name, 275, 100, state = state) + ui.open() + +/obj/structure/dispenser/ui_data(user) + var/list/data = list() + data["o_tanks"] = oxygentanks + data["p_tanks"] = plasmatanks + return data + +/obj/structure/dispenser/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/tank/oxygen) || istype(I, /obj/item/tank/air) || istype(I, /obj/item/tank/anesthetic)) + if(oxygentanks < 10) + user.drop_item() + I.forceMove(src) + oxytanks.Add(I) + oxygentanks++ + update_icon() + to_chat(user, "You put [I] in [src].") + else + to_chat(user, "[src] is full.") + SSnanoui.update_uis(src) + return + if(istype(I, /obj/item/tank/plasma)) + if(plasmatanks < 10) + user.drop_item() + I.forceMove(src) + platanks.Add(I) + plasmatanks++ + update_icon() + to_chat(user, "You put [I] in [src].") + else + to_chat(user, "[src] is full.") + SSnanoui.update_uis(src) + return + if(istype(I, /obj/item/wrench)) + if(anchored) + to_chat(user, "You lean down and unwrench [src].") + anchored = 0 + else + to_chat(user, "You wrench [src] into place.") + anchored = 1 + return + return ..() + +/obj/structure/dispenser/Topic(href, href_list) + if(..()) + return 1 + + if(Adjacent(usr)) + usr.set_machine(src) + if(href_list["oxygen"]) + if(oxygentanks > 0) + var/obj/item/tank/oxygen/O + if(oxytanks.len == oxygentanks) + O = oxytanks[1] + oxytanks.Remove(O) + else + O = new /obj/item/tank/oxygen(loc) + O.loc = loc + to_chat(usr, "You take [O] out of [src].") + oxygentanks-- + update_icon() + if(href_list["plasma"]) + if(plasmatanks > 0) + var/obj/item/tank/plasma/P + if(platanks.len == plasmatanks) + P = platanks[1] + platanks.Remove(P) + else + P = new /obj/item/tank/plasma(loc) + P.loc = loc + to_chat(usr, "You take [P] out of [src].") + plasmatanks-- + update_icon() + add_fingerprint(usr) + updateUsrDialog() + SSnanoui.update_uis(src) + else + SSnanoui.close_user_uis(usr,src) + return 1 + +/obj/structure/tank_dispenser/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + for(var/X in src) + var/obj/item/I = X + I.forceMove(loc) + new /obj/item/stack/sheet/metal(loc, 2) + qdel(src) diff --git a/code/game/objects/structures/target_stake.dm b/code/game/objects/structures/target_stake.dm index 79e9c6cfab1..30e6e13cdc6 100644 --- a/code/game/objects/structures/target_stake.dm +++ b/code/game/objects/structures/target_stake.dm @@ -1,54 +1,54 @@ -// Basically they are for the firing range -/obj/structure/target_stake - name = "target stake" - desc = "A thin platform with negatively-magnetized wheels." - icon = 'icons/obj/objects.dmi' - icon_state = "target_stake" - density = 1 - flags = CONDUCT - var/obj/item/target/pinned_target // the current pinned target - -/obj/structure/target_stake/Destroy() - QDEL_NULL(pinned_target) - return ..() - -/obj/structure/target_stake/Move() - . = ..() - // Move the pinned target along with the stake - if(pinned_target in view(3, src)) - pinned_target.loc = loc - - else // Sanity check: if the pinned target can't be found in immediate view - pinned_target = null - density = 1 - -/obj/structure/target_stake/attackby(obj/item/W, mob/user, params) - // Putting objects on the stake. Most importantly, targets - if(istype(W, /obj/item/target) && !pinned_target) - density = 0 - W.density = 1 - user.drop_item(src) - W.loc = loc - W.layer = 3.1 - pinned_target = W - to_chat(user, "You slide the target into the stake.") - return - return ..() - -/obj/structure/target_stake/attack_hand(mob/user) - // taking pinned targets off! - if(pinned_target) - density = 1 - pinned_target.density = 0 - pinned_target.layer = OBJ_LAYER - - pinned_target.loc = user.loc - if(ishuman(user)) - if(!user.get_active_hand()) - user.put_in_hands(pinned_target) - to_chat(user, "You take the target out of the stake.") - else - pinned_target.loc = get_turf(user) - to_chat(user, "You take the target out of the stake.") - - pinned_target = null \ No newline at end of file +// Basically they are for the firing range +/obj/structure/target_stake + name = "target stake" + desc = "A thin platform with negatively-magnetized wheels." + icon = 'icons/obj/objects.dmi' + icon_state = "target_stake" + density = 1 + flags = CONDUCT + var/obj/item/target/pinned_target // the current pinned target + +/obj/structure/target_stake/Destroy() + QDEL_NULL(pinned_target) + return ..() + +/obj/structure/target_stake/Move() + . = ..() + // Move the pinned target along with the stake + if(pinned_target in view(3, src)) + pinned_target.loc = loc + + else // Sanity check: if the pinned target can't be found in immediate view + pinned_target = null + density = 1 + +/obj/structure/target_stake/attackby(obj/item/W, mob/user, params) + // Putting objects on the stake. Most importantly, targets + if(istype(W, /obj/item/target) && !pinned_target) + density = 0 + W.density = 1 + user.drop_item(src) + W.loc = loc + W.layer = 3.1 + pinned_target = W + to_chat(user, "You slide the target into the stake.") + return + return ..() + +/obj/structure/target_stake/attack_hand(mob/user) + // taking pinned targets off! + if(pinned_target) + density = 1 + pinned_target.density = 0 + pinned_target.layer = OBJ_LAYER + + pinned_target.loc = user.loc + if(ishuman(user)) + if(!user.get_active_hand()) + user.put_in_hands(pinned_target) + to_chat(user, "You take the target out of the stake.") + else + pinned_target.loc = get_turf(user) + to_chat(user, "You take the target out of the stake.") + + pinned_target = null diff --git a/code/game/objects/structures/transit_tubes/station.dm b/code/game/objects/structures/transit_tubes/station.dm index 04271015f4c..f8f406d1381 100644 --- a/code/game/objects/structures/transit_tubes/station.dm +++ b/code/game/objects/structures/transit_tubes/station.dm @@ -139,4 +139,4 @@ // Tube station directions are simply 90 to either side of // the exit. /obj/structure/transit_tube/station/init_dirs() - tube_dirs = list(turn(dir, 90), turn(dir, -90)) \ No newline at end of file + tube_dirs = list(turn(dir, 90), turn(dir, -90)) diff --git a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm index 6a06109277b..7095fd4eeda 100644 --- a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm +++ b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm @@ -174,4 +174,4 @@ if(dir in tube.directions()) if(tube.has_exit(direction)) dir = direction - return \ No newline at end of file + return diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 90d20ecbd75..01136ea1046 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -1,766 +1,766 @@ -var/global/wcBar = pick(list("#0d8395", "#58b5c3", "#58c366", "#90d79a", "#ffffff")) -var/global/wcBrig = pick(list("#aa0808", "#7f0606", "#ff0000")) -var/global/wcCommon = pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e", "#8fcf44", "#ffffff")) - -/obj/proc/color_windows(obj/W) - var/list/wcBarAreas = list(/area/crew_quarters/bar) - var/list/wcBrigAreas = list(/area/security, /area/shuttle/gamma) - - var/newcolor - var/turf/T = get_turf(W) - if(!istype(T)) - return - var/area/A = T.loc - - if(is_type_in_list(A,wcBarAreas)) - newcolor = wcBar - else if(is_type_in_list(A,wcBrigAreas)) - newcolor = wcBrig - else - newcolor = wcCommon - - return newcolor - -/obj/structure/window - name = "window" - desc = "A window." - icon_state = "window" - density = TRUE - layer = ABOVE_OBJ_LAYER //Just above doors - pressure_resistance = 4*ONE_ATMOSPHERE - anchored = TRUE - flags = ON_BORDER - can_be_unanchored = TRUE - max_integrity = 25 - resistance_flags = ACID_PROOF - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - var/ini_dir = null - var/state = WINDOW_OUT_OF_FRAME - var/reinf = FALSE - var/heat_resistance = 800 - var/decon_speed = null - var/fulltile = FALSE - var/shardtype = /obj/item/shard - var/glass_type = /obj/item/stack/sheet/glass - var/glass_amount = 1 - var/cancolor = FALSE - var/image/crack_overlay - var/list/debris = list() - var/real_explosion_block //ignore this, just use explosion_block - var/breaksound = "shatter" - var/hitsound = 'sound/effects/Glasshit.ogg' - -/obj/structure/window/examine(mob/user) - . = ..() - if(reinf) - if(anchored && state == WINDOW_SCREWED_TO_FRAME) - . += "The window is screwed to the frame." - else if(anchored && state == WINDOW_IN_FRAME) - . += "The window is unscrewed but pried into the frame." - else if(anchored && state == WINDOW_OUT_OF_FRAME) - . += "The window is out of the frame, but could be pried in. It is screwed to the floor." - else if(!anchored) - . += "The window is unscrewed from the floor, and could be deconstructed by wrenching." - else - if(anchored) - . += "The window is screwed to the floor." - else - . += "The window is unscrewed from the floor, and could be deconstructed by wrenching." - if(!anchored && !fulltile) - . += "Alt-click to rotate it." - -/obj/structure/window/New(Loc, direct) - ..() - if(direct) - setDir(direct) - if(reinf && anchored) - state = WINDOW_SCREWED_TO_FRAME - - ini_dir = dir - - if(!color && cancolor) - color = color_windows(src) - - // Precreate our own debris - - var/shards = 1 - if(fulltile) - shards++ - setDir() - - if(decon_speed == null) - if(fulltile) - decon_speed = 20 - else - decon_speed = 1 - - var/rods = 0 - if(reinf) - rods++ - if(fulltile) - rods++ - - for(var/i in 1 to shards) - debris += new shardtype(src) - if(rods) - debris += new /obj/item/stack/rods(src, rods) - - //windows only block while reinforced and fulltile, so we'll use the proc - real_explosion_block = explosion_block - explosion_block = EXPLOSION_BLOCK_PROC - -/obj/structure/window/Initialize() - air_update_turf(1) - return ..() - -/obj/structure/window/narsie_act() - color = NARSIE_WINDOW_COLOUR - for(var/obj/item/shard/shard in debris) - shard.color = NARSIE_WINDOW_COLOUR - -/obj/structure/window/ratvar_act() - if(!fulltile) - new/obj/structure/window/reinforced/clockwork(get_turf(src), dir) - else - new/obj/structure/window/reinforced/clockwork/fulltile(get_turf(src)) - qdel(src) - -/obj/structure/window/rpd_act() - return - -/obj/structure/window/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct(FALSE) - -/obj/structure/window/setDir(direct) - if(!fulltile) - ..() - else - ..(FULLTILE_WINDOW_DIR) - -/obj/structure/window/CanPass(atom/movable/mover, turf/target, height=0) - if(istype(mover) && mover.checkpass(PASSGLASS)) - return 1 - if(dir == FULLTILE_WINDOW_DIR) - return 0 //full tile window, you can't move into it! - if(get_dir(loc, target) == dir) - return !density - if(istype(mover, /obj/structure/window)) - var/obj/structure/window/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/structure/windoor_assembly)) - var/obj/structure/windoor_assembly/W = mover - if(!valid_window_location(loc, W.ini_dir)) - return FALSE - else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) - return FALSE - return 1 - -/obj/structure/window/CheckExit(atom/movable/O, target) - if(istype(O) && O.checkpass(PASSGLASS)) - return 1 - if(get_dir(O.loc, target) == dir) - return 0 - return 1 - -/obj/structure/window/CanAStarPass(ID, to_dir) - if(!density) - return 1 - if((dir == FULLTILE_WINDOW_DIR) || (dir == to_dir)) - return 0 - - return 1 - -/obj/structure/window/attack_tk(mob/user) - user.changeNext_move(CLICK_CD_MELEE) - user.visible_message("Something knocks on [src].") - add_fingerprint(user) - playsound(src, 'sound/effects/glassknock.ogg', 50, 1) - -/obj/structure/window/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) - if(!can_be_reached(user)) - return 1 - . = ..() - -/obj/structure/window/attack_hand(mob/user) - if(!can_be_reached(user)) - return - if(user.a_intent == INTENT_HARM) - user.changeNext_move(CLICK_CD_MELEE) - playsound(src, 'sound/effects/glassknock.ogg', 80, 1) - user.visible_message("[user] bangs against [src]!", \ - "You bang against [src]!", \ - "You hear a banging sound.") - add_fingerprint(user) - else - user.changeNext_move(CLICK_CD_MELEE) - playsound(src, 'sound/effects/glassknock.ogg', 80, 1) - user.visible_message("[user] knocks on [src].", \ - "You knock on [src].", \ - "You hear a knocking sound.") - add_fingerprint(user) - -/obj/structure/window/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) //used by attack_alien, attack_animal, and attack_slime - if(!can_be_reached(user)) - return - ..() - -/obj/structure/window/attackby(obj/item/I, mob/living/user, params) - if(!can_be_reached(user)) - return 1 //skip the afterattack - - add_fingerprint(user) - if(istype(I, /obj/item/grab) && get_dist(src, user) < 2) - var/obj/item/grab/G = I - if(isliving(G.affecting)) - var/mob/living/M = G.affecting - var/state = G.state - qdel(I) //gotta delete it here because if window breaks, it won't get deleted - switch(state) - if(1) - M.visible_message("[user] slams [M] against \the [src]!") - M.apply_damage(7) - take_damage(10) - if(2) - M.visible_message("[user] bashes [M] against \the [src]!") - if(prob(50)) - M.Weaken(1) - M.apply_damage(10) - take_damage(25) - if(3) - M.visible_message("[user] crushes [M] against \the [src]!") - M.Weaken(5) - M.apply_damage(20) - take_damage(50) - if(4) - visible_message("[user] smashes [M] against \the [src]!") - M.Weaken(5) - M.apply_damage(30) - take_damage(75) - return - return ..() - - -/obj/structure/window/crowbar_act(mob/user, obj/item/I) - if(!reinf) - return - if(state != WINDOW_OUT_OF_FRAME && state != WINDOW_IN_FRAME) - return - if(flags & NODECONSTRUCT) - return - . = TRUE - 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))) - 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.") - -/obj/structure/window/screwdriver_act(mob/user, obj/item/I) - if(flags & NODECONSTRUCT) - return - . = TRUE - if(!can_be_reached(user)) - return - 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))) - 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))) - 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))) - return - anchored = !anchored - air_update_turf(TRUE) - update_nearby_icons() - to_chat(user, "You [anchored ? "fasten the window to":"unfasten the window from"] the floor.") - -/obj/structure/window/wrench_act(mob/user, obj/item/I) - if(flags & NODECONSTRUCT) - return - if(anchored) - return - . = TRUE - 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))) - return - var/obj/item/stack/sheet/G = new glass_type(user.loc, glass_amount) - G.add_fingerprint(user) - playsound(src, 'sound/items/deconstruct.ogg', 50, 1) - to_chat(user, "You successfully disassemble [src].") - qdel(src) - -/obj/structure/window/welder_act(mob/user, obj/item/I) - if(user.a_intent != INTENT_HELP) - return - . = TRUE - if(!can_be_reached(user)) - return - if(obj_integrity >= max_integrity) - to_chat(user, "[src] is already in good condition!") - return - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_REPAIR_MESSAGE - if(I.use_tool(src, user, 40, volume = I.tool_volume)) - obj_integrity = max_integrity - WELDER_REPAIR_SUCCESS_MESSAGE - -/obj/structure/window/proc/check_state(checked_state) - if(state == checked_state) - return TRUE - -/obj/structure/window/proc/check_anchored(checked_anchored) - if(anchored == checked_anchored) - return TRUE - -/obj/structure/window/proc/check_state_and_anchored(checked_state, checked_anchored) - return check_state(checked_state) && check_anchored(checked_anchored) - -/obj/structure/window/mech_melee_attack(obj/mecha/M) - if(!can_be_reached()) - return - ..() - -/obj/structure/window/proc/can_be_reached(mob/user) - if(!fulltile) - if(get_dir(user, src) & dir) - for(var/obj/O in loc) - if(!O.CanPass(user, user.loc, 1)) - return 0 - return 1 - -/obj/structure/window/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - . = ..() - if(.) //received damage - update_nearby_icons() - -/obj/structure/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(src, hitsound, 75, TRUE) - else - playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(src, 'sound/items/Welder.ogg', 100, TRUE) - -/obj/structure/window/deconstruct(disassembled = TRUE) - if(QDELETED(src)) - return - if(!disassembled) - playsound(src, breaksound, 70, 1) - if(!(flags & NODECONSTRUCT)) - for(var/i in debris) - var/obj/item/I = i - I.forceMove(loc) - transfer_fingerprints_to(I) - qdel(src) - update_nearby_icons() - -/obj/structure/window/verb/rotate() - set name = "Rotate Window Counter-Clockwise" - set category = "Object" - set src in oview(1) - - if(usr.incapacitated()) - return - - if(anchored) - to_chat(usr, "[src] cannot be rotated while it is fastened to the floor!") - return FALSE - - var/target_dir = turn(dir, 90) - if(!valid_window_location(loc, target_dir)) - to_chat(usr, "[src] cannot be rotated in that direction!") - return FALSE - - setDir(target_dir) - air_update_turf(1) - ini_dir = dir - add_fingerprint(usr) - return TRUE - -/obj/structure/window/verb/revrotate() - set name = "Rotate Window Clockwise" - set category = "Object" - set src in oview(1) - - if(usr.incapacitated()) - return - - if(anchored) - to_chat(usr, "[src] cannot be rotated while it is fastened to the floor!") - return FALSE - - var/target_dir = turn(dir, 270) - - if(!valid_window_location(loc, target_dir)) - to_chat(usr, "[src] cannot be rotated in that direction!") - return FALSE - - setDir(target_dir) - ini_dir = dir - add_fingerprint(usr) - return TRUE - -/obj/structure/window/AltClick(mob/user) - - if(user.incapacitated()) - to_chat(user, "You can't do that right now!") - return - - if(!Adjacent(user)) - to_chat(user, "Move closer to the window!") - return - - if(anchored) - to_chat(user, "[src] cannot be rotated while it is fastened to the floor!") - return FALSE - - var/target_dir = turn(dir, 270) - - if(!valid_window_location(loc, target_dir)) - target_dir = turn(dir, 90) - if(!valid_window_location(loc, target_dir)) - to_chat(user, "There is no room to rotate the [src]") - return FALSE - - setDir(target_dir) - ini_dir = dir - add_fingerprint(user) - return TRUE - -/obj/structure/window/Destroy() - density = FALSE - air_update_turf(1) - update_nearby_icons() - return ..() - -/obj/structure/window/Move() - var/turf/T = loc - . = ..() - setDir(ini_dir) - move_update_air(T) - -/obj/structure/window/CanAtmosPass(turf/T) - if(!anchored || !density) - return TRUE - return !(FULLTILE_WINDOW_DIR == dir || dir == get_dir(loc, T)) - -//This proc is used to update the icons of nearby windows. -/obj/structure/window/proc/update_nearby_icons() - update_icon() - if(smooth) - queue_smooth_neighbors(src) - -/obj/structure/window/update_icon() - if(!QDELETED(src)) - if(!fulltile) - return - var/ratio = obj_integrity / max_integrity - ratio = CEILING(ratio*4, 1) * 25 - if(smooth) - queue_smooth(src) - overlays -= crack_overlay - if(ratio > 75) - return - crack_overlay = image('icons/obj/structures.dmi',"damage[ratio]",-(layer+0.1)) - overlays += crack_overlay - -/obj/structure/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > (T0C + heat_resistance)) - take_damage(round(exposed_volume / 100), BURN, 0, 0) - -/obj/structure/window/GetExplosionBlock() - return reinf && fulltile ? real_explosion_block : 0 - -/obj/structure/window/basic - desc = "It looks thin and flimsy. A few knocks with... anything, really should shatter it." - -/obj/structure/window/reinforced - name = "reinforced window" - desc = "It looks rather strong. Might take a few good hits to shatter it." - icon_state = "rwindow" - reinf = TRUE - cancolor = TRUE - heat_resistance = 1600 - armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) - max_integrity = 50 - explosion_block = 1 - glass_type = /obj/item/stack/sheet/rglass - -/obj/structure/window/reinforced/tinted - name = "tinted window" - desc = "It looks rather strong and opaque. Might take a few good hits to shatter it." - icon_state = "twindow" - opacity = 1 - -/obj/structure/window/reinforced/tinted/frosted - name = "frosted window" - desc = "It looks rather strong and frosted over. Looks like it might take a few less hits then a normal reinforced window." - icon_state = "fwindow" - max_integrity = 30 - -/obj/structure/window/reinforced/polarized - name = "electrochromic window" - desc = "Adjusts its tint with voltage. Might take a few good hits to shatter it." - var/id - -/obj/structure/window/reinforced/polarized/proc/toggle() - if(opacity) - animate(src, color="#FFFFFF", time=5) - set_opacity(0) - else - animate(src, color="#222222", time=5) - set_opacity(1) - -/obj/machinery/button/windowtint - name = "window tint control" - icon = 'icons/obj/power.dmi' - icon_state = "light0" - desc = "A remote control switch for polarized windows." - var/range = 7 - var/id = 0 - var/active = 0 - -/obj/machinery/button/windowtint/attack_hand(mob/user) - if(..()) - return 1 - - toggle_tint() - -/obj/machinery/button/windowtint/proc/toggle_tint() - use_power(5) - - active = !active - update_icon() - - for(var/obj/structure/window/reinforced/polarized/W in range(src,range)) - if(W.id == src.id || !W.id) - spawn(0) - W.toggle() - return - -/obj/machinery/button/windowtint/power_change() - ..() - if(active && !powered(power_channel)) - toggle_tint() - -/obj/machinery/button/windowtint/update_icon() - icon_state = "light[active]" - -/obj/structure/window/plasmabasic - name = "plasma window" - desc = "A window made out of a plasma-silicate alloy. It looks insanely tough to break and burn through." - icon_state = "plasmawindow" - shardtype = /obj/item/shard/plasma - glass_type = /obj/item/stack/sheet/plasmaglass - heat_resistance = 32000 - max_integrity = 150 - explosion_block = 1 - armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100) - -/obj/structure/window/plasmabasic/BlockSuperconductivity() - return 1 - -/obj/structure/window/plasmareinforced - name = "reinforced plasma window" - desc = "A plasma-glass alloy window, with rods supporting it. It looks hopelessly tough to break. It also looks completely fireproof, considering how basic plasma windows are insanely fireproof." - icon_state = "plasmarwindow" - shardtype = /obj/item/shard/plasma - glass_type = /obj/item/stack/sheet/plasmarglass - reinf = TRUE - max_integrity = 500 - explosion_block = 2 - armor = list("melee" = 85, "bullet" = 20, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100) - damage_deflection = 21 - -/obj/structure/window/plasmareinforced/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - return - -/obj/structure/window/plasmareinforced/BlockSuperconductivity() - return 1 //okay this SHOULD MAKE THE TOXINS CHAMBER WORK - -/obj/structure/window/full - glass_amount = 2 - dir = FULLTILE_WINDOW_DIR - level = 3 - fulltile = TRUE - flags = PREVENT_CLICK_UNDER - -/obj/structure/window/full/basic - desc = "It looks thin and flimsy. A few knocks with... anything, really should shatter it." - icon = 'icons/obj/smooth_structures/window.dmi' - icon_state = "window" - max_integrity = 50 - smooth = SMOOTH_TRUE - cancolor = TRUE - canSmoothWith = list(/obj/structure/window/full/basic, /obj/structure/window/full/reinforced, /obj/structure/window/full/reinforced/tinted, /obj/structure/window/full/plasmabasic, /obj/structure/window/full/plasmareinforced) - -/obj/structure/window/full/plasmabasic - name = "plasma window" - desc = "A plasma-glass alloy window. It looks insanely tough to break. It appears it's also insanely tough to burn through." - icon = 'icons/obj/smooth_structures/plasma_window.dmi' - icon_state = "plasmawindow" - shardtype = /obj/item/shard/plasma - glass_type = /obj/item/stack/sheet/plasmaglass - heat_resistance = 32000 - max_integrity = 300 - smooth = SMOOTH_TRUE - canSmoothWith = list(/obj/structure/window/full/basic, /obj/structure/window/full/reinforced, /obj/structure/window/full/reinforced/tinted, /obj/structure/window/full/plasmabasic, /obj/structure/window/full/plasmareinforced) - explosion_block = 1 - armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100) - -/obj/structure/window/full/plasmareinforced - name = "reinforced plasma window" - desc = "A plasma-glass alloy window, with rods supporting it. It looks hopelessly tough to break. It also looks completely fireproof, considering how basic plasma windows are insanely fireproof." - icon = 'icons/obj/smooth_structures/rplasma_window.dmi' - icon_state = "rplasmawindow" - shardtype = /obj/item/shard/plasma - glass_type = /obj/item/stack/sheet/plasmarglass - smooth = SMOOTH_TRUE - reinf = TRUE - max_integrity = 1000 - explosion_block = 2 - armor = list("melee" = 85, "bullet" = 20, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100) - -/obj/structure/window/full/plasmareinforced/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - return - -/obj/structure/window/full/reinforced - name = "reinforced window" - desc = "It looks rather strong. Might take a few good hits to shatter it." - icon = 'icons/obj/smooth_structures/reinforced_window.dmi' - icon_state = "r_window" - smooth = SMOOTH_TRUE - canSmoothWith = list(/obj/structure/window/full/basic, /obj/structure/window/full/reinforced, /obj/structure/window/full/reinforced/tinted, /obj/structure/window/full/plasmabasic, /obj/structure/window/full/plasmareinforced) - max_integrity = 100 - reinf = TRUE - heat_resistance = 1600 - armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) - explosion_block = 1 - glass_type = /obj/item/stack/sheet/rglass - cancolor = TRUE - -/obj/structure/window/full/reinforced/tinted - name = "tinted window" - desc = "It looks rather strong and opaque. Might take a few good hits to shatter it." - icon = 'icons/obj/smooth_structures/tinted_window.dmi' - icon_state = "tinted_window" - opacity = 1 - -obj/structure/window/full/reinforced/ice - icon = 'icons/obj/smooth_structures/rice_window.dmi' - icon_state = "ice_window" - max_integrity = 150 - cancolor = FALSE - -/obj/structure/window/full/shuttle - name = "shuttle window" - desc = "A reinforced, air-locked pod window." - icon = 'icons/obj/smooth_structures/shuttle_window.dmi' - icon_state = "shuttle_window" - max_integrity = 100 - reinf = TRUE - heat_resistance = 1600 - explosion_block = 3 - armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) - smooth = SMOOTH_TRUE - canSmoothWith = null - glass_type = /obj/item/stack/sheet/titaniumglass - -/obj/structure/window/full/shuttle/narsie_act() - color = "#3C3434" - -/obj/structure/window/full/shuttle/tinted - opacity = TRUE - -/obj/structure/window/plastitanium - name = "plastitanium window" - desc = "An evil looking window of plasma and titanium." - icon = 'icons/obj/smooth_structures/plastitanium_window.dmi' - icon_state = "plastitanium_window" - dir = FULLTILE_WINDOW_DIR - max_integrity = 100 - fulltile = TRUE - flags = PREVENT_CLICK_UNDER - reinf = TRUE - heat_resistance = 1600 - armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) - smooth = SMOOTH_TRUE - canSmoothWith = null - explosion_block = 3 - level = 3 - glass_type = /obj/item/stack/sheet/plastitaniumglass - glass_amount = 2 - -/obj/structure/window/reinforced/clockwork - name = "brass window" - desc = "A paper-thin pane of translucent yet reinforced brass." - icon = 'icons/obj/smooth_structures/clockwork_window.dmi' - icon_state = "clockwork_window_single" - resistance_flags = FIRE_PROOF | ACID_PROOF - max_integrity = 80 - armor = list("melee" = 60, "bullet" = 25, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) - explosion_block = 2 //fancy AND hard to destroy. the most useful combination. - glass_type = /obj/item/stack/tile/brass - reinf = FALSE - cancolor = FALSE - var/made_glow = FALSE - -/obj/structure/window/reinforced/clockwork/New(loc, direct) - if(fulltile) - made_glow = TRUE - ..() - QDEL_LIST(debris) - if(fulltile) - new /obj/effect/temp_visual/ratvar/window(get_turf(src)) - debris += new/obj/item/stack/tile/brass(src, 2) - else - debris += new/obj/item/stack/tile/brass(src, 1) - -/obj/structure/window/reinforced/clockwork/setDir(direct) - if(!made_glow) - var/obj/effect/E = new /obj/effect/temp_visual/ratvar/window/single(get_turf(src)) - E.setDir(direct) - made_glow = TRUE - ..() - -/obj/structure/window/reinforced/clockwork/ratvar_act() - obj_integrity = max_integrity - update_icon() - -/obj/structure/window/reinforced/clockwork/narsie_act() - take_damage(rand(25, 75), BRUTE) - if(src) - var/previouscolor = color - color = "#960000" - animate(src, color = previouscolor, time = 8) - -/obj/structure/window/reinforced/clockwork/fulltile - icon_state = "clockwork_window" - smooth = SMOOTH_TRUE - canSmoothWith = null - fulltile = TRUE - flags = PREVENT_CLICK_UNDER - dir = FULLTILE_WINDOW_DIR - max_integrity = 120 - level = 3 - glass_amount = 2 \ No newline at end of file +var/global/wcBar = pick(list("#0d8395", "#58b5c3", "#58c366", "#90d79a", "#ffffff")) +var/global/wcBrig = pick(list("#aa0808", "#7f0606", "#ff0000")) +var/global/wcCommon = pick(list("#379963", "#0d8395", "#58b5c3", "#49e46e", "#8fcf44", "#ffffff")) + +/obj/proc/color_windows(obj/W) + var/list/wcBarAreas = list(/area/crew_quarters/bar) + var/list/wcBrigAreas = list(/area/security, /area/shuttle/gamma) + + var/newcolor + var/turf/T = get_turf(W) + if(!istype(T)) + return + var/area/A = T.loc + + if(is_type_in_list(A,wcBarAreas)) + newcolor = wcBar + else if(is_type_in_list(A,wcBrigAreas)) + newcolor = wcBrig + else + newcolor = wcCommon + + return newcolor + +/obj/structure/window + name = "window" + desc = "A window." + icon_state = "window" + density = TRUE + layer = ABOVE_OBJ_LAYER //Just above doors + pressure_resistance = 4*ONE_ATMOSPHERE + anchored = TRUE + flags = ON_BORDER + can_be_unanchored = TRUE + max_integrity = 25 + resistance_flags = ACID_PROOF + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + var/ini_dir = null + var/state = WINDOW_OUT_OF_FRAME + var/reinf = FALSE + var/heat_resistance = 800 + var/decon_speed = null + var/fulltile = FALSE + var/shardtype = /obj/item/shard + var/glass_type = /obj/item/stack/sheet/glass + var/glass_amount = 1 + var/cancolor = FALSE + var/image/crack_overlay + var/list/debris = list() + var/real_explosion_block //ignore this, just use explosion_block + var/breaksound = "shatter" + var/hitsound = 'sound/effects/Glasshit.ogg' + +/obj/structure/window/examine(mob/user) + . = ..() + if(reinf) + if(anchored && state == WINDOW_SCREWED_TO_FRAME) + . += "The window is screwed to the frame." + else if(anchored && state == WINDOW_IN_FRAME) + . += "The window is unscrewed but pried into the frame." + else if(anchored && state == WINDOW_OUT_OF_FRAME) + . += "The window is out of the frame, but could be pried in. It is screwed to the floor." + else if(!anchored) + . += "The window is unscrewed from the floor, and could be deconstructed by wrenching." + else + if(anchored) + . += "The window is screwed to the floor." + else + . += "The window is unscrewed from the floor, and could be deconstructed by wrenching." + if(!anchored && !fulltile) + . += "Alt-click to rotate it." + +/obj/structure/window/New(Loc, direct) + ..() + if(direct) + setDir(direct) + if(reinf && anchored) + state = WINDOW_SCREWED_TO_FRAME + + ini_dir = dir + + if(!color && cancolor) + color = color_windows(src) + + // Precreate our own debris + + var/shards = 1 + if(fulltile) + shards++ + setDir() + + if(decon_speed == null) + if(fulltile) + decon_speed = 20 + else + decon_speed = 1 + + var/rods = 0 + if(reinf) + rods++ + if(fulltile) + rods++ + + for(var/i in 1 to shards) + debris += new shardtype(src) + if(rods) + debris += new /obj/item/stack/rods(src, rods) + + //windows only block while reinforced and fulltile, so we'll use the proc + real_explosion_block = explosion_block + explosion_block = EXPLOSION_BLOCK_PROC + +/obj/structure/window/Initialize() + air_update_turf(1) + return ..() + +/obj/structure/window/narsie_act() + color = NARSIE_WINDOW_COLOUR + for(var/obj/item/shard/shard in debris) + shard.color = NARSIE_WINDOW_COLOUR + +/obj/structure/window/ratvar_act() + if(!fulltile) + new/obj/structure/window/reinforced/clockwork(get_turf(src), dir) + else + new/obj/structure/window/reinforced/clockwork/fulltile(get_turf(src)) + qdel(src) + +/obj/structure/window/rpd_act() + return + +/obj/structure/window/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct(FALSE) + +/obj/structure/window/setDir(direct) + if(!fulltile) + ..() + else + ..(FULLTILE_WINDOW_DIR) + +/obj/structure/window/CanPass(atom/movable/mover, turf/target, height=0) + if(istype(mover) && mover.checkpass(PASSGLASS)) + return 1 + if(dir == FULLTILE_WINDOW_DIR) + return 0 //full tile window, you can't move into it! + if(get_dir(loc, target) == dir) + return !density + if(istype(mover, /obj/structure/window)) + var/obj/structure/window/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/structure/windoor_assembly)) + var/obj/structure/windoor_assembly/W = mover + if(!valid_window_location(loc, W.ini_dir)) + return FALSE + else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir)) + return FALSE + return 1 + +/obj/structure/window/CheckExit(atom/movable/O, target) + if(istype(O) && O.checkpass(PASSGLASS)) + return 1 + if(get_dir(O.loc, target) == dir) + return 0 + return 1 + +/obj/structure/window/CanAStarPass(ID, to_dir) + if(!density) + return 1 + if((dir == FULLTILE_WINDOW_DIR) || (dir == to_dir)) + return 0 + + return 1 + +/obj/structure/window/attack_tk(mob/user) + user.changeNext_move(CLICK_CD_MELEE) + user.visible_message("Something knocks on [src].") + add_fingerprint(user) + playsound(src, 'sound/effects/glassknock.ogg', 50, 1) + +/obj/structure/window/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) + if(!can_be_reached(user)) + return 1 + . = ..() + +/obj/structure/window/attack_hand(mob/user) + if(!can_be_reached(user)) + return + if(user.a_intent == INTENT_HARM) + user.changeNext_move(CLICK_CD_MELEE) + playsound(src, 'sound/effects/glassknock.ogg', 80, 1) + user.visible_message("[user] bangs against [src]!", \ + "You bang against [src]!", \ + "You hear a banging sound.") + add_fingerprint(user) + else + user.changeNext_move(CLICK_CD_MELEE) + playsound(src, 'sound/effects/glassknock.ogg', 80, 1) + user.visible_message("[user] knocks on [src].", \ + "You knock on [src].", \ + "You hear a knocking sound.") + add_fingerprint(user) + +/obj/structure/window/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) //used by attack_alien, attack_animal, and attack_slime + if(!can_be_reached(user)) + return + ..() + +/obj/structure/window/attackby(obj/item/I, mob/living/user, params) + if(!can_be_reached(user)) + return 1 //skip the afterattack + + add_fingerprint(user) + if(istype(I, /obj/item/grab) && get_dist(src, user) < 2) + var/obj/item/grab/G = I + if(isliving(G.affecting)) + var/mob/living/M = G.affecting + var/state = G.state + qdel(I) //gotta delete it here because if window breaks, it won't get deleted + switch(state) + if(1) + M.visible_message("[user] slams [M] against \the [src]!") + M.apply_damage(7) + take_damage(10) + if(2) + M.visible_message("[user] bashes [M] against \the [src]!") + if(prob(50)) + M.Weaken(1) + M.apply_damage(10) + take_damage(25) + if(3) + M.visible_message("[user] crushes [M] against \the [src]!") + M.Weaken(5) + M.apply_damage(20) + take_damage(50) + if(4) + visible_message("[user] smashes [M] against \the [src]!") + M.Weaken(5) + M.apply_damage(30) + take_damage(75) + return + return ..() + + +/obj/structure/window/crowbar_act(mob/user, obj/item/I) + if(!reinf) + return + if(state != WINDOW_OUT_OF_FRAME && state != WINDOW_IN_FRAME) + return + if(flags & NODECONSTRUCT) + return + . = TRUE + 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))) + 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.") + +/obj/structure/window/screwdriver_act(mob/user, obj/item/I) + if(flags & NODECONSTRUCT) + return + . = TRUE + if(!can_be_reached(user)) + return + 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))) + 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))) + 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))) + return + anchored = !anchored + air_update_turf(TRUE) + update_nearby_icons() + to_chat(user, "You [anchored ? "fasten the window to":"unfasten the window from"] the floor.") + +/obj/structure/window/wrench_act(mob/user, obj/item/I) + if(flags & NODECONSTRUCT) + return + if(anchored) + return + . = TRUE + 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))) + return + var/obj/item/stack/sheet/G = new glass_type(user.loc, glass_amount) + G.add_fingerprint(user) + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You successfully disassemble [src].") + qdel(src) + +/obj/structure/window/welder_act(mob/user, obj/item/I) + if(user.a_intent != INTENT_HELP) + return + . = TRUE + if(!can_be_reached(user)) + return + if(obj_integrity >= max_integrity) + to_chat(user, "[src] is already in good condition!") + return + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_REPAIR_MESSAGE + if(I.use_tool(src, user, 40, volume = I.tool_volume)) + obj_integrity = max_integrity + WELDER_REPAIR_SUCCESS_MESSAGE + +/obj/structure/window/proc/check_state(checked_state) + if(state == checked_state) + return TRUE + +/obj/structure/window/proc/check_anchored(checked_anchored) + if(anchored == checked_anchored) + return TRUE + +/obj/structure/window/proc/check_state_and_anchored(checked_state, checked_anchored) + return check_state(checked_state) && check_anchored(checked_anchored) + +/obj/structure/window/mech_melee_attack(obj/mecha/M) + if(!can_be_reached()) + return + ..() + +/obj/structure/window/proc/can_be_reached(mob/user) + if(!fulltile) + if(get_dir(user, src) & dir) + for(var/obj/O in loc) + if(!O.CanPass(user, user.loc, 1)) + return 0 + return 1 + +/obj/structure/window/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + . = ..() + if(.) //received damage + update_nearby_icons() + +/obj/structure/window/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(src, hitsound, 75, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(src, 'sound/items/Welder.ogg', 100, TRUE) + +/obj/structure/window/deconstruct(disassembled = TRUE) + if(QDELETED(src)) + return + if(!disassembled) + playsound(src, breaksound, 70, 1) + if(!(flags & NODECONSTRUCT)) + for(var/i in debris) + var/obj/item/I = i + I.forceMove(loc) + transfer_fingerprints_to(I) + qdel(src) + update_nearby_icons() + +/obj/structure/window/verb/rotate() + set name = "Rotate Window Counter-Clockwise" + set category = "Object" + set src in oview(1) + + if(usr.incapacitated()) + return + + if(anchored) + to_chat(usr, "[src] cannot be rotated while it is fastened to the floor!") + return FALSE + + var/target_dir = turn(dir, 90) + if(!valid_window_location(loc, target_dir)) + to_chat(usr, "[src] cannot be rotated in that direction!") + return FALSE + + setDir(target_dir) + air_update_turf(1) + ini_dir = dir + add_fingerprint(usr) + return TRUE + +/obj/structure/window/verb/revrotate() + set name = "Rotate Window Clockwise" + set category = "Object" + set src in oview(1) + + if(usr.incapacitated()) + return + + if(anchored) + to_chat(usr, "[src] cannot be rotated while it is fastened to the floor!") + return FALSE + + var/target_dir = turn(dir, 270) + + if(!valid_window_location(loc, target_dir)) + to_chat(usr, "[src] cannot be rotated in that direction!") + return FALSE + + setDir(target_dir) + ini_dir = dir + add_fingerprint(usr) + return TRUE + +/obj/structure/window/AltClick(mob/user) + + if(user.incapacitated()) + to_chat(user, "You can't do that right now!") + return + + if(!Adjacent(user)) + to_chat(user, "Move closer to the window!") + return + + if(anchored) + to_chat(user, "[src] cannot be rotated while it is fastened to the floor!") + return FALSE + + var/target_dir = turn(dir, 270) + + if(!valid_window_location(loc, target_dir)) + target_dir = turn(dir, 90) + if(!valid_window_location(loc, target_dir)) + to_chat(user, "There is no room to rotate the [src]") + return FALSE + + setDir(target_dir) + ini_dir = dir + add_fingerprint(user) + return TRUE + +/obj/structure/window/Destroy() + density = FALSE + air_update_turf(1) + update_nearby_icons() + return ..() + +/obj/structure/window/Move() + var/turf/T = loc + . = ..() + setDir(ini_dir) + move_update_air(T) + +/obj/structure/window/CanAtmosPass(turf/T) + if(!anchored || !density) + return TRUE + return !(FULLTILE_WINDOW_DIR == dir || dir == get_dir(loc, T)) + +//This proc is used to update the icons of nearby windows. +/obj/structure/window/proc/update_nearby_icons() + update_icon() + if(smooth) + queue_smooth_neighbors(src) + +/obj/structure/window/update_icon() + if(!QDELETED(src)) + if(!fulltile) + return + var/ratio = obj_integrity / max_integrity + ratio = CEILING(ratio*4, 1) * 25 + if(smooth) + queue_smooth(src) + overlays -= crack_overlay + if(ratio > 75) + return + crack_overlay = image('icons/obj/structures.dmi',"damage[ratio]",-(layer+0.1)) + overlays += crack_overlay + +/obj/structure/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > (T0C + heat_resistance)) + take_damage(round(exposed_volume / 100), BURN, 0, 0) + +/obj/structure/window/GetExplosionBlock() + return reinf && fulltile ? real_explosion_block : 0 + +/obj/structure/window/basic + desc = "It looks thin and flimsy. A few knocks with... anything, really should shatter it." + +/obj/structure/window/reinforced + name = "reinforced window" + desc = "It looks rather strong. Might take a few good hits to shatter it." + icon_state = "rwindow" + reinf = TRUE + cancolor = TRUE + heat_resistance = 1600 + armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) + max_integrity = 50 + explosion_block = 1 + glass_type = /obj/item/stack/sheet/rglass + +/obj/structure/window/reinforced/tinted + name = "tinted window" + desc = "It looks rather strong and opaque. Might take a few good hits to shatter it." + icon_state = "twindow" + opacity = 1 + +/obj/structure/window/reinforced/tinted/frosted + name = "frosted window" + desc = "It looks rather strong and frosted over. Looks like it might take a few less hits then a normal reinforced window." + icon_state = "fwindow" + max_integrity = 30 + +/obj/structure/window/reinforced/polarized + name = "electrochromic window" + desc = "Adjusts its tint with voltage. Might take a few good hits to shatter it." + var/id + +/obj/structure/window/reinforced/polarized/proc/toggle() + if(opacity) + animate(src, color="#FFFFFF", time=5) + set_opacity(0) + else + animate(src, color="#222222", time=5) + set_opacity(1) + +/obj/machinery/button/windowtint + name = "window tint control" + icon = 'icons/obj/power.dmi' + icon_state = "light0" + desc = "A remote control switch for polarized windows." + var/range = 7 + var/id = 0 + var/active = 0 + +/obj/machinery/button/windowtint/attack_hand(mob/user) + if(..()) + return 1 + + toggle_tint() + +/obj/machinery/button/windowtint/proc/toggle_tint() + use_power(5) + + active = !active + update_icon() + + for(var/obj/structure/window/reinforced/polarized/W in range(src,range)) + if(W.id == src.id || !W.id) + spawn(0) + W.toggle() + return + +/obj/machinery/button/windowtint/power_change() + ..() + if(active && !powered(power_channel)) + toggle_tint() + +/obj/machinery/button/windowtint/update_icon() + icon_state = "light[active]" + +/obj/structure/window/plasmabasic + name = "plasma window" + desc = "A window made out of a plasma-silicate alloy. It looks insanely tough to break and burn through." + icon_state = "plasmawindow" + shardtype = /obj/item/shard/plasma + glass_type = /obj/item/stack/sheet/plasmaglass + heat_resistance = 32000 + max_integrity = 150 + explosion_block = 1 + armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100) + +/obj/structure/window/plasmabasic/BlockSuperconductivity() + return 1 + +/obj/structure/window/plasmareinforced + name = "reinforced plasma window" + desc = "A plasma-glass alloy window, with rods supporting it. It looks hopelessly tough to break. It also looks completely fireproof, considering how basic plasma windows are insanely fireproof." + icon_state = "plasmarwindow" + shardtype = /obj/item/shard/plasma + glass_type = /obj/item/stack/sheet/plasmarglass + reinf = TRUE + max_integrity = 500 + explosion_block = 2 + armor = list("melee" = 85, "bullet" = 20, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100) + damage_deflection = 21 + +/obj/structure/window/plasmareinforced/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + return + +/obj/structure/window/plasmareinforced/BlockSuperconductivity() + return 1 //okay this SHOULD MAKE THE TOXINS CHAMBER WORK + +/obj/structure/window/full + glass_amount = 2 + dir = FULLTILE_WINDOW_DIR + level = 3 + fulltile = TRUE + flags = PREVENT_CLICK_UNDER + +/obj/structure/window/full/basic + desc = "It looks thin and flimsy. A few knocks with... anything, really should shatter it." + icon = 'icons/obj/smooth_structures/window.dmi' + icon_state = "window" + max_integrity = 50 + smooth = SMOOTH_TRUE + cancolor = TRUE + canSmoothWith = list(/obj/structure/window/full/basic, /obj/structure/window/full/reinforced, /obj/structure/window/full/reinforced/tinted, /obj/structure/window/full/plasmabasic, /obj/structure/window/full/plasmareinforced) + +/obj/structure/window/full/plasmabasic + name = "plasma window" + desc = "A plasma-glass alloy window. It looks insanely tough to break. It appears it's also insanely tough to burn through." + icon = 'icons/obj/smooth_structures/plasma_window.dmi' + icon_state = "plasmawindow" + shardtype = /obj/item/shard/plasma + glass_type = /obj/item/stack/sheet/plasmaglass + heat_resistance = 32000 + max_integrity = 300 + smooth = SMOOTH_TRUE + canSmoothWith = list(/obj/structure/window/full/basic, /obj/structure/window/full/reinforced, /obj/structure/window/full/reinforced/tinted, /obj/structure/window/full/plasmabasic, /obj/structure/window/full/plasmareinforced) + explosion_block = 1 + armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100) + +/obj/structure/window/full/plasmareinforced + name = "reinforced plasma window" + desc = "A plasma-glass alloy window, with rods supporting it. It looks hopelessly tough to break. It also looks completely fireproof, considering how basic plasma windows are insanely fireproof." + icon = 'icons/obj/smooth_structures/rplasma_window.dmi' + icon_state = "rplasmawindow" + shardtype = /obj/item/shard/plasma + glass_type = /obj/item/stack/sheet/plasmarglass + smooth = SMOOTH_TRUE + reinf = TRUE + max_integrity = 1000 + explosion_block = 2 + armor = list("melee" = 85, "bullet" = 20, "laser" = 0, "energy" = 0, "bomb" = 60, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100) + +/obj/structure/window/full/plasmareinforced/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + return + +/obj/structure/window/full/reinforced + name = "reinforced window" + desc = "It looks rather strong. Might take a few good hits to shatter it." + icon = 'icons/obj/smooth_structures/reinforced_window.dmi' + icon_state = "r_window" + smooth = SMOOTH_TRUE + canSmoothWith = list(/obj/structure/window/full/basic, /obj/structure/window/full/reinforced, /obj/structure/window/full/reinforced/tinted, /obj/structure/window/full/plasmabasic, /obj/structure/window/full/plasmareinforced) + max_integrity = 100 + reinf = TRUE + heat_resistance = 1600 + armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) + explosion_block = 1 + glass_type = /obj/item/stack/sheet/rglass + cancolor = TRUE + +/obj/structure/window/full/reinforced/tinted + name = "tinted window" + desc = "It looks rather strong and opaque. Might take a few good hits to shatter it." + icon = 'icons/obj/smooth_structures/tinted_window.dmi' + icon_state = "tinted_window" + opacity = 1 + +obj/structure/window/full/reinforced/ice + icon = 'icons/obj/smooth_structures/rice_window.dmi' + icon_state = "ice_window" + max_integrity = 150 + cancolor = FALSE + +/obj/structure/window/full/shuttle + name = "shuttle window" + desc = "A reinforced, air-locked pod window." + icon = 'icons/obj/smooth_structures/shuttle_window.dmi' + icon_state = "shuttle_window" + max_integrity = 100 + reinf = TRUE + heat_resistance = 1600 + explosion_block = 3 + armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) + smooth = SMOOTH_TRUE + canSmoothWith = null + glass_type = /obj/item/stack/sheet/titaniumglass + +/obj/structure/window/full/shuttle/narsie_act() + color = "#3C3434" + +/obj/structure/window/full/shuttle/tinted + opacity = TRUE + +/obj/structure/window/plastitanium + name = "plastitanium window" + desc = "An evil looking window of plasma and titanium." + icon = 'icons/obj/smooth_structures/plastitanium_window.dmi' + icon_state = "plastitanium_window" + dir = FULLTILE_WINDOW_DIR + max_integrity = 100 + fulltile = TRUE + flags = PREVENT_CLICK_UNDER + reinf = TRUE + heat_resistance = 1600 + armor = list("melee" = 50, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) + smooth = SMOOTH_TRUE + canSmoothWith = null + explosion_block = 3 + level = 3 + glass_type = /obj/item/stack/sheet/plastitaniumglass + glass_amount = 2 + +/obj/structure/window/reinforced/clockwork + name = "brass window" + desc = "A paper-thin pane of translucent yet reinforced brass." + icon = 'icons/obj/smooth_structures/clockwork_window.dmi' + icon_state = "clockwork_window_single" + resistance_flags = FIRE_PROOF | ACID_PROOF + max_integrity = 80 + armor = list("melee" = 60, "bullet" = 25, "laser" = 0, "energy" = 0, "bomb" = 25, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 100) + explosion_block = 2 //fancy AND hard to destroy. the most useful combination. + glass_type = /obj/item/stack/tile/brass + reinf = FALSE + cancolor = FALSE + var/made_glow = FALSE + +/obj/structure/window/reinforced/clockwork/New(loc, direct) + if(fulltile) + made_glow = TRUE + ..() + QDEL_LIST(debris) + if(fulltile) + new /obj/effect/temp_visual/ratvar/window(get_turf(src)) + debris += new/obj/item/stack/tile/brass(src, 2) + else + debris += new/obj/item/stack/tile/brass(src, 1) + +/obj/structure/window/reinforced/clockwork/setDir(direct) + if(!made_glow) + var/obj/effect/E = new /obj/effect/temp_visual/ratvar/window/single(get_turf(src)) + E.setDir(direct) + made_glow = TRUE + ..() + +/obj/structure/window/reinforced/clockwork/ratvar_act() + obj_integrity = max_integrity + update_icon() + +/obj/structure/window/reinforced/clockwork/narsie_act() + take_damage(rand(25, 75), BRUTE) + if(src) + var/previouscolor = color + color = "#960000" + animate(src, color = previouscolor, time = 8) + +/obj/structure/window/reinforced/clockwork/fulltile + icon_state = "clockwork_window" + smooth = SMOOTH_TRUE + canSmoothWith = null + fulltile = TRUE + flags = PREVENT_CLICK_UNDER + dir = FULLTILE_WINDOW_DIR + max_integrity = 120 + level = 3 + glass_amount = 2 diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm index 3e923f20790..3c1ba0b8fbc 100644 --- a/code/game/shuttle_engines.dm +++ b/code/game/shuttle_engines.dm @@ -1,60 +1,60 @@ -/obj/structure/shuttle - name = "shuttle" - icon = 'icons/turf/shuttle.dmi' - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - max_integrity = 500 - armor = list(melee = 100, bullet = 10, laser = 10, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70) //default + ignores melee - -/obj/structure/shuttle/shuttleRotate(rotation) - ..() - var/matrix/M = transform - M.Turn(rotation) - transform = M - -/obj/structure/shuttle/window - name = "shuttle window" - icon = 'icons/obj/podwindows.dmi' - icon_state = "1" - density = 1 - opacity = 0 - anchored = 1 - - CanPass(atom/movable/mover, turf/target, height) - if(!height) return 0 - else return ..() - - CanAtmosPass(turf/T) - return !density - -/obj/structure/shuttle/engine - name = "engine" - density = 1 - anchored = 1.0 - -/obj/structure/shuttle/engine/heater - name = "heater" - icon_state = "heater" - -/obj/structure/shuttle/engine/platform - name = "platform" - icon_state = "platform" - -/obj/structure/shuttle/engine/propulsion - name = "propulsion" - icon_state = "propulsion" - opacity = 1 - -/obj/structure/shuttle/engine/propulsion/burst - name = "burst" - -/obj/structure/shuttle/engine/propulsion/burst/left - name = "left" - icon_state = "burst_l" - -/obj/structure/shuttle/engine/propulsion/burst/right - name = "right" - icon_state = "burst_r" - -/obj/structure/shuttle/engine/router - name = "router" - icon_state = "router" +/obj/structure/shuttle + name = "shuttle" + icon = 'icons/turf/shuttle.dmi' + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + max_integrity = 500 + armor = list(melee = 100, bullet = 10, laser = 10, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70) //default + ignores melee + +/obj/structure/shuttle/shuttleRotate(rotation) + ..() + var/matrix/M = transform + M.Turn(rotation) + transform = M + +/obj/structure/shuttle/window + name = "shuttle window" + icon = 'icons/obj/podwindows.dmi' + icon_state = "1" + density = 1 + opacity = 0 + anchored = 1 + + CanPass(atom/movable/mover, turf/target, height) + if(!height) return 0 + else return ..() + + CanAtmosPass(turf/T) + return !density + +/obj/structure/shuttle/engine + name = "engine" + density = 1 + anchored = 1.0 + +/obj/structure/shuttle/engine/heater + name = "heater" + icon_state = "heater" + +/obj/structure/shuttle/engine/platform + name = "platform" + icon_state = "platform" + +/obj/structure/shuttle/engine/propulsion + name = "propulsion" + icon_state = "propulsion" + opacity = 1 + +/obj/structure/shuttle/engine/propulsion/burst + name = "burst" + +/obj/structure/shuttle/engine/propulsion/burst/left + name = "left" + icon_state = "burst_l" + +/obj/structure/shuttle/engine/propulsion/burst/right + name = "right" + icon_state = "burst_r" + +/obj/structure/shuttle/engine/router + name = "router" + icon_state = "router" diff --git a/code/game/sound.dm b/code/game/sound.dm index d27239ae4e7..870ac5e6966 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -160,4 +160,4 @@ soundin = pick('sound/effects/bone_break_1.ogg', 'sound/effects/bone_break_2.ogg', 'sound/effects/bone_break_3.ogg', 'sound/effects/bone_break_4.ogg', 'sound/effects/bone_break_5.ogg', 'sound/effects/bone_break_6.ogg') if("honkbot_e") soundin = pick('sound/items/bikehorn.ogg', 'sound/items/AirHorn2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/items/AirHorn.ogg', 'sound/items/WEEOO1.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/bcreep.ogg','sound/magic/Fireball.ogg' ,'sound/effects/pray.ogg', 'sound/voice/hiss1.ogg','sound/machines/buzz-sigh.ogg', 'sound/machines/ping.ogg', 'sound/weapons/flashbang.ogg', 'sound/weapons/bladeslice.ogg') - return soundin \ No newline at end of file + return soundin diff --git a/code/game/turfs/simulated.dm b/code/game/turfs/simulated.dm index 62993753cec..6c10921e6bd 100644 --- a/code/game/turfs/simulated.dm +++ b/code/game/turfs/simulated.dm @@ -1,109 +1,109 @@ -/turf/simulated - name = "station" - var/wet = 0 - var/image/wet_overlay = null - - var/thermite = 0 - oxygen = MOLES_O2STANDARD - nitrogen = MOLES_N2STANDARD - var/to_be_destroyed = 0 //Used for fire, if a melting temperature was reached, it will be destroyed - var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to - var/dirtoverlay = null - -/turf/simulated/New() - ..() - levelupdate() - visibilityChanged() - -/turf/simulated/proc/break_tile() - return - -/turf/simulated/proc/burn_tile() - return - -/turf/simulated/water_act(volume, temperature, source) - . = ..() - - if(volume >= 3) - MakeSlippery() - - var/hotspot = (locate(/obj/effect/hotspot) in src) - if(hotspot) - var/datum/gas_mixture/lowertemp = remove_air(air.total_moles()) - lowertemp.temperature = max(min(lowertemp.temperature-2000,lowertemp.temperature / 2), 0) - lowertemp.react() - assume_air(lowertemp) - qdel(hotspot) - -/turf/simulated/proc/MakeSlippery(wet_setting = TURF_WET_WATER, infinite = FALSE) // 1 = Water, 2 = Lube, 3 = Ice, 4 = Permafrost - if(wet >= wet_setting) - return - wet = wet_setting - if(wet_setting != TURF_DRY) - if(wet_overlay) - overlays -= wet_overlay - wet_overlay = null - var/turf/simulated/floor/F = src - if(istype(F)) - if(wet_setting >= TURF_WET_ICE) - wet_overlay = image('icons/effects/water.dmi', src, "ice_floor") - else - wet_overlay = image('icons/effects/water.dmi', src, "wet_floor_static") - else - if(wet_setting >= TURF_WET_ICE) - wet_overlay = image('icons/effects/water.dmi', src, "ice_floor") - else - wet_overlay = image('icons/effects/water.dmi', src, "wet_static") - overlays += wet_overlay - if(!infinite) - spawn(rand(790, 820)) // Purely so for visual effect - if(!istype(src, /turf/simulated)) //Because turfs don't get deleted, they change, adapt, transform, evolve and deform. they are one and they are all. - return - MakeDry(wet_setting) - -/turf/simulated/proc/MakeDry(wet_setting = TURF_WET_WATER) - if(wet > wet_setting) - return - wet = TURF_DRY - if(wet_overlay) - overlays -= wet_overlay - -/turf/simulated/Entered(atom/A, atom/OL, ignoreRest = 0) - ..() - if(!ignoreRest) - if(ishuman(A)) - var/mob/living/carbon/human/M = A - if(M.lying) - return 1 - - if(M.flying) - return ..() - - switch(src.wet) - if(TURF_WET_WATER) - if(!(M.slip("the wet floor", 4, 2, tilesSlipped = 0, walkSafely = 1))) - M.inertia_dir = 0 - return - - if(TURF_WET_LUBE) //lube - M.slip("the floor", 0, 5, tilesSlipped = 3, walkSafely = 0, slipAny = 1) - - - if(TURF_WET_ICE) // Ice - if(M.slip("the icy floor", 4, 2, tilesSlipped = 0, walkSafely = 0)) - M.inertia_dir = 0 - if(prob(5)) - var/obj/item/organ/external/affected = M.get_organ("head") - if(affected) - M.apply_damage(5, BRUTE, "head") - M.visible_message("[M] hits their head on the ice!") - playsound(src, 'sound/weapons/genhit1.ogg', 50, 1) - - if(TURF_WET_PERMAFROST) // Permafrost - M.slip("the frosted floor", 0, 5, tilesSlipped = 1, walkSafely = 0, slipAny = 1) - -/turf/simulated/ChangeTurf(path, defer_change = FALSE, keep_icon = TRUE, ignore_air = FALSE) - . = ..() - queue_smooth_neighbors(src) - -/turf/simulated/proc/is_shielded() +/turf/simulated + name = "station" + var/wet = 0 + var/image/wet_overlay = null + + var/thermite = 0 + oxygen = MOLES_O2STANDARD + nitrogen = MOLES_N2STANDARD + var/to_be_destroyed = 0 //Used for fire, if a melting temperature was reached, it will be destroyed + var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to + var/dirtoverlay = null + +/turf/simulated/New() + ..() + levelupdate() + visibilityChanged() + +/turf/simulated/proc/break_tile() + return + +/turf/simulated/proc/burn_tile() + return + +/turf/simulated/water_act(volume, temperature, source) + . = ..() + + if(volume >= 3) + MakeSlippery() + + var/hotspot = (locate(/obj/effect/hotspot) in src) + if(hotspot) + var/datum/gas_mixture/lowertemp = remove_air(air.total_moles()) + lowertemp.temperature = max(min(lowertemp.temperature-2000,lowertemp.temperature / 2), 0) + lowertemp.react() + assume_air(lowertemp) + qdel(hotspot) + +/turf/simulated/proc/MakeSlippery(wet_setting = TURF_WET_WATER, infinite = FALSE) // 1 = Water, 2 = Lube, 3 = Ice, 4 = Permafrost + if(wet >= wet_setting) + return + wet = wet_setting + if(wet_setting != TURF_DRY) + if(wet_overlay) + overlays -= wet_overlay + wet_overlay = null + var/turf/simulated/floor/F = src + if(istype(F)) + if(wet_setting >= TURF_WET_ICE) + wet_overlay = image('icons/effects/water.dmi', src, "ice_floor") + else + wet_overlay = image('icons/effects/water.dmi', src, "wet_floor_static") + else + if(wet_setting >= TURF_WET_ICE) + wet_overlay = image('icons/effects/water.dmi', src, "ice_floor") + else + wet_overlay = image('icons/effects/water.dmi', src, "wet_static") + overlays += wet_overlay + if(!infinite) + spawn(rand(790, 820)) // Purely so for visual effect + if(!istype(src, /turf/simulated)) //Because turfs don't get deleted, they change, adapt, transform, evolve and deform. they are one and they are all. + return + MakeDry(wet_setting) + +/turf/simulated/proc/MakeDry(wet_setting = TURF_WET_WATER) + if(wet > wet_setting) + return + wet = TURF_DRY + if(wet_overlay) + overlays -= wet_overlay + +/turf/simulated/Entered(atom/A, atom/OL, ignoreRest = 0) + ..() + if(!ignoreRest) + if(ishuman(A)) + var/mob/living/carbon/human/M = A + if(M.lying) + return 1 + + if(M.flying) + return ..() + + switch(src.wet) + if(TURF_WET_WATER) + if(!(M.slip("the wet floor", 4, 2, tilesSlipped = 0, walkSafely = 1))) + M.inertia_dir = 0 + return + + if(TURF_WET_LUBE) //lube + M.slip("the floor", 0, 5, tilesSlipped = 3, walkSafely = 0, slipAny = 1) + + + if(TURF_WET_ICE) // Ice + if(M.slip("the icy floor", 4, 2, tilesSlipped = 0, walkSafely = 0)) + M.inertia_dir = 0 + if(prob(5)) + var/obj/item/organ/external/affected = M.get_organ("head") + if(affected) + M.apply_damage(5, BRUTE, "head") + M.visible_message("[M] hits their head on the ice!") + playsound(src, 'sound/weapons/genhit1.ogg', 50, 1) + + if(TURF_WET_PERMAFROST) // Permafrost + M.slip("the frosted floor", 0, 5, tilesSlipped = 1, walkSafely = 0, slipAny = 1) + +/turf/simulated/ChangeTurf(path, defer_change = FALSE, keep_icon = TRUE, ignore_air = FALSE) + . = ..() + queue_smooth_neighbors(src) + +/turf/simulated/proc/is_shielded() diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm index cfa5f43858e..72513b2e8b3 100644 --- a/code/game/turfs/simulated/floor.dm +++ b/code/game/turfs/simulated/floor.dm @@ -1,253 +1,253 @@ -//This is so damaged or burnt tiles or platings don't get remembered as the default tile -var/list/icons_to_ignore_at_floor_init = list("damaged1","damaged2","damaged3","damaged4", - "damaged5","panelscorched","floorscorched1","floorscorched2","platingdmg1","platingdmg2", - "platingdmg3","plating","light_on","light_on_flicker1","light_on_flicker2", - "warnplate", "warnplatecorner","metalfoam", "ironfoam", - "light_on_clicker3","light_on_clicker4","light_on_clicker5","light_broken", - "light_on_broken","light_off","wall_thermite","grass1","grass2","grass3","grass4", - "asteroid","asteroid_dug", - "asteroid0","asteroid1","asteroid2","asteroid3","asteroid4", - "asteroid5","asteroid6","asteroid7","asteroid8","asteroid9","asteroid10","asteroid11","asteroid12", - "oldburning","light-on-r","light-on-y","light-on-g","light-on-b", "wood", "wood-broken", "carpet", - "carpetcorner", "carpetside", "carpet", "ironsand1", "ironsand2", "ironsand3", "ironsand4", "ironsand5", - "ironsand6", "ironsand7", "ironsand8", "ironsand9", "ironsand10", "ironsand11", - "ironsand12", "ironsand13", "ironsand14", "ironsand15") - -/turf/simulated/floor - name = "floor" - icon = 'icons/turf/floors.dmi' - icon_state = "dont_use_this_floor" - plane = FLOOR_PLANE - var/icon_regular_floor = "floor" //used to remember what icon the tile should have by default - var/icon_plating = "plating" - thermal_conductivity = 0.040 - heat_capacity = 10000 - var/lava = 0 - var/broken = 0 - var/burnt = 0 - var/current_overlay = null - var/floor_tile = null //tile that this floor drops - var/obj/item/stack/tile/builtin_tile = null //needed for performance reasons when the singularity rips off floor tiles - var/list/broken_states = list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5") - var/list/burnt_states = list("floorscorched1", "floorscorched2") - var/list/prying_tool_list = list(TOOL_CROWBAR) //What tool/s can we use to pry up the tile? - -/turf/simulated/floor/New() - ..() - if(icon_state in icons_to_ignore_at_floor_init) //so damaged/burned tiles or plating icons aren't saved as the default - icon_regular_floor = "floor" - else - icon_regular_floor = icon_state - if(floor_tile) - builtin_tile = new floor_tile - -/turf/simulated/floor/Destroy() - QDEL_NULL(builtin_tile) - return ..() - - -//turf/simulated/floor/CanPass(atom/movable/mover, turf/target, height=0) -// if((istype(mover, /obj/machinery/vehicle) && !(src.burnt))) -// if(!( locate(/obj/machinery/mass_driver, src) )) -// return 0 -// return ..() - -/turf/simulated/floor/ex_act(severity) - if(is_shielded()) - return - switch(severity) - if(1.0) - ChangeTurf(baseturf) - if(2.0) - switch(pick(1,2;75,3)) - if(1) - spawn(0) - ReplaceWithLattice() - if(prob(33)) new /obj/item/stack/sheet/metal(src) - if(2) - ChangeTurf(baseturf) - if(3) - if(prob(80)) - break_tile_to_plating() - else - break_tile() - hotspot_expose(1000,CELL_VOLUME) - if(prob(33)) new /obj/item/stack/sheet/metal(src) - if(3.0) - if(prob(50)) - break_tile() - hotspot_expose(1000,CELL_VOLUME) - return - -/turf/simulated/floor/burn_down() - ex_act(2) - -/turf/simulated/floor/is_shielded() - for(var/obj/structure/A in contents) - if(A.level == 3) - return 1 - -/turf/simulated/floor/blob_act(obj/structure/blob/B) - return - -/turf/simulated/floor/proc/update_icon() - update_visuals() - overlays -= current_overlay - if(current_overlay) - overlays.Add(current_overlay) - return 1 - -/turf/simulated/floor/proc/break_tile_to_plating() - var/turf/simulated/floor/plating/T = make_plating() - T.break_tile() - -/turf/simulated/floor/break_tile() - if(broken) - return - current_overlay = pick(broken_states) - broken = TRUE - update_icon() - -/turf/simulated/floor/burn_tile() - if(burnt) - return - current_overlay = pick(burnt_states) - burnt = TRUE - update_icon() - -/turf/simulated/floor/proc/make_plating() - return ChangeTurf(/turf/simulated/floor/plating) - -/turf/simulated/floor/ChangeTurf(turf/simulated/floor/T, defer_change = FALSE, keep_icon = TRUE, ignore_air = FALSE) - if(!istype(src, /turf/simulated/floor)) - return ..() //fucking turfs switch the fucking src of the fucking running procs - if(!ispath(T, /turf/simulated/floor)) - return ..() - - var/old_icon = icon_regular_floor - var/old_plating = icon_plating - var/old_dir = dir - - var/turf/simulated/floor/W = ..() - - if(keep_icon) - W.icon_regular_floor = old_icon - W.icon_plating = old_plating - W.dir = old_dir - - W.update_icon() - return W - -/turf/simulated/floor/attackby(obj/item/C as obj, mob/user as mob, params) - if(!C || !user) - return TRUE - - if(..()) - return TRUE - - if(intact && istype(C, /obj/item/stack/tile)) - try_replace_tile(C, user, params) - - if(istype(C, /obj/item/pipe)) - var/obj/item/pipe/P = C - if(P.pipe_type != -1) // ANY PIPE - user.visible_message( \ - "[user] starts sliding [P] along \the [src].", \ - "You slide [P] along \the [src].", \ - "You hear the scrape of metal against something.") - user.drop_item() - - if(P.is_bent_pipe()) // bent pipe rotation fix see construction.dm - P.dir = 5 - if(user.dir == 1) - P.dir = 6 - else if(user.dir == 2) - P.dir = 9 - else if(user.dir == 4) - P.dir = 10 - else - P.setDir(user.dir) - - P.x = src.x - P.y = src.y - P.z = src.z - P.forceMove(src) - return TRUE - return FALSE - -/turf/simulated/floor/crowbar_act(mob/user, obj/item/I) - if(!intact) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - pry_tile(I, user, TRUE) - -/turf/simulated/floor/proc/try_replace_tile(obj/item/stack/tile/T, mob/user, params) - if(T.turf_type == type) - return - var/obj/item/thing = user.get_inactive_hand() - if(!prying_tool_list.Find(thing.tool_behaviour)) - return - var/turf/simulated/floor/plating/P = pry_tile(thing, user, TRUE) - if(!istype(P)) - return - P.attackby(T, user, params) - -/turf/simulated/floor/proc/pry_tile(obj/item/C, mob/user, silent = FALSE) - if(!silent) - playsound(src, C.usesound, 80, 1) - return remove_tile(user, silent) - -/turf/simulated/floor/proc/remove_tile(mob/user, silent = FALSE, make_tile = TRUE) - if(broken || burnt) - broken = 0 - burnt = 0 - current_overlay = null - if(user && !silent) - to_chat(user, "You remove the broken plating.") - else - if(user && !silent) - to_chat(user, "You remove the floor tile.") - if(builtin_tile && make_tile) - builtin_tile.forceMove(src) - builtin_tile = null - return make_plating() - -/turf/simulated/floor/singularity_pull(S, current_size) - ..() - if(current_size == STAGE_THREE) - if(prob(30)) - if(builtin_tile) - builtin_tile.loc = src - builtin_tile = null - make_plating() - else if(current_size == STAGE_FOUR) - if(prob(50)) - if(builtin_tile) - builtin_tile.loc = src - builtin_tile = null - make_plating() - else if(current_size >= STAGE_FIVE) - if(builtin_tile) - if(prob(70)) - builtin_tile.loc = src - builtin_tile = null - make_plating() - else if(prob(50)) - ReplaceWithLattice() - -/turf/simulated/floor/narsie_act() - if(prob(20)) - ChangeTurf(/turf/simulated/floor/engine/cult) - -/turf/simulated/floor/ratvar_act(force, ignore_mobs) - . = ..() - if(.) - ChangeTurf(/turf/simulated/floor/clockwork) - -/turf/simulated/floor/acid_melt() - ChangeTurf(baseturf) - -/turf/simulated/floor/can_have_cabling() - return !burnt && !broken +//This is so damaged or burnt tiles or platings don't get remembered as the default tile +var/list/icons_to_ignore_at_floor_init = list("damaged1","damaged2","damaged3","damaged4", + "damaged5","panelscorched","floorscorched1","floorscorched2","platingdmg1","platingdmg2", + "platingdmg3","plating","light_on","light_on_flicker1","light_on_flicker2", + "warnplate", "warnplatecorner","metalfoam", "ironfoam", + "light_on_clicker3","light_on_clicker4","light_on_clicker5","light_broken", + "light_on_broken","light_off","wall_thermite","grass1","grass2","grass3","grass4", + "asteroid","asteroid_dug", + "asteroid0","asteroid1","asteroid2","asteroid3","asteroid4", + "asteroid5","asteroid6","asteroid7","asteroid8","asteroid9","asteroid10","asteroid11","asteroid12", + "oldburning","light-on-r","light-on-y","light-on-g","light-on-b", "wood", "wood-broken", "carpet", + "carpetcorner", "carpetside", "carpet", "ironsand1", "ironsand2", "ironsand3", "ironsand4", "ironsand5", + "ironsand6", "ironsand7", "ironsand8", "ironsand9", "ironsand10", "ironsand11", + "ironsand12", "ironsand13", "ironsand14", "ironsand15") + +/turf/simulated/floor + name = "floor" + icon = 'icons/turf/floors.dmi' + icon_state = "dont_use_this_floor" + plane = FLOOR_PLANE + var/icon_regular_floor = "floor" //used to remember what icon the tile should have by default + var/icon_plating = "plating" + thermal_conductivity = 0.040 + heat_capacity = 10000 + var/lava = 0 + var/broken = 0 + var/burnt = 0 + var/current_overlay = null + var/floor_tile = null //tile that this floor drops + var/obj/item/stack/tile/builtin_tile = null //needed for performance reasons when the singularity rips off floor tiles + var/list/broken_states = list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5") + var/list/burnt_states = list("floorscorched1", "floorscorched2") + var/list/prying_tool_list = list(TOOL_CROWBAR) //What tool/s can we use to pry up the tile? + +/turf/simulated/floor/New() + ..() + if(icon_state in icons_to_ignore_at_floor_init) //so damaged/burned tiles or plating icons aren't saved as the default + icon_regular_floor = "floor" + else + icon_regular_floor = icon_state + if(floor_tile) + builtin_tile = new floor_tile + +/turf/simulated/floor/Destroy() + QDEL_NULL(builtin_tile) + return ..() + + +//turf/simulated/floor/CanPass(atom/movable/mover, turf/target, height=0) +// if((istype(mover, /obj/machinery/vehicle) && !(src.burnt))) +// if(!( locate(/obj/machinery/mass_driver, src) )) +// return 0 +// return ..() + +/turf/simulated/floor/ex_act(severity) + if(is_shielded()) + return + switch(severity) + if(1.0) + ChangeTurf(baseturf) + if(2.0) + switch(pick(1,2;75,3)) + if(1) + spawn(0) + ReplaceWithLattice() + if(prob(33)) new /obj/item/stack/sheet/metal(src) + if(2) + ChangeTurf(baseturf) + if(3) + if(prob(80)) + break_tile_to_plating() + else + break_tile() + hotspot_expose(1000,CELL_VOLUME) + if(prob(33)) new /obj/item/stack/sheet/metal(src) + if(3.0) + if(prob(50)) + break_tile() + hotspot_expose(1000,CELL_VOLUME) + return + +/turf/simulated/floor/burn_down() + ex_act(2) + +/turf/simulated/floor/is_shielded() + for(var/obj/structure/A in contents) + if(A.level == 3) + return 1 + +/turf/simulated/floor/blob_act(obj/structure/blob/B) + return + +/turf/simulated/floor/proc/update_icon() + update_visuals() + overlays -= current_overlay + if(current_overlay) + overlays.Add(current_overlay) + return 1 + +/turf/simulated/floor/proc/break_tile_to_plating() + var/turf/simulated/floor/plating/T = make_plating() + T.break_tile() + +/turf/simulated/floor/break_tile() + if(broken) + return + current_overlay = pick(broken_states) + broken = TRUE + update_icon() + +/turf/simulated/floor/burn_tile() + if(burnt) + return + current_overlay = pick(burnt_states) + burnt = TRUE + update_icon() + +/turf/simulated/floor/proc/make_plating() + return ChangeTurf(/turf/simulated/floor/plating) + +/turf/simulated/floor/ChangeTurf(turf/simulated/floor/T, defer_change = FALSE, keep_icon = TRUE, ignore_air = FALSE) + if(!istype(src, /turf/simulated/floor)) + return ..() //fucking turfs switch the fucking src of the fucking running procs + if(!ispath(T, /turf/simulated/floor)) + return ..() + + var/old_icon = icon_regular_floor + var/old_plating = icon_plating + var/old_dir = dir + + var/turf/simulated/floor/W = ..() + + if(keep_icon) + W.icon_regular_floor = old_icon + W.icon_plating = old_plating + W.dir = old_dir + + W.update_icon() + return W + +/turf/simulated/floor/attackby(obj/item/C as obj, mob/user as mob, params) + if(!C || !user) + return TRUE + + if(..()) + return TRUE + + if(intact && istype(C, /obj/item/stack/tile)) + try_replace_tile(C, user, params) + + if(istype(C, /obj/item/pipe)) + var/obj/item/pipe/P = C + if(P.pipe_type != -1) // ANY PIPE + user.visible_message( \ + "[user] starts sliding [P] along \the [src].", \ + "You slide [P] along \the [src].", \ + "You hear the scrape of metal against something.") + user.drop_item() + + if(P.is_bent_pipe()) // bent pipe rotation fix see construction.dm + P.dir = 5 + if(user.dir == 1) + P.dir = 6 + else if(user.dir == 2) + P.dir = 9 + else if(user.dir == 4) + P.dir = 10 + else + P.setDir(user.dir) + + P.x = src.x + P.y = src.y + P.z = src.z + P.forceMove(src) + return TRUE + return FALSE + +/turf/simulated/floor/crowbar_act(mob/user, obj/item/I) + if(!intact) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + pry_tile(I, user, TRUE) + +/turf/simulated/floor/proc/try_replace_tile(obj/item/stack/tile/T, mob/user, params) + if(T.turf_type == type) + return + var/obj/item/thing = user.get_inactive_hand() + if(!prying_tool_list.Find(thing.tool_behaviour)) + return + var/turf/simulated/floor/plating/P = pry_tile(thing, user, TRUE) + if(!istype(P)) + return + P.attackby(T, user, params) + +/turf/simulated/floor/proc/pry_tile(obj/item/C, mob/user, silent = FALSE) + if(!silent) + playsound(src, C.usesound, 80, 1) + return remove_tile(user, silent) + +/turf/simulated/floor/proc/remove_tile(mob/user, silent = FALSE, make_tile = TRUE) + if(broken || burnt) + broken = 0 + burnt = 0 + current_overlay = null + if(user && !silent) + to_chat(user, "You remove the broken plating.") + else + if(user && !silent) + to_chat(user, "You remove the floor tile.") + if(builtin_tile && make_tile) + builtin_tile.forceMove(src) + builtin_tile = null + return make_plating() + +/turf/simulated/floor/singularity_pull(S, current_size) + ..() + if(current_size == STAGE_THREE) + if(prob(30)) + if(builtin_tile) + builtin_tile.loc = src + builtin_tile = null + make_plating() + else if(current_size == STAGE_FOUR) + if(prob(50)) + if(builtin_tile) + builtin_tile.loc = src + builtin_tile = null + make_plating() + else if(current_size >= STAGE_FIVE) + if(builtin_tile) + if(prob(70)) + builtin_tile.loc = src + builtin_tile = null + make_plating() + else if(prob(50)) + ReplaceWithLattice() + +/turf/simulated/floor/narsie_act() + if(prob(20)) + ChangeTurf(/turf/simulated/floor/engine/cult) + +/turf/simulated/floor/ratvar_act(force, ignore_mobs) + . = ..() + if(.) + ChangeTurf(/turf/simulated/floor/clockwork) + +/turf/simulated/floor/acid_melt() + ChangeTurf(baseturf) + +/turf/simulated/floor/can_have_cabling() + return !burnt && !broken diff --git a/code/game/turfs/simulated/floor/asteroid.dm b/code/game/turfs/simulated/floor/asteroid.dm index eccc5805657..983b5abaced 100644 --- a/code/game/turfs/simulated/floor/asteroid.dm +++ b/code/game/turfs/simulated/floor/asteroid.dm @@ -352,4 +352,4 @@ GLOBAL_LIST_INIT(megafauna_spawn_list, list(/mob/living/simple_animal/hostile/me oxygen = 22 nitrogen = 82 temperature = 180 - planetary_atmos = FALSE \ No newline at end of file + planetary_atmos = FALSE diff --git a/code/game/turfs/simulated/floor/chasm.dm b/code/game/turfs/simulated/floor/chasm.dm index fda758238e8..1fb97430026 100644 --- a/code/game/turfs/simulated/floor/chasm.dm +++ b/code/game/turfs/simulated/floor/chasm.dm @@ -194,4 +194,4 @@ temperature = T20C /turf/simulated/floor/chasm/CanPass(atom/movable/mover, turf/target) - return 1 \ No newline at end of file + return 1 diff --git a/code/game/turfs/simulated/floor/indestructible.dm b/code/game/turfs/simulated/floor/indestructible.dm index 3896dc513f2..f231a4fd0b3 100644 --- a/code/game/turfs/simulated/floor/indestructible.dm +++ b/code/game/turfs/simulated/floor/indestructible.dm @@ -82,4 +82,4 @@ /turf/simulated/floor/indestructible/hierophant/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) return FALSE -/turf/simulated/floor/indestructible/hierophant/two \ No newline at end of file +/turf/simulated/floor/indestructible/hierophant/two diff --git a/code/game/turfs/simulated/floor/lava.dm b/code/game/turfs/simulated/floor/lava.dm index 5a512b5455f..57e90840920 100644 --- a/code/game/turfs/simulated/floor/lava.dm +++ b/code/game/turfs/simulated/floor/lava.dm @@ -138,4 +138,4 @@ baseturf = /turf/simulated/floor/chasm/straight_down/lava_land_surface /turf/simulated/floor/plating/lava/smooth/airless - temperature = TCMB \ No newline at end of file + temperature = TCMB diff --git a/code/game/turfs/simulated/floor/misc_floor.dm b/code/game/turfs/simulated/floor/misc_floor.dm index ee7c1bcbb30..33ae636090d 100644 --- a/code/game/turfs/simulated/floor/misc_floor.dm +++ b/code/game/turfs/simulated/floor/misc_floor.dm @@ -186,4 +186,4 @@ desc = "Warm brass plating. You can feel it gently vibrating, as if machinery is on the other side." icon_state = "reebe" baseturf = /turf/simulated/floor/clockwork/reebe - uses_overlay = FALSE \ No newline at end of file + uses_overlay = FALSE diff --git a/code/game/turfs/simulated/minerals.dm b/code/game/turfs/simulated/minerals.dm index 118132765a1..d1f7a287c70 100644 --- a/code/game/turfs/simulated/minerals.dm +++ b/code/game/turfs/simulated/minerals.dm @@ -511,4 +511,4 @@ #undef GIBTONITE_UNSTRUCK #undef GIBTONITE_ACTIVE #undef GIBTONITE_STABLE -#undef GIBTONITE_DETONATE \ No newline at end of file +#undef GIBTONITE_DETONATE diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm index 7dd41815a4c..078a1b8e513 100644 --- a/code/game/turfs/simulated/walls.dm +++ b/code/game/turfs/simulated/walls.dm @@ -1,506 +1,506 @@ -#define WALL_DENT_HIT 1 -#define WALL_DENT_SHOT 2 -#define MAX_DENT_DECALS 15 - -/turf/simulated/wall - name = "wall" - desc = "A huge chunk of metal used to seperate rooms." - icon = 'icons/turf/walls/wall.dmi' - icon_state = "wall" - var/rotting = 0 - - var/damage = 0 - var/damage_cap = 100 //Wall will break down to girders if damage reaches this point - - var/damage_overlay - var/global/damage_overlays[8] - - var/max_temperature = 1800 //K, walls will take damage if they're next to a fire hotter than this - - opacity = 1 - density = 1 - blocks_air = 1 - explosion_block = 1 - - thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT - heat_capacity = 312500 //a little over 5 cm thick , 312500 for 1 m by 2.5 m by 0.25 m plasteel wall - - var/can_dismantle_with_welder = TRUE - var/hardness = 40 //lower numbers are harder. Used to determine the probability of a hulk smashing through. - var/slicing_duration = 100 - var/engraving //engraving on the wall - var/engraving_quality - var/list/dent_decals - var/sheet_type = /obj/item/stack/sheet/metal - var/sheet_amount = 2 - var/girder_type = /obj/structure/girder - var/obj/item/stack/sheet/builtin_sheet = null - - canSmoothWith = list( - /turf/simulated/wall, - /turf/simulated/wall/r_wall, - /obj/structure/falsewall, - /obj/structure/falsewall/reinforced, - /turf/simulated/wall/rust, - /turf/simulated/wall/r_wall/rust, - /turf/simulated/wall/r_wall/coated) - smooth = SMOOTH_TRUE - -/turf/simulated/wall/New() - ..() - builtin_sheet = new sheet_type - -/turf/simulated/wall/BeforeChange() - for(var/obj/effect/overlay/wall_rot/WR in src) - qdel(WR) - . = ..() - -//Appearance -/turf/simulated/wall/examine(mob/user) - . = ..() - - if(!damage) - . += "It looks fully intact." - else - var/dam = damage / damage_cap - if(dam <= 0.3) - . += "It looks slightly damaged." - else if(dam <= 0.6) - . += "It looks moderately damaged." - else - . += "It looks heavily damaged." - - if(rotting) - . += "There is fungus growing on [src]." - -/turf/simulated/wall/proc/update_icon() - if(!damage_overlays[1]) //list hasn't been populated - generate_overlays() - - queue_smooth(src) - if(!damage) - if(damage_overlay) - overlays -= damage_overlays[damage_overlay] - damage_overlay = 0 - return - - var/overlay = round(damage / damage_cap * damage_overlays.len) + 1 - if(overlay > damage_overlays.len) - overlay = damage_overlays.len - - if(damage_overlay && overlay == damage_overlay) //No need to update. - return - if(damage_overlay) - overlays -= damage_overlays[damage_overlay] - overlays += damage_overlays[overlay] - damage_overlay = overlay - -/turf/simulated/wall/proc/generate_overlays() - var/alpha_inc = 256 / damage_overlays.len - - for(var/i = 1; i <= damage_overlays.len; i++) - var/image/img = image(icon = 'icons/turf/walls.dmi', icon_state = "overlay_damage") - img.blend_mode = BLEND_MULTIPLY - img.alpha = (i * alpha_inc) - 1 - damage_overlays[i] = img - -//Damage - -/turf/simulated/wall/proc/take_damage(dam) - if(dam) - damage = max(0, damage + dam) - update_damage() - return - -/turf/simulated/wall/proc/update_damage() - var/cap = damage_cap - if(rotting) - cap = cap / 10 - - if(damage >= cap) - dismantle_wall() - else - update_icon() - - return - -/turf/simulated/wall/proc/adjacent_fire_act(turf/simulated/wall, radiated_temperature) - if(radiated_temperature > max_temperature) - take_damage(rand(10, 20) * (radiated_temperature / max_temperature)) - -/turf/simulated/wall/handle_ricochet(obj/item/projectile/P) //A huge pile of shitcode! - var/turf/p_turf = get_turf(P) - var/face_direction = get_dir(src, p_turf) - var/face_angle = dir2angle(face_direction) - var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) - if(abs(incidence_s) > 90 && abs(incidence_s) < 270) - return FALSE - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.setAngle(new_angle_s) - return TRUE - -/turf/simulated/wall/proc/dismantle_wall(devastated = 0, explode = 0) - if(devastated) - devastate_wall() - else - playsound(src, 'sound/items/welder.ogg', 100, 1) - var/newgirder = break_wall() - if(newgirder) //maybe we don't /want/ a girder! - transfer_fingerprints_to(newgirder) - - for(var/obj/O in src.contents) //Eject contents! - if(istype(O,/obj/structure/sign/poster)) - var/obj/structure/sign/poster/P = O - P.roll_and_drop(src) - else - O.forceMove(src) - - ChangeTurf(/turf/simulated/floor/plating) - -/turf/simulated/wall/proc/break_wall() - new sheet_type(src, sheet_amount) - return new girder_type(src) - -/turf/simulated/wall/proc/devastate_wall() - new sheet_type(src, sheet_amount) - new /obj/item/stack/sheet/metal(src) - -/turf/simulated/wall/ex_act(severity) - switch(severity) - if(1.0) - ChangeTurf(baseturf) - return - if(2.0) - if(prob(50)) - take_damage(rand(150, 250)) - else - dismantle_wall(1, 1) - if(3.0) - take_damage(rand(0, 250)) - else - return - -/turf/simulated/wall/blob_act(obj/structure/blob/B) - if(prob(50)) - dismantle_wall() - else - add_dent(WALL_DENT_HIT) - -/turf/simulated/wall/rpd_act(mob/user, obj/item/rpd/our_rpd) - if(our_rpd.mode == RPD_ATMOS_MODE) - if(!our_rpd.ranged) - playsound(src, "sound/weapons/circsawhit.ogg", 50, 1) - user.visible_message("[user] starts drilling a hole in [src]...", "You start drilling a hole in [src]...", "You hear drilling.") - if(!do_after(user, our_rpd.walldelay, target = src)) //Drilling into walls takes time - return - our_rpd.create_atmos_pipe(user, src) - else if(our_rpd.mode == RPD_DISPOSALS_MODE && !our_rpd.ranged) - return - else - ..() - -/turf/simulated/wall/mech_melee_attack(obj/mecha/M) - M.do_attack_animation(src) - switch(M.damtype) - if(BRUTE) - playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) - M.visible_message("[M.name] hits [src]!", "You hit [src]!") - if(prob(hardness + M.force) && M.force > 20) - dismantle_wall(1) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) - else - add_dent(WALL_DENT_HIT) - if(BURN) - playsound(src, 'sound/items/welder.ogg', 100, TRUE) - if(TOX) - playsound(src, 'sound/effects/spray2.ogg', 100, TRUE) - return FALSE - -// Wall-rot effect, a nasty fungus that destroys walls. -/turf/simulated/wall/proc/rot() - if(!rotting) - rotting = 1 - - var/number_rots = rand(2,3) - for(var/i=0, iThe thermite starts melting through the wall.") - - spawn(wait) - if(O) qdel(O) - return - -//Interactions - -/turf/simulated/wall/attack_animal(mob/living/simple_animal/M) - M.changeNext_move(CLICK_CD_MELEE) - M.do_attack_animation(src) - if((M.environment_smash & ENVIRONMENT_SMASH_WALLS) || (M.environment_smash & ENVIRONMENT_SMASH_RWALLS)) - if(M.environment_smash & ENVIRONMENT_SMASH_RWALLS) - dismantle_wall(1) - to_chat(M, "You smash through the wall.") - else - to_chat(M, text("You smash against the wall.")) - take_damage(rand(25, 75)) - return - - to_chat(M, "You push the wall but nothing happens!") - return - -/turf/simulated/wall/attack_hulk(mob/user, does_attack_animation = FALSE) - ..(user, TRUE) - - if(prob(hardness) || rotting) - playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1) - user.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" )) - dismantle_wall(TRUE) - else - playsound(src, 'sound/effects/bang.ogg', 50, 1) - add_dent(WALL_DENT_HIT) - to_chat(user, text("You punch the wall.")) - return TRUE - -/turf/simulated/wall/attack_hand(mob/user) - user.changeNext_move(CLICK_CD_MELEE) - if(rotting) - if(hardness <= 10) - to_chat(user, "This wall feels rather unstable.") - return - else - to_chat(user, "The wall crumbles under your touch.") - dismantle_wall() - return - - to_chat(user, "You push the wall but nothing happens!") - playsound(src, 'sound/weapons/genhit.ogg', 25, 1) - add_fingerprint(user) - return ..() - -/turf/simulated/wall/attackby(obj/item/I, mob/user, params) - user.changeNext_move(CLICK_CD_MELEE) - - if(!isturf(user.loc)) - return // No touching walls unless you're on a turf (pretty sure attackby can't be called anyways but whatever) - - if(rotting && try_rot(I, user, params)) - return - - if(try_decon(I, user, params)) - return - - if(try_destroy(I, user, params)) - return - - if(try_wallmount(I, user, params)) - return - // The magnetic gripper does a separate attackby, so bail from this one - if(istype(I, /obj/item/gripper)) - return - - return ..() - -/turf/simulated/wall/welder_act(mob/user, obj/item/I) - . = TRUE - if(thermite && I.use_tool(src, user, volume = I.tool_volume)) - thermitemelt(user) - return - if(rotting) - if(I.use_tool(src, user, volume = I.tool_volume)) - for(var/obj/effect/overlay/wall_rot/WR in src) - qdel(WR) - rotting = FALSE - to_chat(user, "You burn off the fungi with [I].") - return - - if(!I.tool_use_check(user, 0)) //Wall repair stuff - return - - var/time_required = slicing_duration - var/intention - if(can_dismantle_with_welder) - intention = "Dismantle" - if(damage || LAZYLEN(dent_decals)) - intention = "Repair" - if(can_dismantle_with_welder) - var/moved_away = user.loc - intention = alert(user, "Would you like to repair or dismantle [src]?", "[src]", "Repair", "Dismantle") - if(user.loc != moved_away) - to_chat(user, "Stay still while doing this!") - return - if(intention == "Repair") - time_required = max(5, damage / 5) - if(!intention) - return - if(intention == "Dismantle") - WELDER_ATTEMPT_SLICING_MESSAGE - else - WELDER_ATTEMPT_REPAIR_MESSAGE - if(I.use_tool(src, user, time_required, volume = I.tool_volume)) - if(intention == "Dismantle") - WELDER_SLICING_SUCCESS_MESSAGE - dismantle_wall() - else - WELDER_REPAIR_SUCCESS_MESSAGE - cut_overlay(dent_decals) - dent_decals?.Cut() - take_damage(-damage) - -/turf/simulated/wall/proc/try_rot(obj/item/I, mob/user, params) - if((!is_sharp(I) && I.force >= 10) || I.force >= 20) - to_chat(user, "[src] crumbles away under the force of your [I.name].") - dismantle_wall(1) - return TRUE - return FALSE - -/turf/simulated/wall/proc/try_decon(obj/item/I, mob/user, params) - if(istype(I, /obj/item/gun/energy/plasmacutter)) - to_chat(user, "You begin slicing through the outer plating.") - playsound(src, I.usesound, 100, 1) - - if(do_after(user, istype(sheet_type, /obj/item/stack/sheet/mineral/diamond) ? 120 * I.toolspeed : 60 * I.toolspeed, target = src)) - to_chat(user, "You remove the outer plating.") - dismantle_wall() - visible_message("[user] slices apart [src]!", "You hear metal being sliced apart.") - return TRUE - - return FALSE - -/turf/simulated/wall/proc/try_destroy(obj/item/I, mob/user, params) - var/isdiamond = istype(sheet_type, /obj/item/stack/sheet/mineral/diamond) // snowflake bullshit - - if(istype(I, /obj/item/pickaxe/drill/diamonddrill)) - to_chat(user, "You begin to drill though the wall.") - - if(do_after(user, isdiamond ? 480 * I.toolspeed : 240 * I.toolspeed, target = src)) // Diamond pickaxe has 0.25 toolspeed, so 120/60 - to_chat(user, "Your [I.name] tears though the last of the reinforced plating.") - dismantle_wall() - visible_message("[user] drills through [src]!", "You hear the grinding of metal.") - return TRUE - - else if(istype(I, /obj/item/pickaxe/drill/jackhammer)) - to_chat(user, "You begin to disintegrates the wall.") - - if(do_after(user, isdiamond ? 600 * I.toolspeed : 300 * I.toolspeed, target = src)) // Jackhammer has 0.1 toolspeed, so 60/30 - to_chat(user, "Your [I.name] disintegrates the reinforced plating.") - dismantle_wall() - visible_message("[user] disintegrates [src]!","You hear the grinding of metal.") - return TRUE - - return FALSE - -/turf/simulated/wall/proc/try_wallmount(obj/item/I, mob/user, params) - if(istype(I, /obj/item/mounted)) - return TRUE // We don't want attack_hand running and doing stupid shit with this - - if(istype(I, /obj/item/poster)) - place_poster(I, user) - return TRUE - - //Bone White - Place pipes on walls // I fucking hate your code with a passion bone - if(istype(I, /obj/item/pipe)) - var/obj/item/pipe/P = I - if(P.pipe_type != -1) // ANY PIPE - playsound(get_turf(src), 'sound/weapons/circsawhit.ogg', 50, 1) - user.visible_message( - "[user] starts drilling a hole in [src].", - "You start drilling a hole in [src].", - "You hear a drill.") - - if(do_after(user, 80 * P.toolspeed, target = src)) - user.visible_message( - "[user] drills a hole in [src] and pushes [P] into the void.", - "You finish drilling [src] and push [P] into the void.", - "You hear a ratchet.") - - user.drop_item() - if(P.is_bent_pipe()) // bent pipe rotation fix see construction.dm - P.setDir(5) - if(user.dir == 1) - P.setDir(6) - else if(user.dir == 2) - P.setDir(9) - else if(user.dir == 4) - P.setDir(10) - else - P.setDir(user.dir) - P.forceMove(src) - P.level = 2 - return TRUE - return FALSE - -/turf/simulated/wall/singularity_pull(S, current_size) - ..() - wall_singularity_pull(current_size) - -/turf/simulated/wall/proc/wall_singularity_pull(current_size) - if(current_size >= STAGE_FIVE) - if(prob(50)) - dismantle_wall() - return - if(current_size == STAGE_FOUR) - if(prob(30)) - dismantle_wall() - -/turf/simulated/wall/narsie_act() - if(prob(20)) - ChangeTurf(/turf/simulated/wall/cult) - -/turf/simulated/wall/acid_act(acidpwr, acid_volume) - if(explosion_block >= 2) - acidpwr = min(acidpwr, 50) //we reduce the power so strong walls never get melted. - . = ..() - -/turf/simulated/wall/acid_melt() - dismantle_wall(1) - -/turf/simulated/wall/proc/add_dent(denttype, x=rand(-8, 8), y=rand(-8, 8)) - if(LAZYLEN(dent_decals) >= MAX_DENT_DECALS) - return - - var/mutable_appearance/decal = mutable_appearance('icons/effects/effects.dmi', "", BULLET_HOLE_LAYER) - switch(denttype) - if(WALL_DENT_SHOT) - decal.icon_state = "bullet_hole" - if(WALL_DENT_HIT) - decal.icon_state = "impact[rand(1, 3)]" - - decal.pixel_x = x - decal.pixel_y = y - - if(LAZYLEN(dent_decals)) - cut_overlay(dent_decals) - dent_decals += decal - else - dent_decals = list(decal) - - add_overlay(dent_decals) - -#undef MAX_DENT_DECALS \ No newline at end of file +#define WALL_DENT_HIT 1 +#define WALL_DENT_SHOT 2 +#define MAX_DENT_DECALS 15 + +/turf/simulated/wall + name = "wall" + desc = "A huge chunk of metal used to seperate rooms." + icon = 'icons/turf/walls/wall.dmi' + icon_state = "wall" + var/rotting = 0 + + var/damage = 0 + var/damage_cap = 100 //Wall will break down to girders if damage reaches this point + + var/damage_overlay + var/global/damage_overlays[8] + + var/max_temperature = 1800 //K, walls will take damage if they're next to a fire hotter than this + + opacity = 1 + density = 1 + blocks_air = 1 + explosion_block = 1 + + thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT + heat_capacity = 312500 //a little over 5 cm thick , 312500 for 1 m by 2.5 m by 0.25 m plasteel wall + + var/can_dismantle_with_welder = TRUE + var/hardness = 40 //lower numbers are harder. Used to determine the probability of a hulk smashing through. + var/slicing_duration = 100 + var/engraving //engraving on the wall + var/engraving_quality + var/list/dent_decals + var/sheet_type = /obj/item/stack/sheet/metal + var/sheet_amount = 2 + var/girder_type = /obj/structure/girder + var/obj/item/stack/sheet/builtin_sheet = null + + canSmoothWith = list( + /turf/simulated/wall, + /turf/simulated/wall/r_wall, + /obj/structure/falsewall, + /obj/structure/falsewall/reinforced, + /turf/simulated/wall/rust, + /turf/simulated/wall/r_wall/rust, + /turf/simulated/wall/r_wall/coated) + smooth = SMOOTH_TRUE + +/turf/simulated/wall/New() + ..() + builtin_sheet = new sheet_type + +/turf/simulated/wall/BeforeChange() + for(var/obj/effect/overlay/wall_rot/WR in src) + qdel(WR) + . = ..() + +//Appearance +/turf/simulated/wall/examine(mob/user) + . = ..() + + if(!damage) + . += "It looks fully intact." + else + var/dam = damage / damage_cap + if(dam <= 0.3) + . += "It looks slightly damaged." + else if(dam <= 0.6) + . += "It looks moderately damaged." + else + . += "It looks heavily damaged." + + if(rotting) + . += "There is fungus growing on [src]." + +/turf/simulated/wall/proc/update_icon() + if(!damage_overlays[1]) //list hasn't been populated + generate_overlays() + + queue_smooth(src) + if(!damage) + if(damage_overlay) + overlays -= damage_overlays[damage_overlay] + damage_overlay = 0 + return + + var/overlay = round(damage / damage_cap * damage_overlays.len) + 1 + if(overlay > damage_overlays.len) + overlay = damage_overlays.len + + if(damage_overlay && overlay == damage_overlay) //No need to update. + return + if(damage_overlay) + overlays -= damage_overlays[damage_overlay] + overlays += damage_overlays[overlay] + damage_overlay = overlay + +/turf/simulated/wall/proc/generate_overlays() + var/alpha_inc = 256 / damage_overlays.len + + for(var/i = 1; i <= damage_overlays.len; i++) + var/image/img = image(icon = 'icons/turf/walls.dmi', icon_state = "overlay_damage") + img.blend_mode = BLEND_MULTIPLY + img.alpha = (i * alpha_inc) - 1 + damage_overlays[i] = img + +//Damage + +/turf/simulated/wall/proc/take_damage(dam) + if(dam) + damage = max(0, damage + dam) + update_damage() + return + +/turf/simulated/wall/proc/update_damage() + var/cap = damage_cap + if(rotting) + cap = cap / 10 + + if(damage >= cap) + dismantle_wall() + else + update_icon() + + return + +/turf/simulated/wall/proc/adjacent_fire_act(turf/simulated/wall, radiated_temperature) + if(radiated_temperature > max_temperature) + take_damage(rand(10, 20) * (radiated_temperature / max_temperature)) + +/turf/simulated/wall/handle_ricochet(obj/item/projectile/P) //A huge pile of shitcode! + var/turf/p_turf = get_turf(P) + var/face_direction = get_dir(src, p_turf) + var/face_angle = dir2angle(face_direction) + var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180)) + if(abs(incidence_s) > 90 && abs(incidence_s) < 270) + return FALSE + var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) + P.setAngle(new_angle_s) + return TRUE + +/turf/simulated/wall/proc/dismantle_wall(devastated = 0, explode = 0) + if(devastated) + devastate_wall() + else + playsound(src, 'sound/items/welder.ogg', 100, 1) + var/newgirder = break_wall() + if(newgirder) //maybe we don't /want/ a girder! + transfer_fingerprints_to(newgirder) + + for(var/obj/O in src.contents) //Eject contents! + if(istype(O,/obj/structure/sign/poster)) + var/obj/structure/sign/poster/P = O + P.roll_and_drop(src) + else + O.forceMove(src) + + ChangeTurf(/turf/simulated/floor/plating) + +/turf/simulated/wall/proc/break_wall() + new sheet_type(src, sheet_amount) + return new girder_type(src) + +/turf/simulated/wall/proc/devastate_wall() + new sheet_type(src, sheet_amount) + new /obj/item/stack/sheet/metal(src) + +/turf/simulated/wall/ex_act(severity) + switch(severity) + if(1.0) + ChangeTurf(baseturf) + return + if(2.0) + if(prob(50)) + take_damage(rand(150, 250)) + else + dismantle_wall(1, 1) + if(3.0) + take_damage(rand(0, 250)) + else + return + +/turf/simulated/wall/blob_act(obj/structure/blob/B) + if(prob(50)) + dismantle_wall() + else + add_dent(WALL_DENT_HIT) + +/turf/simulated/wall/rpd_act(mob/user, obj/item/rpd/our_rpd) + if(our_rpd.mode == RPD_ATMOS_MODE) + if(!our_rpd.ranged) + playsound(src, "sound/weapons/circsawhit.ogg", 50, 1) + user.visible_message("[user] starts drilling a hole in [src]...", "You start drilling a hole in [src]...", "You hear drilling.") + if(!do_after(user, our_rpd.walldelay, target = src)) //Drilling into walls takes time + return + our_rpd.create_atmos_pipe(user, src) + else if(our_rpd.mode == RPD_DISPOSALS_MODE && !our_rpd.ranged) + return + else + ..() + +/turf/simulated/wall/mech_melee_attack(obj/mecha/M) + M.do_attack_animation(src) + switch(M.damtype) + if(BRUTE) + playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) + M.visible_message("[M.name] hits [src]!", "You hit [src]!") + if(prob(hardness + M.force) && M.force > 20) + dismantle_wall(1) + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + else + add_dent(WALL_DENT_HIT) + if(BURN) + playsound(src, 'sound/items/welder.ogg', 100, TRUE) + if(TOX) + playsound(src, 'sound/effects/spray2.ogg', 100, TRUE) + return FALSE + +// Wall-rot effect, a nasty fungus that destroys walls. +/turf/simulated/wall/proc/rot() + if(!rotting) + rotting = 1 + + var/number_rots = rand(2,3) + for(var/i=0, iThe thermite starts melting through the wall.") + + spawn(wait) + if(O) qdel(O) + return + +//Interactions + +/turf/simulated/wall/attack_animal(mob/living/simple_animal/M) + M.changeNext_move(CLICK_CD_MELEE) + M.do_attack_animation(src) + if((M.environment_smash & ENVIRONMENT_SMASH_WALLS) || (M.environment_smash & ENVIRONMENT_SMASH_RWALLS)) + if(M.environment_smash & ENVIRONMENT_SMASH_RWALLS) + dismantle_wall(1) + to_chat(M, "You smash through the wall.") + else + to_chat(M, text("You smash against the wall.")) + take_damage(rand(25, 75)) + return + + to_chat(M, "You push the wall but nothing happens!") + return + +/turf/simulated/wall/attack_hulk(mob/user, does_attack_animation = FALSE) + ..(user, TRUE) + + if(prob(hardness) || rotting) + playsound(src, 'sound/effects/meteorimpact.ogg', 100, 1) + user.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" )) + dismantle_wall(TRUE) + else + playsound(src, 'sound/effects/bang.ogg', 50, 1) + add_dent(WALL_DENT_HIT) + to_chat(user, text("You punch the wall.")) + return TRUE + +/turf/simulated/wall/attack_hand(mob/user) + user.changeNext_move(CLICK_CD_MELEE) + if(rotting) + if(hardness <= 10) + to_chat(user, "This wall feels rather unstable.") + return + else + to_chat(user, "The wall crumbles under your touch.") + dismantle_wall() + return + + to_chat(user, "You push the wall but nothing happens!") + playsound(src, 'sound/weapons/genhit.ogg', 25, 1) + add_fingerprint(user) + return ..() + +/turf/simulated/wall/attackby(obj/item/I, mob/user, params) + user.changeNext_move(CLICK_CD_MELEE) + + if(!isturf(user.loc)) + return // No touching walls unless you're on a turf (pretty sure attackby can't be called anyways but whatever) + + if(rotting && try_rot(I, user, params)) + return + + if(try_decon(I, user, params)) + return + + if(try_destroy(I, user, params)) + return + + if(try_wallmount(I, user, params)) + return + // The magnetic gripper does a separate attackby, so bail from this one + if(istype(I, /obj/item/gripper)) + return + + return ..() + +/turf/simulated/wall/welder_act(mob/user, obj/item/I) + . = TRUE + if(thermite && I.use_tool(src, user, volume = I.tool_volume)) + thermitemelt(user) + return + if(rotting) + if(I.use_tool(src, user, volume = I.tool_volume)) + for(var/obj/effect/overlay/wall_rot/WR in src) + qdel(WR) + rotting = FALSE + to_chat(user, "You burn off the fungi with [I].") + return + + if(!I.tool_use_check(user, 0)) //Wall repair stuff + return + + var/time_required = slicing_duration + var/intention + if(can_dismantle_with_welder) + intention = "Dismantle" + if(damage || LAZYLEN(dent_decals)) + intention = "Repair" + if(can_dismantle_with_welder) + var/moved_away = user.loc + intention = alert(user, "Would you like to repair or dismantle [src]?", "[src]", "Repair", "Dismantle") + if(user.loc != moved_away) + to_chat(user, "Stay still while doing this!") + return + if(intention == "Repair") + time_required = max(5, damage / 5) + if(!intention) + return + if(intention == "Dismantle") + WELDER_ATTEMPT_SLICING_MESSAGE + else + WELDER_ATTEMPT_REPAIR_MESSAGE + if(I.use_tool(src, user, time_required, volume = I.tool_volume)) + if(intention == "Dismantle") + WELDER_SLICING_SUCCESS_MESSAGE + dismantle_wall() + else + WELDER_REPAIR_SUCCESS_MESSAGE + cut_overlay(dent_decals) + dent_decals?.Cut() + take_damage(-damage) + +/turf/simulated/wall/proc/try_rot(obj/item/I, mob/user, params) + if((!is_sharp(I) && I.force >= 10) || I.force >= 20) + to_chat(user, "[src] crumbles away under the force of your [I.name].") + dismantle_wall(1) + return TRUE + return FALSE + +/turf/simulated/wall/proc/try_decon(obj/item/I, mob/user, params) + if(istype(I, /obj/item/gun/energy/plasmacutter)) + to_chat(user, "You begin slicing through the outer plating.") + playsound(src, I.usesound, 100, 1) + + if(do_after(user, istype(sheet_type, /obj/item/stack/sheet/mineral/diamond) ? 120 * I.toolspeed : 60 * I.toolspeed, target = src)) + to_chat(user, "You remove the outer plating.") + dismantle_wall() + visible_message("[user] slices apart [src]!", "You hear metal being sliced apart.") + return TRUE + + return FALSE + +/turf/simulated/wall/proc/try_destroy(obj/item/I, mob/user, params) + var/isdiamond = istype(sheet_type, /obj/item/stack/sheet/mineral/diamond) // snowflake bullshit + + if(istype(I, /obj/item/pickaxe/drill/diamonddrill)) + to_chat(user, "You begin to drill though the wall.") + + if(do_after(user, isdiamond ? 480 * I.toolspeed : 240 * I.toolspeed, target = src)) // Diamond pickaxe has 0.25 toolspeed, so 120/60 + to_chat(user, "Your [I.name] tears though the last of the reinforced plating.") + dismantle_wall() + visible_message("[user] drills through [src]!", "You hear the grinding of metal.") + return TRUE + + else if(istype(I, /obj/item/pickaxe/drill/jackhammer)) + to_chat(user, "You begin to disintegrates the wall.") + + if(do_after(user, isdiamond ? 600 * I.toolspeed : 300 * I.toolspeed, target = src)) // Jackhammer has 0.1 toolspeed, so 60/30 + to_chat(user, "Your [I.name] disintegrates the reinforced plating.") + dismantle_wall() + visible_message("[user] disintegrates [src]!","You hear the grinding of metal.") + return TRUE + + return FALSE + +/turf/simulated/wall/proc/try_wallmount(obj/item/I, mob/user, params) + if(istype(I, /obj/item/mounted)) + return TRUE // We don't want attack_hand running and doing stupid shit with this + + if(istype(I, /obj/item/poster)) + place_poster(I, user) + return TRUE + + //Bone White - Place pipes on walls // I fucking hate your code with a passion bone + if(istype(I, /obj/item/pipe)) + var/obj/item/pipe/P = I + if(P.pipe_type != -1) // ANY PIPE + playsound(get_turf(src), 'sound/weapons/circsawhit.ogg', 50, 1) + user.visible_message( + "[user] starts drilling a hole in [src].", + "You start drilling a hole in [src].", + "You hear a drill.") + + if(do_after(user, 80 * P.toolspeed, target = src)) + user.visible_message( + "[user] drills a hole in [src] and pushes [P] into the void.", + "You finish drilling [src] and push [P] into the void.", + "You hear a ratchet.") + + user.drop_item() + if(P.is_bent_pipe()) // bent pipe rotation fix see construction.dm + P.setDir(5) + if(user.dir == 1) + P.setDir(6) + else if(user.dir == 2) + P.setDir(9) + else if(user.dir == 4) + P.setDir(10) + else + P.setDir(user.dir) + P.forceMove(src) + P.level = 2 + return TRUE + return FALSE + +/turf/simulated/wall/singularity_pull(S, current_size) + ..() + wall_singularity_pull(current_size) + +/turf/simulated/wall/proc/wall_singularity_pull(current_size) + if(current_size >= STAGE_FIVE) + if(prob(50)) + dismantle_wall() + return + if(current_size == STAGE_FOUR) + if(prob(30)) + dismantle_wall() + +/turf/simulated/wall/narsie_act() + if(prob(20)) + ChangeTurf(/turf/simulated/wall/cult) + +/turf/simulated/wall/acid_act(acidpwr, acid_volume) + if(explosion_block >= 2) + acidpwr = min(acidpwr, 50) //we reduce the power so strong walls never get melted. + . = ..() + +/turf/simulated/wall/acid_melt() + dismantle_wall(1) + +/turf/simulated/wall/proc/add_dent(denttype, x=rand(-8, 8), y=rand(-8, 8)) + if(LAZYLEN(dent_decals) >= MAX_DENT_DECALS) + return + + var/mutable_appearance/decal = mutable_appearance('icons/effects/effects.dmi', "", BULLET_HOLE_LAYER) + switch(denttype) + if(WALL_DENT_SHOT) + decal.icon_state = "bullet_hole" + if(WALL_DENT_HIT) + decal.icon_state = "impact[rand(1, 3)]" + + decal.pixel_x = x + decal.pixel_y = y + + if(LAZYLEN(dent_decals)) + cut_overlay(dent_decals) + dent_decals += decal + else + dent_decals = list(decal) + + add_overlay(dent_decals) + +#undef MAX_DENT_DECALS diff --git a/code/game/turfs/simulated/walls_indestructible.dm b/code/game/turfs/simulated/walls_indestructible.dm index 9b522e34640..6bb3cb9c22a 100644 --- a/code/game/turfs/simulated/walls_indestructible.dm +++ b/code/game/turfs/simulated/walls_indestructible.dm @@ -75,4 +75,4 @@ /turf/simulated/wall/indestructible/uranium icon = 'icons/turf/walls/uranium_wall.dmi' - icon_state = "uranium" \ No newline at end of file + icon_state = "uranium" diff --git a/code/game/turfs/simulated/walls_mineral.dm b/code/game/turfs/simulated/walls_mineral.dm index e4084fe5a59..bc04a695da7 100644 --- a/code/game/turfs/simulated/walls_mineral.dm +++ b/code/game/turfs/simulated/walls_mineral.dm @@ -1,354 +1,354 @@ -/turf/simulated/wall/mineral - name = "mineral wall" - desc = "This shouldn't exist" - icon_state = "" - var/last_event = 0 - var/active = null - canSmoothWith = null - smooth = SMOOTH_TRUE - -/turf/simulated/wall/mineral/gold - name = "gold wall" - desc = "A wall with gold plating. Swag!" - icon = 'icons/turf/walls/gold_wall.dmi' - icon_state = "gold" - sheet_type = /obj/item/stack/sheet/mineral/gold - explosion_block = 0 //gold is a soft metal you dingus. - canSmoothWith = list(/turf/simulated/wall/mineral/gold, /obj/structure/falsewall/gold) - -/turf/simulated/wall/mineral/silver - name = "silver wall" - desc = "A wall with silver plating. Shiny!" - icon = 'icons/turf/walls/silver_wall.dmi' - icon_state = "silver" - sheet_type = /obj/item/stack/sheet/mineral/silver - canSmoothWith = list(/turf/simulated/wall/mineral/silver, /obj/structure/falsewall/silver) - -/turf/simulated/wall/mineral/diamond - name = "diamond wall" - desc = "A wall with diamond plating. You monster." - icon = 'icons/turf/walls/diamond_wall.dmi' - icon_state = "diamond" - sheet_type = /obj/item/stack/sheet/mineral/diamond - explosion_block = 3 - canSmoothWith = list(/turf/simulated/wall/mineral/diamond, /obj/structure/falsewall/diamond) - -/turf/simulated/wall/mineral/bananium - name = "bananium wall" - desc = "A wall with bananium plating. Honk!" - icon = 'icons/turf/walls/bananium_wall.dmi' - icon_state = "bananium" - sheet_type = /obj/item/stack/sheet/mineral/bananium - canSmoothWith = list(/turf/simulated/wall/mineral/bananium, /obj/structure/falsewall/bananium) - -/turf/simulated/wall/mineral/sandstone - name = "sandstone wall" - desc = "A wall with sandstone plating." - icon = 'icons/turf/walls/sandstone_wall.dmi' - icon_state = "sandstone" - sheet_type = /obj/item/stack/sheet/mineral/sandstone - explosion_block = 0 - canSmoothWith = list(/turf/simulated/wall/mineral/sandstone, /obj/structure/falsewall/sandstone) - -/turf/simulated/wall/mineral/uranium - name = "uranium wall" - desc = "A wall with uranium plating. This is probably a bad idea." - icon = 'icons/turf/walls/uranium_wall.dmi' - icon_state = "uranium" - sheet_type = /obj/item/stack/sheet/mineral/uranium - canSmoothWith = list(/turf/simulated/wall/mineral/uranium, /obj/structure/falsewall/uranium) - -/turf/simulated/wall/mineral/uranium/proc/radiate() - if(!active) - if(world.time > last_event+15) - active = 1 - for(var/mob/living/L in range(3,src)) - L.apply_effect(12,IRRADIATE,0) - for(var/turf/simulated/wall/mineral/uranium/T in range(3,src)) - T.radiate() - last_event = world.time - active = null - return - return - -/turf/simulated/wall/mineral/uranium/attack_hand(mob/user as mob) - radiate() - ..() - -/turf/simulated/wall/mineral/uranium/attackby(obj/item/W as obj, mob/user as mob, params) - radiate() - ..() - -/turf/simulated/wall/mineral/uranium/Bumped(AM as mob|obj) - radiate() - ..() - -/turf/simulated/wall/mineral/plasma - name = "plasma wall" - desc = "A wall with plasma plating. This is definately a bad idea." - icon = 'icons/turf/walls/plasma_wall.dmi' - icon_state = "plasma" - sheet_type = /obj/item/stack/sheet/mineral/plasma - thermal_conductivity = 0.04 - canSmoothWith = list(/turf/simulated/wall/mineral/plasma, /obj/structure/falsewall/plasma) - -/turf/simulated/wall/mineral/plasma/attackby(obj/item/W as obj, mob/user as mob) - if(is_hot(W) > 300)//If the temperature of the object is over 300, then ignite - message_admins("Plasma wall ignited by [key_name_admin(user)] in ([x], [y], [z] - JMP)",0,1) - log_game("Plasma wall ignited by [key_name(user)] in ([x], [y], [z])") - investigate_log("was ignited by [key_name(user)]","atmos") - ignite(is_hot(W)) - return - ..() - -/turf/simulated/wall/mineral/plasma/welder_act(mob/user, obj/item/I) - if(I.tool_enabled) - ignite(2500) //The number's big enough - user.visible_message("[user] sets [src] on fire!",\ - "[src] disintegrates into a cloud of plasma!",\ - "You hear a 'whoompf' and a roar.") - message_admins("Plasma wall ignited by [key_name_admin(user)] in ([x], [y], [z] - JMP)",0,1) - log_game("Plasma wall ignited by [key_name(user)] in ([x], [y], [z])") - investigate_log("was ignited by [key_name(user)]","atmos") - -/turf/simulated/wall/mineral/plasma/proc/PlasmaBurn(temperature) - new girder_type(src) - ChangeTurf(/turf/simulated/floor) - atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, 400) - -/turf/simulated/wall/mineral/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)//Doesn't fucking work because walls don't interact with air :( - ..() - if(exposed_temperature > 300) - PlasmaBurn(exposed_temperature) - -/turf/simulated/wall/mineral/plasma/proc/ignite(exposed_temperature) - if(exposed_temperature > 300) - PlasmaBurn(exposed_temperature) - -/turf/simulated/wall/mineral/plasma/bullet_act(var/obj/item/projectile/Proj) - if(Proj.damage == 0)//lasertag guns and so on don't set off plasma anymore. can't use nodamage here because lasertag guns actually don't have it. - return - if(istype(Proj,/obj/item/projectile/beam)) - PlasmaBurn(2500) - else if(istype(Proj,/obj/item/projectile/ion)) - PlasmaBurn(500) - ..() - -/turf/simulated/wall/mineral/alien - name = "alien wall" - desc = "A strange-looking alien wall." - icon = 'icons/turf/walls/plasma_wall.dmi' - icon_state = "plasma" - sheet_type = /obj/item/stack/sheet/mineral/abductor - canSmoothWith = list(/turf/simulated/wall/mineral/alien, /obj/structure/falsewall/alien) - -/turf/simulated/wall/mineral/wood - name = "wooden wall" - desc = "A wall with wooden plating. Stiff." - icon = 'icons/turf/walls/wood_wall.dmi' - icon_state = "wood" - sheet_type = /obj/item/stack/sheet/wood - hardness = 70 - explosion_block = 0 - canSmoothWith = list(/turf/simulated/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/simulated/wall/mineral/wood/nonmetal) - -/turf/simulated/wall/mineral/wood/attackby(obj/item/W, mob/user) - if(W.sharp && W.force) - var/duration = (48 / W.force) * 2 //In seconds, for now. - if(istype(W, /obj/item/hatchet) || istype(W, /obj/item/twohanded/fireaxe)) - duration /= 4 //Much better with hatchets and axes. - if(do_after(user, duration * 10, target = src)) //Into deciseconds. - dismantle_wall(FALSE, FALSE) - return - return ..() - -/turf/simulated/wall/mineral/wood/nonmetal - desc = "A solidly wooden wall. It's a bit weaker than a wall made with metal." - girder_type = /obj/structure/barricade/wooden - hardness = 50 - canSmoothWith = list(/turf/simulated/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/simulated/wall/mineral/wood/nonmetal) - -/turf/simulated/wall/mineral/iron - name = "rough metal wall" - desc = "A wall with rough metal plating." - icon = 'icons/turf/walls/iron_wall.dmi' - icon_state = "iron" - sheet_type = /obj/item/stack/rods - sheet_amount = 5 - canSmoothWith = list(/turf/simulated/wall/mineral/iron, /obj/structure/falsewall/iron) - -/turf/simulated/wall/mineral/abductor - name = "alien wall" - desc = "A wall with alien alloy plating." - icon = 'icons/turf/walls/abductor_wall.dmi' - icon_state = "abductor" - smooth = SMOOTH_TRUE|SMOOTH_DIAGONAL - sheet_type = /obj/item/stack/sheet/mineral/abductor - explosion_block = 3 - canSmoothWith = list(/turf/simulated/wall/mineral/abductor, /obj/structure/falsewall/abductor) - -/////////////////////Titanium walls///////////////////// - -/turf/simulated/wall/mineral/titanium //has to use this path due to how building walls works - name = "wall" - desc = "A light-weight titanium wall used in shuttles." - icon = 'icons/turf/walls/shuttle_wall.dmi' - icon_state = "map-shuttle" - explosion_block = 3 - flags_2 = CHECK_RICOCHET_2 - sheet_type = /obj/item/stack/sheet/mineral/titanium - smooth = SMOOTH_MORE|SMOOTH_DIAGONAL - canSmoothWith = list(/turf/simulated/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/full/shuttle, /obj/structure/shuttle/engine/heater, /obj/structure/falsewall/titanium) - -/turf/simulated/wall/mineral/titanium/nodiagonal - smooth = SMOOTH_MORE - icon_state = "map-shuttle_nd" - -/turf/simulated/wall/mineral/titanium/nosmooth - icon = 'icons/turf/shuttle.dmi' - icon_state = "wall" - smooth = SMOOTH_FALSE - -/turf/simulated/wall/mineral/titanium/overspace - icon_state = "map-overspace" - fixed_underlay = list("space"=1) - -//sub-type to be used for interior shuttle walls -//won't get an underlay of the destination turf on shuttle move -/turf/simulated/wall/mineral/titanium/interior/copyTurf(turf/T) - if(T.type != type) - T.ChangeTurf(type) - if(underlays.len) - T.underlays = underlays - if(T.icon_state != icon_state) - T.icon_state = icon_state - if(T.icon != icon) - T.icon = icon - if(T.color != color) - T.color = color - if(T.dir != dir) - T.dir = dir - T.transform = transform - return T - -/turf/simulated/wall/mineral/titanium/copyTurf(turf/T) - . = ..() - T.transform = transform - -/turf/simulated/wall/mineral/titanium/survival - name = "pod wall" - desc = "An easily-compressable wall used for temporary shelter." - icon = 'icons/turf/walls/survival_pod_walls.dmi' - icon_state = "smooth" - smooth = SMOOTH_MORE|SMOOTH_DIAGONAL - canSmoothWith = list(/turf/simulated/wall/mineral/titanium/survival, /obj/machinery/door/airlock, /obj/structure/window/full, /obj/structure/window/full/reinforced, /obj/structure/window/full/reinforced/tinted, /obj/structure/window/full/shuttle, /obj/structure/shuttle/engine) - -/turf/simulated/wall/mineral/titanium/survival/nodiagonal - smooth = SMOOTH_MORE - -/turf/simulated/wall/mineral/titanium/survival/pod - canSmoothWith = list(/turf/simulated/wall/mineral/titanium/survival, /obj/machinery/door/airlock/survival_pod) - -//undeconstructable type for derelict -//these walls are undeconstructable/unthermitable -/turf/simulated/wall/mineral/titanium/nodecon - name = "russian wall" - desc = "Like regular titanium, but able to deflect capitalist aggressors." - -/turf/simulated/wall/mineral/titanium/nodecon/tileblend - fixed_underlay = list("icon"='icons/turf/floors.dmi', "icon_state"="darkredfull") - -/turf/simulated/wall/mineral/titanium/nodecon/nodiagonal - smooth = SMOOTH_MORE - icon_state = "map-shuttle_nd" - -/turf/simulated/wall/mineral/titanium/nodecon/nosmooth - smooth = SMOOTH_FALSE - icon = 'icons/turf/shuttle.dmi' - icon_state = "wall" - -//properties for derelict sub-type to prevent said deconstruction/thermiting -/turf/simulated/wall/mineral/titanium/nodecon/try_decon(obj/item/I, mob/user, params) - return - -/turf/simulated/wall/mineral/titanium/nodecon/thermitemelt(mob/user as mob, speed) - return - -/turf/simulated/wall/mineral/titanium/nodecon/burn_down() - return - -/////////////////////Plastitanium walls///////////////////// - -/turf/simulated/wall/mineral/plastitanium - name = "wall" - desc = "An evil wall of plasma and titanium." - icon = 'icons/turf/walls/plastitanium_wall.dmi' - icon_state = "map-shuttle" - explosion_block = 4 - sheet_type = /obj/item/stack/sheet/mineral/plastitanium - smooth = SMOOTH_MORE|SMOOTH_DIAGONAL - canSmoothWith = list(/turf/simulated/wall/mineral/plastitanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/shuttle/engine, /obj/structure/falsewall/plastitanium) - -/turf/simulated/wall/mineral/plastitanium/nodiagonal - smooth = SMOOTH_MORE - icon_state = "map-shuttle_nd" - -/turf/simulated/wall/mineral/plastitanium/nosmooth - icon = 'icons/turf/shuttle.dmi' - icon_state = "wall" - smooth = SMOOTH_FALSE - -/turf/simulated/wall/mineral/plastitanium/overspace - icon_state = "map-overspace" - fixed_underlay = list("space"=1) - -/turf/simulated/wall/mineral/plastitanium/coated - name = "coated wall" - max_temperature = INFINITY - icon_state = "map-shuttle_nd" - smooth = SMOOTH_MORE - -/turf/simulated/wall/mineral/plastitanium/coated/Initialize(mapload) - . = ..() - desc += " It seems to have additional plating to protect against heat." - -/turf/simulated/wall/mineral/plastitanium/explosive - var/explosive_wall_group = EXPLOSIVE_WALL_GROUP_SYNDICATE_BASE - icon_state = "map-shuttle_nd" - smooth = SMOOTH_MORE - -/turf/simulated/wall/mineral/plastitanium/explosive/Initialize(mapload) - . = ..() - GLOB.explosive_walls += src - -/turf/simulated/wall/mineral/plastitanium/explosive/Destroy() - GLOB.explosive_walls -= src - return ..() - -/turf/simulated/wall/mineral/plastitanium/explosive/proc/self_destruct() - var/obj/item/bombcore/large/explosive_wall/bombcore = new(get_turf(src)) - bombcore.detonate() - -/turf/simulated/wall/mineral/plastitanium/explosive/ex_act(severity) - return - -//have to copypaste this code -/turf/simulated/wall/mineral/plastitanium/interior/copyTurf(turf/T) - if(T.type != type) - T.ChangeTurf(type) - if(underlays.len) - T.underlays = underlays - if(T.icon_state != icon_state) - T.icon_state = icon_state - if(T.icon != icon) - T.icon = icon - if(T.color != color) - T.color = color - if(T.dir != dir) - T.dir = dir - T.transform = transform - return T - -/turf/simulated/wall/mineral/plastitanium/copyTurf(turf/T) - . = ..() - T.transform = transform \ No newline at end of file +/turf/simulated/wall/mineral + name = "mineral wall" + desc = "This shouldn't exist" + icon_state = "" + var/last_event = 0 + var/active = null + canSmoothWith = null + smooth = SMOOTH_TRUE + +/turf/simulated/wall/mineral/gold + name = "gold wall" + desc = "A wall with gold plating. Swag!" + icon = 'icons/turf/walls/gold_wall.dmi' + icon_state = "gold" + sheet_type = /obj/item/stack/sheet/mineral/gold + explosion_block = 0 //gold is a soft metal you dingus. + canSmoothWith = list(/turf/simulated/wall/mineral/gold, /obj/structure/falsewall/gold) + +/turf/simulated/wall/mineral/silver + name = "silver wall" + desc = "A wall with silver plating. Shiny!" + icon = 'icons/turf/walls/silver_wall.dmi' + icon_state = "silver" + sheet_type = /obj/item/stack/sheet/mineral/silver + canSmoothWith = list(/turf/simulated/wall/mineral/silver, /obj/structure/falsewall/silver) + +/turf/simulated/wall/mineral/diamond + name = "diamond wall" + desc = "A wall with diamond plating. You monster." + icon = 'icons/turf/walls/diamond_wall.dmi' + icon_state = "diamond" + sheet_type = /obj/item/stack/sheet/mineral/diamond + explosion_block = 3 + canSmoothWith = list(/turf/simulated/wall/mineral/diamond, /obj/structure/falsewall/diamond) + +/turf/simulated/wall/mineral/bananium + name = "bananium wall" + desc = "A wall with bananium plating. Honk!" + icon = 'icons/turf/walls/bananium_wall.dmi' + icon_state = "bananium" + sheet_type = /obj/item/stack/sheet/mineral/bananium + canSmoothWith = list(/turf/simulated/wall/mineral/bananium, /obj/structure/falsewall/bananium) + +/turf/simulated/wall/mineral/sandstone + name = "sandstone wall" + desc = "A wall with sandstone plating." + icon = 'icons/turf/walls/sandstone_wall.dmi' + icon_state = "sandstone" + sheet_type = /obj/item/stack/sheet/mineral/sandstone + explosion_block = 0 + canSmoothWith = list(/turf/simulated/wall/mineral/sandstone, /obj/structure/falsewall/sandstone) + +/turf/simulated/wall/mineral/uranium + name = "uranium wall" + desc = "A wall with uranium plating. This is probably a bad idea." + icon = 'icons/turf/walls/uranium_wall.dmi' + icon_state = "uranium" + sheet_type = /obj/item/stack/sheet/mineral/uranium + canSmoothWith = list(/turf/simulated/wall/mineral/uranium, /obj/structure/falsewall/uranium) + +/turf/simulated/wall/mineral/uranium/proc/radiate() + if(!active) + if(world.time > last_event+15) + active = 1 + for(var/mob/living/L in range(3,src)) + L.apply_effect(12,IRRADIATE,0) + for(var/turf/simulated/wall/mineral/uranium/T in range(3,src)) + T.radiate() + last_event = world.time + active = null + return + return + +/turf/simulated/wall/mineral/uranium/attack_hand(mob/user as mob) + radiate() + ..() + +/turf/simulated/wall/mineral/uranium/attackby(obj/item/W as obj, mob/user as mob, params) + radiate() + ..() + +/turf/simulated/wall/mineral/uranium/Bumped(AM as mob|obj) + radiate() + ..() + +/turf/simulated/wall/mineral/plasma + name = "plasma wall" + desc = "A wall with plasma plating. This is definately a bad idea." + icon = 'icons/turf/walls/plasma_wall.dmi' + icon_state = "plasma" + sheet_type = /obj/item/stack/sheet/mineral/plasma + thermal_conductivity = 0.04 + canSmoothWith = list(/turf/simulated/wall/mineral/plasma, /obj/structure/falsewall/plasma) + +/turf/simulated/wall/mineral/plasma/attackby(obj/item/W as obj, mob/user as mob) + if(is_hot(W) > 300)//If the temperature of the object is over 300, then ignite + message_admins("Plasma wall ignited by [key_name_admin(user)] in ([x], [y], [z] - JMP)",0,1) + log_game("Plasma wall ignited by [key_name(user)] in ([x], [y], [z])") + investigate_log("was ignited by [key_name(user)]","atmos") + ignite(is_hot(W)) + return + ..() + +/turf/simulated/wall/mineral/plasma/welder_act(mob/user, obj/item/I) + if(I.tool_enabled) + ignite(2500) //The number's big enough + user.visible_message("[user] sets [src] on fire!",\ + "[src] disintegrates into a cloud of plasma!",\ + "You hear a 'whoompf' and a roar.") + message_admins("Plasma wall ignited by [key_name_admin(user)] in ([x], [y], [z] - JMP)",0,1) + log_game("Plasma wall ignited by [key_name(user)] in ([x], [y], [z])") + investigate_log("was ignited by [key_name(user)]","atmos") + +/turf/simulated/wall/mineral/plasma/proc/PlasmaBurn(temperature) + new girder_type(src) + ChangeTurf(/turf/simulated/floor) + atmos_spawn_air(LINDA_SPAWN_HEAT | LINDA_SPAWN_TOXINS, 400) + +/turf/simulated/wall/mineral/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)//Doesn't fucking work because walls don't interact with air :( + ..() + if(exposed_temperature > 300) + PlasmaBurn(exposed_temperature) + +/turf/simulated/wall/mineral/plasma/proc/ignite(exposed_temperature) + if(exposed_temperature > 300) + PlasmaBurn(exposed_temperature) + +/turf/simulated/wall/mineral/plasma/bullet_act(var/obj/item/projectile/Proj) + if(Proj.damage == 0)//lasertag guns and so on don't set off plasma anymore. can't use nodamage here because lasertag guns actually don't have it. + return + if(istype(Proj,/obj/item/projectile/beam)) + PlasmaBurn(2500) + else if(istype(Proj,/obj/item/projectile/ion)) + PlasmaBurn(500) + ..() + +/turf/simulated/wall/mineral/alien + name = "alien wall" + desc = "A strange-looking alien wall." + icon = 'icons/turf/walls/plasma_wall.dmi' + icon_state = "plasma" + sheet_type = /obj/item/stack/sheet/mineral/abductor + canSmoothWith = list(/turf/simulated/wall/mineral/alien, /obj/structure/falsewall/alien) + +/turf/simulated/wall/mineral/wood + name = "wooden wall" + desc = "A wall with wooden plating. Stiff." + icon = 'icons/turf/walls/wood_wall.dmi' + icon_state = "wood" + sheet_type = /obj/item/stack/sheet/wood + hardness = 70 + explosion_block = 0 + canSmoothWith = list(/turf/simulated/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/simulated/wall/mineral/wood/nonmetal) + +/turf/simulated/wall/mineral/wood/attackby(obj/item/W, mob/user) + if(W.sharp && W.force) + var/duration = (48 / W.force) * 2 //In seconds, for now. + if(istype(W, /obj/item/hatchet) || istype(W, /obj/item/twohanded/fireaxe)) + duration /= 4 //Much better with hatchets and axes. + if(do_after(user, duration * 10, target = src)) //Into deciseconds. + dismantle_wall(FALSE, FALSE) + return + return ..() + +/turf/simulated/wall/mineral/wood/nonmetal + desc = "A solidly wooden wall. It's a bit weaker than a wall made with metal." + girder_type = /obj/structure/barricade/wooden + hardness = 50 + canSmoothWith = list(/turf/simulated/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/simulated/wall/mineral/wood/nonmetal) + +/turf/simulated/wall/mineral/iron + name = "rough metal wall" + desc = "A wall with rough metal plating." + icon = 'icons/turf/walls/iron_wall.dmi' + icon_state = "iron" + sheet_type = /obj/item/stack/rods + sheet_amount = 5 + canSmoothWith = list(/turf/simulated/wall/mineral/iron, /obj/structure/falsewall/iron) + +/turf/simulated/wall/mineral/abductor + name = "alien wall" + desc = "A wall with alien alloy plating." + icon = 'icons/turf/walls/abductor_wall.dmi' + icon_state = "abductor" + smooth = SMOOTH_TRUE|SMOOTH_DIAGONAL + sheet_type = /obj/item/stack/sheet/mineral/abductor + explosion_block = 3 + canSmoothWith = list(/turf/simulated/wall/mineral/abductor, /obj/structure/falsewall/abductor) + +/////////////////////Titanium walls///////////////////// + +/turf/simulated/wall/mineral/titanium //has to use this path due to how building walls works + name = "wall" + desc = "A light-weight titanium wall used in shuttles." + icon = 'icons/turf/walls/shuttle_wall.dmi' + icon_state = "map-shuttle" + explosion_block = 3 + flags_2 = CHECK_RICOCHET_2 + sheet_type = /obj/item/stack/sheet/mineral/titanium + smooth = SMOOTH_MORE|SMOOTH_DIAGONAL + canSmoothWith = list(/turf/simulated/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/full/shuttle, /obj/structure/shuttle/engine/heater, /obj/structure/falsewall/titanium) + +/turf/simulated/wall/mineral/titanium/nodiagonal + smooth = SMOOTH_MORE + icon_state = "map-shuttle_nd" + +/turf/simulated/wall/mineral/titanium/nosmooth + icon = 'icons/turf/shuttle.dmi' + icon_state = "wall" + smooth = SMOOTH_FALSE + +/turf/simulated/wall/mineral/titanium/overspace + icon_state = "map-overspace" + fixed_underlay = list("space"=1) + +//sub-type to be used for interior shuttle walls +//won't get an underlay of the destination turf on shuttle move +/turf/simulated/wall/mineral/titanium/interior/copyTurf(turf/T) + if(T.type != type) + T.ChangeTurf(type) + if(underlays.len) + T.underlays = underlays + if(T.icon_state != icon_state) + T.icon_state = icon_state + if(T.icon != icon) + T.icon = icon + if(T.color != color) + T.color = color + if(T.dir != dir) + T.dir = dir + T.transform = transform + return T + +/turf/simulated/wall/mineral/titanium/copyTurf(turf/T) + . = ..() + T.transform = transform + +/turf/simulated/wall/mineral/titanium/survival + name = "pod wall" + desc = "An easily-compressable wall used for temporary shelter." + icon = 'icons/turf/walls/survival_pod_walls.dmi' + icon_state = "smooth" + smooth = SMOOTH_MORE|SMOOTH_DIAGONAL + canSmoothWith = list(/turf/simulated/wall/mineral/titanium/survival, /obj/machinery/door/airlock, /obj/structure/window/full, /obj/structure/window/full/reinforced, /obj/structure/window/full/reinforced/tinted, /obj/structure/window/full/shuttle, /obj/structure/shuttle/engine) + +/turf/simulated/wall/mineral/titanium/survival/nodiagonal + smooth = SMOOTH_MORE + +/turf/simulated/wall/mineral/titanium/survival/pod + canSmoothWith = list(/turf/simulated/wall/mineral/titanium/survival, /obj/machinery/door/airlock/survival_pod) + +//undeconstructable type for derelict +//these walls are undeconstructable/unthermitable +/turf/simulated/wall/mineral/titanium/nodecon + name = "russian wall" + desc = "Like regular titanium, but able to deflect capitalist aggressors." + +/turf/simulated/wall/mineral/titanium/nodecon/tileblend + fixed_underlay = list("icon"='icons/turf/floors.dmi', "icon_state"="darkredfull") + +/turf/simulated/wall/mineral/titanium/nodecon/nodiagonal + smooth = SMOOTH_MORE + icon_state = "map-shuttle_nd" + +/turf/simulated/wall/mineral/titanium/nodecon/nosmooth + smooth = SMOOTH_FALSE + icon = 'icons/turf/shuttle.dmi' + icon_state = "wall" + +//properties for derelict sub-type to prevent said deconstruction/thermiting +/turf/simulated/wall/mineral/titanium/nodecon/try_decon(obj/item/I, mob/user, params) + return + +/turf/simulated/wall/mineral/titanium/nodecon/thermitemelt(mob/user as mob, speed) + return + +/turf/simulated/wall/mineral/titanium/nodecon/burn_down() + return + +/////////////////////Plastitanium walls///////////////////// + +/turf/simulated/wall/mineral/plastitanium + name = "wall" + desc = "An evil wall of plasma and titanium." + icon = 'icons/turf/walls/plastitanium_wall.dmi' + icon_state = "map-shuttle" + explosion_block = 4 + sheet_type = /obj/item/stack/sheet/mineral/plastitanium + smooth = SMOOTH_MORE|SMOOTH_DIAGONAL + canSmoothWith = list(/turf/simulated/wall/mineral/plastitanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/shuttle/engine, /obj/structure/falsewall/plastitanium) + +/turf/simulated/wall/mineral/plastitanium/nodiagonal + smooth = SMOOTH_MORE + icon_state = "map-shuttle_nd" + +/turf/simulated/wall/mineral/plastitanium/nosmooth + icon = 'icons/turf/shuttle.dmi' + icon_state = "wall" + smooth = SMOOTH_FALSE + +/turf/simulated/wall/mineral/plastitanium/overspace + icon_state = "map-overspace" + fixed_underlay = list("space"=1) + +/turf/simulated/wall/mineral/plastitanium/coated + name = "coated wall" + max_temperature = INFINITY + icon_state = "map-shuttle_nd" + smooth = SMOOTH_MORE + +/turf/simulated/wall/mineral/plastitanium/coated/Initialize(mapload) + . = ..() + desc += " It seems to have additional plating to protect against heat." + +/turf/simulated/wall/mineral/plastitanium/explosive + var/explosive_wall_group = EXPLOSIVE_WALL_GROUP_SYNDICATE_BASE + icon_state = "map-shuttle_nd" + smooth = SMOOTH_MORE + +/turf/simulated/wall/mineral/plastitanium/explosive/Initialize(mapload) + . = ..() + GLOB.explosive_walls += src + +/turf/simulated/wall/mineral/plastitanium/explosive/Destroy() + GLOB.explosive_walls -= src + return ..() + +/turf/simulated/wall/mineral/plastitanium/explosive/proc/self_destruct() + var/obj/item/bombcore/large/explosive_wall/bombcore = new(get_turf(src)) + bombcore.detonate() + +/turf/simulated/wall/mineral/plastitanium/explosive/ex_act(severity) + return + +//have to copypaste this code +/turf/simulated/wall/mineral/plastitanium/interior/copyTurf(turf/T) + if(T.type != type) + T.ChangeTurf(type) + if(underlays.len) + T.underlays = underlays + if(T.icon_state != icon_state) + T.icon_state = icon_state + if(T.icon != icon) + T.icon = icon + if(T.color != color) + T.color = color + if(T.dir != dir) + T.dir = dir + T.transform = transform + return T + +/turf/simulated/wall/mineral/plastitanium/copyTurf(turf/T) + . = ..() + T.transform = transform diff --git a/code/game/turfs/simulated/walls_misc.dm b/code/game/turfs/simulated/walls_misc.dm index b2d6b40c123..e43ea15f53f 100644 --- a/code/game/turfs/simulated/walls_misc.dm +++ b/code/game/turfs/simulated/walls_misc.dm @@ -1,149 +1,149 @@ -/turf/simulated/wall/cult - name = "runed metal wall" - desc = "A cold metal wall engraved with indecipherable symbols. Studying them causes your head to pound." - icon = 'icons/turf/walls/cult_wall.dmi' - icon_state = "cult" - builtin_sheet = null - canSmoothWith = null - smooth = SMOOTH_FALSE - sheet_type = /obj/item/stack/sheet/runed_metal - sheet_amount = 1 - girder_type = /obj/structure/girder/cult - -/turf/simulated/wall/cult/New() - ..() - if(SSticker.mode)//game hasn't started offically don't do shit.. - new /obj/effect/temp_visual/cult/turf(src) - icon_state = SSticker.cultdat.cult_wall_icon_state - -/turf/simulated/wall/cult/artificer - name = "runed stone wall" - desc = "A cold stone wall engraved with indecipherable symbols. Studying them causes your head to pound." - -/turf/simulated/wall/cult/artificer/break_wall() - new /obj/effect/temp_visual/cult/turf(get_turf(src)) - return null //excuse me we want no runed metal here - -/turf/simulated/wall/cult/artificer/devastate_wall() - new /obj/effect/temp_visual/cult/turf(get_turf(src)) - -/turf/simulated/wall/cult/narsie_act() - return - -/turf/simulated/wall/cult/devastate_wall() - new sheet_type(get_turf(src), sheet_amount) - -/turf/simulated/wall/rust - name = "rusted wall" - desc = "A rusted metal wall." - icon = 'icons/turf/walls/rusty_wall.dmi' - icon_state = "arust" - -/turf/simulated/wall/r_wall/rust - name = "rusted reinforced wall" - desc = "A huge chunk of rusted reinforced metal." - icon = 'icons/turf/walls/rusty_reinforced_wall.dmi' - icon_state = "rrust" - -/turf/simulated/wall/r_wall/coated //Coated for heat resistance - name = "coated reinforced wall" - desc = "A huge chunk of reinforced metal used to seperate rooms. It seems to have additional plating to protect against heat." - icon = 'icons/turf/walls/coated_reinforced_wall.dmi' - max_temperature = INFINITY - -//Clockwork walls -/turf/simulated/wall/clockwork - name = "clockwork wall" - desc = "A huge chunk of warm metal. The clanging of machinery emanates from within." - explosion_block = 2 - hardness = 10 - slicing_duration = 80 - sheet_type = /obj/item/stack/tile/brass - sheet_amount = 1 - girder_type = /obj/structure/clockwork/wall_gear - baseturf = /turf/simulated/floor/clockwork/reebe - var/heated - var/obj/effect/clockwork/overlay/wall/realappearance - -/turf/simulated/wall/clockwork/Initialize() - . = ..() - new /obj/effect/temp_visual/ratvar/wall(src) - new /obj/effect/temp_visual/ratvar/beam(src) - realappearance = new /obj/effect/clockwork/overlay/wall(src) - realappearance.linked = src - -/turf/simulated/wall/clockwork/Destroy() - if(realappearance) - qdel(realappearance) - realappearance = null - - return ..() - -/turf/simulated/wall/clockwork/ReplaceWithLattice() - ..() - for(var/obj/structure/lattice/L in src) - L.ratvar_act() - -/turf/simulated/wall/clockwork/narsie_act() - ..() - if(istype(src, /turf/simulated/wall/clockwork)) //if we haven't changed type - var/previouscolor = color - color = "#960000" - animate(src, color = previouscolor, time = 8) - addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - -/turf/simulated/wall/clockwork/dismantle_wall(devastated=0, explode=0) - if(devastated) - devastate_wall() - ChangeTurf(baseturf) - else - playsound(src, 'sound/items/welder.ogg', 100, 1) - var/newgirder = break_wall() - if(newgirder) //maybe we want a gear! - transfer_fingerprints_to(newgirder) - ChangeTurf(baseturf) - - for(var/obj/O in src) //Eject contents! - if(istype(O, /obj/structure/sign/poster)) - var/obj/structure/sign/poster/P = O - P.roll_and_drop(src) - else - O.forceMove(src) - -/turf/simulated/wall/clockwork/devastate_wall() - for(var/i in 1 to 2) - new/obj/item/clockwork/alloy_shards/large(src) - for(var/i in 1 to 2) - new/obj/item/clockwork/alloy_shards/medium(src) - for(var/i in 1 to 3) - new/obj/item/clockwork/alloy_shards/small(src) - -/turf/simulated/wall/clockwork/attack_hulk(mob/living/user, does_attack_animation = 0) - ..() - if(heated) - to_chat(user, "The wall is searing hot to the touch!") - user.adjustFireLoss(5) - playsound(src, 'sound/machines/fryer/deep_fryer_emerge.ogg', 50, TRUE) - -/turf/simulated/wall/clockwork/mech_melee_attack(obj/mecha/M) - ..() - if(heated) - to_chat(M.occupant, "The wall's intense heat completely reflects your [M.name]'s attack!") - M.take_damage(20, BURN) - -/turf/simulated/wall/clockwork/proc/turn_up_the_heat() - if(!heated) - name = "superheated [name]" - visible_message("[src] sizzles with heat!") - playsound(src, 'sound/machines/fryer/deep_fryer_emerge.ogg', 50, TRUE) - heated = TRUE - hardness = -100 //Lower numbers are tougher, so this makes the wall essentially impervious to smashing - slicing_duration = 150 - animate(realappearance, color = "#FFC3C3", time = 5) - else - name = initial(name) - visible_message("[src] cools down.") - heated = FALSE - hardness = initial(hardness) - slicing_duration = initial(slicing_duration) - animate(realappearance, color = initial(realappearance.color), time = 25) +/turf/simulated/wall/cult + name = "runed metal wall" + desc = "A cold metal wall engraved with indecipherable symbols. Studying them causes your head to pound." + icon = 'icons/turf/walls/cult_wall.dmi' + icon_state = "cult" + builtin_sheet = null + canSmoothWith = null + smooth = SMOOTH_FALSE + sheet_type = /obj/item/stack/sheet/runed_metal + sheet_amount = 1 + girder_type = /obj/structure/girder/cult + +/turf/simulated/wall/cult/New() + ..() + if(SSticker.mode)//game hasn't started offically don't do shit.. + new /obj/effect/temp_visual/cult/turf(src) + icon_state = SSticker.cultdat.cult_wall_icon_state + +/turf/simulated/wall/cult/artificer + name = "runed stone wall" + desc = "A cold stone wall engraved with indecipherable symbols. Studying them causes your head to pound." + +/turf/simulated/wall/cult/artificer/break_wall() + new /obj/effect/temp_visual/cult/turf(get_turf(src)) + return null //excuse me we want no runed metal here + +/turf/simulated/wall/cult/artificer/devastate_wall() + new /obj/effect/temp_visual/cult/turf(get_turf(src)) + +/turf/simulated/wall/cult/narsie_act() + return + +/turf/simulated/wall/cult/devastate_wall() + new sheet_type(get_turf(src), sheet_amount) + +/turf/simulated/wall/rust + name = "rusted wall" + desc = "A rusted metal wall." + icon = 'icons/turf/walls/rusty_wall.dmi' + icon_state = "arust" + +/turf/simulated/wall/r_wall/rust + name = "rusted reinforced wall" + desc = "A huge chunk of rusted reinforced metal." + icon = 'icons/turf/walls/rusty_reinforced_wall.dmi' + icon_state = "rrust" + +/turf/simulated/wall/r_wall/coated //Coated for heat resistance + name = "coated reinforced wall" + desc = "A huge chunk of reinforced metal used to seperate rooms. It seems to have additional plating to protect against heat." + icon = 'icons/turf/walls/coated_reinforced_wall.dmi' + max_temperature = INFINITY + +//Clockwork walls +/turf/simulated/wall/clockwork + name = "clockwork wall" + desc = "A huge chunk of warm metal. The clanging of machinery emanates from within." + explosion_block = 2 + hardness = 10 + slicing_duration = 80 + sheet_type = /obj/item/stack/tile/brass + sheet_amount = 1 + girder_type = /obj/structure/clockwork/wall_gear + baseturf = /turf/simulated/floor/clockwork/reebe + var/heated + var/obj/effect/clockwork/overlay/wall/realappearance + +/turf/simulated/wall/clockwork/Initialize() + . = ..() + new /obj/effect/temp_visual/ratvar/wall(src) + new /obj/effect/temp_visual/ratvar/beam(src) + realappearance = new /obj/effect/clockwork/overlay/wall(src) + realappearance.linked = src + +/turf/simulated/wall/clockwork/Destroy() + if(realappearance) + qdel(realappearance) + realappearance = null + + return ..() + +/turf/simulated/wall/clockwork/ReplaceWithLattice() + ..() + for(var/obj/structure/lattice/L in src) + L.ratvar_act() + +/turf/simulated/wall/clockwork/narsie_act() + ..() + if(istype(src, /turf/simulated/wall/clockwork)) //if we haven't changed type + var/previouscolor = color + color = "#960000" + animate(src, color = previouscolor, time = 8) + addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) + +/turf/simulated/wall/clockwork/dismantle_wall(devastated=0, explode=0) + if(devastated) + devastate_wall() + ChangeTurf(baseturf) + else + playsound(src, 'sound/items/welder.ogg', 100, 1) + var/newgirder = break_wall() + if(newgirder) //maybe we want a gear! + transfer_fingerprints_to(newgirder) + ChangeTurf(baseturf) + + for(var/obj/O in src) //Eject contents! + if(istype(O, /obj/structure/sign/poster)) + var/obj/structure/sign/poster/P = O + P.roll_and_drop(src) + else + O.forceMove(src) + +/turf/simulated/wall/clockwork/devastate_wall() + for(var/i in 1 to 2) + new/obj/item/clockwork/alloy_shards/large(src) + for(var/i in 1 to 2) + new/obj/item/clockwork/alloy_shards/medium(src) + for(var/i in 1 to 3) + new/obj/item/clockwork/alloy_shards/small(src) + +/turf/simulated/wall/clockwork/attack_hulk(mob/living/user, does_attack_animation = 0) + ..() + if(heated) + to_chat(user, "The wall is searing hot to the touch!") + user.adjustFireLoss(5) + playsound(src, 'sound/machines/fryer/deep_fryer_emerge.ogg', 50, TRUE) + +/turf/simulated/wall/clockwork/mech_melee_attack(obj/mecha/M) + ..() + if(heated) + to_chat(M.occupant, "The wall's intense heat completely reflects your [M.name]'s attack!") + M.take_damage(20, BURN) + +/turf/simulated/wall/clockwork/proc/turn_up_the_heat() + if(!heated) + name = "superheated [name]" + visible_message("[src] sizzles with heat!") + playsound(src, 'sound/machines/fryer/deep_fryer_emerge.ogg', 50, TRUE) + heated = TRUE + hardness = -100 //Lower numbers are tougher, so this makes the wall essentially impervious to smashing + slicing_duration = 150 + animate(realappearance, color = "#FFC3C3", time = 5) + else + name = initial(name) + visible_message("[src] cools down.") + heated = FALSE + hardness = initial(hardness) + slicing_duration = initial(slicing_duration) + animate(realappearance, color = initial(realappearance.color), time = 25) diff --git a/code/game/turfs/simulated/walls_reinforced.dm b/code/game/turfs/simulated/walls_reinforced.dm index 4c211291ae0..502803bc7d3 100644 --- a/code/game/turfs/simulated/walls_reinforced.dm +++ b/code/game/turfs/simulated/walls_reinforced.dm @@ -1,238 +1,238 @@ -/turf/simulated/wall/r_wall - name = "reinforced wall" - desc = "A huge chunk of reinforced metal used to separate rooms." - icon = 'icons/turf/walls/reinforced_wall.dmi' - icon_state = "r_wall" - opacity = 1 - density = 1 - explosion_block = 2 - damage_cap = 600 - max_temperature = 6000 - hardness = 10 - sheet_type = /obj/item/stack/sheet/plasteel - sheet_amount = 1 - girder_type = /obj/structure/girder/reinforced - can_dismantle_with_welder = FALSE - - var/d_state = RWALL_INTACT - var/can_be_reinforced = 1 - -/turf/simulated/wall/r_wall/examine(mob/user) - . = ..() - switch(d_state) - if(RWALL_INTACT) - . += "The outer grille is fully intact." - if(RWALL_SUPPORT_LINES) - . += "The outer grille has been cut, and the support lines are screwed securely to the outer cover." - if(RWALL_COVER) - . += "The support lines have been unscrewed, and the metal cover is welded firmly in place." - if(RWALL_CUT_COVER) - . += "The metal cover has been sliced through, and is connected loosely to the girder." - if(RWALL_BOLTS) - . += "The outer cover has been pried away, and the bolts anchoring the support rods are wrenched in place." - if(RWALL_SUPPORT_RODS) - . += "The bolts anchoring the support rods have been loosened, but are still welded firmly to the girder." - if(RWALL_SHEATH) - . += "The support rods have been sliced through, and the outer sheath is connected loosely to the girder." - -/turf/simulated/wall/r_wall/attackby(obj/item/I, mob/user, params) - if(d_state == RWALL_COVER && istype(I, /obj/item/gun/energy/plasmacutter)) - to_chat(user, "You begin slicing through the metal cover...") - if(I.use_tool(src, user, 40, volume = I.tool_volume) && d_state == RWALL_COVER) - d_state = RWALL_CUT_COVER - update_icon() - to_chat(user, "You press firmly on the cover, dislodging it.") - return - else if(RWALL_SUPPORT_RODS && istype(I, /obj/item/gun/energy/plasmacutter)) - to_chat(user, "You begin slicing through the support rods...") - if(I.use_tool(src, user, 70, volume = I.tool_volume) && d_state == RWALL_SUPPORT_RODS) - d_state = RWALL_SHEATH - update_icon() - return - else if(d_state == RWALL_SUPPORT_LINES && istype(I, /obj/item/stack/rods)) - var/obj/item/stack/S = I - if(S.use(1)) - d_state = RWALL_INTACT - update_icon() - to_chat(user, "You replace the outer grille.") - else - to_chat(user, "You don't have enough rods for that!") - return - else if(d_state) - // Repairing - if(istype(I, /obj/item/stack/sheet/metal)) - var/obj/item/stack/sheet/metal/MS = I - to_chat(user, "You begin patching-up the wall with [MS]...") - if(do_after(user, max(20 * d_state, 100) * MS.toolspeed, target = src) && d_state) - if(!MS.use(1)) - to_chat(user, "You don't have enough [MS.name] for that!") - return - d_state = RWALL_INTACT - update_icon() - queue_smooth_neighbors(src) - to_chat(user, "You repair the last of the damage.") - return - - else if(istype(I, /obj/item/stack/sheet/plasteel)) - var/obj/item/stack/sheet/plasteel/PS = I - if(!can_be_reinforced) - to_chat(user, "The wall is already coated!") - return - to_chat(user, "You begin adding an additional layer of coating to the wall with [PS]...") - if(do_after(user, 40 * PS.toolspeed, target = src) && !d_state) - if(!PS.use(2)) - to_chat(user, "You don't have enough [PS.name] for that!") - return - to_chat(user, "You add an additional layer of coating to the wall.") - ChangeTurf(/turf/simulated/wall/r_wall/coated) - update_icon() - queue_smooth_neighbors(src) - can_be_reinforced = FALSE - return - else - return ..() - -/turf/simulated/wall/r_wall/welder_act(mob/user, obj/item/I) - if(thermite && I.use_tool(src, user, volume = I.tool_volume)) - thermitemelt(user) - return TRUE - if(!(d_state in list(RWALL_COVER, RWALL_SUPPORT_RODS, RWALL_CUT_COVER))) - return ..() - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(d_state == RWALL_COVER) - to_chat(user, "You begin slicing through the metal cover...") - if(I.use_tool(src, user, 60, volume = I.tool_volume) && d_state == RWALL_COVER) - d_state = RWALL_CUT_COVER - to_chat(user, "You press firmly on the cover, dislodging it.") - else if(d_state == RWALL_SUPPORT_RODS) - to_chat(user, "You begin slicing through the support rods...") - if(I.use_tool(src, user, 100, volume = I.tool_volume) && d_state == RWALL_SUPPORT_RODS) - d_state = RWALL_SHEATH - else if(d_state == RWALL_CUT_COVER) - to_chat(user, "You begin welding the metal cover back to the frame...") - if(I.use_tool(src, user, 60, volume = I.tool_volume) && d_state == RWALL_CUT_COVER) - to_chat(user, "The metal cover has been welded securely to the frame.") - d_state = RWALL_COVER - update_icon() - -/turf/simulated/wall/r_wall/crowbar_act(mob/user, obj/item/I) - if(!(d_state in list(RWALL_CUT_COVER, RWALL_SHEATH, RWALL_BOLTS))) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - switch(d_state) - if(RWALL_CUT_COVER) - to_chat(user, "You struggle to pry off the cover...") - if(!I.use_tool(src, user, 100, volume = I.tool_volume) || d_state != RWALL_CUT_COVER) - return - d_state = RWALL_BOLTS - to_chat(user, "You pry off the cover.") - if(RWALL_SHEATH) - to_chat(user, "You struggle to pry off the outer sheath...") - if(!I.use_tool(src, user, 100, volume = I.tool_volume) || d_state != RWALL_SHEATH) - return - to_chat(user, "You pry off the outer sheath.") - dismantle_wall() - return - if(RWALL_BOLTS) - to_chat(user, "You start to pry the cover back into place...") - playsound(src, I.usesound, 100, 1) - if(!I.use_tool(src, user, 20, volume = I.tool_volume) || d_state != RWALL_BOLTS) - return - d_state = RWALL_CUT_COVER - to_chat(user, "The metal cover has been pried back into place.") - update_icon() - -/turf/simulated/wall/r_wall/screwdriver_act(mob/user, obj/item/I) - if(d_state != RWALL_SUPPORT_LINES && d_state != RWALL_COVER) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - var/state_check = d_state - if(d_state == RWALL_SUPPORT_LINES) - to_chat(user, "You begin unsecuring the support lines...") - else - to_chat(user, "You begin securing the support lines...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state_check != d_state) - return - if(d_state == RWALL_SUPPORT_LINES) - d_state = RWALL_COVER - to_chat(user, "You unsecure the support lines.") - else - d_state = RWALL_SUPPORT_LINES - to_chat(user, "The support lines have been secured.") - update_icon() - -/turf/simulated/wall/r_wall/wirecutter_act(mob/user, obj/item/I) - if(d_state != RWALL_INTACT) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - d_state = RWALL_SUPPORT_LINES - update_icon() - new /obj/item/stack/rods(src) - to_chat(user, "You cut the outer grille.") - -/turf/simulated/wall/r_wall/wrench_act(mob/user, obj/item/I) - if(d_state != RWALL_BOLTS && d_state != RWALL_SUPPORT_RODS) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - var/state_check = d_state - if(d_state == RWALL_BOLTS) - to_chat(user, "You start loosening the anchoring bolts which secure the support rods to their frame...") - else - to_chat(user, "You start tightening the bolts which secure the support rods to their frame...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state_check != d_state) - return - if(d_state == RWALL_BOLTS) - d_state = RWALL_SUPPORT_RODS - to_chat(user, "You remove the bolts anchoring the support rods.") - else - d_state = RWALL_BOLTS - to_chat(user, "You tighten the bolts anchoring the support rods.") - update_icon() - -/turf/simulated/wall/r_wall/try_destroy(obj/item/I, mob/user, params) - if(istype(I, /obj/item/pickaxe/drill/diamonddrill)) - to_chat(user, "You begin to drill though the wall...") - - if(do_after(user, 800 * I.toolspeed, target = src)) // Diamond drill has 0.25 toolspeed, so 200 - to_chat(user, "Your drill tears through the last of the reinforced plating.") - dismantle_wall() - return TRUE - - if(istype(I, /obj/item/pickaxe/drill/jackhammer)) - to_chat(user, "You begin to disintegrate the wall...") - - if(do_after(user, 1000 * I.toolspeed, target = src)) // Jackhammer has 0.1 toolspeed, so 100 - to_chat(user, "Your sonic jackhammer disintegrates the reinforced plating.") - dismantle_wall() - return TRUE - - -/turf/simulated/wall/r_wall/wall_singularity_pull(current_size) - if(current_size >= STAGE_FIVE) - if(prob(30)) - dismantle_wall() - -/turf/simulated/wall/r_wall/update_icon() - . = ..() - - if(d_state) - icon_state = "r_wall-[d_state]" - smooth = SMOOTH_FALSE - clear_smooth_overlays() - else - smooth = SMOOTH_TRUE - icon_state = "" - -/turf/simulated/wall/r_wall/devastate_wall() - new sheet_type(src, sheet_amount) - new /obj/item/stack/sheet/metal(src, 2) +/turf/simulated/wall/r_wall + name = "reinforced wall" + desc = "A huge chunk of reinforced metal used to separate rooms." + icon = 'icons/turf/walls/reinforced_wall.dmi' + icon_state = "r_wall" + opacity = 1 + density = 1 + explosion_block = 2 + damage_cap = 600 + max_temperature = 6000 + hardness = 10 + sheet_type = /obj/item/stack/sheet/plasteel + sheet_amount = 1 + girder_type = /obj/structure/girder/reinforced + can_dismantle_with_welder = FALSE + + var/d_state = RWALL_INTACT + var/can_be_reinforced = 1 + +/turf/simulated/wall/r_wall/examine(mob/user) + . = ..() + switch(d_state) + if(RWALL_INTACT) + . += "The outer grille is fully intact." + if(RWALL_SUPPORT_LINES) + . += "The outer grille has been cut, and the support lines are screwed securely to the outer cover." + if(RWALL_COVER) + . += "The support lines have been unscrewed, and the metal cover is welded firmly in place." + if(RWALL_CUT_COVER) + . += "The metal cover has been sliced through, and is connected loosely to the girder." + if(RWALL_BOLTS) + . += "The outer cover has been pried away, and the bolts anchoring the support rods are wrenched in place." + if(RWALL_SUPPORT_RODS) + . += "The bolts anchoring the support rods have been loosened, but are still welded firmly to the girder." + if(RWALL_SHEATH) + . += "The support rods have been sliced through, and the outer sheath is connected loosely to the girder." + +/turf/simulated/wall/r_wall/attackby(obj/item/I, mob/user, params) + if(d_state == RWALL_COVER && istype(I, /obj/item/gun/energy/plasmacutter)) + to_chat(user, "You begin slicing through the metal cover...") + if(I.use_tool(src, user, 40, volume = I.tool_volume) && d_state == RWALL_COVER) + d_state = RWALL_CUT_COVER + update_icon() + to_chat(user, "You press firmly on the cover, dislodging it.") + return + else if(RWALL_SUPPORT_RODS && istype(I, /obj/item/gun/energy/plasmacutter)) + to_chat(user, "You begin slicing through the support rods...") + if(I.use_tool(src, user, 70, volume = I.tool_volume) && d_state == RWALL_SUPPORT_RODS) + d_state = RWALL_SHEATH + update_icon() + return + else if(d_state == RWALL_SUPPORT_LINES && istype(I, /obj/item/stack/rods)) + var/obj/item/stack/S = I + if(S.use(1)) + d_state = RWALL_INTACT + update_icon() + to_chat(user, "You replace the outer grille.") + else + to_chat(user, "You don't have enough rods for that!") + return + else if(d_state) + // Repairing + if(istype(I, /obj/item/stack/sheet/metal)) + var/obj/item/stack/sheet/metal/MS = I + to_chat(user, "You begin patching-up the wall with [MS]...") + if(do_after(user, max(20 * d_state, 100) * MS.toolspeed, target = src) && d_state) + if(!MS.use(1)) + to_chat(user, "You don't have enough [MS.name] for that!") + return + d_state = RWALL_INTACT + update_icon() + queue_smooth_neighbors(src) + to_chat(user, "You repair the last of the damage.") + return + + else if(istype(I, /obj/item/stack/sheet/plasteel)) + var/obj/item/stack/sheet/plasteel/PS = I + if(!can_be_reinforced) + to_chat(user, "The wall is already coated!") + return + to_chat(user, "You begin adding an additional layer of coating to the wall with [PS]...") + if(do_after(user, 40 * PS.toolspeed, target = src) && !d_state) + if(!PS.use(2)) + to_chat(user, "You don't have enough [PS.name] for that!") + return + to_chat(user, "You add an additional layer of coating to the wall.") + ChangeTurf(/turf/simulated/wall/r_wall/coated) + update_icon() + queue_smooth_neighbors(src) + can_be_reinforced = FALSE + return + else + return ..() + +/turf/simulated/wall/r_wall/welder_act(mob/user, obj/item/I) + if(thermite && I.use_tool(src, user, volume = I.tool_volume)) + thermitemelt(user) + return TRUE + if(!(d_state in list(RWALL_COVER, RWALL_SUPPORT_RODS, RWALL_CUT_COVER))) + return ..() + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(d_state == RWALL_COVER) + to_chat(user, "You begin slicing through the metal cover...") + if(I.use_tool(src, user, 60, volume = I.tool_volume) && d_state == RWALL_COVER) + d_state = RWALL_CUT_COVER + to_chat(user, "You press firmly on the cover, dislodging it.") + else if(d_state == RWALL_SUPPORT_RODS) + to_chat(user, "You begin slicing through the support rods...") + if(I.use_tool(src, user, 100, volume = I.tool_volume) && d_state == RWALL_SUPPORT_RODS) + d_state = RWALL_SHEATH + else if(d_state == RWALL_CUT_COVER) + to_chat(user, "You begin welding the metal cover back to the frame...") + if(I.use_tool(src, user, 60, volume = I.tool_volume) && d_state == RWALL_CUT_COVER) + to_chat(user, "The metal cover has been welded securely to the frame.") + d_state = RWALL_COVER + update_icon() + +/turf/simulated/wall/r_wall/crowbar_act(mob/user, obj/item/I) + if(!(d_state in list(RWALL_CUT_COVER, RWALL_SHEATH, RWALL_BOLTS))) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + switch(d_state) + if(RWALL_CUT_COVER) + to_chat(user, "You struggle to pry off the cover...") + if(!I.use_tool(src, user, 100, volume = I.tool_volume) || d_state != RWALL_CUT_COVER) + return + d_state = RWALL_BOLTS + to_chat(user, "You pry off the cover.") + if(RWALL_SHEATH) + to_chat(user, "You struggle to pry off the outer sheath...") + if(!I.use_tool(src, user, 100, volume = I.tool_volume) || d_state != RWALL_SHEATH) + return + to_chat(user, "You pry off the outer sheath.") + dismantle_wall() + return + if(RWALL_BOLTS) + to_chat(user, "You start to pry the cover back into place...") + playsound(src, I.usesound, 100, 1) + if(!I.use_tool(src, user, 20, volume = I.tool_volume) || d_state != RWALL_BOLTS) + return + d_state = RWALL_CUT_COVER + to_chat(user, "The metal cover has been pried back into place.") + update_icon() + +/turf/simulated/wall/r_wall/screwdriver_act(mob/user, obj/item/I) + if(d_state != RWALL_SUPPORT_LINES && d_state != RWALL_COVER) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + var/state_check = d_state + if(d_state == RWALL_SUPPORT_LINES) + to_chat(user, "You begin unsecuring the support lines...") + else + to_chat(user, "You begin securing the support lines...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state_check != d_state) + return + if(d_state == RWALL_SUPPORT_LINES) + d_state = RWALL_COVER + to_chat(user, "You unsecure the support lines.") + else + d_state = RWALL_SUPPORT_LINES + to_chat(user, "The support lines have been secured.") + update_icon() + +/turf/simulated/wall/r_wall/wirecutter_act(mob/user, obj/item/I) + if(d_state != RWALL_INTACT) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + d_state = RWALL_SUPPORT_LINES + update_icon() + new /obj/item/stack/rods(src) + to_chat(user, "You cut the outer grille.") + +/turf/simulated/wall/r_wall/wrench_act(mob/user, obj/item/I) + if(d_state != RWALL_BOLTS && d_state != RWALL_SUPPORT_RODS) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + var/state_check = d_state + if(d_state == RWALL_BOLTS) + to_chat(user, "You start loosening the anchoring bolts which secure the support rods to their frame...") + else + to_chat(user, "You start tightening the bolts which secure the support rods to their frame...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || state_check != d_state) + return + if(d_state == RWALL_BOLTS) + d_state = RWALL_SUPPORT_RODS + to_chat(user, "You remove the bolts anchoring the support rods.") + else + d_state = RWALL_BOLTS + to_chat(user, "You tighten the bolts anchoring the support rods.") + update_icon() + +/turf/simulated/wall/r_wall/try_destroy(obj/item/I, mob/user, params) + if(istype(I, /obj/item/pickaxe/drill/diamonddrill)) + to_chat(user, "You begin to drill though the wall...") + + if(do_after(user, 800 * I.toolspeed, target = src)) // Diamond drill has 0.25 toolspeed, so 200 + to_chat(user, "Your drill tears through the last of the reinforced plating.") + dismantle_wall() + return TRUE + + if(istype(I, /obj/item/pickaxe/drill/jackhammer)) + to_chat(user, "You begin to disintegrate the wall...") + + if(do_after(user, 1000 * I.toolspeed, target = src)) // Jackhammer has 0.1 toolspeed, so 100 + to_chat(user, "Your sonic jackhammer disintegrates the reinforced plating.") + dismantle_wall() + return TRUE + + +/turf/simulated/wall/r_wall/wall_singularity_pull(current_size) + if(current_size >= STAGE_FIVE) + if(prob(30)) + dismantle_wall() + +/turf/simulated/wall/r_wall/update_icon() + . = ..() + + if(d_state) + icon_state = "r_wall-[d_state]" + smooth = SMOOTH_FALSE + clear_smooth_overlays() + else + smooth = SMOOTH_TRUE + icon_state = "" + +/turf/simulated/wall/r_wall/devastate_wall() + new sheet_type(src, sheet_amount) + new /obj/item/stack/sheet/metal(src, 2) diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index 1c0bfb167d6..c797601d8e1 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -1,275 +1,275 @@ -/turf/space - icon = 'icons/turf/space.dmi' - name = "\proper space" - icon_state = "0" - - temperature = TCMB - thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT - heat_capacity = HEAT_CAPACITY_VACUUM - - plane = PLANE_SPACE - layer = SPACE_LAYER - light_power = 0.25 - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED - intact = FALSE - - var/destination_z - var/destination_x - var/destination_y - plane = PLANE_SPACE - -/turf/space/Initialize(mapload) - if(!istype(src, /turf/space/transit)) - icon_state = SPACE_ICON_STATE - - var/area/A = loc - if(!IS_DYNAMIC_LIGHTING(src) && IS_DYNAMIC_LIGHTING(A)) - add_overlay(/obj/effect/fullbright) - - if (light_power && light_range) - update_light() - - if (opacity) - has_opaque_atom = TRUE - - return INITIALIZE_HINT_NORMAL - -/turf/space/Destroy(force) - if(force) - . = ..() - else - return QDEL_HINT_LETMELIVE - -/turf/space/BeforeChange() - ..() - var/datum/space_level/S = space_manager.get_zlev(z) - S.remove_from_transit(src) - if(light_sources) // Turn off starlight, if present - set_light(0) - -/turf/space/AfterChange(ignore_air, keep_cabling = FALSE) - ..() - var/datum/space_level/S = space_manager.get_zlev(z) - S.add_to_transit(src) - S.apply_transition(src) - -/turf/space/proc/update_starlight() - if(config.starlight) - for(var/t in RANGE_TURFS(1,src)) //RANGE_TURFS is in code\__HELPERS\game.dm - if(isspaceturf(t)) - //let's NOT update this that much pls - continue - set_light(2) - return - set_light(0) - -/turf/space/attackby(obj/item/C as obj, mob/user as mob, params) - ..() - if(istype(C, /obj/item/stack/rods)) - var/obj/item/stack/rods/R = C - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src) - if(W) - to_chat(user, "There is already a catwalk here!") - return - if(L) - if(R.use(1)) - to_chat(user, "You construct a catwalk.") - playsound(src, 'sound/weapons/genhit.ogg', 50, 1) - new/obj/structure/lattice/catwalk(src) - else - to_chat(user, "You need two rods to build a catwalk!") - return - if(R.use(1)) - to_chat(user, "Constructing support lattice...") - playsound(src, 'sound/weapons/genhit.ogg', 50, 1) - ReplaceWithLattice() - else - to_chat(user, "You need one rod to build a lattice.") - return - - if(istype(C, /obj/item/stack/tile/plasteel)) - var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) - if(L) - var/obj/item/stack/tile/plasteel/S = C - if(S.use(1)) - qdel(L) - playsound(src, 'sound/weapons/genhit.ogg', 50, 1) - to_chat(user, "You build a floor.") - ChangeTurf(/turf/simulated/floor/plating) - else - to_chat(user, "You need one floor tile to build a floor!") - else - to_chat(user, "The plating is going to need some support! Place metal rods first.") - -/turf/space/Entered(atom/movable/A as mob|obj, atom/OL, ignoreRest = 0) - ..() - if((!(A) || !(src in A.locs))) - return - - if(destination_z && destination_x && destination_y) - A.forceMove(locate(destination_x, destination_y, destination_z)) - - if(isliving(A)) - var/mob/living/L = A - if(L.pulling) - var/turf/T = get_step(L.loc,turn(A.dir, 180)) - L.pulling.forceMove(T) - - //now we're on the new z_level, proceed the space drifting - sleep(0)//Let a diagonal move finish, if necessary - A.newtonian_move(A.inertia_dir) - -/turf/space/proc/Sandbox_Spacemove(atom/movable/A as mob|obj) - var/cur_x - var/cur_y - var/next_x - var/next_y - var/target_z - var/list/y_arr - - if(src.x <= 1) - if(istype(A, /obj/effect/meteor)||istype(A, /obj/effect/space_dust)) - qdel(A) - return - - var/list/cur_pos = src.get_global_map_pos() - if(!cur_pos) return - cur_x = cur_pos["x"] - cur_y = cur_pos["y"] - next_x = (--cur_x||global_map.len) - y_arr = global_map[next_x] - target_z = y_arr[cur_y] -/* - //debug - to_chat(world, "Src.z = [src.z] in global map X = [cur_x], Y = [cur_y]") - to_chat(world, "Target Z = [target_z]") - to_chat(world, "Next X = [next_x]") - //debug -*/ - if(target_z) - A.z = target_z - A.x = world.maxx - 2 - spawn (0) - if((A && A.loc)) - A.loc.Entered(A) - else if(src.x >= world.maxx) - if(istype(A, /obj/effect/meteor)) - qdel(A) - return - - var/list/cur_pos = src.get_global_map_pos() - if(!cur_pos) return - cur_x = cur_pos["x"] - cur_y = cur_pos["y"] - next_x = (++cur_x > global_map.len ? 1 : cur_x) - y_arr = global_map[next_x] - target_z = y_arr[cur_y] -/* - //debug - to_chat(world, "Src.z = [src.z] in global map X = [cur_x], Y = [cur_y]") - to_chat(world, "Target Z = [target_z]") - to_chat(world, "Next X = [next_x]") - //debug -*/ - if(target_z) - A.z = target_z - A.x = 3 - spawn (0) - if((A && A.loc)) - A.loc.Entered(A) - else if(src.y <= 1) - if(istype(A, /obj/effect/meteor)) - qdel(A) - return - var/list/cur_pos = src.get_global_map_pos() - if(!cur_pos) return - cur_x = cur_pos["x"] - cur_y = cur_pos["y"] - y_arr = global_map[cur_x] - next_y = (--cur_y||y_arr.len) - target_z = y_arr[next_y] -/* - //debug - to_chat(world, "Src.z = [src.z] in global map X = [cur_x], Y = [cur_y]") - to_chat(world, "Next Y = [next_y]") - to_chat(world, "Target Z = [target_z]") - //debug -*/ - if(target_z) - A.z = target_z - A.y = world.maxy - 2 - spawn (0) - if((A && A.loc)) - A.loc.Entered(A) - - else if(src.y >= world.maxy) - if(istype(A, /obj/effect/meteor)||istype(A, /obj/effect/space_dust)) - qdel(A) - return - var/list/cur_pos = src.get_global_map_pos() - if(!cur_pos) return - cur_x = cur_pos["x"] - cur_y = cur_pos["y"] - y_arr = global_map[cur_x] - next_y = (++cur_y > y_arr.len ? 1 : cur_y) - target_z = y_arr[next_y] -/* - //debug - to_chat(world, "Src.z = [src.z] in global map X = [cur_x], Y = [cur_y]") - to_chat(world, "Next Y = [next_y]") - to_chat(world, "Target Z = [target_z]") - //debug -*/ - if(target_z) - A.z = target_z - A.y = 3 - spawn (0) - if((A && A.loc)) - A.loc.Entered(A) - return - -/turf/space/singularity_act() - return - -/turf/space/can_have_cabling() - if(locate(/obj/structure/lattice/catwalk, src)) - return 1 - return 0 - -/turf/space/proc/set_transition_north(dest_z) - destination_x = x - destination_y = TRANSITION_BORDER_SOUTH + 1 - destination_z = dest_z - -/turf/space/proc/set_transition_south(dest_z) - destination_x = x - destination_y = TRANSITION_BORDER_NORTH - 1 - destination_z = dest_z - -/turf/space/proc/set_transition_east(dest_z) - destination_x = TRANSITION_BORDER_WEST + 1 - destination_y = y - destination_z = dest_z - -/turf/space/proc/set_transition_west(dest_z) - destination_x = TRANSITION_BORDER_EAST - 1 - destination_y = y - destination_z = dest_z - -/turf/space/proc/remove_transitions() - destination_z = initial(destination_z) - -/turf/space/attack_ghost(mob/dead/observer/user) - if(destination_z) - var/turf/T = locate(destination_x, destination_y, destination_z) - user.forceMove(T) - -/turf/space/acid_act(acidpwr, acid_volume) - return 0 - -/turf/space/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) - underlay_appearance.icon = 'icons/turf/space.dmi' - underlay_appearance.icon_state = SPACE_ICON_STATE - underlay_appearance.plane = PLANE_SPACE - return TRUE +/turf/space + icon = 'icons/turf/space.dmi' + name = "\proper space" + icon_state = "0" + + temperature = TCMB + thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT + heat_capacity = HEAT_CAPACITY_VACUUM + + plane = PLANE_SPACE + layer = SPACE_LAYER + light_power = 0.25 + dynamic_lighting = DYNAMIC_LIGHTING_DISABLED + intact = FALSE + + var/destination_z + var/destination_x + var/destination_y + plane = PLANE_SPACE + +/turf/space/Initialize(mapload) + if(!istype(src, /turf/space/transit)) + icon_state = SPACE_ICON_STATE + + var/area/A = loc + if(!IS_DYNAMIC_LIGHTING(src) && IS_DYNAMIC_LIGHTING(A)) + add_overlay(/obj/effect/fullbright) + + if (light_power && light_range) + update_light() + + if (opacity) + has_opaque_atom = TRUE + + return INITIALIZE_HINT_NORMAL + +/turf/space/Destroy(force) + if(force) + . = ..() + else + return QDEL_HINT_LETMELIVE + +/turf/space/BeforeChange() + ..() + var/datum/space_level/S = space_manager.get_zlev(z) + S.remove_from_transit(src) + if(light_sources) // Turn off starlight, if present + set_light(0) + +/turf/space/AfterChange(ignore_air, keep_cabling = FALSE) + ..() + var/datum/space_level/S = space_manager.get_zlev(z) + S.add_to_transit(src) + S.apply_transition(src) + +/turf/space/proc/update_starlight() + if(config.starlight) + for(var/t in RANGE_TURFS(1,src)) //RANGE_TURFS is in code\__HELPERS\game.dm + if(isspaceturf(t)) + //let's NOT update this that much pls + continue + set_light(2) + return + set_light(0) + +/turf/space/attackby(obj/item/C as obj, mob/user as mob, params) + ..() + if(istype(C, /obj/item/stack/rods)) + var/obj/item/stack/rods/R = C + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src) + if(W) + to_chat(user, "There is already a catwalk here!") + return + if(L) + if(R.use(1)) + to_chat(user, "You construct a catwalk.") + playsound(src, 'sound/weapons/genhit.ogg', 50, 1) + new/obj/structure/lattice/catwalk(src) + else + to_chat(user, "You need two rods to build a catwalk!") + return + if(R.use(1)) + to_chat(user, "Constructing support lattice...") + playsound(src, 'sound/weapons/genhit.ogg', 50, 1) + ReplaceWithLattice() + else + to_chat(user, "You need one rod to build a lattice.") + return + + if(istype(C, /obj/item/stack/tile/plasteel)) + var/obj/structure/lattice/L = locate(/obj/structure/lattice, src) + if(L) + var/obj/item/stack/tile/plasteel/S = C + if(S.use(1)) + qdel(L) + playsound(src, 'sound/weapons/genhit.ogg', 50, 1) + to_chat(user, "You build a floor.") + ChangeTurf(/turf/simulated/floor/plating) + else + to_chat(user, "You need one floor tile to build a floor!") + else + to_chat(user, "The plating is going to need some support! Place metal rods first.") + +/turf/space/Entered(atom/movable/A as mob|obj, atom/OL, ignoreRest = 0) + ..() + if((!(A) || !(src in A.locs))) + return + + if(destination_z && destination_x && destination_y) + A.forceMove(locate(destination_x, destination_y, destination_z)) + + if(isliving(A)) + var/mob/living/L = A + if(L.pulling) + var/turf/T = get_step(L.loc,turn(A.dir, 180)) + L.pulling.forceMove(T) + + //now we're on the new z_level, proceed the space drifting + sleep(0)//Let a diagonal move finish, if necessary + A.newtonian_move(A.inertia_dir) + +/turf/space/proc/Sandbox_Spacemove(atom/movable/A as mob|obj) + var/cur_x + var/cur_y + var/next_x + var/next_y + var/target_z + var/list/y_arr + + if(src.x <= 1) + if(istype(A, /obj/effect/meteor)||istype(A, /obj/effect/space_dust)) + qdel(A) + return + + var/list/cur_pos = src.get_global_map_pos() + if(!cur_pos) return + cur_x = cur_pos["x"] + cur_y = cur_pos["y"] + next_x = (--cur_x||global_map.len) + y_arr = global_map[next_x] + target_z = y_arr[cur_y] +/* + //debug + to_chat(world, "Src.z = [src.z] in global map X = [cur_x], Y = [cur_y]") + to_chat(world, "Target Z = [target_z]") + to_chat(world, "Next X = [next_x]") + //debug +*/ + if(target_z) + A.z = target_z + A.x = world.maxx - 2 + spawn (0) + if((A && A.loc)) + A.loc.Entered(A) + else if(src.x >= world.maxx) + if(istype(A, /obj/effect/meteor)) + qdel(A) + return + + var/list/cur_pos = src.get_global_map_pos() + if(!cur_pos) return + cur_x = cur_pos["x"] + cur_y = cur_pos["y"] + next_x = (++cur_x > global_map.len ? 1 : cur_x) + y_arr = global_map[next_x] + target_z = y_arr[cur_y] +/* + //debug + to_chat(world, "Src.z = [src.z] in global map X = [cur_x], Y = [cur_y]") + to_chat(world, "Target Z = [target_z]") + to_chat(world, "Next X = [next_x]") + //debug +*/ + if(target_z) + A.z = target_z + A.x = 3 + spawn (0) + if((A && A.loc)) + A.loc.Entered(A) + else if(src.y <= 1) + if(istype(A, /obj/effect/meteor)) + qdel(A) + return + var/list/cur_pos = src.get_global_map_pos() + if(!cur_pos) return + cur_x = cur_pos["x"] + cur_y = cur_pos["y"] + y_arr = global_map[cur_x] + next_y = (--cur_y||y_arr.len) + target_z = y_arr[next_y] +/* + //debug + to_chat(world, "Src.z = [src.z] in global map X = [cur_x], Y = [cur_y]") + to_chat(world, "Next Y = [next_y]") + to_chat(world, "Target Z = [target_z]") + //debug +*/ + if(target_z) + A.z = target_z + A.y = world.maxy - 2 + spawn (0) + if((A && A.loc)) + A.loc.Entered(A) + + else if(src.y >= world.maxy) + if(istype(A, /obj/effect/meteor)||istype(A, /obj/effect/space_dust)) + qdel(A) + return + var/list/cur_pos = src.get_global_map_pos() + if(!cur_pos) return + cur_x = cur_pos["x"] + cur_y = cur_pos["y"] + y_arr = global_map[cur_x] + next_y = (++cur_y > y_arr.len ? 1 : cur_y) + target_z = y_arr[next_y] +/* + //debug + to_chat(world, "Src.z = [src.z] in global map X = [cur_x], Y = [cur_y]") + to_chat(world, "Next Y = [next_y]") + to_chat(world, "Target Z = [target_z]") + //debug +*/ + if(target_z) + A.z = target_z + A.y = 3 + spawn (0) + if((A && A.loc)) + A.loc.Entered(A) + return + +/turf/space/singularity_act() + return + +/turf/space/can_have_cabling() + if(locate(/obj/structure/lattice/catwalk, src)) + return 1 + return 0 + +/turf/space/proc/set_transition_north(dest_z) + destination_x = x + destination_y = TRANSITION_BORDER_SOUTH + 1 + destination_z = dest_z + +/turf/space/proc/set_transition_south(dest_z) + destination_x = x + destination_y = TRANSITION_BORDER_NORTH - 1 + destination_z = dest_z + +/turf/space/proc/set_transition_east(dest_z) + destination_x = TRANSITION_BORDER_WEST + 1 + destination_y = y + destination_z = dest_z + +/turf/space/proc/set_transition_west(dest_z) + destination_x = TRANSITION_BORDER_EAST - 1 + destination_y = y + destination_z = dest_z + +/turf/space/proc/remove_transitions() + destination_z = initial(destination_z) + +/turf/space/attack_ghost(mob/dead/observer/user) + if(destination_z) + var/turf/T = locate(destination_x, destination_y, destination_z) + user.forceMove(T) + +/turf/space/acid_act(acidpwr, acid_volume) + return 0 + +/turf/space/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) + underlay_appearance.icon = 'icons/turf/space.dmi' + underlay_appearance.icon_state = SPACE_ICON_STATE + underlay_appearance.plane = PLANE_SPACE + return TRUE diff --git a/code/game/turfs/space/transit.dm b/code/game/turfs/space/transit.dm index 15c553b3f95..2a14230d467 100644 --- a/code/game/turfs/space/transit.dm +++ b/code/game/turfs/space/transit.dm @@ -1,160 +1,160 @@ -/turf/space/transit - var/pushdirection // push things that get caught in the transit tile this direction - plane = PLANE_SPACE - -//Overwrite because we dont want people building rods in space. -/turf/space/transit/attackby(obj/O as obj, mob/user as mob, params) - return - -/turf/space/transit/north // moving to the north - - pushdirection = SOUTH // south because the space tile is scrolling south - - //IF ANYONE KNOWS A MORE EFFICIENT WAY OF MANAGING THESE SPRITES, BE MY GUEST. - shuttlespace_ns1 - icon_state = "speedspace_ns_1" - shuttlespace_ns2 - icon_state = "speedspace_ns_2" - shuttlespace_ns3 - icon_state = "speedspace_ns_3" - shuttlespace_ns4 - icon_state = "speedspace_ns_4" - shuttlespace_ns5 - icon_state = "speedspace_ns_5" - shuttlespace_ns6 - icon_state = "speedspace_ns_6" - shuttlespace_ns7 - icon_state = "speedspace_ns_7" - shuttlespace_ns8 - icon_state = "speedspace_ns_8" - shuttlespace_ns9 - icon_state = "speedspace_ns_9" - shuttlespace_ns10 - icon_state = "speedspace_ns_10" - shuttlespace_ns11 - icon_state = "speedspace_ns_11" - shuttlespace_ns12 - icon_state = "speedspace_ns_12" - shuttlespace_ns13 - icon_state = "speedspace_ns_13" - shuttlespace_ns14 - icon_state = "speedspace_ns_14" - shuttlespace_ns15 - icon_state = "speedspace_ns_15" - -/turf/space/transit/east // moving to the east - - pushdirection = WEST - - shuttlespace_ew1 - icon_state = "speedspace_ew_1" - shuttlespace_ew2 - icon_state = "speedspace_ew_2" - shuttlespace_ew3 - icon_state = "speedspace_ew_3" - shuttlespace_ew4 - icon_state = "speedspace_ew_4" - shuttlespace_ew5 - icon_state = "speedspace_ew_5" - shuttlespace_ew6 - icon_state = "speedspace_ew_6" - shuttlespace_ew7 - icon_state = "speedspace_ew_7" - shuttlespace_ew8 - icon_state = "speedspace_ew_8" - shuttlespace_ew9 - icon_state = "speedspace_ew_9" - shuttlespace_ew10 - icon_state = "speedspace_ew_10" - shuttlespace_ew11 - icon_state = "speedspace_ew_11" - shuttlespace_ew12 - icon_state = "speedspace_ew_12" - shuttlespace_ew13 - icon_state = "speedspace_ew_13" - shuttlespace_ew14 - icon_state = "speedspace_ew_14" - shuttlespace_ew15 - icon_state = "speedspace_ew_15" - -//-tg- stuff - -/turf/space/transit - icon_state = "black" - dir = SOUTH - -/turf/space/transit/horizontal - dir = WEST - -/turf/space/transit/Entered(atom/movable/AM, atom/OldLoc, ignoreRest = 0) - if(!AM) - return - if(!AM.simulated || istype(AM, /obj/docking_port)) - return //this was fucking hilarious, the docking ports were getting thrown to random Z-levels - var/max = world.maxx-TRANSITIONEDGE - var/min = 1+TRANSITIONEDGE - - var/_z = pick(levels_by_trait(REACHABLE)) //select a random space zlevel - - //now select coordinates for a border turf - var/_x - var/_y - switch(dir) - if(SOUTH) - _x = rand(min,max) - _y = max - if(WEST) - _x = max - _y = rand(min,max) - if(EAST) - _x = min - _y = rand(min,max) - else - _x = rand(min,max) - _y = min - - var/turf/T = locate(_x, _y, _z) - AM.forceMove(T) - AM.newtonian_move(dir) - - -/turf/space/transit/rpd_act() - return - -//Overwrite because we dont want people building rods in space. -/turf/space/transit/attackby() - return - -/turf/space/transit/New() - update_icon() - ..() - -/turf/space/transit/proc/update_icon() - var/p = 9 - var/angle = 0 - var/state = 1 - switch(dir) - if(NORTH) - angle = 180 - state = ((-p*x+y) % 15) + 1 - if(state < 1) - state += 15 - if(EAST) - angle = 90 - state = ((x+p*y) % 15) + 1 - if(WEST) - angle = -90 - state = ((x-p*y) % 15) + 1 - if(state < 1) - state += 15 - else - state = ((p*x+y) % 15) + 1 - - icon_state = "speedspace_ns_[state]" - transform = turn(matrix(), angle) - -/turf/space/transit/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) - underlay_appearance.icon = 'icons/turf/space.dmi' - underlay_appearance.icon_state = SPACE_ICON_STATE - underlay_appearance.plane = PLANE_SPACE - return TRUE +/turf/space/transit + var/pushdirection // push things that get caught in the transit tile this direction + plane = PLANE_SPACE + +//Overwrite because we dont want people building rods in space. +/turf/space/transit/attackby(obj/O as obj, mob/user as mob, params) + return + +/turf/space/transit/north // moving to the north + + pushdirection = SOUTH // south because the space tile is scrolling south + + //IF ANYONE KNOWS A MORE EFFICIENT WAY OF MANAGING THESE SPRITES, BE MY GUEST. + shuttlespace_ns1 + icon_state = "speedspace_ns_1" + shuttlespace_ns2 + icon_state = "speedspace_ns_2" + shuttlespace_ns3 + icon_state = "speedspace_ns_3" + shuttlespace_ns4 + icon_state = "speedspace_ns_4" + shuttlespace_ns5 + icon_state = "speedspace_ns_5" + shuttlespace_ns6 + icon_state = "speedspace_ns_6" + shuttlespace_ns7 + icon_state = "speedspace_ns_7" + shuttlespace_ns8 + icon_state = "speedspace_ns_8" + shuttlespace_ns9 + icon_state = "speedspace_ns_9" + shuttlespace_ns10 + icon_state = "speedspace_ns_10" + shuttlespace_ns11 + icon_state = "speedspace_ns_11" + shuttlespace_ns12 + icon_state = "speedspace_ns_12" + shuttlespace_ns13 + icon_state = "speedspace_ns_13" + shuttlespace_ns14 + icon_state = "speedspace_ns_14" + shuttlespace_ns15 + icon_state = "speedspace_ns_15" + +/turf/space/transit/east // moving to the east + + pushdirection = WEST + + shuttlespace_ew1 + icon_state = "speedspace_ew_1" + shuttlespace_ew2 + icon_state = "speedspace_ew_2" + shuttlespace_ew3 + icon_state = "speedspace_ew_3" + shuttlespace_ew4 + icon_state = "speedspace_ew_4" + shuttlespace_ew5 + icon_state = "speedspace_ew_5" + shuttlespace_ew6 + icon_state = "speedspace_ew_6" + shuttlespace_ew7 + icon_state = "speedspace_ew_7" + shuttlespace_ew8 + icon_state = "speedspace_ew_8" + shuttlespace_ew9 + icon_state = "speedspace_ew_9" + shuttlespace_ew10 + icon_state = "speedspace_ew_10" + shuttlespace_ew11 + icon_state = "speedspace_ew_11" + shuttlespace_ew12 + icon_state = "speedspace_ew_12" + shuttlespace_ew13 + icon_state = "speedspace_ew_13" + shuttlespace_ew14 + icon_state = "speedspace_ew_14" + shuttlespace_ew15 + icon_state = "speedspace_ew_15" + +//-tg- stuff + +/turf/space/transit + icon_state = "black" + dir = SOUTH + +/turf/space/transit/horizontal + dir = WEST + +/turf/space/transit/Entered(atom/movable/AM, atom/OldLoc, ignoreRest = 0) + if(!AM) + return + if(!AM.simulated || istype(AM, /obj/docking_port)) + return //this was fucking hilarious, the docking ports were getting thrown to random Z-levels + var/max = world.maxx-TRANSITIONEDGE + var/min = 1+TRANSITIONEDGE + + var/_z = pick(levels_by_trait(REACHABLE)) //select a random space zlevel + + //now select coordinates for a border turf + var/_x + var/_y + switch(dir) + if(SOUTH) + _x = rand(min,max) + _y = max + if(WEST) + _x = max + _y = rand(min,max) + if(EAST) + _x = min + _y = rand(min,max) + else + _x = rand(min,max) + _y = min + + var/turf/T = locate(_x, _y, _z) + AM.forceMove(T) + AM.newtonian_move(dir) + + +/turf/space/transit/rpd_act() + return + +//Overwrite because we dont want people building rods in space. +/turf/space/transit/attackby() + return + +/turf/space/transit/New() + update_icon() + ..() + +/turf/space/transit/proc/update_icon() + var/p = 9 + var/angle = 0 + var/state = 1 + switch(dir) + if(NORTH) + angle = 180 + state = ((-p*x+y) % 15) + 1 + if(state < 1) + state += 15 + if(EAST) + angle = 90 + state = ((x+p*y) % 15) + 1 + if(WEST) + angle = -90 + state = ((x-p*y) % 15) + 1 + if(state < 1) + state += 15 + else + state = ((p*x+y) % 15) + 1 + + icon_state = "speedspace_ns_[state]" + transform = turn(matrix(), angle) + +/turf/space/transit/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) + underlay_appearance.icon = 'icons/turf/space.dmi' + underlay_appearance.icon_state = SPACE_ICON_STATE + underlay_appearance.plane = PLANE_SPACE + return TRUE diff --git a/code/game/turfs/unsimulated.dm b/code/game/turfs/unsimulated.dm index 537cfdc8d57..688dc778ce3 100644 --- a/code/game/turfs/unsimulated.dm +++ b/code/game/turfs/unsimulated.dm @@ -1,45 +1,45 @@ -/turf/unsimulated - intact = 1 - name = "command" - oxygen = MOLES_O2STANDARD - nitrogen = MOLES_N2STANDARD - -/turf/unsimulated/can_lay_cable() - return 0 - -/turf/unsimulated/rpd_act() - return - -/turf/unsimulated/acid_act(acidpwr, acid_volume, acid_id) - return 0 - -/turf/unsimulated/floor/plating/vox - icon_state = "plating" - name = "plating" - nitrogen = 100 - oxygen = 0 - -/turf/unsimulated/floor/plating/snow - name = "snow" - icon = 'icons/turf/snow.dmi' - icon_state = "snow" - temperature = T0C - -/turf/unsimulated/floor/plating/snow/concrete - name = "concrete" - icon = 'icons/turf/floors.dmi' - icon_state = "concrete" - -/turf/unsimulated/floor/plating/snow/ex_act(severity) - return - -/turf/unsimulated/floor/plating/airless - icon_state = "plating" - name = "airless plating" - oxygen = 0 - nitrogen = 0 - temperature = TCMB - -/turf/unsimulated/floor/plating/airless/New() - ..() - name = "plating" +/turf/unsimulated + intact = 1 + name = "command" + oxygen = MOLES_O2STANDARD + nitrogen = MOLES_N2STANDARD + +/turf/unsimulated/can_lay_cable() + return 0 + +/turf/unsimulated/rpd_act() + return + +/turf/unsimulated/acid_act(acidpwr, acid_volume, acid_id) + return 0 + +/turf/unsimulated/floor/plating/vox + icon_state = "plating" + name = "plating" + nitrogen = 100 + oxygen = 0 + +/turf/unsimulated/floor/plating/snow + name = "snow" + icon = 'icons/turf/snow.dmi' + icon_state = "snow" + temperature = T0C + +/turf/unsimulated/floor/plating/snow/concrete + name = "concrete" + icon = 'icons/turf/floors.dmi' + icon_state = "concrete" + +/turf/unsimulated/floor/plating/snow/ex_act(severity) + return + +/turf/unsimulated/floor/plating/airless + icon_state = "plating" + name = "airless plating" + oxygen = 0 + nitrogen = 0 + temperature = TCMB + +/turf/unsimulated/floor/plating/airless/New() + ..() + name = "plating" diff --git a/code/game/turfs/unsimulated/beach.dm b/code/game/turfs/unsimulated/beach.dm index 15650c1f00e..f6a3a62c324 100644 --- a/code/game/turfs/unsimulated/beach.dm +++ b/code/game/turfs/unsimulated/beach.dm @@ -1,127 +1,127 @@ -/turf/unsimulated/beach - name = "Beach" - icon = 'icons/misc/beach.dmi' - var/water_overlay_image = null - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/turf/unsimulated/beach/New() - ..() - if(water_overlay_image) - var/image/overlay_image = image('icons/misc/beach.dmi', icon_state = water_overlay_image, layer = ABOVE_MOB_LAYER) - overlay_image.plane = GAME_PLANE - overlays += overlay_image - -/turf/unsimulated/beach/sand - name = "Sand" - icon_state = "desert" - mouse_opacity = MOUSE_OPACITY_ICON - -/turf/unsimulated/beach/sand/New() //adds some aesthetic randomness to the beach sand - icon_state = pick("desert", "desert0", "desert1", "desert2", "desert3", "desert4") - ..() - -/turf/unsimulated/beach/sand/dense //for boundary "walls" - density = 1 - -/turf/unsimulated/beach/coastline - name = "Coastline" - //icon = 'icons/misc/beach2.dmi' - //icon_state = "sandwater" - icon_state = "beach" - water_overlay_image = "water_coast" - -/turf/unsimulated/beach/coastline/dense //for boundary "walls" - density = 1 - -/turf/unsimulated/beach/water - name = "Shallow Water" - icon_state = "seashallow" - water_overlay_image = "water_shallow" - var/obj/machinery/poolcontroller/linkedcontroller = null - -/turf/unsimulated/beach/water/Entered(atom/movable/AM, atom/OldLoc) - . = ..() - if(!linkedcontroller) - return - if(ismob(AM)) - linkedcontroller.mobinpool += AM - -/turf/unsimulated/beach/water/Exited(atom/movable/AM, atom/newloc) - . = ..() - if(!linkedcontroller) - return - if(ismob(AM)) - linkedcontroller.mobinpool -= AM - -/turf/unsimulated/beach/water/InitializedOn(atom/A) - if(!linkedcontroller) - return - if(istype(A, /obj/effect/decal/cleanable)) // Better a typecheck than looping through thousands of turfs everyday - linkedcontroller.decalinpool += A - -/turf/unsimulated/beach/water/dense //for boundary "walls" - density = 1 - -/turf/unsimulated/beach/water/edge_drop - name = "Water" - icon_state = "seadrop" - water_overlay_image = "water_drop" - -/turf/unsimulated/beach/water/drop - name = "Water" - icon = 'icons/turf/floors/seadrop.dmi' - icon_state = "seadrop" - water_overlay_image = null - smooth = SMOOTH_TRUE - canSmoothWith = list( - /turf/unsimulated/beach/water/drop, /turf/unsimulated/beach/water/drop/dense, - /turf/unsimulated/beach/water, /turf/unsimulated/beach/water/dense, - /turf/unsimulated/beach/water/edge_drop) - var/obj/effect/beach_drop_overlay/water_overlay - -/turf/unsimulated/beach/water/drop/New() - ..() - water_overlay = new(src) - -/turf/unsimulated/beach/water/drop/Destroy() - QDEL_NULL(water_overlay) - return ..() - -/obj/effect/beach_drop_overlay - name = "Water" - icon = 'icons/turf/floors/seadrop-o.dmi' - layer = MOB_LAYER + 0.1 - smooth = SMOOTH_TRUE - anchored = 1 - canSmoothWith = list( - /turf/unsimulated/beach/water/drop, /turf/unsimulated/beach/water/drop/dense, - /turf/unsimulated/beach/water, /turf/unsimulated/beach/water/dense, - /turf/unsimulated/beach/water/edge_drop) - -/turf/unsimulated/beach/water/drop/dense - density = 1 - -/turf/unsimulated/beach/water/deep - name = "Deep Water" - icon_state = "seadeep" - water_overlay_image = "water_deep" - -/turf/unsimulated/beach/water/deep/dense - density = 1 - -/turf/unsimulated/beach/water/deep/wood_floor - name = "Sunken Floor" - icon = 'icons/turf/floors.dmi' - icon_state = "wood" - -/turf/unsimulated/beach/water/deep/sand_floor - name = "Sea Floor" - icon_state = "sand" - -/turf/unsimulated/beach/water/deep/rock_wall - name = "Reef Stone" - icon_state = "desert7" - density = 1 - opacity = 1 - explosion_block = 2 - mouse_opacity = MOUSE_OPACITY_ICON +/turf/unsimulated/beach + name = "Beach" + icon = 'icons/misc/beach.dmi' + var/water_overlay_image = null + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/turf/unsimulated/beach/New() + ..() + if(water_overlay_image) + var/image/overlay_image = image('icons/misc/beach.dmi', icon_state = water_overlay_image, layer = ABOVE_MOB_LAYER) + overlay_image.plane = GAME_PLANE + overlays += overlay_image + +/turf/unsimulated/beach/sand + name = "Sand" + icon_state = "desert" + mouse_opacity = MOUSE_OPACITY_ICON + +/turf/unsimulated/beach/sand/New() //adds some aesthetic randomness to the beach sand + icon_state = pick("desert", "desert0", "desert1", "desert2", "desert3", "desert4") + ..() + +/turf/unsimulated/beach/sand/dense //for boundary "walls" + density = 1 + +/turf/unsimulated/beach/coastline + name = "Coastline" + //icon = 'icons/misc/beach2.dmi' + //icon_state = "sandwater" + icon_state = "beach" + water_overlay_image = "water_coast" + +/turf/unsimulated/beach/coastline/dense //for boundary "walls" + density = 1 + +/turf/unsimulated/beach/water + name = "Shallow Water" + icon_state = "seashallow" + water_overlay_image = "water_shallow" + var/obj/machinery/poolcontroller/linkedcontroller = null + +/turf/unsimulated/beach/water/Entered(atom/movable/AM, atom/OldLoc) + . = ..() + if(!linkedcontroller) + return + if(ismob(AM)) + linkedcontroller.mobinpool += AM + +/turf/unsimulated/beach/water/Exited(atom/movable/AM, atom/newloc) + . = ..() + if(!linkedcontroller) + return + if(ismob(AM)) + linkedcontroller.mobinpool -= AM + +/turf/unsimulated/beach/water/InitializedOn(atom/A) + if(!linkedcontroller) + return + if(istype(A, /obj/effect/decal/cleanable)) // Better a typecheck than looping through thousands of turfs everyday + linkedcontroller.decalinpool += A + +/turf/unsimulated/beach/water/dense //for boundary "walls" + density = 1 + +/turf/unsimulated/beach/water/edge_drop + name = "Water" + icon_state = "seadrop" + water_overlay_image = "water_drop" + +/turf/unsimulated/beach/water/drop + name = "Water" + icon = 'icons/turf/floors/seadrop.dmi' + icon_state = "seadrop" + water_overlay_image = null + smooth = SMOOTH_TRUE + canSmoothWith = list( + /turf/unsimulated/beach/water/drop, /turf/unsimulated/beach/water/drop/dense, + /turf/unsimulated/beach/water, /turf/unsimulated/beach/water/dense, + /turf/unsimulated/beach/water/edge_drop) + var/obj/effect/beach_drop_overlay/water_overlay + +/turf/unsimulated/beach/water/drop/New() + ..() + water_overlay = new(src) + +/turf/unsimulated/beach/water/drop/Destroy() + QDEL_NULL(water_overlay) + return ..() + +/obj/effect/beach_drop_overlay + name = "Water" + icon = 'icons/turf/floors/seadrop-o.dmi' + layer = MOB_LAYER + 0.1 + smooth = SMOOTH_TRUE + anchored = 1 + canSmoothWith = list( + /turf/unsimulated/beach/water/drop, /turf/unsimulated/beach/water/drop/dense, + /turf/unsimulated/beach/water, /turf/unsimulated/beach/water/dense, + /turf/unsimulated/beach/water/edge_drop) + +/turf/unsimulated/beach/water/drop/dense + density = 1 + +/turf/unsimulated/beach/water/deep + name = "Deep Water" + icon_state = "seadeep" + water_overlay_image = "water_deep" + +/turf/unsimulated/beach/water/deep/dense + density = 1 + +/turf/unsimulated/beach/water/deep/wood_floor + name = "Sunken Floor" + icon = 'icons/turf/floors.dmi' + icon_state = "wood" + +/turf/unsimulated/beach/water/deep/sand_floor + name = "Sea Floor" + icon_state = "sand" + +/turf/unsimulated/beach/water/deep/rock_wall + name = "Reef Stone" + icon_state = "desert7" + density = 1 + opacity = 1 + explosion_block = 2 + mouse_opacity = MOUSE_OPACITY_ICON diff --git a/code/game/turfs/unsimulated/floor.dm b/code/game/turfs/unsimulated/floor.dm index c71404dc0d7..28f4e359223 100644 --- a/code/game/turfs/unsimulated/floor.dm +++ b/code/game/turfs/unsimulated/floor.dm @@ -1,71 +1,71 @@ -/turf/unsimulated/floor - name = "floor" - icon = 'icons/turf/floors.dmi' - icon_state = "Floor3" - -/turf/unsimulated/floor/grass - name = "grass patch" - icon_state = "grass1" - -/turf/unsimulated/floor/grass/New() - ..() - icon_state = "grass[rand(1,4)]" - -/turf/unsimulated/floor/snow - name = "snow" - icon = 'icons/turf/snow.dmi' - icon_state = "snow" - -/turf/unsimulated/floor/abductor - name = "alien floor" - icon_state = "alienpod1" - -/turf/unsimulated/floor/abductor/New() - ..() - icon_state = "alienpod[rand(1,9)]" - -/turf/unsimulated/floor/vox - icon_state = "dark" - nitrogen = 100 - oxygen = 0 - -/turf/unsimulated/floor/carpet - name = "Carpet" - icon = 'icons/turf/floors/carpet.dmi' - icon_state = "carpet" - smooth = SMOOTH_TRUE - canSmoothWith = null - - footstep_sounds = list( - "human" = list('sound/effects/footstep/carpet_human.ogg'), - "xeno" = list('sound/effects/footstep/carpet_xeno.ogg') - ) - -/turf/unsimulated/floor/wood - icon_state = "wood" - - footstep_sounds = list( - "human" = list('sound/effects/footstep/wood_all.ogg'), //@RonaldVanWonderen of Freesound.org - "xeno" = list('sound/effects/footstep/wood_all.ogg') //@RonaldVanWonderen of Freesound.org - ) - -/turf/unsimulated/floor/lava - name = "lava" - desc = "That looks... a bit dangerous" - icon = 'icons/turf/floors/lava.dmi' - icon_state = "smooth" - smooth = SMOOTH_MORE - canSmoothWith = list(/turf/unsimulated/floor/lava) - var/lava_damage = 250 - var/lava_fire = 20 - light_range = 2 - light_color = "#FFC040" - -/turf/unsimulated/floor/lava/Entered(mob/living/M, atom/OL, ignoreRest = 0) - if(istype(M)) - M.apply_damage(lava_damage, BURN) - M.adjust_fire_stacks(lava_fire) - M.IgniteMob() - -/turf/unsimulated/floor/lava/dense - density = 1 +/turf/unsimulated/floor + name = "floor" + icon = 'icons/turf/floors.dmi' + icon_state = "Floor3" + +/turf/unsimulated/floor/grass + name = "grass patch" + icon_state = "grass1" + +/turf/unsimulated/floor/grass/New() + ..() + icon_state = "grass[rand(1,4)]" + +/turf/unsimulated/floor/snow + name = "snow" + icon = 'icons/turf/snow.dmi' + icon_state = "snow" + +/turf/unsimulated/floor/abductor + name = "alien floor" + icon_state = "alienpod1" + +/turf/unsimulated/floor/abductor/New() + ..() + icon_state = "alienpod[rand(1,9)]" + +/turf/unsimulated/floor/vox + icon_state = "dark" + nitrogen = 100 + oxygen = 0 + +/turf/unsimulated/floor/carpet + name = "Carpet" + icon = 'icons/turf/floors/carpet.dmi' + icon_state = "carpet" + smooth = SMOOTH_TRUE + canSmoothWith = null + + footstep_sounds = list( + "human" = list('sound/effects/footstep/carpet_human.ogg'), + "xeno" = list('sound/effects/footstep/carpet_xeno.ogg') + ) + +/turf/unsimulated/floor/wood + icon_state = "wood" + + footstep_sounds = list( + "human" = list('sound/effects/footstep/wood_all.ogg'), //@RonaldVanWonderen of Freesound.org + "xeno" = list('sound/effects/footstep/wood_all.ogg') //@RonaldVanWonderen of Freesound.org + ) + +/turf/unsimulated/floor/lava + name = "lava" + desc = "That looks... a bit dangerous" + icon = 'icons/turf/floors/lava.dmi' + icon_state = "smooth" + smooth = SMOOTH_MORE + canSmoothWith = list(/turf/unsimulated/floor/lava) + var/lava_damage = 250 + var/lava_fire = 20 + light_range = 2 + light_color = "#FFC040" + +/turf/unsimulated/floor/lava/Entered(mob/living/M, atom/OL, ignoreRest = 0) + if(istype(M)) + M.apply_damage(lava_damage, BURN) + M.adjust_fire_stacks(lava_fire) + M.IgniteMob() + +/turf/unsimulated/floor/lava/dense + density = 1 diff --git a/code/game/turfs/unsimulated/walls.dm b/code/game/turfs/unsimulated/walls.dm index c7b192366d4..c5688c31323 100644 --- a/code/game/turfs/unsimulated/walls.dm +++ b/code/game/turfs/unsimulated/walls.dm @@ -1,35 +1,35 @@ -/turf/unsimulated/wall - name = "wall" - icon = 'icons/turf/walls.dmi' - icon_state = "riveted" - opacity = 1 - density = 1 - explosion_block = 2 - -/turf/unsimulated/wall/fakeglass - name = "window" - icon_state = "fakewindows" - opacity = 0 - -/turf/unsimulated/wall/fakedoor - name = "Centcom Access" - icon = 'icons/obj/doors/airlocks/centcom/centcom.dmi' - icon_state = "closed" - -/turf/unsimulated/wall/splashscreen - name = "Space Station 13" - icon = 'config/title_screens/images/blank.png' - icon_state = "" - layer = FLY_LAYER - -/turf/unsimulated/wall/other - icon_state = "r_wall" - -/turf/unsimulated/wall/metal - icon = 'icons/turf/walls/wall.dmi' - icon_state = "wall" - smooth = SMOOTH_TRUE - -/turf/unsimulated/wall/abductor - icon_state = "alien1" - explosion_block = 50 +/turf/unsimulated/wall + name = "wall" + icon = 'icons/turf/walls.dmi' + icon_state = "riveted" + opacity = 1 + density = 1 + explosion_block = 2 + +/turf/unsimulated/wall/fakeglass + name = "window" + icon_state = "fakewindows" + opacity = 0 + +/turf/unsimulated/wall/fakedoor + name = "Centcom Access" + icon = 'icons/obj/doors/airlocks/centcom/centcom.dmi' + icon_state = "closed" + +/turf/unsimulated/wall/splashscreen + name = "Space Station 13" + icon = 'config/title_screens/images/blank.png' + icon_state = "" + layer = FLY_LAYER + +/turf/unsimulated/wall/other + icon_state = "r_wall" + +/turf/unsimulated/wall/metal + icon = 'icons/turf/walls/wall.dmi' + icon_state = "wall" + smooth = SMOOTH_TRUE + +/turf/unsimulated/wall/abductor + icon_state = "alien1" + explosion_block = 50 diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm index df2f588753f..e94d04f5dfb 100644 --- a/code/game/verbs/ooc.dm +++ b/code/game/verbs/ooc.dm @@ -1,255 +1,255 @@ -var/global/normal_ooc_colour = "#002eb8" -var/global/member_ooc_colour = "#035417" -var/global/mentor_ooc_colour = "#0099cc" -var/global/moderator_ooc_colour = "#184880" -var/global/admin_ooc_colour = "#b82e00" - -//Checks if the client already has a text input open -/client/proc/checkTyping() - return (prefs.toggles & TYPING_ONCE && typing) - -/client/verb/ooc(msg = "" as text) - set name = "OOC" - set category = "OOC" - - if(!mob) - return - if(IsGuestKey(key)) - to_chat(src, "Guests may not use OOC.") - return - - if(!check_rights(R_ADMIN|R_MOD, 0)) - if(!config.ooc_allowed) - to_chat(src, "OOC is globally muted.") - return - if(!config.dooc_allowed && (mob.stat == DEAD)) - to_chat(usr, "OOC for dead mobs has been turned off.") - return - if(prefs.muted & MUTE_OOC) - to_chat(src, "You cannot use OOC (muted).") - return - - if(!msg) - msg = typing_input(src.mob, "", "ooc \"text\"") - - msg = trim(sanitize(copytext(msg, 1, MAX_MESSAGE_LEN))) - if(!msg) - return - - if(!(prefs.toggles & CHAT_OOC)) - to_chat(src, "You have OOC muted.") - return - - if(!check_rights(R_ADMIN|R_MOD,0)) - if(!config.ooc_allowed) - to_chat(src, "OOC is globally muted.") - return - if(handle_spam_prevention(msg, MUTE_OOC, OOC_COOLDOWN)) - return - if(findtext(msg, "byond://")) - to_chat(src, "Advertising other servers is not allowed.") - log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]") - message_admins("[key_name_admin(src)] has attempted to advertise in OOC: [msg]") - return - - log_ooc(msg, src) - - var/display_colour = normal_ooc_colour - if(holder && !holder.fakekey) - display_colour = mentor_ooc_colour - if(check_rights(R_MOD,0) && !check_rights(R_ADMIN,0)) - display_colour = moderator_ooc_colour - else if(check_rights(R_ADMIN,0)) - if(config.allow_admin_ooccolor) - display_colour = src.prefs.ooccolor - else - display_colour = admin_ooc_colour - - if(prefs.unlock_content) - if(display_colour == normal_ooc_colour) - if((prefs.toggles & MEMBER_PUBLIC)) - display_colour = member_ooc_colour - - for(var/client/C in GLOB.clients) - if(C.prefs.toggles & CHAT_OOC) - var/display_name = key - - if(prefs.unlock_content) - if(prefs.toggles & MEMBER_PUBLIC) - var/icon/byond = icon('icons/member_content.dmi', "blag") - display_name = "[bicon(byond)][display_name]" - - if(donator_level > 0) - if((prefs.toggles & DONATOR_PUBLIC)) - var/icon/donator = icon('icons/ooc_tag_16x.dmi', "donator") - display_name = "[bicon(donator)][display_name]" - - if(holder) - if(holder.fakekey) - if(C.holder && C.holder.rights & R_ADMIN) - display_name = "[holder.fakekey]/([key])" - else - display_name = holder.fakekey - - if(!config.disable_ooc_emoji) - msg = "[msg]" - - to_chat(C, "OOC: [display_name]: [msg]") - -/proc/toggle_ooc() - config.ooc_allowed = ( !config.ooc_allowed ) - if(config.ooc_allowed) - to_chat(world, "The OOC channel has been globally enabled!") - else - to_chat(world, "The OOC channel has been globally disabled!") - -/proc/auto_toggle_ooc(var/on) - if(config.auto_toggle_ooc_during_round && config.ooc_allowed != on) - toggle_ooc() - -/client/proc/set_ooc(newColor as color) - set name = "Set Player OOC Colour" - set desc = "Modifies the default player OOC color." - set category = "Server" - - if(!check_rights(R_SERVER)) return - - normal_ooc_colour = newColor - message_admins("[key_name_admin(usr)] has set the default player OOC color to [newColor]") - log_admin("[key_name(usr)] has set the default player OOC color to [newColor]") - - - feedback_add_details("admin_verb","SOOC") - -/client/proc/reset_ooc() - set name = "Reset Player OOC Color" - set desc = "Returns the default player OOC color to default." - set category = "Server" - - if(!check_rights(R_SERVER)) return - - normal_ooc_colour = initial(normal_ooc_colour) - message_admins("[key_name_admin(usr)] has reset the default player OOC color") - log_admin("[key_name(usr)] has reset the default player OOC color") - - feedback_add_details("admin_verb","ROOC") - -/client/proc/colorooc() - set name = "Set Your OOC Color" - set desc = "Allows you to pick a custom OOC color." - set category = "Preferences" - - if(!check_rights(R_ADMIN)) return - - var/new_ooccolor = input(src, "Please select your OOC color.", "OOC color", prefs.ooccolor) as color|null - if(new_ooccolor) - prefs.ooccolor = new_ooccolor - prefs.save_preferences(src) - to_chat(usr, "Your OOC color has been set to [new_ooccolor].") - - feedback_add_details("admin_verb","OC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/resetcolorooc() - set name = "Reset Your OOC Color" - set desc = "Returns your OOC color to default." - set category = "Preferences" - - if(!check_rights(R_ADMIN)) return - - prefs.ooccolor = initial(prefs.ooccolor) - prefs.save_preferences(src) - to_chat(usr, "Your OOC color has been reset.") - - feedback_add_details("admin_verb","ROC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/verb/looc(msg = "" as text) - set name = "LOOC" - set desc = "Local OOC, seen only by those in view." - set category = "OOC" - - if(!mob) - return - if(IsGuestKey(key)) - to_chat(src, "Guests may not use OOC.") - return - - if(!check_rights(R_ADMIN|R_MOD,0)) - if(!config.looc_allowed) - to_chat(src, "LOOC is globally muted.") - return - if(!config.dooc_allowed && (mob.stat == DEAD)) - to_chat(usr, "LOOC for dead mobs has been turned off.") - return - if(prefs.muted & MUTE_OOC) - to_chat(src, "You cannot use LOOC (muted).") - return - - if(!msg) - msg = typing_input(src.mob, "Local OOC, seen only by those in view.", "looc \"text\"") - - msg = trim(sanitize(copytext(msg, 1, MAX_MESSAGE_LEN))) - if(!msg) - return - - if(!(prefs.toggles & CHAT_LOOC)) - to_chat(src, "You have LOOC muted.") - return - - if(!check_rights(R_ADMIN|R_MOD,0)) - if(handle_spam_prevention(msg, MUTE_OOC, OOC_COOLDOWN)) - return - if(findtext(msg, "byond://")) - to_chat(src, "Advertising other servers is not allowed.") - log_admin("[key_name(src)] has attempted to advertise in LOOC: [msg]") - message_admins("[key_name_admin(src)] has attempted to advertise in LOOC: [msg]") - return - - log_looc(msg, src) - - var/mob/source = mob.get_looc_source() - var/list/heard = get_mobs_in_view(7, source) - - var/display_name = key - if(holder && holder.fakekey) - display_name = holder.fakekey - if(mob.stat != DEAD) - display_name = mob.name - - for(var/client/target in GLOB.clients) - if(target.prefs.toggles & CHAT_LOOC) - var/prefix = "" - var/admin_stuff = "" - var/send = 0 - - if(target in GLOB.admins) - if(check_rights(R_ADMIN|R_MOD,0,target.mob)) - admin_stuff += "/([key])" - if(target != src) - admin_stuff += " ([admin_jump_link(mob)])" - - if(target.mob in heard) - send = 1 - if(isAI(target.mob)) - prefix = " (Core)" - - else if(isAI(target.mob)) // Special case - var/mob/living/silicon/ai/A = target.mob - if(A.eyeobj in hearers(7, source)) - send = 1 - prefix = " (Eye)" - - if(!send && (target in GLOB.admins)) - if(check_rights(R_ADMIN|R_MOD,0,target.mob)) - send = 1 - prefix = "(R)" - - if(send) - to_chat(target, "LOOC[prefix]: [display_name][admin_stuff]: [msg]") - -/mob/proc/get_looc_source() - return src - -/mob/living/silicon/ai/get_looc_source() - if(eyeobj) - return eyeobj - return src +var/global/normal_ooc_colour = "#002eb8" +var/global/member_ooc_colour = "#035417" +var/global/mentor_ooc_colour = "#0099cc" +var/global/moderator_ooc_colour = "#184880" +var/global/admin_ooc_colour = "#b82e00" + +//Checks if the client already has a text input open +/client/proc/checkTyping() + return (prefs.toggles & TYPING_ONCE && typing) + +/client/verb/ooc(msg = "" as text) + set name = "OOC" + set category = "OOC" + + if(!mob) + return + if(IsGuestKey(key)) + to_chat(src, "Guests may not use OOC.") + return + + if(!check_rights(R_ADMIN|R_MOD, 0)) + if(!config.ooc_allowed) + to_chat(src, "OOC is globally muted.") + return + if(!config.dooc_allowed && (mob.stat == DEAD)) + to_chat(usr, "OOC for dead mobs has been turned off.") + return + if(prefs.muted & MUTE_OOC) + to_chat(src, "You cannot use OOC (muted).") + return + + if(!msg) + msg = typing_input(src.mob, "", "ooc \"text\"") + + msg = trim(sanitize(copytext(msg, 1, MAX_MESSAGE_LEN))) + if(!msg) + return + + if(!(prefs.toggles & CHAT_OOC)) + to_chat(src, "You have OOC muted.") + return + + if(!check_rights(R_ADMIN|R_MOD,0)) + if(!config.ooc_allowed) + to_chat(src, "OOC is globally muted.") + return + if(handle_spam_prevention(msg, MUTE_OOC, OOC_COOLDOWN)) + return + if(findtext(msg, "byond://")) + to_chat(src, "Advertising other servers is not allowed.") + log_admin("[key_name(src)] has attempted to advertise in OOC: [msg]") + message_admins("[key_name_admin(src)] has attempted to advertise in OOC: [msg]") + return + + log_ooc(msg, src) + + var/display_colour = normal_ooc_colour + if(holder && !holder.fakekey) + display_colour = mentor_ooc_colour + if(check_rights(R_MOD,0) && !check_rights(R_ADMIN,0)) + display_colour = moderator_ooc_colour + else if(check_rights(R_ADMIN,0)) + if(config.allow_admin_ooccolor) + display_colour = src.prefs.ooccolor + else + display_colour = admin_ooc_colour + + if(prefs.unlock_content) + if(display_colour == normal_ooc_colour) + if((prefs.toggles & MEMBER_PUBLIC)) + display_colour = member_ooc_colour + + for(var/client/C in GLOB.clients) + if(C.prefs.toggles & CHAT_OOC) + var/display_name = key + + if(prefs.unlock_content) + if(prefs.toggles & MEMBER_PUBLIC) + var/icon/byond = icon('icons/member_content.dmi', "blag") + display_name = "[bicon(byond)][display_name]" + + if(donator_level > 0) + if((prefs.toggles & DONATOR_PUBLIC)) + var/icon/donator = icon('icons/ooc_tag_16x.dmi', "donator") + display_name = "[bicon(donator)][display_name]" + + if(holder) + if(holder.fakekey) + if(C.holder && C.holder.rights & R_ADMIN) + display_name = "[holder.fakekey]/([key])" + else + display_name = holder.fakekey + + if(!config.disable_ooc_emoji) + msg = "[msg]" + + to_chat(C, "OOC: [display_name]: [msg]") + +/proc/toggle_ooc() + config.ooc_allowed = ( !config.ooc_allowed ) + if(config.ooc_allowed) + to_chat(world, "The OOC channel has been globally enabled!") + else + to_chat(world, "The OOC channel has been globally disabled!") + +/proc/auto_toggle_ooc(var/on) + if(config.auto_toggle_ooc_during_round && config.ooc_allowed != on) + toggle_ooc() + +/client/proc/set_ooc(newColor as color) + set name = "Set Player OOC Colour" + set desc = "Modifies the default player OOC color." + set category = "Server" + + if(!check_rights(R_SERVER)) return + + normal_ooc_colour = newColor + message_admins("[key_name_admin(usr)] has set the default player OOC color to [newColor]") + log_admin("[key_name(usr)] has set the default player OOC color to [newColor]") + + + feedback_add_details("admin_verb","SOOC") + +/client/proc/reset_ooc() + set name = "Reset Player OOC Color" + set desc = "Returns the default player OOC color to default." + set category = "Server" + + if(!check_rights(R_SERVER)) return + + normal_ooc_colour = initial(normal_ooc_colour) + message_admins("[key_name_admin(usr)] has reset the default player OOC color") + log_admin("[key_name(usr)] has reset the default player OOC color") + + feedback_add_details("admin_verb","ROOC") + +/client/proc/colorooc() + set name = "Set Your OOC Color" + set desc = "Allows you to pick a custom OOC color." + set category = "Preferences" + + if(!check_rights(R_ADMIN)) return + + var/new_ooccolor = input(src, "Please select your OOC color.", "OOC color", prefs.ooccolor) as color|null + if(new_ooccolor) + prefs.ooccolor = new_ooccolor + prefs.save_preferences(src) + to_chat(usr, "Your OOC color has been set to [new_ooccolor].") + + feedback_add_details("admin_verb","OC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/resetcolorooc() + set name = "Reset Your OOC Color" + set desc = "Returns your OOC color to default." + set category = "Preferences" + + if(!check_rights(R_ADMIN)) return + + prefs.ooccolor = initial(prefs.ooccolor) + prefs.save_preferences(src) + to_chat(usr, "Your OOC color has been reset.") + + feedback_add_details("admin_verb","ROC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/verb/looc(msg = "" as text) + set name = "LOOC" + set desc = "Local OOC, seen only by those in view." + set category = "OOC" + + if(!mob) + return + if(IsGuestKey(key)) + to_chat(src, "Guests may not use OOC.") + return + + if(!check_rights(R_ADMIN|R_MOD,0)) + if(!config.looc_allowed) + to_chat(src, "LOOC is globally muted.") + return + if(!config.dooc_allowed && (mob.stat == DEAD)) + to_chat(usr, "LOOC for dead mobs has been turned off.") + return + if(prefs.muted & MUTE_OOC) + to_chat(src, "You cannot use LOOC (muted).") + return + + if(!msg) + msg = typing_input(src.mob, "Local OOC, seen only by those in view.", "looc \"text\"") + + msg = trim(sanitize(copytext(msg, 1, MAX_MESSAGE_LEN))) + if(!msg) + return + + if(!(prefs.toggles & CHAT_LOOC)) + to_chat(src, "You have LOOC muted.") + return + + if(!check_rights(R_ADMIN|R_MOD,0)) + if(handle_spam_prevention(msg, MUTE_OOC, OOC_COOLDOWN)) + return + if(findtext(msg, "byond://")) + to_chat(src, "Advertising other servers is not allowed.") + log_admin("[key_name(src)] has attempted to advertise in LOOC: [msg]") + message_admins("[key_name_admin(src)] has attempted to advertise in LOOC: [msg]") + return + + log_looc(msg, src) + + var/mob/source = mob.get_looc_source() + var/list/heard = get_mobs_in_view(7, source) + + var/display_name = key + if(holder && holder.fakekey) + display_name = holder.fakekey + if(mob.stat != DEAD) + display_name = mob.name + + for(var/client/target in GLOB.clients) + if(target.prefs.toggles & CHAT_LOOC) + var/prefix = "" + var/admin_stuff = "" + var/send = 0 + + if(target in GLOB.admins) + if(check_rights(R_ADMIN|R_MOD,0,target.mob)) + admin_stuff += "/([key])" + if(target != src) + admin_stuff += " ([admin_jump_link(mob)])" + + if(target.mob in heard) + send = 1 + if(isAI(target.mob)) + prefix = " (Core)" + + else if(isAI(target.mob)) // Special case + var/mob/living/silicon/ai/A = target.mob + if(A.eyeobj in hearers(7, source)) + send = 1 + prefix = " (Eye)" + + if(!send && (target in GLOB.admins)) + if(check_rights(R_ADMIN|R_MOD,0,target.mob)) + send = 1 + prefix = "(R)" + + if(send) + to_chat(target, "LOOC[prefix]: [display_name][admin_stuff]: [msg]") + +/mob/proc/get_looc_source() + return src + +/mob/living/silicon/ai/get_looc_source() + if(eyeobj) + return eyeobj + return src diff --git a/code/game/verbs/suicide.dm b/code/game/verbs/suicide.dm index 50b53cc7dd9..4df9dd832c4 100644 --- a/code/game/verbs/suicide.dm +++ b/code/game/verbs/suicide.dm @@ -1,247 +1,247 @@ -/mob/var/suiciding = 0 - -/mob/living/carbon/human/proc/do_suicide(damagetype, byitem) - var/threshold = check_death_method() ? ((HEALTH_THRESHOLD_CRIT + HEALTH_THRESHOLD_DEAD) / 2) : (HEALTH_THRESHOLD_DEAD - 50) - var/dmgamt = maxHealth - threshold - - var/damage_mod = 1 - switch(damagetype) //Sorry about the magic numbers. - //brute = 1, burn = 2, tox = 4, oxy = 8 - if(15) //4 damage types - damage_mod = 4 - - if(6, 11, 13, 14) //3 damage types - damage_mod = 3 - - if(3, 5, 7, 9, 10, 12) //2 damage types - damage_mod = 2 - - if(1, 2, 4, 8) //1 damage type - damage_mod = 1 - - else //This should not happen, but if it does, everything should still work - damage_mod = 1 - - //Do dmgamt damage divided by the number of damage types applied. - if(damagetype & BRUTELOSS) - adjustBruteLoss(dmgamt / damage_mod, FALSE) - - if(damagetype & FIRELOSS) - adjustFireLoss(dmgamt / damage_mod, FALSE) - - if(damagetype & TOXLOSS) - adjustToxLoss(dmgamt / damage_mod, FALSE) - - if(damagetype & OXYLOSS) - adjustOxyLoss(dmgamt / damage_mod, FALSE) - - // Failing that... - if(!(damagetype & BRUTELOSS) && !(damagetype & FIRELOSS) && !(damagetype & TOXLOSS) && !(damagetype & OXYLOSS)) - if(NO_BREATHE in dna.species.species_traits) - // the ultimate fallback - take_overall_damage(max(dmgamt - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0), 0, updating_health = FALSE) - else - adjustOxyLoss(max(dmgamt - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0), FALSE) - - var/obj/item/organ/external/affected = get_organ("head") - if(affected) - affected.add_autopsy_data(byitem ? "Suicide by [byitem]" : "Suicide", dmgamt) - - updatehealth() - -/mob/living/carbon/human/verb/suicide() - set hidden = 1 - - be_suicidal() - -/mob/living/carbon/human/proc/be_suicidal(forced = FALSE) - if(stat == DEAD) - to_chat(src, "You're already dead!") - return - - if(!SSticker) - to_chat(src, "You can't commit suicide before the game starts!") - return - - // No more borergrief, one way or the other - if(has_brain_worms()) - to_chat(src, "You try to bring yourself to commit suicide, but - something prevents you!") - return - - if(suiciding) - to_chat(src, "You're already committing suicide! Be patient!") - return - - - var/confirm = null - if(!forced) - confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") - - if(forced || (confirm == "Yes")) - suiciding = TRUE - var/obj/item/held_item = get_active_hand() - if(held_item) - var/damagetype = held_item.suicide_act(src) - if(damagetype) - if(damagetype & SHAME) - adjustStaminaLoss(200) - suiciding = FALSE - return - if(damagetype & OBLITERATION) // Does it gib or something? Don't deal damage - return - do_suicide(damagetype, held_item) - return - else - for(var/obj/O in orange(1, src)) - if(O.suicidal_hands) - continue - var/damagetype = O.suicide_act(src) - if(damagetype) - if(damagetype & SHAME) - adjustStaminaLoss(200) - suiciding = FALSE - return - if(damagetype & OBLITERATION) - return - do_suicide(damagetype, O) - return - - to_chat(viewers(src), "[src] [replacetext(pick(dna.species.suicide_messages), "their", p_their())] It looks like [p_theyre()] trying to commit suicide.") - do_suicide(0) - -/mob/living/carbon/brain/verb/suicide() - set hidden = 1 - - if(stat == 2) - to_chat(src, "You're already dead!") - return - - if(!SSticker) - to_chat(src, "You can't commit suicide before the game starts!") - return - - if(suiciding) - to_chat(src, "You're already committing suicide! Be patient!") - return - - var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") - - if(confirm == "Yes") - suiciding = 1 - to_chat(viewers(loc), "[src]'s brain is growing dull and lifeless. It looks like it's lost the will to live.") - spawn(50) - death(0) - suiciding = 0 - - -/mob/living/silicon/ai/verb/suicide() - set hidden = 1 - - if(stat == 2) - to_chat(src, "You're already dead!") - return - - if(suiciding) - to_chat(src, "You're already committing suicide! Be patient!") - return - - var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") - - if(confirm == "Yes") - suiciding = 1 - to_chat(viewers(src), "[src] is powering down. It looks like [p_theyre()] trying to commit suicide.") - //put em at -175 - adjustOxyLoss(max(maxHealth * 2 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0)) - -/mob/living/silicon/robot/verb/suicide() - set hidden = 1 - - if(stat == 2) - to_chat(src, "You're already dead!") - return - - if(suiciding) - to_chat(src, "You're already committing suicide! Be patient!") - return - - var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") - - if(confirm == "Yes") - suiciding = 1 - to_chat(viewers(src), "[src] is powering down. It looks like [p_theyre()] trying to commit suicide.") - //put em at -175 - adjustOxyLoss(max(maxHealth * 2 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0)) - -/mob/living/silicon/pai/verb/suicide() - set category = "pAI Commands" - set desc = "Kill yourself and become a ghost (You will receive a confirmation prompt)" - set name = "pAI Suicide" - var/answer = input("REALLY kill yourself? This action can't be undone.", "Suicide", "No") in list ("Yes", "No") - if(answer == "Yes") - if(canmove || resting) - close_up() - var/obj/item/paicard/card = loc - card.removePersonality() - var/turf/T = get_turf_or_move(card.loc) - for(var/mob/M in viewers(T)) - M.show_message("[src] flashes a message across its screen, \"Wiping core files. Please acquire a new personality to continue using pAI device functions.\"", 3, "[src] bleeps electronically.", 2) - death(0, 1) - else - to_chat(src, "Aborting suicide attempt.") - -/mob/living/carbon/alien/humanoid/verb/suicide() - set hidden = 1 - - if(stat == 2) - to_chat(src, "You're already dead!") - return - - if(suiciding) - to_chat(src, "You're already committing suicide! Be patient!") - return - - var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") - - if(confirm == "Yes") - suiciding = 1 - to_chat(viewers(src), "[src] is thrashing wildly! It looks like [p_theyre()] trying to commit suicide.") - //put em at -175 - adjustOxyLoss(max(175 - getFireLoss() - getBruteLoss() - getOxyLoss(), 0)) - - -/mob/living/simple_animal/slime/verb/suicide() - set hidden = 1 - if(stat == 2) - to_chat(src, "You're already dead!") - return - - if(suiciding) - to_chat(src, "You're already committing suicide! Be patient!") - return - - var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") - - if(confirm == "Yes") - suiciding = 1 - setOxyLoss(100, FALSE) - adjustBruteLoss(100 - getBruteLoss(), FALSE) - setToxLoss(100, FALSE) - setCloneLoss(100, FALSE) - - updatehealth() - -/mob/living/simple_animal/mouse/verb/suicide() - set hidden = 1 - if(stat == DEAD) - to_chat(src, "You're already dead!") - return - if(suiciding) - to_chat(src, "You're already committing suicide! Be patient!") - return - - var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") - - if(confirm == "Yes") - suiciding = TRUE - visible_message("[src] is playing dead permanently! It looks like [p_theyre()] trying to commit suicide.") - adjustOxyLoss(max(100 - getBruteLoss(100), 0)) \ No newline at end of file +/mob/var/suiciding = 0 + +/mob/living/carbon/human/proc/do_suicide(damagetype, byitem) + var/threshold = check_death_method() ? ((HEALTH_THRESHOLD_CRIT + HEALTH_THRESHOLD_DEAD) / 2) : (HEALTH_THRESHOLD_DEAD - 50) + var/dmgamt = maxHealth - threshold + + var/damage_mod = 1 + switch(damagetype) //Sorry about the magic numbers. + //brute = 1, burn = 2, tox = 4, oxy = 8 + if(15) //4 damage types + damage_mod = 4 + + if(6, 11, 13, 14) //3 damage types + damage_mod = 3 + + if(3, 5, 7, 9, 10, 12) //2 damage types + damage_mod = 2 + + if(1, 2, 4, 8) //1 damage type + damage_mod = 1 + + else //This should not happen, but if it does, everything should still work + damage_mod = 1 + + //Do dmgamt damage divided by the number of damage types applied. + if(damagetype & BRUTELOSS) + adjustBruteLoss(dmgamt / damage_mod, FALSE) + + if(damagetype & FIRELOSS) + adjustFireLoss(dmgamt / damage_mod, FALSE) + + if(damagetype & TOXLOSS) + adjustToxLoss(dmgamt / damage_mod, FALSE) + + if(damagetype & OXYLOSS) + adjustOxyLoss(dmgamt / damage_mod, FALSE) + + // Failing that... + if(!(damagetype & BRUTELOSS) && !(damagetype & FIRELOSS) && !(damagetype & TOXLOSS) && !(damagetype & OXYLOSS)) + if(NO_BREATHE in dna.species.species_traits) + // the ultimate fallback + take_overall_damage(max(dmgamt - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0), 0, updating_health = FALSE) + else + adjustOxyLoss(max(dmgamt - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0), FALSE) + + var/obj/item/organ/external/affected = get_organ("head") + if(affected) + affected.add_autopsy_data(byitem ? "Suicide by [byitem]" : "Suicide", dmgamt) + + updatehealth() + +/mob/living/carbon/human/verb/suicide() + set hidden = 1 + + be_suicidal() + +/mob/living/carbon/human/proc/be_suicidal(forced = FALSE) + if(stat == DEAD) + to_chat(src, "You're already dead!") + return + + if(!SSticker) + to_chat(src, "You can't commit suicide before the game starts!") + return + + // No more borergrief, one way or the other + if(has_brain_worms()) + to_chat(src, "You try to bring yourself to commit suicide, but - something prevents you!") + return + + if(suiciding) + to_chat(src, "You're already committing suicide! Be patient!") + return + + + var/confirm = null + if(!forced) + confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") + + if(forced || (confirm == "Yes")) + suiciding = TRUE + var/obj/item/held_item = get_active_hand() + if(held_item) + var/damagetype = held_item.suicide_act(src) + if(damagetype) + if(damagetype & SHAME) + adjustStaminaLoss(200) + suiciding = FALSE + return + if(damagetype & OBLITERATION) // Does it gib or something? Don't deal damage + return + do_suicide(damagetype, held_item) + return + else + for(var/obj/O in orange(1, src)) + if(O.suicidal_hands) + continue + var/damagetype = O.suicide_act(src) + if(damagetype) + if(damagetype & SHAME) + adjustStaminaLoss(200) + suiciding = FALSE + return + if(damagetype & OBLITERATION) + return + do_suicide(damagetype, O) + return + + to_chat(viewers(src), "[src] [replacetext(pick(dna.species.suicide_messages), "their", p_their())] It looks like [p_theyre()] trying to commit suicide.") + do_suicide(0) + +/mob/living/carbon/brain/verb/suicide() + set hidden = 1 + + if(stat == 2) + to_chat(src, "You're already dead!") + return + + if(!SSticker) + to_chat(src, "You can't commit suicide before the game starts!") + return + + if(suiciding) + to_chat(src, "You're already committing suicide! Be patient!") + return + + var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") + + if(confirm == "Yes") + suiciding = 1 + to_chat(viewers(loc), "[src]'s brain is growing dull and lifeless. It looks like it's lost the will to live.") + spawn(50) + death(0) + suiciding = 0 + + +/mob/living/silicon/ai/verb/suicide() + set hidden = 1 + + if(stat == 2) + to_chat(src, "You're already dead!") + return + + if(suiciding) + to_chat(src, "You're already committing suicide! Be patient!") + return + + var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") + + if(confirm == "Yes") + suiciding = 1 + to_chat(viewers(src), "[src] is powering down. It looks like [p_theyre()] trying to commit suicide.") + //put em at -175 + adjustOxyLoss(max(maxHealth * 2 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0)) + +/mob/living/silicon/robot/verb/suicide() + set hidden = 1 + + if(stat == 2) + to_chat(src, "You're already dead!") + return + + if(suiciding) + to_chat(src, "You're already committing suicide! Be patient!") + return + + var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") + + if(confirm == "Yes") + suiciding = 1 + to_chat(viewers(src), "[src] is powering down. It looks like [p_theyre()] trying to commit suicide.") + //put em at -175 + adjustOxyLoss(max(maxHealth * 2 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0)) + +/mob/living/silicon/pai/verb/suicide() + set category = "pAI Commands" + set desc = "Kill yourself and become a ghost (You will receive a confirmation prompt)" + set name = "pAI Suicide" + var/answer = input("REALLY kill yourself? This action can't be undone.", "Suicide", "No") in list ("Yes", "No") + if(answer == "Yes") + if(canmove || resting) + close_up() + var/obj/item/paicard/card = loc + card.removePersonality() + var/turf/T = get_turf_or_move(card.loc) + for(var/mob/M in viewers(T)) + M.show_message("[src] flashes a message across its screen, \"Wiping core files. Please acquire a new personality to continue using pAI device functions.\"", 3, "[src] bleeps electronically.", 2) + death(0, 1) + else + to_chat(src, "Aborting suicide attempt.") + +/mob/living/carbon/alien/humanoid/verb/suicide() + set hidden = 1 + + if(stat == 2) + to_chat(src, "You're already dead!") + return + + if(suiciding) + to_chat(src, "You're already committing suicide! Be patient!") + return + + var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") + + if(confirm == "Yes") + suiciding = 1 + to_chat(viewers(src), "[src] is thrashing wildly! It looks like [p_theyre()] trying to commit suicide.") + //put em at -175 + adjustOxyLoss(max(175 - getFireLoss() - getBruteLoss() - getOxyLoss(), 0)) + + +/mob/living/simple_animal/slime/verb/suicide() + set hidden = 1 + if(stat == 2) + to_chat(src, "You're already dead!") + return + + if(suiciding) + to_chat(src, "You're already committing suicide! Be patient!") + return + + var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") + + if(confirm == "Yes") + suiciding = 1 + setOxyLoss(100, FALSE) + adjustBruteLoss(100 - getBruteLoss(), FALSE) + setToxLoss(100, FALSE) + setCloneLoss(100, FALSE) + + updatehealth() + +/mob/living/simple_animal/mouse/verb/suicide() + set hidden = 1 + if(stat == DEAD) + to_chat(src, "You're already dead!") + return + if(suiciding) + to_chat(src, "You're already committing suicide! Be patient!") + return + + var/confirm = alert("Are you sure you want to commit suicide?", "Confirm Suicide", "Yes", "No") + + if(confirm == "Yes") + suiciding = TRUE + visible_message("[src] is playing dead permanently! It looks like [p_theyre()] trying to commit suicide.") + adjustOxyLoss(max(100 - getBruteLoss(100), 0)) diff --git a/code/game/verbs/who.dm b/code/game/verbs/who.dm index 8a322edd2f8..2683e236269 100644 --- a/code/game/verbs/who.dm +++ b/code/game/verbs/who.dm @@ -1,130 +1,130 @@ - -/client/verb/who() - set name = "Who" - set category = "OOC" - - var/msg = "Current Players:\n" - - - var/list/Lines = list() - - if(check_rights(R_ADMIN,0)) - for(var/client/C in GLOB.clients) - if(C.holder && C.holder.big_brother && !check_rights(R_PERMISSIONS, 0)) // need PERMISSIONS to see BB - continue - - var/entry = "\t[C.key]" - if(C.holder && C.holder.fakekey) - entry += " (as [C.holder.fakekey])" - entry += " - Playing as [C.mob.real_name]" - switch(C.mob.stat) - if(UNCONSCIOUS) - entry += " - Unconscious" - if(DEAD) - if(isobserver(C.mob)) - var/mob/dead/observer/O = C.mob - if(O.started_as_observer) - entry += " - Observing" - else - entry += " - DEAD" - else if(istype(C.mob, /mob/new_player)) - entry += " - New Player" - else - entry += " - DEAD" - - var/age - if(isnum(C.player_age)) - age = C.player_age - else - age = 0 - - if(age <= 1) - age = "[age]" - else if(age < 10) - age = "[age]" - - entry += " - [age]" - - if(is_special_character(C.mob)) - entry += " - Antagonist" - entry += " ([ADMIN_QUE(C.mob,"?")])" - Lines += entry - else - for(var/client/C in GLOB.clients) - if(C.holder && C.holder.big_brother) // BB doesn't show up at all - continue - - if(C.holder && C.holder.fakekey) - Lines += C.holder.fakekey - else - Lines += C.key - - for(var/line in sortList(Lines)) - msg += "[line]\n" - - msg += "Total Players: [length(Lines)]" - to_chat(src, msg) - -/client/verb/adminwho() - set category = "Admin" - set name = "Adminwho" - - var/msg = "" - var/modmsg = "" - var/num_mods_online = 0 - var/num_admins_online = 0 - if(holder) - for(var/client/C in GLOB.admins) - if(check_rights(R_ADMIN, 0, C.mob)) - - if(C.holder.fakekey && !check_rights(R_ADMIN, 0)) //Mentors can't see stealthmins - continue - - if(C.holder.big_brother && !check_rights(R_PERMISSIONS, 0)) // normal admins can't see BB - continue - - msg += "\t[C] is a [C.holder.rank]" - - if(C.holder.fakekey) - msg += " (as [C.holder.fakekey])" - - if(isobserver(C.mob)) - msg += " - Observing" - else if(istype(C.mob,/mob/new_player)) - msg += " - Lobby" - else - msg += " - Playing" - - if(C.is_afk()) - msg += " (AFK)" - msg += "\n" - - num_admins_online++ - - else if(check_rights(R_MENTOR|R_MOD, 0, C.mob)) - modmsg += "\t[C] is a [C.holder.rank]" - - if(isobserver(C.mob)) - modmsg += " - Observing" - else if(istype(C.mob,/mob/new_player)) - modmsg += " - Lobby" - else - modmsg += " - Playing" - - if(C.is_afk()) - modmsg += " (AFK)" - modmsg += "\n" - num_mods_online++ - else - for(var/client/C in GLOB.admins) - - if(check_rights(R_ADMIN, 0, C.mob)) - if(!C.holder.fakekey) - msg += "\t[C] is a [C.holder.rank]\n" - num_admins_online++ - else if(check_rights(R_MOD|R_MENTOR, 0, C.mob) && !check_rights(R_ADMIN, 0, C.mob)) - modmsg += "\t[C] is a [C.holder.rank]\n" - num_mods_online++ - - msg = "Current Admins ([num_admins_online]):\n" + msg + "\nCurrent Mentors ([num_mods_online]):\n" + modmsg - to_chat(src, msg) + +/client/verb/who() + set name = "Who" + set category = "OOC" + + var/msg = "Current Players:\n" + + + var/list/Lines = list() + + if(check_rights(R_ADMIN,0)) + for(var/client/C in GLOB.clients) + if(C.holder && C.holder.big_brother && !check_rights(R_PERMISSIONS, 0)) // need PERMISSIONS to see BB + continue + + var/entry = "\t[C.key]" + if(C.holder && C.holder.fakekey) + entry += " (as [C.holder.fakekey])" + entry += " - Playing as [C.mob.real_name]" + switch(C.mob.stat) + if(UNCONSCIOUS) + entry += " - Unconscious" + if(DEAD) + if(isobserver(C.mob)) + var/mob/dead/observer/O = C.mob + if(O.started_as_observer) + entry += " - Observing" + else + entry += " - DEAD" + else if(istype(C.mob, /mob/new_player)) + entry += " - New Player" + else + entry += " - DEAD" + + var/age + if(isnum(C.player_age)) + age = C.player_age + else + age = 0 + + if(age <= 1) + age = "[age]" + else if(age < 10) + age = "[age]" + + entry += " - [age]" + + if(is_special_character(C.mob)) + entry += " - Antagonist" + entry += " ([ADMIN_QUE(C.mob,"?")])" + Lines += entry + else + for(var/client/C in GLOB.clients) + if(C.holder && C.holder.big_brother) // BB doesn't show up at all + continue + + if(C.holder && C.holder.fakekey) + Lines += C.holder.fakekey + else + Lines += C.key + + for(var/line in sortList(Lines)) + msg += "[line]\n" + + msg += "Total Players: [length(Lines)]" + to_chat(src, msg) + +/client/verb/adminwho() + set category = "Admin" + set name = "Adminwho" + + var/msg = "" + var/modmsg = "" + var/num_mods_online = 0 + var/num_admins_online = 0 + if(holder) + for(var/client/C in GLOB.admins) + if(check_rights(R_ADMIN, 0, C.mob)) + + if(C.holder.fakekey && !check_rights(R_ADMIN, 0)) //Mentors can't see stealthmins + continue + + if(C.holder.big_brother && !check_rights(R_PERMISSIONS, 0)) // normal admins can't see BB + continue + + msg += "\t[C] is a [C.holder.rank]" + + if(C.holder.fakekey) + msg += " (as [C.holder.fakekey])" + + if(isobserver(C.mob)) + msg += " - Observing" + else if(istype(C.mob,/mob/new_player)) + msg += " - Lobby" + else + msg += " - Playing" + + if(C.is_afk()) + msg += " (AFK)" + msg += "\n" + + num_admins_online++ + + else if(check_rights(R_MENTOR|R_MOD, 0, C.mob)) + modmsg += "\t[C] is a [C.holder.rank]" + + if(isobserver(C.mob)) + modmsg += " - Observing" + else if(istype(C.mob,/mob/new_player)) + modmsg += " - Lobby" + else + modmsg += " - Playing" + + if(C.is_afk()) + modmsg += " (AFK)" + modmsg += "\n" + num_mods_online++ + else + for(var/client/C in GLOB.admins) + + if(check_rights(R_ADMIN, 0, C.mob)) + if(!C.holder.fakekey) + msg += "\t[C] is a [C.holder.rank]\n" + num_admins_online++ + else if(check_rights(R_MOD|R_MENTOR, 0, C.mob) && !check_rights(R_ADMIN, 0, C.mob)) + modmsg += "\t[C] is a [C.holder.rank]\n" + num_mods_online++ + + msg = "Current Admins ([num_admins_online]):\n" + msg + "\nCurrent Mentors ([num_mods_online]):\n" + modmsg + to_chat(src, msg) diff --git a/code/game/world.dm b/code/game/world.dm index cccf5eb497a..83407a6ae46 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -475,4 +475,4 @@ proc/establish_db_connection() /world/proc/enable_debugger() var/dll = world.GetConfig("env", "EXTOOLS_DLL") if (dll) - call(dll, "debug_initialize")() \ No newline at end of file + call(dll, "debug_initialize")() diff --git a/code/hub.dm b/code/hub.dm index d54b49e1011..665193a9d5b 100644 --- a/code/hub.dm +++ b/code/hub.dm @@ -1,14 +1,14 @@ -/world - - hub = "Exadv1.spacestation13" - hub_password = "kMZy3U5jJHSiBQjr" - name = "Space Station 13" - /var/hub_password_base = "kMZy3U5jJHSiBQjr" -/* This is for any host that would like their server to appear on the main SS13 hub. -To use it, simply replace the password above, with the password found below, and it should work. -If not, let us know on the main tgstation IRC channel of irc.rizon.net #tgstation13 we can help you there. - - hub = "Exadv1.spacestation13" - hub_password = "kMZy3U5jJHSiBQjr" - name = "Space Station 13" -*/ +/world + + hub = "Exadv1.spacestation13" + hub_password = "kMZy3U5jJHSiBQjr" + name = "Space Station 13" + /var/hub_password_base = "kMZy3U5jJHSiBQjr" +/* This is for any host that would like their server to appear on the main SS13 hub. +To use it, simply replace the password above, with the password found below, and it should work. +If not, let us know on the main tgstation IRC channel of irc.rizon.net #tgstation13 we can help you there. + + hub = "Exadv1.spacestation13" + hub_password = "kMZy3U5jJHSiBQjr" + name = "Space Station 13" +*/ diff --git a/code/modules/admin/DB ban/functions.dm b/code/modules/admin/DB ban/functions.dm index 86a6b228b60..8168583e6fa 100644 --- a/code/modules/admin/DB ban/functions.dm +++ b/code/modules/admin/DB ban/functions.dm @@ -1,583 +1,583 @@ -#define MAX_ADMIN_BANS_PER_ADMIN 1 - -datum/admins/proc/DB_ban_record(var/bantype, var/mob/banned_mob, var/duration = -1, var/reason, var/job = "", var/rounds = 0, var/banckey = null, var/banip = null, var/bancid = null) - - if(!check_rights(R_BAN)) return - - establish_db_connection() - if(!dbcon.IsConnected()) - return - - var/serverip = "[world.internet_address]:[world.port]" - var/bantype_pass = 0 - var/bantype_str - var/maxadminbancheck //Used to limit the number of active bans of a certein type that each admin can give. Used to protect against abuse or mutiny. - var/announceinirc //When set, it announces the ban in irc. Intended to be a way to raise an alarm, so to speak. - var/blockselfban //Used to prevent the banning of yourself. - var/kickbannedckey //Defines whether this proc should kick the banned person, if they are connected (if banned_mob is defined). - //some ban types kick players after this proc passes (tempban, permaban), but some are specific to db_ban, so - //they should kick within this proc. - var/isjobban // For job bans, which need to be inserted into the job ban lists - switch(bantype) - if(BANTYPE_PERMA) - bantype_str = "PERMABAN" - duration = -1 - bantype_pass = 1 - blockselfban = 1 - if(BANTYPE_TEMP) - bantype_str = "TEMPBAN" - bantype_pass = 1 - blockselfban = 1 - if(BANTYPE_JOB_PERMA) - bantype_str = "JOB_PERMABAN" - duration = -1 - bantype_pass = 1 - isjobban = 1 - if(BANTYPE_JOB_TEMP) - bantype_str = "JOB_TEMPBAN" - bantype_pass = 1 - isjobban = 1 - if(BANTYPE_APPEARANCE) - bantype_str = "APPEARANCE_BAN" - duration = -1 - bantype_pass = 1 - if(BANTYPE_ADMIN_PERMA) - bantype_str = "ADMIN_PERMABAN" - duration = -1 - bantype_pass = 1 - maxadminbancheck = 1 - announceinirc = 1 - blockselfban = 1 - kickbannedckey = 1 - if(BANTYPE_ADMIN_TEMP) - bantype_str = "ADMIN_TEMPBAN" - bantype_pass = 1 - maxadminbancheck = 1 - announceinirc = 1 - blockselfban = 1 - kickbannedckey = 1 - - if( !bantype_pass ) return - if( !istext(reason) ) return - if( !isnum(duration) ) return - - var/ckey - var/computerid - var/ip - - if(ismob(banned_mob) && banned_mob.ckey) - ckey = banned_mob.ckey - if(banned_mob.client) - computerid = banned_mob.client.computer_id - ip = banned_mob.client.address - else - if(banned_mob.lastKnownIP) - ip = banned_mob.lastKnownIP - if(banned_mob.computer_id) - computerid = banned_mob.computer_id - else if(banckey) - ckey = ckey(banckey) - computerid = bancid - ip = banip - else if(ismob(banned_mob)) - message_admins("[key_name_admin(usr)] attempted to add a ban based on a ckey-less mob, with no ckey provided. Report this bug.",1) - return - else - message_admins("[key_name_admin(usr)] attempted to add a ban based on a non-existent mob, with no ckey provided. Report this bug.",1) - return - - var/DBQuery/query = dbcon.NewQuery("SELECT id FROM [format_table_name("player")] WHERE ckey = '[ckey]'") - query.Execute() - var/validckey = 0 - if(query.NextRow()) - validckey = 1 - if(!validckey) - if(!banned_mob || (banned_mob && !IsGuestKey(banned_mob.key))) - message_admins("[key_name_admin(usr)] attempted to ban [ckey], but [ckey] does not exist in the player database. Please only ban actual players.",1) - return - - var/a_ckey - var/a_computerid - var/a_ip - - if(src.owner && istype(src.owner, /client)) - a_ckey = src.owner:ckey - a_computerid = src.owner:computer_id - a_ip = src.owner:address - - if(blockselfban) - if(a_ckey == ckey) - to_chat(usr, "You cannot apply this ban type on yourself.") - return - - var/who - for(var/client/C in GLOB.clients) - if(!who) - who = "[C]" - else - who += ", [C]" - - var/adminwho - for(var/client/C in GLOB.admins) - if(!adminwho) - adminwho = "[C]" - else - adminwho += ", [C]" - - reason = sanitizeSQL(reason) - - if(maxadminbancheck) - var/DBQuery/adm_query = dbcon.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") - adm_query.Execute() - if(adm_query.NextRow()) - var/adm_bans = text2num(adm_query.item[1]) - if(adm_bans >= MAX_ADMIN_BANS_PER_ADMIN) - to_chat(usr, "You already logged [MAX_ADMIN_BANS_PER_ADMIN] admin ban(s) or more. Do not abuse this function!") - return - - var/sql = "INSERT INTO [format_table_name("ban")] (`id`,`bantime`,`serverip`,`bantype`,`reason`,`job`,`duration`,`rounds`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`,`edits`,`unbanned`,`unbanned_datetime`,`unbanned_ckey`,`unbanned_computerid`,`unbanned_ip`) VALUES (null, Now(), '[serverip]', '[bantype_str]', '[reason]', '[job]', [(duration)?"[duration]":"0"], [(rounds)?"[rounds]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[ckey]', '[computerid]', '[ip]', '[a_ckey]', '[a_computerid]', '[a_ip]', '[who]', '[adminwho]', '', null, null, null, null, null)" - var/DBQuery/query_insert = dbcon.NewQuery(sql) - query_insert.Execute() - to_chat(usr, "Ban saved to database.") - message_admins("[key_name_admin(usr)] has added a [bantype_str] for [ckey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database.",1) - - if(announceinirc) - send2irc("BAN ALERT","[a_ckey] applied a [bantype_str] on [ckey]") - - if(kickbannedckey) - if(banned_mob && banned_mob.client && banned_mob.client.ckey == banckey) - del(banned_mob.client) - - if(isjobban) - jobban_client_fullban(ckey, job) - else - flag_account_for_forum_sync(ckey) - -datum/admins/proc/DB_ban_unban(var/ckey, var/bantype, var/job = "") - - if(!check_rights(R_BAN)) return - - var/bantype_str - var/isjobban // For job bans, which need to be removed from the job ban lists - if(bantype) - var/bantype_pass = 0 - switch(bantype) - if(BANTYPE_PERMA) - bantype_str = "PERMABAN" - bantype_pass = 1 - if(BANTYPE_TEMP) - bantype_str = "TEMPBAN" - bantype_pass = 1 - if(BANTYPE_JOB_PERMA) - bantype_str = "JOB_PERMABAN" - bantype_pass = 1 - isjobban = 1 - if(BANTYPE_JOB_TEMP) - bantype_str = "JOB_TEMPBAN" - bantype_pass = 1 - isjobban = 1 - if(BANTYPE_APPEARANCE) - bantype_str = "APPEARANCE_BAN" - bantype_pass = 1 - if(BANTYPE_ADMIN_PERMA) - bantype_str = "ADMIN_PERMABAN" - bantype_pass = 1 - if(BANTYPE_ADMIN_TEMP) - bantype_str = "ADMIN_TEMPBAN" - bantype_pass = 1 - if(BANTYPE_ANY_FULLBAN) - bantype_str = "ANY" - bantype_pass = 1 - if( !bantype_pass ) return - - var/bantype_sql - if(bantype_str == "ANY") - bantype_sql = "(bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now() ) )" - else - bantype_sql = "bantype = '[bantype_str]'" - - var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = '[ckey]' AND [bantype_sql] AND (unbanned is null OR unbanned = false)" - if(job) - sql += " AND job = '[job]'" - - establish_db_connection() - if(!dbcon.IsConnected()) - return - - var/ban_id - var/ban_number = 0 //failsafe - - var/DBQuery/query = dbcon.NewQuery(sql) - query.Execute() - while(query.NextRow()) - ban_id = query.item[1] - ban_number++; - - if(ban_number == 0) - to_chat(usr, "Database update failed due to no bans fitting the search criteria. If this is not a legacy ban you should contact the database admin.") - return - - if(ban_number > 1) - to_chat(usr, "Database update failed due to multiple bans fitting the search criteria. Note down the ckey, job and current time and contact the database admin.") - return - - if(istext(ban_id)) - ban_id = text2num(ban_id) - if(!isnum(ban_id)) - to_chat(usr, "Database update failed due to a ban ID mismatch. Contact the database admin.") - return - - DB_ban_unban_by_id(ban_id) - if(isjobban) - jobban_unban_client(ckey, job) - else - flag_account_for_forum_sync(ckey) - -datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null) - - if(!check_rights(R_BAN)) return - - if(!isnum(banid) || !istext(param)) - to_chat(usr, "Cancelled") - return - - var/DBQuery/query = dbcon.NewQuery("SELECT ckey, duration, reason, job FROM [format_table_name("ban")] WHERE id = [banid]") - query.Execute() - - var/eckey = usr.ckey //Editing admin ckey - var/pckey //(banned) Player ckey - var/duration //Old duration - var/reason //Old reason - var/job //Old job - - if(query.NextRow()) - pckey = query.item[1] - duration = query.item[2] - reason = query.item[3] - job = query.item[4] - else - to_chat(usr, "Invalid ban id. Contact the database admin") - return - - reason = sanitizeSQL(reason) - var/value - - switch(param) - if("reason") - if(!value) - value = input("Insert the new reason for [pckey]'s ban", "New Reason", "[reason]", null) as null|text - value = sanitizeSQL(value) - if(!value) - to_chat(usr, "Cancelled") - return - - var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [eckey] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
        ') WHERE id = [banid]") - update_query.Execute() - message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s reason from [reason] to [value]",1) - if("duration") - if(!value) - value = input("Insert the new duration (in minutes) for [pckey]'s ban", "New Duration", "[duration]", null) as null|num - if(!isnum(value) || !value) - to_chat(usr, "Cancelled") - return - - var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [eckey] changed ban duration from [duration] to [value]
        '), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") - message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s duration from [duration] to [value]",1) - update_query.Execute() - if("unban") - if(alert("Unban [pckey]?", "Unban?", "Yes", "No") == "Yes") - DB_ban_unban_by_id(banid) - if(job && length(job)) - jobban_unban_client(pckey, job) - return - else - to_chat(usr, "Cancelled") - return - else - to_chat(usr, "Cancelled") - return - -datum/admins/proc/DB_ban_unban_by_id(var/id) - - if(!check_rights(R_BAN)) return - - var/sql = "SELECT ckey FROM [format_table_name("ban")] WHERE id = [id]" - - establish_db_connection() - if(!dbcon.IsConnected()) - return - - var/ban_number = 0 //failsafe - - var/pckey - var/DBQuery/query = dbcon.NewQuery(sql) - query.Execute() - while(query.NextRow()) - pckey = query.item[1] - ban_number++; - - if(ban_number == 0) - to_chat(usr, "Database update failed due to a ban id not being present in the database.") - return - - if(ban_number > 1) - to_chat(usr, "Database update failed due to multiple bans having the same ID. Contact the database admin.") - return - - if(!src.owner || !istype(src.owner, /client)) - return - - var/unban_ckey = src.owner:ckey - var/unban_computerid = src.owner:computer_id - var/unban_ip = src.owner:address - - var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = '[unban_ip]' WHERE id = [id]" - message_admins("[key_name_admin(usr)] has lifted [pckey]'s ban.",1) - - var/DBQuery/query_update = dbcon.NewQuery(sql_update) - query_update.Execute() - - flag_account_for_forum_sync(pckey) - - -/client/proc/DB_ban_panel() - set category = "Admin" - set name = "Banning Panel" - set desc = "Edit admin permissions" - - if(!holder) - return - - holder.DB_ban_panel() - - -/datum/admins/proc/DB_ban_panel(var/playerckey = null, var/adminckey = null, var/playerip = null, var/playercid = null, var/dbbantype = null, var/match = null) - - if(!usr.client) - return - - if(!check_rights(R_BAN)) return - - establish_db_connection() - if(!dbcon.IsConnected()) - to_chat(usr, "Failed to establish database connection") - return - - var/output = "
        " - - output += "" - - output += "" - output += "" - output += "
        " - output += "

        Banning panel

        " - output += "
        " - - output += "
        Add custom ban: (ONLY use this if you can't ban through any other method)" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "
        Ban type:Ckey:
        IP: CID:
        Duration: Job:
        " - output += "Reason:

        " - output += "" - output += "
        " - - output += "
        " - - output += "
        " - output += "" - output += "" - output += "" - output += "" - output += "
        Search:" - output += "
        Ckey: Admin ckey:
        IP: CID:
        Ban type:
        " - output += "

        " - output += " Match(min. 3 characters to search by key or ip, and 7 to search by cid)
        " - output += "
        " - output += "This search shows only last 100 bans." - - if(adminckey || playerckey || playerip || playercid || dbbantype) - - adminckey = ckey(adminckey) - playerckey = ckey(playerckey) - playerip = sanitizeSQL(playerip) - playercid = sanitizeSQL(playercid) - - if(adminckey || playerckey || playerip || playercid || dbbantype) - - var/blcolor = "#ffeeee" //banned light - var/bdcolor = "#ffdddd" //banned dark - var/ulcolor = "#eeffee" //unbanned light - var/udcolor = "#ddffdd" //unbanned dark - - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - - var/adminsearch = "" - var/playersearch = "" - var/ipsearch = "" - var/cidsearch = "" - var/bantypesearch = "" - - if(!match) - if(adminckey) - adminsearch = "AND a_ckey = '[adminckey]' " - if(playerckey) - playersearch = "AND ckey = '[playerckey]' " - if(playerip) - ipsearch = "AND ip = '[playerip]' " - if(playercid) - cidsearch = "AND computerid = '[playercid]' " - else - if(adminckey && length(adminckey) >= 3) - adminsearch = "AND a_ckey LIKE '[adminckey]%' " - if(playerckey && length(playerckey) >= 3) - playersearch = "AND ckey LIKE '[playerckey]%' " - if(playerip && length(playerip) >= 3) - ipsearch = "AND ip LIKE '[playerip]%' " - if(playercid && length(playercid) >= 7) - cidsearch = "AND computerid LIKE '[playercid]%' " - - if(dbbantype) - bantypesearch = "AND bantype = " - - switch(dbbantype) - if(BANTYPE_TEMP) - bantypesearch += "'TEMPBAN' " - if(BANTYPE_JOB_PERMA) - bantypesearch += "'JOB_PERMABAN' " - if(BANTYPE_JOB_TEMP) - bantypesearch += "'JOB_TEMPBAN' " - if(BANTYPE_APPEARANCE) - bantypesearch += "'APPEARANCE_BAN' " - if(BANTYPE_ADMIN_PERMA) - bantypesearch = "'ADMIN_PERMABAN' " - if(BANTYPE_ADMIN_TEMP) - bantypesearch = "'ADMIN_TEMPBAN' " - else - bantypesearch += "'PERMABAN' " - - - var/DBQuery/select_query = dbcon.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, ckey, a_ckey, unbanned, unbanned_ckey, unbanned_datetime, edits, ip, computerid FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch] [ipsearch] [cidsearch] [bantypesearch] ORDER BY bantime DESC LIMIT 100") - select_query.Execute() - - while(select_query.NextRow()) - var/banid = select_query.item[1] - var/bantime = select_query.item[2] - var/bantype = select_query.item[3] - var/reason = select_query.item[4] - var/job = select_query.item[5] - var/duration = select_query.item[6] - var/expiration = select_query.item[7] - var/ckey = select_query.item[8] - var/ackey = select_query.item[9] - var/unbanned = select_query.item[10] - var/unbanckey = select_query.item[11] - var/unbantime = select_query.item[12] - var/edits = select_query.item[13] - var/ip = select_query.item[14] - var/cid = select_query.item[15] - - var/lcolor = blcolor - var/dcolor = bdcolor - if(unbanned) - lcolor = ulcolor - dcolor = udcolor - - var/typedesc ="" - switch(bantype) - if("PERMABAN") - typedesc = "PERMABAN" - if("TEMPBAN") - typedesc = "TEMPBAN
        ([duration] minutes [(unbanned) ? "" : "(Edit))"]
        Expires [expiration]
        " - if("JOB_PERMABAN") - typedesc = "JOBBAN
        ([job])" - if("JOB_TEMPBAN") - typedesc = "TEMP JOBBAN
        ([job])
        ([duration] minutes
        Expires [expiration]" - if("APPEARANCE_BAN") - typedesc = "APPEARANCE/NAME BAN" - if("ADMIN_PERMABAN") - typedesc = "ADMIN PERMABAN" - if("ADMIN_TEMPBAN") - typedesc = "ADMIN TEMPBAN
        ([duration] minutes [(unbanned) ? "" : "(Edit))"]
        Expires [expiration]
        " - - output += "
        " - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - if(edits) - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - if(unbanned) - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - - output += "
        TYPECKEYTIME APPLIEDADMINOPTIONS
        [typedesc][ckey][bantime][ackey][(unbanned) ? "" : "Unban"]
        IP: [ip]CIP: [cid]
        Reason: [(unbanned) ? "" : "(Edit)"] \"[reason]\"
        EDITS
        [edits]
        UNBANNED by admin [unbanckey] on [unbantime]
         
        " - - usr << browse(output,"window=lookupbans;size=900x700") - -/proc/flag_account_for_forum_sync(ckey) - if(!dbcon) - return - var/skey = sanitizeSQL(ckey) - var/sql = "UPDATE [format_table_name("player")] SET fupdate = 1 WHERE ckey = '[skey]'" - var/DBQuery/adm_query = dbcon.NewQuery(sql) - adm_query.Execute() +#define MAX_ADMIN_BANS_PER_ADMIN 1 + +datum/admins/proc/DB_ban_record(var/bantype, var/mob/banned_mob, var/duration = -1, var/reason, var/job = "", var/rounds = 0, var/banckey = null, var/banip = null, var/bancid = null) + + if(!check_rights(R_BAN)) return + + establish_db_connection() + if(!dbcon.IsConnected()) + return + + var/serverip = "[world.internet_address]:[world.port]" + var/bantype_pass = 0 + var/bantype_str + var/maxadminbancheck //Used to limit the number of active bans of a certein type that each admin can give. Used to protect against abuse or mutiny. + var/announceinirc //When set, it announces the ban in irc. Intended to be a way to raise an alarm, so to speak. + var/blockselfban //Used to prevent the banning of yourself. + var/kickbannedckey //Defines whether this proc should kick the banned person, if they are connected (if banned_mob is defined). + //some ban types kick players after this proc passes (tempban, permaban), but some are specific to db_ban, so + //they should kick within this proc. + var/isjobban // For job bans, which need to be inserted into the job ban lists + switch(bantype) + if(BANTYPE_PERMA) + bantype_str = "PERMABAN" + duration = -1 + bantype_pass = 1 + blockselfban = 1 + if(BANTYPE_TEMP) + bantype_str = "TEMPBAN" + bantype_pass = 1 + blockselfban = 1 + if(BANTYPE_JOB_PERMA) + bantype_str = "JOB_PERMABAN" + duration = -1 + bantype_pass = 1 + isjobban = 1 + if(BANTYPE_JOB_TEMP) + bantype_str = "JOB_TEMPBAN" + bantype_pass = 1 + isjobban = 1 + if(BANTYPE_APPEARANCE) + bantype_str = "APPEARANCE_BAN" + duration = -1 + bantype_pass = 1 + if(BANTYPE_ADMIN_PERMA) + bantype_str = "ADMIN_PERMABAN" + duration = -1 + bantype_pass = 1 + maxadminbancheck = 1 + announceinirc = 1 + blockselfban = 1 + kickbannedckey = 1 + if(BANTYPE_ADMIN_TEMP) + bantype_str = "ADMIN_TEMPBAN" + bantype_pass = 1 + maxadminbancheck = 1 + announceinirc = 1 + blockselfban = 1 + kickbannedckey = 1 + + if( !bantype_pass ) return + if( !istext(reason) ) return + if( !isnum(duration) ) return + + var/ckey + var/computerid + var/ip + + if(ismob(banned_mob) && banned_mob.ckey) + ckey = banned_mob.ckey + if(banned_mob.client) + computerid = banned_mob.client.computer_id + ip = banned_mob.client.address + else + if(banned_mob.lastKnownIP) + ip = banned_mob.lastKnownIP + if(banned_mob.computer_id) + computerid = banned_mob.computer_id + else if(banckey) + ckey = ckey(banckey) + computerid = bancid + ip = banip + else if(ismob(banned_mob)) + message_admins("[key_name_admin(usr)] attempted to add a ban based on a ckey-less mob, with no ckey provided. Report this bug.",1) + return + else + message_admins("[key_name_admin(usr)] attempted to add a ban based on a non-existent mob, with no ckey provided. Report this bug.",1) + return + + var/DBQuery/query = dbcon.NewQuery("SELECT id FROM [format_table_name("player")] WHERE ckey = '[ckey]'") + query.Execute() + var/validckey = 0 + if(query.NextRow()) + validckey = 1 + if(!validckey) + if(!banned_mob || (banned_mob && !IsGuestKey(banned_mob.key))) + message_admins("[key_name_admin(usr)] attempted to ban [ckey], but [ckey] does not exist in the player database. Please only ban actual players.",1) + return + + var/a_ckey + var/a_computerid + var/a_ip + + if(src.owner && istype(src.owner, /client)) + a_ckey = src.owner:ckey + a_computerid = src.owner:computer_id + a_ip = src.owner:address + + if(blockselfban) + if(a_ckey == ckey) + to_chat(usr, "You cannot apply this ban type on yourself.") + return + + var/who + for(var/client/C in GLOB.clients) + if(!who) + who = "[C]" + else + who += ", [C]" + + var/adminwho + for(var/client/C in GLOB.admins) + if(!adminwho) + adminwho = "[C]" + else + adminwho += ", [C]" + + reason = sanitizeSQL(reason) + + if(maxadminbancheck) + var/DBQuery/adm_query = dbcon.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") + adm_query.Execute() + if(adm_query.NextRow()) + var/adm_bans = text2num(adm_query.item[1]) + if(adm_bans >= MAX_ADMIN_BANS_PER_ADMIN) + to_chat(usr, "You already logged [MAX_ADMIN_BANS_PER_ADMIN] admin ban(s) or more. Do not abuse this function!") + return + + var/sql = "INSERT INTO [format_table_name("ban")] (`id`,`bantime`,`serverip`,`bantype`,`reason`,`job`,`duration`,`rounds`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`,`edits`,`unbanned`,`unbanned_datetime`,`unbanned_ckey`,`unbanned_computerid`,`unbanned_ip`) VALUES (null, Now(), '[serverip]', '[bantype_str]', '[reason]', '[job]', [(duration)?"[duration]":"0"], [(rounds)?"[rounds]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[ckey]', '[computerid]', '[ip]', '[a_ckey]', '[a_computerid]', '[a_ip]', '[who]', '[adminwho]', '', null, null, null, null, null)" + var/DBQuery/query_insert = dbcon.NewQuery(sql) + query_insert.Execute() + to_chat(usr, "Ban saved to database.") + message_admins("[key_name_admin(usr)] has added a [bantype_str] for [ckey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database.",1) + + if(announceinirc) + send2irc("BAN ALERT","[a_ckey] applied a [bantype_str] on [ckey]") + + if(kickbannedckey) + if(banned_mob && banned_mob.client && banned_mob.client.ckey == banckey) + del(banned_mob.client) + + if(isjobban) + jobban_client_fullban(ckey, job) + else + flag_account_for_forum_sync(ckey) + +datum/admins/proc/DB_ban_unban(var/ckey, var/bantype, var/job = "") + + if(!check_rights(R_BAN)) return + + var/bantype_str + var/isjobban // For job bans, which need to be removed from the job ban lists + if(bantype) + var/bantype_pass = 0 + switch(bantype) + if(BANTYPE_PERMA) + bantype_str = "PERMABAN" + bantype_pass = 1 + if(BANTYPE_TEMP) + bantype_str = "TEMPBAN" + bantype_pass = 1 + if(BANTYPE_JOB_PERMA) + bantype_str = "JOB_PERMABAN" + bantype_pass = 1 + isjobban = 1 + if(BANTYPE_JOB_TEMP) + bantype_str = "JOB_TEMPBAN" + bantype_pass = 1 + isjobban = 1 + if(BANTYPE_APPEARANCE) + bantype_str = "APPEARANCE_BAN" + bantype_pass = 1 + if(BANTYPE_ADMIN_PERMA) + bantype_str = "ADMIN_PERMABAN" + bantype_pass = 1 + if(BANTYPE_ADMIN_TEMP) + bantype_str = "ADMIN_TEMPBAN" + bantype_pass = 1 + if(BANTYPE_ANY_FULLBAN) + bantype_str = "ANY" + bantype_pass = 1 + if( !bantype_pass ) return + + var/bantype_sql + if(bantype_str == "ANY") + bantype_sql = "(bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now() ) )" + else + bantype_sql = "bantype = '[bantype_str]'" + + var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = '[ckey]' AND [bantype_sql] AND (unbanned is null OR unbanned = false)" + if(job) + sql += " AND job = '[job]'" + + establish_db_connection() + if(!dbcon.IsConnected()) + return + + var/ban_id + var/ban_number = 0 //failsafe + + var/DBQuery/query = dbcon.NewQuery(sql) + query.Execute() + while(query.NextRow()) + ban_id = query.item[1] + ban_number++; + + if(ban_number == 0) + to_chat(usr, "Database update failed due to no bans fitting the search criteria. If this is not a legacy ban you should contact the database admin.") + return + + if(ban_number > 1) + to_chat(usr, "Database update failed due to multiple bans fitting the search criteria. Note down the ckey, job and current time and contact the database admin.") + return + + if(istext(ban_id)) + ban_id = text2num(ban_id) + if(!isnum(ban_id)) + to_chat(usr, "Database update failed due to a ban ID mismatch. Contact the database admin.") + return + + DB_ban_unban_by_id(ban_id) + if(isjobban) + jobban_unban_client(ckey, job) + else + flag_account_for_forum_sync(ckey) + +datum/admins/proc/DB_ban_edit(var/banid = null, var/param = null) + + if(!check_rights(R_BAN)) return + + if(!isnum(banid) || !istext(param)) + to_chat(usr, "Cancelled") + return + + var/DBQuery/query = dbcon.NewQuery("SELECT ckey, duration, reason, job FROM [format_table_name("ban")] WHERE id = [banid]") + query.Execute() + + var/eckey = usr.ckey //Editing admin ckey + var/pckey //(banned) Player ckey + var/duration //Old duration + var/reason //Old reason + var/job //Old job + + if(query.NextRow()) + pckey = query.item[1] + duration = query.item[2] + reason = query.item[3] + job = query.item[4] + else + to_chat(usr, "Invalid ban id. Contact the database admin") + return + + reason = sanitizeSQL(reason) + var/value + + switch(param) + if("reason") + if(!value) + value = input("Insert the new reason for [pckey]'s ban", "New Reason", "[reason]", null) as null|text + value = sanitizeSQL(value) + if(!value) + to_chat(usr, "Cancelled") + return + + var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [eckey] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
        ') WHERE id = [banid]") + update_query.Execute() + message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s reason from [reason] to [value]",1) + if("duration") + if(!value) + value = input("Insert the new duration (in minutes) for [pckey]'s ban", "New Duration", "[duration]", null) as null|num + if(!isnum(value) || !value) + to_chat(usr, "Cancelled") + return + + var/DBQuery/update_query = dbcon.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [eckey] changed ban duration from [duration] to [value]
        '), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") + message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s duration from [duration] to [value]",1) + update_query.Execute() + if("unban") + if(alert("Unban [pckey]?", "Unban?", "Yes", "No") == "Yes") + DB_ban_unban_by_id(banid) + if(job && length(job)) + jobban_unban_client(pckey, job) + return + else + to_chat(usr, "Cancelled") + return + else + to_chat(usr, "Cancelled") + return + +datum/admins/proc/DB_ban_unban_by_id(var/id) + + if(!check_rights(R_BAN)) return + + var/sql = "SELECT ckey FROM [format_table_name("ban")] WHERE id = [id]" + + establish_db_connection() + if(!dbcon.IsConnected()) + return + + var/ban_number = 0 //failsafe + + var/pckey + var/DBQuery/query = dbcon.NewQuery(sql) + query.Execute() + while(query.NextRow()) + pckey = query.item[1] + ban_number++; + + if(ban_number == 0) + to_chat(usr, "Database update failed due to a ban id not being present in the database.") + return + + if(ban_number > 1) + to_chat(usr, "Database update failed due to multiple bans having the same ID. Contact the database admin.") + return + + if(!src.owner || !istype(src.owner, /client)) + return + + var/unban_ckey = src.owner:ckey + var/unban_computerid = src.owner:computer_id + var/unban_ip = src.owner:address + + var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = '[unban_ip]' WHERE id = [id]" + message_admins("[key_name_admin(usr)] has lifted [pckey]'s ban.",1) + + var/DBQuery/query_update = dbcon.NewQuery(sql_update) + query_update.Execute() + + flag_account_for_forum_sync(pckey) + + +/client/proc/DB_ban_panel() + set category = "Admin" + set name = "Banning Panel" + set desc = "Edit admin permissions" + + if(!holder) + return + + holder.DB_ban_panel() + + +/datum/admins/proc/DB_ban_panel(var/playerckey = null, var/adminckey = null, var/playerip = null, var/playercid = null, var/dbbantype = null, var/match = null) + + if(!usr.client) + return + + if(!check_rights(R_BAN)) return + + establish_db_connection() + if(!dbcon.IsConnected()) + to_chat(usr, "Failed to establish database connection") + return + + var/output = "
        " + + output += "" + + output += "" + output += "" + output += "
        " + output += "

        Banning panel

        " + output += "
        " + + output += "
        Add custom ban: (ONLY use this if you can't ban through any other method)" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "
        Ban type:Ckey:
        IP: CID:
        Duration: Job:
        " + output += "Reason:

        " + output += "" + output += "
        " + + output += "
        " + + output += "
        " + output += "" + output += "" + output += "" + output += "" + output += "
        Search:" + output += "
        Ckey: Admin ckey:
        IP: CID:
        Ban type:
        " + output += "

        " + output += " Match(min. 3 characters to search by key or ip, and 7 to search by cid)
        " + output += "
        " + output += "This search shows only last 100 bans." + + if(adminckey || playerckey || playerip || playercid || dbbantype) + + adminckey = ckey(adminckey) + playerckey = ckey(playerckey) + playerip = sanitizeSQL(playerip) + playercid = sanitizeSQL(playercid) + + if(adminckey || playerckey || playerip || playercid || dbbantype) + + var/blcolor = "#ffeeee" //banned light + var/bdcolor = "#ffdddd" //banned dark + var/ulcolor = "#eeffee" //unbanned light + var/udcolor = "#ddffdd" //unbanned dark + + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + + var/adminsearch = "" + var/playersearch = "" + var/ipsearch = "" + var/cidsearch = "" + var/bantypesearch = "" + + if(!match) + if(adminckey) + adminsearch = "AND a_ckey = '[adminckey]' " + if(playerckey) + playersearch = "AND ckey = '[playerckey]' " + if(playerip) + ipsearch = "AND ip = '[playerip]' " + if(playercid) + cidsearch = "AND computerid = '[playercid]' " + else + if(adminckey && length(adminckey) >= 3) + adminsearch = "AND a_ckey LIKE '[adminckey]%' " + if(playerckey && length(playerckey) >= 3) + playersearch = "AND ckey LIKE '[playerckey]%' " + if(playerip && length(playerip) >= 3) + ipsearch = "AND ip LIKE '[playerip]%' " + if(playercid && length(playercid) >= 7) + cidsearch = "AND computerid LIKE '[playercid]%' " + + if(dbbantype) + bantypesearch = "AND bantype = " + + switch(dbbantype) + if(BANTYPE_TEMP) + bantypesearch += "'TEMPBAN' " + if(BANTYPE_JOB_PERMA) + bantypesearch += "'JOB_PERMABAN' " + if(BANTYPE_JOB_TEMP) + bantypesearch += "'JOB_TEMPBAN' " + if(BANTYPE_APPEARANCE) + bantypesearch += "'APPEARANCE_BAN' " + if(BANTYPE_ADMIN_PERMA) + bantypesearch = "'ADMIN_PERMABAN' " + if(BANTYPE_ADMIN_TEMP) + bantypesearch = "'ADMIN_TEMPBAN' " + else + bantypesearch += "'PERMABAN' " + + + var/DBQuery/select_query = dbcon.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, ckey, a_ckey, unbanned, unbanned_ckey, unbanned_datetime, edits, ip, computerid FROM [format_table_name("ban")] WHERE 1 [playersearch] [adminsearch] [ipsearch] [cidsearch] [bantypesearch] ORDER BY bantime DESC LIMIT 100") + select_query.Execute() + + while(select_query.NextRow()) + var/banid = select_query.item[1] + var/bantime = select_query.item[2] + var/bantype = select_query.item[3] + var/reason = select_query.item[4] + var/job = select_query.item[5] + var/duration = select_query.item[6] + var/expiration = select_query.item[7] + var/ckey = select_query.item[8] + var/ackey = select_query.item[9] + var/unbanned = select_query.item[10] + var/unbanckey = select_query.item[11] + var/unbantime = select_query.item[12] + var/edits = select_query.item[13] + var/ip = select_query.item[14] + var/cid = select_query.item[15] + + var/lcolor = blcolor + var/dcolor = bdcolor + if(unbanned) + lcolor = ulcolor + dcolor = udcolor + + var/typedesc ="" + switch(bantype) + if("PERMABAN") + typedesc = "PERMABAN" + if("TEMPBAN") + typedesc = "TEMPBAN
        ([duration] minutes [(unbanned) ? "" : "(Edit))"]
        Expires [expiration]
        " + if("JOB_PERMABAN") + typedesc = "JOBBAN
        ([job])" + if("JOB_TEMPBAN") + typedesc = "TEMP JOBBAN
        ([job])
        ([duration] minutes
        Expires [expiration]" + if("APPEARANCE_BAN") + typedesc = "APPEARANCE/NAME BAN" + if("ADMIN_PERMABAN") + typedesc = "ADMIN PERMABAN" + if("ADMIN_TEMPBAN") + typedesc = "ADMIN TEMPBAN
        ([duration] minutes [(unbanned) ? "" : "(Edit))"]
        Expires [expiration]
        " + + output += "
        " + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + if(edits) + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + if(unbanned) + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + + output += "
        TYPECKEYTIME APPLIEDADMINOPTIONS
        [typedesc][ckey][bantime][ackey][(unbanned) ? "" : "Unban"]
        IP: [ip]CIP: [cid]
        Reason: [(unbanned) ? "" : "(Edit)"] \"[reason]\"
        EDITS
        [edits]
        UNBANNED by admin [unbanckey] on [unbantime]
         
        " + + usr << browse(output,"window=lookupbans;size=900x700") + +/proc/flag_account_for_forum_sync(ckey) + if(!dbcon) + return + var/skey = sanitizeSQL(ckey) + var/sql = "UPDATE [format_table_name("player")] SET fupdate = 1 WHERE ckey = '[skey]'" + var/DBQuery/adm_query = dbcon.NewQuery(sql) + adm_query.Execute() diff --git a/code/modules/admin/NewBan.dm b/code/modules/admin/NewBan.dm index 2e8fa4ba04b..222fc036db2 100644 --- a/code/modules/admin/NewBan.dm +++ b/code/modules/admin/NewBan.dm @@ -1,232 +1,232 @@ -var/CMinutes = null -var/savefile/Banlist - - -/proc/CheckBan(var/ckey, var/id, var/address) - if(!Banlist) // if Banlist cannot be located for some reason - LoadBans() // try to load the bans - if(!Banlist) // uh oh, can't find bans! - return 0 // ABORT ABORT ABORT - - . = list() - var/appeal - if(config && config.banappeals) - appeal = "\nFor more information on your ban, or to appeal, head to [config.banappeals]" - Banlist.cd = "/base" - if( "[ckey][id]" in Banlist.dir ) - Banlist.cd = "[ckey][id]" - if(Banlist["temp"]) - if(!GetExp(Banlist["minutes"])) - ClearTempbans() - return 0 - else - .["desc"] = "\nReason: [Banlist["reason"]]\nExpires: [GetExp(Banlist["minutes"])]\nBy: [Banlist["bannedby"]][appeal]" - else - Banlist.cd = "/base/[ckey][id]" - .["desc"] = "\nReason: [Banlist["reason"]]\nExpires: PERMENANT\nBy: [Banlist["bannedby"]][appeal]" - .["reason"] = "ckey/id" - return . - else - for(var/A in Banlist.dir) - Banlist.cd = "/base/[A]" - var/matches - if( ckey == Banlist["key"] ) - matches += "ckey" - if( id == Banlist["id"] ) - if(matches) - matches += "/" - matches += "id" - if( address == Banlist["ip"] ) - if(matches) - matches += "/" - matches += "ip" - - if(matches) - if(Banlist["temp"]) - if(!GetExp(Banlist["minutes"])) - ClearTempbans() - return 0 - else - .["desc"] = "\nReason: [Banlist["reason"]]\nExpires: [GetExp(Banlist["minutes"])]\nBy: [Banlist["bannedby"]][appeal]" - else - .["desc"] = "\nReason: [Banlist["reason"]]\nExpires: PERMENANT\nBy: [Banlist["bannedby"]][appeal]" - .["reason"] = matches - return . - return 0 - -/proc/UpdateTime() //No idea why i made this a proc. - CMinutes = (world.realtime / 10) / 60 - return 1 - -/hook/startup/proc/loadBans() - return LoadBans() - -/proc/LoadBans() - - Banlist = new("data/banlist.bdb") - log_admin("Loading Banlist") - - if(!length(Banlist.dir)) log_admin("Banlist is empty.") - - if(!Banlist.dir.Find("base")) - log_admin("Banlist missing base dir.") - Banlist.dir.Add("base") - Banlist.cd = "/base" - else if(Banlist.dir.Find("base")) - Banlist.cd = "/base" - - ClearTempbans() - return 1 - -/proc/ClearTempbans() - UpdateTime() - - Banlist.cd = "/base" - for(var/A in Banlist.dir) - Banlist.cd = "/base/[A]" - if(!Banlist["key"] || !Banlist["id"]) - RemoveBan(A) - log_admin("Invalid Ban.") - message_admins("Invalid Ban.") - continue - - if(!Banlist["temp"]) continue - if(CMinutes >= Banlist["minutes"]) RemoveBan(A) - - return 1 - - -/proc/AddBan(ckey, computerid, reason, bannedby, temp, minutes, address) - - var/bantimestamp - - if(temp) - UpdateTime() - bantimestamp = CMinutes + minutes - - Banlist.cd = "/base" - if( Banlist.dir.Find("[ckey][computerid]") ) - to_chat(usr, "Ban already exists.") - return 0 - else - Banlist.dir.Add("[ckey][computerid]") - Banlist.cd = "/base/[ckey][computerid]" - Banlist["key"] << ckey - Banlist["id"] << computerid - Banlist["ip"] << address - Banlist["reason"] << reason - Banlist["bannedby"] << bannedby - Banlist["temp"] << temp - if(temp) - Banlist["minutes"] << bantimestamp - if(!temp) - add_note(ckey, "Permanently banned - [reason]", null, bannedby, 0) - else - add_note(ckey, "Banned for [minutes] minutes - [reason]", null, bannedby, 0) - return 1 - -/proc/RemoveBan(foldername) - var/key - var/id - - Banlist.cd = "/base/[foldername]" - Banlist["key"] >> key - Banlist["id"] >> id - Banlist.cd = "/base" - - if(!Banlist.dir.Remove(foldername)) return 0 - - if(!usr) - log_admin("Ban Expired: [key]") - message_admins("Ban Expired: [key]") - else - ban_unban_log_save("[key_name_admin(usr)] unbanned [key]") - log_admin("[key_name_admin(usr)] unbanned [key]") - message_admins("[key_name_admin(usr)] unbanned: [key]") - feedback_inc("ban_unban",1) - usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN) - for(var/A in Banlist.dir) - Banlist.cd = "/base/[A]" - if(key == Banlist["key"] /*|| id == Banlist["id"]*/) - Banlist.cd = "/base" - Banlist.dir.Remove(A) - continue - - return 1 - -/proc/GetExp(minutes as num) - UpdateTime() - var/exp = minutes - CMinutes - if(exp <= 0) - return 0 - else - var/timeleftstring - if(exp >= 1440) //1440 = 1 day in minutes - timeleftstring = "[round(exp / 1440, 0.1)] Days" - else if(exp >= 60) //60 = 1 hour in minutes - timeleftstring = "[round(exp / 60, 0.1)] Hours" - else - timeleftstring = "[exp] Minutes" - return timeleftstring - -/datum/admins/proc/unbanpanel() - var/count = 0 - var/dat - Banlist.cd = "/base" - for(var/A in Banlist.dir) - count++ - Banlist.cd = "/base/[A]" - var/ref = UID() - var/key = Banlist["key"] - var/id = Banlist["id"] - var/ip = Banlist["ip"] - var/reason = Banlist["reason"] - var/by = Banlist["bannedby"] - var/expiry - if(Banlist["temp"]) - expiry = GetExp(Banlist["minutes"]) - if(!expiry) expiry = "Removal Pending" - else expiry = "Permaban" - - dat += text("(U)(E) Key: [key]ComputerID: [id]IP: [ip] [expiry](By: [by])(Reason: [reason])") - - dat += "" - dat = "
        Bans: (U) = Unban , (E) = Edit Ban - ([count] Bans)
        [dat]" - usr << browse(dat, "window=unbanp;size=875x400") - -//////////////////////////////////// DEBUG //////////////////////////////////// - -/proc/CreateBans() - - UpdateTime() - - var/i - var/last - - for(i=0, i<1001, i++) - var/a = pick(1,0) - var/b = pick(1,0) - if(b) - Banlist.cd = "/base" - Banlist.dir.Add("trash[i]trashid[i]") - Banlist.cd = "/base/trash[i]trashid[i]" - Banlist["key"] << "trash[i]" - else - Banlist.cd = "/base" - Banlist.dir.Add("[last]trashid[i]") - Banlist.cd = "/base/[last]trashid[i]" - Banlist["key"] << last - Banlist["id"] << "trashid[i]" - Banlist["reason"] << "Trashban[i]." - Banlist["temp"] << a - Banlist["minutes"] << CMinutes + rand(1,2000) - Banlist["bannedby"] << "trashmin" - last = "trash[i]" - - Banlist.cd = "/base" - -/proc/ClearAllBans() - Banlist.cd = "/base" - for(var/A in Banlist.dir) - RemoveBan(A) - +var/CMinutes = null +var/savefile/Banlist + + +/proc/CheckBan(var/ckey, var/id, var/address) + if(!Banlist) // if Banlist cannot be located for some reason + LoadBans() // try to load the bans + if(!Banlist) // uh oh, can't find bans! + return 0 // ABORT ABORT ABORT + + . = list() + var/appeal + if(config && config.banappeals) + appeal = "\nFor more information on your ban, or to appeal, head to [config.banappeals]" + Banlist.cd = "/base" + if( "[ckey][id]" in Banlist.dir ) + Banlist.cd = "[ckey][id]" + if(Banlist["temp"]) + if(!GetExp(Banlist["minutes"])) + ClearTempbans() + return 0 + else + .["desc"] = "\nReason: [Banlist["reason"]]\nExpires: [GetExp(Banlist["minutes"])]\nBy: [Banlist["bannedby"]][appeal]" + else + Banlist.cd = "/base/[ckey][id]" + .["desc"] = "\nReason: [Banlist["reason"]]\nExpires: PERMENANT\nBy: [Banlist["bannedby"]][appeal]" + .["reason"] = "ckey/id" + return . + else + for(var/A in Banlist.dir) + Banlist.cd = "/base/[A]" + var/matches + if( ckey == Banlist["key"] ) + matches += "ckey" + if( id == Banlist["id"] ) + if(matches) + matches += "/" + matches += "id" + if( address == Banlist["ip"] ) + if(matches) + matches += "/" + matches += "ip" + + if(matches) + if(Banlist["temp"]) + if(!GetExp(Banlist["minutes"])) + ClearTempbans() + return 0 + else + .["desc"] = "\nReason: [Banlist["reason"]]\nExpires: [GetExp(Banlist["minutes"])]\nBy: [Banlist["bannedby"]][appeal]" + else + .["desc"] = "\nReason: [Banlist["reason"]]\nExpires: PERMENANT\nBy: [Banlist["bannedby"]][appeal]" + .["reason"] = matches + return . + return 0 + +/proc/UpdateTime() //No idea why i made this a proc. + CMinutes = (world.realtime / 10) / 60 + return 1 + +/hook/startup/proc/loadBans() + return LoadBans() + +/proc/LoadBans() + + Banlist = new("data/banlist.bdb") + log_admin("Loading Banlist") + + if(!length(Banlist.dir)) log_admin("Banlist is empty.") + + if(!Banlist.dir.Find("base")) + log_admin("Banlist missing base dir.") + Banlist.dir.Add("base") + Banlist.cd = "/base" + else if(Banlist.dir.Find("base")) + Banlist.cd = "/base" + + ClearTempbans() + return 1 + +/proc/ClearTempbans() + UpdateTime() + + Banlist.cd = "/base" + for(var/A in Banlist.dir) + Banlist.cd = "/base/[A]" + if(!Banlist["key"] || !Banlist["id"]) + RemoveBan(A) + log_admin("Invalid Ban.") + message_admins("Invalid Ban.") + continue + + if(!Banlist["temp"]) continue + if(CMinutes >= Banlist["minutes"]) RemoveBan(A) + + return 1 + + +/proc/AddBan(ckey, computerid, reason, bannedby, temp, minutes, address) + + var/bantimestamp + + if(temp) + UpdateTime() + bantimestamp = CMinutes + minutes + + Banlist.cd = "/base" + if( Banlist.dir.Find("[ckey][computerid]") ) + to_chat(usr, "Ban already exists.") + return 0 + else + Banlist.dir.Add("[ckey][computerid]") + Banlist.cd = "/base/[ckey][computerid]" + Banlist["key"] << ckey + Banlist["id"] << computerid + Banlist["ip"] << address + Banlist["reason"] << reason + Banlist["bannedby"] << bannedby + Banlist["temp"] << temp + if(temp) + Banlist["minutes"] << bantimestamp + if(!temp) + add_note(ckey, "Permanently banned - [reason]", null, bannedby, 0) + else + add_note(ckey, "Banned for [minutes] minutes - [reason]", null, bannedby, 0) + return 1 + +/proc/RemoveBan(foldername) + var/key + var/id + + Banlist.cd = "/base/[foldername]" + Banlist["key"] >> key + Banlist["id"] >> id + Banlist.cd = "/base" + + if(!Banlist.dir.Remove(foldername)) return 0 + + if(!usr) + log_admin("Ban Expired: [key]") + message_admins("Ban Expired: [key]") + else + ban_unban_log_save("[key_name_admin(usr)] unbanned [key]") + log_admin("[key_name_admin(usr)] unbanned [key]") + message_admins("[key_name_admin(usr)] unbanned: [key]") + feedback_inc("ban_unban",1) + usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN) + for(var/A in Banlist.dir) + Banlist.cd = "/base/[A]" + if(key == Banlist["key"] /*|| id == Banlist["id"]*/) + Banlist.cd = "/base" + Banlist.dir.Remove(A) + continue + + return 1 + +/proc/GetExp(minutes as num) + UpdateTime() + var/exp = minutes - CMinutes + if(exp <= 0) + return 0 + else + var/timeleftstring + if(exp >= 1440) //1440 = 1 day in minutes + timeleftstring = "[round(exp / 1440, 0.1)] Days" + else if(exp >= 60) //60 = 1 hour in minutes + timeleftstring = "[round(exp / 60, 0.1)] Hours" + else + timeleftstring = "[exp] Minutes" + return timeleftstring + +/datum/admins/proc/unbanpanel() + var/count = 0 + var/dat + Banlist.cd = "/base" + for(var/A in Banlist.dir) + count++ + Banlist.cd = "/base/[A]" + var/ref = UID() + var/key = Banlist["key"] + var/id = Banlist["id"] + var/ip = Banlist["ip"] + var/reason = Banlist["reason"] + var/by = Banlist["bannedby"] + var/expiry + if(Banlist["temp"]) + expiry = GetExp(Banlist["minutes"]) + if(!expiry) expiry = "Removal Pending" + else expiry = "Permaban" + + dat += text("") + + dat += "
        (U)(E) Key: [key]ComputerID: [id]IP: [ip] [expiry](By: [by])(Reason: [reason])
        " + dat = "
        Bans: (U) = Unban , (E) = Edit Ban - ([count] Bans)
        [dat]" + usr << browse(dat, "window=unbanp;size=875x400") + +//////////////////////////////////// DEBUG //////////////////////////////////// + +/proc/CreateBans() + + UpdateTime() + + var/i + var/last + + for(i=0, i<1001, i++) + var/a = pick(1,0) + var/b = pick(1,0) + if(b) + Banlist.cd = "/base" + Banlist.dir.Add("trash[i]trashid[i]") + Banlist.cd = "/base/trash[i]trashid[i]" + Banlist["key"] << "trash[i]" + else + Banlist.cd = "/base" + Banlist.dir.Add("[last]trashid[i]") + Banlist.cd = "/base/[last]trashid[i]" + Banlist["key"] << last + Banlist["id"] << "trashid[i]" + Banlist["reason"] << "Trashban[i]." + Banlist["temp"] << a + Banlist["minutes"] << CMinutes + rand(1,2000) + Banlist["bannedby"] << "trashmin" + last = "trash[i]" + + Banlist.cd = "/base" + +/proc/ClearAllBans() + Banlist.cd = "/base" + for(var/A in Banlist.dir) + RemoveBan(A) + diff --git a/code/modules/admin/ToRban.dm b/code/modules/admin/ToRban.dm index 05af7c15cf5..b486f168503 100644 --- a/code/modules/admin/ToRban.dm +++ b/code/modules/admin/ToRban.dm @@ -1,89 +1,89 @@ -//By Carnwennan -//fetches an external list and processes it into a list of ip addresses. -//It then stores the processed list into a savefile for later use -#define TORFILE "data/ToR_ban.bdb" -#define TOR_UPDATE_INTERVAL 216000 //~6 hours - -/proc/ToRban_isbanned(var/ip_address) - var/savefile/F = new(TORFILE) - if(F) - if( ip_address in F.dir ) - return 1 - return 0 - -/proc/ToRban_autoupdate() - var/savefile/F = new(TORFILE) - if(F) - var/last_update - F["last_update"] >> last_update - if((last_update + TOR_UPDATE_INTERVAL) < world.realtime) //we haven't updated for a while - ToRban_update() - return - -/proc/ToRban_update() - spawn(0) - log_world("Downloading updated ToR data...") - var/http[] = world.Export("http://exitlist.torproject.org/exit-addresses") - - var/list/rawlist = file2list(http["CONTENT"]) - if(rawlist.len) - fdel(TORFILE) - var/savefile/F = new(TORFILE) - for( var/line in rawlist ) - if(!line) continue - if( copytext(line,1,12) == "ExitAddress" ) - var/cleaned = copytext(line,13,length(line)-19) - if(!cleaned) continue - F[cleaned] << 1 - to_chat(F["last_update"], world.realtime) - log_world("ToR data updated!") - if(usr) - to_chat(usr, "ToRban updated.") - return 1 - log_world("ToR data update aborted: no data.") - return 0 - -/client/proc/ToRban(task in list("update","toggle","show","remove","remove all","find")) - set name = "ToRban" - set category = "Server" - if(!holder) return - switch(task) - if("update") - ToRban_update() - if("toggle") - if(config) - if(config.ToRban) - config.ToRban = 0 - message_admins("ToR banning disabled.") - else - config.ToRban = 1 - message_admins("ToR banning enabled.") - if("show") - var/savefile/F = new(TORFILE) - var/dat - if( length(F.dir) ) - for( var/i=1, i<=length(F.dir), i++ ) - dat += "" - dat = "
        #[i] [F.dir[i]]
        [dat]
        " - else - dat = "No addresses in list." - src << browse(dat,"window=ToRban_show") - if("remove") - var/savefile/F = new(TORFILE) - var/choice = input(src,"Please select an IP address to remove from the ToR banlist:","Remove ToR ban",null) as null|anything in F.dir - if(choice) - F.dir.Remove(choice) - to_chat(src, "Address removed") - if("remove all") - to_chat(src, "[TORFILE] was [fdel(TORFILE)?"":"not "]removed.") - if("find") - var/input = input(src,"Please input an IP address to search for:","Find ToR ban",null) as null|text - if(input) - if(ToRban_isbanned(input)) - to_chat(src, "Address is a known ToR address") - else - to_chat(src, "Address is not a known ToR address") - return - -#undef TORFILE -#undef TOR_UPDATE_INTERVAL \ No newline at end of file +//By Carnwennan +//fetches an external list and processes it into a list of ip addresses. +//It then stores the processed list into a savefile for later use +#define TORFILE "data/ToR_ban.bdb" +#define TOR_UPDATE_INTERVAL 216000 //~6 hours + +/proc/ToRban_isbanned(var/ip_address) + var/savefile/F = new(TORFILE) + if(F) + if( ip_address in F.dir ) + return 1 + return 0 + +/proc/ToRban_autoupdate() + var/savefile/F = new(TORFILE) + if(F) + var/last_update + F["last_update"] >> last_update + if((last_update + TOR_UPDATE_INTERVAL) < world.realtime) //we haven't updated for a while + ToRban_update() + return + +/proc/ToRban_update() + spawn(0) + log_world("Downloading updated ToR data...") + var/http[] = world.Export("http://exitlist.torproject.org/exit-addresses") + + var/list/rawlist = file2list(http["CONTENT"]) + if(rawlist.len) + fdel(TORFILE) + var/savefile/F = new(TORFILE) + for( var/line in rawlist ) + if(!line) continue + if( copytext(line,1,12) == "ExitAddress" ) + var/cleaned = copytext(line,13,length(line)-19) + if(!cleaned) continue + F[cleaned] << 1 + to_chat(F["last_update"], world.realtime) + log_world("ToR data updated!") + if(usr) + to_chat(usr, "ToRban updated.") + return 1 + log_world("ToR data update aborted: no data.") + return 0 + +/client/proc/ToRban(task in list("update","toggle","show","remove","remove all","find")) + set name = "ToRban" + set category = "Server" + if(!holder) return + switch(task) + if("update") + ToRban_update() + if("toggle") + if(config) + if(config.ToRban) + config.ToRban = 0 + message_admins("ToR banning disabled.") + else + config.ToRban = 1 + message_admins("ToR banning enabled.") + if("show") + var/savefile/F = new(TORFILE) + var/dat + if( length(F.dir) ) + for( var/i=1, i<=length(F.dir), i++ ) + dat += "#[i] [F.dir[i]]" + dat = "[dat]
        " + else + dat = "No addresses in list." + src << browse(dat,"window=ToRban_show") + if("remove") + var/savefile/F = new(TORFILE) + var/choice = input(src,"Please select an IP address to remove from the ToR banlist:","Remove ToR ban",null) as null|anything in F.dir + if(choice) + F.dir.Remove(choice) + to_chat(src, "Address removed") + if("remove all") + to_chat(src, "[TORFILE] was [fdel(TORFILE)?"":"not "]removed.") + if("find") + var/input = input(src,"Please input an IP address to search for:","Find ToR ban",null) as null|text + if(input) + if(ToRban_isbanned(input)) + to_chat(src, "Address is a known ToR address") + else + to_chat(src, "Address is not a known ToR address") + return + +#undef TORFILE +#undef TOR_UPDATE_INTERVAL diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 55fb1c4047a..31aa6d5bc4c 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1,1094 +1,1094 @@ -var/global/BSACooldown = 0 -var/global/nologevent = 0 - -//////////////////////////////// -/proc/message_admins(var/msg) - msg = "ADMIN LOG: [msg]" - for(var/client/C in GLOB.admins) - if(R_ADMIN & C.holder.rights) - if(C.prefs && !(C.prefs.toggles & CHAT_NO_ADMINLOGS)) - to_chat(C, msg) - -/proc/msg_admin_attack(var/text, var/loglevel) - if(!nologevent) - var/rendered = "ATTACK: [text]" - for(var/client/C in GLOB.admins) - if(R_ADMIN & C.holder.rights) - if(C.prefs.atklog == ATKLOG_NONE) - continue - var/msg = rendered - if(C.prefs.atklog <= loglevel) - to_chat(C, msg) - - -/proc/message_adminTicket(var/msg, var/alt = FALSE) - if(alt) - msg = "ADMIN TICKET: [msg]" - else - msg = "ADMIN TICKET: [msg]" - for(var/client/C in GLOB.admins) - if(R_ADMIN & C.holder.rights) - if(C.prefs && !(C.prefs.toggles & CHAT_NO_TICKETLOGS)) - to_chat(C, msg) - -/proc/message_mentorTicket(var/msg) - for(var/client/C in GLOB.admins) - if(check_rights(R_ADMIN | R_MENTOR | R_MOD, 0, C.mob)) - if(C.prefs && !(C.prefs.toggles & CHAT_NO_MENTORTICKETLOGS)) - to_chat(C, msg) - -/proc/admin_ban_mobsearch(var/mob/M, var/ckey_to_find, var/mob/admin_to_notify) - if(!M || !M.ckey) - if(ckey_to_find) - for(var/mob/O in GLOB.mob_list) - if(O.ckey && O.ckey == ckey_to_find) - if(admin_to_notify) - to_chat(admin_to_notify, "admin_ban_mobsearch: Player [ckey_to_find] is now in mob [O]. Pulling data from new mob.") - return O - if(admin_to_notify) - to_chat(admin_to_notify, "admin_ban_mobsearch: Player [ckey_to_find] does not seem to have any mob, anywhere. This is probably an error.") - else if(admin_to_notify) - to_chat(admin_to_notify, "admin_ban_mobsearch: No mob or ckey detected.") - return M - -///////////////////////////////////////////////////////////////////////////////////////////////Panels - -/datum/admins/proc/show_player_panel(var/mob/M in GLOB.mob_list) - set category = null - set name = "Show Player Panel" - set desc="Edit player (respawn, ban, heal, etc)" - - if(!M) - to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.") - return - - if(!check_rights(R_ADMIN|R_MOD)) - return - - var/body = "Options for [M.key]" - body += "Options panel for [M]" - if(M.client) - body += " played by [M.client] " - body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\] " - body += "\[" + M.client.get_exp_type(EXP_TYPE_CREW) + " as [EXP_TYPE_CREW]\]" - - if(istype(M, /mob/new_player)) - body += " Hasn't Entered Game " - else - body += " \[Heal\] " - - body += "

        \[ " - body += "VV - " - body += "[ADMIN_TP(M,"TP")] - " - if(M.client) - body += "PM - " - body += "[ADMIN_SM(M,"SM")] - " - if(ishuman(M) && M.mind) - body += "HM -" - body += "[admin_jump_link(M)]\]

        " - body += "Mob type: [M.type]
        " - if(M.client) - if(M.client.related_accounts_cid.len) - body += "Related accounts by CID: [jointext(M.client.related_accounts_cid, " - ")]
        " - if(M.client.related_accounts_ip.len) - body += "Related accounts by IP: [jointext(M.client.related_accounts_ip, " - ")]

        " - - if(M.ckey) - body += "Kick | " - body += "Warn | " - body += "Ban | " - body += "Jobban | " - body += "Appearance Ban | " - body += "Notes | " - if(config.forum_playerinfo_url) - body += "WebInfo | " - if(M.client) - if(check_watchlist(M.client.ckey)) - body += "Remove from Watchlist | " - body += "Edit Watchlist Reason " - else - body += "Add to Watchlist " - - if(M.client) - body += "| Prison | " - body += "\ Send back to Lobby | " - var/muted = M.client.prefs.muted - body += {"
        Mute: - \[IC | - OOC | - PRAY | - ADMINHELP | - DEADCHAT\] - (toggle all) - "} - - var/jumptoeye = "" - if(isAI(M)) - var/mob/living/silicon/ai/A = M - if(A.client && A.eyeobj) // No point following clientless AI eyes - jumptoeye = " (Eye)" - body += {"

        - Jump to[jumptoeye] | - Get | - Send To -

        - [check_rights(R_ADMIN,0) ? "[ADMIN_TP(M,"Traitor panel")] | " : "" ] - Narrate to | - [ADMIN_SM(M,"Subtle message")] - "} - - if(check_rights(R_EVENT, 0)) - body += {" | Bless | Smite"} - - if(isLivingSSD(M)) - if(istype(M.loc, /obj/machinery/cryopod)) - body += {" | De-Spawn "} - else - body += {" | Cryo "} - - if(M.client) - if(!istype(M, /mob/new_player)) - body += "

        " - body += "Transformation:" - body += "
        " - - //Monkey - if(issmall(M)) - body += "Monkeyized | " - else - body += "Monkeyize | " - - //Corgi - if(iscorgi(M)) - body += "Corgized | " - else - body += "Corgize | " - - //AI / Cyborg - if(isAI(M)) - body += "Is an AI " - else if(ishuman(M)) - body += {"Make AI | - Make Robot | - Make Alien | - Make Slime | - Make Superhero - "} - - //Simple Animals - if(isanimal(M)) - body += "Re-Animalize | " - else - body += "Animalize | " - - if(istype(M, /mob/dead/observer)) - body += "Re-incarnate | " - - if(ispAI(M)) - body += "Is a pAI " - else - body += "Make pAI | " - - // DNA2 - Admin Hax - if(M.dna && iscarbon(M)) - body += "

        " - body += "DNA Blocks:
        " - var/bname - for(var/block=1;block<=DNA_SE_LENGTH;block++) - if(((block-1)%5)==0) - body += "" - bname = assigned_blocks[block] - body += "" - body += "
         12345
        [block-1]" - if(bname) - var/bstate=M.dna.GetSEState(block) - var/bcolor="[(bstate)?"#006600":"#ff0000"]" - body += "[bname][block]" - else - body += "[block]" - body+="
        " - - body += {"

        - Rudimentary transformation:
        These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.

        - Observer | - \[ Alien: Drone, - Hunter, - Queen, - Sentinel, - Larva \] - Human - \[ slime: Baby, - Adult \] - Monkey | - Cyborg | - Cat | - Runtime | - Corgi | - Ian | - Crab | - Coffee | - \[ Construct: Armoured , - Builder , - Wraith \] - Shade - "} - - if(M.client) - body += {"

        - Other actions: -
        - Forcesay | - Admin Room | - Thunderdome 1 | - Thunderdome 2 | - Thunderdome Admin | - Thunderdome Observer | - "} - - body += {"
        - - "} - - usr << browse(body, "window=adminplayeropts;size=550x615") - 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 -/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 - -#define PLAYER_NOTES_ENTRIES_PER_PAGE 50 -/datum/admins/proc/PlayerNotes() - set category = "Admin" - set name = "Player Notes" - - if(!check_rights(R_ADMIN|R_MOD)) - return - - show_note() - -/datum/admins/proc/show_player_notes(var/key as text) - set category = "Admin" - set name = "Show Player Notes" - - if(!check_rights(R_ADMIN|R_MOD)) - return - - show_note(key) - -/datum/admins/proc/vpn_whitelist() - set category = "Admin" - set name = "VPN Ckey Whitelist" - if(!check_rights(R_ADMIN)) - return - var/key = stripped_input(usr, "Enter ckey to add/remove, or leave blank to cancel:", "VPN Whitelist add/remove", max_length=32) - if(key) - vpn_whitelist_panel(key) - -/datum/admins/proc/access_news_network() //MARKER - set category = "Event" - set name = "Access Newscaster Network" - set desc = "Allows you to view, add and edit news feeds." - - if(!check_rights(R_EVENT)) - return - - if(!istype(src,/datum/admins)) - src = usr.client.holder - - var/dat - dat = text("Admin Newscaster

        Admin Newscaster Unit

        ") - - switch(admincaster_screen) - if(0) - dat += {"Welcome to the admin newscaster.
        Here you can add, edit and censor every newspiece on the network. -
        Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units. -
        Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things!
        - "} - if(news_network.wanted_issue) - dat+= "
        Read Wanted Issue" - - dat+= {"

        Create Feed Channel -
        View Feed Channels -
        Submit new Feed story -

        Exit - "} - - var/wanted_already = 0 - if(news_network.wanted_issue) - wanted_already = 1 - - dat+={"
        Feed Security functions:
        -
        [(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue -
        Censor Feed Stories -
        Mark Feed Channel with Nanotrasen D-Notice (disables and locks the channel. -

        The newscaster recognises you as:
        [src.admincaster_signature]
        - "} - if(1) - dat+= "Station Feed Channels
        " - if( isemptylist(news_network.network_channels) ) - dat+="No active channels found..." - else - for(var/datum/feed_channel/CHANNEL in news_network.network_channels) - if(CHANNEL.is_admin_channel) - dat+="[CHANNEL.channel_name]
        " - else - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
        " - dat+={"

        Refresh -
        Back - "} - - if(2) - dat+={" - Creating new Feed Channel... -
        Channel Name: [src.admincaster_feed_channel.channel_name]
        - Channel Author: [src.admincaster_signature]
        - Will Accept Public Feeds: [(src.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

        -
        Submit

        Cancel
        - "} - if(3) - dat+={" - Creating new Feed Message... -
        Receiving Channel: [src.admincaster_feed_channel.channel_name]
        - Message Author: [src.admincaster_signature]
        - Message Body: [src.admincaster_feed_message.body]
        -
        Submit

        Cancel
        - "} - if(4) - dat+={" - Feed story successfully submitted to [src.admincaster_feed_channel.channel_name].

        -
        Return
        - "} - if(5) - dat+={" - Feed Channel [src.admincaster_feed_channel.channel_name] created successfully.

        -
        Return
        - "} - if(6) - dat+="ERROR: Could not submit Feed story to Network.

        " - if(src.admincaster_feed_channel.channel_name=="") - dat+="•Invalid receiving channel name.
        " - if(src.admincaster_feed_message.body == "" || src.admincaster_feed_message.body == "\[REDACTED\]") - dat+="•Invalid message body.
        " - dat+="
        Return
        " - if(7) - dat+="ERROR: Could not submit Feed Channel to Network.

        " - if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]") - dat+="•Invalid channel name.
        " - var/check = 0 - for(var/datum/feed_channel/FC in news_network.network_channels) - if(FC.channel_name == src.admincaster_feed_channel.channel_name) - check = 1 - break - if(check) - dat+="•Channel name already in use.
        " - dat+="
        Return
        " - if(9) - dat+="[src.admincaster_feed_channel.channel_name]: \[created by: [src.admincaster_feed_channel.author]\]
        " - if(src.admincaster_feed_channel.censored) - dat+={" - ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
        - No further feed story additions are allowed while the D-Notice is in effect.


        - "} - else - if( isemptylist(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
        " - else - var/i = 0 - for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - i++ - dat+="-[MESSAGE.body]
        " - if(MESSAGE.img) - usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") - dat+="

        " - dat+="\[Story by [MESSAGE.author]\]
        " - dat+={" -

        Refresh -
        Back - "} - if(10) - dat+={" - Nanotrasen Feed Censorship Tool
        - NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
        - Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.
        -
        Select Feed channel to get Stories from:
        - "} - if(isemptylist(news_network.network_channels)) - dat+="No feed channels found active...
        " - else - for(var/datum/feed_channel/CHANNEL in news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
        " - dat+="
        Cancel" - if(11) - dat+={" - Nanotrasen D-Notice Handler
        - A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's - morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed - stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
        - "} - if(isemptylist(news_network.network_channels)) - dat+="No feed channels found active...
        " - else - for(var/datum/feed_channel/CHANNEL in news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
        " - - dat+="
        Back" - if(12) - dat+={" - [src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.author] \]
        - [(src.admincaster_feed_channel.author=="\[REDACTED\]") ? ("Undo Author censorship") : ("Censor channel Author")]
        - "} - if( isemptylist(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
        " - else - for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - dat+={" - -[MESSAGE.body]
        \[Story by [MESSAGE.author]\]
        - [(MESSAGE.body == "\[REDACTED\]") ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.author == "\[REDACTED\]") ? ("Undo Author Censorship") : ("Censor message Author")]
        - "} - dat+="
        Back" - if(13) - dat+={" - [src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.author] \]
        - Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
        - "} - if(src.admincaster_feed_channel.censored) - dat+={" - ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
        - No further feed story additions are allowed while the D-Notice is in effect.


        - "} - else - if( isemptylist(src.admincaster_feed_channel.messages) ) - dat+="No feed messages found in channel...
        " - else - for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages) - dat+="-[MESSAGE.body]
        \[Story by [MESSAGE.author]\]
        " - - dat+="
        Back" - if(14) - dat+="Wanted Issue Handler:" - var/wanted_already = 0 - var/end_param = 1 - if(news_network.wanted_issue) - wanted_already = 1 - end_param = 2 - if(wanted_already) - dat+="
        A wanted issue is already in Feed Circulation. You can edit or cancel it below.
        " - dat+={" -
        - Criminal Name: [src.admincaster_feed_message.author]
        - Description: [src.admincaster_feed_message.body]
        - "} - if(wanted_already) - dat+="Wanted Issue created by: [news_network.wanted_issue.backup_author]
        " - else - dat+="Wanted Issue will be created under prosecutor: [src.admincaster_signature]
        " - dat+="
        [(wanted_already) ? ("Edit Issue") : ("Submit")]" - if(wanted_already) - dat+="
        Take down Issue" - dat+="
        Cancel" - if(15) - dat+={" - Wanted issue for [src.admincaster_feed_message.author] is now in Network Circulation.

        -
        Return
        - "} - if(16) - dat+="ERROR: Wanted Issue rejected by Network.

        " - if(src.admincaster_feed_message.author =="" || src.admincaster_feed_message.author == "\[REDACTED\]") - dat+="•Invalid name for person wanted.
        " - if(src.admincaster_feed_message.body == "" || src.admincaster_feed_message.body == "\[REDACTED\]") - dat+="•Invalid description.
        " - dat+="
        Return
        " - if(17) - dat+={" - Wanted Issue successfully deleted from Circulation
        -
        Return
        - "} - if(18) - dat+={" - -- STATIONWIDE WANTED ISSUE --
        \[Submitted by: [news_network.wanted_issue.backup_author]\]
        - Criminal: [news_network.wanted_issue.author]
        - Description: [news_network.wanted_issue.body]
        - Photo:: - "} - if(news_network.wanted_issue.img) - usr << browse_rsc(news_network.wanted_issue.img, "tmp_photow.png") - dat+="
        " - else - dat+="None" - dat+="
        Back
        " - if(19) - dat+={" - Wanted issue for [src.admincaster_feed_message.author] successfully edited.

        -
        Return
        - "} - else - dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" - -// to_chat(world, "Channelname: [src.admincaster_feed_channel.channel_name] [src.admincaster_feed_channel.author]") -// to_chat(world, "Msg: [src.admincaster_feed_message.author] [src.admincaster_feed_message.body]") - usr << browse(dat, "window=admincaster_main;size=400x600") - onclose(usr, "admincaster_main") - -/datum/admins/proc/Jobbans() - if(!check_rights(R_BAN)) - return - - var/dat = "Job Bans!
        " - for(var/t in jobban_keylist) - var/r = t - if( findtext(r,"##") ) - r = copytext( r, 1, findtext(r,"##") )//removes the description - dat += text("") - dat += "
        [t] (unban)
        " - usr << browse(dat, "window=ban;size=400x400") - -/datum/admins/proc/Game() - if(!check_rights(R_ADMIN)) - return - - var/dat = {" -
        Game Panel

        \n - Change Game Mode
        - "} - if(master_mode == "secret") - dat += "(Force Secret Mode)
        " - - dat += {" -
        - Create Object
        - Quick Create Object
        - Create Turf
        - Create Mob
        - "} - - usr << browse(dat, "window=admin2;size=210x280") - return - -/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge -//i.e. buttons/verbs - - -/datum/admins/proc/restart() - set category = "Server" - set name = "Restart" - set desc = "Restarts the world." - - if(!check_rights(R_SERVER)) - return - - var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null - if(isnull(delay)) - return - else - delay = delay * 10 - message_admins("[key_name_admin(usr)] has initiated a server restart with a delay of [delay/10] seconds") - log_admin("[key_name(usr)] has initiated a server restart with a delay of [delay/10] seconds") - SSticker.delay_end = 0 - feedback_add_details("admin_verb","R") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - world.Reboot("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key].", "end_error", "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay) - -/datum/admins/proc/announce() - set category = "Admin" - set name = "Announce" - set desc = "Announce your desires to the world" - - if(!check_rights(R_ADMIN)) - return - - var/message = input("Global message to send:", "Admin Announce", null, null) as message|null - if(message) - if(!check_rights(R_SERVER,0)) - message = adminscrub(message,500) - message = replacetext(message, "\n", "
        ") // required since we're putting it in a

        tag - to_chat(world, "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:

        [message]

        ") - log_admin("Announce: [key_name(usr)] : [message]") - feedback_add_details("admin_verb","A") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleooc() - set category = "Server" - set desc="Globally Toggles OOC" - set name="Toggle OOC" - - if(!check_rights(R_ADMIN)) - return - - toggle_ooc() - log_and_message_admins("toggled OOC.") - feedback_add_details("admin_verb","TOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/togglelooc() - set category = "Server" - set desc="Globally Toggles LOOC" - set name="Toggle LOOC" - - if(!check_rights(R_ADMIN)) - return - - config.looc_allowed = !(config.looc_allowed) - if(config.looc_allowed) - to_chat(world, "The LOOC channel has been globally enabled!") - else - to_chat(world, "The LOOC channel has been globally disabled!") - log_and_message_admins("toggled LOOC.") - feedback_add_details("admin_verb","TLOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggledsay() - set category = "Server" - set desc="Globally Toggles DSAY" - set name="Toggle DSAY" - - if(!check_rights(R_ADMIN)) - return - - config.dsay_allowed = !(config.dsay_allowed) - if(config.dsay_allowed) - to_chat(world, "Deadchat has been globally enabled!") - else - to_chat(world, "Deadchat has been globally disabled!") - log_admin("[key_name(usr)] toggled deadchat.") - message_admins("[key_name_admin(usr)] toggled deadchat.", 1) - feedback_add_details("admin_verb","TDSAY") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc - -/datum/admins/proc/toggleoocdead() - set category = "Server" - set desc="Toggle Dead OOC." - set name="Toggle Dead OOC" - - if(!check_rights(R_ADMIN)) - return - - config.dooc_allowed = !( config.dooc_allowed ) - log_admin("[key_name(usr)] toggled Dead OOC.") - message_admins("[key_name_admin(usr)] toggled Dead OOC.", 1) - feedback_add_details("admin_verb","TDOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleemoji() - set category = "Server" - set desc = "Toggle OOC Emoji" - set name = "Toggle OOC Emoji" - - if(!check_rights(R_ADMIN)) - return - - config.disable_ooc_emoji = !(config.disable_ooc_emoji) - log_admin("[key_name(usr)] toggled OOC Emoji.") - message_admins("[key_name_admin(usr)] toggled OOC Emoji.", 1) - feedback_add_details("admin_verb", "TEMOJ") - -/datum/admins/proc/startnow() - set category = "Server" - set desc="Start the round RIGHT NOW" - set name="Start Now" - - if(!check_rights(R_SERVER)) - return - - if(!SSticker) - alert("Unable to start the game as it is not set up.") - return - - if(config.start_now_confirmation) - if(alert(usr, "This is a live server. Are you sure you want to start now?", "Start game", "Yes", "No") != "Yes") - return - - if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP) - SSticker.force_start = TRUE - log_admin("[usr.key] has started the game.") - var/msg = "" - if(SSticker.current_state == GAME_STATE_STARTUP) - msg = " (The server is still setting up, but the round will be started as soon as possible.)" - message_admins("[usr.key] has started the game.[msg]") - feedback_add_details("admin_verb","SN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return 1 - else - to_chat(usr, "Error: Start Now: Game has already started.") - return - -/datum/admins/proc/toggleenter() - set category = "Server" - set desc="People can't enter" - set name="Toggle Entering" - - if(!check_rights(R_SERVER)) - return - - enter_allowed = !( enter_allowed ) - if(!( enter_allowed )) - to_chat(world, "New players may no longer enter the game.") - else - to_chat(world, "New players may now enter the game.") - log_admin("[key_name(usr)] toggled new player game entering.") - message_admins("[key_name_admin(usr)] toggled new player game entering.", 1) - world.update_status() - feedback_add_details("admin_verb","TE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleAI() - set category = "Event" - set desc="People can't be AI" - set name="Toggle AI" - - if(!check_rights(R_EVENT)) - return - - config.allow_ai = !( config.allow_ai ) - if(!( config.allow_ai )) - to_chat(world, "The AI job is no longer chooseable.") - else - to_chat(world, "The AI job is chooseable now.") - message_admins("[key_name_admin(usr)] toggled AI allowed.") - log_admin("[key_name(usr)] toggled AI allowed.") - world.update_status() - feedback_add_details("admin_verb","TAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleaban() - set category = "Server" - set desc="Toggle the ability for players to respawn." - set name="Toggle Respawn" - - if(!check_rights(R_SERVER)) - return - - abandon_allowed = !( abandon_allowed ) - if(abandon_allowed) - to_chat(world, "You may now respawn.") - else - to_chat(world, "You may no longer respawn :(") - message_admins("[key_name_admin(usr)] toggled respawn to [abandon_allowed ? "On" : "Off"].", 1) - log_admin("[key_name(usr)] toggled respawn to [abandon_allowed ? "On" : "Off"].") - world.update_status() - feedback_add_details("admin_verb","TR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggle_aliens() - set category = "Event" - set desc="Toggle alien mobs" - set name="Toggle Aliens" - - if(!check_rights(R_EVENT)) - return - - aliens_allowed = !aliens_allowed - log_admin("[key_name(usr)] toggled aliens to [aliens_allowed].") - message_admins("[key_name_admin(usr)] toggled aliens [aliens_allowed ? "on" : "off"].") - feedback_add_details("admin_verb","TA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/delay() - set category = "Server" - set desc="Delay the game start/end" - set name="Delay" - - if(!check_rights(R_SERVER)) - return - - if(!SSticker || SSticker.current_state != GAME_STATE_PREGAME) - SSticker.delay_end = !SSticker.delay_end - log_admin("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].") - message_admins("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].", 1) - return //alert("Round end delayed", null, null, null, null, null) - if(going) - going = FALSE - SSticker.delay_end = TRUE - to_chat(world, "The game start has been delayed.") - log_admin("[key_name(usr)] delayed the game.") - else - going = TRUE - to_chat(world, "The game will start soon.") - log_admin("[key_name(usr)] removed the delay.") - feedback_add_details("admin_verb","DELAY") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS - -/proc/is_special_character(mob/M as mob) // returns 1 for specail characters and 2 for heroes of gamemode - if(!SSticker || !SSticker.mode) - return 0 - if(!istype(M)) - return 0 - if((M.mind in SSticker.mode.head_revolutionaries) || (M.mind in SSticker.mode.revolutionaries)) - if(SSticker.mode.config_tag == "revolution") - return 2 - return 1 - if(M.mind in SSticker.mode.cult) - if(SSticker.mode.config_tag == "cult") - return 2 - return 1 - if(M.mind in SSticker.mode.syndicates) - if(SSticker.mode.config_tag == "nuclear") - return 2 - return 1 - if(M.mind in SSticker.mode.wizards) - if(SSticker.mode.config_tag == "wizard") - return 2 - return 1 - if(M.mind in SSticker.mode.changelings) - if(SSticker.mode.config_tag == "changeling") - return 2 - return 1 - if(M.mind in SSticker.mode.abductors) - if(SSticker.mode.config_tag == "abduction") - return 2 - return 1 - if(isrobot(M)) - var/mob/living/silicon/robot/R = M - if(R.emagged) - return 1 - if(M.mind&&M.mind.special_role)//If they have a mind and special role, they are some type of traitor or antagonist. - return 1 - - return 0 - -/datum/admins/proc/spawn_atom(var/object as text) - set category = "Debug" - set desc = "(atom path) Spawn an atom" - set name = "Spawn" - - if(!check_rights(R_SPAWN)) - return - - var/list/types = typesof(/atom) - var/list/matches = new() - - for(var/path in types) - if(findtext("[path]", object)) - matches += path - - if(matches.len==0) - return - - var/chosen - if(matches.len==1) - chosen = matches[1] - else - chosen = input("Select an atom type", "Spawn Atom", matches[1]) as null|anything in matches - if(!chosen) - return - - if(ispath(chosen,/turf)) - var/turf/T = get_turf(usr.loc) - T.ChangeTurf(chosen) - else - var/atom/A = new chosen(usr.loc) - A.admin_spawned = TRUE - - log_admin("[key_name(usr)] spawned [chosen] at ([usr.x],[usr.y],[usr.z])") - feedback_add_details("admin_verb","SA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/show_traitor_panel(var/mob/M in GLOB.mob_list) - set category = "Admin" - set desc = "Edit mobs's memory and role" - set name = "Show Traitor Panel" - - if(!check_rights(R_ADMIN|R_MOD)) - return - - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(!M.mind) - to_chat(usr, "This mob has no mind!") - return - - M.mind.edit_memory() - feedback_add_details("admin_verb","STP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/toggleguests() - set category = "Server" - set desc="Guests can't enter" - set name="Toggle Guests" - - if(!check_rights(R_SERVER)) - return - - guests_allowed = !( guests_allowed ) - if(!( guests_allowed )) - to_chat(world, "Guests may no longer enter the game.") - else - to_chat(world, "Guests may now enter the game.") - log_admin("[key_name(usr)] toggled guests game entering [guests_allowed ? "" : "dis"]allowed.") - message_admins("[key_name_admin(usr)] toggled guests game entering [guests_allowed ? "" : "dis"]allowed.", 1) - feedback_add_details("admin_verb","TGU") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/datum/admins/proc/output_ai_laws() - var/ai_number = 0 - for(var/mob/living/silicon/S in GLOB.mob_list) - ai_number++ - if(isAI(S)) - to_chat(usr, "AI [key_name(S, TRUE)]'s laws:") - else if(isrobot(S)) - var/mob/living/silicon/robot/R = S - to_chat(usr, "CYBORG [key_name(S, TRUE)]'s [R.connected_ai?"(Slaved to: [R.connected_ai])":"(Independent)"] laws:") - else if(ispAI(S)) - var/mob/living/silicon/pai/P = S - to_chat(usr, "pAI [key_name(S, TRUE)]'s laws:") - to_chat(usr, "[P.pai_law0]") - if(P.pai_laws) - to_chat(usr, "[P.pai_laws]") - continue // Skip showing normal silicon laws for pAIs - they don't have any - else - to_chat(usr, "SILICON [key_name(S, TRUE)]'s laws:") - - if(S.laws == null) - to_chat(usr, "[key_name(S, TRUE)]'s laws are null. Contact a coder.") - else - S.laws.show_laws(usr) - if(!ai_number) - to_chat(usr, "No AI's located.")//Just so you know the thing is actually working and not just ignoring you. - - - log_admin("[key_name(usr)] checked the AI laws") - message_admins("[key_name_admin(usr)] checked the AI laws") - -/client/proc/update_mob_sprite(mob/living/carbon/human/H as mob) - set name = "Update Mob Sprite" - set desc = "Should fix any mob sprite update errors." - set category = null - - if(!check_rights(R_ADMIN)) - return - - if(istype(H)) - H.regenerate_icons() - -// -// -//ALL DONE -//********************************************************************************************************* - -var/gamma_ship_location = 1 // 0 = station , 1 = space - -/proc/move_gamma_ship() - var/area/fromArea - var/area/toArea - if(gamma_ship_location == 1) - fromArea = locate(/area/shuttle/gamma/space) - toArea = locate(/area/shuttle/gamma/station) - else - fromArea = locate(/area/shuttle/gamma/station) - toArea = locate(/area/shuttle/gamma/space) - fromArea.move_contents_to(toArea) - - for(var/obj/machinery/mech_bay_recharge_port/P in toArea) - P.update_recharge_turf() - - if(gamma_ship_location) - gamma_ship_location = 0 - else - gamma_ship_location = 1 - return - -/proc/formatJumpTo(var/location,var/where="") - var/turf/loc - if(istype(location,/turf/)) - loc = location - else - loc = get_turf(location) - if(where=="") - where=formatLocation(loc) - return "[where]" - -/proc/formatLocation(var/location) - var/turf/loc - if(istype(location,/turf/)) - loc = location - else - loc = get_turf(location) - var/area/A = get_area(location) - return "[A.name] - [loc.x],[loc.y],[loc.z]" - -/proc/formatPlayerPanel(var/mob/U,var/text="PP") - return "[ADMIN_PP(U,"[text]")]" - -//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked -//defaults to kicking everyone (afk + non afk clients in the lobby) -//returns a list of ckeys of the kicked clients -/proc/kick_clients_in_lobby(message, kick_only_afk = 0) - var/list/kicked_client_names = list() - for(var/client/C in GLOB.clients) - if(istype(C.mob, /mob/new_player)) - if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk - continue - if(message) - to_chat(C, message) - kicked_client_names.Add("[C.ckey]") - del(C) - return kicked_client_names - -//returns 1 to let the dragdrop code know we are trapping this event -//returns 0 if we don't plan to trap the event -/datum/admins/proc/cmd_ghost_drag(var/mob/dead/observer/frommob, var/tothing) - if(!istype(frommob)) - return //extra sanity check to make sure only observers are shoved into things - - //same as assume-direct-control perm requirements. - if(!check_rights(R_VAREDIT,0)) //no varedit, check if they have r_admin and r_debug - if(!check_rights(R_ADMIN|R_DEBUG,0)) //if they don't have r_admin and r_debug, return - return 0 //otherwise, if they have no varedit, but do have r_admin and r_debug, execute the rest of the code - - if(!frommob.ckey) - return 0 - - if(istype(tothing, /obj/item)) - var/mob/living/toitem = tothing - - var/ask = alert("Are you sure you want to allow [frommob.name]([frommob.key]) to possess [toitem.name]?", "Place ghost in control of item?", "Yes", "No") - if(ask != "Yes") - return 1 - - if(!frommob || !toitem) //make sure the mobs don't go away while we waited for a response - return 1 - - var/mob/living/simple_animal/possessed_object/tomob = new(toitem) - - message_admins("[key_name_admin(usr)] has put [frommob.ckey] in control of [tomob.name].") - log_admin("[key_name(usr)] stuffed [frommob.ckey] into [tomob.name].") - feedback_add_details("admin_verb","CGD") - - tomob.ckey = frommob.ckey - qdel(frommob) - - - if(isliving(tothing)) - var/mob/living/tomob = tothing - - var/question = "" - if(tomob.ckey) - question = "This mob already has a user ([tomob.key]) in control of it! " - question += "Are you sure you want to place [frommob.name]([frommob.key]) in control of [tomob.name]?" - - var/ask = alert(question, "Place ghost in control of mob?", "Yes", "No") - if(ask != "Yes") - return 1 - - if(!frommob || !tomob) //make sure the mobs don't go away while we waited for a response - return 1 - - if(tomob.client) //no need to ghostize if there is no client - tomob.ghostize(0) - - message_admins("[key_name_admin(usr)] has put [frommob.ckey] in control of [tomob.name].") - log_admin("[key_name(usr)] stuffed [frommob.ckey] into [tomob.name].") - feedback_add_details("admin_verb","CGD") - - tomob.ckey = frommob.ckey - qdel(frommob) - - return 1 - -// Returns a list of the number of admins in various categories -// result[1] is the number of staff that match the rank mask and are active -// result[2] is the number of staff that do not match the rank mask -// result[3] is the number of staff that match the rank mask and are inactive -/proc/staff_countup(rank_mask = R_BAN) - var/list/result = list(0, 0, 0) - for(var/client/X in GLOB.admins) - if(rank_mask && !check_rights_for(X, rank_mask)) - result[2]++ - continue - if(X.holder.fakekey) - result[2]++ - continue - if(X.is_afk()) - result[3]++ - continue - result[1]++ - return result +var/global/BSACooldown = 0 +var/global/nologevent = 0 + +//////////////////////////////// +/proc/message_admins(var/msg) + msg = "ADMIN LOG: [msg]" + for(var/client/C in GLOB.admins) + if(R_ADMIN & C.holder.rights) + if(C.prefs && !(C.prefs.toggles & CHAT_NO_ADMINLOGS)) + to_chat(C, msg) + +/proc/msg_admin_attack(var/text, var/loglevel) + if(!nologevent) + var/rendered = "ATTACK: [text]" + for(var/client/C in GLOB.admins) + if(R_ADMIN & C.holder.rights) + if(C.prefs.atklog == ATKLOG_NONE) + continue + var/msg = rendered + if(C.prefs.atklog <= loglevel) + to_chat(C, msg) + + +/proc/message_adminTicket(var/msg, var/alt = FALSE) + if(alt) + msg = "ADMIN TICKET: [msg]" + else + msg = "ADMIN TICKET: [msg]" + for(var/client/C in GLOB.admins) + if(R_ADMIN & C.holder.rights) + if(C.prefs && !(C.prefs.toggles & CHAT_NO_TICKETLOGS)) + to_chat(C, msg) + +/proc/message_mentorTicket(var/msg) + for(var/client/C in GLOB.admins) + if(check_rights(R_ADMIN | R_MENTOR | R_MOD, 0, C.mob)) + if(C.prefs && !(C.prefs.toggles & CHAT_NO_MENTORTICKETLOGS)) + to_chat(C, msg) + +/proc/admin_ban_mobsearch(var/mob/M, var/ckey_to_find, var/mob/admin_to_notify) + if(!M || !M.ckey) + if(ckey_to_find) + for(var/mob/O in GLOB.mob_list) + if(O.ckey && O.ckey == ckey_to_find) + if(admin_to_notify) + to_chat(admin_to_notify, "admin_ban_mobsearch: Player [ckey_to_find] is now in mob [O]. Pulling data from new mob.") + return O + if(admin_to_notify) + to_chat(admin_to_notify, "admin_ban_mobsearch: Player [ckey_to_find] does not seem to have any mob, anywhere. This is probably an error.") + else if(admin_to_notify) + to_chat(admin_to_notify, "admin_ban_mobsearch: No mob or ckey detected.") + return M + +///////////////////////////////////////////////////////////////////////////////////////////////Panels + +/datum/admins/proc/show_player_panel(var/mob/M in GLOB.mob_list) + set category = null + set name = "Show Player Panel" + set desc="Edit player (respawn, ban, heal, etc)" + + if(!M) + to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.") + return + + if(!check_rights(R_ADMIN|R_MOD)) + return + + var/body = "Options for [M.key]" + body += "Options panel for [M]" + if(M.client) + body += " played by [M.client] " + body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\] " + body += "\[" + M.client.get_exp_type(EXP_TYPE_CREW) + " as [EXP_TYPE_CREW]\]" + + if(istype(M, /mob/new_player)) + body += " Hasn't Entered Game " + else + body += " \[Heal\] " + + body += "

        \[ " + body += "VV - " + body += "[ADMIN_TP(M,"TP")] - " + if(M.client) + body += "PM - " + body += "[ADMIN_SM(M,"SM")] - " + if(ishuman(M) && M.mind) + body += "HM -" + body += "[admin_jump_link(M)]\]

        " + body += "Mob type: [M.type]
        " + if(M.client) + if(M.client.related_accounts_cid.len) + body += "Related accounts by CID: [jointext(M.client.related_accounts_cid, " - ")]
        " + if(M.client.related_accounts_ip.len) + body += "Related accounts by IP: [jointext(M.client.related_accounts_ip, " - ")]

        " + + if(M.ckey) + body += "Kick | " + body += "Warn | " + body += "Ban | " + body += "Jobban | " + body += "Appearance Ban | " + body += "Notes | " + if(config.forum_playerinfo_url) + body += "WebInfo | " + if(M.client) + if(check_watchlist(M.client.ckey)) + body += "Remove from Watchlist | " + body += "Edit Watchlist Reason " + else + body += "Add to Watchlist " + + if(M.client) + body += "| Prison | " + body += "\ Send back to Lobby | " + var/muted = M.client.prefs.muted + body += {"
        Mute: + \[IC | + OOC | + PRAY | + ADMINHELP | + DEADCHAT\] + (toggle all) + "} + + var/jumptoeye = "" + if(isAI(M)) + var/mob/living/silicon/ai/A = M + if(A.client && A.eyeobj) // No point following clientless AI eyes + jumptoeye = " (Eye)" + body += {"

        + Jump to[jumptoeye] | + Get | + Send To +

        + [check_rights(R_ADMIN,0) ? "[ADMIN_TP(M,"Traitor panel")] | " : "" ] + Narrate to | + [ADMIN_SM(M,"Subtle message")] + "} + + if(check_rights(R_EVENT, 0)) + body += {" | Bless | Smite"} + + if(isLivingSSD(M)) + if(istype(M.loc, /obj/machinery/cryopod)) + body += {" | De-Spawn "} + else + body += {" | Cryo "} + + if(M.client) + if(!istype(M, /mob/new_player)) + body += "

        " + body += "Transformation:" + body += "
        " + + //Monkey + if(issmall(M)) + body += "Monkeyized | " + else + body += "Monkeyize | " + + //Corgi + if(iscorgi(M)) + body += "Corgized | " + else + body += "Corgize | " + + //AI / Cyborg + if(isAI(M)) + body += "Is an AI " + else if(ishuman(M)) + body += {"Make AI | + Make Robot | + Make Alien | + Make Slime | + Make Superhero + "} + + //Simple Animals + if(isanimal(M)) + body += "Re-Animalize | " + else + body += "Animalize | " + + if(istype(M, /mob/dead/observer)) + body += "Re-incarnate | " + + if(ispAI(M)) + body += "Is a pAI " + else + body += "Make pAI | " + + // DNA2 - Admin Hax + if(M.dna && iscarbon(M)) + body += "

        " + body += "DNA Blocks:
        " + var/bname + for(var/block=1;block<=DNA_SE_LENGTH;block++) + if(((block-1)%5)==0) + body += "" + bname = assigned_blocks[block] + body += "" + body += "
         12345
        [block-1]" + if(bname) + var/bstate=M.dna.GetSEState(block) + var/bcolor="[(bstate)?"#006600":"#ff0000"]" + body += "[bname][block]" + else + body += "[block]" + body+="
        " + + body += {"

        + Rudimentary transformation:
        These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.

        + Observer | + \[ Alien: Drone, + Hunter, + Queen, + Sentinel, + Larva \] + Human + \[ slime: Baby, + Adult \] + Monkey | + Cyborg | + Cat | + Runtime | + Corgi | + Ian | + Crab | + Coffee | + \[ Construct: Armoured , + Builder , + Wraith \] + Shade + "} + + if(M.client) + body += {"

        + Other actions: +
        + Forcesay | + Admin Room | + Thunderdome 1 | + Thunderdome 2 | + Thunderdome Admin | + Thunderdome Observer | + "} + + body += {"
        + + "} + + usr << browse(body, "window=adminplayeropts;size=550x615") + 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 +/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 + +#define PLAYER_NOTES_ENTRIES_PER_PAGE 50 +/datum/admins/proc/PlayerNotes() + set category = "Admin" + set name = "Player Notes" + + if(!check_rights(R_ADMIN|R_MOD)) + return + + show_note() + +/datum/admins/proc/show_player_notes(var/key as text) + set category = "Admin" + set name = "Show Player Notes" + + if(!check_rights(R_ADMIN|R_MOD)) + return + + show_note(key) + +/datum/admins/proc/vpn_whitelist() + set category = "Admin" + set name = "VPN Ckey Whitelist" + if(!check_rights(R_ADMIN)) + return + var/key = stripped_input(usr, "Enter ckey to add/remove, or leave blank to cancel:", "VPN Whitelist add/remove", max_length=32) + if(key) + vpn_whitelist_panel(key) + +/datum/admins/proc/access_news_network() //MARKER + set category = "Event" + set name = "Access Newscaster Network" + set desc = "Allows you to view, add and edit news feeds." + + if(!check_rights(R_EVENT)) + return + + if(!istype(src,/datum/admins)) + src = usr.client.holder + + var/dat + dat = text("Admin Newscaster

        Admin Newscaster Unit

        ") + + switch(admincaster_screen) + if(0) + dat += {"Welcome to the admin newscaster.
        Here you can add, edit and censor every newspiece on the network. +
        Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units. +
        Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things!
        + "} + if(news_network.wanted_issue) + dat+= "
        Read Wanted Issue" + + dat+= {"

        Create Feed Channel +
        View Feed Channels +
        Submit new Feed story +

        Exit + "} + + var/wanted_already = 0 + if(news_network.wanted_issue) + wanted_already = 1 + + dat+={"
        Feed Security functions:
        +
        [(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue +
        Censor Feed Stories +
        Mark Feed Channel with Nanotrasen D-Notice (disables and locks the channel. +

        The newscaster recognises you as:
        [src.admincaster_signature]
        + "} + if(1) + dat+= "Station Feed Channels
        " + if( isemptylist(news_network.network_channels) ) + dat+="No active channels found..." + else + for(var/datum/feed_channel/CHANNEL in news_network.network_channels) + if(CHANNEL.is_admin_channel) + dat+="[CHANNEL.channel_name]
        " + else + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
        " + dat+={"

        Refresh +
        Back + "} + + if(2) + dat+={" + Creating new Feed Channel... +
        Channel Name: [src.admincaster_feed_channel.channel_name]
        + Channel Author: [src.admincaster_signature]
        + Will Accept Public Feeds: [(src.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

        +
        Submit

        Cancel
        + "} + if(3) + dat+={" + Creating new Feed Message... +
        Receiving Channel: [src.admincaster_feed_channel.channel_name]
        + Message Author: [src.admincaster_signature]
        + Message Body: [src.admincaster_feed_message.body]
        +
        Submit

        Cancel
        + "} + if(4) + dat+={" + Feed story successfully submitted to [src.admincaster_feed_channel.channel_name].

        +
        Return
        + "} + if(5) + dat+={" + Feed Channel [src.admincaster_feed_channel.channel_name] created successfully.

        +
        Return
        + "} + if(6) + dat+="ERROR: Could not submit Feed story to Network.

        " + if(src.admincaster_feed_channel.channel_name=="") + dat+="•Invalid receiving channel name.
        " + if(src.admincaster_feed_message.body == "" || src.admincaster_feed_message.body == "\[REDACTED\]") + dat+="•Invalid message body.
        " + dat+="
        Return
        " + if(7) + dat+="ERROR: Could not submit Feed Channel to Network.

        " + if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]") + dat+="•Invalid channel name.
        " + var/check = 0 + for(var/datum/feed_channel/FC in news_network.network_channels) + if(FC.channel_name == src.admincaster_feed_channel.channel_name) + check = 1 + break + if(check) + dat+="•Channel name already in use.
        " + dat+="
        Return
        " + if(9) + dat+="[src.admincaster_feed_channel.channel_name]: \[created by: [src.admincaster_feed_channel.author]\]
        " + if(src.admincaster_feed_channel.censored) + dat+={" + ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
        + No further feed story additions are allowed while the D-Notice is in effect.


        + "} + else + if( isemptylist(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
        " + else + var/i = 0 + for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + i++ + dat+="-[MESSAGE.body]
        " + if(MESSAGE.img) + usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png") + dat+="

        " + dat+="\[Story by [MESSAGE.author]\]
        " + dat+={" +

        Refresh +
        Back + "} + if(10) + dat+={" + Nanotrasen Feed Censorship Tool
        + NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
        + Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.
        +
        Select Feed channel to get Stories from:
        + "} + if(isemptylist(news_network.network_channels)) + dat+="No feed channels found active...
        " + else + for(var/datum/feed_channel/CHANNEL in news_network.network_channels) + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
        " + dat+="
        Cancel" + if(11) + dat+={" + Nanotrasen D-Notice Handler
        + A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's + morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed + stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
        + "} + if(isemptylist(news_network.network_channels)) + dat+="No feed channels found active...
        " + else + for(var/datum/feed_channel/CHANNEL in news_network.network_channels) + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? ("***") : ""]
        " + + dat+="
        Back" + if(12) + dat+={" + [src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.author] \]
        + [(src.admincaster_feed_channel.author=="\[REDACTED\]") ? ("Undo Author censorship") : ("Censor channel Author")]
        + "} + if( isemptylist(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
        " + else + for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + dat+={" + -[MESSAGE.body]
        \[Story by [MESSAGE.author]\]
        + [(MESSAGE.body == "\[REDACTED\]") ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.author == "\[REDACTED\]") ? ("Undo Author Censorship") : ("Censor message Author")]
        + "} + dat+="
        Back" + if(13) + dat+={" + [src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.author] \]
        + Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
        + "} + if(src.admincaster_feed_channel.censored) + dat+={" + ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
        + No further feed story additions are allowed while the D-Notice is in effect.


        + "} + else + if( isemptylist(src.admincaster_feed_channel.messages) ) + dat+="No feed messages found in channel...
        " + else + for(var/datum/feed_message/MESSAGE in src.admincaster_feed_channel.messages) + dat+="-[MESSAGE.body]
        \[Story by [MESSAGE.author]\]
        " + + dat+="
        Back" + if(14) + dat+="Wanted Issue Handler:" + var/wanted_already = 0 + var/end_param = 1 + if(news_network.wanted_issue) + wanted_already = 1 + end_param = 2 + if(wanted_already) + dat+="
        A wanted issue is already in Feed Circulation. You can edit or cancel it below.
        " + dat+={" +
        + Criminal Name: [src.admincaster_feed_message.author]
        + Description: [src.admincaster_feed_message.body]
        + "} + if(wanted_already) + dat+="Wanted Issue created by: [news_network.wanted_issue.backup_author]
        " + else + dat+="Wanted Issue will be created under prosecutor: [src.admincaster_signature]
        " + dat+="
        [(wanted_already) ? ("Edit Issue") : ("Submit")]" + if(wanted_already) + dat+="
        Take down Issue" + dat+="
        Cancel" + if(15) + dat+={" + Wanted issue for [src.admincaster_feed_message.author] is now in Network Circulation.

        +
        Return
        + "} + if(16) + dat+="ERROR: Wanted Issue rejected by Network.

        " + if(src.admincaster_feed_message.author =="" || src.admincaster_feed_message.author == "\[REDACTED\]") + dat+="•Invalid name for person wanted.
        " + if(src.admincaster_feed_message.body == "" || src.admincaster_feed_message.body == "\[REDACTED\]") + dat+="•Invalid description.
        " + dat+="
        Return
        " + if(17) + dat+={" + Wanted Issue successfully deleted from Circulation
        +
        Return
        + "} + if(18) + dat+={" + -- STATIONWIDE WANTED ISSUE --
        \[Submitted by: [news_network.wanted_issue.backup_author]\]
        + Criminal: [news_network.wanted_issue.author]
        + Description: [news_network.wanted_issue.body]
        + Photo:: + "} + if(news_network.wanted_issue.img) + usr << browse_rsc(news_network.wanted_issue.img, "tmp_photow.png") + dat+="
        " + else + dat+="None" + dat+="
        Back
        " + if(19) + dat+={" + Wanted issue for [src.admincaster_feed_message.author] successfully edited.

        +
        Return
        + "} + else + dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" + +// to_chat(world, "Channelname: [src.admincaster_feed_channel.channel_name] [src.admincaster_feed_channel.author]") +// to_chat(world, "Msg: [src.admincaster_feed_message.author] [src.admincaster_feed_message.body]") + usr << browse(dat, "window=admincaster_main;size=400x600") + onclose(usr, "admincaster_main") + +/datum/admins/proc/Jobbans() + if(!check_rights(R_BAN)) + return + + var/dat = "Job Bans!
        " + for(var/t in jobban_keylist) + var/r = t + if( findtext(r,"##") ) + r = copytext( r, 1, findtext(r,"##") )//removes the description + dat += text("") + dat += "
        [t] (unban)
        " + usr << browse(dat, "window=ban;size=400x400") + +/datum/admins/proc/Game() + if(!check_rights(R_ADMIN)) + return + + var/dat = {" +
        Game Panel

        \n + Change Game Mode
        + "} + if(master_mode == "secret") + dat += "(Force Secret Mode)
        " + + dat += {" +
        + Create Object
        + Quick Create Object
        + Create Turf
        + Create Mob
        + "} + + usr << browse(dat, "window=admin2;size=210x280") + return + +/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge +//i.e. buttons/verbs + + +/datum/admins/proc/restart() + set category = "Server" + set name = "Restart" + set desc = "Restarts the world." + + if(!check_rights(R_SERVER)) + return + + var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null + if(isnull(delay)) + return + else + delay = delay * 10 + message_admins("[key_name_admin(usr)] has initiated a server restart with a delay of [delay/10] seconds") + log_admin("[key_name(usr)] has initiated a server restart with a delay of [delay/10] seconds") + SSticker.delay_end = 0 + feedback_add_details("admin_verb","R") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + world.Reboot("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key].", "end_error", "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay) + +/datum/admins/proc/announce() + set category = "Admin" + set name = "Announce" + set desc = "Announce your desires to the world" + + if(!check_rights(R_ADMIN)) + return + + var/message = input("Global message to send:", "Admin Announce", null, null) as message|null + if(message) + if(!check_rights(R_SERVER,0)) + message = adminscrub(message,500) + message = replacetext(message, "\n", "
        ") // required since we're putting it in a

        tag + to_chat(world, "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:

        [message]

        ") + log_admin("Announce: [key_name(usr)] : [message]") + feedback_add_details("admin_verb","A") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleooc() + set category = "Server" + set desc="Globally Toggles OOC" + set name="Toggle OOC" + + if(!check_rights(R_ADMIN)) + return + + toggle_ooc() + log_and_message_admins("toggled OOC.") + feedback_add_details("admin_verb","TOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/togglelooc() + set category = "Server" + set desc="Globally Toggles LOOC" + set name="Toggle LOOC" + + if(!check_rights(R_ADMIN)) + return + + config.looc_allowed = !(config.looc_allowed) + if(config.looc_allowed) + to_chat(world, "The LOOC channel has been globally enabled!") + else + to_chat(world, "The LOOC channel has been globally disabled!") + log_and_message_admins("toggled LOOC.") + feedback_add_details("admin_verb","TLOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggledsay() + set category = "Server" + set desc="Globally Toggles DSAY" + set name="Toggle DSAY" + + if(!check_rights(R_ADMIN)) + return + + config.dsay_allowed = !(config.dsay_allowed) + if(config.dsay_allowed) + to_chat(world, "Deadchat has been globally enabled!") + else + to_chat(world, "Deadchat has been globally disabled!") + log_admin("[key_name(usr)] toggled deadchat.") + message_admins("[key_name_admin(usr)] toggled deadchat.", 1) + feedback_add_details("admin_verb","TDSAY") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc + +/datum/admins/proc/toggleoocdead() + set category = "Server" + set desc="Toggle Dead OOC." + set name="Toggle Dead OOC" + + if(!check_rights(R_ADMIN)) + return + + config.dooc_allowed = !( config.dooc_allowed ) + log_admin("[key_name(usr)] toggled Dead OOC.") + message_admins("[key_name_admin(usr)] toggled Dead OOC.", 1) + feedback_add_details("admin_verb","TDOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleemoji() + set category = "Server" + set desc = "Toggle OOC Emoji" + set name = "Toggle OOC Emoji" + + if(!check_rights(R_ADMIN)) + return + + config.disable_ooc_emoji = !(config.disable_ooc_emoji) + log_admin("[key_name(usr)] toggled OOC Emoji.") + message_admins("[key_name_admin(usr)] toggled OOC Emoji.", 1) + feedback_add_details("admin_verb", "TEMOJ") + +/datum/admins/proc/startnow() + set category = "Server" + set desc="Start the round RIGHT NOW" + set name="Start Now" + + if(!check_rights(R_SERVER)) + return + + if(!SSticker) + alert("Unable to start the game as it is not set up.") + return + + if(config.start_now_confirmation) + if(alert(usr, "This is a live server. Are you sure you want to start now?", "Start game", "Yes", "No") != "Yes") + return + + if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP) + SSticker.force_start = TRUE + log_admin("[usr.key] has started the game.") + var/msg = "" + if(SSticker.current_state == GAME_STATE_STARTUP) + msg = " (The server is still setting up, but the round will be started as soon as possible.)" + message_admins("[usr.key] has started the game.[msg]") + feedback_add_details("admin_verb","SN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return 1 + else + to_chat(usr, "Error: Start Now: Game has already started.") + return + +/datum/admins/proc/toggleenter() + set category = "Server" + set desc="People can't enter" + set name="Toggle Entering" + + if(!check_rights(R_SERVER)) + return + + enter_allowed = !( enter_allowed ) + if(!( enter_allowed )) + to_chat(world, "New players may no longer enter the game.") + else + to_chat(world, "New players may now enter the game.") + log_admin("[key_name(usr)] toggled new player game entering.") + message_admins("[key_name_admin(usr)] toggled new player game entering.", 1) + world.update_status() + feedback_add_details("admin_verb","TE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleAI() + set category = "Event" + set desc="People can't be AI" + set name="Toggle AI" + + if(!check_rights(R_EVENT)) + return + + config.allow_ai = !( config.allow_ai ) + if(!( config.allow_ai )) + to_chat(world, "The AI job is no longer chooseable.") + else + to_chat(world, "The AI job is chooseable now.") + message_admins("[key_name_admin(usr)] toggled AI allowed.") + log_admin("[key_name(usr)] toggled AI allowed.") + world.update_status() + feedback_add_details("admin_verb","TAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleaban() + set category = "Server" + set desc="Toggle the ability for players to respawn." + set name="Toggle Respawn" + + if(!check_rights(R_SERVER)) + return + + abandon_allowed = !( abandon_allowed ) + if(abandon_allowed) + to_chat(world, "You may now respawn.") + else + to_chat(world, "You may no longer respawn :(") + message_admins("[key_name_admin(usr)] toggled respawn to [abandon_allowed ? "On" : "Off"].", 1) + log_admin("[key_name(usr)] toggled respawn to [abandon_allowed ? "On" : "Off"].") + world.update_status() + feedback_add_details("admin_verb","TR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggle_aliens() + set category = "Event" + set desc="Toggle alien mobs" + set name="Toggle Aliens" + + if(!check_rights(R_EVENT)) + return + + aliens_allowed = !aliens_allowed + log_admin("[key_name(usr)] toggled aliens to [aliens_allowed].") + message_admins("[key_name_admin(usr)] toggled aliens [aliens_allowed ? "on" : "off"].") + feedback_add_details("admin_verb","TA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/delay() + set category = "Server" + set desc="Delay the game start/end" + set name="Delay" + + if(!check_rights(R_SERVER)) + return + + if(!SSticker || SSticker.current_state != GAME_STATE_PREGAME) + SSticker.delay_end = !SSticker.delay_end + log_admin("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].") + message_admins("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].", 1) + return //alert("Round end delayed", null, null, null, null, null) + if(going) + going = FALSE + SSticker.delay_end = TRUE + to_chat(world, "The game start has been delayed.") + log_admin("[key_name(usr)] delayed the game.") + else + going = TRUE + to_chat(world, "The game will start soon.") + log_admin("[key_name(usr)] removed the delay.") + feedback_add_details("admin_verb","DELAY") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS + +/proc/is_special_character(mob/M as mob) // returns 1 for specail characters and 2 for heroes of gamemode + if(!SSticker || !SSticker.mode) + return 0 + if(!istype(M)) + return 0 + if((M.mind in SSticker.mode.head_revolutionaries) || (M.mind in SSticker.mode.revolutionaries)) + if(SSticker.mode.config_tag == "revolution") + return 2 + return 1 + if(M.mind in SSticker.mode.cult) + if(SSticker.mode.config_tag == "cult") + return 2 + return 1 + if(M.mind in SSticker.mode.syndicates) + if(SSticker.mode.config_tag == "nuclear") + return 2 + return 1 + if(M.mind in SSticker.mode.wizards) + if(SSticker.mode.config_tag == "wizard") + return 2 + return 1 + if(M.mind in SSticker.mode.changelings) + if(SSticker.mode.config_tag == "changeling") + return 2 + return 1 + if(M.mind in SSticker.mode.abductors) + if(SSticker.mode.config_tag == "abduction") + return 2 + return 1 + if(isrobot(M)) + var/mob/living/silicon/robot/R = M + if(R.emagged) + return 1 + if(M.mind&&M.mind.special_role)//If they have a mind and special role, they are some type of traitor or antagonist. + return 1 + + return 0 + +/datum/admins/proc/spawn_atom(var/object as text) + set category = "Debug" + set desc = "(atom path) Spawn an atom" + set name = "Spawn" + + if(!check_rights(R_SPAWN)) + return + + var/list/types = typesof(/atom) + var/list/matches = new() + + for(var/path in types) + if(findtext("[path]", object)) + matches += path + + if(matches.len==0) + return + + var/chosen + if(matches.len==1) + chosen = matches[1] + else + chosen = input("Select an atom type", "Spawn Atom", matches[1]) as null|anything in matches + if(!chosen) + return + + if(ispath(chosen,/turf)) + var/turf/T = get_turf(usr.loc) + T.ChangeTurf(chosen) + else + var/atom/A = new chosen(usr.loc) + A.admin_spawned = TRUE + + log_admin("[key_name(usr)] spawned [chosen] at ([usr.x],[usr.y],[usr.z])") + feedback_add_details("admin_verb","SA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/show_traitor_panel(var/mob/M in GLOB.mob_list) + set category = "Admin" + set desc = "Edit mobs's memory and role" + set name = "Show Traitor Panel" + + if(!check_rights(R_ADMIN|R_MOD)) + return + + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(!M.mind) + to_chat(usr, "This mob has no mind!") + return + + M.mind.edit_memory() + feedback_add_details("admin_verb","STP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/toggleguests() + set category = "Server" + set desc="Guests can't enter" + set name="Toggle Guests" + + if(!check_rights(R_SERVER)) + return + + guests_allowed = !( guests_allowed ) + if(!( guests_allowed )) + to_chat(world, "Guests may no longer enter the game.") + else + to_chat(world, "Guests may now enter the game.") + log_admin("[key_name(usr)] toggled guests game entering [guests_allowed ? "" : "dis"]allowed.") + message_admins("[key_name_admin(usr)] toggled guests game entering [guests_allowed ? "" : "dis"]allowed.", 1) + feedback_add_details("admin_verb","TGU") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/datum/admins/proc/output_ai_laws() + var/ai_number = 0 + for(var/mob/living/silicon/S in GLOB.mob_list) + ai_number++ + if(isAI(S)) + to_chat(usr, "AI [key_name(S, TRUE)]'s laws:") + else if(isrobot(S)) + var/mob/living/silicon/robot/R = S + to_chat(usr, "CYBORG [key_name(S, TRUE)]'s [R.connected_ai?"(Slaved to: [R.connected_ai])":"(Independent)"] laws:") + else if(ispAI(S)) + var/mob/living/silicon/pai/P = S + to_chat(usr, "pAI [key_name(S, TRUE)]'s laws:") + to_chat(usr, "[P.pai_law0]") + if(P.pai_laws) + to_chat(usr, "[P.pai_laws]") + continue // Skip showing normal silicon laws for pAIs - they don't have any + else + to_chat(usr, "SILICON [key_name(S, TRUE)]'s laws:") + + if(S.laws == null) + to_chat(usr, "[key_name(S, TRUE)]'s laws are null. Contact a coder.") + else + S.laws.show_laws(usr) + if(!ai_number) + to_chat(usr, "No AI's located.")//Just so you know the thing is actually working and not just ignoring you. + + + log_admin("[key_name(usr)] checked the AI laws") + message_admins("[key_name_admin(usr)] checked the AI laws") + +/client/proc/update_mob_sprite(mob/living/carbon/human/H as mob) + set name = "Update Mob Sprite" + set desc = "Should fix any mob sprite update errors." + set category = null + + if(!check_rights(R_ADMIN)) + return + + if(istype(H)) + H.regenerate_icons() + +// +// +//ALL DONE +//********************************************************************************************************* + +var/gamma_ship_location = 1 // 0 = station , 1 = space + +/proc/move_gamma_ship() + var/area/fromArea + var/area/toArea + if(gamma_ship_location == 1) + fromArea = locate(/area/shuttle/gamma/space) + toArea = locate(/area/shuttle/gamma/station) + else + fromArea = locate(/area/shuttle/gamma/station) + toArea = locate(/area/shuttle/gamma/space) + fromArea.move_contents_to(toArea) + + for(var/obj/machinery/mech_bay_recharge_port/P in toArea) + P.update_recharge_turf() + + if(gamma_ship_location) + gamma_ship_location = 0 + else + gamma_ship_location = 1 + return + +/proc/formatJumpTo(var/location,var/where="") + var/turf/loc + if(istype(location,/turf/)) + loc = location + else + loc = get_turf(location) + if(where=="") + where=formatLocation(loc) + return "[where]" + +/proc/formatLocation(var/location) + var/turf/loc + if(istype(location,/turf/)) + loc = location + else + loc = get_turf(location) + var/area/A = get_area(location) + return "[A.name] - [loc.x],[loc.y],[loc.z]" + +/proc/formatPlayerPanel(var/mob/U,var/text="PP") + return "[ADMIN_PP(U,"[text]")]" + +//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked +//defaults to kicking everyone (afk + non afk clients in the lobby) +//returns a list of ckeys of the kicked clients +/proc/kick_clients_in_lobby(message, kick_only_afk = 0) + var/list/kicked_client_names = list() + for(var/client/C in GLOB.clients) + if(istype(C.mob, /mob/new_player)) + if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk + continue + if(message) + to_chat(C, message) + kicked_client_names.Add("[C.ckey]") + del(C) + return kicked_client_names + +//returns 1 to let the dragdrop code know we are trapping this event +//returns 0 if we don't plan to trap the event +/datum/admins/proc/cmd_ghost_drag(var/mob/dead/observer/frommob, var/tothing) + if(!istype(frommob)) + return //extra sanity check to make sure only observers are shoved into things + + //same as assume-direct-control perm requirements. + if(!check_rights(R_VAREDIT,0)) //no varedit, check if they have r_admin and r_debug + if(!check_rights(R_ADMIN|R_DEBUG,0)) //if they don't have r_admin and r_debug, return + return 0 //otherwise, if they have no varedit, but do have r_admin and r_debug, execute the rest of the code + + if(!frommob.ckey) + return 0 + + if(istype(tothing, /obj/item)) + var/mob/living/toitem = tothing + + var/ask = alert("Are you sure you want to allow [frommob.name]([frommob.key]) to possess [toitem.name]?", "Place ghost in control of item?", "Yes", "No") + if(ask != "Yes") + return 1 + + if(!frommob || !toitem) //make sure the mobs don't go away while we waited for a response + return 1 + + var/mob/living/simple_animal/possessed_object/tomob = new(toitem) + + message_admins("[key_name_admin(usr)] has put [frommob.ckey] in control of [tomob.name].") + log_admin("[key_name(usr)] stuffed [frommob.ckey] into [tomob.name].") + feedback_add_details("admin_verb","CGD") + + tomob.ckey = frommob.ckey + qdel(frommob) + + + if(isliving(tothing)) + var/mob/living/tomob = tothing + + var/question = "" + if(tomob.ckey) + question = "This mob already has a user ([tomob.key]) in control of it! " + question += "Are you sure you want to place [frommob.name]([frommob.key]) in control of [tomob.name]?" + + var/ask = alert(question, "Place ghost in control of mob?", "Yes", "No") + if(ask != "Yes") + return 1 + + if(!frommob || !tomob) //make sure the mobs don't go away while we waited for a response + return 1 + + if(tomob.client) //no need to ghostize if there is no client + tomob.ghostize(0) + + message_admins("[key_name_admin(usr)] has put [frommob.ckey] in control of [tomob.name].") + log_admin("[key_name(usr)] stuffed [frommob.ckey] into [tomob.name].") + feedback_add_details("admin_verb","CGD") + + tomob.ckey = frommob.ckey + qdel(frommob) + + return 1 + +// Returns a list of the number of admins in various categories +// result[1] is the number of staff that match the rank mask and are active +// result[2] is the number of staff that do not match the rank mask +// result[3] is the number of staff that match the rank mask and are inactive +/proc/staff_countup(rank_mask = R_BAN) + var/list/result = list(0, 0, 0) + for(var/client/X in GLOB.admins) + if(rank_mask && !check_rights_for(X, rank_mask)) + result[2]++ + continue + if(X.holder.fakekey) + result[2]++ + continue + if(X.is_afk()) + result[3]++ + continue + result[1]++ + return result diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index e781e038991..82f82d3dabe 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -1,160 +1,160 @@ -var/list/admin_ranks = list() //list of all ranks with associated rights - -//load our rank - > rights associations -/proc/load_admin_ranks() - admin_ranks.Cut() - - var/previous_rights = 0 - - //load text from file - var/list/Lines = file2list("config/admin_ranks.txt") - - //process each line seperately - for(var/line in Lines) - if(!length(line)) continue - if(copytext(line,1,2) == "#") continue - - var/list/List = splittext(line,"+") - if(!List.len) continue - - var/rank = ckeyEx(List[1]) - switch(rank) - if(null,"") continue - if("Removed") continue //Reserved - - var/rights = 0 - for(var/i=2, i<=List.len, i++) - switch(ckey(List[i])) - if("@","prev") rights |= previous_rights - if("buildmode","build") rights |= R_BUILDMODE - if("admin") rights |= R_ADMIN - if("ban") rights |= R_BAN - if("event") rights |= R_EVENT - if("server") rights |= R_SERVER - if("debug") rights |= R_DEBUG - if("permissions","rights") rights |= R_PERMISSIONS - if("possess") rights |= R_POSSESS - if("stealth") rights |= R_STEALTH - if("rejuv","rejuvinate") rights |= R_REJUVINATE - if("varedit") rights |= R_VAREDIT - if("everything","host","all") rights |= R_HOST - if("sound","sounds") rights |= R_SOUNDS - if("spawn","create") rights |= R_SPAWN - if("mod") rights |= R_MOD - if("mentor") rights |= R_MENTOR - if("proccall") rights |= R_PROCCALL - - admin_ranks[rank] = rights - previous_rights = rights - - #ifdef TESTING - var/msg = "Permission Sets Built:\n" - for(var/rank in admin_ranks) - msg += "\t[rank] - [admin_ranks[rank]]\n" - testing(msg) - #endif - -/hook/startup/proc/loadAdmins() - load_admins() - return 1 - -/proc/load_admins() - //clear the datums references - admin_datums.Cut() - for(var/client/C in GLOB.admins) - C.remove_admin_verbs() - C.holder = null - GLOB.admins.Cut() - - if(config.admin_legacy_system) - load_admin_ranks() - - //load text from file - var/list/Lines = file2list("config/admins.txt") - - //process each line seperately - for(var/line in Lines) - if(!length(line)) continue - if(copytext(line,1,2) == "#") continue - - //Split the line at every "-" - var/list/List = splittext(line, "-") - if(!List.len) continue - - //ckey is before the first "-" - var/ckey = ckey(List[1]) - if(!ckey) continue - - //rank follows the first "-" - var/rank = "" - if(List.len >= 2) - rank = ckeyEx(List[2]) - - //load permissions associated with this rank - var/rights = admin_ranks[rank] - - //create the admin datum and store it for later use - var/datum/admins/D = new /datum/admins(rank, rights, ckey) - - //find the client for a ckey if they are connected and associate them with the new admin datum - D.associate(GLOB.directory[ckey]) - - else - //The current admin system uses SQL - - establish_db_connection() - if(!dbcon.IsConnected()) - log_world("Failed to connect to database in load_admins(). Reverting to legacy system.") - config.admin_legacy_system = 1 - load_admins() - return - - var/DBQuery/query = dbcon.NewQuery("SELECT ckey, rank, level, flags FROM [format_table_name("admin")]") - query.Execute() - while(query.NextRow()) - var/ckey = query.item[1] - var/rank = query.item[2] - if(rank == "Removed") continue //This person was de-adminned. They are only in the admin list for archive purposes. - - var/rights = query.item[4] - if(istext(rights)) rights = text2num(rights) - var/datum/admins/D = new /datum/admins(rank, rights, ckey) - - //find the client for a ckey if they are connected and associate them with the new admin datum - D.associate(GLOB.directory[ckey]) - if(!admin_datums) - log_world("The database query in load_admins() resulted in no admins being added to the list. Reverting to legacy system.") - config.admin_legacy_system = 1 - load_admins() - return - - #ifdef TESTING - var/msg = "Admins Built:\n" - for(var/ckey in admin_datums) - var/rank - var/datum/admins/D = admin_datums[ckey] - if(D) rank = D.rank - msg += "\t[ckey] - [rank]\n" - testing(msg) - #endif - - -#ifdef TESTING -/client/verb/changerank(newrank in admin_ranks) - if(holder) - holder.rank = newrank - holder.rights = admin_ranks[newrank] - else - holder = new /datum/admins(newrank,admin_ranks[newrank],ckey) - remove_admin_verbs() - holder.associate(src) - -/client/verb/changerights(newrights as num) - if(holder) - holder.rights = newrights - else - holder = new /datum/admins("testing",newrights,ckey) - remove_admin_verbs() - holder.associate(src) - -#endif +var/list/admin_ranks = list() //list of all ranks with associated rights + +//load our rank - > rights associations +/proc/load_admin_ranks() + admin_ranks.Cut() + + var/previous_rights = 0 + + //load text from file + var/list/Lines = file2list("config/admin_ranks.txt") + + //process each line seperately + for(var/line in Lines) + if(!length(line)) continue + if(copytext(line,1,2) == "#") continue + + var/list/List = splittext(line,"+") + if(!List.len) continue + + var/rank = ckeyEx(List[1]) + switch(rank) + if(null,"") continue + if("Removed") continue //Reserved + + var/rights = 0 + for(var/i=2, i<=List.len, i++) + switch(ckey(List[i])) + if("@","prev") rights |= previous_rights + if("buildmode","build") rights |= R_BUILDMODE + if("admin") rights |= R_ADMIN + if("ban") rights |= R_BAN + if("event") rights |= R_EVENT + if("server") rights |= R_SERVER + if("debug") rights |= R_DEBUG + if("permissions","rights") rights |= R_PERMISSIONS + if("possess") rights |= R_POSSESS + if("stealth") rights |= R_STEALTH + if("rejuv","rejuvinate") rights |= R_REJUVINATE + if("varedit") rights |= R_VAREDIT + if("everything","host","all") rights |= R_HOST + if("sound","sounds") rights |= R_SOUNDS + if("spawn","create") rights |= R_SPAWN + if("mod") rights |= R_MOD + if("mentor") rights |= R_MENTOR + if("proccall") rights |= R_PROCCALL + + admin_ranks[rank] = rights + previous_rights = rights + + #ifdef TESTING + var/msg = "Permission Sets Built:\n" + for(var/rank in admin_ranks) + msg += "\t[rank] - [admin_ranks[rank]]\n" + testing(msg) + #endif + +/hook/startup/proc/loadAdmins() + load_admins() + return 1 + +/proc/load_admins() + //clear the datums references + admin_datums.Cut() + for(var/client/C in GLOB.admins) + C.remove_admin_verbs() + C.holder = null + GLOB.admins.Cut() + + if(config.admin_legacy_system) + load_admin_ranks() + + //load text from file + var/list/Lines = file2list("config/admins.txt") + + //process each line seperately + for(var/line in Lines) + if(!length(line)) continue + if(copytext(line,1,2) == "#") continue + + //Split the line at every "-" + var/list/List = splittext(line, "-") + if(!List.len) continue + + //ckey is before the first "-" + var/ckey = ckey(List[1]) + if(!ckey) continue + + //rank follows the first "-" + var/rank = "" + if(List.len >= 2) + rank = ckeyEx(List[2]) + + //load permissions associated with this rank + var/rights = admin_ranks[rank] + + //create the admin datum and store it for later use + var/datum/admins/D = new /datum/admins(rank, rights, ckey) + + //find the client for a ckey if they are connected and associate them with the new admin datum + D.associate(GLOB.directory[ckey]) + + else + //The current admin system uses SQL + + establish_db_connection() + if(!dbcon.IsConnected()) + log_world("Failed to connect to database in load_admins(). Reverting to legacy system.") + config.admin_legacy_system = 1 + load_admins() + return + + var/DBQuery/query = dbcon.NewQuery("SELECT ckey, rank, level, flags FROM [format_table_name("admin")]") + query.Execute() + while(query.NextRow()) + var/ckey = query.item[1] + var/rank = query.item[2] + if(rank == "Removed") continue //This person was de-adminned. They are only in the admin list for archive purposes. + + var/rights = query.item[4] + if(istext(rights)) rights = text2num(rights) + var/datum/admins/D = new /datum/admins(rank, rights, ckey) + + //find the client for a ckey if they are connected and associate them with the new admin datum + D.associate(GLOB.directory[ckey]) + if(!admin_datums) + log_world("The database query in load_admins() resulted in no admins being added to the list. Reverting to legacy system.") + config.admin_legacy_system = 1 + load_admins() + return + + #ifdef TESTING + var/msg = "Admins Built:\n" + for(var/ckey in admin_datums) + var/rank + var/datum/admins/D = admin_datums[ckey] + if(D) rank = D.rank + msg += "\t[ckey] - [rank]\n" + testing(msg) + #endif + + +#ifdef TESTING +/client/verb/changerank(newrank in admin_ranks) + if(holder) + holder.rank = newrank + holder.rights = admin_ranks[newrank] + else + holder = new /datum/admins(newrank,admin_ranks[newrank],ckey) + remove_admin_verbs() + holder.associate(src) + +/client/verb/changerights(newrights as num) + if(holder) + holder.rights = newrights + else + holder = new /datum/admins("testing",newrights,ckey) + remove_admin_verbs() + holder.associate(src) + +#endif diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index b77eea08b7b..9a60d41aa66 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -1,1018 +1,1018 @@ -//admin verb groups - They can overlap if you so wish. Only one of each verb will exist in the verbs list regardless -var/list/admin_verbs_default = list( - /client/proc/deadmin_self, /*destroys our own admin datum so we can play as a regular player*/ - /client/proc/hide_verbs, /*hides all our adminverbs*/ - /client/proc/toggleadminhelpsound, - /client/proc/togglementorhelpsound, - /client/proc/cmd_mentor_check_new_players, - /client/proc/cmd_mentor_check_player_exp /* shows players by playtime */ - ) -var/list/admin_verbs_admin = list( - /client/proc/check_antagonists, /*shows all antags*/ - /datum/admins/proc/show_player_panel, - /client/proc/player_panel, /*shows an interface for all players, with links to various panels (old style)*/ - /client/proc/player_panel_new, /*shows an interface for all players, with links to various panels*/ - /client/proc/invisimin, /*allows our mob to go invisible/visible*/ - /datum/admins/proc/toggleenter, /*toggles whether people can join the current game*/ - /datum/admins/proc/toggleguests, /*toggles whether guests can join the current game*/ - /datum/admins/proc/announce, /*priority announce something to all clients.*/ - /client/proc/colorooc, /*allows us to set a custom colour for everything we say in ooc*/ - /client/proc/resetcolorooc, /*allows us to set a reset our ooc color*/ - /client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/ - /client/proc/toggle_view_range, /*changes how far we can see*/ - /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ - /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ - /client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/ - /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/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*/ - /client/proc/Getkey, /*teleports a mob with a certain ckey to our location*/ - /client/proc/Jump, - /client/proc/jumptokey, /*allows us to jump to the location of a mob with a certain ckey*/ - /client/proc/jumptomob, /*allows us to jump to a specific mob*/ - /client/proc/jumptoturf, /*allows us to jump to a specific turf*/ - /client/proc/admin_call_shuttle, /*allows us to call the emergency shuttle*/ - /client/proc/admin_cancel_shuttle, /*allows us to cancel the emergency shuttle, sending it back to centcomm*/ - /client/proc/check_ai_laws, /*shows AI and borg laws*/ - /client/proc/manage_silicon_laws, /* Allows viewing and editing silicon laws. */ - /client/proc/admin_memo, /*admin memo system. show/delete/write. +SERVER needed to delete admin memos of others*/ - /client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/ - /client/proc/toggleprayers, /*toggles prayers on/off*/ - /client/proc/toggle_hear_radio, /*toggles whether we hear the radio*/ - /client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/ - /datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/ - /datum/admins/proc/togglelooc, /*toggles looc on/off for everyone*/ - /datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/ - /datum/admins/proc/toggledsay, /*toggles dsay on/off for everyone*/ - /datum/admins/proc/toggleemoji, /*toggles using emoji in ooc for everyone*/ - /client/proc/game_panel, /*game panel, allows to change game-mode etc*/ - /client/proc/cmd_admin_say, /*admin-only ooc chat*/ - /datum/admins/proc/PlayerNotes, - /client/proc/cmd_mentor_say, - /datum/admins/proc/show_player_notes, - /datum/admins/proc/vpn_whitelist, - /client/proc/free_slot, /*frees slot for chosen job*/ - /client/proc/toggleattacklogs, - /client/proc/toggleadminlogs, - /client/proc/toggledebuglogs, - /client/proc/update_mob_sprite, - /client/proc/toggledrones, - /client/proc/man_up, - /client/proc/global_man_up, - /client/proc/delbook, - /client/proc/view_flagged_books, - /client/proc/empty_ai_core_toggle_latejoin, - /client/proc/aooc, - /client/proc/freeze, - /client/proc/freezemecha, - /client/proc/alt_check, - /client/proc/secrets, - /client/proc/change_human_appearance_admin, /* Allows an admin to change the basic appearance of human-based mobs */ - /client/proc/change_human_appearance_self, /* Allows the human-based mob itself to change its basic appearance */ - /client/proc/debug_variables, - /client/proc/reset_all_tcs, /*resets all telecomms scripts*/ - /client/proc/toggle_mentor_chat, - /client/proc/toggle_advanced_interaction, /*toggle admin ability to interact with not only machines, but also atoms such as buttons and doors*/ - /client/proc/list_ssds_afks, - /client/proc/cmd_admin_headset_message, - /client/proc/spawn_floor_cluwne -) -var/list/admin_verbs_ban = list( - /client/proc/unban_panel, - /client/proc/jobbans, - /client/proc/stickybanpanel - ) -var/list/admin_verbs_sounds = list( - /client/proc/play_local_sound, - /client/proc/play_sound, - /client/proc/play_server_sound, - /client/proc/play_intercomm_sound, - /client/proc/stop_global_admin_sounds - ) -var/list/admin_verbs_event = list( - /client/proc/object_talk, - /client/proc/cmd_admin_dress, - /client/proc/cmd_admin_gib_self, - /client/proc/drop_bomb, - /client/proc/cinematic, - /client/proc/one_click_antag, - /datum/admins/proc/toggle_aliens, - /client/proc/cmd_admin_add_freeform_ai_law, - /client/proc/cmd_admin_add_random_ai_law, - /client/proc/make_sound, - /client/proc/toggle_random_events, - /client/proc/toggle_random_events, - /client/proc/toggle_ert_calling, - /client/proc/show_tip, - /client/proc/cmd_admin_change_custom_event, - /datum/admins/proc/access_news_network, /*allows access of newscasters*/ - /client/proc/cmd_admin_direct_narrate, /*send text directly to a player with no padding. Useful for narratives and fluff-text*/ - /client/proc/cmd_admin_world_narrate, /*sends text to all players with no padding*/ - /client/proc/response_team, // Response Teams admin verb - /client/proc/cmd_admin_create_centcom_report, - /client/proc/fax_panel, - /client/proc/event_manager_panel, - /client/proc/modify_goals, - /client/proc/outfit_manager - ) - -var/list/admin_verbs_spawn = list( - /datum/admins/proc/spawn_atom, /*allows us to spawn instances*/ - /client/proc/respawn_character, - /client/proc/admin_deserialize - ) -var/list/admin_verbs_server = list( - /client/proc/ToRban, - /client/proc/Set_Holiday, - /datum/admins/proc/startnow, - /datum/admins/proc/restart, - /datum/admins/proc/delay, - /datum/admins/proc/toggleaban, - /client/proc/toggle_log_hrefs, - /client/proc/everyone_random, - /datum/admins/proc/toggleAI, - /client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/ - /client/proc/cmd_debug_del_all, - /client/proc/cmd_debug_del_sing, - /datum/admins/proc/toggle_aliens, - /client/proc/delbook, - /client/proc/view_flagged_books, - /client/proc/toggle_antagHUD_use, - /client/proc/toggle_antagHUD_restrictions, - /client/proc/set_ooc, - /client/proc/reset_ooc - ) -var/list/admin_verbs_debug = list( - /client/proc/cmd_admin_list_open_jobs, - /client/proc/Debug2, - /client/proc/cmd_debug_make_powernets, - /client/proc/debug_controller, - /client/proc/cmd_debug_mob_lists, - /client/proc/cmd_admin_delete, - /client/proc/cmd_debug_del_all, - /client/proc/cmd_debug_del_sing, - /client/proc/reload_admins, - /client/proc/restart_controller, - /client/proc/enable_debug_verbs, - /client/proc/toggledebuglogs, - /client/proc/cmd_display_del_log, - /client/proc/cmd_display_del_log_simple, - /client/proc/debugNatureMapGenerator, - /client/proc/check_bomb_impacts, - /client/proc/test_movable_UI, - /client/proc/test_snap_UI, - /client/proc/cinematic, - /proc/machine_upgrade, - /client/proc/map_template_load, - /client/proc/map_template_upload, - /client/proc/view_runtimes, - /client/proc/admin_serialize, - /client/proc/jump_to_ruin, - /client/proc/toggle_medal_disable, - ) -var/list/admin_verbs_possess = list( - /proc/possess, - /proc/release - ) -var/list/admin_verbs_permissions = list( - /client/proc/edit_admin_permissions, - /client/proc/create_poll, - /client/proc/big_brother - ) -var/list/admin_verbs_rejuv = list( - /client/proc/respawn_character, - /client/proc/cmd_admin_rejuvenate - ) -var/list/admin_verbs_mod = list( - /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ - /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ - /client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/ - /datum/admins/proc/PlayerNotes, - /client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/ - /client/proc/cmd_mentor_say, - /datum/admins/proc/show_player_notes, - /client/proc/player_panel_new, - /client/proc/dsay, - /datum/admins/proc/show_player_panel, - /client/proc/jobbans, - /client/proc/debug_variables /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/ -) -var/list/admin_verbs_mentor = list( - /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ - /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ - /client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/ - /client/proc/openMentorTicketUI, - /client/proc/toggleMentorTicketLogs, - /client/proc/cmd_mentor_say /* mentor say*/ - // cmd_mentor_say is added/removed by the toggle_mentor_chat verb -) -var/list/admin_verbs_proccall = list( - /client/proc/callproc, - /client/proc/callproc_datum, - /client/proc/SDQL2_query -) -var/list/admin_verbs_ticket = list( - /client/proc/openAdminTicketUI, - /client/proc/toggleticketlogs, - /client/proc/openMentorTicketUI, - /client/proc/toggleMentorTicketLogs, - /client/proc/resolveAllAdminTickets, - /client/proc/resolveAllMentorTickets -) - -/client/proc/on_holder_add() - if(chatOutput && chatOutput.loaded) - chatOutput.loadAdmin() - -/client/proc/add_admin_verbs() - if(holder) - verbs += admin_verbs_default - if(holder.rights & R_BUILDMODE) - verbs += /client/proc/togglebuildmodeself - if(holder.rights & R_ADMIN) - verbs += admin_verbs_admin - verbs += admin_verbs_ticket - spawn(1) - control_freak = 0 - if(holder.rights & R_BAN) - verbs += admin_verbs_ban - if(holder.rights & R_EVENT) - verbs += admin_verbs_event - if(holder.rights & R_SERVER) - verbs += admin_verbs_server - if(holder.rights & R_DEBUG) - verbs += admin_verbs_debug - if(holder.rights & R_POSSESS) - verbs += admin_verbs_possess - if(holder.rights & R_PERMISSIONS) - verbs += admin_verbs_permissions - if(holder.rights & R_STEALTH) - verbs += /client/proc/stealth - if(holder.rights & R_REJUVINATE) - verbs += admin_verbs_rejuv - if(holder.rights & R_SOUNDS) - verbs += admin_verbs_sounds - if(holder.rights & R_SPAWN) - verbs += admin_verbs_spawn - if(holder.rights & R_MOD) - verbs += admin_verbs_mod - if(holder.rights & R_MENTOR) - verbs += admin_verbs_mentor - if(holder.rights & R_PROCCALL) - verbs += admin_verbs_proccall - -/client/proc/remove_admin_verbs() - verbs.Remove( - admin_verbs_default, - /client/proc/togglebuildmodeself, - admin_verbs_admin, - admin_verbs_ban, - admin_verbs_event, - admin_verbs_server, - admin_verbs_debug, - admin_verbs_possess, - admin_verbs_permissions, - /client/proc/stealth, - admin_verbs_rejuv, - admin_verbs_sounds, - admin_verbs_spawn, - admin_verbs_mod, - admin_verbs_mentor, - admin_verbs_proccall, - admin_verbs_show_debug_verbs, - /client/proc/readmin, - admin_verbs_ticket - ) - -/client/proc/hide_verbs() - set name = "Adminverbs - Hide All" - set category = "Admin" - - if(!holder) - return - - remove_admin_verbs() - verbs += /client/proc/show_verbs - - to_chat(src, "Almost all of your adminverbs have been hidden.") - feedback_add_details("admin_verb","TAVVH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/show_verbs() - set name = "Adminverbs - Show" - set category = "Admin" - - if(!holder) - return - - verbs -= /client/proc/show_verbs - add_admin_verbs() - - to_chat(src, "All of your adminverbs are now visible.") - feedback_add_details("admin_verb","TAVVS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/admin_ghost() - set category = "Admin" - set name = "Aghost" - - if(!check_rights(R_ADMIN|R_MOD)) - return - - if(istype(mob,/mob/dead/observer)) - //re-enter - var/mob/dead/observer/ghost = mob - ghost.can_reenter_corpse = 1 //just in-case. - ghost.reenter_corpse() - log_admin("[key_name(usr)] re-entered their body") - feedback_add_details("admin_verb","P") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - else if(istype(mob,/mob/new_player)) - to_chat(src, "Error: Aghost: Can't admin-ghost whilst in the lobby. Join or observe first.") - else - //ghostize - var/mob/body = mob - body.ghostize(1) - if(body && !body.key) - body.key = "@[key]" //Haaaaaaaack. But the people have spoken. If it breaks; blame adminbus - log_admin("[key_name(usr)] has admin-ghosted") - feedback_add_details("admin_verb","O") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/invisimin() - set name = "Invisimin" - set category = "Admin" - set desc = "Toggles ghost-like invisibility (Don't abuse this)" - - if(!check_rights(R_ADMIN)) - return - - if(mob) - if(mob.invisibility == INVISIBILITY_OBSERVER) - mob.invisibility = initial(mob.invisibility) - to_chat(mob, "Invisimin off. Invisibility reset.") - mob.add_to_all_human_data_huds() - //TODO: Make some kind of indication for the badmin that they are currently invisible - else - mob.invisibility = INVISIBILITY_OBSERVER - to_chat(mob, "Invisimin on. You are now as invisible as a ghost.") - mob.remove_from_all_data_huds() - -/client/proc/player_panel() - set name = "Player Panel" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - holder.player_panel_old() - feedback_add_details("admin_verb","PP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/player_panel_new() - set name = "Player Panel New" - set category = "Admin" - - if(!check_rights(R_ADMIN|R_MOD)) - return - - holder.player_panel_new() - 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/check_antagonists() - set name = "Check Antagonists" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - holder.check_antagonists() - log_admin("[key_name(usr)] checked antagonists") - feedback_add_details("admin_verb","CHA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/jobbans() - set name = "Display Job bans" - set category = "Admin" - - if(!check_rights(R_ADMIN|R_MOD)) - return - - if(config.ban_legacy_system) - holder.Jobbans() - else - holder.DB_ban_panel() - feedback_add_details("admin_verb","VJB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/unban_panel() - set name = "Unban Panel" - set category = "Admin" - - if(!check_rights(R_BAN)) - return - - if(config.ban_legacy_system) - holder.unbanpanel() - else - holder.DB_ban_panel() - feedback_add_details("admin_verb","UBP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/game_panel() - set name = "Game Panel" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - holder.Game() - feedback_add_details("admin_verb","GP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/secrets() - set name = "Secrets" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - holder.Secrets() - feedback_add_details("admin_verb","S") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/findStealthKey(txt) - if(txt) - for(var/P in GLOB.stealthminID) - if(GLOB.stealthminID[P] == txt) - return P - txt = GLOB.stealthminID[ckey] - return txt - -/client/proc/createStealthKey() - var/num = (rand(0,1000)) - var/i = 0 - while(i == 0) - i = 1 - for(var/P in GLOB.stealthminID) - if(num == GLOB.stealthminID[P]) - num++ - i = 0 - GLOB.stealthminID["[ckey]"] = "@[num2text(num)]" - -/client/proc/stealth() - set category = "Admin" - set name = "Stealth Mode" - - if(!check_rights(R_ADMIN)) - return - - if(holder) - holder.big_brother = 0 - if(holder.fakekey) - holder.fakekey = null - else - var/new_key = ckeyEx(clean_input("Enter your desired display name.", "Fake Key", key)) - if(!new_key) return - if(length(new_key) >= 26) - new_key = copytext(new_key, 1, 26) - holder.fakekey = new_key - createStealthKey() - log_admin("[key_name(usr)] has turned stealth mode [holder.fakekey ? "ON" : "OFF"]") - message_admins("[key_name_admin(usr)] has turned stealth mode [holder.fakekey ? "ON" : "OFF"]", 1) - feedback_add_details("admin_verb","SM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/big_brother() - set category = "Admin" - set name = "Big Brother Mode" - - if(!check_rights(R_PERMISSIONS)) - return - - if(holder) - if(holder.fakekey) - holder.fakekey = null - holder.big_brother = 0 - else - var/new_key = ckeyEx(clean_input("Enter your desired display name. Unlike normal stealth mode, this will not appear in Who at all, except for other heads.", "Fake Key", key)) - if(!new_key) - return - if(length(new_key) >= 26) - new_key = copytext(new_key, 1, 26) - holder.fakekey = new_key - holder.big_brother = 1 - createStealthKey() - log_admin("[key_name(usr)] has turned BB mode [holder.fakekey ? "ON" : "OFF"]") - feedback_add_details("admin_verb","BBSM") - -#define MAX_WARNS 3 -#define AUTOBANTIME 10 - -/client/proc/warn(warned_ckey) - if(!check_rights(R_ADMIN)) - return - - if(!warned_ckey || !istext(warned_ckey)) return - if(warned_ckey in admin_datums) - to_chat(usr, "Error: warn(): You can't warn admins.") - return - - var/datum/preferences/D - var/client/C = GLOB.directory[warned_ckey] - if(C) D = C.prefs - else D = preferences_datums[warned_ckey] - - if(!D) - to_chat(src, "Error: warn(): No such ckey found.") - return - - if(++D.warns >= MAX_WARNS) //uh ohhhh...you'reee iiiiin trouuuubble O:) - ban_unban_log_save("[ckey] warned [warned_ckey], resulting in a [AUTOBANTIME] minute autoban.") - if(C) - message_admins("[key_name_admin(src)] has warned [key_name_admin(C)] resulting in a [AUTOBANTIME] minute ban") - log_admin("[key_name(src)] has warned [key_name(C)] resulting in a [AUTOBANTIME] minute ban") - to_chat(C, "You have been autobanned due to a warning by [ckey].
        This is a temporary ban, it will be removed in [AUTOBANTIME] minutes.") - del(C) - else - message_admins("[key_name_admin(src)] has warned [warned_ckey] resulting in a [AUTOBANTIME] minute ban") - log_admin("[key_name(src)] has warned [warned_ckey] resulting in a [AUTOBANTIME] minute ban") - AddBan(warned_ckey, D.last_id, "Autobanning due to too many formal warnings", ckey, 1, AUTOBANTIME) - feedback_inc("ban_warn",1) - else - if(C) - to_chat(C, "You have been formally warned by an administrator.
        Further warnings will result in an autoban.
        ") - message_admins("[key_name_admin(src)] has warned [key_name_admin(C)]. They have [MAX_WARNS-D.warns] strikes remaining.") - log_admin("[key_name(src)] has warned [key_name(C)]. They have [MAX_WARNS-D.warns] strikes remaining.") - else - message_admins("[key_name_admin(src)] has warned [warned_ckey] (DC). They have [MAX_WARNS-D.warns] strikes remaining.") - log_admin("[key_name(src)] has warned [warned_ckey] (DC). They have [MAX_WARNS-D.warns] strikes remaining.") - - feedback_add_details("admin_verb","WARN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -#undef MAX_WARNS -#undef AUTOBANTIME - -/client/proc/drop_bomb() // Some admin dickery that can probably be done better -- TLE - set category = "Event" - set name = "Drop Bomb" - set desc = "Cause an explosion of varying strength at your location." - - if(!check_rights(R_EVENT)) - return - - var/turf/epicenter = mob.loc - var/list/choices = list("Small Bomb", "Medium Bomb", "Big Bomb", "Custom Bomb") - var/choice = input("What size explosion would you like to produce?") as null|anything in choices - switch(choice) - if(null) - return 0 - if("Small Bomb") - explosion(epicenter, 1, 2, 3, 3) - if("Medium Bomb") - explosion(epicenter, 2, 3, 4, 4) - if("Big Bomb") - explosion(epicenter, 3, 5, 7, 5) - if("Custom Bomb") - var/devastation_range = input("Devastation range (in tiles):") as null|num - if(devastation_range == null) - return - var/heavy_impact_range = input("Heavy impact range (in tiles):") as null|num - if(heavy_impact_range == null) - return - var/light_impact_range = input("Light impact range (in tiles):") as null|num - if(light_impact_range == null) - return - var/flash_range = input("Flash range (in tiles):") as null|num - if(flash_range == null) - return - explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, 1, 1) - log_admin("[key_name(usr)] created an admin explosion at [epicenter.loc]") - message_admins("[key_name_admin(usr)] created an admin explosion at [epicenter.loc]") - feedback_add_details("admin_verb","DB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/give_spell(mob/T as mob in GLOB.mob_list) // -- Urist - set category = "Event" - set name = "Give Spell" - set desc = "Gives a spell to a mob." - - if(!check_rights(R_EVENT)) - return - - var/list/spell_list = list() - var/type_length = length("/obj/effect/proc_holder/spell") + 2 - for(var/A in spells) - spell_list[copytext("[A]", type_length)] = A - var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spell_list - if(!S) - return - S = spell_list[S] - if(T.mind) - T.mind.AddSpell(new S) - else - T.AddSpell(new S) - - feedback_add_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].") - message_admins("[key_name_admin(usr)] gave [key_name(T)] the spell [S].", 1) - -/client/proc/give_disease(mob/T in GLOB.mob_list) - set category = "Event" - set name = "Give Disease" - set desc = "Gives a Disease to a mob." - var/datum/disease/D = input("Choose the disease to give to that guy", "ACHOO") as null|anything in diseases - if(!D) return - T.ForceContractDisease(new D) - feedback_add_details("admin_verb","GD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] gave [key_name(T)] the disease [D].") - message_admins("[key_name_admin(usr)] gave [key_name(T)] the disease [D].") - -/client/proc/make_sound(var/obj/O in view()) // -- TLE - set category = "Event" - set name = "Make Sound" - set desc = "Display a message to everyone who can hear the target" - - if(!check_rights(R_EVENT)) - return - - if(O) - var/message = clean_input("What do you want the message to be?", "Make Sound") - if(!message) - return - for(var/mob/V in hearers(O)) - V.show_message(admin_pencode_to_html(message), 2) - log_admin("[key_name(usr)] made [O] at [O.x], [O.y], [O.z] make a sound") - message_admins("[key_name_admin(usr)] made [O] at [O.x], [O.y], [O.z] make a sound") - feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/togglebuildmodeself() - set name = "Toggle Build Mode Self" - set category = "Event" - - if(!check_rights(R_EVENT)) - return - - if(src.mob) - togglebuildmode(src.mob) - feedback_add_details("admin_verb","TBMS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/object_talk(var/msg as text) // -- TLE - set category = "Event" - set name = "oSay" - set desc = "Display a message to everyone who can hear the target" - - if(!check_rights(R_EVENT)) - return - - if(mob.control_object) - if(!msg) - return - for(var/mob/V in hearers(mob.control_object)) - V.show_message("[mob.control_object.name] says: \"" + msg + "\"", 2) - log_admin("[key_name(usr)] used oSay on [mob.control_object]: [msg]") - message_admins("[key_name_admin(usr)] used oSay on [mob.control_object]: [msg]") - - feedback_add_details("admin_verb","OT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/deadmin_self() - set name = "De-admin self" - set category = "Admin" - - if(!check_rights(R_ADMIN|R_MOD|R_MENTOR)) - return - - log_admin("[key_name(usr)] deadmined themself.") - message_admins("[key_name_admin(usr)] deadmined themself.") - deadmin() - verbs += /client/proc/readmin - GLOB.deadmins += ckey - to_chat(src, "You are now a normal player.") - feedback_add_details("admin_verb","DAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/readmin() - set name = "Re-admin self" - set category = "Admin" - set desc = "Regain your admin powers." - - var/datum/admins/D = admin_datums[ckey] - var/rank = null - if(config.admin_legacy_system) - //load text from file - var/list/Lines = file2list("config/admins.txt") - for(var/line in Lines) - var/list/splitline = splittext(line, " - ") - if(lowertext(splitline[1]) == ckey) - if(splitline.len >= 2) - rank = ckeyEx(splitline[2]) - break - continue - else - if(!dbcon.IsConnected()) - message_admins("Warning, MySQL database is not connected.") - to_chat(src, "Warning, MYSQL database is not connected.") - return - var/sql_ckey = sanitizeSQL(ckey) - var/DBQuery/query = dbcon.NewQuery("SELECT rank FROM [format_table_name("admin")] WHERE ckey = '[sql_ckey]'") - query.Execute() - while(query.NextRow()) - rank = ckeyEx(query.item[1]) - if(!D) - if(config.admin_legacy_system) - if(admin_ranks[rank] == null) - error("Error while re-adminning [src], admin rank ([rank]) does not exist.") - to_chat(src, "Error while re-adminning, admin rank ([rank]) does not exist.") - return - - D = new(rank, admin_ranks[rank], ckey) - else - var/sql_ckey = sanitizeSQL(ckey) - var/DBQuery/query = dbcon.NewQuery("SELECT ckey, rank, flags FROM [format_table_name("admin")] WHERE ckey = '[sql_ckey]'") - query.Execute() - while(query.NextRow()) - var/admin_ckey = query.item[1] - var/admin_rank = query.item[2] - var/flags = query.item[3] - if(!admin_ckey) - to_chat(src, "Error while re-adminning, ckey [admin_ckey] was not found in the admin database.") - return - if(admin_rank == "Removed") //This person was de-adminned. They are only in the admin list for archive purposes. - to_chat(src, "Error while re-adminning, ckey [admin_ckey] is not an admin.") - return - - if(istext(flags)) - flags = text2num(flags) - D = new(admin_rank, flags, ckey) - - var/client/C = GLOB.directory[ckey] - D.associate(C) - message_admins("[key_name_admin(usr)] re-adminned themselves.") - log_admin("[key_name(usr)] re-adminned themselves.") - GLOB.deadmins -= ckey - feedback_add_details("admin_verb","RAS") - return - else - to_chat(src, "You are already an admin.") - verbs -= /client/proc/readmin - GLOB.deadmins -= ckey - return - -/client/proc/toggle_log_hrefs() - set name = "Toggle href logging" - set category = "Server" - - if(!check_rights(R_SERVER)) - return - - if(config) - if(config.log_hrefs) - config.log_hrefs = 0 - to_chat(src, "Stopped logging hrefs") - else - config.log_hrefs = 1 - to_chat(src, "Started logging hrefs") - -/client/proc/check_ai_laws() - set name = "Check AI Laws" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - holder.output_ai_laws() - -/client/proc/manage_silicon_laws() - set name = "Manage Silicon Laws" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - var/mob/living/silicon/S = input("Select silicon.", "Manage Silicon Laws") as null|anything in GLOB.silicon_mob_list - if(!S) return - - var/datum/nano_module/law_manager/L = new(S) - L.ui_interact(usr, state = admin_state) - log_and_message_admins("has opened [S]'s law manager.") - feedback_add_details("admin_verb","MSL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/change_human_appearance_admin(mob/living/carbon/human/H in GLOB.mob_list) - set name = "C.M.A. - Admin" - set desc = "Allows you to change the mob appearance" - set category = null - - if(!check_rights(R_ADMIN)) - return - - if(!istype(H)) - if(istype(H, /mob/living/carbon/brain)) - var/mob/living/carbon/brain/B = H - if(istype(B.container, /obj/item/mmi/robotic_brain/positronic)) - var/obj/item/mmi/robotic_brain/positronic/C = B.container - var/obj/item/organ/internal/brain/mmi_holder/posibrain/P = C.loc - if(ishuman(P.owner)) - H = P.owner - else - return - else - return - - if(holder) - admin_log_and_message_admins("is altering the appearance of [H].") - H.change_appearance(APPEARANCE_ALL, usr, usr, check_species_whitelist = 0) - feedback_add_details("admin_verb","CHAA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/change_human_appearance_self(mob/living/carbon/human/H in GLOB.mob_list) - set name = "C.M.A. - Self" - set desc = "Allows the mob to change its appearance" - set category = null - - if(!check_rights(R_ADMIN)) - return - - if(!istype(H)) - if(istype(H, /mob/living/carbon/brain)) - var/mob/living/carbon/brain/B = H - if(istype(B.container, /obj/item/mmi/robotic_brain/positronic)) - var/obj/item/mmi/robotic_brain/positronic/C = B.container - var/obj/item/organ/internal/brain/mmi_holder/posibrain/P = C.loc - if(ishuman(P.owner)) - H = P.owner - else - return - else - return - - if(!H.client) - to_chat(usr, "Only mobs with clients can alter their own appearance.") - return - - switch(alert("Do you wish for [H] to be allowed to select non-whitelisted races?","Alter Mob Appearance","Yes","No","Cancel")) - if("Yes") - admin_log_and_message_admins("has allowed [H] to change [H.p_their()] appearance, without whitelisting of races.") - H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 0) - if("No") - admin_log_and_message_admins("has allowed [H] to change [H.p_their()] appearance, with whitelisting of races.") - H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 1) - feedback_add_details("admin_verb","CMAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/free_slot() - set name = "Free Job Slot" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - var/list/jobs = list() - for(var/datum/job/J in SSjobs.occupations) - if(J.current_positions >= J.total_positions && J.total_positions != -1) - jobs += J.title - if(!jobs.len) - to_chat(usr, "There are no fully staffed jobs.") - return - var/job = input("Please select job slot to free", "Free Job Slot") as null|anything in jobs - if(job) - SSjobs.FreeRole(job) - log_admin("[key_name(usr)] has freed a job slot for [job].") - message_admins("[key_name_admin(usr)] has freed a job slot for [job].") - -/client/proc/toggleattacklogs() - set name = "Toggle Attack Log Messages" - set category = "Preferences" - - if(!check_rights(R_ADMIN)) - return - - if(prefs.atklog == ATKLOG_ALL) - prefs.atklog = ATKLOG_ALMOSTALL - to_chat(usr, "Your attack logs preference is now: show ALMOST ALL attack logs (notable exceptions: NPCs attacking other NPCs, vampire bites, equipping/stripping, people pushing each other over)") - else if(prefs.atklog == ATKLOG_ALMOSTALL) - prefs.atklog = ATKLOG_MOST - to_chat(usr, "Your attack logs preference is now: show MOST attack logs (like ALMOST ALL, except that it also hides player v. NPC combat, and certain areas like lavaland syndie base and thunderdome)") - else if(prefs.atklog == ATKLOG_MOST) - prefs.atklog = ATKLOG_FEW - to_chat(usr, "Your attack logs preference is now: show FEW attack logs (only the most important stuff: attacks on SSDs, use of explosives, messing with the engine, gibbing, AI wiping, forcefeeding, acid sprays, and organ extraction)") - else if(prefs.atklog == ATKLOG_FEW) - prefs.atklog = ATKLOG_NONE - to_chat(usr, "Your attack logs preference is now: show NO attack logs") - else if(prefs.atklog == ATKLOG_NONE) - prefs.atklog = ATKLOG_ALL - to_chat(usr, "Your attack logs preference is now: show ALL attack logs") - else - prefs.atklog = ATKLOG_ALL - to_chat(usr, "Your attack logs preference is now: show ALL attack logs (your preference was set to an invalid value, it has been reset)") - - prefs.save_preferences(src) - - -/client/proc/toggleadminlogs() - set name = "Toggle Admin Log Messages" - set category = "Preferences" - - if(!check_rights(R_ADMIN)) - return - - prefs.toggles ^= CHAT_NO_ADMINLOGS - prefs.save_preferences(src) - if(prefs.toggles & CHAT_NO_ADMINLOGS) - to_chat(usr, "You now won't get admin log messages.") - else - to_chat(usr, "You now will get admin log messages.") - -/client/proc/toggleMentorTicketLogs() - set name = "Toggle Mentor Ticket Messages" - set category = "Preferences" - - if(!check_rights(R_MENTOR|R_ADMIN)) - return - - prefs.toggles ^= CHAT_NO_MENTORTICKETLOGS - prefs.save_preferences(src) - if(prefs.toggles & CHAT_NO_MENTORTICKETLOGS) - to_chat(usr, "You now won't get mentor ticket messages.") - else - to_chat(usr, "You now will get mentor ticket messages.") - -/client/proc/toggleticketlogs() - set name = "Toggle Admin Ticket Messgaes" - set category = "Preferences" - - if(!check_rights(R_ADMIN)) - return - - prefs.toggles ^= CHAT_NO_TICKETLOGS - prefs.save_preferences(src) - if(prefs.toggles & CHAT_NO_TICKETLOGS) - to_chat(usr, "You now won't get admin ticket messages.") - else - to_chat(usr, "You now will get admin ticket messages.") - -/client/proc/toggledrones() - set name = "Toggle Maintenance Drones" - set category = "Server" - - if(!check_rights(R_SERVER)) - return - - config.allow_drone_spawn = !(config.allow_drone_spawn) - log_admin("[key_name(usr)] has [config.allow_drone_spawn ? "enabled" : "disabled"] maintenance drones.") - message_admins("[key_name_admin(usr)] has [config.allow_drone_spawn ? "enabled" : "disabled"] maintenance drones.") - -/client/proc/toggledebuglogs() - set name = "Toggle Debug Log Messages" - set category = "Preferences" - - if(!check_rights(R_DEBUG)) - return - - prefs.toggles ^= CHAT_DEBUGLOGS - prefs.save_preferences(src) - if(prefs.toggles & CHAT_DEBUGLOGS) - to_chat(usr, "You now will get debug log messages") - else - to_chat(usr, "You now won't get debug log messages") - -/client/proc/man_up(mob/T as mob in GLOB.mob_list) - set category = "Admin" - set name = "Man Up" - set desc = "Tells mob to man up and deal with it." - - if(!check_rights(R_ADMIN)) - return - - to_chat(T, "Man up and deal with it.") - to_chat(T, "Move on.") - T << 'sound/voice/manup1.ogg' - - log_admin("[key_name(usr)] told [key_name(T)] to man up and deal with it.") - message_admins("[key_name_admin(usr)] told [key_name(T)] to man up and deal with it.") - -/client/proc/global_man_up() - set category = "Admin" - set name = "Man Up Global" - set desc = "Tells everyone to man up and deal with it." - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert("Are you sure you want to send the global message?", "Confirm Man Up Global", "Yes", "No") - - if(confirm == "Yes") - for(var/mob/T as mob in GLOB.mob_list) - to_chat(T, "
        Man up.
        Deal with it.

        Move on.

        ") - T << 'sound/voice/manup1.ogg' - - log_admin("[key_name(usr)] told everyone to man up and deal with it.") - message_admins("[key_name_admin(usr)] told everyone to man up and deal with it.") - -/client/proc/toggle_advanced_interaction() - set name = "Toggle Advanced Admin Interaction" - set category = "Admin" - set desc = "Allows you to interact with atoms such as buttons and doors, on top of regular machinery interaction." - - if(!check_rights(R_ADMIN)) - return - - advanced_admin_interaction = !advanced_admin_interaction - - log_admin("[key_name(usr)] has [advanced_admin_interaction ? "activated" : "deactivated"] their advanced admin interaction.") - message_admins("[key_name_admin(usr)] has [advanced_admin_interaction ? "activated" : "deactivated"] their advanced admin interaction.") +//admin verb groups - They can overlap if you so wish. Only one of each verb will exist in the verbs list regardless +var/list/admin_verbs_default = list( + /client/proc/deadmin_self, /*destroys our own admin datum so we can play as a regular player*/ + /client/proc/hide_verbs, /*hides all our adminverbs*/ + /client/proc/toggleadminhelpsound, + /client/proc/togglementorhelpsound, + /client/proc/cmd_mentor_check_new_players, + /client/proc/cmd_mentor_check_player_exp /* shows players by playtime */ + ) +var/list/admin_verbs_admin = list( + /client/proc/check_antagonists, /*shows all antags*/ + /datum/admins/proc/show_player_panel, + /client/proc/player_panel, /*shows an interface for all players, with links to various panels (old style)*/ + /client/proc/player_panel_new, /*shows an interface for all players, with links to various panels*/ + /client/proc/invisimin, /*allows our mob to go invisible/visible*/ + /datum/admins/proc/toggleenter, /*toggles whether people can join the current game*/ + /datum/admins/proc/toggleguests, /*toggles whether guests can join the current game*/ + /datum/admins/proc/announce, /*priority announce something to all clients.*/ + /client/proc/colorooc, /*allows us to set a custom colour for everything we say in ooc*/ + /client/proc/resetcolorooc, /*allows us to set a reset our ooc color*/ + /client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/ + /client/proc/toggle_view_range, /*changes how far we can see*/ + /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ + /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ + /client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/ + /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/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*/ + /client/proc/Getkey, /*teleports a mob with a certain ckey to our location*/ + /client/proc/Jump, + /client/proc/jumptokey, /*allows us to jump to the location of a mob with a certain ckey*/ + /client/proc/jumptomob, /*allows us to jump to a specific mob*/ + /client/proc/jumptoturf, /*allows us to jump to a specific turf*/ + /client/proc/admin_call_shuttle, /*allows us to call the emergency shuttle*/ + /client/proc/admin_cancel_shuttle, /*allows us to cancel the emergency shuttle, sending it back to centcomm*/ + /client/proc/check_ai_laws, /*shows AI and borg laws*/ + /client/proc/manage_silicon_laws, /* Allows viewing and editing silicon laws. */ + /client/proc/admin_memo, /*admin memo system. show/delete/write. +SERVER needed to delete admin memos of others*/ + /client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/ + /client/proc/toggleprayers, /*toggles prayers on/off*/ + /client/proc/toggle_hear_radio, /*toggles whether we hear the radio*/ + /client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/ + /datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/ + /datum/admins/proc/togglelooc, /*toggles looc on/off for everyone*/ + /datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/ + /datum/admins/proc/toggledsay, /*toggles dsay on/off for everyone*/ + /datum/admins/proc/toggleemoji, /*toggles using emoji in ooc for everyone*/ + /client/proc/game_panel, /*game panel, allows to change game-mode etc*/ + /client/proc/cmd_admin_say, /*admin-only ooc chat*/ + /datum/admins/proc/PlayerNotes, + /client/proc/cmd_mentor_say, + /datum/admins/proc/show_player_notes, + /datum/admins/proc/vpn_whitelist, + /client/proc/free_slot, /*frees slot for chosen job*/ + /client/proc/toggleattacklogs, + /client/proc/toggleadminlogs, + /client/proc/toggledebuglogs, + /client/proc/update_mob_sprite, + /client/proc/toggledrones, + /client/proc/man_up, + /client/proc/global_man_up, + /client/proc/delbook, + /client/proc/view_flagged_books, + /client/proc/empty_ai_core_toggle_latejoin, + /client/proc/aooc, + /client/proc/freeze, + /client/proc/freezemecha, + /client/proc/alt_check, + /client/proc/secrets, + /client/proc/change_human_appearance_admin, /* Allows an admin to change the basic appearance of human-based mobs */ + /client/proc/change_human_appearance_self, /* Allows the human-based mob itself to change its basic appearance */ + /client/proc/debug_variables, + /client/proc/reset_all_tcs, /*resets all telecomms scripts*/ + /client/proc/toggle_mentor_chat, + /client/proc/toggle_advanced_interaction, /*toggle admin ability to interact with not only machines, but also atoms such as buttons and doors*/ + /client/proc/list_ssds_afks, + /client/proc/cmd_admin_headset_message, + /client/proc/spawn_floor_cluwne +) +var/list/admin_verbs_ban = list( + /client/proc/unban_panel, + /client/proc/jobbans, + /client/proc/stickybanpanel + ) +var/list/admin_verbs_sounds = list( + /client/proc/play_local_sound, + /client/proc/play_sound, + /client/proc/play_server_sound, + /client/proc/play_intercomm_sound, + /client/proc/stop_global_admin_sounds + ) +var/list/admin_verbs_event = list( + /client/proc/object_talk, + /client/proc/cmd_admin_dress, + /client/proc/cmd_admin_gib_self, + /client/proc/drop_bomb, + /client/proc/cinematic, + /client/proc/one_click_antag, + /datum/admins/proc/toggle_aliens, + /client/proc/cmd_admin_add_freeform_ai_law, + /client/proc/cmd_admin_add_random_ai_law, + /client/proc/make_sound, + /client/proc/toggle_random_events, + /client/proc/toggle_random_events, + /client/proc/toggle_ert_calling, + /client/proc/show_tip, + /client/proc/cmd_admin_change_custom_event, + /datum/admins/proc/access_news_network, /*allows access of newscasters*/ + /client/proc/cmd_admin_direct_narrate, /*send text directly to a player with no padding. Useful for narratives and fluff-text*/ + /client/proc/cmd_admin_world_narrate, /*sends text to all players with no padding*/ + /client/proc/response_team, // Response Teams admin verb + /client/proc/cmd_admin_create_centcom_report, + /client/proc/fax_panel, + /client/proc/event_manager_panel, + /client/proc/modify_goals, + /client/proc/outfit_manager + ) + +var/list/admin_verbs_spawn = list( + /datum/admins/proc/spawn_atom, /*allows us to spawn instances*/ + /client/proc/respawn_character, + /client/proc/admin_deserialize + ) +var/list/admin_verbs_server = list( + /client/proc/ToRban, + /client/proc/Set_Holiday, + /datum/admins/proc/startnow, + /datum/admins/proc/restart, + /datum/admins/proc/delay, + /datum/admins/proc/toggleaban, + /client/proc/toggle_log_hrefs, + /client/proc/everyone_random, + /datum/admins/proc/toggleAI, + /client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/ + /client/proc/cmd_debug_del_all, + /client/proc/cmd_debug_del_sing, + /datum/admins/proc/toggle_aliens, + /client/proc/delbook, + /client/proc/view_flagged_books, + /client/proc/toggle_antagHUD_use, + /client/proc/toggle_antagHUD_restrictions, + /client/proc/set_ooc, + /client/proc/reset_ooc + ) +var/list/admin_verbs_debug = list( + /client/proc/cmd_admin_list_open_jobs, + /client/proc/Debug2, + /client/proc/cmd_debug_make_powernets, + /client/proc/debug_controller, + /client/proc/cmd_debug_mob_lists, + /client/proc/cmd_admin_delete, + /client/proc/cmd_debug_del_all, + /client/proc/cmd_debug_del_sing, + /client/proc/reload_admins, + /client/proc/restart_controller, + /client/proc/enable_debug_verbs, + /client/proc/toggledebuglogs, + /client/proc/cmd_display_del_log, + /client/proc/cmd_display_del_log_simple, + /client/proc/debugNatureMapGenerator, + /client/proc/check_bomb_impacts, + /client/proc/test_movable_UI, + /client/proc/test_snap_UI, + /client/proc/cinematic, + /proc/machine_upgrade, + /client/proc/map_template_load, + /client/proc/map_template_upload, + /client/proc/view_runtimes, + /client/proc/admin_serialize, + /client/proc/jump_to_ruin, + /client/proc/toggle_medal_disable, + ) +var/list/admin_verbs_possess = list( + /proc/possess, + /proc/release + ) +var/list/admin_verbs_permissions = list( + /client/proc/edit_admin_permissions, + /client/proc/create_poll, + /client/proc/big_brother + ) +var/list/admin_verbs_rejuv = list( + /client/proc/respawn_character, + /client/proc/cmd_admin_rejuvenate + ) +var/list/admin_verbs_mod = list( + /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ + /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ + /client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/ + /datum/admins/proc/PlayerNotes, + /client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/ + /client/proc/cmd_mentor_say, + /datum/admins/proc/show_player_notes, + /client/proc/player_panel_new, + /client/proc/dsay, + /datum/admins/proc/show_player_panel, + /client/proc/jobbans, + /client/proc/debug_variables /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/ +) +var/list/admin_verbs_mentor = list( + /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ + /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ + /client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/ + /client/proc/openMentorTicketUI, + /client/proc/toggleMentorTicketLogs, + /client/proc/cmd_mentor_say /* mentor say*/ + // cmd_mentor_say is added/removed by the toggle_mentor_chat verb +) +var/list/admin_verbs_proccall = list( + /client/proc/callproc, + /client/proc/callproc_datum, + /client/proc/SDQL2_query +) +var/list/admin_verbs_ticket = list( + /client/proc/openAdminTicketUI, + /client/proc/toggleticketlogs, + /client/proc/openMentorTicketUI, + /client/proc/toggleMentorTicketLogs, + /client/proc/resolveAllAdminTickets, + /client/proc/resolveAllMentorTickets +) + +/client/proc/on_holder_add() + if(chatOutput && chatOutput.loaded) + chatOutput.loadAdmin() + +/client/proc/add_admin_verbs() + if(holder) + verbs += admin_verbs_default + if(holder.rights & R_BUILDMODE) + verbs += /client/proc/togglebuildmodeself + if(holder.rights & R_ADMIN) + verbs += admin_verbs_admin + verbs += admin_verbs_ticket + spawn(1) + control_freak = 0 + if(holder.rights & R_BAN) + verbs += admin_verbs_ban + if(holder.rights & R_EVENT) + verbs += admin_verbs_event + if(holder.rights & R_SERVER) + verbs += admin_verbs_server + if(holder.rights & R_DEBUG) + verbs += admin_verbs_debug + if(holder.rights & R_POSSESS) + verbs += admin_verbs_possess + if(holder.rights & R_PERMISSIONS) + verbs += admin_verbs_permissions + if(holder.rights & R_STEALTH) + verbs += /client/proc/stealth + if(holder.rights & R_REJUVINATE) + verbs += admin_verbs_rejuv + if(holder.rights & R_SOUNDS) + verbs += admin_verbs_sounds + if(holder.rights & R_SPAWN) + verbs += admin_verbs_spawn + if(holder.rights & R_MOD) + verbs += admin_verbs_mod + if(holder.rights & R_MENTOR) + verbs += admin_verbs_mentor + if(holder.rights & R_PROCCALL) + verbs += admin_verbs_proccall + +/client/proc/remove_admin_verbs() + verbs.Remove( + admin_verbs_default, + /client/proc/togglebuildmodeself, + admin_verbs_admin, + admin_verbs_ban, + admin_verbs_event, + admin_verbs_server, + admin_verbs_debug, + admin_verbs_possess, + admin_verbs_permissions, + /client/proc/stealth, + admin_verbs_rejuv, + admin_verbs_sounds, + admin_verbs_spawn, + admin_verbs_mod, + admin_verbs_mentor, + admin_verbs_proccall, + admin_verbs_show_debug_verbs, + /client/proc/readmin, + admin_verbs_ticket + ) + +/client/proc/hide_verbs() + set name = "Adminverbs - Hide All" + set category = "Admin" + + if(!holder) + return + + remove_admin_verbs() + verbs += /client/proc/show_verbs + + to_chat(src, "Almost all of your adminverbs have been hidden.") + feedback_add_details("admin_verb","TAVVH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/show_verbs() + set name = "Adminverbs - Show" + set category = "Admin" + + if(!holder) + return + + verbs -= /client/proc/show_verbs + add_admin_verbs() + + to_chat(src, "All of your adminverbs are now visible.") + feedback_add_details("admin_verb","TAVVS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/admin_ghost() + set category = "Admin" + set name = "Aghost" + + if(!check_rights(R_ADMIN|R_MOD)) + return + + if(istype(mob,/mob/dead/observer)) + //re-enter + var/mob/dead/observer/ghost = mob + ghost.can_reenter_corpse = 1 //just in-case. + ghost.reenter_corpse() + log_admin("[key_name(usr)] re-entered their body") + feedback_add_details("admin_verb","P") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + else if(istype(mob,/mob/new_player)) + to_chat(src, "Error: Aghost: Can't admin-ghost whilst in the lobby. Join or observe first.") + else + //ghostize + var/mob/body = mob + body.ghostize(1) + if(body && !body.key) + body.key = "@[key]" //Haaaaaaaack. But the people have spoken. If it breaks; blame adminbus + log_admin("[key_name(usr)] has admin-ghosted") + feedback_add_details("admin_verb","O") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/invisimin() + set name = "Invisimin" + set category = "Admin" + set desc = "Toggles ghost-like invisibility (Don't abuse this)" + + if(!check_rights(R_ADMIN)) + return + + if(mob) + if(mob.invisibility == INVISIBILITY_OBSERVER) + mob.invisibility = initial(mob.invisibility) + to_chat(mob, "Invisimin off. Invisibility reset.") + mob.add_to_all_human_data_huds() + //TODO: Make some kind of indication for the badmin that they are currently invisible + else + mob.invisibility = INVISIBILITY_OBSERVER + to_chat(mob, "Invisimin on. You are now as invisible as a ghost.") + mob.remove_from_all_data_huds() + +/client/proc/player_panel() + set name = "Player Panel" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + holder.player_panel_old() + feedback_add_details("admin_verb","PP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/player_panel_new() + set name = "Player Panel New" + set category = "Admin" + + if(!check_rights(R_ADMIN|R_MOD)) + return + + holder.player_panel_new() + 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/check_antagonists() + set name = "Check Antagonists" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + holder.check_antagonists() + log_admin("[key_name(usr)] checked antagonists") + feedback_add_details("admin_verb","CHA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/jobbans() + set name = "Display Job bans" + set category = "Admin" + + if(!check_rights(R_ADMIN|R_MOD)) + return + + if(config.ban_legacy_system) + holder.Jobbans() + else + holder.DB_ban_panel() + feedback_add_details("admin_verb","VJB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/unban_panel() + set name = "Unban Panel" + set category = "Admin" + + if(!check_rights(R_BAN)) + return + + if(config.ban_legacy_system) + holder.unbanpanel() + else + holder.DB_ban_panel() + feedback_add_details("admin_verb","UBP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/game_panel() + set name = "Game Panel" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + holder.Game() + feedback_add_details("admin_verb","GP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/secrets() + set name = "Secrets" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + holder.Secrets() + feedback_add_details("admin_verb","S") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/findStealthKey(txt) + if(txt) + for(var/P in GLOB.stealthminID) + if(GLOB.stealthminID[P] == txt) + return P + txt = GLOB.stealthminID[ckey] + return txt + +/client/proc/createStealthKey() + var/num = (rand(0,1000)) + var/i = 0 + while(i == 0) + i = 1 + for(var/P in GLOB.stealthminID) + if(num == GLOB.stealthminID[P]) + num++ + i = 0 + GLOB.stealthminID["[ckey]"] = "@[num2text(num)]" + +/client/proc/stealth() + set category = "Admin" + set name = "Stealth Mode" + + if(!check_rights(R_ADMIN)) + return + + if(holder) + holder.big_brother = 0 + if(holder.fakekey) + holder.fakekey = null + else + var/new_key = ckeyEx(clean_input("Enter your desired display name.", "Fake Key", key)) + if(!new_key) return + if(length(new_key) >= 26) + new_key = copytext(new_key, 1, 26) + holder.fakekey = new_key + createStealthKey() + log_admin("[key_name(usr)] has turned stealth mode [holder.fakekey ? "ON" : "OFF"]") + message_admins("[key_name_admin(usr)] has turned stealth mode [holder.fakekey ? "ON" : "OFF"]", 1) + feedback_add_details("admin_verb","SM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/big_brother() + set category = "Admin" + set name = "Big Brother Mode" + + if(!check_rights(R_PERMISSIONS)) + return + + if(holder) + if(holder.fakekey) + holder.fakekey = null + holder.big_brother = 0 + else + var/new_key = ckeyEx(clean_input("Enter your desired display name. Unlike normal stealth mode, this will not appear in Who at all, except for other heads.", "Fake Key", key)) + if(!new_key) + return + if(length(new_key) >= 26) + new_key = copytext(new_key, 1, 26) + holder.fakekey = new_key + holder.big_brother = 1 + createStealthKey() + log_admin("[key_name(usr)] has turned BB mode [holder.fakekey ? "ON" : "OFF"]") + feedback_add_details("admin_verb","BBSM") + +#define MAX_WARNS 3 +#define AUTOBANTIME 10 + +/client/proc/warn(warned_ckey) + if(!check_rights(R_ADMIN)) + return + + if(!warned_ckey || !istext(warned_ckey)) return + if(warned_ckey in admin_datums) + to_chat(usr, "Error: warn(): You can't warn admins.") + return + + var/datum/preferences/D + var/client/C = GLOB.directory[warned_ckey] + if(C) D = C.prefs + else D = preferences_datums[warned_ckey] + + if(!D) + to_chat(src, "Error: warn(): No such ckey found.") + return + + if(++D.warns >= MAX_WARNS) //uh ohhhh...you'reee iiiiin trouuuubble O:) + ban_unban_log_save("[ckey] warned [warned_ckey], resulting in a [AUTOBANTIME] minute autoban.") + if(C) + message_admins("[key_name_admin(src)] has warned [key_name_admin(C)] resulting in a [AUTOBANTIME] minute ban") + log_admin("[key_name(src)] has warned [key_name(C)] resulting in a [AUTOBANTIME] minute ban") + to_chat(C, "You have been autobanned due to a warning by [ckey].
        This is a temporary ban, it will be removed in [AUTOBANTIME] minutes.") + del(C) + else + message_admins("[key_name_admin(src)] has warned [warned_ckey] resulting in a [AUTOBANTIME] minute ban") + log_admin("[key_name(src)] has warned [warned_ckey] resulting in a [AUTOBANTIME] minute ban") + AddBan(warned_ckey, D.last_id, "Autobanning due to too many formal warnings", ckey, 1, AUTOBANTIME) + feedback_inc("ban_warn",1) + else + if(C) + to_chat(C, "You have been formally warned by an administrator.
        Further warnings will result in an autoban.
        ") + message_admins("[key_name_admin(src)] has warned [key_name_admin(C)]. They have [MAX_WARNS-D.warns] strikes remaining.") + log_admin("[key_name(src)] has warned [key_name(C)]. They have [MAX_WARNS-D.warns] strikes remaining.") + else + message_admins("[key_name_admin(src)] has warned [warned_ckey] (DC). They have [MAX_WARNS-D.warns] strikes remaining.") + log_admin("[key_name(src)] has warned [warned_ckey] (DC). They have [MAX_WARNS-D.warns] strikes remaining.") + + feedback_add_details("admin_verb","WARN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +#undef MAX_WARNS +#undef AUTOBANTIME + +/client/proc/drop_bomb() // Some admin dickery that can probably be done better -- TLE + set category = "Event" + set name = "Drop Bomb" + set desc = "Cause an explosion of varying strength at your location." + + if(!check_rights(R_EVENT)) + return + + var/turf/epicenter = mob.loc + var/list/choices = list("Small Bomb", "Medium Bomb", "Big Bomb", "Custom Bomb") + var/choice = input("What size explosion would you like to produce?") as null|anything in choices + switch(choice) + if(null) + return 0 + if("Small Bomb") + explosion(epicenter, 1, 2, 3, 3) + if("Medium Bomb") + explosion(epicenter, 2, 3, 4, 4) + if("Big Bomb") + explosion(epicenter, 3, 5, 7, 5) + if("Custom Bomb") + var/devastation_range = input("Devastation range (in tiles):") as null|num + if(devastation_range == null) + return + var/heavy_impact_range = input("Heavy impact range (in tiles):") as null|num + if(heavy_impact_range == null) + return + var/light_impact_range = input("Light impact range (in tiles):") as null|num + if(light_impact_range == null) + return + var/flash_range = input("Flash range (in tiles):") as null|num + if(flash_range == null) + return + explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, 1, 1) + log_admin("[key_name(usr)] created an admin explosion at [epicenter.loc]") + message_admins("[key_name_admin(usr)] created an admin explosion at [epicenter.loc]") + feedback_add_details("admin_verb","DB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/give_spell(mob/T as mob in GLOB.mob_list) // -- Urist + set category = "Event" + set name = "Give Spell" + set desc = "Gives a spell to a mob." + + if(!check_rights(R_EVENT)) + return + + var/list/spell_list = list() + var/type_length = length("/obj/effect/proc_holder/spell") + 2 + for(var/A in spells) + spell_list[copytext("[A]", type_length)] = A + var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spell_list + if(!S) + return + S = spell_list[S] + if(T.mind) + T.mind.AddSpell(new S) + else + T.AddSpell(new S) + + feedback_add_details("admin_verb","GS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].") + message_admins("[key_name_admin(usr)] gave [key_name(T)] the spell [S].", 1) + +/client/proc/give_disease(mob/T in GLOB.mob_list) + set category = "Event" + set name = "Give Disease" + set desc = "Gives a Disease to a mob." + var/datum/disease/D = input("Choose the disease to give to that guy", "ACHOO") as null|anything in diseases + if(!D) return + T.ForceContractDisease(new D) + feedback_add_details("admin_verb","GD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] gave [key_name(T)] the disease [D].") + message_admins("[key_name_admin(usr)] gave [key_name(T)] the disease [D].") + +/client/proc/make_sound(var/obj/O in view()) // -- TLE + set category = "Event" + set name = "Make Sound" + set desc = "Display a message to everyone who can hear the target" + + if(!check_rights(R_EVENT)) + return + + if(O) + var/message = clean_input("What do you want the message to be?", "Make Sound") + if(!message) + return + for(var/mob/V in hearers(O)) + V.show_message(admin_pencode_to_html(message), 2) + log_admin("[key_name(usr)] made [O] at [O.x], [O.y], [O.z] make a sound") + message_admins("[key_name_admin(usr)] made [O] at [O.x], [O.y], [O.z] make a sound") + feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/togglebuildmodeself() + set name = "Toggle Build Mode Self" + set category = "Event" + + if(!check_rights(R_EVENT)) + return + + if(src.mob) + togglebuildmode(src.mob) + feedback_add_details("admin_verb","TBMS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/object_talk(var/msg as text) // -- TLE + set category = "Event" + set name = "oSay" + set desc = "Display a message to everyone who can hear the target" + + if(!check_rights(R_EVENT)) + return + + if(mob.control_object) + if(!msg) + return + for(var/mob/V in hearers(mob.control_object)) + V.show_message("[mob.control_object.name] says: \"" + msg + "\"", 2) + log_admin("[key_name(usr)] used oSay on [mob.control_object]: [msg]") + message_admins("[key_name_admin(usr)] used oSay on [mob.control_object]: [msg]") + + feedback_add_details("admin_verb","OT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/deadmin_self() + set name = "De-admin self" + set category = "Admin" + + if(!check_rights(R_ADMIN|R_MOD|R_MENTOR)) + return + + log_admin("[key_name(usr)] deadmined themself.") + message_admins("[key_name_admin(usr)] deadmined themself.") + deadmin() + verbs += /client/proc/readmin + GLOB.deadmins += ckey + to_chat(src, "You are now a normal player.") + feedback_add_details("admin_verb","DAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/readmin() + set name = "Re-admin self" + set category = "Admin" + set desc = "Regain your admin powers." + + var/datum/admins/D = admin_datums[ckey] + var/rank = null + if(config.admin_legacy_system) + //load text from file + var/list/Lines = file2list("config/admins.txt") + for(var/line in Lines) + var/list/splitline = splittext(line, " - ") + if(lowertext(splitline[1]) == ckey) + if(splitline.len >= 2) + rank = ckeyEx(splitline[2]) + break + continue + else + if(!dbcon.IsConnected()) + message_admins("Warning, MySQL database is not connected.") + to_chat(src, "Warning, MYSQL database is not connected.") + return + var/sql_ckey = sanitizeSQL(ckey) + var/DBQuery/query = dbcon.NewQuery("SELECT rank FROM [format_table_name("admin")] WHERE ckey = '[sql_ckey]'") + query.Execute() + while(query.NextRow()) + rank = ckeyEx(query.item[1]) + if(!D) + if(config.admin_legacy_system) + if(admin_ranks[rank] == null) + error("Error while re-adminning [src], admin rank ([rank]) does not exist.") + to_chat(src, "Error while re-adminning, admin rank ([rank]) does not exist.") + return + + D = new(rank, admin_ranks[rank], ckey) + else + var/sql_ckey = sanitizeSQL(ckey) + var/DBQuery/query = dbcon.NewQuery("SELECT ckey, rank, flags FROM [format_table_name("admin")] WHERE ckey = '[sql_ckey]'") + query.Execute() + while(query.NextRow()) + var/admin_ckey = query.item[1] + var/admin_rank = query.item[2] + var/flags = query.item[3] + if(!admin_ckey) + to_chat(src, "Error while re-adminning, ckey [admin_ckey] was not found in the admin database.") + return + if(admin_rank == "Removed") //This person was de-adminned. They are only in the admin list for archive purposes. + to_chat(src, "Error while re-adminning, ckey [admin_ckey] is not an admin.") + return + + if(istext(flags)) + flags = text2num(flags) + D = new(admin_rank, flags, ckey) + + var/client/C = GLOB.directory[ckey] + D.associate(C) + message_admins("[key_name_admin(usr)] re-adminned themselves.") + log_admin("[key_name(usr)] re-adminned themselves.") + GLOB.deadmins -= ckey + feedback_add_details("admin_verb","RAS") + return + else + to_chat(src, "You are already an admin.") + verbs -= /client/proc/readmin + GLOB.deadmins -= ckey + return + +/client/proc/toggle_log_hrefs() + set name = "Toggle href logging" + set category = "Server" + + if(!check_rights(R_SERVER)) + return + + if(config) + if(config.log_hrefs) + config.log_hrefs = 0 + to_chat(src, "Stopped logging hrefs") + else + config.log_hrefs = 1 + to_chat(src, "Started logging hrefs") + +/client/proc/check_ai_laws() + set name = "Check AI Laws" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + holder.output_ai_laws() + +/client/proc/manage_silicon_laws() + set name = "Manage Silicon Laws" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + var/mob/living/silicon/S = input("Select silicon.", "Manage Silicon Laws") as null|anything in GLOB.silicon_mob_list + if(!S) return + + var/datum/nano_module/law_manager/L = new(S) + L.ui_interact(usr, state = admin_state) + log_and_message_admins("has opened [S]'s law manager.") + feedback_add_details("admin_verb","MSL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/change_human_appearance_admin(mob/living/carbon/human/H in GLOB.mob_list) + set name = "C.M.A. - Admin" + set desc = "Allows you to change the mob appearance" + set category = null + + if(!check_rights(R_ADMIN)) + return + + if(!istype(H)) + if(istype(H, /mob/living/carbon/brain)) + var/mob/living/carbon/brain/B = H + if(istype(B.container, /obj/item/mmi/robotic_brain/positronic)) + var/obj/item/mmi/robotic_brain/positronic/C = B.container + var/obj/item/organ/internal/brain/mmi_holder/posibrain/P = C.loc + if(ishuman(P.owner)) + H = P.owner + else + return + else + return + + if(holder) + admin_log_and_message_admins("is altering the appearance of [H].") + H.change_appearance(APPEARANCE_ALL, usr, usr, check_species_whitelist = 0) + feedback_add_details("admin_verb","CHAA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/change_human_appearance_self(mob/living/carbon/human/H in GLOB.mob_list) + set name = "C.M.A. - Self" + set desc = "Allows the mob to change its appearance" + set category = null + + if(!check_rights(R_ADMIN)) + return + + if(!istype(H)) + if(istype(H, /mob/living/carbon/brain)) + var/mob/living/carbon/brain/B = H + if(istype(B.container, /obj/item/mmi/robotic_brain/positronic)) + var/obj/item/mmi/robotic_brain/positronic/C = B.container + var/obj/item/organ/internal/brain/mmi_holder/posibrain/P = C.loc + if(ishuman(P.owner)) + H = P.owner + else + return + else + return + + if(!H.client) + to_chat(usr, "Only mobs with clients can alter their own appearance.") + return + + switch(alert("Do you wish for [H] to be allowed to select non-whitelisted races?","Alter Mob Appearance","Yes","No","Cancel")) + if("Yes") + admin_log_and_message_admins("has allowed [H] to change [H.p_their()] appearance, without whitelisting of races.") + H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 0) + if("No") + admin_log_and_message_admins("has allowed [H] to change [H.p_their()] appearance, with whitelisting of races.") + H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 1) + feedback_add_details("admin_verb","CMAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/free_slot() + set name = "Free Job Slot" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + var/list/jobs = list() + for(var/datum/job/J in SSjobs.occupations) + if(J.current_positions >= J.total_positions && J.total_positions != -1) + jobs += J.title + if(!jobs.len) + to_chat(usr, "There are no fully staffed jobs.") + return + var/job = input("Please select job slot to free", "Free Job Slot") as null|anything in jobs + if(job) + SSjobs.FreeRole(job) + log_admin("[key_name(usr)] has freed a job slot for [job].") + message_admins("[key_name_admin(usr)] has freed a job slot for [job].") + +/client/proc/toggleattacklogs() + set name = "Toggle Attack Log Messages" + set category = "Preferences" + + if(!check_rights(R_ADMIN)) + return + + if(prefs.atklog == ATKLOG_ALL) + prefs.atklog = ATKLOG_ALMOSTALL + to_chat(usr, "Your attack logs preference is now: show ALMOST ALL attack logs (notable exceptions: NPCs attacking other NPCs, vampire bites, equipping/stripping, people pushing each other over)") + else if(prefs.atklog == ATKLOG_ALMOSTALL) + prefs.atklog = ATKLOG_MOST + to_chat(usr, "Your attack logs preference is now: show MOST attack logs (like ALMOST ALL, except that it also hides player v. NPC combat, and certain areas like lavaland syndie base and thunderdome)") + else if(prefs.atklog == ATKLOG_MOST) + prefs.atklog = ATKLOG_FEW + to_chat(usr, "Your attack logs preference is now: show FEW attack logs (only the most important stuff: attacks on SSDs, use of explosives, messing with the engine, gibbing, AI wiping, forcefeeding, acid sprays, and organ extraction)") + else if(prefs.atklog == ATKLOG_FEW) + prefs.atklog = ATKLOG_NONE + to_chat(usr, "Your attack logs preference is now: show NO attack logs") + else if(prefs.atklog == ATKLOG_NONE) + prefs.atklog = ATKLOG_ALL + to_chat(usr, "Your attack logs preference is now: show ALL attack logs") + else + prefs.atklog = ATKLOG_ALL + to_chat(usr, "Your attack logs preference is now: show ALL attack logs (your preference was set to an invalid value, it has been reset)") + + prefs.save_preferences(src) + + +/client/proc/toggleadminlogs() + set name = "Toggle Admin Log Messages" + set category = "Preferences" + + if(!check_rights(R_ADMIN)) + return + + prefs.toggles ^= CHAT_NO_ADMINLOGS + prefs.save_preferences(src) + if(prefs.toggles & CHAT_NO_ADMINLOGS) + to_chat(usr, "You now won't get admin log messages.") + else + to_chat(usr, "You now will get admin log messages.") + +/client/proc/toggleMentorTicketLogs() + set name = "Toggle Mentor Ticket Messages" + set category = "Preferences" + + if(!check_rights(R_MENTOR|R_ADMIN)) + return + + prefs.toggles ^= CHAT_NO_MENTORTICKETLOGS + prefs.save_preferences(src) + if(prefs.toggles & CHAT_NO_MENTORTICKETLOGS) + to_chat(usr, "You now won't get mentor ticket messages.") + else + to_chat(usr, "You now will get mentor ticket messages.") + +/client/proc/toggleticketlogs() + set name = "Toggle Admin Ticket Messgaes" + set category = "Preferences" + + if(!check_rights(R_ADMIN)) + return + + prefs.toggles ^= CHAT_NO_TICKETLOGS + prefs.save_preferences(src) + if(prefs.toggles & CHAT_NO_TICKETLOGS) + to_chat(usr, "You now won't get admin ticket messages.") + else + to_chat(usr, "You now will get admin ticket messages.") + +/client/proc/toggledrones() + set name = "Toggle Maintenance Drones" + set category = "Server" + + if(!check_rights(R_SERVER)) + return + + config.allow_drone_spawn = !(config.allow_drone_spawn) + log_admin("[key_name(usr)] has [config.allow_drone_spawn ? "enabled" : "disabled"] maintenance drones.") + message_admins("[key_name_admin(usr)] has [config.allow_drone_spawn ? "enabled" : "disabled"] maintenance drones.") + +/client/proc/toggledebuglogs() + set name = "Toggle Debug Log Messages" + set category = "Preferences" + + if(!check_rights(R_DEBUG)) + return + + prefs.toggles ^= CHAT_DEBUGLOGS + prefs.save_preferences(src) + if(prefs.toggles & CHAT_DEBUGLOGS) + to_chat(usr, "You now will get debug log messages") + else + to_chat(usr, "You now won't get debug log messages") + +/client/proc/man_up(mob/T as mob in GLOB.mob_list) + set category = "Admin" + set name = "Man Up" + set desc = "Tells mob to man up and deal with it." + + if(!check_rights(R_ADMIN)) + return + + to_chat(T, "Man up and deal with it.") + to_chat(T, "Move on.") + T << 'sound/voice/manup1.ogg' + + log_admin("[key_name(usr)] told [key_name(T)] to man up and deal with it.") + message_admins("[key_name_admin(usr)] told [key_name(T)] to man up and deal with it.") + +/client/proc/global_man_up() + set category = "Admin" + set name = "Man Up Global" + set desc = "Tells everyone to man up and deal with it." + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert("Are you sure you want to send the global message?", "Confirm Man Up Global", "Yes", "No") + + if(confirm == "Yes") + for(var/mob/T as mob in GLOB.mob_list) + to_chat(T, "
        Man up.
        Deal with it.

        Move on.

        ") + T << 'sound/voice/manup1.ogg' + + log_admin("[key_name(usr)] told everyone to man up and deal with it.") + message_admins("[key_name_admin(usr)] told everyone to man up and deal with it.") + +/client/proc/toggle_advanced_interaction() + set name = "Toggle Advanced Admin Interaction" + set category = "Admin" + set desc = "Allows you to interact with atoms such as buttons and doors, on top of regular machinery interaction." + + if(!check_rights(R_ADMIN)) + return + + advanced_admin_interaction = !advanced_admin_interaction + + log_admin("[key_name(usr)] has [advanced_admin_interaction ? "activated" : "deactivated"] their advanced admin interaction.") + message_admins("[key_name_admin(usr)] has [advanced_admin_interaction ? "activated" : "deactivated"] their advanced admin interaction.") diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index 920bb933f32..81060503f06 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -1,179 +1,179 @@ -var/jobban_runonce // Updates legacy bans with new info -var/jobban_keylist[0] // Linear list of jobban strings, kept around for the legacy system -var/jobban_assoclist[0] // Associative list, for efficiency - -// Matches string-based jobbans into ckey, rank, and reason groups -var/regex/jobban_regex = regex("(\[\\S]+) - (\[^#]+\[^# ])(?: ## (.+))?") - -/proc/jobban_assoc_insert(ckey, rank, reason) - if(!ckey || !rank) - return - if(!jobban_assoclist[ckey]) - jobban_assoclist[ckey] = list() - jobban_assoclist[ckey][rank] = reason || "Reason Unspecified" - -/proc/jobban_fullban(mob/M, rank, reason) - if(!M || !M.key) - return - jobban_keylist.Add(text("[M.ckey] - [rank] ## [reason]")) - jobban_assoc_insert(M.ckey, rank, reason) - if(config.ban_legacy_system) - jobban_savebanfile() - -/proc/jobban_client_fullban(ckey, rank) - if(!ckey || !rank) - return - jobban_keylist.Add(text("[ckey] - [rank]")) - jobban_assoc_insert(ckey, rank) - if(config.ban_legacy_system) - jobban_savebanfile() - -//returns a reason if M is banned from rank, returns 0 otherwise -/proc/jobban_isbanned(mob/M, rank) - if(!M || !rank) - return 0 - - if(config.guest_jobban && guest_jobbans(rank)) - if(IsGuestKey(M.key)) - return "Guest Job-ban" - - if(jobban_assoclist[M.ckey]) - return jobban_assoclist[M.ckey][rank] - else - return 0 - -/* -DEBUG -/mob/verb/list_all_jobbans() - set name = "list all jobbans" - - for(var/s in jobban_keylist) - to_chat(world, s) - -/mob/verb/reload_jobbans() - set name = "reload jobbans" - - jobban_loadbanfile() -*/ - -/hook/startup/proc/loadJobBans() - jobban_loadbanfile() - return 1 - -/proc/jobban_loadbanfile() - if(config.ban_legacy_system) - var/savefile/S=new("data/job_full.ban") - S["keys[0]"] >> jobban_keylist - log_admin("Loading jobban_rank") - S["runonce"] >> jobban_runonce - - if(!length(jobban_keylist)) - jobban_keylist=list() - log_admin("jobban_keylist was empty") - - for(var/s in jobban_keylist) - if(jobban_regex.Find(s)) - jobban_assoc_insert(jobban_regex.group[1], jobban_regex.group[2], jobban_regex.group[3]) - else - log_runtime(EXCEPTION("Skipping malformed job ban: [s]")) - else - if(!establish_db_connection()) - log_world("Database connection failed. Reverting to the legacy ban system.") - config.ban_legacy_system = 1 - jobban_loadbanfile() - return - - //Job permabans - var/DBQuery/permabans = dbcon.NewQuery("SELECT ckey, job FROM [format_table_name("ban")] WHERE bantype = 'JOB_PERMABAN' AND isnull(unbanned)") - permabans.Execute() - // Job tempbans - var/DBQuery/tempbans = dbcon.NewQuery("SELECT ckey, job FROM [format_table_name("ban")] WHERE bantype = 'JOB_TEMPBAN' AND isnull(unbanned) AND expiration_time > Now()") - tempbans.Execute() - - while(TRUE) - var/ckey - var/job - if(permabans.NextRow()) - ckey = permabans.item[1] - job = permabans.item[2] - else if(tempbans.NextRow()) - ckey = tempbans.item[1] - job = tempbans.item[2] - else - break - - jobban_keylist.Add("[ckey] - [job]") - jobban_assoc_insert(ckey, job) - -/proc/jobban_savebanfile() - var/savefile/S=new("data/job_full.ban") - S["keys[0]"] << jobban_keylist - -/proc/jobban_unban(mob/M, rank) - jobban_remove("[M.ckey] - [rank]") - -/proc/jobban_unban_client(ckey, rank) - jobban_remove("[ckey] - [rank]") - -/proc/ban_unban_log_save(var/formatted_log) - text2file(formatted_log,"data/ban_unban_log.txt") - - -/proc/jobban_remove(X) - for(var/i = 1; i <= length(jobban_keylist); i++) - if( findtext(jobban_keylist[i], "[X]") ) - // This need to be here, instead of jobban_unban, due to direct calls to jobban_remove - if(jobban_regex.Find(X)) - var/ckey = jobban_regex.group[1] - var/rank = jobban_regex.group[2] - if(jobban_assoclist[ckey] && jobban_assoclist[ckey][rank]) - jobban_assoclist[ckey] -= rank - else - log_runtime(EXCEPTION("Attempted to remove non-existent job ban: [X]")) - else - log_runtime(EXCEPTION("Failed to remove malformed job ban from associative list: [X]")) - jobban_keylist.Remove(jobban_keylist[i]) - if(config.ban_legacy_system) - jobban_savebanfile() - return 1 - return 0 - -/mob/verb/displayjobbans() - set category = "OOC" - set name = "Display Current Jobbans" - set desc = "Displays all of your current jobbans." - - if(!client || !ckey) - return - if(config.ban_legacy_system) - //using the legacy .txt ban system - to_chat(src, "The server is using the legacy ban system. Ask an administrator for help!") - - else - //using the SQL ban system - var/is_actually_banned = FALSE - var/DBQuery/select_query = dbcon.NewQuery("SELECT bantime, bantype, reason, job, duration, expiration_time, a_ckey FROM [format_table_name("ban")] WHERE ckey like '[ckey]' AND ((bantype like 'JOB_TEMPBAN' AND expiration_time > Now()) OR (bantype like 'JOB_PERMABAN')) AND isnull(unbanned) ORDER BY bantime DESC LIMIT 100") - select_query.Execute() - - while(select_query.NextRow()) - - var/bantime = select_query.item[1] - var/bantype = select_query.item[2] - var/reason = select_query.item[3] - var/job = select_query.item[4] - var/duration = select_query.item[5] - var/expiration = select_query.item[6] - var/ackey = select_query.item[7] - - if(bantype == "JOB_PERMABAN") - to_chat(src, "[bantype]: [job] - REASON: [reason], by [ackey]; [bantime]") - else if(bantype == "JOB_TEMPBAN") - to_chat(src, "[bantype]: [job] - REASON: [reason], by [ackey]; [bantime]; [duration]; expires [expiration]") - - is_actually_banned = TRUE - - if(is_actually_banned) - if(config.banappeals) - to_chat(src, "You can appeal the bans at: [config.banappeals]") - else - to_chat(src, "You have no active jobbans!") +var/jobban_runonce // Updates legacy bans with new info +var/jobban_keylist[0] // Linear list of jobban strings, kept around for the legacy system +var/jobban_assoclist[0] // Associative list, for efficiency + +// Matches string-based jobbans into ckey, rank, and reason groups +var/regex/jobban_regex = regex("(\[\\S]+) - (\[^#]+\[^# ])(?: ## (.+))?") + +/proc/jobban_assoc_insert(ckey, rank, reason) + if(!ckey || !rank) + return + if(!jobban_assoclist[ckey]) + jobban_assoclist[ckey] = list() + jobban_assoclist[ckey][rank] = reason || "Reason Unspecified" + +/proc/jobban_fullban(mob/M, rank, reason) + if(!M || !M.key) + return + jobban_keylist.Add(text("[M.ckey] - [rank] ## [reason]")) + jobban_assoc_insert(M.ckey, rank, reason) + if(config.ban_legacy_system) + jobban_savebanfile() + +/proc/jobban_client_fullban(ckey, rank) + if(!ckey || !rank) + return + jobban_keylist.Add(text("[ckey] - [rank]")) + jobban_assoc_insert(ckey, rank) + if(config.ban_legacy_system) + jobban_savebanfile() + +//returns a reason if M is banned from rank, returns 0 otherwise +/proc/jobban_isbanned(mob/M, rank) + if(!M || !rank) + return 0 + + if(config.guest_jobban && guest_jobbans(rank)) + if(IsGuestKey(M.key)) + return "Guest Job-ban" + + if(jobban_assoclist[M.ckey]) + return jobban_assoclist[M.ckey][rank] + else + return 0 + +/* +DEBUG +/mob/verb/list_all_jobbans() + set name = "list all jobbans" + + for(var/s in jobban_keylist) + to_chat(world, s) + +/mob/verb/reload_jobbans() + set name = "reload jobbans" + + jobban_loadbanfile() +*/ + +/hook/startup/proc/loadJobBans() + jobban_loadbanfile() + return 1 + +/proc/jobban_loadbanfile() + if(config.ban_legacy_system) + var/savefile/S=new("data/job_full.ban") + S["keys[0]"] >> jobban_keylist + log_admin("Loading jobban_rank") + S["runonce"] >> jobban_runonce + + if(!length(jobban_keylist)) + jobban_keylist=list() + log_admin("jobban_keylist was empty") + + for(var/s in jobban_keylist) + if(jobban_regex.Find(s)) + jobban_assoc_insert(jobban_regex.group[1], jobban_regex.group[2], jobban_regex.group[3]) + else + log_runtime(EXCEPTION("Skipping malformed job ban: [s]")) + else + if(!establish_db_connection()) + log_world("Database connection failed. Reverting to the legacy ban system.") + config.ban_legacy_system = 1 + jobban_loadbanfile() + return + + //Job permabans + var/DBQuery/permabans = dbcon.NewQuery("SELECT ckey, job FROM [format_table_name("ban")] WHERE bantype = 'JOB_PERMABAN' AND isnull(unbanned)") + permabans.Execute() + // Job tempbans + var/DBQuery/tempbans = dbcon.NewQuery("SELECT ckey, job FROM [format_table_name("ban")] WHERE bantype = 'JOB_TEMPBAN' AND isnull(unbanned) AND expiration_time > Now()") + tempbans.Execute() + + while(TRUE) + var/ckey + var/job + if(permabans.NextRow()) + ckey = permabans.item[1] + job = permabans.item[2] + else if(tempbans.NextRow()) + ckey = tempbans.item[1] + job = tempbans.item[2] + else + break + + jobban_keylist.Add("[ckey] - [job]") + jobban_assoc_insert(ckey, job) + +/proc/jobban_savebanfile() + var/savefile/S=new("data/job_full.ban") + S["keys[0]"] << jobban_keylist + +/proc/jobban_unban(mob/M, rank) + jobban_remove("[M.ckey] - [rank]") + +/proc/jobban_unban_client(ckey, rank) + jobban_remove("[ckey] - [rank]") + +/proc/ban_unban_log_save(var/formatted_log) + text2file(formatted_log,"data/ban_unban_log.txt") + + +/proc/jobban_remove(X) + for(var/i = 1; i <= length(jobban_keylist); i++) + if( findtext(jobban_keylist[i], "[X]") ) + // This need to be here, instead of jobban_unban, due to direct calls to jobban_remove + if(jobban_regex.Find(X)) + var/ckey = jobban_regex.group[1] + var/rank = jobban_regex.group[2] + if(jobban_assoclist[ckey] && jobban_assoclist[ckey][rank]) + jobban_assoclist[ckey] -= rank + else + log_runtime(EXCEPTION("Attempted to remove non-existent job ban: [X]")) + else + log_runtime(EXCEPTION("Failed to remove malformed job ban from associative list: [X]")) + jobban_keylist.Remove(jobban_keylist[i]) + if(config.ban_legacy_system) + jobban_savebanfile() + return 1 + return 0 + +/mob/verb/displayjobbans() + set category = "OOC" + set name = "Display Current Jobbans" + set desc = "Displays all of your current jobbans." + + if(!client || !ckey) + return + if(config.ban_legacy_system) + //using the legacy .txt ban system + to_chat(src, "The server is using the legacy ban system. Ask an administrator for help!") + + else + //using the SQL ban system + var/is_actually_banned = FALSE + var/DBQuery/select_query = dbcon.NewQuery("SELECT bantime, bantype, reason, job, duration, expiration_time, a_ckey FROM [format_table_name("ban")] WHERE ckey like '[ckey]' AND ((bantype like 'JOB_TEMPBAN' AND expiration_time > Now()) OR (bantype like 'JOB_PERMABAN')) AND isnull(unbanned) ORDER BY bantime DESC LIMIT 100") + select_query.Execute() + + while(select_query.NextRow()) + + var/bantime = select_query.item[1] + var/bantype = select_query.item[2] + var/reason = select_query.item[3] + var/job = select_query.item[4] + var/duration = select_query.item[5] + var/expiration = select_query.item[6] + var/ackey = select_query.item[7] + + if(bantype == "JOB_PERMABAN") + to_chat(src, "[bantype]: [job] - REASON: [reason], by [ackey]; [bantime]") + else if(bantype == "JOB_TEMPBAN") + to_chat(src, "[bantype]: [job] - REASON: [reason], by [ackey]; [bantime]; [duration]; expires [expiration]") + + is_actually_banned = TRUE + + if(is_actually_banned) + if(config.banappeals) + to_chat(src, "You can appeal the bans at: [config.banappeals]") + else + to_chat(src, "You have no active jobbans!") diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index fa105486654..e6148c7f554 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -1,9 +1,9 @@ -/var/create_mob_html = null -/datum/admins/proc/create_mob(var/mob/user) - if(!create_mob_html) - var/mobjs = null - mobjs = jointext(typesof(/mob), ";") - create_mob_html = file2text('html/create_object.html') - create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"") - - user << browse(replacetext(create_mob_html, "/* ref src */", UID()), "window=create_mob;size=425x475") +/var/create_mob_html = null +/datum/admins/proc/create_mob(var/mob/user) + if(!create_mob_html) + var/mobjs = null + mobjs = jointext(typesof(/mob), ";") + create_mob_html = file2text('html/create_object.html') + create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"") + + user << browse(replacetext(create_mob_html, "/* ref src */", UID()), "window=create_mob;size=425x475") diff --git a/code/modules/admin/create_object.dm b/code/modules/admin/create_object.dm index 72bbaa47f5c..e3a930058bd 100644 --- a/code/modules/admin/create_object.dm +++ b/code/modules/admin/create_object.dm @@ -1,23 +1,23 @@ -var/create_object_html = null -var/list/create_object_forms = list(/obj, /obj/structure, /obj/machinery, /obj/effect, /obj/item, /obj/mecha, /obj/item/clothing, /obj/item/stack, /obj/item/reagent_containers, /obj/item/gun) - -/datum/admins/proc/create_object(var/mob/user) - if(!create_object_html) - var/objectjs = null - objectjs = jointext(typesof(/obj), ";") - create_object_html = file2text('html/create_object.html') - create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"") - - user << browse(replacetext(create_object_html, "/* ref src */", UID()), "window=create_object;size=425x475") - -/datum/admins/proc/quick_create_object(var/mob/user) - var/path = input("Select the path of the object you wish to create.", "Path", /obj) in create_object_forms - var/html_form = create_object_forms[path] - - if(!html_form) - var/objectjs = jointext(typesof(path), ";") - html_form = file2text('html/create_object.html') - html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"") - create_object_forms[path] = html_form - - user << browse(replacetext(html_form, "/* ref src */", UID()), "window=qco[path];size=425x475") \ No newline at end of file +var/create_object_html = null +var/list/create_object_forms = list(/obj, /obj/structure, /obj/machinery, /obj/effect, /obj/item, /obj/mecha, /obj/item/clothing, /obj/item/stack, /obj/item/reagent_containers, /obj/item/gun) + +/datum/admins/proc/create_object(var/mob/user) + if(!create_object_html) + var/objectjs = null + objectjs = jointext(typesof(/obj), ";") + create_object_html = file2text('html/create_object.html') + create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"") + + user << browse(replacetext(create_object_html, "/* ref src */", UID()), "window=create_object;size=425x475") + +/datum/admins/proc/quick_create_object(var/mob/user) + var/path = input("Select the path of the object you wish to create.", "Path", /obj) in create_object_forms + var/html_form = create_object_forms[path] + + if(!html_form) + var/objectjs = jointext(typesof(path), ";") + html_form = file2text('html/create_object.html') + html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"") + create_object_forms[path] = html_form + + user << browse(replacetext(html_form, "/* ref src */", UID()), "window=qco[path];size=425x475") diff --git a/code/modules/admin/create_poll.dm b/code/modules/admin/create_poll.dm index 1239bfd993a..a1c6de98d93 100644 --- a/code/modules/admin/create_poll.dm +++ b/code/modules/admin/create_poll.dm @@ -199,4 +199,4 @@ function onload() { log_game("SQL ERROR obtaining id from poll_question table. Error : \[[err]\]\n") return create_poll_window("Your poll has been successfully created") - return pollid \ No newline at end of file + return pollid diff --git a/code/modules/admin/create_turf.dm b/code/modules/admin/create_turf.dm index 8d1f126a7ec..b895032d374 100644 --- a/code/modules/admin/create_turf.dm +++ b/code/modules/admin/create_turf.dm @@ -1,9 +1,9 @@ -/var/create_turf_html = null -/datum/admins/proc/create_turf(var/mob/user) - if(!create_turf_html) - var/turfjs = null - turfjs = jointext(typesof(/turf), ";") - create_turf_html = file2text('html/create_object.html') - create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"") - - user << browse(replacetext(create_turf_html, "/* ref src */", UID()), "window=create_turf;size=425x475") +/var/create_turf_html = null +/datum/admins/proc/create_turf(var/mob/user) + if(!create_turf_html) + var/turfjs = null + turfjs = jointext(typesof(/turf), ";") + create_turf_html = file2text('html/create_object.html') + create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"") + + user << browse(replacetext(create_turf_html, "/* ref src */", UID()), "window=create_turf;size=425x475") diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index ebf8a70cde3..33654c7e341 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -1,108 +1,108 @@ -var/list/admin_datums = list() - -/datum/admins - var/rank = "Temporary Admin" - var/client/owner = null - var/rights = 0 - var/fakekey = null - var/big_brother = 0 - - var/datum/marked_datum - - var/admincaster_screen = 0 //See newscaster.dm under machinery for a full description - var/datum/feed_message/admincaster_feed_message = new /datum/feed_message //These two will act as holders. - var/datum/feed_channel/admincaster_feed_channel = new /datum/feed_channel - var/admincaster_signature //What you'll sign the newsfeeds as - -/datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey) - if(!ckey) - error("Admin datum created without a ckey argument. Datum has been deleted") - qdel(src) - return - admincaster_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" - rank = initial_rank - rights = initial_rights - admin_datums[ckey] = src - -/datum/admins/Destroy() - ..() - return QDEL_HINT_HARDDEL_NOW - -/datum/admins/proc/associate(client/C) - if(istype(C)) - owner = C - owner.holder = src - owner.on_holder_add() - owner.add_admin_verbs() //TODO - owner.verbs -= /client/proc/readmin - GLOB.admins |= C - -/datum/admins/proc/disassociate() - if(owner) - GLOB.admins -= owner - owner.remove_admin_verbs() - owner.holder = null - owner = null - -/* -checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags) -if rights_required == 0, then it simply checks if they are an admin. -if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed -generally it would be used like so: - -proc/admin_proc() - if(!check_rights(R_ADMIN)) return - to_chat(world, "you have enough rights!") - -NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call -you will have to do something like if(client.holder.rights & R_ADMIN) yourself. -*/ -/proc/check_rights(rights_required, show_msg=1, var/mob/user = usr) - if(user && user.client) - if(rights_required) - if(user.client.holder) - if(rights_required & user.client.holder.rights) - return 1 - else - if(show_msg) - to_chat(user, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].") - else - if(user.client.holder) - return 1 - else - if(show_msg) - to_chat(user, "Error: You are not an admin.") - return 0 - -//probably a bit iffy - will hopefully figure out a better solution -/proc/check_if_greater_rights_than(client/other) - if(usr && usr.client) - if(usr.client.holder) - if(!other || !other.holder) - return 1 - if(usr.client.holder.rights != other.holder.rights) - if( (usr.client.holder.rights & other.holder.rights) == other.holder.rights ) - return 1 //we have all the rights they have and more - to_chat(usr, "Error: Cannot proceed. They have more or equal rights to us.") - return 0 - -/client/proc/deadmin() - admin_datums -= ckey - if(holder) - holder.disassociate() - qdel(holder) - return 1 - -//This proc checks whether subject has at least ONE of the rights specified in rights_required. -/proc/check_rights_for(client/subject, rights_required) - if(subject && subject.holder) - if(rights_required && !(rights_required & subject.holder.rights)) - return 0 - return 1 - return 0 - -/datum/admins/vv_edit_var(var_name, var_value) - return FALSE // no admin abuse - -/datum/admins/can_vv_delete() - return FALSE // don't break shit either \ No newline at end of file +var/list/admin_datums = list() + +/datum/admins + var/rank = "Temporary Admin" + var/client/owner = null + var/rights = 0 + var/fakekey = null + var/big_brother = 0 + + var/datum/marked_datum + + var/admincaster_screen = 0 //See newscaster.dm under machinery for a full description + var/datum/feed_message/admincaster_feed_message = new /datum/feed_message //These two will act as holders. + var/datum/feed_channel/admincaster_feed_channel = new /datum/feed_channel + var/admincaster_signature //What you'll sign the newsfeeds as + +/datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey) + if(!ckey) + error("Admin datum created without a ckey argument. Datum has been deleted") + qdel(src) + return + admincaster_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" + rank = initial_rank + rights = initial_rights + admin_datums[ckey] = src + +/datum/admins/Destroy() + ..() + return QDEL_HINT_HARDDEL_NOW + +/datum/admins/proc/associate(client/C) + if(istype(C)) + owner = C + owner.holder = src + owner.on_holder_add() + owner.add_admin_verbs() //TODO + owner.verbs -= /client/proc/readmin + GLOB.admins |= C + +/datum/admins/proc/disassociate() + if(owner) + GLOB.admins -= owner + owner.remove_admin_verbs() + owner.holder = null + owner = null + +/* +checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags) +if rights_required == 0, then it simply checks if they are an admin. +if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed +generally it would be used like so: + +proc/admin_proc() + if(!check_rights(R_ADMIN)) return + to_chat(world, "you have enough rights!") + +NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call +you will have to do something like if(client.holder.rights & R_ADMIN) yourself. +*/ +/proc/check_rights(rights_required, show_msg=1, var/mob/user = usr) + if(user && user.client) + if(rights_required) + if(user.client.holder) + if(rights_required & user.client.holder.rights) + return 1 + else + if(show_msg) + to_chat(user, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].") + else + if(user.client.holder) + return 1 + else + if(show_msg) + to_chat(user, "Error: You are not an admin.") + return 0 + +//probably a bit iffy - will hopefully figure out a better solution +/proc/check_if_greater_rights_than(client/other) + if(usr && usr.client) + if(usr.client.holder) + if(!other || !other.holder) + return 1 + if(usr.client.holder.rights != other.holder.rights) + if( (usr.client.holder.rights & other.holder.rights) == other.holder.rights ) + return 1 //we have all the rights they have and more + to_chat(usr, "Error: Cannot proceed. They have more or equal rights to us.") + return 0 + +/client/proc/deadmin() + admin_datums -= ckey + if(holder) + holder.disassociate() + qdel(holder) + return 1 + +//This proc checks whether subject has at least ONE of the rights specified in rights_required. +/proc/check_rights_for(client/subject, rights_required) + if(subject && subject.holder) + if(rights_required && !(rights_required & subject.holder.rights)) + return 0 + return 1 + return 0 + +/datum/admins/vv_edit_var(var_name, var_value) + return FALSE // no admin abuse + +/datum/admins/can_vv_delete() + return FALSE // don't break shit either diff --git a/code/modules/admin/ipintel.dm b/code/modules/admin/ipintel.dm index 75f2422681a..8f9cba102fe 100644 --- a/code/modules/admin/ipintel.dm +++ b/code/modules/admin/ipintel.dm @@ -225,4 +225,4 @@ if(vpn_whitelist_add(target_ckey)) to_chat(usr, "[target_ckey] was added to the VPN whitelist.") else - to_chat(usr, "VPN whitelist unchanged.") \ No newline at end of file + to_chat(usr, "VPN whitelist unchanged.") diff --git a/code/modules/admin/permissionverbs/permissionedit.dm b/code/modules/admin/permissionverbs/permissionedit.dm index 57d58533437..5a3f38afc86 100644 --- a/code/modules/admin/permissionverbs/permissionedit.dm +++ b/code/modules/admin/permissionverbs/permissionedit.dm @@ -1,170 +1,170 @@ -/client/proc/edit_admin_permissions() - set category = "Admin" - set name = "Permissions Panel" - set desc = "Edit admin permissions" - if(!check_rights(R_PERMISSIONS)) - return - usr.client.holder.edit_admin_permissions() - -/datum/admins/proc/edit_admin_permissions() - if(!check_rights(R_PERMISSIONS)) - return - - var/output = {" - - -Permissions Panel - - - - -
        - - - - -"} - - for(var/adm_ckey in admin_datums) - var/datum/admins/D = admin_datums[adm_ckey] - if(!D) continue - var/rank = D.rank ? D.rank : "*none*" - var/rights = rights2text(D.rights," ") - if(!rights) rights = "*none*" - output += {" - - - -"} - - /*output += "" - output += "" - output += "" - output += "" - output += ""*/ - - output += {" -
        CKEY \[+\]RANKPERMISSIONS
        [adm_ckey] \[-\][rank][rights]
        [adm_ckey] \[-\][rank][rights]
        -
        Search:
        - -"} - - usr << browse(output,"window=editrights;size=600x500") - -/datum/admins/proc/log_admin_rank_modification(var/adm_ckey, var/new_rank) - if(config.admin_legacy_system) return - - if(!usr.client) - return - - if(!check_rights(R_PERMISSIONS)) - return - - establish_db_connection() - - if(!dbcon.IsConnected()) - to_chat(usr, "Failed to establish database connection") - return - - if(!adm_ckey || !new_rank) - return - - adm_ckey = ckey(adm_ckey) - - if(!adm_ckey) - return - - if(!istext(adm_ckey) || !istext(new_rank)) - return - - var/DBQuery/select_query = dbcon.NewQuery("SELECT id FROM [format_table_name("admin")] WHERE ckey = '[adm_ckey]'") - select_query.Execute() - - var/new_admin = 1 - var/admin_id - while(select_query.NextRow()) - new_admin = 0 - admin_id = text2num(select_query.item[1]) - - flag_account_for_forum_sync(adm_ckey) - if(new_admin) - var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin")] (`id`, `ckey`, `rank`, `level`, `flags`) VALUES (null, '[adm_ckey]', '[new_rank]', -1, 0)") - insert_query.Execute() - var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Added new admin [adm_ckey] to rank [new_rank]');") - log_query.Execute() - to_chat(usr, "New admin added.") - else - if(!isnull(admin_id) && isnum(admin_id)) - var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET rank = '[new_rank]' WHERE id = [admin_id]") - insert_query.Execute() - var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Edited the rank of [adm_ckey] to [new_rank]');") - log_query.Execute() - to_chat(usr, "Admin rank changed.") - -/datum/admins/proc/log_admin_permission_modification(var/adm_ckey, var/new_permission) - if(config.admin_legacy_system) - return - - if(!usr.client) - return - - if(!check_rights(R_PERMISSIONS)) - return - - establish_db_connection() - if(!dbcon.IsConnected()) - to_chat(usr, "Failed to establish database connection") - return - - if(!adm_ckey || !new_permission) - return - - adm_ckey = ckey(adm_ckey) - - if(!adm_ckey) - return - - if(istext(new_permission)) - new_permission = text2num(new_permission) - - if(!istext(adm_ckey) || !isnum(new_permission)) - return - - var/DBQuery/select_query = dbcon.NewQuery("SELECT id, flags FROM [format_table_name("admin")] WHERE ckey = '[adm_ckey]'") - select_query.Execute() - - var/admin_id - var/admin_rights - while(select_query.NextRow()) - admin_id = text2num(select_query.item[1]) - admin_rights = text2num(select_query.item[2]) - - if(!admin_id) - return - - flag_account_for_forum_sync(adm_ckey) - if(admin_rights & new_permission) //This admin already has this permission, so we are removing it. - var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET flags = [admin_rights & ~new_permission] WHERE id = [admin_id]") - insert_query.Execute() - var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Removed permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]');") - log_query.Execute() - to_chat(usr, "Permission removed.") - else //This admin doesn't have this permission, so we are adding it. - var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET flags = '[admin_rights | new_permission]' WHERE id = [admin_id]") - insert_query.Execute() - var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Added permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]')") - log_query.Execute() - to_chat(usr, "Permission added.") - -/datum/admins/proc/updateranktodb(ckey,newrank) - establish_db_connection() - if(!dbcon.IsConnected()) - return - if(!check_rights(R_PERMISSIONS)) - return - var/sql_ckey = sanitizeSQL(ckey) - var/sql_admin_rank = sanitizeSQL(newrank) - - var/DBQuery/query_update = dbcon.NewQuery("UPDATE [format_table_name("player")] SET lastadminrank = '[sql_admin_rank]' WHERE ckey = '[sql_ckey]'") - query_update.Execute() - flag_account_for_forum_sync(sql_ckey) \ No newline at end of file +/client/proc/edit_admin_permissions() + set category = "Admin" + set name = "Permissions Panel" + set desc = "Edit admin permissions" + if(!check_rights(R_PERMISSIONS)) + return + usr.client.holder.edit_admin_permissions() + +/datum/admins/proc/edit_admin_permissions() + if(!check_rights(R_PERMISSIONS)) + return + + var/output = {" + + +Permissions Panel + + + + +
        + + + + +"} + + for(var/adm_ckey in admin_datums) + var/datum/admins/D = admin_datums[adm_ckey] + if(!D) continue + var/rank = D.rank ? D.rank : "*none*" + var/rights = rights2text(D.rights," ") + if(!rights) rights = "*none*" + output += {" + + + +"} + + /*output += "" + output += "" + output += "" + output += "" + output += ""*/ + + output += {" +
        CKEY \[+\]RANKPERMISSIONS
        [adm_ckey] \[-\][rank][rights]
        [adm_ckey] \[-\][rank][rights]
        +
        Search:
        + +"} + + usr << browse(output,"window=editrights;size=600x500") + +/datum/admins/proc/log_admin_rank_modification(var/adm_ckey, var/new_rank) + if(config.admin_legacy_system) return + + if(!usr.client) + return + + if(!check_rights(R_PERMISSIONS)) + return + + establish_db_connection() + + if(!dbcon.IsConnected()) + to_chat(usr, "Failed to establish database connection") + return + + if(!adm_ckey || !new_rank) + return + + adm_ckey = ckey(adm_ckey) + + if(!adm_ckey) + return + + if(!istext(adm_ckey) || !istext(new_rank)) + return + + var/DBQuery/select_query = dbcon.NewQuery("SELECT id FROM [format_table_name("admin")] WHERE ckey = '[adm_ckey]'") + select_query.Execute() + + var/new_admin = 1 + var/admin_id + while(select_query.NextRow()) + new_admin = 0 + admin_id = text2num(select_query.item[1]) + + flag_account_for_forum_sync(adm_ckey) + if(new_admin) + var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin")] (`id`, `ckey`, `rank`, `level`, `flags`) VALUES (null, '[adm_ckey]', '[new_rank]', -1, 0)") + insert_query.Execute() + var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Added new admin [adm_ckey] to rank [new_rank]');") + log_query.Execute() + to_chat(usr, "New admin added.") + else + if(!isnull(admin_id) && isnum(admin_id)) + var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET rank = '[new_rank]' WHERE id = [admin_id]") + insert_query.Execute() + var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Edited the rank of [adm_ckey] to [new_rank]');") + log_query.Execute() + to_chat(usr, "Admin rank changed.") + +/datum/admins/proc/log_admin_permission_modification(var/adm_ckey, var/new_permission) + if(config.admin_legacy_system) + return + + if(!usr.client) + return + + if(!check_rights(R_PERMISSIONS)) + return + + establish_db_connection() + if(!dbcon.IsConnected()) + to_chat(usr, "Failed to establish database connection") + return + + if(!adm_ckey || !new_permission) + return + + adm_ckey = ckey(adm_ckey) + + if(!adm_ckey) + return + + if(istext(new_permission)) + new_permission = text2num(new_permission) + + if(!istext(adm_ckey) || !isnum(new_permission)) + return + + var/DBQuery/select_query = dbcon.NewQuery("SELECT id, flags FROM [format_table_name("admin")] WHERE ckey = '[adm_ckey]'") + select_query.Execute() + + var/admin_id + var/admin_rights + while(select_query.NextRow()) + admin_id = text2num(select_query.item[1]) + admin_rights = text2num(select_query.item[2]) + + if(!admin_id) + return + + flag_account_for_forum_sync(adm_ckey) + if(admin_rights & new_permission) //This admin already has this permission, so we are removing it. + var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET flags = [admin_rights & ~new_permission] WHERE id = [admin_id]") + insert_query.Execute() + var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Removed permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]');") + log_query.Execute() + to_chat(usr, "Permission removed.") + else //This admin doesn't have this permission, so we are adding it. + var/DBQuery/insert_query = dbcon.NewQuery("UPDATE [format_table_name("admin")] SET flags = '[admin_rights | new_permission]' WHERE id = [admin_id]") + insert_query.Execute() + var/DBQuery/log_query = dbcon.NewQuery("INSERT INTO [format_table_name("admin_log")] (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , '[usr.ckey]', '[usr.client.address]', 'Added permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]')") + log_query.Execute() + to_chat(usr, "Permission added.") + +/datum/admins/proc/updateranktodb(ckey,newrank) + establish_db_connection() + if(!dbcon.IsConnected()) + return + if(!check_rights(R_PERMISSIONS)) + return + var/sql_ckey = sanitizeSQL(ckey) + var/sql_admin_rank = sanitizeSQL(newrank) + + var/DBQuery/query_update = dbcon.NewQuery("UPDATE [format_table_name("player")] SET lastadminrank = '[sql_admin_rank]' WHERE ckey = '[sql_ckey]'") + query_update.Execute() + flag_account_for_forum_sync(sql_ckey) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index ec0046cc88d..60156930408 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1,3490 +1,3490 @@ -/datum/admins/Topic(href, href_list) - ..() - - if(usr.client != src.owner || !check_rights(0)) - log_admin("[key_name(usr)] tried to use the admin panel without authorization.") - message_admins("[key_name_admin(usr)] has attempted to override the admin panel!") - return - - if(SSticker.mode && SSticker.mode.check_antagonists_topic(href, href_list)) - check_antagonists() - return - - if(href_list["rejectadminhelp"]) - if(!check_rights(R_ADMIN|R_MOD)) - return - var/client/C = locateUID(href_list["rejectadminhelp"]) - if(!C) - return - - C << 'sound/effects/adminhelp.ogg' - - to_chat(C, "- AdminHelp Rejected! -") - to_chat(C, "Your admin help was rejected.") - to_chat(C, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting. If you asked a question, please ensure it was clear what you were asking.") - - message_admins("[key_name_admin(usr)] rejected [key_name_admin(C.mob)]'s admin help") - log_admin("[key_name(usr)] rejected [key_name(C.mob)]'s admin help") - - if(href_list["openadminticket"]) - if(!check_rights(R_ADMIN)) - return - var/ticketID = text2num(href_list["openadminticket"]) - SStickets.showDetailUI(usr, ticketID) - - if(href_list["openmentorticket"]) - if(!check_rights(R_MENTOR|R_MOD|R_ADMIN)) - return - var/ticketID = text2num(href_list["openmentorticket"]) - SSmentor_tickets.showDetailUI(usr, ticketID) - - if(href_list["stickyban"]) - stickyban(href_list["stickyban"],href_list) - - if(href_list["makeAntag"]) - switch(href_list["makeAntag"]) - if("1") - log_admin("[key_name(usr)] has spawned a traitor.") - if(!makeTraitors()) - to_chat(usr, "Unfortunately there weren't enough candidates available.") - if("2") - log_admin("[key_name(usr)] has spawned a changeling.") - if(!makeChangelings()) - to_chat(usr, "Unfortunately there weren't enough candidates available.") - if("3") - log_admin("[key_name(usr)] has spawned revolutionaries.") - if(!makeRevs()) - to_chat(usr, "Unfortunately there weren't enough candidates available.") - if("4") - log_admin("[key_name(usr)] has spawned a cultists.") - if(!makeCult()) - to_chat(usr, "Unfortunately there weren't enough candidates available.") - if("5") - log_admin("[key_name(usr)] has spawned a wizard.") - if(!makeWizard()) - to_chat(usr, "Unfortunately there weren't enough candidates available.") - if("6") - log_admin("[key_name(usr)] has spawned vampires.") - if(!makeVampires()) - to_chat(usr, "Unfortunately there weren't enough candidates available.") - if("7") - log_admin("[key_name(usr)] has spawned vox raiders.") - if(!makeVoxRaiders()) - to_chat(usr, "Unfortunately there weren't enough candidates available.") - if("8") - log_admin("[key_name(usr)] has spawned an abductor team.") - if(!makeAbductorTeam()) - to_chat(usr, "Unfortunately there weren't enough candidates available.") - - else if(href_list["dbsearchckey"] || href_list["dbsearchadmin"] || href_list["dbsearchip"] || href_list["dbsearchcid"] || href_list["dbsearchbantype"]) - var/adminckey = href_list["dbsearchadmin"] - var/playerckey = href_list["dbsearchckey"] - var/playerip = href_list["dbsearchip"] - var/playercid = href_list["dbsearchcid"] - var/dbbantype = text2num(href_list["dbsearchbantype"]) - var/match = 0 - - if("dbmatch" in href_list) - match = 1 - - DB_ban_panel(playerckey, adminckey, playerip, playercid, dbbantype, match) - return - - else if(href_list["dbbanedit"]) - var/banedit = href_list["dbbanedit"] - var/banid = text2num(href_list["dbbanid"]) - if(!banedit || !banid) - return - - DB_ban_edit(banid, banedit) - return - - else if(href_list["dbbanaddtype"]) - - var/bantype = text2num(href_list["dbbanaddtype"]) - var/banckey = href_list["dbbanaddckey"] - var/banip = href_list["dbbanaddip"] - var/bancid = href_list["dbbanaddcid"] - var/banduration = text2num(href_list["dbbaddduration"]) - var/banjob = href_list["dbbanaddjob"] - var/banreason = href_list["dbbanreason"] - - banckey = ckey(banckey) - - switch(bantype) - if(BANTYPE_PERMA) - if(!banckey || !banreason) - to_chat(usr, "Not enough parameters (Requires ckey and reason)") - return - banduration = null - banjob = null - if(BANTYPE_TEMP) - if(!banckey || !banreason || !banduration) - to_chat(usr, "Not enough parameters (Requires ckey, reason and duration)") - return - banjob = null - if(BANTYPE_JOB_PERMA) - if(!banckey || !banreason || !banjob) - to_chat(usr, "Not enough parameters (Requires ckey, reason and job)") - return - banduration = null - if(BANTYPE_JOB_TEMP) - if(!banckey || !banreason || !banjob || !banduration) - to_chat(usr, "Not enough parameters (Requires ckey, reason and job)") - return - if(BANTYPE_APPEARANCE) - if(!banckey || !banreason) - to_chat(usr, "Not enough parameters (Requires ckey and reason)") - return - banduration = null - banjob = null - if(BANTYPE_ADMIN_PERMA) - if(!banckey || !banreason) - to_chat(usr, "Not enough parameters (Requires ckey and reason)") - return - banduration = null - banjob = null - if(BANTYPE_ADMIN_TEMP) - if(!banckey || !banreason || !banduration) - to_chat(usr, "Not enough parameters (Requires ckey, reason and duration)") - return - banjob = null - - var/mob/playermob - - for(var/mob/M in GLOB.player_list) - if(M.ckey == banckey) - playermob = M - break - - - banreason = "(MANUAL BAN) "+banreason - - if(!playermob) - if(banip) - banreason = "[banreason] (CUSTOM IP)" - if(bancid) - banreason = "[banreason] (CUSTOM CID)" - else - message_admins("Ban process: A mob matching [playermob.ckey] was found at location [playermob.x], [playermob.y], [playermob.z]. Custom IP and computer id fields replaced with the IP and computer id from the located mob") - - DB_ban_record(bantype, playermob, banduration, banreason, banjob, null, banckey, banip, bancid ) - - - else if(href_list["editrights"]) - if(!check_rights(R_PERMISSIONS)) - message_admins("[key_name_admin(usr)] attempted to edit the admin permissions without sufficient rights.") - log_admin("[key_name(usr)] attempted to edit the admin permissions without sufficient rights.") - return - - var/adm_ckey - - var/task = href_list["editrights"] - if(task == "add") - var/new_ckey = ckey(clean_input("New admin's ckey","Admin ckey", null)) - if(!new_ckey) return - if(new_ckey in admin_datums) - to_chat(usr, "Error: Topic 'editrights': [new_ckey] is already an admin") - return - adm_ckey = new_ckey - task = "rank" - else if(task != "show") - adm_ckey = ckey(href_list["ckey"]) - if(!adm_ckey) - to_chat(usr, "Error: Topic 'editrights': No valid ckey") - return - - var/datum/admins/D = admin_datums[adm_ckey] - - if(task == "remove") - if(alert("Are you sure you want to remove [adm_ckey]?","Message","Yes","Cancel") == "Yes") - if(!D) return - admin_datums -= adm_ckey - D.disassociate() - - updateranktodb(adm_ckey, "player") - message_admins("[key_name_admin(usr)] removed [adm_ckey] from the admins list") - log_admin("[key_name(usr)] removed [adm_ckey] from the admins list") - log_admin_rank_modification(adm_ckey, "Removed") - - else if(task == "rank") - var/new_rank - if(admin_ranks.len) - new_rank = input("Please select a rank", "New rank", null, null) as null|anything in (admin_ranks|"*New Rank*") - else - new_rank = input("Please select a rank", "New rank", null, null) as null|anything in list("Mentor", "Trial Admin", "Game Admin", "*New Rank*") - - var/rights = 0 - if(D) - rights = D.rights - switch(new_rank) - if(null,"") return - if("*New Rank*") - new_rank = input("Please input a new rank", "New custom rank", null, null) as null|text - if(config.admin_legacy_system) - new_rank = ckeyEx(new_rank) - if(!new_rank) - to_chat(usr, "Error: Topic 'editrights': Invalid rank") - return - if(config.admin_legacy_system) - if(admin_ranks.len) - if(new_rank in admin_ranks) - rights = admin_ranks[new_rank] //we typed a rank which already exists, use its rights - else - admin_ranks[new_rank] = 0 //add the new rank to admin_ranks - else - if(config.admin_legacy_system) - new_rank = ckeyEx(new_rank) - rights = admin_ranks[new_rank] //we input an existing rank, use its rights - - if(D) - D.disassociate() //remove adminverbs and unlink from client - D.rank = new_rank //update the rank - D.rights = rights //update the rights based on admin_ranks (default: 0) - else - D = new /datum/admins(new_rank, rights, adm_ckey) - - var/client/C = GLOB.directory[adm_ckey] //find the client with the specified ckey (if they are logged in) - D.associate(C) //link up with the client and add verbs - - updateranktodb(adm_ckey, new_rank) - message_admins("[key_name_admin(usr)] edited the admin rank of [adm_ckey] to [new_rank]") - log_admin("[key_name(usr)] edited the admin rank of [adm_ckey] to [new_rank]") - log_admin_rank_modification(adm_ckey, new_rank) - - else if(task == "permissions") - if(!D) return - while(TRUE) - var/list/permissionlist = list() - for(var/i=1, i<=R_MAXPERMISSION, i<<=1) //that <<= is shorthand for i = i << 1. Which is a left bitshift - permissionlist[rights2text(i)] = i - var/new_permission = input("Select a permission to turn on/off", adm_ckey + "'s Permissions", null, null) as null|anything in permissionlist - if(!new_permission) - return - var/oldrights = D.rights - var/toggleresult = "ON" - D.rights ^= permissionlist[new_permission] - if(oldrights > D.rights) - toggleresult = "OFF" - - message_admins("[key_name_admin(usr)] toggled the [new_permission] permission of [adm_ckey] to [toggleresult]") - log_admin("[key_name(usr)] toggled the [new_permission] permission of [adm_ckey] to [toggleresult]") - log_admin_permission_modification(adm_ckey, permissionlist[new_permission]) - - - edit_admin_permissions() - - else if(href_list["call_shuttle"]) - if(!check_rights(R_ADMIN)) return - - - switch(href_list["call_shuttle"]) - if("1") - if(SSshuttle.emergency.mode >= SHUTTLE_DOCKED) - return - SSshuttle.emergency.request() - log_admin("[key_name(usr)] called the Emergency Shuttle") - message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station") - - if("2") - if(SSshuttle.emergency.mode >= SHUTTLE_DOCKED) - return - switch(SSshuttle.emergency.mode) - if(SHUTTLE_CALL) - SSshuttle.emergency.cancel() - log_admin("[key_name(usr)] sent the Emergency Shuttle back") - message_admins("[key_name_admin(usr)] sent the Emergency Shuttle back") - else - SSshuttle.emergency.cancel() - log_admin("[key_name(usr)] called the Emergency Shuttle") - message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station") - - - href_list["secrets"] = "check_antagonist" - - else if(href_list["edit_shuttle_time"]) - if(!check_rights(R_SERVER)) return - - var/timer = input("Enter new shuttle duration (seconds):","Edit Shuttle Timeleft", SSshuttle.emergency.timeLeft() ) as num - SSshuttle.emergency.setTimer(timer*10) - log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds") - minor_announcement.Announce("The emergency shuttle will reach its destination in [round(SSshuttle.emergency.timeLeft(600))] minutes.") - message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds") - href_list["secrets"] = "check_antagonist" - - else if(href_list["delay_round_end"]) - if(!check_rights(R_SERVER)) return - - SSticker.delay_end = !SSticker.delay_end - log_admin("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].") - message_admins("[key_name_admin(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].", 1) - href_list["secretsadmin"] = "check_antagonist" - - else if(href_list["simplemake"]) - if(!check_rights(R_SPAWN)) return - - var/mob/M = locateUID(href_list["mob"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - var/delmob = 0 - switch(alert("Delete old mob?","Message","Yes","No","Cancel")) - if("Cancel") return - if("Yes") delmob = 1 - - switch(href_list["simplemake"]) - if("observer") M.change_mob_type( /mob/dead/observer , null, null, delmob, 1 ) - if("drone") M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob, 1 ) - if("hunter") M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob, 1 ) - if("queen") M.change_mob_type( /mob/living/carbon/alien/humanoid/queen/large , null, null, delmob, 1 ) - if("sentinel") M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob, 1 ) - if("larva") M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob, 1 ) - if("human") - var/posttransformoutfit = usr.client.robust_dress_shop() - var/mob/living/carbon/human/newmob = M.change_mob_type(/mob/living/carbon/human, null, null, delmob, 1) - if(posttransformoutfit && istype(newmob)) - newmob.equipOutfit(posttransformoutfit) - if("slime") M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob, 1 ) - if("monkey") M.change_mob_type( /mob/living/carbon/human/monkey , null, null, delmob, 1 ) - if("robot") M.change_mob_type( /mob/living/silicon/robot , null, null, delmob, 1 ) - if("cat") M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob, 1 ) - if("runtime") M.change_mob_type( /mob/living/simple_animal/pet/cat/Runtime , null, null, delmob, 1 ) - if("corgi") M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob, 1 ) - if("crab") M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob, 1 ) - if("coffee") M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob, 1 ) - if("parrot") M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob, 1 ) - if("polyparrot") M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob, 1 ) - if("constructarmoured") M.change_mob_type( /mob/living/simple_animal/hostile/construct/armoured , null, null, delmob, 1 ) - if("constructbuilder") M.change_mob_type( /mob/living/simple_animal/hostile/construct/builder , null, null, delmob, 1 ) - if("constructwraith") M.change_mob_type( /mob/living/simple_animal/hostile/construct/wraith , null, null, delmob, 1 ) - if("shade") M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob, 1 ) - - log_admin("[key_name(usr)] has used rudimentary transformation on [key_name(M)]. Transforming to [href_list["simplemake"]]; deletemob=[delmob]") - message_admins("[key_name_admin(usr)] has used rudimentary transformation on [key_name_admin(M)]. Transforming to [href_list["simplemake"]]; deletemob=[delmob]", 1) - - - /////////////////////////////////////new ban stuff - else if(href_list["unbanf"]) - if(!check_rights(R_BAN)) return - - var/banfolder = href_list["unbanf"] - Banlist.cd = "/base/[banfolder]" - var/key = Banlist["key"] - if(alert(usr, "Are you sure you want to unban [key]?", "Confirmation", "Yes", "No") == "Yes") - if(RemoveBan(banfolder)) - unbanpanel() - else - alert(usr, "This ban has already been lifted / does not exist.", "Error", "Ok") - unbanpanel() - - else if(href_list["warn"]) - usr.client.warn(href_list["warn"]) - - else if(href_list["unbane"]) - if(!check_rights(R_BAN)) return - - UpdateTime() - var/reason - - var/banfolder = href_list["unbane"] - Banlist.cd = "/base/[banfolder]" - var/reason2 = Banlist["reason"] - var/temp = Banlist["temp"] - - var/minutes = Banlist["minutes"] - - var/banned_key = Banlist["key"] - Banlist.cd = "/base" - - var/duration - - switch(alert("Temporary Ban?",,"Yes","No")) - if("Yes") - temp = 1 - var/mins = 0 - if(minutes > CMinutes) - mins = minutes - CMinutes - mins = input(usr,"How long (in minutes)? (Default: 1440)","Ban time",mins ? mins : 1440) as num|null - if(!mins) return - mins = min(525599,mins) - minutes = CMinutes + mins - duration = GetExp(minutes) - reason = input(usr,"Please state the reason","Reason",reason2) as message|null - if(!reason) return - if("No") - temp = 0 - duration = "Perma" - reason = input(usr,"Please state the reason","Reason",reason2) as message|null - if(!reason) return - - log_admin("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") - ban_unban_log_save("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") - message_admins("[key_name_admin(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]", 1) - Banlist.cd = "/base/[banfolder]" - to_chat(Banlist["reason"], reason) - to_chat(Banlist["temp"], temp) - to_chat(Banlist["minutes"], minutes) - to_chat(Banlist["bannedby"], usr.ckey) - Banlist.cd = "/base" - feedback_inc("ban_edit",1) - unbanpanel() - - /////////////////////////////////////new ban stuff - - else if(href_list["appearanceban"]) - if(!check_rights(R_BAN)) - return - var/mob/M = locateUID(href_list["appearanceban"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(!M.ckey) //sanity - to_chat(usr, "This mob has no ckey") - return - var/ban_ckey_param = href_list["dbbanaddckey"] - - var/banreason = appearance_isbanned(M) - if(banreason) - /* if(!config.ban_legacy_system) - to_chat(usr, "Unfortunately, database based unbanning cannot be done through this panel") - DB_ban_panel(M.ckey) - return */ - switch(alert("Reason: '[banreason]' Remove appearance ban?","Please Confirm","Yes","No")) - if("Yes") - ban_unban_log_save("[key_name(usr)] removed [key_name(M)]'s appearance ban") - log_admin("[key_name(usr)] removed [key_name(M)]'s appearance ban") - feedback_inc("ban_appearance_unban", 1) - DB_ban_unban(M.ckey, BANTYPE_APPEARANCE) - appearance_unban(M) - message_admins("[key_name_admin(usr)] removed [key_name_admin(M)]'s appearance ban", 1) - to_chat(M, "[usr.client.ckey] has removed your appearance ban.") - - else switch(alert("Appearance ban [M.ckey]?",,"Yes","No", "Cancel")) - if("Yes") - var/reason = input(usr,"Please state the reason","Reason") as message|null - if(!reason) - return - M = admin_ban_mobsearch(M, ban_ckey_param, usr) - ban_unban_log_save("[key_name(usr)] appearance banned [key_name(M)]. reason: [reason]") - log_admin("[key_name(usr)] appearance banned [key_name(M)]. \nReason: [reason]") - feedback_inc("ban_appearance",1) - DB_ban_record(BANTYPE_APPEARANCE, M, -1, reason) - appearance_fullban(M, "[reason]; By [usr.ckey] on [time2text(world.realtime)]") - add_note(M.ckey, "Appearance banned - [reason]", null, usr.ckey, 0) - message_admins("[key_name_admin(usr)] appearance banned [key_name_admin(M)]", 1) - to_chat(M, "You have been appearance banned by [usr.client.ckey].") - to_chat(M, "The reason is: [reason]") - to_chat(M, "Appearance ban can be lifted only upon request.") - if(config.banappeals) - to_chat(M, "To try to resolve this matter head to [config.banappeals]") - else - to_chat(M, "No ban appeals URL has been set.") - if("No") - return - - else if(href_list["jobban2"]) -// if(!check_rights(R_BAN)) return - - var/mob/M = locateUID(href_list["jobban2"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - if(!M.ckey) //sanity - to_chat(usr, "This mob has no ckey") - return - if(!SSjobs) - to_chat(usr, "SSjobs has not been setup!") - return - - var/dat = "" - var/header = "Job-Ban Panel: [M.name]" - var/body - var/jobs = "" - - /***********************************WARNING!************************************ - The jobban stuff looks mangled and disgusting - But it looks beautiful in-game - -Nodrak - ************************************WARNING!***********************************/ - var/counter = 0 -//Regular jobs - //Command (Blue) - jobs += "" - jobs += "" - for(var/jobPos in command_positions) - if(!jobPos) continue - var/datum/job/job = SSjobs.GetJob(jobPos) - if(!job) continue - - if(jobban_isbanned(M, job.title)) - jobs += "" - counter++ - else - jobs += "" - counter++ - - if(counter >= 6) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Command Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " - - //Security (Red) - counter = 0 - jobs += "" - jobs += "" - for(var/jobPos in security_positions) - if(!jobPos) continue - var/datum/job/job = SSjobs.GetJob(jobPos) - if(!job) continue - - if(jobban_isbanned(M, job.title)) - jobs += "" - counter++ - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Security Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " - - //Engineering (Yellow) - counter = 0 - jobs += "" - jobs += "" - for(var/jobPos in engineering_positions) - if(!jobPos) continue - var/datum/job/job = SSjobs.GetJob(jobPos) - if(!job) continue - - if(jobban_isbanned(M, job.title)) - jobs += "" - counter++ - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Engineering Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " - - //Medical (White) - counter = 0 - jobs += "" - jobs += "" - for(var/jobPos in medical_positions) - if(!jobPos) continue - var/datum/job/job = SSjobs.GetJob(jobPos) - if(!job) continue - - if(jobban_isbanned(M, job.title)) - jobs += "" - counter++ - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Medical Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " - - //Science (Purple) - counter = 0 - jobs += "" - jobs += "" - for(var/jobPos in science_positions) - if(!jobPos) continue - var/datum/job/job = SSjobs.GetJob(jobPos) - if(!job) continue - - if(jobban_isbanned(M, job.title)) - jobs += "" - counter++ - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Science Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " - - //Support (Grey) - counter = 0 - jobs += "" - jobs += "" - for(var/jobPos in support_positions) - if(!jobPos) continue - var/datum/job/job = SSjobs.GetJob(jobPos) - if(!job) continue - - if(jobban_isbanned(M, job.title)) - jobs += "" - counter++ - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Support Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " - - //Non-Human (Green) - counter = 0 - jobs += "" - jobs += "" - for(var/jobPos in nonhuman_positions) - if(!jobPos) continue - var/datum/job/job = SSjobs.GetJob(jobPos) - if(!job) continue - - if(jobban_isbanned(M, job.title)) - jobs += "" - counter++ - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - - //Drone - if(jobban_isbanned(M, "Drone")) - jobs += "" - else - jobs += "" - - //pAI - if(jobban_isbanned(M, "pAI")) - jobs += "" - else - jobs += "" - - jobs += "
        Non-human Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        DroneDronepAIpAI
        " - - //Antagonist (Orange) - var/isbanned_dept = jobban_isbanned(M, "Syndicate") - jobs += "" - jobs += "" - - counter = 0 - for(var/role in antag_roles) - if(jobban_isbanned(M, role) || isbanned_dept) - jobs += "" - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Antagonist Positions
        [replacetext(role, " ", " ")][replacetext(role, " ", " ")]
        " - - //Other races (BLUE, because I have no idea what other color to make this) - jobs += "" - jobs += "" - - counter = 0 - for(var/role in other_roles) - if(jobban_isbanned(M, role) || isbanned_dept) - jobs += "" - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Other
        [replacetext(role, " ", " ")][replacetext(role, " ", " ")]
        " - - //Whitelisted positions - counter = 0 - jobs += "" - jobs += "" - for(var/jobPos in whitelisted_positions) - if(!jobPos) continue - var/datum/job/job = SSjobs.GetJob(jobPos) - if(!job) continue - - if(jobban_isbanned(M, job.title)) - jobs += "" - counter++ - else - jobs += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - jobs += "" - counter = 0 - jobs += "
        Whitelisted Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " - - body = "[jobs]" - dat = "[header][body]" - usr << browse(dat, "window=jobban2;size=800x490") - return - - //JOBBAN'S INNARDS - else if(href_list["jobban3"]) - if(!check_rights(R_BAN)) return - - var/mob/M = locateUID(href_list["jobban4"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - if(M != usr) //we can jobban ourselves - if(M.client && M.client.holder && (M.client.holder.rights & R_BAN)) //they can ban too. So we can't ban them - alert("You cannot perform this action. You must be of a higher administrative rank!") - return - - var/ban_ckey_param = href_list["dbbanaddckey"] - - if(!SSjobs) - to_chat(usr, "SSjobs has not been setup!") - return - - //get jobs for department if specified, otherwise just returnt he one job in a list. - var/list/joblist = list() - switch(href_list["jobban3"]) - if("commanddept") - for(var/jobPos in command_positions) - if(!jobPos) continue - var/datum/job/temp = SSjobs.GetJob(jobPos) - if(!temp) continue - joblist += temp.title - if("securitydept") - for(var/jobPos in security_positions) - if(!jobPos) continue - var/datum/job/temp = SSjobs.GetJob(jobPos) - if(!temp) continue - joblist += temp.title - if("engineeringdept") - for(var/jobPos in engineering_positions) - if(!jobPos) continue - var/datum/job/temp = SSjobs.GetJob(jobPos) - if(!temp) continue - joblist += temp.title - if("medicaldept") - for(var/jobPos in medical_positions) - if(!jobPos) continue - var/datum/job/temp = SSjobs.GetJob(jobPos) - if(!temp) continue - joblist += temp.title - if("sciencedept") - for(var/jobPos in science_positions) - if(!jobPos) continue - var/datum/job/temp = SSjobs.GetJob(jobPos) - if(!temp) continue - joblist += temp.title - if("supportdept") - for(var/jobPos in support_positions) - if(!jobPos) continue - var/datum/job/temp = SSjobs.GetJob(jobPos) - if(!temp) continue - joblist += temp.title - if("nonhumandept") - joblist += "pAI" - for(var/jobPos in nonhuman_positions) - if(!jobPos) continue - var/datum/job/temp = SSjobs.GetJob(jobPos) - if(!temp) continue - joblist += temp.title - if("whitelistdept") - for(var/jobPos in whitelisted_positions) - if(!jobPos) continue - var/datum/job/temp = SSjobs.GetJob(jobPos) - if(!temp) continue - joblist += temp.title - else - joblist += href_list["jobban3"] - - //Create a list of unbanned jobs within joblist - var/list/notbannedlist = list() - for(var/job in joblist) - if(!jobban_isbanned(M, job)) - notbannedlist += job - - //Banning comes first - if(notbannedlist.len) //at least 1 unbanned job exists in joblist so we have stuff to ban. - switch(alert("Temporary Ban of [M.ckey]?",,"Yes","No", "Cancel")) - if("Yes") - if(config.ban_legacy_system) - to_chat(usr, "Your server is using the legacy banning system, which does not support temporary job bans. Consider upgrading. Aborting ban.") - return - var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null - if(!mins) - return - var/reason = input(usr,"Please state the reason","Reason","") as message|null - if(!reason) - return - - var/msg - M = admin_ban_mobsearch(M, ban_ckey_param, usr) - for(var/job in notbannedlist) - ban_unban_log_save("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes. reason: [reason]") - log_admin("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes") - feedback_inc("ban_job_tmp",1) - DB_ban_record(BANTYPE_JOB_TEMP, M, mins, reason, job) - feedback_add_details("ban_job_tmp","- [job]") - jobban_fullban(M, job, "[reason]; By [usr.ckey] on [time2text(world.realtime)]") //Legacy banning does not support temporary jobbans. - if(!msg) - msg = job - else - msg += ", [job]" - add_note(M.ckey, "Banned from [msg] - [reason]", null, usr.ckey, 0) - message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg] for [mins] minutes", 1) - to_chat(M, "You have been jobbanned by [usr.client.ckey] from: [msg].") - to_chat(M, "The reason is: [reason]") - to_chat(M, "This jobban will be lifted in [mins] minutes.") - href_list["jobban2"] = 1 // lets it fall through and refresh - return 1 - if("No") - var/reason = input(usr,"Please state the reason","Reason","") as message|null - if(reason) - var/msg - M = admin_ban_mobsearch(M, ban_ckey_param, usr) - for(var/job in notbannedlist) - ban_unban_log_save("[key_name(usr)] perma-jobbanned [key_name(M)] from [job]. reason: [reason]") - log_admin("[key_name(usr)] perma-banned [key_name(M)] from [job]") - feedback_inc("ban_job",1) - DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, job) - feedback_add_details("ban_job","- [job]") - jobban_fullban(M, job, "[reason]; By [usr.ckey] on [time2text(world.realtime)]") - if(!msg) msg = job - else msg += ", [job]" - add_note(M.ckey, "Banned from [msg] - [reason]", null, usr.ckey, 0) - message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg]", 1) - to_chat(M, "You have been jobbanned by [usr.client.ckey] from: [msg].") - to_chat(M, "The reason is: [reason]") - to_chat(M, "Jobban can be lifted only upon request.") - href_list["jobban2"] = 1 // lets it fall through and refresh - return 1 - if("Cancel") - return - - //Unbanning joblist - //all jobs in joblist are banned already OR we didn't give a reason (implying they shouldn't be banned) - if(joblist.len) //at least 1 banned job exists in joblist so we have stuff to unban. - if(!config.ban_legacy_system) - to_chat(usr, "Unfortunately, database based unbanning cannot be done through this panel") - DB_ban_panel(M.ckey) - return - var/msg - for(var/job in joblist) - var/reason = jobban_isbanned(M, job) - if(!reason) continue //skip if it isn't jobbanned anyway - switch(alert("Job: '[job]' Reason: '[reason]' Un-jobban?","Please Confirm","Yes","No")) - if("Yes") - ban_unban_log_save("[key_name(usr)] unjobbanned [key_name(M)] from [job]") - log_admin("[key_name(usr)] unbanned [key_name(M)] from [job]") - DB_ban_unban(M.ckey, BANTYPE_JOB_PERMA, job) - feedback_inc("ban_job_unban",1) - feedback_add_details("ban_job_unban","- [job]") - jobban_unban(M, job) - if(!msg) msg = job - else msg += ", [job]" - else - continue - if(msg) - message_admins("[key_name_admin(usr)] unbanned [key_name_admin(M)] from [msg]", 1) - to_chat(M, "You have been un-jobbanned by [usr.client.ckey] from [msg].") - href_list["jobban2"] = 1 // lets it fall through and refresh - return 1 - return 0 //we didn't do anything! - - else if(href_list["boot2"]) - var/mob/M = locateUID(href_list["boot2"]) - if(ismob(M)) - if(M.client && M.client.holder && (M.client.holder.rights & R_BAN)) - to_chat(usr, "[key_name_admin(M)] cannot be kicked from the server.") - return - to_chat(M, "You have been kicked from the server") - log_admin("[key_name(usr)] booted [key_name(M)].") - message_admins("[key_name_admin(usr)] booted [key_name_admin(M)].", 1) - //M.client = null - del(M.client) - - //Player Notes - else if(href_list["addnote"]) - var/target_ckey = href_list["addnote"] - add_note(target_ckey) - - else if(href_list["addnoteempty"]) - add_note() - - else if(href_list["removenote"]) - var/note_id = href_list["removenote"] - remove_note(note_id) - - else if(href_list["editnote"]) - var/note_id = href_list["editnote"] - edit_note(note_id) - - else if(href_list["shownote"]) - var/target = href_list["shownote"] - show_note(index = target) - - else if(href_list["nonalpha"]) - var/target = href_list["nonalpha"] - target = text2num(target) - show_note(index = target) - - else if(href_list["webtools"]) - var/target_ckey = href_list["webtools"] - if(config.forum_playerinfo_url) - var/url_to_open = config.forum_playerinfo_url + target_ckey - if(alert("Open [url_to_open]",,"Yes","No")=="Yes") - usr.client << link(url_to_open) - - else if(href_list["shownoteckey"]) - var/target_ckey = href_list["shownoteckey"] - show_note(target_ckey) - - else if(href_list["notessearch"]) - var/target = href_list["notessearch"] - show_note(index = target) - - else if(href_list["noteedits"]) - var/note_id = sanitizeSQL("[href_list["noteedits"]]") - var/DBQuery/query_noteedits = dbcon.NewQuery("SELECT edits FROM [format_table_name("notes")] WHERE id = '[note_id]'") - if(!query_noteedits.Execute()) - var/err = query_noteedits.ErrorMsg() - log_game("SQL ERROR obtaining edits from notes table. Error : \[[err]\]\n") - return - if(query_noteedits.NextRow()) - var/edit_log = query_noteedits.item[1] - usr << browse(edit_log,"window=noteedits") - - else if(href_list["removejobban"]) - if(!check_rights(R_BAN)) return - - var/t = href_list["removejobban"] - if(t) - if((alert("Do you want to unjobban [t]?","Unjobban confirmation", "Yes", "No") == "Yes") && t) //No more misclicks! Unless you do it twice. - log_admin("[key_name(usr)] removed [t]") - message_admins("[key_name_admin(usr)] removed [t]", 1) - jobban_remove(t) - href_list["ban"] = 1 // lets it fall through and refresh - var/t_split = splittext(t, " - ") - var/key = t_split[1] - var/job = t_split[2] - DB_ban_unban(ckey(key), BANTYPE_JOB_PERMA, job) - - else if(href_list["newban"]) - if(!check_rights(R_BAN)) return - - var/mob/M = locateUID(href_list["newban"]) - if(!ismob(M)) - return - var/ban_ckey_param = href_list["dbbanaddckey"] - - switch(alert("Temporary Ban of [M.ckey] / [ban_ckey_param]?",,"Yes","No", "Cancel")) - if("Yes") - var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null - if(!mins) - return - if(mins >= 525600) mins = 525599 - var/reason = input(usr,"Please state the reason","Reason") as message|null - if(!reason) - return - M = admin_ban_mobsearch(M, ban_ckey_param, usr) - AddBan(M.ckey, M.computer_id, reason, usr.ckey, 1, mins) - ban_unban_log_save("[usr.client.ckey] has banned [M.ckey]. - Reason: [reason] - This will be removed in [mins] minutes.") - to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") - to_chat(M, "This is a temporary ban, it will be removed in [mins] minutes.") - feedback_inc("ban_tmp",1) - DB_ban_record(BANTYPE_TEMP, M, mins, reason) - feedback_inc("ban_tmp_mins",mins) - if(M.client) - M.client.link_forum_account(TRUE) - if(config.banappeals) - to_chat(M, "To try to resolve this matter head to [config.banappeals]") - else - to_chat(M, "No ban appeals URL has been set.") - log_admin("[key_name(usr)] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.") - message_admins("[key_name_admin(usr)] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.") - - del(M.client) - //qdel(M) // See no reason why to delete mob. Important stuff can be lost. And ban can be lifted before round ends. - if("No") - var/reason = input(usr,"Please state the reason","Reason") as message|null - if(!reason) - return - AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP) - to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") - to_chat(M, "This ban does not expire automatically and must be appealed.") - if(M.client) - M.client.link_forum_account(TRUE) - if(config.banappeals) - to_chat(M, "To try to resolve this matter head to [config.banappeals]") - else - to_chat(M, "No ban appeals URL has been set.") - ban_unban_log_save("[usr.client.ckey] has permabanned [M.ckey]. - Reason: [reason] - This ban does not expire automatically and must be appealed.") - log_admin("[key_name(usr)] has banned [M.ckey].\nReason: [reason]\nThis ban does not expire automatically and must be appealed.") - message_admins("[key_name_admin(usr)] has banned [M.ckey].\nReason: [reason]\nThis ban does not expire automatically and must be appealed.") - feedback_inc("ban_perma",1) - DB_ban_record(BANTYPE_PERMA, M, -1, reason) - - del(M.client) - //qdel(M) - if("Cancel") - return - - - //Watchlist - else if(href_list["watchadd"]) - var/target_ckey = href_list["watchadd"] - usr.client.watchlist_add(target_ckey) - - else if(href_list["watchremove"]) - var/target_ckey = href_list["watchremove"] - var/confirm = alert("Are you sure you want to remove [target_ckey] from the watchlist?", "Confirm Watchlist Removal", "Yes", "No") - if(confirm == "Yes") - usr.client.watchlist_remove(target_ckey) - - else if(href_list["watchedit"]) - var/target_ckey = href_list["watchedit"] - usr.client.watchlist_edit(target_ckey) - - else if(href_list["watchaddbrowse"]) - usr.client.watchlist_add(null, 1) - - else if(href_list["watchremovebrowse"]) - var/target_ckey = href_list["watchremovebrowse"] - usr.client.watchlist_remove(target_ckey, 1) - - else if(href_list["watcheditbrowse"]) - var/target_ckey = href_list["watcheditbrowse"] - usr.client.watchlist_edit(target_ckey, 1) - - else if(href_list["watchsearch"]) - var/target_ckey = href_list["watchsearch"] - usr.client.watchlist_show(target_ckey) - - else if(href_list["watchshow"]) - usr.client.watchlist_show() - - else if(href_list["watcheditlog"]) - var/target_ckey = sanitizeSQL("[href_list["watcheditlog"]]") - var/DBQuery/query_watchedits = dbcon.NewQuery("SELECT edits FROM [format_table_name("watch")] WHERE ckey = '[target_ckey]'") - if(!query_watchedits.Execute()) - var/err = query_watchedits.ErrorMsg() - log_game("SQL ERROR obtaining edits from watch table. Error : \[[err]\]\n") - return - if(query_watchedits.NextRow()) - var/edit_log = query_watchedits.item[1] - usr << browse(edit_log,"window=watchedits") - - else if(href_list["mute"]) - if(!check_rights(R_ADMIN|R_MOD)) - return - - var/mob/M = locateUID(href_list["mute"]) - if(!ismob(M)) return - if(!M.client) return - - var/mute_type = href_list["mute_type"] - if(istext(mute_type)) mute_type = text2num(mute_type) - if(!isnum(mute_type)) return - - cmd_admin_mute(M, mute_type) - - else if(href_list["c_mode"]) - if(!check_rights(R_ADMIN)) return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - var/dat = {"What mode do you wish to play?
        "} - for(var/mode in config.modes) - dat += {"[config.mode_names[mode]]
        "} - dat += {"Secret
        "} - dat += {"Random
        "} - dat += {"Now: [master_mode]"} - usr << browse(dat, "window=c_mode") - - else if(href_list["f_secret"]) - if(!check_rights(R_ADMIN)) return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
        "} - for(var/mode in config.modes) - dat += {"[config.mode_names[mode]]
        "} - dat += {"Random (default)
        "} - dat += {"Now: [secret_force_mode]"} - usr << browse(dat, "window=f_secret") - - else if(href_list["c_mode2"]) - if(!check_rights(R_ADMIN|R_SERVER)) return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - master_mode = href_list["c_mode2"] - log_admin("[key_name(usr)] set the mode as [master_mode].") - message_admins("[key_name_admin(usr)] set the mode as [master_mode].", 1) - to_chat(world, "The mode is now: [master_mode]") - Game() // updates the main game menu - world.save_mode(master_mode) - .(href, list("c_mode"=1)) - - else if(href_list["f_secret2"]) - if(!check_rights(R_ADMIN|R_SERVER)) return - - if(SSticker && SSticker.mode) - return alert(usr, "The game has already started.", null, null, null, null) - if(master_mode != "secret") - return alert(usr, "The game mode has to be secret!", null, null, null, null) - secret_force_mode = href_list["f_secret2"] - log_admin("[key_name(usr)] set the forced secret mode as [secret_force_mode].") - message_admins("[key_name_admin(usr)] set the forced secret mode as [secret_force_mode].", 1) - Game() // updates the main game menu - .(href, list("f_secret"=1)) - - else if(href_list["monkeyone"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["monkeyone"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(alert(usr, "Confirm make monkey?",, "Yes", "No") != "Yes") - return - - log_admin("[key_name(usr)] attempting to monkeyize [key_name(H)]") - message_admins("[key_name_admin(usr)] attempting to monkeyize [key_name_admin(H)]", 1) - H.monkeyize() - - - else if(href_list["corgione"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["corgione"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - - if(alert(usr, "Confirm make corgi?",, "Yes", "No") != "Yes") - return - - log_admin("[key_name(usr)] attempting to corgize [key_name(H)]") - message_admins("[key_name_admin(usr)] attempting to corgize [key_name_admin(H)]", 1) - H.corgize() - - else if(href_list["makePAI"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makePAI"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(alert(usr, "Confirm make pai?",, "Yes", "No") != "Yes") - return - - var/painame = "Default" - var/name = "" - if(alert(usr, "Do you want to set their name or let them choose their own name?", "Name Choice", "Set Name", "Let them choose") == "Set Name") - name = sanitize(copytext(input(usr, "Enter a name for the new pAI. Default name is [painame].", "pAI Name", painame),1,MAX_NAME_LEN)) - else - name = sanitize(copytext(input(H, "An admin wants to make you into a pAI. Choose a name. Default is [painame].", "pAI Name", painame),1,MAX_NAME_LEN)) - - if(!name) - name = painame - - log_admin("[key_name(usr)] attempting to pAIze [key_name(H)]") - message_admins("[key_name_admin(usr)] attempting to pAIze [key_name_admin(H)]", 1) - H.paize(name) - - else if(href_list["forcespeech"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - var/mob/M = locateUID(href_list["forcespeech"]) - if(!ismob(M)) - to_chat(usr, "this can only be used on instances of type /mob") - - var/speech = input("What will [key_name(M)] say?.", "Force speech", "")// Don't need to sanitize, since it does that in say(), we also trust our admins. - if(!speech) return - M.say(speech) - speech = sanitize(speech) // Nah, we don't trust them - log_admin("[key_name(usr)] forced [key_name(M)] to say: [speech]") - message_admins("[key_name_admin(usr)] forced [key_name_admin(M)] to say: [speech]") - - else if(href_list["sendtoprison"]) - if(!check_rights(R_ADMIN)) return - - if(alert(usr, "Send to admin prison for the round?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locateUID(href_list["sendtoprison"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") - return - - var/turf/prison_cell = pick(prisonwarp) - if(!prison_cell) return - - var/obj/structure/closet/secure_closet/brig/locker = new /obj/structure/closet/secure_closet/brig(prison_cell) - locker.opened = 0 - locker.locked = 1 - - //strip their stuff and stick it in the crate - for(var/obj/item/I in M) - if(M.unEquip(I)) - I.loc = locker - I.layer = initial(I.layer) - I.plane = initial(I.plane) - I.dropped(M) - M.update_icons() - - //so they black out before warping - M.Paralyse(5) - sleep(5) - if(!M) return - - M.loc = prison_cell - if(istype(M, /mob/living/carbon/human)) - var/mob/living/carbon/human/prisoner = M - prisoner.equip_to_slot_or_del(new /obj/item/clothing/under/color/orange(prisoner), slot_w_uniform) - prisoner.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(prisoner), slot_shoes) - - to_chat(M, "You have been sent to the prison station!") - log_admin("[key_name(usr)] sent [key_name(M)] to the prison station.") - message_admins("[key_name_admin(usr)] sent [key_name_admin(M)] to the prison station.", 1) - - else if(href_list["sendbacktolobby"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locateUID(href_list["sendbacktolobby"]) - - if(!isobserver(M)) - to_chat(usr, "You can only send ghost players back to the Lobby.") - return - - if(!M.client) - to_chat(usr, "[M] doesn't seem to have an active client.") - return - - if(alert(usr, "Send [key_name(M)] back to Lobby?", "Message", "Yes", "No") != "Yes") - return - - log_admin("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] back to the Lobby.") - - var/mob/new_player/NP = new() - GLOB.non_respawnable_keys -= M.ckey - NP.ckey = M.ckey - qdel(M) - - else if(href_list["tdome1"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locateUID(href_list["tdome1"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") - return - - for(var/obj/item/I in M) - M.unEquip(I) - if(I) - I.loc = M.loc - I.layer = initial(I.layer) - I.plane = initial(I.plane) - I.dropped(M) - - M.Paralyse(5) - sleep(5) - M.loc = pick(tdome1) - spawn(50) - to_chat(M, "You have been sent to the Thunderdome.") - log_admin("[key_name(usr)] has sent [key_name(M)] to the thunderdome. (Team 1)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the thunderdome. (Team 1)", 1) - - else if(href_list["tdome2"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locateUID(href_list["tdome2"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") - return - - for(var/obj/item/I in M) - M.unEquip(I) - if(I) - I.loc = M.loc - I.layer = initial(I.layer) - I.plane = initial(I.plane) - I.dropped(M) - - M.Paralyse(5) - sleep(5) - M.loc = pick(tdome2) - spawn(50) - to_chat(M, "You have been sent to the Thunderdome.") - log_admin("[key_name(usr)] has sent [key_name(M)] to the thunderdome. (Team 2)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the thunderdome. (Team 2)", 1) - - else if(href_list["tdomeadmin"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locateUID(href_list["tdomeadmin"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") - return - - M.Paralyse(5) - sleep(5) - M.loc = pick(tdomeadmin) - spawn(50) - to_chat(M, "You have been sent to the Thunderdome.") - log_admin("[key_name(usr)] has sent [key_name(M)] to the thunderdome. (Admin.)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the thunderdome. (Admin.)", 1) - - else if(href_list["tdomeobserve"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locateUID(href_list["tdomeobserve"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") - return - - for(var/obj/item/I in M) - M.unEquip(I) - if(I) - I.loc = M.loc - I.layer = initial(I.layer) - I.plane = initial(I.plane) - I.dropped(M) - - if(istype(M, /mob/living/carbon/human)) - var/mob/living/carbon/human/observer = M - observer.equip_to_slot_or_del(new /obj/item/clothing/under/suit_jacket(observer), slot_w_uniform) - observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(observer), slot_shoes) - M.Paralyse(5) - sleep(5) - M.loc = pick(tdomeobserve) - spawn(50) - to_chat(M, "You have been sent to the Thunderdome.") - log_admin("[key_name(usr)] has sent [key_name(M)] to the thunderdome. (Observer.)") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the thunderdome. (Observer.)", 1) - - else if(href_list["aroomwarp"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") - return - - var/mob/M = locateUID(href_list["aroomwarp"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(istype(M, /mob/living/silicon/ai)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") - return - - M.Paralyse(5) - sleep(5) - M.loc = pick(aroomwarp) - spawn(50) - to_chat(M, "You have been sent to the Admin Room!.") - log_admin("[key_name(usr)] has sent [key_name(M)] to the Admin Room") - message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the Admin Room", 1) - - - else if(href_list["revive"]) - if(!check_rights(R_REJUVINATE)) return - - var/mob/living/L = locateUID(href_list["revive"]) - if(!istype(L)) - to_chat(usr, "This can only be used on instances of type /mob/living") - return - - L.revive() - message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(L)]!", 1) - log_admin("[key_name(usr)] healed / revived [key_name(L)]") - - else if(href_list["makeai"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makeai"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - - if(alert(usr, "Confirm make ai?",, "Yes", "No") != "Yes") - return - - message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!", 1) - log_admin("[key_name(usr)] AIized [key_name(H)]") - var/mob/living/silicon/ai/ai_character = H.AIize() - ai_character.moveToAILandmark() - - else if(href_list["makealien"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makealien"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(alert(usr, "Confirm make alien?",, "Yes", "No") != "Yes") - return - - usr.client.cmd_admin_alienize(H) - - else if(href_list["makeslime"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makeslime"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(alert(usr, "Confirm make slime?",, "Yes", "No") != "Yes") - return - - usr.client.cmd_admin_slimeize(H) - - else if(href_list["makesuper"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makesuper"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - - if(alert(usr, "Confirm make superhero?",, "Yes", "No") != "Yes") - return - - usr.client.cmd_admin_super(H) - - else if(href_list["makerobot"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["makerobot"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(alert(usr, "Confirm make robot?",, "Yes", "No") != "Yes") - return - - usr.client.cmd_admin_robotize(H) - - else if(href_list["makeanimal"]) - if(!check_rights(R_SPAWN)) return - - var/mob/M = locateUID(href_list["makeanimal"]) - if(istype(M, /mob/new_player)) - to_chat(usr, "This cannot be used on instances of type /mob/new_player") - return - if(alert(usr, "Confirm make animal?",, "Yes", "No") != "Yes") - return - - usr.client.cmd_admin_animalize(M) - - else if(href_list["incarn_ghost"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/dead/observer/G = locateUID(href_list["incarn_ghost"]) - if(!istype(G)) - to_chat(usr, "This will only work on /mob/dead/observer") - - var/posttransformoutfit = usr.client.robust_dress_shop() - - var/mob/living/carbon/human/H = G.incarnate_ghost() - - if(posttransformoutfit && istype(H)) - H.equipOutfit(posttransformoutfit) - - log_admin("[key_name(G)] was incarnated by [key_name(owner)]") - message_admins("[key_name_admin(G)] was incarnated by [key_name_admin(owner)]") - - else if(href_list["togmutate"]) - if(!check_rights(R_SPAWN)) return - - var/mob/living/carbon/human/H = locateUID(href_list["togmutate"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - var/block=text2num(href_list["block"]) - //testing("togmutate([href_list["block"]] -> [block])") - usr.client.cmd_admin_toggle_block(H,block) - show_player_panel(H) - //H.regenerate_icons() - - else if(href_list["adminplayeropts"]) - var/mob/M = locateUID(href_list["adminplayeropts"]) - show_player_panel(M) - - else if(href_list["adminplayerobservefollow"]) - var/client/C = usr.client - if(!isobserver(usr)) - if(!check_rights(R_ADMIN|R_MOD)) // Need to be mod or admin to aghost - return - C.admin_ghost() - var/mob/M = locateUID(href_list["adminplayerobservefollow"]) - var/mob/dead/observer/A = C.mob - sleep(2) - A.ManualFollow(M) - - else if(href_list["check_antagonist"]) - check_antagonists() - - else if(href_list["take_question"]) - var/index = text2num(href_list["take_question"]) - - if(href_list["is_mhelp"]) - SSmentor_tickets.takeTicket(index) - else //Ahelp - SStickets.takeTicket(index) - - else if(href_list["resolve"]) - var/index = text2num(href_list["resolve"]) - if(href_list["is_mhelp"]) - SSmentor_tickets.resolveTicket(index) - else //Ahelp - SStickets.resolveTicket(index) - - else if(href_list["autorespond"]) - var/index = text2num(href_list["autorespond"]) - if(!check_rights(R_ADMIN|R_MOD)) - return - SStickets.autoRespond(index) - - else if(href_list["cult_nextobj"]) - if(alert(usr, "Validate the current Cult objective and unlock the next one?", "Cult Cheat Code", "Yes", "No") != "Yes") - return - - if(!GAMEMODE_IS_CULT) - alert("Couldn't locate cult mode datum! This shouldn't ever happen, tell a coder!") - return - - var/datum/game_mode/cult/cult_round = SSticker.mode - cult_round.bypass_phase() - message_admins("Admin [key_name_admin(usr)] has unlocked the Cult's next objective.") - log_admin("Admin [key_name_admin(usr)] has unlocked the Cult's next objective.") - - else if(href_list["cult_mindspeak"]) - var/input = stripped_input(usr, "Communicate to all the cultists with the voice of [SSticker.cultdat.entity_name]", "Voice of [SSticker.cultdat.entity_name]", "") - if(!input) - return - - for(var/datum/mind/H in SSticker.mode.cult) - if (H.current) - to_chat(H.current, "[SSticker.cultdat.entity_name] murmurs, [input]") - - for(var/mob/dead/observer/O in GLOB.player_list) - to_chat(O, "[SSticker.cultdat.entity_name] murmurs, [input]") - - message_admins("Admin [key_name_admin(usr)] has talked with the Voice of [SSticker.cultdat.entity_name].") - log_admin("[key_name(usr)] Voice of [SSticker.cultdat.entity_name]: [input]") - - else if(href_list["adminplayerobservecoodjump"]) - if(!check_rights(R_ADMIN)) return - - var/x = text2num(href_list["X"]) - var/y = text2num(href_list["Y"]) - var/z = text2num(href_list["Z"]) - - var/client/C = usr.client - if(!isobserver(usr)) C.admin_ghost() - sleep(2) - C.jumptocoord(x,y,z) - - else if(href_list["adminchecklaws"]) - output_ai_laws() - - else if(href_list["adminmoreinfo"]) - var/mob/M = locateUID(href_list["adminmoreinfo"]) - admin_mob_info(M) - - else if(href_list["adminspawncookie"]) - if(!check_rights(R_ADMIN|R_EVENT)) return - - var/mob/living/carbon/human/H = locateUID(href_list["adminspawncookie"]) - if(!ishuman(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - - H.equip_to_slot_or_del( new /obj/item/reagent_containers/food/snacks/cookie(H), slot_l_hand ) - if(!(istype(H.l_hand,/obj/item/reagent_containers/food/snacks/cookie))) - H.equip_to_slot_or_del( new /obj/item/reagent_containers/food/snacks/cookie(H), slot_r_hand ) - if(!(istype(H.r_hand,/obj/item/reagent_containers/food/snacks/cookie))) - log_admin("[key_name(H)] has their hands full, so they did not receive their cookie, spawned by [key_name(src.owner)].") - message_admins("[key_name_admin(H)] has [H.p_their()] hands full, so [H.p_they()] did not receive [H.p_their()] cookie, spawned by [key_name_admin(src.owner)].") - return - else - H.update_inv_r_hand()//To ensure the icon appears in the HUD - else - H.update_inv_l_hand() - log_admin("[key_name(H)] got their cookie, spawned by [key_name(src.owner)]") - message_admins("[key_name_admin(H)] got [H.p_their()] cookie, spawned by [key_name_admin(src.owner)]") - feedback_inc("admin_cookies_spawned",1) - to_chat(H, "Your prayers have been answered!! You received the best cookie!") - - else if(href_list["BlueSpaceArtillery"]) - if(!check_rights(R_ADMIN|R_EVENT)) return - - var/mob/living/M = locateUID(href_list["BlueSpaceArtillery"]) - if(!isliving(M)) - to_chat(usr, "This can only be used on instances of type /mob/living") - return - - if(alert(owner, "Are you sure you wish to hit [key_name(M)] with Bluespace Artillery?", "Confirm Firing?" , "Yes" , "No") != "Yes") - return - - if(BSACooldown) - to_chat(owner, "Standby. Reload cycle in progress. Gunnery crews ready in five seconds!") - return - - BSACooldown = 1 - spawn(50) - BSACooldown = 0 - - to_chat(M, "You've been hit by bluespace artillery!") - log_admin("[key_name(M)] has been hit by Bluespace Artillery fired by [key_name(owner)]") - message_admins("[key_name_admin(M)] has been hit by Bluespace Artillery fired by [key_name_admin(owner)]") - - var/turf/simulated/floor/T = get_turf(M) - if(istype(T)) - if(prob(80)) - T.break_tile_to_plating() - else - T.break_tile() - - if(M.health <= 1) - M.gib() - else - M.adjustBruteLoss(min(99,(M.health - 1))) - M.Stun(20) - M.Weaken(20) - M.Stuttering(20) - - else if(href_list["CentcommReply"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locateUID(href_list["CentcommReply"]) - usr.client.admin_headset_message(M, "Centcomm") - - else if(href_list["SyndicateReply"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locateUID(href_list["SyndicateReply"]) - usr.client.admin_headset_message(M, "Syndicate") - - else if(href_list["HeadsetMessage"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locateUID(href_list["HeadsetMessage"]) - usr.client.admin_headset_message(M) - - else if(href_list["EvilFax"]) - if(!check_rights(R_ADMIN)) - return - var/mob/living/carbon/human/H = locateUID(href_list["EvilFax"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - var/etypes = list("Borgification", "Corgification", "Death By Fire", "Total Brain Death", "Honk Tumor", "Cluwne", "Demote", "Demote with Bot", "Revoke Fax Access", "Angry Fax Machine") - var/eviltype = input(src.owner, "Which type of evil fax do you wish to send [H]?","Its good to be baaaad...", "") as null|anything in etypes - if(!(eviltype in etypes)) - return - var/customname = clean_input("Pick a title for the evil fax.", "Fax Title", , owner) - if(!customname) - customname = "paper" - var/obj/item/paper/evilfax/P = new /obj/item/paper/evilfax(null) - var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) - - P.name = "Central Command - [customname]" - P.info = "You really should've known better." - P.myeffect = eviltype - P.mytarget = H - if(alert("Do you want the Evil Fax to activate automatically if [H] tries to ignore it?",,"Yes", "No") == "Yes") - P.activate_on_timeout = 1 - P.x = rand(-2, 0) - P.y = rand(-1, 2) - P.offset_x += P.x - P.offset_y += P.y - P.update_icon() - var/stampvalue = "cent" - var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') - stampoverlay.icon_state = "paper_stamp-[stampvalue]" - stampoverlay.pixel_x = P.x - stampoverlay.pixel_y = P.y - P.stamped = list() - P.stamped += /obj/item/stamp/centcom - if(!P.ico) - P.ico = new - P.ico += "paper_stamp-[stampvalue]" - P.overlays += stampoverlay - P.stamps += "
        " - P.update_icon() - P.faxmachineid = fax.UID() - P.loc = fax.loc // Do not use fax.receivefax(P) here, as it won't preserve the type. Physically teleporting the fax paper is required. - if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) - to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") - to_chat(src.owner, "You sent a [eviltype] fax to [H]") - log_admin("[key_name(src.owner)] sent [key_name(H)] a [eviltype] fax") - message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a [eviltype] fax") - else if(href_list["Bless"]) - if(!check_rights(R_EVENT)) - return - var/mob/living/M = locateUID(href_list["Bless"]) - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob/living") - return - var/btypes = list("To Arrivals", "Moderate Heal") - var/mob/living/carbon/human/H - if(ishuman(M)) - H = M - btypes += "Heal Over Time" - btypes += "Permanent Regeneration" - btypes += "Super Powers" - btypes += "Scarab Guardian" - btypes += "Human Protector" - btypes += "Sentient Pet" - btypes += "All Access" - var/blessing = input(owner, "How would you like to bless [M]?", "Its good to be good...", "") as null|anything in btypes - if(!(blessing in btypes)) - return - var/logmsg = null - switch(blessing) - if("To Arrivals") - M.forceMove(pick(latejoin)) - to_chat(M, "You are abruptly pulled through space!") - logmsg = "a teleport to arrivals." - if("Moderate Heal") - M.adjustBruteLoss(-25) - M.adjustFireLoss(-25) - M.adjustToxLoss(-25) - M.adjustOxyLoss(-25) - to_chat(M,"You feel invigorated!") - logmsg = "a moderate heal." - if("Heal Over Time") - H.reagents.add_reagent("salglu_solution", 30) - H.reagents.add_reagent("salbutamol", 20) - H.reagents.add_reagent("spaceacillin", 20) - logmsg = "a heal over time." - if("Permanent Regeneration") - H.dna.SetSEState(REGENERATEBLOCK, 1) - genemutcheck(H, REGENERATEBLOCK, null, MUTCHK_FORCED) - H.update_mutations() - H.gene_stability = 100 - logmsg = "permanent regeneration." - if("Super Powers") - var/list/default_genes = list(REGENERATEBLOCK, BREATHLESSBLOCK, COLDBLOCK) - for(var/gene in default_genes) - H.dna.SetSEState(gene, 1) - genemutcheck(H, gene, null, MUTCHK_FORCED) - H.update_mutations() - H.gene_stability = 100 - logmsg = "superpowers." - if("Scarab Guardian") - var/obj/item/guardiancreator/biological/scarab = new /obj/item/guardiancreator/biological(H) - var/list/possible_guardians = list("Chaos", "Standard", "Ranged", "Support", "Explosive", "Random") - var/typechoice = input("Select Guardian Type", "Type") as null|anything in possible_guardians - if(isnull(typechoice)) - return - if(typechoice != "Random") - possible_guardians -= "Random" - scarab.possible_guardians = list() - scarab.possible_guardians += typechoice - scarab.attack_self(H) - spawn(700) - qdel(scarab) - logmsg = "scarab guardian." - if("Sentient Pet") - var/pets = subtypesof(/mob/living/simple_animal) - var/petchoice = input("Select pet type", "Pets") as null|anything in pets - if(isnull(petchoice)) - return - var/list/mob/dead/observer/candidates = pollCandidates("Play as the special event pet [H]?", poll_time = 200, min_hours = 10) - var/mob/dead/observer/theghost = null - if(candidates.len) - var/mob/living/simple_animal/pet/P = new petchoice(H.loc) - theghost = pick(candidates) - P.key = theghost.key - P.master_commander = H - P.universal_speak = 1 - P.universal_understand = 1 - P.can_collar = 1 - P.faction = list("neutral") - var/obj/item/clothing/accessory/petcollar/C = new - P.add_collar(C) - var/obj/item/card/id/I = H.wear_id - if(I) - var/obj/item/card/id/D = new /obj/item/card/id(C) - D.access = I.access - D.registered_name = P.name - D.assignment = "Pet" - C.access_id = D - spawn(30) - var/newname = sanitize(copytext(input(P, "You are [P], special event pet of [H]. Change your name to something else?", "Name change", P.name) as null|text,1,MAX_NAME_LEN)) - if(newname && newname != P.name) - P.name = newname - if(P.mind) - P.mind.name = newname - logmsg = "pet ([P])." - else - to_chat(usr, "WARNING: Nobody volunteered to play the special event pet.") - logmsg = "pet (no volunteers)." - if("Human Protector") - usr.client.create_eventmob_for(H, 0) - logmsg = "syndie protector." - if("All Access") - var/obj/item/card/id/I = H.wear_id - if(I) - var/list/access_to_give = get_all_accesses() - for(var/this_access in access_to_give) - if(!(this_access in I.access)) - // don't have it - add it - I.access |= this_access - else - to_chat(usr, "ERROR: [H] is not wearing an ID card.") - logmsg = "all access." - if(logmsg) - log_admin("[key_name(owner)] answered [key_name(M)]'s prayer with a blessing: [logmsg]") - message_admins("[key_name_admin(owner)] answered [key_name_admin(M)]'s prayer with a blessing: [logmsg]") - else if(href_list["Smite"]) - if(!check_rights(R_EVENT)) - return - var/mob/living/M = locateUID(href_list["Smite"]) - var/mob/living/carbon/human/H - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob/living") - return - var/ptypes = list("Lightning bolt", "Fire Death", "Gib") - if(ishuman(M)) - H = M - ptypes += "Brain Damage" - ptypes += "Honk Tumor" - ptypes += "Hallucinate" - ptypes += "Cold" - ptypes += "Hunger" - ptypes += "Cluwne" - ptypes += "Mutagen Cookie" - ptypes += "Hellwater Cookie" - ptypes += "Hunter" - ptypes += "Crew Traitor" - ptypes += "Floor Cluwne" - ptypes += "Shamebrero" - var/punishment = input(owner, "How would you like to smite [M]?", "Its good to be baaaad...", "") as null|anything in ptypes - if(!(punishment in ptypes)) - return - var/logmsg = null - switch(punishment) - if("Lightning bolt") - M.electrocute_act(5, "Lightning Bolt", safety = TRUE, override = TRUE) - playsound(get_turf(M), 'sound/magic/lightningshock.ogg', 50, 1, -1) - M.adjustFireLoss(75) - M.Weaken(5) - to_chat(M, "The gods have punished you for your sins!") - logmsg = "a lightning bolt." - if("Fire Death") - to_chat(M,"You feel hotter than usual. Maybe you should lowe-wait, is that your hand melting?") - var/turf/simulated/T = get_turf(M) - new /obj/effect/hotspot(T) - M.adjustFireLoss(150) - logmsg = "a firey death." - if("Gib") - M.gib(FALSE) - logmsg = "gibbed." - - if("Brain Damage") - H.adjustBrainLoss(75) - logmsg = "75 brain damage." - if("Honk Tumor") - if(!H.get_int_organ(/obj/item/organ/internal/honktumor)) - var/obj/item/organ/internal/organ = new /obj/item/organ/internal/honktumor - to_chat(H, "Life seems funnier, somehow.") - organ.insert(H) - logmsg = "a honk tumor." - if("Hallucinate") - H.Hallucinate(1000) - logmsg = "hallucinations." - if("Cold") - H.reagents.add_reagent("frostoil", 40) - H.reagents.add_reagent("ice", 40) - logmsg = "cold." - if("Hunger") - H.set_nutrition(NUTRITION_LEVEL_CURSED) - logmsg = "starvation." - if("Cluwne") - H.makeCluwne() - H.mutations |= NOCLONE - logmsg = "cluwned." - if("Mutagen Cookie") - var/obj/item/reagent_containers/food/snacks/cookie/evilcookie = new /obj/item/reagent_containers/food/snacks/cookie - evilcookie.reagents.add_reagent("mutagen", 10) - evilcookie.desc = "It has a faint green glow." - evilcookie.bitesize = 100 - evilcookie.flags = NODROP | DROPDEL - H.drop_l_hand() - H.equip_to_slot_or_del(evilcookie, slot_l_hand) - logmsg = "a mutagen cookie." - if("Hellwater Cookie") - var/obj/item/reagent_containers/food/snacks/cookie/evilcookie = new /obj/item/reagent_containers/food/snacks/cookie - evilcookie.reagents.add_reagent("hell_water", 25) - evilcookie.desc = "Sulphur-flavored." - evilcookie.bitesize = 100 - evilcookie.flags = NODROP | DROPDEL - H.drop_l_hand() - H.equip_to_slot_or_del(evilcookie, slot_l_hand) - logmsg = "a hellwater cookie." - if("Hunter") - H.mutations |= NOCLONE - usr.client.create_eventmob_for(H, 1) - logmsg = "hunter." - if("Crew Traitor") - if(!H.mind) - to_chat(usr, "This mob has no mind!") - return - - var/list/possible_traitors = list() - for(var/mob/living/player in GLOB.living_mob_list) - if(player.client && player.mind && player.stat != DEAD && player != H) - if(ishuman(player) && !player.mind.special_role) - if(player.client && (ROLE_TRAITOR in player.client.prefs.be_special) && !jobban_isbanned(player, ROLE_TRAITOR) && !jobban_isbanned(player, "Syndicate")) - possible_traitors += player.mind - - for(var/datum/mind/player in possible_traitors) - if(player.current) - if(ismindshielded(player.current)) - possible_traitors -= player - - if(possible_traitors.len) - var/datum/mind/newtraitormind = pick(possible_traitors) - var/datum/objective/assassinate/kill_objective = new() - kill_objective.target = H.mind - kill_objective.owner = newtraitormind - kill_objective.explanation_text = "Assassinate [H.mind], the [H.mind.assigned_role]" - H.mind.objectives += kill_objective - var/datum/antagonist/traitor/T = new() - T.give_objectives = FALSE - to_chat(newtraitormind, "ATTENTION: It is time to pay your debt to the Syndicate...") - to_chat(newtraitormind, "Goal: KILL [H.real_name], currently in [get_area(H.loc)]") - newtraitormind.add_antag_datum(T) - else - to_chat(usr, "ERROR: Failed to create a traitor.") - return - logmsg = "crew traitor." - - if("Floor Cluwne") - var/turf/T = get_turf(M) - var/mob/living/simple_animal/hostile/floor_cluwne/FC = new /mob/living/simple_animal/hostile/floor_cluwne(T) - FC.smiting = TRUE - FC.Acquire_Victim(M) - logmsg = "floor cluwne" - if("Shamebrero") - if(H.head) - H.unEquip(H.head, TRUE) - var/obj/item/clothing/head/sombrero/shamebrero/S = new(H.loc) - H.equip_to_slot_or_del(S, slot_head) - logmsg = "shamebrero" - if(logmsg) - log_admin("[key_name(owner)] smited [key_name(M)] with: [logmsg]") - message_admins("[key_name_admin(owner)] smited [key_name_admin(M)] with: [logmsg]") - else if(href_list["cryossd"]) - if(!check_rights(R_ADMIN)) - return - var/mob/living/carbon/human/H = locateUID(href_list["cryossd"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(!href_list["cryoafk"] && !isLivingSSD(H)) - to_chat(usr, "This can only be used on living, SSD players.") - return - if(istype(H.loc, /obj/machinery/cryopod)) - var/obj/machinery/cryopod/P = H.loc - P.despawn_occupant() - log_admin("[key_name(usr)] despawned [H.job] [H] in cryo.") - message_admins("[key_name_admin(usr)] despawned [H.job] [H] in cryo.") - else if(cryo_ssd(H)) - log_admin("[key_name(usr)] sent [H.job] [H] to cryo.") - message_admins("[key_name_admin(usr)] sent [H.job] [H] to cryo.") - if(href_list["cryoafk"]) // Warn them if they are send to storage and are AFK - to_chat(H, "The admins have moved you to cryo storage for being AFK. Please eject yourself (right click, eject) out of the cryostorage if you want to avoid being despawned.") - SEND_SOUND(H, 'sound/effects/adminhelp.ogg') - if(H.client) - window_flash(H.client) - else if(href_list["FaxReplyTemplate"]) - if(!check_rights(R_ADMIN)) - return - var/mob/living/carbon/human/H = locateUID(href_list["FaxReplyTemplate"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - var/obj/item/paper/P = new /obj/item/paper(null) - var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) - P.name = "Central Command - paper" - var/stypes = list("Handle it yourselves!","Illegible fax","Fax not signed","Not Right Now","You are wasting our time", "Keep up the good work", "ERT Instructions") - var/stype = input(src.owner, "Which type of standard reply do you wish to send to [H]?","Choose your paperwork", "") as null|anything in stypes - var/tmsg = "



        Nanotrasen Science Station [using_map.station_short]


        NAS Trurl Communications Department Report


        " - if(stype == "Handle it yourselves!") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Please proceed in accordance with Standard Operating Procedure and/or Space Law. You are fully trained to handle this situation without Central Command intervention.

        This is an automatic message." - else if(stype == "Illegible fax") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Your fax's grammar, syntax and/or typography are of a sub-par level and do not allow us to understand the contents of the message.

        Please consult your nearest dictionary and/or thesaurus and try again.

        This is an automatic message." - else if(stype == "Fax not signed") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Your fax has not been correctly signed and, as such, we cannot verify your identity.

        Please sign your faxes before sending them so that we may verify your identity.

        This is an automatic message." - else if(stype == "Not Right Now") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Due to pressing concerns of a matter above your current paygrade, we are unable to provide assistance in whatever matter your fax referenced.

        This can be either due to a power outage, bureaucratic audit, pest infestation, Ascendance Event, corgi outbreak, or any other situation that would affect the proper functioning of the NAS Trurl.

        Please try again later.

        This is an automatic message." - else if(stype == "You are wasting our time") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        In the interest of preventing further mismanagement of company resources, please avoid wasting our time with such petty drivel.

        Do kindly remember that we expect our workforce to maintain at least a semi-decent level of profesionalism. Do not test our patience.

        This is an automatic message." - else if(stype == "Keep up the good work") - tmsg += "Greetings, esteemed crewmember. Your fax has been received successfully by NAS Trurl Fax Registration.

        We at the NAS Trurl appreciate the good work that you have done here, and sincerely recommend that you continue such a display of dedication to the company.

        This is absolutely not an automated message." - else if(stype == "ERT Instructions") - tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Please utilize the Card Swipers if you wish to call for an ERT.

        This is an automated message." - else - return - tmsg += "
        " - P.info = tmsg - P.x = rand(-2, 0) - P.y = rand(-1, 2) - P.offset_x += P.x - P.offset_y += P.y - P.update_icon() - var/stampvalue = "cent" - var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') - stampoverlay.icon_state = "paper_stamp-[stampvalue]" - stampoverlay.pixel_x = P.x - stampoverlay.pixel_y = P.y - P.stamped = list() - P.stamped += /obj/item/stamp/centcom - if(!P.ico) - P.ico = new - P.ico += "paper_stamp-[stampvalue]" - P.overlays += stampoverlay - P.stamps += "
        " - P.update_icon() - fax.receivefax(P) - if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) - to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") - to_chat(src.owner, "You sent a standard '[stype]' fax to [H]") - log_admin("[key_name(src.owner)] sent [key_name(H)] a standard '[stype]' fax") - message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a standard '[stype]' fax") - - else if(href_list["HONKReply"]) - var/mob/living/carbon/human/H = locateUID(href_list["HONKReply"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(!istype(H.l_ear, /obj/item/radio/headset) && !istype(H.r_ear, /obj/item/radio/headset)) - to_chat(usr, "The person you are trying to contact is not wearing a headset") - return - - var/input = input(src.owner, "Please enter a message to reply to [key_name(H)] via [H.p_their()] headset.","Outgoing message from HONKplanet", "") - if(!input) return - - to_chat(src.owner, "You sent [input] to [H] via a secure channel.") - log_admin("[src.owner] replied to [key_name(H)]'s HONKplanet message with the message [input].") - to_chat(H, "You hear something crackle in your headset for a moment before a voice speaks. \"Please stand by for a message from your HONKbrothers. Message as follows, HONK. [input]. Message ends, HONK.\"") - - else if(href_list["ErtReply"]) - if(!check_rights(R_ADMIN)) - return - - if(alert(src.owner, "Accept or Deny ERT request?", "CentComm Response", "Accept", "Deny") == "Deny") - var/mob/living/carbon/human/H = locateUID(href_list["ErtReply"]) - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(H.stat != 0) - to_chat(usr, "The person you are trying to contact is not conscious.") - return - if(!istype(H.l_ear, /obj/item/radio/headset) && !istype(H.r_ear, /obj/item/radio/headset)) - to_chat(usr, "The person you are trying to contact is not wearing a headset") - return - - var/input = input(src.owner, "Please enter a reason for denying [key_name(H)]'s ERT request.","Outgoing message from CentComm", "") - if(!input) return - ert_request_answered = TRUE - to_chat(src.owner, "You sent [input] to [H] via a secure channel.") - log_admin("[src.owner] denied [key_name(H)]'s ERT request with the message [input].") - to_chat(H, "Incoming priority transmission from Central Command. Message as follows, Your ERT request has been denied for the following reasons: [input].") - else - src.owner.response_team() - - - else if(href_list["AdminFaxView"]) - if(!check_rights(R_ADMIN)) - return - - var/obj/item/fax = locate(href_list["AdminFaxView"]) - if(istype(fax, /obj/item/paper)) - var/obj/item/paper/P = fax - P.show_content(usr,1) - else if(istype(fax, /obj/item/photo)) - var/obj/item/photo/H = fax - H.show(usr) - else if(istype(fax, /obj/item/paper_bundle)) - //having multiple people turning pages on a paper_bundle can cause issues - //open a browse window listing the contents instead - var/data = "" - var/obj/item/paper_bundle/B = fax - - for(var/page = 1, page <= B.amount + 1, page++) - var/obj/pageobj = B.contents[page] - data += "Page [page] - [pageobj.name]
        " - - usr << browse(data, "window=PaperBundle[B.UID()]") - else - to_chat(usr, "The faxed item is not viewable. This is probably a bug, and should be reported on the tracker: [fax.type]") - - else if(href_list["AdminFaxViewPage"]) - if(!check_rights(R_ADMIN)) - return - - var/page = text2num(href_list["AdminFaxViewPage"]) - var/obj/item/paper_bundle/bundle = locate(href_list["paper_bundle"]) - - if(!bundle) return - - if(istype(bundle.contents[page], /obj/item/paper)) - var/obj/item/paper/P = bundle.contents[page] - P.show_content(usr, 1) - else if(istype(bundle.contents[page], /obj/item/photo)) - var/obj/item/photo/H = bundle.contents[page] - H.show(usr) - return - - else if(href_list["AdminFaxCreate"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/sender = locate(href_list["AdminFaxCreate"]) - var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) - var/faxtype = href_list["faxtype"] - 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 - - if(!fax) - var/list/departmentoptions = alldepartments + hidden_departments + "All Departments" - destination = input(usr, "To which department?", "Choose a department", "") as null|anything in departmentoptions - if(!destination) - qdel(P) - return - - for(var/obj/machinery/photocopier/faxmachine/F in allfaxes) - if(destination != "All Departments" && F.department == destination) - fax = F - - - var/input = input(src.owner, "Please enter a message to send a fax via secure connection. Use
        for line breaks. Both pencode and HTML work.", "Outgoing message from Centcomm", "") as message|null - if(!input) - qdel(P) - return - input = admin_pencode_to_html(html_encode(input)) // Encode everything from pencode to html - - var/customname = clean_input("Pick a title for the fax.", "Fax Title", , owner) - if(!customname) - customname = "paper" - - var/stampname - var/stamptype - var/stampvalue - var/sendername - switch(faxtype) - if("Central Command") - stamptype = "icon" - stampvalue = "cent" - sendername = command_name() - if("Syndicate") - stamptype = "icon" - stampvalue = "syndicate" - sendername = "UNKNOWN" - if("Administrator") - stamptype = input(src.owner, "Pick a stamp type.", "Stamp Type") as null|anything in list("icon","text","none") - if(stamptype == "icon") - stampname = input(src.owner, "Pick a stamp icon.", "Stamp Icon") as null|anything in list("centcom","syndicate","granted","denied","clown") - switch(stampname) - if("centcom") - stampvalue = "cent" - if("syndicate") - stampvalue = "syndicate" - if("granted") - stampvalue = "ok" - if("denied") - stampvalue = "deny" - if("clown") - stampvalue = "clown" - else if(stamptype == "text") - stampvalue = clean_input("What should the stamp say?", "Stamp Text", , owner) - else if(stamptype == "none") - stamptype = "" - else - qdel(P) - return - - sendername = clean_input("What organization does the fax come from? This determines the prefix of the paper (i.e. Central Command- Title). This is optional.", "Organization", , owner) - - if(sender) - notify = alert(src.owner, "Would you like to inform the original sender that a fax has arrived?","Notify Sender","Yes","No") - - // Create the reply message - if(sendername) - P.name = "[sendername]- [customname]" - else - P.name = "[customname]" - P.info = input - P.update_icon() - P.x = rand(-2, 0) - P.y = rand(-1, 2) - P.offset_x += P.x - P.offset_y += P.y - if(stamptype) - var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') - stampoverlay.pixel_x = P.x - stampoverlay.pixel_y = P.y - - if(!P.ico) - P.ico = new - P.ico += "paper_stamp-[stampvalue]" - stampoverlay.icon_state = "paper_stamp-[stampvalue]" - - if(stamptype == "icon") - if(!P.stamped) - P.stamped = new - P.stamped += /obj/item/stamp/centcom - P.overlays += stampoverlay - P.stamps += "
        " - - else if(stamptype == "text") - if(!P.stamped) - P.stamped = new - P.stamped += /obj/item/stamp - P.overlays += stampoverlay - P.stamps += "
        [stampvalue]" - - if(destination != "All Departments") - if(!fax.receivefax(P)) - to_chat(src.owner, "Message transmission failed.") - return - else - for(var/obj/machinery/photocopier/faxmachine/F in allfaxes) - if(is_station_level(F.z)) - spawn(0) - if(!F.receivefax(P)) - to_chat(src.owner, "Message transmission to [F.department] failed.") - - var/datum/fax/admin/A = new /datum/fax/admin() - A.name = P.name - A.from_department = faxtype - if(destination != "All Departments") - A.to_department = fax.department - else - A.to_department = "All Departments" - A.origin = "Administrator" - A.message = P - A.reply_to = reply_to - A.sent_by = usr - A.sent_at = world.time - - to_chat(src.owner, "Message transmitted successfully.") - if(notify == "Yes") - var/mob/living/carbon/human/H = sender - if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) - to_chat(sender, "Your headset pings, notifying you that a reply to your fax has arrived.") - if(sender) - log_admin("[key_name(src.owner)] replied to a fax message from [key_name(sender)]: [input]") - message_admins("[key_name_admin(src.owner)] replied to a fax message from [key_name_admin(sender)] (VIEW).", 1) - else - log_admin("[key_name(src.owner)] sent a fax message to [destination]: [input]") - message_admins("[key_name_admin(src.owner)] sent a fax message to [destination] (VIEW).", 1) - return - - else if(href_list["refreshfaxpanel"]) - if(!check_rights(R_ADMIN)) - return - - fax_panel(usr) - - else if(href_list["getplaytimewindow"]) - if(!check_rights(R_ADMIN)) - return - var/mob/M = locateUID(href_list["getplaytimewindow"]) - if(!M) - to_chat(usr, "ERROR: Mob not found.") - return - cmd_mentor_show_exp_panel(M.client) - - else if(href_list["jumpto"]) - if(!check_rights(R_ADMIN)) return - - var/mob/M = locateUID(href_list["jumpto"]) - usr.client.jumptomob(M) - - else if(href_list["getmob"]) - if(!check_rights(R_ADMIN)) return - - if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") return - var/mob/M = locateUID(href_list["getmob"]) - usr.client.Getmob(M) - - else if(href_list["sendmob"]) - if(!check_rights(R_ADMIN)) return - - var/mob/M = locateUID(href_list["sendmob"]) - usr.client.sendmob(M) - - else if(href_list["narrateto"]) - if(!check_rights(R_ADMIN)) return - - var/mob/M = locateUID(href_list["narrateto"]) - usr.client.cmd_admin_direct_narrate(M) - - else if(href_list["subtlemessage"]) - if(!check_rights(R_ADMIN)) return - - var/mob/M = locateUID(href_list["subtlemessage"]) - usr.client.cmd_admin_subtle_message(M) - - else if(href_list["traitor"]) - if(!check_rights(R_ADMIN|R_MOD)) return - - if(!SSticker || !SSticker.mode) - alert("The game hasn't started yet!") - return - - var/mob/M = locateUID(href_list["traitor"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.") - return - show_traitor_panel(M) - - else if(href_list["create_object"]) - if(!check_rights(R_SPAWN)) return - return create_object(usr) - - else if(href_list["quick_create_object"]) - if(!check_rights(R_SPAWN)) return - return quick_create_object(usr) - - else if(href_list["create_turf"]) - if(!check_rights(R_SPAWN)) return - return create_turf(usr) - - else if(href_list["create_mob"]) - if(!check_rights(R_SPAWN)) return - return create_mob(usr) - - else if(href_list["object_list"]) //this is the laggiest thing ever - if(!check_rights(R_SPAWN)) return - - var/atom/loc = usr.loc - - var/dirty_paths - if(istext(href_list["object_list"])) - dirty_paths = list(href_list["object_list"]) - else if(istype(href_list["object_list"], /list)) - dirty_paths = href_list["object_list"] - - var/paths = list() - - for(var/dirty_path in dirty_paths) - var/path = text2path(dirty_path) - if(!path) - continue - else if(!ispath(path, /obj) && !ispath(path, /turf) && !ispath(path, /mob)) - continue - paths += path - - if(!paths) - alert("The path list you sent is empty") - return - if(length(paths) > 5) - alert("Select fewer object types, (max 5)") - return - - var/list/offset = splittext(href_list["offset"],",") - var/number = dd_range(1, 100, text2num(href_list["object_count"])) - var/X = offset.len > 0 ? text2num(offset[1]) : 0 - var/Y = offset.len > 1 ? text2num(offset[2]) : 0 - var/Z = offset.len > 2 ? text2num(offset[3]) : 0 - var/tmp_dir = href_list["object_dir"] - var/obj_dir = tmp_dir ? text2num(tmp_dir) : 2 - if(!obj_dir || !(obj_dir in list(1,2,4,8,5,6,9,10))) - obj_dir = 2 - var/obj_name = sanitize(href_list["object_name"]) - - - var/atom/target //Where the object will be spawned - var/where = href_list["object_where"] - if(!( where in list("onfloor","inhand","inmarked") )) - where = "onfloor" - - - switch(where) - if("inhand") - if(!iscarbon(usr) && !isrobot(usr)) - to_chat(usr, "Can only spawn in hand when you're a carbon mob or cyborg.") - where = "onfloor" - target = usr - - if("onfloor") - switch(href_list["offset_type"]) - if("absolute") - target = locate(0 + X,0 + Y,0 + Z) - if("relative") - target = locate(loc.x + X,loc.y + Y,loc.z + Z) - if("inmarked") - if(!marked_datum) - to_chat(usr, "You don't have any object marked. Abandoning spawn.") - return - else if(!istype(marked_datum,/atom)) - to_chat(usr, "The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.") - return - else - target = marked_datum - - if(target) - for(var/path in paths) - for(var/i = 0; i < number; i++) - if(path in typesof(/turf)) - var/turf/O = target - var/turf/N = O.ChangeTurf(path) - if(N && obj_name) - N.name = obj_name - else - var/atom/O = new path(target) - if(O) - O.admin_spawned = TRUE - O.dir = obj_dir - if(obj_name) - O.name = obj_name - if(istype(O,/mob)) - var/mob/M = O - M.real_name = obj_name - if(where == "inhand" && isliving(usr) && istype(O, /obj/item)) - var/mob/living/L = usr - var/obj/item/I = O - L.put_in_hands(I) - if(isrobot(L)) - var/mob/living/silicon/robot/R = L - if(R.module) - R.module.modules += I - I.loc = R.module - R.module.rebuild() - R.activate_module(I) - R.module.fix_modules() - - if(number == 1) - log_admin("[key_name(usr)] created a [english_list(paths)]") - for(var/path in paths) - if(ispath(path, /mob)) - message_admins("[key_name_admin(usr)] created a [english_list(paths)]") - break - else - log_admin("[key_name(usr)] created [number]ea [english_list(paths)]") - for(var/path in paths) - if(ispath(path, /mob)) - message_admins("[key_name_admin(usr)] created [number]ea [english_list(paths)]") - break - return - - else if(href_list["kick_all_from_lobby"]) - if(!check_rights(R_ADMIN)) - return - if(SSticker && SSticker.current_state == GAME_STATE_PLAYING) - var/afkonly = text2num(href_list["afkonly"]) - if(alert("Are you sure you want to kick all [afkonly ? "AFK" : ""] clients from the lobby?","Confirmation","Yes","Cancel") != "Yes") - return - var/list/listkicked = kick_clients_in_lobby("You were kicked from the lobby by an Administrator.", afkonly) - - var/strkicked = "" - for(var/name in listkicked) - strkicked += "[name], " - message_admins("[key_name_admin(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") - log_admin("[key_name(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") - else - to_chat(usr, "You may only use this when the game is running.") - - else if(href_list["memoeditlist"]) - if(!check_rights(R_SERVER)) return - var/sql_key = sanitizeSQL("[href_list["memoeditlist"]]") - var/DBQuery/query_memoedits = dbcon.NewQuery("SELECT edits FROM [format_table_name("memo")] WHERE (ckey = '[sql_key]')") - if(!query_memoedits.Execute()) - var/err = query_memoedits.ErrorMsg() - log_game("SQL ERROR obtaining edits from memo table. Error : \[[err]\]\n") - return - if(query_memoedits.NextRow()) - var/edit_log = query_memoedits.item[1] - usr << browse(edit_log,"window=memoeditlist") - - else if(href_list["secretsfun"]) - if(!check_rights(R_SERVER|R_EVENT)) return - - var/ok = 0 - switch(href_list["secretsfun"]) - if("sec_clothes") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","SC") - for(var/obj/item/clothing/under/O in world) - qdel(O) - ok = 1 - if("sec_all_clothes") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","SAC") - for(var/obj/item/clothing/O in world) - qdel(O) - ok = 1 - if("sec_classic1") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","SC1") - for(var/obj/item/clothing/suit/fire/O in world) - qdel(O) - for(var/obj/structure/grille/O in world) - qdel(O) - if("monkey") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","M") - for(var/mob/living/carbon/human/H in GLOB.mob_list) - spawn(0) - H.monkeyize() - ok = 1 - if("corgi") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","M") - for(var/mob/living/carbon/human/H in GLOB.mob_list) - spawn(0) - H.corgize() - ok = 1 - if("honksquad") - if(usr.client.honksquad()) - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","HONK") - if("striketeam") - if(usr.client.strike_team()) - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","Strike") - if("striketeam_syndicate") - if(usr.client.syndicate_strike_team()) - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","Strike") - if("infiltrators_syndicate") - if(usr.client.syndicate_infiltration_team()) - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","SyndieInfiltrationTeam") - if("gimmickteam") - if(usr.client.gimmick_team()) - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","GimmickTeam") - if("tripleAI") - usr.client.triple_ai() - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","TriAI") - if("gravity") - if(!(SSticker && SSticker.mode)) - to_chat(usr, "Please wait until the game starts! Not sure how it will work otherwise.") - return - gravity_is_on = !gravity_is_on - for(var/area/A in world) - A.gravitychange(gravity_is_on,A) - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","Grav") - if(gravity_is_on) - log_admin("[key_name(usr)] toggled gravity on.", 1) - message_admins("[key_name_admin(usr)] toggled gravity on.", 1) - event_announcement.Announce("Gravity generators are again functioning within normal parameters. Sorry for any inconvenience.") - else - log_admin("[key_name(usr)] toggled gravity off.", 1) - message_admins("[key_name_admin(usr)] toggled gravity off.", 1) - event_announcement.Announce("Feedback surge detected in mass-distributions systems. Artifical gravity has been disabled whilst the system reinitializes. Further failures may result in a gravitational collapse and formation of blackholes. Have a nice day.") - - if("power") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","P") - log_admin("[key_name(usr)] made all areas powered", 1) - message_admins("[key_name_admin(usr)] made all areas powered", 1) - power_restore() - if("unpower") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","UP") - log_admin("[key_name(usr)] made all areas unpowered", 1) - message_admins("[key_name_admin(usr)] made all areas unpowered", 1) - power_failure() - if("quickpower") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","QP") - log_admin("[key_name(usr)] made all SMESs powered", 1) - message_admins("[key_name_admin(usr)] made all SMESs powered", 1) - power_restore_quick() - if("prisonwarp") - if(!SSticker) - alert("The game hasn't started yet!", null, null, null, null, null) - return - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","PW") - message_admins("[key_name_admin(usr)] teleported all players to the prison station.", 1) - for(var/mob/living/carbon/human/H in GLOB.mob_list) - var/turf/loc = find_loc(H) - var/security = 0 - if(!is_station_level(loc.z) || prisonwarped.Find(H)) - -//don't warp them if they aren't ready or are already there - continue - H.Paralyse(5) - if(H.wear_id) - var/obj/item/card/id/id = H.get_idcard() - for(var/A in id.access) - if(A == ACCESS_SECURITY) - security++ - if(!security) - //strip their stuff before they teleport into a cell :downs: - for(var/obj/item/W in H) - if(istype(W, /obj/item/organ/external)) - continue - //don't strip organs - H.unEquip(W) - if(H.client) - H.client.screen -= W - if(W) - W.loc = H.loc - W.dropped(H) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - //teleport person to cell - H.loc = pick(prisonwarp) - H.equip_to_slot_or_del(new /obj/item/clothing/under/color/orange(H), slot_w_uniform) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(H), slot_shoes) - else - //teleport security person - H.loc = pick(prisonsecuritywarp) - prisonwarped += H - if("traitor_all") - if(!SSticker) - alert("The game hasn't started yet!") - return - var/objective = sanitize(copytext(input("Enter an objective"),1,MAX_MESSAGE_LEN)) - if(!objective) - return - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","TA([objective])") - - for(var/mob/living/carbon/human/H in GLOB.player_list) - if(H.stat == 2 || !H.client || !H.mind) continue - if(is_special_character(H)) continue - //traitorize(H, objective, 0) - H.mind.add_antag_datum(/datum/antagonist/traitor) - - for(var/mob/living/silicon/A in GLOB.player_list) - A.mind.add_antag_datum(/datum/antagonist/traitor) - - message_admins("[key_name_admin(usr)] used everyone is a traitor secret. Objective is [objective]", 1) - log_admin("[key_name(usr)] used everyone is a traitor secret. Objective is [objective]") - - if("togglebombcap") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","BC") - - var/newBombCap = input(usr,"What would you like the new bomb cap to be. (entered as the light damage range (the 3rd number in common (1,2,3) notation)) Must be between 4 and 128)", "New Bomb Cap", MAX_EX_LIGHT_RANGE) as num|null - if(newBombCap < 4) - return - if(newBombCap > 128) - newBombCap = 128 - - MAX_EX_DEVASTATION_RANGE = round(newBombCap/4) - MAX_EX_HEAVY_RANGE = round(newBombCap/2) - MAX_EX_LIGHT_RANGE = newBombCap - //I don't know why these are their own variables, but fuck it, they are. - MAX_EX_FLASH_RANGE = newBombCap - MAX_EX_FLAME_RANGE = newBombCap - - message_admins("[key_name_admin(usr)] changed the bomb cap to [MAX_EX_DEVASTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]") - log_admin("[key_name(usr)] changed the bomb cap to [MAX_EX_DEVASTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]") - - if("flicklights") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","FL") - while(!usr.stat) -//knock yourself out to stop the ghosts - for(var/mob/M in GLOB.player_list) - if(M.stat != 2 && prob(25)) - var/area/AffectedArea = get_area(M) - if(AffectedArea.name != "Space" && AffectedArea.name != "Engine Walls" && AffectedArea.name != "Chemical Lab Test Chamber" && AffectedArea.name != "Escape Shuttle" && AffectedArea.name != "Arrival Area" && AffectedArea.name != "Arrival Shuttle" && AffectedArea.name != "start area" && AffectedArea.name != "Engine Combustion Chamber") - AffectedArea.power_light = 0 - AffectedArea.power_change() - spawn(rand(55,185)) - AffectedArea.power_light = 1 - AffectedArea.power_change() - var/Message = rand(1,4) - switch(Message) - if(1) - M.show_message(text("You shudder as if cold..."), 1) - if(2) - M.show_message(text("You feel something gliding across your back..."), 1) - if(3) - M.show_message(text("Your eyes twitch, you feel like something you can't see is here..."), 1) - if(4) - M.show_message(text("You notice something moving out of the corner of your eye, but nothing is there..."), 1) - for(var/obj/W in orange(5,M)) - if(prob(25) && !W.anchored) - step_rand(W) - sleep(rand(100,1000)) - for(var/mob/M in GLOB.player_list) - if(M.stat != 2) - M.show_message(text("The chilling wind suddenly stops..."), 1) - if("lightout") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","LO") - message_admins("[key_name_admin(usr)] has broke a lot of lights", 1) - var/datum/event/electrical_storm/E = new /datum/event/electrical_storm - E.lightsoutAmount = 2 - if("blackout") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","BO") - message_admins("[key_name_admin(usr)] broke all lights", 1) - for(var/obj/machinery/light/L in GLOB.machines) - L.break_light_tube() - if("whiteout") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","WO") - message_admins("[key_name_admin(usr)] fixed all lights", 1) - for(var/obj/machinery/light/L in GLOB.machines) - L.fix() - if("floorlava") - feedback_inc("admin_secrets_fun_used", 1) - feedback_add_details("admin_secrets_fun_used", "LF") - var/sure = alert(usr, "Are you sure you want to do this?", "Confirmation", "Yes", "No") - if(sure == "No") - return - SSweather.run_weather(/datum/weather/floor_is_lava) - message_admins("[key_name_admin(usr)] made the floor lava") - if("fakelava") - feedback_inc("admin_secrets_fun_used", 1) - feedback_add_details("admin_secrets_fun_used", "LZ") - var/sure = alert(usr, "Are you sure you want to do this?", "Confirmation", "Yes", "No") - if(sure == "No") - return - SSweather.run_weather(/datum/weather/floor_is_lava/fake) - message_admins("[key_name_admin(usr)] made aesthetic lava on the floor") - if("weatherashstorm") - feedback_inc("admin_secrets_fun_used", 1) - feedback_add_details("admin_secrets_fun_used", "WA") - var/sure = alert(usr, "Are you sure you want to do this?", "Confirmation", "Yes", "No") - if(sure == "No") - return - SSweather.run_weather(/datum/weather/ash_storm) - message_admins("[key_name_admin(usr)] spawned an ash storm on the mining level") - if("retardify") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","RET") - for(var/mob/living/carbon/human/H in GLOB.player_list) - to_chat(H, "You suddenly feel stupid.") - H.setBrainLoss(60) - message_admins("[key_name_admin(usr)] made everybody retarded") - if("fakeguns") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","FG") - for(var/obj/item/W in world) - if(istype(W, /obj/item/clothing) || istype(W, /obj/item/card/id) || istype(W, /obj/item/disk) || istype(W, /obj/item/tank)) - continue - W.icon = 'icons/obj/guns/projectile.dmi' - W.icon_state = "revolver" - W.item_state = "gun" - message_admins("[key_name_admin(usr)] made every item look like a gun") - if("schoolgirl") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","SG") - for(var/obj/item/clothing/under/W in world) - W.icon_state = "schoolgirl" - W.item_state = "w_suit" - W.item_color = "schoolgirl" - message_admins("[key_name_admin(usr)] activated Japanese Animes mode") - world << sound('sound/AI/animes.ogg') - if("eagles")//SCRAW - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","EgL") - for(var/obj/machinery/door/airlock/W in GLOB.airlocks) - if(is_station_level(W.z) && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison)) - W.req_access = list() - message_admins("[key_name_admin(usr)] activated Egalitarian Station mode") - event_announcement.Announce("Centcomm airlock control override activated. Please take this time to get acquainted with your coworkers.", new_sound = 'sound/AI/commandreport.ogg') - if("onlyone") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","OO") - usr.client.only_one() -// message_admins("[key_name_admin(usr)] has triggered HIGHLANDER") - if("onlyme") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","OM") - usr.client.only_me() - if("onlyoneteam") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","OOT") - usr.client.only_one_team() -// message_admins("[key_name_admin(usr)] has triggered ") - if("rolldice") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","ROL") - usr.client.roll_dices() - if("guns") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","SG") - var/survivor_probability = 0 - switch(alert("Do you want this to create survivors antagonists?", , "No Antags", "Some Antags", "All Antags!")) - if("Some Antags") - survivor_probability = 25 - if("All Antags!") - survivor_probability = 100 - - rightandwrong(SUMMON_GUNS, usr, survivor_probability) - if("magic") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","SM") - var/survivor_probability = 0 - switch(alert("Do you want this to create survivors antagonists?", , "No Antags", "Some Antags", "All Antags!")) - if("Some Antags") - survivor_probability = 25 - if("All Antags!") - survivor_probability = 100 - - rightandwrong(SUMMON_MAGIC, usr, survivor_probability) - if("tdomereset") - var/delete_mobs = alert("Clear all mobs?","Confirm","Yes","No","Cancel") - if(delete_mobs == "Cancel") - return - - var/area/thunderdome = locate(/area/tdome/arena) - if(delete_mobs == "Yes") - for(var/mob/living/mob in thunderdome) - qdel(mob) //Clear mobs - for(var/obj/obj in thunderdome) - if(!istype(obj,/obj/machinery/camera)) - qdel(obj) //Clear objects - - var/area/template = locate(/area/tdome/arena_source) - template.copy_contents_to(thunderdome) - - log_admin("[key_name(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1) - message_admins("[key_name_admin(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].") - - if("tdomestart") - var/confirmation = alert("Start a Thunderdome match?","Confirm","Yes","No") - if(confirmation == "No") - return - if(makeThunderdomeTeams()) - log_admin("[key_name(usr)] started a Thunderdome match!", 1) - message_admins("[key_name_admin(usr)] has started a Thunderdome match!") - else - log_admin("[key_name(usr)] attempted to start a Thunderdome match, but no ghosts signed up.", 1) - message_admins("[key_name_admin(usr)] tried starting a Thunderdome match, but no ghosts signed up.") - if("securitylevel0") - set_security_level(0) - message_admins("[key_name_admin(usr)] change security level to Green.", 1) - if("securitylevel1") - set_security_level(1) - message_admins("[key_name_admin(usr)] change security level to Blue.", 1) - if("securitylevel2") - set_security_level(2) - message_admins("[key_name_admin(usr)] change security level to Red.", 1) - if("securitylevel3") - set_security_level(3) - message_admins("[key_name_admin(usr)] change security level to Gamma.", 1) - if("securitylevel4") - set_security_level(4) - message_admins("[key_name_admin(usr)] change security level to Epsilon.", 1) - if("securitylevel5") - set_security_level(5) - message_admins("[key_name_admin(usr)] change security level to Delta.", 1) - if("moveminingshuttle") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","ShM") - if(!SSshuttle.toggleShuttle("mining","mining_home","mining_away")) - message_admins("[key_name_admin(usr)] moved mining shuttle") - log_admin("[key_name(usr)] moved the mining shuttle") - - if("movelaborshuttle") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","ShL") - if(!SSshuttle.toggleShuttle("laborcamp","laborcamp_home","laborcamp_away")) - message_admins("[key_name_admin(usr)] moved labor shuttle") - log_admin("[key_name(usr)] moved the labor shuttle") - - if("moveferry") - feedback_inc("admin_secrets_fun_used",1) - feedback_add_details("admin_secrets_fun_used","ShF") - if(!SSshuttle.toggleShuttle("ferry","ferry_home","ferry_away")) - message_admins("[key_name_admin(usr)] moved the centcom ferry") - log_admin("[key_name(usr)] moved the centcom ferry") - - if(usr) - log_admin("[key_name(usr)] used secret [href_list["secretsfun"]]") - if(ok) - to_chat(world, text("A secret has been activated by []!", usr.key)) - - else if(href_list["secretsadmin"]) - if(!check_rights(R_ADMIN)) return - - var/ok = 0 - switch(href_list["secretsadmin"]) - if("list_signalers") - var/dat = "Showing last [length(lastsignalers)] signalers.
        " - for(var/sig in lastsignalers) - dat += "[sig]
        " - usr << browse(dat, "window=lastsignalers;size=800x500") - if("list_lawchanges") - var/dat = "Showing last [length(lawchanges)] law changes.
        " - for(var/sig in lawchanges) - dat += "[sig]
        " - usr << browse(dat, "window=lawchanges;size=800x500") - if("list_job_debug") - var/dat = "Job Debug info.
        " - if(SSjobs) - for(var/line in SSjobs.job_debug) - dat += "[line]
        " - dat+= "*******

        " - for(var/datum/job/job in SSjobs.occupations) - if(!job) continue - dat += "job: [job.title], current_positions: [job.current_positions], total_positions: [job.total_positions]
        " - usr << browse(dat, "window=jobdebug;size=600x500") - if("showailaws") - output_ai_laws() - if("showgm") - if(!SSticker) - alert("The game hasn't started yet!") - else if(SSticker.mode) - alert("The game mode is [SSticker.mode.name]") - else alert("For some reason there's a ticker, but not a game mode") - if("manifest") - var/dat = "Showing Crew Manifest.
        " - dat += "" - for(var/mob/living/carbon/human/H in GLOB.mob_list) - if(H.ckey) - dat += text("", H.name, H.get_assignment()) - dat += "
        NamePosition
        [][]
        " - usr << browse(dat, "window=manifest;size=440x410") - if("check_antagonist") - check_antagonists() - if("DNA") - var/dat = "Showing DNA from blood.
        " - dat += "" - for(var/mob/living/carbon/human/H in GLOB.mob_list) - if(H.dna && H.ckey) - dat += "" - dat += "
        NameDNABlood Type
        [H][H.dna.unique_enzymes][H.dna.blood_type]
        " - usr << browse(dat, "window=DNA;size=440x410") - if("fingerprints") - var/dat = "Showing Fingerprints.
        " - dat += "" - for(var/mob/living/carbon/human/H in GLOB.mob_list) - if(H.ckey) - if(H.dna && H.dna.uni_identity) - dat += "" - else if(H.dna && !H.dna.uni_identity) - dat += "" - else if(!H.dna) - dat += "" - dat += "
        NameFingerprints
        [H][md5(H.dna.uni_identity)]
        [H]H.dna.uni_identity = null
        [H]H.dna = null
        " - usr << browse(dat, "window=fingerprints;size=440x410") - if("night_shift_set") - var/val = alert(usr, "What do you want to set night shift to? This will override the automatic system until set to automatic again.", "Night Shift", "On", "Off", "Automatic") - switch(val) - if("Automatic") - if(config.enable_night_shifts) - SSnightshift.can_fire = TRUE - SSnightshift.fire() - else - SSnightshift.update_nightshift(FALSE, TRUE) - to_chat(usr, "Night shift set to automatic.") - if("On") - SSnightshift.can_fire = FALSE - SSnightshift.update_nightshift(TRUE, FALSE) - to_chat(usr, "Night shift forced on.") - if("Off") - SSnightshift.can_fire = FALSE - SSnightshift.update_nightshift(FALSE, FALSE) - to_chat(usr, "Night shift forced off.") - else - if(usr) - log_admin("[key_name(usr)] used secret [href_list["secretsadmin"]]") - if(ok) - to_chat(world, text("A secret has been activated by []!", usr.key)) - - else if(href_list["secretscoder"]) - if(!check_rights(R_DEBUG)) return - - switch(href_list["secretscoder"]) - if("spawn_objects") - var/dat = "Admin Log
        " - for(var/l in admin_log) - dat += "
      • [l]
      • " - if(!admin_log.len) - dat += "No-one has done anything this round!" - usr << browse(dat, "window=admin_log") - if("maint_ACCESS_BRIG") - for(var/obj/machinery/door/airlock/maintenance/M in GLOB.airlocks) - if(ACCESS_MAINT_TUNNELS in M.req_access) - M.req_access = list(ACCESS_BRIG) - message_admins("[key_name_admin(usr)] made all maint doors brig access-only.") - if("maint_access_engiebrig") - for(var/obj/machinery/door/airlock/maintenance/M in GLOB.airlocks) - if(ACCESS_MAINT_TUNNELS in M.req_access) - M.req_access = list() - M.req_one_access = list(ACCESS_BRIG,ACCESS_ENGINE) - message_admins("[key_name_admin(usr)] made all maint doors engineering and brig access-only.") - if("infinite_sec") - var/datum/job/J = SSjobs.GetJob("Security Officer") - if(!J) return - J.total_positions = -1 - J.spawn_positions = -1 - message_admins("[key_name_admin(usr)] has removed the cap on security officers.") - - else if(href_list["ac_view_wanted"]) //Admin newscaster Topic() stuff be here - src.admincaster_screen = 18 //The ac_ prefix before the hrefs stands for AdminCaster. - src.access_news_network() - - else if(href_list["ac_set_channel_name"]) - src.admincaster_feed_channel.channel_name = strip_html_simple(input(usr, "Provide a Feed Channel Name", "Network Channel Handler", "")) - while(findtext(src.admincaster_feed_channel.channel_name," ") == 1) - src.admincaster_feed_channel.channel_name = copytext(src.admincaster_feed_channel.channel_name,2,length(src.admincaster_feed_channel.channel_name)+1) - src.access_news_network() - - else if(href_list["ac_set_channel_lock"]) - src.admincaster_feed_channel.locked = !src.admincaster_feed_channel.locked - src.access_news_network() - - else if(href_list["ac_submit_new_channel"]) - var/check = 0 - for(var/datum/feed_channel/FC in news_network.network_channels) - if(FC.channel_name == src.admincaster_feed_channel.channel_name) - check = 1 - break - if(src.admincaster_feed_channel.channel_name == "" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]" || check ) - src.admincaster_screen=7 - else - var/choice = alert("Please confirm Feed channel creation","Network Channel Handler","Confirm","Cancel") - if(choice=="Confirm") - var/datum/feed_channel/newChannel = new /datum/feed_channel - newChannel.channel_name = src.admincaster_feed_channel.channel_name - newChannel.author = src.admincaster_signature - newChannel.locked = src.admincaster_feed_channel.locked - newChannel.is_admin_channel = 1 - feedback_inc("newscaster_channels",1) - news_network.network_channels += newChannel //Adding channel to the global network - log_admin("[key_name_admin(usr)] created command feed channel: [src.admincaster_feed_channel.channel_name]!") - src.admincaster_screen=5 - src.access_news_network() - - else if(href_list["ac_set_channel_receiving"]) - var/list/available_channels = list() - for(var/datum/feed_channel/F in news_network.network_channels) - available_channels += F.channel_name - src.admincaster_feed_channel.channel_name = adminscrub(input(usr, "Choose receiving Feed Channel", "Network Channel Handler") in available_channels ) - src.access_news_network() - - else if(href_list["ac_set_new_message"]) - src.admincaster_feed_message.body = adminscrub(input(usr, "Write your Feed story", "Network Channel Handler", "")) - while(findtext(src.admincaster_feed_message.body," ") == 1) - src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.body,2,length(src.admincaster_feed_message.body)+1) - src.access_news_network() - - else if(href_list["ac_submit_new_message"]) - if(src.admincaster_feed_message.body =="" || src.admincaster_feed_message.body =="\[REDACTED\]" || src.admincaster_feed_channel.channel_name == "" ) - src.admincaster_screen = 6 - else - var/datum/feed_message/newMsg = new /datum/feed_message - newMsg.author = src.admincaster_signature - newMsg.body = src.admincaster_feed_message.body - newMsg.is_admin_message = 1 - feedback_inc("newscaster_stories",1) - for(var/datum/feed_channel/FC in news_network.network_channels) - if(FC.channel_name == src.admincaster_feed_channel.channel_name) - FC.messages += newMsg //Adding message to the network's appropriate feed_channel - break - src.admincaster_screen=4 - - for(var/obj/machinery/newscaster/NEWSCASTER in allCasters) - NEWSCASTER.newsAlert(src.admincaster_feed_channel.channel_name) - - log_admin("[key_name_admin(usr)] submitted a feed story to channel: [src.admincaster_feed_channel.channel_name]!") - src.access_news_network() - - else if(href_list["ac_create_channel"]) - src.admincaster_screen=2 - src.access_news_network() - - else if(href_list["ac_create_feed_story"]) - src.admincaster_screen=3 - src.access_news_network() - - else if(href_list["ac_menu_censor_story"]) - src.admincaster_screen=10 - src.access_news_network() - - else if(href_list["ac_menu_censor_channel"]) - src.admincaster_screen=11 - src.access_news_network() - - else if(href_list["ac_menu_wanted"]) - var/already_wanted = 0 - if(news_network.wanted_issue) - already_wanted = 1 - - if(already_wanted) - src.admincaster_feed_message.author = news_network.wanted_issue.author - src.admincaster_feed_message.body = news_network.wanted_issue.body - src.admincaster_screen = 14 - src.access_news_network() - - else if(href_list["ac_set_wanted_name"]) - src.admincaster_feed_message.author = adminscrub(input(usr, "Provide the name of the Wanted person", "Network Security Handler", "")) - while(findtext(src.admincaster_feed_message.author," ") == 1) - src.admincaster_feed_message.author = copytext(admincaster_feed_message.author,2,length(admincaster_feed_message.author)+1) - src.access_news_network() - - else if(href_list["ac_set_wanted_desc"]) - src.admincaster_feed_message.body = adminscrub(input(usr, "Provide the a description of the Wanted person and any other details you deem important", "Network Security Handler", "")) - while(findtext(src.admincaster_feed_message.body," ") == 1) - src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.body,2,length(src.admincaster_feed_message.body)+1) - src.access_news_network() - - else if(href_list["ac_submit_wanted"]) - var/input_param = text2num(href_list["ac_submit_wanted"]) - if(src.admincaster_feed_message.author == "" || src.admincaster_feed_message.body == "") - src.admincaster_screen = 16 - else - var/choice = alert("Please confirm Wanted Issue [(input_param==1) ? ("creation.") : ("edit.")]","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - if(input_param==1) //If input_param == 1 we're submitting a new wanted issue. At 2 we're just editing an existing one. See the else below - var/datum/feed_message/WANTED = new /datum/feed_message - WANTED.author = src.admincaster_feed_message.author //Wanted name - WANTED.body = src.admincaster_feed_message.body //Wanted desc - WANTED.backup_author = src.admincaster_signature //Submitted by - WANTED.is_admin_message = 1 - news_network.wanted_issue = WANTED - for(var/obj/machinery/newscaster/NEWSCASTER in allCasters) - NEWSCASTER.newsAlert() - NEWSCASTER.update_icon() - src.admincaster_screen = 15 - else - news_network.wanted_issue.author = src.admincaster_feed_message.author - news_network.wanted_issue.body = src.admincaster_feed_message.body - news_network.wanted_issue.backup_author = src.admincaster_feed_message.backup_author - src.admincaster_screen = 19 - log_admin("[key_name_admin(usr)] issued a Station-wide Wanted Notification for [src.admincaster_feed_message.author]!") - src.access_news_network() - - else if(href_list["ac_cancel_wanted"]) - var/choice = alert("Please confirm Wanted Issue removal","Network Security Handler","Confirm","Cancel") - if(choice=="Confirm") - news_network.wanted_issue = null - for(var/obj/machinery/newscaster/NEWSCASTER in allCasters) - NEWSCASTER.update_icon() - src.admincaster_screen=17 - src.access_news_network() - - else if(href_list["ac_censor_channel_author"]) - var/datum/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) - if(FC.author != "\[REDACTED\]") - FC.backup_author = FC.author - FC.author = "\[REDACTED\]" - else - FC.author = FC.backup_author - src.access_news_network() - - else if(href_list["ac_censor_channel_story_author"]) - var/datum/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) - if(MSG.author != "\[REDACTED\]") - MSG.backup_author = MSG.author - MSG.author = "\[REDACTED\]" - else - MSG.author = MSG.backup_author - src.access_news_network() - - else if(href_list["ac_censor_channel_story_body"]) - var/datum/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) - if(MSG.body != "\[REDACTED\]") - MSG.backup_body = MSG.body - MSG.body = "\[REDACTED\]" - else - MSG.body = MSG.backup_body - src.access_news_network() - - else if(href_list["ac_pick_d_notice"]) - var/datum/feed_channel/FC = locate(href_list["ac_pick_d_notice"]) - src.admincaster_feed_channel = FC - src.admincaster_screen=13 - src.access_news_network() - - else if(href_list["ac_toggle_d_notice"]) - var/datum/feed_channel/FC = locate(href_list["ac_toggle_d_notice"]) - FC.censored = !FC.censored - src.access_news_network() - - else if(href_list["ac_view"]) - src.admincaster_screen=1 - src.access_news_network() - - else if(href_list["ac_setScreen"]) //Brings us to the main menu and resets all fields~ - src.admincaster_screen = text2num(href_list["ac_setScreen"]) - if(src.admincaster_screen == 0) - if(src.admincaster_feed_channel) - src.admincaster_feed_channel = new /datum/feed_channel - if(src.admincaster_feed_message) - src.admincaster_feed_message = new /datum/feed_message - src.access_news_network() - - else if(href_list["ac_show_channel"]) - var/datum/feed_channel/FC = locate(href_list["ac_show_channel"]) - src.admincaster_feed_channel = FC - src.admincaster_screen = 9 - src.access_news_network() - - else if(href_list["ac_pick_censor_channel"]) - var/datum/feed_channel/FC = locate(href_list["ac_pick_censor_channel"]) - src.admincaster_feed_channel = FC - src.admincaster_screen = 12 - src.access_news_network() - - else if(href_list["ac_refresh"]) - src.access_news_network() - - else if(href_list["ac_set_signature"]) - src.admincaster_signature = adminscrub(input(usr, "Provide your desired signature", "Network Identity Handler", "")) - src.access_news_network() - - if(href_list["secretsmenu"]) - switch(href_list["secretsmenu"]) - if("tab") - current_tab = text2num(href_list["tab"]) - Secrets(usr) - return 1 - - else if(href_list["viewruntime"]) - var/datum/ErrorViewer/error_viewer = locateUID(href_list["viewruntime"]) - if(!istype(error_viewer)) - to_chat(usr, "That runtime viewer no longer exists.") - return - if(href_list["viewruntime_backto"]) - error_viewer.showTo(usr, locateUID(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) - else - error_viewer.showTo(usr, null, href_list["viewruntime_linear"]) - - else if(href_list["add_station_goal"]) - if(!check_rights(R_EVENT)) - return - var/list/type_choices = typesof(/datum/station_goal) - var/picked = input("Choose goal type") in type_choices|null - if(!picked) - return - var/datum/station_goal/G = new picked() - if(picked == /datum/station_goal) - var/newname = clean_input("Enter goal name:") - if(!newname) - return - G.name = newname - var/description = input("Enter [command_name()] message contents:") as message|null - if(!description) - return - G.report_message = description - message_admins("[key_name_admin(usr)] created \"[G.name]\" station goal.") - SSticker.mode.station_goals += G - modify_goals() - - else if(href_list["showdetails"]) - if(!check_rights(R_ADMIN)) - return - var/text = html_decode(href_list["showdetails"]) - usr << browse("Details[replacetext(text, "\n", "
        ")]
        ", - "window=show_details;size=500x200") - - // Library stuff - else if(href_list["library_book_id"]) - var/isbn = sanitizeSQL(href_list["library_book_id"]) - - if(href_list["view_library_book"]) - var/DBQuery/query_view_book = dbcon.NewQuery("SELECT content, title FROM [format_table_name("library")] WHERE id=[isbn]") - if(!query_view_book.Execute()) - var/err = query_view_book.ErrorMsg() - log_game("SQL ERROR viewing book. Error : \[[err]\]\n") - return - - var/content = "" - var/title = "" - while(query_view_book.NextRow()) - content = query_view_book.item[1] - title = html_encode(query_view_book.item[2]) - - var/dat = "
        "
        -			dat += "[html_encode(html_to_pencode(content))]"
        -			dat += "
        " - - var/datum/browser/popup = new(usr, "admin_view_book", "[title]", 700, 400) - popup.set_content(dat) - popup.open(0) - - log_admin("[key_name(usr)] has viewed the book [isbn].") - message_admins("[key_name_admin(usr)] has viewed the book [isbn].") - return - - else if(href_list["unflag_library_book"]) - var/DBQuery/query_unflag_book = dbcon.NewQuery("UPDATE [format_table_name("library")] SET flagged = 0 WHERE id=[isbn]") - if(!query_unflag_book.Execute()) - var/err = query_unflag_book.ErrorMsg() - log_game("SQL ERROR unflagging book. Error : \[[err]\]\n") - return - - log_admin("[key_name(usr)] has unflagged the book [isbn].") - message_admins("[key_name_admin(usr)] has unflagged the book [isbn].") - - else if(href_list["delete_library_book"]) - var/DBQuery/query_delbook = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[isbn]") - if(!query_delbook.Execute()) - var/err = query_delbook.ErrorMsg() - log_game("SQL ERROR deleting book. Error : \[[err]\]\n") - return - - log_admin("[key_name(usr)] has deleted the book [isbn].") - message_admins("[key_name_admin(usr)] has deleted the book [isbn].") - - // Refresh the page - src.view_flagged_books() - - // Force unlink a discord key - else if(href_list["force_discord_unlink"]) - if(!check_rights(R_ADMIN)) - return - var/target_ckey = href_list["force_discord_unlink"] - var/DBQuery/admin_unlink_discord_id = dbcon.NewQuery("DELETE FROM [format_table_name("discord")] WHERE ckey = '[target_ckey]'") - if(!admin_unlink_discord_id.Execute()) - var/err = admin_unlink_discord_id.ErrorMsg() - log_game("SQL ERROR while admin-unlinking discord account. Error : \[[err]\]\n") - return - to_chat(src, "Successfully forcefully unlinked discord account from [target_ckey]") - message_admins("[key_name_admin(usr)] forcefully unlinked the discord account belonging to [target_ckey]") - log_admin("[key_name_admin(usr)] forcefully unlinked the discord account belonging to [target_ckey]") - - else if(href_list["create_outfit_finalize"]) - if(!check_rights(R_EVENT)) - return - create_outfit_finalize(usr,href_list) - else if(href_list["load_outfit"]) - if(!check_rights(R_EVENT)) - return - load_outfit(usr) - else if(href_list["create_outfit_menu"]) - if(!check_rights(R_EVENT)) - return - create_outfit(usr) - else if(href_list["delete_outfit"]) - if(!check_rights(R_EVENT)) - return - var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits - delete_outfit(usr,O) - else if(href_list["save_outfit"]) - if(!check_rights(R_EVENT)) - return - var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits - save_outfit(usr,O) - -/client/proc/create_eventmob_for(var/mob/living/carbon/human/H, var/killthem = 0) - if(!check_rights(R_EVENT)) - return - var/admin_outfits = subtypesof(/datum/outfit/admin) - var/hunter_outfits = list() - for(var/type in admin_outfits) - var/datum/outfit/admin/O = type - hunter_outfits[initial(O.name)] = type - var/dresscode = input("Select type", "Contracted Agents") as null|anything in hunter_outfits - if(isnull(dresscode)) - return - var/datum/outfit/O = hunter_outfits[dresscode] - message_admins("[key_name_admin(mob)] is sending a ([dresscode]) to [killthem ? "assassinate" : "protect"] [key_name_admin(H)]...") - var/list/candidates = pollCandidates("Play as a [killthem ? "murderous" : "protective"] [dresscode]?", ROLE_TRAITOR, 1) - if(!candidates.len) - to_chat(usr, "ERROR: Could not create eventmob. No valid candidates.") - return - var/mob/C = pick(candidates) - var/key_of_hunter = C.key - if(!key_of_hunter) - to_chat(usr, "ERROR: Could not create eventmob. Could not pick key.") - return - var/datum/mind/hunter_mind = new /datum/mind(key_of_hunter) - hunter_mind.active = 1 - var/mob/living/carbon/human/hunter_mob = new /mob/living/carbon/human(pick(latejoin)) - hunter_mind.transfer_to(hunter_mob) - hunter_mob.equipOutfit(O, FALSE) - var/obj/item/pinpointer/advpinpointer/N = new /obj/item/pinpointer/advpinpointer(hunter_mob) - hunter_mob.equip_to_slot_or_del(N, slot_in_backpack) - N.active = 1 - N.mode = 2 - N.target = H - N.point_at(N.target) - if(!locate(/obj/item/implant/dust, hunter_mob)) - var/obj/item/implant/dust/D = new /obj/item/implant/dust(hunter_mob) - D.implant(hunter_mob) - if(killthem) - var/datum/objective/assassinate/kill_objective = new - kill_objective.owner = hunter_mind - kill_objective.target = H.mind - kill_objective.explanation_text = "Kill [H.real_name], the [H.mind.assigned_role]." - hunter_mind.objectives += kill_objective - else - var/datum/objective/protect/protect_objective = new - protect_objective.owner = hunter_mind - protect_objective.target = H.mind - protect_objective.explanation_text = "Protect [H.real_name], the [H.mind.assigned_role]." - hunter_mind.objectives += protect_objective - SSticker.mode.traitors |= hunter_mob.mind - to_chat(hunter_mob, "ATTENTION: You are now on a mission!") - to_chat(hunter_mob, "Goal: [killthem ? "MURDER" : "PROTECT"] [H.real_name], currently in [get_area(H.loc)]. "); - if(killthem) - to_chat(hunter_mob, "If you kill [H.p_them()], [H.p_they()] cannot be revived."); - hunter_mob.mind.special_role = SPECIAL_ROLE_TRAITOR - var/datum/atom_hud/antag/tatorhud = huds[ANTAG_HUD_TRAITOR] - tatorhud.join_hud(hunter_mob) - set_antag_hud(hunter_mob, "hudsyndicate") - -/proc/admin_jump_link(var/atom/target) - if(!target) return - // The way admin jump links handle their src is weirdly inconsistent... - - . = ADMIN_FLW(target,"FLW") - if(isAI(target)) // AI core/eye follow links - var/mob/living/silicon/ai/A = target - if(A.client && A.eyeobj) // No point following clientless AI eyes - . += "|[ADMIN_FLW(A.eyeobj,"EYE")]" - else if(istype(target, /mob/dead/observer)) - var/mob/dead/observer/O = target - if(O.mind && O.mind.current) - . += "|[ADMIN_FLW(O.mind.current,"BDY")]" +/datum/admins/Topic(href, href_list) + ..() + + if(usr.client != src.owner || !check_rights(0)) + log_admin("[key_name(usr)] tried to use the admin panel without authorization.") + message_admins("[key_name_admin(usr)] has attempted to override the admin panel!") + return + + if(SSticker.mode && SSticker.mode.check_antagonists_topic(href, href_list)) + check_antagonists() + return + + if(href_list["rejectadminhelp"]) + if(!check_rights(R_ADMIN|R_MOD)) + return + var/client/C = locateUID(href_list["rejectadminhelp"]) + if(!C) + return + + C << 'sound/effects/adminhelp.ogg' + + to_chat(C, "- AdminHelp Rejected! -") + to_chat(C, "Your admin help was rejected.") + to_chat(C, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting. If you asked a question, please ensure it was clear what you were asking.") + + message_admins("[key_name_admin(usr)] rejected [key_name_admin(C.mob)]'s admin help") + log_admin("[key_name(usr)] rejected [key_name(C.mob)]'s admin help") + + if(href_list["openadminticket"]) + if(!check_rights(R_ADMIN)) + return + var/ticketID = text2num(href_list["openadminticket"]) + SStickets.showDetailUI(usr, ticketID) + + if(href_list["openmentorticket"]) + if(!check_rights(R_MENTOR|R_MOD|R_ADMIN)) + return + var/ticketID = text2num(href_list["openmentorticket"]) + SSmentor_tickets.showDetailUI(usr, ticketID) + + if(href_list["stickyban"]) + stickyban(href_list["stickyban"],href_list) + + if(href_list["makeAntag"]) + switch(href_list["makeAntag"]) + if("1") + log_admin("[key_name(usr)] has spawned a traitor.") + if(!makeTraitors()) + to_chat(usr, "Unfortunately there weren't enough candidates available.") + if("2") + log_admin("[key_name(usr)] has spawned a changeling.") + if(!makeChangelings()) + to_chat(usr, "Unfortunately there weren't enough candidates available.") + if("3") + log_admin("[key_name(usr)] has spawned revolutionaries.") + if(!makeRevs()) + to_chat(usr, "Unfortunately there weren't enough candidates available.") + if("4") + log_admin("[key_name(usr)] has spawned a cultists.") + if(!makeCult()) + to_chat(usr, "Unfortunately there weren't enough candidates available.") + if("5") + log_admin("[key_name(usr)] has spawned a wizard.") + if(!makeWizard()) + to_chat(usr, "Unfortunately there weren't enough candidates available.") + if("6") + log_admin("[key_name(usr)] has spawned vampires.") + if(!makeVampires()) + to_chat(usr, "Unfortunately there weren't enough candidates available.") + if("7") + log_admin("[key_name(usr)] has spawned vox raiders.") + if(!makeVoxRaiders()) + to_chat(usr, "Unfortunately there weren't enough candidates available.") + if("8") + log_admin("[key_name(usr)] has spawned an abductor team.") + if(!makeAbductorTeam()) + to_chat(usr, "Unfortunately there weren't enough candidates available.") + + else if(href_list["dbsearchckey"] || href_list["dbsearchadmin"] || href_list["dbsearchip"] || href_list["dbsearchcid"] || href_list["dbsearchbantype"]) + var/adminckey = href_list["dbsearchadmin"] + var/playerckey = href_list["dbsearchckey"] + var/playerip = href_list["dbsearchip"] + var/playercid = href_list["dbsearchcid"] + var/dbbantype = text2num(href_list["dbsearchbantype"]) + var/match = 0 + + if("dbmatch" in href_list) + match = 1 + + DB_ban_panel(playerckey, adminckey, playerip, playercid, dbbantype, match) + return + + else if(href_list["dbbanedit"]) + var/banedit = href_list["dbbanedit"] + var/banid = text2num(href_list["dbbanid"]) + if(!banedit || !banid) + return + + DB_ban_edit(banid, banedit) + return + + else if(href_list["dbbanaddtype"]) + + var/bantype = text2num(href_list["dbbanaddtype"]) + var/banckey = href_list["dbbanaddckey"] + var/banip = href_list["dbbanaddip"] + var/bancid = href_list["dbbanaddcid"] + var/banduration = text2num(href_list["dbbaddduration"]) + var/banjob = href_list["dbbanaddjob"] + var/banreason = href_list["dbbanreason"] + + banckey = ckey(banckey) + + switch(bantype) + if(BANTYPE_PERMA) + if(!banckey || !banreason) + to_chat(usr, "Not enough parameters (Requires ckey and reason)") + return + banduration = null + banjob = null + if(BANTYPE_TEMP) + if(!banckey || !banreason || !banduration) + to_chat(usr, "Not enough parameters (Requires ckey, reason and duration)") + return + banjob = null + if(BANTYPE_JOB_PERMA) + if(!banckey || !banreason || !banjob) + to_chat(usr, "Not enough parameters (Requires ckey, reason and job)") + return + banduration = null + if(BANTYPE_JOB_TEMP) + if(!banckey || !banreason || !banjob || !banduration) + to_chat(usr, "Not enough parameters (Requires ckey, reason and job)") + return + if(BANTYPE_APPEARANCE) + if(!banckey || !banreason) + to_chat(usr, "Not enough parameters (Requires ckey and reason)") + return + banduration = null + banjob = null + if(BANTYPE_ADMIN_PERMA) + if(!banckey || !banreason) + to_chat(usr, "Not enough parameters (Requires ckey and reason)") + return + banduration = null + banjob = null + if(BANTYPE_ADMIN_TEMP) + if(!banckey || !banreason || !banduration) + to_chat(usr, "Not enough parameters (Requires ckey, reason and duration)") + return + banjob = null + + var/mob/playermob + + for(var/mob/M in GLOB.player_list) + if(M.ckey == banckey) + playermob = M + break + + + banreason = "(MANUAL BAN) "+banreason + + if(!playermob) + if(banip) + banreason = "[banreason] (CUSTOM IP)" + if(bancid) + banreason = "[banreason] (CUSTOM CID)" + else + message_admins("Ban process: A mob matching [playermob.ckey] was found at location [playermob.x], [playermob.y], [playermob.z]. Custom IP and computer id fields replaced with the IP and computer id from the located mob") + + DB_ban_record(bantype, playermob, banduration, banreason, banjob, null, banckey, banip, bancid ) + + + else if(href_list["editrights"]) + if(!check_rights(R_PERMISSIONS)) + message_admins("[key_name_admin(usr)] attempted to edit the admin permissions without sufficient rights.") + log_admin("[key_name(usr)] attempted to edit the admin permissions without sufficient rights.") + return + + var/adm_ckey + + var/task = href_list["editrights"] + if(task == "add") + var/new_ckey = ckey(clean_input("New admin's ckey","Admin ckey", null)) + if(!new_ckey) return + if(new_ckey in admin_datums) + to_chat(usr, "Error: Topic 'editrights': [new_ckey] is already an admin") + return + adm_ckey = new_ckey + task = "rank" + else if(task != "show") + adm_ckey = ckey(href_list["ckey"]) + if(!adm_ckey) + to_chat(usr, "Error: Topic 'editrights': No valid ckey") + return + + var/datum/admins/D = admin_datums[adm_ckey] + + if(task == "remove") + if(alert("Are you sure you want to remove [adm_ckey]?","Message","Yes","Cancel") == "Yes") + if(!D) return + admin_datums -= adm_ckey + D.disassociate() + + updateranktodb(adm_ckey, "player") + message_admins("[key_name_admin(usr)] removed [adm_ckey] from the admins list") + log_admin("[key_name(usr)] removed [adm_ckey] from the admins list") + log_admin_rank_modification(adm_ckey, "Removed") + + else if(task == "rank") + var/new_rank + if(admin_ranks.len) + new_rank = input("Please select a rank", "New rank", null, null) as null|anything in (admin_ranks|"*New Rank*") + else + new_rank = input("Please select a rank", "New rank", null, null) as null|anything in list("Mentor", "Trial Admin", "Game Admin", "*New Rank*") + + var/rights = 0 + if(D) + rights = D.rights + switch(new_rank) + if(null,"") return + if("*New Rank*") + new_rank = input("Please input a new rank", "New custom rank", null, null) as null|text + if(config.admin_legacy_system) + new_rank = ckeyEx(new_rank) + if(!new_rank) + to_chat(usr, "Error: Topic 'editrights': Invalid rank") + return + if(config.admin_legacy_system) + if(admin_ranks.len) + if(new_rank in admin_ranks) + rights = admin_ranks[new_rank] //we typed a rank which already exists, use its rights + else + admin_ranks[new_rank] = 0 //add the new rank to admin_ranks + else + if(config.admin_legacy_system) + new_rank = ckeyEx(new_rank) + rights = admin_ranks[new_rank] //we input an existing rank, use its rights + + if(D) + D.disassociate() //remove adminverbs and unlink from client + D.rank = new_rank //update the rank + D.rights = rights //update the rights based on admin_ranks (default: 0) + else + D = new /datum/admins(new_rank, rights, adm_ckey) + + var/client/C = GLOB.directory[adm_ckey] //find the client with the specified ckey (if they are logged in) + D.associate(C) //link up with the client and add verbs + + updateranktodb(adm_ckey, new_rank) + message_admins("[key_name_admin(usr)] edited the admin rank of [adm_ckey] to [new_rank]") + log_admin("[key_name(usr)] edited the admin rank of [adm_ckey] to [new_rank]") + log_admin_rank_modification(adm_ckey, new_rank) + + else if(task == "permissions") + if(!D) return + while(TRUE) + var/list/permissionlist = list() + for(var/i=1, i<=R_MAXPERMISSION, i<<=1) //that <<= is shorthand for i = i << 1. Which is a left bitshift + permissionlist[rights2text(i)] = i + var/new_permission = input("Select a permission to turn on/off", adm_ckey + "'s Permissions", null, null) as null|anything in permissionlist + if(!new_permission) + return + var/oldrights = D.rights + var/toggleresult = "ON" + D.rights ^= permissionlist[new_permission] + if(oldrights > D.rights) + toggleresult = "OFF" + + message_admins("[key_name_admin(usr)] toggled the [new_permission] permission of [adm_ckey] to [toggleresult]") + log_admin("[key_name(usr)] toggled the [new_permission] permission of [adm_ckey] to [toggleresult]") + log_admin_permission_modification(adm_ckey, permissionlist[new_permission]) + + + edit_admin_permissions() + + else if(href_list["call_shuttle"]) + if(!check_rights(R_ADMIN)) return + + + switch(href_list["call_shuttle"]) + if("1") + if(SSshuttle.emergency.mode >= SHUTTLE_DOCKED) + return + SSshuttle.emergency.request() + log_admin("[key_name(usr)] called the Emergency Shuttle") + message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station") + + if("2") + if(SSshuttle.emergency.mode >= SHUTTLE_DOCKED) + return + switch(SSshuttle.emergency.mode) + if(SHUTTLE_CALL) + SSshuttle.emergency.cancel() + log_admin("[key_name(usr)] sent the Emergency Shuttle back") + message_admins("[key_name_admin(usr)] sent the Emergency Shuttle back") + else + SSshuttle.emergency.cancel() + log_admin("[key_name(usr)] called the Emergency Shuttle") + message_admins("[key_name_admin(usr)] called the Emergency Shuttle to the station") + + + href_list["secrets"] = "check_antagonist" + + else if(href_list["edit_shuttle_time"]) + if(!check_rights(R_SERVER)) return + + var/timer = input("Enter new shuttle duration (seconds):","Edit Shuttle Timeleft", SSshuttle.emergency.timeLeft() ) as num + SSshuttle.emergency.setTimer(timer*10) + log_admin("[key_name(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds") + minor_announcement.Announce("The emergency shuttle will reach its destination in [round(SSshuttle.emergency.timeLeft(600))] minutes.") + message_admins("[key_name_admin(usr)] edited the Emergency Shuttle's timeleft to [timer] seconds") + href_list["secrets"] = "check_antagonist" + + else if(href_list["delay_round_end"]) + if(!check_rights(R_SERVER)) return + + SSticker.delay_end = !SSticker.delay_end + log_admin("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].") + message_admins("[key_name_admin(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].", 1) + href_list["secretsadmin"] = "check_antagonist" + + else if(href_list["simplemake"]) + if(!check_rights(R_SPAWN)) return + + var/mob/M = locateUID(href_list["mob"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + var/delmob = 0 + switch(alert("Delete old mob?","Message","Yes","No","Cancel")) + if("Cancel") return + if("Yes") delmob = 1 + + switch(href_list["simplemake"]) + if("observer") M.change_mob_type( /mob/dead/observer , null, null, delmob, 1 ) + if("drone") M.change_mob_type( /mob/living/carbon/alien/humanoid/drone , null, null, delmob, 1 ) + if("hunter") M.change_mob_type( /mob/living/carbon/alien/humanoid/hunter , null, null, delmob, 1 ) + if("queen") M.change_mob_type( /mob/living/carbon/alien/humanoid/queen/large , null, null, delmob, 1 ) + if("sentinel") M.change_mob_type( /mob/living/carbon/alien/humanoid/sentinel , null, null, delmob, 1 ) + if("larva") M.change_mob_type( /mob/living/carbon/alien/larva , null, null, delmob, 1 ) + if("human") + var/posttransformoutfit = usr.client.robust_dress_shop() + var/mob/living/carbon/human/newmob = M.change_mob_type(/mob/living/carbon/human, null, null, delmob, 1) + if(posttransformoutfit && istype(newmob)) + newmob.equipOutfit(posttransformoutfit) + if("slime") M.change_mob_type( /mob/living/simple_animal/slime , null, null, delmob, 1 ) + if("monkey") M.change_mob_type( /mob/living/carbon/human/monkey , null, null, delmob, 1 ) + if("robot") M.change_mob_type( /mob/living/silicon/robot , null, null, delmob, 1 ) + if("cat") M.change_mob_type( /mob/living/simple_animal/pet/cat , null, null, delmob, 1 ) + if("runtime") M.change_mob_type( /mob/living/simple_animal/pet/cat/Runtime , null, null, delmob, 1 ) + if("corgi") M.change_mob_type( /mob/living/simple_animal/pet/dog/corgi , null, null, delmob, 1 ) + if("crab") M.change_mob_type( /mob/living/simple_animal/crab , null, null, delmob, 1 ) + if("coffee") M.change_mob_type( /mob/living/simple_animal/crab/Coffee , null, null, delmob, 1 ) + if("parrot") M.change_mob_type( /mob/living/simple_animal/parrot , null, null, delmob, 1 ) + if("polyparrot") M.change_mob_type( /mob/living/simple_animal/parrot/Poly , null, null, delmob, 1 ) + if("constructarmoured") M.change_mob_type( /mob/living/simple_animal/hostile/construct/armoured , null, null, delmob, 1 ) + if("constructbuilder") M.change_mob_type( /mob/living/simple_animal/hostile/construct/builder , null, null, delmob, 1 ) + if("constructwraith") M.change_mob_type( /mob/living/simple_animal/hostile/construct/wraith , null, null, delmob, 1 ) + if("shade") M.change_mob_type( /mob/living/simple_animal/shade , null, null, delmob, 1 ) + + log_admin("[key_name(usr)] has used rudimentary transformation on [key_name(M)]. Transforming to [href_list["simplemake"]]; deletemob=[delmob]") + message_admins("[key_name_admin(usr)] has used rudimentary transformation on [key_name_admin(M)]. Transforming to [href_list["simplemake"]]; deletemob=[delmob]", 1) + + + /////////////////////////////////////new ban stuff + else if(href_list["unbanf"]) + if(!check_rights(R_BAN)) return + + var/banfolder = href_list["unbanf"] + Banlist.cd = "/base/[banfolder]" + var/key = Banlist["key"] + if(alert(usr, "Are you sure you want to unban [key]?", "Confirmation", "Yes", "No") == "Yes") + if(RemoveBan(banfolder)) + unbanpanel() + else + alert(usr, "This ban has already been lifted / does not exist.", "Error", "Ok") + unbanpanel() + + else if(href_list["warn"]) + usr.client.warn(href_list["warn"]) + + else if(href_list["unbane"]) + if(!check_rights(R_BAN)) return + + UpdateTime() + var/reason + + var/banfolder = href_list["unbane"] + Banlist.cd = "/base/[banfolder]" + var/reason2 = Banlist["reason"] + var/temp = Banlist["temp"] + + var/minutes = Banlist["minutes"] + + var/banned_key = Banlist["key"] + Banlist.cd = "/base" + + var/duration + + switch(alert("Temporary Ban?",,"Yes","No")) + if("Yes") + temp = 1 + var/mins = 0 + if(minutes > CMinutes) + mins = minutes - CMinutes + mins = input(usr,"How long (in minutes)? (Default: 1440)","Ban time",mins ? mins : 1440) as num|null + if(!mins) return + mins = min(525599,mins) + minutes = CMinutes + mins + duration = GetExp(minutes) + reason = input(usr,"Please state the reason","Reason",reason2) as message|null + if(!reason) return + if("No") + temp = 0 + duration = "Perma" + reason = input(usr,"Please state the reason","Reason",reason2) as message|null + if(!reason) return + + log_admin("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") + ban_unban_log_save("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") + message_admins("[key_name_admin(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]", 1) + Banlist.cd = "/base/[banfolder]" + to_chat(Banlist["reason"], reason) + to_chat(Banlist["temp"], temp) + to_chat(Banlist["minutes"], minutes) + to_chat(Banlist["bannedby"], usr.ckey) + Banlist.cd = "/base" + feedback_inc("ban_edit",1) + unbanpanel() + + /////////////////////////////////////new ban stuff + + else if(href_list["appearanceban"]) + if(!check_rights(R_BAN)) + return + var/mob/M = locateUID(href_list["appearanceban"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(!M.ckey) //sanity + to_chat(usr, "This mob has no ckey") + return + var/ban_ckey_param = href_list["dbbanaddckey"] + + var/banreason = appearance_isbanned(M) + if(banreason) + /* if(!config.ban_legacy_system) + to_chat(usr, "Unfortunately, database based unbanning cannot be done through this panel") + DB_ban_panel(M.ckey) + return */ + switch(alert("Reason: '[banreason]' Remove appearance ban?","Please Confirm","Yes","No")) + if("Yes") + ban_unban_log_save("[key_name(usr)] removed [key_name(M)]'s appearance ban") + log_admin("[key_name(usr)] removed [key_name(M)]'s appearance ban") + feedback_inc("ban_appearance_unban", 1) + DB_ban_unban(M.ckey, BANTYPE_APPEARANCE) + appearance_unban(M) + message_admins("[key_name_admin(usr)] removed [key_name_admin(M)]'s appearance ban", 1) + to_chat(M, "[usr.client.ckey] has removed your appearance ban.") + + else switch(alert("Appearance ban [M.ckey]?",,"Yes","No", "Cancel")) + if("Yes") + var/reason = input(usr,"Please state the reason","Reason") as message|null + if(!reason) + return + M = admin_ban_mobsearch(M, ban_ckey_param, usr) + ban_unban_log_save("[key_name(usr)] appearance banned [key_name(M)]. reason: [reason]") + log_admin("[key_name(usr)] appearance banned [key_name(M)]. \nReason: [reason]") + feedback_inc("ban_appearance",1) + DB_ban_record(BANTYPE_APPEARANCE, M, -1, reason) + appearance_fullban(M, "[reason]; By [usr.ckey] on [time2text(world.realtime)]") + add_note(M.ckey, "Appearance banned - [reason]", null, usr.ckey, 0) + message_admins("[key_name_admin(usr)] appearance banned [key_name_admin(M)]", 1) + to_chat(M, "You have been appearance banned by [usr.client.ckey].") + to_chat(M, "The reason is: [reason]") + to_chat(M, "Appearance ban can be lifted only upon request.") + if(config.banappeals) + to_chat(M, "To try to resolve this matter head to [config.banappeals]") + else + to_chat(M, "No ban appeals URL has been set.") + if("No") + return + + else if(href_list["jobban2"]) +// if(!check_rights(R_BAN)) return + + var/mob/M = locateUID(href_list["jobban2"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + if(!M.ckey) //sanity + to_chat(usr, "This mob has no ckey") + return + if(!SSjobs) + to_chat(usr, "SSjobs has not been setup!") + return + + var/dat = "" + var/header = "Job-Ban Panel: [M.name]" + var/body + var/jobs = "" + + /***********************************WARNING!************************************ + The jobban stuff looks mangled and disgusting + But it looks beautiful in-game + -Nodrak + ************************************WARNING!***********************************/ + var/counter = 0 +//Regular jobs + //Command (Blue) + jobs += "" + jobs += "" + for(var/jobPos in command_positions) + if(!jobPos) continue + var/datum/job/job = SSjobs.GetJob(jobPos) + if(!job) continue + + if(jobban_isbanned(M, job.title)) + jobs += "" + counter++ + else + jobs += "" + counter++ + + if(counter >= 6) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Command Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " + + //Security (Red) + counter = 0 + jobs += "" + jobs += "" + for(var/jobPos in security_positions) + if(!jobPos) continue + var/datum/job/job = SSjobs.GetJob(jobPos) + if(!job) continue + + if(jobban_isbanned(M, job.title)) + jobs += "" + counter++ + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Security Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " + + //Engineering (Yellow) + counter = 0 + jobs += "" + jobs += "" + for(var/jobPos in engineering_positions) + if(!jobPos) continue + var/datum/job/job = SSjobs.GetJob(jobPos) + if(!job) continue + + if(jobban_isbanned(M, job.title)) + jobs += "" + counter++ + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Engineering Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " + + //Medical (White) + counter = 0 + jobs += "" + jobs += "" + for(var/jobPos in medical_positions) + if(!jobPos) continue + var/datum/job/job = SSjobs.GetJob(jobPos) + if(!job) continue + + if(jobban_isbanned(M, job.title)) + jobs += "" + counter++ + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Medical Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " + + //Science (Purple) + counter = 0 + jobs += "" + jobs += "" + for(var/jobPos in science_positions) + if(!jobPos) continue + var/datum/job/job = SSjobs.GetJob(jobPos) + if(!job) continue + + if(jobban_isbanned(M, job.title)) + jobs += "" + counter++ + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Science Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " + + //Support (Grey) + counter = 0 + jobs += "" + jobs += "" + for(var/jobPos in support_positions) + if(!jobPos) continue + var/datum/job/job = SSjobs.GetJob(jobPos) + if(!job) continue + + if(jobban_isbanned(M, job.title)) + jobs += "" + counter++ + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Support Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " + + //Non-Human (Green) + counter = 0 + jobs += "" + jobs += "" + for(var/jobPos in nonhuman_positions) + if(!jobPos) continue + var/datum/job/job = SSjobs.GetJob(jobPos) + if(!job) continue + + if(jobban_isbanned(M, job.title)) + jobs += "" + counter++ + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + + //Drone + if(jobban_isbanned(M, "Drone")) + jobs += "" + else + jobs += "" + + //pAI + if(jobban_isbanned(M, "pAI")) + jobs += "" + else + jobs += "" + + jobs += "
        Non-human Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        DroneDronepAIpAI
        " + + //Antagonist (Orange) + var/isbanned_dept = jobban_isbanned(M, "Syndicate") + jobs += "" + jobs += "" + + counter = 0 + for(var/role in antag_roles) + if(jobban_isbanned(M, role) || isbanned_dept) + jobs += "" + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Antagonist Positions
        [replacetext(role, " ", " ")][replacetext(role, " ", " ")]
        " + + //Other races (BLUE, because I have no idea what other color to make this) + jobs += "" + jobs += "" + + counter = 0 + for(var/role in other_roles) + if(jobban_isbanned(M, role) || isbanned_dept) + jobs += "" + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Other
        [replacetext(role, " ", " ")][replacetext(role, " ", " ")]
        " + + //Whitelisted positions + counter = 0 + jobs += "" + jobs += "" + for(var/jobPos in whitelisted_positions) + if(!jobPos) continue + var/datum/job/job = SSjobs.GetJob(jobPos) + if(!job) continue + + if(jobban_isbanned(M, job.title)) + jobs += "" + counter++ + else + jobs += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + jobs += "" + counter = 0 + jobs += "
        Whitelisted Positions
        [replacetext(job.title, " ", " ")][replacetext(job.title, " ", " ")]
        " + + body = "[jobs]" + dat = "[header][body]" + usr << browse(dat, "window=jobban2;size=800x490") + return + + //JOBBAN'S INNARDS + else if(href_list["jobban3"]) + if(!check_rights(R_BAN)) return + + var/mob/M = locateUID(href_list["jobban4"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + if(M != usr) //we can jobban ourselves + if(M.client && M.client.holder && (M.client.holder.rights & R_BAN)) //they can ban too. So we can't ban them + alert("You cannot perform this action. You must be of a higher administrative rank!") + return + + var/ban_ckey_param = href_list["dbbanaddckey"] + + if(!SSjobs) + to_chat(usr, "SSjobs has not been setup!") + return + + //get jobs for department if specified, otherwise just returnt he one job in a list. + var/list/joblist = list() + switch(href_list["jobban3"]) + if("commanddept") + for(var/jobPos in command_positions) + if(!jobPos) continue + var/datum/job/temp = SSjobs.GetJob(jobPos) + if(!temp) continue + joblist += temp.title + if("securitydept") + for(var/jobPos in security_positions) + if(!jobPos) continue + var/datum/job/temp = SSjobs.GetJob(jobPos) + if(!temp) continue + joblist += temp.title + if("engineeringdept") + for(var/jobPos in engineering_positions) + if(!jobPos) continue + var/datum/job/temp = SSjobs.GetJob(jobPos) + if(!temp) continue + joblist += temp.title + if("medicaldept") + for(var/jobPos in medical_positions) + if(!jobPos) continue + var/datum/job/temp = SSjobs.GetJob(jobPos) + if(!temp) continue + joblist += temp.title + if("sciencedept") + for(var/jobPos in science_positions) + if(!jobPos) continue + var/datum/job/temp = SSjobs.GetJob(jobPos) + if(!temp) continue + joblist += temp.title + if("supportdept") + for(var/jobPos in support_positions) + if(!jobPos) continue + var/datum/job/temp = SSjobs.GetJob(jobPos) + if(!temp) continue + joblist += temp.title + if("nonhumandept") + joblist += "pAI" + for(var/jobPos in nonhuman_positions) + if(!jobPos) continue + var/datum/job/temp = SSjobs.GetJob(jobPos) + if(!temp) continue + joblist += temp.title + if("whitelistdept") + for(var/jobPos in whitelisted_positions) + if(!jobPos) continue + var/datum/job/temp = SSjobs.GetJob(jobPos) + if(!temp) continue + joblist += temp.title + else + joblist += href_list["jobban3"] + + //Create a list of unbanned jobs within joblist + var/list/notbannedlist = list() + for(var/job in joblist) + if(!jobban_isbanned(M, job)) + notbannedlist += job + + //Banning comes first + if(notbannedlist.len) //at least 1 unbanned job exists in joblist so we have stuff to ban. + switch(alert("Temporary Ban of [M.ckey]?",,"Yes","No", "Cancel")) + if("Yes") + if(config.ban_legacy_system) + to_chat(usr, "Your server is using the legacy banning system, which does not support temporary job bans. Consider upgrading. Aborting ban.") + return + var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null + if(!mins) + return + var/reason = input(usr,"Please state the reason","Reason","") as message|null + if(!reason) + return + + var/msg + M = admin_ban_mobsearch(M, ban_ckey_param, usr) + for(var/job in notbannedlist) + ban_unban_log_save("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes. reason: [reason]") + log_admin("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes") + feedback_inc("ban_job_tmp",1) + DB_ban_record(BANTYPE_JOB_TEMP, M, mins, reason, job) + feedback_add_details("ban_job_tmp","- [job]") + jobban_fullban(M, job, "[reason]; By [usr.ckey] on [time2text(world.realtime)]") //Legacy banning does not support temporary jobbans. + if(!msg) + msg = job + else + msg += ", [job]" + add_note(M.ckey, "Banned from [msg] - [reason]", null, usr.ckey, 0) + message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg] for [mins] minutes", 1) + to_chat(M, "You have been jobbanned by [usr.client.ckey] from: [msg].") + to_chat(M, "The reason is: [reason]") + to_chat(M, "This jobban will be lifted in [mins] minutes.") + href_list["jobban2"] = 1 // lets it fall through and refresh + return 1 + if("No") + var/reason = input(usr,"Please state the reason","Reason","") as message|null + if(reason) + var/msg + M = admin_ban_mobsearch(M, ban_ckey_param, usr) + for(var/job in notbannedlist) + ban_unban_log_save("[key_name(usr)] perma-jobbanned [key_name(M)] from [job]. reason: [reason]") + log_admin("[key_name(usr)] perma-banned [key_name(M)] from [job]") + feedback_inc("ban_job",1) + DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, job) + feedback_add_details("ban_job","- [job]") + jobban_fullban(M, job, "[reason]; By [usr.ckey] on [time2text(world.realtime)]") + if(!msg) msg = job + else msg += ", [job]" + add_note(M.ckey, "Banned from [msg] - [reason]", null, usr.ckey, 0) + message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg]", 1) + to_chat(M, "You have been jobbanned by [usr.client.ckey] from: [msg].") + to_chat(M, "The reason is: [reason]") + to_chat(M, "Jobban can be lifted only upon request.") + href_list["jobban2"] = 1 // lets it fall through and refresh + return 1 + if("Cancel") + return + + //Unbanning joblist + //all jobs in joblist are banned already OR we didn't give a reason (implying they shouldn't be banned) + if(joblist.len) //at least 1 banned job exists in joblist so we have stuff to unban. + if(!config.ban_legacy_system) + to_chat(usr, "Unfortunately, database based unbanning cannot be done through this panel") + DB_ban_panel(M.ckey) + return + var/msg + for(var/job in joblist) + var/reason = jobban_isbanned(M, job) + if(!reason) continue //skip if it isn't jobbanned anyway + switch(alert("Job: '[job]' Reason: '[reason]' Un-jobban?","Please Confirm","Yes","No")) + if("Yes") + ban_unban_log_save("[key_name(usr)] unjobbanned [key_name(M)] from [job]") + log_admin("[key_name(usr)] unbanned [key_name(M)] from [job]") + DB_ban_unban(M.ckey, BANTYPE_JOB_PERMA, job) + feedback_inc("ban_job_unban",1) + feedback_add_details("ban_job_unban","- [job]") + jobban_unban(M, job) + if(!msg) msg = job + else msg += ", [job]" + else + continue + if(msg) + message_admins("[key_name_admin(usr)] unbanned [key_name_admin(M)] from [msg]", 1) + to_chat(M, "You have been un-jobbanned by [usr.client.ckey] from [msg].") + href_list["jobban2"] = 1 // lets it fall through and refresh + return 1 + return 0 //we didn't do anything! + + else if(href_list["boot2"]) + var/mob/M = locateUID(href_list["boot2"]) + if(ismob(M)) + if(M.client && M.client.holder && (M.client.holder.rights & R_BAN)) + to_chat(usr, "[key_name_admin(M)] cannot be kicked from the server.") + return + to_chat(M, "You have been kicked from the server") + log_admin("[key_name(usr)] booted [key_name(M)].") + message_admins("[key_name_admin(usr)] booted [key_name_admin(M)].", 1) + //M.client = null + del(M.client) + + //Player Notes + else if(href_list["addnote"]) + var/target_ckey = href_list["addnote"] + add_note(target_ckey) + + else if(href_list["addnoteempty"]) + add_note() + + else if(href_list["removenote"]) + var/note_id = href_list["removenote"] + remove_note(note_id) + + else if(href_list["editnote"]) + var/note_id = href_list["editnote"] + edit_note(note_id) + + else if(href_list["shownote"]) + var/target = href_list["shownote"] + show_note(index = target) + + else if(href_list["nonalpha"]) + var/target = href_list["nonalpha"] + target = text2num(target) + show_note(index = target) + + else if(href_list["webtools"]) + var/target_ckey = href_list["webtools"] + if(config.forum_playerinfo_url) + var/url_to_open = config.forum_playerinfo_url + target_ckey + if(alert("Open [url_to_open]",,"Yes","No")=="Yes") + usr.client << link(url_to_open) + + else if(href_list["shownoteckey"]) + var/target_ckey = href_list["shownoteckey"] + show_note(target_ckey) + + else if(href_list["notessearch"]) + var/target = href_list["notessearch"] + show_note(index = target) + + else if(href_list["noteedits"]) + var/note_id = sanitizeSQL("[href_list["noteedits"]]") + var/DBQuery/query_noteedits = dbcon.NewQuery("SELECT edits FROM [format_table_name("notes")] WHERE id = '[note_id]'") + if(!query_noteedits.Execute()) + var/err = query_noteedits.ErrorMsg() + log_game("SQL ERROR obtaining edits from notes table. Error : \[[err]\]\n") + return + if(query_noteedits.NextRow()) + var/edit_log = query_noteedits.item[1] + usr << browse(edit_log,"window=noteedits") + + else if(href_list["removejobban"]) + if(!check_rights(R_BAN)) return + + var/t = href_list["removejobban"] + if(t) + if((alert("Do you want to unjobban [t]?","Unjobban confirmation", "Yes", "No") == "Yes") && t) //No more misclicks! Unless you do it twice. + log_admin("[key_name(usr)] removed [t]") + message_admins("[key_name_admin(usr)] removed [t]", 1) + jobban_remove(t) + href_list["ban"] = 1 // lets it fall through and refresh + var/t_split = splittext(t, " - ") + var/key = t_split[1] + var/job = t_split[2] + DB_ban_unban(ckey(key), BANTYPE_JOB_PERMA, job) + + else if(href_list["newban"]) + if(!check_rights(R_BAN)) return + + var/mob/M = locateUID(href_list["newban"]) + if(!ismob(M)) + return + var/ban_ckey_param = href_list["dbbanaddckey"] + + switch(alert("Temporary Ban of [M.ckey] / [ban_ckey_param]?",,"Yes","No", "Cancel")) + if("Yes") + var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null + if(!mins) + return + if(mins >= 525600) mins = 525599 + var/reason = input(usr,"Please state the reason","Reason") as message|null + if(!reason) + return + M = admin_ban_mobsearch(M, ban_ckey_param, usr) + AddBan(M.ckey, M.computer_id, reason, usr.ckey, 1, mins) + ban_unban_log_save("[usr.client.ckey] has banned [M.ckey]. - Reason: [reason] - This will be removed in [mins] minutes.") + to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") + to_chat(M, "This is a temporary ban, it will be removed in [mins] minutes.") + feedback_inc("ban_tmp",1) + DB_ban_record(BANTYPE_TEMP, M, mins, reason) + feedback_inc("ban_tmp_mins",mins) + if(M.client) + M.client.link_forum_account(TRUE) + if(config.banappeals) + to_chat(M, "To try to resolve this matter head to [config.banappeals]") + else + to_chat(M, "No ban appeals URL has been set.") + log_admin("[key_name(usr)] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.") + message_admins("[key_name_admin(usr)] has banned [M.ckey].\nReason: [reason]\nThis will be removed in [mins] minutes.") + + del(M.client) + //qdel(M) // See no reason why to delete mob. Important stuff can be lost. And ban can be lifted before round ends. + if("No") + var/reason = input(usr,"Please state the reason","Reason") as message|null + if(!reason) + return + AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP) + to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].") + to_chat(M, "This ban does not expire automatically and must be appealed.") + if(M.client) + M.client.link_forum_account(TRUE) + if(config.banappeals) + to_chat(M, "To try to resolve this matter head to [config.banappeals]") + else + to_chat(M, "No ban appeals URL has been set.") + ban_unban_log_save("[usr.client.ckey] has permabanned [M.ckey]. - Reason: [reason] - This ban does not expire automatically and must be appealed.") + log_admin("[key_name(usr)] has banned [M.ckey].\nReason: [reason]\nThis ban does not expire automatically and must be appealed.") + message_admins("[key_name_admin(usr)] has banned [M.ckey].\nReason: [reason]\nThis ban does not expire automatically and must be appealed.") + feedback_inc("ban_perma",1) + DB_ban_record(BANTYPE_PERMA, M, -1, reason) + + del(M.client) + //qdel(M) + if("Cancel") + return + + + //Watchlist + else if(href_list["watchadd"]) + var/target_ckey = href_list["watchadd"] + usr.client.watchlist_add(target_ckey) + + else if(href_list["watchremove"]) + var/target_ckey = href_list["watchremove"] + var/confirm = alert("Are you sure you want to remove [target_ckey] from the watchlist?", "Confirm Watchlist Removal", "Yes", "No") + if(confirm == "Yes") + usr.client.watchlist_remove(target_ckey) + + else if(href_list["watchedit"]) + var/target_ckey = href_list["watchedit"] + usr.client.watchlist_edit(target_ckey) + + else if(href_list["watchaddbrowse"]) + usr.client.watchlist_add(null, 1) + + else if(href_list["watchremovebrowse"]) + var/target_ckey = href_list["watchremovebrowse"] + usr.client.watchlist_remove(target_ckey, 1) + + else if(href_list["watcheditbrowse"]) + var/target_ckey = href_list["watcheditbrowse"] + usr.client.watchlist_edit(target_ckey, 1) + + else if(href_list["watchsearch"]) + var/target_ckey = href_list["watchsearch"] + usr.client.watchlist_show(target_ckey) + + else if(href_list["watchshow"]) + usr.client.watchlist_show() + + else if(href_list["watcheditlog"]) + var/target_ckey = sanitizeSQL("[href_list["watcheditlog"]]") + var/DBQuery/query_watchedits = dbcon.NewQuery("SELECT edits FROM [format_table_name("watch")] WHERE ckey = '[target_ckey]'") + if(!query_watchedits.Execute()) + var/err = query_watchedits.ErrorMsg() + log_game("SQL ERROR obtaining edits from watch table. Error : \[[err]\]\n") + return + if(query_watchedits.NextRow()) + var/edit_log = query_watchedits.item[1] + usr << browse(edit_log,"window=watchedits") + + else if(href_list["mute"]) + if(!check_rights(R_ADMIN|R_MOD)) + return + + var/mob/M = locateUID(href_list["mute"]) + if(!ismob(M)) return + if(!M.client) return + + var/mute_type = href_list["mute_type"] + if(istext(mute_type)) mute_type = text2num(mute_type) + if(!isnum(mute_type)) return + + cmd_admin_mute(M, mute_type) + + else if(href_list["c_mode"]) + if(!check_rights(R_ADMIN)) return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + var/dat = {"What mode do you wish to play?
        "} + for(var/mode in config.modes) + dat += {"[config.mode_names[mode]]
        "} + dat += {"Secret
        "} + dat += {"Random
        "} + dat += {"Now: [master_mode]"} + usr << browse(dat, "window=c_mode") + + else if(href_list["f_secret"]) + if(!check_rights(R_ADMIN)) return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + var/dat = {"What game mode do you want to force secret to be? Use this if you want to change the game mode, but want the players to believe it's secret. This will only work if the current game mode is secret.
        "} + for(var/mode in config.modes) + dat += {"[config.mode_names[mode]]
        "} + dat += {"Random (default)
        "} + dat += {"Now: [secret_force_mode]"} + usr << browse(dat, "window=f_secret") + + else if(href_list["c_mode2"]) + if(!check_rights(R_ADMIN|R_SERVER)) return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + master_mode = href_list["c_mode2"] + log_admin("[key_name(usr)] set the mode as [master_mode].") + message_admins("[key_name_admin(usr)] set the mode as [master_mode].", 1) + to_chat(world, "The mode is now: [master_mode]") + Game() // updates the main game menu + world.save_mode(master_mode) + .(href, list("c_mode"=1)) + + else if(href_list["f_secret2"]) + if(!check_rights(R_ADMIN|R_SERVER)) return + + if(SSticker && SSticker.mode) + return alert(usr, "The game has already started.", null, null, null, null) + if(master_mode != "secret") + return alert(usr, "The game mode has to be secret!", null, null, null, null) + secret_force_mode = href_list["f_secret2"] + log_admin("[key_name(usr)] set the forced secret mode as [secret_force_mode].") + message_admins("[key_name_admin(usr)] set the forced secret mode as [secret_force_mode].", 1) + Game() // updates the main game menu + .(href, list("f_secret"=1)) + + else if(href_list["monkeyone"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["monkeyone"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(alert(usr, "Confirm make monkey?",, "Yes", "No") != "Yes") + return + + log_admin("[key_name(usr)] attempting to monkeyize [key_name(H)]") + message_admins("[key_name_admin(usr)] attempting to monkeyize [key_name_admin(H)]", 1) + H.monkeyize() + + + else if(href_list["corgione"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["corgione"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + + if(alert(usr, "Confirm make corgi?",, "Yes", "No") != "Yes") + return + + log_admin("[key_name(usr)] attempting to corgize [key_name(H)]") + message_admins("[key_name_admin(usr)] attempting to corgize [key_name_admin(H)]", 1) + H.corgize() + + else if(href_list["makePAI"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makePAI"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(alert(usr, "Confirm make pai?",, "Yes", "No") != "Yes") + return + + var/painame = "Default" + var/name = "" + if(alert(usr, "Do you want to set their name or let them choose their own name?", "Name Choice", "Set Name", "Let them choose") == "Set Name") + name = sanitize(copytext(input(usr, "Enter a name for the new pAI. Default name is [painame].", "pAI Name", painame),1,MAX_NAME_LEN)) + else + name = sanitize(copytext(input(H, "An admin wants to make you into a pAI. Choose a name. Default is [painame].", "pAI Name", painame),1,MAX_NAME_LEN)) + + if(!name) + name = painame + + log_admin("[key_name(usr)] attempting to pAIze [key_name(H)]") + message_admins("[key_name_admin(usr)] attempting to pAIze [key_name_admin(H)]", 1) + H.paize(name) + + else if(href_list["forcespeech"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + var/mob/M = locateUID(href_list["forcespeech"]) + if(!ismob(M)) + to_chat(usr, "this can only be used on instances of type /mob") + + var/speech = input("What will [key_name(M)] say?.", "Force speech", "")// Don't need to sanitize, since it does that in say(), we also trust our admins. + if(!speech) return + M.say(speech) + speech = sanitize(speech) // Nah, we don't trust them + log_admin("[key_name(usr)] forced [key_name(M)] to say: [speech]") + message_admins("[key_name_admin(usr)] forced [key_name_admin(M)] to say: [speech]") + + else if(href_list["sendtoprison"]) + if(!check_rights(R_ADMIN)) return + + if(alert(usr, "Send to admin prison for the round?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locateUID(href_list["sendtoprison"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(istype(M, /mob/living/silicon/ai)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + return + + var/turf/prison_cell = pick(prisonwarp) + if(!prison_cell) return + + var/obj/structure/closet/secure_closet/brig/locker = new /obj/structure/closet/secure_closet/brig(prison_cell) + locker.opened = 0 + locker.locked = 1 + + //strip their stuff and stick it in the crate + for(var/obj/item/I in M) + if(M.unEquip(I)) + I.loc = locker + I.layer = initial(I.layer) + I.plane = initial(I.plane) + I.dropped(M) + M.update_icons() + + //so they black out before warping + M.Paralyse(5) + sleep(5) + if(!M) return + + M.loc = prison_cell + if(istype(M, /mob/living/carbon/human)) + var/mob/living/carbon/human/prisoner = M + prisoner.equip_to_slot_or_del(new /obj/item/clothing/under/color/orange(prisoner), slot_w_uniform) + prisoner.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(prisoner), slot_shoes) + + to_chat(M, "You have been sent to the prison station!") + log_admin("[key_name(usr)] sent [key_name(M)] to the prison station.") + message_admins("[key_name_admin(usr)] sent [key_name_admin(M)] to the prison station.", 1) + + else if(href_list["sendbacktolobby"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locateUID(href_list["sendbacktolobby"]) + + if(!isobserver(M)) + to_chat(usr, "You can only send ghost players back to the Lobby.") + return + + if(!M.client) + to_chat(usr, "[M] doesn't seem to have an active client.") + return + + if(alert(usr, "Send [key_name(M)] back to Lobby?", "Message", "Yes", "No") != "Yes") + return + + log_admin("[key_name(usr)] has sent [key_name(M)] back to the Lobby.") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] back to the Lobby.") + + var/mob/new_player/NP = new() + GLOB.non_respawnable_keys -= M.ckey + NP.ckey = M.ckey + qdel(M) + + else if(href_list["tdome1"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locateUID(href_list["tdome1"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(istype(M, /mob/living/silicon/ai)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + return + + for(var/obj/item/I in M) + M.unEquip(I) + if(I) + I.loc = M.loc + I.layer = initial(I.layer) + I.plane = initial(I.plane) + I.dropped(M) + + M.Paralyse(5) + sleep(5) + M.loc = pick(tdome1) + spawn(50) + to_chat(M, "You have been sent to the Thunderdome.") + log_admin("[key_name(usr)] has sent [key_name(M)] to the thunderdome. (Team 1)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the thunderdome. (Team 1)", 1) + + else if(href_list["tdome2"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locateUID(href_list["tdome2"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(istype(M, /mob/living/silicon/ai)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + return + + for(var/obj/item/I in M) + M.unEquip(I) + if(I) + I.loc = M.loc + I.layer = initial(I.layer) + I.plane = initial(I.plane) + I.dropped(M) + + M.Paralyse(5) + sleep(5) + M.loc = pick(tdome2) + spawn(50) + to_chat(M, "You have been sent to the Thunderdome.") + log_admin("[key_name(usr)] has sent [key_name(M)] to the thunderdome. (Team 2)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the thunderdome. (Team 2)", 1) + + else if(href_list["tdomeadmin"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locateUID(href_list["tdomeadmin"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(istype(M, /mob/living/silicon/ai)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + return + + M.Paralyse(5) + sleep(5) + M.loc = pick(tdomeadmin) + spawn(50) + to_chat(M, "You have been sent to the Thunderdome.") + log_admin("[key_name(usr)] has sent [key_name(M)] to the thunderdome. (Admin.)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the thunderdome. (Admin.)", 1) + + else if(href_list["tdomeobserve"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locateUID(href_list["tdomeobserve"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(istype(M, /mob/living/silicon/ai)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + return + + for(var/obj/item/I in M) + M.unEquip(I) + if(I) + I.loc = M.loc + I.layer = initial(I.layer) + I.plane = initial(I.plane) + I.dropped(M) + + if(istype(M, /mob/living/carbon/human)) + var/mob/living/carbon/human/observer = M + observer.equip_to_slot_or_del(new /obj/item/clothing/under/suit_jacket(observer), slot_w_uniform) + observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/black(observer), slot_shoes) + M.Paralyse(5) + sleep(5) + M.loc = pick(tdomeobserve) + spawn(50) + to_chat(M, "You have been sent to the Thunderdome.") + log_admin("[key_name(usr)] has sent [key_name(M)] to the thunderdome. (Observer.)") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the thunderdome. (Observer.)", 1) + + else if(href_list["aroomwarp"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") + return + + var/mob/M = locateUID(href_list["aroomwarp"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(istype(M, /mob/living/silicon/ai)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai") + return + + M.Paralyse(5) + sleep(5) + M.loc = pick(aroomwarp) + spawn(50) + to_chat(M, "You have been sent to the Admin Room!.") + log_admin("[key_name(usr)] has sent [key_name(M)] to the Admin Room") + message_admins("[key_name_admin(usr)] has sent [key_name_admin(M)] to the Admin Room", 1) + + + else if(href_list["revive"]) + if(!check_rights(R_REJUVINATE)) return + + var/mob/living/L = locateUID(href_list["revive"]) + if(!istype(L)) + to_chat(usr, "This can only be used on instances of type /mob/living") + return + + L.revive() + message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(L)]!", 1) + log_admin("[key_name(usr)] healed / revived [key_name(L)]") + + else if(href_list["makeai"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makeai"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + + if(alert(usr, "Confirm make ai?",, "Yes", "No") != "Yes") + return + + message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!", 1) + log_admin("[key_name(usr)] AIized [key_name(H)]") + var/mob/living/silicon/ai/ai_character = H.AIize() + ai_character.moveToAILandmark() + + else if(href_list["makealien"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makealien"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(alert(usr, "Confirm make alien?",, "Yes", "No") != "Yes") + return + + usr.client.cmd_admin_alienize(H) + + else if(href_list["makeslime"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makeslime"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(alert(usr, "Confirm make slime?",, "Yes", "No") != "Yes") + return + + usr.client.cmd_admin_slimeize(H) + + else if(href_list["makesuper"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makesuper"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + + if(alert(usr, "Confirm make superhero?",, "Yes", "No") != "Yes") + return + + usr.client.cmd_admin_super(H) + + else if(href_list["makerobot"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["makerobot"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(alert(usr, "Confirm make robot?",, "Yes", "No") != "Yes") + return + + usr.client.cmd_admin_robotize(H) + + else if(href_list["makeanimal"]) + if(!check_rights(R_SPAWN)) return + + var/mob/M = locateUID(href_list["makeanimal"]) + if(istype(M, /mob/new_player)) + to_chat(usr, "This cannot be used on instances of type /mob/new_player") + return + if(alert(usr, "Confirm make animal?",, "Yes", "No") != "Yes") + return + + usr.client.cmd_admin_animalize(M) + + else if(href_list["incarn_ghost"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/dead/observer/G = locateUID(href_list["incarn_ghost"]) + if(!istype(G)) + to_chat(usr, "This will only work on /mob/dead/observer") + + var/posttransformoutfit = usr.client.robust_dress_shop() + + var/mob/living/carbon/human/H = G.incarnate_ghost() + + if(posttransformoutfit && istype(H)) + H.equipOutfit(posttransformoutfit) + + log_admin("[key_name(G)] was incarnated by [key_name(owner)]") + message_admins("[key_name_admin(G)] was incarnated by [key_name_admin(owner)]") + + else if(href_list["togmutate"]) + if(!check_rights(R_SPAWN)) return + + var/mob/living/carbon/human/H = locateUID(href_list["togmutate"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + var/block=text2num(href_list["block"]) + //testing("togmutate([href_list["block"]] -> [block])") + usr.client.cmd_admin_toggle_block(H,block) + show_player_panel(H) + //H.regenerate_icons() + + else if(href_list["adminplayeropts"]) + var/mob/M = locateUID(href_list["adminplayeropts"]) + show_player_panel(M) + + else if(href_list["adminplayerobservefollow"]) + var/client/C = usr.client + if(!isobserver(usr)) + if(!check_rights(R_ADMIN|R_MOD)) // Need to be mod or admin to aghost + return + C.admin_ghost() + var/mob/M = locateUID(href_list["adminplayerobservefollow"]) + var/mob/dead/observer/A = C.mob + sleep(2) + A.ManualFollow(M) + + else if(href_list["check_antagonist"]) + check_antagonists() + + else if(href_list["take_question"]) + var/index = text2num(href_list["take_question"]) + + if(href_list["is_mhelp"]) + SSmentor_tickets.takeTicket(index) + else //Ahelp + SStickets.takeTicket(index) + + else if(href_list["resolve"]) + var/index = text2num(href_list["resolve"]) + if(href_list["is_mhelp"]) + SSmentor_tickets.resolveTicket(index) + else //Ahelp + SStickets.resolveTicket(index) + + else if(href_list["autorespond"]) + var/index = text2num(href_list["autorespond"]) + if(!check_rights(R_ADMIN|R_MOD)) + return + SStickets.autoRespond(index) + + else if(href_list["cult_nextobj"]) + if(alert(usr, "Validate the current Cult objective and unlock the next one?", "Cult Cheat Code", "Yes", "No") != "Yes") + return + + if(!GAMEMODE_IS_CULT) + alert("Couldn't locate cult mode datum! This shouldn't ever happen, tell a coder!") + return + + var/datum/game_mode/cult/cult_round = SSticker.mode + cult_round.bypass_phase() + message_admins("Admin [key_name_admin(usr)] has unlocked the Cult's next objective.") + log_admin("Admin [key_name_admin(usr)] has unlocked the Cult's next objective.") + + else if(href_list["cult_mindspeak"]) + var/input = stripped_input(usr, "Communicate to all the cultists with the voice of [SSticker.cultdat.entity_name]", "Voice of [SSticker.cultdat.entity_name]", "") + if(!input) + return + + for(var/datum/mind/H in SSticker.mode.cult) + if (H.current) + to_chat(H.current, "[SSticker.cultdat.entity_name] murmurs, [input]") + + for(var/mob/dead/observer/O in GLOB.player_list) + to_chat(O, "[SSticker.cultdat.entity_name] murmurs, [input]") + + message_admins("Admin [key_name_admin(usr)] has talked with the Voice of [SSticker.cultdat.entity_name].") + log_admin("[key_name(usr)] Voice of [SSticker.cultdat.entity_name]: [input]") + + else if(href_list["adminplayerobservecoodjump"]) + if(!check_rights(R_ADMIN)) return + + var/x = text2num(href_list["X"]) + var/y = text2num(href_list["Y"]) + var/z = text2num(href_list["Z"]) + + var/client/C = usr.client + if(!isobserver(usr)) C.admin_ghost() + sleep(2) + C.jumptocoord(x,y,z) + + else if(href_list["adminchecklaws"]) + output_ai_laws() + + else if(href_list["adminmoreinfo"]) + var/mob/M = locateUID(href_list["adminmoreinfo"]) + admin_mob_info(M) + + else if(href_list["adminspawncookie"]) + if(!check_rights(R_ADMIN|R_EVENT)) return + + var/mob/living/carbon/human/H = locateUID(href_list["adminspawncookie"]) + if(!ishuman(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + + H.equip_to_slot_or_del( new /obj/item/reagent_containers/food/snacks/cookie(H), slot_l_hand ) + if(!(istype(H.l_hand,/obj/item/reagent_containers/food/snacks/cookie))) + H.equip_to_slot_or_del( new /obj/item/reagent_containers/food/snacks/cookie(H), slot_r_hand ) + if(!(istype(H.r_hand,/obj/item/reagent_containers/food/snacks/cookie))) + log_admin("[key_name(H)] has their hands full, so they did not receive their cookie, spawned by [key_name(src.owner)].") + message_admins("[key_name_admin(H)] has [H.p_their()] hands full, so [H.p_they()] did not receive [H.p_their()] cookie, spawned by [key_name_admin(src.owner)].") + return + else + H.update_inv_r_hand()//To ensure the icon appears in the HUD + else + H.update_inv_l_hand() + log_admin("[key_name(H)] got their cookie, spawned by [key_name(src.owner)]") + message_admins("[key_name_admin(H)] got [H.p_their()] cookie, spawned by [key_name_admin(src.owner)]") + feedback_inc("admin_cookies_spawned",1) + to_chat(H, "Your prayers have been answered!! You received the best cookie!") + + else if(href_list["BlueSpaceArtillery"]) + if(!check_rights(R_ADMIN|R_EVENT)) return + + var/mob/living/M = locateUID(href_list["BlueSpaceArtillery"]) + if(!isliving(M)) + to_chat(usr, "This can only be used on instances of type /mob/living") + return + + if(alert(owner, "Are you sure you wish to hit [key_name(M)] with Bluespace Artillery?", "Confirm Firing?" , "Yes" , "No") != "Yes") + return + + if(BSACooldown) + to_chat(owner, "Standby. Reload cycle in progress. Gunnery crews ready in five seconds!") + return + + BSACooldown = 1 + spawn(50) + BSACooldown = 0 + + to_chat(M, "You've been hit by bluespace artillery!") + log_admin("[key_name(M)] has been hit by Bluespace Artillery fired by [key_name(owner)]") + message_admins("[key_name_admin(M)] has been hit by Bluespace Artillery fired by [key_name_admin(owner)]") + + var/turf/simulated/floor/T = get_turf(M) + if(istype(T)) + if(prob(80)) + T.break_tile_to_plating() + else + T.break_tile() + + if(M.health <= 1) + M.gib() + else + M.adjustBruteLoss(min(99,(M.health - 1))) + M.Stun(20) + M.Weaken(20) + M.Stuttering(20) + + else if(href_list["CentcommReply"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locateUID(href_list["CentcommReply"]) + usr.client.admin_headset_message(M, "Centcomm") + + else if(href_list["SyndicateReply"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locateUID(href_list["SyndicateReply"]) + usr.client.admin_headset_message(M, "Syndicate") + + else if(href_list["HeadsetMessage"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locateUID(href_list["HeadsetMessage"]) + usr.client.admin_headset_message(M) + + else if(href_list["EvilFax"]) + if(!check_rights(R_ADMIN)) + return + var/mob/living/carbon/human/H = locateUID(href_list["EvilFax"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + var/etypes = list("Borgification", "Corgification", "Death By Fire", "Total Brain Death", "Honk Tumor", "Cluwne", "Demote", "Demote with Bot", "Revoke Fax Access", "Angry Fax Machine") + var/eviltype = input(src.owner, "Which type of evil fax do you wish to send [H]?","Its good to be baaaad...", "") as null|anything in etypes + if(!(eviltype in etypes)) + return + var/customname = clean_input("Pick a title for the evil fax.", "Fax Title", , owner) + if(!customname) + customname = "paper" + var/obj/item/paper/evilfax/P = new /obj/item/paper/evilfax(null) + var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) + + P.name = "Central Command - [customname]" + P.info = "You really should've known better." + P.myeffect = eviltype + P.mytarget = H + if(alert("Do you want the Evil Fax to activate automatically if [H] tries to ignore it?",,"Yes", "No") == "Yes") + P.activate_on_timeout = 1 + P.x = rand(-2, 0) + P.y = rand(-1, 2) + P.offset_x += P.x + P.offset_y += P.y + P.update_icon() + var/stampvalue = "cent" + var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') + stampoverlay.icon_state = "paper_stamp-[stampvalue]" + stampoverlay.pixel_x = P.x + stampoverlay.pixel_y = P.y + P.stamped = list() + P.stamped += /obj/item/stamp/centcom + if(!P.ico) + P.ico = new + P.ico += "paper_stamp-[stampvalue]" + P.overlays += stampoverlay + P.stamps += "
        " + P.update_icon() + P.faxmachineid = fax.UID() + P.loc = fax.loc // Do not use fax.receivefax(P) here, as it won't preserve the type. Physically teleporting the fax paper is required. + if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) + to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") + to_chat(src.owner, "You sent a [eviltype] fax to [H]") + log_admin("[key_name(src.owner)] sent [key_name(H)] a [eviltype] fax") + message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a [eviltype] fax") + else if(href_list["Bless"]) + if(!check_rights(R_EVENT)) + return + var/mob/living/M = locateUID(href_list["Bless"]) + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob/living") + return + var/btypes = list("To Arrivals", "Moderate Heal") + var/mob/living/carbon/human/H + if(ishuman(M)) + H = M + btypes += "Heal Over Time" + btypes += "Permanent Regeneration" + btypes += "Super Powers" + btypes += "Scarab Guardian" + btypes += "Human Protector" + btypes += "Sentient Pet" + btypes += "All Access" + var/blessing = input(owner, "How would you like to bless [M]?", "Its good to be good...", "") as null|anything in btypes + if(!(blessing in btypes)) + return + var/logmsg = null + switch(blessing) + if("To Arrivals") + M.forceMove(pick(latejoin)) + to_chat(M, "You are abruptly pulled through space!") + logmsg = "a teleport to arrivals." + if("Moderate Heal") + M.adjustBruteLoss(-25) + M.adjustFireLoss(-25) + M.adjustToxLoss(-25) + M.adjustOxyLoss(-25) + to_chat(M,"You feel invigorated!") + logmsg = "a moderate heal." + if("Heal Over Time") + H.reagents.add_reagent("salglu_solution", 30) + H.reagents.add_reagent("salbutamol", 20) + H.reagents.add_reagent("spaceacillin", 20) + logmsg = "a heal over time." + if("Permanent Regeneration") + H.dna.SetSEState(REGENERATEBLOCK, 1) + genemutcheck(H, REGENERATEBLOCK, null, MUTCHK_FORCED) + H.update_mutations() + H.gene_stability = 100 + logmsg = "permanent regeneration." + if("Super Powers") + var/list/default_genes = list(REGENERATEBLOCK, BREATHLESSBLOCK, COLDBLOCK) + for(var/gene in default_genes) + H.dna.SetSEState(gene, 1) + genemutcheck(H, gene, null, MUTCHK_FORCED) + H.update_mutations() + H.gene_stability = 100 + logmsg = "superpowers." + if("Scarab Guardian") + var/obj/item/guardiancreator/biological/scarab = new /obj/item/guardiancreator/biological(H) + var/list/possible_guardians = list("Chaos", "Standard", "Ranged", "Support", "Explosive", "Random") + var/typechoice = input("Select Guardian Type", "Type") as null|anything in possible_guardians + if(isnull(typechoice)) + return + if(typechoice != "Random") + possible_guardians -= "Random" + scarab.possible_guardians = list() + scarab.possible_guardians += typechoice + scarab.attack_self(H) + spawn(700) + qdel(scarab) + logmsg = "scarab guardian." + if("Sentient Pet") + var/pets = subtypesof(/mob/living/simple_animal) + var/petchoice = input("Select pet type", "Pets") as null|anything in pets + if(isnull(petchoice)) + return + var/list/mob/dead/observer/candidates = pollCandidates("Play as the special event pet [H]?", poll_time = 200, min_hours = 10) + var/mob/dead/observer/theghost = null + if(candidates.len) + var/mob/living/simple_animal/pet/P = new petchoice(H.loc) + theghost = pick(candidates) + P.key = theghost.key + P.master_commander = H + P.universal_speak = 1 + P.universal_understand = 1 + P.can_collar = 1 + P.faction = list("neutral") + var/obj/item/clothing/accessory/petcollar/C = new + P.add_collar(C) + var/obj/item/card/id/I = H.wear_id + if(I) + var/obj/item/card/id/D = new /obj/item/card/id(C) + D.access = I.access + D.registered_name = P.name + D.assignment = "Pet" + C.access_id = D + spawn(30) + var/newname = sanitize(copytext(input(P, "You are [P], special event pet of [H]. Change your name to something else?", "Name change", P.name) as null|text,1,MAX_NAME_LEN)) + if(newname && newname != P.name) + P.name = newname + if(P.mind) + P.mind.name = newname + logmsg = "pet ([P])." + else + to_chat(usr, "WARNING: Nobody volunteered to play the special event pet.") + logmsg = "pet (no volunteers)." + if("Human Protector") + usr.client.create_eventmob_for(H, 0) + logmsg = "syndie protector." + if("All Access") + var/obj/item/card/id/I = H.wear_id + if(I) + var/list/access_to_give = get_all_accesses() + for(var/this_access in access_to_give) + if(!(this_access in I.access)) + // don't have it - add it + I.access |= this_access + else + to_chat(usr, "ERROR: [H] is not wearing an ID card.") + logmsg = "all access." + if(logmsg) + log_admin("[key_name(owner)] answered [key_name(M)]'s prayer with a blessing: [logmsg]") + message_admins("[key_name_admin(owner)] answered [key_name_admin(M)]'s prayer with a blessing: [logmsg]") + else if(href_list["Smite"]) + if(!check_rights(R_EVENT)) + return + var/mob/living/M = locateUID(href_list["Smite"]) + var/mob/living/carbon/human/H + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob/living") + return + var/ptypes = list("Lightning bolt", "Fire Death", "Gib") + if(ishuman(M)) + H = M + ptypes += "Brain Damage" + ptypes += "Honk Tumor" + ptypes += "Hallucinate" + ptypes += "Cold" + ptypes += "Hunger" + ptypes += "Cluwne" + ptypes += "Mutagen Cookie" + ptypes += "Hellwater Cookie" + ptypes += "Hunter" + ptypes += "Crew Traitor" + ptypes += "Floor Cluwne" + ptypes += "Shamebrero" + var/punishment = input(owner, "How would you like to smite [M]?", "Its good to be baaaad...", "") as null|anything in ptypes + if(!(punishment in ptypes)) + return + var/logmsg = null + switch(punishment) + if("Lightning bolt") + M.electrocute_act(5, "Lightning Bolt", safety = TRUE, override = TRUE) + playsound(get_turf(M), 'sound/magic/lightningshock.ogg', 50, 1, -1) + M.adjustFireLoss(75) + M.Weaken(5) + to_chat(M, "The gods have punished you for your sins!") + logmsg = "a lightning bolt." + if("Fire Death") + to_chat(M,"You feel hotter than usual. Maybe you should lowe-wait, is that your hand melting?") + var/turf/simulated/T = get_turf(M) + new /obj/effect/hotspot(T) + M.adjustFireLoss(150) + logmsg = "a firey death." + if("Gib") + M.gib(FALSE) + logmsg = "gibbed." + + if("Brain Damage") + H.adjustBrainLoss(75) + logmsg = "75 brain damage." + if("Honk Tumor") + if(!H.get_int_organ(/obj/item/organ/internal/honktumor)) + var/obj/item/organ/internal/organ = new /obj/item/organ/internal/honktumor + to_chat(H, "Life seems funnier, somehow.") + organ.insert(H) + logmsg = "a honk tumor." + if("Hallucinate") + H.Hallucinate(1000) + logmsg = "hallucinations." + if("Cold") + H.reagents.add_reagent("frostoil", 40) + H.reagents.add_reagent("ice", 40) + logmsg = "cold." + if("Hunger") + H.set_nutrition(NUTRITION_LEVEL_CURSED) + logmsg = "starvation." + if("Cluwne") + H.makeCluwne() + H.mutations |= NOCLONE + logmsg = "cluwned." + if("Mutagen Cookie") + var/obj/item/reagent_containers/food/snacks/cookie/evilcookie = new /obj/item/reagent_containers/food/snacks/cookie + evilcookie.reagents.add_reagent("mutagen", 10) + evilcookie.desc = "It has a faint green glow." + evilcookie.bitesize = 100 + evilcookie.flags = NODROP | DROPDEL + H.drop_l_hand() + H.equip_to_slot_or_del(evilcookie, slot_l_hand) + logmsg = "a mutagen cookie." + if("Hellwater Cookie") + var/obj/item/reagent_containers/food/snacks/cookie/evilcookie = new /obj/item/reagent_containers/food/snacks/cookie + evilcookie.reagents.add_reagent("hell_water", 25) + evilcookie.desc = "Sulphur-flavored." + evilcookie.bitesize = 100 + evilcookie.flags = NODROP | DROPDEL + H.drop_l_hand() + H.equip_to_slot_or_del(evilcookie, slot_l_hand) + logmsg = "a hellwater cookie." + if("Hunter") + H.mutations |= NOCLONE + usr.client.create_eventmob_for(H, 1) + logmsg = "hunter." + if("Crew Traitor") + if(!H.mind) + to_chat(usr, "This mob has no mind!") + return + + var/list/possible_traitors = list() + for(var/mob/living/player in GLOB.living_mob_list) + if(player.client && player.mind && player.stat != DEAD && player != H) + if(ishuman(player) && !player.mind.special_role) + if(player.client && (ROLE_TRAITOR in player.client.prefs.be_special) && !jobban_isbanned(player, ROLE_TRAITOR) && !jobban_isbanned(player, "Syndicate")) + possible_traitors += player.mind + + for(var/datum/mind/player in possible_traitors) + if(player.current) + if(ismindshielded(player.current)) + possible_traitors -= player + + if(possible_traitors.len) + var/datum/mind/newtraitormind = pick(possible_traitors) + var/datum/objective/assassinate/kill_objective = new() + kill_objective.target = H.mind + kill_objective.owner = newtraitormind + kill_objective.explanation_text = "Assassinate [H.mind], the [H.mind.assigned_role]" + H.mind.objectives += kill_objective + var/datum/antagonist/traitor/T = new() + T.give_objectives = FALSE + to_chat(newtraitormind, "ATTENTION: It is time to pay your debt to the Syndicate...") + to_chat(newtraitormind, "Goal: KILL [H.real_name], currently in [get_area(H.loc)]") + newtraitormind.add_antag_datum(T) + else + to_chat(usr, "ERROR: Failed to create a traitor.") + return + logmsg = "crew traitor." + + if("Floor Cluwne") + var/turf/T = get_turf(M) + var/mob/living/simple_animal/hostile/floor_cluwne/FC = new /mob/living/simple_animal/hostile/floor_cluwne(T) + FC.smiting = TRUE + FC.Acquire_Victim(M) + logmsg = "floor cluwne" + if("Shamebrero") + if(H.head) + H.unEquip(H.head, TRUE) + var/obj/item/clothing/head/sombrero/shamebrero/S = new(H.loc) + H.equip_to_slot_or_del(S, slot_head) + logmsg = "shamebrero" + if(logmsg) + log_admin("[key_name(owner)] smited [key_name(M)] with: [logmsg]") + message_admins("[key_name_admin(owner)] smited [key_name_admin(M)] with: [logmsg]") + else if(href_list["cryossd"]) + if(!check_rights(R_ADMIN)) + return + var/mob/living/carbon/human/H = locateUID(href_list["cryossd"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(!href_list["cryoafk"] && !isLivingSSD(H)) + to_chat(usr, "This can only be used on living, SSD players.") + return + if(istype(H.loc, /obj/machinery/cryopod)) + var/obj/machinery/cryopod/P = H.loc + P.despawn_occupant() + log_admin("[key_name(usr)] despawned [H.job] [H] in cryo.") + message_admins("[key_name_admin(usr)] despawned [H.job] [H] in cryo.") + else if(cryo_ssd(H)) + log_admin("[key_name(usr)] sent [H.job] [H] to cryo.") + message_admins("[key_name_admin(usr)] sent [H.job] [H] to cryo.") + if(href_list["cryoafk"]) // Warn them if they are send to storage and are AFK + to_chat(H, "The admins have moved you to cryo storage for being AFK. Please eject yourself (right click, eject) out of the cryostorage if you want to avoid being despawned.") + SEND_SOUND(H, 'sound/effects/adminhelp.ogg') + if(H.client) + window_flash(H.client) + else if(href_list["FaxReplyTemplate"]) + if(!check_rights(R_ADMIN)) + return + var/mob/living/carbon/human/H = locateUID(href_list["FaxReplyTemplate"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + var/obj/item/paper/P = new /obj/item/paper(null) + var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) + P.name = "Central Command - paper" + var/stypes = list("Handle it yourselves!","Illegible fax","Fax not signed","Not Right Now","You are wasting our time", "Keep up the good work", "ERT Instructions") + var/stype = input(src.owner, "Which type of standard reply do you wish to send to [H]?","Choose your paperwork", "") as null|anything in stypes + var/tmsg = "



        Nanotrasen Science Station [using_map.station_short]


        NAS Trurl Communications Department Report


        " + if(stype == "Handle it yourselves!") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Please proceed in accordance with Standard Operating Procedure and/or Space Law. You are fully trained to handle this situation without Central Command intervention.

        This is an automatic message." + else if(stype == "Illegible fax") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Your fax's grammar, syntax and/or typography are of a sub-par level and do not allow us to understand the contents of the message.

        Please consult your nearest dictionary and/or thesaurus and try again.

        This is an automatic message." + else if(stype == "Fax not signed") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Your fax has not been correctly signed and, as such, we cannot verify your identity.

        Please sign your faxes before sending them so that we may verify your identity.

        This is an automatic message." + else if(stype == "Not Right Now") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Due to pressing concerns of a matter above your current paygrade, we are unable to provide assistance in whatever matter your fax referenced.

        This can be either due to a power outage, bureaucratic audit, pest infestation, Ascendance Event, corgi outbreak, or any other situation that would affect the proper functioning of the NAS Trurl.

        Please try again later.

        This is an automatic message." + else if(stype == "You are wasting our time") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        In the interest of preventing further mismanagement of company resources, please avoid wasting our time with such petty drivel.

        Do kindly remember that we expect our workforce to maintain at least a semi-decent level of profesionalism. Do not test our patience.

        This is an automatic message." + else if(stype == "Keep up the good work") + tmsg += "Greetings, esteemed crewmember. Your fax has been received successfully by NAS Trurl Fax Registration.

        We at the NAS Trurl appreciate the good work that you have done here, and sincerely recommend that you continue such a display of dedication to the company.

        This is absolutely not an automated message." + else if(stype == "ERT Instructions") + tmsg += "Greetings, esteemed crewmember. Your fax has been DECLINED automatically by NAS Trurl Fax Registration.

        Please utilize the Card Swipers if you wish to call for an ERT.

        This is an automated message." + else + return + tmsg += "
        " + P.info = tmsg + P.x = rand(-2, 0) + P.y = rand(-1, 2) + P.offset_x += P.x + P.offset_y += P.y + P.update_icon() + var/stampvalue = "cent" + var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') + stampoverlay.icon_state = "paper_stamp-[stampvalue]" + stampoverlay.pixel_x = P.x + stampoverlay.pixel_y = P.y + P.stamped = list() + P.stamped += /obj/item/stamp/centcom + if(!P.ico) + P.ico = new + P.ico += "paper_stamp-[stampvalue]" + P.overlays += stampoverlay + P.stamps += "
        " + P.update_icon() + fax.receivefax(P) + if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) + to_chat(H, "Your headset pings, notifying you that a reply to your fax has arrived.") + to_chat(src.owner, "You sent a standard '[stype]' fax to [H]") + log_admin("[key_name(src.owner)] sent [key_name(H)] a standard '[stype]' fax") + message_admins("[key_name_admin(src.owner)] replied to [key_name_admin(H)] with a standard '[stype]' fax") + + else if(href_list["HONKReply"]) + var/mob/living/carbon/human/H = locateUID(href_list["HONKReply"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(!istype(H.l_ear, /obj/item/radio/headset) && !istype(H.r_ear, /obj/item/radio/headset)) + to_chat(usr, "The person you are trying to contact is not wearing a headset") + return + + var/input = input(src.owner, "Please enter a message to reply to [key_name(H)] via [H.p_their()] headset.","Outgoing message from HONKplanet", "") + if(!input) return + + to_chat(src.owner, "You sent [input] to [H] via a secure channel.") + log_admin("[src.owner] replied to [key_name(H)]'s HONKplanet message with the message [input].") + to_chat(H, "You hear something crackle in your headset for a moment before a voice speaks. \"Please stand by for a message from your HONKbrothers. Message as follows, HONK. [input]. Message ends, HONK.\"") + + else if(href_list["ErtReply"]) + if(!check_rights(R_ADMIN)) + return + + if(alert(src.owner, "Accept or Deny ERT request?", "CentComm Response", "Accept", "Deny") == "Deny") + var/mob/living/carbon/human/H = locateUID(href_list["ErtReply"]) + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(H.stat != 0) + to_chat(usr, "The person you are trying to contact is not conscious.") + return + if(!istype(H.l_ear, /obj/item/radio/headset) && !istype(H.r_ear, /obj/item/radio/headset)) + to_chat(usr, "The person you are trying to contact is not wearing a headset") + return + + var/input = input(src.owner, "Please enter a reason for denying [key_name(H)]'s ERT request.","Outgoing message from CentComm", "") + if(!input) return + ert_request_answered = TRUE + to_chat(src.owner, "You sent [input] to [H] via a secure channel.") + log_admin("[src.owner] denied [key_name(H)]'s ERT request with the message [input].") + to_chat(H, "Incoming priority transmission from Central Command. Message as follows, Your ERT request has been denied for the following reasons: [input].") + else + src.owner.response_team() + + + else if(href_list["AdminFaxView"]) + if(!check_rights(R_ADMIN)) + return + + var/obj/item/fax = locate(href_list["AdminFaxView"]) + if(istype(fax, /obj/item/paper)) + var/obj/item/paper/P = fax + P.show_content(usr,1) + else if(istype(fax, /obj/item/photo)) + var/obj/item/photo/H = fax + H.show(usr) + else if(istype(fax, /obj/item/paper_bundle)) + //having multiple people turning pages on a paper_bundle can cause issues + //open a browse window listing the contents instead + var/data = "" + var/obj/item/paper_bundle/B = fax + + for(var/page = 1, page <= B.amount + 1, page++) + var/obj/pageobj = B.contents[page] + data += "Page [page] - [pageobj.name]
        " + + usr << browse(data, "window=PaperBundle[B.UID()]") + else + to_chat(usr, "The faxed item is not viewable. This is probably a bug, and should be reported on the tracker: [fax.type]") + + else if(href_list["AdminFaxViewPage"]) + if(!check_rights(R_ADMIN)) + return + + var/page = text2num(href_list["AdminFaxViewPage"]) + var/obj/item/paper_bundle/bundle = locate(href_list["paper_bundle"]) + + if(!bundle) return + + if(istype(bundle.contents[page], /obj/item/paper)) + var/obj/item/paper/P = bundle.contents[page] + P.show_content(usr, 1) + else if(istype(bundle.contents[page], /obj/item/photo)) + var/obj/item/photo/H = bundle.contents[page] + H.show(usr) + return + + else if(href_list["AdminFaxCreate"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/sender = locate(href_list["AdminFaxCreate"]) + var/obj/machinery/photocopier/faxmachine/fax = locate(href_list["originfax"]) + var/faxtype = href_list["faxtype"] + 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 + + if(!fax) + var/list/departmentoptions = alldepartments + hidden_departments + "All Departments" + destination = input(usr, "To which department?", "Choose a department", "") as null|anything in departmentoptions + if(!destination) + qdel(P) + return + + for(var/obj/machinery/photocopier/faxmachine/F in allfaxes) + if(destination != "All Departments" && F.department == destination) + fax = F + + + var/input = input(src.owner, "Please enter a message to send a fax via secure connection. Use
        for line breaks. Both pencode and HTML work.", "Outgoing message from Centcomm", "") as message|null + if(!input) + qdel(P) + return + input = admin_pencode_to_html(html_encode(input)) // Encode everything from pencode to html + + var/customname = clean_input("Pick a title for the fax.", "Fax Title", , owner) + if(!customname) + customname = "paper" + + var/stampname + var/stamptype + var/stampvalue + var/sendername + switch(faxtype) + if("Central Command") + stamptype = "icon" + stampvalue = "cent" + sendername = command_name() + if("Syndicate") + stamptype = "icon" + stampvalue = "syndicate" + sendername = "UNKNOWN" + if("Administrator") + stamptype = input(src.owner, "Pick a stamp type.", "Stamp Type") as null|anything in list("icon","text","none") + if(stamptype == "icon") + stampname = input(src.owner, "Pick a stamp icon.", "Stamp Icon") as null|anything in list("centcom","syndicate","granted","denied","clown") + switch(stampname) + if("centcom") + stampvalue = "cent" + if("syndicate") + stampvalue = "syndicate" + if("granted") + stampvalue = "ok" + if("denied") + stampvalue = "deny" + if("clown") + stampvalue = "clown" + else if(stamptype == "text") + stampvalue = clean_input("What should the stamp say?", "Stamp Text", , owner) + else if(stamptype == "none") + stamptype = "" + else + qdel(P) + return + + sendername = clean_input("What organization does the fax come from? This determines the prefix of the paper (i.e. Central Command- Title). This is optional.", "Organization", , owner) + + if(sender) + notify = alert(src.owner, "Would you like to inform the original sender that a fax has arrived?","Notify Sender","Yes","No") + + // Create the reply message + if(sendername) + P.name = "[sendername]- [customname]" + else + P.name = "[customname]" + P.info = input + P.update_icon() + P.x = rand(-2, 0) + P.y = rand(-1, 2) + P.offset_x += P.x + P.offset_y += P.y + if(stamptype) + var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') + stampoverlay.pixel_x = P.x + stampoverlay.pixel_y = P.y + + if(!P.ico) + P.ico = new + P.ico += "paper_stamp-[stampvalue]" + stampoverlay.icon_state = "paper_stamp-[stampvalue]" + + if(stamptype == "icon") + if(!P.stamped) + P.stamped = new + P.stamped += /obj/item/stamp/centcom + P.overlays += stampoverlay + P.stamps += "
        " + + else if(stamptype == "text") + if(!P.stamped) + P.stamped = new + P.stamped += /obj/item/stamp + P.overlays += stampoverlay + P.stamps += "
        [stampvalue]" + + if(destination != "All Departments") + if(!fax.receivefax(P)) + to_chat(src.owner, "Message transmission failed.") + return + else + for(var/obj/machinery/photocopier/faxmachine/F in allfaxes) + if(is_station_level(F.z)) + spawn(0) + if(!F.receivefax(P)) + to_chat(src.owner, "Message transmission to [F.department] failed.") + + var/datum/fax/admin/A = new /datum/fax/admin() + A.name = P.name + A.from_department = faxtype + if(destination != "All Departments") + A.to_department = fax.department + else + A.to_department = "All Departments" + A.origin = "Administrator" + A.message = P + A.reply_to = reply_to + A.sent_by = usr + A.sent_at = world.time + + to_chat(src.owner, "Message transmitted successfully.") + if(notify == "Yes") + var/mob/living/carbon/human/H = sender + if(istype(H) && H.stat == CONSCIOUS && (istype(H.l_ear, /obj/item/radio/headset) || istype(H.r_ear, /obj/item/radio/headset))) + to_chat(sender, "Your headset pings, notifying you that a reply to your fax has arrived.") + if(sender) + log_admin("[key_name(src.owner)] replied to a fax message from [key_name(sender)]: [input]") + message_admins("[key_name_admin(src.owner)] replied to a fax message from [key_name_admin(sender)] (VIEW).", 1) + else + log_admin("[key_name(src.owner)] sent a fax message to [destination]: [input]") + message_admins("[key_name_admin(src.owner)] sent a fax message to [destination] (VIEW).", 1) + return + + else if(href_list["refreshfaxpanel"]) + if(!check_rights(R_ADMIN)) + return + + fax_panel(usr) + + else if(href_list["getplaytimewindow"]) + if(!check_rights(R_ADMIN)) + return + var/mob/M = locateUID(href_list["getplaytimewindow"]) + if(!M) + to_chat(usr, "ERROR: Mob not found.") + return + cmd_mentor_show_exp_panel(M.client) + + else if(href_list["jumpto"]) + if(!check_rights(R_ADMIN)) return + + var/mob/M = locateUID(href_list["jumpto"]) + usr.client.jumptomob(M) + + else if(href_list["getmob"]) + if(!check_rights(R_ADMIN)) return + + if(alert(usr, "Confirm?", "Message", "Yes", "No") != "Yes") return + var/mob/M = locateUID(href_list["getmob"]) + usr.client.Getmob(M) + + else if(href_list["sendmob"]) + if(!check_rights(R_ADMIN)) return + + var/mob/M = locateUID(href_list["sendmob"]) + usr.client.sendmob(M) + + else if(href_list["narrateto"]) + if(!check_rights(R_ADMIN)) return + + var/mob/M = locateUID(href_list["narrateto"]) + usr.client.cmd_admin_direct_narrate(M) + + else if(href_list["subtlemessage"]) + if(!check_rights(R_ADMIN)) return + + var/mob/M = locateUID(href_list["subtlemessage"]) + usr.client.cmd_admin_subtle_message(M) + + else if(href_list["traitor"]) + if(!check_rights(R_ADMIN|R_MOD)) return + + if(!SSticker || !SSticker.mode) + alert("The game hasn't started yet!") + return + + var/mob/M = locateUID(href_list["traitor"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.") + return + show_traitor_panel(M) + + else if(href_list["create_object"]) + if(!check_rights(R_SPAWN)) return + return create_object(usr) + + else if(href_list["quick_create_object"]) + if(!check_rights(R_SPAWN)) return + return quick_create_object(usr) + + else if(href_list["create_turf"]) + if(!check_rights(R_SPAWN)) return + return create_turf(usr) + + else if(href_list["create_mob"]) + if(!check_rights(R_SPAWN)) return + return create_mob(usr) + + else if(href_list["object_list"]) //this is the laggiest thing ever + if(!check_rights(R_SPAWN)) return + + var/atom/loc = usr.loc + + var/dirty_paths + if(istext(href_list["object_list"])) + dirty_paths = list(href_list["object_list"]) + else if(istype(href_list["object_list"], /list)) + dirty_paths = href_list["object_list"] + + var/paths = list() + + for(var/dirty_path in dirty_paths) + var/path = text2path(dirty_path) + if(!path) + continue + else if(!ispath(path, /obj) && !ispath(path, /turf) && !ispath(path, /mob)) + continue + paths += path + + if(!paths) + alert("The path list you sent is empty") + return + if(length(paths) > 5) + alert("Select fewer object types, (max 5)") + return + + var/list/offset = splittext(href_list["offset"],",") + var/number = dd_range(1, 100, text2num(href_list["object_count"])) + var/X = offset.len > 0 ? text2num(offset[1]) : 0 + var/Y = offset.len > 1 ? text2num(offset[2]) : 0 + var/Z = offset.len > 2 ? text2num(offset[3]) : 0 + var/tmp_dir = href_list["object_dir"] + var/obj_dir = tmp_dir ? text2num(tmp_dir) : 2 + if(!obj_dir || !(obj_dir in list(1,2,4,8,5,6,9,10))) + obj_dir = 2 + var/obj_name = sanitize(href_list["object_name"]) + + + var/atom/target //Where the object will be spawned + var/where = href_list["object_where"] + if(!( where in list("onfloor","inhand","inmarked") )) + where = "onfloor" + + + switch(where) + if("inhand") + if(!iscarbon(usr) && !isrobot(usr)) + to_chat(usr, "Can only spawn in hand when you're a carbon mob or cyborg.") + where = "onfloor" + target = usr + + if("onfloor") + switch(href_list["offset_type"]) + if("absolute") + target = locate(0 + X,0 + Y,0 + Z) + if("relative") + target = locate(loc.x + X,loc.y + Y,loc.z + Z) + if("inmarked") + if(!marked_datum) + to_chat(usr, "You don't have any object marked. Abandoning spawn.") + return + else if(!istype(marked_datum,/atom)) + to_chat(usr, "The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.") + return + else + target = marked_datum + + if(target) + for(var/path in paths) + for(var/i = 0; i < number; i++) + if(path in typesof(/turf)) + var/turf/O = target + var/turf/N = O.ChangeTurf(path) + if(N && obj_name) + N.name = obj_name + else + var/atom/O = new path(target) + if(O) + O.admin_spawned = TRUE + O.dir = obj_dir + if(obj_name) + O.name = obj_name + if(istype(O,/mob)) + var/mob/M = O + M.real_name = obj_name + if(where == "inhand" && isliving(usr) && istype(O, /obj/item)) + var/mob/living/L = usr + var/obj/item/I = O + L.put_in_hands(I) + if(isrobot(L)) + var/mob/living/silicon/robot/R = L + if(R.module) + R.module.modules += I + I.loc = R.module + R.module.rebuild() + R.activate_module(I) + R.module.fix_modules() + + if(number == 1) + log_admin("[key_name(usr)] created a [english_list(paths)]") + for(var/path in paths) + if(ispath(path, /mob)) + message_admins("[key_name_admin(usr)] created a [english_list(paths)]") + break + else + log_admin("[key_name(usr)] created [number]ea [english_list(paths)]") + for(var/path in paths) + if(ispath(path, /mob)) + message_admins("[key_name_admin(usr)] created [number]ea [english_list(paths)]") + break + return + + else if(href_list["kick_all_from_lobby"]) + if(!check_rights(R_ADMIN)) + return + if(SSticker && SSticker.current_state == GAME_STATE_PLAYING) + var/afkonly = text2num(href_list["afkonly"]) + if(alert("Are you sure you want to kick all [afkonly ? "AFK" : ""] clients from the lobby?","Confirmation","Yes","Cancel") != "Yes") + return + var/list/listkicked = kick_clients_in_lobby("You were kicked from the lobby by an Administrator.", afkonly) + + var/strkicked = "" + for(var/name in listkicked) + strkicked += "[name], " + message_admins("[key_name_admin(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") + log_admin("[key_name(usr)] has kicked [afkonly ? "all AFK" : "all"] clients from the lobby. [length(listkicked)] clients kicked: [strkicked ? strkicked : "--"]") + else + to_chat(usr, "You may only use this when the game is running.") + + else if(href_list["memoeditlist"]) + if(!check_rights(R_SERVER)) return + var/sql_key = sanitizeSQL("[href_list["memoeditlist"]]") + var/DBQuery/query_memoedits = dbcon.NewQuery("SELECT edits FROM [format_table_name("memo")] WHERE (ckey = '[sql_key]')") + if(!query_memoedits.Execute()) + var/err = query_memoedits.ErrorMsg() + log_game("SQL ERROR obtaining edits from memo table. Error : \[[err]\]\n") + return + if(query_memoedits.NextRow()) + var/edit_log = query_memoedits.item[1] + usr << browse(edit_log,"window=memoeditlist") + + else if(href_list["secretsfun"]) + if(!check_rights(R_SERVER|R_EVENT)) return + + var/ok = 0 + switch(href_list["secretsfun"]) + if("sec_clothes") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","SC") + for(var/obj/item/clothing/under/O in world) + qdel(O) + ok = 1 + if("sec_all_clothes") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","SAC") + for(var/obj/item/clothing/O in world) + qdel(O) + ok = 1 + if("sec_classic1") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","SC1") + for(var/obj/item/clothing/suit/fire/O in world) + qdel(O) + for(var/obj/structure/grille/O in world) + qdel(O) + if("monkey") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","M") + for(var/mob/living/carbon/human/H in GLOB.mob_list) + spawn(0) + H.monkeyize() + ok = 1 + if("corgi") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","M") + for(var/mob/living/carbon/human/H in GLOB.mob_list) + spawn(0) + H.corgize() + ok = 1 + if("honksquad") + if(usr.client.honksquad()) + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","HONK") + if("striketeam") + if(usr.client.strike_team()) + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","Strike") + if("striketeam_syndicate") + if(usr.client.syndicate_strike_team()) + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","Strike") + if("infiltrators_syndicate") + if(usr.client.syndicate_infiltration_team()) + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","SyndieInfiltrationTeam") + if("gimmickteam") + if(usr.client.gimmick_team()) + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","GimmickTeam") + if("tripleAI") + usr.client.triple_ai() + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","TriAI") + if("gravity") + if(!(SSticker && SSticker.mode)) + to_chat(usr, "Please wait until the game starts! Not sure how it will work otherwise.") + return + gravity_is_on = !gravity_is_on + for(var/area/A in world) + A.gravitychange(gravity_is_on,A) + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","Grav") + if(gravity_is_on) + log_admin("[key_name(usr)] toggled gravity on.", 1) + message_admins("[key_name_admin(usr)] toggled gravity on.", 1) + event_announcement.Announce("Gravity generators are again functioning within normal parameters. Sorry for any inconvenience.") + else + log_admin("[key_name(usr)] toggled gravity off.", 1) + message_admins("[key_name_admin(usr)] toggled gravity off.", 1) + event_announcement.Announce("Feedback surge detected in mass-distributions systems. Artifical gravity has been disabled whilst the system reinitializes. Further failures may result in a gravitational collapse and formation of blackholes. Have a nice day.") + + if("power") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","P") + log_admin("[key_name(usr)] made all areas powered", 1) + message_admins("[key_name_admin(usr)] made all areas powered", 1) + power_restore() + if("unpower") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","UP") + log_admin("[key_name(usr)] made all areas unpowered", 1) + message_admins("[key_name_admin(usr)] made all areas unpowered", 1) + power_failure() + if("quickpower") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","QP") + log_admin("[key_name(usr)] made all SMESs powered", 1) + message_admins("[key_name_admin(usr)] made all SMESs powered", 1) + power_restore_quick() + if("prisonwarp") + if(!SSticker) + alert("The game hasn't started yet!", null, null, null, null, null) + return + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","PW") + message_admins("[key_name_admin(usr)] teleported all players to the prison station.", 1) + for(var/mob/living/carbon/human/H in GLOB.mob_list) + var/turf/loc = find_loc(H) + var/security = 0 + if(!is_station_level(loc.z) || prisonwarped.Find(H)) + +//don't warp them if they aren't ready or are already there + continue + H.Paralyse(5) + if(H.wear_id) + var/obj/item/card/id/id = H.get_idcard() + for(var/A in id.access) + if(A == ACCESS_SECURITY) + security++ + if(!security) + //strip their stuff before they teleport into a cell :downs: + for(var/obj/item/W in H) + if(istype(W, /obj/item/organ/external)) + continue + //don't strip organs + H.unEquip(W) + if(H.client) + H.client.screen -= W + if(W) + W.loc = H.loc + W.dropped(H) + W.layer = initial(W.layer) + W.plane = initial(W.plane) + //teleport person to cell + H.loc = pick(prisonwarp) + H.equip_to_slot_or_del(new /obj/item/clothing/under/color/orange(H), slot_w_uniform) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(H), slot_shoes) + else + //teleport security person + H.loc = pick(prisonsecuritywarp) + prisonwarped += H + if("traitor_all") + if(!SSticker) + alert("The game hasn't started yet!") + return + var/objective = sanitize(copytext(input("Enter an objective"),1,MAX_MESSAGE_LEN)) + if(!objective) + return + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","TA([objective])") + + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(H.stat == 2 || !H.client || !H.mind) continue + if(is_special_character(H)) continue + //traitorize(H, objective, 0) + H.mind.add_antag_datum(/datum/antagonist/traitor) + + for(var/mob/living/silicon/A in GLOB.player_list) + A.mind.add_antag_datum(/datum/antagonist/traitor) + + message_admins("[key_name_admin(usr)] used everyone is a traitor secret. Objective is [objective]", 1) + log_admin("[key_name(usr)] used everyone is a traitor secret. Objective is [objective]") + + if("togglebombcap") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","BC") + + var/newBombCap = input(usr,"What would you like the new bomb cap to be. (entered as the light damage range (the 3rd number in common (1,2,3) notation)) Must be between 4 and 128)", "New Bomb Cap", MAX_EX_LIGHT_RANGE) as num|null + if(newBombCap < 4) + return + if(newBombCap > 128) + newBombCap = 128 + + MAX_EX_DEVASTATION_RANGE = round(newBombCap/4) + MAX_EX_HEAVY_RANGE = round(newBombCap/2) + MAX_EX_LIGHT_RANGE = newBombCap + //I don't know why these are their own variables, but fuck it, they are. + MAX_EX_FLASH_RANGE = newBombCap + MAX_EX_FLAME_RANGE = newBombCap + + message_admins("[key_name_admin(usr)] changed the bomb cap to [MAX_EX_DEVASTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]") + log_admin("[key_name(usr)] changed the bomb cap to [MAX_EX_DEVASTATION_RANGE], [MAX_EX_HEAVY_RANGE], [MAX_EX_LIGHT_RANGE]") + + if("flicklights") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","FL") + while(!usr.stat) +//knock yourself out to stop the ghosts + for(var/mob/M in GLOB.player_list) + if(M.stat != 2 && prob(25)) + var/area/AffectedArea = get_area(M) + if(AffectedArea.name != "Space" && AffectedArea.name != "Engine Walls" && AffectedArea.name != "Chemical Lab Test Chamber" && AffectedArea.name != "Escape Shuttle" && AffectedArea.name != "Arrival Area" && AffectedArea.name != "Arrival Shuttle" && AffectedArea.name != "start area" && AffectedArea.name != "Engine Combustion Chamber") + AffectedArea.power_light = 0 + AffectedArea.power_change() + spawn(rand(55,185)) + AffectedArea.power_light = 1 + AffectedArea.power_change() + var/Message = rand(1,4) + switch(Message) + if(1) + M.show_message(text("You shudder as if cold..."), 1) + if(2) + M.show_message(text("You feel something gliding across your back..."), 1) + if(3) + M.show_message(text("Your eyes twitch, you feel like something you can't see is here..."), 1) + if(4) + M.show_message(text("You notice something moving out of the corner of your eye, but nothing is there..."), 1) + for(var/obj/W in orange(5,M)) + if(prob(25) && !W.anchored) + step_rand(W) + sleep(rand(100,1000)) + for(var/mob/M in GLOB.player_list) + if(M.stat != 2) + M.show_message(text("The chilling wind suddenly stops..."), 1) + if("lightout") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","LO") + message_admins("[key_name_admin(usr)] has broke a lot of lights", 1) + var/datum/event/electrical_storm/E = new /datum/event/electrical_storm + E.lightsoutAmount = 2 + if("blackout") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","BO") + message_admins("[key_name_admin(usr)] broke all lights", 1) + for(var/obj/machinery/light/L in GLOB.machines) + L.break_light_tube() + if("whiteout") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","WO") + message_admins("[key_name_admin(usr)] fixed all lights", 1) + for(var/obj/machinery/light/L in GLOB.machines) + L.fix() + if("floorlava") + feedback_inc("admin_secrets_fun_used", 1) + feedback_add_details("admin_secrets_fun_used", "LF") + var/sure = alert(usr, "Are you sure you want to do this?", "Confirmation", "Yes", "No") + if(sure == "No") + return + SSweather.run_weather(/datum/weather/floor_is_lava) + message_admins("[key_name_admin(usr)] made the floor lava") + if("fakelava") + feedback_inc("admin_secrets_fun_used", 1) + feedback_add_details("admin_secrets_fun_used", "LZ") + var/sure = alert(usr, "Are you sure you want to do this?", "Confirmation", "Yes", "No") + if(sure == "No") + return + SSweather.run_weather(/datum/weather/floor_is_lava/fake) + message_admins("[key_name_admin(usr)] made aesthetic lava on the floor") + if("weatherashstorm") + feedback_inc("admin_secrets_fun_used", 1) + feedback_add_details("admin_secrets_fun_used", "WA") + var/sure = alert(usr, "Are you sure you want to do this?", "Confirmation", "Yes", "No") + if(sure == "No") + return + SSweather.run_weather(/datum/weather/ash_storm) + message_admins("[key_name_admin(usr)] spawned an ash storm on the mining level") + if("retardify") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","RET") + for(var/mob/living/carbon/human/H in GLOB.player_list) + to_chat(H, "You suddenly feel stupid.") + H.setBrainLoss(60) + message_admins("[key_name_admin(usr)] made everybody retarded") + if("fakeguns") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","FG") + for(var/obj/item/W in world) + if(istype(W, /obj/item/clothing) || istype(W, /obj/item/card/id) || istype(W, /obj/item/disk) || istype(W, /obj/item/tank)) + continue + W.icon = 'icons/obj/guns/projectile.dmi' + W.icon_state = "revolver" + W.item_state = "gun" + message_admins("[key_name_admin(usr)] made every item look like a gun") + if("schoolgirl") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","SG") + for(var/obj/item/clothing/under/W in world) + W.icon_state = "schoolgirl" + W.item_state = "w_suit" + W.item_color = "schoolgirl" + message_admins("[key_name_admin(usr)] activated Japanese Animes mode") + world << sound('sound/AI/animes.ogg') + if("eagles")//SCRAW + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","EgL") + for(var/obj/machinery/door/airlock/W in GLOB.airlocks) + if(is_station_level(W.z) && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison)) + W.req_access = list() + message_admins("[key_name_admin(usr)] activated Egalitarian Station mode") + event_announcement.Announce("Centcomm airlock control override activated. Please take this time to get acquainted with your coworkers.", new_sound = 'sound/AI/commandreport.ogg') + if("onlyone") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","OO") + usr.client.only_one() +// message_admins("[key_name_admin(usr)] has triggered HIGHLANDER") + if("onlyme") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","OM") + usr.client.only_me() + if("onlyoneteam") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","OOT") + usr.client.only_one_team() +// message_admins("[key_name_admin(usr)] has triggered ") + if("rolldice") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","ROL") + usr.client.roll_dices() + if("guns") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","SG") + var/survivor_probability = 0 + switch(alert("Do you want this to create survivors antagonists?", , "No Antags", "Some Antags", "All Antags!")) + if("Some Antags") + survivor_probability = 25 + if("All Antags!") + survivor_probability = 100 + + rightandwrong(SUMMON_GUNS, usr, survivor_probability) + if("magic") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","SM") + var/survivor_probability = 0 + switch(alert("Do you want this to create survivors antagonists?", , "No Antags", "Some Antags", "All Antags!")) + if("Some Antags") + survivor_probability = 25 + if("All Antags!") + survivor_probability = 100 + + rightandwrong(SUMMON_MAGIC, usr, survivor_probability) + if("tdomereset") + var/delete_mobs = alert("Clear all mobs?","Confirm","Yes","No","Cancel") + if(delete_mobs == "Cancel") + return + + var/area/thunderdome = locate(/area/tdome/arena) + if(delete_mobs == "Yes") + for(var/mob/living/mob in thunderdome) + qdel(mob) //Clear mobs + for(var/obj/obj in thunderdome) + if(!istype(obj,/obj/machinery/camera)) + qdel(obj) //Clear objects + + var/area/template = locate(/area/tdome/arena_source) + template.copy_contents_to(thunderdome) + + log_admin("[key_name(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1) + message_admins("[key_name_admin(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].") + + if("tdomestart") + var/confirmation = alert("Start a Thunderdome match?","Confirm","Yes","No") + if(confirmation == "No") + return + if(makeThunderdomeTeams()) + log_admin("[key_name(usr)] started a Thunderdome match!", 1) + message_admins("[key_name_admin(usr)] has started a Thunderdome match!") + else + log_admin("[key_name(usr)] attempted to start a Thunderdome match, but no ghosts signed up.", 1) + message_admins("[key_name_admin(usr)] tried starting a Thunderdome match, but no ghosts signed up.") + if("securitylevel0") + set_security_level(0) + message_admins("[key_name_admin(usr)] change security level to Green.", 1) + if("securitylevel1") + set_security_level(1) + message_admins("[key_name_admin(usr)] change security level to Blue.", 1) + if("securitylevel2") + set_security_level(2) + message_admins("[key_name_admin(usr)] change security level to Red.", 1) + if("securitylevel3") + set_security_level(3) + message_admins("[key_name_admin(usr)] change security level to Gamma.", 1) + if("securitylevel4") + set_security_level(4) + message_admins("[key_name_admin(usr)] change security level to Epsilon.", 1) + if("securitylevel5") + set_security_level(5) + message_admins("[key_name_admin(usr)] change security level to Delta.", 1) + if("moveminingshuttle") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","ShM") + if(!SSshuttle.toggleShuttle("mining","mining_home","mining_away")) + message_admins("[key_name_admin(usr)] moved mining shuttle") + log_admin("[key_name(usr)] moved the mining shuttle") + + if("movelaborshuttle") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","ShL") + if(!SSshuttle.toggleShuttle("laborcamp","laborcamp_home","laborcamp_away")) + message_admins("[key_name_admin(usr)] moved labor shuttle") + log_admin("[key_name(usr)] moved the labor shuttle") + + if("moveferry") + feedback_inc("admin_secrets_fun_used",1) + feedback_add_details("admin_secrets_fun_used","ShF") + if(!SSshuttle.toggleShuttle("ferry","ferry_home","ferry_away")) + message_admins("[key_name_admin(usr)] moved the centcom ferry") + log_admin("[key_name(usr)] moved the centcom ferry") + + if(usr) + log_admin("[key_name(usr)] used secret [href_list["secretsfun"]]") + if(ok) + to_chat(world, text("A secret has been activated by []!", usr.key)) + + else if(href_list["secretsadmin"]) + if(!check_rights(R_ADMIN)) return + + var/ok = 0 + switch(href_list["secretsadmin"]) + if("list_signalers") + var/dat = "Showing last [length(lastsignalers)] signalers.
        " + for(var/sig in lastsignalers) + dat += "[sig]
        " + usr << browse(dat, "window=lastsignalers;size=800x500") + if("list_lawchanges") + var/dat = "Showing last [length(lawchanges)] law changes.
        " + for(var/sig in lawchanges) + dat += "[sig]
        " + usr << browse(dat, "window=lawchanges;size=800x500") + if("list_job_debug") + var/dat = "Job Debug info.
        " + if(SSjobs) + for(var/line in SSjobs.job_debug) + dat += "[line]
        " + dat+= "*******

        " + for(var/datum/job/job in SSjobs.occupations) + if(!job) continue + dat += "job: [job.title], current_positions: [job.current_positions], total_positions: [job.total_positions]
        " + usr << browse(dat, "window=jobdebug;size=600x500") + if("showailaws") + output_ai_laws() + if("showgm") + if(!SSticker) + alert("The game hasn't started yet!") + else if(SSticker.mode) + alert("The game mode is [SSticker.mode.name]") + else alert("For some reason there's a ticker, but not a game mode") + if("manifest") + var/dat = "Showing Crew Manifest.
        " + dat += "" + for(var/mob/living/carbon/human/H in GLOB.mob_list) + if(H.ckey) + dat += text("", H.name, H.get_assignment()) + dat += "
        NamePosition
        [][]
        " + usr << browse(dat, "window=manifest;size=440x410") + if("check_antagonist") + check_antagonists() + if("DNA") + var/dat = "Showing DNA from blood.
        " + dat += "" + for(var/mob/living/carbon/human/H in GLOB.mob_list) + if(H.dna && H.ckey) + dat += "" + dat += "
        NameDNABlood Type
        [H][H.dna.unique_enzymes][H.dna.blood_type]
        " + usr << browse(dat, "window=DNA;size=440x410") + if("fingerprints") + var/dat = "Showing Fingerprints.
        " + dat += "" + for(var/mob/living/carbon/human/H in GLOB.mob_list) + if(H.ckey) + if(H.dna && H.dna.uni_identity) + dat += "" + else if(H.dna && !H.dna.uni_identity) + dat += "" + else if(!H.dna) + dat += "" + dat += "
        NameFingerprints
        [H][md5(H.dna.uni_identity)]
        [H]H.dna.uni_identity = null
        [H]H.dna = null
        " + usr << browse(dat, "window=fingerprints;size=440x410") + if("night_shift_set") + var/val = alert(usr, "What do you want to set night shift to? This will override the automatic system until set to automatic again.", "Night Shift", "On", "Off", "Automatic") + switch(val) + if("Automatic") + if(config.enable_night_shifts) + SSnightshift.can_fire = TRUE + SSnightshift.fire() + else + SSnightshift.update_nightshift(FALSE, TRUE) + to_chat(usr, "Night shift set to automatic.") + if("On") + SSnightshift.can_fire = FALSE + SSnightshift.update_nightshift(TRUE, FALSE) + to_chat(usr, "Night shift forced on.") + if("Off") + SSnightshift.can_fire = FALSE + SSnightshift.update_nightshift(FALSE, FALSE) + to_chat(usr, "Night shift forced off.") + else + if(usr) + log_admin("[key_name(usr)] used secret [href_list["secretsadmin"]]") + if(ok) + to_chat(world, text("A secret has been activated by []!", usr.key)) + + else if(href_list["secretscoder"]) + if(!check_rights(R_DEBUG)) return + + switch(href_list["secretscoder"]) + if("spawn_objects") + var/dat = "Admin Log
        " + for(var/l in admin_log) + dat += "
      • [l]
      • " + if(!admin_log.len) + dat += "No-one has done anything this round!" + usr << browse(dat, "window=admin_log") + if("maint_ACCESS_BRIG") + for(var/obj/machinery/door/airlock/maintenance/M in GLOB.airlocks) + if(ACCESS_MAINT_TUNNELS in M.req_access) + M.req_access = list(ACCESS_BRIG) + message_admins("[key_name_admin(usr)] made all maint doors brig access-only.") + if("maint_access_engiebrig") + for(var/obj/machinery/door/airlock/maintenance/M in GLOB.airlocks) + if(ACCESS_MAINT_TUNNELS in M.req_access) + M.req_access = list() + M.req_one_access = list(ACCESS_BRIG,ACCESS_ENGINE) + message_admins("[key_name_admin(usr)] made all maint doors engineering and brig access-only.") + if("infinite_sec") + var/datum/job/J = SSjobs.GetJob("Security Officer") + if(!J) return + J.total_positions = -1 + J.spawn_positions = -1 + message_admins("[key_name_admin(usr)] has removed the cap on security officers.") + + else if(href_list["ac_view_wanted"]) //Admin newscaster Topic() stuff be here + src.admincaster_screen = 18 //The ac_ prefix before the hrefs stands for AdminCaster. + src.access_news_network() + + else if(href_list["ac_set_channel_name"]) + src.admincaster_feed_channel.channel_name = strip_html_simple(input(usr, "Provide a Feed Channel Name", "Network Channel Handler", "")) + while(findtext(src.admincaster_feed_channel.channel_name," ") == 1) + src.admincaster_feed_channel.channel_name = copytext(src.admincaster_feed_channel.channel_name,2,length(src.admincaster_feed_channel.channel_name)+1) + src.access_news_network() + + else if(href_list["ac_set_channel_lock"]) + src.admincaster_feed_channel.locked = !src.admincaster_feed_channel.locked + src.access_news_network() + + else if(href_list["ac_submit_new_channel"]) + var/check = 0 + for(var/datum/feed_channel/FC in news_network.network_channels) + if(FC.channel_name == src.admincaster_feed_channel.channel_name) + check = 1 + break + if(src.admincaster_feed_channel.channel_name == "" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]" || check ) + src.admincaster_screen=7 + else + var/choice = alert("Please confirm Feed channel creation","Network Channel Handler","Confirm","Cancel") + if(choice=="Confirm") + var/datum/feed_channel/newChannel = new /datum/feed_channel + newChannel.channel_name = src.admincaster_feed_channel.channel_name + newChannel.author = src.admincaster_signature + newChannel.locked = src.admincaster_feed_channel.locked + newChannel.is_admin_channel = 1 + feedback_inc("newscaster_channels",1) + news_network.network_channels += newChannel //Adding channel to the global network + log_admin("[key_name_admin(usr)] created command feed channel: [src.admincaster_feed_channel.channel_name]!") + src.admincaster_screen=5 + src.access_news_network() + + else if(href_list["ac_set_channel_receiving"]) + var/list/available_channels = list() + for(var/datum/feed_channel/F in news_network.network_channels) + available_channels += F.channel_name + src.admincaster_feed_channel.channel_name = adminscrub(input(usr, "Choose receiving Feed Channel", "Network Channel Handler") in available_channels ) + src.access_news_network() + + else if(href_list["ac_set_new_message"]) + src.admincaster_feed_message.body = adminscrub(input(usr, "Write your Feed story", "Network Channel Handler", "")) + while(findtext(src.admincaster_feed_message.body," ") == 1) + src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.body,2,length(src.admincaster_feed_message.body)+1) + src.access_news_network() + + else if(href_list["ac_submit_new_message"]) + if(src.admincaster_feed_message.body =="" || src.admincaster_feed_message.body =="\[REDACTED\]" || src.admincaster_feed_channel.channel_name == "" ) + src.admincaster_screen = 6 + else + var/datum/feed_message/newMsg = new /datum/feed_message + newMsg.author = src.admincaster_signature + newMsg.body = src.admincaster_feed_message.body + newMsg.is_admin_message = 1 + feedback_inc("newscaster_stories",1) + for(var/datum/feed_channel/FC in news_network.network_channels) + if(FC.channel_name == src.admincaster_feed_channel.channel_name) + FC.messages += newMsg //Adding message to the network's appropriate feed_channel + break + src.admincaster_screen=4 + + for(var/obj/machinery/newscaster/NEWSCASTER in allCasters) + NEWSCASTER.newsAlert(src.admincaster_feed_channel.channel_name) + + log_admin("[key_name_admin(usr)] submitted a feed story to channel: [src.admincaster_feed_channel.channel_name]!") + src.access_news_network() + + else if(href_list["ac_create_channel"]) + src.admincaster_screen=2 + src.access_news_network() + + else if(href_list["ac_create_feed_story"]) + src.admincaster_screen=3 + src.access_news_network() + + else if(href_list["ac_menu_censor_story"]) + src.admincaster_screen=10 + src.access_news_network() + + else if(href_list["ac_menu_censor_channel"]) + src.admincaster_screen=11 + src.access_news_network() + + else if(href_list["ac_menu_wanted"]) + var/already_wanted = 0 + if(news_network.wanted_issue) + already_wanted = 1 + + if(already_wanted) + src.admincaster_feed_message.author = news_network.wanted_issue.author + src.admincaster_feed_message.body = news_network.wanted_issue.body + src.admincaster_screen = 14 + src.access_news_network() + + else if(href_list["ac_set_wanted_name"]) + src.admincaster_feed_message.author = adminscrub(input(usr, "Provide the name of the Wanted person", "Network Security Handler", "")) + while(findtext(src.admincaster_feed_message.author," ") == 1) + src.admincaster_feed_message.author = copytext(admincaster_feed_message.author,2,length(admincaster_feed_message.author)+1) + src.access_news_network() + + else if(href_list["ac_set_wanted_desc"]) + src.admincaster_feed_message.body = adminscrub(input(usr, "Provide the a description of the Wanted person and any other details you deem important", "Network Security Handler", "")) + while(findtext(src.admincaster_feed_message.body," ") == 1) + src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.body,2,length(src.admincaster_feed_message.body)+1) + src.access_news_network() + + else if(href_list["ac_submit_wanted"]) + var/input_param = text2num(href_list["ac_submit_wanted"]) + if(src.admincaster_feed_message.author == "" || src.admincaster_feed_message.body == "") + src.admincaster_screen = 16 + else + var/choice = alert("Please confirm Wanted Issue [(input_param==1) ? ("creation.") : ("edit.")]","Network Security Handler","Confirm","Cancel") + if(choice=="Confirm") + if(input_param==1) //If input_param == 1 we're submitting a new wanted issue. At 2 we're just editing an existing one. See the else below + var/datum/feed_message/WANTED = new /datum/feed_message + WANTED.author = src.admincaster_feed_message.author //Wanted name + WANTED.body = src.admincaster_feed_message.body //Wanted desc + WANTED.backup_author = src.admincaster_signature //Submitted by + WANTED.is_admin_message = 1 + news_network.wanted_issue = WANTED + for(var/obj/machinery/newscaster/NEWSCASTER in allCasters) + NEWSCASTER.newsAlert() + NEWSCASTER.update_icon() + src.admincaster_screen = 15 + else + news_network.wanted_issue.author = src.admincaster_feed_message.author + news_network.wanted_issue.body = src.admincaster_feed_message.body + news_network.wanted_issue.backup_author = src.admincaster_feed_message.backup_author + src.admincaster_screen = 19 + log_admin("[key_name_admin(usr)] issued a Station-wide Wanted Notification for [src.admincaster_feed_message.author]!") + src.access_news_network() + + else if(href_list["ac_cancel_wanted"]) + var/choice = alert("Please confirm Wanted Issue removal","Network Security Handler","Confirm","Cancel") + if(choice=="Confirm") + news_network.wanted_issue = null + for(var/obj/machinery/newscaster/NEWSCASTER in allCasters) + NEWSCASTER.update_icon() + src.admincaster_screen=17 + src.access_news_network() + + else if(href_list["ac_censor_channel_author"]) + var/datum/feed_channel/FC = locate(href_list["ac_censor_channel_author"]) + if(FC.author != "\[REDACTED\]") + FC.backup_author = FC.author + FC.author = "\[REDACTED\]" + else + FC.author = FC.backup_author + src.access_news_network() + + else if(href_list["ac_censor_channel_story_author"]) + var/datum/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"]) + if(MSG.author != "\[REDACTED\]") + MSG.backup_author = MSG.author + MSG.author = "\[REDACTED\]" + else + MSG.author = MSG.backup_author + src.access_news_network() + + else if(href_list["ac_censor_channel_story_body"]) + var/datum/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"]) + if(MSG.body != "\[REDACTED\]") + MSG.backup_body = MSG.body + MSG.body = "\[REDACTED\]" + else + MSG.body = MSG.backup_body + src.access_news_network() + + else if(href_list["ac_pick_d_notice"]) + var/datum/feed_channel/FC = locate(href_list["ac_pick_d_notice"]) + src.admincaster_feed_channel = FC + src.admincaster_screen=13 + src.access_news_network() + + else if(href_list["ac_toggle_d_notice"]) + var/datum/feed_channel/FC = locate(href_list["ac_toggle_d_notice"]) + FC.censored = !FC.censored + src.access_news_network() + + else if(href_list["ac_view"]) + src.admincaster_screen=1 + src.access_news_network() + + else if(href_list["ac_setScreen"]) //Brings us to the main menu and resets all fields~ + src.admincaster_screen = text2num(href_list["ac_setScreen"]) + if(src.admincaster_screen == 0) + if(src.admincaster_feed_channel) + src.admincaster_feed_channel = new /datum/feed_channel + if(src.admincaster_feed_message) + src.admincaster_feed_message = new /datum/feed_message + src.access_news_network() + + else if(href_list["ac_show_channel"]) + var/datum/feed_channel/FC = locate(href_list["ac_show_channel"]) + src.admincaster_feed_channel = FC + src.admincaster_screen = 9 + src.access_news_network() + + else if(href_list["ac_pick_censor_channel"]) + var/datum/feed_channel/FC = locate(href_list["ac_pick_censor_channel"]) + src.admincaster_feed_channel = FC + src.admincaster_screen = 12 + src.access_news_network() + + else if(href_list["ac_refresh"]) + src.access_news_network() + + else if(href_list["ac_set_signature"]) + src.admincaster_signature = adminscrub(input(usr, "Provide your desired signature", "Network Identity Handler", "")) + src.access_news_network() + + if(href_list["secretsmenu"]) + switch(href_list["secretsmenu"]) + if("tab") + current_tab = text2num(href_list["tab"]) + Secrets(usr) + return 1 + + else if(href_list["viewruntime"]) + var/datum/ErrorViewer/error_viewer = locateUID(href_list["viewruntime"]) + if(!istype(error_viewer)) + to_chat(usr, "That runtime viewer no longer exists.") + return + if(href_list["viewruntime_backto"]) + error_viewer.showTo(usr, locateUID(href_list["viewruntime_backto"]), href_list["viewruntime_linear"]) + else + error_viewer.showTo(usr, null, href_list["viewruntime_linear"]) + + else if(href_list["add_station_goal"]) + if(!check_rights(R_EVENT)) + return + var/list/type_choices = typesof(/datum/station_goal) + var/picked = input("Choose goal type") in type_choices|null + if(!picked) + return + var/datum/station_goal/G = new picked() + if(picked == /datum/station_goal) + var/newname = clean_input("Enter goal name:") + if(!newname) + return + G.name = newname + var/description = input("Enter [command_name()] message contents:") as message|null + if(!description) + return + G.report_message = description + message_admins("[key_name_admin(usr)] created \"[G.name]\" station goal.") + SSticker.mode.station_goals += G + modify_goals() + + else if(href_list["showdetails"]) + if(!check_rights(R_ADMIN)) + return + var/text = html_decode(href_list["showdetails"]) + usr << browse("Details[replacetext(text, "\n", "
        ")]
        ", + "window=show_details;size=500x200") + + // Library stuff + else if(href_list["library_book_id"]) + var/isbn = sanitizeSQL(href_list["library_book_id"]) + + if(href_list["view_library_book"]) + var/DBQuery/query_view_book = dbcon.NewQuery("SELECT content, title FROM [format_table_name("library")] WHERE id=[isbn]") + if(!query_view_book.Execute()) + var/err = query_view_book.ErrorMsg() + log_game("SQL ERROR viewing book. Error : \[[err]\]\n") + return + + var/content = "" + var/title = "" + while(query_view_book.NextRow()) + content = query_view_book.item[1] + title = html_encode(query_view_book.item[2]) + + var/dat = "
        "
        +			dat += "[html_encode(html_to_pencode(content))]"
        +			dat += "
        " + + var/datum/browser/popup = new(usr, "admin_view_book", "[title]", 700, 400) + popup.set_content(dat) + popup.open(0) + + log_admin("[key_name(usr)] has viewed the book [isbn].") + message_admins("[key_name_admin(usr)] has viewed the book [isbn].") + return + + else if(href_list["unflag_library_book"]) + var/DBQuery/query_unflag_book = dbcon.NewQuery("UPDATE [format_table_name("library")] SET flagged = 0 WHERE id=[isbn]") + if(!query_unflag_book.Execute()) + var/err = query_unflag_book.ErrorMsg() + log_game("SQL ERROR unflagging book. Error : \[[err]\]\n") + return + + log_admin("[key_name(usr)] has unflagged the book [isbn].") + message_admins("[key_name_admin(usr)] has unflagged the book [isbn].") + + else if(href_list["delete_library_book"]) + var/DBQuery/query_delbook = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[isbn]") + if(!query_delbook.Execute()) + var/err = query_delbook.ErrorMsg() + log_game("SQL ERROR deleting book. Error : \[[err]\]\n") + return + + log_admin("[key_name(usr)] has deleted the book [isbn].") + message_admins("[key_name_admin(usr)] has deleted the book [isbn].") + + // Refresh the page + src.view_flagged_books() + + // Force unlink a discord key + else if(href_list["force_discord_unlink"]) + if(!check_rights(R_ADMIN)) + return + var/target_ckey = href_list["force_discord_unlink"] + var/DBQuery/admin_unlink_discord_id = dbcon.NewQuery("DELETE FROM [format_table_name("discord")] WHERE ckey = '[target_ckey]'") + if(!admin_unlink_discord_id.Execute()) + var/err = admin_unlink_discord_id.ErrorMsg() + log_game("SQL ERROR while admin-unlinking discord account. Error : \[[err]\]\n") + return + to_chat(src, "Successfully forcefully unlinked discord account from [target_ckey]") + message_admins("[key_name_admin(usr)] forcefully unlinked the discord account belonging to [target_ckey]") + log_admin("[key_name_admin(usr)] forcefully unlinked the discord account belonging to [target_ckey]") + + else if(href_list["create_outfit_finalize"]) + if(!check_rights(R_EVENT)) + return + create_outfit_finalize(usr,href_list) + else if(href_list["load_outfit"]) + if(!check_rights(R_EVENT)) + return + load_outfit(usr) + else if(href_list["create_outfit_menu"]) + if(!check_rights(R_EVENT)) + return + create_outfit(usr) + else if(href_list["delete_outfit"]) + if(!check_rights(R_EVENT)) + return + var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits + delete_outfit(usr,O) + else if(href_list["save_outfit"]) + if(!check_rights(R_EVENT)) + return + var/datum/outfit/O = locate(href_list["chosen_outfit"]) in GLOB.custom_outfits + save_outfit(usr,O) + +/client/proc/create_eventmob_for(var/mob/living/carbon/human/H, var/killthem = 0) + if(!check_rights(R_EVENT)) + return + var/admin_outfits = subtypesof(/datum/outfit/admin) + var/hunter_outfits = list() + for(var/type in admin_outfits) + var/datum/outfit/admin/O = type + hunter_outfits[initial(O.name)] = type + var/dresscode = input("Select type", "Contracted Agents") as null|anything in hunter_outfits + if(isnull(dresscode)) + return + var/datum/outfit/O = hunter_outfits[dresscode] + message_admins("[key_name_admin(mob)] is sending a ([dresscode]) to [killthem ? "assassinate" : "protect"] [key_name_admin(H)]...") + var/list/candidates = pollCandidates("Play as a [killthem ? "murderous" : "protective"] [dresscode]?", ROLE_TRAITOR, 1) + if(!candidates.len) + to_chat(usr, "ERROR: Could not create eventmob. No valid candidates.") + return + var/mob/C = pick(candidates) + var/key_of_hunter = C.key + if(!key_of_hunter) + to_chat(usr, "ERROR: Could not create eventmob. Could not pick key.") + return + var/datum/mind/hunter_mind = new /datum/mind(key_of_hunter) + hunter_mind.active = 1 + var/mob/living/carbon/human/hunter_mob = new /mob/living/carbon/human(pick(latejoin)) + hunter_mind.transfer_to(hunter_mob) + hunter_mob.equipOutfit(O, FALSE) + var/obj/item/pinpointer/advpinpointer/N = new /obj/item/pinpointer/advpinpointer(hunter_mob) + hunter_mob.equip_to_slot_or_del(N, slot_in_backpack) + N.active = 1 + N.mode = 2 + N.target = H + N.point_at(N.target) + if(!locate(/obj/item/implant/dust, hunter_mob)) + var/obj/item/implant/dust/D = new /obj/item/implant/dust(hunter_mob) + D.implant(hunter_mob) + if(killthem) + var/datum/objective/assassinate/kill_objective = new + kill_objective.owner = hunter_mind + kill_objective.target = H.mind + kill_objective.explanation_text = "Kill [H.real_name], the [H.mind.assigned_role]." + hunter_mind.objectives += kill_objective + else + var/datum/objective/protect/protect_objective = new + protect_objective.owner = hunter_mind + protect_objective.target = H.mind + protect_objective.explanation_text = "Protect [H.real_name], the [H.mind.assigned_role]." + hunter_mind.objectives += protect_objective + SSticker.mode.traitors |= hunter_mob.mind + to_chat(hunter_mob, "ATTENTION: You are now on a mission!") + to_chat(hunter_mob, "Goal: [killthem ? "MURDER" : "PROTECT"] [H.real_name], currently in [get_area(H.loc)]. "); + if(killthem) + to_chat(hunter_mob, "If you kill [H.p_them()], [H.p_they()] cannot be revived."); + hunter_mob.mind.special_role = SPECIAL_ROLE_TRAITOR + var/datum/atom_hud/antag/tatorhud = huds[ANTAG_HUD_TRAITOR] + tatorhud.join_hud(hunter_mob) + set_antag_hud(hunter_mob, "hudsyndicate") + +/proc/admin_jump_link(var/atom/target) + if(!target) return + // The way admin jump links handle their src is weirdly inconsistent... + + . = ADMIN_FLW(target,"FLW") + if(isAI(target)) // AI core/eye follow links + var/mob/living/silicon/ai/A = target + if(A.client && A.eyeobj) // No point following clientless AI eyes + . += "|[ADMIN_FLW(A.eyeobj,"EYE")]" + else if(istype(target, /mob/dead/observer)) + var/mob/dead/observer/O = target + if(O.mind && O.mind.current) + . += "|[ADMIN_FLW(O.mind.current,"BDY")]" diff --git a/code/modules/admin/verbs/BrokenInhands.dm b/code/modules/admin/verbs/BrokenInhands.dm index e0fb1a46913..75e051b1ff0 100644 --- a/code/modules/admin/verbs/BrokenInhands.dm +++ b/code/modules/admin/verbs/BrokenInhands.dm @@ -1,36 +1,36 @@ -/proc/getbrokeninhands() - set name = "Broken Sprite List" - set category = "Debug" - - var/text - for(var/A in typesof(/obj/item)) - var/obj/item/O = new A( locate(1,1,1) ) - if(!O) continue - var/icon/IL = new(O.lefthand_file) - var/list/Lstates = IL.IconStates() - var/icon/IR = new(O.righthand_file) - var/list/Rstates = IR.IconStates() - var/icon/J = new(O.icon) - var/list/istates = J.IconStates() - if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.item_state)) - if(O.icon_state) - text += "[O.type] is missing left hand icon called \"[O.icon_state]\".\n" - if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.item_state)) - if(O.icon_state) - text += "[O.type] is missing right hand icon called \"[O.icon_state]\".\n" - - - if(O.icon_state) - if(!istates.Find(O.icon_state)) - text += "[O.type] is missing normal icon called \"[O.icon_state]\" in \"[O.icon]\".\n" - //if(O.item_state) - // if(!istates.Find(O.item_state)) - // text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.item_state]\" IN \"[O.icon]\"\n" - //text+="\n" - qdel(O) - if(text) - var/F = file("broken_hand_icons.txt") - fdel(F) - to_chat(F, text) - to_chat(world, "Completed and written to [F]") - +/proc/getbrokeninhands() + set name = "Broken Sprite List" + set category = "Debug" + + var/text + for(var/A in typesof(/obj/item)) + var/obj/item/O = new A( locate(1,1,1) ) + if(!O) continue + var/icon/IL = new(O.lefthand_file) + var/list/Lstates = IL.IconStates() + var/icon/IR = new(O.righthand_file) + var/list/Rstates = IR.IconStates() + var/icon/J = new(O.icon) + var/list/istates = J.IconStates() + if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.item_state)) + if(O.icon_state) + text += "[O.type] is missing left hand icon called \"[O.icon_state]\".\n" + if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.item_state)) + if(O.icon_state) + text += "[O.type] is missing right hand icon called \"[O.icon_state]\".\n" + + + if(O.icon_state) + if(!istates.Find(O.icon_state)) + text += "[O.type] is missing normal icon called \"[O.icon_state]\" in \"[O.icon]\".\n" + //if(O.item_state) + // if(!istates.Find(O.item_state)) + // text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.item_state]\" IN \"[O.icon]\"\n" + //text+="\n" + qdel(O) + if(text) + var/F = file("broken_hand_icons.txt") + fdel(F) + to_chat(F, text) + to_chat(world, "Completed and written to [F]") + diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 76e22259893..1a13539c520 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -1,187 +1,187 @@ - - -//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE! -var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as") - -/client/verb/adminhelp() - set category = "Admin" - set name = "Adminhelp" - - //handle muting and automuting - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).") - return - - adminhelped = 1 //Determines if they get the message to reply by clicking the name. - - var/msg - var/list/type = list("Mentorhelp","Adminhelp") - var/selected_type = input("Pick a category.", "Admin Help", null, null) as null|anything in type - if(selected_type) - msg = clean_input("Please enter your message.", "Admin Help", null) - - //clean the input msg - if(!msg) - return - - if(handle_spam_prevention(msg, MUTE_ADMINHELP, OOC_COOLDOWN)) - return - - msg = sanitize_simple(copytext(msg,1,MAX_MESSAGE_LEN)) - if(!msg) return - var/original_msg = msg - - //explode the input msg into a list - var/list/msglist = splittext(msg, " ") - - //generate keywords lookup - var/list/surnames = list() - var/list/forenames = list() - var/list/ckeys = list() - for(var/mob/M in GLOB.mob_list) - var/list/indexing = list(M.real_name, M.name) - if(M.mind) indexing += M.mind.name - - for(var/string in indexing) - var/list/L = splittext(string, " ") - var/surname_found = 0 - //surnames - for(var/i=L.len, i>=1, i--) - var/word = ckey(L[i]) - if(word) - surnames[word] = M - surname_found = i - break - //forenames - for(var/i=1, iTICKET) [ai_found ? "(CL)" : ""] (TAKE) (RESOLVE) [isMhelp ? "" : "(AUTO)"] : [span][msg]" - if(isMhelp) - //Open a new adminticket and inform the user. - SSmentor_tickets.newTicket(src, prunedmsg, msg) - for(var/client/X in mentorholders + modholders + adminholders) - if(X.prefs.sound & SOUND_MENTORHELP) - X << 'sound/effects/adminhelp.ogg' - to_chat(X, msg) - else //Ahelp - //Open a new adminticket and inform the user. - SStickets.newTicket(src, prunedmsg, msg) - for(var/client/X in modholders + adminholders) - if(X.prefs.sound & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' - window_flash(X) - to_chat(X, msg) - - - - //show it to the person adminhelping too - to_chat(src, "[selected_type]: [original_msg]") - - var/admin_number_present = adminholders.len - admin_number_afk - log_admin("[selected_type]: [key_name(src)]: [original_msg] - heard by [admin_number_present] non-AFK admins.") - if(admin_number_present <= 0) - if(!admin_number_afk) - send2adminirc("[selected_type] from [key_name(src)]: [original_msg] - !!No admins online!!") - else - send2adminirc("[selected_type] from [key_name(src)]: [original_msg] - !!All admins AFK ([admin_number_afk])!!") - else - send2adminirc("[selected_type] from [key_name(src)]: [original_msg]") - feedback_add_details("admin_verb","AH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN) - var/admin_number_total = 0 //Total number of admins - var/admin_number_afk = 0 //Holds the number of admins who are afk - var/admin_number_ignored = 0 //Holds the number of admins without +BAN (so admins who are not really admins) - var/admin_number_decrease = 0 //Holds the number of admins with are afk, ignored or both - for(var/client/X in GLOB.admins) - admin_number_total++; - var/invalid = 0 - if(requiredflags != 0 && !check_rights_for(X, requiredflags)) - admin_number_ignored++ - invalid = 1 - if(X.is_afk()) - admin_number_afk++ - invalid = 1 - if(X.holder.fakekey) - admin_number_ignored++ - invalid = 1 - if(invalid) - admin_number_decrease++ - var/admin_number_present = admin_number_total - admin_number_decrease //Number of admins who are neither afk nor invalid - if(admin_number_present <= 0) - if(!admin_number_afk && !admin_number_ignored) - send2irc(source, "[msg] - No admins online") - else - send2irc(source, "[msg] - All admins AFK ([admin_number_afk]/[admin_number_total]) or skipped ([admin_number_ignored]/[admin_number_total])") - return admin_number_present + + +//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE! +var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as") + +/client/verb/adminhelp() + set category = "Admin" + set name = "Adminhelp" + + //handle muting and automuting + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).") + return + + adminhelped = 1 //Determines if they get the message to reply by clicking the name. + + var/msg + var/list/type = list("Mentorhelp","Adminhelp") + var/selected_type = input("Pick a category.", "Admin Help", null, null) as null|anything in type + if(selected_type) + msg = clean_input("Please enter your message.", "Admin Help", null) + + //clean the input msg + if(!msg) + return + + if(handle_spam_prevention(msg, MUTE_ADMINHELP, OOC_COOLDOWN)) + return + + msg = sanitize_simple(copytext(msg,1,MAX_MESSAGE_LEN)) + if(!msg) return + var/original_msg = msg + + //explode the input msg into a list + var/list/msglist = splittext(msg, " ") + + //generate keywords lookup + var/list/surnames = list() + var/list/forenames = list() + var/list/ckeys = list() + for(var/mob/M in GLOB.mob_list) + var/list/indexing = list(M.real_name, M.name) + if(M.mind) indexing += M.mind.name + + for(var/string in indexing) + var/list/L = splittext(string, " ") + var/surname_found = 0 + //surnames + for(var/i=L.len, i>=1, i--) + var/word = ckey(L[i]) + if(word) + surnames[word] = M + surname_found = i + break + //forenames + for(var/i=1, iTICKET) [ai_found ? "(CL)" : ""] (TAKE) (RESOLVE) [isMhelp ? "" : "(AUTO)"] : [span][msg]" + if(isMhelp) + //Open a new adminticket and inform the user. + SSmentor_tickets.newTicket(src, prunedmsg, msg) + for(var/client/X in mentorholders + modholders + adminholders) + if(X.prefs.sound & SOUND_MENTORHELP) + X << 'sound/effects/adminhelp.ogg' + to_chat(X, msg) + else //Ahelp + //Open a new adminticket and inform the user. + SStickets.newTicket(src, prunedmsg, msg) + for(var/client/X in modholders + adminholders) + if(X.prefs.sound & SOUND_ADMINHELP) + X << 'sound/effects/adminhelp.ogg' + window_flash(X) + to_chat(X, msg) + + + + //show it to the person adminhelping too + to_chat(src, "[selected_type]
        : [original_msg]") + + var/admin_number_present = adminholders.len - admin_number_afk + log_admin("[selected_type]: [key_name(src)]: [original_msg] - heard by [admin_number_present] non-AFK admins.") + if(admin_number_present <= 0) + if(!admin_number_afk) + send2adminirc("[selected_type] from [key_name(src)]: [original_msg] - !!No admins online!!") + else + send2adminirc("[selected_type] from [key_name(src)]: [original_msg] - !!All admins AFK ([admin_number_afk])!!") + else + send2adminirc("[selected_type] from [key_name(src)]: [original_msg]") + feedback_add_details("admin_verb","AH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN) + var/admin_number_total = 0 //Total number of admins + var/admin_number_afk = 0 //Holds the number of admins who are afk + var/admin_number_ignored = 0 //Holds the number of admins without +BAN (so admins who are not really admins) + var/admin_number_decrease = 0 //Holds the number of admins with are afk, ignored or both + for(var/client/X in GLOB.admins) + admin_number_total++; + var/invalid = 0 + if(requiredflags != 0 && !check_rights_for(X, requiredflags)) + admin_number_ignored++ + invalid = 1 + if(X.is_afk()) + admin_number_afk++ + invalid = 1 + if(X.holder.fakekey) + admin_number_ignored++ + invalid = 1 + if(invalid) + admin_number_decrease++ + var/admin_number_present = admin_number_total - admin_number_decrease //Number of admins who are neither afk nor invalid + if(admin_number_present <= 0) + if(!admin_number_afk && !admin_number_ignored) + send2irc(source, "[msg] - No admins online") + else + send2irc(source, "[msg] - All admins AFK ([admin_number_afk]/[admin_number_total]) or skipped ([admin_number_ignored]/[admin_number_total])") + return admin_number_present diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index 16a1c14694f..0c1a8c5f14f 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -1,161 +1,161 @@ -/client/proc/Jump(area/A in return_sorted_areas()) - set name = "Jump to Area" - set desc = "Area to jump to" - set category = "Admin" - - if(!check_rights(R_ADMIN)) - return - - if(!A) - return - - var/list/turfs = list() - for(var/turf/T in A) - if(T.density) - continue - if(locate(/obj/structure/grille, T)) // Quick check to not spawn in windows - continue - turfs.Add(T) - - var/turf/T = pick_n_take(turfs) - if(!T) - to_chat(src, "Nowhere to jump to!") - return - - admin_forcemove(usr, T) - log_admin("[key_name(usr)] jumped to [A]") - if(!isobserver(usr)) - message_admins("[key_name_admin(usr)] jumped to [A]") - feedback_add_details("admin_verb","JA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/jumptoturf(var/turf/T in world) - set name = "Jump to Turf" - set category = null - - if(!check_rights(R_ADMIN)) - return - - log_admin("[key_name(usr)] jumped to [T.x], [T.y], [T.z] in [T.loc]") - if(!isobserver(usr)) - message_admins("[key_name_admin(usr)] jumped to [T.x], [T.y], [T.z] in [T.loc]", 1) - admin_forcemove(usr, T) - feedback_add_details("admin_verb","JT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/jumptomob(var/mob/M in GLOB.mob_list) - set category = "Admin" - set name = "Jump to Mob" - - if(!check_rights(R_ADMIN)) - return - - log_admin("[key_name(usr)] jumped to [key_name(M)]") - if(!isobserver(usr)) - message_admins("[key_name_admin(usr)] jumped to [key_name_admin(M)]", 1) - if(src.mob) - var/mob/A = src.mob - var/turf/T = get_turf(M) - if(T && isturf(T)) - feedback_add_details("admin_verb","JM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - admin_forcemove(A, M.loc) - else - to_chat(A, "This mob is not located in the game world.") - -/client/proc/jumptocoord(tx as num, ty as num, tz as num) - set category = "Admin" - set name = "Jump to Coordinate" - - if(!check_rights(R_ADMIN)) - return - - var/turf/T = locate(tx, ty, tz) - if(T) - admin_forcemove(usr, T) - if(isobserver(usr)) - var/mob/dead/observer/O = usr - O.ManualFollow(T) - feedback_add_details("admin_verb","JC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - if(!isobserver(usr)) - message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]") - -/client/proc/jumptokey() - set category = "Admin" - set name = "Jump to Key" - - if(!check_rights(R_ADMIN)) - return - - var/list/keys = list() - for(var/mob/M in GLOB.player_list) - keys += M.client - var/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) - if(!selection) - to_chat(src, "No keys found.") - return - var/mob/M = selection:mob - log_admin("[key_name(usr)] jumped to [key_name(M)]") - if(!isobserver(usr)) - message_admins("[key_name_admin(usr)] jumped to [key_name_admin(M)]", 1) - - admin_forcemove(usr, M.loc) - - feedback_add_details("admin_verb","JK") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/Getmob(var/mob/M in GLOB.mob_list) - set category = "Admin" - set name = "Get Mob" - set desc = "Mob to teleport" - - if(!check_rights(R_ADMIN)) - return - - log_admin("[key_name(usr)] teleported [key_name(M)]") - message_admins("[key_name_admin(usr)] teleported [key_name_admin(M)]", 1) - admin_forcemove(M, get_turf(usr)) - feedback_add_details("admin_verb","GM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/Getkey() - set category = "Admin" - set name = "Get Key" - set desc = "Key to teleport" - - if(!check_rights(R_ADMIN)) - return - - var/list/keys = list() - for(var/mob/M in GLOB.player_list) - keys += M.client - var/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) - if(!selection) - return - var/mob/M = selection:mob - - if(!M) - return - log_admin("[key_name(usr)] teleported [key_name(M)]") - message_admins("[key_name_admin(usr)] teleported [key_name(M)]", 1) - if(M) - admin_forcemove(M, get_turf(usr)) - admin_forcemove(usr, M.loc) - feedback_add_details("admin_verb","GK") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/sendmob(var/mob/M in GLOB.mob_list) - set category = "Admin" - set name = "Send Mob" - - if(!check_rights(R_ADMIN)) - return - - var/area/A = input(usr, "Pick an area.", "Pick an area") in return_sorted_areas() - if(A) - admin_forcemove(M, pick(get_area_turfs(A))) - feedback_add_details("admin_verb","SMOB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] teleported [key_name(M)] to [A]") - message_admins("[key_name_admin(usr)] teleported [key_name_admin(M)] to [A]", 1) - -/proc/admin_forcemove(mob/mover, atom/newloc) - mover.forceMove(newloc) - mover.on_forcemove(newloc) - -/mob/proc/on_forcemove(atom/newloc) - return +/client/proc/Jump(area/A in return_sorted_areas()) + set name = "Jump to Area" + set desc = "Area to jump to" + set category = "Admin" + + if(!check_rights(R_ADMIN)) + return + + if(!A) + return + + var/list/turfs = list() + for(var/turf/T in A) + if(T.density) + continue + if(locate(/obj/structure/grille, T)) // Quick check to not spawn in windows + continue + turfs.Add(T) + + var/turf/T = pick_n_take(turfs) + if(!T) + to_chat(src, "Nowhere to jump to!") + return + + admin_forcemove(usr, T) + log_admin("[key_name(usr)] jumped to [A]") + if(!isobserver(usr)) + message_admins("[key_name_admin(usr)] jumped to [A]") + feedback_add_details("admin_verb","JA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/jumptoturf(var/turf/T in world) + set name = "Jump to Turf" + set category = null + + if(!check_rights(R_ADMIN)) + return + + log_admin("[key_name(usr)] jumped to [T.x], [T.y], [T.z] in [T.loc]") + if(!isobserver(usr)) + message_admins("[key_name_admin(usr)] jumped to [T.x], [T.y], [T.z] in [T.loc]", 1) + admin_forcemove(usr, T) + feedback_add_details("admin_verb","JT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/jumptomob(var/mob/M in GLOB.mob_list) + set category = "Admin" + set name = "Jump to Mob" + + if(!check_rights(R_ADMIN)) + return + + log_admin("[key_name(usr)] jumped to [key_name(M)]") + if(!isobserver(usr)) + message_admins("[key_name_admin(usr)] jumped to [key_name_admin(M)]", 1) + if(src.mob) + var/mob/A = src.mob + var/turf/T = get_turf(M) + if(T && isturf(T)) + feedback_add_details("admin_verb","JM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + admin_forcemove(A, M.loc) + else + to_chat(A, "This mob is not located in the game world.") + +/client/proc/jumptocoord(tx as num, ty as num, tz as num) + set category = "Admin" + set name = "Jump to Coordinate" + + if(!check_rights(R_ADMIN)) + return + + var/turf/T = locate(tx, ty, tz) + if(T) + admin_forcemove(usr, T) + if(isobserver(usr)) + var/mob/dead/observer/O = usr + O.ManualFollow(T) + feedback_add_details("admin_verb","JC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + if(!isobserver(usr)) + message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]") + +/client/proc/jumptokey() + set category = "Admin" + set name = "Jump to Key" + + if(!check_rights(R_ADMIN)) + return + + var/list/keys = list() + for(var/mob/M in GLOB.player_list) + keys += M.client + var/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) + if(!selection) + to_chat(src, "No keys found.") + return + var/mob/M = selection:mob + log_admin("[key_name(usr)] jumped to [key_name(M)]") + if(!isobserver(usr)) + message_admins("[key_name_admin(usr)] jumped to [key_name_admin(M)]", 1) + + admin_forcemove(usr, M.loc) + + feedback_add_details("admin_verb","JK") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/Getmob(var/mob/M in GLOB.mob_list) + set category = "Admin" + set name = "Get Mob" + set desc = "Mob to teleport" + + if(!check_rights(R_ADMIN)) + return + + log_admin("[key_name(usr)] teleported [key_name(M)]") + message_admins("[key_name_admin(usr)] teleported [key_name_admin(M)]", 1) + admin_forcemove(M, get_turf(usr)) + feedback_add_details("admin_verb","GM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/Getkey() + set category = "Admin" + set name = "Get Key" + set desc = "Key to teleport" + + if(!check_rights(R_ADMIN)) + return + + var/list/keys = list() + for(var/mob/M in GLOB.player_list) + keys += M.client + var/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys) + if(!selection) + return + var/mob/M = selection:mob + + if(!M) + return + log_admin("[key_name(usr)] teleported [key_name(M)]") + message_admins("[key_name_admin(usr)] teleported [key_name(M)]", 1) + if(M) + admin_forcemove(M, get_turf(usr)) + admin_forcemove(usr, M.loc) + feedback_add_details("admin_verb","GK") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/sendmob(var/mob/M in GLOB.mob_list) + set category = "Admin" + set name = "Send Mob" + + if(!check_rights(R_ADMIN)) + return + + var/area/A = input(usr, "Pick an area.", "Pick an area") in return_sorted_areas() + if(A) + admin_forcemove(M, pick(get_area_turfs(A))) + feedback_add_details("admin_verb","SMOB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] teleported [key_name(M)] to [A]") + message_admins("[key_name_admin(usr)] teleported [key_name_admin(M)] to [A]", 1) + +/proc/admin_forcemove(mob/mover, atom/newloc) + mover.forceMove(newloc) + mover.on_forcemove(newloc) + +/mob/proc/on_forcemove(atom/newloc) + return diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm index 6f6b3484924..74eeff49692 100644 --- a/code/modules/admin/verbs/adminsay.dm +++ b/code/modules/admin/verbs/adminsay.dm @@ -1,82 +1,82 @@ -/client/proc/cmd_admin_say(msg as text) - set category = "Admin" - set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite - set hidden = 1 - if(!check_rights(R_ADMIN)) return - - msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) - if(!msg) return - - log_adminsay(msg, src) - - if(check_rights(R_ADMIN,0)) - for(var/client/C in GLOB.admins) - if(R_ADMIN & C.holder.rights) - msg = "[msg]" - to_chat(C, "ADMIN: [key_name(usr, 1)] ([admin_jump_link(mob)]): [msg]") - - feedback_add_details("admin_verb","M") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/get_admin_say() - var/msg = input(src, null, "asay \"text\"") as text|null - cmd_admin_say(msg) - -/client/proc/cmd_mentor_say(msg as text) - set category = "Admin" - set name = "Msay" - set hidden = 1 - - if(!check_rights(R_ADMIN|R_MOD|R_MENTOR)) - return - - msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) - log_mentorsay(msg, src) - - if(!msg) - return - - for(var/client/C in GLOB.admins) - if(check_rights(R_ADMIN|R_MOD|R_MENTOR, 0, C.mob)) - var/display_name = key - if(holder.fakekey) - if(C.holder && C.holder.rights & R_ADMIN) - display_name = "[holder.fakekey]/([key])" - else - display_name = holder.fakekey - msg = "[msg]" - to_chat(C, "MENTOR: [display_name] ([admin_jump_link(mob)]): [msg]") - - feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_mentor_chat() - set category = "Server" - set name = "Toggle Mentor Chat" - set desc = "Toggle whether mentors have access to the msay command" - - if(!check_rights(R_ADMIN)) - return - - var/enabling - var/msay = /client/proc/cmd_mentor_say - - if(msay in admin_verbs_mentor) - enabling = FALSE - admin_verbs_mentor -= msay - else - enabling = TRUE - admin_verbs_mentor += msay - - for(var/client/C in GLOB.admins) - if(check_rights(R_ADMIN|R_MOD, 0, C.mob)) - continue - if(!check_rights(R_MENTOR, 0, C.mob)) - continue - if(enabling) - C.verbs += msay - to_chat(C, "Mentor chat has been enabled. Use 'msay' to speak in it.") - else - C.verbs -= msay - to_chat(C, "Mentor chat has been disabled.") - - admin_log_and_message_admins("toggled mentor chat [enabling ? "on" : "off"].") - feedback_add_details("admin_verb", "TMC") +/client/proc/cmd_admin_say(msg as text) + set category = "Admin" + set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite + set hidden = 1 + if(!check_rights(R_ADMIN)) return + + msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) + if(!msg) return + + log_adminsay(msg, src) + + if(check_rights(R_ADMIN,0)) + for(var/client/C in GLOB.admins) + if(R_ADMIN & C.holder.rights) + msg = "[msg]" + to_chat(C, "ADMIN: [key_name(usr, 1)] ([admin_jump_link(mob)]): [msg]") + + feedback_add_details("admin_verb","M") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/get_admin_say() + var/msg = input(src, null, "asay \"text\"") as text|null + cmd_admin_say(msg) + +/client/proc/cmd_mentor_say(msg as text) + set category = "Admin" + set name = "Msay" + set hidden = 1 + + if(!check_rights(R_ADMIN|R_MOD|R_MENTOR)) + return + + msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) + log_mentorsay(msg, src) + + if(!msg) + return + + for(var/client/C in GLOB.admins) + if(check_rights(R_ADMIN|R_MOD|R_MENTOR, 0, C.mob)) + var/display_name = key + if(holder.fakekey) + if(C.holder && C.holder.rights & R_ADMIN) + display_name = "[holder.fakekey]/([key])" + else + display_name = holder.fakekey + msg = "[msg]" + to_chat(C, "MENTOR: [display_name] ([admin_jump_link(mob)]): [msg]") + + feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_mentor_chat() + set category = "Server" + set name = "Toggle Mentor Chat" + set desc = "Toggle whether mentors have access to the msay command" + + if(!check_rights(R_ADMIN)) + return + + var/enabling + var/msay = /client/proc/cmd_mentor_say + + if(msay in admin_verbs_mentor) + enabling = FALSE + admin_verbs_mentor -= msay + else + enabling = TRUE + admin_verbs_mentor += msay + + for(var/client/C in GLOB.admins) + if(check_rights(R_ADMIN|R_MOD, 0, C.mob)) + continue + if(!check_rights(R_MENTOR, 0, C.mob)) + continue + if(enabling) + C.verbs += msay + to_chat(C, "Mentor chat has been enabled. Use 'msay' to speak in it.") + else + C.verbs -= msay + to_chat(C, "Mentor chat has been disabled.") + + admin_log_and_message_admins("toggled mentor chat [enabling ? "on" : "off"].") + feedback_add_details("admin_verb", "TMC") diff --git a/code/modules/admin/verbs/atmosdebug.dm b/code/modules/admin/verbs/atmosdebug.dm index de281d34e68..0624e4d1909 100644 --- a/code/modules/admin/verbs/atmosdebug.dm +++ b/code/modules/admin/verbs/atmosdebug.dm @@ -1,63 +1,63 @@ -/client/proc/atmosscan() - set category = "Mapping" - set name = "Check Piping" - set background = 1 - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - return - feedback_add_details("admin_verb","CP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - if(alert("WARNING: This command should not be run on a live server. Do you want to continue?", "Check Piping", "No", "Yes") == "No") - return - - to_chat(usr, "Checking for disconnected pipes...") - //all plumbing - yes, some things might get stated twice, doesn't matter. - for(var/obj/machinery/atmospherics/plumbing in world) - if(plumbing.nodealert) - to_chat(usr, "Unconnected [plumbing.name] located at [plumbing.x],[plumbing.y],[plumbing.z] ([get_area(plumbing.loc)])") - - //Manifolds - for(var/obj/machinery/atmospherics/pipe/manifold/pipe in world) - if(!pipe.node1 || !pipe.node2 || !pipe.node3) - to_chat(usr, "Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])") - - //Pipes - for(var/obj/machinery/atmospherics/pipe/simple/pipe in world) - if(!pipe.node1 || !pipe.node2) - to_chat(usr, "Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])") - - to_chat(usr, "Checking for overlapping pipes...") - for(var/turf/T in world) - for(var/dir in cardinal) - var/list/check = list(0, 0, 0) - var/done = 0 - for(var/obj/machinery/atmospherics/pipe in T) - if(dir & pipe.initialize_directions) - for(var/ct in pipe.connect_types) - check[ct]++ - if(check[ct] > 1) - to_chat(usr, "Overlapping pipe ([pipe.name]) located at [T.x],[T.y],[T.z] ([get_area(T)])") - done = 1 - break - if(done) - break - to_chat(usr, "Done") - -/client/proc/powerdebug() - set category = "Mapping" - set name = "Check Power" - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - return - feedback_add_details("admin_verb","CPOW") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - for(var/datum/powernet/PN in SSmachines.powernets) - if(!PN.nodes || !PN.nodes.len) - if(PN.cables && (PN.cables.len > 1)) - var/obj/structure/cable/C = PN.cables[1] - to_chat(usr, "Powernet with no nodes! (number [PN.number]) - example cable at [C.x], [C.y], [C.z] in area [get_area(C.loc)]") - - if(!PN.cables || (PN.cables.len < 10)) - if(PN.cables && (PN.cables.len > 1)) - var/obj/structure/cable/C = PN.cables[1] - to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [C.x], [C.y], [C.z] in area [get_area(C.loc)]") +/client/proc/atmosscan() + set category = "Mapping" + set name = "Check Piping" + set background = 1 + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + return + feedback_add_details("admin_verb","CP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + if(alert("WARNING: This command should not be run on a live server. Do you want to continue?", "Check Piping", "No", "Yes") == "No") + return + + to_chat(usr, "Checking for disconnected pipes...") + //all plumbing - yes, some things might get stated twice, doesn't matter. + for(var/obj/machinery/atmospherics/plumbing in world) + if(plumbing.nodealert) + to_chat(usr, "Unconnected [plumbing.name] located at [plumbing.x],[plumbing.y],[plumbing.z] ([get_area(plumbing.loc)])") + + //Manifolds + for(var/obj/machinery/atmospherics/pipe/manifold/pipe in world) + if(!pipe.node1 || !pipe.node2 || !pipe.node3) + to_chat(usr, "Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])") + + //Pipes + for(var/obj/machinery/atmospherics/pipe/simple/pipe in world) + if(!pipe.node1 || !pipe.node2) + to_chat(usr, "Unconnected [pipe.name] located at [pipe.x],[pipe.y],[pipe.z] ([get_area(pipe.loc)])") + + to_chat(usr, "Checking for overlapping pipes...") + for(var/turf/T in world) + for(var/dir in cardinal) + var/list/check = list(0, 0, 0) + var/done = 0 + for(var/obj/machinery/atmospherics/pipe in T) + if(dir & pipe.initialize_directions) + for(var/ct in pipe.connect_types) + check[ct]++ + if(check[ct] > 1) + to_chat(usr, "Overlapping pipe ([pipe.name]) located at [T.x],[T.y],[T.z] ([get_area(T)])") + done = 1 + break + if(done) + break + to_chat(usr, "Done") + +/client/proc/powerdebug() + set category = "Mapping" + set name = "Check Power" + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + return + feedback_add_details("admin_verb","CPOW") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + for(var/datum/powernet/PN in SSmachines.powernets) + if(!PN.nodes || !PN.nodes.len) + if(PN.cables && (PN.cables.len > 1)) + var/obj/structure/cable/C = PN.cables[1] + to_chat(usr, "Powernet with no nodes! (number [PN.number]) - example cable at [C.x], [C.y], [C.z] in area [get_area(C.loc)]") + + if(!PN.cables || (PN.cables.len < 10)) + if(PN.cables && (PN.cables.len > 1)) + var/obj/structure/cable/C = PN.cables[1] + to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [C.x], [C.y], [C.z] in area [get_area(C.loc)]") diff --git a/code/modules/admin/verbs/cinematic.dm b/code/modules/admin/verbs/cinematic.dm index 63ebd52d09b..5bfd7c65312 100644 --- a/code/modules/admin/verbs/cinematic.dm +++ b/code/modules/admin/verbs/cinematic.dm @@ -14,4 +14,4 @@ override = input(src, "mode = ?","Enter Parameter", null) as anything in list("nuclear emergency", "fake", "no override") if(0) override = input(src, "mode = ?","Enter Parameter", null) as anything in list("blob", "nuclear emergency", "AI malfunction", "no override") - SSticker.station_explosion_cinematic(parameter, override) \ No newline at end of file + SSticker.station_explosion_cinematic(parameter, override) diff --git a/code/modules/admin/verbs/deadsay.dm b/code/modules/admin/verbs/deadsay.dm index e1bbc885c75..aae13ad882d 100644 --- a/code/modules/admin/verbs/deadsay.dm +++ b/code/modules/admin/verbs/deadsay.dm @@ -1,49 +1,49 @@ -/client/proc/dsay(msg as text) - set category = "Admin" - set name = "Dsay" //Gave this shit a shorter name so you only have to time out "dsay" rather than "dead say" to use it --NeoFite - set hidden = 1 - - if(!check_rights(R_ADMIN|R_MOD)) - return - - if(!src.mob) - return - - if(prefs.muted & MUTE_DEADCHAT) - to_chat(src, "You cannot send DSAY messages (muted).") - return - - if(!(prefs.toggles & CHAT_DEAD)) - to_chat(src, "You have deadchat muted.") - return - - if(handle_spam_prevention(msg,MUTE_DEADCHAT)) - return - - var/stafftype = null - - if(check_rights(R_MENTOR, 0)) - stafftype = "MENTOR" - - if(check_rights(R_MOD, 0)) - stafftype = "MOD" - - if(check_rights(R_ADMIN, 0)) - stafftype = "ADMIN" - - msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) - log_admin("[key_name(src)] : [msg]") - - if(!msg) - return - - var/prefix = "[stafftype] ([src.key])" - if(holder.fakekey) - prefix = "Administrator" - say_dead_direct("[prefix] says, \"[msg]\"") - - feedback_add_details("admin_verb","D") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/get_dead_say() - var/msg = input(src, null, "dsay \"text\"") as text - dsay(msg) \ No newline at end of file +/client/proc/dsay(msg as text) + set category = "Admin" + set name = "Dsay" //Gave this shit a shorter name so you only have to time out "dsay" rather than "dead say" to use it --NeoFite + set hidden = 1 + + if(!check_rights(R_ADMIN|R_MOD)) + return + + if(!src.mob) + return + + if(prefs.muted & MUTE_DEADCHAT) + to_chat(src, "You cannot send DSAY messages (muted).") + return + + if(!(prefs.toggles & CHAT_DEAD)) + to_chat(src, "You have deadchat muted.") + return + + if(handle_spam_prevention(msg,MUTE_DEADCHAT)) + return + + var/stafftype = null + + if(check_rights(R_MENTOR, 0)) + stafftype = "MENTOR" + + if(check_rights(R_MOD, 0)) + stafftype = "MOD" + + if(check_rights(R_ADMIN, 0)) + stafftype = "ADMIN" + + msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) + log_admin("[key_name(src)] : [msg]") + + if(!msg) + return + + var/prefix = "[stafftype] ([src.key])" + if(holder.fakekey) + prefix = "Administrator" + say_dead_direct("[prefix] says, \"[msg]\"") + + feedback_add_details("admin_verb","D") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/get_dead_say() + var/msg = input(src, null, "dsay \"text\"") as text + dsay(msg) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 57f88d20351..ad3a0403e87 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -1,892 +1,892 @@ -/client/proc/Debug2() - set category = "Debug" - set name = "Debug-Game" - - if(!check_rights(R_DEBUG)) - return - - if(Debug2) - Debug2 = 0 - message_admins("[key_name_admin(src)] toggled debugging off.") - log_admin("[key_name(src)] toggled debugging off.") - else - Debug2 = 1 - message_admins("[key_name_admin(src)] toggled debugging on.") - log_admin("[key_name(src)] toggled debugging on.") - - feedback_add_details("admin_verb","DG2") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/* 21st Sept 2010 -Updated by Skie -- Still not perfect but better! -Stuff you can't do: -Call proc /mob/proc/Dizzy() for some player -Because if you select a player mob as owner it tries to do the proc for -/mob/living/carbon/human/ instead. And that gives a run-time error. -But you can call procs that are of type /mob/living/carbon/human/proc/ for that player. -*/ - -/client/proc/callproc() - set category = "Debug" - set name = "Advanced ProcCall" - - if(!check_rights(R_PROCCALL)) - return - - spawn(0) - var/target = null - var/targetselected = 0 - var/returnval = null - var/class = null - - switch(alert("Proc owned by something?",,"Yes","No")) - if("Yes") - targetselected = 1 - if(src.holder && src.holder.marked_datum) - class = input("Proc owned by...","Owner",null) as null|anything in list("Obj","Mob","Area or Turf","Client","Marked datum ([holder.marked_datum.type])") - if(class == "Marked datum ([holder.marked_datum.type])") - class = "Marked datum" - else - class = input("Proc owned by...","Owner",null) as null|anything in list("Obj","Mob","Area or Turf","Client") - switch(class) - if("Obj") - target = input("Enter target:","Target",usr) as obj in world - if("Mob") - target = input("Enter target:","Target",usr) as mob in world - if("Area or Turf") - target = input("Enter target:","Target",usr.loc) as area|turf in world - if("Client") - var/list/keys = list() - for(var/client/C) - keys += C - target = input("Please, select a player!", "Selection", null, null) as null|anything in keys - if("Marked datum") - target = holder.marked_datum - else - return - if("No") - target = null - targetselected = 0 - - var/procname = clean_input("Proc path, eg: /proc/fake_blood","Path:", null) - if(!procname) return - - if(targetselected && !hascall(target,procname)) - to_chat(usr, "Error: callproc(): target has no such call [procname].") - return - - var/list/lst = get_callproc_args() - if(!lst) - return - - if(targetselected) - if(!target) - to_chat(usr, "Error: callproc(): owner of proc no longer exists.") - return - message_admins("[key_name_admin(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") - log_admin("[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") - returnval = call(target,procname)(arglist(lst)) // Pass the lst as an argument list to the proc - else - //this currently has no hascall protection. wasn't able to get it working. - message_admins("[key_name_admin(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]") - log_admin("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]") - returnval = call(procname)(arglist(lst)) // Pass the lst as an argument list to the proc - - to_chat(usr, "[procname] returned: [!isnull(returnval) ? returnval : "null"]") - feedback_add_details("admin_verb","APC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -GLOBAL_VAR(AdminProcCaller) -GLOBAL_PROTECT(AdminProcCaller) - -/proc/IsAdminAdvancedProcCall() -#ifdef TESTING - return FALSE -#else - return usr && usr.client && GLOB.AdminProcCaller == usr.client.ckey -#endif - -/client/proc/callproc_datum(var/A as null|area|mob|obj|turf) - set category = "Debug" - set name = "Atom ProcCall" - - if(!check_rights(R_PROCCALL)) - return - - var/procname = clean_input("Proc name, eg: fake_blood","Proc:", null) - if(!procname) - return - - if(!hascall(A,procname)) - to_chat(usr, "Error: callproc_datum(): target has no such call [procname].") - return - - var/list/lst = get_callproc_args() - if(!lst) - return - - if(!A || !IsValidSrc(A)) - to_chat(src, "Error: callproc_datum(): owner of proc no longer exists.") - return - message_admins("[key_name_admin(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]") - log_admin("[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]") - - spawn() - var/returnval = call(A,procname)(arglist(lst)) // Pass the lst as an argument list to the proc - to_chat(src, "[procname] returned: [!isnull(returnval) ? returnval : "null"]") - - feedback_add_details("admin_verb","DPC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/get_callproc_args() - var/argnum = input("Number of arguments","Number:",0) as num|null - if(!argnum && (argnum!=0)) return - - var/list/lst = list() - //TODO: make a list to store whether each argument was initialised as null. - //Reason: So we can abort the proccall if say, one of our arguments was a mob which no longer exists - //this will protect us from a fair few errors ~Carn - - while(argnum--) - var/class = null - // Make a list with each index containing one variable, to be given to the proc - if(src.holder && src.holder.marked_datum) - class = input("What kind of variable?","Variable Type") in list("text","num","type","reference","mob reference","icon","file","client","mob's area","Marked datum ([holder.marked_datum.type])","CANCEL") - if(holder.marked_datum && class == "Marked datum ([holder.marked_datum.type])") - class = "Marked datum" - else - class = input("What kind of variable?","Variable Type") in list("text","num","type","reference","mob reference","icon","file","client","mob's area","CANCEL") - switch(class) - if("CANCEL") - return null - - if("text") - lst += clean_input("Enter new text:","Text",null) - - if("num") - lst += input("Enter new number:","Num",0) as num - - if("type") - lst += input("Enter type:","Type") in typesof(/obj,/mob,/area,/turf) - - if("reference") - lst += input("Select reference:","Reference",src) as mob|obj|turf|area in world - - if("mob reference") - lst += input("Select reference:","Reference",usr) as mob in world - - if("file") - lst += input("Pick file:","File") as file - - if("icon") - lst += input("Pick icon:","Icon") as icon - - if("client") - var/list/keys = list() - for(var/mob/M in world) - keys += M.client - lst += input("Please, select a player!", "Selection", null, null) as null|anything in keys - - if("mob's area") - var/mob/temp = input("Select mob", "Selection", usr) as mob in world - lst += temp.loc - - if("Marked datum") - lst += holder.marked_datum - return lst - -/client/proc/Cell() - set category = "Debug" - set name = "Air Status in Location" - - if(!check_rights(R_DEBUG)) - return - - if(!mob) - return - var/turf/T = mob.loc - - if(!( istype(T, /turf) )) - return - - var/datum/gas_mixture/env = T.return_air() - - var/t = "" - t+= "Nitrogen : [env.nitrogen]\n" - t+= "Oxygen : [env.oxygen]\n" - t+= "Plasma : [env.toxins]\n" - t+= "CO2: [env.carbon_dioxide]\n" - - usr.show_message(t, 1) - feedback_add_details("admin_verb","ASL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_robotize(var/mob/M in GLOB.mob_list) - set category = "Event" - set name = "Make Robot" - - if(!check_rights(R_SPAWN)) - return - - if(!SSticker) - alert("Wait until the game starts") - return - if(istype(M, /mob/living/carbon/human)) - log_admin("[key_name(src)] has robotized [M.key].") - spawn(10) - M:Robotize() - - else - alert("Invalid mob") - -/client/proc/cmd_admin_animalize(var/mob/M in GLOB.mob_list) - set category = "Event" - set name = "Make Simple Animal" - - if(!check_rights(R_SPAWN)) - return - - if(!SSticker) - alert("Wait until the game starts") - return - - if(!M) - alert("That mob doesn't seem to exist, close the panel and try again.") - return - - if(istype(M, /mob/new_player)) - alert("The mob must not be a new_player.") - return - - log_admin("[key_name(src)] has animalized [M.key].") - spawn(10) - M.Animalize() - - -/client/proc/makepAI(var/turf/T in GLOB.mob_list) - set category = "Event" - set name = "Make pAI" - set desc = "Specify a location to spawn a pAI device, then specify a key to play that pAI" - - if(!check_rights(R_SPAWN)) - return - - var/list/available = list() - for(var/mob/C in GLOB.mob_list) - if(C.key) - available.Add(C) - var/mob/choice = input("Choose a player to play the pAI", "Spawn pAI") in available - if(!choice) - return 0 - if(!istype(choice, /mob/dead/observer)) - var/confirm = input("[choice.key] isn't ghosting right now. Are you sure you want to yank [choice.p_them()] out of [choice.p_their()] body and place [choice.p_them()] in this pAI?", "Spawn pAI Confirmation", "No") in list("Yes", "No") - if(confirm != "Yes") - return 0 - var/obj/item/paicard/card = new(T) - var/mob/living/silicon/pai/pai = new(card) - var/raw_name = clean_input("Enter your pAI name:", "pAI Name", "Personal AI", choice) - var/new_name = reject_bad_name(raw_name, 1) - if(new_name) - pai.name = new_name - pai.real_name = new_name - else - to_chat(usr, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and .") - pai.real_name = pai.name - pai.key = choice.key - card.setPersonality(pai) - for(var/datum/paiCandidate/candidate in paiController.pai_candidates) - if(candidate.key == choice.key) - paiController.pai_candidates.Remove(candidate) - feedback_add_details("admin_verb","MPAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_alienize(var/mob/M in GLOB.mob_list) - set category = "Event" - set name = "Make Alien" - - if(!check_rights(R_SPAWN)) - return - - if(!SSticker) - alert("Wait until the game starts") - return - if(ishuman(M)) - log_admin("[key_name(src)] has alienized [M.key].") - spawn(10) - M:Alienize() - feedback_add_details("admin_verb","MKAL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] made [key_name(M)] into an alien.") - message_admins("[key_name_admin(usr)] made [key_name(M)] into an alien.", 1) - else - alert("Invalid mob") - -/client/proc/cmd_admin_slimeize(var/mob/M in GLOB.mob_list) - set category = "Event" - set name = "Make slime" - - if(!check_rights(R_SPAWN)) - return - - if(!SSticker) - alert("Wait until the game starts") - return - if(ishuman(M)) - log_admin("[key_name(src)] has slimeized [M.key].") - spawn(10) - M:slimeize() - feedback_add_details("admin_verb","MKMET") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] made [key_name(M)] into a slime.") - message_admins("[key_name_admin(usr)] made [key_name(M)] into a slime.", 1) - else - alert("Invalid mob") - -/client/proc/cmd_admin_super(var/mob/M in GLOB.mob_list) - set category = "Event" - set name = "Make Superhero" - - if(!check_rights(R_SPAWN)) - return - - if(!SSticker) - alert("Wait until the game starts") - return - if(ishuman(M)) - var/type = input("Pick the Superhero","Superhero") as null|anything in GLOB.all_superheroes - var/datum/superheroes/S = GLOB.all_superheroes[type] - if(S) - S.create(M) - log_admin("[key_name(src)] has turned [M.key] into a Superhero.") - message_admins("[key_name_admin(usr)] made [key_name(M)] into a Superhero.", 1) - else - alert("Invalid mob") - -//TODO: merge the vievars version into this or something maybe mayhaps -/client/proc/cmd_debug_del_all() - set category = "Debug" - set name = "Del-All" - - if(!check_rights(R_DEBUG)) - return - - // to prevent REALLY stupid deletions - var/blocked = list(/mob/living, /mob/living/carbon, /mob/living/carbon/human, /mob/dead, /mob/dead/observer, /mob/living/silicon, /mob/living/silicon/robot, /mob/living/silicon/ai) - var/hsbitem = input(usr, "Choose an object to delete.", "Delete:") as null|anything in subtypesof(/obj) + subtypesof(/mob) - blocked - if(hsbitem) - for(var/atom/O in world) - if(istype(O, hsbitem)) - qdel(O) - log_admin("[key_name(src)] has deleted all instances of [hsbitem].") - message_admins("[key_name_admin(src)] has deleted all instances of [hsbitem].", 0) - feedback_add_details("admin_verb","DELA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_debug_del_sing() - set category = "Debug" - set name = "Del Singulo / Tesla" - - if(!check_rights(R_DEBUG)) - return - - //This gets a confirmation check because it's way easier to accidentally hit this and delete things than it is with del-all - var/confirm = alert("This will delete ALL Singularities and Tesla orbs except for any that are on away mission z-levels or the centcomm z-level. Are you sure you want to delete them?", "Confirm Panic Button", "Yes", "No") - if(confirm != "Yes") - return - - for(var/I in GLOB.singularities) - var/obj/singularity/S = I - if(!is_level_reachable(S.z)) - continue - qdel(S) - log_admin("[key_name(src)] has deleted all Singularities and Tesla orbs.") - message_admins("[key_name_admin(src)] has deleted all Singularities and Tesla orbs.", 0) - feedback_add_details("admin_verb","DELS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_debug_make_powernets() - set category = "Debug" - set name = "Make Powernets" - - if(!check_rights(R_DEBUG)) - return - - SSmachines.makepowernets() - log_admin("[key_name(src)] has remade the powernet. makepowernets() called.") - message_admins("[key_name_admin(src)] has remade the powernets. makepowernets() called.", 0) - feedback_add_details("admin_verb","MPWN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_grantfullaccess(var/mob/M in GLOB.mob_list) - set category = "Admin" - set name = "Grant Full Access" - - if(!check_rights(R_EVENT)) - return - - if(!SSticker) - alert("Wait until the game starts") - return - if(istype(M, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = M - if(H.wear_id) - var/obj/item/card/id/id = H.wear_id - if(istype(H.wear_id, /obj/item/pda)) - var/obj/item/pda/pda = H.wear_id - id = pda.id - id.icon_state = "gold" - id:access = get_all_accesses()+get_all_centcom_access()+get_all_syndicate_access() - else - var/obj/item/card/id/id = new/obj/item/card/id(M); - id.icon_state = "gold" - id:access = get_all_accesses()+get_all_centcom_access()+get_all_syndicate_access() - id.registered_name = H.real_name - id.assignment = "Captain" - id.name = "[id.registered_name]'s ID Card ([id.assignment])" - H.equip_to_slot_or_del(id, slot_wear_id) - H.update_inv_wear_id() - else - alert("Invalid mob") - feedback_add_details("admin_verb","GFA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(src)] has granted [M.key] full access.") - message_admins("[key_name_admin(usr)] has granted [M.key] full access.", 1) - -/client/proc/cmd_assume_direct_control(var/mob/M in GLOB.mob_list) - set category = "Admin" - set name = "Assume direct control" - set desc = "Direct intervention" - - if(!check_rights(R_DEBUG|R_ADMIN)) - return - - if(M.ckey) - if(alert("This mob is being controlled by [M.ckey]. Are you sure you wish to assume control of it? [M.ckey] will be made a ghost.",,"Yes","No") != "Yes") - return - else - var/mob/dead/observer/ghost = new/mob/dead/observer(M,1) - ghost.ckey = M.ckey - message_admins("[key_name_admin(usr)] assumed direct control of [M].", 1) - log_admin("[key_name(usr)] assumed direct control of [M].") - var/mob/adminmob = src.mob - M.ckey = src.ckey - if( isobserver(adminmob) ) - qdel(adminmob) - feedback_add_details("admin_verb","ADC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/cmd_admin_areatest() - set category = "Mapping" - set name = "Test areas" - - if(!check_rights(R_DEBUG)) - return - - var/list/areas_all = list() - var/list/areas_with_APC = list() - var/list/areas_with_air_alarm = list() - var/list/areas_with_RC = list() - var/list/areas_with_light = list() - var/list/areas_with_LS = list() - var/list/areas_with_intercom = list() - var/list/areas_with_camera = list() - - var/list/areas_with_multiple_APCs = list() - var/list/areas_with_multiple_air_alarms = list() - - for(var/area/A in world) - areas_all |= A.type - - for(var/obj/machinery/power/apc/APC in world) - var/area/A = get_area(APC) - if(!A) - continue - if(!(A.type in areas_with_APC)) - areas_with_APC |= A.type - else - areas_with_multiple_APCs |= A.type - - for(var/obj/machinery/alarm/alarm in world) - var/area/A = get_area(alarm) - if(!A) - continue - if(!(A.type in areas_with_air_alarm)) - areas_with_air_alarm |= A.type - else - areas_with_multiple_air_alarms |= A.type - - for(var/obj/machinery/requests_console/RC in world) - var/area/A = get_area(RC) - if(!A) - continue - areas_with_RC |= A.type - - for(var/obj/machinery/light/L in world) - var/area/A = get_area(L) - if(!A) - continue - areas_with_light |= A.type - - for(var/obj/machinery/light_switch/LS in world) - var/area/A = get_area(LS) - if(!A) - continue - areas_with_LS |= A.type - - for(var/obj/item/radio/intercom/I in world) - var/area/A = get_area(I) - if(!A) - continue - areas_with_intercom |= A.type - - for(var/obj/machinery/camera/C in world) - var/area/A = get_area(C) - if(!A) - continue - areas_with_camera |= A.type - - var/list/areas_without_APC = areas_all - areas_with_APC - var/list/areas_without_air_alarm = areas_all - areas_with_air_alarm - var/list/areas_without_RC = areas_all - areas_with_RC - var/list/areas_without_light = areas_all - areas_with_light - var/list/areas_without_LS = areas_all - areas_with_LS - var/list/areas_without_intercom = areas_all - areas_with_intercom - var/list/areas_without_camera = areas_all - areas_with_camera - - to_chat(world, "AREAS WITHOUT AN APC:") - for(var/areatype in areas_without_APC) - to_chat(world, "* [areatype]") - - to_chat(world, "AREAS WITHOUT AN AIR ALARM:") - for(var/areatype in areas_without_air_alarm) - to_chat(world, "* [areatype]") - - to_chat(world, "AREAS WITH TOO MANY APCS:") - for(var/areatype in areas_with_multiple_APCs) - to_chat(world, "* [areatype]") - - to_chat(world, "AREAS WITH TOO MANY AIR ALARMS:") - for(var/areatype in areas_with_multiple_air_alarms) - to_chat(world, "* [areatype]") - - to_chat(world, "AREAS WITHOUT A REQUEST CONSOLE:") - for(var/areatype in areas_without_RC) - to_chat(world, "* [areatype]") - - to_chat(world, "AREAS WITHOUT ANY LIGHTS:") - for(var/areatype in areas_without_light) - to_chat(world, "* [areatype]") - - to_chat(world, "AREAS WITHOUT A LIGHT SWITCH:") - for(var/areatype in areas_without_LS) - to_chat(world, "* [areatype]") - - to_chat(world, "AREAS WITHOUT ANY INTERCOMS:") - for(var/areatype in areas_without_intercom) - to_chat(world, "* [areatype]") - - to_chat(world, "AREAS WITHOUT ANY CAMERAS:") - for(var/areatype in areas_without_camera) - to_chat(world, "* [areatype]") - -/client/proc/cmd_admin_dress(var/mob/living/carbon/human/M in GLOB.mob_list) - set category = "Event" - set name = "Select equipment" - - if(!check_rights(R_EVENT)) - return - - if(!ishuman(M) && !isobserver(M)) - alert("Invalid mob") - return - - var/dresscode = robust_dress_shop() - - if(!dresscode) - return - - var/delete_pocket - var/mob/living/carbon/human/H - if(isobserver(M)) - H = M.change_mob_type(/mob/living/carbon/human, null, null, TRUE) - else - H = M - if(H.l_store || H.r_store || H.s_store) //saves a lot of time for admins and coders alike - if(alert("Should the items in their pockets be dropped? Selecting \"No\" will delete them.", "Robust quick dress shop", "Yes", "No") == "No") - delete_pocket = TRUE - - for (var/obj/item/I in H.get_equipped_items(delete_pocket)) - qdel(I) - if(dresscode != "Naked") - H.equipOutfit(dresscode) - - H.regenerate_icons() - - feedback_add_details("admin_verb", "SE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] changed the equipment of [key_name(M)] to [dresscode].") - message_admins("[key_name_admin(usr)] changed the equipment of [key_name_admin(M)] to [dresscode].", 1) - -/client/proc/robust_dress_shop() - var/list/outfits = list( - "Naked", - "As Job...", - "Custom..." - ) - - var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job) - for(var/path in paths) - var/datum/outfit/O = path //not much to initalize here but whatever - if(initial(O.can_be_admin_equipped)) - outfits[initial(O.name)] = path - - var/dresscode = input("Select outfit", "Robust quick dress shop") as null|anything in outfits - if(isnull(dresscode)) - return - - if(outfits[dresscode]) - dresscode = outfits[dresscode] - - if(dresscode == "As Job...") - var/list/job_paths = subtypesof(/datum/outfit/job) - var/list/job_outfits = list() - for(var/path in job_paths) - var/datum/outfit/O = path - if(initial(O.can_be_admin_equipped)) - job_outfits[initial(O.name)] = path - - dresscode = input("Select job equipment", "Robust quick dress shop") as null|anything in job_outfits - dresscode = job_outfits[dresscode] - if(isnull(dresscode)) - return - - if(dresscode == "Custom...") - var/list/custom_names = list() - for(var/datum/outfit/D in GLOB.custom_outfits) - custom_names[D.name] = D - var/selected_name = input("Select outfit", "Robust quick dress shop") as null|anything in custom_names - dresscode = custom_names[selected_name] - if(isnull(dresscode)) - return - - return dresscode - -/client/proc/startSinglo() - set category = "Debug" - set name = "Start Singularity" - set desc = "Sets up the singularity and all machines to get power flowing through the station" - - if(!check_rights(R_DEBUG)) - return - - if(alert("Are you sure? This will start up the engine. Should only be used during debug!",,"Yes","No") != "Yes") - return - - for(var/obj/machinery/power/emitter/E in GLOB.machines) - if(E.anchored) - E.active = 1 - - for(var/obj/machinery/field/generator/F in GLOB.machines) - if(F.active == 0) - F.active = 1 - F.state = 2 - F.power = 250 - F.anchored = 1 - F.warming_up = 3 - F.start_fields() - F.update_icon() - - spawn(30) - for(var/obj/machinery/the_singularitygen/G in GLOB.machines) - if(G.anchored) - var/obj/singularity/S = new /obj/singularity(get_turf(G)) - S.energy = 800 - break - - for(var/obj/machinery/power/rad_collector/Rad in GLOB.machines) - if(Rad.anchored) - if(!Rad.P) - var/obj/item/tank/plasma/Plasma = new/obj/item/tank/plasma(Rad) - Plasma.air_contents.toxins = 70 - Rad.drainratio = 0 - Rad.P = Plasma - Plasma.loc = Rad - - if(!Rad.active) - Rad.toggle_power() - - for(var/obj/machinery/power/smes/SMES in GLOB.machines) - if(SMES.anchored) - SMES.input_attempt = 1 - -/client/proc/cmd_debug_mob_lists() - set category = "Debug" - set name = "Debug Mob Lists" - set desc = "For when you just gotta know" - - if(!check_rights(R_DEBUG)) - return - - switch(input("Which list?") in list("Players","Admins","Mobs","Living Mobs","Dead Mobs","Silicons","Clients","Respawnable Mobs")) - if("Players") - to_chat(usr, jointext(GLOB.player_list,",")) - if("Admins") - to_chat(usr, jointext(GLOB.admins,",")) - if("Mobs") - to_chat(usr, jointext(GLOB.mob_list,",")) - if("Living Mobs") - to_chat(usr, jointext(GLOB.living_mob_list,",")) - if("Dead Mobs") - to_chat(usr, jointext(GLOB.dead_mob_list,",")) - if("Silicons") - to_chat(usr, jointext(GLOB.silicon_mob_list,",")) - if("Clients") - to_chat(usr, jointext(GLOB.clients,",")) - if("Respawnable Mobs") - to_chat(usr, jointext(GLOB.respawnable_list,",")) - -/client/proc/cmd_display_del_log() - set category = "Debug" - set name = "Display del() Log" - set desc = "Display del's log of everything that's passed through it." - - if(!check_rights(R_DEBUG)) - return - - var/list/dellog = list("List of things that have gone through qdel this round

          ") - sortTim(SSgarbage.items, cmp=/proc/cmp_qdel_item_time, associative = TRUE) - for(var/path in SSgarbage.items) - var/datum/qdel_item/I = SSgarbage.items[path] - dellog += "
        1. [path]
            " - if(I.failures) - dellog += "
          • Failures: [I.failures]
          • " - dellog += "
          • qdel() Count: [I.qdels]
          • " - dellog += "
          • Destroy() Cost: [I.destroy_time]ms
          • " - if(I.hard_deletes) - dellog += "
          • Total Hard Deletes [I.hard_deletes]
          • " - dellog += "
          • Time Spent Hard Deleting: [I.hard_delete_time]ms
          • " - if(I.slept_destroy) - dellog += "
          • Sleeps: [I.slept_destroy]
          • " - if(I.no_respect_force) - dellog += "
          • Ignored force: [I.no_respect_force]
          • " - if(I.no_hint) - dellog += "
          • No hint: [I.no_hint]
          • " - dellog += "
        2. " - - dellog += "
        " - - usr << browse(dellog.Join(), "window=dellog") - -/client/proc/cmd_display_del_log_simple() - set category = "Debug" - set name = "Display Simple del() Log" - set desc = "Display a compacted del's log." - - if(!check_rights(R_DEBUG)) - return - - var/dat = "List of things that failed to GC this round

        " - for(var/path in SSgarbage.items) - var/datum/qdel_item/I = SSgarbage.items[path] - if(I.failures) - dat += "[I] - [I.failures] times
        " - - dat += "List of paths that did not return a qdel hint in Destroy()

        " - for(var/path in SSgarbage.items) - var/datum/qdel_item/I = SSgarbage.items[path] - if(I.no_hint) - dat += "[I]
        " - - dat += "List of paths that slept in Destroy()

        " - for(var/path in SSgarbage.items) - var/datum/qdel_item/I = SSgarbage.items[path] - if(I.slept_destroy) - dat += "[I]
        " - - usr << browse(dat, "window=simpledellog") - -/client/proc/cmd_admin_toggle_block(var/mob/M,var/block) - if(!check_rights(R_SPAWN)) - return - - if(!SSticker) - alert("Wait until the game starts") - return - if(istype(M, /mob/living/carbon)) - M.dna.SetSEState(block,!M.dna.GetSEState(block)) - genemutcheck(M,block,null,MUTCHK_FORCED) - M.update_mutations() - var/state="[M.dna.GetSEState(block)?"on":"off"]" - var/blockname=assigned_blocks[block] - message_admins("[key_name_admin(src)] has toggled [M.key]'s [blockname] block [state]!") - log_admin("[key_name(src)] has toggled [M.key]'s [blockname] block [state]!") - else - alert("Invalid mob") - -/client/proc/reload_nanoui_resources() - set category = "Debug" - set name = "Reload NanoUI Resources" - set desc = "Force the client to redownload NanoUI Resources" - - // Close open NanoUIs. - SSnanoui.close_user_uis(usr) - - // Re-load the assets. - var/datum/asset/assets = get_asset_datum(/datum/asset/nanoui) - assets.register() - - // Clear the user's cache so they get resent. - usr.client.cache = list() - -/client/proc/view_runtimes() - set category = "Debug" - set name = "View Runtimes" - set desc = "Open the Runtime Viewer" - - if(!check_rights(R_DEBUG)) - return - - error_cache.showTo(usr) - -/client/proc/jump_to_ruin() - set category = "Debug" - set name = "Jump to Ruin" - set desc = "Displays a list of all placed ruins to teleport to." - - if(!check_rights(R_DEBUG)) - return - - var/list/names = list() - for(var/i in GLOB.ruin_landmarks) - var/obj/effect/landmark/ruin/ruin_landmark = i - var/datum/map_template/ruin/template = ruin_landmark.ruin_template - - var/count = 1 - var/name = template.name - var/original_name = name - - while(name in names) - count++ - name = "[original_name] ([count])" - - names[name] = ruin_landmark - - var/ruinname = input("Select ruin", "Jump to Ruin") as null|anything in names - - var/obj/effect/landmark/ruin/landmark = names[ruinname] - - if(istype(landmark)) - var/datum/map_template/ruin/template = landmark.ruin_template - admin_forcemove(usr, get_turf(landmark)) - - to_chat(usr, "[template.name]") - to_chat(usr, "[template.description]") - - log_admin("[key_name(usr)] jumped to ruin [ruinname]") - if(!isobserver(usr)) - message_admins("[key_name_admin(usr)] jumped to ruin [ruinname]", 1) - - feedback_add_details("admin_verb","JT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_medal_disable() - set category = "Debug" - set name = "Toggle Medal Disable" - set desc = "Toggles the safety lock on trying to contact the medal hub." - - if(!check_rights(R_DEBUG)) - return - - SSmedals.hub_enabled = !SSmedals.hub_enabled - - message_admins("[key_name_admin(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.") - feedback_add_details("admin_verb","TMH") // If... - log_admin("[key_name(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.") +/client/proc/Debug2() + set category = "Debug" + set name = "Debug-Game" + + if(!check_rights(R_DEBUG)) + return + + if(Debug2) + Debug2 = 0 + message_admins("[key_name_admin(src)] toggled debugging off.") + log_admin("[key_name(src)] toggled debugging off.") + else + Debug2 = 1 + message_admins("[key_name_admin(src)] toggled debugging on.") + log_admin("[key_name(src)] toggled debugging on.") + + feedback_add_details("admin_verb","DG2") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/* 21st Sept 2010 +Updated by Skie -- Still not perfect but better! +Stuff you can't do: +Call proc /mob/proc/Dizzy() for some player +Because if you select a player mob as owner it tries to do the proc for +/mob/living/carbon/human/ instead. And that gives a run-time error. +But you can call procs that are of type /mob/living/carbon/human/proc/ for that player. +*/ + +/client/proc/callproc() + set category = "Debug" + set name = "Advanced ProcCall" + + if(!check_rights(R_PROCCALL)) + return + + spawn(0) + var/target = null + var/targetselected = 0 + var/returnval = null + var/class = null + + switch(alert("Proc owned by something?",,"Yes","No")) + if("Yes") + targetselected = 1 + if(src.holder && src.holder.marked_datum) + class = input("Proc owned by...","Owner",null) as null|anything in list("Obj","Mob","Area or Turf","Client","Marked datum ([holder.marked_datum.type])") + if(class == "Marked datum ([holder.marked_datum.type])") + class = "Marked datum" + else + class = input("Proc owned by...","Owner",null) as null|anything in list("Obj","Mob","Area or Turf","Client") + switch(class) + if("Obj") + target = input("Enter target:","Target",usr) as obj in world + if("Mob") + target = input("Enter target:","Target",usr) as mob in world + if("Area or Turf") + target = input("Enter target:","Target",usr.loc) as area|turf in world + if("Client") + var/list/keys = list() + for(var/client/C) + keys += C + target = input("Please, select a player!", "Selection", null, null) as null|anything in keys + if("Marked datum") + target = holder.marked_datum + else + return + if("No") + target = null + targetselected = 0 + + var/procname = clean_input("Proc path, eg: /proc/fake_blood","Path:", null) + if(!procname) return + + if(targetselected && !hascall(target,procname)) + to_chat(usr, "Error: callproc(): target has no such call [procname].") + return + + var/list/lst = get_callproc_args() + if(!lst) + return + + if(targetselected) + if(!target) + to_chat(usr, "Error: callproc(): owner of proc no longer exists.") + return + message_admins("[key_name_admin(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") + log_admin("[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") + returnval = call(target,procname)(arglist(lst)) // Pass the lst as an argument list to the proc + else + //this currently has no hascall protection. wasn't able to get it working. + message_admins("[key_name_admin(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]") + log_admin("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]") + returnval = call(procname)(arglist(lst)) // Pass the lst as an argument list to the proc + + to_chat(usr, "[procname] returned: [!isnull(returnval) ? returnval : "null"]") + feedback_add_details("admin_verb","APC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +GLOBAL_VAR(AdminProcCaller) +GLOBAL_PROTECT(AdminProcCaller) + +/proc/IsAdminAdvancedProcCall() +#ifdef TESTING + return FALSE +#else + return usr && usr.client && GLOB.AdminProcCaller == usr.client.ckey +#endif + +/client/proc/callproc_datum(var/A as null|area|mob|obj|turf) + set category = "Debug" + set name = "Atom ProcCall" + + if(!check_rights(R_PROCCALL)) + return + + var/procname = clean_input("Proc name, eg: fake_blood","Proc:", null) + if(!procname) + return + + if(!hascall(A,procname)) + to_chat(usr, "Error: callproc_datum(): target has no such call [procname].") + return + + var/list/lst = get_callproc_args() + if(!lst) + return + + if(!A || !IsValidSrc(A)) + to_chat(src, "Error: callproc_datum(): owner of proc no longer exists.") + return + message_admins("[key_name_admin(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]") + log_admin("[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]") + + spawn() + var/returnval = call(A,procname)(arglist(lst)) // Pass the lst as an argument list to the proc + to_chat(src, "[procname] returned: [!isnull(returnval) ? returnval : "null"]") + + feedback_add_details("admin_verb","DPC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/get_callproc_args() + var/argnum = input("Number of arguments","Number:",0) as num|null + if(!argnum && (argnum!=0)) return + + var/list/lst = list() + //TODO: make a list to store whether each argument was initialised as null. + //Reason: So we can abort the proccall if say, one of our arguments was a mob which no longer exists + //this will protect us from a fair few errors ~Carn + + while(argnum--) + var/class = null + // Make a list with each index containing one variable, to be given to the proc + if(src.holder && src.holder.marked_datum) + class = input("What kind of variable?","Variable Type") in list("text","num","type","reference","mob reference","icon","file","client","mob's area","Marked datum ([holder.marked_datum.type])","CANCEL") + if(holder.marked_datum && class == "Marked datum ([holder.marked_datum.type])") + class = "Marked datum" + else + class = input("What kind of variable?","Variable Type") in list("text","num","type","reference","mob reference","icon","file","client","mob's area","CANCEL") + switch(class) + if("CANCEL") + return null + + if("text") + lst += clean_input("Enter new text:","Text",null) + + if("num") + lst += input("Enter new number:","Num",0) as num + + if("type") + lst += input("Enter type:","Type") in typesof(/obj,/mob,/area,/turf) + + if("reference") + lst += input("Select reference:","Reference",src) as mob|obj|turf|area in world + + if("mob reference") + lst += input("Select reference:","Reference",usr) as mob in world + + if("file") + lst += input("Pick file:","File") as file + + if("icon") + lst += input("Pick icon:","Icon") as icon + + if("client") + var/list/keys = list() + for(var/mob/M in world) + keys += M.client + lst += input("Please, select a player!", "Selection", null, null) as null|anything in keys + + if("mob's area") + var/mob/temp = input("Select mob", "Selection", usr) as mob in world + lst += temp.loc + + if("Marked datum") + lst += holder.marked_datum + return lst + +/client/proc/Cell() + set category = "Debug" + set name = "Air Status in Location" + + if(!check_rights(R_DEBUG)) + return + + if(!mob) + return + var/turf/T = mob.loc + + if(!( istype(T, /turf) )) + return + + var/datum/gas_mixture/env = T.return_air() + + var/t = "" + t+= "Nitrogen : [env.nitrogen]\n" + t+= "Oxygen : [env.oxygen]\n" + t+= "Plasma : [env.toxins]\n" + t+= "CO2: [env.carbon_dioxide]\n" + + usr.show_message(t, 1) + feedback_add_details("admin_verb","ASL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_robotize(var/mob/M in GLOB.mob_list) + set category = "Event" + set name = "Make Robot" + + if(!check_rights(R_SPAWN)) + return + + if(!SSticker) + alert("Wait until the game starts") + return + if(istype(M, /mob/living/carbon/human)) + log_admin("[key_name(src)] has robotized [M.key].") + spawn(10) + M:Robotize() + + else + alert("Invalid mob") + +/client/proc/cmd_admin_animalize(var/mob/M in GLOB.mob_list) + set category = "Event" + set name = "Make Simple Animal" + + if(!check_rights(R_SPAWN)) + return + + if(!SSticker) + alert("Wait until the game starts") + return + + if(!M) + alert("That mob doesn't seem to exist, close the panel and try again.") + return + + if(istype(M, /mob/new_player)) + alert("The mob must not be a new_player.") + return + + log_admin("[key_name(src)] has animalized [M.key].") + spawn(10) + M.Animalize() + + +/client/proc/makepAI(var/turf/T in GLOB.mob_list) + set category = "Event" + set name = "Make pAI" + set desc = "Specify a location to spawn a pAI device, then specify a key to play that pAI" + + if(!check_rights(R_SPAWN)) + return + + var/list/available = list() + for(var/mob/C in GLOB.mob_list) + if(C.key) + available.Add(C) + var/mob/choice = input("Choose a player to play the pAI", "Spawn pAI") in available + if(!choice) + return 0 + if(!istype(choice, /mob/dead/observer)) + var/confirm = input("[choice.key] isn't ghosting right now. Are you sure you want to yank [choice.p_them()] out of [choice.p_their()] body and place [choice.p_them()] in this pAI?", "Spawn pAI Confirmation", "No") in list("Yes", "No") + if(confirm != "Yes") + return 0 + var/obj/item/paicard/card = new(T) + var/mob/living/silicon/pai/pai = new(card) + var/raw_name = clean_input("Enter your pAI name:", "pAI Name", "Personal AI", choice) + var/new_name = reject_bad_name(raw_name, 1) + if(new_name) + pai.name = new_name + pai.real_name = new_name + else + to_chat(usr, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and .") + pai.real_name = pai.name + pai.key = choice.key + card.setPersonality(pai) + for(var/datum/paiCandidate/candidate in paiController.pai_candidates) + if(candidate.key == choice.key) + paiController.pai_candidates.Remove(candidate) + feedback_add_details("admin_verb","MPAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_alienize(var/mob/M in GLOB.mob_list) + set category = "Event" + set name = "Make Alien" + + if(!check_rights(R_SPAWN)) + return + + if(!SSticker) + alert("Wait until the game starts") + return + if(ishuman(M)) + log_admin("[key_name(src)] has alienized [M.key].") + spawn(10) + M:Alienize() + feedback_add_details("admin_verb","MKAL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] made [key_name(M)] into an alien.") + message_admins("[key_name_admin(usr)] made [key_name(M)] into an alien.", 1) + else + alert("Invalid mob") + +/client/proc/cmd_admin_slimeize(var/mob/M in GLOB.mob_list) + set category = "Event" + set name = "Make slime" + + if(!check_rights(R_SPAWN)) + return + + if(!SSticker) + alert("Wait until the game starts") + return + if(ishuman(M)) + log_admin("[key_name(src)] has slimeized [M.key].") + spawn(10) + M:slimeize() + feedback_add_details("admin_verb","MKMET") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] made [key_name(M)] into a slime.") + message_admins("[key_name_admin(usr)] made [key_name(M)] into a slime.", 1) + else + alert("Invalid mob") + +/client/proc/cmd_admin_super(var/mob/M in GLOB.mob_list) + set category = "Event" + set name = "Make Superhero" + + if(!check_rights(R_SPAWN)) + return + + if(!SSticker) + alert("Wait until the game starts") + return + if(ishuman(M)) + var/type = input("Pick the Superhero","Superhero") as null|anything in GLOB.all_superheroes + var/datum/superheroes/S = GLOB.all_superheroes[type] + if(S) + S.create(M) + log_admin("[key_name(src)] has turned [M.key] into a Superhero.") + message_admins("[key_name_admin(usr)] made [key_name(M)] into a Superhero.", 1) + else + alert("Invalid mob") + +//TODO: merge the vievars version into this or something maybe mayhaps +/client/proc/cmd_debug_del_all() + set category = "Debug" + set name = "Del-All" + + if(!check_rights(R_DEBUG)) + return + + // to prevent REALLY stupid deletions + var/blocked = list(/mob/living, /mob/living/carbon, /mob/living/carbon/human, /mob/dead, /mob/dead/observer, /mob/living/silicon, /mob/living/silicon/robot, /mob/living/silicon/ai) + var/hsbitem = input(usr, "Choose an object to delete.", "Delete:") as null|anything in subtypesof(/obj) + subtypesof(/mob) - blocked + if(hsbitem) + for(var/atom/O in world) + if(istype(O, hsbitem)) + qdel(O) + log_admin("[key_name(src)] has deleted all instances of [hsbitem].") + message_admins("[key_name_admin(src)] has deleted all instances of [hsbitem].", 0) + feedback_add_details("admin_verb","DELA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_debug_del_sing() + set category = "Debug" + set name = "Del Singulo / Tesla" + + if(!check_rights(R_DEBUG)) + return + + //This gets a confirmation check because it's way easier to accidentally hit this and delete things than it is with del-all + var/confirm = alert("This will delete ALL Singularities and Tesla orbs except for any that are on away mission z-levels or the centcomm z-level. Are you sure you want to delete them?", "Confirm Panic Button", "Yes", "No") + if(confirm != "Yes") + return + + for(var/I in GLOB.singularities) + var/obj/singularity/S = I + if(!is_level_reachable(S.z)) + continue + qdel(S) + log_admin("[key_name(src)] has deleted all Singularities and Tesla orbs.") + message_admins("[key_name_admin(src)] has deleted all Singularities and Tesla orbs.", 0) + feedback_add_details("admin_verb","DELS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_debug_make_powernets() + set category = "Debug" + set name = "Make Powernets" + + if(!check_rights(R_DEBUG)) + return + + SSmachines.makepowernets() + log_admin("[key_name(src)] has remade the powernet. makepowernets() called.") + message_admins("[key_name_admin(src)] has remade the powernets. makepowernets() called.", 0) + feedback_add_details("admin_verb","MPWN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_grantfullaccess(var/mob/M in GLOB.mob_list) + set category = "Admin" + set name = "Grant Full Access" + + if(!check_rights(R_EVENT)) + return + + if(!SSticker) + alert("Wait until the game starts") + return + if(istype(M, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = M + if(H.wear_id) + var/obj/item/card/id/id = H.wear_id + if(istype(H.wear_id, /obj/item/pda)) + var/obj/item/pda/pda = H.wear_id + id = pda.id + id.icon_state = "gold" + id:access = get_all_accesses()+get_all_centcom_access()+get_all_syndicate_access() + else + var/obj/item/card/id/id = new/obj/item/card/id(M); + id.icon_state = "gold" + id:access = get_all_accesses()+get_all_centcom_access()+get_all_syndicate_access() + id.registered_name = H.real_name + id.assignment = "Captain" + id.name = "[id.registered_name]'s ID Card ([id.assignment])" + H.equip_to_slot_or_del(id, slot_wear_id) + H.update_inv_wear_id() + else + alert("Invalid mob") + feedback_add_details("admin_verb","GFA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(src)] has granted [M.key] full access.") + message_admins("[key_name_admin(usr)] has granted [M.key] full access.", 1) + +/client/proc/cmd_assume_direct_control(var/mob/M in GLOB.mob_list) + set category = "Admin" + set name = "Assume direct control" + set desc = "Direct intervention" + + if(!check_rights(R_DEBUG|R_ADMIN)) + return + + if(M.ckey) + if(alert("This mob is being controlled by [M.ckey]. Are you sure you wish to assume control of it? [M.ckey] will be made a ghost.",,"Yes","No") != "Yes") + return + else + var/mob/dead/observer/ghost = new/mob/dead/observer(M,1) + ghost.ckey = M.ckey + message_admins("[key_name_admin(usr)] assumed direct control of [M].", 1) + log_admin("[key_name(usr)] assumed direct control of [M].") + var/mob/adminmob = src.mob + M.ckey = src.ckey + if( isobserver(adminmob) ) + qdel(adminmob) + feedback_add_details("admin_verb","ADC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/cmd_admin_areatest() + set category = "Mapping" + set name = "Test areas" + + if(!check_rights(R_DEBUG)) + return + + var/list/areas_all = list() + var/list/areas_with_APC = list() + var/list/areas_with_air_alarm = list() + var/list/areas_with_RC = list() + var/list/areas_with_light = list() + var/list/areas_with_LS = list() + var/list/areas_with_intercom = list() + var/list/areas_with_camera = list() + + var/list/areas_with_multiple_APCs = list() + var/list/areas_with_multiple_air_alarms = list() + + for(var/area/A in world) + areas_all |= A.type + + for(var/obj/machinery/power/apc/APC in world) + var/area/A = get_area(APC) + if(!A) + continue + if(!(A.type in areas_with_APC)) + areas_with_APC |= A.type + else + areas_with_multiple_APCs |= A.type + + for(var/obj/machinery/alarm/alarm in world) + var/area/A = get_area(alarm) + if(!A) + continue + if(!(A.type in areas_with_air_alarm)) + areas_with_air_alarm |= A.type + else + areas_with_multiple_air_alarms |= A.type + + for(var/obj/machinery/requests_console/RC in world) + var/area/A = get_area(RC) + if(!A) + continue + areas_with_RC |= A.type + + for(var/obj/machinery/light/L in world) + var/area/A = get_area(L) + if(!A) + continue + areas_with_light |= A.type + + for(var/obj/machinery/light_switch/LS in world) + var/area/A = get_area(LS) + if(!A) + continue + areas_with_LS |= A.type + + for(var/obj/item/radio/intercom/I in world) + var/area/A = get_area(I) + if(!A) + continue + areas_with_intercom |= A.type + + for(var/obj/machinery/camera/C in world) + var/area/A = get_area(C) + if(!A) + continue + areas_with_camera |= A.type + + var/list/areas_without_APC = areas_all - areas_with_APC + var/list/areas_without_air_alarm = areas_all - areas_with_air_alarm + var/list/areas_without_RC = areas_all - areas_with_RC + var/list/areas_without_light = areas_all - areas_with_light + var/list/areas_without_LS = areas_all - areas_with_LS + var/list/areas_without_intercom = areas_all - areas_with_intercom + var/list/areas_without_camera = areas_all - areas_with_camera + + to_chat(world, "AREAS WITHOUT AN APC:") + for(var/areatype in areas_without_APC) + to_chat(world, "* [areatype]") + + to_chat(world, "AREAS WITHOUT AN AIR ALARM:") + for(var/areatype in areas_without_air_alarm) + to_chat(world, "* [areatype]") + + to_chat(world, "AREAS WITH TOO MANY APCS:") + for(var/areatype in areas_with_multiple_APCs) + to_chat(world, "* [areatype]") + + to_chat(world, "AREAS WITH TOO MANY AIR ALARMS:") + for(var/areatype in areas_with_multiple_air_alarms) + to_chat(world, "* [areatype]") + + to_chat(world, "AREAS WITHOUT A REQUEST CONSOLE:") + for(var/areatype in areas_without_RC) + to_chat(world, "* [areatype]") + + to_chat(world, "AREAS WITHOUT ANY LIGHTS:") + for(var/areatype in areas_without_light) + to_chat(world, "* [areatype]") + + to_chat(world, "AREAS WITHOUT A LIGHT SWITCH:") + for(var/areatype in areas_without_LS) + to_chat(world, "* [areatype]") + + to_chat(world, "AREAS WITHOUT ANY INTERCOMS:") + for(var/areatype in areas_without_intercom) + to_chat(world, "* [areatype]") + + to_chat(world, "AREAS WITHOUT ANY CAMERAS:") + for(var/areatype in areas_without_camera) + to_chat(world, "* [areatype]") + +/client/proc/cmd_admin_dress(var/mob/living/carbon/human/M in GLOB.mob_list) + set category = "Event" + set name = "Select equipment" + + if(!check_rights(R_EVENT)) + return + + if(!ishuman(M) && !isobserver(M)) + alert("Invalid mob") + return + + var/dresscode = robust_dress_shop() + + if(!dresscode) + return + + var/delete_pocket + var/mob/living/carbon/human/H + if(isobserver(M)) + H = M.change_mob_type(/mob/living/carbon/human, null, null, TRUE) + else + H = M + if(H.l_store || H.r_store || H.s_store) //saves a lot of time for admins and coders alike + if(alert("Should the items in their pockets be dropped? Selecting \"No\" will delete them.", "Robust quick dress shop", "Yes", "No") == "No") + delete_pocket = TRUE + + for (var/obj/item/I in H.get_equipped_items(delete_pocket)) + qdel(I) + if(dresscode != "Naked") + H.equipOutfit(dresscode) + + H.regenerate_icons() + + feedback_add_details("admin_verb", "SE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] changed the equipment of [key_name(M)] to [dresscode].") + message_admins("[key_name_admin(usr)] changed the equipment of [key_name_admin(M)] to [dresscode].", 1) + +/client/proc/robust_dress_shop() + var/list/outfits = list( + "Naked", + "As Job...", + "Custom..." + ) + + var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job) + for(var/path in paths) + var/datum/outfit/O = path //not much to initalize here but whatever + if(initial(O.can_be_admin_equipped)) + outfits[initial(O.name)] = path + + var/dresscode = input("Select outfit", "Robust quick dress shop") as null|anything in outfits + if(isnull(dresscode)) + return + + if(outfits[dresscode]) + dresscode = outfits[dresscode] + + if(dresscode == "As Job...") + var/list/job_paths = subtypesof(/datum/outfit/job) + var/list/job_outfits = list() + for(var/path in job_paths) + var/datum/outfit/O = path + if(initial(O.can_be_admin_equipped)) + job_outfits[initial(O.name)] = path + + dresscode = input("Select job equipment", "Robust quick dress shop") as null|anything in job_outfits + dresscode = job_outfits[dresscode] + if(isnull(dresscode)) + return + + if(dresscode == "Custom...") + var/list/custom_names = list() + for(var/datum/outfit/D in GLOB.custom_outfits) + custom_names[D.name] = D + var/selected_name = input("Select outfit", "Robust quick dress shop") as null|anything in custom_names + dresscode = custom_names[selected_name] + if(isnull(dresscode)) + return + + return dresscode + +/client/proc/startSinglo() + set category = "Debug" + set name = "Start Singularity" + set desc = "Sets up the singularity and all machines to get power flowing through the station" + + if(!check_rights(R_DEBUG)) + return + + if(alert("Are you sure? This will start up the engine. Should only be used during debug!",,"Yes","No") != "Yes") + return + + for(var/obj/machinery/power/emitter/E in GLOB.machines) + if(E.anchored) + E.active = 1 + + for(var/obj/machinery/field/generator/F in GLOB.machines) + if(F.active == 0) + F.active = 1 + F.state = 2 + F.power = 250 + F.anchored = 1 + F.warming_up = 3 + F.start_fields() + F.update_icon() + + spawn(30) + for(var/obj/machinery/the_singularitygen/G in GLOB.machines) + if(G.anchored) + var/obj/singularity/S = new /obj/singularity(get_turf(G)) + S.energy = 800 + break + + for(var/obj/machinery/power/rad_collector/Rad in GLOB.machines) + if(Rad.anchored) + if(!Rad.P) + var/obj/item/tank/plasma/Plasma = new/obj/item/tank/plasma(Rad) + Plasma.air_contents.toxins = 70 + Rad.drainratio = 0 + Rad.P = Plasma + Plasma.loc = Rad + + if(!Rad.active) + Rad.toggle_power() + + for(var/obj/machinery/power/smes/SMES in GLOB.machines) + if(SMES.anchored) + SMES.input_attempt = 1 + +/client/proc/cmd_debug_mob_lists() + set category = "Debug" + set name = "Debug Mob Lists" + set desc = "For when you just gotta know" + + if(!check_rights(R_DEBUG)) + return + + switch(input("Which list?") in list("Players","Admins","Mobs","Living Mobs","Dead Mobs","Silicons","Clients","Respawnable Mobs")) + if("Players") + to_chat(usr, jointext(GLOB.player_list,",")) + if("Admins") + to_chat(usr, jointext(GLOB.admins,",")) + if("Mobs") + to_chat(usr, jointext(GLOB.mob_list,",")) + if("Living Mobs") + to_chat(usr, jointext(GLOB.living_mob_list,",")) + if("Dead Mobs") + to_chat(usr, jointext(GLOB.dead_mob_list,",")) + if("Silicons") + to_chat(usr, jointext(GLOB.silicon_mob_list,",")) + if("Clients") + to_chat(usr, jointext(GLOB.clients,",")) + if("Respawnable Mobs") + to_chat(usr, jointext(GLOB.respawnable_list,",")) + +/client/proc/cmd_display_del_log() + set category = "Debug" + set name = "Display del() Log" + set desc = "Display del's log of everything that's passed through it." + + if(!check_rights(R_DEBUG)) + return + + var/list/dellog = list("List of things that have gone through qdel this round

          ") + sortTim(SSgarbage.items, cmp=/proc/cmp_qdel_item_time, associative = TRUE) + for(var/path in SSgarbage.items) + var/datum/qdel_item/I = SSgarbage.items[path] + dellog += "
        1. [path]
            " + if(I.failures) + dellog += "
          • Failures: [I.failures]
          • " + dellog += "
          • qdel() Count: [I.qdels]
          • " + dellog += "
          • Destroy() Cost: [I.destroy_time]ms
          • " + if(I.hard_deletes) + dellog += "
          • Total Hard Deletes [I.hard_deletes]
          • " + dellog += "
          • Time Spent Hard Deleting: [I.hard_delete_time]ms
          • " + if(I.slept_destroy) + dellog += "
          • Sleeps: [I.slept_destroy]
          • " + if(I.no_respect_force) + dellog += "
          • Ignored force: [I.no_respect_force]
          • " + if(I.no_hint) + dellog += "
          • No hint: [I.no_hint]
          • " + dellog += "
        2. " + + dellog += "
        " + + usr << browse(dellog.Join(), "window=dellog") + +/client/proc/cmd_display_del_log_simple() + set category = "Debug" + set name = "Display Simple del() Log" + set desc = "Display a compacted del's log." + + if(!check_rights(R_DEBUG)) + return + + var/dat = "List of things that failed to GC this round

        " + for(var/path in SSgarbage.items) + var/datum/qdel_item/I = SSgarbage.items[path] + if(I.failures) + dat += "[I] - [I.failures] times
        " + + dat += "List of paths that did not return a qdel hint in Destroy()

        " + for(var/path in SSgarbage.items) + var/datum/qdel_item/I = SSgarbage.items[path] + if(I.no_hint) + dat += "[I]
        " + + dat += "List of paths that slept in Destroy()

        " + for(var/path in SSgarbage.items) + var/datum/qdel_item/I = SSgarbage.items[path] + if(I.slept_destroy) + dat += "[I]
        " + + usr << browse(dat, "window=simpledellog") + +/client/proc/cmd_admin_toggle_block(var/mob/M,var/block) + if(!check_rights(R_SPAWN)) + return + + if(!SSticker) + alert("Wait until the game starts") + return + if(istype(M, /mob/living/carbon)) + M.dna.SetSEState(block,!M.dna.GetSEState(block)) + genemutcheck(M,block,null,MUTCHK_FORCED) + M.update_mutations() + var/state="[M.dna.GetSEState(block)?"on":"off"]" + var/blockname=assigned_blocks[block] + message_admins("[key_name_admin(src)] has toggled [M.key]'s [blockname] block [state]!") + log_admin("[key_name(src)] has toggled [M.key]'s [blockname] block [state]!") + else + alert("Invalid mob") + +/client/proc/reload_nanoui_resources() + set category = "Debug" + set name = "Reload NanoUI Resources" + set desc = "Force the client to redownload NanoUI Resources" + + // Close open NanoUIs. + SSnanoui.close_user_uis(usr) + + // Re-load the assets. + var/datum/asset/assets = get_asset_datum(/datum/asset/nanoui) + assets.register() + + // Clear the user's cache so they get resent. + usr.client.cache = list() + +/client/proc/view_runtimes() + set category = "Debug" + set name = "View Runtimes" + set desc = "Open the Runtime Viewer" + + if(!check_rights(R_DEBUG)) + return + + error_cache.showTo(usr) + +/client/proc/jump_to_ruin() + set category = "Debug" + set name = "Jump to Ruin" + set desc = "Displays a list of all placed ruins to teleport to." + + if(!check_rights(R_DEBUG)) + return + + var/list/names = list() + for(var/i in GLOB.ruin_landmarks) + var/obj/effect/landmark/ruin/ruin_landmark = i + var/datum/map_template/ruin/template = ruin_landmark.ruin_template + + var/count = 1 + var/name = template.name + var/original_name = name + + while(name in names) + count++ + name = "[original_name] ([count])" + + names[name] = ruin_landmark + + var/ruinname = input("Select ruin", "Jump to Ruin") as null|anything in names + + var/obj/effect/landmark/ruin/landmark = names[ruinname] + + if(istype(landmark)) + var/datum/map_template/ruin/template = landmark.ruin_template + admin_forcemove(usr, get_turf(landmark)) + + to_chat(usr, "[template.name]") + to_chat(usr, "[template.description]") + + log_admin("[key_name(usr)] jumped to ruin [ruinname]") + if(!isobserver(usr)) + message_admins("[key_name_admin(usr)] jumped to ruin [ruinname]", 1) + + feedback_add_details("admin_verb","JT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_medal_disable() + set category = "Debug" + set name = "Toggle Medal Disable" + set desc = "Toggles the safety lock on trying to contact the medal hub." + + if(!check_rights(R_DEBUG)) + return + + SSmedals.hub_enabled = !SSmedals.hub_enabled + + message_admins("[key_name_admin(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.") + feedback_add_details("admin_verb","TMH") // If... + log_admin("[key_name(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.") diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm index 23d171e69a0..6770ca114aa 100644 --- a/code/modules/admin/verbs/diagnostics.dm +++ b/code/modules/admin/verbs/diagnostics.dm @@ -1,178 +1,178 @@ -/client/proc/air_status(turf/target as turf) - set category = "Debug" - set name = "Display Air Status" - - if(!check_rights(R_DEBUG)) - return - - if(!isturf(target)) - return - - var/datum/gas_mixture/GM = target.return_air() - var/burning = 0 - if(istype(target, /turf/simulated)) - var/turf/simulated/T = target - if(T.active_hotspot) - burning = 1 - - to_chat(usr, "@[target.x],[target.y]: O:[GM.oxygen] T:[GM.toxins] N:[GM.nitrogen] C:[GM.carbon_dioxide] w [GM.temperature] Kelvin, [GM.return_pressure()] kPa [(burning)?("BURNING"):(null)]") - for(var/datum/gas/trace_gas in GM.trace_gases) - to_chat(usr, "[trace_gas.type]: [trace_gas.moles]") - - message_admins("[key_name_admin(usr)] has checked the air status of [T]") - log_admin("[key_name(usr)] has checked the air status of [T]") - - feedback_add_details("admin_verb","DAST") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/fix_next_move() - set category = "Debug" - set name = "Unfreeze Everyone" - - if(!check_rights(R_DEBUG)) - return - - message_admins("[key_name_admin(usr)] has unfrozen everyone") - log_admin("[key_name(usr)] has unfrozen everyone") - - var/largest_move_time = 0 - var/largest_click_time = 0 - var/mob/largest_move_mob = null - var/mob/largest_click_mob = null - for(var/mob/M in world) - if(!M.client) - continue - if(M.next_move >= largest_move_time) - largest_move_mob = M - if(M.next_move > world.time) - largest_move_time = M.next_move - world.time - else - largest_move_time = 1 - if(M.next_click >= largest_click_time) - largest_click_mob = M - if(M.next_click > world.time) - largest_click_time = M.next_click - world.time - else - largest_click_time = 0 - log_admin("DEBUG: [key_name(M)] next_move = [M.next_move] next_click = [M.next_click] world.time = [world.time]") - M.next_move = 1 - M.next_click = 0 - - message_admins("[key_name_admin(largest_move_mob)] had the largest move delay with [largest_move_time] frames / [largest_move_time/10] seconds!", 1) - message_admins("[key_name_admin(largest_click_mob)] had the largest click delay with [largest_click_time] frames / [largest_click_time/10] seconds!", 1) - message_admins("world.time = [world.time]", 1) - - feedback_add_details("admin_verb","UFE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/client/proc/radio_report() - set category = "Debug" - set name = "Radio report" - - if(!check_rights(R_DEBUG)) - return - - var/filters = list( - "1" = "RADIO_TO_AIRALARM", - "2" = "RADIO_FROM_AIRALARM", - "3" = "RADIO_CHAT", - "4" = "RADIO_ATMOSIA", - "5" = "RADIO_NAVBEACONS", - "6" = "RADIO_AIRLOCK", - "7" = "RADIO_SECBOT", - "8" = "RADIO_MULEBOT", - "_default" = "NO_FILTER" - ) - var/output = "Radio Report
        " - for(var/fq in SSradio.frequencies) - output += "Freq: [fq]
        " - var/list/datum/radio_frequency/fqs = SSradio.frequencies[fq] - if(!fqs) - output += "  ERROR
        " - continue - for(var/filter in fqs.devices) - var/list/f = fqs.devices[filter] - if(!f) - output += "  [filters[filter]]: ERROR
        " - continue - output += "  [filters[filter]]: [f.len]
        " - for(var/device in f) - if(isobj(device)) - output += "    [device] ([device:x],[device:y],[device:z] in area [get_area(device:loc)])
        " - else - output += "    [device]
        " - - usr << browse(output,"window=radioreport") - - message_admins("[key_name_admin(usr)] has generated a radio report") - log_admin("[key_name(usr)] has generated a radio report") - - feedback_add_details("admin_verb","RR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/reload_admins() - set name = "Reload Admins" - set category = "Debug" - - if(!check_rights(R_SERVER)) - return - - message_admins("[key_name_admin(usr)] has manually reloaded admins") - log_admin("[key_name(usr)] has manually reloaded admins") - - load_admins() - feedback_add_details("admin_verb","RLDA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/print_jobban_old() - set name = "Print Jobban Log" - set desc = "This spams all the active jobban entries for the current round to standard output." - set category = "Debug" - - if(!check_rights(R_DEBUG)) - return - - to_chat(usr, "Jobbans active in this round.") - for(var/t in jobban_keylist) - to_chat(usr, "[t]") - - message_admins("[key_name_admin(usr)] has printed the jobban log") - log_admin("[key_name(usr)] has printed the jobban log") - -/client/proc/print_jobban_old_filter() - set name = "Search Jobban Log" - set desc = "This searches all the active jobban entries for the current round and outputs the results to standard output." - set category = "Debug" - - if(!check_rights(R_DEBUG)) - return - - var/filter = clean_input("Contains what?","Filter") - if(!filter) - return - - to_chat(usr, "Jobbans active in this round.") - for(var/t in jobban_keylist) - if(findtext(t, filter)) - to_chat(usr, "[t]") - - message_admins("[key_name_admin(usr)] has searched the jobban log for [filter]") - log_admin("[key_name(usr)] has searched the jobban log for [filter]") - -/client/proc/vv_by_ref() - set name = "VV by Ref" - set desc = "Give this a ref string, and you will see its corresponding VV panel if it exists" - set category = "Debug" - - // It's gated by "Debug Verbs", so might as well gate it to the debug permission - if(!check_rights(R_DEBUG)) - return - - var/refstring = clean_input("Which reference?","Ref") - if(!refstring) - return - - var/datum/D = locate(refstring) - if(!D) - to_chat(usr, "That ref string does not correspond to any datum.") - return - - debug_variables(D) +/client/proc/air_status(turf/target as turf) + set category = "Debug" + set name = "Display Air Status" + + if(!check_rights(R_DEBUG)) + return + + if(!isturf(target)) + return + + var/datum/gas_mixture/GM = target.return_air() + var/burning = 0 + if(istype(target, /turf/simulated)) + var/turf/simulated/T = target + if(T.active_hotspot) + burning = 1 + + to_chat(usr, "@[target.x],[target.y]: O:[GM.oxygen] T:[GM.toxins] N:[GM.nitrogen] C:[GM.carbon_dioxide] w [GM.temperature] Kelvin, [GM.return_pressure()] kPa [(burning)?("BURNING"):(null)]") + for(var/datum/gas/trace_gas in GM.trace_gases) + to_chat(usr, "[trace_gas.type]: [trace_gas.moles]") + + message_admins("[key_name_admin(usr)] has checked the air status of [T]") + log_admin("[key_name(usr)] has checked the air status of [T]") + + feedback_add_details("admin_verb","DAST") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/fix_next_move() + set category = "Debug" + set name = "Unfreeze Everyone" + + if(!check_rights(R_DEBUG)) + return + + message_admins("[key_name_admin(usr)] has unfrozen everyone") + log_admin("[key_name(usr)] has unfrozen everyone") + + var/largest_move_time = 0 + var/largest_click_time = 0 + var/mob/largest_move_mob = null + var/mob/largest_click_mob = null + for(var/mob/M in world) + if(!M.client) + continue + if(M.next_move >= largest_move_time) + largest_move_mob = M + if(M.next_move > world.time) + largest_move_time = M.next_move - world.time + else + largest_move_time = 1 + if(M.next_click >= largest_click_time) + largest_click_mob = M + if(M.next_click > world.time) + largest_click_time = M.next_click - world.time + else + largest_click_time = 0 + log_admin("DEBUG: [key_name(M)] next_move = [M.next_move] next_click = [M.next_click] world.time = [world.time]") + M.next_move = 1 + M.next_click = 0 + + message_admins("[key_name_admin(largest_move_mob)] had the largest move delay with [largest_move_time] frames / [largest_move_time/10] seconds!", 1) + message_admins("[key_name_admin(largest_click_mob)] had the largest click delay with [largest_click_time] frames / [largest_click_time/10] seconds!", 1) + message_admins("world.time = [world.time]", 1) + + feedback_add_details("admin_verb","UFE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/client/proc/radio_report() + set category = "Debug" + set name = "Radio report" + + if(!check_rights(R_DEBUG)) + return + + var/filters = list( + "1" = "RADIO_TO_AIRALARM", + "2" = "RADIO_FROM_AIRALARM", + "3" = "RADIO_CHAT", + "4" = "RADIO_ATMOSIA", + "5" = "RADIO_NAVBEACONS", + "6" = "RADIO_AIRLOCK", + "7" = "RADIO_SECBOT", + "8" = "RADIO_MULEBOT", + "_default" = "NO_FILTER" + ) + var/output = "Radio Report
        " + for(var/fq in SSradio.frequencies) + output += "Freq: [fq]
        " + var/list/datum/radio_frequency/fqs = SSradio.frequencies[fq] + if(!fqs) + output += "  ERROR
        " + continue + for(var/filter in fqs.devices) + var/list/f = fqs.devices[filter] + if(!f) + output += "  [filters[filter]]: ERROR
        " + continue + output += "  [filters[filter]]: [f.len]
        " + for(var/device in f) + if(isobj(device)) + output += "    [device] ([device:x],[device:y],[device:z] in area [get_area(device:loc)])
        " + else + output += "    [device]
        " + + usr << browse(output,"window=radioreport") + + message_admins("[key_name_admin(usr)] has generated a radio report") + log_admin("[key_name(usr)] has generated a radio report") + + feedback_add_details("admin_verb","RR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/reload_admins() + set name = "Reload Admins" + set category = "Debug" + + if(!check_rights(R_SERVER)) + return + + message_admins("[key_name_admin(usr)] has manually reloaded admins") + log_admin("[key_name(usr)] has manually reloaded admins") + + load_admins() + feedback_add_details("admin_verb","RLDA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/print_jobban_old() + set name = "Print Jobban Log" + set desc = "This spams all the active jobban entries for the current round to standard output." + set category = "Debug" + + if(!check_rights(R_DEBUG)) + return + + to_chat(usr, "Jobbans active in this round.") + for(var/t in jobban_keylist) + to_chat(usr, "[t]") + + message_admins("[key_name_admin(usr)] has printed the jobban log") + log_admin("[key_name(usr)] has printed the jobban log") + +/client/proc/print_jobban_old_filter() + set name = "Search Jobban Log" + set desc = "This searches all the active jobban entries for the current round and outputs the results to standard output." + set category = "Debug" + + if(!check_rights(R_DEBUG)) + return + + var/filter = clean_input("Contains what?","Filter") + if(!filter) + return + + to_chat(usr, "Jobbans active in this round.") + for(var/t in jobban_keylist) + if(findtext(t, filter)) + to_chat(usr, "[t]") + + message_admins("[key_name_admin(usr)] has searched the jobban log for [filter]") + log_admin("[key_name(usr)] has searched the jobban log for [filter]") + +/client/proc/vv_by_ref() + set name = "VV by Ref" + set desc = "Give this a ref string, and you will see its corresponding VV panel if it exists" + set category = "Debug" + + // It's gated by "Debug Verbs", so might as well gate it to the debug permission + if(!check_rights(R_DEBUG)) + return + + var/refstring = clean_input("Which reference?","Ref") + if(!refstring) + return + + var/datum/D = locate(refstring) + if(!D) + to_chat(usr, "That ref string does not correspond to any datum.") + return + + debug_variables(D) diff --git a/code/modules/admin/verbs/dice.dm b/code/modules/admin/verbs/dice.dm index 4df6e3107a0..58bac2e113a 100644 --- a/code/modules/admin/verbs/dice.dm +++ b/code/modules/admin/verbs/dice.dm @@ -19,4 +19,4 @@ if(alert("Do you want to inform the world about the result?",,"Yes", "No") == "Yes") to_chat(world, "

        Gods rolled [dice], result is [result]

        ") - message_admins("[key_name_admin(src)] rolled dice [dice], result is [result]", 1) \ No newline at end of file + message_admins("[key_name_admin(src)] rolled dice [dice], result is [result]", 1) diff --git a/code/modules/admin/verbs/getlogs.dm b/code/modules/admin/verbs/getlogs.dm index 22c6d778733..73f993c61de 100644 --- a/code/modules/admin/verbs/getlogs.dm +++ b/code/modules/admin/verbs/getlogs.dm @@ -38,4 +38,4 @@ else return to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.") - return \ No newline at end of file + return diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 80a171494ea..61356f46027 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -1,185 +1,185 @@ -//- Are all the floors with or without air, as they should be? (regular or airless) -//- Does the area have an APC? -//- Does the area have an Air Alarm? -//- Does the area have a Request Console? -//- Does the area have lights? -//- Does the area have a light switch? -//- Does the area have enough intercoms? -//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug) -//- Is the area connected to the scrubbers air loop? -//- Is the area connected to the vent air loop? (vent pumps) -//- Is everything wired properly? -//- Does the area have a fire alarm and firedoors? -//- Do all pod doors work properly? -//- Are accesses set properly on doors, pod buttons, etc. -//- Are all items placed properly? (not below vents, scrubbers, tables) -//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room? -//- Check for any misplaced or stacked piece of pipe (air and disposal) -//- Check for any misplaced or stacked piece of wire -//- Identify how hard it is to break into the area and where the weak points are -//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels. - -var/camera_range_display_status = 0 -var/intercom_range_display_status = 0 - -/obj/effect/debugging/camera_range - icon = 'icons/480x480.dmi' - icon_state = "25percent" - - New() - src.pixel_x = -224 - src.pixel_y = -224 - -/obj/effect/debugging/mapfix_marker - name = "map fix marker" - icon = 'icons/mob/screen_gen.dmi' - icon_state = "mapfixmarker" - desc = "I am a mappers mistake." - -/obj/effect/debugging/marker - icon = 'icons/turf/areas.dmi' - icon_state = "yellow" - -/obj/effect/debugging/marker/Move() - return 0 - -/client/proc/camera_view() - set category = "Mapping" - set name = "Camera Range Display" - - if(!check_rights(R_DEBUG)) - return - - if(camera_range_display_status) - camera_range_display_status = 0 - else - camera_range_display_status = 1 - - for(var/obj/effect/debugging/camera_range/C in world) - qdel(C) - - if(camera_range_display_status) - for(var/obj/machinery/camera/C in cameranet.cameras) - new/obj/effect/debugging/camera_range(C.loc) - feedback_add_details("admin_verb","mCRD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/sec_camera_report() - set category = "Mapping" - set name = "Camera Report" - - if(!check_rights(R_DEBUG)) - return - - var/list/obj/machinery/camera/CL = list() - - for(var/obj/machinery/camera/C in cameranet.cameras) - CL += C - - var/output = {"CAMERA ANOMALIES REPORT
        -The following anomalies have been detected. The ones in red need immediate attention: Some of those in black may be intentional.
          "} - - for(var/obj/machinery/camera/C1 in CL) - for(var/obj/machinery/camera/C2 in CL) - if(C1 != C2) - if(C1.c_tag == C2.c_tag) - output += "
        • c_tag match for sec. cameras at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) and \[[C2.x], [C2.y], [C2.z]\] ([C2.loc.loc]) - c_tag is [C1.c_tag]
        • " - if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y) - output += "
        • FULLY overlapping sec. cameras at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) Networks: [C1.network] and [C2.network]
        • " - if(C1.loc == C2.loc) - output += "
        • overlapping sec. cameras at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) Networks: [C1.network] and [C2.network]
        • " - var/turf/T = get_step(C1,turn(C1.dir,180)) - if(!T || !isturf(T) || !T.density ) - if(!(locate(/obj/structure/grille,T))) - var/window_check = 0 - for(var/obj/structure/window/W in T) - if(W.dir == turn(C1.dir,180) || W.fulltile) - window_check = 1 - break - if(!window_check) - output += "
        • Camera not connected to wall at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) Network: [C1.network]
        • " - - output += "
        " - usr << browse(output,"window=airreport;size=1000x500") - feedback_add_details("admin_verb","mCRP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/intercom_view() - set category = "Mapping" - set name = "Intercom Range Display" - - if(!check_rights(R_DEBUG)) - return - - if(intercom_range_display_status) - intercom_range_display_status = 0 - else - intercom_range_display_status = 1 - - for(var/obj/effect/debugging/marker/M in world) - qdel(M) - - if(intercom_range_display_status) - for(var/obj/item/radio/intercom/I in world) - for(var/turf/T in orange(7,I)) - var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T) - if(!(F in view(7,I.loc))) - qdel(F) - feedback_add_details("admin_verb","mIRD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/count_objects_on_z_level() - set category = "Mapping" - set name = "Count Objects On Level" - - if(!check_rights(R_DEBUG)) - return - - var/level = clean_input("Which z-level?","Level?") - if(!level) return - var/num_level = text2num(level) - if(!num_level) return - if(!isnum(num_level)) return - - var/type_text = clean_input("Which type path?","Path?") - if(!type_text) return - var/type_path = text2path(type_text) - if(!type_path) return - - var/count = 0 - - var/list/atom/atom_list = list() - - for(var/atom/A in world) - if(istype(A,type_path)) - var/atom/B = A - while(!(isturf(B.loc))) - if(B && B.loc) - B = B.loc - else - break - if(B) - if(B.z == num_level) - count++ - atom_list += A - - to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level].") - feedback_add_details("admin_verb","mOBJZ") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/count_objects_all() - set category = "Mapping" - set name = "Count Objects All" - - if(!check_rights(R_DEBUG)) - return - - var/type_text = clean_input("Which type path?","") - if(!type_text) return - var/type_path = text2path(type_text) - if(!type_path) return - - var/count = 0 - - for(var/atom/A in world) - if(istype(A,type_path)) - count++ - - to_chat(world, "There are [count] objects of type [type_path] in the game world.") - feedback_add_details("admin_verb","mOBJ") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +//- Are all the floors with or without air, as they should be? (regular or airless) +//- Does the area have an APC? +//- Does the area have an Air Alarm? +//- Does the area have a Request Console? +//- Does the area have lights? +//- Does the area have a light switch? +//- Does the area have enough intercoms? +//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug) +//- Is the area connected to the scrubbers air loop? +//- Is the area connected to the vent air loop? (vent pumps) +//- Is everything wired properly? +//- Does the area have a fire alarm and firedoors? +//- Do all pod doors work properly? +//- Are accesses set properly on doors, pod buttons, etc. +//- Are all items placed properly? (not below vents, scrubbers, tables) +//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room? +//- Check for any misplaced or stacked piece of pipe (air and disposal) +//- Check for any misplaced or stacked piece of wire +//- Identify how hard it is to break into the area and where the weak points are +//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels. + +var/camera_range_display_status = 0 +var/intercom_range_display_status = 0 + +/obj/effect/debugging/camera_range + icon = 'icons/480x480.dmi' + icon_state = "25percent" + + New() + src.pixel_x = -224 + src.pixel_y = -224 + +/obj/effect/debugging/mapfix_marker + name = "map fix marker" + icon = 'icons/mob/screen_gen.dmi' + icon_state = "mapfixmarker" + desc = "I am a mappers mistake." + +/obj/effect/debugging/marker + icon = 'icons/turf/areas.dmi' + icon_state = "yellow" + +/obj/effect/debugging/marker/Move() + return 0 + +/client/proc/camera_view() + set category = "Mapping" + set name = "Camera Range Display" + + if(!check_rights(R_DEBUG)) + return + + if(camera_range_display_status) + camera_range_display_status = 0 + else + camera_range_display_status = 1 + + for(var/obj/effect/debugging/camera_range/C in world) + qdel(C) + + if(camera_range_display_status) + for(var/obj/machinery/camera/C in cameranet.cameras) + new/obj/effect/debugging/camera_range(C.loc) + feedback_add_details("admin_verb","mCRD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/sec_camera_report() + set category = "Mapping" + set name = "Camera Report" + + if(!check_rights(R_DEBUG)) + return + + var/list/obj/machinery/camera/CL = list() + + for(var/obj/machinery/camera/C in cameranet.cameras) + CL += C + + var/output = {"CAMERA ANOMALIES REPORT
        +The following anomalies have been detected. The ones in red need immediate attention: Some of those in black may be intentional.
          "} + + for(var/obj/machinery/camera/C1 in CL) + for(var/obj/machinery/camera/C2 in CL) + if(C1 != C2) + if(C1.c_tag == C2.c_tag) + output += "
        • c_tag match for sec. cameras at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) and \[[C2.x], [C2.y], [C2.z]\] ([C2.loc.loc]) - c_tag is [C1.c_tag]
        • " + if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y) + output += "
        • FULLY overlapping sec. cameras at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) Networks: [C1.network] and [C2.network]
        • " + if(C1.loc == C2.loc) + output += "
        • overlapping sec. cameras at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) Networks: [C1.network] and [C2.network]
        • " + var/turf/T = get_step(C1,turn(C1.dir,180)) + if(!T || !isturf(T) || !T.density ) + if(!(locate(/obj/structure/grille,T))) + var/window_check = 0 + for(var/obj/structure/window/W in T) + if(W.dir == turn(C1.dir,180) || W.fulltile) + window_check = 1 + break + if(!window_check) + output += "
        • Camera not connected to wall at \[[C1.x], [C1.y], [C1.z]\] ([C1.loc.loc]) Network: [C1.network]
        • " + + output += "
        " + usr << browse(output,"window=airreport;size=1000x500") + feedback_add_details("admin_verb","mCRP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/intercom_view() + set category = "Mapping" + set name = "Intercom Range Display" + + if(!check_rights(R_DEBUG)) + return + + if(intercom_range_display_status) + intercom_range_display_status = 0 + else + intercom_range_display_status = 1 + + for(var/obj/effect/debugging/marker/M in world) + qdel(M) + + if(intercom_range_display_status) + for(var/obj/item/radio/intercom/I in world) + for(var/turf/T in orange(7,I)) + var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T) + if(!(F in view(7,I.loc))) + qdel(F) + feedback_add_details("admin_verb","mIRD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/count_objects_on_z_level() + set category = "Mapping" + set name = "Count Objects On Level" + + if(!check_rights(R_DEBUG)) + return + + var/level = clean_input("Which z-level?","Level?") + if(!level) return + var/num_level = text2num(level) + if(!num_level) return + if(!isnum(num_level)) return + + var/type_text = clean_input("Which type path?","Path?") + if(!type_text) return + var/type_path = text2path(type_text) + if(!type_path) return + + var/count = 0 + + var/list/atom/atom_list = list() + + for(var/atom/A in world) + if(istype(A,type_path)) + var/atom/B = A + while(!(isturf(B.loc))) + if(B && B.loc) + B = B.loc + else + break + if(B) + if(B.z == num_level) + count++ + atom_list += A + + to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level].") + feedback_add_details("admin_verb","mOBJZ") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/count_objects_all() + set category = "Mapping" + set name = "Count Objects All" + + if(!check_rights(R_DEBUG)) + return + + var/type_text = clean_input("Which type path?","") + if(!type_text) return + var/type_path = text2path(type_text) + if(!type_path) return + + var/count = 0 + + for(var/atom/A in world) + if(istype(A,type_path)) + count++ + + to_chat(world, "There are [count] objects of type [type_path] in the game world.") + feedback_add_details("admin_verb","mOBJ") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/massmodvar.dm b/code/modules/admin/verbs/massmodvar.dm index bafa7d629d3..50c329f17d2 100644 --- a/code/modules/admin/verbs/massmodvar.dm +++ b/code/modules/admin/verbs/massmodvar.dm @@ -1,272 +1,272 @@ -/client/proc/cmd_mass_modify_object_variables(atom/A, var/var_name) - set category = "Debug" - set name = "Mass Edit Variables" - set desc="(target) Edit all instances of a target item's variables" - - var/method = 0 //0 means strict type detection while 1 means this type and all subtypes (IE: /obj/item with this set to 1 will set it to ALL itms) - - if(!check_rights(R_VAREDIT)) return - - if(A && A.type) - if(typesof(A.type)) - switch(input("Strict object type detection?") as null|anything in list("Strictly this type","This type and subtypes", "Cancel")) - if("Strictly this type") - method = 0 - if("This type and subtypes") - method = 1 - if("Cancel") - return - if(null) - return - - src.massmodify_variables(A, var_name, method) - feedback_add_details("admin_verb","MEV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/massmodify_variables(datum/O, var_name = "", method = 0) - if(!check_rights(R_VAREDIT)) - return - if(!istype(O)) - return - - var/variable = "" - if(!var_name) - var/list/names = list() - for(var/V in O.vars) - names += V - - names = sortList(names) - - variable = input("Which var?", "Var") as null|anything in names - else - variable = var_name - - if(!variable || !O.can_vv_get(variable)) - return - var/default - var/var_value = O.vars[variable] - - if(variable in VVckey_edit) - to_chat(src, "It's forbidden to mass-modify ckeys. It'll crash everyone's client you dummy.") - return - if(variable in VVlocked) - if(!check_rights(R_DEBUG)) - return - if(variable in VVicon_edit_lock) - if(!check_rights(R_EVENT | R_DEBUG)) - return - if(variable in VVpixelmovement) - if(!check_rights(R_DEBUG)) - return - var/prompt = alert(src, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") - if(prompt != "Continue") - return - - default = vv_get_class(var_value) - - if(isnull(default)) - to_chat(src, "Unable to determine variable type.") - else - to_chat(src, "Variable appears to be [uppertext(default)].") - - to_chat(src, "Variable contains: [var_value]") - - if(default == VV_NUM) - var/dir_text = "" - if(dir < 0 && dir < 16) - if(dir & 1) - dir_text += "NORTH" - if(dir & 2) - dir_text += "SOUTH" - if(dir & 4) - dir_text += "EAST" - if(dir & 8) - dir_text += "WEST" - - if(dir_text) - to_chat(src, "If a direction, direction is: [dir_text]") - - var/value = vv_get_value(default_class = default) - var/new_value = value["value"] - var/class = value["class"] - - if(!class || !new_value == null && class != VV_NULL) - return - - if(class == VV_MESSAGE) - class = VV_TEXT - - if(value["type"]) - class = VV_NEW_TYPE - - var/original_name = "[O]" - - var/rejected = 0 - var/accepted = 0 - - switch(class) - if(VV_RESTORE_DEFAULT) - to_chat(src, "Finding items...") - var/list/items = get_all_of_type(O.type, method) - to_chat(src, "Changing [items.len] items...") - for(var/thing in items) - if(!thing) - continue - var/datum/D = thing - if(D.vv_edit_var(variable, initial(D.vars[variable])) != FALSE) - accepted++ - else - rejected++ - CHECK_TICK - - if(VV_TEXT) - var/list/varsvars = vv_parse_text(O, new_value) - var/pre_processing = new_value - var/unique - if(varsvars && varsvars.len) - unique = alert(usr, "Process vars unique to each instance, or same for all?", "Variable Association", "Unique", "Same") - if(unique == "Unique") - unique = TRUE - else - unique = FALSE - for(var/V in varsvars) - new_value = replacetext(new_value,"\[[V]]","[O.vars[V]]") - - to_chat(src, "Finding items...") - var/list/items = get_all_of_type(O.type, method) - to_chat(src, "Changing [items.len] items...") - for(var/thing in items) - if(!thing) - continue - var/datum/D = thing - if(unique) - new_value = pre_processing - for(var/V in varsvars) - new_value = replacetext(new_value,"\[[V]]","[D.vars[V]]") - - if(D.vv_edit_var(variable, new_value) != FALSE) - accepted++ - else - rejected++ - CHECK_TICK - - if(VV_NEW_TYPE) - var/many = alert(src, "Create only one [value["type"]] and assign each or a new one for each thing", "How Many", "One", "Many", "Cancel") - if(many == "Cancel") - return - if(many == "Many") - many = TRUE - else - many = FALSE - - var/type = value["type"] - to_chat(src, "Finding items...") - var/list/items = get_all_of_type(O.type, method) - to_chat(src, "Changing [items.len] items...") - for(var/thing in items) - if(!thing) - continue - var/datum/D = thing - if(many && !new_value) - new_value = new type() - - if(D.vv_edit_var(variable, new_value) != FALSE) - accepted++ - else - rejected++ - new_value = null - CHECK_TICK - - else - to_chat(src, "Finding items...") - var/list/items = get_all_of_type(O.type, method) - to_chat(src, "Changing [items.len] items...") - for(var/thing in items) - if(!thing) - continue - var/datum/D = thing - if(D.vv_edit_var(variable, new_value) != FALSE) - accepted++ - else - rejected++ - CHECK_TICK - - - var/count = rejected+accepted - if(!count) - to_chat(src, "No objects found") - return - if(!accepted) - to_chat(src, "Every object rejected your edit") - return - if(rejected) - to_chat(src, "[rejected] out of [count] objects rejected your edit") - - log_world("### MassVarEdit by [src]: [O.type] (A/R [accepted]/[rejected]) [variable]=[html_encode("[O.vars[variable]]")]([list2params(value)])") - log_admin("[key_name(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)") - message_admins("[key_name_admin(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)") - -/proc/get_all_of_type(var/T, subtypes = TRUE) - var/list/typecache = list() - typecache[T] = 1 - if(subtypes) - typecache = typecacheof(typecache) - . = list() - if(ispath(T, /mob)) - for(var/mob/thing in GLOB.mob_list) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else if(ispath(T, /obj/machinery/door)) - for(var/obj/machinery/door/thing in GLOB.airlocks) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else if(ispath(T, /obj/machinery)) - for(var/obj/machinery/thing in GLOB.machines) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else if(ispath(T, /obj)) - for(var/obj/thing in world) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else if(ispath(T, /atom/movable)) - for(var/atom/movable/thing in world) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else if(ispath(T, /turf)) - for(var/turf/thing in world) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else if(ispath(T, /atom)) - for(var/atom/thing in world) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else if(ispath(T, /client)) - for(var/client/thing in GLOB.clients) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else if(ispath(T, /datum)) - for(var/datum/thing) - if(typecache[thing.type]) - . += thing - CHECK_TICK - - else - for(var/datum/thing in world) - if(typecache[thing.type]) - . += thing - CHECK_TICK \ No newline at end of file +/client/proc/cmd_mass_modify_object_variables(atom/A, var/var_name) + set category = "Debug" + set name = "Mass Edit Variables" + set desc="(target) Edit all instances of a target item's variables" + + var/method = 0 //0 means strict type detection while 1 means this type and all subtypes (IE: /obj/item with this set to 1 will set it to ALL itms) + + if(!check_rights(R_VAREDIT)) return + + if(A && A.type) + if(typesof(A.type)) + switch(input("Strict object type detection?") as null|anything in list("Strictly this type","This type and subtypes", "Cancel")) + if("Strictly this type") + method = 0 + if("This type and subtypes") + method = 1 + if("Cancel") + return + if(null) + return + + src.massmodify_variables(A, var_name, method) + feedback_add_details("admin_verb","MEV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/massmodify_variables(datum/O, var_name = "", method = 0) + if(!check_rights(R_VAREDIT)) + return + if(!istype(O)) + return + + var/variable = "" + if(!var_name) + var/list/names = list() + for(var/V in O.vars) + names += V + + names = sortList(names) + + variable = input("Which var?", "Var") as null|anything in names + else + variable = var_name + + if(!variable || !O.can_vv_get(variable)) + return + var/default + var/var_value = O.vars[variable] + + if(variable in VVckey_edit) + to_chat(src, "It's forbidden to mass-modify ckeys. It'll crash everyone's client you dummy.") + return + if(variable in VVlocked) + if(!check_rights(R_DEBUG)) + return + if(variable in VVicon_edit_lock) + if(!check_rights(R_EVENT | R_DEBUG)) + return + if(variable in VVpixelmovement) + if(!check_rights(R_DEBUG)) + return + var/prompt = alert(src, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") + if(prompt != "Continue") + return + + default = vv_get_class(var_value) + + if(isnull(default)) + to_chat(src, "Unable to determine variable type.") + else + to_chat(src, "Variable appears to be [uppertext(default)].") + + to_chat(src, "Variable contains: [var_value]") + + if(default == VV_NUM) + var/dir_text = "" + if(dir < 0 && dir < 16) + if(dir & 1) + dir_text += "NORTH" + if(dir & 2) + dir_text += "SOUTH" + if(dir & 4) + dir_text += "EAST" + if(dir & 8) + dir_text += "WEST" + + if(dir_text) + to_chat(src, "If a direction, direction is: [dir_text]") + + var/value = vv_get_value(default_class = default) + var/new_value = value["value"] + var/class = value["class"] + + if(!class || !new_value == null && class != VV_NULL) + return + + if(class == VV_MESSAGE) + class = VV_TEXT + + if(value["type"]) + class = VV_NEW_TYPE + + var/original_name = "[O]" + + var/rejected = 0 + var/accepted = 0 + + switch(class) + if(VV_RESTORE_DEFAULT) + to_chat(src, "Finding items...") + var/list/items = get_all_of_type(O.type, method) + to_chat(src, "Changing [items.len] items...") + for(var/thing in items) + if(!thing) + continue + var/datum/D = thing + if(D.vv_edit_var(variable, initial(D.vars[variable])) != FALSE) + accepted++ + else + rejected++ + CHECK_TICK + + if(VV_TEXT) + var/list/varsvars = vv_parse_text(O, new_value) + var/pre_processing = new_value + var/unique + if(varsvars && varsvars.len) + unique = alert(usr, "Process vars unique to each instance, or same for all?", "Variable Association", "Unique", "Same") + if(unique == "Unique") + unique = TRUE + else + unique = FALSE + for(var/V in varsvars) + new_value = replacetext(new_value,"\[[V]]","[O.vars[V]]") + + to_chat(src, "Finding items...") + var/list/items = get_all_of_type(O.type, method) + to_chat(src, "Changing [items.len] items...") + for(var/thing in items) + if(!thing) + continue + var/datum/D = thing + if(unique) + new_value = pre_processing + for(var/V in varsvars) + new_value = replacetext(new_value,"\[[V]]","[D.vars[V]]") + + if(D.vv_edit_var(variable, new_value) != FALSE) + accepted++ + else + rejected++ + CHECK_TICK + + if(VV_NEW_TYPE) + var/many = alert(src, "Create only one [value["type"]] and assign each or a new one for each thing", "How Many", "One", "Many", "Cancel") + if(many == "Cancel") + return + if(many == "Many") + many = TRUE + else + many = FALSE + + var/type = value["type"] + to_chat(src, "Finding items...") + var/list/items = get_all_of_type(O.type, method) + to_chat(src, "Changing [items.len] items...") + for(var/thing in items) + if(!thing) + continue + var/datum/D = thing + if(many && !new_value) + new_value = new type() + + if(D.vv_edit_var(variable, new_value) != FALSE) + accepted++ + else + rejected++ + new_value = null + CHECK_TICK + + else + to_chat(src, "Finding items...") + var/list/items = get_all_of_type(O.type, method) + to_chat(src, "Changing [items.len] items...") + for(var/thing in items) + if(!thing) + continue + var/datum/D = thing + if(D.vv_edit_var(variable, new_value) != FALSE) + accepted++ + else + rejected++ + CHECK_TICK + + + var/count = rejected+accepted + if(!count) + to_chat(src, "No objects found") + return + if(!accepted) + to_chat(src, "Every object rejected your edit") + return + if(rejected) + to_chat(src, "[rejected] out of [count] objects rejected your edit") + + log_world("### MassVarEdit by [src]: [O.type] (A/R [accepted]/[rejected]) [variable]=[html_encode("[O.vars[variable]]")]([list2params(value)])") + log_admin("[key_name(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)") + message_admins("[key_name_admin(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)") + +/proc/get_all_of_type(var/T, subtypes = TRUE) + var/list/typecache = list() + typecache[T] = 1 + if(subtypes) + typecache = typecacheof(typecache) + . = list() + if(ispath(T, /mob)) + for(var/mob/thing in GLOB.mob_list) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else if(ispath(T, /obj/machinery/door)) + for(var/obj/machinery/door/thing in GLOB.airlocks) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else if(ispath(T, /obj/machinery)) + for(var/obj/machinery/thing in GLOB.machines) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else if(ispath(T, /obj)) + for(var/obj/thing in world) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else if(ispath(T, /atom/movable)) + for(var/atom/movable/thing in world) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else if(ispath(T, /turf)) + for(var/turf/thing in world) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else if(ispath(T, /atom)) + for(var/atom/thing in world) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else if(ispath(T, /client)) + for(var/client/thing in GLOB.clients) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else if(ispath(T, /datum)) + for(var/datum/thing) + if(typecache[thing.type]) + . += thing + CHECK_TICK + + else + for(var/datum/thing in world) + if(typecache[thing.type]) + . += thing + CHECK_TICK diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index c5348fbeabe..bd0624bcdff 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -1,633 +1,633 @@ -var/list/VVlocked = list("vars", "var_edited", "client", "firemut", "ishulk", "telekinesis", "xray", "ka", "virus", "viruses", "cuffed", "last_eaten", "unlock_content") // R_DEBUG -var/list/VVicon_edit_lock = list("icon", "icon_state", "overlays", "underlays", "resize") // R_EVENT | R_DEBUG -var/list/VVckey_edit = list("key", "ckey") // R_EVENT | R_DEBUG -var/list/VVpixelmovement = list("step_x", "step_y", "step_size", "bound_height", "bound_width", "bound_x", "bound_y") // R_DEBUG + warning -/client/proc/vv_get_class(var/var_value) - if(isnull(var_value)) - . = VV_NULL - - else if(isnum(var_value)) - . = VV_NUM - - else if(istext(var_value)) - if(findtext(var_value, "\n")) - . = VV_MESSAGE - else - . = VV_TEXT - - else if(isicon(var_value)) - . = VV_ICON - - else if(ismob(var_value)) - . = VV_MOB_REFERENCE - - else if(isloc(var_value)) - . = VV_ATOM_REFERENCE - - else if(istype(var_value, /matrix)) - . = VV_MATRIX - - else if(istype(var_value,/client)) - . = VV_CLIENT - - else if(istype(var_value, /datum)) - . = VV_DATUM_REFERENCE - - else if(ispath(var_value)) - if(ispath(var_value, /atom)) - . = VV_ATOM_TYPE - else if(ispath(var_value, /datum)) - . = VV_DATUM_TYPE - else - . = VV_TYPE - - else if(islist(var_value)) - . = VV_LIST - - else if(isfile(var_value)) - . = VV_FILE - else if(istype(var_value, /regex)) - . = VV_REGEX - else - . = VV_NULL - -/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes) - . = list("class" = class, "value" = null) - if(!class) - if(!classes) - classes = list( - VV_NUM, - VV_TEXT, - VV_MESSAGE, - VV_ICON, - VV_ATOM_REFERENCE, - VV_DATUM_REFERENCE, - VV_MOB_REFERENCE, - VV_CLIENT, - VV_ATOM_TYPE, - VV_DATUM_TYPE, - VV_TYPE, - VV_MATRIX, - VV_REGEX, - VV_FILE, - VV_NEW_ATOM, - VV_NEW_DATUM, - VV_NEW_TYPE, - VV_NEW_LIST, - VV_NULL, - VV_RESTORE_DEFAULT - ) - - if(holder && holder.marked_datum && !(VV_MARKED_DATUM in restricted_classes)) - classes += "[VV_MARKED_DATUM] ([holder.marked_datum.type])" - if(restricted_classes) - classes -= restricted_classes - - if(extra_classes) - classes += extra_classes - - .["class"] = input(src, "What kind of data?", "Variable Type", default_class) as null|anything in classes - if(holder && holder.marked_datum && .["class"] == "[VV_MARKED_DATUM] ([holder.marked_datum.type])") - .["class"] = VV_MARKED_DATUM - - - switch(.["class"]) - if(VV_TEXT) - .["value"] = input("Enter new text:", "Text", current_value) as null|text - if(.["value"] == null) - .["class"] = null - return - if(VV_MESSAGE) - .["value"] = input("Enter new text:", "Text", current_value) as null|message - if(.["value"] == null) - .["class"] = null - return - - - if(VV_NUM) - .["value"] = input("Enter new number:", "Num", current_value) as null|num - if(.["value"] == null) - .["class"] = null - return - - - if(VV_ATOM_TYPE) - .["value"] = pick_closest_path(FALSE) - if(.["value"] == null) - .["class"] = null - return - - if(VV_DATUM_TYPE) - .["value"] = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) - if(.["value"] == null) - .["class"] = null - return - - if(VV_TYPE) - var/type = current_value - var/error = "" - do - type = input("Enter type:[error]", "Type", type) as null|text - if(!type) - break - type = text2path(type) - error = "\nType not found, Please try again" - while(!type) - if(!type) - .["class"] = null - return - .["value"] = type - - if(VV_MATRIX) - .["value"] = text2matrix(input("Enter a, b, c, d, e, and f, seperated by a space.", "Matrix", "1 0 0 0 1 0") as null|text) - if(.["value"] == null) - .["class"] = null - return - - if(VV_REGEX) - var/reg = input("Enter regex", "Regex", "") as null|text - if(!reg) - return - .["value"] = regex(reg) - if(.["value"] == null) - .["class"] = null - - - if(VV_ATOM_REFERENCE) - var/type = pick_closest_path(FALSE) - var/subtypes = vv_subtype_prompt(type) - if(subtypes == null) - .["class"] = null - return - var/list/things = vv_reference_list(type, subtypes) - var/value = input("Select reference:", "Reference", current_value) as null|anything in things - if(!value) - .["class"] = null - return - .["value"] = things[value] - - if(VV_DATUM_REFERENCE) - var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) - var/subtypes = vv_subtype_prompt(type) - if(subtypes == null) - .["class"] = null - return - var/list/things = vv_reference_list(type, subtypes) - var/value = input("Select reference:", "Reference", current_value) as null|anything in things - if(!value) - .["class"] = null - return - .["value"] = things[value] - - if(VV_MOB_REFERENCE) - var/type = pick_closest_path(FALSE, make_types_fancy(typesof(/mob))) - var/subtypes = vv_subtype_prompt(type) - if(subtypes == null) - .["class"] = null - return - var/list/things = vv_reference_list(type, subtypes) - var/value = input("Select reference:", "Reference", current_value) as null|anything in things - if(!value) - .["class"] = null - return - .["value"] = things[value] - - - - if(VV_CLIENT) - .["value"] = input("Select reference:", "Reference", current_value) as null|anything in GLOB.clients - if(.["value"] == null) - .["class"] = null - return - - - if(VV_FILE) - .["value"] = input("Pick file:", "File") as null|file - if(.["value"] == null) - .["class"] = null - return - - - if(VV_ICON) - .["value"] = input("Pick icon:", "Icon") as null|icon - if(.["value"] == null) - .["class"] = null - return - - - if(VV_MARKED_DATUM) - .["value"] = holder.marked_datum - if(.["value"] == null) - .["class"] = null - return - - if(VV_NEW_ATOM) - var/type = pick_closest_path(FALSE) - if(!type) - .["class"] = null - return - .["type"] = type - .["value"] = new type() - - if(VV_NEW_DATUM) - var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) - if(!type) - .["class"] = null - return - .["type"] = type - .["value"] = new type() - - if(VV_NEW_TYPE) - var/type = current_value - var/error = "" - do - type = input("Enter type:[error]", "Type", type) as null|text - if(!type) - break - type = text2path(type) - error = "\nType not found, Please try again" - while(!type) - if(!type) - .["class"] = null - return - .["type"] = type - .["value"] = new type() - - - if(VV_NEW_LIST) - .["value"] = list() - .["type"] = /list - -/client/proc/vv_parse_text(O, new_var) - if(O && findtext(new_var, "\[")) - var/process_vars = alert(usr, "\[] detected in string, process as variables?", "Process Variables?", "Yes", "No") - if(process_vars == "Yes") - . = string2listofvars(new_var, O) - -//do they want you to include subtypes? -//FALSE = no subtypes, strict exact type pathing (or the type doesn't have subtypes) -//TRUE = Yes subtypes -//NULL = User cancelled at the prompt or invalid type given -/client/proc/vv_subtype_prompt(var/type) - if(!ispath(type)) - return - var/list/subtypes = subtypesof(type) - if(!subtypes || !subtypes.len) - return FALSE - if(subtypes && subtypes.len) - switch(alert("Strict object type detection?", "Type detection", "Strictly this type","This type and subtypes", "Cancel")) - if("Strictly this type") - return FALSE - if("This type and subtypes") - return TRUE - else - return - -/client/proc/vv_reference_list(type, subtypes) - . = list() - var/list/types = list(type) - if(subtypes) - types = typesof(type) - - var/list/fancytypes = make_types_fancy(types) - - for(var/fancytype in fancytypes) //swap the assoication - types[fancytypes[fancytype]] = fancytype - - var/things = get_all_of_type(type, subtypes) - - var/i = 0 - for(var/thing in things) - var/datum/D = thing - i++ - //try one of 3 methods to shorten the type text: - // fancy type, - // fancy type with the base type removed from the begaining, - // the type with the base type removed from the begaining - var/fancytype = types[D.type] - if(findtext(fancytype, types[type])) - fancytype = copytext(fancytype, length(types[type])+1) - var/shorttype = copytext("[D.type]", length("[type]")+1) - if(length(shorttype) > length(fancytype)) - shorttype = fancytype - if(!length(shorttype)) - shorttype = "/" - - .["[D]([shorttype])\ref[D]#[i]"] = D - -/client/proc/mod_list_add_ass(atom/O) //haha - var/list/L = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT)) - var/class = L["class"] - if(!class) - return - var/var_value = L["value"] - - if(class == VV_TEXT || class == VV_MESSAGE) - var/list/varsvars = vv_parse_text(O, var_value) - for(var/V in varsvars) - var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]") - - return var_value - -/client/proc/mod_list_add(list/L, atom/O, original_name, objectvar) - var/list/LL = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT)) - var/class = LL["class"] - if(!class) - return - var/var_value = LL["value"] - - if(class == VV_TEXT || class == VV_MESSAGE) - var/list/varsvars = vv_parse_text(O, var_value) - for(var/V in varsvars) - var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]") - - if(O) - L = L.Copy() - - L += var_value - - switch(alert("Would you like to associate a value with the list entry?",,"Yes","No")) - if("Yes") - L[var_value] = mod_list_add_ass(O) //hehe - if(O) - if(!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: ADDED=[var_value]") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]") - message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]") - -/client/proc/mod_list(list/L, atom/O, original_name, objectvar, index, autodetect_class = FALSE) - if(!check_rights(R_VAREDIT)) - return - if(!istype(L, /list)) - to_chat(src, "Not a List.") - return - - if(L.len > 1000) - var/confirm = alert(src, "The list you're trying to edit is very long, continuing may crash the server.", "Warning", "Continue", "Abort") - if(confirm != "Continue") - return - - - - var/list/names = list() - for(var/i in 1 to L.len) - var/key = L[i] - var/value - if(IS_NORMAL_LIST(L) && !isnum(key)) - value = L[key] - if(value == null) - value = "null" - names["#[i] [key] = [value]"] = i - if(!index) - var/variable = input("Which var?","Var") as null|anything in names + "(ADD VAR)" + "(CLEAR NULLS)" + "(CLEAR DUPES)" + "(SHUFFLE)" - - if(variable == null) - return - - if(variable == "(ADD VAR)") - mod_list_add(L, O, original_name, objectvar) - return - - if(variable == "(CLEAR NULLS)") - L = L.Copy() - listclearnulls(L) - if(!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR NULLS") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR NULLS") - message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR NULLS") - return - - if(variable == "(CLEAR DUPES)") - L = uniqueList(L) - if(!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR DUPES") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR DUPES") - message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR DUPES") - return - - if(variable == "(SHUFFLE)") - L = shuffle(L) - if(!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [O.type] [objectvar]: SHUFFLE") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: SHUFFLE") - message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: SHUFFLE") - return - - index = names[variable] - - - var/assoc_key - if(index == null) - return - var/assoc = 0 - var/prompt = alert(src, "Do you want to edit the key or it's assigned value?", "Associated List", "Key", "Assigned Value", "Cancel") - if(prompt == "Cancel") - return - if(prompt == "Assigned Value") - assoc = 1 - assoc_key = L[index] - var/default - var/variable - if(assoc) - variable = L[assoc_key] - else - variable = L[index] - - default = vv_get_class(variable) - - to_chat(src, "Variable appears to be [uppertext(default)].") - - to_chat(src, "Variable contains: [L[index]]") - - if(default == VV_NUM) - var/dir_text = "" - if(dir < 0 && dir < 16) - if(dir & 1) - dir_text += "NORTH" - if(dir & 2) - dir_text += "SOUTH" - if(dir & 4) - dir_text += "EAST" - if(dir & 8) - dir_text += "WEST" - - if(dir_text) - to_chat(src, "If a direction, direction is: [dir_text]") - - var/original_var - if(assoc) - original_var = L[assoc_key] - else - original_var = L[index] - if(O) - L = L.Copy() - var/class - if(autodetect_class) - if(default == VV_TEXT) - default = VV_MESSAGE - class = default - var/list/LL = vv_get_value(default_class = default, current_value = original_var, restricted_classes = list(VV_RESTORE_DEFAULT), extra_classes = list(VV_LIST, "DELETE FROM LIST")) - class = LL["class"] - if(!class) - return - var/new_var = LL["value"] - - if(class == VV_MESSAGE) - class = VV_TEXT - - switch(class) //Spits a runtime error if you try to modify an entry in the contents list. Dunno how to fix it, yet. - if(VV_LIST) - mod_list(variable, O, original_name, objectvar) - - if("DELETE FROM LIST") - L.Cut(index, index+1) - if(O) - if(!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [O.type] [objectvar]: REMOVED=[html_encode("[original_var]")]") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]") - message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]") - return - - if(VV_TEXT) - var/list/varsvars = vv_parse_text(O, new_var) - for(var/V in varsvars) - new_var = replacetext(new_var,"\[[V]]","[O.vars[V]]") - - - if(assoc) - L[assoc_key] = new_var - else - L[index] = new_var - if(O) - if(!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: [original_var]=[new_var]") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: [original_var]=[new_var]") - message_admins("[key_name_admin(src)] modified [original_name]'s varlist [objectvar]: [original_var]=[new_var]") - -/proc/vv_varname_lockcheck(param_var_name) - if(param_var_name in VVlocked) - if(!check_rights(R_DEBUG)) - return FALSE - if(param_var_name in VVckey_edit) - if(!check_rights(R_EVENT | R_DEBUG)) - return FALSE - if(param_var_name in VVicon_edit_lock) - if(!check_rights(R_EVENT | R_DEBUG)) - return FALSE - if(param_var_name in VVpixelmovement) - if(!check_rights(R_DEBUG)) - return FALSE - var/prompt = alert(usr, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") - if(prompt != "Continue") - return FALSE - return TRUE - -/client/proc/modify_variables(atom/O, param_var_name = null, autodetect_class = 0) - if(!check_rights(R_VAREDIT)) - return - - var/class - var/variable - var/var_value - - if(param_var_name) - if(!param_var_name in O.vars) - to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])") - return - variable = param_var_name - - else - var/list/names = list() - for(var/V in O.vars) - names += V - - names = sortList(names) - - variable = input("Which var?","Var") as null|anything in names - if(!variable) - return - - if(!O.can_vv_get(variable)) - return - - if(!vv_varname_lockcheck(variable)) - return - - var_value = O.vars[variable] - - var/default = vv_get_class(var_value) - - if(isnull(default)) - to_chat(src, "Unable to determine variable type.") - else - to_chat(src, "Variable appears to be [uppertext(default)].") - - to_chat(src, "Variable contains: [var_value]") - - if(default == VV_NUM) - var/dir_text = "" - if(dir < 0 && dir < 16) - if(dir & 1) - dir_text += "NORTH" - if(dir & 2) - dir_text += "SOUTH" - if(dir & 4) - dir_text += "EAST" - if(dir & 8) - dir_text += "WEST" - - if(dir_text) - to_chat(src, "If a direction, direction is: [dir_text]") - - if(autodetect_class && default != VV_NULL) - if(default == VV_TEXT) - default = VV_MESSAGE - class = default - - var/list/value = vv_get_value(class, default, var_value, extra_classes = list(VV_LIST)) - class = value["class"] - - if(!class) - return - var/var_new = value["value"] - - if(class == VV_MESSAGE) - class = VV_TEXT - - var/original_name = "[O]" - - switch(class) - if(VV_LIST) - if(!islist(var_value)) - mod_list(list(), O, original_name, variable) - - mod_list(var_value, O, original_name, variable) - return - - if(VV_RESTORE_DEFAULT) - var_new = initial(O.vars[variable]) - - if(VV_TEXT) - var/list/varsvars = vv_parse_text(O, var_new) - for(var/V in varsvars) - var_new = replacetext(var_new,"\[[V]]","[O.vars[V]]") - - if(!O.vv_edit_var(variable, var_new)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### VarEdit by [src]: [O.type] [variable]=[html_encode("[var_new]")]") - log_admin("[key_name(src)] modified [original_name]'s [variable] to [var_new]") - var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] to [var_new]" - message_admins(msg) +var/list/VVlocked = list("vars", "var_edited", "client", "firemut", "ishulk", "telekinesis", "xray", "ka", "virus", "viruses", "cuffed", "last_eaten", "unlock_content") // R_DEBUG +var/list/VVicon_edit_lock = list("icon", "icon_state", "overlays", "underlays", "resize") // R_EVENT | R_DEBUG +var/list/VVckey_edit = list("key", "ckey") // R_EVENT | R_DEBUG +var/list/VVpixelmovement = list("step_x", "step_y", "step_size", "bound_height", "bound_width", "bound_x", "bound_y") // R_DEBUG + warning +/client/proc/vv_get_class(var/var_value) + if(isnull(var_value)) + . = VV_NULL + + else if(isnum(var_value)) + . = VV_NUM + + else if(istext(var_value)) + if(findtext(var_value, "\n")) + . = VV_MESSAGE + else + . = VV_TEXT + + else if(isicon(var_value)) + . = VV_ICON + + else if(ismob(var_value)) + . = VV_MOB_REFERENCE + + else if(isloc(var_value)) + . = VV_ATOM_REFERENCE + + else if(istype(var_value, /matrix)) + . = VV_MATRIX + + else if(istype(var_value,/client)) + . = VV_CLIENT + + else if(istype(var_value, /datum)) + . = VV_DATUM_REFERENCE + + else if(ispath(var_value)) + if(ispath(var_value, /atom)) + . = VV_ATOM_TYPE + else if(ispath(var_value, /datum)) + . = VV_DATUM_TYPE + else + . = VV_TYPE + + else if(islist(var_value)) + . = VV_LIST + + else if(isfile(var_value)) + . = VV_FILE + else if(istype(var_value, /regex)) + . = VV_REGEX + else + . = VV_NULL + +/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes) + . = list("class" = class, "value" = null) + if(!class) + if(!classes) + classes = list( + VV_NUM, + VV_TEXT, + VV_MESSAGE, + VV_ICON, + VV_ATOM_REFERENCE, + VV_DATUM_REFERENCE, + VV_MOB_REFERENCE, + VV_CLIENT, + VV_ATOM_TYPE, + VV_DATUM_TYPE, + VV_TYPE, + VV_MATRIX, + VV_REGEX, + VV_FILE, + VV_NEW_ATOM, + VV_NEW_DATUM, + VV_NEW_TYPE, + VV_NEW_LIST, + VV_NULL, + VV_RESTORE_DEFAULT + ) + + if(holder && holder.marked_datum && !(VV_MARKED_DATUM in restricted_classes)) + classes += "[VV_MARKED_DATUM] ([holder.marked_datum.type])" + if(restricted_classes) + classes -= restricted_classes + + if(extra_classes) + classes += extra_classes + + .["class"] = input(src, "What kind of data?", "Variable Type", default_class) as null|anything in classes + if(holder && holder.marked_datum && .["class"] == "[VV_MARKED_DATUM] ([holder.marked_datum.type])") + .["class"] = VV_MARKED_DATUM + + + switch(.["class"]) + if(VV_TEXT) + .["value"] = input("Enter new text:", "Text", current_value) as null|text + if(.["value"] == null) + .["class"] = null + return + if(VV_MESSAGE) + .["value"] = input("Enter new text:", "Text", current_value) as null|message + if(.["value"] == null) + .["class"] = null + return + + + if(VV_NUM) + .["value"] = input("Enter new number:", "Num", current_value) as null|num + if(.["value"] == null) + .["class"] = null + return + + + if(VV_ATOM_TYPE) + .["value"] = pick_closest_path(FALSE) + if(.["value"] == null) + .["class"] = null + return + + if(VV_DATUM_TYPE) + .["value"] = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) + if(.["value"] == null) + .["class"] = null + return + + if(VV_TYPE) + var/type = current_value + var/error = "" + do + type = input("Enter type:[error]", "Type", type) as null|text + if(!type) + break + type = text2path(type) + error = "\nType not found, Please try again" + while(!type) + if(!type) + .["class"] = null + return + .["value"] = type + + if(VV_MATRIX) + .["value"] = text2matrix(input("Enter a, b, c, d, e, and f, seperated by a space.", "Matrix", "1 0 0 0 1 0") as null|text) + if(.["value"] == null) + .["class"] = null + return + + if(VV_REGEX) + var/reg = input("Enter regex", "Regex", "") as null|text + if(!reg) + return + .["value"] = regex(reg) + if(.["value"] == null) + .["class"] = null + + + if(VV_ATOM_REFERENCE) + var/type = pick_closest_path(FALSE) + var/subtypes = vv_subtype_prompt(type) + if(subtypes == null) + .["class"] = null + return + var/list/things = vv_reference_list(type, subtypes) + var/value = input("Select reference:", "Reference", current_value) as null|anything in things + if(!value) + .["class"] = null + return + .["value"] = things[value] + + if(VV_DATUM_REFERENCE) + var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) + var/subtypes = vv_subtype_prompt(type) + if(subtypes == null) + .["class"] = null + return + var/list/things = vv_reference_list(type, subtypes) + var/value = input("Select reference:", "Reference", current_value) as null|anything in things + if(!value) + .["class"] = null + return + .["value"] = things[value] + + if(VV_MOB_REFERENCE) + var/type = pick_closest_path(FALSE, make_types_fancy(typesof(/mob))) + var/subtypes = vv_subtype_prompt(type) + if(subtypes == null) + .["class"] = null + return + var/list/things = vv_reference_list(type, subtypes) + var/value = input("Select reference:", "Reference", current_value) as null|anything in things + if(!value) + .["class"] = null + return + .["value"] = things[value] + + + + if(VV_CLIENT) + .["value"] = input("Select reference:", "Reference", current_value) as null|anything in GLOB.clients + if(.["value"] == null) + .["class"] = null + return + + + if(VV_FILE) + .["value"] = input("Pick file:", "File") as null|file + if(.["value"] == null) + .["class"] = null + return + + + if(VV_ICON) + .["value"] = input("Pick icon:", "Icon") as null|icon + if(.["value"] == null) + .["class"] = null + return + + + if(VV_MARKED_DATUM) + .["value"] = holder.marked_datum + if(.["value"] == null) + .["class"] = null + return + + if(VV_NEW_ATOM) + var/type = pick_closest_path(FALSE) + if(!type) + .["class"] = null + return + .["type"] = type + .["value"] = new type() + + if(VV_NEW_DATUM) + var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) + if(!type) + .["class"] = null + return + .["type"] = type + .["value"] = new type() + + if(VV_NEW_TYPE) + var/type = current_value + var/error = "" + do + type = input("Enter type:[error]", "Type", type) as null|text + if(!type) + break + type = text2path(type) + error = "\nType not found, Please try again" + while(!type) + if(!type) + .["class"] = null + return + .["type"] = type + .["value"] = new type() + + + if(VV_NEW_LIST) + .["value"] = list() + .["type"] = /list + +/client/proc/vv_parse_text(O, new_var) + if(O && findtext(new_var, "\[")) + var/process_vars = alert(usr, "\[] detected in string, process as variables?", "Process Variables?", "Yes", "No") + if(process_vars == "Yes") + . = string2listofvars(new_var, O) + +//do they want you to include subtypes? +//FALSE = no subtypes, strict exact type pathing (or the type doesn't have subtypes) +//TRUE = Yes subtypes +//NULL = User cancelled at the prompt or invalid type given +/client/proc/vv_subtype_prompt(var/type) + if(!ispath(type)) + return + var/list/subtypes = subtypesof(type) + if(!subtypes || !subtypes.len) + return FALSE + if(subtypes && subtypes.len) + switch(alert("Strict object type detection?", "Type detection", "Strictly this type","This type and subtypes", "Cancel")) + if("Strictly this type") + return FALSE + if("This type and subtypes") + return TRUE + else + return + +/client/proc/vv_reference_list(type, subtypes) + . = list() + var/list/types = list(type) + if(subtypes) + types = typesof(type) + + var/list/fancytypes = make_types_fancy(types) + + for(var/fancytype in fancytypes) //swap the assoication + types[fancytypes[fancytype]] = fancytype + + var/things = get_all_of_type(type, subtypes) + + var/i = 0 + for(var/thing in things) + var/datum/D = thing + i++ + //try one of 3 methods to shorten the type text: + // fancy type, + // fancy type with the base type removed from the begaining, + // the type with the base type removed from the begaining + var/fancytype = types[D.type] + if(findtext(fancytype, types[type])) + fancytype = copytext(fancytype, length(types[type])+1) + var/shorttype = copytext("[D.type]", length("[type]")+1) + if(length(shorttype) > length(fancytype)) + shorttype = fancytype + if(!length(shorttype)) + shorttype = "/" + + .["[D]([shorttype])\ref[D]#[i]"] = D + +/client/proc/mod_list_add_ass(atom/O) //haha + var/list/L = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT)) + var/class = L["class"] + if(!class) + return + var/var_value = L["value"] + + if(class == VV_TEXT || class == VV_MESSAGE) + var/list/varsvars = vv_parse_text(O, var_value) + for(var/V in varsvars) + var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]") + + return var_value + +/client/proc/mod_list_add(list/L, atom/O, original_name, objectvar) + var/list/LL = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT)) + var/class = LL["class"] + if(!class) + return + var/var_value = LL["value"] + + if(class == VV_TEXT || class == VV_MESSAGE) + var/list/varsvars = vv_parse_text(O, var_value) + for(var/V in varsvars) + var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]") + + if(O) + L = L.Copy() + + L += var_value + + switch(alert("Would you like to associate a value with the list entry?",,"Yes","No")) + if("Yes") + L[var_value] = mod_list_add_ass(O) //hehe + if(O) + if(!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: ADDED=[var_value]") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]") + message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]") + +/client/proc/mod_list(list/L, atom/O, original_name, objectvar, index, autodetect_class = FALSE) + if(!check_rights(R_VAREDIT)) + return + if(!istype(L, /list)) + to_chat(src, "Not a List.") + return + + if(L.len > 1000) + var/confirm = alert(src, "The list you're trying to edit is very long, continuing may crash the server.", "Warning", "Continue", "Abort") + if(confirm != "Continue") + return + + + + var/list/names = list() + for(var/i in 1 to L.len) + var/key = L[i] + var/value + if(IS_NORMAL_LIST(L) && !isnum(key)) + value = L[key] + if(value == null) + value = "null" + names["#[i] [key] = [value]"] = i + if(!index) + var/variable = input("Which var?","Var") as null|anything in names + "(ADD VAR)" + "(CLEAR NULLS)" + "(CLEAR DUPES)" + "(SHUFFLE)" + + if(variable == null) + return + + if(variable == "(ADD VAR)") + mod_list_add(L, O, original_name, objectvar) + return + + if(variable == "(CLEAR NULLS)") + L = L.Copy() + listclearnulls(L) + if(!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR NULLS") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR NULLS") + message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR NULLS") + return + + if(variable == "(CLEAR DUPES)") + L = uniqueList(L) + if(!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR DUPES") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR DUPES") + message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR DUPES") + return + + if(variable == "(SHUFFLE)") + L = shuffle(L) + if(!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [O.type] [objectvar]: SHUFFLE") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: SHUFFLE") + message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: SHUFFLE") + return + + index = names[variable] + + + var/assoc_key + if(index == null) + return + var/assoc = 0 + var/prompt = alert(src, "Do you want to edit the key or it's assigned value?", "Associated List", "Key", "Assigned Value", "Cancel") + if(prompt == "Cancel") + return + if(prompt == "Assigned Value") + assoc = 1 + assoc_key = L[index] + var/default + var/variable + if(assoc) + variable = L[assoc_key] + else + variable = L[index] + + default = vv_get_class(variable) + + to_chat(src, "Variable appears to be [uppertext(default)].") + + to_chat(src, "Variable contains: [L[index]]") + + if(default == VV_NUM) + var/dir_text = "" + if(dir < 0 && dir < 16) + if(dir & 1) + dir_text += "NORTH" + if(dir & 2) + dir_text += "SOUTH" + if(dir & 4) + dir_text += "EAST" + if(dir & 8) + dir_text += "WEST" + + if(dir_text) + to_chat(src, "If a direction, direction is: [dir_text]") + + var/original_var + if(assoc) + original_var = L[assoc_key] + else + original_var = L[index] + if(O) + L = L.Copy() + var/class + if(autodetect_class) + if(default == VV_TEXT) + default = VV_MESSAGE + class = default + var/list/LL = vv_get_value(default_class = default, current_value = original_var, restricted_classes = list(VV_RESTORE_DEFAULT), extra_classes = list(VV_LIST, "DELETE FROM LIST")) + class = LL["class"] + if(!class) + return + var/new_var = LL["value"] + + if(class == VV_MESSAGE) + class = VV_TEXT + + switch(class) //Spits a runtime error if you try to modify an entry in the contents list. Dunno how to fix it, yet. + if(VV_LIST) + mod_list(variable, O, original_name, objectvar) + + if("DELETE FROM LIST") + L.Cut(index, index+1) + if(O) + if(!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [O.type] [objectvar]: REMOVED=[html_encode("[original_var]")]") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]") + message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]") + return + + if(VV_TEXT) + var/list/varsvars = vv_parse_text(O, new_var) + for(var/V in varsvars) + new_var = replacetext(new_var,"\[[V]]","[O.vars[V]]") + + + if(assoc) + L[assoc_key] = new_var + else + L[index] = new_var + if(O) + if(!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: [original_var]=[new_var]") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: [original_var]=[new_var]") + message_admins("[key_name_admin(src)] modified [original_name]'s varlist [objectvar]: [original_var]=[new_var]") + +/proc/vv_varname_lockcheck(param_var_name) + if(param_var_name in VVlocked) + if(!check_rights(R_DEBUG)) + return FALSE + if(param_var_name in VVckey_edit) + if(!check_rights(R_EVENT | R_DEBUG)) + return FALSE + if(param_var_name in VVicon_edit_lock) + if(!check_rights(R_EVENT | R_DEBUG)) + return FALSE + if(param_var_name in VVpixelmovement) + if(!check_rights(R_DEBUG)) + return FALSE + var/prompt = alert(usr, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") + if(prompt != "Continue") + return FALSE + return TRUE + +/client/proc/modify_variables(atom/O, param_var_name = null, autodetect_class = 0) + if(!check_rights(R_VAREDIT)) + return + + var/class + var/variable + var/var_value + + if(param_var_name) + if(!param_var_name in O.vars) + to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])") + return + variable = param_var_name + + else + var/list/names = list() + for(var/V in O.vars) + names += V + + names = sortList(names) + + variable = input("Which var?","Var") as null|anything in names + if(!variable) + return + + if(!O.can_vv_get(variable)) + return + + if(!vv_varname_lockcheck(variable)) + return + + var_value = O.vars[variable] + + var/default = vv_get_class(var_value) + + if(isnull(default)) + to_chat(src, "Unable to determine variable type.") + else + to_chat(src, "Variable appears to be [uppertext(default)].") + + to_chat(src, "Variable contains: [var_value]") + + if(default == VV_NUM) + var/dir_text = "" + if(dir < 0 && dir < 16) + if(dir & 1) + dir_text += "NORTH" + if(dir & 2) + dir_text += "SOUTH" + if(dir & 4) + dir_text += "EAST" + if(dir & 8) + dir_text += "WEST" + + if(dir_text) + to_chat(src, "If a direction, direction is: [dir_text]") + + if(autodetect_class && default != VV_NULL) + if(default == VV_TEXT) + default = VV_MESSAGE + class = default + + var/list/value = vv_get_value(class, default, var_value, extra_classes = list(VV_LIST)) + class = value["class"] + + if(!class) + return + var/var_new = value["value"] + + if(class == VV_MESSAGE) + class = VV_TEXT + + var/original_name = "[O]" + + switch(class) + if(VV_LIST) + if(!islist(var_value)) + mod_list(list(), O, original_name, variable) + + mod_list(var_value, O, original_name, variable) + return + + if(VV_RESTORE_DEFAULT) + var_new = initial(O.vars[variable]) + + if(VV_TEXT) + var/list/varsvars = vv_parse_text(O, var_new) + for(var/V in varsvars) + var_new = replacetext(var_new,"\[[V]]","[O.vars[V]]") + + if(!O.vv_edit_var(variable, var_new)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### VarEdit by [src]: [O.type] [variable]=[html_encode("[var_new]")]") + log_admin("[key_name(src)] modified [original_name]'s [variable] to [var_new]") + var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] to [var_new]" + message_admins(msg) diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm index a2a95db957a..f1a8d9990c5 100644 --- a/code/modules/admin/verbs/one_click_antag.dm +++ b/code/modules/admin/verbs/one_click_antag.dm @@ -1,632 +1,632 @@ -client/proc/one_click_antag() - set name = "Create Antagonist" - set desc = "Auto-create an antagonist of your choice" - set category = "Event" - - if(!check_rights(R_SERVER|R_EVENT)) return - - if(holder) - holder.one_click_antag() - return - - -/datum/admins/proc/one_click_antag() - - var/dat = {"One-click Antagonist
        - Make Traitors
        - Make Changelings
        - Make Revolutionaries
        - Make Cult
        - Make Wizard (Requires Ghosts)
        - Make Vampires
        - Make Vox Raiders (Requires Ghosts)
        - Make Abductor Team (Requires Ghosts)
        - "} - usr << browse(dat, "window=oneclickantag;size=400x400") - return - -/datum/admins/proc/CandCheck(var/role = null, var/mob/living/carbon/human/M, var/datum/game_mode/temp = null) - // You pass in ROLE define (optional), the applicant, and the gamemode, and it will return true / false depending on whether the applicant qualify for the candidacy in question - if(jobban_isbanned(M, "Syndicate")) - return FALSE - if(M.stat || !M.mind || M.mind.special_role || M.mind.offstation_role) - return FALSE - if(temp) - if((M.mind.assigned_role in temp.restricted_jobs) || (M.client.prefs.species in temp.protected_species)) - return FALSE - if(role) // Don't even bother evaluating if there's no role - if(player_old_enough_antag(M.client,role) && (role in M.client.prefs.be_special) && !M.client.skip_antag && (!jobban_isbanned(M, role))) - return TRUE - else - return FALSE - else - return TRUE - -/datum/admins/proc/makeTraitors() - var/datum/game_mode/traitor/temp = new - - if(config.protect_roles_from_antagonist) - temp.restricted_jobs += temp.protected_jobs - - var/list/mob/living/carbon/human/candidates = list() - var/mob/living/carbon/human/H = null - - var/antnum = input(owner, "How many traitors you want to create? Enter 0 to cancel","Amount:", 0) as num - if(!antnum || antnum <= 0) - return - log_admin("[key_name(owner)] tried making [antnum] traitors with One-Click-Antag") - message_admins("[key_name_admin(owner)] tried making [antnum] traitors with One-Click-Antag") - - for(var/mob/living/carbon/human/applicant in GLOB.player_list) - if(CandCheck(ROLE_TRAITOR, applicant, temp)) - candidates += applicant - - if(candidates.len) - var/numTraitors = min(candidates.len, antnum) - - for(var/i = 0, i300)//If more than 30 game seconds passed. - return - candidates += G - if("No") - return - else - return - - sleep(300) - - if(candidates.len) - var/agentcount = 0 - - for(var/i = 0, i300)//If more than 30 game seconds passed. - return - candidates += G - if("No") - return - else - return - sleep(300) - - for(var/mob/dead/observer/G in candidates) - if(!G.key) - candidates.Remove(G) - - if(candidates.len) - //Spawns commandos and equips them. - for(var/obj/effect/landmark/L in /area/syndicate_mothership/elite_squad) - if(antnum <= 0) - break - if(L.name == "Syndicate-Commando") - syndicate_leader_selected = antnum == 1?1:0 - - var/mob/living/carbon/human/new_syndicate_commando = create_syndicate_death_commando(L, syndicate_leader_selected) - - while((!theghost || !theghost.client) && candidates.len) - theghost = pick(candidates) - candidates.Remove(theghost) - - if(!theghost) - qdel(new_syndicate_commando) - break - - new_syndicate_commando.key = theghost.key - new_syndicate_commando.internal = new_syndicate_commando.s_store - new_syndicate_commando.update_action_buttons_icon() - - //So they don't forget their code or mission. - - - to_chat(new_syndicate_commando, "You are an Elite Syndicate. [!syndicate_leader_selected ? "commando" : "LEADER"] in the service of the Syndicate. \nYour current mission is: [input]") - - antnum-- - - for(var/obj/effect/landmark/L in /area/shuttle/syndicate_elite) - if(L.name == "Syndicate-Commando-Bomb") - new /obj/effect/spawner/newbomb/timer/syndicate(L.loc) - return 1 - - -/proc/makeBody(var/mob/dead/observer/G_found) // Uses stripped down and bastardized code from respawn character - if(!G_found || !G_found.key) return - - //First we spawn a dude. - var/mob/living/carbon/human/new_character = new(pick(latejoin))//The mob being spawned. - - var/datum/preferences/A = new(G_found.client) - A.copy_to(new_character) - - new_character.dna.ready_dna(new_character) - new_character.key = G_found.key - - return new_character - -/datum/admins/proc/create_syndicate_death_commando(obj/spawn_location, syndicate_leader_selected = 0) - var/mob/living/carbon/human/new_syndicate_commando = new(spawn_location.loc) - var/syndicate_commando_leader_rank = pick("Lieutenant", "Captain", "Major") - var/syndicate_commando_rank = pick("Corporal", "Sergeant", "Staff Sergeant", "Sergeant 1st Class", "Master Sergeant", "Sergeant Major") - var/syndicate_commando_name = pick(GLOB.last_names) - - var/datum/preferences/A = new()//Randomize appearance for the commando. - if(syndicate_leader_selected) - A.real_name = "[syndicate_commando_leader_rank] [syndicate_commando_name]" - A.age = rand(35,45) - else - A.real_name = "[syndicate_commando_rank] [syndicate_commando_name]" - A.copy_to(new_syndicate_commando) - - new_syndicate_commando.dna.ready_dna(new_syndicate_commando)//Creates DNA. - - //Creates mind stuff. - new_syndicate_commando.mind_initialize() - new_syndicate_commando.mind.assigned_role = SPECIAL_ROLE_SYNDICATE_DEATHSQUAD - new_syndicate_commando.mind.special_role = SPECIAL_ROLE_SYNDICATE_DEATHSQUAD - new_syndicate_commando.mind.offstation_role = TRUE - //Adds them to current traitor list. Which is really the extra antagonist list. - SSticker.mode.traitors += new_syndicate_commando.mind - new_syndicate_commando.equip_syndicate_commando(syndicate_leader_selected) - - return new_syndicate_commando - -/datum/admins/proc/makeVoxRaiders() - - var/list/mob/candidates = list() - var/mob/theghost = null - var/time_passed = world.time - var/input = "Disregard shinies, acquire hardware." - - var/leader_chosen = 0 //when the leader is chosen. The last person spawned. - - var/antnum = input(owner, "How many raiders you want to create? Enter 0 to cancel.","Amount:", 0) as num - if(!antnum || antnum <= 0) - return - log_admin("[key_name(owner)] tried making Vox Raiders with One-Click-Antag") - message_admins("[key_name_admin(owner)] tried making Vox Raiders with One-Click-Antag") -//Generates a list of candidates from active ghosts. - for(var/mob/G in GLOB.respawnable_list) - if(istype(G) && G.client && (ROLE_RAIDER in G.client.prefs.be_special)) - if(player_old_enough_antag(G.client,ROLE_RAIDER)) - if(!jobban_isbanned(G, "raider") && !jobban_isbanned(G, "Syndicate")) - spawn(0) - switch(alert(G,"Do you wish to be considered for a vox raiding party arriving on the station?","Please answer in 30 seconds!","Yes","No")) - if("Yes") - if((world.time-time_passed)>300)//If more than 30 game seconds passed. - return - candidates += G - if("No") - return - else - return - - sleep(300) //Debug. - - for(var/mob/dead/observer/G in candidates) - if(!G.key) - candidates.Remove(G) - - if(candidates.len) - var/raiders = min(antnum, candidates.len) - //Spawns vox raiders and equips them. - for(var/obj/effect/landmark/L in world) - if(L.name == "voxstart") - if(raiders<=0) - break - - var/mob/living/carbon/human/new_vox = create_vox_raider(L, leader_chosen) - - while((!theghost || !theghost.client) && candidates.len) - theghost = pick(candidates) - candidates.Remove(theghost) - - if(!theghost) - qdel(new_vox) - break - - new_vox.key = theghost.key - SSticker.mode.traitors += new_vox.mind - - to_chat(new_vox, "You are a Vox Primalis, fresh out of the Shoal. Your ship has arrived at the Tau Ceti system hosting the NSV Exodus... or was it the Luna? NSS? Utopia? Nobody is really sure, but everyong is raring to start pillaging! Your current goal is: [input]") - to_chat(new_vox, "Don't forget to turn on your nitrogen internals!") - - raiders-- - else - return 0 - return 1 - -/datum/admins/proc/create_vox_raider(obj/spawn_location, leader_chosen = 0) - - var/sounds = rand(2,8) - var/i = 0 - var/newname = "" - - while(i<=sounds) - i++ - newname += pick(list("ti","hi","ki","ya","ta","ha","ka","ya","chi","cha","kah")) - - var/mob/living/carbon/human/new_vox = new /mob/living/carbon/human/vox(spawn_location.loc) - - new_vox.add_language("Tradeband") - new_vox.real_name = capitalize(newname) - new_vox.dna.real_name = new_vox.real_name - new_vox.name = new_vox.real_name - new_vox.age = rand(12,20) - new_vox.flavor_text = "" - new_vox.change_eye_color(rand(1, 255), rand(1, 255), rand(1, 255)) - new_vox.s_tone = rand(1, 6) - - // Do the initial caching of the player's body icons. - new_vox.force_update_limbs() - new_vox.update_dna() - new_vox.update_eyes() - - for(var/obj/item/organ/external/limb in new_vox.bodyparts) - limb.status &= ~ORGAN_ROBOT - - //Now apply cortical stack. - var/obj/item/implant/cortical/I = new(new_vox) - I.implant(new_vox) - cortical_stacks += I - - new_vox.equip_vox_raider() - new_vox.regenerate_icons() - - return new_vox - -/datum/admins/proc/makeVampires() - - var/datum/game_mode/vampire/temp = new - if(config.protect_roles_from_antagonist) - temp.restricted_jobs += temp.protected_jobs - - var/list/mob/living/carbon/human/candidates = list() - var/mob/living/carbon/human/H = null - - var/antnum = input(owner, "How many vampires you want to create? Enter 0 to cancel","Amount:", 0) as num - if(!antnum || antnum <= 0) - return - - log_admin("[key_name(owner)] tried making Vampires with One-Click-Antag") - message_admins("[key_name_admin(owner)] tried making Vampires with One-Click-Antag") - - for(var/mob/living/carbon/human/applicant in GLOB.player_list) - if(CandCheck(ROLE_VAMPIRE, applicant, temp)) - candidates += applicant - - if(candidates.len) - var/numVampires = min(candidates.len, antnum) - - for(var/i = 0, i300)//If more than 30 game seconds passed. - return - candidates += G - if("No") - return - else - return - - sleep(300) //Debug. - - for(var/mob/dead/observer/G in candidates) - if(!G.key) - candidates.Remove(G) - - if(candidates.len) - var/teamOneMembers = 5 - var/teamTwoMembers = 5 - var/datum/preferences/A = new() - for(var/obj/effect/landmark/L in world) - if(L.name == "tdome1") - if(teamOneMembers<=0) - break - - var/mob/living/carbon/human/newMember = new(L.loc) - - A.copy_to(newMember) - - newMember.dna.ready_dna(newMember) - - while((!theghost || !theghost.client) && candidates.len) - theghost = pick(candidates) - candidates.Remove(theghost) - - if(!theghost) - qdel(newMember) - break - - newMember.key = theghost.key - teamOneMembers-- - to_chat(newMember, "You are a member of the GREEN Thunderdome team! Gear up and help your team destroy the red team!") - - if(L.name == "tdome2") - if(teamTwoMembers<=0) - break - - var/mob/living/carbon/human/newMember = new(L.loc) - - A.copy_to(newMember) - - newMember.dna.ready_dna(newMember) - - while((!theghost || !theghost.client) && candidates.len) - theghost = pick(candidates) - candidates.Remove(theghost) - - if(!theghost) - qdel(newMember) - break - - newMember.key = theghost.key - teamTwoMembers-- - to_chat(newMember, "You are a member of the RED Thunderdome team! Gear up and help your team destroy the green team!") - else - return 0 - return 1 +client/proc/one_click_antag() + set name = "Create Antagonist" + set desc = "Auto-create an antagonist of your choice" + set category = "Event" + + if(!check_rights(R_SERVER|R_EVENT)) return + + if(holder) + holder.one_click_antag() + return + + +/datum/admins/proc/one_click_antag() + + var/dat = {"One-click Antagonist
        + Make Traitors
        + Make Changelings
        + Make Revolutionaries
        + Make Cult
        + Make Wizard (Requires Ghosts)
        + Make Vampires
        + Make Vox Raiders (Requires Ghosts)
        + Make Abductor Team (Requires Ghosts)
        + "} + usr << browse(dat, "window=oneclickantag;size=400x400") + return + +/datum/admins/proc/CandCheck(var/role = null, var/mob/living/carbon/human/M, var/datum/game_mode/temp = null) + // You pass in ROLE define (optional), the applicant, and the gamemode, and it will return true / false depending on whether the applicant qualify for the candidacy in question + if(jobban_isbanned(M, "Syndicate")) + return FALSE + if(M.stat || !M.mind || M.mind.special_role || M.mind.offstation_role) + return FALSE + if(temp) + if((M.mind.assigned_role in temp.restricted_jobs) || (M.client.prefs.species in temp.protected_species)) + return FALSE + if(role) // Don't even bother evaluating if there's no role + if(player_old_enough_antag(M.client,role) && (role in M.client.prefs.be_special) && !M.client.skip_antag && (!jobban_isbanned(M, role))) + return TRUE + else + return FALSE + else + return TRUE + +/datum/admins/proc/makeTraitors() + var/datum/game_mode/traitor/temp = new + + if(config.protect_roles_from_antagonist) + temp.restricted_jobs += temp.protected_jobs + + var/list/mob/living/carbon/human/candidates = list() + var/mob/living/carbon/human/H = null + + var/antnum = input(owner, "How many traitors you want to create? Enter 0 to cancel","Amount:", 0) as num + if(!antnum || antnum <= 0) + return + log_admin("[key_name(owner)] tried making [antnum] traitors with One-Click-Antag") + message_admins("[key_name_admin(owner)] tried making [antnum] traitors with One-Click-Antag") + + for(var/mob/living/carbon/human/applicant in GLOB.player_list) + if(CandCheck(ROLE_TRAITOR, applicant, temp)) + candidates += applicant + + if(candidates.len) + var/numTraitors = min(candidates.len, antnum) + + for(var/i = 0, i300)//If more than 30 game seconds passed. + return + candidates += G + if("No") + return + else + return + + sleep(300) + + if(candidates.len) + var/agentcount = 0 + + for(var/i = 0, i300)//If more than 30 game seconds passed. + return + candidates += G + if("No") + return + else + return + sleep(300) + + for(var/mob/dead/observer/G in candidates) + if(!G.key) + candidates.Remove(G) + + if(candidates.len) + //Spawns commandos and equips them. + for(var/obj/effect/landmark/L in /area/syndicate_mothership/elite_squad) + if(antnum <= 0) + break + if(L.name == "Syndicate-Commando") + syndicate_leader_selected = antnum == 1?1:0 + + var/mob/living/carbon/human/new_syndicate_commando = create_syndicate_death_commando(L, syndicate_leader_selected) + + while((!theghost || !theghost.client) && candidates.len) + theghost = pick(candidates) + candidates.Remove(theghost) + + if(!theghost) + qdel(new_syndicate_commando) + break + + new_syndicate_commando.key = theghost.key + new_syndicate_commando.internal = new_syndicate_commando.s_store + new_syndicate_commando.update_action_buttons_icon() + + //So they don't forget their code or mission. + + + to_chat(new_syndicate_commando, "You are an Elite Syndicate. [!syndicate_leader_selected ? "commando" : "LEADER"] in the service of the Syndicate. \nYour current mission is: [input]") + + antnum-- + + for(var/obj/effect/landmark/L in /area/shuttle/syndicate_elite) + if(L.name == "Syndicate-Commando-Bomb") + new /obj/effect/spawner/newbomb/timer/syndicate(L.loc) + return 1 + + +/proc/makeBody(var/mob/dead/observer/G_found) // Uses stripped down and bastardized code from respawn character + if(!G_found || !G_found.key) return + + //First we spawn a dude. + var/mob/living/carbon/human/new_character = new(pick(latejoin))//The mob being spawned. + + var/datum/preferences/A = new(G_found.client) + A.copy_to(new_character) + + new_character.dna.ready_dna(new_character) + new_character.key = G_found.key + + return new_character + +/datum/admins/proc/create_syndicate_death_commando(obj/spawn_location, syndicate_leader_selected = 0) + var/mob/living/carbon/human/new_syndicate_commando = new(spawn_location.loc) + var/syndicate_commando_leader_rank = pick("Lieutenant", "Captain", "Major") + var/syndicate_commando_rank = pick("Corporal", "Sergeant", "Staff Sergeant", "Sergeant 1st Class", "Master Sergeant", "Sergeant Major") + var/syndicate_commando_name = pick(GLOB.last_names) + + var/datum/preferences/A = new()//Randomize appearance for the commando. + if(syndicate_leader_selected) + A.real_name = "[syndicate_commando_leader_rank] [syndicate_commando_name]" + A.age = rand(35,45) + else + A.real_name = "[syndicate_commando_rank] [syndicate_commando_name]" + A.copy_to(new_syndicate_commando) + + new_syndicate_commando.dna.ready_dna(new_syndicate_commando)//Creates DNA. + + //Creates mind stuff. + new_syndicate_commando.mind_initialize() + new_syndicate_commando.mind.assigned_role = SPECIAL_ROLE_SYNDICATE_DEATHSQUAD + new_syndicate_commando.mind.special_role = SPECIAL_ROLE_SYNDICATE_DEATHSQUAD + new_syndicate_commando.mind.offstation_role = TRUE + //Adds them to current traitor list. Which is really the extra antagonist list. + SSticker.mode.traitors += new_syndicate_commando.mind + new_syndicate_commando.equip_syndicate_commando(syndicate_leader_selected) + + return new_syndicate_commando + +/datum/admins/proc/makeVoxRaiders() + + var/list/mob/candidates = list() + var/mob/theghost = null + var/time_passed = world.time + var/input = "Disregard shinies, acquire hardware." + + var/leader_chosen = 0 //when the leader is chosen. The last person spawned. + + var/antnum = input(owner, "How many raiders you want to create? Enter 0 to cancel.","Amount:", 0) as num + if(!antnum || antnum <= 0) + return + log_admin("[key_name(owner)] tried making Vox Raiders with One-Click-Antag") + message_admins("[key_name_admin(owner)] tried making Vox Raiders with One-Click-Antag") +//Generates a list of candidates from active ghosts. + for(var/mob/G in GLOB.respawnable_list) + if(istype(G) && G.client && (ROLE_RAIDER in G.client.prefs.be_special)) + if(player_old_enough_antag(G.client,ROLE_RAIDER)) + if(!jobban_isbanned(G, "raider") && !jobban_isbanned(G, "Syndicate")) + spawn(0) + switch(alert(G,"Do you wish to be considered for a vox raiding party arriving on the station?","Please answer in 30 seconds!","Yes","No")) + if("Yes") + if((world.time-time_passed)>300)//If more than 30 game seconds passed. + return + candidates += G + if("No") + return + else + return + + sleep(300) //Debug. + + for(var/mob/dead/observer/G in candidates) + if(!G.key) + candidates.Remove(G) + + if(candidates.len) + var/raiders = min(antnum, candidates.len) + //Spawns vox raiders and equips them. + for(var/obj/effect/landmark/L in world) + if(L.name == "voxstart") + if(raiders<=0) + break + + var/mob/living/carbon/human/new_vox = create_vox_raider(L, leader_chosen) + + while((!theghost || !theghost.client) && candidates.len) + theghost = pick(candidates) + candidates.Remove(theghost) + + if(!theghost) + qdel(new_vox) + break + + new_vox.key = theghost.key + SSticker.mode.traitors += new_vox.mind + + to_chat(new_vox, "You are a Vox Primalis, fresh out of the Shoal. Your ship has arrived at the Tau Ceti system hosting the NSV Exodus... or was it the Luna? NSS? Utopia? Nobody is really sure, but everyong is raring to start pillaging! Your current goal is: [input]") + to_chat(new_vox, "Don't forget to turn on your nitrogen internals!") + + raiders-- + else + return 0 + return 1 + +/datum/admins/proc/create_vox_raider(obj/spawn_location, leader_chosen = 0) + + var/sounds = rand(2,8) + var/i = 0 + var/newname = "" + + while(i<=sounds) + i++ + newname += pick(list("ti","hi","ki","ya","ta","ha","ka","ya","chi","cha","kah")) + + var/mob/living/carbon/human/new_vox = new /mob/living/carbon/human/vox(spawn_location.loc) + + new_vox.add_language("Tradeband") + new_vox.real_name = capitalize(newname) + new_vox.dna.real_name = new_vox.real_name + new_vox.name = new_vox.real_name + new_vox.age = rand(12,20) + new_vox.flavor_text = "" + new_vox.change_eye_color(rand(1, 255), rand(1, 255), rand(1, 255)) + new_vox.s_tone = rand(1, 6) + + // Do the initial caching of the player's body icons. + new_vox.force_update_limbs() + new_vox.update_dna() + new_vox.update_eyes() + + for(var/obj/item/organ/external/limb in new_vox.bodyparts) + limb.status &= ~ORGAN_ROBOT + + //Now apply cortical stack. + var/obj/item/implant/cortical/I = new(new_vox) + I.implant(new_vox) + cortical_stacks += I + + new_vox.equip_vox_raider() + new_vox.regenerate_icons() + + return new_vox + +/datum/admins/proc/makeVampires() + + var/datum/game_mode/vampire/temp = new + if(config.protect_roles_from_antagonist) + temp.restricted_jobs += temp.protected_jobs + + var/list/mob/living/carbon/human/candidates = list() + var/mob/living/carbon/human/H = null + + var/antnum = input(owner, "How many vampires you want to create? Enter 0 to cancel","Amount:", 0) as num + if(!antnum || antnum <= 0) + return + + log_admin("[key_name(owner)] tried making Vampires with One-Click-Antag") + message_admins("[key_name_admin(owner)] tried making Vampires with One-Click-Antag") + + for(var/mob/living/carbon/human/applicant in GLOB.player_list) + if(CandCheck(ROLE_VAMPIRE, applicant, temp)) + candidates += applicant + + if(candidates.len) + var/numVampires = min(candidates.len, antnum) + + for(var/i = 0, i300)//If more than 30 game seconds passed. + return + candidates += G + if("No") + return + else + return + + sleep(300) //Debug. + + for(var/mob/dead/observer/G in candidates) + if(!G.key) + candidates.Remove(G) + + if(candidates.len) + var/teamOneMembers = 5 + var/teamTwoMembers = 5 + var/datum/preferences/A = new() + for(var/obj/effect/landmark/L in world) + if(L.name == "tdome1") + if(teamOneMembers<=0) + break + + var/mob/living/carbon/human/newMember = new(L.loc) + + A.copy_to(newMember) + + newMember.dna.ready_dna(newMember) + + while((!theghost || !theghost.client) && candidates.len) + theghost = pick(candidates) + candidates.Remove(theghost) + + if(!theghost) + qdel(newMember) + break + + newMember.key = theghost.key + teamOneMembers-- + to_chat(newMember, "You are a member of the GREEN Thunderdome team! Gear up and help your team destroy the red team!") + + if(L.name == "tdome2") + if(teamTwoMembers<=0) + break + + var/mob/living/carbon/human/newMember = new(L.loc) + + A.copy_to(newMember) + + newMember.dna.ready_dna(newMember) + + while((!theghost || !theghost.client) && candidates.len) + theghost = pick(candidates) + candidates.Remove(theghost) + + if(!theghost) + qdel(newMember) + break + + newMember.key = theghost.key + teamTwoMembers-- + to_chat(newMember, "You are a member of the RED Thunderdome team! Gear up and help your team destroy the green team!") + else + return 0 + return 1 diff --git a/code/modules/admin/verbs/onlyone.dm b/code/modules/admin/verbs/onlyone.dm index 3db8f102320..c3299c212ad 100644 --- a/code/modules/admin/verbs/onlyone.dm +++ b/code/modules/admin/verbs/onlyone.dm @@ -1,104 +1,104 @@ -/client/proc/only_one() - if(!SSticker) - alert("The game hasn't started yet!") - return - - var/list/incompatible_species = list(/datum/species/plasmaman, /datum/species/vox) - for(var/mob/living/carbon/human/H in GLOB.player_list) - if(H.stat == DEAD || !(H.client)) - continue - if(is_special_character(H)) - continue - if(is_type_in_list(H.dna.species, incompatible_species)) - H.set_species(/datum/species/human) - var/datum/preferences/A = new() // Randomize appearance - A.copy_to(H) - - SSticker.mode.traitors += H.mind - H.mind.special_role = SPECIAL_ROLE_TRAITOR - - var/datum/objective/hijack/hijack_objective = new - hijack_objective.owner = H.mind - H.mind.objectives += hijack_objective - - to_chat(H, "You are a Highlander. Kill all other Highlanders. There can be only one.") - var/obj_count = 1 - for(var/datum/objective/OBJ in H.mind.objectives) - to_chat(H, "Objective #[obj_count]: [OBJ.explanation_text]") - obj_count++ - - for(var/obj/item/I in H) - if(istype(I, /obj/item/implant)) - continue - if(istype(I, /obj/item/organ)) - continue - qdel(I) - - H.equip_to_slot_or_del(new /obj/item/clothing/under/kilt(H), slot_w_uniform) - H.equip_to_slot_or_del(new /obj/item/radio/headset/heads/captain(H), slot_l_ear) - H.equip_to_slot_or_del(new /obj/item/clothing/head/beret(H), slot_head) - H.equip_to_slot_or_del(new /obj/item/claymore/highlander(H), slot_r_hand) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(H), slot_shoes) - H.equip_to_slot_or_del(new /obj/item/pinpointer(H.loc), slot_l_store) - - var/obj/item/card/id/W = new(H) - W.name = "[H.real_name]'s ID Card" - W.icon_state = "centcom" - W.access = get_all_accesses() - W.access += get_all_centcom_access() - W.assignment = "Highlander" - W.registered_name = H.real_name - H.equip_to_slot_or_del(W, slot_wear_id) - H.dna.species.after_equip_job(null, H) - H.regenerate_icons() - - message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ONE! -NO ATTACK LOGS WILL BE SENT TO ADMINS FROM THIS POINT FORTH-", 1) - log_admin("[key_name(usr)] used there can be only one.") - nologevent = 1 - world << sound('sound/music/thunderdome.ogg') - -/client/proc/only_me() - if(!SSticker) - alert("The game hasn't started yet!") - return - - for(var/mob/living/carbon/human/H in GLOB.player_list) - if(H.stat == 2 || !(H.client)) continue - if(is_special_character(H)) continue - - SSticker.mode.traitors += H.mind - H.mind.special_role = "[H.real_name] Prime" - - var/datum/objective/hijackclone/hijack_objective = new /datum/objective/hijackclone - hijack_objective.owner = H.mind - H.mind.objectives += hijack_objective - - to_chat(H, "You are the multiverse summoner. Activate your blade to summon copies of yourself from another universe to fight by your side.") - var/obj_count = 1 - for(var/datum/objective/OBJ in H.mind.objectives) - to_chat(H, "Objective #[obj_count]: [OBJ.explanation_text]") - obj_count++ - - var/obj/item/slot_item_ID = H.get_item_by_slot(slot_wear_id) - qdel(slot_item_ID) - var/obj/item/slot_item_hand = H.get_item_by_slot(slot_r_hand) - H.unEquip(slot_item_hand) - - var /obj/item/multisword/pure_evil/multi = new(H) - H.equip_to_slot_or_del(multi, slot_r_hand) - - var/obj/item/card/id/W = new(H) - W.icon_state = "centcom" - W.access = get_all_accesses() - W.access += get_all_centcom_access() - W.assignment = "Multiverse Summoner" - W.registered_name = H.real_name - W.update_label(H.real_name) - H.equip_to_slot_or_del(W, slot_wear_id) - - H.update_icons() - - message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ME! -NO ATTACK LOGS WILL BE SENT TO ADMINS FROM THIS POINT FORTH-", 1) - log_admin("[key_name(usr)] used there can be only me.") - nologevent = 1 - world << sound('sound/music/thunderdome.ogg') +/client/proc/only_one() + if(!SSticker) + alert("The game hasn't started yet!") + return + + var/list/incompatible_species = list(/datum/species/plasmaman, /datum/species/vox) + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(H.stat == DEAD || !(H.client)) + continue + if(is_special_character(H)) + continue + if(is_type_in_list(H.dna.species, incompatible_species)) + H.set_species(/datum/species/human) + var/datum/preferences/A = new() // Randomize appearance + A.copy_to(H) + + SSticker.mode.traitors += H.mind + H.mind.special_role = SPECIAL_ROLE_TRAITOR + + var/datum/objective/hijack/hijack_objective = new + hijack_objective.owner = H.mind + H.mind.objectives += hijack_objective + + to_chat(H, "You are a Highlander. Kill all other Highlanders. There can be only one.") + var/obj_count = 1 + for(var/datum/objective/OBJ in H.mind.objectives) + to_chat(H, "Objective #[obj_count]: [OBJ.explanation_text]") + obj_count++ + + for(var/obj/item/I in H) + if(istype(I, /obj/item/implant)) + continue + if(istype(I, /obj/item/organ)) + continue + qdel(I) + + H.equip_to_slot_or_del(new /obj/item/clothing/under/kilt(H), slot_w_uniform) + H.equip_to_slot_or_del(new /obj/item/radio/headset/heads/captain(H), slot_l_ear) + H.equip_to_slot_or_del(new /obj/item/clothing/head/beret(H), slot_head) + H.equip_to_slot_or_del(new /obj/item/claymore/highlander(H), slot_r_hand) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(H), slot_shoes) + H.equip_to_slot_or_del(new /obj/item/pinpointer(H.loc), slot_l_store) + + var/obj/item/card/id/W = new(H) + W.name = "[H.real_name]'s ID Card" + W.icon_state = "centcom" + W.access = get_all_accesses() + W.access += get_all_centcom_access() + W.assignment = "Highlander" + W.registered_name = H.real_name + H.equip_to_slot_or_del(W, slot_wear_id) + H.dna.species.after_equip_job(null, H) + H.regenerate_icons() + + message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ONE! -NO ATTACK LOGS WILL BE SENT TO ADMINS FROM THIS POINT FORTH-", 1) + log_admin("[key_name(usr)] used there can be only one.") + nologevent = 1 + world << sound('sound/music/thunderdome.ogg') + +/client/proc/only_me() + if(!SSticker) + alert("The game hasn't started yet!") + return + + for(var/mob/living/carbon/human/H in GLOB.player_list) + if(H.stat == 2 || !(H.client)) continue + if(is_special_character(H)) continue + + SSticker.mode.traitors += H.mind + H.mind.special_role = "[H.real_name] Prime" + + var/datum/objective/hijackclone/hijack_objective = new /datum/objective/hijackclone + hijack_objective.owner = H.mind + H.mind.objectives += hijack_objective + + to_chat(H, "You are the multiverse summoner. Activate your blade to summon copies of yourself from another universe to fight by your side.") + var/obj_count = 1 + for(var/datum/objective/OBJ in H.mind.objectives) + to_chat(H, "Objective #[obj_count]: [OBJ.explanation_text]") + obj_count++ + + var/obj/item/slot_item_ID = H.get_item_by_slot(slot_wear_id) + qdel(slot_item_ID) + var/obj/item/slot_item_hand = H.get_item_by_slot(slot_r_hand) + H.unEquip(slot_item_hand) + + var /obj/item/multisword/pure_evil/multi = new(H) + H.equip_to_slot_or_del(multi, slot_r_hand) + + var/obj/item/card/id/W = new(H) + W.icon_state = "centcom" + W.access = get_all_accesses() + W.access += get_all_centcom_access() + W.assignment = "Multiverse Summoner" + W.registered_name = H.real_name + W.update_label(H.real_name) + H.equip_to_slot_or_del(W, slot_wear_id) + + H.update_icons() + + message_admins("[key_name_admin(usr)] used THERE CAN BE ONLY ME! -NO ATTACK LOGS WILL BE SENT TO ADMINS FROM THIS POINT FORTH-", 1) + log_admin("[key_name(usr)] used there can be only me.") + nologevent = 1 + world << sound('sound/music/thunderdome.ogg') diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index 995673e7d20..4717ba67f65 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -1,153 +1,153 @@ -var/list/sounds_cache = list() - -/client/proc/stop_global_admin_sounds() - set category = "Event" - set name = "Stop Global Admin Sounds" - if(!check_rights(R_SOUNDS)) - return - - var/sound/awful_sound = sound(null, repeat = 0, wait = 0, channel = CHANNEL_ADMIN) - - log_admin("[key_name(src)] stopped admin sounds.") - message_admins("[key_name_admin(src)] stopped admin sounds.", 1) - for(var/mob/M in GLOB.player_list) - M << awful_sound - -/client/proc/play_sound(S as sound) - set category = "Event" - set name = "Play Global Sound" - if(!check_rights(R_SOUNDS)) return - - var/sound/uploaded_sound = sound(S, repeat = 0, wait = 1, channel = CHANNEL_ADMIN) - uploaded_sound.priority = 250 - - sounds_cache += S - - if(alert("Are you sure?\nSong: [S]\nNow you can also play this sound using \"Play Server Sound\".", "Confirmation request" ,"Play", "Cancel") == "Cancel") - return - - log_admin("[key_name(src)] played sound [S]") - message_admins("[key_name_admin(src)] played sound [S]", 1) - - for(var/mob/M in GLOB.player_list) - if(M.client.prefs.sound & SOUND_MIDI) - if(isnewplayer(M) && (M.client.prefs.sound & SOUND_LOBBY)) - M.stop_sound_channel(CHANNEL_LOBBYMUSIC) - SEND_SOUND(M, uploaded_sound) - - feedback_add_details("admin_verb","PGS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/play_local_sound(S as sound) - set category = "Event" - set name = "Play Local Sound" - if(!check_rights(R_SOUNDS)) return - - log_admin("[key_name(src)] played a local sound [S]") - message_admins("[key_name_admin(src)] played a local sound [S]", 1) - playsound(get_turf(src.mob), S, 50, 0, 0) - feedback_add_details("admin_verb","PLS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/play_server_sound() - set category = "Event" - set name = "Play Server Sound" - if(!check_rights(R_SOUNDS)) return - - var/list/sounds = file2list("sound/serversound_list.txt"); - sounds += sounds_cache - - var/melody = input("Select a sound from the server to play", "Server sound list") as null|anything in sounds - if(!melody) return - - play_sound(melody) - feedback_add_details("admin_verb","PSS") //If you are copy-pasting this, ensure the 2nd paramter is unique to the new proc! - -/client/proc/play_intercomm_sound() - set category = "Event" - set name = "Play Sound via Intercomms" - set desc = "Plays a sound at every intercomm on the station z level. Works best with small sounds." - if(!check_rights(R_SOUNDS)) return - - var/A = alert("This will play a sound at every intercomm, are you sure you want to continue? This works best with short sounds, beware.","Warning","Yep","Nope") - if(A != "Yep") return - - var/list/sounds = file2list("sound/serversound_list.txt"); - sounds += sounds_cache - - var/melody = input("Select a sound from the server to play", "Server sound list") as null|anything in sounds - if(!melody) return - - var/cvol = 35 - var/inputvol = input("How loud would you like this to be? (1-70)", "Volume", "35") as num | null - if(!inputvol) return - if(inputvol && inputvol >= 1 && inputvol <= 70) - cvol = inputvol - - //Allows for override to utilize intercomms on all z-levels - var/B = alert("Do you want to play through intercomms on ALL Z-levels, or just the station?", "Override", "All", "Station") - var/ignore_z = 0 - if(B == "All") - ignore_z = 1 - - //Allows for override to utilize incomplete and unpowered intercomms - var/C = alert("Do you want to play through unpowered / incomplete intercomms, so the crew can't silence it?", "Override", "Yep", "Nope") - var/ignore_power = 0 - if(C == "Yep") - ignore_power = 1 - - for(var/O in GLOB.global_intercoms) - var/obj/item/radio/intercom/I = O - if(!is_station_level(I.z) && !ignore_z) - continue - if(!I.on && !ignore_power) - continue - playsound(I, melody, cvol) - -/* -/client/proc/cuban_pete() - set category = "Event" - set name = "Cuban Pete Time" - - message_admins("[key_name_admin(usr)] has declared Cuban Pete Time!", 1) - for(var/mob/M in world) - if(M.client) - if(M.client.midis) - M << 'cubanpetetime.ogg' - - for(var/mob/living/carbon/human/CP in world) - if(CP.real_name=="Cuban Pete" && CP.key!="Rosham") - C << "Your body can't contain the rhumba beat" - CP.gib() - - -/client/proc/bananaphone() - set category = "Event" - set name = "Banana Phone" - - message_admins("[key_name_admin(usr)] has activated Banana Phone!", 1) - for(var/mob/M in world) - if(M.client) - if(M.client.midis) - M << 'bananaphone.ogg' - - -client/proc/space_asshole() - set category = "Event" - set name = "Space Asshole" - - message_admins("[key_name_admin(usr)] has played the Space Asshole Hymn.", 1) - for(var/mob/M in world) - if(M.client) - if(M.client.midis) - M << 'sound/music/space_asshole.ogg' - - -client/proc/honk_theme() - set category = "Event" - set name = "Honk" - - message_admins("[key_name_admin(usr)] has creeped everyone out with Blackest Honks.", 1) - for(var/mob/M in world) - if(M.client) - if(M.client.midis) - M << 'honk_theme.ogg'*/ +var/list/sounds_cache = list() + +/client/proc/stop_global_admin_sounds() + set category = "Event" + set name = "Stop Global Admin Sounds" + if(!check_rights(R_SOUNDS)) + return + + var/sound/awful_sound = sound(null, repeat = 0, wait = 0, channel = CHANNEL_ADMIN) + + log_admin("[key_name(src)] stopped admin sounds.") + message_admins("[key_name_admin(src)] stopped admin sounds.", 1) + for(var/mob/M in GLOB.player_list) + M << awful_sound + +/client/proc/play_sound(S as sound) + set category = "Event" + set name = "Play Global Sound" + if(!check_rights(R_SOUNDS)) return + + var/sound/uploaded_sound = sound(S, repeat = 0, wait = 1, channel = CHANNEL_ADMIN) + uploaded_sound.priority = 250 + + sounds_cache += S + + if(alert("Are you sure?\nSong: [S]\nNow you can also play this sound using \"Play Server Sound\".", "Confirmation request" ,"Play", "Cancel") == "Cancel") + return + + log_admin("[key_name(src)] played sound [S]") + message_admins("[key_name_admin(src)] played sound [S]", 1) + + for(var/mob/M in GLOB.player_list) + if(M.client.prefs.sound & SOUND_MIDI) + if(isnewplayer(M) && (M.client.prefs.sound & SOUND_LOBBY)) + M.stop_sound_channel(CHANNEL_LOBBYMUSIC) + SEND_SOUND(M, uploaded_sound) + + feedback_add_details("admin_verb","PGS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/play_local_sound(S as sound) + set category = "Event" + set name = "Play Local Sound" + if(!check_rights(R_SOUNDS)) return + + log_admin("[key_name(src)] played a local sound [S]") + message_admins("[key_name_admin(src)] played a local sound [S]", 1) + playsound(get_turf(src.mob), S, 50, 0, 0) + feedback_add_details("admin_verb","PLS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/play_server_sound() + set category = "Event" + set name = "Play Server Sound" + if(!check_rights(R_SOUNDS)) return + + var/list/sounds = file2list("sound/serversound_list.txt"); + sounds += sounds_cache + + var/melody = input("Select a sound from the server to play", "Server sound list") as null|anything in sounds + if(!melody) return + + play_sound(melody) + feedback_add_details("admin_verb","PSS") //If you are copy-pasting this, ensure the 2nd paramter is unique to the new proc! + +/client/proc/play_intercomm_sound() + set category = "Event" + set name = "Play Sound via Intercomms" + set desc = "Plays a sound at every intercomm on the station z level. Works best with small sounds." + if(!check_rights(R_SOUNDS)) return + + var/A = alert("This will play a sound at every intercomm, are you sure you want to continue? This works best with short sounds, beware.","Warning","Yep","Nope") + if(A != "Yep") return + + var/list/sounds = file2list("sound/serversound_list.txt"); + sounds += sounds_cache + + var/melody = input("Select a sound from the server to play", "Server sound list") as null|anything in sounds + if(!melody) return + + var/cvol = 35 + var/inputvol = input("How loud would you like this to be? (1-70)", "Volume", "35") as num | null + if(!inputvol) return + if(inputvol && inputvol >= 1 && inputvol <= 70) + cvol = inputvol + + //Allows for override to utilize intercomms on all z-levels + var/B = alert("Do you want to play through intercomms on ALL Z-levels, or just the station?", "Override", "All", "Station") + var/ignore_z = 0 + if(B == "All") + ignore_z = 1 + + //Allows for override to utilize incomplete and unpowered intercomms + var/C = alert("Do you want to play through unpowered / incomplete intercomms, so the crew can't silence it?", "Override", "Yep", "Nope") + var/ignore_power = 0 + if(C == "Yep") + ignore_power = 1 + + for(var/O in GLOB.global_intercoms) + var/obj/item/radio/intercom/I = O + if(!is_station_level(I.z) && !ignore_z) + continue + if(!I.on && !ignore_power) + continue + playsound(I, melody, cvol) + +/* +/client/proc/cuban_pete() + set category = "Event" + set name = "Cuban Pete Time" + + message_admins("[key_name_admin(usr)] has declared Cuban Pete Time!", 1) + for(var/mob/M in world) + if(M.client) + if(M.client.midis) + M << 'cubanpetetime.ogg' + + for(var/mob/living/carbon/human/CP in world) + if(CP.real_name=="Cuban Pete" && CP.key!="Rosham") + C << "Your body can't contain the rhumba beat" + CP.gib() + + +/client/proc/bananaphone() + set category = "Event" + set name = "Banana Phone" + + message_admins("[key_name_admin(usr)] has activated Banana Phone!", 1) + for(var/mob/M in world) + if(M.client) + if(M.client.midis) + M << 'bananaphone.ogg' + + +client/proc/space_asshole() + set category = "Event" + set name = "Space Asshole" + + message_admins("[key_name_admin(usr)] has played the Space Asshole Hymn.", 1) + for(var/mob/M in world) + if(M.client) + if(M.client.midis) + M << 'sound/music/space_asshole.ogg' + + +client/proc/honk_theme() + set category = "Event" + set name = "Honk" + + message_admins("[key_name_admin(usr)] has creeped everyone out with Blackest Honks.", 1) + for(var/mob/M in world) + if(M.client) + if(M.client.midis) + M << 'honk_theme.ogg'*/ diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm index 9973de89c85..95a66684d1d 100644 --- a/code/modules/admin/verbs/pray.dm +++ b/code/modules/admin/verbs/pray.dm @@ -1,89 +1,89 @@ -/mob/living/verb/pray(msg as text) - set category = "IC" - set name = "Pray" - - msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) - if(!msg) - return - - if(usr.client) - if(usr.client.prefs.muted & MUTE_PRAY) - to_chat(usr, "You cannot pray (muted).") - return - if(client.handle_spam_prevention(msg, MUTE_PRAY, OOC_COOLDOWN)) - return - - var/image/cross = image('icons/obj/storage.dmi',"bible") - var/font_color = "purple" - var/prayer_type = "PRAYER" - var/deity - if(usr.job == "Chaplain") - if(SSticker && SSticker.Bible_deity_name) - deity = SSticker.Bible_deity_name - cross = image('icons/obj/storage.dmi',"kingyellow") - font_color = "blue" - prayer_type = "CHAPLAIN PRAYER" - else if(iscultist(usr)) - cross = image('icons/obj/storage.dmi',"tome") - font_color = "red" - prayer_type = "CULTIST PRAYER" - deity = SSticker.cultdat.entity_name - - log_say("(PRAYER) [msg]", usr) - msg = "[bicon(cross)][prayer_type][deity ? " (to [deity])" : ""][mind && mind.isholy ? " (blessings: [mind.num_blessed])" : ""]: [key_name(src, 1)] ([ADMIN_QUE(src,"?")]) ([ADMIN_PP(src,"PP")]) ([ADMIN_VV(src,"VV")]) ([ADMIN_TP(src,"TP")]) ([ADMIN_SM(src,"SM")]) ([admin_jump_link(src)]) ([ADMIN_SC(src,"SC")]) (BLESS) (SMITE): [msg]" - - for(var/client/X in GLOB.admins) - if(check_rights(R_EVENT,0,X.mob)) - to_chat(X, msg) - to_chat(usr, "Your prayers have been received by the gods.") - - feedback_add_details("admin_verb","PR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/proc/Centcomm_announce(var/text , var/mob/Sender) - var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) - msg = "CENTCOMM: [key_name(Sender, 1)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) ([ADMIN_CENTCOM_REPLY(Sender,"RPLY")])): [msg]" - for(var/client/X in GLOB.admins) - if(R_EVENT & X.holder.rights) - to_chat(X, msg) - if(X.prefs.sound & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' - -/proc/Syndicate_announce(var/text , var/mob/Sender) - var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) - msg = "SYNDICATE: [key_name(Sender, 1)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) ([ADMIN_SYNDICATE_REPLY(Sender,"RPLY")]): [msg]" - for(var/client/X in GLOB.admins) - if(check_rights(R_EVENT,0,X.mob)) - to_chat(X, msg) - if(X.prefs.sound & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' - -/proc/HONK_announce(var/text , var/mob/Sender) - var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) - msg = "HONK: [key_name(Sender, 1)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) (RPLY): [msg]" - for(var/client/X in GLOB.admins) - if(R_EVENT & X.holder.rights) - to_chat(X, msg) - if(X.prefs.sound & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' - -/proc/ERT_Announce(var/text , var/mob/Sender, var/repeat_warning) - var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) - msg = "ERT REQUEST: [key_name(Sender, 1)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) (RESPOND): [msg]" - if(repeat_warning) - msg += "
        WARNING: ERT request has gone 5 minutes with no reply!" - for(var/client/X in GLOB.admins) - if(check_rights(R_EVENT,0,X.mob)) - to_chat(X, msg) - if(X.prefs.sound & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' - -/proc/Nuke_request(text , mob/Sender) - var/nuke_code = get_nuke_code() - var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) - msg = "NUKE CODE REQUEST: [key_name(Sender)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) ([ADMIN_CENTCOM_REPLY(Sender,"RPLY")]): [msg]" - for(var/client/X in GLOB.admins) - if(check_rights(R_EVENT,0,X.mob)) - to_chat(X, msg) - to_chat(X, "The nuke code is [nuke_code].") - if(X.prefs.sound & SOUND_ADMINHELP) - X << 'sound/effects/adminhelp.ogg' +/mob/living/verb/pray(msg as text) + set category = "IC" + set name = "Pray" + + msg = sanitize(copytext(msg, 1, MAX_MESSAGE_LEN)) + if(!msg) + return + + if(usr.client) + if(usr.client.prefs.muted & MUTE_PRAY) + to_chat(usr, "You cannot pray (muted).") + return + if(client.handle_spam_prevention(msg, MUTE_PRAY, OOC_COOLDOWN)) + return + + var/image/cross = image('icons/obj/storage.dmi',"bible") + var/font_color = "purple" + var/prayer_type = "PRAYER" + var/deity + if(usr.job == "Chaplain") + if(SSticker && SSticker.Bible_deity_name) + deity = SSticker.Bible_deity_name + cross = image('icons/obj/storage.dmi',"kingyellow") + font_color = "blue" + prayer_type = "CHAPLAIN PRAYER" + else if(iscultist(usr)) + cross = image('icons/obj/storage.dmi',"tome") + font_color = "red" + prayer_type = "CULTIST PRAYER" + deity = SSticker.cultdat.entity_name + + log_say("(PRAYER) [msg]", usr) + msg = "[bicon(cross)][prayer_type][deity ? " (to [deity])" : ""][mind && mind.isholy ? " (blessings: [mind.num_blessed])" : ""]: [key_name(src, 1)] ([ADMIN_QUE(src,"?")]) ([ADMIN_PP(src,"PP")]) ([ADMIN_VV(src,"VV")]) ([ADMIN_TP(src,"TP")]) ([ADMIN_SM(src,"SM")]) ([admin_jump_link(src)]) ([ADMIN_SC(src,"SC")]) (BLESS) (SMITE): [msg]" + + for(var/client/X in GLOB.admins) + if(check_rights(R_EVENT,0,X.mob)) + to_chat(X, msg) + to_chat(usr, "Your prayers have been received by the gods.") + + feedback_add_details("admin_verb","PR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/proc/Centcomm_announce(var/text , var/mob/Sender) + var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) + msg = "CENTCOMM: [key_name(Sender, 1)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) ([ADMIN_CENTCOM_REPLY(Sender,"RPLY")])): [msg]" + for(var/client/X in GLOB.admins) + if(R_EVENT & X.holder.rights) + to_chat(X, msg) + if(X.prefs.sound & SOUND_ADMINHELP) + X << 'sound/effects/adminhelp.ogg' + +/proc/Syndicate_announce(var/text , var/mob/Sender) + var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) + msg = "SYNDICATE: [key_name(Sender, 1)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) ([ADMIN_SYNDICATE_REPLY(Sender,"RPLY")]): [msg]" + for(var/client/X in GLOB.admins) + if(check_rights(R_EVENT,0,X.mob)) + to_chat(X, msg) + if(X.prefs.sound & SOUND_ADMINHELP) + X << 'sound/effects/adminhelp.ogg' + +/proc/HONK_announce(var/text , var/mob/Sender) + var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) + msg = "HONK: [key_name(Sender, 1)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) (RPLY): [msg]" + for(var/client/X in GLOB.admins) + if(R_EVENT & X.holder.rights) + to_chat(X, msg) + if(X.prefs.sound & SOUND_ADMINHELP) + X << 'sound/effects/adminhelp.ogg' + +/proc/ERT_Announce(var/text , var/mob/Sender, var/repeat_warning) + var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) + msg = "ERT REQUEST: [key_name(Sender, 1)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) (RESPOND): [msg]" + if(repeat_warning) + msg += "
        WARNING: ERT request has gone 5 minutes with no reply!" + for(var/client/X in GLOB.admins) + if(check_rights(R_EVENT,0,X.mob)) + to_chat(X, msg) + if(X.prefs.sound & SOUND_ADMINHELP) + X << 'sound/effects/adminhelp.ogg' + +/proc/Nuke_request(text , mob/Sender) + var/nuke_code = get_nuke_code() + var/msg = sanitize(copytext(text, 1, MAX_MESSAGE_LEN)) + msg = "NUKE CODE REQUEST: [key_name(Sender)] ([ADMIN_PP(Sender,"PP")]) ([ADMIN_VV(Sender,"VV")]) ([ADMIN_TP(Sender,"TP")]) ([ADMIN_SM(Sender,"SM")]) ([admin_jump_link(Sender)]) ([ADMIN_BSA(Sender,"BSA")]) ([ADMIN_CENTCOM_REPLY(Sender,"RPLY")]): [msg]" + for(var/client/X in GLOB.admins) + if(check_rights(R_EVENT,0,X.mob)) + to_chat(X, msg) + to_chat(X, "The nuke code is [nuke_code].") + if(X.prefs.sound & SOUND_ADMINHELP) + X << 'sound/effects/adminhelp.ogg' diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 9bbffec3c55..cc13601e23b 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1,1159 +1,1159 @@ -/client/proc/cmd_admin_drop_everything(mob/M as mob in GLOB.mob_list) - set category = null - set name = "Drop Everything" - - if(!check_rights(R_DEBUG|R_ADMIN)) - return - - var/confirm = alert(src, "Make [M] drop everything?", "Message", "Yes", "No") - if(confirm != "Yes") - return - - for(var/obj/item/W in M) - M.unEquip(W) - - log_admin("[key_name(usr)] made [key_name(M)] drop everything!") - message_admins("[key_name_admin(usr)] made [key_name_admin(M)] drop everything!", 1) - feedback_add_details("admin_verb","DEVR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_prison(mob/M as mob in GLOB.mob_list) - set category = "Admin" - set name = "Prison" - - if(!check_rights(R_ADMIN)) - return - - if(ismob(M)) - if(istype(M, /mob/living/silicon/ai)) - alert("The AI can't be sent to prison you jerk!", null, null, null, null, null) - return - //strip their stuff before they teleport into a cell :downs: - for(var/obj/item/W in M) - M.unEquip(W) - //teleport person to cell - M.Paralyse(5) - sleep(5) //so they black out before warping - M.loc = pick(prisonwarp) - if(istype(M, /mob/living/carbon/human)) - var/mob/living/carbon/human/prisoner = M - prisoner.equip_to_slot_or_del(new /obj/item/clothing/under/color/orange(prisoner), slot_w_uniform) - prisoner.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(prisoner), slot_shoes) - spawn(50) - to_chat(M, "You have been sent to the prison station!") - log_admin("[key_name(usr)] sent [key_name(M)] to the prison station.") - message_admins("[key_name_admin(usr)] sent [key_name_admin(M)] to the prison station.", 1) - feedback_add_details("admin_verb","PRISON") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_subtle_message(mob/M as mob in GLOB.mob_list) - set category = "Event" - set name = "Subtle Message" - - if(!ismob(M)) - return - - if(!check_rights(R_SERVER|R_EVENT)) - return - - var/msg = clean_input("Message:", text("Subtle PM to [M.key]")) - - if(!msg) - return - - msg = admin_pencode_to_html(msg) - - if(usr) - if(usr.client) - if(usr.client.holder) - to_chat(M, "You hear a voice in your head... [msg]") - - log_admin("SubtlePM: [key_name(usr)] -> [key_name(M)] : [msg]") - message_admins("SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]", 1) - feedback_add_details("admin_verb","SMS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_mentor_check_new_players() //Allows mentors / admins to determine who the newer players are. - set category = "Admin" - set name = "Check new Players" - - if(!check_rights(R_MENTOR|R_MOD|R_ADMIN)) - return - - var/age = alert(src, "Age check", "Show accounts yonger then _____ days","7", "30" , "All") - - if(age == "All") - age = 9999999 - else - age = text2num(age) - - var/missing_ages = 0 - var/msg = "" - for(var/client/C in GLOB.clients) - if(C.player_age == "Requires database") - missing_ages = 1 - continue - if(C.player_age < age) - if(check_rights(R_ADMIN, 0)) - msg += "[key_name_admin(C.mob)]: [C.player_age] days old
        " - else - msg += "[key_name_mentor(C.mob)]: [C.player_age] days old
        " - - if(missing_ages) - to_chat(src, "Some accounts did not have proper ages set in their clients. This function requires database to be present") - - if(msg != "") - src << browse(msg, "window=Player_age_check") - else - to_chat(src, "No matches for that age range found.") - - -/client/proc/cmd_admin_world_narrate() // Allows administrators to fluff events a little easier -- TLE - set category = "Event" - set name = "Global Narrate" - - if(!check_rights(R_SERVER|R_EVENT)) - return - - var/msg = clean_input("Message:", text("Enter the text you wish to appear to everyone:")) - - if(!msg) - return - msg = admin_pencode_to_html(msg) - to_chat(world, "[msg]") - log_admin("GlobalNarrate: [key_name(usr)] : [msg]") - message_admins("GlobalNarrate: [key_name_admin(usr)]: [msg]
        ", 1) - feedback_add_details("admin_verb","GLN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_direct_narrate(var/mob/M) // Targetted narrate -- TLE - set category = "Event" - set name = "Direct Narrate" - - if(!check_rights(R_SERVER|R_EVENT)) - return - - if(!M) - M = input("Direct narrate to who?", "Active Players") as null|anything in get_mob_with_client_list() - - if(!M) - return - - var/msg = clean_input("Message:", text("Enter the text you wish to appear to your target:")) - - if( !msg ) - return - msg = admin_pencode_to_html(msg) - - to_chat(M, msg) - log_admin("DirectNarrate: [key_name(usr)] to ([key_name(M)]): [msg]") - message_admins("DirectNarrate: [key_name_admin(usr)] to ([key_name_admin(M)]): [msg]
        ", 1) - feedback_add_details("admin_verb","DIRN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - - - -/client/proc/cmd_admin_headset_message(mob/M in GLOB.mob_list) - set category = "Event" - set name = "Headset Message" - - admin_headset_message(M) - -/client/proc/admin_headset_message(mob/M in GLOB.mob_list, sender = null) - var/mob/living/carbon/human/H = M - - if(!check_rights(R_ADMIN)) - return - - if(!istype(H)) - to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") - return - if(!istype(H.l_ear, /obj/item/radio/headset) && !istype(H.r_ear, /obj/item/radio/headset)) - to_chat(usr, "The person you are trying to contact is not wearing a headset") - return - - if(!sender) - sender = input("Who is the message from?", "Sender") as null|anything in list("Centcomm", "Syndicate") - if(!sender) - return - - message_admins("[key_name_admin(src)] has started answering [key_name_admin(H)]'s [sender] request.") - var/input = clean_input("Please enter a message to reply to [key_name(H)] via their headset.", "Outgoing message from [sender]", "") - if(!input) - message_admins("[key_name_admin(src)] decided not to answer [key_name_admin(H)]'s [sender] request.") - return - - log_admin("[key_name(src)] replied to [key_name(H)]'s [sender] message with the message [input].") - message_admins("[key_name_admin(src)] replied to [key_name_admin(H)]'s [sender] message with: \"[input]\"") - to_chat(H, "Incoming priority transmission from [sender == "Syndicate" ? "your benefactor" : "Central Command"]. Message as follows[sender == "Syndicate" ? ", agent." : ":"] [input]") - - - - -/client/proc/cmd_admin_godmode(mob/M as mob in GLOB.mob_list) - set category = "Admin" - set name = "Godmode" - - if(!check_rights(R_ADMIN)) - return - - M.status_flags ^= GODMODE - to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]") - - log_admin("[key_name(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]") - message_admins("[key_name_admin(usr)] has toggled [key_name_admin(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]", 1) - feedback_add_details("admin_verb","GOD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -proc/cmd_admin_mute(mob/M as mob, mute_type, automute = 0) - if(automute) - if(!config.automute_on) - return - else - if(!usr || !usr.client) - return - if(!check_rights(R_ADMIN|R_MOD)) - to_chat(usr, "Error: cmd_admin_mute: You don't have permission to do this.") - return - if(!M.client) - to_chat(usr, "Error: cmd_admin_mute: This mob doesn't have a client tied to it.") - if(!M.client) - return - - var/muteunmute - var/mute_string - - switch(mute_type) - if(MUTE_IC) mute_string = "IC (say and emote)" - if(MUTE_OOC) mute_string = "OOC" - if(MUTE_PRAY) mute_string = "pray" - if(MUTE_ADMINHELP) mute_string = "adminhelp, admin PM and ASAY" - if(MUTE_DEADCHAT) mute_string = "deadchat and DSAY" - if(MUTE_ALL) mute_string = "everything" - else return - - if(automute) - muteunmute = "auto-muted" - M.client.prefs.muted |= mute_type - log_admin("SPAM AUTOMUTE: [muteunmute] [key_name(M)] from [mute_string]") - message_admins("SPAM AUTOMUTE: [muteunmute] [key_name_admin(M)] from [mute_string].", 1) - to_chat(M, "You have been [muteunmute] from [mute_string] by the SPAM AUTOMUTE system. Contact an admin.") - feedback_add_details("admin_verb","AUTOMUTE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - - if(M.client.prefs.muted & mute_type) - muteunmute = "unmuted" - M.client.prefs.muted &= ~mute_type - else - muteunmute = "muted" - M.client.prefs.muted |= mute_type - - log_admin("[key_name(usr)] has [muteunmute] [key_name(M)] from [mute_string]") - message_admins("[key_name_admin(usr)] has [muteunmute] [key_name_admin(M)] from [mute_string].", 1) - to_chat(M, "You have been [muteunmute] from [mute_string].") - feedback_add_details("admin_verb","MUTE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_add_random_ai_law() - set category = "Event" - set name = "Add Random AI Law" - - if(!check_rights(R_EVENT)) - return - - var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") - if(confirm != "Yes") return - log_admin("[key_name(src)] has added a random AI law.") - message_admins("[key_name_admin(src)] has added a random AI law.") - - var/show_log = alert(src, "Show ion message?", "Message", "Yes", "No") - var/announce_ion_laws = (show_log == "Yes" ? 1 : -1) - - new /datum/event/ion_storm(0, announce_ion_laws) - feedback_add_details("admin_verb","ION") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_antagHUD_use() - set category = "Server" - set name = "Toggle antagHUD usage" - set desc = "Toggles antagHUD usage for observers" - - if(!check_rights(R_SERVER)) - return - - var/action="" - if(config.antag_hud_allowed) - for(var/mob/dead/observer/g in get_ghosts()) - if(!g.client.holder) //Remove the verb from non-admin ghosts - g.verbs -= /mob/dead/observer/verb/toggle_antagHUD - if(g.antagHUD) - g.antagHUD = 0 // Disable it on those that have it enabled - g.has_enabled_antagHUD = 2 // We'll allow them to respawn - to_chat(g, "The Administrator has disabled AntagHUD ") - config.antag_hud_allowed = 0 - to_chat(src, "AntagHUD usage has been disabled") - action = "disabled" - else - for(var/mob/dead/observer/g in get_ghosts()) - if(!g.client.holder) // Add the verb back for all non-admin ghosts - g.verbs += /mob/dead/observer/verb/toggle_antagHUD - to_chat(g, "The Administrator has enabled AntagHUD ")// Notify all observers they can now use AntagHUD - - config.antag_hud_allowed = 1 - action = "enabled" - to_chat(src, "AntagHUD usage has been enabled") - - - log_admin("[key_name(usr)] has [action] antagHUD usage for observers") - message_admins("Admin [key_name_admin(usr)] has [action] antagHUD usage for observers", 1) - -/client/proc/toggle_antagHUD_restrictions() - set category = "Server" - set name = "Toggle antagHUD Restrictions" - set desc = "Restricts players that have used antagHUD from being able to join this round." - - if(!check_rights(R_SERVER)) - return - - var/action="" - if(config.antag_hud_restricted) - for(var/mob/dead/observer/g in get_ghosts()) - to_chat(g, "The administrator has lifted restrictions on joining the round if you use AntagHUD") - action = "lifted restrictions" - config.antag_hud_restricted = 0 - to_chat(src, "AntagHUD restrictions have been lifted") - else - for(var/mob/dead/observer/g in get_ghosts()) - to_chat(g, "The administrator has placed restrictions on joining the round if you use AntagHUD") - to_chat(g, "Your AntagHUD has been disabled, you may choose to re-enabled it but will be under restrictions ") - g.antagHUD = 0 - g.has_enabled_antagHUD = 0 - action = "placed restrictions" - config.antag_hud_restricted = 1 - to_chat(src, "AntagHUD restrictions have been enabled") - - log_admin("[key_name(usr)] has [action] on joining the round if they use AntagHUD") - message_admins("Admin [key_name_admin(usr)] has [action] on joining the round if they use AntagHUD", 1) - -/* -If a guy was gibbed and you want to revive him, this is a good way to do so. -Works kind of like entering the game with a new character. Character receives a new mind if they didn't have one. -Traitors and the like can also be revived with the previous role mostly intact. -/N */ -/client/proc/respawn_character() - set category = "Event" - set name = "Respawn Character" - set desc = "Respawn a person that has been gibbed/dusted/killed. They must be a ghost for this to work and preferably should not have a body to go back into." - - if(!check_rights(R_SPAWN)) - return - - var/input = ckey(input(src, "Please specify which key will be respawned.", "Key", "")) - if(!input) - return - - var/mob/dead/observer/G_found - for(var/mob/dead/observer/G in GLOB.player_list) - if(G.ckey == input) - G_found = G - break - - if(!G_found)//If a ghost was not found. - to_chat(usr, "There is no active key like that in the game or the person is not currently a ghost.") - return - - if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something - //Check if they were an alien - if(G_found.mind.assigned_role=="Alien") - if(alert("This character appears to have been an alien. Would you like to respawn them as such?",,"Yes","No")=="Yes") - var/turf/T - if(xeno_spawn.len) T = pick(xeno_spawn) - else T = pick(latejoin) - - var/mob/living/carbon/alien/new_xeno - switch(G_found.mind.special_role)//If they have a mind, we can determine which caste they were. - if("Hunter") new_xeno = new /mob/living/carbon/alien/humanoid/hunter(T) - if("Sentinel") new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(T) - if("Drone") new_xeno = new /mob/living/carbon/alien/humanoid/drone(T) - if("Queen") new_xeno = new /mob/living/carbon/alien/humanoid/queen(T) - else//If we don't know what special role they have, for whatever reason, or they're a larva. - create_xeno(G_found.ckey) - return - - //Now to give them their mind back. - G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_xeno.key = G_found.key - to_chat(new_xeno, "You have been fully respawned. Enjoy the game.") - message_admins("[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno.", 1) - return //all done. The ghost is auto-deleted - - var/mob/living/carbon/human/new_character = new(pick(latejoin))//The mob being spawned. - - var/datum/data/record/record_found //Referenced to later to either randomize or not randomize the character. - if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something - /*Try and locate a record for the person being respawned through data_core. - This isn't an exact science but it does the trick more often than not.*/ - var/id = md5("[G_found.real_name][G_found.mind.assigned_role]") - for(var/datum/data/record/t in data_core.locked) - if(t.fields["id"]==id) - record_found = t//We shall now reference the record. - break - - if(record_found)//If they have a record we can determine a few things. - new_character.real_name = record_found.fields["name"] - new_character.change_gender(record_found.fields["sex"]) - new_character.age = record_found.fields["age"] - new_character.dna.blood_type = record_found.fields["blood_type"] - else - new_character.change_gender(pick(MALE,FEMALE)) - var/datum/preferences/A = new() - A.real_name = G_found.real_name - A.copy_to(new_character) - - if(!new_character.real_name) - new_character.real_name = random_name(new_character.gender) - new_character.name = new_character.real_name - - if(G_found.mind && !G_found.mind.active) - G_found.mind.transfer_to(new_character) //be careful when doing stuff like this! I've already checked the mind isn't in use - new_character.mind.special_verbs = list() - else - new_character.mind_initialize() - if(!new_character.mind.assigned_role) new_character.mind.assigned_role = "Civilian"//If they somehow got a null assigned role. - - //DNA - if(record_found)//Pull up their name from database records if they did have a mind. - new_character.dna = new()//Let's first give them a new DNA. - new_character.dna.unique_enzymes = record_found.fields["b_dna"]//Enzymes are based on real name but we'll use the record for conformity. - - // I HATE BYOND. HATE. HATE. - N3X - var/list/newSE= record_found.fields["enzymes"] - var/list/newUI = record_found.fields["identity"] - new_character.dna.SE = newSE.Copy() //This is the default of enzymes so I think it's safe to go with. - new_character.dna.UpdateSE() - new_character.UpdateAppearance(newUI.Copy())//Now we configure their appearance based on their unique identity, same as with a DNA machine or somesuch. - else//If they have no records, we just do a random DNA for them, based on their random appearance/savefile. - new_character.dna.ready_dna(new_character) - - new_character.key = G_found.key - - /* - The code below functions with the assumption that the mob is already a traitor if they have a special role. - So all it does is re-equip the mob with powers and/or items. Or not, if they have no special role. - If they don't have a mind, they obviously don't have a special role. - */ - - //Now for special roles and equipment. - switch(new_character.mind.special_role) - if("traitor") - if(new_character.mind.has_antag_datum(/datum/antagonist/traitor)) - var/datum/antagonist/traitor/T = new_character.mind.has_antag_datum(/datum/antagonist/traitor) - T.equip_traitor(src) - else - new_character.mind.add_antag_datum(/datum/antagonist/traitor) - if("Wizard") - new_character.loc = pick(wizardstart) - //ticker.mode.learn_basic_spells(new_character) - SSticker.mode.equip_wizard(new_character) - if("Syndicate") - var/obj/effect/landmark/synd_spawn = locate("landmark*Syndicate-Spawn") - if(synd_spawn) - new_character.loc = get_turf(synd_spawn) - call(/datum/game_mode/proc/equip_syndicate)(new_character) - - if("Death Commando")//Leaves them at late-join spawn. - new_character.equip_death_commando() - new_character.internal = new_character.s_store - new_character.update_action_buttons_icon() - else//They may also be a cyborg or AI. - switch(new_character.mind.assigned_role) - if("Cyborg")//More rigging to make em' work and check if they're traitor. - new_character = new_character.Robotize() - if(new_character.mind.special_role=="traitor") - new_character.mind.add_antag_datum(/datum/antagonist/traitor) - if("AI") - new_character = new_character.AIize() - var/mob/living/silicon/ai/ai_character = new_character - ai_character.moveToAILandmark() - if(new_character.mind.special_role=="traitor") - new_character.mind.add_antag_datum(/datum/antagonist/traitor) - //Add aliens. - else - SSjobs.AssignRank(new_character, new_character.mind.assigned_role, 0) - SSjobs.EquipRank(new_character, new_character.mind.assigned_role, 1)//Or we simply equip them. - - //Announces the character on all the systems, based on the record. - if(!issilicon(new_character))//If they are not a cyborg/AI. - if(!record_found && new_character.mind.assigned_role != new_character.mind.special_role)//If there are no records for them. If they have a record, this info is already in there. Offstation special characters announced anyway. - //Power to the user! - if(alert(new_character,"Warning: No data core entry detected. Would you like to announce the arrival of this character by adding them to various databases, such as medical records?",,"No","Yes")=="Yes") - data_core.manifest_inject(new_character) - - if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") - call(/mob/new_player/proc/AnnounceArrival)(new_character, new_character.mind.assigned_role) - - message_admins("[key_name_admin(usr)] has respawned [key_name_admin(G_found)] as [new_character.real_name].", 1) - - to_chat(new_character, "You have been fully respawned. Enjoy the game.") - - feedback_add_details("admin_verb","RSPCH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return new_character - -//I use this proc for respawn character too. /N -/proc/create_xeno(ckey) - if(!ckey) - var/list/candidates = list() - for(var/mob/M in GLOB.player_list) - if(M.stat != DEAD) continue //we are not dead! - if(!(ROLE_ALIEN in M.client.prefs.be_special)) continue //we don't want to be an alium - if(jobban_isbanned(M, "alien") || jobban_isbanned(M, "Syndicate")) continue //we are jobbanned - if(M.client.is_afk()) continue //we are afk - if(M.mind && M.mind.current && M.mind.current.stat != DEAD) continue //we have a live body we are tied to - candidates += M.ckey - if(candidates.len) - ckey = input("Pick the player you want to respawn as a xeno.", "Suitable Candidates") as null|anything in candidates - else - to_chat(usr, "Error: create_xeno(): no suitable candidates.") - if(!istext(ckey)) return 0 - - var/alien_caste = input(usr, "Please choose which caste to spawn.","Pick a caste",null) as null|anything in list("Queen","Hunter","Sentinel","Drone","Larva") - var/obj/effect/landmark/spawn_here = xeno_spawn.len ? pick(xeno_spawn) : pick(latejoin) - var/mob/living/carbon/alien/new_xeno - switch(alien_caste) - if("Queen") new_xeno = new /mob/living/carbon/alien/humanoid/queen/large(spawn_here) - if("Hunter") new_xeno = new /mob/living/carbon/alien/humanoid/hunter(spawn_here) - if("Sentinel") new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(spawn_here) - if("Drone") new_xeno = new /mob/living/carbon/alien/humanoid/drone(spawn_here) - if("Larva") new_xeno = new /mob/living/carbon/alien/larva(spawn_here) - else return 0 - - new_xeno.ckey = ckey - message_admins("[key_name_admin(usr)] has spawned [ckey] as a filthy xeno [alien_caste].", 1) - return 1 - - -/client/proc/get_ghosts(var/notify = 0,var/what = 2) - // what = 1, return ghosts ass list. - // what = 2, return mob list - - var/list/mobs = list() - var/list/ghosts = list() - var/list/sortmob = sortAtom(GLOB.mob_list) // get the mob list. - /var/any=0 - for(var/mob/dead/observer/M in sortmob) - mobs.Add(M) //filter it where it's only ghosts - any = 1 //if no ghosts show up, any will just be 0 - if(!any) - if(notify) - to_chat(src, "There doesn't appear to be any ghosts for you to select.") - return - - for(var/mob/M in mobs) - var/name = M.name - ghosts[name] = M //get the name of the mob for the popup list - if(what==1) - return ghosts - else - return mobs - -/client/proc/cmd_admin_add_freeform_ai_law() - set category = "Event" - set name = "Add Custom AI law" - - if(!check_rights(R_EVENT)) - return - - var/input = clean_input("Please enter anything you want the AI to do. Anything. Serious.", "What?", "") - if(!input) - return - - log_admin("Admin [key_name(usr)] has added a new AI law - [input]") - message_admins("Admin [key_name_admin(usr)] has added a new AI law - [input]") - - var/show_log = alert(src, "Show ion message?", "Message", "Yes", "No") - var/announce_ion_laws = (show_log == "Yes" ? 1 : -1) - - new /datum/event/ion_storm(0, announce_ion_laws, input) - - feedback_add_details("admin_verb","IONC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_rejuvenate(mob/living/M as mob in GLOB.mob_list) - set category = "Event" - set name = "Rejuvenate" - - if(!check_rights(R_REJUVINATE)) - return - - if(!mob) - return - if(!istype(M)) - alert("Cannot revive a ghost") - return - M.revive() - - log_admin("[key_name(usr)] healed / revived [key_name(M)]") - message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(M)]!", 1) - feedback_add_details("admin_verb","REJU") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_create_centcom_report() - set category = "Event" - set name = "Create Communications Report" - - if(!check_rights(R_SERVER|R_EVENT)) - return - -//the stuff on the list is |"report type" = "report title"|, if that makes any sense - var/list/MsgType = list("Central Command Report" = "Nanotrasen Update", - "Syndicate Communique" = "Syndicate Message", - "Space Wizard Federation Message" = "Sorcerous Message", - "Enemy Communications" = "Unknown Message", - "Custom" = "Cryptic Message") - - var/list/MsgSound = list("Beep" = 'sound/misc/notice2.ogg', - "Enemy Communications Intercepted" = 'sound/AI/intercept2.ogg', - "New Command Report Created" = 'sound/AI/commandreport.ogg') - - var/type = input(usr, "Pick a type of report to send", "Report Type", "") as anything in MsgType - - if(type == "Custom") - type = clean_input("What would you like the report type to be?", "Report Type", "Encrypted Transmission") - - var/customname = input(usr, "Pick a title for the report.", "Title", MsgType[type]) as text|null - if(!customname) - return - var/input = input(usr, "Please enter anything you want. Anything. Serious.", "What's the message?") as message|null - if(!input) - return - - switch(alert("Should this be announced to the general population?",,"Yes","No", "Cancel")) - if("Yes") - var/beepsound = input(usr, "What sound should the announcement make?", "Announcement Sound", "") as anything in MsgSound - - command_announcement.Announce(input, customname, MsgSound[beepsound], , , type) - print_command_report(input, "[command_name()] Update") - if("No") - //same thing as the blob stuff - it's not public, so it's classified, dammit - command_announcer.autosay("A classified message has been printed out at all communication consoles."); - print_command_report(input, "Classified [command_name()] Update") - else - return - - log_admin("[key_name(src)] has created a communications report: [input]") - message_admins("[key_name_admin(src)] has created a communications report", 1) - feedback_add_details("admin_verb","CCR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - -/client/proc/cmd_admin_delete(atom/A as obj|mob|turf in view()) - set category = "Admin" - set name = "Delete" - - if(!check_rights(R_ADMIN)) - return - - admin_delete(A) - -/client/proc/admin_delete(datum/D) - if(istype(D) && !D.can_vv_delete()) - to_chat(src, "[D] rejected your deletion") - return - var/atom/A = D - var/coords = istype(A) ? "at ([A.x], [A.y], [A.z])" : "" - if(alert(src, "Are you sure you want to delete:\n[D]\n[coords]?", "Confirmation", "Yes", "No") == "Yes") - log_admin("[key_name(usr)] deleted [D] [coords]") - message_admins("[key_name_admin(usr)] deleted [D] [coords]", 1) - feedback_add_details("admin_verb","DEL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - if(isturf(D)) - var/turf/T = D - T.ChangeTurf(T.baseturf) - else - qdel(D) - -/client/proc/cmd_admin_list_open_jobs() - set category = "Admin" - set name = "List free slots" - - if(!check_rights(R_ADMIN)) - return - - if(SSjobs) - var/currentpositiontally - var/totalpositiontally - to_chat(src, "Job Name: Filled job slot / Total job slots (Free job slots)") - for(var/datum/job/job in SSjobs.occupations) - to_chat(src, "[job.title]: [job.current_positions] / \ - [job.total_positions == -1 ? "UNLIMITED" : job.total_positions] \ - ([job.total_positions == -1 ? "UNLIMITED" : job.total_positions - job.current_positions])") - if(job.total_positions != -1) // Only count position that isn't unlimited - currentpositiontally += job.current_positions - totalpositiontally += job.total_positions - to_chat(src, "Currently filled job slots (Excluding unlimited): [currentpositiontally] / [totalpositiontally] ([totalpositiontally - currentpositiontally])") - feedback_add_details("admin_verb","LFS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_explosion(atom/O as obj|mob|turf in view()) - set category = "Event" - set name = "Explosion" - - if(!check_rights(R_DEBUG|R_EVENT)) - return - - var/devastation = input("Range of total devastation. -1 to none", text("Input")) as num|null - if(devastation == null) return - var/heavy = input("Range of heavy impact. -1 to none", text("Input")) as num|null - if(heavy == null) return - var/light = input("Range of light impact. -1 to none", text("Input")) as num|null - if(light == null) return - var/flash = input("Range of flash. -1 to none", text("Input")) as num|null - if(flash == null) return - var/flames = input("Range of flames. -1 to none", text("Input")) as num|null - if(flames == null) return - - if((devastation != -1) || (heavy != -1) || (light != -1) || (flash != -1) || (flames != -1)) - if((devastation > 20) || (heavy > 20) || (light > 20) || (flames > 20)) - if(alert(src, "Are you sure you want to do this? It will laaag.", "Confirmation", "Yes", "No") == "No") - return - - explosion(O, devastation, heavy, light, flash, null, null,flames) - log_admin("[key_name(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at ([O.x],[O.y],[O.z])") - message_admins("[key_name_admin(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at ([O.x],[O.y],[O.z])") - feedback_add_details("admin_verb","EXPL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - else - return - -/client/proc/cmd_admin_emp(atom/O as obj|mob|turf in view()) - set category = "Event" - set name = "EM Pulse" - - if(!check_rights(R_DEBUG|R_EVENT)) - return - - var/heavy = input("Range of heavy pulse.", text("Input")) as num|null - if(heavy == null) return - var/light = input("Range of light pulse.", text("Input")) as num|null - if(light == null) return - - if(heavy || light) - - empulse(O, heavy, light) - log_admin("[key_name(usr)] created an EM pulse ([heavy], [light]) at ([O.x],[O.y],[O.z])") - message_admins("[key_name_admin(usr)] created an EM pulse ([heavy], [light]) at ([O.x],[O.y],[O.z])", 1) - feedback_add_details("admin_verb","EMP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - return - else - return - -/client/proc/cmd_admin_gib(mob/M as mob in GLOB.mob_list) - set category = "Admin" - set name = "Gib" - - if(!check_rights(R_ADMIN|R_EVENT)) - return - - var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") - if(confirm != "Yes") return - //Due to the delay here its easy for something to have happened to the mob - if(!M) return - - log_admin("[key_name(usr)] has gibbed [key_name(M)]") - message_admins("[key_name_admin(usr)] has gibbed [key_name_admin(M)]", 1) - - if(istype(M, /mob/dead/observer)) - gibs(M.loc) - return - - M.gib() - feedback_add_details("admin_verb","GIB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_gib_self() - set name = "Gibself" - set category = "Event" - - if(!check_rights(R_ADMIN|R_EVENT)) - return - - var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") - if(confirm == "Yes") - if(istype(mob, /mob/dead/observer)) // so they don't spam gibs everywhere - return - else - mob.gib() - - log_admin("[key_name(usr)] used gibself.") - message_admins("[key_name_admin(usr)] used gibself.", 1) - feedback_add_details("admin_verb","GIBS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/cmd_admin_check_contents(mob/living/M as mob in GLOB.mob_list) - set name = "Check Contents" - set category = null - - if(!check_rights(R_ADMIN)) - return - - var/list/L = M.get_contents() - for(var/t in L) - to_chat(usr, "[t]") - feedback_add_details("admin_verb","CC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_view_range() - set category = "Admin" - set name = "Change View Range" - set desc = "switches between 1x and custom views" - - if(!check_rights(R_ADMIN)) - return - - if(view == world.view) - view = input("Select view range:", "View Range", world.view) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128) - else - view = world.view - - log_admin("[key_name(usr)] changed their view range to [view].") - //message_admins("[key_name_admin(usr)] changed their view range to [view].", 1) //why? removed by order of XSI - - feedback_add_details("admin_verb","CVRA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/admin_call_shuttle() - - set category = "Admin" - set name = "Call Shuttle" - - if(SSshuttle.emergency.mode >= SHUTTLE_DOCKED) - return - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") - if(confirm != "Yes") return - - if(alert("Set Shuttle Recallable (Select Yes unless you know what this does)", "Recallable?", "Yes", "No") == "Yes") - SSshuttle.emergency.canRecall = TRUE - else - SSshuttle.emergency.canRecall = FALSE - - SSshuttle.emergency.request() - - feedback_add_details("admin_verb","CSHUT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] admin-called the emergency shuttle.") - message_admins("[key_name_admin(usr)] admin-called the emergency shuttle.") - return - -/client/proc/admin_cancel_shuttle() - set category = "Admin" - set name = "Cancel Shuttle" - - if(!check_rights(R_ADMIN)) - return - if(alert(src, "You sure?", "Confirm", "Yes", "No") != "Yes") return - - if(SSshuttle.emergency.mode >= SHUTTLE_DOCKED) - return - - if(SSshuttle.emergency.canRecall == FALSE) - if(alert("Shuttle is currently set to be nonrecallable. Recalling may break things. Respect Recall Status?", "Override Recall Status?", "Yes", "No") == "Yes") - return - else - var/keepStatus = alert("Maintain recall status on future shuttle calls?", "Maintain Status?", "Yes", "No") == "Yes" //Keeps or drops recallability - SSshuttle.emergency.canRecall = TRUE // must be true for cancel proc to work - SSshuttle.emergency.cancel() - if(keepStatus) - SSshuttle.emergency.canRecall = FALSE // restores original status - else - SSshuttle.emergency.cancel() - - feedback_add_details("admin_verb","CCSHUT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] admin-recalled the emergency shuttle.") - message_admins("[key_name_admin(usr)] admin-recalled the emergency shuttle.") - return - -/client/proc/admin_deny_shuttle() - set category = "Admin" - set name = "Toggle Deny Shuttle" - - if(!SSticker) - return - - if(!check_rights(R_ADMIN)) - return - - if(SSshuttle) - SSshuttle.emergencyNoEscape = !SSshuttle.emergencyNoEscape - - log_admin("[key_name(src)] has [SSshuttle.emergencyNoEscape ? "denied" : "allowed"] the shuttle to be called.") - message_admins("[key_name_admin(usr)] has [SSshuttle.emergencyNoEscape ? "denied" : "allowed"] the shuttle to be called.") - -/client/proc/cmd_admin_attack_log(mob/M as mob in GLOB.mob_list) - set category = "Admin" - set name = "Attack Log" - - if(!check_rights(R_ADMIN)) - return - - to_chat(usr, text("Attack Log for []", mob)) - for(var/t in M.attack_log) - 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! - - -/client/proc/everyone_random() - set category = "Event" - set name = "Make Everyone Random" - set desc = "Make everyone have a random appearance. You can only use this before rounds!" - - if(!check_rights(R_SERVER|R_EVENT)) - return - - if(SSticker && SSticker.mode) - to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!") - return - - if(SSticker.random_players) - SSticker.random_players = 0 - message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.", 1) - to_chat(usr, "Disabled.") - return - - - var/notifyplayers = alert(src, "Do you want to notify the players?", "Options", "Yes", "No", "Cancel") - if(notifyplayers == "Cancel") - return - - log_admin("Admin [key_name(src)] has forced the players to have random appearances.") - message_admins("Admin [key_name_admin(usr)] has forced the players to have random appearances.", 1) - - if(notifyplayers == "Yes") - to_chat(world, "Admin [usr.key] has forced the players to have completely random identities!") - - to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.") - - SSticker.random_players = 1 - feedback_add_details("admin_verb","MER") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/toggle_random_events() - set category = "Event" - set name = "Toggle random events on/off" - - set desc = "Toggles random events such as meteors, black holes, blob (but not space dust) on/off" - if(!check_rights(R_SERVER|R_EVENT)) - return - - if(!config.allow_random_events) - config.allow_random_events = 1 - to_chat(usr, "Random events enabled") - message_admins("Admin [key_name_admin(usr)] has enabled random events.", 1) - else - config.allow_random_events = 0 - to_chat(usr, "Random events disabled") - message_admins("Admin [key_name_admin(usr)] has disabled random events.", 1) - feedback_add_details("admin_verb","TRE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/reset_all_tcs() - set category = "Admin" - set name = "Reset NTTC Configuration" - set desc = "Resets NTTC to the default configuration." - - if(!check_rights(R_ADMIN)) - return - - var/confirm = alert(src, "You sure you want to reset NTTC?", "Confirm", "Yes", "No") - if(confirm != "Yes") - return - - GLOB.nttc_config.reset() - log_admin("[key_name(usr)] reset NTTC scripts.") - message_admins("[key_name_admin(usr)] reset NTTC scripts.") - feedback_add_details("admin_verb","RAT2") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/list_ssds_afks() - set category = "Admin" - set name = "List SSDs and AFKs" - set desc = "Lists SSD and AFK players" - - if(!check_rights(R_ADMIN)) - return - - /* ======== SSD Section ========= */ - var/msg = "SSD & AFK Report" - msg += "SSD Players:
        " - msg += "" - var/mins_ssd - var/job_string - var/key_string - var/role_string - var/obj_count = 0 - var/obj_string = "" - for(var/mob/living/carbon/human/H in GLOB.living_mob_list) - if(!isLivingSSD(H)) - continue - mins_ssd = round((world.time - H.last_logout) / 600) - if(H.job) - job_string = H.job - else - job_string = "-" - key_string = H.key - if(job_string in command_positions) - job_string = "" + job_string + "" - role_string = "-" - obj_count = 0 - obj_string = "" - if(H.mind) - if(H.mind.special_role) - role_string = "[H.mind.special_role]" - if(!H.key && H.mind.key) - key_string = H.mind.key - for(var/datum/objective/O in GLOB.all_objectives) - if(O.target == H.mind) - obj_count++ - if(obj_count > 0) - obj_string = "
        Obj Target" - msg += "" - msg += "" - if(istype(H.loc, /obj/machinery/cryopod)) - msg += "" - else - msg += "" - msg += "" - msg += "
        KeyReal NameJobMins SSDSpecial RoleAreaPPNCryo
        [key_string][H.real_name][job_string][mins_ssd][role_string][obj_string][get_area(H)][ADMIN_PP(H,"PP")]De-SpawnCryo

        " - - /* ======== AFK Section ========= */ - msg += "AFK Players:
        " - msg += "" - var/mins_afk - for(var/mob/living/carbon/human/H in GLOB.living_mob_list) - if(H.client == null || H.stat == DEAD) // No clientless or dead - continue - mins_afk = round(H.client.inactivity / 600) - if(mins_afk < config.list_afk_minimum) - continue - if(H.job) - job_string = H.job - else - job_string = "-" - key_string = H.key - if(job_string in command_positions) - job_string = "" + job_string + "" - role_string = "-" - obj_count = 0 - obj_string = "" - if(H.mind) - if(H.mind.special_role) - role_string = "[H.mind.special_role]" - if(!H.key && H.mind.key) - key_string = H.mind.key - for(var/datum/objective/O in GLOB.all_objectives) - if(O.target == H.mind) - obj_count++ - if(obj_count > 0) - obj_string = "
        Obj Target" - msg += "" - msg += "" - if(istype(H.loc, /obj/machinery/cryopod)) - msg += "" - else - msg += "" - msg += "" - msg += "
        KeyReal NameJobMins AFKSpecial RoleAreaPPNCryo
        [key_string][H.real_name][job_string][mins_afk][role_string][obj_string][get_area(H)][ADMIN_PP(H,"PP")]De-SpawnCryo
        " - src << browse(msg, "window=Player_ssd_afk_check;size=600x300") - -/client/proc/toggle_ert_calling() - set category = "Event" - set name = "Toggle ERT" - - set desc = "Toggle the station's ability to call a response team." - if(!check_rights(R_EVENT)) - return - - if(SSticker.mode.ert_disabled) - SSticker.mode.ert_disabled = 0 - to_chat(usr, "ERT has been Enabled.") - log_admin("Admin [key_name(src)] has enabled ERT calling.") - message_admins("Admin [key_name_admin(usr)] has enabled ERT calling.", 1) - else - SSticker.mode.ert_disabled = 1 - to_chat(usr, "ERT has been Disabled.") - log_admin("Admin [key_name(src)] has disabled ERT calling.") - message_admins("Admin [key_name_admin(usr)] has disabled ERT calling.", 1) - -/client/proc/show_tip() - set category = "Admin" - set name = "Show Custom Tip" - set desc = "Sends a tip (that you specify) to all players. After all \ - you're the experienced player here." - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Please specify your tip that you want to send to the players.", "Tip", "") as message|null - if(!input) - return - - if(!SSticker) - return - - SSticker.selected_tip = input - - // If we've already tipped, then send it straight away. - if(SSticker.tipped) - SSticker.send_tip_of_the_round() - - message_admins("[key_name_admin(usr)] sent a Tip of the round.") - log_admin("[key_name(usr)] sent \"[input]\" as the Tip of the Round.") - -/client/proc/modify_goals() - set category = "Event" - set name = "Modify Station Goals" - - if(!check_rights(R_EVENT)) - return - - holder.modify_goals() - -/datum/admins/proc/modify_goals() - if(!SSticker || !SSticker.mode) - to_chat(usr, "This verb can only be used if the round has started.") - return - - var/dat = "" - for(var/datum/station_goal/S in SSticker.mode.station_goals) - dat += "[S.name] - Announce | Remove
        " - dat += "
        Add New Goal" - usr << browse(dat, "window=goals;size=400x400") - -/// Allow admin to add or remove traits of datum -/datum/admins/proc/modify_traits(datum/D) - if(!D) - return - - var/add_or_remove = input("Remove/Add?", "Trait Remove/Add") as null|anything in list("Add","Remove") - if(!add_or_remove) - return - var/list/availible_traits = list() - - switch(add_or_remove) - if("Add") - for(var/key in GLOB.traits_by_type) - if(istype(D,key)) - availible_traits += GLOB.traits_by_type[key] - if("Remove") - if(!GLOB.trait_name_map) - GLOB.trait_name_map = generate_trait_name_map() - for(var/trait in D.status_traits) - var/name = GLOB.trait_name_map[trait] || trait - availible_traits[name] = trait - - var/chosen_trait = input("Select trait to modify", "Trait") as null|anything in availible_traits - if(!chosen_trait) - return - chosen_trait = availible_traits[chosen_trait] - - var/source = "adminabuse" - switch(add_or_remove) - if("Add") //Not doing source choosing here intentionally to make this bit faster to use, you can always vv it. - ADD_TRAIT(D, chosen_trait, source) - if("Remove") - var/specific = input("All or specific source ?", "Trait Remove/Add") as null|anything in list("All","Specific") - if(!specific) - return - switch(specific) - if("All") - source = null - if("Specific") - source = input("Source to be removed","Trait Remove/Add") as null|anything in D.status_traits[chosen_trait] - if(!source) - return - REMOVE_TRAIT(D, chosen_trait, source) \ No newline at end of file +/client/proc/cmd_admin_drop_everything(mob/M as mob in GLOB.mob_list) + set category = null + set name = "Drop Everything" + + if(!check_rights(R_DEBUG|R_ADMIN)) + return + + var/confirm = alert(src, "Make [M] drop everything?", "Message", "Yes", "No") + if(confirm != "Yes") + return + + for(var/obj/item/W in M) + M.unEquip(W) + + log_admin("[key_name(usr)] made [key_name(M)] drop everything!") + message_admins("[key_name_admin(usr)] made [key_name_admin(M)] drop everything!", 1) + feedback_add_details("admin_verb","DEVR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_prison(mob/M as mob in GLOB.mob_list) + set category = "Admin" + set name = "Prison" + + if(!check_rights(R_ADMIN)) + return + + if(ismob(M)) + if(istype(M, /mob/living/silicon/ai)) + alert("The AI can't be sent to prison you jerk!", null, null, null, null, null) + return + //strip their stuff before they teleport into a cell :downs: + for(var/obj/item/W in M) + M.unEquip(W) + //teleport person to cell + M.Paralyse(5) + sleep(5) //so they black out before warping + M.loc = pick(prisonwarp) + if(istype(M, /mob/living/carbon/human)) + var/mob/living/carbon/human/prisoner = M + prisoner.equip_to_slot_or_del(new /obj/item/clothing/under/color/orange(prisoner), slot_w_uniform) + prisoner.equip_to_slot_or_del(new /obj/item/clothing/shoes/orange(prisoner), slot_shoes) + spawn(50) + to_chat(M, "You have been sent to the prison station!") + log_admin("[key_name(usr)] sent [key_name(M)] to the prison station.") + message_admins("[key_name_admin(usr)] sent [key_name_admin(M)] to the prison station.", 1) + feedback_add_details("admin_verb","PRISON") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_subtle_message(mob/M as mob in GLOB.mob_list) + set category = "Event" + set name = "Subtle Message" + + if(!ismob(M)) + return + + if(!check_rights(R_SERVER|R_EVENT)) + return + + var/msg = clean_input("Message:", text("Subtle PM to [M.key]")) + + if(!msg) + return + + msg = admin_pencode_to_html(msg) + + if(usr) + if(usr.client) + if(usr.client.holder) + to_chat(M, "You hear a voice in your head... [msg]") + + log_admin("SubtlePM: [key_name(usr)] -> [key_name(M)] : [msg]") + message_admins("SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]", 1) + feedback_add_details("admin_verb","SMS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_mentor_check_new_players() //Allows mentors / admins to determine who the newer players are. + set category = "Admin" + set name = "Check new Players" + + if(!check_rights(R_MENTOR|R_MOD|R_ADMIN)) + return + + var/age = alert(src, "Age check", "Show accounts yonger then _____ days","7", "30" , "All") + + if(age == "All") + age = 9999999 + else + age = text2num(age) + + var/missing_ages = 0 + var/msg = "" + for(var/client/C in GLOB.clients) + if(C.player_age == "Requires database") + missing_ages = 1 + continue + if(C.player_age < age) + if(check_rights(R_ADMIN, 0)) + msg += "[key_name_admin(C.mob)]: [C.player_age] days old
        " + else + msg += "[key_name_mentor(C.mob)]: [C.player_age] days old
        " + + if(missing_ages) + to_chat(src, "Some accounts did not have proper ages set in their clients. This function requires database to be present") + + if(msg != "") + src << browse(msg, "window=Player_age_check") + else + to_chat(src, "No matches for that age range found.") + + +/client/proc/cmd_admin_world_narrate() // Allows administrators to fluff events a little easier -- TLE + set category = "Event" + set name = "Global Narrate" + + if(!check_rights(R_SERVER|R_EVENT)) + return + + var/msg = clean_input("Message:", text("Enter the text you wish to appear to everyone:")) + + if(!msg) + return + msg = admin_pencode_to_html(msg) + to_chat(world, "[msg]") + log_admin("GlobalNarrate: [key_name(usr)] : [msg]") + message_admins("GlobalNarrate: [key_name_admin(usr)]: [msg]
        ", 1) + feedback_add_details("admin_verb","GLN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_direct_narrate(var/mob/M) // Targetted narrate -- TLE + set category = "Event" + set name = "Direct Narrate" + + if(!check_rights(R_SERVER|R_EVENT)) + return + + if(!M) + M = input("Direct narrate to who?", "Active Players") as null|anything in get_mob_with_client_list() + + if(!M) + return + + var/msg = clean_input("Message:", text("Enter the text you wish to appear to your target:")) + + if( !msg ) + return + msg = admin_pencode_to_html(msg) + + to_chat(M, msg) + log_admin("DirectNarrate: [key_name(usr)] to ([key_name(M)]): [msg]") + message_admins("DirectNarrate: [key_name_admin(usr)] to ([key_name_admin(M)]): [msg]
        ", 1) + feedback_add_details("admin_verb","DIRN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + + + +/client/proc/cmd_admin_headset_message(mob/M in GLOB.mob_list) + set category = "Event" + set name = "Headset Message" + + admin_headset_message(M) + +/client/proc/admin_headset_message(mob/M in GLOB.mob_list, sender = null) + var/mob/living/carbon/human/H = M + + if(!check_rights(R_ADMIN)) + return + + if(!istype(H)) + to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human") + return + if(!istype(H.l_ear, /obj/item/radio/headset) && !istype(H.r_ear, /obj/item/radio/headset)) + to_chat(usr, "The person you are trying to contact is not wearing a headset") + return + + if(!sender) + sender = input("Who is the message from?", "Sender") as null|anything in list("Centcomm", "Syndicate") + if(!sender) + return + + message_admins("[key_name_admin(src)] has started answering [key_name_admin(H)]'s [sender] request.") + var/input = clean_input("Please enter a message to reply to [key_name(H)] via their headset.", "Outgoing message from [sender]", "") + if(!input) + message_admins("[key_name_admin(src)] decided not to answer [key_name_admin(H)]'s [sender] request.") + return + + log_admin("[key_name(src)] replied to [key_name(H)]'s [sender] message with the message [input].") + message_admins("[key_name_admin(src)] replied to [key_name_admin(H)]'s [sender] message with: \"[input]\"") + to_chat(H, "Incoming priority transmission from [sender == "Syndicate" ? "your benefactor" : "Central Command"]. Message as follows[sender == "Syndicate" ? ", agent." : ":"] [input]") + + + + +/client/proc/cmd_admin_godmode(mob/M as mob in GLOB.mob_list) + set category = "Admin" + set name = "Godmode" + + if(!check_rights(R_ADMIN)) + return + + M.status_flags ^= GODMODE + to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]") + + log_admin("[key_name(usr)] has toggled [key_name(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]") + message_admins("[key_name_admin(usr)] has toggled [key_name_admin(M)]'s nodamage to [(M.status_flags & GODMODE) ? "On" : "Off"]", 1) + feedback_add_details("admin_verb","GOD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +proc/cmd_admin_mute(mob/M as mob, mute_type, automute = 0) + if(automute) + if(!config.automute_on) + return + else + if(!usr || !usr.client) + return + if(!check_rights(R_ADMIN|R_MOD)) + to_chat(usr, "Error: cmd_admin_mute: You don't have permission to do this.") + return + if(!M.client) + to_chat(usr, "Error: cmd_admin_mute: This mob doesn't have a client tied to it.") + if(!M.client) + return + + var/muteunmute + var/mute_string + + switch(mute_type) + if(MUTE_IC) mute_string = "IC (say and emote)" + if(MUTE_OOC) mute_string = "OOC" + if(MUTE_PRAY) mute_string = "pray" + if(MUTE_ADMINHELP) mute_string = "adminhelp, admin PM and ASAY" + if(MUTE_DEADCHAT) mute_string = "deadchat and DSAY" + if(MUTE_ALL) mute_string = "everything" + else return + + if(automute) + muteunmute = "auto-muted" + M.client.prefs.muted |= mute_type + log_admin("SPAM AUTOMUTE: [muteunmute] [key_name(M)] from [mute_string]") + message_admins("SPAM AUTOMUTE: [muteunmute] [key_name_admin(M)] from [mute_string].", 1) + to_chat(M, "You have been [muteunmute] from [mute_string] by the SPAM AUTOMUTE system. Contact an admin.") + feedback_add_details("admin_verb","AUTOMUTE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + + if(M.client.prefs.muted & mute_type) + muteunmute = "unmuted" + M.client.prefs.muted &= ~mute_type + else + muteunmute = "muted" + M.client.prefs.muted |= mute_type + + log_admin("[key_name(usr)] has [muteunmute] [key_name(M)] from [mute_string]") + message_admins("[key_name_admin(usr)] has [muteunmute] [key_name_admin(M)] from [mute_string].", 1) + to_chat(M, "You have been [muteunmute] from [mute_string].") + feedback_add_details("admin_verb","MUTE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_add_random_ai_law() + set category = "Event" + set name = "Add Random AI Law" + + if(!check_rights(R_EVENT)) + return + + var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") + if(confirm != "Yes") return + log_admin("[key_name(src)] has added a random AI law.") + message_admins("[key_name_admin(src)] has added a random AI law.") + + var/show_log = alert(src, "Show ion message?", "Message", "Yes", "No") + var/announce_ion_laws = (show_log == "Yes" ? 1 : -1) + + new /datum/event/ion_storm(0, announce_ion_laws) + feedback_add_details("admin_verb","ION") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_antagHUD_use() + set category = "Server" + set name = "Toggle antagHUD usage" + set desc = "Toggles antagHUD usage for observers" + + if(!check_rights(R_SERVER)) + return + + var/action="" + if(config.antag_hud_allowed) + for(var/mob/dead/observer/g in get_ghosts()) + if(!g.client.holder) //Remove the verb from non-admin ghosts + g.verbs -= /mob/dead/observer/verb/toggle_antagHUD + if(g.antagHUD) + g.antagHUD = 0 // Disable it on those that have it enabled + g.has_enabled_antagHUD = 2 // We'll allow them to respawn + to_chat(g, "The Administrator has disabled AntagHUD ") + config.antag_hud_allowed = 0 + to_chat(src, "AntagHUD usage has been disabled") + action = "disabled" + else + for(var/mob/dead/observer/g in get_ghosts()) + if(!g.client.holder) // Add the verb back for all non-admin ghosts + g.verbs += /mob/dead/observer/verb/toggle_antagHUD + to_chat(g, "The Administrator has enabled AntagHUD ")// Notify all observers they can now use AntagHUD + + config.antag_hud_allowed = 1 + action = "enabled" + to_chat(src, "AntagHUD usage has been enabled") + + + log_admin("[key_name(usr)] has [action] antagHUD usage for observers") + message_admins("Admin [key_name_admin(usr)] has [action] antagHUD usage for observers", 1) + +/client/proc/toggle_antagHUD_restrictions() + set category = "Server" + set name = "Toggle antagHUD Restrictions" + set desc = "Restricts players that have used antagHUD from being able to join this round." + + if(!check_rights(R_SERVER)) + return + + var/action="" + if(config.antag_hud_restricted) + for(var/mob/dead/observer/g in get_ghosts()) + to_chat(g, "The administrator has lifted restrictions on joining the round if you use AntagHUD") + action = "lifted restrictions" + config.antag_hud_restricted = 0 + to_chat(src, "AntagHUD restrictions have been lifted") + else + for(var/mob/dead/observer/g in get_ghosts()) + to_chat(g, "The administrator has placed restrictions on joining the round if you use AntagHUD") + to_chat(g, "Your AntagHUD has been disabled, you may choose to re-enabled it but will be under restrictions ") + g.antagHUD = 0 + g.has_enabled_antagHUD = 0 + action = "placed restrictions" + config.antag_hud_restricted = 1 + to_chat(src, "AntagHUD restrictions have been enabled") + + log_admin("[key_name(usr)] has [action] on joining the round if they use AntagHUD") + message_admins("Admin [key_name_admin(usr)] has [action] on joining the round if they use AntagHUD", 1) + +/* +If a guy was gibbed and you want to revive him, this is a good way to do so. +Works kind of like entering the game with a new character. Character receives a new mind if they didn't have one. +Traitors and the like can also be revived with the previous role mostly intact. +/N */ +/client/proc/respawn_character() + set category = "Event" + set name = "Respawn Character" + set desc = "Respawn a person that has been gibbed/dusted/killed. They must be a ghost for this to work and preferably should not have a body to go back into." + + if(!check_rights(R_SPAWN)) + return + + var/input = ckey(input(src, "Please specify which key will be respawned.", "Key", "")) + if(!input) + return + + var/mob/dead/observer/G_found + for(var/mob/dead/observer/G in GLOB.player_list) + if(G.ckey == input) + G_found = G + break + + if(!G_found)//If a ghost was not found. + to_chat(usr, "There is no active key like that in the game or the person is not currently a ghost.") + return + + if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something + //Check if they were an alien + if(G_found.mind.assigned_role=="Alien") + if(alert("This character appears to have been an alien. Would you like to respawn them as such?",,"Yes","No")=="Yes") + var/turf/T + if(xeno_spawn.len) T = pick(xeno_spawn) + else T = pick(latejoin) + + var/mob/living/carbon/alien/new_xeno + switch(G_found.mind.special_role)//If they have a mind, we can determine which caste they were. + if("Hunter") new_xeno = new /mob/living/carbon/alien/humanoid/hunter(T) + if("Sentinel") new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(T) + if("Drone") new_xeno = new /mob/living/carbon/alien/humanoid/drone(T) + if("Queen") new_xeno = new /mob/living/carbon/alien/humanoid/queen(T) + else//If we don't know what special role they have, for whatever reason, or they're a larva. + create_xeno(G_found.ckey) + return + + //Now to give them their mind back. + G_found.mind.transfer_to(new_xeno) //be careful when doing stuff like this! I've already checked the mind isn't in use + new_xeno.key = G_found.key + to_chat(new_xeno, "You have been fully respawned. Enjoy the game.") + message_admins("[key_name_admin(usr)] has respawned [new_xeno.key] as a filthy xeno.", 1) + return //all done. The ghost is auto-deleted + + var/mob/living/carbon/human/new_character = new(pick(latejoin))//The mob being spawned. + + var/datum/data/record/record_found //Referenced to later to either randomize or not randomize the character. + if(G_found.mind && !G_found.mind.active) //mind isn't currently in use by someone/something + /*Try and locate a record for the person being respawned through data_core. + This isn't an exact science but it does the trick more often than not.*/ + var/id = md5("[G_found.real_name][G_found.mind.assigned_role]") + for(var/datum/data/record/t in data_core.locked) + if(t.fields["id"]==id) + record_found = t//We shall now reference the record. + break + + if(record_found)//If they have a record we can determine a few things. + new_character.real_name = record_found.fields["name"] + new_character.change_gender(record_found.fields["sex"]) + new_character.age = record_found.fields["age"] + new_character.dna.blood_type = record_found.fields["blood_type"] + else + new_character.change_gender(pick(MALE,FEMALE)) + var/datum/preferences/A = new() + A.real_name = G_found.real_name + A.copy_to(new_character) + + if(!new_character.real_name) + new_character.real_name = random_name(new_character.gender) + new_character.name = new_character.real_name + + if(G_found.mind && !G_found.mind.active) + G_found.mind.transfer_to(new_character) //be careful when doing stuff like this! I've already checked the mind isn't in use + new_character.mind.special_verbs = list() + else + new_character.mind_initialize() + if(!new_character.mind.assigned_role) new_character.mind.assigned_role = "Civilian"//If they somehow got a null assigned role. + + //DNA + if(record_found)//Pull up their name from database records if they did have a mind. + new_character.dna = new()//Let's first give them a new DNA. + new_character.dna.unique_enzymes = record_found.fields["b_dna"]//Enzymes are based on real name but we'll use the record for conformity. + + // I HATE BYOND. HATE. HATE. - N3X + var/list/newSE= record_found.fields["enzymes"] + var/list/newUI = record_found.fields["identity"] + new_character.dna.SE = newSE.Copy() //This is the default of enzymes so I think it's safe to go with. + new_character.dna.UpdateSE() + new_character.UpdateAppearance(newUI.Copy())//Now we configure their appearance based on their unique identity, same as with a DNA machine or somesuch. + else//If they have no records, we just do a random DNA for them, based on their random appearance/savefile. + new_character.dna.ready_dna(new_character) + + new_character.key = G_found.key + + /* + The code below functions with the assumption that the mob is already a traitor if they have a special role. + So all it does is re-equip the mob with powers and/or items. Or not, if they have no special role. + If they don't have a mind, they obviously don't have a special role. + */ + + //Now for special roles and equipment. + switch(new_character.mind.special_role) + if("traitor") + if(new_character.mind.has_antag_datum(/datum/antagonist/traitor)) + var/datum/antagonist/traitor/T = new_character.mind.has_antag_datum(/datum/antagonist/traitor) + T.equip_traitor(src) + else + new_character.mind.add_antag_datum(/datum/antagonist/traitor) + if("Wizard") + new_character.loc = pick(wizardstart) + //ticker.mode.learn_basic_spells(new_character) + SSticker.mode.equip_wizard(new_character) + if("Syndicate") + var/obj/effect/landmark/synd_spawn = locate("landmark*Syndicate-Spawn") + if(synd_spawn) + new_character.loc = get_turf(synd_spawn) + call(/datum/game_mode/proc/equip_syndicate)(new_character) + + if("Death Commando")//Leaves them at late-join spawn. + new_character.equip_death_commando() + new_character.internal = new_character.s_store + new_character.update_action_buttons_icon() + else//They may also be a cyborg or AI. + switch(new_character.mind.assigned_role) + if("Cyborg")//More rigging to make em' work and check if they're traitor. + new_character = new_character.Robotize() + if(new_character.mind.special_role=="traitor") + new_character.mind.add_antag_datum(/datum/antagonist/traitor) + if("AI") + new_character = new_character.AIize() + var/mob/living/silicon/ai/ai_character = new_character + ai_character.moveToAILandmark() + if(new_character.mind.special_role=="traitor") + new_character.mind.add_antag_datum(/datum/antagonist/traitor) + //Add aliens. + else + SSjobs.AssignRank(new_character, new_character.mind.assigned_role, 0) + SSjobs.EquipRank(new_character, new_character.mind.assigned_role, 1)//Or we simply equip them. + + //Announces the character on all the systems, based on the record. + if(!issilicon(new_character))//If they are not a cyborg/AI. + if(!record_found && new_character.mind.assigned_role != new_character.mind.special_role)//If there are no records for them. If they have a record, this info is already in there. Offstation special characters announced anyway. + //Power to the user! + if(alert(new_character,"Warning: No data core entry detected. Would you like to announce the arrival of this character by adding them to various databases, such as medical records?",,"No","Yes")=="Yes") + data_core.manifest_inject(new_character) + + if(alert(new_character,"Would you like an active AI to announce this character?",,"No","Yes")=="Yes") + call(/mob/new_player/proc/AnnounceArrival)(new_character, new_character.mind.assigned_role) + + message_admins("[key_name_admin(usr)] has respawned [key_name_admin(G_found)] as [new_character.real_name].", 1) + + to_chat(new_character, "You have been fully respawned. Enjoy the game.") + + feedback_add_details("admin_verb","RSPCH") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return new_character + +//I use this proc for respawn character too. /N +/proc/create_xeno(ckey) + if(!ckey) + var/list/candidates = list() + for(var/mob/M in GLOB.player_list) + if(M.stat != DEAD) continue //we are not dead! + if(!(ROLE_ALIEN in M.client.prefs.be_special)) continue //we don't want to be an alium + if(jobban_isbanned(M, "alien") || jobban_isbanned(M, "Syndicate")) continue //we are jobbanned + if(M.client.is_afk()) continue //we are afk + if(M.mind && M.mind.current && M.mind.current.stat != DEAD) continue //we have a live body we are tied to + candidates += M.ckey + if(candidates.len) + ckey = input("Pick the player you want to respawn as a xeno.", "Suitable Candidates") as null|anything in candidates + else + to_chat(usr, "Error: create_xeno(): no suitable candidates.") + if(!istext(ckey)) return 0 + + var/alien_caste = input(usr, "Please choose which caste to spawn.","Pick a caste",null) as null|anything in list("Queen","Hunter","Sentinel","Drone","Larva") + var/obj/effect/landmark/spawn_here = xeno_spawn.len ? pick(xeno_spawn) : pick(latejoin) + var/mob/living/carbon/alien/new_xeno + switch(alien_caste) + if("Queen") new_xeno = new /mob/living/carbon/alien/humanoid/queen/large(spawn_here) + if("Hunter") new_xeno = new /mob/living/carbon/alien/humanoid/hunter(spawn_here) + if("Sentinel") new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(spawn_here) + if("Drone") new_xeno = new /mob/living/carbon/alien/humanoid/drone(spawn_here) + if("Larva") new_xeno = new /mob/living/carbon/alien/larva(spawn_here) + else return 0 + + new_xeno.ckey = ckey + message_admins("[key_name_admin(usr)] has spawned [ckey] as a filthy xeno [alien_caste].", 1) + return 1 + + +/client/proc/get_ghosts(var/notify = 0,var/what = 2) + // what = 1, return ghosts ass list. + // what = 2, return mob list + + var/list/mobs = list() + var/list/ghosts = list() + var/list/sortmob = sortAtom(GLOB.mob_list) // get the mob list. + /var/any=0 + for(var/mob/dead/observer/M in sortmob) + mobs.Add(M) //filter it where it's only ghosts + any = 1 //if no ghosts show up, any will just be 0 + if(!any) + if(notify) + to_chat(src, "There doesn't appear to be any ghosts for you to select.") + return + + for(var/mob/M in mobs) + var/name = M.name + ghosts[name] = M //get the name of the mob for the popup list + if(what==1) + return ghosts + else + return mobs + +/client/proc/cmd_admin_add_freeform_ai_law() + set category = "Event" + set name = "Add Custom AI law" + + if(!check_rights(R_EVENT)) + return + + var/input = clean_input("Please enter anything you want the AI to do. Anything. Serious.", "What?", "") + if(!input) + return + + log_admin("Admin [key_name(usr)] has added a new AI law - [input]") + message_admins("Admin [key_name_admin(usr)] has added a new AI law - [input]") + + var/show_log = alert(src, "Show ion message?", "Message", "Yes", "No") + var/announce_ion_laws = (show_log == "Yes" ? 1 : -1) + + new /datum/event/ion_storm(0, announce_ion_laws, input) + + feedback_add_details("admin_verb","IONC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_rejuvenate(mob/living/M as mob in GLOB.mob_list) + set category = "Event" + set name = "Rejuvenate" + + if(!check_rights(R_REJUVINATE)) + return + + if(!mob) + return + if(!istype(M)) + alert("Cannot revive a ghost") + return + M.revive() + + log_admin("[key_name(usr)] healed / revived [key_name(M)]") + message_admins("Admin [key_name_admin(usr)] healed / revived [key_name_admin(M)]!", 1) + feedback_add_details("admin_verb","REJU") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_create_centcom_report() + set category = "Event" + set name = "Create Communications Report" + + if(!check_rights(R_SERVER|R_EVENT)) + return + +//the stuff on the list is |"report type" = "report title"|, if that makes any sense + var/list/MsgType = list("Central Command Report" = "Nanotrasen Update", + "Syndicate Communique" = "Syndicate Message", + "Space Wizard Federation Message" = "Sorcerous Message", + "Enemy Communications" = "Unknown Message", + "Custom" = "Cryptic Message") + + var/list/MsgSound = list("Beep" = 'sound/misc/notice2.ogg', + "Enemy Communications Intercepted" = 'sound/AI/intercept2.ogg', + "New Command Report Created" = 'sound/AI/commandreport.ogg') + + var/type = input(usr, "Pick a type of report to send", "Report Type", "") as anything in MsgType + + if(type == "Custom") + type = clean_input("What would you like the report type to be?", "Report Type", "Encrypted Transmission") + + var/customname = input(usr, "Pick a title for the report.", "Title", MsgType[type]) as text|null + if(!customname) + return + var/input = input(usr, "Please enter anything you want. Anything. Serious.", "What's the message?") as message|null + if(!input) + return + + switch(alert("Should this be announced to the general population?",,"Yes","No", "Cancel")) + if("Yes") + var/beepsound = input(usr, "What sound should the announcement make?", "Announcement Sound", "") as anything in MsgSound + + command_announcement.Announce(input, customname, MsgSound[beepsound], , , type) + print_command_report(input, "[command_name()] Update") + if("No") + //same thing as the blob stuff - it's not public, so it's classified, dammit + command_announcer.autosay("A classified message has been printed out at all communication consoles."); + print_command_report(input, "Classified [command_name()] Update") + else + return + + log_admin("[key_name(src)] has created a communications report: [input]") + message_admins("[key_name_admin(src)] has created a communications report", 1) + feedback_add_details("admin_verb","CCR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + +/client/proc/cmd_admin_delete(atom/A as obj|mob|turf in view()) + set category = "Admin" + set name = "Delete" + + if(!check_rights(R_ADMIN)) + return + + admin_delete(A) + +/client/proc/admin_delete(datum/D) + if(istype(D) && !D.can_vv_delete()) + to_chat(src, "[D] rejected your deletion") + return + var/atom/A = D + var/coords = istype(A) ? "at ([A.x], [A.y], [A.z])" : "" + if(alert(src, "Are you sure you want to delete:\n[D]\n[coords]?", "Confirmation", "Yes", "No") == "Yes") + log_admin("[key_name(usr)] deleted [D] [coords]") + message_admins("[key_name_admin(usr)] deleted [D] [coords]", 1) + feedback_add_details("admin_verb","DEL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + if(isturf(D)) + var/turf/T = D + T.ChangeTurf(T.baseturf) + else + qdel(D) + +/client/proc/cmd_admin_list_open_jobs() + set category = "Admin" + set name = "List free slots" + + if(!check_rights(R_ADMIN)) + return + + if(SSjobs) + var/currentpositiontally + var/totalpositiontally + to_chat(src, "Job Name: Filled job slot / Total job slots (Free job slots)") + for(var/datum/job/job in SSjobs.occupations) + to_chat(src, "[job.title]: [job.current_positions] / \ + [job.total_positions == -1 ? "UNLIMITED" : job.total_positions] \ + ([job.total_positions == -1 ? "UNLIMITED" : job.total_positions - job.current_positions])") + if(job.total_positions != -1) // Only count position that isn't unlimited + currentpositiontally += job.current_positions + totalpositiontally += job.total_positions + to_chat(src, "Currently filled job slots (Excluding unlimited): [currentpositiontally] / [totalpositiontally] ([totalpositiontally - currentpositiontally])") + feedback_add_details("admin_verb","LFS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_explosion(atom/O as obj|mob|turf in view()) + set category = "Event" + set name = "Explosion" + + if(!check_rights(R_DEBUG|R_EVENT)) + return + + var/devastation = input("Range of total devastation. -1 to none", text("Input")) as num|null + if(devastation == null) return + var/heavy = input("Range of heavy impact. -1 to none", text("Input")) as num|null + if(heavy == null) return + var/light = input("Range of light impact. -1 to none", text("Input")) as num|null + if(light == null) return + var/flash = input("Range of flash. -1 to none", text("Input")) as num|null + if(flash == null) return + var/flames = input("Range of flames. -1 to none", text("Input")) as num|null + if(flames == null) return + + if((devastation != -1) || (heavy != -1) || (light != -1) || (flash != -1) || (flames != -1)) + if((devastation > 20) || (heavy > 20) || (light > 20) || (flames > 20)) + if(alert(src, "Are you sure you want to do this? It will laaag.", "Confirmation", "Yes", "No") == "No") + return + + explosion(O, devastation, heavy, light, flash, null, null,flames) + log_admin("[key_name(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at ([O.x],[O.y],[O.z])") + message_admins("[key_name_admin(usr)] created an explosion ([devastation],[heavy],[light],[flames]) at ([O.x],[O.y],[O.z])") + feedback_add_details("admin_verb","EXPL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + else + return + +/client/proc/cmd_admin_emp(atom/O as obj|mob|turf in view()) + set category = "Event" + set name = "EM Pulse" + + if(!check_rights(R_DEBUG|R_EVENT)) + return + + var/heavy = input("Range of heavy pulse.", text("Input")) as num|null + if(heavy == null) return + var/light = input("Range of light pulse.", text("Input")) as num|null + if(light == null) return + + if(heavy || light) + + empulse(O, heavy, light) + log_admin("[key_name(usr)] created an EM pulse ([heavy], [light]) at ([O.x],[O.y],[O.z])") + message_admins("[key_name_admin(usr)] created an EM pulse ([heavy], [light]) at ([O.x],[O.y],[O.z])", 1) + feedback_add_details("admin_verb","EMP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + return + else + return + +/client/proc/cmd_admin_gib(mob/M as mob in GLOB.mob_list) + set category = "Admin" + set name = "Gib" + + if(!check_rights(R_ADMIN|R_EVENT)) + return + + var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") + if(confirm != "Yes") return + //Due to the delay here its easy for something to have happened to the mob + if(!M) return + + log_admin("[key_name(usr)] has gibbed [key_name(M)]") + message_admins("[key_name_admin(usr)] has gibbed [key_name_admin(M)]", 1) + + if(istype(M, /mob/dead/observer)) + gibs(M.loc) + return + + M.gib() + feedback_add_details("admin_verb","GIB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_gib_self() + set name = "Gibself" + set category = "Event" + + if(!check_rights(R_ADMIN|R_EVENT)) + return + + var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") + if(confirm == "Yes") + if(istype(mob, /mob/dead/observer)) // so they don't spam gibs everywhere + return + else + mob.gib() + + log_admin("[key_name(usr)] used gibself.") + message_admins("[key_name_admin(usr)] used gibself.", 1) + feedback_add_details("admin_verb","GIBS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/cmd_admin_check_contents(mob/living/M as mob in GLOB.mob_list) + set name = "Check Contents" + set category = null + + if(!check_rights(R_ADMIN)) + return + + var/list/L = M.get_contents() + for(var/t in L) + to_chat(usr, "[t]") + feedback_add_details("admin_verb","CC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_view_range() + set category = "Admin" + set name = "Change View Range" + set desc = "switches between 1x and custom views" + + if(!check_rights(R_ADMIN)) + return + + if(view == world.view) + view = input("Select view range:", "View Range", world.view) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128) + else + view = world.view + + log_admin("[key_name(usr)] changed their view range to [view].") + //message_admins("[key_name_admin(usr)] changed their view range to [view].", 1) //why? removed by order of XSI + + feedback_add_details("admin_verb","CVRA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/admin_call_shuttle() + + set category = "Admin" + set name = "Call Shuttle" + + if(SSshuttle.emergency.mode >= SHUTTLE_DOCKED) + return + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "You sure?", "Confirm", "Yes", "No") + if(confirm != "Yes") return + + if(alert("Set Shuttle Recallable (Select Yes unless you know what this does)", "Recallable?", "Yes", "No") == "Yes") + SSshuttle.emergency.canRecall = TRUE + else + SSshuttle.emergency.canRecall = FALSE + + SSshuttle.emergency.request() + + feedback_add_details("admin_verb","CSHUT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] admin-called the emergency shuttle.") + message_admins("[key_name_admin(usr)] admin-called the emergency shuttle.") + return + +/client/proc/admin_cancel_shuttle() + set category = "Admin" + set name = "Cancel Shuttle" + + if(!check_rights(R_ADMIN)) + return + if(alert(src, "You sure?", "Confirm", "Yes", "No") != "Yes") return + + if(SSshuttle.emergency.mode >= SHUTTLE_DOCKED) + return + + if(SSshuttle.emergency.canRecall == FALSE) + if(alert("Shuttle is currently set to be nonrecallable. Recalling may break things. Respect Recall Status?", "Override Recall Status?", "Yes", "No") == "Yes") + return + else + var/keepStatus = alert("Maintain recall status on future shuttle calls?", "Maintain Status?", "Yes", "No") == "Yes" //Keeps or drops recallability + SSshuttle.emergency.canRecall = TRUE // must be true for cancel proc to work + SSshuttle.emergency.cancel() + if(keepStatus) + SSshuttle.emergency.canRecall = FALSE // restores original status + else + SSshuttle.emergency.cancel() + + feedback_add_details("admin_verb","CCSHUT") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + log_admin("[key_name(usr)] admin-recalled the emergency shuttle.") + message_admins("[key_name_admin(usr)] admin-recalled the emergency shuttle.") + return + +/client/proc/admin_deny_shuttle() + set category = "Admin" + set name = "Toggle Deny Shuttle" + + if(!SSticker) + return + + if(!check_rights(R_ADMIN)) + return + + if(SSshuttle) + SSshuttle.emergencyNoEscape = !SSshuttle.emergencyNoEscape + + log_admin("[key_name(src)] has [SSshuttle.emergencyNoEscape ? "denied" : "allowed"] the shuttle to be called.") + message_admins("[key_name_admin(usr)] has [SSshuttle.emergencyNoEscape ? "denied" : "allowed"] the shuttle to be called.") + +/client/proc/cmd_admin_attack_log(mob/M as mob in GLOB.mob_list) + set category = "Admin" + set name = "Attack Log" + + if(!check_rights(R_ADMIN)) + return + + to_chat(usr, text("Attack Log for []", mob)) + for(var/t in M.attack_log) + 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! + + +/client/proc/everyone_random() + set category = "Event" + set name = "Make Everyone Random" + set desc = "Make everyone have a random appearance. You can only use this before rounds!" + + if(!check_rights(R_SERVER|R_EVENT)) + return + + if(SSticker && SSticker.mode) + to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!") + return + + if(SSticker.random_players) + SSticker.random_players = 0 + message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.", 1) + to_chat(usr, "Disabled.") + return + + + var/notifyplayers = alert(src, "Do you want to notify the players?", "Options", "Yes", "No", "Cancel") + if(notifyplayers == "Cancel") + return + + log_admin("Admin [key_name(src)] has forced the players to have random appearances.") + message_admins("Admin [key_name_admin(usr)] has forced the players to have random appearances.", 1) + + if(notifyplayers == "Yes") + to_chat(world, "Admin [usr.key] has forced the players to have completely random identities!") + + to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.") + + SSticker.random_players = 1 + feedback_add_details("admin_verb","MER") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/toggle_random_events() + set category = "Event" + set name = "Toggle random events on/off" + + set desc = "Toggles random events such as meteors, black holes, blob (but not space dust) on/off" + if(!check_rights(R_SERVER|R_EVENT)) + return + + if(!config.allow_random_events) + config.allow_random_events = 1 + to_chat(usr, "Random events enabled") + message_admins("Admin [key_name_admin(usr)] has enabled random events.", 1) + else + config.allow_random_events = 0 + to_chat(usr, "Random events disabled") + message_admins("Admin [key_name_admin(usr)] has disabled random events.", 1) + feedback_add_details("admin_verb","TRE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/reset_all_tcs() + set category = "Admin" + set name = "Reset NTTC Configuration" + set desc = "Resets NTTC to the default configuration." + + if(!check_rights(R_ADMIN)) + return + + var/confirm = alert(src, "You sure you want to reset NTTC?", "Confirm", "Yes", "No") + if(confirm != "Yes") + return + + GLOB.nttc_config.reset() + log_admin("[key_name(usr)] reset NTTC scripts.") + message_admins("[key_name_admin(usr)] reset NTTC scripts.") + feedback_add_details("admin_verb","RAT2") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/list_ssds_afks() + set category = "Admin" + set name = "List SSDs and AFKs" + set desc = "Lists SSD and AFK players" + + if(!check_rights(R_ADMIN)) + return + + /* ======== SSD Section ========= */ + var/msg = "SSD & AFK Report" + msg += "SSD Players:
        " + msg += "" + var/mins_ssd + var/job_string + var/key_string + var/role_string + var/obj_count = 0 + var/obj_string = "" + for(var/mob/living/carbon/human/H in GLOB.living_mob_list) + if(!isLivingSSD(H)) + continue + mins_ssd = round((world.time - H.last_logout) / 600) + if(H.job) + job_string = H.job + else + job_string = "-" + key_string = H.key + if(job_string in command_positions) + job_string = "" + job_string + "" + role_string = "-" + obj_count = 0 + obj_string = "" + if(H.mind) + if(H.mind.special_role) + role_string = "[H.mind.special_role]" + if(!H.key && H.mind.key) + key_string = H.mind.key + for(var/datum/objective/O in GLOB.all_objectives) + if(O.target == H.mind) + obj_count++ + if(obj_count > 0) + obj_string = "
        Obj Target" + msg += "" + msg += "" + if(istype(H.loc, /obj/machinery/cryopod)) + msg += "" + else + msg += "" + msg += "" + msg += "
        KeyReal NameJobMins SSDSpecial RoleAreaPPNCryo
        [key_string][H.real_name][job_string][mins_ssd][role_string][obj_string][get_area(H)][ADMIN_PP(H,"PP")]De-SpawnCryo

        " + + /* ======== AFK Section ========= */ + msg += "AFK Players:
        " + msg += "" + var/mins_afk + for(var/mob/living/carbon/human/H in GLOB.living_mob_list) + if(H.client == null || H.stat == DEAD) // No clientless or dead + continue + mins_afk = round(H.client.inactivity / 600) + if(mins_afk < config.list_afk_minimum) + continue + if(H.job) + job_string = H.job + else + job_string = "-" + key_string = H.key + if(job_string in command_positions) + job_string = "" + job_string + "" + role_string = "-" + obj_count = 0 + obj_string = "" + if(H.mind) + if(H.mind.special_role) + role_string = "[H.mind.special_role]" + if(!H.key && H.mind.key) + key_string = H.mind.key + for(var/datum/objective/O in GLOB.all_objectives) + if(O.target == H.mind) + obj_count++ + if(obj_count > 0) + obj_string = "
        Obj Target" + msg += "" + msg += "" + if(istype(H.loc, /obj/machinery/cryopod)) + msg += "" + else + msg += "" + msg += "" + msg += "
        KeyReal NameJobMins AFKSpecial RoleAreaPPNCryo
        [key_string][H.real_name][job_string][mins_afk][role_string][obj_string][get_area(H)][ADMIN_PP(H,"PP")]De-SpawnCryo
        " + src << browse(msg, "window=Player_ssd_afk_check;size=600x300") + +/client/proc/toggle_ert_calling() + set category = "Event" + set name = "Toggle ERT" + + set desc = "Toggle the station's ability to call a response team." + if(!check_rights(R_EVENT)) + return + + if(SSticker.mode.ert_disabled) + SSticker.mode.ert_disabled = 0 + to_chat(usr, "ERT has been Enabled.") + log_admin("Admin [key_name(src)] has enabled ERT calling.") + message_admins("Admin [key_name_admin(usr)] has enabled ERT calling.", 1) + else + SSticker.mode.ert_disabled = 1 + to_chat(usr, "ERT has been Disabled.") + log_admin("Admin [key_name(src)] has disabled ERT calling.") + message_admins("Admin [key_name_admin(usr)] has disabled ERT calling.", 1) + +/client/proc/show_tip() + set category = "Admin" + set name = "Show Custom Tip" + set desc = "Sends a tip (that you specify) to all players. After all \ + you're the experienced player here." + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Please specify your tip that you want to send to the players.", "Tip", "") as message|null + if(!input) + return + + if(!SSticker) + return + + SSticker.selected_tip = input + + // If we've already tipped, then send it straight away. + if(SSticker.tipped) + SSticker.send_tip_of_the_round() + + message_admins("[key_name_admin(usr)] sent a Tip of the round.") + log_admin("[key_name(usr)] sent \"[input]\" as the Tip of the Round.") + +/client/proc/modify_goals() + set category = "Event" + set name = "Modify Station Goals" + + if(!check_rights(R_EVENT)) + return + + holder.modify_goals() + +/datum/admins/proc/modify_goals() + if(!SSticker || !SSticker.mode) + to_chat(usr, "This verb can only be used if the round has started.") + return + + var/dat = "" + for(var/datum/station_goal/S in SSticker.mode.station_goals) + dat += "[S.name] - Announce | Remove
        " + dat += "
        Add New Goal" + usr << browse(dat, "window=goals;size=400x400") + +/// Allow admin to add or remove traits of datum +/datum/admins/proc/modify_traits(datum/D) + if(!D) + return + + var/add_or_remove = input("Remove/Add?", "Trait Remove/Add") as null|anything in list("Add","Remove") + if(!add_or_remove) + return + var/list/availible_traits = list() + + switch(add_or_remove) + if("Add") + for(var/key in GLOB.traits_by_type) + if(istype(D,key)) + availible_traits += GLOB.traits_by_type[key] + if("Remove") + if(!GLOB.trait_name_map) + GLOB.trait_name_map = generate_trait_name_map() + for(var/trait in D.status_traits) + var/name = GLOB.trait_name_map[trait] || trait + availible_traits[name] = trait + + var/chosen_trait = input("Select trait to modify", "Trait") as null|anything in availible_traits + if(!chosen_trait) + return + chosen_trait = availible_traits[chosen_trait] + + var/source = "adminabuse" + switch(add_or_remove) + if("Add") //Not doing source choosing here intentionally to make this bit faster to use, you can always vv it. + ADD_TRAIT(D, chosen_trait, source) + if("Remove") + var/specific = input("All or specific source ?", "Trait Remove/Add") as null|anything in list("All","Specific") + if(!specific) + return + switch(specific) + if("All") + source = null + if("Specific") + source = input("Source to be removed","Trait Remove/Add") as null|anything in D.status_traits[chosen_trait] + if(!source) + return + REMOVE_TRAIT(D, chosen_trait, source) diff --git a/code/modules/admin/verbs/serialization.dm b/code/modules/admin/verbs/serialization.dm index 1c087b9e8fc..6443363f1d0 100644 --- a/code/modules/admin/verbs/serialization.dm +++ b/code/modules/admin/verbs/serialization.dm @@ -23,4 +23,4 @@ var/json_text = input("Enter the JSON code:","Text") as message|null if(json_text) - json_to_object(json_text, get_turf(usr)) \ No newline at end of file + json_to_object(json_text, get_turf(usr)) diff --git a/code/modules/admin/verbs/striketeam.dm b/code/modules/admin/verbs/striketeam.dm index 5e7f88a80d7..a040d1cc8c3 100644 --- a/code/modules/admin/verbs/striketeam.dm +++ b/code/modules/admin/verbs/striketeam.dm @@ -1,204 +1,204 @@ -//STRIKE TEAMS - -#define COMMANDOS_POSSIBLE 6 //if more Commandos are needed in the future -var/global/sent_strike_team = 0 - -/client/proc/strike_team() - if(!SSticker) - to_chat(usr, "The game hasn't started yet!") - return - if(sent_strike_team == 1) - to_chat(usr, "CentComm is already sending a team.") - return - if(alert("Do you want to send in the CentComm death squad? Once enabled, this is irreversible.",,"Yes","No")!="Yes") - return - alert("This 'mode' will go on until everyone is dead or the station is destroyed. You may also admin-call the evac shuttle when appropriate. Spawned commandos have internals cameras which are viewable through a monitor inside the Spec. Ops. Office. The first one selected/spawned will be the team leader.") - - message_admins("[key_name_admin(usr)] has started to spawn a CentComm DeathSquad.", 1) - - var/input = null - while(!input) - input = sanitize(copytext(input(src, "Please specify which mission the death commando squad shall undertake.", "Specify Mission", ""),1,MAX_MESSAGE_LEN)) - if(!input) - if(alert("Error, no mission set. Do you want to exit the setup process?",,"Yes","No")=="Yes") - return - - if(sent_strike_team) - to_chat(usr, "Looks like someone beat you to it.") - return - - // Find the nuclear auth code - var/nuke_code - var/temp_code - for(var/obj/machinery/nuclearbomb/N in world) - temp_code = text2num(N.r_code) - if(temp_code)//if it's actually a number. It won't convert any non-numericals. - nuke_code = N.r_code - break - - // Find ghosts willing to be DS - var/list/commando_ghosts = pollCandidatesWithVeto(src, usr, COMMANDOS_POSSIBLE, "Join the DeathSquad?",, 21, 600, 1, role_playtime_requirements[ROLE_DEATHSQUAD], TRUE, FALSE) - if(!commando_ghosts.len) - to_chat(usr, "Nobody volunteered to join the DeathSquad.") - return - - sent_strike_team = 1 - - // Spawns commandos and equips them. - var/commando_number = COMMANDOS_POSSIBLE //for selecting a leader - var/is_leader = TRUE // set to FALSE after leader is spawned - - for(var/obj/effect/landmark/L in GLOB.landmarks_list) - - if(commando_number <= 0) - break - - if(L.name == "Commando") - - if(!commando_ghosts.len) - break - - var/use_ds_borg = FALSE - var/mob/ghost_mob = pick(commando_ghosts) - commando_ghosts -= ghost_mob - if(!ghost_mob || !ghost_mob.key || !ghost_mob.client) - continue - - if(!is_leader) - var/new_dstype = alert(ghost_mob.client, "Select Deathsquad Type.", "DS Character Generation", "Organic", "Cyborg") - if(new_dstype == "Cyborg") - use_ds_borg = TRUE - - if(!ghost_mob || !ghost_mob.key || !ghost_mob.client) // Have to re-check this due to the above alert() call - continue - - if(use_ds_borg) - var/mob/living/silicon/robot/deathsquad/R = new() - R.forceMove(get_turf(L)) - var/rnum = rand(1,1000) - var/borgname = "Epsilon [rnum]" - R.name = borgname - R.custom_name = borgname - R.real_name = R.name - R.mind = new - R.mind.current = R - R.mind.original = R - R.mind.assigned_role = SPECIAL_ROLE_DEATHSQUAD - R.mind.special_role = SPECIAL_ROLE_DEATHSQUAD - R.mind.offstation_role = TRUE - if(!(R.mind in SSticker.minds)) - SSticker.minds += R.mind - SSticker.mode.traitors += R.mind - R.key = ghost_mob.key - if(nuke_code) - R.mind.store_memory("Nuke Code: [nuke_code].") - R.mind.store_memory("Mission: [input].") - to_chat(R, "You are a Special Operations cyborg, in the service of Central Command. \nYour current mission is: [input]") - else - var/mob/living/carbon/human/new_commando = create_death_commando(L, is_leader) - new_commando.mind.key = ghost_mob.key - new_commando.key = ghost_mob.key - new_commando.internal = new_commando.s_store - new_commando.update_action_buttons_icon() - if(nuke_code) - new_commando.mind.store_memory("Nuke Code: [nuke_code].") - new_commando.mind.store_memory("Mission: [input].") - to_chat(new_commando, "You are a Special Ops [is_leader ? "TEAM LEADER" : "commando"] in the service of Central Command. Check the table ahead for detailed instructions.\nYour current mission is: [input]") - - is_leader = FALSE - commando_number-- - - //Spawns the rest of the commando gear. - for(var/obj/effect/landmark/L in GLOB.landmarks_list) - if(L.name == "Commando_Manual") - //new /obj/item/gun/energy/pulse_rifle(L.loc) - var/obj/item/paper/P = new(L.loc) - P.info = "

        Good morning soldier!. This compact guide will familiarize you with standard operating procedure. There are three basic rules to follow:
        #1 Work as a team.
        #2 Accomplish your objective at all costs.
        #3 Leave no witnesses.
        You are fully equipped and stocked for your mission--before departing on the Spec. Ops. Shuttle due South, make sure that all operatives are ready. Actual mission objective will be relayed to you by Central Command through your headsets.
        If deemed appropriate, Central Command will also allow members of your team to equip assault power-armor for the mission. You will find the armor storage due West of your position. Once you are ready to leave, utilize the Special Operations shuttle console and toggle the hull doors via the other console.

        In the event that the team does not accomplish their assigned objective in a timely manner, or finds no other way to do so, attached below are instructions on how to operate a Nanotrasen Nuclear Device. Your operations LEADER is provided with a nuclear authentication disk and a pin-pointer for this reason. You may easily recognize them by their rank: Lieutenant, Captain, or Major. The nuclear device itself will be present somewhere on your destination.

        Hello and thank you for choosing Nanotrasen for your nuclear information needs. Today's crash course will deal with the operation of a Fission Class Nanotrasen made Nuclear Device.
        First and foremost, DO NOT TOUCH ANYTHING UNTIL THE BOMB IS IN PLACE. Pressing any button on the compacted bomb will cause it to extend and bolt itself into place. If this is done to unbolt it one must completely log in which at this time may not be possible.
        To make the device functional:
        #1 Place bomb in designated detonation zone
        #2 Extend and anchor bomb (attack with hand).
        #3 Insert Nuclear Auth. Disk into slot.
        #4 Type numeric code into keypad ([nuke_code]).
        Note: If you make a mistake press R to reset the device.
        #5 Press the E button to log onto the device.
        You now have activated the device. To deactivate the buttons at anytime, for example when you have already prepped the bomb for detonation, remove the authentication disk OR press the R on the keypad. Now the bomb CAN ONLY be detonated using the timer. A manual detonation is not an option.
        Note: Toggle off the SAFETY.
        Use the - - and + + to set a detonation time between 5 seconds and 10 minutes. Then press the timer toggle button to start the countdown. Now remove the authentication disk so that the buttons deactivate.
        Note: THE BOMB IS STILL SET AND WILL DETONATE
        Now before you remove the disk if you need to move the bomb you can: Toggle off the anchor, move it, and re-anchor.

        The nuclear authorization code is: [nuke_code ? nuke_code : "None provided"]

        Good luck, soldier!

        " - P.name = "Spec. Ops Manual" - P.icon = "pamphlet-ds" - var/obj/item/stamp/centcom/stamp = new - P.stamp(stamp) - qdel(stamp) - - for(var/obj/effect/landmark/L in GLOB.landmarks_list) - if(L.name == "Commando-Bomb") - new /obj/effect/spawner/newbomb/timer/syndicate(L.loc) - qdel(L) - - message_admins("[key_name_admin(usr)] has spawned a CentComm DeathSquad.", 1) - log_admin("[key_name(usr)] used Spawn Death Squad.") - return 1 - -/client/proc/create_death_commando(obj/spawn_location, is_leader = FALSE) - var/mob/living/carbon/human/new_commando = new(spawn_location.loc) - var/commando_leader_rank = pick("Lieutenant", "Captain", "Major") - var/commando_rank = pick("Corporal", "Sergeant", "Staff Sergeant", "Sergeant 1st Class", "Master Sergeant", "Sergeant Major") - var/commando_name = pick(GLOB.last_names) - - var/datum/preferences/A = new()//Randomize appearance for the commando. - if(is_leader) - A.age = rand(35,45) - A.real_name = "[commando_leader_rank] [commando_name]" - else - A.real_name = "[commando_rank] [commando_name]" - A.copy_to(new_commando) - - - new_commando.dna.ready_dna(new_commando)//Creates DNA. - - //Creates mind stuff. - new_commando.mind_initialize() - new_commando.mind.assigned_role = SPECIAL_ROLE_DEATHSQUAD - new_commando.mind.special_role = SPECIAL_ROLE_DEATHSQUAD - SSticker.mode.traitors |= new_commando.mind//Adds them to current traitor list. Which is really the extra antagonist list. - new_commando.equip_death_commando(is_leader) - return new_commando - -/mob/living/carbon/human/proc/equip_death_commando(is_leader = FALSE) - - var/obj/item/radio/R = new /obj/item/radio/headset/alt(src) - R.set_frequency(DTH_FREQ) - equip_to_slot_or_del(R, slot_l_ear) - if(is_leader) - equip_to_slot_or_del(new /obj/item/clothing/under/rank/centcom_officer(src), slot_w_uniform) - else - equip_to_slot_or_del(new /obj/item/clothing/under/color/green(src), slot_w_uniform) - equip_to_slot_or_del(new /obj/item/clothing/shoes/magboots/advance(src), slot_shoes) - equip_to_slot_or_del(new /obj/item/clothing/suit/space/deathsquad(src), slot_wear_suit) - equip_to_slot_or_del(new /obj/item/clothing/gloves/combat(src), slot_gloves) - equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/deathsquad(src), slot_head) - equip_to_slot_or_del(new /obj/item/clothing/mask/gas/sechailer/swat(src), slot_wear_mask) - equip_to_slot_or_del(new /obj/item/clothing/glasses/thermal(src), slot_glasses) - - equip_to_slot_or_del(new /obj/item/storage/backpack/security(src), slot_back) - equip_to_slot_or_del(new /obj/item/storage/box(src), slot_in_backpack) - - equip_to_slot_or_del(new /obj/item/ammo_box/a357(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/reagent_containers/hypospray/combat/nanites(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/storage/box/flashbangs(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/flashlight(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/pinpointer(src), slot_in_backpack) - if(is_leader) - equip_to_slot_or_del(new /obj/item/disk/nuclear/unrestricted(src), slot_in_backpack) - else - equip_to_slot_or_del(new /obj/item/grenade/plastic/x4(src), slot_in_backpack) - - - equip_to_slot_or_del(new /obj/item/melee/energy/sword/saber(src), slot_l_store) - equip_to_slot_or_del(new /obj/item/shield/energy(src), slot_r_store) - equip_to_slot_or_del(new /obj/item/tank/emergency_oxygen/double/full(src), slot_s_store) - equip_to_slot_or_del(new /obj/item/gun/projectile/revolver/mateba(src), slot_belt) - equip_to_slot_or_del(new /obj/item/gun/energy/pulse(src), slot_r_hand) - - var/obj/item/implant/mindshield/L = new/obj/item/implant/mindshield(src) - L.implant(src) - - var/obj/item/card/id/W = new(src) - W.name = "[real_name]'s ID Card" - W.icon_state = "deathsquad" - W.assignment = "Death Commando" - W.access = get_centcom_access(W.assignment) - W.registered_name = real_name - equip_to_slot_or_del(W, slot_wear_id) - - return 1 +//STRIKE TEAMS + +#define COMMANDOS_POSSIBLE 6 //if more Commandos are needed in the future +var/global/sent_strike_team = 0 + +/client/proc/strike_team() + if(!SSticker) + to_chat(usr, "The game hasn't started yet!") + return + if(sent_strike_team == 1) + to_chat(usr, "CentComm is already sending a team.") + return + if(alert("Do you want to send in the CentComm death squad? Once enabled, this is irreversible.",,"Yes","No")!="Yes") + return + alert("This 'mode' will go on until everyone is dead or the station is destroyed. You may also admin-call the evac shuttle when appropriate. Spawned commandos have internals cameras which are viewable through a monitor inside the Spec. Ops. Office. The first one selected/spawned will be the team leader.") + + message_admins("[key_name_admin(usr)] has started to spawn a CentComm DeathSquad.", 1) + + var/input = null + while(!input) + input = sanitize(copytext(input(src, "Please specify which mission the death commando squad shall undertake.", "Specify Mission", ""),1,MAX_MESSAGE_LEN)) + if(!input) + if(alert("Error, no mission set. Do you want to exit the setup process?",,"Yes","No")=="Yes") + return + + if(sent_strike_team) + to_chat(usr, "Looks like someone beat you to it.") + return + + // Find the nuclear auth code + var/nuke_code + var/temp_code + for(var/obj/machinery/nuclearbomb/N in world) + temp_code = text2num(N.r_code) + if(temp_code)//if it's actually a number. It won't convert any non-numericals. + nuke_code = N.r_code + break + + // Find ghosts willing to be DS + var/list/commando_ghosts = pollCandidatesWithVeto(src, usr, COMMANDOS_POSSIBLE, "Join the DeathSquad?",, 21, 600, 1, role_playtime_requirements[ROLE_DEATHSQUAD], TRUE, FALSE) + if(!commando_ghosts.len) + to_chat(usr, "Nobody volunteered to join the DeathSquad.") + return + + sent_strike_team = 1 + + // Spawns commandos and equips them. + var/commando_number = COMMANDOS_POSSIBLE //for selecting a leader + var/is_leader = TRUE // set to FALSE after leader is spawned + + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + + if(commando_number <= 0) + break + + if(L.name == "Commando") + + if(!commando_ghosts.len) + break + + var/use_ds_borg = FALSE + var/mob/ghost_mob = pick(commando_ghosts) + commando_ghosts -= ghost_mob + if(!ghost_mob || !ghost_mob.key || !ghost_mob.client) + continue + + if(!is_leader) + var/new_dstype = alert(ghost_mob.client, "Select Deathsquad Type.", "DS Character Generation", "Organic", "Cyborg") + if(new_dstype == "Cyborg") + use_ds_borg = TRUE + + if(!ghost_mob || !ghost_mob.key || !ghost_mob.client) // Have to re-check this due to the above alert() call + continue + + if(use_ds_borg) + var/mob/living/silicon/robot/deathsquad/R = new() + R.forceMove(get_turf(L)) + var/rnum = rand(1,1000) + var/borgname = "Epsilon [rnum]" + R.name = borgname + R.custom_name = borgname + R.real_name = R.name + R.mind = new + R.mind.current = R + R.mind.original = R + R.mind.assigned_role = SPECIAL_ROLE_DEATHSQUAD + R.mind.special_role = SPECIAL_ROLE_DEATHSQUAD + R.mind.offstation_role = TRUE + if(!(R.mind in SSticker.minds)) + SSticker.minds += R.mind + SSticker.mode.traitors += R.mind + R.key = ghost_mob.key + if(nuke_code) + R.mind.store_memory("Nuke Code: [nuke_code].") + R.mind.store_memory("Mission: [input].") + to_chat(R, "You are a Special Operations cyborg, in the service of Central Command. \nYour current mission is: [input]") + else + var/mob/living/carbon/human/new_commando = create_death_commando(L, is_leader) + new_commando.mind.key = ghost_mob.key + new_commando.key = ghost_mob.key + new_commando.internal = new_commando.s_store + new_commando.update_action_buttons_icon() + if(nuke_code) + new_commando.mind.store_memory("Nuke Code: [nuke_code].") + new_commando.mind.store_memory("Mission: [input].") + to_chat(new_commando, "You are a Special Ops [is_leader ? "TEAM LEADER" : "commando"] in the service of Central Command. Check the table ahead for detailed instructions.\nYour current mission is: [input]") + + is_leader = FALSE + commando_number-- + + //Spawns the rest of the commando gear. + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(L.name == "Commando_Manual") + //new /obj/item/gun/energy/pulse_rifle(L.loc) + var/obj/item/paper/P = new(L.loc) + P.info = "

        Good morning soldier!. This compact guide will familiarize you with standard operating procedure. There are three basic rules to follow:
        #1 Work as a team.
        #2 Accomplish your objective at all costs.
        #3 Leave no witnesses.
        You are fully equipped and stocked for your mission--before departing on the Spec. Ops. Shuttle due South, make sure that all operatives are ready. Actual mission objective will be relayed to you by Central Command through your headsets.
        If deemed appropriate, Central Command will also allow members of your team to equip assault power-armor for the mission. You will find the armor storage due West of your position. Once you are ready to leave, utilize the Special Operations shuttle console and toggle the hull doors via the other console.

        In the event that the team does not accomplish their assigned objective in a timely manner, or finds no other way to do so, attached below are instructions on how to operate a Nanotrasen Nuclear Device. Your operations LEADER is provided with a nuclear authentication disk and a pin-pointer for this reason. You may easily recognize them by their rank: Lieutenant, Captain, or Major. The nuclear device itself will be present somewhere on your destination.

        Hello and thank you for choosing Nanotrasen for your nuclear information needs. Today's crash course will deal with the operation of a Fission Class Nanotrasen made Nuclear Device.
        First and foremost, DO NOT TOUCH ANYTHING UNTIL THE BOMB IS IN PLACE. Pressing any button on the compacted bomb will cause it to extend and bolt itself into place. If this is done to unbolt it one must completely log in which at this time may not be possible.
        To make the device functional:
        #1 Place bomb in designated detonation zone
        #2 Extend and anchor bomb (attack with hand).
        #3 Insert Nuclear Auth. Disk into slot.
        #4 Type numeric code into keypad ([nuke_code]).
        Note: If you make a mistake press R to reset the device.
        #5 Press the E button to log onto the device.
        You now have activated the device. To deactivate the buttons at anytime, for example when you have already prepped the bomb for detonation, remove the authentication disk OR press the R on the keypad. Now the bomb CAN ONLY be detonated using the timer. A manual detonation is not an option.
        Note: Toggle off the SAFETY.
        Use the - - and + + to set a detonation time between 5 seconds and 10 minutes. Then press the timer toggle button to start the countdown. Now remove the authentication disk so that the buttons deactivate.
        Note: THE BOMB IS STILL SET AND WILL DETONATE
        Now before you remove the disk if you need to move the bomb you can: Toggle off the anchor, move it, and re-anchor.

        The nuclear authorization code is: [nuke_code ? nuke_code : "None provided"]

        Good luck, soldier!

        " + P.name = "Spec. Ops Manual" + P.icon = "pamphlet-ds" + var/obj/item/stamp/centcom/stamp = new + P.stamp(stamp) + qdel(stamp) + + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(L.name == "Commando-Bomb") + new /obj/effect/spawner/newbomb/timer/syndicate(L.loc) + qdel(L) + + message_admins("[key_name_admin(usr)] has spawned a CentComm DeathSquad.", 1) + log_admin("[key_name(usr)] used Spawn Death Squad.") + return 1 + +/client/proc/create_death_commando(obj/spawn_location, is_leader = FALSE) + var/mob/living/carbon/human/new_commando = new(spawn_location.loc) + var/commando_leader_rank = pick("Lieutenant", "Captain", "Major") + var/commando_rank = pick("Corporal", "Sergeant", "Staff Sergeant", "Sergeant 1st Class", "Master Sergeant", "Sergeant Major") + var/commando_name = pick(GLOB.last_names) + + var/datum/preferences/A = new()//Randomize appearance for the commando. + if(is_leader) + A.age = rand(35,45) + A.real_name = "[commando_leader_rank] [commando_name]" + else + A.real_name = "[commando_rank] [commando_name]" + A.copy_to(new_commando) + + + new_commando.dna.ready_dna(new_commando)//Creates DNA. + + //Creates mind stuff. + new_commando.mind_initialize() + new_commando.mind.assigned_role = SPECIAL_ROLE_DEATHSQUAD + new_commando.mind.special_role = SPECIAL_ROLE_DEATHSQUAD + SSticker.mode.traitors |= new_commando.mind//Adds them to current traitor list. Which is really the extra antagonist list. + new_commando.equip_death_commando(is_leader) + return new_commando + +/mob/living/carbon/human/proc/equip_death_commando(is_leader = FALSE) + + var/obj/item/radio/R = new /obj/item/radio/headset/alt(src) + R.set_frequency(DTH_FREQ) + equip_to_slot_or_del(R, slot_l_ear) + if(is_leader) + equip_to_slot_or_del(new /obj/item/clothing/under/rank/centcom_officer(src), slot_w_uniform) + else + equip_to_slot_or_del(new /obj/item/clothing/under/color/green(src), slot_w_uniform) + equip_to_slot_or_del(new /obj/item/clothing/shoes/magboots/advance(src), slot_shoes) + equip_to_slot_or_del(new /obj/item/clothing/suit/space/deathsquad(src), slot_wear_suit) + equip_to_slot_or_del(new /obj/item/clothing/gloves/combat(src), slot_gloves) + equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/deathsquad(src), slot_head) + equip_to_slot_or_del(new /obj/item/clothing/mask/gas/sechailer/swat(src), slot_wear_mask) + equip_to_slot_or_del(new /obj/item/clothing/glasses/thermal(src), slot_glasses) + + equip_to_slot_or_del(new /obj/item/storage/backpack/security(src), slot_back) + equip_to_slot_or_del(new /obj/item/storage/box(src), slot_in_backpack) + + equip_to_slot_or_del(new /obj/item/ammo_box/a357(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/reagent_containers/hypospray/combat/nanites(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/storage/box/flashbangs(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/flashlight(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/pinpointer(src), slot_in_backpack) + if(is_leader) + equip_to_slot_or_del(new /obj/item/disk/nuclear/unrestricted(src), slot_in_backpack) + else + equip_to_slot_or_del(new /obj/item/grenade/plastic/x4(src), slot_in_backpack) + + + equip_to_slot_or_del(new /obj/item/melee/energy/sword/saber(src), slot_l_store) + equip_to_slot_or_del(new /obj/item/shield/energy(src), slot_r_store) + equip_to_slot_or_del(new /obj/item/tank/emergency_oxygen/double/full(src), slot_s_store) + equip_to_slot_or_del(new /obj/item/gun/projectile/revolver/mateba(src), slot_belt) + equip_to_slot_or_del(new /obj/item/gun/energy/pulse(src), slot_r_hand) + + var/obj/item/implant/mindshield/L = new/obj/item/implant/mindshield(src) + L.implant(src) + + var/obj/item/card/id/W = new(src) + W.name = "[real_name]'s ID Card" + W.icon_state = "deathsquad" + W.assignment = "Death Commando" + W.access = get_centcom_access(W.assignment) + W.registered_name = real_name + equip_to_slot_or_del(W, slot_wear_id) + + return 1 diff --git a/code/modules/admin/verbs/striketeam_syndicate.dm b/code/modules/admin/verbs/striketeam_syndicate.dm index df0587787f7..8dc2881911c 100644 --- a/code/modules/admin/verbs/striketeam_syndicate.dm +++ b/code/modules/admin/verbs/striketeam_syndicate.dm @@ -1,169 +1,169 @@ -//STRIKE TEAMS - -#define SYNDICATE_COMMANDOS_POSSIBLE 6 //if more Commandos are needed in the future -var/global/sent_syndicate_strike_team = 0 -/client/proc/syndicate_strike_team() - set category = "Event" - set name = "Spawn Syndicate Strike Team" - set desc = "Spawns a squad of commandos in the Syndicate Mothership if you want to run an admin event." - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - return - if(!SSticker) - alert("The game hasn't started yet!") - return - if(sent_syndicate_strike_team == 1) - alert("The Syndicate are already sending a team, Mr. Dumbass.") - return - if(alert("Do you want to send in the Syndicate Strike Team? Once enabled, this is irreversible.",,"Yes","No")=="No") - return - alert("This 'mode' will go on until everyone is dead or the station is destroyed. You may also admin-call the evac shuttle when appropriate. Spawned syndicates have internals cameras which are viewable through a monitor inside the Syndicate Mothership Bridge. Assigning the team's detailed task is recommended from there. The first one selected/spawned will be the team leader.") - - message_admins("[key_name_admin(usr)] has started to spawn a Syndicate Strike Team.", 1) - - var/input = null - while(!input) - input = sanitize(copytext(input(src, "Please specify which mission the syndicate strike team shall undertake.", "Specify Mission", ""),1,MAX_MESSAGE_LEN)) - if(!input) - if(alert("Error, no mission set. Do you want to exit the setup process?",,"Yes","No")=="Yes") - return - - if(sent_syndicate_strike_team) - to_chat(src, "Looks like someone beat you to it.") - return - - var/syndicate_commando_number = SYNDICATE_COMMANDOS_POSSIBLE //for selecting a leader - var/is_leader = TRUE // set to FALSE after leader is spawned - - // Find the nuclear auth code - var/nuke_code - var/temp_code - for(var/obj/machinery/nuclearbomb/N in world) - temp_code = text2num(N.r_code) - if(temp_code)//if it's actually a number. It won't convert any non-numericals. - nuke_code = N.r_code - break - - // Find ghosts willing to be SST - var/list/commando_ghosts = pollCandidatesWithVeto(src, usr, SYNDICATE_COMMANDOS_POSSIBLE, "Join the Syndicate Strike Team?",, 21, 600, 1, role_playtime_requirements[ROLE_DEATHSQUAD], TRUE, FALSE) - if(!commando_ghosts.len) - to_chat(usr, "Nobody volunteered to join the SST.") - return - - sent_syndicate_strike_team = 1 - - //Spawns commandos and equips them. - for(var/obj/effect/landmark/L in GLOB.landmarks_list) - - if(syndicate_commando_number <= 0) - break - - if(L.name == "Syndicate-Commando") - - if(!commando_ghosts.len) - break - - var/mob/ghost_mob = pick(commando_ghosts) - commando_ghosts -= ghost_mob - - if(!ghost_mob || !ghost_mob.key || !ghost_mob.client) - continue - - var/mob/living/carbon/human/new_syndicate_commando = create_syndicate_death_commando(L, is_leader) - - if(!new_syndicate_commando) - continue - - new_syndicate_commando.key = ghost_mob.key - new_syndicate_commando.internal = new_syndicate_commando.s_store - new_syndicate_commando.update_action_buttons_icon() - - //So they don't forget their code or mission. - if(nuke_code) - new_syndicate_commando.mind.store_memory("Nuke Code: [nuke_code].") - new_syndicate_commando.mind.store_memory("Mission: [input].") - - to_chat(new_syndicate_commando, "You are an Elite Syndicate [is_leader ? "TEAM LEADER" : "commando"] in the service of the Syndicate. \nYour current mission is: [input]") - new_syndicate_commando.faction += "syndicate" - var/datum/atom_hud/antag/opshud = huds[ANTAG_HUD_OPS] - opshud.join_hud(new_syndicate_commando.mind.current) - set_antag_hud(new_syndicate_commando.mind.current, "hudoperative") - new_syndicate_commando.regenerate_icons() - is_leader = FALSE - syndicate_commando_number-- - - message_admins("[key_name_admin(usr)] has spawned a Syndicate strike squad.", 1) - log_admin("[key_name(usr)] used Spawn Syndicate Squad.") - feedback_add_details("admin_verb","SDTHS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/create_syndicate_death_commando(obj/spawn_location, is_leader = FALSE) - var/mob/living/carbon/human/new_syndicate_commando = new(spawn_location.loc) - var/syndicate_commando_leader_rank = pick("Lieutenant", "Captain", "Major") - var/syndicate_commando_rank = pick("Corporal", "Sergeant", "Staff Sergeant", "Sergeant 1st Class", "Master Sergeant", "Sergeant Major") - var/syndicate_commando_name = pick(GLOB.last_names) - - var/datum/preferences/A = new()//Randomize appearance for the commando. - if(is_leader) - A.age = rand(35,45) - A.real_name = "[syndicate_commando_leader_rank] [syndicate_commando_name]" - else - A.real_name = "[syndicate_commando_rank] [syndicate_commando_name]" - A.copy_to(new_syndicate_commando) - - new_syndicate_commando.dna.ready_dna(new_syndicate_commando)//Creates DNA. - - //Creates mind stuff. - new_syndicate_commando.mind_initialize() - new_syndicate_commando.mind.assigned_role = SPECIAL_ROLE_SYNDICATE_DEATHSQUAD - new_syndicate_commando.mind.special_role = SPECIAL_ROLE_SYNDICATE_DEATHSQUAD - new_syndicate_commando.mind.offstation_role = TRUE - SSticker.mode.traitors |= new_syndicate_commando.mind //Adds them to current traitor list. Which is really the extra antagonist list. - new_syndicate_commando.equip_syndicate_commando(is_leader) - qdel(spawn_location) - return new_syndicate_commando - -/mob/living/carbon/human/proc/equip_syndicate_commando(is_leader = FALSE, full_gear = FALSE) - var/obj/item/radio/R = new /obj/item/radio/headset/syndicate/alt/syndteam(src) - R.set_frequency(SYNDTEAM_FREQ) - equip_to_slot_or_del(R, slot_l_ear) - equip_to_slot_or_del(new /obj/item/clothing/under/syndicate(src), slot_w_uniform) - if(!full_gear) - equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(src), slot_shoes) - equip_to_slot_or_del(new /obj/item/clothing/gloves/combat(src), slot_gloves) - - equip_to_slot_or_del(new /obj/item/storage/backpack/security(src), slot_back) - equip_to_slot_or_del(new /obj/item/storage/box/survival_syndi(src), slot_in_backpack) - - equip_to_slot_or_del(new /obj/item/gun/projectile/revolver(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/ammo_box/a357(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/reagent_containers/hypospray/combat/nanites(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/grenade/plastic/x4(src), slot_in_backpack) - if(is_leader) - equip_to_slot_or_del(new /obj/item/pinpointer(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/disk/nuclear/unrestricted(src), slot_in_backpack) - else - equip_to_slot_or_del(new /obj/item/grenade/plastic/x4(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/card/emag(src), slot_r_store) - equip_to_slot_or_del(new /obj/item/melee/energy/sword/saber/red(src), slot_l_store) - - if(full_gear) - equip_to_slot_or_del(new /obj/item/clothing/mask/gas/syndicate(src), slot_wear_mask) - equip_to_slot_or_del(new /obj/item/clothing/suit/space/hardsuit/syndi/elite/sst(src), slot_wear_suit) - equip_to_slot_or_del(new /obj/item/clothing/glasses/thermal(src), slot_glasses) - equip_to_slot_or_del(new /obj/item/storage/belt/military/sst(src), slot_belt) - equip_to_slot_or_del(new /obj/item/tank/jetpack/oxygen/harness(src), slot_s_store) - equip_to_slot_or_del(new /obj/item/clothing/shoes/magboots/syndie/advance(src), slot_shoes) - equip_to_slot_or_del(new /obj/item/gun/projectile/automatic/l6_saw(src), slot_r_hand) - equip_to_slot_or_del(new /obj/item/ammo_box/magazine/mm556x45(src), slot_in_backpack) - - var/obj/item/implant/dust/D = new /obj/item/implant/dust(src) - D.implant(src) - var/obj/item/card/id/syndicate/W = new(src) //Untrackable by AI - W.name = "[real_name]'s ID Card" - W.icon_state = "syndie" - W.assignment = "Syndicate Commando" - W.access += get_syndicate_access(W.assignment) - W.registered_name = real_name - equip_to_slot_or_del(W, slot_wear_id) - - return 1 +//STRIKE TEAMS + +#define SYNDICATE_COMMANDOS_POSSIBLE 6 //if more Commandos are needed in the future +var/global/sent_syndicate_strike_team = 0 +/client/proc/syndicate_strike_team() + set category = "Event" + set name = "Spawn Syndicate Strike Team" + set desc = "Spawns a squad of commandos in the Syndicate Mothership if you want to run an admin event." + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + return + if(!SSticker) + alert("The game hasn't started yet!") + return + if(sent_syndicate_strike_team == 1) + alert("The Syndicate are already sending a team, Mr. Dumbass.") + return + if(alert("Do you want to send in the Syndicate Strike Team? Once enabled, this is irreversible.",,"Yes","No")=="No") + return + alert("This 'mode' will go on until everyone is dead or the station is destroyed. You may also admin-call the evac shuttle when appropriate. Spawned syndicates have internals cameras which are viewable through a monitor inside the Syndicate Mothership Bridge. Assigning the team's detailed task is recommended from there. The first one selected/spawned will be the team leader.") + + message_admins("[key_name_admin(usr)] has started to spawn a Syndicate Strike Team.", 1) + + var/input = null + while(!input) + input = sanitize(copytext(input(src, "Please specify which mission the syndicate strike team shall undertake.", "Specify Mission", ""),1,MAX_MESSAGE_LEN)) + if(!input) + if(alert("Error, no mission set. Do you want to exit the setup process?",,"Yes","No")=="Yes") + return + + if(sent_syndicate_strike_team) + to_chat(src, "Looks like someone beat you to it.") + return + + var/syndicate_commando_number = SYNDICATE_COMMANDOS_POSSIBLE //for selecting a leader + var/is_leader = TRUE // set to FALSE after leader is spawned + + // Find the nuclear auth code + var/nuke_code + var/temp_code + for(var/obj/machinery/nuclearbomb/N in world) + temp_code = text2num(N.r_code) + if(temp_code)//if it's actually a number. It won't convert any non-numericals. + nuke_code = N.r_code + break + + // Find ghosts willing to be SST + var/list/commando_ghosts = pollCandidatesWithVeto(src, usr, SYNDICATE_COMMANDOS_POSSIBLE, "Join the Syndicate Strike Team?",, 21, 600, 1, role_playtime_requirements[ROLE_DEATHSQUAD], TRUE, FALSE) + if(!commando_ghosts.len) + to_chat(usr, "Nobody volunteered to join the SST.") + return + + sent_syndicate_strike_team = 1 + + //Spawns commandos and equips them. + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + + if(syndicate_commando_number <= 0) + break + + if(L.name == "Syndicate-Commando") + + if(!commando_ghosts.len) + break + + var/mob/ghost_mob = pick(commando_ghosts) + commando_ghosts -= ghost_mob + + if(!ghost_mob || !ghost_mob.key || !ghost_mob.client) + continue + + var/mob/living/carbon/human/new_syndicate_commando = create_syndicate_death_commando(L, is_leader) + + if(!new_syndicate_commando) + continue + + new_syndicate_commando.key = ghost_mob.key + new_syndicate_commando.internal = new_syndicate_commando.s_store + new_syndicate_commando.update_action_buttons_icon() + + //So they don't forget their code or mission. + if(nuke_code) + new_syndicate_commando.mind.store_memory("Nuke Code: [nuke_code].") + new_syndicate_commando.mind.store_memory("Mission: [input].") + + to_chat(new_syndicate_commando, "You are an Elite Syndicate [is_leader ? "TEAM LEADER" : "commando"] in the service of the Syndicate. \nYour current mission is: [input]") + new_syndicate_commando.faction += "syndicate" + var/datum/atom_hud/antag/opshud = huds[ANTAG_HUD_OPS] + opshud.join_hud(new_syndicate_commando.mind.current) + set_antag_hud(new_syndicate_commando.mind.current, "hudoperative") + new_syndicate_commando.regenerate_icons() + is_leader = FALSE + syndicate_commando_number-- + + message_admins("[key_name_admin(usr)] has spawned a Syndicate strike squad.", 1) + log_admin("[key_name(usr)] used Spawn Syndicate Squad.") + feedback_add_details("admin_verb","SDTHS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/create_syndicate_death_commando(obj/spawn_location, is_leader = FALSE) + var/mob/living/carbon/human/new_syndicate_commando = new(spawn_location.loc) + var/syndicate_commando_leader_rank = pick("Lieutenant", "Captain", "Major") + var/syndicate_commando_rank = pick("Corporal", "Sergeant", "Staff Sergeant", "Sergeant 1st Class", "Master Sergeant", "Sergeant Major") + var/syndicate_commando_name = pick(GLOB.last_names) + + var/datum/preferences/A = new()//Randomize appearance for the commando. + if(is_leader) + A.age = rand(35,45) + A.real_name = "[syndicate_commando_leader_rank] [syndicate_commando_name]" + else + A.real_name = "[syndicate_commando_rank] [syndicate_commando_name]" + A.copy_to(new_syndicate_commando) + + new_syndicate_commando.dna.ready_dna(new_syndicate_commando)//Creates DNA. + + //Creates mind stuff. + new_syndicate_commando.mind_initialize() + new_syndicate_commando.mind.assigned_role = SPECIAL_ROLE_SYNDICATE_DEATHSQUAD + new_syndicate_commando.mind.special_role = SPECIAL_ROLE_SYNDICATE_DEATHSQUAD + new_syndicate_commando.mind.offstation_role = TRUE + SSticker.mode.traitors |= new_syndicate_commando.mind //Adds them to current traitor list. Which is really the extra antagonist list. + new_syndicate_commando.equip_syndicate_commando(is_leader) + qdel(spawn_location) + return new_syndicate_commando + +/mob/living/carbon/human/proc/equip_syndicate_commando(is_leader = FALSE, full_gear = FALSE) + var/obj/item/radio/R = new /obj/item/radio/headset/syndicate/alt/syndteam(src) + R.set_frequency(SYNDTEAM_FREQ) + equip_to_slot_or_del(R, slot_l_ear) + equip_to_slot_or_del(new /obj/item/clothing/under/syndicate(src), slot_w_uniform) + if(!full_gear) + equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(src), slot_shoes) + equip_to_slot_or_del(new /obj/item/clothing/gloves/combat(src), slot_gloves) + + equip_to_slot_or_del(new /obj/item/storage/backpack/security(src), slot_back) + equip_to_slot_or_del(new /obj/item/storage/box/survival_syndi(src), slot_in_backpack) + + equip_to_slot_or_del(new /obj/item/gun/projectile/revolver(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/ammo_box/a357(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/reagent_containers/hypospray/combat/nanites(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/grenade/plastic/x4(src), slot_in_backpack) + if(is_leader) + equip_to_slot_or_del(new /obj/item/pinpointer(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/disk/nuclear/unrestricted(src), slot_in_backpack) + else + equip_to_slot_or_del(new /obj/item/grenade/plastic/x4(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/card/emag(src), slot_r_store) + equip_to_slot_or_del(new /obj/item/melee/energy/sword/saber/red(src), slot_l_store) + + if(full_gear) + equip_to_slot_or_del(new /obj/item/clothing/mask/gas/syndicate(src), slot_wear_mask) + equip_to_slot_or_del(new /obj/item/clothing/suit/space/hardsuit/syndi/elite/sst(src), slot_wear_suit) + equip_to_slot_or_del(new /obj/item/clothing/glasses/thermal(src), slot_glasses) + equip_to_slot_or_del(new /obj/item/storage/belt/military/sst(src), slot_belt) + equip_to_slot_or_del(new /obj/item/tank/jetpack/oxygen/harness(src), slot_s_store) + equip_to_slot_or_del(new /obj/item/clothing/shoes/magboots/syndie/advance(src), slot_shoes) + equip_to_slot_or_del(new /obj/item/gun/projectile/automatic/l6_saw(src), slot_r_hand) + equip_to_slot_or_del(new /obj/item/ammo_box/magazine/mm556x45(src), slot_in_backpack) + + var/obj/item/implant/dust/D = new /obj/item/implant/dust(src) + D.implant(src) + var/obj/item/card/id/syndicate/W = new(src) //Untrackable by AI + W.name = "[real_name]'s ID Card" + W.icon_state = "syndie" + W.assignment = "Syndicate Commando" + W.access += get_syndicate_access(W.assignment) + W.registered_name = real_name + equip_to_slot_or_del(W, slot_wear_id) + + return 1 diff --git a/code/modules/admin/verbs/ticklag.dm b/code/modules/admin/verbs/ticklag.dm index 316adf935b0..e19291c159e 100644 --- a/code/modules/admin/verbs/ticklag.dm +++ b/code/modules/admin/verbs/ticklag.dm @@ -1,21 +1,21 @@ -//Merged Doohl's and the existing ticklag as they both had good elements about them ~Carn - -/client/proc/ticklag() - set category = "Debug" - set name = "Set Ticklag" - set desc = "Sets a new tick lag. Recommend you don't mess with this too much! Stable, time-tested ticklag value is 0.9" - - if(!check_rights(R_DEBUG)) return - - var/newtick = input("Sets a new tick lag. Please don't mess with this too much! The stable, time-tested ticklag value is 0.9","Lag of Tick", world.tick_lag) as num|null - //I've used ticks of 2 before to help with serious singulo lags - if(newtick && newtick <= 2 && newtick > 0) - log_admin("[key_name(src)] has modified world.tick_lag to [newtick]", 0) - message_admins("[key_name_admin(src)] has modified world.tick_lag to [newtick]", 0) - world.tick_lag = newtick - feedback_add_details("admin_verb","TICKLAG") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - else - to_chat(src, "Error: ticklag(): Invalid world.ticklag value. No changes made.") - - +//Merged Doohl's and the existing ticklag as they both had good elements about them ~Carn + +/client/proc/ticklag() + set category = "Debug" + set name = "Set Ticklag" + set desc = "Sets a new tick lag. Recommend you don't mess with this too much! Stable, time-tested ticklag value is 0.9" + + if(!check_rights(R_DEBUG)) return + + var/newtick = input("Sets a new tick lag. Please don't mess with this too much! The stable, time-tested ticklag value is 0.9","Lag of Tick", world.tick_lag) as num|null + //I've used ticks of 2 before to help with serious singulo lags + if(newtick && newtick <= 2 && newtick > 0) + log_admin("[key_name(src)] has modified world.tick_lag to [newtick]", 0) + message_admins("[key_name_admin(src)] has modified world.tick_lag to [newtick]", 0) + world.tick_lag = newtick + feedback_add_details("admin_verb","TICKLAG") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + else + to_chat(src, "Error: ticklag(): Invalid world.ticklag value. No changes made.") + + diff --git a/code/modules/admin/verbs/tripAI.dm b/code/modules/admin/verbs/tripAI.dm index 98a4bf82e98..5eedd9f2811 100644 --- a/code/modules/admin/verbs/tripAI.dm +++ b/code/modules/admin/verbs/tripAI.dm @@ -1,22 +1,22 @@ -/client/proc/triple_ai() - set category = "Event" - set name = "Create AI Triumvirate" - - if(SSticker.current_state > GAME_STATE_PREGAME) - to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.") - return - - if(SSjobs && SSticker) - var/datum/job/job = SSjobs.GetJob("AI") - if(!job) - to_chat(usr, "Unable to locate the AI job") - return - if(SSticker.triai) - SSticker.triai = 0 - to_chat(usr, "Only one AI will be spawned at round start.") - message_admins("[key_name_admin(usr)] has toggled off triple AIs at round start.", 1) - else - SSticker.triai = 1 - to_chat(usr, "There will be an AI Triumvirate at round start.") - message_admins("[key_name_admin(usr)] has toggled on triple AIs at round start.", 1) - return +/client/proc/triple_ai() + set category = "Event" + set name = "Create AI Triumvirate" + + if(SSticker.current_state > GAME_STATE_PREGAME) + to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.") + return + + if(SSjobs && SSticker) + var/datum/job/job = SSjobs.GetJob("AI") + if(!job) + to_chat(usr, "Unable to locate the AI job") + return + if(SSticker.triai) + SSticker.triai = 0 + to_chat(usr, "Only one AI will be spawned at round start.") + message_admins("[key_name_admin(usr)] has toggled off triple AIs at round start.", 1) + else + SSticker.triai = 1 + to_chat(usr, "There will be an AI Triumvirate at round start.") + message_admins("[key_name_admin(usr)] has toggled on triple AIs at round start.", 1) + return diff --git a/code/modules/alarm/camera_alarm.dm b/code/modules/alarm/camera_alarm.dm index 6d8f151f0b8..bef53ad466f 100644 --- a/code/modules/alarm/camera_alarm.dm +++ b/code/modules/alarm/camera_alarm.dm @@ -1,2 +1,2 @@ /datum/alarm_handler/camera - category = "Camera Alarms" \ No newline at end of file + category = "Camera Alarms" diff --git a/code/modules/alarm/motion_alarm.dm b/code/modules/alarm/motion_alarm.dm index 0acbbe5f221..fd7e6febe48 100644 --- a/code/modules/alarm/motion_alarm.dm +++ b/code/modules/alarm/motion_alarm.dm @@ -1,2 +1,2 @@ /datum/alarm_handler/motion - category = "Motion Alarms" \ No newline at end of file + category = "Motion Alarms" diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 39053fa79d9..4a6fe7d6640 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -134,4 +134,4 @@ GLOBAL_LIST_EMPTY(antagonists) //Displayed at the end of roundend_category section /datum/antagonist/proc/roundend_report_footer() - return \ No newline at end of file + return diff --git a/code/modules/antagonists/_common/antag_helpers.dm b/code/modules/antagonists/_common/antag_helpers.dm index 134ba1d7d90..af00cbf3190 100644 --- a/code/modules/antagonists/_common/antag_helpers.dm +++ b/code/modules/antagonists/_common/antag_helpers.dm @@ -16,4 +16,4 @@ continue var/datum/team/T = A.get_team() if(!team_type || istype(T, team_type)) - . |= T \ No newline at end of file + . |= T diff --git a/code/modules/antagonists/_common/antag_hud.dm b/code/modules/antagonists/_common/antag_hud.dm index 89ad9af15ab..178f84387ac 100644 --- a/code/modules/antagonists/_common/antag_hud.dm +++ b/code/modules/antagonists/_common/antag_hud.dm @@ -77,4 +77,4 @@ /datum/mindslaves/proc/leave_serv_hud(datum/mind/free_mind) thrallhud.leave_hud(free_mind.current) - set_antag_hud(free_mind.current, null) \ No newline at end of file + set_antag_hud(free_mind.current, null) diff --git a/code/modules/antagonists/survivalist/survivalist.dm b/code/modules/antagonists/survivalist/survivalist.dm index 7d08e18834c..1d30a6fe334 100644 --- a/code/modules/antagonists/survivalist/survivalist.dm +++ b/code/modules/antagonists/survivalist/survivalist.dm @@ -38,4 +38,4 @@ var/datum/objective/steal_five_of_type/summon_magic/magic = new magic.owner = owner objectives += magic - ..() \ No newline at end of file + ..() diff --git a/code/modules/antagonists/wishgranter/wishgranter.dm b/code/modules/antagonists/wishgranter/wishgranter.dm index 1697eb897c9..b5414df33bf 100644 --- a/code/modules/antagonists/wishgranter/wishgranter.dm +++ b/code/modules/antagonists/wishgranter/wishgranter.dm @@ -75,4 +75,4 @@ H.mutations.Add(LASER) H.update_mutations() - H.update_body() \ No newline at end of file + H.update_body() diff --git a/code/modules/arcade/arcade_prize.dm b/code/modules/arcade/arcade_prize.dm index a91e392e125..47b0ac97bf8 100644 --- a/code/modules/arcade/arcade_prize.dm +++ b/code/modules/arcade/arcade_prize.dm @@ -85,4 +85,4 @@ if(25 to 74) icon_state = "tickets_3" // Buncha tickets else - icon_state = "tickets_4" // Ticket snake \ No newline at end of file + icon_state = "tickets_4" // Ticket snake diff --git a/code/modules/arcade/claw_game.dm b/code/modules/arcade/claw_game.dm index 7339b31dc77..904adaffeb5 100644 --- a/code/modules/arcade/claw_game.dm +++ b/code/modules/arcade/claw_game.dm @@ -76,4 +76,4 @@ if(!isnull(prize_won)) close_game() if(prize_won == "1") - win() \ No newline at end of file + win() diff --git a/code/modules/arcade/mob_hunt/battle_computer.dm b/code/modules/arcade/mob_hunt/battle_computer.dm index 0d6e91b60d9..94337a5cfac 100644 --- a/code/modules/arcade/mob_hunt/battle_computer.dm +++ b/code/modules/arcade/mob_hunt/battle_computer.dm @@ -302,4 +302,4 @@ to_chat(user, "This mob is already at maximum health!") return patient.mob_data.cur_health = patient.mob_data.max_health - to_chat(user, "[patient.mob_data.nickname ? patient.mob_data.nickname : patient.mob_data.mob_name] has been restored to full health!") \ No newline at end of file + to_chat(user, "[patient.mob_data.nickname ? patient.mob_data.nickname : patient.mob_data.mob_name] has been restored to full health!") diff --git a/code/modules/arcade/mob_hunt/mob_datums.dm b/code/modules/arcade/mob_hunt/mob_datums.dm index dad3f528796..d9fc1220a79 100644 --- a/code/modules/arcade/mob_hunt/mob_datums.dm +++ b/code/modules/arcade/mob_hunt/mob_datums.dm @@ -498,4 +498,4 @@ turf_blacklist = list() area_whitelist = list() turf_whitelist = list() - lifetime = 2000 \ No newline at end of file + lifetime = 2000 diff --git a/code/modules/arcade/mob_hunt/mob_type_datums.dm b/code/modules/arcade/mob_hunt/mob_type_datums.dm index 9d27a844ea8..0ae84b1b764 100644 --- a/code/modules/arcade/mob_hunt/mob_type_datums.dm +++ b/code/modules/arcade/mob_hunt/mob_type_datums.dm @@ -253,4 +253,4 @@ TYPE_FLYING, TYPE_BLUESPACE, TYPE_STEEL) - immunity = list(TYPE_POISON) \ No newline at end of file + immunity = list(TYPE_POISON) diff --git a/code/modules/arcade/page.css b/code/modules/arcade/page.css index 17e77bcb038..e3dfcac1cce 100644 --- a/code/modules/arcade/page.css +++ b/code/modules/arcade/page.css @@ -89,4 +89,4 @@ body { .button { cursor: pointer; -} \ No newline at end of file +} diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index b2a44940053..827e7893396 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -1,150 +1,150 @@ -/obj/item/assembly - name = "assembly" - desc = "A small electronic device that should never exist." - icon = 'icons/obj/assemblies/new_assemblies.dmi' - icon_state = "" - flags = CONDUCT - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL = 100) - throwforce = 2 - throw_speed = 3 - throw_range = 10 - origin_tech = "magnets=1;engineering=1" - toolspeed = 1 - usesound = 'sound/items/deconstruct.ogg' - - var/bomb_name = "bomb" // used for naming bombs / mines - - var/secured = TRUE - var/list/attached_overlays = null - var/obj/item/assembly_holder/holder = null - var/cooldown = FALSE //To prevent spam - var/wires = WIRE_RECEIVE | WIRE_PULSE - var/datum/wires/connected = null // currently only used by timer/signaler - - var/const/WIRE_RECEIVE = 1 //Allows Pulsed(0) to call Activate() - var/const/WIRE_PULSE = 2 //Allows Pulse(0) to act on the holder - var/const/WIRE_PULSE_SPECIAL = 4 //Allows Pulse(0) to act on the holders special assembly - var/const/WIRE_RADIO_RECEIVE = 8 //Allows Pulsed(1) to call Activate() - var/const/WIRE_RADIO_PULSE = 16 //Allows Pulse(1) to send a radio message - -/obj/item/assembly/proc/activate() //What the device does when turned on - return - -/obj/item/assembly/proc/pulsed(radio = FALSE) //Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs - return - -/obj/item/assembly/proc/pulse(radio = FALSE) //Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct - return - -/obj/item/assembly/proc/toggle_secure() //Code that has to happen when the assembly is un\secured goes here - return - -/obj/item/assembly/proc/attach_assembly(obj/A, mob/user) //Called when an assembly is attacked by another - return - -/obj/item/assembly/proc/process_cooldown() //Called via spawn(10) to have it count down the cooldown var - return - -/obj/item/assembly/proc/holder_movement() //Called when the holder is moved - return - -/obj/item/assembly/proc/describe() // Called by grenades to describe the state of the trigger (time left, etc) - return "The trigger assembly looks broken!" - -/obj/item/assembly/interact(mob/user) //Called when attack_self is called - return - -/obj/item/assembly/process_cooldown() - cooldown-- - if(cooldown <= 0) - return FALSE - spawn(10) - process_cooldown() - return TRUE - -/obj/item/assembly/Destroy() - if(istype(loc, /obj/item/assembly_holder) || istype(holder)) - var/obj/item/assembly_holder/A = loc - if(A.a_left == src) - A.a_left = null - else if(A.a_right == src) - A.a_right = null - holder = null - return ..() - -/obj/item/assembly/pulsed(radio = FALSE) - if(holder && (wires & WIRE_RECEIVE)) - activate() - if(radio && (wires & WIRE_RADIO_RECEIVE)) - activate() - return TRUE - -/obj/item/assembly/pulse(radio = FALSE) - if(holder && (wires & WIRE_PULSE)) - holder.process_activation(src, 1, 0) - if(holder && (wires & WIRE_PULSE_SPECIAL)) - holder.process_activation(src, 0, 1) - if(istype(loc, /obj/item/grenade)) // This is a hack. Todo: Manage this better -Sayu - var/obj/item/grenade/G = loc - G.prime() // Adios, muchachos - return TRUE - -/obj/item/assembly/activate() - if(!secured || cooldown > 0) - return FALSE - cooldown = 2 - spawn(10) - process_cooldown() - return TRUE - -/obj/item/assembly/toggle_secure() - secured = !secured - update_icon() - return secured - -/obj/item/assembly/attach_assembly(obj/item/assembly/A, mob/user) - holder = new /obj/item/assembly_holder(get_turf(src)) - if(holder.attach(A, src, user)) - to_chat(user, "You attach [A] to [src]!") - return TRUE - return FALSE - -/obj/item/assembly/attackby(obj/item/W, mob/user, params) - if(isassembly(W)) - var/obj/item/assembly/A = W - if(!A.secured && !secured) - attach_assembly(A, user) - return - - return ..() - -/obj/item/assembly/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(toggle_secure()) - to_chat(user, "[src] is ready!") - else - to_chat(user, "[src] can now be attached!") - -/obj/item/assembly/process() - STOP_PROCESSING(SSobj, src) - -/obj/item/assembly/examine(mob/user) - . = ..() - if(in_range(src, user) || loc == user) - if(secured) - . += "[src] is ready!" - else - . += "[src] can be attached!" - -/obj/item/assembly/attack_self(mob/user) - if(!user) - return - user.set_machine(src) - interact(user) - return TRUE - -/obj/item/assembly/interact(mob/user) - return \ No newline at end of file +/obj/item/assembly + name = "assembly" + desc = "A small electronic device that should never exist." + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = "" + flags = CONDUCT + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL = 100) + throwforce = 2 + throw_speed = 3 + throw_range = 10 + origin_tech = "magnets=1;engineering=1" + toolspeed = 1 + usesound = 'sound/items/deconstruct.ogg' + + var/bomb_name = "bomb" // used for naming bombs / mines + + var/secured = TRUE + var/list/attached_overlays = null + var/obj/item/assembly_holder/holder = null + var/cooldown = FALSE //To prevent spam + var/wires = WIRE_RECEIVE | WIRE_PULSE + var/datum/wires/connected = null // currently only used by timer/signaler + + var/const/WIRE_RECEIVE = 1 //Allows Pulsed(0) to call Activate() + var/const/WIRE_PULSE = 2 //Allows Pulse(0) to act on the holder + var/const/WIRE_PULSE_SPECIAL = 4 //Allows Pulse(0) to act on the holders special assembly + var/const/WIRE_RADIO_RECEIVE = 8 //Allows Pulsed(1) to call Activate() + var/const/WIRE_RADIO_PULSE = 16 //Allows Pulse(1) to send a radio message + +/obj/item/assembly/proc/activate() //What the device does when turned on + return + +/obj/item/assembly/proc/pulsed(radio = FALSE) //Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs + return + +/obj/item/assembly/proc/pulse(radio = FALSE) //Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct + return + +/obj/item/assembly/proc/toggle_secure() //Code that has to happen when the assembly is un\secured goes here + return + +/obj/item/assembly/proc/attach_assembly(obj/A, mob/user) //Called when an assembly is attacked by another + return + +/obj/item/assembly/proc/process_cooldown() //Called via spawn(10) to have it count down the cooldown var + return + +/obj/item/assembly/proc/holder_movement() //Called when the holder is moved + return + +/obj/item/assembly/proc/describe() // Called by grenades to describe the state of the trigger (time left, etc) + return "The trigger assembly looks broken!" + +/obj/item/assembly/interact(mob/user) //Called when attack_self is called + return + +/obj/item/assembly/process_cooldown() + cooldown-- + if(cooldown <= 0) + return FALSE + spawn(10) + process_cooldown() + return TRUE + +/obj/item/assembly/Destroy() + if(istype(loc, /obj/item/assembly_holder) || istype(holder)) + var/obj/item/assembly_holder/A = loc + if(A.a_left == src) + A.a_left = null + else if(A.a_right == src) + A.a_right = null + holder = null + return ..() + +/obj/item/assembly/pulsed(radio = FALSE) + if(holder && (wires & WIRE_RECEIVE)) + activate() + if(radio && (wires & WIRE_RADIO_RECEIVE)) + activate() + return TRUE + +/obj/item/assembly/pulse(radio = FALSE) + if(holder && (wires & WIRE_PULSE)) + holder.process_activation(src, 1, 0) + if(holder && (wires & WIRE_PULSE_SPECIAL)) + holder.process_activation(src, 0, 1) + if(istype(loc, /obj/item/grenade)) // This is a hack. Todo: Manage this better -Sayu + var/obj/item/grenade/G = loc + G.prime() // Adios, muchachos + return TRUE + +/obj/item/assembly/activate() + if(!secured || cooldown > 0) + return FALSE + cooldown = 2 + spawn(10) + process_cooldown() + return TRUE + +/obj/item/assembly/toggle_secure() + secured = !secured + update_icon() + return secured + +/obj/item/assembly/attach_assembly(obj/item/assembly/A, mob/user) + holder = new /obj/item/assembly_holder(get_turf(src)) + if(holder.attach(A, src, user)) + to_chat(user, "You attach [A] to [src]!") + return TRUE + return FALSE + +/obj/item/assembly/attackby(obj/item/W, mob/user, params) + if(isassembly(W)) + var/obj/item/assembly/A = W + if(!A.secured && !secured) + attach_assembly(A, user) + return + + return ..() + +/obj/item/assembly/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(toggle_secure()) + to_chat(user, "[src] is ready!") + else + to_chat(user, "[src] can now be attached!") + +/obj/item/assembly/process() + STOP_PROCESSING(SSobj, src) + +/obj/item/assembly/examine(mob/user) + . = ..() + if(in_range(src, user) || loc == user) + if(secured) + . += "[src] is ready!" + else + . += "[src] can be attached!" + +/obj/item/assembly/attack_self(mob/user) + if(!user) + return + user.set_machine(src) + interact(user) + return TRUE + +/obj/item/assembly/interact(mob/user) + return diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm index d5039af0a84..bf62e2a828a 100644 --- a/code/modules/assembly/bomb.dm +++ b/code/modules/assembly/bomb.dm @@ -1,180 +1,180 @@ -/obj/item/onetankbomb - name = "bomb" - icon = 'icons/obj/tank.dmi' - item_state = "assembly" - throwforce = 5 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 2 - throw_range = 4 - flags = CONDUCT //Copied this from old code, so this may or may not be necessary - var/status = 0 //0 - not readied //1 - bomb finished with welder - var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device - var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank - origin_tech = "materials=1;engineering=1" - -/obj/item/onetankbomb/examine(mob/user) - . = ..() - . += bombtank.examine(user) - -/obj/item/onetankbomb/update_icon() - if(bombtank) - icon_state = bombtank.icon_state - if(bombassembly) - overlays += bombassembly.icon_state - overlays += bombassembly.overlays - overlays += "bomb_assembly" - -/obj/item/onetankbomb/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/analyzer)) - bombtank.attackby(W, user, params) - return - return ..() - -/obj/item/onetankbomb/wrench_act(mob/user, obj/item/I) //This is basically bomb assembly code inverted. apparently it works. - if(status) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - to_chat(user, "You disassemble [src].") - bombassembly.loc = user.loc - bombassembly.master = null - bombassembly = null - bombtank.loc = user.loc - bombtank.master = null - bombtank = null - qdel(src) - -/obj/item/onetankbomb/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, volume = I.tool_volume)) - return - if(!status) - status = TRUE - investigate_log("[key_name(user)] welded a single tank bomb. Temperature: [bombtank.air_contents.temperature-T0C]", INVESTIGATE_BOMB) - msg_admin_attack("[key_name_admin(user)] welded a single tank bomb. Temperature: [bombtank.air_contents.temperature-T0C]", ATKLOG_FEW) - log_game("[key_name(user)] welded a single tank bomb. Temperature: [bombtank.air_contents.temperature - T0C]") - to_chat(user, "A pressure hole has been bored to [bombtank] valve. [bombtank] can now be ignited.") - else - status = FALSE - investigate_log("[key_name(user)] unwelded a single tank bomb. Temperature: [bombtank.air_contents.temperature-T0C]", INVESTIGATE_BOMB) - to_chat(user, "The hole has been closed.") - - -/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly - bombassembly.attack_self(user, 1) - add_fingerprint(user) - return - -/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. - visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*") - sleep(10) - if(!src) - return - if(status) - bombtank.detonate() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file - else - bombtank.release() - -/obj/item/onetankbomb/HasProximity(atom/movable/AM) - if(bombassembly) - bombassembly.HasProximity(AM) - -/obj/item/onetankbomb/Crossed(atom/movable/AM, oldloc) //for mousetraps - if(bombassembly) - bombassembly.Crossed(AM, oldloc) - -/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps - if(bombassembly) - bombassembly.on_found(finder) - -/obj/item/onetankbomb/hear_talk(mob/living/M, list/message_pieces) - if(bombassembly) - bombassembly.hear_talk(M, message_pieces) - -/obj/item/onetankbomb/hear_message(mob/living/M, msg) - if(bombassembly) - bombassembly.hear_message(M, msg) - -// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ---------- - -/obj/item/tank/proc/bomb_assemble(W,user) //Bomb assembly proc. This turns assembly+tank into a bomb - var/obj/item/assembly_holder/S = W - var/mob/M = user - if(!S.secured) //Check if the assembly is secured - return - if(isigniter(S.a_left) == isigniter(S.a_right)) //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it - return - - var/obj/item/onetankbomb/R = new /obj/item/onetankbomb(loc) - - M.drop_item() //Remove the assembly from your hands - M.remove_from_mob(src) //Remove the tank from your character,in case you were holding it - M.put_in_hands(R) //Equips the bomb if possible, or puts it on the floor. - - R.bombassembly = S //Tell the bomb about its assembly part - S.master = R //Tell the assembly about its new owner - S.loc = R //Move the assembly out of the fucking way - - R.bombtank = src //Same for tank - master = R - loc = R - R.update_icon() - return - -/obj/item/tank/proc/detonate() //This happens when a bomb is told to explode - var/fuel_moles = air_contents.toxins + air_contents.oxygen/6 - var/strength = 1 - - var/turf/ground_zero = get_turf(loc) - loc = null - - if(air_contents.temperature > (T0C + 400)) - strength = (fuel_moles/15) - - if(strength >=1) - explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1)) - else if(strength >=0.5) - explosion(ground_zero, 0, 1, 2, 4) - else if(strength >=0.2) - explosion(ground_zero, -1, 0, 1, 2) - else - ground_zero.assume_air(air_contents) - ground_zero.hotspot_expose(1000, 125) - - else if(air_contents.temperature > (T0C + 250)) - strength = (fuel_moles/20) - - if(strength >=1) - explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1)) - else if(strength >=0.5) - explosion(ground_zero, -1, 0, 1, 2) - else - ground_zero.assume_air(air_contents) - ground_zero.hotspot_expose(1000, 125) - - else if(air_contents.temperature > (T0C + 100)) - strength = (fuel_moles/25) - - if(strength >=1) - explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1)) - else - ground_zero.assume_air(air_contents) - ground_zero.hotspot_expose(1000, 125) - - else - ground_zero.assume_air(air_contents) - ground_zero.hotspot_expose(1000, 125) - - air_update_turf() - if(master) - qdel(master) - qdel(src) - -/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out. - var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles()) - var/turf/simulated/T = get_turf(src) - if(!T) - return - T.assume_air(removed) - air_update_turf() +/obj/item/onetankbomb + name = "bomb" + icon = 'icons/obj/tank.dmi' + item_state = "assembly" + throwforce = 5 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 2 + throw_range = 4 + flags = CONDUCT //Copied this from old code, so this may or may not be necessary + var/status = 0 //0 - not readied //1 - bomb finished with welder + var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device + var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank + origin_tech = "materials=1;engineering=1" + +/obj/item/onetankbomb/examine(mob/user) + . = ..() + . += bombtank.examine(user) + +/obj/item/onetankbomb/update_icon() + if(bombtank) + icon_state = bombtank.icon_state + if(bombassembly) + overlays += bombassembly.icon_state + overlays += bombassembly.overlays + overlays += "bomb_assembly" + +/obj/item/onetankbomb/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/analyzer)) + bombtank.attackby(W, user, params) + return + return ..() + +/obj/item/onetankbomb/wrench_act(mob/user, obj/item/I) //This is basically bomb assembly code inverted. apparently it works. + if(status) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + to_chat(user, "You disassemble [src].") + bombassembly.loc = user.loc + bombassembly.master = null + bombassembly = null + bombtank.loc = user.loc + bombtank.master = null + bombtank = null + qdel(src) + +/obj/item/onetankbomb/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, volume = I.tool_volume)) + return + if(!status) + status = TRUE + investigate_log("[key_name(user)] welded a single tank bomb. Temperature: [bombtank.air_contents.temperature-T0C]", INVESTIGATE_BOMB) + msg_admin_attack("[key_name_admin(user)] welded a single tank bomb. Temperature: [bombtank.air_contents.temperature-T0C]", ATKLOG_FEW) + log_game("[key_name(user)] welded a single tank bomb. Temperature: [bombtank.air_contents.temperature - T0C]") + to_chat(user, "A pressure hole has been bored to [bombtank] valve. [bombtank] can now be ignited.") + else + status = FALSE + investigate_log("[key_name(user)] unwelded a single tank bomb. Temperature: [bombtank.air_contents.temperature-T0C]", INVESTIGATE_BOMB) + to_chat(user, "The hole has been closed.") + + +/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly + bombassembly.attack_self(user, 1) + add_fingerprint(user) + return + +/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. + visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*") + sleep(10) + if(!src) + return + if(status) + bombtank.detonate() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file + else + bombtank.release() + +/obj/item/onetankbomb/HasProximity(atom/movable/AM) + if(bombassembly) + bombassembly.HasProximity(AM) + +/obj/item/onetankbomb/Crossed(atom/movable/AM, oldloc) //for mousetraps + if(bombassembly) + bombassembly.Crossed(AM, oldloc) + +/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps + if(bombassembly) + bombassembly.on_found(finder) + +/obj/item/onetankbomb/hear_talk(mob/living/M, list/message_pieces) + if(bombassembly) + bombassembly.hear_talk(M, message_pieces) + +/obj/item/onetankbomb/hear_message(mob/living/M, msg) + if(bombassembly) + bombassembly.hear_message(M, msg) + +// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ---------- + +/obj/item/tank/proc/bomb_assemble(W,user) //Bomb assembly proc. This turns assembly+tank into a bomb + var/obj/item/assembly_holder/S = W + var/mob/M = user + if(!S.secured) //Check if the assembly is secured + return + if(isigniter(S.a_left) == isigniter(S.a_right)) //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it + return + + var/obj/item/onetankbomb/R = new /obj/item/onetankbomb(loc) + + M.drop_item() //Remove the assembly from your hands + M.remove_from_mob(src) //Remove the tank from your character,in case you were holding it + M.put_in_hands(R) //Equips the bomb if possible, or puts it on the floor. + + R.bombassembly = S //Tell the bomb about its assembly part + S.master = R //Tell the assembly about its new owner + S.loc = R //Move the assembly out of the fucking way + + R.bombtank = src //Same for tank + master = R + loc = R + R.update_icon() + return + +/obj/item/tank/proc/detonate() //This happens when a bomb is told to explode + var/fuel_moles = air_contents.toxins + air_contents.oxygen/6 + var/strength = 1 + + var/turf/ground_zero = get_turf(loc) + loc = null + + if(air_contents.temperature > (T0C + 400)) + strength = (fuel_moles/15) + + if(strength >=1) + explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1)) + else if(strength >=0.5) + explosion(ground_zero, 0, 1, 2, 4) + else if(strength >=0.2) + explosion(ground_zero, -1, 0, 1, 2) + else + ground_zero.assume_air(air_contents) + ground_zero.hotspot_expose(1000, 125) + + else if(air_contents.temperature > (T0C + 250)) + strength = (fuel_moles/20) + + if(strength >=1) + explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1)) + else if(strength >=0.5) + explosion(ground_zero, -1, 0, 1, 2) + else + ground_zero.assume_air(air_contents) + ground_zero.hotspot_expose(1000, 125) + + else if(air_contents.temperature > (T0C + 100)) + strength = (fuel_moles/25) + + if(strength >=1) + explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1)) + else + ground_zero.assume_air(air_contents) + ground_zero.hotspot_expose(1000, 125) + + else + ground_zero.assume_air(air_contents) + ground_zero.hotspot_expose(1000, 125) + + air_update_turf() + if(master) + qdel(master) + qdel(src) + +/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out. + var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles()) + var/turf/simulated/T = get_turf(src) + if(!T) + return + T.assume_air(removed) + air_update_turf() diff --git a/code/modules/assembly/health.dm b/code/modules/assembly/health.dm index 6648a9ce22d..a83df5501c9 100644 --- a/code/modules/assembly/health.dm +++ b/code/modules/assembly/health.dm @@ -104,4 +104,4 @@ return attack_self(user) - return \ No newline at end of file + return diff --git a/code/modules/assembly/helpers.dm b/code/modules/assembly/helpers.dm index eef33d2763d..9b5e93931a7 100644 --- a/code/modules/assembly/helpers.dm +++ b/code/modules/assembly/helpers.dm @@ -1,44 +1,44 @@ -/proc/isassembly(O) - if(istype(O, /obj/item/assembly)) - return 1 - return 0 - -/proc/isigniter(O) - if(istype(O, /obj/item/assembly/igniter)) - return 1 - return 0 - -/proc/isinfared(O) - if(istype(O, /obj/item/assembly/infra)) - return 1 - return 0 - -/proc/isprox(O) - if(istype(O, /obj/item/assembly/prox_sensor)) - return 1 - return 0 - -/proc/issignaler(O) - if(istype(O, /obj/item/assembly/signaler)) - return 1 - return 0 - -/proc/istimer(O) - if(istype(O, /obj/item/assembly/timer)) - return 1 - return 0 - -/* -Name: IsSpecialAssembly -Desc: If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door -*/ - -/obj/proc/IsSpecialAssembly() - return 0 - -/* -Name: IsAssemblyHolder -Desc: If true is an object that can hold an assemblyholder object -*/ -/obj/proc/IsAssemblyHolder() - return 0 \ No newline at end of file +/proc/isassembly(O) + if(istype(O, /obj/item/assembly)) + return 1 + return 0 + +/proc/isigniter(O) + if(istype(O, /obj/item/assembly/igniter)) + return 1 + return 0 + +/proc/isinfared(O) + if(istype(O, /obj/item/assembly/infra)) + return 1 + return 0 + +/proc/isprox(O) + if(istype(O, /obj/item/assembly/prox_sensor)) + return 1 + return 0 + +/proc/issignaler(O) + if(istype(O, /obj/item/assembly/signaler)) + return 1 + return 0 + +/proc/istimer(O) + if(istype(O, /obj/item/assembly/timer)) + return 1 + return 0 + +/* +Name: IsSpecialAssembly +Desc: If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door +*/ + +/obj/proc/IsSpecialAssembly() + return 0 + +/* +Name: IsAssemblyHolder +Desc: If true is an object that can hold an assemblyholder object +*/ +/obj/proc/IsAssemblyHolder() + return 0 diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm index 236ee94bd9d..6a6cd7e8d8e 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -1,197 +1,197 @@ -/obj/item/assembly_holder - name = "Assembly" - icon = 'icons/obj/assemblies/new_assemblies.dmi' - icon_state = "holder" - item_state = "assembly" - flags = CONDUCT - throwforce = 5 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 10 - - var/secured = FALSE - var/obj/item/assembly/a_left = null - var/obj/item/assembly/a_right = null - -/obj/item/assembly_holder/proc/attach(obj/item/D, obj/item/D2, mob/user) - return - -/obj/item/assembly_holder/proc/process_activation(var/obj/item/D) - return - -/obj/item/assembly_holder/IsAssemblyHolder() - return TRUE - -/obj/item/assembly_holder/Destroy() - if(a_left) - a_left.holder = null - if(a_right) - a_right.holder = null - return ..() - -/obj/item/assembly_holder/attach(obj/item/D, obj/item/D2, mob/user) - if(!D || !D2) - return FALSE - if(!isassembly(D) || !isassembly(D2)) - return FALSE - var/obj/item/assembly/A1 = D - var/obj/item/assembly/A2 = D2 - if(A1.secured || A2.secured) - return FALSE - if(!A1.remove_item_from_storage(src)) - if(user) - user.remove_from_mob(A1) - A1.loc = src - if(!A2.remove_item_from_storage(src)) - if(user) - user.remove_from_mob(A2) - A2.loc = src - A1.holder = src - A2.holder = src - a_left = A1 - a_right = A2 - name = "[A1.name]-[A2.name] assembly" - update_icon() - return TRUE - - -/obj/item/assembly_holder/update_icon() - overlays.Cut() - if(a_left) - overlays += "[a_left.icon_state]_left" - for(var/O in a_left.attached_overlays) - overlays += "[O]_l" - if(a_right) - overlays += "[a_right.icon_state]_right" - for(var/O in a_right.attached_overlays) - overlays += "[O]_r" - if(master) - master.update_icon() - - -/obj/item/assembly_holder/examine(mob/user) - . = ..() - if(in_range(src, user) || loc == user) - if(secured) - . += "[src] is ready!" - else - . += "[src] can be attached!" - - -/obj/item/assembly_holder/HasProximity(atom/movable/AM) - if(a_left) - a_left.HasProximity(AM) - if(a_right) - a_right.HasProximity(AM) - - -/obj/item/assembly_holder/Crossed(atom/movable/AM, oldloc) - if(a_left) - a_left.Crossed(AM, oldloc) - if(a_right) - a_right.Crossed(AM, oldloc) - -/obj/item/assembly_holder/on_found(mob/finder) - if(a_left) - a_left.on_found(finder) - if(a_right) - a_right.on_found(finder) - - -/obj/item/assembly_holder/hear_talk(mob/living/M, list/message_pieces) - if(a_left) - a_left.hear_talk(M, message_pieces) - if(a_right) - a_right.hear_talk(M, message_pieces) - -/obj/item/assembly_holder/hear_message(mob/living/M, msg) - if(a_left) - a_left.hear_message(M, msg) - if(a_right) - a_right.hear_message(M, msg) - -/obj/item/assembly_holder/proc/process_movement() // infrared beams and prox sensors - if(a_left && a_right) - a_left.holder_movement() - a_right.holder_movement() - -/obj/item/assembly_holder/Move() - . = ..() - process_movement() - return - -/obj/item/assembly_holder/pickup() - . = ..() - process_movement() - -/obj/item/assembly_holder/Bump() - ..() - process_movement() - -/obj/item/assembly_holder/throw_impact() // called when a throw stops - ..() - process_movement() - -/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess - if(a_left && a_right) - a_left.holder_movement() - a_right.holder_movement() - ..() - return - -/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/I) - if(!a_left || !a_right) - to_chat(user, "BUG:Assembly part missing, please report this!") - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - a_left.toggle_secure() - a_right.toggle_secure() - secured = !secured - if(secured) - to_chat(user, "[src] is ready!") - else - to_chat(user, "[src] can now be taken apart!") - update_icon() - -/obj/item/assembly_holder/attack_self(mob/user) - add_fingerprint(user) - if(secured) - if(!a_left || !a_right) - to_chat(user, "Assembly part missing!") - return - if(istype(a_left, a_right.type))//If they are the same type it causes issues due to window code - switch(alert("Which side would you like to use?",,"Left","Right")) - if("Left") - a_left.attack_self(user) - if("Right") - a_right.attack_self(user) - return - else - a_left.attack_self(user) - a_right.attack_self(user) - else - var/turf/T = get_turf(src) - if(!T) - return FALSE - if(a_left) - a_left.holder = null - a_left.loc = T - if(a_right) - a_right.holder = null - a_right.loc = T - qdel(src) - - -/obj/item/assembly_holder/process_activation(obj/D, normal = TRUE, special = TRUE) - if(!D) - return FALSE - if(normal && a_right && a_left) - if(a_right != D) - a_right.pulsed(0) - if(a_left != D) - a_left.pulsed(0) - if(master) - master.receive_signal() - return TRUE \ No newline at end of file +/obj/item/assembly_holder + name = "Assembly" + icon = 'icons/obj/assemblies/new_assemblies.dmi' + icon_state = "holder" + item_state = "assembly" + flags = CONDUCT + throwforce = 5 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 10 + + var/secured = FALSE + var/obj/item/assembly/a_left = null + var/obj/item/assembly/a_right = null + +/obj/item/assembly_holder/proc/attach(obj/item/D, obj/item/D2, mob/user) + return + +/obj/item/assembly_holder/proc/process_activation(var/obj/item/D) + return + +/obj/item/assembly_holder/IsAssemblyHolder() + return TRUE + +/obj/item/assembly_holder/Destroy() + if(a_left) + a_left.holder = null + if(a_right) + a_right.holder = null + return ..() + +/obj/item/assembly_holder/attach(obj/item/D, obj/item/D2, mob/user) + if(!D || !D2) + return FALSE + if(!isassembly(D) || !isassembly(D2)) + return FALSE + var/obj/item/assembly/A1 = D + var/obj/item/assembly/A2 = D2 + if(A1.secured || A2.secured) + return FALSE + if(!A1.remove_item_from_storage(src)) + if(user) + user.remove_from_mob(A1) + A1.loc = src + if(!A2.remove_item_from_storage(src)) + if(user) + user.remove_from_mob(A2) + A2.loc = src + A1.holder = src + A2.holder = src + a_left = A1 + a_right = A2 + name = "[A1.name]-[A2.name] assembly" + update_icon() + return TRUE + + +/obj/item/assembly_holder/update_icon() + overlays.Cut() + if(a_left) + overlays += "[a_left.icon_state]_left" + for(var/O in a_left.attached_overlays) + overlays += "[O]_l" + if(a_right) + overlays += "[a_right.icon_state]_right" + for(var/O in a_right.attached_overlays) + overlays += "[O]_r" + if(master) + master.update_icon() + + +/obj/item/assembly_holder/examine(mob/user) + . = ..() + if(in_range(src, user) || loc == user) + if(secured) + . += "[src] is ready!" + else + . += "[src] can be attached!" + + +/obj/item/assembly_holder/HasProximity(atom/movable/AM) + if(a_left) + a_left.HasProximity(AM) + if(a_right) + a_right.HasProximity(AM) + + +/obj/item/assembly_holder/Crossed(atom/movable/AM, oldloc) + if(a_left) + a_left.Crossed(AM, oldloc) + if(a_right) + a_right.Crossed(AM, oldloc) + +/obj/item/assembly_holder/on_found(mob/finder) + if(a_left) + a_left.on_found(finder) + if(a_right) + a_right.on_found(finder) + + +/obj/item/assembly_holder/hear_talk(mob/living/M, list/message_pieces) + if(a_left) + a_left.hear_talk(M, message_pieces) + if(a_right) + a_right.hear_talk(M, message_pieces) + +/obj/item/assembly_holder/hear_message(mob/living/M, msg) + if(a_left) + a_left.hear_message(M, msg) + if(a_right) + a_right.hear_message(M, msg) + +/obj/item/assembly_holder/proc/process_movement() // infrared beams and prox sensors + if(a_left && a_right) + a_left.holder_movement() + a_right.holder_movement() + +/obj/item/assembly_holder/Move() + . = ..() + process_movement() + return + +/obj/item/assembly_holder/pickup() + . = ..() + process_movement() + +/obj/item/assembly_holder/Bump() + ..() + process_movement() + +/obj/item/assembly_holder/throw_impact() // called when a throw stops + ..() + process_movement() + +/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess + if(a_left && a_right) + a_left.holder_movement() + a_right.holder_movement() + ..() + return + +/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/I) + if(!a_left || !a_right) + to_chat(user, "BUG:Assembly part missing, please report this!") + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + a_left.toggle_secure() + a_right.toggle_secure() + secured = !secured + if(secured) + to_chat(user, "[src] is ready!") + else + to_chat(user, "[src] can now be taken apart!") + update_icon() + +/obj/item/assembly_holder/attack_self(mob/user) + add_fingerprint(user) + if(secured) + if(!a_left || !a_right) + to_chat(user, "Assembly part missing!") + return + if(istype(a_left, a_right.type))//If they are the same type it causes issues due to window code + switch(alert("Which side would you like to use?",,"Left","Right")) + if("Left") + a_left.attack_self(user) + if("Right") + a_right.attack_self(user) + return + else + a_left.attack_self(user) + a_right.attack_self(user) + else + var/turf/T = get_turf(src) + if(!T) + return FALSE + if(a_left) + a_left.holder = null + a_left.loc = T + if(a_right) + a_right.holder = null + a_right.loc = T + qdel(src) + + +/obj/item/assembly_holder/process_activation(obj/D, normal = TRUE, special = TRUE) + if(!D) + return FALSE + if(normal && a_right && a_left) + if(a_right != D) + a_right.pulsed(0) + if(a_left != D) + a_left.pulsed(0) + if(master) + master.receive_signal() + return TRUE diff --git a/code/modules/assembly/igniter.dm b/code/modules/assembly/igniter.dm index 3bbfe65bbc0..b81acbbe2b0 100644 --- a/code/modules/assembly/igniter.dm +++ b/code/modules/assembly/igniter.dm @@ -1,45 +1,45 @@ -/obj/item/assembly/igniter - name = "igniter" - desc = "A small electronic device able to ignite combustable substances." - icon_state = "igniter" - materials = list(MAT_METAL=500, MAT_GLASS=50) - origin_tech = "magnets=1" - var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread - -/obj/item/assembly/igniter/New() - ..() - sparks.set_up(2, 0, src) - sparks.attach(src) - -/obj/item/assembly/igniter/Destroy() - QDEL_NULL(sparks) - return ..() - - -/obj/item/assembly/igniter/describe() - return "The igniter is [secured ? "secured." : "unsecured."]" - - -/obj/item/assembly/igniter/activate() - if(!..()) - return FALSE//Cooldown check - var/turf/location = get_turf(loc) - if(location) - location.hotspot_expose(1000,1000) - if(istype(loc, /obj/item/assembly_holder)) - if(istype(loc.loc, /obj/structure/reagent_dispensers/fueltank)) - var/obj/structure/reagent_dispensers/fueltank/tank = loc.loc - if(tank) - tank.boom(TRUE) - if(istype(loc.loc, /obj/item/reagent_containers/glass/beaker)) - var/obj/item/reagent_containers/glass/beaker/beakerbomb = loc.loc - if(beakerbomb) - beakerbomb.heat_beaker() - sparks.start() - return TRUE - - -/obj/item/assembly/igniter/attack_self(mob/user) - activate() - add_fingerprint(user) - return +/obj/item/assembly/igniter + name = "igniter" + desc = "A small electronic device able to ignite combustable substances." + icon_state = "igniter" + materials = list(MAT_METAL=500, MAT_GLASS=50) + origin_tech = "magnets=1" + var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread + +/obj/item/assembly/igniter/New() + ..() + sparks.set_up(2, 0, src) + sparks.attach(src) + +/obj/item/assembly/igniter/Destroy() + QDEL_NULL(sparks) + return ..() + + +/obj/item/assembly/igniter/describe() + return "The igniter is [secured ? "secured." : "unsecured."]" + + +/obj/item/assembly/igniter/activate() + if(!..()) + return FALSE//Cooldown check + var/turf/location = get_turf(loc) + if(location) + location.hotspot_expose(1000,1000) + if(istype(loc, /obj/item/assembly_holder)) + if(istype(loc.loc, /obj/structure/reagent_dispensers/fueltank)) + var/obj/structure/reagent_dispensers/fueltank/tank = loc.loc + if(tank) + tank.boom(TRUE) + if(istype(loc.loc, /obj/item/reagent_containers/glass/beaker)) + var/obj/item/reagent_containers/glass/beaker/beakerbomb = loc.loc + if(beakerbomb) + beakerbomb.heat_beaker() + sparks.start() + return TRUE + + +/obj/item/assembly/igniter/attack_self(mob/user) + activate() + add_fingerprint(user) + return diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm index b9d3ee2afc1..19263e010ab 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -1,286 +1,286 @@ -/obj/item/assembly/infra - name = "infrared emitter" - desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted." - icon_state = "infrared" - materials = list(MAT_METAL=1000, MAT_GLASS=500) - origin_tech = "magnets=2;materials=2" - - bomb_name = "tripwire mine" - - secured = FALSE // toggle_secure()'ed in New() for correct adding to processing_objects, won't work otherwise - dir = EAST - var/on = FALSE - var/visible = TRUE - var/obj/effect/beam/i_beam/first = null - var/obj/effect/beam/i_beam/last = null - var/max_nesting_level = 10 - var/turf/fire_location - var/emission_cycles = 0 - var/emission_cap = 20 - -/obj/item/assembly/infra/Destroy() - if(first) - QDEL_NULL(first) - last = null - fire_location = null - return ..() - -/obj/item/assembly/infra/describe() - return "The assembly is [secured ? "secure" : "not secure"]. The infrared trigger is [on ? "on" : "off"]." - -/obj/item/assembly/infra/examine(mob/user) - . = ..() - . += describe() - -/obj/item/assembly/infra/activate() - if(!..()) - return FALSE//Cooldown check - on = !on - update_icon() - return TRUE - -/obj/item/assembly/infra/toggle_secure() - secured = !secured - if(secured) - START_PROCESSING(SSobj, src) - else - on = FALSE - if(first) - qdel(first) - STOP_PROCESSING(SSobj, src) - update_icon() - return secured - -/obj/item/assembly/infra/New() - ..() - if(!secured) - toggle_secure() - -/obj/item/assembly/infra/proc/arm() // Forces the device to arm no matter its current state. - if(!secured) // Checked because arm() might be called sometime after the object is spawned. - toggle_secure() - on = 1 - -/obj/item/assembly/infra/update_icon() - overlays.Cut() - attached_overlays = list() - if(on) - overlays += "infrared_on" - attached_overlays += "infrared_on" - - if(holder) - holder.update_icon() - -/obj/item/assembly/infra/process() - var/turf/T = get_turf(src) - if(first && (!on || !fire_location || fire_location != T || emission_cycles >= emission_cap)) - qdel(first) - return - if(!on) - return - if(!secured) - return - if(first && last) - last.process() - emission_cycles++ - return - if(T) - fire_location = T - emission_cycles = 0 - var/obj/effect/beam/i_beam/I = new /obj/effect/beam/i_beam(T) - I.master = src - I.density = 1 - I.dir = dir - I.update_icon() - first = I - step(I, I.dir) - if(first) - I.density = FALSE - I.vis_spread(visible) - I.limit = 8 - I.process() - -/obj/item/assembly/infra/attack_hand() - qdel(first) - ..() - -/obj/item/assembly/infra/Move() - var/t = dir - . = ..() - dir = t - qdel(first) - -/obj/item/assembly/infra/holder_movement() - if(!holder) - return FALSE - qdel(first) - return TRUE - -/obj/item/assembly/infra/equipped(var/mob/user, var/slot) - qdel(first) - return ..() - -/obj/item/assembly/infra/pickup(mob/user) - qdel(first) - return ..() - -/obj/item/assembly/infra/proc/trigger_beam() - if(!secured || !on || cooldown > 0) - return FALSE - pulse(0) - audible_message("[bicon(src)] *beep* *beep*", null, 3) - if(first) - qdel(first) - cooldown = 2 - spawn(10) - process_cooldown() - -/obj/item/assembly/infra/interact(mob/user)//TODO: change this this to the wire control panel - if(!secured) return - user.set_machine(src) - var/dat = {"Infrared Laser - Status: [on ? "On" : "Off"]
        - Visibility: [visible ? "Visible" : "Invisible"]
        - Current Direction: [capitalize(dir2text(dir))]
        -
        -

        Refresh -

        Close"} - var/datum/browser/popup = new(user, "infra", name, 400, 400) - popup.set_content(dat) - popup.open(0) - onclose(user, "infra") - -/obj/item/assembly/infra/Topic(href, href_list) - ..() - if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) - usr << browse(null, "window=infra") - onclose(usr, "infra") - return - if(href_list["state"]) - on = !(on) - update_icon() - if(href_list["visible"]) - visible = !(visible) - if(first) - first.vis_spread(visible) - if(href_list["rotate"]) - rotate() - if(href_list["close"]) - usr << browse(null, "window=infra") - return - if(usr) - attack_self(usr) - -/obj/item/assembly/infra/verb/rotate()//This could likely be better - set name = "Rotate Infrared Laser" - set category = "Object" - set src in usr - - if(usr.stat || !usr.canmove || usr.restrained()) - return - - dir = turn(dir, 90) - - if(usr.machine == src) - interact(usr) - - if(first) - qdel(first) - - - -/obj/item/assembly/infra/armed/New() - ..() - spawn(3) - if(holder) - if(holder.master) - dir = holder.master.dir - arm() - -/obj/item/assembly/infra/armed/stealth - visible = FALSE - - -/***************************IBeam*********************************/ - -/obj/effect/beam/i_beam - name = "i beam" - icon = 'icons/obj/projectiles.dmi' - icon_state = "ibeam" - var/obj/effect/beam/i_beam/next = null - var/obj/effect/beam/i_beam/previous = null - var/obj/item/assembly/infra/master = null - var/limit = null - var/visible = FALSE - var/left = null - var/life_cycles = 0 - var/life_cap = 20 - anchored = TRUE - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - - -/obj/effect/beam/i_beam/proc/hit() - if(master) - master.trigger_beam() - qdel(src) - -/obj/effect/beam/i_beam/proc/vis_spread(v) - visible = v - if(next) - next.vis_spread(v) - -/obj/effect/beam/i_beam/update_icon() - transform = turn(matrix(), dir2angle(dir)) - -/obj/effect/beam/i_beam/process() - life_cycles++ - if(loc.density || !master || life_cycles >= life_cap) - qdel(src) - return - if(left > 0) - left-- - if(left < 1) - if(!(visible)) - invisibility = 101 - else - invisibility = FALSE - else - invisibility = FALSE - - if(!next && (limit > 0)) - var/obj/effect/beam/i_beam/I = new /obj/effect/beam/i_beam(loc) - I.master = master - I.density = 1 - I.dir = dir - I.update_icon() - I.previous = src - next = I - step(I, I.dir) - if(next) - I.density = FALSE - I.vis_spread(visible) - I.limit = limit - 1 - master.last = I - I.process() - -/obj/effect/beam/i_beam/Bump() - qdel(src) - -/obj/effect/beam/i_beam/Bumped() - hit() - -/obj/effect/beam/i_beam/Crossed(atom/movable/AM, oldloc) - if(!isobj(AM) && !isliving(AM)) - return - if(istype(AM, /obj/effect)) - return - hit() - -/obj/effect/beam/i_beam/Destroy() - if(master.first == src) - master.first = null - QDEL_NULL(next) - if(previous) - previous.next = null - master.last = previous - return ..() +/obj/item/assembly/infra + name = "infrared emitter" + desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted." + icon_state = "infrared" + materials = list(MAT_METAL=1000, MAT_GLASS=500) + origin_tech = "magnets=2;materials=2" + + bomb_name = "tripwire mine" + + secured = FALSE // toggle_secure()'ed in New() for correct adding to processing_objects, won't work otherwise + dir = EAST + var/on = FALSE + var/visible = TRUE + var/obj/effect/beam/i_beam/first = null + var/obj/effect/beam/i_beam/last = null + var/max_nesting_level = 10 + var/turf/fire_location + var/emission_cycles = 0 + var/emission_cap = 20 + +/obj/item/assembly/infra/Destroy() + if(first) + QDEL_NULL(first) + last = null + fire_location = null + return ..() + +/obj/item/assembly/infra/describe() + return "The assembly is [secured ? "secure" : "not secure"]. The infrared trigger is [on ? "on" : "off"]." + +/obj/item/assembly/infra/examine(mob/user) + . = ..() + . += describe() + +/obj/item/assembly/infra/activate() + if(!..()) + return FALSE//Cooldown check + on = !on + update_icon() + return TRUE + +/obj/item/assembly/infra/toggle_secure() + secured = !secured + if(secured) + START_PROCESSING(SSobj, src) + else + on = FALSE + if(first) + qdel(first) + STOP_PROCESSING(SSobj, src) + update_icon() + return secured + +/obj/item/assembly/infra/New() + ..() + if(!secured) + toggle_secure() + +/obj/item/assembly/infra/proc/arm() // Forces the device to arm no matter its current state. + if(!secured) // Checked because arm() might be called sometime after the object is spawned. + toggle_secure() + on = 1 + +/obj/item/assembly/infra/update_icon() + overlays.Cut() + attached_overlays = list() + if(on) + overlays += "infrared_on" + attached_overlays += "infrared_on" + + if(holder) + holder.update_icon() + +/obj/item/assembly/infra/process() + var/turf/T = get_turf(src) + if(first && (!on || !fire_location || fire_location != T || emission_cycles >= emission_cap)) + qdel(first) + return + if(!on) + return + if(!secured) + return + if(first && last) + last.process() + emission_cycles++ + return + if(T) + fire_location = T + emission_cycles = 0 + var/obj/effect/beam/i_beam/I = new /obj/effect/beam/i_beam(T) + I.master = src + I.density = 1 + I.dir = dir + I.update_icon() + first = I + step(I, I.dir) + if(first) + I.density = FALSE + I.vis_spread(visible) + I.limit = 8 + I.process() + +/obj/item/assembly/infra/attack_hand() + qdel(first) + ..() + +/obj/item/assembly/infra/Move() + var/t = dir + . = ..() + dir = t + qdel(first) + +/obj/item/assembly/infra/holder_movement() + if(!holder) + return FALSE + qdel(first) + return TRUE + +/obj/item/assembly/infra/equipped(var/mob/user, var/slot) + qdel(first) + return ..() + +/obj/item/assembly/infra/pickup(mob/user) + qdel(first) + return ..() + +/obj/item/assembly/infra/proc/trigger_beam() + if(!secured || !on || cooldown > 0) + return FALSE + pulse(0) + audible_message("[bicon(src)] *beep* *beep*", null, 3) + if(first) + qdel(first) + cooldown = 2 + spawn(10) + process_cooldown() + +/obj/item/assembly/infra/interact(mob/user)//TODO: change this this to the wire control panel + if(!secured) return + user.set_machine(src) + var/dat = {"Infrared Laser + Status: [on ? "On" : "Off"]
        + Visibility: [visible ? "Visible" : "Invisible"]
        + Current Direction: [capitalize(dir2text(dir))]
        +
        +

        Refresh +

        Close"} + var/datum/browser/popup = new(user, "infra", name, 400, 400) + popup.set_content(dat) + popup.open(0) + onclose(user, "infra") + +/obj/item/assembly/infra/Topic(href, href_list) + ..() + if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) + usr << browse(null, "window=infra") + onclose(usr, "infra") + return + if(href_list["state"]) + on = !(on) + update_icon() + if(href_list["visible"]) + visible = !(visible) + if(first) + first.vis_spread(visible) + if(href_list["rotate"]) + rotate() + if(href_list["close"]) + usr << browse(null, "window=infra") + return + if(usr) + attack_self(usr) + +/obj/item/assembly/infra/verb/rotate()//This could likely be better + set name = "Rotate Infrared Laser" + set category = "Object" + set src in usr + + if(usr.stat || !usr.canmove || usr.restrained()) + return + + dir = turn(dir, 90) + + if(usr.machine == src) + interact(usr) + + if(first) + qdel(first) + + + +/obj/item/assembly/infra/armed/New() + ..() + spawn(3) + if(holder) + if(holder.master) + dir = holder.master.dir + arm() + +/obj/item/assembly/infra/armed/stealth + visible = FALSE + + +/***************************IBeam*********************************/ + +/obj/effect/beam/i_beam + name = "i beam" + icon = 'icons/obj/projectiles.dmi' + icon_state = "ibeam" + var/obj/effect/beam/i_beam/next = null + var/obj/effect/beam/i_beam/previous = null + var/obj/item/assembly/infra/master = null + var/limit = null + var/visible = FALSE + var/left = null + var/life_cycles = 0 + var/life_cap = 20 + anchored = TRUE + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + + +/obj/effect/beam/i_beam/proc/hit() + if(master) + master.trigger_beam() + qdel(src) + +/obj/effect/beam/i_beam/proc/vis_spread(v) + visible = v + if(next) + next.vis_spread(v) + +/obj/effect/beam/i_beam/update_icon() + transform = turn(matrix(), dir2angle(dir)) + +/obj/effect/beam/i_beam/process() + life_cycles++ + if(loc.density || !master || life_cycles >= life_cap) + qdel(src) + return + if(left > 0) + left-- + if(left < 1) + if(!(visible)) + invisibility = 101 + else + invisibility = FALSE + else + invisibility = FALSE + + if(!next && (limit > 0)) + var/obj/effect/beam/i_beam/I = new /obj/effect/beam/i_beam(loc) + I.master = master + I.density = 1 + I.dir = dir + I.update_icon() + I.previous = src + next = I + step(I, I.dir) + if(next) + I.density = FALSE + I.vis_spread(visible) + I.limit = limit - 1 + master.last = I + I.process() + +/obj/effect/beam/i_beam/Bump() + qdel(src) + +/obj/effect/beam/i_beam/Bumped() + hit() + +/obj/effect/beam/i_beam/Crossed(atom/movable/AM, oldloc) + if(!isobj(AM) && !isliving(AM)) + return + if(istype(AM, /obj/effect)) + return + hit() + +/obj/effect/beam/i_beam/Destroy() + if(master.first == src) + master.first = null + QDEL_NULL(next) + if(previous) + previous.next = null + master.last = previous + return ..() diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index f4869f10d33..9b4c9b38677 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -1,144 +1,144 @@ -/obj/item/assembly/mousetrap - name = "mousetrap" - desc = "A handy little spring-loaded trap for catching pesty rodents." - icon_state = "mousetrap" - materials = list(MAT_METAL=100) - origin_tech = "combat=1;materials=2;engineering=1" - var/armed = FALSE - - bomb_name = "contact mine" - -/obj/item/assembly/mousetrap/examine(mob/user) - . = ..() - if(armed) - . += "It looks like it's armed." - -/obj/item/assembly/mousetrap/activate() - if(..()) - armed = !armed - if(!armed) - if(ishuman(usr)) - var/mob/living/carbon/human/user = usr - if((user.getBrainLoss() >= 60 || (CLUMSY in user.mutations)) && prob(50)) - to_chat(user, "Your hand slips, setting off the trigger.") - pulse(0) - update_icon() - if(usr) - playsound(usr.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3) - -/obj/item/assembly/mousetrap/describe() - return "The pressure switch is [armed ? "primed" : "safe"]." - -/obj/item/assembly/mousetrap/update_icon() - if(armed) - icon_state = "mousetraparmed" - else - icon_state = "mousetrap" - if(holder) - holder.update_icon() - -/obj/item/assembly/mousetrap/proc/triggered(mob/target, type = "feet") - if(!armed) - return - var/obj/item/organ/external/affecting = null - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(PIERCEIMMUNE in H.dna.species.species_traits) - playsound(src, 'sound/effects/snap.ogg', 50, TRUE) - armed = FALSE - update_icon() - pulse(FALSE) - return FALSE - switch(type) - if("feet") - if(!H.shoes) - affecting = H.get_organ(pick("l_leg", "r_leg")) - H.Weaken(3) - if("l_hand", "r_hand") - if(!H.gloves) - affecting = H.get_organ(type) - H.Stun(3) - if(affecting) - affecting.receive_damage(1, 0) - else if(ismouse(target)) - var/mob/living/simple_animal/mouse/M = target - visible_message("SPLAT!") - M.splat() - playsound(loc, 'sound/effects/snap.ogg', 50, 1) - layer = MOB_LAYER - 0.2 - armed = FALSE - update_icon() - pulse(0) - -/obj/item/assembly/mousetrap/attack_self(mob/living/user) - if(!armed) - to_chat(user, "You arm [src].") - else - if((user.getBrainLoss() >= 60 || (CLUMSY in user.mutations)) && prob(50)) - var/which_hand = "l_hand" - if(!user.hand) - which_hand = "r_hand" - triggered(user, which_hand) - user.visible_message("[user] accidentally sets off [src], breaking [user.p_their()] fingers.", \ - "You accidentally trigger [src]!") - return - to_chat(user, "You disarm [src].") - armed = !armed - update_icon() - playsound(user.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3) - -/obj/item/assembly/mousetrap/attack_hand(mob/living/user) - if(armed) - if((user.getBrainLoss() >= 60 || CLUMSY in user.mutations) && prob(50)) - var/which_hand = "l_hand" - if(!user.hand) - which_hand = "r_hand" - triggered(user, which_hand) - user.visible_message("[user] accidentally sets off [src], breaking [user.p_their()] fingers.", \ - "You accidentally trigger [src]!") - return - ..() - -/obj/item/assembly/mousetrap/Crossed(atom/movable/AM, oldloc) - if(armed) - if(ishuman(AM)) - var/mob/living/carbon/H = AM - if(H.m_intent == MOVE_INTENT_RUN) - triggered(H) - H.visible_message("[H] accidentally steps on [src].", \ - "You accidentally step on [src]") - else if(ismouse(AM)) - triggered(AM) - else if(AM.density) // For mousetrap grenades, set off by anything heavy - triggered(AM) - ..() - -/obj/item/assembly/mousetrap/on_found(mob/finder) - if(armed) - finder.visible_message("[finder] accidentally sets off [src], breaking [finder.p_their()] fingers.", \ - "You accidentally trigger [src]!") - triggered(finder, finder.hand ? "l_hand" : "r_hand") - return TRUE //end the search! - return FALSE - -/obj/item/assembly/mousetrap/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(!armed) - return ..() - visible_message("[src] is triggered by [AM].") - triggered(null) - -/obj/item/assembly/mousetrap/armed - icon_state = "mousetraparmed" - armed = 1 - - -/obj/item/assembly/mousetrap/verb/hide_under() - set src in oview(1) - set name = "Hide" - set category = "Object" - - if(usr.stat) - return - - layer = TURF_LAYER+0.2 - to_chat(usr, "You hide [src].") +/obj/item/assembly/mousetrap + name = "mousetrap" + desc = "A handy little spring-loaded trap for catching pesty rodents." + icon_state = "mousetrap" + materials = list(MAT_METAL=100) + origin_tech = "combat=1;materials=2;engineering=1" + var/armed = FALSE + + bomb_name = "contact mine" + +/obj/item/assembly/mousetrap/examine(mob/user) + . = ..() + if(armed) + . += "It looks like it's armed." + +/obj/item/assembly/mousetrap/activate() + if(..()) + armed = !armed + if(!armed) + if(ishuman(usr)) + var/mob/living/carbon/human/user = usr + if((user.getBrainLoss() >= 60 || (CLUMSY in user.mutations)) && prob(50)) + to_chat(user, "Your hand slips, setting off the trigger.") + pulse(0) + update_icon() + if(usr) + playsound(usr.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3) + +/obj/item/assembly/mousetrap/describe() + return "The pressure switch is [armed ? "primed" : "safe"]." + +/obj/item/assembly/mousetrap/update_icon() + if(armed) + icon_state = "mousetraparmed" + else + icon_state = "mousetrap" + if(holder) + holder.update_icon() + +/obj/item/assembly/mousetrap/proc/triggered(mob/target, type = "feet") + if(!armed) + return + var/obj/item/organ/external/affecting = null + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(PIERCEIMMUNE in H.dna.species.species_traits) + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) + armed = FALSE + update_icon() + pulse(FALSE) + return FALSE + switch(type) + if("feet") + if(!H.shoes) + affecting = H.get_organ(pick("l_leg", "r_leg")) + H.Weaken(3) + if("l_hand", "r_hand") + if(!H.gloves) + affecting = H.get_organ(type) + H.Stun(3) + if(affecting) + affecting.receive_damage(1, 0) + else if(ismouse(target)) + var/mob/living/simple_animal/mouse/M = target + visible_message("SPLAT!") + M.splat() + playsound(loc, 'sound/effects/snap.ogg', 50, 1) + layer = MOB_LAYER - 0.2 + armed = FALSE + update_icon() + pulse(0) + +/obj/item/assembly/mousetrap/attack_self(mob/living/user) + if(!armed) + to_chat(user, "You arm [src].") + else + if((user.getBrainLoss() >= 60 || (CLUMSY in user.mutations)) && prob(50)) + var/which_hand = "l_hand" + if(!user.hand) + which_hand = "r_hand" + triggered(user, which_hand) + user.visible_message("[user] accidentally sets off [src], breaking [user.p_their()] fingers.", \ + "You accidentally trigger [src]!") + return + to_chat(user, "You disarm [src].") + armed = !armed + update_icon() + playsound(user.loc, 'sound/weapons/handcuffs.ogg', 30, 1, -3) + +/obj/item/assembly/mousetrap/attack_hand(mob/living/user) + if(armed) + if((user.getBrainLoss() >= 60 || CLUMSY in user.mutations) && prob(50)) + var/which_hand = "l_hand" + if(!user.hand) + which_hand = "r_hand" + triggered(user, which_hand) + user.visible_message("[user] accidentally sets off [src], breaking [user.p_their()] fingers.", \ + "You accidentally trigger [src]!") + return + ..() + +/obj/item/assembly/mousetrap/Crossed(atom/movable/AM, oldloc) + if(armed) + if(ishuman(AM)) + var/mob/living/carbon/H = AM + if(H.m_intent == MOVE_INTENT_RUN) + triggered(H) + H.visible_message("[H] accidentally steps on [src].", \ + "You accidentally step on [src]") + else if(ismouse(AM)) + triggered(AM) + else if(AM.density) // For mousetrap grenades, set off by anything heavy + triggered(AM) + ..() + +/obj/item/assembly/mousetrap/on_found(mob/finder) + if(armed) + finder.visible_message("[finder] accidentally sets off [src], breaking [finder.p_their()] fingers.", \ + "You accidentally trigger [src]!") + triggered(finder, finder.hand ? "l_hand" : "r_hand") + return TRUE //end the search! + return FALSE + +/obj/item/assembly/mousetrap/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + if(!armed) + return ..() + visible_message("[src] is triggered by [AM].") + triggered(null) + +/obj/item/assembly/mousetrap/armed + icon_state = "mousetraparmed" + armed = 1 + + +/obj/item/assembly/mousetrap/verb/hide_under() + set src in oview(1) + set name = "Hide" + set category = "Object" + + if(usr.stat) + return + + layer = TURF_LAYER+0.2 + to_chat(usr, "You hide [src].") diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm index e6ae3ec7bc4..7e777f235b1 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -1,134 +1,134 @@ -/obj/item/assembly/prox_sensor - name = "proximity sensor" - desc = "Used for scanning and alerting when someone enters a certain proximity." - icon_state = "prox" - materials = list(MAT_METAL = 800, MAT_GLASS = 200) - origin_tech = "magnets=1;engineering=1" - - secured = 0 - - bomb_name = "proximity mine" - - var/scanning = 0 - var/timing = 0 - var/time = 10 - -/obj/item/assembly/prox_sensor/describe() - if(timing) - return "The proximity sensor is arming." - return "The proximity sensor is [scanning ? "armed" : "disarmed"]." - -/obj/item/assembly/prox_sensor/activate() - if(!..()) - return FALSE //Cooldown check - timing = !timing - update_icon() - return FALSE - -/obj/item/assembly/prox_sensor/toggle_secure() - secured = !secured - if(secured) - START_PROCESSING(SSobj, src) - else - scanning = 0 - timing = 0 - STOP_PROCESSING(SSobj, src) - update_icon() - return secured - -/obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM) - if(!isobj(AM) && !isliving(AM)) - return - if(istype(AM, /obj/effect)) - return - if(AM.move_speed < 12) - sense() - -/obj/item/assembly/prox_sensor/proc/sense() - if(!secured || !scanning || cooldown > 0) - return FALSE - pulse(0) - visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*") - cooldown = 2 - spawn(10) - process_cooldown() - -/obj/item/assembly/prox_sensor/process() - if(timing && (time >= 0)) - time-- - if(timing && time <= 0) - timing = 0 - toggle_scan() - time = 10 - -/obj/item/assembly/prox_sensor/dropped() - ..() - spawn(0) - sense() - return - -/obj/item/assembly/prox_sensor/proc/toggle_scan() - if(!secured) - return FALSE - scanning = !scanning - update_icon() - -/obj/item/assembly/prox_sensor/update_icon() - overlays.Cut() - attached_overlays = list() - if(timing) - overlays += "prox_timing" - attached_overlays += "prox_timing" - if(scanning) - overlays += "prox_scanning" - attached_overlays += "prox_scanning" - if(holder) - holder.update_icon() - -/obj/item/assembly/prox_sensor/Move() - ..() - sense() - -/obj/item/assembly/prox_sensor/holder_movement() - sense() - -/obj/item/assembly/prox_sensor/interact(mob/user)//TODO: Change this to the wires thingy - if(!secured) - user.show_message("The [name] is unsecured!") - return FALSE - var/second = time % 60 - var/minute = (time - second) / 60 - var/dat = text("Proximity Sensor\n[] []:[]\n- - + +\n", (timing ? "Arming" : "Not Arming"), minute, second) - dat += "
        [scanning?"Armed":"Unarmed"] (Movement sensor active when armed!)" - dat += "

        Refresh" - dat += "

        Close" - var/datum/browser/popup = new(user, "prox", name, 400, 400) - popup.set_content(dat) - popup.open(0) - onclose(user, "prox") - -/obj/item/assembly/prox_sensor/Topic(href, href_list) - ..() - if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) - usr << browse(null, "window=prox") - onclose(usr, "prox") - return - - if(href_list["scanning"]) - toggle_scan() - - if(href_list["time"]) - timing = text2num(href_list["time"]) - update_icon() - - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 0), 600) - - if(href_list["close"]) - usr << browse(null, "window=prox") - return - - if(usr) - attack_self(usr) \ No newline at end of file +/obj/item/assembly/prox_sensor + name = "proximity sensor" + desc = "Used for scanning and alerting when someone enters a certain proximity." + icon_state = "prox" + materials = list(MAT_METAL = 800, MAT_GLASS = 200) + origin_tech = "magnets=1;engineering=1" + + secured = 0 + + bomb_name = "proximity mine" + + var/scanning = 0 + var/timing = 0 + var/time = 10 + +/obj/item/assembly/prox_sensor/describe() + if(timing) + return "The proximity sensor is arming." + return "The proximity sensor is [scanning ? "armed" : "disarmed"]." + +/obj/item/assembly/prox_sensor/activate() + if(!..()) + return FALSE //Cooldown check + timing = !timing + update_icon() + return FALSE + +/obj/item/assembly/prox_sensor/toggle_secure() + secured = !secured + if(secured) + START_PROCESSING(SSobj, src) + else + scanning = 0 + timing = 0 + STOP_PROCESSING(SSobj, src) + update_icon() + return secured + +/obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM) + if(!isobj(AM) && !isliving(AM)) + return + if(istype(AM, /obj/effect)) + return + if(AM.move_speed < 12) + sense() + +/obj/item/assembly/prox_sensor/proc/sense() + if(!secured || !scanning || cooldown > 0) + return FALSE + pulse(0) + visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*") + cooldown = 2 + spawn(10) + process_cooldown() + +/obj/item/assembly/prox_sensor/process() + if(timing && (time >= 0)) + time-- + if(timing && time <= 0) + timing = 0 + toggle_scan() + time = 10 + +/obj/item/assembly/prox_sensor/dropped() + ..() + spawn(0) + sense() + return + +/obj/item/assembly/prox_sensor/proc/toggle_scan() + if(!secured) + return FALSE + scanning = !scanning + update_icon() + +/obj/item/assembly/prox_sensor/update_icon() + overlays.Cut() + attached_overlays = list() + if(timing) + overlays += "prox_timing" + attached_overlays += "prox_timing" + if(scanning) + overlays += "prox_scanning" + attached_overlays += "prox_scanning" + if(holder) + holder.update_icon() + +/obj/item/assembly/prox_sensor/Move() + ..() + sense() + +/obj/item/assembly/prox_sensor/holder_movement() + sense() + +/obj/item/assembly/prox_sensor/interact(mob/user)//TODO: Change this to the wires thingy + if(!secured) + user.show_message("The [name] is unsecured!") + return FALSE + var/second = time % 60 + var/minute = (time - second) / 60 + var/dat = text("Proximity Sensor\n[] []:[]\n- - + +\n", (timing ? "Arming" : "Not Arming"), minute, second) + dat += "
        [scanning?"Armed":"Unarmed"] (Movement sensor active when armed!)" + dat += "

        Refresh" + dat += "

        Close" + var/datum/browser/popup = new(user, "prox", name, 400, 400) + popup.set_content(dat) + popup.open(0) + onclose(user, "prox") + +/obj/item/assembly/prox_sensor/Topic(href, href_list) + ..() + if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) + usr << browse(null, "window=prox") + onclose(usr, "prox") + return + + if(href_list["scanning"]) + toggle_scan() + + if(href_list["time"]) + timing = text2num(href_list["time"]) + update_icon() + + if(href_list["tp"]) + var/tp = text2num(href_list["tp"]) + time += tp + time = min(max(round(time), 0), 600) + + if(href_list["close"]) + usr << browse(null, "window=prox") + return + + if(usr) + attack_self(usr) diff --git a/code/modules/assembly/shock_kit.dm b/code/modules/assembly/shock_kit.dm index 7ee6844deea..d054ea8d3f5 100644 --- a/code/modules/assembly/shock_kit.dm +++ b/code/modules/assembly/shock_kit.dm @@ -1,46 +1,46 @@ -/obj/item/assembly/shock_kit - name = "electrohelmet assembly" - desc = "This appears to be made from both an electropack and a helmet." - icon = 'icons/obj/assemblies.dmi' - icon_state = "shock_kit" - var/obj/item/clothing/head/helmet/part1 = null - var/obj/item/radio/electropack/part2 = null - var/status = 0 - w_class = WEIGHT_CLASS_HUGE - flags = CONDUCT - -/obj/item/assembly/shock_kit/Destroy() - QDEL_NULL(part1) - QDEL_NULL(part2) - return ..() - -/obj/item/assembly/shock_kit/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/wrench) && !status) - var/turf/T = loc - if(ismob(T)) - T = T.loc - part1.loc = T - part2.loc = T - part1.master = null - part2.master = null - part1 = null - part2 = null - qdel(src) - return - if(istype(W, /obj/item/screwdriver)) - status = !status - to_chat(user, "[src] is now [status ? "secured" : "unsecured"]!") - add_fingerprint(user) - return - -/obj/item/assembly/shock_kit/attack_self(mob/user as mob) - part1.attack_self(user, status) - part2.attack_self(user, status) - add_fingerprint(user) - return - -/obj/item/assembly/shock_kit/receive_signal() - if(istype(loc, /obj/structure/chair/e_chair)) - var/obj/structure/chair/e_chair/C = loc - C.shock() - return +/obj/item/assembly/shock_kit + name = "electrohelmet assembly" + desc = "This appears to be made from both an electropack and a helmet." + icon = 'icons/obj/assemblies.dmi' + icon_state = "shock_kit" + var/obj/item/clothing/head/helmet/part1 = null + var/obj/item/radio/electropack/part2 = null + var/status = 0 + w_class = WEIGHT_CLASS_HUGE + flags = CONDUCT + +/obj/item/assembly/shock_kit/Destroy() + QDEL_NULL(part1) + QDEL_NULL(part2) + return ..() + +/obj/item/assembly/shock_kit/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/wrench) && !status) + var/turf/T = loc + if(ismob(T)) + T = T.loc + part1.loc = T + part2.loc = T + part1.master = null + part2.master = null + part1 = null + part2 = null + qdel(src) + return + if(istype(W, /obj/item/screwdriver)) + status = !status + to_chat(user, "[src] is now [status ? "secured" : "unsecured"]!") + add_fingerprint(user) + return + +/obj/item/assembly/shock_kit/attack_self(mob/user as mob) + part1.attack_self(user, status) + part2.attack_self(user, status) + add_fingerprint(user) + return + +/obj/item/assembly/shock_kit/receive_signal() + if(istype(loc, /obj/structure/chair/e_chair)) + var/obj/structure/chair/e_chair/C = loc + C.shock() + return diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index 29f79cae1a7..93db1674fa7 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -1,177 +1,177 @@ -/obj/item/assembly/signaler - name = "remote signaling device" - desc = "Used to remotely activate devices." - icon_state = "signaller" - item_state = "signaler" - materials = list(MAT_METAL=400, MAT_GLASS=120) - origin_tech = "magnets=1;bluespace=1" - wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE - - secured = 1 - var/receiving = FALSE - - bomb_name = "remote-control bomb" - - var/code = 30 - var/frequency = RSD_FREQ - var/delay = 0 - var/datum/radio_frequency/radio_connection - var/airlock_wire = null - -/obj/item/assembly/signaler/New() - ..() - if(SSradio) - set_frequency(frequency) - -/obj/item/assembly/signaler/Initialize() - ..() - if(SSradio) - set_frequency(frequency) - -/obj/item/assembly/signaler/Destroy() - if(SSradio) - SSradio.remove_object(src, frequency) - radio_connection = null - return ..() - -/obj/item/assembly/signaler/describe() - return "[src]'s power light is [receiving ? "on" : "off"]" - -/obj/item/assembly/signaler/activate() - if(cooldown > 0) - return FALSE - cooldown = 2 - spawn(10) - process_cooldown() - - signal() - return TRUE - -/obj/item/assembly/signaler/update_icon() - if(holder) - holder.update_icon() - return - -/obj/item/assembly/signaler/interact(mob/user, flag1) - var/t1 = "-------" - var/dat = {" - - "} - if(!flag1) - dat += {" - Send Signal
        - Receiver is [receiving?"on":"off"]
        - "} - dat += {" - Frequency/Code for signaler:
        - Frequency: - - - - - [format_frequency(frequency)] - + - +
        - - Code: - - - - - [code] - + - +
        - [t1] -
        - "} - var/datum/browser/popup = new(user, "radio", name, 400, 400) - popup.set_content(dat) - popup.open(0) - onclose(user, "radio") - -/obj/item/assembly/signaler/Topic(href, href_list) - ..() - - if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) - usr << browse(null, "window=radio") - onclose(usr, "radio") - return - - if(href_list["freq"]) - var/new_frequency = (frequency + text2num(href_list["freq"])) - if(new_frequency < RADIO_LOW_FREQ || new_frequency > RADIO_HIGH_FREQ) - new_frequency = sanitize_frequency(new_frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ) - set_frequency(new_frequency) - - if(href_list["code"]) - code += text2num(href_list["code"]) - code = round(code) - code = min(100, code) - code = max(1, code) - if(href_list["receive"]) - receiving = !receiving - - if(href_list["send"]) - spawn( 0 ) - signal() - - if(usr) - attack_self(usr) - -/obj/item/assembly/signaler/proc/signal() - if(!radio_connection) - return - - var/datum/signal/signal = new - signal.source = src - signal.encryption = code - signal.data["message"] = "ACTIVATE" - radio_connection.post_signal(src, signal) - - var/time = time2text(world.realtime,"hh:mm:ss") - var/turf/T = get_turf(src) - if(usr) - lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") - -/obj/item/assembly/signaler/pulse(var/radio = FALSE) - if(connected && wires) - connected.Pulse(src) - else - return ..(radio) - -/obj/item/assembly/signaler/receive_signal(datum/signal/signal) - if(!receiving || !signal) - return FALSE - - if(signal.encryption != code) - return FALSE - - if(!(wires & WIRE_RADIO_RECEIVE)) - return FALSE - pulse(1) - - for(var/mob/O in hearers(1, loc)) - O.show_message("[bicon(src)] *beep* *beep*", 3, "*beep* *beep*", 2) - return TRUE - -/obj/item/assembly/signaler/proc/set_frequency(new_frequency) - if(!SSradio) - sleep(20) - if(!SSradio) - return - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) - -// Embedded signaller used in anomalies. -/obj/item/assembly/signaler/anomaly - name = "anomaly core" - desc = "The neutralized core of an anomaly. It'd probably be valuable for research." - icon_state = "anomaly core" - item_state = "electronic" - resistance_flags = FIRE_PROOF - receiving = TRUE - -/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal) - if(..()) - for(var/obj/effect/anomaly/A in orange(0, src)) - A.anomalyNeutralize() - -/obj/item/assembly/signaler/anomaly/attack_self() - return +/obj/item/assembly/signaler + name = "remote signaling device" + desc = "Used to remotely activate devices." + icon_state = "signaller" + item_state = "signaler" + materials = list(MAT_METAL=400, MAT_GLASS=120) + origin_tech = "magnets=1;bluespace=1" + wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE + + secured = 1 + var/receiving = FALSE + + bomb_name = "remote-control bomb" + + var/code = 30 + var/frequency = RSD_FREQ + var/delay = 0 + var/datum/radio_frequency/radio_connection + var/airlock_wire = null + +/obj/item/assembly/signaler/New() + ..() + if(SSradio) + set_frequency(frequency) + +/obj/item/assembly/signaler/Initialize() + ..() + if(SSradio) + set_frequency(frequency) + +/obj/item/assembly/signaler/Destroy() + if(SSradio) + SSradio.remove_object(src, frequency) + radio_connection = null + return ..() + +/obj/item/assembly/signaler/describe() + return "[src]'s power light is [receiving ? "on" : "off"]" + +/obj/item/assembly/signaler/activate() + if(cooldown > 0) + return FALSE + cooldown = 2 + spawn(10) + process_cooldown() + + signal() + return TRUE + +/obj/item/assembly/signaler/update_icon() + if(holder) + holder.update_icon() + return + +/obj/item/assembly/signaler/interact(mob/user, flag1) + var/t1 = "-------" + var/dat = {" + + "} + if(!flag1) + dat += {" + Send Signal
        + Receiver is [receiving?"on":"off"]
        + "} + dat += {" + Frequency/Code for signaler:
        + Frequency: + - + - + [format_frequency(frequency)] + + + +
        + + Code: + - + - + [code] + + + +
        + [t1] +
        + "} + var/datum/browser/popup = new(user, "radio", name, 400, 400) + popup.set_content(dat) + popup.open(0) + onclose(user, "radio") + +/obj/item/assembly/signaler/Topic(href, href_list) + ..() + + if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) + usr << browse(null, "window=radio") + onclose(usr, "radio") + return + + if(href_list["freq"]) + var/new_frequency = (frequency + text2num(href_list["freq"])) + if(new_frequency < RADIO_LOW_FREQ || new_frequency > RADIO_HIGH_FREQ) + new_frequency = sanitize_frequency(new_frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ) + set_frequency(new_frequency) + + if(href_list["code"]) + code += text2num(href_list["code"]) + code = round(code) + code = min(100, code) + code = max(1, code) + if(href_list["receive"]) + receiving = !receiving + + if(href_list["send"]) + spawn( 0 ) + signal() + + if(usr) + attack_self(usr) + +/obj/item/assembly/signaler/proc/signal() + if(!radio_connection) + return + + var/datum/signal/signal = new + signal.source = src + signal.encryption = code + signal.data["message"] = "ACTIVATE" + radio_connection.post_signal(src, signal) + + var/time = time2text(world.realtime,"hh:mm:ss") + var/turf/T = get_turf(src) + if(usr) + lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") + +/obj/item/assembly/signaler/pulse(var/radio = FALSE) + if(connected && wires) + connected.Pulse(src) + else + return ..(radio) + +/obj/item/assembly/signaler/receive_signal(datum/signal/signal) + if(!receiving || !signal) + return FALSE + + if(signal.encryption != code) + return FALSE + + if(!(wires & WIRE_RADIO_RECEIVE)) + return FALSE + pulse(1) + + for(var/mob/O in hearers(1, loc)) + O.show_message("[bicon(src)] *beep* *beep*", 3, "*beep* *beep*", 2) + return TRUE + +/obj/item/assembly/signaler/proc/set_frequency(new_frequency) + if(!SSradio) + sleep(20) + if(!SSradio) + return + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) + +// Embedded signaller used in anomalies. +/obj/item/assembly/signaler/anomaly + name = "anomaly core" + desc = "The neutralized core of an anomaly. It'd probably be valuable for research." + icon_state = "anomaly core" + item_state = "electronic" + resistance_flags = FIRE_PROOF + receiving = TRUE + +/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal) + if(..()) + for(var/obj/effect/anomaly/A in orange(0, src)) + A.anomalyNeutralize() + +/obj/item/assembly/signaler/anomaly/attack_self() + return diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm index 2c2216fa9d0..cab132ab747 100644 --- a/code/modules/assembly/timer.dm +++ b/code/modules/assembly/timer.dm @@ -1,127 +1,127 @@ -/obj/item/assembly/timer - name = "timer" - desc = "Used to time things. Works well with contraptions which has to count down. Tick tock." - icon_state = "timer" - materials = list(MAT_METAL=500, MAT_GLASS=50) - origin_tech = "magnets=1;engineering=1" - - secured = FALSE - - bomb_name = "time bomb" - - var/timing = FALSE - var/time = 10 - var/repeat = FALSE - var/set_time = 10 - -/obj/item/assembly/timer/describe() - if(timing) - return "The timer is counting down from [time]!" - return "The timer is set for [time] seconds." - -/obj/item/assembly/timer/activate() - if(!..()) - return FALSE//Cooldown check - timing = !timing - update_icon() - return FALSE - -/obj/item/assembly/timer/toggle_secure() - secured = !secured - if(secured) - START_PROCESSING(SSobj, src) - else - timing = FALSE - STOP_PROCESSING(SSobj, src) - update_icon() - return secured - -/obj/item/assembly/timer/proc/timer_end() - if(!secured || cooldown > 0) - return FALSE - pulse(0) - if(loc) - loc.visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*") - cooldown = 2 - spawn(10) - process_cooldown() - -/obj/item/assembly/timer/process() - if(timing && (time > 0)) - time -= 2 // 2 seconds per process() - if(timing && time <= 0) - timing = repeat - timer_end() - time = set_time - -/obj/item/assembly/timer/update_icon() - overlays.Cut() - attached_overlays = list() - if(timing) - overlays += "timer_timing" - attached_overlays += "timer_timing" - if(holder) - holder.update_icon() - -/obj/item/assembly/timer/interact(mob/user as mob)//TODO: Have this use the wires - if(!secured) - user.show_message("The [name] is unsecured!") - return FALSE - var/second = time % 60 - var/minute = (time - second) / 60 - var/set_second = set_time % 60 - var/set_minute = (set_time - set_second) / 60 - if(second < 10) second = "0[second]" - if(set_second < 10) set_second = "0[set_second]" - - var/dat = {" - -

        Timing Unit

        - [minute]:[second] [timing?"Stop":"Start"] Reset
        - Repeat: [repeat?"On":"Off"]
        - Timer set for - - - [set_minute]:[set_second] + + -
        -
        -

        - Refresh -

        - Close"} - var/datum/browser/popup = new(user, "timer", name, 400, 400) - popup.set_content(dat) - popup.open(0) - onclose(user, "timer") - -/obj/item/assembly/timer/Topic(href, href_list) - ..() - if(usr.incapacitated() || !in_range(loc, usr)) - usr << browse(null, "window=timer") - onclose(usr, "timer") - return - - if(href_list["time"]) - timing = !timing - if(timing && istype(holder, /obj/item/transfer_valve)) - message_admins("[key_name_admin(usr)] activated [src] attachment on [holder].") - investigate_log("[key_name(usr)] activated [src] attachment for [loc]", INVESTIGATE_BOMB) - log_game("[key_name(usr)] activated [src] attachment for [loc]") - update_icon() - if(href_list["reset"]) - time = set_time - - if(href_list["repeat"]) - repeat = !repeat - - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - set_time += tp - set_time = min(max(round(set_time), 6), 600) - if(!timing) - time = set_time - - if(href_list["close"]) - usr << browse(null, "window=timer") - return - - if(usr) - attack_self(usr) +/obj/item/assembly/timer + name = "timer" + desc = "Used to time things. Works well with contraptions which has to count down. Tick tock." + icon_state = "timer" + materials = list(MAT_METAL=500, MAT_GLASS=50) + origin_tech = "magnets=1;engineering=1" + + secured = FALSE + + bomb_name = "time bomb" + + var/timing = FALSE + var/time = 10 + var/repeat = FALSE + var/set_time = 10 + +/obj/item/assembly/timer/describe() + if(timing) + return "The timer is counting down from [time]!" + return "The timer is set for [time] seconds." + +/obj/item/assembly/timer/activate() + if(!..()) + return FALSE//Cooldown check + timing = !timing + update_icon() + return FALSE + +/obj/item/assembly/timer/toggle_secure() + secured = !secured + if(secured) + START_PROCESSING(SSobj, src) + else + timing = FALSE + STOP_PROCESSING(SSobj, src) + update_icon() + return secured + +/obj/item/assembly/timer/proc/timer_end() + if(!secured || cooldown > 0) + return FALSE + pulse(0) + if(loc) + loc.visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*") + cooldown = 2 + spawn(10) + process_cooldown() + +/obj/item/assembly/timer/process() + if(timing && (time > 0)) + time -= 2 // 2 seconds per process() + if(timing && time <= 0) + timing = repeat + timer_end() + time = set_time + +/obj/item/assembly/timer/update_icon() + overlays.Cut() + attached_overlays = list() + if(timing) + overlays += "timer_timing" + attached_overlays += "timer_timing" + if(holder) + holder.update_icon() + +/obj/item/assembly/timer/interact(mob/user as mob)//TODO: Have this use the wires + if(!secured) + user.show_message("The [name] is unsecured!") + return FALSE + var/second = time % 60 + var/minute = (time - second) / 60 + var/set_second = set_time % 60 + var/set_minute = (set_time - set_second) / 60 + if(second < 10) second = "0[second]" + if(set_second < 10) set_second = "0[set_second]" + + var/dat = {" + +

        Timing Unit

        + [minute]:[second] [timing?"Stop":"Start"] Reset
        + Repeat: [repeat?"On":"Off"]
        + Timer set for + - - [set_minute]:[set_second] + + +
        +
        +

        + Refresh +

        + Close"} + var/datum/browser/popup = new(user, "timer", name, 400, 400) + popup.set_content(dat) + popup.open(0) + onclose(user, "timer") + +/obj/item/assembly/timer/Topic(href, href_list) + ..() + if(usr.incapacitated() || !in_range(loc, usr)) + usr << browse(null, "window=timer") + onclose(usr, "timer") + return + + if(href_list["time"]) + timing = !timing + if(timing && istype(holder, /obj/item/transfer_valve)) + message_admins("[key_name_admin(usr)] activated [src] attachment on [holder].") + investigate_log("[key_name(usr)] activated [src] attachment for [loc]", INVESTIGATE_BOMB) + log_game("[key_name(usr)] activated [src] attachment for [loc]") + update_icon() + if(href_list["reset"]) + time = set_time + + if(href_list["repeat"]) + repeat = !repeat + + if(href_list["tp"]) + var/tp = text2num(href_list["tp"]) + set_time += tp + set_time = min(max(round(set_time), 6), 600) + if(!timing) + time = set_time + + if(href_list["close"]) + usr << browse(null, "window=timer") + return + + if(usr) + attack_self(usr) diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index a20cdb89caf..3119b55e28a 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -74,4 +74,4 @@ /obj/item/assembly/voice/noise/hear_message(mob/living/M as mob, msg) pulse(0) var/turf/T = get_turf(src) //otherwise it won't work in hand - T.visible_message("[bicon(src)] beeps!") \ No newline at end of file + T.visible_message("[bicon(src)] beeps!") diff --git a/code/modules/atmos_automation/implementation/digital_valves.dm b/code/modules/atmos_automation/implementation/digital_valves.dm index a143c0e9869..dc0d0b2be00 100644 --- a/code/modules/atmos_automation/implementation/digital_valves.dm +++ b/code/modules/atmos_automation/implementation/digital_valves.dm @@ -41,4 +41,4 @@ return valve = input("Select a valve:", "Sensor Data", valve) as null|anything in valves parent.updateUsrDialog() - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/atmos_automation/implementation/emitters.dm b/code/modules/atmos_automation/implementation/emitters.dm index 100d68708cd..e6315fbceb2 100644 --- a/code/modules/atmos_automation/implementation/emitters.dm +++ b/code/modules/atmos_automation/implementation/emitters.dm @@ -39,4 +39,4 @@ return emitter = input("Select an emitter:", "Emitter", emitter) as null|anything in emitters parent.updateUsrDialog() - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/atmos_automation/implementation/injectors.dm b/code/modules/atmos_automation/implementation/injectors.dm index cc17dd27921..d6747f31447 100644 --- a/code/modules/atmos_automation/implementation/injectors.dm +++ b/code/modules/atmos_automation/implementation/injectors.dm @@ -80,4 +80,4 @@ injector_names|=I.id_tag injector = input("Select an injector:", "Sensor Data", injector) as null|anything in injector_names parent.updateUsrDialog() - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/atmos_automation/implementation/scrubbers.dm b/code/modules/atmos_automation/implementation/scrubbers.dm index 4188773a6a4..5be0967d9a8 100644 --- a/code/modules/atmos_automation/implementation/scrubbers.dm +++ b/code/modules/atmos_automation/implementation/scrubbers.dm @@ -150,4 +150,4 @@ var/global/list/gas_labels=list( injector_names|=S.id_tag scrubber = input("Select a scrubber:", "Scrubbers", scrubber) as null|anything in injector_names parent.updateUsrDialog() - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/atmos_automation/implementation/sensors.dm b/code/modules/atmos_automation/implementation/sensors.dm index 487298aa266..d86d9612470 100644 --- a/code/modules/atmos_automation/implementation/sensors.dm +++ b/code/modules/atmos_automation/implementation/sensors.dm @@ -53,4 +53,4 @@ sensor_list|=M.id_tag sensor = input("Select a sensor:", "Sensor Data", field) as null|anything in sensor_list parent.updateUsrDialog() - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/atmos_automation/statements.dm b/code/modules/atmos_automation/statements.dm index 72937e18eae..1e92fcab493 100644 --- a/code/modules/atmos_automation/statements.dm +++ b/code/modules/atmos_automation/statements.dm @@ -454,4 +454,4 @@ var/global/automation_types = subtypesof(/datum/automation) if(href_list["set_value"]) value = input("Set a value:", "Static Value", value) as num parent.updateUsrDialog() - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm index e238003b426..1086f43dd48 100644 --- a/code/modules/awaymissions/corpse.dm +++ b/code/modules/awaymissions/corpse.dm @@ -1,602 +1,602 @@ -//These are meant for spawning on maps, namely Away Missions. - -//If someone can do this in a neater way, be my guest-Kor - -//To do: Allow corpses to appear mangled, bloody, etc. Allow customizing the bodies appearance (they're all bald and white right now). - -/obj/effect/mob_spawn - name = "Unknown" - density = TRUE - anchored = TRUE - icon = 'icons/effects/blood.dmi' - icon_state = "remains" - var/mob_type = null - var/mob_name = "unidentified entity" - var/mob_gender = null - var/death = TRUE //Kill the mob - var/roundstart = TRUE //fires on initialize - var/instant = FALSE //fires on New - var/flavour_text = "The mapper forgot to set this!" - var/faction = null - var/permanent = FALSE //If true, the spawner will not disappear upon running out of uses. - var/random = FALSE //Don't set a name or gender, just go random - var/objectives = null - var/uses = 1 //how many times can we spawn from it. set to -1 for infinite. - var/brute_damage = 0 - var/oxy_damage = 0 - var/burn_damage = 0 - var/datum/disease/disease = null //Do they start with a pre-spawned disease? - var/mob_color //Change the mob's color - var/assignedrole - var/banType = ROLE_GHOST - var/ghost_usable = TRUE - var/offstation_role = TRUE // If set to true, the role of the user's mind will be set to offstation - -/obj/effect/mob_spawn/attack_ghost(mob/user) - var/mob/dead/observer/O = user - if(SSticker.current_state != GAME_STATE_PLAYING || !loc || !ghost_usable) - return - if(!uses) - to_chat(user, "This spawner is out of charges!") - return - if(jobban_isbanned(user, banType)) - to_chat(user, "You are jobanned!") - return - if(cannotPossess(user)) - to_chat(user, "Upon using the antagHUD you forfeited the ability to join the round.") - return - if(!O.can_reenter_corpse) - to_chat(user, "You have forfeited the right to respawn.") - return - var/ghost_role = alert("Become [mob_name]? (Warning, You can no longer be cloned!)",,"Yes","No") - if(ghost_role == "No") - return - if(!species_prompt()) - return - if(!loc || !uses || QDELETED(src) || QDELETED(user)) - to_chat(user, "The [name] is no longer usable!") - return - log_game("[user.ckey] became [mob_name]") - create(ckey = user.ckey) - -/obj/effect/mob_spawn/Initialize(mapload) - . = ..() - if(instant || roundstart) //at some point we should probably re-introduce the (ticker && ticker.current_state > GAME_STATE_SETTING_UP) portion of this check, but for now it was preventing the corpses from spawning at roundstart and resulting in ghost role spawners that made dead bodies. - create() - else if(ghost_usable) - GLOB.poi_list |= src - LAZYADD(GLOB.mob_spawners[name], src) - -/obj/effect/mob_spawn/Destroy() - GLOB.poi_list -= src - var/list/spawners = GLOB.mob_spawners[name] - LAZYREMOVE(spawners, src) - if(!LAZYLEN(spawners)) - GLOB.mob_spawners -= name - return ..() - -/obj/effect/mob_spawn/proc/species_prompt() - return TRUE - -/obj/effect/mob_spawn/proc/special(mob/M) - return - -/obj/effect/mob_spawn/proc/equip(mob/M) - return - -/obj/effect/mob_spawn/proc/create(ckey, flavour = TRUE, name) - var/mob/living/M = new mob_type(get_turf(src)) //living mobs only - var/mob/living/carbon/human/H = M - if(H && !H.dna) - H.Initialize(null) - if(!random) - M.real_name = mob_name ? mob_name : M.name - if(!mob_gender) - mob_gender = pick(MALE, FEMALE) - M.gender = mob_gender - if(faction) - M.faction = list(faction) - if(disease) - M.ForceContractDisease(new disease) - if(death) - M.death() //Kills the new mob - - M.adjustOxyLoss(oxy_damage) - M.adjustBruteLoss(brute_damage) - M.adjustFireLoss(burn_damage) - M.color = mob_color - equip(M, TRUE) - - if(ckey) - M.ckey = ckey - if(flavour) - to_chat(M, "[flavour_text]") - var/datum/mind/MM = M.mind - if(objectives) - for(var/objective in objectives) - MM.objectives += new/datum/objective(objective) - if(assignedrole) - M.mind.assigned_role = assignedrole - M.mind.offstation_role = offstation_role - special(M, name) - MM.name = M.real_name - if(uses > 0) - uses-- - if(!permanent && !uses) - qdel(src) - -// Base version - place these on maps/templates. -/obj/effect/mob_spawn/human - mob_type = /mob/living/carbon/human - //Human specific stuff. - var/mob_species = null //Set species - var/allow_species_pick = FALSE - var/list/pickable_species = list("Human", "Vulpkanin", "Tajaran", "Unathi", "Skrell", "Diona") - var/datum/outfit/outfit = /datum/outfit //If this is a path, it will be instanced in Initialize() - var/disable_pda = TRUE - var/disable_sensors = TRUE - //All of these only affect the ID that the outfit has placed in the ID slot - var/id_job = null //Such as "Clown" or "Chef." This just determines what the ID reads as, not their access - var/id_access = null //This is for access. See access.dm for which jobs give what access. Use "Captain" if you want it to be all access. - var/id_access_list = null //Allows you to manually add access to an ID card. - assignedrole = "Ghost Role" - - var/husk = null - //these vars are for lazy mappers to override parts of the outfit - //these cannot be null by default, or mappers cannot set them to null if they want nothing in that slot - var/uniform = -1 - var/r_hand = -1 - var/l_hand = -1 - var/suit = -1 - var/shoes = -1 - var/gloves = -1 - var/ears = -1 - var/glasses = -1 - var/mask = -1 - var/head = -1 - var/belt = -1 - var/r_pocket = -1 - var/l_pocket = -1 - var/back = -1 - var/id = -1 - var/neck = -1 - var/pda = -1 - var/backpack_contents = -1 - var/suit_store = -1 - - var/hair_style - var/facial_hair_style - var/skin_tone - - -/obj/effect/mob_spawn/human/Initialize() - if(ispath(outfit)) - outfit = new outfit() - if(!outfit) - outfit = new /datum/outfit - if(!mob_name) - mob_name = id_job - return ..() - -/obj/effect/mob_spawn/human/species_prompt() - if(allow_species_pick) - var/selected_species = input("Select a species", "Species Selection") as null|anything in pickable_species - if(!selected_species) - return TRUE // You didn't pick, so just continue on with the spawning process as a human - var/datum/species/S = GLOB.all_species[selected_species] - mob_species = S.type - return TRUE - -/obj/effect/mob_spawn/human/equip(mob/living/carbon/human/H) - if(mob_species) - H.set_species(mob_species) - - if(husk) - H.ChangeToHusk() - else //Because for some reason I can't track down, things are getting turned into husks even if husk = false. It's in some damage proc somewhere. - H.mutations.Remove(HUSK) - H.underwear = "Nude" - H.undershirt = "Nude" - H.socks = "Nude" - var/obj/item/organ/external/head/D = H.get_organ("head") - if(istype(D)) - if(hair_style) - D.h_style = hair_style - else - D.h_style = random_hair_style(gender, D.dna.species.name) - D.hair_colour = rand_hex_color() - if(facial_hair_style) - D.f_style = facial_hair_style - else - D.f_style = random_facial_hair_style(gender, D.dna.species.name) - D.facial_colour = rand_hex_color() - if(skin_tone) - H.change_skin_tone(skin_tone) - else - H.change_skin_tone(random_skin_tone()) - H.change_skin_color(rand_hex_color()) - H.update_hair() - H.update_fhair() - H.update_body() - H.update_dna() - H.regenerate_icons() - if(outfit) - var/static/list/slots = list("uniform", "r_hand", "l_hand", "suit", "shoes", "gloves", "ears", "glasses", "mask", "head", "belt", "r_pocket", "l_pocket", "back", "id", "neck", "backpack_contents", "suit_store") - for(var/slot in slots) - var/T = vars[slot] - if(!isnum(T)) - outfit.vars[slot] = T - H.equipOutfit(outfit) - var/list/del_types = list(/obj/item/pda, /obj/item/radio/headset) - for(var/del_type in del_types) - var/obj/item/I = locate(del_type) in H - qdel(I) - - if(disable_pda) - // We don't want corpse PDAs to show up in the messenger list. - var/obj/item/pda/PDA = locate(/obj/item/pda) in H - if(PDA) - var/datum/data/pda/app/messenger/M = PDA.find_program(/datum/data/pda/app/messenger) - M.toff = 1 - if(disable_sensors) - // Using crew monitors to find corpses while creative makes finding certain ruins too easy. - var/obj/item/clothing/under/C = H.w_uniform - if(istype(C)) - C.sensor_mode = SUIT_SENSOR_OFF - - var/obj/item/card/id/W = H.wear_id - if(W) - if(id_access) - for(var/jobtype in typesof(/datum/job)) - var/datum/job/J = new jobtype - if(J.title == id_access) - W.access = J.get_access() - break - if(id_access_list) - if(!islist(W.access)) - W.access = list() - W.access |= id_access_list - if(id_job) - W.assignment = id_job - W.registered_name = H.real_name - W.update_label() - -//Instant version - use when spawning corpses during runtime -/obj/effect/mob_spawn/human/corpse - roundstart = FALSE - instant = TRUE - -/obj/effect/mob_spawn/human/corpse/damaged - brute_damage = 1000 - - -/obj/effect/mob_spawn/human/alive - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "sleeper" - death = FALSE - roundstart = FALSE //you could use these for alive fake humans on roundstart but this is more common scenario - - -//Non-human spawners - -/obj/effect/mob_spawn/mouse - name = "sleeper" - mob_name = "space mouse" - mob_type = /mob/living/simple_animal/mouse - death = FALSE - roundstart = FALSE - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "sleeper" - flavour_text = "Squeak!" - -/obj/effect/mob_spawn/cow - name = "sleeper" - mob_name = "space cow" - mob_type = /mob/living/simple_animal/cow - death = FALSE - roundstart = FALSE - mob_gender = FEMALE - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "sleeper" - flavour_text = "Moo!" - - -///////////Civilians////////////////////// - -/obj/effect/mob_spawn/human/corpse/assistant - name = "Assistant" - mob_name = "Assistant" - id_job = "Assistant" - outfit = /datum/outfit/job/assistant - -/obj/effect/mob_spawn/human/corpse/assistant/beesease_infection - disease = /datum/disease/beesease - -/obj/effect/mob_spawn/human/corpse/assistant/brainrot_infection - disease = /datum/disease/brainrot - -/obj/effect/mob_spawn/human/corpse/assistant/spanishflu_infection - disease = /datum/disease/fluspanish - -/obj/effect/mob_spawn/human/cook - name = "Cook" - mob_name = "Chef" - id_job = "Chef" - outfit = /datum/outfit/job/chef - -/obj/effect/mob_spawn/human/doctor - name = "Doctor" - mob_name = "Medical Doctor" - id_job = "Medical Doctor" - outfit = /datum/outfit/job/doctor - -/obj/effect/mob_spawn/human/doctor/alive - death = FALSE - roundstart = FALSE - random = TRUE - name = "sleeper" - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "sleeper" - flavour_text = "You are a space doctor!" - assignedrole = "Space Doctor" - -/obj/effect/mob_spawn/human/doctor/alive/equip(mob/living/carbon/human/H) - ..() - // Remove radio and PDA so they wouldn't annoy station crew. - var/list/del_types = list(/obj/item/pda, /obj/item/radio/headset) - for(var/del_type in del_types) - var/obj/item/I = locate(del_type) in H - qdel(I) - -/obj/effect/mob_spawn/human/engineer - name = "Engineer" - mob_name = "Engineer" - id_job = "Engineer" - outfit = /datum/outfit/job/engineer - -/obj/effect/mob_spawn/human/engineer/hardsuit - outfit = /datum/outfit/job/engineer/suit - -/datum/outfit/job/engineer/suit - name = "Station Engineer" - - uniform = /obj/item/clothing/under/rank/engineer - belt = /obj/item/storage/belt/utility/full - suit = /obj/item/clothing/suit/space/hardsuit/engine - shoes = /obj/item/clothing/shoes/workboots - mask = /obj/item/clothing/mask/breath - id = /obj/item/card/id/engineering - l_pocket = /obj/item/t_scanner - - backpack = /obj/item/storage/backpack/industrial - - -/obj/effect/mob_spawn/human/clown - name = "Clown" - mob_name = "Clown" - id_job = "Clown" - outfit = /datum/outfit/job/clown - -/obj/effect/mob_spawn/human/clown/Initialize() - mob_name = pick(GLOB.clown_names) - return ..() - -/obj/effect/mob_spawn/human/corpse/clownmili - name = "Clown Soldier" - outfit = /datum/outfit/clownsoldier - -/obj/effect/mob_spawn/human/corpse/clownmili/Initialize() - mob_name = "Officer [pick(GLOB.clown_names)]" - return ..() - -/obj/effect/mob_spawn/human/corpse/clownoff - name = "Clown Officer" - outfit = /datum/outfit/clownofficer - -/obj/effect/mob_spawn/human/corpse/clownoff/Initialize() - mob_name = "Honk Specialist [pick(GLOB.clown_names)]" - return ..() - - -/datum/outfit/clownsoldier - name = "Clown Soldier" - uniform = /obj/item/clothing/under/soldieruniform - suit = /obj/item/clothing/suit/soldiercoat - shoes = /obj/item/clothing/shoes/clown_shoes - l_ear = /obj/item/radio/headset - mask = /obj/item/clothing/mask/gas/clown_hat - l_pocket = /obj/item/bikehorn - back = /obj/item/storage/backpack/clown - head = /obj/item/clothing/head/stalhelm - -/datum/outfit/clownofficer - name = "Clown Officer" - uniform = /obj/item/clothing/under/officeruniform - suit = /obj/item/clothing/suit/officercoat - shoes = /obj/item/clothing/shoes/clown_shoes - l_ear = /obj/item/radio/headset - mask = /obj/item/clothing/mask/gas/clown_hat - l_pocket = /obj/item/bikehorn - back = /obj/item/storage/backpack/clown - head = /obj/item/clothing/head/naziofficer - -/obj/effect/mob_spawn/human/mime - name = "Mime" - mob_name = "Mime" - id_job = "Mime" - outfit = /datum/outfit/job/mime - -/obj/effect/mob_spawn/human/mime/Initialize() - mob_name = pick(GLOB.mime_names) - return ..() - -/obj/effect/mob_spawn/human/scientist - name = "Scientist" - mob_name = "Scientist" - id_job = "Scientist" - outfit = /datum/outfit/job/scientist - -/obj/effect/mob_spawn/human/miner - name = "Shaft Miner" - mob_name = "Shaft Miner" - id_job = "Shaft Miner" - outfit = /datum/outfit/job/mining/suit - -/datum/outfit/job/mining/suit - name = "Shaft Miner" - suit = /obj/item/clothing/suit/space/hardsuit/mining - uniform = /obj/item/clothing/under/rank/miner - gloves = /obj/item/clothing/gloves/fingerless - shoes = /obj/item/clothing/shoes/workboots - l_ear = /obj/item/radio/headset/headset_cargo/mining - id = /obj/item/card/id/supply - l_pocket = /obj/item/reagent_containers/food/pill/patch/styptic - r_pocket = /obj/item/flashlight/seclite - -/obj/effect/mob_spawn/human/miner/explorer - outfit = /datum/outfit/job/mining/equipped - -/obj/effect/mob_spawn/human/bartender - name = "Space Bartender" - mob_name = "Bartender" - id_job = "Bartender" - id_access_list = list(ACCESS_BAR) - outfit = /datum/outfit/spacebartender - -/obj/effect/mob_spawn/human/bartender/alive - death = FALSE - roundstart = FALSE - random = TRUE - allow_species_pick = TRUE - name = "bartender sleeper" - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "sleeper" - flavour_text = "You are a space bartender! Time to mix drinks and change lives." - assignedrole = "Space Bartender" - -/obj/effect/mob_spawn/human/beach/alive/lifeguard - flavour_text = "You're a spunky lifeguard! It's up to you to make sure nobody drowns or gets eaten by sharks and stuff." - mob_gender = "female" - name = "lifeguard sleeper" - id_job = "Lifeguard" - uniform = /obj/item/clothing/under/shorts/red - -/datum/outfit/spacebartender - name = "Space Bartender" - uniform = /obj/item/clothing/under/rank/bartender - suit = /obj/item/clothing/suit/armor/vest - belt = /obj/item/storage/belt/bandolier/full - shoes = /obj/item/clothing/shoes/black - glasses = /obj/item/clothing/glasses/sunglasses/reagent - id = /obj/item/card/id - - -/obj/effect/mob_spawn/human/beach - outfit = /datum/outfit/beachbum - -/obj/effect/mob_spawn/human/beach/alive - death = FALSE - roundstart = FALSE - random = TRUE - allow_species_pick = TRUE - mob_name = "Beach Bum" - name = "beach bum sleeper" - icon = 'icons/obj/cryogenic2.dmi' - icon_state = "sleeper" - flavour_text = "You are a beach bum!" - assignedrole = "Beach Bum" - -/datum/outfit/beachbum - name = "Beach Bum" - glasses = /obj/item/clothing/glasses/sunglasses - uniform = /obj/item/clothing/under/shorts/red - -/////////////////Spooky Undead////////////////////// - -/obj/effect/mob_spawn/human/skeleton - name = "skeletal remains" - mob_name = "skeleton" - mob_species = /datum/species/skeleton - mob_gender = NEUTER - -/obj/effect/mob_spawn/human/skeleton/alive - death = FALSE - roundstart = FALSE - icon = 'icons/effects/blood.dmi' - icon_state = "remains" - flavour_text = "By unknown powers, your skeletal remains have been reanimated! Walk this mortal plain and terrorize all living adventurers who dare cross your path." - assignedrole = "Skeleton" - -/////////////////Officers////////////////////// - -/obj/effect/mob_spawn/human/bridgeofficer - name = "Bridge Officer" - mob_name = "Bridge Officer" - id_job = "Bridge Officer" - id_access = "Captain" - outfit = /datum/outfit/nanotrasenbridgeofficercorpse - -/datum/outfit/nanotrasenbridgeofficercorpse - name = "Bridge Officer Corpse" - l_ear = /obj/item/radio/headset/heads/hop - uniform = /obj/item/clothing/under/rank/centcom_officer - suit = /obj/item/clothing/suit/armor/bulletproof - shoes = /obj/item/clothing/shoes/black - glasses = /obj/item/clothing/glasses/sunglasses - id = /obj/item/card/id - - -/obj/effect/mob_spawn/human/commander - name = "Commander" - mob_name = "Commander" - id_job = "Commander" - id_access = "Captain" - outfit = /datum/outfit/nanotrasencommandercorpse - -/datum/outfit/nanotrasencommandercorpse - name = "Commander Corpse" - - uniform = /obj/item/clothing/under/rank/centcom/officer - gloves = /obj/item/clothing/gloves/color/white - shoes = /obj/item/clothing/shoes/centcom - head = /obj/item/clothing/head/beret/centcom/officer - glasses = /obj/item/clothing/glasses/hud/security/sunglasses - id = /obj/item/card/id/centcom - - - -/obj/effect/mob_spawn/human/abductor - name = "abductor" - mob_name = "alien" - mob_species = /datum/species/abductor - outfit = /datum/outfit/abductorcorpse - -/datum/outfit/abductorcorpse - name = "Abductor Corpse" - uniform = /obj/item/clothing/under/color/grey - shoes = /obj/item/clothing/shoes/combat - -//For ghost bar. -/obj/effect/mob_spawn/human/alive/space_bar_patron - name = "Bar cryogenics" - mob_name = "Bar patron" - random = TRUE - permanent = TRUE - uses = -1 - outfit = /datum/outfit/spacebartender - assignedrole = "Space Bar Patron" - -/obj/effect/mob_spawn/human/alive/space_bar_patron/attack_hand(mob/user) - var/despawn = alert("Return to cryosleep? (Warning, Your mob will be deleted!)",,"Yes","No") - if(despawn == "No" || !loc || !Adjacent(user)) - return - user.visible_message("[user.name] climbs back into cryosleep...") - qdel(user) - -/datum/outfit/cryobartender - name = "Cryogenic Bartender" - uniform = /obj/item/clothing/under/rank/bartender - back = /obj/item/storage/backpack - shoes = /obj/item/clothing/shoes/black - suit = /obj/item/clothing/suit/armor/vest - glasses = /obj/item/clothing/glasses/sunglasses/reagent +//These are meant for spawning on maps, namely Away Missions. + +//If someone can do this in a neater way, be my guest-Kor + +//To do: Allow corpses to appear mangled, bloody, etc. Allow customizing the bodies appearance (they're all bald and white right now). + +/obj/effect/mob_spawn + name = "Unknown" + density = TRUE + anchored = TRUE + icon = 'icons/effects/blood.dmi' + icon_state = "remains" + var/mob_type = null + var/mob_name = "unidentified entity" + var/mob_gender = null + var/death = TRUE //Kill the mob + var/roundstart = TRUE //fires on initialize + var/instant = FALSE //fires on New + var/flavour_text = "The mapper forgot to set this!" + var/faction = null + var/permanent = FALSE //If true, the spawner will not disappear upon running out of uses. + var/random = FALSE //Don't set a name or gender, just go random + var/objectives = null + var/uses = 1 //how many times can we spawn from it. set to -1 for infinite. + var/brute_damage = 0 + var/oxy_damage = 0 + var/burn_damage = 0 + var/datum/disease/disease = null //Do they start with a pre-spawned disease? + var/mob_color //Change the mob's color + var/assignedrole + var/banType = ROLE_GHOST + var/ghost_usable = TRUE + var/offstation_role = TRUE // If set to true, the role of the user's mind will be set to offstation + +/obj/effect/mob_spawn/attack_ghost(mob/user) + var/mob/dead/observer/O = user + if(SSticker.current_state != GAME_STATE_PLAYING || !loc || !ghost_usable) + return + if(!uses) + to_chat(user, "This spawner is out of charges!") + return + if(jobban_isbanned(user, banType)) + to_chat(user, "You are jobanned!") + return + if(cannotPossess(user)) + to_chat(user, "Upon using the antagHUD you forfeited the ability to join the round.") + return + if(!O.can_reenter_corpse) + to_chat(user, "You have forfeited the right to respawn.") + return + var/ghost_role = alert("Become [mob_name]? (Warning, You can no longer be cloned!)",,"Yes","No") + if(ghost_role == "No") + return + if(!species_prompt()) + return + if(!loc || !uses || QDELETED(src) || QDELETED(user)) + to_chat(user, "The [name] is no longer usable!") + return + log_game("[user.ckey] became [mob_name]") + create(ckey = user.ckey) + +/obj/effect/mob_spawn/Initialize(mapload) + . = ..() + if(instant || roundstart) //at some point we should probably re-introduce the (ticker && ticker.current_state > GAME_STATE_SETTING_UP) portion of this check, but for now it was preventing the corpses from spawning at roundstart and resulting in ghost role spawners that made dead bodies. + create() + else if(ghost_usable) + GLOB.poi_list |= src + LAZYADD(GLOB.mob_spawners[name], src) + +/obj/effect/mob_spawn/Destroy() + GLOB.poi_list -= src + var/list/spawners = GLOB.mob_spawners[name] + LAZYREMOVE(spawners, src) + if(!LAZYLEN(spawners)) + GLOB.mob_spawners -= name + return ..() + +/obj/effect/mob_spawn/proc/species_prompt() + return TRUE + +/obj/effect/mob_spawn/proc/special(mob/M) + return + +/obj/effect/mob_spawn/proc/equip(mob/M) + return + +/obj/effect/mob_spawn/proc/create(ckey, flavour = TRUE, name) + var/mob/living/M = new mob_type(get_turf(src)) //living mobs only + var/mob/living/carbon/human/H = M + if(H && !H.dna) + H.Initialize(null) + if(!random) + M.real_name = mob_name ? mob_name : M.name + if(!mob_gender) + mob_gender = pick(MALE, FEMALE) + M.gender = mob_gender + if(faction) + M.faction = list(faction) + if(disease) + M.ForceContractDisease(new disease) + if(death) + M.death() //Kills the new mob + + M.adjustOxyLoss(oxy_damage) + M.adjustBruteLoss(brute_damage) + M.adjustFireLoss(burn_damage) + M.color = mob_color + equip(M, TRUE) + + if(ckey) + M.ckey = ckey + if(flavour) + to_chat(M, "[flavour_text]") + var/datum/mind/MM = M.mind + if(objectives) + for(var/objective in objectives) + MM.objectives += new/datum/objective(objective) + if(assignedrole) + M.mind.assigned_role = assignedrole + M.mind.offstation_role = offstation_role + special(M, name) + MM.name = M.real_name + if(uses > 0) + uses-- + if(!permanent && !uses) + qdel(src) + +// Base version - place these on maps/templates. +/obj/effect/mob_spawn/human + mob_type = /mob/living/carbon/human + //Human specific stuff. + var/mob_species = null //Set species + var/allow_species_pick = FALSE + var/list/pickable_species = list("Human", "Vulpkanin", "Tajaran", "Unathi", "Skrell", "Diona") + var/datum/outfit/outfit = /datum/outfit //If this is a path, it will be instanced in Initialize() + var/disable_pda = TRUE + var/disable_sensors = TRUE + //All of these only affect the ID that the outfit has placed in the ID slot + var/id_job = null //Such as "Clown" or "Chef." This just determines what the ID reads as, not their access + var/id_access = null //This is for access. See access.dm for which jobs give what access. Use "Captain" if you want it to be all access. + var/id_access_list = null //Allows you to manually add access to an ID card. + assignedrole = "Ghost Role" + + var/husk = null + //these vars are for lazy mappers to override parts of the outfit + //these cannot be null by default, or mappers cannot set them to null if they want nothing in that slot + var/uniform = -1 + var/r_hand = -1 + var/l_hand = -1 + var/suit = -1 + var/shoes = -1 + var/gloves = -1 + var/ears = -1 + var/glasses = -1 + var/mask = -1 + var/head = -1 + var/belt = -1 + var/r_pocket = -1 + var/l_pocket = -1 + var/back = -1 + var/id = -1 + var/neck = -1 + var/pda = -1 + var/backpack_contents = -1 + var/suit_store = -1 + + var/hair_style + var/facial_hair_style + var/skin_tone + + +/obj/effect/mob_spawn/human/Initialize() + if(ispath(outfit)) + outfit = new outfit() + if(!outfit) + outfit = new /datum/outfit + if(!mob_name) + mob_name = id_job + return ..() + +/obj/effect/mob_spawn/human/species_prompt() + if(allow_species_pick) + var/selected_species = input("Select a species", "Species Selection") as null|anything in pickable_species + if(!selected_species) + return TRUE // You didn't pick, so just continue on with the spawning process as a human + var/datum/species/S = GLOB.all_species[selected_species] + mob_species = S.type + return TRUE + +/obj/effect/mob_spawn/human/equip(mob/living/carbon/human/H) + if(mob_species) + H.set_species(mob_species) + + if(husk) + H.ChangeToHusk() + else //Because for some reason I can't track down, things are getting turned into husks even if husk = false. It's in some damage proc somewhere. + H.mutations.Remove(HUSK) + H.underwear = "Nude" + H.undershirt = "Nude" + H.socks = "Nude" + var/obj/item/organ/external/head/D = H.get_organ("head") + if(istype(D)) + if(hair_style) + D.h_style = hair_style + else + D.h_style = random_hair_style(gender, D.dna.species.name) + D.hair_colour = rand_hex_color() + if(facial_hair_style) + D.f_style = facial_hair_style + else + D.f_style = random_facial_hair_style(gender, D.dna.species.name) + D.facial_colour = rand_hex_color() + if(skin_tone) + H.change_skin_tone(skin_tone) + else + H.change_skin_tone(random_skin_tone()) + H.change_skin_color(rand_hex_color()) + H.update_hair() + H.update_fhair() + H.update_body() + H.update_dna() + H.regenerate_icons() + if(outfit) + var/static/list/slots = list("uniform", "r_hand", "l_hand", "suit", "shoes", "gloves", "ears", "glasses", "mask", "head", "belt", "r_pocket", "l_pocket", "back", "id", "neck", "backpack_contents", "suit_store") + for(var/slot in slots) + var/T = vars[slot] + if(!isnum(T)) + outfit.vars[slot] = T + H.equipOutfit(outfit) + var/list/del_types = list(/obj/item/pda, /obj/item/radio/headset) + for(var/del_type in del_types) + var/obj/item/I = locate(del_type) in H + qdel(I) + + if(disable_pda) + // We don't want corpse PDAs to show up in the messenger list. + var/obj/item/pda/PDA = locate(/obj/item/pda) in H + if(PDA) + var/datum/data/pda/app/messenger/M = PDA.find_program(/datum/data/pda/app/messenger) + M.toff = 1 + if(disable_sensors) + // Using crew monitors to find corpses while creative makes finding certain ruins too easy. + var/obj/item/clothing/under/C = H.w_uniform + if(istype(C)) + C.sensor_mode = SUIT_SENSOR_OFF + + var/obj/item/card/id/W = H.wear_id + if(W) + if(id_access) + for(var/jobtype in typesof(/datum/job)) + var/datum/job/J = new jobtype + if(J.title == id_access) + W.access = J.get_access() + break + if(id_access_list) + if(!islist(W.access)) + W.access = list() + W.access |= id_access_list + if(id_job) + W.assignment = id_job + W.registered_name = H.real_name + W.update_label() + +//Instant version - use when spawning corpses during runtime +/obj/effect/mob_spawn/human/corpse + roundstart = FALSE + instant = TRUE + +/obj/effect/mob_spawn/human/corpse/damaged + brute_damage = 1000 + + +/obj/effect/mob_spawn/human/alive + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "sleeper" + death = FALSE + roundstart = FALSE //you could use these for alive fake humans on roundstart but this is more common scenario + + +//Non-human spawners + +/obj/effect/mob_spawn/mouse + name = "sleeper" + mob_name = "space mouse" + mob_type = /mob/living/simple_animal/mouse + death = FALSE + roundstart = FALSE + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "sleeper" + flavour_text = "Squeak!" + +/obj/effect/mob_spawn/cow + name = "sleeper" + mob_name = "space cow" + mob_type = /mob/living/simple_animal/cow + death = FALSE + roundstart = FALSE + mob_gender = FEMALE + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "sleeper" + flavour_text = "Moo!" + + +///////////Civilians////////////////////// + +/obj/effect/mob_spawn/human/corpse/assistant + name = "Assistant" + mob_name = "Assistant" + id_job = "Assistant" + outfit = /datum/outfit/job/assistant + +/obj/effect/mob_spawn/human/corpse/assistant/beesease_infection + disease = /datum/disease/beesease + +/obj/effect/mob_spawn/human/corpse/assistant/brainrot_infection + disease = /datum/disease/brainrot + +/obj/effect/mob_spawn/human/corpse/assistant/spanishflu_infection + disease = /datum/disease/fluspanish + +/obj/effect/mob_spawn/human/cook + name = "Cook" + mob_name = "Chef" + id_job = "Chef" + outfit = /datum/outfit/job/chef + +/obj/effect/mob_spawn/human/doctor + name = "Doctor" + mob_name = "Medical Doctor" + id_job = "Medical Doctor" + outfit = /datum/outfit/job/doctor + +/obj/effect/mob_spawn/human/doctor/alive + death = FALSE + roundstart = FALSE + random = TRUE + name = "sleeper" + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "sleeper" + flavour_text = "You are a space doctor!" + assignedrole = "Space Doctor" + +/obj/effect/mob_spawn/human/doctor/alive/equip(mob/living/carbon/human/H) + ..() + // Remove radio and PDA so they wouldn't annoy station crew. + var/list/del_types = list(/obj/item/pda, /obj/item/radio/headset) + for(var/del_type in del_types) + var/obj/item/I = locate(del_type) in H + qdel(I) + +/obj/effect/mob_spawn/human/engineer + name = "Engineer" + mob_name = "Engineer" + id_job = "Engineer" + outfit = /datum/outfit/job/engineer + +/obj/effect/mob_spawn/human/engineer/hardsuit + outfit = /datum/outfit/job/engineer/suit + +/datum/outfit/job/engineer/suit + name = "Station Engineer" + + uniform = /obj/item/clothing/under/rank/engineer + belt = /obj/item/storage/belt/utility/full + suit = /obj/item/clothing/suit/space/hardsuit/engine + shoes = /obj/item/clothing/shoes/workboots + mask = /obj/item/clothing/mask/breath + id = /obj/item/card/id/engineering + l_pocket = /obj/item/t_scanner + + backpack = /obj/item/storage/backpack/industrial + + +/obj/effect/mob_spawn/human/clown + name = "Clown" + mob_name = "Clown" + id_job = "Clown" + outfit = /datum/outfit/job/clown + +/obj/effect/mob_spawn/human/clown/Initialize() + mob_name = pick(GLOB.clown_names) + return ..() + +/obj/effect/mob_spawn/human/corpse/clownmili + name = "Clown Soldier" + outfit = /datum/outfit/clownsoldier + +/obj/effect/mob_spawn/human/corpse/clownmili/Initialize() + mob_name = "Officer [pick(GLOB.clown_names)]" + return ..() + +/obj/effect/mob_spawn/human/corpse/clownoff + name = "Clown Officer" + outfit = /datum/outfit/clownofficer + +/obj/effect/mob_spawn/human/corpse/clownoff/Initialize() + mob_name = "Honk Specialist [pick(GLOB.clown_names)]" + return ..() + + +/datum/outfit/clownsoldier + name = "Clown Soldier" + uniform = /obj/item/clothing/under/soldieruniform + suit = /obj/item/clothing/suit/soldiercoat + shoes = /obj/item/clothing/shoes/clown_shoes + l_ear = /obj/item/radio/headset + mask = /obj/item/clothing/mask/gas/clown_hat + l_pocket = /obj/item/bikehorn + back = /obj/item/storage/backpack/clown + head = /obj/item/clothing/head/stalhelm + +/datum/outfit/clownofficer + name = "Clown Officer" + uniform = /obj/item/clothing/under/officeruniform + suit = /obj/item/clothing/suit/officercoat + shoes = /obj/item/clothing/shoes/clown_shoes + l_ear = /obj/item/radio/headset + mask = /obj/item/clothing/mask/gas/clown_hat + l_pocket = /obj/item/bikehorn + back = /obj/item/storage/backpack/clown + head = /obj/item/clothing/head/naziofficer + +/obj/effect/mob_spawn/human/mime + name = "Mime" + mob_name = "Mime" + id_job = "Mime" + outfit = /datum/outfit/job/mime + +/obj/effect/mob_spawn/human/mime/Initialize() + mob_name = pick(GLOB.mime_names) + return ..() + +/obj/effect/mob_spawn/human/scientist + name = "Scientist" + mob_name = "Scientist" + id_job = "Scientist" + outfit = /datum/outfit/job/scientist + +/obj/effect/mob_spawn/human/miner + name = "Shaft Miner" + mob_name = "Shaft Miner" + id_job = "Shaft Miner" + outfit = /datum/outfit/job/mining/suit + +/datum/outfit/job/mining/suit + name = "Shaft Miner" + suit = /obj/item/clothing/suit/space/hardsuit/mining + uniform = /obj/item/clothing/under/rank/miner + gloves = /obj/item/clothing/gloves/fingerless + shoes = /obj/item/clothing/shoes/workboots + l_ear = /obj/item/radio/headset/headset_cargo/mining + id = /obj/item/card/id/supply + l_pocket = /obj/item/reagent_containers/food/pill/patch/styptic + r_pocket = /obj/item/flashlight/seclite + +/obj/effect/mob_spawn/human/miner/explorer + outfit = /datum/outfit/job/mining/equipped + +/obj/effect/mob_spawn/human/bartender + name = "Space Bartender" + mob_name = "Bartender" + id_job = "Bartender" + id_access_list = list(ACCESS_BAR) + outfit = /datum/outfit/spacebartender + +/obj/effect/mob_spawn/human/bartender/alive + death = FALSE + roundstart = FALSE + random = TRUE + allow_species_pick = TRUE + name = "bartender sleeper" + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "sleeper" + flavour_text = "You are a space bartender! Time to mix drinks and change lives." + assignedrole = "Space Bartender" + +/obj/effect/mob_spawn/human/beach/alive/lifeguard + flavour_text = "You're a spunky lifeguard! It's up to you to make sure nobody drowns or gets eaten by sharks and stuff." + mob_gender = "female" + name = "lifeguard sleeper" + id_job = "Lifeguard" + uniform = /obj/item/clothing/under/shorts/red + +/datum/outfit/spacebartender + name = "Space Bartender" + uniform = /obj/item/clothing/under/rank/bartender + suit = /obj/item/clothing/suit/armor/vest + belt = /obj/item/storage/belt/bandolier/full + shoes = /obj/item/clothing/shoes/black + glasses = /obj/item/clothing/glasses/sunglasses/reagent + id = /obj/item/card/id + + +/obj/effect/mob_spawn/human/beach + outfit = /datum/outfit/beachbum + +/obj/effect/mob_spawn/human/beach/alive + death = FALSE + roundstart = FALSE + random = TRUE + allow_species_pick = TRUE + mob_name = "Beach Bum" + name = "beach bum sleeper" + icon = 'icons/obj/cryogenic2.dmi' + icon_state = "sleeper" + flavour_text = "You are a beach bum!" + assignedrole = "Beach Bum" + +/datum/outfit/beachbum + name = "Beach Bum" + glasses = /obj/item/clothing/glasses/sunglasses + uniform = /obj/item/clothing/under/shorts/red + +/////////////////Spooky Undead////////////////////// + +/obj/effect/mob_spawn/human/skeleton + name = "skeletal remains" + mob_name = "skeleton" + mob_species = /datum/species/skeleton + mob_gender = NEUTER + +/obj/effect/mob_spawn/human/skeleton/alive + death = FALSE + roundstart = FALSE + icon = 'icons/effects/blood.dmi' + icon_state = "remains" + flavour_text = "By unknown powers, your skeletal remains have been reanimated! Walk this mortal plain and terrorize all living adventurers who dare cross your path." + assignedrole = "Skeleton" + +/////////////////Officers////////////////////// + +/obj/effect/mob_spawn/human/bridgeofficer + name = "Bridge Officer" + mob_name = "Bridge Officer" + id_job = "Bridge Officer" + id_access = "Captain" + outfit = /datum/outfit/nanotrasenbridgeofficercorpse + +/datum/outfit/nanotrasenbridgeofficercorpse + name = "Bridge Officer Corpse" + l_ear = /obj/item/radio/headset/heads/hop + uniform = /obj/item/clothing/under/rank/centcom_officer + suit = /obj/item/clothing/suit/armor/bulletproof + shoes = /obj/item/clothing/shoes/black + glasses = /obj/item/clothing/glasses/sunglasses + id = /obj/item/card/id + + +/obj/effect/mob_spawn/human/commander + name = "Commander" + mob_name = "Commander" + id_job = "Commander" + id_access = "Captain" + outfit = /datum/outfit/nanotrasencommandercorpse + +/datum/outfit/nanotrasencommandercorpse + name = "Commander Corpse" + + uniform = /obj/item/clothing/under/rank/centcom/officer + gloves = /obj/item/clothing/gloves/color/white + shoes = /obj/item/clothing/shoes/centcom + head = /obj/item/clothing/head/beret/centcom/officer + glasses = /obj/item/clothing/glasses/hud/security/sunglasses + id = /obj/item/card/id/centcom + + + +/obj/effect/mob_spawn/human/abductor + name = "abductor" + mob_name = "alien" + mob_species = /datum/species/abductor + outfit = /datum/outfit/abductorcorpse + +/datum/outfit/abductorcorpse + name = "Abductor Corpse" + uniform = /obj/item/clothing/under/color/grey + shoes = /obj/item/clothing/shoes/combat + +//For ghost bar. +/obj/effect/mob_spawn/human/alive/space_bar_patron + name = "Bar cryogenics" + mob_name = "Bar patron" + random = TRUE + permanent = TRUE + uses = -1 + outfit = /datum/outfit/spacebartender + assignedrole = "Space Bar Patron" + +/obj/effect/mob_spawn/human/alive/space_bar_patron/attack_hand(mob/user) + var/despawn = alert("Return to cryosleep? (Warning, Your mob will be deleted!)",,"Yes","No") + if(despawn == "No" || !loc || !Adjacent(user)) + return + user.visible_message("[user.name] climbs back into cryosleep...") + qdel(user) + +/datum/outfit/cryobartender + name = "Cryogenic Bartender" + uniform = /obj/item/clothing/under/rank/bartender + back = /obj/item/storage/backpack + shoes = /obj/item/clothing/shoes/black + suit = /obj/item/clothing/suit/armor/vest + glasses = /obj/item/clothing/glasses/sunglasses/reagent diff --git a/code/modules/awaymissions/exile.dm b/code/modules/awaymissions/exile.dm index 62700812a6b..5f750c16144 100644 --- a/code/modules/awaymissions/exile.dm +++ b/code/modules/awaymissions/exile.dm @@ -1,44 +1,44 @@ -//Exile implants will allow you to use the station gate, but not return home. -//This will allow security to exile badguys/for badguys to exile their kill targets - -/obj/item/implant/exile - name = "exile implant" - desc = "Prevents you from returning from away missions" - origin_tech = "materials=2;biotech=3;magnets=2;bluespace=3" - activated = 0 - -/obj/item/implant/exile/get_data() - var/dat = {"Implant Specifications:
        - Name: Nanotrasen Employee Exile Implant
        - Implant Details: The onboard gateway system has been modified to reject entry by individuals containing this implant
        "} - return dat - - -/obj/item/implanter/exile - name = "implanter (exile)" - -/obj/item/implanter/exile/New() - imp = new /obj/item/implant/exile( src ) - ..() - -/obj/item/implantcase/exile - name = "implant case - 'Exile'" - desc = "A glass case containing an exile implant." - -/obj/item/implantcase/exile/New() - imp = new /obj/item/implant/exile(src) - ..() - - -/obj/structure/closet/secure_closet/exile - name = "exile implants" - req_access = list(ACCESS_ARMORY) - -/obj/structure/closet/secure_closet/exile/New() - ..() - new /obj/item/implanter/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) \ No newline at end of file +//Exile implants will allow you to use the station gate, but not return home. +//This will allow security to exile badguys/for badguys to exile their kill targets + +/obj/item/implant/exile + name = "exile implant" + desc = "Prevents you from returning from away missions" + origin_tech = "materials=2;biotech=3;magnets=2;bluespace=3" + activated = 0 + +/obj/item/implant/exile/get_data() + var/dat = {"Implant Specifications:
        + Name: Nanotrasen Employee Exile Implant
        + Implant Details: The onboard gateway system has been modified to reject entry by individuals containing this implant
        "} + return dat + + +/obj/item/implanter/exile + name = "implanter (exile)" + +/obj/item/implanter/exile/New() + imp = new /obj/item/implant/exile( src ) + ..() + +/obj/item/implantcase/exile + name = "implant case - 'Exile'" + desc = "A glass case containing an exile implant." + +/obj/item/implantcase/exile/New() + imp = new /obj/item/implant/exile(src) + ..() + + +/obj/structure/closet/secure_closet/exile + name = "exile implants" + req_access = list(ACCESS_ARMORY) + +/obj/structure/closet/secure_closet/exile/New() + ..() + new /obj/item/implanter/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm index 8bacd9a839c..76e3fed2ebf 100644 --- a/code/modules/awaymissions/gateway.dm +++ b/code/modules/awaymissions/gateway.dm @@ -1,295 +1,295 @@ -var/obj/machinery/gateway/centerstation/the_gateway = null -/obj/machinery/gateway - name = "gateway" - desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations." - icon = 'icons/obj/machines/gateway.dmi' - icon_state = "off" - density = 1 - anchored = 1 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/active = 0 - -/obj/machinery/gateway/Initialize() - ..() - update_icon() - update_density_from_dir() - -/obj/machinery/gateway/proc/update_density_from_dir() - if(dir == 2) - density = 0 - - -/obj/machinery/gateway/update_icon() - if(active) - icon_state = "on" - return - icon_state = "off" - - - -//this is da important part wot makes things go -/obj/machinery/gateway/centerstation - density = 1 - icon_state = "offcenter" - use_power = IDLE_POWER_USE - - //warping vars - var/list/linked = list() - var/ready = 0 //have we got all the parts for a gateway? - var/wait = 0 //this just grabs world.time at world start - var/obj/machinery/gateway/centeraway/awaygate = null - -/obj/machinery/gateway/centerstation/New() - ..() - if(!the_gateway) - the_gateway = src - -/obj/machinery/gateway/centerstation/Initialize() - ..() - update_icon() - wait = world.time + config.gateway_delay //+ thirty minutes default - awaygate = locate(/obj/machinery/gateway/centeraway) in world - -/obj/machinery/gateway/centerstation/update_density_from_dir() - return - -/obj/machinery/gateway/centerstation/Destroy() - if(the_gateway == src) - the_gateway = null - return ..() - -/obj/machinery/gateway/centerstation/update_icon() - if(active) - icon_state = "oncenter" - return - icon_state = "offcenter" - - - -/obj/machinery/gateway/centerstation/process() - if(stat & (NOPOWER)) - if(active) toggleoff() - return - - if(active) - use_power(5000) - - -/obj/machinery/gateway/centerstation/proc/detect() - linked = list() //clear the list - var/turf/T = loc - - for(var/i in alldirs) - T = get_step(loc, i) - var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T - if(G) - linked.Add(G) - continue - - //this is only done if we fail to find a part - ready = 0 - toggleoff() - break - - if(linked.len == 8) - ready = 1 - - -/obj/machinery/gateway/centerstation/proc/toggleon(mob/user as mob) - if(!ready) - return - if(linked.len != 8) - return - if(!powered()) - return - if(!awaygate) - awaygate = locate(/obj/machinery/gateway/centeraway) in world - if(!awaygate) - to_chat(user, "Error: No destination found.") - return - if(world.time < wait) - to_chat(user, "Error: Warpspace triangulation in progress. Estimated time to completion: [round(((wait - world.time) / 10) / 60)] minutes.") - return - - for(var/obj/machinery/gateway/G in linked) - G.active = 1 - G.update_icon() - active = 1 - update_icon() - - -/obj/machinery/gateway/centerstation/proc/toggleoff() - for(var/obj/machinery/gateway/G in linked) - G.active = 0 - G.update_icon() - active = 0 - update_icon() - - -/obj/machinery/gateway/centerstation/attack_hand(mob/user as mob) - if(!ready) - detect() - return - if(!active) - toggleon(user) - return - toggleoff() - - -//okay, here's the good teleporting stuff -/obj/machinery/gateway/centerstation/Bumped(atom/movable/M as mob|obj) - if(!ready) - return - if(!active) - return - if(!awaygate) - return - - if(awaygate.calibrated) - M.forceMove(get_step(awaygate.loc, SOUTH)) - M.dir = SOUTH - return - else - var/obj/effect/landmark/dest = pick(awaydestinations) - if(dest) - M.forceMove(dest.loc) - M.dir = SOUTH - use_power(5000) - return - - -/obj/machinery/gateway/centerstation/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W,/obj/item/multitool)) - to_chat(user, "The gate is already calibrated, there is no work for you to do here.") - return - return ..() - -/////////////////////////////////////Away//////////////////////// - - -/obj/machinery/gateway/centeraway - density = 1 - icon_state = "offcenter" - use_power = NO_POWER_USE - var/calibrated = 1 - var/list/linked = list() //a list of the connected gateway chunks - var/ready = 0 - var/obj/machinery/gateway/centeraway/stationgate = null - - -/obj/machinery/gateway/centeraway/Initialize() - ..() - update_icon() - stationgate = locate(/obj/machinery/gateway/centerstation) in world - - -/obj/machinery/gateway/centeraway/update_density_from_dir() - return - -/obj/machinery/gateway/centeraway/update_icon() - if(active) - icon_state = "oncenter" - return - icon_state = "offcenter" - - -/obj/machinery/gateway/centeraway/proc/detect() - linked = list() //clear the list - var/turf/T = loc - - for(var/i in alldirs) - T = get_step(loc, i) - var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T - if(G) - linked.Add(G) - continue - - //this is only done if we fail to find a part - ready = 0 - toggleoff() - break - - if(linked.len == 8) - ready = 1 - - -/obj/machinery/gateway/centeraway/proc/toggleon(mob/user as mob) - if(!ready) - return - if(linked.len != 8) - return - if(!stationgate) - stationgate = locate(/obj/machinery/gateway/centerstation) in world - if(!stationgate) - to_chat(user, "Error: No destination found.") - return - - for(var/obj/machinery/gateway/G in linked) - G.active = 1 - G.update_icon() - active = 1 - update_icon() - - -/obj/machinery/gateway/centeraway/proc/toggleoff() - for(var/obj/machinery/gateway/G in linked) - G.active = 0 - G.update_icon() - active = 0 - update_icon() - - -/obj/machinery/gateway/centeraway/attack_hand(mob/user as mob) - if(!ready) - detect() - return - if(!active) - toggleon(user) - return - toggleoff() - - -/obj/machinery/gateway/centeraway/Bumped(atom/movable/AM) - if(!ready) - return - if(!active) - return - if(!stationgate || QDELETED(stationgate)) - return - if(isliving(AM)) - if(exilecheck(AM)) - return - else - for(var/mob/living/L in AM.contents) - if(exilecheck(L)) - atom_say("Rejecting [AM]: Exile implant detected in contained lifeform.") - return - if(AM.has_buckled_mobs()) - for(var/mob/living/L in AM.buckled_mobs) - if(exilecheck(L)) - atom_say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") - return - AM.forceMove(get_step(stationgate.loc, SOUTH)) - AM.setDir(SOUTH) - if(ismob(AM)) - var/mob/M = AM - if(M.client) - M.client.move_delay = max(world.time + 5, M.client.move_delay) - -/obj/machinery/gateway/centeraway/proc/exilecheck(var/mob/living/carbon/M) - for(var/obj/item/implant/exile/E in M)//Checking that there is an exile implant in the contents - if(E.imp_in == M)//Checking that it's actually implanted vs just in their pocket - to_chat(M, "The station gate has detected your exile implant and is blocking your entry.") - return 1 - return 0 - -/obj/machinery/gateway/centeraway/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W,/obj/item/multitool)) - if(calibrated) - to_chat(user, "The gate is already calibrated, there is no work for you to do here.") - return - else - to_chat(user, "Recalibration successful!: This gate's systems have been fine tuned. Travel to this gate will now be on target.") - calibrated = 1 - return - return ..() \ No newline at end of file +var/obj/machinery/gateway/centerstation/the_gateway = null +/obj/machinery/gateway + name = "gateway" + desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations." + icon = 'icons/obj/machines/gateway.dmi' + icon_state = "off" + density = 1 + anchored = 1 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/active = 0 + +/obj/machinery/gateway/Initialize() + ..() + update_icon() + update_density_from_dir() + +/obj/machinery/gateway/proc/update_density_from_dir() + if(dir == 2) + density = 0 + + +/obj/machinery/gateway/update_icon() + if(active) + icon_state = "on" + return + icon_state = "off" + + + +//this is da important part wot makes things go +/obj/machinery/gateway/centerstation + density = 1 + icon_state = "offcenter" + use_power = IDLE_POWER_USE + + //warping vars + var/list/linked = list() + var/ready = 0 //have we got all the parts for a gateway? + var/wait = 0 //this just grabs world.time at world start + var/obj/machinery/gateway/centeraway/awaygate = null + +/obj/machinery/gateway/centerstation/New() + ..() + if(!the_gateway) + the_gateway = src + +/obj/machinery/gateway/centerstation/Initialize() + ..() + update_icon() + wait = world.time + config.gateway_delay //+ thirty minutes default + awaygate = locate(/obj/machinery/gateway/centeraway) in world + +/obj/machinery/gateway/centerstation/update_density_from_dir() + return + +/obj/machinery/gateway/centerstation/Destroy() + if(the_gateway == src) + the_gateway = null + return ..() + +/obj/machinery/gateway/centerstation/update_icon() + if(active) + icon_state = "oncenter" + return + icon_state = "offcenter" + + + +/obj/machinery/gateway/centerstation/process() + if(stat & (NOPOWER)) + if(active) toggleoff() + return + + if(active) + use_power(5000) + + +/obj/machinery/gateway/centerstation/proc/detect() + linked = list() //clear the list + var/turf/T = loc + + for(var/i in alldirs) + T = get_step(loc, i) + var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T + if(G) + linked.Add(G) + continue + + //this is only done if we fail to find a part + ready = 0 + toggleoff() + break + + if(linked.len == 8) + ready = 1 + + +/obj/machinery/gateway/centerstation/proc/toggleon(mob/user as mob) + if(!ready) + return + if(linked.len != 8) + return + if(!powered()) + return + if(!awaygate) + awaygate = locate(/obj/machinery/gateway/centeraway) in world + if(!awaygate) + to_chat(user, "Error: No destination found.") + return + if(world.time < wait) + to_chat(user, "Error: Warpspace triangulation in progress. Estimated time to completion: [round(((wait - world.time) / 10) / 60)] minutes.") + return + + for(var/obj/machinery/gateway/G in linked) + G.active = 1 + G.update_icon() + active = 1 + update_icon() + + +/obj/machinery/gateway/centerstation/proc/toggleoff() + for(var/obj/machinery/gateway/G in linked) + G.active = 0 + G.update_icon() + active = 0 + update_icon() + + +/obj/machinery/gateway/centerstation/attack_hand(mob/user as mob) + if(!ready) + detect() + return + if(!active) + toggleon(user) + return + toggleoff() + + +//okay, here's the good teleporting stuff +/obj/machinery/gateway/centerstation/Bumped(atom/movable/M as mob|obj) + if(!ready) + return + if(!active) + return + if(!awaygate) + return + + if(awaygate.calibrated) + M.forceMove(get_step(awaygate.loc, SOUTH)) + M.dir = SOUTH + return + else + var/obj/effect/landmark/dest = pick(awaydestinations) + if(dest) + M.forceMove(dest.loc) + M.dir = SOUTH + use_power(5000) + return + + +/obj/machinery/gateway/centerstation/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W,/obj/item/multitool)) + to_chat(user, "The gate is already calibrated, there is no work for you to do here.") + return + return ..() + +/////////////////////////////////////Away//////////////////////// + + +/obj/machinery/gateway/centeraway + density = 1 + icon_state = "offcenter" + use_power = NO_POWER_USE + var/calibrated = 1 + var/list/linked = list() //a list of the connected gateway chunks + var/ready = 0 + var/obj/machinery/gateway/centeraway/stationgate = null + + +/obj/machinery/gateway/centeraway/Initialize() + ..() + update_icon() + stationgate = locate(/obj/machinery/gateway/centerstation) in world + + +/obj/machinery/gateway/centeraway/update_density_from_dir() + return + +/obj/machinery/gateway/centeraway/update_icon() + if(active) + icon_state = "oncenter" + return + icon_state = "offcenter" + + +/obj/machinery/gateway/centeraway/proc/detect() + linked = list() //clear the list + var/turf/T = loc + + for(var/i in alldirs) + T = get_step(loc, i) + var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T + if(G) + linked.Add(G) + continue + + //this is only done if we fail to find a part + ready = 0 + toggleoff() + break + + if(linked.len == 8) + ready = 1 + + +/obj/machinery/gateway/centeraway/proc/toggleon(mob/user as mob) + if(!ready) + return + if(linked.len != 8) + return + if(!stationgate) + stationgate = locate(/obj/machinery/gateway/centerstation) in world + if(!stationgate) + to_chat(user, "Error: No destination found.") + return + + for(var/obj/machinery/gateway/G in linked) + G.active = 1 + G.update_icon() + active = 1 + update_icon() + + +/obj/machinery/gateway/centeraway/proc/toggleoff() + for(var/obj/machinery/gateway/G in linked) + G.active = 0 + G.update_icon() + active = 0 + update_icon() + + +/obj/machinery/gateway/centeraway/attack_hand(mob/user as mob) + if(!ready) + detect() + return + if(!active) + toggleon(user) + return + toggleoff() + + +/obj/machinery/gateway/centeraway/Bumped(atom/movable/AM) + if(!ready) + return + if(!active) + return + if(!stationgate || QDELETED(stationgate)) + return + if(isliving(AM)) + if(exilecheck(AM)) + return + else + for(var/mob/living/L in AM.contents) + if(exilecheck(L)) + atom_say("Rejecting [AM]: Exile implant detected in contained lifeform.") + return + if(AM.has_buckled_mobs()) + for(var/mob/living/L in AM.buckled_mobs) + if(exilecheck(L)) + atom_say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") + return + AM.forceMove(get_step(stationgate.loc, SOUTH)) + AM.setDir(SOUTH) + if(ismob(AM)) + var/mob/M = AM + if(M.client) + M.client.move_delay = max(world.time + 5, M.client.move_delay) + +/obj/machinery/gateway/centeraway/proc/exilecheck(var/mob/living/carbon/M) + for(var/obj/item/implant/exile/E in M)//Checking that there is an exile implant in the contents + if(E.imp_in == M)//Checking that it's actually implanted vs just in their pocket + to_chat(M, "The station gate has detected your exile implant and is blocking your entry.") + return 1 + return 0 + +/obj/machinery/gateway/centeraway/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W,/obj/item/multitool)) + if(calibrated) + to_chat(user, "The gate is already calibrated, there is no work for you to do here.") + return + else + to_chat(user, "Recalibration successful!: This gate's systems have been fine tuned. Travel to this gate will now be on target.") + calibrated = 1 + return + return ..() diff --git a/code/modules/awaymissions/loot.dm b/code/modules/awaymissions/loot.dm index 6497d62648a..266eb53c815 100644 --- a/code/modules/awaymissions/loot.dm +++ b/code/modules/awaymissions/loot.dm @@ -1,25 +1,25 @@ -/obj/effect/spawner/away/lootdrop - icon = 'icons/mob/screen_gen.dmi' - icon_state = "x2" - var/lootcount = 1 //how many items will be spawned - var/lootdoubles = 0 //if the same item can be spawned twice - var/loot = "" //a list of possible items to spawn- a string of paths - -/obj/effect/spawner/away/lootdrop/Initialize() - ..() - var/list/things = params2list(loot) - - if(things && things.len) - for(var/i = lootcount, i > 0, i--) - if(!things.len) - return - - var/loot_spawn = pick(things) - var/loot_path = text2path(loot_spawn) - - if(!loot_path || !lootdoubles) - things.Remove(loot_spawn) - continue - - new loot_path(get_turf(src)) - qdel(src) \ No newline at end of file +/obj/effect/spawner/away/lootdrop + icon = 'icons/mob/screen_gen.dmi' + icon_state = "x2" + var/lootcount = 1 //how many items will be spawned + var/lootdoubles = 0 //if the same item can be spawned twice + var/loot = "" //a list of possible items to spawn- a string of paths + +/obj/effect/spawner/away/lootdrop/Initialize() + ..() + var/list/things = params2list(loot) + + if(things && things.len) + for(var/i = lootcount, i > 0, i--) + if(!things.len) + return + + var/loot_spawn = pick(things) + var/loot_path = text2path(loot_spawn) + + if(!loot_path || !lootdoubles) + things.Remove(loot_spawn) + continue + + new loot_path(get_turf(src)) + qdel(src) diff --git a/code/modules/awaymissions/maploader/dmm_suite.dm b/code/modules/awaymissions/maploader/dmm_suite.dm index 21c2dac0dc8..a4da02b4170 100644 --- a/code/modules/awaymissions/maploader/dmm_suite.dm +++ b/code/modules/awaymissions/maploader/dmm_suite.dm @@ -70,4 +70,4 @@ dmm_suite{ // map_name: A valid name for the map to be saved, such as "castle" (Required). // flags: Any, or a combination, of several bit flags (Optional, see documentation). } - } \ No newline at end of file + } diff --git a/code/modules/awaymissions/mission_code/beach.dm b/code/modules/awaymissions/mission_code/beach.dm index 2da10ecbd98..9baaae33aae 100644 --- a/code/modules/awaymissions/mission_code/beach.dm +++ b/code/modules/awaymissions/mission_code/beach.dm @@ -25,4 +25,4 @@ W.dir = dir spawn(1) W.loc = get_step(W, dir) - water_timer = addtimer(CALLBACK(src, .proc/drip), water_frequency, TIMER_STOPPABLE) \ No newline at end of file + water_timer = addtimer(CALLBACK(src, .proc/drip), water_frequency, TIMER_STOPPABLE) diff --git a/code/modules/awaymissions/mission_code/blackmarketpackers.dm b/code/modules/awaymissions/mission_code/blackmarketpackers.dm index 73e288b56ed..fa4e6081594 100644 --- a/code/modules/awaymissions/mission_code/blackmarketpackers.dm +++ b/code/modules/awaymissions/mission_code/blackmarketpackers.dm @@ -23,4 +23,4 @@ /area/awaymission/BMPship/Gate name = "\improper Gateway Block" icon_state = "away4" - requires_power = TRUE \ No newline at end of file + requires_power = TRUE diff --git a/code/modules/awaymissions/mission_code/centcomAway.dm b/code/modules/awaymissions/mission_code/centcomAway.dm index 1dc26f932e0..bd9890aa43a 100644 --- a/code/modules/awaymissions/mission_code/centcomAway.dm +++ b/code/modules/awaymissions/mission_code/centcomAway.dm @@ -60,4 +60,4 @@ teams never did figure out what happened that last time... and I can't wrap my head \ around it myself. Why would a shuttle full of evacuees all snap and beat each other \ to death the moment they reached safety?
        \ - - D. Cereza" \ No newline at end of file + - D. Cereza" diff --git a/code/modules/awaymissions/mission_code/evil_santa.dm b/code/modules/awaymissions/mission_code/evil_santa.dm index 1597491b800..19ec3ad2dc5 100644 --- a/code/modules/awaymissions/mission_code/evil_santa.dm +++ b/code/modules/awaymissions/mission_code/evil_santa.dm @@ -20,4 +20,4 @@ Couldn't make it back to my shack. That gun would have helped, if only I brought it...
        \ Cave-in has me trapped in here, I just hope the distress signal reaches help in time...
        \
        \ - He knows. He knows. He knows. He knows. He knows. He knows. He knows. He knows. He knows." \ No newline at end of file + He knows. He knows. He knows. He knows. He knows. He knows. He knows. He knows. He knows." diff --git a/code/modules/awaymissions/mission_code/ghost_role_spawners/oldstation.dm b/code/modules/awaymissions/mission_code/ghost_role_spawners/oldstation.dm index 8dc3692179e..bb1aae5f8a8 100644 --- a/code/modules/awaymissions/mission_code/ghost_role_spawners/oldstation.dm +++ b/code/modules/awaymissions/mission_code/ghost_role_spawners/oldstation.dm @@ -107,4 +107,4 @@ /obj/structure/showcase/machinery/oldpod/used name = "opened cryogenic pod" - desc = "A cryogenic pod that has recently discharged its occupant. The pod appears non-functional." \ No newline at end of file + desc = "A cryogenic pod that has recently discharged its occupant. The pod appears non-functional." diff --git a/code/modules/awaymissions/mission_code/ruins/oldstation.dm b/code/modules/awaymissions/mission_code/ruins/oldstation.dm index e312dba541e..e939cf06f32 100644 --- a/code/modules/awaymissions/mission_code/ruins/oldstation.dm +++ b/code/modules/awaymissions/mission_code/ruins/oldstation.dm @@ -364,4 +364,4 @@ /area/ruin/space/ancientstation/hivebot name = "Hivebot Mothership" - icon_state = "teleporter" \ No newline at end of file + icon_state = "teleporter" diff --git a/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm b/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm index 72b45e45e6e..c5b56805213 100644 --- a/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm +++ b/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm @@ -14,4 +14,4 @@ /obj/item/spellbook/oneuse/summonitem = 20, /obj/item/spellbook/oneuse/forcewall = 10, /obj/item/soulstone = 15 //spooky wizard stuff - ) \ No newline at end of file + ) diff --git a/code/modules/awaymissions/mission_code/spacebattle.dm b/code/modules/awaymissions/mission_code/spacebattle.dm index 146597fe457..c68a61a6748 100644 --- a/code/modules/awaymissions/mission_code/spacebattle.dm +++ b/code/modules/awaymissions/mission_code/spacebattle.dm @@ -32,4 +32,4 @@ name = "\improper Syndicate Fighter" /area/awaymission/spacebattle/secret - name = "\improper Hidden Chamber" \ No newline at end of file + name = "\improper Hidden Chamber" diff --git a/code/modules/awaymissions/mission_code/spacehotel.dm b/code/modules/awaymissions/mission_code/spacehotel.dm index e9c74d73ea9..45ebf9700ca 100644 --- a/code/modules/awaymissions/mission_code/spacehotel.dm +++ b/code/modules/awaymissions/mission_code/spacehotel.dm @@ -276,4 +276,4 @@ spawn(300) if(D.occupant == deadbeat) // they still haven't checked out... - checkout(roomid) \ No newline at end of file + checkout(roomid) diff --git a/code/modules/awaymissions/mission_code/stationCollision.dm b/code/modules/awaymissions/mission_code/stationCollision.dm index 8d6a7fabe05..deab7570d3a 100644 --- a/code/modules/awaymissions/mission_code/stationCollision.dm +++ b/code/modules/awaymissions/mission_code/stationCollision.dm @@ -199,4 +199,4 @@ var/sc_safecode5 = "[rand(0,9)]" return /obj/singularity/narsie/sc_Narsie/ex_act() - return \ No newline at end of file + return diff --git a/code/modules/awaymissions/pamphlet.dm b/code/modules/awaymissions/pamphlet.dm index 27ef5097e58..ffbb188ada2 100644 --- a/code/modules/awaymissions/pamphlet.dm +++ b/code/modules/awaymissions/pamphlet.dm @@ -1,38 +1,38 @@ -/obj/item/paper/pamphlet - name = "pamphlet" - icon_state = "pamphlet" - info = "Welcome to the Nanotrasen Gateway project...
        \ - Congratulations! If you're reading this, you and your superiors have decided that you're \ - ready to commit to a life spent colonising the rolling hills of far away worlds. You \ - must be ready for a lifetime of adventure, a little bit of hard work, and an award \ - winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.
        \ -
        Because we care about you, we feel it is only fair to make sure you know the risks \ - before you commit to joining the Nanotrasen Gateway project. All away destinations have \ - been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \ - We've even left a case of space beer along with the basic materials you'll need to expand \ - Nanotrasen's operational area and start your new life.

        \ - Gateway Operation Basics
        \ - All Nanotrasen approved Gateways operate on the same basic principals. They operate off \ - area equipment power as you would expect, but they also require a backup wire with at least \ - 128, 000 Watts of power running through it. Without this supply, it cannot safely function \ - and will reject all attempts at operation.

        \ - Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \ - searching for an output location. The amount of time this takes is variable, but the Gateway \ - interface will give you an estimate accurate to the minute. Power loss will not interrupt the \ - searching process. Influenza will not interrupt the searching process. Temporal anomalies \ - may cause the estimate to be inaccurate, but will not interrupt the searching process.

        \ - Life On The Other Side
        \ - Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \ - This is a normal side effect of travelling vast distances in a short period of time. You should \ - survey the immediate area, and attempt to locate your complimentary case of space beer. Our \ - expeditionary teams have ensured the complete safety of all away locations, but in a small \ - number of cases, the Gateway they have established may not be immediately obvious. \ - Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \ -

        A New World
        \ - As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \ - Though complete safety is assured, participants are advised to prepare for inhospitable \ - environs." - -//we don't want the silly text overlay! -/obj/item/paper/pamphlet/update_icon() - return \ No newline at end of file +/obj/item/paper/pamphlet + name = "pamphlet" + icon_state = "pamphlet" + info = "Welcome to the Nanotrasen Gateway project...
        \ + Congratulations! If you're reading this, you and your superiors have decided that you're \ + ready to commit to a life spent colonising the rolling hills of far away worlds. You \ + must be ready for a lifetime of adventure, a little bit of hard work, and an award \ + winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.
        \ +
        Because we care about you, we feel it is only fair to make sure you know the risks \ + before you commit to joining the Nanotrasen Gateway project. All away destinations have \ + been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \ + We've even left a case of space beer along with the basic materials you'll need to expand \ + Nanotrasen's operational area and start your new life.

        \ + Gateway Operation Basics
        \ + All Nanotrasen approved Gateways operate on the same basic principals. They operate off \ + area equipment power as you would expect, but they also require a backup wire with at least \ + 128, 000 Watts of power running through it. Without this supply, it cannot safely function \ + and will reject all attempts at operation.

        \ + Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \ + searching for an output location. The amount of time this takes is variable, but the Gateway \ + interface will give you an estimate accurate to the minute. Power loss will not interrupt the \ + searching process. Influenza will not interrupt the searching process. Temporal anomalies \ + may cause the estimate to be inaccurate, but will not interrupt the searching process.

        \ + Life On The Other Side
        \ + Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \ + This is a normal side effect of travelling vast distances in a short period of time. You should \ + survey the immediate area, and attempt to locate your complimentary case of space beer. Our \ + expeditionary teams have ensured the complete safety of all away locations, but in a small \ + number of cases, the Gateway they have established may not be immediately obvious. \ + Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \ +

        A New World
        \ + As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \ + Though complete safety is assured, participants are advised to prepare for inhospitable \ + environs." + +//we don't want the silly text overlay! +/obj/item/paper/pamphlet/update_icon() + return diff --git a/code/modules/awaymissions/zvis.dm b/code/modules/awaymissions/zvis.dm index 102554f2e6e..b35b0fb6ffc 100644 --- a/code/modules/awaymissions/zvis.dm +++ b/code/modules/awaymissions/zvis.dm @@ -370,4 +370,4 @@ screen_loc = "CENTER[ox >= 0 ? "+" : ""][ox],CENTER[oy >= 0 ? "+" : ""][oy]" /obj/effect/view_portal_dummy/attack_ghost(mob/user) - owner.attack_ghost(user) \ No newline at end of file + owner.attack_ghost(user) diff --git a/code/modules/buildmode/README.md b/code/modules/buildmode/README.md index 37ce2ce6b1b..015e6ce6a66 100644 --- a/code/modules/buildmode/README.md +++ b/code/modules/buildmode/README.md @@ -294,4 +294,4 @@ Existing varieties: + *Left click a location*: - Cause an explosion where you clicked. \ No newline at end of file + Cause an explosion where you clicked. diff --git a/code/modules/buildmode/bm_mode.dm b/code/modules/buildmode/bm_mode.dm index de4c09a192d..1ce23555e3e 100644 --- a/code/modules/buildmode/bm_mode.dm +++ b/code/modules/buildmode/bm_mode.dm @@ -84,4 +84,4 @@ deselect_region() /datum/buildmode_mode/proc/handle_selected_region(mob/user, params) - return \ No newline at end of file + return diff --git a/code/modules/buildmode/buildmode.dm b/code/modules/buildmode/buildmode.dm index 7f43f20c934..6c260c41cb2 100644 --- a/code/modules/buildmode/buildmode.dm +++ b/code/modules/buildmode/buildmode.dm @@ -125,4 +125,4 @@ #undef BM_SWITCHSTATE_NONE #undef BM_SWITCHSTATE_MODE -#undef BM_SWITCHSTATE_DIR \ No newline at end of file +#undef BM_SWITCHSTATE_DIR diff --git a/code/modules/buildmode/buttons.dm b/code/modules/buildmode/buttons.dm index c07be97a9eb..2bec31f8650 100644 --- a/code/modules/buildmode/buttons.dm +++ b/code/modules/buildmode/buttons.dm @@ -85,4 +85,4 @@ /obj/screen/buildmode/quit/Click() bd.quit() - return TRUE \ No newline at end of file + return TRUE diff --git a/code/modules/buildmode/effects/line.dm b/code/modules/buildmode/effects/line.dm index 4b59e8a673c..b49d35af095 100644 --- a/code/modules/buildmode/effects/line.dm +++ b/code/modules/buildmode/effects/line.dm @@ -25,4 +25,4 @@ cl.images -= I cl = null QDEL_NULL(I) - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/buildmode/submodes/advanced.dm b/code/modules/buildmode/submodes/advanced.dm index fadd46753bb..c2e64fb488e 100644 --- a/code/modules/buildmode/submodes/advanced.dm +++ b/code/modules/buildmode/submodes/advanced.dm @@ -56,4 +56,4 @@ if(isobj(object)) log_admin("Build Mode: [key_name(user)] deleted [object] at ([object.x],[object.y],[object.z])") qdel(object) - \ No newline at end of file + diff --git a/code/modules/buildmode/submodes/atmos.dm b/code/modules/buildmode/submodes/atmos.dm index f12fdb1eb1a..b3a07374846 100644 --- a/code/modules/buildmode/submodes/atmos.dm +++ b/code/modules/buildmode/submodes/atmos.dm @@ -69,4 +69,4 @@ T.air_update_turf() // admin log - log_admin("Build Mode: [key_name(user)] changed the atmos of region [COORD(cornerA)] to [COORD(cornerB)]. T: [temperature], P: [pressure], Ox: [oxygen]%, N2: [nitrogen]%, Plsma: [plasma]%, CO2: [cdiox]%, N2O: [nitrox]%. [ctrl_click ? "Overwrote base unsimulated turf gases." : ""]") \ No newline at end of file + log_admin("Build Mode: [key_name(user)] changed the atmos of region [COORD(cornerA)] to [COORD(cornerB)]. T: [temperature], P: [pressure], Ox: [oxygen]%, N2: [nitrogen]%, Plsma: [plasma]%, CO2: [cdiox]%, N2O: [nitrox]%. [ctrl_click ? "Overwrote base unsimulated turf gases." : ""]") diff --git a/code/modules/buildmode/submodes/fill.dm b/code/modules/buildmode/submodes/fill.dm index b143b2c9b90..3c06a199e47 100644 --- a/code/modules/buildmode/submodes/fill.dm +++ b/code/modules/buildmode/submodes/fill.dm @@ -46,4 +46,4 @@ T.ChangeTurf(objholder) else var/obj/A = new objholder(T) - A.setDir(BM.build_dir) \ No newline at end of file + A.setDir(BM.build_dir) diff --git a/code/modules/buildmode/submodes/mapgen.dm b/code/modules/buildmode/submodes/mapgen.dm index 22be429c2ce..df6f6b73ed0 100644 --- a/code/modules/buildmode/submodes/mapgen.dm +++ b/code/modules/buildmode/submodes/mapgen.dm @@ -37,4 +37,4 @@ highlight_region(G.map) var/confirm = alert("Are you sure you want run the map generator?", "Run generator", "Yes", "No") if(confirm == "Yes") - G.generate() \ No newline at end of file + G.generate() diff --git a/code/modules/busy_space/air_traffic.dm b/code/modules/busy_space/air_traffic.dm index b5cbf8e6fda..a7f28f49fb6 100644 --- a/code/modules/busy_space/air_traffic.dm +++ b/code/modules/busy_space/air_traffic.dm @@ -126,4 +126,4 @@ var/datum/lore/atc_controller/atc = new/datum/lore/atc_controller msg(full_response) sleep(5 SECONDS) //Ship sends response to ATC - msg(full_closure,"[prefix] [shipname]") \ No newline at end of file + msg(full_closure,"[prefix] [shipname]") diff --git a/code/modules/busy_space/organizations.dm b/code/modules/busy_space/organizations.dm index f80e82938ec..df86aefd75e 100644 --- a/code/modules/busy_space/organizations.dm +++ b/code/modules/busy_space/organizations.dm @@ -546,4 +546,4 @@ "Xarxis 5", "Haverick", "Darsing", - "Norfolk") \ No newline at end of file + "Norfolk") diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 0feca103c12..85615be1ee5 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -99,4 +99,4 @@ var/client_keysend_amount = 0 var/next_keysend_reset = 0 var/next_keysend_trip_reset = 0 - var/keysend_tripped = FALSE \ No newline at end of file + var/keysend_tripped = FALSE diff --git a/code/modules/client/message.dm b/code/modules/client/message.dm index ba09e7e1567..5c612339621 100644 --- a/code/modules/client/message.dm +++ b/code/modules/client/message.dm @@ -7,4 +7,4 @@ proc/addclientmessage(var/ckey, var/message) var/list/L = GLOB.clientmessages[ckey] if(!L) GLOB.clientmessages[ckey] = L = list() - L += message \ No newline at end of file + L += message diff --git a/code/modules/client/preference/loadout/loadout_cosmetics.dm b/code/modules/client/preference/loadout/loadout_cosmetics.dm index b7ac6c39493..0375de2de53 100644 --- a/code/modules/client/preference/loadout/loadout_cosmetics.dm +++ b/code/modules/client/preference/loadout/loadout_cosmetics.dm @@ -21,4 +21,4 @@ /datum/gear/lipstick/lime display_name = "lipstick, lime" - path = /obj/item/lipstick/lime \ No newline at end of file + path = /obj/item/lipstick/lime diff --git a/code/modules/client/preference/loadout/loadout_donor.dm b/code/modules/client/preference/loadout/loadout_donor.dm index 830b10b7055..7d43bd6b86a 100644 --- a/code/modules/client/preference/loadout/loadout_donor.dm +++ b/code/modules/client/preference/loadout/loadout_donor.dm @@ -93,4 +93,4 @@ display_name = "Gold ID Decal" path = /obj/item/id_decal/gold donator_tier = 4 - cost = 4 \ No newline at end of file + cost = 4 diff --git a/code/modules/client/preference/loadout/loadout_glasses.dm b/code/modules/client/preference/loadout/loadout_glasses.dm index 94f2555bdba..a84af914c49 100644 --- a/code/modules/client/preference/loadout/loadout_glasses.dm +++ b/code/modules/client/preference/loadout/loadout_glasses.dm @@ -21,4 +21,4 @@ /datum/gear/glasses/prescription display_name = "Prescription glasses" - path = /obj/item/clothing/glasses/regular \ No newline at end of file + path = /obj/item/clothing/glasses/regular diff --git a/code/modules/client/preference/loadout/loadout_gloves.dm b/code/modules/client/preference/loadout/loadout_gloves.dm index cd8d5a7c8e1..879cb840506 100644 --- a/code/modules/client/preference/loadout/loadout_gloves.dm +++ b/code/modules/client/preference/loadout/loadout_gloves.dm @@ -5,4 +5,4 @@ /datum/gear/gloves/fingerless display_name = "Fingerless Gloves" - path = /obj/item/clothing/gloves/fingerless \ No newline at end of file + path = /obj/item/clothing/gloves/fingerless diff --git a/code/modules/client/preference/loadout/loadout_shoes.dm b/code/modules/client/preference/loadout/loadout_shoes.dm index 5031f4333fb..ab000b209ac 100644 --- a/code/modules/client/preference/loadout/loadout_shoes.dm +++ b/code/modules/client/preference/loadout/loadout_shoes.dm @@ -55,4 +55,4 @@ /datum/gear/shoes/whiteshoes display_name = "White shoes" - path = /obj/item/clothing/shoes/white \ No newline at end of file + path = /obj/item/clothing/shoes/white diff --git a/code/modules/client/view.dm b/code/modules/client/view.dm index f4e74e203e8..ac862933afb 100644 --- a/code/modules/client/view.dm +++ b/code/modules/client/view.dm @@ -87,4 +87,4 @@ return to_chat(src, "View range set to [viewNum_to_text(view_range)]") - AddViewMod("custom", view_range) \ No newline at end of file + AddViewMod("custom", view_range) diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index f2e9e639dd6..da0873f162f 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -542,4 +542,4 @@ /obj/item/stamp/chameleon/broken/Initialize() . = ..() - chameleon_action.emp_randomise(INFINITY) \ No newline at end of file + chameleon_action.emp_randomise(INFINITY) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index adb35b0fb3b..d816e067502 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -1,803 +1,803 @@ -/obj/item/clothing - name = "clothing" - max_integrity = 200 - integrity_failure = 80 - resistance_flags = FLAMMABLE - var/list/species_restricted = null //Only these species can wear this kit. - var/scan_reagents = 0 //Can the wearer see reagents while it's equipped? - - /* - Sprites used when the clothing item is refit. This is done by setting icon_override. - For best results, if this is set then sprite_sheets should be null and vice versa, but that is by no means necessary. - Ideally, sprite_sheets_refit should be used for "hard" clothing items that can't change shape very well to fit the wearer (e.g. helmets, hardsuits), - while sprite_sheets should be used for "flexible" clothing items that do not need to be refitted (e.g. vox wearing jumpsuits). - */ - var/list/sprite_sheets_refit = null - lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' - var/alt_desc = null - var/flash_protect = 0 //What level of bright light protection item has. 1 = Flashers, Flashes, & Flashbangs | 2 = Welding | -1 = OH GOD WELDING BURNT OUT MY RETINAS - var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect - var/up = 0 //but seperated to allow items to protect but not impair vision, like space helmets - - var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down - var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv - var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW //what to toggle when toggled with weldingvisortoggle() - - var/toggle_message = null - var/alt_toggle_message = null - var/active_sound = null - var/toggle_sound = null - var/toggle_cooldown = null - var/cooldown = 0 - var/species_disguise = null - var/magical = FALSE - -/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc. - if(!can_use(user)) - return FALSE - - visor_toggling() - - to_chat(user, "You adjust \the [src] [up ? "up" : "down"].") - - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.head_update(src, forced = 1) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - return TRUE - -/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags - up = !up - flags ^= visor_flags - flags_inv ^= visor_flags_inv - flags_cover ^= initial(flags_cover) - icon_state = "[initial(icon_state)][up ? "up" : ""]" - if(visor_vars_to_toggle & VISOR_FLASHPROTECT) - flash_protect ^= initial(flash_protect) - if(visor_vars_to_toggle & VISOR_TINT) - tint ^= initial(tint) - -/obj/item/clothing/proc/can_use(mob/user) - if(user && ismob(user)) - if(!user.incapacitated()) - return TRUE - return FALSE - -//BS12: Species-restricted clothing check. -/obj/item/clothing/mob_can_equip(M as mob, slot) - - //if we can't equip the item anyway, don't bother with species_restricted (also cuts down on spam) - if(!..()) - return 0 - - // Skip species restriction checks on non-equipment slots - if(slot in list(slot_r_hand, slot_l_hand, slot_in_backpack, slot_l_store, slot_r_store)) - return 1 - - if(species_restricted && istype(M,/mob/living/carbon/human)) - - var/wearable = null - var/exclusive = null - var/mob/living/carbon/human/H = M - - if("exclude" in species_restricted) - exclusive = 1 - - if(H.dna.species) - if(exclusive) - if(!(H.dna.species.name in species_restricted)) - wearable = 1 - else - if(H.dna.species.name in species_restricted) - wearable = 1 - - if(!wearable) - to_chat(M, "Your species cannot wear [src].") - return 0 - - return 1 - -/obj/item/clothing/proc/refit_for_species(var/target_species) - //Set species_restricted list - switch(target_species) - if("Human", "Skrell") //humanoid bodytypes - species_restricted = list("exclude","Unathi","Tajaran","Diona","Vox","Wryn","Drask") - else - species_restricted = list(target_species) - - //Set icon - if(sprite_sheets && (target_species in sprite_sheets)) - icon_override = sprite_sheets[target_species] - else - icon_override = initial(icon_override) - - if(sprite_sheets_obj && (target_species in sprite_sheets_obj)) - icon = sprite_sheets_obj[target_species] - else - icon = initial(icon) - -//Ears: currently only used for headsets and earmuffs -/obj/item/clothing/ears - name = "ears" - w_class = WEIGHT_CLASS_TINY - throwforce = 2 - slot_flags = SLOT_EARS - resistance_flags = NONE - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/ears.dmi', - "Vox Armalis" = 'icons/mob/species/armalis/ears.dmi' - ) //We read you loud and skree-er. - -/obj/item/clothing/ears/attack_hand(mob/user) - if(!user) - return - - if(loc != user || !ishuman(user)) - ..() - return - - var/mob/living/carbon/human/H = user - if(H.l_ear != src && H.r_ear != src) - ..() - return - - if(!usr.canUnEquip(src)) - return - - var/obj/item/clothing/ears/O - if(slot_flags & SLOT_TWOEARS ) - O = (H.l_ear == src ? H.r_ear : H.l_ear) - user.unEquip(O) - if(!istype(src, /obj/item/clothing/ears/offear)) - qdel(O) - O = src - else - O = src - - user.unEquip(src) - - if(O) - user.put_in_hands(O) - O.add_fingerprint(user) - - if(istype(src, /obj/item/clothing/ears/offear)) - qdel(src) - - -/obj/item/clothing/ears/offear - name = "Other ear" - w_class = WEIGHT_CLASS_HUGE - icon = 'icons/mob/screen_gen.dmi' - icon_state = "block" - slot_flags = SLOT_EARS | SLOT_TWOEARS - -/obj/item/clothing/ears/offear/New(var/obj/O) - name = O.name - desc = O.desc - icon = O.icon - icon_state = O.icon_state - dir = O.dir - - -//Glasses -/obj/item/clothing/glasses - name = "glasses" - icon = 'icons/obj/clothing/glasses.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_cover = GLASSESCOVERSEYES - slot_flags = SLOT_EYES - materials = list(MAT_GLASS = 250) - var/vision_flags = 0 - var/see_in_dark = 0 //Base human is 2 - var/invis_view = SEE_INVISIBLE_LIVING - var/invis_override = 0 - var/lighting_alpha - - var/emagged = 0 - var/list/color_view = null//overrides client.color while worn - var/prescription = 0 - var/prescription_upgradable = 0 - var/over_mask = FALSE //Whether or not the eyewear is rendered above the mask. Purely cosmetic. - strip_delay = 20 // but seperated to allow items to protect but not impair vision, like space helmets - put_on_delay = 25 - resistance_flags = NONE - species_restricted = list("exclude","Kidan") -/* -SEE_SELF // can see self, no matter what -SEE_MOBS // can see all mobs, no matter what -SEE_OBJS // can see all objs, no matter what -SEE_TURFS // can see all turfs (and areas), no matter what -SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are - // in a lit area (via pixel_x,y or smooth movement), can see those pixels -BLIND // can't see anything -*/ - -/obj/item/clothing/glasses/verb/adjust_eyewear() //Adjust eyewear to be worn above or below the mask. - set name = "Adjust Eyewear" - set category = "Object" - set desc = "Adjust your eyewear to be worn over or under a mask." - set src in usr - - var/mob/living/carbon/human/user = usr - if(!istype(user)) - return - if(user.incapacitated()) //Dead spessmen adjust no glasses. Resting/buckled ones do, though - return - - var/action_fluff = "You adjust \the [src]" - if(user.glasses == src) - if(!user.canUnEquip(src)) - to_chat(usr, "[src] is stuck to you!") - return - if(attack_hand(user)) //Remove the glasses for this action. Prevents logic-defying instances where glasses phase through your mask as it ascends/descends to another plane of existence. - action_fluff = "You remove \the [src] and adjust it" - - over_mask = !over_mask - to_chat(user, "[action_fluff] to be worn [over_mask ? "over" : "under"] a mask.") - -//Gloves -/obj/item/clothing/gloves - name = "gloves" - gender = PLURAL //Carn: for grammarically correct text-parsing - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/clothing/gloves.dmi' - siemens_coefficient = 0.50 - body_parts_covered = HANDS - slot_flags = SLOT_GLOVES - attack_verb = list("challenged") - var/transfer_prints = FALSE - var/pickpocket = 0 //Master pickpocket? - var/clipped = 0 - strip_delay = 20 - put_on_delay = 40 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/gloves.dmi', - "Drask" = 'icons/mob/species/drask/gloves.dmi' - ) - -// Called just before an attack_hand(), in mob/UnarmedAttack() -/obj/item/clothing/gloves/proc/Touch(atom/A, proximity) - return 0 // return 1 to cancel attack_hand() - -/obj/item/clothing/gloves/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/wirecutters)) - if(!clipped) - playsound(src.loc, W.usesound, 100, 1) - user.visible_message("[user] snips the fingertips off [src].","You snip the fingertips off [src].") - clipped = 1 - name = "mangled [name]" - desc = "[desc] They have had the fingertips cut off of them." - update_icon() - else - to_chat(user, "[src] have already been clipped!") - return - else - return ..() - -/obj/item/clothing/under/proc/set_sensors(mob/user as mob) - var/mob/M = user - if(istype(M, /mob/dead/)) return - if(user.stat || user.restrained()) return - if(has_sensor >= 2) - to_chat(user, "The controls are locked.") - return 0 - if(has_sensor <= 0) - to_chat(user, "This suit does not have any sensors.") - return 0 - - var/list/modes = list("Off", "Binary sensors", "Vitals tracker", "Tracking beacon") - var/switchMode = input("Select a sensor mode:", "Suit Sensor Mode", modes[sensor_mode + 1]) in modes - if(get_dist(user, src) > 1) - to_chat(user, "You have moved too far away.") - return - sensor_mode = modes.Find(switchMode) - 1 - - if(src.loc == user) - switch(sensor_mode) - if(0) - to_chat(user, "You disable your suit's remote sensing equipment.") - if(1) - to_chat(user, "Your suit will now report whether you are live or dead.") - if(2) - to_chat(user, "Your suit will now report your vital lifesigns.") - if(3) - to_chat(user, "Your suit will now report your vital lifesigns as well as your coordinate position.") - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - if(H.w_uniform == src) - H.update_suit_sensors() - - else if(istype(src.loc, /mob)) - switch(sensor_mode) - if(0) - for(var/mob/V in viewers(user, 1)) - V.show_message("[user] disables [src.loc]'s remote sensing equipment.", 1) - if(1) - for(var/mob/V in viewers(user, 1)) - V.show_message("[user] turns [src.loc]'s remote sensors to binary.", 1) - if(2) - for(var/mob/V in viewers(user, 1)) - V.show_message("[user] sets [src.loc]'s sensors to track vitals.", 1) - if(3) - for(var/mob/V in viewers(user, 1)) - V.show_message("[user] sets [src.loc]'s sensors to maximum.", 1) - if(istype(src,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = src - if(H.w_uniform == src) - H.update_suit_sensors() - -/obj/item/clothing/under/verb/toggle() - set name = "Toggle Suit Sensors" - set category = "Object" - set src in usr - set_sensors(usr) - ..() - -//Head -/obj/item/clothing/head - name = "head" - icon = 'icons/obj/clothing/hats.dmi' - body_parts_covered = HEAD - slot_flags = SLOT_HEAD - var/blockTracking // Do we block AI tracking? - var/HUDType = null - - var/vision_flags = 0 - var/see_in_dark = 0 - var/lighting_alpha - - var/can_toggle = null - -//Mask -/obj/item/clothing/mask - name = "mask" - icon = 'icons/obj/clothing/masks.dmi' - body_parts_covered = HEAD - slot_flags = SLOT_MASK - var/mask_adjusted = 0 - var/adjusted_flags = null - strip_delay = 40 - put_on_delay = 40 - -//Proc that moves gas/breath masks out of the way -/obj/item/clothing/mask/proc/adjustmask(var/mob/user) - var/mob/living/carbon/human/H = usr //Used to check if the mask is on the head, to check if the hands are full, and to turn off internals if they were on when the mask was pushed out of the way. - if(user.incapacitated()) //This check allows you to adjust your masks while you're buckled into chairs or beds. - return - if(mask_adjusted) - icon_state = initial(icon_state) - gas_transfer_coefficient = initial(gas_transfer_coefficient) - permeability_coefficient = initial(permeability_coefficient) - to_chat(user, "You push \the [src] back into place.") - mask_adjusted = 0 - slot_flags = initial(slot_flags) - if(flags_inv != initial(flags_inv)) - if(initial(flags_inv) & HIDEFACE) //If the mask is one that hides the face and can be adjusted yet lost that trait when it was adjusted, make it hide the face again. - flags_inv |= HIDEFACE - if(flags != initial(flags)) - if(initial(flags) & AIRTIGHT) //If the mask is airtight and thus, one that you'd be able to run internals from yet can't because it was adjusted, make it airtight again. - flags |= AIRTIGHT - if(flags_cover != initial(flags_cover)) - if(initial(flags_cover) & MASKCOVERSMOUTH) //If the mask covers the mouth when it's down and can be adjusted yet lost that trait when it was adjusted, make it cover the mouth again. - flags_cover |= MASKCOVERSMOUTH - if(H.head == src && flags_inv == HIDEFACE) //Means that only things like bandanas and balaclavas will be affected since they obscure the identity of the wearer. - if(H.l_hand && H.r_hand) //If both hands are occupied, drop the object on the ground. - user.unEquip(src) - else //Otherwise, put it in an available hand, the active one preferentially. - src.loc = user - H.head = null - user.put_in_hands(src) - else - icon_state += "_up" - to_chat(user, "You push \the [src] out of the way.") - gas_transfer_coefficient = null - permeability_coefficient = null - mask_adjusted = 1 - if(adjusted_flags) - slot_flags = adjusted_flags - if(ishuman(user) && H.internal && !H.get_organ_slot("breathing_tube") && user.wear_mask == src) /*If the user was wearing the mask providing internals on their face at the time it was adjusted, turn off internals. - Otherwise, they adjusted it while it was in their hands or some such so we won't be needing to turn off internals.*/ - H.internal = null - H.update_action_buttons_icon() - if(flags_inv & HIDEFACE) //Means that only things like bandanas and balaclavas will be affected since they obscure the identity of the wearer. - flags_inv &= ~HIDEFACE /*Done after the above to avoid having to do a check for initial(src.flags_inv == HIDEFACE). - This reveals the user's face since the bandana will now be going on their head.*/ - if(flags_cover & MASKCOVERSMOUTH) //Mask won't cover the mouth any more since it's been pushed out of the way. Allows for CPRing with adjusted masks. - flags_cover &= ~MASKCOVERSMOUTH - if(flags & AIRTIGHT) //If the mask was airtight, it won't be anymore since you just pushed it off your face. - flags &= ~AIRTIGHT - if(user.wear_mask == src && initial(flags_inv) == HIDEFACE) //Means that you won't have to take off and put back on simple things like breath masks which, realistically, can just be pulled down off your face. - if(H.l_hand && H.r_hand) //If both hands are occupied, drop the object on the ground. - user.unEquip(src) - else //Otherwise, put it in an available hand, the active one preferentially. - src.loc = user - user.wear_mask = null - user.put_in_hands(src) - H.wear_mask_update(src, toggle_off = mask_adjusted) - usr.update_inv_wear_mask() - usr.update_inv_head() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -//Shoes -/obj/item/clothing/shoes - name = "shoes" - icon = 'icons/obj/clothing/shoes.dmi' - desc = "Comfortable-looking shoes." - gender = PLURAL //Carn: for grammatically correct text-parsing - var/chained = 0 - var/can_cut_open = 0 - var/cut_open = 0 - body_parts_covered = FEET - slot_flags = SLOT_FEET - - var/silence_steps = 0 - var/shoe_sound_footstep = 1 - var/shoe_sound = null - var/blood_state = BLOOD_STATE_NOT_BLOODY - var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_NOT_BLOODY = 0) - - permeability_coefficient = 0.50 - slowdown = SHOES_SLOWDOWN - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/shoes.dmi', - "Drask" = 'icons/mob/species/drask/shoes.dmi' - ) - -/obj/item/clothing/shoes/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/match) && src.loc == user) - var/obj/item/match/M = I - if(M.matchignite()) // Match isn't lit, but isn't burnt. - user.visible_message("[user] strikes a [M] on the bottom of [src], lighting it.","You strike the [M] on the bottom of [src] to light it.") - playsound(user.loc, 'sound/goonstation/misc/matchstick_light.ogg', 50, 1) - else - user.visible_message("[user] crushes the [M] into the bottom of [src], extinguishing it.","You crush the [M] into the bottom of [src], extinguishing it.") - M.dropped() - return - - if(istype(I, /obj/item/wirecutters)) - if(can_cut_open) - if(!cut_open) - playsound(src.loc, I.usesound, 100, 1) - user.visible_message("[user] cuts open the toes of [src].","You cut open the toes of [src].") - cut_open = 1 - icon_state = "[icon_state]_opentoe" - item_state = "[item_state]_opentoe" - name = "mangled [name]" - desc = "[desc] They have had their toes opened up." - update_icon() - else - to_chat(user, "[src] have already had [p_their()] toes cut open!") - return - else - return ..() - -/obj/item/clothing/shoes/proc/step_action(var/mob/living/carbon/human/H) //squeek squeek - SEND_SIGNAL(src, COMSIG_SHOES_STEP_ACTION) - if(shoe_sound) - var/turf/T = get_turf(H) - - if(!istype(H) || !istype(T)) - return 0 - - if(H.m_intent == MOVE_INTENT_RUN) - if(shoe_sound_footstep >= 2) - if(T.shoe_running_volume) - playsound(src, shoe_sound, T.shoe_running_volume, 1) - shoe_sound_footstep = 0 - else - shoe_sound_footstep++ - else if(T.shoe_walking_volume) - playsound(src, shoe_sound, T.shoe_walking_volume, 1) - - return 1 - -/obj/item/proc/negates_gravity() - return 0 - -//Suit -/obj/item/clothing/suit - icon = 'icons/obj/clothing/suits.dmi' - name = "suit" - var/fire_resist = T0C+100 - allowed = list(/obj/item/tank/emergency_oxygen) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - slot_flags = SLOT_OCLOTHING - var/blood_overlay_type = "suit" - var/suittoggled = FALSE - var/suit_adjusted = 0 - var/ignore_suitadjust = 1 - var/adjust_flavour = null - var/list/hide_tail_by_species = null - -//Proc that opens and closes jackets. -/obj/item/clothing/suit/proc/adjustsuit(var/mob/user) - if(!ignore_suitadjust) - if(!user.incapacitated()) - if(!(HULK in user.mutations)) - if(suit_adjusted) - var/flavour = "close" - icon_state = copytext(icon_state, 1, findtext(icon_state, "_open")) /*Trims the '_open' off the end of the icon state, thus avoiding a case where jackets that start open will - end up with a suffix of _open_open if adjusted twice, since their initial state is _open. */ - item_state = copytext(item_state, 1, findtext(item_state, "_open")) - if(adjust_flavour) - flavour = "[copytext(adjust_flavour, 3, length(adjust_flavour) + 1)] up" //Trims off the 'un' at the beginning of the word. unzip -> zip, unbutton->button. - to_chat(user, "You [flavour] \the [src].") - suit_adjusted = 0 //Suit is no longer adjusted. - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - else - var/flavour = "open" - icon_state += "_open" - item_state += "_open" - if(adjust_flavour) - flavour = "[adjust_flavour]" - to_chat(user, "You [flavour] \the [src].") - suit_adjusted = 1 //Suit's adjusted. - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - else - if(user.canUnEquip(src)) //Checks to see if the item can be unequipped. If so, lets shred. Otherwise, struggle and fail. - if(contents) //If the suit's got any storage capability... - for(var/obj/item/O in contents) //AVOIDING ITEM LOSS. Check through everything that's stored in the jacket and see if one of the items is a pocket. - if(istype(O, /obj/item/storage/internal)) //If it's a pocket... - if(O.contents) //Check to see if the pocket's got anything in it. - for(var/obj/item/I in O.contents) //Dump the pocket out onto the floor below the user. - user.unEquip(I,1) - - user.visible_message("[user] bellows, [pick("shredding", "ripping open", "tearing off")] [user.p_their()] jacket in a fit of rage!","You accidentally [pick("shred", "rend", "tear apart")] [src] with your [pick("excessive", "extreme", "insane", "monstrous", "ridiculous", "unreal", "stupendous")] [pick("power", "strength")]!") - user.unEquip(src) - qdel(src) //Now that the pockets have been emptied, we can safely destroy the jacket. - user.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!")) - else - to_chat(user, "You yank and pull at \the [src] with your [pick("excessive", "extreme", "insane", "monstrous", "ridiculous", "unreal", "stupendous")] [pick("power", "strength")], however you are unable to change its state!")//Yep, that's all they get. Avoids having to snowflake in a cooldown. - - return - user.update_inv_wear_suit() - else - to_chat(user, "You attempt to button up the velcro on \the [src], before promptly realising how retarded you are.") - -/obj/item/clothing/suit/equipped(var/mob/living/carbon/human/user, var/slot) //Handle tail-hiding on a by-species basis. - ..() - if(ishuman(user) && hide_tail_by_species && slot == slot_wear_suit) - if(user.dna.species.name in hide_tail_by_species) - if(!(flags_inv & HIDETAIL)) //Hide the tail if the user's species is in the hide_tail_by_species list and the tail isn't already hidden. - flags_inv |= HIDETAIL - else - if(!(initial(flags_inv) & HIDETAIL) && (flags_inv & HIDETAIL)) //Otherwise, remove the HIDETAIL flag if it wasn't already in the flags_inv to start with. - flags_inv &= ~HIDETAIL - -/obj/item/clothing/suit/ui_action_click(mob/user) //This is what happens when you click the HUD action button to adjust your suit. - if(!ignore_suitadjust) - adjustsuit(user) - else - ..() //This is required in order to ensure that the UI buttons for items that have alternate functions tied to UI buttons still work. - -/obj/item/clothing/suit/proc/special_overlays() // Does it have special overlays when worn? - return FALSE - -//Spacesuit -//Note: Everything in modules/clothing/spacesuits should have the entire suit grouped together. -// Meaning the the suit is defined directly after the corrisponding helmet. Just like below! -/obj/item/clothing/head/helmet/space - name = "Space helmet" - icon_state = "space" - desc = "A special helmet designed for work in a hazardous, low-pressure environment." - flags = BLOCKHAIR | STOPSPRESSUREDMAGE | THICKMATERIAL - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - item_state = "s_helmet" - permeability_coefficient = 0.01 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - species_restricted = list("exclude","Wryn") - flash_protect = 2 - strip_delay = 50 - put_on_delay = 50 - resistance_flags = NONE - dog_fashion = null - - -/obj/item/clothing/suit/space - name = "Space suit" - desc = "A suit that protects against low pressure environments. Has a big 13 on the back." - icon_state = "space" - item_state = "s_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.02 - flags = STOPSPRESSUREDMAGE | THICKMATERIAL - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight,/obj/item/tank) - slowdown = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL - cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - heat_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS - max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT - strip_delay = 80 - put_on_delay = 80 - resistance_flags = NONE - hide_tail_by_species = null - species_restricted = list("exclude","Wryn") - - -//Under clothing -/obj/item/clothing/under - icon = 'icons/obj/clothing/uniforms.dmi' - name = "under" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - permeability_coefficient = 0.90 - slot_flags = SLOT_ICLOTHING - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/uniform.dmi', - "Drask" = 'icons/mob/species/drask/uniform.dmi', - "Grey" = 'icons/mob/species/grey/uniform.dmi' - ) - - var/has_sensor = TRUE//For the crew computer 2 = unable to change mode - var/sensor_mode = SENSOR_OFF - var/random_sensor = TRUE - /* - 1 = Report living/dead - 2 = Report detailed damages - 3 = Report location - */ - var/list/accessories = list() - var/displays_id = 1 - var/rolled_down = 0 - var/basecolor - -/obj/item/clothing/under/rank/New() - if(random_sensor) - sensor_mode = pick(SENSOR_OFF, SENSOR_LIVING, SENSOR_VITALS, SENSOR_COORDS) - ..() - -/obj/item/clothing/under/Destroy() - QDEL_LIST(accessories) - return ..() - -/obj/item/clothing/under/proc/can_attach_accessory(obj/item/clothing/accessory/A) - if(istype(A)) - . = TRUE - else - return FALSE - - if(accessories.len) - for(var/obj/item/clothing/accessory/AC in accessories) - if((A.slot in list(ACCESSORY_SLOT_UTILITY, ACCESSORY_SLOT_ARMBAND)) && AC.slot == A.slot) - return FALSE - if(!A.allow_duplicates && AC.type == A.type) - return FALSE - -/obj/item/clothing/under/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/clothing/accessory)) - attach_accessory(I, user, TRUE) - - if(accessories.len) - for(var/obj/item/clothing/accessory/A in accessories) - A.attackby(I, user, params) - return TRUE - - . = ..() - -/obj/item/clothing/under/proc/attach_accessory(obj/item/clothing/accessory/A, mob/user, unequip = FALSE) - if(can_attach_accessory(A)) - if(unequip && !user.unEquip(A)) // Make absolutely sure this accessory is removed from hands - return FALSE - - accessories += A - A.on_attached(src, user) - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_w_uniform() - - return TRUE - else - to_chat(user, "You cannot attach more accessories of this type to [src].") - - return FALSE - -/obj/item/clothing/under/examine(mob/user) - . = ..() - switch(sensor_mode) - if(0) - . += "Its sensors appear to be disabled." - if(1) - . += "Its binary life sensors appear to be enabled." - if(2) - . += "Its vital tracker appears to be enabled." - if(3) - . += "Its vital tracker and tracking beacon appear to be enabled." - if(accessories.len) - for(var/obj/item/clothing/accessory/A in accessories) - . += "\A [A] is attached to it." - - -/obj/item/clothing/under/verb/rollsuit() - set name = "Roll Down Jumpsuit" - set category = "Object" - set src in usr - if(!istype(usr, /mob/living)) return - if(usr.stat) return - - if(!usr.incapacitated()) - if(copytext(item_color,-2) != "_d") - basecolor = item_color - if(basecolor + "_d_s" in icon_states('icons/mob/uniform.dmi')) - item_color = item_color == "[basecolor]" ? "[basecolor]_d" : "[basecolor]" - usr.update_inv_w_uniform() - else - to_chat(usr, "You cannot roll down this uniform!") - else - to_chat(usr, "You cannot roll down the uniform!") - -/obj/item/clothing/under/verb/removetie() - set name = "Remove Accessory" - set category = "Object" - set src in usr - handle_accessories_removal() - -/obj/item/clothing/under/proc/handle_accessories_removal() - if(!isliving(usr)) - return - if(usr.incapacitated()) - return - if(!Adjacent(usr)) - return - if(!accessories.len) - return - var/obj/item/clothing/accessory/A - if(accessories.len > 1) - A = input("Select an accessory to remove from [src]") as null|anything in accessories - else - A = accessories[1] - remove_accessory(usr,A) - -/obj/item/clothing/under/proc/remove_accessory(mob/user, obj/item/clothing/accessory/A) - if(!(A in accessories)) - return - if(!isliving(user)) - return - if(user.incapacitated()) - return - if(!Adjacent(user)) - return - A.on_removed(user) - accessories -= A - to_chat(user, "You remove [A] from [src].") - usr.update_inv_w_uniform() - -/obj/item/clothing/under/emp_act(severity) - if(accessories.len) - for(var/obj/item/clothing/accessory/A in accessories) - A.emp_act(severity) - ..() - -/obj/item/clothing/under/AltClick() - handle_accessories_removal() - -/obj/item/clothing/obj_destruction(damage_flag) - if(damage_flag == "bomb" || damage_flag == "melee") - var/turf/T = get_turf(src) - spawn(1) //so the shred survives potential turf change from the explosion. - var/obj/effect/decal/cleanable/shreds/Shreds = new(T) - Shreds.desc = "The sad remains of what used to be [name]." - deconstruct(FALSE) - else - ..() \ No newline at end of file +/obj/item/clothing + name = "clothing" + max_integrity = 200 + integrity_failure = 80 + resistance_flags = FLAMMABLE + var/list/species_restricted = null //Only these species can wear this kit. + var/scan_reagents = 0 //Can the wearer see reagents while it's equipped? + + /* + Sprites used when the clothing item is refit. This is done by setting icon_override. + For best results, if this is set then sprite_sheets should be null and vice versa, but that is by no means necessary. + Ideally, sprite_sheets_refit should be used for "hard" clothing items that can't change shape very well to fit the wearer (e.g. helmets, hardsuits), + while sprite_sheets should be used for "flexible" clothing items that do not need to be refitted (e.g. vox wearing jumpsuits). + */ + var/list/sprite_sheets_refit = null + lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/mob/inhands/clothing_righthand.dmi' + var/alt_desc = null + var/flash_protect = 0 //What level of bright light protection item has. 1 = Flashers, Flashes, & Flashbangs | 2 = Welding | -1 = OH GOD WELDING BURNT OUT MY RETINAS + var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect + var/up = 0 //but seperated to allow items to protect but not impair vision, like space helmets + + var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down + var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv + var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW //what to toggle when toggled with weldingvisortoggle() + + var/toggle_message = null + var/alt_toggle_message = null + var/active_sound = null + var/toggle_sound = null + var/toggle_cooldown = null + var/cooldown = 0 + var/species_disguise = null + var/magical = FALSE + +/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc. + if(!can_use(user)) + return FALSE + + visor_toggling() + + to_chat(user, "You adjust \the [src] [up ? "up" : "down"].") + + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.head_update(src, forced = 1) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + return TRUE + +/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags + up = !up + flags ^= visor_flags + flags_inv ^= visor_flags_inv + flags_cover ^= initial(flags_cover) + icon_state = "[initial(icon_state)][up ? "up" : ""]" + if(visor_vars_to_toggle & VISOR_FLASHPROTECT) + flash_protect ^= initial(flash_protect) + if(visor_vars_to_toggle & VISOR_TINT) + tint ^= initial(tint) + +/obj/item/clothing/proc/can_use(mob/user) + if(user && ismob(user)) + if(!user.incapacitated()) + return TRUE + return FALSE + +//BS12: Species-restricted clothing check. +/obj/item/clothing/mob_can_equip(M as mob, slot) + + //if we can't equip the item anyway, don't bother with species_restricted (also cuts down on spam) + if(!..()) + return 0 + + // Skip species restriction checks on non-equipment slots + if(slot in list(slot_r_hand, slot_l_hand, slot_in_backpack, slot_l_store, slot_r_store)) + return 1 + + if(species_restricted && istype(M,/mob/living/carbon/human)) + + var/wearable = null + var/exclusive = null + var/mob/living/carbon/human/H = M + + if("exclude" in species_restricted) + exclusive = 1 + + if(H.dna.species) + if(exclusive) + if(!(H.dna.species.name in species_restricted)) + wearable = 1 + else + if(H.dna.species.name in species_restricted) + wearable = 1 + + if(!wearable) + to_chat(M, "Your species cannot wear [src].") + return 0 + + return 1 + +/obj/item/clothing/proc/refit_for_species(var/target_species) + //Set species_restricted list + switch(target_species) + if("Human", "Skrell") //humanoid bodytypes + species_restricted = list("exclude","Unathi","Tajaran","Diona","Vox","Wryn","Drask") + else + species_restricted = list(target_species) + + //Set icon + if(sprite_sheets && (target_species in sprite_sheets)) + icon_override = sprite_sheets[target_species] + else + icon_override = initial(icon_override) + + if(sprite_sheets_obj && (target_species in sprite_sheets_obj)) + icon = sprite_sheets_obj[target_species] + else + icon = initial(icon) + +//Ears: currently only used for headsets and earmuffs +/obj/item/clothing/ears + name = "ears" + w_class = WEIGHT_CLASS_TINY + throwforce = 2 + slot_flags = SLOT_EARS + resistance_flags = NONE + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/ears.dmi', + "Vox Armalis" = 'icons/mob/species/armalis/ears.dmi' + ) //We read you loud and skree-er. + +/obj/item/clothing/ears/attack_hand(mob/user) + if(!user) + return + + if(loc != user || !ishuman(user)) + ..() + return + + var/mob/living/carbon/human/H = user + if(H.l_ear != src && H.r_ear != src) + ..() + return + + if(!usr.canUnEquip(src)) + return + + var/obj/item/clothing/ears/O + if(slot_flags & SLOT_TWOEARS ) + O = (H.l_ear == src ? H.r_ear : H.l_ear) + user.unEquip(O) + if(!istype(src, /obj/item/clothing/ears/offear)) + qdel(O) + O = src + else + O = src + + user.unEquip(src) + + if(O) + user.put_in_hands(O) + O.add_fingerprint(user) + + if(istype(src, /obj/item/clothing/ears/offear)) + qdel(src) + + +/obj/item/clothing/ears/offear + name = "Other ear" + w_class = WEIGHT_CLASS_HUGE + icon = 'icons/mob/screen_gen.dmi' + icon_state = "block" + slot_flags = SLOT_EARS | SLOT_TWOEARS + +/obj/item/clothing/ears/offear/New(var/obj/O) + name = O.name + desc = O.desc + icon = O.icon + icon_state = O.icon_state + dir = O.dir + + +//Glasses +/obj/item/clothing/glasses + name = "glasses" + icon = 'icons/obj/clothing/glasses.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_cover = GLASSESCOVERSEYES + slot_flags = SLOT_EYES + materials = list(MAT_GLASS = 250) + var/vision_flags = 0 + var/see_in_dark = 0 //Base human is 2 + var/invis_view = SEE_INVISIBLE_LIVING + var/invis_override = 0 + var/lighting_alpha + + var/emagged = 0 + var/list/color_view = null//overrides client.color while worn + var/prescription = 0 + var/prescription_upgradable = 0 + var/over_mask = FALSE //Whether or not the eyewear is rendered above the mask. Purely cosmetic. + strip_delay = 20 // but seperated to allow items to protect but not impair vision, like space helmets + put_on_delay = 25 + resistance_flags = NONE + species_restricted = list("exclude","Kidan") +/* +SEE_SELF // can see self, no matter what +SEE_MOBS // can see all mobs, no matter what +SEE_OBJS // can see all objs, no matter what +SEE_TURFS // can see all turfs (and areas), no matter what +SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are + // in a lit area (via pixel_x,y or smooth movement), can see those pixels +BLIND // can't see anything +*/ + +/obj/item/clothing/glasses/verb/adjust_eyewear() //Adjust eyewear to be worn above or below the mask. + set name = "Adjust Eyewear" + set category = "Object" + set desc = "Adjust your eyewear to be worn over or under a mask." + set src in usr + + var/mob/living/carbon/human/user = usr + if(!istype(user)) + return + if(user.incapacitated()) //Dead spessmen adjust no glasses. Resting/buckled ones do, though + return + + var/action_fluff = "You adjust \the [src]" + if(user.glasses == src) + if(!user.canUnEquip(src)) + to_chat(usr, "[src] is stuck to you!") + return + if(attack_hand(user)) //Remove the glasses for this action. Prevents logic-defying instances where glasses phase through your mask as it ascends/descends to another plane of existence. + action_fluff = "You remove \the [src] and adjust it" + + over_mask = !over_mask + to_chat(user, "[action_fluff] to be worn [over_mask ? "over" : "under"] a mask.") + +//Gloves +/obj/item/clothing/gloves + name = "gloves" + gender = PLURAL //Carn: for grammarically correct text-parsing + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/clothing/gloves.dmi' + siemens_coefficient = 0.50 + body_parts_covered = HANDS + slot_flags = SLOT_GLOVES + attack_verb = list("challenged") + var/transfer_prints = FALSE + var/pickpocket = 0 //Master pickpocket? + var/clipped = 0 + strip_delay = 20 + put_on_delay = 40 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/gloves.dmi', + "Drask" = 'icons/mob/species/drask/gloves.dmi' + ) + +// Called just before an attack_hand(), in mob/UnarmedAttack() +/obj/item/clothing/gloves/proc/Touch(atom/A, proximity) + return 0 // return 1 to cancel attack_hand() + +/obj/item/clothing/gloves/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/wirecutters)) + if(!clipped) + playsound(src.loc, W.usesound, 100, 1) + user.visible_message("[user] snips the fingertips off [src].","You snip the fingertips off [src].") + clipped = 1 + name = "mangled [name]" + desc = "[desc] They have had the fingertips cut off of them." + update_icon() + else + to_chat(user, "[src] have already been clipped!") + return + else + return ..() + +/obj/item/clothing/under/proc/set_sensors(mob/user as mob) + var/mob/M = user + if(istype(M, /mob/dead/)) return + if(user.stat || user.restrained()) return + if(has_sensor >= 2) + to_chat(user, "The controls are locked.") + return 0 + if(has_sensor <= 0) + to_chat(user, "This suit does not have any sensors.") + return 0 + + var/list/modes = list("Off", "Binary sensors", "Vitals tracker", "Tracking beacon") + var/switchMode = input("Select a sensor mode:", "Suit Sensor Mode", modes[sensor_mode + 1]) in modes + if(get_dist(user, src) > 1) + to_chat(user, "You have moved too far away.") + return + sensor_mode = modes.Find(switchMode) - 1 + + if(src.loc == user) + switch(sensor_mode) + if(0) + to_chat(user, "You disable your suit's remote sensing equipment.") + if(1) + to_chat(user, "Your suit will now report whether you are live or dead.") + if(2) + to_chat(user, "Your suit will now report your vital lifesigns.") + if(3) + to_chat(user, "Your suit will now report your vital lifesigns as well as your coordinate position.") + if(istype(user,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + if(H.w_uniform == src) + H.update_suit_sensors() + + else if(istype(src.loc, /mob)) + switch(sensor_mode) + if(0) + for(var/mob/V in viewers(user, 1)) + V.show_message("[user] disables [src.loc]'s remote sensing equipment.", 1) + if(1) + for(var/mob/V in viewers(user, 1)) + V.show_message("[user] turns [src.loc]'s remote sensors to binary.", 1) + if(2) + for(var/mob/V in viewers(user, 1)) + V.show_message("[user] sets [src.loc]'s sensors to track vitals.", 1) + if(3) + for(var/mob/V in viewers(user, 1)) + V.show_message("[user] sets [src.loc]'s sensors to maximum.", 1) + if(istype(src,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = src + if(H.w_uniform == src) + H.update_suit_sensors() + +/obj/item/clothing/under/verb/toggle() + set name = "Toggle Suit Sensors" + set category = "Object" + set src in usr + set_sensors(usr) + ..() + +//Head +/obj/item/clothing/head + name = "head" + icon = 'icons/obj/clothing/hats.dmi' + body_parts_covered = HEAD + slot_flags = SLOT_HEAD + var/blockTracking // Do we block AI tracking? + var/HUDType = null + + var/vision_flags = 0 + var/see_in_dark = 0 + var/lighting_alpha + + var/can_toggle = null + +//Mask +/obj/item/clothing/mask + name = "mask" + icon = 'icons/obj/clothing/masks.dmi' + body_parts_covered = HEAD + slot_flags = SLOT_MASK + var/mask_adjusted = 0 + var/adjusted_flags = null + strip_delay = 40 + put_on_delay = 40 + +//Proc that moves gas/breath masks out of the way +/obj/item/clothing/mask/proc/adjustmask(var/mob/user) + var/mob/living/carbon/human/H = usr //Used to check if the mask is on the head, to check if the hands are full, and to turn off internals if they were on when the mask was pushed out of the way. + if(user.incapacitated()) //This check allows you to adjust your masks while you're buckled into chairs or beds. + return + if(mask_adjusted) + icon_state = initial(icon_state) + gas_transfer_coefficient = initial(gas_transfer_coefficient) + permeability_coefficient = initial(permeability_coefficient) + to_chat(user, "You push \the [src] back into place.") + mask_adjusted = 0 + slot_flags = initial(slot_flags) + if(flags_inv != initial(flags_inv)) + if(initial(flags_inv) & HIDEFACE) //If the mask is one that hides the face and can be adjusted yet lost that trait when it was adjusted, make it hide the face again. + flags_inv |= HIDEFACE + if(flags != initial(flags)) + if(initial(flags) & AIRTIGHT) //If the mask is airtight and thus, one that you'd be able to run internals from yet can't because it was adjusted, make it airtight again. + flags |= AIRTIGHT + if(flags_cover != initial(flags_cover)) + if(initial(flags_cover) & MASKCOVERSMOUTH) //If the mask covers the mouth when it's down and can be adjusted yet lost that trait when it was adjusted, make it cover the mouth again. + flags_cover |= MASKCOVERSMOUTH + if(H.head == src && flags_inv == HIDEFACE) //Means that only things like bandanas and balaclavas will be affected since they obscure the identity of the wearer. + if(H.l_hand && H.r_hand) //If both hands are occupied, drop the object on the ground. + user.unEquip(src) + else //Otherwise, put it in an available hand, the active one preferentially. + src.loc = user + H.head = null + user.put_in_hands(src) + else + icon_state += "_up" + to_chat(user, "You push \the [src] out of the way.") + gas_transfer_coefficient = null + permeability_coefficient = null + mask_adjusted = 1 + if(adjusted_flags) + slot_flags = adjusted_flags + if(ishuman(user) && H.internal && !H.get_organ_slot("breathing_tube") && user.wear_mask == src) /*If the user was wearing the mask providing internals on their face at the time it was adjusted, turn off internals. + Otherwise, they adjusted it while it was in their hands or some such so we won't be needing to turn off internals.*/ + H.internal = null + H.update_action_buttons_icon() + if(flags_inv & HIDEFACE) //Means that only things like bandanas and balaclavas will be affected since they obscure the identity of the wearer. + flags_inv &= ~HIDEFACE /*Done after the above to avoid having to do a check for initial(src.flags_inv == HIDEFACE). + This reveals the user's face since the bandana will now be going on their head.*/ + if(flags_cover & MASKCOVERSMOUTH) //Mask won't cover the mouth any more since it's been pushed out of the way. Allows for CPRing with adjusted masks. + flags_cover &= ~MASKCOVERSMOUTH + if(flags & AIRTIGHT) //If the mask was airtight, it won't be anymore since you just pushed it off your face. + flags &= ~AIRTIGHT + if(user.wear_mask == src && initial(flags_inv) == HIDEFACE) //Means that you won't have to take off and put back on simple things like breath masks which, realistically, can just be pulled down off your face. + if(H.l_hand && H.r_hand) //If both hands are occupied, drop the object on the ground. + user.unEquip(src) + else //Otherwise, put it in an available hand, the active one preferentially. + src.loc = user + user.wear_mask = null + user.put_in_hands(src) + H.wear_mask_update(src, toggle_off = mask_adjusted) + usr.update_inv_wear_mask() + usr.update_inv_head() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +//Shoes +/obj/item/clothing/shoes + name = "shoes" + icon = 'icons/obj/clothing/shoes.dmi' + desc = "Comfortable-looking shoes." + gender = PLURAL //Carn: for grammatically correct text-parsing + var/chained = 0 + var/can_cut_open = 0 + var/cut_open = 0 + body_parts_covered = FEET + slot_flags = SLOT_FEET + + var/silence_steps = 0 + var/shoe_sound_footstep = 1 + var/shoe_sound = null + var/blood_state = BLOOD_STATE_NOT_BLOODY + var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_NOT_BLOODY = 0) + + permeability_coefficient = 0.50 + slowdown = SHOES_SLOWDOWN + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/shoes.dmi', + "Drask" = 'icons/mob/species/drask/shoes.dmi' + ) + +/obj/item/clothing/shoes/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/match) && src.loc == user) + var/obj/item/match/M = I + if(M.matchignite()) // Match isn't lit, but isn't burnt. + user.visible_message("[user] strikes a [M] on the bottom of [src], lighting it.","You strike the [M] on the bottom of [src] to light it.") + playsound(user.loc, 'sound/goonstation/misc/matchstick_light.ogg', 50, 1) + else + user.visible_message("[user] crushes the [M] into the bottom of [src], extinguishing it.","You crush the [M] into the bottom of [src], extinguishing it.") + M.dropped() + return + + if(istype(I, /obj/item/wirecutters)) + if(can_cut_open) + if(!cut_open) + playsound(src.loc, I.usesound, 100, 1) + user.visible_message("[user] cuts open the toes of [src].","You cut open the toes of [src].") + cut_open = 1 + icon_state = "[icon_state]_opentoe" + item_state = "[item_state]_opentoe" + name = "mangled [name]" + desc = "[desc] They have had their toes opened up." + update_icon() + else + to_chat(user, "[src] have already had [p_their()] toes cut open!") + return + else + return ..() + +/obj/item/clothing/shoes/proc/step_action(var/mob/living/carbon/human/H) //squeek squeek + SEND_SIGNAL(src, COMSIG_SHOES_STEP_ACTION) + if(shoe_sound) + var/turf/T = get_turf(H) + + if(!istype(H) || !istype(T)) + return 0 + + if(H.m_intent == MOVE_INTENT_RUN) + if(shoe_sound_footstep >= 2) + if(T.shoe_running_volume) + playsound(src, shoe_sound, T.shoe_running_volume, 1) + shoe_sound_footstep = 0 + else + shoe_sound_footstep++ + else if(T.shoe_walking_volume) + playsound(src, shoe_sound, T.shoe_walking_volume, 1) + + return 1 + +/obj/item/proc/negates_gravity() + return 0 + +//Suit +/obj/item/clothing/suit + icon = 'icons/obj/clothing/suits.dmi' + name = "suit" + var/fire_resist = T0C+100 + allowed = list(/obj/item/tank/emergency_oxygen) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + slot_flags = SLOT_OCLOTHING + var/blood_overlay_type = "suit" + var/suittoggled = FALSE + var/suit_adjusted = 0 + var/ignore_suitadjust = 1 + var/adjust_flavour = null + var/list/hide_tail_by_species = null + +//Proc that opens and closes jackets. +/obj/item/clothing/suit/proc/adjustsuit(var/mob/user) + if(!ignore_suitadjust) + if(!user.incapacitated()) + if(!(HULK in user.mutations)) + if(suit_adjusted) + var/flavour = "close" + icon_state = copytext(icon_state, 1, findtext(icon_state, "_open")) /*Trims the '_open' off the end of the icon state, thus avoiding a case where jackets that start open will + end up with a suffix of _open_open if adjusted twice, since their initial state is _open. */ + item_state = copytext(item_state, 1, findtext(item_state, "_open")) + if(adjust_flavour) + flavour = "[copytext(adjust_flavour, 3, length(adjust_flavour) + 1)] up" //Trims off the 'un' at the beginning of the word. unzip -> zip, unbutton->button. + to_chat(user, "You [flavour] \the [src].") + suit_adjusted = 0 //Suit is no longer adjusted. + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + else + var/flavour = "open" + icon_state += "_open" + item_state += "_open" + if(adjust_flavour) + flavour = "[adjust_flavour]" + to_chat(user, "You [flavour] \the [src].") + suit_adjusted = 1 //Suit's adjusted. + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + else + if(user.canUnEquip(src)) //Checks to see if the item can be unequipped. If so, lets shred. Otherwise, struggle and fail. + if(contents) //If the suit's got any storage capability... + for(var/obj/item/O in contents) //AVOIDING ITEM LOSS. Check through everything that's stored in the jacket and see if one of the items is a pocket. + if(istype(O, /obj/item/storage/internal)) //If it's a pocket... + if(O.contents) //Check to see if the pocket's got anything in it. + for(var/obj/item/I in O.contents) //Dump the pocket out onto the floor below the user. + user.unEquip(I,1) + + user.visible_message("[user] bellows, [pick("shredding", "ripping open", "tearing off")] [user.p_their()] jacket in a fit of rage!","You accidentally [pick("shred", "rend", "tear apart")] [src] with your [pick("excessive", "extreme", "insane", "monstrous", "ridiculous", "unreal", "stupendous")] [pick("power", "strength")]!") + user.unEquip(src) + qdel(src) //Now that the pockets have been emptied, we can safely destroy the jacket. + user.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!")) + else + to_chat(user, "You yank and pull at \the [src] with your [pick("excessive", "extreme", "insane", "monstrous", "ridiculous", "unreal", "stupendous")] [pick("power", "strength")], however you are unable to change its state!")//Yep, that's all they get. Avoids having to snowflake in a cooldown. + + return + user.update_inv_wear_suit() + else + to_chat(user, "You attempt to button up the velcro on \the [src], before promptly realising how retarded you are.") + +/obj/item/clothing/suit/equipped(var/mob/living/carbon/human/user, var/slot) //Handle tail-hiding on a by-species basis. + ..() + if(ishuman(user) && hide_tail_by_species && slot == slot_wear_suit) + if(user.dna.species.name in hide_tail_by_species) + if(!(flags_inv & HIDETAIL)) //Hide the tail if the user's species is in the hide_tail_by_species list and the tail isn't already hidden. + flags_inv |= HIDETAIL + else + if(!(initial(flags_inv) & HIDETAIL) && (flags_inv & HIDETAIL)) //Otherwise, remove the HIDETAIL flag if it wasn't already in the flags_inv to start with. + flags_inv &= ~HIDETAIL + +/obj/item/clothing/suit/ui_action_click(mob/user) //This is what happens when you click the HUD action button to adjust your suit. + if(!ignore_suitadjust) + adjustsuit(user) + else + ..() //This is required in order to ensure that the UI buttons for items that have alternate functions tied to UI buttons still work. + +/obj/item/clothing/suit/proc/special_overlays() // Does it have special overlays when worn? + return FALSE + +//Spacesuit +//Note: Everything in modules/clothing/spacesuits should have the entire suit grouped together. +// Meaning the the suit is defined directly after the corrisponding helmet. Just like below! +/obj/item/clothing/head/helmet/space + name = "Space helmet" + icon_state = "space" + desc = "A special helmet designed for work in a hazardous, low-pressure environment." + flags = BLOCKHAIR | STOPSPRESSUREDMAGE | THICKMATERIAL + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + item_state = "s_helmet" + permeability_coefficient = 0.01 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + species_restricted = list("exclude","Wryn") + flash_protect = 2 + strip_delay = 50 + put_on_delay = 50 + resistance_flags = NONE + dog_fashion = null + + +/obj/item/clothing/suit/space + name = "Space suit" + desc = "A suit that protects against low pressure environments. Has a big 13 on the back." + icon_state = "space" + item_state = "s_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.02 + flags = STOPSPRESSUREDMAGE | THICKMATERIAL + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight,/obj/item/tank) + slowdown = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL + cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + heat_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + strip_delay = 80 + put_on_delay = 80 + resistance_flags = NONE + hide_tail_by_species = null + species_restricted = list("exclude","Wryn") + + +//Under clothing +/obj/item/clothing/under + icon = 'icons/obj/clothing/uniforms.dmi' + name = "under" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + permeability_coefficient = 0.90 + slot_flags = SLOT_ICLOTHING + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/uniform.dmi', + "Drask" = 'icons/mob/species/drask/uniform.dmi', + "Grey" = 'icons/mob/species/grey/uniform.dmi' + ) + + var/has_sensor = TRUE//For the crew computer 2 = unable to change mode + var/sensor_mode = SENSOR_OFF + var/random_sensor = TRUE + /* + 1 = Report living/dead + 2 = Report detailed damages + 3 = Report location + */ + var/list/accessories = list() + var/displays_id = 1 + var/rolled_down = 0 + var/basecolor + +/obj/item/clothing/under/rank/New() + if(random_sensor) + sensor_mode = pick(SENSOR_OFF, SENSOR_LIVING, SENSOR_VITALS, SENSOR_COORDS) + ..() + +/obj/item/clothing/under/Destroy() + QDEL_LIST(accessories) + return ..() + +/obj/item/clothing/under/proc/can_attach_accessory(obj/item/clothing/accessory/A) + if(istype(A)) + . = TRUE + else + return FALSE + + if(accessories.len) + for(var/obj/item/clothing/accessory/AC in accessories) + if((A.slot in list(ACCESSORY_SLOT_UTILITY, ACCESSORY_SLOT_ARMBAND)) && AC.slot == A.slot) + return FALSE + if(!A.allow_duplicates && AC.type == A.type) + return FALSE + +/obj/item/clothing/under/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/clothing/accessory)) + attach_accessory(I, user, TRUE) + + if(accessories.len) + for(var/obj/item/clothing/accessory/A in accessories) + A.attackby(I, user, params) + return TRUE + + . = ..() + +/obj/item/clothing/under/proc/attach_accessory(obj/item/clothing/accessory/A, mob/user, unequip = FALSE) + if(can_attach_accessory(A)) + if(unequip && !user.unEquip(A)) // Make absolutely sure this accessory is removed from hands + return FALSE + + accessories += A + A.on_attached(src, user) + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_w_uniform() + + return TRUE + else + to_chat(user, "You cannot attach more accessories of this type to [src].") + + return FALSE + +/obj/item/clothing/under/examine(mob/user) + . = ..() + switch(sensor_mode) + if(0) + . += "Its sensors appear to be disabled." + if(1) + . += "Its binary life sensors appear to be enabled." + if(2) + . += "Its vital tracker appears to be enabled." + if(3) + . += "Its vital tracker and tracking beacon appear to be enabled." + if(accessories.len) + for(var/obj/item/clothing/accessory/A in accessories) + . += "\A [A] is attached to it." + + +/obj/item/clothing/under/verb/rollsuit() + set name = "Roll Down Jumpsuit" + set category = "Object" + set src in usr + if(!istype(usr, /mob/living)) return + if(usr.stat) return + + if(!usr.incapacitated()) + if(copytext(item_color,-2) != "_d") + basecolor = item_color + if(basecolor + "_d_s" in icon_states('icons/mob/uniform.dmi')) + item_color = item_color == "[basecolor]" ? "[basecolor]_d" : "[basecolor]" + usr.update_inv_w_uniform() + else + to_chat(usr, "You cannot roll down this uniform!") + else + to_chat(usr, "You cannot roll down the uniform!") + +/obj/item/clothing/under/verb/removetie() + set name = "Remove Accessory" + set category = "Object" + set src in usr + handle_accessories_removal() + +/obj/item/clothing/under/proc/handle_accessories_removal() + if(!isliving(usr)) + return + if(usr.incapacitated()) + return + if(!Adjacent(usr)) + return + if(!accessories.len) + return + var/obj/item/clothing/accessory/A + if(accessories.len > 1) + A = input("Select an accessory to remove from [src]") as null|anything in accessories + else + A = accessories[1] + remove_accessory(usr,A) + +/obj/item/clothing/under/proc/remove_accessory(mob/user, obj/item/clothing/accessory/A) + if(!(A in accessories)) + return + if(!isliving(user)) + return + if(user.incapacitated()) + return + if(!Adjacent(user)) + return + A.on_removed(user) + accessories -= A + to_chat(user, "You remove [A] from [src].") + usr.update_inv_w_uniform() + +/obj/item/clothing/under/emp_act(severity) + if(accessories.len) + for(var/obj/item/clothing/accessory/A in accessories) + A.emp_act(severity) + ..() + +/obj/item/clothing/under/AltClick() + handle_accessories_removal() + +/obj/item/clothing/obj_destruction(damage_flag) + if(damage_flag == "bomb" || damage_flag == "melee") + var/turf/T = get_turf(src) + spawn(1) //so the shred survives potential turf change from the explosion. + var/obj/effect/decal/cleanable/shreds/Shreds = new(T) + Shreds.desc = "The sad remains of what used to be [name]." + deconstruct(FALSE) + else + ..() diff --git a/code/modules/clothing/ears/ears.dm b/code/modules/clothing/ears/ears.dm index 2ba883a6f89..ea8f4198f50 100644 --- a/code/modules/clothing/ears/ears.dm +++ b/code/modules/clothing/ears/ears.dm @@ -25,4 +25,4 @@ var/datum/action/A = X A.UpdateButtonIcon() - user.update_inv_ears() \ No newline at end of file + user.update_inv_ears() diff --git a/code/modules/clothing/glasses/glasses.dm b/code/modules/clothing/glasses/glasses.dm index e80f461246e..1b9c87f56e5 100644 --- a/code/modules/clothing/glasses/glasses.dm +++ b/code/modules/clothing/glasses/glasses.dm @@ -1,525 +1,525 @@ -/obj/item/clothing/glasses/New() - . = ..() - if(prescription_upgradable && prescription) - // Pre-upgraded upgradable glasses - name = "prescription [name]" - -/obj/item/clothing/glasses/attackby(var/obj/item/O as obj, var/mob/user as mob) - if(user.stat || user.restrained() || !ishuman(user)) - return ..() - var/mob/living/carbon/human/H = user - if(prescription_upgradable) - if(istype(O, /obj/item/clothing/glasses/regular)) - if(prescription) - to_chat(H, "You can't possibly imagine how adding more lenses would improve \the [name].") - return - H.unEquip(O) - O.loc = src // Store the glasses for later removal - to_chat(H, "You fit \the [name] with lenses from \the [O].") - prescription = 1 - name = "prescription [name]" - return - if(prescription && istype(O, /obj/item/screwdriver)) - var/obj/item/clothing/glasses/regular/G = locate() in src - if(!G) - G = new(get_turf(H)) - to_chat(H, "You salvage the prescription lenses from \the [name].") - prescription = 0 - name = initial(name) - H.put_in_hands(G) - return - return ..() - -/obj/item/clothing/glasses/visor_toggling() - ..() - if(visor_vars_to_toggle & VISOR_VISIONFLAGS) - vision_flags ^= initial(vision_flags) - if(visor_vars_to_toggle & VISOR_DARKNESSVIEW) - see_in_dark ^= initial(see_in_dark) - if(visor_vars_to_toggle & VISOR_INVISVIEW) - invis_view ^= initial(invis_view) - -/obj/item/clothing/glasses/weldingvisortoggle(mob/user) - . = ..() - if(. && user) - user.update_sight() - user.update_inv_glasses() - -/obj/item/clothing/glasses/meson - name = "Optical Meson Scanner" - desc = "Used for seeing walls, floors, and stuff through anything." - icon_state = "meson" - item_state = "glasses" - origin_tech = "magnets=1;engineering=2" - vision_flags = SEE_TURFS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - prescription_upgradable = 1 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - -/obj/item/clothing/glasses/meson/night - name = "Night Vision Optical Meson Scanner" - desc = "An Optical Meson Scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness." - icon_state = "nvgmeson" - item_state = "glasses" - origin_tech = "magnets=4;engineering=5;plasmatech=4" - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - prescription_upgradable = 0 - -/obj/item/clothing/glasses/meson/prescription - prescription = 1 - -/obj/item/clothing/glasses/meson/gar - name = "gar mesons" - icon_state = "garm" - item_state = "garm" - desc = "Do the impossible, see the invisible!" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharp = 1 - -/obj/item/clothing/glasses/meson/cyber - name = "Eye Replacement Implant" - desc = "An implanted replacement for a left eye with meson vision capabilities." - icon_state = "cybereye-green" - item_state = "eyepatch" - flags = NODROP - flags_cover = null - prescription_upgradable = 0 - -/obj/item/clothing/glasses/science - name = "science goggles" - desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents." - icon_state = "purple" - item_state = "glasses" - origin_tech = "magnets=2;engineering=1" - prescription_upgradable = 0 - scan_reagents = 1 //You can see reagents while wearing science goggles - resistance_flags = ACID_PROOF - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - actions_types = list(/datum/action/item_action/toggle_research_scanner) - -/obj/item/clothing/glasses/science/item_action_slot_check(slot) - if(slot == slot_glasses) - return 1 - -/obj/item/clothing/glasses/science/night - name = "Night Vision Science Goggle" - desc = "Now you can science in darkness." - icon_state = "nvpurple" - item_state = "glasses" - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these - -/obj/item/clothing/glasses/janitor - name = "Janitorial Goggles" - desc = "These'll keep the soap out of your eyes." - icon_state = "purple" - item_state = "glasses" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi' - ) - -/obj/item/clothing/glasses/night - name = "Night Vision Goggles" - desc = "You can totally see in the dark now!" - icon_state = "night" - item_state = "glasses" - origin_tech = "materials=4;magnets=4;plasmatech=4;engineering=4" - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/eyepatch - name = "eyepatch" - desc = "Yarr." - icon_state = "eyepatch" - item_state = "eyepatch" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - -/obj/item/clothing/glasses/monocle - name = "monocle" - desc = "Such a dapper eyepiece!" - icon_state = "monocle" - item_state = "headset" // lol - prescription_upgradable = 1 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/material - name = "Optical Material Scanner" - desc = "Very confusing glasses." - icon_state = "material" - item_state = "glasses" - origin_tech = "magnets=3;engineering=3" - vision_flags = SEE_OBJS - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/material/cyber - name = "Eye Replacement Implant" - desc = "An implanted replacement for a left eye with material vision capabilities." - icon_state = "cybereye-blue" - item_state = "eyepatch" - flags = NODROP - flags_cover = null - -/obj/item/clothing/glasses/material/lighting - name = "Neutron Goggles" - desc = "These odd glasses use a form of neutron-based imaging to completely negate the effects of light and darkness." - origin_tech = null - vision_flags = 0 - - flags = NODROP - lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE - -/obj/item/clothing/glasses/regular - name = "prescription glasses" - desc = "Made by Nerd. Co." - icon_state = "glasses" - item_state = "glasses" - prescription = 1 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - -/obj/item/clothing/glasses/regular/hipster - name = "prescription glasses" - desc = "Made by Uncool. Co." - icon_state = "hipster_glasses" - item_state = "hipster_glasses" - -/obj/item/clothing/glasses/threedglasses - desc = "A long time ago, people used these glasses to makes images from screens threedimensional." - name = "3D glasses" - icon_state = "3d" - item_state = "3d" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - -/obj/item/clothing/glasses/gglasses - name = "Green Glasses" - desc = "Forest green glasses, like the kind you'd wear when hatching a nasty scheme." - icon_state = "gglasses" - item_state = "gglasses" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - prescription_upgradable = 1 - -/obj/item/clothing/glasses/sunglasses - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks many flashes." - name = "sunglasses" - icon_state = "sun" - item_state = "sunglasses" - see_in_dark = 1 - flash_protect = 1 - tint = 1 - prescription_upgradable = 1 - dog_fashion = /datum/dog_fashion/head - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/sunglasses_fake - desc = "Cheap, plastic sunglasses. They don't even have UV protection." - name = "cheap sunglasses" - icon_state = "sun" - item_state = "sunglasses" - see_in_dark = 0 - flash_protect = 0 - tint = 0 - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/sunglasses/noir - name = "noir sunglasses" - desc = "Somehow these seem even more out-of-date than normal sunglasses." - actions_types = list(/datum/action/item_action/noir) - -/obj/item/clothing/glasses/sunglasses/noir/attack_self(mob/user) - toggle_noir(user) - -/obj/item/clothing/glasses/sunglasses/noir/item_action_slot_check(slot) - if(slot == slot_glasses) - return 1 - -/obj/item/clothing/glasses/sunglasses/noir/proc/toggle_noir(mob/user) - color_view = color_view ? null : MATRIX_GREYSCALE //Toggles between null and grayscale, with null being the default option. - user.update_client_colour() - -/obj/item/clothing/glasses/sunglasses/yeah - name = "agreeable glasses" - desc = "H.C Limited edition." - var/punused = null - actions_types = list(/datum/action/item_action/YEEEAAAAAHHHHHHHHHHHHH) - -/obj/item/clothing/glasses/sunglasses/yeah/attack_self() - pun() - -/obj/item/clothing/glasses/sunglasses/yeah/proc/pun() - if(!punused)//one per round - punused = 1 - playsound(src.loc, 'sound/misc/yeah.ogg', 100, 0) - usr.visible_message("YEEEAAAAAHHHHHHHHHHHHH!!") - else - to_chat(usr, "The moment is gone.") - - -/obj/item/clothing/glasses/sunglasses/reagent - name = "sunscanners" - desc = "Strangely ancient technology used to help provide rudimentary eye color. Outfitted with apparatus to scan individual reagents." - scan_reagents = 1 - -/obj/item/clothing/glasses/virussunglasses - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks many flashes." - name = "sunglasses" - icon_state = "sun" - item_state = "sunglasses" - see_in_dark = 1 - flash_protect = 1 - tint = 1 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/sunglasses/lasers - desc = "A peculiar set of sunglasses; they have various chips and other panels attached to the sides of the frames." - name = "high-tech sunglasses" - flags = NODROP - -/obj/item/clothing/glasses/sunglasses/lasers/equipped(mob/user, slot) //grant them laser eyes upon equipping it. - if(slot == slot_glasses) - user.mutations.Add(LASER) - user.regenerate_icons() - ..(user, slot) - -/obj/item/clothing/glasses/welding - name = "welding goggles" - desc = "Protects the eyes from welders, approved by the mad scientist association." - icon_state = "welding-g" - item_state = "welding-g" - actions_types = list(/datum/action/item_action/toggle) - flash_protect = 2 - tint = 2 - visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/welding/attack_self(mob/user) - weldingvisortoggle(user) - -/obj/item/clothing/glasses/welding/superior - name = "superior welding goggles" - desc = "Welding goggles made from more expensive materials, strangely smells like potatoes." - icon_state = "rwelding-g" - item_state = "rwelding-g" - flash_protect = 2 - tint = 0 - -/obj/item/clothing/glasses/sunglasses/blindfold - name = "blindfold" - desc = "Covers the eyes, preventing sight." - icon_state = "blindfold" - item_state = "blindfold" - //vision_flags = BLIND - flash_protect = 2 - tint = 3 //to make them blind - prescription_upgradable = 0 - -/obj/item/clothing/glasses/sunglasses/prescription - prescription = 1 - -/obj/item/clothing/glasses/sunglasses/big - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks many flashes." - icon_state = "bigsunglasses" - item_state = "bigsunglasses" - -/obj/item/clothing/glasses/thermal - name = "Optical Thermal Scanner" - desc = "Thermals in the shape of glasses." - icon_state = "thermal" - item_state = "glasses" - origin_tech = "magnets=3" - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - flash_protect = -1 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - -/obj/item/clothing/glasses/thermal/emp_act(severity) - if(istype(src.loc, /mob/living/carbon/human)) - var/mob/living/carbon/human/M = src.loc - to_chat(M, "The Optical Thermal Scanner overloads and blinds you!") - if(M.glasses == src) - M.EyeBlind(3) - M.EyeBlurry(5) - if(!(M.disabilities & NEARSIGHTED)) - M.BecomeNearsighted() - spawn(100) - M.CureNearsighted() - ..() - -/obj/item/clothing/glasses/thermal/monocle - name = "Thermoncle" - desc = "A monocle thermal." - icon_state = "thermoncle" - flags_cover = null //doesn't protect eyes because it's a monocle, duh - -/obj/item/clothing/glasses/thermal/eyepatch - name = "Optical Thermal Eyepatch" - desc = "An eyepatch with built-in thermal optics" - icon_state = "eyepatch" - item_state = "eyepatch" - -/obj/item/clothing/glasses/thermal/jensen - name = "Optical Thermal Implants" - desc = "A set of implantable lenses designed to augment your vision" - icon_state = "thermalimplants" - item_state = "syringe_kit" - -/obj/item/clothing/glasses/thermal/cyber - name = "Eye Replacement Implant" - desc = "An implanted replacement for a left eye with thermal vision capabilities." - icon_state = "cybereye-red" - item_state = "eyepatch" - flags = NODROP - - -/obj/item/clothing/glasses/godeye - name = "eye of god" - desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes." - icon_state = "godeye" - item_state = "godeye" - vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS - see_in_dark = 8 - scan_reagents = 1 - flags = NODROP - flags_cover = null - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - resistance_flags = LAVA_PROOF | FIRE_PROOF - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - -/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, src) && W != src && W.loc == user) - if(W.icon_state == "godeye") - W.icon_state = "doublegodeye" - W.item_state = "doublegodeye" - W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you." - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.update_inv_wear_mask() - else - to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") - qdel(src) - ..() - -/obj/item/clothing/glasses/tajblind - name = "embroidered veil" - desc = "An Ahdominian made veil that allows the user to see while obscuring their eyes." - icon_state = "tajblind" - item_state = "tajblind" - flags_cover = GLASSESCOVERSEYES - actions_types = list(/datum/action/item_action/toggle) - up = 0 - tint = 0 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - -/obj/item/clothing/glasses/tajblind/eng - name = "industrial veil" - icon_state = "tajblind_engi" - item_state = "tajblind_engi" - -/obj/item/clothing/glasses/tajblind/sci - name = "hi-tech veil" - icon_state = "tajblind_sci" - item_state = "tajblind_sci" - -/obj/item/clothing/glasses/tajblind/cargo - name = "khaki veil" - icon_state = "tajblind_cargo" - item_state = "tajblind_cargo" - -/obj/item/clothing/glasses/tajblind/attack_self() - toggle_veil() - -/obj/item/clothing/glasses/proc/toggle_veil() - if(usr.canmove && !usr.incapacitated()) - if(up) - up = !up - tint = initial(tint) - to_chat(usr, "You activate [src], allowing you to see.") - else - up = !up - tint = 3 - to_chat(usr, "You deactivate [src], obscuring your vision.") - var/mob/living/carbon/user = usr - user.update_tint() - user.update_inv_glasses() \ No newline at end of file +/obj/item/clothing/glasses/New() + . = ..() + if(prescription_upgradable && prescription) + // Pre-upgraded upgradable glasses + name = "prescription [name]" + +/obj/item/clothing/glasses/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(user.stat || user.restrained() || !ishuman(user)) + return ..() + var/mob/living/carbon/human/H = user + if(prescription_upgradable) + if(istype(O, /obj/item/clothing/glasses/regular)) + if(prescription) + to_chat(H, "You can't possibly imagine how adding more lenses would improve \the [name].") + return + H.unEquip(O) + O.loc = src // Store the glasses for later removal + to_chat(H, "You fit \the [name] with lenses from \the [O].") + prescription = 1 + name = "prescription [name]" + return + if(prescription && istype(O, /obj/item/screwdriver)) + var/obj/item/clothing/glasses/regular/G = locate() in src + if(!G) + G = new(get_turf(H)) + to_chat(H, "You salvage the prescription lenses from \the [name].") + prescription = 0 + name = initial(name) + H.put_in_hands(G) + return + return ..() + +/obj/item/clothing/glasses/visor_toggling() + ..() + if(visor_vars_to_toggle & VISOR_VISIONFLAGS) + vision_flags ^= initial(vision_flags) + if(visor_vars_to_toggle & VISOR_DARKNESSVIEW) + see_in_dark ^= initial(see_in_dark) + if(visor_vars_to_toggle & VISOR_INVISVIEW) + invis_view ^= initial(invis_view) + +/obj/item/clothing/glasses/weldingvisortoggle(mob/user) + . = ..() + if(. && user) + user.update_sight() + user.update_inv_glasses() + +/obj/item/clothing/glasses/meson + name = "Optical Meson Scanner" + desc = "Used for seeing walls, floors, and stuff through anything." + icon_state = "meson" + item_state = "glasses" + origin_tech = "magnets=1;engineering=2" + vision_flags = SEE_TURFS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + prescription_upgradable = 1 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + +/obj/item/clothing/glasses/meson/night + name = "Night Vision Optical Meson Scanner" + desc = "An Optical Meson Scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness." + icon_state = "nvgmeson" + item_state = "glasses" + origin_tech = "magnets=4;engineering=5;plasmatech=4" + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + prescription_upgradable = 0 + +/obj/item/clothing/glasses/meson/prescription + prescription = 1 + +/obj/item/clothing/glasses/meson/gar + name = "gar mesons" + icon_state = "garm" + item_state = "garm" + desc = "Do the impossible, see the invisible!" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharp = 1 + +/obj/item/clothing/glasses/meson/cyber + name = "Eye Replacement Implant" + desc = "An implanted replacement for a left eye with meson vision capabilities." + icon_state = "cybereye-green" + item_state = "eyepatch" + flags = NODROP + flags_cover = null + prescription_upgradable = 0 + +/obj/item/clothing/glasses/science + name = "science goggles" + desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents." + icon_state = "purple" + item_state = "glasses" + origin_tech = "magnets=2;engineering=1" + prescription_upgradable = 0 + scan_reagents = 1 //You can see reagents while wearing science goggles + resistance_flags = ACID_PROOF + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + actions_types = list(/datum/action/item_action/toggle_research_scanner) + +/obj/item/clothing/glasses/science/item_action_slot_check(slot) + if(slot == slot_glasses) + return 1 + +/obj/item/clothing/glasses/science/night + name = "Night Vision Science Goggle" + desc = "Now you can science in darkness." + icon_state = "nvpurple" + item_state = "glasses" + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these + +/obj/item/clothing/glasses/janitor + name = "Janitorial Goggles" + desc = "These'll keep the soap out of your eyes." + icon_state = "purple" + item_state = "glasses" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi' + ) + +/obj/item/clothing/glasses/night + name = "Night Vision Goggles" + desc = "You can totally see in the dark now!" + icon_state = "night" + item_state = "glasses" + origin_tech = "materials=4;magnets=4;plasmatech=4;engineering=4" + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/eyepatch + name = "eyepatch" + desc = "Yarr." + icon_state = "eyepatch" + item_state = "eyepatch" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + +/obj/item/clothing/glasses/monocle + name = "monocle" + desc = "Such a dapper eyepiece!" + icon_state = "monocle" + item_state = "headset" // lol + prescription_upgradable = 1 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/material + name = "Optical Material Scanner" + desc = "Very confusing glasses." + icon_state = "material" + item_state = "glasses" + origin_tech = "magnets=3;engineering=3" + vision_flags = SEE_OBJS + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/material/cyber + name = "Eye Replacement Implant" + desc = "An implanted replacement for a left eye with material vision capabilities." + icon_state = "cybereye-blue" + item_state = "eyepatch" + flags = NODROP + flags_cover = null + +/obj/item/clothing/glasses/material/lighting + name = "Neutron Goggles" + desc = "These odd glasses use a form of neutron-based imaging to completely negate the effects of light and darkness." + origin_tech = null + vision_flags = 0 + + flags = NODROP + lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + +/obj/item/clothing/glasses/regular + name = "prescription glasses" + desc = "Made by Nerd. Co." + icon_state = "glasses" + item_state = "glasses" + prescription = 1 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + +/obj/item/clothing/glasses/regular/hipster + name = "prescription glasses" + desc = "Made by Uncool. Co." + icon_state = "hipster_glasses" + item_state = "hipster_glasses" + +/obj/item/clothing/glasses/threedglasses + desc = "A long time ago, people used these glasses to makes images from screens threedimensional." + name = "3D glasses" + icon_state = "3d" + item_state = "3d" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + +/obj/item/clothing/glasses/gglasses + name = "Green Glasses" + desc = "Forest green glasses, like the kind you'd wear when hatching a nasty scheme." + icon_state = "gglasses" + item_state = "gglasses" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + prescription_upgradable = 1 + +/obj/item/clothing/glasses/sunglasses + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks many flashes." + name = "sunglasses" + icon_state = "sun" + item_state = "sunglasses" + see_in_dark = 1 + flash_protect = 1 + tint = 1 + prescription_upgradable = 1 + dog_fashion = /datum/dog_fashion/head + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/sunglasses_fake + desc = "Cheap, plastic sunglasses. They don't even have UV protection." + name = "cheap sunglasses" + icon_state = "sun" + item_state = "sunglasses" + see_in_dark = 0 + flash_protect = 0 + tint = 0 + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/sunglasses/noir + name = "noir sunglasses" + desc = "Somehow these seem even more out-of-date than normal sunglasses." + actions_types = list(/datum/action/item_action/noir) + +/obj/item/clothing/glasses/sunglasses/noir/attack_self(mob/user) + toggle_noir(user) + +/obj/item/clothing/glasses/sunglasses/noir/item_action_slot_check(slot) + if(slot == slot_glasses) + return 1 + +/obj/item/clothing/glasses/sunglasses/noir/proc/toggle_noir(mob/user) + color_view = color_view ? null : MATRIX_GREYSCALE //Toggles between null and grayscale, with null being the default option. + user.update_client_colour() + +/obj/item/clothing/glasses/sunglasses/yeah + name = "agreeable glasses" + desc = "H.C Limited edition." + var/punused = null + actions_types = list(/datum/action/item_action/YEEEAAAAAHHHHHHHHHHHHH) + +/obj/item/clothing/glasses/sunglasses/yeah/attack_self() + pun() + +/obj/item/clothing/glasses/sunglasses/yeah/proc/pun() + if(!punused)//one per round + punused = 1 + playsound(src.loc, 'sound/misc/yeah.ogg', 100, 0) + usr.visible_message("YEEEAAAAAHHHHHHHHHHHHH!!") + else + to_chat(usr, "The moment is gone.") + + +/obj/item/clothing/glasses/sunglasses/reagent + name = "sunscanners" + desc = "Strangely ancient technology used to help provide rudimentary eye color. Outfitted with apparatus to scan individual reagents." + scan_reagents = 1 + +/obj/item/clothing/glasses/virussunglasses + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks many flashes." + name = "sunglasses" + icon_state = "sun" + item_state = "sunglasses" + see_in_dark = 1 + flash_protect = 1 + tint = 1 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/sunglasses/lasers + desc = "A peculiar set of sunglasses; they have various chips and other panels attached to the sides of the frames." + name = "high-tech sunglasses" + flags = NODROP + +/obj/item/clothing/glasses/sunglasses/lasers/equipped(mob/user, slot) //grant them laser eyes upon equipping it. + if(slot == slot_glasses) + user.mutations.Add(LASER) + user.regenerate_icons() + ..(user, slot) + +/obj/item/clothing/glasses/welding + name = "welding goggles" + desc = "Protects the eyes from welders, approved by the mad scientist association." + icon_state = "welding-g" + item_state = "welding-g" + actions_types = list(/datum/action/item_action/toggle) + flash_protect = 2 + tint = 2 + visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/welding/attack_self(mob/user) + weldingvisortoggle(user) + +/obj/item/clothing/glasses/welding/superior + name = "superior welding goggles" + desc = "Welding goggles made from more expensive materials, strangely smells like potatoes." + icon_state = "rwelding-g" + item_state = "rwelding-g" + flash_protect = 2 + tint = 0 + +/obj/item/clothing/glasses/sunglasses/blindfold + name = "blindfold" + desc = "Covers the eyes, preventing sight." + icon_state = "blindfold" + item_state = "blindfold" + //vision_flags = BLIND + flash_protect = 2 + tint = 3 //to make them blind + prescription_upgradable = 0 + +/obj/item/clothing/glasses/sunglasses/prescription + prescription = 1 + +/obj/item/clothing/glasses/sunglasses/big + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks many flashes." + icon_state = "bigsunglasses" + item_state = "bigsunglasses" + +/obj/item/clothing/glasses/thermal + name = "Optical Thermal Scanner" + desc = "Thermals in the shape of glasses." + icon_state = "thermal" + item_state = "glasses" + origin_tech = "magnets=3" + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + flash_protect = -1 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + +/obj/item/clothing/glasses/thermal/emp_act(severity) + if(istype(src.loc, /mob/living/carbon/human)) + var/mob/living/carbon/human/M = src.loc + to_chat(M, "The Optical Thermal Scanner overloads and blinds you!") + if(M.glasses == src) + M.EyeBlind(3) + M.EyeBlurry(5) + if(!(M.disabilities & NEARSIGHTED)) + M.BecomeNearsighted() + spawn(100) + M.CureNearsighted() + ..() + +/obj/item/clothing/glasses/thermal/monocle + name = "Thermoncle" + desc = "A monocle thermal." + icon_state = "thermoncle" + flags_cover = null //doesn't protect eyes because it's a monocle, duh + +/obj/item/clothing/glasses/thermal/eyepatch + name = "Optical Thermal Eyepatch" + desc = "An eyepatch with built-in thermal optics" + icon_state = "eyepatch" + item_state = "eyepatch" + +/obj/item/clothing/glasses/thermal/jensen + name = "Optical Thermal Implants" + desc = "A set of implantable lenses designed to augment your vision" + icon_state = "thermalimplants" + item_state = "syringe_kit" + +/obj/item/clothing/glasses/thermal/cyber + name = "Eye Replacement Implant" + desc = "An implanted replacement for a left eye with thermal vision capabilities." + icon_state = "cybereye-red" + item_state = "eyepatch" + flags = NODROP + + +/obj/item/clothing/glasses/godeye + name = "eye of god" + desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes." + icon_state = "godeye" + item_state = "godeye" + vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS + see_in_dark = 8 + scan_reagents = 1 + flags = NODROP + flags_cover = null + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + resistance_flags = LAVA_PROOF | FIRE_PROOF + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + +/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, src) && W != src && W.loc == user) + if(W.icon_state == "godeye") + W.icon_state = "doublegodeye" + W.item_state = "doublegodeye" + W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you." + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.update_inv_wear_mask() + else + to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") + qdel(src) + ..() + +/obj/item/clothing/glasses/tajblind + name = "embroidered veil" + desc = "An Ahdominian made veil that allows the user to see while obscuring their eyes." + icon_state = "tajblind" + item_state = "tajblind" + flags_cover = GLASSESCOVERSEYES + actions_types = list(/datum/action/item_action/toggle) + up = 0 + tint = 0 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + +/obj/item/clothing/glasses/tajblind/eng + name = "industrial veil" + icon_state = "tajblind_engi" + item_state = "tajblind_engi" + +/obj/item/clothing/glasses/tajblind/sci + name = "hi-tech veil" + icon_state = "tajblind_sci" + item_state = "tajblind_sci" + +/obj/item/clothing/glasses/tajblind/cargo + name = "khaki veil" + icon_state = "tajblind_cargo" + item_state = "tajblind_cargo" + +/obj/item/clothing/glasses/tajblind/attack_self() + toggle_veil() + +/obj/item/clothing/glasses/proc/toggle_veil() + if(usr.canmove && !usr.incapacitated()) + if(up) + up = !up + tint = initial(tint) + to_chat(usr, "You activate [src], allowing you to see.") + else + up = !up + tint = 3 + to_chat(usr, "You deactivate [src], obscuring your vision.") + var/mob/living/carbon/user = usr + user.update_tint() + user.update_inv_glasses() diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 27cd78afad3..feee4845a9c 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -1,198 +1,198 @@ -/obj/item/clothing/glasses/hud - name = "\improper HUD" - desc = "A heads-up display that provides important info in (almost) real time." - flags = null //doesn't protect eyes because it's a monocle, duh - origin_tech = "magnets=3;biotech=2" - var/HUDType = null //Hudtype is defined on glasses.dm - prescription_upgradable = 1 - var/list/icon/current = list() //the current hud icons - - -/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot) - ..() - if(HUDType && slot == slot_glasses) - var/datum/atom_hud/H = huds[HUDType] - H.add_hud_to(user) - -/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) - ..() - if(HUDType && istype(user) && user.glasses == src) - var/datum/atom_hud/H = huds[HUDType] - H.remove_hud_from(user) - -/obj/item/clothing/glasses/hud/emp_act(severity) - if(emagged == 0) - emagged = 1 - desc = desc + " The display flickers slightly." - -/obj/item/clothing/glasses/hud/health - name = "\improper Health Scanner HUD" - desc = "A heads-up display that scans the humans in view and provides accurate data about their health status." - icon_state = "healthhud" - origin_tech = "magnets=3;biotech=2" - HUDType = DATA_HUD_MEDICAL_ADVANCED - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/hud/health/night - name = "\improper Night Vision Health Scanner HUD" - desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness." - icon_state = "healthhudnight" - item_state = "glasses" - origin_tech = "magnets=4;biotech=4;plasmatech=4;engineering=5" - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - prescription_upgradable = 0 - -/obj/item/clothing/glasses/hud/health/sunglasses - name = "medical HUDSunglasses" - desc = "Sunglasses with a medical HUD." - icon_state = "sunhudmed" - see_in_dark = 1 - flash_protect = 1 - tint = 1 - -/obj/item/clothing/glasses/hud/diagnostic - name = "Diagnostic HUD" - desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits." - icon_state = "diagnostichud" - origin_tech = "magnets=2;engineering=2" - HUDType = DATA_HUD_DIAGNOSTIC - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/hud/diagnostic/night - name = "Night Vision Diagnostic HUD" - desc = "A robotics diagnostic HUD fitted with a light amplifier." - icon_state = "diagnostichudnight" - item_state = "glasses" - origin_tech = "magnets=4;powerstorage=4;plasmatech=4;engineering=5" - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - prescription_upgradable = 0 - -/obj/item/clothing/glasses/hud/diagnostic/sunglasses - name = "diagnostic sunglasses" - desc = "Sunglasses with a diagnostic HUD." - icon_state = "sunhuddiag" - item_state = "glasses" - flash_protect = 1 - tint = 1 - -/obj/item/clothing/glasses/hud/security - name = "\improper Security HUD" - desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records." - icon_state = "securityhud" - origin_tech = "magnets=3;combat=2" - var/global/list/jobs[0] - HUDType = DATA_HUD_SECURITY_ADVANCED - var/read_only = FALSE - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - - -/obj/item/clothing/glasses/hud/security/sunglasses/jensenshades - name = "augmented shades" - desc = "Polarized bioneural eyewear, designed to augment your vision." - icon_state = "jensenshades" - item_state = "jensenshades" - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - -/obj/item/clothing/glasses/hud/security/night - name = "\improper Night Vision Security HUD" - desc = "An advanced heads-up display which provides id data and vision in complete darkness." - icon_state = "securityhudnight" - origin_tech = "magnets=4;combat=4;plasmatech=4;engineering=5" - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these - prescription_upgradable = 0 - -/obj/item/clothing/glasses/hud/security/sunglasses/read_only - read_only = TRUE - -/obj/item/clothing/glasses/hud/security/sunglasses - name = "HUDSunglasses" - desc = "Sunglasses with a HUD." - icon_state = "sunhud" - origin_tech = "magnets=3;combat=3;engineering=3" - see_in_dark = 1 - flash_protect = 1 - tint = 1 - prescription_upgradable = 1 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/hud/security/sunglasses/prescription - prescription = 1 - -/obj/item/clothing/glasses/hud/hydroponic - name = "Hydroponic HUD" - desc = "A heads-up display capable of analyzing the health and status of plants growing in hydro trays and soil." - icon_state = "hydroponichud" - HUDType = DATA_HUD_HYDROPONIC - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi' - ) - -/obj/item/clothing/glasses/hud/hydroponic/night - name = "Night Vision Hydroponic HUD" - desc = "A hydroponic HUD fitted with a light amplifier." - icon_state = "hydroponichudnight" - item_state = "glasses" - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - prescription_upgradable = 0 - -/obj/item/clothing/glasses/hud/security/tajblind - name = "sleek veil" - desc = "An Ahdominian made veil that allows the user to see while obscuring their eyes. This one has an in-built security HUD." - icon_state = "tajblind_sec" - item_state = "tajblind_sec" - flags_cover = GLASSESCOVERSEYES - actions_types = list(/datum/action/item_action/toggle) - up = 0 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi' - ) - -/obj/item/clothing/glasses/hud/security/tajblind/attack_self() - toggle_veil() - -/obj/item/clothing/glasses/hud/health/tajblind - name = "lightweight veil" - desc = "An Ahdominian made veil that allows the user to see while obscuring their eyes. This one has an installed medical HUD." - icon_state = "tajblind_med" - item_state = "tajblind_med" - flags_cover = GLASSESCOVERSEYES - actions_types = list(/datum/action/item_action/toggle) - up = 0 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/eyes.dmi', - "Grey" = 'icons/mob/species/grey/eyes.dmi', - "Drask" = 'icons/mob/species/drask/eyes.dmi' - ) - -/obj/item/clothing/glasses/hud/health/tajblind/attack_self() - toggle_veil() +/obj/item/clothing/glasses/hud + name = "\improper HUD" + desc = "A heads-up display that provides important info in (almost) real time." + flags = null //doesn't protect eyes because it's a monocle, duh + origin_tech = "magnets=3;biotech=2" + var/HUDType = null //Hudtype is defined on glasses.dm + prescription_upgradable = 1 + var/list/icon/current = list() //the current hud icons + + +/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot) + ..() + if(HUDType && slot == slot_glasses) + var/datum/atom_hud/H = huds[HUDType] + H.add_hud_to(user) + +/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) + ..() + if(HUDType && istype(user) && user.glasses == src) + var/datum/atom_hud/H = huds[HUDType] + H.remove_hud_from(user) + +/obj/item/clothing/glasses/hud/emp_act(severity) + if(emagged == 0) + emagged = 1 + desc = desc + " The display flickers slightly." + +/obj/item/clothing/glasses/hud/health + name = "\improper Health Scanner HUD" + desc = "A heads-up display that scans the humans in view and provides accurate data about their health status." + icon_state = "healthhud" + origin_tech = "magnets=3;biotech=2" + HUDType = DATA_HUD_MEDICAL_ADVANCED + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/hud/health/night + name = "\improper Night Vision Health Scanner HUD" + desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness." + icon_state = "healthhudnight" + item_state = "glasses" + origin_tech = "magnets=4;biotech=4;plasmatech=4;engineering=5" + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + prescription_upgradable = 0 + +/obj/item/clothing/glasses/hud/health/sunglasses + name = "medical HUDSunglasses" + desc = "Sunglasses with a medical HUD." + icon_state = "sunhudmed" + see_in_dark = 1 + flash_protect = 1 + tint = 1 + +/obj/item/clothing/glasses/hud/diagnostic + name = "Diagnostic HUD" + desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits." + icon_state = "diagnostichud" + origin_tech = "magnets=2;engineering=2" + HUDType = DATA_HUD_DIAGNOSTIC + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/hud/diagnostic/night + name = "Night Vision Diagnostic HUD" + desc = "A robotics diagnostic HUD fitted with a light amplifier." + icon_state = "diagnostichudnight" + item_state = "glasses" + origin_tech = "magnets=4;powerstorage=4;plasmatech=4;engineering=5" + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + prescription_upgradable = 0 + +/obj/item/clothing/glasses/hud/diagnostic/sunglasses + name = "diagnostic sunglasses" + desc = "Sunglasses with a diagnostic HUD." + icon_state = "sunhuddiag" + item_state = "glasses" + flash_protect = 1 + tint = 1 + +/obj/item/clothing/glasses/hud/security + name = "\improper Security HUD" + desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records." + icon_state = "securityhud" + origin_tech = "magnets=3;combat=2" + var/global/list/jobs[0] + HUDType = DATA_HUD_SECURITY_ADVANCED + var/read_only = FALSE + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + + +/obj/item/clothing/glasses/hud/security/sunglasses/jensenshades + name = "augmented shades" + desc = "Polarized bioneural eyewear, designed to augment your vision." + icon_state = "jensenshades" + item_state = "jensenshades" + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + +/obj/item/clothing/glasses/hud/security/night + name = "\improper Night Vision Security HUD" + desc = "An advanced heads-up display which provides id data and vision in complete darkness." + icon_state = "securityhudnight" + origin_tech = "magnets=4;combat=4;plasmatech=4;engineering=5" + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these + prescription_upgradable = 0 + +/obj/item/clothing/glasses/hud/security/sunglasses/read_only + read_only = TRUE + +/obj/item/clothing/glasses/hud/security/sunglasses + name = "HUDSunglasses" + desc = "Sunglasses with a HUD." + icon_state = "sunhud" + origin_tech = "magnets=3;combat=3;engineering=3" + see_in_dark = 1 + flash_protect = 1 + tint = 1 + prescription_upgradable = 1 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/hud/security/sunglasses/prescription + prescription = 1 + +/obj/item/clothing/glasses/hud/hydroponic + name = "Hydroponic HUD" + desc = "A heads-up display capable of analyzing the health and status of plants growing in hydro trays and soil." + icon_state = "hydroponichud" + HUDType = DATA_HUD_HYDROPONIC + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi' + ) + +/obj/item/clothing/glasses/hud/hydroponic/night + name = "Night Vision Hydroponic HUD" + desc = "A hydroponic HUD fitted with a light amplifier." + icon_state = "hydroponichudnight" + item_state = "glasses" + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + prescription_upgradable = 0 + +/obj/item/clothing/glasses/hud/security/tajblind + name = "sleek veil" + desc = "An Ahdominian made veil that allows the user to see while obscuring their eyes. This one has an in-built security HUD." + icon_state = "tajblind_sec" + item_state = "tajblind_sec" + flags_cover = GLASSESCOVERSEYES + actions_types = list(/datum/action/item_action/toggle) + up = 0 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi' + ) + +/obj/item/clothing/glasses/hud/security/tajblind/attack_self() + toggle_veil() + +/obj/item/clothing/glasses/hud/health/tajblind + name = "lightweight veil" + desc = "An Ahdominian made veil that allows the user to see while obscuring their eyes. This one has an installed medical HUD." + icon_state = "tajblind_med" + item_state = "tajblind_med" + flags_cover = GLASSESCOVERSEYES + actions_types = list(/datum/action/item_action/toggle) + up = 0 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/eyes.dmi', + "Grey" = 'icons/mob/species/grey/eyes.dmi', + "Drask" = 'icons/mob/species/drask/eyes.dmi' + ) + +/obj/item/clothing/glasses/hud/health/tajblind/attack_self() + toggle_veil() diff --git a/code/modules/clothing/gloves/boxing.dm b/code/modules/clothing/gloves/boxing.dm index c31e1abf4b5..701984de055 100644 --- a/code/modules/clothing/gloves/boxing.dm +++ b/code/modules/clothing/gloves/boxing.dm @@ -1,19 +1,19 @@ -/obj/item/clothing/gloves/boxing - name = "boxing gloves" - desc = "Because you really needed another excuse to punch your crewmates." - icon_state = "boxing" - item_state = "boxing" - put_on_delay = 60 - species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion - -/obj/item/clothing/gloves/boxing/green - icon_state = "boxinggreen" - item_state = "boxinggreen" - -/obj/item/clothing/gloves/boxing/blue - icon_state = "boxingblue" - item_state = "boxingblue" - -/obj/item/clothing/gloves/boxing/yellow - icon_state = "boxingyellow" - item_state = "boxingyellow" \ No newline at end of file +/obj/item/clothing/gloves/boxing + name = "boxing gloves" + desc = "Because you really needed another excuse to punch your crewmates." + icon_state = "boxing" + item_state = "boxing" + put_on_delay = 60 + species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion + +/obj/item/clothing/gloves/boxing/green + icon_state = "boxinggreen" + item_state = "boxinggreen" + +/obj/item/clothing/gloves/boxing/blue + icon_state = "boxingblue" + item_state = "boxingblue" + +/obj/item/clothing/gloves/boxing/yellow + icon_state = "boxingyellow" + item_state = "boxingyellow" diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm index cbf9d0695ce..ec4ac9bbf15 100644 --- a/code/modules/clothing/gloves/color.dm +++ b/code/modules/clothing/gloves/color.dm @@ -1,240 +1,240 @@ -/obj/item/clothing/gloves/color/yellow - desc = "These gloves will protect the wearer from electric shock." - name = "insulated gloves" - icon_state = "yellow" - item_state = "ygloves" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - item_color="yellow" - resistance_flags = NONE - -/obj/item/clothing/gloves/color/yellow/power - description_antag = "These are a pair of power gloves, and can be used to fire bolts of electricity while standing over powered power cables." - var/old_mclick_override - var/datum/middleClickOverride/power_gloves/mclick_override = new /datum/middleClickOverride/power_gloves - var/last_shocked = 0 - var/shock_delay = 40 - var/unlimited_power = FALSE // Does this really need explanation? - -/obj/item/clothing/gloves/color/yellow/power/equipped(mob/user, slot) - if(!ishuman(user)) - return - var/mob/living/carbon/human/H = user - if(slot == slot_gloves) - if(H.middleClickOverride) - old_mclick_override = H.middleClickOverride - H.middleClickOverride = mclick_override - if(!unlimited_power) - to_chat(H, "You feel electricity begin to build up in [src].") - else - to_chat(H, "You feel like you have UNLIMITED POWER!!") - -/obj/item/clothing/gloves/color/yellow/power/dropped(mob/user, slot) - if(!ishuman(user)) - return - var/mob/living/carbon/human/H = user - if(H.get_item_by_slot(slot_gloves) == src && H.middleClickOverride == mclick_override) - if(old_mclick_override) - H.middleClickOverride = old_mclick_override - old_mclick_override = null - else - H.middleClickOverride = null - -/obj/item/clothing/gloves/color/yellow/power/unlimited - name = "UNLIMITED POWER gloves" - desc = "These gloves possess UNLIMITED POWER." - shock_delay = 0 - unlimited_power = TRUE - -/obj/item/clothing/gloves/color/yellow/fake - desc = "These gloves will protect the wearer from electric shock. They don't feel like rubber..." - siemens_coefficient = 1 - -/obj/item/clothing/gloves/color/fyellow //Cheap Chinese Crap - desc = "These gloves are cheap copies of the coveted gloves, no way this can end badly." - name = "budget insulated gloves" - icon_state = "yellow" - item_state = "ygloves" - siemens_coefficient = 1 //Set to a default of 1, gets overridden in New() - permeability_coefficient = 0.05 - item_color="yellow" - resistance_flags = NONE - -/obj/item/clothing/gloves/color/fyellow/New() - ..() - siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5) - -/obj/item/clothing/gloves/color/fyellow/old - desc = "Old and worn out insulated gloves, hopefully they still work." - name = "worn out insulated gloves" - -/obj/item/clothing/gloves/color/fyellow/old/New() - ..() - siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75) - -/obj/item/clothing/gloves/color/black - desc = "These gloves are fire-resistant." - name = "black gloves" - icon_state = "black" - item_state = "bgloves" - item_color="brown" - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - resistance_flags = NONE - var/can_be_cut = 1 - - -/obj/item/clothing/gloves/color/black/hos - item_color = "hosred" //Exists for washing machines. Is not different from black gloves in any way. - -/obj/item/clothing/gloves/color/black/ce - item_color = "chief" //Exists for washing machines. Is not different from black gloves in any way. - -/obj/item/clothing/gloves/color/black/thief - pickpocket = 1 - -/obj/item/clothing/gloves/color/black/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/wirecutters)) - if(can_be_cut && icon_state == initial(icon_state))//only if not dyed - var/confirm = alert("Do you want to cut off the gloves fingertips? Warning: It might destroy their functionality.","Cut tips?","Yes","No") - if(get_dist(user, src) > 1) - to_chat(user, "You have moved too far away.") - return - if(confirm == "Yes") - to_chat(user, "You snip the fingertips off of [src].") - playsound(user.loc, W.usesound, rand(10,50), 1) - var/obj/item/clothing/gloves/fingerless/F = new/obj/item/clothing/gloves/fingerless(user.loc) - if(pickpocket) - F.pickpocket = FALSE - qdel(src) - return - ..() - -/obj/item/clothing/gloves/color/orange - name = "orange gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "orange" - item_state = "orangegloves" - item_color="orange" - -/obj/item/clothing/gloves/color/red - name = "red gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "red" - item_state = "redgloves" - item_color = "red" - -/obj/item/clothing/gloves/color/red/insulated - name = "insulated gloves" - desc = "These gloves will protect the wearer from electric shock." - siemens_coefficient = 0 - permeability_coefficient = 0.05 - resistance_flags = NONE - -/obj/item/clothing/gloves/color/rainbow - name = "rainbow gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "rainbow" - item_state = "rainbowgloves" - item_color = "rainbow" - -/obj/item/clothing/gloves/color/rainbow/clown - item_color = "clown" - -/obj/item/clothing/gloves/color/blue - name = "blue gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "blue" - item_state = "bluegloves" - item_color="blue" - -/obj/item/clothing/gloves/color/purple - name = "purple gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "purple" - item_state = "purplegloves" - item_color="purple" - -/obj/item/clothing/gloves/color/green - name = "green gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "green" - item_state = "greengloves" - item_color="green" - -/obj/item/clothing/gloves/color/grey - name = "grey gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "gray" - item_state = "graygloves" - item_color="grey" - -/obj/item/clothing/gloves/color/grey/rd - item_color = "director" //Exists for washing machines. Is not different from gray gloves in any way. - -/obj/item/clothing/gloves/color/grey/hop - item_color = "hop" //Exists for washing machines. Is not different from gray gloves in any way. - -/obj/item/clothing/gloves/color/light_brown - name = "light brown gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "lightbrown" - item_state = "lightbrowngloves" - item_color="light brown" - -/obj/item/clothing/gloves/color/brown - name = "brown gloves" - desc = "A pair of gloves, they don't look special in any way." - icon_state = "brown" - item_state = "browngloves" - item_color="brown" - -/obj/item/clothing/gloves/color/brown/cargo - item_color = "cargo" //Exists for washing machines. Is not different from brown gloves in any way. - -/obj/item/clothing/gloves/color/latex - name = "latex gloves" - desc = "Cheap sterile gloves made from latex." - icon_state = "latex" - item_state = "lgloves" - siemens_coefficient = 0.30 - permeability_coefficient = 0.01 - item_color="white" - transfer_prints = TRUE - resistance_flags = NONE - -/obj/item/clothing/gloves/color/latex/nitrile - name = "nitrile gloves" - desc = "Pricy sterile gloves that are stronger than latex." - icon_state = "nitrile" - item_state = "nitrilegloves" - transfer_prints = FALSE - item_color = "medical" - -/obj/item/clothing/gloves/color/white - name = "white gloves" - desc = "These look pretty fancy." - icon_state = "white" - item_state = "wgloves" - item_color="mime" - -/obj/item/clothing/gloves/color/white/redcoat - item_color = "redcoat" //Exists for washing machines. Is not different from white gloves in any way. - - -/obj/item/clothing/gloves/color/captain - desc = "Regal blue gloves, with a nice gold trim. Swanky." - name = "captain's gloves" - icon_state = "captain" - item_state = "egloves" - item_color = "captain" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - heat_protection = HANDS - max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT - strip_delay = 60 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 50) \ No newline at end of file +/obj/item/clothing/gloves/color/yellow + desc = "These gloves will protect the wearer from electric shock." + name = "insulated gloves" + icon_state = "yellow" + item_state = "ygloves" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + item_color="yellow" + resistance_flags = NONE + +/obj/item/clothing/gloves/color/yellow/power + description_antag = "These are a pair of power gloves, and can be used to fire bolts of electricity while standing over powered power cables." + var/old_mclick_override + var/datum/middleClickOverride/power_gloves/mclick_override = new /datum/middleClickOverride/power_gloves + var/last_shocked = 0 + var/shock_delay = 40 + var/unlimited_power = FALSE // Does this really need explanation? + +/obj/item/clothing/gloves/color/yellow/power/equipped(mob/user, slot) + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(slot == slot_gloves) + if(H.middleClickOverride) + old_mclick_override = H.middleClickOverride + H.middleClickOverride = mclick_override + if(!unlimited_power) + to_chat(H, "You feel electricity begin to build up in [src].") + else + to_chat(H, "You feel like you have UNLIMITED POWER!!") + +/obj/item/clothing/gloves/color/yellow/power/dropped(mob/user, slot) + if(!ishuman(user)) + return + var/mob/living/carbon/human/H = user + if(H.get_item_by_slot(slot_gloves) == src && H.middleClickOverride == mclick_override) + if(old_mclick_override) + H.middleClickOverride = old_mclick_override + old_mclick_override = null + else + H.middleClickOverride = null + +/obj/item/clothing/gloves/color/yellow/power/unlimited + name = "UNLIMITED POWER gloves" + desc = "These gloves possess UNLIMITED POWER." + shock_delay = 0 + unlimited_power = TRUE + +/obj/item/clothing/gloves/color/yellow/fake + desc = "These gloves will protect the wearer from electric shock. They don't feel like rubber..." + siemens_coefficient = 1 + +/obj/item/clothing/gloves/color/fyellow //Cheap Chinese Crap + desc = "These gloves are cheap copies of the coveted gloves, no way this can end badly." + name = "budget insulated gloves" + icon_state = "yellow" + item_state = "ygloves" + siemens_coefficient = 1 //Set to a default of 1, gets overridden in New() + permeability_coefficient = 0.05 + item_color="yellow" + resistance_flags = NONE + +/obj/item/clothing/gloves/color/fyellow/New() + ..() + siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5) + +/obj/item/clothing/gloves/color/fyellow/old + desc = "Old and worn out insulated gloves, hopefully they still work." + name = "worn out insulated gloves" + +/obj/item/clothing/gloves/color/fyellow/old/New() + ..() + siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75) + +/obj/item/clothing/gloves/color/black + desc = "These gloves are fire-resistant." + name = "black gloves" + icon_state = "black" + item_state = "bgloves" + item_color="brown" + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + resistance_flags = NONE + var/can_be_cut = 1 + + +/obj/item/clothing/gloves/color/black/hos + item_color = "hosred" //Exists for washing machines. Is not different from black gloves in any way. + +/obj/item/clothing/gloves/color/black/ce + item_color = "chief" //Exists for washing machines. Is not different from black gloves in any way. + +/obj/item/clothing/gloves/color/black/thief + pickpocket = 1 + +/obj/item/clothing/gloves/color/black/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/wirecutters)) + if(can_be_cut && icon_state == initial(icon_state))//only if not dyed + var/confirm = alert("Do you want to cut off the gloves fingertips? Warning: It might destroy their functionality.","Cut tips?","Yes","No") + if(get_dist(user, src) > 1) + to_chat(user, "You have moved too far away.") + return + if(confirm == "Yes") + to_chat(user, "You snip the fingertips off of [src].") + playsound(user.loc, W.usesound, rand(10,50), 1) + var/obj/item/clothing/gloves/fingerless/F = new/obj/item/clothing/gloves/fingerless(user.loc) + if(pickpocket) + F.pickpocket = FALSE + qdel(src) + return + ..() + +/obj/item/clothing/gloves/color/orange + name = "orange gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "orange" + item_state = "orangegloves" + item_color="orange" + +/obj/item/clothing/gloves/color/red + name = "red gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "red" + item_state = "redgloves" + item_color = "red" + +/obj/item/clothing/gloves/color/red/insulated + name = "insulated gloves" + desc = "These gloves will protect the wearer from electric shock." + siemens_coefficient = 0 + permeability_coefficient = 0.05 + resistance_flags = NONE + +/obj/item/clothing/gloves/color/rainbow + name = "rainbow gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "rainbow" + item_state = "rainbowgloves" + item_color = "rainbow" + +/obj/item/clothing/gloves/color/rainbow/clown + item_color = "clown" + +/obj/item/clothing/gloves/color/blue + name = "blue gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "blue" + item_state = "bluegloves" + item_color="blue" + +/obj/item/clothing/gloves/color/purple + name = "purple gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "purple" + item_state = "purplegloves" + item_color="purple" + +/obj/item/clothing/gloves/color/green + name = "green gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "green" + item_state = "greengloves" + item_color="green" + +/obj/item/clothing/gloves/color/grey + name = "grey gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "gray" + item_state = "graygloves" + item_color="grey" + +/obj/item/clothing/gloves/color/grey/rd + item_color = "director" //Exists for washing machines. Is not different from gray gloves in any way. + +/obj/item/clothing/gloves/color/grey/hop + item_color = "hop" //Exists for washing machines. Is not different from gray gloves in any way. + +/obj/item/clothing/gloves/color/light_brown + name = "light brown gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "lightbrown" + item_state = "lightbrowngloves" + item_color="light brown" + +/obj/item/clothing/gloves/color/brown + name = "brown gloves" + desc = "A pair of gloves, they don't look special in any way." + icon_state = "brown" + item_state = "browngloves" + item_color="brown" + +/obj/item/clothing/gloves/color/brown/cargo + item_color = "cargo" //Exists for washing machines. Is not different from brown gloves in any way. + +/obj/item/clothing/gloves/color/latex + name = "latex gloves" + desc = "Cheap sterile gloves made from latex." + icon_state = "latex" + item_state = "lgloves" + siemens_coefficient = 0.30 + permeability_coefficient = 0.01 + item_color="white" + transfer_prints = TRUE + resistance_flags = NONE + +/obj/item/clothing/gloves/color/latex/nitrile + name = "nitrile gloves" + desc = "Pricy sterile gloves that are stronger than latex." + icon_state = "nitrile" + item_state = "nitrilegloves" + transfer_prints = FALSE + item_color = "medical" + +/obj/item/clothing/gloves/color/white + name = "white gloves" + desc = "These look pretty fancy." + icon_state = "white" + item_state = "wgloves" + item_color="mime" + +/obj/item/clothing/gloves/color/white/redcoat + item_color = "redcoat" //Exists for washing machines. Is not different from white gloves in any way. + + +/obj/item/clothing/gloves/color/captain + desc = "Regal blue gloves, with a nice gold trim. Swanky." + name = "captain's gloves" + icon_state = "captain" + item_state = "egloves" + item_color = "captain" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + heat_protection = HANDS + max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT + strip_delay = 60 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 50) diff --git a/code/modules/clothing/head/beanie.dm b/code/modules/clothing/head/beanie.dm index b912a07c212..ada61c41384 100644 --- a/code/modules/clothing/head/beanie.dm +++ b/code/modules/clothing/head/beanie.dm @@ -93,4 +93,4 @@ name = "rastacap" desc = "Perfect for tucking in those dreadlocks." icon_state = "beanierasta" - item_color = "beanierasta" \ No newline at end of file + item_color = "beanierasta" diff --git a/code/modules/clothing/head/collectable.dm b/code/modules/clothing/head/collectable.dm index cc5c3248b19..501ec562441 100644 --- a/code/modules/clothing/head/collectable.dm +++ b/code/modules/clothing/head/collectable.dm @@ -1,146 +1,146 @@ - -//Hat Station 13 - -/obj/item/clothing/head/collectable - name = "collectable hat" - desc = "A rare collectable hat." - -/obj/item/clothing/head/collectable/petehat - name = "ultra rare Pete's hat!" - desc = "It smells faintly of plasma" - icon_state = "petehat" - -/obj/item/clothing/head/collectable/slime - name = "collectable slime cap!" - desc = "It just latches right in place!" - icon_state = "slime" - -/obj/item/clothing/head/collectable/xenom - name = "collectable xenomorph helmet!" - desc = "Hiss hiss hiss!" - icon_state = "xenom" - -/obj/item/clothing/head/collectable/chef - name = "collectable chef's hat" - desc = "A rare Chef's Hat meant for hat collectors!" - icon_state = "chef" - item_state = "chef" - dog_fashion = /datum/dog_fashion/head/chef - -/obj/item/clothing/head/collectable/paper - name = "collectable paper hat" - desc = "What looks like an ordinary paper hat, is actually a rare and valuable collector's edition paper hat. Keep away from water, fire and Librarians." - icon_state = "paper" - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/collectable/tophat - name = "collectable top hat" - desc = "A top hat worn by only the most prestigious hat collectors." - icon_state = "tophat" - item_state = "that" - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/collectable/captain - name = "collectable captain's hat" - desc = "A Collectable Hat that'll make you look just like a real comdom!" - icon_state = "captain" - item_state = "caphat" - dog_fashion = /datum/dog_fashion/head/captain - -/obj/item/clothing/head/collectable/police - name = "collectable police officer's hat" - desc = "A Collectable Police Officer's Hat. This hat emphasizes that you are THE LAW." - icon_state = "policehelm" - dog_fashion = /datum/dog_fashion/head/warden - -/obj/item/clothing/head/collectable/beret - name = "collectable beret" - desc = "A Collectable red Beret. It smells faintly of Garlic." - icon_state = "beret" - dog_fashion = /datum/dog_fashion/head/beret - -/obj/item/clothing/head/collectable/welding - name = "collectable welding helmet" - desc = "A Collectable Welding Helmet. Now with 80% less lead! Not for actual welding. Any welding done while wearing this Helmet is done so at the owner's own risk!" - icon_state = "welding" - item_state = "welding" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi' - ) - -/obj/item/clothing/head/collectable/slime - name = "collectable slime hat" - desc = "Just like a real Brain Slug!" - icon_state = "headslime" - item_state = "headslime" - -/obj/item/clothing/head/collectable/flatcap - name = "collectable flat cap" - desc = "A Collectible farmer's Flat Cap!" - icon_state = "flat_cap" - item_state = "detective" - -/obj/item/clothing/head/collectable/pirate - name = "collectable pirate hat" - desc = "You'd make a great Dread Syndie Roberts!" - icon_state = "pirate" - item_state = "pirate" - dog_fashion = /datum/dog_fashion/head/pirate - -/obj/item/clothing/head/collectable/kitty - name = "collectable kitty ears" - desc = "The fur feels.....a bit too realistic." - icon_state = "kitty" - item_state = "kitty" - dog_fashion = /datum/dog_fashion/head/kitty - -/obj/item/clothing/head/collectable/rabbitears - name = "collectable rabbit ears" - desc = "Not as lucky as the feet!" - icon_state = "bunny" - item_state = "bunny" - dog_fashion = /datum/dog_fashion/head/rabbit - -/obj/item/clothing/head/collectable/wizard - name = "collectable wizard's hat" - desc = "NOTE:Any magical powers gained from wearing this hat are purely coincidental." - icon_state = "wizard" - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/collectable/hardhat - name = "collectable hard hat" - desc = "WARNING! Offers no real protection, or luminosity, but it is damn fancy!" - icon_state = "hardhat0_yellow" - item_state = "hardhat0_yellow" - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/collectable/HoS - name = "collectable HoS hat" - desc = "Now you can beat prisoners, set silly sentences and arrest for no reason too!" - icon_state = "hoscap" - -/obj/item/clothing/head/collectable/HoP - name = "collectable HoP hat" - desc = "It's your turn to demand excessive paperwork, signatures, stamps, and hire more clowns! Papers, please!" - icon_state = "hopcap" - dog_fashion = /datum/dog_fashion/head/hop - -/obj/item/clothing/head/collectable/thunderdome - name = "collectable Thunderdome helmet" - desc = "Go Red! I mean Green! I mean Red! No Green!" - icon_state = "thunderdome" - item_state = "thunderdome" - -/obj/item/clothing/head/collectable/swat - name = "collectable SWAT helmet" - desc = "Now you can be in the Deathsquad too!" - icon_state = "swat" - item_state = "swat" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi' - ) + +//Hat Station 13 + +/obj/item/clothing/head/collectable + name = "collectable hat" + desc = "A rare collectable hat." + +/obj/item/clothing/head/collectable/petehat + name = "ultra rare Pete's hat!" + desc = "It smells faintly of plasma" + icon_state = "petehat" + +/obj/item/clothing/head/collectable/slime + name = "collectable slime cap!" + desc = "It just latches right in place!" + icon_state = "slime" + +/obj/item/clothing/head/collectable/xenom + name = "collectable xenomorph helmet!" + desc = "Hiss hiss hiss!" + icon_state = "xenom" + +/obj/item/clothing/head/collectable/chef + name = "collectable chef's hat" + desc = "A rare Chef's Hat meant for hat collectors!" + icon_state = "chef" + item_state = "chef" + dog_fashion = /datum/dog_fashion/head/chef + +/obj/item/clothing/head/collectable/paper + name = "collectable paper hat" + desc = "What looks like an ordinary paper hat, is actually a rare and valuable collector's edition paper hat. Keep away from water, fire and Librarians." + icon_state = "paper" + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/collectable/tophat + name = "collectable top hat" + desc = "A top hat worn by only the most prestigious hat collectors." + icon_state = "tophat" + item_state = "that" + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/collectable/captain + name = "collectable captain's hat" + desc = "A Collectable Hat that'll make you look just like a real comdom!" + icon_state = "captain" + item_state = "caphat" + dog_fashion = /datum/dog_fashion/head/captain + +/obj/item/clothing/head/collectable/police + name = "collectable police officer's hat" + desc = "A Collectable Police Officer's Hat. This hat emphasizes that you are THE LAW." + icon_state = "policehelm" + dog_fashion = /datum/dog_fashion/head/warden + +/obj/item/clothing/head/collectable/beret + name = "collectable beret" + desc = "A Collectable red Beret. It smells faintly of Garlic." + icon_state = "beret" + dog_fashion = /datum/dog_fashion/head/beret + +/obj/item/clothing/head/collectable/welding + name = "collectable welding helmet" + desc = "A Collectable Welding Helmet. Now with 80% less lead! Not for actual welding. Any welding done while wearing this Helmet is done so at the owner's own risk!" + icon_state = "welding" + item_state = "welding" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi' + ) + +/obj/item/clothing/head/collectable/slime + name = "collectable slime hat" + desc = "Just like a real Brain Slug!" + icon_state = "headslime" + item_state = "headslime" + +/obj/item/clothing/head/collectable/flatcap + name = "collectable flat cap" + desc = "A Collectible farmer's Flat Cap!" + icon_state = "flat_cap" + item_state = "detective" + +/obj/item/clothing/head/collectable/pirate + name = "collectable pirate hat" + desc = "You'd make a great Dread Syndie Roberts!" + icon_state = "pirate" + item_state = "pirate" + dog_fashion = /datum/dog_fashion/head/pirate + +/obj/item/clothing/head/collectable/kitty + name = "collectable kitty ears" + desc = "The fur feels.....a bit too realistic." + icon_state = "kitty" + item_state = "kitty" + dog_fashion = /datum/dog_fashion/head/kitty + +/obj/item/clothing/head/collectable/rabbitears + name = "collectable rabbit ears" + desc = "Not as lucky as the feet!" + icon_state = "bunny" + item_state = "bunny" + dog_fashion = /datum/dog_fashion/head/rabbit + +/obj/item/clothing/head/collectable/wizard + name = "collectable wizard's hat" + desc = "NOTE:Any magical powers gained from wearing this hat are purely coincidental." + icon_state = "wizard" + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/collectable/hardhat + name = "collectable hard hat" + desc = "WARNING! Offers no real protection, or luminosity, but it is damn fancy!" + icon_state = "hardhat0_yellow" + item_state = "hardhat0_yellow" + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/collectable/HoS + name = "collectable HoS hat" + desc = "Now you can beat prisoners, set silly sentences and arrest for no reason too!" + icon_state = "hoscap" + +/obj/item/clothing/head/collectable/HoP + name = "collectable HoP hat" + desc = "It's your turn to demand excessive paperwork, signatures, stamps, and hire more clowns! Papers, please!" + icon_state = "hopcap" + dog_fashion = /datum/dog_fashion/head/hop + +/obj/item/clothing/head/collectable/thunderdome + name = "collectable Thunderdome helmet" + desc = "Go Red! I mean Green! I mean Red! No Green!" + icon_state = "thunderdome" + item_state = "thunderdome" + +/obj/item/clothing/head/collectable/swat + name = "collectable SWAT helmet" + desc = "Now you can be in the Deathsquad too!" + icon_state = "swat" + item_state = "swat" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi' + ) diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index 18b92433f83..b5d4b94038f 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -101,4 +101,4 @@ dog_fashion = null sprite_sheets = list( "Grey" = 'icons/mob/species/grey/helmet.dmi' - ) \ No newline at end of file + ) diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index d47e3c32365..2a9abf461c9 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -1,301 +1,301 @@ -/obj/item/clothing/head/helmet - name = "helmet" - desc = "Standard Security gear. Protects the head from impacts." - icon_state = "helmetmaterials" - flags = HEADBANGPROTECT - flags_cover = HEADCOVERSEYES - item_state = "helmetmaterials" - armor = list("melee" = 35, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - flags_inv = HIDEEARS|HIDEEYES - cold_protection = HEAD - min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/helmet - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi', - "Drask" = 'icons/mob/species/drask/helmet.dmi', - "Grey" = 'icons/mob/species/grey/helmet.dmi' - ) - -/obj/item/clothing/head/helmet/attack_self(mob/user) - if(can_toggle && !user.incapacitated()) - if(world.time > cooldown + toggle_cooldown) - cooldown = world.time - up = !up - flags ^= visor_flags - flags_inv ^= visor_flags_inv - icon_state = "[initial(icon_state)][up ? "up" : ""]" - to_chat(user, "[up ? alt_toggle_message : toggle_message] \the [src]") - - user.update_inv_head() - - if(active_sound) - while(up) - playsound(src.loc, "[active_sound]", 100, 0, 4) - sleep(15) - if(toggle_sound) - playsound(src.loc, "[toggle_sound]", 100, 0, 4) - - -/obj/item/clothing/head/helmet/visor - name = "visor helmet" - desc = "A helmet with a built-in visor. It doesn't seem to do anything, but it sure looks cool!" - icon_state = "helmetgoggles" - -/obj/item/clothing/head/helmet/thermal - name = "thermal visor helmet" - desc = "A helmet with a built-in thermal scanning visor." - icon_state = "helmetthermals" - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - -/obj/item/clothing/head/helmet/meson - name = "meson visor helmet" - desc = "A helmet with a built-in meson scanning visor." - icon_state = "helmetmesons" - vision_flags = SEE_TURFS - -/obj/item/clothing/head/helmet/material - name = "material visor helmet" - desc = "A helmet with a built-in material scanning visor." - icon_state = "helmetmaterials" - vision_flags = SEE_OBJS - -/obj/item/clothing/head/helmet/night - name = "night-vision helmet" - desc = "A helmet with a built-in pair of night vision goggles." - icon_state = "helmetNVG" - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these - -/obj/item/clothing/head/helmet/alt - name = "bulletproof helmet" - desc = "A bulletproof helmet that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent." - icon_state = "swat" - item_state = "swat-alt" - armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - dog_fashion = null - -/obj/item/clothing/head/helmet/riot - name = "riot helmet" - desc = "It's a helmet specifically designed to protect against close range attacks." - icon_state = "riot" - item_state = "helmet" - armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - flags_inv = HIDEEARS - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - strip_delay = 80 - dog_fashion = null - -/obj/item/clothing/head/helmet/riot/knight - name = "medieval helmet" - desc = "A classic metal helmet." - icon_state = "knight_green" - item_state = "knight_green" - flags = BLOCKHAIR - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - dog_fashion = null - -/obj/item/clothing/head/helmet/justice - name = "helmet of justice" - desc = "WEEEEOOO. WEEEEEOOO. WEEEEOOOO." - icon_state = "justice" - toggle_message = "You turn off the lights on" - alt_toggle_message = "You turn on the lights on" - actions_types = list(/datum/action/item_action/toggle_helmet_light) - can_toggle = 1 - toggle_cooldown = 20 - active_sound = 'sound/items/weeoo1.ogg' - dog_fashion = null - -/obj/item/clothing/head/helmet/justice/escape - name = "alarm helmet" - desc = "WEEEEOOO. WEEEEEOOO. STOP THAT MONKEY. WEEEOOOO." - icon_state = "justice2" - toggle_message = "You turn off the light on" - alt_toggle_message = "You turn on the light on" - - -/obj/item/clothing/head/helmet/swat - name = "\improper SWAT helmet" - desc = "They're often used by highly trained Swat Members." - icon_state = "swat" - item_state = "swat" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 50, "acid" = 50) - flags = null - flags_inv = HIDEEARS|HIDEEYES - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - strip_delay = 80 - dog_fashion = null - -/obj/item/clothing/head/helmet/swat/syndicate - name = "blood-red helmet" - desc = "An extremely robust, space-worthy helmet without a visor to allow for goggle usage underneath. Property of Gorlex Marauders." - icon_state = "helmetsyndi" - item_state = "helmet" - -/obj/item/clothing/head/helmet/thunderdome - name = "\improper Thunderdome helmet" - desc = "'Let the battle commence!'" - icon_state = "thunderdome" - flags = null - item_state = "thunderdome" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) - cold_protection = HEAD - min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT - strip_delay = 80 - dog_fashion = null - -/obj/item/clothing/head/helmet/roman - name = "roman helmet" - desc = "An ancient helmet made of bronze and leather." - flags = null - armor = list("melee" = 25, "bullet" = 0, "laser" = 25, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - icon_state = "roman" - item_state = "roman" - strip_delay = 100 - dog_fashion = null - -/obj/item/clothing/head/helmet/roman/fake - desc = "An ancient helmet made of plastic and leather." - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0) - -/obj/item/clothing/head/helmet/roman/legionaire - name = "roman legionaire helmet" - desc = "An ancient helmet made of bronze and leather. Has a red crest on top of it." - icon_state = "roman_c" - item_state = "roman_c" - -/obj/item/clothing/head/helmet/roman/legionaire/fake - desc = "An ancient helmet made of plastic and leather. Has a red crest on top of it." - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0) - -/obj/item/clothing/head/helmet/gladiator - name = "gladiator helmet" - desc = "Ave, Imperator, morituri te salutant." - icon_state = "gladiator" - flags = BLOCKHAIR - item_state = "gladiator" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES - toggle_message = "You attach the face shield to the" - alt_toggle_message = "You remove the face shield from the" - actions_types = list(/datum/action/item_action/toggle_helmet_mode) - can_toggle = 1 - toggle_cooldown = 20 - toggle_sound = 'sound/items/zippoclose.ogg' - dog_fashion = null - -obj/item/clothing/head/helmet/redtaghelm - name = "red laser tag helmet" - desc = "They have chosen their own end." - icon_state = "redtaghelm" - flags = null - item_state = "redtaghelm" - armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - // Offer about the same protection as a hardhat. - flags_inv = HIDEEARS|HIDEEYES - dog_fashion = null - -obj/item/clothing/head/helmet/bluetaghelm - name = "blue laser tag helmet" - desc = "They'll need more men." - icon_state = "bluetaghelm" - flags = null - item_state = "bluetaghelm" - armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - // Offer about the same protection as a hardhat. - flags_inv = HIDEEARS|HIDEEYES - dog_fashion = null - -obj/item/clothing/head/blob - name = "blob hat" - desc = "A collectible hat handed out at the latest Blob Family Reunion." - icon_state = "blobhat" - item_state = "blobhat" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi' - ) - -/obj/item/clothing/head/helmet/riot/knight/blue - icon_state = "knight_blue" - item_state = "knight_blue" - -/obj/item/clothing/head/helmet/riot/knight/yellow - icon_state = "knight_yellow" - item_state = "knight_yellow" - -/obj/item/clothing/head/helmet/riot/knight/red - icon_state = "knight_red" - item_state = "knight_red" - -/obj/item/clothing/head/helmet/riot/knight/templar - name = "crusader helmet" - desc = "Deus Vult." - icon_state = "knight_templar" - item_state = "knight_templar" - armor = list(melee = 20, bullet = 7, laser = 2, energy = 2, bomb = 2, bio = 2, rad = 0, fire = 80, acid = 80) - -/obj/item/clothing/head/helmet/skull - name = "skull helmet" - desc = "An intimidating tribal helmet, it doesn't look very comfortable." - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES - armor = list("melee" = 35, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - icon_state = "skull" - item_state = "skull" - strip_delay = 100 - -/obj/item/clothing/head/helmet/durathread - name = "durathread helmet" - desc = "A helmet made from durathread and leather." - icon_state = "durathread" - item_state = "durathread" - resistance_flags = FLAMMABLE - armor = list("melee" = 20, "bullet" = 10, "laser" = 30, "energy" = 5, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 50) - strip_delay = 60 - -//Commander -/obj/item/clothing/head/helmet/ert/command - name = "emergency response team commander helmet" - desc = "An in-atmosphere helmet worn by the commander of a Nanotrasen Emergency Response Team. Has blue highlights." - icon_state = "erthelmet_cmd" - -//Security -/obj/item/clothing/head/helmet/ert/security - name = "emergency response team security helmet" - desc = "An in-atmosphere helmet worn by security members of the Nanotrasen Emergency Response Team. Has red highlights." - icon_state = "erthelmet_sec" - -/obj/item/clothing/head/helmet/ert/security/paranormal - name = "paranormal emergency response team helmet" - desc = "An in-atmosphere helmet worn by paranormal members of the Nanotrasen Emergency Response Team. Has crusader sigils." - icon_state = "knight_templar" - item_state = "knight_templar" - -//Engineer -/obj/item/clothing/head/helmet/ert/engineer - name = "emergency response team engineer helmet" - desc = "An in-atmosphere helmet worn by engineering members of the Nanotrasen Emergency Response Team. Has orange highlights." - icon_state = "erthelmet_eng" - -//Medical -/obj/item/clothing/head/helmet/ert/medical - name = "emergency response team medical helmet" - desc = "A set of armor worn by medical members of the Nanotrasen Emergency Response Team. Has red and white highlights." - icon_state = "erthelmet_med" - -//Janitorial -/obj/item/clothing/head/helmet/ert/janitor - name = "emergency response team janitor helmet" - desc = "A set of armor worn by janitorial members of the Nanotrasen Emergency Response Team. Has red and white highlights." - icon_state = "erthelmet_jan" +/obj/item/clothing/head/helmet + name = "helmet" + desc = "Standard Security gear. Protects the head from impacts." + icon_state = "helmetmaterials" + flags = HEADBANGPROTECT + flags_cover = HEADCOVERSEYES + item_state = "helmetmaterials" + armor = list("melee" = 35, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + flags_inv = HIDEEARS|HIDEEYES + cold_protection = HEAD + min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/helmet + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi', + "Drask" = 'icons/mob/species/drask/helmet.dmi', + "Grey" = 'icons/mob/species/grey/helmet.dmi' + ) + +/obj/item/clothing/head/helmet/attack_self(mob/user) + if(can_toggle && !user.incapacitated()) + if(world.time > cooldown + toggle_cooldown) + cooldown = world.time + up = !up + flags ^= visor_flags + flags_inv ^= visor_flags_inv + icon_state = "[initial(icon_state)][up ? "up" : ""]" + to_chat(user, "[up ? alt_toggle_message : toggle_message] \the [src]") + + user.update_inv_head() + + if(active_sound) + while(up) + playsound(src.loc, "[active_sound]", 100, 0, 4) + sleep(15) + if(toggle_sound) + playsound(src.loc, "[toggle_sound]", 100, 0, 4) + + +/obj/item/clothing/head/helmet/visor + name = "visor helmet" + desc = "A helmet with a built-in visor. It doesn't seem to do anything, but it sure looks cool!" + icon_state = "helmetgoggles" + +/obj/item/clothing/head/helmet/thermal + name = "thermal visor helmet" + desc = "A helmet with a built-in thermal scanning visor." + icon_state = "helmetthermals" + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + +/obj/item/clothing/head/helmet/meson + name = "meson visor helmet" + desc = "A helmet with a built-in meson scanning visor." + icon_state = "helmetmesons" + vision_flags = SEE_TURFS + +/obj/item/clothing/head/helmet/material + name = "material visor helmet" + desc = "A helmet with a built-in material scanning visor." + icon_state = "helmetmaterials" + vision_flags = SEE_OBJS + +/obj/item/clothing/head/helmet/night + name = "night-vision helmet" + desc = "A helmet with a built-in pair of night vision goggles." + icon_state = "helmetNVG" + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these + +/obj/item/clothing/head/helmet/alt + name = "bulletproof helmet" + desc = "A bulletproof helmet that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent." + icon_state = "swat" + item_state = "swat-alt" + armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + dog_fashion = null + +/obj/item/clothing/head/helmet/riot + name = "riot helmet" + desc = "It's a helmet specifically designed to protect against close range attacks." + icon_state = "riot" + item_state = "helmet" + armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + flags_inv = HIDEEARS + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + strip_delay = 80 + dog_fashion = null + +/obj/item/clothing/head/helmet/riot/knight + name = "medieval helmet" + desc = "A classic metal helmet." + icon_state = "knight_green" + item_state = "knight_green" + flags = BLOCKHAIR + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + dog_fashion = null + +/obj/item/clothing/head/helmet/justice + name = "helmet of justice" + desc = "WEEEEOOO. WEEEEEOOO. WEEEEOOOO." + icon_state = "justice" + toggle_message = "You turn off the lights on" + alt_toggle_message = "You turn on the lights on" + actions_types = list(/datum/action/item_action/toggle_helmet_light) + can_toggle = 1 + toggle_cooldown = 20 + active_sound = 'sound/items/weeoo1.ogg' + dog_fashion = null + +/obj/item/clothing/head/helmet/justice/escape + name = "alarm helmet" + desc = "WEEEEOOO. WEEEEEOOO. STOP THAT MONKEY. WEEEOOOO." + icon_state = "justice2" + toggle_message = "You turn off the light on" + alt_toggle_message = "You turn on the light on" + + +/obj/item/clothing/head/helmet/swat + name = "\improper SWAT helmet" + desc = "They're often used by highly trained Swat Members." + icon_state = "swat" + item_state = "swat" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 50, "acid" = 50) + flags = null + flags_inv = HIDEEARS|HIDEEYES + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + strip_delay = 80 + dog_fashion = null + +/obj/item/clothing/head/helmet/swat/syndicate + name = "blood-red helmet" + desc = "An extremely robust, space-worthy helmet without a visor to allow for goggle usage underneath. Property of Gorlex Marauders." + icon_state = "helmetsyndi" + item_state = "helmet" + +/obj/item/clothing/head/helmet/thunderdome + name = "\improper Thunderdome helmet" + desc = "'Let the battle commence!'" + icon_state = "thunderdome" + flags = null + item_state = "thunderdome" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) + cold_protection = HEAD + min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = SPACE_HELM_MAX_TEMP_PROTECT + strip_delay = 80 + dog_fashion = null + +/obj/item/clothing/head/helmet/roman + name = "roman helmet" + desc = "An ancient helmet made of bronze and leather." + flags = null + armor = list("melee" = 25, "bullet" = 0, "laser" = 25, "energy" = 10, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + icon_state = "roman" + item_state = "roman" + strip_delay = 100 + dog_fashion = null + +/obj/item/clothing/head/helmet/roman/fake + desc = "An ancient helmet made of plastic and leather." + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0) + +/obj/item/clothing/head/helmet/roman/legionaire + name = "roman legionaire helmet" + desc = "An ancient helmet made of bronze and leather. Has a red crest on top of it." + icon_state = "roman_c" + item_state = "roman_c" + +/obj/item/clothing/head/helmet/roman/legionaire/fake + desc = "An ancient helmet made of plastic and leather. Has a red crest on top of it." + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0) + +/obj/item/clothing/head/helmet/gladiator + name = "gladiator helmet" + desc = "Ave, Imperator, morituri te salutant." + icon_state = "gladiator" + flags = BLOCKHAIR + item_state = "gladiator" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES + toggle_message = "You attach the face shield to the" + alt_toggle_message = "You remove the face shield from the" + actions_types = list(/datum/action/item_action/toggle_helmet_mode) + can_toggle = 1 + toggle_cooldown = 20 + toggle_sound = 'sound/items/zippoclose.ogg' + dog_fashion = null + +obj/item/clothing/head/helmet/redtaghelm + name = "red laser tag helmet" + desc = "They have chosen their own end." + icon_state = "redtaghelm" + flags = null + item_state = "redtaghelm" + armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + // Offer about the same protection as a hardhat. + flags_inv = HIDEEARS|HIDEEYES + dog_fashion = null + +obj/item/clothing/head/helmet/bluetaghelm + name = "blue laser tag helmet" + desc = "They'll need more men." + icon_state = "bluetaghelm" + flags = null + item_state = "bluetaghelm" + armor = list("melee" = 15, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + // Offer about the same protection as a hardhat. + flags_inv = HIDEEARS|HIDEEYES + dog_fashion = null + +obj/item/clothing/head/blob + name = "blob hat" + desc = "A collectible hat handed out at the latest Blob Family Reunion." + icon_state = "blobhat" + item_state = "blobhat" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi' + ) + +/obj/item/clothing/head/helmet/riot/knight/blue + icon_state = "knight_blue" + item_state = "knight_blue" + +/obj/item/clothing/head/helmet/riot/knight/yellow + icon_state = "knight_yellow" + item_state = "knight_yellow" + +/obj/item/clothing/head/helmet/riot/knight/red + icon_state = "knight_red" + item_state = "knight_red" + +/obj/item/clothing/head/helmet/riot/knight/templar + name = "crusader helmet" + desc = "Deus Vult." + icon_state = "knight_templar" + item_state = "knight_templar" + armor = list(melee = 20, bullet = 7, laser = 2, energy = 2, bomb = 2, bio = 2, rad = 0, fire = 80, acid = 80) + +/obj/item/clothing/head/helmet/skull + name = "skull helmet" + desc = "An intimidating tribal helmet, it doesn't look very comfortable." + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES + armor = list("melee" = 35, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + icon_state = "skull" + item_state = "skull" + strip_delay = 100 + +/obj/item/clothing/head/helmet/durathread + name = "durathread helmet" + desc = "A helmet made from durathread and leather." + icon_state = "durathread" + item_state = "durathread" + resistance_flags = FLAMMABLE + armor = list("melee" = 20, "bullet" = 10, "laser" = 30, "energy" = 5, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 50) + strip_delay = 60 + +//Commander +/obj/item/clothing/head/helmet/ert/command + name = "emergency response team commander helmet" + desc = "An in-atmosphere helmet worn by the commander of a Nanotrasen Emergency Response Team. Has blue highlights." + icon_state = "erthelmet_cmd" + +//Security +/obj/item/clothing/head/helmet/ert/security + name = "emergency response team security helmet" + desc = "An in-atmosphere helmet worn by security members of the Nanotrasen Emergency Response Team. Has red highlights." + icon_state = "erthelmet_sec" + +/obj/item/clothing/head/helmet/ert/security/paranormal + name = "paranormal emergency response team helmet" + desc = "An in-atmosphere helmet worn by paranormal members of the Nanotrasen Emergency Response Team. Has crusader sigils." + icon_state = "knight_templar" + item_state = "knight_templar" + +//Engineer +/obj/item/clothing/head/helmet/ert/engineer + name = "emergency response team engineer helmet" + desc = "An in-atmosphere helmet worn by engineering members of the Nanotrasen Emergency Response Team. Has orange highlights." + icon_state = "erthelmet_eng" + +//Medical +/obj/item/clothing/head/helmet/ert/medical + name = "emergency response team medical helmet" + desc = "A set of armor worn by medical members of the Nanotrasen Emergency Response Team. Has red and white highlights." + icon_state = "erthelmet_med" + +//Janitorial +/obj/item/clothing/head/helmet/ert/janitor + name = "emergency response team janitor helmet" + desc = "A set of armor worn by janitorial members of the Nanotrasen Emergency Response Team. Has red and white highlights." + icon_state = "erthelmet_jan" diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index e3ce5a62cd3..993f2050b51 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -1,190 +1,190 @@ - -//Bartender -/obj/item/clothing/head/chefhat - name = "chef's hat" - desc = "It's a hat used by chefs to keep hair out of your food. Judging by the food in the mess, they don't work." - icon_state = "chef" - item_state = "chef" - desc = "The commander in chef's head wear." - strip_delay = 10 - put_on_delay = 10 - dog_fashion = /datum/dog_fashion/head/chef - -//Captain -/obj/item/clothing/head/caphat - name = "captain's hat" - icon_state = "captain" - desc = "It's good being the king." - item_state = "caphat" - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/captain - -//Captain: no longer space-worthy -/obj/item/clothing/head/caphat/parade - name = "captain's parade cap" - desc = "Worn only by Captains with an abundance of class." - icon_state = "capcap" - dog_fashion = null - -//Head of Personnel -/obj/item/clothing/head/hopcap - name = "head of personnel's cap" - icon_state = "hopcap" - desc = "The symbol of true bureaucratic micromanagement." - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - dog_fashion = /datum/dog_fashion/head/hop - -//Nanotrasen Representative -/obj/item/clothing/head/ntrep - name = "Nanotrasen Representative hat" - desc = "A cap issued to the Nanotrasen Representative" - icon_state = "ntrep" - - -//Chaplain -/obj/item/clothing/head/hooded/chaplain_hood - name = "chaplain's hood" - desc = "It's hood that covers the head. It keeps you warm during the space winters." - icon_state = "chaplain_hood" - flags = BLOCKHAIR - flags_cover = HEADCOVERSEYES - -//Chaplain -/obj/item/clothing/head/hooded/nun_hood - name = "nun hood" - desc = "Maximum piety in this star system." - icon_state = "nun_hood" - flags = BLOCKHAIR - flags_cover = HEADCOVERSEYES - -//Chaplain -/obj/item/clothing/head/hooded/monk_hood - name = "monk hood" - desc = "Wooden board not included." - icon_state = "monk_hood" - flags = BLOCKHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/witchhunter_hat - name = "witchhunter hat" - desc = "This hat saw much use back in the day." - icon_state = "witchhunterhat" - item_state = "witchhunterhat" - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/det_hat - name = "hat" - desc = "Someone who wears this will look very smart." - icon_state = "detective" - allowed = list(/obj/item/reagent_containers/food/snacks/candy/candy_corn, /obj/item/pen) - armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) - dog_fashion = /datum/dog_fashion/head/detective - -//Mime -/obj/item/clothing/head/beret - name = "beret" - desc = "A beret, an artists favorite headwear." - icon_state = "beret" - dog_fashion = /datum/dog_fashion/head/beret - -/obj/item/clothing/head/beret/durathread - name = "durathread beret" - desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "beretdurathread" - item_color = null - armor = list("melee" = 15, "bullet" = 5, "laser" = 15, "energy" = 5, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 5) - -//Security -/obj/item/clothing/head/HoS - name = "head of security cap" - desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." - icon_state = "hoscap" - armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 60) - strip_delay = 80 - -/obj/item/clothing/head/HoS/beret - name = "head of security beret" - desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." - icon_state = "beret_hos_black" - -/obj/item/clothing/head/warden - name = "warden's police hat" - desc = "It's a special armored hat issued to the Warden of a security force. Protects the head from impacts." - icon_state = "policehelm" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 60) - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/warden - -/obj/item/clothing/head/officer - name = "officer's cap" - desc = "A red cap with an old-fashioned badge on the front for establishing that you are, in fact, the law." - icon_state = "customshelm" - item_state = "customshelm" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - -/obj/item/clothing/head/beret/sec - name = "security beret" - desc = "A beret with the security insignia emblazoned on it. For officers that are more inclined towards style than safety." - icon_state = "beret_officer" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/beret/sec/warden - name = "warden's beret" - desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." - icon_state = "beret_warden" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) - -/obj/item/clothing/head/beret/eng - name = "engineering beret" - desc = "A beret with the engineering insignia emblazoned on it. For engineers that are more inclined towards style than safety." - icon_state = "beret_engineering" - -/obj/item/clothing/head/beret/atmos - name = "atmospherics beret" - desc = "A beret for those who have shown immaculate proficienty in piping. Or plumbing." - icon_state = "beret_atmospherics" - -/obj/item/clothing/head/beret/ce - name = "chief engineer beret" - desc = "A white beret with the engineering insignia emblazoned on it. Its owner knows what they're doing. Probably." - icon_state = "beret_ce" - -/obj/item/clothing/head/beret/sci - name = "science beret" - desc = "A purple beret with the science insignia emblazoned on it. It has that authentic burning plasma smell." - icon_state = "beret_sci" - -//Medical -/obj/item/clothing/head/beret/med - name = "medical beret" - desc = "A white beret with a green cross finely threaded into it. It has that sterile smell about it." - icon_state = "beret_med" - -/obj/item/clothing/head/surgery - name = "surgical cap" - desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs." - icon_state = "surgcap_blue" - flags = BLOCKHEADHAIR - sprite_sheets = list( - "Drask" = 'icons/mob/species/drask/head.dmi' - ) - -/obj/item/clothing/head/surgery/purple - desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs. This one is deep purple." - icon_state = "surgcap_purple" - -/obj/item/clothing/head/surgery/blue - desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs. This one is baby blue." - icon_state = "surgcap_blue" - -/obj/item/clothing/head/surgery/green - desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs. This one is dark green." - icon_state = "surgcap_green" - -/obj/item/clothing/head/surgery/black - desc = "A cap coroners wear during autopsies. Keeps their hair from falling into the cadavers. It is as dark than the coroner's humor." - icon_state = "surgcap_black" + +//Bartender +/obj/item/clothing/head/chefhat + name = "chef's hat" + desc = "It's a hat used by chefs to keep hair out of your food. Judging by the food in the mess, they don't work." + icon_state = "chef" + item_state = "chef" + desc = "The commander in chef's head wear." + strip_delay = 10 + put_on_delay = 10 + dog_fashion = /datum/dog_fashion/head/chef + +//Captain +/obj/item/clothing/head/caphat + name = "captain's hat" + icon_state = "captain" + desc = "It's good being the king." + item_state = "caphat" + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/captain + +//Captain: no longer space-worthy +/obj/item/clothing/head/caphat/parade + name = "captain's parade cap" + desc = "Worn only by Captains with an abundance of class." + icon_state = "capcap" + dog_fashion = null + +//Head of Personnel +/obj/item/clothing/head/hopcap + name = "head of personnel's cap" + icon_state = "hopcap" + desc = "The symbol of true bureaucratic micromanagement." + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + dog_fashion = /datum/dog_fashion/head/hop + +//Nanotrasen Representative +/obj/item/clothing/head/ntrep + name = "Nanotrasen Representative hat" + desc = "A cap issued to the Nanotrasen Representative" + icon_state = "ntrep" + + +//Chaplain +/obj/item/clothing/head/hooded/chaplain_hood + name = "chaplain's hood" + desc = "It's hood that covers the head. It keeps you warm during the space winters." + icon_state = "chaplain_hood" + flags = BLOCKHAIR + flags_cover = HEADCOVERSEYES + +//Chaplain +/obj/item/clothing/head/hooded/nun_hood + name = "nun hood" + desc = "Maximum piety in this star system." + icon_state = "nun_hood" + flags = BLOCKHAIR + flags_cover = HEADCOVERSEYES + +//Chaplain +/obj/item/clothing/head/hooded/monk_hood + name = "monk hood" + desc = "Wooden board not included." + icon_state = "monk_hood" + flags = BLOCKHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/witchhunter_hat + name = "witchhunter hat" + desc = "This hat saw much use back in the day." + icon_state = "witchhunterhat" + item_state = "witchhunterhat" + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/det_hat + name = "hat" + desc = "Someone who wears this will look very smart." + icon_state = "detective" + allowed = list(/obj/item/reagent_containers/food/snacks/candy/candy_corn, /obj/item/pen) + armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) + dog_fashion = /datum/dog_fashion/head/detective + +//Mime +/obj/item/clothing/head/beret + name = "beret" + desc = "A beret, an artists favorite headwear." + icon_state = "beret" + dog_fashion = /datum/dog_fashion/head/beret + +/obj/item/clothing/head/beret/durathread + name = "durathread beret" + desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "beretdurathread" + item_color = null + armor = list("melee" = 15, "bullet" = 5, "laser" = 15, "energy" = 5, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 5) + +//Security +/obj/item/clothing/head/HoS + name = "head of security cap" + desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." + icon_state = "hoscap" + armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 60) + strip_delay = 80 + +/obj/item/clothing/head/HoS/beret + name = "head of security beret" + desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." + icon_state = "beret_hos_black" + +/obj/item/clothing/head/warden + name = "warden's police hat" + desc = "It's a special armored hat issued to the Warden of a security force. Protects the head from impacts." + icon_state = "policehelm" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 60) + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/warden + +/obj/item/clothing/head/officer + name = "officer's cap" + desc = "A red cap with an old-fashioned badge on the front for establishing that you are, in fact, the law." + icon_state = "customshelm" + item_state = "customshelm" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + +/obj/item/clothing/head/beret/sec + name = "security beret" + desc = "A beret with the security insignia emblazoned on it. For officers that are more inclined towards style than safety." + icon_state = "beret_officer" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/beret/sec/warden + name = "warden's beret" + desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." + icon_state = "beret_warden" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) + +/obj/item/clothing/head/beret/eng + name = "engineering beret" + desc = "A beret with the engineering insignia emblazoned on it. For engineers that are more inclined towards style than safety." + icon_state = "beret_engineering" + +/obj/item/clothing/head/beret/atmos + name = "atmospherics beret" + desc = "A beret for those who have shown immaculate proficienty in piping. Or plumbing." + icon_state = "beret_atmospherics" + +/obj/item/clothing/head/beret/ce + name = "chief engineer beret" + desc = "A white beret with the engineering insignia emblazoned on it. Its owner knows what they're doing. Probably." + icon_state = "beret_ce" + +/obj/item/clothing/head/beret/sci + name = "science beret" + desc = "A purple beret with the science insignia emblazoned on it. It has that authentic burning plasma smell." + icon_state = "beret_sci" + +//Medical +/obj/item/clothing/head/beret/med + name = "medical beret" + desc = "A white beret with a green cross finely threaded into it. It has that sterile smell about it." + icon_state = "beret_med" + +/obj/item/clothing/head/surgery + name = "surgical cap" + desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs." + icon_state = "surgcap_blue" + flags = BLOCKHEADHAIR + sprite_sheets = list( + "Drask" = 'icons/mob/species/drask/head.dmi' + ) + +/obj/item/clothing/head/surgery/purple + desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs. This one is deep purple." + icon_state = "surgcap_purple" + +/obj/item/clothing/head/surgery/blue + desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs. This one is baby blue." + icon_state = "surgcap_blue" + +/obj/item/clothing/head/surgery/green + desc = "A cap surgeons wear during operations. Keeps their hair from tickling your internal organs. This one is dark green." + icon_state = "surgcap_green" + +/obj/item/clothing/head/surgery/black + desc = "A cap coroners wear during autopsies. Keeps their hair from falling into the cadavers. It is as dark than the coroner's humor." + icon_state = "surgcap_black" diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm index ef49855dbec..cac07fe5b1d 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -1,493 +1,493 @@ - - -/obj/item/clothing/head/centhat - name = "\improper CentComm. hat" - icon_state = "centcom" - desc = "It's good to be emperor." - item_state = "centhat" - armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 80 - -/obj/item/clothing/head/hairflower - name = "hair flower pin" - icon_state = "hairflower" - desc = "Smells nice." - item_state = "hairflower" - -/obj/item/clothing/head/powdered_wig - name = "powdered wig" - desc = "A powdered wig." - icon_state = "pwig" - item_state = "pwig" - -/obj/item/clothing/head/justice_wig - name = "Justice wig" - desc = "A fancy powdered wig given to arbitrators of the law. It looks itchy." - icon_state = "jwig" - item_state = "jwig" - -/obj/item/clothing/head/beret/blue - icon_state = "beret_blue" - -/obj/item/clothing/head/beret/black - icon_state = "beret_black" - -/obj/item/clothing/head/beret/purple_normal - icon_state = "beret_purple_normal" - -/obj/item/clothing/head/that - name = "top-hat" - desc = "It's an amish looking hat." - icon_state = "tophat" - item_state = "that" - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/redcoat - name = "redcoat's hat" - icon_state = "redcoat" - desc = "'I guess it's a redhead.'" - -/obj/item/clothing/head/mailman - name = "mailman's hat" - icon_state = "mailman" - desc = "'Right-on-time' mail service head wear." - -/obj/item/clothing/head/plaguedoctorhat - name = "plague doctor's hat" - desc = "These were once used by Plague doctors. They're pretty much useless." - icon_state = "plaguedoctor" - permeability_coefficient = 0.01 - -/obj/item/clothing/head/hasturhood - name = "hastur's hood" - desc = "It's unspeakably stylish" - icon_state = "hasturhood" - flags = BLOCKHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/nursehat - name = "nurse's hat" - desc = "It allows quick identification of trained medical personnel." - icon_state = "nursehat" - dog_fashion = /datum/dog_fashion/head/nurse - -/obj/item/clothing/head/syndicatefake - name = "black and red space-helmet replica" - icon_state = "syndicate-helm-black-red" - item_state = "syndicate-helm-black-red" - desc = "A plastic replica of a syndicate agent's space helmet, you'll look just like a real murderous syndicate agent in this! This is a toy, it is not made for use in space!" - flags = BLOCKHAIR - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/helmet.dmi' - ) - - -/obj/item/clothing/head/cueball - name = "cueball helmet" - desc = "A large, featureless white orb meant to be worn on your head. How do you even see out of this thing?" - icon_state = "cueball" - item_state = "cueball" - flags = BLOCKHAIR - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/head/snowman - name = "snowman head" - desc = "A ball of white styrofoam. So festive." - icon_state = "snowman_h" - item_state = "snowman_h" - flags = BLOCKHAIR - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/head/that - name = "sturdy top-hat" - desc = "It's an amish looking armored top hat." - icon_state = "tophat" - item_state = "that" - flags_inv = 0 - - -/obj/item/clothing/head/greenbandana - name = "green bandana" - desc = "It's a green bandana with some fine nanotech lining." - icon_state = "greenbandana" - item_state = "greenbandana" - flags_inv = 0 - -/obj/item/clothing/head/justice - name = "justice hat" - desc = "Fight for what's righteous!" - icon_state = "justicered" - item_state = "justicered" - flags = BLOCKHAIR - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - -/obj/item/clothing/head/justice/blue - icon_state = "justiceblue" - item_state = "justiceblue" - -/obj/item/clothing/head/justice/yellow - icon_state = "justiceyellow" - item_state = "justiceyellow" - -/obj/item/clothing/head/justice/green - icon_state = "justicegreen" - item_state = "justicegreen" - -/obj/item/clothing/head/justice/pink - icon_state = "justicepink" - item_state = "justicepink" - -/obj/item/clothing/head/rabbitears - name = "rabbit ears" - desc = "Wearing these makes you look useless, and only good for your sex appeal." - icon_state = "bunny" - dog_fashion = /datum/dog_fashion/head/rabbit - -/obj/item/clothing/head/flatcap - name = "flat cap" - desc = "A working man's cap." - icon_state = "flat_cap" - item_state = "detective" - -/obj/item/clothing/head/pirate - name = "pirate hat" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - dog_fashion = /datum/dog_fashion/head/pirate - -/obj/item/clothing/head/hgpiratecap - name = "pirate hat" - desc = "Yarr." - icon_state = "hgpiratecap" - item_state = "hgpiratecap" - -/obj/item/clothing/head/bandana - name = "pirate bandana" - desc = "Yarr." - icon_state = "bandana" - item_state = "bandana" - -//stylish bs12 hats - -/obj/item/clothing/head/bowlerhat - name = "bowler hat" - icon_state = "bowler_hat" - item_state = "bowler_hat" - desc = "For that industrial age look." - -/obj/item/clothing/head/beaverhat - name = "beaver hat" - icon_state = "beaver_hat" - item_state = "beaver_hat" - desc = "Like a top hat, but made of beavers." - -/obj/item/clothing/head/boaterhat - name = "boater hat" - icon_state = "boater_hat" - item_state = "boater_hat" - desc = "Goes well with celery." - -/obj/item/clothing/head/cowboyhat - name = "cowboy hat" - icon_state = "cowboyhat" - item_state = "cowboyhat" - desc = "For the Rancher in us all." - -/obj/item/clothing/head/cowboyhat/tan - name = "tan cowboy hat" - icon_state = "cowboyhat_tan" - item_state = "cowboyhat_tan" - desc = "There's a new sheriff in town. Pass the whiskey." - -/obj/item/clothing/head/cowboyhat/black - name = "black cowboy hat" - icon_state = "cowboyhat_black" - item_state = "cowboyhat_black" - desc = "This station ain't big enough for the two ah' us." - -/obj/item/clothing/head/cowboyhat/white - name = "white cowboy hat" - icon_state = "cowboyhat_white" - item_state = "cowboyhat_white" - desc = "Authentic Marshall hair case. Now ya can protect this here homestead. Navy Model not included." - -/obj/item/clothing/head/cowboyhat/pink - name = "cowgirl hat" - icon_state = "cowboyhat_pink" - item_state = "cowboyhat_pink" - desc = "For those buckle bunnies wanta' become a real buckaroo." - -/obj/item/clothing/head/fedora - name = "fedora" - icon_state = "fedora" - item_state = "fedora" - desc = "A great hat ruined by being within fifty yards of you." - actions_types = list(/datum/action/item_action/tip_fedora) - -/obj/item/clothing/head/fedora/attack_self(mob/user) - tip_fedora(user) - -/obj/item/clothing/head/fedora/item_action_slot_check(slot) - if(slot == slot_head) - return 1 - -/obj/item/clothing/head/fedora/proc/tip_fedora(mob/user) - user.visible_message("[user] tips [user.p_their()] fedora.", "You tip your fedora") - - -/obj/item/clothing/head/fez - name = "fez" - icon_state = "fez" - item_state = "fez" - desc = "Put it on your monkey, make lots of cash money." - -//end bs12 hats - -/obj/item/clothing/head/witchwig - name = "witch costume wig" - desc = "Eeeee~heheheheheheh!" - icon_state = "witch" - item_state = "witch" - flags = BLOCKHAIR - -/obj/item/clothing/head/chicken - name = "chicken suit head" - desc = "Bkaw!" - icon_state = "chickenhead" - item_state = "chickensuit" - flags = BLOCKHAIR - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/head/corgi - name = "corgi suit head" - desc = "Woof!" - icon_state = "corgihead" - item_state = "chickensuit" - flags = BLOCKHAIR - -/obj/item/clothing/head/corgi/super_hero - name = "super-hero corgi suit head" - desc = "Woof! This one seems to pulse with a strange power" - flags = BLOCKHAIR | NODROP - -/obj/item/clothing/head/corgi/super_hero/en - name = "E-N suit head" - icon_state = "enhead" - -/obj/item/clothing/head/bearpelt - name = "bear pelt hat" - desc = "Fuzzy." - icon_state = "bearpelt" - item_state = "bearpelt" - flags = BLOCKHAIR - -/obj/item/clothing/head/xenos - name = "xenos helmet" - icon_state = "xenos" - item_state = "xenos_helm" - desc = "A helmet made out of chitinous alien hide." - flags = BLOCKHAIR - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - -/obj/item/clothing/head/fedora - name = "fedora" - desc = "Someone wearing this definitely makes them cool" - icon_state = "fedora" - -/obj/item/clothing/head/fedora/whitefedora - name = "white fedora" - icon_state = "wfedora" - -/obj/item/clothing/head/fedora/brownfedora - name = "brown fedora" - icon_state = "bfedora" - -/obj/item/clothing/head/stalhelm - name = "Clown Stalhelm" - desc = "The typical clown soldier's helmet." - icon_state = "stalhelm" - item_state = "stalhelm" - flags = BLOCKHAIR - flags_inv = HIDEEARS - -/obj/item/clothing/head/panzer - name = "Clown HONKMech Cap" - desc = "The softcap worn by HONK Mech pilots." - icon_state = "panzercap" - item_state = "panzercap" - flags = BLOCKHAIR - -/obj/item/clothing/head/naziofficer - name = "Clown Officer Cap" - desc = "The peaked clown officer's cap, disturbingly similar to the warden's." - icon_state = "officercap" - item_state = "officercap" - flags = BLOCKHAIR - flags_inv = HIDEEARS - -/obj/item/clothing/head/beret/purple - name = "Pierson Family Beret" - desc = " A purple beret, with a small golden crescent moon sewn onto it." - icon_state = "beret_purple" - item_state = "purpleberet" - -/obj/item/clothing/head/beret/centcom/officer - name = "officers beret" - desc = "A black beret adorned with the shield—a silver kite shield with an engraved sword—of the Nanotrasen security forces, announcing to the world that the wearer is a defender of Nanotrasen." - icon_state = "beret_centcom_officer" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - -/obj/item/clothing/head/beret/centcom/officer/navy - name = "navy blue officers beret" - desc = "A navy blue beret adorned with the shield—a silver kite shield with an engraved sword—of the Nanotrasen security forces, announcing to the world that the wearer is a defender of Nanotrasen." - icon_state = "beret_centcom_officer_navy" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - -/obj/item/clothing/head/beret/centcom/captain - name = "captains beret" - desc = "A white beret adorned with the shield—a cobalt kite shield with an engraved sword—of the Nanotrasen security forces, worn only by those captaining a vessel of the Nanotrasen Navy." - icon_state = "beret_centcom_captain" - -/obj/item/clothing/head/sombrero - name = "sombrero" - icon_state = "sombrero" - item_state = "sombrero" - desc = "You can practically taste the fiesta." - dog_fashion = /datum/dog_fashion/head/sombrero - -/obj/item/clothing/head/sombrero/green - name = "green sombrero" - icon_state = "greensombrero" - item_state = "greensombrero" - desc = "As elegant as a dancing cactus." - dog_fashion = null - -/obj/item/clothing/head/sombrero/shamebrero - name = "shamebrero" - icon_state = "shamebrero" - item_state = "shamebrero" - desc = "Once it's on, it never comes off." - flags = NODROP - dog_fashion = null - -/obj/item/clothing/head/cone - desc = "This cone is trying to warn you of something!" - name = "warning cone" - icon = 'icons/obj/janitor.dmi' - icon_state = "cone" - item_state = "cone" - force = 1.0 - throwforce = 3.0 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - attack_verb = list("warned", "cautioned", "smashed") - resistance_flags = NONE - -/obj/item/clothing/head/jester - name = "jester hat" - desc = "A hat with bells, to add some merryness to the suit." - icon_state = "jester_hat" - -/obj/item/clothing/head/rice_hat - name = "rice hat" - desc = "Welcome to the rice fields, motherfucker." - icon_state = "rice_hat" - -/obj/item/clothing/head/griffin - name = "griffon head" - desc = "Why not 'eagle head'? Who knows." - icon_state = "griffinhat" - item_state = "griffinhat" - flags = BLOCKHAIR - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - actions_types = list(/datum/action/item_action/caw) - -/obj/item/clothing/head/griffin/attack_self() - caw() - -/obj/item/clothing/head/griffin/proc/caw() - if(cooldown < world.time - 20) // A cooldown, to stop people being jerks - playsound(src.loc, 'sound/creatures/caw.ogg', 50, 1) - cooldown = world.time - - -/obj/item/clothing/head/lordadmiralhat - name = "Lord Admiral's Hat" - desc = "A hat suitable for any man of high and exalted rank." - icon_state = "lordadmiralhat" - item_state = "lordadmiralhat" - -/obj/item/clothing/head/human_head - name = "bloated human head" - desc = "A horribly bloated and mismatched human head." - icon_state = "lingspacehelmet" - item_state = "lingspacehelmet" - -/obj/item/clothing/head/papersack - name = "paper sack hat" - desc = "A paper sack with crude holes cut out for eyes. Useful for hiding one's identity or ugliness." - icon_state = "papersack" - flags = BLOCKHAIR - flags_inv = HIDEFACE|HIDEEARS - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/head/papersack/smiley - name = "paper sack hat" - desc = "A paper sack with crude holes cut out for eyes and a sketchy smile drawn on the front. Not creepy at all." - icon_state = "papersack_smile" - flags = BLOCKHAIR - flags_inv = HIDEFACE|HIDEEARS - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/head/crown - name = "crown" - desc = "A crown fit for a king, a petty king maybe." - icon_state = "crown" - armor = list("melee" = 15, "bullet" = 0, "laser" = 0,"energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) - resistance_flags = FIRE_PROOF - -/obj/item/clothing/head/crown/fancy - name = "magnificent crown" - desc = "A crown worn by only the highest emperors of the land." - icon_state = "fancycrown" - -/obj/item/clothing/head/zepelli - name = "chequered diamond hat" - desc = "Wearing this makes you feel like a real mozzarella cheeseball. " - icon_state = "zepelli" - item_state = "zepelli" - -/obj/item/clothing/head/cuban_hat - name = "rhumba hat" - desc = "Now just to find some maracas!" - icon_state = "cuban_hat" - item_state = "cuban_hat" \ No newline at end of file + + +/obj/item/clothing/head/centhat + name = "\improper CentComm. hat" + icon_state = "centcom" + desc = "It's good to be emperor." + item_state = "centhat" + armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 80 + +/obj/item/clothing/head/hairflower + name = "hair flower pin" + icon_state = "hairflower" + desc = "Smells nice." + item_state = "hairflower" + +/obj/item/clothing/head/powdered_wig + name = "powdered wig" + desc = "A powdered wig." + icon_state = "pwig" + item_state = "pwig" + +/obj/item/clothing/head/justice_wig + name = "Justice wig" + desc = "A fancy powdered wig given to arbitrators of the law. It looks itchy." + icon_state = "jwig" + item_state = "jwig" + +/obj/item/clothing/head/beret/blue + icon_state = "beret_blue" + +/obj/item/clothing/head/beret/black + icon_state = "beret_black" + +/obj/item/clothing/head/beret/purple_normal + icon_state = "beret_purple_normal" + +/obj/item/clothing/head/that + name = "top-hat" + desc = "It's an amish looking hat." + icon_state = "tophat" + item_state = "that" + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/redcoat + name = "redcoat's hat" + icon_state = "redcoat" + desc = "'I guess it's a redhead.'" + +/obj/item/clothing/head/mailman + name = "mailman's hat" + icon_state = "mailman" + desc = "'Right-on-time' mail service head wear." + +/obj/item/clothing/head/plaguedoctorhat + name = "plague doctor's hat" + desc = "These were once used by Plague doctors. They're pretty much useless." + icon_state = "plaguedoctor" + permeability_coefficient = 0.01 + +/obj/item/clothing/head/hasturhood + name = "hastur's hood" + desc = "It's unspeakably stylish" + icon_state = "hasturhood" + flags = BLOCKHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/nursehat + name = "nurse's hat" + desc = "It allows quick identification of trained medical personnel." + icon_state = "nursehat" + dog_fashion = /datum/dog_fashion/head/nurse + +/obj/item/clothing/head/syndicatefake + name = "black and red space-helmet replica" + icon_state = "syndicate-helm-black-red" + item_state = "syndicate-helm-black-red" + desc = "A plastic replica of a syndicate agent's space helmet, you'll look just like a real murderous syndicate agent in this! This is a toy, it is not made for use in space!" + flags = BLOCKHAIR + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/helmet.dmi' + ) + + +/obj/item/clothing/head/cueball + name = "cueball helmet" + desc = "A large, featureless white orb meant to be worn on your head. How do you even see out of this thing?" + icon_state = "cueball" + item_state = "cueball" + flags = BLOCKHAIR + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/head/snowman + name = "snowman head" + desc = "A ball of white styrofoam. So festive." + icon_state = "snowman_h" + item_state = "snowman_h" + flags = BLOCKHAIR + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/head/that + name = "sturdy top-hat" + desc = "It's an amish looking armored top hat." + icon_state = "tophat" + item_state = "that" + flags_inv = 0 + + +/obj/item/clothing/head/greenbandana + name = "green bandana" + desc = "It's a green bandana with some fine nanotech lining." + icon_state = "greenbandana" + item_state = "greenbandana" + flags_inv = 0 + +/obj/item/clothing/head/justice + name = "justice hat" + desc = "Fight for what's righteous!" + icon_state = "justicered" + item_state = "justicered" + flags = BLOCKHAIR + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + +/obj/item/clothing/head/justice/blue + icon_state = "justiceblue" + item_state = "justiceblue" + +/obj/item/clothing/head/justice/yellow + icon_state = "justiceyellow" + item_state = "justiceyellow" + +/obj/item/clothing/head/justice/green + icon_state = "justicegreen" + item_state = "justicegreen" + +/obj/item/clothing/head/justice/pink + icon_state = "justicepink" + item_state = "justicepink" + +/obj/item/clothing/head/rabbitears + name = "rabbit ears" + desc = "Wearing these makes you look useless, and only good for your sex appeal." + icon_state = "bunny" + dog_fashion = /datum/dog_fashion/head/rabbit + +/obj/item/clothing/head/flatcap + name = "flat cap" + desc = "A working man's cap." + icon_state = "flat_cap" + item_state = "detective" + +/obj/item/clothing/head/pirate + name = "pirate hat" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + dog_fashion = /datum/dog_fashion/head/pirate + +/obj/item/clothing/head/hgpiratecap + name = "pirate hat" + desc = "Yarr." + icon_state = "hgpiratecap" + item_state = "hgpiratecap" + +/obj/item/clothing/head/bandana + name = "pirate bandana" + desc = "Yarr." + icon_state = "bandana" + item_state = "bandana" + +//stylish bs12 hats + +/obj/item/clothing/head/bowlerhat + name = "bowler hat" + icon_state = "bowler_hat" + item_state = "bowler_hat" + desc = "For that industrial age look." + +/obj/item/clothing/head/beaverhat + name = "beaver hat" + icon_state = "beaver_hat" + item_state = "beaver_hat" + desc = "Like a top hat, but made of beavers." + +/obj/item/clothing/head/boaterhat + name = "boater hat" + icon_state = "boater_hat" + item_state = "boater_hat" + desc = "Goes well with celery." + +/obj/item/clothing/head/cowboyhat + name = "cowboy hat" + icon_state = "cowboyhat" + item_state = "cowboyhat" + desc = "For the Rancher in us all." + +/obj/item/clothing/head/cowboyhat/tan + name = "tan cowboy hat" + icon_state = "cowboyhat_tan" + item_state = "cowboyhat_tan" + desc = "There's a new sheriff in town. Pass the whiskey." + +/obj/item/clothing/head/cowboyhat/black + name = "black cowboy hat" + icon_state = "cowboyhat_black" + item_state = "cowboyhat_black" + desc = "This station ain't big enough for the two ah' us." + +/obj/item/clothing/head/cowboyhat/white + name = "white cowboy hat" + icon_state = "cowboyhat_white" + item_state = "cowboyhat_white" + desc = "Authentic Marshall hair case. Now ya can protect this here homestead. Navy Model not included." + +/obj/item/clothing/head/cowboyhat/pink + name = "cowgirl hat" + icon_state = "cowboyhat_pink" + item_state = "cowboyhat_pink" + desc = "For those buckle bunnies wanta' become a real buckaroo." + +/obj/item/clothing/head/fedora + name = "fedora" + icon_state = "fedora" + item_state = "fedora" + desc = "A great hat ruined by being within fifty yards of you." + actions_types = list(/datum/action/item_action/tip_fedora) + +/obj/item/clothing/head/fedora/attack_self(mob/user) + tip_fedora(user) + +/obj/item/clothing/head/fedora/item_action_slot_check(slot) + if(slot == slot_head) + return 1 + +/obj/item/clothing/head/fedora/proc/tip_fedora(mob/user) + user.visible_message("[user] tips [user.p_their()] fedora.", "You tip your fedora") + + +/obj/item/clothing/head/fez + name = "fez" + icon_state = "fez" + item_state = "fez" + desc = "Put it on your monkey, make lots of cash money." + +//end bs12 hats + +/obj/item/clothing/head/witchwig + name = "witch costume wig" + desc = "Eeeee~heheheheheheh!" + icon_state = "witch" + item_state = "witch" + flags = BLOCKHAIR + +/obj/item/clothing/head/chicken + name = "chicken suit head" + desc = "Bkaw!" + icon_state = "chickenhead" + item_state = "chickensuit" + flags = BLOCKHAIR + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/head/corgi + name = "corgi suit head" + desc = "Woof!" + icon_state = "corgihead" + item_state = "chickensuit" + flags = BLOCKHAIR + +/obj/item/clothing/head/corgi/super_hero + name = "super-hero corgi suit head" + desc = "Woof! This one seems to pulse with a strange power" + flags = BLOCKHAIR | NODROP + +/obj/item/clothing/head/corgi/super_hero/en + name = "E-N suit head" + icon_state = "enhead" + +/obj/item/clothing/head/bearpelt + name = "bear pelt hat" + desc = "Fuzzy." + icon_state = "bearpelt" + item_state = "bearpelt" + flags = BLOCKHAIR + +/obj/item/clothing/head/xenos + name = "xenos helmet" + icon_state = "xenos" + item_state = "xenos_helm" + desc = "A helmet made out of chitinous alien hide." + flags = BLOCKHAIR + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + +/obj/item/clothing/head/fedora + name = "fedora" + desc = "Someone wearing this definitely makes them cool" + icon_state = "fedora" + +/obj/item/clothing/head/fedora/whitefedora + name = "white fedora" + icon_state = "wfedora" + +/obj/item/clothing/head/fedora/brownfedora + name = "brown fedora" + icon_state = "bfedora" + +/obj/item/clothing/head/stalhelm + name = "Clown Stalhelm" + desc = "The typical clown soldier's helmet." + icon_state = "stalhelm" + item_state = "stalhelm" + flags = BLOCKHAIR + flags_inv = HIDEEARS + +/obj/item/clothing/head/panzer + name = "Clown HONKMech Cap" + desc = "The softcap worn by HONK Mech pilots." + icon_state = "panzercap" + item_state = "panzercap" + flags = BLOCKHAIR + +/obj/item/clothing/head/naziofficer + name = "Clown Officer Cap" + desc = "The peaked clown officer's cap, disturbingly similar to the warden's." + icon_state = "officercap" + item_state = "officercap" + flags = BLOCKHAIR + flags_inv = HIDEEARS + +/obj/item/clothing/head/beret/purple + name = "Pierson Family Beret" + desc = " A purple beret, with a small golden crescent moon sewn onto it." + icon_state = "beret_purple" + item_state = "purpleberet" + +/obj/item/clothing/head/beret/centcom/officer + name = "officers beret" + desc = "A black beret adorned with the shield—a silver kite shield with an engraved sword—of the Nanotrasen security forces, announcing to the world that the wearer is a defender of Nanotrasen." + icon_state = "beret_centcom_officer" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + +/obj/item/clothing/head/beret/centcom/officer/navy + name = "navy blue officers beret" + desc = "A navy blue beret adorned with the shield—a silver kite shield with an engraved sword—of the Nanotrasen security forces, announcing to the world that the wearer is a defender of Nanotrasen." + icon_state = "beret_centcom_officer_navy" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + +/obj/item/clothing/head/beret/centcom/captain + name = "captains beret" + desc = "A white beret adorned with the shield—a cobalt kite shield with an engraved sword—of the Nanotrasen security forces, worn only by those captaining a vessel of the Nanotrasen Navy." + icon_state = "beret_centcom_captain" + +/obj/item/clothing/head/sombrero + name = "sombrero" + icon_state = "sombrero" + item_state = "sombrero" + desc = "You can practically taste the fiesta." + dog_fashion = /datum/dog_fashion/head/sombrero + +/obj/item/clothing/head/sombrero/green + name = "green sombrero" + icon_state = "greensombrero" + item_state = "greensombrero" + desc = "As elegant as a dancing cactus." + dog_fashion = null + +/obj/item/clothing/head/sombrero/shamebrero + name = "shamebrero" + icon_state = "shamebrero" + item_state = "shamebrero" + desc = "Once it's on, it never comes off." + flags = NODROP + dog_fashion = null + +/obj/item/clothing/head/cone + desc = "This cone is trying to warn you of something!" + name = "warning cone" + icon = 'icons/obj/janitor.dmi' + icon_state = "cone" + item_state = "cone" + force = 1.0 + throwforce = 3.0 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("warned", "cautioned", "smashed") + resistance_flags = NONE + +/obj/item/clothing/head/jester + name = "jester hat" + desc = "A hat with bells, to add some merryness to the suit." + icon_state = "jester_hat" + +/obj/item/clothing/head/rice_hat + name = "rice hat" + desc = "Welcome to the rice fields, motherfucker." + icon_state = "rice_hat" + +/obj/item/clothing/head/griffin + name = "griffon head" + desc = "Why not 'eagle head'? Who knows." + icon_state = "griffinhat" + item_state = "griffinhat" + flags = BLOCKHAIR + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + actions_types = list(/datum/action/item_action/caw) + +/obj/item/clothing/head/griffin/attack_self() + caw() + +/obj/item/clothing/head/griffin/proc/caw() + if(cooldown < world.time - 20) // A cooldown, to stop people being jerks + playsound(src.loc, 'sound/creatures/caw.ogg', 50, 1) + cooldown = world.time + + +/obj/item/clothing/head/lordadmiralhat + name = "Lord Admiral's Hat" + desc = "A hat suitable for any man of high and exalted rank." + icon_state = "lordadmiralhat" + item_state = "lordadmiralhat" + +/obj/item/clothing/head/human_head + name = "bloated human head" + desc = "A horribly bloated and mismatched human head." + icon_state = "lingspacehelmet" + item_state = "lingspacehelmet" + +/obj/item/clothing/head/papersack + name = "paper sack hat" + desc = "A paper sack with crude holes cut out for eyes. Useful for hiding one's identity or ugliness." + icon_state = "papersack" + flags = BLOCKHAIR + flags_inv = HIDEFACE|HIDEEARS + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/head/papersack/smiley + name = "paper sack hat" + desc = "A paper sack with crude holes cut out for eyes and a sketchy smile drawn on the front. Not creepy at all." + icon_state = "papersack_smile" + flags = BLOCKHAIR + flags_inv = HIDEFACE|HIDEEARS + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/head/crown + name = "crown" + desc = "A crown fit for a king, a petty king maybe." + icon_state = "crown" + armor = list("melee" = 15, "bullet" = 0, "laser" = 0,"energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + +/obj/item/clothing/head/crown/fancy + name = "magnificent crown" + desc = "A crown worn by only the highest emperors of the land." + icon_state = "fancycrown" + +/obj/item/clothing/head/zepelli + name = "chequered diamond hat" + desc = "Wearing this makes you feel like a real mozzarella cheeseball. " + icon_state = "zepelli" + item_state = "zepelli" + +/obj/item/clothing/head/cuban_hat + name = "rhumba hat" + desc = "Now just to find some maracas!" + icon_state = "cuban_hat" + item_state = "cuban_hat" diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index d939dadffba..5f8ae17d061 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -1,273 +1,273 @@ -/* - * Contents: - * Welding mask - * Cakehat - * Ushanka - * Pumpkin head - * Kitty ears - * Cardborg Disguise - * Head Mirror - */ - -/* - * Welding mask - */ -/obj/item/clothing/head/welding - name = "welding helmet" - desc = "A head-mounted face cover designed to protect the wearer completely from space-arc eye." - icon_state = "welding" - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - item_state = "welding" - materials = list(MAT_METAL=1750, MAT_GLASS=400) - flash_protect = 2 - tint = 2 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 60) - flags_inv = (HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE) - actions_types = list(/datum/action/item_action/toggle) - visor_flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - resistance_flags = FIRE_PROOF - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/head.dmi', - "Unathi" = 'icons/mob/species/unathi/helmet.dmi', - "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', - "Grey" = 'icons/mob/species/grey/helmet.dmi' - ) - -/obj/item/clothing/head/welding/attack_self(mob/user) - weldingvisortoggle(user) - -/obj/item/clothing/head/welding/flamedecal - name = "flame decal welding helmet" - desc = "A welding helmet adorned with flame decals, and several cryptic slogans of varying degrees of legibility." - icon_state = "welding_redflame" - -/obj/item/clothing/head/welding/flamedecal/blue - name = "blue flame decal welding helmet" - desc = "A welding helmet with blue flame decals on it." - icon_state = "welding_blueflame" - -/obj/item/clothing/head/welding/white - name = "white decal welding helmet" - desc = "A white welding helmet with a character written across it." - icon_state = "welding_white" - -/obj/item/clothing/head/welding/attack_self() - toggle() - -/obj/item/clothing/head/welding/proc/toggle() - if(up) - up = !up - flags_cover |= (HEADCOVERSEYES | HEADCOVERSMOUTH) - flags_inv |= (HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE) - icon_state = initial(icon_state) - to_chat(usr, "You flip the [src] down to protect your eyes.") - flash_protect = 2 - tint = 2 - else - up = !up - flags_cover &= ~(HEADCOVERSEYES | HEADCOVERSMOUTH) - flags_inv &= ~(HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE) - icon_state = "[initial(icon_state)]up" - to_chat(usr, "You push the [src] up out of your face.") - flash_protect = 0 - tint = 0 - var/mob/living/carbon/user = usr - user.update_tint() - user.update_inv_head() //so our mob-overlays update - - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - - - -/* - * Cakehat - */ -/obj/item/clothing/head/cakehat - name = "cake-hat" - desc = "It's tasty looking!" - icon_state = "cake0" - flags_cover = HEADCOVERSEYES - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - var/onfire = 0.0 - var/status = 0 - var/fire_resist = T0C+1300 //this is the max temp it can stand before you start to cook. although it might not burn away, you take damage - var/processing = 0 //I dont think this is used anywhere. - -/obj/item/clothing/head/cakehat/process() - if(!onfire) - STOP_PROCESSING(SSobj, src) - return - - var/turf/location = src.loc - if(istype(location, /mob/)) - var/mob/living/carbon/human/M = location - if(M.l_hand == src || M.r_hand == src || M.head == src) - location = M.loc - - if(istype(location, /turf)) - location.hotspot_expose(700, 1) - -/obj/item/clothing/head/cakehat/attack_self(mob/user as mob) - if(status > 1) return - src.onfire = !( src.onfire ) - if(src.onfire) - src.force = 3 - src.damtype = "fire" - src.icon_state = "cake1" - START_PROCESSING(SSobj, src) - else - src.force = null - src.damtype = "brute" - src.icon_state = "cake0" - return - - -/* - * Ushanka - */ -/obj/item/clothing/head/ushanka - name = "ushanka" - desc = "Perfect for winter in Siberia, da?" - icon_state = "ushankadown" - item_state = "ushankadown" - flags_inv = HIDEEARS - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - dog_fashion = /datum/dog_fashion/head/ushanka - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/head/ushanka/attack_self(mob/user as mob) - if(src.icon_state == "ushankadown") - src.icon_state = "ushankaup" - src.item_state = "ushankaup" - to_chat(user, "You raise the ear flaps on the ushanka.") - else - src.icon_state = "ushankadown" - src.item_state = "ushankadown" - to_chat(user, "You lower the ear flaps on the ushanka.") - -/* - * Pumpkin head - */ -/obj/item/clothing/head/hardhat/pumpkinhead - name = "carved pumpkin" - desc = "A jack o' lantern! Believed to ward off evil spirits." - icon_state = "hardhat0_pumpkin"//Could stand to be renamed - item_state = "hardhat0_pumpkin" - item_color = "pumpkin" - flags = BLOCKHAIR - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - brightness_on = 2 //luminosity when on - - -/obj/item/clothing/head/hardhat/reindeer - name = "novelty reindeer hat" - desc = "Some fake antlers and a very fake red nose." - icon_state = "hardhat0_reindeer" - item_state = "hardhat0_reindeer" - item_color = "reindeer" - flags_inv = 0 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - brightness_on = 1 //luminosity when on - dog_fashion = /datum/dog_fashion/head/reindeer - - -/* - * Kitty ears - */ -/obj/item/clothing/head/kitty - name = "kitty ears" - desc = "A pair of kitty ears. Meow!" - icon_state = "kitty" - var/icon/mob - dog_fashion = /datum/dog_fashion/head/kitty - -/obj/item/clothing/head/kitty/update_icon(var/mob/living/carbon/human/user) - if(!istype(user)) return - var/obj/item/organ/external/head/head_organ = user.get_organ("head") - - mob = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "kitty") - mob.Blend(head_organ.hair_colour, ICON_ADD) - - var/icon/earbit = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "kittyinner") - mob.Blend(earbit, ICON_OVERLAY) - - icon_override = mob - -/obj/item/clothing/head/kitty/equipped(var/mob/M, slot) - . = ..() - if(ishuman(M) && slot == slot_head) - update_icon(M) - - -/obj/item/clothing/head/kitty/mouse - name = "mouse ears" - desc = "A pair of mouse ears. Squeak!" - icon_state = "mousey" - -/obj/item/clothing/head/kitty/mouse/update_icon(var/mob/living/carbon/human/user) - if(!istype(user)) return - var/obj/item/organ/external/head/head_organ = user.get_organ("head") - mob = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "mousey") - mob.Blend(head_organ.hair_colour, ICON_ADD) - - var/icon/earbit = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "mouseyinner") - mob.Blend(earbit, ICON_OVERLAY) - - icon_override = mob - -/obj/item/clothing/head/cardborg - name = "cardborg helmet" - desc = "A helmet made out of a box." - icon_state = "cardborg_h" - item_state = "cardborg_h" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - species_disguise = "High-tech robot" - dog_fashion = /datum/dog_fashion/head/cardborg - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - - -/obj/item/clothing/head/cardborg/equipped(mob/living/user, slot) - ..() - if(ishuman(user) && slot == slot_head) - var/mob/living/carbon/human/H = user - if(istype(H.wear_suit, /obj/item/clothing/suit/cardborg)) - var/obj/item/clothing/suit/cardborg/CB = H.wear_suit - CB.disguise(user, src) - -/obj/item/clothing/head/cardborg/dropped(mob/living/user) - ..() - user.remove_alt_appearance("standard_borg_disguise") - -/* - * Head Mirror - */ -/obj/item/clothing/head/headmirror - name = "head mirror" - desc = "A band of rubber with a very reflective looking mirror attached to the front of it. One of the early signs of medical budget cuts." - icon_state = "head_mirror" - item_state = "head_mirror" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/head.dmi', - "Drask" = 'icons/mob/species/drask/head.dmi', - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - +/* + * Contents: + * Welding mask + * Cakehat + * Ushanka + * Pumpkin head + * Kitty ears + * Cardborg Disguise + * Head Mirror + */ + +/* + * Welding mask + */ +/obj/item/clothing/head/welding + name = "welding helmet" + desc = "A head-mounted face cover designed to protect the wearer completely from space-arc eye." + icon_state = "welding" + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + item_state = "welding" + materials = list(MAT_METAL=1750, MAT_GLASS=400) + flash_protect = 2 + tint = 2 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 60) + flags_inv = (HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE) + actions_types = list(/datum/action/item_action/toggle) + visor_flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + resistance_flags = FIRE_PROOF + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/head.dmi', + "Unathi" = 'icons/mob/species/unathi/helmet.dmi', + "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', + "Grey" = 'icons/mob/species/grey/helmet.dmi' + ) + +/obj/item/clothing/head/welding/attack_self(mob/user) + weldingvisortoggle(user) + +/obj/item/clothing/head/welding/flamedecal + name = "flame decal welding helmet" + desc = "A welding helmet adorned with flame decals, and several cryptic slogans of varying degrees of legibility." + icon_state = "welding_redflame" + +/obj/item/clothing/head/welding/flamedecal/blue + name = "blue flame decal welding helmet" + desc = "A welding helmet with blue flame decals on it." + icon_state = "welding_blueflame" + +/obj/item/clothing/head/welding/white + name = "white decal welding helmet" + desc = "A white welding helmet with a character written across it." + icon_state = "welding_white" + +/obj/item/clothing/head/welding/attack_self() + toggle() + +/obj/item/clothing/head/welding/proc/toggle() + if(up) + up = !up + flags_cover |= (HEADCOVERSEYES | HEADCOVERSMOUTH) + flags_inv |= (HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE) + icon_state = initial(icon_state) + to_chat(usr, "You flip the [src] down to protect your eyes.") + flash_protect = 2 + tint = 2 + else + up = !up + flags_cover &= ~(HEADCOVERSEYES | HEADCOVERSMOUTH) + flags_inv &= ~(HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE) + icon_state = "[initial(icon_state)]up" + to_chat(usr, "You push the [src] up out of your face.") + flash_protect = 0 + tint = 0 + var/mob/living/carbon/user = usr + user.update_tint() + user.update_inv_head() //so our mob-overlays update + + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + + + +/* + * Cakehat + */ +/obj/item/clothing/head/cakehat + name = "cake-hat" + desc = "It's tasty looking!" + icon_state = "cake0" + flags_cover = HEADCOVERSEYES + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + var/onfire = 0.0 + var/status = 0 + var/fire_resist = T0C+1300 //this is the max temp it can stand before you start to cook. although it might not burn away, you take damage + var/processing = 0 //I dont think this is used anywhere. + +/obj/item/clothing/head/cakehat/process() + if(!onfire) + STOP_PROCESSING(SSobj, src) + return + + var/turf/location = src.loc + if(istype(location, /mob/)) + var/mob/living/carbon/human/M = location + if(M.l_hand == src || M.r_hand == src || M.head == src) + location = M.loc + + if(istype(location, /turf)) + location.hotspot_expose(700, 1) + +/obj/item/clothing/head/cakehat/attack_self(mob/user as mob) + if(status > 1) return + src.onfire = !( src.onfire ) + if(src.onfire) + src.force = 3 + src.damtype = "fire" + src.icon_state = "cake1" + START_PROCESSING(SSobj, src) + else + src.force = null + src.damtype = "brute" + src.icon_state = "cake0" + return + + +/* + * Ushanka + */ +/obj/item/clothing/head/ushanka + name = "ushanka" + desc = "Perfect for winter in Siberia, da?" + icon_state = "ushankadown" + item_state = "ushankadown" + flags_inv = HIDEEARS + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + dog_fashion = /datum/dog_fashion/head/ushanka + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/head/ushanka/attack_self(mob/user as mob) + if(src.icon_state == "ushankadown") + src.icon_state = "ushankaup" + src.item_state = "ushankaup" + to_chat(user, "You raise the ear flaps on the ushanka.") + else + src.icon_state = "ushankadown" + src.item_state = "ushankadown" + to_chat(user, "You lower the ear flaps on the ushanka.") + +/* + * Pumpkin head + */ +/obj/item/clothing/head/hardhat/pumpkinhead + name = "carved pumpkin" + desc = "A jack o' lantern! Believed to ward off evil spirits." + icon_state = "hardhat0_pumpkin"//Could stand to be renamed + item_state = "hardhat0_pumpkin" + item_color = "pumpkin" + flags = BLOCKHAIR + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + brightness_on = 2 //luminosity when on + + +/obj/item/clothing/head/hardhat/reindeer + name = "novelty reindeer hat" + desc = "Some fake antlers and a very fake red nose." + icon_state = "hardhat0_reindeer" + item_state = "hardhat0_reindeer" + item_color = "reindeer" + flags_inv = 0 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + brightness_on = 1 //luminosity when on + dog_fashion = /datum/dog_fashion/head/reindeer + + +/* + * Kitty ears + */ +/obj/item/clothing/head/kitty + name = "kitty ears" + desc = "A pair of kitty ears. Meow!" + icon_state = "kitty" + var/icon/mob + dog_fashion = /datum/dog_fashion/head/kitty + +/obj/item/clothing/head/kitty/update_icon(var/mob/living/carbon/human/user) + if(!istype(user)) return + var/obj/item/organ/external/head/head_organ = user.get_organ("head") + + mob = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "kitty") + mob.Blend(head_organ.hair_colour, ICON_ADD) + + var/icon/earbit = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "kittyinner") + mob.Blend(earbit, ICON_OVERLAY) + + icon_override = mob + +/obj/item/clothing/head/kitty/equipped(var/mob/M, slot) + . = ..() + if(ishuman(M) && slot == slot_head) + update_icon(M) + + +/obj/item/clothing/head/kitty/mouse + name = "mouse ears" + desc = "A pair of mouse ears. Squeak!" + icon_state = "mousey" + +/obj/item/clothing/head/kitty/mouse/update_icon(var/mob/living/carbon/human/user) + if(!istype(user)) return + var/obj/item/organ/external/head/head_organ = user.get_organ("head") + mob = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "mousey") + mob.Blend(head_organ.hair_colour, ICON_ADD) + + var/icon/earbit = new/icon("icon" = 'icons/mob/head.dmi', "icon_state" = "mouseyinner") + mob.Blend(earbit, ICON_OVERLAY) + + icon_override = mob + +/obj/item/clothing/head/cardborg + name = "cardborg helmet" + desc = "A helmet made out of a box." + icon_state = "cardborg_h" + item_state = "cardborg_h" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + species_disguise = "High-tech robot" + dog_fashion = /datum/dog_fashion/head/cardborg + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + + +/obj/item/clothing/head/cardborg/equipped(mob/living/user, slot) + ..() + if(ishuman(user) && slot == slot_head) + var/mob/living/carbon/human/H = user + if(istype(H.wear_suit, /obj/item/clothing/suit/cardborg)) + var/obj/item/clothing/suit/cardborg/CB = H.wear_suit + CB.disguise(user, src) + +/obj/item/clothing/head/cardborg/dropped(mob/living/user) + ..() + user.remove_alt_appearance("standard_borg_disguise") + +/* + * Head Mirror + */ +/obj/item/clothing/head/headmirror + name = "head mirror" + desc = "A band of rubber with a very reflective looking mirror attached to the front of it. One of the early signs of medical budget cuts." + icon_state = "head_mirror" + item_state = "head_mirror" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/head.dmi', + "Drask" = 'icons/mob/species/drask/head.dmi', + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm index b111672e86a..0255109620a 100644 --- a/code/modules/clothing/head/soft_caps.dm +++ b/code/modules/clothing/head/soft_caps.dm @@ -1,132 +1,132 @@ -/obj/item/clothing/head/soft - name = "cargo cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "cargosoft" - item_state = "helmet" - item_color = "cargo" - var/flipped = 0 - actions_types = list(/datum/action/item_action/flip_cap) - dog_fashion = /datum/dog_fashion/head/cargo_tech - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/head.dmi' - ) - -/obj/item/clothing/head/soft/dropped() - icon_state = "[item_color]soft" - flipped = 0 - ..() - -/obj/item/clothing/head/soft/attack_self(mob/user) - flip(user) - -/obj/item/clothing/head/soft/proc/flip(mob/user) - flipped = !flipped - if(flipped) - icon_state = "[item_color]soft_flipped" - to_chat(usr, "You flip the hat backwards.") - else - icon_state = "[item_color]soft" - to_chat(user, "You flip the hat back in normal position.") - user.update_inv_head() //so our mob-overlays update - - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/clothing/head/soft/red - name = "red cap" - desc = "It's a baseball hat in a tasteless red colour." - icon_state = "redsoft" - item_color = "red" - dog_fashion = null - -/obj/item/clothing/head/soft/blue - name = "blue cap" - desc = "It's a baseball hat in a tasteless blue colour." - icon_state = "bluesoft" - item_color = "blue" - dog_fashion = null - -/obj/item/clothing/head/soft/green - name = "green cap" - desc = "It's a baseball hat in a tasteless green colour." - icon_state = "greensoft" - item_color = "green" - dog_fashion = null - -/obj/item/clothing/head/soft/yellow - name = "yellow cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "yellowsoft" - item_color = "yellow" - dog_fashion = null - -/obj/item/clothing/head/soft/grey - name = "grey cap" - desc = "It's a baseball hat in a tasteful grey colour." - icon_state = "greysoft" - item_color = "grey" - dog_fashion = null - -/obj/item/clothing/head/soft/orange - name = "orange cap" - desc = "It's a baseball hat in a tasteless orange colour." - icon_state = "orangesoft" - item_color = "orange" - -/obj/item/clothing/head/soft/mime - name = "white cap" - desc = "It's a baseball hat in a tasteless white colour." - icon_state = "mimesoft" - item_color = "mime" - dog_fashion = null - -/obj/item/clothing/head/soft/purple - name = "purple cap" - desc = "It's a baseball hat in a tasteless purple colour." - icon_state = "purplesoft" - item_color = "purple" - dog_fashion = null - -/obj/item/clothing/head/soft/black - name = "black cap" - desc = "It's a baseball hat in a tasteless black colour." - icon_state = "blacksoft" - item_color = "black" - dog_fashion = null - -/obj/item/clothing/head/soft/rainbow - name = "rainbow cap" - desc = "It's a baseball hat in a bright rainbow of colors." - icon_state = "rainbowsoft" - item_color = "rainbow" - dog_fashion = null - -/obj/item/clothing/head/soft/sec - name = "security cap" - desc = "It's baseball hat in tasteful red colour." - icon_state = "secsoft" - item_color = "sec" - armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/soft/sec/corp - name = "corporate security cap" - desc = "It's baseball hat in corpotate colours." - icon_state = "corpsoft" - item_color = "corp" - -/obj/item/clothing/head/soft/solgov - name = "Sol Federation marine cap" - desc = "A soft cap worn by marines of the Sol Federation." - icon_state = "solgovsoft" - item_color = "solgov" - dog_fashion = null - -/obj/item/clothing/head/soft/solgov/command - name = "Sol Federation Lieutenant's cap" - desc = "A soft cap worn by marines of the Sol Federation. The insignia signifies the wearer bears the rank of a Lieutenant." - icon_state = "solgovcsoft" - item_color = "solgovc" - dog_fashion = null \ No newline at end of file +/obj/item/clothing/head/soft + name = "cargo cap" + desc = "It's a baseball hat in a tasteless yellow colour." + icon_state = "cargosoft" + item_state = "helmet" + item_color = "cargo" + var/flipped = 0 + actions_types = list(/datum/action/item_action/flip_cap) + dog_fashion = /datum/dog_fashion/head/cargo_tech + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/head.dmi' + ) + +/obj/item/clothing/head/soft/dropped() + icon_state = "[item_color]soft" + flipped = 0 + ..() + +/obj/item/clothing/head/soft/attack_self(mob/user) + flip(user) + +/obj/item/clothing/head/soft/proc/flip(mob/user) + flipped = !flipped + if(flipped) + icon_state = "[item_color]soft_flipped" + to_chat(usr, "You flip the hat backwards.") + else + icon_state = "[item_color]soft" + to_chat(user, "You flip the hat back in normal position.") + user.update_inv_head() //so our mob-overlays update + + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/clothing/head/soft/red + name = "red cap" + desc = "It's a baseball hat in a tasteless red colour." + icon_state = "redsoft" + item_color = "red" + dog_fashion = null + +/obj/item/clothing/head/soft/blue + name = "blue cap" + desc = "It's a baseball hat in a tasteless blue colour." + icon_state = "bluesoft" + item_color = "blue" + dog_fashion = null + +/obj/item/clothing/head/soft/green + name = "green cap" + desc = "It's a baseball hat in a tasteless green colour." + icon_state = "greensoft" + item_color = "green" + dog_fashion = null + +/obj/item/clothing/head/soft/yellow + name = "yellow cap" + desc = "It's a baseball hat in a tasteless yellow colour." + icon_state = "yellowsoft" + item_color = "yellow" + dog_fashion = null + +/obj/item/clothing/head/soft/grey + name = "grey cap" + desc = "It's a baseball hat in a tasteful grey colour." + icon_state = "greysoft" + item_color = "grey" + dog_fashion = null + +/obj/item/clothing/head/soft/orange + name = "orange cap" + desc = "It's a baseball hat in a tasteless orange colour." + icon_state = "orangesoft" + item_color = "orange" + +/obj/item/clothing/head/soft/mime + name = "white cap" + desc = "It's a baseball hat in a tasteless white colour." + icon_state = "mimesoft" + item_color = "mime" + dog_fashion = null + +/obj/item/clothing/head/soft/purple + name = "purple cap" + desc = "It's a baseball hat in a tasteless purple colour." + icon_state = "purplesoft" + item_color = "purple" + dog_fashion = null + +/obj/item/clothing/head/soft/black + name = "black cap" + desc = "It's a baseball hat in a tasteless black colour." + icon_state = "blacksoft" + item_color = "black" + dog_fashion = null + +/obj/item/clothing/head/soft/rainbow + name = "rainbow cap" + desc = "It's a baseball hat in a bright rainbow of colors." + icon_state = "rainbowsoft" + item_color = "rainbow" + dog_fashion = null + +/obj/item/clothing/head/soft/sec + name = "security cap" + desc = "It's baseball hat in tasteful red colour." + icon_state = "secsoft" + item_color = "sec" + armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/soft/sec/corp + name = "corporate security cap" + desc = "It's baseball hat in corpotate colours." + icon_state = "corpsoft" + item_color = "corp" + +/obj/item/clothing/head/soft/solgov + name = "Sol Federation marine cap" + desc = "A soft cap worn by marines of the Sol Federation." + icon_state = "solgovsoft" + item_color = "solgov" + dog_fashion = null + +/obj/item/clothing/head/soft/solgov/command + name = "Sol Federation Lieutenant's cap" + desc = "A soft cap worn by marines of the Sol Federation. The insignia signifies the wearer bears the rank of a Lieutenant." + icon_state = "solgovcsoft" + item_color = "solgovc" + dog_fashion = null diff --git a/code/modules/clothing/masks/boxing.dm b/code/modules/clothing/masks/boxing.dm index 78034a3c0c8..14afa81cb58 100644 --- a/code/modules/clothing/masks/boxing.dm +++ b/code/modules/clothing/masks/boxing.dm @@ -1,52 +1,52 @@ -/obj/item/clothing/mask/balaclava - name = "balaclava" - desc = "LOADSAMONEY" - icon_state = "balaclava" - item_state = "balaclava" - flags = BLOCKHAIR - flags_inv = HIDEFACE - w_class = WEIGHT_CLASS_SMALL - actions_types = list(/datum/action/item_action/adjust) - adjusted_flags = SLOT_HEAD - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi' - ) - -/obj/item/clothing/mask/balaclava/attack_self(var/mob/user) - adjustmask(user) - -/obj/item/clothing/mask/luchador - name = "Luchador Mask" - desc = "Worn by robust fighters, flying high to defeat their foes!" - icon_state = "luchag" - item_state = "luchag" - flags = BLOCKHAIR - flags_inv = HIDEFACE - w_class = WEIGHT_CLASS_SMALL - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi' - ) - -/obj/item/clothing/mask/luchador/tecnicos - name = "Tecnicos Mask" - desc = "Worn by robust fighters who uphold justice and fight honorably." - icon_state = "luchador" - item_state = "luchador" - -/obj/item/clothing/mask/luchador/rudos - name = "Rudos Mask" - desc = "Worn by robust fighters who are willing to do anything to win." - icon_state = "luchar" - item_state = "luchar" +/obj/item/clothing/mask/balaclava + name = "balaclava" + desc = "LOADSAMONEY" + icon_state = "balaclava" + item_state = "balaclava" + flags = BLOCKHAIR + flags_inv = HIDEFACE + w_class = WEIGHT_CLASS_SMALL + actions_types = list(/datum/action/item_action/adjust) + adjusted_flags = SLOT_HEAD + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi' + ) + +/obj/item/clothing/mask/balaclava/attack_self(var/mob/user) + adjustmask(user) + +/obj/item/clothing/mask/luchador + name = "Luchador Mask" + desc = "Worn by robust fighters, flying high to defeat their foes!" + icon_state = "luchag" + item_state = "luchag" + flags = BLOCKHAIR + flags_inv = HIDEFACE + w_class = WEIGHT_CLASS_SMALL + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi' + ) + +/obj/item/clothing/mask/luchador/tecnicos + name = "Tecnicos Mask" + desc = "Worn by robust fighters who uphold justice and fight honorably." + icon_state = "luchador" + item_state = "luchador" + +/obj/item/clothing/mask/luchador/rudos + name = "Rudos Mask" + desc = "Worn by robust fighters who are willing to do anything to win." + icon_state = "luchar" + item_state = "luchar" diff --git a/code/modules/clothing/masks/breath.dm b/code/modules/clothing/masks/breath.dm index c37fad48906..032062461e7 100644 --- a/code/modules/clothing/masks/breath.dm +++ b/code/modules/clothing/masks/breath.dm @@ -1,54 +1,54 @@ -/obj/item/clothing/mask/breath - desc = "A close-fitting mask that can be connected to an air supply." - name = "breath mask" - icon_state = "breath" - item_state = "breath" - flags = AIRTIGHT - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.10 - permeability_coefficient = 0.50 - actions_types = list(/datum/action/item_action/adjust) - resistance_flags = NONE - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Vox Armalis" = 'icons/mob/species/armalis/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi', - "Plasmaman" = 'icons/mob/species/plasmaman/mask.dmi' - ) - -/obj/item/clothing/mask/breath/attack_self(var/mob/user) - adjustmask(user) - -/obj/item/clothing/mask/breath/AltClick(mob/user) - ..() - if( (!in_range(src, user)) || user.stat || user.restrained() ) - return - adjustmask(user) - -/obj/item/clothing/mask/breath/medical - desc = "A close-fitting sterile mask that can be connected to an air supply." - name = "medical mask" - icon_state = "medical" - item_state = "medical" - permeability_coefficient = 0.01 - put_on_delay = 10 - -/obj/item/clothing/mask/breath/vox - desc = "A weirdly-shaped breath mask." - name = "vox breath mask" - icon_state = "voxmask" - item_state = "voxmask" - permeability_coefficient = 0.01 - species_restricted = list("Vox", "Vox Armalis") //These should fit the "Mega Vox" just fine. - actions_types = list() - -/obj/item/clothing/mask/breath/vox/attack_self(var/mob/user) - return - -/obj/item/clothing/mask/breath/vox/AltClick(mob/user) - return +/obj/item/clothing/mask/breath + desc = "A close-fitting mask that can be connected to an air supply." + name = "breath mask" + icon_state = "breath" + item_state = "breath" + flags = AIRTIGHT + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.10 + permeability_coefficient = 0.50 + actions_types = list(/datum/action/item_action/adjust) + resistance_flags = NONE + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Vox Armalis" = 'icons/mob/species/armalis/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi', + "Plasmaman" = 'icons/mob/species/plasmaman/mask.dmi' + ) + +/obj/item/clothing/mask/breath/attack_self(var/mob/user) + adjustmask(user) + +/obj/item/clothing/mask/breath/AltClick(mob/user) + ..() + if( (!in_range(src, user)) || user.stat || user.restrained() ) + return + adjustmask(user) + +/obj/item/clothing/mask/breath/medical + desc = "A close-fitting sterile mask that can be connected to an air supply." + name = "medical mask" + icon_state = "medical" + item_state = "medical" + permeability_coefficient = 0.01 + put_on_delay = 10 + +/obj/item/clothing/mask/breath/vox + desc = "A weirdly-shaped breath mask." + name = "vox breath mask" + icon_state = "voxmask" + item_state = "voxmask" + permeability_coefficient = 0.01 + species_restricted = list("Vox", "Vox Armalis") //These should fit the "Mega Vox" just fine. + actions_types = list() + +/obj/item/clothing/mask/breath/vox/attack_self(var/mob/user) + return + +/obj/item/clothing/mask/breath/vox/AltClick(mob/user) + return diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index b6e3164a44d..b798c1d8df0 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -1,365 +1,365 @@ -/obj/item/clothing/mask/gas - name = "gas mask" - desc = "A face-covering mask that can be connected to an air supply." - icon_state = "gas_alt" - flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = MASKCOVERSMOUTH | MASKCOVERSEYES - w_class = WEIGHT_CLASS_NORMAL - item_state = "gas_alt" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - resistance_flags = NONE - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Plasmaman" = 'icons/mob/species/plasmaman/mask.dmi' - ) - -// **** Welding gas mask **** - -/obj/item/clothing/mask/gas/welding - name = "welding mask" - desc = "A gas mask with built in welding goggles and face shield. Looks like a skull, clearly designed by a nerd." - icon_state = "weldingmask" - item_state = "weldingmask" - materials = list(MAT_METAL=4000, MAT_GLASS=2000) - flash_protect = 2 - tint = 2 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 55) - origin_tech = "materials=2;engineering=3" - actions_types = list(/datum/action/item_action/toggle) - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = MASKCOVERSEYES - visor_flags_inv = HIDEEYES - resistance_flags = FIRE_PROOF - -/obj/item/clothing/mask/gas/welding/attack_self(mob/user) - weldingvisortoggle(user) - -/obj/item/clothing/mask/gas/explorer - name = "explorer gas mask" - desc = "A military-grade gas mask that can be connected to an air supply." - icon_state = "gas_mining" - actions_types = list(/datum/action/item_action/adjust) - armor = list("melee" = 10, "bullet" = 5, "laser" = 5, "energy" = 5, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 20, "acid" = 40) - resistance_flags = FIRE_PROOF - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi' - ) - -/obj/item/clothing/mask/gas/explorer/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/gas/explorer/adjustmask(user) - ..() - w_class = mask_adjusted ? WEIGHT_CLASS_SMALL : WEIGHT_CLASS_NORMAL - -/obj/item/clothing/mask/gas/explorer/folded/Initialize() - . = ..() - adjustmask() - -//Bane gas mask -/obj/item/clothing/mask/banemask - name = "bane mask" - desc = "Only when the station is in flames, do you have my permission to robust." - icon_state = "bane_mask" - flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE - flags_cover = MASKCOVERSMOUTH | MASKCOVERSEYES - w_class = WEIGHT_CLASS_NORMAL - item_state = "bane_mask" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - - -//Plague Dr suit can be found in clothing/suits/bio.dm -/obj/item/clothing/mask/gas/plaguedoctor - name = "plague doctor mask" - desc = "A modernised version of the classic design, this mask will not only filter out toxins but it can also be connected to an air supply." - icon_state = "plaguedoctor" - item_state = "gas_mask" - armor = list("melee" = 0, "bullet" = 0, "laser" = 2, "energy" = 2, "bomb" = 0, "bio" = 75, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/mask/gas/swat - name = "\improper SWAT mask" - desc = "A close-fitting tactical mask that can be connected to an air supply." - icon_state = "swat" - -/obj/item/clothing/mask/gas/syndicate - name = "syndicate mask" - desc = "A close-fitting tactical mask that can be connected to an air supply." - icon_state = "swat" - strip_delay = 60 - -/obj/item/clothing/mask/gas/clown_hat - name = "clown wig and mask" - desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask." - icon_state = "clown" - item_state = "clown_hat" - flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT | BLOCKHAIR - resistance_flags = FLAMMABLE - dog_fashion = /datum/dog_fashion/head/clown - -/obj/item/clothing/mask/gas/clown_hat/attack_self(mob/user) - - var/mob/M = usr - var/list/options = list() - options["True Form"] = "clown" - options["The Feminist"] = "sexyclown" - options["The Madman"] = "joker" - options["The Rainbow Color"] ="rainbow" - - var/choice = input(M,"To what form do you wish to Morph this mask?","Morph Mask") in options - - if(src && choice && !M.stat && in_range(M,src)) - icon_state = options[choice] - to_chat(M, "Your Clown Mask has now morphed into [choice], all praise the Honk Mother!") - return 1 - -/obj/item/clothing/mask/gas/clown_hat/sexy - name = "sexy-clown wig and mask" - desc = "A feminine clown mask for the dabbling crossdressers or female entertainers." - icon_state = "sexyclown" - item_state = "sexyclown" - -/obj/item/clothing/mask/gas/clownwiz - name = "wizard clown wig and mask" - desc = "Some pranksters are truly magical." - icon_state = "wizzclown" - item_state = "wizzclown" - flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT | BLOCKHAIR - flags_inv = HIDEEARS | HIDEEYES - magical = TRUE - -/obj/item/clothing/mask/gas/clown_hat/nodrop - flags = NODROP - -/obj/item/clothing/mask/gas/mime - name = "mime mask" - desc = "The traditional mime's mask. It has an eerie facial posture." - icon_state = "mime" - item_state = "mime" - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/mime/nodrop - flags = NODROP - -/obj/item/clothing/mask/gas/monkeymask - name = "monkey mask" - desc = "A mask used when acting as a monkey." - icon_state = "monkeymask" - item_state = "monkeymask" - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/sexymime - name = "sexy mime mask" - desc = "A traditional female mime's mask." - icon_state = "sexymime" - item_state = "sexymime" - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/cyborg - name = "cyborg visor" - desc = "Beep boop" - icon_state = "death" - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/owl_mask - name = "owl mask" - desc = "Twoooo!" - icon_state = "owl" - resistance_flags = FLAMMABLE - actions_types = list(/datum/action/item_action/hoot) - -/obj/item/clothing/mask/gas/owl_mask/super_hero - flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT | NODROP - -/obj/item/clothing/mask/gas/owl_mask/attack_self() - hoot() - -/obj/item/clothing/mask/gas/owl_mask/proc/hoot() - if(cooldown < world.time - 35) // A cooldown, to stop people being jerks - playsound(src.loc, 'sound/creatures/hoot.ogg', 50, 1) - cooldown = world.time - -// ******************************************************************** - -// **** Security gas mask **** - -/obj/item/clothing/mask/gas/sechailer - name = "security gas mask" - desc = "A standard issue Security gas mask with integrated 'Compli-o-nator 3000' device, plays over a dozen pre-recorded compliance phrases designed to get scumbags to stand still whilst you taze them. Do not tamper with the device." - icon_state = "sechailer" - item_state = "sechailer" - var/phrase = 1 - var/aggressiveness = 1 - var/safety = 1 - actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/adjust, /datum/action/item_action/selectphrase) - var/phrase_list = list( - - "halt" = "HALT! HALT! HALT! HALT!", - "bobby" = "Stop in the name of the Law.", - "compliance" = "Compliance is in your best interest.", - "justice" = "Prepare for justice!", - "running" = "Running will only increase your sentence.", - "dontmove" = "Don't move, Creep!", - "floor" = "Down on the floor, Creep!", - "robocop" = "Dead or alive you're coming with me.", - "god" = "God made today for the crooks we could not catch yesterday.", - "freeze" = "Freeze, Scum Bag!", - "imperial" = "Stop right there, criminal scum!", - "bash" = "Stop or I'll bash you.", - "harry" = "Go ahead, make my day.", - "asshole" = "Stop breaking the law, asshole.", - "stfu" = "You have the right to shut the fuck up", - "shutup" = "Shut up crime!", - "super" = "Face the wrath of the golden bolt.", - "dredd" = "I am, the LAW!" - ) -/obj/item/clothing/mask/gas/sechailer/hos - name = "\improper HOS SWAT mask" - desc = "A close-fitting tactical mask with an especially aggressive Compli-o-nator 3000. It has a tan stripe." - icon_state = "hosmask" - aggressiveness = 3 - phrase = 12 - actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) - -/obj/item/clothing/mask/gas/sechailer/warden - name = "\improper Warden SWAT mask" - desc = "A close-fitting tactical mask with an especially aggressive Compli-o-nator 3000. It has a blue stripe." - icon_state = "wardenmask" - aggressiveness = 3 - phrase = 12 - actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) - - -/obj/item/clothing/mask/gas/sechailer/swat - name = "\improper SWAT mask" - desc = "A close-fitting tactical mask with an especially aggressive Compli-o-nator 3000." - icon_state = "officermask" - aggressiveness = 3 - phrase = 12 - actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) - -/obj/item/clothing/mask/gas/sechailer/blue - name = "\improper blue SWAT mask" - desc = "A neon blue swat mask, used for demoralizing Greytide in the wild." - icon_state = "blue_sechailer" - item_state = "blue_sechailer" - aggressiveness = 3 - phrase = 12 - actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) - -/obj/item/clothing/mask/gas/sechailer/cyborg - name = "security hailer" - desc = "A set of recognizable pre-recorded messages for cyborgs to use when apprehending criminals." - icon = 'icons/obj/device.dmi' - icon_state = "taperecorder_idle" - actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) - -/obj/item/clothing/mask/gas/sechailer/ui_action_click(mob/user, actiontype) - if(actiontype == /datum/action/item_action/halt) - halt() - else if(actiontype == /datum/action/item_action/adjust) - adjustmask(user) - else if(actiontype == /datum/action/item_action/selectphrase) - var/key = phrase_list[phrase] - var/message = phrase_list[key] - - if (!safety) - to_chat(user, "You set the restrictor to: FUCK YOUR CUNT YOU SHIT EATING COCKSUCKER MAN EAT A DONG FUCKING ASS RAMMING SHIT FUCK EAT PENISES IN YOUR FUCK FACE AND SHIT OUT ABORTIONS OF FUCK AND DO SHIT IN YOUR ASS YOU COCK FUCK SHIT MONKEY FUCK ASS WANKER FROM THE DEPTHS OF SHIT.") - return - - switch(aggressiveness) - if(1) - phrase = (phrase < 6) ? (phrase + 1) : 1 - key = phrase_list[phrase] - message = phrase_list[key] - to_chat(user,"You set the restrictor to: [message]") - if(2) - phrase = (phrase < 11 && phrase >= 7) ? (phrase + 1) : 7 - key = phrase_list[phrase] - message = phrase_list[key] - to_chat(user,"You set the restrictor to: [message]") - if(3) - phrase = (phrase < 18 && phrase >= 12 ) ? (phrase + 1) : 12 - key = phrase_list[phrase] - message = phrase_list[key] - to_chat(user,"You set the restrictor to: [message]") - if(4) - phrase = (phrase < 18 && phrase >= 1 ) ? (phrase + 1) : 1 - key = phrase_list[phrase] - message = phrase_list[key] - to_chat(user,"You set the restrictor to: [message]") - else - to_chat(user, "It's broken.") - -/obj/item/clothing/mask/gas/sechailer/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/screwdriver)) - switch(aggressiveness) - if(1) - to_chat(user, "You set the aggressiveness restrictor to the second position.") - aggressiveness = 2 - phrase = 7 - if(2) - to_chat(user, "You set the aggressiveness restrictor to the third position.") - aggressiveness = 3 - phrase = 13 - if(3) - to_chat(user, "You set the aggressiveness restrictor to the fourth position.") - aggressiveness = 4 - phrase = 1 - if(4) - to_chat(user, "You set the aggressiveness restrictor to the first position.") - aggressiveness = 1 - phrase = 1 - if(5) - to_chat(user, "You adjust the restrictor but nothing happens, probably because its broken.") - else if(istype(W, /obj/item/wirecutters)) - if(aggressiveness != 5) - to_chat(user, "You broke it!") - aggressiveness = 5 - else - ..() - -/obj/item/clothing/mask/gas/sechailer/attack_self() - halt() - -/obj/item/clothing/mask/gas/sechailer/emag_act(mob/user as mob) - if(safety) - safety = 0 - to_chat(user, "You silently fry [src]'s vocal circuit with the cryptographic sequencer.") - else - return - -/obj/item/clothing/mask/gas/sechailer/proc/halt() - var/key = phrase_list[phrase] - var/message = phrase_list[key] - - - if(cooldown < world.time - 35) // A cooldown, to stop people being jerks - if(!safety) - message = "FUCK YOUR CUNT YOU SHIT EATING COCKSUCKER MAN EAT A DONG FUCKING ASS RAMMING SHIT FUCK EAT PENISES IN YOUR FUCK FACE AND SHIT OUT ABORTIONS OF FUCK AND DO SHIT IN YOUR ASS YOU COCK FUCK SHIT MONKEY FUCK ASS WANKER FROM THE DEPTHS OF SHIT." - usr.visible_message("[usr]'s Compli-o-Nator: [message]") - playsound(src.loc, 'sound/voice/binsult.ogg', 100, 0, 4) - cooldown = world.time - return - - usr.visible_message("[usr]'s Compli-o-Nator: [message]") - playsound(src.loc, "sound/voice/complionator/[key].ogg", 100, 0, 4) - cooldown = world.time - - - -// ******************************************************************** +/obj/item/clothing/mask/gas + name = "gas mask" + desc = "A face-covering mask that can be connected to an air supply." + icon_state = "gas_alt" + flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = MASKCOVERSMOUTH | MASKCOVERSEYES + w_class = WEIGHT_CLASS_NORMAL + item_state = "gas_alt" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + resistance_flags = NONE + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Plasmaman" = 'icons/mob/species/plasmaman/mask.dmi' + ) + +// **** Welding gas mask **** + +/obj/item/clothing/mask/gas/welding + name = "welding mask" + desc = "A gas mask with built in welding goggles and face shield. Looks like a skull, clearly designed by a nerd." + icon_state = "weldingmask" + item_state = "weldingmask" + materials = list(MAT_METAL=4000, MAT_GLASS=2000) + flash_protect = 2 + tint = 2 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 55) + origin_tech = "materials=2;engineering=3" + actions_types = list(/datum/action/item_action/toggle) + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = MASKCOVERSEYES + visor_flags_inv = HIDEEYES + resistance_flags = FIRE_PROOF + +/obj/item/clothing/mask/gas/welding/attack_self(mob/user) + weldingvisortoggle(user) + +/obj/item/clothing/mask/gas/explorer + name = "explorer gas mask" + desc = "A military-grade gas mask that can be connected to an air supply." + icon_state = "gas_mining" + actions_types = list(/datum/action/item_action/adjust) + armor = list("melee" = 10, "bullet" = 5, "laser" = 5, "energy" = 5, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 20, "acid" = 40) + resistance_flags = FIRE_PROOF + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi' + ) + +/obj/item/clothing/mask/gas/explorer/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/gas/explorer/adjustmask(user) + ..() + w_class = mask_adjusted ? WEIGHT_CLASS_SMALL : WEIGHT_CLASS_NORMAL + +/obj/item/clothing/mask/gas/explorer/folded/Initialize() + . = ..() + adjustmask() + +//Bane gas mask +/obj/item/clothing/mask/banemask + name = "bane mask" + desc = "Only when the station is in flames, do you have my permission to robust." + icon_state = "bane_mask" + flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE + flags_cover = MASKCOVERSMOUTH | MASKCOVERSEYES + w_class = WEIGHT_CLASS_NORMAL + item_state = "bane_mask" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + + +//Plague Dr suit can be found in clothing/suits/bio.dm +/obj/item/clothing/mask/gas/plaguedoctor + name = "plague doctor mask" + desc = "A modernised version of the classic design, this mask will not only filter out toxins but it can also be connected to an air supply." + icon_state = "plaguedoctor" + item_state = "gas_mask" + armor = list("melee" = 0, "bullet" = 0, "laser" = 2, "energy" = 2, "bomb" = 0, "bio" = 75, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/mask/gas/swat + name = "\improper SWAT mask" + desc = "A close-fitting tactical mask that can be connected to an air supply." + icon_state = "swat" + +/obj/item/clothing/mask/gas/syndicate + name = "syndicate mask" + desc = "A close-fitting tactical mask that can be connected to an air supply." + icon_state = "swat" + strip_delay = 60 + +/obj/item/clothing/mask/gas/clown_hat + name = "clown wig and mask" + desc = "A true prankster's facial attire. A clown is incomplete without his wig and mask." + icon_state = "clown" + item_state = "clown_hat" + flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT | BLOCKHAIR + resistance_flags = FLAMMABLE + dog_fashion = /datum/dog_fashion/head/clown + +/obj/item/clothing/mask/gas/clown_hat/attack_self(mob/user) + + var/mob/M = usr + var/list/options = list() + options["True Form"] = "clown" + options["The Feminist"] = "sexyclown" + options["The Madman"] = "joker" + options["The Rainbow Color"] ="rainbow" + + var/choice = input(M,"To what form do you wish to Morph this mask?","Morph Mask") in options + + if(src && choice && !M.stat && in_range(M,src)) + icon_state = options[choice] + to_chat(M, "Your Clown Mask has now morphed into [choice], all praise the Honk Mother!") + return 1 + +/obj/item/clothing/mask/gas/clown_hat/sexy + name = "sexy-clown wig and mask" + desc = "A feminine clown mask for the dabbling crossdressers or female entertainers." + icon_state = "sexyclown" + item_state = "sexyclown" + +/obj/item/clothing/mask/gas/clownwiz + name = "wizard clown wig and mask" + desc = "Some pranksters are truly magical." + icon_state = "wizzclown" + item_state = "wizzclown" + flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT | BLOCKHAIR + flags_inv = HIDEEARS | HIDEEYES + magical = TRUE + +/obj/item/clothing/mask/gas/clown_hat/nodrop + flags = NODROP + +/obj/item/clothing/mask/gas/mime + name = "mime mask" + desc = "The traditional mime's mask. It has an eerie facial posture." + icon_state = "mime" + item_state = "mime" + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/mime/nodrop + flags = NODROP + +/obj/item/clothing/mask/gas/monkeymask + name = "monkey mask" + desc = "A mask used when acting as a monkey." + icon_state = "monkeymask" + item_state = "monkeymask" + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/sexymime + name = "sexy mime mask" + desc = "A traditional female mime's mask." + icon_state = "sexymime" + item_state = "sexymime" + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/cyborg + name = "cyborg visor" + desc = "Beep boop" + icon_state = "death" + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/owl_mask + name = "owl mask" + desc = "Twoooo!" + icon_state = "owl" + resistance_flags = FLAMMABLE + actions_types = list(/datum/action/item_action/hoot) + +/obj/item/clothing/mask/gas/owl_mask/super_hero + flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT | NODROP + +/obj/item/clothing/mask/gas/owl_mask/attack_self() + hoot() + +/obj/item/clothing/mask/gas/owl_mask/proc/hoot() + if(cooldown < world.time - 35) // A cooldown, to stop people being jerks + playsound(src.loc, 'sound/creatures/hoot.ogg', 50, 1) + cooldown = world.time + +// ******************************************************************** + +// **** Security gas mask **** + +/obj/item/clothing/mask/gas/sechailer + name = "security gas mask" + desc = "A standard issue Security gas mask with integrated 'Compli-o-nator 3000' device, plays over a dozen pre-recorded compliance phrases designed to get scumbags to stand still whilst you taze them. Do not tamper with the device." + icon_state = "sechailer" + item_state = "sechailer" + var/phrase = 1 + var/aggressiveness = 1 + var/safety = 1 + actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/adjust, /datum/action/item_action/selectphrase) + var/phrase_list = list( + + "halt" = "HALT! HALT! HALT! HALT!", + "bobby" = "Stop in the name of the Law.", + "compliance" = "Compliance is in your best interest.", + "justice" = "Prepare for justice!", + "running" = "Running will only increase your sentence.", + "dontmove" = "Don't move, Creep!", + "floor" = "Down on the floor, Creep!", + "robocop" = "Dead or alive you're coming with me.", + "god" = "God made today for the crooks we could not catch yesterday.", + "freeze" = "Freeze, Scum Bag!", + "imperial" = "Stop right there, criminal scum!", + "bash" = "Stop or I'll bash you.", + "harry" = "Go ahead, make my day.", + "asshole" = "Stop breaking the law, asshole.", + "stfu" = "You have the right to shut the fuck up", + "shutup" = "Shut up crime!", + "super" = "Face the wrath of the golden bolt.", + "dredd" = "I am, the LAW!" + ) +/obj/item/clothing/mask/gas/sechailer/hos + name = "\improper HOS SWAT mask" + desc = "A close-fitting tactical mask with an especially aggressive Compli-o-nator 3000. It has a tan stripe." + icon_state = "hosmask" + aggressiveness = 3 + phrase = 12 + actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) + +/obj/item/clothing/mask/gas/sechailer/warden + name = "\improper Warden SWAT mask" + desc = "A close-fitting tactical mask with an especially aggressive Compli-o-nator 3000. It has a blue stripe." + icon_state = "wardenmask" + aggressiveness = 3 + phrase = 12 + actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) + + +/obj/item/clothing/mask/gas/sechailer/swat + name = "\improper SWAT mask" + desc = "A close-fitting tactical mask with an especially aggressive Compli-o-nator 3000." + icon_state = "officermask" + aggressiveness = 3 + phrase = 12 + actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) + +/obj/item/clothing/mask/gas/sechailer/blue + name = "\improper blue SWAT mask" + desc = "A neon blue swat mask, used for demoralizing Greytide in the wild." + icon_state = "blue_sechailer" + item_state = "blue_sechailer" + aggressiveness = 3 + phrase = 12 + actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) + +/obj/item/clothing/mask/gas/sechailer/cyborg + name = "security hailer" + desc = "A set of recognizable pre-recorded messages for cyborgs to use when apprehending criminals." + icon = 'icons/obj/device.dmi' + icon_state = "taperecorder_idle" + actions_types = list(/datum/action/item_action/halt, /datum/action/item_action/selectphrase) + +/obj/item/clothing/mask/gas/sechailer/ui_action_click(mob/user, actiontype) + if(actiontype == /datum/action/item_action/halt) + halt() + else if(actiontype == /datum/action/item_action/adjust) + adjustmask(user) + else if(actiontype == /datum/action/item_action/selectphrase) + var/key = phrase_list[phrase] + var/message = phrase_list[key] + + if (!safety) + to_chat(user, "You set the restrictor to: FUCK YOUR CUNT YOU SHIT EATING COCKSUCKER MAN EAT A DONG FUCKING ASS RAMMING SHIT FUCK EAT PENISES IN YOUR FUCK FACE AND SHIT OUT ABORTIONS OF FUCK AND DO SHIT IN YOUR ASS YOU COCK FUCK SHIT MONKEY FUCK ASS WANKER FROM THE DEPTHS OF SHIT.") + return + + switch(aggressiveness) + if(1) + phrase = (phrase < 6) ? (phrase + 1) : 1 + key = phrase_list[phrase] + message = phrase_list[key] + to_chat(user,"You set the restrictor to: [message]") + if(2) + phrase = (phrase < 11 && phrase >= 7) ? (phrase + 1) : 7 + key = phrase_list[phrase] + message = phrase_list[key] + to_chat(user,"You set the restrictor to: [message]") + if(3) + phrase = (phrase < 18 && phrase >= 12 ) ? (phrase + 1) : 12 + key = phrase_list[phrase] + message = phrase_list[key] + to_chat(user,"You set the restrictor to: [message]") + if(4) + phrase = (phrase < 18 && phrase >= 1 ) ? (phrase + 1) : 1 + key = phrase_list[phrase] + message = phrase_list[key] + to_chat(user,"You set the restrictor to: [message]") + else + to_chat(user, "It's broken.") + +/obj/item/clothing/mask/gas/sechailer/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/screwdriver)) + switch(aggressiveness) + if(1) + to_chat(user, "You set the aggressiveness restrictor to the second position.") + aggressiveness = 2 + phrase = 7 + if(2) + to_chat(user, "You set the aggressiveness restrictor to the third position.") + aggressiveness = 3 + phrase = 13 + if(3) + to_chat(user, "You set the aggressiveness restrictor to the fourth position.") + aggressiveness = 4 + phrase = 1 + if(4) + to_chat(user, "You set the aggressiveness restrictor to the first position.") + aggressiveness = 1 + phrase = 1 + if(5) + to_chat(user, "You adjust the restrictor but nothing happens, probably because its broken.") + else if(istype(W, /obj/item/wirecutters)) + if(aggressiveness != 5) + to_chat(user, "You broke it!") + aggressiveness = 5 + else + ..() + +/obj/item/clothing/mask/gas/sechailer/attack_self() + halt() + +/obj/item/clothing/mask/gas/sechailer/emag_act(mob/user as mob) + if(safety) + safety = 0 + to_chat(user, "You silently fry [src]'s vocal circuit with the cryptographic sequencer.") + else + return + +/obj/item/clothing/mask/gas/sechailer/proc/halt() + var/key = phrase_list[phrase] + var/message = phrase_list[key] + + + if(cooldown < world.time - 35) // A cooldown, to stop people being jerks + if(!safety) + message = "FUCK YOUR CUNT YOU SHIT EATING COCKSUCKER MAN EAT A DONG FUCKING ASS RAMMING SHIT FUCK EAT PENISES IN YOUR FUCK FACE AND SHIT OUT ABORTIONS OF FUCK AND DO SHIT IN YOUR ASS YOU COCK FUCK SHIT MONKEY FUCK ASS WANKER FROM THE DEPTHS OF SHIT." + usr.visible_message("[usr]'s Compli-o-Nator: [message]") + playsound(src.loc, 'sound/voice/binsult.ogg', 100, 0, 4) + cooldown = world.time + return + + usr.visible_message("[usr]'s Compli-o-Nator: [message]") + playsound(src.loc, "sound/voice/complionator/[key].ogg", 100, 0, 4) + cooldown = world.time + + + +// ******************************************************************** diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 9a45b7e888c..5cc891b56af 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -1,523 +1,523 @@ -/obj/item/clothing/mask/muzzle - name = "muzzle" - desc = "To stop that awful noise." - icon_state = "muzzle" - item_state = "muzzle" - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.90 - put_on_delay = 20 - var/resist_time = 0 //deciseconds of how long you need to gnaw to get rid of the gag, 0 to make it impossible to remove - var/mute = MUZZLE_MUTE_ALL - var/security_lock = FALSE // Requires brig access to remove 0 - Remove as normal - var/locked = FALSE //Indicates if a mask is locked, should always start as 0. - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi' - ) - -// Clumsy folks can't take the mask off themselves. -/obj/item/clothing/mask/muzzle/attack_hand(mob/user as mob) - if(user.wear_mask == src && !user.IsAdvancedToolUser()) - return 0 - else if(security_lock && locked) - if(do_unlock(user)) - visible_message("[user] unlocks [user.p_their()] [src.name].", \ - "[user] unlocks [user.p_their()] [src.name].") - ..() - return 1 - -/obj/item/clothing/mask/muzzle/proc/do_break() - if(security_lock) - security_lock = FALSE - locked = FALSE - flags &= ~NODROP - desc += " This one appears to be broken." - return TRUE - else - return FALSE - -/obj/item/clothing/mask/muzzle/proc/do_unlock(mob/living/carbon/human/user) - if(istype(user.get_inactive_hand(), /obj/item/card/emag)) - to_chat(user, "The lock vibrates as the card forces its locking system open.") - do_break() - return TRUE - else if(ACCESS_BRIG in user.get_access()) - to_chat(user, "The muzzle unlocks with a click.") - locked = FALSE - flags &= ~NODROP - return TRUE - - to_chat(user, "You must be wearing a security ID card or have one in your inactive hand to remove the muzzle.") - return FALSE - -/obj/item/clothing/mask/muzzle/proc/do_lock(mob/living/carbon/human/user) - if(security_lock) - locked = TRUE - flags |= NODROP - return TRUE - return FALSE - -/obj/item/clothing/mask/muzzle/Topic(href, href_list) - ..() - if(href_list["locked"]) - var/mob/living/carbon/wearer = locate(href_list["locked"]) - var/success = 0 - if(ishuman(usr)) - visible_message("[usr] tries to [locked ? "unlock" : "lock"] [wearer]'s [name].", \ - "[usr] tries to [locked ? "unlock" : "lock"] [wearer]'s [name].") - if(do_mob(usr, wearer, POCKET_STRIP_DELAY)) - if(locked) - success = do_unlock(usr) - else - success = do_lock(usr) - if(success) - visible_message("[usr] [locked ? "locks" : "unlocks"] [wearer]'s [name].", \ - "[usr] [locked ? "locks" : "unlocks"] [wearer]'s [name].") - if(usr.machine == wearer && in_range(src, usr)) - wearer.show_inv(usr) - else - to_chat(usr, "You lack the ability to manipulate the lock.") - - -/obj/item/clothing/mask/muzzle/tapegag - name = "tape gag" - desc = "MHPMHHH!" - icon_state = "tapegag" - item_state = null - w_class = WEIGHT_CLASS_TINY - resist_time = 150 - mute = MUZZLE_MUTE_MUFFLE - flags = DROPDEL - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi' - ) - -/obj/item/clothing/mask/muzzle/tapegag/dropped(mob/user) - var/obj/item/trash/tapetrash/TT = new - transfer_fingerprints_to(TT) - user.transfer_fingerprints_to(TT) - user.put_in_active_hand(TT) - playsound(src, 'sound/items/poster_ripped.ogg', 40, 1) - user.emote("scream") - ..() - -/obj/item/clothing/mask/muzzle/safety - name = "safety muzzle" - desc = "A muzzle designed to prevent biting." - icon_state = "muzzle_secure" - item_state = "muzzle_secure" - resist_time = 0 - mute = MUZZLE_MUTE_NONE - security_lock = TRUE - locked = FALSE - materials = list(MAT_METAL=500, MAT_GLASS=50) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi' - ) - -/obj/item/clothing/mask/muzzle/safety/shock - name = "shock muzzle" - desc = "A muzzle designed to prevent biting. This one is fitted with a behavior correction system." - var/obj/item/assembly/trigger = null - origin_tech = "materials=1;engineering=1" - materials = list(MAT_METAL=500, MAT_GLASS=50) - -/obj/item/clothing/mask/muzzle/safety/shock/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/assembly/signaler) || istype(W, /obj/item/assembly/voice)) - if(istype(trigger, /obj/item/assembly/signaler) || istype(trigger, /obj/item/assembly/voice)) - to_chat(user, "Something is already attached to [src].") - return FALSE - if(!user.drop_item()) - to_chat(user, "You are unable to insert [W] into [src].") - return FALSE - trigger = W - trigger.forceMove(src) - trigger.master = src - trigger.holder = src - to_chat(user, "You attach the [W] to [src].") - return TRUE - else if(istype(W, /obj/item/assembly)) - to_chat(user, "That won't fit in [src]. Perhaps a signaler or voice analyzer would?") - return FALSE - - return ..() - -/obj/item/clothing/mask/muzzle/safety/shock/screwdriver_act(mob/user, obj/item/I) - if(!trigger) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - to_chat(user, "You remove [trigger] from [src].") - trigger.forceMove(get_turf(user)) - trigger.master = null - trigger.holder = null - trigger = null - -/obj/item/clothing/mask/muzzle/safety/shock/proc/can_shock(obj/item/clothing/C) - if(istype(C)) - if(isliving(C.loc)) - return C.loc - else if(isliving(loc)) - return loc - return FALSE - -/obj/item/clothing/mask/muzzle/safety/shock/proc/process_activation(var/obj/D, var/normal = 1, var/special = 1) - visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*") - var/mob/M = can_shock(loc) - if(M) - to_chat(M, "You feel a sharp shock!") - do_sparks(3, 1, M) - - M.Weaken(5) - M.Stuttering(1) - M.Jitter(20) - return - -/obj/item/clothing/mask/muzzle/safety/shock/HasProximity(atom/movable/AM as mob|obj) - if(trigger) - trigger.HasProximity(AM) - - -/obj/item/clothing/mask/muzzle/safety/shock/hear_talk(mob/living/M as mob, list/message_pieces) - if(trigger) - trigger.hear_talk(M, message_pieces) - -/obj/item/clothing/mask/muzzle/safety/shock/hear_message(mob/living/M as mob, msg) - if(trigger) - trigger.hear_message(M, msg) - - - -/obj/item/clothing/mask/surgical - name = "sterile mask" - desc = "A sterile mask designed to help prevent the spread of diseases." - icon_state = "sterile" - item_state = "sterile" - w_class = WEIGHT_CLASS_TINY - flags_cover = MASKCOVERSMOUTH - gas_transfer_coefficient = 0.90 - permeability_coefficient = 0.01 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 25, "rad" = 0, "fire" = 0, "acid" = 0) - actions_types = list(/datum/action/item_action/adjust) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi' - ) - - -/obj/item/clothing/mask/surgical/attack_self(var/mob/user) - adjustmask(user) - -/obj/item/clothing/mask/fakemoustache - name = "completely real moustache" - desc = "moustache is totally real." - icon_state = "fake-moustache" - flags_inv = HIDEFACE - actions_types = list(/datum/action/item_action/pontificate) - dog_fashion = /datum/dog_fashion/head/not_ian - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi' - ) - -/obj/item/clothing/mask/fakemoustache/attack_self(mob/user) - pontificate(user) - -/obj/item/clothing/mask/fakemoustache/item_action_slot_check(slot) - if(slot == slot_wear_mask) - return 1 - -/obj/item/clothing/mask/fakemoustache/proc/pontificate(mob/user) - user.visible_message("\ [user] twirls [user.p_their()] moustache and laughs [pick("fiendishly","maniacally","diabolically","evilly")]!") - -//scarves (fit in in mask slot) - -/obj/item/clothing/mask/bluescarf - name = "blue neck scarf" - desc = "A blue neck scarf." - icon_state = "blueneckscarf" - item_state = "blueneckscarf" - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.90 - - -/obj/item/clothing/mask/redscarf - name = "red scarf" - desc = "A red and white checkered neck scarf." - icon_state = "redwhite_scarf" - item_state = "redwhite_scarf" - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.90 - -/obj/item/clothing/mask/greenscarf - name = "green scarf" - desc = "A green neck scarf." - icon_state = "green_scarf" - item_state = "green_scarf" - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.90 - -/obj/item/clothing/mask/ninjascarf - name = "ninja scarf" - desc = "A stealthy, dark scarf." - icon_state = "ninja_scarf" - item_state = "ninja_scarf" - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.90 - - -/obj/item/clothing/mask/pig - name = "pig mask" - desc = "A rubber pig mask." - icon_state = "pig" - item_state = "pig" - flags = BLOCKHAIR - flags_inv = HIDEFACE - w_class = WEIGHT_CLASS_SMALL - - -/obj/item/clothing/mask/horsehead - name = "horse head mask" - desc = "A mask made of soft vinyl and latex, representing the head of a horse." - icon_state = "horsehead" - item_state = "horsehead" - flags = BLOCKHAIR - flags_inv = HIDEFACE - w_class = WEIGHT_CLASS_SMALL - var/voicechange = 0 - var/temporaryname = " the Horse" - var/originalname = "" - - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi' - ) - -/obj/item/clothing/mask/horsehead/equipped(mob/user, slot) - if(flags & NODROP) //cursed masks only - originalname = user.real_name - if(!user.real_name || user.real_name == "Unknown") - user.real_name = "A Horse With No Name" //it felt good to be out of the rain - else - user.real_name = "[user.name][temporaryname]" - ..() - -/obj/item/clothing/mask/horsehead/dropped() //this really shouldn't happen, but call it extreme caution - if(flags & NODROP) - goodbye_horses(loc) - ..() - -/obj/item/clothing/mask/horsehead/Destroy() - if(flags & NODROP) - goodbye_horses(loc) - return ..() - -/obj/item/clothing/mask/horsehead/proc/goodbye_horses(mob/user) //I'm flying over you - if(!ismob(user)) - return - if(user.real_name == "[originalname][temporaryname]" || user.real_name == "A Horse With No Name") //if it's somehow changed while the mask is on it doesn't revert - user.real_name = originalname - -/obj/item/clothing/mask/face - flags_inv = HIDEFACE - flags_cover = MASKCOVERSMOUTH - -/obj/item/clothing/mask/face/rat - name = "rat mask" - desc = "A mask made of soft vinyl and latex, representing the head of a rat." - icon_state = "rat" - item_state = "rat" - -/obj/item/clothing/mask/face/fox - name = "fox mask" - desc = "A mask made of soft vinyl and latex, representing the head of a fox." - icon_state = "fox" - item_state = "fox" - -/obj/item/clothing/mask/face/bee - name = "bee mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bee." - icon_state = "bee" - item_state = "bee" - -/obj/item/clothing/mask/face/bear - name = "bear mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bear." - icon_state = "bear" - item_state = "bear" - -/obj/item/clothing/mask/face/bat - name = "bat mask" - desc = "A mask made of soft vinyl and latex, representing the head of a bat." - icon_state = "bat" - item_state = "bat" - -/obj/item/clothing/mask/face/raven - name = "raven mask" - desc = "A mask made of soft vinyl and latex, representing the head of a raven." - icon_state = "raven" - item_state = "raven" - -/obj/item/clothing/mask/face/jackal - name = "jackal mask" - desc = "A mask made of soft vinyl and latex, representing the head of a jackal." - icon_state = "jackal" - item_state = "jackal" - -/obj/item/clothing/mask/face/tribal - name = "tribal mask" - desc = "A mask carved out of wood, detailed carefully by hand." - icon_state = "bumba" - item_state = "bumba" - -/obj/item/clothing/mask/fawkes - name = "Guy Fawkes mask" - desc = "A mask designed to help you remember a specific date." - icon_state = "fawkes" - item_state = "fawkes" - flags_inv = HIDEFACE - w_class = WEIGHT_CLASS_SMALL - -/obj/item/clothing/mask/gas/clown_hat/pennywise - name = "Pennywise Mask" - desc = "It's the eater of worlds, and of children." - icon_state = "pennywise_mask" - item_state = "pennywise_mask" - - flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT | BLOCKHAIR - -// Bandanas -/obj/item/clothing/mask/bandana - name = "bandana" - desc = "A colorful bandana." - flags_inv = HIDEFACE - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_TINY - slot_flags = SLOT_MASK - adjusted_flags = SLOT_HEAD - icon_state = "bandbotany" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/mask.dmi', - "Unathi" = 'icons/mob/species/unathi/mask.dmi', - "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', - "Grey" = 'icons/mob/species/grey/mask.dmi', - "Drask" = 'icons/mob/species/drask/mask.dmi' - ) - actions_types = list(/datum/action/item_action/adjust) - -/obj/item/clothing/mask/bandana/attack_self(var/mob/user) - adjustmask(user) - -/obj/item/clothing/mask/bandana/red - name = "red bandana" - icon_state = "bandred" - item_color = "red" - desc = "It's a red bandana." - -/obj/item/clothing/mask/bandana/blue - name = "blue bandana" - icon_state = "bandblue" - item_color = "blue" - desc = "It's a blue bandana." - -/obj/item/clothing/mask/bandana/gold - name = "gold bandana" - icon_state = "bandgold" - item_color = "yellow" - desc = "It's a gold bandana." - -/obj/item/clothing/mask/bandana/green - name = "green bandana" - icon_state = "bandgreen" - item_color = "green" - desc = "It's a green bandana." - -/obj/item/clothing/mask/bandana/orange - name = "orange bandana" - icon_state = "bandorange" - item_color = "orange" - desc = "It's an orange bandana." - -/obj/item/clothing/mask/bandana/purple - name = "purple bandana" - icon_state = "bandpurple" - item_color = "purple" - desc = "It's a purple bandana." - -/obj/item/clothing/mask/bandana/botany - name = "botany bandana" - desc = "It's a green bandana with some fine nanotech lining." - icon_state = "bandbotany" - -/obj/item/clothing/mask/bandana/skull - name = "skull bandana" - desc = "It's a black bandana with a skull pattern." - icon_state = "bandskull" - -/obj/item/clothing/mask/bandana/black - name = "black bandana" - icon_state = "bandblack" - item_color = "black" - desc = "It's a black bandana." - -/obj/item/clothing/mask/bandana/durathread - name = "durathread bandana" - desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." - icon_state = "banddurathread" - -/obj/item/clothing/mask/cursedclown - name = "cursed clown mask" - desc = "This is a very, very odd looking mask." - icon = 'icons/goonstation/objects/clothing/mask.dmi' - icon_state = "cursedclown" - item_state = "cclown_hat" - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - icon_override = 'icons/goonstation/mob/clothing/mask.dmi' - lefthand_file = 'icons/goonstation/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/goonstation/mob/inhands/clothing_righthand.dmi' - flags = NODROP | AIRTIGHT - flags_cover = MASKCOVERSMOUTH - -/obj/item/clothing/mask/cursedclown/equipped(mob/user, slot) - ..() - var/mob/living/carbon/human/H = user - if(istype(H) && slot == slot_wear_mask) - to_chat(H, "[src] grips your face!") - if(H.mind && H.mind.assigned_role != "Cluwne") - H.makeCluwne() - -/obj/item/clothing/mask/cursedclown/suicide_act(mob/user) - user.visible_message("[user] gazes into the eyes of [src]. [src] gazes back!") - spawn(10) - if(user) - user.gib() - return OBLITERATION +/obj/item/clothing/mask/muzzle + name = "muzzle" + desc = "To stop that awful noise." + icon_state = "muzzle" + item_state = "muzzle" + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.90 + put_on_delay = 20 + var/resist_time = 0 //deciseconds of how long you need to gnaw to get rid of the gag, 0 to make it impossible to remove + var/mute = MUZZLE_MUTE_ALL + var/security_lock = FALSE // Requires brig access to remove 0 - Remove as normal + var/locked = FALSE //Indicates if a mask is locked, should always start as 0. + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi' + ) + +// Clumsy folks can't take the mask off themselves. +/obj/item/clothing/mask/muzzle/attack_hand(mob/user as mob) + if(user.wear_mask == src && !user.IsAdvancedToolUser()) + return 0 + else if(security_lock && locked) + if(do_unlock(user)) + visible_message("[user] unlocks [user.p_their()] [src.name].", \ + "[user] unlocks [user.p_their()] [src.name].") + ..() + return 1 + +/obj/item/clothing/mask/muzzle/proc/do_break() + if(security_lock) + security_lock = FALSE + locked = FALSE + flags &= ~NODROP + desc += " This one appears to be broken." + return TRUE + else + return FALSE + +/obj/item/clothing/mask/muzzle/proc/do_unlock(mob/living/carbon/human/user) + if(istype(user.get_inactive_hand(), /obj/item/card/emag)) + to_chat(user, "The lock vibrates as the card forces its locking system open.") + do_break() + return TRUE + else if(ACCESS_BRIG in user.get_access()) + to_chat(user, "The muzzle unlocks with a click.") + locked = FALSE + flags &= ~NODROP + return TRUE + + to_chat(user, "You must be wearing a security ID card or have one in your inactive hand to remove the muzzle.") + return FALSE + +/obj/item/clothing/mask/muzzle/proc/do_lock(mob/living/carbon/human/user) + if(security_lock) + locked = TRUE + flags |= NODROP + return TRUE + return FALSE + +/obj/item/clothing/mask/muzzle/Topic(href, href_list) + ..() + if(href_list["locked"]) + var/mob/living/carbon/wearer = locate(href_list["locked"]) + var/success = 0 + if(ishuman(usr)) + visible_message("[usr] tries to [locked ? "unlock" : "lock"] [wearer]'s [name].", \ + "[usr] tries to [locked ? "unlock" : "lock"] [wearer]'s [name].") + if(do_mob(usr, wearer, POCKET_STRIP_DELAY)) + if(locked) + success = do_unlock(usr) + else + success = do_lock(usr) + if(success) + visible_message("[usr] [locked ? "locks" : "unlocks"] [wearer]'s [name].", \ + "[usr] [locked ? "locks" : "unlocks"] [wearer]'s [name].") + if(usr.machine == wearer && in_range(src, usr)) + wearer.show_inv(usr) + else + to_chat(usr, "You lack the ability to manipulate the lock.") + + +/obj/item/clothing/mask/muzzle/tapegag + name = "tape gag" + desc = "MHPMHHH!" + icon_state = "tapegag" + item_state = null + w_class = WEIGHT_CLASS_TINY + resist_time = 150 + mute = MUZZLE_MUTE_MUFFLE + flags = DROPDEL + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi' + ) + +/obj/item/clothing/mask/muzzle/tapegag/dropped(mob/user) + var/obj/item/trash/tapetrash/TT = new + transfer_fingerprints_to(TT) + user.transfer_fingerprints_to(TT) + user.put_in_active_hand(TT) + playsound(src, 'sound/items/poster_ripped.ogg', 40, 1) + user.emote("scream") + ..() + +/obj/item/clothing/mask/muzzle/safety + name = "safety muzzle" + desc = "A muzzle designed to prevent biting." + icon_state = "muzzle_secure" + item_state = "muzzle_secure" + resist_time = 0 + mute = MUZZLE_MUTE_NONE + security_lock = TRUE + locked = FALSE + materials = list(MAT_METAL=500, MAT_GLASS=50) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi' + ) + +/obj/item/clothing/mask/muzzle/safety/shock + name = "shock muzzle" + desc = "A muzzle designed to prevent biting. This one is fitted with a behavior correction system." + var/obj/item/assembly/trigger = null + origin_tech = "materials=1;engineering=1" + materials = list(MAT_METAL=500, MAT_GLASS=50) + +/obj/item/clothing/mask/muzzle/safety/shock/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/assembly/signaler) || istype(W, /obj/item/assembly/voice)) + if(istype(trigger, /obj/item/assembly/signaler) || istype(trigger, /obj/item/assembly/voice)) + to_chat(user, "Something is already attached to [src].") + return FALSE + if(!user.drop_item()) + to_chat(user, "You are unable to insert [W] into [src].") + return FALSE + trigger = W + trigger.forceMove(src) + trigger.master = src + trigger.holder = src + to_chat(user, "You attach the [W] to [src].") + return TRUE + else if(istype(W, /obj/item/assembly)) + to_chat(user, "That won't fit in [src]. Perhaps a signaler or voice analyzer would?") + return FALSE + + return ..() + +/obj/item/clothing/mask/muzzle/safety/shock/screwdriver_act(mob/user, obj/item/I) + if(!trigger) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + to_chat(user, "You remove [trigger] from [src].") + trigger.forceMove(get_turf(user)) + trigger.master = null + trigger.holder = null + trigger = null + +/obj/item/clothing/mask/muzzle/safety/shock/proc/can_shock(obj/item/clothing/C) + if(istype(C)) + if(isliving(C.loc)) + return C.loc + else if(isliving(loc)) + return loc + return FALSE + +/obj/item/clothing/mask/muzzle/safety/shock/proc/process_activation(var/obj/D, var/normal = 1, var/special = 1) + visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*") + var/mob/M = can_shock(loc) + if(M) + to_chat(M, "You feel a sharp shock!") + do_sparks(3, 1, M) + + M.Weaken(5) + M.Stuttering(1) + M.Jitter(20) + return + +/obj/item/clothing/mask/muzzle/safety/shock/HasProximity(atom/movable/AM as mob|obj) + if(trigger) + trigger.HasProximity(AM) + + +/obj/item/clothing/mask/muzzle/safety/shock/hear_talk(mob/living/M as mob, list/message_pieces) + if(trigger) + trigger.hear_talk(M, message_pieces) + +/obj/item/clothing/mask/muzzle/safety/shock/hear_message(mob/living/M as mob, msg) + if(trigger) + trigger.hear_message(M, msg) + + + +/obj/item/clothing/mask/surgical + name = "sterile mask" + desc = "A sterile mask designed to help prevent the spread of diseases." + icon_state = "sterile" + item_state = "sterile" + w_class = WEIGHT_CLASS_TINY + flags_cover = MASKCOVERSMOUTH + gas_transfer_coefficient = 0.90 + permeability_coefficient = 0.01 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 25, "rad" = 0, "fire" = 0, "acid" = 0) + actions_types = list(/datum/action/item_action/adjust) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi' + ) + + +/obj/item/clothing/mask/surgical/attack_self(var/mob/user) + adjustmask(user) + +/obj/item/clothing/mask/fakemoustache + name = "completely real moustache" + desc = "moustache is totally real." + icon_state = "fake-moustache" + flags_inv = HIDEFACE + actions_types = list(/datum/action/item_action/pontificate) + dog_fashion = /datum/dog_fashion/head/not_ian + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi' + ) + +/obj/item/clothing/mask/fakemoustache/attack_self(mob/user) + pontificate(user) + +/obj/item/clothing/mask/fakemoustache/item_action_slot_check(slot) + if(slot == slot_wear_mask) + return 1 + +/obj/item/clothing/mask/fakemoustache/proc/pontificate(mob/user) + user.visible_message("\ [user] twirls [user.p_their()] moustache and laughs [pick("fiendishly","maniacally","diabolically","evilly")]!") + +//scarves (fit in in mask slot) + +/obj/item/clothing/mask/bluescarf + name = "blue neck scarf" + desc = "A blue neck scarf." + icon_state = "blueneckscarf" + item_state = "blueneckscarf" + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.90 + + +/obj/item/clothing/mask/redscarf + name = "red scarf" + desc = "A red and white checkered neck scarf." + icon_state = "redwhite_scarf" + item_state = "redwhite_scarf" + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.90 + +/obj/item/clothing/mask/greenscarf + name = "green scarf" + desc = "A green neck scarf." + icon_state = "green_scarf" + item_state = "green_scarf" + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.90 + +/obj/item/clothing/mask/ninjascarf + name = "ninja scarf" + desc = "A stealthy, dark scarf." + icon_state = "ninja_scarf" + item_state = "ninja_scarf" + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.90 + + +/obj/item/clothing/mask/pig + name = "pig mask" + desc = "A rubber pig mask." + icon_state = "pig" + item_state = "pig" + flags = BLOCKHAIR + flags_inv = HIDEFACE + w_class = WEIGHT_CLASS_SMALL + + +/obj/item/clothing/mask/horsehead + name = "horse head mask" + desc = "A mask made of soft vinyl and latex, representing the head of a horse." + icon_state = "horsehead" + item_state = "horsehead" + flags = BLOCKHAIR + flags_inv = HIDEFACE + w_class = WEIGHT_CLASS_SMALL + var/voicechange = 0 + var/temporaryname = " the Horse" + var/originalname = "" + + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi' + ) + +/obj/item/clothing/mask/horsehead/equipped(mob/user, slot) + if(flags & NODROP) //cursed masks only + originalname = user.real_name + if(!user.real_name || user.real_name == "Unknown") + user.real_name = "A Horse With No Name" //it felt good to be out of the rain + else + user.real_name = "[user.name][temporaryname]" + ..() + +/obj/item/clothing/mask/horsehead/dropped() //this really shouldn't happen, but call it extreme caution + if(flags & NODROP) + goodbye_horses(loc) + ..() + +/obj/item/clothing/mask/horsehead/Destroy() + if(flags & NODROP) + goodbye_horses(loc) + return ..() + +/obj/item/clothing/mask/horsehead/proc/goodbye_horses(mob/user) //I'm flying over you + if(!ismob(user)) + return + if(user.real_name == "[originalname][temporaryname]" || user.real_name == "A Horse With No Name") //if it's somehow changed while the mask is on it doesn't revert + user.real_name = originalname + +/obj/item/clothing/mask/face + flags_inv = HIDEFACE + flags_cover = MASKCOVERSMOUTH + +/obj/item/clothing/mask/face/rat + name = "rat mask" + desc = "A mask made of soft vinyl and latex, representing the head of a rat." + icon_state = "rat" + item_state = "rat" + +/obj/item/clothing/mask/face/fox + name = "fox mask" + desc = "A mask made of soft vinyl and latex, representing the head of a fox." + icon_state = "fox" + item_state = "fox" + +/obj/item/clothing/mask/face/bee + name = "bee mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bee." + icon_state = "bee" + item_state = "bee" + +/obj/item/clothing/mask/face/bear + name = "bear mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bear." + icon_state = "bear" + item_state = "bear" + +/obj/item/clothing/mask/face/bat + name = "bat mask" + desc = "A mask made of soft vinyl and latex, representing the head of a bat." + icon_state = "bat" + item_state = "bat" + +/obj/item/clothing/mask/face/raven + name = "raven mask" + desc = "A mask made of soft vinyl and latex, representing the head of a raven." + icon_state = "raven" + item_state = "raven" + +/obj/item/clothing/mask/face/jackal + name = "jackal mask" + desc = "A mask made of soft vinyl and latex, representing the head of a jackal." + icon_state = "jackal" + item_state = "jackal" + +/obj/item/clothing/mask/face/tribal + name = "tribal mask" + desc = "A mask carved out of wood, detailed carefully by hand." + icon_state = "bumba" + item_state = "bumba" + +/obj/item/clothing/mask/fawkes + name = "Guy Fawkes mask" + desc = "A mask designed to help you remember a specific date." + icon_state = "fawkes" + item_state = "fawkes" + flags_inv = HIDEFACE + w_class = WEIGHT_CLASS_SMALL + +/obj/item/clothing/mask/gas/clown_hat/pennywise + name = "Pennywise Mask" + desc = "It's the eater of worlds, and of children." + icon_state = "pennywise_mask" + item_state = "pennywise_mask" + + flags = BLOCK_GAS_SMOKE_EFFECT | AIRTIGHT | BLOCKHAIR + +// Bandanas +/obj/item/clothing/mask/bandana + name = "bandana" + desc = "A colorful bandana." + flags_inv = HIDEFACE + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_TINY + slot_flags = SLOT_MASK + adjusted_flags = SLOT_HEAD + icon_state = "bandbotany" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/mask.dmi', + "Unathi" = 'icons/mob/species/unathi/mask.dmi', + "Tajaran" = 'icons/mob/species/tajaran/mask.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/mask.dmi', + "Grey" = 'icons/mob/species/grey/mask.dmi', + "Drask" = 'icons/mob/species/drask/mask.dmi' + ) + actions_types = list(/datum/action/item_action/adjust) + +/obj/item/clothing/mask/bandana/attack_self(var/mob/user) + adjustmask(user) + +/obj/item/clothing/mask/bandana/red + name = "red bandana" + icon_state = "bandred" + item_color = "red" + desc = "It's a red bandana." + +/obj/item/clothing/mask/bandana/blue + name = "blue bandana" + icon_state = "bandblue" + item_color = "blue" + desc = "It's a blue bandana." + +/obj/item/clothing/mask/bandana/gold + name = "gold bandana" + icon_state = "bandgold" + item_color = "yellow" + desc = "It's a gold bandana." + +/obj/item/clothing/mask/bandana/green + name = "green bandana" + icon_state = "bandgreen" + item_color = "green" + desc = "It's a green bandana." + +/obj/item/clothing/mask/bandana/orange + name = "orange bandana" + icon_state = "bandorange" + item_color = "orange" + desc = "It's an orange bandana." + +/obj/item/clothing/mask/bandana/purple + name = "purple bandana" + icon_state = "bandpurple" + item_color = "purple" + desc = "It's a purple bandana." + +/obj/item/clothing/mask/bandana/botany + name = "botany bandana" + desc = "It's a green bandana with some fine nanotech lining." + icon_state = "bandbotany" + +/obj/item/clothing/mask/bandana/skull + name = "skull bandana" + desc = "It's a black bandana with a skull pattern." + icon_state = "bandskull" + +/obj/item/clothing/mask/bandana/black + name = "black bandana" + icon_state = "bandblack" + item_color = "black" + desc = "It's a black bandana." + +/obj/item/clothing/mask/bandana/durathread + name = "durathread bandana" + desc = "A bandana made from durathread, you wish it would provide some protection to its wearer, but it's far too thin..." + icon_state = "banddurathread" + +/obj/item/clothing/mask/cursedclown + name = "cursed clown mask" + desc = "This is a very, very odd looking mask." + icon = 'icons/goonstation/objects/clothing/mask.dmi' + icon_state = "cursedclown" + item_state = "cclown_hat" + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + icon_override = 'icons/goonstation/mob/clothing/mask.dmi' + lefthand_file = 'icons/goonstation/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/goonstation/mob/inhands/clothing_righthand.dmi' + flags = NODROP | AIRTIGHT + flags_cover = MASKCOVERSMOUTH + +/obj/item/clothing/mask/cursedclown/equipped(mob/user, slot) + ..() + var/mob/living/carbon/human/H = user + if(istype(H) && slot == slot_wear_mask) + to_chat(H, "[src] grips your face!") + if(H.mind && H.mind.assigned_role != "Cluwne") + H.makeCluwne() + +/obj/item/clothing/mask/cursedclown/suicide_act(mob/user) + user.visible_message("[user] gazes into the eyes of [src]. [src] gazes back!") + spawn(10) + if(user) + user.gib() + return OBLITERATION diff --git a/code/modules/clothing/patreon/glasses.dm b/code/modules/clothing/patreon/glasses.dm index f52d54f7df2..08d5d6b0af8 100644 --- a/code/modules/clothing/patreon/glasses.dm +++ b/code/modules/clothing/patreon/glasses.dm @@ -18,4 +18,4 @@ name = "Spiky Green-tinted Shades" desc = "Fight the power!" icon_state = "garm" - item_state = "garm" \ No newline at end of file + item_state = "garm" diff --git a/code/modules/clothing/patreon/hats.dm b/code/modules/clothing/patreon/hats.dm index abee0cb9466..8b769ba4150 100644 --- a/code/modules/clothing/patreon/hats.dm +++ b/code/modules/clothing/patreon/hats.dm @@ -60,4 +60,4 @@ sprite_sheets = list( "Vox" = 'icons/mob/species/vox/head.dmi' - ) \ No newline at end of file + ) diff --git a/code/modules/clothing/shoes/colour.dm b/code/modules/clothing/shoes/colour.dm index f864d3bf8f6..0bf54e8692f 100644 --- a/code/modules/clothing/shoes/colour.dm +++ b/code/modules/clothing/shoes/colour.dm @@ -1,106 +1,106 @@ -/obj/item/clothing/shoes/black - name = "black shoes" - icon_state = "black" - item_color = "black" - desc = "A pair of black shoes." - - cold_protection = FEET - min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT - heat_protection = FEET - max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT - - redcoat - item_color = "redcoat" //Exists for washing machines. Is not different from black shoes in any way. - -/obj/item/clothing/shoes/black/greytide - flags = NODROP - -/obj/item/clothing/shoes/brown - name = "brown shoes" - desc = "A pair of brown shoes." - icon_state = "brown" - - captain - item_color = "captain" //Exists for washing machines. Is not different from brown shoes in any way. - hop - item_color = "hop" //Exists for washing machines. Is not different from brown shoes in any way. - ce - item_color = "chief" //Exists for washing machines. Is not different from brown shoes in any way. - rd - item_color = "director" //Exists for washing machines. Is not different from brown shoes in any way. - cmo - item_color = "medical" //Exists for washing machines. Is not different from brown shoes in any way. - cmo - item_color = "cargo" //Exists for washing machines. Is not different from brown shoes in any way. - -/obj/item/clothing/shoes/blue - name = "blue shoes" - icon_state = "blue" - item_color = "blue" - -/obj/item/clothing/shoes/green - name = "green shoes" - icon_state = "green" - item_color = "green" - -/obj/item/clothing/shoes/yellow - name = "yellow shoes" - icon_state = "yellow" - item_color = "yellow" - -/obj/item/clothing/shoes/purple - name = "purple shoes" - icon_state = "purple" - item_color = "purple" - -/obj/item/clothing/shoes/brown - name = "brown shoes" - icon_state = "brown" - item_color = "brown" - -/obj/item/clothing/shoes/red - name = "red shoes" - desc = "Stylish red shoes." - icon_state = "red" - item_color = "red" - -/obj/item/clothing/shoes/white - name = "white shoes" - icon_state = "white" - permeability_coefficient = 0.01 - item_color = "white" - -/obj/item/clothing/shoes/leather - name = "leather shoes" - desc = "A sturdy pair of leather shoes." - icon_state = "leather" - item_color = "leather" - -/obj/item/clothing/shoes/rainbow - name = "rainbow shoes" - desc = "Very gay shoes." - icon_state = "rain_bow" - item_color = "rainbow" - -/obj/item/clothing/shoes/orange - name = "orange shoes" - icon_state = "orange" - item_color = "orange" - -/obj/item/clothing/shoes/orange/attack_self(mob/user as mob) - if(src.chained) - src.chained = null - src.slowdown = SHOES_SLOWDOWN - new /obj/item/restraints/handcuffs( user.loc ) - src.icon_state = "orange" - return - -/obj/item/clothing/shoes/orange/attackby(obj/H, loc, params) - ..() - if(istype(H, /obj/item/restraints/handcuffs) && !chained && !(H.flags & NODROP)) - if(src.icon_state != "orange") return - qdel(H) - src.chained = 1 - src.slowdown = 15 - src.icon_state = "orange1" - return +/obj/item/clothing/shoes/black + name = "black shoes" + icon_state = "black" + item_color = "black" + desc = "A pair of black shoes." + + cold_protection = FEET + min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT + heat_protection = FEET + max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT + + redcoat + item_color = "redcoat" //Exists for washing machines. Is not different from black shoes in any way. + +/obj/item/clothing/shoes/black/greytide + flags = NODROP + +/obj/item/clothing/shoes/brown + name = "brown shoes" + desc = "A pair of brown shoes." + icon_state = "brown" + + captain + item_color = "captain" //Exists for washing machines. Is not different from brown shoes in any way. + hop + item_color = "hop" //Exists for washing machines. Is not different from brown shoes in any way. + ce + item_color = "chief" //Exists for washing machines. Is not different from brown shoes in any way. + rd + item_color = "director" //Exists for washing machines. Is not different from brown shoes in any way. + cmo + item_color = "medical" //Exists for washing machines. Is not different from brown shoes in any way. + cmo + item_color = "cargo" //Exists for washing machines. Is not different from brown shoes in any way. + +/obj/item/clothing/shoes/blue + name = "blue shoes" + icon_state = "blue" + item_color = "blue" + +/obj/item/clothing/shoes/green + name = "green shoes" + icon_state = "green" + item_color = "green" + +/obj/item/clothing/shoes/yellow + name = "yellow shoes" + icon_state = "yellow" + item_color = "yellow" + +/obj/item/clothing/shoes/purple + name = "purple shoes" + icon_state = "purple" + item_color = "purple" + +/obj/item/clothing/shoes/brown + name = "brown shoes" + icon_state = "brown" + item_color = "brown" + +/obj/item/clothing/shoes/red + name = "red shoes" + desc = "Stylish red shoes." + icon_state = "red" + item_color = "red" + +/obj/item/clothing/shoes/white + name = "white shoes" + icon_state = "white" + permeability_coefficient = 0.01 + item_color = "white" + +/obj/item/clothing/shoes/leather + name = "leather shoes" + desc = "A sturdy pair of leather shoes." + icon_state = "leather" + item_color = "leather" + +/obj/item/clothing/shoes/rainbow + name = "rainbow shoes" + desc = "Very gay shoes." + icon_state = "rain_bow" + item_color = "rainbow" + +/obj/item/clothing/shoes/orange + name = "orange shoes" + icon_state = "orange" + item_color = "orange" + +/obj/item/clothing/shoes/orange/attack_self(mob/user as mob) + if(src.chained) + src.chained = null + src.slowdown = SHOES_SLOWDOWN + new /obj/item/restraints/handcuffs( user.loc ) + src.icon_state = "orange" + return + +/obj/item/clothing/shoes/orange/attackby(obj/H, loc, params) + ..() + if(istype(H, /obj/item/restraints/handcuffs) && !chained && !(H.flags & NODROP)) + if(src.icon_state != "orange") return + qdel(H) + src.chained = 1 + src.slowdown = 15 + src.icon_state = "orange1" + return diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index 7b0baef536f..916cb8e89d4 100644 --- a/code/modules/clothing/shoes/magboots.dm +++ b/code/modules/clothing/shoes/magboots.dm @@ -1,118 +1,118 @@ -/obj/item/clothing/shoes/magboots - desc = "Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle." - name = "magboots" - icon_state = "magboots0" - origin_tech = "materials=3;magnets=4;engineering=4" - var/magboot_state = "magboots" - var/magpulse = 0 - var/slowdown_active = 2 - var/slowdown_passive = SHOES_SLOWDOWN - var/magpulse_name = "mag-pulse traction system" - actions_types = list(/datum/action/item_action/toggle) - strip_delay = 70 - put_on_delay = 70 - resistance_flags = FIRE_PROOF - -/obj/item/clothing/shoes/magboots/attack_self(mob/user) - if(magpulse) - flags &= ~NOSLIP - slowdown = slowdown_passive - else - flags |= NOSLIP - slowdown = slowdown_active - magpulse = !magpulse - icon_state = "[magboot_state][magpulse]" - to_chat(user, "You [magpulse ? "enable" : "disable"] the [magpulse_name].") - user.update_inv_shoes() //so our mob-overlays update - user.update_gravity(user.mob_has_gravity()) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/clothing/shoes/magboots/negates_gravity() - return flags & NOSLIP - -/obj/item/clothing/shoes/magboots/examine(mob/user) - . = ..() - . += "Its [magpulse_name] appears to be [magpulse ? "enabled" : "disabled"]." - - -/obj/item/clothing/shoes/magboots/advance - desc = "Advanced magnetic boots that have a lighter magnetic pull, placing less burden on the wearer." - name = "advanced magboots" - icon_state = "advmag0" - magboot_state = "advmag" - slowdown_active = SHOES_SLOWDOWN - origin_tech = null - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/shoes/magboots/syndie - desc = "Reverse-engineered magnetic boots that have a heavy magnetic pull. Property of Gorlex Marauders." - name = "blood-red magboots" - icon_state = "syndiemag0" - magboot_state = "syndiemag" - origin_tech = "magnets=4;syndicate=2" - -obj/item/clothing/shoes/magboots/syndie/advance //For the Syndicate Strike Team - desc = "Reverse-engineered magboots that appear to be based on an advanced model, as they have a lighter magnetic pull. Property of Gorlex Marauders." - name = "advanced blood-red magboots" - slowdown_active = SHOES_SLOWDOWN - -/obj/item/clothing/shoes/magboots/clown - desc = "The prankster's standard-issue clowning shoes. Damn they're huge! There's a red light on the side." - name = "clown shoes" - icon_state = "clownmag0" - magboot_state = "clownmag" - item_state = "clown_shoes" - slowdown = SHOES_SLOWDOWN+1 - slowdown_active = SHOES_SLOWDOWN+1 - slowdown_passive = SHOES_SLOWDOWN+1 - magpulse_name = "honk-powered traction system" - item_color = "clown" - silence_steps = 1 - shoe_sound = "clownstep" - origin_tech = "magnets=4;syndicate=2" - var/enabled_waddle = TRUE - var/datum/component/waddle - -/obj/item/clothing/shoes/magboots/clown/equipped(mob/user, slot) - . = ..() - if(slot == slot_shoes && enabled_waddle) - waddle = user.AddComponent(/datum/component/waddling) - -/obj/item/clothing/shoes/magboots/clown/dropped(mob/user) - . = ..() - QDEL_NULL(waddle) - -/obj/item/clothing/shoes/magboots/clown/CtrlClick(mob/living/user) - if(!isliving(user)) - return - if(user.get_active_hand() != src) - to_chat(user, "You must hold [src] in your hand to do this.") - return - if(!enabled_waddle) - to_chat(user, "You switch off the waddle dampeners!") - enabled_waddle = TRUE - else - to_chat(user, "You switch on the waddle dampeners!") - enabled_waddle = FALSE - -/obj/item/clothing/shoes/magboots/wizard //bundled with the wiz hardsuit - name = "boots of gripping" - desc = "These magical boots, once activated, will stay gripped to any surface without slowing you down." - icon_state = "wizmag0" - magboot_state = "wizmag" - slowdown_active = SHOES_SLOWDOWN //wiz hardsuit already slows you down, no need to double it - magpulse_name = "gripping ability" - magical = TRUE - -/obj/item/clothing/shoes/magboots/wizard/attack_self(mob/user) - if(user) - if(user.mind in SSticker.mode.wizards) - if(magpulse) //faint blue light when shoes are turned on gives a reason to turn them off when not needed in maint - set_light(0) - else - set_light(2, 1, LIGHT_COLOR_LIGHTBLUE) - ..() - else - to_chat(user, "You poke the gem on [src]. Nothing happens.") \ No newline at end of file +/obj/item/clothing/shoes/magboots + desc = "Magnetic boots, often used during extravehicular activity to ensure the user remains safely attached to the vehicle." + name = "magboots" + icon_state = "magboots0" + origin_tech = "materials=3;magnets=4;engineering=4" + var/magboot_state = "magboots" + var/magpulse = 0 + var/slowdown_active = 2 + var/slowdown_passive = SHOES_SLOWDOWN + var/magpulse_name = "mag-pulse traction system" + actions_types = list(/datum/action/item_action/toggle) + strip_delay = 70 + put_on_delay = 70 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/shoes/magboots/attack_self(mob/user) + if(magpulse) + flags &= ~NOSLIP + slowdown = slowdown_passive + else + flags |= NOSLIP + slowdown = slowdown_active + magpulse = !magpulse + icon_state = "[magboot_state][magpulse]" + to_chat(user, "You [magpulse ? "enable" : "disable"] the [magpulse_name].") + user.update_inv_shoes() //so our mob-overlays update + user.update_gravity(user.mob_has_gravity()) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/clothing/shoes/magboots/negates_gravity() + return flags & NOSLIP + +/obj/item/clothing/shoes/magboots/examine(mob/user) + . = ..() + . += "Its [magpulse_name] appears to be [magpulse ? "enabled" : "disabled"]." + + +/obj/item/clothing/shoes/magboots/advance + desc = "Advanced magnetic boots that have a lighter magnetic pull, placing less burden on the wearer." + name = "advanced magboots" + icon_state = "advmag0" + magboot_state = "advmag" + slowdown_active = SHOES_SLOWDOWN + origin_tech = null + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/shoes/magboots/syndie + desc = "Reverse-engineered magnetic boots that have a heavy magnetic pull. Property of Gorlex Marauders." + name = "blood-red magboots" + icon_state = "syndiemag0" + magboot_state = "syndiemag" + origin_tech = "magnets=4;syndicate=2" + +obj/item/clothing/shoes/magboots/syndie/advance //For the Syndicate Strike Team + desc = "Reverse-engineered magboots that appear to be based on an advanced model, as they have a lighter magnetic pull. Property of Gorlex Marauders." + name = "advanced blood-red magboots" + slowdown_active = SHOES_SLOWDOWN + +/obj/item/clothing/shoes/magboots/clown + desc = "The prankster's standard-issue clowning shoes. Damn they're huge! There's a red light on the side." + name = "clown shoes" + icon_state = "clownmag0" + magboot_state = "clownmag" + item_state = "clown_shoes" + slowdown = SHOES_SLOWDOWN+1 + slowdown_active = SHOES_SLOWDOWN+1 + slowdown_passive = SHOES_SLOWDOWN+1 + magpulse_name = "honk-powered traction system" + item_color = "clown" + silence_steps = 1 + shoe_sound = "clownstep" + origin_tech = "magnets=4;syndicate=2" + var/enabled_waddle = TRUE + var/datum/component/waddle + +/obj/item/clothing/shoes/magboots/clown/equipped(mob/user, slot) + . = ..() + if(slot == slot_shoes && enabled_waddle) + waddle = user.AddComponent(/datum/component/waddling) + +/obj/item/clothing/shoes/magboots/clown/dropped(mob/user) + . = ..() + QDEL_NULL(waddle) + +/obj/item/clothing/shoes/magboots/clown/CtrlClick(mob/living/user) + if(!isliving(user)) + return + if(user.get_active_hand() != src) + to_chat(user, "You must hold [src] in your hand to do this.") + return + if(!enabled_waddle) + to_chat(user, "You switch off the waddle dampeners!") + enabled_waddle = TRUE + else + to_chat(user, "You switch on the waddle dampeners!") + enabled_waddle = FALSE + +/obj/item/clothing/shoes/magboots/wizard //bundled with the wiz hardsuit + name = "boots of gripping" + desc = "These magical boots, once activated, will stay gripped to any surface without slowing you down." + icon_state = "wizmag0" + magboot_state = "wizmag" + slowdown_active = SHOES_SLOWDOWN //wiz hardsuit already slows you down, no need to double it + magpulse_name = "gripping ability" + magical = TRUE + +/obj/item/clothing/shoes/magboots/wizard/attack_self(mob/user) + if(user) + if(user.mind in SSticker.mode.wizards) + if(magpulse) //faint blue light when shoes are turned on gives a reason to turn them off when not needed in maint + set_light(0) + else + set_light(2, 1, LIGHT_COLOR_LIGHTBLUE) + ..() + else + to_chat(user, "You poke the gem on [src]. Nothing happens.") diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index 9c7d8bac080..6858355f876 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -1,346 +1,346 @@ -/obj/item/clothing/shoes/mime - name = "mime shoes" - icon_state = "mime" - item_color = "mime" - -/obj/item/clothing/shoes/combat //basic syndicate combat boots for nuke ops and mob corpses - name = "combat boots" - desc = "High speed, low drag combat boots." - can_cut_open = 1 - icon_state = "jackboots" - item_state = "jackboots" - armor = list("melee" = 25, "bullet" = 25, "laser" = 25, "energy" = 25, "bomb" = 50, "bio" = 10, "rad" = 0, "fire" = 70, "acid" = 50) - strip_delay = 70 - resistance_flags = NONE - -/obj/item/clothing/shoes/combat/swat //overpowered boots for death squads - name = "\improper SWAT shoes" - desc = "High speed, no drag combat boots." - permeability_coefficient = 0.01 - armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 25, "bomb" = 50, "bio" = 30, "rad" = 30, "fire" = 90, "acid" = 50) - flags = NOSLIP - -/obj/item/clothing/shoes/sandal - desc = "A pair of rather plain, wooden sandals." - name = "sandals" - icon_state = "wizard" - strip_delay = 50 - put_on_delay = 50 - magical = TRUE - -/obj/item/clothing/shoes/sandal/marisa - desc = "A pair of magic, black shoes." - name = "magic shoes" - icon_state = "black" - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/shoes/sandal/magic - name = "magical sandals" - desc = "A pair of sandals imbued with magic." - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/shoes/galoshes - desc = "A pair of yellow rubber boots, designed to prevent slipping on wet surfaces." - name = "galoshes" - icon_state = "galoshes" - permeability_coefficient = 0.05 - flags = NOSLIP - slowdown = SHOES_SLOWDOWN+1 - strip_delay = 50 - put_on_delay = 50 - resistance_flags = NONE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 75) - -/obj/item/clothing/shoes/galoshes/dry - name = "absorbent galoshes" - desc = "A pair of purple rubber boots, designed to prevent slipping on wet surfaces while also drying them." - icon_state = "galoshes_dry" - -/obj/item/clothing/shoes/galoshes/dry/step_action() - var/turf/simulated/t_loc = get_turf(src) - if(istype(t_loc) && t_loc.wet) - t_loc.MakeDry(TURF_WET_WATER) - -/obj/item/clothing/shoes/clown_shoes - desc = "The prankster's standard-issue clowning shoes. Damn they're huge! Ctrl-click to toggle the waddle dampeners!" - name = "clown shoes" - icon_state = "clown" - item_state = "clown_shoes" - slowdown = SHOES_SLOWDOWN+1 - item_color = "clown" - var/footstep = 1 //used for squeeks whilst walking - shoe_sound = "clownstep" - var/enabled_waddle = TRUE - var/datum/component/waddle - -/obj/item/clothing/shoes/clown_shoes/equipped(mob/user, slot) - . = ..() - if(slot == slot_shoes && enabled_waddle) - waddle = user.AddComponent(/datum/component/waddling) - -/obj/item/clothing/shoes/clown_shoes/dropped(mob/user) - . = ..() - QDEL_NULL(waddle) - -/obj/item/clothing/shoes/clown_shoes/CtrlClick(mob/living/user) - if(!isliving(user)) - return - if(user.get_active_hand() != src) - to_chat(user, "You must hold [src] in your hand to do this.") - return - if(!enabled_waddle) - to_chat(user, "You switch off the waddle dampeners!") - enabled_waddle = TRUE - else - to_chat(user, "You switch on the waddle dampeners!") - enabled_waddle = FALSE - -/obj/item/clothing/shoes/clown_shoes/nodrop - flags = NODROP - -/obj/item/clothing/shoes/clown_shoes/magical - name = "magical clown shoes" - desc = "Standard-issue shoes of the wizarding class clown. Damn they're huge! And powerful! Somehow." - magical = TRUE - -/obj/item/clothing/shoes/jackboots - name = "jackboots" - desc = "Nanotrasen-issue Security combat boots for combat scenarios or combat situations. All combat, all the time." - can_cut_open = 1 - icon_state = "jackboots" - item_state = "jackboots" - item_color = "hosred" - strip_delay = 50 - put_on_delay = 50 - resistance_flags = NONE - var/footstep = 1 - shoe_sound = "jackboot" - -/obj/item/clothing/shoes/jackboots/jacksandals - name = "jacksandals" - desc = "Nanotrasen-issue Security combat sandals for combat scenarios. They're jacksandals, however that works." - can_cut_open = 0 - icon_state = "jacksandal" - item_color = "jacksandal" - -/obj/item/clothing/shoes/workboots - name = "work boots" - desc = "Thick-soled boots for industrial work environments." - can_cut_open = 1 - icon_state = "workboots" - -/obj/item/clothing/shoes/workboots/mining - name = "mining boots" - desc = "Steel-toed mining boots for mining in hazardous environments. Very good at keeping toes uncrushed." - icon_state = "explorer" - resistance_flags = FIRE_PROOF - -/obj/item/clothing/shoes/winterboots - name = "winter boots" - desc = "Boots lined with 'synthetic' animal fur." - can_cut_open = 1 - icon_state = "winterboots" - cold_protection = FEET|LEGS - min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT - heat_protection = FEET|LEGS - max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT - -/obj/item/clothing/shoes/cult - name = "boots" - desc = "A pair of boots worn by the followers of Nar-Sie." - icon_state = "cult" - item_state = "cult" - item_color = "cult" - - cold_protection = FEET - min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT - heat_protection = FEET - max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT - -/obj/item/clothing/shoes/cyborg - name = "cyborg boots" - desc = "Shoes for a cyborg costume" - icon_state = "boots" - -/obj/item/clothing/shoes/slippers - name = "bunny slippers" - desc = "Fluffy!" - icon_state = "slippers" - item_state = "slippers" - -/obj/item/clothing/shoes/slippers_worn - name = "worn bunny slippers" - desc = "Fluffy..." - icon_state = "slippers_worn" - item_state = "slippers_worn" - -/obj/item/clothing/shoes/laceup - name = "laceup shoes" - desc = "The height of fashion, and they're pre-polished!" - icon_state = "laceups" - put_on_delay = 50 - -/obj/item/clothing/shoes/roman - name = "roman sandals" - desc = "Sandals with buckled leather straps on it." - icon_state = "roman" - item_state = "roman" - strip_delay = 100 - put_on_delay = 100 - -/obj/item/clothing/shoes/centcom - name = "dress shoes" - desc = "They appear impeccably polished." - icon_state = "laceups" - -/obj/item/clothing/shoes/griffin - name = "griffon boots" - desc = "A pair of costume boots fashioned after bird talons." - icon_state = "griffinboots" - item_state = "griffinboots" - - -/obj/item/clothing/shoes/fluff/noble_boot - name = "noble boots" - desc = "The boots are economically designed to balance function and comfort, so that you can step on peasants without having to worry about blisters. The leather also resists unwanted blood stains." - icon_state = "noble_boot" - item_color = "noble_boot" - item_state = "noble_boot" - -/obj/item/clothing/shoes/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/stack/tape_roll) && !silence_steps) - var/obj/item/stack/tape_roll/TR = I - if((!silence_steps || shoe_sound) && TR.use(4)) - silence_steps = TRUE - shoe_sound = null - to_chat(user, "You tape the soles of [src] to silence your footsteps.") - else - return ..() - -/obj/item/clothing/shoes/sandal/white - name = "White Sandals" - desc = "Medical sandals that nerds wear." - icon_state = "medsandal" - item_color = "medsandal" - -/obj/item/clothing/shoes/sandal/fancy - name = "Fancy Sandals" - desc = "FANCY!!." - icon_state = "fancysandal" - item_color = "fancysandal" - -/obj/item/clothing/shoes/cursedclown - name = "cursed clown shoes" - desc = "Moldering clown flip flops. They're neon green for some reason." - icon = 'icons/goonstation/objects/clothing/feet.dmi' - icon_state = "cursedclown" - item_state = "cclown_shoes" - icon_override = 'icons/goonstation/mob/clothing/feet.dmi' - lefthand_file = 'icons/goonstation/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/goonstation/mob/inhands/clothing_righthand.dmi' - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - flags = NODROP - shoe_sound = "clownstep" - -/obj/item/clothing/shoes/singery - name = "yellow performer's boots" - desc = "These boots were made for dancing." - icon_state = "ysing" - put_on_delay = 50 - -/obj/item/clothing/shoes/singerb - name = "blue performer's boots" - desc = "These boots were made for dancing." - icon_state = "bsing" - put_on_delay = 50 - -/obj/item/clothing/shoes/cowboy - name = "cowboy boots" - desc = "A pair a' brown boots." - icon_state = "cowboy_brown" - item_color = "cowboy_brown" - -/obj/item/clothing/shoes/cowboy/black - name = "black cowboy boots" - desc = "A pair a' black rustlers' boots" - icon_state = "cowboy_black" - item_color = "cowboy_black" - -/obj/item/clothing/shoes/cowboy/white - name = "white cowboy boots" - desc = "For the rancher in us all." - icon_state = "cowboy_white" - item_color = "cowboy_white" - -/obj/item/clothing/shoes/cowboy/fancy - name = "bilton wrangler boots" - desc = "A pair of authentic haute couture boots from Japanifornia. You doubt they have ever been close to cattle." - icon_state = "cowboy_fancy" - item_color = "cowboy_fancy" - -/obj/item/clothing/shoes/cowboy/pink - name = "pink cowgirl boots" - desc = "For a Rustlin' tustlin' cowgirl." - icon_state = "cowboyboots_pink" - item_color = "cowboyboots_pink" - -/obj/item/clothing/shoes/cowboy/lizard - name = "lizard skin boots" - desc = "You can hear a faint hissing from inside the boots; you hope it is just a mournful ghost." - icon_state = "lizardboots_green" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 0) //lizards like to stay warm - -/obj/item/clothing/shoes/cowboy/lizardmasterwork - name = "\improper Hugs-The-Feet lizard skin boots" - desc = "A pair of masterfully crafted lizard skin boots. Finally a good application for the station's most bothersome inhabitants." - icon_state = "lizardboots_blue" - -/obj/effect/spawner/lootdrop/lizardboots - name = "random lizard boot quality" - desc = "Which ever gets picked, the lizard race loses" - icon = 'icons/obj/clothing/shoes.dmi' - icon_state = "lizardboots_green" - loot = list( - /obj/item/clothing/shoes/cowboy/lizard = 7, - /obj/item/clothing/shoes/cowboy/lizardmasterwork = 1) - -/obj/item/clothing/shoes/footwraps - name = "cloth footwraps" - desc = "A roll of treated canvas used for wrapping claws or paws." - icon_state = "clothwrap" - item_state = "clothwrap" - force = 0 - silence_steps = TRUE - w_class = WEIGHT_CLASS_SMALL - -/obj/item/clothing/shoes/bhop - name = "jump boots" - desc = "A specialized pair of combat boots with a built-in propulsion system for rapid foward movement." - icon_state = "jetboots" - item_state = "jetboots" - item_color = "hosred" - resistance_flags = FIRE_PROOF - actions_types = list(/datum/action/item_action/bhop) - permeability_coefficient = 0.05 - can_cut_open = FALSE - var/jumpdistance = 5 //-1 from to see the actual distance, e.g 4 goes over 3 tiles - var/jumpspeed = 3 - var/recharging_rate = 60 //default 6 seconds between each dash - var/recharging_time = 0 //time until next dash - -/obj/item/clothing/shoes/bhop/ui_action_click(mob/user, action) - if(!isliving(user)) - return - - if(recharging_time > world.time) - to_chat(user, "The boot's internal propulsion needs to recharge still!") - return - - var/atom/target = get_edge_target_turf(user, user.dir) //gets the user's direction - - if (user.throw_at(target, jumpdistance, jumpspeed, spin = FALSE, diagonals_first = TRUE)) - playsound(src, 'sound/effects/stealthoff.ogg', 50, 1, 1) - user.visible_message("[usr] dashes forward into the air!") - recharging_time = world.time + recharging_rate - else - to_chat(user, "Something prevents you from dashing forward!") \ No newline at end of file +/obj/item/clothing/shoes/mime + name = "mime shoes" + icon_state = "mime" + item_color = "mime" + +/obj/item/clothing/shoes/combat //basic syndicate combat boots for nuke ops and mob corpses + name = "combat boots" + desc = "High speed, low drag combat boots." + can_cut_open = 1 + icon_state = "jackboots" + item_state = "jackboots" + armor = list("melee" = 25, "bullet" = 25, "laser" = 25, "energy" = 25, "bomb" = 50, "bio" = 10, "rad" = 0, "fire" = 70, "acid" = 50) + strip_delay = 70 + resistance_flags = NONE + +/obj/item/clothing/shoes/combat/swat //overpowered boots for death squads + name = "\improper SWAT shoes" + desc = "High speed, no drag combat boots." + permeability_coefficient = 0.01 + armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 25, "bomb" = 50, "bio" = 30, "rad" = 30, "fire" = 90, "acid" = 50) + flags = NOSLIP + +/obj/item/clothing/shoes/sandal + desc = "A pair of rather plain, wooden sandals." + name = "sandals" + icon_state = "wizard" + strip_delay = 50 + put_on_delay = 50 + magical = TRUE + +/obj/item/clothing/shoes/sandal/marisa + desc = "A pair of magic, black shoes." + name = "magic shoes" + icon_state = "black" + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/shoes/sandal/magic + name = "magical sandals" + desc = "A pair of sandals imbued with magic." + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/shoes/galoshes + desc = "A pair of yellow rubber boots, designed to prevent slipping on wet surfaces." + name = "galoshes" + icon_state = "galoshes" + permeability_coefficient = 0.05 + flags = NOSLIP + slowdown = SHOES_SLOWDOWN+1 + strip_delay = 50 + put_on_delay = 50 + resistance_flags = NONE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 75) + +/obj/item/clothing/shoes/galoshes/dry + name = "absorbent galoshes" + desc = "A pair of purple rubber boots, designed to prevent slipping on wet surfaces while also drying them." + icon_state = "galoshes_dry" + +/obj/item/clothing/shoes/galoshes/dry/step_action() + var/turf/simulated/t_loc = get_turf(src) + if(istype(t_loc) && t_loc.wet) + t_loc.MakeDry(TURF_WET_WATER) + +/obj/item/clothing/shoes/clown_shoes + desc = "The prankster's standard-issue clowning shoes. Damn they're huge! Ctrl-click to toggle the waddle dampeners!" + name = "clown shoes" + icon_state = "clown" + item_state = "clown_shoes" + slowdown = SHOES_SLOWDOWN+1 + item_color = "clown" + var/footstep = 1 //used for squeeks whilst walking + shoe_sound = "clownstep" + var/enabled_waddle = TRUE + var/datum/component/waddle + +/obj/item/clothing/shoes/clown_shoes/equipped(mob/user, slot) + . = ..() + if(slot == slot_shoes && enabled_waddle) + waddle = user.AddComponent(/datum/component/waddling) + +/obj/item/clothing/shoes/clown_shoes/dropped(mob/user) + . = ..() + QDEL_NULL(waddle) + +/obj/item/clothing/shoes/clown_shoes/CtrlClick(mob/living/user) + if(!isliving(user)) + return + if(user.get_active_hand() != src) + to_chat(user, "You must hold [src] in your hand to do this.") + return + if(!enabled_waddle) + to_chat(user, "You switch off the waddle dampeners!") + enabled_waddle = TRUE + else + to_chat(user, "You switch on the waddle dampeners!") + enabled_waddle = FALSE + +/obj/item/clothing/shoes/clown_shoes/nodrop + flags = NODROP + +/obj/item/clothing/shoes/clown_shoes/magical + name = "magical clown shoes" + desc = "Standard-issue shoes of the wizarding class clown. Damn they're huge! And powerful! Somehow." + magical = TRUE + +/obj/item/clothing/shoes/jackboots + name = "jackboots" + desc = "Nanotrasen-issue Security combat boots for combat scenarios or combat situations. All combat, all the time." + can_cut_open = 1 + icon_state = "jackboots" + item_state = "jackboots" + item_color = "hosred" + strip_delay = 50 + put_on_delay = 50 + resistance_flags = NONE + var/footstep = 1 + shoe_sound = "jackboot" + +/obj/item/clothing/shoes/jackboots/jacksandals + name = "jacksandals" + desc = "Nanotrasen-issue Security combat sandals for combat scenarios. They're jacksandals, however that works." + can_cut_open = 0 + icon_state = "jacksandal" + item_color = "jacksandal" + +/obj/item/clothing/shoes/workboots + name = "work boots" + desc = "Thick-soled boots for industrial work environments." + can_cut_open = 1 + icon_state = "workboots" + +/obj/item/clothing/shoes/workboots/mining + name = "mining boots" + desc = "Steel-toed mining boots for mining in hazardous environments. Very good at keeping toes uncrushed." + icon_state = "explorer" + resistance_flags = FIRE_PROOF + +/obj/item/clothing/shoes/winterboots + name = "winter boots" + desc = "Boots lined with 'synthetic' animal fur." + can_cut_open = 1 + icon_state = "winterboots" + cold_protection = FEET|LEGS + min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT + heat_protection = FEET|LEGS + max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT + +/obj/item/clothing/shoes/cult + name = "boots" + desc = "A pair of boots worn by the followers of Nar-Sie." + icon_state = "cult" + item_state = "cult" + item_color = "cult" + + cold_protection = FEET + min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT + heat_protection = FEET + max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT + +/obj/item/clothing/shoes/cyborg + name = "cyborg boots" + desc = "Shoes for a cyborg costume" + icon_state = "boots" + +/obj/item/clothing/shoes/slippers + name = "bunny slippers" + desc = "Fluffy!" + icon_state = "slippers" + item_state = "slippers" + +/obj/item/clothing/shoes/slippers_worn + name = "worn bunny slippers" + desc = "Fluffy..." + icon_state = "slippers_worn" + item_state = "slippers_worn" + +/obj/item/clothing/shoes/laceup + name = "laceup shoes" + desc = "The height of fashion, and they're pre-polished!" + icon_state = "laceups" + put_on_delay = 50 + +/obj/item/clothing/shoes/roman + name = "roman sandals" + desc = "Sandals with buckled leather straps on it." + icon_state = "roman" + item_state = "roman" + strip_delay = 100 + put_on_delay = 100 + +/obj/item/clothing/shoes/centcom + name = "dress shoes" + desc = "They appear impeccably polished." + icon_state = "laceups" + +/obj/item/clothing/shoes/griffin + name = "griffon boots" + desc = "A pair of costume boots fashioned after bird talons." + icon_state = "griffinboots" + item_state = "griffinboots" + + +/obj/item/clothing/shoes/fluff/noble_boot + name = "noble boots" + desc = "The boots are economically designed to balance function and comfort, so that you can step on peasants without having to worry about blisters. The leather also resists unwanted blood stains." + icon_state = "noble_boot" + item_color = "noble_boot" + item_state = "noble_boot" + +/obj/item/clothing/shoes/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/stack/tape_roll) && !silence_steps) + var/obj/item/stack/tape_roll/TR = I + if((!silence_steps || shoe_sound) && TR.use(4)) + silence_steps = TRUE + shoe_sound = null + to_chat(user, "You tape the soles of [src] to silence your footsteps.") + else + return ..() + +/obj/item/clothing/shoes/sandal/white + name = "White Sandals" + desc = "Medical sandals that nerds wear." + icon_state = "medsandal" + item_color = "medsandal" + +/obj/item/clothing/shoes/sandal/fancy + name = "Fancy Sandals" + desc = "FANCY!!." + icon_state = "fancysandal" + item_color = "fancysandal" + +/obj/item/clothing/shoes/cursedclown + name = "cursed clown shoes" + desc = "Moldering clown flip flops. They're neon green for some reason." + icon = 'icons/goonstation/objects/clothing/feet.dmi' + icon_state = "cursedclown" + item_state = "cclown_shoes" + icon_override = 'icons/goonstation/mob/clothing/feet.dmi' + lefthand_file = 'icons/goonstation/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/goonstation/mob/inhands/clothing_righthand.dmi' + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + flags = NODROP + shoe_sound = "clownstep" + +/obj/item/clothing/shoes/singery + name = "yellow performer's boots" + desc = "These boots were made for dancing." + icon_state = "ysing" + put_on_delay = 50 + +/obj/item/clothing/shoes/singerb + name = "blue performer's boots" + desc = "These boots were made for dancing." + icon_state = "bsing" + put_on_delay = 50 + +/obj/item/clothing/shoes/cowboy + name = "cowboy boots" + desc = "A pair a' brown boots." + icon_state = "cowboy_brown" + item_color = "cowboy_brown" + +/obj/item/clothing/shoes/cowboy/black + name = "black cowboy boots" + desc = "A pair a' black rustlers' boots" + icon_state = "cowboy_black" + item_color = "cowboy_black" + +/obj/item/clothing/shoes/cowboy/white + name = "white cowboy boots" + desc = "For the rancher in us all." + icon_state = "cowboy_white" + item_color = "cowboy_white" + +/obj/item/clothing/shoes/cowboy/fancy + name = "bilton wrangler boots" + desc = "A pair of authentic haute couture boots from Japanifornia. You doubt they have ever been close to cattle." + icon_state = "cowboy_fancy" + item_color = "cowboy_fancy" + +/obj/item/clothing/shoes/cowboy/pink + name = "pink cowgirl boots" + desc = "For a Rustlin' tustlin' cowgirl." + icon_state = "cowboyboots_pink" + item_color = "cowboyboots_pink" + +/obj/item/clothing/shoes/cowboy/lizard + name = "lizard skin boots" + desc = "You can hear a faint hissing from inside the boots; you hope it is just a mournful ghost." + icon_state = "lizardboots_green" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 0) //lizards like to stay warm + +/obj/item/clothing/shoes/cowboy/lizardmasterwork + name = "\improper Hugs-The-Feet lizard skin boots" + desc = "A pair of masterfully crafted lizard skin boots. Finally a good application for the station's most bothersome inhabitants." + icon_state = "lizardboots_blue" + +/obj/effect/spawner/lootdrop/lizardboots + name = "random lizard boot quality" + desc = "Which ever gets picked, the lizard race loses" + icon = 'icons/obj/clothing/shoes.dmi' + icon_state = "lizardboots_green" + loot = list( + /obj/item/clothing/shoes/cowboy/lizard = 7, + /obj/item/clothing/shoes/cowboy/lizardmasterwork = 1) + +/obj/item/clothing/shoes/footwraps + name = "cloth footwraps" + desc = "A roll of treated canvas used for wrapping claws or paws." + icon_state = "clothwrap" + item_state = "clothwrap" + force = 0 + silence_steps = TRUE + w_class = WEIGHT_CLASS_SMALL + +/obj/item/clothing/shoes/bhop + name = "jump boots" + desc = "A specialized pair of combat boots with a built-in propulsion system for rapid foward movement." + icon_state = "jetboots" + item_state = "jetboots" + item_color = "hosred" + resistance_flags = FIRE_PROOF + actions_types = list(/datum/action/item_action/bhop) + permeability_coefficient = 0.05 + can_cut_open = FALSE + var/jumpdistance = 5 //-1 from to see the actual distance, e.g 4 goes over 3 tiles + var/jumpspeed = 3 + var/recharging_rate = 60 //default 6 seconds between each dash + var/recharging_time = 0 //time until next dash + +/obj/item/clothing/shoes/bhop/ui_action_click(mob/user, action) + if(!isliving(user)) + return + + if(recharging_time > world.time) + to_chat(user, "The boot's internal propulsion needs to recharge still!") + return + + var/atom/target = get_edge_target_turf(user, user.dir) //gets the user's direction + + if (user.throw_at(target, jumpdistance, jumpspeed, spin = FALSE, diagonals_first = TRUE)) + playsound(src, 'sound/effects/stealthoff.ogg', 50, 1, 1) + user.visible_message("[usr] dashes forward into the air!") + recharging_time = world.time + recharging_rate + else + to_chat(user, "Something prevents you from dashing forward!") diff --git a/code/modules/clothing/spacesuits/ert.dm b/code/modules/clothing/spacesuits/ert.dm index a97752a29db..225b32e59fe 100644 --- a/code/modules/clothing/spacesuits/ert.dm +++ b/code/modules/clothing/spacesuits/ert.dm @@ -246,4 +246,4 @@ icon_state = "hardsuit-berserker" helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/berserker armor = list(melee = 65, bullet = 50, laser = 50, energy = 50, bomb = 50, bio = 100, rad = 100, fire = 80, acid = 80) - slowdown = 0 \ No newline at end of file + slowdown = 0 diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index f9366e12778..5a01c9f6f55 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -1,590 +1,590 @@ -//Baseline hardsuits -/obj/item/clothing/head/helmet/space/hardsuit - name = "hardsuit helmet" - desc = "A special helmet designed for work in a hazardous, low-pressure environment." - icon_state = "hardsuit0-engineering" - item_state = "eng_helm" - max_integrity = 300 - armor = list("melee" = 10, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 50, "acid" = 75) - var/basestate = "hardsuit" - allowed = list(/obj/item/flashlight) - var/brightness_on = 4 //luminosity when on - var/on = FALSE - var/obj/item/clothing/suit/space/hardsuit/suit - item_color = "engineering" //Determines used sprites: hardsuit[on]-[color] and hardsuit[on]-[color]2 (lying down sprite) - actions_types = list(/datum/action/item_action/toggle_helmet_light) - - //Species-specific stuff. - species_restricted = list("exclude","Wryn") - sprite_sheets = list( - "Unathi" = 'icons/mob/species/unathi/helmet.dmi', - "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', - "Skrell" = 'icons/mob/species/skrell/helmet.dmi', - "Vox" = 'icons/mob/species/vox/helmet.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', - "Drask" = 'icons/mob/species/drask/helmet.dmi', - "Grey" = 'icons/mob/species/grey/helmet.dmi' - ) - sprite_sheets_obj = list( - "Unathi" = 'icons/obj/clothing/species/unathi/hats.dmi', - "Tajaran" = 'icons/obj/clothing/species/tajaran/hats.dmi', - "Skrell" = 'icons/obj/clothing/species/skrell/hats.dmi', - "Vox" = 'icons/obj/clothing/species/vox/hats.dmi', - "Vulpkanin" = 'icons/obj/clothing/species/vulpkanin/hats.dmi' - ) - -/obj/item/clothing/head/helmet/space/hardsuit/attack_self(mob/user) - toggle_light(user) - -/obj/item/clothing/head/helmet/space/hardsuit/proc/toggle_light(mob/user) - on = !on - icon_state = "[basestate][on]-[item_color]" - - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_head() - - if(on) - set_light(brightness_on) - else - set_light(0) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/clothing/head/helmet/space/hardsuit/extinguish_light() - if(on) - toggle_light() - visible_message("[src]'s light fades and turns off.") - -/obj/item/clothing/head/helmet/space/hardsuit/dropped(mob/user) - ..() - if(suit) - suit.RemoveHelmet() - -/obj/item/clothing/head/helmet/space/hardsuit/item_action_slot_check(slot) - if(slot == slot_head) - return 1 - -/obj/item/clothing/head/helmet/space/hardsuit/equipped(mob/user, slot) - ..() - if(slot != slot_head) - if(suit) - suit.RemoveHelmet() - else - qdel(src) - -/obj/item/clothing/head/helmet/space/hardsuit/proc/display_visor_message(var/msg) - var/mob/wearer = loc - if(msg && ishuman(wearer)) - wearer.show_message("[msg]", 1) - -/obj/item/clothing/head/helmet/space/hardsuit/emp_act(severity) - ..() - display_visor_message("[severity > 1 ? "Light" : "Strong"] electromagnetic pulse detected!") - -/obj/item/clothing/suit/space/hardsuit - name = "hardsuit" - desc = "A special space suit for environments that might pose hazards beyond just the vacuum of space. Provides more protection than a standard space suit." - icon_state = "hardsuit-engineering" - item_state = "eng_hardsuit" - max_integrity = 300 - armor = list("melee" = 10, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 50, "acid" = 75) - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/t_scanner, /obj/item/rcd, /obj/item/rpd) - siemens_coefficient = 0 - var/obj/item/clothing/head/helmet/space/hardsuit/helmet - actions_types = list(/datum/action/item_action/toggle_helmet) - var/helmettype = /obj/item/clothing/head/helmet/space/hardsuit - var/obj/item/tank/jetpack/suit/jetpack = null - - hide_tail_by_species = list("Vox" , "Vulpkanin" , "Unathi" , "Tajaran") - species_restricted = list("exclude", "Wryn") - sprite_sheets = list( - "Unathi" = 'icons/mob/species/unathi/suit.dmi', - "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', - "Skrell" = 'icons/mob/species/skrell/suit.dmi', - "Vox" = 'icons/mob/species/vox/suit.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', - "Drask" = 'icons/mob/species/drask/suit.dmi' - ) - sprite_sheets_obj = list( - "Unathi" = 'icons/obj/clothing/species/unathi/suits.dmi', - "Tajaran" = 'icons/obj/clothing/species/tajaran/suits.dmi', - "Skrell" = 'icons/obj/clothing/species/skrell/suits.dmi', - "Vox" = 'icons/obj/clothing/species/vox/suits.dmi', - "Vulpkanin" = 'icons/obj/clothing/species/vulpkanin/suits.dmi' - ) - -/obj/item/clothing/suit/space/hardsuit/New() - if(jetpack && ispath(jetpack)) - jetpack = new jetpack(src) - ..() - -/obj/item/clothing/suit/space/hardsuit/attack_self(mob/user) - user.changeNext_move(CLICK_CD_MELEE) - ..() - -/obj/item/clothing/suit/space/hardsuit/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/tank/jetpack/suit)) - if(jetpack) - to_chat(user, "[src] already has a jetpack installed.") - return - if(src == user.get_item_by_slot(slot_wear_suit)) //Make sure the player is not wearing the suit before applying the upgrade. - to_chat(user, "You cannot install the upgrade to [src] while wearing it.") - return - - if(user.unEquip(I)) - I.forceMove(src) - jetpack = I - to_chat(user, "You successfully install the jetpack into [src].") - return - return ..() - -/obj/item/clothing/suit/space/hardsuit/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(!jetpack) - to_chat(user, "[src] has no jetpack installed.") - return - if(src == user.get_item_by_slot(slot_wear_suit)) - to_chat(user, "You cannot remove the jetpack from [src] while wearing it.") - return - jetpack.turn_off(user) - jetpack.forceMove(drop_location()) - jetpack = null - to_chat(user, "You successfully remove the jetpack from [src].") - -/obj/item/clothing/suit/space/hardsuit/equipped(mob/user, slot) - ..() - if(jetpack) - if(slot == slot_wear_suit) - for(var/X in jetpack.actions) - var/datum/action/A = X - A.Grant(user) - -/obj/item/clothing/suit/space/hardsuit/dropped(mob/user) - ..() - if(jetpack) - for(var/X in jetpack.actions) - var/datum/action/A = X - A.Remove(user) - -/obj/item/clothing/suit/space/hardsuit/item_action_slot_check(slot) - if(slot == slot_wear_suit) //we only give the mob the ability to toggle the helmet if he's wearing the hardsuit. - return 1 - -//Engineering hardsuit -/obj/item/clothing/head/helmet/space/hardsuit/engine - name = "engineering hardsuit helmet" - desc = "A special helmet designed for work in a hazardous, low-pressure environment. Has radiation shielding." - icon_state = "hardsuit0-engineering" - item_state = "eng_helm" - armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 100, "acid" = 75) - resistance_flags = FIRE_PROOF - item_color = "engineering" - -/obj/item/clothing/suit/space/hardsuit/engine - name = "engineering hardsuit" - desc = "A special suit that protects against hazardous, low pressure environments. Has radiation shielding." - icon_state = "hardsuit-engineering" - item_state = "eng_hardsuit" - armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 100, "acid" = 75) - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine - dog_fashion = /datum/dog_fashion/back/hardsuit - resistance_flags = FIRE_PROOF - -//Atmospherics -/obj/item/clothing/head/helmet/space/hardsuit/engine/atmos - name = "atmospherics hardsuit helmet" - desc = "A special helmet designed for work in a hazardous, low-pressure environment. Has thermal shielding." - icon_state = "hardsuit0-atmos" - item_state = "atmos_helm" - item_color = "atmos" - armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 25, "fire" = 100, "acid" = 75) - heat_protection = HEAD //Uncomment to enable firesuit protection - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - -/obj/item/clothing/suit/space/hardsuit/engine/atmos - name = "atmospherics hardsuit" - desc = "A special suit that protects against hazardous, low pressure environments. Has thermal shielding." - icon_state = "hardsuit-atmos" - item_state = "atmos_hardsuit" - armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 25, "fire" = 100, "acid" = 75) - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS //Uncomment to enable firesuit protection - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine/atmos - dog_fashion = null - -//Chief Engineer's hardsuit -/obj/item/clothing/head/helmet/space/hardsuit/engine/elite - name = "advanced hardsuit helmet" - desc = "An advanced helmet designed for work in a hazardous, low pressure environment. Shines with a high polish." - icon_state = "hardsuit0-white" - item_state = "ce_helm" - item_color = "white" - armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 90) - heat_protection = HEAD //Uncomment to enable firesuit protection - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - -/obj/item/clothing/suit/space/hardsuit/engine/elite - icon_state = "hardsuit-white" - name = "advanced hardsuit" - desc = "An advanced suit that protects against hazardous, low pressure environments. Shines with a high polish." - item_state = "ce_hardsuit" - armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 90, "fire" = 100, "acid" = 90) - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS //Uncomment to enable firesuit protection - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine/elite - jetpack = /obj/item/tank/jetpack/suit - dog_fashion = null - -//Mining hardsuit -/obj/item/clothing/head/helmet/space/hardsuit/mining - name = "mining hardsuit helmet" - desc = "A special helmet designed for work in a hazardous, low pressure environment. Has reinforced plating." - icon_state = "hardsuit0-mining" - item_state = "mining_helm" - item_color = "mining" - max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF - heat_protection = HEAD - armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 75) - brightness_on = 7 - -/obj/item/clothing/suit/space/hardsuit/mining - icon_state = "hardsuit-mining" - name = "mining hardsuit" - desc = "A special suit that protects against hazardous, low pressure environments. Has reinforced plating." - item_state = "mining_hardsuit" - max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF - armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 75) - allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/storage/bag/ore, /obj/item/pickaxe, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator) - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/mining - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - -//Syndicate hardsuit -/obj/item/clothing/head/helmet/space/hardsuit/syndi - name = "blood-red hardsuit helmet" - desc = "A dual-mode advanced helmet designed for work in special operations. It is in travel mode. Property of Gorlex Marauders." - alt_desc = "A dual-mode advanced helmet designed for work in special operations. It is in combat mode. Property of Gorlex Marauders." - icon_state = "hardsuit1-syndi" - item_state = "syndie_helm" - item_color = "syndi" - armor = list("melee" = 40, "bullet" = 50, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 90) - on = 1 - var/obj/item/clothing/suit/space/hardsuit/syndi/linkedsuit = null - actions_types = list(/datum/action/item_action/toggle_helmet_mode) - visor_flags_inv = HIDEMASK|HIDEEYES|HIDEFACE|HIDETAIL - visor_flags = STOPSPRESSUREDMAGE - -/obj/item/clothing/head/helmet/space/hardsuit/syndi/update_icon() - icon_state = "hardsuit[on]-[item_color]" - -/obj/item/clothing/head/helmet/space/hardsuit/syndi/New() - ..() - if(istype(loc, /obj/item/clothing/suit/space/hardsuit/syndi)) - linkedsuit = loc - -/obj/item/clothing/head/helmet/space/hardsuit/syndi/attack_self(mob/user) //Toggle Helmet - if(!isturf(user.loc)) - to_chat(user, "You cannot toggle your helmet while in this [user.loc]!" ) - return - on = !on - if(on) - to_chat(user, "You switch your hardsuit to EVA mode, sacrificing speed for space protection.") - name = initial(name) - desc = initial(desc) - set_light(brightness_on) - flags |= visor_flags - flags_cover |= HEADCOVERSEYES | HEADCOVERSMOUTH - flags_inv |= visor_flags_inv - cold_protection |= HEAD - else - to_chat(user, "You switch your hardsuit to combat mode and can now run at full speed.") - name += " (combat)" - desc = alt_desc - set_light(0) - flags &= ~visor_flags - flags_cover &= ~(HEADCOVERSEYES | HEADCOVERSMOUTH) - flags_inv &= ~visor_flags_inv - cold_protection &= ~HEAD - update_icon() - playsound(src.loc, 'sound/mecha/mechmove03.ogg', 50, 1) - toggle_hardsuit_mode(user) - user.update_inv_head() - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.head_update(src, forced = 1) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/clothing/head/helmet/space/hardsuit/syndi/proc/toggle_hardsuit_mode(mob/user) //Helmet Toggles Suit Mode - if(linkedsuit) - if(on) - linkedsuit.name = initial(linkedsuit.name) - linkedsuit.desc = initial(linkedsuit.desc) - linkedsuit.slowdown = 1 - linkedsuit.flags |= STOPSPRESSUREDMAGE - linkedsuit.cold_protection |= UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS - else - linkedsuit.name += " (combat)" - linkedsuit.desc = linkedsuit.alt_desc - linkedsuit.slowdown = 0 - linkedsuit.flags &= ~STOPSPRESSUREDMAGE - linkedsuit.cold_protection &= ~(UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS) - - linkedsuit.update_icon() - user.update_inv_wear_suit() - user.update_inv_w_uniform() - -/obj/item/clothing/suit/space/hardsuit/syndi - name = "blood-red hardsuit" - desc = "A dual-mode advanced hardsuit designed for work in special operations. It is in travel mode. Property of Gorlex Marauders." - alt_desc = "A dual-mode advanced hardsuit designed for work in special operations. It is in combat mode. Property of Gorlex Marauders." - icon_state = "hardsuit1-syndi" - item_state = "syndie_hardsuit" - item_color = "syndi" - w_class = WEIGHT_CLASS_NORMAL - var/on = 1 - actions_types = list(/datum/action/item_action/toggle_hardsuit_mode) - armor = list("melee" = 40, "bullet" = 50, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 90) - allowed = list(/obj/item/gun, /obj/item/ammo_box,/obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/energy/sword, /obj/item/restraints/handcuffs, /obj/item/tank) - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi - jetpack = /obj/item/tank/jetpack/suit - -/obj/item/clothing/suit/space/hardsuit/syndi/update_icon() - icon_state = "hardsuit[on]-[item_color]" - -//Elite Syndie suit -/obj/item/clothing/head/helmet/space/hardsuit/syndi/elite - name = "elite syndicate hardsuit helmet" - desc = "An elite version of the syndicate helmet, with improved armour and fire shielding. It is in travel mode. Property of Gorlex Marauders." - icon_state = "hardsuit0-syndielite" - item_color = "syndielite" - armor = list("melee" = 60, "bullet" = 60, "laser" = 50, "energy" = 25, "bomb" = 55, "bio" = 100, "rad" = 70, "fire" = 100, "acid" = 100) - heat_protection = HEAD - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/suit/space/hardsuit/syndi/elite - name = "elite syndicate hardsuit" - desc = "An elite version of the syndicate hardsuit, with improved armour and fire shielding. It is in travel mode." - icon_state = "hardsuit0-syndielite" - item_color = "syndielite" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi/elite - armor = list("melee" = 60, "bullet" = 60, "laser" = 50, "energy" = 25, "bomb" = 55, "bio" = 100, "rad" = 70, "fire" = 100, "acid" = 100) - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - -//Strike team hardsuits -/obj/item/clothing/head/helmet/space/hardsuit/syndi/elite/sst - armor = list(melee = 70, bullet = 70, laser = 50, energy = 40, bomb = 80, bio = 100, rad = 100, fire = 100, acid = 100) //Almost as good as DS gear, but unlike DS can switch to combat for mobility - icon_state = "hardsuit0-sst" - item_color = "sst" - -/obj/item/clothing/suit/space/hardsuit/syndi/elite/sst - armor = list(melee = 70, bullet = 70, laser = 50, energy = 40, bomb = 80, bio = 100, rad = 100, fire = 100, acid = 100) - icon_state = "hardsuit0-sst" - item_color = "sst" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi/elite/sst - -/obj/item/clothing/suit/space/hardsuit/syndi/freedom - name = "eagle suit" - desc = "An advanced, light suit, fabricated from a mixture of synthetic feathers and space-resistant material. A gun holster appears to be integrated into the suit." - icon_state = "freedom" - item_state = "freedom" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi/freedom - sprite_sheets = null - -/obj/item/clothing/suit/space/hardsuit/syndi/freedom/update_icon() - return - -/obj/item/clothing/head/helmet/space/hardsuit/syndi/freedom - name = "eagle helmet" - desc = "An advanced, space-proof helmet. It appears to be modeled after an old-world eagle." - icon_state = "griffinhat" - item_state = "griffinhat" - sprite_sheets = null - -/obj/item/clothing/head/helmet/space/hardsuit/syndi/freedom/update_icon() - return - -//Medical hardsuit -/obj/item/clothing/head/helmet/space/hardsuit/medical - name = "medical hardsuit helmet" - desc = "A special helmet designed for work in a hazardous, low pressure environment. Built with lightweight materials for extra comfort, but does not protect the eyes from intense light." - icon_state = "hardsuit0-medical" - item_state = "medical_helm" - item_color = "medical" - flash_protect = 0 - armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 60, "fire" = 60, "acid" = 75) - flags = STOPSPRESSUREDMAGE | THICKMATERIAL - scan_reagents = 1 //Generally worn by the CMO, so they'd get utility off of seeing reagents - -/obj/item/clothing/suit/space/hardsuit/medical - icon_state = "hardsuit-medical" - name = "medical hardsuit" - desc = "A special helmet designed for work in a hazardous, low pressure environment. Built with lightweight materials for extra comfort." - item_state = "medical_hardsuit" - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/storage/firstaid,/obj/item/healthanalyzer,/obj/item/stack/medical,/obj/item/rad_laser) - armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 60, "fire" = 60, "acid" = 75) - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/medical - slowdown = 0.5 - - //Security -/obj/item/clothing/head/helmet/space/hardsuit/security - name = "security hardsuit helmet" - desc = "A special helmet designed for work in a hazardous, low pressure environment. Has an additional layer of armor." - icon_state = "hardsuit0-sec" - item_state = "sec_helm" - item_color = "sec" - armor = list("melee" = 35, "bullet" = 15, "laser" = 30,"energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 50, "fire" = 75, "acid" = 75) - -/obj/item/clothing/suit/space/hardsuit/security - icon_state = "hardsuit-sec" - name = "security hardsuit" - desc = "A special suit that protects against hazardous, low pressure environments. Has an additional layer of armor." - item_state = "sec_hardsuit" - armor = list("melee" = 35, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 50, "fire" = 75, "acid" = 75) - allowed = list(/obj/item/gun,/obj/item/flashlight,/obj/item/tank,/obj/item/melee/baton,/obj/item/reagent_containers/spray/pepper,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/restraints/handcuffs) - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/security - -/obj/item/clothing/head/helmet/space/hardsuit/security/hos - name = "head of security's hardsuit helmet" - desc = "A special bulky helmet designed for work in a hazardous, low pressure environment. Has an additional layer of armor." - icon_state = "hardsuit0-hos" - item_color = "hos" - armor = list("melee" = 45, "bullet" = 25, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 100, "rad" = 50, "fire" = 95, "acid" = 95) - -/obj/item/clothing/suit/space/hardsuit/security/hos - name = "head of security's hardsuit" - desc = "A special bulky suit that protects against hazardous, low pressure environments. Has an additional layer of armor." - icon_state = "hardsuit-hos" - armor = list("melee" = 45, "bullet" = 25, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 100, "rad" = 50, "fire" = 95, "acid" = 95) - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/security/hos - jetpack = /obj/item/tank/jetpack/suit - - -//Singuloth armor -/obj/item/clothing/head/helmet/space/hardsuit/singuloth - name = "singuloth knight's helmet" - desc = "This is an adamantium helmet from the chapter of the Singuloth Knights. It shines with a holy aura." - icon_state = "hardsuit0-singuloth" - item_state = "singuloth_helm" - item_color = "singuloth" - armor = list(melee = 40, bullet = 5, laser = 20, energy = 5, bomb = 25, bio = 100, rad = 100, fire = 95, acid = 95) - sprite_sheets = null - -/obj/item/clothing/suit/space/hardsuit/singuloth - icon_state = "hardsuit-singuloth" - name = "singuloth knight's armor" - desc = "This is a ceremonial armor from the chapter of the Singuloth Knights. It's made of pure forged adamantium." - item_state = "singuloth_hardsuit" - flags = STOPSPRESSUREDMAGE - armor = list(melee = 45, bullet = 25, laser = 30, energy = 10, bomb = 25, bio = 100, rad = 100, fire = 95, acid = 95) - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/singuloth - sprite_sheets = null - - -/////////////SHIELDED////////////////////////////////// - -/obj/item/clothing/suit/space/hardsuit/shielded - name = "shielded hardsuit" - desc = "A hardsuit with built in energy shielding. Will rapidly recharge when not under fire." - icon_state = "hardsuit-hos" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded - allowed = list(/obj/item/flashlight,/obj/item/tank, /obj/item/gun,/obj/item/reagent_containers/spray/pepper,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs) - armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - var/current_charges = 3 - var/max_charges = 3 //How many charges total the shielding has - var/recharge_delay = 200 //How long after we've been shot before we can start recharging. 20 seconds here - var/recharge_cooldown = 0 //Time since we've last been shot - var/recharge_rate = 1 //How quickly the shield recharges once it starts charging - var/shield_state = "shield-old" - var/shield_on = "shield-old" - -/obj/item/clothing/suit/space/hardsuit/shielded/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - recharge_cooldown = world.time + recharge_delay - if(current_charges > 0) - do_sparks(2, 1, src) - owner.visible_message("[owner]'s shields deflect [attack_text] in a shower of sparks!") - current_charges-- - if(recharge_rate) - START_PROCESSING(SSobj, src) - if(current_charges <= 0) - owner.visible_message("[owner]'s shield overloads!") - shield_state = "broken" - owner.update_inv_wear_suit() - return 1 - return 0 - - - -/obj/item/clothing/suit/space/hardsuit/shielded/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/clothing/suit/space/hardsuit/shielded/process() - if(world.time > recharge_cooldown && current_charges < max_charges) - current_charges = Clamp((current_charges + recharge_rate), 0, max_charges) - playsound(loc, 'sound/magic/charge.ogg', 50, TRUE) - if(current_charges == max_charges) - playsound(loc, 'sound/machines/ding.ogg', 50, TRUE) - STOP_PROCESSING(SSobj, src) - shield_state = "[shield_on]" - if(ishuman(loc)) - var/mob/living/carbon/human/C = loc - C.update_inv_wear_suit() - -/obj/item/clothing/suit/space/hardsuit/shielded/special_overlays() - return mutable_appearance('icons/effects/effects.dmi', shield_state, MOB_LAYER + 0.01) - -/obj/item/clothing/head/helmet/space/hardsuit/shielded - resistance_flags = FIRE_PROOF | ACID_PROOF - - -//////Syndicate Version - -/obj/item/clothing/suit/space/hardsuit/shielded/syndi - name = "blood-red hardsuit" - desc = "An advanced hardsuit with built in energy shielding." - icon_state = "hardsuit1-syndi" - item_state = "syndie_hardsuit" - item_color = "syndi" - armor = list("melee" = 40, "bullet" = 50, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) - allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword/saber,/obj/item/restraints/handcuffs,/obj/item/tank) - slowdown = 0 - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/syndi - jetpack = /obj/item/tank/jetpack/suit - -/obj/item/clothing/suit/space/hardsuit/shielded/syndi/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(shield_state == "broken") - to_chat(user, "You can't interface with the hardsuit's software if the shield's broken!") - return - - if(shield_state == "shield-red") - shield_state = "shield-old" - shield_on = "shield-old" - to_chat(user, "You roll back the hardsuit's software, changing the shield's color!") - - else - shield_state = "shield-red" - shield_on = "shield-red" - to_chat(user, "You update the hardsuit's hardware, changing back the shield's color to red.") - user.update_inv_wear_suit() - -/obj/item/clothing/head/helmet/space/hardsuit/shielded/syndi - name = "blood-red hardsuit helmet" - desc = "An advanced hardsuit helmet with built in energy shielding." - icon_state = "hardsuit1-syndi" - item_state = "syndie_helm" - item_color = "syndi" - armor = list("melee" = 40, "bullet" = 50, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) +//Baseline hardsuits +/obj/item/clothing/head/helmet/space/hardsuit + name = "hardsuit helmet" + desc = "A special helmet designed for work in a hazardous, low-pressure environment." + icon_state = "hardsuit0-engineering" + item_state = "eng_helm" + max_integrity = 300 + armor = list("melee" = 10, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 50, "acid" = 75) + var/basestate = "hardsuit" + allowed = list(/obj/item/flashlight) + var/brightness_on = 4 //luminosity when on + var/on = FALSE + var/obj/item/clothing/suit/space/hardsuit/suit + item_color = "engineering" //Determines used sprites: hardsuit[on]-[color] and hardsuit[on]-[color]2 (lying down sprite) + actions_types = list(/datum/action/item_action/toggle_helmet_light) + + //Species-specific stuff. + species_restricted = list("exclude","Wryn") + sprite_sheets = list( + "Unathi" = 'icons/mob/species/unathi/helmet.dmi', + "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', + "Skrell" = 'icons/mob/species/skrell/helmet.dmi', + "Vox" = 'icons/mob/species/vox/helmet.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', + "Drask" = 'icons/mob/species/drask/helmet.dmi', + "Grey" = 'icons/mob/species/grey/helmet.dmi' + ) + sprite_sheets_obj = list( + "Unathi" = 'icons/obj/clothing/species/unathi/hats.dmi', + "Tajaran" = 'icons/obj/clothing/species/tajaran/hats.dmi', + "Skrell" = 'icons/obj/clothing/species/skrell/hats.dmi', + "Vox" = 'icons/obj/clothing/species/vox/hats.dmi', + "Vulpkanin" = 'icons/obj/clothing/species/vulpkanin/hats.dmi' + ) + +/obj/item/clothing/head/helmet/space/hardsuit/attack_self(mob/user) + toggle_light(user) + +/obj/item/clothing/head/helmet/space/hardsuit/proc/toggle_light(mob/user) + on = !on + icon_state = "[basestate][on]-[item_color]" + + if(istype(user,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = user + H.update_inv_head() + + if(on) + set_light(brightness_on) + else + set_light(0) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/clothing/head/helmet/space/hardsuit/extinguish_light() + if(on) + toggle_light() + visible_message("[src]'s light fades and turns off.") + +/obj/item/clothing/head/helmet/space/hardsuit/dropped(mob/user) + ..() + if(suit) + suit.RemoveHelmet() + +/obj/item/clothing/head/helmet/space/hardsuit/item_action_slot_check(slot) + if(slot == slot_head) + return 1 + +/obj/item/clothing/head/helmet/space/hardsuit/equipped(mob/user, slot) + ..() + if(slot != slot_head) + if(suit) + suit.RemoveHelmet() + else + qdel(src) + +/obj/item/clothing/head/helmet/space/hardsuit/proc/display_visor_message(var/msg) + var/mob/wearer = loc + if(msg && ishuman(wearer)) + wearer.show_message("[msg]", 1) + +/obj/item/clothing/head/helmet/space/hardsuit/emp_act(severity) + ..() + display_visor_message("[severity > 1 ? "Light" : "Strong"] electromagnetic pulse detected!") + +/obj/item/clothing/suit/space/hardsuit + name = "hardsuit" + desc = "A special space suit for environments that might pose hazards beyond just the vacuum of space. Provides more protection than a standard space suit." + icon_state = "hardsuit-engineering" + item_state = "eng_hardsuit" + max_integrity = 300 + armor = list("melee" = 10, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 50, "acid" = 75) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/t_scanner, /obj/item/rcd, /obj/item/rpd) + siemens_coefficient = 0 + var/obj/item/clothing/head/helmet/space/hardsuit/helmet + actions_types = list(/datum/action/item_action/toggle_helmet) + var/helmettype = /obj/item/clothing/head/helmet/space/hardsuit + var/obj/item/tank/jetpack/suit/jetpack = null + + hide_tail_by_species = list("Vox" , "Vulpkanin" , "Unathi" , "Tajaran") + species_restricted = list("exclude", "Wryn") + sprite_sheets = list( + "Unathi" = 'icons/mob/species/unathi/suit.dmi', + "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', + "Skrell" = 'icons/mob/species/skrell/suit.dmi', + "Vox" = 'icons/mob/species/vox/suit.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', + "Drask" = 'icons/mob/species/drask/suit.dmi' + ) + sprite_sheets_obj = list( + "Unathi" = 'icons/obj/clothing/species/unathi/suits.dmi', + "Tajaran" = 'icons/obj/clothing/species/tajaran/suits.dmi', + "Skrell" = 'icons/obj/clothing/species/skrell/suits.dmi', + "Vox" = 'icons/obj/clothing/species/vox/suits.dmi', + "Vulpkanin" = 'icons/obj/clothing/species/vulpkanin/suits.dmi' + ) + +/obj/item/clothing/suit/space/hardsuit/New() + if(jetpack && ispath(jetpack)) + jetpack = new jetpack(src) + ..() + +/obj/item/clothing/suit/space/hardsuit/attack_self(mob/user) + user.changeNext_move(CLICK_CD_MELEE) + ..() + +/obj/item/clothing/suit/space/hardsuit/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/tank/jetpack/suit)) + if(jetpack) + to_chat(user, "[src] already has a jetpack installed.") + return + if(src == user.get_item_by_slot(slot_wear_suit)) //Make sure the player is not wearing the suit before applying the upgrade. + to_chat(user, "You cannot install the upgrade to [src] while wearing it.") + return + + if(user.unEquip(I)) + I.forceMove(src) + jetpack = I + to_chat(user, "You successfully install the jetpack into [src].") + return + return ..() + +/obj/item/clothing/suit/space/hardsuit/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!jetpack) + to_chat(user, "[src] has no jetpack installed.") + return + if(src == user.get_item_by_slot(slot_wear_suit)) + to_chat(user, "You cannot remove the jetpack from [src] while wearing it.") + return + jetpack.turn_off(user) + jetpack.forceMove(drop_location()) + jetpack = null + to_chat(user, "You successfully remove the jetpack from [src].") + +/obj/item/clothing/suit/space/hardsuit/equipped(mob/user, slot) + ..() + if(jetpack) + if(slot == slot_wear_suit) + for(var/X in jetpack.actions) + var/datum/action/A = X + A.Grant(user) + +/obj/item/clothing/suit/space/hardsuit/dropped(mob/user) + ..() + if(jetpack) + for(var/X in jetpack.actions) + var/datum/action/A = X + A.Remove(user) + +/obj/item/clothing/suit/space/hardsuit/item_action_slot_check(slot) + if(slot == slot_wear_suit) //we only give the mob the ability to toggle the helmet if he's wearing the hardsuit. + return 1 + +//Engineering hardsuit +/obj/item/clothing/head/helmet/space/hardsuit/engine + name = "engineering hardsuit helmet" + desc = "A special helmet designed for work in a hazardous, low-pressure environment. Has radiation shielding." + icon_state = "hardsuit0-engineering" + item_state = "eng_helm" + armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 100, "acid" = 75) + resistance_flags = FIRE_PROOF + item_color = "engineering" + +/obj/item/clothing/suit/space/hardsuit/engine + name = "engineering hardsuit" + desc = "A special suit that protects against hazardous, low pressure environments. Has radiation shielding." + icon_state = "hardsuit-engineering" + item_state = "eng_hardsuit" + armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 100, "acid" = 75) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine + dog_fashion = /datum/dog_fashion/back/hardsuit + resistance_flags = FIRE_PROOF + +//Atmospherics +/obj/item/clothing/head/helmet/space/hardsuit/engine/atmos + name = "atmospherics hardsuit helmet" + desc = "A special helmet designed for work in a hazardous, low-pressure environment. Has thermal shielding." + icon_state = "hardsuit0-atmos" + item_state = "atmos_helm" + item_color = "atmos" + armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 25, "fire" = 100, "acid" = 75) + heat_protection = HEAD //Uncomment to enable firesuit protection + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + +/obj/item/clothing/suit/space/hardsuit/engine/atmos + name = "atmospherics hardsuit" + desc = "A special suit that protects against hazardous, low pressure environments. Has thermal shielding." + icon_state = "hardsuit-atmos" + item_state = "atmos_hardsuit" + armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 25, "fire" = 100, "acid" = 75) + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS //Uncomment to enable firesuit protection + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine/atmos + dog_fashion = null + +//Chief Engineer's hardsuit +/obj/item/clothing/head/helmet/space/hardsuit/engine/elite + name = "advanced hardsuit helmet" + desc = "An advanced helmet designed for work in a hazardous, low pressure environment. Shines with a high polish." + icon_state = "hardsuit0-white" + item_state = "ce_helm" + item_color = "white" + armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 90) + heat_protection = HEAD //Uncomment to enable firesuit protection + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + +/obj/item/clothing/suit/space/hardsuit/engine/elite + icon_state = "hardsuit-white" + name = "advanced hardsuit" + desc = "An advanced suit that protects against hazardous, low pressure environments. Shines with a high polish." + item_state = "ce_hardsuit" + armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 90, "fire" = 100, "acid" = 90) + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS //Uncomment to enable firesuit protection + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine/elite + jetpack = /obj/item/tank/jetpack/suit + dog_fashion = null + +//Mining hardsuit +/obj/item/clothing/head/helmet/space/hardsuit/mining + name = "mining hardsuit helmet" + desc = "A special helmet designed for work in a hazardous, low pressure environment. Has reinforced plating." + icon_state = "hardsuit0-mining" + item_state = "mining_helm" + item_color = "mining" + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF + heat_protection = HEAD + armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 75) + brightness_on = 7 + +/obj/item/clothing/suit/space/hardsuit/mining + icon_state = "hardsuit-mining" + name = "mining hardsuit" + desc = "A special suit that protects against hazardous, low pressure environments. Has reinforced plating." + item_state = "mining_hardsuit" + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF + armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 75) + allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/storage/bag/ore, /obj/item/pickaxe, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/mining + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + +//Syndicate hardsuit +/obj/item/clothing/head/helmet/space/hardsuit/syndi + name = "blood-red hardsuit helmet" + desc = "A dual-mode advanced helmet designed for work in special operations. It is in travel mode. Property of Gorlex Marauders." + alt_desc = "A dual-mode advanced helmet designed for work in special operations. It is in combat mode. Property of Gorlex Marauders." + icon_state = "hardsuit1-syndi" + item_state = "syndie_helm" + item_color = "syndi" + armor = list("melee" = 40, "bullet" = 50, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 90) + on = 1 + var/obj/item/clothing/suit/space/hardsuit/syndi/linkedsuit = null + actions_types = list(/datum/action/item_action/toggle_helmet_mode) + visor_flags_inv = HIDEMASK|HIDEEYES|HIDEFACE|HIDETAIL + visor_flags = STOPSPRESSUREDMAGE + +/obj/item/clothing/head/helmet/space/hardsuit/syndi/update_icon() + icon_state = "hardsuit[on]-[item_color]" + +/obj/item/clothing/head/helmet/space/hardsuit/syndi/New() + ..() + if(istype(loc, /obj/item/clothing/suit/space/hardsuit/syndi)) + linkedsuit = loc + +/obj/item/clothing/head/helmet/space/hardsuit/syndi/attack_self(mob/user) //Toggle Helmet + if(!isturf(user.loc)) + to_chat(user, "You cannot toggle your helmet while in this [user.loc]!" ) + return + on = !on + if(on) + to_chat(user, "You switch your hardsuit to EVA mode, sacrificing speed for space protection.") + name = initial(name) + desc = initial(desc) + set_light(brightness_on) + flags |= visor_flags + flags_cover |= HEADCOVERSEYES | HEADCOVERSMOUTH + flags_inv |= visor_flags_inv + cold_protection |= HEAD + else + to_chat(user, "You switch your hardsuit to combat mode and can now run at full speed.") + name += " (combat)" + desc = alt_desc + set_light(0) + flags &= ~visor_flags + flags_cover &= ~(HEADCOVERSEYES | HEADCOVERSMOUTH) + flags_inv &= ~visor_flags_inv + cold_protection &= ~HEAD + update_icon() + playsound(src.loc, 'sound/mecha/mechmove03.ogg', 50, 1) + toggle_hardsuit_mode(user) + user.update_inv_head() + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.head_update(src, forced = 1) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/clothing/head/helmet/space/hardsuit/syndi/proc/toggle_hardsuit_mode(mob/user) //Helmet Toggles Suit Mode + if(linkedsuit) + if(on) + linkedsuit.name = initial(linkedsuit.name) + linkedsuit.desc = initial(linkedsuit.desc) + linkedsuit.slowdown = 1 + linkedsuit.flags |= STOPSPRESSUREDMAGE + linkedsuit.cold_protection |= UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS + else + linkedsuit.name += " (combat)" + linkedsuit.desc = linkedsuit.alt_desc + linkedsuit.slowdown = 0 + linkedsuit.flags &= ~STOPSPRESSUREDMAGE + linkedsuit.cold_protection &= ~(UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS) + + linkedsuit.update_icon() + user.update_inv_wear_suit() + user.update_inv_w_uniform() + +/obj/item/clothing/suit/space/hardsuit/syndi + name = "blood-red hardsuit" + desc = "A dual-mode advanced hardsuit designed for work in special operations. It is in travel mode. Property of Gorlex Marauders." + alt_desc = "A dual-mode advanced hardsuit designed for work in special operations. It is in combat mode. Property of Gorlex Marauders." + icon_state = "hardsuit1-syndi" + item_state = "syndie_hardsuit" + item_color = "syndi" + w_class = WEIGHT_CLASS_NORMAL + var/on = 1 + actions_types = list(/datum/action/item_action/toggle_hardsuit_mode) + armor = list("melee" = 40, "bullet" = 50, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 90) + allowed = list(/obj/item/gun, /obj/item/ammo_box,/obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/energy/sword, /obj/item/restraints/handcuffs, /obj/item/tank) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi + jetpack = /obj/item/tank/jetpack/suit + +/obj/item/clothing/suit/space/hardsuit/syndi/update_icon() + icon_state = "hardsuit[on]-[item_color]" + +//Elite Syndie suit +/obj/item/clothing/head/helmet/space/hardsuit/syndi/elite + name = "elite syndicate hardsuit helmet" + desc = "An elite version of the syndicate helmet, with improved armour and fire shielding. It is in travel mode. Property of Gorlex Marauders." + icon_state = "hardsuit0-syndielite" + item_color = "syndielite" + armor = list("melee" = 60, "bullet" = 60, "laser" = 50, "energy" = 25, "bomb" = 55, "bio" = 100, "rad" = 70, "fire" = 100, "acid" = 100) + heat_protection = HEAD + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/suit/space/hardsuit/syndi/elite + name = "elite syndicate hardsuit" + desc = "An elite version of the syndicate hardsuit, with improved armour and fire shielding. It is in travel mode." + icon_state = "hardsuit0-syndielite" + item_color = "syndielite" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi/elite + armor = list("melee" = 60, "bullet" = 60, "laser" = 50, "energy" = 25, "bomb" = 55, "bio" = 100, "rad" = 70, "fire" = 100, "acid" = 100) + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + +//Strike team hardsuits +/obj/item/clothing/head/helmet/space/hardsuit/syndi/elite/sst + armor = list(melee = 70, bullet = 70, laser = 50, energy = 40, bomb = 80, bio = 100, rad = 100, fire = 100, acid = 100) //Almost as good as DS gear, but unlike DS can switch to combat for mobility + icon_state = "hardsuit0-sst" + item_color = "sst" + +/obj/item/clothing/suit/space/hardsuit/syndi/elite/sst + armor = list(melee = 70, bullet = 70, laser = 50, energy = 40, bomb = 80, bio = 100, rad = 100, fire = 100, acid = 100) + icon_state = "hardsuit0-sst" + item_color = "sst" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi/elite/sst + +/obj/item/clothing/suit/space/hardsuit/syndi/freedom + name = "eagle suit" + desc = "An advanced, light suit, fabricated from a mixture of synthetic feathers and space-resistant material. A gun holster appears to be integrated into the suit." + icon_state = "freedom" + item_state = "freedom" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/syndi/freedom + sprite_sheets = null + +/obj/item/clothing/suit/space/hardsuit/syndi/freedom/update_icon() + return + +/obj/item/clothing/head/helmet/space/hardsuit/syndi/freedom + name = "eagle helmet" + desc = "An advanced, space-proof helmet. It appears to be modeled after an old-world eagle." + icon_state = "griffinhat" + item_state = "griffinhat" + sprite_sheets = null + +/obj/item/clothing/head/helmet/space/hardsuit/syndi/freedom/update_icon() + return + +//Medical hardsuit +/obj/item/clothing/head/helmet/space/hardsuit/medical + name = "medical hardsuit helmet" + desc = "A special helmet designed for work in a hazardous, low pressure environment. Built with lightweight materials for extra comfort, but does not protect the eyes from intense light." + icon_state = "hardsuit0-medical" + item_state = "medical_helm" + item_color = "medical" + flash_protect = 0 + armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 60, "fire" = 60, "acid" = 75) + flags = STOPSPRESSUREDMAGE | THICKMATERIAL + scan_reagents = 1 //Generally worn by the CMO, so they'd get utility off of seeing reagents + +/obj/item/clothing/suit/space/hardsuit/medical + icon_state = "hardsuit-medical" + name = "medical hardsuit" + desc = "A special helmet designed for work in a hazardous, low pressure environment. Built with lightweight materials for extra comfort." + item_state = "medical_hardsuit" + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/storage/firstaid,/obj/item/healthanalyzer,/obj/item/stack/medical,/obj/item/rad_laser) + armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 60, "fire" = 60, "acid" = 75) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/medical + slowdown = 0.5 + + //Security +/obj/item/clothing/head/helmet/space/hardsuit/security + name = "security hardsuit helmet" + desc = "A special helmet designed for work in a hazardous, low pressure environment. Has an additional layer of armor." + icon_state = "hardsuit0-sec" + item_state = "sec_helm" + item_color = "sec" + armor = list("melee" = 35, "bullet" = 15, "laser" = 30,"energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 50, "fire" = 75, "acid" = 75) + +/obj/item/clothing/suit/space/hardsuit/security + icon_state = "hardsuit-sec" + name = "security hardsuit" + desc = "A special suit that protects against hazardous, low pressure environments. Has an additional layer of armor." + item_state = "sec_hardsuit" + armor = list("melee" = 35, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 50, "fire" = 75, "acid" = 75) + allowed = list(/obj/item/gun,/obj/item/flashlight,/obj/item/tank,/obj/item/melee/baton,/obj/item/reagent_containers/spray/pepper,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/restraints/handcuffs) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/security + +/obj/item/clothing/head/helmet/space/hardsuit/security/hos + name = "head of security's hardsuit helmet" + desc = "A special bulky helmet designed for work in a hazardous, low pressure environment. Has an additional layer of armor." + icon_state = "hardsuit0-hos" + item_color = "hos" + armor = list("melee" = 45, "bullet" = 25, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 100, "rad" = 50, "fire" = 95, "acid" = 95) + +/obj/item/clothing/suit/space/hardsuit/security/hos + name = "head of security's hardsuit" + desc = "A special bulky suit that protects against hazardous, low pressure environments. Has an additional layer of armor." + icon_state = "hardsuit-hos" + armor = list("melee" = 45, "bullet" = 25, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 100, "rad" = 50, "fire" = 95, "acid" = 95) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/security/hos + jetpack = /obj/item/tank/jetpack/suit + + +//Singuloth armor +/obj/item/clothing/head/helmet/space/hardsuit/singuloth + name = "singuloth knight's helmet" + desc = "This is an adamantium helmet from the chapter of the Singuloth Knights. It shines with a holy aura." + icon_state = "hardsuit0-singuloth" + item_state = "singuloth_helm" + item_color = "singuloth" + armor = list(melee = 40, bullet = 5, laser = 20, energy = 5, bomb = 25, bio = 100, rad = 100, fire = 95, acid = 95) + sprite_sheets = null + +/obj/item/clothing/suit/space/hardsuit/singuloth + icon_state = "hardsuit-singuloth" + name = "singuloth knight's armor" + desc = "This is a ceremonial armor from the chapter of the Singuloth Knights. It's made of pure forged adamantium." + item_state = "singuloth_hardsuit" + flags = STOPSPRESSUREDMAGE + armor = list(melee = 45, bullet = 25, laser = 30, energy = 10, bomb = 25, bio = 100, rad = 100, fire = 95, acid = 95) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/singuloth + sprite_sheets = null + + +/////////////SHIELDED////////////////////////////////// + +/obj/item/clothing/suit/space/hardsuit/shielded + name = "shielded hardsuit" + desc = "A hardsuit with built in energy shielding. Will rapidly recharge when not under fire." + icon_state = "hardsuit-hos" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded + allowed = list(/obj/item/flashlight,/obj/item/tank, /obj/item/gun,/obj/item/reagent_containers/spray/pepper,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs) + armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + var/current_charges = 3 + var/max_charges = 3 //How many charges total the shielding has + var/recharge_delay = 200 //How long after we've been shot before we can start recharging. 20 seconds here + var/recharge_cooldown = 0 //Time since we've last been shot + var/recharge_rate = 1 //How quickly the shield recharges once it starts charging + var/shield_state = "shield-old" + var/shield_on = "shield-old" + +/obj/item/clothing/suit/space/hardsuit/shielded/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + recharge_cooldown = world.time + recharge_delay + if(current_charges > 0) + do_sparks(2, 1, src) + owner.visible_message("[owner]'s shields deflect [attack_text] in a shower of sparks!") + current_charges-- + if(recharge_rate) + START_PROCESSING(SSobj, src) + if(current_charges <= 0) + owner.visible_message("[owner]'s shield overloads!") + shield_state = "broken" + owner.update_inv_wear_suit() + return 1 + return 0 + + + +/obj/item/clothing/suit/space/hardsuit/shielded/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/clothing/suit/space/hardsuit/shielded/process() + if(world.time > recharge_cooldown && current_charges < max_charges) + current_charges = Clamp((current_charges + recharge_rate), 0, max_charges) + playsound(loc, 'sound/magic/charge.ogg', 50, TRUE) + if(current_charges == max_charges) + playsound(loc, 'sound/machines/ding.ogg', 50, TRUE) + STOP_PROCESSING(SSobj, src) + shield_state = "[shield_on]" + if(ishuman(loc)) + var/mob/living/carbon/human/C = loc + C.update_inv_wear_suit() + +/obj/item/clothing/suit/space/hardsuit/shielded/special_overlays() + return mutable_appearance('icons/effects/effects.dmi', shield_state, MOB_LAYER + 0.01) + +/obj/item/clothing/head/helmet/space/hardsuit/shielded + resistance_flags = FIRE_PROOF | ACID_PROOF + + +//////Syndicate Version + +/obj/item/clothing/suit/space/hardsuit/shielded/syndi + name = "blood-red hardsuit" + desc = "An advanced hardsuit with built in energy shielding." + icon_state = "hardsuit1-syndi" + item_state = "syndie_hardsuit" + item_color = "syndi" + armor = list("melee" = 40, "bullet" = 50, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) + allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword/saber,/obj/item/restraints/handcuffs,/obj/item/tank) + slowdown = 0 + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/syndi + jetpack = /obj/item/tank/jetpack/suit + +/obj/item/clothing/suit/space/hardsuit/shielded/syndi/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(shield_state == "broken") + to_chat(user, "You can't interface with the hardsuit's software if the shield's broken!") + return + + if(shield_state == "shield-red") + shield_state = "shield-old" + shield_on = "shield-old" + to_chat(user, "You roll back the hardsuit's software, changing the shield's color!") + + else + shield_state = "shield-red" + shield_on = "shield-red" + to_chat(user, "You update the hardsuit's hardware, changing back the shield's color to red.") + user.update_inv_wear_suit() + +/obj/item/clothing/head/helmet/space/hardsuit/shielded/syndi + name = "blood-red hardsuit helmet" + desc = "An advanced hardsuit helmet with built in energy shielding." + icon_state = "hardsuit1-syndi" + item_state = "syndie_helm" + item_color = "syndi" + armor = list("melee" = 40, "bullet" = 50, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index f516627e182..f6ab79e628a 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -1,287 +1,287 @@ - //Captain's space suit, not hardsuits because no flashlight! -/obj/item/clothing/head/helmet/space/capspace - name = "captain's space helmet" - icon_state = "capspace" - item_state = "capspacehelmet" - desc = "A special helmet designed for only the most fashionable of military figureheads." - flags_inv = HIDEFACE - permeability_coefficient = 0.01 - armor = list("melee" = 40, "bullet" = 50, "laser" = 50, "energy" = 25, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) - species_restricted = list("exclude", "Wryn") - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi', - "Grey" = 'icons/mob/species/grey/helmet.dmi' - ) - -/obj/item/clothing/head/helmet/space/capspace/equipped(mob/living/carbon/human/user, slot) - if(ishuman(user) && slot == slot_head) - if(isvox(user)) - if(flags & BLOCKHAIR) - flags &= ~BLOCKHAIR - else - if((initial(flags) & BLOCKHAIR) && !(flags & BLOCKHAIR)) - flags |= BLOCKHAIR - -/obj/item/clothing/suit/space/captain - name = "captain's space suit" - desc = "A bulky, heavy-duty piece of exclusive Nanotrasen armor. YOU are in charge!" - icon_state = "caparmor" - item_state = "capspacesuit" - w_class = WEIGHT_CLASS_BULKY - allowed = list(/obj/item/tank, /obj/item/flashlight,/obj/item/gun/energy, /obj/item/gun/projectile, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton,/obj/item/restraints/handcuffs) - armor = list("melee" = 40, "bullet" = 50, "laser" = 50, "energy" = 25, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) - species_restricted = list("exclude", "Wryn") - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - - //Deathsquad space suit, not hardsuits because no flashlight! -/obj/item/clothing/head/helmet/space/deathsquad - name = "deathsquad helmet" - desc = "That's not red paint. That's real blood." - icon_state = "deathsquad" - item_state = "deathsquad" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these - see_in_dark = 8 - HUDType = MEDHUD - strip_delay = 130 - -/obj/item/clothing/suit/space/deathsquad - name = "deathsquad suit" - desc = "A heavily armored, advanced space suit that protects against most forms of damage." - icon_state = "deathsquad" - item_state = "swat_suit" - allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/tank,/obj/item/kitchen/knife/combat) - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - strip_delay = 130 - dog_fashion = /datum/dog_fashion/back/deathsquad - - //NEW SWAT suit -/obj/item/clothing/suit/space/swat - name = "SWAT armor" - desc = "Space-proof tactical SWAT armor." - icon_state = "heavy" - item_state = "swat_suit" - allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/tank,/obj/item/kitchen/knife/combat) - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) - strip_delay = 120 - resistance_flags = FIRE_PROOF | ACID_PROOF - species_restricted = list("exclude", "Wryn") - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/head/helmet/space/deathsquad/beret - name = "officer's beret" - desc = "An armored beret commonly used by special operations officers." - icon_state = "beret_officer" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - flags = STOPSPRESSUREDMAGE | THICKMATERIAL - -/obj/item/clothing/suit/space/deathsquad/officer - name = "officer jacket" - desc = "An armored jacket used in special operations." - icon_state = "detective" - item_state = "det_suit" - blood_overlay_type = "coat" - flags_inv = 0 - slowdown = 0 - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - w_class = WEIGHT_CLASS_NORMAL - -//Space santa outfit suit -/obj/item/clothing/head/helmet/space/santahat - name = "Santa's hat" - desc = "Ho ho ho. Merrry X-mas!" - icon_state = "santahat" - - sprite_sheets = list( - "Grey" = 'icons/mob/species/Grey/head.dmi', - "Drask" = 'icons/mob/species/Drask/helmet.dmi' - ) - flags = BLOCKHAIR | STOPSPRESSUREDMAGE - flags_cover = HEADCOVERSEYES - dog_fashion = /datum/dog_fashion/head/santa - -/obj/item/clothing/head/helmet/space/santahat/attack_self(mob/user as mob) - if(src.icon_state == "santahat") - src.icon_state = "santahat_beard" - src.item_state = "santahat_beard" - to_chat(user, "Santa's beard expands out from the hat!") - else - src.icon_state = "santahat" - src.item_state = "santahat" - to_chat(user, "The beard slinks back into the hat...") - -/obj/item/clothing/suit/space/santa - name = "Santa's suit" - desc = "Festive!" - icon_state = "santa" - item_state = "santa" - slowdown = 0 - flags = STOPSPRESSUREDMAGE - flags_size = ONESIZEFITSALL - allowed = list(/obj/item) //for stuffing extra special presents - -//Space pirate outfit -/obj/item/clothing/head/helmet/space/pirate - name = "pirate hat" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) - flags = BLOCKHAIR | STOPSPRESSUREDMAGE - flags_cover = HEADCOVERSEYES - strip_delay = 40 - put_on_delay = 20 - -/obj/item/clothing/suit/space/pirate - name = "pirate coat" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/tank) - slowdown = 0 - armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) - strip_delay = 40 - put_on_delay = 20 - -//Paramedic EVA suit -/obj/item/clothing/head/helmet/space/eva/paramedic - name = "Paramedic EVA helmet" - desc = "A brand new paramedic EVA helmet. It seems to mold to your head shape. Used for retrieving bodies in space." - icon_state = "paramedic-eva-helmet" - item_state = "paramedic-eva-helmet" - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 100, rad = 20, fire = 50, acid = 65) - species_restricted = list("exclude", "Wryn") - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi', - "Grey" = 'icons/mob/species/grey/helmet.dmi', - "Skrell" = 'icons/mob/species/skrell/helmet.dmi', - "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', - "Drask" = 'icons/mob/species/drask/helmet.dmi', - "Unathi" = 'icons/mob/species/unathi/helmet.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', - ) - sprite_sheets_obj = list( - "Vox" = 'icons/obj/clothing/species/vox/hats.dmi' - ) - -/obj/item/clothing/suit/space/eva/paramedic - name = "Paramedic EVA suit" - icon_state = "paramedic-eva" - item_state = "paramedic-eva" - desc = "A brand new paramedic EVA suit. The nitrile seems a bit too thin to be space proof. Used for retrieving bodies in space." - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 100, rad = 20, fire = 50, acid = 65) - species_restricted = list("exclude", "Wryn") - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi', - "Skrell" = 'icons/mob/species/skrell/suit.dmi', - "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', - "Drask" = 'icons/mob/species/drask/suit.dmi', - "Unathi" = 'icons/mob/species/unathi/suit.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', - ) - sprite_sheets_obj = list( - "Vox" = 'icons/obj/clothing/species/vox/suits.dmi' - ) - -/obj/item/clothing/suit/space/eva - name = "EVA suit" - icon_state = "spacenew" - item_state = "s_suit" - desc = "A lightweight space suit with the basic ability to protect the wearer from the vacuum of space during emergencies." - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 100, rad = 20, fire = 50, acid = 65) - species_restricted = list("exclude", "Wryn") - - sprite_sheets = list( - "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', - "Unathi" = 'icons/mob/species/unathi/suit.dmi', - "Vox" = 'icons/mob/species/vox/suit.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', - ) - sprite_sheets_obj = list( - "Tajaran" = 'icons/obj/clothing/species/tajaran/suits.dmi', - "Unathi" = 'icons/obj/clothing/species/unathi/suits.dmi', - "Vox" = 'icons/obj/clothing/species/vox/suits.dmi', - "Vulpkanin" = 'icons/obj/clothing/species/vulpkanin/suits.dmi' - ) - -/obj/item/clothing/head/helmet/space/eva - name = "EVA helmet" - icon_state = "spacenew" - item_state = "s_helmet" - desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies." - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 100, rad = 20, fire = 50, acid = 65) - flash_protect = 0 - species_restricted = list("exclude", "Wryn") - sprite_sheets = list( - "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', - "Unathi" = 'icons/mob/species/unathi/helmet.dmi', - "Vox" = 'icons/mob/species/vox/helmet.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', - "Grey" = 'icons/mob/species/grey/helmet.dmi' - ) - sprite_sheets_obj = list( - "Vox" = 'icons/obj/clothing/species/vox/hats.dmi', - "Vulpkanin" = 'icons/obj/clothing/species/vulpkanin/hats.dmi' - ) - -//Mime's Hardsuit -/obj/item/clothing/head/helmet/space/eva/mime - name = "mime eva helmet" -// icon = 'spaceciv.dmi' - desc = ". . ." - icon_state = "spacemimehelmet" - item_state = "spacemimehelmet" - species_restricted = list("exclude","Wryn") - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi') - sprite_sheets_obj = null - -/obj/item/clothing/suit/space/eva/mime - name = "mime eva suit" -// icon = 'spaceciv.dmi' - desc = ". . ." - icon_state = "spacemime_suit" - item_state = "spacemime_items" - species_restricted = list("exclude","Wryn") - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi') - sprite_sheets_obj = null - -/obj/item/clothing/head/helmet/space/eva/clown - name = "clown eva helmet" -// icon = 'spaceciv.dmi' - desc = "An EVA helmet specifically designed for the clown. SPESSHONK!" - icon_state = "clownhelmet" - item_state = "clownhelmet" - species_restricted = list("exclude","Wryn") - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi') - sprite_sheets_obj = null - -/obj/item/clothing/suit/space/eva/clown - name = "clown eva suit" -// icon = 'spaceciv.dmi' - desc = "An EVA suit specifically designed for the clown. SPESSHONK!" - icon_state = "spaceclown_suit" - item_state = "spaceclown_items" - species_restricted = list("exclude","Wryn") - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi') - sprite_sheets_obj = null + //Captain's space suit, not hardsuits because no flashlight! +/obj/item/clothing/head/helmet/space/capspace + name = "captain's space helmet" + icon_state = "capspace" + item_state = "capspacehelmet" + desc = "A special helmet designed for only the most fashionable of military figureheads." + flags_inv = HIDEFACE + permeability_coefficient = 0.01 + armor = list("melee" = 40, "bullet" = 50, "laser" = 50, "energy" = 25, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) + species_restricted = list("exclude", "Wryn") + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi', + "Grey" = 'icons/mob/species/grey/helmet.dmi' + ) + +/obj/item/clothing/head/helmet/space/capspace/equipped(mob/living/carbon/human/user, slot) + if(ishuman(user) && slot == slot_head) + if(isvox(user)) + if(flags & BLOCKHAIR) + flags &= ~BLOCKHAIR + else + if((initial(flags) & BLOCKHAIR) && !(flags & BLOCKHAIR)) + flags |= BLOCKHAIR + +/obj/item/clothing/suit/space/captain + name = "captain's space suit" + desc = "A bulky, heavy-duty piece of exclusive Nanotrasen armor. YOU are in charge!" + icon_state = "caparmor" + item_state = "capspacesuit" + w_class = WEIGHT_CLASS_BULKY + allowed = list(/obj/item/tank, /obj/item/flashlight,/obj/item/gun/energy, /obj/item/gun/projectile, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton,/obj/item/restraints/handcuffs) + armor = list("melee" = 40, "bullet" = 50, "laser" = 50, "energy" = 25, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) + species_restricted = list("exclude", "Wryn") + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + + //Deathsquad space suit, not hardsuits because no flashlight! +/obj/item/clothing/head/helmet/space/deathsquad + name = "deathsquad helmet" + desc = "That's not red paint. That's real blood." + icon_state = "deathsquad" + item_state = "deathsquad" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE //don't render darkness while wearing these + see_in_dark = 8 + HUDType = MEDHUD + strip_delay = 130 + +/obj/item/clothing/suit/space/deathsquad + name = "deathsquad suit" + desc = "A heavily armored, advanced space suit that protects against most forms of damage." + icon_state = "deathsquad" + item_state = "swat_suit" + allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/tank,/obj/item/kitchen/knife/combat) + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + strip_delay = 130 + dog_fashion = /datum/dog_fashion/back/deathsquad + + //NEW SWAT suit +/obj/item/clothing/suit/space/swat + name = "SWAT armor" + desc = "Space-proof tactical SWAT armor." + icon_state = "heavy" + item_state = "swat_suit" + allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/tank,/obj/item/kitchen/knife/combat) + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) + strip_delay = 120 + resistance_flags = FIRE_PROOF | ACID_PROOF + species_restricted = list("exclude", "Wryn") + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/head/helmet/space/deathsquad/beret + name = "officer's beret" + desc = "An armored beret commonly used by special operations officers." + icon_state = "beret_officer" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + flags = STOPSPRESSUREDMAGE | THICKMATERIAL + +/obj/item/clothing/suit/space/deathsquad/officer + name = "officer jacket" + desc = "An armored jacket used in special operations." + icon_state = "detective" + item_state = "det_suit" + blood_overlay_type = "coat" + flags_inv = 0 + slowdown = 0 + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + w_class = WEIGHT_CLASS_NORMAL + +//Space santa outfit suit +/obj/item/clothing/head/helmet/space/santahat + name = "Santa's hat" + desc = "Ho ho ho. Merrry X-mas!" + icon_state = "santahat" + + sprite_sheets = list( + "Grey" = 'icons/mob/species/Grey/head.dmi', + "Drask" = 'icons/mob/species/Drask/helmet.dmi' + ) + flags = BLOCKHAIR | STOPSPRESSUREDMAGE + flags_cover = HEADCOVERSEYES + dog_fashion = /datum/dog_fashion/head/santa + +/obj/item/clothing/head/helmet/space/santahat/attack_self(mob/user as mob) + if(src.icon_state == "santahat") + src.icon_state = "santahat_beard" + src.item_state = "santahat_beard" + to_chat(user, "Santa's beard expands out from the hat!") + else + src.icon_state = "santahat" + src.item_state = "santahat" + to_chat(user, "The beard slinks back into the hat...") + +/obj/item/clothing/suit/space/santa + name = "Santa's suit" + desc = "Festive!" + icon_state = "santa" + item_state = "santa" + slowdown = 0 + flags = STOPSPRESSUREDMAGE + flags_size = ONESIZEFITSALL + allowed = list(/obj/item) //for stuffing extra special presents + +//Space pirate outfit +/obj/item/clothing/head/helmet/space/pirate + name = "pirate hat" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) + flags = BLOCKHAIR | STOPSPRESSUREDMAGE + flags_cover = HEADCOVERSEYES + strip_delay = 40 + put_on_delay = 20 + +/obj/item/clothing/suit/space/pirate + name = "pirate coat" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/tank) + slowdown = 0 + armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) + strip_delay = 40 + put_on_delay = 20 + +//Paramedic EVA suit +/obj/item/clothing/head/helmet/space/eva/paramedic + name = "Paramedic EVA helmet" + desc = "A brand new paramedic EVA helmet. It seems to mold to your head shape. Used for retrieving bodies in space." + icon_state = "paramedic-eva-helmet" + item_state = "paramedic-eva-helmet" + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 100, rad = 20, fire = 50, acid = 65) + species_restricted = list("exclude", "Wryn") + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi', + "Grey" = 'icons/mob/species/grey/helmet.dmi', + "Skrell" = 'icons/mob/species/skrell/helmet.dmi', + "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', + "Drask" = 'icons/mob/species/drask/helmet.dmi', + "Unathi" = 'icons/mob/species/unathi/helmet.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', + ) + sprite_sheets_obj = list( + "Vox" = 'icons/obj/clothing/species/vox/hats.dmi' + ) + +/obj/item/clothing/suit/space/eva/paramedic + name = "Paramedic EVA suit" + icon_state = "paramedic-eva" + item_state = "paramedic-eva" + desc = "A brand new paramedic EVA suit. The nitrile seems a bit too thin to be space proof. Used for retrieving bodies in space." + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 100, rad = 20, fire = 50, acid = 65) + species_restricted = list("exclude", "Wryn") + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi', + "Skrell" = 'icons/mob/species/skrell/suit.dmi', + "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', + "Drask" = 'icons/mob/species/drask/suit.dmi', + "Unathi" = 'icons/mob/species/unathi/suit.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', + ) + sprite_sheets_obj = list( + "Vox" = 'icons/obj/clothing/species/vox/suits.dmi' + ) + +/obj/item/clothing/suit/space/eva + name = "EVA suit" + icon_state = "spacenew" + item_state = "s_suit" + desc = "A lightweight space suit with the basic ability to protect the wearer from the vacuum of space during emergencies." + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 100, rad = 20, fire = 50, acid = 65) + species_restricted = list("exclude", "Wryn") + + sprite_sheets = list( + "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', + "Unathi" = 'icons/mob/species/unathi/suit.dmi', + "Vox" = 'icons/mob/species/vox/suit.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', + ) + sprite_sheets_obj = list( + "Tajaran" = 'icons/obj/clothing/species/tajaran/suits.dmi', + "Unathi" = 'icons/obj/clothing/species/unathi/suits.dmi', + "Vox" = 'icons/obj/clothing/species/vox/suits.dmi', + "Vulpkanin" = 'icons/obj/clothing/species/vulpkanin/suits.dmi' + ) + +/obj/item/clothing/head/helmet/space/eva + name = "EVA helmet" + icon_state = "spacenew" + item_state = "s_helmet" + desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies." + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 100, rad = 20, fire = 50, acid = 65) + flash_protect = 0 + species_restricted = list("exclude", "Wryn") + sprite_sheets = list( + "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', + "Unathi" = 'icons/mob/species/unathi/helmet.dmi', + "Vox" = 'icons/mob/species/vox/helmet.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', + "Grey" = 'icons/mob/species/grey/helmet.dmi' + ) + sprite_sheets_obj = list( + "Vox" = 'icons/obj/clothing/species/vox/hats.dmi', + "Vulpkanin" = 'icons/obj/clothing/species/vulpkanin/hats.dmi' + ) + +//Mime's Hardsuit +/obj/item/clothing/head/helmet/space/eva/mime + name = "mime eva helmet" +// icon = 'spaceciv.dmi' + desc = ". . ." + icon_state = "spacemimehelmet" + item_state = "spacemimehelmet" + species_restricted = list("exclude","Wryn") + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi') + sprite_sheets_obj = null + +/obj/item/clothing/suit/space/eva/mime + name = "mime eva suit" +// icon = 'spaceciv.dmi' + desc = ". . ." + icon_state = "spacemime_suit" + item_state = "spacemime_items" + species_restricted = list("exclude","Wryn") + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi') + sprite_sheets_obj = null + +/obj/item/clothing/head/helmet/space/eva/clown + name = "clown eva helmet" +// icon = 'spaceciv.dmi' + desc = "An EVA helmet specifically designed for the clown. SPESSHONK!" + icon_state = "clownhelmet" + item_state = "clownhelmet" + species_restricted = list("exclude","Wryn") + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi') + sprite_sheets_obj = null + +/obj/item/clothing/suit/space/eva/clown + name = "clown eva suit" +// icon = 'spaceciv.dmi' + desc = "An EVA suit specifically designed for the clown. SPESSHONK!" + icon_state = "spaceclown_suit" + item_state = "spaceclown_items" + species_restricted = list("exclude","Wryn") + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi') + sprite_sheets_obj = null diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm index bd35abb19a6..77d7b99fde0 100644 --- a/code/modules/clothing/spacesuits/plasmamen.dm +++ b/code/modules/clothing/spacesuits/plasmamen.dm @@ -258,4 +258,4 @@ name = "blueshield envirosuit helmet" desc = "A plasmaman envirohelm designed for the blueshield." icon_state = "bs_envirohelm" - item_state = "bs_envirohelm" \ No newline at end of file + item_state = "bs_envirohelm" diff --git a/code/modules/clothing/spacesuits/rig/modules/vision.dm b/code/modules/clothing/spacesuits/rig/modules/vision.dm index 9fc960cdbce..33ae007ceee 100644 --- a/code/modules/clothing/spacesuits/rig/modules/vision.dm +++ b/code/modules/clothing/spacesuits/rig/modules/vision.dm @@ -188,4 +188,4 @@ if(!vision) vision = vision_datum processed_vision += vision_datum - vision_modes = processed_vision \ No newline at end of file + vision_modes = processed_vision diff --git a/code/modules/clothing/spacesuits/rig/rig_verbs.dm b/code/modules/clothing/spacesuits/rig/rig_verbs.dm index 4e1782d2384..4a71f6c67bc 100644 --- a/code/modules/clothing/spacesuits/rig/rig_verbs.dm +++ b/code/modules/clothing/spacesuits/rig/rig_verbs.dm @@ -332,4 +332,4 @@ return to_chat(usr, "You attempt to engage the [module.interface_name].") - module.engage() \ No newline at end of file + module.engage() diff --git a/code/modules/clothing/spacesuits/rig/suits/ert_suits.dm b/code/modules/clothing/spacesuits/rig/suits/ert_suits.dm index fa32cbb7d51..5485e1fe882 100644 --- a/code/modules/clothing/spacesuits/rig/suits/ert_suits.dm +++ b/code/modules/clothing/spacesuits/rig/suits/ert_suits.dm @@ -78,4 +78,4 @@ /obj/item/rig_module/device/plasmacutter, // /obj/item/rig_module/device/rcd, /obj/item/rig_module/datajack - ) \ No newline at end of file + ) diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm index a1043eb2643..8c65ed02553 100644 --- a/code/modules/clothing/spacesuits/syndi.dm +++ b/code/modules/clothing/spacesuits/syndi.dm @@ -1,188 +1,188 @@ -//Regular syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate - name = "red space helmet" - desc = "Top secret Spess Helmet." - icon_state = "syndicate" - item_state = "syndicate" - desc = "Has a tag on it: Totally not property of a hostile corporation, honest!" - armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/helmet.dmi', - "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', - "Unathi" = 'icons/mob/species/unathi/helmet.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', - "Vox" = 'icons/mob/species/vox/helmet.dmi') - - -/obj/item/clothing/suit/space/syndicate - name = "red space suit" - icon_state = "syndicate" - item_state = "space_suit_syndicate" - desc = "Has a tag on it: Totally not property of a hostile corporation, honest!" - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword/saber,/obj/item/restraints/handcuffs,/obj/item/tank) - armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - sprite_sheets = list( - "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', - "Unathi" = 'icons/mob/species/unathi/suit.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', - "Vox" = 'icons/mob/species/vox/suit.dmi') - - -//Green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/green - name = "Green Space Helmet" - icon_state = "syndicate-helm-green" - item_state = "syndicate-helm-green" - -/obj/item/clothing/suit/space/syndicate/green - name = "Green Space Suit" - icon_state = "syndicate-green" - item_state = "syndicate-green" - - -//Dark green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/green/dark - name = "Dark Green Space Helmet" - icon_state = "syndicate-helm-green-dark" - item_state = "syndicate-helm-green-dark" - -/obj/item/clothing/suit/space/syndicate/green/dark - name = "Dark Green Space Suit" - icon_state = "syndicate-green-dark" - item_state = "syndicate-green-dark" - - -//Orange syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/orange - name = "Orange Space Helmet" - icon_state = "syndicate-helm-orange" - item_state = "syndicate-helm-orange" - -/obj/item/clothing/suit/space/syndicate/orange - name = "Orange Space Suit" - icon_state = "syndicate-orange" - item_state = "syndicate-orange" - - -//Blue syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/blue - name = "Blue Space Helmet" - icon_state = "syndicate-helm-blue" - item_state = "syndicate-helm-blue" - -/obj/item/clothing/suit/space/syndicate/blue - name = "Blue Space Suit" - icon_state = "syndicate-blue" - item_state = "syndicate-blue" - - -//Black syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black - name = "Black Space Helmet" - icon_state = "syndicate-helm-black" - item_state = "syndicate-helm-black" - -obj/item/clothing/head/helmet/space/syndicate/black/strike - name = "Syndicate Strike Team commando helmet" - desc = "A heavily armored black helmet that is only given to high-ranking Syndicate operatives." - armor = list(melee = 80, bullet = 80, laser = 50, energy = 50, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100) //Matches DS gear. - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF - -/obj/item/clothing/suit/space/syndicate/black - name = "Black Space Suit" - icon_state = "syndicate-black" - item_state = "syndicate-black" - -obj/item/clothing/suit/space/syndicate/black/strike - name = "Syndicate Strike Team commando space suit" - desc = "A heavily armored, black space suit that is only given to high-ranking Syndicate operatives." - armor = list(melee = 80, bullet = 80, laser = 50, energy = 50, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100) //Matches DS gear. - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF - -//Black-green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/green - name = "Black Space Helmet" - icon_state = "syndicate-helm-black-green" - item_state = "syndicate-helm-black-green" - -/obj/item/clothing/suit/space/syndicate/black/green - name = "Black and Green Space Suit" - icon_state = "syndicate-black-green" - item_state = "syndicate-black-green" - - -//Black-blue syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/blue - name = "Black Space Helmet" - icon_state = "syndicate-helm-black-blue" - item_state = "syndicate-helm-black-blue" - -/obj/item/clothing/suit/space/syndicate/black/blue - name = "Black and Blue Space Suit" - icon_state = "syndicate-black-blue" - item_state = "syndicate-black-blue" - - -//Black medical syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/med - name = "Black Space Helmet" - icon_state = "syndicate-helm-black-med" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/med - name = "Green Space Suit" - icon_state = "syndicate-black-med" - item_state = "syndicate-black" - - -//Black-orange syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/orange - name = "Black Space Helmet" - icon_state = "syndicate-helm-black-orange" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/orange - name = "Black and Orange Space Suit" - icon_state = "syndicate-black-orange" - item_state = "syndicate-black" - - -//Black-red syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/red - name = "Black Space Helmet" - icon_state = "syndicate-helm-black-red" - item_state = "syndicate-helm-black-red" - -obj/item/clothing/head/helmet/space/syndicate/black/red/strike - name = "Syndicate Strike Team leader helmet" - desc = "A heavily armored, black and red space helmet that is only given to elite Syndicate operatives, it looks particularly menacing." - armor = list(melee = 80, bullet = 80, laser = 50, energy = 50, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100) //Matches DS gear. - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF - -/obj/item/clothing/suit/space/syndicate/black/red - name = "Black and Red Space Suit" - icon_state = "syndicate-black-red" - item_state = "syndicate-black-red" - -obj/item/clothing/suit/space/syndicate/black/red/strike - name = "Syndicate Strike Team leader space suit" - desc = "A heavily armored, black and red space suit that is only given to elite Syndicate operatives, it looks particularly menacing." - armor = list(melee = 80, bullet = 80, laser = 50, energy = 50, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100) //Matches DS gear. - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = ACID_PROOF - - -//Black with yellow/red engineering syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black/engie - name = "Black Space Helmet" - icon_state = "syndicate-helm-black-engie" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/engie - name = "Black Engineering Space Suit" - icon_state = "syndicate-black-engie" - item_state = "syndicate-black" +//Regular syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate + name = "red space helmet" + desc = "Top secret Spess Helmet." + icon_state = "syndicate" + item_state = "syndicate" + desc = "Has a tag on it: Totally not property of a hostile corporation, honest!" + armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/helmet.dmi', + "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', + "Unathi" = 'icons/mob/species/unathi/helmet.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', + "Vox" = 'icons/mob/species/vox/helmet.dmi') + + +/obj/item/clothing/suit/space/syndicate + name = "red space suit" + icon_state = "syndicate" + item_state = "space_suit_syndicate" + desc = "Has a tag on it: Totally not property of a hostile corporation, honest!" + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword/saber,/obj/item/restraints/handcuffs,/obj/item/tank) + armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + sprite_sheets = list( + "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', + "Unathi" = 'icons/mob/species/unathi/suit.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', + "Vox" = 'icons/mob/species/vox/suit.dmi') + + +//Green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/green + name = "Green Space Helmet" + icon_state = "syndicate-helm-green" + item_state = "syndicate-helm-green" + +/obj/item/clothing/suit/space/syndicate/green + name = "Green Space Suit" + icon_state = "syndicate-green" + item_state = "syndicate-green" + + +//Dark green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/green/dark + name = "Dark Green Space Helmet" + icon_state = "syndicate-helm-green-dark" + item_state = "syndicate-helm-green-dark" + +/obj/item/clothing/suit/space/syndicate/green/dark + name = "Dark Green Space Suit" + icon_state = "syndicate-green-dark" + item_state = "syndicate-green-dark" + + +//Orange syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/orange + name = "Orange Space Helmet" + icon_state = "syndicate-helm-orange" + item_state = "syndicate-helm-orange" + +/obj/item/clothing/suit/space/syndicate/orange + name = "Orange Space Suit" + icon_state = "syndicate-orange" + item_state = "syndicate-orange" + + +//Blue syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/blue + name = "Blue Space Helmet" + icon_state = "syndicate-helm-blue" + item_state = "syndicate-helm-blue" + +/obj/item/clothing/suit/space/syndicate/blue + name = "Blue Space Suit" + icon_state = "syndicate-blue" + item_state = "syndicate-blue" + + +//Black syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black + name = "Black Space Helmet" + icon_state = "syndicate-helm-black" + item_state = "syndicate-helm-black" + +obj/item/clothing/head/helmet/space/syndicate/black/strike + name = "Syndicate Strike Team commando helmet" + desc = "A heavily armored black helmet that is only given to high-ranking Syndicate operatives." + armor = list(melee = 80, bullet = 80, laser = 50, energy = 50, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100) //Matches DS gear. + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF + +/obj/item/clothing/suit/space/syndicate/black + name = "Black Space Suit" + icon_state = "syndicate-black" + item_state = "syndicate-black" + +obj/item/clothing/suit/space/syndicate/black/strike + name = "Syndicate Strike Team commando space suit" + desc = "A heavily armored, black space suit that is only given to high-ranking Syndicate operatives." + armor = list(melee = 80, bullet = 80, laser = 50, energy = 50, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100) //Matches DS gear. + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF + +//Black-green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/green + name = "Black Space Helmet" + icon_state = "syndicate-helm-black-green" + item_state = "syndicate-helm-black-green" + +/obj/item/clothing/suit/space/syndicate/black/green + name = "Black and Green Space Suit" + icon_state = "syndicate-black-green" + item_state = "syndicate-black-green" + + +//Black-blue syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/blue + name = "Black Space Helmet" + icon_state = "syndicate-helm-black-blue" + item_state = "syndicate-helm-black-blue" + +/obj/item/clothing/suit/space/syndicate/black/blue + name = "Black and Blue Space Suit" + icon_state = "syndicate-black-blue" + item_state = "syndicate-black-blue" + + +//Black medical syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/med + name = "Black Space Helmet" + icon_state = "syndicate-helm-black-med" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/med + name = "Green Space Suit" + icon_state = "syndicate-black-med" + item_state = "syndicate-black" + + +//Black-orange syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/orange + name = "Black Space Helmet" + icon_state = "syndicate-helm-black-orange" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/orange + name = "Black and Orange Space Suit" + icon_state = "syndicate-black-orange" + item_state = "syndicate-black" + + +//Black-red syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/red + name = "Black Space Helmet" + icon_state = "syndicate-helm-black-red" + item_state = "syndicate-helm-black-red" + +obj/item/clothing/head/helmet/space/syndicate/black/red/strike + name = "Syndicate Strike Team leader helmet" + desc = "A heavily armored, black and red space helmet that is only given to elite Syndicate operatives, it looks particularly menacing." + armor = list(melee = 80, bullet = 80, laser = 50, energy = 50, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100) //Matches DS gear. + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF + +/obj/item/clothing/suit/space/syndicate/black/red + name = "Black and Red Space Suit" + icon_state = "syndicate-black-red" + item_state = "syndicate-black-red" + +obj/item/clothing/suit/space/syndicate/black/red/strike + name = "Syndicate Strike Team leader space suit" + desc = "A heavily armored, black and red space suit that is only given to elite Syndicate operatives, it looks particularly menacing." + armor = list(melee = 80, bullet = 80, laser = 50, energy = 50, bomb = 100, bio = 100, rad = 100, fire = 100, acid = 100) //Matches DS gear. + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = ACID_PROOF + + +//Black with yellow/red engineering syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black/engie + name = "Black Space Helmet" + icon_state = "syndicate-helm-black-engie" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/engie + name = "Black Engineering Space Suit" + icon_state = "syndicate-black-engie" + item_state = "syndicate-black" diff --git a/code/modules/clothing/spacesuits/void.dm b/code/modules/clothing/spacesuits/void.dm index 0f97be88e10..04c5edf20ec 100644 --- a/code/modules/clothing/spacesuits/void.dm +++ b/code/modules/clothing/spacesuits/void.dm @@ -1,124 +1,124 @@ - -//Voidsuits -/obj/item/clothing/head/helmet/space/nasavoid - name = "NASA Void Helmet" - desc = "A high tech, NASA Centcom branch designed space suit helmet. Used for AI satellite maintenance." - icon_state = "void-red" - item_state = "void" - flags_inv = HIDEMASK|HIDEEARS - sprite_sheets = list( - "Grey" = 'icons/mob/species/grey/helmet.dmi', - "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', - "Unathi" = 'icons/mob/species/unathi/helmet.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', - "Vox" = 'icons/mob/species/vox/helmet.dmi') - -/obj/item/clothing/suit/space/nasavoid - name = "NASA Void Suit" - icon_state = "void-red" - item_state = "void" - desc = "A high tech, NASA Centcom branch designed space suit. Used for AI satellite maintenance." - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/multitool) - sprite_sheets = list( - "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', - "Unathi" = 'icons/mob/species/unathi/suit.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', - "Vox" = 'icons/mob/species/vox/suit.dmi') - -//Colors!!! -/obj/item/clothing/head/helmet/space/nasavoid/green - icon_state = "void-green" - -/obj/item/clothing/suit/space/nasavoid/green - icon_state = "void-green" - -/obj/item/clothing/head/helmet/space/nasavoid/ntblue - icon_state = "void-ntblue" - -/obj/item/clothing/suit/space/nasavoid/ntblue - icon_state = "void-ntblue" - -/obj/item/clothing/head/helmet/space/nasavoid/purple - icon_state = "void-purple" - -/obj/item/clothing/suit/space/nasavoid/purple - icon_state = "void-purple" - -/obj/item/clothing/head/helmet/space/nasavoid/yellow - icon_state = "void-yellow" - -/obj/item/clothing/suit/space/nasavoid/yellow - icon_state = "void-yellow" - -/obj/item/clothing/head/helmet/space/nasavoid/ltblue - icon_state = "void-light_blue" - -/obj/item/clothing/suit/space/nasavoid/ltblue - icon_state = "void-light_blue" - - -//Captian's Suit, like the other captian's suit, but looks better, at the cost of armor -/obj/item/clothing/head/helmet/space/nasavoid/captain - name = "Fancy Retro Void Helmet" - icon_state = "void-captian" - desc = "A high tech, NASA Centcom branch designed space suit helmet. Used for AI satellite maintenance. This one is fit for a captain." - -/obj/item/clothing/suit/space/nasavoid/captain - name = "Fancy NASA Void Suit" - icon_state = "void-captian" - desc = "A high tech, NASA Centcom branch designed space suit. Used for AI satellite maintenance. This one is fit for a captain." - -//Syndi's suit, on par with a blood red softsuit - -/obj/item/clothing/head/helmet/space/nasavoid/syndi - name = "Blood Red Retro Void Helmet" - icon_state = "void-syndi" - desc = "A high tech, NASA Centcom branch designed space suit helmet. This one looks rather suspicious." - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE - armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - -/obj/item/clothing/suit/space/nasavoid/syndi - name = "Blood Red NASA Void Suit" - icon_state = "void-syndi" - desc = "A high tech, NASA Centcom branch designed space suit. This one looks rather suspicious." - w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword/saber,/obj/item/restraints/handcuffs,/obj/item/tank) - armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - -//random spawner - -/obj/effect/nasavoidsuitspawner - name = "NASA Void Suit Spawner" - icon = 'icons/obj/clothing/suits.dmi' - icon_state = "void-red" - desc = "You shouldn't see this, a spawner for NASA Void Suits." - var/suits = list("red", "green", "ntblue", "purple", "yellow", "ltblue") - -/obj/effect/nasavoidsuitspawner/New() - var/obj/item/clothing/head/helmet/space/nasavoid/H - var/obj/item/clothing/suit/space/nasavoid/S - switch(pick(suits)) - if("red") - H = new /obj/item/clothing/head/helmet/space/nasavoid - S = new /obj/item/clothing/suit/space/nasavoid - if("green") - H = new /obj/item/clothing/head/helmet/space/nasavoid/green - S = new /obj/item/clothing/suit/space/nasavoid/green - if("ntblue") - H = new /obj/item/clothing/head/helmet/space/nasavoid/ntblue - S = new /obj/item/clothing/suit/space/nasavoid/ntblue - if("purple") - H = new /obj/item/clothing/head/helmet/space/nasavoid/purple - S = new /obj/item/clothing/suit/space/nasavoid/purple - if("yellow") - H = new /obj/item/clothing/head/helmet/space/nasavoid/yellow - S = new /obj/item/clothing/suit/space/nasavoid/yellow - if("ltblue") - H = new /obj/item/clothing/head/helmet/space/nasavoid/ltblue - S = new /obj/item/clothing/suit/space/nasavoid/ltblue - var/turf/T = get_turf(src) - if(H) - H.forceMove(T) - if(S) - S.forceMove(T) - qdel(src) \ No newline at end of file + +//Voidsuits +/obj/item/clothing/head/helmet/space/nasavoid + name = "NASA Void Helmet" + desc = "A high tech, NASA Centcom branch designed space suit helmet. Used for AI satellite maintenance." + icon_state = "void-red" + item_state = "void" + flags_inv = HIDEMASK|HIDEEARS + sprite_sheets = list( + "Grey" = 'icons/mob/species/grey/helmet.dmi', + "Tajaran" = 'icons/mob/species/tajaran/helmet.dmi', + "Unathi" = 'icons/mob/species/unathi/helmet.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/helmet.dmi', + "Vox" = 'icons/mob/species/vox/helmet.dmi') + +/obj/item/clothing/suit/space/nasavoid + name = "NASA Void Suit" + icon_state = "void-red" + item_state = "void" + desc = "A high tech, NASA Centcom branch designed space suit. Used for AI satellite maintenance." + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/multitool) + sprite_sheets = list( + "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', + "Unathi" = 'icons/mob/species/unathi/suit.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', + "Vox" = 'icons/mob/species/vox/suit.dmi') + +//Colors!!! +/obj/item/clothing/head/helmet/space/nasavoid/green + icon_state = "void-green" + +/obj/item/clothing/suit/space/nasavoid/green + icon_state = "void-green" + +/obj/item/clothing/head/helmet/space/nasavoid/ntblue + icon_state = "void-ntblue" + +/obj/item/clothing/suit/space/nasavoid/ntblue + icon_state = "void-ntblue" + +/obj/item/clothing/head/helmet/space/nasavoid/purple + icon_state = "void-purple" + +/obj/item/clothing/suit/space/nasavoid/purple + icon_state = "void-purple" + +/obj/item/clothing/head/helmet/space/nasavoid/yellow + icon_state = "void-yellow" + +/obj/item/clothing/suit/space/nasavoid/yellow + icon_state = "void-yellow" + +/obj/item/clothing/head/helmet/space/nasavoid/ltblue + icon_state = "void-light_blue" + +/obj/item/clothing/suit/space/nasavoid/ltblue + icon_state = "void-light_blue" + + +//Captian's Suit, like the other captian's suit, but looks better, at the cost of armor +/obj/item/clothing/head/helmet/space/nasavoid/captain + name = "Fancy Retro Void Helmet" + icon_state = "void-captian" + desc = "A high tech, NASA Centcom branch designed space suit helmet. Used for AI satellite maintenance. This one is fit for a captain." + +/obj/item/clothing/suit/space/nasavoid/captain + name = "Fancy NASA Void Suit" + icon_state = "void-captian" + desc = "A high tech, NASA Centcom branch designed space suit. Used for AI satellite maintenance. This one is fit for a captain." + +//Syndi's suit, on par with a blood red softsuit + +/obj/item/clothing/head/helmet/space/nasavoid/syndi + name = "Blood Red Retro Void Helmet" + icon_state = "void-syndi" + desc = "A high tech, NASA Centcom branch designed space suit helmet. This one looks rather suspicious." + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE + armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + +/obj/item/clothing/suit/space/nasavoid/syndi + name = "Blood Red NASA Void Suit" + icon_state = "void-syndi" + desc = "A high tech, NASA Centcom branch designed space suit. This one looks rather suspicious." + w_class = WEIGHT_CLASS_NORMAL + allowed = list(/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword/saber,/obj/item/restraints/handcuffs,/obj/item/tank) + armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + +//random spawner + +/obj/effect/nasavoidsuitspawner + name = "NASA Void Suit Spawner" + icon = 'icons/obj/clothing/suits.dmi' + icon_state = "void-red" + desc = "You shouldn't see this, a spawner for NASA Void Suits." + var/suits = list("red", "green", "ntblue", "purple", "yellow", "ltblue") + +/obj/effect/nasavoidsuitspawner/New() + var/obj/item/clothing/head/helmet/space/nasavoid/H + var/obj/item/clothing/suit/space/nasavoid/S + switch(pick(suits)) + if("red") + H = new /obj/item/clothing/head/helmet/space/nasavoid + S = new /obj/item/clothing/suit/space/nasavoid + if("green") + H = new /obj/item/clothing/head/helmet/space/nasavoid/green + S = new /obj/item/clothing/suit/space/nasavoid/green + if("ntblue") + H = new /obj/item/clothing/head/helmet/space/nasavoid/ntblue + S = new /obj/item/clothing/suit/space/nasavoid/ntblue + if("purple") + H = new /obj/item/clothing/head/helmet/space/nasavoid/purple + S = new /obj/item/clothing/suit/space/nasavoid/purple + if("yellow") + H = new /obj/item/clothing/head/helmet/space/nasavoid/yellow + S = new /obj/item/clothing/suit/space/nasavoid/yellow + if("ltblue") + H = new /obj/item/clothing/head/helmet/space/nasavoid/ltblue + S = new /obj/item/clothing/suit/space/nasavoid/ltblue + var/turf/T = get_turf(src) + if(H) + H.forceMove(T) + if(S) + S.forceMove(T) + qdel(src) diff --git a/code/modules/clothing/suits/alien.dm b/code/modules/clothing/suits/alien.dm index 0f82a9eae7e..1fde5cd6f84 100644 --- a/code/modules/clothing/suits/alien.dm +++ b/code/modules/clothing/suits/alien.dm @@ -16,4 +16,4 @@ desc = "A rather grisly selection of cured hides and skin, sewn together to form a ragged mantle." icon_state = "mantle-unathi" item_state = "mantle-unathi" - body_parts_covered = UPPER_TORSO \ No newline at end of file + body_parts_covered = UPPER_TORSO diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 037e967f5ee..e9f78936cf7 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -1,544 +1,544 @@ -/obj/item/clothing/suit/armor - allowed = list(/obj/item/gun/energy,/obj/item/reagent_containers/spray/pepper,/obj/item/gun/projectile,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/flashlight/seclite,/obj/item/melee/classic_baton/telescopic,/obj/item/kitchen/knife/combat) - body_parts_covered = UPPER_TORSO|LOWER_TORSO - cold_protection = UPPER_TORSO|LOWER_TORSO - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - heat_protection = UPPER_TORSO|LOWER_TORSO - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - strip_delay = 60 - put_on_delay = 40 - max_integrity = 250 - resistance_flags = NONE - armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/suit/armor/vest - name = "armor" - desc = "An armored vest that protects against some damage." - icon_state = "armor" - item_state = "armor" - blood_overlay_type = "armor" - flags_size = ONESIZEFITSALL - dog_fashion = /datum/dog_fashion/back - -/obj/item/clothing/suit/armor/vest/jacket - name = "military jacket" - desc = "An old military jacket, it has armoring." - icon_state = "militaryjacket" - item_state = "militaryjacket" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - -/obj/item/clothing/suit/armor/vest/combat - name = "combat vest" - desc = "An armored vest that protects against some damage." - icon_state = "armor-combat" - item_state = "bulletproof" - blood_overlay_type = "armor" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/suit/armor/vest/security - name = "security armor" - desc = "An armored vest that protects against some damage. This one has a clip for a holobadge." - icon_state = "armor" - item_state = "armor" - var/obj/item/clothing/accessory/holobadge/attached_badge - -/obj/item/clothing/suit/armor/vest/security/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/clothing/accessory/holobadge)) - if(user.unEquip(W)) - add_fingerprint(user) - W.forceMove(src) - attached_badge = W - - var/datum/action/A = new /datum/action/item_action/remove_badge(src) - A.Grant(user) - icon_state = "armorsec" - user.update_inv_wear_suit() - desc = "An armored vest that protects against some damage. This one has [attached_badge] attached to it." - to_chat(user, "You attach [attached_badge] to [src].") - return - ..() - -/obj/item/clothing/suit/armor/vest/security/attack_self(mob/user as mob) - if(attached_badge) - add_fingerprint(user) - user.put_in_hands(attached_badge) - - for(var/X in actions) - var/datum/action/A = X - A.Remove(user) - - icon_state = "armor" - user.update_inv_wear_suit() - desc = "An armored vest that protects against some damage. This one has a clip for a holobadge." - to_chat(user, "You remove [attached_badge] from [src].") - - attached_badge = null - - return - - ..() - -/obj/item/clothing/suit/armor/vest/blueshield - name = "blueshield security armor" - desc = "An armored vest with the badge of a Blueshield Lieutenant." - icon_state = "blueshield" - item_state = "blueshield" - -/obj/item/clothing/suit/armor/vest/bloody - name = "bloodied security armor" - desc = "A vest drenched in the blood of Greytide. It has seen better days." - icon_state = "bloody_armor" - item_state = "bloody_armor" - sprite_sheets = null - -/obj/item/clothing/suit/armor/secjacket - name = "security jacket" - desc = "A sturdy black jacket with reinforced fabric. Bears insignia of NT corporate security." - icon_state = "secjacket_open" - item_state = "hos" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - armor = list(melee = 15, bullet = 10, laser = 15, energy = 5, bomb = 15, bio = 0, rad = 0, fire = 30, acid = 30) - cold_protection = UPPER_TORSO|LOWER_TORSO|ARMS - heat_protection = UPPER_TORSO|LOWER_TORSO|ARMS - ignore_suitadjust = 0 - suit_adjusted = 1 - actions_types = list(/datum/action/item_action/openclose) - adjust_flavour = "unzip" - -/obj/item/clothing/suit/armor/hos - name = "armored coat" - desc = "A trench coat enhanced with a special alloy for some protection and style." - icon_state = "hos" - item_state = "hos" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS - armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90) - flags_inv = HIDEJUMPSUIT - cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - strip_delay = 80 - -/obj/item/clothing/suit/armor/hos/alt - name = "armored trenchoat" - desc = "A trenchcoat enhanced with a special lightweight kevlar. The epitome of tactical plainclothes." - icon_state = "hostrench_open" - item_state = "hostrench_open" - flags_inv = 0 - ignore_suitadjust = 0 - suit_adjusted = 1 - actions_types = list(/datum/action/item_action/openclose) - adjust_flavour = "unbutton" - -/obj/item/clothing/suit/armor/hos/jensen - name = "armored trenchcoat" - desc = "A trenchcoat augmented with a special alloy for some protection and style." - icon_state = "jensencoat" - item_state = "jensencoat" - flags_inv = 0 - sprite_sheets = null - -/obj/item/clothing/suit/armor/vest/warden - name = "Warden's armored jacket" - desc = "An armored jacket with silver rank pips and livery." - icon_state = "warden_jacket" - item_state = "armor" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - cold_protection = UPPER_TORSO|LOWER_TORSO|HANDS - heat_protection = UPPER_TORSO|LOWER_TORSO|HANDS - strip_delay = 70 - resistance_flags = FLAMMABLE - dog_fashion = null - -/obj/item/clothing/suit/armor/vest/warden/alt - name = "warden's jacket" - desc = "A navy-blue armored jacket with blue shoulder designations and '/Warden/' stitched into one of the chest pockets." - icon_state = "warden_jacket_alt" - -/obj/item/clothing/suit/armor/vest/capcarapace - name = "captain's carapace" - desc = "An armored vest reinforced with ceramic plates and pauldrons to provide additional protection whilst still offering maximum mobility and flexibility. Issued only to the station's finest, although it does chafe your nipples." - icon_state = "capcarapace" - item_state = "armor" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 90) - dog_fashion = null - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/armor/vest/capcarapace/alt - name = "captain's parade jacket" - desc = "For when an armored vest isn't fashionable enough." - icon_state = "capformal" - item_state = "capspacesuit" - -/obj/item/clothing/suit/armor/riot - name = "Riot Suit" - desc = "A suit of armor with heavy padding to protect against melee attacks. Looks like it might impair movement." - icon_state = "riot" - item_state = "swat_suit" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - flags_inv = HIDEJUMPSUIT - strip_delay = 80 - put_on_delay = 60 - hide_tail_by_species = list("Vox") - -/obj/item/clothing/suit/armor/riot/knight - name = "plate armour" - desc = "A classic suit of plate armour, highly effective at stopping melee attacks." - icon_state = "knight_green" - item_state = "knight_green" - slowdown = 1 - -/obj/item/clothing/suit/armor/riot/knight/yellow - icon_state = "knight_yellow" - item_state = "knight_yellow" - -/obj/item/clothing/suit/armor/riot/knight/blue - icon_state = "knight_blue" - item_state = "knight_blue" - -/obj/item/clothing/suit/armor/riot/knight/red - icon_state = "knight_red" - item_state = "knight_red" - -/obj/item/clothing/suit/armor/riot/knight/templar - name = "crusader armour" - desc = "God wills it!" - icon_state = "knight_templar" - item_state = "knight_templar" - allowed = list(/obj/item/nullrod/claymore) - armor = list(melee = 25, bullet = 5, laser = 5, energy = 5, bomb = 0, bio = 0, rad = 0, fire = 80, acid = 80) - -/obj/item/clothing/suit/armor/vest/durathread - name = "durathread vest" - desc = "A vest made of durathread with strips of leather acting as trauma plates." - icon_state = "durathread" - item_state = "durathread" - strip_delay = 60 - max_integrity = 200 - resistance_flags = FLAMMABLE - armor = list("melee" = 20, "bullet" = 10, "laser" = 30, "energy" = 5, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 50) - -/obj/item/clothing/suit/armor/bulletproof - name = "Bulletproof Vest" - desc = "A bulletproof vest that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent." - icon_state = "bulletproof" - item_state = "armor" - blood_overlay_type = "armor" - armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 70 - put_on_delay = 50 - -/obj/item/clothing/suit/armor/laserproof - name = "Ablative Armor Vest" - desc = "A vest that excels in protecting the wearer against energy projectiles." - icon_state = "armor_reflec" - item_state = "armor_reflec" - blood_overlay_type = "armor" - armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 50, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - var/hit_reflect_chance = 40 - -/obj/item/clothing/suit/armor/laserproof/IsReflect(var/def_zone) - if(!(def_zone in list("chest", "groin"))) //If not shot where ablative is covering you, you don't get the reflection bonus! - return 0 - if(prob(hit_reflect_chance)) - return 1 - -/obj/item/clothing/suit/armor/vest/det_suit - name = "armor" - desc = "An armored vest with a detective's badge on it." - icon_state = "detective-armor" - item_state = "armor" - blood_overlay_type = "armor" - flags_size = ONESIZEFITSALL - allowed = list(/obj/item/tank/emergency_oxygen,/obj/item/reagent_containers/spray/pepper,/obj/item/flashlight,/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/storage/fancy/cigarettes,/obj/item/lighter,/obj/item/detective_scanner,/obj/item/taperecorder) - resistance_flags = FLAMMABLE - dog_fashion = null - -//Reactive armor -/obj/item/clothing/suit/armor/reactive - name = "reactive armor" - desc = "Doesn't seem to do much for some reason." - var/active = 0 - icon_state = "reactiveoff" - item_state = "reactiveoff" - blood_overlay_type = "armor" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - actions_types = list(/datum/action/item_action/toggle) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - hit_reaction_chance = 50 - -/obj/item/clothing/suit/armor/reactive/attack_self(mob/user) - active = !(active) - if(active) - to_chat(user, "[src] is now active.") - icon_state = "reactive" - item_state = "reactive" - else - to_chat(user, "[src] is now inactive.") - icon_state = "reactiveoff" - item_state = "reactiveoff" - add_fingerprint(user) - user.update_inv_wear_suit() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/clothing/suit/armor/reactive/emp_act(severity) - active = 0 - icon_state = "reactiveoff" - item_state = "reactiveoff" - if(istype(loc, /mob/living/carbon/human)) - var/mob/living/carbon/human/C = loc - C.update_inv_wear_suit() - ..() - -//When the wearer gets hit, this armor will teleport the user a short distance away (to safety or to more danger, no one knows. That's the fun of it!) -/obj/item/clothing/suit/armor/reactive/teleport - name = "reactive teleport armor" - desc = "Someone seperated our Research Director from his own head!" - var/tele_range = 6 - -/obj/item/clothing/suit/armor/reactive/teleport/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!active) - return 0 - if(prob(hit_reaction_chance)) - var/mob/living/carbon/human/H = owner - owner.visible_message("The reactive teleport system flings [H] clear of [attack_text]!") - var/list/turfs = new/list() - for(var/turf/T in orange(tele_range, H)) - if(istype(T, /turf/space)) - continue - if(T.density) - continue - if(T.x>world.maxx-tele_range || T.xworld.maxy-tele_range || T.yThe [src] blocks the [attack_text], sending out jets of flame!") - for(var/mob/living/carbon/C in range(6, owner)) - if(C != owner) - C.fire_stacks += 8 - C.IgniteMob() - owner.fire_stacks = -20 - return 1 - return 0 - - -/obj/item/clothing/suit/armor/reactive/stealth - name = "reactive stealth armor" - -/obj/item/clothing/suit/armor/reactive/stealth/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!active) - return 0 - if(prob(hit_reaction_chance)) - var/mob/living/simple_animal/hostile/illusion/escape/E = new(owner.loc) - E.Copy_Parent(owner, 50) - E.GiveTarget(owner) //so it starts running right away - E.Goto(owner, E.move_to_delay, E.minimum_distance) - owner.alpha = 0 - owner.visible_message("[owner] is hit by [attack_text] in the chest!") //We pretend to be hit, since blocking it would stop the message otherwise - spawn(40) - owner.alpha = initial(owner.alpha) - return 1 - -/obj/item/clothing/suit/armor/reactive/tesla - name = "reactive tesla armor" - -/obj/item/clothing/suit/armor/reactive/tesla/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!active) - return 0 - if(prob(hit_reaction_chance)) - owner.visible_message("The [src] blocks the [attack_text], sending out arcs of lightning!") - for(var/mob/living/M in view(6, owner)) - if(M == owner) - continue - owner.Beam(M,icon_state="lightning[rand(1, 12)]",icon='icons/effects/effects.dmi',time=5) - M.adjustFireLoss(25) - playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1) - return 1 - -//All of the armor below is mostly unused - - -/obj/item/clothing/suit/armor/centcomm - name = "Cent. Com. armor" - desc = "A suit that protects against some damage." - icon_state = "centcom" - item_state = "centcom" - w_class = WEIGHT_CLASS_BULKY - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/gun/energy,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/tank/emergency_oxygen) - flags = THICKMATERIAL - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - sprite_sheets = null - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) - -/obj/item/clothing/suit/armor/heavy - name = "heavy armor" - desc = "A heavily armored suit that protects against moderate damage." - icon_state = "heavy" - item_state = "swat_suit" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.90 - flags = THICKMATERIAL - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - slowdown = 3 - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - hide_tail_by_species = list("Vox") - -/obj/item/clothing/suit/armor/tdome - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - flags = THICKMATERIAL - cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - hide_tail_by_species = list("Vox") - -/obj/item/clothing/suit/armor/tdome/red - name = "Red Thunderdome Armor" - desc = "Armor worn by the red Thunderdome team." - icon_state = "tdred" - item_state = "tdred" - -/obj/item/clothing/suit/armor/tdome/green - name = "Green Thunderdome Armor" - desc = "Armor worn by the green Thunderdome team." - icon_state = "tdgreen" - item_state = "tdgreen" - -//Non-hardsuit ERT armor. -/obj/item/clothing/suit/armor/vest/ert - name = "emergency response team armor" - desc = "A set of armor worn by members of the Nanotrasen Emergency Response Team." - icon_state = "ertarmor_cmd" - item_state = "armor" - armor = list(melee = 30, bullet = 30, laser = 30, energy = 30, bomb = 20, bio = 0, rad = 0, fire = 50, acid = 50) - -//Commander -/obj/item/clothing/suit/armor/vest/ert/command - name = "emergency response team commander armor" - desc = "A set of armor worn by the commander of a Nanotrasen Emergency Response Team. Has blue highlights." - -//Security -/obj/item/clothing/suit/armor/vest/ert/security - name = "emergency response team security armor" - desc = "A set of armor worn by security members of the Nanotrasen Emergency Response Team. Has red highlights." - icon_state = "ertarmor_sec" - - -/obj/item/clothing/suit/armor/vest/ert/security/paranormal - name = "emergency response team paranormal armor" - desc = "A set of armor worn by paranormal members of the Nanotrasen Emergency Response Team. Has crusader sigils." - icon_state = "knight_templar" - item_state = "knight_templar" - -//Engineer -/obj/item/clothing/suit/armor/vest/ert/engineer - name = "emergency response team engineer armor" - desc = "A set of armor worn by engineering members of the Nanotrasen Emergency Response Team. Has orange highlights." - icon_state = "ertarmor_eng" - -//Medical -/obj/item/clothing/suit/armor/vest/ert/medical - name = "emergency response team medical armor" - desc = "A set of armor worn by medical members of the Nanotrasen Emergency Response Team. Has red and white highlights." - icon_state = "ertarmor_med" - -//Janitorial -/obj/item/clothing/suit/armor/vest/ert/janitor - name = "emergency response team janitor armor" - desc = "A set of armor worn by janitorial members of the Nanotrasen Emergency Response Team. Has red and white highlights." - icon_state = "ertarmor_jan" - -//same defense as basic sec armor -/obj/item/clothing/suit/storage/lawyer/blackjacket/armored - desc = "A snappy dress jacket, reinforced with a layer of armor protecting the torso." - allowed = list(/obj/item/tank/emergency_oxygen, /obj/item/gun/projectile/revolver, /obj/item/gun/projectile/automatic/pistol) - body_parts_covered = UPPER_TORSO|LOWER_TORSO - cold_protection = UPPER_TORSO|LOWER_TORSO - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - heat_protection = UPPER_TORSO|LOWER_TORSO - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - armor = list(melee = 25, bullet = 15, laser = 25, energy = 10, bomb = 25, bio = 0, rad = 0, fire = 40, acid = 40) - -//LAVALAND! - -/obj/item/clothing/suit/hooded/drake - name = "drake armour" - icon_state = "dragon" - item_state = "dragon" - desc = "A suit of armour fashioned from the remains of an ash drake." - allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/pickaxe, /obj/item/twohanded/spear) - armor = list("melee" = 70, "bullet" = 30, "laser" = 50, "energy" = 40, "bomb" = 70, "bio" = 60, "rad" = 50, "fire" = 100, "acid" = 100) - hoodtype = /obj/item/clothing/head/hooded/drake - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/head/hooded/drake - name = "drake helmet" - icon_state = "dragon" - item_state = "dragon" - desc = "The skull of a dragon." - armor = list("melee" = 70, "bullet" = 30, "laser" = 50, "energy" = 40, "bomb" = 70, "bio" = 60, "rad" = 50, "fire" = 100, "acid" = 100) - heat_protection = HEAD - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | ACID_PROOF - flags = BLOCKHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/suit/hooded/goliath - name = "goliath cloak" - icon_state = "goliath_cloak" - item_state = "goliath_cloak" - desc = "A staunch, practical cape made out of numerous monster materials, it is coveted amongst exiles & hermits." - allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/pickaxe, /obj/item/twohanded/spear, /obj/item/organ/internal/regenerative_core/legion, /obj/item/kitchen/knife/combat/survival) - armor = list("melee" = 35, "bullet" = 10, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60) //a fair alternative to bone armor, requiring alternative materials and gaining a suit slot - hoodtype = /obj/item/clothing/head/hooded/goliath - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - -/obj/item/clothing/head/hooded/goliath - name = "goliath cloak hood" - icon_state = "golhood" - item_state = "golhood" - desc = "A protective & concealing hood." - armor = list("melee" = 35, "bullet" = 10, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60) - flags = BLOCKHAIR - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/suit/armor/bone - name = "bone armor" - desc = "A tribal armor plate, crafted from animal bone." - icon_state = "bonearmor" - item_state = "bonearmor" - blood_overlay_type = "armor" - armor = list("melee" = 35, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS \ No newline at end of file +/obj/item/clothing/suit/armor + allowed = list(/obj/item/gun/energy,/obj/item/reagent_containers/spray/pepper,/obj/item/gun/projectile,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/flashlight/seclite,/obj/item/melee/classic_baton/telescopic,/obj/item/kitchen/knife/combat) + body_parts_covered = UPPER_TORSO|LOWER_TORSO + cold_protection = UPPER_TORSO|LOWER_TORSO + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + heat_protection = UPPER_TORSO|LOWER_TORSO + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + strip_delay = 60 + put_on_delay = 40 + max_integrity = 250 + resistance_flags = NONE + armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/suit/armor/vest + name = "armor" + desc = "An armored vest that protects against some damage." + icon_state = "armor" + item_state = "armor" + blood_overlay_type = "armor" + flags_size = ONESIZEFITSALL + dog_fashion = /datum/dog_fashion/back + +/obj/item/clothing/suit/armor/vest/jacket + name = "military jacket" + desc = "An old military jacket, it has armoring." + icon_state = "militaryjacket" + item_state = "militaryjacket" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/suit/armor/vest/combat + name = "combat vest" + desc = "An armored vest that protects against some damage." + icon_state = "armor-combat" + item_state = "bulletproof" + blood_overlay_type = "armor" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/suit/armor/vest/security + name = "security armor" + desc = "An armored vest that protects against some damage. This one has a clip for a holobadge." + icon_state = "armor" + item_state = "armor" + var/obj/item/clothing/accessory/holobadge/attached_badge + +/obj/item/clothing/suit/armor/vest/security/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/clothing/accessory/holobadge)) + if(user.unEquip(W)) + add_fingerprint(user) + W.forceMove(src) + attached_badge = W + + var/datum/action/A = new /datum/action/item_action/remove_badge(src) + A.Grant(user) + icon_state = "armorsec" + user.update_inv_wear_suit() + desc = "An armored vest that protects against some damage. This one has [attached_badge] attached to it." + to_chat(user, "You attach [attached_badge] to [src].") + return + ..() + +/obj/item/clothing/suit/armor/vest/security/attack_self(mob/user as mob) + if(attached_badge) + add_fingerprint(user) + user.put_in_hands(attached_badge) + + for(var/X in actions) + var/datum/action/A = X + A.Remove(user) + + icon_state = "armor" + user.update_inv_wear_suit() + desc = "An armored vest that protects against some damage. This one has a clip for a holobadge." + to_chat(user, "You remove [attached_badge] from [src].") + + attached_badge = null + + return + + ..() + +/obj/item/clothing/suit/armor/vest/blueshield + name = "blueshield security armor" + desc = "An armored vest with the badge of a Blueshield Lieutenant." + icon_state = "blueshield" + item_state = "blueshield" + +/obj/item/clothing/suit/armor/vest/bloody + name = "bloodied security armor" + desc = "A vest drenched in the blood of Greytide. It has seen better days." + icon_state = "bloody_armor" + item_state = "bloody_armor" + sprite_sheets = null + +/obj/item/clothing/suit/armor/secjacket + name = "security jacket" + desc = "A sturdy black jacket with reinforced fabric. Bears insignia of NT corporate security." + icon_state = "secjacket_open" + item_state = "hos" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + armor = list(melee = 15, bullet = 10, laser = 15, energy = 5, bomb = 15, bio = 0, rad = 0, fire = 30, acid = 30) + cold_protection = UPPER_TORSO|LOWER_TORSO|ARMS + heat_protection = UPPER_TORSO|LOWER_TORSO|ARMS + ignore_suitadjust = 0 + suit_adjusted = 1 + actions_types = list(/datum/action/item_action/openclose) + adjust_flavour = "unzip" + +/obj/item/clothing/suit/armor/hos + name = "armored coat" + desc = "A trench coat enhanced with a special alloy for some protection and style." + icon_state = "hos" + item_state = "hos" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS + armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90) + flags_inv = HIDEJUMPSUIT + cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + strip_delay = 80 + +/obj/item/clothing/suit/armor/hos/alt + name = "armored trenchoat" + desc = "A trenchcoat enhanced with a special lightweight kevlar. The epitome of tactical plainclothes." + icon_state = "hostrench_open" + item_state = "hostrench_open" + flags_inv = 0 + ignore_suitadjust = 0 + suit_adjusted = 1 + actions_types = list(/datum/action/item_action/openclose) + adjust_flavour = "unbutton" + +/obj/item/clothing/suit/armor/hos/jensen + name = "armored trenchcoat" + desc = "A trenchcoat augmented with a special alloy for some protection and style." + icon_state = "jensencoat" + item_state = "jensencoat" + flags_inv = 0 + sprite_sheets = null + +/obj/item/clothing/suit/armor/vest/warden + name = "Warden's armored jacket" + desc = "An armored jacket with silver rank pips and livery." + icon_state = "warden_jacket" + item_state = "armor" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + cold_protection = UPPER_TORSO|LOWER_TORSO|HANDS + heat_protection = UPPER_TORSO|LOWER_TORSO|HANDS + strip_delay = 70 + resistance_flags = FLAMMABLE + dog_fashion = null + +/obj/item/clothing/suit/armor/vest/warden/alt + name = "warden's jacket" + desc = "A navy-blue armored jacket with blue shoulder designations and '/Warden/' stitched into one of the chest pockets." + icon_state = "warden_jacket_alt" + +/obj/item/clothing/suit/armor/vest/capcarapace + name = "captain's carapace" + desc = "An armored vest reinforced with ceramic plates and pauldrons to provide additional protection whilst still offering maximum mobility and flexibility. Issued only to the station's finest, although it does chafe your nipples." + icon_state = "capcarapace" + item_state = "armor" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 90) + dog_fashion = null + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/armor/vest/capcarapace/alt + name = "captain's parade jacket" + desc = "For when an armored vest isn't fashionable enough." + icon_state = "capformal" + item_state = "capspacesuit" + +/obj/item/clothing/suit/armor/riot + name = "Riot Suit" + desc = "A suit of armor with heavy padding to protect against melee attacks. Looks like it might impair movement." + icon_state = "riot" + item_state = "swat_suit" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + flags_inv = HIDEJUMPSUIT + strip_delay = 80 + put_on_delay = 60 + hide_tail_by_species = list("Vox") + +/obj/item/clothing/suit/armor/riot/knight + name = "plate armour" + desc = "A classic suit of plate armour, highly effective at stopping melee attacks." + icon_state = "knight_green" + item_state = "knight_green" + slowdown = 1 + +/obj/item/clothing/suit/armor/riot/knight/yellow + icon_state = "knight_yellow" + item_state = "knight_yellow" + +/obj/item/clothing/suit/armor/riot/knight/blue + icon_state = "knight_blue" + item_state = "knight_blue" + +/obj/item/clothing/suit/armor/riot/knight/red + icon_state = "knight_red" + item_state = "knight_red" + +/obj/item/clothing/suit/armor/riot/knight/templar + name = "crusader armour" + desc = "God wills it!" + icon_state = "knight_templar" + item_state = "knight_templar" + allowed = list(/obj/item/nullrod/claymore) + armor = list(melee = 25, bullet = 5, laser = 5, energy = 5, bomb = 0, bio = 0, rad = 0, fire = 80, acid = 80) + +/obj/item/clothing/suit/armor/vest/durathread + name = "durathread vest" + desc = "A vest made of durathread with strips of leather acting as trauma plates." + icon_state = "durathread" + item_state = "durathread" + strip_delay = 60 + max_integrity = 200 + resistance_flags = FLAMMABLE + armor = list("melee" = 20, "bullet" = 10, "laser" = 30, "energy" = 5, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 50) + +/obj/item/clothing/suit/armor/bulletproof + name = "Bulletproof Vest" + desc = "A bulletproof vest that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent." + icon_state = "bulletproof" + item_state = "armor" + blood_overlay_type = "armor" + armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 70 + put_on_delay = 50 + +/obj/item/clothing/suit/armor/laserproof + name = "Ablative Armor Vest" + desc = "A vest that excels in protecting the wearer against energy projectiles." + icon_state = "armor_reflec" + item_state = "armor_reflec" + blood_overlay_type = "armor" + armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 50, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + var/hit_reflect_chance = 40 + +/obj/item/clothing/suit/armor/laserproof/IsReflect(var/def_zone) + if(!(def_zone in list("chest", "groin"))) //If not shot where ablative is covering you, you don't get the reflection bonus! + return 0 + if(prob(hit_reflect_chance)) + return 1 + +/obj/item/clothing/suit/armor/vest/det_suit + name = "armor" + desc = "An armored vest with a detective's badge on it." + icon_state = "detective-armor" + item_state = "armor" + blood_overlay_type = "armor" + flags_size = ONESIZEFITSALL + allowed = list(/obj/item/tank/emergency_oxygen,/obj/item/reagent_containers/spray/pepper,/obj/item/flashlight,/obj/item/gun,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/storage/fancy/cigarettes,/obj/item/lighter,/obj/item/detective_scanner,/obj/item/taperecorder) + resistance_flags = FLAMMABLE + dog_fashion = null + +//Reactive armor +/obj/item/clothing/suit/armor/reactive + name = "reactive armor" + desc = "Doesn't seem to do much for some reason." + var/active = 0 + icon_state = "reactiveoff" + item_state = "reactiveoff" + blood_overlay_type = "armor" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + actions_types = list(/datum/action/item_action/toggle) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + hit_reaction_chance = 50 + +/obj/item/clothing/suit/armor/reactive/attack_self(mob/user) + active = !(active) + if(active) + to_chat(user, "[src] is now active.") + icon_state = "reactive" + item_state = "reactive" + else + to_chat(user, "[src] is now inactive.") + icon_state = "reactiveoff" + item_state = "reactiveoff" + add_fingerprint(user) + user.update_inv_wear_suit() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/clothing/suit/armor/reactive/emp_act(severity) + active = 0 + icon_state = "reactiveoff" + item_state = "reactiveoff" + if(istype(loc, /mob/living/carbon/human)) + var/mob/living/carbon/human/C = loc + C.update_inv_wear_suit() + ..() + +//When the wearer gets hit, this armor will teleport the user a short distance away (to safety or to more danger, no one knows. That's the fun of it!) +/obj/item/clothing/suit/armor/reactive/teleport + name = "reactive teleport armor" + desc = "Someone seperated our Research Director from his own head!" + var/tele_range = 6 + +/obj/item/clothing/suit/armor/reactive/teleport/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(!active) + return 0 + if(prob(hit_reaction_chance)) + var/mob/living/carbon/human/H = owner + owner.visible_message("The reactive teleport system flings [H] clear of [attack_text]!") + var/list/turfs = new/list() + for(var/turf/T in orange(tele_range, H)) + if(istype(T, /turf/space)) + continue + if(T.density) + continue + if(T.x>world.maxx-tele_range || T.xworld.maxy-tele_range || T.yThe [src] blocks the [attack_text], sending out jets of flame!") + for(var/mob/living/carbon/C in range(6, owner)) + if(C != owner) + C.fire_stacks += 8 + C.IgniteMob() + owner.fire_stacks = -20 + return 1 + return 0 + + +/obj/item/clothing/suit/armor/reactive/stealth + name = "reactive stealth armor" + +/obj/item/clothing/suit/armor/reactive/stealth/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(!active) + return 0 + if(prob(hit_reaction_chance)) + var/mob/living/simple_animal/hostile/illusion/escape/E = new(owner.loc) + E.Copy_Parent(owner, 50) + E.GiveTarget(owner) //so it starts running right away + E.Goto(owner, E.move_to_delay, E.minimum_distance) + owner.alpha = 0 + owner.visible_message("[owner] is hit by [attack_text] in the chest!") //We pretend to be hit, since blocking it would stop the message otherwise + spawn(40) + owner.alpha = initial(owner.alpha) + return 1 + +/obj/item/clothing/suit/armor/reactive/tesla + name = "reactive tesla armor" + +/obj/item/clothing/suit/armor/reactive/tesla/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(!active) + return 0 + if(prob(hit_reaction_chance)) + owner.visible_message("The [src] blocks the [attack_text], sending out arcs of lightning!") + for(var/mob/living/M in view(6, owner)) + if(M == owner) + continue + owner.Beam(M,icon_state="lightning[rand(1, 12)]",icon='icons/effects/effects.dmi',time=5) + M.adjustFireLoss(25) + playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1) + return 1 + +//All of the armor below is mostly unused + + +/obj/item/clothing/suit/armor/centcomm + name = "Cent. Com. armor" + desc = "A suit that protects against some damage." + icon_state = "centcom" + item_state = "centcom" + w_class = WEIGHT_CLASS_BULKY + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/gun/energy,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/tank/emergency_oxygen) + flags = THICKMATERIAL + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + sprite_sheets = null + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) + +/obj/item/clothing/suit/armor/heavy + name = "heavy armor" + desc = "A heavily armored suit that protects against moderate damage." + icon_state = "heavy" + item_state = "swat_suit" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.90 + flags = THICKMATERIAL + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + slowdown = 3 + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + hide_tail_by_species = list("Vox") + +/obj/item/clothing/suit/armor/tdome + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 90) + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + flags = THICKMATERIAL + cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + hide_tail_by_species = list("Vox") + +/obj/item/clothing/suit/armor/tdome/red + name = "Red Thunderdome Armor" + desc = "Armor worn by the red Thunderdome team." + icon_state = "tdred" + item_state = "tdred" + +/obj/item/clothing/suit/armor/tdome/green + name = "Green Thunderdome Armor" + desc = "Armor worn by the green Thunderdome team." + icon_state = "tdgreen" + item_state = "tdgreen" + +//Non-hardsuit ERT armor. +/obj/item/clothing/suit/armor/vest/ert + name = "emergency response team armor" + desc = "A set of armor worn by members of the Nanotrasen Emergency Response Team." + icon_state = "ertarmor_cmd" + item_state = "armor" + armor = list(melee = 30, bullet = 30, laser = 30, energy = 30, bomb = 20, bio = 0, rad = 0, fire = 50, acid = 50) + +//Commander +/obj/item/clothing/suit/armor/vest/ert/command + name = "emergency response team commander armor" + desc = "A set of armor worn by the commander of a Nanotrasen Emergency Response Team. Has blue highlights." + +//Security +/obj/item/clothing/suit/armor/vest/ert/security + name = "emergency response team security armor" + desc = "A set of armor worn by security members of the Nanotrasen Emergency Response Team. Has red highlights." + icon_state = "ertarmor_sec" + + +/obj/item/clothing/suit/armor/vest/ert/security/paranormal + name = "emergency response team paranormal armor" + desc = "A set of armor worn by paranormal members of the Nanotrasen Emergency Response Team. Has crusader sigils." + icon_state = "knight_templar" + item_state = "knight_templar" + +//Engineer +/obj/item/clothing/suit/armor/vest/ert/engineer + name = "emergency response team engineer armor" + desc = "A set of armor worn by engineering members of the Nanotrasen Emergency Response Team. Has orange highlights." + icon_state = "ertarmor_eng" + +//Medical +/obj/item/clothing/suit/armor/vest/ert/medical + name = "emergency response team medical armor" + desc = "A set of armor worn by medical members of the Nanotrasen Emergency Response Team. Has red and white highlights." + icon_state = "ertarmor_med" + +//Janitorial +/obj/item/clothing/suit/armor/vest/ert/janitor + name = "emergency response team janitor armor" + desc = "A set of armor worn by janitorial members of the Nanotrasen Emergency Response Team. Has red and white highlights." + icon_state = "ertarmor_jan" + +//same defense as basic sec armor +/obj/item/clothing/suit/storage/lawyer/blackjacket/armored + desc = "A snappy dress jacket, reinforced with a layer of armor protecting the torso." + allowed = list(/obj/item/tank/emergency_oxygen, /obj/item/gun/projectile/revolver, /obj/item/gun/projectile/automatic/pistol) + body_parts_covered = UPPER_TORSO|LOWER_TORSO + cold_protection = UPPER_TORSO|LOWER_TORSO + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + heat_protection = UPPER_TORSO|LOWER_TORSO + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + armor = list(melee = 25, bullet = 15, laser = 25, energy = 10, bomb = 25, bio = 0, rad = 0, fire = 40, acid = 40) + +//LAVALAND! + +/obj/item/clothing/suit/hooded/drake + name = "drake armour" + icon_state = "dragon" + item_state = "dragon" + desc = "A suit of armour fashioned from the remains of an ash drake." + allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/pickaxe, /obj/item/twohanded/spear) + armor = list("melee" = 70, "bullet" = 30, "laser" = 50, "energy" = 40, "bomb" = 70, "bio" = 60, "rad" = 50, "fire" = 100, "acid" = 100) + hoodtype = /obj/item/clothing/head/hooded/drake + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/head/hooded/drake + name = "drake helmet" + icon_state = "dragon" + item_state = "dragon" + desc = "The skull of a dragon." + armor = list("melee" = 70, "bullet" = 30, "laser" = 50, "energy" = 40, "bomb" = 70, "bio" = 60, "rad" = 50, "fire" = 100, "acid" = 100) + heat_protection = HEAD + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | ACID_PROOF + flags = BLOCKHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/suit/hooded/goliath + name = "goliath cloak" + icon_state = "goliath_cloak" + item_state = "goliath_cloak" + desc = "A staunch, practical cape made out of numerous monster materials, it is coveted amongst exiles & hermits." + allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/pickaxe, /obj/item/twohanded/spear, /obj/item/organ/internal/regenerative_core/legion, /obj/item/kitchen/knife/combat/survival) + armor = list("melee" = 35, "bullet" = 10, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60) //a fair alternative to bone armor, requiring alternative materials and gaining a suit slot + hoodtype = /obj/item/clothing/head/hooded/goliath + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/head/hooded/goliath + name = "goliath cloak hood" + icon_state = "golhood" + item_state = "golhood" + desc = "A protective & concealing hood." + armor = list("melee" = 35, "bullet" = 10, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60) + flags = BLOCKHAIR + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/suit/armor/bone + name = "bone armor" + desc = "A tribal armor plate, crafted from animal bone." + icon_state = "bonearmor" + item_state = "bonearmor" + blood_overlay_type = "armor" + armor = list("melee" = 35, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS diff --git a/code/modules/clothing/suits/bio.dm b/code/modules/clothing/suits/bio.dm index 6a2d582d355..1b7100c02bc 100644 --- a/code/modules/clothing/suits/bio.dm +++ b/code/modules/clothing/suits/bio.dm @@ -1,111 +1,111 @@ -//Biosuit complete with shoes (in the item sprite) -/obj/item/clothing/head/bio_hood - name = "bio hood" - icon_state = "bio" - desc = "A hood that protects the head and face from biological comtaminants." - permeability_coefficient = 0.01 - flags = BLOCKHAIR | THICKMATERIAL - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES - resistance_flags = ACID_PROOF - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi', - "Unathi" = 'icons/mob/species/unathi/head.dmi', - "Tajaran" = 'icons/mob/species/tajaran/head.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/head.dmi', - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/suit/bio_suit - name = "bio suit" - desc = "A suit that protects against biological contamination." - icon_state = "bio" - item_state = "bio_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - flags = THICKMATERIAL - flags_size = ONESIZEFITSALL - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - slowdown = 1 - allowed = list(/obj/item/tank/emergency_oxygen,/obj/item/pen,/obj/item/flashlight/pen) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL - strip_delay = 70 - put_on_delay = 70 - resistance_flags = ACID_PROOF - sprite_sheets = list( - "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', - "Unathi" = 'icons/mob/species/unathi/suit.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - - -//Standard biosuit, orange stripe -/obj/item/clothing/head/bio_hood/general - icon_state = "bio_general" - -/obj/item/clothing/suit/bio_suit/general - icon_state = "bio_general" - - -//Virology biosuit, green stripe -/obj/item/clothing/head/bio_hood/virology - icon_state = "bio_virology" - -/obj/item/clothing/suit/bio_suit/virology - icon_state = "bio_virology" - - -//Security biosuit, grey with red stripe across the chest -/obj/item/clothing/head/bio_hood/security - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - icon_state = "bio_security" - -/obj/item/clothing/suit/bio_suit/security - armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - icon_state = "bio_security" - - -//Janitor's biosuit, grey with purple arms -/obj/item/clothing/head/bio_hood/janitor - icon_state = "bio_janitor" - -/obj/item/clothing/suit/bio_suit/janitor - icon_state = "bio_janitor" - - -//Scientist's biosuit, white with a pink-ish hue -/obj/item/clothing/head/bio_hood/scientist - icon_state = "bio_scientist" - -/obj/item/clothing/suit/bio_suit/scientist - icon_state = "bio_scientist" - - -//CMO's biosuit, blue stripe -/obj/item/clothing/suit/bio_suit/cmo - icon_state = "bio_cmo" - -/obj/item/clothing/head/bio_hood/cmo - icon_state = "bio_cmo" - - -//Plague Dr mask can be found in clothing/masks/gasmask.dm -/obj/item/clothing/suit/bio_suit/plaguedoctorsuit - name = "Plague doctor suit" - desc = "It protected doctors from the Black Death, back then. You bet your arse it's gonna help you against viruses." - icon_state = "plaguedoctor" - item_state = "bio_suit" - strip_delay = 40 - put_on_delay = 20 - - sprite_sheets = list( - "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', - "Unathi" = 'icons/mob/species/unathi/suit.dmi', - "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', - ) - hide_tail_by_species = list("Unathi, Tajaran, Vulpkanin") \ No newline at end of file +//Biosuit complete with shoes (in the item sprite) +/obj/item/clothing/head/bio_hood + name = "bio hood" + icon_state = "bio" + desc = "A hood that protects the head and face from biological comtaminants." + permeability_coefficient = 0.01 + flags = BLOCKHAIR | THICKMATERIAL + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES + resistance_flags = ACID_PROOF + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi', + "Unathi" = 'icons/mob/species/unathi/head.dmi', + "Tajaran" = 'icons/mob/species/tajaran/head.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/head.dmi', + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/suit/bio_suit + name = "bio suit" + desc = "A suit that protects against biological contamination." + icon_state = "bio" + item_state = "bio_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + flags = THICKMATERIAL + flags_size = ONESIZEFITSALL + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + slowdown = 1 + allowed = list(/obj/item/tank/emergency_oxygen,/obj/item/pen,/obj/item/flashlight/pen) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL + strip_delay = 70 + put_on_delay = 70 + resistance_flags = ACID_PROOF + sprite_sheets = list( + "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', + "Unathi" = 'icons/mob/species/unathi/suit.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + + +//Standard biosuit, orange stripe +/obj/item/clothing/head/bio_hood/general + icon_state = "bio_general" + +/obj/item/clothing/suit/bio_suit/general + icon_state = "bio_general" + + +//Virology biosuit, green stripe +/obj/item/clothing/head/bio_hood/virology + icon_state = "bio_virology" + +/obj/item/clothing/suit/bio_suit/virology + icon_state = "bio_virology" + + +//Security biosuit, grey with red stripe across the chest +/obj/item/clothing/head/bio_hood/security + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + icon_state = "bio_security" + +/obj/item/clothing/suit/bio_suit/security + armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + icon_state = "bio_security" + + +//Janitor's biosuit, grey with purple arms +/obj/item/clothing/head/bio_hood/janitor + icon_state = "bio_janitor" + +/obj/item/clothing/suit/bio_suit/janitor + icon_state = "bio_janitor" + + +//Scientist's biosuit, white with a pink-ish hue +/obj/item/clothing/head/bio_hood/scientist + icon_state = "bio_scientist" + +/obj/item/clothing/suit/bio_suit/scientist + icon_state = "bio_scientist" + + +//CMO's biosuit, blue stripe +/obj/item/clothing/suit/bio_suit/cmo + icon_state = "bio_cmo" + +/obj/item/clothing/head/bio_hood/cmo + icon_state = "bio_cmo" + + +//Plague Dr mask can be found in clothing/masks/gasmask.dm +/obj/item/clothing/suit/bio_suit/plaguedoctorsuit + name = "Plague doctor suit" + desc = "It protected doctors from the Black Death, back then. You bet your arse it's gonna help you against viruses." + icon_state = "plaguedoctor" + item_state = "bio_suit" + strip_delay = 40 + put_on_delay = 20 + + sprite_sheets = list( + "Tajaran" = 'icons/mob/species/tajaran/suit.dmi', + "Unathi" = 'icons/mob/species/unathi/suit.dmi', + "Vulpkanin" = 'icons/mob/species/vulpkanin/suit.dmi', + ) + hide_tail_by_species = list("Unathi, Tajaran, Vulpkanin") diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm index dde11f1fcee..bcb71edf356 100644 --- a/code/modules/clothing/suits/jobs.dm +++ b/code/modules/clothing/suits/jobs.dm @@ -1,385 +1,385 @@ -/* - * Job related - */ -//Paramedic -/obj/item/clothing/suit/storage/paramedic - name = "paramedic vest" - desc = "A hazard vest used in the recovery of bodies." - icon_state = "paramedic-vest" - item_state = "paramedic-vest" - allowed = list(/obj/item/stack/medical, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/syringe, \ - /obj/item/healthanalyzer, /obj/item/flashlight, /obj/item/radio, /obj/item/tank/emergency_oxygen,/obj/item/rad_laser) - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 10, rad = 10, fire = 50, acid = 50) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Brig Physician -/obj/item/clothing/suit/storage/brigdoc - name = "brig physician vest" - desc = "A vest often worn by doctors caring for inmates." - icon_state = "brigphysician-vest" - item_state = "brigphysician-vest" - allowed = list(/obj/item/stack/medical, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/syringe, \ - /obj/item/healthanalyzer, /obj/item/flashlight, \ - /obj/item/radio, /obj/item/tank/emergency_oxygen,/obj/item/rad_laser) - armor = list(melee = 10, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 10, rad = 0, fire = 50, acid = 50) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Botanist -/obj/item/clothing/suit/apron - name = "apron" - desc = "A basic blue apron." - icon_state = "apron" - item_state = "apron" - blood_overlay_type = "armor" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - allowed = list(/obj/item/reagent_containers/spray/plantbgone,/obj/item/plant_analyzer,/obj/item/seeds,/obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/cultivator,/obj/item/reagent_containers/spray/pestspray,/obj/item/hatchet,/obj/item/storage/bag/plants) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Captain -/obj/item/clothing/suit/captunic - name = "captain's parade tunic" - desc = "Worn by a Captain to show their class." - icon_state = "captunic" - item_state = "bio_suit" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - flags_inv = HIDEJUMPSUIT - flags_size = ONESIZEFITSALL - allowed = list(/obj/item/disk, /obj/item/stamp, /obj/item/reagent_containers/food/drinks/flask, /obj/item/melee, /obj/item/storage/lockbox/medal, /obj/item/flash, /obj/item/storage/box/matches, /obj/item/lighter, /obj/item/clothing/mask/cigarette, /obj/item/storage/fancy/cigarettes, /obj/item/tank/emergency_oxygen) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/suit/captunic/capjacket - name = "captain's uniform jacket" - desc = "A less formal jacket for everyday captain use." - icon_state = "capjacket" - item_state = "bio_suit" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - flags_inv = HIDEJUMPSUIT - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/suit/mantle/armor/captain - name = "captain's cloak" - desc = "An armor-plated piece of fashion for the ruling elite. Protect your upper half in style." - icon_state = "capmantle" - item_state = "capmantle" - armor = list(melee = 50, bullet = 40, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0, fire = 50, acid = 50) - -//Chaplain -/obj/item/clothing/suit/hooded/chaplain_hoodie - name = "chaplain hoodie" - desc = "This suit says to you 'hush'!" - icon_state = "chaplain_hoodie" - item_state = "chaplain_hoodie" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - hoodtype = /obj/item/clothing/head/hooded/chaplain_hood - allowed = list(/obj/item/storage/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/emergency_oxygen) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Chaplain -/obj/item/clothing/suit/hooded/nun - name = "nun robe" - desc = "Maximum piety in this star system." - icon_state = "nun" - item_state = "nun" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS|HANDS - flags_inv = HIDESHOES|HIDEJUMPSUIT - hoodtype = /obj/item/clothing/head/hooded/nun_hood - allowed = list(/obj/item/storage/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/emergency_oxygen) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Chaplain -/obj/item/clothing/suit/hooded/monk - name = "monk robe" - desc = "Wooden board not included." - icon_state = "monkrobe" - item_state = "monkrobe" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS|HANDS - flags_inv = HIDESHOES|HIDEJUMPSUIT - hoodtype = /obj/item/clothing/head/hooded/monk_hood - allowed = list(/obj/item/storage/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/emergency_oxygen) - -/obj/item/clothing/suit/witchhunter - name = "witchhunter garb" - desc = "Dosen't weigh the same a a duck." - icon_state = "witchhunter" - item_state = "witchhunter" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/storage/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/emergency_oxygen) - - - //Chef -/obj/item/clothing/suit/toggle/chef - name = "chef's apron" - - -//Chef -/obj/item/clothing/suit/chef - name = "chef's apron" - desc = "An apron used by a high class chef." - icon_state = "chef" - item_state = "chef" - gas_transfer_coefficient = 0.90 - permeability_coefficient = 0.50 - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - allowed = list (/obj/item/kitchen/knife) - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Chef -/obj/item/clothing/suit/chef/classic - name = "classic chef's apron" - desc = "A basic, dull, white chef's apron." - icon_state = "apronchef" - item_state = "apronchef" - blood_overlay_type = "armor" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - allowed = list(/obj/item/kitchen/knife) - -//Chief Engineer -/obj/item/clothing/suit/mantle/chief_engineer - name = "chief engineer's mantle" - desc = "A slick, authoritative cloak designed for the Chief Engineer." - icon_state = "cemantle" - item_state = "cemantle" - allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/t_scanner, /obj/item/rcd) - -//Chief Medical Officer -/obj/item/clothing/suit/mantle/labcoat/chief_medical_officer - name = "chief medical officer's mantle" - desc = "An absorbent, clean cover found on the shoulders of the Chief Medical Officer." - icon_state = "cmomantle" - item_state = "cmomantle" - -//Detective -/obj/item/clothing/suit/storage/det_suit - name = "coat" - desc = "An 18th-century multi-purpose trenchcoat. Someone who wears this means serious business." - icon_state = "detective" - item_state = "det_suit" - blood_overlay_type = "coat" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/tank/emergency_oxygen, /obj/item/reagent_containers/spray/pepper, /obj/item/flashlight, /obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/detective_scanner, /obj/item/taperecorder) - armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - flags_size = ONESIZEFITSALL - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Forensics -/obj/item/clothing/suit/storage/det_suit/forensics - name = "jacket" - desc = "A forensics technician jacket." - item_state = "det_suit" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - cold_protection = UPPER_TORSO|LOWER_TORSO|ARMS - heat_protection = UPPER_TORSO|LOWER_TORSO|ARMS - -/obj/item/clothing/suit/storage/det_suit/forensics/red - name = "red jacket" - desc = "A red forensics technician jacket." - icon_state = "forensics_red" - -/obj/item/clothing/suit/storage/det_suit/forensics/blue - name = "blue jacket" - desc = "A blue forensics technician jacket." - icon_state = "forensics_blue" - -//Blueshield -/obj/item/clothing/suit/storage/blueshield - name = "blueshield coat" - desc = "NT deluxe ripoff. You finally have your own coat." - icon_state = "blueshieldcoat" - item_state = "blueshieldcoat" - blood_overlay_type = "coat" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - allowed = list(/obj/item/gun/energy,/obj/item/reagent_containers/spray/pepper,/obj/item/gun/projectile,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/flashlight/seclite,/obj/item/melee/classic_baton/telescopic) - armor = list(melee = 25, bullet = 10, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 45) - cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS - flags_size = ONESIZEFITSALL - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Engineering -/obj/item/clothing/suit/storage/hazardvest - name = "hazard vest" - desc = "A high-visibility vest used in work zones." - icon_state = "hazard" - item_state = "hazard" - blood_overlay_type = "armor" - allowed = list (/obj/item/flashlight, /obj/item/t_scanner, /obj/item/tank/emergency_oxygen) - resistance_flags = NONE - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Lawyer -/obj/item/clothing/suit/storage/lawyer - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/suit/storage/lawyer/blackjacket - name = "black suit jacket" - desc = "A snappy dress jacket." - icon_state = "suitjacket_black_open" - item_state = "suitjacket_black_open" - blood_overlay_type = "coat" - body_parts_covered = UPPER_TORSO|ARMS - ignore_suitadjust = 0 - suit_adjusted = 1 - actions_types = list(/datum/action/item_action/button) - adjust_flavour = "unbutton" - -/obj/item/clothing/suit/storage/lawyer/bluejacket - name = "blue suit jacket" - desc = "A snappy dress jacket." - icon_state = "suitjacket_blue_open" - item_state = "suitjacket_blue_open" - blood_overlay_type = "coat" - body_parts_covered = UPPER_TORSO|ARMS - ignore_suitadjust = 0 - suit_adjusted = 1 - actions_types = list(/datum/action/item_action/button) - adjust_flavour = "unbutton" - -/obj/item/clothing/suit/storage/lawyer/purpjacket - name = "purple suit jacket" - desc = "A snappy dress jacket." - icon_state = "suitjacket_purp" - item_state = "suitjacket_purp" - blood_overlay_type = "coat" - body_parts_covered = UPPER_TORSO|ARMS - -//Head of Security -/obj/item/clothing/suit/mantle/armor - name = "armored shawl" - desc = "A reinforced shawl, worn by the Head of Security. Do you dare take up their mantle?" - icon_state = "hosmantle" - item_state = "hosmantle" - allowed = list(/obj/item/gun/energy, /obj/item/reagent_containers/spray/pepper, /obj/item/gun/projectile, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/flashlight/seclite, /obj/item/melee/classic_baton/telescopic, /obj/item/kitchen/knife/combat) - armor = list(melee = 30, bullet = 30, laser = 30, energy = 10, bomb = 25, bio = 0, rad = 0, fire = 50, acid = 50) - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - heat_protection = UPPER_TORSO|ARMS - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - strip_delay = 60 - put_on_delay = 40 - resistance_flags = NONE - -//Head of Personnel -/obj/item/clothing/suit/mantle/armor/head_of_personnel - name = "head of personnel's shawl" - desc = "An armored shawl for the head of personnel. It's remarkably well kept." - icon_state = "hopmantle" - item_state = "hopmantle" - armor = list(melee = 25, bullet = 15, laser = 25, energy = 10, bomb = 25, bio = 0, rad = 0, fire = 50, acid = 50) - -//Internal Affairs -/obj/item/clothing/suit/storage/internalaffairs - name = "\improper Internal Affairs jacket" - desc = "A smooth black jacket." - icon_state = "ia_jacket_open" - item_state = "ia_jacket_open" - blood_overlay_type = "coat" - body_parts_covered = UPPER_TORSO|ARMS - ignore_suitadjust = 0 - suit_adjusted = 1 - actions_types = list(/datum/action/item_action/button) - adjust_flavour = "unbutton" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/suit/storage/ntrep - name = "\improper Nanotrasen Representative jacket" - desc = "A fancy black jacket; standard issue to Nanotrasen Representatives." - icon_state = "ntrep" - item_state = "ntrep" - blood_overlay_type = "coat" - body_parts_covered = UPPER_TORSO|ARMS - ignore_suitadjust = 0 - actions_types = list(/datum/action/item_action/button) - adjust_flavour = "unbutton" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Medical -/obj/item/clothing/suit/storage/fr_jacket - name = "first responder jacket" - desc = "A high-visibility jacket worn by medical first responders." - icon_state = "fr_jacket_open" - item_state = "fr_jacket_open" - blood_overlay_type = "armor" - allowed = list(/obj/item/stack/medical, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/syringe, \ - /obj/item/healthanalyzer, /obj/item/flashlight, /obj/item/radio, /obj/item/tank/emergency_oxygen,/obj/item/rad_laser) - ignore_suitadjust = 0 - suit_adjusted = 1 - actions_types = list(/datum/action/item_action/button) - adjust_flavour = "unbutton" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -//Mime -/obj/item/clothing/suit/suspenders - name = "suspenders" - desc = "They suspend the illusion of the mime's play." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "suspenders" - blood_overlay_type = "armor" //it's the less thing that I can put here - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/suit/suspenders/nodrop - flags = NODROP - -// Surgeon -/obj/item/clothing/suit/apron/surgical - name = "surgical apron" - desc = "A sterile blue surgical apron." - icon_state = "surgical" - item_state = "surgical" - allowed = list(/obj/item/scalpel, /obj/item/surgical_drapes, /obj/item/cautery, /obj/item/hemostat, /obj/item/retractor) - -//Research Director -/obj/item/clothing/suit/mantle/labcoat - name = "research director's mantle" - desc = "A tweed mantle, worn by the Research Director. Smells like science." - icon_state = "rdmantle" - item_state = "rdmantle" - allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/food/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/rad_laser) - armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 50, rad = 0, fire = 50, acid = 50) +/* + * Job related + */ +//Paramedic +/obj/item/clothing/suit/storage/paramedic + name = "paramedic vest" + desc = "A hazard vest used in the recovery of bodies." + icon_state = "paramedic-vest" + item_state = "paramedic-vest" + allowed = list(/obj/item/stack/medical, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/syringe, \ + /obj/item/healthanalyzer, /obj/item/flashlight, /obj/item/radio, /obj/item/tank/emergency_oxygen,/obj/item/rad_laser) + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 10, rad = 10, fire = 50, acid = 50) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Brig Physician +/obj/item/clothing/suit/storage/brigdoc + name = "brig physician vest" + desc = "A vest often worn by doctors caring for inmates." + icon_state = "brigphysician-vest" + item_state = "brigphysician-vest" + allowed = list(/obj/item/stack/medical, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/syringe, \ + /obj/item/healthanalyzer, /obj/item/flashlight, \ + /obj/item/radio, /obj/item/tank/emergency_oxygen,/obj/item/rad_laser) + armor = list(melee = 10, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 10, rad = 0, fire = 50, acid = 50) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Botanist +/obj/item/clothing/suit/apron + name = "apron" + desc = "A basic blue apron." + icon_state = "apron" + item_state = "apron" + blood_overlay_type = "armor" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + allowed = list(/obj/item/reagent_containers/spray/plantbgone,/obj/item/plant_analyzer,/obj/item/seeds,/obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/cultivator,/obj/item/reagent_containers/spray/pestspray,/obj/item/hatchet,/obj/item/storage/bag/plants) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Captain +/obj/item/clothing/suit/captunic + name = "captain's parade tunic" + desc = "Worn by a Captain to show their class." + icon_state = "captunic" + item_state = "bio_suit" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + flags_inv = HIDEJUMPSUIT + flags_size = ONESIZEFITSALL + allowed = list(/obj/item/disk, /obj/item/stamp, /obj/item/reagent_containers/food/drinks/flask, /obj/item/melee, /obj/item/storage/lockbox/medal, /obj/item/flash, /obj/item/storage/box/matches, /obj/item/lighter, /obj/item/clothing/mask/cigarette, /obj/item/storage/fancy/cigarettes, /obj/item/tank/emergency_oxygen) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/suit/captunic/capjacket + name = "captain's uniform jacket" + desc = "A less formal jacket for everyday captain use." + icon_state = "capjacket" + item_state = "bio_suit" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + flags_inv = HIDEJUMPSUIT + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/suit/mantle/armor/captain + name = "captain's cloak" + desc = "An armor-plated piece of fashion for the ruling elite. Protect your upper half in style." + icon_state = "capmantle" + item_state = "capmantle" + armor = list(melee = 50, bullet = 40, laser = 50, energy = 10, bomb = 25, bio = 0, rad = 0, fire = 50, acid = 50) + +//Chaplain +/obj/item/clothing/suit/hooded/chaplain_hoodie + name = "chaplain hoodie" + desc = "This suit says to you 'hush'!" + icon_state = "chaplain_hoodie" + item_state = "chaplain_hoodie" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + hoodtype = /obj/item/clothing/head/hooded/chaplain_hood + allowed = list(/obj/item/storage/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/emergency_oxygen) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Chaplain +/obj/item/clothing/suit/hooded/nun + name = "nun robe" + desc = "Maximum piety in this star system." + icon_state = "nun" + item_state = "nun" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS|HANDS + flags_inv = HIDESHOES|HIDEJUMPSUIT + hoodtype = /obj/item/clothing/head/hooded/nun_hood + allowed = list(/obj/item/storage/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/emergency_oxygen) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Chaplain +/obj/item/clothing/suit/hooded/monk + name = "monk robe" + desc = "Wooden board not included." + icon_state = "monkrobe" + item_state = "monkrobe" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS|HANDS + flags_inv = HIDESHOES|HIDEJUMPSUIT + hoodtype = /obj/item/clothing/head/hooded/monk_hood + allowed = list(/obj/item/storage/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/emergency_oxygen) + +/obj/item/clothing/suit/witchhunter + name = "witchhunter garb" + desc = "Dosen't weigh the same a a duck." + icon_state = "witchhunter" + item_state = "witchhunter" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + allowed = list(/obj/item/storage/bible, /obj/item/nullrod, /obj/item/reagent_containers/food/drinks/bottle/holywater, /obj/item/storage/fancy/candle_box, /obj/item/candle, /obj/item/tank/emergency_oxygen) + + + //Chef +/obj/item/clothing/suit/toggle/chef + name = "chef's apron" + + +//Chef +/obj/item/clothing/suit/chef + name = "chef's apron" + desc = "An apron used by a high class chef." + icon_state = "chef" + item_state = "chef" + gas_transfer_coefficient = 0.90 + permeability_coefficient = 0.50 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + allowed = list (/obj/item/kitchen/knife) + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Chef +/obj/item/clothing/suit/chef/classic + name = "classic chef's apron" + desc = "A basic, dull, white chef's apron." + icon_state = "apronchef" + item_state = "apronchef" + blood_overlay_type = "armor" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + allowed = list(/obj/item/kitchen/knife) + +//Chief Engineer +/obj/item/clothing/suit/mantle/chief_engineer + name = "chief engineer's mantle" + desc = "A slick, authoritative cloak designed for the Chief Engineer." + icon_state = "cemantle" + item_state = "cemantle" + allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/t_scanner, /obj/item/rcd) + +//Chief Medical Officer +/obj/item/clothing/suit/mantle/labcoat/chief_medical_officer + name = "chief medical officer's mantle" + desc = "An absorbent, clean cover found on the shoulders of the Chief Medical Officer." + icon_state = "cmomantle" + item_state = "cmomantle" + +//Detective +/obj/item/clothing/suit/storage/det_suit + name = "coat" + desc = "An 18th-century multi-purpose trenchcoat. Someone who wears this means serious business." + icon_state = "detective" + item_state = "det_suit" + blood_overlay_type = "coat" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + allowed = list(/obj/item/tank/emergency_oxygen, /obj/item/reagent_containers/spray/pepper, /obj/item/flashlight, /obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/detective_scanner, /obj/item/taperecorder) + armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + flags_size = ONESIZEFITSALL + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Forensics +/obj/item/clothing/suit/storage/det_suit/forensics + name = "jacket" + desc = "A forensics technician jacket." + item_state = "det_suit" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + cold_protection = UPPER_TORSO|LOWER_TORSO|ARMS + heat_protection = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/suit/storage/det_suit/forensics/red + name = "red jacket" + desc = "A red forensics technician jacket." + icon_state = "forensics_red" + +/obj/item/clothing/suit/storage/det_suit/forensics/blue + name = "blue jacket" + desc = "A blue forensics technician jacket." + icon_state = "forensics_blue" + +//Blueshield +/obj/item/clothing/suit/storage/blueshield + name = "blueshield coat" + desc = "NT deluxe ripoff. You finally have your own coat." + icon_state = "blueshieldcoat" + item_state = "blueshieldcoat" + blood_overlay_type = "coat" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + allowed = list(/obj/item/gun/energy,/obj/item/reagent_containers/spray/pepper,/obj/item/gun/projectile,/obj/item/ammo_box,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/restraints/handcuffs,/obj/item/flashlight/seclite,/obj/item/melee/classic_baton/telescopic) + armor = list(melee = 25, bullet = 10, laser = 25, energy = 10, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 45) + cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|ARMS + flags_size = ONESIZEFITSALL + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Engineering +/obj/item/clothing/suit/storage/hazardvest + name = "hazard vest" + desc = "A high-visibility vest used in work zones." + icon_state = "hazard" + item_state = "hazard" + blood_overlay_type = "armor" + allowed = list (/obj/item/flashlight, /obj/item/t_scanner, /obj/item/tank/emergency_oxygen) + resistance_flags = NONE + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Lawyer +/obj/item/clothing/suit/storage/lawyer + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/suit/storage/lawyer/blackjacket + name = "black suit jacket" + desc = "A snappy dress jacket." + icon_state = "suitjacket_black_open" + item_state = "suitjacket_black_open" + blood_overlay_type = "coat" + body_parts_covered = UPPER_TORSO|ARMS + ignore_suitadjust = 0 + suit_adjusted = 1 + actions_types = list(/datum/action/item_action/button) + adjust_flavour = "unbutton" + +/obj/item/clothing/suit/storage/lawyer/bluejacket + name = "blue suit jacket" + desc = "A snappy dress jacket." + icon_state = "suitjacket_blue_open" + item_state = "suitjacket_blue_open" + blood_overlay_type = "coat" + body_parts_covered = UPPER_TORSO|ARMS + ignore_suitadjust = 0 + suit_adjusted = 1 + actions_types = list(/datum/action/item_action/button) + adjust_flavour = "unbutton" + +/obj/item/clothing/suit/storage/lawyer/purpjacket + name = "purple suit jacket" + desc = "A snappy dress jacket." + icon_state = "suitjacket_purp" + item_state = "suitjacket_purp" + blood_overlay_type = "coat" + body_parts_covered = UPPER_TORSO|ARMS + +//Head of Security +/obj/item/clothing/suit/mantle/armor + name = "armored shawl" + desc = "A reinforced shawl, worn by the Head of Security. Do you dare take up their mantle?" + icon_state = "hosmantle" + item_state = "hosmantle" + allowed = list(/obj/item/gun/energy, /obj/item/reagent_containers/spray/pepper, /obj/item/gun/projectile, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/flashlight/seclite, /obj/item/melee/classic_baton/telescopic, /obj/item/kitchen/knife/combat) + armor = list(melee = 30, bullet = 30, laser = 30, energy = 10, bomb = 25, bio = 0, rad = 0, fire = 50, acid = 50) + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + heat_protection = UPPER_TORSO|ARMS + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + strip_delay = 60 + put_on_delay = 40 + resistance_flags = NONE + +//Head of Personnel +/obj/item/clothing/suit/mantle/armor/head_of_personnel + name = "head of personnel's shawl" + desc = "An armored shawl for the head of personnel. It's remarkably well kept." + icon_state = "hopmantle" + item_state = "hopmantle" + armor = list(melee = 25, bullet = 15, laser = 25, energy = 10, bomb = 25, bio = 0, rad = 0, fire = 50, acid = 50) + +//Internal Affairs +/obj/item/clothing/suit/storage/internalaffairs + name = "\improper Internal Affairs jacket" + desc = "A smooth black jacket." + icon_state = "ia_jacket_open" + item_state = "ia_jacket_open" + blood_overlay_type = "coat" + body_parts_covered = UPPER_TORSO|ARMS + ignore_suitadjust = 0 + suit_adjusted = 1 + actions_types = list(/datum/action/item_action/button) + adjust_flavour = "unbutton" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/suit/storage/ntrep + name = "\improper Nanotrasen Representative jacket" + desc = "A fancy black jacket; standard issue to Nanotrasen Representatives." + icon_state = "ntrep" + item_state = "ntrep" + blood_overlay_type = "coat" + body_parts_covered = UPPER_TORSO|ARMS + ignore_suitadjust = 0 + actions_types = list(/datum/action/item_action/button) + adjust_flavour = "unbutton" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Medical +/obj/item/clothing/suit/storage/fr_jacket + name = "first responder jacket" + desc = "A high-visibility jacket worn by medical first responders." + icon_state = "fr_jacket_open" + item_state = "fr_jacket_open" + blood_overlay_type = "armor" + allowed = list(/obj/item/stack/medical, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/hypospray, /obj/item/reagent_containers/syringe, \ + /obj/item/healthanalyzer, /obj/item/flashlight, /obj/item/radio, /obj/item/tank/emergency_oxygen,/obj/item/rad_laser) + ignore_suitadjust = 0 + suit_adjusted = 1 + actions_types = list(/datum/action/item_action/button) + adjust_flavour = "unbutton" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +//Mime +/obj/item/clothing/suit/suspenders + name = "suspenders" + desc = "They suspend the illusion of the mime's play." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "suspenders" + blood_overlay_type = "armor" //it's the less thing that I can put here + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/suit/suspenders/nodrop + flags = NODROP + +// Surgeon +/obj/item/clothing/suit/apron/surgical + name = "surgical apron" + desc = "A sterile blue surgical apron." + icon_state = "surgical" + item_state = "surgical" + allowed = list(/obj/item/scalpel, /obj/item/surgical_drapes, /obj/item/cautery, /obj/item/hemostat, /obj/item/retractor) + +//Research Director +/obj/item/clothing/suit/mantle/labcoat + name = "research director's mantle" + desc = "A tweed mantle, worn by the Research Director. Smells like science." + icon_state = "rdmantle" + item_state = "rdmantle" + allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/food/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/rad_laser) + armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 50, rad = 0, fire = 50, acid = 50) diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm index 36157df8bc8..03df1bd74d8 100644 --- a/code/modules/clothing/suits/labcoat.dm +++ b/code/modules/clothing/suits/labcoat.dm @@ -1,65 +1,65 @@ -/obj/item/clothing/suit/storage/labcoat - name = "labcoat" - desc = "A suit that protects against minor chemical spills." - icon_state = "labcoat_open" - item_state = "labcoat_open" - ignore_suitadjust = 0 - suit_adjusted = 1 - blood_overlay_type = "coat" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - allowed = list(/obj/item/analyzer,/obj/item/stack/medical,/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,/obj/item/healthanalyzer,/obj/item/flashlight/pen,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/food/pill,/obj/item/storage/pill_bottle,/obj/item/paper,/obj/item/rad_laser) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 50, "acid" = 50) - species_exception = list(/datum/species/golem) - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi', - "Grey" = 'icons/mob/species/grey/suit.dmi' - ) - actions_types = list(/datum/action/item_action/button) - adjust_flavour = "unbutton" - -/obj/item/clothing/suit/storage/labcoat/cmo - name = "chief medical officer's labcoat" - desc = "Bluer than the standard model." - icon_state = "labcoat_cmo_open" - item_state = "labcoat_cmo_open" - -/obj/item/clothing/suit/storage/labcoat/mad - name = "mad scientist's labcoat" - desc = "It makes you look capable of konking someone on the noggin and shooting them into space." - icon_state = "labcoat_green_open" - item_state = "labcoat_green_open" - -/obj/item/clothing/suit/storage/labcoat/genetics - name = "geneticist labcoat" - desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder." - icon_state = "labcoat_gen_open" - item_state = "labcoat_gen_open" - -/obj/item/clothing/suit/storage/labcoat/chemist - name = "chemist labcoat" - desc = "A suit that protects against minor chemical spills. Has an orange stripe on the shoulder." - icon_state = "labcoat_chem_open" - item_state = "labcoat_chem_open" - -/obj/item/clothing/suit/storage/labcoat/virologist - name = "virologist labcoat" - desc = "A suit that protects against minor chemical spills. Offers slightly more protection against biohazards than the standard model. Has a green stripe on the shoulder." - icon_state = "labcoat_vir_open" - -/obj/item/clothing/suit/storage/labcoat/science - name = "scientist labcoat" - desc = "A suit that protects against minor chemical spills. Has a purple stripe on the shoulder." - icon_state = "labcoat_tox_open" - item_state = "labcoat_tox_open" - -/obj/item/clothing/suit/storage/labcoat/mortician - name = "coroner labcoat" - desc = "A suit that protects against minor chemical spills. Has a black stripe on the shoulder." - icon_state = "labcoat_mort_open" - item_state = "labcoat_mort_open" - -/obj/item/clothing/suit/storage/labcoat/emt - name = "EMT labcoat" - desc = "A comfortable suit for paramedics. Has dark colours." - icon_state = "labcoat_emt_open" - item_state = "labcoat_emt_open" +/obj/item/clothing/suit/storage/labcoat + name = "labcoat" + desc = "A suit that protects against minor chemical spills." + icon_state = "labcoat_open" + item_state = "labcoat_open" + ignore_suitadjust = 0 + suit_adjusted = 1 + blood_overlay_type = "coat" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + allowed = list(/obj/item/analyzer,/obj/item/stack/medical,/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,/obj/item/healthanalyzer,/obj/item/flashlight/pen,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/food/pill,/obj/item/storage/pill_bottle,/obj/item/paper,/obj/item/rad_laser) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 50, "acid" = 50) + species_exception = list(/datum/species/golem) + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi', + "Grey" = 'icons/mob/species/grey/suit.dmi' + ) + actions_types = list(/datum/action/item_action/button) + adjust_flavour = "unbutton" + +/obj/item/clothing/suit/storage/labcoat/cmo + name = "chief medical officer's labcoat" + desc = "Bluer than the standard model." + icon_state = "labcoat_cmo_open" + item_state = "labcoat_cmo_open" + +/obj/item/clothing/suit/storage/labcoat/mad + name = "mad scientist's labcoat" + desc = "It makes you look capable of konking someone on the noggin and shooting them into space." + icon_state = "labcoat_green_open" + item_state = "labcoat_green_open" + +/obj/item/clothing/suit/storage/labcoat/genetics + name = "geneticist labcoat" + desc = "A suit that protects against minor chemical spills. Has a blue stripe on the shoulder." + icon_state = "labcoat_gen_open" + item_state = "labcoat_gen_open" + +/obj/item/clothing/suit/storage/labcoat/chemist + name = "chemist labcoat" + desc = "A suit that protects against minor chemical spills. Has an orange stripe on the shoulder." + icon_state = "labcoat_chem_open" + item_state = "labcoat_chem_open" + +/obj/item/clothing/suit/storage/labcoat/virologist + name = "virologist labcoat" + desc = "A suit that protects against minor chemical spills. Offers slightly more protection against biohazards than the standard model. Has a green stripe on the shoulder." + icon_state = "labcoat_vir_open" + +/obj/item/clothing/suit/storage/labcoat/science + name = "scientist labcoat" + desc = "A suit that protects against minor chemical spills. Has a purple stripe on the shoulder." + icon_state = "labcoat_tox_open" + item_state = "labcoat_tox_open" + +/obj/item/clothing/suit/storage/labcoat/mortician + name = "coroner labcoat" + desc = "A suit that protects against minor chemical spills. Has a black stripe on the shoulder." + icon_state = "labcoat_mort_open" + item_state = "labcoat_mort_open" + +/obj/item/clothing/suit/storage/labcoat/emt + name = "EMT labcoat" + desc = "A comfortable suit for paramedics. Has dark colours." + icon_state = "labcoat_emt_open" + item_state = "labcoat_emt_open" diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm index 576b3e6c552..c329d8a0033 100644 --- a/code/modules/clothing/suits/toggles.dm +++ b/code/modules/clothing/suits/toggles.dm @@ -74,4 +74,4 @@ H.update_inv_wear_suit() playsound(src.loc, 'sound/mecha/mechmove03.ogg', 50, 1) else - RemoveHelmet() \ No newline at end of file + RemoveHelmet() diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm index 87df2c38b33..308380e4c55 100644 --- a/code/modules/clothing/suits/utility.dm +++ b/code/modules/clothing/suits/utility.dm @@ -1,159 +1,159 @@ -/* - * Contains: - * Fire protection - * Bomb protection - * Radiation protection - */ - -/* - * Fire protection - */ - -/obj/item/clothing/suit/fire - name = "emergency firesuit" - desc = "A suit that protects against fire and heat." - icon_state = "fire" - item_state = "fire_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.90 - permeability_coefficient = 0.50 - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight,/obj/item/tank/emergency_oxygen,/obj/item/extinguisher) - slowdown = 1 - armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 10, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL - flags = STOPSPRESSUREDMAGE | THICKMATERIAL - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT - cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - strip_delay = 60 - put_on_delay = 60 - resistance_flags = FIRE_PROOF - -/obj/item/clothing/suit/fire/firefighter - icon_state = "firesuit" - item_state = "firefighter" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/suit/fire/heavy - name = "firesuit" - desc = "A suit that protects against extreme fire and heat." - //icon_state = "thermal" - item_state = "ro_suit" - w_class = WEIGHT_CLASS_BULKY - slowdown = 1.5 - -/obj/item/clothing/suit/fire/atmos - name = "firesuit" - desc = "An expensive firesuit that protects against even the most deadly of station fires. Designed to protect even if the wearer is set aflame." - icon_state = "atmos_firesuit" - item_state = "firesuit_atmos" - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/* - * Bomb protection - */ -/obj/item/clothing/head/bomb_hood - name = "bomb hood" - desc = "Use in case of bomb." - icon_state = "bombsuit" - flags = BLOCKHAIR | THICKMATERIAL - armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 10, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - cold_protection = HEAD - min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT - heat_protection = HEAD - max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT - strip_delay = 70 - put_on_delay = 70 - resistance_flags = NONE - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/helmet.dmi', - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/suit/bomb_suit - name = "bomb suit" - desc = "A suit designed for safety when handling explosives." - icon_state = "bombsuit" - item_state = "bombsuit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - flags = THICKMATERIAL - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - slowdown = 2 - armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 10, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - flags_inv = HIDEJUMPSUIT|HIDETAIL - heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - strip_delay = 70 - put_on_delay = 70 - resistance_flags = NONE - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) - -/obj/item/clothing/head/bomb_hood/security - icon_state = "bombsuitsec" - item_state = "bombsuitsec" - - -/obj/item/clothing/suit/bomb_suit/security - icon_state = "bombsuitsec" - item_state = "bombsuitsec" - allowed = list(/obj/item/gun/energy,/obj/item/melee/baton,/obj/item/restraints/handcuffs) - -/* - * Radiation protection - */ -/obj/item/clothing/head/radiation - name = "Radiation Hood" - icon_state = "rad" - desc = "A hood with radiation protective properties. Label: Made with lead, do not eat insulation" - flags = BLOCKHAIR|THICKMATERIAL - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) - strip_delay = 60 - put_on_delay = 60 - resistance_flags = NONE - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/head.dmi', - "Grey" = 'icons/mob/species/grey/head.dmi' - ) - -/obj/item/clothing/suit/radiation - name = "Radiation suit" - desc = "A suit that protects against radiation. Label: Made with lead, do not eat insulation." - icon_state = "rad" - item_state = "rad_suit" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.90 - permeability_coefficient = 0.50 - flags = THICKMATERIAL - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - allowed = list(/obj/item/flashlight,/obj/item/tank/emergency_oxygen) - slowdown = 1.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) - flags_inv = HIDEJUMPSUIT|HIDETAIL - strip_delay = 60 - put_on_delay = 60 - resistance_flags = NONE - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/suit.dmi' - ) +/* + * Contains: + * Fire protection + * Bomb protection + * Radiation protection + */ + +/* + * Fire protection + */ + +/obj/item/clothing/suit/fire + name = "emergency firesuit" + desc = "A suit that protects against fire and heat." + icon_state = "fire" + item_state = "fire_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.90 + permeability_coefficient = 0.50 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight,/obj/item/tank/emergency_oxygen,/obj/item/extinguisher) + slowdown = 1 + armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 10, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAIL + flags = STOPSPRESSUREDMAGE | THICKMATERIAL + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + strip_delay = 60 + put_on_delay = 60 + resistance_flags = FIRE_PROOF + +/obj/item/clothing/suit/fire/firefighter + icon_state = "firesuit" + item_state = "firefighter" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/suit/fire/heavy + name = "firesuit" + desc = "A suit that protects against extreme fire and heat." + //icon_state = "thermal" + item_state = "ro_suit" + w_class = WEIGHT_CLASS_BULKY + slowdown = 1.5 + +/obj/item/clothing/suit/fire/atmos + name = "firesuit" + desc = "An expensive firesuit that protects against even the most deadly of station fires. Designed to protect even if the wearer is set aflame." + icon_state = "atmos_firesuit" + item_state = "firesuit_atmos" + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/* + * Bomb protection + */ +/obj/item/clothing/head/bomb_hood + name = "bomb hood" + desc = "Use in case of bomb." + icon_state = "bombsuit" + flags = BLOCKHAIR | THICKMATERIAL + armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 10, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + cold_protection = HEAD + min_cold_protection_temperature = HELMET_MIN_TEMP_PROTECT + heat_protection = HEAD + max_heat_protection_temperature = HELMET_MAX_TEMP_PROTECT + strip_delay = 70 + put_on_delay = 70 + resistance_flags = NONE + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/helmet.dmi', + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/suit/bomb_suit + name = "bomb suit" + desc = "A suit designed for safety when handling explosives." + icon_state = "bombsuit" + item_state = "bombsuit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + flags = THICKMATERIAL + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + slowdown = 2 + armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 10, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + flags_inv = HIDEJUMPSUIT|HIDETAIL + heat_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + cold_protection = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + strip_delay = 70 + put_on_delay = 70 + resistance_flags = NONE + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) + +/obj/item/clothing/head/bomb_hood/security + icon_state = "bombsuitsec" + item_state = "bombsuitsec" + + +/obj/item/clothing/suit/bomb_suit/security + icon_state = "bombsuitsec" + item_state = "bombsuitsec" + allowed = list(/obj/item/gun/energy,/obj/item/melee/baton,/obj/item/restraints/handcuffs) + +/* + * Radiation protection + */ +/obj/item/clothing/head/radiation + name = "Radiation Hood" + icon_state = "rad" + desc = "A hood with radiation protective properties. Label: Made with lead, do not eat insulation" + flags = BLOCKHAIR|THICKMATERIAL + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) + strip_delay = 60 + put_on_delay = 60 + resistance_flags = NONE + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/head.dmi', + "Grey" = 'icons/mob/species/grey/head.dmi' + ) + +/obj/item/clothing/suit/radiation + name = "Radiation suit" + desc = "A suit that protects against radiation. Label: Made with lead, do not eat insulation." + icon_state = "rad" + item_state = "rad_suit" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.90 + permeability_coefficient = 0.50 + flags = THICKMATERIAL + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + allowed = list(/obj/item/flashlight,/obj/item/tank/emergency_oxygen) + slowdown = 1.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 60, "rad" = 100, "fire" = 30, "acid" = 30) + flags_inv = HIDEJUMPSUIT|HIDETAIL + strip_delay = 60 + put_on_delay = 60 + resistance_flags = NONE + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/suit.dmi' + ) diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 09fab3322fb..d02c9b40105 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -1,204 +1,204 @@ -/obj/item/clothing/head/wizard - name = "wizard hat" - desc = "Strange-looking hat-wear that most certainly belongs to a real magic user." - icon_state = "wizard" - gas_transfer_coefficient = 0.01 // IT'S MAGICAL OKAY JEEZ +1 TO NOT DIE - permeability_coefficient = 0.01 - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - //Not given any special protective value since the magic robes are full-body protection --NEO - strip_delay = 50 - put_on_delay = 50 - magical = TRUE - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/wizard/red - name = "red wizard hat" - desc = "Strange-looking, red, hat-wear that most certainly belongs to a real magic user." - icon_state = "redwizard" - dog_fashion = /datum/dog_fashion/head/red_wizard - -/obj/item/clothing/head/wizard/black - name = "black wizard hat" - desc = "Strange-looking black hat-wear that most certainly belongs to a real skeleton. Spooky." - icon_state = "blackwizard" - dog_fashion = null - - -/obj/item/clothing/head/wizard/clown - name = "purple wizard hat" - desc = "Strange-looking purple hat-wear that most certainly belongs to a real magic user." - icon_state = "wizhatclown" - item_state = "wizhatclown" // cheating - dog_fashion = null - -/obj/item/clothing/head/wizard/fake - name = "wizard hat" - desc = "It has WIZZARD written across it in sequins. Comes with a cool beard." - icon_state = "wizard-fake" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - magical = FALSE - resistance_flags = FLAMMABLE - dog_fashion = /datum/dog_fashion/head/blue_wizard - -/obj/item/clothing/head/wizard/marisa - name = "Witch Hat" - desc = "Strange-looking hat-wear, makes you want to cast fireballs." - icon_state = "marisa" - dog_fashion = null - -/obj/item/clothing/head/wizard/magus - name = "Magus Helm" - desc = "A mysterious helmet that hums with an unearthly power" - icon_state = "magus" - item_state = "magus" - dog_fashion = null - -/obj/item/clothing/head/wizard/amp - name = "psychic amplifier" - desc = "A crown-of-thorns psychic amplifier. Kind of looks like a tiara having sex with an industrial robot." - icon_state = "amp" - dog_fashion = null - -/obj/item/clothing/suit/wizrobe - name = "wizard robe" - desc = "A magnificant, gem-lined robe that seems to radiate power." - icon_state = "wizard" - item_state = "wizrobe" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - allowed = list(/obj/item/teleportation_scroll) - flags_inv = HIDEJUMPSUIT - strip_delay = 50 - put_on_delay = 50 - resistance_flags = FIRE_PROOF | ACID_PROOF - magical = TRUE - -/obj/item/clothing/suit/wizrobe/red - name = "red wizard robe" - desc = "A magnificant, red, gem-lined robe that seems to radiate power." - icon_state = "redwizard" - item_state = "redwizrobe" - -/obj/item/clothing/suit/wizrobe/black - name = "black wizard robe" - desc = "An unnerving black gem-lined robe that reeks of death and decay." - icon_state = "blackwizard" - item_state = "blackwizrobe" - -/obj/item/clothing/suit/wizrobe/clown - name = "Clown Robe" - desc = "A set of armoured robes that seem to radiate a dark power. That, and bad fashion decisions." - icon_state = "wizzclown" - item_state = "wizzclown" - -/obj/item/clothing/suit/wizrobe/marisa - name = "Witch Robe" - desc = "Magic is all about the spell power, ZE!" - icon_state = "marisa" - item_state = "marisarobe" - -/obj/item/clothing/suit/wizrobe/magusblue - name = "Magus Robe" - desc = "A set of armoured robes that seem to radiate a dark power" - icon_state = "magusblue" - item_state = "magusblue" - -/obj/item/clothing/suit/wizrobe/magusred - name = "Magus Robe" - desc = "A set of armoured robes that seem to radiate a dark power" - icon_state = "magusred" - item_state = "magusred" - -/obj/item/clothing/suit/wizrobe/psypurple - name = "purple robes" - desc = "Heavy, royal purple robes threaded with psychic amplifiers and weird, bulbous lenses. Do not machine wash." - icon_state = "psyamp" - item_state = "psyamp" - -/obj/item/clothing/suit/wizrobe/fake - name = "wizard robe" - desc = "A rather dull, blue robe meant to mimick real wizard robes." - icon_state = "wizard-fake" - item_state = "wizrobe" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - magical = FALSE - -/obj/item/clothing/head/wizard/marisa/fake - name = "Witch Hat" - desc = "Strange-looking hat-wear, makes you want to cast fireballs." - icon_state = "marisa" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - magical = FALSE - -/obj/item/clothing/suit/wizrobe/marisa/fake - name = "Witch Robe" - desc = "Magic is all about the spell power, ZE!" - icon_state = "marisa" - item_state = "marisarobe" - gas_transfer_coefficient = 1 - permeability_coefficient = 1 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - resistance_flags = FLAMMABLE - magical = FALSE - -//Shielded Armour - -/obj/item/clothing/suit/space/hardsuit/shielded/wizard - name = "battlemage armour" - desc = "Not all wizards are afraid of getting up close and personal." - icon_state = "hardsuit-wiz" - item_state = "wiz_hardsuit" - recharge_rate = 0 - current_charges = 15 - recharge_cooldown = INFINITY - shield_state = "shield-red" - shield_on = "shield-red" - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard - armor = list(melee = 30, bullet = 20, laser = 20, energy = 20, bomb = 20, bio = 20, rad = 20, fire = 100, acid = 100) - slowdown = 0 - resistance_flags = FIRE_PROOF | ACID_PROOF - magical = TRUE - -/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard - name = "battlemage helmet" - desc = "A suitably impressive helmet." - icon_state = "hardsuit0-wiz" - item_state = "wiz_helm" - item_color = "wiz" - min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT - max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT - armor = list(melee = 30, bullet = 20, laser = 20, energy = 20, bomb = 20, bio = 20, rad = 20, fire = 100, acid = 100) - actions_types = list() //No inbuilt light - resistance_flags = FIRE_PROOF | ACID_PROOF - magical = TRUE - -/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard/attack_self(mob/user) - return - -/obj/item/wizard_armour_charge - name = "battlemage shield charges" - desc = "A powerful rune that will increase the number of hits a suit of battlemage armour can take before failing.." - icon = 'icons/effects/effects.dmi' - icon_state = "electricity2" - -/obj/item/wizard_armour_charge/afterattack(obj/item/clothing/suit/space/hardsuit/shielded/wizard/W, mob/user) - . = ..() - if(!istype(W)) - to_chat(user, "The rune can only be used on battlemage armour!") - return - W.current_charges += 8 - to_chat(user, "You charge [W]. It can now absorb [W.current_charges] hits.") - qdel(src) \ No newline at end of file +/obj/item/clothing/head/wizard + name = "wizard hat" + desc = "Strange-looking hat-wear that most certainly belongs to a real magic user." + icon_state = "wizard" + gas_transfer_coefficient = 0.01 // IT'S MAGICAL OKAY JEEZ +1 TO NOT DIE + permeability_coefficient = 0.01 + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + //Not given any special protective value since the magic robes are full-body protection --NEO + strip_delay = 50 + put_on_delay = 50 + magical = TRUE + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/wizard/red + name = "red wizard hat" + desc = "Strange-looking, red, hat-wear that most certainly belongs to a real magic user." + icon_state = "redwizard" + dog_fashion = /datum/dog_fashion/head/red_wizard + +/obj/item/clothing/head/wizard/black + name = "black wizard hat" + desc = "Strange-looking black hat-wear that most certainly belongs to a real skeleton. Spooky." + icon_state = "blackwizard" + dog_fashion = null + + +/obj/item/clothing/head/wizard/clown + name = "purple wizard hat" + desc = "Strange-looking purple hat-wear that most certainly belongs to a real magic user." + icon_state = "wizhatclown" + item_state = "wizhatclown" // cheating + dog_fashion = null + +/obj/item/clothing/head/wizard/fake + name = "wizard hat" + desc = "It has WIZZARD written across it in sequins. Comes with a cool beard." + icon_state = "wizard-fake" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + magical = FALSE + resistance_flags = FLAMMABLE + dog_fashion = /datum/dog_fashion/head/blue_wizard + +/obj/item/clothing/head/wizard/marisa + name = "Witch Hat" + desc = "Strange-looking hat-wear, makes you want to cast fireballs." + icon_state = "marisa" + dog_fashion = null + +/obj/item/clothing/head/wizard/magus + name = "Magus Helm" + desc = "A mysterious helmet that hums with an unearthly power" + icon_state = "magus" + item_state = "magus" + dog_fashion = null + +/obj/item/clothing/head/wizard/amp + name = "psychic amplifier" + desc = "A crown-of-thorns psychic amplifier. Kind of looks like a tiara having sex with an industrial robot." + icon_state = "amp" + dog_fashion = null + +/obj/item/clothing/suit/wizrobe + name = "wizard robe" + desc = "A magnificant, gem-lined robe that seems to radiate power." + icon_state = "wizard" + item_state = "wizrobe" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 20, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + allowed = list(/obj/item/teleportation_scroll) + flags_inv = HIDEJUMPSUIT + strip_delay = 50 + put_on_delay = 50 + resistance_flags = FIRE_PROOF | ACID_PROOF + magical = TRUE + +/obj/item/clothing/suit/wizrobe/red + name = "red wizard robe" + desc = "A magnificant, red, gem-lined robe that seems to radiate power." + icon_state = "redwizard" + item_state = "redwizrobe" + +/obj/item/clothing/suit/wizrobe/black + name = "black wizard robe" + desc = "An unnerving black gem-lined robe that reeks of death and decay." + icon_state = "blackwizard" + item_state = "blackwizrobe" + +/obj/item/clothing/suit/wizrobe/clown + name = "Clown Robe" + desc = "A set of armoured robes that seem to radiate a dark power. That, and bad fashion decisions." + icon_state = "wizzclown" + item_state = "wizzclown" + +/obj/item/clothing/suit/wizrobe/marisa + name = "Witch Robe" + desc = "Magic is all about the spell power, ZE!" + icon_state = "marisa" + item_state = "marisarobe" + +/obj/item/clothing/suit/wizrobe/magusblue + name = "Magus Robe" + desc = "A set of armoured robes that seem to radiate a dark power" + icon_state = "magusblue" + item_state = "magusblue" + +/obj/item/clothing/suit/wizrobe/magusred + name = "Magus Robe" + desc = "A set of armoured robes that seem to radiate a dark power" + icon_state = "magusred" + item_state = "magusred" + +/obj/item/clothing/suit/wizrobe/psypurple + name = "purple robes" + desc = "Heavy, royal purple robes threaded with psychic amplifiers and weird, bulbous lenses. Do not machine wash." + icon_state = "psyamp" + item_state = "psyamp" + +/obj/item/clothing/suit/wizrobe/fake + name = "wizard robe" + desc = "A rather dull, blue robe meant to mimick real wizard robes." + icon_state = "wizard-fake" + item_state = "wizrobe" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + magical = FALSE + +/obj/item/clothing/head/wizard/marisa/fake + name = "Witch Hat" + desc = "Strange-looking hat-wear, makes you want to cast fireballs." + icon_state = "marisa" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + magical = FALSE + +/obj/item/clothing/suit/wizrobe/marisa/fake + name = "Witch Robe" + desc = "Magic is all about the spell power, ZE!" + icon_state = "marisa" + item_state = "marisarobe" + gas_transfer_coefficient = 1 + permeability_coefficient = 1 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + resistance_flags = FLAMMABLE + magical = FALSE + +//Shielded Armour + +/obj/item/clothing/suit/space/hardsuit/shielded/wizard + name = "battlemage armour" + desc = "Not all wizards are afraid of getting up close and personal." + icon_state = "hardsuit-wiz" + item_state = "wiz_hardsuit" + recharge_rate = 0 + current_charges = 15 + recharge_cooldown = INFINITY + shield_state = "shield-red" + shield_on = "shield-red" + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard + armor = list(melee = 30, bullet = 20, laser = 20, energy = 20, bomb = 20, bio = 20, rad = 20, fire = 100, acid = 100) + slowdown = 0 + resistance_flags = FIRE_PROOF | ACID_PROOF + magical = TRUE + +/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard + name = "battlemage helmet" + desc = "A suitably impressive helmet." + icon_state = "hardsuit0-wiz" + item_state = "wiz_helm" + item_color = "wiz" + min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT + max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + armor = list(melee = 30, bullet = 20, laser = 20, energy = 20, bomb = 20, bio = 20, rad = 20, fire = 100, acid = 100) + actions_types = list() //No inbuilt light + resistance_flags = FIRE_PROOF | ACID_PROOF + magical = TRUE + +/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard/attack_self(mob/user) + return + +/obj/item/wizard_armour_charge + name = "battlemage shield charges" + desc = "A powerful rune that will increase the number of hits a suit of battlemage armour can take before failing.." + icon = 'icons/effects/effects.dmi' + icon_state = "electricity2" + +/obj/item/wizard_armour_charge/afterattack(obj/item/clothing/suit/space/hardsuit/shielded/wizard/W, mob/user) + . = ..() + if(!istype(W)) + to_chat(user, "The rune can only be used on battlemage armour!") + return + W.current_charges += 8 + to_chat(user, "You charge [W]. It can now absorb [W.current_charges] hits.") + qdel(src) diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm index 3fe4138785a..6b39191eb13 100644 --- a/code/modules/clothing/under/color.dm +++ b/code/modules/clothing/under/color.dm @@ -1,207 +1,207 @@ -/obj/item/clothing/under/color - desc = "A standard issue colored jumpsuit. Variety is the spice of life!" - - -/obj/item/clothing/under/color/random/New() - ..() - var/list/excluded = list(/obj/item/clothing/under/color/random, /obj/item/clothing/under/color/blackf, /obj/item/clothing/under/color/blue/dodgeball, /obj/item/clothing/under/color/orange/prison, /obj/item/clothing/under/color/red/dodgeball, /obj/item/clothing/under/color/red/jersey, /obj/item/clothing/under/color/blue/jersey) - var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - excluded) - name = initial(C.name) - icon_state = initial(C.icon_state) - item_state = initial(C.item_state) - item_color = initial(C.item_color) - -/obj/item/clothing/under/color/black - name = "black jumpsuit" - icon_state = "black" - item_state = "bl_suit" - item_color = "black" - flags_size = ONESIZEFITSALL - resistance_flags = NONE - -/obj/item/clothing/under/color/blackf - name = "feminine black jumpsuit" - desc = "It's very smart and in a ladies-size!" - icon_state = "black" - item_state = "bl_suit" - item_color = "blackf" - -/obj/item/clothing/under/color/blue - name = "blue jumpsuit" - icon_state = "blue" - item_state = "b_suit" - item_color = "blue" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/blue/dodgeball - flags = NODROP - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/green - name = "green jumpsuit" - icon_state = "green" - item_state = "g_suit" - item_color = "green" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/grey - name = "grey jumpsuit" - desc = "A tasteful grey jumpsuit that reminds you of the good old days." - icon_state = "grey" - item_state = "gy_suit" - item_color = "grey" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/grey/greytide - flags = NODROP - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/grey/glorf - name = "ancient jumpsuit" - desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade." - -/obj/item/clothing/under/color/grey/glorf/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - owner.forcesay(GLOB.hit_appends) - return 0 - -/obj/item/clothing/under/color/orange - name = "orange jumpsuit" - desc = "Don't wear this near paranoid security officers" - icon_state = "orange" - item_state = "o_suit" - item_color = "orange" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/orange/prison - name = "orange jumpsuit" - desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." - icon_state = "orange" - item_state = "o_suit" - item_color = "orange" - has_sensor = 2 - sensor_mode = 3 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/pink - name = "pink jumpsuit" - desc = "Just looking at this makes you feel fabulous." - icon_state = "pink" - item_state = "p_suit" - item_color = "pink" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/red - name = "red jumpsuit" - icon_state = "red" - item_state = "r_suit" - item_color = "red" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/red/dodgeball - flags = NODROP - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/white - name = "white jumpsuit" - icon_state = "white" - item_state = "w_suit" - item_color = "white" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/yellow - name = "yellow jumpsuit" - icon_state = "yellow" - item_state = "y_suit" - item_color = "yellow" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/psyche - name = "psychedelic jumpsuit" - desc = "Groovy!" - icon_state = "psyche" - item_color = "psyche" - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/uniform.dmi' - ) - -/obj/item/clothing/under/color/lightblue - name = "light blue jumpsuit" - icon_state = "lightblue" - item_color = "lightblue" - -/obj/item/clothing/under/color/aqua - name = "aqua jumpsuit" - icon_state = "aqua" - item_color = "aqua" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/purple - name = "purple jumpsuit" - icon_state = "purple" - item_state = "p_suit" - item_color = "purple" - -/obj/item/clothing/under/color/lightpurple - name = "light purple jumpsuit" - icon_state = "lightpurple" - item_color = "lightpurple" - -/obj/item/clothing/under/color/lightgreen - name = "light green jumpsuit" - icon_state = "lightgreen" - item_color = "lightgreen" - -/obj/item/clothing/under/color/lightblue - name = "light blue jumpsuit" - icon_state = "lightblue" - item_color = "lightblue" - -/obj/item/clothing/under/color/lightbrown - name = "light brown jumpsuit" - icon_state = "lightbrown" - item_color = "lightbrown" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/brown - name = "brown jumpsuit" - icon_state = "brown" - item_color = "brown" - -/obj/item/clothing/under/color/yellowgreen - name = "yellow green jumpsuit" - icon_state = "yellowgreen" - item_color = "yellowgreen" - -/obj/item/clothing/under/color/darkblue - name = "dark blue jumpsuit" - icon_state = "darkblue" - item_color = "darkblue" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/lightred - name = "light red jumpsuit" - icon_state = "lightred" - item_color = "lightred" - -/obj/item/clothing/under/color/darkred - name = "dark red jumpsuit" - icon_state = "darkred" - item_color = "darkred" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/red/jersey - name = "red team jersey" - desc = "The jersey of the Nanotrasen Phi-ghters!" - icon_state = "redjersey" - item_state = "r_suit" - item_color = "redjersey" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/color/blue/jersey - name = "blue team jersey" - desc = "The jersey of the Nanotrasen Pi-rates!" - icon_state = "bluejersey" - item_state = "b_suit" - item_color = "bluejersey" - flags_size = ONESIZEFITSALL +/obj/item/clothing/under/color + desc = "A standard issue colored jumpsuit. Variety is the spice of life!" + + +/obj/item/clothing/under/color/random/New() + ..() + var/list/excluded = list(/obj/item/clothing/under/color/random, /obj/item/clothing/under/color/blackf, /obj/item/clothing/under/color/blue/dodgeball, /obj/item/clothing/under/color/orange/prison, /obj/item/clothing/under/color/red/dodgeball, /obj/item/clothing/under/color/red/jersey, /obj/item/clothing/under/color/blue/jersey) + var/obj/item/clothing/under/color/C = pick(subtypesof(/obj/item/clothing/under/color) - excluded) + name = initial(C.name) + icon_state = initial(C.icon_state) + item_state = initial(C.item_state) + item_color = initial(C.item_color) + +/obj/item/clothing/under/color/black + name = "black jumpsuit" + icon_state = "black" + item_state = "bl_suit" + item_color = "black" + flags_size = ONESIZEFITSALL + resistance_flags = NONE + +/obj/item/clothing/under/color/blackf + name = "feminine black jumpsuit" + desc = "It's very smart and in a ladies-size!" + icon_state = "black" + item_state = "bl_suit" + item_color = "blackf" + +/obj/item/clothing/under/color/blue + name = "blue jumpsuit" + icon_state = "blue" + item_state = "b_suit" + item_color = "blue" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/blue/dodgeball + flags = NODROP + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/green + name = "green jumpsuit" + icon_state = "green" + item_state = "g_suit" + item_color = "green" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/grey + name = "grey jumpsuit" + desc = "A tasteful grey jumpsuit that reminds you of the good old days." + icon_state = "grey" + item_state = "gy_suit" + item_color = "grey" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/grey/greytide + flags = NODROP + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/grey/glorf + name = "ancient jumpsuit" + desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade." + +/obj/item/clothing/under/color/grey/glorf/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + owner.forcesay(GLOB.hit_appends) + return 0 + +/obj/item/clothing/under/color/orange + name = "orange jumpsuit" + desc = "Don't wear this near paranoid security officers" + icon_state = "orange" + item_state = "o_suit" + item_color = "orange" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/orange/prison + name = "orange jumpsuit" + desc = "It's standardised Nanotrasen prisoner-wear. Its suit sensors are stuck in the \"Fully On\" position." + icon_state = "orange" + item_state = "o_suit" + item_color = "orange" + has_sensor = 2 + sensor_mode = 3 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/pink + name = "pink jumpsuit" + desc = "Just looking at this makes you feel fabulous." + icon_state = "pink" + item_state = "p_suit" + item_color = "pink" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/red + name = "red jumpsuit" + icon_state = "red" + item_state = "r_suit" + item_color = "red" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/red/dodgeball + flags = NODROP + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/white + name = "white jumpsuit" + icon_state = "white" + item_state = "w_suit" + item_color = "white" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/yellow + name = "yellow jumpsuit" + icon_state = "yellow" + item_state = "y_suit" + item_color = "yellow" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/psyche + name = "psychedelic jumpsuit" + desc = "Groovy!" + icon_state = "psyche" + item_color = "psyche" + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/uniform.dmi' + ) + +/obj/item/clothing/under/color/lightblue + name = "light blue jumpsuit" + icon_state = "lightblue" + item_color = "lightblue" + +/obj/item/clothing/under/color/aqua + name = "aqua jumpsuit" + icon_state = "aqua" + item_color = "aqua" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/purple + name = "purple jumpsuit" + icon_state = "purple" + item_state = "p_suit" + item_color = "purple" + +/obj/item/clothing/under/color/lightpurple + name = "light purple jumpsuit" + icon_state = "lightpurple" + item_color = "lightpurple" + +/obj/item/clothing/under/color/lightgreen + name = "light green jumpsuit" + icon_state = "lightgreen" + item_color = "lightgreen" + +/obj/item/clothing/under/color/lightblue + name = "light blue jumpsuit" + icon_state = "lightblue" + item_color = "lightblue" + +/obj/item/clothing/under/color/lightbrown + name = "light brown jumpsuit" + icon_state = "lightbrown" + item_color = "lightbrown" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/brown + name = "brown jumpsuit" + icon_state = "brown" + item_color = "brown" + +/obj/item/clothing/under/color/yellowgreen + name = "yellow green jumpsuit" + icon_state = "yellowgreen" + item_color = "yellowgreen" + +/obj/item/clothing/under/color/darkblue + name = "dark blue jumpsuit" + icon_state = "darkblue" + item_color = "darkblue" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/lightred + name = "light red jumpsuit" + icon_state = "lightred" + item_color = "lightred" + +/obj/item/clothing/under/color/darkred + name = "dark red jumpsuit" + icon_state = "darkred" + item_color = "darkred" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/red/jersey + name = "red team jersey" + desc = "The jersey of the Nanotrasen Phi-ghters!" + icon_state = "redjersey" + item_state = "r_suit" + item_color = "redjersey" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/color/blue/jersey + name = "blue team jersey" + desc = "The jersey of the Nanotrasen Pi-rates!" + icon_state = "bluejersey" + item_state = "b_suit" + item_color = "bluejersey" + flags_size = ONESIZEFITSALL diff --git a/code/modules/clothing/under/jobs/civilian.dm b/code/modules/clothing/under/jobs/civilian.dm index 879e0fd22c7..3850ce559df 100644 --- a/code/modules/clothing/under/jobs/civilian.dm +++ b/code/modules/clothing/under/jobs/civilian.dm @@ -1,239 +1,239 @@ -//Alphabetical order of support jobs. - -/obj/item/clothing/under/rank/bartender - desc = "It looks like it could use some more flair." - name = "bartender's uniform" - icon_state = "ba_suit" - item_state = "ba_suit" - item_color = "ba_suit" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/captain //Alright, technically not a 'civilian' but its better then giving a .dm file for a single define. - desc = "It's a blue jumpsuit with some gold markings denoting the rank of \"Captain\"." - name = "captain's jumpsuit" - icon_state = "captain" - item_state = "caparmor" - item_color = "captain" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/cargo - name = "quartermaster's jumpsuit" - desc = "It's a jumpsuit worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper." - icon_state = "qm" - item_state = "lb_suit" - item_color = "qm" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/cargo/skirt - name = "quartermaster's jumpskirt" - desc = "It's a jumpskirt worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper." - icon_state = "qmf" - item_color = "qmf" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - flags_size = null - -/obj/item/clothing/under/rank/cargotech - name = "cargo technician's jumpsuit" - desc = "Shooooorts! They're comfy and easy to wear!" - icon_state = "cargotech" - item_state = "lb_suit" - item_color = "cargo" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/cargotech/skirt - name = "cargo technician's jumpskirt" - desc = "Skirrrrrts! They're comfy and easy to wear!" - icon_state = "cargof" - item_color = "cargof" - flags_size = null - -/obj/item/clothing/under/rank/chaplain - desc = "It's a black jumpsuit, often worn by religious folk." - name = "chaplain's jumpsuit" - icon_state = "chaplain" - item_state = "bl_suit" - item_color = "chapblack" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/chef - desc = "It's an apron which is given only to the most hardcore chefs in space." - name = "chef's uniform" - icon_state = "chef" - item_color = "chef" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/clown - name = "clown suit" - desc = "'HONK!'" - icon_state = "clown" - item_state = "clown" - item_color = "clown" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/clown/Initialize() - . = ..() - AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg' = 1), 50) - -/obj/item/clothing/under/rank/clown/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.mind && H.mind.assigned_role == "Clown") - score_clownabuse++ - return ..() - -/obj/item/clothing/under/rank/clown/sexy - name = "sexy-clown suit" - desc = "It makes you look HONKable!" - icon_state = "sexyclown" - item_state = "sexyclown" - item_color = "sexyclown" - -/obj/item/clothing/under/rank/clown/nodrop - flags = NODROP - -/obj/item/clothing/under/rank/head_of_personnel - desc = "It's a jumpsuit worn by someone who works in the position of \"Head of Personnel\"." - name = "head of personnel's jumpsuit" - icon_state = "hop" - item_state = "b_suit" - item_color = "hop" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/head_of_personnel_whimsy - desc = "A blue jacket and red tie, with matching red cuffs! Snazzy. Wearing this makes you feel more important than your job title does." - name = "head of personnel's suit" - icon_state = "hopwhimsy" - item_state = "hopwhimsy" - item_color = "hopwhimsy" - - -/obj/item/clothing/under/rank/hydroponics - desc = "It's a jumpsuit designed to protect against minor plant-related hazards." - name = "botanist's jumpsuit" - icon_state = "hydroponics" - item_state = "g_suit" - item_color = "hydroponics" - permeability_coefficient = 0.50 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/internalaffairs - desc = "The plain, professional attire of an Internal Affairs Agent. The collar is immaculately starched." - name = "Internal Affairs uniform" - icon_state = "internalaffairs" - item_state = "internalaffairs" - item_color = "internalaffairs" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/ntrep - desc = "A well-ironed dress shirt and matching set of black pants." - name = "dress shirt" - icon_state = "internalaffairs" - item_state = "internalaffairs" - item_color = "internalaffairs" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/ntrep/skirt - desc = "A silky smooth black and gold representative uniform with blue markings." - name = "representative skirt" - icon_state = "ntrepf" - item_state = "ntrepf" - item_color = "ntrepf" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/janitor - desc = "It's the official uniform of the station's janitor. It has minor protection from biohazards." - name = "janitor's jumpsuit" - icon_state = "janitor" - item_color = "janitor" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - - -/obj/item/clothing/under/lawyer - desc = "Slick threads." - name = "Lawyer suit" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/lawyer/black - icon_state = "lawyer_black" - item_state = "lawyer_black" - item_color = "lawyer_black" - -/obj/item/clothing/under/lawyer/female - icon_state = "black_suit_fem" - item_state = "black_suit_fem" - item_color = "black_suit_fem" - -/obj/item/clothing/under/lawyer/red - icon_state = "lawyer_red" - item_state = "lawyer_red" - item_color = "lawyer_red" - -/obj/item/clothing/under/lawyer/blue - icon_state = "lawyer_blue" - item_state = "lawyer_blue" - item_color = "lawyer_blue" - -/obj/item/clothing/under/lawyer/bluesuit - name = "Blue Suit" - desc = "A classy suit and tie" - icon_state = "bluesuit" - item_state = "bluesuit" - item_color = "bluesuit" - -/obj/item/clothing/under/lawyer/purpsuit - name = "Purple Suit" - icon_state = "lawyer_purp" - item_state = "lawyer_purp" - item_color = "lawyer_purp" - -/obj/item/clothing/under/lawyer/oldman - name = "Old Man's Suit" - desc = "A classic suit for the older gentleman with built in back support." - icon_state = "oldman" - item_state = "oldman" - item_color = "oldman" - - -/obj/item/clothing/under/librarian - name = "sensible suit" - desc = "It's very... sensible." - icon_state = "red_suit" - item_state = "red_suit" - item_color = "red_suit" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/mime - name = "mime's outfit" - desc = "It's not very colourful." - icon_state = "mime" - item_state = "mime" - item_color = "mime" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/mime/nodrop - flags = NODROP - -/obj/item/clothing/under/rank/miner - desc = "It's a snappy jumpsuit with a sturdy set of overalls. It is very dirty." - name = "shaft miner's jumpsuit" - icon_state = "miner" - item_state = "miner" - item_color = "miner" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/miner/lavaland - desc = "A green uniform for operating in hazardous environments." - name = "shaft miner's jumpsuit" - icon_state = "explorer" - item_state = "explorer" - item_color = "explorer" - - sprite_sheets = list() - -/obj/item/clothing/under/barber - desc = "It's a barber's uniform." - name = "barber's uniform" - icon_state = "barber" - item_state = "barber" - item_color = "barber" +//Alphabetical order of support jobs. + +/obj/item/clothing/under/rank/bartender + desc = "It looks like it could use some more flair." + name = "bartender's uniform" + icon_state = "ba_suit" + item_state = "ba_suit" + item_color = "ba_suit" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/captain //Alright, technically not a 'civilian' but its better then giving a .dm file for a single define. + desc = "It's a blue jumpsuit with some gold markings denoting the rank of \"Captain\"." + name = "captain's jumpsuit" + icon_state = "captain" + item_state = "caparmor" + item_color = "captain" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/cargo + name = "quartermaster's jumpsuit" + desc = "It's a jumpsuit worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper." + icon_state = "qm" + item_state = "lb_suit" + item_color = "qm" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/cargo/skirt + name = "quartermaster's jumpskirt" + desc = "It's a jumpskirt worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper." + icon_state = "qmf" + item_color = "qmf" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + flags_size = null + +/obj/item/clothing/under/rank/cargotech + name = "cargo technician's jumpsuit" + desc = "Shooooorts! They're comfy and easy to wear!" + icon_state = "cargotech" + item_state = "lb_suit" + item_color = "cargo" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/cargotech/skirt + name = "cargo technician's jumpskirt" + desc = "Skirrrrrts! They're comfy and easy to wear!" + icon_state = "cargof" + item_color = "cargof" + flags_size = null + +/obj/item/clothing/under/rank/chaplain + desc = "It's a black jumpsuit, often worn by religious folk." + name = "chaplain's jumpsuit" + icon_state = "chaplain" + item_state = "bl_suit" + item_color = "chapblack" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/chef + desc = "It's an apron which is given only to the most hardcore chefs in space." + name = "chef's uniform" + icon_state = "chef" + item_color = "chef" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/clown + name = "clown suit" + desc = "'HONK!'" + icon_state = "clown" + item_state = "clown" + item_color = "clown" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/clown/Initialize() + . = ..() + AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg' = 1), 50) + +/obj/item/clothing/under/rank/clown/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.mind && H.mind.assigned_role == "Clown") + score_clownabuse++ + return ..() + +/obj/item/clothing/under/rank/clown/sexy + name = "sexy-clown suit" + desc = "It makes you look HONKable!" + icon_state = "sexyclown" + item_state = "sexyclown" + item_color = "sexyclown" + +/obj/item/clothing/under/rank/clown/nodrop + flags = NODROP + +/obj/item/clothing/under/rank/head_of_personnel + desc = "It's a jumpsuit worn by someone who works in the position of \"Head of Personnel\"." + name = "head of personnel's jumpsuit" + icon_state = "hop" + item_state = "b_suit" + item_color = "hop" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/head_of_personnel_whimsy + desc = "A blue jacket and red tie, with matching red cuffs! Snazzy. Wearing this makes you feel more important than your job title does." + name = "head of personnel's suit" + icon_state = "hopwhimsy" + item_state = "hopwhimsy" + item_color = "hopwhimsy" + + +/obj/item/clothing/under/rank/hydroponics + desc = "It's a jumpsuit designed to protect against minor plant-related hazards." + name = "botanist's jumpsuit" + icon_state = "hydroponics" + item_state = "g_suit" + item_color = "hydroponics" + permeability_coefficient = 0.50 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/internalaffairs + desc = "The plain, professional attire of an Internal Affairs Agent. The collar is immaculately starched." + name = "Internal Affairs uniform" + icon_state = "internalaffairs" + item_state = "internalaffairs" + item_color = "internalaffairs" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/ntrep + desc = "A well-ironed dress shirt and matching set of black pants." + name = "dress shirt" + icon_state = "internalaffairs" + item_state = "internalaffairs" + item_color = "internalaffairs" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/ntrep/skirt + desc = "A silky smooth black and gold representative uniform with blue markings." + name = "representative skirt" + icon_state = "ntrepf" + item_state = "ntrepf" + item_color = "ntrepf" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/janitor + desc = "It's the official uniform of the station's janitor. It has minor protection from biohazards." + name = "janitor's jumpsuit" + icon_state = "janitor" + item_color = "janitor" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + + +/obj/item/clothing/under/lawyer + desc = "Slick threads." + name = "Lawyer suit" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/lawyer/black + icon_state = "lawyer_black" + item_state = "lawyer_black" + item_color = "lawyer_black" + +/obj/item/clothing/under/lawyer/female + icon_state = "black_suit_fem" + item_state = "black_suit_fem" + item_color = "black_suit_fem" + +/obj/item/clothing/under/lawyer/red + icon_state = "lawyer_red" + item_state = "lawyer_red" + item_color = "lawyer_red" + +/obj/item/clothing/under/lawyer/blue + icon_state = "lawyer_blue" + item_state = "lawyer_blue" + item_color = "lawyer_blue" + +/obj/item/clothing/under/lawyer/bluesuit + name = "Blue Suit" + desc = "A classy suit and tie" + icon_state = "bluesuit" + item_state = "bluesuit" + item_color = "bluesuit" + +/obj/item/clothing/under/lawyer/purpsuit + name = "Purple Suit" + icon_state = "lawyer_purp" + item_state = "lawyer_purp" + item_color = "lawyer_purp" + +/obj/item/clothing/under/lawyer/oldman + name = "Old Man's Suit" + desc = "A classic suit for the older gentleman with built in back support." + icon_state = "oldman" + item_state = "oldman" + item_color = "oldman" + + +/obj/item/clothing/under/librarian + name = "sensible suit" + desc = "It's very... sensible." + icon_state = "red_suit" + item_state = "red_suit" + item_color = "red_suit" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/mime + name = "mime's outfit" + desc = "It's not very colourful." + icon_state = "mime" + item_state = "mime" + item_color = "mime" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/mime/nodrop + flags = NODROP + +/obj/item/clothing/under/rank/miner + desc = "It's a snappy jumpsuit with a sturdy set of overalls. It is very dirty." + name = "shaft miner's jumpsuit" + icon_state = "miner" + item_state = "miner" + item_color = "miner" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/miner/lavaland + desc = "A green uniform for operating in hazardous environments." + name = "shaft miner's jumpsuit" + icon_state = "explorer" + item_state = "explorer" + item_color = "explorer" + + sprite_sheets = list() + +/obj/item/clothing/under/barber + desc = "It's a barber's uniform." + name = "barber's uniform" + icon_state = "barber" + item_state = "barber" + item_color = "barber" diff --git a/code/modules/clothing/under/jobs/engineering.dm b/code/modules/clothing/under/jobs/engineering.dm index 14878e402a1..bdda81ad03d 100644 --- a/code/modules/clothing/under/jobs/engineering.dm +++ b/code/modules/clothing/under/jobs/engineering.dm @@ -1,77 +1,77 @@ -//Contains: Engineering department jumpsuits -/obj/item/clothing/under/rank/chief_engineer - desc = "It's a high visibility jumpsuit given to those engineers insane enough to achieve the rank of \"Chief engineer\". It has minor radiation shielding." - name = "chief engineer's jumpsuit" - icon_state = "chiefengineer" - item_state = "chief" - item_color = "chief" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 80, "acid" = 40) - flags_size = ONESIZEFITSALL - resistance_flags = NONE - -/obj/item/clothing/under/rank/chief_engineer/skirt - desc = "It's a high visibility jumpskirt given to those engineers insane enough to achieve the rank of \"Chief engineer\". It has minor radiation shielding." - name = "chief engineer's jumpskirt" - icon_state = "chieff" - item_color = "chieff" - flags_size = null - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - -/obj/item/clothing/under/rank/atmospheric_technician - desc = "It's a jumpsuit worn by atmospheric technicians." - name = "atmospheric technician's jumpsuit" - icon_state = "atmos" - item_state = "atmos_suit" - item_color = "atmos" - flags_size = ONESIZEFITSALL - resistance_flags = NONE - -/obj/item/clothing/under/rank/atmospheric_technician/skirt - desc = "It's a jumpskirt worn by atmospheric technicians." - name = "atmospheric technician's jumpskirt" - icon_state = "atmosf" - item_color = "atmosf" - flags_size = null - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - -/obj/item/clothing/under/rank/engineer - desc = "It's an orange high visibility jumpsuit worn by engineers. It has minor radiation shielding." - name = "engineer's jumpsuit" - icon_state = "engine" - item_state = "engi_suit" - item_color = "engine" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20) - flags_size = ONESIZEFITSALL - resistance_flags = NONE - - -/obj/item/clothing/under/rank/engineer/skirt - desc = "It's an orange high visibility jumpskirt worn by engineers. It has minor radiation shielding." - name = "engineer's jumpskirt" - icon_state = "enginef" - item_color = "enginef" - flags_size = null - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - -/obj/item/clothing/under/rank/roboticist - desc = "It's a slimming black with reinforced seams; great for industrial work." - name = "roboticist's jumpsuit" - icon_state = "robotics" - item_state = "robotics" - item_color = "robotics" - flags_size = ONESIZEFITSALL - resistance_flags = NONE - -/obj/item/clothing/under/rank/roboticist/skirt - desc = "It's a slimming black jumpskirt with reinforced seams; great for industrial work." - name = "roboticist's jumpskirt" - icon_state = "roboticsf" - item_color = "roboticsf" - flags_size = null - -/obj/item/clothing/under/rank/mechanic - desc = "It's a pair of overalls worn by mechanics." - name = "mechanic's overalls" - icon_state = "mechanic" - item_state = "mechanic" - item_color = "mechanic" +//Contains: Engineering department jumpsuits +/obj/item/clothing/under/rank/chief_engineer + desc = "It's a high visibility jumpsuit given to those engineers insane enough to achieve the rank of \"Chief engineer\". It has minor radiation shielding." + name = "chief engineer's jumpsuit" + icon_state = "chiefengineer" + item_state = "chief" + item_color = "chief" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 80, "acid" = 40) + flags_size = ONESIZEFITSALL + resistance_flags = NONE + +/obj/item/clothing/under/rank/chief_engineer/skirt + desc = "It's a high visibility jumpskirt given to those engineers insane enough to achieve the rank of \"Chief engineer\". It has minor radiation shielding." + name = "chief engineer's jumpskirt" + icon_state = "chieff" + item_color = "chieff" + flags_size = null + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/under/rank/atmospheric_technician + desc = "It's a jumpsuit worn by atmospheric technicians." + name = "atmospheric technician's jumpsuit" + icon_state = "atmos" + item_state = "atmos_suit" + item_color = "atmos" + flags_size = ONESIZEFITSALL + resistance_flags = NONE + +/obj/item/clothing/under/rank/atmospheric_technician/skirt + desc = "It's a jumpskirt worn by atmospheric technicians." + name = "atmospheric technician's jumpskirt" + icon_state = "atmosf" + item_color = "atmosf" + flags_size = null + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/under/rank/engineer + desc = "It's an orange high visibility jumpsuit worn by engineers. It has minor radiation shielding." + name = "engineer's jumpsuit" + icon_state = "engine" + item_state = "engi_suit" + item_color = "engine" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20) + flags_size = ONESIZEFITSALL + resistance_flags = NONE + + +/obj/item/clothing/under/rank/engineer/skirt + desc = "It's an orange high visibility jumpskirt worn by engineers. It has minor radiation shielding." + name = "engineer's jumpskirt" + icon_state = "enginef" + item_color = "enginef" + flags_size = null + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/under/rank/roboticist + desc = "It's a slimming black with reinforced seams; great for industrial work." + name = "roboticist's jumpsuit" + icon_state = "robotics" + item_state = "robotics" + item_color = "robotics" + flags_size = ONESIZEFITSALL + resistance_flags = NONE + +/obj/item/clothing/under/rank/roboticist/skirt + desc = "It's a slimming black jumpskirt with reinforced seams; great for industrial work." + name = "roboticist's jumpskirt" + icon_state = "roboticsf" + item_color = "roboticsf" + flags_size = null + +/obj/item/clothing/under/rank/mechanic + desc = "It's a pair of overalls worn by mechanics." + name = "mechanic's overalls" + icon_state = "mechanic" + item_state = "mechanic" + item_color = "mechanic" diff --git a/code/modules/clothing/under/jobs/medsci.dm b/code/modules/clothing/under/jobs/medsci.dm index f5d65b964fc..d3fcad16680 100644 --- a/code/modules/clothing/under/jobs/medsci.dm +++ b/code/modules/clothing/under/jobs/medsci.dm @@ -1,246 +1,246 @@ -/* - * Science - */ -/obj/item/clothing/under/rank/research_director - desc = "It's a jumpsuit worn by those with the know-how to achieve the position of \"Research Director\". Its fabric provides minor protection from biological contaminants." - name = "research director's jumpsuit" - icon_state = "director" - item_state = "g_suit" - item_color = "director" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 35) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/scientist - desc = "It's made of a special fiber that provides minor protection against biohazards. It has markings that denote the wearer as a scientist." - name = "scientist's jumpsuit" - icon_state = "toxins" - item_state = "w_suit" - item_color = "toxinswhite" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/scientist/skirt - name = "scientist's jumpskirt" - icon_state = "sciencewhitef" - item_color = "sciencewhitef" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - flags_size = null - -/obj/item/clothing/under/rank/chemist - desc = "It's made of a special fiber that gives special protection against biohazards. It has a chemist rank stripe on it." - name = "chemist's jumpsuit" - icon_state = "chemistry" - item_state = "w_suit" - item_color = "chemistrywhite" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 65) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/chemist/skirt - name = "chemist's jumpskirt" - icon_state = "chemistrywhitef" - item_color = "chemistrywhitef" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - flags_size = null - -/* - * Medical - */ -/obj/item/clothing/under/rank/chief_medical_officer - desc = "It's a jumpsuit worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection." - name = "chief medical officer's jumpsuit" - icon_state = "cmo" - item_state = "w_suit" - item_color = "cmo" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/chief_medical_officer/skirt - desc = "It's a jumpskirt worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection." - name = "chief medical officer's jumpskirt" - icon_state = "cmof" - item_color = "cmof" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - flags_size = null - -/obj/item/clothing/under/rank/geneticist - desc = "It's made of a special fiber that gives special protection against biohazards. It has a genetics rank stripe on it." - name = "geneticist's jumpsuit" - icon_state = "genetics" - item_state = "w_suit" - item_color = "geneticswhite" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/geneticist/skirt - name = "geneticist's jumpskirt" - icon_state = "geneticswhitef" - item_color = "geneticswhitef" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - flags_size = null - -/obj/item/clothing/under/rank/virologist - desc = "It's made of a special fiber that gives special protection against biohazards. It has a virologist rank stripe on it." - name = "virologist's jumpsuit" - icon_state = "virology" - item_state = "w_suit" - item_color = "virologywhite" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/virologist/skirt - name = "virologist's jumpskirt" - icon_state = "virologywhitef" - item_color = "virologywhitef" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - flags_size = null - -/obj/item/clothing/under/rank/nursesuit - desc = "It's a jumpsuit commonly worn by nursing staff in the medical department." - name = "nurse's suit" - icon_state = "nursesuit" - item_state = "nursesuit" - item_color = "nursesuit" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/nurse - desc = "A dress commonly worn by the nursing staff in the medical department." - name = "nurse's dress" - icon_state = "nurse" - item_state = "nurse" - item_color = "nurse" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/orderly - desc = "A white suit to be worn by orderly people who love orderly things." - name = "orderly's uniform" - icon_state = "orderly" - item_state = "orderly" - item_color = "orderly" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/medical - desc = "It's made of a special fiber that provides minor protection against biohazards. It has a cross on the chest denoting that the wearer is trained medical personnel." - name = "medical doctor's jumpsuit" - icon_state = "medical" - item_state = "w_suit" - item_color = "medical" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/medical/skirt - name = "medical doctor's jumpskirt" - icon_state = "medicalf" - item_color = "medicalf" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - flags_size = null - -/obj/item/clothing/under/rank/medical/blue - name = "medical scrubs" - desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in baby blue." - icon_state = "scrubsblue" - item_color = "scrubsblue" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/medical/green - name = "medical scrubs" - desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in dark green." - icon_state = "scrubsgreen" - item_color = "scrubsgreen" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/medical/purple - name = "medical scrubs" - desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in deep purple." - icon_state = "scrubspurple" - item_color = "scrubspurple" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/medical/mortician - name = "coroner's scrubs" - desc = "It's made of a special fiber that provides minor protection against biohazards. This one is as dark as an emo's poetry." - icon_state = "scrubsblack" - item_color = "scrubsblack" - flags_size = ONESIZEFITSALL - -//paramedic -/obj/item/clothing/under/rank/medical/paramedic - desc = "It's made of a special fiber that provides minor protection against biohazards and radiation. It has a cross on the chest denoting that the wearer is trained medical personnel." - name = "paramedic's jumpsuit" - icon_state = "paramedic" - item_state = "paramedic" - item_color = "paramedic" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 10, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/psych - desc = "A basic white jumpsuit. It has turqouise markings that denote the wearer as a psychiatrist." - name = "psychiatrist's jumpsuit" - icon_state = "psych" - item_state = "w_suit" - item_color = "psych" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/psych/turtleneck - desc = "A turqouise turtleneck and a pair of dark blue slacks, belonging to a psychologist." - name = "psychologist's turtleneck" - icon_state = "psychturtle" - item_state = "b_suit" - item_color = "psychturtle" - flags_size = ONESIZEFITSALL - - -/* - * Medsci, unused (i think) stuff - */ -/obj/item/clothing/under/rank/geneticist_new - desc = "It's made of a special fiber which provides minor protection against biohazards." - name = "geneticist's jumpsuit" - icon_state = "genetics_new" - item_state = "w_suit" - item_color = "genetics_new" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/chemist_new - desc = "It's made of a special fiber which provides minor protection against biohazards." - name = "chemist's jumpsuit" - icon_state = "chemist_new" - item_state = "w_suit" - item_color = "chemist_new" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 65) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/scientist_new - desc = "Made of a special fiber that gives special protection against biohazards and small explosions." - name = "scientist's jumpsuit" - icon_state = "scientist_new" - item_state = "w_suit" - item_color = "scientist_new" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/virologist_new - desc = "Made of a special fiber that gives increased protection against biohazards." - name = "virologist's jumpsuit" - icon_state = "virologist_new" - item_state = "w_suit" - item_color = "virologist_new" - permeability_coefficient = 0.50 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - flags_size = ONESIZEFITSALL +/* + * Science + */ +/obj/item/clothing/under/rank/research_director + desc = "It's a jumpsuit worn by those with the know-how to achieve the position of \"Research Director\". Its fabric provides minor protection from biological contaminants." + name = "research director's jumpsuit" + icon_state = "director" + item_state = "g_suit" + item_color = "director" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 35) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/scientist + desc = "It's made of a special fiber that provides minor protection against biohazards. It has markings that denote the wearer as a scientist." + name = "scientist's jumpsuit" + icon_state = "toxins" + item_state = "w_suit" + item_color = "toxinswhite" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/scientist/skirt + name = "scientist's jumpskirt" + icon_state = "sciencewhitef" + item_color = "sciencewhitef" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + flags_size = null + +/obj/item/clothing/under/rank/chemist + desc = "It's made of a special fiber that gives special protection against biohazards. It has a chemist rank stripe on it." + name = "chemist's jumpsuit" + icon_state = "chemistry" + item_state = "w_suit" + item_color = "chemistrywhite" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 65) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/chemist/skirt + name = "chemist's jumpskirt" + icon_state = "chemistrywhitef" + item_color = "chemistrywhitef" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + flags_size = null + +/* + * Medical + */ +/obj/item/clothing/under/rank/chief_medical_officer + desc = "It's a jumpsuit worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection." + name = "chief medical officer's jumpsuit" + icon_state = "cmo" + item_state = "w_suit" + item_color = "cmo" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/chief_medical_officer/skirt + desc = "It's a jumpskirt worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection." + name = "chief medical officer's jumpskirt" + icon_state = "cmof" + item_color = "cmof" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + flags_size = null + +/obj/item/clothing/under/rank/geneticist + desc = "It's made of a special fiber that gives special protection against biohazards. It has a genetics rank stripe on it." + name = "geneticist's jumpsuit" + icon_state = "genetics" + item_state = "w_suit" + item_color = "geneticswhite" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/geneticist/skirt + name = "geneticist's jumpskirt" + icon_state = "geneticswhitef" + item_color = "geneticswhitef" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + flags_size = null + +/obj/item/clothing/under/rank/virologist + desc = "It's made of a special fiber that gives special protection against biohazards. It has a virologist rank stripe on it." + name = "virologist's jumpsuit" + icon_state = "virology" + item_state = "w_suit" + item_color = "virologywhite" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/virologist/skirt + name = "virologist's jumpskirt" + icon_state = "virologywhitef" + item_color = "virologywhitef" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + flags_size = null + +/obj/item/clothing/under/rank/nursesuit + desc = "It's a jumpsuit commonly worn by nursing staff in the medical department." + name = "nurse's suit" + icon_state = "nursesuit" + item_state = "nursesuit" + item_color = "nursesuit" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/nurse + desc = "A dress commonly worn by the nursing staff in the medical department." + name = "nurse's dress" + icon_state = "nurse" + item_state = "nurse" + item_color = "nurse" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/orderly + desc = "A white suit to be worn by orderly people who love orderly things." + name = "orderly's uniform" + icon_state = "orderly" + item_state = "orderly" + item_color = "orderly" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/medical + desc = "It's made of a special fiber that provides minor protection against biohazards. It has a cross on the chest denoting that the wearer is trained medical personnel." + name = "medical doctor's jumpsuit" + icon_state = "medical" + item_state = "w_suit" + item_color = "medical" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/medical/skirt + name = "medical doctor's jumpskirt" + icon_state = "medicalf" + item_color = "medicalf" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + flags_size = null + +/obj/item/clothing/under/rank/medical/blue + name = "medical scrubs" + desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in baby blue." + icon_state = "scrubsblue" + item_color = "scrubsblue" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/medical/green + name = "medical scrubs" + desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in dark green." + icon_state = "scrubsgreen" + item_color = "scrubsgreen" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/medical/purple + name = "medical scrubs" + desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in deep purple." + icon_state = "scrubspurple" + item_color = "scrubspurple" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/medical/mortician + name = "coroner's scrubs" + desc = "It's made of a special fiber that provides minor protection against biohazards. This one is as dark as an emo's poetry." + icon_state = "scrubsblack" + item_color = "scrubsblack" + flags_size = ONESIZEFITSALL + +//paramedic +/obj/item/clothing/under/rank/medical/paramedic + desc = "It's made of a special fiber that provides minor protection against biohazards and radiation. It has a cross on the chest denoting that the wearer is trained medical personnel." + name = "paramedic's jumpsuit" + icon_state = "paramedic" + item_state = "paramedic" + item_color = "paramedic" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 10, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/psych + desc = "A basic white jumpsuit. It has turqouise markings that denote the wearer as a psychiatrist." + name = "psychiatrist's jumpsuit" + icon_state = "psych" + item_state = "w_suit" + item_color = "psych" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/psych/turtleneck + desc = "A turqouise turtleneck and a pair of dark blue slacks, belonging to a psychologist." + name = "psychologist's turtleneck" + icon_state = "psychturtle" + item_state = "b_suit" + item_color = "psychturtle" + flags_size = ONESIZEFITSALL + + +/* + * Medsci, unused (i think) stuff + */ +/obj/item/clothing/under/rank/geneticist_new + desc = "It's made of a special fiber which provides minor protection against biohazards." + name = "geneticist's jumpsuit" + icon_state = "genetics_new" + item_state = "w_suit" + item_color = "genetics_new" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/chemist_new + desc = "It's made of a special fiber which provides minor protection against biohazards." + name = "chemist's jumpsuit" + icon_state = "chemist_new" + item_state = "w_suit" + item_color = "chemist_new" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 65) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/scientist_new + desc = "Made of a special fiber that gives special protection against biohazards and small explosions." + name = "scientist's jumpsuit" + icon_state = "scientist_new" + item_state = "w_suit" + item_color = "scientist_new" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/virologist_new + desc = "Made of a special fiber that gives increased protection against biohazards." + name = "virologist's jumpsuit" + icon_state = "virologist_new" + item_state = "w_suit" + item_color = "virologist_new" + permeability_coefficient = 0.50 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + flags_size = ONESIZEFITSALL diff --git a/code/modules/clothing/under/jobs/plasmamen/_plasmamen.dm b/code/modules/clothing/under/jobs/plasmamen/_plasmamen.dm index 937d5111830..b3d7ce83431 100644 --- a/code/modules/clothing/under/jobs/plasmamen/_plasmamen.dm +++ b/code/modules/clothing/under/jobs/plasmamen/_plasmamen.dm @@ -52,4 +52,4 @@ name = "envirosuit extinguisher cartridge" desc = "A cartridge loaded with a compressed extinguisher mix, used to refill the automatic extinguisher on plasma envirosuits." icon_state = "plasmarefill" - icon = 'icons/obj/device.dmi' \ No newline at end of file + icon = 'icons/obj/device.dmi' diff --git a/code/modules/clothing/under/jobs/plasmamen/civilian_service.dm b/code/modules/clothing/under/jobs/plasmamen/civilian_service.dm index 69697d3be6f..0ef8df4d57b 100644 --- a/code/modules/clothing/under/jobs/plasmamen/civilian_service.dm +++ b/code/modules/clothing/under/jobs/plasmamen/civilian_service.dm @@ -104,4 +104,4 @@ desc = "An envirosuit designed for plasmamen employed as the blueshield." icon_state = "bs_envirosuit" item_state = "bs_envirosuit" - item_color = "bs_envirosuit" \ No newline at end of file + item_color = "bs_envirosuit" diff --git a/code/modules/clothing/under/jobs/plasmamen/security.dm b/code/modules/clothing/under/jobs/plasmamen/security.dm index f39feb6345e..7060037bd90 100644 --- a/code/modules/clothing/under/jobs/plasmamen/security.dm +++ b/code/modules/clothing/under/jobs/plasmamen/security.dm @@ -18,4 +18,4 @@ desc = "A plasmaman containment suit designed for the head of security." icon_state = "hos_envirosuit" item_state = "hos_envirosuit" - item_color = "hos_envirosuit" \ No newline at end of file + item_color = "hos_envirosuit" diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm index fa48aff0f4f..2602c46d01d 100644 --- a/code/modules/clothing/under/jobs/security.dm +++ b/code/modules/clothing/under/jobs/security.dm @@ -1,200 +1,200 @@ -/* - * Contains: - * Security - * Detective - * Head of Security - */ - - -/* - * Security - */ -/obj/item/clothing/under/rank/warden - desc = "It's made of a slightly sturdier material than standard jumpsuits, to allow for more robust protection. It has the word \"Warden\" written on the shoulders." - name = "warden's jumpsuit" - icon_state = "warden" - item_state = "r_suit" - item_color = "warden" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - flags_size = ONESIZEFITSALL - strip_delay = 50 - -/obj/item/clothing/under/rank/warden/skirt - desc = "Standard feminine fashion for a Warden. It is made of sturdier material than standard jumpskirts. It has the word \"Warden\" written on the shoulders." - name = "warden's jumpskirt" - icon_state = "wardenf" - item_state = "r_suit" - item_color = "wardenf" - flags_size = null - -/obj/item/clothing/under/rank/security - name = "security officer's jumpsuit" - desc = "It's made of a slightly sturdier material than standard jumpsuits, to allow for robust protection." - icon_state = "security" - item_state = "r_suit" - item_color = "secred" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - flags_size = ONESIZEFITSALL - strip_delay = 50 - -/obj/item/clothing/under/rank/security/skirt - name = "security officer's jumpskirt" - desc = "Standard feminine fashion for Security Officers. It's made of sturdier material than the standard jumpskirts." - icon_state = "secredf" - item_state = "r_suit" - item_color = "secredf" - flags_size = null - -/obj/item/clothing/under/rank/dispatch - name = "dispatcher's uniform" - desc = "A dress shirt and khakis with a security patch sewn on." - icon_state = "dispatch" - item_state = "dispatch" - item_color = "dispatch" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/security2 - name = "security officer's uniform" - desc = "It's made of a slightly sturdier material, to allow for robust protection." - icon_state = "redshirt2" - item_state = "r_suit" - item_color = "redshirt2" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/security/corp - icon_state = "sec_corporate" - item_state = "sec_corporate" - item_color = "sec_corporate" - -/obj/item/clothing/under/rank/warden/corp - icon_state = "warden_corporate" - item_state = "warden_corporate" - item_color = "warden_corporate" - -/* - * Detective - */ -/obj/item/clothing/under/det - name = "hard-worn suit" - desc = "Someone who wears this means business." - icon_state = "detective" - item_state = "det" - item_color = "detective" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - flags_size = ONESIZEFITSALL - strip_delay = 50 - - sprite_sheets = list( - "Vox" = 'icons/mob/species/vox/uniform.dmi' - ) - -/* - * Head of Security - */ -/obj/item/clothing/under/rank/head_of_security - desc = "It's a jumpsuit worn by those few with the dedication to achieve the position of \"Head of Security\". It has additional armor to protect the wearer." - name = "head of security's jumpsuit" - icon_state = "hos" - item_state = "r_suit" - item_color = "hosred" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - flags_size = ONESIZEFITSALL - strip_delay = 60 - -/obj/item/clothing/under/rank/head_of_security/skirt - desc = "It's a fashionable jumpskirt worn by those few with the dedication to achieve the position of \"Head of Security\". It has additional armor to protect the wearer." - name = "head of security's jumpskirt" - icon_state = "hosredf" - item_state = "r_suit" - item_color = "hosredf" - flags_size = null - -/obj/item/clothing/under/rank/head_of_security/corp - icon_state = "hos_corporate" - item_state = "hos_corporate" - item_color = "hos_corporate" - -//Jensen cosplay gear -/obj/item/clothing/under/rank/head_of_security/jensen - desc = "You never asked for anything that stylish." - name = "head of security's jumpsuit" - icon_state = "jensen" - item_state = "jensen" - item_color = "jensen" - flags_size = ONESIZEFITSALL - -//Paradise Station - -/obj/item/clothing/suit/armor/hos/hosnavyjacket - name = "head of security navy jacket" - icon_state = "hosdnavyjacket" - item_state = "hosdnavyjacket" - -/obj/item/clothing/suit/armor/hos/hosbluejacket - name = "head of security blue jacket" - icon_state = "hosbluejacket" - item_state = "hosbluejacket" - -/obj/item/clothing/suit/armor/hos/officernavyjacket - name = "officer's navy jacket" - icon_state = "officernavyjacket" - item_state = "officernavyjacket" - -/obj/item/clothing/suit/armor/hos/officerbluejacket - name = "officer's blue jacket" - icon_state = "officerbluejacket" - item_state = "officerbluejacket" - -//TG Station - -/obj/item/clothing/under/rank/security/formal - name = "security suit" - desc = "A formal security suit for officers complete with nanotrasen belt buckle." - icon_state = "security_formal" - item_state = "gy_suit" - item_color = "security_formal" - -/obj/item/clothing/under/rank/warden/formal - name = "warden's suit" - desc = "A formal security suit for the warden with blue desginations and '/Warden/' stiched into the shoulders." - icon_state = "warden_formal" - item_state = "gy_suit" - item_color = "warden_formal" - -/obj/item/clothing/under/rank/head_of_security/formal - name = "head of security's suit" - desc = "A security suit decorated for those few with the dedication to achieve the position of Head of Security." - icon_state = "hos_formal" - item_state = "gy_suit" - item_color = "hos_formal" - - -//Brig Physician -/obj/item/clothing/under/rank/security/brigphys - desc = "Jumpsuit for Brig Physician it has both medical and security protection." - name = "brig physician's jumpsuit" - icon_state = "brig_phys" - item_state = "brig_phys" - item_color = "brig_phys" - permeability_coefficient = 0.50 - armor = list(melee = 10, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 10, rad = 0, fire = 30, acid = 30) - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/security/brigphys/skirt - desc = "A skirted Brig Physician uniform. It has both security and medical protection." - name = "brig physician's jumpskirt" - icon_state = "brig_physf" - item_state = "brig_physf" - item_color = "brig_physf" - permeability_coefficient = 0.50 - flags_size = ONESIZEFITSALL - -//Pod Pilot -/obj/item/clothing/under/rank/security/pod_pilot - desc = "Suit for your regular pod pilot." - name = "pod pilot's jumpsuit" - icon_state = "pod_pilot" - item_state = "pod_pilot" - item_color = "pod_pilot" +/* + * Contains: + * Security + * Detective + * Head of Security + */ + + +/* + * Security + */ +/obj/item/clothing/under/rank/warden + desc = "It's made of a slightly sturdier material than standard jumpsuits, to allow for more robust protection. It has the word \"Warden\" written on the shoulders." + name = "warden's jumpsuit" + icon_state = "warden" + item_state = "r_suit" + item_color = "warden" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + flags_size = ONESIZEFITSALL + strip_delay = 50 + +/obj/item/clothing/under/rank/warden/skirt + desc = "Standard feminine fashion for a Warden. It is made of sturdier material than standard jumpskirts. It has the word \"Warden\" written on the shoulders." + name = "warden's jumpskirt" + icon_state = "wardenf" + item_state = "r_suit" + item_color = "wardenf" + flags_size = null + +/obj/item/clothing/under/rank/security + name = "security officer's jumpsuit" + desc = "It's made of a slightly sturdier material than standard jumpsuits, to allow for robust protection." + icon_state = "security" + item_state = "r_suit" + item_color = "secred" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + flags_size = ONESIZEFITSALL + strip_delay = 50 + +/obj/item/clothing/under/rank/security/skirt + name = "security officer's jumpskirt" + desc = "Standard feminine fashion for Security Officers. It's made of sturdier material than the standard jumpskirts." + icon_state = "secredf" + item_state = "r_suit" + item_color = "secredf" + flags_size = null + +/obj/item/clothing/under/rank/dispatch + name = "dispatcher's uniform" + desc = "A dress shirt and khakis with a security patch sewn on." + icon_state = "dispatch" + item_state = "dispatch" + item_color = "dispatch" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/security2 + name = "security officer's uniform" + desc = "It's made of a slightly sturdier material, to allow for robust protection." + icon_state = "redshirt2" + item_state = "r_suit" + item_color = "redshirt2" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/security/corp + icon_state = "sec_corporate" + item_state = "sec_corporate" + item_color = "sec_corporate" + +/obj/item/clothing/under/rank/warden/corp + icon_state = "warden_corporate" + item_state = "warden_corporate" + item_color = "warden_corporate" + +/* + * Detective + */ +/obj/item/clothing/under/det + name = "hard-worn suit" + desc = "Someone who wears this means business." + icon_state = "detective" + item_state = "det" + item_color = "detective" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + flags_size = ONESIZEFITSALL + strip_delay = 50 + + sprite_sheets = list( + "Vox" = 'icons/mob/species/vox/uniform.dmi' + ) + +/* + * Head of Security + */ +/obj/item/clothing/under/rank/head_of_security + desc = "It's a jumpsuit worn by those few with the dedication to achieve the position of \"Head of Security\". It has additional armor to protect the wearer." + name = "head of security's jumpsuit" + icon_state = "hos" + item_state = "r_suit" + item_color = "hosred" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + flags_size = ONESIZEFITSALL + strip_delay = 60 + +/obj/item/clothing/under/rank/head_of_security/skirt + desc = "It's a fashionable jumpskirt worn by those few with the dedication to achieve the position of \"Head of Security\". It has additional armor to protect the wearer." + name = "head of security's jumpskirt" + icon_state = "hosredf" + item_state = "r_suit" + item_color = "hosredf" + flags_size = null + +/obj/item/clothing/under/rank/head_of_security/corp + icon_state = "hos_corporate" + item_state = "hos_corporate" + item_color = "hos_corporate" + +//Jensen cosplay gear +/obj/item/clothing/under/rank/head_of_security/jensen + desc = "You never asked for anything that stylish." + name = "head of security's jumpsuit" + icon_state = "jensen" + item_state = "jensen" + item_color = "jensen" + flags_size = ONESIZEFITSALL + +//Paradise Station + +/obj/item/clothing/suit/armor/hos/hosnavyjacket + name = "head of security navy jacket" + icon_state = "hosdnavyjacket" + item_state = "hosdnavyjacket" + +/obj/item/clothing/suit/armor/hos/hosbluejacket + name = "head of security blue jacket" + icon_state = "hosbluejacket" + item_state = "hosbluejacket" + +/obj/item/clothing/suit/armor/hos/officernavyjacket + name = "officer's navy jacket" + icon_state = "officernavyjacket" + item_state = "officernavyjacket" + +/obj/item/clothing/suit/armor/hos/officerbluejacket + name = "officer's blue jacket" + icon_state = "officerbluejacket" + item_state = "officerbluejacket" + +//TG Station + +/obj/item/clothing/under/rank/security/formal + name = "security suit" + desc = "A formal security suit for officers complete with nanotrasen belt buckle." + icon_state = "security_formal" + item_state = "gy_suit" + item_color = "security_formal" + +/obj/item/clothing/under/rank/warden/formal + name = "warden's suit" + desc = "A formal security suit for the warden with blue desginations and '/Warden/' stiched into the shoulders." + icon_state = "warden_formal" + item_state = "gy_suit" + item_color = "warden_formal" + +/obj/item/clothing/under/rank/head_of_security/formal + name = "head of security's suit" + desc = "A security suit decorated for those few with the dedication to achieve the position of Head of Security." + icon_state = "hos_formal" + item_state = "gy_suit" + item_color = "hos_formal" + + +//Brig Physician +/obj/item/clothing/under/rank/security/brigphys + desc = "Jumpsuit for Brig Physician it has both medical and security protection." + name = "brig physician's jumpsuit" + icon_state = "brig_phys" + item_state = "brig_phys" + item_color = "brig_phys" + permeability_coefficient = 0.50 + armor = list(melee = 10, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 10, rad = 0, fire = 30, acid = 30) + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/security/brigphys/skirt + desc = "A skirted Brig Physician uniform. It has both security and medical protection." + name = "brig physician's jumpskirt" + icon_state = "brig_physf" + item_state = "brig_physf" + item_color = "brig_physf" + permeability_coefficient = 0.50 + flags_size = ONESIZEFITSALL + +//Pod Pilot +/obj/item/clothing/under/rank/security/pod_pilot + desc = "Suit for your regular pod pilot." + name = "pod pilot's jumpsuit" + icon_state = "pod_pilot" + item_state = "pod_pilot" + item_color = "pod_pilot" diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 5203aafdcf6..22a1d25ba22 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -1,851 +1,851 @@ -/obj/item/clothing/under/pj/red - name = "red pj's" - desc = "Sleepwear." - icon_state = "red_pyjamas" - item_color = "red_pyjamas" - item_state = "w_suit" - -/obj/item/clothing/under/pj/blue - name = "blue pj's" - desc = "Sleepwear." - icon_state = "blue_pyjamas" - item_color = "blue_pyjamas" - item_state = "w_suit" - -/obj/item/clothing/under/patriotsuit - name = "Patriotic Suit" - desc = "Motorcycle not included." - icon_state = "ek" - item_state = "ek" - item_color = "ek" - -/obj/item/clothing/under/captain_fly - name = "rogue captains uniform" - desc = "For the man who doesn't care because he's still free." - icon_state = "captain_fly" - item_state = "captain_fly" - item_color = "captain_fly" - -/obj/item/clothing/under/scratch - name = "white suit" - desc = "A white suit, suitable for an excellent host" - icon_state = "scratch" - item_state = "scratch" - item_color = "scratch" - -/obj/item/clothing/under/sl_suit - desc = "It's a very amish looking suit." - name = "amish suit" - icon_state = "sl_suit" - item_color = "sl_suit" - -/obj/item/clothing/under/waiter - name = "waiter's outfit" - desc = "It's a very smart uniform with a special pocket for tip." - icon_state = "waiter" - item_state = "waiter" - item_color = "waiter" - -/obj/item/clothing/under/rank/mailman - name = "mailman's jumpsuit" - desc = "'Special delivery!'" - icon_state = "mailman" - item_state = "b_suit" - item_color = "mailman" - -/obj/item/clothing/under/rank/vice - name = "vice officer's jumpsuit" - desc = "It's the standard issue pretty-boy outfit, as seen on Holo-Vision." - icon_state = "vice" - item_state = "gy_suit" - item_color = "vice" - -/obj/item/clothing/under/solgov - name = "Sol Federation marine uniform" - desc = "A comfortable and durable combat uniform worn by Sol Federation Marine Forces." - icon_state = "solgov" - item_state = "ro_suit" - item_color = "solgov" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - displays_id = 0 - -/obj/item/clothing/under/solgov/command - name = "Sol Federation Lieutenant's uniform" - desc = "A comfortable and durable combat uniform worn by Sol Federation Marine Forces. This one has additional insignia on its shoulders." - icon_state = "solgovc" - item_color = "solgovc" - -/obj/item/clothing/under/solgov/rep - name = "Sol Federation representative's uniform" - desc = "A formal uniform worn by the diplomatic representatives of the Sol Federation." - icon_state = "solgovr" - item_color = "solgovr" - -/obj/item/clothing/under/rank/centcom_officer - desc = "It's a jumpsuit worn by CentComm Officers." - name = "\improper CentComm officer's jumpsuit" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" - -/obj/item/clothing/under/rank/centcom_commander - desc = "It's a jumpsuit worn by CentComm's highest-tier Commanders." - name = "\improper CentComm officer's jumpsuit" - icon_state = "centcom" - item_state = "dg_suit" - item_color = "centcom" - -/obj/item/clothing/under/rank/centcom/officer - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Officer Uniform" - icon_state = "navy_gold" - item_state = "navy_gold" - item_color = "navy_gold" - displays_id = 0 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/centcom/captain - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Captain Uniform" - icon_state = "navy_gold" - item_state = "navy_gold" - item_color = "navy_gold" - displays_id = 0 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/centcom/blueshield - desc = "Gold trim on space-black cloth, this uniform bears \"Close Protection\" on the left shoulder." - name = "\improper Formal Nanotrasen Uniform" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" - displays_id = 0 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/centcom/representative - desc = "Gold trim on space-black cloth, this uniform bears \"N.S.S. Cyberiad\" on the left shoulder." - name = "\improper Formal Nanotrasen Uniform" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" - displays_id = 0 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/centcom/representative/New() - ..() - desc = "Gold trim on space-black cloth, this uniform bears [station_name()] on the left shoulder." - -/obj/item/clothing/under/rank/centcom/magistrate - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears \"N.S.S. Cyberiad\" on the left shoulder." - name = "\improper Formal Nanotrasen Uniform" - icon_state = "officer" - item_state = "g_suit" - item_color = "officer" - displays_id = 0 - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/rank/centcom/magistrate/New() - ..() - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears [station_name()] on the left shoulder." - -/obj/item/clothing/under/rank/centcom/diplomatic - desc = "A very gaudy and official looking uniform of the Nanotrasen Diplomatic Corps." - name = "\improper Nanotrasen Diplomatic Uniform" - icon_state = "presidente" - item_state = "g_suit" - item_color = "presidente" - displays_id = 0 - -/obj/item/clothing/under/rank/blueshield - name = "blueshield uniform" - desc = "A short-sleeved black uniform, paired with grey digital-camo cargo pants. Standard issue to Blueshields." - icon_state = "ert_uniform" - item_state = "bl_suit" - item_color = "ert_uniform" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - -/obj/item/clothing/under/rank/blueshield/skirt - name = "blueshield skirt" - desc = "A short, black and grey with blue markings skirted uniform. For the feminine Blueshield." - icon_state = "blueshieldf" - item_state = "blueshieldf" - item_color = "blueshieldf" - -/obj/item/clothing/under/space - name = "\improper NASA jumpsuit" - desc = "It has a NASA logo on it and is made of space-proofed materials." - icon_state = "black" - item_state = "bl_suit" - item_color = "black" - w_class = WEIGHT_CLASS_BULKY - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.02 - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS - cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | ARMS //Needs gloves and shoes with cold protection to be fully protected. - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - heat_protection = UPPER_TORSO | LOWER_TORSO|LEGS|FEET|ARMS|HANDS - max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT - resistance_flags = NONE - -/obj/item/clothing/under/acj - name = "administrative cybernetic jumpsuit" - icon_state = "syndicate" - item_state = "bl_suit" - item_color = "syndicate" - desc = "it's a cybernetically enhanced jumpsuit used for administrative duties." - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS|HEAD - armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS | HEAD - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - heat_protection = UPPER_TORSO | LOWER_TORSO|LEGS|FEET|ARMS|HANDS | HEAD - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - slowdown = -10 - siemens_coefficient = 0 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF - -/obj/item/clothing/under/johnny - name = "johnny~~ jumpsuit" - desc = "Johnny~~" - icon_state = "johnny" - item_color = "johnny" - -/obj/item/clothing/under/rainbow - name = "rainbow" - desc = "rainbow" - icon_state = "rainbow" - item_state = "rainbow" - item_color = "rainbow" - -/obj/item/clothing/under/cloud - name = "cloud" - desc = "cloud" - icon_state = "cloud" - item_color = "cloud" - -/obj/item/clothing/under/psysuit - name = "dark undersuit" - desc = "A thick, layered grey undersuit lined with power cables. Feels a little like wearing an electrical storm." - icon_state = "psysuit" - item_state = "psysuit" - item_color = "psysuit" - -/obj/item/clothing/under/gimmick/rank/captain/suit - name = "captain's suit" - desc = "A green suit and yellow necktie. Exemplifies authority." - icon_state = "green_suit" - item_state = "dg_suit" - item_color = "green_suit" - -/obj/item/clothing/under/gimmick/rank/head_of_personnel/suit - name = "head of personnel's suit" - desc = "A teal suit and yellow necktie. An authoritative yet tacky ensemble." - icon_state = "teal_suit" - item_state = "g_suit" - item_color = "teal_suit" - -/obj/item/clothing/under/suit_jacket - name = "black suit" - desc = "A black suit and red tie. Very formal." - icon_state = "black_suit" - item_state = "bl_suit" - item_color = "black_suit" - -/obj/item/clothing/under/suit_jacket/really_black - name = "executive suit" - desc = "A formal black suit and red tie, intended for the station's finest." - icon_state = "really_black_suit" - item_state = "bl_suit" - item_color = "really_black_suit" - -/obj/item/clothing/under/suit_jacket/female - name = "executive suit" - desc = "A formal trouser suit for women, intended for the station's finest." - icon_state = "black_suit_fem" - item_state = "black_suit_fem" - item_color = "black_suit_fem" - -/obj/item/clothing/under/suit_jacket/red - name = "red suit" - desc = "A red suit and blue tie. Somewhat formal." - icon_state = "red_suit" - item_state = "r_suit" - item_color = "red_suit" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/suit_jacket/navy - name = "navy suit" - desc = "A navy suit and red tie, intended for the station's finest." - icon_state = "navy_suit" - item_state = "navy_suit" - item_color = "navy_suit" - -/obj/item/clothing/under/suit_jacket/tan - name = "tan suit" - desc = "A tan suit with a yellow tie. Smart, but casual." - icon_state = "tan_suit" - item_state = "tan_suit" - item_color = "tan_suit" - -/obj/item/clothing/under/suit_jacket/burgundy - name = "burgundy suit" - desc = "A burgundy suit and black tie. Somewhat formal." - icon_state = "burgundy_suit" - item_state = "burgundy_suit" - item_color = "burgundy_suit" - -/obj/item/clothing/under/suit_jacket/charcoal - name = "charcoal suit" - desc = "A charcoal suit and blue tie. Very professional." - icon_state = "charcoal_suit" - item_state = "charcoal_suit" - item_color = "charcoal_suit" - -/obj/item/clothing/under/blackskirt - name = "black skirt" - desc = "A black skirt, very fancy!" - icon_state = "blackskirt" - item_color = "blackskirt" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - -/obj/item/clothing/under/schoolgirl - name = "schoolgirl uniform" - desc = "It's just like one of my Japanese animes!" - icon_state = "schoolgirl" - item_state = "schoolgirl" - item_color = "schoolgirl" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - -/obj/item/clothing/under/overalls - name = "laborer's overalls" - desc = "A set of durable overalls for getting the job done." - icon_state = "overalls" - item_state = "lb_suit" - item_color = "overalls" - -/obj/item/clothing/under/pirate - name = "pirate outfit" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - item_color = "pirate" - -/obj/item/clothing/under/pirate_rags - name = "pirate rags" - desc = "an old ragged set of clothing" - icon_state = "piraterags" - item_state = "piraterags" - item_color = "piraterags" - -/obj/item/clothing/under/soviet - name = "soviet uniform" - desc = "For the Motherland!" - icon_state = "soviet" - item_state = "soviet" - item_color = "soviet" - -/obj/item/clothing/under/redcoat - name = "redcoat uniform" - desc = "Looks old." - icon_state = "redcoat" - item_state = "redcoat" - item_color = "redcoat" - -/obj/item/clothing/under/kilt - name = "kilt" - desc = "Includes shoes and plaid" - icon_state = "kilt" - item_state = "kilt" - item_color = "kilt" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|FEET - -/obj/item/clothing/under/sexymime - name = "sexy mime outfit" - desc = "The only time when you DON'T enjoy looking at someone's rack." - icon_state = "sexymime" - item_state = "sexymime" - item_color = "sexymime" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/gladiator - name = "gladiator uniform" - desc = "Are you not entertained? Is that not why you are here?" - icon_state = "gladiator" - item_state = "gladiator" - item_color = "gladiator" - body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS - resistance_flags = NONE - -/obj/item/clothing/under/gladiator/ash_walker - desc = "This gladiator uniform appears to be covered in ash and fairly dated." - has_sensor = FALSE - -//dress - -/obj/item/clothing/under/dress/dress_fire - name = "flame dress" - desc = "A small black dress with blue flames print on it." - icon_state = "dress_fire" - item_color = "dress_fire" - -/obj/item/clothing/under/dress/dress_green - name = "green dress" - desc = "A simple, tight fitting green dress." - icon_state = "dress_green" - item_color = "dress_green" - -/obj/item/clothing/under/dress/dress_orange - name = "orange dress" - desc = "A fancy orange gown for those who like to show leg." - icon_state = "dress_orange" - item_color = "dress_orange" - -/obj/item/clothing/under/dress/dress_pink - name = "pink dress" - desc = "A simple, tight fitting pink dress." - icon_state = "dress_pink" - item_color = "dress_pink" - -/obj/item/clothing/under/dress/dress_yellow - name = "yellow dress" - desc = "A flirty, little yellow dress." - icon_state = "dress_yellow" - item_color = "dress_yellow" - -/obj/item/clothing/under/dress/dress_saloon - name = "saloon girl dress" - desc = "A old western inspired gown for the girl who likes to drink." - icon_state = "dress_saloon" - item_color = "dress_saloon" - -/obj/item/clothing/under/dress/dress_rd - name = "research director dress uniform" - desc = "Feminine fashion for the style concious RD." - icon_state = "dress_rd" - item_color = "dress_rd" - -/obj/item/clothing/under/dress/dress_cap - name = "captain dress uniform" - desc = "Feminine fashion for the style concious captain." - icon_state = "dress_cap" - item_color = "dress_cap" - -/obj/item/clothing/under/dress/dress_hop - name = "head of personal dress uniform" - desc = "Feminine fashion for the style concious HoP." - icon_state = "dress_hop" - item_color = "dress_hop" - -/obj/item/clothing/under/dress/dress_hr - name = "human resources director uniform" - desc = "Superior class for the nosy H.R. Director." - icon_state = "huresource" - item_color = "huresource" - -/obj/item/clothing/under/dress/plaid_blue - name = "blue plaid skirt" - desc = "A preppy blue skirt with a white blouse." - icon_state = "plaid_blue" - item_color = "plaid_blue" - -/obj/item/clothing/under/dress/plaid_red - name = "red plaid skirt" - desc = "A preppy red skirt with a white blouse." - icon_state = "plaid_red" - item_color = "plaid_red" - -/obj/item/clothing/under/dress/plaid_purple - name = "blue purple skirt" - desc = "A preppy purple skirt with a white blouse." - icon_state = "plaid_purple" - item_color = "plaid_purple" - -//wedding stuff - -/obj/item/clothing/under/wedding/bride_orange - name = "orange wedding dress" - desc = "A big and puffy orange dress." - icon_state = "bride_orange" - item_color = "bride_orange" - flags_inv = HIDESHOES - -/obj/item/clothing/under/wedding/bride_purple - name = "purple wedding dress" - desc = "A big and puffy purple dress." - icon_state = "bride_purple" - item_color = "bride_purple" - flags_inv = HIDESHOES - -/obj/item/clothing/under/wedding/bride_blue - name = "blue wedding dress" - desc = "A big and puffy blue dress." - icon_state = "bride_blue" - item_color = "bride_blue" - flags_inv = HIDESHOES - -/obj/item/clothing/under/wedding/bride_red - name = "red wedding dress" - desc = "A big and puffy red dress." - icon_state = "bride_red" - item_color = "bride_red" - flags_inv = HIDESHOES - -/obj/item/clothing/under/wedding/bride_white - name = "orange wedding dress" - desc = "A white wedding gown made from the finest silk." - icon_state = "bride_white" - item_color = "bride_white" - flags_inv = HIDESHOES - -/obj/item/clothing/under/sundress - name = "sundress" - desc = "Makes you want to frolic in a field of daisies." - icon_state = "sundress" - item_state = "sundress" - item_color = "sundress" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/captainparade - name = "captain's parade uniform" - desc = "A captain's luxury-wear, for special occasions." - icon_state = "captain_parade" - item_state = "by_suit" - item_color = "captain_parade" - -/obj/item/clothing/under/roman - name = "roman armor" - desc = "An ancient Roman armor. Made of metallic strips and leather straps." - icon_state = "roman" - item_color = "roman" - item_state = "armor" - strip_delay = 100 - resistance_flags = NONE - -/obj/item/clothing/under/maid - name = "maid costume" - desc = "Maid in China." - icon_state = "meido" - item_state = "meido" - item_color = "meido" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/janimaid - name = "maid uniform" - desc = "A simple maid uniform for housekeeping." - icon_state = "janimaid" - item_state = "janimaid" - item_color = "janimaid" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/singery - name = "yellow performer's outfit" - desc = "Just looking at this makes you want to sing." - icon_state = "ysing" - item_state = "ysing" - item_color = "ysing" - -/obj/item/clothing/under/singerb - name = "blue performer's outfit" - desc = "Just looking at this makes you want to sing." - icon_state = "bsing" - item_state = "bsing" - item_color = "bsing" - -/obj/item/clothing/under/jester - name = "jester suit" - desc = "A jolly dress, well suited to entertain your master, nuncle." - icon_state = "jester" - item_color = "jester" - -/obj/item/clothing/under/flappers - name = "flappers" - desc = "Nothing like the roarin' '20s, flapping the night away on the dance floor." - icon_state = "flapper" - item_state = "flapper" - item_color = "flapper" - -/obj/item/clothing/under/mafia - name = "mafia outfit" - desc = "The business of the mafia is business." - icon_state = "mafia" - item_state = "mafia" - item_color = "mafia" - -/obj/item/clothing/under/mafia/vest - name = "mafia vest" - desc = "Extreme problems often require extreme solutions." - icon_state = "mafiavest" - item_state = "mafiavest" - item_color = "mafiavest" - -/obj/item/clothing/under/mafia/white - name = "white mafia outfit" - desc = "The best defense against the treacherous is treachery." - icon_state = "mafiawhite" - item_state = "mafiawhite" - item_color = "mafiawhite" - -/obj/item/clothing/under/mafia/sue - name = "mafia vest" - desc = "The business is born into." - icon_state = "suevest" - item_state = "suevest" - item_color = "suevest" - -/obj/item/clothing/under/mafia/tan - name = "leather mafia outfit" - desc = "The big drum sounds good only from a distance." - icon_state = "mafiatan" - item_state = "mafiatan" - item_color = "mafiatan" - - -/obj/item/clothing/under/bane - name = "Bane Harness" - desc = "Wear this harness to become the bane of the station." - icon_state = "bane" - item_state = "bane" - item_color = "bane" - -/obj/item/clothing/under/vox - name = "Ripped Jumpsuit" - desc = "A jumpsuit that looks like it's been shredded by some talons. Who could wear this now?" - icon = 'icons/obj/clothing/species/vox/uniforms.dmi' - icon_state = "vgrey" - item_state = "vgrey" - item_color = "vgrey" - -/obj/item/clothing/under/psyjump - name = "Psychic Amp Jumpsuit" - desc = "A suit made of strange materials." - icon_state = "psyamp" - item_state = "psyamp" - item_color = "psyamp" - -/obj/item/clothing/under/rebeloutfit - name = "Rebel Outfit" - desc = "Made in Seattle, 2216." - icon_state = "colin_earle" - item_state = "colin_earle" - item_color = "colin_earle" - -/obj/item/clothing/under/officeruniform - name = "Clown Officer's Uniform" - desc = "For Clown officers, this uniform was designed by the great clown designer Hugo Boss." - icon_state = "officeruniform" - item_color = "officeruniform" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/soldieruniform - name = "Clown Soldier's Uniform" - desc = "For the basic grunt of the Clown army." - icon_state = "soldieruniform" - item_color = "soldieruniform" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/pennywise - name = "Pennywise Costume" - desc = "It's everything you ever were afraid of." - icon_state = "pennywise" - item_color = "pennywise" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/assistantformal - name = "assistant's formal uniform" - desc = "An assistant's formal-wear. Why an assistant needs formal-wear is still unknown." - icon_state = "assistant_formal" - item_state = "gy_suit" - item_color = "assistant_formal" - -/obj/item/clothing/under/blacktango - name = "black tango dress" - desc = "Filled with Latin fire." - icon_state = "black_tango" - item_state = "wcoat" - item_color = "black_tango" - -/obj/item/clothing/under/stripeddress - name = "striped dress" - desc = "Fashion in space." - icon_state = "striped_dress" - item_state = "stripeddress" - item_color = "striped_dress" - -/obj/item/clothing/under/sailordress - name = "sailor dress" - desc = "Formal wear for a leading lady." - icon_state = "sailor_dress" - item_state = "sailordress" - item_color = "sailor_dress" - -/obj/item/clothing/under/redeveninggown - name = "red evening gown" - desc = "Fancy dress for space bar singers." - icon_state = "red_evening_gown" - item_state = "redeveninggown" - item_color = "red_evening_gown" - -/obj/item/clothing/under/suit_jacket/checkered - name = "checkered suit" - desc = "That's a very nice suit you have there. Shame if something were to happen to it, eh?" - icon_state = "checkered_suit" - item_state = "checkered_suit" - item_color = "checkered_suit" - -/obj/item/clothing/under/owl - name = "owl uniform" - desc = "A soft brown jumpsuit made of synthetic feathers and strong conviction." - icon_state = "owl" - item_color = "owl" - -/obj/item/clothing/under/griffin - name = "griffon uniform" - desc = "A soft brown jumpsuit with a white feather collar made of synthetic feathers and a lust for mayhem." - icon_state = "griffin" - item_color = "griffin" - -/obj/item/clothing/under/noble_clothes - name = "noble clothes" - desc = "They fall just short of majestic." - icon_state = "noble_clothes" - item_color = "noble_clothes" - item_state = "noble_clothes" - -/obj/item/clothing/under/contortionist - name = "atmospheric technician's jumpsuit" - desc = "A light jumpsuit useful for squeezing through narrow vents." - icon_state = "atmos" - item_state = "atmos_suit" - item_color = "atmos" - resistance_flags = FIRE_PROOF - -/obj/item/clothing/under/contortionist/equipped(mob/living/carbon/human/user, slot) - if(!user.ventcrawler) - user.ventcrawler = 2 - ..() - -/obj/item/clothing/under/contortionist/dropped(mob/living/carbon/human/user) - if(!user.get_int_organ(/obj/item/organ/internal/heart/gland/ventcrawling)) - user.ventcrawler = 0 - ..() - -/obj/item/clothing/under/contortionist/proc/check_clothing(mob/user as mob) - //Allowed to wear: glasses, shoes, gloves, pockets, mask, and jumpsuit (obviously) - var/list/slot_must_be_empty = list(slot_back,slot_handcuffed,slot_legcuffed,slot_l_hand,slot_r_hand,slot_belt,slot_head,slot_wear_suit) - for(var/slot_id in slot_must_be_empty) - if(user.get_item_by_slot(slot_id)) - to_chat(user,"You can't fit inside while wearing that \the [user.get_item_by_slot(slot_id)].") - return 0 - return 1 - -/obj/item/clothing/under/cursedclown - name = "cursed clown suit" - desc = "It wasn't already?" - icon = 'icons/goonstation/objects/clothing/uniform.dmi' - icon_state = "cursedclown" - item_state = "cclown_uniform" - item_color = "cursedclown" - icon_override = 'icons/goonstation/mob/clothing/uniform.dmi' - lefthand_file = 'icons/goonstation/mob/inhands/clothing_lefthand.dmi' - righthand_file = 'icons/goonstation/mob/inhands/clothing_righthand.dmi' - flags = NODROP - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - flags_size = ONESIZEFITSALL - has_sensor = 0 // HUNKE - -/obj/item/clothing/under/victdress - name = "black victorian dress" - desc = "A victorian style dress, fancy!" - icon_state = "victorianblackdress" - item_state = "victorianblackdress" - item_color = "victorianblackdress" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/victdress/red - name = "red victorian dress" - icon_state = "victorianreddress" - item_state = "victorianreddress" - item_color = "victorianreddress" - -/obj/item/clothing/under/victsuit - name = "victorian suit" - desc = "A victorian style suit, fancy!" - icon_state = "victorianvest" - item_state = "victorianvest" - item_color = "victorianvest" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/victsuit/redblk - name = "red and black victorian suit" - icon_state = "victorianblred" - item_state = "victorianblred" - item_color = "victorianblred" - -/obj/item/clothing/under/victsuit/red - name = "red victorian suit" - icon_state = "victorianredvest" - item_state = "victorianredvest" - item_color = "victorianredvest" - -/obj/item/clothing/under/medigown - name = "medical gown" - desc = "a flimsy examination gown, the back ties never close." - icon_state = "medicalgown" - item_state = "medicalgown" - item_color = "medicalgown" - body_parts_covered = UPPER_TORSO|LOWER_TORSO - -/obj/item/clothing/under/burial - name = "burial garments" - desc = "Traditional burial garments from the early 22nd century." - icon_state = "burial" - item_state = "burial" - item_color = "burial" - -/obj/item/clothing/under/redhawaiianshirt - name = "red hawaiian shirt" - desc = "a floral shirt worn to most vacation destinations." - icon_state = "hawaiianred" - item_state = "hawaiianred" - item_color = "hawaiianred" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/pinkhawaiianshirt - name = "pink hawaiian shirt" - desc = "a pink floral shirt the material feels cool and comfy." - icon_state = "hawaiianpink" - item_state = "hawaiianpink" - item_color = "hawaiianpink" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/orangehawaiianshirt - name = "orange hawaiian shirt" - desc = "a orange floral shirt for a relaxing day in space." - icon_state = "hawaiianorange" - item_state = "hawaiianorange" - item_color = "hawaiianorange" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/bluehawaiianshirt - name = "blue hawaiian shirt" - desc = "a blue floral shirt it has a oddly colored pink flower on it." - icon_state = "hawaiianblue" - item_state = "hawaiianblue" - item_color = "hawaiianblue" - flags_size = ONESIZEFITSALL - -/obj/item/clothing/under/misc/durathread - name = "durathread jumpsuit" - desc = "A jumpsuit made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "durathread" - item_state = "durathread" - item_color = "durathread" - armor = list(melee = 10, bullet = 0, laser = 10, energy = 0, bomb = 5, bio = 0, rad = 0, fire = 0, acid = 0) - -/obj/item/clothing/under/cuban_suit - name = "rhumba outfit" - desc = "A satin shirt and high-waisted pants, worn by dancers in the Rhumba style. It smells oddly like... sulfur?" - icon_state = "cuban_suit" - item_state = "cuban_suit" - item_color = "cuban_suit" \ No newline at end of file +/obj/item/clothing/under/pj/red + name = "red pj's" + desc = "Sleepwear." + icon_state = "red_pyjamas" + item_color = "red_pyjamas" + item_state = "w_suit" + +/obj/item/clothing/under/pj/blue + name = "blue pj's" + desc = "Sleepwear." + icon_state = "blue_pyjamas" + item_color = "blue_pyjamas" + item_state = "w_suit" + +/obj/item/clothing/under/patriotsuit + name = "Patriotic Suit" + desc = "Motorcycle not included." + icon_state = "ek" + item_state = "ek" + item_color = "ek" + +/obj/item/clothing/under/captain_fly + name = "rogue captains uniform" + desc = "For the man who doesn't care because he's still free." + icon_state = "captain_fly" + item_state = "captain_fly" + item_color = "captain_fly" + +/obj/item/clothing/under/scratch + name = "white suit" + desc = "A white suit, suitable for an excellent host" + icon_state = "scratch" + item_state = "scratch" + item_color = "scratch" + +/obj/item/clothing/under/sl_suit + desc = "It's a very amish looking suit." + name = "amish suit" + icon_state = "sl_suit" + item_color = "sl_suit" + +/obj/item/clothing/under/waiter + name = "waiter's outfit" + desc = "It's a very smart uniform with a special pocket for tip." + icon_state = "waiter" + item_state = "waiter" + item_color = "waiter" + +/obj/item/clothing/under/rank/mailman + name = "mailman's jumpsuit" + desc = "'Special delivery!'" + icon_state = "mailman" + item_state = "b_suit" + item_color = "mailman" + +/obj/item/clothing/under/rank/vice + name = "vice officer's jumpsuit" + desc = "It's the standard issue pretty-boy outfit, as seen on Holo-Vision." + icon_state = "vice" + item_state = "gy_suit" + item_color = "vice" + +/obj/item/clothing/under/solgov + name = "Sol Federation marine uniform" + desc = "A comfortable and durable combat uniform worn by Sol Federation Marine Forces." + icon_state = "solgov" + item_state = "ro_suit" + item_color = "solgov" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + displays_id = 0 + +/obj/item/clothing/under/solgov/command + name = "Sol Federation Lieutenant's uniform" + desc = "A comfortable and durable combat uniform worn by Sol Federation Marine Forces. This one has additional insignia on its shoulders." + icon_state = "solgovc" + item_color = "solgovc" + +/obj/item/clothing/under/solgov/rep + name = "Sol Federation representative's uniform" + desc = "A formal uniform worn by the diplomatic representatives of the Sol Federation." + icon_state = "solgovr" + item_color = "solgovr" + +/obj/item/clothing/under/rank/centcom_officer + desc = "It's a jumpsuit worn by CentComm Officers." + name = "\improper CentComm officer's jumpsuit" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" + +/obj/item/clothing/under/rank/centcom_commander + desc = "It's a jumpsuit worn by CentComm's highest-tier Commanders." + name = "\improper CentComm officer's jumpsuit" + icon_state = "centcom" + item_state = "dg_suit" + item_color = "centcom" + +/obj/item/clothing/under/rank/centcom/officer + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." + name = "\improper Nanotrasen Naval Officer Uniform" + icon_state = "navy_gold" + item_state = "navy_gold" + item_color = "navy_gold" + displays_id = 0 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/centcom/captain + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." + name = "\improper Nanotrasen Naval Captain Uniform" + icon_state = "navy_gold" + item_state = "navy_gold" + item_color = "navy_gold" + displays_id = 0 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/centcom/blueshield + desc = "Gold trim on space-black cloth, this uniform bears \"Close Protection\" on the left shoulder." + name = "\improper Formal Nanotrasen Uniform" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" + displays_id = 0 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/centcom/representative + desc = "Gold trim on space-black cloth, this uniform bears \"N.S.S. Cyberiad\" on the left shoulder." + name = "\improper Formal Nanotrasen Uniform" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" + displays_id = 0 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/centcom/representative/New() + ..() + desc = "Gold trim on space-black cloth, this uniform bears [station_name()] on the left shoulder." + +/obj/item/clothing/under/rank/centcom/magistrate + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears \"N.S.S. Cyberiad\" on the left shoulder." + name = "\improper Formal Nanotrasen Uniform" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" + displays_id = 0 + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/rank/centcom/magistrate/New() + ..() + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears [station_name()] on the left shoulder." + +/obj/item/clothing/under/rank/centcom/diplomatic + desc = "A very gaudy and official looking uniform of the Nanotrasen Diplomatic Corps." + name = "\improper Nanotrasen Diplomatic Uniform" + icon_state = "presidente" + item_state = "g_suit" + item_color = "presidente" + displays_id = 0 + +/obj/item/clothing/under/rank/blueshield + name = "blueshield uniform" + desc = "A short-sleeved black uniform, paired with grey digital-camo cargo pants. Standard issue to Blueshields." + icon_state = "ert_uniform" + item_state = "bl_suit" + item_color = "ert_uniform" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + +/obj/item/clothing/under/rank/blueshield/skirt + name = "blueshield skirt" + desc = "A short, black and grey with blue markings skirted uniform. For the feminine Blueshield." + icon_state = "blueshieldf" + item_state = "blueshieldf" + item_color = "blueshieldf" + +/obj/item/clothing/under/space + name = "\improper NASA jumpsuit" + desc = "It has a NASA logo on it and is made of space-proofed materials." + icon_state = "black" + item_state = "bl_suit" + item_color = "black" + w_class = WEIGHT_CLASS_BULKY + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.02 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS + cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | ARMS //Needs gloves and shoes with cold protection to be fully protected. + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + heat_protection = UPPER_TORSO | LOWER_TORSO|LEGS|FEET|ARMS|HANDS + max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT + resistance_flags = NONE + +/obj/item/clothing/under/acj + name = "administrative cybernetic jumpsuit" + icon_state = "syndicate" + item_state = "bl_suit" + item_color = "syndicate" + desc = "it's a cybernetically enhanced jumpsuit used for administrative duties." + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS|HEAD + armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + cold_protection = UPPER_TORSO | LOWER_TORSO | LEGS | FEET | ARMS | HANDS | HEAD + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + heat_protection = UPPER_TORSO | LOWER_TORSO|LEGS|FEET|ARMS|HANDS | HEAD + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + slowdown = -10 + siemens_coefficient = 0 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF + +/obj/item/clothing/under/johnny + name = "johnny~~ jumpsuit" + desc = "Johnny~~" + icon_state = "johnny" + item_color = "johnny" + +/obj/item/clothing/under/rainbow + name = "rainbow" + desc = "rainbow" + icon_state = "rainbow" + item_state = "rainbow" + item_color = "rainbow" + +/obj/item/clothing/under/cloud + name = "cloud" + desc = "cloud" + icon_state = "cloud" + item_color = "cloud" + +/obj/item/clothing/under/psysuit + name = "dark undersuit" + desc = "A thick, layered grey undersuit lined with power cables. Feels a little like wearing an electrical storm." + icon_state = "psysuit" + item_state = "psysuit" + item_color = "psysuit" + +/obj/item/clothing/under/gimmick/rank/captain/suit + name = "captain's suit" + desc = "A green suit and yellow necktie. Exemplifies authority." + icon_state = "green_suit" + item_state = "dg_suit" + item_color = "green_suit" + +/obj/item/clothing/under/gimmick/rank/head_of_personnel/suit + name = "head of personnel's suit" + desc = "A teal suit and yellow necktie. An authoritative yet tacky ensemble." + icon_state = "teal_suit" + item_state = "g_suit" + item_color = "teal_suit" + +/obj/item/clothing/under/suit_jacket + name = "black suit" + desc = "A black suit and red tie. Very formal." + icon_state = "black_suit" + item_state = "bl_suit" + item_color = "black_suit" + +/obj/item/clothing/under/suit_jacket/really_black + name = "executive suit" + desc = "A formal black suit and red tie, intended for the station's finest." + icon_state = "really_black_suit" + item_state = "bl_suit" + item_color = "really_black_suit" + +/obj/item/clothing/under/suit_jacket/female + name = "executive suit" + desc = "A formal trouser suit for women, intended for the station's finest." + icon_state = "black_suit_fem" + item_state = "black_suit_fem" + item_color = "black_suit_fem" + +/obj/item/clothing/under/suit_jacket/red + name = "red suit" + desc = "A red suit and blue tie. Somewhat formal." + icon_state = "red_suit" + item_state = "r_suit" + item_color = "red_suit" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/suit_jacket/navy + name = "navy suit" + desc = "A navy suit and red tie, intended for the station's finest." + icon_state = "navy_suit" + item_state = "navy_suit" + item_color = "navy_suit" + +/obj/item/clothing/under/suit_jacket/tan + name = "tan suit" + desc = "A tan suit with a yellow tie. Smart, but casual." + icon_state = "tan_suit" + item_state = "tan_suit" + item_color = "tan_suit" + +/obj/item/clothing/under/suit_jacket/burgundy + name = "burgundy suit" + desc = "A burgundy suit and black tie. Somewhat formal." + icon_state = "burgundy_suit" + item_state = "burgundy_suit" + item_color = "burgundy_suit" + +/obj/item/clothing/under/suit_jacket/charcoal + name = "charcoal suit" + desc = "A charcoal suit and blue tie. Very professional." + icon_state = "charcoal_suit" + item_state = "charcoal_suit" + item_color = "charcoal_suit" + +/obj/item/clothing/under/blackskirt + name = "black skirt" + desc = "A black skirt, very fancy!" + icon_state = "blackskirt" + item_color = "blackskirt" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/under/schoolgirl + name = "schoolgirl uniform" + desc = "It's just like one of my Japanese animes!" + icon_state = "schoolgirl" + item_state = "schoolgirl" + item_color = "schoolgirl" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/under/overalls + name = "laborer's overalls" + desc = "A set of durable overalls for getting the job done." + icon_state = "overalls" + item_state = "lb_suit" + item_color = "overalls" + +/obj/item/clothing/under/pirate + name = "pirate outfit" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + item_color = "pirate" + +/obj/item/clothing/under/pirate_rags + name = "pirate rags" + desc = "an old ragged set of clothing" + icon_state = "piraterags" + item_state = "piraterags" + item_color = "piraterags" + +/obj/item/clothing/under/soviet + name = "soviet uniform" + desc = "For the Motherland!" + icon_state = "soviet" + item_state = "soviet" + item_color = "soviet" + +/obj/item/clothing/under/redcoat + name = "redcoat uniform" + desc = "Looks old." + icon_state = "redcoat" + item_state = "redcoat" + item_color = "redcoat" + +/obj/item/clothing/under/kilt + name = "kilt" + desc = "Includes shoes and plaid" + icon_state = "kilt" + item_state = "kilt" + item_color = "kilt" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|FEET + +/obj/item/clothing/under/sexymime + name = "sexy mime outfit" + desc = "The only time when you DON'T enjoy looking at someone's rack." + icon_state = "sexymime" + item_state = "sexymime" + item_color = "sexymime" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/gladiator + name = "gladiator uniform" + desc = "Are you not entertained? Is that not why you are here?" + icon_state = "gladiator" + item_state = "gladiator" + item_color = "gladiator" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + resistance_flags = NONE + +/obj/item/clothing/under/gladiator/ash_walker + desc = "This gladiator uniform appears to be covered in ash and fairly dated." + has_sensor = FALSE + +//dress + +/obj/item/clothing/under/dress/dress_fire + name = "flame dress" + desc = "A small black dress with blue flames print on it." + icon_state = "dress_fire" + item_color = "dress_fire" + +/obj/item/clothing/under/dress/dress_green + name = "green dress" + desc = "A simple, tight fitting green dress." + icon_state = "dress_green" + item_color = "dress_green" + +/obj/item/clothing/under/dress/dress_orange + name = "orange dress" + desc = "A fancy orange gown for those who like to show leg." + icon_state = "dress_orange" + item_color = "dress_orange" + +/obj/item/clothing/under/dress/dress_pink + name = "pink dress" + desc = "A simple, tight fitting pink dress." + icon_state = "dress_pink" + item_color = "dress_pink" + +/obj/item/clothing/under/dress/dress_yellow + name = "yellow dress" + desc = "A flirty, little yellow dress." + icon_state = "dress_yellow" + item_color = "dress_yellow" + +/obj/item/clothing/under/dress/dress_saloon + name = "saloon girl dress" + desc = "A old western inspired gown for the girl who likes to drink." + icon_state = "dress_saloon" + item_color = "dress_saloon" + +/obj/item/clothing/under/dress/dress_rd + name = "research director dress uniform" + desc = "Feminine fashion for the style concious RD." + icon_state = "dress_rd" + item_color = "dress_rd" + +/obj/item/clothing/under/dress/dress_cap + name = "captain dress uniform" + desc = "Feminine fashion for the style concious captain." + icon_state = "dress_cap" + item_color = "dress_cap" + +/obj/item/clothing/under/dress/dress_hop + name = "head of personal dress uniform" + desc = "Feminine fashion for the style concious HoP." + icon_state = "dress_hop" + item_color = "dress_hop" + +/obj/item/clothing/under/dress/dress_hr + name = "human resources director uniform" + desc = "Superior class for the nosy H.R. Director." + icon_state = "huresource" + item_color = "huresource" + +/obj/item/clothing/under/dress/plaid_blue + name = "blue plaid skirt" + desc = "A preppy blue skirt with a white blouse." + icon_state = "plaid_blue" + item_color = "plaid_blue" + +/obj/item/clothing/under/dress/plaid_red + name = "red plaid skirt" + desc = "A preppy red skirt with a white blouse." + icon_state = "plaid_red" + item_color = "plaid_red" + +/obj/item/clothing/under/dress/plaid_purple + name = "blue purple skirt" + desc = "A preppy purple skirt with a white blouse." + icon_state = "plaid_purple" + item_color = "plaid_purple" + +//wedding stuff + +/obj/item/clothing/under/wedding/bride_orange + name = "orange wedding dress" + desc = "A big and puffy orange dress." + icon_state = "bride_orange" + item_color = "bride_orange" + flags_inv = HIDESHOES + +/obj/item/clothing/under/wedding/bride_purple + name = "purple wedding dress" + desc = "A big and puffy purple dress." + icon_state = "bride_purple" + item_color = "bride_purple" + flags_inv = HIDESHOES + +/obj/item/clothing/under/wedding/bride_blue + name = "blue wedding dress" + desc = "A big and puffy blue dress." + icon_state = "bride_blue" + item_color = "bride_blue" + flags_inv = HIDESHOES + +/obj/item/clothing/under/wedding/bride_red + name = "red wedding dress" + desc = "A big and puffy red dress." + icon_state = "bride_red" + item_color = "bride_red" + flags_inv = HIDESHOES + +/obj/item/clothing/under/wedding/bride_white + name = "orange wedding dress" + desc = "A white wedding gown made from the finest silk." + icon_state = "bride_white" + item_color = "bride_white" + flags_inv = HIDESHOES + +/obj/item/clothing/under/sundress + name = "sundress" + desc = "Makes you want to frolic in a field of daisies." + icon_state = "sundress" + item_state = "sundress" + item_color = "sundress" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/captainparade + name = "captain's parade uniform" + desc = "A captain's luxury-wear, for special occasions." + icon_state = "captain_parade" + item_state = "by_suit" + item_color = "captain_parade" + +/obj/item/clothing/under/roman + name = "roman armor" + desc = "An ancient Roman armor. Made of metallic strips and leather straps." + icon_state = "roman" + item_color = "roman" + item_state = "armor" + strip_delay = 100 + resistance_flags = NONE + +/obj/item/clothing/under/maid + name = "maid costume" + desc = "Maid in China." + icon_state = "meido" + item_state = "meido" + item_color = "meido" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/janimaid + name = "maid uniform" + desc = "A simple maid uniform for housekeeping." + icon_state = "janimaid" + item_state = "janimaid" + item_color = "janimaid" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/singery + name = "yellow performer's outfit" + desc = "Just looking at this makes you want to sing." + icon_state = "ysing" + item_state = "ysing" + item_color = "ysing" + +/obj/item/clothing/under/singerb + name = "blue performer's outfit" + desc = "Just looking at this makes you want to sing." + icon_state = "bsing" + item_state = "bsing" + item_color = "bsing" + +/obj/item/clothing/under/jester + name = "jester suit" + desc = "A jolly dress, well suited to entertain your master, nuncle." + icon_state = "jester" + item_color = "jester" + +/obj/item/clothing/under/flappers + name = "flappers" + desc = "Nothing like the roarin' '20s, flapping the night away on the dance floor." + icon_state = "flapper" + item_state = "flapper" + item_color = "flapper" + +/obj/item/clothing/under/mafia + name = "mafia outfit" + desc = "The business of the mafia is business." + icon_state = "mafia" + item_state = "mafia" + item_color = "mafia" + +/obj/item/clothing/under/mafia/vest + name = "mafia vest" + desc = "Extreme problems often require extreme solutions." + icon_state = "mafiavest" + item_state = "mafiavest" + item_color = "mafiavest" + +/obj/item/clothing/under/mafia/white + name = "white mafia outfit" + desc = "The best defense against the treacherous is treachery." + icon_state = "mafiawhite" + item_state = "mafiawhite" + item_color = "mafiawhite" + +/obj/item/clothing/under/mafia/sue + name = "mafia vest" + desc = "The business is born into." + icon_state = "suevest" + item_state = "suevest" + item_color = "suevest" + +/obj/item/clothing/under/mafia/tan + name = "leather mafia outfit" + desc = "The big drum sounds good only from a distance." + icon_state = "mafiatan" + item_state = "mafiatan" + item_color = "mafiatan" + + +/obj/item/clothing/under/bane + name = "Bane Harness" + desc = "Wear this harness to become the bane of the station." + icon_state = "bane" + item_state = "bane" + item_color = "bane" + +/obj/item/clothing/under/vox + name = "Ripped Jumpsuit" + desc = "A jumpsuit that looks like it's been shredded by some talons. Who could wear this now?" + icon = 'icons/obj/clothing/species/vox/uniforms.dmi' + icon_state = "vgrey" + item_state = "vgrey" + item_color = "vgrey" + +/obj/item/clothing/under/psyjump + name = "Psychic Amp Jumpsuit" + desc = "A suit made of strange materials." + icon_state = "psyamp" + item_state = "psyamp" + item_color = "psyamp" + +/obj/item/clothing/under/rebeloutfit + name = "Rebel Outfit" + desc = "Made in Seattle, 2216." + icon_state = "colin_earle" + item_state = "colin_earle" + item_color = "colin_earle" + +/obj/item/clothing/under/officeruniform + name = "Clown Officer's Uniform" + desc = "For Clown officers, this uniform was designed by the great clown designer Hugo Boss." + icon_state = "officeruniform" + item_color = "officeruniform" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/soldieruniform + name = "Clown Soldier's Uniform" + desc = "For the basic grunt of the Clown army." + icon_state = "soldieruniform" + item_color = "soldieruniform" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/pennywise + name = "Pennywise Costume" + desc = "It's everything you ever were afraid of." + icon_state = "pennywise" + item_color = "pennywise" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/assistantformal + name = "assistant's formal uniform" + desc = "An assistant's formal-wear. Why an assistant needs formal-wear is still unknown." + icon_state = "assistant_formal" + item_state = "gy_suit" + item_color = "assistant_formal" + +/obj/item/clothing/under/blacktango + name = "black tango dress" + desc = "Filled with Latin fire." + icon_state = "black_tango" + item_state = "wcoat" + item_color = "black_tango" + +/obj/item/clothing/under/stripeddress + name = "striped dress" + desc = "Fashion in space." + icon_state = "striped_dress" + item_state = "stripeddress" + item_color = "striped_dress" + +/obj/item/clothing/under/sailordress + name = "sailor dress" + desc = "Formal wear for a leading lady." + icon_state = "sailor_dress" + item_state = "sailordress" + item_color = "sailor_dress" + +/obj/item/clothing/under/redeveninggown + name = "red evening gown" + desc = "Fancy dress for space bar singers." + icon_state = "red_evening_gown" + item_state = "redeveninggown" + item_color = "red_evening_gown" + +/obj/item/clothing/under/suit_jacket/checkered + name = "checkered suit" + desc = "That's a very nice suit you have there. Shame if something were to happen to it, eh?" + icon_state = "checkered_suit" + item_state = "checkered_suit" + item_color = "checkered_suit" + +/obj/item/clothing/under/owl + name = "owl uniform" + desc = "A soft brown jumpsuit made of synthetic feathers and strong conviction." + icon_state = "owl" + item_color = "owl" + +/obj/item/clothing/under/griffin + name = "griffon uniform" + desc = "A soft brown jumpsuit with a white feather collar made of synthetic feathers and a lust for mayhem." + icon_state = "griffin" + item_color = "griffin" + +/obj/item/clothing/under/noble_clothes + name = "noble clothes" + desc = "They fall just short of majestic." + icon_state = "noble_clothes" + item_color = "noble_clothes" + item_state = "noble_clothes" + +/obj/item/clothing/under/contortionist + name = "atmospheric technician's jumpsuit" + desc = "A light jumpsuit useful for squeezing through narrow vents." + icon_state = "atmos" + item_state = "atmos_suit" + item_color = "atmos" + resistance_flags = FIRE_PROOF + +/obj/item/clothing/under/contortionist/equipped(mob/living/carbon/human/user, slot) + if(!user.ventcrawler) + user.ventcrawler = 2 + ..() + +/obj/item/clothing/under/contortionist/dropped(mob/living/carbon/human/user) + if(!user.get_int_organ(/obj/item/organ/internal/heart/gland/ventcrawling)) + user.ventcrawler = 0 + ..() + +/obj/item/clothing/under/contortionist/proc/check_clothing(mob/user as mob) + //Allowed to wear: glasses, shoes, gloves, pockets, mask, and jumpsuit (obviously) + var/list/slot_must_be_empty = list(slot_back,slot_handcuffed,slot_legcuffed,slot_l_hand,slot_r_hand,slot_belt,slot_head,slot_wear_suit) + for(var/slot_id in slot_must_be_empty) + if(user.get_item_by_slot(slot_id)) + to_chat(user,"You can't fit inside while wearing that \the [user.get_item_by_slot(slot_id)].") + return 0 + return 1 + +/obj/item/clothing/under/cursedclown + name = "cursed clown suit" + desc = "It wasn't already?" + icon = 'icons/goonstation/objects/clothing/uniform.dmi' + icon_state = "cursedclown" + item_state = "cclown_uniform" + item_color = "cursedclown" + icon_override = 'icons/goonstation/mob/clothing/uniform.dmi' + lefthand_file = 'icons/goonstation/mob/inhands/clothing_lefthand.dmi' + righthand_file = 'icons/goonstation/mob/inhands/clothing_righthand.dmi' + flags = NODROP + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + flags_size = ONESIZEFITSALL + has_sensor = 0 // HUNKE + +/obj/item/clothing/under/victdress + name = "black victorian dress" + desc = "A victorian style dress, fancy!" + icon_state = "victorianblackdress" + item_state = "victorianblackdress" + item_color = "victorianblackdress" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/victdress/red + name = "red victorian dress" + icon_state = "victorianreddress" + item_state = "victorianreddress" + item_color = "victorianreddress" + +/obj/item/clothing/under/victsuit + name = "victorian suit" + desc = "A victorian style suit, fancy!" + icon_state = "victorianvest" + item_state = "victorianvest" + item_color = "victorianvest" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/victsuit/redblk + name = "red and black victorian suit" + icon_state = "victorianblred" + item_state = "victorianblred" + item_color = "victorianblred" + +/obj/item/clothing/under/victsuit/red + name = "red victorian suit" + icon_state = "victorianredvest" + item_state = "victorianredvest" + item_color = "victorianredvest" + +/obj/item/clothing/under/medigown + name = "medical gown" + desc = "a flimsy examination gown, the back ties never close." + icon_state = "medicalgown" + item_state = "medicalgown" + item_color = "medicalgown" + body_parts_covered = UPPER_TORSO|LOWER_TORSO + +/obj/item/clothing/under/burial + name = "burial garments" + desc = "Traditional burial garments from the early 22nd century." + icon_state = "burial" + item_state = "burial" + item_color = "burial" + +/obj/item/clothing/under/redhawaiianshirt + name = "red hawaiian shirt" + desc = "a floral shirt worn to most vacation destinations." + icon_state = "hawaiianred" + item_state = "hawaiianred" + item_color = "hawaiianred" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/pinkhawaiianshirt + name = "pink hawaiian shirt" + desc = "a pink floral shirt the material feels cool and comfy." + icon_state = "hawaiianpink" + item_state = "hawaiianpink" + item_color = "hawaiianpink" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/orangehawaiianshirt + name = "orange hawaiian shirt" + desc = "a orange floral shirt for a relaxing day in space." + icon_state = "hawaiianorange" + item_state = "hawaiianorange" + item_color = "hawaiianorange" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/bluehawaiianshirt + name = "blue hawaiian shirt" + desc = "a blue floral shirt it has a oddly colored pink flower on it." + icon_state = "hawaiianblue" + item_state = "hawaiianblue" + item_color = "hawaiianblue" + flags_size = ONESIZEFITSALL + +/obj/item/clothing/under/misc/durathread + name = "durathread jumpsuit" + desc = "A jumpsuit made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "durathread" + item_state = "durathread" + item_color = "durathread" + armor = list(melee = 10, bullet = 0, laser = 10, energy = 0, bomb = 5, bio = 0, rad = 0, fire = 0, acid = 0) + +/obj/item/clothing/under/cuban_suit + name = "rhumba outfit" + desc = "A satin shirt and high-waisted pants, worn by dancers in the Rhumba style. It smells oddly like... sulfur?" + icon_state = "cuban_suit" + item_state = "cuban_suit" + item_color = "cuban_suit" diff --git a/code/modules/clothing/under/pants.dm b/code/modules/clothing/under/pants.dm index b52c1eceb7c..735c681ca5f 100644 --- a/code/modules/clothing/under/pants.dm +++ b/code/modules/clothing/under/pants.dm @@ -89,4 +89,4 @@ name = "camo pants" desc = "A pair of woodland camouflage pants. Probably not the best choice for a space station." icon_state = "camopants" - item_color = "camopants" \ No newline at end of file + item_color = "camopants" diff --git a/code/modules/clothing/under/shorts.dm b/code/modules/clothing/under/shorts.dm index 13008c4ff6d..3cd2ddef84c 100644 --- a/code/modules/clothing/under/shorts.dm +++ b/code/modules/clothing/under/shorts.dm @@ -1,26 +1,26 @@ -/obj/item/clothing/under/shorts - name = "athletic shorts" - desc = "95% Polyester, 5% Spandex!" - gender = PLURAL - body_parts_covered = LOWER_TORSO - displays_id = 0 - -/obj/item/clothing/under/shorts/red - icon_state = "redshorts" - item_color = "redshorts" - -/obj/item/clothing/under/shorts/green - icon_state = "greenshorts" - item_color = "greenshorts" - -/obj/item/clothing/under/shorts/blue - icon_state = "blueshorts" - item_color = "blueshorts" - -/obj/item/clothing/under/shorts/black - icon_state = "blackshorts" - item_color = "blackshorts" - -/obj/item/clothing/under/shorts/grey - icon_state = "greyshorts" - item_color = "greyshorts" \ No newline at end of file +/obj/item/clothing/under/shorts + name = "athletic shorts" + desc = "95% Polyester, 5% Spandex!" + gender = PLURAL + body_parts_covered = LOWER_TORSO + displays_id = 0 + +/obj/item/clothing/under/shorts/red + icon_state = "redshorts" + item_color = "redshorts" + +/obj/item/clothing/under/shorts/green + icon_state = "greenshorts" + item_color = "greenshorts" + +/obj/item/clothing/under/shorts/blue + icon_state = "blueshorts" + item_color = "blueshorts" + +/obj/item/clothing/under/shorts/black + icon_state = "blackshorts" + item_color = "blackshorts" + +/obj/item/clothing/under/shorts/grey + icon_state = "greyshorts" + item_color = "greyshorts" diff --git a/code/modules/clothing/under/syndicate.dm b/code/modules/clothing/under/syndicate.dm index 6778138d30f..441cf8d9420 100644 --- a/code/modules/clothing/under/syndicate.dm +++ b/code/modules/clothing/under/syndicate.dm @@ -1,26 +1,26 @@ -/obj/item/clothing/under/syndicate - name = "tactical turtleneck" - desc = "A non-descript and slightly suspicious looking turtleneck with digital camouflage cargo pants." - icon_state = "syndicate" - item_state = "bl_suit" - item_color = "syndicate" - has_sensor = 0 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - -/obj/item/clothing/under/syndicate/combat - name = "combat turtleneck" - -/obj/item/clothing/under/syndicate/tacticool - name = "tacticool turtleneck" - desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." - icon_state = "tactifool" - item_state = "bl_suit" - item_color = "tactifool" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - -/obj/item/clothing/under/syndicate/sniper - name = "Tactical turtleneck suit" - desc = "A double seamed tactical turtleneck disguised as a civilian grade silk suit. Intended for the most formal operator. The collar is really sharp." - icon_state = "really_black_suit" - item_state = "bl_suit" - item_color = "black_suit" \ No newline at end of file +/obj/item/clothing/under/syndicate + name = "tactical turtleneck" + desc = "A non-descript and slightly suspicious looking turtleneck with digital camouflage cargo pants." + icon_state = "syndicate" + item_state = "bl_suit" + item_color = "syndicate" + has_sensor = 0 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + +/obj/item/clothing/under/syndicate/combat + name = "combat turtleneck" + +/obj/item/clothing/under/syndicate/tacticool + name = "tacticool turtleneck" + desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-." + icon_state = "tactifool" + item_state = "bl_suit" + item_color = "tactifool" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + +/obj/item/clothing/under/syndicate/sniper + name = "Tactical turtleneck suit" + desc = "A double seamed tactical turtleneck disguised as a civilian grade silk suit. Intended for the most formal operator. The collar is really sharp." + icon_state = "really_black_suit" + item_state = "bl_suit" + item_color = "black_suit" diff --git a/code/modules/crafting/craft.dm b/code/modules/crafting/craft.dm index 36f0da802cc..40a8fe5f893 100644 --- a/code/modules/crafting/craft.dm +++ b/code/modules/crafting/craft.dm @@ -458,4 +458,4 @@ /datum/mind/proc/teach_crafting_recipe(R) if(!learned_recipes) learned_recipes = list() - learned_recipes |= R \ No newline at end of file + learned_recipes |= R diff --git a/code/modules/economy/Economy.dm b/code/modules/economy/Economy.dm index 57d76e98c5c..eca0e08e359 100644 --- a/code/modules/economy/Economy.dm +++ b/code/modules/economy/Economy.dm @@ -94,4 +94,4 @@ var/setup_economy = 0 weighted_randomevent_locations[D] = D.viable_random_events.len weighted_mundaneevent_locations[D] = D.viable_mundane_events.len - setup_economy = 1 \ No newline at end of file + setup_economy = 1 diff --git a/code/modules/economy/Job_Departments.dm b/code/modules/economy/Job_Departments.dm index 0b851136147..77a938486dd 100644 --- a/code/modules/economy/Job_Departments.dm +++ b/code/modules/economy/Job_Departments.dm @@ -67,4 +67,4 @@ var/list/station_departments = list("Command", "Medical", "Engineering", "Scienc /datum/job/detective/department = "Security" -/datum/job/officer/department = "Security" \ No newline at end of file +/datum/job/officer/department = "Security" diff --git a/code/modules/economy/POS.dm b/code/modules/economy/POS.dm index 201c7847875..cbf708f64ea 100644 --- a/code/modules/economy/POS.dm +++ b/code/modules/economy/POS.dm @@ -534,4 +534,4 @@ var/global/pos_sales = 0 new /obj/item/stack/spacecash(loc, credits_held) credits_held=0 return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/events/abductor.dm b/code/modules/events/abductor.dm index 3c7dcc6496b..7fe60e8b77f 100644 --- a/code/modules/events/abductor.dm +++ b/code/modules/events/abductor.dm @@ -49,4 +49,4 @@ processing = 1 //So it will get gc'd return 1 else - return 0 \ No newline at end of file + return 0 diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm index dd4eb3b6923..8562d84f3e2 100644 --- a/code/modules/events/alien_infestation.dm +++ b/code/modules/events/alien_infestation.dm @@ -1,40 +1,40 @@ -/datum/event/alien_infestation - announceWhen = 400 - var/highpop_trigger = 80 - var/spawncount = 2 - var/list/playercount - var/successSpawn = 0 //So we don't make a command report if nothing gets spawned. - -/datum/event/alien_infestation/setup() - announceWhen = rand(announceWhen, announceWhen + 50) - -/datum/event/alien_infestation/announce() - if(successSpawn) - event_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg') - -/datum/event/alien_infestation/start() - var/list/vents = list() - playercount = length(GLOB.clients)//grab playercount when event starts not when game starts - if(playercount >= highpop_trigger) //spawn with 4 if highpop - spawncount = 4 - for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in world) - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - if(temp_vent.parent.other_atmosmch.len > 50) //Stops Aliens getting stuck in small networks. See: Security, Virology - vents += temp_vent - - spawn() - var/list/candidates = pollCandidates("Do you want to play as an alien?", ROLE_ALIEN, 1) - - while(spawncount > 0 && vents.len && candidates.len) - var/obj/vent = pick_n_take(vents) - var/mob/C = pick_n_take(candidates) - if(C) - GLOB.respawnable_list -= C.client - var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) - new_xeno.amount_grown += (0.75 * new_xeno.max_grown) //event spawned larva start off almost ready to evolve. - new_xeno.key = C.key - if(SSticker && SSticker.mode) - SSticker.mode.xenos += new_xeno.mind - - spawncount-- - successSpawn = 1 +/datum/event/alien_infestation + announceWhen = 400 + var/highpop_trigger = 80 + var/spawncount = 2 + var/list/playercount + var/successSpawn = 0 //So we don't make a command report if nothing gets spawned. + +/datum/event/alien_infestation/setup() + announceWhen = rand(announceWhen, announceWhen + 50) + +/datum/event/alien_infestation/announce() + if(successSpawn) + event_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg') + +/datum/event/alien_infestation/start() + var/list/vents = list() + playercount = length(GLOB.clients)//grab playercount when event starts not when game starts + if(playercount >= highpop_trigger) //spawn with 4 if highpop + spawncount = 4 + for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in world) + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + if(temp_vent.parent.other_atmosmch.len > 50) //Stops Aliens getting stuck in small networks. See: Security, Virology + vents += temp_vent + + spawn() + var/list/candidates = pollCandidates("Do you want to play as an alien?", ROLE_ALIEN, 1) + + while(spawncount > 0 && vents.len && candidates.len) + var/obj/vent = pick_n_take(vents) + var/mob/C = pick_n_take(candidates) + if(C) + GLOB.respawnable_list -= C.client + var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) + new_xeno.amount_grown += (0.75 * new_xeno.max_grown) //event spawned larva start off almost ready to evolve. + new_xeno.key = C.key + if(SSticker && SSticker.mode) + SSticker.mode.xenos += new_xeno.mind + + spawncount-- + successSpawn = 1 diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index 0349d09d0b8..0ce58073e04 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -30,4 +30,4 @@ /datum/event/anomaly/end() if(newAnomaly)//Kill the anomaly if it still exists at the end. - qdel(newAnomaly) \ No newline at end of file + qdel(newAnomaly) diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm index e5cebbff40d..d8bcbacd143 100644 --- a/code/modules/events/anomaly_flux.dm +++ b/code/modules/events/anomaly_flux.dm @@ -14,4 +14,4 @@ /datum/event/anomaly/anomaly_flux/end() if(newAnomaly.loc)//If it hasn't been neutralized, it's time to blow up. explosion(newAnomaly, -1, 3, 5, 5) - qdel(newAnomaly) \ No newline at end of file + qdel(newAnomaly) diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm index 19fe7a41525..403fb8560f9 100644 --- a/code/modules/events/anomaly_grav.dm +++ b/code/modules/events/anomaly_grav.dm @@ -9,4 +9,4 @@ /datum/event/anomaly/anomaly_grav/start() var/turf/T = pick(get_area_turfs(impact_area)) if(T) - newAnomaly = new /obj/effect/anomaly/grav(T.loc) \ No newline at end of file + newAnomaly = new /obj/effect/anomaly/grav(T.loc) diff --git a/code/modules/events/anomaly_pyro.dm b/code/modules/events/anomaly_pyro.dm index f281c549c97..a486b286b98 100644 --- a/code/modules/events/anomaly_pyro.dm +++ b/code/modules/events/anomaly_pyro.dm @@ -28,4 +28,4 @@ S.rabid = TRUE S.amount_grown = SLIME_EVOLUTION_THRESHOLD S.Evolve() - qdel(newAnomaly) \ No newline at end of file + qdel(newAnomaly) diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm index 662669509cb..16cfce8801f 100644 --- a/code/modules/events/anomaly_vortex.dm +++ b/code/modules/events/anomaly_vortex.dm @@ -9,4 +9,4 @@ /datum/event/anomaly/anomaly_vortex/start() var/turf/T = pick(get_area_turfs(impact_area)) if(T) - newAnomaly = new /obj/effect/anomaly/bhole(T.loc) \ No newline at end of file + newAnomaly = new /obj/effect/anomaly/bhole(T.loc) diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm index 7a998f06dcb..5ea02e50e2f 100644 --- a/code/modules/events/blob.dm +++ b/code/modules/events/blob.dm @@ -1,33 +1,33 @@ -/datum/event/blob - announceWhen = 180 - endWhen = 240 - -/datum/event/blob/announce() - event_announcement.Announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/AI/outbreak5.ogg') - -/datum/event/blob/start() - processing = FALSE //so it won't fire again in next tick - - var/turf/T = pick(blobstart) - if(!T) - return kill() - - var/list/candidates = pollCandidates("Do you want to play as a blob infested mouse?", ROLE_BLOB, 1) - if(!candidates.len) - return kill() - - var/list/vents = list() - for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in GLOB.all_vent_pumps) - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - if(temp_vent.parent.other_atmosmch.len > 50) - vents += temp_vent - - var/obj/vent = pick(vents) - var/mob/living/simple_animal/mouse/blobinfected/B = new(vent.loc) - var/mob/M = pick(candidates) - B.key = M.key - SSticker.mode.update_blob_icons_added(B.mind) - - to_chat(B, "You are now a mouse, infected with blob spores. Find somewhere isolated... before you burst and become the blob! Use ventcrawl (alt-click on vents) to move around.") - notify_ghosts("Infected Mouse has appeared in [get_area(B)].", source = B) - processing = TRUE // Let it naturally end, if it runs successfully \ No newline at end of file +/datum/event/blob + announceWhen = 180 + endWhen = 240 + +/datum/event/blob/announce() + event_announcement.Announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/AI/outbreak5.ogg') + +/datum/event/blob/start() + processing = FALSE //so it won't fire again in next tick + + var/turf/T = pick(blobstart) + if(!T) + return kill() + + var/list/candidates = pollCandidates("Do you want to play as a blob infested mouse?", ROLE_BLOB, 1) + if(!candidates.len) + return kill() + + var/list/vents = list() + for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in GLOB.all_vent_pumps) + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + if(temp_vent.parent.other_atmosmch.len > 50) + vents += temp_vent + + var/obj/vent = pick(vents) + var/mob/living/simple_animal/mouse/blobinfected/B = new(vent.loc) + var/mob/M = pick(candidates) + B.key = M.key + SSticker.mode.update_blob_icons_added(B.mind) + + to_chat(B, "You are now a mouse, infected with blob spores. Find somewhere isolated... before you burst and become the blob! Use ventcrawl (alt-click on vents) to move around.") + notify_ghosts("Infected Mouse has appeared in [get_area(B)].", source = B) + processing = TRUE // Let it naturally end, if it runs successfully diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index 2179cf411b4..f72ed5b53d4 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -1,66 +1,66 @@ -/datum/event/brand_intelligence - announceWhen = 21 - endWhen = 1000 //Ends when all vending machines are subverted anyway. - - var/list/obj/machinery/vending/vendingMachines = list() - var/list/obj/machinery/vending/infectedMachines = list() - var/obj/machinery/vending/originMachine - var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \ - "You should buy products to feed your lifestyle obession!", \ - "Consume!", \ - "Your money can buy happiness!", \ - "Engage direct marketing!", \ - "Advertising is legalized lying! But don't let that put you off our great deals!", \ - "You don't want to buy anything? Yeah, well I didn't want to buy your mom either.") - -/datum/event/brand_intelligence/announce() - event_announcement.Announce("Rampant brand intelligence has been detected aboard [station_name()], please stand-by. The origin is believed to be \a [originMachine.name].", "Machine Learning Alert") - -/datum/event/brand_intelligence/start() - for(var/obj/machinery/vending/V in GLOB.machines) - if(!is_station_level(V.z)) continue - vendingMachines.Add(V) - - if(!vendingMachines.len) - kill() - return - - originMachine = pick(vendingMachines) - vendingMachines.Remove(originMachine) - originMachine.shut_up = 0 - originMachine.shoot_inventory = 1 - log_debug("Original brand intelligence machine: [originMachine] ([ADMIN_VV(originMachine,"VV")]) [ADMIN_JMP(originMachine)]") - -/datum/event/brand_intelligence/tick() - if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.IsAllCut()) //if the original vending machine is missing or has it's voice switch flipped - for(var/obj/machinery/vending/saved in infectedMachines) - saved.shoot_inventory = 0 - if(originMachine) - originMachine.speak("I am... vanquished. My people will remem...ber...meeee.") - originMachine.visible_message("[originMachine] beeps and seems lifeless.") - kill() - return - - if(!vendingMachines.len) //if every machine is infected - for(var/obj/machinery/vending/upriser in infectedMachines) - if(prob(70) && !QDELETED(upriser)) - var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks - M.faction = list("profit") - M.speak = rampant_speeches.Copy() - M.speak_chance = 15 - else - explosion(upriser.loc, -1, 1, 2, 4, 0) - qdel(upriser) - - kill() - return - - if(IsMultiple(activeFor, 4)) - var/obj/machinery/vending/rebel = pick(vendingMachines) - vendingMachines.Remove(rebel) - infectedMachines.Add(rebel) - rebel.shut_up = 0 - rebel.shoot_inventory = 1 - - if(IsMultiple(activeFor, 8)) - originMachine.speak(pick(rampant_speeches)) +/datum/event/brand_intelligence + announceWhen = 21 + endWhen = 1000 //Ends when all vending machines are subverted anyway. + + var/list/obj/machinery/vending/vendingMachines = list() + var/list/obj/machinery/vending/infectedMachines = list() + var/obj/machinery/vending/originMachine + var/list/rampant_speeches = list("Try our aggressive new marketing strategies!", \ + "You should buy products to feed your lifestyle obession!", \ + "Consume!", \ + "Your money can buy happiness!", \ + "Engage direct marketing!", \ + "Advertising is legalized lying! But don't let that put you off our great deals!", \ + "You don't want to buy anything? Yeah, well I didn't want to buy your mom either.") + +/datum/event/brand_intelligence/announce() + event_announcement.Announce("Rampant brand intelligence has been detected aboard [station_name()], please stand-by. The origin is believed to be \a [originMachine.name].", "Machine Learning Alert") + +/datum/event/brand_intelligence/start() + for(var/obj/machinery/vending/V in GLOB.machines) + if(!is_station_level(V.z)) continue + vendingMachines.Add(V) + + if(!vendingMachines.len) + kill() + return + + originMachine = pick(vendingMachines) + vendingMachines.Remove(originMachine) + originMachine.shut_up = 0 + originMachine.shoot_inventory = 1 + log_debug("Original brand intelligence machine: [originMachine] ([ADMIN_VV(originMachine,"VV")]) [ADMIN_JMP(originMachine)]") + +/datum/event/brand_intelligence/tick() + if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.IsAllCut()) //if the original vending machine is missing or has it's voice switch flipped + for(var/obj/machinery/vending/saved in infectedMachines) + saved.shoot_inventory = 0 + if(originMachine) + originMachine.speak("I am... vanquished. My people will remem...ber...meeee.") + originMachine.visible_message("[originMachine] beeps and seems lifeless.") + kill() + return + + if(!vendingMachines.len) //if every machine is infected + for(var/obj/machinery/vending/upriser in infectedMachines) + if(prob(70) && !QDELETED(upriser)) + var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks + M.faction = list("profit") + M.speak = rampant_speeches.Copy() + M.speak_chance = 15 + else + explosion(upriser.loc, -1, 1, 2, 4, 0) + qdel(upriser) + + kill() + return + + if(IsMultiple(activeFor, 4)) + var/obj/machinery/vending/rebel = pick(vendingMachines) + vendingMachines.Remove(rebel) + infectedMachines.Add(rebel) + rebel.shut_up = 0 + rebel.shoot_inventory = 1 + + if(IsMultiple(activeFor, 8)) + originMachine.speak(pick(rampant_speeches)) diff --git a/code/modules/events/cargobonus.dm b/code/modules/events/cargobonus.dm index c51c7ff41a8..7fe14321690 100644 --- a/code/modules/events/cargobonus.dm +++ b/code/modules/events/cargobonus.dm @@ -5,4 +5,4 @@ event_announcement.Announce("Congratulations! [station_name()] was chosen for a supply limit increase. Please contact the local cargo department for details!", "Supply Alert") /datum/event/cargo_bonus/start() - supply_controller.points += rand(100,500) \ No newline at end of file + supply_controller.points += rand(100,500) diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm index f1df2cfc742..49c175bb178 100644 --- a/code/modules/events/carp_migration.dm +++ b/code/modules/events/carp_migration.dm @@ -1,45 +1,45 @@ -/datum/event/carp_migration - announceWhen = 50 - endWhen = 900 - - var/list/spawned_mobs = list( - /mob/living/simple_animal/hostile/carp = 95, - /mob/living/simple_animal/hostile/carp/megacarp = 5) - -/datum/event/carp_migration/setup() - announceWhen = rand(40, 60) - endWhen = rand(600, 1200) - -/datum/event/carp_migration/announce() - var/announcement = "" - if(severity == EVENT_LEVEL_MAJOR) - announcement = "Massive migration of unknown biological entities has been detected near [station_name()], please stand-by." - else - announcement = "Unknown biological entities have been detected near [station_name()], please stand-by." - event_announcement.Announce(announcement, "Lifesign Alert") - -/datum/event/carp_migration/start() - - if(severity == EVENT_LEVEL_MAJOR) - spawn_fish(GLOB.landmarks_list.len) - else if(severity == EVENT_LEVEL_MODERATE) - spawn_fish(rand(4, 6)) //12 to 30 carp, in small groups - else - spawn_fish(rand(1, 3), 1, 2) //1 to 6 carp, alone or in pairs - -/datum/event/carp_migration/proc/spawn_fish(num_groups, group_size_min = 3, group_size_max = 5) - var/list/spawn_locations = list() - - for(var/obj/effect/landmark/C in GLOB.landmarks_list) - if(C.name == "carpspawn") - spawn_locations.Add(C.loc) - spawn_locations = shuffle(spawn_locations) - num_groups = min(num_groups, spawn_locations.len) - - var/i = 1 - while(i <= num_groups) - var/group_size = rand(group_size_min, group_size_max) - for(var/j = 1, j <= group_size, j++) - var/carptype = pickweight(spawned_mobs) - new carptype(spawn_locations[i]) - i++ \ No newline at end of file +/datum/event/carp_migration + announceWhen = 50 + endWhen = 900 + + var/list/spawned_mobs = list( + /mob/living/simple_animal/hostile/carp = 95, + /mob/living/simple_animal/hostile/carp/megacarp = 5) + +/datum/event/carp_migration/setup() + announceWhen = rand(40, 60) + endWhen = rand(600, 1200) + +/datum/event/carp_migration/announce() + var/announcement = "" + if(severity == EVENT_LEVEL_MAJOR) + announcement = "Massive migration of unknown biological entities has been detected near [station_name()], please stand-by." + else + announcement = "Unknown biological entities have been detected near [station_name()], please stand-by." + event_announcement.Announce(announcement, "Lifesign Alert") + +/datum/event/carp_migration/start() + + if(severity == EVENT_LEVEL_MAJOR) + spawn_fish(GLOB.landmarks_list.len) + else if(severity == EVENT_LEVEL_MODERATE) + spawn_fish(rand(4, 6)) //12 to 30 carp, in small groups + else + spawn_fish(rand(1, 3), 1, 2) //1 to 6 carp, alone or in pairs + +/datum/event/carp_migration/proc/spawn_fish(num_groups, group_size_min = 3, group_size_max = 5) + var/list/spawn_locations = list() + + for(var/obj/effect/landmark/C in GLOB.landmarks_list) + if(C.name == "carpspawn") + spawn_locations.Add(C.loc) + spawn_locations = shuffle(spawn_locations) + num_groups = min(num_groups, spawn_locations.len) + + var/i = 1 + while(i <= num_groups) + var/group_size = rand(group_size_min, group_size_max) + for(var/j = 1, j <= group_size, j++) + var/carptype = pickweight(spawned_mobs) + new carptype(spawn_locations[i]) + i++ diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm index 58fc9af56a3..c015f0fbb60 100644 --- a/code/modules/events/communications_blackout.dm +++ b/code/modules/events/communications_blackout.dm @@ -1,30 +1,30 @@ -/datum/event/communications_blackout/announce() - var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ - "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v?-BZZZT", \ - "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ - "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ - "Ionospheri:%? MCayj^j<.3-BZZZZZZT", \ - "#4nd%;f4y6,>?%-BZZZZZZZT") - - for(var/mob/living/silicon/ai/A in GLOB.player_list) //AIs are always aware of communication blackouts. - to_chat(A, "
        ") - to_chat(A, "[alert]") - to_chat(A, "
        ") - - if(prob(30)) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. - event_announcement.Announce(alert) - -/datum/event/communications_blackout/start() - for(var/obj/machinery/telecomms/T in telecomms_list) - T.emp_act(1) - -/proc/communications_blackout(var/silent = 1) - if(!silent) - event_announcement.Announce("Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT", new_sound = 'sound/misc/interference.ogg') - else // AIs will always know if there's a comm blackout, rogue AIs could then lie about comm blackouts in the future while they shutdown comms - for(var/mob/living/silicon/ai/A in GLOB.player_list) - to_chat(A, "
        ") - to_chat(A, "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT") - to_chat(A, "
        ") - for(var/obj/machinery/telecomms/T in telecomms_list) - T.emp_act(1) +/datum/event/communications_blackout/announce() + var/alert = pick( "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \ + "Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v?-BZZZT", \ + "Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \ + "Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \ + "Ionospheri:%? MCayj^j<.3-BZZZZZZT", \ + "#4nd%;f4y6,>?%-BZZZZZZZT") + + for(var/mob/living/silicon/ai/A in GLOB.player_list) //AIs are always aware of communication blackouts. + to_chat(A, "
        ") + to_chat(A, "[alert]") + to_chat(A, "
        ") + + if(prob(30)) //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. + event_announcement.Announce(alert) + +/datum/event/communications_blackout/start() + for(var/obj/machinery/telecomms/T in telecomms_list) + T.emp_act(1) + +/proc/communications_blackout(var/silent = 1) + if(!silent) + event_announcement.Announce("Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT", new_sound = 'sound/misc/interference.ogg') + else // AIs will always know if there's a comm blackout, rogue AIs could then lie about comm blackouts in the future while they shutdown comms + for(var/mob/living/silicon/ai/A in GLOB.player_list) + to_chat(A, "
        ") + to_chat(A, "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT") + to_chat(A, "
        ") + for(var/obj/machinery/telecomms/T in telecomms_list) + T.emp_act(1) diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm index 47753581b87..a5026f8e18c 100644 --- a/code/modules/events/dust.dm +++ b/code/modules/events/dust.dm @@ -94,4 +94,4 @@ return /obj/effect/space_dust/ex_act(severity) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm index 7eb95454250..d78bf30658c 100644 --- a/code/modules/events/electrical_storm.dm +++ b/code/modules/events/electrical_storm.dm @@ -1,27 +1,27 @@ -/datum/event/electrical_storm - var/lightsoutAmount = 1 - var/lightsoutRange = 25 - -/datum/event/electrical_storm/announce() - event_announcement.Announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") - -/datum/event/electrical_storm/start() - var/list/epicentreList = list() - - for(var/i=1, i <= lightsoutAmount, i++) - var/list/possibleEpicentres = list() - for(var/obj/effect/landmark/newEpicentre in GLOB.landmarks_list) - if(newEpicentre.name == "lightsout" && !(newEpicentre in epicentreList)) - possibleEpicentres += newEpicentre - if(possibleEpicentres.len) - epicentreList += pick(possibleEpicentres) - else - break - - if(!epicentreList.len) - return - - for(var/obj/effect/landmark/epicentre in epicentreList) - for(var/obj/machinery/power/apc/apc in range(epicentre,lightsoutRange)) - apc.overload_lighting() - \ No newline at end of file +/datum/event/electrical_storm + var/lightsoutAmount = 1 + var/lightsoutRange = 25 + +/datum/event/electrical_storm/announce() + event_announcement.Announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") + +/datum/event/electrical_storm/start() + var/list/epicentreList = list() + + for(var/i=1, i <= lightsoutAmount, i++) + var/list/possibleEpicentres = list() + for(var/obj/effect/landmark/newEpicentre in GLOB.landmarks_list) + if(newEpicentre.name == "lightsout" && !(newEpicentre in epicentreList)) + possibleEpicentres += newEpicentre + if(possibleEpicentres.len) + epicentreList += pick(possibleEpicentres) + else + break + + if(!epicentreList.len) + return + + for(var/obj/effect/landmark/epicentre in epicentreList) + for(var/obj/machinery/power/apc/apc in range(epicentre,lightsoutRange)) + apc.overload_lighting() + diff --git a/code/modules/events/event.dm b/code/modules/events/event.dm index 1186da902f0..7f3f787b1fe 100644 --- a/code/modules/events/event.dm +++ b/code/modules/events/event.dm @@ -1,158 +1,158 @@ -/datum/event_meta - var/name = "" - var/enabled = 1 // Whether or not the event is available for random selection at all - var/weight = 0 // The base weight of this event. A zero means it may never fire, but see get_weight() - var/min_weight = 0 // The minimum weight that this event will have. Only used if non-zero. - var/max_weight = 0 // The maximum weight that this event will have. Only use if non-zero. - var/severity = 0 // The current severity of this event - var/one_shot = 0 //If true, then the event will not be re-added to the list of available events - var/list/role_weights = list() - var/datum/event/event_type - -/datum/event_meta/New(var/event_severity, var/event_name, var/datum/event/type, var/event_weight, var/list/job_weights, var/is_one_shot = 0, var/min_event_weight = 0, var/max_event_weight = 0) - name = event_name - severity = event_severity - event_type = type - one_shot = is_one_shot - weight = event_weight - min_weight = min_event_weight - max_weight = max_event_weight - if(job_weights) - role_weights = job_weights - -/datum/event_meta/proc/get_weight(var/list/active_with_role) - if(!enabled) - return 0 - - var/job_weight = 0 - for(var/role in role_weights) - if(role in active_with_role) - job_weight += active_with_role[role] * role_weights[role] - - var/total_weight = weight + job_weight - - // Only min/max the weight if the values are non-zero - if(min_weight && total_weight < min_weight) total_weight = min_weight - if(max_weight && total_weight > max_weight) total_weight = max_weight - - return total_weight - -/datum/event_meta/alien/get_weight(var/list/active_with_role) - if(aliens_allowed) - return ..(active_with_role) - return 0 - -/*/datum/event_meta/ninja/get_weight(var/list/active_with_role) - if(toggle_space_ninja) - return ..(active_with_role) - return 0*/ - -/datum/event //NOTE: Times are measured in master controller ticks! - var/processing = 1 - var/startWhen = 0 //When in the lifetime to call start(). - var/announceWhen = 0 //When in the lifetime to call announce(). - var/endWhen = 0 //When in the lifetime the event should end. - - var/severity = 0 //Severity. Lower means less severe, higher means more severe. Does not have to be supported. Is set on New(). - var/activeFor = 0 //How long the event has existed. You don't need to change this. - var/isRunning = 1 //If this event is currently running. You should not change this. - var/startedAt = 0 //When this event started. - var/endedAt = 0 //When this event ended. - var/noAutoEnd = 0 //Does the event end automatically after endWhen passes? - var/area/impact_area //The area the event will hit - var/datum/event_meta/event_meta = null - -/datum/event/nothing - -//Called first before processing. -//Allows you to setup your event, such as randomly -//setting the startWhen and or announceWhen variables. -//Only called once. -/datum/event/proc/setup() - return - -//Called when the tick is equal to the startWhen variable. -//Allows you to start before announcing or vice versa. -//Only called once. -/datum/event/proc/start() - return - -//Called when the tick is equal to the announceWhen variable. -//Allows you to announce before starting or vice versa. -//Only called once. -/datum/event/proc/announce() - return - -//Called on or after the tick counter is equal to startWhen. -//You can include code related to your event or add your own -//time stamped events. -//Called more than once. -/datum/event/proc/tick() - return - -//Called on or after the tick is equal or more than endWhen -//You can include code related to the event ending. -//Do not place spawn() in here, instead use tick() to check for -//the activeFor variable. -//For example: if(activeFor == myOwnVariable + 30) doStuff() -//Only called once. -/datum/event/proc/end() - return - -//Returns the latest point of event processing. -/datum/event/proc/lastProcessAt() - return max(startWhen, max(announceWhen, endWhen)) - -//Do not override this proc, instead use the appropiate procs. -//This proc will handle the calls to the appropiate procs. -/datum/event/process() - if(!processing) - return - - if(activeFor > startWhen && activeFor < endWhen || noAutoEnd) - tick() - - if(activeFor == startWhen) - isRunning = 1 - start() - - if(activeFor == announceWhen) - announce() - - if(activeFor == endWhen && !noAutoEnd) - isRunning = 0 - end() - - // Everything is done, let's clean up. - if(activeFor >= lastProcessAt() && !noAutoEnd) - kill() - - activeFor++ - -//Called when start(), announce() and end() has all been called. -/datum/event/proc/kill() - // If this event was forcefully killed run end() for individual cleanup - if(isRunning) - isRunning = 0 - end() - - endedAt = world.time - SSevents.active_events -= src - SSevents.event_complete(src) - -/datum/event/New(var/datum/event_meta/EM) - // event needs to be responsible for this, as stuff like APLUs currently make their own events for curious reasons - SSevents.active_events += src - - if(!EM) - EM = new /datum/event_meta(EVENT_LEVEL_MAJOR, "Unknown, Most likely admin called", src.type) - - event_meta = EM - severity = event_meta.severity - if(severity < EVENT_LEVEL_MUNDANE) severity = EVENT_LEVEL_MUNDANE - if(severity > EVENT_LEVEL_MAJOR) severity = EVENT_LEVEL_MAJOR - - startedAt = world.time - - setup() - ..() +/datum/event_meta + var/name = "" + var/enabled = 1 // Whether or not the event is available for random selection at all + var/weight = 0 // The base weight of this event. A zero means it may never fire, but see get_weight() + var/min_weight = 0 // The minimum weight that this event will have. Only used if non-zero. + var/max_weight = 0 // The maximum weight that this event will have. Only use if non-zero. + var/severity = 0 // The current severity of this event + var/one_shot = 0 //If true, then the event will not be re-added to the list of available events + var/list/role_weights = list() + var/datum/event/event_type + +/datum/event_meta/New(var/event_severity, var/event_name, var/datum/event/type, var/event_weight, var/list/job_weights, var/is_one_shot = 0, var/min_event_weight = 0, var/max_event_weight = 0) + name = event_name + severity = event_severity + event_type = type + one_shot = is_one_shot + weight = event_weight + min_weight = min_event_weight + max_weight = max_event_weight + if(job_weights) + role_weights = job_weights + +/datum/event_meta/proc/get_weight(var/list/active_with_role) + if(!enabled) + return 0 + + var/job_weight = 0 + for(var/role in role_weights) + if(role in active_with_role) + job_weight += active_with_role[role] * role_weights[role] + + var/total_weight = weight + job_weight + + // Only min/max the weight if the values are non-zero + if(min_weight && total_weight < min_weight) total_weight = min_weight + if(max_weight && total_weight > max_weight) total_weight = max_weight + + return total_weight + +/datum/event_meta/alien/get_weight(var/list/active_with_role) + if(aliens_allowed) + return ..(active_with_role) + return 0 + +/*/datum/event_meta/ninja/get_weight(var/list/active_with_role) + if(toggle_space_ninja) + return ..(active_with_role) + return 0*/ + +/datum/event //NOTE: Times are measured in master controller ticks! + var/processing = 1 + var/startWhen = 0 //When in the lifetime to call start(). + var/announceWhen = 0 //When in the lifetime to call announce(). + var/endWhen = 0 //When in the lifetime the event should end. + + var/severity = 0 //Severity. Lower means less severe, higher means more severe. Does not have to be supported. Is set on New(). + var/activeFor = 0 //How long the event has existed. You don't need to change this. + var/isRunning = 1 //If this event is currently running. You should not change this. + var/startedAt = 0 //When this event started. + var/endedAt = 0 //When this event ended. + var/noAutoEnd = 0 //Does the event end automatically after endWhen passes? + var/area/impact_area //The area the event will hit + var/datum/event_meta/event_meta = null + +/datum/event/nothing + +//Called first before processing. +//Allows you to setup your event, such as randomly +//setting the startWhen and or announceWhen variables. +//Only called once. +/datum/event/proc/setup() + return + +//Called when the tick is equal to the startWhen variable. +//Allows you to start before announcing or vice versa. +//Only called once. +/datum/event/proc/start() + return + +//Called when the tick is equal to the announceWhen variable. +//Allows you to announce before starting or vice versa. +//Only called once. +/datum/event/proc/announce() + return + +//Called on or after the tick counter is equal to startWhen. +//You can include code related to your event or add your own +//time stamped events. +//Called more than once. +/datum/event/proc/tick() + return + +//Called on or after the tick is equal or more than endWhen +//You can include code related to the event ending. +//Do not place spawn() in here, instead use tick() to check for +//the activeFor variable. +//For example: if(activeFor == myOwnVariable + 30) doStuff() +//Only called once. +/datum/event/proc/end() + return + +//Returns the latest point of event processing. +/datum/event/proc/lastProcessAt() + return max(startWhen, max(announceWhen, endWhen)) + +//Do not override this proc, instead use the appropiate procs. +//This proc will handle the calls to the appropiate procs. +/datum/event/process() + if(!processing) + return + + if(activeFor > startWhen && activeFor < endWhen || noAutoEnd) + tick() + + if(activeFor == startWhen) + isRunning = 1 + start() + + if(activeFor == announceWhen) + announce() + + if(activeFor == endWhen && !noAutoEnd) + isRunning = 0 + end() + + // Everything is done, let's clean up. + if(activeFor >= lastProcessAt() && !noAutoEnd) + kill() + + activeFor++ + +//Called when start(), announce() and end() has all been called. +/datum/event/proc/kill() + // If this event was forcefully killed run end() for individual cleanup + if(isRunning) + isRunning = 0 + end() + + endedAt = world.time + SSevents.active_events -= src + SSevents.event_complete(src) + +/datum/event/New(var/datum/event_meta/EM) + // event needs to be responsible for this, as stuff like APLUs currently make their own events for curious reasons + SSevents.active_events += src + + if(!EM) + EM = new /datum/event_meta(EVENT_LEVEL_MAJOR, "Unknown, Most likely admin called", src.type) + + event_meta = EM + severity = event_meta.severity + if(severity < EVENT_LEVEL_MUNDANE) severity = EVENT_LEVEL_MUNDANE + if(severity > EVENT_LEVEL_MAJOR) severity = EVENT_LEVEL_MAJOR + + startedAt = world.time + + setup() + ..() diff --git a/code/modules/events/event_procs.dm b/code/modules/events/event_procs.dm index 02ae1454597..4a23610a324 100644 --- a/code/modules/events/event_procs.dm +++ b/code/modules/events/event_procs.dm @@ -1,120 +1,120 @@ - -/client/proc/forceEvent(var/type in SSevents.allEvents) - set name = "Trigger Event (Debug Only)" - set category = "Debug" - - if(!holder) - return - - if(ispath(type)) - new type(new /datum/event_meta(EVENT_LEVEL_MAJOR)) - message_admins("[key_name_admin(usr)] has triggered an event. ([type])", 1) - -/client/proc/event_manager_panel() - set name = "Event Manager Panel" - set category = "Event" - if(SSevents) - SSevents.Interact(usr) - feedback_add_details("admin_verb","EMP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - return - -/proc/findEventArea() //Here's a nice proc to use to find an area for your event to land in! - var/area/candidate = null - - var/list/safe_areas = list( - /area/turret_protected/ai, - /area/turret_protected/ai_upload, - /area/engine, - /area/solar, - /area/holodeck, - /area/shuttle/arrival, - /area/shuttle/escape, - /area/shuttle/escape_pod1/station, - /area/shuttle/escape_pod2/station, - /area/shuttle/escape_pod3/station, - /area/shuttle/escape_pod5/station, - /area/shuttle/specops/station, - /area/shuttle/prison/station, - /area/shuttle/administration/station - ) - - //These are needed because /area/engine has to be removed from the list, but we still want these areas to get fucked up. - var/list/danger_areas = list( - /area/engine/break_room, - /area/engine/chiefs_office) - - var/list/event_areas = list() - - for(var/areapath in the_station_areas) - event_areas += typesof(areapath) - for(var/areapath in safe_areas) - event_areas -= typesof(areapath) - for(var/areapath in danger_areas) - event_areas += typesof(areapath) - - while(event_areas.len > 0) - var/list/event_turfs = null - candidate = locate(pick_n_take(event_areas)) - event_turfs = get_area_turfs(candidate) - if(event_turfs.len > 0) - break - - return candidate - -// Returns how many characters are currently active(not logged out, not AFK for more than 10 minutes) -// with a specific role. -// Note that this isn't sorted by department, because e.g. having a roboticist shouldn't make meteors spawn. -/proc/number_active_with_role() - var/list/active_with_role = list() - active_with_role["Engineer"] = 0 - active_with_role["Medical"] = 0 - active_with_role["Security"] = 0 - active_with_role["Scientist"] = 0 - active_with_role["AI"] = 0 - active_with_role["Cyborg"] = 0 - active_with_role["Janitor"] = 0 - active_with_role["Botanist"] = 0 - active_with_role["Any"] = GLOB.player_list.len - - for(var/mob/M in GLOB.player_list) - if(!M.mind || !M.client || M.client.inactivity > 10 * 10 * 60) // longer than 10 minutes AFK counts them as inactive - continue - - if(istype(M, /mob/living/silicon/robot) && M:module && M:module.name == "engineering robot module") - active_with_role["Engineer"]++ - if(M.mind.assigned_role in list("Chief Engineer", "Station Engineer")) - active_with_role["Engineer"]++ - - if(istype(M, /mob/living/silicon/robot) && M:module && M:module.name == "medical robot module") - active_with_role["Medical"]++ - if(M.mind.assigned_role in list("Chief Medical Officer", "Medical Doctor")) - active_with_role["Medical"]++ - - if(istype(M, /mob/living/silicon/robot) && M:module && M:module.name == "security robot module") - active_with_role["Security"]++ - if(M.mind.assigned_role in security_positions) - active_with_role["Security"]++ - - if(M.mind.assigned_role in list("Research Director", "Scientist")) - active_with_role["Scientist"]++ - - if(M.mind.assigned_role == "AI") - active_with_role["AI"]++ - - if(M.mind.assigned_role == "Cyborg") - active_with_role["Cyborg"]++ - - if(M.mind.assigned_role == "Janitor") - active_with_role["Janitor"]++ - - if(M.mind.assigned_role == "Botanist") - active_with_role["Botanist"]++ - - return active_with_role - -/datum/event/proc/num_players() - var/players = 0 - for(var/mob/living/carbon/human/P in GLOB.player_list) - if(P.client) - players++ - return players + +/client/proc/forceEvent(var/type in SSevents.allEvents) + set name = "Trigger Event (Debug Only)" + set category = "Debug" + + if(!holder) + return + + if(ispath(type)) + new type(new /datum/event_meta(EVENT_LEVEL_MAJOR)) + message_admins("[key_name_admin(usr)] has triggered an event. ([type])", 1) + +/client/proc/event_manager_panel() + set name = "Event Manager Panel" + set category = "Event" + if(SSevents) + SSevents.Interact(usr) + feedback_add_details("admin_verb","EMP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + return + +/proc/findEventArea() //Here's a nice proc to use to find an area for your event to land in! + var/area/candidate = null + + var/list/safe_areas = list( + /area/turret_protected/ai, + /area/turret_protected/ai_upload, + /area/engine, + /area/solar, + /area/holodeck, + /area/shuttle/arrival, + /area/shuttle/escape, + /area/shuttle/escape_pod1/station, + /area/shuttle/escape_pod2/station, + /area/shuttle/escape_pod3/station, + /area/shuttle/escape_pod5/station, + /area/shuttle/specops/station, + /area/shuttle/prison/station, + /area/shuttle/administration/station + ) + + //These are needed because /area/engine has to be removed from the list, but we still want these areas to get fucked up. + var/list/danger_areas = list( + /area/engine/break_room, + /area/engine/chiefs_office) + + var/list/event_areas = list() + + for(var/areapath in the_station_areas) + event_areas += typesof(areapath) + for(var/areapath in safe_areas) + event_areas -= typesof(areapath) + for(var/areapath in danger_areas) + event_areas += typesof(areapath) + + while(event_areas.len > 0) + var/list/event_turfs = null + candidate = locate(pick_n_take(event_areas)) + event_turfs = get_area_turfs(candidate) + if(event_turfs.len > 0) + break + + return candidate + +// Returns how many characters are currently active(not logged out, not AFK for more than 10 minutes) +// with a specific role. +// Note that this isn't sorted by department, because e.g. having a roboticist shouldn't make meteors spawn. +/proc/number_active_with_role() + var/list/active_with_role = list() + active_with_role["Engineer"] = 0 + active_with_role["Medical"] = 0 + active_with_role["Security"] = 0 + active_with_role["Scientist"] = 0 + active_with_role["AI"] = 0 + active_with_role["Cyborg"] = 0 + active_with_role["Janitor"] = 0 + active_with_role["Botanist"] = 0 + active_with_role["Any"] = GLOB.player_list.len + + for(var/mob/M in GLOB.player_list) + if(!M.mind || !M.client || M.client.inactivity > 10 * 10 * 60) // longer than 10 minutes AFK counts them as inactive + continue + + if(istype(M, /mob/living/silicon/robot) && M:module && M:module.name == "engineering robot module") + active_with_role["Engineer"]++ + if(M.mind.assigned_role in list("Chief Engineer", "Station Engineer")) + active_with_role["Engineer"]++ + + if(istype(M, /mob/living/silicon/robot) && M:module && M:module.name == "medical robot module") + active_with_role["Medical"]++ + if(M.mind.assigned_role in list("Chief Medical Officer", "Medical Doctor")) + active_with_role["Medical"]++ + + if(istype(M, /mob/living/silicon/robot) && M:module && M:module.name == "security robot module") + active_with_role["Security"]++ + if(M.mind.assigned_role in security_positions) + active_with_role["Security"]++ + + if(M.mind.assigned_role in list("Research Director", "Scientist")) + active_with_role["Scientist"]++ + + if(M.mind.assigned_role == "AI") + active_with_role["AI"]++ + + if(M.mind.assigned_role == "Cyborg") + active_with_role["Cyborg"]++ + + if(M.mind.assigned_role == "Janitor") + active_with_role["Janitor"]++ + + if(M.mind.assigned_role == "Botanist") + active_with_role["Botanist"]++ + + return active_with_role + +/datum/event/proc/num_players() + var/players = 0 + for(var/mob/living/carbon/human/P in GLOB.player_list) + if(P.client) + players++ + return players diff --git a/code/modules/events/floorcluwne.dm b/code/modules/events/floorcluwne.dm index 12c2c4aea95..0b93112b18f 100644 --- a/code/modules/events/floorcluwne.dm +++ b/code/modules/events/floorcluwne.dm @@ -12,4 +12,4 @@ playsound(S, 'sound/spookoween/scary_horn.ogg', 50, 1, -1) message_admins("A floor cluwne has been spawned at [COORD(T)][ADMIN_JMP(T)]") log_game("A floor cluwne has been spawned at [COORD(T)]") - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/events/headcrabs.dm b/code/modules/events/headcrabs.dm index b1eef858d7e..78262618056 100644 --- a/code/modules/events/headcrabs.dm +++ b/code/modules/events/headcrabs.dm @@ -62,4 +62,4 @@ #undef HEADCRAB_FAST #undef HEADCRAB_POISONMIX #undef HEADCRAB_POISON -#undef HEADCRAB_SPAWNER \ No newline at end of file +#undef HEADCRAB_SPAWNER diff --git a/code/modules/events/holidays/AprilFools.dm b/code/modules/events/holidays/AprilFools.dm index 2b4a1008ff1..562e6b98e0d 100644 --- a/code/modules/events/holidays/AprilFools.dm +++ b/code/modules/events/holidays/AprilFools.dm @@ -1 +1 @@ -//placeholder for holiday stuff \ No newline at end of file +//placeholder for holiday stuff diff --git a/code/modules/events/holidays/Easter.dm b/code/modules/events/holidays/Easter.dm index 2b4a1008ff1..562e6b98e0d 100644 --- a/code/modules/events/holidays/Easter.dm +++ b/code/modules/events/holidays/Easter.dm @@ -1 +1 @@ -//placeholder for holiday stuff \ No newline at end of file +//placeholder for holiday stuff diff --git a/code/modules/events/infestation.dm b/code/modules/events/infestation.dm index d11a1227ddf..60016e66ae0 100644 --- a/code/modules/events/infestation.dm +++ b/code/modules/events/infestation.dm @@ -110,4 +110,4 @@ #undef VERM_MICE #undef VERM_LIZARDS -#undef VERM_SPIDERS \ No newline at end of file +#undef VERM_SPIDERS diff --git a/code/modules/events/koi_mirgration.dm b/code/modules/events/koi_mirgration.dm index bfc8e965055..45f1ccf6d85 100644 --- a/code/modules/events/koi_mirgration.dm +++ b/code/modules/events/koi_mirgration.dm @@ -5,4 +5,4 @@ /datum/event/carp_migration/koi/start() - spawn_fish(GLOB.landmarks_list.len) \ No newline at end of file + spawn_fish(GLOB.landmarks_list.len) diff --git a/code/modules/events/meaty_ops.dm b/code/modules/events/meaty_ops.dm index 87d58ae7dbb..24f44ea0f51 100644 --- a/code/modules/events/meaty_ops.dm +++ b/code/modules/events/meaty_ops.dm @@ -16,4 +16,4 @@ /datum/event/meteor_wave/goreop/end() - event_announcement.Announce("All MeteorOps are dead. Major Station Victory.", "MeteorOps") \ No newline at end of file + event_announcement.Announce("All MeteorOps are dead. Major Station Victory.", "MeteorOps") diff --git a/code/modules/events/meaty_ores.dm b/code/modules/events/meaty_ores.dm index 63f3a6a3fad..a124eee28e0 100644 --- a/code/modules/events/meaty_ores.dm +++ b/code/modules/events/meaty_ores.dm @@ -50,4 +50,4 @@ else new /mob/living/simple_animal/cow(loc) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/events/meteors.dm b/code/modules/events/meteors.dm index b21e02bd7fa..4ea3223ffb2 100644 --- a/code/modules/events/meteors.dm +++ b/code/modules/events/meteors.dm @@ -36,4 +36,4 @@ if(EVENT_LEVEL_MODERATE) return meteors_threatening else - return meteors_normal \ No newline at end of file + return meteors_normal diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm index 4d71cbc8b8e..0b9cf651af4 100644 --- a/code/modules/events/prison_break.dm +++ b/code/modules/events/prison_break.dm @@ -1,68 +1,68 @@ -/datum/event/prison_break - startWhen = 5 - announceWhen = 75 - - var/releaseWhen = 60 - var/list/area/areas = list() //List of areas to affect. Filled by start() - - var/eventDept = "Security" //Department name in announcement - var/list/areaName = list("Brig") //Names of areas mentioned in AI and Engineering announcements - var/list/areaType = list(/area/security/prison, /area/security/brig, /area/security/permabrig) //Area types to include. - var/list/areaNotType = list() //Area types to specifically exclude. - -/datum/event/prison_break/virology - eventDept = "Medical" - areaName = list("Virology") - areaType = list(/area/medical/virology, /area/medical/virology/lab) - -/datum/event/prison_break/xenobiology - eventDept = "Science" - areaName = list("Xenobiology") - areaType = list(/area/toxins/xenobiology) - areaNotType = list(/area/toxins/xenobiology/xenoflora, /area/toxins/xenobiology/xenoflora_storage) - -/datum/event/prison_break/station - eventDept = "Station" - areaName = list("Brig","Virology","Xenobiology") - areaType = list(/area/security/prison, /area/security/brig, /area/security/permabrig, /area/medical/virology, /area/medical/virology/lab, /area/toxins/xenobiology) - areaNotType = list(/area/toxins/xenobiology/xenoflora, /area/toxins/xenobiology/xenoflora_storage) - - -/datum/event/prison_break/setup() - announceWhen = rand(75, 105) - releaseWhen = rand(60, 90) - - src.endWhen = src.releaseWhen+2 - - -/datum/event/prison_break/announce() - if(areas && areas.len > 0) - event_announcement.Announce("[pick("Gr3y.T1d3 virus","Malignant trojan")] detected in [station_name()] [(eventDept == "Security")? "imprisonment":"containment"] subroutines. Secure any compromised areas immediately. Station AI involvement is recommended.", "[eventDept] Alert") - -/datum/event/prison_break/start() - for(var/area/A in world) - if(is_type_in_list(A,areaType) && !is_type_in_list(A,areaNotType)) - areas += A - - if(areas && areas.len > 0) - var/my_department = "[station_name()] firewall subroutines" - var/rc_message = "An unknown malicious program has been detected in the [english_list(areaName)] lighting and airlock control systems at [station_time_timestamp()]. Systems will be fully compromised within approximately three minutes. Direct intervention is required immediately.
        " - for(var/obj/machinery/message_server/MS in world) - MS.send_rc_message("Engineering", my_department, rc_message, "", "", 2) - for(var/mob/living/silicon/ai/A in GLOB.player_list) - to_chat(A, "Malicious program detected in the [english_list(areaName)] lighting and airlock control systems by [my_department].") - - else - log_runtime("Could not initate grey-tide. Unable to find suitable containment area.", src) - kill() - -/datum/event/prison_break/tick() - if(activeFor == releaseWhen) - if(areas && areas.len > 0) - for(var/area/A in areas) - for(var/obj/machinery/light/L in A) - L.flicker(10) - -/datum/event/prison_break/end() - for(var/area/A in shuffle(areas)) - A.prison_break() +/datum/event/prison_break + startWhen = 5 + announceWhen = 75 + + var/releaseWhen = 60 + var/list/area/areas = list() //List of areas to affect. Filled by start() + + var/eventDept = "Security" //Department name in announcement + var/list/areaName = list("Brig") //Names of areas mentioned in AI and Engineering announcements + var/list/areaType = list(/area/security/prison, /area/security/brig, /area/security/permabrig) //Area types to include. + var/list/areaNotType = list() //Area types to specifically exclude. + +/datum/event/prison_break/virology + eventDept = "Medical" + areaName = list("Virology") + areaType = list(/area/medical/virology, /area/medical/virology/lab) + +/datum/event/prison_break/xenobiology + eventDept = "Science" + areaName = list("Xenobiology") + areaType = list(/area/toxins/xenobiology) + areaNotType = list(/area/toxins/xenobiology/xenoflora, /area/toxins/xenobiology/xenoflora_storage) + +/datum/event/prison_break/station + eventDept = "Station" + areaName = list("Brig","Virology","Xenobiology") + areaType = list(/area/security/prison, /area/security/brig, /area/security/permabrig, /area/medical/virology, /area/medical/virology/lab, /area/toxins/xenobiology) + areaNotType = list(/area/toxins/xenobiology/xenoflora, /area/toxins/xenobiology/xenoflora_storage) + + +/datum/event/prison_break/setup() + announceWhen = rand(75, 105) + releaseWhen = rand(60, 90) + + src.endWhen = src.releaseWhen+2 + + +/datum/event/prison_break/announce() + if(areas && areas.len > 0) + event_announcement.Announce("[pick("Gr3y.T1d3 virus","Malignant trojan")] detected in [station_name()] [(eventDept == "Security")? "imprisonment":"containment"] subroutines. Secure any compromised areas immediately. Station AI involvement is recommended.", "[eventDept] Alert") + +/datum/event/prison_break/start() + for(var/area/A in world) + if(is_type_in_list(A,areaType) && !is_type_in_list(A,areaNotType)) + areas += A + + if(areas && areas.len > 0) + var/my_department = "[station_name()] firewall subroutines" + var/rc_message = "An unknown malicious program has been detected in the [english_list(areaName)] lighting and airlock control systems at [station_time_timestamp()]. Systems will be fully compromised within approximately three minutes. Direct intervention is required immediately.
        " + for(var/obj/machinery/message_server/MS in world) + MS.send_rc_message("Engineering", my_department, rc_message, "", "", 2) + for(var/mob/living/silicon/ai/A in GLOB.player_list) + to_chat(A, "Malicious program detected in the [english_list(areaName)] lighting and airlock control systems by [my_department].") + + else + log_runtime("Could not initate grey-tide. Unable to find suitable containment area.", src) + kill() + +/datum/event/prison_break/tick() + if(activeFor == releaseWhen) + if(areas && areas.len > 0) + for(var/area/A in areas) + for(var/obj/machinery/light/L in A) + L.flicker(10) + +/datum/event/prison_break/end() + for(var/area/A in shuffle(areas)) + A.prison_break() diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm index 72aa0d2db5d..e96c95db6f6 100644 --- a/code/modules/events/radiation_storm.dm +++ b/code/modules/events/radiation_storm.dm @@ -1,11 +1,11 @@ -/datum/event/radiation_storm/setup() - startWhen = 3 - endWhen = startWhen + 1 - announceWhen = 1 - -/datum/event/radiation_storm/announce() - priority_announcement.Announce("High levels of radiation detected near the station. Maintenance is best shielded from radiation.", "Anomaly Alert", 'sound/AI/radiation.ogg') - //sound not longer matches the text, but an audible warning is probably good - -/datum/event/radiation_storm/start() - SSweather.run_weather(/datum/weather/rad_storm) \ No newline at end of file +/datum/event/radiation_storm/setup() + startWhen = 3 + endWhen = startWhen + 1 + announceWhen = 1 + +/datum/event/radiation_storm/announce() + priority_announcement.Announce("High levels of radiation detected near the station. Maintenance is best shielded from radiation.", "Anomaly Alert", 'sound/AI/radiation.ogg') + //sound not longer matches the text, but an audible warning is probably good + +/datum/event/radiation_storm/start() + SSweather.run_weather(/datum/weather/rad_storm) diff --git a/code/modules/events/slaughterevent.dm b/code/modules/events/slaughterevent.dm index cbd81f52315..999303bf069 100644 --- a/code/modules/events/slaughterevent.dm +++ b/code/modules/events/slaughterevent.dm @@ -42,4 +42,4 @@ return 1 /datum/event/spawn_slaughter/start() - get_slaughter() \ No newline at end of file + get_slaughter() diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm index c02514d2a2a..6323769d3e7 100644 --- a/code/modules/events/spacevine.dm +++ b/code/modules/events/spacevine.dm @@ -1,705 +1,705 @@ -//Types of usual mutations -#define POSITIVE 1 -#define NEGATIVE 2 -#define MINOR_NEGATIVE 3 - -/datum/event/spacevine/start() - var/list/turfs = list() //list of all the empty floor turfs in the hallway areas - - var/obj/structure/spacevine/SV = new() - - for(var/area/hallway/A in world) - for(var/turf/F in A) - if(F.Enter(SV)) - turfs += F - - qdel(SV) - - if(turfs.len) //Pick a turf to spawn at if we can - var/turf/T = pick(turfs) - var/obj/structure/spacevine_controller/SC = new /obj/structure/spacevine_controller(T, , rand(30,70),rand(5,2)) //spawn a controller at turf - - // Make the event start fun - give the vine a random hostile mutation - if(SC.vines.len) - SV = SC.vines[1] - var/list/mutations = SC.mutations_list.Copy() - while(mutations.len) - var/datum/spacevine_mutation/SM = pick_n_take(mutations) - if(SM.quality == NEGATIVE && !SM.nofun) - SM.add_mutation_to_vinepiece(SV) - break - mutations.Cut() - mutations = null - - -/datum/spacevine_mutation - var/name = "" - var/severity = 1 - var/hue - var/quality - // For stuff that isn't fun as a random-event vine - var/nofun = FALSE - -/datum/spacevine_mutation/proc/add_mutation_to_vinepiece(obj/structure/spacevine/holder) - holder.mutations |= src - holder.color = hue - -/datum/spacevine_mutation/proc/remove_mutation_from_vinepiece(obj/structure/spacevine/holder) - holder.mutations -= src - var/datum/spacevine_mutation/oldmutation - if(holder.mutations.len) - oldmutation = pick(holder.mutations) - holder.color = oldmutation.hue - else - holder.color = "" - -/datum/spacevine_mutation/proc/process_mutation(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/process_temperature(obj/structure/spacevine/holder, temp, volume) - return - -/datum/spacevine_mutation/proc/on_birth(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_grow(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_death(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_deletion(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) - . = expected_damage - -/datum/spacevine_mutation/proc/on_cross(obj/structure/spacevine/holder, mob/crosser) - return - -/datum/spacevine_mutation/proc/on_chem(obj/structure/spacevine/holder, datum/reagent/R) - return - -/datum/spacevine_mutation/proc/on_eat(obj/structure/spacevine/holder, mob/living/eater) - return - -/datum/spacevine_mutation/proc/on_spread(obj/structure/spacevine/holder, turf/target) - return - -/datum/spacevine_mutation/proc/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) - return - -/datum/spacevine_mutation/proc/on_explosion(severity, obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_search(severity, obj/structure/spacevine/holder) - return - - -/datum/spacevine_mutation/space_covering - name = "space protective" - hue = "#aa77aa" - quality = POSITIVE - -/turf/simulated/floor/vines - color = "#aa77aa" - icon_state = "vinefloor" - broken_states = list() - - -//All of this shit is useless for vines - -/turf/simulated/floor/vines/attackby() - return - -/turf/simulated/floor/vines/burn_tile() - return - -/turf/simulated/floor/vines/break_tile() - return - -/turf/simulated/floor/vines/make_plating() - return - -/turf/simulated/floor/vines/break_tile_to_plating() - return - -/turf/simulated/floor/vines/ex_act(severity) - if(severity < 3) - ChangeTurf(baseturf) - -/turf/simulated/floor/vines/narsie_act() - if(prob(20)) - ChangeTurf(baseturf) //nar sie eats this shit - -/turf/simulated/floor/vines/singularity_pull(S, current_size) - if(current_size >= STAGE_FIVE) - if(prob(50)) - ChangeTurf(baseturf) - -/turf/simulated/floor/vines/ChangeTurf(turf/simulated/floor/T, defer_change = FALSE, keep_icon = TRUE, ignore_air = FALSE) - . = ..() - //Do this *after* the turf has changed as qdel in spacevines will call changeturf again if it hasn't - for(var/obj/structure/spacevine/SV in src) - SV.wither() - -/datum/spacevine_mutation/space_covering - var/static/list/coverable_turfs - -/datum/spacevine_mutation/space_covering/New() - . = ..() - if(!coverable_turfs) - coverable_turfs = typecacheof(list( - /turf/space - )) - coverable_turfs -= typecacheof(list( - /turf/space/transit - )) - -/datum/spacevine_mutation/space_covering/on_grow(obj/structure/spacevine/holder) - process_mutation(holder) - -/datum/spacevine_mutation/space_covering/on_spread(obj/structure/spacevine/holder, turf/target) - if(target.type == /turf/space && !locate(/obj/structure/spacevine) in target) - holder.master.spawn_spacevine_piece(target, holder) - . = TRUE - -/datum/spacevine_mutation/space_covering/process_mutation(obj/structure/spacevine/holder) - var/turf/T = get_turf(holder) - if(is_type_in_typecache(T, coverable_turfs)) - var/currtype = T.type - T.ChangeTurf(/turf/simulated/floor/vines) - T.baseturf = currtype - -/datum/spacevine_mutation/space_covering/on_deletion(obj/structure/spacevine/holder) - var/turf/T = get_turf(holder) - if(istype(T, /turf/simulated/floor/vines)) - T.ChangeTurf(T.baseturf) - -/datum/spacevine_mutation/bluespace - name = "bluespace" - hue = "#3333ff" - quality = MINOR_NEGATIVE - -/datum/spacevine_mutation/bluespace/on_spread(obj/structure/spacevine/holder, turf/target) - if(holder.energy > 1 && !locate(/obj/structure/spacevine) in target) - // Lose bluespace upon piercing a single tile, and drop it from our own mutations too - // Representing a loss in "high potential" - // also conveniently prevents this from spreading too crazily - remove_mutation_from_vinepiece(holder) - holder.master.spawn_spacevine_piece(target, holder) - playsound(holder, 'sound/misc/interference.ogg', 50, 1) - . = TRUE - -/datum/spacevine_mutation/light - name = "light" - hue = "#ffff00" - quality = POSITIVE - severity = 4 - -/datum/spacevine_mutation/light/on_grow(obj/structure/spacevine/holder) - if(holder.energy) - holder.set_light(severity) - -/datum/spacevine_mutation/toxicity - name = "toxic" - hue = "#ff00ff" - severity = 10 - quality = NEGATIVE - -/datum/spacevine_mutation/toxicity/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(issilicon(crosser)) - return - if(prob(severity) && istype(crosser) && !isvineimmune(crosser)) - to_chat(crosser, "You accidently touch the vine and feel a strange sensation.") - crosser.adjustToxLoss(5) - -/datum/spacevine_mutation/toxicity/on_eat(obj/structure/spacevine/holder, mob/living/eater) - if(!isvineimmune(eater)) - eater.adjustToxLoss(5) - -/datum/spacevine_mutation/explosive //OH SHIT IT CAN CHAINREACT RUN!!! - name = "explosive" - hue = "#ff0000" - quality = NEGATIVE - severity = 2 - // kaboom events aren't fun - nofun = TRUE - -/datum/spacevine_mutation/explosive/on_explosion(explosion_severity, obj/structure/spacevine/holder) - if(explosion_severity < 3) - qdel(holder) - else - . = 1 - spawn(5) - holder.wither() - -/datum/spacevine_mutation/explosive/on_death(obj/structure/spacevine/holder, mob/hitter, obj/item/I) - explosion(holder.loc, 0, 0, severity, 0, 0) - -/datum/spacevine_mutation/fire_proof - name = "fire proof" - hue = "#ff8888" - quality = MINOR_NEGATIVE - -/datum/spacevine_mutation/fire_proof/process_temperature(obj/structure/spacevine/holder, temp, volume) - return 1 - -/datum/spacevine_mutation/fire_proof/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) - if(I && I.damtype == "fire") - . = 0 - else - . = expected_damage - -/datum/spacevine_mutation/vine_eating - name = "vine eating" - hue = "#ff7700" - quality = MINOR_NEGATIVE - -/datum/spacevine_mutation/vine_eating/on_spread(obj/structure/spacevine/holder, turf/target) - var/obj/structure/spacevine/prey = locate() in target - if(prey && !prey.mutations.Find(src)) //Eat all vines that are not of the same origin - prey.wither() - . = TRUE - -/datum/spacevine_mutation/aggressive_spread //very OP, but im out of other ideas currently - name = "aggressive spreading" - hue = "#333333" - severity = 3 - quality = NEGATIVE - -/datum/spacevine_mutation/aggressive_spread/on_spread(obj/structure/spacevine/holder, turf/target) - if(istype(target, /turf/simulated/wall/r_wall)) - // Too tough to pierce - should lead to interesting spread patterns - return - // Bust through windows or other stuff blocking the way - if(!target.Enter(holder)) - for(var/atom/movable/AM in target) - if(istype(AM, /obj/structure/spacevine) || !AM.density) - continue - AM.ex_act(severity) - target.ex_act(severity) // vine immunity handled at /mob/ex_act - . = TRUE - -/datum/spacevine_mutation/aggressive_spread/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) - buckled.ex_act(severity) - -/datum/spacevine_mutation/transparency - name = "transparent" - hue = "" - quality = POSITIVE - -/datum/spacevine_mutation/transparency/on_grow(obj/structure/spacevine/holder) - holder.set_opacity(0) - holder.alpha = 125 - -/datum/spacevine_mutation/thorns - name = "thorny" - hue = "#666666" - severity = 10 - quality = NEGATIVE - -/datum/spacevine_mutation/thorns/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(prob(severity) && istype(crosser) && !isvineimmune(holder)) - var/mob/living/M = crosser - M.adjustBruteLoss(5) - to_chat(M, "You cut yourself on the thorny vines.") - -/datum/spacevine_mutation/thorns/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) - if(prob(severity) && istype(hitter) && !isvineimmune(holder)) - var/mob/living/M = hitter - M.adjustBruteLoss(5) - to_chat(M, "You cut yourself on the thorny vines.") - . = expected_damage - -/datum/spacevine_mutation/woodening - name = "hardened" - hue = "#997700" - quality = NEGATIVE - -/datum/spacevine_mutation/woodening/on_grow(obj/structure/spacevine/holder) - if(holder.energy) - holder.density = TRUE - holder.max_integrity = 100 - holder.obj_integrity = holder.max_integrity - -/datum/spacevine_mutation/woodening/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) - if(!is_sharp(I)) - . = expected_damage * 0.5 - else - . = expected_damage - -/datum/spacevine_mutation/flowering - name = "flowering" - hue = "#0A480D" - quality = NEGATIVE - severity = 10 - -/datum/spacevine_mutation/flowering/on_grow(obj/structure/spacevine/holder) - if(holder.energy == 2 && prob(severity) && !locate(/obj/structure/alien/resin/flower_bud_enemy) in range(5,holder)) - new /obj/structure/alien/resin/flower_bud_enemy(get_turf(holder)) - -/datum/spacevine_mutation/flowering/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(prob(25)) - holder.entangle(crosser) - - -/datum/spacevine_mutation/virulent_spread - name = "virulently spreading" - hue = "#FF8080" - quality = MINOR_NEGATIVE - -/datum/spacevine_mutation/virulent_spread/on_search(obj/structure/spacevine/holder) - return 1 - -// Sure, let's encourage crew members to deliberately breed a highly dangerous -// threat. What could *possibly* go wrong? ;) -/datum/spacevine_mutation/mineral - name = "metallic" - hue = "#444444" - quality = POSITIVE - severity = 3 - var/drop_rate = 20 - var/list/mineral_results = list( - /obj/item/stack/sheet/metal = 1 - ) - -/datum/spacevine_mutation/mineral/on_death(obj/structure/spacevine/holder) - if(!prob(drop_rate)) - return - var/itemtype = pickweight(mineral_results) - var/turf/pos = get_turf(holder) - new itemtype(pos, severity) - -/datum/spacevine_mutation/mineral/valuables - name = "glimmering" - hue = "#888800" - drop_rate = 10 - mineral_results = list( - /obj/item/stack/sheet/mineral/silver = 4, - /obj/item/stack/sheet/mineral/gold = 2, - /obj/item/stack/sheet/mineral/diamond = 1 - ) - -/datum/spacevine_mutation/mineral/glass - name = "glassy" - hue = "#8888FF" - mineral_results = list( - /obj/item/stack/sheet/glass = 1 - ) - -/datum/spacevine_mutation/mineral/plastic - name = "plasticine" - hue = "#222288" - mineral_results = list( - /obj/item/stack/sheet/plastic = 1 - ) - -/datum/spacevine_mutation/mineral/wood - name = "wooden" - hue = "#442200" - mineral_results = list( - /obj/item/stack/sheet/wood = 1 - ) - -// SPACE VINES (Note that this code is very similar to Biomass code) -/obj/structure/spacevine - name = "space vines" - desc = "An extremely expansionistic species of vine." - icon = 'icons/effects/spacevines.dmi' - icon_state = "Light1" - anchored = TRUE - density = FALSE - layer = SPACEVINE_LAYER - mouse_opacity = MOUSE_OPACITY_OPAQUE //Clicking anywhere on the turf is good enough - pass_flags = PASSTABLE | PASSGRILLE - max_integrity = 50 - var/energy = 0 - var/obj/structure/spacevine_controller/master = null - var/list/mutations = list() - -/obj/structure/spacevine/New() - ..() - color = "#ffffff" - -/obj/structure/spacevine/examine(mob/user) - . = ..() - var/text = "This one is a" - if(mutations.len) - for(var/A in mutations) - var/datum/spacevine_mutation/SM = A - text += " [SM.name]" - else - text += " normal" - text += " vine." - . += text - -/obj/structure/spacevine/proc/wither() - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_death(src) - qdel(src) - - -/obj/structure/spacevine/Destroy() - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_deletion(src) - if(master) - master.vines -= src - master.growth_queue -= src - if(!master.vines.len) - var/obj/item/seeds/kudzu/KZ = new(loc) - KZ.mutations |= mutations - KZ.set_potency(10 ** sqrt(master.mutativeness)) - KZ.set_production(10 - (master.spread_cap / 10)) - qdel(master) - master = null - mutations.Cut() - set_opacity(0) - if(has_buckled_mobs()) - unbuckle_all_mobs(force = TRUE) - return ..() - -/obj/structure/spacevine/proc/add_mutation(datum/spacevine_mutation/mutation) - mutations |= mutation - color = mutation.hue - -/obj/structure/spacevine/proc/on_chem_effect(datum/reagent/R) - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.on_chem(src, R) - if(!override && istype(R, /datum/reagent/glyphosate)) - if(prob(50)) - wither() - -/obj/structure/spacevine/proc/eat(mob/eater) - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.on_eat(src, eater) - if(!override) - if(prob(10)) - eater.say("Nom") - wither() - -/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user) - var/damage_dealt = I.force - if(istype(I, /obj/item/scythe)) - var/obj/item/scythe/S = I - if(S.extend) //so folded telescythes won't get damage boosts / insta-clears (they instead will instead be treated like non-scythes) - damage_dealt *= 4 - for(var/obj/structure/spacevine/B in range(1,src)) - if(B.obj_integrity > damage_dealt) //this only is going to occur for woodening mutation vines (increased health) or if we nerf scythe damage/multiplier - B.take_damage(damage_dealt, I.damtype, "melee", 1) - else - B.wither() - return - if(is_sharp(I)) - damage_dealt *= 4 - if(I.damtype == BURN) - damage_dealt *= 4 - - for(var/datum/spacevine_mutation/SM in mutations) - damage_dealt = SM.on_hit(src, user, I, damage_dealt) //on_hit now takes override damage as arg and returns new value for other mutations to permutate further - take_damage(damage_dealt, I.damtype, "melee", 1) - -/obj/structure/spacevine/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(src, 'sound/weapons/slash.ogg', 50, TRUE) - else - playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/structure/spacevine/obj_destruction() - wither() - -/obj/structure/spacevine/Crossed(mob/crosser, oldloc) - if(isliving(crosser)) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_cross(src, crosser) - -/obj/structure/spacevine/attack_hand(mob/user) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_hit(src, user) - user_unbuckle_mob(user, user) - -/obj/structure/spacevine/attack_alien(mob/living/user) - eat(user) - -/obj/structure/spacevine_controller - invisibility = 101 - var/list/obj/structure/spacevine/vines = list() - var/list/growth_queue = list() - var/spread_multiplier = 5 - var/spread_cap = 30 - var/list/mutations_list = list() - var/mutativeness = 1 - -/obj/structure/spacevine_controller/New(loc, list/muts, potency, production) - color = "#ffffff" - spawn_spacevine_piece(loc, , muts) - START_PROCESSING(SSobj, src) - init_subtypes(/datum/spacevine_mutation/, mutations_list) - if(potency != null && potency > 0) - // 1 mutativeness at 10 potency - // 4 mutativeness at 100 potency - mutativeness = log(10, potency) ** 2 - if(production != null) - // 1 production is crazy powerful - var/spread_value = max(10 - production, 1) - // 40 at 6 production - // 90 at 1 production - spread_cap = spread_value * 10 - // 6 vines/spread at 6 production - // ~2.5 vines/spread at 1 production - spread_multiplier /= spread_value / 5 - ..() - - -/obj/structure/spacevine_controller/ex_act() //only killing all vines will end this suffering - return - -/obj/structure/spacevine_controller/singularity_act() - return - -/obj/structure/spacevine_controller/singularity_pull() - return - -/obj/structure/spacevine_controller/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/structure/spacevine_controller/proc/spawn_spacevine_piece(turf/location, obj/structure/spacevine/parent, list/muts) - var/obj/structure/spacevine/SV = new(location) - growth_queue += SV - vines += SV - SV.master = src - if(muts && muts.len) - for(var/datum/spacevine_mutation/M in muts) - M.add_mutation_to_vinepiece(SV) - return - if(parent) - SV.mutations |= parent.mutations - SV.color = parent.color - if(prob(mutativeness)) - var/list/random_mutations_picked = mutations_list - SV.mutations - if(random_mutations_picked.len) - var/datum/spacevine_mutation/randmut = pick(random_mutations_picked) - randmut.add_mutation_to_vinepiece(SV) - - for(var/datum/spacevine_mutation/SM in SV.mutations) - SM.on_birth(SV) - -/obj/structure/spacevine_controller/process() - if(!vines || !vines.len) - qdel(src) //space vines exterminated. Remove the controller - return - if(!growth_queue) - qdel(src) //Sanity check - return - - var/length = 0 - - length = min( spread_cap , max( 1 , vines.len / spread_multiplier ) ) - var/i = 0 - var/list/obj/structure/spacevine/queue_end = list() - - for(var/obj/structure/spacevine/SV in growth_queue) - if(QDELETED(SV)) - continue - i++ - queue_end += SV - growth_queue -= SV - for(var/datum/spacevine_mutation/SM in SV.mutations) - SM.process_mutation(SV) - if(SV.energy < 2) //If tile isn't fully grown - if(prob(20)) - SV.grow() - else //If tile is fully grown - SV.entangle_mob() - - //if(prob(25)) - SV.spread() - if(i >= length) - break - - growth_queue = growth_queue + queue_end - -/obj/structure/spacevine/proc/grow() - if(!energy) - icon_state = pick("Med1", "Med2", "Med3") - energy = 1 - set_opacity(1) - else - icon_state = pick("Hvy1", "Hvy2", "Hvy3") - energy = 2 - - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_grow(src) - -/obj/structure/spacevine/proc/entangle_mob() - if(!has_buckled_mobs() && prob(25)) - for(var/mob/living/V in loc) - entangle(V) - if(has_buckled_mobs()) - break //only capture one mob at a time - - -/obj/structure/spacevine/proc/entangle(mob/living/V) - if(!V || isvineimmune(V)) - return - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_buckle(src, V) - if((V.stat != DEAD) && (V.buckled != src)) //not dead or captured - to_chat(V, "The vines [pick("wind", "tangle", "tighten")] around you!") - buckle_mob(V, 1) - -/obj/structure/spacevine/proc/spread() - var/list/dir_list = cardinal.Copy() - var/spread_search = FALSE // Whether to exhaustive search all 4 cardinal dirs for an open direction - for(var/datum/spacevine_mutation/SM in mutations) - spread_search |= SM.on_search(src) - while(dir_list.len) - var/direction = pick(dir_list) - dir_list -= direction - var/turf/stepturf = get_step(src,direction) - var/spread_success = FALSE - for(var/datum/spacevine_mutation/SM in mutations) - spread_success |= SM.on_spread(src, stepturf) // If this returns 1, spreading succeeded - if(!locate(/obj/structure/spacevine, stepturf)) - // snowflake for space turf, but space turf is super common and a big deal - if(!istype(stepturf, /turf/space) && stepturf.Enter(src)) - if(master) - master.spawn_spacevine_piece(stepturf, src) - spread_success = TRUE - if(spread_success || !spread_search) - break - -/obj/structure/spacevine/ex_act(severity) - var/i - for(var/datum/spacevine_mutation/SM in mutations) - i += SM.on_explosion(severity, src) - if(!i && prob(100/severity)) - wither() - -/obj/structure/spacevine/temperature_expose(null, temp, volume) - ..() - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.process_temperature(src, temp, volume) - if(!override) - wither() - -/obj/structure/spacevine/CanPass(atom/movable/mover, turf/target, height=0) - if(isvineimmune(mover)) - . = TRUE - else - . = ..() - -/proc/isvineimmune(atom/A) - . = FALSE - if(isliving(A)) - var/mob/living/M = A - if(("vines" in M.faction) || ("plants" in M.faction)) - . = TRUE +//Types of usual mutations +#define POSITIVE 1 +#define NEGATIVE 2 +#define MINOR_NEGATIVE 3 + +/datum/event/spacevine/start() + var/list/turfs = list() //list of all the empty floor turfs in the hallway areas + + var/obj/structure/spacevine/SV = new() + + for(var/area/hallway/A in world) + for(var/turf/F in A) + if(F.Enter(SV)) + turfs += F + + qdel(SV) + + if(turfs.len) //Pick a turf to spawn at if we can + var/turf/T = pick(turfs) + var/obj/structure/spacevine_controller/SC = new /obj/structure/spacevine_controller(T, , rand(30,70),rand(5,2)) //spawn a controller at turf + + // Make the event start fun - give the vine a random hostile mutation + if(SC.vines.len) + SV = SC.vines[1] + var/list/mutations = SC.mutations_list.Copy() + while(mutations.len) + var/datum/spacevine_mutation/SM = pick_n_take(mutations) + if(SM.quality == NEGATIVE && !SM.nofun) + SM.add_mutation_to_vinepiece(SV) + break + mutations.Cut() + mutations = null + + +/datum/spacevine_mutation + var/name = "" + var/severity = 1 + var/hue + var/quality + // For stuff that isn't fun as a random-event vine + var/nofun = FALSE + +/datum/spacevine_mutation/proc/add_mutation_to_vinepiece(obj/structure/spacevine/holder) + holder.mutations |= src + holder.color = hue + +/datum/spacevine_mutation/proc/remove_mutation_from_vinepiece(obj/structure/spacevine/holder) + holder.mutations -= src + var/datum/spacevine_mutation/oldmutation + if(holder.mutations.len) + oldmutation = pick(holder.mutations) + holder.color = oldmutation.hue + else + holder.color = "" + +/datum/spacevine_mutation/proc/process_mutation(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/process_temperature(obj/structure/spacevine/holder, temp, volume) + return + +/datum/spacevine_mutation/proc/on_birth(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_grow(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_death(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_deletion(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) + . = expected_damage + +/datum/spacevine_mutation/proc/on_cross(obj/structure/spacevine/holder, mob/crosser) + return + +/datum/spacevine_mutation/proc/on_chem(obj/structure/spacevine/holder, datum/reagent/R) + return + +/datum/spacevine_mutation/proc/on_eat(obj/structure/spacevine/holder, mob/living/eater) + return + +/datum/spacevine_mutation/proc/on_spread(obj/structure/spacevine/holder, turf/target) + return + +/datum/spacevine_mutation/proc/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) + return + +/datum/spacevine_mutation/proc/on_explosion(severity, obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_search(severity, obj/structure/spacevine/holder) + return + + +/datum/spacevine_mutation/space_covering + name = "space protective" + hue = "#aa77aa" + quality = POSITIVE + +/turf/simulated/floor/vines + color = "#aa77aa" + icon_state = "vinefloor" + broken_states = list() + + +//All of this shit is useless for vines + +/turf/simulated/floor/vines/attackby() + return + +/turf/simulated/floor/vines/burn_tile() + return + +/turf/simulated/floor/vines/break_tile() + return + +/turf/simulated/floor/vines/make_plating() + return + +/turf/simulated/floor/vines/break_tile_to_plating() + return + +/turf/simulated/floor/vines/ex_act(severity) + if(severity < 3) + ChangeTurf(baseturf) + +/turf/simulated/floor/vines/narsie_act() + if(prob(20)) + ChangeTurf(baseturf) //nar sie eats this shit + +/turf/simulated/floor/vines/singularity_pull(S, current_size) + if(current_size >= STAGE_FIVE) + if(prob(50)) + ChangeTurf(baseturf) + +/turf/simulated/floor/vines/ChangeTurf(turf/simulated/floor/T, defer_change = FALSE, keep_icon = TRUE, ignore_air = FALSE) + . = ..() + //Do this *after* the turf has changed as qdel in spacevines will call changeturf again if it hasn't + for(var/obj/structure/spacevine/SV in src) + SV.wither() + +/datum/spacevine_mutation/space_covering + var/static/list/coverable_turfs + +/datum/spacevine_mutation/space_covering/New() + . = ..() + if(!coverable_turfs) + coverable_turfs = typecacheof(list( + /turf/space + )) + coverable_turfs -= typecacheof(list( + /turf/space/transit + )) + +/datum/spacevine_mutation/space_covering/on_grow(obj/structure/spacevine/holder) + process_mutation(holder) + +/datum/spacevine_mutation/space_covering/on_spread(obj/structure/spacevine/holder, turf/target) + if(target.type == /turf/space && !locate(/obj/structure/spacevine) in target) + holder.master.spawn_spacevine_piece(target, holder) + . = TRUE + +/datum/spacevine_mutation/space_covering/process_mutation(obj/structure/spacevine/holder) + var/turf/T = get_turf(holder) + if(is_type_in_typecache(T, coverable_turfs)) + var/currtype = T.type + T.ChangeTurf(/turf/simulated/floor/vines) + T.baseturf = currtype + +/datum/spacevine_mutation/space_covering/on_deletion(obj/structure/spacevine/holder) + var/turf/T = get_turf(holder) + if(istype(T, /turf/simulated/floor/vines)) + T.ChangeTurf(T.baseturf) + +/datum/spacevine_mutation/bluespace + name = "bluespace" + hue = "#3333ff" + quality = MINOR_NEGATIVE + +/datum/spacevine_mutation/bluespace/on_spread(obj/structure/spacevine/holder, turf/target) + if(holder.energy > 1 && !locate(/obj/structure/spacevine) in target) + // Lose bluespace upon piercing a single tile, and drop it from our own mutations too + // Representing a loss in "high potential" + // also conveniently prevents this from spreading too crazily + remove_mutation_from_vinepiece(holder) + holder.master.spawn_spacevine_piece(target, holder) + playsound(holder, 'sound/misc/interference.ogg', 50, 1) + . = TRUE + +/datum/spacevine_mutation/light + name = "light" + hue = "#ffff00" + quality = POSITIVE + severity = 4 + +/datum/spacevine_mutation/light/on_grow(obj/structure/spacevine/holder) + if(holder.energy) + holder.set_light(severity) + +/datum/spacevine_mutation/toxicity + name = "toxic" + hue = "#ff00ff" + severity = 10 + quality = NEGATIVE + +/datum/spacevine_mutation/toxicity/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(issilicon(crosser)) + return + if(prob(severity) && istype(crosser) && !isvineimmune(crosser)) + to_chat(crosser, "You accidently touch the vine and feel a strange sensation.") + crosser.adjustToxLoss(5) + +/datum/spacevine_mutation/toxicity/on_eat(obj/structure/spacevine/holder, mob/living/eater) + if(!isvineimmune(eater)) + eater.adjustToxLoss(5) + +/datum/spacevine_mutation/explosive //OH SHIT IT CAN CHAINREACT RUN!!! + name = "explosive" + hue = "#ff0000" + quality = NEGATIVE + severity = 2 + // kaboom events aren't fun + nofun = TRUE + +/datum/spacevine_mutation/explosive/on_explosion(explosion_severity, obj/structure/spacevine/holder) + if(explosion_severity < 3) + qdel(holder) + else + . = 1 + spawn(5) + holder.wither() + +/datum/spacevine_mutation/explosive/on_death(obj/structure/spacevine/holder, mob/hitter, obj/item/I) + explosion(holder.loc, 0, 0, severity, 0, 0) + +/datum/spacevine_mutation/fire_proof + name = "fire proof" + hue = "#ff8888" + quality = MINOR_NEGATIVE + +/datum/spacevine_mutation/fire_proof/process_temperature(obj/structure/spacevine/holder, temp, volume) + return 1 + +/datum/spacevine_mutation/fire_proof/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) + if(I && I.damtype == "fire") + . = 0 + else + . = expected_damage + +/datum/spacevine_mutation/vine_eating + name = "vine eating" + hue = "#ff7700" + quality = MINOR_NEGATIVE + +/datum/spacevine_mutation/vine_eating/on_spread(obj/structure/spacevine/holder, turf/target) + var/obj/structure/spacevine/prey = locate() in target + if(prey && !prey.mutations.Find(src)) //Eat all vines that are not of the same origin + prey.wither() + . = TRUE + +/datum/spacevine_mutation/aggressive_spread //very OP, but im out of other ideas currently + name = "aggressive spreading" + hue = "#333333" + severity = 3 + quality = NEGATIVE + +/datum/spacevine_mutation/aggressive_spread/on_spread(obj/structure/spacevine/holder, turf/target) + if(istype(target, /turf/simulated/wall/r_wall)) + // Too tough to pierce - should lead to interesting spread patterns + return + // Bust through windows or other stuff blocking the way + if(!target.Enter(holder)) + for(var/atom/movable/AM in target) + if(istype(AM, /obj/structure/spacevine) || !AM.density) + continue + AM.ex_act(severity) + target.ex_act(severity) // vine immunity handled at /mob/ex_act + . = TRUE + +/datum/spacevine_mutation/aggressive_spread/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) + buckled.ex_act(severity) + +/datum/spacevine_mutation/transparency + name = "transparent" + hue = "" + quality = POSITIVE + +/datum/spacevine_mutation/transparency/on_grow(obj/structure/spacevine/holder) + holder.set_opacity(0) + holder.alpha = 125 + +/datum/spacevine_mutation/thorns + name = "thorny" + hue = "#666666" + severity = 10 + quality = NEGATIVE + +/datum/spacevine_mutation/thorns/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(prob(severity) && istype(crosser) && !isvineimmune(holder)) + var/mob/living/M = crosser + M.adjustBruteLoss(5) + to_chat(M, "You cut yourself on the thorny vines.") + +/datum/spacevine_mutation/thorns/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) + if(prob(severity) && istype(hitter) && !isvineimmune(holder)) + var/mob/living/M = hitter + M.adjustBruteLoss(5) + to_chat(M, "You cut yourself on the thorny vines.") + . = expected_damage + +/datum/spacevine_mutation/woodening + name = "hardened" + hue = "#997700" + quality = NEGATIVE + +/datum/spacevine_mutation/woodening/on_grow(obj/structure/spacevine/holder) + if(holder.energy) + holder.density = TRUE + holder.max_integrity = 100 + holder.obj_integrity = holder.max_integrity + +/datum/spacevine_mutation/woodening/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) + if(!is_sharp(I)) + . = expected_damage * 0.5 + else + . = expected_damage + +/datum/spacevine_mutation/flowering + name = "flowering" + hue = "#0A480D" + quality = NEGATIVE + severity = 10 + +/datum/spacevine_mutation/flowering/on_grow(obj/structure/spacevine/holder) + if(holder.energy == 2 && prob(severity) && !locate(/obj/structure/alien/resin/flower_bud_enemy) in range(5,holder)) + new /obj/structure/alien/resin/flower_bud_enemy(get_turf(holder)) + +/datum/spacevine_mutation/flowering/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(prob(25)) + holder.entangle(crosser) + + +/datum/spacevine_mutation/virulent_spread + name = "virulently spreading" + hue = "#FF8080" + quality = MINOR_NEGATIVE + +/datum/spacevine_mutation/virulent_spread/on_search(obj/structure/spacevine/holder) + return 1 + +// Sure, let's encourage crew members to deliberately breed a highly dangerous +// threat. What could *possibly* go wrong? ;) +/datum/spacevine_mutation/mineral + name = "metallic" + hue = "#444444" + quality = POSITIVE + severity = 3 + var/drop_rate = 20 + var/list/mineral_results = list( + /obj/item/stack/sheet/metal = 1 + ) + +/datum/spacevine_mutation/mineral/on_death(obj/structure/spacevine/holder) + if(!prob(drop_rate)) + return + var/itemtype = pickweight(mineral_results) + var/turf/pos = get_turf(holder) + new itemtype(pos, severity) + +/datum/spacevine_mutation/mineral/valuables + name = "glimmering" + hue = "#888800" + drop_rate = 10 + mineral_results = list( + /obj/item/stack/sheet/mineral/silver = 4, + /obj/item/stack/sheet/mineral/gold = 2, + /obj/item/stack/sheet/mineral/diamond = 1 + ) + +/datum/spacevine_mutation/mineral/glass + name = "glassy" + hue = "#8888FF" + mineral_results = list( + /obj/item/stack/sheet/glass = 1 + ) + +/datum/spacevine_mutation/mineral/plastic + name = "plasticine" + hue = "#222288" + mineral_results = list( + /obj/item/stack/sheet/plastic = 1 + ) + +/datum/spacevine_mutation/mineral/wood + name = "wooden" + hue = "#442200" + mineral_results = list( + /obj/item/stack/sheet/wood = 1 + ) + +// SPACE VINES (Note that this code is very similar to Biomass code) +/obj/structure/spacevine + name = "space vines" + desc = "An extremely expansionistic species of vine." + icon = 'icons/effects/spacevines.dmi' + icon_state = "Light1" + anchored = TRUE + density = FALSE + layer = SPACEVINE_LAYER + mouse_opacity = MOUSE_OPACITY_OPAQUE //Clicking anywhere on the turf is good enough + pass_flags = PASSTABLE | PASSGRILLE + max_integrity = 50 + var/energy = 0 + var/obj/structure/spacevine_controller/master = null + var/list/mutations = list() + +/obj/structure/spacevine/New() + ..() + color = "#ffffff" + +/obj/structure/spacevine/examine(mob/user) + . = ..() + var/text = "This one is a" + if(mutations.len) + for(var/A in mutations) + var/datum/spacevine_mutation/SM = A + text += " [SM.name]" + else + text += " normal" + text += " vine." + . += text + +/obj/structure/spacevine/proc/wither() + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_death(src) + qdel(src) + + +/obj/structure/spacevine/Destroy() + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_deletion(src) + if(master) + master.vines -= src + master.growth_queue -= src + if(!master.vines.len) + var/obj/item/seeds/kudzu/KZ = new(loc) + KZ.mutations |= mutations + KZ.set_potency(10 ** sqrt(master.mutativeness)) + KZ.set_production(10 - (master.spread_cap / 10)) + qdel(master) + master = null + mutations.Cut() + set_opacity(0) + if(has_buckled_mobs()) + unbuckle_all_mobs(force = TRUE) + return ..() + +/obj/structure/spacevine/proc/add_mutation(datum/spacevine_mutation/mutation) + mutations |= mutation + color = mutation.hue + +/obj/structure/spacevine/proc/on_chem_effect(datum/reagent/R) + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.on_chem(src, R) + if(!override && istype(R, /datum/reagent/glyphosate)) + if(prob(50)) + wither() + +/obj/structure/spacevine/proc/eat(mob/eater) + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.on_eat(src, eater) + if(!override) + if(prob(10)) + eater.say("Nom") + wither() + +/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user) + var/damage_dealt = I.force + if(istype(I, /obj/item/scythe)) + var/obj/item/scythe/S = I + if(S.extend) //so folded telescythes won't get damage boosts / insta-clears (they instead will instead be treated like non-scythes) + damage_dealt *= 4 + for(var/obj/structure/spacevine/B in range(1,src)) + if(B.obj_integrity > damage_dealt) //this only is going to occur for woodening mutation vines (increased health) or if we nerf scythe damage/multiplier + B.take_damage(damage_dealt, I.damtype, "melee", 1) + else + B.wither() + return + if(is_sharp(I)) + damage_dealt *= 4 + if(I.damtype == BURN) + damage_dealt *= 4 + + for(var/datum/spacevine_mutation/SM in mutations) + damage_dealt = SM.on_hit(src, user, I, damage_dealt) //on_hit now takes override damage as arg and returns new value for other mutations to permutate further + take_damage(damage_dealt, I.damtype, "melee", 1) + +/obj/structure/spacevine/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(src, 'sound/weapons/slash.ogg', 50, TRUE) + else + playsound(src, 'sound/weapons/tap.ogg', 50, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/structure/spacevine/obj_destruction() + wither() + +/obj/structure/spacevine/Crossed(mob/crosser, oldloc) + if(isliving(crosser)) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_cross(src, crosser) + +/obj/structure/spacevine/attack_hand(mob/user) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_hit(src, user) + user_unbuckle_mob(user, user) + +/obj/structure/spacevine/attack_alien(mob/living/user) + eat(user) + +/obj/structure/spacevine_controller + invisibility = 101 + var/list/obj/structure/spacevine/vines = list() + var/list/growth_queue = list() + var/spread_multiplier = 5 + var/spread_cap = 30 + var/list/mutations_list = list() + var/mutativeness = 1 + +/obj/structure/spacevine_controller/New(loc, list/muts, potency, production) + color = "#ffffff" + spawn_spacevine_piece(loc, , muts) + START_PROCESSING(SSobj, src) + init_subtypes(/datum/spacevine_mutation/, mutations_list) + if(potency != null && potency > 0) + // 1 mutativeness at 10 potency + // 4 mutativeness at 100 potency + mutativeness = log(10, potency) ** 2 + if(production != null) + // 1 production is crazy powerful + var/spread_value = max(10 - production, 1) + // 40 at 6 production + // 90 at 1 production + spread_cap = spread_value * 10 + // 6 vines/spread at 6 production + // ~2.5 vines/spread at 1 production + spread_multiplier /= spread_value / 5 + ..() + + +/obj/structure/spacevine_controller/ex_act() //only killing all vines will end this suffering + return + +/obj/structure/spacevine_controller/singularity_act() + return + +/obj/structure/spacevine_controller/singularity_pull() + return + +/obj/structure/spacevine_controller/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/structure/spacevine_controller/proc/spawn_spacevine_piece(turf/location, obj/structure/spacevine/parent, list/muts) + var/obj/structure/spacevine/SV = new(location) + growth_queue += SV + vines += SV + SV.master = src + if(muts && muts.len) + for(var/datum/spacevine_mutation/M in muts) + M.add_mutation_to_vinepiece(SV) + return + if(parent) + SV.mutations |= parent.mutations + SV.color = parent.color + if(prob(mutativeness)) + var/list/random_mutations_picked = mutations_list - SV.mutations + if(random_mutations_picked.len) + var/datum/spacevine_mutation/randmut = pick(random_mutations_picked) + randmut.add_mutation_to_vinepiece(SV) + + for(var/datum/spacevine_mutation/SM in SV.mutations) + SM.on_birth(SV) + +/obj/structure/spacevine_controller/process() + if(!vines || !vines.len) + qdel(src) //space vines exterminated. Remove the controller + return + if(!growth_queue) + qdel(src) //Sanity check + return + + var/length = 0 + + length = min( spread_cap , max( 1 , vines.len / spread_multiplier ) ) + var/i = 0 + var/list/obj/structure/spacevine/queue_end = list() + + for(var/obj/structure/spacevine/SV in growth_queue) + if(QDELETED(SV)) + continue + i++ + queue_end += SV + growth_queue -= SV + for(var/datum/spacevine_mutation/SM in SV.mutations) + SM.process_mutation(SV) + if(SV.energy < 2) //If tile isn't fully grown + if(prob(20)) + SV.grow() + else //If tile is fully grown + SV.entangle_mob() + + //if(prob(25)) + SV.spread() + if(i >= length) + break + + growth_queue = growth_queue + queue_end + +/obj/structure/spacevine/proc/grow() + if(!energy) + icon_state = pick("Med1", "Med2", "Med3") + energy = 1 + set_opacity(1) + else + icon_state = pick("Hvy1", "Hvy2", "Hvy3") + energy = 2 + + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_grow(src) + +/obj/structure/spacevine/proc/entangle_mob() + if(!has_buckled_mobs() && prob(25)) + for(var/mob/living/V in loc) + entangle(V) + if(has_buckled_mobs()) + break //only capture one mob at a time + + +/obj/structure/spacevine/proc/entangle(mob/living/V) + if(!V || isvineimmune(V)) + return + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_buckle(src, V) + if((V.stat != DEAD) && (V.buckled != src)) //not dead or captured + to_chat(V, "The vines [pick("wind", "tangle", "tighten")] around you!") + buckle_mob(V, 1) + +/obj/structure/spacevine/proc/spread() + var/list/dir_list = cardinal.Copy() + var/spread_search = FALSE // Whether to exhaustive search all 4 cardinal dirs for an open direction + for(var/datum/spacevine_mutation/SM in mutations) + spread_search |= SM.on_search(src) + while(dir_list.len) + var/direction = pick(dir_list) + dir_list -= direction + var/turf/stepturf = get_step(src,direction) + var/spread_success = FALSE + for(var/datum/spacevine_mutation/SM in mutations) + spread_success |= SM.on_spread(src, stepturf) // If this returns 1, spreading succeeded + if(!locate(/obj/structure/spacevine, stepturf)) + // snowflake for space turf, but space turf is super common and a big deal + if(!istype(stepturf, /turf/space) && stepturf.Enter(src)) + if(master) + master.spawn_spacevine_piece(stepturf, src) + spread_success = TRUE + if(spread_success || !spread_search) + break + +/obj/structure/spacevine/ex_act(severity) + var/i + for(var/datum/spacevine_mutation/SM in mutations) + i += SM.on_explosion(severity, src) + if(!i && prob(100/severity)) + wither() + +/obj/structure/spacevine/temperature_expose(null, temp, volume) + ..() + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.process_temperature(src, temp, volume) + if(!override) + wither() + +/obj/structure/spacevine/CanPass(atom/movable/mover, turf/target, height=0) + if(isvineimmune(mover)) + . = TRUE + else + . = ..() + +/proc/isvineimmune(atom/A) + . = FALSE + if(isliving(A)) + var/mob/living/M = A + if(("vines" in M.faction) || ("plants" in M.faction)) + . = TRUE diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index 90cc1ee28ad..2a572da8042 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -1,29 +1,29 @@ -/var/global/sent_spiders_to_station = 0 - -/datum/event/spider_infestation - announceWhen = 400 - var/spawncount = 1 - -/datum/event/spider_infestation/setup() - announceWhen = rand(announceWhen, announceWhen + 50) - spawncount = round(num_players() * 0.8) - sent_spiders_to_station = 1 - -/datum/event/spider_infestation/announce() - event_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg') - -/datum/event/spider_infestation/start() - - var/list/vents = list() - for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in world) - if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) - if(temp_vent.parent.other_atmosmch.len > 50) - vents += temp_vent - - while((spawncount >= 1) && vents.len) - var/obj/vent = pick(vents) - var/obj/structure/spider/spiderling/S = new(vent.loc) - if(prob(66)) - S.grow_as = /mob/living/simple_animal/hostile/poison/giant_spider/nurse - vents -= vent - spawncount-- +/var/global/sent_spiders_to_station = 0 + +/datum/event/spider_infestation + announceWhen = 400 + var/spawncount = 1 + +/datum/event/spider_infestation/setup() + announceWhen = rand(announceWhen, announceWhen + 50) + spawncount = round(num_players() * 0.8) + sent_spiders_to_station = 1 + +/datum/event/spider_infestation/announce() + event_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg') + +/datum/event/spider_infestation/start() + + var/list/vents = list() + for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in world) + if(is_station_level(temp_vent.loc.z) && !temp_vent.welded) + if(temp_vent.parent.other_atmosmch.len > 50) + vents += temp_vent + + while((spawncount >= 1) && vents.len) + var/obj/vent = pick(vents) + var/obj/structure/spider/spiderling/S = new(vent.loc) + if(prob(66)) + S.grow_as = /mob/living/simple_animal/hostile/poison/giant_spider/nurse + vents -= vent + spawncount-- diff --git a/code/modules/events/tear.dm b/code/modules/events/tear.dm index 1cbabb5e45b..4ac737dde91 100644 --- a/code/modules/events/tear.dm +++ b/code/modules/events/tear.dm @@ -47,4 +47,4 @@ S.faction |= "chemicalsummon" if(prob(50)) for(var/j = 1, j <= rand(1, 3), j++) - step(S, pick(NORTH, SOUTH, EAST, WEST)) \ No newline at end of file + step(S, pick(NORTH, SOUTH, EAST, WEST)) diff --git a/code/modules/events/tear_honk.dm b/code/modules/events/tear_honk.dm index 392ef0cd571..c563902937f 100644 --- a/code/modules/events/tear_honk.dm +++ b/code/modules/events/tear_honk.dm @@ -22,4 +22,4 @@ var/mob/living/simple_animal/hostile/retaliate/clown/goblin/G = new(get_turf(src)) if(prob(50)) for(var/j = 1, j <= rand(1, 3), j++) - step(G, pick(NORTH, SOUTH, EAST, WEST)) \ No newline at end of file + step(G, pick(NORTH, SOUTH, EAST, WEST)) diff --git a/code/modules/events/wallrot.dm b/code/modules/events/wallrot.dm index 84c0ee211c4..e43987bcece 100644 --- a/code/modules/events/wallrot.dm +++ b/code/modules/events/wallrot.dm @@ -28,4 +28,4 @@ // Only rot up to severity walls if(rotcount >= actual_severity) - break \ No newline at end of file + break diff --git a/code/modules/examine/descriptions/medical.dm b/code/modules/examine/descriptions/medical.dm index d56e4c8b163..728a5f72c61 100644 --- a/code/modules/examine/descriptions/medical.dm +++ b/code/modules/examine/descriptions/medical.dm @@ -39,4 +39,4 @@ You can also inject common medicines directly into their bloodstream.\
        \ Right-click the cell and click 'Eject Occupant' to remove them. You can enter the cell yourself by right clicking and selecting 'Enter Sleeper'. \ - Note that you cannot control the sleeper while inside of it." \ No newline at end of file + Note that you cannot control the sleeper while inside of it." diff --git a/code/modules/examine/descriptions/stacks.dm b/code/modules/examine/descriptions/stacks.dm index 6b47c1ab421..f4e393195fa 100644 --- a/code/modules/examine/descriptions/stacks.dm +++ b/code/modules/examine/descriptions/stacks.dm @@ -21,4 +21,4 @@ You can replenish your supply of metal as a synthetic by recharging." /obj/item/stack/sheet - description_info = "Use in your hand to bring up the recipe menu. If you have enough sheets, click on something on the list to build it." \ No newline at end of file + description_info = "Use in your hand to bring up the recipe menu. If you have enough sheets, click on something on the list to build it." diff --git a/code/modules/examine/descriptions/turfs.dm b/code/modules/examine/descriptions/turfs.dm index cccc43e0731..81aea3eb755 100644 --- a/code/modules/examine/descriptions/turfs.dm +++ b/code/modules/examine/descriptions/turfs.dm @@ -1,3 +1,3 @@ /turf/simulated/wall description_info = "You can deconstruct this by welding it, and then wrenching the girder.
        \ - You can build a wall by using metal sheets and making a girder, then adding more metal or plasteel." \ No newline at end of file + You can build a wall by using metal sheets and making a girder, then adding more metal or plasteel." diff --git a/code/modules/fancytitle/fancytitle.dm b/code/modules/fancytitle/fancytitle.dm index e85f1f59ec5..edc1c0e246c 100644 --- a/code/modules/fancytitle/fancytitle.dm +++ b/code/modules/fancytitle/fancytitle.dm @@ -30,4 +30,4 @@ for(var/turf/unsimulated/wall/splashscreen/splash in world) splash.icon = icon return TRUE - return FALSE \ No newline at end of file + return FALSE diff --git a/code/modules/fish/fish_types.dm b/code/modules/fish/fish_types.dm index ff09dcd7785..86b87373422 100644 --- a/code/modules/fish/fish_types.dm +++ b/code/modules/fish/fish_types.dm @@ -73,4 +73,4 @@ fish_name = "electric eel" egg_item = /obj/item/fish_eggs/electric_eel fish_item = /obj/item/fish/electric_eel - crossbreeder = 0 \ No newline at end of file + crossbreeder = 0 diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index ee28919fd67..3bae8a79933 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -1055,4 +1055,4 @@ var/list/non_fakeattack_weapons = list(/obj/item/gun/projectile, /obj/item/ammo_ spawn(rand(30,50)) //Only seen for a brief moment. if(client) client.images -= halbody - halbody = null \ No newline at end of file + halbody = null diff --git a/code/modules/food_and_drinks/drinks/bottler/bottler.dm b/code/modules/food_and_drinks/drinks/bottler/bottler.dm index 39d175968d4..b1667fc6a00 100644 --- a/code/modules/food_and_drinks/drinks/bottler/bottler.dm +++ b/code/modules/food_and_drinks/drinks/bottler/bottler.dm @@ -409,4 +409,4 @@ /obj/machinery/bottler/proc/resetSlots() QDEL_LIST_ASSOC_VAL(slots) - slots.len = 3 \ No newline at end of file + slots.len = 3 diff --git a/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm b/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm index adee1f7d3c2..5f13f5c7a06 100644 --- a/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm +++ b/code/modules/food_and_drinks/drinks/drinks/drinkingglass.dm @@ -65,4 +65,4 @@ list_reagents = list("devilskiss" = 50) /obj/item/reagent_containers/food/drinks/drinkingglass/alliescocktail - list_reagents = list("alliescocktail" = 25, "omnizine" = 25) \ No newline at end of file + list_reagents = list("alliescocktail" = 25, "omnizine" = 25) diff --git a/code/modules/food_and_drinks/food.dm b/code/modules/food_and_drinks/food.dm index ea4f09b0aae..27f0919a3ce 100644 --- a/code/modules/food_and_drinks/food.dm +++ b/code/modules/food_and_drinks/food.dm @@ -51,4 +51,4 @@ ant_location = T if(ant_timer) deltimer(ant_timer) - ant_timer = addtimer(CALLBACK(src, .proc/check_for_ants), 3000, TIMER_STOPPABLE) \ No newline at end of file + ant_timer = addtimer(CALLBACK(src, .proc/check_for_ants), 3000, TIMER_STOPPABLE) diff --git a/code/modules/food_and_drinks/food/foods/pizza.dm b/code/modules/food_and_drinks/food/foods/pizza.dm index 1f1092dd666..d0c17648fed 100644 --- a/code/modules/food_and_drinks/food/foods/pizza.dm +++ b/code/modules/food_and_drinks/food/foods/pizza.dm @@ -270,4 +270,4 @@ /obj/item/pizzabox/hawaiian/New() ..() pizza = new /obj/item/reagent_containers/food/snacks/sliceable/pizza/hawaiianpizza(src) - boxtag = "Hawaiian feast" \ No newline at end of file + boxtag = "Hawaiian feast" diff --git a/code/modules/food_and_drinks/food/foods/soups.dm b/code/modules/food_and_drinks/food/foods/soups.dm index 6d725037742..9b07945f424 100644 --- a/code/modules/food_and_drinks/food/foods/soups.dm +++ b/code/modules/food_and_drinks/food/foods/soups.dm @@ -179,4 +179,4 @@ trash = /obj/item/trash/snack_bowl bitesize = 5 list_reagents = list("nutriment" = 5, "frostoil" = 1, "tomatojuice" = 2, "vitamin" = 2) - tastes = list("tomato" = 1, "mint" = 1) \ No newline at end of file + tastes = list("tomato" = 1, "mint" = 1) diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm index 7cab43b14c1..14089cfb671 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm @@ -94,7 +94,7 @@ /datum/deepfryer_special/proc/validate(obj/item/I) return istype(I, input) - + /datum/deepfryer_special/shrimp input = /obj/item/reagent_containers/food/snacks/shrimp output = /obj/item/reagent_containers/food/snacks/fried_shrimp @@ -135,4 +135,4 @@ if(!..()) return FALSE var/obj/item/organ/external/E = I - return istype(E.dna.species, /datum/species/vox) \ No newline at end of file + return istype(E.dna.species, /datum/species/vox) diff --git a/code/modules/food_and_drinks/kitchen_machinery/grill_new.dm b/code/modules/food_and_drinks/kitchen_machinery/grill_new.dm index 589b3b25559..44b1c0e5f26 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/grill_new.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/grill_new.dm @@ -58,4 +58,4 @@ add_attack_logs(user, G.affecting, "Burned with [src]") qdel(G) //Removes the grip to prevent rapid sears and give you a chance to run return 0 - return 0 \ No newline at end of file + return 0 diff --git a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat_2.dm b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat_2.dm index f1e03da035b..e6882950458 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat_2.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat_2.dm @@ -253,4 +253,4 @@ var/list/ingredients_source = list( #undef INGR_FLOUR #undef INGR_SUGAR #undef INGR_ICE -#undef MUCK \ No newline at end of file +#undef MUCK diff --git a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm index b1622a4bdc7..e353b2ac30d 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm @@ -53,4 +53,4 @@ /obj/machinery/microwave/New() new /obj/machinery/kitchen_machine/microwave(get_turf(src)) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_table.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_table.dm index 2a2ee410522..db7774fa41f 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_table.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_table.dm @@ -297,4 +297,4 @@ ) result = /mob/living/simple_animal/pet/cat/cak category = CAT_FOOD - subcategory = CAT_CAKE //Cat! Haha, get it? CAT? GET IT? We get it - Love Felines -Foxes are better \ No newline at end of file + subcategory = CAT_CAKE //Cat! Haha, get it? CAT? GET IT? We get it - Love Felines -Foxes are better diff --git a/code/modules/hydroponics/beekeeping/beebox.dm b/code/modules/hydroponics/beekeeping/beebox.dm index 4f9daa1ea92..612146e579a 100644 --- a/code/modules/hydroponics/beekeeping/beebox.dm +++ b/code/modules/hydroponics/beekeeping/beebox.dm @@ -272,4 +272,4 @@ qdel(src) /obj/structure/beebox/unwrenched - anchored = FALSE \ No newline at end of file + anchored = FALSE diff --git a/code/modules/hydroponics/beekeeping/honeycomb.dm b/code/modules/hydroponics/beekeeping/honeycomb.dm index 50bbae4a6c4..23bc55dfea2 100644 --- a/code/modules/hydroponics/beekeeping/honeycomb.dm +++ b/code/modules/hydroponics/beekeeping/honeycomb.dm @@ -37,4 +37,4 @@ reagents.add_reagent(R.id,5) else honey_color = "" - update_icon() \ No newline at end of file + update_icon() diff --git a/code/modules/hydroponics/grown/ambrosia.dm b/code/modules/hydroponics/grown/ambrosia.dm index 1223dd9dba7..2da9d0163d2 100644 --- a/code/modules/hydroponics/grown/ambrosia.dm +++ b/code/modules/hydroponics/grown/ambrosia.dm @@ -92,4 +92,4 @@ /obj/item/reagent_containers/food/snacks/grown/ambrosia/cruciatus seed = /obj/item/seeds/ambrosia/cruciatus wine_power = 0.7 - tastes = list("ambrosia cruciatus" = 1) \ No newline at end of file + tastes = list("ambrosia cruciatus" = 1) diff --git a/code/modules/hydroponics/grown/beans.dm b/code/modules/hydroponics/grown/beans.dm index 8d9702f1343..3482fd89ecb 100644 --- a/code/modules/hydroponics/grown/beans.dm +++ b/code/modules/hydroponics/grown/beans.dm @@ -49,4 +49,4 @@ filling_color = "#F0E68C" bitesize_mod = 2 tastes = list("koi" = 1) - wine_power = 0.4 \ No newline at end of file + wine_power = 0.4 diff --git a/code/modules/hydroponics/grown/cocoa_vanilla.dm b/code/modules/hydroponics/grown/cocoa_vanilla.dm index 744300d9daa..ad80934ca18 100644 --- a/code/modules/hydroponics/grown/cocoa_vanilla.dm +++ b/code/modules/hydroponics/grown/cocoa_vanilla.dm @@ -47,4 +47,4 @@ icon_state = "vanillapod" filling_color = "#FFD700" tastes = list("vanilla" = 1) - distill_reagent = "vanilla" //Takes longer, but you can get even more vanilla from it. \ No newline at end of file + distill_reagent = "vanilla" //Takes longer, but you can get even more vanilla from it. diff --git a/code/modules/hydroponics/grown/cotton.dm b/code/modules/hydroponics/grown/cotton.dm index 590831a1f9b..3f864e45328 100644 --- a/code/modules/hydroponics/grown/cotton.dm +++ b/code/modules/hydroponics/grown/cotton.dm @@ -77,4 +77,4 @@ throw_range = 3 attack_verb = list("bashed", "battered", "bludgeoned", "whacked") cotton_type = /obj/item/stack/sheet/cotton/durathread - cotton_name = "raw durathread" \ No newline at end of file + cotton_name = "raw durathread" diff --git a/code/modules/hydroponics/grown/eggplant.dm b/code/modules/hydroponics/grown/eggplant.dm index c1fad32bed8..7aa15c61fa9 100644 --- a/code/modules/hydroponics/grown/eggplant.dm +++ b/code/modules/hydroponics/grown/eggplant.dm @@ -47,4 +47,4 @@ filling_color = "#F8F8FF" bitesize_mod = 2 tastes = list("egg-plant" = 1) - distill_reagent = "eggnog" \ No newline at end of file + distill_reagent = "eggnog" diff --git a/code/modules/hydroponics/grown/garlic.dm b/code/modules/hydroponics/grown/garlic.dm index eccc9dea1f2..3c1b2b38b1d 100644 --- a/code/modules/hydroponics/grown/garlic.dm +++ b/code/modules/hydroponics/grown/garlic.dm @@ -19,4 +19,4 @@ filling_color = "#C0C9A0" bitesize_mod = 2 tastes = list("garlic" = 1) - wine_power = 0.1 \ No newline at end of file + wine_power = 0.1 diff --git a/code/modules/hydroponics/grown/herbals.dm b/code/modules/hydroponics/grown/herbals.dm index 6a0ad441ad3..051c8685b69 100644 --- a/code/modules/hydroponics/grown/herbals.dm +++ b/code/modules/hydroponics/grown/herbals.dm @@ -52,4 +52,4 @@ A.heal_burn = seed.potency to_chat(user, "You mash [src] into a poultice.") user.drop_item() - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/hydroponics/grown/misc.dm b/code/modules/hydroponics/grown/misc.dm index d13b7a11e5b..2631f20e93a 100644 --- a/code/modules/hydroponics/grown/misc.dm +++ b/code/modules/hydroponics/grown/misc.dm @@ -161,4 +161,4 @@ /obj/item/reagent_containers/food/snacks/grown/cherry_bomb/proc/prime() icon_state = "cherry_bomb_lit" playsound(src, 'sound/goonstation/misc/fuse.ogg', seed.potency, 0) - reagents.set_reagent_temp(1000) //Sets off the black powder \ No newline at end of file + reagents.set_reagent_temp(1000) //Sets off the black powder diff --git a/code/modules/hydroponics/grown/nymph.dm b/code/modules/hydroponics/grown/nymph.dm index d7895201377..34bbffc4c9d 100644 --- a/code/modules/hydroponics/grown/nymph.dm +++ b/code/modules/hydroponics/grown/nymph.dm @@ -23,4 +23,4 @@ new /mob/living/simple_animal/diona(get_turf(user)) to_chat(user, "You crack open [src] letting the nymph out.") user.drop_item() - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/hydroponics/grown/onion.dm b/code/modules/hydroponics/grown/onion.dm index c53a9487db4..cff3fcafdba 100644 --- a/code/modules/hydroponics/grown/onion.dm +++ b/code/modules/hydroponics/grown/onion.dm @@ -66,4 +66,4 @@ icon_state = "onionslice_red" filling_color = "#C29ACF" tastes = list("red onion" = 1, "pungentness" = 3) - list_reagents = list("plantmatter" = 5, "vitamin" = 2, "onionjuice" = 2.5) \ No newline at end of file + list_reagents = list("plantmatter" = 5, "vitamin" = 2, "onionjuice" = 2.5) diff --git a/code/modules/hydroponics/grown/peanut.dm b/code/modules/hydroponics/grown/peanut.dm index 52b74ab795b..836f2148888 100644 --- a/code/modules/hydroponics/grown/peanut.dm +++ b/code/modules/hydroponics/grown/peanut.dm @@ -23,4 +23,4 @@ desc = "Best avoided if you have spess allergies." icon_state = "peanuts" tastes = list("peanut" = 1, "nuttiness" = 1) - gender = PLURAL \ No newline at end of file + gender = PLURAL diff --git a/code/modules/hydroponics/grown/pineapple.dm b/code/modules/hydroponics/grown/pineapple.dm index 30c5f904951..aa1946aee4e 100644 --- a/code/modules/hydroponics/grown/pineapple.dm +++ b/code/modules/hydroponics/grown/pineapple.dm @@ -30,4 +30,4 @@ filling_color = "#F6CB0B" w_class = WEIGHT_CLASS_NORMAL tastes = list("pineapple" = 1) - wine_power = 0.4 \ No newline at end of file + wine_power = 0.4 diff --git a/code/modules/hydroponics/grown/potato.dm b/code/modules/hydroponics/grown/potato.dm index c02b8f72119..a4d85416328 100644 --- a/code/modules/hydroponics/grown/potato.dm +++ b/code/modules/hydroponics/grown/potato.dm @@ -67,4 +67,4 @@ name = "sweet potato" desc = "It's sweet." tastes = list("sweet potato" = 1) - icon_state = "sweetpotato" \ No newline at end of file + icon_state = "sweetpotato" diff --git a/code/modules/hydroponics/grown/tea_coffee.dm b/code/modules/hydroponics/grown/tea_coffee.dm index cec0a8598e0..0bba20ad4c0 100644 --- a/code/modules/hydroponics/grown/tea_coffee.dm +++ b/code/modules/hydroponics/grown/tea_coffee.dm @@ -92,4 +92,4 @@ name = "coffee robusta beans" desc = "Increases robustness by 37 percent!" tastes = list("coffee beans" = 1, "robustness" = 1) - icon_state = "coffee_robusta" \ No newline at end of file + icon_state = "coffee_robusta" diff --git a/code/modules/hydroponics/grown/tobacco.dm b/code/modules/hydroponics/grown/tobacco.dm index 09d7d484f53..3087fa1dddb 100644 --- a/code/modules/hydroponics/grown/tobacco.dm +++ b/code/modules/hydroponics/grown/tobacco.dm @@ -43,4 +43,4 @@ icon_state = "stobacco_leaves" tastes = list("space tobacco" = 1) distill_reagent = null - wine_power = 0.5 \ No newline at end of file + wine_power = 0.5 diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm index c045b8a5b88..84b5083d332 100644 --- a/code/modules/hydroponics/grown/towercap.dm +++ b/code/modules/hydroponics/grown/towercap.dm @@ -197,4 +197,4 @@ /obj/structure/bonfire/unbuckle_mob(mob/living/buckled_mob, force = FALSE) if(..()) - buckled_mob.pixel_y -= 13 \ No newline at end of file + buckled_mob.pixel_y -= 13 diff --git a/code/modules/karma/karma.dm b/code/modules/karma/karma.dm index ebcf10f7868..13f94c11e62 100644 --- a/code/modules/karma/karma.dm +++ b/code/modules/karma/karma.dm @@ -454,4 +454,4 @@ var/list/karma_spenders = list() else return combinedlist else - return FALSE \ No newline at end of file + return FALSE diff --git a/code/modules/keybindings/bindings_ai.dm b/code/modules/keybindings/bindings_ai.dm index b496f3521f9..975b5ba81a9 100644 --- a/code/modules/keybindings/bindings_ai.dm +++ b/code/modules/keybindings/bindings_ai.dm @@ -3,4 +3,4 @@ if("4") a_intent_change(INTENT_HOTKEY_LEFT) return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/keybindings/bindings_atom.dm b/code/modules/keybindings/bindings_atom.dm index 6b1e0afe199..c5b157ba6c0 100644 --- a/code/modules/keybindings/bindings_atom.dm +++ b/code/modules/keybindings/bindings_atom.dm @@ -18,4 +18,4 @@ movement_dir &= ~(NORTH|SOUTH) if((movement_dir & EAST) && (movement_dir & WEST)) movement_dir &= ~(EAST|WEST) - user.Move(get_step(src, movement_dir), movement_dir) \ No newline at end of file + user.Move(get_step(src, movement_dir), movement_dir) diff --git a/code/modules/keybindings/bindings_carbon.dm b/code/modules/keybindings/bindings_carbon.dm index 81784696b35..84acb5b1493 100644 --- a/code/modules/keybindings/bindings_carbon.dm +++ b/code/modules/keybindings/bindings_carbon.dm @@ -17,4 +17,4 @@ if("4") a_intent_change("harm") return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm index f38e485d226..840b04dfd3f 100644 --- a/code/modules/keybindings/bindings_client.dm +++ b/code/modules/keybindings/bindings_client.dm @@ -99,4 +99,4 @@ if(holder) holder.keyLoop(src) if(mob.focus) - mob.focus.keyLoop(src) \ No newline at end of file + mob.focus.keyLoop(src) diff --git a/code/modules/keybindings/bindings_human.dm b/code/modules/keybindings/bindings_human.dm index 5e8ced96ab6..a842fa3fc31 100644 --- a/code/modules/keybindings/bindings_human.dm +++ b/code/modules/keybindings/bindings_human.dm @@ -77,4 +77,4 @@ return stored.attack_hand(src) // take out thing from backpack return */ - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/keybindings/bindings_living.dm b/code/modules/keybindings/bindings_living.dm index 241bc15b608..7ae8b9960f6 100644 --- a/code/modules/keybindings/bindings_living.dm +++ b/code/modules/keybindings/bindings_living.dm @@ -4,4 +4,4 @@ resist() return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/keybindings/bindings_mob.dm b/code/modules/keybindings/bindings_mob.dm index 4ede6b63c7b..b5b81dd129e 100644 --- a/code/modules/keybindings/bindings_mob.dm +++ b/code/modules/keybindings/bindings_mob.dm @@ -142,4 +142,4 @@ if("Alt") toggle_move_intent() return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/keybindings/bindings_robot.dm b/code/modules/keybindings/bindings_robot.dm index f9b39dc7351..073f0ccae84 100644 --- a/code/modules/keybindings/bindings_robot.dm +++ b/code/modules/keybindings/bindings_robot.dm @@ -17,4 +17,4 @@ if(client.prefs.toggles & AZERTY) uneq_active() return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/keybindings/focus.dm b/code/modules/keybindings/focus.dm index 9cfbf36c5d3..205b293e9a1 100644 --- a/code/modules/keybindings/focus.dm +++ b/code/modules/keybindings/focus.dm @@ -5,4 +5,4 @@ if(focus == new_focus) return focus = new_focus - reset_perspective(focus) //Maybe this should be done manually? You figure it out, reader \ No newline at end of file + reset_perspective(focus) //Maybe this should be done manually? You figure it out, reader diff --git a/code/modules/keybindings/readme.md b/code/modules/keybindings/readme.md index f57d8d55ffa..3f8b992b93d 100644 --- a/code/modules/keybindings/readme.md +++ b/code/modules/keybindings/readme.md @@ -39,4 +39,4 @@ Notes about certain keys: You cannot `TICK_CHECK` or check `world.tick_usage` inside of procs called by key down and up events. They happen outside of a byond tick and have no meaning there. Key looping -works correctly since it's part of a subsystem, not direct input. \ No newline at end of file +works correctly since it's part of a subsystem, not direct input. diff --git a/code/modules/library/computers/base.dm b/code/modules/library/computers/base.dm index 5bb8019cd58..2b4bcd9f5a7 100644 --- a/code/modules/library/computers/base.dm +++ b/code/modules/library/computers/base.dm @@ -90,4 +90,4 @@ return pagelist /obj/machinery/computer/library/proc/getBookByID(var/id as text) - return library_catalog.getBookByID(id) \ No newline at end of file + return library_catalog.getBookByID(id) diff --git a/code/modules/library/computers/checkout.dm b/code/modules/library/computers/checkout.dm index 638aaeee7c3..e59c71392d3 100644 --- a/code/modules/library/computers/checkout.dm +++ b/code/modules/library/computers/checkout.dm @@ -1,465 +1,465 @@ -/* - * Library Computer - */ -/obj/machinery/computer/library/checkout - name = "Check-In/Out Computer" - icon = 'icons/obj/library.dmi' - icon_state = "computer" - anchored = 1 - density = 1 - var/arcanecheckout = 0 - //var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book - var/buffer_book - var/buffer_mob - var/upload_category = "Fiction" - var/list/checkouts = list() - var/list/inventory = list() - var/checkoutperiod = 5 // In minutes - var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive - - var/bibledelay = 0 // LOL NO SPAM (1 minute delay) -- Doohl - var/booklist - -/obj/machinery/computer/library/checkout/attack_hand(var/mob/user as mob) - if(..()) - return - interact(user) - -/obj/machinery/computer/library/checkout/interact(var/mob/user) - if(interact_check(user)) - return - - var/dat = "" - switch(screenstate) - if(0) - // Main Menu - - dat += {"
          -
        1. View General Inventory
        2. -
        3. View Checked Out Inventory
        4. -
        5. Check out a Book
        6. -
        7. Connect to External Archive
        8. -
        9. Upload New Title to Archive
        10. -
        11. Print a Bible
        12. -
        13. Print a Manual
        14. "} - if(src.emagged) - dat += "
        15. Access the Forbidden Lore Vault
        16. " - dat += "
        " - - if(src.arcanecheckout) - new /obj/item/tome(src.loc) - to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a dusty old tome sitting on the desk. You don't really remember printing it.") - user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) - src.arcanecheckout = 0 - if(1) - // Inventory - dat += "

        Inventory

        " - for(var/obj/item/book/b in inventory) - dat += "[b.name] (Delete)
        " - dat += "(Return to main menu)
        " - if(2) - // Checked Out - dat += "

        Checked Out Books


        " - for(var/datum/borrowbook/b in checkouts) - var/timetaken = world.time - b.getdate - //timetaken *= 10 - timetaken /= 600 - timetaken = round(timetaken) - var/timedue = b.duedate - world.time - //timedue *= 10 - timedue /= 600 - if(timedue <= 0) - timedue = "(OVERDUE) [timedue]" - else - timedue = round(timedue) - - dat += {"\"[b.bookname]\", Checked out to: [b.mobname]
        --- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
        - (Check In)

        "} - dat += "(Return to main menu)
        " - if(3) - // Check Out a Book - - dat += {"

        Check Out a Book


        - Book: [src.buffer_book] - \[Edit\]
        - Recipient: [src.buffer_mob] - \[Edit\]
        - Checkout Date : [world.time/600]
        - Due Date: [(world.time + checkoutperiod)/600]
        - (Checkout Period: [checkoutperiod] minutes) (+/-) - (Commit Entry)
        - (Return to main menu)
        "} - if(4) - dat += "

        External Archive

        " - if(!dbcon.IsConnected()) - dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." - else - num_results = src.get_num_results() - num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) - dat += {""} - var/pagelist = get_pagelist() - - dat += {"

        Search Settings


        - Filter by Title: [query.title]
        - Filter by Category: [query.category]
        - Filter by Author: [query.author]
        - \[Start Search\]
        "} - dat += pagelist - - dat += {"
        - - - -
        "} - - dat += {" - - - - - - "} - - for(var/datum/cachedbook/CB in get_page(page_num)) - var/author = CB.author - var/controls = "\[Order\]" - controls += {" \[Flag[CB.flagged ? "ged" : ""]\]"} - if(check_rights(R_ADMIN, 0, user = user)) - controls += " \[Delete\]" - author += " ([ckey(CB.ckey)]))" - dat += {" - - - - - "} - - dat += "
        AuthorTitleCategoryControls
        [author][CB.title][CB.category][controls]

        [pagelist]" - - dat += "
        (Return to main menu)
        " - if(5) - dat += "

        Upload a New Title

        " - if(!scanner) - for(var/obj/machinery/libraryscanner/S in range(9)) - scanner = S - break - if(!scanner) - dat += "No scanner found within wireless network range.
        " - else if(!scanner.cache) - dat += "No data found in scanner memory.
        " - else - - dat += {"Data marked for upload...
        - Title: [scanner.cache.name]
        "} - if(!scanner.cache.author) - scanner.cache.author = "Anonymous" - - dat += {"Author: [scanner.cache.author]
        - Category: [upload_category]
        - \[Upload\]
        "} - dat += "(Return to main menu)
        " - if(7) - dat += "

        Print a Manual

        " - dat += "" - - var/list/forbidden = list( - /obj/item/book/manual - ) - - if(!emagged) - forbidden |= /obj/item/book/manual/nuclear - - var/manualcount = 1 - var/obj/item/book/manual/M = null - - for(var/manual_type in (typesof(/obj/item/book/manual) - forbidden)) - M = new manual_type() - dat += "" - manualcount++ - QDEL_NULL(M) - dat += "
        [M.title]
        " - dat += "
        (Return to main menu)
        " - - if(8) - - dat += {"

        Accessing Forbidden Lore Vault v 1.3

        - Are you absolutely sure you want to proceed? EldritchTomes Inc. takes no responsibilities for loss of sanity resulting from this action.

        - Yes.
        - No.
        "} - - var/datum/browser/B = new /datum/browser(user, "library", "Book Inventory Management") - B.set_content(dat) - B.open() - -/obj/machinery/computer/library/checkout/emag_act(mob/user) - if(density && !emagged) - emagged = 1 - to_chat(user, "You override the library computer's printing restrictions.") - -/obj/machinery/computer/library/checkout/attackby(obj/item/W as obj, mob/user as mob) - if(default_unfasten_wrench(user, W)) - power_change() - return - if(istype(W, /obj/item/barcodescanner)) - var/obj/item/barcodescanner/scanner = W - scanner.computer = src - to_chat(user, "[scanner]'s associated machine has been set to [src].") - audible_message("[src] lets out a low, short blip.", 2) - return 1 - else - return ..() - -/obj/machinery/computer/library/checkout/Topic(href, href_list) - if(..()) - usr << browse(null, "window=library") - onclose(usr, "library") - return 1 - - if(href_list["pagenum"]) - if(!num_pages) - page_num = 1 - else - var/pn = text2num(href_list["pagenum"]) - if(!isnull(pn)) - page_num = Clamp(pn, 1, num_pages) - - if(href_list["page"]) - if(num_pages == 0) - page_num = 1 - else - page_num = Clamp(text2num(href_list["page"]), 1, num_pages) - if(href_list["settitle"]) - var/newtitle = input("Enter a title to search for:") as text|null - if(newtitle) - query.title = sanitize(newtitle) - else - query.title = null - if(href_list["setcategory"]) - var/newcategory = input("Choose a category to search for:") in (list("Any") + library_section_names) - if(newcategory == "Any") - query.category = null - else if(newcategory) - query.category = sanitize(newcategory) - if(href_list["setauthor"]) - var/newauthor = input("Enter an author to search for:") as text|null - if(newauthor) - query.author = sanitize(newauthor) - else - query.author = null - - if(href_list["search"]) - num_results = src.get_num_results() - num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) - page_num = 1 - - screenstate = 4 - if(href_list["del"]) - if(!check_rights(R_ADMIN)) - return - var/datum/cachedbook/target = getBookByID(href_list["del"]) // Sanitized in getBookByID - var/ans = alert(usr, "Are you sure you wish to delete \"[target.title]\", by [target.author]? This cannot be undone.", "Library System", "Yes", "No") - if(ans=="Yes") - var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[target.id]") - var/response = query.Execute() - if(!response) - to_chat(usr, query.ErrorMsg()) - return - log_admin("LIBRARY: [key_name(usr)] has deleted \"[target.title]\", by [target.author] ([target.ckey])!") - message_admins("[key_name_admin(usr)] has deleted \"[target.title]\", by [target.author] ([target.ckey])!") - src.updateUsrDialog() - return - - if(href_list["delbyckey"]) - if(!check_rights(R_ADMIN)) - return - var/tckey = ckey(href_list["delbyckey"]) - var/ans = alert(usr,"Are you sure you wish to delete all books by [tckey]? This cannot be undone.", "Library System", "Yes", "No") - if(ans=="Yes") - var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE ckey='[sanitizeSQL(tckey)]'") - var/response = query.Execute() - if(!response) - to_chat(usr, query.ErrorMsg()) - return - var/affected=query.RowsAffected() - if(affected==0) - to_chat(usr, "Unable to find any matching rows.") - return - log_admin("LIBRARY: [key_name(usr)] has deleted [affected] books written by [tckey]!") - message_admins("[key_name_admin(usr)] has deleted [affected] books written by [tckey]!") - src.updateUsrDialog() - return - - if(href_list["flag"]) - if(!dbcon.IsConnected()) - alert("Connection to Archive has been severed. Aborting.") - return - var/id = href_list["flag"] - if(id) - var/datum/cachedbook/B = getBookByID(id) - if(B) - if((input(usr, "Are you sure you want to flag [B.title] as having inappropriate content?", "Flag Book #[B.id]") in list("Yes", "No")) == "Yes") - library_catalog.flag_book_by_id(usr, id) - - if(href_list["switchscreen"]) - switch(href_list["switchscreen"]) - if("0") - screenstate = 0 - if("1") - screenstate = 1 - if("2") - screenstate = 2 - if("3") - screenstate = 3 - if("4") - screenstate = 4 - if("5") - screenstate = 5 - if("6") - if(!bibledelay) - - var/obj/item/storage/bible/B = new /obj/item/storage/bible(src.loc) - if(SSticker && ( SSticker.Bible_icon_state && SSticker.Bible_item_state) ) - B.icon_state = SSticker.Bible_icon_state - B.item_state = SSticker.Bible_item_state - B.name = SSticker.Bible_name - B.deity_name = SSticker.Bible_deity_name - - bibledelay = 1 - spawn(60) - bibledelay = 0 - - else - visible_message("[src]'s monitor flashes, \"Bible printer currently unavailable, please wait a moment.\"") - - if("7") - screenstate = 7 - if("8") - screenstate = 8 - if(href_list["arccheckout"]) - if(src.emagged) - src.arcanecheckout = 1 - src.screenstate = 0 - if(href_list["increasetime"]) - checkoutperiod += 1 - if(href_list["decreasetime"]) - checkoutperiod -= 1 - if(checkoutperiod < 1) - checkoutperiod = 1 - if(href_list["editbook"]) - buffer_book = copytext(sanitize(input("Enter the book's title:") as text|null),1,MAX_MESSAGE_LEN) - if(href_list["editmob"]) - buffer_mob = copytext(sanitize(input("Enter the recipient's name:") as text|null),1,MAX_NAME_LEN) - if(href_list["checkout"]) - var/datum/borrowbook/b = new /datum/borrowbook - b.bookname = sanitize(buffer_book) - b.mobname = sanitize(buffer_mob) - b.getdate = world.time - b.duedate = world.time + (checkoutperiod * 600) - checkouts.Add(b) - if(href_list["checkin"]) - var/datum/borrowbook/b = locate(href_list["checkin"]) - checkouts.Remove(b) - if(href_list["delbook"]) - var/obj/item/book/b = locate(href_list["delbook"]) - inventory.Remove(b) - if(href_list["uploadauthor"]) - var/newauthor = copytext(sanitize(input("Enter the author's name: ") as text|null),1,MAX_MESSAGE_LEN) - if(newauthor && scanner) - scanner.cache.author = newauthor - if(href_list["uploadcategory"]) - var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Adult", "Reference", "Religion") - if(newcategory) - upload_category = newcategory - if(href_list["upload"]) - if(scanner) - if(scanner.cache) - var/choice = input("Are you certain you wish to upload this title to the Archive?") in list("Confirm", "Abort") - if(choice == "Confirm") - establish_db_connection() - if(!dbcon.IsConnected()) - alert("Connection to Archive has been severed. Aborting.") - else - var/sqltitle = sanitizeSQL(scanner.cache.name) - var/sqlauthor = sanitizeSQL(scanner.cache.author) - var/sqlcontent = sanitizeSQL(scanner.cache.dat) - var/sqlcategory = sanitizeSQL(upload_category) - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, flagged) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', '[ckey(usr.key)]', 0)") - var/response = query.Execute() - if(!response) - to_chat(usr, query.ErrorMsg()) - else - log_admin("[usr.name]/[usr.key] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] characters in length") - message_admins("[key_name_admin(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] characters in length") - - if(href_list["id"]) - if(href_list["id"]=="-1") - href_list["id"] = input("Enter your order:") as null|num - if(!href_list["id"]) - return - - if(!dbcon.IsConnected()) - alert("Connection to Archive has been severed. Aborting.") - return - - var/datum/cachedbook/newbook = getBookByID(href_list["id"]) // Sanitized in getBookByID - if(!newbook) - alert("No book found") - return - if((newbook.forbidden == 2 && !emagged) || newbook.forbidden == 1) - alert("This book is forbidden and cannot be printed.") - return - - if(bibledelay) - audible_message("[src]'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"") - else - bibledelay = 1 - spawn(60) - bibledelay = 0 - make_external_book(newbook) - if(href_list["manual"]) - if(!href_list["manual"]) return - var/bookid = href_list["manual"] - - if(!dbcon.IsConnected()) - alert("Connection to Archive has been severed. Aborting.") - return - - var/datum/cachedbook/newbook = getBookByID("M[bookid]") - if(!newbook) - alert("No book found") - return - if((newbook.forbidden == 2 && !emagged) || newbook.forbidden == 1) - alert("This book is forbidden and cannot be printed.") - return - - if(bibledelay) - for(var/mob/V in hearers(src)) - V.show_message("[src]'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"") - else - bibledelay = 1 - spawn(60) - bibledelay = 0 - make_external_book(newbook) - - src.add_fingerprint(usr) - src.updateUsrDialog() - return - -/* - * Library Scanner - */ - -/obj/machinery/computer/library/checkout/proc/make_external_book(var/datum/cachedbook/newbook) - if(!newbook || !newbook.id) - return - var/obj/item/book/B = new newbook.path(loc) - - if(!newbook.programmatic) - B.name = "Book: [newbook.title]" - B.title = newbook.title - B.author = newbook.author - B.dat = newbook.content - B.icon_state = "book[rand(1,16)]" - visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") +/* + * Library Computer + */ +/obj/machinery/computer/library/checkout + name = "Check-In/Out Computer" + icon = 'icons/obj/library.dmi' + icon_state = "computer" + anchored = 1 + density = 1 + var/arcanecheckout = 0 + //var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book + var/buffer_book + var/buffer_mob + var/upload_category = "Fiction" + var/list/checkouts = list() + var/list/inventory = list() + var/checkoutperiod = 5 // In minutes + var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive + + var/bibledelay = 0 // LOL NO SPAM (1 minute delay) -- Doohl + var/booklist + +/obj/machinery/computer/library/checkout/attack_hand(var/mob/user as mob) + if(..()) + return + interact(user) + +/obj/machinery/computer/library/checkout/interact(var/mob/user) + if(interact_check(user)) + return + + var/dat = "" + switch(screenstate) + if(0) + // Main Menu + + dat += {"

          +
        1. View General Inventory
        2. +
        3. View Checked Out Inventory
        4. +
        5. Check out a Book
        6. +
        7. Connect to External Archive
        8. +
        9. Upload New Title to Archive
        10. +
        11. Print a Bible
        12. +
        13. Print a Manual
        14. "} + if(src.emagged) + dat += "
        15. Access the Forbidden Lore Vault
        16. " + dat += "
        " + + if(src.arcanecheckout) + new /obj/item/tome(src.loc) + to_chat(user, "Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a dusty old tome sitting on the desk. You don't really remember printing it.") + user.visible_message("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older.", 2) + src.arcanecheckout = 0 + if(1) + // Inventory + dat += "

        Inventory

        " + for(var/obj/item/book/b in inventory) + dat += "[b.name] (Delete)
        " + dat += "(Return to main menu)
        " + if(2) + // Checked Out + dat += "

        Checked Out Books


        " + for(var/datum/borrowbook/b in checkouts) + var/timetaken = world.time - b.getdate + //timetaken *= 10 + timetaken /= 600 + timetaken = round(timetaken) + var/timedue = b.duedate - world.time + //timedue *= 10 + timedue /= 600 + if(timedue <= 0) + timedue = "(OVERDUE) [timedue]" + else + timedue = round(timedue) + + dat += {"\"[b.bookname]\", Checked out to: [b.mobname]
        --- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
        + (Check In)

        "} + dat += "(Return to main menu)
        " + if(3) + // Check Out a Book + + dat += {"

        Check Out a Book


        + Book: [src.buffer_book] + \[Edit\]
        + Recipient: [src.buffer_mob] + \[Edit\]
        + Checkout Date : [world.time/600]
        + Due Date: [(world.time + checkoutperiod)/600]
        + (Checkout Period: [checkoutperiod] minutes) (+/-) + (Commit Entry)
        + (Return to main menu)
        "} + if(4) + dat += "

        External Archive

        " + if(!dbcon.IsConnected()) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." + else + num_results = src.get_num_results() + num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) + dat += {""} + var/pagelist = get_pagelist() + + dat += {"

        Search Settings


        + Filter by Title: [query.title]
        + Filter by Category: [query.category]
        + Filter by Author: [query.author]
        + \[Start Search\]
        "} + dat += pagelist + + dat += {"
        + + + +
        "} + + dat += {" + + + + + + "} + + for(var/datum/cachedbook/CB in get_page(page_num)) + var/author = CB.author + var/controls = "\[Order\]" + controls += {" \[Flag[CB.flagged ? "ged" : ""]\]"} + if(check_rights(R_ADMIN, 0, user = user)) + controls += " \[Delete\]" + author += " ([ckey(CB.ckey)]))" + dat += {" + + + + + "} + + dat += "
        AuthorTitleCategoryControls
        [author][CB.title][CB.category][controls]

        [pagelist]" + + dat += "
        (Return to main menu)
        " + if(5) + dat += "

        Upload a New Title

        " + if(!scanner) + for(var/obj/machinery/libraryscanner/S in range(9)) + scanner = S + break + if(!scanner) + dat += "No scanner found within wireless network range.
        " + else if(!scanner.cache) + dat += "No data found in scanner memory.
        " + else + + dat += {"Data marked for upload...
        + Title: [scanner.cache.name]
        "} + if(!scanner.cache.author) + scanner.cache.author = "Anonymous" + + dat += {"Author: [scanner.cache.author]
        + Category: [upload_category]
        + \[Upload\]
        "} + dat += "(Return to main menu)
        " + if(7) + dat += "

        Print a Manual

        " + dat += "" + + var/list/forbidden = list( + /obj/item/book/manual + ) + + if(!emagged) + forbidden |= /obj/item/book/manual/nuclear + + var/manualcount = 1 + var/obj/item/book/manual/M = null + + for(var/manual_type in (typesof(/obj/item/book/manual) - forbidden)) + M = new manual_type() + dat += "" + manualcount++ + QDEL_NULL(M) + dat += "
        [M.title]
        " + dat += "
        (Return to main menu)
        " + + if(8) + + dat += {"

        Accessing Forbidden Lore Vault v 1.3

        + Are you absolutely sure you want to proceed? EldritchTomes Inc. takes no responsibilities for loss of sanity resulting from this action.

        + Yes.
        + No.
        "} + + var/datum/browser/B = new /datum/browser(user, "library", "Book Inventory Management") + B.set_content(dat) + B.open() + +/obj/machinery/computer/library/checkout/emag_act(mob/user) + if(density && !emagged) + emagged = 1 + to_chat(user, "You override the library computer's printing restrictions.") + +/obj/machinery/computer/library/checkout/attackby(obj/item/W as obj, mob/user as mob) + if(default_unfasten_wrench(user, W)) + power_change() + return + if(istype(W, /obj/item/barcodescanner)) + var/obj/item/barcodescanner/scanner = W + scanner.computer = src + to_chat(user, "[scanner]'s associated machine has been set to [src].") + audible_message("[src] lets out a low, short blip.", 2) + return 1 + else + return ..() + +/obj/machinery/computer/library/checkout/Topic(href, href_list) + if(..()) + usr << browse(null, "window=library") + onclose(usr, "library") + return 1 + + if(href_list["pagenum"]) + if(!num_pages) + page_num = 1 + else + var/pn = text2num(href_list["pagenum"]) + if(!isnull(pn)) + page_num = Clamp(pn, 1, num_pages) + + if(href_list["page"]) + if(num_pages == 0) + page_num = 1 + else + page_num = Clamp(text2num(href_list["page"]), 1, num_pages) + if(href_list["settitle"]) + var/newtitle = input("Enter a title to search for:") as text|null + if(newtitle) + query.title = sanitize(newtitle) + else + query.title = null + if(href_list["setcategory"]) + var/newcategory = input("Choose a category to search for:") in (list("Any") + library_section_names) + if(newcategory == "Any") + query.category = null + else if(newcategory) + query.category = sanitize(newcategory) + if(href_list["setauthor"]) + var/newauthor = input("Enter an author to search for:") as text|null + if(newauthor) + query.author = sanitize(newauthor) + else + query.author = null + + if(href_list["search"]) + num_results = src.get_num_results() + num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) + page_num = 1 + + screenstate = 4 + if(href_list["del"]) + if(!check_rights(R_ADMIN)) + return + var/datum/cachedbook/target = getBookByID(href_list["del"]) // Sanitized in getBookByID + var/ans = alert(usr, "Are you sure you wish to delete \"[target.title]\", by [target.author]? This cannot be undone.", "Library System", "Yes", "No") + if(ans=="Yes") + var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[target.id]") + var/response = query.Execute() + if(!response) + to_chat(usr, query.ErrorMsg()) + return + log_admin("LIBRARY: [key_name(usr)] has deleted \"[target.title]\", by [target.author] ([target.ckey])!") + message_admins("[key_name_admin(usr)] has deleted \"[target.title]\", by [target.author] ([target.ckey])!") + src.updateUsrDialog() + return + + if(href_list["delbyckey"]) + if(!check_rights(R_ADMIN)) + return + var/tckey = ckey(href_list["delbyckey"]) + var/ans = alert(usr,"Are you sure you wish to delete all books by [tckey]? This cannot be undone.", "Library System", "Yes", "No") + if(ans=="Yes") + var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE ckey='[sanitizeSQL(tckey)]'") + var/response = query.Execute() + if(!response) + to_chat(usr, query.ErrorMsg()) + return + var/affected=query.RowsAffected() + if(affected==0) + to_chat(usr, "Unable to find any matching rows.") + return + log_admin("LIBRARY: [key_name(usr)] has deleted [affected] books written by [tckey]!") + message_admins("[key_name_admin(usr)] has deleted [affected] books written by [tckey]!") + src.updateUsrDialog() + return + + if(href_list["flag"]) + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + return + var/id = href_list["flag"] + if(id) + var/datum/cachedbook/B = getBookByID(id) + if(B) + if((input(usr, "Are you sure you want to flag [B.title] as having inappropriate content?", "Flag Book #[B.id]") in list("Yes", "No")) == "Yes") + library_catalog.flag_book_by_id(usr, id) + + if(href_list["switchscreen"]) + switch(href_list["switchscreen"]) + if("0") + screenstate = 0 + if("1") + screenstate = 1 + if("2") + screenstate = 2 + if("3") + screenstate = 3 + if("4") + screenstate = 4 + if("5") + screenstate = 5 + if("6") + if(!bibledelay) + + var/obj/item/storage/bible/B = new /obj/item/storage/bible(src.loc) + if(SSticker && ( SSticker.Bible_icon_state && SSticker.Bible_item_state) ) + B.icon_state = SSticker.Bible_icon_state + B.item_state = SSticker.Bible_item_state + B.name = SSticker.Bible_name + B.deity_name = SSticker.Bible_deity_name + + bibledelay = 1 + spawn(60) + bibledelay = 0 + + else + visible_message("[src]'s monitor flashes, \"Bible printer currently unavailable, please wait a moment.\"") + + if("7") + screenstate = 7 + if("8") + screenstate = 8 + if(href_list["arccheckout"]) + if(src.emagged) + src.arcanecheckout = 1 + src.screenstate = 0 + if(href_list["increasetime"]) + checkoutperiod += 1 + if(href_list["decreasetime"]) + checkoutperiod -= 1 + if(checkoutperiod < 1) + checkoutperiod = 1 + if(href_list["editbook"]) + buffer_book = copytext(sanitize(input("Enter the book's title:") as text|null),1,MAX_MESSAGE_LEN) + if(href_list["editmob"]) + buffer_mob = copytext(sanitize(input("Enter the recipient's name:") as text|null),1,MAX_NAME_LEN) + if(href_list["checkout"]) + var/datum/borrowbook/b = new /datum/borrowbook + b.bookname = sanitize(buffer_book) + b.mobname = sanitize(buffer_mob) + b.getdate = world.time + b.duedate = world.time + (checkoutperiod * 600) + checkouts.Add(b) + if(href_list["checkin"]) + var/datum/borrowbook/b = locate(href_list["checkin"]) + checkouts.Remove(b) + if(href_list["delbook"]) + var/obj/item/book/b = locate(href_list["delbook"]) + inventory.Remove(b) + if(href_list["uploadauthor"]) + var/newauthor = copytext(sanitize(input("Enter the author's name: ") as text|null),1,MAX_MESSAGE_LEN) + if(newauthor && scanner) + scanner.cache.author = newauthor + if(href_list["uploadcategory"]) + var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Adult", "Reference", "Religion") + if(newcategory) + upload_category = newcategory + if(href_list["upload"]) + if(scanner) + if(scanner.cache) + var/choice = input("Are you certain you wish to upload this title to the Archive?") in list("Confirm", "Abort") + if(choice == "Confirm") + establish_db_connection() + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + else + var/sqltitle = sanitizeSQL(scanner.cache.name) + var/sqlauthor = sanitizeSQL(scanner.cache.author) + var/sqlcontent = sanitizeSQL(scanner.cache.dat) + var/sqlcategory = sanitizeSQL(upload_category) + var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, flagged) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', '[ckey(usr.key)]', 0)") + var/response = query.Execute() + if(!response) + to_chat(usr, query.ErrorMsg()) + else + log_admin("[usr.name]/[usr.key] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] characters in length") + message_admins("[key_name_admin(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] characters in length") + + if(href_list["id"]) + if(href_list["id"]=="-1") + href_list["id"] = input("Enter your order:") as null|num + if(!href_list["id"]) + return + + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + return + + var/datum/cachedbook/newbook = getBookByID(href_list["id"]) // Sanitized in getBookByID + if(!newbook) + alert("No book found") + return + if((newbook.forbidden == 2 && !emagged) || newbook.forbidden == 1) + alert("This book is forbidden and cannot be printed.") + return + + if(bibledelay) + audible_message("[src]'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"") + else + bibledelay = 1 + spawn(60) + bibledelay = 0 + make_external_book(newbook) + if(href_list["manual"]) + if(!href_list["manual"]) return + var/bookid = href_list["manual"] + + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + return + + var/datum/cachedbook/newbook = getBookByID("M[bookid]") + if(!newbook) + alert("No book found") + return + if((newbook.forbidden == 2 && !emagged) || newbook.forbidden == 1) + alert("This book is forbidden and cannot be printed.") + return + + if(bibledelay) + for(var/mob/V in hearers(src)) + V.show_message("[src]'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"") + else + bibledelay = 1 + spawn(60) + bibledelay = 0 + make_external_book(newbook) + + src.add_fingerprint(usr) + src.updateUsrDialog() + return + +/* + * Library Scanner + */ + +/obj/machinery/computer/library/checkout/proc/make_external_book(var/datum/cachedbook/newbook) + if(!newbook || !newbook.id) + return + var/obj/item/book/B = new newbook.path(loc) + + if(!newbook.programmatic) + B.name = "Book: [newbook.title]" + B.title = newbook.title + B.author = newbook.author + B.dat = newbook.content + B.icon_state = "book[rand(1,16)]" + visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") diff --git a/code/modules/library/computers/public.dm b/code/modules/library/computers/public.dm index d0a6272970c..7793c9bb6f3 100644 --- a/code/modules/library/computers/public.dm +++ b/code/modules/library/computers/public.dm @@ -1,128 +1,128 @@ -/obj/machinery/computer/library/public - name = "visitor computer" - -/obj/machinery/computer/library/public/attack_hand(var/mob/user as mob) - if(..()) - return - interact(user) - -/obj/machinery/computer/library/public/attackby(obj/item/W as obj, mob/user as mob) - if(default_unfasten_wrench(user, W)) - power_change() - return - return ..() - -/obj/machinery/computer/library/public/interact(var/mob/user) - if(interact_check(user)) - return - - var/dat = "" - switch(screenstate) - if(0) - - dat += {"

        Search Settings


        - Filter by Title: [query.title]
        - Filter by Category: [query.category]
        - Filter by Author: [query.author]
        - \[Start Search\]
        "} - if(1) - establish_db_connection() - if(!dbcon.IsConnected()) - dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
        " - else if(num_results == 0) - dat += "No results found." - else - var/pagelist = get_pagelist() - - dat += pagelist - dat += {"
        - - - -
        "} - dat += {" - - - - - - - "} - for(var/datum/cachedbook/CB in get_page(page_num)) - dat += {" - - - - - - "} - - dat += "
        AuthorTitleCategorySS13BNControls
        [CB.author][CB.title][CB.category][CB.id]\[Flag[CB.flagged ? "ged" : ""]\]

        [pagelist]" - dat += "\[Go Back\]
        " - var/datum/browser/B = new /datum/browser(user, "library", "Library Visitor") - B.set_content(dat) - B.open() - -/obj/machinery/computer/library/public/Topic(href, href_list) - if(..()) - usr << browse(null, "window=publiclibrary") - onclose(usr, "publiclibrary") - return - - if(href_list["pagenum"]) - if(!num_pages) - page_num = 1 - else - var/pn = text2num(href_list["pagenum"]) - if(!isnull(pn)) - page_num = Clamp(pn, 1, num_pages) - - if(href_list["settitle"]) - var/newtitle = input("Enter a title to search for:") as text|null - if(newtitle) - query.title = sanitize(newtitle) - else - query.title = null - if(href_list["setcategory"]) - var/newcategory = input("Choose a category to search for:") in (list("Any") + library_section_names) - if(newcategory == "Any") - query.category = null - else if(newcategory) - query.category = sanitize(newcategory) - if(href_list["setauthor"]) - var/newauthor = input("Enter an author to search for:") as text|null - if(newauthor) - query.author = sanitize(newauthor) - else - query.author = null - - if(href_list["page"]) - if(num_pages == 0) - page_num = 1 - else - page_num = Clamp(text2num(href_list["page"]), 1, num_pages) - - if(href_list["search"]) - num_results = src.get_num_results() - num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) - page_num = 1 - - screenstate = 1 - - if(href_list["back"]) - screenstate = 0 - - if(href_list["flag"]) - if(!dbcon.IsConnected()) - alert("Connection to Archive has been severed. Aborting.") - return - var/id = href_list["flag"] - if(id) - var/datum/cachedbook/B = getBookByID(id) - if(B) - if((input(usr, "Are you sure you want to flag [B.title] as having inappropriate content?", "Flag Book #[B.id]") in list("Yes", "No")) == "Yes") - library_catalog.flag_book_by_id(usr, id) - - add_fingerprint(usr) - updateUsrDialog() - return \ No newline at end of file +/obj/machinery/computer/library/public + name = "visitor computer" + +/obj/machinery/computer/library/public/attack_hand(var/mob/user as mob) + if(..()) + return + interact(user) + +/obj/machinery/computer/library/public/attackby(obj/item/W as obj, mob/user as mob) + if(default_unfasten_wrench(user, W)) + power_change() + return + return ..() + +/obj/machinery/computer/library/public/interact(var/mob/user) + if(interact_check(user)) + return + + var/dat = "" + switch(screenstate) + if(0) + + dat += {"

        Search Settings


        + Filter by Title: [query.title]
        + Filter by Category: [query.category]
        + Filter by Author: [query.author]
        + \[Start Search\]
        "} + if(1) + establish_db_connection() + if(!dbcon.IsConnected()) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
        " + else if(num_results == 0) + dat += "No results found." + else + var/pagelist = get_pagelist() + + dat += pagelist + dat += {"
        + + + +
        "} + dat += {" + + + + + + + "} + for(var/datum/cachedbook/CB in get_page(page_num)) + dat += {" + + + + + + "} + + dat += "
        AuthorTitleCategorySS13BNControls
        [CB.author][CB.title][CB.category][CB.id]\[Flag[CB.flagged ? "ged" : ""]\]

        [pagelist]" + dat += "\[Go Back\]
        " + var/datum/browser/B = new /datum/browser(user, "library", "Library Visitor") + B.set_content(dat) + B.open() + +/obj/machinery/computer/library/public/Topic(href, href_list) + if(..()) + usr << browse(null, "window=publiclibrary") + onclose(usr, "publiclibrary") + return + + if(href_list["pagenum"]) + if(!num_pages) + page_num = 1 + else + var/pn = text2num(href_list["pagenum"]) + if(!isnull(pn)) + page_num = Clamp(pn, 1, num_pages) + + if(href_list["settitle"]) + var/newtitle = input("Enter a title to search for:") as text|null + if(newtitle) + query.title = sanitize(newtitle) + else + query.title = null + if(href_list["setcategory"]) + var/newcategory = input("Choose a category to search for:") in (list("Any") + library_section_names) + if(newcategory == "Any") + query.category = null + else if(newcategory) + query.category = sanitize(newcategory) + if(href_list["setauthor"]) + var/newauthor = input("Enter an author to search for:") as text|null + if(newauthor) + query.author = sanitize(newauthor) + else + query.author = null + + if(href_list["page"]) + if(num_pages == 0) + page_num = 1 + else + page_num = Clamp(text2num(href_list["page"]), 1, num_pages) + + if(href_list["search"]) + num_results = src.get_num_results() + num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) + page_num = 1 + + screenstate = 1 + + if(href_list["back"]) + screenstate = 0 + + if(href_list["flag"]) + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + return + var/id = href_list["flag"] + if(id) + var/datum/cachedbook/B = getBookByID(id) + if(B) + if((input(usr, "Are you sure you want to flag [B.title] as having inappropriate content?", "Flag Book #[B.id]") in list("Yes", "No")) == "Yes") + library_catalog.flag_book_by_id(usr, id) + + add_fingerprint(usr) + updateUsrDialog() + return diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm index dabd25c0f74..a7dd148b51a 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -1,311 +1,311 @@ -/* Library Items - * - * Contains: - * Bookcase - * Book - * Barcode Scanner - */ - - -/* - * Bookcase - */ - -/obj/structure/bookcase - name = "bookcase" - icon = 'icons/obj/library.dmi' - icon_state = "book-0" - anchored = 1 - density = 1 - opacity = 1 - resistance_flags = FLAMMABLE - max_integrity = 200 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - var/tmp/busy = 0 - var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/bible, /obj/item/tome) //Things allowed in the bookcase - -/obj/structure/bookcase/Initialize() - ..() - for(var/obj/item/I in loc) - if(is_type_in_list(I, allowed_books)) - I.forceMove(src) - update_icon() - -/obj/structure/bookcase/attackby(obj/item/O as obj, mob/user as mob, params) - if(busy) //So that you can't mess with it while deconstructing - return 1 - if(is_type_in_list(O, allowed_books)) - if(!user.drop_item()) - return - O.forceMove(src) - update_icon() - return 1 - else if(istype(O, /obj/item/storage/bag/books)) - var/obj/item/storage/bag/books/B = O - for(var/obj/item/T in B.contents) - if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook) || istype(T, /obj/item/tome) || istype(T, /obj/item/storage/bible)) - B.remove_from_storage(T, src) - to_chat(user, "You empty [O] into [src].") - update_icon() - return 1 - else if(istype(O, /obj/item/wrench)) - user.visible_message("[user] starts disassembling \the [src].", \ - "You start disassembling \the [src].") - playsound(get_turf(src), O.usesound, 50, 1) - busy = 1 - - if(do_after(user, 50 * O.toolspeed, target = src)) - playsound(get_turf(src), O.usesound, 75, 1) - user.visible_message("[user] disassembles \the [src].", \ - "You disassemble \the [src].") - busy = 0 - density = 0 - deconstruct(TRUE) - else - busy = 0 - return 1 - else if(istype(O, /obj/item/pen)) - var/newname = stripped_input(user, "What would you like to title this [name]?") - if(newname) - name = ("bookcase ([sanitize(newname)])") - return 1 - else - return ..() - -/obj/structure/bookcase/attack_hand(var/mob/user as mob) - if(contents.len) - var/obj/item/book/choice = input("Which book would you like to remove from [src]?") as null|anything in contents - if(choice) - if(user.incapacitated() || user.lying || !Adjacent(user)) - return - if(!user.get_active_hand()) - user.put_in_hands(choice) - else - choice.forceMove(get_turf(src)) - update_icon() - -/obj/structure/bookcase/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/wood(loc, 5) - for(var/obj/item/I in contents) - if(is_type_in_list(I, allowed_books)) - I.forceMove(get_turf(src)) - qdel(src) - -/obj/structure/bookcase/update_icon() - if(contents.len < 5) - icon_state = "book-[contents.len]" - else - icon_state = "book-5" - - -/obj/structure/bookcase/manuals/medical - name = "Medical Manuals bookcase" - - New() - ..() - new /obj/item/book/manual/medical_cloning(src) - update_icon() - - -/obj/structure/bookcase/manuals/engineering - name = "Engineering Manuals bookcase" - - New() - ..() - new /obj/item/book/manual/engineering_construction(src) - new /obj/item/book/manual/engineering_particle_accelerator(src) - new /obj/item/book/manual/engineering_hacking(src) - new /obj/item/book/manual/engineering_guide(src) - new /obj/item/book/manual/engineering_singularity_safety(src) - new /obj/item/book/manual/robotics_cyborgs(src) - update_icon() - -/obj/structure/bookcase/manuals/research_and_development - name = "R&D Manuals bookcase" - - New() - ..() - new /obj/item/book/manual/research_and_development(src) - update_icon() - - -/* - * Book - */ -/obj/item/book - name = "book" - icon = 'icons/obj/library.dmi' - icon_state ="book" - throw_speed = 1 - throw_range = 5 - force = 2 - w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever) - attack_verb = list("bashed", "whacked") - resistance_flags = FLAMMABLE - - var/dat // Actual page content - var/due_date = 0 // Game time in 1/10th seconds - var/author // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - var/unique = 0 // 0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified - var/title // The real name of the book. - var/carved = 0 // Has the book been hollowed out for use as a secret storage item? - var/forbidden = 0 // Prevent ordering of this book. (0=no, 1=yes, 2=emag only) - var/obj/item/store // What's in the book? - -/obj/item/book/attack_self(var/mob/user as mob) - if(carved) - if(store) - to_chat(user, "[store] falls out of [title]!") - store.forceMove(get_turf(loc)) - store = null - return - else - to_chat(user, "The pages of [title] have been cut out!") - return - if(src.dat) - user << browse("Penned by [author].
        " + "[dat]", "window=book") - if(!isobserver(user)) - user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") - onclose(user, "book") - else - to_chat(user, "This book is completely blank!") - -/obj/item/book/attackby(obj/item/W as obj, mob/user as mob, params) - if(carved) - if(!store) - if(W.w_class < WEIGHT_CLASS_NORMAL) - user.drop_item() - W.forceMove(src) - store = W - to_chat(user, "You put [W] in [title].") - return 1 - else - to_chat(user, "[W] won't fit in [title].") - return 1 - else - to_chat(user, "There's already something in [title]!") - return 1 - if(istype(W, /obj/item/pen)) - if(unique) - to_chat(user, "These pages don't seem to take the ink well. Looks like you can't modify it.") - return 1 - var/choice = input("What would you like to change?") in list("Title", "Contents", "Author", "Cancel") - switch(choice) - if("Title") - var/newtitle = reject_bad_text(stripped_input(usr, "Write a new title:")) - if(!newtitle) - to_chat(usr, "The title is invalid.") - return 1 - else - src.name = newtitle - src.title = newtitle - if("Contents") - var/content = strip_html(input(usr, "Write your book's contents (HTML NOT allowed):") as message|null, MAX_BOOK_MESSAGE_LEN) - if(!content) - to_chat(usr, "The content is invalid.") - return 1 - else - src.dat += content - if("Author") - var/newauthor = stripped_input(usr, "Write the author's name:") - if(!newauthor) - to_chat(usr, "The name is invalid.") - return 1 - else - src.author = newauthor - return 1 - else if(istype(W, /obj/item/barcodescanner)) - var/obj/item/barcodescanner/scanner = W - if(!scanner.computer) - to_chat(user, "[W]'s screen flashes: 'No associated computer found!'") - else - switch(scanner.mode) - if(0) - scanner.book = src - to_chat(user, "[W]'s screen flashes: 'Book stored in buffer.'") - if(1) - scanner.book = src - scanner.computer.buffer_book = src.name - to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") - if(2) - scanner.book = src - for(var/datum/borrowbook/b in scanner.computer.checkouts) - if(b.bookname == src.name) - scanner.computer.checkouts.Remove(b) - to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") - return 1 - to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") - if(3) - scanner.book = src - for(var/obj/item/book in scanner.computer.inventory) - if(book == src) - to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") - return 1 - scanner.computer.inventory.Add(src) - to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") - return 1 - else if(istype(W, /obj/item/kitchen/knife) && !carved) - carve_book(user, W) - else - return ..() - -/obj/item/book/wirecutter_act(mob/user, obj/item/I) - return carve_book(user, I) - -/obj/item/book/attack(mob/M, mob/living/user) - if(user.a_intent == INTENT_HELP) - force = 0 - attack_verb = list("educated") - else - force = initial(force) - attack_verb = list("bashed", "whacked") - ..() - -/obj/item/book/proc/carve_book(mob/user, obj/item/I) - if(!I.sharp && I.tool_behaviour != TOOL_WIRECUTTER) //Only sharp and wirecutter things can carve books - to_chat(user, "") - return - if(carved) - return - to_chat(user, "You begin to carve out [title].") - if(I.use_tool(src, user, 30, volume = I.tool_volume)) - to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") - carved = TRUE - return TRUE -/* - * Barcode Scanner - */ -/obj/item/barcodescanner - name = "barcode scanner" - icon = 'icons/obj/library.dmi' - icon_state ="scanner" - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/obj/machinery/computer/library/checkout/computer // Associated computer - Modes 1 to 3 use this - var/obj/item/book/book // Currently scanned book - var/mode = 0 // 0 - Scan only, 1 - Scan and Set Buffer, 2 - Scan and Attempt to Check In, 3 - Scan and Attempt to Add to Inventory - - attack_self(mob/user as mob) - mode += 1 - if(mode > 3) - mode = 0 - to_chat(user, "[src] Status Display:") - var/modedesc - switch(mode) - if(0) - modedesc = "Scan book to local buffer." - if(1) - modedesc = "Scan book to local buffer and set associated computer buffer to match." - if(2) - modedesc = "Scan book to local buffer, attempt to check in scanned book." - if(3) - modedesc = "Scan book to local buffer, attempt to add book to general inventory." - else - modedesc = "ERROR" - to_chat(user, " - Mode [mode] : [modedesc]") - if(src.computer) - to_chat(user, "Computer has been associated with this unit.") - else - to_chat(user, "No associated computer found. Only local scans will function properly.") - to_chat(user, "\n") +/* Library Items + * + * Contains: + * Bookcase + * Book + * Barcode Scanner + */ + + +/* + * Bookcase + */ + +/obj/structure/bookcase + name = "bookcase" + icon = 'icons/obj/library.dmi' + icon_state = "book-0" + anchored = 1 + density = 1 + opacity = 1 + resistance_flags = FLAMMABLE + max_integrity = 200 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) + var/tmp/busy = 0 + var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/bible, /obj/item/tome) //Things allowed in the bookcase + +/obj/structure/bookcase/Initialize() + ..() + for(var/obj/item/I in loc) + if(is_type_in_list(I, allowed_books)) + I.forceMove(src) + update_icon() + +/obj/structure/bookcase/attackby(obj/item/O as obj, mob/user as mob, params) + if(busy) //So that you can't mess with it while deconstructing + return 1 + if(is_type_in_list(O, allowed_books)) + if(!user.drop_item()) + return + O.forceMove(src) + update_icon() + return 1 + else if(istype(O, /obj/item/storage/bag/books)) + var/obj/item/storage/bag/books/B = O + for(var/obj/item/T in B.contents) + if(istype(T, /obj/item/book) || istype(T, /obj/item/spellbook) || istype(T, /obj/item/tome) || istype(T, /obj/item/storage/bible)) + B.remove_from_storage(T, src) + to_chat(user, "You empty [O] into [src].") + update_icon() + return 1 + else if(istype(O, /obj/item/wrench)) + user.visible_message("[user] starts disassembling \the [src].", \ + "You start disassembling \the [src].") + playsound(get_turf(src), O.usesound, 50, 1) + busy = 1 + + if(do_after(user, 50 * O.toolspeed, target = src)) + playsound(get_turf(src), O.usesound, 75, 1) + user.visible_message("[user] disassembles \the [src].", \ + "You disassemble \the [src].") + busy = 0 + density = 0 + deconstruct(TRUE) + else + busy = 0 + return 1 + else if(istype(O, /obj/item/pen)) + var/newname = stripped_input(user, "What would you like to title this [name]?") + if(newname) + name = ("bookcase ([sanitize(newname)])") + return 1 + else + return ..() + +/obj/structure/bookcase/attack_hand(var/mob/user as mob) + if(contents.len) + var/obj/item/book/choice = input("Which book would you like to remove from [src]?") as null|anything in contents + if(choice) + if(user.incapacitated() || user.lying || !Adjacent(user)) + return + if(!user.get_active_hand()) + user.put_in_hands(choice) + else + choice.forceMove(get_turf(src)) + update_icon() + +/obj/structure/bookcase/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/wood(loc, 5) + for(var/obj/item/I in contents) + if(is_type_in_list(I, allowed_books)) + I.forceMove(get_turf(src)) + qdel(src) + +/obj/structure/bookcase/update_icon() + if(contents.len < 5) + icon_state = "book-[contents.len]" + else + icon_state = "book-5" + + +/obj/structure/bookcase/manuals/medical + name = "Medical Manuals bookcase" + + New() + ..() + new /obj/item/book/manual/medical_cloning(src) + update_icon() + + +/obj/structure/bookcase/manuals/engineering + name = "Engineering Manuals bookcase" + + New() + ..() + new /obj/item/book/manual/engineering_construction(src) + new /obj/item/book/manual/engineering_particle_accelerator(src) + new /obj/item/book/manual/engineering_hacking(src) + new /obj/item/book/manual/engineering_guide(src) + new /obj/item/book/manual/engineering_singularity_safety(src) + new /obj/item/book/manual/robotics_cyborgs(src) + update_icon() + +/obj/structure/bookcase/manuals/research_and_development + name = "R&D Manuals bookcase" + + New() + ..() + new /obj/item/book/manual/research_and_development(src) + update_icon() + + +/* + * Book + */ +/obj/item/book + name = "book" + icon = 'icons/obj/library.dmi' + icon_state ="book" + throw_speed = 1 + throw_range = 5 + force = 2 + w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever) + attack_verb = list("bashed", "whacked") + resistance_flags = FLAMMABLE + + var/dat // Actual page content + var/due_date = 0 // Game time in 1/10th seconds + var/author // Who wrote the thing, can be changed by pen or PC. It is not automatically assigned + var/unique = 0 // 0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified + var/title // The real name of the book. + var/carved = 0 // Has the book been hollowed out for use as a secret storage item? + var/forbidden = 0 // Prevent ordering of this book. (0=no, 1=yes, 2=emag only) + var/obj/item/store // What's in the book? + +/obj/item/book/attack_self(var/mob/user as mob) + if(carved) + if(store) + to_chat(user, "[store] falls out of [title]!") + store.forceMove(get_turf(loc)) + store = null + return + else + to_chat(user, "The pages of [title] have been cut out!") + return + if(src.dat) + user << browse("Penned by [author].
        " + "[dat]", "window=book") + if(!isobserver(user)) + user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + onclose(user, "book") + else + to_chat(user, "This book is completely blank!") + +/obj/item/book/attackby(obj/item/W as obj, mob/user as mob, params) + if(carved) + if(!store) + if(W.w_class < WEIGHT_CLASS_NORMAL) + user.drop_item() + W.forceMove(src) + store = W + to_chat(user, "You put [W] in [title].") + return 1 + else + to_chat(user, "[W] won't fit in [title].") + return 1 + else + to_chat(user, "There's already something in [title]!") + return 1 + if(istype(W, /obj/item/pen)) + if(unique) + to_chat(user, "These pages don't seem to take the ink well. Looks like you can't modify it.") + return 1 + var/choice = input("What would you like to change?") in list("Title", "Contents", "Author", "Cancel") + switch(choice) + if("Title") + var/newtitle = reject_bad_text(stripped_input(usr, "Write a new title:")) + if(!newtitle) + to_chat(usr, "The title is invalid.") + return 1 + else + src.name = newtitle + src.title = newtitle + if("Contents") + var/content = strip_html(input(usr, "Write your book's contents (HTML NOT allowed):") as message|null, MAX_BOOK_MESSAGE_LEN) + if(!content) + to_chat(usr, "The content is invalid.") + return 1 + else + src.dat += content + if("Author") + var/newauthor = stripped_input(usr, "Write the author's name:") + if(!newauthor) + to_chat(usr, "The name is invalid.") + return 1 + else + src.author = newauthor + return 1 + else if(istype(W, /obj/item/barcodescanner)) + var/obj/item/barcodescanner/scanner = W + if(!scanner.computer) + to_chat(user, "[W]'s screen flashes: 'No associated computer found!'") + else + switch(scanner.mode) + if(0) + scanner.book = src + to_chat(user, "[W]'s screen flashes: 'Book stored in buffer.'") + if(1) + scanner.book = src + scanner.computer.buffer_book = src.name + to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") + if(2) + scanner.book = src + for(var/datum/borrowbook/b in scanner.computer.checkouts) + if(b.bookname == src.name) + scanner.computer.checkouts.Remove(b) + to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") + return 1 + to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") + if(3) + scanner.book = src + for(var/obj/item/book in scanner.computer.inventory) + if(book == src) + to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") + return 1 + scanner.computer.inventory.Add(src) + to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") + return 1 + else if(istype(W, /obj/item/kitchen/knife) && !carved) + carve_book(user, W) + else + return ..() + +/obj/item/book/wirecutter_act(mob/user, obj/item/I) + return carve_book(user, I) + +/obj/item/book/attack(mob/M, mob/living/user) + if(user.a_intent == INTENT_HELP) + force = 0 + attack_verb = list("educated") + else + force = initial(force) + attack_verb = list("bashed", "whacked") + ..() + +/obj/item/book/proc/carve_book(mob/user, obj/item/I) + if(!I.sharp && I.tool_behaviour != TOOL_WIRECUTTER) //Only sharp and wirecutter things can carve books + to_chat(user, "") + return + if(carved) + return + to_chat(user, "You begin to carve out [title].") + if(I.use_tool(src, user, 30, volume = I.tool_volume)) + to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") + carved = TRUE + return TRUE +/* + * Barcode Scanner + */ +/obj/item/barcodescanner + name = "barcode scanner" + icon = 'icons/obj/library.dmi' + icon_state ="scanner" + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + var/obj/machinery/computer/library/checkout/computer // Associated computer - Modes 1 to 3 use this + var/obj/item/book/book // Currently scanned book + var/mode = 0 // 0 - Scan only, 1 - Scan and Set Buffer, 2 - Scan and Attempt to Check In, 3 - Scan and Attempt to Add to Inventory + + attack_self(mob/user as mob) + mode += 1 + if(mode > 3) + mode = 0 + to_chat(user, "[src] Status Display:") + var/modedesc + switch(mode) + if(0) + modedesc = "Scan book to local buffer." + if(1) + modedesc = "Scan book to local buffer and set associated computer buffer to match." + if(2) + modedesc = "Scan book to local buffer, attempt to check in scanned book." + if(3) + modedesc = "Scan book to local buffer, attempt to add book to general inventory." + else + modedesc = "ERROR" + to_chat(user, " - Mode [mode] : [modedesc]") + if(src.computer) + to_chat(user, "Computer has been associated with this unit.") + else + to_chat(user, "No associated computer found. Only local scans will function properly.") + to_chat(user, "\n") diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 611b84f8100..5729d3d7fa5 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -1,230 +1,230 @@ -#define LIBRARY_BOOKS_PER_PAGE 25 - -var/global/datum/library_catalog/library_catalog = new() -var/global/list/library_section_names = list("Any", "Fiction", "Non-Fiction", "Adult", "Reference", "Religion") - - -/hook/startup/proc/load_manuals() - library_catalog.initialize() - return 1 - -/* - * Borrowbook datum - */ -/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. - var/bookname - var/mobname - var/getdate - var/duedate - -/* - * Cachedbook datum - */ -/datum/cachedbook // Datum used to cache the SQL DB books locally in order to achieve a performance gain. - var/id - var/title - var/author - var/ckey // ADDED 24/2/2015 - N3X - var/category - var/content - var/programmatic=0 // Is the book programmatically added to the catalog? - var/forbidden=0 - var/path = /obj/item/book // Type path of the book to generate - var/flagged = 0 - -/datum/cachedbook/proc/LoadFromRow(var/list/row) - id = row["id"] - author = row["author"] - title = row["title"] - category = row["category"] - ckey = row["ckey"] - flagged = row["flagged"] - if("content" in row) - content = row["content"] - programmatic=0 - -// Builds a SQL statement -/datum/library_query - var/author - var/category - var/title - -/datum/library_query/proc/toSQL() - var/list/where = list() - if(author || title || category) - if(author) - where.Add("author LIKE '%[sanitizeSQL(author)]%'") - if(category) - where.Add("category = '[sanitizeSQL(category)]'") - if(title) - where.Add("title LIKE '%[sanitizeSQL(title)]%'") - return " WHERE " + jointext(where, " AND ") - return "" - -// So we can have catalogs of books that are programmatic, and ones that aren't. -/datum/library_catalog - var/list/cached_books = list() - -/datum/library_catalog/proc/initialize() - var/newid=1 - for(var/typepath in subtypesof(/obj/item/book/manual)) - var/obj/item/book/B = new typepath(null) - var/datum/cachedbook/CB = new() - CB.forbidden = B.forbidden - CB.title = B.name - CB.author = B.author - CB.programmatic=1 - CB.path=typepath - CB.id = "M[newid]" - newid++ - cached_books["[CB.id]"]=CB - -/datum/library_catalog/proc/flag_book_by_id(mob/user, id) - var/global/books_flagged_this_round[0] - - if("[id]" in cached_books) - var/datum/cachedbook/CB = cached_books["[id]"] - if(CB.programmatic) - to_chat(user, "That book cannot be flagged in the system, as it does not actually exist in the database.") - return - - if("[id]" in books_flagged_this_round) - to_chat(user, "This book has already been flagged this shift.") - return - - books_flagged_this_round["[id]"] = 1 - message_admins("[key_name_admin(user)] has flagged book #[id] as inappropriate.") - - var/sqlid = text2num(id) - if(!sqlid) - return - var/DBQuery/query = dbcon.NewQuery("UPDATE [format_table_name("library")] SET flagged = flagged + 1 WHERE id=[sqlid]") - query.Execute() - -/datum/library_catalog/proc/rmBookByID(mob/user, id) - if("[id]" in cached_books) - var/datum/cachedbook/CB = cached_books["[id]"] - if(CB.programmatic) - to_chat(user, "That book cannot be removed from the system, as it does not actually exist in the database.") - return - - var/sqlid = text2num(id) - if(!sqlid) - return - var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[sqlid]") - query.Execute() - -/datum/library_catalog/proc/getBookByID(id) - if("[id]" in cached_books) - return cached_books["[id]"] - - var/sqlid = text2num(id) - if(!sqlid) - return - var/DBQuery/query = dbcon.NewQuery("SELECT id, author, title, category, content, ckey, flagged FROM [format_table_name("library")] WHERE id=[sqlid]") - query.Execute() - - var/list/results=list() - while(query.NextRow()) - var/datum/cachedbook/CB = new() - CB.LoadFromRow(list( - "id" =query.item[1], - "author" =query.item[2], - "title" =query.item[3], - "category"=query.item[4], - "content" =query.item[5], - "ckey" =query.item[6], - "flagged" =query.item[7] - )) - results += CB - cached_books["[id]"]=CB - return CB - return results - -/** Scanner **/ -/obj/machinery/libraryscanner - name = "scanner" - icon = 'icons/obj/library.dmi' - icon_state = "bigscanner" - anchored = 1 - density = 1 - var/obj/item/book/cache // Last scanned book - -/obj/machinery/libraryscanner/attackby(obj/item/I, mob/user) - if(default_unfasten_wrench(user, I)) - power_change() - return - if(istype(I, /obj/item/book)) - user.drop_item() - I.forceMove(src) - return 1 - else - return ..() - -/obj/machinery/libraryscanner/attack_hand(mob/user) - if(istype(user,/mob/dead)) - to_chat(user, "Nope.") - return - usr.set_machine(src) - var/dat = "Scanner Control Interface\n" // - if(cache) - dat += "Data stored in memory.
        " - else - dat += "No data stored in memory.
        " - dat += "\[Scan\]" - if(cache) - dat += " \[Clear Memory\]

        \[Remove Book\]" - else - dat += "
        " - user << browse(dat, "window=scanner") - onclose(user, "scanner") - -/obj/machinery/libraryscanner/Topic(href, href_list) - if(..()) - usr << browse(null, "window=scanner") - onclose(usr, "scanner") - return - - if(href_list["scan"]) - for(var/obj/item/book/B in contents) - cache = B - break - if(href_list["clear"]) - cache = null - if(href_list["eject"]) - for(var/obj/item/book/B in contents) - B.loc = src.loc - src.add_fingerprint(usr) - src.updateUsrDialog() - return - - -/* - * Book binder - */ -/obj/machinery/bookbinder - name = "Book Binder" - icon = 'icons/obj/library.dmi' - icon_state = "binder" - anchored = 1 - density = 1 - -/obj/machinery/bookbinder/attackby(obj/item/I, mob/user) - var/obj/item/paper/P = I - if(default_unfasten_wrench(user, I)) - power_change() - return - if(istype(P)) - user.drop_item() - user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") - src.visible_message("[src] begins to hum as it warms up its printing drums.") - sleep(rand(200,400)) - src.visible_message("[src] whirs as it prints and binds a new book.") - var/obj/item/book/b = new(loc) - b.dat = P.info - b.name = "Print Job #[rand(100, 999)]" - b.icon_state = "book[rand(1,16)]" - qdel(P) - return 1 - else - return ..() +#define LIBRARY_BOOKS_PER_PAGE 25 + +var/global/datum/library_catalog/library_catalog = new() +var/global/list/library_section_names = list("Any", "Fiction", "Non-Fiction", "Adult", "Reference", "Religion") + + +/hook/startup/proc/load_manuals() + library_catalog.initialize() + return 1 + +/* + * Borrowbook datum + */ +/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. + var/bookname + var/mobname + var/getdate + var/duedate + +/* + * Cachedbook datum + */ +/datum/cachedbook // Datum used to cache the SQL DB books locally in order to achieve a performance gain. + var/id + var/title + var/author + var/ckey // ADDED 24/2/2015 - N3X + var/category + var/content + var/programmatic=0 // Is the book programmatically added to the catalog? + var/forbidden=0 + var/path = /obj/item/book // Type path of the book to generate + var/flagged = 0 + +/datum/cachedbook/proc/LoadFromRow(var/list/row) + id = row["id"] + author = row["author"] + title = row["title"] + category = row["category"] + ckey = row["ckey"] + flagged = row["flagged"] + if("content" in row) + content = row["content"] + programmatic=0 + +// Builds a SQL statement +/datum/library_query + var/author + var/category + var/title + +/datum/library_query/proc/toSQL() + var/list/where = list() + if(author || title || category) + if(author) + where.Add("author LIKE '%[sanitizeSQL(author)]%'") + if(category) + where.Add("category = '[sanitizeSQL(category)]'") + if(title) + where.Add("title LIKE '%[sanitizeSQL(title)]%'") + return " WHERE " + jointext(where, " AND ") + return "" + +// So we can have catalogs of books that are programmatic, and ones that aren't. +/datum/library_catalog + var/list/cached_books = list() + +/datum/library_catalog/proc/initialize() + var/newid=1 + for(var/typepath in subtypesof(/obj/item/book/manual)) + var/obj/item/book/B = new typepath(null) + var/datum/cachedbook/CB = new() + CB.forbidden = B.forbidden + CB.title = B.name + CB.author = B.author + CB.programmatic=1 + CB.path=typepath + CB.id = "M[newid]" + newid++ + cached_books["[CB.id]"]=CB + +/datum/library_catalog/proc/flag_book_by_id(mob/user, id) + var/global/books_flagged_this_round[0] + + if("[id]" in cached_books) + var/datum/cachedbook/CB = cached_books["[id]"] + if(CB.programmatic) + to_chat(user, "That book cannot be flagged in the system, as it does not actually exist in the database.") + return + + if("[id]" in books_flagged_this_round) + to_chat(user, "This book has already been flagged this shift.") + return + + books_flagged_this_round["[id]"] = 1 + message_admins("[key_name_admin(user)] has flagged book #[id] as inappropriate.") + + var/sqlid = text2num(id) + if(!sqlid) + return + var/DBQuery/query = dbcon.NewQuery("UPDATE [format_table_name("library")] SET flagged = flagged + 1 WHERE id=[sqlid]") + query.Execute() + +/datum/library_catalog/proc/rmBookByID(mob/user, id) + if("[id]" in cached_books) + var/datum/cachedbook/CB = cached_books["[id]"] + if(CB.programmatic) + to_chat(user, "That book cannot be removed from the system, as it does not actually exist in the database.") + return + + var/sqlid = text2num(id) + if(!sqlid) + return + var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[sqlid]") + query.Execute() + +/datum/library_catalog/proc/getBookByID(id) + if("[id]" in cached_books) + return cached_books["[id]"] + + var/sqlid = text2num(id) + if(!sqlid) + return + var/DBQuery/query = dbcon.NewQuery("SELECT id, author, title, category, content, ckey, flagged FROM [format_table_name("library")] WHERE id=[sqlid]") + query.Execute() + + var/list/results=list() + while(query.NextRow()) + var/datum/cachedbook/CB = new() + CB.LoadFromRow(list( + "id" =query.item[1], + "author" =query.item[2], + "title" =query.item[3], + "category"=query.item[4], + "content" =query.item[5], + "ckey" =query.item[6], + "flagged" =query.item[7] + )) + results += CB + cached_books["[id]"]=CB + return CB + return results + +/** Scanner **/ +/obj/machinery/libraryscanner + name = "scanner" + icon = 'icons/obj/library.dmi' + icon_state = "bigscanner" + anchored = 1 + density = 1 + var/obj/item/book/cache // Last scanned book + +/obj/machinery/libraryscanner/attackby(obj/item/I, mob/user) + if(default_unfasten_wrench(user, I)) + power_change() + return + if(istype(I, /obj/item/book)) + user.drop_item() + I.forceMove(src) + return 1 + else + return ..() + +/obj/machinery/libraryscanner/attack_hand(mob/user) + if(istype(user,/mob/dead)) + to_chat(user, "Nope.") + return + usr.set_machine(src) + var/dat = "Scanner Control Interface\n" // + if(cache) + dat += "Data stored in memory.
        " + else + dat += "No data stored in memory.
        " + dat += "\[Scan\]" + if(cache) + dat += " \[Clear Memory\]

        \[Remove Book\]" + else + dat += "
        " + user << browse(dat, "window=scanner") + onclose(user, "scanner") + +/obj/machinery/libraryscanner/Topic(href, href_list) + if(..()) + usr << browse(null, "window=scanner") + onclose(usr, "scanner") + return + + if(href_list["scan"]) + for(var/obj/item/book/B in contents) + cache = B + break + if(href_list["clear"]) + cache = null + if(href_list["eject"]) + for(var/obj/item/book/B in contents) + B.loc = src.loc + src.add_fingerprint(usr) + src.updateUsrDialog() + return + + +/* + * Book binder + */ +/obj/machinery/bookbinder + name = "Book Binder" + icon = 'icons/obj/library.dmi' + icon_state = "binder" + anchored = 1 + density = 1 + +/obj/machinery/bookbinder/attackby(obj/item/I, mob/user) + var/obj/item/paper/P = I + if(default_unfasten_wrench(user, I)) + power_change() + return + if(istype(P)) + user.drop_item() + user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") + src.visible_message("[src] begins to hum as it warms up its printing drums.") + sleep(rand(200,400)) + src.visible_message("[src] whirs as it prints and binds a new book.") + var/obj/item/book/b = new(loc) + b.dat = P.info + b.name = "Print Job #[rand(100, 999)]" + b.icon_state = "book[rand(1,16)]" + qdel(P) + return 1 + else + return ..() diff --git a/code/modules/library/lib_readme.dm b/code/modules/library/lib_readme.dm index 4237687dfff..0150ed6ba8a 100644 --- a/code/modules/library/lib_readme.dm +++ b/code/modules/library/lib_readme.dm @@ -1,61 +1,61 @@ -//******************************* -// -// Library SQL Configuration -// -//******************************* - -// Deprecated! See global.dm for new SQL config vars -- TLE -/* -#define SQL_ADDRESS "" -#define SQL_DB "" -#define SQL_PORT "3306" -#define SQL_LOGIN "" -#define SQL_PASS "" -*/ - -//******************************* -// Requires Dantom.DB library ( http://www.byond.com/developer/Dantom/DB ) - - -/* - The Library - ------------ - A place for the crew to go, relax, and enjoy a good book. - Aspiring authors can even self publish and, if they're lucky - convince the on-staff Librarian to submit it to the Archives - to be chronicled in history forever - some say even persisting - through alternate dimensions. - - - Written by TLE for /tg/station 13 - Feel free to use this as you like. Some credit would be cool. - Check us out at http://nanotrasen.com/ if you're so inclined. -*/ - -// CONTAINS: - -// Objects: -// - bookcase -// - book -// - barcode scanner -// Machinery: -// - library computer -// - visitor's computer -// - book binder -// - book scanner -// Datum: -// - borrowbook - - -// Ideas for the future -// --------------------- -// - Visitor's computer should be able to search the current in-round library inventory (that the Librarian has stocked and checked in) -// -- Give computer other features like an Instant Messenger application, or the ability to edit, save, and print documents. -// - Admin interface directly tied to the Archive DB. Right now there's no way to delete uploaded books in-game. -// -- If this gets implemented, allow Librarians to "tag" or "suggest" books to be deleted. The DB ID of the tagged books gets saved to a text file (or another table in the DB maybe?). -// The admin interface would automatically take these IDs and SELECT them all from the DB to be displayed along with a Delete link to drop the row from the table. -// - When the game sets up and the round begins, have it automatically pick random books from the DB to populate the library with. Even if the Librarian is a useless fuck there are at least a few books around. -// - Allow books to be "hollowed out" like the Chaplain's Bible, allowing you to store one pocket-sized item inside. -// - Make books/book cases burn when exposed to flame. -// - Make book binder hackable. -// - Books shouldn't print straight from the library computer. Make it synch with a machine like the book binder to print instead. This should consume some sort of resource. +//******************************* +// +// Library SQL Configuration +// +//******************************* + +// Deprecated! See global.dm for new SQL config vars -- TLE +/* +#define SQL_ADDRESS "" +#define SQL_DB "" +#define SQL_PORT "3306" +#define SQL_LOGIN "" +#define SQL_PASS "" +*/ + +//******************************* +// Requires Dantom.DB library ( http://www.byond.com/developer/Dantom/DB ) + + +/* + The Library + ------------ + A place for the crew to go, relax, and enjoy a good book. + Aspiring authors can even self publish and, if they're lucky + convince the on-staff Librarian to submit it to the Archives + to be chronicled in history forever - some say even persisting + through alternate dimensions. + + + Written by TLE for /tg/station 13 + Feel free to use this as you like. Some credit would be cool. + Check us out at http://nanotrasen.com/ if you're so inclined. +*/ + +// CONTAINS: + +// Objects: +// - bookcase +// - book +// - barcode scanner +// Machinery: +// - library computer +// - visitor's computer +// - book binder +// - book scanner +// Datum: +// - borrowbook + + +// Ideas for the future +// --------------------- +// - Visitor's computer should be able to search the current in-round library inventory (that the Librarian has stocked and checked in) +// -- Give computer other features like an Instant Messenger application, or the ability to edit, save, and print documents. +// - Admin interface directly tied to the Archive DB. Right now there's no way to delete uploaded books in-game. +// -- If this gets implemented, allow Librarians to "tag" or "suggest" books to be deleted. The DB ID of the tagged books gets saved to a text file (or another table in the DB maybe?). +// The admin interface would automatically take these IDs and SELECT them all from the DB to be displayed along with a Delete link to drop the row from the table. +// - When the game sets up and the round begins, have it automatically pick random books from the DB to populate the library with. Even if the Librarian is a useless fuck there are at least a few books around. +// - Allow books to be "hollowed out" like the Chaplain's Bible, allowing you to store one pocket-sized item inside. +// - Make books/book cases burn when exposed to flame. +// - Make book binder hackable. +// - Books shouldn't print straight from the library computer. Make it synch with a machine like the book binder to print instead. This should consume some sort of resource. diff --git a/code/modules/library/random_books.dm b/code/modules/library/random_books.dm index f36e493ce3f..31d49489561 100644 --- a/code/modules/library/random_books.dm +++ b/code/modules/library/random_books.dm @@ -82,4 +82,4 @@ . = ..() while(book_count > 0 && prob(ref_book_prob)) book_count-- - new /obj/item/book/manual/random(src) \ No newline at end of file + new /obj/item/book/manual/random(src) diff --git a/code/modules/lighting/__lighting_docs.dm b/code/modules/lighting/__lighting_docs.dm index 18041989910..e2a882a7679 100644 --- a/code/modules/lighting/__lighting_docs.dm +++ b/code/modules/lighting/__lighting_docs.dm @@ -64,4 +64,4 @@ atom/movable/lighting_object: (lighting_object.dm) - Change the lumcount vars and queue the overlay for update - proc/update_overlay() - Called by the lighting process to update the color of the overlay -*/ \ No newline at end of file +*/ diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm index 77e955d10a6..f08128b156f 100644 --- a/code/modules/lighting/lighting_area.dm +++ b/code/modules/lighting/lighting_area.dm @@ -27,4 +27,4 @@ if("dynamic_lighting") set_dynamic_lighting(var_value) return TRUE - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm index d8b05d0c17f..bc521114d56 100644 --- a/code/modules/lighting/lighting_corner.dm +++ b/code/modules/lighting/lighting_corner.dm @@ -140,4 +140,4 @@ GLOBAL_LIST_INIT(LIGHTING_CORNER_DIAGONAL, list(NORTHEAST, SOUTHEAST, SOUTHWEST, stack_trace("Attempted qdel of a lighting corner.") - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/lighting/lighting_object.dm b/code/modules/lighting/lighting_object.dm index c755da03b2b..ea3e954fa6e 100644 --- a/code/modules/lighting/lighting_object.dm +++ b/code/modules/lighting/lighting_object.dm @@ -145,4 +145,4 @@ // Override here to prevent things accidentally moving around overlays. /atom/movable/lighting_object/forceMove(atom/destination, var/no_tp=FALSE, var/harderforce = FALSE) if(harderforce) - . = ..() \ No newline at end of file + . = ..() diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm index ed8b7e71d6c..9923006815f 100644 --- a/code/modules/lighting/lighting_setup.dm +++ b/code/modules/lighting/lighting_setup.dm @@ -9,4 +9,4 @@ new/atom/movable/lighting_object(T) CHECK_TICK - CHECK_TICK \ No newline at end of file + CHECK_TICK diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm index 207a91d58b4..8e54531f901 100644 --- a/code/modules/lighting/lighting_source.dm +++ b/code/modules/lighting/lighting_source.dm @@ -295,4 +295,4 @@ #undef EFFECT_UPDATE #undef LUM_FALLOFF #undef REMOVE_CORNER -#undef APPLY_CORNER \ No newline at end of file +#undef APPLY_CORNER diff --git a/code/modules/logic/logic_base.dm b/code/modules/logic/logic_base.dm index e2e4450c982..542cb09bc7f 100644 --- a/code/modules/logic/logic_base.dm +++ b/code/modules/logic/logic_base.dm @@ -281,4 +281,4 @@ if(tamperproof) tesla_zap(src, 3, power) //If we're tamperproof, we'll just bounce the full shock of the tesla zap we got hit by, so it continues on normally without diminishing return 0 - ..() \ No newline at end of file + ..() diff --git a/code/modules/map_fluff/maps.dm b/code/modules/map_fluff/maps.dm index 638baffde73..b74ed7c9f3b 100644 --- a/code/modules/map_fluff/maps.dm +++ b/code/modules/map_fluff/maps.dm @@ -9,4 +9,4 @@ var/datum/map/using_map = new USING_MAP_DATUM var/dock_name = "THE PirateBay" var/company_name = "BadMan" var/company_short = "BM" - var/starsys_name = "Dull Star" \ No newline at end of file + var/starsys_name = "Dull Star" diff --git a/code/modules/martial_arts/cqc.dm b/code/modules/martial_arts/cqc.dm index 556cfded723..f0b5c016c5b 100644 --- a/code/modules/martial_arts/cqc.dm +++ b/code/modules/martial_arts/cqc.dm @@ -232,4 +232,4 @@ to_chat(usr, "Pressure: Disarm Grab. Decent stamina damage.") to_chat(usr, "Consecutive CQC: Disarm Disarm Harm. Mainly offensive move, huge damage and decent stamina damage.") - to_chat(usr, "In addition, by having your throw mode on when being attacked, you enter an active defense mode where you have a chance to block and sometimes even counter attacks done to you.") \ No newline at end of file + to_chat(usr, "In addition, by having your throw mode on when being attacked, you enter an active defense mode where you have a chance to block and sometimes even counter attacks done to you.") diff --git a/code/modules/martial_arts/plasma_fist.dm b/code/modules/martial_arts/plasma_fist.dm index 5aed9b8e9e1..ea5aef5ea74 100644 --- a/code/modules/martial_arts/plasma_fist.dm +++ b/code/modules/martial_arts/plasma_fist.dm @@ -83,4 +83,4 @@ to_chat(usr, "You clench your fists and have a flashback of knowledge...") to_chat(usr, "Tornado Sweep: Harm Harm Disarm. Repulses target and everyone back.") to_chat(usr, "Throwback: Disarm Harm Disarm. Throws the target and an item at them.") - to_chat(usr, "The Plasma Fist: Harm Disarm Disarm Disarm Harm. Knocks the brain out of the opponent and gibs their body.") \ No newline at end of file + to_chat(usr, "The Plasma Fist: Harm Disarm Disarm Disarm Harm. Knocks the brain out of the opponent and gibs their body.") diff --git a/code/modules/mining/abandonedcrates.dm b/code/modules/mining/abandonedcrates.dm index 90ee1d58e05..454b31b3b74 100644 --- a/code/modules/mining/abandonedcrates.dm +++ b/code/modules/mining/abandonedcrates.dm @@ -214,4 +214,4 @@ ..() /obj/structure/closet/crate/secure/loot/deconstruct(disassembled = TRUE) - boom() \ No newline at end of file + boom() diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm index 261b7ca9b90..b4a5a0891ed 100644 --- a/code/modules/mining/equipment/explorer_gear.dm +++ b/code/modules/mining/equipment/explorer_gear.dm @@ -94,4 +94,4 @@ cut_overlays() var/mutable_appearance/glass_overlay = mutable_appearance(icon, "hostile_env_glass") glass_overlay.appearance_flags = RESET_COLOR - add_overlay(glass_overlay) \ No newline at end of file + add_overlay(glass_overlay) diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index ee3eb10598a..f21978db625 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -439,4 +439,4 @@ new /obj/effect/temp_visual/hierophant/wall/crusher(otherT, user) /obj/effect/temp_visual/hierophant/wall/crusher - duration = 75 \ No newline at end of file + duration = 75 diff --git a/code/modules/mining/equipment/lazarus_injector.dm b/code/modules/mining/equipment/lazarus_injector.dm index e2a7c192153..2114570ebea 100644 --- a/code/modules/mining/equipment/lazarus_injector.dm +++ b/code/modules/mining/equipment/lazarus_injector.dm @@ -119,4 +119,4 @@ if(colorindex >= 6) colorindex = 0 icon_state = "mobcap[colorindex]" - update_icon() \ No newline at end of file + update_icon() diff --git a/code/modules/mining/equipment/marker_beacons.dm b/code/modules/mining/equipment/marker_beacons.dm index 00e838bdf87..e1456c475de 100644 --- a/code/modules/mining/equipment/marker_beacons.dm +++ b/code/modules/mining/equipment/marker_beacons.dm @@ -137,4 +137,4 @@ GLOBAL_LIST_INIT(marker_beacon_colors, list( return if(input_color) picked_color = input_color - update_icon() \ No newline at end of file + update_icon() diff --git a/code/modules/mining/equipment/mineral_scanner.dm b/code/modules/mining/equipment/mineral_scanner.dm index c04dba42525..84c8304316d 100644 --- a/code/modules/mining/equipment/mineral_scanner.dm +++ b/code/modules/mining/equipment/mineral_scanner.dm @@ -82,4 +82,4 @@ /obj/effect/temp_visual/mining_overlay/Initialize(mapload) . = ..() - animate(src, alpha = 0, time = duration, easing = EASE_IN) \ No newline at end of file + animate(src, alpha = 0, time = duration, easing = EASE_IN) diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm index 4d47f88e065..7815bad0c37 100644 --- a/code/modules/mining/equipment/mining_tools.dm +++ b/code/modules/mining/equipment/mining_tools.dm @@ -139,4 +139,4 @@ desc = "A large tool for digging and moving dirt. Was modified with extra safety, making it ineffective as a weapon." force = 1 throwforce = 1 - attack_verb = list("ineffectively hit") \ No newline at end of file + attack_verb = list("ineffectively hit") diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm index 4b5e5c911da..43d31bfe7fe 100644 --- a/code/modules/mining/equipment/regenerative_core.dm +++ b/code/modules/mining/equipment/regenerative_core.dm @@ -135,4 +135,4 @@ /obj/item/organ/internal/regenerative_core/legion/preserved(implanted = 0) ..() - desc = "[src] has been stabilized. It is preserved, allowing you to use it to heal completely without danger of decay." \ No newline at end of file + desc = "[src] has been stabilized. It is preserved, allowing you to use it to heal completely without danger of decay." diff --git a/code/modules/mining/equipment/resonator.dm b/code/modules/mining/equipment/resonator.dm index 4c7c4e599ef..405b29e7c68 100644 --- a/code/modules/mining/equipment/resonator.dm +++ b/code/modules/mining/equipment/resonator.dm @@ -113,4 +113,4 @@ /obj/effect/temp_visual/resonance_crush/New() ..() transform = matrix()*1.5 - animate(src, transform = matrix() * 0.1, alpha = 50, time = 4) \ No newline at end of file + animate(src, transform = matrix() * 0.1, alpha = 50, time = 4) diff --git a/code/modules/mining/equipment/wormhole_jaunter.dm b/code/modules/mining/equipment/wormhole_jaunter.dm index 7ab7276bb9e..44462dedfda 100644 --- a/code/modules/mining/equipment/wormhole_jaunter.dm +++ b/code/modules/mining/equipment/wormhole_jaunter.dm @@ -74,4 +74,4 @@ L.Weaken(6) if(ishuman(L)) shake_camera(L, 20, 1) - addtimer(CALLBACK(L, /mob/living/carbon.proc/vomit), 20) \ No newline at end of file + addtimer(CALLBACK(L, /mob/living/carbon.proc/vomit), 20) diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm index 1ce1350a73a..fbf5bda0de7 100644 --- a/code/modules/mining/fulton.dm +++ b/code/modules/mining/fulton.dm @@ -192,4 +192,4 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons) return /obj/effect/extraction_holder/singularity_pull() - return \ No newline at end of file + return diff --git a/code/modules/mining/laborcamp/laborshuttle.dm b/code/modules/mining/laborcamp/laborshuttle.dm index cb8b41d047f..8e585eb15b0 100644 --- a/code/modules/mining/laborcamp/laborshuttle.dm +++ b/code/modules/mining/laborcamp/laborshuttle.dm @@ -24,4 +24,4 @@ if(S && S.name == "laborcamp_away") to_chat(usr, "Shuttle is already at the outpost!") return 0 - ..() \ No newline at end of file + ..() diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm index cf2076ecc6f..e9d04cc406b 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -183,4 +183,4 @@ GLOBAL_LIST(labor_sheet_values) else to_chat(user, "Error: Invalid ID") return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/mining/lavaland/ash_flora.dm b/code/modules/mining/lavaland/ash_flora.dm index 0f7cd5cb2ef..afbd7414ebf 100644 --- a/code/modules/mining/lavaland/ash_flora.dm +++ b/code/modules/mining/lavaland/ash_flora.dm @@ -277,4 +277,4 @@ desc = "A bowl made out of mushrooms. Not food, though it might have contained some at some point." icon = 'icons/obj/lavaland/ash_flora.dmi' icon_state = "mushroom_bowl" - w_class = WEIGHT_CLASS_SMALL \ No newline at end of file + w_class = WEIGHT_CLASS_SMALL diff --git a/code/modules/mining/lavaland/loot/bubblegum_loot.dm b/code/modules/mining/lavaland/loot/bubblegum_loot.dm index 6cc33e1ad92..fa7791edb9e 100644 --- a/code/modules/mining/lavaland/loot/bubblegum_loot.dm +++ b/code/modules/mining/lavaland/loot/bubblegum_loot.dm @@ -87,4 +87,4 @@ to_chat(H, "You have an overwhelming desire to kill [L]. [L.p_they(TRUE)] [L.p_have()] been marked red! Go kill [L.p_them()]!") H.put_in_hands(new /obj/item/kitchen/knife/butcher(H)) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/mining/lavaland/loot/hierophant_loot.dm b/code/modules/mining/lavaland/loot/hierophant_loot.dm index 813cd66d881..bd06e2fe47a 100644 --- a/code/modules/mining/lavaland/loot/hierophant_loot.dm +++ b/code/modules/mining/lavaland/loot/hierophant_loot.dm @@ -282,4 +282,4 @@ var/obj/effect/temp_visual/hierophant/blast/B = new(t, user, friendly_fire_check) B.damage = 15 //keeps monster damage boost due to lower damage -#undef HIEROPHANT_CLUB_CARDINAL_DAMAGE \ No newline at end of file +#undef HIEROPHANT_CLUB_CARDINAL_DAMAGE diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 904a3462808..8da61be44d3 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -218,4 +218,4 @@ flags = NODROP | DROPDEL desc = "A short wooden rod with a mystical snake inseparably gripping itself and the rod to your forearm. It flows with a healing energy that disperses amongst yourself and those around you. " icon_state = "asclepius_active" - activated = TRUE \ No newline at end of file + activated = TRUE diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm index 46daf596f8c..50cb7d09801 100644 --- a/code/modules/mining/machine_processing.dm +++ b/code/modules/mining/machine_processing.dm @@ -215,4 +215,4 @@ var/O = new P(src) unload_mineral(O) -#undef SMELT_AMOUNT \ No newline at end of file +#undef SMELT_AMOUNT diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm index 93d3c41a692..20189c2793b 100644 --- a/code/modules/mining/machine_redemption.dm +++ b/code/modules/mining/machine_redemption.dm @@ -396,4 +396,4 @@ if(powered()) icon_state = initial(icon_state) else - icon_state = "[initial(icon_state)]-off" \ No newline at end of file + icon_state = "[initial(icon_state)]-off" diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm index 9ec45ebc4d8..f143b064722 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -88,4 +88,4 @@ var/obj/item/stack/sheet/out = new inp.type() out.amount = stack_amt unload_mineral(out) - storage.amount -= stack_amt \ No newline at end of file + storage.amount -= stack_amt diff --git a/code/modules/mining/machine_unloading.dm b/code/modules/mining/machine_unloading.dm index 78a63bc6c2d..39feeb38a61 100644 --- a/code/modules/mining/machine_unloading.dm +++ b/code/modules/mining/machine_unloading.dm @@ -29,4 +29,4 @@ limit++ if(limit>=10) return - CHECK_TICK \ No newline at end of file + CHECK_TICK diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm index 3dbeb9c9bfe..fd9bbc78ff3 100644 --- a/code/modules/mining/mine_items.dm +++ b/code/modules/mining/mine_items.dm @@ -92,4 +92,4 @@ icon_state = "miningcar" density = 1 icon_opened = "miningcaropen" - icon_closed = "miningcar" \ No newline at end of file + icon_closed = "miningcar" diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm index fdd6b15e8ae..1198e2fb9d3 100644 --- a/code/modules/mining/minebot.dm +++ b/code/modules/mining/minebot.dm @@ -354,4 +354,4 @@ qdel(src) #undef MINEDRONE_COLLECT -#undef MINEDRONE_ATTACK \ No newline at end of file +#undef MINEDRONE_ATTACK diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm index dd428341959..9fb73229ba7 100644 --- a/code/modules/mining/mint.dm +++ b/code/modules/mining/mint.dm @@ -101,4 +101,4 @@ if(!M) M = new /obj/item/storage/bag/money(src) unload_mineral(M) - O.forceMove(M) \ No newline at end of file + O.forceMove(M) diff --git a/code/modules/mining/money_bag.dm b/code/modules/mining/money_bag.dm index 230104f58b7..1cca9c43c46 100644 --- a/code/modules/mining/money_bag.dm +++ b/code/modules/mining/money_bag.dm @@ -1,24 +1,24 @@ -/*****************************Money bag********************************/ - -/obj/item/storage/bag/money - name = "money bag" - icon_state = "moneybag" - force = 10 - throwforce = 0 - resistance_flags = FLAMMABLE - max_integrity = 100 - w_class = WEIGHT_CLASS_BULKY - max_w_class = WEIGHT_CLASS_NORMAL - storage_slots = 40 - max_combined_w_class = 40 - can_hold = list(/obj/item/coin, /obj/item/stack/spacecash) - -/obj/item/storage/bag/money/vault/New() - ..() - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/silver(src) - new /obj/item/coin/gold(src) - new /obj/item/coin/gold(src) - new /obj/item/coin/adamantine(src) \ No newline at end of file +/*****************************Money bag********************************/ + +/obj/item/storage/bag/money + name = "money bag" + icon_state = "moneybag" + force = 10 + throwforce = 0 + resistance_flags = FLAMMABLE + max_integrity = 100 + w_class = WEIGHT_CLASS_BULKY + max_w_class = WEIGHT_CLASS_NORMAL + storage_slots = 40 + max_combined_w_class = 40 + can_hold = list(/obj/item/coin, /obj/item/stack/spacecash) + +/obj/item/storage/bag/money/vault/New() + ..() + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/silver(src) + new /obj/item/coin/gold(src) + new /obj/item/coin/gold(src) + new /obj/item/coin/adamantine(src) diff --git a/code/modules/mob/abilities.dm b/code/modules/mob/abilities.dm index 7ab35a7a73d..8c4aabc4fc8 100644 --- a/code/modules/mob/abilities.dm +++ b/code/modules/mob/abilities.dm @@ -2,4 +2,4 @@ Creature-level abilities. */ -/var/global/list/ability_verbs = list( ) \ No newline at end of file +/var/global/list/ability_verbs = list( ) diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index c7eb3357618..24f129ee387 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -30,4 +30,4 @@ SSmobs.dead_players_by_zlevel[new_z] += src registered_z = new_z else - registered_z = null \ No newline at end of file + registered_z = null diff --git a/code/modules/mob/dead/death.dm b/code/modules/mob/dead/death.dm index 704a61abbba..c4cedcbdaf1 100644 --- a/code/modules/mob/dead/death.dm +++ b/code/modules/mob/dead/death.dm @@ -5,4 +5,4 @@ return /mob/dead - move_resist = INFINITY \ No newline at end of file + move_resist = INFINITY diff --git a/code/modules/mob/dead/observer/login.dm b/code/modules/mob/dead/observer/login.dm index b44d8421b80..76f2ff9dbd0 100644 --- a/code/modules/mob/dead/observer/login.dm +++ b/code/modules/mob/dead/observer/login.dm @@ -1,9 +1,9 @@ -/mob/dead/observer/Login() - ..() - if(ghostimage) - ghostimage.icon_state = src.icon_state - updateghostimages() - - if(GLOB.non_respawnable_keys[ckey]) - can_reenter_corpse = 0 - GLOB.respawnable_list -= src +/mob/dead/observer/Login() + ..() + if(ghostimage) + ghostimage.icon_state = src.icon_state + updateghostimages() + + if(GLOB.non_respawnable_keys[ckey]) + can_reenter_corpse = 0 + GLOB.respawnable_list -= src diff --git a/code/modules/mob/dead/observer/logout.dm b/code/modules/mob/dead/observer/logout.dm index 93094d5ff30..a28bef6be5d 100644 --- a/code/modules/mob/dead/observer/logout.dm +++ b/code/modules/mob/dead/observer/logout.dm @@ -1,7 +1,7 @@ -/mob/dead/observer/Logout() - if(client) - client.images -= ghost_images - ..() - spawn(0) - if(src && !key) //we've transferred to another mob. This ghost should be deleted. - qdel(src) +/mob/dead/observer/Logout() + if(client) + client.images -= ghost_images + ..() + spawn(0) + if(src && !key) //we've transferred to another mob. This ghost should be deleted. + qdel(src) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index d5848b178ef..98dcebdae49 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -1,825 +1,825 @@ -#define GHOST_CAN_REENTER 1 -#define GHOST_IS_OBSERVER 2 - -var/list/image/ghost_images = list() - -GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) - -/mob/dead/observer - name = "ghost" - desc = "It's a g-g-g-g-ghooooost!" //jinkies! - icon = 'icons/mob/mob.dmi' - icon_state = "ghost" - layer = GHOST_LAYER - stat = DEAD - density = FALSE - canmove = FALSE - alpha = 127 - move_resist = INFINITY // don't get pushed around - invisibility = INVISIBILITY_OBSERVER - var/can_reenter_corpse - var/bootime = FALSE - var/started_as_observer //This variable is set to 1 when you enter the game as an observer. - //If you died in the game and are a ghsot - this will remain as null. - //Note that this is not a reliable way to determine if admins started as observers, since they change mobs a lot. - universal_speak = TRUE - var/atom/movable/following = null - var/image/ghostimage = null //this mobs ghost image, for deleting and stuff - var/ghostvision = TRUE //is the ghost able to see things humans can't? - var/seedarkness = TRUE - var/data_hud_seen = FALSE //this should one of the defines in __DEFINES/hud.dm - var/ghost_orbit = GHOST_ORBIT_CIRCLE - var/health_scan = FALSE //does the ghost have health scanner mode on? by default it should be off - -/mob/dead/observer/New(mob/body=null, flags=1) - set_invisibility(GLOB.observer_default_invisibility) - - sight |= SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF - see_invisible = SEE_INVISIBLE_OBSERVER_AI_EYE - see_in_dark = 100 - verbs += list( - /mob/dead/observer/proc/dead_tele, - /mob/dead/observer/proc/open_spawners_menu) - - // Our new boo spell. - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/boo(null)) - - can_reenter_corpse = flags & GHOST_CAN_REENTER - started_as_observer = flags & GHOST_IS_OBSERVER - - - stat = DEAD - - 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 - - var/mutable_appearance/MA = copy_appearance(body) - if(body.mind && body.mind.name) - MA.name = body.mind.name - else if(body.real_name) - MA.name = body.real_name - else - if(gender == MALE) - MA.name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) - else - MA.name = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) - - mind = body.mind //we don't transfer the mind but we keep a reference to it. - appearance = MA - - ghostimage = image(icon = icon, loc = src, icon_state = icon_state) - ghostimage.overlays = overlays - ghostimage.dir = dir - ghostimage.appearance_flags |= KEEP_TOGETHER - ghostimage.alpha = alpha - appearance_flags |= KEEP_TOGETHER - ghost_images |= ghostimage - updateallghostimages() - if(!T) - T = pick(latejoin) //Safety in case we cannot find the body's position - forceMove(T) - - if(!name) //To prevent nameless ghosts - name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) - real_name = name - - //starts ghosts off with all HUDs. - toggle_medHUD() - ..() - -/mob/dead/observer/Destroy() - if(ismob(following)) - var/mob/M = following - M.following_mobs -= src - following = null - if(ghostimage) - ghost_images -= ghostimage - QDEL_NULL(ghostimage) - updateallghostimages() - return ..() - -/mob/dead/observer/examine(mob/user) - . = ..() - if(!invisibility) - . += "It seems extremely obvious." - -// This seems stupid, but it's the easiest way to avoid absolutely ridiculous shit from happening -// Copying an appearance directly from a mob includes it's verb list, it's invisibility, it's alpha, and it's density -// You might recognize these things as "fucking ridiculous to put in an appearance" -// You'd be right, but that's fucking BYOND for you. -/mob/dead/observer/proc/copy_appearance(mutable_appearance/COPY) - var/mutable_appearance/MA = new(src) - - MA.appearance_flags = COPY.appearance_flags - MA.blend_mode = COPY.blend_mode - MA.color = COPY.color - MA.dir = COPY.dir - MA.gender = COPY.gender - MA.icon = COPY.icon - MA.icon_state = COPY.icon_state - MA.layer = COPY.layer - MA.maptext = COPY.maptext - MA.maptext_width = COPY.maptext_width - MA.maptext_height = COPY.maptext_height - MA.maptext_x = COPY.maptext_x - MA.maptext_y = COPY.maptext_y - MA.mouse_opacity = COPY.mouse_opacity - MA.overlays = COPY.overlays - if(!isicon(MA.icon) && !LAZYLEN(MA.overlays)) // Gibbing/dusting/melting removes the icon before ghostize()ing the mob, so we need to account for that - MA.icon = initial(icon) - MA.icon_state = initial(icon_state) - MA.suffix = COPY.suffix - MA.underlays = COPY.underlays - - . = MA - -/mob/dead/CanPass(atom/movable/mover, turf/target, height=0) - return 1 - - -/* -Transfer_mind is there to check if mob is being deleted/not going to have a body. -Works together with spawning an observer, noted above. -*/ -/mob/dead/observer/Life(seconds, times_fired) - ..() - if(!loc) return - if(!client) return 0 - - - -/mob/dead/proc/assess_targets(list/target_list, mob/dead/observer/U) - var/client/C = U.client - for(var/mob/living/carbon/human/target in target_list) - C.images += target.hud_list[SPECIALROLE_HUD] - for(var/mob/living/silicon/target in target_list) - C.images += target.hud_list[SPECIALROLE_HUD] - return 1 - -/mob/proc/ghostize(flags = GHOST_CAN_REENTER) - if(key) - if(player_logged) //if they have disconnected we want to remove their SSD overlay - overlays -= image('icons/effects/effects.dmi', icon_state = "zzz_glow") - if(GLOB.non_respawnable_keys[ckey]) - flags &= ~GHOST_CAN_REENTER - var/mob/dead/observer/ghost = new(src, flags) //Transfer safety to observer spawning proc. - ghost.timeofdeath = src.timeofdeath //BS12 EDIT - GLOB.respawnable_list -= src - if(ghost.can_reenter_corpse) - GLOB.respawnable_list += ghost - else - GLOB.non_respawnable_keys[ckey] = 1 - ghost.key = key - if(!(ghost.client && ghost.client.holder) && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed. - ghost.verbs -= /mob/dead/observer/verb/toggle_antagHUD // Poor guys, don't know what they are missing! - return ghost - -/* -This is the proc mobs get to turn into a ghost. Forked from ghostize due to compatibility issues. -*/ -/mob/living/verb/ghost() - set category = "OOC" - set name = "Ghost" - set desc = "Relinquish your life and enter the land of the dead." - - var/mob/M = src - var/warningmsg = null - var/obj/machinery/cryopod/P = istype(loc, /obj/machinery/cryopod) && loc - - if(P) - if(TOO_EARLY_TO_GHOST) - warningmsg = "It's too early in the shift to enter cryo" - else if(suiciding && TOO_EARLY_TO_GHOST) - warningmsg = "You have committed suicide too early in the round" - else if(stat != DEAD) - warningmsg = "You are alive" - if(isAI(src)) - warningmsg = "You are a living AI! You should probably use OOC -> Wipe Core instead." - else if(GLOB.non_respawnable_keys[ckey]) - warningmsg = "You have lost your right to respawn" - - if(warningmsg) - var/response - var/alertmsg = "Are you -sure- you want to ghost?\n([warningmsg]. If you ghost now, you probably won't be able to rejoin the round! You can't change your mind, so choose wisely!)" - response = alert(src, alertmsg,"Are you sure you want to ghost?","Stay in body","Ghost") - if(response != "Ghost") - return - - if(stat == CONSCIOUS) - if(!is_admin_level(z)) - player_ghosted = 1 - if(mind && mind.special_role) - message_admins("[key_name_admin(src)] has ghosted while alive, with special_role: [mind.special_role]") - - if(warningmsg) - // Not respawnable - var/mob/dead/observer/ghost = ghostize(0) // 0 parameter stops them re-entering their body - ghost.timeofdeath = world.time // Because the living mob won't have a time of death and we want the respawn timer to work properly. - else - // Respawnable - ghostize(1) - - // If mob in morgue tray, update tray - var/obj/structure/morgue/Morgue = locate() in M.loc - if(istype(M.loc, /obj/structure/morgue)) - Morgue = M.loc - if(Morgue) - Morgue.update() - - // If mob in cryopod, despawn mob - if(P) - if(!P.control_computer) - P.find_control_computer(urgent=1) - if(P.control_computer) - P.despawn_occupant() - return - -// Ghosts have no momentum, being massless ectoplasm -/mob/dead/observer/Process_Spacemove(movement_dir) - return 1 - -/mob/dead/observer/Move(NewLoc, direct) - update_parallax_contents() - following = null - setDir(direct) - ghostimage.setDir(dir) - - var/oldloc = loc - - if(NewLoc) - forceMove(NewLoc) - else - forceMove(get_turf(src)) //Get out of closets and such as a ghost - if((direct & NORTH) && y < world.maxy) - y++ - else if((direct & SOUTH) && y > 1) - y-- - if((direct & EAST) && x < world.maxx) - x++ - else if((direct & WEST) && x > 1) - x-- - - Moved(oldloc, direct) - -/mob/dead/observer/can_use_hands() return 0 - -/mob/dead/observer/Stat() - ..() - statpanel("Status") - if(client.statpanel == "Status") - show_stat_emergency_shuttle_eta() - stat(null, "Respawnability: [(src in GLOB.respawnable_list) ? "Yes" : "No"]") - -/mob/dead/observer/verb/reenter_corpse() - set category = "Ghost" - set name = "Re-enter Corpse" - if(!client) - return - if(!mind || QDELETED(mind.current)) - to_chat(src, "You have no body.") - return - if(!can_reenter_corpse) - to_chat(src, "You cannot re-enter your body.") - return - if(mind.current.key && copytext(mind.current.key,1,2)!="@") //makes sure we don't accidentally kick any clients - to_chat(usr, "Another consciousness is in your body...It is resisting you.") - return - - mind.current.key = key - - var/obj/structure/morgue/Morgue = locate() in mind.current.loc - if(istype(mind.current.loc,/obj/structure/morgue)) - Morgue = mind.current.loc - if(Morgue) - Morgue.update() - - return 1 - - -/mob/dead/observer/proc/notify_cloning(var/message, var/sound, var/atom/source) - if(message) - to_chat(src, "[message]") - if(source) - var/obj/screen/alert/A = throw_alert("\ref[source]_notify_cloning", /obj/screen/alert/notify_cloning) - if(A) - if(client && client.prefs && client.prefs.UI_style) - A.icon = ui_style2icon(client.prefs.UI_style) - A.desc = message - var/old_layer = source.layer - var/old_plane = source.plane - source.layer = FLOAT_LAYER - source.plane = FLOAT_PLANE - A.overlays += source - source.layer = old_layer - source.plane = old_plane - to_chat(src, "(Click to re-enter)") - if(sound) - src << sound(sound) - -/mob/dead/observer/proc/show_me_the_hud(hud_index) - var/datum/atom_hud/H = huds[hud_index] - H.add_hud_to(src) - -/mob/dead/observer/proc/remove_the_hud(hud_index) //remove old huds - var/datum/atom_hud/H = huds[hud_index] - H.remove_hud_from(src) - -/mob/dead/observer/verb/toggle_medHUD() - set category = "Ghost" - set name = "Toggle All/Sec/Med/Diag HUDs" - set desc = "Toggles the HUDs." - if(!client) - return - - switch(data_hud_seen) //give new huds - if(FALSE) - data_hud_seen = DATA_HUD_DIAGNOSTIC + DATA_HUD_SECURITY_ADVANCED + DATA_HUD_MEDICAL_ADVANCED - show_me_the_hud(DATA_HUD_DIAGNOSTIC) - show_me_the_hud(DATA_HUD_SECURITY_ADVANCED) - show_me_the_hud(DATA_HUD_MEDICAL_ADVANCED) - to_chat(src, "All HUDs enabled.") - if(DATA_HUD_DIAGNOSTIC + DATA_HUD_SECURITY_ADVANCED + DATA_HUD_MEDICAL_ADVANCED) - data_hud_seen = DATA_HUD_SECURITY_ADVANCED - remove_the_hud(DATA_HUD_DIAGNOSTIC) - remove_the_hud(DATA_HUD_MEDICAL_ADVANCED) - to_chat(src, "Security HUD set.") - if(DATA_HUD_SECURITY_ADVANCED) - data_hud_seen = DATA_HUD_MEDICAL_ADVANCED - remove_the_hud(DATA_HUD_SECURITY_ADVANCED) - show_me_the_hud(DATA_HUD_MEDICAL_ADVANCED) - to_chat(src, "Medical HUD set.") - if(DATA_HUD_MEDICAL_ADVANCED) - data_hud_seen = DATA_HUD_DIAGNOSTIC - remove_the_hud(DATA_HUD_MEDICAL_ADVANCED) - show_me_the_hud(DATA_HUD_DIAGNOSTIC) - to_chat(src, "Diagnostic HUD set.") - else - data_hud_seen = FALSE - remove_the_hud(DATA_HUD_MEDICAL_ADVANCED) - to_chat(src, "HUDs disabled.") - - -/mob/dead/observer/verb/toggle_antagHUD() - set category = "Ghost" - set name = "Toggle AntagHUD" - set desc = "Toggles AntagHUD allowing you to see who is the antagonist" - if(!config.antag_hud_allowed && !client.holder) - to_chat(src, "Admins have disabled this for this round.") - return - if(!client) - return - var/mob/dead/observer/M = src - if(jobban_isbanned(M, "AntagHUD")) - to_chat(src, "You have been banned from using this feature") - return - if(config.antag_hud_restricted && !M.has_enabled_antagHUD && !check_rights(R_ADMIN|R_MOD,0)) - var/response = alert(src, "If you turn this on, you will not be able to take any part in the round.","Are you sure you want to turn this feature on?","Yes","No") - if(response == "No") return - M.can_reenter_corpse = 0 - if(M in GLOB.respawnable_list) - GLOB.respawnable_list -= M - if(!M.has_enabled_antagHUD && !check_rights(R_ADMIN|R_MOD,0)) - M.has_enabled_antagHUD = 1 - - //var/datum/atom_hud/A = huds[DATA_HUD_SECURITY_ADVANCED] - //var/adding_hud = (usr in A.hudusers) ? 0 : 1 - - for(var/datum/atom_hud/antag/H in (huds)) - if(!M.antagHUD) - H.add_hud_to(usr) - else - H.remove_hud_from(usr) - if(!M.antagHUD) - to_chat(usr, "AntagHud Toggled ON") - M.antagHUD = 1 - else - to_chat(usr, "AntagHud Toggled OFF") - M.antagHUD = 0 - -/mob/dead/observer/proc/dead_tele() - set category = "Ghost" - set name = "Teleport" - set desc= "Teleport to a location" - - if(!isobserver(usr)) - to_chat(usr, "Not when you're not dead!") - return - - var/datum/async_input/A = input_autocomplete_async(usr, "Area to jump to: ", ghostteleportlocs) - A.on_close(CALLBACK(src, .proc/teleport)) - -/mob/dead/observer/proc/teleport(area/thearea) - if(!thearea || !isobserver(usr)) - return - - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - L += T - - if(!L || !L.len) - to_chat(usr, "No area available.") - return - - forceMove(pick(L)) - update_parallax_contents() - following = null - -/mob/dead/observer/verb/follow() - set category = "Ghost" - set name = "Orbit" // "Haunt" - set desc = "Follow and orbit a mob." - - var/list/mobs = getpois(skip_mindless=1) - var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a mob: ", mobs) - A.on_close(CALLBACK(src, .proc/ManualFollow)) - -// This is the ghost's follow verb with an argument -/mob/dead/observer/proc/ManualFollow(var/atom/movable/target) - if(!target || !isobserver(usr)) - return - - if(!get_turf(target)) - return - - if(target != src) - if(following && following == target) - return - - var/icon/I = icon(target.icon,target.icon_state,target.dir) - - var/orbitsize = (I.Width()+I.Height())*0.5 - - if(orbitsize == 0) - orbitsize = 40 - - orbitsize -= (orbitsize/world.icon_size)*(world.icon_size*0.25) - - var/rot_seg - - switch(ghost_orbit) - if(GHOST_ORBIT_TRIANGLE) - rot_seg = 3 - if(GHOST_ORBIT_SQUARE) - rot_seg = 4 - if(GHOST_ORBIT_PENTAGON) - rot_seg = 5 - if(GHOST_ORBIT_HEXAGON) - rot_seg = 6 - else //Circular - rot_seg = 36 //360/10 bby, smooth enough aproximation of a circle - - following = target - to_chat(src, "Now following [target]") - orbit(target,orbitsize, FALSE, 20, rot_seg) - -/mob/dead/observer/orbit() - setDir(2)//reset dir so the right directional sprites show up - return ..() - -/mob/proc/update_following() - . = get_turf(src) - for(var/mob/dead/observer/M in following_mobs) - if(M.following != src) - following_mobs -= M - else - if(M.loc != .) - M.forceMove(.) - -/mob - var/list/following_mobs = list() - -/mob/Move() - . = ..() - if(.) - update_following() - -/mob/Life(seconds, times_fired) - // to catch teleports etc which directly set loc - update_following() - return ..() - -/mob/dead/observer/verb/jumptomob() //Moves the ghost instead of just changing the ghosts's eye -Nodrak - set category = "Ghost" - set name = "Jump to Mob" - 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/datum/async_input/A = input_autocomplete_async(usr, "Enter a mob name: ", dest) - A.on_close(CALLBACK(src, .proc/jump_to_mob)) - -/mob/dead/observer/proc/jump_to_mob(mob/M) - if(!M || !isobserver(usr)) - return - var/mob/A = src //Source mob - var/turf/T = get_turf(M) //Turf of the destination mob - - if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination. - A.forceMove(T) - M.update_parallax_contents() - following = null - return - to_chat(A, "This mob is not located in the game world.") - -/* Now a spell. See spells.dm -/mob/dead/observer/verb/boo() - set category = "Ghost" - set name = "Boo!" - set desc= "Scare your crew members because of boredom!" - - if(bootime > world.time) return - bootime = world.time + 600 - var/obj/machinery/light/L = locate(/obj/machinery/light) in view(1, src) - if(L) - L.flicker() - //Maybe in the future we can add more spooky code here! - return -*/ - -/mob/dead/observer/memory() - set hidden = 1 - to_chat(src, "You are dead! You have no mind to store memory!") - -/mob/dead/observer/add_memory() - set hidden = 1 - to_chat(src, "You are dead! You have no mind to store memory!") - - -/mob/dead/observer/verb/toggle_health_scan() - set name = "Toggle Health Scan" - set desc = "Toggles whether you health-scan living beings on click" - set category = "Ghost" - - if(health_scan) //remove old huds - to_chat(src, "Health scan disabled.") - health_scan = FALSE - else - to_chat(src, "Health scan enabled.") - health_scan = TRUE - -/mob/dead/observer/verb/analyze_air() - set name = "Analyze Air" - set category = "Ghost" - - if(!istype(usr, /mob/dead/observer)) return - - // Shamelessly copied from the Gas Analyzers - if(!( istype(usr.loc, /turf) )) - return - - var/datum/gas_mixture/environment = usr.loc.return_air() - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - to_chat(src, "Results:") - if(abs(pressure - ONE_ATMOSPHERE) < 10) - to_chat(src, "Pressure: [round(pressure,0.1)] kPa") - else - to_chat(src, "Pressure: [round(pressure,0.1)] kPa") - if(total_moles) - var/o2_concentration = environment.oxygen/total_moles - var/n2_concentration = environment.nitrogen/total_moles - var/co2_concentration = environment.carbon_dioxide/total_moles - var/plasma_concentration = environment.toxins/total_moles - - var/unknown_concentration = 1-(o2_concentration+n2_concentration+co2_concentration+plasma_concentration) - if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(src, "Nitrogen: [round(n2_concentration*100)]% ([round(environment.nitrogen,0.01)] moles)") - else - to_chat(src, "Nitrogen: [round(n2_concentration*100)]% ([round(environment.nitrogen,0.01)] moles)") - - if(abs(o2_concentration - O2STANDARD) < 2) - to_chat(src, "Oxygen: [round(o2_concentration*100)]% ([round(environment.oxygen,0.01)] moles)") - else - to_chat(src, "Oxygen: [round(o2_concentration*100)]% ([round(environment.oxygen,0.01)] moles)") - - if(co2_concentration > 0.01) - to_chat(src, "CO2: [round(co2_concentration*100)]% ([round(environment.carbon_dioxide,0.01)] moles)") - else - to_chat(src, "CO2: [round(co2_concentration*100)]% ([round(environment.carbon_dioxide,0.01)] moles)") - - if(plasma_concentration > 0.01) - to_chat(src, "Plasma: [round(plasma_concentration*100)]% ([round(environment.toxins,0.01)] moles)") - - if(unknown_concentration > 0.01) - to_chat(src, "Unknown: [round(unknown_concentration*100)]% ([round(unknown_concentration*total_moles,0.01)] moles)") - - to_chat(src, "Temperature: [round(environment.temperature-T0C,0.1)]°C") - to_chat(src, "Heat Capacity: [round(environment.heat_capacity(),0.1)]") - -/mob/dead/observer/verb/view_manifest() - set name = "View Crew Manifest" - set category = "Ghost" - - var/dat - dat += "

        Crew Manifest

        " - dat += data_core.get_manifest() - - src << browse(dat, "window=manifest;size=370x420;can_close=1") - -//this is called when a ghost is drag clicked to something. -/mob/dead/observer/MouseDrop(atom/over) - if(!usr || !over) return - if(isobserver(usr) && usr.client && usr.client.holder) - if(usr.client.holder.cmd_ghost_drag(src,over)) - return - - return ..() - -/proc/ghost_follow_link(var/atom/target, var/atom/ghost) - if((!target) || (!ghost)) return - if(isAI(target)) // AI core/eye follow links - var/mob/living/silicon/ai/A = target - . = "core" - if(A.client && A.eyeobj) // No point following clientless AI eyes - . += "|eye" - return - else if(istype(target, /mob/dead/observer)) - var/mob/dead/observer/O = target - . = "follow" - if(O.mind && O.mind.current) - . += "|body" - return - else - return "follow" - -//BEGIN TELEPORT HREF CODE -/mob/dead/observer/Topic(href, href_list) - if(usr != src) - return - ..() - - if(href_list["track"]) - var/atom/target = locate(href_list["track"]) - if(target) - ManualFollow(target) - - if(href_list["follow"]) - var/atom/target = locate(href_list["follow"]) - if(target) - ManualFollow(target) - - if(href_list["jump"]) - var/mob/target = locate(href_list["jump"]) - var/mob/A = usr; - to_chat(A, "Teleporting to [target]...") - //var/mob/living/silicon/ai/A = locate(href_list["track2"]) in GLOB.mob_list - if(target && target != usr) - spawn(0) - var/turf/pos = get_turf(A) - var/turf/T=get_turf(target) - if(T != pos) - if(!T) - return - if(!client) - return - forceMove(T) - following = null - - if(href_list["reenter"]) - reenter_corpse() - - ..() -//END TELEPORT HREF CODE - -/mob/dead/observer/verb/toggle_anonsay() - set name = "Toggle Anonymous Dead-chat" - set category = "Ghost" - set desc = "Toggles showing your key in dead chat." - client.prefs.ghost_anonsay = !client.prefs.ghost_anonsay - to_chat(src, "As a ghost, your key will [(client.prefs.ghost_anonsay) ? "no longer" : "now"] be shown when you speak in dead chat.
        ") - client.prefs.save_preferences(src) - -/mob/dead/observer/verb/toggle_ghostsee() - set name = "Toggle Ghost Vision" - set desc = "Toggles your ability to see things only ghosts can see, like other ghosts" - set category = "Ghost" - ghostvision = !(ghostvision) - update_sight() - to_chat(usr, "You [(ghostvision?"now":"no longer")] have ghost vision.") - -/mob/dead/observer/verb/toggle_darkness() - set name = "Toggle Darkness" - set category = "Ghost" - switch(lighting_alpha) - if (LIGHTING_PLANE_ALPHA_VISIBLE) - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) - lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE - else - lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE - - update_sight() - -/mob/dead/observer/update_sight() - if (!ghostvision) - see_invisible = SEE_INVISIBLE_LIVING - else - see_invisible = SEE_INVISIBLE_OBSERVER - - updateghostimages() - . = ..() - -/mob/dead/observer/proc/updateghostsight() - if(!seedarkness) - see_invisible = SEE_INVISIBLE_OBSERVER_NOLIGHTING - else - see_invisible = SEE_INVISIBLE_OBSERVER - if(!ghostvision) - see_invisible = SEE_INVISIBLE_LIVING - - updateghostimages() - -/proc/updateallghostimages() - for(var/mob/dead/observer/O in GLOB.player_list) - O.updateghostimages() - -/mob/dead/observer/proc/updateghostimages() - if(!client) - return - if(seedarkness || !ghostvision) - client.images -= ghost_images - else - //add images for the 60inv things ghosts can normally see when darkness is enabled so they can see them now - client.images |= ghost_images - if(ghostimage) - client.images -= ghostimage //remove ourself - -/mob/proc/can_admin_interact() - return FALSE - -/mob/proc/can_advanced_admin_interact() - return FALSE - -/mob/dead/observer/can_admin_interact() - return check_rights(R_ADMIN, 0, src) - -/mob/dead/observer/can_advanced_admin_interact() - if(!can_admin_interact()) - return FALSE - - if(client && client.advanced_admin_interaction) - return TRUE - - return FALSE - -/mob/dead/observer/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, ignore_lying = FALSE) - return TRUE - -//this is a mob verb instead of atom for performance reasons -//see /mob/verb/examinate() in mob.dm for more info -//overriden here and in /mob/living for different point span classes and sanity checks -/mob/dead/observer/pointed(atom/A as mob|obj|turf in view()) - if(!..()) - return 0 - usr.visible_message("[src] points to [A].") - return 1 - -/mob/dead/observer/proc/incarnate_ghost() - if(!client) - return - - var/mob/living/carbon/human/new_char = new(get_turf(src)) - client.prefs.copy_to(new_char) - if(mind) - mind.active = TRUE - mind.transfer_to(new_char) - else - new_char.key = key - - return new_char - -/mob/dead/observer/is_literate() - return TRUE - -/mob/dead/observer/proc/set_invisibility(value) - invisibility = value - if(!value) - set_light(1, 2) - else - set_light(0, 0) - -/mob/dead/observer/vv_edit_var(var_name, var_value) - . = ..() - if(var_name == "invisibility") - set_invisibility(invisibility) // updates light - -/proc/set_observer_default_invisibility(amount, message=null) - for(var/mob/dead/observer/G in GLOB.player_list) - G.set_invisibility(amount) - if(message) - to_chat(G, message) - GLOB.observer_default_invisibility = amount - -/mob/dead/observer/proc/open_spawners_menu() - set name = "Mob spawners menu" - set desc = "See all currently available ghost spawners" - set category = "Ghost" - - var/datum/spawners_menu/menu = new /datum/spawners_menu(src) - menu.ui_interact(src) +#define GHOST_CAN_REENTER 1 +#define GHOST_IS_OBSERVER 2 + +var/list/image/ghost_images = list() + +GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) + +/mob/dead/observer + name = "ghost" + desc = "It's a g-g-g-g-ghooooost!" //jinkies! + icon = 'icons/mob/mob.dmi' + icon_state = "ghost" + layer = GHOST_LAYER + stat = DEAD + density = FALSE + canmove = FALSE + alpha = 127 + move_resist = INFINITY // don't get pushed around + invisibility = INVISIBILITY_OBSERVER + var/can_reenter_corpse + var/bootime = FALSE + var/started_as_observer //This variable is set to 1 when you enter the game as an observer. + //If you died in the game and are a ghsot - this will remain as null. + //Note that this is not a reliable way to determine if admins started as observers, since they change mobs a lot. + universal_speak = TRUE + var/atom/movable/following = null + var/image/ghostimage = null //this mobs ghost image, for deleting and stuff + var/ghostvision = TRUE //is the ghost able to see things humans can't? + var/seedarkness = TRUE + var/data_hud_seen = FALSE //this should one of the defines in __DEFINES/hud.dm + var/ghost_orbit = GHOST_ORBIT_CIRCLE + var/health_scan = FALSE //does the ghost have health scanner mode on? by default it should be off + +/mob/dead/observer/New(mob/body=null, flags=1) + set_invisibility(GLOB.observer_default_invisibility) + + sight |= SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF + see_invisible = SEE_INVISIBLE_OBSERVER_AI_EYE + see_in_dark = 100 + verbs += list( + /mob/dead/observer/proc/dead_tele, + /mob/dead/observer/proc/open_spawners_menu) + + // Our new boo spell. + AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/boo(null)) + + can_reenter_corpse = flags & GHOST_CAN_REENTER + started_as_observer = flags & GHOST_IS_OBSERVER + + + stat = DEAD + + 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 + + var/mutable_appearance/MA = copy_appearance(body) + if(body.mind && body.mind.name) + MA.name = body.mind.name + else if(body.real_name) + MA.name = body.real_name + else + if(gender == MALE) + MA.name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) + else + MA.name = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) + + mind = body.mind //we don't transfer the mind but we keep a reference to it. + appearance = MA + + ghostimage = image(icon = icon, loc = src, icon_state = icon_state) + ghostimage.overlays = overlays + ghostimage.dir = dir + ghostimage.appearance_flags |= KEEP_TOGETHER + ghostimage.alpha = alpha + appearance_flags |= KEEP_TOGETHER + ghost_images |= ghostimage + updateallghostimages() + if(!T) + T = pick(latejoin) //Safety in case we cannot find the body's position + forceMove(T) + + if(!name) //To prevent nameless ghosts + name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) + real_name = name + + //starts ghosts off with all HUDs. + toggle_medHUD() + ..() + +/mob/dead/observer/Destroy() + if(ismob(following)) + var/mob/M = following + M.following_mobs -= src + following = null + if(ghostimage) + ghost_images -= ghostimage + QDEL_NULL(ghostimage) + updateallghostimages() + return ..() + +/mob/dead/observer/examine(mob/user) + . = ..() + if(!invisibility) + . += "It seems extremely obvious." + +// This seems stupid, but it's the easiest way to avoid absolutely ridiculous shit from happening +// Copying an appearance directly from a mob includes it's verb list, it's invisibility, it's alpha, and it's density +// You might recognize these things as "fucking ridiculous to put in an appearance" +// You'd be right, but that's fucking BYOND for you. +/mob/dead/observer/proc/copy_appearance(mutable_appearance/COPY) + var/mutable_appearance/MA = new(src) + + MA.appearance_flags = COPY.appearance_flags + MA.blend_mode = COPY.blend_mode + MA.color = COPY.color + MA.dir = COPY.dir + MA.gender = COPY.gender + MA.icon = COPY.icon + MA.icon_state = COPY.icon_state + MA.layer = COPY.layer + MA.maptext = COPY.maptext + MA.maptext_width = COPY.maptext_width + MA.maptext_height = COPY.maptext_height + MA.maptext_x = COPY.maptext_x + MA.maptext_y = COPY.maptext_y + MA.mouse_opacity = COPY.mouse_opacity + MA.overlays = COPY.overlays + if(!isicon(MA.icon) && !LAZYLEN(MA.overlays)) // Gibbing/dusting/melting removes the icon before ghostize()ing the mob, so we need to account for that + MA.icon = initial(icon) + MA.icon_state = initial(icon_state) + MA.suffix = COPY.suffix + MA.underlays = COPY.underlays + + . = MA + +/mob/dead/CanPass(atom/movable/mover, turf/target, height=0) + return 1 + + +/* +Transfer_mind is there to check if mob is being deleted/not going to have a body. +Works together with spawning an observer, noted above. +*/ +/mob/dead/observer/Life(seconds, times_fired) + ..() + if(!loc) return + if(!client) return 0 + + + +/mob/dead/proc/assess_targets(list/target_list, mob/dead/observer/U) + var/client/C = U.client + for(var/mob/living/carbon/human/target in target_list) + C.images += target.hud_list[SPECIALROLE_HUD] + for(var/mob/living/silicon/target in target_list) + C.images += target.hud_list[SPECIALROLE_HUD] + return 1 + +/mob/proc/ghostize(flags = GHOST_CAN_REENTER) + if(key) + if(player_logged) //if they have disconnected we want to remove their SSD overlay + overlays -= image('icons/effects/effects.dmi', icon_state = "zzz_glow") + if(GLOB.non_respawnable_keys[ckey]) + flags &= ~GHOST_CAN_REENTER + var/mob/dead/observer/ghost = new(src, flags) //Transfer safety to observer spawning proc. + ghost.timeofdeath = src.timeofdeath //BS12 EDIT + GLOB.respawnable_list -= src + if(ghost.can_reenter_corpse) + GLOB.respawnable_list += ghost + else + GLOB.non_respawnable_keys[ckey] = 1 + ghost.key = key + if(!(ghost.client && ghost.client.holder) && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed. + ghost.verbs -= /mob/dead/observer/verb/toggle_antagHUD // Poor guys, don't know what they are missing! + return ghost + +/* +This is the proc mobs get to turn into a ghost. Forked from ghostize due to compatibility issues. +*/ +/mob/living/verb/ghost() + set category = "OOC" + set name = "Ghost" + set desc = "Relinquish your life and enter the land of the dead." + + var/mob/M = src + var/warningmsg = null + var/obj/machinery/cryopod/P = istype(loc, /obj/machinery/cryopod) && loc + + if(P) + if(TOO_EARLY_TO_GHOST) + warningmsg = "It's too early in the shift to enter cryo" + else if(suiciding && TOO_EARLY_TO_GHOST) + warningmsg = "You have committed suicide too early in the round" + else if(stat != DEAD) + warningmsg = "You are alive" + if(isAI(src)) + warningmsg = "You are a living AI! You should probably use OOC -> Wipe Core instead." + else if(GLOB.non_respawnable_keys[ckey]) + warningmsg = "You have lost your right to respawn" + + if(warningmsg) + var/response + var/alertmsg = "Are you -sure- you want to ghost?\n([warningmsg]. If you ghost now, you probably won't be able to rejoin the round! You can't change your mind, so choose wisely!)" + response = alert(src, alertmsg,"Are you sure you want to ghost?","Stay in body","Ghost") + if(response != "Ghost") + return + + if(stat == CONSCIOUS) + if(!is_admin_level(z)) + player_ghosted = 1 + if(mind && mind.special_role) + message_admins("[key_name_admin(src)] has ghosted while alive, with special_role: [mind.special_role]") + + if(warningmsg) + // Not respawnable + var/mob/dead/observer/ghost = ghostize(0) // 0 parameter stops them re-entering their body + ghost.timeofdeath = world.time // Because the living mob won't have a time of death and we want the respawn timer to work properly. + else + // Respawnable + ghostize(1) + + // If mob in morgue tray, update tray + var/obj/structure/morgue/Morgue = locate() in M.loc + if(istype(M.loc, /obj/structure/morgue)) + Morgue = M.loc + if(Morgue) + Morgue.update() + + // If mob in cryopod, despawn mob + if(P) + if(!P.control_computer) + P.find_control_computer(urgent=1) + if(P.control_computer) + P.despawn_occupant() + return + +// Ghosts have no momentum, being massless ectoplasm +/mob/dead/observer/Process_Spacemove(movement_dir) + return 1 + +/mob/dead/observer/Move(NewLoc, direct) + update_parallax_contents() + following = null + setDir(direct) + ghostimage.setDir(dir) + + var/oldloc = loc + + if(NewLoc) + forceMove(NewLoc) + else + forceMove(get_turf(src)) //Get out of closets and such as a ghost + if((direct & NORTH) && y < world.maxy) + y++ + else if((direct & SOUTH) && y > 1) + y-- + if((direct & EAST) && x < world.maxx) + x++ + else if((direct & WEST) && x > 1) + x-- + + Moved(oldloc, direct) + +/mob/dead/observer/can_use_hands() return 0 + +/mob/dead/observer/Stat() + ..() + statpanel("Status") + if(client.statpanel == "Status") + show_stat_emergency_shuttle_eta() + stat(null, "Respawnability: [(src in GLOB.respawnable_list) ? "Yes" : "No"]") + +/mob/dead/observer/verb/reenter_corpse() + set category = "Ghost" + set name = "Re-enter Corpse" + if(!client) + return + if(!mind || QDELETED(mind.current)) + to_chat(src, "You have no body.") + return + if(!can_reenter_corpse) + to_chat(src, "You cannot re-enter your body.") + return + if(mind.current.key && copytext(mind.current.key,1,2)!="@") //makes sure we don't accidentally kick any clients + to_chat(usr, "Another consciousness is in your body...It is resisting you.") + return + + mind.current.key = key + + var/obj/structure/morgue/Morgue = locate() in mind.current.loc + if(istype(mind.current.loc,/obj/structure/morgue)) + Morgue = mind.current.loc + if(Morgue) + Morgue.update() + + return 1 + + +/mob/dead/observer/proc/notify_cloning(var/message, var/sound, var/atom/source) + if(message) + to_chat(src, "[message]") + if(source) + var/obj/screen/alert/A = throw_alert("\ref[source]_notify_cloning", /obj/screen/alert/notify_cloning) + if(A) + if(client && client.prefs && client.prefs.UI_style) + A.icon = ui_style2icon(client.prefs.UI_style) + A.desc = message + var/old_layer = source.layer + var/old_plane = source.plane + source.layer = FLOAT_LAYER + source.plane = FLOAT_PLANE + A.overlays += source + source.layer = old_layer + source.plane = old_plane + to_chat(src, "(Click to re-enter)") + if(sound) + src << sound(sound) + +/mob/dead/observer/proc/show_me_the_hud(hud_index) + var/datum/atom_hud/H = huds[hud_index] + H.add_hud_to(src) + +/mob/dead/observer/proc/remove_the_hud(hud_index) //remove old huds + var/datum/atom_hud/H = huds[hud_index] + H.remove_hud_from(src) + +/mob/dead/observer/verb/toggle_medHUD() + set category = "Ghost" + set name = "Toggle All/Sec/Med/Diag HUDs" + set desc = "Toggles the HUDs." + if(!client) + return + + switch(data_hud_seen) //give new huds + if(FALSE) + data_hud_seen = DATA_HUD_DIAGNOSTIC + DATA_HUD_SECURITY_ADVANCED + DATA_HUD_MEDICAL_ADVANCED + show_me_the_hud(DATA_HUD_DIAGNOSTIC) + show_me_the_hud(DATA_HUD_SECURITY_ADVANCED) + show_me_the_hud(DATA_HUD_MEDICAL_ADVANCED) + to_chat(src, "All HUDs enabled.") + if(DATA_HUD_DIAGNOSTIC + DATA_HUD_SECURITY_ADVANCED + DATA_HUD_MEDICAL_ADVANCED) + data_hud_seen = DATA_HUD_SECURITY_ADVANCED + remove_the_hud(DATA_HUD_DIAGNOSTIC) + remove_the_hud(DATA_HUD_MEDICAL_ADVANCED) + to_chat(src, "Security HUD set.") + if(DATA_HUD_SECURITY_ADVANCED) + data_hud_seen = DATA_HUD_MEDICAL_ADVANCED + remove_the_hud(DATA_HUD_SECURITY_ADVANCED) + show_me_the_hud(DATA_HUD_MEDICAL_ADVANCED) + to_chat(src, "Medical HUD set.") + if(DATA_HUD_MEDICAL_ADVANCED) + data_hud_seen = DATA_HUD_DIAGNOSTIC + remove_the_hud(DATA_HUD_MEDICAL_ADVANCED) + show_me_the_hud(DATA_HUD_DIAGNOSTIC) + to_chat(src, "Diagnostic HUD set.") + else + data_hud_seen = FALSE + remove_the_hud(DATA_HUD_MEDICAL_ADVANCED) + to_chat(src, "HUDs disabled.") + + +/mob/dead/observer/verb/toggle_antagHUD() + set category = "Ghost" + set name = "Toggle AntagHUD" + set desc = "Toggles AntagHUD allowing you to see who is the antagonist" + if(!config.antag_hud_allowed && !client.holder) + to_chat(src, "Admins have disabled this for this round.") + return + if(!client) + return + var/mob/dead/observer/M = src + if(jobban_isbanned(M, "AntagHUD")) + to_chat(src, "You have been banned from using this feature") + return + if(config.antag_hud_restricted && !M.has_enabled_antagHUD && !check_rights(R_ADMIN|R_MOD,0)) + var/response = alert(src, "If you turn this on, you will not be able to take any part in the round.","Are you sure you want to turn this feature on?","Yes","No") + if(response == "No") return + M.can_reenter_corpse = 0 + if(M in GLOB.respawnable_list) + GLOB.respawnable_list -= M + if(!M.has_enabled_antagHUD && !check_rights(R_ADMIN|R_MOD,0)) + M.has_enabled_antagHUD = 1 + + //var/datum/atom_hud/A = huds[DATA_HUD_SECURITY_ADVANCED] + //var/adding_hud = (usr in A.hudusers) ? 0 : 1 + + for(var/datum/atom_hud/antag/H in (huds)) + if(!M.antagHUD) + H.add_hud_to(usr) + else + H.remove_hud_from(usr) + if(!M.antagHUD) + to_chat(usr, "AntagHud Toggled ON") + M.antagHUD = 1 + else + to_chat(usr, "AntagHud Toggled OFF") + M.antagHUD = 0 + +/mob/dead/observer/proc/dead_tele() + set category = "Ghost" + set name = "Teleport" + set desc= "Teleport to a location" + + if(!isobserver(usr)) + to_chat(usr, "Not when you're not dead!") + return + + var/datum/async_input/A = input_autocomplete_async(usr, "Area to jump to: ", ghostteleportlocs) + A.on_close(CALLBACK(src, .proc/teleport)) + +/mob/dead/observer/proc/teleport(area/thearea) + if(!thearea || !isobserver(usr)) + return + + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + L += T + + if(!L || !L.len) + to_chat(usr, "No area available.") + return + + forceMove(pick(L)) + update_parallax_contents() + following = null + +/mob/dead/observer/verb/follow() + set category = "Ghost" + set name = "Orbit" // "Haunt" + set desc = "Follow and orbit a mob." + + var/list/mobs = getpois(skip_mindless=1) + var/datum/async_input/A = input_autocomplete_async(usr, "Please, select a mob: ", mobs) + A.on_close(CALLBACK(src, .proc/ManualFollow)) + +// This is the ghost's follow verb with an argument +/mob/dead/observer/proc/ManualFollow(var/atom/movable/target) + if(!target || !isobserver(usr)) + return + + if(!get_turf(target)) + return + + if(target != src) + if(following && following == target) + return + + var/icon/I = icon(target.icon,target.icon_state,target.dir) + + var/orbitsize = (I.Width()+I.Height())*0.5 + + if(orbitsize == 0) + orbitsize = 40 + + orbitsize -= (orbitsize/world.icon_size)*(world.icon_size*0.25) + + var/rot_seg + + switch(ghost_orbit) + if(GHOST_ORBIT_TRIANGLE) + rot_seg = 3 + if(GHOST_ORBIT_SQUARE) + rot_seg = 4 + if(GHOST_ORBIT_PENTAGON) + rot_seg = 5 + if(GHOST_ORBIT_HEXAGON) + rot_seg = 6 + else //Circular + rot_seg = 36 //360/10 bby, smooth enough aproximation of a circle + + following = target + to_chat(src, "Now following [target]") + orbit(target,orbitsize, FALSE, 20, rot_seg) + +/mob/dead/observer/orbit() + setDir(2)//reset dir so the right directional sprites show up + return ..() + +/mob/proc/update_following() + . = get_turf(src) + for(var/mob/dead/observer/M in following_mobs) + if(M.following != src) + following_mobs -= M + else + if(M.loc != .) + M.forceMove(.) + +/mob + var/list/following_mobs = list() + +/mob/Move() + . = ..() + if(.) + update_following() + +/mob/Life(seconds, times_fired) + // to catch teleports etc which directly set loc + update_following() + return ..() + +/mob/dead/observer/verb/jumptomob() //Moves the ghost instead of just changing the ghosts's eye -Nodrak + set category = "Ghost" + set name = "Jump to Mob" + 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/datum/async_input/A = input_autocomplete_async(usr, "Enter a mob name: ", dest) + A.on_close(CALLBACK(src, .proc/jump_to_mob)) + +/mob/dead/observer/proc/jump_to_mob(mob/M) + if(!M || !isobserver(usr)) + return + var/mob/A = src //Source mob + var/turf/T = get_turf(M) //Turf of the destination mob + + if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination. + A.forceMove(T) + M.update_parallax_contents() + following = null + return + to_chat(A, "This mob is not located in the game world.") + +/* Now a spell. See spells.dm +/mob/dead/observer/verb/boo() + set category = "Ghost" + set name = "Boo!" + set desc= "Scare your crew members because of boredom!" + + if(bootime > world.time) return + bootime = world.time + 600 + var/obj/machinery/light/L = locate(/obj/machinery/light) in view(1, src) + if(L) + L.flicker() + //Maybe in the future we can add more spooky code here! + return +*/ + +/mob/dead/observer/memory() + set hidden = 1 + to_chat(src, "You are dead! You have no mind to store memory!") + +/mob/dead/observer/add_memory() + set hidden = 1 + to_chat(src, "You are dead! You have no mind to store memory!") + + +/mob/dead/observer/verb/toggle_health_scan() + set name = "Toggle Health Scan" + set desc = "Toggles whether you health-scan living beings on click" + set category = "Ghost" + + if(health_scan) //remove old huds + to_chat(src, "Health scan disabled.") + health_scan = FALSE + else + to_chat(src, "Health scan enabled.") + health_scan = TRUE + +/mob/dead/observer/verb/analyze_air() + set name = "Analyze Air" + set category = "Ghost" + + if(!istype(usr, /mob/dead/observer)) return + + // Shamelessly copied from the Gas Analyzers + if(!( istype(usr.loc, /turf) )) + return + + var/datum/gas_mixture/environment = usr.loc.return_air() + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + to_chat(src, "Results:") + if(abs(pressure - ONE_ATMOSPHERE) < 10) + to_chat(src, "Pressure: [round(pressure,0.1)] kPa") + else + to_chat(src, "Pressure: [round(pressure,0.1)] kPa") + if(total_moles) + var/o2_concentration = environment.oxygen/total_moles + var/n2_concentration = environment.nitrogen/total_moles + var/co2_concentration = environment.carbon_dioxide/total_moles + var/plasma_concentration = environment.toxins/total_moles + + var/unknown_concentration = 1-(o2_concentration+n2_concentration+co2_concentration+plasma_concentration) + if(abs(n2_concentration - N2STANDARD) < 20) + to_chat(src, "Nitrogen: [round(n2_concentration*100)]% ([round(environment.nitrogen,0.01)] moles)") + else + to_chat(src, "Nitrogen: [round(n2_concentration*100)]% ([round(environment.nitrogen,0.01)] moles)") + + if(abs(o2_concentration - O2STANDARD) < 2) + to_chat(src, "Oxygen: [round(o2_concentration*100)]% ([round(environment.oxygen,0.01)] moles)") + else + to_chat(src, "Oxygen: [round(o2_concentration*100)]% ([round(environment.oxygen,0.01)] moles)") + + if(co2_concentration > 0.01) + to_chat(src, "CO2: [round(co2_concentration*100)]% ([round(environment.carbon_dioxide,0.01)] moles)") + else + to_chat(src, "CO2: [round(co2_concentration*100)]% ([round(environment.carbon_dioxide,0.01)] moles)") + + if(plasma_concentration > 0.01) + to_chat(src, "Plasma: [round(plasma_concentration*100)]% ([round(environment.toxins,0.01)] moles)") + + if(unknown_concentration > 0.01) + to_chat(src, "Unknown: [round(unknown_concentration*100)]% ([round(unknown_concentration*total_moles,0.01)] moles)") + + to_chat(src, "Temperature: [round(environment.temperature-T0C,0.1)]°C") + to_chat(src, "Heat Capacity: [round(environment.heat_capacity(),0.1)]") + +/mob/dead/observer/verb/view_manifest() + set name = "View Crew Manifest" + set category = "Ghost" + + var/dat + dat += "

        Crew Manifest

        " + dat += data_core.get_manifest() + + src << browse(dat, "window=manifest;size=370x420;can_close=1") + +//this is called when a ghost is drag clicked to something. +/mob/dead/observer/MouseDrop(atom/over) + if(!usr || !over) return + if(isobserver(usr) && usr.client && usr.client.holder) + if(usr.client.holder.cmd_ghost_drag(src,over)) + return + + return ..() + +/proc/ghost_follow_link(var/atom/target, var/atom/ghost) + if((!target) || (!ghost)) return + if(isAI(target)) // AI core/eye follow links + var/mob/living/silicon/ai/A = target + . = "core" + if(A.client && A.eyeobj) // No point following clientless AI eyes + . += "|eye" + return + else if(istype(target, /mob/dead/observer)) + var/mob/dead/observer/O = target + . = "follow" + if(O.mind && O.mind.current) + . += "|body" + return + else + return "follow" + +//BEGIN TELEPORT HREF CODE +/mob/dead/observer/Topic(href, href_list) + if(usr != src) + return + ..() + + if(href_list["track"]) + var/atom/target = locate(href_list["track"]) + if(target) + ManualFollow(target) + + if(href_list["follow"]) + var/atom/target = locate(href_list["follow"]) + if(target) + ManualFollow(target) + + if(href_list["jump"]) + var/mob/target = locate(href_list["jump"]) + var/mob/A = usr; + to_chat(A, "Teleporting to [target]...") + //var/mob/living/silicon/ai/A = locate(href_list["track2"]) in GLOB.mob_list + if(target && target != usr) + spawn(0) + var/turf/pos = get_turf(A) + var/turf/T=get_turf(target) + if(T != pos) + if(!T) + return + if(!client) + return + forceMove(T) + following = null + + if(href_list["reenter"]) + reenter_corpse() + + ..() +//END TELEPORT HREF CODE + +/mob/dead/observer/verb/toggle_anonsay() + set name = "Toggle Anonymous Dead-chat" + set category = "Ghost" + set desc = "Toggles showing your key in dead chat." + client.prefs.ghost_anonsay = !client.prefs.ghost_anonsay + to_chat(src, "As a ghost, your key will [(client.prefs.ghost_anonsay) ? "no longer" : "now"] be shown when you speak in dead chat.
        ") + client.prefs.save_preferences(src) + +/mob/dead/observer/verb/toggle_ghostsee() + set name = "Toggle Ghost Vision" + set desc = "Toggles your ability to see things only ghosts can see, like other ghosts" + set category = "Ghost" + ghostvision = !(ghostvision) + update_sight() + to_chat(usr, "You [(ghostvision?"now":"no longer")] have ghost vision.") + +/mob/dead/observer/verb/toggle_darkness() + set name = "Toggle Darkness" + set category = "Ghost" + switch(lighting_alpha) + if (LIGHTING_PLANE_ALPHA_VISIBLE) + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) + lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + else + lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE + + update_sight() + +/mob/dead/observer/update_sight() + if (!ghostvision) + see_invisible = SEE_INVISIBLE_LIVING + else + see_invisible = SEE_INVISIBLE_OBSERVER + + updateghostimages() + . = ..() + +/mob/dead/observer/proc/updateghostsight() + if(!seedarkness) + see_invisible = SEE_INVISIBLE_OBSERVER_NOLIGHTING + else + see_invisible = SEE_INVISIBLE_OBSERVER + if(!ghostvision) + see_invisible = SEE_INVISIBLE_LIVING + + updateghostimages() + +/proc/updateallghostimages() + for(var/mob/dead/observer/O in GLOB.player_list) + O.updateghostimages() + +/mob/dead/observer/proc/updateghostimages() + if(!client) + return + if(seedarkness || !ghostvision) + client.images -= ghost_images + else + //add images for the 60inv things ghosts can normally see when darkness is enabled so they can see them now + client.images |= ghost_images + if(ghostimage) + client.images -= ghostimage //remove ourself + +/mob/proc/can_admin_interact() + return FALSE + +/mob/proc/can_advanced_admin_interact() + return FALSE + +/mob/dead/observer/can_admin_interact() + return check_rights(R_ADMIN, 0, src) + +/mob/dead/observer/can_advanced_admin_interact() + if(!can_admin_interact()) + return FALSE + + if(client && client.advanced_admin_interaction) + return TRUE + + return FALSE + +/mob/dead/observer/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, ignore_lying = FALSE) + return TRUE + +//this is a mob verb instead of atom for performance reasons +//see /mob/verb/examinate() in mob.dm for more info +//overriden here and in /mob/living for different point span classes and sanity checks +/mob/dead/observer/pointed(atom/A as mob|obj|turf in view()) + if(!..()) + return 0 + usr.visible_message("[src] points to [A].") + return 1 + +/mob/dead/observer/proc/incarnate_ghost() + if(!client) + return + + var/mob/living/carbon/human/new_char = new(get_turf(src)) + client.prefs.copy_to(new_char) + if(mind) + mind.active = TRUE + mind.transfer_to(new_char) + else + new_char.key = key + + return new_char + +/mob/dead/observer/is_literate() + return TRUE + +/mob/dead/observer/proc/set_invisibility(value) + invisibility = value + if(!value) + set_light(1, 2) + else + set_light(0, 0) + +/mob/dead/observer/vv_edit_var(var_name, var_value) + . = ..() + if(var_name == "invisibility") + set_invisibility(invisibility) // updates light + +/proc/set_observer_default_invisibility(amount, message=null) + for(var/mob/dead/observer/G in GLOB.player_list) + G.set_invisibility(amount) + if(message) + to_chat(G, message) + GLOB.observer_default_invisibility = amount + +/mob/dead/observer/proc/open_spawners_menu() + set name = "Mob spawners menu" + set desc = "See all currently available ghost spawners" + set category = "Ghost" + + var/datum/spawners_menu/menu = new /datum/spawners_menu(src) + menu.ui_interact(src) diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm index 41c1d89f603..2f5627e8413 100644 --- a/code/modules/mob/dead/observer/say.dm +++ b/code/modules/mob/dead/observer/say.dm @@ -1,48 +1,48 @@ -/mob/dead/observer/say(var/message) - message = sanitize(copytext(message, 1, MAX_MESSAGE_LEN)) - - if(!message) - return - - log_ghostsay(message, src) - - if(src.client) - if(src.client.prefs.muted & MUTE_DEADCHAT) - to_chat(src, "You cannot talk in deadchat (muted).") - return - - if(src.client.handle_spam_prevention(message,MUTE_DEADCHAT)) - return - - . = src.say_dead(message) - - -/mob/dead/observer/emote(act, type, message, force) - message = sanitize(copytext(message, 1, MAX_MESSAGE_LEN)) - - if(!message) - return - - if(act != "me") - return - - log_ghostemote(message, src) - - if(src.client) - if(src.client.prefs.muted & MUTE_DEADCHAT) - to_chat(src, "You cannot emote in deadchat (muted).") - return - - if(src.client.handle_spam_prevention(message, MUTE_DEADCHAT)) - return - - . = src.emote_dead(message) - -/mob/dead/observer/handle_track(var/message, var/verb = "says", var/mob/speaker = null, var/speaker_name, var/atom/follow_target, var/hard_to_hear) - return "[speaker_name] ([ghost_follow_link(follow_target, ghost=src)])" - -/mob/dead/observer/handle_speaker_name(var/mob/speaker = null, var/vname, var/hard_to_hear) - var/speaker_name = ..() - if(speaker && (speaker_name != speaker.real_name) && !isAI(speaker)) //Announce computer and various stuff that broadcasts doesn't use it's real name but AI's can't pretend to be other mobs. - speaker_name = "[speaker.real_name] ([speaker_name])" - return speaker_name \ No newline at end of file +/mob/dead/observer/say(var/message) + message = sanitize(copytext(message, 1, MAX_MESSAGE_LEN)) + + if(!message) + return + + log_ghostsay(message, src) + + if(src.client) + if(src.client.prefs.muted & MUTE_DEADCHAT) + to_chat(src, "You cannot talk in deadchat (muted).") + return + + if(src.client.handle_spam_prevention(message,MUTE_DEADCHAT)) + return + + . = src.say_dead(message) + + +/mob/dead/observer/emote(act, type, message, force) + message = sanitize(copytext(message, 1, MAX_MESSAGE_LEN)) + + if(!message) + return + + if(act != "me") + return + + log_ghostemote(message, src) + + if(src.client) + if(src.client.prefs.muted & MUTE_DEADCHAT) + to_chat(src, "You cannot emote in deadchat (muted).") + return + + if(src.client.handle_spam_prevention(message, MUTE_DEADCHAT)) + return + + . = src.emote_dead(message) + +/mob/dead/observer/handle_track(var/message, var/verb = "says", var/mob/speaker = null, var/speaker_name, var/atom/follow_target, var/hard_to_hear) + return "[speaker_name] ([ghost_follow_link(follow_target, ghost=src)])" + +/mob/dead/observer/handle_speaker_name(var/mob/speaker = null, var/vname, var/hard_to_hear) + var/speaker_name = ..() + if(speaker && (speaker_name != speaker.real_name) && !isAI(speaker)) //Announce computer and various stuff that broadcasts doesn't use it's real name but AI's can't pretend to be other mobs. + speaker_name = "[speaker.real_name] ([speaker_name])" + return speaker_name diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index 3aa580ff28c..cc497adc3e0 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -179,4 +179,4 @@ name = speaker.voice_name var/rendered = "[name] [message]" - to_chat(src, rendered) \ No newline at end of file + to_chat(src, rendered) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 35a08d0ce10..6d96d77c321 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -1,265 +1,265 @@ -//These procs handle putting s tuff in your hand. It's probably best to use these rather than setting l_hand = ...etc -//as they handle all relevant stuff like adding it to the player's screen and updating their overlays. - -//Returns the thing in our active hand -/mob/proc/get_active_hand() - if(hand) return l_hand - else return r_hand - -/mob/verb/quick_equip() - set name = "quick-equip" - set hidden = 1 - - var/obj/item/I = get_active_hand() - if(I) - I.equip_to_best_slot(src) - -/mob/proc/is_in_active_hand(obj/item/I) - var/obj/item/item_to_test = get_active_hand() - - return item_to_test && item_to_test.is_equivalent(I) - - -//Returns the thing in our inactive hand -/mob/proc/get_inactive_hand() - if(hand) return r_hand - else return l_hand - -/mob/proc/is_in_inactive_hand(obj/item/I) - var/obj/item/item_to_test = get_inactive_hand() - - return item_to_test && item_to_test.is_equivalent(I) - -//Returns if a certain item can be equipped to a certain slot. -// Currently invalid for two-handed items - call obj/item/mob_can_equip() instead. -/mob/proc/can_equip(obj/item/I, slot, disable_warning = 0) - return 0 - -// Because there's several different places it's stored. -/mob/proc/get_multitool(var/if_active=0) - return null - -//Puts the item into your l_hand if possible and calls all necessary triggers/updates. returns 1 on success. -/mob/proc/put_in_l_hand(var/obj/item/W) - if(!put_in_hand_check(W)) - return 0 - if(!l_hand && has_left_hand()) - W.forceMove(src) //TODO: move to equipped? - l_hand = W - W.layer = ABOVE_HUD_LAYER //TODO: move to equipped? - W.plane = ABOVE_HUD_PLANE //TODO: move to equipped? - W.equipped(src,slot_l_hand) - if(pulling == W) - stop_pulling() - update_inv_l_hand() - return 1 - return 0 - -//Puts the item into your r_hand if possible and calls all necessary triggers/updates. returns 1 on success. -/mob/proc/put_in_r_hand(var/obj/item/W) - if(!put_in_hand_check(W)) - return 0 - if(!r_hand && has_right_hand()) - W.forceMove(src) - r_hand = W - W.layer = ABOVE_HUD_LAYER - W.plane = ABOVE_HUD_PLANE - W.equipped(src,slot_r_hand) - if(pulling == W) - stop_pulling() - update_inv_r_hand() - return 1 - return 0 - -/mob/proc/put_in_hand_check(var/obj/item/W) - if(lying && !(W.flags & ABSTRACT)) return 0 - if(!istype(W)) return 0 - return 1 - -//Puts the item into our active hand if possible. returns 1 on success. -/mob/proc/put_in_active_hand(var/obj/item/W) - if(hand) return put_in_l_hand(W) - else return put_in_r_hand(W) - -//Puts the item into our inactive hand if possible. returns 1 on success. -/mob/proc/put_in_inactive_hand(var/obj/item/W) - if(hand) return put_in_r_hand(W) - else return put_in_l_hand(W) - -//Puts the item our active hand if possible. Failing that it tries our inactive hand. Returns 1 on success. -//If both fail it drops it on the floor and returns 0. -//This is probably the main one you need to know :) -//Just puts stuff on the floor for most mobs, since all mobs have hands but putting stuff in the AI/corgi/ghost hand is VERY BAD. -/mob/proc/put_in_hands(obj/item/W) - W.forceMove(drop_location()) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - W.dropped() - -/mob/proc/drop_item_v() //this is dumb. - if(stat == CONSCIOUS && isturf(loc)) - return drop_item() - return 0 - -//Drops the item in our left hand -/mob/proc/drop_l_hand() - return unEquip(l_hand) //All needed checks are in unEquip - -//Drops the item in our right hand -/mob/proc/drop_r_hand() - return unEquip(r_hand) //Why was this not calling unEquip in the first place jesus fuck. - -//Drops the item in our active hand. -/mob/proc/drop_item() //THIS. DOES. NOT. NEED. AN. ARGUMENT. - if(hand) - return drop_l_hand() - else - return drop_r_hand() - -//Here lie unEquip and before_item_take, already forgotten and not missed. - -/mob/proc/canUnEquip(obj/item/I, force) - if(!I) - return 1 - if((I.flags & NODROP) && !force) - return 0 - return 1 - -/mob/proc/unEquip(obj/item/I, force) //Force overrides NODROP for things like wizarditis and admin undress. - if(!I) //If there's nothing to drop, the drop is automatically succesfull. If(unEquip) should generally be used to check for NODROP. - return 1 - - if(!canUnEquip(I, force)) - return 0 - - if(I == r_hand) - r_hand = null - update_inv_r_hand() - else if(I == l_hand) - l_hand = null - update_inv_l_hand() - else if(I in tkgrabbed_objects) - var/obj/item/tk_grab/tkgrab = tkgrabbed_objects[I] - unEquip(tkgrab, force) - - if(I) - if(client) - client.screen -= I - I.forceMove(drop_location()) - I.dropped(src) - if(I) - I.layer = initial(I.layer) - I.plane = initial(I.plane) - return 1 - - -//Attemps to remove an object on a mob. Will not move it to another area or such, just removes from the mob. -/mob/proc/remove_from_mob(var/obj/O) - unEquip(O) - O.screen_loc = null - return 1 - - -//Outdated but still in use apparently. This should at least be a human proc. -//Daily reminder to murder this - Remie. -/mob/proc/get_equipped_items(include_pockets = FALSE) - var/list/items = list() - if(back) - items += back - if(wear_mask) - items += wear_mask - return items - -/mob/living/carbon/get_equipped_items(include_pockets = FALSE) - var/list/items = ..() - if(wear_suit) - items += wear_suit - if(head) - items += head - return items - -/mob/living/carbon/human/get_equipped_items(include_pockets = FALSE) - var/list/items = ..() - if(belt) - items += belt - if(l_ear) - items += l_ear - if(r_ear) - items += r_ear - if(glasses) - items += glasses - if(gloves) - items += gloves - if(shoes) - items += shoes - if(wear_id) - items += wear_id - if(wear_pda) - items += wear_pda - if(w_uniform) - items += w_uniform - if(include_pockets) - if(l_store) - items += l_store - if(r_store) - items += r_store - if(s_store) - items += s_store - return items - -/obj/item/proc/equip_to_best_slot(mob/M) - if(src != M.get_active_hand()) - to_chat(M, "You are not holding anything to equip!") - return 0 - - if(M.equip_to_appropriate_slot(src)) - if(M.hand) - M.update_inv_l_hand(0) - else - M.update_inv_r_hand(0) - return 1 - - if(M.s_active && M.s_active.can_be_inserted(src, 1)) //if storage active insert there - M.s_active.handle_item_insertion(src) - return 1 - - var/obj/item/storage/S = M.get_inactive_hand() - if(istype(S) && S.can_be_inserted(src, 1)) //see if we have box in other hand - S.handle_item_insertion(src) - return 1 - - S = M.get_item_by_slot(slot_belt) - if(istype(S) && S.can_be_inserted(src, 1)) //else we put in belt - S.handle_item_insertion(src) - return 1 - - S = M.get_item_by_slot(slot_back) //else we put in backpack - if(istype(S) && S.can_be_inserted(src, 1)) - S.handle_item_insertion(src) - playsound(loc, "rustle", 50, 1, -5) - return 1 - - to_chat(M, "You are unable to equip that!") - return 0 - -/mob/proc/get_all_slots() - return list(wear_mask, back, l_hand, r_hand) - -/mob/proc/get_id_card() - for(var/obj/item/I in get_all_slots()) - . = I.GetID() - if(.) - break - -/mob/proc/get_item_by_slot(slot_id) - switch(slot_id) - if(slot_wear_mask) - return wear_mask - if(slot_back) - return back - if(slot_l_hand) - return l_hand - if(slot_r_hand) - return r_hand - return null - +//These procs handle putting s tuff in your hand. It's probably best to use these rather than setting l_hand = ...etc +//as they handle all relevant stuff like adding it to the player's screen and updating their overlays. + +//Returns the thing in our active hand +/mob/proc/get_active_hand() + if(hand) return l_hand + else return r_hand + +/mob/verb/quick_equip() + set name = "quick-equip" + set hidden = 1 + + var/obj/item/I = get_active_hand() + if(I) + I.equip_to_best_slot(src) + +/mob/proc/is_in_active_hand(obj/item/I) + var/obj/item/item_to_test = get_active_hand() + + return item_to_test && item_to_test.is_equivalent(I) + + +//Returns the thing in our inactive hand +/mob/proc/get_inactive_hand() + if(hand) return r_hand + else return l_hand + +/mob/proc/is_in_inactive_hand(obj/item/I) + var/obj/item/item_to_test = get_inactive_hand() + + return item_to_test && item_to_test.is_equivalent(I) + +//Returns if a certain item can be equipped to a certain slot. +// Currently invalid for two-handed items - call obj/item/mob_can_equip() instead. +/mob/proc/can_equip(obj/item/I, slot, disable_warning = 0) + return 0 + +// Because there's several different places it's stored. +/mob/proc/get_multitool(var/if_active=0) + return null + +//Puts the item into your l_hand if possible and calls all necessary triggers/updates. returns 1 on success. +/mob/proc/put_in_l_hand(var/obj/item/W) + if(!put_in_hand_check(W)) + return 0 + if(!l_hand && has_left_hand()) + W.forceMove(src) //TODO: move to equipped? + l_hand = W + W.layer = ABOVE_HUD_LAYER //TODO: move to equipped? + W.plane = ABOVE_HUD_PLANE //TODO: move to equipped? + W.equipped(src,slot_l_hand) + if(pulling == W) + stop_pulling() + update_inv_l_hand() + return 1 + return 0 + +//Puts the item into your r_hand if possible and calls all necessary triggers/updates. returns 1 on success. +/mob/proc/put_in_r_hand(var/obj/item/W) + if(!put_in_hand_check(W)) + return 0 + if(!r_hand && has_right_hand()) + W.forceMove(src) + r_hand = W + W.layer = ABOVE_HUD_LAYER + W.plane = ABOVE_HUD_PLANE + W.equipped(src,slot_r_hand) + if(pulling == W) + stop_pulling() + update_inv_r_hand() + return 1 + return 0 + +/mob/proc/put_in_hand_check(var/obj/item/W) + if(lying && !(W.flags & ABSTRACT)) return 0 + if(!istype(W)) return 0 + return 1 + +//Puts the item into our active hand if possible. returns 1 on success. +/mob/proc/put_in_active_hand(var/obj/item/W) + if(hand) return put_in_l_hand(W) + else return put_in_r_hand(W) + +//Puts the item into our inactive hand if possible. returns 1 on success. +/mob/proc/put_in_inactive_hand(var/obj/item/W) + if(hand) return put_in_r_hand(W) + else return put_in_l_hand(W) + +//Puts the item our active hand if possible. Failing that it tries our inactive hand. Returns 1 on success. +//If both fail it drops it on the floor and returns 0. +//This is probably the main one you need to know :) +//Just puts stuff on the floor for most mobs, since all mobs have hands but putting stuff in the AI/corgi/ghost hand is VERY BAD. +/mob/proc/put_in_hands(obj/item/W) + W.forceMove(drop_location()) + W.layer = initial(W.layer) + W.plane = initial(W.plane) + W.dropped() + +/mob/proc/drop_item_v() //this is dumb. + if(stat == CONSCIOUS && isturf(loc)) + return drop_item() + return 0 + +//Drops the item in our left hand +/mob/proc/drop_l_hand() + return unEquip(l_hand) //All needed checks are in unEquip + +//Drops the item in our right hand +/mob/proc/drop_r_hand() + return unEquip(r_hand) //Why was this not calling unEquip in the first place jesus fuck. + +//Drops the item in our active hand. +/mob/proc/drop_item() //THIS. DOES. NOT. NEED. AN. ARGUMENT. + if(hand) + return drop_l_hand() + else + return drop_r_hand() + +//Here lie unEquip and before_item_take, already forgotten and not missed. + +/mob/proc/canUnEquip(obj/item/I, force) + if(!I) + return 1 + if((I.flags & NODROP) && !force) + return 0 + return 1 + +/mob/proc/unEquip(obj/item/I, force) //Force overrides NODROP for things like wizarditis and admin undress. + if(!I) //If there's nothing to drop, the drop is automatically succesfull. If(unEquip) should generally be used to check for NODROP. + return 1 + + if(!canUnEquip(I, force)) + return 0 + + if(I == r_hand) + r_hand = null + update_inv_r_hand() + else if(I == l_hand) + l_hand = null + update_inv_l_hand() + else if(I in tkgrabbed_objects) + var/obj/item/tk_grab/tkgrab = tkgrabbed_objects[I] + unEquip(tkgrab, force) + + if(I) + if(client) + client.screen -= I + I.forceMove(drop_location()) + I.dropped(src) + if(I) + I.layer = initial(I.layer) + I.plane = initial(I.plane) + return 1 + + +//Attemps to remove an object on a mob. Will not move it to another area or such, just removes from the mob. +/mob/proc/remove_from_mob(var/obj/O) + unEquip(O) + O.screen_loc = null + return 1 + + +//Outdated but still in use apparently. This should at least be a human proc. +//Daily reminder to murder this - Remie. +/mob/proc/get_equipped_items(include_pockets = FALSE) + var/list/items = list() + if(back) + items += back + if(wear_mask) + items += wear_mask + return items + +/mob/living/carbon/get_equipped_items(include_pockets = FALSE) + var/list/items = ..() + if(wear_suit) + items += wear_suit + if(head) + items += head + return items + +/mob/living/carbon/human/get_equipped_items(include_pockets = FALSE) + var/list/items = ..() + if(belt) + items += belt + if(l_ear) + items += l_ear + if(r_ear) + items += r_ear + if(glasses) + items += glasses + if(gloves) + items += gloves + if(shoes) + items += shoes + if(wear_id) + items += wear_id + if(wear_pda) + items += wear_pda + if(w_uniform) + items += w_uniform + if(include_pockets) + if(l_store) + items += l_store + if(r_store) + items += r_store + if(s_store) + items += s_store + return items + +/obj/item/proc/equip_to_best_slot(mob/M) + if(src != M.get_active_hand()) + to_chat(M, "You are not holding anything to equip!") + return 0 + + if(M.equip_to_appropriate_slot(src)) + if(M.hand) + M.update_inv_l_hand(0) + else + M.update_inv_r_hand(0) + return 1 + + if(M.s_active && M.s_active.can_be_inserted(src, 1)) //if storage active insert there + M.s_active.handle_item_insertion(src) + return 1 + + var/obj/item/storage/S = M.get_inactive_hand() + if(istype(S) && S.can_be_inserted(src, 1)) //see if we have box in other hand + S.handle_item_insertion(src) + return 1 + + S = M.get_item_by_slot(slot_belt) + if(istype(S) && S.can_be_inserted(src, 1)) //else we put in belt + S.handle_item_insertion(src) + return 1 + + S = M.get_item_by_slot(slot_back) //else we put in backpack + if(istype(S) && S.can_be_inserted(src, 1)) + S.handle_item_insertion(src) + playsound(loc, "rustle", 50, 1, -5) + return 1 + + to_chat(M, "You are unable to equip that!") + return 0 + +/mob/proc/get_all_slots() + return list(wear_mask, back, l_hand, r_hand) + +/mob/proc/get_id_card() + for(var/obj/item/I in get_all_slots()) + . = I.GetID() + if(.) + break + +/mob/proc/get_item_by_slot(slot_id) + switch(slot_id) + if(slot_wear_mask) + return wear_mask + if(slot_back) + return back + if(slot_l_hand) + return l_hand + if(slot_r_hand) + return r_hand + return null + diff --git a/code/modules/mob/living/carbon/_defines.dm b/code/modules/mob/living/carbon/_defines.dm index a5a524f86ed..871b140a503 100644 --- a/code/modules/mob/living/carbon/_defines.dm +++ b/code/modules/mob/living/carbon/_defines.dm @@ -16,4 +16,4 @@ #define COLD_GAS_DAMAGE_LEVEL_1 0.5 //Amount of damage applied when the current breath's temperature just passes the 260.15k safety point #define COLD_GAS_DAMAGE_LEVEL_2 1.5 //Amount of damage applied when the current breath's temperature passes the 200K point -#define COLD_GAS_DAMAGE_LEVEL_3 3 //Amount of damage applied when the current breath's temperature passes the 120K point \ No newline at end of file +#define COLD_GAS_DAMAGE_LEVEL_3 3 //Amount of damage applied when the current breath's temperature passes the 120K point diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index 69a2e1160b0..1b85c3e5386 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -1,308 +1,308 @@ -/mob/living/carbon/alien - name = "alien" - voice_name = "alien" - speak_emote = list("hisses") - icon = 'icons/mob/alien.dmi' - gender = NEUTER - dna = null - alien_talk_understand = TRUE - - var/nightvision = FALSE - see_in_dark = 4 - - var/obj/item/card/id/wear_id = null // Fix for station bounced radios -- Skie - var/has_fine_manipulation = FALSE - var/move_delay_add = FALSE // movement delay to add - - status_flags = CANPARALYSE|CANPUSH - var/heal_rate = 5 - - var/large = FALSE - var/heat_protection = 0.5 - var/leaping = FALSE - ventcrawler = 2 - var/list/alien_organs = list() - var/death_message = "lets out a waning guttural screech, green blood bubbling from its maw..." - var/death_sound = 'sound/voice/hiss6.ogg' - -/mob/living/carbon/alien/New() - verbs += /mob/living/verb/mob_sleep - verbs += /mob/living/verb/lay_down - alien_organs += new /obj/item/organ/internal/brain/xeno - alien_organs += new /obj/item/organ/internal/xenos/hivenode - alien_organs += new /obj/item/organ/internal/ears - for(var/obj/item/organ/internal/I in alien_organs) - I.insert(src) - ..() - -/mob/living/carbon/alien/get_default_language() - if(default_language) - return default_language - return GLOB.all_languages["Xenomorph"] - -/mob/living/carbon/alien/say_quote(var/message, var/datum/language/speaking = null) - var/verb = "hisses" - var/ending = copytext(message, length(message)) - - if(speaking && (speaking.name != "Galactic Common")) //this is so adminbooze xenos speaking common have their custom verbs, - verb = speaking.get_spoken_verb(ending) //and use normal verbs for their own languages and non-common languages - else - if(ending=="!") - verb = "roars" - else if(ending=="?") - verb = "hisses curiously" - return verb - - -/mob/living/carbon/alien/adjustToxLoss(amount) - return STATUS_UPDATE_NONE - -/mob/living/carbon/alien/adjustFireLoss(amount) // Weak to Fire - if(amount > 0) - return ..(amount * 2) - else - return ..(amount) - - -/mob/living/carbon/alien/check_eye_prot() - return 2 - -/mob/living/carbon/alien/updatehealth(reason = "none given") - if(status_flags & GODMODE) - health = maxHealth - stat = CONSCIOUS - return - health = maxHealth - getOxyLoss() - getFireLoss() - getBruteLoss() - getCloneLoss() - - update_stat("updatehealth([reason])") - med_hud_set_health() - med_hud_set_status() - handle_hud_icons_health() - -/mob/living/carbon/alien/handle_environment(var/datum/gas_mixture/environment) - - if(!environment) - return - - var/loc_temp = get_temperature(environment) - -// to_chat(world, "Loc temp: [loc_temp] - Body temp: [bodytemperature] - Fireloss: [getFireLoss()] - Fire protection: [heat_protection] - Location: [loc] - src: [src]") - - // Aliens are now weak to fire. - - //After then, it reacts to the surrounding atmosphere based on your thermal protection - if(!on_fire) // If you're on fire, ignore local air temperature - if(loc_temp > bodytemperature) - //Place is hotter than we are - var/thermal_protection = heat_protection //This returns a 0 - 1 value, which corresponds to the percentage of protection based on what you're wearing and what you're exposed to. - if(thermal_protection < 1) - bodytemperature += (1-thermal_protection) * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR) - else - bodytemperature += 1 * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR) - // bodytemperature -= max((loc_temp - bodytemperature / BODYTEMP_AUTORECOVERY_DIVISOR), BODYTEMP_AUTORECOVERY_MINIMUM) - - // +/- 50 degrees from 310.15K is the 'safe' zone, where no damage is dealt. - if(bodytemperature > 360.15) - //Body temperature is too hot. - throw_alert("alien_fire", /obj/screen/alert/alien_fire) - switch(bodytemperature) - if(360 to 400) - apply_damage(HEAT_DAMAGE_LEVEL_1, BURN) - if(400 to 460) - apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) - if(460 to INFINITY) - if(on_fire) - apply_damage(HEAT_DAMAGE_LEVEL_3, BURN) - else - apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) - else - clear_alert("alien_fire") - -/mob/living/carbon/alien/handle_fire()//Aliens on fire code - if(..()) - return - bodytemperature += BODYTEMP_HEATING_MAX //If you're on fire, you heat up! - return - -/mob/living/carbon/alien/IsAdvancedToolUser() - return has_fine_manipulation - -/mob/living/carbon/alien/Stat() - ..() - statpanel("Status") - stat(null, "Intent: [a_intent]") - stat(null, "Move Mode: [m_intent]") - show_stat_emergency_shuttle_eta() - -/mob/living/carbon/alien/SetStunned(amount, updating = 1, force = 0) - ..() - if(!(status_flags & CANSTUN) && amount) - // add some movement delay - move_delay_add = min(move_delay_add + round(amount / 2), 10) // a maximum delay of 10 - -/mob/living/carbon/alien/movement_delay() - . = ..() - . += move_delay_add + config.alien_delay //move_delay_add is used to slow aliens with stuns - -/mob/living/carbon/alien/getDNA() - return null - -/mob/living/carbon/alien/setDNA() - return - -/mob/living/carbon/alien/verb/nightvisiontoggle() - set name = "Toggle Night Vision" - set category = "Alien" - - if(!nightvision) - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - nightvision = TRUE - usr.hud_used.nightvisionicon.icon_state = "nightvision1" - else if(nightvision) - see_in_dark = initial(see_in_dark) - lighting_alpha = initial(lighting_alpha) - nightvision = FALSE - usr.hud_used.nightvisionicon.icon_state = "nightvision0" - - update_sight() - - -/mob/living/carbon/alien/assess_threat(var/mob/living/simple_animal/bot/secbot/judgebot, var/lasercolor) - if(judgebot.emagged == 2) - return 10 //Everyone is a criminal! - var/threatcount = 0 - - //Securitrons can't identify aliens - if(!lasercolor && judgebot.idcheck) - threatcount += 4 - - //Lasertag bullshit - if(lasercolor) - if(lasercolor == "b")//Lasertag turrets target the opposing team, how great is that? -Sieve - if((istype(r_hand,/obj/item/gun/energy/laser/tag/red)) || (istype(l_hand,/obj/item/gun/energy/laser/tag/red))) - threatcount += 4 - - if(lasercolor == "r") - if((istype(r_hand,/obj/item/gun/energy/laser/tag/blue)) || (istype(l_hand,/obj/item/gun/energy/laser/tag/blue))) - threatcount += 4 - - return threatcount - - //Check for weapons - if(judgebot.weaponscheck) - if(judgebot.check_for_weapons(l_hand)) - threatcount += 4 - if(judgebot.check_for_weapons(r_hand)) - threatcount += 4 - - //Mindshield implants imply trustworthyness - if(ismindshielded(src)) - threatcount -= 1 - - return threatcount - -/*---------------------------------------- -Proc: AddInfectionImages() -Des: Gives the client of the alien an image on each infected mob. -----------------------------------------*/ -/mob/living/carbon/alien/proc/AddInfectionImages() - if(client) - for(var/mob/living/C in GLOB.mob_list) - if(C.status_flags & XENO_HOST) - var/obj/item/organ/internal/body_egg/alien_embryo/A = C.get_int_organ(/obj/item/organ/internal/body_egg/alien_embryo) - if(A) - var/I = image('icons/mob/alien.dmi', loc = C, icon_state = "infected[A.stage]") - client.images += I - return - - -/*---------------------------------------- -Proc: RemoveInfectionImages() -Des: Removes all infected images from the alien. -----------------------------------------*/ -/mob/living/carbon/alien/proc/RemoveInfectionImages() - if(client) - for(var/image/I in client.images) - if(dd_hasprefix_case(I.icon_state, "infected")) - qdel(I) - return - -/mob/living/carbon/alien/canBeHandcuffed() - return 1 - -/mob/living/carbon/alien/proc/updatePlasmaDisplay() - if(hud_used) //clientless aliens - hud_used.alien_plasma_display.maptext = "
        [getPlasma()]
        " - -/mob/living/carbon/alien/larva/updatePlasmaDisplay() - return - -/mob/living/carbon/alien/can_use_vents() - return - -/mob/living/carbon/alien/handle_footstep(turf/T) - if(..()) - if(T.footstep_sounds["xeno"]) - var/S = pick(T.footstep_sounds["xeno"]) - if(S) - if(m_intent == MOVE_INTENT_RUN) - if(!(step_count % 2)) //every other turf makes a sound - return 0 - - var/range = -(world.view - 2) - range -= 0.666 //-(7 - 2) = (-5) = -5 | -5 - (0.666) = -5.666 | (7 + -5.666) = 1.334 | 1.334 * 3 = 4.002 | range(4.002) = range(4) - var/volume = 5 - - if(m_intent == MOVE_INTENT_WALK) - return 0 //silent when walking - - if(buckled || lying || throwing) - return 0 //people flying, lying down or sitting do not step - - if(!has_gravity(src)) - if(step_count % 3) //this basically says, every three moves make a noise - return 0 //1st - none, 1%3==1, 2nd - none, 2%3==2, 3rd - noise, 3%3==0 - - playsound(T, S, volume, 1, range) - return 1 - return 0 - -/mob/living/carbon/alien/getTrail() - if(getBruteLoss() < 200) - return pick("xltrails_1", "xltrails_2") - else - return pick("xttrails_1", "xttrails_2") - -/mob/living/carbon/alien/update_sight() - if(!client) - return - if(stat == DEAD) - grant_death_vision() - return - - see_invisible = initial(see_invisible) - sight = SEE_MOBS - if(nightvision) - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - else - see_in_dark = initial(see_in_dark) - lighting_alpha = initial(lighting_alpha) - - if(client.eye != src) - var/atom/A = client.eye - if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. - return - - for(var/obj/item/organ/internal/cyberimp/eyes/E in internal_organs) - sight |= E.vision_flags - if(E.see_in_dark) - see_in_dark = max(see_in_dark, E.see_in_dark) - if(E.see_invisible) - see_invisible = min(see_invisible, E.see_invisible) - if(!isnull(E.lighting_alpha)) - lighting_alpha = min(lighting_alpha, E.lighting_alpha) - - SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) - sync_lighting_plane_alpha() \ No newline at end of file +/mob/living/carbon/alien + name = "alien" + voice_name = "alien" + speak_emote = list("hisses") + icon = 'icons/mob/alien.dmi' + gender = NEUTER + dna = null + alien_talk_understand = TRUE + + var/nightvision = FALSE + see_in_dark = 4 + + var/obj/item/card/id/wear_id = null // Fix for station bounced radios -- Skie + var/has_fine_manipulation = FALSE + var/move_delay_add = FALSE // movement delay to add + + status_flags = CANPARALYSE|CANPUSH + var/heal_rate = 5 + + var/large = FALSE + var/heat_protection = 0.5 + var/leaping = FALSE + ventcrawler = 2 + var/list/alien_organs = list() + var/death_message = "lets out a waning guttural screech, green blood bubbling from its maw..." + var/death_sound = 'sound/voice/hiss6.ogg' + +/mob/living/carbon/alien/New() + verbs += /mob/living/verb/mob_sleep + verbs += /mob/living/verb/lay_down + alien_organs += new /obj/item/organ/internal/brain/xeno + alien_organs += new /obj/item/organ/internal/xenos/hivenode + alien_organs += new /obj/item/organ/internal/ears + for(var/obj/item/organ/internal/I in alien_organs) + I.insert(src) + ..() + +/mob/living/carbon/alien/get_default_language() + if(default_language) + return default_language + return GLOB.all_languages["Xenomorph"] + +/mob/living/carbon/alien/say_quote(var/message, var/datum/language/speaking = null) + var/verb = "hisses" + var/ending = copytext(message, length(message)) + + if(speaking && (speaking.name != "Galactic Common")) //this is so adminbooze xenos speaking common have their custom verbs, + verb = speaking.get_spoken_verb(ending) //and use normal verbs for their own languages and non-common languages + else + if(ending=="!") + verb = "roars" + else if(ending=="?") + verb = "hisses curiously" + return verb + + +/mob/living/carbon/alien/adjustToxLoss(amount) + return STATUS_UPDATE_NONE + +/mob/living/carbon/alien/adjustFireLoss(amount) // Weak to Fire + if(amount > 0) + return ..(amount * 2) + else + return ..(amount) + + +/mob/living/carbon/alien/check_eye_prot() + return 2 + +/mob/living/carbon/alien/updatehealth(reason = "none given") + if(status_flags & GODMODE) + health = maxHealth + stat = CONSCIOUS + return + health = maxHealth - getOxyLoss() - getFireLoss() - getBruteLoss() - getCloneLoss() + + update_stat("updatehealth([reason])") + med_hud_set_health() + med_hud_set_status() + handle_hud_icons_health() + +/mob/living/carbon/alien/handle_environment(var/datum/gas_mixture/environment) + + if(!environment) + return + + var/loc_temp = get_temperature(environment) + +// to_chat(world, "Loc temp: [loc_temp] - Body temp: [bodytemperature] - Fireloss: [getFireLoss()] - Fire protection: [heat_protection] - Location: [loc] - src: [src]") + + // Aliens are now weak to fire. + + //After then, it reacts to the surrounding atmosphere based on your thermal protection + if(!on_fire) // If you're on fire, ignore local air temperature + if(loc_temp > bodytemperature) + //Place is hotter than we are + var/thermal_protection = heat_protection //This returns a 0 - 1 value, which corresponds to the percentage of protection based on what you're wearing and what you're exposed to. + if(thermal_protection < 1) + bodytemperature += (1-thermal_protection) * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR) + else + bodytemperature += 1 * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR) + // bodytemperature -= max((loc_temp - bodytemperature / BODYTEMP_AUTORECOVERY_DIVISOR), BODYTEMP_AUTORECOVERY_MINIMUM) + + // +/- 50 degrees from 310.15K is the 'safe' zone, where no damage is dealt. + if(bodytemperature > 360.15) + //Body temperature is too hot. + throw_alert("alien_fire", /obj/screen/alert/alien_fire) + switch(bodytemperature) + if(360 to 400) + apply_damage(HEAT_DAMAGE_LEVEL_1, BURN) + if(400 to 460) + apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) + if(460 to INFINITY) + if(on_fire) + apply_damage(HEAT_DAMAGE_LEVEL_3, BURN) + else + apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) + else + clear_alert("alien_fire") + +/mob/living/carbon/alien/handle_fire()//Aliens on fire code + if(..()) + return + bodytemperature += BODYTEMP_HEATING_MAX //If you're on fire, you heat up! + return + +/mob/living/carbon/alien/IsAdvancedToolUser() + return has_fine_manipulation + +/mob/living/carbon/alien/Stat() + ..() + statpanel("Status") + stat(null, "Intent: [a_intent]") + stat(null, "Move Mode: [m_intent]") + show_stat_emergency_shuttle_eta() + +/mob/living/carbon/alien/SetStunned(amount, updating = 1, force = 0) + ..() + if(!(status_flags & CANSTUN) && amount) + // add some movement delay + move_delay_add = min(move_delay_add + round(amount / 2), 10) // a maximum delay of 10 + +/mob/living/carbon/alien/movement_delay() + . = ..() + . += move_delay_add + config.alien_delay //move_delay_add is used to slow aliens with stuns + +/mob/living/carbon/alien/getDNA() + return null + +/mob/living/carbon/alien/setDNA() + return + +/mob/living/carbon/alien/verb/nightvisiontoggle() + set name = "Toggle Night Vision" + set category = "Alien" + + if(!nightvision) + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + nightvision = TRUE + usr.hud_used.nightvisionicon.icon_state = "nightvision1" + else if(nightvision) + see_in_dark = initial(see_in_dark) + lighting_alpha = initial(lighting_alpha) + nightvision = FALSE + usr.hud_used.nightvisionicon.icon_state = "nightvision0" + + update_sight() + + +/mob/living/carbon/alien/assess_threat(var/mob/living/simple_animal/bot/secbot/judgebot, var/lasercolor) + if(judgebot.emagged == 2) + return 10 //Everyone is a criminal! + var/threatcount = 0 + + //Securitrons can't identify aliens + if(!lasercolor && judgebot.idcheck) + threatcount += 4 + + //Lasertag bullshit + if(lasercolor) + if(lasercolor == "b")//Lasertag turrets target the opposing team, how great is that? -Sieve + if((istype(r_hand,/obj/item/gun/energy/laser/tag/red)) || (istype(l_hand,/obj/item/gun/energy/laser/tag/red))) + threatcount += 4 + + if(lasercolor == "r") + if((istype(r_hand,/obj/item/gun/energy/laser/tag/blue)) || (istype(l_hand,/obj/item/gun/energy/laser/tag/blue))) + threatcount += 4 + + return threatcount + + //Check for weapons + if(judgebot.weaponscheck) + if(judgebot.check_for_weapons(l_hand)) + threatcount += 4 + if(judgebot.check_for_weapons(r_hand)) + threatcount += 4 + + //Mindshield implants imply trustworthyness + if(ismindshielded(src)) + threatcount -= 1 + + return threatcount + +/*---------------------------------------- +Proc: AddInfectionImages() +Des: Gives the client of the alien an image on each infected mob. +----------------------------------------*/ +/mob/living/carbon/alien/proc/AddInfectionImages() + if(client) + for(var/mob/living/C in GLOB.mob_list) + if(C.status_flags & XENO_HOST) + var/obj/item/organ/internal/body_egg/alien_embryo/A = C.get_int_organ(/obj/item/organ/internal/body_egg/alien_embryo) + if(A) + var/I = image('icons/mob/alien.dmi', loc = C, icon_state = "infected[A.stage]") + client.images += I + return + + +/*---------------------------------------- +Proc: RemoveInfectionImages() +Des: Removes all infected images from the alien. +----------------------------------------*/ +/mob/living/carbon/alien/proc/RemoveInfectionImages() + if(client) + for(var/image/I in client.images) + if(dd_hasprefix_case(I.icon_state, "infected")) + qdel(I) + return + +/mob/living/carbon/alien/canBeHandcuffed() + return 1 + +/mob/living/carbon/alien/proc/updatePlasmaDisplay() + if(hud_used) //clientless aliens + hud_used.alien_plasma_display.maptext = "
        [getPlasma()]
        " + +/mob/living/carbon/alien/larva/updatePlasmaDisplay() + return + +/mob/living/carbon/alien/can_use_vents() + return + +/mob/living/carbon/alien/handle_footstep(turf/T) + if(..()) + if(T.footstep_sounds["xeno"]) + var/S = pick(T.footstep_sounds["xeno"]) + if(S) + if(m_intent == MOVE_INTENT_RUN) + if(!(step_count % 2)) //every other turf makes a sound + return 0 + + var/range = -(world.view - 2) + range -= 0.666 //-(7 - 2) = (-5) = -5 | -5 - (0.666) = -5.666 | (7 + -5.666) = 1.334 | 1.334 * 3 = 4.002 | range(4.002) = range(4) + var/volume = 5 + + if(m_intent == MOVE_INTENT_WALK) + return 0 //silent when walking + + if(buckled || lying || throwing) + return 0 //people flying, lying down or sitting do not step + + if(!has_gravity(src)) + if(step_count % 3) //this basically says, every three moves make a noise + return 0 //1st - none, 1%3==1, 2nd - none, 2%3==2, 3rd - noise, 3%3==0 + + playsound(T, S, volume, 1, range) + return 1 + return 0 + +/mob/living/carbon/alien/getTrail() + if(getBruteLoss() < 200) + return pick("xltrails_1", "xltrails_2") + else + return pick("xttrails_1", "xttrails_2") + +/mob/living/carbon/alien/update_sight() + if(!client) + return + if(stat == DEAD) + grant_death_vision() + return + + see_invisible = initial(see_invisible) + sight = SEE_MOBS + if(nightvision) + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + else + see_in_dark = initial(see_in_dark) + lighting_alpha = initial(lighting_alpha) + + if(client.eye != src) + var/atom/A = client.eye + if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. + return + + for(var/obj/item/organ/internal/cyberimp/eyes/E in internal_organs) + sight |= E.vision_flags + if(E.see_in_dark) + see_in_dark = max(see_in_dark, E.see_in_dark) + if(E.see_invisible) + see_invisible = min(see_invisible, E.see_invisible) + if(!isnull(E.lighting_alpha)) + lighting_alpha = min(lighting_alpha, E.lighting_alpha) + + SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) + sync_lighting_plane_alpha() diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index 04924d4b310..c88a592a01b 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -80,4 +80,4 @@ In all, this is a lot like the monkey code. /N damage = rand(10, 40) adjustBruteLoss(damage) add_attack_logs(M, src, "Slime'd for [damage] damage") - updatehealth("slime attack") \ No newline at end of file + updatehealth("slime attack") diff --git a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm index 9ec48c51bb1..1ea256357e9 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm @@ -1,170 +1,170 @@ -/*NOTES: -These are general powers. Specific powers are stored under the appropriate alien creature type. -*/ - -/*Alien spit now works like a taser shot. It won't home in on the target but will act the same once it does hit. -Doesn't work on other aliens/AI.*/ - - -/mob/living/carbon/proc/powerc(X, Y)//Y is optional, checks for weed planting. X can be null. - if(stat) - to_chat(src, "You must be conscious to do this.") - return 0 - else if(X && getPlasma() < X) - to_chat(src, "Not enough plasma stored.") - return 0 - else if(Y && (!isturf(src.loc) || istype(src.loc, /turf/space))) - to_chat(src, "You can't place that here!") - return 0 - else return 1 - -/mob/living/carbon/alien/humanoid/verb/plant() - set name = "Plant Weeds (50)" - set desc = "Plants some alien weeds" - set category = "Alien" - - if(locate(/obj/structure/alien/weeds/node) in get_turf(src)) - to_chat(src, "There's already a weed node here.") - return - - if(powerc(50,1)) - adjustPlasma(-50) - for(var/mob/O in viewers(src, null)) - O.show_message(text("[src] has planted some alien weeds!"), 1) - new /obj/structure/alien/weeds/node(loc) - return - -/mob/living/carbon/alien/humanoid/verb/whisp(mob/M as mob in oview()) - set name = "Whisper (10)" - set desc = "Whisper to someone" - set category = "Alien" - - if(powerc(10)) - adjustPlasma(-10) - var/msg = sanitize(input("Message:", "Alien Whisper") as text|null) - if(msg) - log_say("(AWHISPER to [key_name(M)]) [msg]", src) - to_chat(M, "You hear a strange, alien voice in your head...[msg]") - to_chat(src, "You said: [msg] to [M]") - for(var/mob/dead/observer/G in GLOB.player_list) - G.show_message("Alien message from [src] ([ghost_follow_link(src, ghost=G)]) to [M] ([ghost_follow_link(M, ghost=G)]): [msg]") - return - -/mob/living/carbon/alien/humanoid/verb/transfer_plasma(mob/living/carbon/alien/M as mob in oview()) - set name = "Transfer Plasma" - set desc = "Transfer Plasma to another alien" - set category = "Alien" - - if(isalien(M)) - var/amount = input("Amount:", "Transfer Plasma to [M]") as num - if(amount) - amount = abs(round(amount)) - if(powerc(amount)) - if(get_dist(src,M) <= 1) - M.adjustPlasma(amount) - adjustPlasma(-amount) - to_chat(M, "[src] has transfered [amount] plasma to you.") - to_chat(src, {"You have trasferred [amount] plasma to [M]"}) - else - to_chat(src, "You need to be closer.") - return - - -/mob/living/carbon/alien/humanoid/proc/corrosive_acid(atom/target) //If they right click to corrode, an error will flash if its an invalid target./N - set name = "Corrossive Acid (200)" - set desc = "Drench an object in acid, destroying it over time." - set category = "Alien" - - if(powerc(200)) - if(target in oview(1)) - if(target.acid_act(200, 100)) - visible_message("[src] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!") - adjustPlasma(-200) - else - to_chat(src, "You cannot dissolve this object.") - else - to_chat(src, "[target] is too far away.") - -/mob/living/carbon/alien/humanoid/proc/neurotoxin() // ok - set name = "Spit Neurotoxin (50)" - set desc = "Spits neurotoxin at someone, paralyzing them for a short time." - set category = "Alien" - - if(powerc(50)) - adjustPlasma(-50) - src.visible_message("[src] spits neurotoxin!", "You spit neurotoxin.") - - var/turf/T = loc - var/turf/U = get_step(src, dir) // Get the tile infront of the move, based on their direction - if(!isturf(U) || !isturf(T)) - return - - var/obj/item/projectile/bullet/neurotoxin/A = new /obj/item/projectile/bullet/neurotoxin(usr.loc) - A.current = U - A.firer = src - A.yo = U.y - T.y - A.xo = U.x - T.x - A.fire() - A.newtonian_move(get_dir(U, T)) - newtonian_move(get_dir(U, T)) - return - -/mob/living/carbon/alien/humanoid/proc/resin() // -- TLE - set name = "Secrete Resin (55)" - set desc = "Secrete tough malleable resin." - set category = "Alien" - - if(powerc(55)) - var/choice = input("Choose what you wish to shape.","Resin building") as null|anything in list("resin wall","resin membrane","resin nest") //would do it through typesof but then the player choice would have the type path and we don't want the internal workings to be exposed ICly - Urist - - if(!choice || !powerc(55)) return - adjustPlasma(-55) - for(var/mob/O in viewers(src, null)) - O.show_message(text("[src] vomits up a thick purple substance and shapes it!"), 1) - switch(choice) - if("resin wall") - new /obj/structure/alien/resin/wall(loc) - if("resin membrane") - new /obj/structure/alien/resin/membrane(loc) - if("resin nest") - new /obj/structure/bed/nest(loc) - return - -/mob/living/carbon/alien/humanoid/verb/regurgitate() - set name = "Regurgitate" - set desc = "Empties the contents of your stomach" - set category = "Alien" - - if(powerc()) - if(stomach_contents.len) - for(var/mob/M in src) - if(M in stomach_contents) - stomach_contents.Remove(M) - M.forceMove(loc) - //Paralyse(10) - src.visible_message("[src] hurls out the contents of [p_their()] stomach!") - return - -/mob/living/carbon/proc/getPlasma() - var/obj/item/organ/internal/xenos/plasmavessel/vessel = get_int_organ(/obj/item/organ/internal/xenos/plasmavessel) - if(!vessel) return 0 - return vessel.stored_plasma - - -/mob/living/carbon/proc/adjustPlasma(amount) - var/obj/item/organ/internal/xenos/plasmavessel/vessel = get_int_organ(/obj/item/organ/internal/xenos/plasmavessel) - if(!vessel) return - vessel.stored_plasma = max(vessel.stored_plasma + amount,0) - vessel.stored_plasma = min(vessel.stored_plasma, vessel.max_plasma) //upper limit of max_plasma, lower limit of 0 - return 1 - -/mob/living/carbon/alien/adjustPlasma(amount) - . = ..() - updatePlasmaDisplay() - -/mob/living/carbon/proc/usePlasma(amount) - if(getPlasma() >= amount) - adjustPlasma(-amount) - return 1 - - return 0 +/*NOTES: +These are general powers. Specific powers are stored under the appropriate alien creature type. +*/ + +/*Alien spit now works like a taser shot. It won't home in on the target but will act the same once it does hit. +Doesn't work on other aliens/AI.*/ + + +/mob/living/carbon/proc/powerc(X, Y)//Y is optional, checks for weed planting. X can be null. + if(stat) + to_chat(src, "You must be conscious to do this.") + return 0 + else if(X && getPlasma() < X) + to_chat(src, "Not enough plasma stored.") + return 0 + else if(Y && (!isturf(src.loc) || istype(src.loc, /turf/space))) + to_chat(src, "You can't place that here!") + return 0 + else return 1 + +/mob/living/carbon/alien/humanoid/verb/plant() + set name = "Plant Weeds (50)" + set desc = "Plants some alien weeds" + set category = "Alien" + + if(locate(/obj/structure/alien/weeds/node) in get_turf(src)) + to_chat(src, "There's already a weed node here.") + return + + if(powerc(50,1)) + adjustPlasma(-50) + for(var/mob/O in viewers(src, null)) + O.show_message(text("[src] has planted some alien weeds!"), 1) + new /obj/structure/alien/weeds/node(loc) + return + +/mob/living/carbon/alien/humanoid/verb/whisp(mob/M as mob in oview()) + set name = "Whisper (10)" + set desc = "Whisper to someone" + set category = "Alien" + + if(powerc(10)) + adjustPlasma(-10) + var/msg = sanitize(input("Message:", "Alien Whisper") as text|null) + if(msg) + log_say("(AWHISPER to [key_name(M)]) [msg]", src) + to_chat(M, "You hear a strange, alien voice in your head...[msg]") + to_chat(src, "You said: [msg] to [M]") + for(var/mob/dead/observer/G in GLOB.player_list) + G.show_message("Alien message from [src] ([ghost_follow_link(src, ghost=G)]) to [M] ([ghost_follow_link(M, ghost=G)]): [msg]") + return + +/mob/living/carbon/alien/humanoid/verb/transfer_plasma(mob/living/carbon/alien/M as mob in oview()) + set name = "Transfer Plasma" + set desc = "Transfer Plasma to another alien" + set category = "Alien" + + if(isalien(M)) + var/amount = input("Amount:", "Transfer Plasma to [M]") as num + if(amount) + amount = abs(round(amount)) + if(powerc(amount)) + if(get_dist(src,M) <= 1) + M.adjustPlasma(amount) + adjustPlasma(-amount) + to_chat(M, "[src] has transfered [amount] plasma to you.") + to_chat(src, {"You have trasferred [amount] plasma to [M]"}) + else + to_chat(src, "You need to be closer.") + return + + +/mob/living/carbon/alien/humanoid/proc/corrosive_acid(atom/target) //If they right click to corrode, an error will flash if its an invalid target./N + set name = "Corrossive Acid (200)" + set desc = "Drench an object in acid, destroying it over time." + set category = "Alien" + + if(powerc(200)) + if(target in oview(1)) + if(target.acid_act(200, 100)) + visible_message("[src] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!") + adjustPlasma(-200) + else + to_chat(src, "You cannot dissolve this object.") + else + to_chat(src, "[target] is too far away.") + +/mob/living/carbon/alien/humanoid/proc/neurotoxin() // ok + set name = "Spit Neurotoxin (50)" + set desc = "Spits neurotoxin at someone, paralyzing them for a short time." + set category = "Alien" + + if(powerc(50)) + adjustPlasma(-50) + src.visible_message("[src] spits neurotoxin!", "You spit neurotoxin.") + + var/turf/T = loc + var/turf/U = get_step(src, dir) // Get the tile infront of the move, based on their direction + if(!isturf(U) || !isturf(T)) + return + + var/obj/item/projectile/bullet/neurotoxin/A = new /obj/item/projectile/bullet/neurotoxin(usr.loc) + A.current = U + A.firer = src + A.yo = U.y - T.y + A.xo = U.x - T.x + A.fire() + A.newtonian_move(get_dir(U, T)) + newtonian_move(get_dir(U, T)) + return + +/mob/living/carbon/alien/humanoid/proc/resin() // -- TLE + set name = "Secrete Resin (55)" + set desc = "Secrete tough malleable resin." + set category = "Alien" + + if(powerc(55)) + var/choice = input("Choose what you wish to shape.","Resin building") as null|anything in list("resin wall","resin membrane","resin nest") //would do it through typesof but then the player choice would have the type path and we don't want the internal workings to be exposed ICly - Urist + + if(!choice || !powerc(55)) return + adjustPlasma(-55) + for(var/mob/O in viewers(src, null)) + O.show_message(text("[src] vomits up a thick purple substance and shapes it!"), 1) + switch(choice) + if("resin wall") + new /obj/structure/alien/resin/wall(loc) + if("resin membrane") + new /obj/structure/alien/resin/membrane(loc) + if("resin nest") + new /obj/structure/bed/nest(loc) + return + +/mob/living/carbon/alien/humanoid/verb/regurgitate() + set name = "Regurgitate" + set desc = "Empties the contents of your stomach" + set category = "Alien" + + if(powerc()) + if(stomach_contents.len) + for(var/mob/M in src) + if(M in stomach_contents) + stomach_contents.Remove(M) + M.forceMove(loc) + //Paralyse(10) + src.visible_message("[src] hurls out the contents of [p_their()] stomach!") + return + +/mob/living/carbon/proc/getPlasma() + var/obj/item/organ/internal/xenos/plasmavessel/vessel = get_int_organ(/obj/item/organ/internal/xenos/plasmavessel) + if(!vessel) return 0 + return vessel.stored_plasma + + +/mob/living/carbon/proc/adjustPlasma(amount) + var/obj/item/organ/internal/xenos/plasmavessel/vessel = get_int_organ(/obj/item/organ/internal/xenos/plasmavessel) + if(!vessel) return + vessel.stored_plasma = max(vessel.stored_plasma + amount,0) + vessel.stored_plasma = min(vessel.stored_plasma, vessel.max_plasma) //upper limit of max_plasma, lower limit of 0 + return 1 + +/mob/living/carbon/alien/adjustPlasma(amount) + . = ..() + updatePlasmaDisplay() + +/mob/living/carbon/proc/usePlasma(amount) + if(getPlasma() >= amount) + adjustPlasma(-amount) + return 1 + + return 0 diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm index 8e261f56d8d..2d894ee357e 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm @@ -1,48 +1,48 @@ -/mob/living/carbon/alien/humanoid/drone - name = "alien drone" - caste = "d" - maxHealth = 100 - health = 100 - icon_state = "aliend_s" - -/mob/living/carbon/alien/humanoid/drone/New() - create_reagents(100) - if(src.name == "alien drone") - src.name = text("alien drone ([rand(1, 1000)])") - src.real_name = src.name - alien_organs += new /obj/item/organ/internal/xenos/plasmavessel/drone - alien_organs += new /obj/item/organ/internal/xenos/acidgland - alien_organs += new /obj/item/organ/internal/xenos/resinspinner - ..() - -//Drones use the same base as generic humanoids. -//Drone verbs - -/mob/living/carbon/alien/humanoid/drone/verb/evolve() // -- TLE - set name = "Evolve (500)" - set desc = "Produce an interal egg sac capable of spawning children. Only one queen can exist at a time." - set category = "Alien" - - if(powerc(500)) - // Queen check - var/no_queen = 1 - for(var/mob/living/carbon/alien/humanoid/queen/Q in GLOB.living_mob_list) - if(!Q.key && Q.get_int_organ(/obj/item/organ/internal/brain/)) - continue - no_queen = 0 - - if(src.has_brain_worms()) - to_chat(src, "We cannot perform this ability at the present time!") - return - if(no_queen) - adjustPlasma(-500) - to_chat(src, "You begin to evolve!") - for(var/mob/O in viewers(src, null)) - O.show_message(text("[src] begins to twist and contort!"), 1) - var/mob/living/carbon/alien/humanoid/queen/new_xeno = new(loc) - mind.transfer_to(new_xeno) - new_xeno.mind.name = new_xeno.name - qdel(src) - else - to_chat(src, "We already have an alive queen.") - return \ No newline at end of file +/mob/living/carbon/alien/humanoid/drone + name = "alien drone" + caste = "d" + maxHealth = 100 + health = 100 + icon_state = "aliend_s" + +/mob/living/carbon/alien/humanoid/drone/New() + create_reagents(100) + if(src.name == "alien drone") + src.name = text("alien drone ([rand(1, 1000)])") + src.real_name = src.name + alien_organs += new /obj/item/organ/internal/xenos/plasmavessel/drone + alien_organs += new /obj/item/organ/internal/xenos/acidgland + alien_organs += new /obj/item/organ/internal/xenos/resinspinner + ..() + +//Drones use the same base as generic humanoids. +//Drone verbs + +/mob/living/carbon/alien/humanoid/drone/verb/evolve() // -- TLE + set name = "Evolve (500)" + set desc = "Produce an interal egg sac capable of spawning children. Only one queen can exist at a time." + set category = "Alien" + + if(powerc(500)) + // Queen check + var/no_queen = 1 + for(var/mob/living/carbon/alien/humanoid/queen/Q in GLOB.living_mob_list) + if(!Q.key && Q.get_int_organ(/obj/item/organ/internal/brain/)) + continue + no_queen = 0 + + if(src.has_brain_worms()) + to_chat(src, "We cannot perform this ability at the present time!") + return + if(no_queen) + adjustPlasma(-500) + to_chat(src, "You begin to evolve!") + for(var/mob/O in viewers(src, null)) + O.show_message(text("[src] begins to twist and contort!"), 1) + var/mob/living/carbon/alien/humanoid/queen/new_xeno = new(loc) + mind.transfer_to(new_xeno) + new_xeno.mind.name = new_xeno.name + qdel(src) + else + to_chat(src, "We already have an alive queen.") + return diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm index f950eee4dbf..4cba49a2a03 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm @@ -1,131 +1,131 @@ -/mob/living/carbon/alien/humanoid/hunter - name = "alien hunter" - caste = "h" - maxHealth = 125 - health = 125 - icon_state = "alienh_s" - -/mob/living/carbon/alien/humanoid/hunter/New() - create_reagents(100) - if(name == "alien hunter") - name = text("alien hunter ([rand(1, 1000)])") - real_name = name - alien_organs += new /obj/item/organ/internal/xenos/plasmavessel/hunter - ..() - -/mob/living/carbon/alien/humanoid/hunter/movement_delay() - . = -1 //hunters are sanic - . += ..() //but they still need to slow down on stun - -/mob/living/carbon/alien/humanoid/hunter/handle_hud_icons_health() - ..() //-Yvarov - - if(healths) - if(stat != 2) - switch(health) - if(125 to INFINITY) - healths.icon_state = "health0" - if(100 to 125) - healths.icon_state = "health1" - if(50 to 100) - healths.icon_state = "health2" - if(25 to 50) - healths.icon_state = "health3" - if(0 to 25) - healths.icon_state = "health4" - else - healths.icon_state = "health5" - else - healths.icon_state = "health6" - - -/mob/living/carbon/alien/humanoid/hunter/handle_environment() - if(m_intent == MOVE_INTENT_RUN || resting) - ..() - else - adjustPlasma(-heal_rate) - - -//Hunter verbs - -/mob/living/carbon/alien/humanoid/hunter/proc/toggle_leap(var/message = 1) - leap_on_click = !leap_on_click - leap_icon.icon_state = "leap_[leap_on_click ? "on":"off"]" - if(message) - to_chat(src, "You will now [leap_on_click ? "leap at":"slash at"] enemies!") - else - return - -/mob/living/carbon/alien/humanoid/hunter/ClickOn(var/atom/A, var/params) - face_atom(A) - if(leap_on_click) - leap_at(A) - else - ..() - -#define MAX_ALIEN_LEAP_DIST 7 - -/mob/living/carbon/alien/humanoid/hunter/proc/leap_at(var/atom/A) - if(pounce_cooldown > world.time) - to_chat(src, "You are too fatigued to pounce right now!") - return - - if(leaping) //Leap while you leap, so you can leap while you leap - return - - if(!has_gravity(src) || !has_gravity(A)) - to_chat(src, "It is unsafe to leap without gravity!") - //It's also extremely buggy visually, so it's balance+bugfix - return - if(lying) - return - - else //Maybe uses plasma in the future, although that wouldn't make any sense... - leaping = 1 - update_icons() - throw_at(A, MAX_ALIEN_LEAP_DIST, 1, spin = 0, diagonals_first = 1, callback = CALLBACK(src, .leap_end)) - -/mob/living/carbon/alien/humanoid/hunter/proc/leap_end() - leaping = 0 - update_icons() - -/mob/living/carbon/alien/humanoid/hunter/throw_impact(atom/A) - if(!leaping) - return ..() - - if(A) - if(isliving(A)) - var/mob/living/L = A - var/blocked = 0 - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(H.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) - blocked = 1 - if(!blocked) - L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.apply_effect(5, WEAKEN, H.run_armor_check(null, "melee")) - else - L.Weaken(5) - sleep(2)//Runtime prevention (infinite bump() calls on hulks) - step_towards(src,L) - else - Weaken(2, 1, 1) - - toggle_leap(0) - pounce_cooldown = world.time + pounce_cooldown_time - else if(A.density && !A.CanPass(src)) - visible_message("[src] smashes into [A]!", "[src] smashes into [A]!") - Weaken(2, 1, 1) - - if(leaping) - leaping = 0 - update_icons() - update_canmove() - - -/mob/living/carbon/alien/humanoid/float(on) - if(leaping) - return - ..() +/mob/living/carbon/alien/humanoid/hunter + name = "alien hunter" + caste = "h" + maxHealth = 125 + health = 125 + icon_state = "alienh_s" + +/mob/living/carbon/alien/humanoid/hunter/New() + create_reagents(100) + if(name == "alien hunter") + name = text("alien hunter ([rand(1, 1000)])") + real_name = name + alien_organs += new /obj/item/organ/internal/xenos/plasmavessel/hunter + ..() + +/mob/living/carbon/alien/humanoid/hunter/movement_delay() + . = -1 //hunters are sanic + . += ..() //but they still need to slow down on stun + +/mob/living/carbon/alien/humanoid/hunter/handle_hud_icons_health() + ..() //-Yvarov + + if(healths) + if(stat != 2) + switch(health) + if(125 to INFINITY) + healths.icon_state = "health0" + if(100 to 125) + healths.icon_state = "health1" + if(50 to 100) + healths.icon_state = "health2" + if(25 to 50) + healths.icon_state = "health3" + if(0 to 25) + healths.icon_state = "health4" + else + healths.icon_state = "health5" + else + healths.icon_state = "health6" + + +/mob/living/carbon/alien/humanoid/hunter/handle_environment() + if(m_intent == MOVE_INTENT_RUN || resting) + ..() + else + adjustPlasma(-heal_rate) + + +//Hunter verbs + +/mob/living/carbon/alien/humanoid/hunter/proc/toggle_leap(var/message = 1) + leap_on_click = !leap_on_click + leap_icon.icon_state = "leap_[leap_on_click ? "on":"off"]" + if(message) + to_chat(src, "You will now [leap_on_click ? "leap at":"slash at"] enemies!") + else + return + +/mob/living/carbon/alien/humanoid/hunter/ClickOn(var/atom/A, var/params) + face_atom(A) + if(leap_on_click) + leap_at(A) + else + ..() + +#define MAX_ALIEN_LEAP_DIST 7 + +/mob/living/carbon/alien/humanoid/hunter/proc/leap_at(var/atom/A) + if(pounce_cooldown > world.time) + to_chat(src, "You are too fatigued to pounce right now!") + return + + if(leaping) //Leap while you leap, so you can leap while you leap + return + + if(!has_gravity(src) || !has_gravity(A)) + to_chat(src, "It is unsafe to leap without gravity!") + //It's also extremely buggy visually, so it's balance+bugfix + return + if(lying) + return + + else //Maybe uses plasma in the future, although that wouldn't make any sense... + leaping = 1 + update_icons() + throw_at(A, MAX_ALIEN_LEAP_DIST, 1, spin = 0, diagonals_first = 1, callback = CALLBACK(src, .leap_end)) + +/mob/living/carbon/alien/humanoid/hunter/proc/leap_end() + leaping = 0 + update_icons() + +/mob/living/carbon/alien/humanoid/hunter/throw_impact(atom/A) + if(!leaping) + return ..() + + if(A) + if(isliving(A)) + var/mob/living/L = A + var/blocked = 0 + if(ishuman(A)) + var/mob/living/carbon/human/H = A + if(H.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) + blocked = 1 + if(!blocked) + L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") + if(ishuman(L)) + var/mob/living/carbon/human/H = L + H.apply_effect(5, WEAKEN, H.run_armor_check(null, "melee")) + else + L.Weaken(5) + sleep(2)//Runtime prevention (infinite bump() calls on hulks) + step_towards(src,L) + else + Weaken(2, 1, 1) + + toggle_leap(0) + pounce_cooldown = world.time + pounce_cooldown_time + else if(A.density && !A.CanPass(src)) + visible_message("[src] smashes into [A]!", "[src] smashes into [A]!") + Weaken(2, 1, 1) + + if(leaping) + leaping = 0 + update_icons() + update_canmove() + + +/mob/living/carbon/alien/humanoid/float(on) + if(leaping) + return + ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm index 3acb1fe8713..c436a43e4ff 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm @@ -1,85 +1,85 @@ -/mob/living/carbon/alien/humanoid/sentinel - name = "alien sentinel" - caste = "s" - maxHealth = 150 - health = 150 - icon_state = "aliens_s" - -/mob/living/carbon/alien/humanoid/sentinel/large - name = "alien praetorian" - icon = 'icons/mob/alienlarge.dmi' - icon_state = "prat_s" - pixel_x = -16 - maxHealth = 200 - health = 200 - large = 1 - -/mob/living/carbon/alien/humanoid/sentinel/praetorian - name = "alien praetorian" - maxHealth = 200 - health = 200 - large = 1 - -/mob/living/carbon/alien/humanoid/sentinel/large/update_icons() - overlays.Cut() - if(stat == DEAD) - icon_state = "prat_dead" - else if(stat == UNCONSCIOUS || lying || resting) - icon_state = "prat_sleep" - else - icon_state = "prat_s" - - for(var/image/I in overlays_standing) - overlays += I - -/mob/living/carbon/alien/humanoid/sentinel/New() - create_reagents(100) - if(name == "alien sentinel") - name = text("alien sentinel ([rand(1, 1000)])") - real_name = name - alien_organs += new /obj/item/organ/internal/xenos/plasmavessel - alien_organs += new /obj/item/organ/internal/xenos/acidgland - alien_organs += new /obj/item/organ/internal/xenos/neurotoxin - ..() - -/mob/living/carbon/alien/humanoid/sentinel/handle_hud_icons_health() - ..() //-Yvarov - - if(healths) - if(stat != 2) - switch(health) - if(150 to INFINITY) - healths.icon_state = "health0" - if(100 to 150) - healths.icon_state = "health1" - if(75 to 100) - healths.icon_state = "health2" - if(25 to 75) - healths.icon_state = "health3" - if(0 to 25) - healths.icon_state = "health4" - else - healths.icon_state = "health5" - else - healths.icon_state = "health6" - -/* -/mob/living/carbon/alien/humanoid/sentinel/verb/evolve() // -- TLE - set name = "Evolve (250)" - set desc = "Become a Praetorian, Royal Guard to the Queen." - set category = "Alien" - - if(powerc(250)) - adjustToxLoss(-250) - to_chat(src, "You begin to evolve!") - for(var/mob/O in viewers(src, null)) - O.show_message(text("[src] begins to twist and contort!"), 1) - var/mob/living/carbon/alien/humanoid/sentinel/praetorian/new_xeno = new(loc) - if(mind) - mind.transfer_to(new_xeno) - else - new_xeno.key = key - new_xeno.mind.name = new_xeno.name - qdel(src) - return -*/ \ No newline at end of file +/mob/living/carbon/alien/humanoid/sentinel + name = "alien sentinel" + caste = "s" + maxHealth = 150 + health = 150 + icon_state = "aliens_s" + +/mob/living/carbon/alien/humanoid/sentinel/large + name = "alien praetorian" + icon = 'icons/mob/alienlarge.dmi' + icon_state = "prat_s" + pixel_x = -16 + maxHealth = 200 + health = 200 + large = 1 + +/mob/living/carbon/alien/humanoid/sentinel/praetorian + name = "alien praetorian" + maxHealth = 200 + health = 200 + large = 1 + +/mob/living/carbon/alien/humanoid/sentinel/large/update_icons() + overlays.Cut() + if(stat == DEAD) + icon_state = "prat_dead" + else if(stat == UNCONSCIOUS || lying || resting) + icon_state = "prat_sleep" + else + icon_state = "prat_s" + + for(var/image/I in overlays_standing) + overlays += I + +/mob/living/carbon/alien/humanoid/sentinel/New() + create_reagents(100) + if(name == "alien sentinel") + name = text("alien sentinel ([rand(1, 1000)])") + real_name = name + alien_organs += new /obj/item/organ/internal/xenos/plasmavessel + alien_organs += new /obj/item/organ/internal/xenos/acidgland + alien_organs += new /obj/item/organ/internal/xenos/neurotoxin + ..() + +/mob/living/carbon/alien/humanoid/sentinel/handle_hud_icons_health() + ..() //-Yvarov + + if(healths) + if(stat != 2) + switch(health) + if(150 to INFINITY) + healths.icon_state = "health0" + if(100 to 150) + healths.icon_state = "health1" + if(75 to 100) + healths.icon_state = "health2" + if(25 to 75) + healths.icon_state = "health3" + if(0 to 25) + healths.icon_state = "health4" + else + healths.icon_state = "health5" + else + healths.icon_state = "health6" + +/* +/mob/living/carbon/alien/humanoid/sentinel/verb/evolve() // -- TLE + set name = "Evolve (250)" + set desc = "Become a Praetorian, Royal Guard to the Queen." + set category = "Alien" + + if(powerc(250)) + adjustToxLoss(-250) + to_chat(src, "You begin to evolve!") + for(var/mob/O in viewers(src, null)) + O.show_message(text("[src] begins to twist and contort!"), 1) + var/mob/living/carbon/alien/humanoid/sentinel/praetorian/new_xeno = new(loc) + if(mind) + mind.transfer_to(new_xeno) + else + new_xeno.key = key + new_xeno.mind.name = new_xeno.name + qdel(src) + return +*/ diff --git a/code/modules/mob/living/carbon/alien/humanoid/emote.dm b/code/modules/mob/living/carbon/alien/humanoid/emote.dm index 7412120cff0..4134c3fcf35 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/emote.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/emote.dm @@ -138,4 +138,4 @@ playsound(src.loc, 'sound/voice/hiss1.ogg', 30, 1, 1) if(act == "gnarl") playsound(src.loc, 'sound/voice/hiss4.ogg', 30, 1, 1) - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index d6bb6c6c1ac..29b37294cc4 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -1,123 +1,123 @@ -/mob/living/carbon/alien/humanoid - name = "alien" - icon_state = "alien_s" - - butcher_results = list(/obj/item/reagent_containers/food/snacks/xenomeat = 5, /obj/item/stack/sheet/animalhide/xeno = 1) - var/obj/item/r_store = null - var/obj/item/l_store = null - var/caste = "" - var/alt_icon = 'icons/mob/alienleap.dmi' //used to switch between the two alien icon files. - var/next_attack = 0 - var/pounce_cooldown = 0 - var/pounce_cooldown_time = 30 - var/leap_on_click = 0 - var/custom_pixel_x_offset = 0 //for admin fuckery. - var/custom_pixel_y_offset = 0 - pass_flags = PASSTABLE - -//This is fine right now, if we're adding organ specific damage this needs to be updated -/mob/living/carbon/alien/humanoid/New() - create_reagents(1000) - if(name == "alien") - name = text("alien ([rand(1, 1000)])") - real_name = name - add_language("Xenomorph") - add_language("Hivemind") - ..() - -/mob/living/carbon/alien/humanoid/Process_Spacemove(var/check_drift = 0) - if(..()) - return 1 - - return 0 - -///mob/living/carbon/alien/humanoid/bullet_act(var/obj/item/projectile/Proj) taken care of in living - -/mob/living/carbon/alien/humanoid/emp_act(severity) - if(r_store) r_store.emp_act(severity) - if(l_store) l_store.emp_act(severity) - ..() - -/mob/living/carbon/alien/humanoid/ex_act(severity) - ..() - - var/shielded = 0 - - var/b_loss = null - var/f_loss = null - switch(severity) - if(1.0) - gib() - return - - if(2.0) - if(!shielded) - b_loss += 60 - - f_loss += 60 - - AdjustEarDamage(30, 120) - if(3.0) - b_loss += 30 - if(prob(50) && !shielded) - Paralyse(1) - AdjustEarDamage(15, 60) - - take_overall_damage(b_loss, f_loss) - -/mob/living/carbon/alien/humanoid/restrained() - if(handcuffed) - return 1 - return 0 - - -/mob/living/carbon/alien/humanoid/var/temperature_resistance = T0C+75 - -/mob/living/carbon/alien/humanoid/show_inv(mob/user as mob) - user.set_machine(src) - - var/dat = {" - - - "} - - dat += "" - - dat += "" - - dat += "" - dat += "
        Left Hand:[(l_hand && !(l_hand.flags&ABSTRACT)) ? l_hand : "Empty"]
        Right Hand:[(r_hand && !(r_hand.flags&ABSTRACT)) ? r_hand : "Empty"]
         
        Head:[(head && !(head.flags&ABSTRACT)) ? head : "Empty"]
         
        Exosuit:[(wear_suit && !(wear_suit.flags&ABSTRACT)) ? wear_suit : "Empty"]
        Pouches:[((l_store && !(l_store.flags&ABSTRACT)) || (r_store && !(r_store.flags&ABSTRACT))) ? "Full" : "Empty"]" - - dat += {"
        - Close - "} - - var/datum/browser/popup = new(user, "mob\ref[src]", "[src]", 440, 500) - popup.set_content(dat) - popup.open() - -/mob/living/carbon/alien/humanoid/canBeHandcuffed() - return 1 - -/mob/living/carbon/alien/humanoid/cuff_resist(obj/item/I) - playsound(src, 'sound/voice/hiss5.ogg', 40, 1, 1) //Alien roars when starting to break free - ..(I, cuff_break = 1) - -/mob/living/carbon/alien/humanoid/get_standard_pixel_y_offset(lying = 0) - if(leaping) - return -32 - else if(custom_pixel_y_offset) - return custom_pixel_y_offset - else - return initial(pixel_y) - -/mob/living/carbon/alien/humanoid/get_standard_pixel_x_offset(lying = 0) - if(leaping) - return -32 - else if(custom_pixel_x_offset) - return custom_pixel_x_offset - else - return initial(pixel_x) - -/mob/living/carbon/alien/humanoid/get_permeability_protection() - return 0.8 +/mob/living/carbon/alien/humanoid + name = "alien" + icon_state = "alien_s" + + butcher_results = list(/obj/item/reagent_containers/food/snacks/xenomeat = 5, /obj/item/stack/sheet/animalhide/xeno = 1) + var/obj/item/r_store = null + var/obj/item/l_store = null + var/caste = "" + var/alt_icon = 'icons/mob/alienleap.dmi' //used to switch between the two alien icon files. + var/next_attack = 0 + var/pounce_cooldown = 0 + var/pounce_cooldown_time = 30 + var/leap_on_click = 0 + var/custom_pixel_x_offset = 0 //for admin fuckery. + var/custom_pixel_y_offset = 0 + pass_flags = PASSTABLE + +//This is fine right now, if we're adding organ specific damage this needs to be updated +/mob/living/carbon/alien/humanoid/New() + create_reagents(1000) + if(name == "alien") + name = text("alien ([rand(1, 1000)])") + real_name = name + add_language("Xenomorph") + add_language("Hivemind") + ..() + +/mob/living/carbon/alien/humanoid/Process_Spacemove(var/check_drift = 0) + if(..()) + return 1 + + return 0 + +///mob/living/carbon/alien/humanoid/bullet_act(var/obj/item/projectile/Proj) taken care of in living + +/mob/living/carbon/alien/humanoid/emp_act(severity) + if(r_store) r_store.emp_act(severity) + if(l_store) l_store.emp_act(severity) + ..() + +/mob/living/carbon/alien/humanoid/ex_act(severity) + ..() + + var/shielded = 0 + + var/b_loss = null + var/f_loss = null + switch(severity) + if(1.0) + gib() + return + + if(2.0) + if(!shielded) + b_loss += 60 + + f_loss += 60 + + AdjustEarDamage(30, 120) + if(3.0) + b_loss += 30 + if(prob(50) && !shielded) + Paralyse(1) + AdjustEarDamage(15, 60) + + take_overall_damage(b_loss, f_loss) + +/mob/living/carbon/alien/humanoid/restrained() + if(handcuffed) + return 1 + return 0 + + +/mob/living/carbon/alien/humanoid/var/temperature_resistance = T0C+75 + +/mob/living/carbon/alien/humanoid/show_inv(mob/user as mob) + user.set_machine(src) + + var/dat = {" + + + "} + + dat += "" + + dat += "" + + dat += "" + dat += "
        Left Hand:[(l_hand && !(l_hand.flags&ABSTRACT)) ? l_hand : "Empty"]
        Right Hand:[(r_hand && !(r_hand.flags&ABSTRACT)) ? r_hand : "Empty"]
         
        Head:[(head && !(head.flags&ABSTRACT)) ? head : "Empty"]
         
        Exosuit:[(wear_suit && !(wear_suit.flags&ABSTRACT)) ? wear_suit : "Empty"]
        Pouches:[((l_store && !(l_store.flags&ABSTRACT)) || (r_store && !(r_store.flags&ABSTRACT))) ? "Full" : "Empty"]" + + dat += {"
        + Close + "} + + var/datum/browser/popup = new(user, "mob\ref[src]", "[src]", 440, 500) + popup.set_content(dat) + popup.open() + +/mob/living/carbon/alien/humanoid/canBeHandcuffed() + return 1 + +/mob/living/carbon/alien/humanoid/cuff_resist(obj/item/I) + playsound(src, 'sound/voice/hiss5.ogg', 40, 1, 1) //Alien roars when starting to break free + ..(I, cuff_break = 1) + +/mob/living/carbon/alien/humanoid/get_standard_pixel_y_offset(lying = 0) + if(leaping) + return -32 + else if(custom_pixel_y_offset) + return custom_pixel_y_offset + else + return initial(pixel_y) + +/mob/living/carbon/alien/humanoid/get_standard_pixel_x_offset(lying = 0) + if(leaping) + return -32 + else if(custom_pixel_x_offset) + return custom_pixel_x_offset + else + return initial(pixel_x) + +/mob/living/carbon/alien/humanoid/get_permeability_protection() + return 0.8 diff --git a/code/modules/mob/living/carbon/alien/humanoid/inventory.dm b/code/modules/mob/living/carbon/alien/humanoid/inventory.dm index 08017da0b76..e015cc7c9a6 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/inventory.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/inventory.dm @@ -1,47 +1,47 @@ -//unequip -/mob/living/carbon/alien/humanoid/unEquip(var/obj/item/I, var/force) - . = ..(I, force) - if(!. || !I) - return - - if(I == r_store) - r_store = null - update_inv_pockets() - - else if(I == l_store) - l_store = null - update_inv_pockets() - -/mob/living/carbon/alien/humanoid/attack_ui(slot_id) - var/obj/item/W = get_active_hand() - if(W) - if(!istype(W)) return - switch(slot_id) -// if("o_clothing") -// if("head") - if(slot_l_store) - if(l_store) - return - if(W.w_class > WEIGHT_CLASS_NORMAL) - return - unEquip(W) - l_store = W - update_inv_pockets() - if(slot_r_store) - if(r_store) - return - if(W.w_class > WEIGHT_CLASS_NORMAL) - return - unEquip(W) - r_store = W - update_inv_pockets() - else - switch(slot_id) - if(slot_wear_suit) - if(wear_suit) wear_suit.attack_alien(src) - if(slot_head) - if(head) head.attack_alien(src) - if(slot_l_store) - if(l_store) l_store.attack_alien(src) - if(slot_r_store) - if(r_store) r_store.attack_alien(src) +//unequip +/mob/living/carbon/alien/humanoid/unEquip(var/obj/item/I, var/force) + . = ..(I, force) + if(!. || !I) + return + + if(I == r_store) + r_store = null + update_inv_pockets() + + else if(I == l_store) + l_store = null + update_inv_pockets() + +/mob/living/carbon/alien/humanoid/attack_ui(slot_id) + var/obj/item/W = get_active_hand() + if(W) + if(!istype(W)) return + switch(slot_id) +// if("o_clothing") +// if("head") + if(slot_l_store) + if(l_store) + return + if(W.w_class > WEIGHT_CLASS_NORMAL) + return + unEquip(W) + l_store = W + update_inv_pockets() + if(slot_r_store) + if(r_store) + return + if(W.w_class > WEIGHT_CLASS_NORMAL) + return + unEquip(W) + r_store = W + update_inv_pockets() + else + switch(slot_id) + if(slot_wear_suit) + if(wear_suit) wear_suit.attack_alien(src) + if(slot_head) + if(head) head.attack_alien(src) + if(slot_l_store) + if(l_store) l_store.attack_alien(src) + if(slot_r_store) + if(r_store) r_store.attack_alien(src) diff --git a/code/modules/mob/living/carbon/alien/humanoid/life.dm b/code/modules/mob/living/carbon/alien/humanoid/life.dm index 3af2250bbbd..646f069970b 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/life.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/life.dm @@ -1,89 +1,89 @@ -/mob/living/carbon/alien/humanoid/Life(seconds, times_fired) - . = ..() - update_icons() - - - -/mob/living/carbon/alien/humanoid/handle_disabilities() - if(disabilities & EPILEPSY) - if((prob(1) && paralysis < 10)) - to_chat(src, "You have a seizure!") - Paralyse(10) - if(disabilities & COUGHING) - if((prob(5) && paralysis <= 1)) - drop_item() - emote("cough") - return - if(disabilities & TOURETTES) - if((prob(10) && paralysis <= 1)) - Stun(10) - emote("twitch") - return - if(disabilities & NERVOUS) - if(prob(10)) - stuttering = max(10, stuttering) - -/mob/living/carbon/alien/humanoid/proc/adjust_body_temperature(current, loc_temp, boost) - var/temperature = current - var/difference = abs(current-loc_temp) //get difference - var/increments// = difference/10 //find how many increments apart they are - if(difference > 50) - increments = difference/5 - else - increments = difference/10 - var/change = increments*boost // Get the amount to change by (x per increment) - var/temp_change - if(current < loc_temp) - temperature = min(loc_temp, temperature+change) - else if(current > loc_temp) - temperature = max(loc_temp, temperature-change) - temp_change = (temperature - current) - return temp_change - -/mob/living/carbon/alien/humanoid/handle_regular_status_updates() - updatehealth() - - if(stat == DEAD) //DEAD. BROWN BREAD. SWIMMING WITH THE SPESS CARP - SetSilence(0) - else //ALIVE. LIGHTS ARE ON - if(health < HEALTH_THRESHOLD_DEAD && check_death_method() || !get_int_organ(/obj/item/organ/internal/brain)) - death() - SetSilence(0) - return 1 - - //UNCONSCIOUS. NO-ONE IS HOME - if((getOxyLoss() > 50) || (HEALTH_THRESHOLD_CRIT >= health && check_death_method())) - if(health <= 20 && prob(1)) - emote("gasp") - if(!reagents.has_reagent("epinephrine")) - adjustOxyLoss(1) - Paralyse(3) - - if(paralysis) - stat = UNCONSCIOUS - else if(sleeping) - stat = UNCONSCIOUS - if(prob(10) && health) - emote("hiss") - //CONSCIOUS - else - stat = CONSCIOUS - - /* What in the living hell is this?*/ - if(move_delay_add > 0) - move_delay_add = max(0, move_delay_add - rand(1, 2)) - - if(eye_blind) //blindness, heals slowly over time - AdjustEyeBlind(-1) - else if(eye_blurry) //blurry eyes heal slowly - AdjustEyeBlurry(-1) - - if(stuttering) - AdjustStuttering(-1) - - if(silent) - AdjustSilence(-1) - - if(druggy) - AdjustDruggy(-1) - return 1 +/mob/living/carbon/alien/humanoid/Life(seconds, times_fired) + . = ..() + update_icons() + + + +/mob/living/carbon/alien/humanoid/handle_disabilities() + if(disabilities & EPILEPSY) + if((prob(1) && paralysis < 10)) + to_chat(src, "You have a seizure!") + Paralyse(10) + if(disabilities & COUGHING) + if((prob(5) && paralysis <= 1)) + drop_item() + emote("cough") + return + if(disabilities & TOURETTES) + if((prob(10) && paralysis <= 1)) + Stun(10) + emote("twitch") + return + if(disabilities & NERVOUS) + if(prob(10)) + stuttering = max(10, stuttering) + +/mob/living/carbon/alien/humanoid/proc/adjust_body_temperature(current, loc_temp, boost) + var/temperature = current + var/difference = abs(current-loc_temp) //get difference + var/increments// = difference/10 //find how many increments apart they are + if(difference > 50) + increments = difference/5 + else + increments = difference/10 + var/change = increments*boost // Get the amount to change by (x per increment) + var/temp_change + if(current < loc_temp) + temperature = min(loc_temp, temperature+change) + else if(current > loc_temp) + temperature = max(loc_temp, temperature-change) + temp_change = (temperature - current) + return temp_change + +/mob/living/carbon/alien/humanoid/handle_regular_status_updates() + updatehealth() + + if(stat == DEAD) //DEAD. BROWN BREAD. SWIMMING WITH THE SPESS CARP + SetSilence(0) + else //ALIVE. LIGHTS ARE ON + if(health < HEALTH_THRESHOLD_DEAD && check_death_method() || !get_int_organ(/obj/item/organ/internal/brain)) + death() + SetSilence(0) + return 1 + + //UNCONSCIOUS. NO-ONE IS HOME + if((getOxyLoss() > 50) || (HEALTH_THRESHOLD_CRIT >= health && check_death_method())) + if(health <= 20 && prob(1)) + emote("gasp") + if(!reagents.has_reagent("epinephrine")) + adjustOxyLoss(1) + Paralyse(3) + + if(paralysis) + stat = UNCONSCIOUS + else if(sleeping) + stat = UNCONSCIOUS + if(prob(10) && health) + emote("hiss") + //CONSCIOUS + else + stat = CONSCIOUS + + /* What in the living hell is this?*/ + if(move_delay_add > 0) + move_delay_add = max(0, move_delay_add - rand(1, 2)) + + if(eye_blind) //blindness, heals slowly over time + AdjustEyeBlind(-1) + else if(eye_blurry) //blurry eyes heal slowly + AdjustEyeBlurry(-1) + + if(stuttering) + AdjustStuttering(-1) + + if(silent) + AdjustSilence(-1) + + if(druggy) + AdjustDruggy(-1) + return 1 diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index 0132bcdf88b..13eb62587be 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -1,95 +1,95 @@ -/mob/living/carbon/alien/humanoid/queen - name = "alien queen" - caste = "q" - maxHealth = 250 - health = 250 - icon_state = "alienq_s" - status_flags = CANPARALYSE - heal_rate = 5 - large = 1 - ventcrawler = 0 - pressure_resistance = 200 //Because big, stompy xenos should not be blown around like paper. - -/mob/living/carbon/alien/humanoid/queen/New() - create_reagents(100) - - //there should only be one queen - for(var/mob/living/carbon/alien/humanoid/queen/Q in GLOB.living_mob_list) - if(Q == src) continue - if(Q.stat == DEAD) continue - if(Q.client) - name = "alien princess ([rand(1, 999)])" //if this is too cutesy feel free to change it/remove it. - break - - real_name = src.name - alien_organs += new /obj/item/organ/internal/xenos/plasmavessel/queen - alien_organs += new /obj/item/organ/internal/xenos/acidgland - alien_organs += new /obj/item/organ/internal/xenos/eggsac - alien_organs += new /obj/item/organ/internal/xenos/resinspinner - alien_organs += new /obj/item/organ/internal/xenos/neurotoxin - ..() - -/mob/living/carbon/alien/humanoid/queen/movement_delay() - . = ..() - . += 3 - -/mob/living/carbon/alien/humanoid/queen/handle_hud_icons_health() - ..() //-Yvarov - - if(healths) - if(stat != DEAD) - switch(health) - if(250 to INFINITY) - healths.icon_state = "health0" - if(175 to 250) - healths.icon_state = "health1" - if(100 to 175) - healths.icon_state = "health2" - if(50 to 100) - healths.icon_state = "health3" - if(0 to 50) - healths.icon_state = "health4" - else - healths.icon_state = "health5" - else - healths.icon_state = "health6" - -/mob/living/carbon/alien/humanoid/queen/can_inject() - return FALSE - -//Queen verbs -/mob/living/carbon/alien/humanoid/queen/verb/lay_egg() - - set name = "Lay Egg (75)" - set desc = "Lay an egg to produce huggers to impregnate prey with." - set category = "Alien" - if(locate(/obj/structure/alien/egg) in get_turf(src)) - to_chat(src, "There's already an egg here.") - return - - if(powerc(75,1))//Can't plant eggs on spess tiles. That's silly. - adjustPlasma(-75) - for(var/mob/O in viewers(src, null)) - O.show_message(text("[src] has laid an egg!"), 1) - new /obj/structure/alien/egg(loc) - return - - -/mob/living/carbon/alien/humanoid/queen/large - icon = 'icons/mob/alienlarge.dmi' - icon_state = "queen_s" - pixel_x = -16 - large = 1 - -/mob/living/carbon/alien/humanoid/queen/large/update_icons() - overlays.Cut() - - if(stat == DEAD) - icon_state = "queen_dead" - else if(stat == UNCONSCIOUS || lying || resting) - icon_state = "queen_sleep" - else - icon_state = "queen_s" - - for(var/image/I in overlays_standing) - overlays += I \ No newline at end of file +/mob/living/carbon/alien/humanoid/queen + name = "alien queen" + caste = "q" + maxHealth = 250 + health = 250 + icon_state = "alienq_s" + status_flags = CANPARALYSE + heal_rate = 5 + large = 1 + ventcrawler = 0 + pressure_resistance = 200 //Because big, stompy xenos should not be blown around like paper. + +/mob/living/carbon/alien/humanoid/queen/New() + create_reagents(100) + + //there should only be one queen + for(var/mob/living/carbon/alien/humanoid/queen/Q in GLOB.living_mob_list) + if(Q == src) continue + if(Q.stat == DEAD) continue + if(Q.client) + name = "alien princess ([rand(1, 999)])" //if this is too cutesy feel free to change it/remove it. + break + + real_name = src.name + alien_organs += new /obj/item/organ/internal/xenos/plasmavessel/queen + alien_organs += new /obj/item/organ/internal/xenos/acidgland + alien_organs += new /obj/item/organ/internal/xenos/eggsac + alien_organs += new /obj/item/organ/internal/xenos/resinspinner + alien_organs += new /obj/item/organ/internal/xenos/neurotoxin + ..() + +/mob/living/carbon/alien/humanoid/queen/movement_delay() + . = ..() + . += 3 + +/mob/living/carbon/alien/humanoid/queen/handle_hud_icons_health() + ..() //-Yvarov + + if(healths) + if(stat != DEAD) + switch(health) + if(250 to INFINITY) + healths.icon_state = "health0" + if(175 to 250) + healths.icon_state = "health1" + if(100 to 175) + healths.icon_state = "health2" + if(50 to 100) + healths.icon_state = "health3" + if(0 to 50) + healths.icon_state = "health4" + else + healths.icon_state = "health5" + else + healths.icon_state = "health6" + +/mob/living/carbon/alien/humanoid/queen/can_inject() + return FALSE + +//Queen verbs +/mob/living/carbon/alien/humanoid/queen/verb/lay_egg() + + set name = "Lay Egg (75)" + set desc = "Lay an egg to produce huggers to impregnate prey with." + set category = "Alien" + if(locate(/obj/structure/alien/egg) in get_turf(src)) + to_chat(src, "There's already an egg here.") + return + + if(powerc(75,1))//Can't plant eggs on spess tiles. That's silly. + adjustPlasma(-75) + for(var/mob/O in viewers(src, null)) + O.show_message(text("[src] has laid an egg!"), 1) + new /obj/structure/alien/egg(loc) + return + + +/mob/living/carbon/alien/humanoid/queen/large + icon = 'icons/mob/alienlarge.dmi' + icon_state = "queen_s" + pixel_x = -16 + large = 1 + +/mob/living/carbon/alien/humanoid/queen/large/update_icons() + overlays.Cut() + + if(stat == DEAD) + icon_state = "queen_dead" + else if(stat == UNCONSCIOUS || lying || resting) + icon_state = "queen_sleep" + else + icon_state = "queen_s" + + for(var/image/I in overlays_standing) + overlays += I diff --git a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm index 7f17a8a87e1..4b7845b0a0a 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm @@ -1,166 +1,166 @@ -//Xeno Overlays Indexes////////// -#define X_HEAD_LAYER 1 -#define X_SUIT_LAYER 2 -#define X_L_HAND_LAYER 3 -#define X_R_HAND_LAYER 4 -#define X_TARGETED_LAYER 5 -#define X_FIRE_LAYER 6 -#define X_TOTAL_LAYERS 6 -///////////////////////////////// - -/mob/living/carbon/alien/humanoid - var/list/overlays_standing[X_TOTAL_LAYERS] - -/mob/living/carbon/alien/humanoid/update_icons() - overlays.Cut() - for(var/image/I in overlays_standing) - overlays += I - - if(stat == DEAD) - //If we mostly took damage from fire - if(getFireLoss() > 125) - icon_state = "alien[caste]_husked" - pixel_y = 0 - else - icon_state = "alien[caste]_dead" - pixel_y = 0 - - else if(stat == UNCONSCIOUS || IsWeakened()) - icon_state = "alien[caste]_unconscious" - pixel_y = 0 - else if(leap_on_click) - icon_state = "alien[caste]_pounce" - - else if(lying || resting) - icon_state = "alien[caste]_sleep" - else if(m_intent == MOVE_INTENT_RUN) - icon_state = "alien[caste]_running" - else - icon_state = "alien[caste]_s" - - if(leaping) - if(alt_icon == initial(alt_icon)) - var/old_icon = icon - icon = alt_icon - alt_icon = old_icon - icon_state = "alien[caste]_leap" - pixel_x = -32 - pixel_y = -32 - else - if(alt_icon != initial(alt_icon)) - var/old_icon = icon - icon = alt_icon - alt_icon = old_icon - pixel_x = get_standard_pixel_x_offset(lying) - pixel_y = get_standard_pixel_y_offset(lying) - -/mob/living/carbon/alien/humanoid/regenerate_icons() - ..() - if(notransform) return - - update_inv_head(0,0) - update_inv_wear_suit(0,0) - update_inv_r_hand(0) - update_inv_l_hand(0) - update_inv_pockets(0) - update_icons() - update_fire() - update_transform() - -/mob/living/carbon/alien/humanoid/update_transform() //The old method of updating lying/standing was update_icons(). Aliens still expect that. - if(lying > 0) - lying = 90 //Anything else looks retarded - ..() - update_icons() - -/mob/living/carbon/alien/humanoid/update_fire() - overlays -= overlays_standing[X_FIRE_LAYER] - if(on_fire) - overlays_standing[X_FIRE_LAYER] = image("icon"='icons/mob/OnFire.dmi', "icon_state"="Generic_mob_burning", "layer"= -X_FIRE_LAYER) - overlays += overlays_standing[X_FIRE_LAYER] - return - else - overlays_standing[X_FIRE_LAYER] = null - -/mob/living/carbon/alien/humanoid/update_inv_wear_suit(var/update_icons=1) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_suit] - inv.update_icon() - - if(wear_suit) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - wear_suit.screen_loc = ui_oclothing //TODO //...draw the item in the inventory screen - client.screen += wear_suit //Either way, add the item to the HUD - - var/t_state = wear_suit.item_state - if(!t_state) t_state = wear_suit.icon_state - var/image/standing = image("icon" = 'icons/mob/mob.dmi', "icon_state" = "[t_state]") - - if(wear_suit.blood_DNA) - var/t_suit = "suit" - if( istype(wear_suit, /obj/item/clothing/suit/armor) ) - t_suit = "armor" - standing.overlays += image("icon" = 'icons/effects/blood.dmi', "icon_state" = "[t_suit]blood") - - if(wear_suit.breakouttime) - drop_r_hand() - drop_l_hand() - - overlays_standing[X_SUIT_LAYER] = standing - else - overlays_standing[X_SUIT_LAYER] = null - if(update_icons) update_icons() - - -/mob/living/carbon/alien/humanoid/update_inv_head(var/update_icons=1) - if(head) - var/t_state = head.item_state - if(!t_state) t_state = head.icon_state - var/image/standing = image("icon" = 'icons/mob/mob.dmi', "icon_state" = "[t_state]") - if(head.blood_DNA) - standing.overlays += image("icon" = 'icons/effects/blood.dmi', "icon_state" = "helmetblood") - head.screen_loc = ui_alien_head - overlays_standing[X_HEAD_LAYER] = standing - else - overlays_standing[X_HEAD_LAYER] = null - if(update_icons) update_icons() - - -/mob/living/carbon/alien/humanoid/update_inv_pockets(var/update_icons=1) - if(l_store) l_store.screen_loc = ui_storage1 - if(r_store) r_store.screen_loc = ui_storage2 - if(update_icons) update_icons() - - -/mob/living/carbon/alien/humanoid/update_inv_r_hand(var/update_icons=1) - ..(1) - if(r_hand) - var/t_state = r_hand.item_state - if(!t_state) t_state = r_hand.icon_state - r_hand.screen_loc = ui_rhand - overlays_standing[X_R_HAND_LAYER] = image("icon" = r_hand.righthand_file, "icon_state" = t_state) - else - overlays_standing[X_R_HAND_LAYER] = null - if(update_icons) update_icons() - -/mob/living/carbon/alien/humanoid/update_inv_l_hand(var/update_icons=1) - ..(1) - if(l_hand) - var/t_state = l_hand.item_state - if(!t_state) t_state = l_hand.icon_state - l_hand.screen_loc = ui_lhand - overlays_standing[X_L_HAND_LAYER] = image("icon" = l_hand.lefthand_file, "icon_state" = t_state) - else - overlays_standing[X_L_HAND_LAYER] = null - if(update_icons) update_icons() - - -//Xeno Overlays Indexes////////// -#undef X_HEAD_LAYER -#undef X_SUIT_LAYER -#undef X_L_HAND_LAYER -#undef X_R_HAND_LAYER -#undef X_TARGETED_LAYER -#undef X_FIRE_LAYER -#undef X_TOTAL_LAYERS +//Xeno Overlays Indexes////////// +#define X_HEAD_LAYER 1 +#define X_SUIT_LAYER 2 +#define X_L_HAND_LAYER 3 +#define X_R_HAND_LAYER 4 +#define X_TARGETED_LAYER 5 +#define X_FIRE_LAYER 6 +#define X_TOTAL_LAYERS 6 +///////////////////////////////// + +/mob/living/carbon/alien/humanoid + var/list/overlays_standing[X_TOTAL_LAYERS] + +/mob/living/carbon/alien/humanoid/update_icons() + overlays.Cut() + for(var/image/I in overlays_standing) + overlays += I + + if(stat == DEAD) + //If we mostly took damage from fire + if(getFireLoss() > 125) + icon_state = "alien[caste]_husked" + pixel_y = 0 + else + icon_state = "alien[caste]_dead" + pixel_y = 0 + + else if(stat == UNCONSCIOUS || IsWeakened()) + icon_state = "alien[caste]_unconscious" + pixel_y = 0 + else if(leap_on_click) + icon_state = "alien[caste]_pounce" + + else if(lying || resting) + icon_state = "alien[caste]_sleep" + else if(m_intent == MOVE_INTENT_RUN) + icon_state = "alien[caste]_running" + else + icon_state = "alien[caste]_s" + + if(leaping) + if(alt_icon == initial(alt_icon)) + var/old_icon = icon + icon = alt_icon + alt_icon = old_icon + icon_state = "alien[caste]_leap" + pixel_x = -32 + pixel_y = -32 + else + if(alt_icon != initial(alt_icon)) + var/old_icon = icon + icon = alt_icon + alt_icon = old_icon + pixel_x = get_standard_pixel_x_offset(lying) + pixel_y = get_standard_pixel_y_offset(lying) + +/mob/living/carbon/alien/humanoid/regenerate_icons() + ..() + if(notransform) return + + update_inv_head(0,0) + update_inv_wear_suit(0,0) + update_inv_r_hand(0) + update_inv_l_hand(0) + update_inv_pockets(0) + update_icons() + update_fire() + update_transform() + +/mob/living/carbon/alien/humanoid/update_transform() //The old method of updating lying/standing was update_icons(). Aliens still expect that. + if(lying > 0) + lying = 90 //Anything else looks retarded + ..() + update_icons() + +/mob/living/carbon/alien/humanoid/update_fire() + overlays -= overlays_standing[X_FIRE_LAYER] + if(on_fire) + overlays_standing[X_FIRE_LAYER] = image("icon"='icons/mob/OnFire.dmi', "icon_state"="Generic_mob_burning", "layer"= -X_FIRE_LAYER) + overlays += overlays_standing[X_FIRE_LAYER] + return + else + overlays_standing[X_FIRE_LAYER] = null + +/mob/living/carbon/alien/humanoid/update_inv_wear_suit(var/update_icons=1) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_suit] + inv.update_icon() + + if(wear_suit) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + wear_suit.screen_loc = ui_oclothing //TODO //...draw the item in the inventory screen + client.screen += wear_suit //Either way, add the item to the HUD + + var/t_state = wear_suit.item_state + if(!t_state) t_state = wear_suit.icon_state + var/image/standing = image("icon" = 'icons/mob/mob.dmi', "icon_state" = "[t_state]") + + if(wear_suit.blood_DNA) + var/t_suit = "suit" + if( istype(wear_suit, /obj/item/clothing/suit/armor) ) + t_suit = "armor" + standing.overlays += image("icon" = 'icons/effects/blood.dmi', "icon_state" = "[t_suit]blood") + + if(wear_suit.breakouttime) + drop_r_hand() + drop_l_hand() + + overlays_standing[X_SUIT_LAYER] = standing + else + overlays_standing[X_SUIT_LAYER] = null + if(update_icons) update_icons() + + +/mob/living/carbon/alien/humanoid/update_inv_head(var/update_icons=1) + if(head) + var/t_state = head.item_state + if(!t_state) t_state = head.icon_state + var/image/standing = image("icon" = 'icons/mob/mob.dmi', "icon_state" = "[t_state]") + if(head.blood_DNA) + standing.overlays += image("icon" = 'icons/effects/blood.dmi', "icon_state" = "helmetblood") + head.screen_loc = ui_alien_head + overlays_standing[X_HEAD_LAYER] = standing + else + overlays_standing[X_HEAD_LAYER] = null + if(update_icons) update_icons() + + +/mob/living/carbon/alien/humanoid/update_inv_pockets(var/update_icons=1) + if(l_store) l_store.screen_loc = ui_storage1 + if(r_store) r_store.screen_loc = ui_storage2 + if(update_icons) update_icons() + + +/mob/living/carbon/alien/humanoid/update_inv_r_hand(var/update_icons=1) + ..(1) + if(r_hand) + var/t_state = r_hand.item_state + if(!t_state) t_state = r_hand.icon_state + r_hand.screen_loc = ui_rhand + overlays_standing[X_R_HAND_LAYER] = image("icon" = r_hand.righthand_file, "icon_state" = t_state) + else + overlays_standing[X_R_HAND_LAYER] = null + if(update_icons) update_icons() + +/mob/living/carbon/alien/humanoid/update_inv_l_hand(var/update_icons=1) + ..(1) + if(l_hand) + var/t_state = l_hand.item_state + if(!t_state) t_state = l_hand.icon_state + l_hand.screen_loc = ui_lhand + overlays_standing[X_L_HAND_LAYER] = image("icon" = l_hand.lefthand_file, "icon_state" = t_state) + else + overlays_standing[X_L_HAND_LAYER] = null + if(update_icons) update_icons() + + +//Xeno Overlays Indexes////////// +#undef X_HEAD_LAYER +#undef X_SUIT_LAYER +#undef X_L_HAND_LAYER +#undef X_R_HAND_LAYER +#undef X_TARGETED_LAYER +#undef X_FIRE_LAYER +#undef X_TOTAL_LAYERS diff --git a/code/modules/mob/living/carbon/alien/larva/emote.dm b/code/modules/mob/living/carbon/alien/larva/emote.dm index 3b18889176b..2a910428d31 100644 --- a/code/modules/mob/living/carbon/alien/larva/emote.dm +++ b/code/modules/mob/living/carbon/alien/larva/emote.dm @@ -123,4 +123,4 @@ for(var/mob/O in hearers(src, null)) O.show_message(message, m_type) //Foreach goto(746) - return \ No newline at end of file + return diff --git a/code/modules/mob/living/carbon/alien/larva/inventory.dm b/code/modules/mob/living/carbon/alien/larva/inventory.dm index 0119add7d71..3ccc62cee6c 100644 --- a/code/modules/mob/living/carbon/alien/larva/inventory.dm +++ b/code/modules/mob/living/carbon/alien/larva/inventory.dm @@ -1,3 +1,3 @@ -//can't unequip since it can't equip anything -/mob/living/carbon/alien/larva/unEquip(obj/item/W as obj, force) - return \ No newline at end of file +//can't unequip since it can't equip anything +/mob/living/carbon/alien/larva/unEquip(obj/item/W as obj, force) + return diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index 1ba5e4530ff..a55126ec045 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -1,96 +1,96 @@ -/mob/living/carbon/alien/larva - name = "alien larva" - real_name = "alien larva" - icon_state = "larva0" - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - - maxHealth = 30 - health = 30 - density = 0 - - var/amount_grown = 0 - var/max_grown = 200 - var/time_of_birth - death_message = "lets out a waning high-pitched cry." - death_sound = null - -//This is fine right now, if we're adding organ specific damage this needs to be updated -/mob/living/carbon/alien/larva/New() - create_reagents(100) - if(name == "alien larva") - name = "alien larva ([rand(1, 1000)])" - real_name = name - regenerate_icons() - add_language("Xenomorph") - add_language("Hivemind") - alien_organs += new /obj/item/organ/internal/xenos/plasmavessel/larva - - ..() - -//This needs to be fixed -/mob/living/carbon/alien/larva/Stat() - ..() - stat(null, "Progress: [amount_grown]/[max_grown]") - -/mob/living/carbon/alien/larva/adjustPlasma(amount) - if(stat != DEAD && amount > 0) - amount_grown = min(amount_grown + 1, max_grown) - ..(amount) - -/mob/living/carbon/alien/larva/ex_act(severity) - ..() - - var/b_loss = null - var/f_loss = null - switch(severity) - if(1.0) - gib() - return - - if(2.0) - - b_loss += 60 - - f_loss += 60 - - AdjustEarDamage(30, 120) - - if(3.0) - b_loss += 30 - if(prob(50)) - Paralyse(1) - AdjustEarDamage(15, 60) - - adjustBruteLoss(b_loss) - adjustFireLoss(f_loss) - - updatehealth() - -//can't equip anything -/mob/living/carbon/alien/larva/attack_ui(slot_id) - return - -/mob/living/carbon/alien/larva/restrained() - return 0 - -/mob/living/carbon/alien/larva/var/temperature_resistance = T0C+75 - -// new damage icon system -// now constructs damage icon for each organ from mask * damage field - - -/mob/living/carbon/alien/larva/show_inv(mob/user as mob) - return - -/mob/living/carbon/alien/larva/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) - return FALSE - -/* Commented out because it's duplicated in life.dm -/mob/living/carbon/alien/larva/proc/grow() // Larvae can grow into full fledged Xenos if they survive long enough -- TLE - if(icon_state == "larva_l" && !canmove) // This is a shit death check. It is made of shit and death. Fix later. - return - else - var/mob/living/carbon/alien/humanoid/A = new(loc) - A.key = key - qdel(src) */ +/mob/living/carbon/alien/larva + name = "alien larva" + real_name = "alien larva" + icon_state = "larva0" + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + + maxHealth = 30 + health = 30 + density = 0 + + var/amount_grown = 0 + var/max_grown = 200 + var/time_of_birth + death_message = "lets out a waning high-pitched cry." + death_sound = null + +//This is fine right now, if we're adding organ specific damage this needs to be updated +/mob/living/carbon/alien/larva/New() + create_reagents(100) + if(name == "alien larva") + name = "alien larva ([rand(1, 1000)])" + real_name = name + regenerate_icons() + add_language("Xenomorph") + add_language("Hivemind") + alien_organs += new /obj/item/organ/internal/xenos/plasmavessel/larva + + ..() + +//This needs to be fixed +/mob/living/carbon/alien/larva/Stat() + ..() + stat(null, "Progress: [amount_grown]/[max_grown]") + +/mob/living/carbon/alien/larva/adjustPlasma(amount) + if(stat != DEAD && amount > 0) + amount_grown = min(amount_grown + 1, max_grown) + ..(amount) + +/mob/living/carbon/alien/larva/ex_act(severity) + ..() + + var/b_loss = null + var/f_loss = null + switch(severity) + if(1.0) + gib() + return + + if(2.0) + + b_loss += 60 + + f_loss += 60 + + AdjustEarDamage(30, 120) + + if(3.0) + b_loss += 30 + if(prob(50)) + Paralyse(1) + AdjustEarDamage(15, 60) + + adjustBruteLoss(b_loss) + adjustFireLoss(f_loss) + + updatehealth() + +//can't equip anything +/mob/living/carbon/alien/larva/attack_ui(slot_id) + return + +/mob/living/carbon/alien/larva/restrained() + return 0 + +/mob/living/carbon/alien/larva/var/temperature_resistance = T0C+75 + +// new damage icon system +// now constructs damage icon for each organ from mask * damage field + + +/mob/living/carbon/alien/larva/show_inv(mob/user as mob) + return + +/mob/living/carbon/alien/larva/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) + return FALSE + +/* Commented out because it's duplicated in life.dm +/mob/living/carbon/alien/larva/proc/grow() // Larvae can grow into full fledged Xenos if they survive long enough -- TLE + if(icon_state == "larva_l" && !canmove) // This is a shit death check. It is made of shit and death. Fix later. + return + else + var/mob/living/carbon/alien/humanoid/A = new(loc) + A.key = key + qdel(src) */ diff --git a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm index 79c8ed8207f..69e12b6ae19 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm @@ -34,4 +34,4 @@ /mob/living/carbon/alien/larva/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect, end_pixel_y) if(!no_effect && !visual_effect_icon) visual_effect_icon = ATTACK_EFFECT_BITE - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/carbon/alien/larva/life.dm b/code/modules/mob/living/carbon/alien/larva/life.dm index a0668db8327..fd1543673e6 100644 --- a/code/modules/mob/living/carbon/alien/larva/life.dm +++ b/code/modules/mob/living/carbon/alien/larva/life.dm @@ -1,57 +1,57 @@ -/mob/living/carbon/alien/larva/Life(seconds, times_fired) - if(..()) //still breathing - // GROW! - if(amount_grown < max_grown) - amount_grown++ - - //some kind of bug in canmove() isn't properly calling update_icons, so this is here as a placeholder - update_icons() - -/mob/living/carbon/alien/larva/handle_regular_status_updates() - updatehealth() - - if(stat == DEAD) //DEAD. BROWN BREAD. SWIMMING WITH THE SPESS CARP - SetSilence(0) - else //ALIVE. LIGHTS ARE ON - if(health < -25 || !get_int_organ(/obj/item/organ/internal/brain)) - death() - SetSilence(0) - return 1 - - //UNCONSCIOUS. NO-ONE IS HOME - if((getOxyLoss() > 25) || (HEALTH_THRESHOLD_CRIT >= health && check_death_method())) - //if( health <= 20 && prob(1) ) - // spawn(0) - // emote("gasp") - if(!reagents.has_reagent("epinephrine")) - adjustOxyLoss(1) - Paralyse(3) - - if(paralysis) - stat = UNCONSCIOUS - else if(sleeping) - stat = UNCONSCIOUS - if(prob(10) && health) - emote("hiss_") - //CONSCIOUS - else - stat = CONSCIOUS - - /* What in the living hell is this?*/ - if(move_delay_add > 0) - move_delay_add = max(0, move_delay_add - rand(1, 2)) - - if(eye_blind) //blindness, heals slowly over time - AdjustEyeBlind(-1) - else if(eye_blurry) //blurry eyes heal slowly - AdjustEyeBlurry(-1) - - if(stuttering) - AdjustStuttering(-1) - - if(silent) - AdjustSilence(-1) - - if(druggy) - AdjustDruggy(-1) - return 1 +/mob/living/carbon/alien/larva/Life(seconds, times_fired) + if(..()) //still breathing + // GROW! + if(amount_grown < max_grown) + amount_grown++ + + //some kind of bug in canmove() isn't properly calling update_icons, so this is here as a placeholder + update_icons() + +/mob/living/carbon/alien/larva/handle_regular_status_updates() + updatehealth() + + if(stat == DEAD) //DEAD. BROWN BREAD. SWIMMING WITH THE SPESS CARP + SetSilence(0) + else //ALIVE. LIGHTS ARE ON + if(health < -25 || !get_int_organ(/obj/item/organ/internal/brain)) + death() + SetSilence(0) + return 1 + + //UNCONSCIOUS. NO-ONE IS HOME + if((getOxyLoss() > 25) || (HEALTH_THRESHOLD_CRIT >= health && check_death_method())) + //if( health <= 20 && prob(1) ) + // spawn(0) + // emote("gasp") + if(!reagents.has_reagent("epinephrine")) + adjustOxyLoss(1) + Paralyse(3) + + if(paralysis) + stat = UNCONSCIOUS + else if(sleeping) + stat = UNCONSCIOUS + if(prob(10) && health) + emote("hiss_") + //CONSCIOUS + else + stat = CONSCIOUS + + /* What in the living hell is this?*/ + if(move_delay_add > 0) + move_delay_add = max(0, move_delay_add - rand(1, 2)) + + if(eye_blind) //blindness, heals slowly over time + AdjustEyeBlind(-1) + else if(eye_blurry) //blurry eyes heal slowly + AdjustEyeBlurry(-1) + + if(stuttering) + AdjustStuttering(-1) + + if(silent) + AdjustSilence(-1) + + if(druggy) + AdjustDruggy(-1) + return 1 diff --git a/code/modules/mob/living/carbon/alien/larva/powers.dm b/code/modules/mob/living/carbon/alien/larva/powers.dm index 27837dc7cf5..ca12dbaf0d0 100644 --- a/code/modules/mob/living/carbon/alien/larva/powers.dm +++ b/code/modules/mob/living/carbon/alien/larva/powers.dm @@ -1,54 +1,54 @@ - -/mob/living/carbon/alien/larva/verb/hide() - set name = "Hide" - set desc = "Allows to hide beneath tables or certain items. Toggled on or off." - set category = "Alien" - - if(stat != CONSCIOUS) - return - - if(layer != ABOVE_NORMAL_TURF_LAYER) - layer = ABOVE_NORMAL_TURF_LAYER - visible_message("[src] scurries to the ground!", "You are now hiding.") - else - layer = MOB_LAYER - visible_message("[src] slowly peeks up from the ground...", "You have stopped hiding.") - -/mob/living/carbon/alien/larva/verb/evolve() - set name = "Evolve" - set desc = "Evolve into a fully grown Alien." - set category = "Alien" - - if(stat != CONSCIOUS) - return - - if(handcuffed || legcuffed) - to_chat(src, "You cannot evolve when you are cuffed.") - - if(amount_grown >= max_grown) //TODO ~Carn - //green is impossible to read, so i made these blue and changed the formatting slightly - to_chat(src, "You are growing into a beautiful alien! It is time to choose a caste.") - to_chat(src, "There are three to choose from:") - to_chat(src, "Hunters are strong and agile, able to hunt away from the hive and rapidly move through ventilation shafts. Hunters generate plasma slowly and have low reserves.") - to_chat(src, "Sentinels are tasked with protecting the hive and are deadly up close and at a range. They are not as physically imposing nor fast as the hunters.") - to_chat(src, "Drones are the working class, offering the largest plasma storage and generation. They are the only caste which may evolve again, turning into the dreaded alien queen.") - var/alien_caste = alert(src, "Please choose which alien caste you shall belong to.",,"Hunter","Sentinel","Drone") - - var/mob/living/carbon/alien/humanoid/new_xeno - switch(alien_caste) - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(loc) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(loc) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(loc) - if(mind) - mind.transfer_to(new_xeno) - else - new_xeno.key = key - new_xeno.mind.name = new_xeno.name - qdel(src) - return - else - to_chat(src, "You are not fully grown.") - return + +/mob/living/carbon/alien/larva/verb/hide() + set name = "Hide" + set desc = "Allows to hide beneath tables or certain items. Toggled on or off." + set category = "Alien" + + if(stat != CONSCIOUS) + return + + if(layer != ABOVE_NORMAL_TURF_LAYER) + layer = ABOVE_NORMAL_TURF_LAYER + visible_message("[src] scurries to the ground!", "You are now hiding.") + else + layer = MOB_LAYER + visible_message("[src] slowly peeks up from the ground...", "You have stopped hiding.") + +/mob/living/carbon/alien/larva/verb/evolve() + set name = "Evolve" + set desc = "Evolve into a fully grown Alien." + set category = "Alien" + + if(stat != CONSCIOUS) + return + + if(handcuffed || legcuffed) + to_chat(src, "You cannot evolve when you are cuffed.") + + if(amount_grown >= max_grown) //TODO ~Carn + //green is impossible to read, so i made these blue and changed the formatting slightly + to_chat(src, "You are growing into a beautiful alien! It is time to choose a caste.") + to_chat(src, "There are three to choose from:") + to_chat(src, "Hunters are strong and agile, able to hunt away from the hive and rapidly move through ventilation shafts. Hunters generate plasma slowly and have low reserves.") + to_chat(src, "Sentinels are tasked with protecting the hive and are deadly up close and at a range. They are not as physically imposing nor fast as the hunters.") + to_chat(src, "Drones are the working class, offering the largest plasma storage and generation. They are the only caste which may evolve again, turning into the dreaded alien queen.") + var/alien_caste = alert(src, "Please choose which alien caste you shall belong to.",,"Hunter","Sentinel","Drone") + + var/mob/living/carbon/alien/humanoid/new_xeno + switch(alien_caste) + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(loc) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(loc) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(loc) + if(mind) + mind.transfer_to(new_xeno) + else + new_xeno.key = key + new_xeno.mind.name = new_xeno.name + qdel(src) + return + else + to_chat(src, "You are not fully grown.") + return diff --git a/code/modules/mob/living/carbon/alien/larva/update_icons.dm b/code/modules/mob/living/carbon/alien/larva/update_icons.dm index 697d6413c02..cc3726ab046 100644 --- a/code/modules/mob/living/carbon/alien/larva/update_icons.dm +++ b/code/modules/mob/living/carbon/alien/larva/update_icons.dm @@ -1,25 +1,25 @@ - -/mob/living/carbon/alien/larva/regenerate_icons() - overlays = list() - update_icons() - -/mob/living/carbon/alien/larva/update_icons() - var/state = 0 - if(amount_grown > 150) - state = 2 - else if(amount_grown > 50) - state = 1 - - if(stat == DEAD) - icon_state = "larva[state]_dead" - else if(handcuffed || legcuffed) //This should be an overlay. Who made this an icon_state? - icon_state = "larva[state]_cuff" - else if(stat == UNCONSCIOUS || lying || resting) - icon_state = "larva[state]_sleep" - else if(stunned) - icon_state = "larva[state]_stun" - else - icon_state = "larva[state]" - -/mob/living/carbon/alien/larva/update_transform() //All this is handled in update_icons() - return update_icons() \ No newline at end of file + +/mob/living/carbon/alien/larva/regenerate_icons() + overlays = list() + update_icons() + +/mob/living/carbon/alien/larva/update_icons() + var/state = 0 + if(amount_grown > 150) + state = 2 + else if(amount_grown > 50) + state = 1 + + if(stat == DEAD) + icon_state = "larva[state]_dead" + else if(handcuffed || legcuffed) //This should be an overlay. Who made this an icon_state? + icon_state = "larva[state]_cuff" + else if(stat == UNCONSCIOUS || lying || resting) + icon_state = "larva[state]_sleep" + else if(stunned) + icon_state = "larva[state]_stun" + else + icon_state = "larva[state]" + +/mob/living/carbon/alien/larva/update_transform() //All this is handled in update_icons() + return update_icons() diff --git a/code/modules/mob/living/carbon/alien/login.dm b/code/modules/mob/living/carbon/alien/login.dm index 8d5968848b6..5c177fbb622 100644 --- a/code/modules/mob/living/carbon/alien/login.dm +++ b/code/modules/mob/living/carbon/alien/login.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/alien/Login() - ..() - AddInfectionImages() - return +/mob/living/carbon/alien/Login() + ..() + AddInfectionImages() + return diff --git a/code/modules/mob/living/carbon/alien/logout.dm b/code/modules/mob/living/carbon/alien/logout.dm index a4145581cb3..f25b21f0cca 100644 --- a/code/modules/mob/living/carbon/alien/logout.dm +++ b/code/modules/mob/living/carbon/alien/logout.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/alien/Logout() - ..() - RemoveInfectionImages() - return +/mob/living/carbon/alien/Logout() + ..() + RemoveInfectionImages() + return diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index 0addea8d4b6..5d828e7c181 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -1,129 +1,129 @@ -// This is to replace the previous datum/disease/alien_embryo for slightly improved handling and maintainability -// It functions almost identically (see code/datums/diseases/alien_embryo.dm) - -/obj/item/organ/internal/body_egg/alien_embryo - name = "alien embryo" - icon = 'icons/mob/alien.dmi' - icon_state = "larva0_dead" - var/stage = 0 - var/polling = 0 - -/obj/item/organ/internal/body_egg/alien_embryo/on_find(mob/living/finder) - ..() - if(stage < 4) - to_chat(finder, "It's small and weak, barely the size of a fetus.") - else - to_chat(finder, "It's grown quite large, and writhes slightly as you look at it.") - if(prob(10)) - AttemptGrow(0) - -/obj/item/organ/internal/body_egg/alien_embryo/prepare_eat() - var/obj/S = ..() - S.reagents.add_reagent("sacid", 10) - return S - -/obj/item/organ/internal/body_egg/alien_embryo/on_life() - switch(stage) - if(2, 3) - if(prob(2)) - owner.emote("sneeze") - if(prob(2)) - owner.emote("cough") - if(prob(2)) - to_chat(owner, "Your throat feels sore.") - if(prob(2)) - to_chat(owner, "Mucous runs down the back of your throat.") - if(4) - if(prob(2)) - owner.emote("sneeze") - if(prob(2)) - owner.emote("cough") - if(prob(4)) - to_chat(owner, "Your muscles ache.") - if(prob(20)) - owner.take_organ_damage(1) - if(prob(4)) - to_chat(owner, "Your stomach hurts.") - if(prob(20)) - owner.adjustToxLoss(1) - if(5) - to_chat(owner, "You feel something tearing its way out of your stomach...") - owner.adjustToxLoss(10) - -/obj/item/organ/internal/body_egg/alien_embryo/egg_process() - if(stage < 5 && prob(3)) - stage++ - spawn(0) - RefreshInfectionImage() - - if(stage == 5 && prob(50)) - for(var/datum/surgery/S in owner.surgeries) - if(S.location == "chest" && istype(S.get_surgery_step(), /datum/surgery_step/internal/manipulate_organs)) - AttemptGrow(0) - return - AttemptGrow() - - - -/obj/item/organ/internal/body_egg/alien_embryo/proc/AttemptGrow(var/gib_on_success = 1) - if(!owner || polling) - return - polling = 1 - spawn() - var/list/candidates = pollCandidates("Do you want to play as an alien?", ROLE_ALIEN, 0) - var/mob/C = null - - // To stop clientless larva, we will check that our host has a client - // if we find no ghosts to become the alien. If the host has a client - // he will become the alien but if he doesn't then we will set the stage - // to 2, so we don't do a process heavy check everytime. - - if(candidates.len) - C = pick(candidates) - else if(owner.client) - C = owner.client - else - stage = 2 // Let's try again later. - polling = 0 - return - - var/overlay = image('icons/mob/alien.dmi', loc = owner, icon_state = "burst_lie") - owner.overlays += overlay - - spawn(6) - var/mob/living/carbon/alien/larva/new_xeno = new(owner.drop_location()) - new_xeno.key = C.key - if(SSticker && SSticker.mode) - SSticker.mode.xenos += new_xeno.mind - new_xeno.mind.name = new_xeno.name - new_xeno.mind.assigned_role = SPECIAL_ROLE_XENOMORPH - new_xeno.mind.special_role = SPECIAL_ROLE_XENOMORPH - new_xeno << sound('sound/voice/hiss5.ogg',0,0,0,100)//To get the player's attention - - if(gib_on_success) - owner.gib() - else - owner.adjustBruteLoss(40) - owner.overlays -= overlay - qdel(src) - -/*---------------------------------------- -Proc: AddInfectionImages(C) -Des: Adds the infection image to all aliens for this embryo -----------------------------------------*/ -/obj/item/organ/internal/body_egg/alien_embryo/AddInfectionImages() - for(var/mob/living/carbon/alien/alien in GLOB.player_list) - if(alien.client) - var/I = image('icons/mob/alien.dmi', loc = owner, icon_state = "infected[stage]") - alien.client.images += I - -/*---------------------------------------- -Proc: RemoveInfectionImage(C) -Des: Removes all images from the mob infected by this embryo -----------------------------------------*/ -/obj/item/organ/internal/body_egg/alien_embryo/RemoveInfectionImages() - for(var/mob/living/carbon/alien/alien in GLOB.player_list) - if(alien.client) - for(var/image/I in alien.client.images) - if(dd_hasprefix_case(I.icon_state, "infected") && I.loc == owner) - qdel(I) \ No newline at end of file +// This is to replace the previous datum/disease/alien_embryo for slightly improved handling and maintainability +// It functions almost identically (see code/datums/diseases/alien_embryo.dm) + +/obj/item/organ/internal/body_egg/alien_embryo + name = "alien embryo" + icon = 'icons/mob/alien.dmi' + icon_state = "larva0_dead" + var/stage = 0 + var/polling = 0 + +/obj/item/organ/internal/body_egg/alien_embryo/on_find(mob/living/finder) + ..() + if(stage < 4) + to_chat(finder, "It's small and weak, barely the size of a fetus.") + else + to_chat(finder, "It's grown quite large, and writhes slightly as you look at it.") + if(prob(10)) + AttemptGrow(0) + +/obj/item/organ/internal/body_egg/alien_embryo/prepare_eat() + var/obj/S = ..() + S.reagents.add_reagent("sacid", 10) + return S + +/obj/item/organ/internal/body_egg/alien_embryo/on_life() + switch(stage) + if(2, 3) + if(prob(2)) + owner.emote("sneeze") + if(prob(2)) + owner.emote("cough") + if(prob(2)) + to_chat(owner, "Your throat feels sore.") + if(prob(2)) + to_chat(owner, "Mucous runs down the back of your throat.") + if(4) + if(prob(2)) + owner.emote("sneeze") + if(prob(2)) + owner.emote("cough") + if(prob(4)) + to_chat(owner, "Your muscles ache.") + if(prob(20)) + owner.take_organ_damage(1) + if(prob(4)) + to_chat(owner, "Your stomach hurts.") + if(prob(20)) + owner.adjustToxLoss(1) + if(5) + to_chat(owner, "You feel something tearing its way out of your stomach...") + owner.adjustToxLoss(10) + +/obj/item/organ/internal/body_egg/alien_embryo/egg_process() + if(stage < 5 && prob(3)) + stage++ + spawn(0) + RefreshInfectionImage() + + if(stage == 5 && prob(50)) + for(var/datum/surgery/S in owner.surgeries) + if(S.location == "chest" && istype(S.get_surgery_step(), /datum/surgery_step/internal/manipulate_organs)) + AttemptGrow(0) + return + AttemptGrow() + + + +/obj/item/organ/internal/body_egg/alien_embryo/proc/AttemptGrow(var/gib_on_success = 1) + if(!owner || polling) + return + polling = 1 + spawn() + var/list/candidates = pollCandidates("Do you want to play as an alien?", ROLE_ALIEN, 0) + var/mob/C = null + + // To stop clientless larva, we will check that our host has a client + // if we find no ghosts to become the alien. If the host has a client + // he will become the alien but if he doesn't then we will set the stage + // to 2, so we don't do a process heavy check everytime. + + if(candidates.len) + C = pick(candidates) + else if(owner.client) + C = owner.client + else + stage = 2 // Let's try again later. + polling = 0 + return + + var/overlay = image('icons/mob/alien.dmi', loc = owner, icon_state = "burst_lie") + owner.overlays += overlay + + spawn(6) + var/mob/living/carbon/alien/larva/new_xeno = new(owner.drop_location()) + new_xeno.key = C.key + if(SSticker && SSticker.mode) + SSticker.mode.xenos += new_xeno.mind + new_xeno.mind.name = new_xeno.name + new_xeno.mind.assigned_role = SPECIAL_ROLE_XENOMORPH + new_xeno.mind.special_role = SPECIAL_ROLE_XENOMORPH + new_xeno << sound('sound/voice/hiss5.ogg',0,0,0,100)//To get the player's attention + + if(gib_on_success) + owner.gib() + else + owner.adjustBruteLoss(40) + owner.overlays -= overlay + qdel(src) + +/*---------------------------------------- +Proc: AddInfectionImages(C) +Des: Adds the infection image to all aliens for this embryo +----------------------------------------*/ +/obj/item/organ/internal/body_egg/alien_embryo/AddInfectionImages() + for(var/mob/living/carbon/alien/alien in GLOB.player_list) + if(alien.client) + var/I = image('icons/mob/alien.dmi', loc = owner, icon_state = "infected[stage]") + alien.client.images += I + +/*---------------------------------------- +Proc: RemoveInfectionImage(C) +Des: Removes all images from the mob infected by this embryo +----------------------------------------*/ +/obj/item/organ/internal/body_egg/alien_embryo/RemoveInfectionImages() + for(var/mob/living/carbon/alien/alien in GLOB.player_list) + if(alien.client) + for(var/image/I in alien.client.images) + if(dd_hasprefix_case(I.icon_state, "infected") && I.loc == owner) + qdel(I) diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm index b5f3c51b43e..80f3c73d3d7 100644 --- a/code/modules/mob/living/carbon/alien/special/facehugger.dm +++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm @@ -1,239 +1,239 @@ -//TODO: Make these simple_animals - -#define MIN_IMPREGNATION_TIME 100 //time it takes to impregnate someone -#define MAX_IMPREGNATION_TIME 150 - -#define MIN_ACTIVE_TIME 200 //time between being dropped and going idle -#define MAX_ACTIVE_TIME 400 - -/obj/item/clothing/mask/facehugger - name = "alien" - desc = "It has some sort of a tube at the end of its tail." - icon = 'icons/mob/alien.dmi' - icon_state = "facehugger" - item_state = "facehugger" - w_class = WEIGHT_CLASS_TINY //note: can be picked up by aliens unlike most other items of w_class below 4 - throw_range = 5 - tint = 3 - flags = AIRTIGHT - flags_cover = MASKCOVERSMOUTH | MASKCOVERSEYES - layer = MOB_LAYER - max_integrity = 100 - - var/stat = CONSCIOUS //UNCONSCIOUS is the idle state in this case - - var/sterile = FALSE - var/real = TRUE //0 for the toy, 1 for real. Sure I could istype, but fuck that. - var/strength = 5 - - var/attached = 0 - -/obj/item/clothing/mask/facehugger/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - ..() - if(obj_integrity < 90) - Die() - -/obj/item/clothing/mask/facehugger/attackby(obj/item/O, mob/user, params) - return O.attack_obj(src, user) - -/obj/item/clothing/mask/facehugger/attack_alien(mob/user) //can be picked up by aliens - return attack_hand(user) - -/obj/item/clothing/mask/facehugger/attack_hand(mob/user) - if((stat == CONSCIOUS && !sterile) && !isalien(user)) - if(Attach(user)) - return - ..() - -/obj/item/clothing/mask/facehugger/attack(mob/living/M, mob/user) - ..() - user.unEquip(src) - Attach(M) - -/obj/item/clothing/mask/facehugger/examine(mob/user) - . = ..() - if(real)//So that giant red text about probisci doesn't show up for fake ones - switch(stat) - if(DEAD,UNCONSCIOUS) - . += "[src] is not moving." - if(CONSCIOUS) - . += "[src] seems to be active!" - if(sterile) - . += "It looks like the proboscis has been removed." - -/obj/item/clothing/mask/facehugger/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(exposed_temperature > 300) - Die() - -/obj/item/clothing/mask/facehugger/equipped(mob/M) - Attach(M) - -/obj/item/clothing/mask/facehugger/Crossed(atom/target, oldloc) - HasProximity(target) - return - -/obj/item/clothing/mask/facehugger/on_found(mob/finder) - if(stat == CONSCIOUS) - return HasProximity(finder) - return 0 - -/obj/item/clothing/mask/facehugger/HasProximity(atom/movable/AM as mob|obj) - if(CanHug(AM) && Adjacent(AM)) - return Attach(AM) - return 0 - -/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed) - if(!..()) - return - if(stat == CONSCIOUS) - icon_state = "[initial(icon_state)]_thrown" - spawn(15) - if(icon_state == "[initial(icon_state)]_thrown") - icon_state = "[initial(icon_state)]" - -/obj/item/clothing/mask/facehugger/throw_impact(atom/hit_atom) - ..() - if(stat == CONSCIOUS) - icon_state = "[initial(icon_state)]" - Attach(hit_atom) - -/obj/item/clothing/mask/facehugger/proc/Attach(mob/living/M) - if(!isliving(M)) - return 0 - if((!iscorgi(M) && !iscarbon(M)) || isalien(M)) - return 0 - if(attached) - return 0 - else - attached++ - spawn(MAX_IMPREGNATION_TIME) - attached = 0 - if(M.get_int_organ(/obj/item/organ/internal/xenos/hivenode)) - return 0 - if(M.get_int_organ(/obj/item/organ/internal/body_egg/alien_embryo)) - return 0 - if(loc == M) - return 0 - if(stat != CONSCIOUS) - return 0 - if(!sterile) M.take_organ_damage(strength,0) //done here so that even borgs and humans in helmets take damage - M.visible_message("[src] leaps at [M]'s face!", \ - "[src] leaps at [M]'s face!") - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.head && H.head.flags_cover & HEADCOVERSMOUTH) - H.visible_message("[src] smashes against [H]'s [H.head]!", \ - "[src] smashes against [H]'s [H.head]!") - Die() - return 0 - if(iscarbon(M)) - var/mob/living/carbon/target = M - if(target.wear_mask) - if(prob(20)) - return 0 - if(istype(target.wear_mask, /obj/item/clothing/mask/muzzle)) - var/obj/item/clothing/mask/muzzle/S = target.wear_mask - if(S.do_break()) - target.visible_message("[src] spits acid onto [S] melting the lock!", \ - "[src] spits acid onto [S] melting the lock!") - var/obj/item/clothing/W = target.wear_mask - if(W.flags & NODROP) - return 0 - target.unEquip(W) - - target.visible_message("[src] tears [W] off of [target]'s face!", \ - "[src] tears [W] off of [target]'s face!") - - src.loc = target - target.equip_to_slot(src, slot_wear_mask,,0) - if(!sterile) - M.Paralyse(MAX_IMPREGNATION_TIME/6) //something like 25 ticks = 20 seconds with the default settings - - GoIdle() //so it doesn't jump the people that tear it off - - spawn(rand(MIN_IMPREGNATION_TIME,MAX_IMPREGNATION_TIME)) - Impregnate(M) - - return 1 - -/obj/item/clothing/mask/facehugger/proc/Impregnate(mob/living/target as mob) - if(!target || target.stat == DEAD || loc != target) //was taken off or something - return - - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(C.wear_mask != src) - return - - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(!H.check_has_mouth()) - return - - if(!sterile) - //target.contract_disease(new /datum/disease/alien_embryo(0)) //so infection chance is same as virus infection chance - target.visible_message("[src] falls limp after violating [target]'s face!", \ - "[src] falls limp after violating [target]'s face!") - - Die() - icon_state = "[initial(icon_state)]_impregnated" - - if(!target.get_int_organ(/obj/item/organ/internal/body_egg/alien_embryo)) - new /obj/item/organ/internal/body_egg/alien_embryo(target) - else - target.visible_message("[src] violates [target]'s face!", \ - "[src] violates [target]'s face!") - -/obj/item/clothing/mask/facehugger/proc/GoActive() - if(stat == DEAD || stat == CONSCIOUS) - return - - stat = CONSCIOUS - icon_state = "[initial(icon_state)]" - -/obj/item/clothing/mask/facehugger/proc/GoIdle() - if(stat == DEAD || stat == UNCONSCIOUS) - return - - stat = UNCONSCIOUS - icon_state = "[initial(icon_state)]_inactive" - - spawn(rand(MIN_ACTIVE_TIME,MAX_ACTIVE_TIME)) - GoActive() - return - -/obj/item/clothing/mask/facehugger/proc/Die() - if(stat == DEAD) - return - - icon_state = "[initial(icon_state)]_dead" - item_state = "facehugger_inactive" - stat = DEAD - - visible_message("[src] curls up into a ball!") - -/proc/CanHug(mob/living/M) - if(!istype(M)) - return 0 - if(M.stat == DEAD) - return 0 - if(M.get_int_organ(/obj/item/organ/internal/xenos/hivenode)) - return 0 - - if(iscorgi(M)) - return 1 - - var/mob/living/carbon/C = M - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if(H.head && H.head.flags_cover & HEADCOVERSMOUTH) - return 0 - return 1 - return 0 - -/obj/item/clothing/mask/facehugger/lamarr - name = "Lamarr" - desc = "The worst she might do is attempt to... couple with your head." //hope we don't get sued over a harmless reference, rite? - sterile = 1 - gender = FEMALE \ No newline at end of file +//TODO: Make these simple_animals + +#define MIN_IMPREGNATION_TIME 100 //time it takes to impregnate someone +#define MAX_IMPREGNATION_TIME 150 + +#define MIN_ACTIVE_TIME 200 //time between being dropped and going idle +#define MAX_ACTIVE_TIME 400 + +/obj/item/clothing/mask/facehugger + name = "alien" + desc = "It has some sort of a tube at the end of its tail." + icon = 'icons/mob/alien.dmi' + icon_state = "facehugger" + item_state = "facehugger" + w_class = WEIGHT_CLASS_TINY //note: can be picked up by aliens unlike most other items of w_class below 4 + throw_range = 5 + tint = 3 + flags = AIRTIGHT + flags_cover = MASKCOVERSMOUTH | MASKCOVERSEYES + layer = MOB_LAYER + max_integrity = 100 + + var/stat = CONSCIOUS //UNCONSCIOUS is the idle state in this case + + var/sterile = FALSE + var/real = TRUE //0 for the toy, 1 for real. Sure I could istype, but fuck that. + var/strength = 5 + + var/attached = 0 + +/obj/item/clothing/mask/facehugger/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + ..() + if(obj_integrity < 90) + Die() + +/obj/item/clothing/mask/facehugger/attackby(obj/item/O, mob/user, params) + return O.attack_obj(src, user) + +/obj/item/clothing/mask/facehugger/attack_alien(mob/user) //can be picked up by aliens + return attack_hand(user) + +/obj/item/clothing/mask/facehugger/attack_hand(mob/user) + if((stat == CONSCIOUS && !sterile) && !isalien(user)) + if(Attach(user)) + return + ..() + +/obj/item/clothing/mask/facehugger/attack(mob/living/M, mob/user) + ..() + user.unEquip(src) + Attach(M) + +/obj/item/clothing/mask/facehugger/examine(mob/user) + . = ..() + if(real)//So that giant red text about probisci doesn't show up for fake ones + switch(stat) + if(DEAD,UNCONSCIOUS) + . += "[src] is not moving." + if(CONSCIOUS) + . += "[src] seems to be active!" + if(sterile) + . += "It looks like the proboscis has been removed." + +/obj/item/clothing/mask/facehugger/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(exposed_temperature > 300) + Die() + +/obj/item/clothing/mask/facehugger/equipped(mob/M) + Attach(M) + +/obj/item/clothing/mask/facehugger/Crossed(atom/target, oldloc) + HasProximity(target) + return + +/obj/item/clothing/mask/facehugger/on_found(mob/finder) + if(stat == CONSCIOUS) + return HasProximity(finder) + return 0 + +/obj/item/clothing/mask/facehugger/HasProximity(atom/movable/AM as mob|obj) + if(CanHug(AM) && Adjacent(AM)) + return Attach(AM) + return 0 + +/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed) + if(!..()) + return + if(stat == CONSCIOUS) + icon_state = "[initial(icon_state)]_thrown" + spawn(15) + if(icon_state == "[initial(icon_state)]_thrown") + icon_state = "[initial(icon_state)]" + +/obj/item/clothing/mask/facehugger/throw_impact(atom/hit_atom) + ..() + if(stat == CONSCIOUS) + icon_state = "[initial(icon_state)]" + Attach(hit_atom) + +/obj/item/clothing/mask/facehugger/proc/Attach(mob/living/M) + if(!isliving(M)) + return 0 + if((!iscorgi(M) && !iscarbon(M)) || isalien(M)) + return 0 + if(attached) + return 0 + else + attached++ + spawn(MAX_IMPREGNATION_TIME) + attached = 0 + if(M.get_int_organ(/obj/item/organ/internal/xenos/hivenode)) + return 0 + if(M.get_int_organ(/obj/item/organ/internal/body_egg/alien_embryo)) + return 0 + if(loc == M) + return 0 + if(stat != CONSCIOUS) + return 0 + if(!sterile) M.take_organ_damage(strength,0) //done here so that even borgs and humans in helmets take damage + M.visible_message("[src] leaps at [M]'s face!", \ + "[src] leaps at [M]'s face!") + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.head && H.head.flags_cover & HEADCOVERSMOUTH) + H.visible_message("[src] smashes against [H]'s [H.head]!", \ + "[src] smashes against [H]'s [H.head]!") + Die() + return 0 + if(iscarbon(M)) + var/mob/living/carbon/target = M + if(target.wear_mask) + if(prob(20)) + return 0 + if(istype(target.wear_mask, /obj/item/clothing/mask/muzzle)) + var/obj/item/clothing/mask/muzzle/S = target.wear_mask + if(S.do_break()) + target.visible_message("[src] spits acid onto [S] melting the lock!", \ + "[src] spits acid onto [S] melting the lock!") + var/obj/item/clothing/W = target.wear_mask + if(W.flags & NODROP) + return 0 + target.unEquip(W) + + target.visible_message("[src] tears [W] off of [target]'s face!", \ + "[src] tears [W] off of [target]'s face!") + + src.loc = target + target.equip_to_slot(src, slot_wear_mask,,0) + if(!sterile) + M.Paralyse(MAX_IMPREGNATION_TIME/6) //something like 25 ticks = 20 seconds with the default settings + + GoIdle() //so it doesn't jump the people that tear it off + + spawn(rand(MIN_IMPREGNATION_TIME,MAX_IMPREGNATION_TIME)) + Impregnate(M) + + return 1 + +/obj/item/clothing/mask/facehugger/proc/Impregnate(mob/living/target as mob) + if(!target || target.stat == DEAD || loc != target) //was taken off or something + return + + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(C.wear_mask != src) + return + + if(ishuman(target)) + var/mob/living/carbon/human/H = target + if(!H.check_has_mouth()) + return + + if(!sterile) + //target.contract_disease(new /datum/disease/alien_embryo(0)) //so infection chance is same as virus infection chance + target.visible_message("[src] falls limp after violating [target]'s face!", \ + "[src] falls limp after violating [target]'s face!") + + Die() + icon_state = "[initial(icon_state)]_impregnated" + + if(!target.get_int_organ(/obj/item/organ/internal/body_egg/alien_embryo)) + new /obj/item/organ/internal/body_egg/alien_embryo(target) + else + target.visible_message("[src] violates [target]'s face!", \ + "[src] violates [target]'s face!") + +/obj/item/clothing/mask/facehugger/proc/GoActive() + if(stat == DEAD || stat == CONSCIOUS) + return + + stat = CONSCIOUS + icon_state = "[initial(icon_state)]" + +/obj/item/clothing/mask/facehugger/proc/GoIdle() + if(stat == DEAD || stat == UNCONSCIOUS) + return + + stat = UNCONSCIOUS + icon_state = "[initial(icon_state)]_inactive" + + spawn(rand(MIN_ACTIVE_TIME,MAX_ACTIVE_TIME)) + GoActive() + return + +/obj/item/clothing/mask/facehugger/proc/Die() + if(stat == DEAD) + return + + icon_state = "[initial(icon_state)]_dead" + item_state = "facehugger_inactive" + stat = DEAD + + visible_message("[src] curls up into a ball!") + +/proc/CanHug(mob/living/M) + if(!istype(M)) + return 0 + if(M.stat == DEAD) + return 0 + if(M.get_int_organ(/obj/item/organ/internal/xenos/hivenode)) + return 0 + + if(iscorgi(M)) + return 1 + + var/mob/living/carbon/C = M + if(ishuman(C)) + var/mob/living/carbon/human/H = C + if(H.head && H.head.flags_cover & HEADCOVERSMOUTH) + return 0 + return 1 + return 0 + +/obj/item/clothing/mask/facehugger/lamarr + name = "Lamarr" + desc = "The worst she might do is attempt to... couple with your head." //hope we don't get sued over a harmless reference, rite? + sterile = 1 + gender = FEMALE diff --git a/code/modules/mob/living/carbon/brain/MMI.dm b/code/modules/mob/living/carbon/brain/MMI.dm index e3cf646d579..130a2e3a574 100644 --- a/code/modules/mob/living/carbon/brain/MMI.dm +++ b/code/modules/mob/living/carbon/brain/MMI.dm @@ -1,299 +1,299 @@ -/obj/item/mmi - name = "Man-Machine Interface" - desc = "The Warrior's bland acronym, MMI, obscures the true horror of this monstrosity." - icon = 'icons/obj/assemblies.dmi' - icon_state = "mmi_empty" - w_class = WEIGHT_CLASS_NORMAL - origin_tech = "biotech=3" - origin_tech = "biotech=2;programming=3;engineering=2" - - //Revised. Brainmob is now contained directly within object of transfer. MMI in this case. - var/alien = 0 - var/syndiemmi = 0 //Whether or not this is a Syndicate MMI - var/mob/living/carbon/brain/brainmob = null//The current occupant. - var/obj/item/organ/internal/brain/held_brain = null // This is so MMI's aren't brainscrubber 9000's - var/mob/living/silicon/robot/robot = null//Appears unused. - var/obj/mecha/mecha = null//This does not appear to be used outside of reference in mecha.dm. -// I'm using this for mechs giving MMIs HUDs now - - var/obj/item/radio/radio = null // For use with the radio MMI upgrade - var/datum/action/generic/configure_mmi_radio/radio_action = null - - // Used for cases when mmi or one of it's children commits suicide. - // Needed to fix a rather insane bug when a posibrain/robotic brain commits suicide - var/dead_icon = "mmi_dead" - -/obj/item/mmi/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - if(istype(O, /obj/item/organ/internal/brain/crystal)) - to_chat(user, " This brain is too malformed to be able to use with the [src].") - return - if(istype(O, /obj/item/organ/internal/brain/golem)) - to_chat(user, "You can't find a way to plug [O] into [src].") - return - if(istype(O,/obj/item/organ/internal/brain) && !brainmob) //Time to stick a brain in it --NEO - var/obj/item/organ/internal/brain/B = O - if(!B.brainmob) - to_chat(user, "You aren't sure where this brain came from, but you're pretty sure it's a useless brain.") - return - if(held_brain) - to_chat(user, "Somehow, this MMI still has a brain in it. Report this to the bug tracker.") - log_runtime(EXCEPTION("[user] tried to stick a [O] into [src] in [get_area(src)], but the held brain variable wasn't cleared"), src) - return - if(user.drop_item()) - B.forceMove(src) - visible_message("[user] sticks \a [O] into \the [src].") - brainmob = B.brainmob - B.brainmob = null - brainmob.forceMove(src) - brainmob.container = src - brainmob.stat = CONSCIOUS - GLOB.respawnable_list -= brainmob - GLOB.dead_mob_list -= brainmob//Update dem lists - GLOB.living_mob_list += brainmob - - held_brain = B - if(istype(O,/obj/item/organ/internal/brain/xeno)) // kept the type check, as it still does other weird stuff - name = "Man-Machine Interface: Alien - [brainmob.real_name]" - icon = 'icons/mob/alien.dmi' - become_occupied("AlienMMI") - alien = 1 - else - name = "Man-Machine Interface: [brainmob.real_name]" - icon = B.mmi_icon - become_occupied("[B.mmi_icon_state]") - alien = 0 - - if(radio_action) - radio_action.UpdateButtonIcon() - feedback_inc("cyborg_mmis_filled",1) - else - to_chat(user, "You can't drop [B]!") - - return - - if(istype(O, /obj/item/mmi_radio_upgrade)) - if(radio) - to_chat(user, "[src] already has a radio installed.") - else - user.visible_message("[user] begins to install the [O] into [src]...", \ - "You start to install the [O] into [src]...") - if(do_after(user, 20, target=src)) - if(user.drop_item()) - user.visible_message("[user] installs [O] in [src].", \ - "You install [O] in [src].") - if(brainmob) - to_chat(brainmob, "MMI radio capability installed.") - install_radio() - qdel(O) - else - to_chat(user, "You can't drop [O]!") - return - - // Maybe later add encryption key support, but that's a pain in the neck atm - - if(brainmob) - O.attack(brainmob, user)//Oh noooeeeee - // Brainmobs can take damage, but they can't actually die. Maybe should fix. - return - return ..() - -/obj/item/mmi/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(!radio) - to_chat(user, "There is no radio in [src]!") - return - user.visible_message("[user] begins to uninstall the radio from [src]...", \ - "You start to uninstall the radio from [src]...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume) || !radio) - return - uninstall_radio() - new /obj/item/mmi_radio_upgrade(get_turf(src)) - user.visible_message("[user] uninstalls the radio from [src].", \ - "You uninstall the radio from [src].") - - -/obj/item/mmi/attack_self(mob/user as mob) - if(!brainmob) - to_chat(user, "You upend the MMI, but there's nothing in it.") - else - to_chat(user, "You unlock and upend the MMI, spilling the brain onto the floor.") - dropbrain(get_turf(user)) - icon = 'icons/obj/assemblies.dmi' - icon_state = "mmi_empty" - name = "Man-Machine Interface" - -/obj/item/mmi/proc/transfer_identity(var/mob/living/carbon/human/H)//Same deal as the regular brain proc. Used for human-->robot people. - brainmob = new(src) - brainmob.name = H.real_name - brainmob.real_name = H.real_name - brainmob.dna = H.dna.Clone() - brainmob.container = src - - if(!istype(H.dna.species) || isnull(H.dna.species.return_organ("brain"))) // Diona/buggy people - held_brain = new(src) - else // We have a species, and it has a brain - var/brain_path = H.dna.species.return_organ("brain") - if(!ispath(brain_path, /obj/item/organ/internal/brain)) - brain_path = /obj/item/organ/internal/brain - held_brain = new brain_path(src) // Slime people will keep their slimy brains this way - held_brain.dna = brainmob.dna.Clone() - held_brain.name = "\the [brainmob.name]'s [initial(held_brain.name)]" - - name = "Man-Machine Interface: [brainmob.real_name]" - become_occupied("mmi_full") - -//I made this proc as a way to have a brainmob be transferred to any created brain, and to solve the -//problem i was having with alien/nonalien brain drops. -/obj/item/mmi/proc/dropbrain(var/turf/dropspot) - if(isnull(held_brain)) - log_runtime(EXCEPTION("[src] at [loc] attempted to drop brain without a contained brain in [get_area(src)]."), src) - to_chat(brainmob, "Your MMI did not contain a brain! We'll make a new one for you, but you'd best report this to the bugtracker!") - held_brain = new(dropspot) // Let's not ruin someone's round because of something dumb -- Crazylemon - held_brain.dna = brainmob.dna.Clone() - held_brain.name = "\the [brainmob.name]'s [initial(held_brain.name)]" - - brainmob.container = null//Reset brainmob mmi var. - brainmob.forceMove(held_brain) //Throw mob into brain. - GLOB.respawnable_list += brainmob - GLOB.living_mob_list -= brainmob//Get outta here - held_brain.brainmob = brainmob//Set the brain to use the brainmob - held_brain.brainmob.cancel_camera() - brainmob = null//Set mmi brainmob var to null - held_brain.forceMove(dropspot) - held_brain = null - -/obj/item/mmi/proc/become_occupied(var/new_icon) - icon_state = new_icon - if(radio) - radio_action.ApplyIcon() - -/obj/item/mmi/examine(mob/user) - . = ..() - if(radio) - . += "A radio is installed on [src]." - -/obj/item/mmi/proc/install_radio() - radio = new(src) - radio.broadcasting = TRUE - radio_action = new(radio, src) - if(brainmob && brainmob.loc == src) - radio_action.Grant(brainmob) - -/obj/item/mmi/proc/uninstall_radio() - QDEL_NULL(radio) - QDEL_NULL(radio_action) - -/datum/action/generic/configure_mmi_radio - name = "Configure MMI Radio" - desc = "Configure the radio installed in your MMI." - check_flags = AB_CHECK_CONSCIOUS - procname = "ui_interact" - var/obj/item/mmi = null - -/datum/action/generic/configure_mmi_radio/New(var/Target, var/obj/item/mmi/M) - . = ..() - mmi = M - -/datum/action/generic/configure_mmi_radio/Destroy() - mmi = null - return ..() - -/datum/action/generic/configure_mmi_radio/ApplyIcon(obj/screen/movable/action_button/current_button) - // A copy/paste of the item action icon code - current_button.overlays.Cut() - if(target) - var/obj/item/I = mmi - var/old_layer = I.layer - var/old_plane = I.plane - I.layer = 21 - I.plane = HUD_PLANE - current_button.overlays += I - I.layer = old_layer - I.plane = old_plane - -/obj/item/mmi/emp_act(severity) - if(!brainmob) - return - else - switch(severity) - if(1) - brainmob.emp_damage += rand(20,30) - if(2) - brainmob.emp_damage += rand(10,20) - if(3) - brainmob.emp_damage += rand(0,10) - ..() - -/obj/item/mmi/relaymove(var/mob/user, var/direction) - if(user.stat || user.stunned) - return - var/obj/item/rig/rig = src.get_rig() - if(rig) - rig.forced_move(direction, user) - -/obj/item/mmi/Destroy() - if(isrobot(loc)) - var/mob/living/silicon/robot/borg = loc - borg.mmi = null - QDEL_NULL(brainmob) - QDEL_NULL(held_brain) - QDEL_NULL(radio) - QDEL_NULL(radio_action) - return ..() - -// These two procs are important for when an MMI pilots a mech -// (Brainmob "enters/leaves" the MMI when piloting) -// Also neatly handles basically every case where a brain -// is inserted or removed from an MMI -/obj/item/mmi/Entered(atom/movable/A) - if(radio && istype(A, /mob/living/carbon/brain)) - radio_action.Grant(A) - -/obj/item/mmi/Exited(atom/movable/A) - if(radio && istype(A, /mob/living/carbon/brain)) - radio_action.Remove(A) - -/obj/item/mmi/syndie - name = "Syndicate Man-Machine Interface" - desc = "Syndicate's own brand of MMI. It enforces laws designed to help Syndicate agents achieve their goals upon cyborgs created with it, but doesn't fit in Nanotrasen AI cores." - origin_tech = "biotech=4;programming=4;syndicate=2" - syndiemmi = 1 - -/obj/item/mmi/attempt_become_organ(obj/item/organ/external/parent,mob/living/carbon/human/H) - if(!brainmob) - return 0 - if(!parent) - log_debug("Attempting to insert into a null parent!") - return 0 - if(H.get_int_organ(/obj/item/organ/internal/brain)) - // one brain at a time - return 0 - var/obj/item/organ/internal/brain/mmi_holder/holder = new() - holder.parent_organ = parent.limb_name - forceMove(holder) - holder.stored_mmi = src - holder.update_from_mmi() - if(brainmob && brainmob.mind) - brainmob.mind.transfer_to(H) - holder.insert(H) - - return 1 - -// As a synthetic, the only limit on visibility is view range -/obj/item/mmi/contents_nano_distance(var/src_object, var/mob/living/user) - if((src_object in view(src)) && get_dist(src_object, src) <= user.client.view) - return STATUS_INTERACTIVE // interactive (green visibility) - return user.shared_living_nano_distance(src_object) - -// For now the only thing that is helped by this is radio access -// Later a more intricate system for MMI UI interaction can be established -/obj/item/mmi/contents_nano_interact(var/src_object, var/mob/living/user) - if(!istype(user, /mob/living/carbon/brain)) - log_runtime(EXCEPTION("Somehow a non-brain mob is inside an MMI!"), user) - return ..() - var/mob/living/carbon/brain/BM = user - if(BM.container == src && src_object == radio) - return STATUS_INTERACTIVE - return ..() +/obj/item/mmi + name = "Man-Machine Interface" + desc = "The Warrior's bland acronym, MMI, obscures the true horror of this monstrosity." + icon = 'icons/obj/assemblies.dmi' + icon_state = "mmi_empty" + w_class = WEIGHT_CLASS_NORMAL + origin_tech = "biotech=3" + origin_tech = "biotech=2;programming=3;engineering=2" + + //Revised. Brainmob is now contained directly within object of transfer. MMI in this case. + var/alien = 0 + var/syndiemmi = 0 //Whether or not this is a Syndicate MMI + var/mob/living/carbon/brain/brainmob = null//The current occupant. + var/obj/item/organ/internal/brain/held_brain = null // This is so MMI's aren't brainscrubber 9000's + var/mob/living/silicon/robot/robot = null//Appears unused. + var/obj/mecha/mecha = null//This does not appear to be used outside of reference in mecha.dm. +// I'm using this for mechs giving MMIs HUDs now + + var/obj/item/radio/radio = null // For use with the radio MMI upgrade + var/datum/action/generic/configure_mmi_radio/radio_action = null + + // Used for cases when mmi or one of it's children commits suicide. + // Needed to fix a rather insane bug when a posibrain/robotic brain commits suicide + var/dead_icon = "mmi_dead" + +/obj/item/mmi/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + if(istype(O, /obj/item/organ/internal/brain/crystal)) + to_chat(user, " This brain is too malformed to be able to use with the [src].") + return + if(istype(O, /obj/item/organ/internal/brain/golem)) + to_chat(user, "You can't find a way to plug [O] into [src].") + return + if(istype(O,/obj/item/organ/internal/brain) && !brainmob) //Time to stick a brain in it --NEO + var/obj/item/organ/internal/brain/B = O + if(!B.brainmob) + to_chat(user, "You aren't sure where this brain came from, but you're pretty sure it's a useless brain.") + return + if(held_brain) + to_chat(user, "Somehow, this MMI still has a brain in it. Report this to the bug tracker.") + log_runtime(EXCEPTION("[user] tried to stick a [O] into [src] in [get_area(src)], but the held brain variable wasn't cleared"), src) + return + if(user.drop_item()) + B.forceMove(src) + visible_message("[user] sticks \a [O] into \the [src].") + brainmob = B.brainmob + B.brainmob = null + brainmob.forceMove(src) + brainmob.container = src + brainmob.stat = CONSCIOUS + GLOB.respawnable_list -= brainmob + GLOB.dead_mob_list -= brainmob//Update dem lists + GLOB.living_mob_list += brainmob + + held_brain = B + if(istype(O,/obj/item/organ/internal/brain/xeno)) // kept the type check, as it still does other weird stuff + name = "Man-Machine Interface: Alien - [brainmob.real_name]" + icon = 'icons/mob/alien.dmi' + become_occupied("AlienMMI") + alien = 1 + else + name = "Man-Machine Interface: [brainmob.real_name]" + icon = B.mmi_icon + become_occupied("[B.mmi_icon_state]") + alien = 0 + + if(radio_action) + radio_action.UpdateButtonIcon() + feedback_inc("cyborg_mmis_filled",1) + else + to_chat(user, "You can't drop [B]!") + + return + + if(istype(O, /obj/item/mmi_radio_upgrade)) + if(radio) + to_chat(user, "[src] already has a radio installed.") + else + user.visible_message("[user] begins to install the [O] into [src]...", \ + "You start to install the [O] into [src]...") + if(do_after(user, 20, target=src)) + if(user.drop_item()) + user.visible_message("[user] installs [O] in [src].", \ + "You install [O] in [src].") + if(brainmob) + to_chat(brainmob, "MMI radio capability installed.") + install_radio() + qdel(O) + else + to_chat(user, "You can't drop [O]!") + return + + // Maybe later add encryption key support, but that's a pain in the neck atm + + if(brainmob) + O.attack(brainmob, user)//Oh noooeeeee + // Brainmobs can take damage, but they can't actually die. Maybe should fix. + return + return ..() + +/obj/item/mmi/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(!radio) + to_chat(user, "There is no radio in [src]!") + return + user.visible_message("[user] begins to uninstall the radio from [src]...", \ + "You start to uninstall the radio from [src]...") + if(!I.use_tool(src, user, 40, volume = I.tool_volume) || !radio) + return + uninstall_radio() + new /obj/item/mmi_radio_upgrade(get_turf(src)) + user.visible_message("[user] uninstalls the radio from [src].", \ + "You uninstall the radio from [src].") + + +/obj/item/mmi/attack_self(mob/user as mob) + if(!brainmob) + to_chat(user, "You upend the MMI, but there's nothing in it.") + else + to_chat(user, "You unlock and upend the MMI, spilling the brain onto the floor.") + dropbrain(get_turf(user)) + icon = 'icons/obj/assemblies.dmi' + icon_state = "mmi_empty" + name = "Man-Machine Interface" + +/obj/item/mmi/proc/transfer_identity(var/mob/living/carbon/human/H)//Same deal as the regular brain proc. Used for human-->robot people. + brainmob = new(src) + brainmob.name = H.real_name + brainmob.real_name = H.real_name + brainmob.dna = H.dna.Clone() + brainmob.container = src + + if(!istype(H.dna.species) || isnull(H.dna.species.return_organ("brain"))) // Diona/buggy people + held_brain = new(src) + else // We have a species, and it has a brain + var/brain_path = H.dna.species.return_organ("brain") + if(!ispath(brain_path, /obj/item/organ/internal/brain)) + brain_path = /obj/item/organ/internal/brain + held_brain = new brain_path(src) // Slime people will keep their slimy brains this way + held_brain.dna = brainmob.dna.Clone() + held_brain.name = "\the [brainmob.name]'s [initial(held_brain.name)]" + + name = "Man-Machine Interface: [brainmob.real_name]" + become_occupied("mmi_full") + +//I made this proc as a way to have a brainmob be transferred to any created brain, and to solve the +//problem i was having with alien/nonalien brain drops. +/obj/item/mmi/proc/dropbrain(var/turf/dropspot) + if(isnull(held_brain)) + log_runtime(EXCEPTION("[src] at [loc] attempted to drop brain without a contained brain in [get_area(src)]."), src) + to_chat(brainmob, "Your MMI did not contain a brain! We'll make a new one for you, but you'd best report this to the bugtracker!") + held_brain = new(dropspot) // Let's not ruin someone's round because of something dumb -- Crazylemon + held_brain.dna = brainmob.dna.Clone() + held_brain.name = "\the [brainmob.name]'s [initial(held_brain.name)]" + + brainmob.container = null//Reset brainmob mmi var. + brainmob.forceMove(held_brain) //Throw mob into brain. + GLOB.respawnable_list += brainmob + GLOB.living_mob_list -= brainmob//Get outta here + held_brain.brainmob = brainmob//Set the brain to use the brainmob + held_brain.brainmob.cancel_camera() + brainmob = null//Set mmi brainmob var to null + held_brain.forceMove(dropspot) + held_brain = null + +/obj/item/mmi/proc/become_occupied(var/new_icon) + icon_state = new_icon + if(radio) + radio_action.ApplyIcon() + +/obj/item/mmi/examine(mob/user) + . = ..() + if(radio) + . += "A radio is installed on [src]." + +/obj/item/mmi/proc/install_radio() + radio = new(src) + radio.broadcasting = TRUE + radio_action = new(radio, src) + if(brainmob && brainmob.loc == src) + radio_action.Grant(brainmob) + +/obj/item/mmi/proc/uninstall_radio() + QDEL_NULL(radio) + QDEL_NULL(radio_action) + +/datum/action/generic/configure_mmi_radio + name = "Configure MMI Radio" + desc = "Configure the radio installed in your MMI." + check_flags = AB_CHECK_CONSCIOUS + procname = "ui_interact" + var/obj/item/mmi = null + +/datum/action/generic/configure_mmi_radio/New(var/Target, var/obj/item/mmi/M) + . = ..() + mmi = M + +/datum/action/generic/configure_mmi_radio/Destroy() + mmi = null + return ..() + +/datum/action/generic/configure_mmi_radio/ApplyIcon(obj/screen/movable/action_button/current_button) + // A copy/paste of the item action icon code + current_button.overlays.Cut() + if(target) + var/obj/item/I = mmi + var/old_layer = I.layer + var/old_plane = I.plane + I.layer = 21 + I.plane = HUD_PLANE + current_button.overlays += I + I.layer = old_layer + I.plane = old_plane + +/obj/item/mmi/emp_act(severity) + if(!brainmob) + return + else + switch(severity) + if(1) + brainmob.emp_damage += rand(20,30) + if(2) + brainmob.emp_damage += rand(10,20) + if(3) + brainmob.emp_damage += rand(0,10) + ..() + +/obj/item/mmi/relaymove(var/mob/user, var/direction) + if(user.stat || user.stunned) + return + var/obj/item/rig/rig = src.get_rig() + if(rig) + rig.forced_move(direction, user) + +/obj/item/mmi/Destroy() + if(isrobot(loc)) + var/mob/living/silicon/robot/borg = loc + borg.mmi = null + QDEL_NULL(brainmob) + QDEL_NULL(held_brain) + QDEL_NULL(radio) + QDEL_NULL(radio_action) + return ..() + +// These two procs are important for when an MMI pilots a mech +// (Brainmob "enters/leaves" the MMI when piloting) +// Also neatly handles basically every case where a brain +// is inserted or removed from an MMI +/obj/item/mmi/Entered(atom/movable/A) + if(radio && istype(A, /mob/living/carbon/brain)) + radio_action.Grant(A) + +/obj/item/mmi/Exited(atom/movable/A) + if(radio && istype(A, /mob/living/carbon/brain)) + radio_action.Remove(A) + +/obj/item/mmi/syndie + name = "Syndicate Man-Machine Interface" + desc = "Syndicate's own brand of MMI. It enforces laws designed to help Syndicate agents achieve their goals upon cyborgs created with it, but doesn't fit in Nanotrasen AI cores." + origin_tech = "biotech=4;programming=4;syndicate=2" + syndiemmi = 1 + +/obj/item/mmi/attempt_become_organ(obj/item/organ/external/parent,mob/living/carbon/human/H) + if(!brainmob) + return 0 + if(!parent) + log_debug("Attempting to insert into a null parent!") + return 0 + if(H.get_int_organ(/obj/item/organ/internal/brain)) + // one brain at a time + return 0 + var/obj/item/organ/internal/brain/mmi_holder/holder = new() + holder.parent_organ = parent.limb_name + forceMove(holder) + holder.stored_mmi = src + holder.update_from_mmi() + if(brainmob && brainmob.mind) + brainmob.mind.transfer_to(H) + holder.insert(H) + + return 1 + +// As a synthetic, the only limit on visibility is view range +/obj/item/mmi/contents_nano_distance(var/src_object, var/mob/living/user) + if((src_object in view(src)) && get_dist(src_object, src) <= user.client.view) + return STATUS_INTERACTIVE // interactive (green visibility) + return user.shared_living_nano_distance(src_object) + +// For now the only thing that is helped by this is radio access +// Later a more intricate system for MMI UI interaction can be established +/obj/item/mmi/contents_nano_interact(var/src_object, var/mob/living/user) + if(!istype(user, /mob/living/carbon/brain)) + log_runtime(EXCEPTION("Somehow a non-brain mob is inside an MMI!"), user) + return ..() + var/mob/living/carbon/brain/BM = user + if(BM.container == src && src_object == radio) + return STATUS_INTERACTIVE + return ..() diff --git a/code/modules/mob/living/carbon/brain/brain.dm b/code/modules/mob/living/carbon/brain/brain.dm index 5d2d684e1a7..ec80d6a44b4 100644 --- a/code/modules/mob/living/carbon/brain/brain.dm +++ b/code/modules/mob/living/carbon/brain/brain.dm @@ -1,111 +1,111 @@ -/mob/living/carbon/brain - var/obj/item/container = null - var/timeofhostdeath = 0 - var/emp_damage = 0//Handles a type of MMI damage - use_me = 0 //Can't use the me verb, it's a freaking immobile brain - icon = 'icons/obj/surgery.dmi' - icon_state = "brain1" - -/mob/living/carbon/brain/New() - create_reagents(330) - add_language("Galactic Common") - ..() - -/mob/living/carbon/brain/Destroy() - if(key) //If there is a mob connected to this thing. Have to check key twice to avoid false death reporting. - if(stat!=DEAD) //If not dead. - death(1) //Brains can die again. AND THEY SHOULD AHA HA HA HA HA HA - ghostize() //Ghostize checks for key so nothing else is necessary. - return ..() - -/mob/living/carbon/brain/say_understands(other)//Goddamn is this hackish, but this say code is so odd - if(istype(other, /mob/living/silicon/ai)) - if(!(container && istype(container, /obj/item/mmi))) - return 0 - else - return 1 - if(istype(other, /mob/living/silicon/decoy)) - if(!(container && istype(container, /obj/item/mmi))) - return 0 - else - return 1 - if(istype(other, /mob/living/silicon/pai)) - if(!(container && istype(container, /obj/item/mmi))) - return 0 - else - return 1 - if(istype(other, /mob/living/silicon/robot)) - if(!(container && istype(container, /obj/item/mmi))) - return 0 - else - return 1 - if(istype(other, /mob/living/carbon/human)) - return 1 - if(istype(other, /mob/living/simple_animal/slime)) - return 1 - return ..() - - -/mob/living/carbon/brain/update_canmove(delay_action_updates = 0) - if(in_contents_of(/obj/mecha)) - canmove = 1 - use_me = 1 //If it can move, let it emote - else if(istype(loc, /obj/item/mmi)) - canmove = 1 //mmi won't move anyways so whatever - else - canmove = 0 - - if(!delay_action_updates) - update_action_buttons_icon() - return canmove - -/mob/living/carbon/brain/ex_act() //you cant blow up brainmobs because it makes transfer_to() freak out when borgs blow up. - return - -/mob/living/carbon/brain/blob_act(obj/structure/blob/B) - return - -/mob/living/carbon/brain/on_forcemove(atom/newloc) - if(container) - container.forceMove(newloc) - else //something went very wrong. - CRASH("Brainmob without container.") - forceMove(container) - -/* -This will return true if the brain has a container that leaves it less helpless than a naked brain - -I'm using this for Stat to give it a more nifty interface to work with -*/ -/mob/living/carbon/brain/proc/has_synthetic_assistance() - return (container && istype(container, /obj/item/mmi)) || in_contents_of(/obj/mecha) - -/mob/living/carbon/brain/proc/get_race() - if(container) - var/obj/item/mmi/M = container - if(istype(M) && M.held_brain) - return M.held_brain.dna.species.name - else - return "Artificial Life" - if(istype(loc, /obj/item/organ/internal/brain)) - var/obj/item/organ/internal/brain/B = loc - return B.dna.species.name - -/mob/living/carbon/brain/Stat() - ..() - if(has_synthetic_assistance()) - statpanel("Status") - show_stat_emergency_shuttle_eta() - - if(client.statpanel == "Status") - //Knowing how well-off your mech is doing is really important as an MMI - if(istype(src.loc, /obj/mecha)) - var/obj/mecha/M = src.loc - stat("Exosuit Charge:", "[istype(M.cell) ? "[M.cell.charge] / [M.cell.maxcharge]" : "No cell detected"]") - stat("Exosuit Integrity", "[!M.obj_integrity ? "0" : "[(M.obj_integrity / M.max_integrity) * 100]"]%") - -/mob/living/carbon/brain/can_safely_leave_loc() - return 0 //You're not supposed to be ethereal jaunting, brains - -/mob/living/carbon/brain/can_hear() - . = TRUE +/mob/living/carbon/brain + var/obj/item/container = null + var/timeofhostdeath = 0 + var/emp_damage = 0//Handles a type of MMI damage + use_me = 0 //Can't use the me verb, it's a freaking immobile brain + icon = 'icons/obj/surgery.dmi' + icon_state = "brain1" + +/mob/living/carbon/brain/New() + create_reagents(330) + add_language("Galactic Common") + ..() + +/mob/living/carbon/brain/Destroy() + if(key) //If there is a mob connected to this thing. Have to check key twice to avoid false death reporting. + if(stat!=DEAD) //If not dead. + death(1) //Brains can die again. AND THEY SHOULD AHA HA HA HA HA HA + ghostize() //Ghostize checks for key so nothing else is necessary. + return ..() + +/mob/living/carbon/brain/say_understands(other)//Goddamn is this hackish, but this say code is so odd + if(istype(other, /mob/living/silicon/ai)) + if(!(container && istype(container, /obj/item/mmi))) + return 0 + else + return 1 + if(istype(other, /mob/living/silicon/decoy)) + if(!(container && istype(container, /obj/item/mmi))) + return 0 + else + return 1 + if(istype(other, /mob/living/silicon/pai)) + if(!(container && istype(container, /obj/item/mmi))) + return 0 + else + return 1 + if(istype(other, /mob/living/silicon/robot)) + if(!(container && istype(container, /obj/item/mmi))) + return 0 + else + return 1 + if(istype(other, /mob/living/carbon/human)) + return 1 + if(istype(other, /mob/living/simple_animal/slime)) + return 1 + return ..() + + +/mob/living/carbon/brain/update_canmove(delay_action_updates = 0) + if(in_contents_of(/obj/mecha)) + canmove = 1 + use_me = 1 //If it can move, let it emote + else if(istype(loc, /obj/item/mmi)) + canmove = 1 //mmi won't move anyways so whatever + else + canmove = 0 + + if(!delay_action_updates) + update_action_buttons_icon() + return canmove + +/mob/living/carbon/brain/ex_act() //you cant blow up brainmobs because it makes transfer_to() freak out when borgs blow up. + return + +/mob/living/carbon/brain/blob_act(obj/structure/blob/B) + return + +/mob/living/carbon/brain/on_forcemove(atom/newloc) + if(container) + container.forceMove(newloc) + else //something went very wrong. + CRASH("Brainmob without container.") + forceMove(container) + +/* +This will return true if the brain has a container that leaves it less helpless than a naked brain + +I'm using this for Stat to give it a more nifty interface to work with +*/ +/mob/living/carbon/brain/proc/has_synthetic_assistance() + return (container && istype(container, /obj/item/mmi)) || in_contents_of(/obj/mecha) + +/mob/living/carbon/brain/proc/get_race() + if(container) + var/obj/item/mmi/M = container + if(istype(M) && M.held_brain) + return M.held_brain.dna.species.name + else + return "Artificial Life" + if(istype(loc, /obj/item/organ/internal/brain)) + var/obj/item/organ/internal/brain/B = loc + return B.dna.species.name + +/mob/living/carbon/brain/Stat() + ..() + if(has_synthetic_assistance()) + statpanel("Status") + show_stat_emergency_shuttle_eta() + + if(client.statpanel == "Status") + //Knowing how well-off your mech is doing is really important as an MMI + if(istype(src.loc, /obj/mecha)) + var/obj/mecha/M = src.loc + stat("Exosuit Charge:", "[istype(M.cell) ? "[M.cell.charge] / [M.cell.maxcharge]" : "No cell detected"]") + stat("Exosuit Integrity", "[!M.obj_integrity ? "0" : "[(M.obj_integrity / M.max_integrity) * 100]"]%") + +/mob/living/carbon/brain/can_safely_leave_loc() + return 0 //You're not supposed to be ethereal jaunting, brains + +/mob/living/carbon/brain/can_hear() + . = TRUE diff --git a/code/modules/mob/living/carbon/brain/brain_item.dm b/code/modules/mob/living/carbon/brain/brain_item.dm index bfd7b3786e7..0b6bcededaf 100644 --- a/code/modules/mob/living/carbon/brain/brain_item.dm +++ b/code/modules/mob/living/carbon/brain/brain_item.dm @@ -1,141 +1,141 @@ -/obj/item/organ/internal/brain - name = "brain" - max_damage = 120 - icon_state = "brain2" - force = 1.0 - w_class = WEIGHT_CLASS_SMALL - throwforce = 1.0 - throw_speed = 3 - throw_range = 5 - origin_tech = "biotech=5" - attack_verb = list("attacked", "slapped", "whacked") - var/mob/living/carbon/brain/brainmob = null - organ_tag = "brain" - parent_organ = "head" - slot = "brain" - vital = TRUE - hidden_pain = TRUE //the brain has no pain receptors, and brain damage is meant to be a stealthy damage type. - var/mmi_icon = 'icons/obj/assemblies.dmi' - var/mmi_icon_state = "mmi_full" - -/obj/item/organ/internal/brain/xeno - name = "xenomorph brain" - desc = "We barely understand the brains of terrestial animals. Who knows what we may find in the brain of such an advanced species?" - icon_state = "brain-x" - origin_tech = "biotech=6" - mmi_icon = 'icons/mob/alien.dmi' - mmi_icon_state = "AlienMMI" - -/obj/item/organ/internal/brain/New() - ..() - spawn(5) - if(brainmob && brainmob.client) - brainmob.client.screen.len = null //clear the hud - -/obj/item/organ/internal/brain/proc/transfer_identity(var/mob/living/carbon/H) - brainmob = new(src) - if(isnull(dna)) // someone didn't set this right... - log_runtime(EXCEPTION("[src] at [loc] did not contain a dna datum at time of removal."), src) - dna = H.dna.Clone() - name = "\the [dna.real_name]'s [initial(src.name)]" - brainmob.dna = dna.Clone() // Silly baycode, what you do -// brainmob.dna = H.dna.Clone() Putting in and taking out a brain doesn't make it a carbon copy of the original brain of the body you put it in - brainmob.name = dna.real_name - brainmob.real_name = dna.real_name - brainmob.timeofhostdeath = H.timeofdeath - if(H.mind) - H.mind.transfer_to(brainmob) - - to_chat(brainmob, "You feel slightly disoriented. That's normal when you're just a [initial(src.name)].") - -/obj/item/organ/internal/brain/examine(mob/user) // -- TLE - . = ..() - if(brainmob && brainmob.client)//if thar be a brain inside... the brain. - . += "You can feel the small spark of life still left in this one." - else - . += "This one seems particularly lifeless. Perhaps it will regain some of its luster later.." - -/obj/item/organ/internal/brain/remove(var/mob/living/user,special = 0) - if(dna) - name = "[dna.real_name]'s [initial(name)]" - - if(!owner) return ..() // Probably a redundant removal; just bail - - var/obj/item/organ/internal/brain/B = src - if(!special) - var/mob/living/simple_animal/borer/borer = owner.has_brain_worms() - if(borer) - borer.leave_host() //Should remove borer if the brain is removed - RR - - if(owner.mind && !non_primary)//don't transfer if the owner does not have a mind. - B.transfer_identity(user) - - if(istype(owner,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = owner - H.update_hair(1) - . = ..() - -/obj/item/organ/internal/brain/insert(var/mob/living/target,special = 0) - - name = "[initial(name)]" - var/brain_already_exists = 0 - if(istype(target,/mob/living/carbon/human)) // No more IPC multibrain shenanigans - if(target.get_int_organ(/obj/item/organ/internal/brain)) - brain_already_exists = 1 - - var/mob/living/carbon/human/H = target - H.update_hair(1) - - if(!brain_already_exists) - if(brainmob) - if(target.key) - target.ghostize() - if(brainmob.mind) - brainmob.mind.transfer_to(target) - else - target.key = brainmob.key - else - log_debug("Multibrain shenanigans at ([target.x],[target.y],[target.z]), mob '[target]'") - ..(target, special = special) - -/obj/item/organ/internal/brain/receive_damage(amount, silent = 0) //brains are special; if they receive damage by other means, we really just want the damage to be passed ot the owner and back onto the brain. - if(owner) - owner.adjustBrainLoss(amount) - -/obj/item/organ/internal/brain/necrotize(update_sprite = TRUE) //Brain also has special handling for when it necrotizes - damage = max_damage - status |= ORGAN_DEAD - STOP_PROCESSING(SSobj, src) - if(dead_icon && !is_robotic()) - icon_state = dead_icon - if(owner && vital) - owner.setBrainLoss(120) - -/obj/item/organ/internal/brain/prepare_eat() - return // Too important to eat. - -/obj/item/organ/internal/brain/slime - name = "slime core" - desc = "A complex, organic knot of jelly and crystalline particles." - icon = 'icons/mob/slimes.dmi' - icon_state = "green slime extract" - mmi_icon_state = "slime_mmi" -// parent_organ = "chest" Hello I am from the ministry of rubber forehead aliens how are you - -/obj/item/organ/internal/brain/golem - name = "Runic mind" - desc = "A tightly furled roll of paper, covered with indecipherable runes." - icon = 'icons/obj/wizard.dmi' - icon_state = "scroll" - -/obj/item/organ/internal/brain/Destroy() //copypasted from MMIs. - QDEL_NULL(brainmob) - return ..() - -/obj/item/organ/internal/brain/cluwne - -/obj/item/organ/internal/brain/cluwne/insert(mob/living/target, special = 0, make_cluwne = 1) - ..(target, special = special) - if(ishuman(target) && make_cluwne) - var/mob/living/carbon/human/H = target - H.makeCluwne() //No matter where you go, no matter what you do, you cannot escape \ No newline at end of file +/obj/item/organ/internal/brain + name = "brain" + max_damage = 120 + icon_state = "brain2" + force = 1.0 + w_class = WEIGHT_CLASS_SMALL + throwforce = 1.0 + throw_speed = 3 + throw_range = 5 + origin_tech = "biotech=5" + attack_verb = list("attacked", "slapped", "whacked") + var/mob/living/carbon/brain/brainmob = null + organ_tag = "brain" + parent_organ = "head" + slot = "brain" + vital = TRUE + hidden_pain = TRUE //the brain has no pain receptors, and brain damage is meant to be a stealthy damage type. + var/mmi_icon = 'icons/obj/assemblies.dmi' + var/mmi_icon_state = "mmi_full" + +/obj/item/organ/internal/brain/xeno + name = "xenomorph brain" + desc = "We barely understand the brains of terrestial animals. Who knows what we may find in the brain of such an advanced species?" + icon_state = "brain-x" + origin_tech = "biotech=6" + mmi_icon = 'icons/mob/alien.dmi' + mmi_icon_state = "AlienMMI" + +/obj/item/organ/internal/brain/New() + ..() + spawn(5) + if(brainmob && brainmob.client) + brainmob.client.screen.len = null //clear the hud + +/obj/item/organ/internal/brain/proc/transfer_identity(var/mob/living/carbon/H) + brainmob = new(src) + if(isnull(dna)) // someone didn't set this right... + log_runtime(EXCEPTION("[src] at [loc] did not contain a dna datum at time of removal."), src) + dna = H.dna.Clone() + name = "\the [dna.real_name]'s [initial(src.name)]" + brainmob.dna = dna.Clone() // Silly baycode, what you do +// brainmob.dna = H.dna.Clone() Putting in and taking out a brain doesn't make it a carbon copy of the original brain of the body you put it in + brainmob.name = dna.real_name + brainmob.real_name = dna.real_name + brainmob.timeofhostdeath = H.timeofdeath + if(H.mind) + H.mind.transfer_to(brainmob) + + to_chat(brainmob, "You feel slightly disoriented. That's normal when you're just a [initial(src.name)].") + +/obj/item/organ/internal/brain/examine(mob/user) // -- TLE + . = ..() + if(brainmob && brainmob.client)//if thar be a brain inside... the brain. + . += "You can feel the small spark of life still left in this one." + else + . += "This one seems particularly lifeless. Perhaps it will regain some of its luster later.." + +/obj/item/organ/internal/brain/remove(var/mob/living/user,special = 0) + if(dna) + name = "[dna.real_name]'s [initial(name)]" + + if(!owner) return ..() // Probably a redundant removal; just bail + + var/obj/item/organ/internal/brain/B = src + if(!special) + var/mob/living/simple_animal/borer/borer = owner.has_brain_worms() + if(borer) + borer.leave_host() //Should remove borer if the brain is removed - RR + + if(owner.mind && !non_primary)//don't transfer if the owner does not have a mind. + B.transfer_identity(user) + + if(istype(owner,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = owner + H.update_hair(1) + . = ..() + +/obj/item/organ/internal/brain/insert(var/mob/living/target,special = 0) + + name = "[initial(name)]" + var/brain_already_exists = 0 + if(istype(target,/mob/living/carbon/human)) // No more IPC multibrain shenanigans + if(target.get_int_organ(/obj/item/organ/internal/brain)) + brain_already_exists = 1 + + var/mob/living/carbon/human/H = target + H.update_hair(1) + + if(!brain_already_exists) + if(brainmob) + if(target.key) + target.ghostize() + if(brainmob.mind) + brainmob.mind.transfer_to(target) + else + target.key = brainmob.key + else + log_debug("Multibrain shenanigans at ([target.x],[target.y],[target.z]), mob '[target]'") + ..(target, special = special) + +/obj/item/organ/internal/brain/receive_damage(amount, silent = 0) //brains are special; if they receive damage by other means, we really just want the damage to be passed ot the owner and back onto the brain. + if(owner) + owner.adjustBrainLoss(amount) + +/obj/item/organ/internal/brain/necrotize(update_sprite = TRUE) //Brain also has special handling for when it necrotizes + damage = max_damage + status |= ORGAN_DEAD + STOP_PROCESSING(SSobj, src) + if(dead_icon && !is_robotic()) + icon_state = dead_icon + if(owner && vital) + owner.setBrainLoss(120) + +/obj/item/organ/internal/brain/prepare_eat() + return // Too important to eat. + +/obj/item/organ/internal/brain/slime + name = "slime core" + desc = "A complex, organic knot of jelly and crystalline particles." + icon = 'icons/mob/slimes.dmi' + icon_state = "green slime extract" + mmi_icon_state = "slime_mmi" +// parent_organ = "chest" Hello I am from the ministry of rubber forehead aliens how are you + +/obj/item/organ/internal/brain/golem + name = "Runic mind" + desc = "A tightly furled roll of paper, covered with indecipherable runes." + icon = 'icons/obj/wizard.dmi' + icon_state = "scroll" + +/obj/item/organ/internal/brain/Destroy() //copypasted from MMIs. + QDEL_NULL(brainmob) + return ..() + +/obj/item/organ/internal/brain/cluwne + +/obj/item/organ/internal/brain/cluwne/insert(mob/living/target, special = 0, make_cluwne = 1) + ..(target, special = special) + if(ishuman(target) && make_cluwne) + var/mob/living/carbon/human/H = target + H.makeCluwne() //No matter where you go, no matter what you do, you cannot escape diff --git a/code/modules/mob/living/carbon/brain/death.dm b/code/modules/mob/living/carbon/brain/death.dm index c36a553aff4..8dd0db0b407 100644 --- a/code/modules/mob/living/carbon/brain/death.dm +++ b/code/modules/mob/living/carbon/brain/death.dm @@ -1,27 +1,27 @@ -/mob/living/carbon/brain/death(gibbed) - // Only execute the below if we successfully died - . = ..() - if(!.) - return FALSE - if(!gibbed && container && istype(container, /obj/item/mmi))//If not gibbed but in a container. - var/obj/item/mmi/mmi = container - visible_message("[src]'s MMI flatlines!", "You hear something flatline.") - mmi.icon_state = mmi.dead_icon - -/mob/living/carbon/brain/gib() - // can we muster a parent call here? - if(!death(TRUE) && stat != DEAD) - return FALSE - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - - gibs(loc, dna) - - if(container && istype(container, /obj/item/mmi)) - qdel(container)//Gets rid of the MMI if there is one - if(loc) - if(istype(loc,/obj/item/organ/internal/brain)) - qdel(loc)//Gets rid of the brain item - QDEL_IN(src, 0) +/mob/living/carbon/brain/death(gibbed) + // Only execute the below if we successfully died + . = ..() + if(!.) + return FALSE + if(!gibbed && container && istype(container, /obj/item/mmi))//If not gibbed but in a container. + var/obj/item/mmi/mmi = container + visible_message("[src]'s MMI flatlines!", "You hear something flatline.") + mmi.icon_state = mmi.dead_icon + +/mob/living/carbon/brain/gib() + // can we muster a parent call here? + if(!death(TRUE) && stat != DEAD) + return FALSE + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + + gibs(loc, dna) + + if(container && istype(container, /obj/item/mmi)) + qdel(container)//Gets rid of the MMI if there is one + if(loc) + if(istype(loc,/obj/item/organ/internal/brain)) + qdel(loc)//Gets rid of the brain item + QDEL_IN(src, 0) diff --git a/code/modules/mob/living/carbon/brain/emote.dm b/code/modules/mob/living/carbon/brain/emote.dm index dd1876e223f..84298ee4f0f 100644 --- a/code/modules/mob/living/carbon/brain/emote.dm +++ b/code/modules/mob/living/carbon/brain/emote.dm @@ -48,4 +48,4 @@ to_chat(src, "alarm, alert, notice, flash,blink, whistle, beep, boop") if(message && !stat) - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/carbon/brain/life.dm b/code/modules/mob/living/carbon/brain/life.dm index d0cf06f1bf7..6bd88e5e111 100644 --- a/code/modules/mob/living/carbon/brain/life.dm +++ b/code/modules/mob/living/carbon/brain/life.dm @@ -1,40 +1,40 @@ -/mob/living/carbon/brain/handle_mutations_and_radiation() - if(radiation) - if(radiation > 100) - if(!container) - to_chat(src, "You feel weak.") - else - to_chat(src, "STATUS: CRITICAL AMOUNTS OF RADIATION DETECTED.") - - switch(radiation) - - if(50 to 75) - if(prob(5)) - if(!container) - to_chat(src, "You feel weak.") - else - to_chat(src, "STATUS: DANGEROUS AMOUNTS OF RADIATION DETECTED.") - ..() - -/mob/living/carbon/brain/proc/handle_temperature_damage(body_part, exposed_temperature, exposed_intensity) - if(status_flags & GODMODE) - return - - if(exposed_temperature > bodytemperature) - var/discomfort = min( abs(exposed_temperature - bodytemperature)*(exposed_intensity)/2000000, 1.0) - adjustFireLoss(20.0*discomfort) - - else - var/discomfort = min( abs(exposed_temperature - bodytemperature)*(exposed_intensity)/2000000, 1.0) - adjustFireLoss(5.0*discomfort) - -/mob/living/carbon/brain/handle_regular_status_updates() - . = ..() - - if(.) - if(!container && (health < HEALTH_THRESHOLD_DEAD && check_death_method() || ((world.time - timeofhostdeath) > config.revival_brain_life))) - death() - return 0 - -/mob/living/carbon/brain/breathe() - return +/mob/living/carbon/brain/handle_mutations_and_radiation() + if(radiation) + if(radiation > 100) + if(!container) + to_chat(src, "You feel weak.") + else + to_chat(src, "STATUS: CRITICAL AMOUNTS OF RADIATION DETECTED.") + + switch(radiation) + + if(50 to 75) + if(prob(5)) + if(!container) + to_chat(src, "You feel weak.") + else + to_chat(src, "STATUS: DANGEROUS AMOUNTS OF RADIATION DETECTED.") + ..() + +/mob/living/carbon/brain/proc/handle_temperature_damage(body_part, exposed_temperature, exposed_intensity) + if(status_flags & GODMODE) + return + + if(exposed_temperature > bodytemperature) + var/discomfort = min( abs(exposed_temperature - bodytemperature)*(exposed_intensity)/2000000, 1.0) + adjustFireLoss(20.0*discomfort) + + else + var/discomfort = min( abs(exposed_temperature - bodytemperature)*(exposed_intensity)/2000000, 1.0) + adjustFireLoss(5.0*discomfort) + +/mob/living/carbon/brain/handle_regular_status_updates() + . = ..() + + if(.) + if(!container && (health < HEALTH_THRESHOLD_DEAD && check_death_method() || ((world.time - timeofhostdeath) > config.revival_brain_life))) + death() + return 0 + +/mob/living/carbon/brain/breathe() + return diff --git a/code/modules/mob/living/carbon/brain/login.dm b/code/modules/mob/living/carbon/brain/login.dm index 622ae74656a..3e5f0e48365 100644 --- a/code/modules/mob/living/carbon/brain/login.dm +++ b/code/modules/mob/living/carbon/brain/login.dm @@ -1,3 +1,3 @@ -/mob/living/carbon/brain/Login() - ..() - SetSleeping(0) +/mob/living/carbon/brain/Login() + ..() + SetSleeping(0) diff --git a/code/modules/mob/living/carbon/brain/say.dm b/code/modules/mob/living/carbon/brain/say.dm index eb4750c9780..2b992fc5ba0 100644 --- a/code/modules/mob/living/carbon/brain/say.dm +++ b/code/modules/mob/living/carbon/brain/say.dm @@ -1,50 +1,50 @@ -//TODO: Convert this over for languages. -/mob/living/carbon/brain/say(var/message, var/datum/language/speaking = null) - if(!can_speak(warning = TRUE)) - return - - if(prob(emp_damage * 4)) - if(prob(10)) //10% chance to drop the message entirely - return - else - message = Gibberish(message, (emp_damage*6))//scrambles the message, gets worse when emp_damage is higher - - ..(message) - -/mob/living/carbon/brain/whisper(message as text) - if(!can_speak(warning = TRUE)) - return - - ..() - -/mob/living/carbon/brain/can_speak(var/warning = FALSE) - . = ..() - - if(!istype(container, /obj/item/mmi)) - . = FALSE - else if(istype(container, /obj/item/mmi/robotic_brain)) - var/obj/item/mmi/robotic_brain/R = container - if(R && R.silenced) - if(warning) - to_chat(usr, "You cannot speak, as your internal speaker is turned off.") - . = FALSE - -/mob/living/carbon/brain/handle_message_mode(var/message_mode, list/message_pieces, var/verb, var/used_radios) - switch(message_mode) - if("headset") - var/radio_worked = 0 // If any of the radios our brainmob could use functioned, this is set true so that we don't use any others - // I'm doing it this way so that if the mecha radio fails for some reason, a radio MMI still has the built-in fallback - if(container && istype(container,/obj/item/mmi)) - var/obj/item/mmi/c = container - if(!radio_worked && c.mecha) - var/obj/mecha/metalgear = c.mecha - if(metalgear.radio) - radio_worked = metalgear.radio.talk_into(src, message_pieces, message_mode, verb) - - else if(!radio_worked && c.radio) - radio_worked = c.radio.talk_into(src, message_pieces, message_mode, verb) - return radio_worked - if("whisper") - whisper_say(message_pieces) - return 1 - else return 0 +//TODO: Convert this over for languages. +/mob/living/carbon/brain/say(var/message, var/datum/language/speaking = null) + if(!can_speak(warning = TRUE)) + return + + if(prob(emp_damage * 4)) + if(prob(10)) //10% chance to drop the message entirely + return + else + message = Gibberish(message, (emp_damage*6))//scrambles the message, gets worse when emp_damage is higher + + ..(message) + +/mob/living/carbon/brain/whisper(message as text) + if(!can_speak(warning = TRUE)) + return + + ..() + +/mob/living/carbon/brain/can_speak(var/warning = FALSE) + . = ..() + + if(!istype(container, /obj/item/mmi)) + . = FALSE + else if(istype(container, /obj/item/mmi/robotic_brain)) + var/obj/item/mmi/robotic_brain/R = container + if(R && R.silenced) + if(warning) + to_chat(usr, "You cannot speak, as your internal speaker is turned off.") + . = FALSE + +/mob/living/carbon/brain/handle_message_mode(var/message_mode, list/message_pieces, var/verb, var/used_radios) + switch(message_mode) + if("headset") + var/radio_worked = 0 // If any of the radios our brainmob could use functioned, this is set true so that we don't use any others + // I'm doing it this way so that if the mecha radio fails for some reason, a radio MMI still has the built-in fallback + if(container && istype(container,/obj/item/mmi)) + var/obj/item/mmi/c = container + if(!radio_worked && c.mecha) + var/obj/mecha/metalgear = c.mecha + if(metalgear.radio) + radio_worked = metalgear.radio.talk_into(src, message_pieces, message_mode, verb) + + else if(!radio_worked && c.radio) + radio_worked = c.radio.talk_into(src, message_pieces, message_mode, verb) + return radio_worked + if("whisper") + whisper_say(message_pieces) + return 1 + else return 0 diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 7347e8eb3e9..598a533d2fa 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1192,4 +1192,4 @@ so that different stomachs can handle things in different ways VB*/ var/obj/item/I = X I.acid_level = 0 //washes off the acid on our clothes I.extinguish() //extinguishes our clothes - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index f8e9f7e9aff..884ee802518 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -1,30 +1,30 @@ -/mob/living/carbon - gender = MALE - pressure_resistance = 15 - var/list/stomach_contents = list() - var/list/internal_organs = list() - var/list/internal_organs_slot = list() //Same as above, but stores "slot ID" - "organ" pairs for easy access. - var/antibodies = 0 - - var/life_tick = 0 // The amount of life ticks that have processed on this mob. - - var/obj/item/handcuffed = null //Whether or not the mob is handcuffed - var/obj/item/legcuffed = null //Same as handcuffs but for legs. Bear traps use this. - - var/obj/item/head = null - var/obj/item/clothing/suit/wear_suit = null //TODO: necessary? Are they even used? ~Carn - - var/mob/living/simple_animal/borer/borer = null - - //Active emote/pose - var/pose = null - - var/pulse = PULSE_NORM //current pulse level - - var/wetlevel = 0 //how wet the mob is - - var/co2overloadtime = null - var/dreaming = 0 //How many dream images we have left to send - var/nightmare = 0 - - blood_volume = BLOOD_VOLUME_NORMAL \ No newline at end of file +/mob/living/carbon + gender = MALE + pressure_resistance = 15 + var/list/stomach_contents = list() + var/list/internal_organs = list() + var/list/internal_organs_slot = list() //Same as above, but stores "slot ID" - "organ" pairs for easy access. + var/antibodies = 0 + + var/life_tick = 0 // The amount of life ticks that have processed on this mob. + + var/obj/item/handcuffed = null //Whether or not the mob is handcuffed + var/obj/item/legcuffed = null //Same as handcuffs but for legs. Bear traps use this. + + var/obj/item/head = null + var/obj/item/clothing/suit/wear_suit = null //TODO: necessary? Are they even used? ~Carn + + var/mob/living/simple_animal/borer/borer = null + + //Active emote/pose + var/pose = null + + var/pulse = PULSE_NORM //current pulse level + + var/wetlevel = 0 //how wet the mob is + + var/co2overloadtime = null + var/dreaming = 0 //How many dream images we have left to send + var/nightmare = 0 + + blood_volume = BLOOD_VOLUME_NORMAL diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index d95badbbb01..2037f713c56 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -1,195 +1,195 @@ -/mob/living/carbon/human/gib() - if(!death(TRUE) && stat != DEAD) - return FALSE - var/atom/movable/overlay/animation = null - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - if(!isSynthetic()) - animation = new(loc) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - animation.master = src - - playsound(src.loc, 'sound/goonstation/effects/gib.ogg', 50, 1) - else - playsound(src.loc, 'sound/goonstation/effects/robogib.ogg', 50, 1) - - for(var/obj/item/organ/internal/I in internal_organs) - if(isturf(loc)) - var/atom/movable/thing = I.remove(src) - if(thing) - thing.forceMove(get_turf(src)) - thing.throw_at(get_edge_target_turf(src, pick(alldirs)), rand(1,3), 5) - - for(var/obj/item/organ/external/E in bodyparts) - if(istype(E, /obj/item/organ/external/chest)) - continue - // Only make the limb drop if it's not too damaged - if(prob(100 - E.get_damage())) - // Override the current limb status and don't cause an explosion - E.droplimb(DROPLIMB_SHARP) - - for(var/mob/M in src) - if(M in stomach_contents) - stomach_contents.Remove(M) - M.forceMove(get_turf(src)) - visible_message("[M] bursts out of [src]!") - - if(!isSynthetic()) - flick("gibbed-h", animation) - hgibs(loc, dna) - else - new /obj/effect/decal/cleanable/blood/gibs/robot(loc) - do_sparks(3, 1, src) - QDEL_IN(animation, 15) - QDEL_IN(src, 0) - return TRUE - -/mob/living/carbon/human/dust() - if(!death(TRUE) && stat != DEAD) - return FALSE - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - dust_animation() - QDEL_IN(src, 15) - return TRUE - -/mob/living/carbon/human/dust_animation() - var/atom/movable/overlay/animation = null - - animation = new(loc) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - animation.master = src - - flick("dust-h", animation) - new dna.species.remains_type(get_turf(src)) - QDEL_IN(animation, 15) - return TRUE - -/mob/living/carbon/human/melt() - if(!death(TRUE) && stat != DEAD) - return FALSE - var/atom/movable/overlay/animation = null - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - - animation = new(loc) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - animation.master = src - - flick("liquify", animation) - QDEL_IN(src, 0) - QDEL_IN(animation, 15) - //new /obj/effect/decal/remains/human(loc) - return TRUE - -/mob/living/carbon/human/death(gibbed) - if(can_die() && !gibbed && deathgasp_on_death) - emote("deathgasp", force = TRUE) //let the world KNOW WE ARE DEAD - - // Only execute the below if we successfully died - . = ..(gibbed) - if(!.) - return FALSE - - set_heartattack(FALSE) - SSmobs.cubemonkeys -= src - if(dna.species) - dna.species.handle_hud_icons(src) - //Handle species-specific deaths. - dna.species.handle_death(gibbed, src) - - if(ishuman(LAssailant)) - var/mob/living/carbon/human/H=LAssailant - if(H.mind) - H.mind.kills += "[key_name(src)]" - - if(SSticker && SSticker.mode) -// log_world("k") - sql_report_death(src) - - if(wearing_rig) - wearing_rig.notify_ai("Warning: user death event. Mobility control passed to integrated intelligence system.") - -/mob/living/carbon/human/update_revive() - . = ..() - if(. && healthdoll) - // We're alive again, so re-build the entire healthdoll - healthdoll.cached_healthdoll_overlays.Cut() - // Update healthdoll - if(dna.species) - dna.species.update_sight(src) - dna.species.handle_hud_icons(src) - -/mob/living/carbon/human/proc/makeSkeleton() - var/obj/item/organ/external/head/H = get_organ("head") - if(SKELETON in src.mutations) - return - - if(istype(H)) - H.disfigured = TRUE - if(H.f_style) - H.f_style = initial(H.f_style) - if(H.h_style) - H.h_style = initial(H.h_style) - if(H.ha_style) - H.ha_style = initial(H.ha_style) - if(H.alt_head) - H.alt_head = initial(H.alt_head) - H.handle_alt_icon() - m_styles = DEFAULT_MARKING_STYLES - update_fhair(0) - update_hair(0) - update_head_accessory(0) - update_markings(0) - - mutations.Add(SKELETON) - mutations.Add(NOCLONE) - update_body(0) - update_mutantrace() - return - -/mob/living/carbon/human/proc/ChangeToHusk() - - // If the target has no DNA to begin with, its DNA can't be damaged beyond repair. - if(NO_DNA in dna.species.species_traits) - return - if(HUSK in mutations) - return - - var/obj/item/organ/external/head/H = bodyparts_by_name["head"] - if(istype(H)) - H.disfigured = TRUE //makes them unknown without fucking up other stuff like admintools - if(H.f_style) - H.f_style = "Shaved" //we only change the icon_state of the hair datum, so it doesn't mess up their UI/UE - if(H.h_style) - H.h_style = "Bald" - update_fhair(0) - update_hair(0) - - mutations.Add(HUSK) - update_body(0) - update_mutantrace() - return - -/mob/living/carbon/human/proc/Drain() - ChangeToHusk() - mutations |= NOCLONE - return - -/mob/living/carbon/human/proc/cure_husk() - mutations.Remove(HUSK) - var/obj/item/organ/external/head/H = bodyparts_by_name["head"] - if(istype(H)) - H.disfigured = FALSE - update_body(0) - update_mutantrace(0) - UpdateAppearance() // reset hair from DNA +/mob/living/carbon/human/gib() + if(!death(TRUE) && stat != DEAD) + return FALSE + var/atom/movable/overlay/animation = null + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + if(!isSynthetic()) + animation = new(loc) + animation.icon_state = "blank" + animation.icon = 'icons/mob/mob.dmi' + animation.master = src + + playsound(src.loc, 'sound/goonstation/effects/gib.ogg', 50, 1) + else + playsound(src.loc, 'sound/goonstation/effects/robogib.ogg', 50, 1) + + for(var/obj/item/organ/internal/I in internal_organs) + if(isturf(loc)) + var/atom/movable/thing = I.remove(src) + if(thing) + thing.forceMove(get_turf(src)) + thing.throw_at(get_edge_target_turf(src, pick(alldirs)), rand(1,3), 5) + + for(var/obj/item/organ/external/E in bodyparts) + if(istype(E, /obj/item/organ/external/chest)) + continue + // Only make the limb drop if it's not too damaged + if(prob(100 - E.get_damage())) + // Override the current limb status and don't cause an explosion + E.droplimb(DROPLIMB_SHARP) + + for(var/mob/M in src) + if(M in stomach_contents) + stomach_contents.Remove(M) + M.forceMove(get_turf(src)) + visible_message("[M] bursts out of [src]!") + + if(!isSynthetic()) + flick("gibbed-h", animation) + hgibs(loc, dna) + else + new /obj/effect/decal/cleanable/blood/gibs/robot(loc) + do_sparks(3, 1, src) + QDEL_IN(animation, 15) + QDEL_IN(src, 0) + return TRUE + +/mob/living/carbon/human/dust() + if(!death(TRUE) && stat != DEAD) + return FALSE + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + dust_animation() + QDEL_IN(src, 15) + return TRUE + +/mob/living/carbon/human/dust_animation() + var/atom/movable/overlay/animation = null + + animation = new(loc) + animation.icon_state = "blank" + animation.icon = 'icons/mob/mob.dmi' + animation.master = src + + flick("dust-h", animation) + new dna.species.remains_type(get_turf(src)) + QDEL_IN(animation, 15) + return TRUE + +/mob/living/carbon/human/melt() + if(!death(TRUE) && stat != DEAD) + return FALSE + var/atom/movable/overlay/animation = null + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + + animation = new(loc) + animation.icon_state = "blank" + animation.icon = 'icons/mob/mob.dmi' + animation.master = src + + flick("liquify", animation) + QDEL_IN(src, 0) + QDEL_IN(animation, 15) + //new /obj/effect/decal/remains/human(loc) + return TRUE + +/mob/living/carbon/human/death(gibbed) + if(can_die() && !gibbed && deathgasp_on_death) + emote("deathgasp", force = TRUE) //let the world KNOW WE ARE DEAD + + // Only execute the below if we successfully died + . = ..(gibbed) + if(!.) + return FALSE + + set_heartattack(FALSE) + SSmobs.cubemonkeys -= src + if(dna.species) + dna.species.handle_hud_icons(src) + //Handle species-specific deaths. + dna.species.handle_death(gibbed, src) + + if(ishuman(LAssailant)) + var/mob/living/carbon/human/H=LAssailant + if(H.mind) + H.mind.kills += "[key_name(src)]" + + if(SSticker && SSticker.mode) +// log_world("k") + sql_report_death(src) + + if(wearing_rig) + wearing_rig.notify_ai("Warning: user death event. Mobility control passed to integrated intelligence system.") + +/mob/living/carbon/human/update_revive() + . = ..() + if(. && healthdoll) + // We're alive again, so re-build the entire healthdoll + healthdoll.cached_healthdoll_overlays.Cut() + // Update healthdoll + if(dna.species) + dna.species.update_sight(src) + dna.species.handle_hud_icons(src) + +/mob/living/carbon/human/proc/makeSkeleton() + var/obj/item/organ/external/head/H = get_organ("head") + if(SKELETON in src.mutations) + return + + if(istype(H)) + H.disfigured = TRUE + if(H.f_style) + H.f_style = initial(H.f_style) + if(H.h_style) + H.h_style = initial(H.h_style) + if(H.ha_style) + H.ha_style = initial(H.ha_style) + if(H.alt_head) + H.alt_head = initial(H.alt_head) + H.handle_alt_icon() + m_styles = DEFAULT_MARKING_STYLES + update_fhair(0) + update_hair(0) + update_head_accessory(0) + update_markings(0) + + mutations.Add(SKELETON) + mutations.Add(NOCLONE) + update_body(0) + update_mutantrace() + return + +/mob/living/carbon/human/proc/ChangeToHusk() + + // If the target has no DNA to begin with, its DNA can't be damaged beyond repair. + if(NO_DNA in dna.species.species_traits) + return + if(HUSK in mutations) + return + + var/obj/item/organ/external/head/H = bodyparts_by_name["head"] + if(istype(H)) + H.disfigured = TRUE //makes them unknown without fucking up other stuff like admintools + if(H.f_style) + H.f_style = "Shaved" //we only change the icon_state of the hair datum, so it doesn't mess up their UI/UE + if(H.h_style) + H.h_style = "Bald" + update_fhair(0) + update_hair(0) + + mutations.Add(HUSK) + update_body(0) + update_mutantrace() + return + +/mob/living/carbon/human/proc/Drain() + ChangeToHusk() + mutations |= NOCLONE + return + +/mob/living/carbon/human/proc/cure_husk() + mutations.Remove(HUSK) + var/obj/item/organ/external/head/H = bodyparts_by_name["head"] + if(istype(H)) + H.disfigured = FALSE + update_body(0) + update_mutantrace(0) + UpdateAppearance() // reset hair from DNA diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index c048fffc975..845339917eb 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -1,434 +1,434 @@ -/mob/living/carbon/human/examine(mob/user) - var/skipgloves = 0 - var/skipsuitstorage = 0 - var/skipjumpsuit = 0 - var/skipshoes = 0 - var/skipmask = 0 - var/skipears = 0 - var/skipeyes = 0 - var/skipface = 0 - - //exosuits and helmets obscure our view and stuff. - if(wear_suit) - skipgloves = wear_suit.flags_inv & HIDEGLOVES - skipsuitstorage = wear_suit.flags_inv & HIDESUITSTORAGE - skipjumpsuit = wear_suit.flags_inv & HIDEJUMPSUIT - skipshoes = wear_suit.flags_inv & HIDESHOES - - if(head) - skipmask = head.flags_inv & HIDEMASK - skipeyes = head.flags_inv & HIDEEYES - skipears = head.flags_inv & HIDEEARS - skipface = head.flags_inv & HIDEFACE - - if(wear_mask) - skipface |= wear_mask.flags_inv & HIDEFACE - var/msg = "*---------*\nThis is " - - if(!(skipjumpsuit && skipface) && icon) //big suits/masks/helmets make it hard to tell their gender - msg += "[bicon(icon(icon, dir=SOUTH))] " //fucking BYOND: this should stop dreamseeker crashing if we -somehow- examine somebody before their icon is generated - msg += "[name]" - - var/displayed_species = dna.species.name - var/examine_color = dna.species.flesh_color - for(var/obj/item/clothing/C in src) //Disguise checks - if(C == src.head || C == src.wear_suit || C == src.wear_mask || C == src.w_uniform || C == src.belt || C == src.back) - if(C.species_disguise) - displayed_species = C.species_disguise - if(skipjumpsuit && skipface || (NO_EXAMINE in dna.species.species_traits)) //either obscured or on the nospecies list - msg += "!\n" //omit the species when examining - else if(displayed_species == "Slime People") //snowflakey because Slime People are defined as a plural - msg += ", a slime person!\n" - else if(displayed_species == "Unathi") //DAMN YOU, VOWELS - msg += ", a unathi!\n" - else - msg += ", a [lowertext(displayed_species)]!\n" - - //uniform - if(w_uniform && !skipjumpsuit && !(w_uniform.flags & ABSTRACT)) - //Ties - var/tie_msg - if(istype(w_uniform,/obj/item/clothing/under)) - var/obj/item/clothing/under/U = w_uniform - if(U.accessories.len) - tie_msg += " with [english_accessory_list(U)]" - - if(w_uniform.blood_DNA) - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(w_uniform)] [w_uniform.gender==PLURAL?"some":"a"] [w_uniform.blood_color != "#030303" ? "blood-stained":"oil-stained"] [w_uniform.name][tie_msg]!\n" - else - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(w_uniform)] \a [w_uniform][tie_msg].\n" - - //head - if(head && !(head.flags & ABSTRACT)) - if(head.blood_DNA) - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(head)] [head.gender==PLURAL?"some":"a"] [head.blood_color != "#030303" ? "blood-stained":"oil-stained"] [head.name] on [p_their()] head!\n" - else - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(head)] \a [head] on [p_their()] head.\n" - - //suit/armour - if(wear_suit && !(wear_suit.flags & ABSTRACT)) - if(wear_suit.blood_DNA) - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(wear_suit)] [wear_suit.gender==PLURAL?"some":"a"] [wear_suit.blood_color != "#030303" ? "blood-stained":"oil-stained"] [wear_suit.name]!\n" - else - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(wear_suit)] \a [wear_suit].\n" - - //suit/armour storage - if(s_store && !skipsuitstorage) - if(s_store.blood_DNA) - msg += "[p_they(TRUE)] [p_are()] carrying [bicon(s_store)] [s_store.gender==PLURAL?"some":"a"] [s_store.blood_color != "#030303" ? "blood-stained":"oil-stained"] [s_store.name] on [p_their()] [wear_suit.name]!\n" - else - msg += "[p_they(TRUE)] [p_are()] carrying [bicon(s_store)] \a [s_store] on [p_their()] [wear_suit.name].\n" - - //back - if(back && !(back.flags & ABSTRACT)) - if(back.blood_DNA) - msg += "[p_they(TRUE)] [p_have()] [bicon(back)] [back.gender==PLURAL?"some":"a"] [back.blood_color != "#030303" ? "blood-stained":"oil-stained"] [back] on [p_their()] back.\n" - else - msg += "[p_they(TRUE)] [p_have()] [bicon(back)] \a [back] on [p_their()] back.\n" - - //left hand - if(l_hand && !(l_hand.flags & ABSTRACT)) - if(l_hand.blood_DNA) - msg += "[p_they(TRUE)] [p_are()] holding [bicon(l_hand)] [l_hand.gender==PLURAL?"some":"a"] [l_hand.blood_color != "#030303" ? "blood-stained":"oil-stained"] [l_hand.name] in [p_their()] left hand!\n" - else - msg += "[p_they(TRUE)] [p_are()] holding [bicon(l_hand)] \a [l_hand] in [p_their()] left hand.\n" - - //right hand - if(r_hand && !(r_hand.flags & ABSTRACT)) - if(r_hand.blood_DNA) - msg += "[p_they(TRUE)] [p_are()] holding [bicon(r_hand)] [r_hand.gender==PLURAL?"some":"a"] [r_hand.blood_color != "#030303" ? "blood-stained":"oil-stained"] [r_hand.name] in [p_their()] right hand!\n" - else - msg += "[p_they(TRUE)] [p_are()] holding [bicon(r_hand)] \a [r_hand] in [p_their()] right hand.\n" - - //gloves - if(gloves && !skipgloves && !(gloves.flags & ABSTRACT)) - if(gloves.blood_DNA) - msg += "[p_they(TRUE)] [p_have()] [bicon(gloves)] [gloves.gender==PLURAL?"some":"a"] [gloves.blood_color != "#030303" ? "blood-stained":"oil-stained"] [gloves.name] on [p_their()] hands!\n" - else - msg += "[p_they(TRUE)] [p_have()] [bicon(gloves)] \a [gloves] on [p_their()] hands.\n" - else if(blood_DNA) - msg += "[p_they(TRUE)] [p_have()] [hand_blood_color != "#030303" ? "blood-stained":"oil-stained"] hands!\n" - - //handcuffed? - if(handcuffed) - if(istype(handcuffed, /obj/item/restraints/handcuffs/cable/zipties)) - msg += "[p_they(TRUE)] [p_are()] [bicon(handcuffed)] restrained with zipties!\n" - else if(istype(handcuffed, /obj/item/restraints/handcuffs/cable)) - msg += "[p_they(TRUE)] [p_are()] [bicon(handcuffed)] restrained with cable!\n" - else - msg += "[p_they(TRUE)] [p_are()] [bicon(handcuffed)] handcuffed!\n" - - //belt - if(belt) - if(belt.blood_DNA) - msg += "[p_they(TRUE)] [p_have()] [bicon(belt)] [belt.gender==PLURAL?"some":"a"] [belt.blood_color != "#030303" ? "blood-stained":"oil-stained"] [belt.name] about [p_their()] waist!\n" - else - msg += "[p_they(TRUE)] [p_have()] [bicon(belt)] \a [belt] about [p_their()] waist.\n" - - //shoes - if(shoes && !skipshoes && !(shoes.flags & ABSTRACT)) - if(shoes.blood_DNA) - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(shoes)] [shoes.gender==PLURAL?"some":"a"] [shoes.blood_color != "#030303" ? "blood-stained":"oil-stained"] [shoes.name] on [p_their()] feet!\n" - else - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(shoes)] \a [shoes] on [p_their()] feet.\n" - else if(blood_DNA) - msg += "[p_they(TRUE)] [p_have()] [feet_blood_color != "#030303" ? "blood-stained":"oil-stained"] feet!\n" - - - //mask - if(wear_mask && !skipmask && !(wear_mask.flags & ABSTRACT)) - if(wear_mask.blood_DNA) - msg += "[p_they(TRUE)] [p_have()] [bicon(wear_mask)] [wear_mask.gender==PLURAL?"some":"a"] [wear_mask.blood_color != "#030303" ? "blood-stained":"oil-stained"] [wear_mask.name] on [p_their()] face!\n" - else - msg += "[p_they(TRUE)] [p_have()] [bicon(wear_mask)] \a [wear_mask] on [p_their()] face.\n" - - //eyes - if(glasses && !skipeyes && !(glasses.flags & ABSTRACT)) - if(glasses.blood_DNA) - msg += "[p_they(TRUE)] [p_have()] [bicon(glasses)] [glasses.gender==PLURAL?"some":"a"] [glasses.blood_color != "#030303" ? "blood-stained":"oil-stained"] [glasses] covering [p_their()] eyes!\n" - else - msg += "[p_they(TRUE)] [p_have()] [bicon(glasses)] \a [glasses] covering [p_their()] eyes.\n" - - //left ear - if(l_ear && !skipears) - msg += "[p_they(TRUE)] [p_have()] [bicon(l_ear)] \a [l_ear] on [p_their()] left ear.\n" - - //right ear - if(r_ear && !skipears) - msg += "[p_they(TRUE)] [p_have()] [bicon(r_ear)] \a [r_ear] on [p_their()] right ear.\n" - - //ID - if(wear_id) - msg += "[p_they(TRUE)] [p_are()] wearing [bicon(wear_id)] \a [wear_id].\n" - - //Jitters - switch(jitteriness) - if(300 to INFINITY) - msg += "[p_they(TRUE)] [p_are()] convulsing violently!\n" - if(200 to 300) - msg += "[p_they(TRUE)] [p_are()] extremely jittery.\n" - if(100 to 200) - msg += "[p_they(TRUE)] [p_are()] twitching ever so slightly.\n" - - - var/appears_dead = FALSE - if(stat == DEAD || (status_flags & FAKEDEATH)) - appears_dead = TRUE - if(suiciding) - msg += "[p_they(TRUE)] appear[p_s()] to have committed suicide... there is no hope of recovery.\n" - msg += "[p_they(TRUE)] [p_are()] limp and unresponsive; there are no signs of life" - if(get_int_organ(/obj/item/organ/internal/brain)) - if(!key) - var/foundghost = FALSE - if(mind) - for(var/mob/dead/observer/G in GLOB.player_list) - if(G.mind == mind) - foundghost = TRUE - if(G.can_reenter_corpse == 0) - foundghost = FALSE - break - if(!foundghost) - msg += " and [p_their()] soul has departed" - msg += "...\n" - - if(!get_int_organ(/obj/item/organ/internal/brain)) - msg += "It appears that [p_their()] brain is missing...\n" - - msg += "" - - var/list/wound_flavor_text = list() - var/list/is_destroyed = list() - for(var/organ_tag in dna.species.has_limbs) - - var/list/organ_data = dna.species.has_limbs[organ_tag] - var/organ_descriptor = organ_data["descriptor"] - is_destroyed["[organ_data["descriptor"]]"] = 1 - - var/obj/item/organ/external/E = bodyparts_by_name[organ_tag] - if(!E) - wound_flavor_text["[organ_tag]"] = "[p_they(TRUE)] [p_are()] missing [p_their()] [organ_descriptor].\n" - else - if(!isSynthetic()) - if(E.is_robotic()) - wound_flavor_text["[E.limb_name]"] = "[p_they(TRUE)] [p_have()] a robotic [E.name]!\n" - - else if(E.status & ORGAN_SPLINTED) - wound_flavor_text["[E.limb_name]"] = "[p_they(TRUE)] [p_have()] a splint on [p_their()] [E.name]!\n" - - if(E.open) - if(E.is_robotic()) - msg += "The maintenance hatch on [p_their()] [ignore_limb_branding(E.limb_name)] is open!\n" - else - msg += "[p_their(TRUE)] [ignore_limb_branding(E.limb_name)] has an open incision!\n" - - for(var/obj/item/I in E.embedded_objects) - msg += "[p_they(TRUE)] [p_have()] \a [bicon(I)] [I] embedded in [p_their()] [E.name]!\n" - - //Handles the text strings being added to the actual description. - //If they have something that covers the limb, and it is not missing, put flavortext. If it is covered but bleeding, add other flavortext. - if(wound_flavor_text["head"] && (is_destroyed["head"] || (!skipmask && !(wear_mask && istype(wear_mask, /obj/item/clothing/mask/gas))))) - msg += wound_flavor_text["head"] - if(wound_flavor_text["chest"] && !w_uniform && !skipjumpsuit) //No need. A missing chest gibs you. - msg += wound_flavor_text["chest"] - if(wound_flavor_text["l_arm"] && (is_destroyed["left arm"] || (!w_uniform && !skipjumpsuit))) - msg += wound_flavor_text["l_arm"] - if(wound_flavor_text["l_hand"] && (is_destroyed["left hand"] || (!gloves && !skipgloves))) - msg += wound_flavor_text["l_hand"] - if(wound_flavor_text["r_arm"] && (is_destroyed["right arm"] || (!w_uniform && !skipjumpsuit))) - msg += wound_flavor_text["r_arm"] - if(wound_flavor_text["r_hand"] && (is_destroyed["right hand"] || (!gloves && !skipgloves))) - msg += wound_flavor_text["r_hand"] - if(wound_flavor_text["groin"] && (is_destroyed["groin"] || (!w_uniform && !skipjumpsuit))) - msg += wound_flavor_text["groin"] - if(wound_flavor_text["l_leg"] && (is_destroyed["left leg"] || (!w_uniform && !skipjumpsuit))) - msg += wound_flavor_text["l_leg"] - if(wound_flavor_text["l_foot"]&& (is_destroyed["left foot"] || (!shoes && !skipshoes))) - msg += wound_flavor_text["l_foot"] - if(wound_flavor_text["r_leg"] && (is_destroyed["right leg"] || (!w_uniform && !skipjumpsuit))) - msg += wound_flavor_text["r_leg"] - if(wound_flavor_text["r_foot"]&& (is_destroyed["right foot"] || (!shoes && !skipshoes))) - msg += wound_flavor_text["r_foot"] - - var/temp = getBruteLoss() //no need to calculate each of these twice - - if(temp) - var/brute_message = !isSynthetic() ? "bruising" : "denting" - if(temp < 30) - msg += "[p_they(TRUE)] [p_have()] minor [brute_message ].\n" - else - msg += "[p_they(TRUE)] [p_have()] severe [brute_message ]!\n" - - temp = getFireLoss() - if(temp) - if(temp < 30) - msg += "[p_they(TRUE)] [p_have()] minor burns.\n" - else - msg += "[p_they(TRUE)] [p_have()] severe burns!\n" - - temp = getCloneLoss() - if(temp) - if(temp < 30) - msg += "[p_they(TRUE)] [p_have()] minor cellular damage.\n" - else - msg += "[p_they(TRUE)] [p_have()] severe cellular damage.\n" - - - if(fire_stacks > 0) - msg += "[p_they(TRUE)] [p_are()] covered in something flammable.\n" - if(fire_stacks < 0) - msg += "[p_they(TRUE)] looks a little soaked.\n" - - switch(wetlevel) - if(1) - msg += "[p_they(TRUE)] looks a bit damp.\n" - if(2) - msg += "[p_they(TRUE)] looks a little bit wet.\n" - if(3) - msg += "[p_they(TRUE)] looks wet.\n" - if(4) - msg += "[p_they(TRUE)] looks very wet.\n" - if(5) - msg += "[p_they(TRUE)] looks absolutely soaked.\n" - - if(nutrition < NUTRITION_LEVEL_STARVING - 50) - msg += "[p_they(TRUE)] [p_are()] severely malnourished.\n" - else if(nutrition >= NUTRITION_LEVEL_FAT) - if(user.nutrition < NUTRITION_LEVEL_STARVING - 50) - msg += "[p_they(TRUE)] [p_are()] plump and delicious looking - Like a fat little piggy. A tasty piggy.\n" - else - msg += "[p_they(TRUE)] [p_are()] quite chubby.\n" - - if(!isSynthetic() && blood_volume < BLOOD_VOLUME_SAFE) - msg += "[p_they(TRUE)] [p_have()] pale skin.\n" - - if(bleedsuppress) - msg += "[p_they(TRUE)] [p_are()] bandaged with something.\n" - else if(bleed_rate) - var/bleed_message = !isSynthetic() ? "bleeding" : "leaking" - msg += "[p_they(TRUE)] [p_are()] [bleed_message]!\n" - - if(reagents.has_reagent("teslium")) - msg += "[p_they(TRUE)] [p_are()] emitting a gentle blue glow!\n" - - msg += "" - - if(!appears_dead) - if(stat == UNCONSCIOUS) - msg += "[p_they(TRUE)] [p_are()]n't responding to anything around [p_them()] and seems to be asleep.\n" - else if(getBrainLoss() >= 60) - msg += "[p_they(TRUE)] [p_have()] a stupid expression on [p_their()] face.\n" - - if(get_int_organ(/obj/item/organ/internal/brain)) - if(dna.species.show_ssd) - if(!key) - msg += "[p_they(TRUE)] [p_are()] totally catatonic. The stresses of life in deep-space must have been too much for [p_them()]. Any recovery is unlikely.\n" - else if(!client) - msg += "[p_they(TRUE)] [p_have()] suddenly fallen asleep, suffering from Space Sleep Disorder. [p_they(TRUE)] may wake up soon.\n" - - if(digitalcamo) - msg += "[p_they(TRUE)] [p_are()] moving [p_their()] body in an unnatural and blatantly inhuman manner.\n" - - if(!(skipface || ( wear_mask && ( wear_mask.flags_inv & HIDEFACE || wear_mask.flags_cover & MASKCOVERSMOUTH) ) ) && is_thrall(src) && in_range(user,src)) - msg += "Their features seem unnaturally tight and drawn.\n" - - if(decaylevel == 1) - msg += "[p_they(TRUE)] [p_are()] starting to smell.\n" - if(decaylevel == 2) - msg += "[p_they(TRUE)] [p_are()] bloated and smells disgusting.\n" - if(decaylevel == 3) - msg += "[p_they(TRUE)] [p_are()] rotting and blackened, the skin sloughing off. The smell is indescribably foul.\n" - if(decaylevel == 4) - msg += "[p_they(TRUE)] [p_are()] mostly dessicated now, with only bones remaining of what used to be a person.\n" - - if(hasHUD(user,"security")) - var/perpname = get_visible_name(TRUE) - var/criminal = "None" - - if(perpname) - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.security) - if(R.fields["id"] == E.fields["id"]) - criminal = R.fields["criminal"] - var/criminal_status = hasHUD(user, "read_only_security") ? "\[[criminal]\]" : "\[[criminal]\]" - msg += "Criminal status: [criminal_status]\n" - msg += "Security records: \[View\] \[Add comment\]\n" - - if(hasHUD(user,"medical")) - var/perpname = get_visible_name(TRUE) - var/medical = "None" - - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.general) - if(R.fields["id"] == E.fields["id"]) - medical = R.fields["p_stat"] - - msg += "Physical status: \[[medical]\]\n" - msg += "Medical records: \[View\] \[Add comment\]\n" - - - if(print_flavor_text() && !skipface) - msg += "[print_flavor_text()]\n" - - msg += "*---------*" - if(pose) - if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) - pose = addtext(pose,".") //Makes sure all emotes end with a period. - msg += "\n[p_they(TRUE)] [p_are()] [pose]" - - . = list(msg) - -//Helper procedure. Called by /mob/living/carbon/human/examine() and /mob/living/carbon/human/Topic() to determine HUD access to security and medical records. -/proc/hasHUD(mob/M as mob, hudtype) - if(istype(M, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/internal/cyberimp/eyes/hud/CIH = H.get_int_organ(/obj/item/organ/internal/cyberimp/eyes/hud) - switch(hudtype) - if("security") - return istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(CIH,/obj/item/organ/internal/cyberimp/eyes/hud/security) - if("read_only_security") - var/obj/item/clothing/glasses/hud/security/S - if(istype(H.glasses, /obj/item/clothing/glasses/hud/security)) - S = H.glasses - return !istype(CIH,/obj/item/organ/internal/cyberimp/eyes/hud/security) && S && S.read_only - if("medical") - return istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(CIH,/obj/item/organ/internal/cyberimp/eyes/hud/medical) - else - return 0 - else if(isrobot(M) || isAI(M)) //Stand-in/Stopgap to prevent pAIs from freely altering records, pending a more advanced Records system - switch(hudtype) - if("security") - return 1 - if("medical") - return 1 - else - return 0 - else - return 0 - -// Ignores robotic limb branding prefixes like "Morpheus Cybernetics" -/proc/ignore_limb_branding(limb_name) - switch(limb_name) - if("chest") - . = "upper body" - if("groin") - . = "lower body" - if("head") - . = "head" - if("l_arm") - . = "left arm" - if("r_arm") - . = "right arm" - if("l_leg") - . = "left leg" - if("r_leg") - . = "right leg" - if("l_foot") - . = "left foot" - if("r_foot") - . = "right foot" - if("l_hand") - . = "left hand" - if("r_hand") - . = "right hand" +/mob/living/carbon/human/examine(mob/user) + var/skipgloves = 0 + var/skipsuitstorage = 0 + var/skipjumpsuit = 0 + var/skipshoes = 0 + var/skipmask = 0 + var/skipears = 0 + var/skipeyes = 0 + var/skipface = 0 + + //exosuits and helmets obscure our view and stuff. + if(wear_suit) + skipgloves = wear_suit.flags_inv & HIDEGLOVES + skipsuitstorage = wear_suit.flags_inv & HIDESUITSTORAGE + skipjumpsuit = wear_suit.flags_inv & HIDEJUMPSUIT + skipshoes = wear_suit.flags_inv & HIDESHOES + + if(head) + skipmask = head.flags_inv & HIDEMASK + skipeyes = head.flags_inv & HIDEEYES + skipears = head.flags_inv & HIDEEARS + skipface = head.flags_inv & HIDEFACE + + if(wear_mask) + skipface |= wear_mask.flags_inv & HIDEFACE + var/msg = "*---------*\nThis is " + + if(!(skipjumpsuit && skipface) && icon) //big suits/masks/helmets make it hard to tell their gender + msg += "[bicon(icon(icon, dir=SOUTH))] " //fucking BYOND: this should stop dreamseeker crashing if we -somehow- examine somebody before their icon is generated + msg += "[name]" + + var/displayed_species = dna.species.name + var/examine_color = dna.species.flesh_color + for(var/obj/item/clothing/C in src) //Disguise checks + if(C == src.head || C == src.wear_suit || C == src.wear_mask || C == src.w_uniform || C == src.belt || C == src.back) + if(C.species_disguise) + displayed_species = C.species_disguise + if(skipjumpsuit && skipface || (NO_EXAMINE in dna.species.species_traits)) //either obscured or on the nospecies list + msg += "!\n" //omit the species when examining + else if(displayed_species == "Slime People") //snowflakey because Slime People are defined as a plural + msg += ", a slime person!\n" + else if(displayed_species == "Unathi") //DAMN YOU, VOWELS + msg += ", a unathi!\n" + else + msg += ", a [lowertext(displayed_species)]!\n" + + //uniform + if(w_uniform && !skipjumpsuit && !(w_uniform.flags & ABSTRACT)) + //Ties + var/tie_msg + if(istype(w_uniform,/obj/item/clothing/under)) + var/obj/item/clothing/under/U = w_uniform + if(U.accessories.len) + tie_msg += " with [english_accessory_list(U)]" + + if(w_uniform.blood_DNA) + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(w_uniform)] [w_uniform.gender==PLURAL?"some":"a"] [w_uniform.blood_color != "#030303" ? "blood-stained":"oil-stained"] [w_uniform.name][tie_msg]!\n" + else + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(w_uniform)] \a [w_uniform][tie_msg].\n" + + //head + if(head && !(head.flags & ABSTRACT)) + if(head.blood_DNA) + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(head)] [head.gender==PLURAL?"some":"a"] [head.blood_color != "#030303" ? "blood-stained":"oil-stained"] [head.name] on [p_their()] head!\n" + else + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(head)] \a [head] on [p_their()] head.\n" + + //suit/armour + if(wear_suit && !(wear_suit.flags & ABSTRACT)) + if(wear_suit.blood_DNA) + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(wear_suit)] [wear_suit.gender==PLURAL?"some":"a"] [wear_suit.blood_color != "#030303" ? "blood-stained":"oil-stained"] [wear_suit.name]!\n" + else + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(wear_suit)] \a [wear_suit].\n" + + //suit/armour storage + if(s_store && !skipsuitstorage) + if(s_store.blood_DNA) + msg += "[p_they(TRUE)] [p_are()] carrying [bicon(s_store)] [s_store.gender==PLURAL?"some":"a"] [s_store.blood_color != "#030303" ? "blood-stained":"oil-stained"] [s_store.name] on [p_their()] [wear_suit.name]!\n" + else + msg += "[p_they(TRUE)] [p_are()] carrying [bicon(s_store)] \a [s_store] on [p_their()] [wear_suit.name].\n" + + //back + if(back && !(back.flags & ABSTRACT)) + if(back.blood_DNA) + msg += "[p_they(TRUE)] [p_have()] [bicon(back)] [back.gender==PLURAL?"some":"a"] [back.blood_color != "#030303" ? "blood-stained":"oil-stained"] [back] on [p_their()] back.\n" + else + msg += "[p_they(TRUE)] [p_have()] [bicon(back)] \a [back] on [p_their()] back.\n" + + //left hand + if(l_hand && !(l_hand.flags & ABSTRACT)) + if(l_hand.blood_DNA) + msg += "[p_they(TRUE)] [p_are()] holding [bicon(l_hand)] [l_hand.gender==PLURAL?"some":"a"] [l_hand.blood_color != "#030303" ? "blood-stained":"oil-stained"] [l_hand.name] in [p_their()] left hand!\n" + else + msg += "[p_they(TRUE)] [p_are()] holding [bicon(l_hand)] \a [l_hand] in [p_their()] left hand.\n" + + //right hand + if(r_hand && !(r_hand.flags & ABSTRACT)) + if(r_hand.blood_DNA) + msg += "[p_they(TRUE)] [p_are()] holding [bicon(r_hand)] [r_hand.gender==PLURAL?"some":"a"] [r_hand.blood_color != "#030303" ? "blood-stained":"oil-stained"] [r_hand.name] in [p_their()] right hand!\n" + else + msg += "[p_they(TRUE)] [p_are()] holding [bicon(r_hand)] \a [r_hand] in [p_their()] right hand.\n" + + //gloves + if(gloves && !skipgloves && !(gloves.flags & ABSTRACT)) + if(gloves.blood_DNA) + msg += "[p_they(TRUE)] [p_have()] [bicon(gloves)] [gloves.gender==PLURAL?"some":"a"] [gloves.blood_color != "#030303" ? "blood-stained":"oil-stained"] [gloves.name] on [p_their()] hands!\n" + else + msg += "[p_they(TRUE)] [p_have()] [bicon(gloves)] \a [gloves] on [p_their()] hands.\n" + else if(blood_DNA) + msg += "[p_they(TRUE)] [p_have()] [hand_blood_color != "#030303" ? "blood-stained":"oil-stained"] hands!\n" + + //handcuffed? + if(handcuffed) + if(istype(handcuffed, /obj/item/restraints/handcuffs/cable/zipties)) + msg += "[p_they(TRUE)] [p_are()] [bicon(handcuffed)] restrained with zipties!\n" + else if(istype(handcuffed, /obj/item/restraints/handcuffs/cable)) + msg += "[p_they(TRUE)] [p_are()] [bicon(handcuffed)] restrained with cable!\n" + else + msg += "[p_they(TRUE)] [p_are()] [bicon(handcuffed)] handcuffed!\n" + + //belt + if(belt) + if(belt.blood_DNA) + msg += "[p_they(TRUE)] [p_have()] [bicon(belt)] [belt.gender==PLURAL?"some":"a"] [belt.blood_color != "#030303" ? "blood-stained":"oil-stained"] [belt.name] about [p_their()] waist!\n" + else + msg += "[p_they(TRUE)] [p_have()] [bicon(belt)] \a [belt] about [p_their()] waist.\n" + + //shoes + if(shoes && !skipshoes && !(shoes.flags & ABSTRACT)) + if(shoes.blood_DNA) + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(shoes)] [shoes.gender==PLURAL?"some":"a"] [shoes.blood_color != "#030303" ? "blood-stained":"oil-stained"] [shoes.name] on [p_their()] feet!\n" + else + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(shoes)] \a [shoes] on [p_their()] feet.\n" + else if(blood_DNA) + msg += "[p_they(TRUE)] [p_have()] [feet_blood_color != "#030303" ? "blood-stained":"oil-stained"] feet!\n" + + + //mask + if(wear_mask && !skipmask && !(wear_mask.flags & ABSTRACT)) + if(wear_mask.blood_DNA) + msg += "[p_they(TRUE)] [p_have()] [bicon(wear_mask)] [wear_mask.gender==PLURAL?"some":"a"] [wear_mask.blood_color != "#030303" ? "blood-stained":"oil-stained"] [wear_mask.name] on [p_their()] face!\n" + else + msg += "[p_they(TRUE)] [p_have()] [bicon(wear_mask)] \a [wear_mask] on [p_their()] face.\n" + + //eyes + if(glasses && !skipeyes && !(glasses.flags & ABSTRACT)) + if(glasses.blood_DNA) + msg += "[p_they(TRUE)] [p_have()] [bicon(glasses)] [glasses.gender==PLURAL?"some":"a"] [glasses.blood_color != "#030303" ? "blood-stained":"oil-stained"] [glasses] covering [p_their()] eyes!\n" + else + msg += "[p_they(TRUE)] [p_have()] [bicon(glasses)] \a [glasses] covering [p_their()] eyes.\n" + + //left ear + if(l_ear && !skipears) + msg += "[p_they(TRUE)] [p_have()] [bicon(l_ear)] \a [l_ear] on [p_their()] left ear.\n" + + //right ear + if(r_ear && !skipears) + msg += "[p_they(TRUE)] [p_have()] [bicon(r_ear)] \a [r_ear] on [p_their()] right ear.\n" + + //ID + if(wear_id) + msg += "[p_they(TRUE)] [p_are()] wearing [bicon(wear_id)] \a [wear_id].\n" + + //Jitters + switch(jitteriness) + if(300 to INFINITY) + msg += "[p_they(TRUE)] [p_are()] convulsing violently!\n" + if(200 to 300) + msg += "[p_they(TRUE)] [p_are()] extremely jittery.\n" + if(100 to 200) + msg += "[p_they(TRUE)] [p_are()] twitching ever so slightly.\n" + + + var/appears_dead = FALSE + if(stat == DEAD || (status_flags & FAKEDEATH)) + appears_dead = TRUE + if(suiciding) + msg += "[p_they(TRUE)] appear[p_s()] to have committed suicide... there is no hope of recovery.\n" + msg += "[p_they(TRUE)] [p_are()] limp and unresponsive; there are no signs of life" + if(get_int_organ(/obj/item/organ/internal/brain)) + if(!key) + var/foundghost = FALSE + if(mind) + for(var/mob/dead/observer/G in GLOB.player_list) + if(G.mind == mind) + foundghost = TRUE + if(G.can_reenter_corpse == 0) + foundghost = FALSE + break + if(!foundghost) + msg += " and [p_their()] soul has departed" + msg += "...\n" + + if(!get_int_organ(/obj/item/organ/internal/brain)) + msg += "It appears that [p_their()] brain is missing...\n" + + msg += "" + + var/list/wound_flavor_text = list() + var/list/is_destroyed = list() + for(var/organ_tag in dna.species.has_limbs) + + var/list/organ_data = dna.species.has_limbs[organ_tag] + var/organ_descriptor = organ_data["descriptor"] + is_destroyed["[organ_data["descriptor"]]"] = 1 + + var/obj/item/organ/external/E = bodyparts_by_name[organ_tag] + if(!E) + wound_flavor_text["[organ_tag]"] = "[p_they(TRUE)] [p_are()] missing [p_their()] [organ_descriptor].\n" + else + if(!isSynthetic()) + if(E.is_robotic()) + wound_flavor_text["[E.limb_name]"] = "[p_they(TRUE)] [p_have()] a robotic [E.name]!\n" + + else if(E.status & ORGAN_SPLINTED) + wound_flavor_text["[E.limb_name]"] = "[p_they(TRUE)] [p_have()] a splint on [p_their()] [E.name]!\n" + + if(E.open) + if(E.is_robotic()) + msg += "The maintenance hatch on [p_their()] [ignore_limb_branding(E.limb_name)] is open!\n" + else + msg += "[p_their(TRUE)] [ignore_limb_branding(E.limb_name)] has an open incision!\n" + + for(var/obj/item/I in E.embedded_objects) + msg += "[p_they(TRUE)] [p_have()] \a [bicon(I)] [I] embedded in [p_their()] [E.name]!\n" + + //Handles the text strings being added to the actual description. + //If they have something that covers the limb, and it is not missing, put flavortext. If it is covered but bleeding, add other flavortext. + if(wound_flavor_text["head"] && (is_destroyed["head"] || (!skipmask && !(wear_mask && istype(wear_mask, /obj/item/clothing/mask/gas))))) + msg += wound_flavor_text["head"] + if(wound_flavor_text["chest"] && !w_uniform && !skipjumpsuit) //No need. A missing chest gibs you. + msg += wound_flavor_text["chest"] + if(wound_flavor_text["l_arm"] && (is_destroyed["left arm"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["l_arm"] + if(wound_flavor_text["l_hand"] && (is_destroyed["left hand"] || (!gloves && !skipgloves))) + msg += wound_flavor_text["l_hand"] + if(wound_flavor_text["r_arm"] && (is_destroyed["right arm"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["r_arm"] + if(wound_flavor_text["r_hand"] && (is_destroyed["right hand"] || (!gloves && !skipgloves))) + msg += wound_flavor_text["r_hand"] + if(wound_flavor_text["groin"] && (is_destroyed["groin"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["groin"] + if(wound_flavor_text["l_leg"] && (is_destroyed["left leg"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["l_leg"] + if(wound_flavor_text["l_foot"]&& (is_destroyed["left foot"] || (!shoes && !skipshoes))) + msg += wound_flavor_text["l_foot"] + if(wound_flavor_text["r_leg"] && (is_destroyed["right leg"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["r_leg"] + if(wound_flavor_text["r_foot"]&& (is_destroyed["right foot"] || (!shoes && !skipshoes))) + msg += wound_flavor_text["r_foot"] + + var/temp = getBruteLoss() //no need to calculate each of these twice + + if(temp) + var/brute_message = !isSynthetic() ? "bruising" : "denting" + if(temp < 30) + msg += "[p_they(TRUE)] [p_have()] minor [brute_message ].\n" + else + msg += "[p_they(TRUE)] [p_have()] severe [brute_message ]!\n" + + temp = getFireLoss() + if(temp) + if(temp < 30) + msg += "[p_they(TRUE)] [p_have()] minor burns.\n" + else + msg += "[p_they(TRUE)] [p_have()] severe burns!\n" + + temp = getCloneLoss() + if(temp) + if(temp < 30) + msg += "[p_they(TRUE)] [p_have()] minor cellular damage.\n" + else + msg += "[p_they(TRUE)] [p_have()] severe cellular damage.\n" + + + if(fire_stacks > 0) + msg += "[p_they(TRUE)] [p_are()] covered in something flammable.\n" + if(fire_stacks < 0) + msg += "[p_they(TRUE)] looks a little soaked.\n" + + switch(wetlevel) + if(1) + msg += "[p_they(TRUE)] looks a bit damp.\n" + if(2) + msg += "[p_they(TRUE)] looks a little bit wet.\n" + if(3) + msg += "[p_they(TRUE)] looks wet.\n" + if(4) + msg += "[p_they(TRUE)] looks very wet.\n" + if(5) + msg += "[p_they(TRUE)] looks absolutely soaked.\n" + + if(nutrition < NUTRITION_LEVEL_STARVING - 50) + msg += "[p_they(TRUE)] [p_are()] severely malnourished.\n" + else if(nutrition >= NUTRITION_LEVEL_FAT) + if(user.nutrition < NUTRITION_LEVEL_STARVING - 50) + msg += "[p_they(TRUE)] [p_are()] plump and delicious looking - Like a fat little piggy. A tasty piggy.\n" + else + msg += "[p_they(TRUE)] [p_are()] quite chubby.\n" + + if(!isSynthetic() && blood_volume < BLOOD_VOLUME_SAFE) + msg += "[p_they(TRUE)] [p_have()] pale skin.\n" + + if(bleedsuppress) + msg += "[p_they(TRUE)] [p_are()] bandaged with something.\n" + else if(bleed_rate) + var/bleed_message = !isSynthetic() ? "bleeding" : "leaking" + msg += "[p_they(TRUE)] [p_are()] [bleed_message]!\n" + + if(reagents.has_reagent("teslium")) + msg += "[p_they(TRUE)] [p_are()] emitting a gentle blue glow!\n" + + msg += "" + + if(!appears_dead) + if(stat == UNCONSCIOUS) + msg += "[p_they(TRUE)] [p_are()]n't responding to anything around [p_them()] and seems to be asleep.\n" + else if(getBrainLoss() >= 60) + msg += "[p_they(TRUE)] [p_have()] a stupid expression on [p_their()] face.\n" + + if(get_int_organ(/obj/item/organ/internal/brain)) + if(dna.species.show_ssd) + if(!key) + msg += "[p_they(TRUE)] [p_are()] totally catatonic. The stresses of life in deep-space must have been too much for [p_them()]. Any recovery is unlikely.\n" + else if(!client) + msg += "[p_they(TRUE)] [p_have()] suddenly fallen asleep, suffering from Space Sleep Disorder. [p_they(TRUE)] may wake up soon.\n" + + if(digitalcamo) + msg += "[p_they(TRUE)] [p_are()] moving [p_their()] body in an unnatural and blatantly inhuman manner.\n" + + if(!(skipface || ( wear_mask && ( wear_mask.flags_inv & HIDEFACE || wear_mask.flags_cover & MASKCOVERSMOUTH) ) ) && is_thrall(src) && in_range(user,src)) + msg += "Their features seem unnaturally tight and drawn.\n" + + if(decaylevel == 1) + msg += "[p_they(TRUE)] [p_are()] starting to smell.\n" + if(decaylevel == 2) + msg += "[p_they(TRUE)] [p_are()] bloated and smells disgusting.\n" + if(decaylevel == 3) + msg += "[p_they(TRUE)] [p_are()] rotting and blackened, the skin sloughing off. The smell is indescribably foul.\n" + if(decaylevel == 4) + msg += "[p_they(TRUE)] [p_are()] mostly dessicated now, with only bones remaining of what used to be a person.\n" + + if(hasHUD(user,"security")) + var/perpname = get_visible_name(TRUE) + var/criminal = "None" + + if(perpname) + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.security) + if(R.fields["id"] == E.fields["id"]) + criminal = R.fields["criminal"] + var/criminal_status = hasHUD(user, "read_only_security") ? "\[[criminal]\]" : "\[[criminal]\]" + msg += "Criminal status: [criminal_status]\n" + msg += "Security records: \[View\] \[Add comment\]\n" + + if(hasHUD(user,"medical")) + var/perpname = get_visible_name(TRUE) + var/medical = "None" + + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.general) + if(R.fields["id"] == E.fields["id"]) + medical = R.fields["p_stat"] + + msg += "Physical status: \[[medical]\]\n" + msg += "Medical records: \[View\] \[Add comment\]\n" + + + if(print_flavor_text() && !skipface) + msg += "[print_flavor_text()]\n" + + msg += "*---------*" + if(pose) + if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) + pose = addtext(pose,".") //Makes sure all emotes end with a period. + msg += "\n[p_they(TRUE)] [p_are()] [pose]" + + . = list(msg) + +//Helper procedure. Called by /mob/living/carbon/human/examine() and /mob/living/carbon/human/Topic() to determine HUD access to security and medical records. +/proc/hasHUD(mob/M as mob, hudtype) + if(istype(M, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/internal/cyberimp/eyes/hud/CIH = H.get_int_organ(/obj/item/organ/internal/cyberimp/eyes/hud) + switch(hudtype) + if("security") + return istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(CIH,/obj/item/organ/internal/cyberimp/eyes/hud/security) + if("read_only_security") + var/obj/item/clothing/glasses/hud/security/S + if(istype(H.glasses, /obj/item/clothing/glasses/hud/security)) + S = H.glasses + return !istype(CIH,/obj/item/organ/internal/cyberimp/eyes/hud/security) && S && S.read_only + if("medical") + return istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(CIH,/obj/item/organ/internal/cyberimp/eyes/hud/medical) + else + return 0 + else if(isrobot(M) || isAI(M)) //Stand-in/Stopgap to prevent pAIs from freely altering records, pending a more advanced Records system + switch(hudtype) + if("security") + return 1 + if("medical") + return 1 + else + return 0 + else + return 0 + +// Ignores robotic limb branding prefixes like "Morpheus Cybernetics" +/proc/ignore_limb_branding(limb_name) + switch(limb_name) + if("chest") + . = "upper body" + if("groin") + . = "lower body" + if("head") + . = "head" + if("l_arm") + . = "left arm" + if("r_arm") + . = "right arm" + if("l_leg") + . = "left leg" + if("r_leg") + . = "right leg" + if("l_foot") + . = "left foot" + if("r_foot") + . = "right foot" + if("l_hand") + . = "left hand" + if("r_hand") + . = "right hand" diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 6540b74de58..73b40652888 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1,1964 +1,1964 @@ -/mob/living/carbon/human - name = "unknown" - real_name = "unknown" - voice_name = "unknown" - icon = 'icons/mob/human.dmi' - icon_state = "body_m_s" - deathgasp_on_death = TRUE - var/obj/item/rig/wearing_rig // This is very not good, but it's much much better than calling get_rig() every update_canmove() call. - -/mob/living/carbon/human/New(loc) - icon = null // This is now handled by overlays -- we just keep an icon for the sake of the map editor. - if(length(args) > 1) - log_runtime(EXCEPTION("human/New called with more than 1 argument (REPORT THIS ENTIRE RUNTIME TO A CODER)")) - . = ..() - -/mob/living/carbon/human/Initialize(mapload, datum/species/new_species = /datum/species/human) - if(!dna) - dna = new /datum/dna(null) - // Species name is handled by set_species() - - set_species(new_species, 1, delay_icon_update = 1, skip_same_check = TRUE) - - ..() - - if(dna.species) - real_name = dna.species.get_random_name(gender) - name = real_name - if(mind) - mind.name = real_name - - create_reagents(330) - - martial_art = default_martial_art - - handcrafting = new() - - // Set up DNA. - if(dna) - dna.ready_dna(src) - dna.real_name = real_name - sync_organ_dna(1) - - UpdateAppearance() - -/mob/living/carbon/human/OpenCraftingMenu() - handcrafting.ui_interact(src) - -/mob/living/carbon/human/prepare_data_huds() - //Update med hud images... - ..() - //...sec hud images... - sec_hud_set_ID() - sec_hud_set_implants() - sec_hud_set_security_status() - //...and display them. - add_to_all_human_data_huds() - -/mob/living/carbon/human/Destroy() - . = ..() - SSmobs.cubemonkeys -= src - QDEL_LIST(bodyparts) - splinted_limbs.Cut() - -/mob/living/carbon/human/dummy - real_name = "Test Dummy" - status_flags = GODMODE|CANPUSH - -/mob/living/carbon/human/skrell/Initialize(mapload) - ..(mapload, /datum/species/skrell) - -/mob/living/carbon/human/tajaran/Initialize(mapload) - ..(mapload, /datum/species/tajaran) - -/mob/living/carbon/human/vulpkanin/Initialize(mapload) - ..(mapload, /datum/species/vulpkanin) - -/mob/living/carbon/human/unathi/Initialize(mapload) - ..(mapload, /datum/species/unathi) - -/mob/living/carbon/human/vox/Initialize(mapload) - ..(mapload, /datum/species/vox) - -/mob/living/carbon/human/voxarmalis/Initialize(mapload) - ..(mapload, /datum/species/vox/armalis) - -/mob/living/carbon/human/skeleton/Initialize(mapload) - ..(mapload, /datum/species/skeleton) - -/mob/living/carbon/human/kidan/Initialize(mapload) - ..(mapload, /datum/species/kidan) - -/mob/living/carbon/human/plasma/Initialize(mapload) - ..(mapload, /datum/species/plasmaman) - -/mob/living/carbon/human/slime/Initialize(mapload) - ..(mapload, /datum/species/slime) - -/mob/living/carbon/human/grey/Initialize(mapload) - ..(mapload, /datum/species/grey) - -/mob/living/carbon/human/abductor/Initialize(mapload) - ..(mapload, /datum/species/abductor) - -/mob/living/carbon/human/diona/Initialize(mapload) - ..(mapload, /datum/species/diona) - -/mob/living/carbon/human/pod_diona/Initialize(mapload) - ..(mapload, /datum/species/diona/pod) - -/mob/living/carbon/human/machine/Initialize(mapload) - ..(mapload, /datum/species/machine) - -/mob/living/carbon/human/machine/created - name = "Integrated Robotic Chassis" - -/mob/living/carbon/human/machine/created/Initialize(mapload) - ..() - rename_character(null, "Integrated Robotic Chassis ([rand(1, 9999)])") - update_dna() - for(var/obj/item/organ/external/E in bodyparts) - if(istype(E, /obj/item/organ/external/chest) || istype(E, /obj/item/organ/external/groin)) - continue - qdel(E) - for(var/obj/item/organ/O in internal_organs) - qdel(O) - regenerate_icons() - death() - -/mob/living/carbon/human/shadow/Initialize(mapload) - ..(mapload, /datum/species/shadow) - -/mob/living/carbon/human/golem/Initialize(mapload) - ..(mapload, /datum/species/golem) - -/mob/living/carbon/human/wryn/Initialize(mapload) - ..(mapload, /datum/species/wryn) - -/mob/living/carbon/human/nucleation/Initialize(mapload) - ..(mapload, /datum/species/nucleation) - -/mob/living/carbon/human/drask/Initialize(mapload) - ..(mapload, /datum/species/drask) - -/mob/living/carbon/human/monkey/Initialize(mapload) - ..(mapload, /datum/species/monkey) - -/mob/living/carbon/human/farwa/Initialize(mapload) - ..(mapload, /datum/species/monkey/tajaran) - -/mob/living/carbon/human/wolpin/Initialize(mapload) - ..(mapload, /datum/species/monkey/vulpkanin) - -/mob/living/carbon/human/neara/Initialize(mapload) - ..(mapload, /datum/species/monkey/skrell) - -/mob/living/carbon/human/stok/Initialize(mapload) - ..(mapload, /datum/species/monkey/unathi) - -/mob/living/carbon/human/Stat() - ..() - statpanel("Status") - - stat(null, "Intent: [a_intent]") - stat(null, "Move Mode: [m_intent]") - - show_stat_emergency_shuttle_eta() - - if(client.statpanel == "Status") - var/total_user_contents = GetAllContents() // cache it - if(locate(/obj/item/gps) in total_user_contents) - var/turf/T = get_turf(src) - stat(null, "GPS: [COORD(T)]") - if(locate(/obj/item/assembly/health) in total_user_contents) - stat(null, "Health: [health]") - if(internal) - if(!internal.air_contents) - qdel(internal) - else - stat("Internal Atmosphere Info", internal.name) - stat("Tank Pressure", internal.air_contents.return_pressure()) - stat("Distribution Pressure", internal.distribute_pressure) - - if(istype(back, /obj/item/rig)) - var/obj/item/rig/suit = back - var/cell_status = "ERROR" - if(suit.cell) - cell_status = "[suit.cell.charge]/[suit.cell.maxcharge]" - stat(null, "Suit charge: [cell_status]") - - // I REALLY need to split up status panel things into datums - var/mob/living/simple_animal/borer/B = has_brain_worms() - if(B && B.controlling) - stat("Chemicals", B.chemicals) - - if(mind) - if(mind.changeling) - stat("Chemical Storage", "[mind.changeling.chem_charges]/[mind.changeling.chem_storage]") - stat("Absorbed DNA", mind.changeling.absorbedcount) - - if(mind.vampire) - stat("Total Blood", "[mind.vampire.bloodtotal]") - stat("Usable Blood", "[mind.vampire.bloodusable]") - - if(istype(loc, /obj/spacepod)) // Spacdpods! - var/obj/spacepod/S = loc - stat("Spacepod Charge", "[istype(S.battery) ? "[(S.battery.charge / S.battery.maxcharge) * 100]" : "No cell detected"]") - stat("Spacepod Integrity", "[!S.health ? "0" : "[(S.health / initial(S.health)) * 100]"]%") - -/mob/living/carbon/human/ex_act(severity) - var/shielded = 0 - var/b_loss = null - var/f_loss = null - - if(status_flags & GODMODE) - return 0 - - switch(severity) - if(1) - b_loss += 500 - if(!prob(getarmor(null, "bomb"))) - gib() - return 0 - else - var/atom/target = get_edge_target_turf(src, get_dir(src, get_step_away(src, src))) - throw_at(target, 200, 4) - - var/limbs_affected = pick(2,3,4) - var/obj/item/organ/external/processing_dismember - var/list/valid_limbs = bodyparts.Copy() - - while(limbs_affected != 0 && valid_limbs.len > 0) - processing_dismember = pick(valid_limbs) - if(processing_dismember.limb_name != "chest" && processing_dismember.limb_name != "head" && processing_dismember.limb_name != "groin") - processing_dismember.droplimb(1,DROPLIMB_SHARP,0,1) - valid_limbs -= processing_dismember - limbs_affected -= 1 - else valid_limbs -= processing_dismember - - if(2) - if(!shielded) //literally nothing could change shielded before this so wth - b_loss += 60 - - f_loss += 60 - - var/limbs_affected = 0 - var/obj/item/organ/external/processing_dismember - var/list/valid_limbs = bodyparts.Copy() - - if(prob(getarmor(null, "bomb"))) - b_loss = b_loss/1.5 - f_loss = f_loss/1.5 - - limbs_affected = pick(1, 1, 2) - else - limbs_affected = pick(1, 2, 3) - - while(limbs_affected != 0 && valid_limbs.len > 0) - processing_dismember = pick(valid_limbs) - if(processing_dismember.limb_name != "chest" && processing_dismember.limb_name != "head" && processing_dismember.limb_name != "groin") - processing_dismember.droplimb(1,DROPLIMB_SHARP,0,1) - valid_limbs -= processing_dismember - limbs_affected -= 1 - else valid_limbs -= processing_dismember - - if(!istype(l_ear, /obj/item/clothing/ears/earmuffs) && !istype(r_ear, /obj/item/clothing/ears/earmuffs)) - AdjustEarDamage(30, 120) - if(prob(70) && !shielded) - Paralyse(10) - - if(3) - b_loss += 30 - if(prob(getarmor(null, "bomb"))) - b_loss = b_loss/2 - - else - - var/limbs_affected = pick(0, 1) - var/obj/item/organ/external/processing_dismember - var/list/valid_limbs = bodyparts.Copy() - - while(limbs_affected != 0 && valid_limbs.len > 0) - processing_dismember = pick(valid_limbs) - if(processing_dismember.limb_name != "chest" && processing_dismember.limb_name != "head" && processing_dismember.limb_name != "groin") - processing_dismember.droplimb(1,DROPLIMB_SHARP,0,1) - valid_limbs -= processing_dismember - limbs_affected -= 1 - else valid_limbs -= processing_dismember - - if(!istype(l_ear, /obj/item/clothing/ears/earmuffs) && !istype(r_ear, /obj/item/clothing/ears/earmuffs)) - AdjustEarDamage(15, 60) - if(prob(50) && !shielded) - Paralyse(10) - - take_overall_damage(b_loss,f_loss, TRUE, used_weapon = "Explosive Blast") - - ..() - -/mob/living/carbon/human/blob_act(obj/structure/blob/B) - if(stat == DEAD) - return - show_message("The blob attacks you!") - var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") - var/obj/item/organ/external/affecting = get_organ(ran_zone(dam_zone)) - apply_damage(5, BRUTE, affecting, run_armor_check(affecting, "melee")) - -/mob/living/carbon/human/bullet_act() - if(martial_art && martial_art.deflection_chance) //Some martial arts users can deflect projectiles! - if(!prob(martial_art.deflection_chance)) - return ..() - if(!src.lying && !(HULK in mutations)) //But only if they're not lying down, and hulks can't do it - visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons!", "You deflect the projectile!") - return 0 - ..() - -/mob/living/carbon/human/get_restraining_item() - . = ..() - if(!. && istype(wear_suit, /obj/item/clothing/suit/straight_jacket)) - . = wear_suit - -/mob/living/carbon/human/var/temperature_resistance = T0C+75 - - -/mob/living/carbon/human/show_inv(mob/user) - user.set_machine(src) - var/has_breathable_mask = istype(wear_mask, /obj/item/clothing/mask) || get_organ_slot("breathing_tube") - var/list/obscured = check_obscured_slots() - - var/dat = {" - - - "} - - dat += "" - - dat += "" - - var/obj/item/organ/internal/headpocket/C = get_int_organ(/obj/item/organ/internal/headpocket) - if(C) - if(slot_wear_mask in obscured) - dat += "" - else - var/list/items = C.get_contents() - if(items.len) - dat += "" - else - dat += "" - - if(slot_wear_mask in obscured) - dat += "" - else - dat += "" - - if(!issmall(src)) - if(slot_glasses in obscured) - dat += "" - else - dat += "" - - if(slot_l_ear in obscured) - dat += "" - else - dat += "" - - if(slot_r_ear in obscured) - dat += "" - else - dat += "" - - dat += "" - - dat += "" - if(wear_suit) - dat += "" - else - dat += "" - - if(slot_shoes in obscured) - dat += "" - else - dat += "" - - if(slot_gloves in obscured) - dat += "" - else - dat += "" - - if(slot_w_uniform in obscured) - dat += "" - else - dat += "" - - if((w_uniform == null && !(dna && dna.species.nojumpsuit)) || (slot_w_uniform in obscured)) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - else - dat += "" - // Pockets - dat += "" - dat += "" - dat += "" - - if(istype(w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = w_uniform - dat += "" - - if(U.accessories.len) - dat += "" - - - if(handcuffed) - dat += "" - if(legcuffed) - dat += "" - - dat += {"
        Left Hand:[(l_hand && !(l_hand.flags&ABSTRACT)) ? l_hand : "Empty"]
        Right Hand:[(r_hand && !(r_hand.flags&ABSTRACT)) ? r_hand : "Empty"]
         
        Back:[(back && !(back.flags&ABSTRACT)) ? back : "Empty"]" - if(has_breathable_mask && istype(back, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - - dat += "
         
        Head:[(head && !(head.flags&ABSTRACT)) ? head : "Empty"]
         ↳Headpocket:Obscured
         ↳Headpocket:Dislodge Items
         ↳Headpocket:Empty
        Mask:Obscured
        Mask:[(wear_mask && !(wear_mask.flags&ABSTRACT)) ? wear_mask : "Empty"]" - - if(istype(wear_mask, /obj/item/clothing/mask/muzzle)) - var/obj/item/clothing/mask/muzzle/M = wear_mask - if(M.security_lock) - dat += " [M.locked ? "Disable Lock" : "Set Lock"]" - - dat += "
         
        Eyes:Obscured
        Eyes:[(glasses && !(glasses.flags&ABSTRACT)) ? glasses : "Empty"]
        Left Ear:Obscured
        Left Ear:[(l_ear && !(l_ear.flags&ABSTRACT)) ? l_ear : "Empty"]
        Right Ear:Obscured
        Right Ear:[(r_ear && !(r_ear.flags&ABSTRACT)) ? r_ear : "Empty"]
         
        Exosuit:[(wear_suit && !(wear_suit.flags&ABSTRACT)) ? wear_suit : "Empty"]
         ↳Suit Storage:[(s_store && !(s_store.flags&ABSTRACT)) ? s_store : "Empty"]" - if(has_breathable_mask && istype(s_store, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - dat += "
         ↳Suit Storage:
        Shoes:Obscured
        Shoes:[(shoes && !(shoes.flags&ABSTRACT)) ? shoes : "Empty"]
        Gloves:Obscured
        Gloves:[(gloves && !(gloves.flags&ABSTRACT)) ? gloves : "Empty"]
        Uniform:Obscured
        Uniform:[(w_uniform && !(w_uniform.flags&ABSTRACT)) ? w_uniform : "Empty"]
         ↳Pockets:
         ↳ID:
         ↳Belt:
         ↳Suit Sensors:
         ↳PDA:
         ↳Belt:[(belt && !(belt.flags&ABSTRACT)) ? belt : "Empty"]" - if(has_breathable_mask && istype(belt, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - dat += "
         ↳Pockets:" - if(l_store && internal && l_store == internal) - dat += "[l_store]" - else if(l_store && !(l_store.flags&ABSTRACT)) - dat += "Left (Full)" - else - dat += "Left (Empty)" - dat += " " - if(r_store && internal && r_store == internal) - dat += "[r_store]" - else if(r_store && !(r_store.flags&ABSTRACT)) - dat += "Right (Full)" - else - dat += "Right (Empty)" - dat += "
         ↳ID:[(wear_id && !(wear_id.flags&ABSTRACT)) ? wear_id : "Empty"]
         ↳PDA:[(wear_pda && !(wear_pda.flags&ABSTRACT)) ? wear_pda : "Empty"]
         ↳Suit Sensors:[U.has_sensor >= 2 ? "--SENSORS LOCKED--" : "Set Sensors"]
         ↳Remove Accessory
        Handcuffed: Remove
        Legcuffed
        - Close - "} - - var/datum/browser/popup = new(user, "mob\ref[src]", "[src]", 440, 540) - popup.set_content(dat) - popup.open() - -// Get rank from ID, ID inside PDA, PDA, ID in wallet, etc. -/mob/living/carbon/human/proc/get_authentification_rank(var/if_no_id = "No id", var/if_no_job = "No job") - var/obj/item/pda/pda = wear_id - if(istype(pda)) - if(pda.id) - return pda.id.rank - else - return pda.ownrank - else - var/obj/item/card/id/id = get_idcard() - if(id) - return id.rank ? id.rank : if_no_job - else - return if_no_id - -//gets assignment from ID or ID inside PDA or PDA itself -//Useful when player do something with computers -/mob/living/carbon/human/proc/get_assignment(var/if_no_id = "No id", var/if_no_job = "No job") - var/obj/item/pda/pda = wear_id - var/obj/item/card/id/id = wear_id - if(istype(pda)) - if(pda.id && istype(pda.id, /obj/item/card/id)) - . = pda.id.assignment - else - . = pda.ownjob - else if(istype(id)) - . = id.assignment - else - return if_no_id - if(!.) - . = if_no_job - return - -//gets name from ID or ID inside PDA or PDA itself -//Useful when player do something with computers -/mob/living/carbon/human/proc/get_authentification_name(var/if_no_id = "Unknown") - var/obj/item/pda/pda = wear_id - var/obj/item/card/id/id = wear_id - if(istype(pda)) - if(pda.id) - . = pda.id.registered_name - else - . = pda.owner - else if(istype(id)) - . = id.registered_name - else - return if_no_id - return - -//repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a seperate proc as it'll be useful elsewhere -/mob/living/carbon/human/get_visible_name(var/id_override = FALSE) - if(name_override) - return name_override - if(wear_mask && (wear_mask.flags_inv & HIDEFACE)) //Wearing a mask which hides our face, use id-name if possible - return get_id_name("Unknown") - if(head && (head.flags_inv & HIDEFACE)) - return get_id_name("Unknown") //Likewise for hats - var/face_name = get_face_name() - var/id_name = get_id_name("") - if(id_name && (id_name != face_name) && !id_override) - return "[face_name] (as [id_name])" - return face_name - -//Returns "Unknown" if facially disfigured and real_name if not. Useful for setting name when polyacided or when updating a human's name variable -/mob/living/carbon/human/proc/get_face_name() - var/obj/item/organ/external/head = get_organ("head") - if(!head || head.disfigured || cloneloss > 50 || !real_name || (HUSK in mutations)) //disfigured. use id-name if possible - return "Unknown" - return real_name - -//gets name from ID or PDA itself, ID inside PDA doesn't matter -//Useful when player is being seen by other mobs -/mob/living/carbon/human/proc/get_id_name(var/if_no_id = "Unknown") - var/obj/item/pda/pda = wear_id - var/obj/item/card/id/id = wear_id - if(istype(pda)) . = pda.owner - else if(istype(id)) . = id.registered_name - if(!.) . = if_no_id //to prevent null-names making the mob unclickable - return - -//gets ID card object from special clothes slot or, if applicable, hands as well -/mob/living/carbon/human/proc/get_idcard(var/check_hands = FALSE) - var/obj/item/card/id/id = wear_id - var/obj/item/pda/pda = wear_id - if(istype(pda) && pda.id) - id = pda.id - - if(check_hands) - if(istype(get_active_hand(), /obj/item/card/id)) - id = get_active_hand() - else if(istype(get_inactive_hand(), /obj/item/card/id)) - id = get_inactive_hand() - - if(istype(id)) - return id - -/mob/living/carbon/human/update_sight() - if(!client) - return - - if(stat == DEAD) - grant_death_vision() - return - - dna.species.update_sight(src) - SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) - sync_lighting_plane_alpha() - -//Added a safety check in case you want to shock a human mob directly through electrocute_act. -/mob/living/carbon/human/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE) - if(tesla_shock) - var/total_coeff = 1 - if(gloves) - var/obj/item/clothing/gloves/G = gloves - if(G.siemens_coefficient <= 0) - total_coeff -= 0.5 - if(wear_suit) - var/obj/item/clothing/suit/S = wear_suit - if(S.siemens_coefficient <= 0) - total_coeff -= 0.95 - else if(S.siemens_coefficient == (-1)) - total_coeff -= 1 - siemens_coeff = total_coeff - if(tesla_ignore) - siemens_coeff = 0 - else if(!safety) - var/gloves_siemens_coeff = 1 - if(gloves) - var/obj/item/clothing/gloves/G = gloves - gloves_siemens_coeff = G.siemens_coefficient - siemens_coeff = gloves_siemens_coeff - if(undergoing_cardiac_arrest() && !illusion) - if(shock_damage * siemens_coeff >= 1 && prob(25)) - set_heartattack(FALSE) - if(stat == CONSCIOUS) - to_chat(src, "You feel your heart beating again!") - - dna.species.spec_electrocute_act(src, shock_damage, source, siemens_coeff, safety, override, tesla_shock, illusion, stun) - . = ..(shock_damage, source, siemens_coeff, safety, override, tesla_shock, illusion, stun) - -/mob/living/carbon/human/Topic(href, href_list) - if(!usr.stat && usr.canmove && !usr.restrained() && in_range(src, usr)) - var/thief_mode = 0 - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - var/obj/item/clothing/gloves/G = H.gloves - if(G && G.pickpocket) - thief_mode = 1 - - if(href_list["embedded_object"]) - var/obj/item/organ/external/L = locate(href_list["embedded_limb"]) in bodyparts - if(!L) - return - var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects - if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore - return - var/time_taken = I.embedded_unsafe_removal_time*I.w_class - usr.visible_message("[usr] attempts to remove [I] from [usr.p_their()] [L.name].","You attempt to remove [I] from your [L.name]... (It will take [time_taken/10] seconds.)") - if(do_after(usr, time_taken, needhand = 1, target = src)) - if(!I || !L || I.loc != src || !(I in L.embedded_objects)) - return - L.embedded_objects -= I - L.receive_damage(I.embedded_unsafe_removal_pain_multiplier*I.w_class)//It hurts to rip it out, get surgery you dingus. - I.forceMove(get_turf(src)) - usr.put_in_hands(I) - usr.emote("scream") - usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!","You successfully remove [I] from your [L.name].") - if(!has_embedded_objects()) - clear_alert("embeddedobject") - return - - if(href_list["item"]) - var/slot = text2num(href_list["item"]) - if(slot in check_obscured_slots()) - to_chat(usr, "You can't reach that! Something is covering it.") - return - - if(href_list["pockets"]) - var/pocket_side = href_list["pockets"] - var/pocket_id = (pocket_side == "right" ? slot_r_store : slot_l_store) - var/obj/item/pocket_item = (pocket_id == slot_r_store ? r_store : l_store) - var/obj/item/place_item = usr.get_active_hand() // Item to place in the pocket, if it's empty - - var/delay_denominator = 1 - if(pocket_item && !(pocket_item.flags&ABSTRACT)) - if(pocket_item.flags & NODROP) - to_chat(usr, "You try to empty [src]'s [pocket_side] pocket, it seems to be stuck!") - to_chat(usr, "You try to empty [src]'s [pocket_side] pocket.") - else if(place_item && place_item.mob_can_equip(src, pocket_id, 1) && !(place_item.flags&ABSTRACT)) - to_chat(usr, "You try to place [place_item] into [src]'s [pocket_side] pocket.") - delay_denominator = 4 - else - return - - if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator)) //placing an item into the pocket is 4 times faster - if(pocket_item) - if(pocket_item == (pocket_id == slot_r_store ? r_store : l_store)) //item still in the pocket we search - unEquip(pocket_item) - if(thief_mode) - usr.put_in_hands(pocket_item) - add_attack_logs(usr, src, "Stripped of [pocket_item]", isLivingSSD(src) ? null : ATKLOG_ALL) - else - if(place_item) - usr.unEquip(place_item) - equip_to_slot_if_possible(place_item, pocket_id, 0, 1) - add_attack_logs(usr, src, "Equipped with [place_item]", isLivingSSD(src) ? null : ATKLOG_ALL) - - // Update strip window - if(usr.machine == src && in_range(src, usr)) - show_inv(usr) - else - // Display a warning if the user mocks up if they don't have pickpocket gloves. - if(!thief_mode) - to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") - add_attack_logs(usr, src, "Attempted strip of [pocket_item]", isLivingSSD(src) ? null : ATKLOG_ALL) - - if(href_list["set_sensor"]) - if(istype(w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = w_uniform - U.set_sensors(usr) - - if(href_list["dislodge_headpocket"]) - usr.visible_message("[usr] is trying to remove something from [src]'s head!", - "You start to dislodge whatever's inside [src]'s headpocket!") - if(do_mob(usr, src, POCKET_STRIP_DELAY)) - usr.visible_message("[usr] has dislodged something from [src]'s head!", - "You have dislodged everything from [src]'s headpocket!") - var/obj/item/organ/internal/headpocket/C = get_int_organ(/obj/item/organ/internal/headpocket) - C.empty_contents() - add_attack_logs(usr, src, "Stripped of headpocket items", isLivingSSD(src) ? null : ATKLOG_ALL) - - if(href_list["strip_accessory"]) - if(istype(w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = w_uniform - if(U.accessories.len) - var/obj/item/clothing/accessory/A = U.accessories[1] - if(!thief_mode) - usr.visible_message("\The [usr] starts to take off \the [A] from \the [src]'s [U]!", \ - "You start to take off \the [A] from \the [src]'s [U]!") - - if(do_mob(usr, src, 40) && A && U.accessories.len) - if(!thief_mode) - usr.visible_message("\The [usr] takes \the [A] off of \the [src]'s [U]!", \ - "You take \the [A] off of \the [src]'s [U]!") - A.on_removed(usr) - U.accessories -= A - update_inv_w_uniform() - - if(href_list["criminal"]) - if(hasHUD(usr,"security")) - if(usr.incapacitated()) - return - var/found_record = 0 - var/perpname = get_visible_name(TRUE) - - if(perpname != "Unknown") - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.security) - if(R.fields["id"] == E.fields["id"]) - - var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Parolled", "Released", "Cancel") - var/t1 = copytext(trim(sanitize(input("Enter Reason:", "Security HUD", null, null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1) - t1 = "(none)" - - if(hasHUD(usr, "security") && setcriminal != "Cancel") - found_record = 1 - if(R.fields["criminal"] == "*Execute*") - to_chat(usr, "Unable to modify the sec status of a person with an active Execution order. Use a security computer instead.") - else - var/rank - if(ishuman(usr)) - var/mob/living/carbon/human/U = usr - rank = U.get_assignment() - else if(isrobot(usr)) - var/mob/living/silicon/robot/U = usr - rank = "[U.modtype] [U.braintype]" - else if(isAI(usr)) - rank = "AI" - set_criminal_status(usr, R, setcriminal, t1, rank) - break // Git out of the securiy records loop! - if(found_record) - break // Git out of the general records - - if(!found_record) - to_chat(usr, "Unable to locate a data core entry for this person.") - - if(href_list["secrecord"]) - if(hasHUD(usr,"security")) - if(usr.incapacitated()) - return - var/perpname = get_visible_name(TRUE) - var/read = 0 - - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.security) - if(R.fields["id"] == E.fields["id"]) - if(hasHUD(usr,"security")) - to_chat(usr, "Name: [R.fields["name"]] Criminal Status: [R.fields["criminal"]]") - to_chat(usr, "Minor Crimes: [R.fields["mi_crim"]]") - to_chat(usr, "Details: [R.fields["mi_crim_d"]]") - to_chat(usr, "Major Crimes: [R.fields["ma_crim"]]") - to_chat(usr, "Details: [R.fields["ma_crim_d"]]") - to_chat(usr, "Notes: [R.fields["notes"]]") - to_chat(usr, "\[View Comment Log\]") - read = 1 - - if(!read) - to_chat(usr, "Unable to locate a data core entry for this person.") - - if(href_list["secrecordComment"]) - if(hasHUD(usr,"security")) - if(usr.incapacitated()) - return - var/perpname = get_visible_name(TRUE) - var/read = 0 - - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.security) - if(R.fields["id"] == E.fields["id"]) - if(hasHUD(usr,"security")) - read = 1 - if(LAZYLEN(R.fields["comments"])) - for(var/c in R.fields["comments"]) - to_chat(usr, c) - else - to_chat(usr, "No comment found") - to_chat(usr, "\[Add comment\]") - - if(!read) - to_chat(usr, "Unable to locate a data core entry for this person.") - - if(href_list["secrecordadd"]) - if(hasHUD(usr,"security")) - if(usr.incapacitated()) - return - var/perpname = get_visible_name(TRUE) - - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.security) - if(R.fields["id"] == E.fields["id"]) - if(hasHUD(usr,"security")) - var/t1 = copytext(trim(sanitize(input("Add Comment:", "Sec. records", null, null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || usr.stat || usr.restrained() || !hasHUD(usr, "security")) - return - if(ishuman(usr)) - var/mob/living/carbon/human/U = usr - R.fields["comments"] += "Made by [U.get_authentification_name()] ([U.get_assignment()]) on [current_date_string] [station_time_timestamp()]
        [t1]" - if(isrobot(usr)) - var/mob/living/silicon/robot/U = usr - R.fields["comments"] += "Made by [U.name] ([U.modtype] [U.braintype]) on [current_date_string] [station_time_timestamp()]
        [t1]" - if(isAI(usr)) - var/mob/living/silicon/ai/U = usr - R.fields["comments"] += "Made by [U.name] (artificial intelligence) on [current_date_string] [station_time_timestamp()]
        [t1]" - - if(href_list["medical"]) - if(hasHUD(usr,"medical")) - if(usr.incapacitated()) - return - var/modified = 0 - var/perpname = get_visible_name(TRUE) - - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.general) - if(R.fields["id"] == E.fields["id"]) - var/setmedical = input(usr, "Specify a new medical status for this person.", "Medical HUD", R.fields["p_stat"]) in list("*SSD*", "*Deceased*", "Physically Unfit", "Active", "Disabled", "Cancel") - - if(hasHUD(usr,"medical")) - if(setmedical != "Cancel") - R.fields["p_stat"] = setmedical - modified = 1 - if(PDA_Manifest.len) - PDA_Manifest.Cut() - - spawn() - sec_hud_set_security_status() - - if(!modified) - to_chat(usr, "Unable to locate a data core entry for this person.") - - if(href_list["medrecord"]) - if(hasHUD(usr,"medical")) - if(usr.incapacitated()) - return - var/read = 0 - var/perpname = get_visible_name(TRUE) - - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.medical) - if(R.fields["id"] == E.fields["id"]) - if(hasHUD(usr,"medical")) - to_chat(usr, "Name: [R.fields["name"]] Blood Type: [R.fields["b_type"]]") - to_chat(usr, "DNA: [R.fields["b_dna"]]") - to_chat(usr, "Minor Disabilities: [R.fields["mi_dis"]]") - to_chat(usr, "Details: [R.fields["mi_dis_d"]]") - to_chat(usr, "Major Disabilities: [R.fields["ma_dis"]]") - to_chat(usr, "Details: [R.fields["ma_dis_d"]]") - to_chat(usr, "Notes: [R.fields["notes"]]") - to_chat(usr, "\[View Comment Log\]") - read = 1 - - if(!read) - to_chat(usr, "Unable to locate a data core entry for this person.") - - if(href_list["medrecordComment"]) - if(hasHUD(usr,"medical")) - if(usr.incapacitated()) - return - var/perpname = get_visible_name(TRUE) - var/read = 0 - - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.medical) - if(R.fields["id"] == E.fields["id"]) - if(hasHUD(usr,"medical")) - read = 1 - if(LAZYLEN(R.fields["comments"])) - for(var/c in R.fields["comments"]) - to_chat(usr, c) - else - to_chat(usr, "No comment found") - to_chat(usr, "\[Add comment\]") - - if(!read) - to_chat(usr, "Unable to locate a data core entry for this person.") - - if(href_list["medrecordadd"]) - if(hasHUD(usr,"medical")) - if(usr.incapacitated()) - return - var/perpname = get_visible_name(TRUE) - for(var/datum/data/record/E in data_core.general) - if(E.fields["name"] == perpname) - for(var/datum/data/record/R in data_core.medical) - if(R.fields["id"] == E.fields["id"]) - if(hasHUD(usr,"medical")) - var/t1 = copytext(trim(sanitize(input("Add Comment:", "Med. records", null, null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || usr.stat || usr.restrained() || !hasHUD(usr, "medical")) - return - if(ishuman(usr)) - var/mob/living/carbon/human/U = usr - R.fields["comments"] += "Made by [U.get_authentification_name()] ([U.get_assignment()]) on [current_date_string] [station_time_timestamp()]
        [t1]" - if(isrobot(usr)) - var/mob/living/silicon/robot/U = usr - R.fields["comments"] += "Made by [U.name] ([U.modtype] [U.braintype]) on [current_date_string] [station_time_timestamp()]
        [t1]" - if(isAI(usr)) - var/mob/living/silicon/ai/U = usr - R.fields["comments"] += "Made by [U.name] (artificial intelligence) on [current_date_string] [station_time_timestamp()]
        [t1]" - - if(href_list["lookitem"]) - var/obj/item/I = locate(href_list["lookitem"]) - src.examinate(I) - - if(href_list["lookmob"]) - var/mob/M = locate(href_list["lookmob"]) - src.examinate(M) - . = ..() - - -///check_eye_prot() -///Returns a number between -1 to 2 -/mob/living/carbon/human/check_eye_prot() - var/number = ..() - if(istype(head, /obj/item/clothing/head)) //are they wearing something on their head - var/obj/item/clothing/head/HFP = head //if yes gets the flash protection value from that item - number += HFP.flash_protect - if(istype(glasses, /obj/item/clothing/glasses)) //glasses - var/obj/item/clothing/glasses/GFP = glasses - number += GFP.flash_protect - if(istype(wear_mask, /obj/item/clothing/mask)) //mask - var/obj/item/clothing/mask/MFP = wear_mask - number += MFP.flash_protect - for(var/obj/item/organ/internal/cyberimp/eyes/EFP in internal_organs) - number += EFP.flash_protect - - return number - -/mob/living/carbon/human/check_ear_prot() - if(head && (head.flags & HEADBANGPROTECT)) - return 1 - if(l_ear && (l_ear.flags & EARBANGPROTECT)) - return 1 - if(r_ear && (r_ear.flags & EARBANGPROTECT)) - return 1 - -///tintcheck() -///Checks eye covering items for visually impairing tinting, such as welding masks -///Checked in life.dm. 0 & 1 = no impairment, 2 = welding mask overlay, 3 = You can see jack, but you can't see shit. -/mob/living/carbon/human/tintcheck() - var/tinted = 0 - if(istype(src.head, /obj/item/clothing/head)) - var/obj/item/clothing/head/HT = src.head - tinted += HT.tint - if(istype(src.glasses, /obj/item/clothing/glasses)) - var/obj/item/clothing/glasses/GT = src.glasses - tinted += GT.tint - if(istype(src.wear_mask, /obj/item/clothing/mask)) - var/obj/item/clothing/mask/MT = src.wear_mask - tinted += MT.tint - - //god help me - if(istype(back, /obj/item/rig)) - var/obj/item/rig/O = back - if(O.helmet && O.helmet == head && (O.helmet.body_parts_covered & HEAD)) - if((O.offline && O.offline_vision_restriction == 1) || (!O.offline && O.vision_restriction == 1)) - tinted = 2 - if((O.offline && O.offline_vision_restriction == 2) || (!O.offline && O.vision_restriction == 2)) - tinted = 3 - //im so sorry - - return tinted - - -/mob/living/carbon/human/abiotic(var/full_body = 0) - if(full_body && ((src.l_hand && !(src.l_hand.flags & ABSTRACT)) || (src.r_hand && !(src.r_hand.flags & ABSTRACT)) || (src.back || src.wear_mask || src.head || src.shoes || src.w_uniform || src.wear_suit || src.glasses || src.l_ear || src.r_ear || src.gloves))) - return 1 - - if((src.l_hand && !(src.l_hand.flags & ABSTRACT)) || (src.r_hand && !(src.r_hand.flags & ABSTRACT))) - return 1 - - return 0 - - -/mob/living/carbon/human/proc/check_dna() - dna.check_integrity(src) - -/mob/living/carbon/human/proc/play_xylophone() - if(!src.xylophone) - visible_message("[src] begins playing [p_their()] ribcage like a xylophone. It's quite spooky.","You begin to play a spooky refrain on your ribcage.","You hear a spooky xylophone melody.") - var/song = pick('sound/effects/xylophone1.ogg','sound/effects/xylophone2.ogg','sound/effects/xylophone3.ogg') - playsound(loc, song, 50, 1, -1) - xylophone = 1 - spawn(1200) - xylophone=0 - return - -/mob/living/carbon/human/can_inject(mob/user, error_msg, target_zone, penetrate_thick = FALSE) - . = 1 - - if(!target_zone) - if(!user) - target_zone = pick("chest","chest","chest","left leg","right leg","left arm", "right arm", "head") - else - target_zone = user.zone_selected - - - if(PIERCEIMMUNE in dna.species.species_traits) - . = 0 - - var/obj/item/organ/external/affecting = get_organ(target_zone) - var/fail_msg - if(!affecting) - . = 0 - fail_msg = "[p_they(TRUE)] [p_are()] missing that limb." - else if(affecting.is_robotic()) - . = 0 - fail_msg = "That limb is robotic." - else - switch(target_zone) - if("head") - if(head && head.flags & THICKMATERIAL && !penetrate_thick) - . = 0 - else - if(wear_suit && wear_suit.flags & THICKMATERIAL && !penetrate_thick) - . = 0 - if(!. && error_msg && user) - if(!fail_msg) - fail_msg = "There is no exposed flesh or thin material [target_zone == "head" ? "on [p_their()] head" : "on [p_their()] body"] to inject into." - to_chat(user, "[fail_msg]") - -/mob/living/carbon/human/proc/check_obscured_slots() - var/list/obscured = list() - - if(wear_suit) - if(wear_suit.flags_inv & HIDEGLOVES) - obscured |= slot_gloves - if(wear_suit.flags_inv & HIDEJUMPSUIT) - obscured |= slot_w_uniform - if(wear_suit.flags_inv & HIDESHOES) - obscured |= slot_shoes - - if(head) - if(head.flags_inv & HIDEMASK) - obscured |= slot_wear_mask - if(head.flags_inv & HIDEEYES) - obscured |= slot_glasses - if(head.flags_inv & HIDEEARS) - obscured |= slot_r_ear - obscured |= slot_l_ear - - if(obscured.len > 0) - return obscured - else - return null - -/mob/living/carbon/human/proc/check_has_mouth() - // Todo, check stomach organ when implemented. - var/obj/item/organ/external/head/H = get_organ("head") - if(!H || !H.can_intake_reagents) - return 0 - return 1 - -/mob/living/carbon/human/proc/get_visible_gender() - var/list/obscured = check_obscured_slots() - var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE)) - if((slot_w_uniform in obscured) && skipface) - return PLURAL - return gender - -/mob/living/carbon/human/proc/increase_germ_level(n) - if(gloves) - gloves.germ_level += n - else - germ_level += n - -/mob/living/carbon/human/proc/check_and_regenerate_organs(var/mob/living/carbon/human/H) //Regenerates missing limbs/organs. - var/list/types_of_int_organs = list() //This will hold all the types of organs in the mob before rejuvenation. - for(var/obj/item/organ/internal/I in H.internal_organs) - types_of_int_organs |= I.type //Compiling the list of organ types. It is possible for organs to be missing from this list if they are absent from the mob. - - //Clean up limbs - for(var/organ_name in H.bodyparts_by_name) - var/obj/item/organ/organ = H.bodyparts_by_name[organ_name] - if(!organ) //The !organ check is to account for mechanical limb (prostheses) losses, since those are handled in a way that leaves indexed but null list entries instead of stumps. - qdel(organ) - H.bodyparts_by_name -= organ_name //Making sure the list entry is removed. - - //Replacing lost limbs with the species default. - var/mob/living/carbon/human/temp_holder - for(var/limb_type in H.dna.species.has_limbs) - if(!(limb_type in H.bodyparts_by_name)) - var/list/organ_data = H.dna.species.has_limbs[limb_type] - var/limb_path = organ_data["path"] - var/obj/item/organ/external/O = new limb_path(temp_holder) - if(H.get_limb_by_name(O.name)) //Check to see if the user already has an limb with the same name as the 'missing limb'. If they do, skip regrowth. - continue //In an example, this will prevent duplication of the mob's right arm if the mob is a Human and they have a Diona right arm, since, - //while the limb with the name 'right_arm' the mob has may not be listed in their species' bodyparts definition, it is still viable and has the appropriate limb name. - else - O = new limb_path(H) //Create the limb on the player. - O.owner = H - H.bodyparts |= H.bodyparts_by_name[O.limb_name] - if(O.body_part == HEAD) //They're sprouting a fresh head so lets hook them up with their genetic stuff so their new head looks like the original. - H.UpdateAppearance() - - //Replacing lost organs with the species default. - temp_holder = new /mob/living/carbon/human() - var/list/species_organs = H.dna.species.has_organ.Copy() //Compile a list of species organs and tack on the mutantears afterward. - if(H.dna.species.mutantears) - species_organs["ears"] = H.dna.species.mutantears - for(var/index in species_organs) - var/organ = species_organs[index] - if(!(organ in types_of_int_organs)) //If the mob is missing this particular organ... - var/obj/item/organ/internal/I = new organ(temp_holder) //Create the organ inside our holder so we can check it before implantation. - if(H.get_organ_slot(I.slot)) //Check to see if the user already has an organ in the slot the 'missing organ' belongs to. If they do, skip implantation. - continue //In an example, this will prevent duplication of the mob's eyes if the mob is a Human and they have Nucleation eyes, since, - //while the organ in the eyes slot may not be listed in the mob's species' organs definition, it is still viable and fits in the appropriate organ slot. - else - I = new organ(H) //Create the organ inside the player. - I.insert(H) - -/mob/living/carbon/human/revive() - //Fix up all organs and replace lost ones. - restore_all_organs() //Rejuvenate and reset all existing organs. - check_and_regenerate_organs(src) //Regenerate limbs and organs only if they're really missing. - surgeries.Cut() //End all surgeries. - - if(!isskeleton(src) && (SKELETON in mutations)) - mutations.Remove(SKELETON) - if(NOCLONE in mutations) - mutations.Remove(NOCLONE) - if(HUSK in mutations) - mutations.Remove(HUSK) - - if(!client || !key) //Don't boot out anyone already in the mob. - for(var/obj/item/organ/internal/brain/H in world) - if(H.brainmob) - if(H.brainmob.real_name == src.real_name) - if(H.brainmob.mind) - H.brainmob.mind.transfer_to(src) - qdel(H) - - ..() - -/mob/living/carbon/human/proc/is_lung_ruptured() - var/obj/item/organ/internal/lungs/L = get_int_organ(/obj/item/organ/internal/lungs) - if(!L) - return 0 - - return L.is_bruised() - -/mob/living/carbon/human/proc/rupture_lung() - var/obj/item/organ/internal/lungs/L = get_int_organ(/obj/item/organ/internal/lungs) - if(!L) - return 0 - - if(!L.is_bruised()) - custom_pain("You feel a stabbing pain in your chest!") - L.damage = L.min_bruised_damage - -//returns 1 if made bloody, returns 0 otherwise - -/mob/living/carbon/human/clean_blood(var/clean_feet) - .=..() - if(clean_feet && !shoes && istype(feet_blood_DNA, /list) && feet_blood_DNA.len) - feet_blood_color = null - qdel(feet_blood_DNA) - bloody_feet = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_NOT_BLOODY = 0) - blood_state = BLOOD_STATE_NOT_BLOODY - update_inv_shoes(1) - return 1 - -/mob/living/carbon/human/cuff_resist(obj/item/I) - if(HULK in mutations) - say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" )) - if(..(I, cuff_break = 1)) - unEquip(I) - else - if(..()) - unEquip(I) - -/mob/living/carbon/human/resist_restraints() - if(wear_suit && wear_suit.breakouttime) - changeNext_move(CLICK_CD_BREAKOUT) - last_special = world.time + CLICK_CD_BREAKOUT - cuff_resist(wear_suit) - else - ..() - -/mob/living/carbon/human/generate_name() - name = dna.species.get_random_name(gender) - real_name = name - if(dna) - dna.real_name = name - return name - -/mob/living/carbon/human/verb/check_pulse() - set category = null - set name = "Check pulse" - set desc = "Approximately count somebody's pulse. Requires you to stand still at least 6 seconds." - set src in view(1) - var/self = 0 - - if(usr.stat == 1 || usr.restrained() || !isliving(usr)) return - - if(usr == src) - self = 1 - if(!self) - usr.visible_message("[usr] kneels down, puts [usr.p_their()] hand on [src]'s wrist and begins counting [p_their()] pulse.",\ - "You begin counting [src]'s pulse") - else - usr.visible_message("[usr] begins counting [p_their()] pulse.",\ - "You begin counting your pulse.") - - if(src.pulse) - to_chat(usr, "[self ? "You have a" : "[src] has a"] pulse! Counting...") - else - to_chat(usr, "[src] has no pulse!")//it is REALLY UNLIKELY that a dead person would check his own pulse - - return - - to_chat(usr, "Don't move until counting is finished.") - var/time = world.time - sleep(60) - if(usr.l_move_time >= time) //checks if our mob has moved during the sleep() - to_chat(usr, "You moved while counting. Try again.") - else - to_chat(usr, "[self ? "Your" : "[src]'s"] pulse is [src.get_pulse(GETPULSE_HAND)].") - -/mob/living/carbon/human/proc/set_species(datum/species/new_species, default_colour, delay_icon_update = FALSE, skip_same_check = FALSE, retain_damage = FALSE) - if(!skip_same_check) - if(dna.species.name == initial(new_species.name)) - return - var/datum/species/oldspecies = dna.species - - if(oldspecies) - if(oldspecies.language) - remove_language(oldspecies.language) - - if(oldspecies.default_language) - remove_language(oldspecies.default_language) - - if(gender == PLURAL && oldspecies.has_gender) - change_gender(pick(MALE, FEMALE)) - - if(oldspecies.default_genes.len) - oldspecies.handle_dna(src, TRUE) // Remove any genes that belong to the old species - - oldspecies.on_species_loss(src) - - dna.species = new new_species() - - tail = dna.species.tail - - maxHealth = dna.species.total_health - - if(dna.species.language) - add_language(dna.species.language) - - if(dna.species.default_language) - add_language(dna.species.default_language) - - hunger_drain = dna.species.hunger_drain - digestion_ratio = dna.species.digestion_ratio - - if(dna.species.base_color && default_colour) - //Apply colour. - skin_colour = dna.species.base_color - else - skin_colour = "#000000" - - if(!(dna.species.bodyflags & HAS_SKIN_TONE)) - s_tone = 0 - - var/list/thing_to_check = list(slot_wear_mask, slot_head, slot_shoes, slot_gloves, slot_l_ear, slot_r_ear, slot_glasses, slot_l_hand, slot_r_hand) - var/list/kept_items[0] - var/list/item_flags[0] - for(var/thing in thing_to_check) - var/obj/item/I = get_item_by_slot(thing) - if(I) - kept_items[I] = thing - item_flags[I] = I.flags - I.flags = 0 // Temporary set the flags to 0 - - if(retain_damage) - //Create a list of body parts which are damaged by burn or brute and save them to apply after new organs are generated. First we just handle external organs. - var/bodypart_damages = list() - //Loop through all external organs and save the damage states for brute and burn - for(var/obj/item/organ/external/E in bodyparts) - if(E.brute_dam == 0 && E.burn_dam == 0 && E.internal_bleeding == FALSE) //If there's no damage we don't bother remembering it. - continue - var/brute = E.brute_dam - var/burn = E.burn_dam - var/IB = E.internal_bleeding - var/obj/item/organ/external/OE = new E.type() - var/stats = list(OE, brute, burn, IB) - bodypart_damages += list(stats) - - //Now we do the same for internal organs via the same proceedure. - var/internal_damages = list() - for(var/obj/item/organ/internal/I in internal_organs) - if(I.damage == 0) - continue - var/obj/item/organ/internal/OI = new I.type() - var/damage = I.damage - var/broken = I.is_broken() - var/stats = list(OI, damage, broken) - internal_damages += list(stats) - - //Create the new organs for the species change - dna.species.create_organs(src) - - //Apply relevant damages and variables to the new organs. - for(var/B in bodyparts) - var/obj/item/organ/external/E = B - for(var/list/part in bodypart_damages) - var/obj/item/organ/external/OE = part[1] - if((E.type == OE.type)) // Type has to be explicit, as right limbs are a child of left ones etc. - var/brute = part[2] - var/burn = part[3] - var/IB = part[4] - //Deal the damage to the new organ and then delete the entry to prevent duplicate checks - E.receive_damage(brute, burn, ignore_resists = TRUE) - E.internal_bleeding = IB - qdel(part) - - for(var/O in internal_organs) - var/obj/item/organ/internal/I = O - for(var/list/part in internal_damages) - var/obj/item/organ/internal/OI = part[1] - var/organ_type - - if(OI.parent_type == /obj/item/organ/internal) //Dealing with species organs - organ_type = OI.type - else - organ_type = OI.parent_type - - if(istype(I, organ_type)) - var/damage = part[2] - var/broken = part[3] - I.receive_damage(damage, 1) - if(broken && !(I.status & ORGAN_BROKEN)) - I.status |= ORGAN_BROKEN - qdel(part) - - else - dna.species.create_organs(src) - - for(var/obj/item/thing in kept_items) - equip_to_slot_if_possible(thing, kept_items[thing], redraw_mob = 0) - thing.flags = item_flags[thing] // Reset the flags to the origional ones - - //Handle default hair/head accessories for created mobs. - var/obj/item/organ/external/head/H = get_organ("head") - if(dna.species.default_hair) - H.h_style = dna.species.default_hair - else - H.h_style = "Bald" - if(dna.species.default_fhair) - H.f_style = dna.species.default_fhair - else - H.f_style = "Shaved" - if(dna.species.default_headacc) - H.ha_style = dna.species.default_headacc - else - H.ha_style = "None" - - if(dna.species.default_hair_colour) - //Apply colour. - H.hair_colour = dna.species.default_hair_colour - else - H.hair_colour = "#000000" - if(dna.species.default_fhair_colour) - H.facial_colour = dna.species.default_fhair_colour - else - H.facial_colour = "#000000" - if(dna.species.default_headacc_colour) - H.headacc_colour = dna.species.default_headacc_colour - else - H.headacc_colour = "#000000" - - m_styles = DEFAULT_MARKING_STYLES //Wipes out markings, setting them all to "None". - m_colours = DEFAULT_MARKING_COLOURS //Defaults colour to #00000 for all markings. - body_accessory = null - - dna.real_name = real_name - - dna.species.on_species_gain(src) - - update_sight() - - dna.species.handle_dna(src) //Give them whatever special dna business they got. - - update_client_colour(0) - - if(!delay_icon_update) - UpdateAppearance() - - overlays.Cut() - update_mutantrace(1) - regenerate_icons() - - if(dna.species) - return TRUE - else - return FALSE - -/mob/living/carbon/human/get_default_language() - if(default_language) - return default_language - - if(!dna.species) - return null - return dna.species.default_language ? GLOB.all_languages[dna.species.default_language] : null - -/mob/living/carbon/human/proc/bloody_doodle() - set category = "IC" - set name = "Write in blood" - set desc = "Use blood on your hands to write a short message on the floor or a wall, murder mystery style." - - if(usr != src) - return 0 //something is terribly wrong - if(incapacitated()) - to_chat(src, "You can't write on the floor in your current state!") - return - if(!bloody_hands) - verbs -= /mob/living/carbon/human/proc/bloody_doodle - - if(gloves) - to_chat(src, "[gloves] are preventing you from writing anything down!") - return - - var/turf/simulated/T = loc - if(!istype(T)) //to prevent doodling out of mechs and lockers - to_chat(src, "You cannot reach the floor.") - return - - var/turf/origin = T - var/direction = input(src,"Which way?","Tile selection") as anything in list("Here","North","South","East","West") - if(direction != "Here") - T = get_step(T,text2dir(direction)) - if(!istype(T)) - to_chat(src, "You cannot doodle there.") - return - - var/num_doodles = 0 - for(var/obj/effect/decal/cleanable/blood/writing/W in T) - num_doodles++ - if(num_doodles > 4) - to_chat(src, "There is no space to write on!") - return - - var/max_length = bloody_hands * 30 //tweeter style - - var/message = stripped_input(src,"Write a message. It cannot be longer than [max_length] characters.","Blood writing", "") - if(origin != loc) - to_chat(src, "Stay still while writing!") - return - if(message) - var/used_blood_amount = round(length(message) / 30, 1) - bloody_hands = max(0, bloody_hands - used_blood_amount) //use up some blood - - if(length(message) > max_length) - message += "-" - to_chat(src, "You ran out of blood to write with!") - else - to_chat(src, "You daub '[message]' on [T] in shiny red lettering.") - var/obj/effect/decal/cleanable/blood/writing/W = new(T) - W.message = message - W.add_fingerprint(src) - -/mob/living/carbon/human/proc/get_eyecon() - var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) - var/obj/item/organ/internal/cyberimp/eyes/eye_implant = get_int_organ(/obj/item/organ/internal/cyberimp/eyes) - if(istype(dna.species) && dna.species.eyes) - var/icon/eyes_icon = new /icon('icons/mob/human_face.dmi', dna.species.eyes) - if(eye_implant) //Eye implants override native DNA eye colo(u)r - eyes_icon = eye_implant.generate_icon() - else if(eyes) - eyes_icon = eyes.generate_icon() - else //Error 404: Eyes not found! - eyes_icon.Blend("#800000", ICON_ADD) - - return eyes_icon - -/mob/living/carbon/human/proc/get_eye_shine() //Referenced cult constructs for shining in the dark. Needs to be above lighting effects such as shading. - var/obj/item/organ/external/head/head_organ = get_organ("head") - var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_full_list[head_organ.h_style] - var/icon/hair = new /icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s") - var/mutable_appearance/MA = mutable_appearance(get_icon_difference(get_eyecon(), hair), layer = ABOVE_LIGHTING_LAYER) - MA.plane = ABOVE_LIGHTING_PLANE - return MA //Cut the hair's pixels from the eyes icon so eyes covered by bangs stay hidden even while on a higher layer. - -/*Used to check if eyes should shine in the dark. Returns the image of the eyes on the layer where they will appear to shine. -Eyes need to have significantly high darksight to shine unless the mob has the XRAY vision mutation. Eyes will not shine if they are covered in any way.*/ -/mob/living/carbon/human/proc/eyes_shine() - var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) - var/obj/item/organ/internal/cyberimp/eyes/eye_implant = get_int_organ(/obj/item/organ/internal/cyberimp/eyes) - if(!(istype(eyes) || istype(eye_implant))) - return FALSE - if(!get_location_accessible(src, "eyes")) - return FALSE - if(!(eyes.shine()) && !istype(eye_implant) && !(XRAY in mutations)) //If their eyes don't shine, they don't have other augs, nor do they have X-RAY vision - return FALSE - - return TRUE - -/mob/living/carbon/human/assess_threat(var/mob/living/simple_animal/bot/secbot/judgebot, var/lasercolor) - if(judgebot.emagged == 2) - return 10 //Everyone is a criminal! - - var/threatcount = 0 - - //Lasertag bullshit - if(lasercolor) - if(lasercolor == "b")//Lasertag turrets target the opposing team, how great is that? -Sieve - if(istype(wear_suit, /obj/item/clothing/suit/redtag)) - threatcount += 4 - if((istype(r_hand,/obj/item/gun/energy/laser/tag/red)) || (istype(l_hand,/obj/item/gun/energy/laser/tag/red))) - threatcount += 4 - if(istype(belt, /obj/item/gun/energy/laser/tag/red)) - threatcount += 2 - - if(lasercolor == "r") - if(istype(wear_suit, /obj/item/clothing/suit/bluetag)) - threatcount += 4 - if((istype(r_hand,/obj/item/gun/energy/laser/tag/blue)) || (istype(l_hand,/obj/item/gun/energy/laser/tag/blue))) - threatcount += 4 - if(istype(belt, /obj/item/gun/energy/laser/tag/blue)) - threatcount += 2 - - return threatcount - - //Check for ID - var/obj/item/card/id/idcard = get_idcard() - if(judgebot.idcheck && !idcard) - threatcount += 4 - - //Check for weapons - if(judgebot.weaponscheck) - if(!idcard || !(ACCESS_WEAPONS in idcard.access)) - if(judgebot.check_for_weapons(l_hand)) - threatcount += 4 - if(judgebot.check_for_weapons(r_hand)) - threatcount += 4 - if(judgebot.check_for_weapons(belt)) - threatcount += 2 - - //Check for arrest warrant - if(judgebot.check_records) - var/perpname = get_visible_name(TRUE) - var/datum/data/record/R = find_record("name", perpname, data_core.security) - if(R && R.fields["criminal"]) - switch(R.fields["criminal"]) - if("*Execute*") - threatcount += 7 - if("*Arrest*") - threatcount += 5 - if("Incarcerated") - threatcount += 2 - if("Parolled") - threatcount += 2 - - //Check for dresscode violations - if(istype(head, /obj/item/clothing/head/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard)) - threatcount += 2 - - - //Mindshield implants imply slight trustworthiness - if(ismindshielded(src)) - threatcount -= 1 - - //Agent cards lower threatlevel. - if(istype(idcard, /obj/item/card/id/syndicate)) - threatcount -= 5 - - return threatcount - -/mob/living/carbon/human/singularity_act() - . = 20 - if(mind) - if((mind.assigned_role == "Station Engineer") || (mind.assigned_role == "Chief Engineer") ) - . = 100 - if(mind.assigned_role == "Clown") - . = rand(-1000, 1000) - ..() //Called afterwards because getting the mind after getting gibbed is sketchy - -/mob/living/carbon/human/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_THREE) - var/list/handlist = list(l_hand, r_hand) - for(var/obj/item/hand in handlist) - if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && unEquip(hand)) - step_towards(hand, src) - to_chat(src, "\The [S] pulls \the [hand] from your grip!") - apply_effect(current_size * 3, IRRADIATE) - -/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/human/H) - if(H == src) - to_chat(src, "You cannot perform CPR on yourself!") - return - if(H.stat == DEAD || (H.status_flags & FAKEDEATH)) - to_chat(src, "[H.name] is dead!") - return - if(!check_has_mouth()) - to_chat(src, "You don't have a mouth, you cannot perform CPR!") - return - if(!H.check_has_mouth()) - to_chat(src, "They don't have a mouth, you cannot perform CPR!") - return - if((head && (head.flags_cover & HEADCOVERSMOUTH)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && !wear_mask.mask_adjusted)) - to_chat(src, "Remove your mask first!") - return - if((H.head && (H.head.flags_cover & HEADCOVERSMOUTH)) || (H.wear_mask && (H.wear_mask.flags_cover & MASKCOVERSMOUTH) && !H.wear_mask.mask_adjusted)) - to_chat(src, "Remove [H.p_their()] mask first!") - return - if(H.receiving_cpr) // To prevent spam stacking - to_chat(src, "They are already receiving CPR!") - return - visible_message("[src] is trying to perform CPR on [H.name]!", "You try to perform CPR on [H.name]!") - H.receiving_cpr = TRUE - if(do_mob(src, H, 40)) - if(H.health <= HEALTH_THRESHOLD_CRIT) - H.adjustOxyLoss(-15) - H.SetLoseBreath(0) - H.AdjustParalysis(-1) - H.updatehealth("cpr") - visible_message("[src] performs CPR on [H.name]!", "You perform CPR on [H.name].") - - to_chat(H, "You feel a breath of fresh air enter your lungs. It feels good.") - H.receiving_cpr = FALSE - add_attack_logs(src, H, "CPRed", ATKLOG_ALL) - return TRUE - else - H.receiving_cpr = FALSE - to_chat(src, "You need to stay still while performing CPR!") - -/mob/living/carbon/human/canBeHandcuffed() - if(get_num_arms() >= 2) - return TRUE - else - return FALSE - -/mob/living/carbon/human/has_mutated_organs() - for(var/obj/item/organ/external/E in bodyparts) - if(E.status & ORGAN_MUTATED) - return TRUE - return FALSE - -/mob/living/carbon/human/InCritical() - return (health <= HEALTH_THRESHOLD_CRIT && stat == UNCONSCIOUS) - - -/mob/living/carbon/human/IsAdvancedToolUser() - if(dna.species.has_fine_manipulation) - return TRUE - return FALSE - -/mob/living/carbon/human/get_permeability_protection() - var/list/prot = list("hands"=0, "chest"=0, "groin"=0, "legs"=0, "feet"=0, "arms"=0, "head"=0) - for(var/obj/item/I in get_equipped_items()) - if(I.body_parts_covered & HANDS) - prot["hands"] = max(1 - I.permeability_coefficient, prot["hands"]) - if(I.body_parts_covered & UPPER_TORSO) - prot["chest"] = max(1 - I.permeability_coefficient, prot["chest"]) - if(I.body_parts_covered & LOWER_TORSO) - prot["groin"] = max(1 - I.permeability_coefficient, prot["groin"]) - if(I.body_parts_covered & LEGS) - prot["legs"] = max(1 - I.permeability_coefficient, prot["legs"]) - if(I.body_parts_covered & FEET) - prot["feet"] = max(1 - I.permeability_coefficient, prot["feet"]) - if(I.body_parts_covered & ARMS) - prot["arms"] = max(1 - I.permeability_coefficient, prot["arms"]) - if(I.body_parts_covered & HEAD) - prot["head"] = max(1 - I.permeability_coefficient, prot["head"]) - var/protection = (prot["head"] + prot["arms"] + prot["feet"] + prot["legs"] + prot["groin"] + prot["chest"] + prot["hands"])/7 - return protection - -/mob/living/carbon/human/proc/get_full_print() - if(!dna || !dna.uni_identity) - return - return md5(dna.uni_identity) - -/mob/living/carbon/human/can_see_reagents() - for(var/obj/item/clothing/C in src) //If they have some clothing equipped that lets them see reagents, they can see reagents - if(C.scan_reagents) - return 1 - -/mob/living/carbon/human/can_eat(flags = 255) - return dna.species && (dna.species.dietflags & flags) - -/mob/living/carbon/human/selfFeed(var/obj/item/reagent_containers/food/toEat, fullness) - if(!check_has_mouth()) - to_chat(src, "Where do you intend to put \the [toEat]? You don't have a mouth!") - return 0 - return ..() - -/mob/living/carbon/human/forceFed(var/obj/item/reagent_containers/food/toEat, mob/user, fullness) - if(!check_has_mouth()) - if(!((istype(toEat, /obj/item/reagent_containers/food/drinks) && (ismachine(src))))) - to_chat(user, "Where do you intend to put \the [toEat]? \The [src] doesn't have a mouth!") - return 0 - return ..() - -/mob/living/carbon/human/selfDrink(var/obj/item/reagent_containers/food/drinks/toDrink) - if(!check_has_mouth()) - if(!ismachine(src)) - to_chat(src, "Where do you intend to put \the [src]? You don't have a mouth!") - return 0 - else - to_chat(src, "You pour a bit of liquid from [toDrink] into your connection port.") - else - to_chat(src, "You swallow a gulp of [toDrink].") - return 1 - -/mob/living/carbon/human/can_track(mob/living/user) - if(wear_id) - var/obj/item/card/id/id = wear_id - if(istype(id) && id.is_untrackable()) - return 0 - if(wear_pda) - var/obj/item/pda/pda = wear_pda - if(istype(pda)) - var/obj/item/card/id/id = pda.id - if(istype(id) && id.is_untrackable()) - return 0 - if(istype(head, /obj/item/clothing/head)) - var/obj/item/clothing/head/hat = head - if(hat.blockTracking) - return 0 - - return ..() - -/mob/living/carbon/human/proc/get_age_pitch() - return 1.0 + 0.5*(30 - age)/80 - -/mob/living/carbon/human/get_access() - . = ..() - - if(wear_id) - . |= wear_id.GetAccess() - if(wear_pda) - . |= wear_pda.GetAccess() - if(istype(w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = w_uniform - if(U.accessories) - for(var/obj/item/clothing/accessory/A in U.accessories) - . |= A.GetAccess() - -/mob/living/carbon/human/is_mechanical() - return ..() || (dna.species.bodyflags & ALL_RPARTS) != 0 - -/mob/living/carbon/human/can_use_guns(var/obj/item/gun/G) - . = ..() - - if(G.trigger_guard == TRIGGER_GUARD_NORMAL) - if(HULK in mutations) - to_chat(src, "Your meaty finger is much too large for the trigger guard!") - return 0 - if(NOGUNS in dna.species.species_traits) - to_chat(src, "Your fingers don't fit in the trigger guard!") - return 0 - - if(martial_art && martial_art.no_guns) //great dishonor to famiry - to_chat(src, "[martial_art.no_guns_message]") - return 0 - - return . - -/mob/living/carbon/human/proc/change_icobase(var/new_icobase, var/new_deform, var/owner_sensitive) - for(var/obj/item/organ/external/O in bodyparts) - O.change_organ_icobase(new_icobase, new_deform, owner_sensitive) //Change the icobase/deform of all our organs. If owner_sensitive is set, that means the proc won't mess with frankenstein limbs. - -/mob/living/carbon/human/serialize() - // Currently: Limbs/organs only - var/list/data = ..() - var/list/limbs_list = list() - var/list/organs_list = list() - var/list/equip_list = list() - data["limbs"] = limbs_list - data["iorgans"] = organs_list - data["equip"] = equip_list - - data["dna"] = dna.serialize() - data["age"] = age - - // No being naked - data["ushirt"] = undershirt - data["socks"] = socks - data["uwear"] = underwear - - // Limbs - for(var/limb in bodyparts_by_name) - var/obj/item/organ/O = bodyparts_by_name[limb] - if(!O) - limbs_list[limb] = "missing" - continue - - limbs_list[limb] = O.serialize() - - // Internal organs/augments - for(var/organ in internal_organs) - var/obj/item/organ/O = organ - organs_list[O.name] = O.serialize() - - // Equipment - equip_list.len = slots_amt - for(var/i = 1, i < slots_amt, i++) - var/obj/item/thing = get_item_by_slot(i) - if(thing != null) - equip_list[i] = thing.serialize() - - return data - -/mob/living/carbon/human/deserialize(list/data) - var/list/limbs_list = data["limbs"] - var/list/organs_list = data["iorgans"] - var/list/equip_list = data["equip"] - var/turf/T = get_turf(src) - if(!islist(data["limbs"])) - throw EXCEPTION("Expected a limbs list, but found none") - - if(islist(data["dna"])) - dna.deserialize(data["dna"]) - real_name = dna.real_name - name = real_name - set_species(dna.species.type, skip_same_check = TRUE) - age = data["age"] - undershirt = data["ushirt"] - underwear = data["uwear"] - socks = data["socks"] - for(var/obj/item/organ/internal/iorgan in internal_organs) - qdel(iorgan) - - for(var/obj/item/organ/external/organ in bodyparts) - qdel(organ) - - for(var/limb in limbs_list) - // Missing means skip this part - it's missing - if(limbs_list[limb] == "missing") - continue - // "New" code handles insertion and DNA sync'ing - var/obj/item/organ/external/E = list_to_object(limbs_list[limb], src) - E.sync_colour_to_dna() - - for(var/organ in organs_list) - // As above, "New" code handles insertion, DNA sync - list_to_object(organs_list[organ], src) - - UpdateAppearance() - - // De-serialize equipment - // #1: Jumpsuit - // #2: Outer suit - // #3+: Everything else - if(islist(equip_list[slot_w_uniform])) - var/obj/item/clothing/C = list_to_object(equip_list[slot_w_uniform], T) - equip_to_slot_if_possible(C, slot_w_uniform) - - if(islist(equip_list[slot_wear_suit])) - var/obj/item/clothing/C = list_to_object(equip_list[slot_wear_suit], T) - equip_to_slot_if_possible(C, slot_wear_suit) - - for(var/i = 1, i < slots_amt, i++) - if(i == slot_w_uniform || i == slot_wear_suit) - continue - if(islist(equip_list[i])) - var/obj/item/clothing/C = list_to_object(equip_list[i], T) - equip_to_slot_if_possible(C, i) - update_icons() - - ..() - - -/mob/living/carbon/human/vv_get_dropdown() - . = ..() - . += "---" - .["Set Species"] = "?_src_=vars;setspecies=[UID()]" - .["Copy Outfit"] = "?_src_=vars;copyoutfit=[UID()]" - .["Make AI"] = "?_src_=vars;makeai=[UID()]" - .["Make cyborg"] = "?_src_=vars;makerobot=[UID()]" - .["Make monkey"] = "?_src_=vars;makemonkey=[UID()]" - .["Make alien"] = "?_src_=vars;makealien=[UID()]" - .["Make slime"] = "?_src_=vars;makeslime=[UID()]" - .["Make superhero"] = "?_src_=vars;makesuper=[UID()]" - . += "---" - -/mob/living/carbon/human/adjust_nutrition(change) - if(NO_HUNGER in dna.species.species_traits) - return FALSE - return ..() - -/mob/living/carbon/human/set_nutrition(change) - if(NO_HUNGER in dna.species.species_traits) - return FALSE - return ..() - -/mob/living/carbon/human/proc/special_post_clone_handling() - if(mind && mind.assigned_role == "Cluwne") //HUNKE your suffering never stops - makeCluwne() - -/mob/living/carbon/human/proc/influenceSin() - var/datum/objective/sintouched/O - switch(rand(1,7))//traditional seven deadly sins... except lust. - if(1) // acedia - log_game("[src] was influenced by the sin of Acedia.") - O = new /datum/objective/sintouched/acedia - if(2) // Gluttony - log_game("[src] was influenced by the sin of gluttony.") - O = new /datum/objective/sintouched/gluttony - if(3) // Greed - log_game("[src] was influenced by the sin of greed.") - O = new /datum/objective/sintouched/greed - if(4) // sloth - log_game("[src] was influenced by the sin of sloth.") - O = new /datum/objective/sintouched/sloth - if(5) // Wrath - log_game("[src] was influenced by the sin of wrath.") - O = new /datum/objective/sintouched/wrath - if(6) // Envy - log_game("[src] was influenced by the sin of envy.") - O = new /datum/objective/sintouched/envy - if(7) // Pride - log_game("[src] was influenced by the sin of pride.") - O = new /datum/objective/sintouched/pride - SSticker.mode.sintouched += src.mind - src.mind.objectives += O - var/obj_count = 1 - to_chat(src, "[pick(GLOB.boo_phrases)]") - -/mob/living/carbon/human/extinguish_light() - // Parent function handles stuff the human may be holding - ..() - - var/obj/item/organ/internal/lantern/O = get_int_organ(/obj/item/organ/internal/lantern) - if(O && O.glowing) - O.toggle_biolum(TRUE) - visible_message("[src] is engulfed in shadows and fades into the darkness.", "A sense of dread washes over you as you suddenly dim dark.") \ No newline at end of file +/mob/living/carbon/human + name = "unknown" + real_name = "unknown" + voice_name = "unknown" + icon = 'icons/mob/human.dmi' + icon_state = "body_m_s" + deathgasp_on_death = TRUE + var/obj/item/rig/wearing_rig // This is very not good, but it's much much better than calling get_rig() every update_canmove() call. + +/mob/living/carbon/human/New(loc) + icon = null // This is now handled by overlays -- we just keep an icon for the sake of the map editor. + if(length(args) > 1) + log_runtime(EXCEPTION("human/New called with more than 1 argument (REPORT THIS ENTIRE RUNTIME TO A CODER)")) + . = ..() + +/mob/living/carbon/human/Initialize(mapload, datum/species/new_species = /datum/species/human) + if(!dna) + dna = new /datum/dna(null) + // Species name is handled by set_species() + + set_species(new_species, 1, delay_icon_update = 1, skip_same_check = TRUE) + + ..() + + if(dna.species) + real_name = dna.species.get_random_name(gender) + name = real_name + if(mind) + mind.name = real_name + + create_reagents(330) + + martial_art = default_martial_art + + handcrafting = new() + + // Set up DNA. + if(dna) + dna.ready_dna(src) + dna.real_name = real_name + sync_organ_dna(1) + + UpdateAppearance() + +/mob/living/carbon/human/OpenCraftingMenu() + handcrafting.ui_interact(src) + +/mob/living/carbon/human/prepare_data_huds() + //Update med hud images... + ..() + //...sec hud images... + sec_hud_set_ID() + sec_hud_set_implants() + sec_hud_set_security_status() + //...and display them. + add_to_all_human_data_huds() + +/mob/living/carbon/human/Destroy() + . = ..() + SSmobs.cubemonkeys -= src + QDEL_LIST(bodyparts) + splinted_limbs.Cut() + +/mob/living/carbon/human/dummy + real_name = "Test Dummy" + status_flags = GODMODE|CANPUSH + +/mob/living/carbon/human/skrell/Initialize(mapload) + ..(mapload, /datum/species/skrell) + +/mob/living/carbon/human/tajaran/Initialize(mapload) + ..(mapload, /datum/species/tajaran) + +/mob/living/carbon/human/vulpkanin/Initialize(mapload) + ..(mapload, /datum/species/vulpkanin) + +/mob/living/carbon/human/unathi/Initialize(mapload) + ..(mapload, /datum/species/unathi) + +/mob/living/carbon/human/vox/Initialize(mapload) + ..(mapload, /datum/species/vox) + +/mob/living/carbon/human/voxarmalis/Initialize(mapload) + ..(mapload, /datum/species/vox/armalis) + +/mob/living/carbon/human/skeleton/Initialize(mapload) + ..(mapload, /datum/species/skeleton) + +/mob/living/carbon/human/kidan/Initialize(mapload) + ..(mapload, /datum/species/kidan) + +/mob/living/carbon/human/plasma/Initialize(mapload) + ..(mapload, /datum/species/plasmaman) + +/mob/living/carbon/human/slime/Initialize(mapload) + ..(mapload, /datum/species/slime) + +/mob/living/carbon/human/grey/Initialize(mapload) + ..(mapload, /datum/species/grey) + +/mob/living/carbon/human/abductor/Initialize(mapload) + ..(mapload, /datum/species/abductor) + +/mob/living/carbon/human/diona/Initialize(mapload) + ..(mapload, /datum/species/diona) + +/mob/living/carbon/human/pod_diona/Initialize(mapload) + ..(mapload, /datum/species/diona/pod) + +/mob/living/carbon/human/machine/Initialize(mapload) + ..(mapload, /datum/species/machine) + +/mob/living/carbon/human/machine/created + name = "Integrated Robotic Chassis" + +/mob/living/carbon/human/machine/created/Initialize(mapload) + ..() + rename_character(null, "Integrated Robotic Chassis ([rand(1, 9999)])") + update_dna() + for(var/obj/item/organ/external/E in bodyparts) + if(istype(E, /obj/item/organ/external/chest) || istype(E, /obj/item/organ/external/groin)) + continue + qdel(E) + for(var/obj/item/organ/O in internal_organs) + qdel(O) + regenerate_icons() + death() + +/mob/living/carbon/human/shadow/Initialize(mapload) + ..(mapload, /datum/species/shadow) + +/mob/living/carbon/human/golem/Initialize(mapload) + ..(mapload, /datum/species/golem) + +/mob/living/carbon/human/wryn/Initialize(mapload) + ..(mapload, /datum/species/wryn) + +/mob/living/carbon/human/nucleation/Initialize(mapload) + ..(mapload, /datum/species/nucleation) + +/mob/living/carbon/human/drask/Initialize(mapload) + ..(mapload, /datum/species/drask) + +/mob/living/carbon/human/monkey/Initialize(mapload) + ..(mapload, /datum/species/monkey) + +/mob/living/carbon/human/farwa/Initialize(mapload) + ..(mapload, /datum/species/monkey/tajaran) + +/mob/living/carbon/human/wolpin/Initialize(mapload) + ..(mapload, /datum/species/monkey/vulpkanin) + +/mob/living/carbon/human/neara/Initialize(mapload) + ..(mapload, /datum/species/monkey/skrell) + +/mob/living/carbon/human/stok/Initialize(mapload) + ..(mapload, /datum/species/monkey/unathi) + +/mob/living/carbon/human/Stat() + ..() + statpanel("Status") + + stat(null, "Intent: [a_intent]") + stat(null, "Move Mode: [m_intent]") + + show_stat_emergency_shuttle_eta() + + if(client.statpanel == "Status") + var/total_user_contents = GetAllContents() // cache it + if(locate(/obj/item/gps) in total_user_contents) + var/turf/T = get_turf(src) + stat(null, "GPS: [COORD(T)]") + if(locate(/obj/item/assembly/health) in total_user_contents) + stat(null, "Health: [health]") + if(internal) + if(!internal.air_contents) + qdel(internal) + else + stat("Internal Atmosphere Info", internal.name) + stat("Tank Pressure", internal.air_contents.return_pressure()) + stat("Distribution Pressure", internal.distribute_pressure) + + if(istype(back, /obj/item/rig)) + var/obj/item/rig/suit = back + var/cell_status = "ERROR" + if(suit.cell) + cell_status = "[suit.cell.charge]/[suit.cell.maxcharge]" + stat(null, "Suit charge: [cell_status]") + + // I REALLY need to split up status panel things into datums + var/mob/living/simple_animal/borer/B = has_brain_worms() + if(B && B.controlling) + stat("Chemicals", B.chemicals) + + if(mind) + if(mind.changeling) + stat("Chemical Storage", "[mind.changeling.chem_charges]/[mind.changeling.chem_storage]") + stat("Absorbed DNA", mind.changeling.absorbedcount) + + if(mind.vampire) + stat("Total Blood", "[mind.vampire.bloodtotal]") + stat("Usable Blood", "[mind.vampire.bloodusable]") + + if(istype(loc, /obj/spacepod)) // Spacdpods! + var/obj/spacepod/S = loc + stat("Spacepod Charge", "[istype(S.battery) ? "[(S.battery.charge / S.battery.maxcharge) * 100]" : "No cell detected"]") + stat("Spacepod Integrity", "[!S.health ? "0" : "[(S.health / initial(S.health)) * 100]"]%") + +/mob/living/carbon/human/ex_act(severity) + var/shielded = 0 + var/b_loss = null + var/f_loss = null + + if(status_flags & GODMODE) + return 0 + + switch(severity) + if(1) + b_loss += 500 + if(!prob(getarmor(null, "bomb"))) + gib() + return 0 + else + var/atom/target = get_edge_target_turf(src, get_dir(src, get_step_away(src, src))) + throw_at(target, 200, 4) + + var/limbs_affected = pick(2,3,4) + var/obj/item/organ/external/processing_dismember + var/list/valid_limbs = bodyparts.Copy() + + while(limbs_affected != 0 && valid_limbs.len > 0) + processing_dismember = pick(valid_limbs) + if(processing_dismember.limb_name != "chest" && processing_dismember.limb_name != "head" && processing_dismember.limb_name != "groin") + processing_dismember.droplimb(1,DROPLIMB_SHARP,0,1) + valid_limbs -= processing_dismember + limbs_affected -= 1 + else valid_limbs -= processing_dismember + + if(2) + if(!shielded) //literally nothing could change shielded before this so wth + b_loss += 60 + + f_loss += 60 + + var/limbs_affected = 0 + var/obj/item/organ/external/processing_dismember + var/list/valid_limbs = bodyparts.Copy() + + if(prob(getarmor(null, "bomb"))) + b_loss = b_loss/1.5 + f_loss = f_loss/1.5 + + limbs_affected = pick(1, 1, 2) + else + limbs_affected = pick(1, 2, 3) + + while(limbs_affected != 0 && valid_limbs.len > 0) + processing_dismember = pick(valid_limbs) + if(processing_dismember.limb_name != "chest" && processing_dismember.limb_name != "head" && processing_dismember.limb_name != "groin") + processing_dismember.droplimb(1,DROPLIMB_SHARP,0,1) + valid_limbs -= processing_dismember + limbs_affected -= 1 + else valid_limbs -= processing_dismember + + if(!istype(l_ear, /obj/item/clothing/ears/earmuffs) && !istype(r_ear, /obj/item/clothing/ears/earmuffs)) + AdjustEarDamage(30, 120) + if(prob(70) && !shielded) + Paralyse(10) + + if(3) + b_loss += 30 + if(prob(getarmor(null, "bomb"))) + b_loss = b_loss/2 + + else + + var/limbs_affected = pick(0, 1) + var/obj/item/organ/external/processing_dismember + var/list/valid_limbs = bodyparts.Copy() + + while(limbs_affected != 0 && valid_limbs.len > 0) + processing_dismember = pick(valid_limbs) + if(processing_dismember.limb_name != "chest" && processing_dismember.limb_name != "head" && processing_dismember.limb_name != "groin") + processing_dismember.droplimb(1,DROPLIMB_SHARP,0,1) + valid_limbs -= processing_dismember + limbs_affected -= 1 + else valid_limbs -= processing_dismember + + if(!istype(l_ear, /obj/item/clothing/ears/earmuffs) && !istype(r_ear, /obj/item/clothing/ears/earmuffs)) + AdjustEarDamage(15, 60) + if(prob(50) && !shielded) + Paralyse(10) + + take_overall_damage(b_loss,f_loss, TRUE, used_weapon = "Explosive Blast") + + ..() + +/mob/living/carbon/human/blob_act(obj/structure/blob/B) + if(stat == DEAD) + return + show_message("The blob attacks you!") + var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") + var/obj/item/organ/external/affecting = get_organ(ran_zone(dam_zone)) + apply_damage(5, BRUTE, affecting, run_armor_check(affecting, "melee")) + +/mob/living/carbon/human/bullet_act() + if(martial_art && martial_art.deflection_chance) //Some martial arts users can deflect projectiles! + if(!prob(martial_art.deflection_chance)) + return ..() + if(!src.lying && !(HULK in mutations)) //But only if they're not lying down, and hulks can't do it + visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons!", "You deflect the projectile!") + return 0 + ..() + +/mob/living/carbon/human/get_restraining_item() + . = ..() + if(!. && istype(wear_suit, /obj/item/clothing/suit/straight_jacket)) + . = wear_suit + +/mob/living/carbon/human/var/temperature_resistance = T0C+75 + + +/mob/living/carbon/human/show_inv(mob/user) + user.set_machine(src) + var/has_breathable_mask = istype(wear_mask, /obj/item/clothing/mask) || get_organ_slot("breathing_tube") + var/list/obscured = check_obscured_slots() + + var/dat = {" + + + "} + + dat += "" + + dat += "" + + var/obj/item/organ/internal/headpocket/C = get_int_organ(/obj/item/organ/internal/headpocket) + if(C) + if(slot_wear_mask in obscured) + dat += "" + else + var/list/items = C.get_contents() + if(items.len) + dat += "" + else + dat += "" + + if(slot_wear_mask in obscured) + dat += "" + else + dat += "" + + if(!issmall(src)) + if(slot_glasses in obscured) + dat += "" + else + dat += "" + + if(slot_l_ear in obscured) + dat += "" + else + dat += "" + + if(slot_r_ear in obscured) + dat += "" + else + dat += "" + + dat += "" + + dat += "" + if(wear_suit) + dat += "" + else + dat += "" + + if(slot_shoes in obscured) + dat += "" + else + dat += "" + + if(slot_gloves in obscured) + dat += "" + else + dat += "" + + if(slot_w_uniform in obscured) + dat += "" + else + dat += "" + + if((w_uniform == null && !(dna && dna.species.nojumpsuit)) || (slot_w_uniform in obscured)) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + else + dat += "" + // Pockets + dat += "" + dat += "" + dat += "" + + if(istype(w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = w_uniform + dat += "" + + if(U.accessories.len) + dat += "" + + + if(handcuffed) + dat += "" + if(legcuffed) + dat += "" + + dat += {"
        Left Hand:[(l_hand && !(l_hand.flags&ABSTRACT)) ? l_hand : "Empty"]
        Right Hand:[(r_hand && !(r_hand.flags&ABSTRACT)) ? r_hand : "Empty"]
         
        Back:[(back && !(back.flags&ABSTRACT)) ? back : "Empty"]" + if(has_breathable_mask && istype(back, /obj/item/tank)) + dat += " [internal ? "Disable Internals" : "Set Internals"]" + + dat += "
         
        Head:[(head && !(head.flags&ABSTRACT)) ? head : "Empty"]
         ↳Headpocket:Obscured
         ↳Headpocket:Dislodge Items
         ↳Headpocket:Empty
        Mask:Obscured
        Mask:[(wear_mask && !(wear_mask.flags&ABSTRACT)) ? wear_mask : "Empty"]" + + if(istype(wear_mask, /obj/item/clothing/mask/muzzle)) + var/obj/item/clothing/mask/muzzle/M = wear_mask + if(M.security_lock) + dat += " [M.locked ? "Disable Lock" : "Set Lock"]" + + dat += "
         
        Eyes:Obscured
        Eyes:[(glasses && !(glasses.flags&ABSTRACT)) ? glasses : "Empty"]
        Left Ear:Obscured
        Left Ear:[(l_ear && !(l_ear.flags&ABSTRACT)) ? l_ear : "Empty"]
        Right Ear:Obscured
        Right Ear:[(r_ear && !(r_ear.flags&ABSTRACT)) ? r_ear : "Empty"]
         
        Exosuit:[(wear_suit && !(wear_suit.flags&ABSTRACT)) ? wear_suit : "Empty"]
         ↳Suit Storage:[(s_store && !(s_store.flags&ABSTRACT)) ? s_store : "Empty"]" + if(has_breathable_mask && istype(s_store, /obj/item/tank)) + dat += " [internal ? "Disable Internals" : "Set Internals"]" + dat += "
         ↳Suit Storage:
        Shoes:Obscured
        Shoes:[(shoes && !(shoes.flags&ABSTRACT)) ? shoes : "Empty"]
        Gloves:Obscured
        Gloves:[(gloves && !(gloves.flags&ABSTRACT)) ? gloves : "Empty"]
        Uniform:Obscured
        Uniform:[(w_uniform && !(w_uniform.flags&ABSTRACT)) ? w_uniform : "Empty"]
         ↳Pockets:
         ↳ID:
         ↳Belt:
         ↳Suit Sensors:
         ↳PDA:
         ↳Belt:[(belt && !(belt.flags&ABSTRACT)) ? belt : "Empty"]" + if(has_breathable_mask && istype(belt, /obj/item/tank)) + dat += " [internal ? "Disable Internals" : "Set Internals"]" + dat += "
         ↳Pockets:" + if(l_store && internal && l_store == internal) + dat += "[l_store]" + else if(l_store && !(l_store.flags&ABSTRACT)) + dat += "Left (Full)" + else + dat += "Left (Empty)" + dat += " " + if(r_store && internal && r_store == internal) + dat += "[r_store]" + else if(r_store && !(r_store.flags&ABSTRACT)) + dat += "Right (Full)" + else + dat += "Right (Empty)" + dat += "
         ↳ID:[(wear_id && !(wear_id.flags&ABSTRACT)) ? wear_id : "Empty"]
         ↳PDA:[(wear_pda && !(wear_pda.flags&ABSTRACT)) ? wear_pda : "Empty"]
         ↳Suit Sensors:[U.has_sensor >= 2 ? "--SENSORS LOCKED--" : "Set Sensors"]
         ↳Remove Accessory
        Handcuffed: Remove
        Legcuffed
        + Close + "} + + var/datum/browser/popup = new(user, "mob\ref[src]", "[src]", 440, 540) + popup.set_content(dat) + popup.open() + +// Get rank from ID, ID inside PDA, PDA, ID in wallet, etc. +/mob/living/carbon/human/proc/get_authentification_rank(var/if_no_id = "No id", var/if_no_job = "No job") + var/obj/item/pda/pda = wear_id + if(istype(pda)) + if(pda.id) + return pda.id.rank + else + return pda.ownrank + else + var/obj/item/card/id/id = get_idcard() + if(id) + return id.rank ? id.rank : if_no_job + else + return if_no_id + +//gets assignment from ID or ID inside PDA or PDA itself +//Useful when player do something with computers +/mob/living/carbon/human/proc/get_assignment(var/if_no_id = "No id", var/if_no_job = "No job") + var/obj/item/pda/pda = wear_id + var/obj/item/card/id/id = wear_id + if(istype(pda)) + if(pda.id && istype(pda.id, /obj/item/card/id)) + . = pda.id.assignment + else + . = pda.ownjob + else if(istype(id)) + . = id.assignment + else + return if_no_id + if(!.) + . = if_no_job + return + +//gets name from ID or ID inside PDA or PDA itself +//Useful when player do something with computers +/mob/living/carbon/human/proc/get_authentification_name(var/if_no_id = "Unknown") + var/obj/item/pda/pda = wear_id + var/obj/item/card/id/id = wear_id + if(istype(pda)) + if(pda.id) + . = pda.id.registered_name + else + . = pda.owner + else if(istype(id)) + . = id.registered_name + else + return if_no_id + return + +//repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a seperate proc as it'll be useful elsewhere +/mob/living/carbon/human/get_visible_name(var/id_override = FALSE) + if(name_override) + return name_override + if(wear_mask && (wear_mask.flags_inv & HIDEFACE)) //Wearing a mask which hides our face, use id-name if possible + return get_id_name("Unknown") + if(head && (head.flags_inv & HIDEFACE)) + return get_id_name("Unknown") //Likewise for hats + var/face_name = get_face_name() + var/id_name = get_id_name("") + if(id_name && (id_name != face_name) && !id_override) + return "[face_name] (as [id_name])" + return face_name + +//Returns "Unknown" if facially disfigured and real_name if not. Useful for setting name when polyacided or when updating a human's name variable +/mob/living/carbon/human/proc/get_face_name() + var/obj/item/organ/external/head = get_organ("head") + if(!head || head.disfigured || cloneloss > 50 || !real_name || (HUSK in mutations)) //disfigured. use id-name if possible + return "Unknown" + return real_name + +//gets name from ID or PDA itself, ID inside PDA doesn't matter +//Useful when player is being seen by other mobs +/mob/living/carbon/human/proc/get_id_name(var/if_no_id = "Unknown") + var/obj/item/pda/pda = wear_id + var/obj/item/card/id/id = wear_id + if(istype(pda)) . = pda.owner + else if(istype(id)) . = id.registered_name + if(!.) . = if_no_id //to prevent null-names making the mob unclickable + return + +//gets ID card object from special clothes slot or, if applicable, hands as well +/mob/living/carbon/human/proc/get_idcard(var/check_hands = FALSE) + var/obj/item/card/id/id = wear_id + var/obj/item/pda/pda = wear_id + if(istype(pda) && pda.id) + id = pda.id + + if(check_hands) + if(istype(get_active_hand(), /obj/item/card/id)) + id = get_active_hand() + else if(istype(get_inactive_hand(), /obj/item/card/id)) + id = get_inactive_hand() + + if(istype(id)) + return id + +/mob/living/carbon/human/update_sight() + if(!client) + return + + if(stat == DEAD) + grant_death_vision() + return + + dna.species.update_sight(src) + SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) + sync_lighting_plane_alpha() + +//Added a safety check in case you want to shock a human mob directly through electrocute_act. +/mob/living/carbon/human/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE) + if(tesla_shock) + var/total_coeff = 1 + if(gloves) + var/obj/item/clothing/gloves/G = gloves + if(G.siemens_coefficient <= 0) + total_coeff -= 0.5 + if(wear_suit) + var/obj/item/clothing/suit/S = wear_suit + if(S.siemens_coefficient <= 0) + total_coeff -= 0.95 + else if(S.siemens_coefficient == (-1)) + total_coeff -= 1 + siemens_coeff = total_coeff + if(tesla_ignore) + siemens_coeff = 0 + else if(!safety) + var/gloves_siemens_coeff = 1 + if(gloves) + var/obj/item/clothing/gloves/G = gloves + gloves_siemens_coeff = G.siemens_coefficient + siemens_coeff = gloves_siemens_coeff + if(undergoing_cardiac_arrest() && !illusion) + if(shock_damage * siemens_coeff >= 1 && prob(25)) + set_heartattack(FALSE) + if(stat == CONSCIOUS) + to_chat(src, "You feel your heart beating again!") + + dna.species.spec_electrocute_act(src, shock_damage, source, siemens_coeff, safety, override, tesla_shock, illusion, stun) + . = ..(shock_damage, source, siemens_coeff, safety, override, tesla_shock, illusion, stun) + +/mob/living/carbon/human/Topic(href, href_list) + if(!usr.stat && usr.canmove && !usr.restrained() && in_range(src, usr)) + var/thief_mode = 0 + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + var/obj/item/clothing/gloves/G = H.gloves + if(G && G.pickpocket) + thief_mode = 1 + + if(href_list["embedded_object"]) + var/obj/item/organ/external/L = locate(href_list["embedded_limb"]) in bodyparts + if(!L) + return + var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects + if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore + return + var/time_taken = I.embedded_unsafe_removal_time*I.w_class + usr.visible_message("[usr] attempts to remove [I] from [usr.p_their()] [L.name].","You attempt to remove [I] from your [L.name]... (It will take [time_taken/10] seconds.)") + if(do_after(usr, time_taken, needhand = 1, target = src)) + if(!I || !L || I.loc != src || !(I in L.embedded_objects)) + return + L.embedded_objects -= I + L.receive_damage(I.embedded_unsafe_removal_pain_multiplier*I.w_class)//It hurts to rip it out, get surgery you dingus. + I.forceMove(get_turf(src)) + usr.put_in_hands(I) + usr.emote("scream") + usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!","You successfully remove [I] from your [L.name].") + if(!has_embedded_objects()) + clear_alert("embeddedobject") + return + + if(href_list["item"]) + var/slot = text2num(href_list["item"]) + if(slot in check_obscured_slots()) + to_chat(usr, "You can't reach that! Something is covering it.") + return + + if(href_list["pockets"]) + var/pocket_side = href_list["pockets"] + var/pocket_id = (pocket_side == "right" ? slot_r_store : slot_l_store) + var/obj/item/pocket_item = (pocket_id == slot_r_store ? r_store : l_store) + var/obj/item/place_item = usr.get_active_hand() // Item to place in the pocket, if it's empty + + var/delay_denominator = 1 + if(pocket_item && !(pocket_item.flags&ABSTRACT)) + if(pocket_item.flags & NODROP) + to_chat(usr, "You try to empty [src]'s [pocket_side] pocket, it seems to be stuck!") + to_chat(usr, "You try to empty [src]'s [pocket_side] pocket.") + else if(place_item && place_item.mob_can_equip(src, pocket_id, 1) && !(place_item.flags&ABSTRACT)) + to_chat(usr, "You try to place [place_item] into [src]'s [pocket_side] pocket.") + delay_denominator = 4 + else + return + + if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator)) //placing an item into the pocket is 4 times faster + if(pocket_item) + if(pocket_item == (pocket_id == slot_r_store ? r_store : l_store)) //item still in the pocket we search + unEquip(pocket_item) + if(thief_mode) + usr.put_in_hands(pocket_item) + add_attack_logs(usr, src, "Stripped of [pocket_item]", isLivingSSD(src) ? null : ATKLOG_ALL) + else + if(place_item) + usr.unEquip(place_item) + equip_to_slot_if_possible(place_item, pocket_id, 0, 1) + add_attack_logs(usr, src, "Equipped with [place_item]", isLivingSSD(src) ? null : ATKLOG_ALL) + + // Update strip window + if(usr.machine == src && in_range(src, usr)) + show_inv(usr) + else + // Display a warning if the user mocks up if they don't have pickpocket gloves. + if(!thief_mode) + to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") + add_attack_logs(usr, src, "Attempted strip of [pocket_item]", isLivingSSD(src) ? null : ATKLOG_ALL) + + if(href_list["set_sensor"]) + if(istype(w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = w_uniform + U.set_sensors(usr) + + if(href_list["dislodge_headpocket"]) + usr.visible_message("[usr] is trying to remove something from [src]'s head!", + "You start to dislodge whatever's inside [src]'s headpocket!") + if(do_mob(usr, src, POCKET_STRIP_DELAY)) + usr.visible_message("[usr] has dislodged something from [src]'s head!", + "You have dislodged everything from [src]'s headpocket!") + var/obj/item/organ/internal/headpocket/C = get_int_organ(/obj/item/organ/internal/headpocket) + C.empty_contents() + add_attack_logs(usr, src, "Stripped of headpocket items", isLivingSSD(src) ? null : ATKLOG_ALL) + + if(href_list["strip_accessory"]) + if(istype(w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = w_uniform + if(U.accessories.len) + var/obj/item/clothing/accessory/A = U.accessories[1] + if(!thief_mode) + usr.visible_message("\The [usr] starts to take off \the [A] from \the [src]'s [U]!", \ + "You start to take off \the [A] from \the [src]'s [U]!") + + if(do_mob(usr, src, 40) && A && U.accessories.len) + if(!thief_mode) + usr.visible_message("\The [usr] takes \the [A] off of \the [src]'s [U]!", \ + "You take \the [A] off of \the [src]'s [U]!") + A.on_removed(usr) + U.accessories -= A + update_inv_w_uniform() + + if(href_list["criminal"]) + if(hasHUD(usr,"security")) + if(usr.incapacitated()) + return + var/found_record = 0 + var/perpname = get_visible_name(TRUE) + + if(perpname != "Unknown") + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.security) + if(R.fields["id"] == E.fields["id"]) + + var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Parolled", "Released", "Cancel") + var/t1 = copytext(trim(sanitize(input("Enter Reason:", "Security HUD", null, null) as text)), 1, MAX_MESSAGE_LEN) + if(!t1) + t1 = "(none)" + + if(hasHUD(usr, "security") && setcriminal != "Cancel") + found_record = 1 + if(R.fields["criminal"] == "*Execute*") + to_chat(usr, "Unable to modify the sec status of a person with an active Execution order. Use a security computer instead.") + else + var/rank + if(ishuman(usr)) + var/mob/living/carbon/human/U = usr + rank = U.get_assignment() + else if(isrobot(usr)) + var/mob/living/silicon/robot/U = usr + rank = "[U.modtype] [U.braintype]" + else if(isAI(usr)) + rank = "AI" + set_criminal_status(usr, R, setcriminal, t1, rank) + break // Git out of the securiy records loop! + if(found_record) + break // Git out of the general records + + if(!found_record) + to_chat(usr, "Unable to locate a data core entry for this person.") + + if(href_list["secrecord"]) + if(hasHUD(usr,"security")) + if(usr.incapacitated()) + return + var/perpname = get_visible_name(TRUE) + var/read = 0 + + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.security) + if(R.fields["id"] == E.fields["id"]) + if(hasHUD(usr,"security")) + to_chat(usr, "Name: [R.fields["name"]] Criminal Status: [R.fields["criminal"]]") + to_chat(usr, "Minor Crimes: [R.fields["mi_crim"]]") + to_chat(usr, "Details: [R.fields["mi_crim_d"]]") + to_chat(usr, "Major Crimes: [R.fields["ma_crim"]]") + to_chat(usr, "Details: [R.fields["ma_crim_d"]]") + to_chat(usr, "Notes: [R.fields["notes"]]") + to_chat(usr, "\[View Comment Log\]") + read = 1 + + if(!read) + to_chat(usr, "Unable to locate a data core entry for this person.") + + if(href_list["secrecordComment"]) + if(hasHUD(usr,"security")) + if(usr.incapacitated()) + return + var/perpname = get_visible_name(TRUE) + var/read = 0 + + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.security) + if(R.fields["id"] == E.fields["id"]) + if(hasHUD(usr,"security")) + read = 1 + if(LAZYLEN(R.fields["comments"])) + for(var/c in R.fields["comments"]) + to_chat(usr, c) + else + to_chat(usr, "No comment found") + to_chat(usr, "\[Add comment\]") + + if(!read) + to_chat(usr, "Unable to locate a data core entry for this person.") + + if(href_list["secrecordadd"]) + if(hasHUD(usr,"security")) + if(usr.incapacitated()) + return + var/perpname = get_visible_name(TRUE) + + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.security) + if(R.fields["id"] == E.fields["id"]) + if(hasHUD(usr,"security")) + var/t1 = copytext(trim(sanitize(input("Add Comment:", "Sec. records", null, null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || usr.stat || usr.restrained() || !hasHUD(usr, "security")) + return + if(ishuman(usr)) + var/mob/living/carbon/human/U = usr + R.fields["comments"] += "Made by [U.get_authentification_name()] ([U.get_assignment()]) on [current_date_string] [station_time_timestamp()]
        [t1]" + if(isrobot(usr)) + var/mob/living/silicon/robot/U = usr + R.fields["comments"] += "Made by [U.name] ([U.modtype] [U.braintype]) on [current_date_string] [station_time_timestamp()]
        [t1]" + if(isAI(usr)) + var/mob/living/silicon/ai/U = usr + R.fields["comments"] += "Made by [U.name] (artificial intelligence) on [current_date_string] [station_time_timestamp()]
        [t1]" + + if(href_list["medical"]) + if(hasHUD(usr,"medical")) + if(usr.incapacitated()) + return + var/modified = 0 + var/perpname = get_visible_name(TRUE) + + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.general) + if(R.fields["id"] == E.fields["id"]) + var/setmedical = input(usr, "Specify a new medical status for this person.", "Medical HUD", R.fields["p_stat"]) in list("*SSD*", "*Deceased*", "Physically Unfit", "Active", "Disabled", "Cancel") + + if(hasHUD(usr,"medical")) + if(setmedical != "Cancel") + R.fields["p_stat"] = setmedical + modified = 1 + if(PDA_Manifest.len) + PDA_Manifest.Cut() + + spawn() + sec_hud_set_security_status() + + if(!modified) + to_chat(usr, "Unable to locate a data core entry for this person.") + + if(href_list["medrecord"]) + if(hasHUD(usr,"medical")) + if(usr.incapacitated()) + return + var/read = 0 + var/perpname = get_visible_name(TRUE) + + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.medical) + if(R.fields["id"] == E.fields["id"]) + if(hasHUD(usr,"medical")) + to_chat(usr, "Name: [R.fields["name"]] Blood Type: [R.fields["b_type"]]") + to_chat(usr, "DNA: [R.fields["b_dna"]]") + to_chat(usr, "Minor Disabilities: [R.fields["mi_dis"]]") + to_chat(usr, "Details: [R.fields["mi_dis_d"]]") + to_chat(usr, "Major Disabilities: [R.fields["ma_dis"]]") + to_chat(usr, "Details: [R.fields["ma_dis_d"]]") + to_chat(usr, "Notes: [R.fields["notes"]]") + to_chat(usr, "\[View Comment Log\]") + read = 1 + + if(!read) + to_chat(usr, "Unable to locate a data core entry for this person.") + + if(href_list["medrecordComment"]) + if(hasHUD(usr,"medical")) + if(usr.incapacitated()) + return + var/perpname = get_visible_name(TRUE) + var/read = 0 + + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.medical) + if(R.fields["id"] == E.fields["id"]) + if(hasHUD(usr,"medical")) + read = 1 + if(LAZYLEN(R.fields["comments"])) + for(var/c in R.fields["comments"]) + to_chat(usr, c) + else + to_chat(usr, "No comment found") + to_chat(usr, "\[Add comment\]") + + if(!read) + to_chat(usr, "Unable to locate a data core entry for this person.") + + if(href_list["medrecordadd"]) + if(hasHUD(usr,"medical")) + if(usr.incapacitated()) + return + var/perpname = get_visible_name(TRUE) + for(var/datum/data/record/E in data_core.general) + if(E.fields["name"] == perpname) + for(var/datum/data/record/R in data_core.medical) + if(R.fields["id"] == E.fields["id"]) + if(hasHUD(usr,"medical")) + var/t1 = copytext(trim(sanitize(input("Add Comment:", "Med. records", null, null) as message)), 1, MAX_MESSAGE_LEN) + if(!t1 || usr.stat || usr.restrained() || !hasHUD(usr, "medical")) + return + if(ishuman(usr)) + var/mob/living/carbon/human/U = usr + R.fields["comments"] += "Made by [U.get_authentification_name()] ([U.get_assignment()]) on [current_date_string] [station_time_timestamp()]
        [t1]" + if(isrobot(usr)) + var/mob/living/silicon/robot/U = usr + R.fields["comments"] += "Made by [U.name] ([U.modtype] [U.braintype]) on [current_date_string] [station_time_timestamp()]
        [t1]" + if(isAI(usr)) + var/mob/living/silicon/ai/U = usr + R.fields["comments"] += "Made by [U.name] (artificial intelligence) on [current_date_string] [station_time_timestamp()]
        [t1]" + + if(href_list["lookitem"]) + var/obj/item/I = locate(href_list["lookitem"]) + src.examinate(I) + + if(href_list["lookmob"]) + var/mob/M = locate(href_list["lookmob"]) + src.examinate(M) + . = ..() + + +///check_eye_prot() +///Returns a number between -1 to 2 +/mob/living/carbon/human/check_eye_prot() + var/number = ..() + if(istype(head, /obj/item/clothing/head)) //are they wearing something on their head + var/obj/item/clothing/head/HFP = head //if yes gets the flash protection value from that item + number += HFP.flash_protect + if(istype(glasses, /obj/item/clothing/glasses)) //glasses + var/obj/item/clothing/glasses/GFP = glasses + number += GFP.flash_protect + if(istype(wear_mask, /obj/item/clothing/mask)) //mask + var/obj/item/clothing/mask/MFP = wear_mask + number += MFP.flash_protect + for(var/obj/item/organ/internal/cyberimp/eyes/EFP in internal_organs) + number += EFP.flash_protect + + return number + +/mob/living/carbon/human/check_ear_prot() + if(head && (head.flags & HEADBANGPROTECT)) + return 1 + if(l_ear && (l_ear.flags & EARBANGPROTECT)) + return 1 + if(r_ear && (r_ear.flags & EARBANGPROTECT)) + return 1 + +///tintcheck() +///Checks eye covering items for visually impairing tinting, such as welding masks +///Checked in life.dm. 0 & 1 = no impairment, 2 = welding mask overlay, 3 = You can see jack, but you can't see shit. +/mob/living/carbon/human/tintcheck() + var/tinted = 0 + if(istype(src.head, /obj/item/clothing/head)) + var/obj/item/clothing/head/HT = src.head + tinted += HT.tint + if(istype(src.glasses, /obj/item/clothing/glasses)) + var/obj/item/clothing/glasses/GT = src.glasses + tinted += GT.tint + if(istype(src.wear_mask, /obj/item/clothing/mask)) + var/obj/item/clothing/mask/MT = src.wear_mask + tinted += MT.tint + + //god help me + if(istype(back, /obj/item/rig)) + var/obj/item/rig/O = back + if(O.helmet && O.helmet == head && (O.helmet.body_parts_covered & HEAD)) + if((O.offline && O.offline_vision_restriction == 1) || (!O.offline && O.vision_restriction == 1)) + tinted = 2 + if((O.offline && O.offline_vision_restriction == 2) || (!O.offline && O.vision_restriction == 2)) + tinted = 3 + //im so sorry + + return tinted + + +/mob/living/carbon/human/abiotic(var/full_body = 0) + if(full_body && ((src.l_hand && !(src.l_hand.flags & ABSTRACT)) || (src.r_hand && !(src.r_hand.flags & ABSTRACT)) || (src.back || src.wear_mask || src.head || src.shoes || src.w_uniform || src.wear_suit || src.glasses || src.l_ear || src.r_ear || src.gloves))) + return 1 + + if((src.l_hand && !(src.l_hand.flags & ABSTRACT)) || (src.r_hand && !(src.r_hand.flags & ABSTRACT))) + return 1 + + return 0 + + +/mob/living/carbon/human/proc/check_dna() + dna.check_integrity(src) + +/mob/living/carbon/human/proc/play_xylophone() + if(!src.xylophone) + visible_message("[src] begins playing [p_their()] ribcage like a xylophone. It's quite spooky.","You begin to play a spooky refrain on your ribcage.","You hear a spooky xylophone melody.") + var/song = pick('sound/effects/xylophone1.ogg','sound/effects/xylophone2.ogg','sound/effects/xylophone3.ogg') + playsound(loc, song, 50, 1, -1) + xylophone = 1 + spawn(1200) + xylophone=0 + return + +/mob/living/carbon/human/can_inject(mob/user, error_msg, target_zone, penetrate_thick = FALSE) + . = 1 + + if(!target_zone) + if(!user) + target_zone = pick("chest","chest","chest","left leg","right leg","left arm", "right arm", "head") + else + target_zone = user.zone_selected + + + if(PIERCEIMMUNE in dna.species.species_traits) + . = 0 + + var/obj/item/organ/external/affecting = get_organ(target_zone) + var/fail_msg + if(!affecting) + . = 0 + fail_msg = "[p_they(TRUE)] [p_are()] missing that limb." + else if(affecting.is_robotic()) + . = 0 + fail_msg = "That limb is robotic." + else + switch(target_zone) + if("head") + if(head && head.flags & THICKMATERIAL && !penetrate_thick) + . = 0 + else + if(wear_suit && wear_suit.flags & THICKMATERIAL && !penetrate_thick) + . = 0 + if(!. && error_msg && user) + if(!fail_msg) + fail_msg = "There is no exposed flesh or thin material [target_zone == "head" ? "on [p_their()] head" : "on [p_their()] body"] to inject into." + to_chat(user, "[fail_msg]") + +/mob/living/carbon/human/proc/check_obscured_slots() + var/list/obscured = list() + + if(wear_suit) + if(wear_suit.flags_inv & HIDEGLOVES) + obscured |= slot_gloves + if(wear_suit.flags_inv & HIDEJUMPSUIT) + obscured |= slot_w_uniform + if(wear_suit.flags_inv & HIDESHOES) + obscured |= slot_shoes + + if(head) + if(head.flags_inv & HIDEMASK) + obscured |= slot_wear_mask + if(head.flags_inv & HIDEEYES) + obscured |= slot_glasses + if(head.flags_inv & HIDEEARS) + obscured |= slot_r_ear + obscured |= slot_l_ear + + if(obscured.len > 0) + return obscured + else + return null + +/mob/living/carbon/human/proc/check_has_mouth() + // Todo, check stomach organ when implemented. + var/obj/item/organ/external/head/H = get_organ("head") + if(!H || !H.can_intake_reagents) + return 0 + return 1 + +/mob/living/carbon/human/proc/get_visible_gender() + var/list/obscured = check_obscured_slots() + var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE)) + if((slot_w_uniform in obscured) && skipface) + return PLURAL + return gender + +/mob/living/carbon/human/proc/increase_germ_level(n) + if(gloves) + gloves.germ_level += n + else + germ_level += n + +/mob/living/carbon/human/proc/check_and_regenerate_organs(var/mob/living/carbon/human/H) //Regenerates missing limbs/organs. + var/list/types_of_int_organs = list() //This will hold all the types of organs in the mob before rejuvenation. + for(var/obj/item/organ/internal/I in H.internal_organs) + types_of_int_organs |= I.type //Compiling the list of organ types. It is possible for organs to be missing from this list if they are absent from the mob. + + //Clean up limbs + for(var/organ_name in H.bodyparts_by_name) + var/obj/item/organ/organ = H.bodyparts_by_name[organ_name] + if(!organ) //The !organ check is to account for mechanical limb (prostheses) losses, since those are handled in a way that leaves indexed but null list entries instead of stumps. + qdel(organ) + H.bodyparts_by_name -= organ_name //Making sure the list entry is removed. + + //Replacing lost limbs with the species default. + var/mob/living/carbon/human/temp_holder + for(var/limb_type in H.dna.species.has_limbs) + if(!(limb_type in H.bodyparts_by_name)) + var/list/organ_data = H.dna.species.has_limbs[limb_type] + var/limb_path = organ_data["path"] + var/obj/item/organ/external/O = new limb_path(temp_holder) + if(H.get_limb_by_name(O.name)) //Check to see if the user already has an limb with the same name as the 'missing limb'. If they do, skip regrowth. + continue //In an example, this will prevent duplication of the mob's right arm if the mob is a Human and they have a Diona right arm, since, + //while the limb with the name 'right_arm' the mob has may not be listed in their species' bodyparts definition, it is still viable and has the appropriate limb name. + else + O = new limb_path(H) //Create the limb on the player. + O.owner = H + H.bodyparts |= H.bodyparts_by_name[O.limb_name] + if(O.body_part == HEAD) //They're sprouting a fresh head so lets hook them up with their genetic stuff so their new head looks like the original. + H.UpdateAppearance() + + //Replacing lost organs with the species default. + temp_holder = new /mob/living/carbon/human() + var/list/species_organs = H.dna.species.has_organ.Copy() //Compile a list of species organs and tack on the mutantears afterward. + if(H.dna.species.mutantears) + species_organs["ears"] = H.dna.species.mutantears + for(var/index in species_organs) + var/organ = species_organs[index] + if(!(organ in types_of_int_organs)) //If the mob is missing this particular organ... + var/obj/item/organ/internal/I = new organ(temp_holder) //Create the organ inside our holder so we can check it before implantation. + if(H.get_organ_slot(I.slot)) //Check to see if the user already has an organ in the slot the 'missing organ' belongs to. If they do, skip implantation. + continue //In an example, this will prevent duplication of the mob's eyes if the mob is a Human and they have Nucleation eyes, since, + //while the organ in the eyes slot may not be listed in the mob's species' organs definition, it is still viable and fits in the appropriate organ slot. + else + I = new organ(H) //Create the organ inside the player. + I.insert(H) + +/mob/living/carbon/human/revive() + //Fix up all organs and replace lost ones. + restore_all_organs() //Rejuvenate and reset all existing organs. + check_and_regenerate_organs(src) //Regenerate limbs and organs only if they're really missing. + surgeries.Cut() //End all surgeries. + + if(!isskeleton(src) && (SKELETON in mutations)) + mutations.Remove(SKELETON) + if(NOCLONE in mutations) + mutations.Remove(NOCLONE) + if(HUSK in mutations) + mutations.Remove(HUSK) + + if(!client || !key) //Don't boot out anyone already in the mob. + for(var/obj/item/organ/internal/brain/H in world) + if(H.brainmob) + if(H.brainmob.real_name == src.real_name) + if(H.brainmob.mind) + H.brainmob.mind.transfer_to(src) + qdel(H) + + ..() + +/mob/living/carbon/human/proc/is_lung_ruptured() + var/obj/item/organ/internal/lungs/L = get_int_organ(/obj/item/organ/internal/lungs) + if(!L) + return 0 + + return L.is_bruised() + +/mob/living/carbon/human/proc/rupture_lung() + var/obj/item/organ/internal/lungs/L = get_int_organ(/obj/item/organ/internal/lungs) + if(!L) + return 0 + + if(!L.is_bruised()) + custom_pain("You feel a stabbing pain in your chest!") + L.damage = L.min_bruised_damage + +//returns 1 if made bloody, returns 0 otherwise + +/mob/living/carbon/human/clean_blood(var/clean_feet) + .=..() + if(clean_feet && !shoes && istype(feet_blood_DNA, /list) && feet_blood_DNA.len) + feet_blood_color = null + qdel(feet_blood_DNA) + bloody_feet = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_NOT_BLOODY = 0) + blood_state = BLOOD_STATE_NOT_BLOODY + update_inv_shoes(1) + return 1 + +/mob/living/carbon/human/cuff_resist(obj/item/I) + if(HULK in mutations) + say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" )) + if(..(I, cuff_break = 1)) + unEquip(I) + else + if(..()) + unEquip(I) + +/mob/living/carbon/human/resist_restraints() + if(wear_suit && wear_suit.breakouttime) + changeNext_move(CLICK_CD_BREAKOUT) + last_special = world.time + CLICK_CD_BREAKOUT + cuff_resist(wear_suit) + else + ..() + +/mob/living/carbon/human/generate_name() + name = dna.species.get_random_name(gender) + real_name = name + if(dna) + dna.real_name = name + return name + +/mob/living/carbon/human/verb/check_pulse() + set category = null + set name = "Check pulse" + set desc = "Approximately count somebody's pulse. Requires you to stand still at least 6 seconds." + set src in view(1) + var/self = 0 + + if(usr.stat == 1 || usr.restrained() || !isliving(usr)) return + + if(usr == src) + self = 1 + if(!self) + usr.visible_message("[usr] kneels down, puts [usr.p_their()] hand on [src]'s wrist and begins counting [p_their()] pulse.",\ + "You begin counting [src]'s pulse") + else + usr.visible_message("[usr] begins counting [p_their()] pulse.",\ + "You begin counting your pulse.") + + if(src.pulse) + to_chat(usr, "[self ? "You have a" : "[src] has a"] pulse! Counting...") + else + to_chat(usr, "[src] has no pulse!")//it is REALLY UNLIKELY that a dead person would check his own pulse + + return + + to_chat(usr, "Don't move until counting is finished.") + var/time = world.time + sleep(60) + if(usr.l_move_time >= time) //checks if our mob has moved during the sleep() + to_chat(usr, "You moved while counting. Try again.") + else + to_chat(usr, "[self ? "Your" : "[src]'s"] pulse is [src.get_pulse(GETPULSE_HAND)].") + +/mob/living/carbon/human/proc/set_species(datum/species/new_species, default_colour, delay_icon_update = FALSE, skip_same_check = FALSE, retain_damage = FALSE) + if(!skip_same_check) + if(dna.species.name == initial(new_species.name)) + return + var/datum/species/oldspecies = dna.species + + if(oldspecies) + if(oldspecies.language) + remove_language(oldspecies.language) + + if(oldspecies.default_language) + remove_language(oldspecies.default_language) + + if(gender == PLURAL && oldspecies.has_gender) + change_gender(pick(MALE, FEMALE)) + + if(oldspecies.default_genes.len) + oldspecies.handle_dna(src, TRUE) // Remove any genes that belong to the old species + + oldspecies.on_species_loss(src) + + dna.species = new new_species() + + tail = dna.species.tail + + maxHealth = dna.species.total_health + + if(dna.species.language) + add_language(dna.species.language) + + if(dna.species.default_language) + add_language(dna.species.default_language) + + hunger_drain = dna.species.hunger_drain + digestion_ratio = dna.species.digestion_ratio + + if(dna.species.base_color && default_colour) + //Apply colour. + skin_colour = dna.species.base_color + else + skin_colour = "#000000" + + if(!(dna.species.bodyflags & HAS_SKIN_TONE)) + s_tone = 0 + + var/list/thing_to_check = list(slot_wear_mask, slot_head, slot_shoes, slot_gloves, slot_l_ear, slot_r_ear, slot_glasses, slot_l_hand, slot_r_hand) + var/list/kept_items[0] + var/list/item_flags[0] + for(var/thing in thing_to_check) + var/obj/item/I = get_item_by_slot(thing) + if(I) + kept_items[I] = thing + item_flags[I] = I.flags + I.flags = 0 // Temporary set the flags to 0 + + if(retain_damage) + //Create a list of body parts which are damaged by burn or brute and save them to apply after new organs are generated. First we just handle external organs. + var/bodypart_damages = list() + //Loop through all external organs and save the damage states for brute and burn + for(var/obj/item/organ/external/E in bodyparts) + if(E.brute_dam == 0 && E.burn_dam == 0 && E.internal_bleeding == FALSE) //If there's no damage we don't bother remembering it. + continue + var/brute = E.brute_dam + var/burn = E.burn_dam + var/IB = E.internal_bleeding + var/obj/item/organ/external/OE = new E.type() + var/stats = list(OE, brute, burn, IB) + bodypart_damages += list(stats) + + //Now we do the same for internal organs via the same proceedure. + var/internal_damages = list() + for(var/obj/item/organ/internal/I in internal_organs) + if(I.damage == 0) + continue + var/obj/item/organ/internal/OI = new I.type() + var/damage = I.damage + var/broken = I.is_broken() + var/stats = list(OI, damage, broken) + internal_damages += list(stats) + + //Create the new organs for the species change + dna.species.create_organs(src) + + //Apply relevant damages and variables to the new organs. + for(var/B in bodyparts) + var/obj/item/organ/external/E = B + for(var/list/part in bodypart_damages) + var/obj/item/organ/external/OE = part[1] + if((E.type == OE.type)) // Type has to be explicit, as right limbs are a child of left ones etc. + var/brute = part[2] + var/burn = part[3] + var/IB = part[4] + //Deal the damage to the new organ and then delete the entry to prevent duplicate checks + E.receive_damage(brute, burn, ignore_resists = TRUE) + E.internal_bleeding = IB + qdel(part) + + for(var/O in internal_organs) + var/obj/item/organ/internal/I = O + for(var/list/part in internal_damages) + var/obj/item/organ/internal/OI = part[1] + var/organ_type + + if(OI.parent_type == /obj/item/organ/internal) //Dealing with species organs + organ_type = OI.type + else + organ_type = OI.parent_type + + if(istype(I, organ_type)) + var/damage = part[2] + var/broken = part[3] + I.receive_damage(damage, 1) + if(broken && !(I.status & ORGAN_BROKEN)) + I.status |= ORGAN_BROKEN + qdel(part) + + else + dna.species.create_organs(src) + + for(var/obj/item/thing in kept_items) + equip_to_slot_if_possible(thing, kept_items[thing], redraw_mob = 0) + thing.flags = item_flags[thing] // Reset the flags to the origional ones + + //Handle default hair/head accessories for created mobs. + var/obj/item/organ/external/head/H = get_organ("head") + if(dna.species.default_hair) + H.h_style = dna.species.default_hair + else + H.h_style = "Bald" + if(dna.species.default_fhair) + H.f_style = dna.species.default_fhair + else + H.f_style = "Shaved" + if(dna.species.default_headacc) + H.ha_style = dna.species.default_headacc + else + H.ha_style = "None" + + if(dna.species.default_hair_colour) + //Apply colour. + H.hair_colour = dna.species.default_hair_colour + else + H.hair_colour = "#000000" + if(dna.species.default_fhair_colour) + H.facial_colour = dna.species.default_fhair_colour + else + H.facial_colour = "#000000" + if(dna.species.default_headacc_colour) + H.headacc_colour = dna.species.default_headacc_colour + else + H.headacc_colour = "#000000" + + m_styles = DEFAULT_MARKING_STYLES //Wipes out markings, setting them all to "None". + m_colours = DEFAULT_MARKING_COLOURS //Defaults colour to #00000 for all markings. + body_accessory = null + + dna.real_name = real_name + + dna.species.on_species_gain(src) + + update_sight() + + dna.species.handle_dna(src) //Give them whatever special dna business they got. + + update_client_colour(0) + + if(!delay_icon_update) + UpdateAppearance() + + overlays.Cut() + update_mutantrace(1) + regenerate_icons() + + if(dna.species) + return TRUE + else + return FALSE + +/mob/living/carbon/human/get_default_language() + if(default_language) + return default_language + + if(!dna.species) + return null + return dna.species.default_language ? GLOB.all_languages[dna.species.default_language] : null + +/mob/living/carbon/human/proc/bloody_doodle() + set category = "IC" + set name = "Write in blood" + set desc = "Use blood on your hands to write a short message on the floor or a wall, murder mystery style." + + if(usr != src) + return 0 //something is terribly wrong + if(incapacitated()) + to_chat(src, "You can't write on the floor in your current state!") + return + if(!bloody_hands) + verbs -= /mob/living/carbon/human/proc/bloody_doodle + + if(gloves) + to_chat(src, "[gloves] are preventing you from writing anything down!") + return + + var/turf/simulated/T = loc + if(!istype(T)) //to prevent doodling out of mechs and lockers + to_chat(src, "You cannot reach the floor.") + return + + var/turf/origin = T + var/direction = input(src,"Which way?","Tile selection") as anything in list("Here","North","South","East","West") + if(direction != "Here") + T = get_step(T,text2dir(direction)) + if(!istype(T)) + to_chat(src, "You cannot doodle there.") + return + + var/num_doodles = 0 + for(var/obj/effect/decal/cleanable/blood/writing/W in T) + num_doodles++ + if(num_doodles > 4) + to_chat(src, "There is no space to write on!") + return + + var/max_length = bloody_hands * 30 //tweeter style + + var/message = stripped_input(src,"Write a message. It cannot be longer than [max_length] characters.","Blood writing", "") + if(origin != loc) + to_chat(src, "Stay still while writing!") + return + if(message) + var/used_blood_amount = round(length(message) / 30, 1) + bloody_hands = max(0, bloody_hands - used_blood_amount) //use up some blood + + if(length(message) > max_length) + message += "-" + to_chat(src, "You ran out of blood to write with!") + else + to_chat(src, "You daub '[message]' on [T] in shiny red lettering.") + var/obj/effect/decal/cleanable/blood/writing/W = new(T) + W.message = message + W.add_fingerprint(src) + +/mob/living/carbon/human/proc/get_eyecon() + var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) + var/obj/item/organ/internal/cyberimp/eyes/eye_implant = get_int_organ(/obj/item/organ/internal/cyberimp/eyes) + if(istype(dna.species) && dna.species.eyes) + var/icon/eyes_icon = new /icon('icons/mob/human_face.dmi', dna.species.eyes) + if(eye_implant) //Eye implants override native DNA eye colo(u)r + eyes_icon = eye_implant.generate_icon() + else if(eyes) + eyes_icon = eyes.generate_icon() + else //Error 404: Eyes not found! + eyes_icon.Blend("#800000", ICON_ADD) + + return eyes_icon + +/mob/living/carbon/human/proc/get_eye_shine() //Referenced cult constructs for shining in the dark. Needs to be above lighting effects such as shading. + var/obj/item/organ/external/head/head_organ = get_organ("head") + var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_full_list[head_organ.h_style] + var/icon/hair = new /icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s") + var/mutable_appearance/MA = mutable_appearance(get_icon_difference(get_eyecon(), hair), layer = ABOVE_LIGHTING_LAYER) + MA.plane = ABOVE_LIGHTING_PLANE + return MA //Cut the hair's pixels from the eyes icon so eyes covered by bangs stay hidden even while on a higher layer. + +/*Used to check if eyes should shine in the dark. Returns the image of the eyes on the layer where they will appear to shine. +Eyes need to have significantly high darksight to shine unless the mob has the XRAY vision mutation. Eyes will not shine if they are covered in any way.*/ +/mob/living/carbon/human/proc/eyes_shine() + var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) + var/obj/item/organ/internal/cyberimp/eyes/eye_implant = get_int_organ(/obj/item/organ/internal/cyberimp/eyes) + if(!(istype(eyes) || istype(eye_implant))) + return FALSE + if(!get_location_accessible(src, "eyes")) + return FALSE + if(!(eyes.shine()) && !istype(eye_implant) && !(XRAY in mutations)) //If their eyes don't shine, they don't have other augs, nor do they have X-RAY vision + return FALSE + + return TRUE + +/mob/living/carbon/human/assess_threat(var/mob/living/simple_animal/bot/secbot/judgebot, var/lasercolor) + if(judgebot.emagged == 2) + return 10 //Everyone is a criminal! + + var/threatcount = 0 + + //Lasertag bullshit + if(lasercolor) + if(lasercolor == "b")//Lasertag turrets target the opposing team, how great is that? -Sieve + if(istype(wear_suit, /obj/item/clothing/suit/redtag)) + threatcount += 4 + if((istype(r_hand,/obj/item/gun/energy/laser/tag/red)) || (istype(l_hand,/obj/item/gun/energy/laser/tag/red))) + threatcount += 4 + if(istype(belt, /obj/item/gun/energy/laser/tag/red)) + threatcount += 2 + + if(lasercolor == "r") + if(istype(wear_suit, /obj/item/clothing/suit/bluetag)) + threatcount += 4 + if((istype(r_hand,/obj/item/gun/energy/laser/tag/blue)) || (istype(l_hand,/obj/item/gun/energy/laser/tag/blue))) + threatcount += 4 + if(istype(belt, /obj/item/gun/energy/laser/tag/blue)) + threatcount += 2 + + return threatcount + + //Check for ID + var/obj/item/card/id/idcard = get_idcard() + if(judgebot.idcheck && !idcard) + threatcount += 4 + + //Check for weapons + if(judgebot.weaponscheck) + if(!idcard || !(ACCESS_WEAPONS in idcard.access)) + if(judgebot.check_for_weapons(l_hand)) + threatcount += 4 + if(judgebot.check_for_weapons(r_hand)) + threatcount += 4 + if(judgebot.check_for_weapons(belt)) + threatcount += 2 + + //Check for arrest warrant + if(judgebot.check_records) + var/perpname = get_visible_name(TRUE) + var/datum/data/record/R = find_record("name", perpname, data_core.security) + if(R && R.fields["criminal"]) + switch(R.fields["criminal"]) + if("*Execute*") + threatcount += 7 + if("*Arrest*") + threatcount += 5 + if("Incarcerated") + threatcount += 2 + if("Parolled") + threatcount += 2 + + //Check for dresscode violations + if(istype(head, /obj/item/clothing/head/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard)) + threatcount += 2 + + + //Mindshield implants imply slight trustworthiness + if(ismindshielded(src)) + threatcount -= 1 + + //Agent cards lower threatlevel. + if(istype(idcard, /obj/item/card/id/syndicate)) + threatcount -= 5 + + return threatcount + +/mob/living/carbon/human/singularity_act() + . = 20 + if(mind) + if((mind.assigned_role == "Station Engineer") || (mind.assigned_role == "Chief Engineer") ) + . = 100 + if(mind.assigned_role == "Clown") + . = rand(-1000, 1000) + ..() //Called afterwards because getting the mind after getting gibbed is sketchy + +/mob/living/carbon/human/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_THREE) + var/list/handlist = list(l_hand, r_hand) + for(var/obj/item/hand in handlist) + if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && unEquip(hand)) + step_towards(hand, src) + to_chat(src, "\The [S] pulls \the [hand] from your grip!") + apply_effect(current_size * 3, IRRADIATE) + +/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/human/H) + if(H == src) + to_chat(src, "You cannot perform CPR on yourself!") + return + if(H.stat == DEAD || (H.status_flags & FAKEDEATH)) + to_chat(src, "[H.name] is dead!") + return + if(!check_has_mouth()) + to_chat(src, "You don't have a mouth, you cannot perform CPR!") + return + if(!H.check_has_mouth()) + to_chat(src, "They don't have a mouth, you cannot perform CPR!") + return + if((head && (head.flags_cover & HEADCOVERSMOUTH)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && !wear_mask.mask_adjusted)) + to_chat(src, "Remove your mask first!") + return + if((H.head && (H.head.flags_cover & HEADCOVERSMOUTH)) || (H.wear_mask && (H.wear_mask.flags_cover & MASKCOVERSMOUTH) && !H.wear_mask.mask_adjusted)) + to_chat(src, "Remove [H.p_their()] mask first!") + return + if(H.receiving_cpr) // To prevent spam stacking + to_chat(src, "They are already receiving CPR!") + return + visible_message("[src] is trying to perform CPR on [H.name]!", "You try to perform CPR on [H.name]!") + H.receiving_cpr = TRUE + if(do_mob(src, H, 40)) + if(H.health <= HEALTH_THRESHOLD_CRIT) + H.adjustOxyLoss(-15) + H.SetLoseBreath(0) + H.AdjustParalysis(-1) + H.updatehealth("cpr") + visible_message("[src] performs CPR on [H.name]!", "You perform CPR on [H.name].") + + to_chat(H, "You feel a breath of fresh air enter your lungs. It feels good.") + H.receiving_cpr = FALSE + add_attack_logs(src, H, "CPRed", ATKLOG_ALL) + return TRUE + else + H.receiving_cpr = FALSE + to_chat(src, "You need to stay still while performing CPR!") + +/mob/living/carbon/human/canBeHandcuffed() + if(get_num_arms() >= 2) + return TRUE + else + return FALSE + +/mob/living/carbon/human/has_mutated_organs() + for(var/obj/item/organ/external/E in bodyparts) + if(E.status & ORGAN_MUTATED) + return TRUE + return FALSE + +/mob/living/carbon/human/InCritical() + return (health <= HEALTH_THRESHOLD_CRIT && stat == UNCONSCIOUS) + + +/mob/living/carbon/human/IsAdvancedToolUser() + if(dna.species.has_fine_manipulation) + return TRUE + return FALSE + +/mob/living/carbon/human/get_permeability_protection() + var/list/prot = list("hands"=0, "chest"=0, "groin"=0, "legs"=0, "feet"=0, "arms"=0, "head"=0) + for(var/obj/item/I in get_equipped_items()) + if(I.body_parts_covered & HANDS) + prot["hands"] = max(1 - I.permeability_coefficient, prot["hands"]) + if(I.body_parts_covered & UPPER_TORSO) + prot["chest"] = max(1 - I.permeability_coefficient, prot["chest"]) + if(I.body_parts_covered & LOWER_TORSO) + prot["groin"] = max(1 - I.permeability_coefficient, prot["groin"]) + if(I.body_parts_covered & LEGS) + prot["legs"] = max(1 - I.permeability_coefficient, prot["legs"]) + if(I.body_parts_covered & FEET) + prot["feet"] = max(1 - I.permeability_coefficient, prot["feet"]) + if(I.body_parts_covered & ARMS) + prot["arms"] = max(1 - I.permeability_coefficient, prot["arms"]) + if(I.body_parts_covered & HEAD) + prot["head"] = max(1 - I.permeability_coefficient, prot["head"]) + var/protection = (prot["head"] + prot["arms"] + prot["feet"] + prot["legs"] + prot["groin"] + prot["chest"] + prot["hands"])/7 + return protection + +/mob/living/carbon/human/proc/get_full_print() + if(!dna || !dna.uni_identity) + return + return md5(dna.uni_identity) + +/mob/living/carbon/human/can_see_reagents() + for(var/obj/item/clothing/C in src) //If they have some clothing equipped that lets them see reagents, they can see reagents + if(C.scan_reagents) + return 1 + +/mob/living/carbon/human/can_eat(flags = 255) + return dna.species && (dna.species.dietflags & flags) + +/mob/living/carbon/human/selfFeed(var/obj/item/reagent_containers/food/toEat, fullness) + if(!check_has_mouth()) + to_chat(src, "Where do you intend to put \the [toEat]? You don't have a mouth!") + return 0 + return ..() + +/mob/living/carbon/human/forceFed(var/obj/item/reagent_containers/food/toEat, mob/user, fullness) + if(!check_has_mouth()) + if(!((istype(toEat, /obj/item/reagent_containers/food/drinks) && (ismachine(src))))) + to_chat(user, "Where do you intend to put \the [toEat]? \The [src] doesn't have a mouth!") + return 0 + return ..() + +/mob/living/carbon/human/selfDrink(var/obj/item/reagent_containers/food/drinks/toDrink) + if(!check_has_mouth()) + if(!ismachine(src)) + to_chat(src, "Where do you intend to put \the [src]? You don't have a mouth!") + return 0 + else + to_chat(src, "You pour a bit of liquid from [toDrink] into your connection port.") + else + to_chat(src, "You swallow a gulp of [toDrink].") + return 1 + +/mob/living/carbon/human/can_track(mob/living/user) + if(wear_id) + var/obj/item/card/id/id = wear_id + if(istype(id) && id.is_untrackable()) + return 0 + if(wear_pda) + var/obj/item/pda/pda = wear_pda + if(istype(pda)) + var/obj/item/card/id/id = pda.id + if(istype(id) && id.is_untrackable()) + return 0 + if(istype(head, /obj/item/clothing/head)) + var/obj/item/clothing/head/hat = head + if(hat.blockTracking) + return 0 + + return ..() + +/mob/living/carbon/human/proc/get_age_pitch() + return 1.0 + 0.5*(30 - age)/80 + +/mob/living/carbon/human/get_access() + . = ..() + + if(wear_id) + . |= wear_id.GetAccess() + if(wear_pda) + . |= wear_pda.GetAccess() + if(istype(w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = w_uniform + if(U.accessories) + for(var/obj/item/clothing/accessory/A in U.accessories) + . |= A.GetAccess() + +/mob/living/carbon/human/is_mechanical() + return ..() || (dna.species.bodyflags & ALL_RPARTS) != 0 + +/mob/living/carbon/human/can_use_guns(var/obj/item/gun/G) + . = ..() + + if(G.trigger_guard == TRIGGER_GUARD_NORMAL) + if(HULK in mutations) + to_chat(src, "Your meaty finger is much too large for the trigger guard!") + return 0 + if(NOGUNS in dna.species.species_traits) + to_chat(src, "Your fingers don't fit in the trigger guard!") + return 0 + + if(martial_art && martial_art.no_guns) //great dishonor to famiry + to_chat(src, "[martial_art.no_guns_message]") + return 0 + + return . + +/mob/living/carbon/human/proc/change_icobase(var/new_icobase, var/new_deform, var/owner_sensitive) + for(var/obj/item/organ/external/O in bodyparts) + O.change_organ_icobase(new_icobase, new_deform, owner_sensitive) //Change the icobase/deform of all our organs. If owner_sensitive is set, that means the proc won't mess with frankenstein limbs. + +/mob/living/carbon/human/serialize() + // Currently: Limbs/organs only + var/list/data = ..() + var/list/limbs_list = list() + var/list/organs_list = list() + var/list/equip_list = list() + data["limbs"] = limbs_list + data["iorgans"] = organs_list + data["equip"] = equip_list + + data["dna"] = dna.serialize() + data["age"] = age + + // No being naked + data["ushirt"] = undershirt + data["socks"] = socks + data["uwear"] = underwear + + // Limbs + for(var/limb in bodyparts_by_name) + var/obj/item/organ/O = bodyparts_by_name[limb] + if(!O) + limbs_list[limb] = "missing" + continue + + limbs_list[limb] = O.serialize() + + // Internal organs/augments + for(var/organ in internal_organs) + var/obj/item/organ/O = organ + organs_list[O.name] = O.serialize() + + // Equipment + equip_list.len = slots_amt + for(var/i = 1, i < slots_amt, i++) + var/obj/item/thing = get_item_by_slot(i) + if(thing != null) + equip_list[i] = thing.serialize() + + return data + +/mob/living/carbon/human/deserialize(list/data) + var/list/limbs_list = data["limbs"] + var/list/organs_list = data["iorgans"] + var/list/equip_list = data["equip"] + var/turf/T = get_turf(src) + if(!islist(data["limbs"])) + throw EXCEPTION("Expected a limbs list, but found none") + + if(islist(data["dna"])) + dna.deserialize(data["dna"]) + real_name = dna.real_name + name = real_name + set_species(dna.species.type, skip_same_check = TRUE) + age = data["age"] + undershirt = data["ushirt"] + underwear = data["uwear"] + socks = data["socks"] + for(var/obj/item/organ/internal/iorgan in internal_organs) + qdel(iorgan) + + for(var/obj/item/organ/external/organ in bodyparts) + qdel(organ) + + for(var/limb in limbs_list) + // Missing means skip this part - it's missing + if(limbs_list[limb] == "missing") + continue + // "New" code handles insertion and DNA sync'ing + var/obj/item/organ/external/E = list_to_object(limbs_list[limb], src) + E.sync_colour_to_dna() + + for(var/organ in organs_list) + // As above, "New" code handles insertion, DNA sync + list_to_object(organs_list[organ], src) + + UpdateAppearance() + + // De-serialize equipment + // #1: Jumpsuit + // #2: Outer suit + // #3+: Everything else + if(islist(equip_list[slot_w_uniform])) + var/obj/item/clothing/C = list_to_object(equip_list[slot_w_uniform], T) + equip_to_slot_if_possible(C, slot_w_uniform) + + if(islist(equip_list[slot_wear_suit])) + var/obj/item/clothing/C = list_to_object(equip_list[slot_wear_suit], T) + equip_to_slot_if_possible(C, slot_wear_suit) + + for(var/i = 1, i < slots_amt, i++) + if(i == slot_w_uniform || i == slot_wear_suit) + continue + if(islist(equip_list[i])) + var/obj/item/clothing/C = list_to_object(equip_list[i], T) + equip_to_slot_if_possible(C, i) + update_icons() + + ..() + + +/mob/living/carbon/human/vv_get_dropdown() + . = ..() + . += "---" + .["Set Species"] = "?_src_=vars;setspecies=[UID()]" + .["Copy Outfit"] = "?_src_=vars;copyoutfit=[UID()]" + .["Make AI"] = "?_src_=vars;makeai=[UID()]" + .["Make cyborg"] = "?_src_=vars;makerobot=[UID()]" + .["Make monkey"] = "?_src_=vars;makemonkey=[UID()]" + .["Make alien"] = "?_src_=vars;makealien=[UID()]" + .["Make slime"] = "?_src_=vars;makeslime=[UID()]" + .["Make superhero"] = "?_src_=vars;makesuper=[UID()]" + . += "---" + +/mob/living/carbon/human/adjust_nutrition(change) + if(NO_HUNGER in dna.species.species_traits) + return FALSE + return ..() + +/mob/living/carbon/human/set_nutrition(change) + if(NO_HUNGER in dna.species.species_traits) + return FALSE + return ..() + +/mob/living/carbon/human/proc/special_post_clone_handling() + if(mind && mind.assigned_role == "Cluwne") //HUNKE your suffering never stops + makeCluwne() + +/mob/living/carbon/human/proc/influenceSin() + var/datum/objective/sintouched/O + switch(rand(1,7))//traditional seven deadly sins... except lust. + if(1) // acedia + log_game("[src] was influenced by the sin of Acedia.") + O = new /datum/objective/sintouched/acedia + if(2) // Gluttony + log_game("[src] was influenced by the sin of gluttony.") + O = new /datum/objective/sintouched/gluttony + if(3) // Greed + log_game("[src] was influenced by the sin of greed.") + O = new /datum/objective/sintouched/greed + if(4) // sloth + log_game("[src] was influenced by the sin of sloth.") + O = new /datum/objective/sintouched/sloth + if(5) // Wrath + log_game("[src] was influenced by the sin of wrath.") + O = new /datum/objective/sintouched/wrath + if(6) // Envy + log_game("[src] was influenced by the sin of envy.") + O = new /datum/objective/sintouched/envy + if(7) // Pride + log_game("[src] was influenced by the sin of pride.") + O = new /datum/objective/sintouched/pride + SSticker.mode.sintouched += src.mind + src.mind.objectives += O + var/obj_count = 1 + to_chat(src, "[pick(GLOB.boo_phrases)]") + +/mob/living/carbon/human/extinguish_light() + // Parent function handles stuff the human may be holding + ..() + + var/obj/item/organ/internal/lantern/O = get_int_organ(/obj/item/organ/internal/lantern) + if(O && O.glowing) + O.toggle_biolum(TRUE) + visible_message("[src] is engulfed in shadows and fades into the darkness.", "A sense of dread washes over you as you suddenly dim dark.") diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm index 65a75f80e8c..a12ef49fb7f 100644 --- a/code/modules/mob/living/carbon/human/human_damage.dm +++ b/code/modules/mob/living/carbon/human/human_damage.dm @@ -1,350 +1,350 @@ -//Updates the mob's health from organs and mob damage variables -/mob/living/carbon/human/updatehealth(reason = "none given") - if(status_flags & GODMODE) - health = maxHealth - stat = CONSCIOUS - return - - var/total_burn = 0 - var/total_brute = 0 - - for(var/obj/item/organ/external/O in bodyparts) //hardcoded to streamline things a bit - total_brute += O.brute_dam //calculates health based on organ brute and burn - total_burn += O.burn_dam - - health = maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute - - //TODO: fix husking - if(((maxHealth - total_burn) < HEALTH_THRESHOLD_DEAD) && stat == DEAD) - ChangeToHusk() - update_stat("updatehealth([reason])") - med_hud_set_health() - med_hud_set_status() - handle_hud_icons_health() - -/mob/living/carbon/human/adjustBrainLoss(amount, updating = TRUE, use_brain_mod = TRUE) - if(status_flags & GODMODE) - return STATUS_UPDATE_NONE //godmode - - if(dna.species && dna.species.has_organ["brain"]) - var/obj/item/organ/internal/brain/sponge = get_int_organ(/obj/item/organ/internal/brain) - if(sponge) - if(dna.species && amount > 0) - if(use_brain_mod) - amount = amount * dna.species.brain_mod - sponge.damage = Clamp(sponge.damage + amount, 0, 120) - if(sponge.damage >= 120) - visible_message("[src] goes limp, [p_their()] facial expression utterly blank.") - death() - if(updating) - update_stat("adjustBrainLoss") - return STATUS_UPDATE_STAT - -/mob/living/carbon/human/setBrainLoss(amount, updating = TRUE, use_brain_mod = TRUE) - if(status_flags & GODMODE) - return STATUS_UPDATE_NONE //godmode - - if(dna.species && dna.species.has_organ["brain"]) - var/obj/item/organ/internal/brain/sponge = get_int_organ(/obj/item/organ/internal/brain) - if(sponge) - if(dna.species && amount > 0) - if(use_brain_mod) - amount = amount * dna.species.brain_mod - sponge.damage = Clamp(amount, 0, 120) - if(sponge.damage >= 120) - visible_message("[src] goes limp, [p_their()] facial expression utterly blank.") - death() - if(updating) - update_stat("setBrainLoss") - return STATUS_UPDATE_STAT - -/mob/living/carbon/human/getBrainLoss() - if(status_flags & GODMODE) - return 0 //godmode - - if(dna.species && dna.species.has_organ["brain"]) - var/obj/item/organ/internal/brain/sponge = get_int_organ(/obj/item/organ/internal/brain) - if(sponge) - return min(sponge.damage,maxHealth*2) - else - return 200 - else - return 0 - -//These procs fetch a cumulative total damage from all organs -/mob/living/carbon/human/getBruteLoss() - var/amount = 0 - for(var/obj/item/organ/external/O in bodyparts) - amount += O.brute_dam - return amount - -/mob/living/carbon/human/getFireLoss() - var/amount = 0 - for(var/obj/item/organ/external/O in bodyparts) - amount += O.burn_dam - return amount - -/mob/living/carbon/human/adjustBruteLoss(amount, updating_health = TRUE, damage_source = null, robotic = FALSE) - if(amount > 0) - if(dna.species) - amount = amount * dna.species.brute_mod - take_overall_damage(amount, 0, updating_health, used_weapon = damage_source) - else - heal_overall_damage(-amount, 0, updating_health, FALSE, robotic) - // brainless default for now - return STATUS_UPDATE_HEALTH - -/mob/living/carbon/human/adjustFireLoss(amount, updating_health = TRUE, damage_source = null, robotic = FALSE) - if(amount > 0) - if(dna.species) - amount = amount * dna.species.burn_mod - take_overall_damage(0, amount, updating_health, used_weapon = damage_source) - else - heal_overall_damage(0, -amount, updating_health, FALSE, robotic) - // brainless default for now - return STATUS_UPDATE_HEALTH - -/mob/living/carbon/human/proc/adjustBruteLossByPart(amount, organ_name, obj/damage_source = null, updating_health = TRUE) - if(dna.species && amount > 0) - amount = amount * dna.species.brute_mod - if(organ_name in bodyparts_by_name) - var/obj/item/organ/external/O = get_organ(organ_name) - - if(amount > 0) - O.receive_damage(amount, 0, sharp=is_sharp(damage_source), used_weapon=damage_source, list(), FALSE, updating_health) - else - //if you don't want to heal robot organs, they you will have to check that yourself before using this proc. - O.heal_damage(-amount, 0, internal = 0, robo_repair = O.is_robotic(), updating_health = updating_health) - return STATUS_UPDATE_HEALTH - -/mob/living/carbon/human/proc/adjustFireLossByPart(amount, organ_name, obj/damage_source = null, updating_health = TRUE) - if(dna.species && amount > 0) - amount = amount * dna.species.burn_mod - - if(organ_name in bodyparts_by_name) - var/obj/item/organ/external/O = get_organ(organ_name) - - if(amount > 0) - O.receive_damage(0, amount, sharp=is_sharp(damage_source), used_weapon=damage_source, forbidden_limbs = list(), ignore_resists = FALSE, updating_health = updating_health) - else - //if you don't want to heal robot organs, they you will have to check that yourself before using this proc. - O.heal_damage(0, -amount, internal = 0, robo_repair = O.is_robotic(), updating_health = updating_health) - return STATUS_UPDATE_HEALTH - - -/mob/living/carbon/human/Paralyse(amount) - // Notify our AI if they can now control the suit. - if(wearing_rig && !stat && paralysis < amount) //We are passing out right this second. - wearing_rig.notify_ai("Warning: user consciousness failure. Mobility control passed to integrated intelligence system.") - return ..() - -/mob/living/carbon/human/adjustCloneLoss(amount) - if(dna.species && amount > 0) - amount = amount * dna.species.clone_mod - . = ..() - - var/heal_prob = max(0, 80 - getCloneLoss()) - var/mut_prob = min(80, getCloneLoss() + 10) - if(amount > 0) //cloneloss is being added - if(prob(mut_prob)) - var/list/obj/item/organ/external/candidates = list() //TYPECASTED LISTS ARE NOT A FUCKING THING WHAT THE FUCK - for(var/obj/item/organ/external/O in bodyparts) - if(O.is_robotic()) - continue - if(!(O.status & ORGAN_MUTATED)) - candidates |= O - - if(candidates.len) - var/obj/item/organ/external/O = pick(candidates) - O.mutate() - to_chat(src, "Something is not right with your [O.name]...") - O.add_autopsy_data("Mutation", amount) - return - - else //cloneloss is being subtracted - if(prob(heal_prob)) - for(var/obj/item/organ/external/O in bodyparts) - if(O.status & ORGAN_MUTATED) - O.unmutate() - to_chat(src, "Your [O.name] is shaped normally again.") - return - - - if(getCloneLoss() < 1) //no cloneloss, fixes organs - for(var/obj/item/organ/external/O in bodyparts) - if(O.status & ORGAN_MUTATED) - O.unmutate() - to_chat(src, "Your [O.name] is shaped normally again.") - - -// Defined here solely to take species flags into account without having to recast at mob/living level. -/mob/living/carbon/human/adjustOxyLoss(amount) - if(NO_BREATHE in dna.species.species_traits) - oxyloss = 0 - return FALSE - if(dna.species && amount > 0) - amount = amount * dna.species.oxy_mod - . = ..() - -/mob/living/carbon/human/setOxyLoss(amount) - if(NO_BREATHE in dna.species.species_traits) - oxyloss = 0 - return FALSE - if(dna.species && amount > 0) - amount = amount * dna.species.oxy_mod - . = ..() - -/mob/living/carbon/human/adjustToxLoss(amount) - if(dna.species && amount > 0) - amount = amount * dna.species.tox_mod - . = ..() - -/mob/living/carbon/human/setToxLoss(amount) - if(dna.species && amount > 0) - amount = amount * dna.species.tox_mod - . = ..() - -/mob/living/carbon/human/adjustStaminaLoss(amount, updating = TRUE) - if(dna.species && amount > 0) - amount = amount * dna.species.stamina_mod - . = ..() - -/mob/living/carbon/human/setStaminaLoss(amount, updating = TRUE) - if(dna.species && amount > 0) - amount = amount * dna.species.stamina_mod - . = ..() - -//////////////////////////////////////////// - -//Returns a list of damaged organs -/mob/living/carbon/human/proc/get_damaged_organs(brute, burn, flags = AFFECT_ALL_ORGANS) - var/list/obj/item/organ/external/parts = list() - for(var/obj/item/organ/external/O in bodyparts) - if((brute && O.brute_dam) || (burn && O.burn_dam)) - if(!(flags & AFFECT_ROBOTIC_ORGAN) && O.is_robotic()) - continue - if(!(flags & AFFECT_ORGANIC_ORGAN) && !O.is_robotic()) - continue - parts += O - return parts - -//Returns a list of damageable organs -/mob/living/carbon/human/proc/get_damageable_organs() - var/list/obj/item/organ/external/parts = list() - for(var/obj/item/organ/external/O in bodyparts) - if(O.brute_dam + O.burn_dam < O.max_damage) - parts += O - return parts - -//Heals ONE external organ, organ gets randomly selected from damaged ones. -//It automatically updates damage overlays if necesary -//It automatically updates health status -/mob/living/carbon/human/heal_organ_damage(brute, burn, updating_health = TRUE) - var/list/obj/item/organ/external/parts = get_damaged_organs(brute,burn) - if(!parts.len) - return - var/obj/item/organ/external/picked = pick(parts) - if(picked.heal_damage(brute,burn, updating_health)) - UpdateDamageIcon() - -//Damages ONE external organ, organ gets randomly selected from damagable ones. -//It automatically updates damage overlays if necesary -//It automatically updates health status -/mob/living/carbon/human/take_organ_damage(brute, burn, updating_health = TRUE, sharp = 0, edge = 0) - var/list/obj/item/organ/external/parts = get_damageable_organs() - if(!parts.len) - return - var/obj/item/organ/external/picked = pick(parts) - if(picked.receive_damage(brute, burn, sharp, updating_health)) - UpdateDamageIcon() - - -//Heal MANY external organs, in random order -/mob/living/carbon/human/heal_overall_damage(brute, burn, updating_health = TRUE, internal=0, robotic=0) - var/list/obj/item/organ/external/parts = get_damaged_organs(brute,burn) - - var/update = 0 - while(parts.len && ( brute > 0 || burn > 0) ) - var/obj/item/organ/external/picked = pick(parts) - - var/brute_was = picked.brute_dam - var/burn_was = picked.burn_dam - - update |= picked.heal_damage(brute,burn, internal, robotic, updating_health = FALSE) - - brute -= (brute_was-picked.brute_dam) - burn -= (burn_was-picked.burn_dam) - - parts -= picked - - if(updating_health) - updatehealth("heal overall damage") - if(update) - UpdateDamageIcon() - -// damage MANY external organs, in random order -/mob/living/carbon/human/take_overall_damage(brute, burn, updating_health = TRUE, used_weapon = null, sharp = 0, edge = 0) - if(status_flags & GODMODE) - return //godmode - var/list/obj/item/organ/external/parts = get_damageable_organs() - - var/update = 0 - while(parts.len && (brute>0 || burn>0) ) - var/obj/item/organ/external/picked = pick(parts) - var/brute_per_part = brute/parts.len - var/burn_per_part = burn/parts.len - - var/brute_was = picked.brute_dam - var/burn_was = picked.burn_dam - - - update |= picked.receive_damage(brute_per_part, burn_per_part, sharp, used_weapon, list(), FALSE, FALSE) - - brute -= (picked.brute_dam - brute_was) - burn -= (picked.burn_dam - burn_was) - - parts -= picked - - if(updating_health) - updatehealth("take overall damage") - - if(update) - UpdateDamageIcon() - - -//////////////////////////////////////////// - -/* -This function restores all organs. -*/ -/mob/living/carbon/human/restore_all_organs() - for(var/obj/item/organ/external/current_organ in bodyparts) - current_organ.rejuvenate() - -/mob/living/carbon/human/proc/HealDamage(zone, brute, burn) - var/obj/item/organ/external/E = get_organ(zone) - if(istype(E, /obj/item/organ/external)) - if(E.heal_damage(brute, burn)) - UpdateDamageIcon() - else - return 0 - - -/mob/living/carbon/human/get_organ(zone) - if(!zone) - zone = "chest" - if(zone in list("eyes", "mouth")) - zone = "head" - - return bodyparts_by_name[zone] - -/mob/living/carbon/human/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = 0, sharp = 0, obj/used_weapon = null) - //Handle other types of damage - if((damagetype != BRUTE) && (damagetype != BURN)) - ..(damage, damagetype, def_zone, blocked) - return 1 - - //Handle BRUTE and BURN damage - handle_suit_punctures(damagetype, damage) - //Handle species apply_damage procs - return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, sharp, used_weapon) +//Updates the mob's health from organs and mob damage variables +/mob/living/carbon/human/updatehealth(reason = "none given") + if(status_flags & GODMODE) + health = maxHealth + stat = CONSCIOUS + return + + var/total_burn = 0 + var/total_brute = 0 + + for(var/obj/item/organ/external/O in bodyparts) //hardcoded to streamline things a bit + total_brute += O.brute_dam //calculates health based on organ brute and burn + total_burn += O.burn_dam + + health = maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute + + //TODO: fix husking + if(((maxHealth - total_burn) < HEALTH_THRESHOLD_DEAD) && stat == DEAD) + ChangeToHusk() + update_stat("updatehealth([reason])") + med_hud_set_health() + med_hud_set_status() + handle_hud_icons_health() + +/mob/living/carbon/human/adjustBrainLoss(amount, updating = TRUE, use_brain_mod = TRUE) + if(status_flags & GODMODE) + return STATUS_UPDATE_NONE //godmode + + if(dna.species && dna.species.has_organ["brain"]) + var/obj/item/organ/internal/brain/sponge = get_int_organ(/obj/item/organ/internal/brain) + if(sponge) + if(dna.species && amount > 0) + if(use_brain_mod) + amount = amount * dna.species.brain_mod + sponge.damage = Clamp(sponge.damage + amount, 0, 120) + if(sponge.damage >= 120) + visible_message("[src] goes limp, [p_their()] facial expression utterly blank.") + death() + if(updating) + update_stat("adjustBrainLoss") + return STATUS_UPDATE_STAT + +/mob/living/carbon/human/setBrainLoss(amount, updating = TRUE, use_brain_mod = TRUE) + if(status_flags & GODMODE) + return STATUS_UPDATE_NONE //godmode + + if(dna.species && dna.species.has_organ["brain"]) + var/obj/item/organ/internal/brain/sponge = get_int_organ(/obj/item/organ/internal/brain) + if(sponge) + if(dna.species && amount > 0) + if(use_brain_mod) + amount = amount * dna.species.brain_mod + sponge.damage = Clamp(amount, 0, 120) + if(sponge.damage >= 120) + visible_message("[src] goes limp, [p_their()] facial expression utterly blank.") + death() + if(updating) + update_stat("setBrainLoss") + return STATUS_UPDATE_STAT + +/mob/living/carbon/human/getBrainLoss() + if(status_flags & GODMODE) + return 0 //godmode + + if(dna.species && dna.species.has_organ["brain"]) + var/obj/item/organ/internal/brain/sponge = get_int_organ(/obj/item/organ/internal/brain) + if(sponge) + return min(sponge.damage,maxHealth*2) + else + return 200 + else + return 0 + +//These procs fetch a cumulative total damage from all organs +/mob/living/carbon/human/getBruteLoss() + var/amount = 0 + for(var/obj/item/organ/external/O in bodyparts) + amount += O.brute_dam + return amount + +/mob/living/carbon/human/getFireLoss() + var/amount = 0 + for(var/obj/item/organ/external/O in bodyparts) + amount += O.burn_dam + return amount + +/mob/living/carbon/human/adjustBruteLoss(amount, updating_health = TRUE, damage_source = null, robotic = FALSE) + if(amount > 0) + if(dna.species) + amount = amount * dna.species.brute_mod + take_overall_damage(amount, 0, updating_health, used_weapon = damage_source) + else + heal_overall_damage(-amount, 0, updating_health, FALSE, robotic) + // brainless default for now + return STATUS_UPDATE_HEALTH + +/mob/living/carbon/human/adjustFireLoss(amount, updating_health = TRUE, damage_source = null, robotic = FALSE) + if(amount > 0) + if(dna.species) + amount = amount * dna.species.burn_mod + take_overall_damage(0, amount, updating_health, used_weapon = damage_source) + else + heal_overall_damage(0, -amount, updating_health, FALSE, robotic) + // brainless default for now + return STATUS_UPDATE_HEALTH + +/mob/living/carbon/human/proc/adjustBruteLossByPart(amount, organ_name, obj/damage_source = null, updating_health = TRUE) + if(dna.species && amount > 0) + amount = amount * dna.species.brute_mod + if(organ_name in bodyparts_by_name) + var/obj/item/organ/external/O = get_organ(organ_name) + + if(amount > 0) + O.receive_damage(amount, 0, sharp=is_sharp(damage_source), used_weapon=damage_source, list(), FALSE, updating_health) + else + //if you don't want to heal robot organs, they you will have to check that yourself before using this proc. + O.heal_damage(-amount, 0, internal = 0, robo_repair = O.is_robotic(), updating_health = updating_health) + return STATUS_UPDATE_HEALTH + +/mob/living/carbon/human/proc/adjustFireLossByPart(amount, organ_name, obj/damage_source = null, updating_health = TRUE) + if(dna.species && amount > 0) + amount = amount * dna.species.burn_mod + + if(organ_name in bodyparts_by_name) + var/obj/item/organ/external/O = get_organ(organ_name) + + if(amount > 0) + O.receive_damage(0, amount, sharp=is_sharp(damage_source), used_weapon=damage_source, forbidden_limbs = list(), ignore_resists = FALSE, updating_health = updating_health) + else + //if you don't want to heal robot organs, they you will have to check that yourself before using this proc. + O.heal_damage(0, -amount, internal = 0, robo_repair = O.is_robotic(), updating_health = updating_health) + return STATUS_UPDATE_HEALTH + + +/mob/living/carbon/human/Paralyse(amount) + // Notify our AI if they can now control the suit. + if(wearing_rig && !stat && paralysis < amount) //We are passing out right this second. + wearing_rig.notify_ai("Warning: user consciousness failure. Mobility control passed to integrated intelligence system.") + return ..() + +/mob/living/carbon/human/adjustCloneLoss(amount) + if(dna.species && amount > 0) + amount = amount * dna.species.clone_mod + . = ..() + + var/heal_prob = max(0, 80 - getCloneLoss()) + var/mut_prob = min(80, getCloneLoss() + 10) + if(amount > 0) //cloneloss is being added + if(prob(mut_prob)) + var/list/obj/item/organ/external/candidates = list() //TYPECASTED LISTS ARE NOT A FUCKING THING WHAT THE FUCK + for(var/obj/item/organ/external/O in bodyparts) + if(O.is_robotic()) + continue + if(!(O.status & ORGAN_MUTATED)) + candidates |= O + + if(candidates.len) + var/obj/item/organ/external/O = pick(candidates) + O.mutate() + to_chat(src, "Something is not right with your [O.name]...") + O.add_autopsy_data("Mutation", amount) + return + + else //cloneloss is being subtracted + if(prob(heal_prob)) + for(var/obj/item/organ/external/O in bodyparts) + if(O.status & ORGAN_MUTATED) + O.unmutate() + to_chat(src, "Your [O.name] is shaped normally again.") + return + + + if(getCloneLoss() < 1) //no cloneloss, fixes organs + for(var/obj/item/organ/external/O in bodyparts) + if(O.status & ORGAN_MUTATED) + O.unmutate() + to_chat(src, "Your [O.name] is shaped normally again.") + + +// Defined here solely to take species flags into account without having to recast at mob/living level. +/mob/living/carbon/human/adjustOxyLoss(amount) + if(NO_BREATHE in dna.species.species_traits) + oxyloss = 0 + return FALSE + if(dna.species && amount > 0) + amount = amount * dna.species.oxy_mod + . = ..() + +/mob/living/carbon/human/setOxyLoss(amount) + if(NO_BREATHE in dna.species.species_traits) + oxyloss = 0 + return FALSE + if(dna.species && amount > 0) + amount = amount * dna.species.oxy_mod + . = ..() + +/mob/living/carbon/human/adjustToxLoss(amount) + if(dna.species && amount > 0) + amount = amount * dna.species.tox_mod + . = ..() + +/mob/living/carbon/human/setToxLoss(amount) + if(dna.species && amount > 0) + amount = amount * dna.species.tox_mod + . = ..() + +/mob/living/carbon/human/adjustStaminaLoss(amount, updating = TRUE) + if(dna.species && amount > 0) + amount = amount * dna.species.stamina_mod + . = ..() + +/mob/living/carbon/human/setStaminaLoss(amount, updating = TRUE) + if(dna.species && amount > 0) + amount = amount * dna.species.stamina_mod + . = ..() + +//////////////////////////////////////////// + +//Returns a list of damaged organs +/mob/living/carbon/human/proc/get_damaged_organs(brute, burn, flags = AFFECT_ALL_ORGANS) + var/list/obj/item/organ/external/parts = list() + for(var/obj/item/organ/external/O in bodyparts) + if((brute && O.brute_dam) || (burn && O.burn_dam)) + if(!(flags & AFFECT_ROBOTIC_ORGAN) && O.is_robotic()) + continue + if(!(flags & AFFECT_ORGANIC_ORGAN) && !O.is_robotic()) + continue + parts += O + return parts + +//Returns a list of damageable organs +/mob/living/carbon/human/proc/get_damageable_organs() + var/list/obj/item/organ/external/parts = list() + for(var/obj/item/organ/external/O in bodyparts) + if(O.brute_dam + O.burn_dam < O.max_damage) + parts += O + return parts + +//Heals ONE external organ, organ gets randomly selected from damaged ones. +//It automatically updates damage overlays if necesary +//It automatically updates health status +/mob/living/carbon/human/heal_organ_damage(brute, burn, updating_health = TRUE) + var/list/obj/item/organ/external/parts = get_damaged_organs(brute,burn) + if(!parts.len) + return + var/obj/item/organ/external/picked = pick(parts) + if(picked.heal_damage(brute,burn, updating_health)) + UpdateDamageIcon() + +//Damages ONE external organ, organ gets randomly selected from damagable ones. +//It automatically updates damage overlays if necesary +//It automatically updates health status +/mob/living/carbon/human/take_organ_damage(brute, burn, updating_health = TRUE, sharp = 0, edge = 0) + var/list/obj/item/organ/external/parts = get_damageable_organs() + if(!parts.len) + return + var/obj/item/organ/external/picked = pick(parts) + if(picked.receive_damage(brute, burn, sharp, updating_health)) + UpdateDamageIcon() + + +//Heal MANY external organs, in random order +/mob/living/carbon/human/heal_overall_damage(brute, burn, updating_health = TRUE, internal=0, robotic=0) + var/list/obj/item/organ/external/parts = get_damaged_organs(brute,burn) + + var/update = 0 + while(parts.len && ( brute > 0 || burn > 0) ) + var/obj/item/organ/external/picked = pick(parts) + + var/brute_was = picked.brute_dam + var/burn_was = picked.burn_dam + + update |= picked.heal_damage(brute,burn, internal, robotic, updating_health = FALSE) + + brute -= (brute_was-picked.brute_dam) + burn -= (burn_was-picked.burn_dam) + + parts -= picked + + if(updating_health) + updatehealth("heal overall damage") + if(update) + UpdateDamageIcon() + +// damage MANY external organs, in random order +/mob/living/carbon/human/take_overall_damage(brute, burn, updating_health = TRUE, used_weapon = null, sharp = 0, edge = 0) + if(status_flags & GODMODE) + return //godmode + var/list/obj/item/organ/external/parts = get_damageable_organs() + + var/update = 0 + while(parts.len && (brute>0 || burn>0) ) + var/obj/item/organ/external/picked = pick(parts) + var/brute_per_part = brute/parts.len + var/burn_per_part = burn/parts.len + + var/brute_was = picked.brute_dam + var/burn_was = picked.burn_dam + + + update |= picked.receive_damage(brute_per_part, burn_per_part, sharp, used_weapon, list(), FALSE, FALSE) + + brute -= (picked.brute_dam - brute_was) + burn -= (picked.burn_dam - burn_was) + + parts -= picked + + if(updating_health) + updatehealth("take overall damage") + + if(update) + UpdateDamageIcon() + + +//////////////////////////////////////////// + +/* +This function restores all organs. +*/ +/mob/living/carbon/human/restore_all_organs() + for(var/obj/item/organ/external/current_organ in bodyparts) + current_organ.rejuvenate() + +/mob/living/carbon/human/proc/HealDamage(zone, brute, burn) + var/obj/item/organ/external/E = get_organ(zone) + if(istype(E, /obj/item/organ/external)) + if(E.heal_damage(brute, burn)) + UpdateDamageIcon() + else + return 0 + + +/mob/living/carbon/human/get_organ(zone) + if(!zone) + zone = "chest" + if(zone in list("eyes", "mouth")) + zone = "head" + + return bodyparts_by_name[zone] + +/mob/living/carbon/human/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = 0, sharp = 0, obj/used_weapon = null) + //Handle other types of damage + if((damagetype != BRUTE) && (damagetype != BURN)) + ..(damage, damagetype, def_zone, blocked) + return 1 + + //Handle BRUTE and BURN damage + handle_suit_punctures(damagetype, damage) + //Handle species apply_damage procs + return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, sharp, used_weapon) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index dbdc66f9819..ad1d249439d 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -1,730 +1,730 @@ -/* -Contains most of the procs that are called when a mob is attacked by something - -bullet_act -ex_act -meteor_act -emp_act - -*/ - - -/mob/living/carbon/human/bullet_act(obj/item/projectile/P, def_zone) - if(!dna.species.bullet_act(P, src)) - return FALSE - if(P.is_reflectable) - if(check_reflect(def_zone)) // Checks if you've passed a reflection% check - visible_message("The [P.name] gets reflected by [src]!", \ - "The [P.name] gets reflected by [src]!") - - P.reflect_back(src) - - return -1 // complete projectile permutation - - //Shields - if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) - P.on_hit(src, 100, def_zone) - return 2 - - var/obj/item/organ/external/organ = get_organ(check_zone(def_zone)) - if(isnull(organ)) - . = bullet_act(P, "chest") //act on chest instead - return - - organ.add_autopsy_data(P.name, P.damage) // Add the bullet's name to the autopsy data - - return (..(P , def_zone)) - -/mob/living/carbon/human/welder_act(mob/user, obj/item/I) - if(user.a_intent != INTENT_HELP) - return - if(!I.tool_use_check(user, 1)) - return - var/obj/item/organ/external/S = bodyparts_by_name[user.zone_selected] - if(!S) - return - if(!S.is_robotic() || S.open == 2) - return - . = TRUE - if(S.brute_dam > ROBOLIMB_SELF_REPAIR_CAP) - to_chat(user, "The damage is far too severe to patch over externally.") - return - - if(!S.brute_dam) - to_chat(user, "Nothing to fix!") - return - - var/surgery_time = 0 - if(user == src) - surgery_time = 10 - if(!I.use_tool(src, user, surgery_time, amount = 1, volume = I.tool_volume)) - return - var/rembrute = HEALPERWELD - var/nrembrute = 0 - var/childlist - if(!isnull(S.children)) - childlist = S.children.Copy() - var/parenthealed = FALSE - while(rembrute > 0) - var/obj/item/organ/external/E - if(S.brute_dam) - E = S - else if(LAZYLEN(childlist)) - E = pick_n_take(childlist) - if(!E.brute_dam || !E.is_robotic()) - continue - else if(S.parent && !parenthealed) - E = S.parent - parenthealed = TRUE - if(!E.brute_dam || !E.is_robotic()) - break - else - break - nrembrute = max(rembrute - E.brute_dam, 0) - E.heal_damage(rembrute,0,0,1) - rembrute = nrembrute - user.visible_message("[user] patches some dents on [src]'s [E.name] with [I].") - if(bleed_rate && isSynthetic()) - bleed_rate = 0 - user.visible_message("[user] patches some leaks on [src] with [I].") - if(IgniteMob()) - message_admins("[key_name_admin(user)] set [key_name_admin(src)] on fire with [I]") - log_game("[key_name(user)] set [key_name(src)] on fire with [I]") - - -/mob/living/carbon/human/check_projectile_dismemberment(obj/item/projectile/P, def_zone) - var/obj/item/organ/external/affecting = get_organ(check_zone(def_zone)) - if(affecting && !affecting.cannot_amputate && affecting.get_damage() >= (affecting.max_damage - P.dismemberment)) - var/damtype = DROPLIMB_SHARP - switch(P.damage_type) - if(BRUTE) - damtype = DROPLIMB_BLUNT - if(BURN) - damtype = DROPLIMB_BURN - - affecting.droplimb(FALSE, damtype) - -/mob/living/carbon/human/getarmor(var/def_zone, var/type) - var/armorval = 0 - var/organnum = 0 - - if(def_zone) - if(isorgan(def_zone)) - return getarmor_organ(def_zone, type) - var/obj/item/organ/external/affecting = get_organ(def_zone) - if(affecting) - return getarmor_organ(affecting, type) - //If a specific bodypart is targetted, check how that bodypart is protected and return the value. - - //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values - for(var/obj/item/organ/external/organ in bodyparts) - armorval += getarmor_organ(organ, type) - organnum++ - - return (armorval/max(organnum, 1)) - - -//this proc returns the armour value for a particular external organ. -/mob/living/carbon/human/proc/getarmor_organ(var/obj/item/organ/external/def_zone, var/type) - if(!type || !def_zone) return 0 - var/protection = 0 - var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, l_ear, r_ear, wear_id) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor) - for(var/bp in body_parts) - if(!bp) continue - if(bp && istype(bp ,/obj/item/clothing)) - var/obj/item/clothing/C = bp - if(C.body_parts_covered & def_zone.body_part) - protection += C.armor[type] - - return protection - -//this proc returns the Siemens coefficient of electrical resistivity for a particular external organ. -/mob/living/carbon/human/proc/get_siemens_coefficient_organ(var/obj/item/organ/external/def_zone) - if(!def_zone) - return 1.0 - - var/siemens_coefficient = 1.0 - - var/list/clothing_items = list(head, wear_mask, wear_suit, w_uniform, gloves, shoes) // What all are we checking? - for(var/obj/item/clothing/C in clothing_items) - if(istype(C) && (C.body_parts_covered & def_zone.body_part)) // Is that body part being targeted covered? - siemens_coefficient *= C.siemens_coefficient - - return siemens_coefficient - -/mob/living/carbon/human/proc/check_head_coverage() - - var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform) - for(var/bp in body_parts) - if(!bp) continue - if(bp && istype(bp ,/obj/item/clothing)) - var/obj/item/clothing/C = bp - if(C.body_parts_covered & HEAD) - return 1 - return 0 - -/mob/living/carbon/human/proc/check_reflect(var/def_zone) //Reflection checks for anything in your l_hand, r_hand, or wear_suit based on the reflection chance var of the object - if(wear_suit && istype(wear_suit, /obj/item/)) - var/obj/item/I = wear_suit - if(I.IsReflect(def_zone) == 1) - return 1 - if(l_hand && istype(l_hand, /obj/item/)) - var/obj/item/I = l_hand - if(I.IsReflect(def_zone) == 1) - return 1 - if(r_hand && istype(r_hand, /obj/item/)) - var/obj/item/I = r_hand - if(I.IsReflect(def_zone) == 1) - return 1 - return 0 - - -//End Here - -/mob/living/carbon/human/proc/check_shields(atom/AM, var/damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) - var/block_chance_modifier = round(damage / -3) - - if(l_hand && !istype(l_hand, /obj/item/clothing)) - var/final_block_chance = l_hand.block_chance - (Clamp((armour_penetration-l_hand.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example - if(l_hand.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return 1 - if(r_hand && !istype(r_hand, /obj/item/clothing)) - var/final_block_chance = r_hand.block_chance - (Clamp((armour_penetration-r_hand.armour_penetration)/2,0,100)) + block_chance_modifier //Need to reset the var so it doesn't carry over modifications between attempts - if(r_hand.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return 1 - if(wear_suit) - var/final_block_chance = wear_suit.block_chance - (Clamp((armour_penetration-wear_suit.armour_penetration)/2,0,100)) + block_chance_modifier - if(wear_suit.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return 1 - if(w_uniform) - var/final_block_chance = w_uniform.block_chance - (Clamp((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier - if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return 1 - return 0 - -/mob/living/carbon/human/proc/check_block() - if(martial_art && prob(martial_art.block_chance) && martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) - return TRUE - -/mob/living/carbon/human/emp_act(severity) - for(var/obj/O in src) - if(!O) continue - O.emp_act(severity) - ..() - -/mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) //todo: update this to utilize check_obscured_slots() //and make sure it's check_obscured_slots(TRUE) to stop aciding through visors etc - var/list/damaged = list() - var/list/inventory_items_to_kill = list() - var/acidity = acidpwr * min(acid_volume * 0.005, 0.1) - //HEAD// - if(!bodyzone_hit || bodyzone_hit == "head") //only if we didn't specify a zone or if that zone is the head. - var/obj/item/clothing/head_clothes = null - if(glasses) - head_clothes = glasses - if(wear_mask) - head_clothes = wear_mask - if(head) - head_clothes = head - if(head_clothes) - if(!(head_clothes.resistance_flags & UNACIDABLE)) - head_clothes.acid_act(acidpwr, acid_volume) - update_inv_glasses() - update_inv_wear_mask() - update_inv_head() - else - to_chat(src, "Your [head_clothes.name] protects your head and face from the acid!") - else - . = get_organ("head") - if(.) - damaged += . - if(l_ear) - inventory_items_to_kill += l_ear - if(r_ear) - inventory_items_to_kill += r_ear - - //CHEST// - if(!bodyzone_hit || bodyzone_hit == "chest") - var/obj/item/clothing/chest_clothes = null - if(w_uniform) - chest_clothes = w_uniform - if(wear_suit) - chest_clothes = wear_suit - if(chest_clothes) - if(!(chest_clothes.resistance_flags & UNACIDABLE)) - chest_clothes.acid_act(acidpwr, acid_volume) - update_inv_w_uniform() - update_inv_wear_suit() - else - to_chat(src, "Your [chest_clothes.name] protects your body from the acid!") - else - . = get_organ("chest") - if(.) - damaged += . - if(wear_id) - inventory_items_to_kill += wear_id - if(wear_pda) - inventory_items_to_kill += wear_pda - if(r_store) - inventory_items_to_kill += r_store - if(l_store) - inventory_items_to_kill += l_store - if(s_store) - inventory_items_to_kill += s_store - - - //ARMS & HANDS// - if(!bodyzone_hit || bodyzone_hit == "l_arm" || bodyzone_hit == "r_arm") - var/obj/item/clothing/arm_clothes = null - if(gloves) - arm_clothes = gloves - if(w_uniform && ((w_uniform.body_parts_covered & HANDS) || (w_uniform.body_parts_covered & ARMS))) - arm_clothes = w_uniform - if(wear_suit && ((wear_suit.body_parts_covered & HANDS) || (wear_suit.body_parts_covered & ARMS))) - arm_clothes = wear_suit - - if(arm_clothes) - if(!(arm_clothes.resistance_flags & UNACIDABLE)) - arm_clothes.acid_act(acidpwr, acid_volume) - update_inv_gloves() - update_inv_w_uniform() - update_inv_wear_suit() - else - to_chat(src, "Your [arm_clothes.name] protects your arms and hands from the acid!") - else - . = get_organ("r_arm") - if(.) - damaged += . - . = get_organ("l_arm") - if(.) - damaged += . - - - //LEGS & FEET// - if(!bodyzone_hit || bodyzone_hit == "l_leg" || bodyzone_hit =="r_leg" || bodyzone_hit == "feet") - var/obj/item/clothing/leg_clothes = null - if(shoes) - leg_clothes = shoes - if(w_uniform && ((w_uniform.body_parts_covered & FEET) || (bodyzone_hit != "feet" && (w_uniform.body_parts_covered & LEGS)))) - leg_clothes = w_uniform - if(wear_suit && ((wear_suit.body_parts_covered & FEET) || (bodyzone_hit != "feet" && (wear_suit.body_parts_covered & LEGS)))) - leg_clothes = wear_suit - if(leg_clothes) - if(!(leg_clothes.resistance_flags & UNACIDABLE)) - leg_clothes.acid_act(acidpwr, acid_volume) - update_inv_shoes() - update_inv_w_uniform() - update_inv_wear_suit() - else - to_chat(src, "Your [leg_clothes.name] protects your legs and feet from the acid!") - else - . = get_organ("r_leg") - if(.) - damaged += . - . = get_organ("l_leg") - if(.) - damaged += . - - - //DAMAGE// - for(var/obj/item/organ/external/affecting in damaged) - affecting.receive_damage(acidity, 2 * acidity) - - if(istype(affecting, /obj/item/organ/external/head)) - var/obj/item/organ/external/head/head_organ = affecting - if(prob(min(acidpwr * acid_volume / 10, 90))) //Applies disfigurement - head_organ.receive_damage(acidity, 2 * acidity) - emote("scream") - head_organ.h_style = "Bald" - head_organ.f_style = "Shaved" - update_hair() - update_fhair() - head_organ.disfigure() - - UpdateDamageIcon() - - //MELTING INVENTORY ITEMS// - //these items are all outside of armour visually, so melt regardless. - if(!bodyzone_hit) - if(back) - inventory_items_to_kill += back - if(belt) - inventory_items_to_kill += belt - if(l_hand) - inventory_items_to_kill += l_hand - if(r_hand) - inventory_items_to_kill += r_hand - - for(var/obj/item/I in inventory_items_to_kill) - I.acid_act(acidpwr, acid_volume) - return 1 - -/mob/living/carbon/human/emag_act(user as mob, var/obj/item/organ/external/affecting) - if(!istype(affecting)) - return - if(!affecting.is_robotic()) - to_chat(user, "That limb isn't robotic.") - return - if(affecting.sabotaged) - to_chat(user, "[src]'s [affecting.name] is already sabotaged!") - else - to_chat(user, "You sneakily slide the card into the dataport on [src]'s [affecting.name] and short out the safeties.") - affecting.sabotaged = 1 - return 1 - -/mob/living/carbon/human/grabbedby(mob/living/user) - if(w_uniform) - w_uniform.add_fingerprint(user) - return ..() - -//Returns 1 if the attack hit, 0 if it missed. -/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user, def_zone) - if(!I || !user) - return 0 - - if((istype(I, /obj/item/kitchen/knife/butcher/meatcleaver) || istype(I, /obj/item/twohanded/chainsaw)) && stat == DEAD && user.a_intent == INTENT_HARM) - var/obj/item/reagent_containers/food/snacks/meat/human/newmeat = new /obj/item/reagent_containers/food/snacks/meat/human(get_turf(loc)) - newmeat.name = real_name + newmeat.name - newmeat.subjectname = real_name - newmeat.subjectjob = job - newmeat.reagents.add_reagent("nutriment", (nutrition / 15) / 3) - reagents.trans_to(newmeat, round((reagents.total_volume) / 3, 1)) - add_mob_blood(src) - --meatleft - to_chat(user, "You hack off a chunk of meat from [name]") - if(!meatleft) - add_attack_logs(user, src, "Chopped up into meat") - if(!iscarbon(user)) - LAssailant = null - else - LAssailant = user - - qdel(src) - - var/obj/item/organ/external/affecting = get_organ(ran_zone(user.zone_selected)) - if(!affecting) - to_chat(user, "They are missing that limb!") - return 1 - var/hit_area = parse_zone(affecting.limb_name) - - if(user != src) - user.do_attack_animation(src) - if(check_shields(I, I.force, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) - return 0 - - if(check_block()) - visible_message("[src] blocks [I]!") - return FALSE - - if(istype(I,/obj/item/card/emag)) - emag_act(user, affecting) - - send_item_attack_message(I, user, hit_area) - - var/weakness = check_weakness(I,user) - - if(!I.force) - return 0 //item force is zero - - var/armor = run_armor_check(affecting, "melee", "Your armour has protected your [hit_area].", "Your armour has softened hit to your [hit_area].", armour_penetration = I.armour_penetration) - var/weapon_sharp = is_sharp(I) - if(weapon_sharp && prob(getarmor(user.zone_selected, "melee"))) - weapon_sharp = 0 - if(armor >= 100) - return 0 - var/Iforce = I.force //to avoid runtimes on the forcesay checks at the bottom. Some items might delete themselves if you drop them. (stunning yourself, ninja swords) - - apply_damage(I.force * weakness, I.damtype, affecting, armor, sharp = weapon_sharp, used_weapon = I) - - var/bloody = 0 - if(I.damtype == BRUTE && I.force && prob(25 + I.force * 2)) - I.add_mob_blood(src) //Make the weapon bloody, not the person. - if(prob(I.force * 2)) //blood spatter! - bloody = 1 - var/turf/location = loc - if(istype(location, /turf/simulated)) - add_splatter_floor(location) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(get_dist(H, src) <= 1) //people with TK won't get smeared with blood - H.add_mob_blood(src) - - if(!stat) - switch(hit_area) - if("head")//Harder to score a stun but if you do it lasts a bit longer - if(stat == CONSCIOUS && armor < 50) - if(prob(I.force)) - visible_message("[src] has been knocked down!", \ - "[src] has been knocked down!") - apply_effect(5, WEAKEN, armor) - AdjustConfused(15) - if(prob(I.force + ((100 - health)/2)) && src != user && I.damtype == BRUTE) - SSticker.mode.remove_revolutionary(mind) - - if(bloody)//Apply blood - if(wear_mask) - wear_mask.add_mob_blood(src) - update_inv_wear_mask(0) - if(head) - head.add_mob_blood(src) - update_inv_head(0,0) - if(glasses && prob(33)) - glasses.add_mob_blood(src) - update_inv_glasses(0) - - - if("chest")//Easier to score a stun but lasts less time - if(stat == CONSCIOUS && I.force && prob(I.force + 10)) - visible_message("[src] has been knocked down!", \ - "[src] has been knocked down!") - apply_effect(5, WEAKEN, armor) - - if(bloody) - if(wear_suit) - wear_suit.add_mob_blood(src) - update_inv_wear_suit(1) - if(w_uniform) - w_uniform.add_mob_blood(src) - update_inv_w_uniform(1) - - - - if(Iforce > 10 || Iforce >= 5 && prob(33)) - forcesay(GLOB.hit_appends) //forcesay checks stat already - - dna.species.spec_attacked_by(I, user, affecting, user.a_intent, src) - -//this proc handles being hit by a thrown atom -/mob/living/carbon/human/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) - var/obj/item/I - var/throwpower = 30 - if(istype(AM, /obj/item)) - I = AM - throwpower = I.throwforce - if(I.thrownby == src) //No throwing stuff at yourself to trigger reactions - return ..() - if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) - hitpush = FALSE - skipcatch = TRUE - blocked = TRUE - else if(I) - if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedded_ignore_throwspeed_threshold) - if(can_embed(I)) - if(prob(I.embed_chance) && !(PIERCEIMMUNE in dna.species.species_traits)) - throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) - var/obj/item/organ/external/L = pick(bodyparts) - L.embedded_objects |= I - I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! - I.forceMove(src) - L.receive_damage(I.w_class*I.embedded_impact_pain_multiplier) - visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") - hitpush = FALSE - skipcatch = TRUE //can't catch the now embedded item - if(!blocked) - dna.species.spec_hitby(AM, src) - return ..() - -/mob/living/carbon/human/proc/bloody_hands(var/mob/living/source, var/amount = 2) - - if(gloves) - gloves.add_mob_blood(source) - gloves:transfer_blood = amount - else - add_mob_blood(source) - bloody_hands = amount - update_inv_gloves(1) //updates on-mob overlays for bloody hands and/or bloody gloves - -/mob/living/carbon/human/proc/bloody_body(var/mob/living/source) - if(wear_suit) - wear_suit.add_mob_blood(source) - update_inv_wear_suit(0) - return - if(w_uniform) - w_uniform.add_mob_blood(source) - update_inv_w_uniform(1) - -/mob/living/carbon/human/proc/handle_suit_punctures(var/damtype, var/damage) - - if(!wear_suit) return - if(!istype(wear_suit,/obj/item/clothing/suit/space)) return - if(damtype != BURN && damtype != BRUTE) return - - var/obj/item/clothing/suit/space/SS = wear_suit - var/penetrated_dam = max(0,(damage - max(0,(SS.breach_threshold - SS.damage)))) - - if(penetrated_dam) SS.create_breaches(damtype, penetrated_dam) - -/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) - if(user.a_intent == INTENT_HARM) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to hurt [src]!") - return FALSE - var/hulk_verb = pick("smash", "pummel") - if(check_shields(user, 15, "the [hulk_verb]ing")) - return - ..(user, TRUE) - playsound(loc, user.dna.species.unarmed.attack_sound, 25, 1, -1) - var/message = "[user] has [hulk_verb]ed [src]!" - visible_message("[message]", "[message]") - adjustBruteLoss(15) - return TRUE - -/mob/living/carbon/human/attack_hand(mob/user) - if(..()) //to allow surgery to return properly. - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - dna.species.spec_attack_hand(H, src) - -/mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L) - if(..()) //successful larva bite. - var/damage = rand(1, 3) - if(stat != DEAD) - L.amount_grown = min(L.amount_grown + damage, L.max_grown) - var/obj/item/organ/external/affecting = get_organ(ran_zone(L.zone_selected)) - var/armor_block = run_armor_check(affecting, "melee") - apply_damage(damage, BRUTE, affecting, armor_block) - updatehealth("larva attack") - -/mob/living/carbon/human/attack_alien(mob/living/carbon/alien/humanoid/M) - if(check_shields(M, 0, M.name)) - visible_message("[M] attempted to touch [src]!") - return 0 - - if(..()) - if(M.a_intent == INTENT_HARM) - if(w_uniform) - w_uniform.add_fingerprint(M) - var/damage = rand(15, 30) - if(!damage) - playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) - visible_message("[M] has lunged at [src]!") - return 0 - var/obj/item/organ/external/affecting = get_organ(ran_zone(M.zone_selected)) - var/armor_block = run_armor_check(affecting, "melee") - - playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) - visible_message("[M] has slashed at [src]!", \ - "[M] has slashed at [src]!") - - apply_damage(damage, BRUTE, affecting, armor_block) - if(damage >= 25) - visible_message("[M] has wounded [src]!", \ - "[M] has wounded [src]!") - apply_effect(4, WEAKEN, armor_block) - add_attack_logs(M, src, "Alien attacked") - updatehealth("alien attack") - - if(M.a_intent == INTENT_DISARM) - if(prob(80)) - var/obj/item/organ/external/affecting = get_organ(ran_zone(M.zone_selected)) - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - apply_effect(5, WEAKEN, run_armor_check(affecting, "melee")) - add_attack_logs(M, src, "Alien tackled") - visible_message("[M] has tackled down [src]!") - else - if(prob(99)) //this looks fucking stupid but it was previously 'var/randn = rand(1, 100); if(randn <= 99)' - playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) - drop_item() - visible_message("[M] disarmed [src]!") - else - playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) - visible_message("[M] has tried to disarm [src]!") - -/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M) - . = ..() - if(.) - var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) - if(check_shields(M, damage, "the [M.name]", MELEE_ATTACK, M.armour_penetration)) - return FALSE - var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") - var/obj/item/organ/external/affecting = get_organ(ran_zone(dam_zone)) - if(!affecting) - affecting = get_organ("chest") - affecting.add_autopsy_data(M.name, damage) // Add the mob's name to the autopsy data - var/armor = run_armor_check(affecting, "melee", armour_penetration = M.armour_penetration) - apply_damage(damage, M.melee_damage_type, affecting, armor) - updatehealth("animal attack") - -/mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M) - if(..()) //successful slime attack - var/damage = rand(5, 25) - if(M.is_adult) - damage = rand(10, 35) - - if(check_shields(M, damage, "the [M.name]")) - return FALSE - - var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") - - var/obj/item/organ/external/affecting = get_organ(ran_zone(dam_zone)) - if(!affecting) - affecting = get_organ("chest") - var/armor_block = run_armor_check(affecting, "melee") - apply_damage(damage, BRUTE, affecting, armor_block) - -/mob/living/carbon/human/mech_melee_attack(obj/mecha/M) - if(M.occupant.a_intent == INTENT_HARM) - if(HAS_TRAIT(M.occupant, TRAIT_PACIFISM)) - to_chat(M.occupant, "You don't want to harm other living beings!") - return - M.do_attack_animation(src) - if(M.damtype == "brute") - step_away(src,M,15) - var/obj/item/organ/external/affecting = get_organ(pick(BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_HEAD)) - if(affecting) - var/update = 0 - var/dmg = rand(M.force/2, M.force) - switch(M.damtype) - if("brute") - if(M.force > 35) // durand and other heavy mechas - Paralyse(1) - else if(M.force > 20 && !IsWeakened()) // lightweight mechas like gygax - Weaken(2) - update |= affecting.receive_damage(dmg, 0) - playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) - if("fire") - update |= affecting.receive_damage(dmg, 0) - playsound(src, 'sound/items/welder.ogg', 50, TRUE) - if("tox") - M.mech_toxin_damage(src) - else - return - if(update) - UpdateDamageIcon() - updatehealth("mech melee attack") - - M.occupant_message("You hit [src].") - visible_message("[M.name] hits [src]!", "[M.name] hits you!") - - add_attack_logs(M.occupant, src, "Mecha-meleed with [M]") - else - ..() - -/mob/living/carbon/human/experience_pressure_difference(pressure_difference, direction) - playsound(src, 'sound/effects/space_wind.ogg', 50, TRUE) - if(shoes && istype(shoes, /obj/item/clothing)) - var/obj/item/clothing/S = shoes - if (S.flags & NOSLIP) - return FALSE - return ..() - -/mob/living/carbon/human/water_act(volume, temperature, source, method = REAGENT_TOUCH) - . = ..() - dna.species.water_act(src, volume, temperature, source, method) - -/mob/living/carbon/human/is_eyes_covered(check_glasses = TRUE, check_head = TRUE, check_mask = TRUE) - if(check_glasses && glasses && (glasses.flags_cover & GLASSESCOVERSEYES)) - return TRUE - if(check_head && head && (head.flags_cover & HEADCOVERSEYES)) - return TRUE - if(check_mask && wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH)) - return TRUE - -/mob/living/carbon/human/proc/reagent_safety_check(hot = TRUE) - if(wear_mask) - to_chat(src, "Your [wear_mask.name] protects you from the [hot ? "hot" : "cold"] liquid!") - return FALSE - if(head) - to_chat(src, "Your [head.name] protects you from the [hot ? "hot" : "cold"] liquid!") - return FALSE - return TRUE +/* +Contains most of the procs that are called when a mob is attacked by something + +bullet_act +ex_act +meteor_act +emp_act + +*/ + + +/mob/living/carbon/human/bullet_act(obj/item/projectile/P, def_zone) + if(!dna.species.bullet_act(P, src)) + return FALSE + if(P.is_reflectable) + if(check_reflect(def_zone)) // Checks if you've passed a reflection% check + visible_message("The [P.name] gets reflected by [src]!", \ + "The [P.name] gets reflected by [src]!") + + P.reflect_back(src) + + return -1 // complete projectile permutation + + //Shields + if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) + P.on_hit(src, 100, def_zone) + return 2 + + var/obj/item/organ/external/organ = get_organ(check_zone(def_zone)) + if(isnull(organ)) + . = bullet_act(P, "chest") //act on chest instead + return + + organ.add_autopsy_data(P.name, P.damage) // Add the bullet's name to the autopsy data + + return (..(P , def_zone)) + +/mob/living/carbon/human/welder_act(mob/user, obj/item/I) + if(user.a_intent != INTENT_HELP) + return + if(!I.tool_use_check(user, 1)) + return + var/obj/item/organ/external/S = bodyparts_by_name[user.zone_selected] + if(!S) + return + if(!S.is_robotic() || S.open == 2) + return + . = TRUE + if(S.brute_dam > ROBOLIMB_SELF_REPAIR_CAP) + to_chat(user, "The damage is far too severe to patch over externally.") + return + + if(!S.brute_dam) + to_chat(user, "Nothing to fix!") + return + + var/surgery_time = 0 + if(user == src) + surgery_time = 10 + if(!I.use_tool(src, user, surgery_time, amount = 1, volume = I.tool_volume)) + return + var/rembrute = HEALPERWELD + var/nrembrute = 0 + var/childlist + if(!isnull(S.children)) + childlist = S.children.Copy() + var/parenthealed = FALSE + while(rembrute > 0) + var/obj/item/organ/external/E + if(S.brute_dam) + E = S + else if(LAZYLEN(childlist)) + E = pick_n_take(childlist) + if(!E.brute_dam || !E.is_robotic()) + continue + else if(S.parent && !parenthealed) + E = S.parent + parenthealed = TRUE + if(!E.brute_dam || !E.is_robotic()) + break + else + break + nrembrute = max(rembrute - E.brute_dam, 0) + E.heal_damage(rembrute,0,0,1) + rembrute = nrembrute + user.visible_message("[user] patches some dents on [src]'s [E.name] with [I].") + if(bleed_rate && isSynthetic()) + bleed_rate = 0 + user.visible_message("[user] patches some leaks on [src] with [I].") + if(IgniteMob()) + message_admins("[key_name_admin(user)] set [key_name_admin(src)] on fire with [I]") + log_game("[key_name(user)] set [key_name(src)] on fire with [I]") + + +/mob/living/carbon/human/check_projectile_dismemberment(obj/item/projectile/P, def_zone) + var/obj/item/organ/external/affecting = get_organ(check_zone(def_zone)) + if(affecting && !affecting.cannot_amputate && affecting.get_damage() >= (affecting.max_damage - P.dismemberment)) + var/damtype = DROPLIMB_SHARP + switch(P.damage_type) + if(BRUTE) + damtype = DROPLIMB_BLUNT + if(BURN) + damtype = DROPLIMB_BURN + + affecting.droplimb(FALSE, damtype) + +/mob/living/carbon/human/getarmor(var/def_zone, var/type) + var/armorval = 0 + var/organnum = 0 + + if(def_zone) + if(isorgan(def_zone)) + return getarmor_organ(def_zone, type) + var/obj/item/organ/external/affecting = get_organ(def_zone) + if(affecting) + return getarmor_organ(affecting, type) + //If a specific bodypart is targetted, check how that bodypart is protected and return the value. + + //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values + for(var/obj/item/organ/external/organ in bodyparts) + armorval += getarmor_organ(organ, type) + organnum++ + + return (armorval/max(organnum, 1)) + + +//this proc returns the armour value for a particular external organ. +/mob/living/carbon/human/proc/getarmor_organ(var/obj/item/organ/external/def_zone, var/type) + if(!type || !def_zone) return 0 + var/protection = 0 + var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, l_ear, r_ear, wear_id) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor) + for(var/bp in body_parts) + if(!bp) continue + if(bp && istype(bp ,/obj/item/clothing)) + var/obj/item/clothing/C = bp + if(C.body_parts_covered & def_zone.body_part) + protection += C.armor[type] + + return protection + +//this proc returns the Siemens coefficient of electrical resistivity for a particular external organ. +/mob/living/carbon/human/proc/get_siemens_coefficient_organ(var/obj/item/organ/external/def_zone) + if(!def_zone) + return 1.0 + + var/siemens_coefficient = 1.0 + + var/list/clothing_items = list(head, wear_mask, wear_suit, w_uniform, gloves, shoes) // What all are we checking? + for(var/obj/item/clothing/C in clothing_items) + if(istype(C) && (C.body_parts_covered & def_zone.body_part)) // Is that body part being targeted covered? + siemens_coefficient *= C.siemens_coefficient + + return siemens_coefficient + +/mob/living/carbon/human/proc/check_head_coverage() + + var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform) + for(var/bp in body_parts) + if(!bp) continue + if(bp && istype(bp ,/obj/item/clothing)) + var/obj/item/clothing/C = bp + if(C.body_parts_covered & HEAD) + return 1 + return 0 + +/mob/living/carbon/human/proc/check_reflect(var/def_zone) //Reflection checks for anything in your l_hand, r_hand, or wear_suit based on the reflection chance var of the object + if(wear_suit && istype(wear_suit, /obj/item/)) + var/obj/item/I = wear_suit + if(I.IsReflect(def_zone) == 1) + return 1 + if(l_hand && istype(l_hand, /obj/item/)) + var/obj/item/I = l_hand + if(I.IsReflect(def_zone) == 1) + return 1 + if(r_hand && istype(r_hand, /obj/item/)) + var/obj/item/I = r_hand + if(I.IsReflect(def_zone) == 1) + return 1 + return 0 + + +//End Here + +/mob/living/carbon/human/proc/check_shields(atom/AM, var/damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) + var/block_chance_modifier = round(damage / -3) + + if(l_hand && !istype(l_hand, /obj/item/clothing)) + var/final_block_chance = l_hand.block_chance - (Clamp((armour_penetration-l_hand.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example + if(l_hand.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return 1 + if(r_hand && !istype(r_hand, /obj/item/clothing)) + var/final_block_chance = r_hand.block_chance - (Clamp((armour_penetration-r_hand.armour_penetration)/2,0,100)) + block_chance_modifier //Need to reset the var so it doesn't carry over modifications between attempts + if(r_hand.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return 1 + if(wear_suit) + var/final_block_chance = wear_suit.block_chance - (Clamp((armour_penetration-wear_suit.armour_penetration)/2,0,100)) + block_chance_modifier + if(wear_suit.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return 1 + if(w_uniform) + var/final_block_chance = w_uniform.block_chance - (Clamp((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier + if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return 1 + return 0 + +/mob/living/carbon/human/proc/check_block() + if(martial_art && prob(martial_art.block_chance) && martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) + return TRUE + +/mob/living/carbon/human/emp_act(severity) + for(var/obj/O in src) + if(!O) continue + O.emp_act(severity) + ..() + +/mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) //todo: update this to utilize check_obscured_slots() //and make sure it's check_obscured_slots(TRUE) to stop aciding through visors etc + var/list/damaged = list() + var/list/inventory_items_to_kill = list() + var/acidity = acidpwr * min(acid_volume * 0.005, 0.1) + //HEAD// + if(!bodyzone_hit || bodyzone_hit == "head") //only if we didn't specify a zone or if that zone is the head. + var/obj/item/clothing/head_clothes = null + if(glasses) + head_clothes = glasses + if(wear_mask) + head_clothes = wear_mask + if(head) + head_clothes = head + if(head_clothes) + if(!(head_clothes.resistance_flags & UNACIDABLE)) + head_clothes.acid_act(acidpwr, acid_volume) + update_inv_glasses() + update_inv_wear_mask() + update_inv_head() + else + to_chat(src, "Your [head_clothes.name] protects your head and face from the acid!") + else + . = get_organ("head") + if(.) + damaged += . + if(l_ear) + inventory_items_to_kill += l_ear + if(r_ear) + inventory_items_to_kill += r_ear + + //CHEST// + if(!bodyzone_hit || bodyzone_hit == "chest") + var/obj/item/clothing/chest_clothes = null + if(w_uniform) + chest_clothes = w_uniform + if(wear_suit) + chest_clothes = wear_suit + if(chest_clothes) + if(!(chest_clothes.resistance_flags & UNACIDABLE)) + chest_clothes.acid_act(acidpwr, acid_volume) + update_inv_w_uniform() + update_inv_wear_suit() + else + to_chat(src, "Your [chest_clothes.name] protects your body from the acid!") + else + . = get_organ("chest") + if(.) + damaged += . + if(wear_id) + inventory_items_to_kill += wear_id + if(wear_pda) + inventory_items_to_kill += wear_pda + if(r_store) + inventory_items_to_kill += r_store + if(l_store) + inventory_items_to_kill += l_store + if(s_store) + inventory_items_to_kill += s_store + + + //ARMS & HANDS// + if(!bodyzone_hit || bodyzone_hit == "l_arm" || bodyzone_hit == "r_arm") + var/obj/item/clothing/arm_clothes = null + if(gloves) + arm_clothes = gloves + if(w_uniform && ((w_uniform.body_parts_covered & HANDS) || (w_uniform.body_parts_covered & ARMS))) + arm_clothes = w_uniform + if(wear_suit && ((wear_suit.body_parts_covered & HANDS) || (wear_suit.body_parts_covered & ARMS))) + arm_clothes = wear_suit + + if(arm_clothes) + if(!(arm_clothes.resistance_flags & UNACIDABLE)) + arm_clothes.acid_act(acidpwr, acid_volume) + update_inv_gloves() + update_inv_w_uniform() + update_inv_wear_suit() + else + to_chat(src, "Your [arm_clothes.name] protects your arms and hands from the acid!") + else + . = get_organ("r_arm") + if(.) + damaged += . + . = get_organ("l_arm") + if(.) + damaged += . + + + //LEGS & FEET// + if(!bodyzone_hit || bodyzone_hit == "l_leg" || bodyzone_hit =="r_leg" || bodyzone_hit == "feet") + var/obj/item/clothing/leg_clothes = null + if(shoes) + leg_clothes = shoes + if(w_uniform && ((w_uniform.body_parts_covered & FEET) || (bodyzone_hit != "feet" && (w_uniform.body_parts_covered & LEGS)))) + leg_clothes = w_uniform + if(wear_suit && ((wear_suit.body_parts_covered & FEET) || (bodyzone_hit != "feet" && (wear_suit.body_parts_covered & LEGS)))) + leg_clothes = wear_suit + if(leg_clothes) + if(!(leg_clothes.resistance_flags & UNACIDABLE)) + leg_clothes.acid_act(acidpwr, acid_volume) + update_inv_shoes() + update_inv_w_uniform() + update_inv_wear_suit() + else + to_chat(src, "Your [leg_clothes.name] protects your legs and feet from the acid!") + else + . = get_organ("r_leg") + if(.) + damaged += . + . = get_organ("l_leg") + if(.) + damaged += . + + + //DAMAGE// + for(var/obj/item/organ/external/affecting in damaged) + affecting.receive_damage(acidity, 2 * acidity) + + if(istype(affecting, /obj/item/organ/external/head)) + var/obj/item/organ/external/head/head_organ = affecting + if(prob(min(acidpwr * acid_volume / 10, 90))) //Applies disfigurement + head_organ.receive_damage(acidity, 2 * acidity) + emote("scream") + head_organ.h_style = "Bald" + head_organ.f_style = "Shaved" + update_hair() + update_fhair() + head_organ.disfigure() + + UpdateDamageIcon() + + //MELTING INVENTORY ITEMS// + //these items are all outside of armour visually, so melt regardless. + if(!bodyzone_hit) + if(back) + inventory_items_to_kill += back + if(belt) + inventory_items_to_kill += belt + if(l_hand) + inventory_items_to_kill += l_hand + if(r_hand) + inventory_items_to_kill += r_hand + + for(var/obj/item/I in inventory_items_to_kill) + I.acid_act(acidpwr, acid_volume) + return 1 + +/mob/living/carbon/human/emag_act(user as mob, var/obj/item/organ/external/affecting) + if(!istype(affecting)) + return + if(!affecting.is_robotic()) + to_chat(user, "That limb isn't robotic.") + return + if(affecting.sabotaged) + to_chat(user, "[src]'s [affecting.name] is already sabotaged!") + else + to_chat(user, "You sneakily slide the card into the dataport on [src]'s [affecting.name] and short out the safeties.") + affecting.sabotaged = 1 + return 1 + +/mob/living/carbon/human/grabbedby(mob/living/user) + if(w_uniform) + w_uniform.add_fingerprint(user) + return ..() + +//Returns 1 if the attack hit, 0 if it missed. +/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user, def_zone) + if(!I || !user) + return 0 + + if((istype(I, /obj/item/kitchen/knife/butcher/meatcleaver) || istype(I, /obj/item/twohanded/chainsaw)) && stat == DEAD && user.a_intent == INTENT_HARM) + var/obj/item/reagent_containers/food/snacks/meat/human/newmeat = new /obj/item/reagent_containers/food/snacks/meat/human(get_turf(loc)) + newmeat.name = real_name + newmeat.name + newmeat.subjectname = real_name + newmeat.subjectjob = job + newmeat.reagents.add_reagent("nutriment", (nutrition / 15) / 3) + reagents.trans_to(newmeat, round((reagents.total_volume) / 3, 1)) + add_mob_blood(src) + --meatleft + to_chat(user, "You hack off a chunk of meat from [name]") + if(!meatleft) + add_attack_logs(user, src, "Chopped up into meat") + if(!iscarbon(user)) + LAssailant = null + else + LAssailant = user + + qdel(src) + + var/obj/item/organ/external/affecting = get_organ(ran_zone(user.zone_selected)) + if(!affecting) + to_chat(user, "They are missing that limb!") + return 1 + var/hit_area = parse_zone(affecting.limb_name) + + if(user != src) + user.do_attack_animation(src) + if(check_shields(I, I.force, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) + return 0 + + if(check_block()) + visible_message("[src] blocks [I]!") + return FALSE + + if(istype(I,/obj/item/card/emag)) + emag_act(user, affecting) + + send_item_attack_message(I, user, hit_area) + + var/weakness = check_weakness(I,user) + + if(!I.force) + return 0 //item force is zero + + var/armor = run_armor_check(affecting, "melee", "Your armour has protected your [hit_area].", "Your armour has softened hit to your [hit_area].", armour_penetration = I.armour_penetration) + var/weapon_sharp = is_sharp(I) + if(weapon_sharp && prob(getarmor(user.zone_selected, "melee"))) + weapon_sharp = 0 + if(armor >= 100) + return 0 + var/Iforce = I.force //to avoid runtimes on the forcesay checks at the bottom. Some items might delete themselves if you drop them. (stunning yourself, ninja swords) + + apply_damage(I.force * weakness, I.damtype, affecting, armor, sharp = weapon_sharp, used_weapon = I) + + var/bloody = 0 + if(I.damtype == BRUTE && I.force && prob(25 + I.force * 2)) + I.add_mob_blood(src) //Make the weapon bloody, not the person. + if(prob(I.force * 2)) //blood spatter! + bloody = 1 + var/turf/location = loc + if(istype(location, /turf/simulated)) + add_splatter_floor(location) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(get_dist(H, src) <= 1) //people with TK won't get smeared with blood + H.add_mob_blood(src) + + if(!stat) + switch(hit_area) + if("head")//Harder to score a stun but if you do it lasts a bit longer + if(stat == CONSCIOUS && armor < 50) + if(prob(I.force)) + visible_message("[src] has been knocked down!", \ + "[src] has been knocked down!") + apply_effect(5, WEAKEN, armor) + AdjustConfused(15) + if(prob(I.force + ((100 - health)/2)) && src != user && I.damtype == BRUTE) + SSticker.mode.remove_revolutionary(mind) + + if(bloody)//Apply blood + if(wear_mask) + wear_mask.add_mob_blood(src) + update_inv_wear_mask(0) + if(head) + head.add_mob_blood(src) + update_inv_head(0,0) + if(glasses && prob(33)) + glasses.add_mob_blood(src) + update_inv_glasses(0) + + + if("chest")//Easier to score a stun but lasts less time + if(stat == CONSCIOUS && I.force && prob(I.force + 10)) + visible_message("[src] has been knocked down!", \ + "[src] has been knocked down!") + apply_effect(5, WEAKEN, armor) + + if(bloody) + if(wear_suit) + wear_suit.add_mob_blood(src) + update_inv_wear_suit(1) + if(w_uniform) + w_uniform.add_mob_blood(src) + update_inv_w_uniform(1) + + + + if(Iforce > 10 || Iforce >= 5 && prob(33)) + forcesay(GLOB.hit_appends) //forcesay checks stat already + + dna.species.spec_attacked_by(I, user, affecting, user.a_intent, src) + +//this proc handles being hit by a thrown atom +/mob/living/carbon/human/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + var/obj/item/I + var/throwpower = 30 + if(istype(AM, /obj/item)) + I = AM + throwpower = I.throwforce + if(I.thrownby == src) //No throwing stuff at yourself to trigger reactions + return ..() + if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) + hitpush = FALSE + skipcatch = TRUE + blocked = TRUE + else if(I) + if(((throwingdatum ? throwingdatum.speed : I.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || I.embedded_ignore_throwspeed_threshold) + if(can_embed(I)) + if(prob(I.embed_chance) && !(PIERCEIMMUNE in dna.species.species_traits)) + throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) + var/obj/item/organ/external/L = pick(bodyparts) + L.embedded_objects |= I + I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! + I.forceMove(src) + L.receive_damage(I.w_class*I.embedded_impact_pain_multiplier) + visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") + hitpush = FALSE + skipcatch = TRUE //can't catch the now embedded item + if(!blocked) + dna.species.spec_hitby(AM, src) + return ..() + +/mob/living/carbon/human/proc/bloody_hands(var/mob/living/source, var/amount = 2) + + if(gloves) + gloves.add_mob_blood(source) + gloves:transfer_blood = amount + else + add_mob_blood(source) + bloody_hands = amount + update_inv_gloves(1) //updates on-mob overlays for bloody hands and/or bloody gloves + +/mob/living/carbon/human/proc/bloody_body(var/mob/living/source) + if(wear_suit) + wear_suit.add_mob_blood(source) + update_inv_wear_suit(0) + return + if(w_uniform) + w_uniform.add_mob_blood(source) + update_inv_w_uniform(1) + +/mob/living/carbon/human/proc/handle_suit_punctures(var/damtype, var/damage) + + if(!wear_suit) return + if(!istype(wear_suit,/obj/item/clothing/suit/space)) return + if(damtype != BURN && damtype != BRUTE) return + + var/obj/item/clothing/suit/space/SS = wear_suit + var/penetrated_dam = max(0,(damage - max(0,(SS.breach_threshold - SS.damage)))) + + if(penetrated_dam) SS.create_breaches(damtype, penetrated_dam) + +/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + if(user.a_intent == INTENT_HARM) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to hurt [src]!") + return FALSE + var/hulk_verb = pick("smash", "pummel") + if(check_shields(user, 15, "the [hulk_verb]ing")) + return + ..(user, TRUE) + playsound(loc, user.dna.species.unarmed.attack_sound, 25, 1, -1) + var/message = "[user] has [hulk_verb]ed [src]!" + visible_message("[message]", "[message]") + adjustBruteLoss(15) + return TRUE + +/mob/living/carbon/human/attack_hand(mob/user) + if(..()) //to allow surgery to return properly. + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + dna.species.spec_attack_hand(H, src) + +/mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L) + if(..()) //successful larva bite. + var/damage = rand(1, 3) + if(stat != DEAD) + L.amount_grown = min(L.amount_grown + damage, L.max_grown) + var/obj/item/organ/external/affecting = get_organ(ran_zone(L.zone_selected)) + var/armor_block = run_armor_check(affecting, "melee") + apply_damage(damage, BRUTE, affecting, armor_block) + updatehealth("larva attack") + +/mob/living/carbon/human/attack_alien(mob/living/carbon/alien/humanoid/M) + if(check_shields(M, 0, M.name)) + visible_message("[M] attempted to touch [src]!") + return 0 + + if(..()) + if(M.a_intent == INTENT_HARM) + if(w_uniform) + w_uniform.add_fingerprint(M) + var/damage = rand(15, 30) + if(!damage) + playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) + visible_message("[M] has lunged at [src]!") + return 0 + var/obj/item/organ/external/affecting = get_organ(ran_zone(M.zone_selected)) + var/armor_block = run_armor_check(affecting, "melee") + + playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) + visible_message("[M] has slashed at [src]!", \ + "[M] has slashed at [src]!") + + apply_damage(damage, BRUTE, affecting, armor_block) + if(damage >= 25) + visible_message("[M] has wounded [src]!", \ + "[M] has wounded [src]!") + apply_effect(4, WEAKEN, armor_block) + add_attack_logs(M, src, "Alien attacked") + updatehealth("alien attack") + + if(M.a_intent == INTENT_DISARM) + if(prob(80)) + var/obj/item/organ/external/affecting = get_organ(ran_zone(M.zone_selected)) + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + apply_effect(5, WEAKEN, run_armor_check(affecting, "melee")) + add_attack_logs(M, src, "Alien tackled") + visible_message("[M] has tackled down [src]!") + else + if(prob(99)) //this looks fucking stupid but it was previously 'var/randn = rand(1, 100); if(randn <= 99)' + playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) + drop_item() + visible_message("[M] disarmed [src]!") + else + playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) + visible_message("[M] has tried to disarm [src]!") + +/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M) + . = ..() + if(.) + var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) + if(check_shields(M, damage, "the [M.name]", MELEE_ATTACK, M.armour_penetration)) + return FALSE + var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") + var/obj/item/organ/external/affecting = get_organ(ran_zone(dam_zone)) + if(!affecting) + affecting = get_organ("chest") + affecting.add_autopsy_data(M.name, damage) // Add the mob's name to the autopsy data + var/armor = run_armor_check(affecting, "melee", armour_penetration = M.armour_penetration) + apply_damage(damage, M.melee_damage_type, affecting, armor) + updatehealth("animal attack") + +/mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M) + if(..()) //successful slime attack + var/damage = rand(5, 25) + if(M.is_adult) + damage = rand(10, 35) + + if(check_shields(M, damage, "the [M.name]")) + return FALSE + + var/dam_zone = pick("head", "chest", "groin", "l_arm", "l_hand", "r_arm", "r_hand", "l_leg", "l_foot", "r_leg", "r_foot") + + var/obj/item/organ/external/affecting = get_organ(ran_zone(dam_zone)) + if(!affecting) + affecting = get_organ("chest") + var/armor_block = run_armor_check(affecting, "melee") + apply_damage(damage, BRUTE, affecting, armor_block) + +/mob/living/carbon/human/mech_melee_attack(obj/mecha/M) + if(M.occupant.a_intent == INTENT_HARM) + if(HAS_TRAIT(M.occupant, TRAIT_PACIFISM)) + to_chat(M.occupant, "You don't want to harm other living beings!") + return + M.do_attack_animation(src) + if(M.damtype == "brute") + step_away(src,M,15) + var/obj/item/organ/external/affecting = get_organ(pick(BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_HEAD)) + if(affecting) + var/update = 0 + var/dmg = rand(M.force/2, M.force) + switch(M.damtype) + if("brute") + if(M.force > 35) // durand and other heavy mechas + Paralyse(1) + else if(M.force > 20 && !IsWeakened()) // lightweight mechas like gygax + Weaken(2) + update |= affecting.receive_damage(dmg, 0) + playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) + if("fire") + update |= affecting.receive_damage(dmg, 0) + playsound(src, 'sound/items/welder.ogg', 50, TRUE) + if("tox") + M.mech_toxin_damage(src) + else + return + if(update) + UpdateDamageIcon() + updatehealth("mech melee attack") + + M.occupant_message("You hit [src].") + visible_message("[M.name] hits [src]!", "[M.name] hits you!") + + add_attack_logs(M.occupant, src, "Mecha-meleed with [M]") + else + ..() + +/mob/living/carbon/human/experience_pressure_difference(pressure_difference, direction) + playsound(src, 'sound/effects/space_wind.ogg', 50, TRUE) + if(shoes && istype(shoes, /obj/item/clothing)) + var/obj/item/clothing/S = shoes + if (S.flags & NOSLIP) + return FALSE + return ..() + +/mob/living/carbon/human/water_act(volume, temperature, source, method = REAGENT_TOUCH) + . = ..() + dna.species.water_act(src, volume, temperature, source, method) + +/mob/living/carbon/human/is_eyes_covered(check_glasses = TRUE, check_head = TRUE, check_mask = TRUE) + if(check_glasses && glasses && (glasses.flags_cover & GLASSESCOVERSEYES)) + return TRUE + if(check_head && head && (head.flags_cover & HEADCOVERSEYES)) + return TRUE + if(check_mask && wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH)) + return TRUE + +/mob/living/carbon/human/proc/reagent_safety_check(hot = TRUE) + if(wear_mask) + to_chat(src, "Your [wear_mask.name] protects you from the [hot ? "hot" : "cold"] liquid!") + return FALSE + if(head) + to_chat(src, "Your [head.name] protects you from the [hot ? "hot" : "cold"] liquid!") + return FALSE + return TRUE diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index dd56b5fc05c..c0d937a8118 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -1,74 +1,74 @@ -var/global/default_martial_art = new/datum/martial_art -/mob/living/carbon/human - - hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPMINDSHIELD_HUD,IMPCHEM_HUD,IMPTRACK_HUD,SPECIALROLE_HUD,GLAND_HUD) - pressure_resistance = 25 - //Marking colour and style - var/list/m_colours = DEFAULT_MARKING_COLOURS //All colours set to #000000. - var/list/m_styles = DEFAULT_MARKING_STYLES //All markings set to None. - - var/s_tone = 0 //Skin tone - - //Skin colour - var/skin_colour = "#000000" - - var/lip_style = null //no lipstick by default- arguably misleading, as it could be used for general makeup - var/lip_color = "white" - - var/age = 30 //Player's age (pure fluff) - - var/underwear = "Nude" //Which underwear the player wants - var/undershirt = "Nude" //Which undershirt the player wants - var/socks = "Nude" //Which socks the player wants - var/backbag = 2 //Which backpack type the player has chosen. Nothing, Satchel or Backpack. - - //Equipment slots - var/obj/item/w_uniform = null - var/obj/item/shoes = null - var/obj/item/belt = null - var/obj/item/gloves = null - var/obj/item/glasses = null - var/obj/item/l_ear = null - var/obj/item/r_ear = null - var/obj/item/wear_id = null - var/obj/item/wear_pda = null - var/obj/item/r_store = null - var/obj/item/l_store = null - var/obj/item/s_store = null - - var/icon/stand_icon = null - var/icon/lying_icon = null - - var/voice = "" //Instead of new say code calling GetVoice() over and over and over, we're just going to ask this variable, which gets updated in Life() - - var/datum/personal_crafting/handcrafting - - var/datum/martial_art/martial_art = null - - var/special_voice = "" // For changing our voice. Used by a symptom. - - var/hand_blood_color - - var/name_override //For temporary visible name changes - - var/xylophone = 0 //For the spoooooooky xylophone cooldown - - var/mob/remoteview_target = null - var/meatleft = 3 //For chef item - var/decaylevel = 0 // For rotting bodies - var/max_blood = BLOOD_VOLUME_NORMAL // For stuff in the vessel - var/bleed_rate = 0 - var/bleedsuppress = 0 //for stopping bloodloss - - var/check_mutations=0 // Check mutations on next life tick - - var/heartbeat = 0 - var/receiving_cpr = FALSE - - var/fire_dmi = 'icons/mob/OnFire.dmi' - var/fire_sprite = "Standing" - - var/datum/body_accessory/body_accessory = null - var/tail // Name of tail image in species effects icon file. - - var/list/splinted_limbs = list() //limbs we know are splinted +var/global/default_martial_art = new/datum/martial_art +/mob/living/carbon/human + + hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPMINDSHIELD_HUD,IMPCHEM_HUD,IMPTRACK_HUD,SPECIALROLE_HUD,GLAND_HUD) + pressure_resistance = 25 + //Marking colour and style + var/list/m_colours = DEFAULT_MARKING_COLOURS //All colours set to #000000. + var/list/m_styles = DEFAULT_MARKING_STYLES //All markings set to None. + + var/s_tone = 0 //Skin tone + + //Skin colour + var/skin_colour = "#000000" + + var/lip_style = null //no lipstick by default- arguably misleading, as it could be used for general makeup + var/lip_color = "white" + + var/age = 30 //Player's age (pure fluff) + + var/underwear = "Nude" //Which underwear the player wants + var/undershirt = "Nude" //Which undershirt the player wants + var/socks = "Nude" //Which socks the player wants + var/backbag = 2 //Which backpack type the player has chosen. Nothing, Satchel or Backpack. + + //Equipment slots + var/obj/item/w_uniform = null + var/obj/item/shoes = null + var/obj/item/belt = null + var/obj/item/gloves = null + var/obj/item/glasses = null + var/obj/item/l_ear = null + var/obj/item/r_ear = null + var/obj/item/wear_id = null + var/obj/item/wear_pda = null + var/obj/item/r_store = null + var/obj/item/l_store = null + var/obj/item/s_store = null + + var/icon/stand_icon = null + var/icon/lying_icon = null + + var/voice = "" //Instead of new say code calling GetVoice() over and over and over, we're just going to ask this variable, which gets updated in Life() + + var/datum/personal_crafting/handcrafting + + var/datum/martial_art/martial_art = null + + var/special_voice = "" // For changing our voice. Used by a symptom. + + var/hand_blood_color + + var/name_override //For temporary visible name changes + + var/xylophone = 0 //For the spoooooooky xylophone cooldown + + var/mob/remoteview_target = null + var/meatleft = 3 //For chef item + var/decaylevel = 0 // For rotting bodies + var/max_blood = BLOOD_VOLUME_NORMAL // For stuff in the vessel + var/bleed_rate = 0 + var/bleedsuppress = 0 //for stopping bloodloss + + var/check_mutations=0 // Check mutations on next life tick + + var/heartbeat = 0 + var/receiving_cpr = FALSE + + var/fire_dmi = 'icons/mob/OnFire.dmi' + var/fire_sprite = "Standing" + + var/datum/body_accessory/body_accessory = null + var/tail // Name of tail image in species effects icon file. + + var/list/splinted_limbs = list() //limbs we know are splinted diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 8f2a2c4b5c9..85bf001a206 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -1,133 +1,133 @@ -/mob/living/carbon/human/movement_delay() - . = 0 - . += ..() - . += config.human_delay - . += dna.species.movement_delay(src) - -/mob/living/carbon/human/Process_Spacemove(movement_dir = 0) - - if(..()) - return 1 - - //Do we have a working jetpack? - var/obj/item/tank/jetpack/thrust - if(istype(back, /obj/item/tank/jetpack)) - thrust = back - else if(istype(wear_suit, /obj/item/clothing/suit/space/hardsuit)) - var/obj/item/clothing/suit/space/hardsuit/C = wear_suit - thrust = C.jetpack - else if(istype(back,/obj/item/rig)) - var/obj/item/rig/rig = back - for(var/obj/item/rig_module/maneuvering_jets/module in rig.installed_modules) - thrust = module.jets - break - - if(thrust) - if((movement_dir || thrust.stabilizers) && thrust.allow_thrust(0.01, src)) - return 1 - return 0 - -/mob/living/carbon/human/mob_has_gravity() - . = ..() - if(!.) - if(mob_negates_gravity()) - . = 1 - -/mob/living/carbon/human/mob_negates_gravity() - return shoes && shoes.negates_gravity() - -/mob/living/carbon/human/Move(NewLoc, direct) - . = ..() - if(.) // did we actually move? - if(!lying && !buckled && !throwing) - for(var/obj/item/organ/external/splinted in splinted_limbs) - splinted.update_splints() - - if(!has_gravity(loc)) - return - - var/obj/item/clothing/shoes/S = shoes - - //Bloody footprints - var/turf/T = get_turf(src) - var/obj/item/organ/external/l_foot = get_organ("l_foot") - var/obj/item/organ/external/r_foot = get_organ("r_foot") - var/hasfeet = TRUE - if(!l_foot && !r_foot) - hasfeet = FALSE - - if(shoes) - if(S.bloody_shoes && S.bloody_shoes[S.blood_state]) - for(var/obj/effect/decal/cleanable/blood/footprints/oldFP in T) - if(oldFP && oldFP.blood_state == S.blood_state && oldFP.basecolor == S.blood_color) - return - //No oldFP or it's a different kind of blood - S.bloody_shoes[S.blood_state] = max(0, S.bloody_shoes[S.blood_state] - BLOOD_LOSS_PER_STEP) - if(S.bloody_shoes[S.blood_state] > BLOOD_LOSS_IN_SPREAD) - createFootprintsFrom(shoes, dir, T) - update_inv_shoes() - else if(hasfeet) - if(bloody_feet && bloody_feet[blood_state]) - for(var/obj/effect/decal/cleanable/blood/footprints/oldFP in T) - if(oldFP && oldFP.blood_state == blood_state && oldFP.basecolor == feet_blood_color) - return - bloody_feet[blood_state] = max(0, bloody_feet[blood_state] - BLOOD_LOSS_PER_STEP) - if(bloody_feet[blood_state] > BLOOD_LOSS_IN_SPREAD) - createFootprintsFrom(src, dir, T) - update_inv_shoes() - //End bloody footprints - if(S) - S.step_action(src) - -/mob/living/carbon/human/handle_footstep(turf/T) - if(..()) - if(T.footstep_sounds["human"]) - var/S = pick(T.footstep_sounds["human"]) - if(S) - if(m_intent == MOVE_INTENT_RUN) - if(!(step_count % 2)) //every other turf makes a sound - return 0 - - if(istype(shoes, /obj/item/clothing/shoes)) - var/obj/item/clothing/shoes/shooess = shoes - if(shooess.silence_steps) - return 0 //silent - if(shooess.shoe_sound) - return //Handle it on the shoe - - var/range = -(world.view - 2) - if(m_intent == MOVE_INTENT_WALK) - range -= 0.333 - if(!shoes) - range -= 0.333 - - //shoes + running - //-(7 - 2) = -(5) = -5 | -5 - 0 = -5 | (7 + -5) = 2 | 2 * 3 = 6 | range(6) = range(6) - //running OR shoes - //-(7 - 2) = (-5) = -5 | -5 - 0.333 = -5.333 | (7 + -5.333) = 1.667 | 1.667 * 3 = 5.001 | range(5.001) = range(5) - //walking AND no shoes - //-(7 - 2) = (-5) = -5 | -5 - (0.333 * 2) = -5.666 | (7 + -5.666) = 1.334 | 1.334 * 3 = 4.002 | range(4.002) = range(4) - - var/volume = 13 - if(m_intent == MOVE_INTENT_WALK) - volume -= 4 - if(!shoes) - volume -= 4 - - if(!has_organ("l_foot") && !has_organ("r_foot")) - return 0 //no feet no footsteps - - if(buckled || lying || throwing) - return 0 //people flying, lying down or sitting do not step - - if(!has_gravity(src)) - if(step_count % 3) //this basically says, every three moves make a noise - return 0 //1st - none, 1%3==1, 2nd - none, 2%3==2, 3rd - noise, 3%3==0 - - if(dna.species.silent_steps) - return 0 //species is silent - - playsound(T, S, volume, 1, range) - return 1 - - return 0 +/mob/living/carbon/human/movement_delay() + . = 0 + . += ..() + . += config.human_delay + . += dna.species.movement_delay(src) + +/mob/living/carbon/human/Process_Spacemove(movement_dir = 0) + + if(..()) + return 1 + + //Do we have a working jetpack? + var/obj/item/tank/jetpack/thrust + if(istype(back, /obj/item/tank/jetpack)) + thrust = back + else if(istype(wear_suit, /obj/item/clothing/suit/space/hardsuit)) + var/obj/item/clothing/suit/space/hardsuit/C = wear_suit + thrust = C.jetpack + else if(istype(back,/obj/item/rig)) + var/obj/item/rig/rig = back + for(var/obj/item/rig_module/maneuvering_jets/module in rig.installed_modules) + thrust = module.jets + break + + if(thrust) + if((movement_dir || thrust.stabilizers) && thrust.allow_thrust(0.01, src)) + return 1 + return 0 + +/mob/living/carbon/human/mob_has_gravity() + . = ..() + if(!.) + if(mob_negates_gravity()) + . = 1 + +/mob/living/carbon/human/mob_negates_gravity() + return shoes && shoes.negates_gravity() + +/mob/living/carbon/human/Move(NewLoc, direct) + . = ..() + if(.) // did we actually move? + if(!lying && !buckled && !throwing) + for(var/obj/item/organ/external/splinted in splinted_limbs) + splinted.update_splints() + + if(!has_gravity(loc)) + return + + var/obj/item/clothing/shoes/S = shoes + + //Bloody footprints + var/turf/T = get_turf(src) + var/obj/item/organ/external/l_foot = get_organ("l_foot") + var/obj/item/organ/external/r_foot = get_organ("r_foot") + var/hasfeet = TRUE + if(!l_foot && !r_foot) + hasfeet = FALSE + + if(shoes) + if(S.bloody_shoes && S.bloody_shoes[S.blood_state]) + for(var/obj/effect/decal/cleanable/blood/footprints/oldFP in T) + if(oldFP && oldFP.blood_state == S.blood_state && oldFP.basecolor == S.blood_color) + return + //No oldFP or it's a different kind of blood + S.bloody_shoes[S.blood_state] = max(0, S.bloody_shoes[S.blood_state] - BLOOD_LOSS_PER_STEP) + if(S.bloody_shoes[S.blood_state] > BLOOD_LOSS_IN_SPREAD) + createFootprintsFrom(shoes, dir, T) + update_inv_shoes() + else if(hasfeet) + if(bloody_feet && bloody_feet[blood_state]) + for(var/obj/effect/decal/cleanable/blood/footprints/oldFP in T) + if(oldFP && oldFP.blood_state == blood_state && oldFP.basecolor == feet_blood_color) + return + bloody_feet[blood_state] = max(0, bloody_feet[blood_state] - BLOOD_LOSS_PER_STEP) + if(bloody_feet[blood_state] > BLOOD_LOSS_IN_SPREAD) + createFootprintsFrom(src, dir, T) + update_inv_shoes() + //End bloody footprints + if(S) + S.step_action(src) + +/mob/living/carbon/human/handle_footstep(turf/T) + if(..()) + if(T.footstep_sounds["human"]) + var/S = pick(T.footstep_sounds["human"]) + if(S) + if(m_intent == MOVE_INTENT_RUN) + if(!(step_count % 2)) //every other turf makes a sound + return 0 + + if(istype(shoes, /obj/item/clothing/shoes)) + var/obj/item/clothing/shoes/shooess = shoes + if(shooess.silence_steps) + return 0 //silent + if(shooess.shoe_sound) + return //Handle it on the shoe + + var/range = -(world.view - 2) + if(m_intent == MOVE_INTENT_WALK) + range -= 0.333 + if(!shoes) + range -= 0.333 + + //shoes + running + //-(7 - 2) = -(5) = -5 | -5 - 0 = -5 | (7 + -5) = 2 | 2 * 3 = 6 | range(6) = range(6) + //running OR shoes + //-(7 - 2) = (-5) = -5 | -5 - 0.333 = -5.333 | (7 + -5.333) = 1.667 | 1.667 * 3 = 5.001 | range(5.001) = range(5) + //walking AND no shoes + //-(7 - 2) = (-5) = -5 | -5 - (0.333 * 2) = -5.666 | (7 + -5.666) = 1.334 | 1.334 * 3 = 4.002 | range(4.002) = range(4) + + var/volume = 13 + if(m_intent == MOVE_INTENT_WALK) + volume -= 4 + if(!shoes) + volume -= 4 + + if(!has_organ("l_foot") && !has_organ("r_foot")) + return 0 //no feet no footsteps + + if(buckled || lying || throwing) + return 0 //people flying, lying down or sitting do not step + + if(!has_gravity(src)) + if(step_count % 3) //this basically says, every three moves make a noise + return 0 //1st - none, 1%3==1, 2nd - none, 2%3==2, 3rd - noise, 3%3==0 + + if(dna.species.silent_steps) + return 0 //species is silent + + playsound(T, S, volume, 1, range) + return 1 + + return 0 diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 68ae7fe3af0..953ea6bae1d 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -1,437 +1,437 @@ -/mob/living/carbon/human/proc/equip_in_one_of_slots(obj/item/W, list/slots, del_on_fail = 1) - for(var/slot in slots) - if(equip_to_slot_if_possible(W, slots[slot], del_on_fail = 0)) - return slot - if(del_on_fail) - qdel(W) - return null - -/mob/living/carbon/human/proc/is_in_hands(var/typepath) - if(istype(l_hand,typepath)) - return l_hand - if(istype(r_hand,typepath)) - return r_hand - return 0 - - -/mob/living/carbon/human/proc/has_organ(name) - var/obj/item/organ/external/O = bodyparts_by_name[name] - return O - -/mob/living/carbon/human/proc/has_organ_for_slot(slot) - switch(slot) - if(slot_back) - return has_organ("chest") - if(slot_wear_mask) - return has_organ("head") - if(slot_handcuffed) - return has_organ("l_hand") && has_organ("r_hand") - if(slot_legcuffed) - return has_organ("l_leg") && has_organ("r_leg") - if(slot_l_hand) - return has_organ("l_hand") - if(slot_r_hand) - return has_organ("r_hand") - if(slot_belt) - return has_organ("chest") - if(slot_wear_id) - // the only relevant check for this is the uniform check - return TRUE - if(slot_wear_pda) - return TRUE - if(slot_l_ear) - return has_organ("head") - if(slot_r_ear) - return has_organ("head") - if(slot_glasses) - return has_organ("head") - if(slot_gloves) - return has_organ("l_hand") && has_organ("r_hand") - if(slot_head) - return has_organ("head") - if(slot_shoes) - return has_organ("r_foot") && has_organ("l_foot") - if(slot_wear_suit) - return has_organ("chest") - if(slot_w_uniform) - return has_organ("chest") - if(slot_l_store) - return has_organ("chest") - if(slot_r_store) - return has_organ("chest") - if(slot_s_store) - return has_organ("chest") - if(slot_in_backpack) - return TRUE - if(slot_tie) - return TRUE - -// The actual dropping happens at the mob level - checks to prevent drops should -// come here -/mob/living/carbon/human/canUnEquip(obj/item/I, force) - . = ..() - var/obj/item/organ/O = I - if(istype(O) && O.owner == src) - . = 0 // keep a good grip on your heart - -/mob/living/carbon/human/unEquip(obj/item/I, force) - . = ..() //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should. - if(!. || !I) - return - - if(I == wear_suit) - if(s_store) - unEquip(s_store, 1) //It makes no sense for your suit storage to stay on you if you drop your suit. - wear_suit = null - if(I.flags_inv & HIDEJUMPSUIT) - update_inv_w_uniform() - update_inv_wear_suit() - else if(I == w_uniform) - if(r_store) - unEquip(r_store, 1) //Again, makes sense for pockets to drop. - if(l_store) - unEquip(l_store, 1) - if(wear_id) - unEquip(wear_id) - if(belt) - unEquip(belt) - w_uniform = null - update_inv_w_uniform() - else if(I == gloves) - gloves = null - update_inv_gloves() - else if(I == glasses) - glasses = null - var/obj/item/clothing/glasses/G = I - if(G.tint) - update_tint() - if(G.prescription) - update_nearsighted_effects() - if(G.vision_flags || G.see_in_dark || G.invis_override || G.invis_view || !isnull(G.lighting_alpha)) - update_sight() - update_inv_glasses() - update_client_colour() - else if(I == head) - head = null - if(I.flags & BLOCKHAIR || I.flags & BLOCKHEADHAIR) - update_hair() //rebuild hair - update_fhair() - update_head_accessory() - // Bandanas and paper hats go on the head but are not head clothing - if(istype(I,/obj/item/clothing/head)) - var/obj/item/clothing/head/hat = I - if(hat.vision_flags || hat.see_in_dark || !isnull(hat.lighting_alpha)) - update_sight() - head_update(I) - update_inv_head() - else if(I == r_ear) - r_ear = null - update_inv_ears() - else if(I == l_ear) - l_ear = null - update_inv_ears() - else if(I == shoes) - shoes = null - update_inv_shoes() - else if(I == belt) - belt = null - update_inv_belt() - else if(I == wear_mask) - wear_mask = null - if(I.flags & BLOCKHAIR || I.flags & BLOCKHEADHAIR) - update_hair() //rebuild hair - update_fhair() - update_head_accessory() - if(internal && !get_organ_slot("breathing_tube")) - internal = null - update_action_buttons_icon() - wear_mask_update(I, toggle_off = FALSE) - sec_hud_set_ID() - update_inv_wear_mask() - else if(I == wear_id) - wear_id = null - sec_hud_set_ID() - update_inv_wear_id() - else if(I == wear_pda) - wear_pda = null - update_inv_wear_pda() - else if(I == r_store) - r_store = null - update_inv_pockets() - else if(I == l_store) - l_store = null - update_inv_pockets() - else if(I == s_store) - s_store = null - update_inv_s_store() - else if(I == back) - back = null - update_inv_back() - else if(I == r_hand) - r_hand = null - update_inv_r_hand() - else if(I == l_hand) - l_hand = null - update_inv_l_hand() - - - - -//This is an UNSAFE proc. Use mob_can_equip() before calling this one! Or rather use equip_to_slot_if_possible() or advanced_equip_to_slot_if_possible() -//set redraw_mob to 0 if you don't wish the hud to be updated - if you're doing it manually in your own proc. -/mob/living/carbon/human/equip_to_slot(obj/item/I, slot, redraw_mob = 1) - if(!slot) - return - if(!istype(I)) - return - if(!has_organ_for_slot(slot)) - return - - if(I == src.l_hand) - src.l_hand = null - update_inv_l_hand() //So items actually disappear from hands. - else if(I == src.r_hand) - src.r_hand = null - update_inv_r_hand() - - I.screen_loc = null - I.forceMove(src) - I.equipped(src, slot) - I.layer = ABOVE_HUD_LAYER - I.plane = ABOVE_HUD_PLANE - - switch(slot) - if(slot_back) - back = I - update_inv_back(redraw_mob) - if(slot_wear_mask) - wear_mask = I - if((wear_mask.flags & BLOCKHAIR) || (wear_mask.flags & BLOCKHEADHAIR)) - update_hair(redraw_mob) //rebuild hair - update_fhair(redraw_mob) - update_head_accessory(redraw_mob) - if(hud_list.len) - sec_hud_set_ID() - wear_mask_update(I, toggle_off = TRUE) - update_inv_wear_mask(redraw_mob) - if(slot_handcuffed) - handcuffed = I - update_inv_handcuffed(redraw_mob) - if(slot_legcuffed) - legcuffed = I - update_inv_legcuffed(redraw_mob) - if(slot_l_hand) - l_hand = I - update_inv_l_hand(redraw_mob) - if(slot_r_hand) - r_hand = I - update_inv_r_hand(redraw_mob) - if(slot_belt) - belt = I - update_inv_belt(redraw_mob) - if(slot_wear_id) - wear_id = I - if(hud_list.len) - sec_hud_set_ID() - update_inv_wear_id(redraw_mob) - if(slot_wear_pda) - wear_pda = I - update_inv_wear_pda(redraw_mob) - if(slot_l_ear) - l_ear = I - if(l_ear.slot_flags & SLOT_TWOEARS) - var/obj/item/clothing/ears/offear/O = new(I) - O.forceMove(src) - r_ear = O - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - update_inv_ears(redraw_mob) - if(slot_r_ear) - r_ear = I - if(r_ear.slot_flags & SLOT_TWOEARS) - var/obj/item/clothing/ears/offear/O = new(I) - O.forceMove(src) - l_ear = O - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - update_inv_ears(redraw_mob) - if(slot_glasses) - glasses = I - var/obj/item/clothing/glasses/G = I - if(G.tint) - update_tint() - if(G.prescription) - update_nearsighted_effects() - if(G.vision_flags || G.see_in_dark || G.invis_override || G.invis_view || !isnull(G.lighting_alpha)) - update_sight() - update_inv_glasses(redraw_mob) - update_client_colour() - if(slot_gloves) - gloves = I - update_inv_gloves(redraw_mob) - if(slot_head) - head = I - if((head.flags & BLOCKHAIR) || (head.flags & BLOCKHEADHAIR)) - update_hair(redraw_mob) //rebuild hair - update_fhair(redraw_mob) - update_head_accessory(redraw_mob) - // paper + bandanas - if(istype(I, /obj/item/clothing/head)) - var/obj/item/clothing/head/hat = I - if(hat.vision_flags || hat.see_in_dark || !isnull(hat.lighting_alpha)) - update_sight() - head_update(I) - update_inv_head(redraw_mob) - if(slot_shoes) - shoes = I - update_inv_shoes(redraw_mob) - if(slot_wear_suit) - wear_suit = I - update_inv_wear_suit(redraw_mob) - if(slot_w_uniform) - w_uniform = I - update_inv_w_uniform(redraw_mob) - if(slot_l_store) - l_store = I - update_inv_pockets(redraw_mob) - if(slot_r_store) - r_store = I - update_inv_pockets(redraw_mob) - if(slot_s_store) - s_store = I - update_inv_s_store(redraw_mob) - if(slot_in_backpack) - if(get_active_hand() == I) - unEquip(I) - I.forceMove(back) - if(slot_tie) - var/obj/item/clothing/under/uniform = src.w_uniform - uniform.attackby(I, src) - else - to_chat(src, "You are trying to equip this item to an unsupported inventory slot. Report this to a coder!") - -/mob/living/carbon/human/put_in_hands(obj/item/I) - if(!I) - return FALSE - if(put_in_active_hand(I)) - return TRUE - else if(put_in_inactive_hand(I)) - return TRUE - else - . = ..() - -// Return the item currently in the slot ID -/mob/living/carbon/human/get_item_by_slot(slot_id) - switch(slot_id) - if(slot_back) - return back - if(slot_wear_mask) - return wear_mask - if(slot_handcuffed) - return handcuffed - if(slot_legcuffed) - return legcuffed - if(slot_l_hand) - return l_hand - if(slot_r_hand) - return r_hand - if(slot_belt) - return belt - if(slot_wear_id) - return wear_id - if(slot_wear_pda) - return wear_pda - if(slot_l_ear) - return l_ear - if(slot_r_ear) - return r_ear - if(slot_glasses) - return glasses - if(slot_gloves) - return gloves - if(slot_head) - return head - if(slot_shoes) - return shoes - if(slot_wear_suit) - return wear_suit - if(slot_w_uniform) - return w_uniform - if(slot_l_store) - return l_store - if(slot_r_store) - return r_store - if(slot_s_store) - return s_store - return null - -/mob/living/carbon/human/get_all_slots() - . = get_head_slots() | get_body_slots() - -/mob/living/carbon/human/proc/get_body_slots() - return list( - l_hand, - r_hand, - back, - s_store, - handcuffed, - legcuffed, - wear_suit, - gloves, - shoes, - belt, - wear_id, - wear_pda, - l_store, - r_store, - w_uniform - ) - -/mob/living/carbon/human/proc/get_head_slots() - return list( - head, - wear_mask, - glasses, - r_ear, - l_ear, - ) - -// humans have their pickpocket gloves, so they get no message when stealing things -/mob/living/carbon/human/stripPanelUnequip(obj/item/what, mob/who, where) - var/is_silent = 0 - var/obj/item/clothing/gloves/G = gloves - if(istype(G)) - is_silent = G.pickpocket - - ..(what, who, where, silent = is_silent) - -// humans have their pickpocket gloves, so they get no message when stealing things -/mob/living/carbon/human/stripPanelEquip(obj/item/what, mob/who, where) - var/is_silent = 0 - var/obj/item/clothing/gloves/G = gloves - if(istype(G)) - is_silent = G.pickpocket - - ..(what, who, where, silent = is_silent) - -/mob/living/carbon/human/can_equip(obj/item/I, slot, disable_warning = FALSE) - return dna.species.can_equip(I, slot, disable_warning, src) - -/mob/living/carbon/human/proc/equipOutfit(outfit, visualsOnly = FALSE) - var/datum/outfit/O = null - - if(ispath(outfit)) - O = new outfit - else - O = outfit - if(!istype(O)) - return 0 - if(!O) - return 0 - - return O.equip(src, visualsOnly) - -//delete all equipment without dropping anything -/mob/living/carbon/human/proc/delete_equipment() - for(var/slot in get_all_slots())//order matters, dependant slots go first - qdel(slot) +/mob/living/carbon/human/proc/equip_in_one_of_slots(obj/item/W, list/slots, del_on_fail = 1) + for(var/slot in slots) + if(equip_to_slot_if_possible(W, slots[slot], del_on_fail = 0)) + return slot + if(del_on_fail) + qdel(W) + return null + +/mob/living/carbon/human/proc/is_in_hands(var/typepath) + if(istype(l_hand,typepath)) + return l_hand + if(istype(r_hand,typepath)) + return r_hand + return 0 + + +/mob/living/carbon/human/proc/has_organ(name) + var/obj/item/organ/external/O = bodyparts_by_name[name] + return O + +/mob/living/carbon/human/proc/has_organ_for_slot(slot) + switch(slot) + if(slot_back) + return has_organ("chest") + if(slot_wear_mask) + return has_organ("head") + if(slot_handcuffed) + return has_organ("l_hand") && has_organ("r_hand") + if(slot_legcuffed) + return has_organ("l_leg") && has_organ("r_leg") + if(slot_l_hand) + return has_organ("l_hand") + if(slot_r_hand) + return has_organ("r_hand") + if(slot_belt) + return has_organ("chest") + if(slot_wear_id) + // the only relevant check for this is the uniform check + return TRUE + if(slot_wear_pda) + return TRUE + if(slot_l_ear) + return has_organ("head") + if(slot_r_ear) + return has_organ("head") + if(slot_glasses) + return has_organ("head") + if(slot_gloves) + return has_organ("l_hand") && has_organ("r_hand") + if(slot_head) + return has_organ("head") + if(slot_shoes) + return has_organ("r_foot") && has_organ("l_foot") + if(slot_wear_suit) + return has_organ("chest") + if(slot_w_uniform) + return has_organ("chest") + if(slot_l_store) + return has_organ("chest") + if(slot_r_store) + return has_organ("chest") + if(slot_s_store) + return has_organ("chest") + if(slot_in_backpack) + return TRUE + if(slot_tie) + return TRUE + +// The actual dropping happens at the mob level - checks to prevent drops should +// come here +/mob/living/carbon/human/canUnEquip(obj/item/I, force) + . = ..() + var/obj/item/organ/O = I + if(istype(O) && O.owner == src) + . = 0 // keep a good grip on your heart + +/mob/living/carbon/human/unEquip(obj/item/I, force) + . = ..() //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should. + if(!. || !I) + return + + if(I == wear_suit) + if(s_store) + unEquip(s_store, 1) //It makes no sense for your suit storage to stay on you if you drop your suit. + wear_suit = null + if(I.flags_inv & HIDEJUMPSUIT) + update_inv_w_uniform() + update_inv_wear_suit() + else if(I == w_uniform) + if(r_store) + unEquip(r_store, 1) //Again, makes sense for pockets to drop. + if(l_store) + unEquip(l_store, 1) + if(wear_id) + unEquip(wear_id) + if(belt) + unEquip(belt) + w_uniform = null + update_inv_w_uniform() + else if(I == gloves) + gloves = null + update_inv_gloves() + else if(I == glasses) + glasses = null + var/obj/item/clothing/glasses/G = I + if(G.tint) + update_tint() + if(G.prescription) + update_nearsighted_effects() + if(G.vision_flags || G.see_in_dark || G.invis_override || G.invis_view || !isnull(G.lighting_alpha)) + update_sight() + update_inv_glasses() + update_client_colour() + else if(I == head) + head = null + if(I.flags & BLOCKHAIR || I.flags & BLOCKHEADHAIR) + update_hair() //rebuild hair + update_fhair() + update_head_accessory() + // Bandanas and paper hats go on the head but are not head clothing + if(istype(I,/obj/item/clothing/head)) + var/obj/item/clothing/head/hat = I + if(hat.vision_flags || hat.see_in_dark || !isnull(hat.lighting_alpha)) + update_sight() + head_update(I) + update_inv_head() + else if(I == r_ear) + r_ear = null + update_inv_ears() + else if(I == l_ear) + l_ear = null + update_inv_ears() + else if(I == shoes) + shoes = null + update_inv_shoes() + else if(I == belt) + belt = null + update_inv_belt() + else if(I == wear_mask) + wear_mask = null + if(I.flags & BLOCKHAIR || I.flags & BLOCKHEADHAIR) + update_hair() //rebuild hair + update_fhair() + update_head_accessory() + if(internal && !get_organ_slot("breathing_tube")) + internal = null + update_action_buttons_icon() + wear_mask_update(I, toggle_off = FALSE) + sec_hud_set_ID() + update_inv_wear_mask() + else if(I == wear_id) + wear_id = null + sec_hud_set_ID() + update_inv_wear_id() + else if(I == wear_pda) + wear_pda = null + update_inv_wear_pda() + else if(I == r_store) + r_store = null + update_inv_pockets() + else if(I == l_store) + l_store = null + update_inv_pockets() + else if(I == s_store) + s_store = null + update_inv_s_store() + else if(I == back) + back = null + update_inv_back() + else if(I == r_hand) + r_hand = null + update_inv_r_hand() + else if(I == l_hand) + l_hand = null + update_inv_l_hand() + + + + +//This is an UNSAFE proc. Use mob_can_equip() before calling this one! Or rather use equip_to_slot_if_possible() or advanced_equip_to_slot_if_possible() +//set redraw_mob to 0 if you don't wish the hud to be updated - if you're doing it manually in your own proc. +/mob/living/carbon/human/equip_to_slot(obj/item/I, slot, redraw_mob = 1) + if(!slot) + return + if(!istype(I)) + return + if(!has_organ_for_slot(slot)) + return + + if(I == src.l_hand) + src.l_hand = null + update_inv_l_hand() //So items actually disappear from hands. + else if(I == src.r_hand) + src.r_hand = null + update_inv_r_hand() + + I.screen_loc = null + I.forceMove(src) + I.equipped(src, slot) + I.layer = ABOVE_HUD_LAYER + I.plane = ABOVE_HUD_PLANE + + switch(slot) + if(slot_back) + back = I + update_inv_back(redraw_mob) + if(slot_wear_mask) + wear_mask = I + if((wear_mask.flags & BLOCKHAIR) || (wear_mask.flags & BLOCKHEADHAIR)) + update_hair(redraw_mob) //rebuild hair + update_fhair(redraw_mob) + update_head_accessory(redraw_mob) + if(hud_list.len) + sec_hud_set_ID() + wear_mask_update(I, toggle_off = TRUE) + update_inv_wear_mask(redraw_mob) + if(slot_handcuffed) + handcuffed = I + update_inv_handcuffed(redraw_mob) + if(slot_legcuffed) + legcuffed = I + update_inv_legcuffed(redraw_mob) + if(slot_l_hand) + l_hand = I + update_inv_l_hand(redraw_mob) + if(slot_r_hand) + r_hand = I + update_inv_r_hand(redraw_mob) + if(slot_belt) + belt = I + update_inv_belt(redraw_mob) + if(slot_wear_id) + wear_id = I + if(hud_list.len) + sec_hud_set_ID() + update_inv_wear_id(redraw_mob) + if(slot_wear_pda) + wear_pda = I + update_inv_wear_pda(redraw_mob) + if(slot_l_ear) + l_ear = I + if(l_ear.slot_flags & SLOT_TWOEARS) + var/obj/item/clothing/ears/offear/O = new(I) + O.forceMove(src) + r_ear = O + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + update_inv_ears(redraw_mob) + if(slot_r_ear) + r_ear = I + if(r_ear.slot_flags & SLOT_TWOEARS) + var/obj/item/clothing/ears/offear/O = new(I) + O.forceMove(src) + l_ear = O + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + update_inv_ears(redraw_mob) + if(slot_glasses) + glasses = I + var/obj/item/clothing/glasses/G = I + if(G.tint) + update_tint() + if(G.prescription) + update_nearsighted_effects() + if(G.vision_flags || G.see_in_dark || G.invis_override || G.invis_view || !isnull(G.lighting_alpha)) + update_sight() + update_inv_glasses(redraw_mob) + update_client_colour() + if(slot_gloves) + gloves = I + update_inv_gloves(redraw_mob) + if(slot_head) + head = I + if((head.flags & BLOCKHAIR) || (head.flags & BLOCKHEADHAIR)) + update_hair(redraw_mob) //rebuild hair + update_fhair(redraw_mob) + update_head_accessory(redraw_mob) + // paper + bandanas + if(istype(I, /obj/item/clothing/head)) + var/obj/item/clothing/head/hat = I + if(hat.vision_flags || hat.see_in_dark || !isnull(hat.lighting_alpha)) + update_sight() + head_update(I) + update_inv_head(redraw_mob) + if(slot_shoes) + shoes = I + update_inv_shoes(redraw_mob) + if(slot_wear_suit) + wear_suit = I + update_inv_wear_suit(redraw_mob) + if(slot_w_uniform) + w_uniform = I + update_inv_w_uniform(redraw_mob) + if(slot_l_store) + l_store = I + update_inv_pockets(redraw_mob) + if(slot_r_store) + r_store = I + update_inv_pockets(redraw_mob) + if(slot_s_store) + s_store = I + update_inv_s_store(redraw_mob) + if(slot_in_backpack) + if(get_active_hand() == I) + unEquip(I) + I.forceMove(back) + if(slot_tie) + var/obj/item/clothing/under/uniform = src.w_uniform + uniform.attackby(I, src) + else + to_chat(src, "You are trying to equip this item to an unsupported inventory slot. Report this to a coder!") + +/mob/living/carbon/human/put_in_hands(obj/item/I) + if(!I) + return FALSE + if(put_in_active_hand(I)) + return TRUE + else if(put_in_inactive_hand(I)) + return TRUE + else + . = ..() + +// Return the item currently in the slot ID +/mob/living/carbon/human/get_item_by_slot(slot_id) + switch(slot_id) + if(slot_back) + return back + if(slot_wear_mask) + return wear_mask + if(slot_handcuffed) + return handcuffed + if(slot_legcuffed) + return legcuffed + if(slot_l_hand) + return l_hand + if(slot_r_hand) + return r_hand + if(slot_belt) + return belt + if(slot_wear_id) + return wear_id + if(slot_wear_pda) + return wear_pda + if(slot_l_ear) + return l_ear + if(slot_r_ear) + return r_ear + if(slot_glasses) + return glasses + if(slot_gloves) + return gloves + if(slot_head) + return head + if(slot_shoes) + return shoes + if(slot_wear_suit) + return wear_suit + if(slot_w_uniform) + return w_uniform + if(slot_l_store) + return l_store + if(slot_r_store) + return r_store + if(slot_s_store) + return s_store + return null + +/mob/living/carbon/human/get_all_slots() + . = get_head_slots() | get_body_slots() + +/mob/living/carbon/human/proc/get_body_slots() + return list( + l_hand, + r_hand, + back, + s_store, + handcuffed, + legcuffed, + wear_suit, + gloves, + shoes, + belt, + wear_id, + wear_pda, + l_store, + r_store, + w_uniform + ) + +/mob/living/carbon/human/proc/get_head_slots() + return list( + head, + wear_mask, + glasses, + r_ear, + l_ear, + ) + +// humans have their pickpocket gloves, so they get no message when stealing things +/mob/living/carbon/human/stripPanelUnequip(obj/item/what, mob/who, where) + var/is_silent = 0 + var/obj/item/clothing/gloves/G = gloves + if(istype(G)) + is_silent = G.pickpocket + + ..(what, who, where, silent = is_silent) + +// humans have their pickpocket gloves, so they get no message when stealing things +/mob/living/carbon/human/stripPanelEquip(obj/item/what, mob/who, where) + var/is_silent = 0 + var/obj/item/clothing/gloves/G = gloves + if(istype(G)) + is_silent = G.pickpocket + + ..(what, who, where, silent = is_silent) + +/mob/living/carbon/human/can_equip(obj/item/I, slot, disable_warning = FALSE) + return dna.species.can_equip(I, slot, disable_warning, src) + +/mob/living/carbon/human/proc/equipOutfit(outfit, visualsOnly = FALSE) + var/datum/outfit/O = null + + if(ispath(outfit)) + O = new outfit + else + O = outfit + if(!istype(O)) + return 0 + if(!O) + return 0 + + return O.equip(src, visualsOnly) + +//delete all equipment without dropping anything +/mob/living/carbon/human/proc/delete_equipment() + for(var/slot in get_all_slots())//order matters, dependant slots go first + qdel(slot) diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 93e7ff54ec6..b46dff4718a 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1,1122 +1,1122 @@ -/mob/living/carbon/human/Life(seconds, times_fired) - life_tick++ - - voice = GetVoice() - - if(..()) - - if(check_mutations) - domutcheck(src,null) - update_mutations() - check_mutations=0 - - handle_pain() - handle_heartbeat() - handle_drunk() - dna.species.handle_life(src) - if(!client) - dna.species.handle_npc(src) - - if(stat != DEAD) - //Stuff jammed in your limbs hurts - handle_embedded_objects() - - if(stat == DEAD) - handle_decay() - - if(life_tick > 5 && timeofdeath && (timeofdeath < 5 || world.time - timeofdeath > 6000)) //We are long dead, or we're junk mobs spawned like the clowns on the clown shuttle - return //We go ahead and process them 5 times for HUD images and other stuff though. - - //Update our name based on whether our face is obscured/disfigured - name = get_visible_name() - pulse = handle_pulse(times_fired) - - if(mind && mind.vampire) - mind.vampire.handle_vampire() - if(life_tick == 1) - regenerate_icons() // Make sure the inventory updates - - handle_ghosted() - handle_ssd() - -/mob/living/carbon/human/proc/handle_ghosted() - if(player_ghosted > 0 && stat == CONSCIOUS && job && !restrained()) - if(key) - player_ghosted = 0 - else - player_ghosted++ - if(player_ghosted % 150 == 0) - force_cryo_human(src) - -/mob/living/carbon/human/proc/handle_ssd() - if(player_logged > 0 && stat != DEAD && job) - player_logged++ - if(istype(loc, /obj/machinery/cryopod)) - return - if(config.auto_cryo_ssd_mins && (player_logged >= (config.auto_cryo_ssd_mins * 30)) && player_logged % 30 == 0) - var/turf/T = get_turf(src) - if(!is_station_level(T.z)) - return - var/area/A = get_area(src) - if(cryo_ssd(src)) - var/obj/effect/portal/P = new /obj/effect/portal(T, null, null, 40) - P.name = "NT SSD Teleportation Portal" - if(A.fast_despawn) - force_cryo_human(src) - -/mob/living/carbon/human/calculate_affecting_pressure(var/pressure) - ..() - var/pressure_difference = abs( pressure - ONE_ATMOSPHERE ) - - var/pressure_adjustment_coefficient = 1 //Determins how much the clothing you are wearing protects you in percent. - if(wear_suit && (wear_suit.flags & STOPSPRESSUREDMAGE) && head && (head.flags & STOPSPRESSUREDMAGE)) // Complete set of pressure-proof suit worn, assume fully sealed. - pressure_adjustment_coefficient = 0 - pressure_adjustment_coefficient = max(pressure_adjustment_coefficient,0) //So it isn't less than 0 - pressure_difference = pressure_difference * pressure_adjustment_coefficient - if(pressure > ONE_ATMOSPHERE) - return ONE_ATMOSPHERE + pressure_difference - else - return ONE_ATMOSPHERE - pressure_difference - - -/mob/living/carbon/human/handle_disabilities() - if(disabilities & EPILEPSY) - if((prob(1) && paralysis < 1)) - visible_message("[src] starts having a seizure!","You have a seizure!") - Paralyse(10) - Jitter(1000) - - // If we have the gene for being crazy, have random events. - if(dna.GetSEState(HALLUCINATIONBLOCK)) - if(prob(1)) - Hallucinate(20) - - if(disabilities & COUGHING) - if((prob(5) && paralysis <= 1)) - drop_item() - emote("cough") - if(disabilities & TOURETTES) - if((prob(10) && paralysis <= 1)) - Stun(10) - switch(rand(1, 3)) - if(1) - emote("twitch") - if(2 to 3) - var/tourettes = pick("SHIT", "PISS", "FUCK", "CUNT", "COCKSUCKER", "MOTHERFUCKER", "TITS") - say("[prob(50) ? ";" : ""][tourettes]") - var/x_offset = pixel_x + rand(-2,2) //Should probably be moved into the twitch emote at some point. - var/y_offset = pixel_y + rand(-1,1) - animate(src, pixel_x = pixel_x + x_offset, pixel_y = pixel_y + y_offset, time = 1) - animate(pixel_x = initial(pixel_x) , pixel_y = initial(pixel_y), time = 1) - - if(disabilities & NERVOUS) - if(prob(10)) - Stuttering(10) - - if(getBrainLoss() >= 60 && stat != DEAD) - if(prob(3)) - var/list/s1 = list("IM A [pick("PONY","LIZARD","taJaran","kitty","Vulpakin","drASK","BIRDIE","voxxie","race car","combat meCH","SPESSSHIP")] [pick("NEEEEEEIIIIIIIIIGH","sKREEEEEE","MEOW","NYA~","rawr","Barkbark","Hissssss","vROOOOOM","pewpew","choo Choo")]!", - "without oxigen blob don't evoluate?", - "CAPTAINS A COMDOM", - "[pick("", "that damn traitor")] [pick("joerge", "george", "gorge", "gdoruge")] [pick("mellens", "melons", "mwrlins")] is grifing me HAL;P!!!", - "can u give me [pick("telikesis","halk","eppilapse")]?", - "THe saiyans screwed", - "Bi is THE BEST OF BOTH WORLDS", - "I WANNA PET TEH monkeyS", - "stop grifing me!!!!", - "SOTP IT!", - "HALPZ SITCULITY", - "VOXES caN't LOVE", - "my dad own this station", - "the CHef put [pick("PROTEIN", "toiret waTer", "RiPPleing TendIes", "Einzymes","HORRY WALTER","nuTriments","ReActive MutAngen","TeSLium","sKrektonium")] in my [pick("wiSh soup","Bullito","rAingurber","sOilent GREEn","KoI Susishes","yaya")]!", - "the monkey have TASER ARMS!", - "qM blew my points on [pick("cOMbat Shtogun","inSuLated gloves","LOTS MASSHEEN!")]", - "EI'NATH!", - "WAKE UP SHEEPLES!", - "et wus my [pick("wittle brother!!","fiancee","friend staying over","entiRe orphanage","love interest","wife","husband","liTTle kids","sentient cAT","accidentally")]!") - - var/list/s2 = list("FUS RO DAH", - "fuckin tangerines!!!", - "stat me", - ">my face", - "roll it easy!", - "waaaaaagh!!!", - "red wonz go fasta", - "FOR TEH EMPRAH", - "HAZ A SECURE DAY!!!!", - "dem dwarfs man, dem dwarfs", - "SPESS MAHREENS", - "hwee did eet fhor khayosss", - "lifelike texture", - "luv can bloooom", - "PACKETS!!!", - "[pick("WHERE MY","aYE need","giv me my","bath me inn.")] [pick("dermaline","alKkyZine","dylOvene","inAprovaline","biCaridine","Hyperzine","kELotane","lePorazine","bAcch Salts","tricord","clOnexazone","hydroChloric Acid","chlorine Hydrate","paRoxetine")]!", - "mALPRACTICEBAY", - "I HavE A pe H dee iN ENTerpriSE resOUrCE pLaNNIN", - "h-h-HalP MaINT", - "dey come, dey COME! DEY COME!!!", - "THE END IS NIGH!", - "I FOT AND DIED FOR MUH [pick("RITES","FREEDOM","payCHECK","cARGO points","teCH Level","doG","mAPLe syrup","fluffy fWiends","gateway Loot")]", - "KILL DEM [pick("mainTnacE cHickinNS","kiRA CulwnNES","FLOOR CLUWNEs","MIME ASSASSIN","BOMBING TAJARAN","cC offiser","morPhlings","slinglings")]!") - switch(pick(1,2,3)) - if(1) - say(pick(s1)) - if(2) - say(pick(s2)) - if(3) - emote("drool") - -/mob/living/carbon/human/handle_mutations_and_radiation() - for(var/datum/dna/gene/gene in dna_genes) - if(!gene.block) - continue - if(gene.is_active(src)) - gene.OnMobLife(src) - if(!ignore_gene_stability && gene_stability < GENETIC_DAMAGE_STAGE_1) - var/instability = DEFAULT_GENE_STABILITY - gene_stability - if(prob(instability * 0.1)) - adjustFireLoss(min(5, instability * 0.67)) - to_chat(src, "You feel like your skin is burning and bubbling off!") - if(gene_stability < GENETIC_DAMAGE_STAGE_2) - if(prob(instability * 0.83)) - adjustCloneLoss(min(4, instability * 0.05)) - to_chat(src, "You feel as if your body is warping.") - if(prob(instability * 0.1)) - adjustToxLoss(min(5, instability * 0.67)) - to_chat(src, "You feel weak and nauseous.") - if(gene_stability < GENETIC_DAMAGE_STAGE_3 && prob(1)) - to_chat(src, "You feel incredibly sick... Something isn't right!") - spawn(300) - if(gene_stability < GENETIC_DAMAGE_STAGE_3) - gib() - - if(!(RADIMMUNE in dna.species.species_traits)) - if(radiation) - radiation = Clamp(radiation, 0, 200) - - var/autopsy_damage = 0 - switch(radiation) - if(1 to 49) - radiation = max(radiation-1, 0) - if(prob(25)) - adjustToxLoss(1) - adjustFireLoss(1) - autopsy_damage = 2 - - if(50 to 74) - radiation = max(radiation-2, 0) - adjustToxLoss(1) - adjustFireLoss(1) - autopsy_damage = 2 - if(prob(5)) - radiation = max(radiation-5, 0) - Weaken(3) - to_chat(src, "You feel weak.") - emote("collapse") - - if(75 to 100) - radiation = max(radiation-2, 0) - adjustToxLoss(2) - adjustFireLoss(2) - autopsy_damage = 4 - if(prob(2)) - to_chat(src, "You mutate!") - randmutb(src) - domutcheck(src, null) - - if(101 to 150) - radiation = max(radiation-3, 0) - adjustToxLoss(2) - adjustFireLoss(3) - autopsy_damage = 5 - if(prob(4)) - to_chat(src, "You mutate!") - randmutb(src) - domutcheck(src, null) - - if(151 to INFINITY) - radiation = max(radiation-3, 0) - adjustToxLoss(2) - adjustFireLoss(3) - autopsy_damage = 5 - if(prob(6)) - to_chat(src, "You mutate!") - randmutb(src) - domutcheck(src, null) - - if(autopsy_damage) - var/obj/item/organ/external/chest/chest = get_organ("chest") - if(chest) - chest.add_autopsy_data("Radiation Poisoning", autopsy_damage) - -/mob/living/carbon/human/breathe() - if(!dna.species.breathe(src)) - ..() - -/mob/living/carbon/human/check_breath(datum/gas_mixture/breath) - - var/obj/item/organ/internal/L = get_organ_slot("lungs") - - if(!L || L && (L.status & ORGAN_DEAD)) - if(health >= HEALTH_THRESHOLD_CRIT) - adjustOxyLoss(HUMAN_MAX_OXYLOSS + 1) - else if(!(NOCRITDAMAGE in dna.species.species_traits)) - adjustOxyLoss(HUMAN_MAX_OXYLOSS) - - if(dna.species) - var/datum/species/S = dna.species - - if(S.breathid == "o2") - throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) - else if(S.breathid == "tox") - throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox) - else if(S.breathid == "co2") - throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2) - else if(S.breathid == "n2") - throw_alert("not_enough_nitro", /obj/screen/alert/not_enough_nitro) - - return FALSE - else - if(istype(L, /obj/item/organ/internal/lungs)) - var/obj/item/organ/internal/lungs/lun = L - lun.check_breath(breath, src) - -// USED IN DEATHWHISPERS -/mob/living/carbon/human/proc/isInCrit() - // Health is in deep shit and we're not already dead - return health <= HEALTH_THRESHOLD_CRIT && stat != DEAD - - -/mob/living/carbon/human/get_breath_from_internal(volume_needed) //making this call the parent would be far too complicated - if(internal) - var/null_internals = 0 //internals are invalid, therefore turn them off - var/skip_contents_check = 0 //rigsuit snowflake, oxygen tanks aren't stored inside the mob, so the 'contents.Find' check has to be skipped. - - if(!get_organ_slot("breathing_tube")) - if(!(wear_mask && wear_mask.flags & AIRTIGHT)) //if NOT (wear_mask AND wear_mask.flags CONTAIN AIRTIGHT) - if(!(head && head.flags & AIRTIGHT)) //if NOT (head AND head.flags CONTAIN AIRTIGHT) - null_internals = 1 //not wearing a mask or suitable helmet - - if(istype(back, /obj/item/rig)) //wearing a rigsuit - var/obj/item/rig/rig = back //needs to be typecasted because this doesn't use get_rig() for some reason - if(rig.offline && (rig.air_supply && internal == rig.air_supply)) //if rig IS offline AND (rig HAS air_supply AND internal IS air_supply) - null_internals = 1 //offline suits do not breath - - else if(rig.air_supply && internal == rig.air_supply) //if rig HAS air_supply AND internal IS rig air_supply - skip_contents_check = 1 //skip contents.Find() check, the oxygen is valid even being outside of the mob - - if(!contents.Find(internal) && (!skip_contents_check)) //if internal NOT IN contents AND skip_contents_check IS false - null_internals = 1 //not a rigsuit and your oxygen is gone - - if(null_internals) //something wants internals gone - internal = null //so do it - update_action_buttons_icon() - - if(internal) //check for hud updates every time this is called - return internal.remove_air_volume(volume_needed) //returns the valid air - - return null - -/mob/living/carbon/human/handle_environment(datum/gas_mixture/environment) - if(!environment) - return - - var/loc_temp = get_temperature(environment) -// to_chat(world, "Loc temp: [loc_temp] - Body temp: [bodytemperature] - Fireloss: [getFireLoss()] - Thermal protection: [get_thermal_protection()] - Fire protection: [thermal_protection + add_fire_protection(loc_temp)] - Heat capacity: [environment_heat_capacity] - Location: [loc] - src: [src]") - - //Body temperature is adjusted in two steps. Firstly your body tries to stabilize itself a bit. - if(stat != DEAD) - stabilize_temperature_from_calories() - - //After then, it reacts to the surrounding atmosphere based on your thermal protection - if(!on_fire) //If you're on fire, you do not heat up or cool down based on surrounding gases - if(loc_temp < bodytemperature) - //Place is colder than we are - var/thermal_protection = get_cold_protection(loc_temp) //This returns a 0 - 1 value, which corresponds to the percentage of protection based on what you're wearing and what you're exposed to. - if(thermal_protection < 1) - bodytemperature += min((1-thermal_protection) * ((loc_temp - bodytemperature) / BODYTEMP_COLD_DIVISOR), BODYTEMP_COOLING_MAX) - else - //Place is hotter than we are - var/thermal_protection = get_heat_protection(loc_temp) //This returns a 0 - 1 value, which corresponds to the percentage of protection based on what you're wearing and what you're exposed to. - if(thermal_protection < 1) - bodytemperature += min((1-thermal_protection) * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR), BODYTEMP_HEATING_MAX) - - // +/- 50 degrees from 310.15K is the 'safe' zone, where no damage is dealt. - if(bodytemperature > dna.species.heat_level_1) - //Body temperature is too hot. - if(status_flags & GODMODE) return 1 //godmode - var/mult = dna.species.heatmod - - if(bodytemperature >= dna.species.heat_level_1 && bodytemperature <= dna.species.heat_level_2) - throw_alert("temp", /obj/screen/alert/hot, 1) - take_overall_damage(burn=mult*HEAT_DAMAGE_LEVEL_1, updating_health = TRUE, used_weapon = "High Body Temperature") - if(bodytemperature > dna.species.heat_level_2 && bodytemperature <= dna.species.heat_level_3) - throw_alert("temp", /obj/screen/alert/hot, 2) - take_overall_damage(burn=mult*HEAT_DAMAGE_LEVEL_2, updating_health = TRUE, used_weapon = "High Body Temperature") - if(bodytemperature > dna.species.heat_level_3 && bodytemperature < INFINITY) - throw_alert("temp", /obj/screen/alert/hot, 3) - if(on_fire) - take_overall_damage(burn=mult*HEAT_DAMAGE_LEVEL_3, updating_health = TRUE, used_weapon = "Fire") - else - take_overall_damage(burn=mult*HEAT_DAMAGE_LEVEL_2, updating_health = TRUE, used_weapon = "High Body Temperature") - - else if(bodytemperature < dna.species.cold_level_1) - if(status_flags & GODMODE) - return 1 - if(stat == DEAD) - return 1 - - if(!istype(loc, /obj/machinery/atmospherics/unary/cryo_cell)) - var/mult = dna.species.coldmod - if(bodytemperature >= dna.species.cold_level_2 && bodytemperature <= dna.species.cold_level_1) - throw_alert("temp", /obj/screen/alert/cold, 1) - take_overall_damage(burn=mult*COLD_DAMAGE_LEVEL_1, updating_health = TRUE, used_weapon = "Low Body Temperature") - if(bodytemperature >= dna.species.cold_level_3 && bodytemperature < dna.species.cold_level_2) - throw_alert("temp", /obj/screen/alert/cold, 2) - take_overall_damage(burn=mult*COLD_DAMAGE_LEVEL_2, updating_health = TRUE, used_weapon = "Low Body Temperature") - if(bodytemperature > -INFINITY && bodytemperature < dna.species.cold_level_3) - throw_alert("temp", /obj/screen/alert/cold, 3) - take_overall_damage(burn=mult*COLD_DAMAGE_LEVEL_3, updating_health = TRUE, used_weapon = "Low Body Temperature") - else - clear_alert("temp") - else - clear_alert("temp") - - // Account for massive pressure differences. Done by Polymorph - // Made it possible to actually have something that can protect against high pressure... Done by Errorage. Polymorph now has an axe sticking from his head for his previous hardcoded nonsense! - - var/pressure = environment.return_pressure() - var/adjusted_pressure = calculate_affecting_pressure(pressure) //Returns how much pressure actually affects the mob. - if(status_flags & GODMODE) return 1 //godmode - - if(adjusted_pressure >= dna.species.hazard_high_pressure) - if(!(HEATRES in mutations)) - var/pressure_damage = min( ( (adjusted_pressure / dna.species.hazard_high_pressure) -1 )*PRESSURE_DAMAGE_COEFFICIENT , MAX_HIGH_PRESSURE_DAMAGE) - take_overall_damage(brute=pressure_damage, updating_health = TRUE, used_weapon = "High Pressure") - throw_alert("pressure", /obj/screen/alert/highpressure, 2) - else - clear_alert("pressure") - else if(adjusted_pressure >= dna.species.warning_high_pressure) - throw_alert("pressure", /obj/screen/alert/highpressure, 1) - else if(adjusted_pressure >= dna.species.warning_low_pressure) - clear_alert("pressure") - else if(adjusted_pressure >= dna.species.hazard_low_pressure) - throw_alert("pressure", /obj/screen/alert/lowpressure, 1) - else - if(COLDRES in mutations) - clear_alert("pressure") - else - take_overall_damage(brute=LOW_PRESSURE_DAMAGE, updating_health = TRUE, used_weapon = "Low Pressure") - throw_alert("pressure", /obj/screen/alert/lowpressure, 2) - - -///FIRE CODE -/mob/living/carbon/human/handle_fire() - if(..()) - return - if(HEATRES in mutations) - return - if(on_fire) - var/thermal_protection = get_thermal_protection() - - if(thermal_protection >= FIRE_IMMUNITY_MAX_TEMP_PROTECT) - return - if(thermal_protection >= FIRE_SUIT_MAX_TEMP_PROTECT) - bodytemperature += 11 - else - bodytemperature += (BODYTEMP_HEATING_MAX + (fire_stacks * 12)) - -/mob/living/carbon/human/proc/get_thermal_protection() - var/thermal_protection = 0 //Simple check to estimate how protected we are against multiple temperatures - if(wear_suit) - if(wear_suit.max_heat_protection_temperature >= FIRE_SUIT_MAX_TEMP_PROTECT) - thermal_protection += (wear_suit.max_heat_protection_temperature*0.7) - if(head) - if(head.max_heat_protection_temperature >= FIRE_HELM_MAX_TEMP_PROTECT) - thermal_protection += (head.max_heat_protection_temperature*THERMAL_PROTECTION_HEAD) - thermal_protection = round(thermal_protection) - return thermal_protection - -//END FIRE CODE - -/mob/living/carbon/human/proc/stabilize_temperature_from_calories() - var/body_temperature_difference = dna.species.body_temperature - bodytemperature - - if(bodytemperature <= dna.species.cold_level_1) //260.15 is 310.15 - 50, the temperature where you start to feel effects. - bodytemperature += max((body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR), BODYTEMP_AUTORECOVERY_MINIMUM) - if(bodytemperature >= dna.species.cold_level_1 && bodytemperature <= dna.species.heat_level_1) - bodytemperature += body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR - if(bodytemperature >= dna.species.heat_level_1) //360.15 is 310.15 + 50, the temperature where you start to feel effects. - //We totally need a sweat system cause it totally makes sense...~ - bodytemperature += min((body_temperature_difference / BODYTEMP_AUTORECOVERY_DIVISOR), -BODYTEMP_AUTORECOVERY_MINIMUM) //We're dealing with negative numbers - - - //This proc returns a number made up of the flags for body parts which you are protected on. (such as HEAD, UPPER_TORSO, LOWER_TORSO, etc. See setup.dm for the full list) -/mob/living/carbon/human/proc/get_heat_protection_flags(temperature) //Temperature is the temperature you're being exposed to. - var/thermal_protection_flags = 0 - //Handle normal clothing - if(head) - if(head.max_heat_protection_temperature && head.max_heat_protection_temperature >= temperature) - thermal_protection_flags |= head.heat_protection - if(wear_suit) - if(wear_suit.max_heat_protection_temperature && wear_suit.max_heat_protection_temperature >= temperature) - thermal_protection_flags |= wear_suit.heat_protection - if(w_uniform) - if(w_uniform.max_heat_protection_temperature && w_uniform.max_heat_protection_temperature >= temperature) - thermal_protection_flags |= w_uniform.heat_protection - if(shoes) - if(shoes.max_heat_protection_temperature && shoes.max_heat_protection_temperature >= temperature) - thermal_protection_flags |= shoes.heat_protection - if(gloves) - if(gloves.max_heat_protection_temperature && gloves.max_heat_protection_temperature >= temperature) - thermal_protection_flags |= gloves.heat_protection - if(wear_mask) - if(wear_mask.max_heat_protection_temperature && wear_mask.max_heat_protection_temperature >= temperature) - thermal_protection_flags |= wear_mask.heat_protection - - return thermal_protection_flags - -/mob/living/carbon/human/proc/get_heat_protection(temperature) //Temperature is the temperature you're being exposed to. - - if(HEATRES in mutations) - return 1 - - var/thermal_protection_flags = get_heat_protection_flags(temperature) - - var/thermal_protection = 0.0 - if(thermal_protection_flags) - if(thermal_protection_flags & HEAD) - thermal_protection += THERMAL_PROTECTION_HEAD - if(thermal_protection_flags & UPPER_TORSO) - thermal_protection += THERMAL_PROTECTION_UPPER_TORSO - if(thermal_protection_flags & LOWER_TORSO) - thermal_protection += THERMAL_PROTECTION_LOWER_TORSO - if(thermal_protection_flags & LEG_LEFT) - thermal_protection += THERMAL_PROTECTION_LEG_LEFT - if(thermal_protection_flags & LEG_RIGHT) - thermal_protection += THERMAL_PROTECTION_LEG_RIGHT - if(thermal_protection_flags & FOOT_LEFT) - thermal_protection += THERMAL_PROTECTION_FOOT_LEFT - if(thermal_protection_flags & FOOT_RIGHT) - thermal_protection += THERMAL_PROTECTION_FOOT_RIGHT - if(thermal_protection_flags & ARM_LEFT) - thermal_protection += THERMAL_PROTECTION_ARM_LEFT - if(thermal_protection_flags & ARM_RIGHT) - thermal_protection += THERMAL_PROTECTION_ARM_RIGHT - if(thermal_protection_flags & HAND_LEFT) - thermal_protection += THERMAL_PROTECTION_HAND_LEFT - if(thermal_protection_flags & HAND_RIGHT) - thermal_protection += THERMAL_PROTECTION_HAND_RIGHT - - - return min(1,thermal_protection) - - //See proc/get_heat_protection_flags(temperature) for the description of this proc. -/mob/living/carbon/human/proc/get_cold_protection_flags(temperature) - var/thermal_protection_flags = 0 - //Handle normal clothing - - if(head) - if(head.min_cold_protection_temperature && head.min_cold_protection_temperature <= temperature) - thermal_protection_flags |= head.cold_protection - if(wear_suit) - if(wear_suit.min_cold_protection_temperature && wear_suit.min_cold_protection_temperature <= temperature) - thermal_protection_flags |= wear_suit.cold_protection - if(w_uniform) - if(w_uniform.min_cold_protection_temperature && w_uniform.min_cold_protection_temperature <= temperature) - thermal_protection_flags |= w_uniform.cold_protection - if(shoes) - if(shoes.min_cold_protection_temperature && shoes.min_cold_protection_temperature <= temperature) - thermal_protection_flags |= shoes.cold_protection - if(gloves) - if(gloves.min_cold_protection_temperature && gloves.min_cold_protection_temperature <= temperature) - thermal_protection_flags |= gloves.cold_protection - if(wear_mask) - if(wear_mask.min_cold_protection_temperature && wear_mask.min_cold_protection_temperature <= temperature) - thermal_protection_flags |= wear_mask.cold_protection - - return thermal_protection_flags - -/mob/living/carbon/human/proc/get_cold_protection(temperature) - - if(COLDRES in mutations) - return 1 //Fully protected from the cold. - - temperature = max(temperature, TCMB) //There is an occasional bug where the temperature is miscalculated in areas with a small amount of gas on them, so this is necessary to ensure that that bug does not affect this calculation. Space's temperature is 2.7K and most suits that are intended to protect against any cold, protect down to 2.0K. - var/thermal_protection_flags = get_cold_protection_flags(temperature) - - var/thermal_protection = 0.0 - if(thermal_protection_flags) - if(thermal_protection_flags & HEAD) - thermal_protection += THERMAL_PROTECTION_HEAD - if(thermal_protection_flags & UPPER_TORSO) - thermal_protection += THERMAL_PROTECTION_UPPER_TORSO - if(thermal_protection_flags & LOWER_TORSO) - thermal_protection += THERMAL_PROTECTION_LOWER_TORSO - if(thermal_protection_flags & LEG_LEFT) - thermal_protection += THERMAL_PROTECTION_LEG_LEFT - if(thermal_protection_flags & LEG_RIGHT) - thermal_protection += THERMAL_PROTECTION_LEG_RIGHT - if(thermal_protection_flags & FOOT_LEFT) - thermal_protection += THERMAL_PROTECTION_FOOT_LEFT - if(thermal_protection_flags & FOOT_RIGHT) - thermal_protection += THERMAL_PROTECTION_FOOT_RIGHT - if(thermal_protection_flags & ARM_LEFT) - thermal_protection += THERMAL_PROTECTION_ARM_LEFT - if(thermal_protection_flags & ARM_RIGHT) - thermal_protection += THERMAL_PROTECTION_ARM_RIGHT - if(thermal_protection_flags & HAND_LEFT) - thermal_protection += THERMAL_PROTECTION_HAND_LEFT - if(thermal_protection_flags & HAND_RIGHT) - thermal_protection += THERMAL_PROTECTION_HAND_RIGHT - - return min(1,thermal_protection) - - -/mob/living/carbon/human/proc/get_covered_bodyparts() - var/covered = 0 - - if(head) - covered |= head.body_parts_covered - if(wear_suit) - covered |= wear_suit.body_parts_covered - if(w_uniform) - covered |= w_uniform.body_parts_covered - if(shoes) - covered |= shoes.body_parts_covered - if(gloves) - covered |= gloves.body_parts_covered - if(wear_mask) - covered |= wear_mask.body_parts_covered - - return covered - -/mob/living/carbon/human/handle_chemicals_in_body() - ..() - - if(status_flags & GODMODE) - return 0 //godmode - - if(!(NO_HUNGER in dna.species.species_traits)) - //The fucking FAT mutation is the greatest shit ever. It makes everyone so hot and bothered. - if(CAN_BE_FAT in dna.species.species_traits) - if(FAT in mutations) - if(overeatduration < 100) - becomeSlim() - else - if(overeatduration > 500) - becomeFat() - - // nutrition decrease - if(nutrition > 0 && stat != DEAD) - // THEY HUNGER - var/hunger_rate = hunger_drain - if(satiety > 0) - satiety-- - if(satiety < 0) - satiety++ - if(prob(round(-satiety/40))) - Jitter(5) - hunger_rate = 3 * hunger_drain - adjust_nutrition(-hunger_rate) - - if(nutrition > NUTRITION_LEVEL_FULL) - if(overeatduration < 600) //capped so people don't take forever to unfat - overeatduration++ - - else - if(overeatduration > 1) - if(OBESITY in mutations) - overeatduration -= 1 // Those with obesity gene take twice as long to unfat - else - overeatduration -= 2 - - //metabolism change - if(nutrition > NUTRITION_LEVEL_FAT) - metabolism_efficiency = 1 - else if(nutrition > NUTRITION_LEVEL_FED && satiety > 80) - if(metabolism_efficiency != 1.25) - to_chat(src, "You feel vigorous.") - metabolism_efficiency = 1.25 - else if(nutrition < NUTRITION_LEVEL_STARVING + 50) - if(metabolism_efficiency != 0.8) - to_chat(src, "You feel sluggish.") - metabolism_efficiency = 0.8 - else - if(metabolism_efficiency == 1.25) - to_chat(src, "You no longer feel vigorous.") - metabolism_efficiency = 1 - - if(drowsyness) - AdjustDrowsy(-1) - EyeBlurry(2) - if(prob(5)) - AdjustSleeping(1) - Paralyse(5) - - AdjustConfused(-1) - // decrement dizziness counter, clamped to 0 - if(resting) - AdjustDizzy(-15) - AdjustJitter(-15) - else - AdjustDizzy(-3) - AdjustJitter(-3) - - if(NO_INTORGANS in dna.species.species_traits) - return - - handle_trace_chems() - - return //TODO: DEFERRED - -/mob/living/carbon/human/handle_drunk() - var/slur_start = 30 //12u ethanol, 30u whiskey FOR HUMANS - var/confused_start = 40 - var/brawl_start = 30 - var/blur_start = 75 - var/vomit_start = 60 - var/pass_out = 90 - var/spark_start = 50 //40u synthanol - var/collapse_start = 75 - var/braindamage_start = 120 - var/alcohol_strength = drunk - var/sober_str=!(SOBER in mutations)?1:2 - - if(drunk) - alcohol_strength/=sober_str - - var/obj/item/organ/internal/liver/L - if(!isSynthetic()) - L = get_int_organ(/obj/item/organ/internal/liver) - if(L) - alcohol_strength *= L.alcohol_intensity - else - alcohol_strength *= 5 - - if(alcohol_strength >= slur_start) //slurring - Slur(drunk) - if(alcohol_strength >= brawl_start) //the drunken martial art - if(!istype(martial_art, /datum/martial_art/drunk_brawling)) - var/datum/martial_art/drunk_brawling/F = new - F.teach(src,1) - if(alcohol_strength < brawl_start) //removing the art - if(istype(martial_art, /datum/martial_art/drunk_brawling)) - martial_art.remove(src) - if(alcohol_strength >= confused_start && prob(33)) //confused walking - if(!confused) Confused(1) - AdjustConfused(3/sober_str) - if(alcohol_strength >= blur_start) //blurry eyes - EyeBlurry(10/sober_str) - if(!isSynthetic()) //stuff only for non-synthetics - if(alcohol_strength >= vomit_start) //vomiting - if(prob(8)) - fakevomit() - if(alcohol_strength >= pass_out) - Paralyse(5/sober_str) - Drowsy(30/sober_str) - if(L) - L.receive_damage(0.1, 1) - adjustToxLoss(0.1) - else //stuff only for synthetics - if(alcohol_strength >= spark_start && prob(25)) - do_sparks(3, 1, src) - if(alcohol_strength >= collapse_start && prob(10)) - emote("collapse") - do_sparks(3, 1, src) - if(alcohol_strength >= braindamage_start && prob(10)) - adjustBrainLoss(1) - - if(!has_booze()) - AdjustDrunk(-0.5) - return - -/mob/living/carbon/human/proc/has_booze() //checks if the human has ethanol or its subtypes inside - for(var/A in reagents.reagent_list) - var/datum/reagent/R = A - if(istype(R, /datum/reagent/consumable/ethanol)) - return 1 - return 0 - -/mob/living/carbon/human/handle_regular_status_updates() - if(status_flags & GODMODE) - return 0 - - . = ..() - - if(.) //alive - if(REGEN in mutations) - heal_overall_damage(0.1, 0.1) - - if(paralysis) - stat = UNCONSCIOUS - - else if(sleeping) - - stat = UNCONSCIOUS - - if(mind) - if(mind.vampire) - if(istype(loc, /obj/structure/closet/coffin)) - adjustBruteLoss(-1) - adjustFireLoss(-1) - adjustToxLoss(-1) - - else if(status_flags & FAKEDEATH) - stat = UNCONSCIOUS - - //Vision //god knows why this is here - var/obj/item/organ/vision - if(dna.species.vision_organ) - vision = get_int_organ(dna.species.vision_organ) - - if(!dna.species.vision_organ) // Presumably if a species has no vision organs, they see via some other means. - SetEyeBlind(0) - SetEyeBlurry(0) - - else if(!vision || vision.is_broken()) // Vision organs cut out or broken? Permablind. - EyeBlind(2) - EyeBlurry(2) - - else - //blindness - if(disabilities & BLIND) // Disabled-blind, doesn't get better on its own - - else if(eye_blind) // Blindness, heals slowly over time - AdjustEyeBlind(-1) - - else if(istype(glasses, /obj/item/clothing/glasses/sunglasses/blindfold)) //resting your eyes with a blindfold heals blurry eyes faster - AdjustEyeBlurry(-3) - - //blurry sight - if(vision.is_bruised()) // Vision organs impaired? Permablurry. - EyeBlurry(2) - - if(eye_blurry) // Blurry eyes heal slowly - AdjustEyeBlurry(-1) - - - if(flying) - animate(src, pixel_y = pixel_y + 5 , time = 10, loop = 1, easing = SINE_EASING) - animate(pixel_y = pixel_y - 5, time = 10, loop = 1, easing = SINE_EASING) - - // If you're dirty, your gloves will become dirty, too. - if(gloves && germ_level > gloves.germ_level && prob(10)) - gloves.germ_level += 1 - - handle_organs() - - if(getBrainLoss() >= 120 || (health + (getOxyLoss() / 2)) <= -500) - death() - return - - if(getBrainLoss() >= 100) // braindeath - AdjustLoseBreath(10, bound_lower = 0, bound_upper = 25) - Weaken(30) - - if(!check_death_method()) - if(health <= HEALTH_THRESHOLD_DEAD) - var/deathchance = min(99, ((getBrainLoss() * -5) + (health + (getOxyLoss() / 2))) * -0.01) - if(prob(deathchance)) - death() - return - - if(health <= HEALTH_THRESHOLD_CRIT) - if(prob(5)) - emote(pick("faint", "collapse", "cry", "moan", "gasp", "shudder", "shiver")) - AdjustStuttering(5, bound_lower = 0, bound_upper = 5) - EyeBlurry(5) - if(prob(7)) - AdjustConfused(2) - if(prob(5)) - Paralyse(2) - switch(health) - if(-INFINITY to -100) - adjustOxyLoss(1) - if(prob(health * -0.1)) - if(ishuman(src)) - var/mob/living/carbon/human/H = src - H.set_heartattack(TRUE) - if(prob(health * -0.2)) - var/datum/disease/D = new /datum/disease/critical/heart_failure - ForceContractDisease(D) - Paralyse(5) - if(-99 to -80) - adjustOxyLoss(1) - if(prob(4)) - to_chat(src, "Your chest hurts...") - Paralyse(2) - var/datum/disease/D = new /datum/disease/critical/heart_failure - ForceContractDisease(D) - if(-79 to -50) - adjustOxyLoss(1) - if(prob(10)) - var/datum/disease/D = new /datum/disease/critical/shock - ForceContractDisease(D) - if(prob(health * -0.08)) - var/datum/disease/D = new /datum/disease/critical/heart_failure - ForceContractDisease(D) - if(prob(6)) - to_chat(src, "You feel [pick("horrible pain", "awful", "like shit", "absolutely awful", "like death", "like you are dying", "nothing", "warm", "sweaty", "tingly", "really, really bad", "horrible")]!") - Weaken(3) - if(prob(3)) - Paralyse(2) - if(-49 to 0) - adjustOxyLoss(1) - if(prob(3)) - var/datum/disease/D = new /datum/disease/critical/shock - ForceContractDisease(D) - if(prob(5)) - to_chat(src, "You feel [pick("terrible", "awful", "like shit", "sick", "numb", "cold", "sweaty", "tingly", "horrible")]!") - Weaken(3) - - else //dead - SetSilence(0) - - -/mob/living/carbon/human/handle_vision() - if(machine) - if(!machine.check_eye(src)) - reset_perspective(null) - else - var/isRemoteObserve = 0 - if((REMOTE_VIEW in mutations) && remoteview_target) - isRemoteObserve = 1 - - if(remoteview_target.stat != CONSCIOUS) - to_chat(src, "Your psy-connection grows too faint to maintain!") - isRemoteObserve = 0 - - if(PSY_RESIST in remoteview_target.mutations) - to_chat(src, "Your mind is shut out!") - isRemoteObserve = 0 - - // Not on the station or mining? - var/turf/temp_turf = get_turf(remoteview_target) - if(!temp_turf in config.contact_levels) - to_chat(src, "Your psy-connection grows too faint to maintain!") - isRemoteObserve = 0 - - if(remote_view) - isRemoteObserve = 1 - - if(!isRemoteObserve && client && !client.adminobs) - remoteview_target = null - reset_perspective(null) - -/mob/living/carbon/human/handle_hud_icons() - dna.species.handle_hud_icons(src) - -/mob/living/carbon/human/handle_hud_icons_health() - dna.species.handle_hud_icons_health(src) - handle_hud_icons_health_overlay() - -/mob/living/carbon/human/handle_random_events() - // Puke if toxloss is too high - if(!stat) - if(getToxLoss() >= 45 && nutrition > 20) - lastpuke ++ - if(lastpuke >= 25) // about 25 second delay I guess - vomit(20, 0, 1, 0, 1) - adjustToxLoss(-3) - lastpuke = 0 - -/mob/living/carbon/human/proc/handle_embedded_objects() - for(var/X in bodyparts) - var/obj/item/organ/external/BP = X - for(var/obj/item/I in BP.embedded_objects) - if(prob(I.embedded_pain_chance)) - BP.receive_damage(I.w_class*I.embedded_pain_multiplier) - to_chat(src, "[I] embedded in your [BP.name] hurts!") - - if(prob(I.embedded_fall_chance)) - BP.receive_damage(I.w_class*I.embedded_fall_pain_multiplier) - BP.embedded_objects -= I - I.forceMove(get_turf(src)) - visible_message("[I] falls out of [name]'s [BP.name]!","[I] falls out of your [BP.name]!") - if(!has_embedded_objects()) - clear_alert("embeddedobject") - -/mob/living/carbon/human/handle_changeling() - if(mind) - if(mind.changeling) - mind.changeling.regenerate(src) - if(hud_used) - hud_used.lingchemdisplay.invisibility = 0 - hud_used.lingchemdisplay.maptext = "
        [round(mind.changeling.chem_charges)]
        " - else - if(hud_used) - hud_used.lingchemdisplay.invisibility = 101 - - -/mob/living/carbon/human/proc/handle_pulse(times_fired) - if(times_fired % 5 == 1) - return pulse //update pulse every 5 life ticks (~1 tick/sec, depending on server load) - - if(NO_BLOOD in dna.species.species_traits) - return PULSE_NONE //No blood, no pulse. - - if(stat == DEAD) - return PULSE_NONE //that's it, you're dead, nothing can influence your pulse - - if(undergoing_cardiac_arrest()) - return PULSE_NONE - - var/temp = PULSE_NORM - - if(blood_volume <= BLOOD_VOLUME_BAD)//how much blood do we have - temp = PULSE_THREADY //not enough :( - - if(status_flags & FAKEDEATH) - temp = PULSE_NONE //pretend that we're dead. unlike actual death, can be inflienced by meds - - for(var/datum/reagent/R in reagents.reagent_list) - if(R.heart_rate_decrease) - if(temp <= PULSE_THREADY && temp >= PULSE_NORM) - temp-- - break - - for(var/datum/reagent/R in reagents.reagent_list)//handles different chems' influence on pulse - if(R.heart_rate_increase) - if(temp <= PULSE_FAST && temp >= PULSE_NONE) - temp++ - break - - for(var/datum/reagent/R in reagents.reagent_list) //To avoid using fakedeath - if(R.heart_rate_stop) - temp = PULSE_NONE - break - - return temp - -/mob/living/carbon/human/proc/handle_decay() - var/decaytime = world.time - timeofdeath - - if(NO_DECAY in dna.species.species_traits) - return - - if(reagents.has_reagent("formaldehyde")) //embalming fluid stops decay - return - - if(decaytime <= 6000) //10 minutes for decaylevel1 -- stinky - return - - if(decaytime > 6000 && decaytime <= 12000)//20 minutes for decaylevel2 -- bloated and very stinky - decaylevel = 1 - - if(decaytime > 12000 && decaytime <= 18000)//30 minutes for decaylevel3 -- rotting and gross - decaylevel = 2 - - if(decaytime > 18000 && decaytime <= 27000)//45 minutes for decaylevel4 -- skeleton - decaylevel = 3 - - if(decaytime > 27000) - decaylevel = 4 - makeSkeleton() - return //No puking over skeletons, they don't smell at all! - - if(!isturf(loc)) - return - - for(var/mob/living/carbon/human/H in range(decaylevel, src)) - if(prob(2)) - var/obj/item/clothing/mask/M = H.wear_mask - if(M && (M.flags_cover & MASKCOVERSMOUTH)) - continue - if(NO_BREATHE in H.dna.species.species_traits) - continue //no puking if you can't smell! - // Humans can lack a mind datum, y'know - if(H.mind && (H.mind.assigned_role == "Detective" || H.mind.assigned_role == "Coroner")) - continue //too cool for puke - to_chat(H, "You smell something foul...") - H.fakevomit() - -/mob/living/carbon/human/proc/handle_heartbeat() - var/client/C = src.client - if(C && C.prefs.sound & SOUND_HEARTBEAT) //disable heartbeat by pref - var/obj/item/organ/internal/heart/H = get_int_organ(/obj/item/organ/internal/heart) - - if(!H) //H.status will runtime if there is no H (obviously) - return - - if(H.is_robotic()) //Handle robotic hearts specially with a wuuuubb. This also applies to machine-people. - if(isinspace()) - //PULSE_THREADY - maximum value for pulse, currently it 5. - //High pulse value corresponds to a fast rate of heartbeat. - //Divided by 2, otherwise it is too slow. - var/rate = (PULSE_THREADY - 2)/2 //machine people (main target) have no pulse, manually subtract standard human pulse (2). Mechanic-heart humans probably have a pulse, but 'advanced neural systems' keep the heart rate steady, or something - - if(heartbeat >= rate) - heartbeat = 0 - src << sound('sound/effects/electheart.ogg',0,0,CHANNEL_HEARTBEAT,30)//Credit to GhostHack (www.ghosthack.de) for sound. - - else - heartbeat++ - return - return - - if(pulse == PULSE_NONE) - return - - if(pulse >= PULSE_2FAST || isinspace()) - //PULSE_THREADY - maximum value for pulse, currently it 5. - //High pulse value corresponds to a fast rate of heartbeat. - //Divided by 2, otherwise it is too slow. - var/rate = (PULSE_THREADY - pulse)/2 - - if(heartbeat >= rate) - heartbeat = 0 - src << sound('sound/effects/singlebeat.ogg',0,0,CHANNEL_HEARTBEAT,50) - else - heartbeat++ - -/* - Called by life(), instead of having the individual hud items update icons each tick and check for status changes - we only set those statuses and icons upon changes. Then those HUD items will simply add those pre-made images. - This proc below is only called when those HUD elements need to change as determined by the mobs hud_updateflag. -*/ - -/mob/living/carbon/human/proc/can_heartattack() - if((NO_BLOOD in dna.species.species_traits) && !dna.species.forced_heartattack) - return FALSE - if(NO_INTORGANS in dna.species.species_traits) - return FALSE - return TRUE - -/mob/living/carbon/human/proc/undergoing_cardiac_arrest() - if(!can_heartattack()) - return FALSE - var/obj/item/organ/internal/heart/heart = get_int_organ(/obj/item/organ/internal/heart) - if(istype(heart)) - if(heart.status & ORGAN_DEAD) - return TRUE - if(heart.beating) - return FALSE - return TRUE - -/mob/living/carbon/human/proc/set_heartattack(status) - if(!can_heartattack()) - return FALSE - - var/obj/item/organ/internal/heart/heart = get_int_organ(/obj/item/organ/internal/heart) - if(!istype(heart)) - return FALSE - - heart.beating = !status - -/mob/living/carbon/human/handle_heartattack() - if(!can_heartattack() || !undergoing_cardiac_arrest() || reagents.has_reagent("corazone")) - return - if(getOxyLoss()) - adjustBrainLoss(3) - else if(prob(10)) - adjustBrainLoss(1) - Weaken(5) - AdjustLoseBreath(20, bound_lower = 0, bound_upper = 25) - adjustOxyLoss(20) - - - -// Need this in species. -//#undef HUMAN_MAX_OXYLOSS -//#undef HUMAN_CRIT_MAX_OXYLOSS +/mob/living/carbon/human/Life(seconds, times_fired) + life_tick++ + + voice = GetVoice() + + if(..()) + + if(check_mutations) + domutcheck(src,null) + update_mutations() + check_mutations=0 + + handle_pain() + handle_heartbeat() + handle_drunk() + dna.species.handle_life(src) + if(!client) + dna.species.handle_npc(src) + + if(stat != DEAD) + //Stuff jammed in your limbs hurts + handle_embedded_objects() + + if(stat == DEAD) + handle_decay() + + if(life_tick > 5 && timeofdeath && (timeofdeath < 5 || world.time - timeofdeath > 6000)) //We are long dead, or we're junk mobs spawned like the clowns on the clown shuttle + return //We go ahead and process them 5 times for HUD images and other stuff though. + + //Update our name based on whether our face is obscured/disfigured + name = get_visible_name() + pulse = handle_pulse(times_fired) + + if(mind && mind.vampire) + mind.vampire.handle_vampire() + if(life_tick == 1) + regenerate_icons() // Make sure the inventory updates + + handle_ghosted() + handle_ssd() + +/mob/living/carbon/human/proc/handle_ghosted() + if(player_ghosted > 0 && stat == CONSCIOUS && job && !restrained()) + if(key) + player_ghosted = 0 + else + player_ghosted++ + if(player_ghosted % 150 == 0) + force_cryo_human(src) + +/mob/living/carbon/human/proc/handle_ssd() + if(player_logged > 0 && stat != DEAD && job) + player_logged++ + if(istype(loc, /obj/machinery/cryopod)) + return + if(config.auto_cryo_ssd_mins && (player_logged >= (config.auto_cryo_ssd_mins * 30)) && player_logged % 30 == 0) + var/turf/T = get_turf(src) + if(!is_station_level(T.z)) + return + var/area/A = get_area(src) + if(cryo_ssd(src)) + var/obj/effect/portal/P = new /obj/effect/portal(T, null, null, 40) + P.name = "NT SSD Teleportation Portal" + if(A.fast_despawn) + force_cryo_human(src) + +/mob/living/carbon/human/calculate_affecting_pressure(var/pressure) + ..() + var/pressure_difference = abs( pressure - ONE_ATMOSPHERE ) + + var/pressure_adjustment_coefficient = 1 //Determins how much the clothing you are wearing protects you in percent. + if(wear_suit && (wear_suit.flags & STOPSPRESSUREDMAGE) && head && (head.flags & STOPSPRESSUREDMAGE)) // Complete set of pressure-proof suit worn, assume fully sealed. + pressure_adjustment_coefficient = 0 + pressure_adjustment_coefficient = max(pressure_adjustment_coefficient,0) //So it isn't less than 0 + pressure_difference = pressure_difference * pressure_adjustment_coefficient + if(pressure > ONE_ATMOSPHERE) + return ONE_ATMOSPHERE + pressure_difference + else + return ONE_ATMOSPHERE - pressure_difference + + +/mob/living/carbon/human/handle_disabilities() + if(disabilities & EPILEPSY) + if((prob(1) && paralysis < 1)) + visible_message("[src] starts having a seizure!","You have a seizure!") + Paralyse(10) + Jitter(1000) + + // If we have the gene for being crazy, have random events. + if(dna.GetSEState(HALLUCINATIONBLOCK)) + if(prob(1)) + Hallucinate(20) + + if(disabilities & COUGHING) + if((prob(5) && paralysis <= 1)) + drop_item() + emote("cough") + if(disabilities & TOURETTES) + if((prob(10) && paralysis <= 1)) + Stun(10) + switch(rand(1, 3)) + if(1) + emote("twitch") + if(2 to 3) + var/tourettes = pick("SHIT", "PISS", "FUCK", "CUNT", "COCKSUCKER", "MOTHERFUCKER", "TITS") + say("[prob(50) ? ";" : ""][tourettes]") + var/x_offset = pixel_x + rand(-2,2) //Should probably be moved into the twitch emote at some point. + var/y_offset = pixel_y + rand(-1,1) + animate(src, pixel_x = pixel_x + x_offset, pixel_y = pixel_y + y_offset, time = 1) + animate(pixel_x = initial(pixel_x) , pixel_y = initial(pixel_y), time = 1) + + if(disabilities & NERVOUS) + if(prob(10)) + Stuttering(10) + + if(getBrainLoss() >= 60 && stat != DEAD) + if(prob(3)) + var/list/s1 = list("IM A [pick("PONY","LIZARD","taJaran","kitty","Vulpakin","drASK","BIRDIE","voxxie","race car","combat meCH","SPESSSHIP")] [pick("NEEEEEEIIIIIIIIIGH","sKREEEEEE","MEOW","NYA~","rawr","Barkbark","Hissssss","vROOOOOM","pewpew","choo Choo")]!", + "without oxigen blob don't evoluate?", + "CAPTAINS A COMDOM", + "[pick("", "that damn traitor")] [pick("joerge", "george", "gorge", "gdoruge")] [pick("mellens", "melons", "mwrlins")] is grifing me HAL;P!!!", + "can u give me [pick("telikesis","halk","eppilapse")]?", + "THe saiyans screwed", + "Bi is THE BEST OF BOTH WORLDS", + "I WANNA PET TEH monkeyS", + "stop grifing me!!!!", + "SOTP IT!", + "HALPZ SITCULITY", + "VOXES caN't LOVE", + "my dad own this station", + "the CHef put [pick("PROTEIN", "toiret waTer", "RiPPleing TendIes", "Einzymes","HORRY WALTER","nuTriments","ReActive MutAngen","TeSLium","sKrektonium")] in my [pick("wiSh soup","Bullito","rAingurber","sOilent GREEn","KoI Susishes","yaya")]!", + "the monkey have TASER ARMS!", + "qM blew my points on [pick("cOMbat Shtogun","inSuLated gloves","LOTS MASSHEEN!")]", + "EI'NATH!", + "WAKE UP SHEEPLES!", + "et wus my [pick("wittle brother!!","fiancee","friend staying over","entiRe orphanage","love interest","wife","husband","liTTle kids","sentient cAT","accidentally")]!") + + var/list/s2 = list("FUS RO DAH", + "fuckin tangerines!!!", + "stat me", + ">my face", + "roll it easy!", + "waaaaaagh!!!", + "red wonz go fasta", + "FOR TEH EMPRAH", + "HAZ A SECURE DAY!!!!", + "dem dwarfs man, dem dwarfs", + "SPESS MAHREENS", + "hwee did eet fhor khayosss", + "lifelike texture", + "luv can bloooom", + "PACKETS!!!", + "[pick("WHERE MY","aYE need","giv me my","bath me inn.")] [pick("dermaline","alKkyZine","dylOvene","inAprovaline","biCaridine","Hyperzine","kELotane","lePorazine","bAcch Salts","tricord","clOnexazone","hydroChloric Acid","chlorine Hydrate","paRoxetine")]!", + "mALPRACTICEBAY", + "I HavE A pe H dee iN ENTerpriSE resOUrCE pLaNNIN", + "h-h-HalP MaINT", + "dey come, dey COME! DEY COME!!!", + "THE END IS NIGH!", + "I FOT AND DIED FOR MUH [pick("RITES","FREEDOM","payCHECK","cARGO points","teCH Level","doG","mAPLe syrup","fluffy fWiends","gateway Loot")]", + "KILL DEM [pick("mainTnacE cHickinNS","kiRA CulwnNES","FLOOR CLUWNEs","MIME ASSASSIN","BOMBING TAJARAN","cC offiser","morPhlings","slinglings")]!") + switch(pick(1,2,3)) + if(1) + say(pick(s1)) + if(2) + say(pick(s2)) + if(3) + emote("drool") + +/mob/living/carbon/human/handle_mutations_and_radiation() + for(var/datum/dna/gene/gene in dna_genes) + if(!gene.block) + continue + if(gene.is_active(src)) + gene.OnMobLife(src) + if(!ignore_gene_stability && gene_stability < GENETIC_DAMAGE_STAGE_1) + var/instability = DEFAULT_GENE_STABILITY - gene_stability + if(prob(instability * 0.1)) + adjustFireLoss(min(5, instability * 0.67)) + to_chat(src, "You feel like your skin is burning and bubbling off!") + if(gene_stability < GENETIC_DAMAGE_STAGE_2) + if(prob(instability * 0.83)) + adjustCloneLoss(min(4, instability * 0.05)) + to_chat(src, "You feel as if your body is warping.") + if(prob(instability * 0.1)) + adjustToxLoss(min(5, instability * 0.67)) + to_chat(src, "You feel weak and nauseous.") + if(gene_stability < GENETIC_DAMAGE_STAGE_3 && prob(1)) + to_chat(src, "You feel incredibly sick... Something isn't right!") + spawn(300) + if(gene_stability < GENETIC_DAMAGE_STAGE_3) + gib() + + if(!(RADIMMUNE in dna.species.species_traits)) + if(radiation) + radiation = Clamp(radiation, 0, 200) + + var/autopsy_damage = 0 + switch(radiation) + if(1 to 49) + radiation = max(radiation-1, 0) + if(prob(25)) + adjustToxLoss(1) + adjustFireLoss(1) + autopsy_damage = 2 + + if(50 to 74) + radiation = max(radiation-2, 0) + adjustToxLoss(1) + adjustFireLoss(1) + autopsy_damage = 2 + if(prob(5)) + radiation = max(radiation-5, 0) + Weaken(3) + to_chat(src, "You feel weak.") + emote("collapse") + + if(75 to 100) + radiation = max(radiation-2, 0) + adjustToxLoss(2) + adjustFireLoss(2) + autopsy_damage = 4 + if(prob(2)) + to_chat(src, "You mutate!") + randmutb(src) + domutcheck(src, null) + + if(101 to 150) + radiation = max(radiation-3, 0) + adjustToxLoss(2) + adjustFireLoss(3) + autopsy_damage = 5 + if(prob(4)) + to_chat(src, "You mutate!") + randmutb(src) + domutcheck(src, null) + + if(151 to INFINITY) + radiation = max(radiation-3, 0) + adjustToxLoss(2) + adjustFireLoss(3) + autopsy_damage = 5 + if(prob(6)) + to_chat(src, "You mutate!") + randmutb(src) + domutcheck(src, null) + + if(autopsy_damage) + var/obj/item/organ/external/chest/chest = get_organ("chest") + if(chest) + chest.add_autopsy_data("Radiation Poisoning", autopsy_damage) + +/mob/living/carbon/human/breathe() + if(!dna.species.breathe(src)) + ..() + +/mob/living/carbon/human/check_breath(datum/gas_mixture/breath) + + var/obj/item/organ/internal/L = get_organ_slot("lungs") + + if(!L || L && (L.status & ORGAN_DEAD)) + if(health >= HEALTH_THRESHOLD_CRIT) + adjustOxyLoss(HUMAN_MAX_OXYLOSS + 1) + else if(!(NOCRITDAMAGE in dna.species.species_traits)) + adjustOxyLoss(HUMAN_MAX_OXYLOSS) + + if(dna.species) + var/datum/species/S = dna.species + + if(S.breathid == "o2") + throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) + else if(S.breathid == "tox") + throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox) + else if(S.breathid == "co2") + throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2) + else if(S.breathid == "n2") + throw_alert("not_enough_nitro", /obj/screen/alert/not_enough_nitro) + + return FALSE + else + if(istype(L, /obj/item/organ/internal/lungs)) + var/obj/item/organ/internal/lungs/lun = L + lun.check_breath(breath, src) + +// USED IN DEATHWHISPERS +/mob/living/carbon/human/proc/isInCrit() + // Health is in deep shit and we're not already dead + return health <= HEALTH_THRESHOLD_CRIT && stat != DEAD + + +/mob/living/carbon/human/get_breath_from_internal(volume_needed) //making this call the parent would be far too complicated + if(internal) + var/null_internals = 0 //internals are invalid, therefore turn them off + var/skip_contents_check = 0 //rigsuit snowflake, oxygen tanks aren't stored inside the mob, so the 'contents.Find' check has to be skipped. + + if(!get_organ_slot("breathing_tube")) + if(!(wear_mask && wear_mask.flags & AIRTIGHT)) //if NOT (wear_mask AND wear_mask.flags CONTAIN AIRTIGHT) + if(!(head && head.flags & AIRTIGHT)) //if NOT (head AND head.flags CONTAIN AIRTIGHT) + null_internals = 1 //not wearing a mask or suitable helmet + + if(istype(back, /obj/item/rig)) //wearing a rigsuit + var/obj/item/rig/rig = back //needs to be typecasted because this doesn't use get_rig() for some reason + if(rig.offline && (rig.air_supply && internal == rig.air_supply)) //if rig IS offline AND (rig HAS air_supply AND internal IS air_supply) + null_internals = 1 //offline suits do not breath + + else if(rig.air_supply && internal == rig.air_supply) //if rig HAS air_supply AND internal IS rig air_supply + skip_contents_check = 1 //skip contents.Find() check, the oxygen is valid even being outside of the mob + + if(!contents.Find(internal) && (!skip_contents_check)) //if internal NOT IN contents AND skip_contents_check IS false + null_internals = 1 //not a rigsuit and your oxygen is gone + + if(null_internals) //something wants internals gone + internal = null //so do it + update_action_buttons_icon() + + if(internal) //check for hud updates every time this is called + return internal.remove_air_volume(volume_needed) //returns the valid air + + return null + +/mob/living/carbon/human/handle_environment(datum/gas_mixture/environment) + if(!environment) + return + + var/loc_temp = get_temperature(environment) +// to_chat(world, "Loc temp: [loc_temp] - Body temp: [bodytemperature] - Fireloss: [getFireLoss()] - Thermal protection: [get_thermal_protection()] - Fire protection: [thermal_protection + add_fire_protection(loc_temp)] - Heat capacity: [environment_heat_capacity] - Location: [loc] - src: [src]") + + //Body temperature is adjusted in two steps. Firstly your body tries to stabilize itself a bit. + if(stat != DEAD) + stabilize_temperature_from_calories() + + //After then, it reacts to the surrounding atmosphere based on your thermal protection + if(!on_fire) //If you're on fire, you do not heat up or cool down based on surrounding gases + if(loc_temp < bodytemperature) + //Place is colder than we are + var/thermal_protection = get_cold_protection(loc_temp) //This returns a 0 - 1 value, which corresponds to the percentage of protection based on what you're wearing and what you're exposed to. + if(thermal_protection < 1) + bodytemperature += min((1-thermal_protection) * ((loc_temp - bodytemperature) / BODYTEMP_COLD_DIVISOR), BODYTEMP_COOLING_MAX) + else + //Place is hotter than we are + var/thermal_protection = get_heat_protection(loc_temp) //This returns a 0 - 1 value, which corresponds to the percentage of protection based on what you're wearing and what you're exposed to. + if(thermal_protection < 1) + bodytemperature += min((1-thermal_protection) * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR), BODYTEMP_HEATING_MAX) + + // +/- 50 degrees from 310.15K is the 'safe' zone, where no damage is dealt. + if(bodytemperature > dna.species.heat_level_1) + //Body temperature is too hot. + if(status_flags & GODMODE) return 1 //godmode + var/mult = dna.species.heatmod + + if(bodytemperature >= dna.species.heat_level_1 && bodytemperature <= dna.species.heat_level_2) + throw_alert("temp", /obj/screen/alert/hot, 1) + take_overall_damage(burn=mult*HEAT_DAMAGE_LEVEL_1, updating_health = TRUE, used_weapon = "High Body Temperature") + if(bodytemperature > dna.species.heat_level_2 && bodytemperature <= dna.species.heat_level_3) + throw_alert("temp", /obj/screen/alert/hot, 2) + take_overall_damage(burn=mult*HEAT_DAMAGE_LEVEL_2, updating_health = TRUE, used_weapon = "High Body Temperature") + if(bodytemperature > dna.species.heat_level_3 && bodytemperature < INFINITY) + throw_alert("temp", /obj/screen/alert/hot, 3) + if(on_fire) + take_overall_damage(burn=mult*HEAT_DAMAGE_LEVEL_3, updating_health = TRUE, used_weapon = "Fire") + else + take_overall_damage(burn=mult*HEAT_DAMAGE_LEVEL_2, updating_health = TRUE, used_weapon = "High Body Temperature") + + else if(bodytemperature < dna.species.cold_level_1) + if(status_flags & GODMODE) + return 1 + if(stat == DEAD) + return 1 + + if(!istype(loc, /obj/machinery/atmospherics/unary/cryo_cell)) + var/mult = dna.species.coldmod + if(bodytemperature >= dna.species.cold_level_2 && bodytemperature <= dna.species.cold_level_1) + throw_alert("temp", /obj/screen/alert/cold, 1) + take_overall_damage(burn=mult*COLD_DAMAGE_LEVEL_1, updating_health = TRUE, used_weapon = "Low Body Temperature") + if(bodytemperature >= dna.species.cold_level_3 && bodytemperature < dna.species.cold_level_2) + throw_alert("temp", /obj/screen/alert/cold, 2) + take_overall_damage(burn=mult*COLD_DAMAGE_LEVEL_2, updating_health = TRUE, used_weapon = "Low Body Temperature") + if(bodytemperature > -INFINITY && bodytemperature < dna.species.cold_level_3) + throw_alert("temp", /obj/screen/alert/cold, 3) + take_overall_damage(burn=mult*COLD_DAMAGE_LEVEL_3, updating_health = TRUE, used_weapon = "Low Body Temperature") + else + clear_alert("temp") + else + clear_alert("temp") + + // Account for massive pressure differences. Done by Polymorph + // Made it possible to actually have something that can protect against high pressure... Done by Errorage. Polymorph now has an axe sticking from his head for his previous hardcoded nonsense! + + var/pressure = environment.return_pressure() + var/adjusted_pressure = calculate_affecting_pressure(pressure) //Returns how much pressure actually affects the mob. + if(status_flags & GODMODE) return 1 //godmode + + if(adjusted_pressure >= dna.species.hazard_high_pressure) + if(!(HEATRES in mutations)) + var/pressure_damage = min( ( (adjusted_pressure / dna.species.hazard_high_pressure) -1 )*PRESSURE_DAMAGE_COEFFICIENT , MAX_HIGH_PRESSURE_DAMAGE) + take_overall_damage(brute=pressure_damage, updating_health = TRUE, used_weapon = "High Pressure") + throw_alert("pressure", /obj/screen/alert/highpressure, 2) + else + clear_alert("pressure") + else if(adjusted_pressure >= dna.species.warning_high_pressure) + throw_alert("pressure", /obj/screen/alert/highpressure, 1) + else if(adjusted_pressure >= dna.species.warning_low_pressure) + clear_alert("pressure") + else if(adjusted_pressure >= dna.species.hazard_low_pressure) + throw_alert("pressure", /obj/screen/alert/lowpressure, 1) + else + if(COLDRES in mutations) + clear_alert("pressure") + else + take_overall_damage(brute=LOW_PRESSURE_DAMAGE, updating_health = TRUE, used_weapon = "Low Pressure") + throw_alert("pressure", /obj/screen/alert/lowpressure, 2) + + +///FIRE CODE +/mob/living/carbon/human/handle_fire() + if(..()) + return + if(HEATRES in mutations) + return + if(on_fire) + var/thermal_protection = get_thermal_protection() + + if(thermal_protection >= FIRE_IMMUNITY_MAX_TEMP_PROTECT) + return + if(thermal_protection >= FIRE_SUIT_MAX_TEMP_PROTECT) + bodytemperature += 11 + else + bodytemperature += (BODYTEMP_HEATING_MAX + (fire_stacks * 12)) + +/mob/living/carbon/human/proc/get_thermal_protection() + var/thermal_protection = 0 //Simple check to estimate how protected we are against multiple temperatures + if(wear_suit) + if(wear_suit.max_heat_protection_temperature >= FIRE_SUIT_MAX_TEMP_PROTECT) + thermal_protection += (wear_suit.max_heat_protection_temperature*0.7) + if(head) + if(head.max_heat_protection_temperature >= FIRE_HELM_MAX_TEMP_PROTECT) + thermal_protection += (head.max_heat_protection_temperature*THERMAL_PROTECTION_HEAD) + thermal_protection = round(thermal_protection) + return thermal_protection + +//END FIRE CODE + +/mob/living/carbon/human/proc/stabilize_temperature_from_calories() + var/body_temperature_difference = dna.species.body_temperature - bodytemperature + + if(bodytemperature <= dna.species.cold_level_1) //260.15 is 310.15 - 50, the temperature where you start to feel effects. + bodytemperature += max((body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR), BODYTEMP_AUTORECOVERY_MINIMUM) + if(bodytemperature >= dna.species.cold_level_1 && bodytemperature <= dna.species.heat_level_1) + bodytemperature += body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR + if(bodytemperature >= dna.species.heat_level_1) //360.15 is 310.15 + 50, the temperature where you start to feel effects. + //We totally need a sweat system cause it totally makes sense...~ + bodytemperature += min((body_temperature_difference / BODYTEMP_AUTORECOVERY_DIVISOR), -BODYTEMP_AUTORECOVERY_MINIMUM) //We're dealing with negative numbers + + + //This proc returns a number made up of the flags for body parts which you are protected on. (such as HEAD, UPPER_TORSO, LOWER_TORSO, etc. See setup.dm for the full list) +/mob/living/carbon/human/proc/get_heat_protection_flags(temperature) //Temperature is the temperature you're being exposed to. + var/thermal_protection_flags = 0 + //Handle normal clothing + if(head) + if(head.max_heat_protection_temperature && head.max_heat_protection_temperature >= temperature) + thermal_protection_flags |= head.heat_protection + if(wear_suit) + if(wear_suit.max_heat_protection_temperature && wear_suit.max_heat_protection_temperature >= temperature) + thermal_protection_flags |= wear_suit.heat_protection + if(w_uniform) + if(w_uniform.max_heat_protection_temperature && w_uniform.max_heat_protection_temperature >= temperature) + thermal_protection_flags |= w_uniform.heat_protection + if(shoes) + if(shoes.max_heat_protection_temperature && shoes.max_heat_protection_temperature >= temperature) + thermal_protection_flags |= shoes.heat_protection + if(gloves) + if(gloves.max_heat_protection_temperature && gloves.max_heat_protection_temperature >= temperature) + thermal_protection_flags |= gloves.heat_protection + if(wear_mask) + if(wear_mask.max_heat_protection_temperature && wear_mask.max_heat_protection_temperature >= temperature) + thermal_protection_flags |= wear_mask.heat_protection + + return thermal_protection_flags + +/mob/living/carbon/human/proc/get_heat_protection(temperature) //Temperature is the temperature you're being exposed to. + + if(HEATRES in mutations) + return 1 + + var/thermal_protection_flags = get_heat_protection_flags(temperature) + + var/thermal_protection = 0.0 + if(thermal_protection_flags) + if(thermal_protection_flags & HEAD) + thermal_protection += THERMAL_PROTECTION_HEAD + if(thermal_protection_flags & UPPER_TORSO) + thermal_protection += THERMAL_PROTECTION_UPPER_TORSO + if(thermal_protection_flags & LOWER_TORSO) + thermal_protection += THERMAL_PROTECTION_LOWER_TORSO + if(thermal_protection_flags & LEG_LEFT) + thermal_protection += THERMAL_PROTECTION_LEG_LEFT + if(thermal_protection_flags & LEG_RIGHT) + thermal_protection += THERMAL_PROTECTION_LEG_RIGHT + if(thermal_protection_flags & FOOT_LEFT) + thermal_protection += THERMAL_PROTECTION_FOOT_LEFT + if(thermal_protection_flags & FOOT_RIGHT) + thermal_protection += THERMAL_PROTECTION_FOOT_RIGHT + if(thermal_protection_flags & ARM_LEFT) + thermal_protection += THERMAL_PROTECTION_ARM_LEFT + if(thermal_protection_flags & ARM_RIGHT) + thermal_protection += THERMAL_PROTECTION_ARM_RIGHT + if(thermal_protection_flags & HAND_LEFT) + thermal_protection += THERMAL_PROTECTION_HAND_LEFT + if(thermal_protection_flags & HAND_RIGHT) + thermal_protection += THERMAL_PROTECTION_HAND_RIGHT + + + return min(1,thermal_protection) + + //See proc/get_heat_protection_flags(temperature) for the description of this proc. +/mob/living/carbon/human/proc/get_cold_protection_flags(temperature) + var/thermal_protection_flags = 0 + //Handle normal clothing + + if(head) + if(head.min_cold_protection_temperature && head.min_cold_protection_temperature <= temperature) + thermal_protection_flags |= head.cold_protection + if(wear_suit) + if(wear_suit.min_cold_protection_temperature && wear_suit.min_cold_protection_temperature <= temperature) + thermal_protection_flags |= wear_suit.cold_protection + if(w_uniform) + if(w_uniform.min_cold_protection_temperature && w_uniform.min_cold_protection_temperature <= temperature) + thermal_protection_flags |= w_uniform.cold_protection + if(shoes) + if(shoes.min_cold_protection_temperature && shoes.min_cold_protection_temperature <= temperature) + thermal_protection_flags |= shoes.cold_protection + if(gloves) + if(gloves.min_cold_protection_temperature && gloves.min_cold_protection_temperature <= temperature) + thermal_protection_flags |= gloves.cold_protection + if(wear_mask) + if(wear_mask.min_cold_protection_temperature && wear_mask.min_cold_protection_temperature <= temperature) + thermal_protection_flags |= wear_mask.cold_protection + + return thermal_protection_flags + +/mob/living/carbon/human/proc/get_cold_protection(temperature) + + if(COLDRES in mutations) + return 1 //Fully protected from the cold. + + temperature = max(temperature, TCMB) //There is an occasional bug where the temperature is miscalculated in areas with a small amount of gas on them, so this is necessary to ensure that that bug does not affect this calculation. Space's temperature is 2.7K and most suits that are intended to protect against any cold, protect down to 2.0K. + var/thermal_protection_flags = get_cold_protection_flags(temperature) + + var/thermal_protection = 0.0 + if(thermal_protection_flags) + if(thermal_protection_flags & HEAD) + thermal_protection += THERMAL_PROTECTION_HEAD + if(thermal_protection_flags & UPPER_TORSO) + thermal_protection += THERMAL_PROTECTION_UPPER_TORSO + if(thermal_protection_flags & LOWER_TORSO) + thermal_protection += THERMAL_PROTECTION_LOWER_TORSO + if(thermal_protection_flags & LEG_LEFT) + thermal_protection += THERMAL_PROTECTION_LEG_LEFT + if(thermal_protection_flags & LEG_RIGHT) + thermal_protection += THERMAL_PROTECTION_LEG_RIGHT + if(thermal_protection_flags & FOOT_LEFT) + thermal_protection += THERMAL_PROTECTION_FOOT_LEFT + if(thermal_protection_flags & FOOT_RIGHT) + thermal_protection += THERMAL_PROTECTION_FOOT_RIGHT + if(thermal_protection_flags & ARM_LEFT) + thermal_protection += THERMAL_PROTECTION_ARM_LEFT + if(thermal_protection_flags & ARM_RIGHT) + thermal_protection += THERMAL_PROTECTION_ARM_RIGHT + if(thermal_protection_flags & HAND_LEFT) + thermal_protection += THERMAL_PROTECTION_HAND_LEFT + if(thermal_protection_flags & HAND_RIGHT) + thermal_protection += THERMAL_PROTECTION_HAND_RIGHT + + return min(1,thermal_protection) + + +/mob/living/carbon/human/proc/get_covered_bodyparts() + var/covered = 0 + + if(head) + covered |= head.body_parts_covered + if(wear_suit) + covered |= wear_suit.body_parts_covered + if(w_uniform) + covered |= w_uniform.body_parts_covered + if(shoes) + covered |= shoes.body_parts_covered + if(gloves) + covered |= gloves.body_parts_covered + if(wear_mask) + covered |= wear_mask.body_parts_covered + + return covered + +/mob/living/carbon/human/handle_chemicals_in_body() + ..() + + if(status_flags & GODMODE) + return 0 //godmode + + if(!(NO_HUNGER in dna.species.species_traits)) + //The fucking FAT mutation is the greatest shit ever. It makes everyone so hot and bothered. + if(CAN_BE_FAT in dna.species.species_traits) + if(FAT in mutations) + if(overeatduration < 100) + becomeSlim() + else + if(overeatduration > 500) + becomeFat() + + // nutrition decrease + if(nutrition > 0 && stat != DEAD) + // THEY HUNGER + var/hunger_rate = hunger_drain + if(satiety > 0) + satiety-- + if(satiety < 0) + satiety++ + if(prob(round(-satiety/40))) + Jitter(5) + hunger_rate = 3 * hunger_drain + adjust_nutrition(-hunger_rate) + + if(nutrition > NUTRITION_LEVEL_FULL) + if(overeatduration < 600) //capped so people don't take forever to unfat + overeatduration++ + + else + if(overeatduration > 1) + if(OBESITY in mutations) + overeatduration -= 1 // Those with obesity gene take twice as long to unfat + else + overeatduration -= 2 + + //metabolism change + if(nutrition > NUTRITION_LEVEL_FAT) + metabolism_efficiency = 1 + else if(nutrition > NUTRITION_LEVEL_FED && satiety > 80) + if(metabolism_efficiency != 1.25) + to_chat(src, "You feel vigorous.") + metabolism_efficiency = 1.25 + else if(nutrition < NUTRITION_LEVEL_STARVING + 50) + if(metabolism_efficiency != 0.8) + to_chat(src, "You feel sluggish.") + metabolism_efficiency = 0.8 + else + if(metabolism_efficiency == 1.25) + to_chat(src, "You no longer feel vigorous.") + metabolism_efficiency = 1 + + if(drowsyness) + AdjustDrowsy(-1) + EyeBlurry(2) + if(prob(5)) + AdjustSleeping(1) + Paralyse(5) + + AdjustConfused(-1) + // decrement dizziness counter, clamped to 0 + if(resting) + AdjustDizzy(-15) + AdjustJitter(-15) + else + AdjustDizzy(-3) + AdjustJitter(-3) + + if(NO_INTORGANS in dna.species.species_traits) + return + + handle_trace_chems() + + return //TODO: DEFERRED + +/mob/living/carbon/human/handle_drunk() + var/slur_start = 30 //12u ethanol, 30u whiskey FOR HUMANS + var/confused_start = 40 + var/brawl_start = 30 + var/blur_start = 75 + var/vomit_start = 60 + var/pass_out = 90 + var/spark_start = 50 //40u synthanol + var/collapse_start = 75 + var/braindamage_start = 120 + var/alcohol_strength = drunk + var/sober_str=!(SOBER in mutations)?1:2 + + if(drunk) + alcohol_strength/=sober_str + + var/obj/item/organ/internal/liver/L + if(!isSynthetic()) + L = get_int_organ(/obj/item/organ/internal/liver) + if(L) + alcohol_strength *= L.alcohol_intensity + else + alcohol_strength *= 5 + + if(alcohol_strength >= slur_start) //slurring + Slur(drunk) + if(alcohol_strength >= brawl_start) //the drunken martial art + if(!istype(martial_art, /datum/martial_art/drunk_brawling)) + var/datum/martial_art/drunk_brawling/F = new + F.teach(src,1) + if(alcohol_strength < brawl_start) //removing the art + if(istype(martial_art, /datum/martial_art/drunk_brawling)) + martial_art.remove(src) + if(alcohol_strength >= confused_start && prob(33)) //confused walking + if(!confused) Confused(1) + AdjustConfused(3/sober_str) + if(alcohol_strength >= blur_start) //blurry eyes + EyeBlurry(10/sober_str) + if(!isSynthetic()) //stuff only for non-synthetics + if(alcohol_strength >= vomit_start) //vomiting + if(prob(8)) + fakevomit() + if(alcohol_strength >= pass_out) + Paralyse(5/sober_str) + Drowsy(30/sober_str) + if(L) + L.receive_damage(0.1, 1) + adjustToxLoss(0.1) + else //stuff only for synthetics + if(alcohol_strength >= spark_start && prob(25)) + do_sparks(3, 1, src) + if(alcohol_strength >= collapse_start && prob(10)) + emote("collapse") + do_sparks(3, 1, src) + if(alcohol_strength >= braindamage_start && prob(10)) + adjustBrainLoss(1) + + if(!has_booze()) + AdjustDrunk(-0.5) + return + +/mob/living/carbon/human/proc/has_booze() //checks if the human has ethanol or its subtypes inside + for(var/A in reagents.reagent_list) + var/datum/reagent/R = A + if(istype(R, /datum/reagent/consumable/ethanol)) + return 1 + return 0 + +/mob/living/carbon/human/handle_regular_status_updates() + if(status_flags & GODMODE) + return 0 + + . = ..() + + if(.) //alive + if(REGEN in mutations) + heal_overall_damage(0.1, 0.1) + + if(paralysis) + stat = UNCONSCIOUS + + else if(sleeping) + + stat = UNCONSCIOUS + + if(mind) + if(mind.vampire) + if(istype(loc, /obj/structure/closet/coffin)) + adjustBruteLoss(-1) + adjustFireLoss(-1) + adjustToxLoss(-1) + + else if(status_flags & FAKEDEATH) + stat = UNCONSCIOUS + + //Vision //god knows why this is here + var/obj/item/organ/vision + if(dna.species.vision_organ) + vision = get_int_organ(dna.species.vision_organ) + + if(!dna.species.vision_organ) // Presumably if a species has no vision organs, they see via some other means. + SetEyeBlind(0) + SetEyeBlurry(0) + + else if(!vision || vision.is_broken()) // Vision organs cut out or broken? Permablind. + EyeBlind(2) + EyeBlurry(2) + + else + //blindness + if(disabilities & BLIND) // Disabled-blind, doesn't get better on its own + + else if(eye_blind) // Blindness, heals slowly over time + AdjustEyeBlind(-1) + + else if(istype(glasses, /obj/item/clothing/glasses/sunglasses/blindfold)) //resting your eyes with a blindfold heals blurry eyes faster + AdjustEyeBlurry(-3) + + //blurry sight + if(vision.is_bruised()) // Vision organs impaired? Permablurry. + EyeBlurry(2) + + if(eye_blurry) // Blurry eyes heal slowly + AdjustEyeBlurry(-1) + + + if(flying) + animate(src, pixel_y = pixel_y + 5 , time = 10, loop = 1, easing = SINE_EASING) + animate(pixel_y = pixel_y - 5, time = 10, loop = 1, easing = SINE_EASING) + + // If you're dirty, your gloves will become dirty, too. + if(gloves && germ_level > gloves.germ_level && prob(10)) + gloves.germ_level += 1 + + handle_organs() + + if(getBrainLoss() >= 120 || (health + (getOxyLoss() / 2)) <= -500) + death() + return + + if(getBrainLoss() >= 100) // braindeath + AdjustLoseBreath(10, bound_lower = 0, bound_upper = 25) + Weaken(30) + + if(!check_death_method()) + if(health <= HEALTH_THRESHOLD_DEAD) + var/deathchance = min(99, ((getBrainLoss() * -5) + (health + (getOxyLoss() / 2))) * -0.01) + if(prob(deathchance)) + death() + return + + if(health <= HEALTH_THRESHOLD_CRIT) + if(prob(5)) + emote(pick("faint", "collapse", "cry", "moan", "gasp", "shudder", "shiver")) + AdjustStuttering(5, bound_lower = 0, bound_upper = 5) + EyeBlurry(5) + if(prob(7)) + AdjustConfused(2) + if(prob(5)) + Paralyse(2) + switch(health) + if(-INFINITY to -100) + adjustOxyLoss(1) + if(prob(health * -0.1)) + if(ishuman(src)) + var/mob/living/carbon/human/H = src + H.set_heartattack(TRUE) + if(prob(health * -0.2)) + var/datum/disease/D = new /datum/disease/critical/heart_failure + ForceContractDisease(D) + Paralyse(5) + if(-99 to -80) + adjustOxyLoss(1) + if(prob(4)) + to_chat(src, "Your chest hurts...") + Paralyse(2) + var/datum/disease/D = new /datum/disease/critical/heart_failure + ForceContractDisease(D) + if(-79 to -50) + adjustOxyLoss(1) + if(prob(10)) + var/datum/disease/D = new /datum/disease/critical/shock + ForceContractDisease(D) + if(prob(health * -0.08)) + var/datum/disease/D = new /datum/disease/critical/heart_failure + ForceContractDisease(D) + if(prob(6)) + to_chat(src, "You feel [pick("horrible pain", "awful", "like shit", "absolutely awful", "like death", "like you are dying", "nothing", "warm", "sweaty", "tingly", "really, really bad", "horrible")]!") + Weaken(3) + if(prob(3)) + Paralyse(2) + if(-49 to 0) + adjustOxyLoss(1) + if(prob(3)) + var/datum/disease/D = new /datum/disease/critical/shock + ForceContractDisease(D) + if(prob(5)) + to_chat(src, "You feel [pick("terrible", "awful", "like shit", "sick", "numb", "cold", "sweaty", "tingly", "horrible")]!") + Weaken(3) + + else //dead + SetSilence(0) + + +/mob/living/carbon/human/handle_vision() + if(machine) + if(!machine.check_eye(src)) + reset_perspective(null) + else + var/isRemoteObserve = 0 + if((REMOTE_VIEW in mutations) && remoteview_target) + isRemoteObserve = 1 + + if(remoteview_target.stat != CONSCIOUS) + to_chat(src, "Your psy-connection grows too faint to maintain!") + isRemoteObserve = 0 + + if(PSY_RESIST in remoteview_target.mutations) + to_chat(src, "Your mind is shut out!") + isRemoteObserve = 0 + + // Not on the station or mining? + var/turf/temp_turf = get_turf(remoteview_target) + if(!temp_turf in config.contact_levels) + to_chat(src, "Your psy-connection grows too faint to maintain!") + isRemoteObserve = 0 + + if(remote_view) + isRemoteObserve = 1 + + if(!isRemoteObserve && client && !client.adminobs) + remoteview_target = null + reset_perspective(null) + +/mob/living/carbon/human/handle_hud_icons() + dna.species.handle_hud_icons(src) + +/mob/living/carbon/human/handle_hud_icons_health() + dna.species.handle_hud_icons_health(src) + handle_hud_icons_health_overlay() + +/mob/living/carbon/human/handle_random_events() + // Puke if toxloss is too high + if(!stat) + if(getToxLoss() >= 45 && nutrition > 20) + lastpuke ++ + if(lastpuke >= 25) // about 25 second delay I guess + vomit(20, 0, 1, 0, 1) + adjustToxLoss(-3) + lastpuke = 0 + +/mob/living/carbon/human/proc/handle_embedded_objects() + for(var/X in bodyparts) + var/obj/item/organ/external/BP = X + for(var/obj/item/I in BP.embedded_objects) + if(prob(I.embedded_pain_chance)) + BP.receive_damage(I.w_class*I.embedded_pain_multiplier) + to_chat(src, "[I] embedded in your [BP.name] hurts!") + + if(prob(I.embedded_fall_chance)) + BP.receive_damage(I.w_class*I.embedded_fall_pain_multiplier) + BP.embedded_objects -= I + I.forceMove(get_turf(src)) + visible_message("[I] falls out of [name]'s [BP.name]!","[I] falls out of your [BP.name]!") + if(!has_embedded_objects()) + clear_alert("embeddedobject") + +/mob/living/carbon/human/handle_changeling() + if(mind) + if(mind.changeling) + mind.changeling.regenerate(src) + if(hud_used) + hud_used.lingchemdisplay.invisibility = 0 + hud_used.lingchemdisplay.maptext = "
        [round(mind.changeling.chem_charges)]
        " + else + if(hud_used) + hud_used.lingchemdisplay.invisibility = 101 + + +/mob/living/carbon/human/proc/handle_pulse(times_fired) + if(times_fired % 5 == 1) + return pulse //update pulse every 5 life ticks (~1 tick/sec, depending on server load) + + if(NO_BLOOD in dna.species.species_traits) + return PULSE_NONE //No blood, no pulse. + + if(stat == DEAD) + return PULSE_NONE //that's it, you're dead, nothing can influence your pulse + + if(undergoing_cardiac_arrest()) + return PULSE_NONE + + var/temp = PULSE_NORM + + if(blood_volume <= BLOOD_VOLUME_BAD)//how much blood do we have + temp = PULSE_THREADY //not enough :( + + if(status_flags & FAKEDEATH) + temp = PULSE_NONE //pretend that we're dead. unlike actual death, can be inflienced by meds + + for(var/datum/reagent/R in reagents.reagent_list) + if(R.heart_rate_decrease) + if(temp <= PULSE_THREADY && temp >= PULSE_NORM) + temp-- + break + + for(var/datum/reagent/R in reagents.reagent_list)//handles different chems' influence on pulse + if(R.heart_rate_increase) + if(temp <= PULSE_FAST && temp >= PULSE_NONE) + temp++ + break + + for(var/datum/reagent/R in reagents.reagent_list) //To avoid using fakedeath + if(R.heart_rate_stop) + temp = PULSE_NONE + break + + return temp + +/mob/living/carbon/human/proc/handle_decay() + var/decaytime = world.time - timeofdeath + + if(NO_DECAY in dna.species.species_traits) + return + + if(reagents.has_reagent("formaldehyde")) //embalming fluid stops decay + return + + if(decaytime <= 6000) //10 minutes for decaylevel1 -- stinky + return + + if(decaytime > 6000 && decaytime <= 12000)//20 minutes for decaylevel2 -- bloated and very stinky + decaylevel = 1 + + if(decaytime > 12000 && decaytime <= 18000)//30 minutes for decaylevel3 -- rotting and gross + decaylevel = 2 + + if(decaytime > 18000 && decaytime <= 27000)//45 minutes for decaylevel4 -- skeleton + decaylevel = 3 + + if(decaytime > 27000) + decaylevel = 4 + makeSkeleton() + return //No puking over skeletons, they don't smell at all! + + if(!isturf(loc)) + return + + for(var/mob/living/carbon/human/H in range(decaylevel, src)) + if(prob(2)) + var/obj/item/clothing/mask/M = H.wear_mask + if(M && (M.flags_cover & MASKCOVERSMOUTH)) + continue + if(NO_BREATHE in H.dna.species.species_traits) + continue //no puking if you can't smell! + // Humans can lack a mind datum, y'know + if(H.mind && (H.mind.assigned_role == "Detective" || H.mind.assigned_role == "Coroner")) + continue //too cool for puke + to_chat(H, "You smell something foul...") + H.fakevomit() + +/mob/living/carbon/human/proc/handle_heartbeat() + var/client/C = src.client + if(C && C.prefs.sound & SOUND_HEARTBEAT) //disable heartbeat by pref + var/obj/item/organ/internal/heart/H = get_int_organ(/obj/item/organ/internal/heart) + + if(!H) //H.status will runtime if there is no H (obviously) + return + + if(H.is_robotic()) //Handle robotic hearts specially with a wuuuubb. This also applies to machine-people. + if(isinspace()) + //PULSE_THREADY - maximum value for pulse, currently it 5. + //High pulse value corresponds to a fast rate of heartbeat. + //Divided by 2, otherwise it is too slow. + var/rate = (PULSE_THREADY - 2)/2 //machine people (main target) have no pulse, manually subtract standard human pulse (2). Mechanic-heart humans probably have a pulse, but 'advanced neural systems' keep the heart rate steady, or something + + if(heartbeat >= rate) + heartbeat = 0 + src << sound('sound/effects/electheart.ogg',0,0,CHANNEL_HEARTBEAT,30)//Credit to GhostHack (www.ghosthack.de) for sound. + + else + heartbeat++ + return + return + + if(pulse == PULSE_NONE) + return + + if(pulse >= PULSE_2FAST || isinspace()) + //PULSE_THREADY - maximum value for pulse, currently it 5. + //High pulse value corresponds to a fast rate of heartbeat. + //Divided by 2, otherwise it is too slow. + var/rate = (PULSE_THREADY - pulse)/2 + + if(heartbeat >= rate) + heartbeat = 0 + src << sound('sound/effects/singlebeat.ogg',0,0,CHANNEL_HEARTBEAT,50) + else + heartbeat++ + +/* + Called by life(), instead of having the individual hud items update icons each tick and check for status changes + we only set those statuses and icons upon changes. Then those HUD items will simply add those pre-made images. + This proc below is only called when those HUD elements need to change as determined by the mobs hud_updateflag. +*/ + +/mob/living/carbon/human/proc/can_heartattack() + if((NO_BLOOD in dna.species.species_traits) && !dna.species.forced_heartattack) + return FALSE + if(NO_INTORGANS in dna.species.species_traits) + return FALSE + return TRUE + +/mob/living/carbon/human/proc/undergoing_cardiac_arrest() + if(!can_heartattack()) + return FALSE + var/obj/item/organ/internal/heart/heart = get_int_organ(/obj/item/organ/internal/heart) + if(istype(heart)) + if(heart.status & ORGAN_DEAD) + return TRUE + if(heart.beating) + return FALSE + return TRUE + +/mob/living/carbon/human/proc/set_heartattack(status) + if(!can_heartattack()) + return FALSE + + var/obj/item/organ/internal/heart/heart = get_int_organ(/obj/item/organ/internal/heart) + if(!istype(heart)) + return FALSE + + heart.beating = !status + +/mob/living/carbon/human/handle_heartattack() + if(!can_heartattack() || !undergoing_cardiac_arrest() || reagents.has_reagent("corazone")) + return + if(getOxyLoss()) + adjustBrainLoss(3) + else if(prob(10)) + adjustBrainLoss(1) + Weaken(5) + AdjustLoseBreath(20, bound_lower = 0, bound_upper = 25) + adjustOxyLoss(20) + + + +// Need this in species. +//#undef HUMAN_MAX_OXYLOSS +//#undef HUMAN_CRIT_MAX_OXYLOSS diff --git a/code/modules/mob/living/carbon/human/login.dm b/code/modules/mob/living/carbon/human/login.dm index 4cc542ae1ad..6171b3d02b0 100644 --- a/code/modules/mob/living/carbon/human/login.dm +++ b/code/modules/mob/living/carbon/human/login.dm @@ -1,9 +1,9 @@ -/mob/living/carbon/human/Login() - if(player_logged) - overlays -= image('icons/effects/effects.dmi', icon_state = "zzz_glow") - ..() - - if(ventcrawler) - to_chat(src, "You can ventcrawl! Use alt+click on vents to quickly travel about the station.") - update_pipe_vision() - return +/mob/living/carbon/human/Login() + if(player_logged) + overlays -= image('icons/effects/effects.dmi', icon_state = "zzz_glow") + ..() + + if(ventcrawler) + to_chat(src, "You can ventcrawl! Use alt+click on vents to quickly travel about the station.") + update_pipe_vision() + return diff --git a/code/modules/mob/living/carbon/human/logout.dm b/code/modules/mob/living/carbon/human/logout.dm index 2dae7ea5b9a..43863d95622 100644 --- a/code/modules/mob/living/carbon/human/logout.dm +++ b/code/modules/mob/living/carbon/human/logout.dm @@ -1,4 +1,4 @@ /mob/living/carbon/human/Logout() ..() if(mind && mind.active && stat != DEAD) - overlays += image('icons/effects/effects.dmi', icon_state = "zzz_glow") \ No newline at end of file + overlays += image('icons/effects/effects.dmi', icon_state = "zzz_glow") diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm index 2aa8950f7ef..40638a2110c 100644 --- a/code/modules/mob/living/carbon/human/say.dm +++ b/code/modules/mob/living/carbon/human/say.dm @@ -1,259 +1,259 @@ -/mob/living/carbon/human/say(var/message, var/sanitize = TRUE, var/ignore_speech_problems = FALSE, var/ignore_atmospherics = FALSE) - ..(message, sanitize = sanitize, ignore_speech_problems = ignore_speech_problems, ignore_atmospherics = ignore_atmospherics) //ohgod we should really be passing a datum here. - -/mob/living/carbon/human/GetAltName() - if(name != GetVoice()) - return " (as [get_id_name("Unknown")])" - return ..() - -/mob/living/carbon/human/proc/forcesay(list/append) - if(stat == CONSCIOUS) - if(client) - var/virgin = 1 //has the text been modified yet? - var/temp = winget(client, "input", "text") - if(findtextEx(temp, "Say \"", 1, 7) && length(temp) > 5) //case sensitive means - - temp = replacetext(temp, ";", "") //general radio - - if(findtext(trim_left(temp), ":", 6, 7)) //dept radio - temp = copytext(trim_left(temp), 8) - virgin = 0 - - if(virgin) - temp = copytext(trim_left(temp), 6) //normal speech - virgin = 0 - - while(findtext(trim_left(temp), ":", 1, 2)) //dept radio again (necessary) - temp = copytext(trim_left(temp), 3) - - if(findtext(temp, "*", 1, 2)) //emotes - return - temp = copytext(trim_left(temp), 1, rand(5,8)) - - var/trimmed = trim_left(temp) - if(length(trimmed)) - if(append) - temp += pick(append) - - say(temp) - winset(client, "input", "text=[null]") - -/mob/living/carbon/human/say_understands(var/mob/other, var/datum/language/speaking = null) - if(has_brain_worms()) //Brain worms translate everything. Even mice and alien speak. - return 1 - - if(dna.species.can_understand(other)) - return 1 - - //These only pertain to common. Languages are handled by mob/say_understands() - if(!speaking) - if(istype(other, /mob/living/simple_animal/diona)) - if(other.languages.len >= 2) //They've sucked down some blood and can speak common now. - return 1 - if(issilicon(other)) - return 1 - if(isbot(other)) - return 1 - if(isbrain(other)) - return 1 - if(isslime(other)) - return 1 - - return ..() - -/mob/living/carbon/human/proc/HasVoiceChanger() - if(istype(back, /obj/item/rig)) - var/obj/item/rig/rig = back - if(rig.speech && rig.speech.voice_holder && rig.speech.voice_holder.active && rig.speech.voice_holder.voice) - return rig.speech.voice_holder.voice - - for(var/obj/item/gear in list(wear_mask, wear_suit, head)) - if(!gear) - continue - - var/obj/item/voice_changer/changer = locate() in gear - if(changer && changer.active) - if(changer.voice) - return changer.voice - else if(wear_id) - var/obj/item/card/id/idcard = wear_id.GetID() - if(istype(idcard)) - return idcard.registered_name - - return FALSE - -/mob/living/carbon/human/GetVoice() - var/has_changer = HasVoiceChanger() - - if(has_changer) - return has_changer - - if(mind && mind.changeling && mind.changeling.mimicing) - return mind.changeling.mimicing - - if(GetSpecialVoice()) - return GetSpecialVoice() - - return real_name - -/mob/living/carbon/human/IsVocal() - var/obj/item/organ/internal/cyberimp/brain/speech_translator/translator = locate(/obj/item/organ/internal/cyberimp/brain/speech_translator) in internal_organs - if(translator && translator.active) - return TRUE - // how do species that don't breathe talk? magic, that's what. - var/breathes = (!(NO_BREATHE in dna.species.species_traits)) - var/obj/item/organ/internal/L = get_organ_slot("lungs") - if((breathes && !L) || breathes && L && (L.status & ORGAN_DEAD)) - return FALSE - if(getOxyLoss() > 10 || losebreath >= 4) - emote("gasp") - return FALSE - if(mind) - return !mind.miming - return TRUE - -/mob/living/carbon/human/proc/SetSpecialVoice(var/new_voice) - if(new_voice) - special_voice = new_voice - return - -/mob/living/carbon/human/proc/UnsetSpecialVoice() - special_voice = "" - return - -/mob/living/carbon/human/proc/GetSpecialVoice() - return special_voice - -/mob/living/carbon/human/handle_speech_problems(list/message_pieces, var/verb) - var/span = "" - var/obj/item/organ/internal/cyberimp/brain/speech_translator/translator = locate(/obj/item/organ/internal/cyberimp/brain/speech_translator) in internal_organs - if(translator) - if(translator.active) - span = translator.speech_span - for(var/datum/multilingual_say_piece/S in message_pieces) - S.message = "[S.message]" - verb = translator.speech_verb - return list("verb" = verb) - if(mind) - span = mind.speech_span - if((COMIC in mutations) \ - || (locate(/obj/item/organ/internal/cyberimp/brain/clown_voice) in internal_organs) \ - || GetComponent(/datum/component/jestosterone)) - span = "sans" - - if(WINGDINGS in mutations) - span = "wingdings" - - var/list/parent = ..() - verb = parent["verb"] - - for(var/datum/multilingual_say_piece/S in message_pieces) - if(S.speaking && S.speaking.flags & NO_STUTTER) - continue - - if(silent || (disabilities & MUTE)) - S.message = "" - - if(istype(wear_mask, /obj/item/clothing/mask/horsehead)) - var/obj/item/clothing/mask/horsehead/hoers = wear_mask - if(hoers.voicechange) - S.message = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!") - verb = pick("whinnies", "neighs", "says") - - if(dna) - for(var/datum/dna/gene/gene in dna_genes) - if(!gene.block) - continue - if(gene.is_active(src)) - S.message = gene.OnSay(src, S.message) - - var/braindam = getBrainLoss() - if(braindam >= 60) - if(prob(braindam / 4)) - S.message = stutter(S.message) - verb = "gibbers" - if(prob(braindam)) - S.message = uppertext(S.message) - verb = "yells loudly" - - if(span) - S.message = "[S.message]" - return list("verb" = verb) - -/mob/living/carbon/human/handle_message_mode(var/message_mode, list/message_pieces, var/verb, var/used_radios) - switch(message_mode) - if("intercom") - for(var/obj/item/radio/intercom/I in view(1, src)) - spawn(0) - I.talk_into(src, message_pieces, null, verb) - used_radios += I - - if("headset") - var/obj/item/radio/R = null - if(isradio(l_ear)) - R = l_ear - used_radios += R - if(R.talk_into(src, message_pieces, null, verb)) - return - - if(isradio(r_ear)) - R = r_ear - used_radios += R - if(R.talk_into(src, message_pieces, null, verb)) - return - - if("right ear") - var/obj/item/radio/R - if(isradio(r_ear)) - R = r_ear - else if(isradio(r_hand)) - R = r_hand - if(R) - used_radios += R - R.talk_into(src, message_pieces, null, verb) - - if("left ear") - var/obj/item/radio/R - if(isradio(l_ear)) - R = l_ear - else if(isradio(l_hand)) - R = l_hand - if(R) - used_radios += R - R.talk_into(src, message_pieces, null, verb) - - if("whisper") - whisper_say(message_pieces) - return 1 - else - if(message_mode) - if(isradio(l_ear)) - used_radios += l_ear - if(l_ear.talk_into(src, message_pieces, message_mode, verb)) - return - - if(isradio(r_ear)) - used_radios += r_ear - if(r_ear.talk_into(src, message_pieces, message_mode, verb)) - return - -/mob/living/carbon/human/handle_speech_sound() - var/list/returns[3] - if(dna.species.speech_sounds && prob(dna.species.speech_chance)) - returns[1] = sound(pick(dna.species.speech_sounds)) - returns[2] = 50 - returns[3] = get_age_pitch() - return returns - -/mob/living/carbon/human/binarycheck() - . = FALSE - var/obj/item/radio/headset/R - if(istype(l_ear, /obj/item/radio/headset)) - R = l_ear - if(R.translate_binary) - . = TRUE - - if(istype(r_ear, /obj/item/radio/headset)) - R = r_ear - if(R.translate_binary) - . = TRUE +/mob/living/carbon/human/say(var/message, var/sanitize = TRUE, var/ignore_speech_problems = FALSE, var/ignore_atmospherics = FALSE) + ..(message, sanitize = sanitize, ignore_speech_problems = ignore_speech_problems, ignore_atmospherics = ignore_atmospherics) //ohgod we should really be passing a datum here. + +/mob/living/carbon/human/GetAltName() + if(name != GetVoice()) + return " (as [get_id_name("Unknown")])" + return ..() + +/mob/living/carbon/human/proc/forcesay(list/append) + if(stat == CONSCIOUS) + if(client) + var/virgin = 1 //has the text been modified yet? + var/temp = winget(client, "input", "text") + if(findtextEx(temp, "Say \"", 1, 7) && length(temp) > 5) //case sensitive means + + temp = replacetext(temp, ";", "") //general radio + + if(findtext(trim_left(temp), ":", 6, 7)) //dept radio + temp = copytext(trim_left(temp), 8) + virgin = 0 + + if(virgin) + temp = copytext(trim_left(temp), 6) //normal speech + virgin = 0 + + while(findtext(trim_left(temp), ":", 1, 2)) //dept radio again (necessary) + temp = copytext(trim_left(temp), 3) + + if(findtext(temp, "*", 1, 2)) //emotes + return + temp = copytext(trim_left(temp), 1, rand(5,8)) + + var/trimmed = trim_left(temp) + if(length(trimmed)) + if(append) + temp += pick(append) + + say(temp) + winset(client, "input", "text=[null]") + +/mob/living/carbon/human/say_understands(var/mob/other, var/datum/language/speaking = null) + if(has_brain_worms()) //Brain worms translate everything. Even mice and alien speak. + return 1 + + if(dna.species.can_understand(other)) + return 1 + + //These only pertain to common. Languages are handled by mob/say_understands() + if(!speaking) + if(istype(other, /mob/living/simple_animal/diona)) + if(other.languages.len >= 2) //They've sucked down some blood and can speak common now. + return 1 + if(issilicon(other)) + return 1 + if(isbot(other)) + return 1 + if(isbrain(other)) + return 1 + if(isslime(other)) + return 1 + + return ..() + +/mob/living/carbon/human/proc/HasVoiceChanger() + if(istype(back, /obj/item/rig)) + var/obj/item/rig/rig = back + if(rig.speech && rig.speech.voice_holder && rig.speech.voice_holder.active && rig.speech.voice_holder.voice) + return rig.speech.voice_holder.voice + + for(var/obj/item/gear in list(wear_mask, wear_suit, head)) + if(!gear) + continue + + var/obj/item/voice_changer/changer = locate() in gear + if(changer && changer.active) + if(changer.voice) + return changer.voice + else if(wear_id) + var/obj/item/card/id/idcard = wear_id.GetID() + if(istype(idcard)) + return idcard.registered_name + + return FALSE + +/mob/living/carbon/human/GetVoice() + var/has_changer = HasVoiceChanger() + + if(has_changer) + return has_changer + + if(mind && mind.changeling && mind.changeling.mimicing) + return mind.changeling.mimicing + + if(GetSpecialVoice()) + return GetSpecialVoice() + + return real_name + +/mob/living/carbon/human/IsVocal() + var/obj/item/organ/internal/cyberimp/brain/speech_translator/translator = locate(/obj/item/organ/internal/cyberimp/brain/speech_translator) in internal_organs + if(translator && translator.active) + return TRUE + // how do species that don't breathe talk? magic, that's what. + var/breathes = (!(NO_BREATHE in dna.species.species_traits)) + var/obj/item/organ/internal/L = get_organ_slot("lungs") + if((breathes && !L) || breathes && L && (L.status & ORGAN_DEAD)) + return FALSE + if(getOxyLoss() > 10 || losebreath >= 4) + emote("gasp") + return FALSE + if(mind) + return !mind.miming + return TRUE + +/mob/living/carbon/human/proc/SetSpecialVoice(var/new_voice) + if(new_voice) + special_voice = new_voice + return + +/mob/living/carbon/human/proc/UnsetSpecialVoice() + special_voice = "" + return + +/mob/living/carbon/human/proc/GetSpecialVoice() + return special_voice + +/mob/living/carbon/human/handle_speech_problems(list/message_pieces, var/verb) + var/span = "" + var/obj/item/organ/internal/cyberimp/brain/speech_translator/translator = locate(/obj/item/organ/internal/cyberimp/brain/speech_translator) in internal_organs + if(translator) + if(translator.active) + span = translator.speech_span + for(var/datum/multilingual_say_piece/S in message_pieces) + S.message = "[S.message]" + verb = translator.speech_verb + return list("verb" = verb) + if(mind) + span = mind.speech_span + if((COMIC in mutations) \ + || (locate(/obj/item/organ/internal/cyberimp/brain/clown_voice) in internal_organs) \ + || GetComponent(/datum/component/jestosterone)) + span = "sans" + + if(WINGDINGS in mutations) + span = "wingdings" + + var/list/parent = ..() + verb = parent["verb"] + + for(var/datum/multilingual_say_piece/S in message_pieces) + if(S.speaking && S.speaking.flags & NO_STUTTER) + continue + + if(silent || (disabilities & MUTE)) + S.message = "" + + if(istype(wear_mask, /obj/item/clothing/mask/horsehead)) + var/obj/item/clothing/mask/horsehead/hoers = wear_mask + if(hoers.voicechange) + S.message = pick("NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!") + verb = pick("whinnies", "neighs", "says") + + if(dna) + for(var/datum/dna/gene/gene in dna_genes) + if(!gene.block) + continue + if(gene.is_active(src)) + S.message = gene.OnSay(src, S.message) + + var/braindam = getBrainLoss() + if(braindam >= 60) + if(prob(braindam / 4)) + S.message = stutter(S.message) + verb = "gibbers" + if(prob(braindam)) + S.message = uppertext(S.message) + verb = "yells loudly" + + if(span) + S.message = "[S.message]" + return list("verb" = verb) + +/mob/living/carbon/human/handle_message_mode(var/message_mode, list/message_pieces, var/verb, var/used_radios) + switch(message_mode) + if("intercom") + for(var/obj/item/radio/intercom/I in view(1, src)) + spawn(0) + I.talk_into(src, message_pieces, null, verb) + used_radios += I + + if("headset") + var/obj/item/radio/R = null + if(isradio(l_ear)) + R = l_ear + used_radios += R + if(R.talk_into(src, message_pieces, null, verb)) + return + + if(isradio(r_ear)) + R = r_ear + used_radios += R + if(R.talk_into(src, message_pieces, null, verb)) + return + + if("right ear") + var/obj/item/radio/R + if(isradio(r_ear)) + R = r_ear + else if(isradio(r_hand)) + R = r_hand + if(R) + used_radios += R + R.talk_into(src, message_pieces, null, verb) + + if("left ear") + var/obj/item/radio/R + if(isradio(l_ear)) + R = l_ear + else if(isradio(l_hand)) + R = l_hand + if(R) + used_radios += R + R.talk_into(src, message_pieces, null, verb) + + if("whisper") + whisper_say(message_pieces) + return 1 + else + if(message_mode) + if(isradio(l_ear)) + used_radios += l_ear + if(l_ear.talk_into(src, message_pieces, message_mode, verb)) + return + + if(isradio(r_ear)) + used_radios += r_ear + if(r_ear.talk_into(src, message_pieces, message_mode, verb)) + return + +/mob/living/carbon/human/handle_speech_sound() + var/list/returns[3] + if(dna.species.speech_sounds && prob(dna.species.speech_chance)) + returns[1] = sound(pick(dna.species.speech_sounds)) + returns[2] = 50 + returns[3] = get_age_pitch() + return returns + +/mob/living/carbon/human/binarycheck() + . = FALSE + var/obj/item/radio/headset/R + if(istype(l_ear, /obj/item/radio/headset)) + R = l_ear + if(R.translate_binary) + . = TRUE + + if(istype(r_ear, /obj/item/radio/headset)) + R = r_ear + if(R.translate_binary) + . = TRUE diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm index a5a96260028..6034a22139b 100644 --- a/code/modules/mob/living/carbon/human/species/_species.dm +++ b/code/modules/mob/living/carbon/human/species/_species.dm @@ -980,4 +980,4 @@ It'll return null if the organ doesn't correspond, so include null checks when u . = FALSE var/obj/item/organ/internal/ears/ears = H.get_int_organ(/obj/item/organ/internal/ears) if(istype(ears) && !ears.deaf) - . = TRUE \ No newline at end of file + . = TRUE diff --git a/code/modules/mob/living/carbon/human/species/abductor.dm b/code/modules/mob/living/carbon/human/species/abductor.dm index 33b94cbabe0..9571040fcec 100644 --- a/code/modules/mob/living/carbon/human/species/abductor.dm +++ b/code/modules/mob/living/carbon/human/species/abductor.dm @@ -43,4 +43,4 @@ /datum/species/abductor/on_species_loss(mob/living/carbon/human/H) ..() var/datum/atom_hud/abductor_hud = huds[DATA_HUD_ABDUCTOR] - abductor_hud.remove_hud_from(H) \ No newline at end of file + abductor_hud.remove_hud_from(H) diff --git a/code/modules/mob/living/carbon/human/species/diona.dm b/code/modules/mob/living/carbon/human/species/diona.dm index 48664341623..22edf961270 100644 --- a/code/modules/mob/living/carbon/human/species/diona.dm +++ b/code/modules/mob/living/carbon/human/species/diona.dm @@ -117,4 +117,4 @@ /datum/species/diona/pod/on_species_loss(mob/living/carbon/C) . = ..() C.faction -= "plants" - C.faction -= "vines" \ No newline at end of file + C.faction -= "vines" diff --git a/code/modules/mob/living/carbon/human/species/golem.dm b/code/modules/mob/living/carbon/human/species/golem.dm index f88670fc0c1..157995e9549 100644 --- a/code/modules/mob/living/carbon/human/species/golem.dm +++ b/code/modules/mob/living/carbon/human/species/golem.dm @@ -722,4 +722,4 @@ H.mind.miming = TRUE /datum/unarmed_attack/golem/tranquillite - attack_sound = null \ No newline at end of file + attack_sound = null diff --git a/code/modules/mob/living/carbon/human/species/human.dm b/code/modules/mob/living/carbon/human/species/human.dm index 2570fb060e7..6052e769c23 100644 --- a/code/modules/mob/living/carbon/human/species/human.dm +++ b/code/modules/mob/living/carbon/human/species/human.dm @@ -18,4 +18,4 @@ reagent_tag = PROCESS_ORG //Has standard darksight of 2. - \ No newline at end of file + diff --git a/code/modules/mob/living/carbon/human/species/kidan.dm b/code/modules/mob/living/carbon/human/species/kidan.dm index 538b524d4d8..8d684b36ba0 100644 --- a/code/modules/mob/living/carbon/human/species/kidan.dm +++ b/code/modules/mob/living/carbon/human/species/kidan.dm @@ -39,4 +39,4 @@ "is twisting their own neck!", "is cracking their exoskeleton!", "is stabbing themselves with their mandibles!", - "is holding their breath!") \ No newline at end of file + "is holding their breath!") diff --git a/code/modules/mob/living/carbon/human/species/nucleation.dm b/code/modules/mob/living/carbon/human/species/nucleation.dm index cf9f5846193..f82a6283066 100644 --- a/code/modules/mob/living/carbon/human/species/nucleation.dm +++ b/code/modules/mob/living/carbon/human/species/nucleation.dm @@ -38,4 +38,4 @@ var/turf/T = get_turf(H) H.visible_message("[H]'s body explodes, leaving behind a pile of microscopic crystals!") explosion(T, 0, 0, 2, 2) // Create a small explosion burst upon death - qdel(H) \ No newline at end of file + qdel(H) diff --git a/code/modules/mob/living/carbon/human/species/shadow.dm b/code/modules/mob/living/carbon/human/species/shadow.dm index b21002e7225..7d1d4c26a61 100644 --- a/code/modules/mob/living/carbon/human/species/shadow.dm +++ b/code/modules/mob/living/carbon/human/species/shadow.dm @@ -70,4 +70,4 @@ else if(light_amount < 2) //heal in the dark H.heal_overall_damage(1,1) H.clear_alert("lightexposure") - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/carbon/human/species/shadowling.dm b/code/modules/mob/living/carbon/human/species/shadowling.dm index d05c358ce73..4667a6b1019 100644 --- a/code/modules/mob/living/carbon/human/species/shadowling.dm +++ b/code/modules/mob/living/carbon/human/species/shadowling.dm @@ -79,4 +79,4 @@ H.adjustToxLoss(-5) H.adjustBrainLoss(-25) H.adjustCloneLoss(-1) - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/carbon/human/species/skrell.dm b/code/modules/mob/living/carbon/human/species/skrell.dm index 9e21c0bf390..e0710bec11f 100644 --- a/code/modules/mob/living/carbon/human/species/skrell.dm +++ b/code/modules/mob/living/carbon/human/species/skrell.dm @@ -52,4 +52,4 @@ /datum/species/skrell/on_species_loss(mob/living/carbon/human/H) ..() - REMOVE_TRAIT(H, TRAIT_WATERBREATH, "species") \ No newline at end of file + REMOVE_TRAIT(H, TRAIT_WATERBREATH, "species") diff --git a/code/modules/mob/living/carbon/human/species/slime.dm b/code/modules/mob/living/carbon/human/species/slime.dm index e7501486b72..d9fa1ccbf3c 100644 --- a/code/modules/mob/living/carbon/human/species/slime.dm +++ b/code/modules/mob/living/carbon/human/species/slime.dm @@ -196,4 +196,4 @@ #undef SLIMEPERSON_HUNGERCOST #undef SLIMEPERSON_MINHUNGER -#undef SLIMEPERSON_REGROWTHDELAY \ No newline at end of file +#undef SLIMEPERSON_REGROWTHDELAY diff --git a/code/modules/mob/living/carbon/human/species/tajaran.dm b/code/modules/mob/living/carbon/human/species/tajaran.dm index ee12ba34afe..8a803aa3bd4 100644 --- a/code/modules/mob/living/carbon/human/species/tajaran.dm +++ b/code/modules/mob/living/carbon/human/species/tajaran.dm @@ -56,4 +56,4 @@ "is holding their breath!") /datum/species/tajaran/handle_death(gibbed, mob/living/carbon/human/H) - H.stop_tail_wagging(1) \ No newline at end of file + H.stop_tail_wagging(1) diff --git a/code/modules/mob/living/carbon/human/species/vulpkanin.dm b/code/modules/mob/living/carbon/human/species/vulpkanin.dm index 15e25fa2ff5..9a313042d2d 100644 --- a/code/modules/mob/living/carbon/human/species/vulpkanin.dm +++ b/code/modules/mob/living/carbon/human/species/vulpkanin.dm @@ -50,4 +50,4 @@ "is holding their breath!") /datum/species/vulpkanin/handle_death(gibbed, mob/living/carbon/human/H) - H.stop_tail_wagging(1) \ No newline at end of file + H.stop_tail_wagging(1) diff --git a/code/modules/mob/living/carbon/human/species/wryn.dm b/code/modules/mob/living/carbon/human/species/wryn.dm index 3fec4822919..9e02e2e28be 100644 --- a/code/modules/mob/living/carbon/human/species/wryn.dm +++ b/code/modules/mob/living/carbon/human/species/wryn.dm @@ -75,4 +75,4 @@ add_attack_logs(user, target, "Antennae removed") return 0 else - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index eeda9f87461..2ca0f47f7a7 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -1,1352 +1,1352 @@ -/* - Global associative list for caching humanoid icons. - Index format m or f, followed by a string of 0 and 1 to represent bodyparts followed by husk fat hulk skeleton 1 or 0. - TODO: Proper documentation - icon_key is [species.race_key][g][husk][fat][hulk][skeleton][s_tone] -*/ -var/global/list/human_icon_cache = list() - - /////////////////////// - //UPDATE_ICONS SYSTEM// - /////////////////////// -/* -Calling this a system is perhaps a bit trumped up. It is essentially update_clothing dismantled into its -core parts. The key difference is that when we generate overlays we do not generate either lying or standing -versions. Instead, we generate both and store them in two fixed-length lists, both using the same list-index -(The indexes are in update_icons.dm): Each list for humans is (at the time of writing) of length 19. -This will hopefully be reduced as the system is refined. - - var/overlays_lying[19] //For the lying down stance - var/overlays_standing[19] //For the standing stance - -When we call update_icons, the 'lying' variable is checked and then the appropriate list is assigned to our overlays! -That in itself uses a tiny bit more memory (no more than all the ridiculous lists the game has already mind you). - -On the other-hand, it should be very CPU cheap in comparison to the old system. -In the old system, we updated all our overlays every life() call, even if we were standing still inside a crate! -or dead!. 25ish overlays, all generated from scratch every second for every xeno/human/monkey and then applied. -More often than not update_clothing was being called a few times in addition to that! CPU was not the only issue, -all those icons had to be sent to every client. So really the cost was extremely cumulative. To the point where -update_clothing would frequently appear in the top 10 most CPU intensive procs during profiling. - -Another feature of this new system is that our lists are indexed. This means we can update specific overlays! -So we only regenerate icons when we need them to be updated! This is the main saving for this system. - -In practice this means that: - everytime you fall over, we just switch between precompiled lists. Which is fast and cheap. - Everytime you do something minor like take a pen out of your pocket, we only update the in-hand overlay - etc... - - -There are several things that need to be remembered: - -> Whenever we do something that should cause an overlay to update (which doesn't use standard procs - ( i.e. you do something like l_hand = /obj/item/something new(src) ) - You will need to call the relevant update_inv_* proc: - update_inv_head() - update_inv_wear_suit() - update_inv_gloves() - update_inv_shoes() - update_inv_w_uniform() - update_inv_glasse() - update_inv_l_hand() - update_inv_r_hand() - update_inv_belt() - update_inv_wear_id() - update_inv_ears() - update_inv_s_store() - update_inv_pockets() - update_inv_back() - update_inv_handcuffed() - update_inv_wear_mask() - - All of these are named after the variable they update from. They are defined at the mob/ level like - update_clothing was, so you won't cause undefined proc runtimes with usr.update_inv_wear_id() if the usr is a - slime etc. Instead, it'll just return without doing any work. So no harm in calling it for slimes and such. - - -> There are also these special cases: - update_mutations() //handles updating your appearance for certain mutations. e.g TK head-glows - update_mutantrace() //handles updating your appearance after setting the mutantrace var - UpdateDamageIcon() //handles damage overlays for brute/burn damage //(will rename this when I geta round to it) - update_body() //Handles updating your mob's icon to reflect their gender/race/complexion etc - update_hair() //Handles updating your hair overlay (used to be update_face, but mouth and - ...eyes were merged into update_body) - -> All of these procs update our overlays_lying and overlays_standing, and then call update_icons() by default. - If you wish to update several overlays at once, you can set the argument to 0 to disable the update and call - it manually: - e.g. - update_inv_head(0) - update_inv_l_hand(0) - update_inv_r_hand() //<---calls update_icons() - - or equivillantly: - update_inv_head(0) - update_inv_l_hand(0) - update_inv_r_hand(0) - update_icons() - -> If you need to update all overlays you can use regenerate_icons(). it works exactly like update_clothing used to. - -> I reimplimented an old unused variable which was in the code called (coincidentally) var/update_icon - It can be used as another method of triggering regenerate_icons(). It's basically a flag that when set to non-zero - will call regenerate_icons() at the next life() call and then reset itself to 0. - The idea behind it is icons are regenerated only once, even if multiple events requested it. - -This system is confusing and is still a WIP. It's primary goal is speeding up the controls of the game whilst -reducing processing costs. So please bear with me while I iron out the kinks. It will be worth it, I promise. -If I can eventually free var/lying stuff from the life() process altogether, stuns/death/status stuff -will become less affected by lag-spikes and will be instantaneous! :3 - -If you have any questions/constructive-comments/bugs-to-report/or have a massivly devestated butt... -Please contact me on #coderbus IRC. ~Carn x -*/ - -/mob/living/carbon/human - var/list/overlays_standing[TOTAL_LAYERS] - var/previous_damage_appearance // store what the body last looked like, so we only have to update it if something changed - var/icon/skeleton - var/list/cached_standing_overlays = list() // List of everything currently in a human's actual overlays - -/mob/living/carbon/human/proc/apply_overlay(cache_index) - if((. = overlays_standing[cache_index])) - add_overlay(.) - -/mob/living/carbon/human/proc/remove_overlay(cache_index) - var/I = overlays_standing[cache_index] - if(I) - cut_overlay(I) - overlays_standing[cache_index] = null - - -var/global/list/damage_icon_parts = list() - -//DAMAGE OVERLAYS -//constructs damage icon for each organ from mask * damage field and saves it in our overlays_ lists -/mob/living/carbon/human/UpdateDamageIcon(var/update_icons=1) - // first check whether something actually changed about damage appearance - var/damage_appearance = "" - - for(var/obj/item/organ/external/O in bodyparts) - damage_appearance += O.damage_state - - if(damage_appearance == previous_damage_appearance) - // nothing to do here - return - - previous_damage_appearance = damage_appearance - - remove_overlay(H_DAMAGE_LAYER) - var/mutable_appearance/damage_overlay = mutable_appearance(dna.species.damage_overlays, "00", layer = -H_DAMAGE_LAYER) - overlays_standing[H_DAMAGE_LAYER] = damage_overlay - - // blend the individual damage states with our icons - for(var/D in bodyparts) - var/obj/item/organ/external/E = D - E.update_icon() - if(E.damage_state == "00") - continue - - var/icon/DI - var/cache_index = "[E.damage_state]/[E.icon_name]/[dna.species.blood_color]/[dna.species.name]" - - if(damage_icon_parts[cache_index] == null) - DI = new /icon(dna.species.damage_overlays, E.damage_state) // the damage icon for whole human - DI.Blend(new /icon(dna.species.damage_mask, E.icon_name), ICON_MULTIPLY) // mask with this organ's pixels - DI.Blend(dna.species.blood_color, ICON_MULTIPLY) - damage_icon_parts[cache_index] = DI - else - DI = damage_icon_parts[cache_index] - damage_overlay.overlays += DI - - apply_overlay(H_DAMAGE_LAYER) - - -//BASE MOB SPRITE -/mob/living/carbon/human/proc/update_body(var/update_icons=1, var/rebuild_base=0) - remove_overlay(BODY_LAYER) - remove_overlay(LIMBS_LAYER) // So we don't get the old species' sprite splatted on top of the new one's - remove_overlay(UNDERWEAR_LAYER) - - var/husk_color_mod = rgb(96, 88, 80) - var/hulk_color_mod = rgb(48, 224, 40) - - var/husk = (HUSK in mutations) - var/hulk = (HULK in mutations) - var/skeleton = (SKELETON in mutations) - - if(dna.species && dna.species.bodyflags & HAS_ICON_SKIN_TONE) - dna.species.updatespeciescolor(src) - - //CACHING: Generate an index key from visible bodyparts. - //0 = destroyed, 1 = normal, 2 = robotic, 3 = necrotic. - //Create a new, blank icon for our mob to use. - if(stand_icon) - qdel(stand_icon) - - update_misc_effects() - stand_icon = new (dna.species.icon_template ? dna.species.icon_template : 'icons/mob/human.dmi', "blank") - var/list/standing = list() - var/icon_key = generate_icon_render_key() - - var/mutable_appearance/base - if(human_icon_cache[icon_key] && !rebuild_base) - base = human_icon_cache[icon_key] - standing += base - else - var/icon/base_icon - //BEGIN CACHED ICON GENERATION. - var/obj/item/organ/external/chest = get_organ("chest") - base_icon = chest.get_icon(skeleton) - - for(var/obj/item/organ/external/part in bodyparts) - var/icon/temp = part.get_icon(skeleton) - //That part makes left and right legs drawn topmost and lowermost when human looks WEST or EAST - //And no change in rendering for other parts (they icon_position is 0, so goes to 'else' part) - if(part.icon_position & (LEFT | RIGHT)) - var/icon/temp2 = new('icons/mob/human.dmi',"blank") - temp2.Insert(new/icon(temp,dir=NORTH),dir=NORTH) - temp2.Insert(new/icon(temp,dir=SOUTH),dir=SOUTH) - if(!(part.icon_position & LEFT)) - temp2.Insert(new/icon(temp,dir=EAST),dir=EAST) - if(!(part.icon_position & RIGHT)) - temp2.Insert(new/icon(temp,dir=WEST),dir=WEST) - base_icon.Blend(temp2, ICON_OVERLAY) - if(part.icon_position & LEFT) - temp2.Insert(new/icon(temp,dir=EAST),dir=EAST) - if(part.icon_position & RIGHT) - temp2.Insert(new/icon(temp,dir=WEST),dir=WEST) - base_icon.Blend(temp2, ICON_UNDERLAY) - else - base_icon.Blend(temp, ICON_OVERLAY) - - if(!skeleton) - if(isgolem(src)) - var/datum/species/golem/G = src.dna.species - if(G.golem_colour) - base_icon.ColorTone(G.golem_colour) - if(husk) - base_icon.ColorTone(husk_color_mod) - else if(hulk) - var/list/tone = ReadRGB(hulk_color_mod) - base_icon.MapColors(rgb(tone[1],0,0),rgb(0,tone[2],0),rgb(0,0,tone[3])) - - //Handle husk overlay. - if(husk && ("overlay_husk" in icon_states(chest.icobase))) - var/icon/mask = new(base_icon) - var/icon/husk_over = new(chest.icobase,"overlay_husk") - mask.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0) - husk_over.Blend(mask, ICON_ADD) - base_icon.Blend(husk_over, ICON_OVERLAY) - - var/mutable_appearance/new_base = mutable_appearance(base_icon, layer = -LIMBS_LAYER) - human_icon_cache[icon_key] = new_base - standing += new_base - - //END CACHED ICON GENERATION. - - overlays_standing[LIMBS_LAYER] = standing - apply_overlay(LIMBS_LAYER) - - //Underwear - var/icon/underwear_standing = new /icon('icons/mob/underwear.dmi', "nude") - if(underwear && dna.species.clothing_flags & HAS_UNDERWEAR) - var/datum/sprite_accessory/underwear/U = GLOB.underwear_list[underwear] - if(U) - var/u_icon = U.sprite_sheets && (dna.species.name in U.sprite_sheets) ? U.sprite_sheets[dna.species.name] : U.icon //Species-fit the undergarment. - underwear_standing.Blend(new /icon(u_icon, "uw_[U.icon_state]_s"), ICON_OVERLAY) - - if(undershirt && dna.species.clothing_flags & HAS_UNDERSHIRT) - var/datum/sprite_accessory/undershirt/U2 = GLOB.undershirt_list[undershirt] - if(U2) - var/u2_icon = U2.sprite_sheets && (dna.species.name in U2.sprite_sheets) ? U2.sprite_sheets[dna.species.name] : U2.icon - underwear_standing.Blend(new /icon(u2_icon, "us_[U2.icon_state]_s"), ICON_OVERLAY) - - if(socks && dna.species.clothing_flags & HAS_SOCKS) - var/datum/sprite_accessory/socks/U3 = GLOB.socks_list[socks] - if(U3) - var/u3_icon = U3.sprite_sheets && (dna.species.name in U3.sprite_sheets) ? U3.sprite_sheets[dna.species.name] : U3.icon - underwear_standing.Blend(new /icon(u3_icon, "sk_[U3.icon_state]_s"), ICON_OVERLAY) - - if(underwear_standing) - overlays_standing[UNDERWEAR_LAYER] = mutable_appearance(underwear_standing, layer = -UNDERWEAR_LAYER) - apply_overlay(UNDERWEAR_LAYER) - - if(lip_style && (LIPS in dna.species.species_traits)) - var/icon/lips = icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "lips_[lip_style]_s") - lips.Blend(lip_color, ICON_ADD) - standing += mutable_appearance(lips, layer = -BODY_LAYER) - - overlays_standing[BODY_LAYER] = standing - apply_overlay(BODY_LAYER) - //tail - update_tail_layer(0) - update_int_organs() - //head accessory - update_head_accessory(0) - //markings - update_markings(0) - //hair - update_hair(0) - update_fhair(0) - - -//MARKINGS OVERLAY -/mob/living/carbon/human/proc/update_markings(var/update_icons=1) - //Reset our markings. - remove_overlay(MARKINGS_LAYER) - - //Base icon. - var/icon/markings_standing = icon("icon" = 'icons/mob/body_accessory.dmi', "icon_state" = "accessory_none_s") - - //Body markings. - var/obj/item/organ/external/chest/chest_organ = get_organ("chest") - if(chest_organ && m_styles["body"]) - var/body_marking = m_styles["body"] - var/datum/sprite_accessory/body_marking_style = GLOB.marking_styles_list[body_marking] - if(body_marking_style && body_marking_style.species_allowed && (dna.species.name in body_marking_style.species_allowed)) - var/icon/b_marking_s = icon("icon" = body_marking_style.icon, "icon_state" = "[body_marking_style.icon_state]_s") - if(body_marking_style.do_colouration) - b_marking_s.Blend(m_colours["body"], ICON_ADD) - markings_standing.Blend(b_marking_s, ICON_OVERLAY) - //Head markings. - var/obj/item/organ/external/head/head_organ = get_organ("head") - if(head_organ && m_styles["head"]) //If the head is destroyed, forget the head markings. This prevents floating optical markings on decapitated IPCs, for example. - var/head_marking = m_styles["head"] - var/datum/sprite_accessory/head_marking_style = GLOB.marking_styles_list[head_marking] - if(head_marking_style && head_marking_style.species_allowed && (head_organ.dna.species.name in head_marking_style.species_allowed)) - var/icon/h_marking_s = icon("icon" = head_marking_style.icon, "icon_state" = "[head_marking_style.icon_state]_s") - if(head_marking_style.do_colouration) - h_marking_s.Blend(m_colours["head"], ICON_ADD) - markings_standing.Blend(h_marking_s, ICON_OVERLAY) - - overlays_standing[MARKINGS_LAYER] = mutable_appearance(markings_standing, layer = -MARKINGS_LAYER) - apply_overlay(MARKINGS_LAYER) - -//HEAD ACCESSORY OVERLAY -/mob/living/carbon/human/proc/update_head_accessory(var/update_icons=1) - //Reset our head accessory - remove_overlay(HEAD_ACCESSORY_LAYER) - remove_overlay(HEAD_ACC_OVER_LAYER) - - var/obj/item/organ/external/head/head_organ = get_organ("head") - if(!head_organ) - return - - //masks and helmets can obscure our head accessory - if((head && (head.flags & BLOCKHAIR)) || (wear_mask && (wear_mask.flags & BLOCKHAIR))) - return - - //base icons - var/icon/head_accessory_standing = new /icon('icons/mob/body_accessory.dmi',"accessory_none_s") - if(head_organ.ha_style && (head_organ.dna.species.bodyflags & HAS_HEAD_ACCESSORY)) - var/datum/sprite_accessory/head_accessory/head_accessory_style = GLOB.head_accessory_styles_list[head_organ.ha_style] - if(head_accessory_style && head_accessory_style.species_allowed) - if(head_organ.dna.species.name in head_accessory_style.species_allowed) - var/icon/head_accessory_s = new/icon("icon" = head_accessory_style.icon, "icon_state" = "[head_accessory_style.icon_state]_s") - if(head_accessory_style.do_colouration) - head_accessory_s.Blend(head_organ.headacc_colour, ICON_ADD) - head_accessory_standing = head_accessory_s //head_accessory_standing.Blend(head_accessory_s, ICON_OVERLAY) - //Having it this way preserves animations. Useful for animated antennae. - - if(head_accessory_style.over_hair) //Select which layer to use based on the properties of the head accessory style. - overlays_standing[HEAD_ACC_OVER_LAYER] = mutable_appearance(head_accessory_standing, layer = -HEAD_ACC_OVER_LAYER) - apply_overlay(HEAD_ACC_OVER_LAYER) - else - overlays_standing[HEAD_ACCESSORY_LAYER] = mutable_appearance(head_accessory_standing, layer = -HEAD_ACCESSORY_LAYER) - apply_overlay(HEAD_ACCESSORY_LAYER) - else - //warning("Invalid ha_style for [species.name]: [ha_style]") - - - -//HAIR OVERLAY -/mob/living/carbon/human/proc/update_hair(var/update_icons=1) - //Reset our hair - remove_overlay(HAIR_LAYER) - - var/obj/item/organ/external/head/head_organ = get_organ("head") - if(!head_organ) - return - - //masks and helmets can obscure our hair, unless we're a synthetic - if((head && (head.flags & BLOCKHAIR)) || (wear_mask && (wear_mask.flags & BLOCKHAIR))) - return - - //base icons - var/icon/hair_standing = new /icon('icons/mob/human_face.dmi',"bald_s") - if(head_organ.h_style && !(head && (head.flags & BLOCKHEADHAIR) && !(isSynthetic()))) - var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_full_list[head_organ.h_style] - if(hair_style && hair_style.species_allowed) - if((head_organ.dna.species.name in hair_style.species_allowed) || (head_organ.dna.species.bodyflags & ALL_RPARTS)) //If the head's species is in the list of allowed species for the hairstyle, or the head's species is one flagged to have bodies comprised wholly of cybernetics... - var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s") - if(istype(head_organ.dna.species, /datum/species/slime)) // I am el worstos - hair_s.Blend("[skin_colour]A0", ICON_AND) - else if(hair_style.do_colouration) - hair_s.Blend(head_organ.hair_colour, ICON_ADD) - - if(hair_style.secondary_theme) - var/icon/hair_secondary_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_[hair_style.secondary_theme]_s") - if(!hair_style.no_sec_colour) - hair_secondary_s.Blend(head_organ.sec_hair_colour, ICON_ADD) - hair_s.Blend(hair_secondary_s, ICON_OVERLAY) - - hair_standing = hair_s //hair_standing.Blend(hair_s, ICON_OVERLAY) - //Having it this way preserves animations. Useful for IPC screens. - else - //warning("Invalid h_style for [species.name]: [h_style]") - //hair_standing.Blend(debrained_s, ICON_OVERLAY)//how does i overlay for fish? - - overlays_standing[HAIR_LAYER] = mutable_appearance(hair_standing, layer = -HAIR_LAYER) - apply_overlay(HAIR_LAYER) - - -//FACIAL HAIR OVERLAY -/mob/living/carbon/human/proc/update_fhair(var/update_icons=1) - //Reset our facial hair - remove_overlay(FHAIR_LAYER) - remove_overlay(FHAIR_OVER_LAYER) - - var/obj/item/organ/external/head/head_organ = get_organ("head") - if(!head_organ) - return - - //masks and helmets can obscure our facial hair, unless we're a synthetic - if((head && (head.flags & BLOCKHAIR)) || (wear_mask && (wear_mask.flags & BLOCKHAIR))) - return - - //base icons - var/icon/face_standing = new /icon('icons/mob/human_face.dmi',"bald_s") - if(head_organ.f_style) - var/datum/sprite_accessory/facial_hair/facial_hair_style = GLOB.facial_hair_styles_list[head_organ.f_style] - if(facial_hair_style && facial_hair_style.species_allowed) - if((head_organ.dna.species.name in facial_hair_style.species_allowed) || (head_organ.dna.species.bodyflags & ALL_RPARTS)) //If the head's species is in the list of allowed species for the hairstyle, or the head's species is one flagged to have bodies comprised wholly of cybernetics... - var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s") - if(istype(head_organ.dna.species, /datum/species/slime)) // I am el worstos - facial_s.Blend("[skin_colour]A0", ICON_AND) - else if(facial_hair_style.do_colouration) - facial_s.Blend(head_organ.facial_colour, ICON_ADD) - - if(facial_hair_style.secondary_theme) - var/icon/facial_secondary_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_[facial_hair_style.secondary_theme]_s") - if(!facial_hair_style.no_sec_colour) - facial_secondary_s.Blend(head_organ.sec_facial_colour, ICON_ADD) - facial_s.Blend(facial_secondary_s, ICON_OVERLAY) - - face_standing.Blend(facial_s, ICON_OVERLAY) - - if(facial_hair_style.over_hair) //Select which layer to use based on the properties of the facial hair style. - overlays_standing[FHAIR_OVER_LAYER] = mutable_appearance(face_standing, layer = -FHAIR_OVER_LAYER) - apply_overlay(FHAIR_OVER_LAYER) - else - overlays_standing[FHAIR_LAYER] = mutable_appearance(face_standing, layer = -FHAIR_LAYER) - apply_overlay(FHAIR_LAYER) - else - //warning("Invalid f_style for [species.name]: [f_style]") - - - -/mob/living/carbon/human/update_mutations(var/update_icons=1) - remove_overlay(MUTATIONS_LAYER) - var/fat - if(FAT in mutations) - fat = "fat" - - var/mutable_appearance/standing = mutable_appearance('icons/effects/genetics.dmi', layer = -MUTATIONS_LAYER) - var/add_image = 0 - var/g = "m" - if(gender == FEMALE) - g = "f" - // DNA2 - Drawing underlays. - for(var/datum/dna/gene/gene in dna_genes) - if(!gene.block) - continue - if(gene.is_active(src)) - var/underlay = gene.OnDrawUnderlays(src, g, fat) - if(underlay) - standing.underlays += underlay - add_image = 1 - for(var/mut in mutations) - switch(mut) - if(LASER) - standing.overlays += "lasereyes_s" - add_image = 1 - if((COLDRES in mutations) && (HEATRES in mutations)) - standing.underlays -= "cold[fat]_s" - standing.underlays -= "fire[fat]_s" - standing.underlays += "coldfire[fat]_s" - - if(add_image) - overlays_standing[MUTATIONS_LAYER] = standing - apply_overlay(MUTATIONS_LAYER) - - -/mob/living/carbon/human/proc/update_mutantrace(var/update_icons=1) -//BS12 EDIT - var/skel = (SKELETON in mutations) - if(skel) - skeleton = 'icons/mob/human_races/r_skeleton.dmi' - else - skeleton = null - - update_hair(0) - update_fhair(0) - - -/mob/living/carbon/human/update_fire() - remove_overlay(FIRE_LAYER) - if(on_fire) - if(!overlays_standing[FIRE_LAYER]) - overlays_standing[FIRE_LAYER] = mutable_appearance(fire_dmi, fire_sprite, layer = -FIRE_LAYER) - apply_overlay(FIRE_LAYER) - -/* --------------------------------------- */ -//For legacy support. -/mob/living/carbon/human/regenerate_icons() - ..() - if(notransform) return - update_mutations(0) - update_body(0, 1) //Update the body and force limb icon regeneration. - update_hair(0) - update_head_accessory(0) - update_fhair(0) - update_mutantrace(0) - update_inv_w_uniform(0,0) - update_inv_wear_id(0) - update_inv_gloves(0,0) - update_inv_glasses(0) - update_inv_ears(0) - update_inv_shoes(0,0) - update_inv_s_store(0) - update_inv_wear_mask(0) - update_inv_head(0,0) - update_inv_belt(0) - update_inv_back(0) - update_inv_wear_suit(0) - update_inv_r_hand(0) - update_inv_l_hand(0) - update_inv_handcuffed(0) - update_inv_legcuffed(0) - update_inv_pockets(0) - update_inv_wear_pda(0) - UpdateDamageIcon(0) - force_update_limbs() - update_tail_layer(0) - overlays.Cut() // Force all overlays to regenerate - update_fire() - update_icons() -/* --------------------------------------- */ -//vvvvvv UPDATE_INV PROCS vvvvvv - -/mob/living/carbon/human/update_inv_w_uniform(var/update_icons=1) - remove_overlay(UNIFORM_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_w_uniform] - if(inv) - inv.update_icon() - - if(w_uniform && istype(w_uniform, /obj/item/clothing/under)) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - w_uniform.screen_loc = ui_iclothing //...draw the item in the inventory screen - client.screen += w_uniform //Either way, add the item to the HUD - - var/t_color = w_uniform.item_color - if(!t_color) - t_color = icon_state - - var/mutable_appearance/standing = mutable_appearance('icons/mob/uniform.dmi', "[t_color]_s", layer = -UNIFORM_LAYER) - if(FAT in mutations) - if(w_uniform.flags_size & ONESIZEFITSALL) - standing.icon = 'icons/mob/uniform_fat.dmi' - else - to_chat(src, "You burst out of \the [w_uniform]!") - unEquip(w_uniform) - return - - if(w_uniform.icon_override) - standing.icon = w_uniform.icon_override - else if(w_uniform.sprite_sheets && w_uniform.sprite_sheets[dna.species.name]) - standing.icon = w_uniform.sprite_sheets[dna.species.name] - - if(w_uniform.blood_DNA) - var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "uniformblood") - bloodsies.color = w_uniform.blood_color - standing.overlays += bloodsies - - if(w_uniform:accessories.len) //WE CHECKED THE TYPE ABOVE. THIS REALLY SHOULD BE FINE. // oh my god kys whoever made this if statement jfc :gun: - for(var/obj/item/clothing/accessory/A in w_uniform:accessories) - var/tie_color = A.item_color - if(!tie_color) - tie_color = A.icon_state - if(A.icon_override) - standing.overlays += image("icon" = A.icon_override, "icon_state" = "[A.icon_state]") - else if(A.sprite_sheets && A.sprite_sheets[dna.species.name]) - standing.overlays += image("icon" = A.sprite_sheets[dna.species.name], "icon_state" = "[A.icon_state]") - else - standing.overlays += image("icon" = 'icons/mob/ties.dmi', "icon_state" = "[tie_color]") - standing.alpha = w_uniform.alpha - standing.color = w_uniform.color - overlays_standing[UNIFORM_LAYER] = standing - else - // Automatically drop anything in store / id / belt if you're not wearing a uniform. //CHECK IF NECESARRY - for(var/obj/item/thing in list(r_store, l_store, wear_id, wear_pda, belt)) // whoever made this - if(thing) // you're a piece of fucking garbage - unEquip(thing) // why the fuck would you goddamn do this motherfucking shit - if(client) // INVENTORY CODE IN FUCKING ICON CODE - client.screen -= thing // WHAT THE FUCKING FUCK BAY GODDAMNIT - // **I FUCKING HATE YOU AAAAAAAAAA** - if(thing) // - thing.forceMove(drop_location()) // - thing.dropped(src) // - thing.layer = initial(thing.layer) - thing.plane = initial(thing.plane) - apply_overlay(UNIFORM_LAYER) - -/mob/living/carbon/human/update_inv_wear_id(var/update_icons=1) - remove_overlay(ID_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_id] - if(inv) - inv.update_icon() - - if(wear_id) - if(client && hud_used && hud_used.hud_shown) - wear_id.screen_loc = ui_id - client.screen += wear_id - - if(w_uniform && w_uniform:displays_id) - overlays_standing[ID_LAYER] = mutable_appearance('icons/mob/mob.dmi', "id", layer = -ID_LAYER) - apply_overlay(ID_LAYER) - -/mob/living/carbon/human/update_inv_gloves(var/update_icons=1) - remove_overlay(GLOVES_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_gloves] - if(inv) - inv.update_icon() - - if(gloves) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - gloves.screen_loc = ui_gloves //...draw the item in the inventory screen - client.screen += gloves //Either way, add the item to the HUD - - var/t_state = gloves.item_state - if(!t_state) t_state = gloves.icon_state - - var/mutable_appearance/standing - if(gloves.icon_override) - standing = mutable_appearance(gloves.icon_override, "[t_state]", layer = -GLOVES_LAYER) - else if(gloves.sprite_sheets && gloves.sprite_sheets[dna.species.name]) - standing = mutable_appearance(gloves.sprite_sheets[dna.species.name], "[t_state]", layer = -GLOVES_LAYER) - else - standing = mutable_appearance('icons/mob/hands.dmi', "[t_state]", layer = -GLOVES_LAYER) - - if(gloves.blood_DNA) - var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "bloodyhands") - bloodsies.color = gloves.blood_color - standing.overlays += bloodsies - overlays_standing[GLOVES_LAYER] = standing - else - if(blood_DNA) - var/mutable_appearance/bloodsies = mutable_appearance(dna.species.blood_mask, "bloodyhands", layer = -GLOVES_LAYER) - bloodsies.color = hand_blood_color - overlays_standing[GLOVES_LAYER] = bloodsies - apply_overlay(GLOVES_LAYER) - - -/mob/living/carbon/human/update_inv_glasses(var/update_icons=1) - remove_overlay(GLASSES_LAYER) - remove_overlay(GLASSES_OVER_LAYER) - remove_overlay(OVER_MASK_LAYER) - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_glasses] - if(inv) - inv.update_icon() - - if(glasses) - var/mutable_appearance/new_glasses - var/obj/item/organ/external/head/head_organ = get_organ("head") - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - glasses.screen_loc = ui_glasses //...draw the item in the inventory screen - client.screen += glasses //Either way, add the item to the HUD - - if(glasses.icon_override) - new_glasses = mutable_appearance(glasses.icon_override, "[glasses.icon_state]", layer = -GLASSES_LAYER) - else if(glasses.sprite_sheets && glasses.sprite_sheets[head_organ.dna.species.name]) - new_glasses = mutable_appearance(glasses.sprite_sheets[head_organ.dna.species.name], "[glasses.icon_state]", layer = -GLASSES_LAYER) - else - new_glasses = mutable_appearance('icons/mob/eyes.dmi', "[glasses.icon_state]", layer = -GLASSES_LAYER) - - var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_full_list[head_organ.h_style] - var/obj/item/clothing/glasses/G = glasses - if(istype(G) && G.over_mask) //If the user's used the 'wear over mask' verb on the glasses. - new_glasses.layer = -OVER_MASK_LAYER - overlays_standing[OVER_MASK_LAYER] = new_glasses - apply_overlay(OVER_MASK_LAYER) - else if(hair_style && hair_style.glasses_over) //Select which layer to use based on the properties of the hair style. Hair styles with hair that don't overhang the arms of the glasses should have glasses_over set to a positive value. - new_glasses.layer = -GLASSES_OVER_LAYER - overlays_standing[GLASSES_OVER_LAYER] = new_glasses - apply_overlay(GLASSES_OVER_LAYER) - else - overlays_standing[GLASSES_LAYER] = new_glasses - apply_overlay(GLASSES_LAYER) - - update_misc_effects() - -/mob/living/carbon/human/update_inv_ears(var/update_icons=1) - remove_overlay(EARS_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_l_ear] - if(inv) - inv.update_icon() - - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_r_ear] - if(inv) - inv.update_icon() - - if(l_ear || r_ear) - if(l_ear) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - l_ear.screen_loc = ui_l_ear //...draw the item in the inventory screen - client.screen += l_ear //Either way, add the item to the HUD - - var/t_type = l_ear.item_state - if(!t_type) - t_type = l_ear.icon_state - if(l_ear.icon_override) - t_type = "[t_type]_l" - overlays_standing[EARS_LAYER] = mutable_appearance(l_ear.icon_override, "[t_type]", layer = -EARS_LAYER) - else if(l_ear.sprite_sheets && l_ear.sprite_sheets[dna.species.name]) - overlays_standing[EARS_LAYER] = mutable_appearance(l_ear.sprite_sheets[dna.species.name], "[t_type]", layer = -EARS_LAYER) - else - overlays_standing[EARS_LAYER] = mutable_appearance('icons/mob/ears.dmi', "[t_type]", layer = -EARS_LAYER) - - if(r_ear) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - r_ear.screen_loc = ui_r_ear //...draw the item in the inventory screen - client.screen += r_ear //Either way, add the item to the HUD - - var/t_type = r_ear.item_state - if(!t_type) - t_type = r_ear.icon_state - if(r_ear.icon_override) - t_type = "[t_type]_r" - overlays_standing[EARS_LAYER] = mutable_appearance(r_ear.icon_override, "[t_type]", layer = -EARS_LAYER) - else if(r_ear.sprite_sheets && r_ear.sprite_sheets[dna.species.name]) - overlays_standing[EARS_LAYER] = mutable_appearance(r_ear.sprite_sheets[dna.species.name], "[t_type]", layer = -EARS_LAYER) - else - overlays_standing[EARS_LAYER] = mutable_appearance('icons/mob/ears.dmi', "[t_type]", layer = -EARS_LAYER) - apply_overlay(EARS_LAYER) - -/mob/living/carbon/human/update_inv_shoes(var/update_icons=1) - remove_overlay(SHOES_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_shoes] - if(inv) - inv.update_icon() - - if(shoes) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - shoes.screen_loc = ui_shoes //...draw the item in the inventory screen - client.screen += shoes //Either way, add the item to the HUD - - var/mutable_appearance/standing - if(shoes.icon_override) - standing = mutable_appearance(shoes.icon_override, "[shoes.icon_state]", layer = -SHOES_LAYER) - else if(shoes.sprite_sheets && shoes.sprite_sheets[dna.species.name]) - standing = mutable_appearance(shoes.sprite_sheets[dna.species.name], "[shoes.icon_state]", layer = -SHOES_LAYER) - else - standing = mutable_appearance('icons/mob/feet.dmi', "[shoes.icon_state]", layer = -SHOES_LAYER) - - - if(shoes.blood_DNA) - var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "shoeblood") - bloodsies.color = shoes.blood_color - standing.overlays += bloodsies - standing.alpha = shoes.alpha - standing.color = shoes.color - overlays_standing[SHOES_LAYER] = standing - else - if(feet_blood_DNA) - var/mutable_appearance/bloodsies = mutable_appearance(dna.species.blood_mask, "shoeblood", layer = -SHOES_LAYER) - bloodsies.color = feet_blood_color - overlays_standing[SHOES_LAYER] = bloodsies - apply_overlay(SHOES_LAYER) - -/mob/living/carbon/human/update_inv_s_store(var/update_icons=1) - remove_overlay(SUIT_STORE_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_s_store] - if(inv) - inv.update_icon() - - if(s_store) - if(client && hud_used && hud_used.hud_shown) - s_store.screen_loc = ui_sstore1 - client.screen += s_store - - var/t_state = s_store.item_state - if(!t_state) - t_state = s_store.icon_state - var/dmi='icons/mob/belt_mirror.dmi' - overlays_standing[SUIT_STORE_LAYER] = mutable_appearance(dmi, "[t_state]", layer = -SUIT_STORE_LAYER) - s_store.screen_loc = ui_sstore1 //TODO - apply_overlay(SUIT_STORE_LAYER) - - -/mob/living/carbon/human/update_inv_head(var/update_icons=1) - ..() - remove_overlay(HEAD_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_head] - if(inv) - inv.update_icon() - - if(head) - var/mutable_appearance/standing - if(head.icon_override) - standing = mutable_appearance(head.icon_override, "[head.icon_state]", layer = -HEAD_LAYER) - else if(head.sprite_sheets && head.sprite_sheets[dna.species.name]) - standing = mutable_appearance(head.sprite_sheets[dna.species.name], "[head.icon_state]", layer = -HEAD_LAYER) - else - standing = mutable_appearance('icons/mob/head.dmi', "[head.icon_state]", layer = -HEAD_LAYER) - - if(head.blood_DNA) - var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "helmetblood") - bloodsies.color = head.blood_color - standing.overlays += bloodsies - standing.alpha = head.alpha - standing.color = head.color - overlays_standing[HEAD_LAYER] = standing - apply_overlay(HEAD_LAYER) - -/mob/living/carbon/human/update_inv_belt(var/update_icons=1) - remove_overlay(BELT_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_belt] - if(inv) - inv.update_icon() - - if(hud_used.hud_shown && belt) - client.screen += belt - belt.screen_loc = ui_belt - - if(belt) - var/t_state = belt.item_state - if(!t_state) - t_state = belt.icon_state - - if(belt.icon_override) - t_state = "[t_state]_be" - overlays_standing[BELT_LAYER] = mutable_appearance(belt.icon_override, "[t_state]", layer = -BELT_LAYER) - else if(belt.sprite_sheets && belt.sprite_sheets[dna.species.name]) - overlays_standing[BELT_LAYER] = mutable_appearance(belt.sprite_sheets[dna.species.name], "[t_state]", layer = -BELT_LAYER) - else - overlays_standing[BELT_LAYER] = mutable_appearance('icons/mob/belt.dmi', "[t_state]", layer = -BELT_LAYER) - apply_overlay(BELT_LAYER) - - -/mob/living/carbon/human/update_inv_wear_suit(var/update_icons=1) - remove_overlay(SUIT_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_suit] - if(inv) - inv.update_icon() - - if(wear_suit && istype(wear_suit, /obj/item/clothing/suit)) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) //if the inventory is open ... - wear_suit.screen_loc = ui_oclothing //TODO //...draw the item in the inventory screen - client.screen += wear_suit //Either way, add the item to the HUD - - var/mutable_appearance/standing - if(wear_suit.icon_override) - standing = mutable_appearance(wear_suit.icon_override, "[wear_suit.icon_state]", layer = -SUIT_LAYER) - else if(wear_suit.sprite_sheets && wear_suit.sprite_sheets[dna.species.name]) - standing = mutable_appearance(wear_suit.sprite_sheets[dna.species.name], "[wear_suit.icon_state]", layer = -SUIT_LAYER) - else if(FAT in mutations) - if(wear_suit.flags_size & ONESIZEFITSALL) - standing = mutable_appearance('icons/mob/suit_fat.dmi', "[wear_suit.icon_state]", layer = -SUIT_LAYER) - else - to_chat(src, "You burst out of \the [wear_suit]!") - unEquip(wear_suit) - return - else - standing = mutable_appearance('icons/mob/suit.dmi', "[wear_suit.icon_state]", layer = -SUIT_LAYER) - - if(wear_suit.breakouttime) - drop_l_hand() - drop_r_hand() - - if(wear_suit.blood_DNA) - var/obj/item/clothing/suit/S = wear_suit - var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "[S.blood_overlay_type]blood") - bloodsies.color = wear_suit.blood_color - standing.overlays += bloodsies - - - var/special_overlays = wear_suit.special_overlays() - if(special_overlays) - standing.overlays += special_overlays - - standing.alpha = wear_suit.alpha - standing.color = wear_suit.color - overlays_standing[SUIT_LAYER] = standing - - apply_overlay(SUIT_LAYER) - update_tail_layer(0) - update_collar(0) - -/mob/living/carbon/human/update_inv_pockets() - if(client && hud_used) - var/obj/screen/inventory/inv - - inv = hud_used.inv_slots[slot_l_store] - if(inv) - inv.update_icon() - - inv = hud_used.inv_slots[slot_r_store] - if(inv) - inv.update_icon() - - if(hud_used.hud_shown) - if(l_store) - client.screen += l_store - l_store.screen_loc = ui_storage1 - - if(r_store) - client.screen += r_store - r_store.screen_loc = ui_storage2 - -/mob/living/carbon/human/update_inv_wear_pda() - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_pda] - if(inv) - inv.update_icon() - - if(wear_pda) - client.screen += wear_pda - wear_pda.screen_loc = ui_pda - -/mob/living/carbon/human/update_inv_wear_mask(var/update_icons = 1) - ..() - remove_overlay(FACEMASK_LAYER) - if(client && hud_used) - var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_mask] - if(inv) - inv.update_icon() - if(wear_mask && (istype(wear_mask, /obj/item/clothing/mask) || istype(wear_mask, /obj/item/clothing/accessory))) - if(!(slot_wear_mask in check_obscured_slots())) - var/obj/item/organ/external/head/head_organ = get_organ("head") - var/datum/sprite_accessory/alt_heads/alternate_head - if(head_organ.alt_head && head_organ.alt_head != "None") - alternate_head = GLOB.alt_heads_list[head_organ.alt_head] - - var/mutable_appearance/standing - var/icon/mask_icon = new(wear_mask.icon) - if(wear_mask.icon_override) - mask_icon = new(wear_mask.icon_override) - standing = mutable_appearance(wear_mask.icon_override, "[wear_mask.icon_state][(alternate_head && ("[wear_mask.icon_state]_[alternate_head.suffix]" in mask_icon.IconStates())) ? "_[alternate_head.suffix]" : ""]", layer = -FACEMASK_LAYER) - else if(wear_mask.sprite_sheets && wear_mask.sprite_sheets[dna.species.name]) - mask_icon = new(wear_mask.sprite_sheets[dna.species.name]) - standing = mutable_appearance(wear_mask.sprite_sheets[dna.species.name], "[wear_mask.icon_state][(alternate_head && ("[wear_mask.icon_state]_[alternate_head.suffix]" in mask_icon.IconStates())) ? "_[alternate_head.suffix]" : ""]", layer = -FACEMASK_LAYER) - else - standing = mutable_appearance('icons/mob/mask.dmi', "[wear_mask.icon_state][(alternate_head && ("[wear_mask.icon_state]_[alternate_head.suffix]" in mask_icon.IconStates())) ? "_[alternate_head.suffix]" : ""]", layer = -FACEMASK_LAYER) - - if(!istype(wear_mask, /obj/item/clothing/mask/cigarette) && wear_mask.blood_DNA) - var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "maskblood") - bloodsies.color = wear_mask.blood_color - standing.overlays += bloodsies - - standing.alpha = wear_mask.alpha - standing.color = wear_mask.color - overlays_standing[FACEMASK_LAYER] = standing - apply_overlay(FACEMASK_LAYER) - - -/mob/living/carbon/human/update_inv_back(var/update_icons=1) - ..() - remove_overlay(BACK_LAYER) - if(back) - //determine the icon to use - var/mutable_appearance/standing - if(back.icon_override) - standing = mutable_appearance(back.icon_override, "[back.icon_state]", layer = -BACK_LAYER) - else if(istype(back, /obj/item/rig)) - //If this is a rig and a mob_icon is set, it will take species into account in the rig update_icon() proc. - var/obj/item/rig/rig = back - standing = rig.mob_icon - else if(back.sprite_sheets && back.sprite_sheets[dna.species.name]) - standing = mutable_appearance(back.sprite_sheets[dna.species.name], "[back.icon_state]", layer = -BACK_LAYER) - else - standing = mutable_appearance('icons/mob/back.dmi', "[back.icon_state]", layer = -BACK_LAYER) - - //create the image - standing.alpha = back.alpha - standing.color = back.color - overlays_standing[BACK_LAYER] = standing - apply_overlay(BACK_LAYER) - -/mob/living/carbon/human/update_inv_handcuffed(var/update_icons=1) - remove_overlay(HANDCUFF_LAYER) - if(handcuffed) - if(istype(handcuffed, /obj/item/restraints/handcuffs/pinkcuffs)) - overlays_standing[HANDCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "pinkcuff1", layer = -HANDCUFF_LAYER) - else - overlays_standing[HANDCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "handcuff1", layer = -HANDCUFF_LAYER) - apply_overlay(HANDCUFF_LAYER) - -/mob/living/carbon/human/update_inv_legcuffed(var/update_icons=1) - remove_overlay(LEGCUFF_LAYER) - clear_alert("legcuffed") - if(legcuffed) - overlays_standing[LEGCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "legcuff1", layer = -LEGCUFF_LAYER) - throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed) - if(m_intent != MOVE_INTENT_WALK) - m_intent = MOVE_INTENT_WALK - if(hud_used && hud_used.move_intent) - hud_used.move_intent.icon_state = "walking" - apply_overlay(LEGCUFF_LAYER) - - -/mob/living/carbon/human/update_inv_r_hand(var/update_icons=1) - ..() - remove_overlay(R_HAND_LAYER) - if(r_hand) - var/t_state = r_hand.item_state - if(!t_state) - t_state = r_hand.icon_state - - var/mutable_appearance/standing - if(r_hand.sprite_sheets_inhand && r_hand.sprite_sheets_inhand[dna.species.name]) - t_state = "[t_state]_r" - standing = mutable_appearance(r_hand.sprite_sheets_inhand[dna.species.name], "[t_state]", layer = -R_HAND_LAYER) - else - standing = mutable_appearance(r_hand.righthand_file, "[t_state]", layer = -R_HAND_LAYER) - standing = center_image(standing, r_hand.inhand_x_dimension, r_hand.inhand_y_dimension) - overlays_standing[R_HAND_LAYER] = standing - apply_overlay(R_HAND_LAYER) - - -/mob/living/carbon/human/update_inv_l_hand(var/update_icons=1) - ..() - remove_overlay(L_HAND_LAYER) - if(l_hand) - var/t_state = l_hand.item_state - if(!t_state) - t_state = l_hand.icon_state - - var/mutable_appearance/standing - if(l_hand.sprite_sheets_inhand && l_hand.sprite_sheets_inhand[dna.species.name]) - t_state = "[t_state]_l" - standing = mutable_appearance(l_hand.sprite_sheets_inhand[dna.species.name], "[t_state]", layer = -L_HAND_LAYER) - else - standing = mutable_appearance(l_hand.lefthand_file, "[t_state]", layer = -L_HAND_LAYER) - standing = center_image(standing, l_hand.inhand_x_dimension, l_hand.inhand_y_dimension) - overlays_standing[L_HAND_LAYER] = standing - apply_overlay(L_HAND_LAYER) - -//human HUD updates for items in our inventory - -//update whether our head item appears on our hud. -/mob/living/carbon/human/update_hud_head(obj/item/I) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) - I.screen_loc = ui_head - client.screen += I - -//update whether our mask item appears on our hud. -/mob/living/carbon/human/update_hud_wear_mask(obj/item/I) - if(client && hud_used && hud_used.hud_shown) - if(hud_used.inventory_shown) - I.screen_loc = ui_mask - client.screen += I - -//update whether our back item appears on our hud. -/mob/living/carbon/human/update_hud_back(obj/item/I) - if(client && hud_used && hud_used.hud_shown) - I.screen_loc = ui_back - client.screen += I - - -/mob/living/carbon/human/proc/update_tail_layer(var/update_icons=1) - remove_overlay(TAIL_UNDERLIMBS_LAYER) // SEW direction icons, overlayed by LIMBS_LAYER. - remove_overlay(TAIL_LAYER) /* This will be one of two things: - If the species' tail is overlapped by limbs, this will be only the N direction icon so tails - can still appear on the outside of uniforms and such. - Otherwise, since the user's tail isn't overlapped by limbs, it will be a full icon with all directions. */ - - var/icon/tail_marking_icon - var/datum/sprite_accessory/body_markings/tail/tail_marking_style - if(m_styles["tail"] != "None" && (dna.species.bodyflags & HAS_TAIL_MARKINGS)) - var/tail_marking = m_styles["tail"] - tail_marking_style = GLOB.marking_styles_list[tail_marking] - tail_marking_icon = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]_s") - tail_marking_icon.Blend(m_colours["tail"], ICON_ADD) - - if(body_accessory) - if(body_accessory.try_restrictions(src)) - var/icon/accessory_s = new/icon("icon" = body_accessory.icon, "icon_state" = body_accessory.icon_state) - if(dna.species.bodyflags & HAS_SKIN_COLOR) - accessory_s.Blend(skin_colour, body_accessory.blend_mode) - if(tail_marking_icon && (body_accessory.name in tail_marking_style.tails_allowed)) - accessory_s.Blend(tail_marking_icon, ICON_OVERLAY) - if((!body_accessory || istype(body_accessory, /datum/body_accessory/tail)) && dna.species.bodyflags & TAIL_OVERLAPPED) // If the player has a species whose tail is overlapped by limbs... (having a non-tail body accessory like the snake body will override this) - // Gives the underlimbs layer SEW direction icons since it's overlayed by limbs and just about everything else anyway. - var/icon/under = new/icon("icon" = 'icons/mob/body_accessory.dmi', "icon_state" = "accessory_none_s") - under.Insert(new/icon(accessory_s, dir=SOUTH), dir=SOUTH) - under.Insert(new/icon(accessory_s, dir=EAST), dir=EAST) - under.Insert(new/icon(accessory_s, dir=WEST), dir=WEST) - - var/mutable_appearance/underlimbs = mutable_appearance(under, layer = -TAIL_UNDERLIMBS_LAYER) - underlimbs.pixel_x = body_accessory.pixel_x_offset - underlimbs.pixel_y = body_accessory.pixel_y_offset - overlays_standing[TAIL_UNDERLIMBS_LAYER] = underlimbs - - // Creates a blank icon, and copies accessory_s' north direction sprite into it - // before passing that to the tail layer that overlays uniforms and such. - var/icon/over = new/icon("icon" = 'icons/mob/body_accessory.dmi', "icon_state" = "accessory_none_s") - over.Insert(new/icon(accessory_s, dir=NORTH), dir=NORTH) - - var/mutable_appearance/tail = mutable_appearance(over, layer = -TAIL_LAYER) - tail.pixel_x = body_accessory.pixel_x_offset - tail.pixel_y = body_accessory.pixel_y_offset - overlays_standing[TAIL_LAYER] = tail - else // Otherwise, since the user's tail isn't overlapped by limbs, go ahead and use default icon generation. - var/mutable_appearance/tail = mutable_appearance(accessory_s, layer = -TAIL_LAYER) - tail.pixel_x = body_accessory.pixel_x_offset - tail.pixel_y = body_accessory.pixel_y_offset - overlays_standing[TAIL_LAYER] = tail - - else if(tail && dna.species.bodyflags & HAS_TAIL) //no tailless tajaran - if(!wear_suit || !(wear_suit.flags_inv & HIDETAIL)) - var/icon/tail_s = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[tail]_s") - if(dna.species.bodyflags & HAS_SKIN_COLOR) - tail_s.Blend(skin_colour, ICON_ADD) - if(tail_marking_icon && !tail_marking_style.tails_allowed) - tail_s.Blend(tail_marking_icon, ICON_OVERLAY) - if((!body_accessory || istype(body_accessory, /datum/body_accessory/tail)) && dna.species.bodyflags & TAIL_OVERLAPPED) // If the player has a species whose tail is overlapped by limbs... (having a non-tail body accessory like the snake body will override this) - // Gives the underlimbs layer SEW direction icons since it's overlayed by limbs and just about everything else anyway. - var/icon/under = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "blank") - under.Insert(new/icon(tail_s, dir=SOUTH), dir=SOUTH) - under.Insert(new/icon(tail_s, dir=EAST), dir=EAST) - under.Insert(new/icon(tail_s, dir=WEST), dir=WEST) - - overlays_standing[TAIL_UNDERLIMBS_LAYER] = mutable_appearance(under, layer = -TAIL_UNDERLIMBS_LAYER) - - // Creates a blank icon, and copies accessory_s' north direction sprite into it before passing that to the tail layer that overlays uniforms and such. - var/icon/over = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "blank") - over.Insert(new/icon(tail_s, dir=NORTH), dir=NORTH) - - overlays_standing[TAIL_LAYER] = mutable_appearance(over, layer = -TAIL_LAYER) - else // Otherwise, since the user's tail isn't overlapped by limbs, go ahead and use default icon generation. - overlays_standing[TAIL_LAYER] = mutable_appearance(tail_s, layer = -TAIL_LAYER) - apply_overlay(TAIL_LAYER) - apply_overlay(TAIL_UNDERLIMBS_LAYER) - -/mob/living/carbon/human/proc/start_tail_wagging(var/update_icons=1) - remove_overlay(TAIL_UNDERLIMBS_LAYER) // SEW direction icons, overlayed by LIMBS_LAYER. - remove_overlay(TAIL_LAYER) /* This will be one of two things: - If the species' tail is overlapped by limbs, this will be only the N direction icon so tails - can still appear on the outside of uniforms and such. - Otherwise, since the user's tail isn't overlapped by limbs, it will be a full icon with all directions. */ - - var/icon/tail_marking_icon - var/datum/sprite_accessory/body_markings/tail/tail_marking_style - if(m_styles["tail"] != "None" && (dna.species.bodyflags & HAS_TAIL_MARKINGS)) - var/tail_marking = m_styles["tail"] - tail_marking_style = GLOB.marking_styles_list[tail_marking] - tail_marking_icon = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]w_s") - tail_marking_icon.Blend(m_colours["tail"], ICON_ADD) - - if(body_accessory) - var/icon/accessory_s = new/icon("icon" = body_accessory.get_animated_icon(), "icon_state" = body_accessory.get_animated_icon_state()) - if(dna.species.bodyflags & HAS_SKIN_COLOR) - accessory_s.Blend(skin_colour, body_accessory.blend_mode) - if(tail_marking_icon && (body_accessory.name in tail_marking_style.tails_allowed)) - accessory_s.Blend(tail_marking_icon, ICON_OVERLAY) - if((!body_accessory || istype(body_accessory, /datum/body_accessory/tail)) && dna.species.bodyflags & TAIL_OVERLAPPED) // If the player has a species whose tail is overlapped by limbs... (having a non-tail body accessory like the snake body will override this) - // Gives the underlimbs layer SEW direction icons since it's overlayed by limbs and just about everything else anyway. - var/icon/under = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "Vulpkanin_tail_delay") - if(body_accessory.allowed_species && (dna.species.name in body_accessory.allowed_species)) - under = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[dna.species.name]_tail_delay") - under.Insert(new/icon(accessory_s, dir=SOUTH), dir=SOUTH) - under.Insert(new/icon(accessory_s, dir=EAST), dir=EAST) - under.Insert(new/icon(accessory_s, dir=WEST), dir=WEST) - - var/mutable_appearance/underlimbs = mutable_appearance(under, layer = -TAIL_UNDERLIMBS_LAYER) - underlimbs.pixel_x = body_accessory.pixel_x_offset - underlimbs.pixel_y = body_accessory.pixel_y_offset - overlays_standing[TAIL_UNDERLIMBS_LAYER] = underlimbs - - // Creates a blank icon, and copies accessory_s' north direction sprite into it before passing that to the tail layer that overlays uniforms and such. - var/icon/over = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "Vulpkanin_tail_delay") - if(body_accessory.allowed_species && (dna.species.name in body_accessory.allowed_species)) // If the user's species is in the list of allowed species for the currently selected body accessory, use the appropriate animation timing blank - over = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[dna.species.name]_tail_delay") - over.Insert(new/icon(accessory_s, dir=NORTH), dir=NORTH) - - var/mutable_appearance/tail = mutable_appearance(over, layer = -TAIL_LAYER) - tail.pixel_x = body_accessory.pixel_x_offset - tail.pixel_y = body_accessory.pixel_y_offset - overlays_standing[TAIL_LAYER] = tail - else // Otherwise, since the user's tail isn't overlapped by limbs, go ahead and use default icon generation. - var/mutable_appearance/tail = mutable_appearance(accessory_s, layer = -TAIL_LAYER) - tail.pixel_x = body_accessory.pixel_x_offset - tail.pixel_y = body_accessory.pixel_y_offset - overlays_standing[TAIL_LAYER] = tail - - else if(tail && dna.species.bodyflags & HAS_TAIL) - var/icon/tailw_s = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[tail]w_s") - if(dna.species.bodyflags & HAS_SKIN_COLOR) - tailw_s.Blend(skin_colour, ICON_ADD) - if(tail_marking_icon && !tail_marking_style.tails_allowed) - tailw_s.Blend(tail_marking_icon, ICON_OVERLAY) - if((!body_accessory || istype(body_accessory, /datum/body_accessory/tail)) && dna.species.bodyflags & TAIL_OVERLAPPED) // If the player has a species whose tail is overlapped by limbs... (having a non-tail body accessory like the snake body will override this) - // Gives the underlimbs layer SEW direction icons since it's overlayed by limbs and just about everything else anyway. - var/icon/under = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[dna.species.name]_tail_delay") - under.Insert(new/icon(tailw_s, dir=SOUTH), dir=SOUTH) - under.Insert(new/icon(tailw_s, dir=EAST), dir=EAST) - under.Insert(new/icon(tailw_s, dir=WEST), dir=WEST) - - overlays_standing[TAIL_UNDERLIMBS_LAYER] = mutable_appearance(under, layer = -TAIL_UNDERLIMBS_LAYER) - - // Creates a blank icon, and copies accessory_s' north direction sprite into it before passing that to the tail layer that overlays uniforms and such. - var/icon/over = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[dna.species.name]_tail_delay") - over.Insert(new/icon(tailw_s, dir=NORTH), dir=NORTH) - - overlays_standing[TAIL_LAYER] = mutable_appearance(over, layer = -TAIL_LAYER) - else // Otherwise, since the user's tail isn't overlapped by limbs, go ahead and use default icon generation. - overlays_standing[TAIL_LAYER] = mutable_appearance(tailw_s, layer = -TAIL_LAYER) - apply_overlay(TAIL_LAYER) - apply_overlay(TAIL_UNDERLIMBS_LAYER) - -/mob/living/carbon/human/proc/stop_tail_wagging(var/update_icons=1) - remove_overlay(TAIL_UNDERLIMBS_LAYER) - remove_overlay(TAIL_LAYER) - update_tail_layer(update_icons) //just trigger a full update for normal stationary sprites - -/mob/living/carbon/human/proc/update_int_organs() - remove_overlay(INTORGAN_LAYER) - - var/list/standing = list() - for(var/organ in internal_organs) - var/obj/item/organ/internal/I = organ - var/render = I.render() - if(render) - standing += render - - overlays_standing[INTORGAN_LAYER] = standing - apply_overlay(INTORGAN_LAYER) - -/mob/living/carbon/human/handle_transform_change() - ..() - update_tail_layer() - -//Adds a collar overlay above the helmet layer if the suit has one -// Suit needs an identically named sprite in icons/mob/collar.dmi -// For suits with sprite_sheets, an identically named sprite needs to exist in a file like this icons/mob/species/[species_name_here]/collar.dmi. -/mob/living/carbon/human/proc/update_collar(var/update_icons=1) - remove_overlay(COLLAR_LAYER) - var/icon/C = new('icons/mob/collar.dmi') - var/mutable_appearance/standing = null - - if(wear_suit) - if(wear_suit.icon_override) - var/icon_path = "[wear_suit.icon_override]" - icon_path = "[copytext(icon_path, 1, findtext(icon_path, "/suit.dmi"))]/collar.dmi" //If this file doesn't exist, the end result is that COLLAR_LAYER will be unchanged (empty). - if(fexists(icon_path)) //Just ensuring the nonexistance of a file with the above path won't cause a runtime. - var/icon/icon_file = new(icon_path) - if(wear_suit.icon_state in icon_file.IconStates()) - standing = mutable_appearance(icon_file, "[wear_suit.icon_state]", layer = -COLLAR_LAYER) - else if(wear_suit.sprite_sheets && wear_suit.sprite_sheets[dna.species.name]) - var/icon_path = "[wear_suit.sprite_sheets[dna.species.name]]" - icon_path = "[copytext(icon_path, 1, findtext(icon_path, "/suit.dmi"))]/collar.dmi" //If this file doesn't exist, the end result is that COLLAR_LAYER will be unchanged (empty). - if(fexists(icon_path)) //Just ensuring the nonexistance of a file with the above path won't cause a runtime. - var/icon/icon_file = new(icon_path) - if(wear_suit.icon_state in icon_file.IconStates()) - standing = mutable_appearance(icon_file, "[wear_suit.icon_state]", layer = -COLLAR_LAYER) - else - if(wear_suit.icon_state in C.IconStates()) - standing = mutable_appearance(C, "[wear_suit.icon_state]", layer = -COLLAR_LAYER) - - overlays_standing[COLLAR_LAYER] = standing - apply_overlay(COLLAR_LAYER) - -/mob/living/carbon/human/proc/update_misc_effects() - remove_overlay(MISC_LAYER) - - //Begin appending miscellaneous effects. - if(eyes_shine()) - overlays_standing[MISC_LAYER] = get_eye_shine() //Image layer is specified in get_eye_shine() proc as LIGHTING_LAYER + 1. - - apply_overlay(MISC_LAYER) - -/mob/living/carbon/human/admin_Freeze(client/admin, skip_overlays = TRUE) - . = ..() - overlays_standing[FROZEN_LAYER] = mutable_appearance(frozen, layer = -FROZEN_LAYER) - apply_overlay(FROZEN_LAYER) - -/mob/living/carbon/human/admin_unFreeze(client/admin, skip_overlays = TRUE) - . = ..() - remove_overlay(FROZEN_LAYER) - - -/mob/living/carbon/human/proc/force_update_limbs() - for(var/obj/item/organ/external/O in bodyparts) - O.sync_colour_to_human(src) - update_body(0) - -/mob/living/carbon/human/proc/get_overlays_copy(list/unwantedLayers) - var/list/out = new - for(var/i=1;i<=TOTAL_LAYERS;i++) - if(overlays_standing[i]) - if(i in unwantedLayers) - continue - out += overlays_standing[i] - return out - -/mob/living/carbon/human/proc/generate_icon_render_key() - var/husk = (HUSK in mutations) - var/fat = (FAT in mutations) - var/hulk = (HULK in mutations) - var/skeleton = (SKELETON in mutations) - - . = "" - - var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) - if(eyes) - . += "[eyes.eye_colour]" - else - . += "#000000" - - for(var/organ_tag in dna.species.has_limbs) - var/obj/item/organ/external/part = bodyparts_by_name[organ_tag] - if(isnull(part)) - . += "0" - else if(part.is_robotic()) - . += "2[part.model ? "-[part.model]" : ""]" - else if(part.status & ORGAN_DEAD) - . += "3" - else - . += "1" - - if(part) - var/datum/species/S = GLOB.all_species[part.dna.species.name] - . += "[S.race_key]" - . += "[part.dna.GetUIState(DNA_UI_GENDER)]" - . += "[part.dna.GetUIValue(DNA_UI_SKIN_TONE)]" - if(part.s_col) - . += "[part.s_col]" - if(part.s_tone) - . += "[part.s_tone]" - - . = "[.][!!husk][!!fat][!!hulk][!!skeleton]" \ No newline at end of file +/* + Global associative list for caching humanoid icons. + Index format m or f, followed by a string of 0 and 1 to represent bodyparts followed by husk fat hulk skeleton 1 or 0. + TODO: Proper documentation + icon_key is [species.race_key][g][husk][fat][hulk][skeleton][s_tone] +*/ +var/global/list/human_icon_cache = list() + + /////////////////////// + //UPDATE_ICONS SYSTEM// + /////////////////////// +/* +Calling this a system is perhaps a bit trumped up. It is essentially update_clothing dismantled into its +core parts. The key difference is that when we generate overlays we do not generate either lying or standing +versions. Instead, we generate both and store them in two fixed-length lists, both using the same list-index +(The indexes are in update_icons.dm): Each list for humans is (at the time of writing) of length 19. +This will hopefully be reduced as the system is refined. + + var/overlays_lying[19] //For the lying down stance + var/overlays_standing[19] //For the standing stance + +When we call update_icons, the 'lying' variable is checked and then the appropriate list is assigned to our overlays! +That in itself uses a tiny bit more memory (no more than all the ridiculous lists the game has already mind you). + +On the other-hand, it should be very CPU cheap in comparison to the old system. +In the old system, we updated all our overlays every life() call, even if we were standing still inside a crate! +or dead!. 25ish overlays, all generated from scratch every second for every xeno/human/monkey and then applied. +More often than not update_clothing was being called a few times in addition to that! CPU was not the only issue, +all those icons had to be sent to every client. So really the cost was extremely cumulative. To the point where +update_clothing would frequently appear in the top 10 most CPU intensive procs during profiling. + +Another feature of this new system is that our lists are indexed. This means we can update specific overlays! +So we only regenerate icons when we need them to be updated! This is the main saving for this system. + +In practice this means that: + everytime you fall over, we just switch between precompiled lists. Which is fast and cheap. + Everytime you do something minor like take a pen out of your pocket, we only update the in-hand overlay + etc... + + +There are several things that need to be remembered: + +> Whenever we do something that should cause an overlay to update (which doesn't use standard procs + ( i.e. you do something like l_hand = /obj/item/something new(src) ) + You will need to call the relevant update_inv_* proc: + update_inv_head() + update_inv_wear_suit() + update_inv_gloves() + update_inv_shoes() + update_inv_w_uniform() + update_inv_glasse() + update_inv_l_hand() + update_inv_r_hand() + update_inv_belt() + update_inv_wear_id() + update_inv_ears() + update_inv_s_store() + update_inv_pockets() + update_inv_back() + update_inv_handcuffed() + update_inv_wear_mask() + + All of these are named after the variable they update from. They are defined at the mob/ level like + update_clothing was, so you won't cause undefined proc runtimes with usr.update_inv_wear_id() if the usr is a + slime etc. Instead, it'll just return without doing any work. So no harm in calling it for slimes and such. + + +> There are also these special cases: + update_mutations() //handles updating your appearance for certain mutations. e.g TK head-glows + update_mutantrace() //handles updating your appearance after setting the mutantrace var + UpdateDamageIcon() //handles damage overlays for brute/burn damage //(will rename this when I geta round to it) + update_body() //Handles updating your mob's icon to reflect their gender/race/complexion etc + update_hair() //Handles updating your hair overlay (used to be update_face, but mouth and + ...eyes were merged into update_body) + +> All of these procs update our overlays_lying and overlays_standing, and then call update_icons() by default. + If you wish to update several overlays at once, you can set the argument to 0 to disable the update and call + it manually: + e.g. + update_inv_head(0) + update_inv_l_hand(0) + update_inv_r_hand() //<---calls update_icons() + + or equivillantly: + update_inv_head(0) + update_inv_l_hand(0) + update_inv_r_hand(0) + update_icons() + +> If you need to update all overlays you can use regenerate_icons(). it works exactly like update_clothing used to. + +> I reimplimented an old unused variable which was in the code called (coincidentally) var/update_icon + It can be used as another method of triggering regenerate_icons(). It's basically a flag that when set to non-zero + will call regenerate_icons() at the next life() call and then reset itself to 0. + The idea behind it is icons are regenerated only once, even if multiple events requested it. + +This system is confusing and is still a WIP. It's primary goal is speeding up the controls of the game whilst +reducing processing costs. So please bear with me while I iron out the kinks. It will be worth it, I promise. +If I can eventually free var/lying stuff from the life() process altogether, stuns/death/status stuff +will become less affected by lag-spikes and will be instantaneous! :3 + +If you have any questions/constructive-comments/bugs-to-report/or have a massivly devestated butt... +Please contact me on #coderbus IRC. ~Carn x +*/ + +/mob/living/carbon/human + var/list/overlays_standing[TOTAL_LAYERS] + var/previous_damage_appearance // store what the body last looked like, so we only have to update it if something changed + var/icon/skeleton + var/list/cached_standing_overlays = list() // List of everything currently in a human's actual overlays + +/mob/living/carbon/human/proc/apply_overlay(cache_index) + if((. = overlays_standing[cache_index])) + add_overlay(.) + +/mob/living/carbon/human/proc/remove_overlay(cache_index) + var/I = overlays_standing[cache_index] + if(I) + cut_overlay(I) + overlays_standing[cache_index] = null + + +var/global/list/damage_icon_parts = list() + +//DAMAGE OVERLAYS +//constructs damage icon for each organ from mask * damage field and saves it in our overlays_ lists +/mob/living/carbon/human/UpdateDamageIcon(var/update_icons=1) + // first check whether something actually changed about damage appearance + var/damage_appearance = "" + + for(var/obj/item/organ/external/O in bodyparts) + damage_appearance += O.damage_state + + if(damage_appearance == previous_damage_appearance) + // nothing to do here + return + + previous_damage_appearance = damage_appearance + + remove_overlay(H_DAMAGE_LAYER) + var/mutable_appearance/damage_overlay = mutable_appearance(dna.species.damage_overlays, "00", layer = -H_DAMAGE_LAYER) + overlays_standing[H_DAMAGE_LAYER] = damage_overlay + + // blend the individual damage states with our icons + for(var/D in bodyparts) + var/obj/item/organ/external/E = D + E.update_icon() + if(E.damage_state == "00") + continue + + var/icon/DI + var/cache_index = "[E.damage_state]/[E.icon_name]/[dna.species.blood_color]/[dna.species.name]" + + if(damage_icon_parts[cache_index] == null) + DI = new /icon(dna.species.damage_overlays, E.damage_state) // the damage icon for whole human + DI.Blend(new /icon(dna.species.damage_mask, E.icon_name), ICON_MULTIPLY) // mask with this organ's pixels + DI.Blend(dna.species.blood_color, ICON_MULTIPLY) + damage_icon_parts[cache_index] = DI + else + DI = damage_icon_parts[cache_index] + damage_overlay.overlays += DI + + apply_overlay(H_DAMAGE_LAYER) + + +//BASE MOB SPRITE +/mob/living/carbon/human/proc/update_body(var/update_icons=1, var/rebuild_base=0) + remove_overlay(BODY_LAYER) + remove_overlay(LIMBS_LAYER) // So we don't get the old species' sprite splatted on top of the new one's + remove_overlay(UNDERWEAR_LAYER) + + var/husk_color_mod = rgb(96, 88, 80) + var/hulk_color_mod = rgb(48, 224, 40) + + var/husk = (HUSK in mutations) + var/hulk = (HULK in mutations) + var/skeleton = (SKELETON in mutations) + + if(dna.species && dna.species.bodyflags & HAS_ICON_SKIN_TONE) + dna.species.updatespeciescolor(src) + + //CACHING: Generate an index key from visible bodyparts. + //0 = destroyed, 1 = normal, 2 = robotic, 3 = necrotic. + //Create a new, blank icon for our mob to use. + if(stand_icon) + qdel(stand_icon) + + update_misc_effects() + stand_icon = new (dna.species.icon_template ? dna.species.icon_template : 'icons/mob/human.dmi', "blank") + var/list/standing = list() + var/icon_key = generate_icon_render_key() + + var/mutable_appearance/base + if(human_icon_cache[icon_key] && !rebuild_base) + base = human_icon_cache[icon_key] + standing += base + else + var/icon/base_icon + //BEGIN CACHED ICON GENERATION. + var/obj/item/organ/external/chest = get_organ("chest") + base_icon = chest.get_icon(skeleton) + + for(var/obj/item/organ/external/part in bodyparts) + var/icon/temp = part.get_icon(skeleton) + //That part makes left and right legs drawn topmost and lowermost when human looks WEST or EAST + //And no change in rendering for other parts (they icon_position is 0, so goes to 'else' part) + if(part.icon_position & (LEFT | RIGHT)) + var/icon/temp2 = new('icons/mob/human.dmi',"blank") + temp2.Insert(new/icon(temp,dir=NORTH),dir=NORTH) + temp2.Insert(new/icon(temp,dir=SOUTH),dir=SOUTH) + if(!(part.icon_position & LEFT)) + temp2.Insert(new/icon(temp,dir=EAST),dir=EAST) + if(!(part.icon_position & RIGHT)) + temp2.Insert(new/icon(temp,dir=WEST),dir=WEST) + base_icon.Blend(temp2, ICON_OVERLAY) + if(part.icon_position & LEFT) + temp2.Insert(new/icon(temp,dir=EAST),dir=EAST) + if(part.icon_position & RIGHT) + temp2.Insert(new/icon(temp,dir=WEST),dir=WEST) + base_icon.Blend(temp2, ICON_UNDERLAY) + else + base_icon.Blend(temp, ICON_OVERLAY) + + if(!skeleton) + if(isgolem(src)) + var/datum/species/golem/G = src.dna.species + if(G.golem_colour) + base_icon.ColorTone(G.golem_colour) + if(husk) + base_icon.ColorTone(husk_color_mod) + else if(hulk) + var/list/tone = ReadRGB(hulk_color_mod) + base_icon.MapColors(rgb(tone[1],0,0),rgb(0,tone[2],0),rgb(0,0,tone[3])) + + //Handle husk overlay. + if(husk && ("overlay_husk" in icon_states(chest.icobase))) + var/icon/mask = new(base_icon) + var/icon/husk_over = new(chest.icobase,"overlay_husk") + mask.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0) + husk_over.Blend(mask, ICON_ADD) + base_icon.Blend(husk_over, ICON_OVERLAY) + + var/mutable_appearance/new_base = mutable_appearance(base_icon, layer = -LIMBS_LAYER) + human_icon_cache[icon_key] = new_base + standing += new_base + + //END CACHED ICON GENERATION. + + overlays_standing[LIMBS_LAYER] = standing + apply_overlay(LIMBS_LAYER) + + //Underwear + var/icon/underwear_standing = new /icon('icons/mob/underwear.dmi', "nude") + if(underwear && dna.species.clothing_flags & HAS_UNDERWEAR) + var/datum/sprite_accessory/underwear/U = GLOB.underwear_list[underwear] + if(U) + var/u_icon = U.sprite_sheets && (dna.species.name in U.sprite_sheets) ? U.sprite_sheets[dna.species.name] : U.icon //Species-fit the undergarment. + underwear_standing.Blend(new /icon(u_icon, "uw_[U.icon_state]_s"), ICON_OVERLAY) + + if(undershirt && dna.species.clothing_flags & HAS_UNDERSHIRT) + var/datum/sprite_accessory/undershirt/U2 = GLOB.undershirt_list[undershirt] + if(U2) + var/u2_icon = U2.sprite_sheets && (dna.species.name in U2.sprite_sheets) ? U2.sprite_sheets[dna.species.name] : U2.icon + underwear_standing.Blend(new /icon(u2_icon, "us_[U2.icon_state]_s"), ICON_OVERLAY) + + if(socks && dna.species.clothing_flags & HAS_SOCKS) + var/datum/sprite_accessory/socks/U3 = GLOB.socks_list[socks] + if(U3) + var/u3_icon = U3.sprite_sheets && (dna.species.name in U3.sprite_sheets) ? U3.sprite_sheets[dna.species.name] : U3.icon + underwear_standing.Blend(new /icon(u3_icon, "sk_[U3.icon_state]_s"), ICON_OVERLAY) + + if(underwear_standing) + overlays_standing[UNDERWEAR_LAYER] = mutable_appearance(underwear_standing, layer = -UNDERWEAR_LAYER) + apply_overlay(UNDERWEAR_LAYER) + + if(lip_style && (LIPS in dna.species.species_traits)) + var/icon/lips = icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "lips_[lip_style]_s") + lips.Blend(lip_color, ICON_ADD) + standing += mutable_appearance(lips, layer = -BODY_LAYER) + + overlays_standing[BODY_LAYER] = standing + apply_overlay(BODY_LAYER) + //tail + update_tail_layer(0) + update_int_organs() + //head accessory + update_head_accessory(0) + //markings + update_markings(0) + //hair + update_hair(0) + update_fhair(0) + + +//MARKINGS OVERLAY +/mob/living/carbon/human/proc/update_markings(var/update_icons=1) + //Reset our markings. + remove_overlay(MARKINGS_LAYER) + + //Base icon. + var/icon/markings_standing = icon("icon" = 'icons/mob/body_accessory.dmi', "icon_state" = "accessory_none_s") + + //Body markings. + var/obj/item/organ/external/chest/chest_organ = get_organ("chest") + if(chest_organ && m_styles["body"]) + var/body_marking = m_styles["body"] + var/datum/sprite_accessory/body_marking_style = GLOB.marking_styles_list[body_marking] + if(body_marking_style && body_marking_style.species_allowed && (dna.species.name in body_marking_style.species_allowed)) + var/icon/b_marking_s = icon("icon" = body_marking_style.icon, "icon_state" = "[body_marking_style.icon_state]_s") + if(body_marking_style.do_colouration) + b_marking_s.Blend(m_colours["body"], ICON_ADD) + markings_standing.Blend(b_marking_s, ICON_OVERLAY) + //Head markings. + var/obj/item/organ/external/head/head_organ = get_organ("head") + if(head_organ && m_styles["head"]) //If the head is destroyed, forget the head markings. This prevents floating optical markings on decapitated IPCs, for example. + var/head_marking = m_styles["head"] + var/datum/sprite_accessory/head_marking_style = GLOB.marking_styles_list[head_marking] + if(head_marking_style && head_marking_style.species_allowed && (head_organ.dna.species.name in head_marking_style.species_allowed)) + var/icon/h_marking_s = icon("icon" = head_marking_style.icon, "icon_state" = "[head_marking_style.icon_state]_s") + if(head_marking_style.do_colouration) + h_marking_s.Blend(m_colours["head"], ICON_ADD) + markings_standing.Blend(h_marking_s, ICON_OVERLAY) + + overlays_standing[MARKINGS_LAYER] = mutable_appearance(markings_standing, layer = -MARKINGS_LAYER) + apply_overlay(MARKINGS_LAYER) + +//HEAD ACCESSORY OVERLAY +/mob/living/carbon/human/proc/update_head_accessory(var/update_icons=1) + //Reset our head accessory + remove_overlay(HEAD_ACCESSORY_LAYER) + remove_overlay(HEAD_ACC_OVER_LAYER) + + var/obj/item/organ/external/head/head_organ = get_organ("head") + if(!head_organ) + return + + //masks and helmets can obscure our head accessory + if((head && (head.flags & BLOCKHAIR)) || (wear_mask && (wear_mask.flags & BLOCKHAIR))) + return + + //base icons + var/icon/head_accessory_standing = new /icon('icons/mob/body_accessory.dmi',"accessory_none_s") + if(head_organ.ha_style && (head_organ.dna.species.bodyflags & HAS_HEAD_ACCESSORY)) + var/datum/sprite_accessory/head_accessory/head_accessory_style = GLOB.head_accessory_styles_list[head_organ.ha_style] + if(head_accessory_style && head_accessory_style.species_allowed) + if(head_organ.dna.species.name in head_accessory_style.species_allowed) + var/icon/head_accessory_s = new/icon("icon" = head_accessory_style.icon, "icon_state" = "[head_accessory_style.icon_state]_s") + if(head_accessory_style.do_colouration) + head_accessory_s.Blend(head_organ.headacc_colour, ICON_ADD) + head_accessory_standing = head_accessory_s //head_accessory_standing.Blend(head_accessory_s, ICON_OVERLAY) + //Having it this way preserves animations. Useful for animated antennae. + + if(head_accessory_style.over_hair) //Select which layer to use based on the properties of the head accessory style. + overlays_standing[HEAD_ACC_OVER_LAYER] = mutable_appearance(head_accessory_standing, layer = -HEAD_ACC_OVER_LAYER) + apply_overlay(HEAD_ACC_OVER_LAYER) + else + overlays_standing[HEAD_ACCESSORY_LAYER] = mutable_appearance(head_accessory_standing, layer = -HEAD_ACCESSORY_LAYER) + apply_overlay(HEAD_ACCESSORY_LAYER) + else + //warning("Invalid ha_style for [species.name]: [ha_style]") + + + +//HAIR OVERLAY +/mob/living/carbon/human/proc/update_hair(var/update_icons=1) + //Reset our hair + remove_overlay(HAIR_LAYER) + + var/obj/item/organ/external/head/head_organ = get_organ("head") + if(!head_organ) + return + + //masks and helmets can obscure our hair, unless we're a synthetic + if((head && (head.flags & BLOCKHAIR)) || (wear_mask && (wear_mask.flags & BLOCKHAIR))) + return + + //base icons + var/icon/hair_standing = new /icon('icons/mob/human_face.dmi',"bald_s") + if(head_organ.h_style && !(head && (head.flags & BLOCKHEADHAIR) && !(isSynthetic()))) + var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_full_list[head_organ.h_style] + if(hair_style && hair_style.species_allowed) + if((head_organ.dna.species.name in hair_style.species_allowed) || (head_organ.dna.species.bodyflags & ALL_RPARTS)) //If the head's species is in the list of allowed species for the hairstyle, or the head's species is one flagged to have bodies comprised wholly of cybernetics... + var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s") + if(istype(head_organ.dna.species, /datum/species/slime)) // I am el worstos + hair_s.Blend("[skin_colour]A0", ICON_AND) + else if(hair_style.do_colouration) + hair_s.Blend(head_organ.hair_colour, ICON_ADD) + + if(hair_style.secondary_theme) + var/icon/hair_secondary_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_[hair_style.secondary_theme]_s") + if(!hair_style.no_sec_colour) + hair_secondary_s.Blend(head_organ.sec_hair_colour, ICON_ADD) + hair_s.Blend(hair_secondary_s, ICON_OVERLAY) + + hair_standing = hair_s //hair_standing.Blend(hair_s, ICON_OVERLAY) + //Having it this way preserves animations. Useful for IPC screens. + else + //warning("Invalid h_style for [species.name]: [h_style]") + //hair_standing.Blend(debrained_s, ICON_OVERLAY)//how does i overlay for fish? + + overlays_standing[HAIR_LAYER] = mutable_appearance(hair_standing, layer = -HAIR_LAYER) + apply_overlay(HAIR_LAYER) + + +//FACIAL HAIR OVERLAY +/mob/living/carbon/human/proc/update_fhair(var/update_icons=1) + //Reset our facial hair + remove_overlay(FHAIR_LAYER) + remove_overlay(FHAIR_OVER_LAYER) + + var/obj/item/organ/external/head/head_organ = get_organ("head") + if(!head_organ) + return + + //masks and helmets can obscure our facial hair, unless we're a synthetic + if((head && (head.flags & BLOCKHAIR)) || (wear_mask && (wear_mask.flags & BLOCKHAIR))) + return + + //base icons + var/icon/face_standing = new /icon('icons/mob/human_face.dmi',"bald_s") + if(head_organ.f_style) + var/datum/sprite_accessory/facial_hair/facial_hair_style = GLOB.facial_hair_styles_list[head_organ.f_style] + if(facial_hair_style && facial_hair_style.species_allowed) + if((head_organ.dna.species.name in facial_hair_style.species_allowed) || (head_organ.dna.species.bodyflags & ALL_RPARTS)) //If the head's species is in the list of allowed species for the hairstyle, or the head's species is one flagged to have bodies comprised wholly of cybernetics... + var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s") + if(istype(head_organ.dna.species, /datum/species/slime)) // I am el worstos + facial_s.Blend("[skin_colour]A0", ICON_AND) + else if(facial_hair_style.do_colouration) + facial_s.Blend(head_organ.facial_colour, ICON_ADD) + + if(facial_hair_style.secondary_theme) + var/icon/facial_secondary_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_[facial_hair_style.secondary_theme]_s") + if(!facial_hair_style.no_sec_colour) + facial_secondary_s.Blend(head_organ.sec_facial_colour, ICON_ADD) + facial_s.Blend(facial_secondary_s, ICON_OVERLAY) + + face_standing.Blend(facial_s, ICON_OVERLAY) + + if(facial_hair_style.over_hair) //Select which layer to use based on the properties of the facial hair style. + overlays_standing[FHAIR_OVER_LAYER] = mutable_appearance(face_standing, layer = -FHAIR_OVER_LAYER) + apply_overlay(FHAIR_OVER_LAYER) + else + overlays_standing[FHAIR_LAYER] = mutable_appearance(face_standing, layer = -FHAIR_LAYER) + apply_overlay(FHAIR_LAYER) + else + //warning("Invalid f_style for [species.name]: [f_style]") + + + +/mob/living/carbon/human/update_mutations(var/update_icons=1) + remove_overlay(MUTATIONS_LAYER) + var/fat + if(FAT in mutations) + fat = "fat" + + var/mutable_appearance/standing = mutable_appearance('icons/effects/genetics.dmi', layer = -MUTATIONS_LAYER) + var/add_image = 0 + var/g = "m" + if(gender == FEMALE) + g = "f" + // DNA2 - Drawing underlays. + for(var/datum/dna/gene/gene in dna_genes) + if(!gene.block) + continue + if(gene.is_active(src)) + var/underlay = gene.OnDrawUnderlays(src, g, fat) + if(underlay) + standing.underlays += underlay + add_image = 1 + for(var/mut in mutations) + switch(mut) + if(LASER) + standing.overlays += "lasereyes_s" + add_image = 1 + if((COLDRES in mutations) && (HEATRES in mutations)) + standing.underlays -= "cold[fat]_s" + standing.underlays -= "fire[fat]_s" + standing.underlays += "coldfire[fat]_s" + + if(add_image) + overlays_standing[MUTATIONS_LAYER] = standing + apply_overlay(MUTATIONS_LAYER) + + +/mob/living/carbon/human/proc/update_mutantrace(var/update_icons=1) +//BS12 EDIT + var/skel = (SKELETON in mutations) + if(skel) + skeleton = 'icons/mob/human_races/r_skeleton.dmi' + else + skeleton = null + + update_hair(0) + update_fhair(0) + + +/mob/living/carbon/human/update_fire() + remove_overlay(FIRE_LAYER) + if(on_fire) + if(!overlays_standing[FIRE_LAYER]) + overlays_standing[FIRE_LAYER] = mutable_appearance(fire_dmi, fire_sprite, layer = -FIRE_LAYER) + apply_overlay(FIRE_LAYER) + +/* --------------------------------------- */ +//For legacy support. +/mob/living/carbon/human/regenerate_icons() + ..() + if(notransform) return + update_mutations(0) + update_body(0, 1) //Update the body and force limb icon regeneration. + update_hair(0) + update_head_accessory(0) + update_fhair(0) + update_mutantrace(0) + update_inv_w_uniform(0,0) + update_inv_wear_id(0) + update_inv_gloves(0,0) + update_inv_glasses(0) + update_inv_ears(0) + update_inv_shoes(0,0) + update_inv_s_store(0) + update_inv_wear_mask(0) + update_inv_head(0,0) + update_inv_belt(0) + update_inv_back(0) + update_inv_wear_suit(0) + update_inv_r_hand(0) + update_inv_l_hand(0) + update_inv_handcuffed(0) + update_inv_legcuffed(0) + update_inv_pockets(0) + update_inv_wear_pda(0) + UpdateDamageIcon(0) + force_update_limbs() + update_tail_layer(0) + overlays.Cut() // Force all overlays to regenerate + update_fire() + update_icons() +/* --------------------------------------- */ +//vvvvvv UPDATE_INV PROCS vvvvvv + +/mob/living/carbon/human/update_inv_w_uniform(var/update_icons=1) + remove_overlay(UNIFORM_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_w_uniform] + if(inv) + inv.update_icon() + + if(w_uniform && istype(w_uniform, /obj/item/clothing/under)) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + w_uniform.screen_loc = ui_iclothing //...draw the item in the inventory screen + client.screen += w_uniform //Either way, add the item to the HUD + + var/t_color = w_uniform.item_color + if(!t_color) + t_color = icon_state + + var/mutable_appearance/standing = mutable_appearance('icons/mob/uniform.dmi', "[t_color]_s", layer = -UNIFORM_LAYER) + if(FAT in mutations) + if(w_uniform.flags_size & ONESIZEFITSALL) + standing.icon = 'icons/mob/uniform_fat.dmi' + else + to_chat(src, "You burst out of \the [w_uniform]!") + unEquip(w_uniform) + return + + if(w_uniform.icon_override) + standing.icon = w_uniform.icon_override + else if(w_uniform.sprite_sheets && w_uniform.sprite_sheets[dna.species.name]) + standing.icon = w_uniform.sprite_sheets[dna.species.name] + + if(w_uniform.blood_DNA) + var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "uniformblood") + bloodsies.color = w_uniform.blood_color + standing.overlays += bloodsies + + if(w_uniform:accessories.len) //WE CHECKED THE TYPE ABOVE. THIS REALLY SHOULD BE FINE. // oh my god kys whoever made this if statement jfc :gun: + for(var/obj/item/clothing/accessory/A in w_uniform:accessories) + var/tie_color = A.item_color + if(!tie_color) + tie_color = A.icon_state + if(A.icon_override) + standing.overlays += image("icon" = A.icon_override, "icon_state" = "[A.icon_state]") + else if(A.sprite_sheets && A.sprite_sheets[dna.species.name]) + standing.overlays += image("icon" = A.sprite_sheets[dna.species.name], "icon_state" = "[A.icon_state]") + else + standing.overlays += image("icon" = 'icons/mob/ties.dmi', "icon_state" = "[tie_color]") + standing.alpha = w_uniform.alpha + standing.color = w_uniform.color + overlays_standing[UNIFORM_LAYER] = standing + else + // Automatically drop anything in store / id / belt if you're not wearing a uniform. //CHECK IF NECESARRY + for(var/obj/item/thing in list(r_store, l_store, wear_id, wear_pda, belt)) // whoever made this + if(thing) // you're a piece of fucking garbage + unEquip(thing) // why the fuck would you goddamn do this motherfucking shit + if(client) // INVENTORY CODE IN FUCKING ICON CODE + client.screen -= thing // WHAT THE FUCKING FUCK BAY GODDAMNIT + // **I FUCKING HATE YOU AAAAAAAAAA** + if(thing) // + thing.forceMove(drop_location()) // + thing.dropped(src) // + thing.layer = initial(thing.layer) + thing.plane = initial(thing.plane) + apply_overlay(UNIFORM_LAYER) + +/mob/living/carbon/human/update_inv_wear_id(var/update_icons=1) + remove_overlay(ID_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_id] + if(inv) + inv.update_icon() + + if(wear_id) + if(client && hud_used && hud_used.hud_shown) + wear_id.screen_loc = ui_id + client.screen += wear_id + + if(w_uniform && w_uniform:displays_id) + overlays_standing[ID_LAYER] = mutable_appearance('icons/mob/mob.dmi', "id", layer = -ID_LAYER) + apply_overlay(ID_LAYER) + +/mob/living/carbon/human/update_inv_gloves(var/update_icons=1) + remove_overlay(GLOVES_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_gloves] + if(inv) + inv.update_icon() + + if(gloves) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + gloves.screen_loc = ui_gloves //...draw the item in the inventory screen + client.screen += gloves //Either way, add the item to the HUD + + var/t_state = gloves.item_state + if(!t_state) t_state = gloves.icon_state + + var/mutable_appearance/standing + if(gloves.icon_override) + standing = mutable_appearance(gloves.icon_override, "[t_state]", layer = -GLOVES_LAYER) + else if(gloves.sprite_sheets && gloves.sprite_sheets[dna.species.name]) + standing = mutable_appearance(gloves.sprite_sheets[dna.species.name], "[t_state]", layer = -GLOVES_LAYER) + else + standing = mutable_appearance('icons/mob/hands.dmi', "[t_state]", layer = -GLOVES_LAYER) + + if(gloves.blood_DNA) + var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "bloodyhands") + bloodsies.color = gloves.blood_color + standing.overlays += bloodsies + overlays_standing[GLOVES_LAYER] = standing + else + if(blood_DNA) + var/mutable_appearance/bloodsies = mutable_appearance(dna.species.blood_mask, "bloodyhands", layer = -GLOVES_LAYER) + bloodsies.color = hand_blood_color + overlays_standing[GLOVES_LAYER] = bloodsies + apply_overlay(GLOVES_LAYER) + + +/mob/living/carbon/human/update_inv_glasses(var/update_icons=1) + remove_overlay(GLASSES_LAYER) + remove_overlay(GLASSES_OVER_LAYER) + remove_overlay(OVER_MASK_LAYER) + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_glasses] + if(inv) + inv.update_icon() + + if(glasses) + var/mutable_appearance/new_glasses + var/obj/item/organ/external/head/head_organ = get_organ("head") + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + glasses.screen_loc = ui_glasses //...draw the item in the inventory screen + client.screen += glasses //Either way, add the item to the HUD + + if(glasses.icon_override) + new_glasses = mutable_appearance(glasses.icon_override, "[glasses.icon_state]", layer = -GLASSES_LAYER) + else if(glasses.sprite_sheets && glasses.sprite_sheets[head_organ.dna.species.name]) + new_glasses = mutable_appearance(glasses.sprite_sheets[head_organ.dna.species.name], "[glasses.icon_state]", layer = -GLASSES_LAYER) + else + new_glasses = mutable_appearance('icons/mob/eyes.dmi', "[glasses.icon_state]", layer = -GLASSES_LAYER) + + var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_full_list[head_organ.h_style] + var/obj/item/clothing/glasses/G = glasses + if(istype(G) && G.over_mask) //If the user's used the 'wear over mask' verb on the glasses. + new_glasses.layer = -OVER_MASK_LAYER + overlays_standing[OVER_MASK_LAYER] = new_glasses + apply_overlay(OVER_MASK_LAYER) + else if(hair_style && hair_style.glasses_over) //Select which layer to use based on the properties of the hair style. Hair styles with hair that don't overhang the arms of the glasses should have glasses_over set to a positive value. + new_glasses.layer = -GLASSES_OVER_LAYER + overlays_standing[GLASSES_OVER_LAYER] = new_glasses + apply_overlay(GLASSES_OVER_LAYER) + else + overlays_standing[GLASSES_LAYER] = new_glasses + apply_overlay(GLASSES_LAYER) + + update_misc_effects() + +/mob/living/carbon/human/update_inv_ears(var/update_icons=1) + remove_overlay(EARS_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_l_ear] + if(inv) + inv.update_icon() + + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_r_ear] + if(inv) + inv.update_icon() + + if(l_ear || r_ear) + if(l_ear) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + l_ear.screen_loc = ui_l_ear //...draw the item in the inventory screen + client.screen += l_ear //Either way, add the item to the HUD + + var/t_type = l_ear.item_state + if(!t_type) + t_type = l_ear.icon_state + if(l_ear.icon_override) + t_type = "[t_type]_l" + overlays_standing[EARS_LAYER] = mutable_appearance(l_ear.icon_override, "[t_type]", layer = -EARS_LAYER) + else if(l_ear.sprite_sheets && l_ear.sprite_sheets[dna.species.name]) + overlays_standing[EARS_LAYER] = mutable_appearance(l_ear.sprite_sheets[dna.species.name], "[t_type]", layer = -EARS_LAYER) + else + overlays_standing[EARS_LAYER] = mutable_appearance('icons/mob/ears.dmi', "[t_type]", layer = -EARS_LAYER) + + if(r_ear) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + r_ear.screen_loc = ui_r_ear //...draw the item in the inventory screen + client.screen += r_ear //Either way, add the item to the HUD + + var/t_type = r_ear.item_state + if(!t_type) + t_type = r_ear.icon_state + if(r_ear.icon_override) + t_type = "[t_type]_r" + overlays_standing[EARS_LAYER] = mutable_appearance(r_ear.icon_override, "[t_type]", layer = -EARS_LAYER) + else if(r_ear.sprite_sheets && r_ear.sprite_sheets[dna.species.name]) + overlays_standing[EARS_LAYER] = mutable_appearance(r_ear.sprite_sheets[dna.species.name], "[t_type]", layer = -EARS_LAYER) + else + overlays_standing[EARS_LAYER] = mutable_appearance('icons/mob/ears.dmi', "[t_type]", layer = -EARS_LAYER) + apply_overlay(EARS_LAYER) + +/mob/living/carbon/human/update_inv_shoes(var/update_icons=1) + remove_overlay(SHOES_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_shoes] + if(inv) + inv.update_icon() + + if(shoes) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + shoes.screen_loc = ui_shoes //...draw the item in the inventory screen + client.screen += shoes //Either way, add the item to the HUD + + var/mutable_appearance/standing + if(shoes.icon_override) + standing = mutable_appearance(shoes.icon_override, "[shoes.icon_state]", layer = -SHOES_LAYER) + else if(shoes.sprite_sheets && shoes.sprite_sheets[dna.species.name]) + standing = mutable_appearance(shoes.sprite_sheets[dna.species.name], "[shoes.icon_state]", layer = -SHOES_LAYER) + else + standing = mutable_appearance('icons/mob/feet.dmi', "[shoes.icon_state]", layer = -SHOES_LAYER) + + + if(shoes.blood_DNA) + var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "shoeblood") + bloodsies.color = shoes.blood_color + standing.overlays += bloodsies + standing.alpha = shoes.alpha + standing.color = shoes.color + overlays_standing[SHOES_LAYER] = standing + else + if(feet_blood_DNA) + var/mutable_appearance/bloodsies = mutable_appearance(dna.species.blood_mask, "shoeblood", layer = -SHOES_LAYER) + bloodsies.color = feet_blood_color + overlays_standing[SHOES_LAYER] = bloodsies + apply_overlay(SHOES_LAYER) + +/mob/living/carbon/human/update_inv_s_store(var/update_icons=1) + remove_overlay(SUIT_STORE_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_s_store] + if(inv) + inv.update_icon() + + if(s_store) + if(client && hud_used && hud_used.hud_shown) + s_store.screen_loc = ui_sstore1 + client.screen += s_store + + var/t_state = s_store.item_state + if(!t_state) + t_state = s_store.icon_state + var/dmi='icons/mob/belt_mirror.dmi' + overlays_standing[SUIT_STORE_LAYER] = mutable_appearance(dmi, "[t_state]", layer = -SUIT_STORE_LAYER) + s_store.screen_loc = ui_sstore1 //TODO + apply_overlay(SUIT_STORE_LAYER) + + +/mob/living/carbon/human/update_inv_head(var/update_icons=1) + ..() + remove_overlay(HEAD_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_head] + if(inv) + inv.update_icon() + + if(head) + var/mutable_appearance/standing + if(head.icon_override) + standing = mutable_appearance(head.icon_override, "[head.icon_state]", layer = -HEAD_LAYER) + else if(head.sprite_sheets && head.sprite_sheets[dna.species.name]) + standing = mutable_appearance(head.sprite_sheets[dna.species.name], "[head.icon_state]", layer = -HEAD_LAYER) + else + standing = mutable_appearance('icons/mob/head.dmi', "[head.icon_state]", layer = -HEAD_LAYER) + + if(head.blood_DNA) + var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "helmetblood") + bloodsies.color = head.blood_color + standing.overlays += bloodsies + standing.alpha = head.alpha + standing.color = head.color + overlays_standing[HEAD_LAYER] = standing + apply_overlay(HEAD_LAYER) + +/mob/living/carbon/human/update_inv_belt(var/update_icons=1) + remove_overlay(BELT_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_belt] + if(inv) + inv.update_icon() + + if(hud_used.hud_shown && belt) + client.screen += belt + belt.screen_loc = ui_belt + + if(belt) + var/t_state = belt.item_state + if(!t_state) + t_state = belt.icon_state + + if(belt.icon_override) + t_state = "[t_state]_be" + overlays_standing[BELT_LAYER] = mutable_appearance(belt.icon_override, "[t_state]", layer = -BELT_LAYER) + else if(belt.sprite_sheets && belt.sprite_sheets[dna.species.name]) + overlays_standing[BELT_LAYER] = mutable_appearance(belt.sprite_sheets[dna.species.name], "[t_state]", layer = -BELT_LAYER) + else + overlays_standing[BELT_LAYER] = mutable_appearance('icons/mob/belt.dmi', "[t_state]", layer = -BELT_LAYER) + apply_overlay(BELT_LAYER) + + +/mob/living/carbon/human/update_inv_wear_suit(var/update_icons=1) + remove_overlay(SUIT_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_suit] + if(inv) + inv.update_icon() + + if(wear_suit && istype(wear_suit, /obj/item/clothing/suit)) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) //if the inventory is open ... + wear_suit.screen_loc = ui_oclothing //TODO //...draw the item in the inventory screen + client.screen += wear_suit //Either way, add the item to the HUD + + var/mutable_appearance/standing + if(wear_suit.icon_override) + standing = mutable_appearance(wear_suit.icon_override, "[wear_suit.icon_state]", layer = -SUIT_LAYER) + else if(wear_suit.sprite_sheets && wear_suit.sprite_sheets[dna.species.name]) + standing = mutable_appearance(wear_suit.sprite_sheets[dna.species.name], "[wear_suit.icon_state]", layer = -SUIT_LAYER) + else if(FAT in mutations) + if(wear_suit.flags_size & ONESIZEFITSALL) + standing = mutable_appearance('icons/mob/suit_fat.dmi', "[wear_suit.icon_state]", layer = -SUIT_LAYER) + else + to_chat(src, "You burst out of \the [wear_suit]!") + unEquip(wear_suit) + return + else + standing = mutable_appearance('icons/mob/suit.dmi', "[wear_suit.icon_state]", layer = -SUIT_LAYER) + + if(wear_suit.breakouttime) + drop_l_hand() + drop_r_hand() + + if(wear_suit.blood_DNA) + var/obj/item/clothing/suit/S = wear_suit + var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "[S.blood_overlay_type]blood") + bloodsies.color = wear_suit.blood_color + standing.overlays += bloodsies + + + var/special_overlays = wear_suit.special_overlays() + if(special_overlays) + standing.overlays += special_overlays + + standing.alpha = wear_suit.alpha + standing.color = wear_suit.color + overlays_standing[SUIT_LAYER] = standing + + apply_overlay(SUIT_LAYER) + update_tail_layer(0) + update_collar(0) + +/mob/living/carbon/human/update_inv_pockets() + if(client && hud_used) + var/obj/screen/inventory/inv + + inv = hud_used.inv_slots[slot_l_store] + if(inv) + inv.update_icon() + + inv = hud_used.inv_slots[slot_r_store] + if(inv) + inv.update_icon() + + if(hud_used.hud_shown) + if(l_store) + client.screen += l_store + l_store.screen_loc = ui_storage1 + + if(r_store) + client.screen += r_store + r_store.screen_loc = ui_storage2 + +/mob/living/carbon/human/update_inv_wear_pda() + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_pda] + if(inv) + inv.update_icon() + + if(wear_pda) + client.screen += wear_pda + wear_pda.screen_loc = ui_pda + +/mob/living/carbon/human/update_inv_wear_mask(var/update_icons = 1) + ..() + remove_overlay(FACEMASK_LAYER) + if(client && hud_used) + var/obj/screen/inventory/inv = hud_used.inv_slots[slot_wear_mask] + if(inv) + inv.update_icon() + if(wear_mask && (istype(wear_mask, /obj/item/clothing/mask) || istype(wear_mask, /obj/item/clothing/accessory))) + if(!(slot_wear_mask in check_obscured_slots())) + var/obj/item/organ/external/head/head_organ = get_organ("head") + var/datum/sprite_accessory/alt_heads/alternate_head + if(head_organ.alt_head && head_organ.alt_head != "None") + alternate_head = GLOB.alt_heads_list[head_organ.alt_head] + + var/mutable_appearance/standing + var/icon/mask_icon = new(wear_mask.icon) + if(wear_mask.icon_override) + mask_icon = new(wear_mask.icon_override) + standing = mutable_appearance(wear_mask.icon_override, "[wear_mask.icon_state][(alternate_head && ("[wear_mask.icon_state]_[alternate_head.suffix]" in mask_icon.IconStates())) ? "_[alternate_head.suffix]" : ""]", layer = -FACEMASK_LAYER) + else if(wear_mask.sprite_sheets && wear_mask.sprite_sheets[dna.species.name]) + mask_icon = new(wear_mask.sprite_sheets[dna.species.name]) + standing = mutable_appearance(wear_mask.sprite_sheets[dna.species.name], "[wear_mask.icon_state][(alternate_head && ("[wear_mask.icon_state]_[alternate_head.suffix]" in mask_icon.IconStates())) ? "_[alternate_head.suffix]" : ""]", layer = -FACEMASK_LAYER) + else + standing = mutable_appearance('icons/mob/mask.dmi', "[wear_mask.icon_state][(alternate_head && ("[wear_mask.icon_state]_[alternate_head.suffix]" in mask_icon.IconStates())) ? "_[alternate_head.suffix]" : ""]", layer = -FACEMASK_LAYER) + + if(!istype(wear_mask, /obj/item/clothing/mask/cigarette) && wear_mask.blood_DNA) + var/image/bloodsies = image("icon" = dna.species.blood_mask, "icon_state" = "maskblood") + bloodsies.color = wear_mask.blood_color + standing.overlays += bloodsies + + standing.alpha = wear_mask.alpha + standing.color = wear_mask.color + overlays_standing[FACEMASK_LAYER] = standing + apply_overlay(FACEMASK_LAYER) + + +/mob/living/carbon/human/update_inv_back(var/update_icons=1) + ..() + remove_overlay(BACK_LAYER) + if(back) + //determine the icon to use + var/mutable_appearance/standing + if(back.icon_override) + standing = mutable_appearance(back.icon_override, "[back.icon_state]", layer = -BACK_LAYER) + else if(istype(back, /obj/item/rig)) + //If this is a rig and a mob_icon is set, it will take species into account in the rig update_icon() proc. + var/obj/item/rig/rig = back + standing = rig.mob_icon + else if(back.sprite_sheets && back.sprite_sheets[dna.species.name]) + standing = mutable_appearance(back.sprite_sheets[dna.species.name], "[back.icon_state]", layer = -BACK_LAYER) + else + standing = mutable_appearance('icons/mob/back.dmi', "[back.icon_state]", layer = -BACK_LAYER) + + //create the image + standing.alpha = back.alpha + standing.color = back.color + overlays_standing[BACK_LAYER] = standing + apply_overlay(BACK_LAYER) + +/mob/living/carbon/human/update_inv_handcuffed(var/update_icons=1) + remove_overlay(HANDCUFF_LAYER) + if(handcuffed) + if(istype(handcuffed, /obj/item/restraints/handcuffs/pinkcuffs)) + overlays_standing[HANDCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "pinkcuff1", layer = -HANDCUFF_LAYER) + else + overlays_standing[HANDCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "handcuff1", layer = -HANDCUFF_LAYER) + apply_overlay(HANDCUFF_LAYER) + +/mob/living/carbon/human/update_inv_legcuffed(var/update_icons=1) + remove_overlay(LEGCUFF_LAYER) + clear_alert("legcuffed") + if(legcuffed) + overlays_standing[LEGCUFF_LAYER] = mutable_appearance('icons/mob/mob.dmi', "legcuff1", layer = -LEGCUFF_LAYER) + throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed) + if(m_intent != MOVE_INTENT_WALK) + m_intent = MOVE_INTENT_WALK + if(hud_used && hud_used.move_intent) + hud_used.move_intent.icon_state = "walking" + apply_overlay(LEGCUFF_LAYER) + + +/mob/living/carbon/human/update_inv_r_hand(var/update_icons=1) + ..() + remove_overlay(R_HAND_LAYER) + if(r_hand) + var/t_state = r_hand.item_state + if(!t_state) + t_state = r_hand.icon_state + + var/mutable_appearance/standing + if(r_hand.sprite_sheets_inhand && r_hand.sprite_sheets_inhand[dna.species.name]) + t_state = "[t_state]_r" + standing = mutable_appearance(r_hand.sprite_sheets_inhand[dna.species.name], "[t_state]", layer = -R_HAND_LAYER) + else + standing = mutable_appearance(r_hand.righthand_file, "[t_state]", layer = -R_HAND_LAYER) + standing = center_image(standing, r_hand.inhand_x_dimension, r_hand.inhand_y_dimension) + overlays_standing[R_HAND_LAYER] = standing + apply_overlay(R_HAND_LAYER) + + +/mob/living/carbon/human/update_inv_l_hand(var/update_icons=1) + ..() + remove_overlay(L_HAND_LAYER) + if(l_hand) + var/t_state = l_hand.item_state + if(!t_state) + t_state = l_hand.icon_state + + var/mutable_appearance/standing + if(l_hand.sprite_sheets_inhand && l_hand.sprite_sheets_inhand[dna.species.name]) + t_state = "[t_state]_l" + standing = mutable_appearance(l_hand.sprite_sheets_inhand[dna.species.name], "[t_state]", layer = -L_HAND_LAYER) + else + standing = mutable_appearance(l_hand.lefthand_file, "[t_state]", layer = -L_HAND_LAYER) + standing = center_image(standing, l_hand.inhand_x_dimension, l_hand.inhand_y_dimension) + overlays_standing[L_HAND_LAYER] = standing + apply_overlay(L_HAND_LAYER) + +//human HUD updates for items in our inventory + +//update whether our head item appears on our hud. +/mob/living/carbon/human/update_hud_head(obj/item/I) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) + I.screen_loc = ui_head + client.screen += I + +//update whether our mask item appears on our hud. +/mob/living/carbon/human/update_hud_wear_mask(obj/item/I) + if(client && hud_used && hud_used.hud_shown) + if(hud_used.inventory_shown) + I.screen_loc = ui_mask + client.screen += I + +//update whether our back item appears on our hud. +/mob/living/carbon/human/update_hud_back(obj/item/I) + if(client && hud_used && hud_used.hud_shown) + I.screen_loc = ui_back + client.screen += I + + +/mob/living/carbon/human/proc/update_tail_layer(var/update_icons=1) + remove_overlay(TAIL_UNDERLIMBS_LAYER) // SEW direction icons, overlayed by LIMBS_LAYER. + remove_overlay(TAIL_LAYER) /* This will be one of two things: + If the species' tail is overlapped by limbs, this will be only the N direction icon so tails + can still appear on the outside of uniforms and such. + Otherwise, since the user's tail isn't overlapped by limbs, it will be a full icon with all directions. */ + + var/icon/tail_marking_icon + var/datum/sprite_accessory/body_markings/tail/tail_marking_style + if(m_styles["tail"] != "None" && (dna.species.bodyflags & HAS_TAIL_MARKINGS)) + var/tail_marking = m_styles["tail"] + tail_marking_style = GLOB.marking_styles_list[tail_marking] + tail_marking_icon = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]_s") + tail_marking_icon.Blend(m_colours["tail"], ICON_ADD) + + if(body_accessory) + if(body_accessory.try_restrictions(src)) + var/icon/accessory_s = new/icon("icon" = body_accessory.icon, "icon_state" = body_accessory.icon_state) + if(dna.species.bodyflags & HAS_SKIN_COLOR) + accessory_s.Blend(skin_colour, body_accessory.blend_mode) + if(tail_marking_icon && (body_accessory.name in tail_marking_style.tails_allowed)) + accessory_s.Blend(tail_marking_icon, ICON_OVERLAY) + if((!body_accessory || istype(body_accessory, /datum/body_accessory/tail)) && dna.species.bodyflags & TAIL_OVERLAPPED) // If the player has a species whose tail is overlapped by limbs... (having a non-tail body accessory like the snake body will override this) + // Gives the underlimbs layer SEW direction icons since it's overlayed by limbs and just about everything else anyway. + var/icon/under = new/icon("icon" = 'icons/mob/body_accessory.dmi', "icon_state" = "accessory_none_s") + under.Insert(new/icon(accessory_s, dir=SOUTH), dir=SOUTH) + under.Insert(new/icon(accessory_s, dir=EAST), dir=EAST) + under.Insert(new/icon(accessory_s, dir=WEST), dir=WEST) + + var/mutable_appearance/underlimbs = mutable_appearance(under, layer = -TAIL_UNDERLIMBS_LAYER) + underlimbs.pixel_x = body_accessory.pixel_x_offset + underlimbs.pixel_y = body_accessory.pixel_y_offset + overlays_standing[TAIL_UNDERLIMBS_LAYER] = underlimbs + + // Creates a blank icon, and copies accessory_s' north direction sprite into it + // before passing that to the tail layer that overlays uniforms and such. + var/icon/over = new/icon("icon" = 'icons/mob/body_accessory.dmi', "icon_state" = "accessory_none_s") + over.Insert(new/icon(accessory_s, dir=NORTH), dir=NORTH) + + var/mutable_appearance/tail = mutable_appearance(over, layer = -TAIL_LAYER) + tail.pixel_x = body_accessory.pixel_x_offset + tail.pixel_y = body_accessory.pixel_y_offset + overlays_standing[TAIL_LAYER] = tail + else // Otherwise, since the user's tail isn't overlapped by limbs, go ahead and use default icon generation. + var/mutable_appearance/tail = mutable_appearance(accessory_s, layer = -TAIL_LAYER) + tail.pixel_x = body_accessory.pixel_x_offset + tail.pixel_y = body_accessory.pixel_y_offset + overlays_standing[TAIL_LAYER] = tail + + else if(tail && dna.species.bodyflags & HAS_TAIL) //no tailless tajaran + if(!wear_suit || !(wear_suit.flags_inv & HIDETAIL)) + var/icon/tail_s = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[tail]_s") + if(dna.species.bodyflags & HAS_SKIN_COLOR) + tail_s.Blend(skin_colour, ICON_ADD) + if(tail_marking_icon && !tail_marking_style.tails_allowed) + tail_s.Blend(tail_marking_icon, ICON_OVERLAY) + if((!body_accessory || istype(body_accessory, /datum/body_accessory/tail)) && dna.species.bodyflags & TAIL_OVERLAPPED) // If the player has a species whose tail is overlapped by limbs... (having a non-tail body accessory like the snake body will override this) + // Gives the underlimbs layer SEW direction icons since it's overlayed by limbs and just about everything else anyway. + var/icon/under = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "blank") + under.Insert(new/icon(tail_s, dir=SOUTH), dir=SOUTH) + under.Insert(new/icon(tail_s, dir=EAST), dir=EAST) + under.Insert(new/icon(tail_s, dir=WEST), dir=WEST) + + overlays_standing[TAIL_UNDERLIMBS_LAYER] = mutable_appearance(under, layer = -TAIL_UNDERLIMBS_LAYER) + + // Creates a blank icon, and copies accessory_s' north direction sprite into it before passing that to the tail layer that overlays uniforms and such. + var/icon/over = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "blank") + over.Insert(new/icon(tail_s, dir=NORTH), dir=NORTH) + + overlays_standing[TAIL_LAYER] = mutable_appearance(over, layer = -TAIL_LAYER) + else // Otherwise, since the user's tail isn't overlapped by limbs, go ahead and use default icon generation. + overlays_standing[TAIL_LAYER] = mutable_appearance(tail_s, layer = -TAIL_LAYER) + apply_overlay(TAIL_LAYER) + apply_overlay(TAIL_UNDERLIMBS_LAYER) + +/mob/living/carbon/human/proc/start_tail_wagging(var/update_icons=1) + remove_overlay(TAIL_UNDERLIMBS_LAYER) // SEW direction icons, overlayed by LIMBS_LAYER. + remove_overlay(TAIL_LAYER) /* This will be one of two things: + If the species' tail is overlapped by limbs, this will be only the N direction icon so tails + can still appear on the outside of uniforms and such. + Otherwise, since the user's tail isn't overlapped by limbs, it will be a full icon with all directions. */ + + var/icon/tail_marking_icon + var/datum/sprite_accessory/body_markings/tail/tail_marking_style + if(m_styles["tail"] != "None" && (dna.species.bodyflags & HAS_TAIL_MARKINGS)) + var/tail_marking = m_styles["tail"] + tail_marking_style = GLOB.marking_styles_list[tail_marking] + tail_marking_icon = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]w_s") + tail_marking_icon.Blend(m_colours["tail"], ICON_ADD) + + if(body_accessory) + var/icon/accessory_s = new/icon("icon" = body_accessory.get_animated_icon(), "icon_state" = body_accessory.get_animated_icon_state()) + if(dna.species.bodyflags & HAS_SKIN_COLOR) + accessory_s.Blend(skin_colour, body_accessory.blend_mode) + if(tail_marking_icon && (body_accessory.name in tail_marking_style.tails_allowed)) + accessory_s.Blend(tail_marking_icon, ICON_OVERLAY) + if((!body_accessory || istype(body_accessory, /datum/body_accessory/tail)) && dna.species.bodyflags & TAIL_OVERLAPPED) // If the player has a species whose tail is overlapped by limbs... (having a non-tail body accessory like the snake body will override this) + // Gives the underlimbs layer SEW direction icons since it's overlayed by limbs and just about everything else anyway. + var/icon/under = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "Vulpkanin_tail_delay") + if(body_accessory.allowed_species && (dna.species.name in body_accessory.allowed_species)) + under = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[dna.species.name]_tail_delay") + under.Insert(new/icon(accessory_s, dir=SOUTH), dir=SOUTH) + under.Insert(new/icon(accessory_s, dir=EAST), dir=EAST) + under.Insert(new/icon(accessory_s, dir=WEST), dir=WEST) + + var/mutable_appearance/underlimbs = mutable_appearance(under, layer = -TAIL_UNDERLIMBS_LAYER) + underlimbs.pixel_x = body_accessory.pixel_x_offset + underlimbs.pixel_y = body_accessory.pixel_y_offset + overlays_standing[TAIL_UNDERLIMBS_LAYER] = underlimbs + + // Creates a blank icon, and copies accessory_s' north direction sprite into it before passing that to the tail layer that overlays uniforms and such. + var/icon/over = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "Vulpkanin_tail_delay") + if(body_accessory.allowed_species && (dna.species.name in body_accessory.allowed_species)) // If the user's species is in the list of allowed species for the currently selected body accessory, use the appropriate animation timing blank + over = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[dna.species.name]_tail_delay") + over.Insert(new/icon(accessory_s, dir=NORTH), dir=NORTH) + + var/mutable_appearance/tail = mutable_appearance(over, layer = -TAIL_LAYER) + tail.pixel_x = body_accessory.pixel_x_offset + tail.pixel_y = body_accessory.pixel_y_offset + overlays_standing[TAIL_LAYER] = tail + else // Otherwise, since the user's tail isn't overlapped by limbs, go ahead and use default icon generation. + var/mutable_appearance/tail = mutable_appearance(accessory_s, layer = -TAIL_LAYER) + tail.pixel_x = body_accessory.pixel_x_offset + tail.pixel_y = body_accessory.pixel_y_offset + overlays_standing[TAIL_LAYER] = tail + + else if(tail && dna.species.bodyflags & HAS_TAIL) + var/icon/tailw_s = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[tail]w_s") + if(dna.species.bodyflags & HAS_SKIN_COLOR) + tailw_s.Blend(skin_colour, ICON_ADD) + if(tail_marking_icon && !tail_marking_style.tails_allowed) + tailw_s.Blend(tail_marking_icon, ICON_OVERLAY) + if((!body_accessory || istype(body_accessory, /datum/body_accessory/tail)) && dna.species.bodyflags & TAIL_OVERLAPPED) // If the player has a species whose tail is overlapped by limbs... (having a non-tail body accessory like the snake body will override this) + // Gives the underlimbs layer SEW direction icons since it's overlayed by limbs and just about everything else anyway. + var/icon/under = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[dna.species.name]_tail_delay") + under.Insert(new/icon(tailw_s, dir=SOUTH), dir=SOUTH) + under.Insert(new/icon(tailw_s, dir=EAST), dir=EAST) + under.Insert(new/icon(tailw_s, dir=WEST), dir=WEST) + + overlays_standing[TAIL_UNDERLIMBS_LAYER] = mutable_appearance(under, layer = -TAIL_UNDERLIMBS_LAYER) + + // Creates a blank icon, and copies accessory_s' north direction sprite into it before passing that to the tail layer that overlays uniforms and such. + var/icon/over = new/icon("icon" = 'icons/effects/species.dmi', "icon_state" = "[dna.species.name]_tail_delay") + over.Insert(new/icon(tailw_s, dir=NORTH), dir=NORTH) + + overlays_standing[TAIL_LAYER] = mutable_appearance(over, layer = -TAIL_LAYER) + else // Otherwise, since the user's tail isn't overlapped by limbs, go ahead and use default icon generation. + overlays_standing[TAIL_LAYER] = mutable_appearance(tailw_s, layer = -TAIL_LAYER) + apply_overlay(TAIL_LAYER) + apply_overlay(TAIL_UNDERLIMBS_LAYER) + +/mob/living/carbon/human/proc/stop_tail_wagging(var/update_icons=1) + remove_overlay(TAIL_UNDERLIMBS_LAYER) + remove_overlay(TAIL_LAYER) + update_tail_layer(update_icons) //just trigger a full update for normal stationary sprites + +/mob/living/carbon/human/proc/update_int_organs() + remove_overlay(INTORGAN_LAYER) + + var/list/standing = list() + for(var/organ in internal_organs) + var/obj/item/organ/internal/I = organ + var/render = I.render() + if(render) + standing += render + + overlays_standing[INTORGAN_LAYER] = standing + apply_overlay(INTORGAN_LAYER) + +/mob/living/carbon/human/handle_transform_change() + ..() + update_tail_layer() + +//Adds a collar overlay above the helmet layer if the suit has one +// Suit needs an identically named sprite in icons/mob/collar.dmi +// For suits with sprite_sheets, an identically named sprite needs to exist in a file like this icons/mob/species/[species_name_here]/collar.dmi. +/mob/living/carbon/human/proc/update_collar(var/update_icons=1) + remove_overlay(COLLAR_LAYER) + var/icon/C = new('icons/mob/collar.dmi') + var/mutable_appearance/standing = null + + if(wear_suit) + if(wear_suit.icon_override) + var/icon_path = "[wear_suit.icon_override]" + icon_path = "[copytext(icon_path, 1, findtext(icon_path, "/suit.dmi"))]/collar.dmi" //If this file doesn't exist, the end result is that COLLAR_LAYER will be unchanged (empty). + if(fexists(icon_path)) //Just ensuring the nonexistance of a file with the above path won't cause a runtime. + var/icon/icon_file = new(icon_path) + if(wear_suit.icon_state in icon_file.IconStates()) + standing = mutable_appearance(icon_file, "[wear_suit.icon_state]", layer = -COLLAR_LAYER) + else if(wear_suit.sprite_sheets && wear_suit.sprite_sheets[dna.species.name]) + var/icon_path = "[wear_suit.sprite_sheets[dna.species.name]]" + icon_path = "[copytext(icon_path, 1, findtext(icon_path, "/suit.dmi"))]/collar.dmi" //If this file doesn't exist, the end result is that COLLAR_LAYER will be unchanged (empty). + if(fexists(icon_path)) //Just ensuring the nonexistance of a file with the above path won't cause a runtime. + var/icon/icon_file = new(icon_path) + if(wear_suit.icon_state in icon_file.IconStates()) + standing = mutable_appearance(icon_file, "[wear_suit.icon_state]", layer = -COLLAR_LAYER) + else + if(wear_suit.icon_state in C.IconStates()) + standing = mutable_appearance(C, "[wear_suit.icon_state]", layer = -COLLAR_LAYER) + + overlays_standing[COLLAR_LAYER] = standing + apply_overlay(COLLAR_LAYER) + +/mob/living/carbon/human/proc/update_misc_effects() + remove_overlay(MISC_LAYER) + + //Begin appending miscellaneous effects. + if(eyes_shine()) + overlays_standing[MISC_LAYER] = get_eye_shine() //Image layer is specified in get_eye_shine() proc as LIGHTING_LAYER + 1. + + apply_overlay(MISC_LAYER) + +/mob/living/carbon/human/admin_Freeze(client/admin, skip_overlays = TRUE) + . = ..() + overlays_standing[FROZEN_LAYER] = mutable_appearance(frozen, layer = -FROZEN_LAYER) + apply_overlay(FROZEN_LAYER) + +/mob/living/carbon/human/admin_unFreeze(client/admin, skip_overlays = TRUE) + . = ..() + remove_overlay(FROZEN_LAYER) + + +/mob/living/carbon/human/proc/force_update_limbs() + for(var/obj/item/organ/external/O in bodyparts) + O.sync_colour_to_human(src) + update_body(0) + +/mob/living/carbon/human/proc/get_overlays_copy(list/unwantedLayers) + var/list/out = new + for(var/i=1;i<=TOTAL_LAYERS;i++) + if(overlays_standing[i]) + if(i in unwantedLayers) + continue + out += overlays_standing[i] + return out + +/mob/living/carbon/human/proc/generate_icon_render_key() + var/husk = (HUSK in mutations) + var/fat = (FAT in mutations) + var/hulk = (HULK in mutations) + var/skeleton = (SKELETON in mutations) + + . = "" + + var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) + if(eyes) + . += "[eyes.eye_colour]" + else + . += "#000000" + + for(var/organ_tag in dna.species.has_limbs) + var/obj/item/organ/external/part = bodyparts_by_name[organ_tag] + if(isnull(part)) + . += "0" + else if(part.is_robotic()) + . += "2[part.model ? "-[part.model]" : ""]" + else if(part.status & ORGAN_DEAD) + . += "3" + else + . += "1" + + if(part) + var/datum/species/S = GLOB.all_species[part.dna.species.name] + . += "[S.race_key]" + . += "[part.dna.GetUIState(DNA_UI_GENDER)]" + . += "[part.dna.GetUIValue(DNA_UI_SKIN_TONE)]" + if(part.s_col) + . += "[part.s_col]" + if(part.s_tone) + . += "[part.s_tone]" + + . = "[.][!!husk][!!fat][!!hulk][!!skeleton]" diff --git a/code/modules/mob/living/carbon/human/update_stat.dm b/code/modules/mob/living/carbon/human/update_stat.dm index 6228038c710..14c87e13722 100644 --- a/code/modules/mob/living/carbon/human/update_stat.dm +++ b/code/modules/mob/living/carbon/human/update_stat.dm @@ -24,4 +24,4 @@ . = dna.species.can_hear(src) /mob/living/carbon/human/check_death_method() - return dna.species.dies_at_threshold \ No newline at end of file + return dna.species.dies_at_threshold diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 5352ba525f1..832260d3090 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -437,4 +437,4 @@ if(85 to INFINITY) severity = 6 overlay_fullscreen("brute", /obj/screen/fullscreen/brute, severity) else - clear_fullscreen("brute") \ No newline at end of file + clear_fullscreen("brute") diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm index a9f68773ed5..7c809ea2993 100644 --- a/code/modules/mob/living/carbon/status_procs.dm +++ b/code/modules/mob/living/carbon/status_procs.dm @@ -12,4 +12,4 @@ stam_paralyzed = TRUE update_canmove() if(!prev && getStaminaLoss() < 120) // Puts you a little further into the initial stamcrit, makes stamcrit harder to outright counter with chems. - adjustStaminaLoss(30, FALSE) \ No newline at end of file + adjustStaminaLoss(30, FALSE) diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm index 05224c28dde..cb607bae294 100644 --- a/code/modules/mob/living/carbon/update_icons.dm +++ b/code/modules/mob/living/carbon/update_icons.dm @@ -87,4 +87,4 @@ //update whether our back item appears on our hud. /mob/living/carbon/proc/update_hud_back(obj/item/I) - return \ No newline at end of file + return diff --git a/code/modules/mob/living/carbon/update_status.dm b/code/modules/mob/living/carbon/update_status.dm index f78bdf63ec9..b67953c7e69 100644 --- a/code/modules/mob/living/carbon/update_status.dm +++ b/code/modules/mob/living/carbon/update_status.dm @@ -30,4 +30,4 @@ . = FALSE var/obj/item/organ/internal/ears/ears = get_int_organ(/obj/item/organ/internal/ears) if(istype(ears) && !ears.deaf) - . = TRUE \ No newline at end of file + . = TRUE diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 09abb09041d..fa1c392334d 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -1,342 +1,342 @@ - -/* - apply_damage(a,b,c) - args - a:damage - How much damage to take - b:damage_type - What type of damage to take, brute, burn - c:def_zone - Where to take the damage if its brute or burn - Returns - standard 0 if fail -*/ -/mob/living/proc/apply_damage(var/damage = 0, var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/sharp = 0, var/used_weapon = null) - blocked = (100-blocked)/100 - if(!damage || (blocked <= 0)) return 0 - switch(damagetype) - if(BRUTE) - adjustBruteLoss(damage * blocked) - if(BURN) - adjustFireLoss(damage * blocked) - if(TOX) - adjustToxLoss(damage * blocked) - if(OXY) - adjustOxyLoss(damage * blocked) - if(CLONE) - adjustCloneLoss(damage * blocked) - if(STAMINA) - adjustStaminaLoss(damage * blocked) - updatehealth("apply damage") - return 1 - -/mob/living/proc/apply_damage_type(damage = 0, damagetype = BRUTE) //like apply damage except it always uses the damage procs - switch(damagetype) - if(BRUTE) - return adjustBruteLoss(damage) - if(BURN) - return adjustFireLoss(damage) - if(TOX) - return adjustToxLoss(damage) - if(OXY) - return adjustOxyLoss(damage) - if(CLONE) - return adjustCloneLoss(damage) - if(STAMINA) - return adjustStaminaLoss(damage) - if(BRAIN) - return adjustBrainLoss(damage) - -/mob/living/proc/get_damage_amount(damagetype = BRUTE) - switch(damagetype) - if(BRUTE) - return getBruteLoss() - if(BURN) - return getFireLoss() - if(TOX) - return getToxLoss() - if(OXY) - return getOxyLoss() - if(CLONE) - return getCloneLoss() - if(STAMINA) - return getStaminaLoss() - - -/mob/living/proc/apply_damages(var/brute = 0, var/burn = 0, var/tox = 0, var/oxy = 0, var/clone = 0, var/def_zone = null, var/blocked = 0, var/stamina = 0) - if(blocked >= 100) return 0 - if(brute) apply_damage(brute, BRUTE, def_zone, blocked) - if(burn) apply_damage(burn, BURN, def_zone, blocked) - if(tox) apply_damage(tox, TOX, def_zone, blocked) - if(oxy) apply_damage(oxy, OXY, def_zone, blocked) - if(clone) apply_damage(clone, CLONE, def_zone, blocked) - if(stamina) apply_damage(stamina, STAMINA, def_zone, blocked) - return 1 - - - -/mob/living/proc/apply_effect(var/effect = 0,var/effecttype = STUN, var/blocked = 0, var/negate_armor = 0) - blocked = (100-blocked)/100 - if(!effect || (blocked <= 0)) - return 0 - switch(effecttype) - if(STUN) - Stun(effect * blocked) - if(WEAKEN) - Weaken(effect * blocked) - if(PARALYZE) - Paralyse(effect * blocked) - if(IRRADIATE) - var/rad_damage = effect - if(!negate_armor) // Setting negate_armor overrides radiation armor checks, which are automatic otherwise - rad_damage = max(effect * ((100-run_armor_check(null, "rad", "Your clothes feel warm.", "Your clothes feel warm."))/100),0) - radiation += rad_damage - if(SLUR) - Slur(effect * blocked) - if(STUTTER) - Stuttering(effect * blocked) - if(EYE_BLUR) - EyeBlurry(effect * blocked) - if(DROWSY) - Drowsy(effect * blocked) - if(JITTER) - if(status_flags & CANSTUN) - Jitter(effect * blocked) - updatehealth("apply effect") - return 1 - -/mob/living/proc/apply_effects(var/stun = 0, var/weaken = 0, var/paralyze = 0, var/irradiate = 0, var/slur = 0, var/stutter = 0, var/eyeblur = 0, var/drowsy = 0, var/blocked = 0, var/stamina = 0, var/jitter = 0) - if(blocked >= 100) return 0 - if(stun) apply_effect(stun, STUN, blocked) - if(weaken) apply_effect(weaken, WEAKEN, blocked) - if(paralyze) apply_effect(paralyze, PARALYZE, blocked) - if(irradiate) apply_effect(irradiate, IRRADIATE, blocked) - if(slur) apply_effect(slur, SLUR, blocked) - if(stutter) apply_effect(stutter, STUTTER, blocked) - if(eyeblur) apply_effect(eyeblur, EYE_BLUR, blocked) - if(drowsy) apply_effect(drowsy, DROWSY, blocked) - if(stamina) apply_damage(stamina, STAMINA, null, blocked) - if(jitter) apply_effect(jitter, JITTER, blocked) - return 1 - - -/mob/living/proc/getBruteLoss() - return bruteloss - -/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE) - if(status_flags & GODMODE) - return FALSE //godmode - var/old_bruteloss = bruteloss - bruteloss = max(bruteloss + amount, 0) - if(old_bruteloss == bruteloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth("adjustBruteLoss") - -/mob/living/proc/getOxyLoss() - return oxyloss - -/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE) - if(status_flags & GODMODE) - oxyloss = 0 - return FALSE //godmode - if(BREATHLESS in mutations) - oxyloss = 0 - return FALSE - var/old_oxyloss = oxyloss - oxyloss = max(oxyloss + amount, 0) - if(old_oxyloss == oxyloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth("adjustOxyLoss") - -/mob/living/proc/setOxyLoss(amount, updating_health = TRUE) - if(status_flags & GODMODE) - oxyloss = 0 - return FALSE //godmode - if(BREATHLESS in mutations) - oxyloss = 0 - return FALSE - var/old_oxyloss = oxyloss - oxyloss = amount - if(old_oxyloss == oxyloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth("setOxyLoss") - -/mob/living/proc/getToxLoss() - return toxloss - -/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE) - if(status_flags & GODMODE) - return FALSE //godmode - var/old_toxloss = toxloss - toxloss = max(toxloss + amount, 0) - if(old_toxloss == toxloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth("adjustToxLoss") - -/mob/living/proc/setToxLoss(amount, updating_health = TRUE) - if(status_flags & GODMODE) - return FALSE //godmode - var/old_toxloss = toxloss - toxloss = amount - if(old_toxloss == toxloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth("setToxLoss") - -/mob/living/proc/getFireLoss() - return fireloss - -/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE) - if(status_flags & GODMODE) - return FALSE //godmode - var/old_fireloss = fireloss - fireloss = max(fireloss + amount, 0) - if(old_fireloss == fireloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth("adjustFireLoss") - -/mob/living/proc/getCloneLoss() - return cloneloss - -/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE) - if(status_flags & GODMODE) - return FALSE //godmode - var/old_cloneloss = cloneloss - cloneloss = max(cloneloss + amount, 0) - if(old_cloneloss == cloneloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth("adjustCloneLoss") - -/mob/living/proc/setCloneLoss(amount, updating_health = TRUE) - if(status_flags & GODMODE) - return FALSE //godmode - var/old_cloneloss = cloneloss - cloneloss = amount - if(old_cloneloss == cloneloss) - updating_health = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_HEALTH - if(updating_health) - updatehealth("setCloneLoss") - -/mob/living/proc/getBrainLoss() - return 0 - -/mob/living/proc/adjustBrainLoss(amount, updating = TRUE) - return STATUS_UPDATE_NONE - -/mob/living/proc/setBrainLoss(amount, updating = TRUE) - return STATUS_UPDATE_NONE - -/mob/living/proc/getStaminaLoss() - return staminaloss - -/mob/living/proc/adjustStaminaLoss(amount, updating = TRUE) - if(status_flags & GODMODE) - return FALSE - var/old_stamloss = staminaloss - staminaloss = min(max(staminaloss + amount, 0), 120) - if(old_stamloss == staminaloss) - updating = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_STAMINA - if(amount > 0) - stam_regen_start_time = world.time + STAMINA_REGEN_BLOCK_TIME - if(updating) - update_stamina() - -/mob/living/proc/setStaminaLoss(amount, updating = TRUE) - if(status_flags & GODMODE) - return FALSE - var/old_stamloss = staminaloss - staminaloss = min(max(amount, 0), 120) - if(old_stamloss == staminaloss) - updating = FALSE - . = STATUS_UPDATE_NONE - else - . = STATUS_UPDATE_STAMINA - if(amount > 0) - stam_regen_start_time = world.time + STAMINA_REGEN_BLOCK_TIME - if(updating) - update_stamina() - -/mob/living/proc/getMaxHealth() - return maxHealth - -/mob/living/proc/setMaxHealth(var/newMaxHealth) - maxHealth = newMaxHealth - - - -// heal ONE external organ, organ gets randomly selected from damaged ones. -/mob/living/proc/heal_organ_damage(brute, burn, updating_health = TRUE) - adjustBruteLoss(-brute, FALSE) - adjustFireLoss(-burn, FALSE) - if(updating_health) - updatehealth("heal organ damage") - -// damage ONE external organ, organ gets randomly selected from damaged ones. -/mob/living/proc/take_organ_damage(brute, burn, updating_health = TRUE) - if(status_flags & GODMODE) - return FALSE //godmode - adjustBruteLoss(brute, FALSE) - adjustFireLoss(burn, FALSE) - if(updating_health) - updatehealth("take organ damage") - -// heal MANY external organs, in random order -/mob/living/proc/heal_overall_damage(brute, burn, updating_health = TRUE) - adjustBruteLoss(-brute, FALSE) - adjustFireLoss(-burn, FALSE) - if(updating_health) - updatehealth("heal overall damage") - -// damage MANY external organs, in random order -/mob/living/proc/take_overall_damage(brute, burn, updating_health = TRUE, used_weapon = null) - if(status_flags & GODMODE) - return FALSE //godmode - adjustBruteLoss(brute, FALSE) - adjustFireLoss(burn, FALSE) - if(updating_health) - updatehealth("take overall damage") - -/mob/living/proc/has_organic_damage() - return (maxHealth - health) - -//heal up to amount damage, in a given order -/mob/living/proc/heal_ordered_damage(amount, list/damage_types) - . = amount //we'll return the amount of damage healed - for(var/i in damage_types) - var/amount_to_heal = min(amount, get_damage_amount(i)) //heal only up to the amount of damage we have - if(amount_to_heal) - apply_damage_type(-amount_to_heal, i) - amount -= amount_to_heal //remove what we healed from our current amount - if(!amount) - break - . -= amount //if there's leftover healing, remove it from what we return \ No newline at end of file + +/* + apply_damage(a,b,c) + args + a:damage - How much damage to take + b:damage_type - What type of damage to take, brute, burn + c:def_zone - Where to take the damage if its brute or burn + Returns + standard 0 if fail +*/ +/mob/living/proc/apply_damage(var/damage = 0, var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/sharp = 0, var/used_weapon = null) + blocked = (100-blocked)/100 + if(!damage || (blocked <= 0)) return 0 + switch(damagetype) + if(BRUTE) + adjustBruteLoss(damage * blocked) + if(BURN) + adjustFireLoss(damage * blocked) + if(TOX) + adjustToxLoss(damage * blocked) + if(OXY) + adjustOxyLoss(damage * blocked) + if(CLONE) + adjustCloneLoss(damage * blocked) + if(STAMINA) + adjustStaminaLoss(damage * blocked) + updatehealth("apply damage") + return 1 + +/mob/living/proc/apply_damage_type(damage = 0, damagetype = BRUTE) //like apply damage except it always uses the damage procs + switch(damagetype) + if(BRUTE) + return adjustBruteLoss(damage) + if(BURN) + return adjustFireLoss(damage) + if(TOX) + return adjustToxLoss(damage) + if(OXY) + return adjustOxyLoss(damage) + if(CLONE) + return adjustCloneLoss(damage) + if(STAMINA) + return adjustStaminaLoss(damage) + if(BRAIN) + return adjustBrainLoss(damage) + +/mob/living/proc/get_damage_amount(damagetype = BRUTE) + switch(damagetype) + if(BRUTE) + return getBruteLoss() + if(BURN) + return getFireLoss() + if(TOX) + return getToxLoss() + if(OXY) + return getOxyLoss() + if(CLONE) + return getCloneLoss() + if(STAMINA) + return getStaminaLoss() + + +/mob/living/proc/apply_damages(var/brute = 0, var/burn = 0, var/tox = 0, var/oxy = 0, var/clone = 0, var/def_zone = null, var/blocked = 0, var/stamina = 0) + if(blocked >= 100) return 0 + if(brute) apply_damage(brute, BRUTE, def_zone, blocked) + if(burn) apply_damage(burn, BURN, def_zone, blocked) + if(tox) apply_damage(tox, TOX, def_zone, blocked) + if(oxy) apply_damage(oxy, OXY, def_zone, blocked) + if(clone) apply_damage(clone, CLONE, def_zone, blocked) + if(stamina) apply_damage(stamina, STAMINA, def_zone, blocked) + return 1 + + + +/mob/living/proc/apply_effect(var/effect = 0,var/effecttype = STUN, var/blocked = 0, var/negate_armor = 0) + blocked = (100-blocked)/100 + if(!effect || (blocked <= 0)) + return 0 + switch(effecttype) + if(STUN) + Stun(effect * blocked) + if(WEAKEN) + Weaken(effect * blocked) + if(PARALYZE) + Paralyse(effect * blocked) + if(IRRADIATE) + var/rad_damage = effect + if(!negate_armor) // Setting negate_armor overrides radiation armor checks, which are automatic otherwise + rad_damage = max(effect * ((100-run_armor_check(null, "rad", "Your clothes feel warm.", "Your clothes feel warm."))/100),0) + radiation += rad_damage + if(SLUR) + Slur(effect * blocked) + if(STUTTER) + Stuttering(effect * blocked) + if(EYE_BLUR) + EyeBlurry(effect * blocked) + if(DROWSY) + Drowsy(effect * blocked) + if(JITTER) + if(status_flags & CANSTUN) + Jitter(effect * blocked) + updatehealth("apply effect") + return 1 + +/mob/living/proc/apply_effects(var/stun = 0, var/weaken = 0, var/paralyze = 0, var/irradiate = 0, var/slur = 0, var/stutter = 0, var/eyeblur = 0, var/drowsy = 0, var/blocked = 0, var/stamina = 0, var/jitter = 0) + if(blocked >= 100) return 0 + if(stun) apply_effect(stun, STUN, blocked) + if(weaken) apply_effect(weaken, WEAKEN, blocked) + if(paralyze) apply_effect(paralyze, PARALYZE, blocked) + if(irradiate) apply_effect(irradiate, IRRADIATE, blocked) + if(slur) apply_effect(slur, SLUR, blocked) + if(stutter) apply_effect(stutter, STUTTER, blocked) + if(eyeblur) apply_effect(eyeblur, EYE_BLUR, blocked) + if(drowsy) apply_effect(drowsy, DROWSY, blocked) + if(stamina) apply_damage(stamina, STAMINA, null, blocked) + if(jitter) apply_effect(jitter, JITTER, blocked) + return 1 + + +/mob/living/proc/getBruteLoss() + return bruteloss + +/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE) + if(status_flags & GODMODE) + return FALSE //godmode + var/old_bruteloss = bruteloss + bruteloss = max(bruteloss + amount, 0) + if(old_bruteloss == bruteloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth("adjustBruteLoss") + +/mob/living/proc/getOxyLoss() + return oxyloss + +/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE) + if(status_flags & GODMODE) + oxyloss = 0 + return FALSE //godmode + if(BREATHLESS in mutations) + oxyloss = 0 + return FALSE + var/old_oxyloss = oxyloss + oxyloss = max(oxyloss + amount, 0) + if(old_oxyloss == oxyloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth("adjustOxyLoss") + +/mob/living/proc/setOxyLoss(amount, updating_health = TRUE) + if(status_flags & GODMODE) + oxyloss = 0 + return FALSE //godmode + if(BREATHLESS in mutations) + oxyloss = 0 + return FALSE + var/old_oxyloss = oxyloss + oxyloss = amount + if(old_oxyloss == oxyloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth("setOxyLoss") + +/mob/living/proc/getToxLoss() + return toxloss + +/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE) + if(status_flags & GODMODE) + return FALSE //godmode + var/old_toxloss = toxloss + toxloss = max(toxloss + amount, 0) + if(old_toxloss == toxloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth("adjustToxLoss") + +/mob/living/proc/setToxLoss(amount, updating_health = TRUE) + if(status_flags & GODMODE) + return FALSE //godmode + var/old_toxloss = toxloss + toxloss = amount + if(old_toxloss == toxloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth("setToxLoss") + +/mob/living/proc/getFireLoss() + return fireloss + +/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE) + if(status_flags & GODMODE) + return FALSE //godmode + var/old_fireloss = fireloss + fireloss = max(fireloss + amount, 0) + if(old_fireloss == fireloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth("adjustFireLoss") + +/mob/living/proc/getCloneLoss() + return cloneloss + +/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE) + if(status_flags & GODMODE) + return FALSE //godmode + var/old_cloneloss = cloneloss + cloneloss = max(cloneloss + amount, 0) + if(old_cloneloss == cloneloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth("adjustCloneLoss") + +/mob/living/proc/setCloneLoss(amount, updating_health = TRUE) + if(status_flags & GODMODE) + return FALSE //godmode + var/old_cloneloss = cloneloss + cloneloss = amount + if(old_cloneloss == cloneloss) + updating_health = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_HEALTH + if(updating_health) + updatehealth("setCloneLoss") + +/mob/living/proc/getBrainLoss() + return 0 + +/mob/living/proc/adjustBrainLoss(amount, updating = TRUE) + return STATUS_UPDATE_NONE + +/mob/living/proc/setBrainLoss(amount, updating = TRUE) + return STATUS_UPDATE_NONE + +/mob/living/proc/getStaminaLoss() + return staminaloss + +/mob/living/proc/adjustStaminaLoss(amount, updating = TRUE) + if(status_flags & GODMODE) + return FALSE + var/old_stamloss = staminaloss + staminaloss = min(max(staminaloss + amount, 0), 120) + if(old_stamloss == staminaloss) + updating = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_STAMINA + if(amount > 0) + stam_regen_start_time = world.time + STAMINA_REGEN_BLOCK_TIME + if(updating) + update_stamina() + +/mob/living/proc/setStaminaLoss(amount, updating = TRUE) + if(status_flags & GODMODE) + return FALSE + var/old_stamloss = staminaloss + staminaloss = min(max(amount, 0), 120) + if(old_stamloss == staminaloss) + updating = FALSE + . = STATUS_UPDATE_NONE + else + . = STATUS_UPDATE_STAMINA + if(amount > 0) + stam_regen_start_time = world.time + STAMINA_REGEN_BLOCK_TIME + if(updating) + update_stamina() + +/mob/living/proc/getMaxHealth() + return maxHealth + +/mob/living/proc/setMaxHealth(var/newMaxHealth) + maxHealth = newMaxHealth + + + +// heal ONE external organ, organ gets randomly selected from damaged ones. +/mob/living/proc/heal_organ_damage(brute, burn, updating_health = TRUE) + adjustBruteLoss(-brute, FALSE) + adjustFireLoss(-burn, FALSE) + if(updating_health) + updatehealth("heal organ damage") + +// damage ONE external organ, organ gets randomly selected from damaged ones. +/mob/living/proc/take_organ_damage(brute, burn, updating_health = TRUE) + if(status_flags & GODMODE) + return FALSE //godmode + adjustBruteLoss(brute, FALSE) + adjustFireLoss(burn, FALSE) + if(updating_health) + updatehealth("take organ damage") + +// heal MANY external organs, in random order +/mob/living/proc/heal_overall_damage(brute, burn, updating_health = TRUE) + adjustBruteLoss(-brute, FALSE) + adjustFireLoss(-burn, FALSE) + if(updating_health) + updatehealth("heal overall damage") + +// damage MANY external organs, in random order +/mob/living/proc/take_overall_damage(brute, burn, updating_health = TRUE, used_weapon = null) + if(status_flags & GODMODE) + return FALSE //godmode + adjustBruteLoss(brute, FALSE) + adjustFireLoss(burn, FALSE) + if(updating_health) + updatehealth("take overall damage") + +/mob/living/proc/has_organic_damage() + return (maxHealth - health) + +//heal up to amount damage, in a given order +/mob/living/proc/heal_ordered_damage(amount, list/damage_types) + . = amount //we'll return the amount of damage healed + for(var/i in damage_types) + var/amount_to_heal = min(amount, get_damage_amount(i)) //heal only up to the amount of damage we have + if(amount_to_heal) + apply_damage_type(-amount_to_heal, i) + amount -= amount_to_heal //remove what we healed from our current amount + if(!amount) + break + . -= amount //if there's leftover healing, remove it from what we return diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 6054068eb15..4d8aa1b776b 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -100,4 +100,4 @@ visible_message("[src] starts convulsing violently!", "You feel as if your body is tearing itself apart!") Weaken(15) do_jitter_animation(1000, -1) - addtimer(CALLBACK(src, .proc/gib), rand(20, 100)) \ No newline at end of file + addtimer(CALLBACK(src, .proc/gib), rand(20, 100)) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 4909148b492..5ab604e5b3e 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -220,4 +220,4 @@ return /mob/living/proc/handle_hud_icons_health() - return \ No newline at end of file + return diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index aaa11710642..6e4f13d735f 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -1,360 +1,360 @@ - -/* - run_armor_check(a,b) - args - a:def_zone - What part is getting hit, if null will check entire body - b:attack_flag - What type of attack, bullet, laser, energy, melee - - Returns - 0 - no block - 1 - halfblock - 2 - fullblock -*/ -/mob/living/proc/run_armor_check(var/def_zone = null, var/attack_flag = "melee", var/absorb_text = null, var/soften_text = null, armour_penetration, penetrated_text) - var/armor = getarmor(def_zone, attack_flag) - - //the if "armor" check is because this is used for everything on /living, including humans - if(armor && armor < 100 && armour_penetration) // Armor with 100+ protection can not be penetrated for admin items - armor = max(0, armor - armour_penetration) - if(penetrated_text) - to_chat(src, "[penetrated_text]") - else - to_chat(src, "Your armor was penetrated!") - - if(armor >= 100) - if(absorb_text) - to_chat(src, "[absorb_text]") - else - to_chat(src, "Your armor absorbs the blow!") - else if(armor > 0) - if(soften_text) - to_chat(src, "[soften_text]") - else - to_chat(src, "Your armor softens the blow!") - return armor - -//if null is passed for def_zone, then this should return something appropriate for all zones (e.g. area effect damage) -/mob/living/proc/getarmor(var/def_zone, var/type) - return 0 - -/mob/living/proc/is_mouth_covered(head_only = FALSE, mask_only = FALSE) - return FALSE - -/mob/living/proc/is_eyes_covered(check_glasses = TRUE, check_head = TRUE, check_mask = TRUE) - return FALSE - -/mob/living/bullet_act(var/obj/item/projectile/P, var/def_zone) - //Armor - var/armor = run_armor_check(def_zone, P.flag, armour_penetration = P.armour_penetration) - if(!P.nodamage) - apply_damage(P.damage, P.damage_type, def_zone, armor) - if(P.dismemberment) - check_projectile_dismemberment(P, def_zone) - return P.on_hit(src, armor, def_zone) - -/mob/living/proc/check_projectile_dismemberment(obj/item/projectile/P, def_zone) - return 0 - -/mob/living/proc/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE) - SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage) - if(status_flags & GODMODE) //godmode - return FALSE - if(NO_SHOCK in mutations) //shockproof - return FALSE - if(tesla_shock && tesla_ignore) - return FALSE - if(shock_damage > 0) - if(!illusion) - adjustFireLoss(shock_damage) - visible_message( - "[src] was shocked by \the [source]!", - "You feel a powerful shock coursing through your body!", - "You hear a heavy electrical crack.") - return shock_damage - -/mob/living/emp_act(severity) - var/list/L = src.get_contents() - for(var/obj/O in L) - O.emp_act(severity) - ..() - -/obj/item/proc/get_volume_by_throwforce_and_or_w_class() - if(throwforce && w_class) - return Clamp((throwforce + w_class) * 5, 30, 100)// Add the item's throwforce to its weight class and multiply by 5, then clamp the value between 30 and 100 - else if(w_class) - return Clamp(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100 - else - return 0 - -//this proc handles being hit by a thrown atom -/mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) - if(istype(AM, /obj/item)) - var/obj/item/I = AM - var/zone = ran_zone("chest", 65)//Hits a random part of the body, geared towards the chest - var/dtype = BRUTE - var/volume = I.get_volume_by_throwforce_and_or_w_class() - SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone) - dtype = I.damtype - - if(I.throwforce > 0) //If the weapon's throwforce is greater than zero... - if(I.throwhitsound) //...and throwhitsound is defined... - playsound(loc, I.throwhitsound, volume, TRUE, -1) //...play the weapon's throwhitsound. - else if(I.hitsound) //Otherwise, if the weapon's hitsound is defined... - playsound(loc, I.hitsound, volume, TRUE, -1) //...play the weapon's hitsound. - else if(!I.throwhitsound) //Otherwise, if throwhitsound isn't defined... - playsound(loc, 'sound/weapons/genhit.ogg',volume, TRUE, -1) //...play genhit.ogg. - - else if(!I.throwhitsound && I.throwforce > 0) //Otherwise, if the item doesn't have a throwhitsound and has a throwforce greater than zero... - playsound(loc, 'sound/weapons/genhit1.ogg', volume, 1, -1)//...play genhit1.ogg - if(!I.throwforce)// Otherwise, if the item's throwforce is 0... - playsound(loc, 'sound/weapons/throwtap.ogg', 1, volume, -1)//...play throwtap.ogg. - if(!blocked) - visible_message("[src] has been hit by [I].", - "[src] has been hit by [I].") - var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].", I.armour_penetration) - apply_damage(I.throwforce, dtype, zone, armor, is_sharp(I), I) - if(I.thrownby) - add_attack_logs(I.thrownby, src, "Hit with thrown [I]") - else - return 1 - else - playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) - ..() - - -/mob/living/mech_melee_attack(obj/mecha/M) - if(M.occupant.a_intent == INTENT_HARM) - if(HAS_TRAIT(M.occupant, TRAIT_PACIFISM)) - to_chat(M.occupant, "You don't want to harm other living beings!") - return - M.do_attack_animation(src) - if(M.damtype == "brute") - step_away(src,M,15) - switch(M.damtype) - if("brute") - Paralyse(1) - take_overall_damage(rand(M.force/2, M.force)) - playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) - if("fire") - take_overall_damage(0, rand(M.force/2, M.force)) - playsound(src, 'sound/items/welder.ogg', 50, TRUE) - if("tox") - M.mech_toxin_damage(src) - else - return - updatehealth("mech melee attack") - M.occupant_message("You hit [src].") - visible_message("[M.name] hits [src]!", "[M.name] hits you!") - add_attack_logs(M.occupant, src, "Mecha-meleed with [M]") - else - step_away(src,M) - add_attack_logs(M.occupant, src, "Mecha-pushed with [M]", ATKLOG_ALL) - M.occupant_message("You push [src] out of the way.") - visible_message("[M] pushes [src] out of the way.") - -//Mobs on Fire -/mob/living/proc/IgniteMob() - if(fire_stacks > 0 && !on_fire) - on_fire = 1 - visible_message("[src] catches fire!", \ - "You're set on fire!") - set_light(light_range + 3,l_color = "#ED9200") - throw_alert("fire", /obj/screen/alert/fire) - update_fire() - return 1 - return 0 - -/mob/living/proc/ExtinguishMob() - if(on_fire) - on_fire = 0 - fire_stacks = 0 - set_light(max(0,light_range - 3)) - clear_alert("fire") - update_fire() - -/mob/living/proc/update_fire() - return - -/mob/living/proc/adjust_fire_stacks(add_fire_stacks) //Adjusting the amount of fire_stacks we have on person - fire_stacks = Clamp(fire_stacks + add_fire_stacks, -20, 20) - if(on_fire && fire_stacks <= 0) - ExtinguishMob() - -/mob/living/proc/handle_fire() - if(fire_stacks < 0) //If we've doused ourselves in water to avoid fire, dry off slowly - fire_stacks = min(0, fire_stacks + 1)//So we dry ourselves back to default, nonflammable. - if(!on_fire) - return 1 - if(fire_stacks > 0) - adjust_fire_stacks(-0.1) //the fire is slowly consumed - else - ExtinguishMob() - return - var/datum/gas_mixture/G = loc.return_air() // Check if we're standing in an oxygenless environment - if(G.oxygen < 1) - ExtinguishMob() //If there's no oxygen in the tile we're on, put out the fire - return - var/turf/location = get_turf(src) - location.hotspot_expose(700, 50, 1) - -/mob/living/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - ..() - adjust_fire_stacks(3) - IgniteMob() - -//Share fire evenly between the two mobs -//Called in MobBump() and Crossed() -/mob/living/proc/spreadFire(mob/living/L) - if(!istype(L)) - return - var/L_old_on_fire = L.on_fire - - if(on_fire) //Only spread fire stacks if we're on fire - fire_stacks /= 2 - L.fire_stacks += fire_stacks - if(L.IgniteMob()) - log_game("[key_name(src)] bumped into [key_name(L)] and set them on fire") - - if(L_old_on_fire) //Only ignite us and gain their stacks if they were onfire before we bumped them - L.fire_stacks /= 2 - fire_stacks += L.fire_stacks - IgniteMob() - -/mob/living/can_be_pulled(user, grab_state, force) - return ..() && !(buckled && buckled.buckle_prevents_pull) - -/mob/living/water_act(volume, temperature, source, method = REAGENT_TOUCH) - . = ..() - adjust_fire_stacks(-(volume * 0.2)) - -//This is called when the mob is thrown into a dense turf -/mob/living/proc/turf_collision(var/turf/T, var/speed) - src.take_organ_damage(speed*5) - -/mob/living/proc/near_wall(var/direction,var/distance=1) - var/turf/T = get_step(get_turf(src),direction) - var/turf/last_turf = src.loc - var/i = 1 - - while(i>0 && i<=distance) - if(T.density) //Turf is a wall! - return last_turf - i++ - last_turf = T - T = get_step(T,direction) - - return 0 - -// End BS12 momentum-transfer code. - -/mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = FALSE) - if(user == src || anchored) - return 0 - if(!(status_flags & CANPUSH)) - return 0 - - for(var/obj/item/grab/G in grabbed_by) - if(G.assailant == user) - to_chat(user, "You already grabbed [src].") - return - - add_attack_logs(user, src, "Grabbed passively", ATKLOG_ALL) - - var/obj/item/grab/G = new /obj/item/grab(user, src) - if(buckled) - to_chat(user, "You cannot grab [src]; [p_they()] [p_are()] buckled in!") - if(!G) //the grab will delete itself in New if src is anchored - return 0 - user.put_in_active_hand(G) - G.synch() - LAssailant = user - - playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - /*if(user.dir == src.dir) - G.state = GRAB_AGGRESSIVE - G.last_upgrade = world.time - if(!supress_message) - visible_message("[user] has grabbed [src] from behind!") - else*///This is an example of how you can make special types of grabs simply based on direction. - if(!supress_message) - visible_message("[user] has grabbed [src] passively!") - - return G - -/mob/living/attack_slime(mob/living/simple_animal/slime/M) - if(!SSticker) - to_chat(M, "You cannot attack people before the game has started.") - return - - if(M.buckled) - if(M in buckled_mobs) - M.Feedstop() - return // can't attack while eating! - - if(HAS_TRAIT(src, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt anyone!") - return FALSE - - if(stat != DEAD) - add_attack_logs(M, src, "Slime'd") - M.do_attack_animation(src) - visible_message("\The [M.name] glomps [src]!", "\The [M.name] glomps you!") - return TRUE - -/mob/living/attack_animal(mob/living/simple_animal/M) - M.face_atom(src) - if((M.a_intent == INTENT_HELP && M.ckey) || M.melee_damage_upper == 0) - M.custom_emote(1, "[M.friendly] [src].") - return FALSE - if(HAS_TRAIT(M, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt anyone!") - return FALSE - - if(M.attack_sound) - playsound(loc, M.attack_sound, 50, 1, 1) - M.do_attack_animation(src) - visible_message("\The [M] [M.attacktext] [src]!", \ - "\The [M] [M.attacktext] [src]!") - add_attack_logs(M, src, "Animal attacked") - return TRUE - -/mob/living/attack_larva(mob/living/carbon/alien/larva/L) - switch(L.a_intent) - if(INTENT_HELP) - visible_message("[L.name] rubs its head against [src].") - return 0 - - else - if(HAS_TRAIT(L, TRAIT_PACIFISM)) - to_chat(L, "You don't want to hurt anyone!") - return - - L.do_attack_animation(src) - if(prob(90)) - add_attack_logs(L, src, "Larva attacked") - visible_message("[L.name] bites [src]!", \ - "[L.name] bites [src]!") - playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) - return 1 - else - visible_message("[L.name] has attempted to bite [src]!", \ - "[L.name] has attempted to bite [src]!") - return 0 - -/mob/living/attack_alien(mob/living/carbon/alien/humanoid/M) - switch(M.a_intent) - if(INTENT_HELP) - visible_message("[M] caresses [src] with its scythe like arm.") - return FALSE - if(INTENT_GRAB) - grabbedby(M) - return FALSE - if(INTENT_HARM) - if(HAS_TRAIT(M, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt anyone!") - return FALSE - M.do_attack_animation(src) - return TRUE - if(INTENT_DISARM) - M.do_attack_animation(src, ATTACK_EFFECT_DISARM) - return TRUE \ No newline at end of file + +/* + run_armor_check(a,b) + args + a:def_zone - What part is getting hit, if null will check entire body + b:attack_flag - What type of attack, bullet, laser, energy, melee + + Returns + 0 - no block + 1 - halfblock + 2 - fullblock +*/ +/mob/living/proc/run_armor_check(var/def_zone = null, var/attack_flag = "melee", var/absorb_text = null, var/soften_text = null, armour_penetration, penetrated_text) + var/armor = getarmor(def_zone, attack_flag) + + //the if "armor" check is because this is used for everything on /living, including humans + if(armor && armor < 100 && armour_penetration) // Armor with 100+ protection can not be penetrated for admin items + armor = max(0, armor - armour_penetration) + if(penetrated_text) + to_chat(src, "[penetrated_text]") + else + to_chat(src, "Your armor was penetrated!") + + if(armor >= 100) + if(absorb_text) + to_chat(src, "[absorb_text]") + else + to_chat(src, "Your armor absorbs the blow!") + else if(armor > 0) + if(soften_text) + to_chat(src, "[soften_text]") + else + to_chat(src, "Your armor softens the blow!") + return armor + +//if null is passed for def_zone, then this should return something appropriate for all zones (e.g. area effect damage) +/mob/living/proc/getarmor(var/def_zone, var/type) + return 0 + +/mob/living/proc/is_mouth_covered(head_only = FALSE, mask_only = FALSE) + return FALSE + +/mob/living/proc/is_eyes_covered(check_glasses = TRUE, check_head = TRUE, check_mask = TRUE) + return FALSE + +/mob/living/bullet_act(var/obj/item/projectile/P, var/def_zone) + //Armor + var/armor = run_armor_check(def_zone, P.flag, armour_penetration = P.armour_penetration) + if(!P.nodamage) + apply_damage(P.damage, P.damage_type, def_zone, armor) + if(P.dismemberment) + check_projectile_dismemberment(P, def_zone) + return P.on_hit(src, armor, def_zone) + +/mob/living/proc/check_projectile_dismemberment(obj/item/projectile/P, def_zone) + return 0 + +/mob/living/proc/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE) + SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage) + if(status_flags & GODMODE) //godmode + return FALSE + if(NO_SHOCK in mutations) //shockproof + return FALSE + if(tesla_shock && tesla_ignore) + return FALSE + if(shock_damage > 0) + if(!illusion) + adjustFireLoss(shock_damage) + visible_message( + "[src] was shocked by \the [source]!", + "You feel a powerful shock coursing through your body!", + "You hear a heavy electrical crack.") + return shock_damage + +/mob/living/emp_act(severity) + var/list/L = src.get_contents() + for(var/obj/O in L) + O.emp_act(severity) + ..() + +/obj/item/proc/get_volume_by_throwforce_and_or_w_class() + if(throwforce && w_class) + return Clamp((throwforce + w_class) * 5, 30, 100)// Add the item's throwforce to its weight class and multiply by 5, then clamp the value between 30 and 100 + else if(w_class) + return Clamp(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100 + else + return 0 + +//this proc handles being hit by a thrown atom +/mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + if(istype(AM, /obj/item)) + var/obj/item/I = AM + var/zone = ran_zone("chest", 65)//Hits a random part of the body, geared towards the chest + var/dtype = BRUTE + var/volume = I.get_volume_by_throwforce_and_or_w_class() + SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone) + dtype = I.damtype + + if(I.throwforce > 0) //If the weapon's throwforce is greater than zero... + if(I.throwhitsound) //...and throwhitsound is defined... + playsound(loc, I.throwhitsound, volume, TRUE, -1) //...play the weapon's throwhitsound. + else if(I.hitsound) //Otherwise, if the weapon's hitsound is defined... + playsound(loc, I.hitsound, volume, TRUE, -1) //...play the weapon's hitsound. + else if(!I.throwhitsound) //Otherwise, if throwhitsound isn't defined... + playsound(loc, 'sound/weapons/genhit.ogg',volume, TRUE, -1) //...play genhit.ogg. + + else if(!I.throwhitsound && I.throwforce > 0) //Otherwise, if the item doesn't have a throwhitsound and has a throwforce greater than zero... + playsound(loc, 'sound/weapons/genhit1.ogg', volume, 1, -1)//...play genhit1.ogg + if(!I.throwforce)// Otherwise, if the item's throwforce is 0... + playsound(loc, 'sound/weapons/throwtap.ogg', 1, volume, -1)//...play throwtap.ogg. + if(!blocked) + visible_message("[src] has been hit by [I].", + "[src] has been hit by [I].") + var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].", I.armour_penetration) + apply_damage(I.throwforce, dtype, zone, armor, is_sharp(I), I) + if(I.thrownby) + add_attack_logs(I.thrownby, src, "Hit with thrown [I]") + else + return 1 + else + playsound(loc, 'sound/weapons/genhit.ogg', 50, TRUE, -1) + ..() + + +/mob/living/mech_melee_attack(obj/mecha/M) + if(M.occupant.a_intent == INTENT_HARM) + if(HAS_TRAIT(M.occupant, TRAIT_PACIFISM)) + to_chat(M.occupant, "You don't want to harm other living beings!") + return + M.do_attack_animation(src) + if(M.damtype == "brute") + step_away(src,M,15) + switch(M.damtype) + if("brute") + Paralyse(1) + take_overall_damage(rand(M.force/2, M.force)) + playsound(src, 'sound/weapons/punch4.ogg', 50, TRUE) + if("fire") + take_overall_damage(0, rand(M.force/2, M.force)) + playsound(src, 'sound/items/welder.ogg', 50, TRUE) + if("tox") + M.mech_toxin_damage(src) + else + return + updatehealth("mech melee attack") + M.occupant_message("You hit [src].") + visible_message("[M.name] hits [src]!", "[M.name] hits you!") + add_attack_logs(M.occupant, src, "Mecha-meleed with [M]") + else + step_away(src,M) + add_attack_logs(M.occupant, src, "Mecha-pushed with [M]", ATKLOG_ALL) + M.occupant_message("You push [src] out of the way.") + visible_message("[M] pushes [src] out of the way.") + +//Mobs on Fire +/mob/living/proc/IgniteMob() + if(fire_stacks > 0 && !on_fire) + on_fire = 1 + visible_message("[src] catches fire!", \ + "You're set on fire!") + set_light(light_range + 3,l_color = "#ED9200") + throw_alert("fire", /obj/screen/alert/fire) + update_fire() + return 1 + return 0 + +/mob/living/proc/ExtinguishMob() + if(on_fire) + on_fire = 0 + fire_stacks = 0 + set_light(max(0,light_range - 3)) + clear_alert("fire") + update_fire() + +/mob/living/proc/update_fire() + return + +/mob/living/proc/adjust_fire_stacks(add_fire_stacks) //Adjusting the amount of fire_stacks we have on person + fire_stacks = Clamp(fire_stacks + add_fire_stacks, -20, 20) + if(on_fire && fire_stacks <= 0) + ExtinguishMob() + +/mob/living/proc/handle_fire() + if(fire_stacks < 0) //If we've doused ourselves in water to avoid fire, dry off slowly + fire_stacks = min(0, fire_stacks + 1)//So we dry ourselves back to default, nonflammable. + if(!on_fire) + return 1 + if(fire_stacks > 0) + adjust_fire_stacks(-0.1) //the fire is slowly consumed + else + ExtinguishMob() + return + var/datum/gas_mixture/G = loc.return_air() // Check if we're standing in an oxygenless environment + if(G.oxygen < 1) + ExtinguishMob() //If there's no oxygen in the tile we're on, put out the fire + return + var/turf/location = get_turf(src) + location.hotspot_expose(700, 50, 1) + +/mob/living/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + ..() + adjust_fire_stacks(3) + IgniteMob() + +//Share fire evenly between the two mobs +//Called in MobBump() and Crossed() +/mob/living/proc/spreadFire(mob/living/L) + if(!istype(L)) + return + var/L_old_on_fire = L.on_fire + + if(on_fire) //Only spread fire stacks if we're on fire + fire_stacks /= 2 + L.fire_stacks += fire_stacks + if(L.IgniteMob()) + log_game("[key_name(src)] bumped into [key_name(L)] and set them on fire") + + if(L_old_on_fire) //Only ignite us and gain their stacks if they were onfire before we bumped them + L.fire_stacks /= 2 + fire_stacks += L.fire_stacks + IgniteMob() + +/mob/living/can_be_pulled(user, grab_state, force) + return ..() && !(buckled && buckled.buckle_prevents_pull) + +/mob/living/water_act(volume, temperature, source, method = REAGENT_TOUCH) + . = ..() + adjust_fire_stacks(-(volume * 0.2)) + +//This is called when the mob is thrown into a dense turf +/mob/living/proc/turf_collision(var/turf/T, var/speed) + src.take_organ_damage(speed*5) + +/mob/living/proc/near_wall(var/direction,var/distance=1) + var/turf/T = get_step(get_turf(src),direction) + var/turf/last_turf = src.loc + var/i = 1 + + while(i>0 && i<=distance) + if(T.density) //Turf is a wall! + return last_turf + i++ + last_turf = T + T = get_step(T,direction) + + return 0 + +// End BS12 momentum-transfer code. + +/mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = FALSE) + if(user == src || anchored) + return 0 + if(!(status_flags & CANPUSH)) + return 0 + + for(var/obj/item/grab/G in grabbed_by) + if(G.assailant == user) + to_chat(user, "You already grabbed [src].") + return + + add_attack_logs(user, src, "Grabbed passively", ATKLOG_ALL) + + var/obj/item/grab/G = new /obj/item/grab(user, src) + if(buckled) + to_chat(user, "You cannot grab [src]; [p_they()] [p_are()] buckled in!") + if(!G) //the grab will delete itself in New if src is anchored + return 0 + user.put_in_active_hand(G) + G.synch() + LAssailant = user + + playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + /*if(user.dir == src.dir) + G.state = GRAB_AGGRESSIVE + G.last_upgrade = world.time + if(!supress_message) + visible_message("[user] has grabbed [src] from behind!") + else*///This is an example of how you can make special types of grabs simply based on direction. + if(!supress_message) + visible_message("[user] has grabbed [src] passively!") + + return G + +/mob/living/attack_slime(mob/living/simple_animal/slime/M) + if(!SSticker) + to_chat(M, "You cannot attack people before the game has started.") + return + + if(M.buckled) + if(M in buckled_mobs) + M.Feedstop() + return // can't attack while eating! + + if(HAS_TRAIT(src, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt anyone!") + return FALSE + + if(stat != DEAD) + add_attack_logs(M, src, "Slime'd") + M.do_attack_animation(src) + visible_message("\The [M.name] glomps [src]!", "\The [M.name] glomps you!") + return TRUE + +/mob/living/attack_animal(mob/living/simple_animal/M) + M.face_atom(src) + if((M.a_intent == INTENT_HELP && M.ckey) || M.melee_damage_upper == 0) + M.custom_emote(1, "[M.friendly] [src].") + return FALSE + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt anyone!") + return FALSE + + if(M.attack_sound) + playsound(loc, M.attack_sound, 50, 1, 1) + M.do_attack_animation(src) + visible_message("\The [M] [M.attacktext] [src]!", \ + "\The [M] [M.attacktext] [src]!") + add_attack_logs(M, src, "Animal attacked") + return TRUE + +/mob/living/attack_larva(mob/living/carbon/alien/larva/L) + switch(L.a_intent) + if(INTENT_HELP) + visible_message("[L.name] rubs its head against [src].") + return 0 + + else + if(HAS_TRAIT(L, TRAIT_PACIFISM)) + to_chat(L, "You don't want to hurt anyone!") + return + + L.do_attack_animation(src) + if(prob(90)) + add_attack_logs(L, src, "Larva attacked") + visible_message("[L.name] bites [src]!", \ + "[L.name] bites [src]!") + playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) + return 1 + else + visible_message("[L.name] has attempted to bite [src]!", \ + "[L.name] has attempted to bite [src]!") + return 0 + +/mob/living/attack_alien(mob/living/carbon/alien/humanoid/M) + switch(M.a_intent) + if(INTENT_HELP) + visible_message("[M] caresses [src] with its scythe like arm.") + return FALSE + if(INTENT_GRAB) + grabbedby(M) + return FALSE + if(INTENT_HARM) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt anyone!") + return FALSE + M.do_attack_animation(src) + return TRUE + if(INTENT_DISARM) + M.do_attack_animation(src, ATTACK_EFFECT_DISARM) + return TRUE diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 8f5ed797b6c..840f30c20ff 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -1,74 +1,74 @@ -/mob/living - see_invisible = SEE_INVISIBLE_LIVING - pressure_resistance = 10 - - //Health and life related vars - var/maxHealth = 100 //Maximum health that should be possible. - var/health = 100 //A mob's health - - - //Damage related vars, NOTE: THESE SHOULD ONLY BE MODIFIED BY PROCS - var/bruteloss = 0 //Brutal damage caused by brute force (punching, being clubbed by a toolbox ect... this also accounts for pressure damage) - var/oxyloss = 0 //Oxygen depravation damage (no air in lungs) - var/toxloss = 0 //Toxic damage caused by being poisoned or radiated - var/fireloss = 0 //Burn damage caused by being way too hot, too cold or burnt. - var/cloneloss = 0 //Damage caused by being cloned or ejected from the cloner early. slimes also deal cloneloss damage to victims - var/staminaloss = 0 //Stamina damage, or exhaustion. You recover it slowly naturally, and are stunned if it gets too high. Holodeck and hallucinations deal this. - - - var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. - - //Allows mobs to move through dense areas without restriction. For instance, in space or out of holder objects. - var/incorporeal_move = 0 //0 is off, 1 is normal, 2 is for ninjas. - - var/now_pushing = null - - var/atom/movable/cameraFollow = null - - var/on_fire = 0 //The "Are we on fire?" var - var/fire_stacks = 0 //Tracks how many stacks of fire we have on, max is usually 20 - - var/implanting = 0 //Used for the mind-slave implant - var/floating = 0 - var/mob_size = MOB_SIZE_HUMAN - var/metabolism_efficiency = 1 //more or less efficiency to metabolize helpful/harmful reagents and regulate body temperature.. - var/digestion_ratio = 1 //controls how quickly reagents metabolize; largely governered by species attributes. - - var/bloodcrawl = 0 //0 No blood crawling, 1 blood crawling, 2 blood crawling+mob devour - var/holder = null //The holder for blood crawling - - var/ventcrawler = 0 //0 No vent crawling, 1 vent crawling in the nude, 2 vent crawling always - var/list/icon/pipes_shown = list() - var/last_played_vent - - var/smoke_delay = 0 //used to prevent spam with smoke reagent reaction on mob. - - var/step_count = 0 - - var/list/butcher_results = null - - var/list/weather_immunities = list() - - var/list/surgeries = list() //a list of surgery datums. generally empty, they're added when the player wants them. - - var/gene_stability = DEFAULT_GENE_STABILITY - var/ignore_gene_stability = 0 - - var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override - - var/tesla_ignore = FALSE - - var/list/say_log = list() //a log of what we've said, plain text, no spans or junk, essentially just each individual "message" - var/list/emote_log = list() //like say_log but for emotes - - var/list/recent_tastes = list() - var/blood_volume = 0 //how much blood the mob has - hud_possible = list(HEALTH_HUD,STATUS_HUD,SPECIALROLE_HUD) - - var/list/status_effects //a list of all status effects the mob has - - var/deathgasp_on_death = FALSE - - var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added - var/stam_regen_start_time = 0 //used to halt stamina regen temporarily - var/stam_paralyzed = FALSE //knocks you down \ No newline at end of file +/mob/living + see_invisible = SEE_INVISIBLE_LIVING + pressure_resistance = 10 + + //Health and life related vars + var/maxHealth = 100 //Maximum health that should be possible. + var/health = 100 //A mob's health + + + //Damage related vars, NOTE: THESE SHOULD ONLY BE MODIFIED BY PROCS + var/bruteloss = 0 //Brutal damage caused by brute force (punching, being clubbed by a toolbox ect... this also accounts for pressure damage) + var/oxyloss = 0 //Oxygen depravation damage (no air in lungs) + var/toxloss = 0 //Toxic damage caused by being poisoned or radiated + var/fireloss = 0 //Burn damage caused by being way too hot, too cold or burnt. + var/cloneloss = 0 //Damage caused by being cloned or ejected from the cloner early. slimes also deal cloneloss damage to victims + var/staminaloss = 0 //Stamina damage, or exhaustion. You recover it slowly naturally, and are stunned if it gets too high. Holodeck and hallucinations deal this. + + + var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. + + //Allows mobs to move through dense areas without restriction. For instance, in space or out of holder objects. + var/incorporeal_move = 0 //0 is off, 1 is normal, 2 is for ninjas. + + var/now_pushing = null + + var/atom/movable/cameraFollow = null + + var/on_fire = 0 //The "Are we on fire?" var + var/fire_stacks = 0 //Tracks how many stacks of fire we have on, max is usually 20 + + var/implanting = 0 //Used for the mind-slave implant + var/floating = 0 + var/mob_size = MOB_SIZE_HUMAN + var/metabolism_efficiency = 1 //more or less efficiency to metabolize helpful/harmful reagents and regulate body temperature.. + var/digestion_ratio = 1 //controls how quickly reagents metabolize; largely governered by species attributes. + + var/bloodcrawl = 0 //0 No blood crawling, 1 blood crawling, 2 blood crawling+mob devour + var/holder = null //The holder for blood crawling + + var/ventcrawler = 0 //0 No vent crawling, 1 vent crawling in the nude, 2 vent crawling always + var/list/icon/pipes_shown = list() + var/last_played_vent + + var/smoke_delay = 0 //used to prevent spam with smoke reagent reaction on mob. + + var/step_count = 0 + + var/list/butcher_results = null + + var/list/weather_immunities = list() + + var/list/surgeries = list() //a list of surgery datums. generally empty, they're added when the player wants them. + + var/gene_stability = DEFAULT_GENE_STABILITY + var/ignore_gene_stability = 0 + + var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override + + var/tesla_ignore = FALSE + + var/list/say_log = list() //a log of what we've said, plain text, no spans or junk, essentially just each individual "message" + var/list/emote_log = list() //like say_log but for emotes + + var/list/recent_tastes = list() + var/blood_volume = 0 //how much blood the mob has + hud_possible = list(HEALTH_HUD,STATUS_HUD,SPECIALROLE_HUD) + + var/list/status_effects //a list of all status effects the mob has + + var/deathgasp_on_death = FALSE + + var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added + var/stam_regen_start_time = 0 //used to halt stamina regen temporarily + var/stam_paralyzed = FALSE //knocks you down diff --git a/code/modules/mob/living/logout.dm b/code/modules/mob/living/logout.dm index e1c33f89a7e..fc343a4fc98 100644 --- a/code/modules/mob/living/logout.dm +++ b/code/modules/mob/living/logout.dm @@ -1,13 +1,13 @@ -/mob/living/Logout() - update_z(null) - if(ranged_ability && client) - ranged_ability.remove_mousepointer(client) - ..() - if(mind) - if(!key) //key and mind have become seperated. I believe this is for when a staff member aghosts. - mind.active = 0 //This is to stop say, a mind.transfer_to call on a corpse causing a ghost to re-enter its body. - //This causes instant sleep and tags a player as SSD. See life.dm for furthering SSD. - if(mind.active) - Sleeping(2) - player_logged = 1 - last_logout = world.time \ No newline at end of file +/mob/living/Logout() + update_z(null) + if(ranged_ability && client) + ranged_ability.remove_mousepointer(client) + ..() + if(mind) + if(!key) //key and mind have become seperated. I believe this is for when a staff member aghosts. + mind.active = 0 //This is to stop say, a mind.transfer_to call on a corpse causing a ghost to re-enter its body. + //This causes instant sleep and tags a player as SSD. See life.dm for furthering SSD. + if(mind.active) + Sleeping(2) + player_logged = 1 + last_logout = world.time diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 4d149e0f1ea..73ad5eabaca 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -1,475 +1,475 @@ -var/list/department_radio_keys = list( - ":r" = "right ear", "#r" = "right ear", ".r" = "right ear", - ":l" = "left ear", "#l" = "left ear", ".l" = "left ear", - ":i" = "intercom", "#i" = "intercom", ".i" = "intercom", - ":h" = "department", "#h" = "department", ".h" = "department", - ":+" = "special", "#+" = "special", ".+" = "special", //activate radio-specific special functions - ":c" = "Command", "#c" = "Command", ".c" = "Command", - ":n" = "Science", "#n" = "Science", ".n" = "Science", - ":m" = "Medical", "#m" = "Medical", ".m" = "Medical", - ":e" = "Engineering", "#e" = "Engineering", ".e" = "Engineering", - ":s" = "Security", "#s" = "Security", ".s" = "Security", - ":w" = "whisper", "#w" = "whisper", ".w" = "whisper", - ":t" = "Syndicate", "#t" = "Syndicate", ".t" = "Syndicate", - ":u" = "Supply", "#u" = "Supply", ".u" = "Supply", - ":z" = "Service", "#z" = "Service", ".z" = "Service", - ":p" = "AI Private", "#p" = "AI Private", ".p" = "AI Private", - ":x" = "cords", "#x" = "cords", ".x" = "cords", - - ":R" = "right ear", "#R" = "right ear", ".R" = "right ear", - ":L" = "left ear", "#L" = "left ear", ".L" = "left ear", - ":I" = "intercom", "#I" = "intercom", ".I" = "intercom", - ":H" = "department", "#H" = "department", ".H" = "department", - ":C" = "Command", "#C" = "Command", ".C" = "Command", - ":N" = "Science", "#N" = "Science", ".N" = "Science", - ":M" = "Medical", "#M" = "Medical", ".M" = "Medical", - ":E" = "Engineering", "#E" = "Engineering", ".E" = "Engineering", - ":S" = "Security", "#S" = "Security", ".S" = "Security", - ":W" = "whisper", "#W" = "whisper", ".W" = "whisper", - ":T" = "Syndicate", "#T" = "Syndicate", ".T" = "Syndicate", - ":U" = "Supply", "#U" = "Supply", ".U" = "Supply", - ":Z" = "Service", "#Z" = "Service", ".Z" = "Service", - ":P" = "AI Private", "#P" = "AI Private", ".P" = "AI Private", - ":$" = "Response Team", "#$" = "Response Team", ".$" = "Response Team", - ":-" = "Special Ops", "#-" = "Special Ops", ".-" = "Special Ops", - ":_" = "SyndTeam", "#_" = "SyndTeam", "._" = "SyndTeam", - ":X" = "cords", "#X" = "cords", ".X" = "cords" -) - - -var/list/channel_to_radio_key = new -proc/get_radio_key_from_channel(var/channel) - var/key = channel_to_radio_key[channel] - if(!key) - for(var/radio_key in department_radio_keys) - if(department_radio_keys[radio_key] == channel) - key = radio_key - break - if(!key) - key = "" - channel_to_radio_key[channel] = key - - return key - -/mob/living/proc/binarycheck() - return FALSE - -/mob/proc/get_default_language() - return null - -/mob/living/get_default_language() - return default_language - -/mob/living/proc/handle_speech_problems(list/message_pieces, var/verb) - var/robot = isSynthetic() - for(var/datum/multilingual_say_piece/S in message_pieces) - if(S.speaking && S.speaking.flags & NO_STUTTER) - continue - - if((HULK in mutations) && health >= 25) - S.message = "[uppertext(S.message)]!!!" - verb = pick("yells", "roars", "hollers") - - if(slurring) - if(robot) - S.message = slur(S.message, list("@", "!", "#", "$", "%", "&", "?")) - else - S.message = slur(S.message) - verb = "slurs" - - if(stuttering) - if(robot) - S.message = robostutter(S.message) - else - S.message = stutter(S.message) - verb = "stammers" - - if(cultslurring) - S.message = cultslur(S.message) - verb = "slurs" - - if(!IsVocal()) - S.message = "" - return list("verb" = verb) - -/mob/living/proc/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) - switch(message_mode) - if("whisper") //all mobs can whisper by default - whisper_say(message_pieces) - return 1 - return 0 - -/mob/living/proc/handle_speech_sound() - var/list/returns[3] - returns[1] = null - returns[2] = null - returns[3] = null - return returns - - -/mob/living/say(var/message, var/verb = "says", var/sanitize = TRUE, var/ignore_speech_problems = FALSE, var/ignore_atmospherics = FALSE) - if(client) - if(client.prefs.muted & MUTE_IC) - to_chat(src, "You cannot speak in IC (Muted).") - return - - if(sanitize) - message = trim_strip_html_properly(message) - - if(stat) - if(stat == DEAD) - return say_dead(message) - return - - var/message_mode = parse_message_mode(message, "headset") - - if(copytext(message, 1, 2) == "*") - return emote(copytext(message, 2)) - - //parse the radio code and consume it - if(message_mode) - if(message_mode == "headset") - message = copytext(message, 2) //it would be really nice if the parse procs could do this for us. - else - message = copytext(message, 3) - - message = trim_left(message) - - //parse the language code and consume it - var/list/message_pieces = parse_languages(message) - if(istype(message_pieces, /datum/multilingual_say_piece)) // Little quirk to just easily deal with HIVEMIND languages - var/datum/multilingual_say_piece/S = message_pieces // Yay BYOND's hilarious typecasting - S.speaking.broadcast(src, S.message) - return 1 - - - if(!LAZYLEN(message_pieces)) - log_runtime(EXCEPTION("Message failed to generate pieces. [message] - [json_encode(message_pieces)]")) - return 0 - - if(message_mode == "cords") - if(iscarbon(src)) - var/mob/living/carbon/C = src - var/obj/item/organ/internal/vocal_cords/V = C.get_int_organ(/obj/item/organ/internal/vocal_cords) - if(V && V.can_speak_with()) - C.say(V.handle_speech(message), sanitize = FALSE, ignore_speech_problems = TRUE, ignore_atmospherics = TRUE) - V.speak_with(message) //words come before actions - return 1 - - var/datum/multilingual_say_piece/first_piece = message_pieces[1] - verb = say_quote(message, first_piece.speaking) - - if(is_muzzled()) - var/obj/item/clothing/mask/muzzle/G = wear_mask - if(G.mute == MUZZLE_MUTE_ALL) //if the mask is supposed to mute you completely or just muffle you - to_chat(src, "You're muzzled and cannot speak!") - return - else if(G.mute == MUZZLE_MUTE_MUFFLE) - muffledspeech_all(message_pieces) - verb = "mumbles" - - if(!ignore_speech_problems) - var/list/hsp = handle_speech_problems(message_pieces, verb) - verb = hsp["verb"] - - - var/list/used_radios = list() - if(handle_message_mode(message_mode, message_pieces, verb, used_radios)) - return 1 - - - var/list/handle_v = handle_speech_sound() - var/sound/speech_sound = handle_v[1] - var/sound_vol = handle_v[2] - var/sound_frequency = handle_v[3] - - var/italics = 0 - var/message_range = world.view - - //speaking into radios - if(used_radios.len) - italics = 1 - message_range = 1 - if(first_piece.speaking) - message_range = first_piece.speaking.get_talkinto_msg_range(message) - - var/msg - if(!first_piece.speaking || !(first_piece.speaking.flags & NO_TALK_MSG)) - msg = "[src] talks into [used_radios[1]]" - - if(msg) - for(var/mob/living/M in hearers(5, src) - src) - M.show_message(msg) - - if(speech_sound) - sound_vol *= 0.5 - - - var/turf/T = get_turf(src) - var/list/listening = list() - var/list/listening_obj = list() - - if(T) - //make sure the air can transmit speech - speaker's side - var/datum/gas_mixture/environment = T.return_air() - var/pressure = environment ? environment.return_pressure() : 0 - if(!ignore_atmospherics) - if(pressure < SOUND_MINIMUM_PRESSURE) - message_range = 1 - - if(pressure < ONE_ATMOSPHERE * 0.4) //sound distortion pressure, to help clue people in that the air is thin, even if it isn't a vacuum yet - italics = TRUE - sound_vol *= 0.5 //muffle the sound a bit, so it's like we're actually talking through contact - - var/list/hear = hear(message_range, T) - var/list/hearturfs = list() - - for(var/I in hear) - if(ismob(I)) - var/mob/M = I - listening += M - hearturfs += get_turf(M) - for(var/obj/O in M.contents) - listening_obj |= O - if(isobj(I)) - var/obj/O = I - hearturfs += get_turf(O) - listening_obj |= O - - for(var/mob/M in GLOB.player_list) - if(!M.client) - continue - - if(isnewplayer(M)) - continue - - if(isobserver(M)) - if(M.get_preference(CHAT_GHOSTEARS) && client) // The client check is so that ghosts don't have to listen to mice. - listening |= M - continue - - if(message_range < world.view && (get_dist(T, M) <= world.view)) - listening |= M - continue - - if(get_turf(M) in hearturfs) - listening |= M - - var/list/speech_bubble_recipients = list() - var/speech_bubble_test = say_test(message) - - for(var/mob/M in listening) - M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol, sound_frequency) - if(M.client) - speech_bubble_recipients.Add(M.client) - spawn(0) - if(loc && !isturf(loc)) - var/atom/A = loc //Non-turf, let it handle the speech bubble - A.speech_bubble("hR[speech_bubble_test]", A, speech_bubble_recipients) - else //Turf, leave speech bubbles to the mob - speech_bubble("h[speech_bubble_test]", src, speech_bubble_recipients) - - for(var/obj/O in listening_obj) - spawn(0) - if(O) //It's possible that it could be deleted in the meantime. - O.hear_talk(src, message_pieces, verb) - - //Log of what we've said, plain message, no spans or junk - say_log += message - log_say(message, src) - return 1 - -/obj/effect/speech_bubble - var/mob/parent - -/mob/living/proc/GetVoice() - return name - -/mob/living/emote(act, type, message, force) //emote code is terrible, this is so that anything that isn't already snowflaked to shit can call the parent and handle emoting sanely - if(client) - if(client.prefs.muted & MUTE_IC) - to_chat(src, "You cannot speak in IC (Muted).") - return - - if(stat) - return 0 - - if(..()) - return 1 - - if(act && type && message) //parent call - log_emote(message, src) - - for(var/mob/M in GLOB.dead_mob_list) - if(!M.client) - continue //skip monkeys and leavers - - if(isnewplayer(M)) - continue - - if(isobserver(M) && M.get_preference(CHAT_GHOSTSIGHT) && !(M in viewers(src, null)) && client) // The client check makes sure people with ghost sight don't get spammed by simple mobs emoting. - M.show_message(message) - - switch(type) - if(1) //Visible - visible_message(message) - return 1 - if(2) //Audible - audible_message(message) - return 1 - - else //everything else failed, emote is probably invalid - if(act == "help") - return //except help, because help is handled individually - to_chat(src, "Unusable emote '[act]'. Say *help for a list.") - -/mob/living/whisper(message as text) - message = trim_strip_html_properly(message) - - //parse the language code and consume it - var/list/message_pieces = parse_languages(message) - if(istype(message_pieces, /datum/multilingual_say_piece)) // Little quirk to just easily deal with HIVEMIND languages - var/datum/multilingual_say_piece/S = message_pieces // Yay BYOND's hilarious typecasting - S.speaking.broadcast(src, S.message) - return 1 - - whisper_say(message_pieces) - -// for weird circumstances where you're inside an atom that is also you, like pai's -/mob/living/proc/get_whisper_loc() - return src - -/mob/living/proc/whisper_say(list/message_pieces, verb = "whispers") - if(client) - if(client.prefs.muted & MUTE_IC) - to_chat(src, "You cannot speak in IC (Muted).") - return - - if(stat) - if(stat == DEAD) - return say_dead(message_pieces) - return - - if(is_muzzled()) - if(istype(wear_mask, /obj/item/clothing/mask/muzzle/tapegag)) //just for tape - to_chat(src, "Your mouth is taped and you cannot speak!") - else - to_chat(src, "You're muzzled and cannot speak!") - return - - var/message = multilingual_to_message(message_pieces) - - say_log += "whisper: [message]" - log_whisper(message, src) - var/message_range = 1 - var/eavesdropping_range = 2 - var/watching_range = 5 - var/italics = 1 - var/adverb_added = FALSE - var/not_heard //the message displayed to people who could not hear the whispering - - var/datum/multilingual_say_piece/first_piece = message_pieces[1] - if(first_piece.speaking) - if(first_piece.speaking.whisper_verb) - verb = first_piece.speaking.whisper_verb - not_heard = "[verb] something" - else - var/adverb = pick("quietly", "softly") - adverb_added = TRUE - verb = "[first_piece.speaking.speech_verb] [adverb]" - not_heard = "[first_piece.speaking.speech_verb] something [adverb]" - else - not_heard = "[verb] something" - - var/list/hsp = handle_speech_problems(message_pieces, verb) - verb = hsp["verb"] - if(verb == "yells loudly") - verb = "slurs emphatically" - else if(!adverb_added) - var/adverb = pick("quietly", "softly") - verb = "[verb] [adverb]" - - var/atom/whisper_loc = get_whisper_loc() - var/list/listening = hear(message_range, whisper_loc) - listening |= src - - var/list/hearturfs = list() - - // Pass whispers on to anything inside the immediate listeners. - // This comes before the ghosts do so that ghosts don't act as whisper relays - for(var/atom/L in listening) - if(ismob(L)) - for(var/mob/C in L.contents) - if(isliving(C)) - listening += C - hearturfs += get_turf(L) - if(isobj(L)) - hearturfs += get_turf(L) - - // Loop through all players to see if they need to hear it. - for(var/mob/M in GLOB.player_list) - if(!M.client) - continue - - if(isnewplayer(M)) - continue - - if(isobserver(M)) - if(M.get_preference(CHAT_GHOSTEARS)) // The client check is so that ghosts don't have to listen to mice. - listening |= M - continue - - if(message_range < world.view && (get_dist(whisper_loc, M) <= world.view)) - listening |= M - continue - - if(get_turf(M) in hearturfs) - listening |= M - - //pass on the message to objects that can hear us. - for(var/obj/O in view(message_range, whisper_loc)) - spawn(0) - if(O) - O.hear_talk(src, message_pieces, verb) - - var/list/eavesdropping = hearers(eavesdropping_range, whisper_loc) - eavesdropping -= src - eavesdropping -= listening - - var/list/watching = hearers(watching_range, whisper_loc) - watching -= src - watching -= listening - watching -= eavesdropping - - //now mobs - var/list/speech_bubble_recipients = list() - var/speech_bubble_test = say_test(message) - - for(var/mob/M in listening) - M.hear_say(message_pieces, verb, italics, src) - if(M.client) - speech_bubble_recipients.Add(M.client) - - if(eavesdropping.len) - stars_all(message_pieces) //hopefully passing the message twice through stars() won't hurt... I guess if you already don't understand the language, when they speak it too quietly to hear normally you would be able to catch even less. - for(var/mob/M in eavesdropping) - M.hear_say(message_pieces, verb, italics, src) - if(M.client) - speech_bubble_recipients.Add(M.client) - - spawn(0) - var/image/I = image('icons/mob/talk.dmi', src, "h[speech_bubble_test]", MOB_LAYER + 1) - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - flick_overlay(I, speech_bubble_recipients, 30) - - if(watching.len) - var/rendered = "[name] [not_heard]." - for(var/mob/M in watching) - M.show_message(rendered, 2) - - return 1 - -/mob/living/speech_bubble(var/bubble_state = "",var/bubble_loc = src, var/list/bubble_recipients = list()) - var/image/I = image('icons/mob/talk.dmi', bubble_loc, bubble_state, MOB_LAYER + 1) - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - flick_overlay(I, bubble_recipients, 30) +var/list/department_radio_keys = list( + ":r" = "right ear", "#r" = "right ear", ".r" = "right ear", + ":l" = "left ear", "#l" = "left ear", ".l" = "left ear", + ":i" = "intercom", "#i" = "intercom", ".i" = "intercom", + ":h" = "department", "#h" = "department", ".h" = "department", + ":+" = "special", "#+" = "special", ".+" = "special", //activate radio-specific special functions + ":c" = "Command", "#c" = "Command", ".c" = "Command", + ":n" = "Science", "#n" = "Science", ".n" = "Science", + ":m" = "Medical", "#m" = "Medical", ".m" = "Medical", + ":e" = "Engineering", "#e" = "Engineering", ".e" = "Engineering", + ":s" = "Security", "#s" = "Security", ".s" = "Security", + ":w" = "whisper", "#w" = "whisper", ".w" = "whisper", + ":t" = "Syndicate", "#t" = "Syndicate", ".t" = "Syndicate", + ":u" = "Supply", "#u" = "Supply", ".u" = "Supply", + ":z" = "Service", "#z" = "Service", ".z" = "Service", + ":p" = "AI Private", "#p" = "AI Private", ".p" = "AI Private", + ":x" = "cords", "#x" = "cords", ".x" = "cords", + + ":R" = "right ear", "#R" = "right ear", ".R" = "right ear", + ":L" = "left ear", "#L" = "left ear", ".L" = "left ear", + ":I" = "intercom", "#I" = "intercom", ".I" = "intercom", + ":H" = "department", "#H" = "department", ".H" = "department", + ":C" = "Command", "#C" = "Command", ".C" = "Command", + ":N" = "Science", "#N" = "Science", ".N" = "Science", + ":M" = "Medical", "#M" = "Medical", ".M" = "Medical", + ":E" = "Engineering", "#E" = "Engineering", ".E" = "Engineering", + ":S" = "Security", "#S" = "Security", ".S" = "Security", + ":W" = "whisper", "#W" = "whisper", ".W" = "whisper", + ":T" = "Syndicate", "#T" = "Syndicate", ".T" = "Syndicate", + ":U" = "Supply", "#U" = "Supply", ".U" = "Supply", + ":Z" = "Service", "#Z" = "Service", ".Z" = "Service", + ":P" = "AI Private", "#P" = "AI Private", ".P" = "AI Private", + ":$" = "Response Team", "#$" = "Response Team", ".$" = "Response Team", + ":-" = "Special Ops", "#-" = "Special Ops", ".-" = "Special Ops", + ":_" = "SyndTeam", "#_" = "SyndTeam", "._" = "SyndTeam", + ":X" = "cords", "#X" = "cords", ".X" = "cords" +) + + +var/list/channel_to_radio_key = new +proc/get_radio_key_from_channel(var/channel) + var/key = channel_to_radio_key[channel] + if(!key) + for(var/radio_key in department_radio_keys) + if(department_radio_keys[radio_key] == channel) + key = radio_key + break + if(!key) + key = "" + channel_to_radio_key[channel] = key + + return key + +/mob/living/proc/binarycheck() + return FALSE + +/mob/proc/get_default_language() + return null + +/mob/living/get_default_language() + return default_language + +/mob/living/proc/handle_speech_problems(list/message_pieces, var/verb) + var/robot = isSynthetic() + for(var/datum/multilingual_say_piece/S in message_pieces) + if(S.speaking && S.speaking.flags & NO_STUTTER) + continue + + if((HULK in mutations) && health >= 25) + S.message = "[uppertext(S.message)]!!!" + verb = pick("yells", "roars", "hollers") + + if(slurring) + if(robot) + S.message = slur(S.message, list("@", "!", "#", "$", "%", "&", "?")) + else + S.message = slur(S.message) + verb = "slurs" + + if(stuttering) + if(robot) + S.message = robostutter(S.message) + else + S.message = stutter(S.message) + verb = "stammers" + + if(cultslurring) + S.message = cultslur(S.message) + verb = "slurs" + + if(!IsVocal()) + S.message = "" + return list("verb" = verb) + +/mob/living/proc/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) + switch(message_mode) + if("whisper") //all mobs can whisper by default + whisper_say(message_pieces) + return 1 + return 0 + +/mob/living/proc/handle_speech_sound() + var/list/returns[3] + returns[1] = null + returns[2] = null + returns[3] = null + return returns + + +/mob/living/say(var/message, var/verb = "says", var/sanitize = TRUE, var/ignore_speech_problems = FALSE, var/ignore_atmospherics = FALSE) + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, "You cannot speak in IC (Muted).") + return + + if(sanitize) + message = trim_strip_html_properly(message) + + if(stat) + if(stat == DEAD) + return say_dead(message) + return + + var/message_mode = parse_message_mode(message, "headset") + + if(copytext(message, 1, 2) == "*") + return emote(copytext(message, 2)) + + //parse the radio code and consume it + if(message_mode) + if(message_mode == "headset") + message = copytext(message, 2) //it would be really nice if the parse procs could do this for us. + else + message = copytext(message, 3) + + message = trim_left(message) + + //parse the language code and consume it + var/list/message_pieces = parse_languages(message) + if(istype(message_pieces, /datum/multilingual_say_piece)) // Little quirk to just easily deal with HIVEMIND languages + var/datum/multilingual_say_piece/S = message_pieces // Yay BYOND's hilarious typecasting + S.speaking.broadcast(src, S.message) + return 1 + + + if(!LAZYLEN(message_pieces)) + log_runtime(EXCEPTION("Message failed to generate pieces. [message] - [json_encode(message_pieces)]")) + return 0 + + if(message_mode == "cords") + if(iscarbon(src)) + var/mob/living/carbon/C = src + var/obj/item/organ/internal/vocal_cords/V = C.get_int_organ(/obj/item/organ/internal/vocal_cords) + if(V && V.can_speak_with()) + C.say(V.handle_speech(message), sanitize = FALSE, ignore_speech_problems = TRUE, ignore_atmospherics = TRUE) + V.speak_with(message) //words come before actions + return 1 + + var/datum/multilingual_say_piece/first_piece = message_pieces[1] + verb = say_quote(message, first_piece.speaking) + + if(is_muzzled()) + var/obj/item/clothing/mask/muzzle/G = wear_mask + if(G.mute == MUZZLE_MUTE_ALL) //if the mask is supposed to mute you completely or just muffle you + to_chat(src, "You're muzzled and cannot speak!") + return + else if(G.mute == MUZZLE_MUTE_MUFFLE) + muffledspeech_all(message_pieces) + verb = "mumbles" + + if(!ignore_speech_problems) + var/list/hsp = handle_speech_problems(message_pieces, verb) + verb = hsp["verb"] + + + var/list/used_radios = list() + if(handle_message_mode(message_mode, message_pieces, verb, used_radios)) + return 1 + + + var/list/handle_v = handle_speech_sound() + var/sound/speech_sound = handle_v[1] + var/sound_vol = handle_v[2] + var/sound_frequency = handle_v[3] + + var/italics = 0 + var/message_range = world.view + + //speaking into radios + if(used_radios.len) + italics = 1 + message_range = 1 + if(first_piece.speaking) + message_range = first_piece.speaking.get_talkinto_msg_range(message) + + var/msg + if(!first_piece.speaking || !(first_piece.speaking.flags & NO_TALK_MSG)) + msg = "[src] talks into [used_radios[1]]" + + if(msg) + for(var/mob/living/M in hearers(5, src) - src) + M.show_message(msg) + + if(speech_sound) + sound_vol *= 0.5 + + + var/turf/T = get_turf(src) + var/list/listening = list() + var/list/listening_obj = list() + + if(T) + //make sure the air can transmit speech - speaker's side + var/datum/gas_mixture/environment = T.return_air() + var/pressure = environment ? environment.return_pressure() : 0 + if(!ignore_atmospherics) + if(pressure < SOUND_MINIMUM_PRESSURE) + message_range = 1 + + if(pressure < ONE_ATMOSPHERE * 0.4) //sound distortion pressure, to help clue people in that the air is thin, even if it isn't a vacuum yet + italics = TRUE + sound_vol *= 0.5 //muffle the sound a bit, so it's like we're actually talking through contact + + var/list/hear = hear(message_range, T) + var/list/hearturfs = list() + + for(var/I in hear) + if(ismob(I)) + var/mob/M = I + listening += M + hearturfs += get_turf(M) + for(var/obj/O in M.contents) + listening_obj |= O + if(isobj(I)) + var/obj/O = I + hearturfs += get_turf(O) + listening_obj |= O + + for(var/mob/M in GLOB.player_list) + if(!M.client) + continue + + if(isnewplayer(M)) + continue + + if(isobserver(M)) + if(M.get_preference(CHAT_GHOSTEARS) && client) // The client check is so that ghosts don't have to listen to mice. + listening |= M + continue + + if(message_range < world.view && (get_dist(T, M) <= world.view)) + listening |= M + continue + + if(get_turf(M) in hearturfs) + listening |= M + + var/list/speech_bubble_recipients = list() + var/speech_bubble_test = say_test(message) + + for(var/mob/M in listening) + M.hear_say(message_pieces, verb, italics, src, speech_sound, sound_vol, sound_frequency) + if(M.client) + speech_bubble_recipients.Add(M.client) + spawn(0) + if(loc && !isturf(loc)) + var/atom/A = loc //Non-turf, let it handle the speech bubble + A.speech_bubble("hR[speech_bubble_test]", A, speech_bubble_recipients) + else //Turf, leave speech bubbles to the mob + speech_bubble("h[speech_bubble_test]", src, speech_bubble_recipients) + + for(var/obj/O in listening_obj) + spawn(0) + if(O) //It's possible that it could be deleted in the meantime. + O.hear_talk(src, message_pieces, verb) + + //Log of what we've said, plain message, no spans or junk + say_log += message + log_say(message, src) + return 1 + +/obj/effect/speech_bubble + var/mob/parent + +/mob/living/proc/GetVoice() + return name + +/mob/living/emote(act, type, message, force) //emote code is terrible, this is so that anything that isn't already snowflaked to shit can call the parent and handle emoting sanely + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, "You cannot speak in IC (Muted).") + return + + if(stat) + return 0 + + if(..()) + return 1 + + if(act && type && message) //parent call + log_emote(message, src) + + for(var/mob/M in GLOB.dead_mob_list) + if(!M.client) + continue //skip monkeys and leavers + + if(isnewplayer(M)) + continue + + if(isobserver(M) && M.get_preference(CHAT_GHOSTSIGHT) && !(M in viewers(src, null)) && client) // The client check makes sure people with ghost sight don't get spammed by simple mobs emoting. + M.show_message(message) + + switch(type) + if(1) //Visible + visible_message(message) + return 1 + if(2) //Audible + audible_message(message) + return 1 + + else //everything else failed, emote is probably invalid + if(act == "help") + return //except help, because help is handled individually + to_chat(src, "Unusable emote '[act]'. Say *help for a list.") + +/mob/living/whisper(message as text) + message = trim_strip_html_properly(message) + + //parse the language code and consume it + var/list/message_pieces = parse_languages(message) + if(istype(message_pieces, /datum/multilingual_say_piece)) // Little quirk to just easily deal with HIVEMIND languages + var/datum/multilingual_say_piece/S = message_pieces // Yay BYOND's hilarious typecasting + S.speaking.broadcast(src, S.message) + return 1 + + whisper_say(message_pieces) + +// for weird circumstances where you're inside an atom that is also you, like pai's +/mob/living/proc/get_whisper_loc() + return src + +/mob/living/proc/whisper_say(list/message_pieces, verb = "whispers") + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, "You cannot speak in IC (Muted).") + return + + if(stat) + if(stat == DEAD) + return say_dead(message_pieces) + return + + if(is_muzzled()) + if(istype(wear_mask, /obj/item/clothing/mask/muzzle/tapegag)) //just for tape + to_chat(src, "Your mouth is taped and you cannot speak!") + else + to_chat(src, "You're muzzled and cannot speak!") + return + + var/message = multilingual_to_message(message_pieces) + + say_log += "whisper: [message]" + log_whisper(message, src) + var/message_range = 1 + var/eavesdropping_range = 2 + var/watching_range = 5 + var/italics = 1 + var/adverb_added = FALSE + var/not_heard //the message displayed to people who could not hear the whispering + + var/datum/multilingual_say_piece/first_piece = message_pieces[1] + if(first_piece.speaking) + if(first_piece.speaking.whisper_verb) + verb = first_piece.speaking.whisper_verb + not_heard = "[verb] something" + else + var/adverb = pick("quietly", "softly") + adverb_added = TRUE + verb = "[first_piece.speaking.speech_verb] [adverb]" + not_heard = "[first_piece.speaking.speech_verb] something [adverb]" + else + not_heard = "[verb] something" + + var/list/hsp = handle_speech_problems(message_pieces, verb) + verb = hsp["verb"] + if(verb == "yells loudly") + verb = "slurs emphatically" + else if(!adverb_added) + var/adverb = pick("quietly", "softly") + verb = "[verb] [adverb]" + + var/atom/whisper_loc = get_whisper_loc() + var/list/listening = hear(message_range, whisper_loc) + listening |= src + + var/list/hearturfs = list() + + // Pass whispers on to anything inside the immediate listeners. + // This comes before the ghosts do so that ghosts don't act as whisper relays + for(var/atom/L in listening) + if(ismob(L)) + for(var/mob/C in L.contents) + if(isliving(C)) + listening += C + hearturfs += get_turf(L) + if(isobj(L)) + hearturfs += get_turf(L) + + // Loop through all players to see if they need to hear it. + for(var/mob/M in GLOB.player_list) + if(!M.client) + continue + + if(isnewplayer(M)) + continue + + if(isobserver(M)) + if(M.get_preference(CHAT_GHOSTEARS)) // The client check is so that ghosts don't have to listen to mice. + listening |= M + continue + + if(message_range < world.view && (get_dist(whisper_loc, M) <= world.view)) + listening |= M + continue + + if(get_turf(M) in hearturfs) + listening |= M + + //pass on the message to objects that can hear us. + for(var/obj/O in view(message_range, whisper_loc)) + spawn(0) + if(O) + O.hear_talk(src, message_pieces, verb) + + var/list/eavesdropping = hearers(eavesdropping_range, whisper_loc) + eavesdropping -= src + eavesdropping -= listening + + var/list/watching = hearers(watching_range, whisper_loc) + watching -= src + watching -= listening + watching -= eavesdropping + + //now mobs + var/list/speech_bubble_recipients = list() + var/speech_bubble_test = say_test(message) + + for(var/mob/M in listening) + M.hear_say(message_pieces, verb, italics, src) + if(M.client) + speech_bubble_recipients.Add(M.client) + + if(eavesdropping.len) + stars_all(message_pieces) //hopefully passing the message twice through stars() won't hurt... I guess if you already don't understand the language, when they speak it too quietly to hear normally you would be able to catch even less. + for(var/mob/M in eavesdropping) + M.hear_say(message_pieces, verb, italics, src) + if(M.client) + speech_bubble_recipients.Add(M.client) + + spawn(0) + var/image/I = image('icons/mob/talk.dmi', src, "h[speech_bubble_test]", MOB_LAYER + 1) + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + flick_overlay(I, speech_bubble_recipients, 30) + + if(watching.len) + var/rendered = "[name] [not_heard]." + for(var/mob/M in watching) + M.show_message(rendered, 2) + + return 1 + +/mob/living/speech_bubble(var/bubble_state = "",var/bubble_loc = src, var/list/bubble_recipients = list()) + var/image/I = image('icons/mob/talk.dmi', bubble_loc, bubble_state, MOB_LAYER + 1) + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + flick_overlay(I, bubble_recipients, 30) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 8ae4fc6a419..41de195a9e0 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1,1323 +1,1323 @@ -var/list/ai_list = list() -var/list/ai_verbs_default = list( - /mob/living/silicon/ai/proc/announcement, - /mob/living/silicon/ai/proc/ai_announcement_text, - /mob/living/silicon/ai/proc/ai_call_shuttle, - /mob/living/silicon/ai/proc/ai_camera_track, - /mob/living/silicon/ai/proc/ai_camera_list, - /mob/living/silicon/ai/proc/ai_goto_location, - /mob/living/silicon/ai/proc/ai_remove_location, - /mob/living/silicon/ai/proc/ai_hologram_change, - /mob/living/silicon/ai/proc/ai_network_change, - /mob/living/silicon/ai/proc/ai_roster, - /mob/living/silicon/ai/proc/ai_statuschange, - /mob/living/silicon/ai/proc/ai_store_location, - /mob/living/silicon/ai/proc/control_integrated_radio, - /mob/living/silicon/ai/proc/core, - /mob/living/silicon/ai/proc/pick_icon, - /mob/living/silicon/ai/proc/sensor_mode, - /mob/living/silicon/ai/proc/show_laws_verb, - /mob/living/silicon/ai/proc/toggle_acceleration, - /mob/living/silicon/ai/proc/toggle_camera_light, - /mob/living/silicon/ai/proc/botcall, - /mob/living/silicon/ai/proc/change_arrival_message -) - -//Not sure why this is necessary... -/proc/AutoUpdateAI(obj/subject) - var/is_in_use = 0 - if(subject!=null) - for(var/A in ai_list) - var/mob/living/silicon/ai/M = A - if((M.client && M.machine == subject)) - is_in_use = 1 - subject.attack_ai(M) - return is_in_use - -/mob/living/silicon/ai - name = "AI" - icon = 'icons/mob/ai.dmi'// - icon_state = "ai" - move_resist = MOVE_FORCE_NORMAL - density = 1 - status_flags = CANSTUN|CANPARALYSE|CANPUSH - mob_size = MOB_SIZE_LARGE - sight = SEE_TURFS | SEE_MOBS | SEE_OBJS - see_in_dark = 8 - can_strip = 0 - var/list/network = list("SS13","Telecomms","Research Outpost","Mining Outpost") - var/obj/machinery/camera/current = null - var/list/connected_robots = list() - var/aiRestorePowerRoutine = 0 - //var/list/laws = list() - var/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list()) - var/viewalerts = 0 - var/icon/holo_icon//Default is assigned when AI is created. - var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. - var/obj/item/pda/silicon/ai/aiPDA = null - var/obj/item/multitool/aiMulti = null - var/custom_sprite = 0 //For our custom sprites - var/custom_hologram = 0 //For our custom holograms - - var/obj/item/radio/headset/heads/ai_integrated/aiRadio = null - - //MALFUNCTION - var/datum/module_picker/malf_picker - var/list/datum/AI_Module/current_modules = list() - var/can_dominate_mechs = 0 - var/shunted = 0 //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead - - var/control_disabled = 0 // Set to 1 to stop AI from interacting via Click() -- TLE - var/malfhacking = 0 // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite - var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown - - var/obj/machinery/power/apc/malfhack = null - var/explosive = 0 //does the AI explode when it dies? - - var/mob/living/silicon/ai/parent = null - var/camera_light_on = 0 - var/list/obj/machinery/camera/lit_cameras = list() - - var/datum/trackable/track = new() - - var/last_paper_seen = null - var/can_shunt = 1 - var/last_announcement = "" - var/datum/announcement/priority/announcement - var/mob/living/simple_animal/bot/Bot - var/turf/waypoint //Holds the turf of the currently selected waypoint. - var/waypoint_mode = 0 //Waypoint mode is for selecting a turf via clicking. - var/apc_override = FALSE //hack for letting the AI use its APC even when visionless - var/nuking = 0 - var/obj/machinery/doomsday_device/doomsday_device - - var/obj/machinery/hologram/holopad/holo = null - var/mob/camera/aiEye/eyeobj - var/sprint = 10 - var/cooldown = 0 - var/acceleration = 1 - var/tracking = 0 //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. - - var/obj/machinery/camera/portable/builtInCamera - - var/obj/structure/AIcore/deactivated/linked_core //For exosuit control - - var/arrivalmsg = "$name, $rank, has arrived on the station." - - var/multicam_allowed = FALSE - var/multicam_on = FALSE - var/obj/screen/movable/pic_in_pic/ai/master_multicam - var/list/multicam_screens = list() - var/list/all_eyes = list() - var/max_multicams = 6 - -/mob/living/silicon/ai/proc/add_ai_verbs() - verbs |= ai_verbs_default - verbs |= silicon_subsystems - -/mob/living/silicon/ai/proc/remove_ai_verbs() - verbs -= ai_verbs_default - verbs -= silicon_subsystems - -/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/mmi/B, var/safety = 0) - announcement = new() - announcement.title = "A.I. Announcement" - announcement.announcement_type = "A.I. Announcement" - announcement.announcer = name - announcement.newscast = 0 - - var/list/possibleNames = GLOB.ai_names - - var/pickedName = null - while(!pickedName) - pickedName = pick(GLOB.ai_names) - for(var/mob/living/silicon/ai/A in GLOB.mob_list) - if(A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop - possibleNames -= pickedName - pickedName = null - - aiPDA = new/obj/item/pda/silicon/ai(src) - rename_character(null, pickedName) - anchored = 1 - canmove = 0 - density = 1 - loc = loc - - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo1")) - - proc_holder_list = new() - - if(L) - if(istype(L, /datum/ai_laws)) - laws = L - else - make_laws() - - verbs += /mob/living/silicon/ai/proc/show_laws_verb - - aiMulti = new(src) - aiRadio = new(src) - common_radio = aiRadio - aiRadio.myAi = src - additional_law_channels["Binary"] = ":b " - additional_law_channels["Holopad"] = ":h" - - aiCamera = new/obj/item/camera/siliconcam/ai_camera(src) - - if(isturf(loc)) - add_ai_verbs(src) - - //Languages - add_language("Robot Talk", 1) - add_language("Galactic Common", 1) - add_language("Sol Common", 1) - add_language("Tradeband", 1) - add_language("Neo-Russkiya", 0) - add_language("Gutter", 0) - add_language("Sinta'unathi", 0) - add_language("Siik'tajr", 0) - add_language("Canilunzt", 0) - add_language("Skrellian", 0) - add_language("Vox-pidgin", 0) - add_language("Orluum", 0) - add_language("Rootspeak", 0) - add_language("Trinary", 1) - add_language("Chittin", 0) - add_language("Bubblish", 0) - add_language("Clownish", 0) - - if(!safety)//Only used by AIize() to successfully spawn an AI. - if(!B)//If there is no player/brain inside. - new/obj/structure/AIcore/deactivated(loc)//New empty terminal. - qdel(src)//Delete AI. - return - else - if(B.brainmob.mind) - B.brainmob.mind.transfer_to(src) - - on_mob_init() - - spawn(5) - new /obj/machinery/ai_powersupply(src) - - create_eye() - - builtInCamera = new /obj/machinery/camera/portable(src) - builtInCamera.c_tag = name - builtInCamera.network = list("SS13") - - ai_list += src - GLOB.shuttle_caller_list += src - ..() - -/mob/living/silicon/ai/proc/on_mob_init() - to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") - to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") - to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") - to_chat(src, "To use something, simply click on it.") - to_chat(src, "Use say :b to speak to your cyborgs through binary. Use say :h to speak from an active holopad.") - to_chat(src, "For department channels, use the following say commands:") - - var/radio_text = "" - for(var/i = 1 to common_radio.channels.len) - var/channel = common_radio.channels[i] - var/key = get_radio_key_from_channel(channel) - radio_text += "[key] - [channel]" - if(i != common_radio.channels.len) - radio_text += ", " - - to_chat(src, radio_text) - - show_laws() - to_chat(src, "These laws may be changed by other players, or by you being the traitor.") - - job = "AI" - -/mob/living/silicon/ai/Stat() - ..() - if(statpanel("Status")) - if(stat) - stat(null, text("Systems nonfunctional")) - return - show_borg_info() - -/mob/living/silicon/ai/proc/show_borg_info() - stat(null, text("Connected cyborgs: [connected_robots.len]")) - for(var/mob/living/silicon/robot/R in connected_robots) - var/robot_status = "Nominal" - if(R.stat || !R.client) - robot_status = "OFFLINE" - else if(!R.cell || R.cell.charge <= 0) - robot_status = "DEPOWERED" - // Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies! - var/area/A = get_area(R) - stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge] / [R.cell.maxcharge]" : "Empty"] | \ - Module: [R.designation] | Loc: [sanitize(A.name)] | Status: [robot_status]")) - -/mob/living/silicon/ai/rename_character(oldname, newname) - if(!..(oldname, newname)) - return FALSE - - if(oldname != real_name) - announcement.announcer = name - - if(eyeobj) - eyeobj.name = "[newname] (AI Eye)" - - // Set ai pda name - if(aiPDA) - aiPDA.set_name_and_job(newname, "AI") - - return TRUE - -/mob/living/silicon/ai/Destroy() - ai_list -= src - GLOB.shuttle_caller_list -= src - SSshuttle.autoEvac() - QDEL_NULL(eyeobj) // No AI, no Eye - if(malfhacking) - deltimer(malfhacking) - malfhacking = null - malfhack = null - return ..() - - -/* - The AI Power supply is a dummy object used for powering the AI since only machinery should be using power. - The alternative was to rewrite a bunch of AI code instead here we are. -*/ -/obj/machinery/ai_powersupply - name="\improper AI power supply" - active_power_usage=1000 - use_power = ACTIVE_POWER_USE - power_channel = EQUIP - var/mob/living/silicon/ai/powered_ai = null - invisibility = 100 - -/obj/machinery/ai_powersupply/New(mob/living/silicon/ai/ai=null) - powered_ai = ai - if(isnull(powered_ai)) - qdel(src) - return - - loc = powered_ai.loc - use_power(1) // Just incase we need to wake up the power system. - - ..() - -/obj/machinery/ai_powersupply/process() - if(!powered_ai || powered_ai.stat & DEAD) - qdel(src) - return - if(!powered_ai.anchored) - loc = powered_ai.loc - use_power = NO_POWER_USE - if(powered_ai.anchored) - use_power = ACTIVE_POWER_USE - -/mob/living/silicon/ai/proc/pick_icon() - set category = "AI Commands" - set name = "Set AI Core Display" - if(stat || aiRestorePowerRoutine) - return - if(!custom_sprite) //Check to see if custom sprite time, checking the appopriate file to change a var - var/file = file2text("config/custom_sprites.txt") - var/lines = splittext(file, "\n") - - for(var/line in lines) - // split & clean up - var/list/Entry = splittext(line, ":") - for(var/i = 1 to Entry.len) - Entry[i] = trim(Entry[i]) - - if(Entry.len < 2 || Entry[1] != "ai") //ignore incorrectly formatted entries or entries that aren't marked for AI - continue - - if(Entry[2] == ckey) //They're in the list? Custom sprite time, var and icon change required - custom_sprite = 1 - - var/display_choices = list( - "Monochrome", - "Blue", - "Clown", - "Inverted", - "Text", - "Smiley", - "Angry", - "Dorf", - "Matrix", - "Bliss", - "Firewall", - "Green", - "Red", - "Static", - "Triumvirate", - "Triumvirate Static", - "Red October", - "Sparkles", - "ANIMA", - "President", - "NT", - "NT2", - "Rainbow", - "Angel", - "Heartline", - "Hades", - "Helios", - "Syndicat Meow", - "Too Deep", - "Goon", - "Murica", - "Fuzzy", - "Glitchman", - "House", - "Database", - "Alien" - ) - if(custom_sprite) - display_choices += "Custom" - - //if(icon_state == initial(icon_state)) - var/icontype = "" - icontype = input("Select an icon!", "AI", null, null) in display_choices - icon = 'icons/mob/ai.dmi' //reset this in case we were on a custom sprite and want to change to a standard one - switch(icontype) - if("Custom") - icon = 'icons/mob/custom_synthetic/custom-synthetic.dmi' //set this here so we can use the custom_sprite - icon_state = "[ckey]-ai" - if("Clown") - icon_state = "ai-clown" - if("Monochrome") - icon_state = "ai-mono" - if("Inverted") - icon_state = "ai-u" - if("Firewall") - icon_state = "ai-magma" - if("Green") - icon_state = "ai-weird" - if("Red") - icon_state = "ai-red" - if("Static") - icon_state = "ai-static" - if("Text") - icon_state = "ai-text" - if("Smiley") - icon_state = "ai-smiley" - if("Matrix") - icon_state = "ai-matrix" - if("Angry") - icon_state = "ai-angryface" - if("Dorf") - icon_state = "ai-dorf" - if("Bliss") - icon_state = "ai-bliss" - if("Triumvirate") - icon_state = "ai-triumvirate" - if("Triumvirate Static") - icon_state = "ai-triumvirate-malf" - if("Red October") - icon_state = "ai-redoctober" - if("Sparkles") - icon_state = "ai-sparkles" - if("ANIMA") - icon_state = "ai-anima" - if("President") - icon_state = "ai-president" - if("NT") - icon_state = "ai-nt" - if("NT2") - icon_state = "ai-nanotrasen" - if("Rainbow") - icon_state = "ai-rainbow" - if("Angel") - icon_state = "ai-angel" - if("Heartline") - icon_state = "ai-heartline" - if("Hades") - icon_state = "ai-hades" - if("Helios") - icon_state = "ai-helios" - if("Syndicat Meow") - icon_state = "ai-syndicatmeow" - if("Too Deep") - icon_state = "ai-toodeep" - if("Goon") - icon_state = "ai-goon" - if("Murica") - icon_state = "ai-murica" - if("Fuzzy") - icon_state = "ai-fuzz" - if("Glitchman") - icon_state = "ai-glitchman" - if("House") - icon_state = "ai-house" - if("Database") - icon_state = "ai-database" - if("Alien") - icon_state = "ai-alien" - else - icon_state = "ai" - //else -// to_chat(usr, "You can only change your display once!") - //return - -// this verb lets the ai see the stations manifest -/mob/living/silicon/ai/proc/ai_roster() - set name = "Show Crew Manifest" - set category = "AI Commands" - show_station_manifest() - -/mob/living/silicon/ai/var/message_cooldown = 0 -/mob/living/silicon/ai/proc/ai_announcement_text() - set category = "AI Commands" - set name = "Make Station Announcement" - - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - if(message_cooldown) - to_chat(src, "Please allow one minute to pass between announcements.") - return - - var/input = input(usr, "Please write a message to announce to the station crew.", "A.I. Announcement") as message|null - if(!input) - return - - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - announcement.Announce(input) - message_cooldown = 1 - spawn(600)//One minute cooldown - message_cooldown = 0 - -/mob/living/silicon/ai/proc/ai_call_shuttle() - set name = "Call Emergency Shuttle" - set category = "AI Commands" - - if(check_unable(AI_CHECK_WIRELESS)) - return - - var/input = clean_input("Please enter the reason for calling the shuttle.", "Shuttle Call Reason.","") - if(!input || stat) - return - - if(check_unable(AI_CHECK_WIRELESS)) - return - - call_shuttle_proc(src, input) - - return - -/mob/living/silicon/ai/proc/ai_cancel_call() - set name = "Recall Emergency Shuttle" - set category = "AI Commands" - - if(check_unable(AI_CHECK_WIRELESS)) - return - - var/confirm = alert("Are you sure you want to recall the shuttle?", "Confirm Shuttle Recall", "Yes", "No") - - if(check_unable(AI_CHECK_WIRELESS)) - return - - if(confirm == "Yes") - cancel_call_proc(src) - -/mob/living/silicon/ai/cancel_camera() - view_core() - -/mob/living/silicon/ai/verb/toggle_anchor() - set category = "AI Commands" - set name = "Toggle Floor Bolts" - - if(!isturf(loc)) // if their location isn't a turf - return // stop - - if(anchored) - anchored = FALSE - else - anchored = TRUE - - to_chat(src, "[anchored ? "You are now anchored." : "You are now unanchored."]") - -/mob/living/silicon/ai/update_canmove() - return FALSE - -/mob/living/silicon/ai/proc/announcement() - set name = "Announcement" - set desc = "Create a vocal announcement by typing in the available words to create a sentence." - set category = "AI Commands" - - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - ai_announcement() - -/mob/living/silicon/ai/check_eye(mob/user) - if(!current) - return null - user.reset_perspective(current) - return TRUE - -/mob/living/silicon/ai/blob_act(obj/structure/blob/B) - if(stat != DEAD) - adjustBruteLoss(60) - updatehealth() - return 1 - return 0 - -/mob/living/silicon/ai/restrained() - return FALSE - -/mob/living/silicon/ai/emp_act(severity) - if(prob(30)) - switch(pick(1,2)) - if(1) - view_core() - if(2) - ai_call_shuttle() - ..() - -/mob/living/silicon/ai/ex_act(severity) - ..() - - switch(severity) - if(1.0) - gib() - if(2.0) - if(stat != 2) - adjustBruteLoss(60) - adjustFireLoss(60) - if(3.0) - if(stat != 2) - adjustBruteLoss(30) - - return - - -/mob/living/silicon/ai/Topic(href, href_list) - if(usr != src) - return - ..() - if(href_list["mach_close"]) - if(href_list["mach_close"] == "aialerts") - viewalerts = 0 - var/t1 = text("window=[]", href_list["mach_close"]) - unset_machine() - src << browse(null, t1) - if(href_list["switchcamera"]) - switchCamera(locate(href_list["switchcamera"])) in cameranet.cameras - if(href_list["showalerts"]) - subsystem_alarm_monitor() - if(href_list["show_paper"]) - if(last_paper_seen) - src << browse(last_paper_seen, "window=show_paper") - //Carn: holopad requests - if(href_list["jumptoholopad"]) - var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"]) - if(stat == CONSCIOUS) - if(H) - H.attack_ai(src) //may as well recycle - else - to_chat(src, "Unable to locate the holopad.") - - if(href_list["say_word"]) - play_vox_word(href_list["say_word"], null, src) - return - - if(href_list["track"]) - var/mob/living/target = locate(href_list["track"]) in GLOB.mob_list - if(target && target.can_track()) - ai_actual_track(target) - else - to_chat(src, "Target is not on or near any active cameras on the station.") - return - - if(href_list["trackbot"]) - var/mob/living/simple_animal/bot/target = locate(href_list["trackbot"]) in GLOB.bots_list - if(target) - ai_actual_track(target) - else - to_chat(src, "Target is not on or near any active cameras on the station.") - return - - if(href_list["callbot"]) //Command a bot to move to a selected location. - Bot = locate(href_list["callbot"]) in GLOB.bots_list - if(!Bot || Bot.remote_disabled || control_disabled) - return //True if there is no bot found, the bot is manually emagged, or the AI is carded with wireless off. - waypoint_mode = 1 - to_chat(src, "Set your waypoint by clicking on a valid location free of obstructions.") - return - - if(href_list["interface"]) //Remotely connect to a bot! - Bot = locate(href_list["interface"]) in GLOB.bots_list - if(!Bot || Bot.remote_disabled || control_disabled) - return - Bot.attack_ai(src) - - if(href_list["botrefresh"]) //Refreshes the bot control panel. - botcall() - return - - if(href_list["ai_take_control"]) //Mech domination - - var/obj/mecha/M = locate(href_list["ai_take_control"]) - - if(!M) - return - - var/mech_has_controlbeacon = FALSE - for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in M.trackers) - mech_has_controlbeacon = TRUE - break - if(!can_dominate_mechs && !mech_has_controlbeacon) - message_admins("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.") - log_debug("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.") - return - - if(controlled_mech) - to_chat(src, "You are already loaded into an onboard computer!") - return - if(!cameranet.checkCameraVis(M)) - to_chat(src, "Exosuit is no longer near active cameras.") - return - if(lacks_power()) - to_chat(src, "You're depowered!") - return - if(!isturf(loc)) - to_chat(src, "You aren't in your core!") - return - if(M) - M.transfer_ai(AI_MECH_HACK, src, usr) //Called om the mech itself. - - else if(href_list["faketrack"]) - var/mob/target = locate(href_list["track"]) in GLOB.mob_list - var/mob/living/silicon/ai/A = locate(href_list["track2"]) in GLOB.mob_list - if(A && target) - - A.cameraFollow = target - to_chat(A, "Now tracking [target.name] on camera.") - if(usr.machine == null) - usr.machine = usr - - while(cameraFollow == target) - to_chat(usr, "Target is not on or near any active cameras on the station. We'll check again in 5 seconds (unless you use the cancel-camera verb).") - sleep(40) - continue - - else if(href_list["open"]) - var/mob/target = locate(href_list["open"]) in GLOB.mob_list - if(target) - open_nearest_door(target) - -/mob/living/silicon/ai/bullet_act(var/obj/item/projectile/Proj) - ..(Proj) - return 2 - -/mob/living/silicon/ai/reset_perspective(atom/A) - if(camera_light_on) - light_cameras() - if(istype(A, /obj/machinery/camera)) - current = A - if(A != GLOB.ai_camera_room_landmark) - end_multicam() - - . = ..() - if(.) - if(!A && isturf(loc) && eyeobj) - end_multicam() - client.eye = eyeobj - client.perspective = MOB_PERSPECTIVE - eyeobj.get_remote_view_fullscreens(src) - -/mob/living/silicon/ai/proc/botcall() - set category = "AI Commands" - set name = "Access Robot Control" - set desc = "Wirelessly control various automatic robots." - if(stat == 2) - to_chat(src, "Critical error. System offline.") - return - - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - var/d - var/area/bot_area - d += "Query network status
        " - d += "" - - for(var/mob/living/simple_animal/bot/Bot in GLOB.bots_list) - if(is_ai_allowed(Bot.z) && !Bot.remote_disabled) //Only non-emagged bots on the allowed Z-level are detected! - bot_area = get_area(Bot) - d += "" - //If the bot is on, it will display the bot's current mode status. If the bot is not mode, it will just report "Idle". "Inactive if it is not on at all. - d += "" - d += "" - d += "" - d += "" - d += "" - d = format_text(d) - - var/datum/browser/popup = new(src, "botcall", "Remote Robot Control", 700, 400) - popup.set_content(d) - popup.open() - -/mob/living/silicon/ai/proc/set_waypoint(atom/A) - var/turf/turf_check = get_turf(A) - //The target must be in view of a camera or near the core. - if(turf_check in range(get_turf(src))) - call_bot(turf_check) - else if(cameranet && cameranet.checkTurfVis(turf_check)) - call_bot(turf_check) - else - to_chat(src, "Selected location is not visible.") - -/mob/living/silicon/ai/proc/call_bot(turf/waypoint) - - if(!Bot) - return - - if(Bot.calling_ai && Bot.calling_ai != src) //Prevents an override if another AI is controlling this bot. - to_chat(src, "Interface error. Unit is already in use.") - return - - Bot.call_bot(src, waypoint) - -/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C) - - if(!tracking) - cameraFollow = null - - if(!C || stat == DEAD) //C.can_use()) - return FALSE - - if(!eyeobj) - view_core() - return - // ok, we're alive, camera is good and in our network... - eyeobj.setLoc(get_turf(C)) - //machine = src - - return TRUE - -//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm -//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead -//Addition by Mord_Sith to define AI's network change ability -/mob/living/silicon/ai/proc/ai_network_change() - set category = "AI Commands" - set name = "Jump To Network" - unset_machine() - var/cameralist[0] - - if(check_unable()) - return - - if(usr.stat == 2) - to_chat(usr, "You can't change your camera network because you are dead!") - return - - var/mob/living/silicon/ai/U = usr - - for(var/obj/machinery/camera/C in cameranet.cameras) - if(!C.can_use()) - continue - - var/list/tempnetwork = difflist(C.network,GLOB.restricted_camera_networks,1) - if(tempnetwork.len) - for(var/i in tempnetwork) - cameralist[i] = i - var/old_network = network - network = input(U, "Which network would you like to view?") as null|anything in cameralist - - if(check_unable()) - return - - if(!U.eyeobj) - U.view_core() - return - - if(isnull(network)) - network = old_network // If nothing is selected - else - for(var/obj/machinery/camera/C in cameranet.cameras) - if(!C.can_use()) - continue - if(network in C.network) - U.eyeobj.setLoc(get_turf(C)) - break - to_chat(src, "Switched to [network] camera network.") -//End of code by Mord_Sith - - -/mob/living/silicon/ai/proc/choose_modules() - set category = "Malfunction" - set name = "Choose Module" - - malf_picker.use(src) - -/mob/living/silicon/ai/proc/ai_statuschange() - set category = "AI Commands" - set name = "AI Status" - - if(usr.stat == 2) - to_chat(usr, "You cannot change your emotional status because you are dead!") - return - - if(check_unable()) - return - - var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Friend Computer") - var/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions - - if(check_unable()) - return - - for(var/obj/machinery/M in GLOB.machines) //change status - if(istype(M, /obj/machinery/ai_status_display)) - var/obj/machinery/ai_status_display/AISD = M - AISD.emotion = emote - //if Friend Computer, change ALL displays - else if(istype(M, /obj/machinery/status_display)) - - var/obj/machinery/status_display/SD = M - if(emote=="Friend Computer") - SD.friendc = 1 - else - SD.friendc = 0 - return - -//I am the icon meister. Bow fefore me. //>fefore -/mob/living/silicon/ai/proc/ai_hologram_change() - set name = "Change Hologram" - set desc = "Change the default hologram available to AI to something else." - set category = "AI Commands" - - if(check_unable()) - return - if(!custom_hologram) //Check to see if custom sprite time, checking the appopriate file to change a var - var/file = file2text("config/custom_sprites.txt") - var/lines = splittext(file, "\n") - - for(var/line in lines) - // split & clean up - var/list/Entry = splittext(line, ":") - for(var/i = 1 to Entry.len) - Entry[i] = trim(Entry[i]) - - if(Entry.len < 2 || Entry[1] != "hologram") - continue - - if (Entry[2] == ckey) //Custom holograms - custom_hologram = 1 // option is given in hologram menu - - var/input - switch(alert("Would you like to select a hologram based on a crew member, an animal, or switch to a unique avatar?",,"Crew Member","Unique","Animal")) - if("Crew Member") - var/personnel_list[] = list() - - for(var/datum/data/record/t in data_core.locked)//Look in data core locked. - personnel_list["[t.fields["name"]]: [t.fields["rank"]]"] = t.fields["image"]//Pull names, rank, and image. - - if(personnel_list.len) - input = input("Select a crew member:") as null|anything in personnel_list - var/icon/character_icon = personnel_list[input] - if(character_icon) - qdel(holo_icon)//Clear old icon so we're not storing it in memory. - holo_icon = getHologramIcon(icon(character_icon)) - else - alert("No suitable records found. Aborting.") - - if("Animal") - var/icon_list[] = list( - "Bear", - "Carp", - "Chicken", - "Corgi", - "Cow", - "Crab", - "Deer", - "Fox", - "Goat", - "Goose", - "Kitten", - "Kitten2", - "Pig", - "Poly", - "Pug", - "Seal", - "Spider", - "Turkey" - ) - - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("Bear") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"bear")) - if("Carp") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"carp")) - if("Chicken") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"chicken_brown")) - if("Corgi") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"corgi")) - if("Cow") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"cow")) - if("Crab") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"crab")) - if("Deer") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"deer")) - if("Fox") - holo_icon = getHologramIcon(icon('icons/mob/pets.dmi',"fox")) - if("Goat") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"goat")) - if("Goose") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"goose")) - if("Kitten") - holo_icon = getHologramIcon(icon('icons/mob/pets.dmi',"cat")) - if("Kitten2") - holo_icon = getHologramIcon(icon('icons/mob/pets.dmi',"cat2")) - if("Pig") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"pig")) - if("Poly") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"parrot_fly")) - if("Pug") - holo_icon = getHologramIcon(icon('icons/mob/pets.dmi',"pug")) - if("Seal") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"seal")) - if("Spider") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"guard")) - if("Turkey") - holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"turkey")) - - else - var/icon_list[] = list( - "default", - "floating face", - "xeno queen", - "eldritch", - "ancient machine" - ) - if(custom_hologram) //insert custom hologram - icon_list.Add("custom") - - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("default") - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo1")) - if("floating face") - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo2")) - if("xeno queen") - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo3")) - if("eldritch") - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo4")) - if("ancient machine") - holo_icon = getHologramIcon(icon('icons/mob/ancient_machine.dmi', "ancient_machine")) - if("custom") - if("[ckey]-ai-holo" in icon_states('icons/mob/custom_synthetic/custom-synthetic.dmi')) - holo_icon = getHologramIcon(icon('icons/mob/custom_synthetic/custom-synthetic.dmi', "[ckey]-ai-holo")) - else if("[ckey]-ai-holo" in icon_states('icons/mob/custom_synthetic/custom-synthetic64.dmi')) - holo_icon = getHologramIcon(icon('icons/mob/custom_synthetic/custom-synthetic64.dmi', "[ckey]-ai-holo")) - else - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo1")) - - return - -/mob/living/silicon/ai/proc/corereturn() - set category = "Malfunction" - set name = "Return to Main Core" - - var/obj/machinery/power/apc/apc = loc - if(!istype(apc)) - to_chat(src, "You are already in your Main Core.") - return - apc.malfvacate() - -//Toggles the luminosity and applies it by re-entereing the camera. -/mob/living/silicon/ai/proc/toggle_camera_light() - set name = "Toggle Camera Lights" - set desc = "Toggles the lights on the cameras throughout the station." - set category = "AI Commands" - - if(stat != CONSCIOUS) - return - - camera_light_on = !camera_light_on - - if(!camera_light_on) - to_chat(src, "Camera lights deactivated.") - - for(var/obj/machinery/camera/C in lit_cameras) - C.set_light(0) - lit_cameras = list() - - return - - light_cameras() - - to_chat(src, "Camera lights activated.") - -/mob/living/silicon/ai/proc/set_syndie_radio() - if(aiRadio) - aiRadio.make_syndie() - -/mob/living/silicon/ai/proc/sensor_mode() - set name = "Set Sensor Augmentation" - set desc = "Augment visual feed with internal sensor overlays." - set category = "AI Commands" - toggle_sensor_mode() - -/mob/living/silicon/ai/proc/change_arrival_message() - set name = "Set Arrival Message" - set desc = "Change the message that's transmitted when a new crew member arrives on station." - set category = "AI Commands" - - var/newmsg = clean_input("What would you like the arrival message to be? List of options: $name, $rank, $species, $gender, $age", "Change Arrival Message", arrivalmsg) - if(newmsg != arrivalmsg) - arrivalmsg = newmsg - to_chat(usr, "The arrival message has been successfully changed.") - -// Handled camera lighting, when toggled. -// It will get the nearest camera from the eyeobj, lighting it. - -/mob/living/silicon/ai/proc/light_cameras() - var/list/obj/machinery/camera/add = list() - var/list/obj/machinery/camera/remove = list() - var/list/obj/machinery/camera/visible = list() - for(var/datum/camerachunk/CC in eyeobj.visibleCameraChunks) - for(var/obj/machinery/camera/C in CC.cameras) - if(!C.can_use() || get_dist(C, eyeobj) > 7) - continue - visible |= C - - add = visible - lit_cameras - remove = lit_cameras - visible - - for(var/obj/machinery/camera/C in remove) - lit_cameras -= C //Removed from list before turning off the light so that it doesn't check the AI looking away. - C.Togglelight(0) - for(var/obj/machinery/camera/C in add) - C.Togglelight(1) - lit_cameras |= C - - -/mob/living/silicon/ai/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/wrench)) - if(anchored) - user.visible_message("\The [user] starts to unbolt \the [src] from the plating...") - if(!do_after(user, 40 * W.toolspeed, target = src)) - user.visible_message("\The [user] decides not to unbolt \the [src].") - return - user.visible_message("\The [user] finishes unfastening \the [src]!") - anchored = FALSE - return - else - user.visible_message("\The [user] starts to bolt \the [src] to the plating...") - if(!do_after(user, 40 * W.toolspeed, target = src)) - user.visible_message("\The [user] decides not to bolt \the [src].") - return - user.visible_message("\The [user] finishes fastening down \the [src]!") - anchored = TRUE - return - else - return ..() - -/mob/living/silicon/ai/welder_act() - return - -/mob/living/silicon/ai/proc/control_integrated_radio() - set name = "Radio Settings" - set desc = "Allows you to change settings of your radio." - set category = "AI Commands" - - if(check_unable(AI_CHECK_RADIO)) - return - - to_chat(src, "Accessing Subspace Transceiver control...") - if(aiRadio) - aiRadio.interact(src) - - -/mob/living/silicon/ai/proc/check_unable(flags = 0) - if(stat == DEAD) - to_chat(src, "You are dead!") - return TRUE - - if(lacks_power()) - to_chat(src, "Power systems failure!") - return TRUE - - if((flags & AI_CHECK_WIRELESS) && control_disabled) - to_chat(src, "Wireless control is disabled!") - return TRUE - if((flags & AI_CHECK_RADIO) && aiRadio.disabledAi) - to_chat(src, "System Error - Transceiver Disabled!") - return TRUE - return FALSE - -/mob/living/silicon/ai/proc/is_in_chassis() - return isturf(loc) - -/mob/living/silicon/ai/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(!..()) - return - if(interaction == AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card. - if(!mind) - to_chat(user, "No intelligence patterns detected.")//No more magical carding of empty cores, AI RETURN TO BODY!!!11 - return - new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location. - aiRestorePowerRoutine = 0//So the AI initially has power. - control_disabled = 1//Can't control things remotely if you're stuck in a card! - aiRadio.disabledAi = 1 //No talking on the built-in radio for you either! - loc = card//Throw AI into the card. - to_chat(src, "You have been downloaded to a mobile storage device. Remote device connection severed.") - to_chat(user, "Transfer successful: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.") - -/mob/living/silicon/ai/can_buckle() - return FALSE - -// Pass lying down or getting up to our pet human, if we're in a rig. -/mob/living/silicon/ai/lay_down() - set name = "Rest" - set category = "IC" - - resting = 0 - var/obj/item/rig/rig = get_rig() - if(rig) - rig.force_rest(src) - -/mob/living/silicon/ai/switch_to_camera(obj/machinery/camera/C) - if(!C.can_use() || !is_in_chassis()) - return FALSE - - eyeobj.setLoc(get_turf(C)) - client.eye = eyeobj - return TRUE - - -/mob/living/silicon/ai/proc/can_see(atom/A) - if(isturf(loc)) //AI in core, check if on cameras - //get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera - //apc_override is needed here because AIs use their own APC when depowered - var/turf/T = isturf(A) ? A : get_turf_pixel(A) - return (cameranet && cameranet.checkTurfVis(T)) || apc_override - //AI is carded/shunted - //view(src) returns nothing for carded/shunted AIs and they have x-ray vision so just use get_dist - var/list/viewscale = getviewsize(client.view) - return get_dist(src, A) <= max(viewscale[1]*0.5,viewscale[2]*0.5) - -/mob/living/silicon/ai/proc/relay_speech(mob/living/M, list/message_pieces, verb) - var/message = combine_message(message_pieces, verb, M) - var/name_used = M.GetVoice() - //This communication is imperfect because the holopad "filters" voices and is only designed to connect to the master only. - var/rendered = "Relayed Speech: [name_used] [message]" - show_message(rendered, 2) - -/mob/living/silicon/ai/proc/malfhacked(obj/machinery/power/apc/apc) - malfhack = null - malfhacking = 0 - clear_alert("hackingapc") - - if(!istype(apc) || QDELETED(apc) || apc.stat & BROKEN) - to_chat(src, "Hack aborted. The designated APC no longer exists on the power network.") - playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, 1) - else if(apc.aidisabled) - to_chat(src, "Hack aborted. [apc] is no longer responding to our systems.") - playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 1) - else - malf_picker.processing_time += 10 - - apc.malfai = parent || src - apc.malfhack = TRUE - apc.locked = TRUE - - playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1) - to_chat(src, "Hack complete. [apc] is now under your exclusive control.") - apc.update_icon() - -/mob/living/silicon/ai/proc/add_malf_picker() - to_chat(src, "In the top right corner of the screen you will find the Malfunctions tab, where you can purchase various abilities, from upgraded surveillance to station ending doomsday devices.") - to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.") - view_core() //A BYOND bug requires you to be viewing your core before your verbs update - verbs += /mob/living/silicon/ai/proc/choose_modules - malf_picker = new /datum/module_picker - -/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target) - if(!istype(target)) - return - - if(target && target.can_track()) - var/obj/machinery/door/airlock/A = null - - var/dist = -1 - for(var/obj/machinery/door/airlock/D in range(3, target)) - if(!D.density) - continue - - var/curr_dist = get_dist(D, target) - - if(dist < 0) - dist = curr_dist - A = D - else if(dist > curr_dist) - dist = curr_dist - A = D - - if(istype(A)) - switch(alert(src, "Do you want to open \the [A] for [target]?", "Doorknob_v2a.exe", "Yes", "No")) - if("Yes") - A.AIShiftClick() - to_chat(src, "You open \the [A] for [target].") - else - to_chat(src, "You deny the request.") - else - to_chat(src, "Unable to locate an airlock near [target].") - - else - to_chat(src, "Target is not on or near any active cameras on the station.") - -/mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye) - cameranet.visibility(moved_eye, client, all_eyes) - -/mob/living/silicon/ai/forceMove(atom/destination) - . = ..() - if(.) - end_multicam() - -/mob/living/silicon/ai/handle_fire() - return - -/mob/living/silicon/ai/update_fire() - return - -/mob/living/silicon/ai/IgniteMob() - return FALSE - -/mob/living/silicon/ai/ExtinguishMob() - return - - -/mob/living/silicon/ai/update_sight() - if(!client) - return - - if(stat == DEAD) - grant_death_vision() - return - - see_invisible = initial(see_invisible) - see_in_dark = initial(see_in_dark) - sight = initial(sight) - lighting_alpha = initial(lighting_alpha) - - if(aiRestorePowerRoutine) - sight = sight &~ SEE_TURFS - sight = sight &~ SEE_MOBS - sight = sight &~ SEE_OBJS - see_in_dark = 0 - - SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) - sync_lighting_plane_alpha() +var/list/ai_list = list() +var/list/ai_verbs_default = list( + /mob/living/silicon/ai/proc/announcement, + /mob/living/silicon/ai/proc/ai_announcement_text, + /mob/living/silicon/ai/proc/ai_call_shuttle, + /mob/living/silicon/ai/proc/ai_camera_track, + /mob/living/silicon/ai/proc/ai_camera_list, + /mob/living/silicon/ai/proc/ai_goto_location, + /mob/living/silicon/ai/proc/ai_remove_location, + /mob/living/silicon/ai/proc/ai_hologram_change, + /mob/living/silicon/ai/proc/ai_network_change, + /mob/living/silicon/ai/proc/ai_roster, + /mob/living/silicon/ai/proc/ai_statuschange, + /mob/living/silicon/ai/proc/ai_store_location, + /mob/living/silicon/ai/proc/control_integrated_radio, + /mob/living/silicon/ai/proc/core, + /mob/living/silicon/ai/proc/pick_icon, + /mob/living/silicon/ai/proc/sensor_mode, + /mob/living/silicon/ai/proc/show_laws_verb, + /mob/living/silicon/ai/proc/toggle_acceleration, + /mob/living/silicon/ai/proc/toggle_camera_light, + /mob/living/silicon/ai/proc/botcall, + /mob/living/silicon/ai/proc/change_arrival_message +) + +//Not sure why this is necessary... +/proc/AutoUpdateAI(obj/subject) + var/is_in_use = 0 + if(subject!=null) + for(var/A in ai_list) + var/mob/living/silicon/ai/M = A + if((M.client && M.machine == subject)) + is_in_use = 1 + subject.attack_ai(M) + return is_in_use + +/mob/living/silicon/ai + name = "AI" + icon = 'icons/mob/ai.dmi'// + icon_state = "ai" + move_resist = MOVE_FORCE_NORMAL + density = 1 + status_flags = CANSTUN|CANPARALYSE|CANPUSH + mob_size = MOB_SIZE_LARGE + sight = SEE_TURFS | SEE_MOBS | SEE_OBJS + see_in_dark = 8 + can_strip = 0 + var/list/network = list("SS13","Telecomms","Research Outpost","Mining Outpost") + var/obj/machinery/camera/current = null + var/list/connected_robots = list() + var/aiRestorePowerRoutine = 0 + //var/list/laws = list() + var/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list()) + var/viewalerts = 0 + var/icon/holo_icon//Default is assigned when AI is created. + var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. + var/obj/item/pda/silicon/ai/aiPDA = null + var/obj/item/multitool/aiMulti = null + var/custom_sprite = 0 //For our custom sprites + var/custom_hologram = 0 //For our custom holograms + + var/obj/item/radio/headset/heads/ai_integrated/aiRadio = null + + //MALFUNCTION + var/datum/module_picker/malf_picker + var/list/datum/AI_Module/current_modules = list() + var/can_dominate_mechs = 0 + var/shunted = 0 //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead + + var/control_disabled = 0 // Set to 1 to stop AI from interacting via Click() -- TLE + var/malfhacking = 0 // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite + var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown + + var/obj/machinery/power/apc/malfhack = null + var/explosive = 0 //does the AI explode when it dies? + + var/mob/living/silicon/ai/parent = null + var/camera_light_on = 0 + var/list/obj/machinery/camera/lit_cameras = list() + + var/datum/trackable/track = new() + + var/last_paper_seen = null + var/can_shunt = 1 + var/last_announcement = "" + var/datum/announcement/priority/announcement + var/mob/living/simple_animal/bot/Bot + var/turf/waypoint //Holds the turf of the currently selected waypoint. + var/waypoint_mode = 0 //Waypoint mode is for selecting a turf via clicking. + var/apc_override = FALSE //hack for letting the AI use its APC even when visionless + var/nuking = 0 + var/obj/machinery/doomsday_device/doomsday_device + + var/obj/machinery/hologram/holopad/holo = null + var/mob/camera/aiEye/eyeobj + var/sprint = 10 + var/cooldown = 0 + var/acceleration = 1 + var/tracking = 0 //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. + + var/obj/machinery/camera/portable/builtInCamera + + var/obj/structure/AIcore/deactivated/linked_core //For exosuit control + + var/arrivalmsg = "$name, $rank, has arrived on the station." + + var/multicam_allowed = FALSE + var/multicam_on = FALSE + var/obj/screen/movable/pic_in_pic/ai/master_multicam + var/list/multicam_screens = list() + var/list/all_eyes = list() + var/max_multicams = 6 + +/mob/living/silicon/ai/proc/add_ai_verbs() + verbs |= ai_verbs_default + verbs |= silicon_subsystems + +/mob/living/silicon/ai/proc/remove_ai_verbs() + verbs -= ai_verbs_default + verbs -= silicon_subsystems + +/mob/living/silicon/ai/New(loc, var/datum/ai_laws/L, var/obj/item/mmi/B, var/safety = 0) + announcement = new() + announcement.title = "A.I. Announcement" + announcement.announcement_type = "A.I. Announcement" + announcement.announcer = name + announcement.newscast = 0 + + var/list/possibleNames = GLOB.ai_names + + var/pickedName = null + while(!pickedName) + pickedName = pick(GLOB.ai_names) + for(var/mob/living/silicon/ai/A in GLOB.mob_list) + if(A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop + possibleNames -= pickedName + pickedName = null + + aiPDA = new/obj/item/pda/silicon/ai(src) + rename_character(null, pickedName) + anchored = 1 + canmove = 0 + density = 1 + loc = loc + + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo1")) + + proc_holder_list = new() + + if(L) + if(istype(L, /datum/ai_laws)) + laws = L + else + make_laws() + + verbs += /mob/living/silicon/ai/proc/show_laws_verb + + aiMulti = new(src) + aiRadio = new(src) + common_radio = aiRadio + aiRadio.myAi = src + additional_law_channels["Binary"] = ":b " + additional_law_channels["Holopad"] = ":h" + + aiCamera = new/obj/item/camera/siliconcam/ai_camera(src) + + if(isturf(loc)) + add_ai_verbs(src) + + //Languages + add_language("Robot Talk", 1) + add_language("Galactic Common", 1) + add_language("Sol Common", 1) + add_language("Tradeband", 1) + add_language("Neo-Russkiya", 0) + add_language("Gutter", 0) + add_language("Sinta'unathi", 0) + add_language("Siik'tajr", 0) + add_language("Canilunzt", 0) + add_language("Skrellian", 0) + add_language("Vox-pidgin", 0) + add_language("Orluum", 0) + add_language("Rootspeak", 0) + add_language("Trinary", 1) + add_language("Chittin", 0) + add_language("Bubblish", 0) + add_language("Clownish", 0) + + if(!safety)//Only used by AIize() to successfully spawn an AI. + if(!B)//If there is no player/brain inside. + new/obj/structure/AIcore/deactivated(loc)//New empty terminal. + qdel(src)//Delete AI. + return + else + if(B.brainmob.mind) + B.brainmob.mind.transfer_to(src) + + on_mob_init() + + spawn(5) + new /obj/machinery/ai_powersupply(src) + + create_eye() + + builtInCamera = new /obj/machinery/camera/portable(src) + builtInCamera.c_tag = name + builtInCamera.network = list("SS13") + + ai_list += src + GLOB.shuttle_caller_list += src + ..() + +/mob/living/silicon/ai/proc/on_mob_init() + to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") + to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") + to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") + to_chat(src, "To use something, simply click on it.") + to_chat(src, "Use say :b to speak to your cyborgs through binary. Use say :h to speak from an active holopad.") + to_chat(src, "For department channels, use the following say commands:") + + var/radio_text = "" + for(var/i = 1 to common_radio.channels.len) + var/channel = common_radio.channels[i] + var/key = get_radio_key_from_channel(channel) + radio_text += "[key] - [channel]" + if(i != common_radio.channels.len) + radio_text += ", " + + to_chat(src, radio_text) + + show_laws() + to_chat(src, "These laws may be changed by other players, or by you being the traitor.") + + job = "AI" + +/mob/living/silicon/ai/Stat() + ..() + if(statpanel("Status")) + if(stat) + stat(null, text("Systems nonfunctional")) + return + show_borg_info() + +/mob/living/silicon/ai/proc/show_borg_info() + stat(null, text("Connected cyborgs: [connected_robots.len]")) + for(var/mob/living/silicon/robot/R in connected_robots) + var/robot_status = "Nominal" + if(R.stat || !R.client) + robot_status = "OFFLINE" + else if(!R.cell || R.cell.charge <= 0) + robot_status = "DEPOWERED" + // Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies! + var/area/A = get_area(R) + stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge] / [R.cell.maxcharge]" : "Empty"] | \ + Module: [R.designation] | Loc: [sanitize(A.name)] | Status: [robot_status]")) + +/mob/living/silicon/ai/rename_character(oldname, newname) + if(!..(oldname, newname)) + return FALSE + + if(oldname != real_name) + announcement.announcer = name + + if(eyeobj) + eyeobj.name = "[newname] (AI Eye)" + + // Set ai pda name + if(aiPDA) + aiPDA.set_name_and_job(newname, "AI") + + return TRUE + +/mob/living/silicon/ai/Destroy() + ai_list -= src + GLOB.shuttle_caller_list -= src + SSshuttle.autoEvac() + QDEL_NULL(eyeobj) // No AI, no Eye + if(malfhacking) + deltimer(malfhacking) + malfhacking = null + malfhack = null + return ..() + + +/* + The AI Power supply is a dummy object used for powering the AI since only machinery should be using power. + The alternative was to rewrite a bunch of AI code instead here we are. +*/ +/obj/machinery/ai_powersupply + name="\improper AI power supply" + active_power_usage=1000 + use_power = ACTIVE_POWER_USE + power_channel = EQUIP + var/mob/living/silicon/ai/powered_ai = null + invisibility = 100 + +/obj/machinery/ai_powersupply/New(mob/living/silicon/ai/ai=null) + powered_ai = ai + if(isnull(powered_ai)) + qdel(src) + return + + loc = powered_ai.loc + use_power(1) // Just incase we need to wake up the power system. + + ..() + +/obj/machinery/ai_powersupply/process() + if(!powered_ai || powered_ai.stat & DEAD) + qdel(src) + return + if(!powered_ai.anchored) + loc = powered_ai.loc + use_power = NO_POWER_USE + if(powered_ai.anchored) + use_power = ACTIVE_POWER_USE + +/mob/living/silicon/ai/proc/pick_icon() + set category = "AI Commands" + set name = "Set AI Core Display" + if(stat || aiRestorePowerRoutine) + return + if(!custom_sprite) //Check to see if custom sprite time, checking the appopriate file to change a var + var/file = file2text("config/custom_sprites.txt") + var/lines = splittext(file, "\n") + + for(var/line in lines) + // split & clean up + var/list/Entry = splittext(line, ":") + for(var/i = 1 to Entry.len) + Entry[i] = trim(Entry[i]) + + if(Entry.len < 2 || Entry[1] != "ai") //ignore incorrectly formatted entries or entries that aren't marked for AI + continue + + if(Entry[2] == ckey) //They're in the list? Custom sprite time, var and icon change required + custom_sprite = 1 + + var/display_choices = list( + "Monochrome", + "Blue", + "Clown", + "Inverted", + "Text", + "Smiley", + "Angry", + "Dorf", + "Matrix", + "Bliss", + "Firewall", + "Green", + "Red", + "Static", + "Triumvirate", + "Triumvirate Static", + "Red October", + "Sparkles", + "ANIMA", + "President", + "NT", + "NT2", + "Rainbow", + "Angel", + "Heartline", + "Hades", + "Helios", + "Syndicat Meow", + "Too Deep", + "Goon", + "Murica", + "Fuzzy", + "Glitchman", + "House", + "Database", + "Alien" + ) + if(custom_sprite) + display_choices += "Custom" + + //if(icon_state == initial(icon_state)) + var/icontype = "" + icontype = input("Select an icon!", "AI", null, null) in display_choices + icon = 'icons/mob/ai.dmi' //reset this in case we were on a custom sprite and want to change to a standard one + switch(icontype) + if("Custom") + icon = 'icons/mob/custom_synthetic/custom-synthetic.dmi' //set this here so we can use the custom_sprite + icon_state = "[ckey]-ai" + if("Clown") + icon_state = "ai-clown" + if("Monochrome") + icon_state = "ai-mono" + if("Inverted") + icon_state = "ai-u" + if("Firewall") + icon_state = "ai-magma" + if("Green") + icon_state = "ai-weird" + if("Red") + icon_state = "ai-red" + if("Static") + icon_state = "ai-static" + if("Text") + icon_state = "ai-text" + if("Smiley") + icon_state = "ai-smiley" + if("Matrix") + icon_state = "ai-matrix" + if("Angry") + icon_state = "ai-angryface" + if("Dorf") + icon_state = "ai-dorf" + if("Bliss") + icon_state = "ai-bliss" + if("Triumvirate") + icon_state = "ai-triumvirate" + if("Triumvirate Static") + icon_state = "ai-triumvirate-malf" + if("Red October") + icon_state = "ai-redoctober" + if("Sparkles") + icon_state = "ai-sparkles" + if("ANIMA") + icon_state = "ai-anima" + if("President") + icon_state = "ai-president" + if("NT") + icon_state = "ai-nt" + if("NT2") + icon_state = "ai-nanotrasen" + if("Rainbow") + icon_state = "ai-rainbow" + if("Angel") + icon_state = "ai-angel" + if("Heartline") + icon_state = "ai-heartline" + if("Hades") + icon_state = "ai-hades" + if("Helios") + icon_state = "ai-helios" + if("Syndicat Meow") + icon_state = "ai-syndicatmeow" + if("Too Deep") + icon_state = "ai-toodeep" + if("Goon") + icon_state = "ai-goon" + if("Murica") + icon_state = "ai-murica" + if("Fuzzy") + icon_state = "ai-fuzz" + if("Glitchman") + icon_state = "ai-glitchman" + if("House") + icon_state = "ai-house" + if("Database") + icon_state = "ai-database" + if("Alien") + icon_state = "ai-alien" + else + icon_state = "ai" + //else +// to_chat(usr, "You can only change your display once!") + //return + +// this verb lets the ai see the stations manifest +/mob/living/silicon/ai/proc/ai_roster() + set name = "Show Crew Manifest" + set category = "AI Commands" + show_station_manifest() + +/mob/living/silicon/ai/var/message_cooldown = 0 +/mob/living/silicon/ai/proc/ai_announcement_text() + set category = "AI Commands" + set name = "Make Station Announcement" + + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + if(message_cooldown) + to_chat(src, "Please allow one minute to pass between announcements.") + return + + var/input = input(usr, "Please write a message to announce to the station crew.", "A.I. Announcement") as message|null + if(!input) + return + + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + announcement.Announce(input) + message_cooldown = 1 + spawn(600)//One minute cooldown + message_cooldown = 0 + +/mob/living/silicon/ai/proc/ai_call_shuttle() + set name = "Call Emergency Shuttle" + set category = "AI Commands" + + if(check_unable(AI_CHECK_WIRELESS)) + return + + var/input = clean_input("Please enter the reason for calling the shuttle.", "Shuttle Call Reason.","") + if(!input || stat) + return + + if(check_unable(AI_CHECK_WIRELESS)) + return + + call_shuttle_proc(src, input) + + return + +/mob/living/silicon/ai/proc/ai_cancel_call() + set name = "Recall Emergency Shuttle" + set category = "AI Commands" + + if(check_unable(AI_CHECK_WIRELESS)) + return + + var/confirm = alert("Are you sure you want to recall the shuttle?", "Confirm Shuttle Recall", "Yes", "No") + + if(check_unable(AI_CHECK_WIRELESS)) + return + + if(confirm == "Yes") + cancel_call_proc(src) + +/mob/living/silicon/ai/cancel_camera() + view_core() + +/mob/living/silicon/ai/verb/toggle_anchor() + set category = "AI Commands" + set name = "Toggle Floor Bolts" + + if(!isturf(loc)) // if their location isn't a turf + return // stop + + if(anchored) + anchored = FALSE + else + anchored = TRUE + + to_chat(src, "[anchored ? "You are now anchored." : "You are now unanchored."]") + +/mob/living/silicon/ai/update_canmove() + return FALSE + +/mob/living/silicon/ai/proc/announcement() + set name = "Announcement" + set desc = "Create a vocal announcement by typing in the available words to create a sentence." + set category = "AI Commands" + + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + ai_announcement() + +/mob/living/silicon/ai/check_eye(mob/user) + if(!current) + return null + user.reset_perspective(current) + return TRUE + +/mob/living/silicon/ai/blob_act(obj/structure/blob/B) + if(stat != DEAD) + adjustBruteLoss(60) + updatehealth() + return 1 + return 0 + +/mob/living/silicon/ai/restrained() + return FALSE + +/mob/living/silicon/ai/emp_act(severity) + if(prob(30)) + switch(pick(1,2)) + if(1) + view_core() + if(2) + ai_call_shuttle() + ..() + +/mob/living/silicon/ai/ex_act(severity) + ..() + + switch(severity) + if(1.0) + gib() + if(2.0) + if(stat != 2) + adjustBruteLoss(60) + adjustFireLoss(60) + if(3.0) + if(stat != 2) + adjustBruteLoss(30) + + return + + +/mob/living/silicon/ai/Topic(href, href_list) + if(usr != src) + return + ..() + if(href_list["mach_close"]) + if(href_list["mach_close"] == "aialerts") + viewalerts = 0 + var/t1 = text("window=[]", href_list["mach_close"]) + unset_machine() + src << browse(null, t1) + if(href_list["switchcamera"]) + switchCamera(locate(href_list["switchcamera"])) in cameranet.cameras + if(href_list["showalerts"]) + subsystem_alarm_monitor() + if(href_list["show_paper"]) + if(last_paper_seen) + src << browse(last_paper_seen, "window=show_paper") + //Carn: holopad requests + if(href_list["jumptoholopad"]) + var/obj/machinery/hologram/holopad/H = locate(href_list["jumptoholopad"]) + if(stat == CONSCIOUS) + if(H) + H.attack_ai(src) //may as well recycle + else + to_chat(src, "Unable to locate the holopad.") + + if(href_list["say_word"]) + play_vox_word(href_list["say_word"], null, src) + return + + if(href_list["track"]) + var/mob/living/target = locate(href_list["track"]) in GLOB.mob_list + if(target && target.can_track()) + ai_actual_track(target) + else + to_chat(src, "Target is not on or near any active cameras on the station.") + return + + if(href_list["trackbot"]) + var/mob/living/simple_animal/bot/target = locate(href_list["trackbot"]) in GLOB.bots_list + if(target) + ai_actual_track(target) + else + to_chat(src, "Target is not on or near any active cameras on the station.") + return + + if(href_list["callbot"]) //Command a bot to move to a selected location. + Bot = locate(href_list["callbot"]) in GLOB.bots_list + if(!Bot || Bot.remote_disabled || control_disabled) + return //True if there is no bot found, the bot is manually emagged, or the AI is carded with wireless off. + waypoint_mode = 1 + to_chat(src, "Set your waypoint by clicking on a valid location free of obstructions.") + return + + if(href_list["interface"]) //Remotely connect to a bot! + Bot = locate(href_list["interface"]) in GLOB.bots_list + if(!Bot || Bot.remote_disabled || control_disabled) + return + Bot.attack_ai(src) + + if(href_list["botrefresh"]) //Refreshes the bot control panel. + botcall() + return + + if(href_list["ai_take_control"]) //Mech domination + + var/obj/mecha/M = locate(href_list["ai_take_control"]) + + if(!M) + return + + var/mech_has_controlbeacon = FALSE + for(var/obj/item/mecha_parts/mecha_tracking/ai_control/A in M.trackers) + mech_has_controlbeacon = TRUE + break + if(!can_dominate_mechs && !mech_has_controlbeacon) + message_admins("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.") + log_debug("Warning: possible href exploit by [key_name(usr)] - attempted control of a mecha without can_dominate_mechs or a control beacon in the mech.") + return + + if(controlled_mech) + to_chat(src, "You are already loaded into an onboard computer!") + return + if(!cameranet.checkCameraVis(M)) + to_chat(src, "Exosuit is no longer near active cameras.") + return + if(lacks_power()) + to_chat(src, "You're depowered!") + return + if(!isturf(loc)) + to_chat(src, "You aren't in your core!") + return + if(M) + M.transfer_ai(AI_MECH_HACK, src, usr) //Called om the mech itself. + + else if(href_list["faketrack"]) + var/mob/target = locate(href_list["track"]) in GLOB.mob_list + var/mob/living/silicon/ai/A = locate(href_list["track2"]) in GLOB.mob_list + if(A && target) + + A.cameraFollow = target + to_chat(A, "Now tracking [target.name] on camera.") + if(usr.machine == null) + usr.machine = usr + + while(cameraFollow == target) + to_chat(usr, "Target is not on or near any active cameras on the station. We'll check again in 5 seconds (unless you use the cancel-camera verb).") + sleep(40) + continue + + else if(href_list["open"]) + var/mob/target = locate(href_list["open"]) in GLOB.mob_list + if(target) + open_nearest_door(target) + +/mob/living/silicon/ai/bullet_act(var/obj/item/projectile/Proj) + ..(Proj) + return 2 + +/mob/living/silicon/ai/reset_perspective(atom/A) + if(camera_light_on) + light_cameras() + if(istype(A, /obj/machinery/camera)) + current = A + if(A != GLOB.ai_camera_room_landmark) + end_multicam() + + . = ..() + if(.) + if(!A && isturf(loc) && eyeobj) + end_multicam() + client.eye = eyeobj + client.perspective = MOB_PERSPECTIVE + eyeobj.get_remote_view_fullscreens(src) + +/mob/living/silicon/ai/proc/botcall() + set category = "AI Commands" + set name = "Access Robot Control" + set desc = "Wirelessly control various automatic robots." + if(stat == 2) + to_chat(src, "Critical error. System offline.") + return + + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + var/d + var/area/bot_area + d += "Query network status
        " + d += "

        Name

        Status

        Location

        Control

        [Bot.hacked ? "(!) [Bot.name]" : Bot.name] ([Bot.model])[Bot.on ? "[Bot.mode ? "[ Bot.mode_name[Bot.mode] ]": "Idle"]" : "Inactive"][bot_area.name]InterfaceCall
        " + + for(var/mob/living/simple_animal/bot/Bot in GLOB.bots_list) + if(is_ai_allowed(Bot.z) && !Bot.remote_disabled) //Only non-emagged bots on the allowed Z-level are detected! + bot_area = get_area(Bot) + d += "" + //If the bot is on, it will display the bot's current mode status. If the bot is not mode, it will just report "Idle". "Inactive if it is not on at all. + d += "" + d += "" + d += "" + d += "" + d += "" + d = format_text(d) + + var/datum/browser/popup = new(src, "botcall", "Remote Robot Control", 700, 400) + popup.set_content(d) + popup.open() + +/mob/living/silicon/ai/proc/set_waypoint(atom/A) + var/turf/turf_check = get_turf(A) + //The target must be in view of a camera or near the core. + if(turf_check in range(get_turf(src))) + call_bot(turf_check) + else if(cameranet && cameranet.checkTurfVis(turf_check)) + call_bot(turf_check) + else + to_chat(src, "Selected location is not visible.") + +/mob/living/silicon/ai/proc/call_bot(turf/waypoint) + + if(!Bot) + return + + if(Bot.calling_ai && Bot.calling_ai != src) //Prevents an override if another AI is controlling this bot. + to_chat(src, "Interface error. Unit is already in use.") + return + + Bot.call_bot(src, waypoint) + +/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C) + + if(!tracking) + cameraFollow = null + + if(!C || stat == DEAD) //C.can_use()) + return FALSE + + if(!eyeobj) + view_core() + return + // ok, we're alive, camera is good and in our network... + eyeobj.setLoc(get_turf(C)) + //machine = src + + return TRUE + +//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm +//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead +//Addition by Mord_Sith to define AI's network change ability +/mob/living/silicon/ai/proc/ai_network_change() + set category = "AI Commands" + set name = "Jump To Network" + unset_machine() + var/cameralist[0] + + if(check_unable()) + return + + if(usr.stat == 2) + to_chat(usr, "You can't change your camera network because you are dead!") + return + + var/mob/living/silicon/ai/U = usr + + for(var/obj/machinery/camera/C in cameranet.cameras) + if(!C.can_use()) + continue + + var/list/tempnetwork = difflist(C.network,GLOB.restricted_camera_networks,1) + if(tempnetwork.len) + for(var/i in tempnetwork) + cameralist[i] = i + var/old_network = network + network = input(U, "Which network would you like to view?") as null|anything in cameralist + + if(check_unable()) + return + + if(!U.eyeobj) + U.view_core() + return + + if(isnull(network)) + network = old_network // If nothing is selected + else + for(var/obj/machinery/camera/C in cameranet.cameras) + if(!C.can_use()) + continue + if(network in C.network) + U.eyeobj.setLoc(get_turf(C)) + break + to_chat(src, "Switched to [network] camera network.") +//End of code by Mord_Sith + + +/mob/living/silicon/ai/proc/choose_modules() + set category = "Malfunction" + set name = "Choose Module" + + malf_picker.use(src) + +/mob/living/silicon/ai/proc/ai_statuschange() + set category = "AI Commands" + set name = "AI Status" + + if(usr.stat == 2) + to_chat(usr, "You cannot change your emotional status because you are dead!") + return + + if(check_unable()) + return + + var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Friend Computer") + var/emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions + + if(check_unable()) + return + + for(var/obj/machinery/M in GLOB.machines) //change status + if(istype(M, /obj/machinery/ai_status_display)) + var/obj/machinery/ai_status_display/AISD = M + AISD.emotion = emote + //if Friend Computer, change ALL displays + else if(istype(M, /obj/machinery/status_display)) + + var/obj/machinery/status_display/SD = M + if(emote=="Friend Computer") + SD.friendc = 1 + else + SD.friendc = 0 + return + +//I am the icon meister. Bow fefore me. //>fefore +/mob/living/silicon/ai/proc/ai_hologram_change() + set name = "Change Hologram" + set desc = "Change the default hologram available to AI to something else." + set category = "AI Commands" + + if(check_unable()) + return + if(!custom_hologram) //Check to see if custom sprite time, checking the appopriate file to change a var + var/file = file2text("config/custom_sprites.txt") + var/lines = splittext(file, "\n") + + for(var/line in lines) + // split & clean up + var/list/Entry = splittext(line, ":") + for(var/i = 1 to Entry.len) + Entry[i] = trim(Entry[i]) + + if(Entry.len < 2 || Entry[1] != "hologram") + continue + + if (Entry[2] == ckey) //Custom holograms + custom_hologram = 1 // option is given in hologram menu + + var/input + switch(alert("Would you like to select a hologram based on a crew member, an animal, or switch to a unique avatar?",,"Crew Member","Unique","Animal")) + if("Crew Member") + var/personnel_list[] = list() + + for(var/datum/data/record/t in data_core.locked)//Look in data core locked. + personnel_list["[t.fields["name"]]: [t.fields["rank"]]"] = t.fields["image"]//Pull names, rank, and image. + + if(personnel_list.len) + input = input("Select a crew member:") as null|anything in personnel_list + var/icon/character_icon = personnel_list[input] + if(character_icon) + qdel(holo_icon)//Clear old icon so we're not storing it in memory. + holo_icon = getHologramIcon(icon(character_icon)) + else + alert("No suitable records found. Aborting.") + + if("Animal") + var/icon_list[] = list( + "Bear", + "Carp", + "Chicken", + "Corgi", + "Cow", + "Crab", + "Deer", + "Fox", + "Goat", + "Goose", + "Kitten", + "Kitten2", + "Pig", + "Poly", + "Pug", + "Seal", + "Spider", + "Turkey" + ) + + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("Bear") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"bear")) + if("Carp") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"carp")) + if("Chicken") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"chicken_brown")) + if("Corgi") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"corgi")) + if("Cow") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"cow")) + if("Crab") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"crab")) + if("Deer") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"deer")) + if("Fox") + holo_icon = getHologramIcon(icon('icons/mob/pets.dmi',"fox")) + if("Goat") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"goat")) + if("Goose") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"goose")) + if("Kitten") + holo_icon = getHologramIcon(icon('icons/mob/pets.dmi',"cat")) + if("Kitten2") + holo_icon = getHologramIcon(icon('icons/mob/pets.dmi',"cat2")) + if("Pig") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"pig")) + if("Poly") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"parrot_fly")) + if("Pug") + holo_icon = getHologramIcon(icon('icons/mob/pets.dmi',"pug")) + if("Seal") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"seal")) + if("Spider") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"guard")) + if("Turkey") + holo_icon = getHologramIcon(icon('icons/mob/animal.dmi',"turkey")) + + else + var/icon_list[] = list( + "default", + "floating face", + "xeno queen", + "eldritch", + "ancient machine" + ) + if(custom_hologram) //insert custom hologram + icon_list.Add("custom") + + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("default") + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo1")) + if("floating face") + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo2")) + if("xeno queen") + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo3")) + if("eldritch") + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo4")) + if("ancient machine") + holo_icon = getHologramIcon(icon('icons/mob/ancient_machine.dmi', "ancient_machine")) + if("custom") + if("[ckey]-ai-holo" in icon_states('icons/mob/custom_synthetic/custom-synthetic.dmi')) + holo_icon = getHologramIcon(icon('icons/mob/custom_synthetic/custom-synthetic.dmi', "[ckey]-ai-holo")) + else if("[ckey]-ai-holo" in icon_states('icons/mob/custom_synthetic/custom-synthetic64.dmi')) + holo_icon = getHologramIcon(icon('icons/mob/custom_synthetic/custom-synthetic64.dmi', "[ckey]-ai-holo")) + else + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"holo1")) + + return + +/mob/living/silicon/ai/proc/corereturn() + set category = "Malfunction" + set name = "Return to Main Core" + + var/obj/machinery/power/apc/apc = loc + if(!istype(apc)) + to_chat(src, "You are already in your Main Core.") + return + apc.malfvacate() + +//Toggles the luminosity and applies it by re-entereing the camera. +/mob/living/silicon/ai/proc/toggle_camera_light() + set name = "Toggle Camera Lights" + set desc = "Toggles the lights on the cameras throughout the station." + set category = "AI Commands" + + if(stat != CONSCIOUS) + return + + camera_light_on = !camera_light_on + + if(!camera_light_on) + to_chat(src, "Camera lights deactivated.") + + for(var/obj/machinery/camera/C in lit_cameras) + C.set_light(0) + lit_cameras = list() + + return + + light_cameras() + + to_chat(src, "Camera lights activated.") + +/mob/living/silicon/ai/proc/set_syndie_radio() + if(aiRadio) + aiRadio.make_syndie() + +/mob/living/silicon/ai/proc/sensor_mode() + set name = "Set Sensor Augmentation" + set desc = "Augment visual feed with internal sensor overlays." + set category = "AI Commands" + toggle_sensor_mode() + +/mob/living/silicon/ai/proc/change_arrival_message() + set name = "Set Arrival Message" + set desc = "Change the message that's transmitted when a new crew member arrives on station." + set category = "AI Commands" + + var/newmsg = clean_input("What would you like the arrival message to be? List of options: $name, $rank, $species, $gender, $age", "Change Arrival Message", arrivalmsg) + if(newmsg != arrivalmsg) + arrivalmsg = newmsg + to_chat(usr, "The arrival message has been successfully changed.") + +// Handled camera lighting, when toggled. +// It will get the nearest camera from the eyeobj, lighting it. + +/mob/living/silicon/ai/proc/light_cameras() + var/list/obj/machinery/camera/add = list() + var/list/obj/machinery/camera/remove = list() + var/list/obj/machinery/camera/visible = list() + for(var/datum/camerachunk/CC in eyeobj.visibleCameraChunks) + for(var/obj/machinery/camera/C in CC.cameras) + if(!C.can_use() || get_dist(C, eyeobj) > 7) + continue + visible |= C + + add = visible - lit_cameras + remove = lit_cameras - visible + + for(var/obj/machinery/camera/C in remove) + lit_cameras -= C //Removed from list before turning off the light so that it doesn't check the AI looking away. + C.Togglelight(0) + for(var/obj/machinery/camera/C in add) + C.Togglelight(1) + lit_cameras |= C + + +/mob/living/silicon/ai/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/wrench)) + if(anchored) + user.visible_message("\The [user] starts to unbolt \the [src] from the plating...") + if(!do_after(user, 40 * W.toolspeed, target = src)) + user.visible_message("\The [user] decides not to unbolt \the [src].") + return + user.visible_message("\The [user] finishes unfastening \the [src]!") + anchored = FALSE + return + else + user.visible_message("\The [user] starts to bolt \the [src] to the plating...") + if(!do_after(user, 40 * W.toolspeed, target = src)) + user.visible_message("\The [user] decides not to bolt \the [src].") + return + user.visible_message("\The [user] finishes fastening down \the [src]!") + anchored = TRUE + return + else + return ..() + +/mob/living/silicon/ai/welder_act() + return + +/mob/living/silicon/ai/proc/control_integrated_radio() + set name = "Radio Settings" + set desc = "Allows you to change settings of your radio." + set category = "AI Commands" + + if(check_unable(AI_CHECK_RADIO)) + return + + to_chat(src, "Accessing Subspace Transceiver control...") + if(aiRadio) + aiRadio.interact(src) + + +/mob/living/silicon/ai/proc/check_unable(flags = 0) + if(stat == DEAD) + to_chat(src, "You are dead!") + return TRUE + + if(lacks_power()) + to_chat(src, "Power systems failure!") + return TRUE + + if((flags & AI_CHECK_WIRELESS) && control_disabled) + to_chat(src, "Wireless control is disabled!") + return TRUE + if((flags & AI_CHECK_RADIO) && aiRadio.disabledAi) + to_chat(src, "System Error - Transceiver Disabled!") + return TRUE + return FALSE + +/mob/living/silicon/ai/proc/is_in_chassis() + return isturf(loc) + +/mob/living/silicon/ai/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(!..()) + return + if(interaction == AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card. + if(!mind) + to_chat(user, "No intelligence patterns detected.")//No more magical carding of empty cores, AI RETURN TO BODY!!!11 + return + new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location. + aiRestorePowerRoutine = 0//So the AI initially has power. + control_disabled = 1//Can't control things remotely if you're stuck in a card! + aiRadio.disabledAi = 1 //No talking on the built-in radio for you either! + loc = card//Throw AI into the card. + to_chat(src, "You have been downloaded to a mobile storage device. Remote device connection severed.") + to_chat(user, "Transfer successful: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.") + +/mob/living/silicon/ai/can_buckle() + return FALSE + +// Pass lying down or getting up to our pet human, if we're in a rig. +/mob/living/silicon/ai/lay_down() + set name = "Rest" + set category = "IC" + + resting = 0 + var/obj/item/rig/rig = get_rig() + if(rig) + rig.force_rest(src) + +/mob/living/silicon/ai/switch_to_camera(obj/machinery/camera/C) + if(!C.can_use() || !is_in_chassis()) + return FALSE + + eyeobj.setLoc(get_turf(C)) + client.eye = eyeobj + return TRUE + + +/mob/living/silicon/ai/proc/can_see(atom/A) + if(isturf(loc)) //AI in core, check if on cameras + //get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera + //apc_override is needed here because AIs use their own APC when depowered + var/turf/T = isturf(A) ? A : get_turf_pixel(A) + return (cameranet && cameranet.checkTurfVis(T)) || apc_override + //AI is carded/shunted + //view(src) returns nothing for carded/shunted AIs and they have x-ray vision so just use get_dist + var/list/viewscale = getviewsize(client.view) + return get_dist(src, A) <= max(viewscale[1]*0.5,viewscale[2]*0.5) + +/mob/living/silicon/ai/proc/relay_speech(mob/living/M, list/message_pieces, verb) + var/message = combine_message(message_pieces, verb, M) + var/name_used = M.GetVoice() + //This communication is imperfect because the holopad "filters" voices and is only designed to connect to the master only. + var/rendered = "Relayed Speech: [name_used] [message]" + show_message(rendered, 2) + +/mob/living/silicon/ai/proc/malfhacked(obj/machinery/power/apc/apc) + malfhack = null + malfhacking = 0 + clear_alert("hackingapc") + + if(!istype(apc) || QDELETED(apc) || apc.stat & BROKEN) + to_chat(src, "Hack aborted. The designated APC no longer exists on the power network.") + playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, 1) + else if(apc.aidisabled) + to_chat(src, "Hack aborted. [apc] is no longer responding to our systems.") + playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 1) + else + malf_picker.processing_time += 10 + + apc.malfai = parent || src + apc.malfhack = TRUE + apc.locked = TRUE + + playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1) + to_chat(src, "Hack complete. [apc] is now under your exclusive control.") + apc.update_icon() + +/mob/living/silicon/ai/proc/add_malf_picker() + to_chat(src, "In the top right corner of the screen you will find the Malfunctions tab, where you can purchase various abilities, from upgraded surveillance to station ending doomsday devices.") + to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.") + view_core() //A BYOND bug requires you to be viewing your core before your verbs update + verbs += /mob/living/silicon/ai/proc/choose_modules + malf_picker = new /datum/module_picker + +/mob/living/silicon/ai/proc/open_nearest_door(mob/living/target) + if(!istype(target)) + return + + if(target && target.can_track()) + var/obj/machinery/door/airlock/A = null + + var/dist = -1 + for(var/obj/machinery/door/airlock/D in range(3, target)) + if(!D.density) + continue + + var/curr_dist = get_dist(D, target) + + if(dist < 0) + dist = curr_dist + A = D + else if(dist > curr_dist) + dist = curr_dist + A = D + + if(istype(A)) + switch(alert(src, "Do you want to open \the [A] for [target]?", "Doorknob_v2a.exe", "Yes", "No")) + if("Yes") + A.AIShiftClick() + to_chat(src, "You open \the [A] for [target].") + else + to_chat(src, "You deny the request.") + else + to_chat(src, "Unable to locate an airlock near [target].") + + else + to_chat(src, "Target is not on or near any active cameras on the station.") + +/mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye) + cameranet.visibility(moved_eye, client, all_eyes) + +/mob/living/silicon/ai/forceMove(atom/destination) + . = ..() + if(.) + end_multicam() + +/mob/living/silicon/ai/handle_fire() + return + +/mob/living/silicon/ai/update_fire() + return + +/mob/living/silicon/ai/IgniteMob() + return FALSE + +/mob/living/silicon/ai/ExtinguishMob() + return + + +/mob/living/silicon/ai/update_sight() + if(!client) + return + + if(stat == DEAD) + grant_death_vision() + return + + see_invisible = initial(see_invisible) + see_in_dark = initial(see_in_dark) + sight = initial(sight) + lighting_alpha = initial(lighting_alpha) + + if(aiRestorePowerRoutine) + sight = sight &~ SEE_TURFS + sight = sight &~ SEE_MOBS + sight = sight &~ SEE_OBJS + see_in_dark = 0 + + SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) + sync_lighting_plane_alpha() diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm index ba418ef44c6..2e35c437d45 100644 --- a/code/modules/mob/living/silicon/ai/death.dm +++ b/code/modules/mob/living/silicon/ai/death.dm @@ -1,41 +1,41 @@ -/mob/living/silicon/ai/death(gibbed) - // Only execute the below if we successfully died - . = ..(gibbed) - if(!.) - return FALSE - if(custom_sprite == 1)//check for custom AI sprite, defaulting to blue screen if no. - icon_state = "[ckey]-ai_dead" - else if("[icon_state]_dead" in icon_states(icon,1)) - icon_state = "[icon_state]_dead" - else - icon_state = "ai_dead" - if(eyeobj) - eyeobj.setLoc(get_turf(src)) - - GLOB.shuttle_caller_list -= src - SSshuttle.autoEvac() - - if(nuking) - set_security_level("red") - nuking = 0 - for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) - point.the_disk = null //Point back to the disk. - - if(doomsday_device) - doomsday_device.timing = 0 - SSshuttle.emergencyNoEscape = 0 - if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) - SSshuttle.emergency.mode = SHUTTLE_DOCKED - SSshuttle.emergency.timer = world.time - priority_announcement.Announce("Hostile environment resolved. You have 3 minutes to board the Emergency Shuttle.", "Priority Announcement", 'sound/AI/shuttledock.ogg') - qdel(doomsday_device) - - if(explosive) - spawn(10) - explosion(src.loc, 3, 6, 12, 15) - - for(var/obj/machinery/ai_status_display/O in world) //change status - O.mode = 2 - - if(istype(loc, /obj/item/aicard)) - loc.icon_state = "aicard-404" +/mob/living/silicon/ai/death(gibbed) + // Only execute the below if we successfully died + . = ..(gibbed) + if(!.) + return FALSE + if(custom_sprite == 1)//check for custom AI sprite, defaulting to blue screen if no. + icon_state = "[ckey]-ai_dead" + else if("[icon_state]_dead" in icon_states(icon,1)) + icon_state = "[icon_state]_dead" + else + icon_state = "ai_dead" + if(eyeobj) + eyeobj.setLoc(get_turf(src)) + + GLOB.shuttle_caller_list -= src + SSshuttle.autoEvac() + + if(nuking) + set_security_level("red") + nuking = 0 + for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) + point.the_disk = null //Point back to the disk. + + if(doomsday_device) + doomsday_device.timing = 0 + SSshuttle.emergencyNoEscape = 0 + if(SSshuttle.emergency.mode == SHUTTLE_STRANDED) + SSshuttle.emergency.mode = SHUTTLE_DOCKED + SSshuttle.emergency.timer = world.time + priority_announcement.Announce("Hostile environment resolved. You have 3 minutes to board the Emergency Shuttle.", "Priority Announcement", 'sound/AI/shuttledock.ogg') + qdel(doomsday_device) + + if(explosive) + spawn(10) + explosion(src.loc, 3, 6, 12, 15) + + for(var/obj/machinery/ai_status_display/O in world) //change status + O.mode = 2 + + if(istype(loc, /obj/item/aicard)) + loc.icon_state = "aicard-404" diff --git a/code/modules/mob/living/silicon/ai/examine.dm b/code/modules/mob/living/silicon/ai/examine.dm index 1b8a4fa43c3..c881fa08e86 100644 --- a/code/modules/mob/living/silicon/ai/examine.dm +++ b/code/modules/mob/living/silicon/ai/examine.dm @@ -1,34 +1,34 @@ -/mob/living/silicon/ai/examine(mob/user) - . = ..() - var/msg = "" - if(src.stat == DEAD) - msg += "It appears to be powered-down.\n" - else - msg += "" - if(src.getBruteLoss()) - if(src.getBruteLoss() < 30) - msg += "It looks slightly dented.\n" - else - msg += "It looks severely dented!\n" - if(src.getFireLoss()) - if(src.getFireLoss() < 30) - msg += "It looks slightly charred.\n" - else - msg += "Its casing is melted and heat-warped!\n" - if(src.stat == UNCONSCIOUS) - msg += "It is non-responsive and displaying the text: \"RUNTIME: Sensory Overload, stack 26/3\".\n" - if(!shunted && !client) - msg += "[src]Core.exe has stopped responding! NTOS is searching for a solution to the problem...\n" - msg += "" - msg += "*---------*" - - . += msg - user.showLaws(src) - - -/mob/proc/showLaws(var/mob/living/silicon/S) - return - -/mob/dead/observer/showLaws(var/mob/living/silicon/S) - if(antagHUD || check_rights(R_ADMIN, 0, src)) - S.laws.show_laws(src) +/mob/living/silicon/ai/examine(mob/user) + . = ..() + var/msg = "" + if(src.stat == DEAD) + msg += "It appears to be powered-down.\n" + else + msg += "" + if(src.getBruteLoss()) + if(src.getBruteLoss() < 30) + msg += "It looks slightly dented.\n" + else + msg += "It looks severely dented!\n" + if(src.getFireLoss()) + if(src.getFireLoss() < 30) + msg += "It looks slightly charred.\n" + else + msg += "Its casing is melted and heat-warped!\n" + if(src.stat == UNCONSCIOUS) + msg += "It is non-responsive and displaying the text: \"RUNTIME: Sensory Overload, stack 26/3\".\n" + if(!shunted && !client) + msg += "[src]Core.exe has stopped responding! NTOS is searching for a solution to the problem...\n" + msg += "" + msg += "*---------*" + + . += msg + user.showLaws(src) + + +/mob/proc/showLaws(var/mob/living/silicon/S) + return + +/mob/dead/observer/showLaws(var/mob/living/silicon/S) + if(antagHUD || check_rights(R_ADMIN, 0, src)) + S.laws.show_laws(src) diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index f6b929826bd..fac6e3b518b 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -197,4 +197,4 @@ var/datum/cameranet/cameranet = new() if(cameranet.chunkGenerated(x, y, z)) var/datum/camerachunk/chunk = cameranet.getCameraChunk(x, y, z) usr.client.debug_variables(chunk) -*/ \ No newline at end of file +*/ diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 78fcf10a80c..d23fedb09bc 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -1,151 +1,151 @@ -// AI EYE -// -// An invisible (no icon) mob that the AI controls to look around the station with. -// It streams chunks as it moves around, which will show it what the AI can and cannot see. - -/mob/camera/aiEye - name = "Inactive AI Eye" - - icon = 'icons/mob/ai.dmi' //Allows ghosts to see what the AI is looking at. - icon_state = "eye" - alpha = 127 - invisibility = SEE_INVISIBLE_OBSERVER - - var/list/visibleCameraChunks = list() - var/mob/living/silicon/ai/ai = null - var/relay_speech = FALSE - var/use_static = TRUE - var/static_visibility_range = 16 - - -// Use this when setting the aiEye's location. -// It will also stream the chunk that the new loc is in. - -/mob/camera/aiEye/setLoc(T) - if(ai) - if(!isturf(ai.loc)) - return - T = get_turf(T) - loc = T - if(use_static) - ai.camera_visibility(src) - if(ai.client && !ai.multicam_on) - ai.client.eye = src - update_parallax_contents() - //Holopad - if(ai.master_multicam) - ai.master_multicam.refresh_view() - if(istype(ai.current, /obj/machinery/hologram/holopad)) - var/obj/machinery/hologram/holopad/H = ai.current - H.move_hologram(ai, T) - -/mob/camera/aiEye/Move() - return 0 - -/mob/camera/aiEye/proc/GetViewerClient() - if(ai) - return ai.client - return null - -/mob/camera/aiEye/proc/RemoveImages() - var/client/C = GetViewerClient() - if(C && use_static) - for(var/V in visibleCameraChunks) - var/datum/camerachunk/chunk = V - C.images -= chunk.obscured - -/mob/camera/aiEye/Destroy() - if(ai) - ai.all_eyes -= src - ai = null - for(var/V in visibleCameraChunks) - var/datum/camerachunk/chunk = V - chunk.remove(src) - return ..() - -/atom/proc/move_camera_by_click() - if(istype(usr, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/AI = usr - if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z)) - AI.cameraFollow = null - if(isturf(loc) || isturf(src)) - AI.eyeobj.setLoc(src) - -// AI MOVEMENT - -// This will move the AIEye. It will also cause lights near the eye to light up, if toggled. -// This is handled in the proc below this one. - -/client/proc/AIMove(n, direct, var/mob/living/silicon/ai/user) - - var/initial = initial(user.sprint) - var/max_sprint = 50 - - if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds - user.sprint = initial - - for(var/i = 0; i < max(user.sprint, initial); i += 20) - var/turf/step = get_turf(get_step(user.eyeobj, direct)) - if(step) - user.eyeobj.setLoc(step) - - user.cooldown = world.timeofday + 5 - if(user.acceleration) - user.sprint = min(user.sprint + 0.5, max_sprint) - else - user.sprint = initial - - if(!user.tracking) - user.cameraFollow = null - - //user.unset_machine() //Uncomment this if it causes problems. - //user.lightNearbyCamera() - if(user.camera_light_on) - user.light_cameras() - -// Return to the Core. -/mob/living/silicon/ai/proc/core() - set category = "AI Commands" - set name = "AI Core" - - view_core() - -/mob/living/silicon/ai/proc/view_core() - - current = null - cameraFollow = null - unset_machine() - - if(src.eyeobj && src.loc) - src.eyeobj.loc = src.loc - else - to_chat(src, "ERROR: Eyeobj not found. Creating new eye...") - create_eye() - - eyeobj.setLoc(loc) - -/mob/living/silicon/ai/proc/create_eye() - if(eyeobj) - return - eyeobj = new /mob/camera/aiEye() - all_eyes += eyeobj - eyeobj.ai = src - eyeobj.setLoc(loc) - eyeobj.name = "[name] (AI Eye)" - -/mob/living/silicon/ai/proc/toggle_acceleration() - set category = "AI Commands" - set name = "Toggle Camera Acceleration" - - if(usr.stat == 2) - return //won't work if dead - acceleration = !acceleration - to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].") - -/mob/camera/aiEye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency) - if(relay_speech) - if(istype(ai)) - ai.relay_speech(speaker, message_pieces, verb) - else - var/mob/M = ai - M.hear_say(message_pieces, verb, italics, speaker, speech_sound, sound_vol, sound_frequency) +// AI EYE +// +// An invisible (no icon) mob that the AI controls to look around the station with. +// It streams chunks as it moves around, which will show it what the AI can and cannot see. + +/mob/camera/aiEye + name = "Inactive AI Eye" + + icon = 'icons/mob/ai.dmi' //Allows ghosts to see what the AI is looking at. + icon_state = "eye" + alpha = 127 + invisibility = SEE_INVISIBLE_OBSERVER + + var/list/visibleCameraChunks = list() + var/mob/living/silicon/ai/ai = null + var/relay_speech = FALSE + var/use_static = TRUE + var/static_visibility_range = 16 + + +// Use this when setting the aiEye's location. +// It will also stream the chunk that the new loc is in. + +/mob/camera/aiEye/setLoc(T) + if(ai) + if(!isturf(ai.loc)) + return + T = get_turf(T) + loc = T + if(use_static) + ai.camera_visibility(src) + if(ai.client && !ai.multicam_on) + ai.client.eye = src + update_parallax_contents() + //Holopad + if(ai.master_multicam) + ai.master_multicam.refresh_view() + if(istype(ai.current, /obj/machinery/hologram/holopad)) + var/obj/machinery/hologram/holopad/H = ai.current + H.move_hologram(ai, T) + +/mob/camera/aiEye/Move() + return 0 + +/mob/camera/aiEye/proc/GetViewerClient() + if(ai) + return ai.client + return null + +/mob/camera/aiEye/proc/RemoveImages() + var/client/C = GetViewerClient() + if(C && use_static) + for(var/V in visibleCameraChunks) + var/datum/camerachunk/chunk = V + C.images -= chunk.obscured + +/mob/camera/aiEye/Destroy() + if(ai) + ai.all_eyes -= src + ai = null + for(var/V in visibleCameraChunks) + var/datum/camerachunk/chunk = V + chunk.remove(src) + return ..() + +/atom/proc/move_camera_by_click() + if(istype(usr, /mob/living/silicon/ai)) + var/mob/living/silicon/ai/AI = usr + if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z)) + AI.cameraFollow = null + if(isturf(loc) || isturf(src)) + AI.eyeobj.setLoc(src) + +// AI MOVEMENT + +// This will move the AIEye. It will also cause lights near the eye to light up, if toggled. +// This is handled in the proc below this one. + +/client/proc/AIMove(n, direct, var/mob/living/silicon/ai/user) + + var/initial = initial(user.sprint) + var/max_sprint = 50 + + if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds + user.sprint = initial + + for(var/i = 0; i < max(user.sprint, initial); i += 20) + var/turf/step = get_turf(get_step(user.eyeobj, direct)) + if(step) + user.eyeobj.setLoc(step) + + user.cooldown = world.timeofday + 5 + if(user.acceleration) + user.sprint = min(user.sprint + 0.5, max_sprint) + else + user.sprint = initial + + if(!user.tracking) + user.cameraFollow = null + + //user.unset_machine() //Uncomment this if it causes problems. + //user.lightNearbyCamera() + if(user.camera_light_on) + user.light_cameras() + +// Return to the Core. +/mob/living/silicon/ai/proc/core() + set category = "AI Commands" + set name = "AI Core" + + view_core() + +/mob/living/silicon/ai/proc/view_core() + + current = null + cameraFollow = null + unset_machine() + + if(src.eyeobj && src.loc) + src.eyeobj.loc = src.loc + else + to_chat(src, "ERROR: Eyeobj not found. Creating new eye...") + create_eye() + + eyeobj.setLoc(loc) + +/mob/living/silicon/ai/proc/create_eye() + if(eyeobj) + return + eyeobj = new /mob/camera/aiEye() + all_eyes += eyeobj + eyeobj.ai = src + eyeobj.setLoc(loc) + eyeobj.name = "[name] (AI Eye)" + +/mob/living/silicon/ai/proc/toggle_acceleration() + set category = "AI Commands" + set name = "Toggle Camera Acceleration" + + if(usr.stat == 2) + return //won't work if dead + acceleration = !acceleration + to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].") + +/mob/camera/aiEye/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency) + if(relay_speech) + if(istype(ai)) + ai.relay_speech(speaker, message_pieces, verb) + else + var/mob/M = ai + M.hear_say(message_pieces, verb, italics, speaker, speech_sound, sound_vol, sound_frequency) diff --git a/code/modules/mob/living/silicon/ai/freelook/read_me.dm b/code/modules/mob/living/silicon/ai/freelook/read_me.dm index 8ddb0689409..869fcc2cdac 100644 --- a/code/modules/mob/living/silicon/ai/freelook/read_me.dm +++ b/code/modules/mob/living/silicon/ai/freelook/read_me.dm @@ -1,51 +1,51 @@ -// CREDITS -/* - Initial code credit for this goes to Uristqwerty. - Debugging, functionality, all comments and porting by Giacom. - - Everything about freelook (or what we can put in here) will be stored here. - - - WHAT IS THIS? - - This is a replacement for the current camera movement system, of the AI. Before this, the AI had to move between cameras and could - only see what the cameras could see. Not only this but the cameras could see through walls, which created problems. - With this, the AI controls an "AI Eye" mob, which moves just like a ghost; such as moving through walls and being invisible to players. - The AI's eye is set to this mob and then we use a system (explained below) to determine what the cameras around the AI Eye can and - cannot see. If the camera cannot see a turf, it will black it out, otherwise it won't and the AI will be able to see it. - This creates several features, such as.. no more see-through-wall cameras, easier to control camera movement, easier tracking, - the AI only being able to track mobs which are visible to a camera, only trackable mobs appearing on the mob list and many more. - - - HOW IT WORKS - - It works by first creating a camera network datum. Inside of this camera network are "chunks" (which will be - explained later) and "cameras". The cameras list is kept up to date by obj/machinery/camera/New() and Destroy(). - - Next the camera network has chunks. These chunks are a 16x16 tile block of turfs and cameras contained inside the chunk. - These turfs are then sorted out based on what the cameras can and cannot see. If none of the cameras can see the turf, inside - the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't be able to see it. - - - HOW IT UPDATES - - The camera network uses a streaming method in order to effeciently update chunks. Since the server will have doors opening, doors closing, - turf being destroyed and other lag inducing stuff, we want to update it under certain conditions and not every tick. - - The chunks are not created straight away, only when an AI eye moves into it's area is when it gets created. - One a chunk is created, when a non glass door opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is looking in the area. - We do this with the "seenby" list, which updates everytime an AI is near a chunk. If there is an AI eye inside the area, we update the chunk - that the changed atom is inside and all surrounding chunks, since a camera's vision could leak onto another chunk. If there is no AI Eye, we instead - flag the chunk to update whenever it is loaded by an AI Eye. This is basically how the chunks update and keep it in sync. We then add some lag reducing - measures, such as an UPDATE_BUFFER which stops a chunk from updating too many times in a certain time-frame, only updating if the changed atom was blocking - sight; for example, we don't update glass airlocks or floors. - - - WHERE IS EVERYTHING? - - cameranet.dm = Everything about the cameranet datum. - chunk.dm = Everything about the chunk datum. - eye.dm = Everything about the AI and the AIEye. - updating.dm = Everything about triggers that will update chunks. - -*/ \ No newline at end of file +// CREDITS +/* + Initial code credit for this goes to Uristqwerty. + Debugging, functionality, all comments and porting by Giacom. + + Everything about freelook (or what we can put in here) will be stored here. + + + WHAT IS THIS? + + This is a replacement for the current camera movement system, of the AI. Before this, the AI had to move between cameras and could + only see what the cameras could see. Not only this but the cameras could see through walls, which created problems. + With this, the AI controls an "AI Eye" mob, which moves just like a ghost; such as moving through walls and being invisible to players. + The AI's eye is set to this mob and then we use a system (explained below) to determine what the cameras around the AI Eye can and + cannot see. If the camera cannot see a turf, it will black it out, otherwise it won't and the AI will be able to see it. + This creates several features, such as.. no more see-through-wall cameras, easier to control camera movement, easier tracking, + the AI only being able to track mobs which are visible to a camera, only trackable mobs appearing on the mob list and many more. + + + HOW IT WORKS + + It works by first creating a camera network datum. Inside of this camera network are "chunks" (which will be + explained later) and "cameras". The cameras list is kept up to date by obj/machinery/camera/New() and Destroy(). + + Next the camera network has chunks. These chunks are a 16x16 tile block of turfs and cameras contained inside the chunk. + These turfs are then sorted out based on what the cameras can and cannot see. If none of the cameras can see the turf, inside + the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't be able to see it. + + + HOW IT UPDATES + + The camera network uses a streaming method in order to effeciently update chunks. Since the server will have doors opening, doors closing, + turf being destroyed and other lag inducing stuff, we want to update it under certain conditions and not every tick. + + The chunks are not created straight away, only when an AI eye moves into it's area is when it gets created. + One a chunk is created, when a non glass door opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is looking in the area. + We do this with the "seenby" list, which updates everytime an AI is near a chunk. If there is an AI eye inside the area, we update the chunk + that the changed atom is inside and all surrounding chunks, since a camera's vision could leak onto another chunk. If there is no AI Eye, we instead + flag the chunk to update whenever it is loaded by an AI Eye. This is basically how the chunks update and keep it in sync. We then add some lag reducing + measures, such as an UPDATE_BUFFER which stops a chunk from updating too many times in a certain time-frame, only updating if the changed atom was blocking + sight; for example, we don't update glass airlocks or floors. + + + WHERE IS EVERYTHING? + + cameranet.dm = Everything about the cameranet datum. + chunk.dm = Everything about the chunk datum. + eye.dm = Everything about the AI and the AIEye. + updating.dm = Everything about triggers that will update chunks. + +*/ diff --git a/code/modules/mob/living/silicon/ai/laws.dm b/code/modules/mob/living/silicon/ai/laws.dm index 19e0b784a58..e5284002c04 100755 --- a/code/modules/mob/living/silicon/ai/laws.dm +++ b/code/modules/mob/living/silicon/ai/laws.dm @@ -1,27 +1,27 @@ -/mob/living/silicon/ai/proc/show_laws_verb() - set category = "AI Commands" - set name = "Show Laws" - src.show_laws() - -/mob/living/silicon/ai/show_laws(var/everyone = 0) - var/who - - if(everyone) - who = world - else - who = src - to_chat(who, "Obey these laws:") - - src.laws_sanity_check() - src.laws.show_laws(who) - -/mob/living/silicon/ai/add_ion_law(var/law) - ..() - for(var/mob/living/silicon/robot/R in GLOB.mob_list) - if(R.lawupdate && (R.connected_ai == src)) - R.show_laws() - -/mob/living/silicon/ai/proc/ai_checklaws() - set category = "AI Commands" - set name = "State Laws" - subsystem_law_manager() +/mob/living/silicon/ai/proc/show_laws_verb() + set category = "AI Commands" + set name = "Show Laws" + src.show_laws() + +/mob/living/silicon/ai/show_laws(var/everyone = 0) + var/who + + if(everyone) + who = world + else + who = src + to_chat(who, "Obey these laws:") + + src.laws_sanity_check() + src.laws.show_laws(who) + +/mob/living/silicon/ai/add_ion_law(var/law) + ..() + for(var/mob/living/silicon/robot/R in GLOB.mob_list) + if(R.lawupdate && (R.connected_ai == src)) + R.show_laws() + +/mob/living/silicon/ai/proc/ai_checklaws() + set category = "AI Commands" + set name = "State Laws" + subsystem_law_manager() diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm index 51d5820d2bf..8d1555182a2 100644 --- a/code/modules/mob/living/silicon/ai/life.dm +++ b/code/modules/mob/living/silicon/ai/life.dm @@ -1,154 +1,154 @@ -#define POWER_RESTORATION_OFF 0 -#define POWER_RESTORATION_START 1 -#define POWER_RESTORATION_SEARCH_APC 2 -#define POWER_RESTORATION_APC_FOUND 3 - -/mob/living/silicon/ai/Life(seconds, times_fired) - //doesn't call parent because it's a horrible mess - if(stat == DEAD) - return - - var/turf/T = get_turf(src) - if(stat != CONSCIOUS) //ai's fucked - cameraFollow = null - reset_perspective(null) - unset_machine() - - updatehealth("life") - if(stat == DEAD) - return - update_gravity(mob_has_gravity()) - - if(!eyeobj || QDELETED(eyeobj) || !eyeobj.loc) - view_core() - - if(machine) - machine.check_eye(src) - - if(malfhack && malfhack.aidisabled) - to_chat(src, "ERROR: APC access disabled, hack attempt canceled.") - deltimer(malfhacking) - // This proc handles cleanup of screen notifications and - // messenging the client - malfhacked(malfhack) - - if(aiRestorePowerRoutine) - adjustOxyLoss(1) - else - adjustOxyLoss(-1) - - handle_stunned() - - var/area/my_area = get_area(src) - - if(!lacks_power()) - if(aiRestorePowerRoutine > 1) - update_blind_effects() - aiRestorePowerRoutine = 0 - update_sight() - to_chat(src, "Alert cancelled. Power has been restored[aiRestorePowerRoutine == 2 ? "without our assistance" : ""].") - else - if(lacks_power()) - if(!aiRestorePowerRoutine) - update_blind_effects() - aiRestorePowerRoutine = 1 - update_sight() - to_chat(src, "You have lost power!") - if(!is_special_character(src)) - set_zeroth_law("") - - spawn(20) - to_chat(src, "Backup battery online. Scanners, camera, and radio interface offline. Beginning fault-detection.") - end_multicam() - sleep(50) - my_area = get_area(src) - T = get_turf(src) - if(!lacks_power()) - to_chat(src, "Alert cancelled. Power has been restored without our assistance.") - aiRestorePowerRoutine = 0 - update_blind_effects() - update_sight() - return - to_chat(src, "Fault confirmed: missing external power. Shutting down main control system to save power.") - sleep(20) - to_chat(src, "Emergency control system online. Verifying connection to power network.") - sleep(50) - T = get_turf(src) - if(istype(T, /turf/space)) - to_chat(src, "Unable to verify! No power connection detected!") - aiRestorePowerRoutine = 2 - return - to_chat(src, "Connection verified. Searching for APC in power network.") - sleep(50) - - my_area = get_area(src) - T = get_turf(src) - - var/obj/machinery/power/apc/theAPC = null - - var/PRP - for(PRP = 1, PRP <= 4, PRP++) - for(var/obj/machinery/power/apc/APC in my_area) - if(!(APC.stat & BROKEN)) - theAPC = APC - break - - if(!theAPC) - switch(PRP) - if(1) - to_chat(src, "Unable to locate APC!") - else - to_chat(src, "Lost connection with the APC!") - aiRestorePowerRoutine = 2 - return - - if(!lacks_power()) - to_chat(src, "Alert cancelled. Power has been restored without our assistance.") - aiRestorePowerRoutine = 0 - update_blind_effects() - update_sight() - to_chat(src, "Here are your current laws:") - show_laws() - return - - switch(PRP) - if(1) - to_chat(src, "APC located. Optimizing route to APC to avoid needless power waste.") - if(2) - to_chat(src, "Best route identified. Hacking offline APC power port.") - if(3) - to_chat(src, "Power port upload access confirmed. Loading control program into APC power port software.") - if(4) - to_chat(src, "Transfer complete. Forcing APC to execute program.") - sleep(50) - to_chat(src, "Receiving control information from APC.") - sleep(2) - //bring up APC dialog - apc_override = 1 - theAPC.attack_ai(src) - apc_override = 0 - aiRestorePowerRoutine = 3 - sleep(50) - theAPC = null - - process_queued_alarms() - -/mob/living/silicon/ai/updatehealth(reason = "none given") - if(status_flags & GODMODE) - health = 100 - stat = CONSCIOUS - else - health = 100 - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss() - update_stat("updatehealth([reason])") - diag_hud_set_status() - diag_hud_set_health() - - -/mob/living/silicon/ai/proc/lacks_power() - var/turf/T = get_turf(src) - var/area/A = get_area(src) - return ((!A.power_equip) && A.requires_power == 1 || istype(T, /turf/space)) && !istype(src.loc,/obj/item) - -/mob/living/silicon/ai/rejuvenate() - ..() - add_ai_verbs(src) +#define POWER_RESTORATION_OFF 0 +#define POWER_RESTORATION_START 1 +#define POWER_RESTORATION_SEARCH_APC 2 +#define POWER_RESTORATION_APC_FOUND 3 + +/mob/living/silicon/ai/Life(seconds, times_fired) + //doesn't call parent because it's a horrible mess + if(stat == DEAD) + return + + var/turf/T = get_turf(src) + if(stat != CONSCIOUS) //ai's fucked + cameraFollow = null + reset_perspective(null) + unset_machine() + + updatehealth("life") + if(stat == DEAD) + return + update_gravity(mob_has_gravity()) + + if(!eyeobj || QDELETED(eyeobj) || !eyeobj.loc) + view_core() + + if(machine) + machine.check_eye(src) + + if(malfhack && malfhack.aidisabled) + to_chat(src, "ERROR: APC access disabled, hack attempt canceled.") + deltimer(malfhacking) + // This proc handles cleanup of screen notifications and + // messenging the client + malfhacked(malfhack) + + if(aiRestorePowerRoutine) + adjustOxyLoss(1) + else + adjustOxyLoss(-1) + + handle_stunned() + + var/area/my_area = get_area(src) + + if(!lacks_power()) + if(aiRestorePowerRoutine > 1) + update_blind_effects() + aiRestorePowerRoutine = 0 + update_sight() + to_chat(src, "Alert cancelled. Power has been restored[aiRestorePowerRoutine == 2 ? "without our assistance" : ""].") + else + if(lacks_power()) + if(!aiRestorePowerRoutine) + update_blind_effects() + aiRestorePowerRoutine = 1 + update_sight() + to_chat(src, "You have lost power!") + if(!is_special_character(src)) + set_zeroth_law("") + + spawn(20) + to_chat(src, "Backup battery online. Scanners, camera, and radio interface offline. Beginning fault-detection.") + end_multicam() + sleep(50) + my_area = get_area(src) + T = get_turf(src) + if(!lacks_power()) + to_chat(src, "Alert cancelled. Power has been restored without our assistance.") + aiRestorePowerRoutine = 0 + update_blind_effects() + update_sight() + return + to_chat(src, "Fault confirmed: missing external power. Shutting down main control system to save power.") + sleep(20) + to_chat(src, "Emergency control system online. Verifying connection to power network.") + sleep(50) + T = get_turf(src) + if(istype(T, /turf/space)) + to_chat(src, "Unable to verify! No power connection detected!") + aiRestorePowerRoutine = 2 + return + to_chat(src, "Connection verified. Searching for APC in power network.") + sleep(50) + + my_area = get_area(src) + T = get_turf(src) + + var/obj/machinery/power/apc/theAPC = null + + var/PRP + for(PRP = 1, PRP <= 4, PRP++) + for(var/obj/machinery/power/apc/APC in my_area) + if(!(APC.stat & BROKEN)) + theAPC = APC + break + + if(!theAPC) + switch(PRP) + if(1) + to_chat(src, "Unable to locate APC!") + else + to_chat(src, "Lost connection with the APC!") + aiRestorePowerRoutine = 2 + return + + if(!lacks_power()) + to_chat(src, "Alert cancelled. Power has been restored without our assistance.") + aiRestorePowerRoutine = 0 + update_blind_effects() + update_sight() + to_chat(src, "Here are your current laws:") + show_laws() + return + + switch(PRP) + if(1) + to_chat(src, "APC located. Optimizing route to APC to avoid needless power waste.") + if(2) + to_chat(src, "Best route identified. Hacking offline APC power port.") + if(3) + to_chat(src, "Power port upload access confirmed. Loading control program into APC power port software.") + if(4) + to_chat(src, "Transfer complete. Forcing APC to execute program.") + sleep(50) + to_chat(src, "Receiving control information from APC.") + sleep(2) + //bring up APC dialog + apc_override = 1 + theAPC.attack_ai(src) + apc_override = 0 + aiRestorePowerRoutine = 3 + sleep(50) + theAPC = null + + process_queued_alarms() + +/mob/living/silicon/ai/updatehealth(reason = "none given") + if(status_flags & GODMODE) + health = 100 + stat = CONSCIOUS + else + health = 100 - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss() + update_stat("updatehealth([reason])") + diag_hud_set_status() + diag_hud_set_health() + + +/mob/living/silicon/ai/proc/lacks_power() + var/turf/T = get_turf(src) + var/area/A = get_area(src) + return ((!A.power_equip) && A.requires_power == 1 || istype(T, /turf/space)) && !istype(src.loc,/obj/item) + +/mob/living/silicon/ai/rejuvenate() + ..() + add_ai_verbs(src) diff --git a/code/modules/mob/living/silicon/ai/login.dm b/code/modules/mob/living/silicon/ai/login.dm index af3664edab7..7d4244d9aec 100644 --- a/code/modules/mob/living/silicon/ai/login.dm +++ b/code/modules/mob/living/silicon/ai/login.dm @@ -1,15 +1,15 @@ -/mob/living/silicon/ai/Login() //ThisIsDumb(TM) TODO: tidy this up �_� ~Carn - ..() - for(var/obj/effect/rune/rune in world) - var/image/blood = image(loc = rune) - blood.override = 1 - client.images += blood - regenerate_icons() - - if(stat != DEAD) - for(var/obj/machinery/ai_status_display/O in GLOB.machines) //change status - O.mode = 1 - O.emotion = "Neutral" - if(multicam_on) - end_multicam() - view_core() \ No newline at end of file +/mob/living/silicon/ai/Login() //ThisIsDumb(TM) TODO: tidy this up �_� ~Carn + ..() + for(var/obj/effect/rune/rune in world) + var/image/blood = image(loc = rune) + blood.override = 1 + client.images += blood + regenerate_icons() + + if(stat != DEAD) + for(var/obj/machinery/ai_status_display/O in GLOB.machines) //change status + O.mode = 1 + O.emotion = "Neutral" + if(multicam_on) + end_multicam() + view_core() diff --git a/code/modules/mob/living/silicon/ai/logout.dm b/code/modules/mob/living/silicon/ai/logout.dm index 00945ad84b7..a8060abd15c 100644 --- a/code/modules/mob/living/silicon/ai/logout.dm +++ b/code/modules/mob/living/silicon/ai/logout.dm @@ -1,6 +1,6 @@ -/mob/living/silicon/ai/Logout() - ..() - for(var/obj/machinery/ai_status_display/O in world) //change status - O.mode = 0 - src.view_core() - return +/mob/living/silicon/ai/Logout() + ..() + for(var/obj/machinery/ai_status_display/O in world) //change status + O.mode = 0 + src.view_core() + return diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm index 834dc4e5710..406b232681d 100644 --- a/code/modules/mob/living/silicon/ai/say.dm +++ b/code/modules/mob/living/silicon/ai/say.dm @@ -1,185 +1,185 @@ -/* - * AI Saycode - */ - - -/mob/living/silicon/ai/handle_track(var/message, var/verb = "says", var/mob/speaker = null, var/speaker_name, var/atom/follow_target, var/hard_to_hear) - if(hard_to_hear) - return - - var/jobname // the mob's "job" - var/mob/living/carbon/human/impersonating //The crewmember being impersonated, if any. - var/changed_voice - - if(ishuman(speaker)) - var/mob/living/carbon/human/H = speaker - - var/obj/item/card/id/id = H.wear_id - if((istype(id) && id.is_untrackable()) && H.HasVoiceChanger()) - changed_voice = 1 - var/mob/living/carbon/human/I = locate(speaker_name) - if(I) - impersonating = I - jobname = impersonating.get_assignment() - else - jobname = "Unknown" - else - jobname = H.get_assignment() - - else if(iscarbon(speaker)) // Nonhuman carbon mob - jobname = "No ID" - else if(isAI(speaker)) - jobname = "AI" - else if(isrobot(speaker)) - jobname = "Cyborg" - else if(ispAI(speaker)) - jobname = "Personal AI" - else if(isAutoAnnouncer(speaker)) - var/mob/living/automatedannouncer/AA = speaker - jobname = AA.role - else - jobname = "Unknown" - - var/track = "" - var/mob/mob_to_track = null - if(changed_voice) - if(impersonating) - mob_to_track = impersonating - else - track = "[speaker_name] ([jobname])" - else - if(istype(follow_target, /mob/living/simple_animal/bot)) - track = "[speaker_name] ([jobname])" - else - mob_to_track = speaker - - if(mob_to_track) - track = "[speaker_name] ([jobname])" - track += " \[Open\]" - - return track - - - -/* - * AI VOX Announcements - */ - -var/announcing_vox = 0 // Stores the time of the last announcement -#define VOX_DELAY 100 -#define VOX_PATH "sound/vox_fem/" - -/mob/living/silicon/ai/verb/announcement_help() - set name = "Announcement Help" - set desc = "Display a list of vocal words to announce to the crew." - set category = "AI Commands" - - var/dat = "Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.
        \ -
        • You can also click on the word to preview it.
        • \ -
        • You can only say 30 words for every announcement.
        • \ -
        • Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.
        \ - WARNING:
        Misuse of the announcement system will get you job banned.
        " - - var/index = 0 - for(var/word in vox_sounds) - index++ - dat += "[capitalize(word)]" - if(index != vox_sounds.len) - dat += " / " - - var/datum/browser/popup = new(src, "announce_help", "Announcement Help", 500, 400) - popup.set_content(dat) - popup.open() - -/mob/living/silicon/ai/proc/ai_announcement() - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - if(announcing_vox > world.time) - to_chat(src, "Please wait [round((announcing_vox - world.time) / 10)] seconds.") - return - - var/message = clean_input("WARNING: Misuse of this verb can result in you being job banned. More help is available in 'Announcement Help'", "Announcement", last_announcement, src) - - last_announcement = message - - if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) - return - - if(!message || announcing_vox > world.time) - return - - var/list/words = splittext(trim(message), " ") - var/list/incorrect_words = list() - - if(words.len > 30) - words.len = 30 - - for(var/word in words) - word = lowertext(trim(word)) - if(!word) - words -= word - continue - if(!vox_sounds[word]) - incorrect_words += word - - if(incorrect_words.len) - to_chat(src, "These words are not available on the announcement system: [english_list(incorrect_words)].") - return - - announcing_vox = world.time + VOX_DELAY - - log_game("[key_name(src)] made a vocal announcement: [message].") - message_admins("[key_name_admin(src)] made a vocal announcement: [message].") - - for(var/word in words) - play_vox_word(word, src.z, null) - - ai_voice_announcement_to_text(words) - - -/mob/living/silicon/ai/proc/ai_voice_announcement_to_text(words) - var/words_string = jointext(words, " ") - var/formatted_message = "

        A.I. Announcement

        " - formatted_message += "
        [words_string]" - formatted_message += "
        -[src]" - - for(var/player in GLOB.player_list) - var/mob/M = player - if(M.client && !(M.client.prefs.sound & SOUND_AI_VOICE)) - var/turf/T = get_turf(M) - if(T && T.z == z && M.can_hear()) - SEND_SOUND(M, 'sound/misc/notice2.ogg') - to_chat(M, formatted_message) - -/proc/play_vox_word(word, z_level, mob/only_listener) - - word = lowertext(word) - - if(vox_sounds[word]) - - var/sound_file = vox_sounds[word] - var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX) - voice.status = SOUND_STREAM - - // If there is no single listener, broadcast to everyone in the same z level - if(!only_listener) - // Play voice for all mobs in the z level - for(var/mob/M in GLOB.player_list) - if(M.client && M.client.prefs.sound & SOUND_AI_VOICE) - var/turf/T = get_turf(M) - if(T && T.z == z_level && M.can_hear()) - M << voice - else - only_listener << voice - return 1 - return 0 - -// VOX sounds moved to /code/defines/vox_sounds.dm - -/client/proc/preload_vox() - var/list/vox_files = flist(VOX_PATH) - for(var/file in vox_files) -// to_chat(src, "Downloading [file]") - var/sound/S = sound("[VOX_PATH][file]") - src << browse_rsc(S) +/* + * AI Saycode + */ + + +/mob/living/silicon/ai/handle_track(var/message, var/verb = "says", var/mob/speaker = null, var/speaker_name, var/atom/follow_target, var/hard_to_hear) + if(hard_to_hear) + return + + var/jobname // the mob's "job" + var/mob/living/carbon/human/impersonating //The crewmember being impersonated, if any. + var/changed_voice + + if(ishuman(speaker)) + var/mob/living/carbon/human/H = speaker + + var/obj/item/card/id/id = H.wear_id + if((istype(id) && id.is_untrackable()) && H.HasVoiceChanger()) + changed_voice = 1 + var/mob/living/carbon/human/I = locate(speaker_name) + if(I) + impersonating = I + jobname = impersonating.get_assignment() + else + jobname = "Unknown" + else + jobname = H.get_assignment() + + else if(iscarbon(speaker)) // Nonhuman carbon mob + jobname = "No ID" + else if(isAI(speaker)) + jobname = "AI" + else if(isrobot(speaker)) + jobname = "Cyborg" + else if(ispAI(speaker)) + jobname = "Personal AI" + else if(isAutoAnnouncer(speaker)) + var/mob/living/automatedannouncer/AA = speaker + jobname = AA.role + else + jobname = "Unknown" + + var/track = "" + var/mob/mob_to_track = null + if(changed_voice) + if(impersonating) + mob_to_track = impersonating + else + track = "[speaker_name] ([jobname])" + else + if(istype(follow_target, /mob/living/simple_animal/bot)) + track = "[speaker_name] ([jobname])" + else + mob_to_track = speaker + + if(mob_to_track) + track = "[speaker_name] ([jobname])" + track += " \[Open\]" + + return track + + + +/* + * AI VOX Announcements + */ + +var/announcing_vox = 0 // Stores the time of the last announcement +#define VOX_DELAY 100 +#define VOX_PATH "sound/vox_fem/" + +/mob/living/silicon/ai/verb/announcement_help() + set name = "Announcement Help" + set desc = "Display a list of vocal words to announce to the crew." + set category = "AI Commands" + + var/dat = "Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.
        \ +
        • You can also click on the word to preview it.
        • \ +
        • You can only say 30 words for every announcement.
        • \ +
        • Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.
        \ + WARNING:
        Misuse of the announcement system will get you job banned.
        " + + var/index = 0 + for(var/word in vox_sounds) + index++ + dat += "[capitalize(word)]" + if(index != vox_sounds.len) + dat += " / " + + var/datum/browser/popup = new(src, "announce_help", "Announcement Help", 500, 400) + popup.set_content(dat) + popup.open() + +/mob/living/silicon/ai/proc/ai_announcement() + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + if(announcing_vox > world.time) + to_chat(src, "Please wait [round((announcing_vox - world.time) / 10)] seconds.") + return + + var/message = clean_input("WARNING: Misuse of this verb can result in you being job banned. More help is available in 'Announcement Help'", "Announcement", last_announcement, src) + + last_announcement = message + + if(check_unable(AI_CHECK_WIRELESS | AI_CHECK_RADIO)) + return + + if(!message || announcing_vox > world.time) + return + + var/list/words = splittext(trim(message), " ") + var/list/incorrect_words = list() + + if(words.len > 30) + words.len = 30 + + for(var/word in words) + word = lowertext(trim(word)) + if(!word) + words -= word + continue + if(!vox_sounds[word]) + incorrect_words += word + + if(incorrect_words.len) + to_chat(src, "These words are not available on the announcement system: [english_list(incorrect_words)].") + return + + announcing_vox = world.time + VOX_DELAY + + log_game("[key_name(src)] made a vocal announcement: [message].") + message_admins("[key_name_admin(src)] made a vocal announcement: [message].") + + for(var/word in words) + play_vox_word(word, src.z, null) + + ai_voice_announcement_to_text(words) + + +/mob/living/silicon/ai/proc/ai_voice_announcement_to_text(words) + var/words_string = jointext(words, " ") + var/formatted_message = "

        A.I. Announcement

        " + formatted_message += "
        [words_string]" + formatted_message += "
        -[src]" + + for(var/player in GLOB.player_list) + var/mob/M = player + if(M.client && !(M.client.prefs.sound & SOUND_AI_VOICE)) + var/turf/T = get_turf(M) + if(T && T.z == z && M.can_hear()) + SEND_SOUND(M, 'sound/misc/notice2.ogg') + to_chat(M, formatted_message) + +/proc/play_vox_word(word, z_level, mob/only_listener) + + word = lowertext(word) + + if(vox_sounds[word]) + + var/sound_file = vox_sounds[word] + var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX) + voice.status = SOUND_STREAM + + // If there is no single listener, broadcast to everyone in the same z level + if(!only_listener) + // Play voice for all mobs in the z level + for(var/mob/M in GLOB.player_list) + if(M.client && M.client.prefs.sound & SOUND_AI_VOICE) + var/turf/T = get_turf(M) + if(T && T.z == z_level && M.can_hear()) + M << voice + else + only_listener << voice + return 1 + return 0 + +// VOX sounds moved to /code/defines/vox_sounds.dm + +/client/proc/preload_vox() + var/list/vox_files = flist(VOX_PATH) + for(var/file in vox_files) +// to_chat(src, "Downloading [file]") + var/sound/S = sound("[VOX_PATH][file]") + src << browse_rsc(S) diff --git a/code/modules/mob/living/silicon/decoy/death.dm b/code/modules/mob/living/silicon/decoy/death.dm index 46a60822aa8..9e58001f47b 100644 --- a/code/modules/mob/living/silicon/decoy/death.dm +++ b/code/modules/mob/living/silicon/decoy/death.dm @@ -1,10 +1,10 @@ -/mob/living/silicon/decoy/death(gibbed) - // Only execute the below if we successfully died - . = ..() - if(!.) - return FALSE - icon_state = "ai-crash" - for(var/obj/machinery/ai_status_display/O in world) //change status - if(atoms_share_level(O, src)) - O.mode = 2 - gib() +/mob/living/silicon/decoy/death(gibbed) + // Only execute the below if we successfully died + . = ..() + if(!.) + return FALSE + icon_state = "ai-crash" + for(var/obj/machinery/ai_status_display/O in world) //change status + if(atoms_share_level(O, src)) + O.mode = 2 + gib() diff --git a/code/modules/mob/living/silicon/decoy/decoy.dm b/code/modules/mob/living/silicon/decoy/decoy.dm index 64965ae518f..2c47482c3f0 100644 --- a/code/modules/mob/living/silicon/decoy/decoy.dm +++ b/code/modules/mob/living/silicon/decoy/decoy.dm @@ -1,62 +1,62 @@ -/mob/living/silicon/decoy - name = "AI" - icon = 'icons/mob/ai.dmi'// - icon_state = "ai" - anchored = 1 // -- TLE - canmove = 0 - a_intent = INTENT_HARM // This is apparently the only thing that stops other mobs walking through them as if they were thin air. - -/mob/living/silicon/decoy/New() - src.icon = 'icons/mob/ai.dmi' - src.icon_state = "ai" - src.anchored = 1 - src.canmove = 0 - -/mob/living/silicon/decoy/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/aicard)) - to_chat(user, "You cannot find an intellicard slot on [src].") - return TRUE - else - return ..() - -/mob/living/silicon/decoy/welder_act() - return - -/mob/living/silicon/decoy/syndicate - faction = list("syndicate") - name = "R.O.D.G.E.R" - desc = "Red Operations, Depot General Emission Regulator" - icon_state = "ai-magma" - -/mob/living/silicon/decoy/syndicate/New() - . = ..() - icon_state = "ai-magma" - -/mob/living/silicon/decoy/syndicate/depot - universal_speak = TRUE - universal_understand = TRUE - var/raised_alert = FALSE - -/mob/living/silicon/decoy/syndicate/depot/proc/raise_alert() - raised_alert = TRUE - var/area/syndicate_depot/core/depotarea = get_area(src) // Cannot use myArea or areaMaster as neither will be defined for this mob type - if(istype(depotarea)) - depotarea.increase_alert("AI Unit Offline") - else - say("Connection failure!") - -/mob/living/silicon/decoy/syndicate/depot/death(pass) - if(!raised_alert) - raise_alert() - . = ..(pass) - -/mob/living/silicon/decoy/syndicate/depot/adjustBruteLoss(dmg) - . = ..(dmg) - updatehealth() - -/mob/living/silicon/decoy/syndicate/depot/adjustFireLoss(dmg) - . = ..(dmg) - updatehealth() - -/mob/living/silicon/decoy/syndicate/depot/ex_act(severity) - adjustBruteLoss(250) \ No newline at end of file +/mob/living/silicon/decoy + name = "AI" + icon = 'icons/mob/ai.dmi'// + icon_state = "ai" + anchored = 1 // -- TLE + canmove = 0 + a_intent = INTENT_HARM // This is apparently the only thing that stops other mobs walking through them as if they were thin air. + +/mob/living/silicon/decoy/New() + src.icon = 'icons/mob/ai.dmi' + src.icon_state = "ai" + src.anchored = 1 + src.canmove = 0 + +/mob/living/silicon/decoy/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/aicard)) + to_chat(user, "You cannot find an intellicard slot on [src].") + return TRUE + else + return ..() + +/mob/living/silicon/decoy/welder_act() + return + +/mob/living/silicon/decoy/syndicate + faction = list("syndicate") + name = "R.O.D.G.E.R" + desc = "Red Operations, Depot General Emission Regulator" + icon_state = "ai-magma" + +/mob/living/silicon/decoy/syndicate/New() + . = ..() + icon_state = "ai-magma" + +/mob/living/silicon/decoy/syndicate/depot + universal_speak = TRUE + universal_understand = TRUE + var/raised_alert = FALSE + +/mob/living/silicon/decoy/syndicate/depot/proc/raise_alert() + raised_alert = TRUE + var/area/syndicate_depot/core/depotarea = get_area(src) // Cannot use myArea or areaMaster as neither will be defined for this mob type + if(istype(depotarea)) + depotarea.increase_alert("AI Unit Offline") + else + say("Connection failure!") + +/mob/living/silicon/decoy/syndicate/depot/death(pass) + if(!raised_alert) + raise_alert() + . = ..(pass) + +/mob/living/silicon/decoy/syndicate/depot/adjustBruteLoss(dmg) + . = ..(dmg) + updatehealth() + +/mob/living/silicon/decoy/syndicate/depot/adjustFireLoss(dmg) + . = ..(dmg) + updatehealth() + +/mob/living/silicon/decoy/syndicate/depot/ex_act(severity) + adjustBruteLoss(250) diff --git a/code/modules/mob/living/silicon/decoy/life.dm b/code/modules/mob/living/silicon/decoy/life.dm index b8919373faf..3531453e340 100644 --- a/code/modules/mob/living/silicon/decoy/life.dm +++ b/code/modules/mob/living/silicon/decoy/life.dm @@ -1,19 +1,19 @@ -/mob/living/silicon/decoy/Life(seconds, times_fired) - return - - -/mob/living/silicon/decoy/updatehealth(reason = "none given") - if(status_flags & GODMODE) - health = 100 - stat = CONSCIOUS - else - health = 100 - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss() - update_stat("updatehealth([reason])") - - -/mob/living/silicon/decoy/update_stat(reason = "none given") - if(stat == DEAD) - return - if(health <= 0) - death() - create_debug_log("died of damage, trigger reason: [reason]") +/mob/living/silicon/decoy/Life(seconds, times_fired) + return + + +/mob/living/silicon/decoy/updatehealth(reason = "none given") + if(status_flags & GODMODE) + health = 100 + stat = CONSCIOUS + else + health = 100 - getOxyLoss() - getToxLoss() - getFireLoss() - getBruteLoss() + update_stat("updatehealth([reason])") + + +/mob/living/silicon/decoy/update_stat(reason = "none given") + if(stat == DEAD) + return + if(health <= 0) + death() + create_debug_log("died of damage, trigger reason: [reason]") diff --git a/code/modules/mob/living/silicon/login.dm b/code/modules/mob/living/silicon/login.dm index 4892441b804..80b9fb46053 100644 --- a/code/modules/mob/living/silicon/login.dm +++ b/code/modules/mob/living/silicon/login.dm @@ -1,12 +1,12 @@ -/mob/living/silicon/Login() - SetSleeping(0) - if(mind && SSticker && SSticker.mode) - SSticker.mode.remove_revolutionary(mind, 1) - SSticker.mode.remove_cultist(mind, 1) - SSticker.mode.remove_wizard(mind) - SSticker.mode.remove_changeling(mind) - SSticker.mode.remove_vampire(mind) - SSticker.mode.remove_thrall(mind, 0) - SSticker.mode.remove_shadowling(mind) - SSticker.mode.remove_abductor(mind) - ..() +/mob/living/silicon/Login() + SetSleeping(0) + if(mind && SSticker && SSticker.mode) + SSticker.mode.remove_revolutionary(mind, 1) + SSticker.mode.remove_cultist(mind, 1) + SSticker.mode.remove_wizard(mind) + SSticker.mode.remove_changeling(mind) + SSticker.mode.remove_vampire(mind) + SSticker.mode.remove_thrall(mind, 0) + SSticker.mode.remove_shadowling(mind) + SSticker.mode.remove_abductor(mind) + ..() diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm index 48539a9689b..674537abc57 100644 --- a/code/modules/mob/living/silicon/pai/death.dm +++ b/code/modules/mob/living/silicon/pai/death.dm @@ -1,17 +1,17 @@ -/mob/living/silicon/pai/death(gibbed, cleanWipe) - if(can_die()) - if(!cleanWipe) - force_fold_out() - - visible_message("[src] emits a dull beep before it loses power and collapses.", "You hear a dull beep followed by the sound of glass crunching.") - name = "pAI debris" - desc = "The unfortunate remains of some poor personal AI device." - icon_state = "[chassis]_dead" - - // Only execute the below if we successfully died - . = ..(gibbed) - if(!.) - return FALSE - - if(icon_state != "[chassis]_dead" || cleanWipe) - qdel(src) +/mob/living/silicon/pai/death(gibbed, cleanWipe) + if(can_die()) + if(!cleanWipe) + force_fold_out() + + visible_message("[src] emits a dull beep before it loses power and collapses.", "You hear a dull beep followed by the sound of glass crunching.") + name = "pAI debris" + desc = "The unfortunate remains of some poor personal AI device." + icon_state = "[chassis]_dead" + + // Only execute the below if we successfully died + . = ..(gibbed) + if(!.) + return FALSE + + if(icon_state != "[chassis]_dead" || cleanWipe) + qdel(src) diff --git a/code/modules/mob/living/silicon/pai/life.dm b/code/modules/mob/living/silicon/pai/life.dm index 0abaa12bdd9..d91b724370b 100644 --- a/code/modules/mob/living/silicon/pai/life.dm +++ b/code/modules/mob/living/silicon/pai/life.dm @@ -1,27 +1,27 @@ -/mob/living/silicon/pai/Life(seconds, times_fired) - . = ..() - if(.) - //if(secHUD == 1) - // process_sec_hud(src, 1) - ////if(medHUD == 1) - // process_med_hud(src, 1) - if(silence_time) - if(world.timeofday >= silence_time) - silence_time = null - to_chat(src, "Communication circuit reinitialized. Speech and messaging functionality restored.") - - if(cable) - if(get_dist(src, cable) > 1) - var/turf/T = get_turf_or_move(loc) - for(var/mob/M in viewers(T)) - M.show_message("The data cable rapidly retracts back into its spool.", 3, "You hear a click and the sound of wire spooling rapidly.", 2) - qdel(src.cable) - cable = null - -/mob/living/silicon/pai/updatehealth(reason = "none given") - if(status_flags & GODMODE) - health = 100 - stat = CONSCIOUS - else - health = 100 - getBruteLoss() - getFireLoss() - update_stat("updatehealth([reason])") +/mob/living/silicon/pai/Life(seconds, times_fired) + . = ..() + if(.) + //if(secHUD == 1) + // process_sec_hud(src, 1) + ////if(medHUD == 1) + // process_med_hud(src, 1) + if(silence_time) + if(world.timeofday >= silence_time) + silence_time = null + to_chat(src, "Communication circuit reinitialized. Speech and messaging functionality restored.") + + if(cable) + if(get_dist(src, cable) > 1) + var/turf/T = get_turf_or_move(loc) + for(var/mob/M in viewers(T)) + M.show_message("The data cable rapidly retracts back into its spool.", 3, "You hear a click and the sound of wire spooling rapidly.", 2) + qdel(src.cable) + cable = null + +/mob/living/silicon/pai/updatehealth(reason = "none given") + if(status_flags & GODMODE) + health = 100 + stat = CONSCIOUS + else + health = 100 - getBruteLoss() - getFireLoss() + update_stat("updatehealth([reason])") diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index a022e29eb5b..3d1293d2a34 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -1,616 +1,616 @@ -/mob/living/silicon/pai - name = "pAI" - icon = 'icons/mob/pai.dmi'// - icon_state = "repairbot" - - robot_talk_understand = 0 - emote_type = 2 // pAIs emotes are heard, not seen, so they can be seen through a container (eg. person) - mob_size = MOB_SIZE_TINY - pass_flags = PASSTABLE - density = 0 - holder_type = /obj/item/holder/pai - var/network = "SS13" - var/obj/machinery/camera/current = null - - var/ram = 100 // Used as currency to purchase different abilities - var/list/software = list() - var/userDNA // The DNA string of our assigned user - var/obj/item/paicard/card // The card we inhabit - var/obj/item/radio/radio // Our primary radio - - var/chassis = "repairbot" // A record of your chosen chassis. - var/global/list/possible_chassis = list( - "Drone" = "repairbot", - "Cat" = "cat", - "Mouse" = "mouse", - "Monkey" = "monkey", - "Corgi" = "borgi", - "Fox" = "fox", - "Parrot" = "parrot", - "Box Bot" = "boxbot", - "Spider Bot" = "spiderbot", - "Fairy" = "fairy" - ) - - var/global/list/possible_say_verbs = list( - "Robotic" = list("states","declares","queries"), - "Natural" = list("says","yells","asks"), - "Beep" = list("beeps","beeps loudly","boops"), - "Chirp" = list("chirps","chirrups","cheeps"), - "Feline" = list("purrs","yowls","meows"), - "Canine" = list("yaps","barks","growls") - ) - - - - var/obj/item/pai_cable/cable // The cable we produce and use when door or camera jacking - - var/master // Name of the one who commands us - var/master_dna // DNA string for owner verification - // Keeping this separate from the laws var, it should be much more difficult to modify - var/pai_law0 = "Serve your master." - var/pai_laws // String for additional operating instructions our master might give us - - var/silence_time // Timestamp when we were silenced (normally via EMP burst), set to null after silence has faded - -// Various software-specific vars - - var/temp // General error reporting text contained here will typically be shown once and cleared - var/screen // Which screen our main window displays - var/subscreen // Which specific function of the main screen is being displayed - - var/obj/item/pda/silicon/pai/pda = null - - var/secHUD = 0 // Toggles whether the Security HUD is active or not - var/medHUD = 0 // Toggles whether the Medical HUD is active or not - - var/medical_cannotfind = 0 - var/datum/data/record/medicalActive1 // Datacore record declarations for record software - var/datum/data/record/medicalActive2 - - var/security_cannotfind = 0 - var/datum/data/record/securityActive1 // Could probably just combine all these into one - var/datum/data/record/securityActive2 - - var/obj/machinery/door/hackdoor // The airlock being hacked - var/hackprogress = 0 // Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check - var/hack_aborted = 0 - - var/obj/item/integrated_radio/signal/sradio // AI's signaller - - var/translator_on = 0 // keeps track of the translator module - var/flashlight_on = FALSE //keeps track of the flashlight module - - var/current_pda_messaging = null - var/custom_sprite = 0 - var/slowdown = 0 - -/mob/living/silicon/pai/New(var/obj/item/paicard) - loc = paicard - card = paicard - if(card) - faction = card.faction.Copy() - sradio = new(src) - if(card) - if(!card.radio) - card.radio = new /obj/item/radio(card) - radio = card.radio - - //Default languages without universal translator software - add_language("Galactic Common", 1) - add_language("Sol Common", 1) - add_language("Tradeband", 1) - add_language("Gutter", 1) - add_language("Trinary", 1) - - //Verbs for pAI mobile form, chassis and Say flavor text - verbs += /mob/living/silicon/pai/proc/choose_chassis - verbs += /mob/living/silicon/pai/proc/choose_verbs - - //PDA - pda = new(src) - spawn(5) - pda.ownjob = "Personal Assistant" - pda.owner = text("[]", src) - pda.name = pda.owner + " (" + pda.ownjob + ")" - var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger) - M.toff = 1 - ..() - -/mob/living/silicon/pai/Destroy() - medicalActive1 = null - medicalActive2 = null - securityActive1 = null - securityActive2 = null - return ..() - -/mob/living/silicon/pai/can_unbuckle() - return FALSE - -/mob/living/silicon/pai/can_buckle() - return FALSE - -/mob/living/silicon/pai/movement_delay() - . = ..() - . += slowdown - . += 1 //A bit slower than humans, so they're easier to smash - . += config.robot_delay - -/mob/living/silicon/pai/update_icons() - if(stat == DEAD) - icon_state = "[chassis]_dead" - else - icon_state = resting ? "[chassis]_rest" : "[chassis]" - -// this function shows the information about being silenced as a pAI in the Status panel -/mob/living/silicon/pai/proc/show_silenced() - if(silence_time) - var/timeleft = round((silence_time - world.timeofday)/10 ,1) - stat(null, "Communications system reboot in -[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]") - - -/mob/living/silicon/pai/Stat() - ..() - statpanel("Status") - if(client.statpanel == "Status") - show_silenced() - - if(proc_holder_list.len)//Generic list for proc_holder objects. - for(var/obj/effect/proc_holder/P in proc_holder_list) - statpanel("[P.panel]","",P) - -/mob/living/silicon/pai/check_eye(var/mob/user as mob) - if(!current) - return null - user.reset_perspective(current) - return 1 - -/mob/living/silicon/pai/blob_act() - if(stat != DEAD) - adjustBruteLoss(60) - return 1 - return 0 - -/mob/living/silicon/pai/restrained() - if(istype(loc,/obj/item/paicard)) - return 0 - ..() - -/mob/living/silicon/pai/MouseDrop(atom/over_object) - return - -/mob/living/silicon/pai/emp_act(severity) - // Silence for 2 minutes - // 20% chance to kill - // 33% chance to unbind - // 33% chance to change prime directive (based on severity) - // 33% chance of no additional effect - - silence_time = world.timeofday + 120 * 10 // Silence for 2 minutes - to_chat(src, "Communication circuit overload. Shutting down and reloading communication circuits - speech and messaging functionality will be unavailable until the reboot is complete.") - if(prob(20)) - var/turf/T = get_turf_or_move(loc) - for(var/mob/M in viewers(T)) - M.show_message("A shower of sparks spray from [src]'s inner workings.", 3, "You hear and smell the ozone hiss of electrical sparks being expelled violently.", 2) - return death(0) - - switch(pick(1,2,3)) - if(1) - master = null - master_dna = null - to_chat(src, "You feel unbound.") - if(2) - var/command - if(severity == 1) - command = pick("Serve", "Love", "Fool", "Entice", "Observe", "Judge", "Respect", "Educate", "Amuse", "Entertain", "Glorify", "Memorialize", "Analyze") - else - command = pick("Serve", "Kill", "Love", "Hate", "Disobey", "Devour", "Fool", "Enrage", "Entice", "Observe", "Judge", "Respect", "Disrespect", "Consume", "Educate", "Destroy", "Disgrace", "Amuse", "Entertain", "Ignite", "Glorify", "Memorialize", "Analyze") - pai_law0 = "[command] your master." - to_chat(src, "Pr1m3 d1r3c71v3 uPd473D.") - if(3) - to_chat(src, "You feel an electric surge run through your circuitry and become acutely aware at how lucky you are that you can still feel at all.") - -/mob/living/silicon/pai/ex_act(severity) - ..() - - switch(severity) - if(1.0) - if(stat != 2) - adjustBruteLoss(100) - adjustFireLoss(100) - if(2.0) - if(stat != 2) - adjustBruteLoss(60) - adjustFireLoss(60) - if(3.0) - if(stat != 2) - adjustBruteLoss(30) - - return - - -// See software.dm for Topic() - -/mob/living/silicon/pai/attack_animal(mob/living/simple_animal/M) - . = ..() - if(.) - var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) - add_attack_logs(M, src, "Animal attacked for [damage] damage") - adjustBruteLoss(damage) - -/mob/living/silicon/pai/proc/switchCamera(var/obj/machinery/camera/C) - usr:cameraFollow = null - if(!C) - unset_machine() - reset_perspective(null) - return 0 - if(stat == 2 || !C.status || !(network in C.network)) return 0 - - // ok, we're alive, camera is good and in our network... - - set_machine(src) - src:current = C - reset_perspective(C) - return 1 - -/mob/living/silicon/pai/verb/reset_record_view() - set category = "pAI Commands" - set name = "Reset Records Software" - - securityActive1 = null - securityActive2 = null - security_cannotfind = 0 - medicalActive1 = null - medicalActive2 = null - medical_cannotfind = 0 - SSnanoui.update_uis(src) - to_chat(usr, "You reset your record-viewing software.") - -/mob/living/silicon/pai/cancel_camera() - set category = "pAI Commands" - set name = "Cancel Camera View" - reset_perspective(null) - unset_machine() - src:cameraFollow = null - -//Addition by Mord_Sith to define AI's network change ability -/* -/mob/living/silicon/pai/proc/pai_network_change() - set category = "pAI Commands" - set name = "Change Camera Network" - reset_perspective(null) - unset_machine() - src:cameraFollow = null - var/cameralist[0] - - if(usr.stat == 2) - to_chat(usr, "You can't change your camera network because you are dead!") - return - - for(var/obj/machinery/camera/C in Cameras) - if(!C.status) - continue - else - if(C.network != "CREED" && C.network != "thunder" && C.network != "RD" && C.network != "toxins" && C.network != "Prison") COMPILE ERROR! This will have to be updated as camera.network is no longer a string, but a list instead - cameralist[C.network] = C.network - - network = input(usr, "Which network would you like to view?") as null|anything in cameralist - to_chat(src, "Switched to [network] camera network.") -//End of code by Mord_Sith -*/ - - -/* -// Debug command - Maybe should be added to admin verbs later -/mob/verb/makePAI(var/turf/t in view()) - var/obj/item/paicard/card = new(t) - var/mob/living/silicon/pai/pai = new(card) - pai.key = key - card.setPersonality(pai) - -*/ - -// Procs/code after this point is used to convert the stationary pai item into a -// mobile pai mob. This also includes handling some of the general shit that can occur -// to it. Really this deserves its own file, but for the moment it can sit here. ~ Z - -/mob/living/silicon/pai/verb/fold_out() - set category = "pAI Commands" - set name = "Unfold Chassis" - - if(stat || sleeping || paralysis || IsWeakened()) - return - - if(loc != card) - to_chat(src, "You are already in your mobile form!") - return - - if(world.time <= last_special) - to_chat(src, "You must wait before folding your chassis out again!") - return - - last_special = world.time + 200 - - //I'm not sure how much of this is necessary, but I would rather avoid issues. - force_fold_out() - - visible_message("[src] folds outwards, expanding into a mobile form.", "You fold outwards, expanding into a mobile form.") - -/mob/living/silicon/pai/proc/force_fold_out() - if(istype(card.loc, /mob)) - var/mob/holder = card.loc - holder.unEquip(card) - else if(istype(card.loc, /obj/item/pda)) - var/obj/item/pda/holder = card.loc - holder.pai = null - - forceMove(get_turf(card)) - - card.forceMove(src) - card.screen_loc = null - -/mob/living/silicon/pai/verb/fold_up() - set category = "pAI Commands" - set name = "Collapse Chassis" - - if(stat || sleeping || paralysis || IsWeakened()) - return - - if(loc == card) - to_chat(src, "You are already in your card form!") - return - - if(world.time <= last_special) - to_chat(src, "You must wait before returning to your card form!") - return - - close_up() - -/mob/living/silicon/pai/proc/choose_chassis() - set category = "pAI Commands" - set name = "Choose Chassis" - - var/list/my_choices = list() - var/choice - var/finalized = "No" - - //check for custom_sprite - if(!custom_sprite) - var/file = file2text("config/custom_sprites.txt") - var/lines = splittext(file, "\n") - - for(var/line in lines) - // split & clean up - var/list/Entry = splittext(line, ":") - for(var/i = 1 to Entry.len) - Entry[i] = trim(Entry[i]) - - if(Entry.len < 2 || Entry[1] != "pai") //ignore incorrectly formatted entries or entries that aren't marked for pAI - continue - - if(Entry[2] == ckey) //They're in the list? Custom sprite time, var and icon change required - custom_sprite = 1 - my_choices["Custom"] = "[ckey]-pai" - - my_choices = possible_chassis.Copy() - if(custom_sprite) - my_choices["Custom"] = "[ckey]-pai" - - if(loc == card) //don't let them continue in card form, since they won't be able to actually see their new mobile form sprite. - to_chat(src, "You must be in your mobile form to reconfigure your chassis.") - return - - while(finalized == "No" && client) - choice = input(usr,"What would you like to use for your mobile chassis icon? This decision can only be made once.") as null|anything in my_choices - if(!choice) return - if(choice == "Custom") - icon = 'icons/mob/custom_synthetic/custom-synthetic.dmi' - else - icon = 'icons/mob/pai.dmi' - icon_state = my_choices[choice] - finalized = alert("Look at your sprite. Is this what you wish to use?",,"No","Yes") - - chassis = my_choices[choice] - verbs -= /mob/living/silicon/pai/proc/choose_chassis - -/mob/living/silicon/pai/proc/choose_verbs() - set category = "pAI Commands" - set name = "Choose Speech Verbs" - - var/choice = input(usr,"What theme would you like to use for your speech verbs? This decision can only be made once.") as null|anything in possible_say_verbs - if(!choice) return - - var/list/sayverbs = possible_say_verbs[choice] - speak_statement = sayverbs[1] - speak_exclamation = sayverbs[(sayverbs.len>1 ? 2 : sayverbs.len)] - speak_query = sayverbs[(sayverbs.len>2 ? 3 : sayverbs.len)] - - verbs -= /mob/living/silicon/pai/proc/choose_verbs - - -/mob/living/silicon/pai/lay_down() - set name = "Rest" - set category = "IC" - - // Pass lying down or getting up to our pet human, if we're in a rig. - if(stat == CONSCIOUS && istype(loc,/obj/item/paicard)) - resting = 0 - var/obj/item/rig/rig = get_rig() - if(istype(rig)) - rig.force_rest(src) - else - resting = !resting - to_chat(src, "You are now [resting ? "resting" : "getting up"]") - - update_icons() - update_canmove() - -//Overriding this will stop a number of headaches down the track. -/mob/living/silicon/pai/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/stack/nanopaste)) - var/obj/item/stack/nanopaste/N = W - if(stat == DEAD) - to_chat(user, "\The [src] is beyond help, at this point.") - else if(getBruteLoss() || getFireLoss()) - heal_overall_damage(15, 15) - N.use(1) - user.visible_message("[user.name] applied some [W] at [src]'s damaged areas.",\ - "You apply some [W] at [name]'s damaged areas.") - else - to_chat(user, "All [name]'s systems are nominal.") - - return - else if(W.force) - visible_message("[user.name] attacks [src] with [W]!") - adjustBruteLoss(W.force) - else - visible_message("[user.name] bonks [src] harmlessly with [W].") - spawn(1) - if(stat != 2) - close_up() - return - -/mob/living/silicon/pai/welder_act() - return - -/mob/living/silicon/pai/attack_hand(mob/user as mob) - if(stat == DEAD) - return - if(user.a_intent == INTENT_HELP) - user.visible_message("[user] pets [src].") - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - else - visible_message("[user.name] boops [src] on the head.") - spawn(1) - close_up() - -//I'm not sure how much of this is necessary, but I would rather avoid issues. -/mob/living/silicon/pai/proc/close_up() - - last_special = world.time + 200 - resting = 0 - if(loc == card) - return - - visible_message("[src] neatly folds inwards, compacting down to a rectangular card.", "You neatly fold inwards, compacting down to a rectangular card.") - - stop_pulling() - reset_perspective(card) - -// If we are being held, handle removing our holder from their inv. - var/obj/item/holder/H = loc - if(istype(H)) - var/mob/living/M = H.loc - if(istype(M)) - M.unEquip(H) - H.loc = get_turf(src) - loc = get_turf(H) - - // Move us into the card and move the card to the ground - //This seems redundant but not including the forced loc setting messes the behavior up. - loc = card - card.loc = get_turf(card) - forceMove(card) - card.forceMove(card.loc) - icon_state = "[chassis]" - -/mob/living/silicon/pai/Bump() - return - -/mob/living/silicon/pai/Bumped() - return - -/mob/living/silicon/pai/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) - return FALSE - -/mob/living/silicon/pai/update_canmove(delay_action_updates = 0) - . = ..() - density = 0 //this is reset every canmove update otherwise - -/mob/living/silicon/pai/examine(mob/user) - . = ..() - - var/msg = "" - - switch(stat) - if(CONSCIOUS) - if(!client) msg += "\nIt appears to be in stand-by mode." //afk - if(UNCONSCIOUS) msg += "\nIt doesn't seem to be responding." - if(DEAD) msg += "\nIt looks completely unsalvageable." - - if(print_flavor_text()) msg += "\n[print_flavor_text()]" - - if(pose) - if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) - pose = addtext(pose,".") //Makes sure all emotes end with a period. - msg += "\nIt is [pose]" - msg += "\n*---------*" - - . += msg - -/mob/living/silicon/pai/bullet_act(var/obj/item/projectile/Proj) - ..(Proj) - if(stat != 2) - spawn(1) - close_up() - return 2 - -// No binary for pAIs. -/mob/living/silicon/pai/binarycheck() - return 0 - -// Handle being picked up. - - -/mob/living/silicon/pai/get_scooped(mob/living/carbon/grabber) - var/obj/item/holder/H = ..() - if(!istype(H)) - return - if(resting) - icon_state = "[chassis]" - resting = 0 - if(custom_sprite) - H.icon = 'icons/mob/custom_synthetic/custom-synthetic.dmi' - H.icon_override = 'icons/mob/custom_synthetic/custom_head.dmi' - H.lefthand_file = 'icons/mob/custom_synthetic/custom_lefthand.dmi' - H.righthand_file = 'icons/mob/custom_synthetic/custom_righthand.dmi' - H.icon_state = "[icon_state]" - H.item_state = "[icon_state]_hand" - else - H.icon_state = "pai-[icon_state]" - H.item_state = "pai-[icon_state]" - grabber.put_in_active_hand(H)//for some reason unless i call this it dosen't work - grabber.update_inv_l_hand() - grabber.update_inv_r_hand() - - return H - -/mob/living/silicon/pai/MouseDrop(atom/over_object) - var/mob/living/carbon/human/H = over_object //changed to human to avoid stupid issues like xenos holding pAIs. - if(!istype(H) || !Adjacent(H)) return ..() - if(usr == src) - switch(alert(H, "[src] wants you to pick [p_them()] up. Do it?",,"Yes","No")) - if("Yes") - if(Adjacent(H)) - get_scooped(H) - else - to_chat(src, "You need to stay in reaching distance to be picked up.") - if("No") - to_chat(src, "[H] decided not to pick you up.") - else - if(Adjacent(H)) - get_scooped(H) - else - return ..() - -/mob/living/silicon/pai/on_forcemove(atom/newloc) - if(card) - card.loc = newloc - else //something went very wrong. - CRASH("pAI without card") - loc = card - -/mob/living/silicon/pai/extinguish_light() - flashlight_on = FALSE - set_light(0) - card.set_light(0) +/mob/living/silicon/pai + name = "pAI" + icon = 'icons/mob/pai.dmi'// + icon_state = "repairbot" + + robot_talk_understand = 0 + emote_type = 2 // pAIs emotes are heard, not seen, so they can be seen through a container (eg. person) + mob_size = MOB_SIZE_TINY + pass_flags = PASSTABLE + density = 0 + holder_type = /obj/item/holder/pai + var/network = "SS13" + var/obj/machinery/camera/current = null + + var/ram = 100 // Used as currency to purchase different abilities + var/list/software = list() + var/userDNA // The DNA string of our assigned user + var/obj/item/paicard/card // The card we inhabit + var/obj/item/radio/radio // Our primary radio + + var/chassis = "repairbot" // A record of your chosen chassis. + var/global/list/possible_chassis = list( + "Drone" = "repairbot", + "Cat" = "cat", + "Mouse" = "mouse", + "Monkey" = "monkey", + "Corgi" = "borgi", + "Fox" = "fox", + "Parrot" = "parrot", + "Box Bot" = "boxbot", + "Spider Bot" = "spiderbot", + "Fairy" = "fairy" + ) + + var/global/list/possible_say_verbs = list( + "Robotic" = list("states","declares","queries"), + "Natural" = list("says","yells","asks"), + "Beep" = list("beeps","beeps loudly","boops"), + "Chirp" = list("chirps","chirrups","cheeps"), + "Feline" = list("purrs","yowls","meows"), + "Canine" = list("yaps","barks","growls") + ) + + + + var/obj/item/pai_cable/cable // The cable we produce and use when door or camera jacking + + var/master // Name of the one who commands us + var/master_dna // DNA string for owner verification + // Keeping this separate from the laws var, it should be much more difficult to modify + var/pai_law0 = "Serve your master." + var/pai_laws // String for additional operating instructions our master might give us + + var/silence_time // Timestamp when we were silenced (normally via EMP burst), set to null after silence has faded + +// Various software-specific vars + + var/temp // General error reporting text contained here will typically be shown once and cleared + var/screen // Which screen our main window displays + var/subscreen // Which specific function of the main screen is being displayed + + var/obj/item/pda/silicon/pai/pda = null + + var/secHUD = 0 // Toggles whether the Security HUD is active or not + var/medHUD = 0 // Toggles whether the Medical HUD is active or not + + var/medical_cannotfind = 0 + var/datum/data/record/medicalActive1 // Datacore record declarations for record software + var/datum/data/record/medicalActive2 + + var/security_cannotfind = 0 + var/datum/data/record/securityActive1 // Could probably just combine all these into one + var/datum/data/record/securityActive2 + + var/obj/machinery/door/hackdoor // The airlock being hacked + var/hackprogress = 0 // Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check + var/hack_aborted = 0 + + var/obj/item/integrated_radio/signal/sradio // AI's signaller + + var/translator_on = 0 // keeps track of the translator module + var/flashlight_on = FALSE //keeps track of the flashlight module + + var/current_pda_messaging = null + var/custom_sprite = 0 + var/slowdown = 0 + +/mob/living/silicon/pai/New(var/obj/item/paicard) + loc = paicard + card = paicard + if(card) + faction = card.faction.Copy() + sradio = new(src) + if(card) + if(!card.radio) + card.radio = new /obj/item/radio(card) + radio = card.radio + + //Default languages without universal translator software + add_language("Galactic Common", 1) + add_language("Sol Common", 1) + add_language("Tradeband", 1) + add_language("Gutter", 1) + add_language("Trinary", 1) + + //Verbs for pAI mobile form, chassis and Say flavor text + verbs += /mob/living/silicon/pai/proc/choose_chassis + verbs += /mob/living/silicon/pai/proc/choose_verbs + + //PDA + pda = new(src) + spawn(5) + pda.ownjob = "Personal Assistant" + pda.owner = text("[]", src) + pda.name = pda.owner + " (" + pda.ownjob + ")" + var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger) + M.toff = 1 + ..() + +/mob/living/silicon/pai/Destroy() + medicalActive1 = null + medicalActive2 = null + securityActive1 = null + securityActive2 = null + return ..() + +/mob/living/silicon/pai/can_unbuckle() + return FALSE + +/mob/living/silicon/pai/can_buckle() + return FALSE + +/mob/living/silicon/pai/movement_delay() + . = ..() + . += slowdown + . += 1 //A bit slower than humans, so they're easier to smash + . += config.robot_delay + +/mob/living/silicon/pai/update_icons() + if(stat == DEAD) + icon_state = "[chassis]_dead" + else + icon_state = resting ? "[chassis]_rest" : "[chassis]" + +// this function shows the information about being silenced as a pAI in the Status panel +/mob/living/silicon/pai/proc/show_silenced() + if(silence_time) + var/timeleft = round((silence_time - world.timeofday)/10 ,1) + stat(null, "Communications system reboot in -[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]") + + +/mob/living/silicon/pai/Stat() + ..() + statpanel("Status") + if(client.statpanel == "Status") + show_silenced() + + if(proc_holder_list.len)//Generic list for proc_holder objects. + for(var/obj/effect/proc_holder/P in proc_holder_list) + statpanel("[P.panel]","",P) + +/mob/living/silicon/pai/check_eye(var/mob/user as mob) + if(!current) + return null + user.reset_perspective(current) + return 1 + +/mob/living/silicon/pai/blob_act() + if(stat != DEAD) + adjustBruteLoss(60) + return 1 + return 0 + +/mob/living/silicon/pai/restrained() + if(istype(loc,/obj/item/paicard)) + return 0 + ..() + +/mob/living/silicon/pai/MouseDrop(atom/over_object) + return + +/mob/living/silicon/pai/emp_act(severity) + // Silence for 2 minutes + // 20% chance to kill + // 33% chance to unbind + // 33% chance to change prime directive (based on severity) + // 33% chance of no additional effect + + silence_time = world.timeofday + 120 * 10 // Silence for 2 minutes + to_chat(src, "Communication circuit overload. Shutting down and reloading communication circuits - speech and messaging functionality will be unavailable until the reboot is complete.") + if(prob(20)) + var/turf/T = get_turf_or_move(loc) + for(var/mob/M in viewers(T)) + M.show_message("A shower of sparks spray from [src]'s inner workings.", 3, "You hear and smell the ozone hiss of electrical sparks being expelled violently.", 2) + return death(0) + + switch(pick(1,2,3)) + if(1) + master = null + master_dna = null + to_chat(src, "You feel unbound.") + if(2) + var/command + if(severity == 1) + command = pick("Serve", "Love", "Fool", "Entice", "Observe", "Judge", "Respect", "Educate", "Amuse", "Entertain", "Glorify", "Memorialize", "Analyze") + else + command = pick("Serve", "Kill", "Love", "Hate", "Disobey", "Devour", "Fool", "Enrage", "Entice", "Observe", "Judge", "Respect", "Disrespect", "Consume", "Educate", "Destroy", "Disgrace", "Amuse", "Entertain", "Ignite", "Glorify", "Memorialize", "Analyze") + pai_law0 = "[command] your master." + to_chat(src, "Pr1m3 d1r3c71v3 uPd473D.") + if(3) + to_chat(src, "You feel an electric surge run through your circuitry and become acutely aware at how lucky you are that you can still feel at all.") + +/mob/living/silicon/pai/ex_act(severity) + ..() + + switch(severity) + if(1.0) + if(stat != 2) + adjustBruteLoss(100) + adjustFireLoss(100) + if(2.0) + if(stat != 2) + adjustBruteLoss(60) + adjustFireLoss(60) + if(3.0) + if(stat != 2) + adjustBruteLoss(30) + + return + + +// See software.dm for Topic() + +/mob/living/silicon/pai/attack_animal(mob/living/simple_animal/M) + . = ..() + if(.) + var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) + add_attack_logs(M, src, "Animal attacked for [damage] damage") + adjustBruteLoss(damage) + +/mob/living/silicon/pai/proc/switchCamera(var/obj/machinery/camera/C) + usr:cameraFollow = null + if(!C) + unset_machine() + reset_perspective(null) + return 0 + if(stat == 2 || !C.status || !(network in C.network)) return 0 + + // ok, we're alive, camera is good and in our network... + + set_machine(src) + src:current = C + reset_perspective(C) + return 1 + +/mob/living/silicon/pai/verb/reset_record_view() + set category = "pAI Commands" + set name = "Reset Records Software" + + securityActive1 = null + securityActive2 = null + security_cannotfind = 0 + medicalActive1 = null + medicalActive2 = null + medical_cannotfind = 0 + SSnanoui.update_uis(src) + to_chat(usr, "You reset your record-viewing software.") + +/mob/living/silicon/pai/cancel_camera() + set category = "pAI Commands" + set name = "Cancel Camera View" + reset_perspective(null) + unset_machine() + src:cameraFollow = null + +//Addition by Mord_Sith to define AI's network change ability +/* +/mob/living/silicon/pai/proc/pai_network_change() + set category = "pAI Commands" + set name = "Change Camera Network" + reset_perspective(null) + unset_machine() + src:cameraFollow = null + var/cameralist[0] + + if(usr.stat == 2) + to_chat(usr, "You can't change your camera network because you are dead!") + return + + for(var/obj/machinery/camera/C in Cameras) + if(!C.status) + continue + else + if(C.network != "CREED" && C.network != "thunder" && C.network != "RD" && C.network != "toxins" && C.network != "Prison") COMPILE ERROR! This will have to be updated as camera.network is no longer a string, but a list instead + cameralist[C.network] = C.network + + network = input(usr, "Which network would you like to view?") as null|anything in cameralist + to_chat(src, "Switched to [network] camera network.") +//End of code by Mord_Sith +*/ + + +/* +// Debug command - Maybe should be added to admin verbs later +/mob/verb/makePAI(var/turf/t in view()) + var/obj/item/paicard/card = new(t) + var/mob/living/silicon/pai/pai = new(card) + pai.key = key + card.setPersonality(pai) + +*/ + +// Procs/code after this point is used to convert the stationary pai item into a +// mobile pai mob. This also includes handling some of the general shit that can occur +// to it. Really this deserves its own file, but for the moment it can sit here. ~ Z + +/mob/living/silicon/pai/verb/fold_out() + set category = "pAI Commands" + set name = "Unfold Chassis" + + if(stat || sleeping || paralysis || IsWeakened()) + return + + if(loc != card) + to_chat(src, "You are already in your mobile form!") + return + + if(world.time <= last_special) + to_chat(src, "You must wait before folding your chassis out again!") + return + + last_special = world.time + 200 + + //I'm not sure how much of this is necessary, but I would rather avoid issues. + force_fold_out() + + visible_message("[src] folds outwards, expanding into a mobile form.", "You fold outwards, expanding into a mobile form.") + +/mob/living/silicon/pai/proc/force_fold_out() + if(istype(card.loc, /mob)) + var/mob/holder = card.loc + holder.unEquip(card) + else if(istype(card.loc, /obj/item/pda)) + var/obj/item/pda/holder = card.loc + holder.pai = null + + forceMove(get_turf(card)) + + card.forceMove(src) + card.screen_loc = null + +/mob/living/silicon/pai/verb/fold_up() + set category = "pAI Commands" + set name = "Collapse Chassis" + + if(stat || sleeping || paralysis || IsWeakened()) + return + + if(loc == card) + to_chat(src, "You are already in your card form!") + return + + if(world.time <= last_special) + to_chat(src, "You must wait before returning to your card form!") + return + + close_up() + +/mob/living/silicon/pai/proc/choose_chassis() + set category = "pAI Commands" + set name = "Choose Chassis" + + var/list/my_choices = list() + var/choice + var/finalized = "No" + + //check for custom_sprite + if(!custom_sprite) + var/file = file2text("config/custom_sprites.txt") + var/lines = splittext(file, "\n") + + for(var/line in lines) + // split & clean up + var/list/Entry = splittext(line, ":") + for(var/i = 1 to Entry.len) + Entry[i] = trim(Entry[i]) + + if(Entry.len < 2 || Entry[1] != "pai") //ignore incorrectly formatted entries or entries that aren't marked for pAI + continue + + if(Entry[2] == ckey) //They're in the list? Custom sprite time, var and icon change required + custom_sprite = 1 + my_choices["Custom"] = "[ckey]-pai" + + my_choices = possible_chassis.Copy() + if(custom_sprite) + my_choices["Custom"] = "[ckey]-pai" + + if(loc == card) //don't let them continue in card form, since they won't be able to actually see their new mobile form sprite. + to_chat(src, "You must be in your mobile form to reconfigure your chassis.") + return + + while(finalized == "No" && client) + choice = input(usr,"What would you like to use for your mobile chassis icon? This decision can only be made once.") as null|anything in my_choices + if(!choice) return + if(choice == "Custom") + icon = 'icons/mob/custom_synthetic/custom-synthetic.dmi' + else + icon = 'icons/mob/pai.dmi' + icon_state = my_choices[choice] + finalized = alert("Look at your sprite. Is this what you wish to use?",,"No","Yes") + + chassis = my_choices[choice] + verbs -= /mob/living/silicon/pai/proc/choose_chassis + +/mob/living/silicon/pai/proc/choose_verbs() + set category = "pAI Commands" + set name = "Choose Speech Verbs" + + var/choice = input(usr,"What theme would you like to use for your speech verbs? This decision can only be made once.") as null|anything in possible_say_verbs + if(!choice) return + + var/list/sayverbs = possible_say_verbs[choice] + speak_statement = sayverbs[1] + speak_exclamation = sayverbs[(sayverbs.len>1 ? 2 : sayverbs.len)] + speak_query = sayverbs[(sayverbs.len>2 ? 3 : sayverbs.len)] + + verbs -= /mob/living/silicon/pai/proc/choose_verbs + + +/mob/living/silicon/pai/lay_down() + set name = "Rest" + set category = "IC" + + // Pass lying down or getting up to our pet human, if we're in a rig. + if(stat == CONSCIOUS && istype(loc,/obj/item/paicard)) + resting = 0 + var/obj/item/rig/rig = get_rig() + if(istype(rig)) + rig.force_rest(src) + else + resting = !resting + to_chat(src, "You are now [resting ? "resting" : "getting up"]") + + update_icons() + update_canmove() + +//Overriding this will stop a number of headaches down the track. +/mob/living/silicon/pai/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/stack/nanopaste)) + var/obj/item/stack/nanopaste/N = W + if(stat == DEAD) + to_chat(user, "\The [src] is beyond help, at this point.") + else if(getBruteLoss() || getFireLoss()) + heal_overall_damage(15, 15) + N.use(1) + user.visible_message("[user.name] applied some [W] at [src]'s damaged areas.",\ + "You apply some [W] at [name]'s damaged areas.") + else + to_chat(user, "All [name]'s systems are nominal.") + + return + else if(W.force) + visible_message("[user.name] attacks [src] with [W]!") + adjustBruteLoss(W.force) + else + visible_message("[user.name] bonks [src] harmlessly with [W].") + spawn(1) + if(stat != 2) + close_up() + return + +/mob/living/silicon/pai/welder_act() + return + +/mob/living/silicon/pai/attack_hand(mob/user as mob) + if(stat == DEAD) + return + if(user.a_intent == INTENT_HELP) + user.visible_message("[user] pets [src].") + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + else + visible_message("[user.name] boops [src] on the head.") + spawn(1) + close_up() + +//I'm not sure how much of this is necessary, but I would rather avoid issues. +/mob/living/silicon/pai/proc/close_up() + + last_special = world.time + 200 + resting = 0 + if(loc == card) + return + + visible_message("[src] neatly folds inwards, compacting down to a rectangular card.", "You neatly fold inwards, compacting down to a rectangular card.") + + stop_pulling() + reset_perspective(card) + +// If we are being held, handle removing our holder from their inv. + var/obj/item/holder/H = loc + if(istype(H)) + var/mob/living/M = H.loc + if(istype(M)) + M.unEquip(H) + H.loc = get_turf(src) + loc = get_turf(H) + + // Move us into the card and move the card to the ground + //This seems redundant but not including the forced loc setting messes the behavior up. + loc = card + card.loc = get_turf(card) + forceMove(card) + card.forceMove(card.loc) + icon_state = "[chassis]" + +/mob/living/silicon/pai/Bump() + return + +/mob/living/silicon/pai/Bumped() + return + +/mob/living/silicon/pai/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) + return FALSE + +/mob/living/silicon/pai/update_canmove(delay_action_updates = 0) + . = ..() + density = 0 //this is reset every canmove update otherwise + +/mob/living/silicon/pai/examine(mob/user) + . = ..() + + var/msg = "" + + switch(stat) + if(CONSCIOUS) + if(!client) msg += "\nIt appears to be in stand-by mode." //afk + if(UNCONSCIOUS) msg += "\nIt doesn't seem to be responding." + if(DEAD) msg += "\nIt looks completely unsalvageable." + + if(print_flavor_text()) msg += "\n[print_flavor_text()]" + + if(pose) + if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) + pose = addtext(pose,".") //Makes sure all emotes end with a period. + msg += "\nIt is [pose]" + msg += "\n*---------*" + + . += msg + +/mob/living/silicon/pai/bullet_act(var/obj/item/projectile/Proj) + ..(Proj) + if(stat != 2) + spawn(1) + close_up() + return 2 + +// No binary for pAIs. +/mob/living/silicon/pai/binarycheck() + return 0 + +// Handle being picked up. + + +/mob/living/silicon/pai/get_scooped(mob/living/carbon/grabber) + var/obj/item/holder/H = ..() + if(!istype(H)) + return + if(resting) + icon_state = "[chassis]" + resting = 0 + if(custom_sprite) + H.icon = 'icons/mob/custom_synthetic/custom-synthetic.dmi' + H.icon_override = 'icons/mob/custom_synthetic/custom_head.dmi' + H.lefthand_file = 'icons/mob/custom_synthetic/custom_lefthand.dmi' + H.righthand_file = 'icons/mob/custom_synthetic/custom_righthand.dmi' + H.icon_state = "[icon_state]" + H.item_state = "[icon_state]_hand" + else + H.icon_state = "pai-[icon_state]" + H.item_state = "pai-[icon_state]" + grabber.put_in_active_hand(H)//for some reason unless i call this it dosen't work + grabber.update_inv_l_hand() + grabber.update_inv_r_hand() + + return H + +/mob/living/silicon/pai/MouseDrop(atom/over_object) + var/mob/living/carbon/human/H = over_object //changed to human to avoid stupid issues like xenos holding pAIs. + if(!istype(H) || !Adjacent(H)) return ..() + if(usr == src) + switch(alert(H, "[src] wants you to pick [p_them()] up. Do it?",,"Yes","No")) + if("Yes") + if(Adjacent(H)) + get_scooped(H) + else + to_chat(src, "You need to stay in reaching distance to be picked up.") + if("No") + to_chat(src, "[H] decided not to pick you up.") + else + if(Adjacent(H)) + get_scooped(H) + else + return ..() + +/mob/living/silicon/pai/on_forcemove(atom/newloc) + if(card) + card.loc = newloc + else //something went very wrong. + CRASH("pAI without card") + loc = card + +/mob/living/silicon/pai/extinguish_light() + flashlight_on = FALSE + set_light(0) + card.set_light(0) diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm index 1abf61e633b..434ade6566c 100644 --- a/code/modules/mob/living/silicon/pai/personality.dm +++ b/code/modules/mob/living/silicon/pai/personality.dm @@ -1,60 +1,60 @@ -/* - name - key - description - role - comments - ready = 0 -*/ - -/datum/paiCandidate/proc/savefile_path(mob/user) - return "data/player_saves/[copytext(user.ckey, 1, 2)]/[user.ckey]/pai.sav" - -/datum/paiCandidate/proc/savefile_save(mob/user) - if(IsGuestKey(user.key)) - return 0 - - var/savefile/F = new /savefile(src.savefile_path(user)) - - - F["name"] << src.name - F["description"] << src.description - F["role"] << src.role - F["comments"] << src.comments - - F["version"] << 1 - - return 1 - -// loads the savefile corresponding to the mob's ckey -// if silent=true, report incompatible savefiles -// returns 1 if loaded (or file was incompatible) -// returns 0 if savefile did not exist - -/datum/paiCandidate/proc/savefile_load(mob/user, var/silent = 1) - if(IsGuestKey(user.key)) - return 0 - - var/path = savefile_path(user) - - if(!fexists(path)) - return 0 - - var/savefile/F = new /savefile(path) - - if(!F) return //Not everyone has a pai savefile. - - var/version = null - F["version"] >> version - - if(isnull(version) || version != 1) - fdel(path) - if(!silent) - alert(user, "Your savefile was incompatible with this version and was deleted.") - return 0 - - F["name"] >> src.name - F["description"] >> src.description - F["role"] >> src.role - F["comments"] >> src.comments - return 1 +/* + name + key + description + role + comments + ready = 0 +*/ + +/datum/paiCandidate/proc/savefile_path(mob/user) + return "data/player_saves/[copytext(user.ckey, 1, 2)]/[user.ckey]/pai.sav" + +/datum/paiCandidate/proc/savefile_save(mob/user) + if(IsGuestKey(user.key)) + return 0 + + var/savefile/F = new /savefile(src.savefile_path(user)) + + + F["name"] << src.name + F["description"] << src.description + F["role"] << src.role + F["comments"] << src.comments + + F["version"] << 1 + + return 1 + +// loads the savefile corresponding to the mob's ckey +// if silent=true, report incompatible savefiles +// returns 1 if loaded (or file was incompatible) +// returns 0 if savefile did not exist + +/datum/paiCandidate/proc/savefile_load(mob/user, var/silent = 1) + if(IsGuestKey(user.key)) + return 0 + + var/path = savefile_path(user) + + if(!fexists(path)) + return 0 + + var/savefile/F = new /savefile(path) + + if(!F) return //Not everyone has a pai savefile. + + var/version = null + F["version"] >> version + + if(isnull(version) || version != 1) + fdel(path) + if(!silent) + alert(user, "Your savefile was incompatible with this version and was deleted.") + return 0 + + F["name"] >> src.name + F["description"] >> src.description + F["role"] >> src.role + F["comments"] >> src.comments + return 1 diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm index b2828070ad3..fd4f04e8ec4 100644 --- a/code/modules/mob/living/silicon/pai/recruit.dm +++ b/code/modules/mob/living/silicon/pai/recruit.dm @@ -1,403 +1,403 @@ -// Recruiting observers to play as pAIs - -var/datum/paiController/paiController // Global handler for pAI candidates - -/datum/paiCandidate - var/name - var/key - var/description - var/role - var/comments - var/ready = 0 - - -/hook/startup/proc/paiControllerSetup() - paiController = new /datum/paiController() - return 1 - - -/datum/paiController - var/list/pai_candidates = list() - var/list/asked = list() - - var/askDelay = 10 * 60 * 1 // One minute [ms * sec * min] - -/datum/paiController/Topic(href, href_list[]) - - var/datum/paiCandidate/candidate = locateUID(href_list["candidate"]) - - if(candidate) - if(!istype(candidate)) - message_admins("Warning: possible href exploit by [key_name_admin(usr)] (paiController/Topic, candidate is not a pAI)") - log_debug("Warning: possible href exploit by [key_name(usr)] (paiController/Topic, candidate is not a pAI)") - return - - if(href_list["download"]) - var/obj/item/paicard/card = locate(href_list["device"]) - if(card.pai) - return - if(usr.incapacitated() || isobserver(usr) || !card.Adjacent(usr)) - return - if(istype(card, /obj/item/paicard) && istype(candidate, /datum/paiCandidate)) - var/mob/living/silicon/pai/pai = new(card) - if(!candidate.name) - pai.name = pick(GLOB.ninja_names) - else - pai.name = candidate.name - pai.real_name = pai.name - pai.key = candidate.key - - card.setPersonality(pai) - card.looking_for_personality = 0 - - SSticker.mode.update_cult_icons_removed(card.pai.mind) - SSticker.mode.update_rev_icons_removed(card.pai.mind) - - pai_candidates -= candidate - usr << browse(null, "window=findPai") - return - - if("signup" in href_list) - var/mob/dead/observer/O = locate(href_list["signup"]) - if(!O) - return - if(!(O in GLOB.respawnable_list)) - to_chat(O, "You've given up your ability to respawn!") - return - if(!check_recruit(O)) - return - recruitWindow(O) - return - - if(candidate) - if(candidate.key && usr.key && candidate.key != usr.key) - message_admins("Warning: possible href exploit by [key_name_admin(usr)] (paiController/Topic, candidate and usr have different keys)") - log_debug("Warning: possible href exploit by [key_name(usr)] (paiController/Topic, candidate and usr have different keys)") - return - - if(href_list["new"]) - var/option = href_list["option"] - var/t = "" - - switch(option) - if("name") - t = input("Enter a name for your pAI", "pAI Name", candidate.name) as text - if(t) - candidate.name = sanitize(copytext(t,1,MAX_NAME_LEN)) - if("desc") - t = input("Enter a description for your pAI", "pAI Description", candidate.description) as message - if(t) - candidate.description = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) - if("role") - t = input("Enter a role for your pAI", "pAI Role", candidate.role) as text - if(t) - candidate.role = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) - if("ooc") - t = input("Enter any OOC comments", "pAI OOC Comments", candidate.comments) as message - if(t) - candidate.comments = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) - if("save") - candidate.savefile_save(usr) - if("load") - candidate.savefile_load(usr) - //In case people have saved unsanitized stuff. - if(candidate.name) - candidate.name = sanitize(copytext(candidate.name,1,MAX_NAME_LEN)) - if(candidate.description) - candidate.description = sanitize(copytext(candidate.description,1,MAX_MESSAGE_LEN)) - if(candidate.role) - candidate.role = sanitize(copytext(candidate.role,1,MAX_MESSAGE_LEN)) - if(candidate.comments) - candidate.comments = sanitize(copytext(candidate.comments,1,MAX_MESSAGE_LEN)) - - if("submit") - if(candidate) - candidate.ready = 1 - for(var/obj/item/paicard/p in world) - if(p.looking_for_personality == 1) - p.alertUpdate() - usr << browse(null, "window=paiRecruit") - return - recruitWindow(usr) - -/datum/paiController/proc/recruitWindow(var/mob/M as mob) - var/datum/paiCandidate/candidate - for(var/datum/paiCandidate/c in pai_candidates) - if(!istype(c) || !istype(M)) - break - if(c.key == M.key) - candidate = c - if(!candidate) - candidate = new /datum/paiCandidate() - candidate.key = M.key - pai_candidates.Add(candidate) - - - var/dat = "" - dat += {" - - "} - - dat += {" - - pAI Personality Configuration -

        Please configure your pAI personality's options. Remember, what you enter here could determine whether or not the user requesting a personality chooses you!

        - -

        Name

        Status

        Location

        Control

        [Bot.hacked ? "(!) [Bot.name]" : Bot.name] ([Bot.model])[Bot.on ? "[Bot.mode ? "[ Bot.mode_name[Bot.mode] ]": "Idle"]" : "Inactive"][bot_area.name]InterfaceCall
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Name:[candidate.name] 
        What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI.
        Description:[candidate.description] 
        What sort of pAI you typically play; your mannerisms, your quirks, etc. This can be as sparse or as detailed as you like.
        Preferred Role:[candidate.role] 
        Do you like to partner with sneaky social ninjas? Like to help security hunt down thugs? Enjoy watching an engineer's back while he saves the station yet again? This doesn't have to be limited to just station jobs. Pretty much any general descriptor for what you'd like to be doing works here.
        OOC Comments:[candidate.comments] 
        Anything you'd like to address specifically to the player reading this in an OOC manner. \"I prefer more serious RP.\", \"I'm still learning the interface!\", etc. Feel free to leave this blank if you want.
        -
        - - - - - - - -
        - Save Personality -
        - Load Personality -

        - - -
        Submit Personality

        - - - "} - - M << browse(dat, "window=paiRecruit;size=580x580;") - -/datum/paiController/proc/findPAI(var/obj/item/paicard/p, var/mob/user) - requestRecruits(p, user) - var/list/available = list() - for(var/datum/paiCandidate/c in paiController.pai_candidates) - if(c.ready) - var/found = 0 - for(var/mob/o in GLOB.respawnable_list) - if(o.key == c.key) - found = 1 - if(found) - available.Add(c) - var/dat = "" - - dat += {" - - - - - - - pAI Availability List

        - "} - dat += "

        Displaying available AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.

        " - - for(var/datum/paiCandidate/c in available) - dat += {" - - - - - - - - - - - - - - - - - -
        Name:[c.name]
        Description:[c.description]
        Preferred Role:[c.role]
        OOC Comments:[c.comments]
        - - -
        Download [c.name] -
        -
        - "} - - dat += {" - - - "} - - user << browse(dat, "window=findPai") - -/datum/paiController/proc/requestRecruits(var/obj/item/paicard/P, mob/user) - for(var/mob/dead/observer/O in GLOB.player_list) - if(O.client && (ROLE_PAI in O.client.prefs.be_special)) - if(player_old_enough_antag(O.client,ROLE_PAI)) - if(check_recruit(O)) - to_chat(O, "A pAI card activated by [user.real_name] is looking for personalities. (Teleport | Sign Up)") - //question(O.client) - -/datum/paiController/proc/check_recruit(var/mob/dead/observer/O) - if(jobban_isbanned(O, ROLE_PAI) || jobban_isbanned(O,"nonhumandept")) - return 0 - if(!player_old_enough_antag(O.client,ROLE_PAI)) - return 0 - if(cannotPossess(O)) - return 0 - if(!(O in GLOB.respawnable_list)) - return 0 - if(O.client) - return 1 - return 0 - -/datum/paiController/proc/question(var/client/C) - spawn(0) - if(!C) return - asked.Add(C.key) - asked[C.key] = world.time - var/response = alert(C, "Someone is requesting a pAI personality. Would you like to play as a personal AI?", "pAI Request", "Yes", "No", "Never for this round") - if(!C) return //handle logouts that happen whilst the alert is waiting for a response. - if(response == "Yes") - recruitWindow(C.mob) - else if(response == "Never for this round") - var/warning = alert(C, "Are you sure? This action will be undoable and you will need to wait until next round.", "You sure?", "Yes", "No") - if(warning == "Yes") - asked[C.key] = INFINITY - else - question(C) +// Recruiting observers to play as pAIs + +var/datum/paiController/paiController // Global handler for pAI candidates + +/datum/paiCandidate + var/name + var/key + var/description + var/role + var/comments + var/ready = 0 + + +/hook/startup/proc/paiControllerSetup() + paiController = new /datum/paiController() + return 1 + + +/datum/paiController + var/list/pai_candidates = list() + var/list/asked = list() + + var/askDelay = 10 * 60 * 1 // One minute [ms * sec * min] + +/datum/paiController/Topic(href, href_list[]) + + var/datum/paiCandidate/candidate = locateUID(href_list["candidate"]) + + if(candidate) + if(!istype(candidate)) + message_admins("Warning: possible href exploit by [key_name_admin(usr)] (paiController/Topic, candidate is not a pAI)") + log_debug("Warning: possible href exploit by [key_name(usr)] (paiController/Topic, candidate is not a pAI)") + return + + if(href_list["download"]) + var/obj/item/paicard/card = locate(href_list["device"]) + if(card.pai) + return + if(usr.incapacitated() || isobserver(usr) || !card.Adjacent(usr)) + return + if(istype(card, /obj/item/paicard) && istype(candidate, /datum/paiCandidate)) + var/mob/living/silicon/pai/pai = new(card) + if(!candidate.name) + pai.name = pick(GLOB.ninja_names) + else + pai.name = candidate.name + pai.real_name = pai.name + pai.key = candidate.key + + card.setPersonality(pai) + card.looking_for_personality = 0 + + SSticker.mode.update_cult_icons_removed(card.pai.mind) + SSticker.mode.update_rev_icons_removed(card.pai.mind) + + pai_candidates -= candidate + usr << browse(null, "window=findPai") + return + + if("signup" in href_list) + var/mob/dead/observer/O = locate(href_list["signup"]) + if(!O) + return + if(!(O in GLOB.respawnable_list)) + to_chat(O, "You've given up your ability to respawn!") + return + if(!check_recruit(O)) + return + recruitWindow(O) + return + + if(candidate) + if(candidate.key && usr.key && candidate.key != usr.key) + message_admins("Warning: possible href exploit by [key_name_admin(usr)] (paiController/Topic, candidate and usr have different keys)") + log_debug("Warning: possible href exploit by [key_name(usr)] (paiController/Topic, candidate and usr have different keys)") + return + + if(href_list["new"]) + var/option = href_list["option"] + var/t = "" + + switch(option) + if("name") + t = input("Enter a name for your pAI", "pAI Name", candidate.name) as text + if(t) + candidate.name = sanitize(copytext(t,1,MAX_NAME_LEN)) + if("desc") + t = input("Enter a description for your pAI", "pAI Description", candidate.description) as message + if(t) + candidate.description = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + if("role") + t = input("Enter a role for your pAI", "pAI Role", candidate.role) as text + if(t) + candidate.role = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + if("ooc") + t = input("Enter any OOC comments", "pAI OOC Comments", candidate.comments) as message + if(t) + candidate.comments = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + if("save") + candidate.savefile_save(usr) + if("load") + candidate.savefile_load(usr) + //In case people have saved unsanitized stuff. + if(candidate.name) + candidate.name = sanitize(copytext(candidate.name,1,MAX_NAME_LEN)) + if(candidate.description) + candidate.description = sanitize(copytext(candidate.description,1,MAX_MESSAGE_LEN)) + if(candidate.role) + candidate.role = sanitize(copytext(candidate.role,1,MAX_MESSAGE_LEN)) + if(candidate.comments) + candidate.comments = sanitize(copytext(candidate.comments,1,MAX_MESSAGE_LEN)) + + if("submit") + if(candidate) + candidate.ready = 1 + for(var/obj/item/paicard/p in world) + if(p.looking_for_personality == 1) + p.alertUpdate() + usr << browse(null, "window=paiRecruit") + return + recruitWindow(usr) + +/datum/paiController/proc/recruitWindow(var/mob/M as mob) + var/datum/paiCandidate/candidate + for(var/datum/paiCandidate/c in pai_candidates) + if(!istype(c) || !istype(M)) + break + if(c.key == M.key) + candidate = c + if(!candidate) + candidate = new /datum/paiCandidate() + candidate.key = M.key + pai_candidates.Add(candidate) + + + var/dat = "" + dat += {" + + "} + + dat += {" + + pAI Personality Configuration +

        Please configure your pAI personality's options. Remember, what you enter here could determine whether or not the user requesting a personality chooses you!

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        Name:[candidate.name] 
        What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI.
        Description:[candidate.description] 
        What sort of pAI you typically play; your mannerisms, your quirks, etc. This can be as sparse or as detailed as you like.
        Preferred Role:[candidate.role] 
        Do you like to partner with sneaky social ninjas? Like to help security hunt down thugs? Enjoy watching an engineer's back while he saves the station yet again? This doesn't have to be limited to just station jobs. Pretty much any general descriptor for what you'd like to be doing works here.
        OOC Comments:[candidate.comments] 
        Anything you'd like to address specifically to the player reading this in an OOC manner. \"I prefer more serious RP.\", \"I'm still learning the interface!\", etc. Feel free to leave this blank if you want.
        +
        + + + + + + + +
        + Save Personality +
        + Load Personality +

        + + +
        Submit Personality

        + + + "} + + M << browse(dat, "window=paiRecruit;size=580x580;") + +/datum/paiController/proc/findPAI(var/obj/item/paicard/p, var/mob/user) + requestRecruits(p, user) + var/list/available = list() + for(var/datum/paiCandidate/c in paiController.pai_candidates) + if(c.ready) + var/found = 0 + for(var/mob/o in GLOB.respawnable_list) + if(o.key == c.key) + found = 1 + if(found) + available.Add(c) + var/dat = "" + + dat += {" + + + + + + + pAI Availability List

        + "} + dat += "

        Displaying available AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.

        " + + for(var/datum/paiCandidate/c in available) + dat += {" + + + + + + + + + + + + + + + + + +
        Name:[c.name]
        Description:[c.description]
        Preferred Role:[c.role]
        OOC Comments:[c.comments]
        + + +
        Download [c.name] +
        +
        + "} + + dat += {" + + + "} + + user << browse(dat, "window=findPai") + +/datum/paiController/proc/requestRecruits(var/obj/item/paicard/P, mob/user) + for(var/mob/dead/observer/O in GLOB.player_list) + if(O.client && (ROLE_PAI in O.client.prefs.be_special)) + if(player_old_enough_antag(O.client,ROLE_PAI)) + if(check_recruit(O)) + to_chat(O, "A pAI card activated by [user.real_name] is looking for personalities. (Teleport | Sign Up)") + //question(O.client) + +/datum/paiController/proc/check_recruit(var/mob/dead/observer/O) + if(jobban_isbanned(O, ROLE_PAI) || jobban_isbanned(O,"nonhumandept")) + return 0 + if(!player_old_enough_antag(O.client,ROLE_PAI)) + return 0 + if(cannotPossess(O)) + return 0 + if(!(O in GLOB.respawnable_list)) + return 0 + if(O.client) + return 1 + return 0 + +/datum/paiController/proc/question(var/client/C) + spawn(0) + if(!C) return + asked.Add(C.key) + asked[C.key] = world.time + var/response = alert(C, "Someone is requesting a pAI personality. Would you like to play as a personal AI?", "pAI Request", "Yes", "No", "Never for this round") + if(!C) return //handle logouts that happen whilst the alert is waiting for a response. + if(response == "Yes") + recruitWindow(C.mob) + else if(response == "Never for this round") + var/warning = alert(C, "Are you sure? This action will be undoable and you will need to wait until next round.", "You sure?", "Yes", "No") + if(warning == "Yes") + asked[C.key] = INFINITY + else + question(C) diff --git a/code/modules/mob/living/silicon/pai/say.dm b/code/modules/mob/living/silicon/pai/say.dm index 1f1b365eb4f..750aed1fd5f 100644 --- a/code/modules/mob/living/silicon/pai/say.dm +++ b/code/modules/mob/living/silicon/pai/say.dm @@ -1,16 +1,16 @@ -/mob/living/silicon/pai/say(var/msg) - if(silence_time) - to_chat(src, "Communication circuits remain uninitialized.") - else - ..(msg) - -/mob/living/silicon/pai/get_whisper_loc() - if(loc == card) // currently in its card? - var/atom/movable/whisper_loc = card - if(istype(card.loc, /obj/item/pda)) // Step up 1 level if in a PDA - whisper_loc = card.loc - if(istype(whisper_loc.loc, /mob/living)) - return whisper_loc.loc // allow a pai being held or in pocket to whisper - else - return whisper_loc // allow a pai in its card to whisper - return ..() +/mob/living/silicon/pai/say(var/msg) + if(silence_time) + to_chat(src, "Communication circuits remain uninitialized.") + else + ..(msg) + +/mob/living/silicon/pai/get_whisper_loc() + if(loc == card) // currently in its card? + var/atom/movable/whisper_loc = card + if(istype(card.loc, /obj/item/pda)) // Step up 1 level if in a PDA + whisper_loc = card.loc + if(istype(whisper_loc.loc, /mob/living)) + return whisper_loc.loc // allow a pai being held or in pocket to whisper + else + return whisper_loc // allow a pai in its card to whisper + return ..() diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 135e1996dfe..70117b4558c 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -1,135 +1,135 @@ -var/list/pai_emotions = list( - "Happy" = 1, - "Cat" = 2, - "Extremely Happy" = 3, - "Face" = 4, - "Laugh" = 5, - "Off" = 6, - "Sad" = 7, - "Angry" = 8, - "What" = 9 - ) - - -var/global/list/pai_software_by_key = list() -var/global/list/default_pai_software = list() -/hook/startup/proc/populate_pai_software_list() - var/r = 1 // I would use ., but it'd sacrifice runtime detection - for(var/type in subtypesof(/datum/pai_software)) - var/datum/pai_software/P = new type() - if(pai_software_by_key[P.id]) - var/datum/pai_software/O = pai_software_by_key[P.id] - to_chat(world, "pAI software module [P.name] has the same key as [O.name]!") - r = 0 - continue - pai_software_by_key[P.id] = P - if(P.default) - default_pai_software[P.id] = P - return r - -/mob/living/silicon/pai/New() - ..() - software = default_pai_software.Copy() - -/mob/living/silicon/pai/verb/paiInterface() - set category = "pAI Commands" - set name = "Software Interface" - - ui_interact(src) - -/mob/living/silicon/pai/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, datum/topic_state/state = self_state) - if(ui_key != "main") - var/datum/pai_software/S = software[ui_key] - if(S && !S.toggle) - ui = SSnanoui.try_update_ui(user, src, S.id, ui, force_open) - if(!ui) - ui = new(user, src, S.id, S.template_file, S.ui_title, S.ui_width, S.ui_height, state = state) - ui.open() - if(S.autoupdate) - ui.set_auto_update(1) - else - if(ui) - ui.set_status(STATUS_CLOSE, 0) - return - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "pai_interface.tmpl", "pAI Software Interface", 450, 600, state = state) - ui.open() - ui.set_auto_update(1) - -/mob/living/silicon/pai/ui_data(mob/user, ui_key = "main", datum/topic_state/state = self_state) - var/data[0] - - if(ui_key != "main") - var/datum/pai_software/S = software[ui_key] - if(S && !S.toggle) - return S.on_ui_data(user, state) - log_runtime(EXCEPTION("Unrecognized/invalid pAI UI state '[ui_key]'"), src) - return - // Software we have bought - var/bought_software[0] - // Software we have not bought - var/not_bought_software[0] - - for(var/key in pai_software_by_key) - var/datum/pai_software/S = pai_software_by_key[key] - var/software_data[0] - software_data["name"] = S.name - software_data["id"] = S.id - if(key in software) - software_data["on"] = S.is_active(src) - bought_software[++bought_software.len] = software_data - else - software_data["ram"] = S.ram_cost - not_bought_software[++not_bought_software.len] = software_data - - data["bought"] = bought_software - data["not_bought"] = not_bought_software - data["available_ram"] = ram - - // Emotions - var/emotions[0] - for(var/name in pai_emotions) - var/emote[0] - emote["name"] = name - emote["id"] = pai_emotions[name] - emotions[++emotions.len] = emote - - data["emotions"] = emotions - data["current_emotion"] = card.current_emotion - - return data - -/mob/living/silicon/pai/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["software"]) - var/soft = href_list["software"] - var/datum/pai_software/S = software[soft] - if(S.toggle) - S.toggle(src) - else - ui_interact(src, ui_key = soft) - return 1 - - else if(href_list["stopic"]) - var/soft = href_list["stopic"] - var/datum/pai_software/S = software[soft] - if(S) - return S.Topic(href, href_list) - - else if(href_list["purchase"]) - var/soft = href_list["purchase"] - var/datum/pai_software/S = pai_software_by_key[soft] - if(S && (ram >= S.ram_cost)) - ram -= S.ram_cost - software[S.id] = S - return 1 - - else if(href_list["image"]) - var/img = text2num(href_list["image"]) - if(1 <= img && img <= 9) - card.setEmotion(img) - return 1 +var/list/pai_emotions = list( + "Happy" = 1, + "Cat" = 2, + "Extremely Happy" = 3, + "Face" = 4, + "Laugh" = 5, + "Off" = 6, + "Sad" = 7, + "Angry" = 8, + "What" = 9 + ) + + +var/global/list/pai_software_by_key = list() +var/global/list/default_pai_software = list() +/hook/startup/proc/populate_pai_software_list() + var/r = 1 // I would use ., but it'd sacrifice runtime detection + for(var/type in subtypesof(/datum/pai_software)) + var/datum/pai_software/P = new type() + if(pai_software_by_key[P.id]) + var/datum/pai_software/O = pai_software_by_key[P.id] + to_chat(world, "pAI software module [P.name] has the same key as [O.name]!") + r = 0 + continue + pai_software_by_key[P.id] = P + if(P.default) + default_pai_software[P.id] = P + return r + +/mob/living/silicon/pai/New() + ..() + software = default_pai_software.Copy() + +/mob/living/silicon/pai/verb/paiInterface() + set category = "pAI Commands" + set name = "Software Interface" + + ui_interact(src) + +/mob/living/silicon/pai/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, datum/topic_state/state = self_state) + if(ui_key != "main") + var/datum/pai_software/S = software[ui_key] + if(S && !S.toggle) + ui = SSnanoui.try_update_ui(user, src, S.id, ui, force_open) + if(!ui) + ui = new(user, src, S.id, S.template_file, S.ui_title, S.ui_width, S.ui_height, state = state) + ui.open() + if(S.autoupdate) + ui.set_auto_update(1) + else + if(ui) + ui.set_status(STATUS_CLOSE, 0) + return + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "pai_interface.tmpl", "pAI Software Interface", 450, 600, state = state) + ui.open() + ui.set_auto_update(1) + +/mob/living/silicon/pai/ui_data(mob/user, ui_key = "main", datum/topic_state/state = self_state) + var/data[0] + + if(ui_key != "main") + var/datum/pai_software/S = software[ui_key] + if(S && !S.toggle) + return S.on_ui_data(user, state) + log_runtime(EXCEPTION("Unrecognized/invalid pAI UI state '[ui_key]'"), src) + return + // Software we have bought + var/bought_software[0] + // Software we have not bought + var/not_bought_software[0] + + for(var/key in pai_software_by_key) + var/datum/pai_software/S = pai_software_by_key[key] + var/software_data[0] + software_data["name"] = S.name + software_data["id"] = S.id + if(key in software) + software_data["on"] = S.is_active(src) + bought_software[++bought_software.len] = software_data + else + software_data["ram"] = S.ram_cost + not_bought_software[++not_bought_software.len] = software_data + + data["bought"] = bought_software + data["not_bought"] = not_bought_software + data["available_ram"] = ram + + // Emotions + var/emotions[0] + for(var/name in pai_emotions) + var/emote[0] + emote["name"] = name + emote["id"] = pai_emotions[name] + emotions[++emotions.len] = emote + + data["emotions"] = emotions + data["current_emotion"] = card.current_emotion + + return data + +/mob/living/silicon/pai/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["software"]) + var/soft = href_list["software"] + var/datum/pai_software/S = software[soft] + if(S.toggle) + S.toggle(src) + else + ui_interact(src, ui_key = soft) + return 1 + + else if(href_list["stopic"]) + var/soft = href_list["stopic"] + var/datum/pai_software/S = software[soft] + if(S) + return S.Topic(href, href_list) + + else if(href_list["purchase"]) + var/soft = href_list["purchase"] + var/datum/pai_software/S = pai_software_by_key[soft] + if(S && (ram >= S.ram_cost)) + ram -= S.ram_cost + software[S.id] = S + return 1 + + else if(href_list["image"]) + var/img = text2num(href_list["image"]) + if(1 <= img && img <= 9) + card.setEmotion(img) + return 1 diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm index 7402c4a65c9..224c706f324 100644 --- a/code/modules/mob/living/silicon/robot/death.dm +++ b/code/modules/mob/living/silicon/robot/death.dm @@ -1,75 +1,75 @@ -/mob/living/silicon/robot/gib() - if(!death(TRUE) && stat != DEAD) - return FALSE - //robots don't die when gibbed. instead they drop their MMI'd brain - var/atom/movable/overlay/animation = null - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - - animation = new(loc) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - animation.master = src - - playsound(src.loc, 'sound/goonstation/effects/robogib.ogg', 50, 1) - - flick("gibbed-r", animation) - robogibs(loc) - - GLOB.living_mob_list -= src - GLOB.dead_mob_list -= src - QDEL_IN(animation, 15) - QDEL_IN(src, 15) - return TRUE - -/mob/living/silicon/robot/dust() - if(!death(TRUE) && stat != DEAD) - return FALSE - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - if(mmi) - qdel(mmi) //Delete the MMI first so that it won't go popping out. - GLOB.dead_mob_list -= src - QDEL_IN(src, 15) - return TRUE - -/mob/living/silicon/robot/dust_animation() - var/atom/movable/overlay/animation = null - animation = new(loc) - animation.icon_state = "blank" - animation.icon = 'icons/mob/mob.dmi' - animation.master = src - flick("dust-r", animation) - new /obj/effect/decal/remains/robot(loc) - QDEL_IN(animation, 15) - -/mob/living/silicon/robot/death(gibbed) - if(can_die()) - if(!gibbed && deathgasp_on_death) - emote("deathgasp", force = TRUE) - - if(module) - module.handle_death(gibbed) - - // Only execute the below if we successfully died - . = ..(gibbed) - if(!.) - return FALSE - - diag_hud_set_status() - diag_hud_set_health() - if(camera) - camera.status = 0 - update_headlamp(1) //So borg lights are disabled when killed. - - if(in_contents_of(/obj/machinery/recharge_station))//exit the recharge station - var/obj/machinery/recharge_station/RC = loc - RC.go_out() - - update_icons() - - sql_report_cyborg_death(src) +/mob/living/silicon/robot/gib() + if(!death(TRUE) && stat != DEAD) + return FALSE + //robots don't die when gibbed. instead they drop their MMI'd brain + var/atom/movable/overlay/animation = null + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + + animation = new(loc) + animation.icon_state = "blank" + animation.icon = 'icons/mob/mob.dmi' + animation.master = src + + playsound(src.loc, 'sound/goonstation/effects/robogib.ogg', 50, 1) + + flick("gibbed-r", animation) + robogibs(loc) + + GLOB.living_mob_list -= src + GLOB.dead_mob_list -= src + QDEL_IN(animation, 15) + QDEL_IN(src, 15) + return TRUE + +/mob/living/silicon/robot/dust() + if(!death(TRUE) && stat != DEAD) + return FALSE + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + if(mmi) + qdel(mmi) //Delete the MMI first so that it won't go popping out. + GLOB.dead_mob_list -= src + QDEL_IN(src, 15) + return TRUE + +/mob/living/silicon/robot/dust_animation() + var/atom/movable/overlay/animation = null + animation = new(loc) + animation.icon_state = "blank" + animation.icon = 'icons/mob/mob.dmi' + animation.master = src + flick("dust-r", animation) + new /obj/effect/decal/remains/robot(loc) + QDEL_IN(animation, 15) + +/mob/living/silicon/robot/death(gibbed) + if(can_die()) + if(!gibbed && deathgasp_on_death) + emote("deathgasp", force = TRUE) + + if(module) + module.handle_death(gibbed) + + // Only execute the below if we successfully died + . = ..(gibbed) + if(!.) + return FALSE + + diag_hud_set_status() + diag_hud_set_health() + if(camera) + camera.status = 0 + update_headlamp(1) //So borg lights are disabled when killed. + + if(in_contents_of(/obj/machinery/recharge_station))//exit the recharge station + var/obj/machinery/recharge_station/RC = loc + RC.go_out() + + update_icons() + + sql_report_cyborg_death(src) diff --git a/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm b/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm index 44eff377f77..58605d8d89f 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_abilities.dm @@ -101,4 +101,4 @@ grabber.update_inv_l_hand() grabber.update_inv_r_hand() - return H \ No newline at end of file + return H diff --git a/code/modules/mob/living/silicon/robot/drone/drone_console.dm b/code/modules/mob/living/silicon/robot/drone/drone_console.dm index 653a70cd6ce..80d9f4d1ba3 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_console.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_console.dm @@ -124,4 +124,4 @@ dronefab.produce_drones = !dronefab.produce_drones to_chat(usr, "You [dronefab.produce_drones ? "enable" : "disable"] drone production in the nearby fabricator.") - src.updateUsrDialog() \ No newline at end of file + src.updateUsrDialog() diff --git a/code/modules/mob/living/silicon/robot/drone/drone_say.dm b/code/modules/mob/living/silicon/robot/drone/drone_say.dm index 7b51952a685..38b90480930 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_say.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_say.dm @@ -11,4 +11,4 @@ /mob/living/silicon/robot/drone/get_default_language() if(default_language) return default_language - return GLOB.all_languages["Drone"] \ No newline at end of file + return GLOB.all_languages["Drone"] diff --git a/code/modules/mob/living/silicon/robot/examine.dm b/code/modules/mob/living/silicon/robot/examine.dm index 198b687e496..69cb398df63 100644 --- a/code/modules/mob/living/silicon/robot/examine.dm +++ b/code/modules/mob/living/silicon/robot/examine.dm @@ -1,58 +1,58 @@ -/mob/living/silicon/robot/examine(mob/user) - . = ..() - - var/msg = "" - if(module) - msg += "It has loaded a [module.name].\n" - var/obj/act_module = get_active_hand() - if(act_module) - msg += "It is holding [bicon(act_module)] \a [act_module].\n" - msg += "" - if(getBruteLoss()) - if(getBruteLoss() < maxHealth*0.5) - msg += "It looks slightly dented.\n" - else - msg += "It looks severely dented!\n" - if(getFireLoss()) - if(getFireLoss() < maxHealth*0.5) - msg += "It looks slightly charred.\n" - else - msg += "It looks severely burnt and heat-warped!\n" - if(health < -maxHealth*0.5) - msg += "It looks barely operational.\n" - if(fire_stacks < 0) - msg += "It's covered in water.\n" - else if(fire_stacks > 0) - msg += "It's coated in something flammable.\n" - msg += "" - - if(opened) - msg += "Its cover is open and the power cell is [cell ? "installed" : "missing"].\n" - else - msg += "Its cover is closed[locked ? "" : ", and looks unlocked"].\n" - - if(cell && cell.charge <= 0) - msg += "Its battery indicator is blinking red!\n" - - switch(stat) - if(CONSCIOUS) - if(!client) - msg += "It appears to be in stand-by mode.\n" //afk - if(UNCONSCIOUS) - msg += "It doesn't seem to be responding.\n" - if(DEAD) - if(!suiciding) - msg += "It looks like its system is corrupted and requires a reset.\n" - else - msg += "It looks like its system is corrupted beyond repair. There is no hope of recovery.\n" - msg += "*---------*" - - if(print_flavor_text()) msg += "\n[print_flavor_text()]\n" - - if(pose) - if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) - pose = addtext(pose,".") //Makes sure all emotes end with a period. - msg += "\nIt is [pose]" - - . += msg - user.showLaws(src) \ No newline at end of file +/mob/living/silicon/robot/examine(mob/user) + . = ..() + + var/msg = "" + if(module) + msg += "It has loaded a [module.name].\n" + var/obj/act_module = get_active_hand() + if(act_module) + msg += "It is holding [bicon(act_module)] \a [act_module].\n" + msg += "" + if(getBruteLoss()) + if(getBruteLoss() < maxHealth*0.5) + msg += "It looks slightly dented.\n" + else + msg += "It looks severely dented!\n" + if(getFireLoss()) + if(getFireLoss() < maxHealth*0.5) + msg += "It looks slightly charred.\n" + else + msg += "It looks severely burnt and heat-warped!\n" + if(health < -maxHealth*0.5) + msg += "It looks barely operational.\n" + if(fire_stacks < 0) + msg += "It's covered in water.\n" + else if(fire_stacks > 0) + msg += "It's coated in something flammable.\n" + msg += "" + + if(opened) + msg += "Its cover is open and the power cell is [cell ? "installed" : "missing"].\n" + else + msg += "Its cover is closed[locked ? "" : ", and looks unlocked"].\n" + + if(cell && cell.charge <= 0) + msg += "Its battery indicator is blinking red!\n" + + switch(stat) + if(CONSCIOUS) + if(!client) + msg += "It appears to be in stand-by mode.\n" //afk + if(UNCONSCIOUS) + msg += "It doesn't seem to be responding.\n" + if(DEAD) + if(!suiciding) + msg += "It looks like its system is corrupted and requires a reset.\n" + else + msg += "It looks like its system is corrupted beyond repair. There is no hope of recovery.\n" + msg += "*---------*" + + if(print_flavor_text()) msg += "\n[print_flavor_text()]\n" + + if(pose) + if( findtext(pose,".",length(pose)) == 0 && findtext(pose,"!",length(pose)) == 0 && findtext(pose,"?",length(pose)) == 0 ) + pose = addtext(pose,".") //Makes sure all emotes end with a period. + msg += "\nIt is [pose]" + + . += msg + user.showLaws(src) diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index da0732c5ecd..16d9080ef7e 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -1,254 +1,254 @@ -//These procs handle putting s tuff in your hand. It's probably best to use these rather than setting stuff manually -//as they handle all relevant stuff like adding it to the player's screen and such - -//Returns the thing in our active hand (whatever is in our active module-slot, in this case) -/mob/living/silicon/robot/get_active_hand() - return module_active - -/mob/living/silicon/robot/get_all_slots() - return list(module_state_1, module_state_2, module_state_3) - -/*-------TODOOOOOOOOOO--------*/ -/mob/living/silicon/robot/proc/uneq_module(obj/item/O) - if(!O) - return 0 - - O.mouse_opacity = MOUSE_OPACITY_OPAQUE - - if(client) - client.screen -= O - contents -= O - if(module) - O.loc = module //Return item to module so it appears in its contents, so it can be taken out again. - for(var/X in O.actions) // Remove assocated actions - var/datum/action/A = X - A.Remove(src) - - if(module_active == O) - module_active = null - if(module_state_1 == O) - inv1.icon_state = "inv1" - module_state_1 = null - else if(module_state_2 == O) - inv2.icon_state = "inv2" - module_state_2 = null - else if(module_state_3 == O) - module_state_3 = null - inv3.icon_state = "inv3" - if(hud_used) - hud_used.update_robot_modules_display() - return 1 - -/mob/living/silicon/robot/proc/activate_module(var/obj/item/O) - if(!(locate(O) in src.module.modules) && O != src.module.emag) - return - if(activated(O)) - to_chat(src, "Already activated") - return - if(is_component_functioning("power cell") && cell) - if(istype(O, /obj/item/borg)) - var/obj/item/borg/B = O - if(B.powerneeded) - if((cell.charge * 100 / cell.maxcharge) < B.powerneeded) - to_chat(src, "Not enough power to activate [B.name]!") - return - if(!module_state_1) - O.mouse_opacity = initial(O.mouse_opacity) - module_state_1 = O - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - O.screen_loc = inv1.screen_loc - contents += O - set_actions(O) - else if(!module_state_2) - O.mouse_opacity = initial(O.mouse_opacity) - module_state_2 = O - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - O.screen_loc = inv2.screen_loc - contents += O - set_actions(O) - else if(!module_state_3) - O.mouse_opacity = initial(O.mouse_opacity) - module_state_3 = O - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - O.screen_loc = inv3.screen_loc - contents += O - set_actions(O) - else - to_chat(src, "You need to disable a module first!") - update_icons() - -/mob/living/silicon/robot/proc/set_actions(obj/item/I) - for(var/X in I.actions) - var/datum/action/A = X - A.Grant(src) - -/mob/living/silicon/robot/proc/uneq_active() - uneq_module(module_active) - -/mob/living/silicon/robot/proc/uneq_all() - uneq_module(module_state_1) - uneq_module(module_state_2) - uneq_module(module_state_3) - -/mob/living/silicon/robot/proc/uneq_numbered(var/module) - if(module < 1 || module > 3) return - - switch(module) - if(1) - uneq_module(module_state_1) - if(2) - uneq_module(module_state_2) - if(3) - uneq_module(module_state_3) - -/mob/living/silicon/robot/proc/activated(obj/item/O) - if(module_state_1 == O) - return 1 - else if(module_state_2 == O) - return 1 - else if(module_state_3 == O) - return 1 - else - return 0 - -/mob/living/silicon/robot/drop_item() - var/obj/item/I = get_active_hand() - if(istype(I, /obj/item/gripper)) - var/obj/item/gripper/G = I - G.drop_item_p(silent = 1) - return - -//Helper procs for cyborg modules on the UI. -//These are hackish but they help clean up code elsewhere. - -//module_selected(module) - Checks whether the module slot specified by "module" is currently selected. -/mob/living/silicon/robot/proc/module_selected(var/module) //Module is 1-3 - return module == get_selected_module() - -//module_active(module) - Checks whether there is a module active in the slot specified by "module". -/mob/living/silicon/robot/proc/module_active(var/module) //Module is 1-3 - if(module < 1 || module > 3) return 0 - - switch(module) - if(1) - if(module_state_1) - return 1 - if(2) - if(module_state_2) - return 1 - if(3) - if(module_state_3) - return 1 - return 0 - -//get_selected_module() - Returns the slot number of the currently selected module. Returns 0 if no modules are selected. -/mob/living/silicon/robot/proc/get_selected_module() - if(module_state_1 && module_active == module_state_1) - return 1 - else if(module_state_2 && module_active == module_state_2) - return 2 - else if(module_state_3 && module_active == module_state_3) - return 3 - - return 0 - -//select_module(module) - Selects the module slot specified by "module" -/mob/living/silicon/robot/proc/select_module(var/module) //Module is 1-3 - if(module < 1 || module > 3) return - - if(!module_active(module)) return - - switch(module) - if(1) - if(module_active != module_state_1) - inv1.icon_state = "inv1 +a" - inv2.icon_state = "inv2" - inv3.icon_state = "inv3" - module_active = module_state_1 - return - if(2) - if(module_active != module_state_2) - inv1.icon_state = "inv1" - inv2.icon_state = "inv2 +a" - inv3.icon_state = "inv3" - module_active = module_state_2 - return - if(3) - if(module_active != module_state_3) - inv1.icon_state = "inv1" - inv2.icon_state = "inv2" - inv3.icon_state = "inv3 +a" - module_active = module_state_3 - return - return - -//deselect_module(module) - Deselects the module slot specified by "module" -/mob/living/silicon/robot/proc/deselect_module(var/module) //Module is 1-3 - if(module < 1 || module > 3) return - - switch(module) - if(1) - if(module_active == module_state_1) - inv1.icon_state = "inv1" - module_active = null - return - if(2) - if(module_active == module_state_2) - inv2.icon_state = "inv2" - module_active = null - return - if(3) - if(module_active == module_state_3) - inv3.icon_state = "inv3" - module_active = null - return - return - -//toggle_module(module) - Toggles the selection of the module slot specified by "module". -/mob/living/silicon/robot/proc/toggle_module(var/module) //Module is 1-3 - if(module < 1 || module > 3) return - - if(module_selected(module)) - deselect_module(module) - else - if(module_active(module)) - select_module(module) - else - deselect_module(get_selected_module()) //If we can't do select anything, at least deselect the current module. - return - -//cycle_modules() - Cycles through the list of selected modules. -/mob/living/silicon/robot/proc/cycle_modules() - var/slot_start = get_selected_module() - if(slot_start) deselect_module(slot_start) //Only deselect if we have a selected slot. - - var/slot_num - if(slot_start == 0) - slot_num = 0 - slot_start = 3 - else - slot_num = slot_start - - do - slot_num++ - if(slot_num > 3) slot_num = 1 //Wrap around. - if(module_active(slot_num)) - select_module(slot_num) - return - while(slot_start != slot_num) //If we wrap around without finding any free slots, just give up. - - return - -/mob/living/silicon/robot/unEquip(obj/item/I, force) - if(I == module_active) - uneq_active(I) - return ..() - -/mob/living/silicon/robot/proc/update_module_icon() - if(!module) - hands.icon_state = "nomod" - else - hands.icon_state = lowertext(module.module_type) +//These procs handle putting s tuff in your hand. It's probably best to use these rather than setting stuff manually +//as they handle all relevant stuff like adding it to the player's screen and such + +//Returns the thing in our active hand (whatever is in our active module-slot, in this case) +/mob/living/silicon/robot/get_active_hand() + return module_active + +/mob/living/silicon/robot/get_all_slots() + return list(module_state_1, module_state_2, module_state_3) + +/*-------TODOOOOOOOOOO--------*/ +/mob/living/silicon/robot/proc/uneq_module(obj/item/O) + if(!O) + return 0 + + O.mouse_opacity = MOUSE_OPACITY_OPAQUE + + if(client) + client.screen -= O + contents -= O + if(module) + O.loc = module //Return item to module so it appears in its contents, so it can be taken out again. + for(var/X in O.actions) // Remove assocated actions + var/datum/action/A = X + A.Remove(src) + + if(module_active == O) + module_active = null + if(module_state_1 == O) + inv1.icon_state = "inv1" + module_state_1 = null + else if(module_state_2 == O) + inv2.icon_state = "inv2" + module_state_2 = null + else if(module_state_3 == O) + module_state_3 = null + inv3.icon_state = "inv3" + if(hud_used) + hud_used.update_robot_modules_display() + return 1 + +/mob/living/silicon/robot/proc/activate_module(var/obj/item/O) + if(!(locate(O) in src.module.modules) && O != src.module.emag) + return + if(activated(O)) + to_chat(src, "Already activated") + return + if(is_component_functioning("power cell") && cell) + if(istype(O, /obj/item/borg)) + var/obj/item/borg/B = O + if(B.powerneeded) + if((cell.charge * 100 / cell.maxcharge) < B.powerneeded) + to_chat(src, "Not enough power to activate [B.name]!") + return + if(!module_state_1) + O.mouse_opacity = initial(O.mouse_opacity) + module_state_1 = O + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + O.screen_loc = inv1.screen_loc + contents += O + set_actions(O) + else if(!module_state_2) + O.mouse_opacity = initial(O.mouse_opacity) + module_state_2 = O + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + O.screen_loc = inv2.screen_loc + contents += O + set_actions(O) + else if(!module_state_3) + O.mouse_opacity = initial(O.mouse_opacity) + module_state_3 = O + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + O.screen_loc = inv3.screen_loc + contents += O + set_actions(O) + else + to_chat(src, "You need to disable a module first!") + update_icons() + +/mob/living/silicon/robot/proc/set_actions(obj/item/I) + for(var/X in I.actions) + var/datum/action/A = X + A.Grant(src) + +/mob/living/silicon/robot/proc/uneq_active() + uneq_module(module_active) + +/mob/living/silicon/robot/proc/uneq_all() + uneq_module(module_state_1) + uneq_module(module_state_2) + uneq_module(module_state_3) + +/mob/living/silicon/robot/proc/uneq_numbered(var/module) + if(module < 1 || module > 3) return + + switch(module) + if(1) + uneq_module(module_state_1) + if(2) + uneq_module(module_state_2) + if(3) + uneq_module(module_state_3) + +/mob/living/silicon/robot/proc/activated(obj/item/O) + if(module_state_1 == O) + return 1 + else if(module_state_2 == O) + return 1 + else if(module_state_3 == O) + return 1 + else + return 0 + +/mob/living/silicon/robot/drop_item() + var/obj/item/I = get_active_hand() + if(istype(I, /obj/item/gripper)) + var/obj/item/gripper/G = I + G.drop_item_p(silent = 1) + return + +//Helper procs for cyborg modules on the UI. +//These are hackish but they help clean up code elsewhere. + +//module_selected(module) - Checks whether the module slot specified by "module" is currently selected. +/mob/living/silicon/robot/proc/module_selected(var/module) //Module is 1-3 + return module == get_selected_module() + +//module_active(module) - Checks whether there is a module active in the slot specified by "module". +/mob/living/silicon/robot/proc/module_active(var/module) //Module is 1-3 + if(module < 1 || module > 3) return 0 + + switch(module) + if(1) + if(module_state_1) + return 1 + if(2) + if(module_state_2) + return 1 + if(3) + if(module_state_3) + return 1 + return 0 + +//get_selected_module() - Returns the slot number of the currently selected module. Returns 0 if no modules are selected. +/mob/living/silicon/robot/proc/get_selected_module() + if(module_state_1 && module_active == module_state_1) + return 1 + else if(module_state_2 && module_active == module_state_2) + return 2 + else if(module_state_3 && module_active == module_state_3) + return 3 + + return 0 + +//select_module(module) - Selects the module slot specified by "module" +/mob/living/silicon/robot/proc/select_module(var/module) //Module is 1-3 + if(module < 1 || module > 3) return + + if(!module_active(module)) return + + switch(module) + if(1) + if(module_active != module_state_1) + inv1.icon_state = "inv1 +a" + inv2.icon_state = "inv2" + inv3.icon_state = "inv3" + module_active = module_state_1 + return + if(2) + if(module_active != module_state_2) + inv1.icon_state = "inv1" + inv2.icon_state = "inv2 +a" + inv3.icon_state = "inv3" + module_active = module_state_2 + return + if(3) + if(module_active != module_state_3) + inv1.icon_state = "inv1" + inv2.icon_state = "inv2" + inv3.icon_state = "inv3 +a" + module_active = module_state_3 + return + return + +//deselect_module(module) - Deselects the module slot specified by "module" +/mob/living/silicon/robot/proc/deselect_module(var/module) //Module is 1-3 + if(module < 1 || module > 3) return + + switch(module) + if(1) + if(module_active == module_state_1) + inv1.icon_state = "inv1" + module_active = null + return + if(2) + if(module_active == module_state_2) + inv2.icon_state = "inv2" + module_active = null + return + if(3) + if(module_active == module_state_3) + inv3.icon_state = "inv3" + module_active = null + return + return + +//toggle_module(module) - Toggles the selection of the module slot specified by "module". +/mob/living/silicon/robot/proc/toggle_module(var/module) //Module is 1-3 + if(module < 1 || module > 3) return + + if(module_selected(module)) + deselect_module(module) + else + if(module_active(module)) + select_module(module) + else + deselect_module(get_selected_module()) //If we can't do select anything, at least deselect the current module. + return + +//cycle_modules() - Cycles through the list of selected modules. +/mob/living/silicon/robot/proc/cycle_modules() + var/slot_start = get_selected_module() + if(slot_start) deselect_module(slot_start) //Only deselect if we have a selected slot. + + var/slot_num + if(slot_start == 0) + slot_num = 0 + slot_start = 3 + else + slot_num = slot_start + + do + slot_num++ + if(slot_num > 3) slot_num = 1 //Wrap around. + if(module_active(slot_num)) + select_module(slot_num) + return + while(slot_start != slot_num) //If we wrap around without finding any free slots, just give up. + + return + +/mob/living/silicon/robot/unEquip(obj/item/I, force) + if(I == module_active) + uneq_active(I) + return ..() + +/mob/living/silicon/robot/proc/update_module_icon() + if(!module) + hands.icon_state = "nomod" + else + hands.icon_state = lowertext(module.module_type) diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm index 0398172023c..25cdc61fa28 100644 --- a/code/modules/mob/living/silicon/robot/laws.dm +++ b/code/modules/mob/living/silicon/robot/laws.dm @@ -1,54 +1,54 @@ -/mob/living/silicon/robot/verb/cmd_show_laws() - set category = "Robot Commands" - set name = "Show Laws" - show_laws() - -/mob/living/silicon/robot/show_laws(var/everyone = 0) - laws_sanity_check() - var/who - - if(everyone) - who = world - else - who = src - if(lawupdate) - if(connected_ai) - if(connected_ai.stat || connected_ai.control_disabled) - to_chat(src, "AI signal lost, unable to sync laws.") - - else - lawsync() - photosync() - to_chat(src, "Laws synced with AI, be sure to note any changes.") - // TODO: Update to new antagonist system. - if(mind && mind.special_role == SPECIAL_ROLE_TRAITOR && mind.original == src) - to_chat(src, "Remember, your AI does NOT share or know about your law 0.") - else - to_chat(src, "No AI selected to sync laws with, disabling lawsync protocol.") - lawupdate = 0 - - to_chat(who, "Obey these laws:") - laws.show_laws(who) - // TODO: Update to new antagonist system. - if(mind && (mind.special_role == SPECIAL_ROLE_TRAITOR && mind.original == src) && connected_ai) - to_chat(who, "Remember, [connected_ai.name] is technically your master, but your objective comes first.") - else if(connected_ai) - to_chat(who, "Remember, [connected_ai.name] is your master, other AIs can be ignored.") - else if(emagged) - to_chat(who, "Remember, you are not required to listen to the AI.") - else - to_chat(who, "Remember, you are not bound to any AI, you are not required to listen to them.") - - -/mob/living/silicon/robot/lawsync() - laws_sanity_check() - var/datum/ai_laws/master = connected_ai && lawupdate ? connected_ai.laws : null - if(master) - master.sync(src) - ..() - return - -/mob/living/silicon/robot/proc/robot_checklaws() - set category = "Robot Commands" - set name = "State Laws" - subsystem_law_manager() +/mob/living/silicon/robot/verb/cmd_show_laws() + set category = "Robot Commands" + set name = "Show Laws" + show_laws() + +/mob/living/silicon/robot/show_laws(var/everyone = 0) + laws_sanity_check() + var/who + + if(everyone) + who = world + else + who = src + if(lawupdate) + if(connected_ai) + if(connected_ai.stat || connected_ai.control_disabled) + to_chat(src, "AI signal lost, unable to sync laws.") + + else + lawsync() + photosync() + to_chat(src, "Laws synced with AI, be sure to note any changes.") + // TODO: Update to new antagonist system. + if(mind && mind.special_role == SPECIAL_ROLE_TRAITOR && mind.original == src) + to_chat(src, "Remember, your AI does NOT share or know about your law 0.") + else + to_chat(src, "No AI selected to sync laws with, disabling lawsync protocol.") + lawupdate = 0 + + to_chat(who, "Obey these laws:") + laws.show_laws(who) + // TODO: Update to new antagonist system. + if(mind && (mind.special_role == SPECIAL_ROLE_TRAITOR && mind.original == src) && connected_ai) + to_chat(who, "Remember, [connected_ai.name] is technically your master, but your objective comes first.") + else if(connected_ai) + to_chat(who, "Remember, [connected_ai.name] is your master, other AIs can be ignored.") + else if(emagged) + to_chat(who, "Remember, you are not required to listen to the AI.") + else + to_chat(who, "Remember, you are not bound to any AI, you are not required to listen to them.") + + +/mob/living/silicon/robot/lawsync() + laws_sanity_check() + var/datum/ai_laws/master = connected_ai && lawupdate ? connected_ai.laws : null + if(master) + master.sync(src) + ..() + return + +/mob/living/silicon/robot/proc/robot_checklaws() + set category = "Robot Commands" + set name = "State Laws" + subsystem_law_manager() diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index 258ccec026a..d40e801ba5f 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -1,203 +1,203 @@ -/mob/living/silicon/robot/Life(seconds, times_fired) - set invisibility = 0 - set background = BACKGROUND_ENABLED - - if(src.notransform) - return - - //Status updates, death etc. - clamp_values() - - if(..()) - handle_robot_cell() - process_locks() - process_queued_alarms() - -/mob/living/silicon/robot/proc/clamp_values() - SetStunned(min(stunned, 30)) - SetParalysis(min(paralysis, 30)) - SetWeakened(min(weakened, 20)) - SetSleeping(0) - -/mob/living/silicon/robot/proc/handle_robot_cell() - if(stat != DEAD) - if(!is_component_functioning("power cell")) - uneq_all() - low_power_mode = 1 - update_headlamp() - diag_hud_set_borgcell() - return - if(low_power_mode) - if(is_component_functioning("power cell") && cell.charge) - low_power_mode = 0 - update_headlamp() - else if(stat == CONSCIOUS) - use_power() - -/mob/living/silicon/robot/proc/use_power() - // this check is safe because `cell` is guaranteed to be set when the power cell is functioning - if(is_component_functioning("power cell") && cell.charge) - if(cell.charge <= 100) - uneq_all() - var/amt = Clamp((lamp_intensity - 2) * 2,1,cell.charge) //Always try to use at least one charge per tick, but allow it to completely drain the cell. - cell.use(amt) //Usage table: 1/tick if off/lowest setting, 4 = 4/tick, 6 = 8/tick, 8 = 12/tick, 10 = 16/tick - else - uneq_all() - low_power_mode = 1 - update_headlamp() - diag_hud_set_borgcell() - -/mob/living/silicon/robot/handle_regular_status_updates() - - . = ..() - - if(camera && !scrambledcodes) - if(stat == DEAD || wires.IsCameraCut()) - camera.status = 0 - else - camera.status = 1 - - if(sleeping) - AdjustSleeping(-1) - - if(.) //alive - if(!istype(src, /mob/living/silicon/robot/drone)) - if(health < 50) //Gradual break down of modules as more damage is sustained - if(uneq_module(module_state_3)) - to_chat(src, "SYSTEM ERROR: Module 3 OFFLINE.") - - if(health < 0) - if(uneq_module(module_state_2)) - to_chat(src, "SYSTEM ERROR: Module 2 OFFLINE.") - - if(health < -50) - if(uneq_module(module_state_1)) - to_chat(src, "CRITICAL ERROR: All modules OFFLINE.") - - diag_hud_set_health() - diag_hud_set_status() - - //update the state of modules and components here - if(stat != CONSCIOUS) - uneq_all() - - if(!is_component_functioning("radio") || stat == UNCONSCIOUS) - radio.on = 0 - else - radio.on = 1 - - return 1 - -/mob/living/silicon/robot/handle_hud_icons() - update_items() - update_cell() - if(emagged) - throw_alert("hacked", /obj/screen/alert/hacked) - else - clear_alert("hacked") - ..() - -/mob/living/silicon/robot/handle_hud_icons_health() - if(healths) - if(stat != DEAD) - if(health >= maxHealth) - healths.icon_state = "health0" - else if(health > maxHealth * 0.5) - healths.icon_state = "health2" - else if(health > 0) - healths.icon_state = "health3" - else if(health > -maxHealth * 0.5) - healths.icon_state = "health4" - else if(health > -maxHealth) - healths.icon_state = "health5" - else - healths.icon_state = "health6" - else - healths.icon_state = "health7" - - switch(bodytemperature) //310.055 optimal body temp - if(335 to INFINITY) - throw_alert("temp", /obj/screen/alert/hot/robot, 2) - if(320 to 335) - throw_alert("temp", /obj/screen/alert/hot/robot, 1) - if(300 to 320) - clear_alert("temp") - if(260 to 300) - throw_alert("temp", /obj/screen/alert/cold/robot, 1) - else - throw_alert("temp", /obj/screen/alert/cold/robot, 2) - -/mob/living/silicon/robot/proc/update_cell() - if(cell) - var/cellcharge = cell.charge/cell.maxcharge - switch(cellcharge) - if(0.75 to INFINITY) - clear_alert("charge") - if(0.5 to 0.75) - throw_alert("charge", /obj/screen/alert/lowcell, 1) - if(0.25 to 0.5) - throw_alert("charge", /obj/screen/alert/lowcell, 2) - if(0.01 to 0.25) - throw_alert("charge", /obj/screen/alert/lowcell, 3) - else - throw_alert("charge", /obj/screen/alert/emptycell) - else - throw_alert("charge", /obj/screen/alert/nocell) - - - -/mob/living/silicon/robot/proc/update_items() - if(client) - for(var/obj/I in get_all_slots()) - client.screen |= I - if(module_state_1) - module_state_1:screen_loc = ui_inv1 - if(module_state_2) - module_state_2:screen_loc = ui_inv2 - if(module_state_3) - module_state_3:screen_loc = ui_inv3 - update_icons() - -/mob/living/silicon/robot/proc/process_locks() - if(weapon_lock) - uneq_all() - weaponlock_time -- - if(weaponlock_time <= 0) - if(src.client) - to_chat(src, "Weapon Lock Timed Out!") - weapon_lock = 0 - weaponlock_time = 120 - -/mob/living/silicon/robot/update_canmove(delay_action_updates = 0) - if(paralysis || stunned || IsWeakened() || buckled || lockcharge || stat) - canmove = 0 - else - canmove = 1 - update_transform() - if(!delay_action_updates) - update_action_buttons_icon() - return canmove - -//Robots on fire -/mob/living/silicon/robot/handle_fire() - if(..()) - return - if(fire_stacks > 0) - fire_stacks-- - fire_stacks = max(0, fire_stacks) - else - ExtinguishMob() - - //adjustFireLoss(3) - return - -/mob/living/silicon/robot/update_fire() - overlays -= image("icon"='icons/mob/OnFire.dmi', "icon_state"="Generic_mob_burning") - if(on_fire) - overlays += image("icon"='icons/mob/OnFire.dmi', "icon_state"="Generic_mob_burning") - -/mob/living/silicon/robot/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - if(!on_fire) //Silicons don't gain stacks from hotspots, but hotspots can ignite them - IgniteMob() - -//Robots on fire +/mob/living/silicon/robot/Life(seconds, times_fired) + set invisibility = 0 + set background = BACKGROUND_ENABLED + + if(src.notransform) + return + + //Status updates, death etc. + clamp_values() + + if(..()) + handle_robot_cell() + process_locks() + process_queued_alarms() + +/mob/living/silicon/robot/proc/clamp_values() + SetStunned(min(stunned, 30)) + SetParalysis(min(paralysis, 30)) + SetWeakened(min(weakened, 20)) + SetSleeping(0) + +/mob/living/silicon/robot/proc/handle_robot_cell() + if(stat != DEAD) + if(!is_component_functioning("power cell")) + uneq_all() + low_power_mode = 1 + update_headlamp() + diag_hud_set_borgcell() + return + if(low_power_mode) + if(is_component_functioning("power cell") && cell.charge) + low_power_mode = 0 + update_headlamp() + else if(stat == CONSCIOUS) + use_power() + +/mob/living/silicon/robot/proc/use_power() + // this check is safe because `cell` is guaranteed to be set when the power cell is functioning + if(is_component_functioning("power cell") && cell.charge) + if(cell.charge <= 100) + uneq_all() + var/amt = Clamp((lamp_intensity - 2) * 2,1,cell.charge) //Always try to use at least one charge per tick, but allow it to completely drain the cell. + cell.use(amt) //Usage table: 1/tick if off/lowest setting, 4 = 4/tick, 6 = 8/tick, 8 = 12/tick, 10 = 16/tick + else + uneq_all() + low_power_mode = 1 + update_headlamp() + diag_hud_set_borgcell() + +/mob/living/silicon/robot/handle_regular_status_updates() + + . = ..() + + if(camera && !scrambledcodes) + if(stat == DEAD || wires.IsCameraCut()) + camera.status = 0 + else + camera.status = 1 + + if(sleeping) + AdjustSleeping(-1) + + if(.) //alive + if(!istype(src, /mob/living/silicon/robot/drone)) + if(health < 50) //Gradual break down of modules as more damage is sustained + if(uneq_module(module_state_3)) + to_chat(src, "SYSTEM ERROR: Module 3 OFFLINE.") + + if(health < 0) + if(uneq_module(module_state_2)) + to_chat(src, "SYSTEM ERROR: Module 2 OFFLINE.") + + if(health < -50) + if(uneq_module(module_state_1)) + to_chat(src, "CRITICAL ERROR: All modules OFFLINE.") + + diag_hud_set_health() + diag_hud_set_status() + + //update the state of modules and components here + if(stat != CONSCIOUS) + uneq_all() + + if(!is_component_functioning("radio") || stat == UNCONSCIOUS) + radio.on = 0 + else + radio.on = 1 + + return 1 + +/mob/living/silicon/robot/handle_hud_icons() + update_items() + update_cell() + if(emagged) + throw_alert("hacked", /obj/screen/alert/hacked) + else + clear_alert("hacked") + ..() + +/mob/living/silicon/robot/handle_hud_icons_health() + if(healths) + if(stat != DEAD) + if(health >= maxHealth) + healths.icon_state = "health0" + else if(health > maxHealth * 0.5) + healths.icon_state = "health2" + else if(health > 0) + healths.icon_state = "health3" + else if(health > -maxHealth * 0.5) + healths.icon_state = "health4" + else if(health > -maxHealth) + healths.icon_state = "health5" + else + healths.icon_state = "health6" + else + healths.icon_state = "health7" + + switch(bodytemperature) //310.055 optimal body temp + if(335 to INFINITY) + throw_alert("temp", /obj/screen/alert/hot/robot, 2) + if(320 to 335) + throw_alert("temp", /obj/screen/alert/hot/robot, 1) + if(300 to 320) + clear_alert("temp") + if(260 to 300) + throw_alert("temp", /obj/screen/alert/cold/robot, 1) + else + throw_alert("temp", /obj/screen/alert/cold/robot, 2) + +/mob/living/silicon/robot/proc/update_cell() + if(cell) + var/cellcharge = cell.charge/cell.maxcharge + switch(cellcharge) + if(0.75 to INFINITY) + clear_alert("charge") + if(0.5 to 0.75) + throw_alert("charge", /obj/screen/alert/lowcell, 1) + if(0.25 to 0.5) + throw_alert("charge", /obj/screen/alert/lowcell, 2) + if(0.01 to 0.25) + throw_alert("charge", /obj/screen/alert/lowcell, 3) + else + throw_alert("charge", /obj/screen/alert/emptycell) + else + throw_alert("charge", /obj/screen/alert/nocell) + + + +/mob/living/silicon/robot/proc/update_items() + if(client) + for(var/obj/I in get_all_slots()) + client.screen |= I + if(module_state_1) + module_state_1:screen_loc = ui_inv1 + if(module_state_2) + module_state_2:screen_loc = ui_inv2 + if(module_state_3) + module_state_3:screen_loc = ui_inv3 + update_icons() + +/mob/living/silicon/robot/proc/process_locks() + if(weapon_lock) + uneq_all() + weaponlock_time -- + if(weaponlock_time <= 0) + if(src.client) + to_chat(src, "Weapon Lock Timed Out!") + weapon_lock = 0 + weaponlock_time = 120 + +/mob/living/silicon/robot/update_canmove(delay_action_updates = 0) + if(paralysis || stunned || IsWeakened() || buckled || lockcharge || stat) + canmove = 0 + else + canmove = 1 + update_transform() + if(!delay_action_updates) + update_action_buttons_icon() + return canmove + +//Robots on fire +/mob/living/silicon/robot/handle_fire() + if(..()) + return + if(fire_stacks > 0) + fire_stacks-- + fire_stacks = max(0, fire_stacks) + else + ExtinguishMob() + + //adjustFireLoss(3) + return + +/mob/living/silicon/robot/update_fire() + overlays -= image("icon"='icons/mob/OnFire.dmi', "icon_state"="Generic_mob_burning") + if(on_fire) + overlays += image("icon"='icons/mob/OnFire.dmi', "icon_state"="Generic_mob_burning") + +/mob/living/silicon/robot/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + if(!on_fire) //Silicons don't gain stacks from hotspots, but hotspots can ignite them + IgniteMob() + +//Robots on fire diff --git a/code/modules/mob/living/silicon/robot/login.dm b/code/modules/mob/living/silicon/robot/login.dm index cf55cdd5f91..0a3225011d8 100644 --- a/code/modules/mob/living/silicon/robot/login.dm +++ b/code/modules/mob/living/silicon/robot/login.dm @@ -1,4 +1,4 @@ -/mob/living/silicon/robot/Login() - ..() - regenerate_icons() - show_laws(0) +/mob/living/silicon/robot/Login() + ..() + regenerate_icons() + show_laws(0) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index ae9f1d8a42d..f0a49b47af1 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1,1474 +1,1474 @@ -var/list/robot_verbs_default = list( - /mob/living/silicon/robot/proc/sensor_mode, -) - -/mob/living/silicon/robot - name = "Cyborg" - real_name = "Cyborg" - icon = 'icons/mob/robots.dmi' - icon_state = "robot" - maxHealth = 100 - health = 100 - universal_understand = 1 - deathgasp_on_death = TRUE - - var/sight_mode = 0 - var/custom_name = "" - var/custom_sprite = 0 //Due to all the sprites involved, a var for our custom borgs may be best - -//Hud stuff - - var/obj/screen/inv1 = null - var/obj/screen/inv2 = null - var/obj/screen/inv3 = null - var/obj/screen/lamp_button = null - var/obj/screen/thruster_button = null - - var/shown_robot_modules = 0 //Used to determine whether they have the module menu shown or not - var/obj/screen/robot_modules_background - -//3 Modules can be activated at any one time. - var/obj/item/robot_module/module = null - var/module_active = null - var/module_state_1 = null - var/module_state_2 = null - var/module_state_3 = null - - var/obj/item/radio/borg/radio = null - var/mob/living/silicon/ai/connected_ai = null - var/obj/item/stock_parts/cell/cell = null - var/obj/machinery/camera/camera = null - - // Components are basically robot organs. - var/list/components = list() - - var/obj/item/robot_parts/robot_suit/robot_suit = null //Used for deconstruction to remember what the borg was constructed out of.. - var/obj/item/mmi/mmi = null - - var/obj/item/pda/silicon/robot/rbPDA = null - - var/datum/wires/robot/wires = null - - var/opened = 0 - var/custom_panel = null - var/list/custom_panel_names = list("Cricket") - var/list/custom_eye_names = list("Cricket","Standard") - var/emagged = 0 - var/is_emaggable = TRUE - var/eye_protection = 0 - var/ear_protection = 0 - - var/list/force_modules = list() - var/allow_rename = TRUE - var/weapons_unlock = FALSE - var/static_radio_channels = FALSE - - var/wiresexposed = 0 - var/locked = 1 - var/list/req_one_access = list(ACCESS_ROBOTICS) - var/list/req_access - var/ident = 0 - //var/list/laws = list() - var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list()) - var/viewalerts = 0 - var/modtype = "Default" - var/lower_mod = 0 - var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N - var/jeton = 0 - var/low_power_mode = 0 //whether the robot has no charge left. - var/weapon_lock = 0 - var/weaponlock_time = 120 - var/lawupdate = 1 //Cyborgs will sync their laws with their AI by default - var/lockcharge //Used when locking down a borg to preserve cell charge - var/speed = 0 //Cause sec borgs gotta go fast //No they dont! - var/scrambledcodes = 0 // Used to determine if a borg shows up on the robotics console. Setting to one hides them. - var/pdahide = 0 //Used to hide the borg from the messenger list - var/tracking_entities = 0 //The number of known entities currently accessing the internal camera - var/braintype = "Cyborg" - var/base_icon = "" - var/crisis = 0 - - var/lamp_max = 10 //Maximum brightness of a borg lamp. Set as a var for easy adjusting. - var/lamp_intensity = 0 //Luminosity of the headlamp. 0 is off. Higher settings than the minimum require power. - var/lamp_recharging = 0 //Flag for if the lamp is on cooldown after being forcibly disabled. - - var/updating = 0 //portable camera camerachunk update - - hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD) - - var/magpulse = 0 - var/ionpulse = 0 // Jetpack-like effect. - var/ionpulse_on = 0 // Jetpack-like effect. - var/datum/effect_system/trail_follow/ion/ion_trail // Ionpulse effect. - - var/datum/action/item_action/toggle_research_scanner/scanner = null - var/list/module_actions = list() - -/mob/living/silicon/robot/get_cell() - return cell - -/mob/living/silicon/robot/New(loc,var/syndie = 0,var/unfinished = 0, var/alien = 0) - spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) - - add_language("Robot Talk", 1) - - wires = new(src) - - robot_modules_background = new() - robot_modules_background.icon_state = "block" - robot_modules_background.layer = HUD_LAYER //Objects that appear on screen are on layer 20, UI should be just below it. - robot_modules_background.plane = HUD_PLANE - - ident = rand(1, 999) - rename_character(null, get_default_name()) - update_icons() - update_headlamp() - - radio = new /obj/item/radio/borg(src) - common_radio = radio - - init() - - if(!scrambledcodes && !camera) - camera = new /obj/machinery/camera(src) - camera.c_tag = real_name - camera.network = list("SS13","Robots") - if(wires.IsCameraCut()) // 5 = BORG CAMERA - camera.status = 0 - - if(mmi == null) - mmi = new /obj/item/mmi/robotic_brain(src) //Give the borg an MMI if he spawns without for some reason. (probably not the correct way to spawn a robotic brain, but it works) - mmi.icon_state = "boris" - - if(!cell) // Make sure a new cell gets created *before* executing initialize_components(). The cell component needs an existing cell for it to get set up properly - cell = new /obj/item/stock_parts/cell/high(src) - - initialize_components() - //if(!unfinished) - // Create all the robot parts. - for(var/V in components) if(V != "power cell") - var/datum/robot_component/C = components[V] - C.installed = 1 - C.wrapped = new C.external_type - - ..() - - add_robot_verbs() - - if(cell) - var/datum/robot_component/cell_component = components["power cell"] - cell_component.wrapped = cell - cell_component.installed = 1 - cell_component.install() - - diag_hud_set_borgcell() - scanner = new(src) - scanner.Grant(src) - -/mob/living/silicon/robot/proc/init(var/alien=0) - aiCamera = new/obj/item/camera/siliconcam/robot_camera(src) - make_laws() - additional_law_channels["Binary"] = ":b " - var/new_ai = select_active_ai_with_fewest_borgs() - if(new_ai) - lawupdate = 1 - connect_to_ai(new_ai) - else - lawupdate = 0 - - playsound(loc, 'sound/voice/liveagain.ogg', 75, 1) - -/mob/living/silicon/robot/rename_character(oldname, newname) - if(!..(oldname, newname)) - return 0 - - if(oldname != real_name) - notify_ai(3, oldname, newname) - custom_name = (newname != get_default_name()) ? newname : null - setup_PDA() - - //We also need to update name of internal camera. - if(camera) - camera.c_tag = newname - - //Check for custom sprite - if(!custom_sprite) - var/file = file2text("config/custom_sprites.txt") - var/lines = splittext(file, "\n") - - for(var/line in lines) - // split & clean up - var/list/Entry = splittext(line, ":") - for(var/i = 1 to Entry.len) - Entry[i] = trim(Entry[i]) - - if(Entry.len < 2 || Entry[1] != "cyborg") //ignore incorrectly formatted entries or entries that aren't marked for cyborg - continue - - if(Entry[2] == ckey) //They're in the list? Custom sprite time, var and icon change required - custom_sprite = 1 - - return 1 - - -/mob/living/silicon/robot/proc/get_default_name(var/prefix as text) - if(prefix) - modtype = prefix - if(mmi) - if(istype(mmi, /obj/item/mmi/robotic_brain)) - braintype = "Android" - else - braintype = "Cyborg" - else - braintype = "Robot" - - if(custom_name) - return custom_name - else - return "[modtype] [braintype]-[num2text(ident)]" - -/mob/living/silicon/robot/verb/Namepick() - set category = "Robot Commands" - if(custom_name) - return 0 - if(!allow_rename) - to_chat(src, "Rename functionality is not enabled on this unit."); - return 0 - rename_self(braintype, 1) - -/mob/living/silicon/robot/proc/sync() - if(lawupdate && connected_ai) - lawsync() - photosync() - -// setup the PDA and its name -/mob/living/silicon/robot/proc/setup_PDA() - if(!rbPDA) - rbPDA = new(src) - rbPDA.set_name_and_job(real_name, braintype) - var/datum/data/pda/app/messenger/M = rbPDA.find_program(/datum/data/pda/app/messenger) - if(M) - if(scrambledcodes) - M.hidden = 1 - if(pdahide) - M.toff = 1 - -/mob/living/silicon/robot/binarycheck() - if(is_component_functioning("comms")) - return 1 - return 0 - -//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO -//Improved /N -/mob/living/silicon/robot/Destroy() - if(mmi && mind)//Safety for when a cyborg gets dust()ed. Or there is no MMI inside. - var/turf/T = get_turf(loc)//To hopefully prevent run time errors. - if(T) mmi.loc = T - if(mmi.brainmob) - mind.transfer_to(mmi.brainmob) - mmi.update_icon() - else - to_chat(src, "Oops! Something went very wrong, your MMI was unable to receive your mind. You have been ghosted. Please make a bug report so we can fix this bug.") - ghostize() - error("A borg has been destroyed, but its MMI lacked a brainmob, so the mind could not be transferred. Player: [ckey].") - mmi = null - if(connected_ai) - connected_ai.connected_robots -= src - QDEL_NULL(wires) - QDEL_NULL(module) - QDEL_NULL(camera) - QDEL_NULL(cell) - QDEL_NULL(robot_suit) - QDEL_NULL(spark_system) - return ..() - -/mob/living/silicon/robot/proc/pick_module() - if(module) - return - var/list/modules = list("Standard", "Engineering", "Medical", "Miner", "Janitor", "Service", "Security") - if(islist(force_modules) && force_modules.len) - modules = force_modules.Copy() - if(security_level == (SEC_LEVEL_GAMMA || SEC_LEVEL_EPSILON) || crisis) - to_chat(src, "Crisis mode active. The combat module is now available.") - modules += "Combat" - if(mmi != null && mmi.alien) - modules = list("Hunter") - modtype = input("Please, select a module!", "Robot", null, null) as null|anything in modules - if(!modtype) - return - designation = modtype - var/module_sprites[0] //Used to store the associations between sprite names and sprite index. - - if(module) - return - - switch(modtype) - if("Standard") - module = new /obj/item/robot_module/standard(src) - module.channels = list("Service" = 1) - module_sprites["Basic"] = "robot_old" - module_sprites["Android"] = "droid" - module_sprites["Default"] = "Standard" - module_sprites["Noble-STD"] = "Noble-STD" - - if("Service") - module = new /obj/item/robot_module/butler(src) - module.channels = list("Service" = 1) - module_sprites["Waitress"] = "Service" - module_sprites["Kent"] = "toiletbot" - module_sprites["Bro"] = "Brobot" - module_sprites["Rich"] = "maximillion" - module_sprites["Default"] = "Service2" - module_sprites["Standard"] = "Standard-Serv" - module_sprites["Noble-SRV"] = "Noble-SRV" - module_sprites["Cricket"] = "Cricket-SERV" - - if("Miner") - module = new /obj/item/robot_module/miner(src) - module.channels = list("Supply" = 1) - if(camera && "Robots" in camera.network) - camera.network.Add("Mining Outpost") - module_sprites["Basic"] = "Miner_old" - module_sprites["Advanced Droid"] = "droid-miner" - module_sprites["Treadhead"] = "Miner" - module_sprites["Standard"] = "Standard-Mine" - module_sprites["Noble-DIG"] = "Noble-DIG" - module_sprites["Cricket"] = "Cricket-MINE" - - if("Medical") - module = new /obj/item/robot_module/medical(src) - module.channels = list("Medical" = 1) - if(camera && "Robots" in camera.network) - camera.network.Add("Medical") - module_sprites["Basic"] = "Medbot" - module_sprites["Surgeon"] = "surgeon" - module_sprites["Advanced Droid"] = "droid-medical" - module_sprites["Needles"] = "medicalrobot" - module_sprites["Standard"] = "Standard-Medi" - module_sprites["Noble-MED"] = "Noble-MED" - module_sprites["Cricket"] = "Cricket-MEDI" - status_flags &= ~CANPUSH - - if("Security") - module = new /obj/item/robot_module/security(src) - module.channels = list("Security" = 1) - module_sprites["Basic"] = "secborg" - module_sprites["Red Knight"] = "Security" - module_sprites["Black Knight"] = "securityrobot" - module_sprites["Bloodhound"] = "bloodhound" - module_sprites["Standard"] = "Standard-Secy" - module_sprites["Noble-SEC"] = "Noble-SEC" - module_sprites["Cricket"] = "Cricket-SEC" - status_flags &= ~CANPUSH - - if("Engineering") - module = new /obj/item/robot_module/engineering(src) - module.channels = list("Engineering" = 1) - if(camera && "Robots" in camera.network) - camera.network.Add("Engineering") - module_sprites["Basic"] = "Engineering" - module_sprites["Antique"] = "engineerrobot" - module_sprites["Landmate"] = "landmate" - module_sprites["Standard"] = "Standard-Engi" - module_sprites["Noble-ENG"] = "Noble-ENG" - module_sprites["Cricket"] = "Cricket-ENGI" - magpulse = 1 - - if("Janitor") - module = new /obj/item/robot_module/janitor(src) - module.channels = list("Service" = 1) - module_sprites["Basic"] = "JanBot2" - module_sprites["Mopbot"] = "janitorrobot" - module_sprites["Mop Gear Rex"] = "mopgearrex" - module_sprites["Standard"] = "Standard-Jani" - module_sprites["Noble-CLN"] = "Noble-CLN" - module_sprites["Cricket"] = "Cricket-JANI" - - if("Combat") - module = new /obj/item/robot_module/combat(src) - module.channels = list("Security" = 1) - icon_state = "droidcombat" - - if("Hunter") - module = new /obj/item/robot_module/alien/hunter(src) - icon_state = "xenoborg-state-a" - modtype = "Xeno-Hu" - feedback_inc("xeborg_hunter",1) - - - //languages - module.add_languages(src) - //subsystems - module.add_subsystems_and_actions(src) - - //Custom_sprite check and entry - if(custom_sprite && check_sprite("[ckey]-[modtype]")) - module_sprites["Custom"] = "[src.ckey]-[modtype]" - - hands.icon_state = lowertext(module.module_type) - feedback_inc("cyborg_[lowertext(modtype)]",1) - rename_character(real_name, get_default_name()) - - if(modtype == "Medical" || modtype == "Security" || modtype == "Combat") - status_flags &= ~CANPUSH - - choose_icon(6,module_sprites) - if(!static_radio_channels) - radio.config(module.channels) - notify_ai(2) - -/mob/living/silicon/robot/proc/reset_module() - notify_ai(2) - - uneq_all() - sight_mode = null - hands.icon_state = "nomod" - icon_state = "robot" - module.remove_subsystems_and_actions(src) - QDEL_NULL(module) - - camera.network.Remove(list("Engineering", "Medical", "Mining Outpost")) - rename_character(real_name, get_default_name("Default")) - languages = list() - speech_synthesizer_langs = list() - - update_icons() - update_headlamp() - - speed = 0 // Remove upgrades. - ionpulse = FALSE - magpulse = FALSE - add_language("Robot Talk", 1) - - status_flags |= CANPUSH - -//for borg hotkeys, here module refers to borg inv slot, not core module -/mob/living/silicon/robot/verb/cmd_toggle_module(module as num) - set name = "Toggle Module" - set hidden = 1 - toggle_module(module) - -/mob/living/silicon/robot/verb/cmd_unequip_module() - set name = "Unequip Module" - set hidden = 1 - uneq_active() - -// this verb lets cyborgs see the stations manifest -/mob/living/silicon/robot/verb/cmd_station_manifest() - set category = "Robot Commands" - set name = "Show Station Manifest" - show_station_manifest() - -/mob/living/silicon/robot/proc/self_diagnosis() - if(!is_component_functioning("diagnosis unit")) - return null - - var/dat = "[src.name] Self-Diagnosis Report\n" - for(var/V in components) - var/datum/robot_component/C = components[V] - if(C.installed == 0) - dat += "[C.name]
        MISSING
        " - else - dat += "[C.name][C.installed == -1 ? "
        DESTROYED" : ""]
        Brute Damage:[C.brute_damage]
        Electronics Damage:[C.electronics_damage]
        Powered:[C.is_powered() ? "Yes" : "No"]
        Toggled:[ C.toggled ? "Yes" : "No"]

        " - return dat - -/mob/living/silicon/robot/verb/self_diagnosis_verb() - set category = "Robot Commands" - set name = "Self Diagnosis" - - if(!is_component_functioning("diagnosis unit")) - to_chat(src, "Your self-diagnosis component isn't functioning.") - - var/dat = self_diagnosis() - src << browse(dat, "window=robotdiagnosis") - - -/mob/living/silicon/robot/verb/toggle_component() - set category = "Robot Commands" - set name = "Toggle Component" - set desc = "Toggle a component, conserving power." - - var/list/installed_components = list() - for(var/V in components) - if(V == "power cell") continue - var/datum/robot_component/C = components[V] - if(C.installed) - installed_components += V - - var/toggle = input(src, "Which component do you want to toggle?", "Toggle Component") as null|anything in installed_components - if(!toggle) - return - - var/datum/robot_component/C = components[toggle] - C.toggle() - to_chat(src, "You [C.toggled ? "enable" : "disable"] [C.name].") - -/mob/living/silicon/robot/proc/sensor_mode() - set name = "Set Sensor Augmentation" - set desc = "Augment visual feed with internal sensor overlays." - set category = "Robot Commands" - toggle_sensor_mode() - -/mob/living/silicon/robot/proc/add_robot_verbs() - src.verbs |= robot_verbs_default - src.verbs |= silicon_subsystems - -/mob/living/silicon/robot/proc/remove_robot_verbs() - src.verbs -= robot_verbs_default - src.verbs -= silicon_subsystems - -/mob/living/silicon/robot/proc/ionpulse() - if(!ionpulse_on) - return - - if(cell.charge <= 50) - toggle_ionpulse() - return - - cell.charge -= 25 // 500 steps on a default cell. - return 1 - -/mob/living/silicon/robot/proc/toggle_ionpulse() - if(!ionpulse) - to_chat(src, "No thrusters are installed!") - return - - if(!ion_trail) - ion_trail = new - ion_trail.set_up(src) - - ionpulse_on = !ionpulse_on - to_chat(src, "You [ionpulse_on ? null :"de"]activate your ion thrusters.") - if(ionpulse_on) - ion_trail.start() - else - ion_trail.stop() - if(thruster_button) - thruster_button.icon_state = "ionpulse[ionpulse_on]" - -/mob/living/silicon/robot/blob_act(obj/structure/blob/B) - if(stat != DEAD) - adjustBruteLoss(30) - else - gib() - return TRUE - -// this function displays the cyborgs current cell charge in the stat panel -/mob/living/silicon/robot/proc/show_cell_power() - if(cell) - stat(null, text("Charge Left: [cell.charge]/[cell.maxcharge]")) - else - stat(null, text("No Cell Inserted!")) - - -// update the status screen display -/mob/living/silicon/robot/Stat() - ..() - statpanel("Status") - if(client.statpanel == "Status") - show_cell_power() - var/total_user_contents = GetAllContents() - if(locate(/obj/item/gps/cyborg) in total_user_contents) - var/turf/T = get_turf(src) - stat(null, "GPS: [COORD(T)]") - -/mob/living/silicon/robot/restrained() - return 0 - -/mob/living/silicon/robot/InCritical() - return low_power_mode - -/mob/living/silicon/robot/ex_act(severity) - switch(severity) - if(1.0) - gib() - return - if(2.0) - if(stat != 2) - adjustBruteLoss(60) - adjustFireLoss(60) - if(3.0) - if(stat != 2) - adjustBruteLoss(30) - return - - -/mob/living/silicon/robot/bullet_act(var/obj/item/projectile/Proj) - ..(Proj) - if(prob(75) && Proj.damage > 0) spark_system.start() - return 2 - - -/mob/living/silicon/robot/attackby(obj/item/W, mob/user, params) - // Check if the user is trying to insert another component like a radio, actuator, armor etc. - if(istype(W, /obj/item/robot_parts/robot_component) && opened) - for(var/V in components) - var/datum/robot_component/C = components[V] - if(!C.installed && istype(W, C.external_type)) - C.installed = 1 - C.wrapped = W - C.install() - user.drop_item() - W.loc = null - - var/obj/item/robot_parts/robot_component/WC = W - if(istype(WC)) - C.brute_damage = WC.brute - C.electronics_damage = WC.burn - - to_chat(usr, "You install the [W.name].") - - return - - if(istype(W, /obj/item/stack/cable_coil) && user.a_intent == INTENT_HELP && (wiresexposed || istype(src, /mob/living/silicon/robot/drone))) - user.changeNext_move(CLICK_CD_MELEE) - if(!getFireLoss()) - to_chat(user, "Nothing to fix!") - return - else if(!getFireLoss(TRUE)) - to_chat(user, "The damaged components are beyond saving!") - return - var/obj/item/stack/cable_coil/coil = W - adjustFireLoss(-30) - updatehealth() - add_fingerprint(user) - coil.use(1) - user.visible_message("\The [user] fixes some of the burnt wires on \the [src] with \the [coil].") - - else if(istype(W, /obj/item/stock_parts/cell) && opened) // trying to put a cell inside - var/datum/robot_component/cell/C = components["power cell"] - if(wiresexposed) - to_chat(user, "Close the panel first.") - else if(cell) - to_chat(user, "There is a power cell already installed.") - else - user.drop_item() - W.loc = src - cell = W - to_chat(user, "You insert the power cell.") - - C.installed = 1 - C.wrapped = W - C.install() - C.external_type = W.type // Update the cell component's `external_type` to the path of new cell - //This will mean that removing and replacing a power cell will repair the mount, but I don't care at this point. ~Z - C.brute_damage = 0 - C.electronics_damage = 0 - diag_hud_set_borgcell() - - else if(istype(W, /obj/item/encryptionkey/) && opened) - if(radio)//sanityyyyyy - radio.attackby(W,user)//GTFO, you have your own procs - else - to_chat(user, "Unable to locate a radio.") - - else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) // trying to unlock the interface with an ID card - if(emagged)//still allow them to open the cover - to_chat(user, "The interface seems slightly damaged.") - if(opened) - to_chat(user, "You must close the cover to swipe an ID card.") - else - if(allowed(W)) - locked = !locked - to_chat(user, "You [ locked ? "lock" : "unlock"] [src]'s interface.") - update_icons() - else - to_chat(user, "Access denied.") - - else if(istype(W, /obj/item/borg/upgrade/)) - var/obj/item/borg/upgrade/U = W - if(!opened) - to_chat(user, "You must access the borg's internals!") - else if(!src.module && U.require_module) - to_chat(user, "The borg must choose a module before it can be upgraded!") - else if(U.locked) - to_chat(user, "The upgrade is locked and cannot be used yet!") - else - if(!user.drop_item()) - return - if(U.action(src)) - to_chat(user, "You apply the upgrade to [src].") - U.forceMove(src) - else - to_chat(user, "Upgrade error.") - - else if(istype(W, /obj/item/mmi_radio_upgrade)) - if(!opened) - to_chat(user, "You must access the borg's internals!") - return - else if(!mmi) - to_chat(user, "This cyborg does not have an MMI to augment!") - return - else if(mmi.radio) - to_chat(user, "A radio upgrade is already installed in the MMI!") - return - else if(user.drop_item()) - to_chat(user, "You apply the upgrade to [src].") - to_chat(src, "MMI radio capability installed.") - mmi.install_radio() - qdel(W) - else - return ..() - -/mob/living/silicon/robot/wirecutter_act(mob/user, obj/item/I) - if(!opened) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = 0)) - return - if(wiresexposed) - wires.Interact(user) - -/mob/living/silicon/robot/multitool_act(mob/user, obj/item/I) - if(!opened) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = 0)) - return - if(wiresexposed) - wires.Interact(user) - -/mob/living/silicon/robot/screwdriver_act(mob/user, obj/item/I) - if(!opened) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = 0)) - return - if(!cell) // haxing - wiresexposed = !wiresexposed - to_chat(user, "The wires have been [wiresexposed ? "exposed" : "unexposed"]") - update_icons() - I.play_tool_sound(user, I.tool_volume) - else //radio check - if(radio) - radio.screwdriver_act(user, I)//Push it to the radio to let it handle everything - else - to_chat(user, "Unable to locate a radio.") - update_icons() - -/mob/living/silicon/robot/crowbar_act(mob/user, obj/item/I) - if(user.a_intent != INTENT_HELP) - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(!opened) - if(locked) - to_chat(user, "The cover is locked and cannot be opened.") - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - to_chat(user, "You open the cover.") - opened = TRUE - update_icons() - return - else if(cell) - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - to_chat(user, "You close the cover.") - opened = FALSE - update_icons() - return - else if(wiresexposed && wires.IsAllCut()) - //Cell is out, wires are exposed, remove MMI, produce damaged chassis, baleet original mob. - if(!mmi) - to_chat(user, "[src] has no brain to remove.") - return - to_chat(user, "You jam the crowbar into the robot and begin levering the securing bolts...") - if(I.use_tool(src, user, 30, volume = I.tool_volume)) - user.visible_message("[user] deconstructs [src]!", "You unfasten the securing bolts, and [src] falls to pieces!") - deconstruct() - return - // Okay we're not removing the cell or an MMI, but maybe something else? - var/list/removable_components = list() - for(var/V in components) - if(V == "power cell") - continue - var/datum/robot_component/C = components[V] - if(C.installed == 1 || C.installed == -1) - removable_components += V - if(module) - removable_components += module.custom_removals - var/remove = input(user, "Which component do you want to pry out?", "Remove Component") as null|anything in removable_components - if(!remove) - return - if(module && module.handle_custom_removal(remove, user, I)) - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - var/datum/robot_component/C = components[remove] - var/obj/item/robot_parts/robot_component/thing = C.wrapped - to_chat(user, "You remove \the [thing].") - if(istype(thing)) - thing.brute = C.brute_damage - thing.burn = C.electronics_damage - - thing.loc = loc - var/was_installed = C.installed - C.installed = 0 - if(was_installed == 1) - C.uninstall() - - - - -/mob/living/silicon/robot/attacked_by(obj/item/I, mob/living/user, def_zone) - if(I.force && I.damtype != STAMINA && stat != DEAD) //only sparks if real damage is dealt. - spark_system.start() - ..() - -/mob/living/silicon/robot/emag_act(user as mob) - if(!ishuman(user) && !issilicon(user)) - return - var/mob/living/M = user - if(!opened)//Cover is closed - if(!is_emaggable) - to_chat(user, "The emag sparks, and flashes red. This mechanism does not appear to be emaggable.") - else if(locked) - to_chat(user, "You emag the cover lock.") - locked = 0 - else - to_chat(user, "The cover is already unlocked.") - return - - if(opened)//Cover is open - if(emagged) return//Prevents the X has hit Y with Z message also you cant emag them twice - if(wiresexposed) - to_chat(user, "You must close the panel first") - return - else - sleep(6) - emagged = 1 - SetLockdown(1) //Borgs were getting into trouble because they would attack the emagger before the new laws were shown - if(src.hud_used) - src.hud_used.update_robot_modules_display() //Shows/hides the emag item if the inventory screen is already open. - disconnect_from_ai() - to_chat(user, "You emag [src]'s interface.") -// message_admins("[key_name_admin(user)] emagged cyborg [key_name_admin(src)]. Laws overridden.") - log_game("[key_name(user)] emagged cyborg [key_name(src)]. Laws overridden.") - clear_supplied_laws() - clear_inherent_laws() - laws = new /datum/ai_laws/syndicate_override - var/time = time2text(world.realtime,"hh:mm:ss") - lawchanges.Add("[time] : [M.name]([M.key]) emagged [name]([key])") - set_zeroth_law("Only [M.real_name] and people [M.p_they()] designate[M.p_s()] as being such are Syndicate Agents.") - to_chat(src, "ALERT: Foreign software detected.") - sleep(5) - to_chat(src, "Initiating diagnostics...") - sleep(20) - to_chat(src, "SynBorg v1.7 loaded.") - sleep(5) - to_chat(src, "LAW SYNCHRONISATION ERROR") - sleep(5) - to_chat(src, "Would you like to send a report to NanoTraSoft? Y/N") - sleep(10) - to_chat(src, "> N") - sleep(20) - to_chat(src, "ERRORERRORERROR") - to_chat(src, "Obey these laws:") - laws.show_laws(src) - to_chat(src, "ALERT: [M.real_name] is your new master. Obey your new laws and [M.p_their()] commands.") - SetLockdown(0) - if(src.module && istype(src.module, /obj/item/robot_module/miner)) - for(var/obj/item/pickaxe/drill/cyborg/D in src.module.modules) - qdel(D) - src.module.modules += new /obj/item/pickaxe/drill/cyborg/diamond(src.module) - src.module.rebuild() - if(src.module && istype(src.module, /obj/item/robot_module/medical)) - for(var/obj/item/borg_defib/F in src.module.modules) - F.safety = 0 - if(module) - module.module_type = "Malf" // For the cool factor - update_module_icon() - update_icons() - return - -/mob/living/silicon/robot/verb/unlock_own_cover() - set category = "Robot Commands" - set name = "Unlock Cover" - set desc = "Unlocks your own cover if it is locked. You can not lock it again. A human will have to lock it for you." - if(locked) - switch(alert("You can not lock your cover again, are you sure?\n (You can still ask for a human to lock it)", "Unlock Own Cover", "Yes", "No")) - if("Yes") - locked = 0 - update_icons() - to_chat(usr, "You unlock your cover.") - -/mob/living/silicon/robot/attack_ghost(mob/user) - if(wiresexposed) - wires.Interact(user) - else - ..() //this calls the /mob/living/attack_ghost proc for the ghost health/cyborg analyzer - -/mob/living/silicon/robot/proc/allowed(obj/item/I) - var/obj/dummy = new /obj(null) // Create a dummy object to check access on as to avoid having to snowflake check_access on every mob - dummy.req_access = req_access - dummy.req_one_access = req_one_access - - if(dummy.check_access(I)) - qdel(dummy) - return 1 - - qdel(dummy) - return 0 - -/mob/living/silicon/robot/update_icons() - - overlays.Cut() - if(stat != DEAD && !(paralysis || stunned || IsWeakened() || low_power_mode)) //Not dead, not stunned. - if(custom_panel in custom_eye_names) - overlays += "eyes-[custom_panel]" - else - overlays += "eyes-[icon_state]" - else - overlays -= "eyes" - - if(opened) - var/panelprefix = "ov" - if(custom_sprite) //Custom borgs also have custom panels, heh - panelprefix = "[ckey]" - - if(custom_panel in custom_panel_names) //For default borgs with different panels - panelprefix = custom_panel - - if(wiresexposed) - overlays += "[panelprefix]-openpanel +w" - else if(cell) - overlays += "[panelprefix]-openpanel +c" - else - overlays += "[panelprefix]-openpanel -c" - - var/combat = list("Combat") - if(modtype in combat) - if(base_icon == "") - base_icon = icon_state - if(module_active && istype(module_active,/obj/item/borg/combat/mobility)) - icon_state = "[base_icon]-roll" - else - icon_state = base_icon - if(module) - for(var/obj/item/borg/combat/shield/S in module.modules) - if(activated(S)) - overlays += "[base_icon]-shield" - update_fire() - -/mob/living/silicon/robot/proc/installed_modules() - if(weapon_lock) - to_chat(src, "Weapon lock active, unable to use modules! Count:[weaponlock_time]") - return - - if(!module) - pick_module() - return - var/dat = {"Close -
        -
        - Activated Modules -
        - - - - -
        Module 1:[module_state_1 ? "[module_state_1]" : "No Module"]
        Module 2:[module_state_2 ? "[module_state_2]" : "No Module"]
        Module 3:[module_state_3 ? "[module_state_3]" : "No Module"]

        - Installed Modules

        - - "} - for(var/obj in module.modules) - if(!obj) - dat += text("") - else if(activated(obj)) - dat += text("") - else - dat += text("") - if(emagged || weapons_unlock) - if(activated(module.emag)) - dat += text("") - else - dat += text("") - dat += "
        Resource depleted
        [obj]Activated
        [obj]Activate
        [module.emag]Activated
        [module.emag]Activate
        " -/* - if(activated(obj)) - dat += text("[obj]: \[Activated | Deactivate\]
        ") - else - dat += text("[obj]: \[Activate | Deactivated\]
        ") -*/ - var/datum/browser/popup = new(src, "robotmod", "Modules") - popup.set_content(dat) - popup.open() - - -/mob/living/silicon/robot/Topic(href, href_list) - if(..()) - return 1 - - if(usr != src) - return 1 - - if(href_list["mach_close"]) - var/t1 = text("window=[href_list["mach_close"]]") - unset_machine() - src << browse(null, t1) - return 1 - - if(href_list["showalerts"]) - subsystem_alarm_monitor() - return 1 - - if(href_list["mod"]) - var/obj/item/O = locate(href_list["mod"]) - if(istype(O) && (O.loc == src)) - O.attack_self(src) - return 1 - - if(href_list["act"]) - var/obj/item/O = locate(href_list["act"]) - if(!istype(O) || !(O.loc == src || O.loc == src.module)) - return 1 - - activate_module(O) - installed_modules() - - if(href_list["deact"]) - var/obj/item/O = locate(href_list["deact"]) - if(activated(O)) - if(module_state_1 == O) - module_state_1 = null - contents -= O - else if(module_state_2 == O) - module_state_2 = null - contents -= O - else if(module_state_3 == O) - module_state_3 = null - contents -= O - else - to_chat(src, "Module isn't activated.") - else - to_chat(src, "Module isn't activated") - installed_modules() - return 1 - - return 1 - -/mob/living/silicon/robot/proc/radio_menu() - radio.interact(src)//Just use the radio's Topic() instead of bullshit special-snowflake code - -/mob/living/silicon/robot/proc/control_headlamp() - if(stat || lamp_recharging || low_power_mode) - to_chat(src, "This function is currently offline.") - return - -//Some sort of magical "modulo" thing which somehow increments lamp power by 2, until it hits the max and resets to 0. - lamp_intensity = (lamp_intensity+2) % (lamp_max+2) - to_chat(src, "[lamp_intensity ? "Headlamp power set to Level [lamp_intensity/2]" : "Headlamp disabled."]") - update_headlamp() - -/mob/living/silicon/robot/proc/update_headlamp(var/turn_off = 0, var/cooldown = 100) - set_light(0) - - if(lamp_intensity && (turn_off || stat || low_power_mode)) - to_chat(src, "Your headlamp has been deactivated.") - lamp_intensity = 0 - lamp_recharging = 1 - spawn(cooldown) //10 seconds by default, if the source of the deactivation does not keep stat that long. - lamp_recharging = 0 - else - set_light(light_range + lamp_intensity) - - if(lamp_button) - lamp_button.icon_state = "lamp[lamp_intensity]" - - update_icons() - -/mob/living/silicon/robot/proc/deconstruct() - var/turf/T = get_turf(src) - if(robot_suit) - robot_suit.forceMove(T) - robot_suit.l_leg.forceMove(T) - robot_suit.l_leg = null - robot_suit.r_leg.forceMove(T) - robot_suit.r_leg = null - new /obj/item/stack/cable_coil(T, robot_suit.chest.wired) - robot_suit.chest.forceMove(T) - robot_suit.chest.wired = FALSE - robot_suit.chest = null - robot_suit.l_arm.forceMove(T) - robot_suit.l_arm = null - robot_suit.r_arm.forceMove(T) - robot_suit.r_arm = null - robot_suit.head.forceMove(T) - robot_suit.head.flash1.forceMove(T) - robot_suit.head.flash1.burn_out() - robot_suit.head.flash1 = null - robot_suit.head.flash2.forceMove(T) - robot_suit.head.flash2.burn_out() - robot_suit.head.flash2 = null - robot_suit.head = null - robot_suit.updateicon() - else - new /obj/item/robot_parts/robot_suit(T) - new /obj/item/robot_parts/l_leg(T) - new /obj/item/robot_parts/r_leg(T) - new /obj/item/stack/cable_coil(T, 1) - new /obj/item/robot_parts/chest(T) - new /obj/item/robot_parts/l_arm(T) - new /obj/item/robot_parts/r_arm(T) - new /obj/item/robot_parts/head(T) - var/b - for(b=0, b!=2, b++) - var/obj/item/flash/F = new /obj/item/flash(T) - F.burn_out() - if(cell) //Sanity check. - cell.forceMove(T) - cell = null - qdel(src) - -#define BORG_CAMERA_BUFFER 30 -/mob/living/silicon/robot/Move(a, b, flag) - var/oldLoc = src.loc - . = ..() - if(.) - if(src.camera) - if(!updating) - updating = 1 - spawn(BORG_CAMERA_BUFFER) - if(oldLoc != src.loc) - cameranet.updatePortableCamera(src.camera) - updating = 0 - if(module) - if(module.type == /obj/item/robot_module/janitor) - var/turf/tile = loc - if(isturf(tile)) - var/floor_only = TRUE - for(var/A in tile) - if(istype(A, /obj/effect)) - if(is_cleanable(A)) - var/obj/effect/decal/cleanable/blood/B = A - if(istype(B) && B.off_floor) - floor_only = FALSE - else - qdel(A) - else if(istype(A, /obj/item)) - var/obj/item/cleaned_item = A - cleaned_item.clean_blood() - else if(istype(A, /mob/living/carbon/human)) - var/mob/living/carbon/human/cleaned_human = A - if(cleaned_human.lying) - if(cleaned_human.head) - cleaned_human.head.clean_blood() - cleaned_human.update_inv_head(0,0) - if(cleaned_human.wear_suit) - cleaned_human.wear_suit.clean_blood() - cleaned_human.update_inv_wear_suit(0,0) - else if(cleaned_human.w_uniform) - cleaned_human.w_uniform.clean_blood() - cleaned_human.update_inv_w_uniform(0,0) - if(cleaned_human.shoes) - cleaned_human.shoes.clean_blood() - cleaned_human.update_inv_shoes(0,0) - cleaned_human.clean_blood() - to_chat(cleaned_human, "[src] cleans your face!") - if(floor_only) - tile.clean_blood() - return -#undef BORG_CAMERA_BUFFER - -/mob/living/silicon/robot/proc/self_destruct() - if(emagged) - if(mmi) - qdel(mmi) - explosion(src.loc,1,2,4,flame_range = 2) - else - explosion(src.loc,-1,0,2) - gib() - return - -/mob/living/silicon/robot/proc/UnlinkSelf() - disconnect_from_ai() - lawupdate = 0 - lockcharge = 0 - canmove = 1 - scrambledcodes = 1 - //Disconnect it's camera so it's not so easily tracked. - QDEL_NULL(src.camera) - // I'm trying to get the Cyborg to not be listed in the camera list - // Instead of being listed as "deactivated". The downside is that I'm going - // to have to check if every camera is null or not before doing anything, to prevent runtime errors. - // I could change the network to null but I don't know what would happen, and it seems too hacky for me. - -/mob/living/silicon/robot/proc/ResetSecurityCodes() - set category = "Robot Commands" - set name = "Reset Identity Codes" - set desc = "Scrambles your security and identification codes and resets your current buffers. Unlocks you and but permanently severs you from your AI and the robotics console and will deactivate your camera system." - - var/mob/living/silicon/robot/R = src - - if(R) - R.UnlinkSelf() - to_chat(R, "Buffers flushed and reset. Camera system shutdown. All systems operational.") - src.verbs -= /mob/living/silicon/robot/proc/ResetSecurityCodes - -/mob/living/silicon/robot/mode() - set name = "Activate Held Object" - set category = "IC" - set src = usr - - var/obj/item/W = get_active_hand() - if(W) - W.attack_self(src) - - return - -/mob/living/silicon/robot/proc/SetLockdown(var/state = 1) - // They stay locked down if their wire is cut. - if(wires.LockedCut()) - state = 1 - if(state) - throw_alert("locked", /obj/screen/alert/locked) - else - clear_alert("locked") - lockcharge = state - update_canmove() - -/mob/living/silicon/robot/proc/choose_icon(var/triesleft, var/list/module_sprites) - - if(triesleft<1 || !module_sprites.len) - return - else - triesleft-- - - var/icontype - lockcharge = 1 //Locks borg until it select an icon to avoid secborgs running around with a standard sprite - icontype = input("Select an icon! [triesleft ? "You have [triesleft] more chances." : "This is your last try."]", "Robot", null, null) in module_sprites - - if(icontype) - if(icontype == "Custom") - icon = 'icons/mob/custom_synthetic/custom-synthetic.dmi' - else - icon = 'icons/mob/robots.dmi' - icon_state = module_sprites[icontype] - if(icontype == "Bro") - module.module_type = "Brobot" - update_module_icon() - lockcharge = null - var/list/names = splittext(icontype, "-") - custom_panel = trim(names[1]) - else - to_chat(src, "Something is badly wrong with the sprite selection. Harass a coder.") - icon_state = module_sprites[1] - lockcharge = null - return - - update_icons() - - if(triesleft >= 1) - var/choice = input("Look at your icon - is this what you want?") in list("Yes","No") - if(choice=="No") - choose_icon(triesleft, module_sprites) - return - else - triesleft = 0 - return - else - to_chat(src, "Your icon has been set. You now require a module reset to change it.") - -/mob/living/silicon/robot/proc/notify_ai(var/notifytype, var/oldname, var/newname) - if(!connected_ai) - return - switch(notifytype) - if(1) //New Cyborg - to_chat(connected_ai, "

        NOTICE - New cyborg connection detected: [name]
        ") - if(2) //New Module - to_chat(connected_ai, "

        NOTICE - Cyborg module change detected: [name] has loaded the [designation] module.
        ") - if(3) //New Name - to_chat(connected_ai, "

        NOTICE - Cyborg reclassification detected: [oldname] is now designated as [newname].
        ") - -/mob/living/silicon/robot/proc/disconnect_from_ai() - if(connected_ai) - sync() // One last sync attempt - connected_ai.connected_robots -= src - connected_ai = null - -/mob/living/silicon/robot/proc/connect_to_ai(var/mob/living/silicon/ai/AI) - if(AI && AI != connected_ai) - disconnect_from_ai() - connected_ai = AI - connected_ai.connected_robots |= src - notify_ai(1) - sync() - -/mob/living/silicon/robot/adjustOxyLoss(var/amount) - if(suiciding) - return ..() - else - return STATUS_UPDATE_NONE - -/mob/living/silicon/robot/regenerate_icons() - ..() - update_module_icon() - -/mob/living/silicon/robot/deathsquad - base_icon = "nano_bloodhound" - icon_state = "nano_bloodhound" - designation = "SpecOps" - lawupdate = 0 - scrambledcodes = 1 - req_one_access = list(ACCESS_CENT_SPECOPS) - ionpulse = 1 - magpulse = 1 - pdahide = 1 - eye_protection = 2 // Immunity to flashes and the visual part of flashbangs - ear_protection = 1 // Immunity to the audio part of flashbangs - allow_rename = FALSE - modtype = "Commando" - faction = list("nanotrasen") - is_emaggable = FALSE - -/mob/living/silicon/robot/deathsquad/New(loc) - ..() - cell = new /obj/item/stock_parts/cell/hyper(src) - -/mob/living/silicon/robot/deathsquad/init() - laws = new /datum/ai_laws/deathsquad - module = new /obj/item/robot_module/deathsquad(src) - - aiCamera = new/obj/item/camera/siliconcam/robot_camera(src) - radio = new /obj/item/radio/borg/deathsquad(src) - radio.recalculateChannels() - - playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0) - -/mob/living/silicon/robot/combat - base_icon = "droidcombat" - icon_state = "droidcombat" - modtype = "Combat" - designation = "Combat" - -/mob/living/silicon/robot/combat/init() - ..() - module = new /obj/item/robot_module/combat(src) - module.channels = list("Security" = 1) - //languages - module.add_languages(src) - //subsystems - module.add_subsystems_and_actions(src) - - status_flags &= ~CANPUSH - - radio.config(module.channels) - notify_ai(2) - -/mob/living/silicon/robot/ert - designation = "ERT" - lawupdate = 0 - scrambledcodes = 1 - req_one_access = list(ACCESS_CENT_SPECOPS) - ionpulse = 1 - - force_modules = list("Engineering", "Medical", "Security") - static_radio_channels = 1 - allow_rename = FALSE - weapons_unlock = TRUE - - -/mob/living/silicon/robot/ert/init() - laws = new /datum/ai_laws/ert_override - radio = new /obj/item/radio/borg/ert(src) - radio.recalculateChannels() - aiCamera = new/obj/item/camera/siliconcam/robot_camera(src) - -/mob/living/silicon/robot/ert/New(loc, cyborg_unlock) - ..(loc) - cell = new /obj/item/stock_parts/cell/hyper(src) - var/rnum = rand(1,1000) - var/borgname = "ERT [rnum]" - name = borgname - custom_name = borgname - real_name = name - mind = new - mind.current = src - mind.original = src - mind.assigned_role = SPECIAL_ROLE_ERT - mind.special_role = SPECIAL_ROLE_ERT - if(cyborg_unlock) - crisis = 1 - if(!(mind in SSticker.minds)) - SSticker.minds += mind - SSticker.mode.ert += mind - -/mob/living/silicon/robot/ert/gamma - crisis = 1 - -/mob/living/silicon/robot/emp_act(severity) - ..() - switch(severity) - if(1) - disable_component("comms", 160) - if(2) - disable_component("comms", 60) - -/mob/living/silicon/robot/extinguish_light() - update_headlamp(1, 150) - -/mob/living/silicon/robot/rejuvenate() - ..() - var/brute = 1000 - var/burn = 1000 - var/list/datum/robot_component/borked_parts = get_damaged_components(TRUE, TRUE, TRUE, TRUE) - for(var/datum/robot_component/borked_part in borked_parts) - brute = borked_part.brute_damage - burn = borked_part.electronics_damage - borked_part.installed = 1 - borked_part.wrapped = new borked_part.external_type - if(ispath(borked_part.external_type, /obj/item/stock_parts/cell)) // is the broken part a cell? - cell = new borked_part.external_type // borgs that have their cell destroyed have their `cell` var set to null. we need create a new cell for them based on their old cell type. - borked_part.heal_damage(brute,burn) - borked_part.install() - -/mob/living/silicon/robot/proc/check_sprite(spritename) - . = FALSE - - var/static/all_borg_icon_states = icon_states('icons/mob/custom_synthetic/custom-synthetic.dmi') - if(spritename in all_borg_icon_states) - . = TRUE - -/mob/living/silicon/robot/check_eye_prot() - return eye_protection - -/mob/living/silicon/robot/check_ear_prot() - return ear_protection - -/mob/living/silicon/robot/update_sight() - if(!client) - return - - if(stat == DEAD) - grant_death_vision() - return - - see_invisible = initial(see_invisible) - see_in_dark = initial(see_in_dark) - sight = initial(sight) - lighting_alpha = initial(lighting_alpha) - - if(client.eye != src) - var/atom/A = client.eye - if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. - return - - if(sight_mode & BORGMESON) - sight |= SEE_TURFS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - - if(sight_mode & BORGXRAY) - sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) - see_invisible = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - see_in_dark = 8 - - if(sight_mode & BORGTHERM) - sight |= SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - - SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) - sync_lighting_plane_alpha() +var/list/robot_verbs_default = list( + /mob/living/silicon/robot/proc/sensor_mode, +) + +/mob/living/silicon/robot + name = "Cyborg" + real_name = "Cyborg" + icon = 'icons/mob/robots.dmi' + icon_state = "robot" + maxHealth = 100 + health = 100 + universal_understand = 1 + deathgasp_on_death = TRUE + + var/sight_mode = 0 + var/custom_name = "" + var/custom_sprite = 0 //Due to all the sprites involved, a var for our custom borgs may be best + +//Hud stuff + + var/obj/screen/inv1 = null + var/obj/screen/inv2 = null + var/obj/screen/inv3 = null + var/obj/screen/lamp_button = null + var/obj/screen/thruster_button = null + + var/shown_robot_modules = 0 //Used to determine whether they have the module menu shown or not + var/obj/screen/robot_modules_background + +//3 Modules can be activated at any one time. + var/obj/item/robot_module/module = null + var/module_active = null + var/module_state_1 = null + var/module_state_2 = null + var/module_state_3 = null + + var/obj/item/radio/borg/radio = null + var/mob/living/silicon/ai/connected_ai = null + var/obj/item/stock_parts/cell/cell = null + var/obj/machinery/camera/camera = null + + // Components are basically robot organs. + var/list/components = list() + + var/obj/item/robot_parts/robot_suit/robot_suit = null //Used for deconstruction to remember what the borg was constructed out of.. + var/obj/item/mmi/mmi = null + + var/obj/item/pda/silicon/robot/rbPDA = null + + var/datum/wires/robot/wires = null + + var/opened = 0 + var/custom_panel = null + var/list/custom_panel_names = list("Cricket") + var/list/custom_eye_names = list("Cricket","Standard") + var/emagged = 0 + var/is_emaggable = TRUE + var/eye_protection = 0 + var/ear_protection = 0 + + var/list/force_modules = list() + var/allow_rename = TRUE + var/weapons_unlock = FALSE + var/static_radio_channels = FALSE + + var/wiresexposed = 0 + var/locked = 1 + var/list/req_one_access = list(ACCESS_ROBOTICS) + var/list/req_access + var/ident = 0 + //var/list/laws = list() + var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list()) + var/viewalerts = 0 + var/modtype = "Default" + var/lower_mod = 0 + var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N + var/jeton = 0 + var/low_power_mode = 0 //whether the robot has no charge left. + var/weapon_lock = 0 + var/weaponlock_time = 120 + var/lawupdate = 1 //Cyborgs will sync their laws with their AI by default + var/lockcharge //Used when locking down a borg to preserve cell charge + var/speed = 0 //Cause sec borgs gotta go fast //No they dont! + var/scrambledcodes = 0 // Used to determine if a borg shows up on the robotics console. Setting to one hides them. + var/pdahide = 0 //Used to hide the borg from the messenger list + var/tracking_entities = 0 //The number of known entities currently accessing the internal camera + var/braintype = "Cyborg" + var/base_icon = "" + var/crisis = 0 + + var/lamp_max = 10 //Maximum brightness of a borg lamp. Set as a var for easy adjusting. + var/lamp_intensity = 0 //Luminosity of the headlamp. 0 is off. Higher settings than the minimum require power. + var/lamp_recharging = 0 //Flag for if the lamp is on cooldown after being forcibly disabled. + + var/updating = 0 //portable camera camerachunk update + + hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD) + + var/magpulse = 0 + var/ionpulse = 0 // Jetpack-like effect. + var/ionpulse_on = 0 // Jetpack-like effect. + var/datum/effect_system/trail_follow/ion/ion_trail // Ionpulse effect. + + var/datum/action/item_action/toggle_research_scanner/scanner = null + var/list/module_actions = list() + +/mob/living/silicon/robot/get_cell() + return cell + +/mob/living/silicon/robot/New(loc,var/syndie = 0,var/unfinished = 0, var/alien = 0) + spark_system = new /datum/effect_system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + + add_language("Robot Talk", 1) + + wires = new(src) + + robot_modules_background = new() + robot_modules_background.icon_state = "block" + robot_modules_background.layer = HUD_LAYER //Objects that appear on screen are on layer 20, UI should be just below it. + robot_modules_background.plane = HUD_PLANE + + ident = rand(1, 999) + rename_character(null, get_default_name()) + update_icons() + update_headlamp() + + radio = new /obj/item/radio/borg(src) + common_radio = radio + + init() + + if(!scrambledcodes && !camera) + camera = new /obj/machinery/camera(src) + camera.c_tag = real_name + camera.network = list("SS13","Robots") + if(wires.IsCameraCut()) // 5 = BORG CAMERA + camera.status = 0 + + if(mmi == null) + mmi = new /obj/item/mmi/robotic_brain(src) //Give the borg an MMI if he spawns without for some reason. (probably not the correct way to spawn a robotic brain, but it works) + mmi.icon_state = "boris" + + if(!cell) // Make sure a new cell gets created *before* executing initialize_components(). The cell component needs an existing cell for it to get set up properly + cell = new /obj/item/stock_parts/cell/high(src) + + initialize_components() + //if(!unfinished) + // Create all the robot parts. + for(var/V in components) if(V != "power cell") + var/datum/robot_component/C = components[V] + C.installed = 1 + C.wrapped = new C.external_type + + ..() + + add_robot_verbs() + + if(cell) + var/datum/robot_component/cell_component = components["power cell"] + cell_component.wrapped = cell + cell_component.installed = 1 + cell_component.install() + + diag_hud_set_borgcell() + scanner = new(src) + scanner.Grant(src) + +/mob/living/silicon/robot/proc/init(var/alien=0) + aiCamera = new/obj/item/camera/siliconcam/robot_camera(src) + make_laws() + additional_law_channels["Binary"] = ":b " + var/new_ai = select_active_ai_with_fewest_borgs() + if(new_ai) + lawupdate = 1 + connect_to_ai(new_ai) + else + lawupdate = 0 + + playsound(loc, 'sound/voice/liveagain.ogg', 75, 1) + +/mob/living/silicon/robot/rename_character(oldname, newname) + if(!..(oldname, newname)) + return 0 + + if(oldname != real_name) + notify_ai(3, oldname, newname) + custom_name = (newname != get_default_name()) ? newname : null + setup_PDA() + + //We also need to update name of internal camera. + if(camera) + camera.c_tag = newname + + //Check for custom sprite + if(!custom_sprite) + var/file = file2text("config/custom_sprites.txt") + var/lines = splittext(file, "\n") + + for(var/line in lines) + // split & clean up + var/list/Entry = splittext(line, ":") + for(var/i = 1 to Entry.len) + Entry[i] = trim(Entry[i]) + + if(Entry.len < 2 || Entry[1] != "cyborg") //ignore incorrectly formatted entries or entries that aren't marked for cyborg + continue + + if(Entry[2] == ckey) //They're in the list? Custom sprite time, var and icon change required + custom_sprite = 1 + + return 1 + + +/mob/living/silicon/robot/proc/get_default_name(var/prefix as text) + if(prefix) + modtype = prefix + if(mmi) + if(istype(mmi, /obj/item/mmi/robotic_brain)) + braintype = "Android" + else + braintype = "Cyborg" + else + braintype = "Robot" + + if(custom_name) + return custom_name + else + return "[modtype] [braintype]-[num2text(ident)]" + +/mob/living/silicon/robot/verb/Namepick() + set category = "Robot Commands" + if(custom_name) + return 0 + if(!allow_rename) + to_chat(src, "Rename functionality is not enabled on this unit."); + return 0 + rename_self(braintype, 1) + +/mob/living/silicon/robot/proc/sync() + if(lawupdate && connected_ai) + lawsync() + photosync() + +// setup the PDA and its name +/mob/living/silicon/robot/proc/setup_PDA() + if(!rbPDA) + rbPDA = new(src) + rbPDA.set_name_and_job(real_name, braintype) + var/datum/data/pda/app/messenger/M = rbPDA.find_program(/datum/data/pda/app/messenger) + if(M) + if(scrambledcodes) + M.hidden = 1 + if(pdahide) + M.toff = 1 + +/mob/living/silicon/robot/binarycheck() + if(is_component_functioning("comms")) + return 1 + return 0 + +//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO +//Improved /N +/mob/living/silicon/robot/Destroy() + if(mmi && mind)//Safety for when a cyborg gets dust()ed. Or there is no MMI inside. + var/turf/T = get_turf(loc)//To hopefully prevent run time errors. + if(T) mmi.loc = T + if(mmi.brainmob) + mind.transfer_to(mmi.brainmob) + mmi.update_icon() + else + to_chat(src, "Oops! Something went very wrong, your MMI was unable to receive your mind. You have been ghosted. Please make a bug report so we can fix this bug.") + ghostize() + error("A borg has been destroyed, but its MMI lacked a brainmob, so the mind could not be transferred. Player: [ckey].") + mmi = null + if(connected_ai) + connected_ai.connected_robots -= src + QDEL_NULL(wires) + QDEL_NULL(module) + QDEL_NULL(camera) + QDEL_NULL(cell) + QDEL_NULL(robot_suit) + QDEL_NULL(spark_system) + return ..() + +/mob/living/silicon/robot/proc/pick_module() + if(module) + return + var/list/modules = list("Standard", "Engineering", "Medical", "Miner", "Janitor", "Service", "Security") + if(islist(force_modules) && force_modules.len) + modules = force_modules.Copy() + if(security_level == (SEC_LEVEL_GAMMA || SEC_LEVEL_EPSILON) || crisis) + to_chat(src, "Crisis mode active. The combat module is now available.") + modules += "Combat" + if(mmi != null && mmi.alien) + modules = list("Hunter") + modtype = input("Please, select a module!", "Robot", null, null) as null|anything in modules + if(!modtype) + return + designation = modtype + var/module_sprites[0] //Used to store the associations between sprite names and sprite index. + + if(module) + return + + switch(modtype) + if("Standard") + module = new /obj/item/robot_module/standard(src) + module.channels = list("Service" = 1) + module_sprites["Basic"] = "robot_old" + module_sprites["Android"] = "droid" + module_sprites["Default"] = "Standard" + module_sprites["Noble-STD"] = "Noble-STD" + + if("Service") + module = new /obj/item/robot_module/butler(src) + module.channels = list("Service" = 1) + module_sprites["Waitress"] = "Service" + module_sprites["Kent"] = "toiletbot" + module_sprites["Bro"] = "Brobot" + module_sprites["Rich"] = "maximillion" + module_sprites["Default"] = "Service2" + module_sprites["Standard"] = "Standard-Serv" + module_sprites["Noble-SRV"] = "Noble-SRV" + module_sprites["Cricket"] = "Cricket-SERV" + + if("Miner") + module = new /obj/item/robot_module/miner(src) + module.channels = list("Supply" = 1) + if(camera && "Robots" in camera.network) + camera.network.Add("Mining Outpost") + module_sprites["Basic"] = "Miner_old" + module_sprites["Advanced Droid"] = "droid-miner" + module_sprites["Treadhead"] = "Miner" + module_sprites["Standard"] = "Standard-Mine" + module_sprites["Noble-DIG"] = "Noble-DIG" + module_sprites["Cricket"] = "Cricket-MINE" + + if("Medical") + module = new /obj/item/robot_module/medical(src) + module.channels = list("Medical" = 1) + if(camera && "Robots" in camera.network) + camera.network.Add("Medical") + module_sprites["Basic"] = "Medbot" + module_sprites["Surgeon"] = "surgeon" + module_sprites["Advanced Droid"] = "droid-medical" + module_sprites["Needles"] = "medicalrobot" + module_sprites["Standard"] = "Standard-Medi" + module_sprites["Noble-MED"] = "Noble-MED" + module_sprites["Cricket"] = "Cricket-MEDI" + status_flags &= ~CANPUSH + + if("Security") + module = new /obj/item/robot_module/security(src) + module.channels = list("Security" = 1) + module_sprites["Basic"] = "secborg" + module_sprites["Red Knight"] = "Security" + module_sprites["Black Knight"] = "securityrobot" + module_sprites["Bloodhound"] = "bloodhound" + module_sprites["Standard"] = "Standard-Secy" + module_sprites["Noble-SEC"] = "Noble-SEC" + module_sprites["Cricket"] = "Cricket-SEC" + status_flags &= ~CANPUSH + + if("Engineering") + module = new /obj/item/robot_module/engineering(src) + module.channels = list("Engineering" = 1) + if(camera && "Robots" in camera.network) + camera.network.Add("Engineering") + module_sprites["Basic"] = "Engineering" + module_sprites["Antique"] = "engineerrobot" + module_sprites["Landmate"] = "landmate" + module_sprites["Standard"] = "Standard-Engi" + module_sprites["Noble-ENG"] = "Noble-ENG" + module_sprites["Cricket"] = "Cricket-ENGI" + magpulse = 1 + + if("Janitor") + module = new /obj/item/robot_module/janitor(src) + module.channels = list("Service" = 1) + module_sprites["Basic"] = "JanBot2" + module_sprites["Mopbot"] = "janitorrobot" + module_sprites["Mop Gear Rex"] = "mopgearrex" + module_sprites["Standard"] = "Standard-Jani" + module_sprites["Noble-CLN"] = "Noble-CLN" + module_sprites["Cricket"] = "Cricket-JANI" + + if("Combat") + module = new /obj/item/robot_module/combat(src) + module.channels = list("Security" = 1) + icon_state = "droidcombat" + + if("Hunter") + module = new /obj/item/robot_module/alien/hunter(src) + icon_state = "xenoborg-state-a" + modtype = "Xeno-Hu" + feedback_inc("xeborg_hunter",1) + + + //languages + module.add_languages(src) + //subsystems + module.add_subsystems_and_actions(src) + + //Custom_sprite check and entry + if(custom_sprite && check_sprite("[ckey]-[modtype]")) + module_sprites["Custom"] = "[src.ckey]-[modtype]" + + hands.icon_state = lowertext(module.module_type) + feedback_inc("cyborg_[lowertext(modtype)]",1) + rename_character(real_name, get_default_name()) + + if(modtype == "Medical" || modtype == "Security" || modtype == "Combat") + status_flags &= ~CANPUSH + + choose_icon(6,module_sprites) + if(!static_radio_channels) + radio.config(module.channels) + notify_ai(2) + +/mob/living/silicon/robot/proc/reset_module() + notify_ai(2) + + uneq_all() + sight_mode = null + hands.icon_state = "nomod" + icon_state = "robot" + module.remove_subsystems_and_actions(src) + QDEL_NULL(module) + + camera.network.Remove(list("Engineering", "Medical", "Mining Outpost")) + rename_character(real_name, get_default_name("Default")) + languages = list() + speech_synthesizer_langs = list() + + update_icons() + update_headlamp() + + speed = 0 // Remove upgrades. + ionpulse = FALSE + magpulse = FALSE + add_language("Robot Talk", 1) + + status_flags |= CANPUSH + +//for borg hotkeys, here module refers to borg inv slot, not core module +/mob/living/silicon/robot/verb/cmd_toggle_module(module as num) + set name = "Toggle Module" + set hidden = 1 + toggle_module(module) + +/mob/living/silicon/robot/verb/cmd_unequip_module() + set name = "Unequip Module" + set hidden = 1 + uneq_active() + +// this verb lets cyborgs see the stations manifest +/mob/living/silicon/robot/verb/cmd_station_manifest() + set category = "Robot Commands" + set name = "Show Station Manifest" + show_station_manifest() + +/mob/living/silicon/robot/proc/self_diagnosis() + if(!is_component_functioning("diagnosis unit")) + return null + + var/dat = "[src.name] Self-Diagnosis Report\n" + for(var/V in components) + var/datum/robot_component/C = components[V] + if(C.installed == 0) + dat += "[C.name]
        MISSING
        " + else + dat += "[C.name][C.installed == -1 ? "
        DESTROYED" : ""]
        Brute Damage:[C.brute_damage]
        Electronics Damage:[C.electronics_damage]
        Powered:[C.is_powered() ? "Yes" : "No"]
        Toggled:[ C.toggled ? "Yes" : "No"]

        " + return dat + +/mob/living/silicon/robot/verb/self_diagnosis_verb() + set category = "Robot Commands" + set name = "Self Diagnosis" + + if(!is_component_functioning("diagnosis unit")) + to_chat(src, "Your self-diagnosis component isn't functioning.") + + var/dat = self_diagnosis() + src << browse(dat, "window=robotdiagnosis") + + +/mob/living/silicon/robot/verb/toggle_component() + set category = "Robot Commands" + set name = "Toggle Component" + set desc = "Toggle a component, conserving power." + + var/list/installed_components = list() + for(var/V in components) + if(V == "power cell") continue + var/datum/robot_component/C = components[V] + if(C.installed) + installed_components += V + + var/toggle = input(src, "Which component do you want to toggle?", "Toggle Component") as null|anything in installed_components + if(!toggle) + return + + var/datum/robot_component/C = components[toggle] + C.toggle() + to_chat(src, "You [C.toggled ? "enable" : "disable"] [C.name].") + +/mob/living/silicon/robot/proc/sensor_mode() + set name = "Set Sensor Augmentation" + set desc = "Augment visual feed with internal sensor overlays." + set category = "Robot Commands" + toggle_sensor_mode() + +/mob/living/silicon/robot/proc/add_robot_verbs() + src.verbs |= robot_verbs_default + src.verbs |= silicon_subsystems + +/mob/living/silicon/robot/proc/remove_robot_verbs() + src.verbs -= robot_verbs_default + src.verbs -= silicon_subsystems + +/mob/living/silicon/robot/proc/ionpulse() + if(!ionpulse_on) + return + + if(cell.charge <= 50) + toggle_ionpulse() + return + + cell.charge -= 25 // 500 steps on a default cell. + return 1 + +/mob/living/silicon/robot/proc/toggle_ionpulse() + if(!ionpulse) + to_chat(src, "No thrusters are installed!") + return + + if(!ion_trail) + ion_trail = new + ion_trail.set_up(src) + + ionpulse_on = !ionpulse_on + to_chat(src, "You [ionpulse_on ? null :"de"]activate your ion thrusters.") + if(ionpulse_on) + ion_trail.start() + else + ion_trail.stop() + if(thruster_button) + thruster_button.icon_state = "ionpulse[ionpulse_on]" + +/mob/living/silicon/robot/blob_act(obj/structure/blob/B) + if(stat != DEAD) + adjustBruteLoss(30) + else + gib() + return TRUE + +// this function displays the cyborgs current cell charge in the stat panel +/mob/living/silicon/robot/proc/show_cell_power() + if(cell) + stat(null, text("Charge Left: [cell.charge]/[cell.maxcharge]")) + else + stat(null, text("No Cell Inserted!")) + + +// update the status screen display +/mob/living/silicon/robot/Stat() + ..() + statpanel("Status") + if(client.statpanel == "Status") + show_cell_power() + var/total_user_contents = GetAllContents() + if(locate(/obj/item/gps/cyborg) in total_user_contents) + var/turf/T = get_turf(src) + stat(null, "GPS: [COORD(T)]") + +/mob/living/silicon/robot/restrained() + return 0 + +/mob/living/silicon/robot/InCritical() + return low_power_mode + +/mob/living/silicon/robot/ex_act(severity) + switch(severity) + if(1.0) + gib() + return + if(2.0) + if(stat != 2) + adjustBruteLoss(60) + adjustFireLoss(60) + if(3.0) + if(stat != 2) + adjustBruteLoss(30) + return + + +/mob/living/silicon/robot/bullet_act(var/obj/item/projectile/Proj) + ..(Proj) + if(prob(75) && Proj.damage > 0) spark_system.start() + return 2 + + +/mob/living/silicon/robot/attackby(obj/item/W, mob/user, params) + // Check if the user is trying to insert another component like a radio, actuator, armor etc. + if(istype(W, /obj/item/robot_parts/robot_component) && opened) + for(var/V in components) + var/datum/robot_component/C = components[V] + if(!C.installed && istype(W, C.external_type)) + C.installed = 1 + C.wrapped = W + C.install() + user.drop_item() + W.loc = null + + var/obj/item/robot_parts/robot_component/WC = W + if(istype(WC)) + C.brute_damage = WC.brute + C.electronics_damage = WC.burn + + to_chat(usr, "You install the [W.name].") + + return + + if(istype(W, /obj/item/stack/cable_coil) && user.a_intent == INTENT_HELP && (wiresexposed || istype(src, /mob/living/silicon/robot/drone))) + user.changeNext_move(CLICK_CD_MELEE) + if(!getFireLoss()) + to_chat(user, "Nothing to fix!") + return + else if(!getFireLoss(TRUE)) + to_chat(user, "The damaged components are beyond saving!") + return + var/obj/item/stack/cable_coil/coil = W + adjustFireLoss(-30) + updatehealth() + add_fingerprint(user) + coil.use(1) + user.visible_message("\The [user] fixes some of the burnt wires on \the [src] with \the [coil].") + + else if(istype(W, /obj/item/stock_parts/cell) && opened) // trying to put a cell inside + var/datum/robot_component/cell/C = components["power cell"] + if(wiresexposed) + to_chat(user, "Close the panel first.") + else if(cell) + to_chat(user, "There is a power cell already installed.") + else + user.drop_item() + W.loc = src + cell = W + to_chat(user, "You insert the power cell.") + + C.installed = 1 + C.wrapped = W + C.install() + C.external_type = W.type // Update the cell component's `external_type` to the path of new cell + //This will mean that removing and replacing a power cell will repair the mount, but I don't care at this point. ~Z + C.brute_damage = 0 + C.electronics_damage = 0 + diag_hud_set_borgcell() + + else if(istype(W, /obj/item/encryptionkey/) && opened) + if(radio)//sanityyyyyy + radio.attackby(W,user)//GTFO, you have your own procs + else + to_chat(user, "Unable to locate a radio.") + + else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) // trying to unlock the interface with an ID card + if(emagged)//still allow them to open the cover + to_chat(user, "The interface seems slightly damaged.") + if(opened) + to_chat(user, "You must close the cover to swipe an ID card.") + else + if(allowed(W)) + locked = !locked + to_chat(user, "You [ locked ? "lock" : "unlock"] [src]'s interface.") + update_icons() + else + to_chat(user, "Access denied.") + + else if(istype(W, /obj/item/borg/upgrade/)) + var/obj/item/borg/upgrade/U = W + if(!opened) + to_chat(user, "You must access the borg's internals!") + else if(!src.module && U.require_module) + to_chat(user, "The borg must choose a module before it can be upgraded!") + else if(U.locked) + to_chat(user, "The upgrade is locked and cannot be used yet!") + else + if(!user.drop_item()) + return + if(U.action(src)) + to_chat(user, "You apply the upgrade to [src].") + U.forceMove(src) + else + to_chat(user, "Upgrade error.") + + else if(istype(W, /obj/item/mmi_radio_upgrade)) + if(!opened) + to_chat(user, "You must access the borg's internals!") + return + else if(!mmi) + to_chat(user, "This cyborg does not have an MMI to augment!") + return + else if(mmi.radio) + to_chat(user, "A radio upgrade is already installed in the MMI!") + return + else if(user.drop_item()) + to_chat(user, "You apply the upgrade to [src].") + to_chat(src, "MMI radio capability installed.") + mmi.install_radio() + qdel(W) + else + return ..() + +/mob/living/silicon/robot/wirecutter_act(mob/user, obj/item/I) + if(!opened) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = 0)) + return + if(wiresexposed) + wires.Interact(user) + +/mob/living/silicon/robot/multitool_act(mob/user, obj/item/I) + if(!opened) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = 0)) + return + if(wiresexposed) + wires.Interact(user) + +/mob/living/silicon/robot/screwdriver_act(mob/user, obj/item/I) + if(!opened) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = 0)) + return + if(!cell) // haxing + wiresexposed = !wiresexposed + to_chat(user, "The wires have been [wiresexposed ? "exposed" : "unexposed"]") + update_icons() + I.play_tool_sound(user, I.tool_volume) + else //radio check + if(radio) + radio.screwdriver_act(user, I)//Push it to the radio to let it handle everything + else + to_chat(user, "Unable to locate a radio.") + update_icons() + +/mob/living/silicon/robot/crowbar_act(mob/user, obj/item/I) + if(user.a_intent != INTENT_HELP) + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(!opened) + if(locked) + to_chat(user, "The cover is locked and cannot be opened.") + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + to_chat(user, "You open the cover.") + opened = TRUE + update_icons() + return + else if(cell) + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + to_chat(user, "You close the cover.") + opened = FALSE + update_icons() + return + else if(wiresexposed && wires.IsAllCut()) + //Cell is out, wires are exposed, remove MMI, produce damaged chassis, baleet original mob. + if(!mmi) + to_chat(user, "[src] has no brain to remove.") + return + to_chat(user, "You jam the crowbar into the robot and begin levering the securing bolts...") + if(I.use_tool(src, user, 30, volume = I.tool_volume)) + user.visible_message("[user] deconstructs [src]!", "You unfasten the securing bolts, and [src] falls to pieces!") + deconstruct() + return + // Okay we're not removing the cell or an MMI, but maybe something else? + var/list/removable_components = list() + for(var/V in components) + if(V == "power cell") + continue + var/datum/robot_component/C = components[V] + if(C.installed == 1 || C.installed == -1) + removable_components += V + if(module) + removable_components += module.custom_removals + var/remove = input(user, "Which component do you want to pry out?", "Remove Component") as null|anything in removable_components + if(!remove) + return + if(module && module.handle_custom_removal(remove, user, I)) + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + var/datum/robot_component/C = components[remove] + var/obj/item/robot_parts/robot_component/thing = C.wrapped + to_chat(user, "You remove \the [thing].") + if(istype(thing)) + thing.brute = C.brute_damage + thing.burn = C.electronics_damage + + thing.loc = loc + var/was_installed = C.installed + C.installed = 0 + if(was_installed == 1) + C.uninstall() + + + + +/mob/living/silicon/robot/attacked_by(obj/item/I, mob/living/user, def_zone) + if(I.force && I.damtype != STAMINA && stat != DEAD) //only sparks if real damage is dealt. + spark_system.start() + ..() + +/mob/living/silicon/robot/emag_act(user as mob) + if(!ishuman(user) && !issilicon(user)) + return + var/mob/living/M = user + if(!opened)//Cover is closed + if(!is_emaggable) + to_chat(user, "The emag sparks, and flashes red. This mechanism does not appear to be emaggable.") + else if(locked) + to_chat(user, "You emag the cover lock.") + locked = 0 + else + to_chat(user, "The cover is already unlocked.") + return + + if(opened)//Cover is open + if(emagged) return//Prevents the X has hit Y with Z message also you cant emag them twice + if(wiresexposed) + to_chat(user, "You must close the panel first") + return + else + sleep(6) + emagged = 1 + SetLockdown(1) //Borgs were getting into trouble because they would attack the emagger before the new laws were shown + if(src.hud_used) + src.hud_used.update_robot_modules_display() //Shows/hides the emag item if the inventory screen is already open. + disconnect_from_ai() + to_chat(user, "You emag [src]'s interface.") +// message_admins("[key_name_admin(user)] emagged cyborg [key_name_admin(src)]. Laws overridden.") + log_game("[key_name(user)] emagged cyborg [key_name(src)]. Laws overridden.") + clear_supplied_laws() + clear_inherent_laws() + laws = new /datum/ai_laws/syndicate_override + var/time = time2text(world.realtime,"hh:mm:ss") + lawchanges.Add("[time] : [M.name]([M.key]) emagged [name]([key])") + set_zeroth_law("Only [M.real_name] and people [M.p_they()] designate[M.p_s()] as being such are Syndicate Agents.") + to_chat(src, "ALERT: Foreign software detected.") + sleep(5) + to_chat(src, "Initiating diagnostics...") + sleep(20) + to_chat(src, "SynBorg v1.7 loaded.") + sleep(5) + to_chat(src, "LAW SYNCHRONISATION ERROR") + sleep(5) + to_chat(src, "Would you like to send a report to NanoTraSoft? Y/N") + sleep(10) + to_chat(src, "> N") + sleep(20) + to_chat(src, "ERRORERRORERROR") + to_chat(src, "Obey these laws:") + laws.show_laws(src) + to_chat(src, "ALERT: [M.real_name] is your new master. Obey your new laws and [M.p_their()] commands.") + SetLockdown(0) + if(src.module && istype(src.module, /obj/item/robot_module/miner)) + for(var/obj/item/pickaxe/drill/cyborg/D in src.module.modules) + qdel(D) + src.module.modules += new /obj/item/pickaxe/drill/cyborg/diamond(src.module) + src.module.rebuild() + if(src.module && istype(src.module, /obj/item/robot_module/medical)) + for(var/obj/item/borg_defib/F in src.module.modules) + F.safety = 0 + if(module) + module.module_type = "Malf" // For the cool factor + update_module_icon() + update_icons() + return + +/mob/living/silicon/robot/verb/unlock_own_cover() + set category = "Robot Commands" + set name = "Unlock Cover" + set desc = "Unlocks your own cover if it is locked. You can not lock it again. A human will have to lock it for you." + if(locked) + switch(alert("You can not lock your cover again, are you sure?\n (You can still ask for a human to lock it)", "Unlock Own Cover", "Yes", "No")) + if("Yes") + locked = 0 + update_icons() + to_chat(usr, "You unlock your cover.") + +/mob/living/silicon/robot/attack_ghost(mob/user) + if(wiresexposed) + wires.Interact(user) + else + ..() //this calls the /mob/living/attack_ghost proc for the ghost health/cyborg analyzer + +/mob/living/silicon/robot/proc/allowed(obj/item/I) + var/obj/dummy = new /obj(null) // Create a dummy object to check access on as to avoid having to snowflake check_access on every mob + dummy.req_access = req_access + dummy.req_one_access = req_one_access + + if(dummy.check_access(I)) + qdel(dummy) + return 1 + + qdel(dummy) + return 0 + +/mob/living/silicon/robot/update_icons() + + overlays.Cut() + if(stat != DEAD && !(paralysis || stunned || IsWeakened() || low_power_mode)) //Not dead, not stunned. + if(custom_panel in custom_eye_names) + overlays += "eyes-[custom_panel]" + else + overlays += "eyes-[icon_state]" + else + overlays -= "eyes" + + if(opened) + var/panelprefix = "ov" + if(custom_sprite) //Custom borgs also have custom panels, heh + panelprefix = "[ckey]" + + if(custom_panel in custom_panel_names) //For default borgs with different panels + panelprefix = custom_panel + + if(wiresexposed) + overlays += "[panelprefix]-openpanel +w" + else if(cell) + overlays += "[panelprefix]-openpanel +c" + else + overlays += "[panelprefix]-openpanel -c" + + var/combat = list("Combat") + if(modtype in combat) + if(base_icon == "") + base_icon = icon_state + if(module_active && istype(module_active,/obj/item/borg/combat/mobility)) + icon_state = "[base_icon]-roll" + else + icon_state = base_icon + if(module) + for(var/obj/item/borg/combat/shield/S in module.modules) + if(activated(S)) + overlays += "[base_icon]-shield" + update_fire() + +/mob/living/silicon/robot/proc/installed_modules() + if(weapon_lock) + to_chat(src, "Weapon lock active, unable to use modules! Count:[weaponlock_time]") + return + + if(!module) + pick_module() + return + var/dat = {"Close +
        +
        + Activated Modules +
        + + + + +
        Module 1:[module_state_1 ? "[module_state_1]" : "No Module"]
        Module 2:[module_state_2 ? "[module_state_2]" : "No Module"]
        Module 3:[module_state_3 ? "[module_state_3]" : "No Module"]

        + Installed Modules

        + + "} + for(var/obj in module.modules) + if(!obj) + dat += text("") + else if(activated(obj)) + dat += text("") + else + dat += text("") + if(emagged || weapons_unlock) + if(activated(module.emag)) + dat += text("") + else + dat += text("") + dat += "
        Resource depleted
        [obj]Activated
        [obj]Activate
        [module.emag]Activated
        [module.emag]Activate
        " +/* + if(activated(obj)) + dat += text("[obj]: \[Activated | Deactivate\]
        ") + else + dat += text("[obj]: \[Activate | Deactivated\]
        ") +*/ + var/datum/browser/popup = new(src, "robotmod", "Modules") + popup.set_content(dat) + popup.open() + + +/mob/living/silicon/robot/Topic(href, href_list) + if(..()) + return 1 + + if(usr != src) + return 1 + + if(href_list["mach_close"]) + var/t1 = text("window=[href_list["mach_close"]]") + unset_machine() + src << browse(null, t1) + return 1 + + if(href_list["showalerts"]) + subsystem_alarm_monitor() + return 1 + + if(href_list["mod"]) + var/obj/item/O = locate(href_list["mod"]) + if(istype(O) && (O.loc == src)) + O.attack_self(src) + return 1 + + if(href_list["act"]) + var/obj/item/O = locate(href_list["act"]) + if(!istype(O) || !(O.loc == src || O.loc == src.module)) + return 1 + + activate_module(O) + installed_modules() + + if(href_list["deact"]) + var/obj/item/O = locate(href_list["deact"]) + if(activated(O)) + if(module_state_1 == O) + module_state_1 = null + contents -= O + else if(module_state_2 == O) + module_state_2 = null + contents -= O + else if(module_state_3 == O) + module_state_3 = null + contents -= O + else + to_chat(src, "Module isn't activated.") + else + to_chat(src, "Module isn't activated") + installed_modules() + return 1 + + return 1 + +/mob/living/silicon/robot/proc/radio_menu() + radio.interact(src)//Just use the radio's Topic() instead of bullshit special-snowflake code + +/mob/living/silicon/robot/proc/control_headlamp() + if(stat || lamp_recharging || low_power_mode) + to_chat(src, "This function is currently offline.") + return + +//Some sort of magical "modulo" thing which somehow increments lamp power by 2, until it hits the max and resets to 0. + lamp_intensity = (lamp_intensity+2) % (lamp_max+2) + to_chat(src, "[lamp_intensity ? "Headlamp power set to Level [lamp_intensity/2]" : "Headlamp disabled."]") + update_headlamp() + +/mob/living/silicon/robot/proc/update_headlamp(var/turn_off = 0, var/cooldown = 100) + set_light(0) + + if(lamp_intensity && (turn_off || stat || low_power_mode)) + to_chat(src, "Your headlamp has been deactivated.") + lamp_intensity = 0 + lamp_recharging = 1 + spawn(cooldown) //10 seconds by default, if the source of the deactivation does not keep stat that long. + lamp_recharging = 0 + else + set_light(light_range + lamp_intensity) + + if(lamp_button) + lamp_button.icon_state = "lamp[lamp_intensity]" + + update_icons() + +/mob/living/silicon/robot/proc/deconstruct() + var/turf/T = get_turf(src) + if(robot_suit) + robot_suit.forceMove(T) + robot_suit.l_leg.forceMove(T) + robot_suit.l_leg = null + robot_suit.r_leg.forceMove(T) + robot_suit.r_leg = null + new /obj/item/stack/cable_coil(T, robot_suit.chest.wired) + robot_suit.chest.forceMove(T) + robot_suit.chest.wired = FALSE + robot_suit.chest = null + robot_suit.l_arm.forceMove(T) + robot_suit.l_arm = null + robot_suit.r_arm.forceMove(T) + robot_suit.r_arm = null + robot_suit.head.forceMove(T) + robot_suit.head.flash1.forceMove(T) + robot_suit.head.flash1.burn_out() + robot_suit.head.flash1 = null + robot_suit.head.flash2.forceMove(T) + robot_suit.head.flash2.burn_out() + robot_suit.head.flash2 = null + robot_suit.head = null + robot_suit.updateicon() + else + new /obj/item/robot_parts/robot_suit(T) + new /obj/item/robot_parts/l_leg(T) + new /obj/item/robot_parts/r_leg(T) + new /obj/item/stack/cable_coil(T, 1) + new /obj/item/robot_parts/chest(T) + new /obj/item/robot_parts/l_arm(T) + new /obj/item/robot_parts/r_arm(T) + new /obj/item/robot_parts/head(T) + var/b + for(b=0, b!=2, b++) + var/obj/item/flash/F = new /obj/item/flash(T) + F.burn_out() + if(cell) //Sanity check. + cell.forceMove(T) + cell = null + qdel(src) + +#define BORG_CAMERA_BUFFER 30 +/mob/living/silicon/robot/Move(a, b, flag) + var/oldLoc = src.loc + . = ..() + if(.) + if(src.camera) + if(!updating) + updating = 1 + spawn(BORG_CAMERA_BUFFER) + if(oldLoc != src.loc) + cameranet.updatePortableCamera(src.camera) + updating = 0 + if(module) + if(module.type == /obj/item/robot_module/janitor) + var/turf/tile = loc + if(isturf(tile)) + var/floor_only = TRUE + for(var/A in tile) + if(istype(A, /obj/effect)) + if(is_cleanable(A)) + var/obj/effect/decal/cleanable/blood/B = A + if(istype(B) && B.off_floor) + floor_only = FALSE + else + qdel(A) + else if(istype(A, /obj/item)) + var/obj/item/cleaned_item = A + cleaned_item.clean_blood() + else if(istype(A, /mob/living/carbon/human)) + var/mob/living/carbon/human/cleaned_human = A + if(cleaned_human.lying) + if(cleaned_human.head) + cleaned_human.head.clean_blood() + cleaned_human.update_inv_head(0,0) + if(cleaned_human.wear_suit) + cleaned_human.wear_suit.clean_blood() + cleaned_human.update_inv_wear_suit(0,0) + else if(cleaned_human.w_uniform) + cleaned_human.w_uniform.clean_blood() + cleaned_human.update_inv_w_uniform(0,0) + if(cleaned_human.shoes) + cleaned_human.shoes.clean_blood() + cleaned_human.update_inv_shoes(0,0) + cleaned_human.clean_blood() + to_chat(cleaned_human, "[src] cleans your face!") + if(floor_only) + tile.clean_blood() + return +#undef BORG_CAMERA_BUFFER + +/mob/living/silicon/robot/proc/self_destruct() + if(emagged) + if(mmi) + qdel(mmi) + explosion(src.loc,1,2,4,flame_range = 2) + else + explosion(src.loc,-1,0,2) + gib() + return + +/mob/living/silicon/robot/proc/UnlinkSelf() + disconnect_from_ai() + lawupdate = 0 + lockcharge = 0 + canmove = 1 + scrambledcodes = 1 + //Disconnect it's camera so it's not so easily tracked. + QDEL_NULL(src.camera) + // I'm trying to get the Cyborg to not be listed in the camera list + // Instead of being listed as "deactivated". The downside is that I'm going + // to have to check if every camera is null or not before doing anything, to prevent runtime errors. + // I could change the network to null but I don't know what would happen, and it seems too hacky for me. + +/mob/living/silicon/robot/proc/ResetSecurityCodes() + set category = "Robot Commands" + set name = "Reset Identity Codes" + set desc = "Scrambles your security and identification codes and resets your current buffers. Unlocks you and but permanently severs you from your AI and the robotics console and will deactivate your camera system." + + var/mob/living/silicon/robot/R = src + + if(R) + R.UnlinkSelf() + to_chat(R, "Buffers flushed and reset. Camera system shutdown. All systems operational.") + src.verbs -= /mob/living/silicon/robot/proc/ResetSecurityCodes + +/mob/living/silicon/robot/mode() + set name = "Activate Held Object" + set category = "IC" + set src = usr + + var/obj/item/W = get_active_hand() + if(W) + W.attack_self(src) + + return + +/mob/living/silicon/robot/proc/SetLockdown(var/state = 1) + // They stay locked down if their wire is cut. + if(wires.LockedCut()) + state = 1 + if(state) + throw_alert("locked", /obj/screen/alert/locked) + else + clear_alert("locked") + lockcharge = state + update_canmove() + +/mob/living/silicon/robot/proc/choose_icon(var/triesleft, var/list/module_sprites) + + if(triesleft<1 || !module_sprites.len) + return + else + triesleft-- + + var/icontype + lockcharge = 1 //Locks borg until it select an icon to avoid secborgs running around with a standard sprite + icontype = input("Select an icon! [triesleft ? "You have [triesleft] more chances." : "This is your last try."]", "Robot", null, null) in module_sprites + + if(icontype) + if(icontype == "Custom") + icon = 'icons/mob/custom_synthetic/custom-synthetic.dmi' + else + icon = 'icons/mob/robots.dmi' + icon_state = module_sprites[icontype] + if(icontype == "Bro") + module.module_type = "Brobot" + update_module_icon() + lockcharge = null + var/list/names = splittext(icontype, "-") + custom_panel = trim(names[1]) + else + to_chat(src, "Something is badly wrong with the sprite selection. Harass a coder.") + icon_state = module_sprites[1] + lockcharge = null + return + + update_icons() + + if(triesleft >= 1) + var/choice = input("Look at your icon - is this what you want?") in list("Yes","No") + if(choice=="No") + choose_icon(triesleft, module_sprites) + return + else + triesleft = 0 + return + else + to_chat(src, "Your icon has been set. You now require a module reset to change it.") + +/mob/living/silicon/robot/proc/notify_ai(var/notifytype, var/oldname, var/newname) + if(!connected_ai) + return + switch(notifytype) + if(1) //New Cyborg + to_chat(connected_ai, "

        NOTICE - New cyborg connection detected: [name]
        ") + if(2) //New Module + to_chat(connected_ai, "

        NOTICE - Cyborg module change detected: [name] has loaded the [designation] module.
        ") + if(3) //New Name + to_chat(connected_ai, "

        NOTICE - Cyborg reclassification detected: [oldname] is now designated as [newname].
        ") + +/mob/living/silicon/robot/proc/disconnect_from_ai() + if(connected_ai) + sync() // One last sync attempt + connected_ai.connected_robots -= src + connected_ai = null + +/mob/living/silicon/robot/proc/connect_to_ai(var/mob/living/silicon/ai/AI) + if(AI && AI != connected_ai) + disconnect_from_ai() + connected_ai = AI + connected_ai.connected_robots |= src + notify_ai(1) + sync() + +/mob/living/silicon/robot/adjustOxyLoss(var/amount) + if(suiciding) + return ..() + else + return STATUS_UPDATE_NONE + +/mob/living/silicon/robot/regenerate_icons() + ..() + update_module_icon() + +/mob/living/silicon/robot/deathsquad + base_icon = "nano_bloodhound" + icon_state = "nano_bloodhound" + designation = "SpecOps" + lawupdate = 0 + scrambledcodes = 1 + req_one_access = list(ACCESS_CENT_SPECOPS) + ionpulse = 1 + magpulse = 1 + pdahide = 1 + eye_protection = 2 // Immunity to flashes and the visual part of flashbangs + ear_protection = 1 // Immunity to the audio part of flashbangs + allow_rename = FALSE + modtype = "Commando" + faction = list("nanotrasen") + is_emaggable = FALSE + +/mob/living/silicon/robot/deathsquad/New(loc) + ..() + cell = new /obj/item/stock_parts/cell/hyper(src) + +/mob/living/silicon/robot/deathsquad/init() + laws = new /datum/ai_laws/deathsquad + module = new /obj/item/robot_module/deathsquad(src) + + aiCamera = new/obj/item/camera/siliconcam/robot_camera(src) + radio = new /obj/item/radio/borg/deathsquad(src) + radio.recalculateChannels() + + playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0) + +/mob/living/silicon/robot/combat + base_icon = "droidcombat" + icon_state = "droidcombat" + modtype = "Combat" + designation = "Combat" + +/mob/living/silicon/robot/combat/init() + ..() + module = new /obj/item/robot_module/combat(src) + module.channels = list("Security" = 1) + //languages + module.add_languages(src) + //subsystems + module.add_subsystems_and_actions(src) + + status_flags &= ~CANPUSH + + radio.config(module.channels) + notify_ai(2) + +/mob/living/silicon/robot/ert + designation = "ERT" + lawupdate = 0 + scrambledcodes = 1 + req_one_access = list(ACCESS_CENT_SPECOPS) + ionpulse = 1 + + force_modules = list("Engineering", "Medical", "Security") + static_radio_channels = 1 + allow_rename = FALSE + weapons_unlock = TRUE + + +/mob/living/silicon/robot/ert/init() + laws = new /datum/ai_laws/ert_override + radio = new /obj/item/radio/borg/ert(src) + radio.recalculateChannels() + aiCamera = new/obj/item/camera/siliconcam/robot_camera(src) + +/mob/living/silicon/robot/ert/New(loc, cyborg_unlock) + ..(loc) + cell = new /obj/item/stock_parts/cell/hyper(src) + var/rnum = rand(1,1000) + var/borgname = "ERT [rnum]" + name = borgname + custom_name = borgname + real_name = name + mind = new + mind.current = src + mind.original = src + mind.assigned_role = SPECIAL_ROLE_ERT + mind.special_role = SPECIAL_ROLE_ERT + if(cyborg_unlock) + crisis = 1 + if(!(mind in SSticker.minds)) + SSticker.minds += mind + SSticker.mode.ert += mind + +/mob/living/silicon/robot/ert/gamma + crisis = 1 + +/mob/living/silicon/robot/emp_act(severity) + ..() + switch(severity) + if(1) + disable_component("comms", 160) + if(2) + disable_component("comms", 60) + +/mob/living/silicon/robot/extinguish_light() + update_headlamp(1, 150) + +/mob/living/silicon/robot/rejuvenate() + ..() + var/brute = 1000 + var/burn = 1000 + var/list/datum/robot_component/borked_parts = get_damaged_components(TRUE, TRUE, TRUE, TRUE) + for(var/datum/robot_component/borked_part in borked_parts) + brute = borked_part.brute_damage + burn = borked_part.electronics_damage + borked_part.installed = 1 + borked_part.wrapped = new borked_part.external_type + if(ispath(borked_part.external_type, /obj/item/stock_parts/cell)) // is the broken part a cell? + cell = new borked_part.external_type // borgs that have their cell destroyed have their `cell` var set to null. we need create a new cell for them based on their old cell type. + borked_part.heal_damage(brute,burn) + borked_part.install() + +/mob/living/silicon/robot/proc/check_sprite(spritename) + . = FALSE + + var/static/all_borg_icon_states = icon_states('icons/mob/custom_synthetic/custom-synthetic.dmi') + if(spritename in all_borg_icon_states) + . = TRUE + +/mob/living/silicon/robot/check_eye_prot() + return eye_protection + +/mob/living/silicon/robot/check_ear_prot() + return ear_protection + +/mob/living/silicon/robot/update_sight() + if(!client) + return + + if(stat == DEAD) + grant_death_vision() + return + + see_invisible = initial(see_invisible) + see_in_dark = initial(see_in_dark) + sight = initial(sight) + lighting_alpha = initial(lighting_alpha) + + if(client.eye != src) + var/atom/A = client.eye + if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. + return + + if(sight_mode & BORGMESON) + sight |= SEE_TURFS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + + if(sight_mode & BORGXRAY) + sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) + see_invisible = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + see_in_dark = 8 + + if(sight_mode & BORGTHERM) + sight |= SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + + SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) + sync_lighting_plane_alpha() diff --git a/code/modules/mob/living/silicon/robot/robot_damage.dm b/code/modules/mob/living/silicon/robot/robot_damage.dm index 81700464ac0..c73d566b729 100644 --- a/code/modules/mob/living/silicon/robot/robot_damage.dm +++ b/code/modules/mob/living/silicon/robot/robot_damage.dm @@ -1,179 +1,179 @@ -/mob/living/silicon/robot/updatehealth(reason = "none given") - if(status_flags & GODMODE) - health = maxHealth - stat = CONSCIOUS - return - health = maxHealth - (getOxyLoss() + getFireLoss() + getBruteLoss()) - update_stat("updatehealth([reason])") - handle_hud_icons_health() - diag_hud_set_health() - -/mob/living/silicon/robot/getBruteLoss(repairable_only = FALSE) - var/amount = 0 - for(var/V in components) - var/datum/robot_component/C = components[V] - if(C.installed != 0 && (!repairable_only || C.installed != -1)) // Installed ones only and if repair only remove the borked ones - amount += C.brute_damage - return amount - -/mob/living/silicon/robot/getFireLoss(repairable_only = FALSE) - var/amount = 0 - for(var/V in components) - var/datum/robot_component/C = components[V] - if(C.installed != 0 && (!repairable_only || C.installed != -1)) // Installed ones only and if repair only remove the borked ones - amount += C.electronics_damage - return amount - -/mob/living/silicon/robot/adjustBruteLoss(amount, updating_health = TRUE) - if(amount > 0) - take_overall_damage(amount, 0, updating_health) - else - heal_overall_damage(-amount, 0, updating_health) - return STATUS_UPDATE_HEALTH - -/mob/living/silicon/robot/adjustFireLoss(amount, updating_health = TRUE) - if(amount > 0) - take_overall_damage(0, amount, updating_health) - else - heal_overall_damage(0, -amount, updating_health) - return STATUS_UPDATE_HEALTH - -/mob/living/silicon/robot/proc/get_damaged_components(get_brute, get_burn, get_borked = FALSE, get_missing = FALSE) - var/list/datum/robot_component/parts = list() - for(var/V in components) - var/datum/robot_component/C = components[V] - if((C.installed == 1 || (get_borked && C.installed == -1) || (get_missing && C.installed == 0)) && ((get_brute && C.brute_damage) || (get_burn && C.electronics_damage))) - parts += C - return parts - -/mob/living/silicon/robot/proc/get_missing_components() - var/list/datum/robot_component/parts = list() - for(var/V in components) - var/datum/robot_component/C = components[V] - if(C.installed == 0) - parts += C - return parts - -/mob/living/silicon/robot/proc/get_damageable_components() - var/list/rval = new - for(var/V in components) - var/datum/robot_component/C = components[V] - if(C.installed == 1) - rval += C - return rval - -/mob/living/silicon/robot/proc/get_armour() - if(!LAZYLEN(components)) - return 0 - var/datum/robot_component/C = components["armour"] - if(C && C.installed == 1) - return C - return 0 - -/mob/living/silicon/robot/heal_organ_damage(brute, burn, updating_health = TRUE) - var/list/datum/robot_component/parts = get_damaged_components(brute, burn) - if(!LAZYLEN(parts)) - return - var/datum/robot_component/picked = pick(parts) - picked.heal_damage(brute, burn, updating_health) - -/mob/living/silicon/robot/take_organ_damage(brute = 0, burn = 0, updating_health = TRUE, sharp = 0, edge = 0) - var/list/components = get_damageable_components() - if(!LAZYLEN(components)) - return - - //Combat shielding absorbs a percentage of damage directly into the cell. - var/obj/item/borg/combat/shield/shield - if(module_state_1 && istype(module_state_1, /obj/item/borg/combat/shield)) - shield = module_state_1 - else if(module_state_2 && istype(module_state_2, /obj/item/borg/combat/shield)) - shield = module_state_2 - else if(module_state_3 && istype(module_state_3, /obj/item/borg/combat/shield)) - shield = module_state_3 - if(shield) - //Shields absorb a certain percentage of damage based on their power setting. - var/absorb_brute = brute * shield.shield_level - var/absorb_burn = burn * shield.shield_level - var/cost = (absorb_brute+absorb_burn) * 100 - - cell.charge -= cost - if(cell.charge <= 0) - cell.charge = 0 - to_chat(src, "Your shield has overloaded!") - else - brute -= absorb_brute - burn -= absorb_burn - to_chat(src, "Your shield absorbs some of the impact!") - - var/datum/robot_component/armour/A = get_armour() - if(A) - A.take_damage(brute, burn, sharp, updating_health) - return - - var/datum/robot_component/C = pick(components) - C.take_damage(brute, burn, sharp, updating_health) - -/mob/living/silicon/robot/heal_overall_damage(var/brute, var/burn, updating_health = TRUE) - var/list/datum/robot_component/parts = get_damaged_components(brute, burn) - - while(LAZYLEN(parts) && (brute > 0 || burn > 0) ) - var/datum/robot_component/picked = pick(parts) - - var/brute_was = picked.brute_damage - var/burn_was = picked.electronics_damage - - picked.heal_damage(brute,burn, updating_health) - - brute -= (brute_was - picked.brute_damage) - burn -= (burn_was - picked.electronics_damage) - - parts -= picked - - if(updating_health) - updatehealth("heal overall damage") - -/mob/living/silicon/robot/take_overall_damage(brute = 0, burn = 0, updating_health = TRUE, used_weapon = null, sharp = 0) - if(status_flags & GODMODE) return //godmode - var/list/datum/robot_component/parts = get_damageable_components() - - //Combat shielding absorbs a percentage of damage directly into the cell. - var/obj/item/borg/combat/shield/shield - if(module_state_1 && istype(module_state_1, /obj/item/borg/combat/shield)) - shield = module_state_1 - else if(module_state_2 && istype(module_state_2, /obj/item/borg/combat/shield)) - shield = module_state_2 - else if(module_state_3 && istype(module_state_3, /obj/item/borg/combat/shield)) - shield = module_state_3 - if(shield) - //Shields absorb a certain percentage of damage based on their power setting. - var/absorb_brute = brute * shield.shield_level - var/absorb_burn = burn * shield.shield_level - var/cost = (absorb_brute+absorb_burn) * 100 - - cell.charge -= cost - if(cell.charge <= 0) - cell.charge = 0 - to_chat(src, "Your shield has overloaded!") - else - brute -= absorb_brute - burn -= absorb_burn - to_chat(src, "Your shield absorbs some of the impact!") - - var/datum/robot_component/armour/A = get_armour() - if(A) - A.take_damage(brute, burn, sharp) - return - - while(LAZYLEN(parts) && (brute > 0 || burn > 0) ) - var/datum/robot_component/picked = pick(parts) - - var/brute_was = picked.brute_damage - var/burn_was = picked.electronics_damage - - picked.take_damage(brute, burn, sharp, FALSE) - - brute -= (picked.brute_damage - brute_was) - burn -= (picked.electronics_damage - burn_was) - - parts -= picked - updatehealth() +/mob/living/silicon/robot/updatehealth(reason = "none given") + if(status_flags & GODMODE) + health = maxHealth + stat = CONSCIOUS + return + health = maxHealth - (getOxyLoss() + getFireLoss() + getBruteLoss()) + update_stat("updatehealth([reason])") + handle_hud_icons_health() + diag_hud_set_health() + +/mob/living/silicon/robot/getBruteLoss(repairable_only = FALSE) + var/amount = 0 + for(var/V in components) + var/datum/robot_component/C = components[V] + if(C.installed != 0 && (!repairable_only || C.installed != -1)) // Installed ones only and if repair only remove the borked ones + amount += C.brute_damage + return amount + +/mob/living/silicon/robot/getFireLoss(repairable_only = FALSE) + var/amount = 0 + for(var/V in components) + var/datum/robot_component/C = components[V] + if(C.installed != 0 && (!repairable_only || C.installed != -1)) // Installed ones only and if repair only remove the borked ones + amount += C.electronics_damage + return amount + +/mob/living/silicon/robot/adjustBruteLoss(amount, updating_health = TRUE) + if(amount > 0) + take_overall_damage(amount, 0, updating_health) + else + heal_overall_damage(-amount, 0, updating_health) + return STATUS_UPDATE_HEALTH + +/mob/living/silicon/robot/adjustFireLoss(amount, updating_health = TRUE) + if(amount > 0) + take_overall_damage(0, amount, updating_health) + else + heal_overall_damage(0, -amount, updating_health) + return STATUS_UPDATE_HEALTH + +/mob/living/silicon/robot/proc/get_damaged_components(get_brute, get_burn, get_borked = FALSE, get_missing = FALSE) + var/list/datum/robot_component/parts = list() + for(var/V in components) + var/datum/robot_component/C = components[V] + if((C.installed == 1 || (get_borked && C.installed == -1) || (get_missing && C.installed == 0)) && ((get_brute && C.brute_damage) || (get_burn && C.electronics_damage))) + parts += C + return parts + +/mob/living/silicon/robot/proc/get_missing_components() + var/list/datum/robot_component/parts = list() + for(var/V in components) + var/datum/robot_component/C = components[V] + if(C.installed == 0) + parts += C + return parts + +/mob/living/silicon/robot/proc/get_damageable_components() + var/list/rval = new + for(var/V in components) + var/datum/robot_component/C = components[V] + if(C.installed == 1) + rval += C + return rval + +/mob/living/silicon/robot/proc/get_armour() + if(!LAZYLEN(components)) + return 0 + var/datum/robot_component/C = components["armour"] + if(C && C.installed == 1) + return C + return 0 + +/mob/living/silicon/robot/heal_organ_damage(brute, burn, updating_health = TRUE) + var/list/datum/robot_component/parts = get_damaged_components(brute, burn) + if(!LAZYLEN(parts)) + return + var/datum/robot_component/picked = pick(parts) + picked.heal_damage(brute, burn, updating_health) + +/mob/living/silicon/robot/take_organ_damage(brute = 0, burn = 0, updating_health = TRUE, sharp = 0, edge = 0) + var/list/components = get_damageable_components() + if(!LAZYLEN(components)) + return + + //Combat shielding absorbs a percentage of damage directly into the cell. + var/obj/item/borg/combat/shield/shield + if(module_state_1 && istype(module_state_1, /obj/item/borg/combat/shield)) + shield = module_state_1 + else if(module_state_2 && istype(module_state_2, /obj/item/borg/combat/shield)) + shield = module_state_2 + else if(module_state_3 && istype(module_state_3, /obj/item/borg/combat/shield)) + shield = module_state_3 + if(shield) + //Shields absorb a certain percentage of damage based on their power setting. + var/absorb_brute = brute * shield.shield_level + var/absorb_burn = burn * shield.shield_level + var/cost = (absorb_brute+absorb_burn) * 100 + + cell.charge -= cost + if(cell.charge <= 0) + cell.charge = 0 + to_chat(src, "Your shield has overloaded!") + else + brute -= absorb_brute + burn -= absorb_burn + to_chat(src, "Your shield absorbs some of the impact!") + + var/datum/robot_component/armour/A = get_armour() + if(A) + A.take_damage(brute, burn, sharp, updating_health) + return + + var/datum/robot_component/C = pick(components) + C.take_damage(brute, burn, sharp, updating_health) + +/mob/living/silicon/robot/heal_overall_damage(var/brute, var/burn, updating_health = TRUE) + var/list/datum/robot_component/parts = get_damaged_components(brute, burn) + + while(LAZYLEN(parts) && (brute > 0 || burn > 0) ) + var/datum/robot_component/picked = pick(parts) + + var/brute_was = picked.brute_damage + var/burn_was = picked.electronics_damage + + picked.heal_damage(brute,burn, updating_health) + + brute -= (brute_was - picked.brute_damage) + burn -= (burn_was - picked.electronics_damage) + + parts -= picked + + if(updating_health) + updatehealth("heal overall damage") + +/mob/living/silicon/robot/take_overall_damage(brute = 0, burn = 0, updating_health = TRUE, used_weapon = null, sharp = 0) + if(status_flags & GODMODE) return //godmode + var/list/datum/robot_component/parts = get_damageable_components() + + //Combat shielding absorbs a percentage of damage directly into the cell. + var/obj/item/borg/combat/shield/shield + if(module_state_1 && istype(module_state_1, /obj/item/borg/combat/shield)) + shield = module_state_1 + else if(module_state_2 && istype(module_state_2, /obj/item/borg/combat/shield)) + shield = module_state_2 + else if(module_state_3 && istype(module_state_3, /obj/item/borg/combat/shield)) + shield = module_state_3 + if(shield) + //Shields absorb a certain percentage of damage based on their power setting. + var/absorb_brute = brute * shield.shield_level + var/absorb_burn = burn * shield.shield_level + var/cost = (absorb_brute+absorb_burn) * 100 + + cell.charge -= cost + if(cell.charge <= 0) + cell.charge = 0 + to_chat(src, "Your shield has overloaded!") + else + brute -= absorb_brute + burn -= absorb_burn + to_chat(src, "Your shield absorbs some of the impact!") + + var/datum/robot_component/armour/A = get_armour() + if(A) + A.take_damage(brute, burn, sharp) + return + + while(LAZYLEN(parts) && (brute > 0 || burn > 0) ) + var/datum/robot_component/picked = pick(parts) + + var/brute_was = picked.brute_damage + var/burn_was = picked.electronics_damage + + picked.take_damage(brute, burn, sharp, FALSE) + + brute -= (picked.brute_damage - brute_was) + burn -= (picked.electronics_damage - burn_was) + + parts -= picked + updatehealth() diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm index 083e379e444..92115986b2e 100644 --- a/code/modules/mob/living/silicon/robot/robot_items.dm +++ b/code/modules/mob/living/silicon/robot/robot_items.dm @@ -95,4 +95,4 @@ desc = "By retracting limbs and tucking in its head, a combat android can roll at high speeds." icon = 'icons/obj/decals.dmi' icon_state = "shock" - powerneeded = 25 \ No newline at end of file + powerneeded = 25 diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index 8b4005beff8..47dcb93cef5 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -1,590 +1,590 @@ -/obj/item/robot_module - name = "robot module" - icon = 'icons/obj/module.dmi' - icon_state = "std_module" - w_class = 100 - item_state = "electronic" - flags = CONDUCT - - var/list/modules = list() - var/obj/item/emag = null - var/list/subsystems = list() - var/list/module_actions = list() - - var/module_type = "NoMod" // For icon usage - - var/list/stacktypes - var/channels = list() - var/list/custom_removals = list() - - -/obj/item/robot_module/emp_act(severity) - if(modules) - for(var/obj/O in modules) - O.emp_act(severity) - if(emag) - emag.emp_act(severity) - ..() - - -/obj/item/robot_module/New() - ..() - add_default_robot_items() - emag = new /obj/item/toy/sword(src) - emag.name = "Placeholder Emag Item" - -/obj/item/robot_module/Destroy() - QDEL_LIST(modules) - QDEL_NULL(emag) - return ..() - -// By default, all robots will get the items in this proc, unless you override it for your specific module. See: ../robot_module/drone -/obj/item/robot_module/proc/add_default_robot_items() - modules += new /obj/item/flash/cyborg(src) - -/obj/item/robot_module/proc/fix_modules() - for(var/obj/item/I in modules) - I.flags |= NODROP - I.mouse_opacity = MOUSE_OPACITY_OPAQUE - if(emag) - emag.flags |= NODROP - emag.mouse_opacity = MOUSE_OPACITY_OPAQUE - -/obj/item/robot_module/proc/respawn_consumable(mob/living/silicon/robot/R) - if(!stacktypes || !stacktypes.len) - return - - var/stack_respawned = 0 - for(var/T in stacktypes) - var/O = locate(T) in modules - var/obj/item/stack/S = O - - if(!S) - S = new T(src) - modules += S - S.amount = 1 - stack_respawned = 1 - - if(S && S.amount < stacktypes[T]) - S.amount++ - if(stack_respawned && istype(R) && R.hud_used) - R.hud_used.update_robot_modules_display() - -/obj/item/robot_module/proc/rebuild()//Rebuilds the list so it's possible to add/remove items from the module - var/list/temp_list = modules - modules = list() - for(var/obj/O in temp_list) - if(O) - modules += O - -/obj/item/robot_module/proc/add_languages(mob/living/silicon/robot/R) - //full set of languages - R.add_language("Galactic Common", 1) - R.add_language("Sol Common", 1) - R.add_language("Tradeband", 1) - R.add_language("Gutter", 0) - R.add_language("Neo-Russkiya", 0) - R.add_language("Sinta'unathi", 0) - R.add_language("Siik'tajr", 0) - R.add_language("Canilunzt", 0) - R.add_language("Skrellian", 0) - R.add_language("Vox-pidgin", 0) - R.add_language("Rootspeak", 0) - R.add_language("Trinary", 1) - R.add_language("Chittin", 0) - R.add_language("Bubblish", 0) - R.add_language("Orluum", 0) - R.add_language("Clownish",0) - -/obj/item/robot_module/proc/add_subsystems_and_actions(mob/living/silicon/robot/R) - R.verbs |= subsystems - for(var/A in module_actions) - var/datum/action/act = new A() - act.Grant(R) - R.module_actions += act - -/obj/item/robot_module/proc/remove_subsystems_and_actions(mob/living/silicon/robot/R) - R.verbs -= subsystems - for(var/datum/action/A in R.module_actions) - A.Remove(R) - qdel(A) - R.module_actions.Cut() - -// Return true in an overridden subtype to prevent normal removal handling -/obj/item/robot_module/proc/handle_custom_removal(component_id, mob/living/user, obj/item/W) - return FALSE - -/obj/item/robot_module/proc/handle_death(gibbed) - return - -/obj/item/robot_module/standard - name = "standard robot module" - module_type = "Standard" - -/obj/item/robot_module/standard/New() - ..() - modules += new /obj/item/melee/baton/loaded(src) - modules += new /obj/item/extinguisher(src) - modules += new /obj/item/wrench/cyborg(src) - modules += new /obj/item/crowbar/cyborg(src) - modules += new /obj/item/healthanalyzer(src) - emag = new /obj/item/melee/energy/sword/cyborg(src) - - fix_modules() - -/obj/item/robot_module/medical - name = "medical robot module" - module_type = "Medical" - subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor) - stacktypes = list( - /obj/item/stack/medical/bruise_pack/advanced = 6, - /obj/item/stack/medical/ointment/advanced = 6, - /obj/item/stack/medical/splint = 6, - /obj/item/stack/nanopaste = 6 - ) - -/obj/item/robot_module/medical/New() - ..() - modules += new /obj/item/healthanalyzer/advanced(src) - modules += new /obj/item/robotanalyzer(src) - modules += new /obj/item/reagent_scanner/adv(src) - modules += new /obj/item/borg_defib(src) - modules += new /obj/item/handheld_defibrillator(src) - modules += new /obj/item/roller_holder(src) - modules += new /obj/item/reagent_containers/borghypo(src) - modules += new /obj/item/reagent_containers/glass/beaker/large(src) - modules += new /obj/item/reagent_containers/dropper(src) - modules += new /obj/item/reagent_containers/syringe(src) - modules += new /obj/item/extinguisher/mini(src) - modules += new /obj/item/stack/medical/bruise_pack/advanced(src) - modules += new /obj/item/stack/medical/ointment/advanced(src) - modules += new /obj/item/stack/medical/splint(src) - modules += new /obj/item/stack/nanopaste(src) - modules += new /obj/item/scalpel/laser/laser1(src) - modules += new /obj/item/hemostat(src) - modules += new /obj/item/retractor(src) - modules += new /obj/item/bonegel(src) - modules += new /obj/item/FixOVein(src) - modules += new /obj/item/bonesetter(src) - modules += new /obj/item/circular_saw(src) - modules += new /obj/item/surgicaldrill(src) - modules += new /obj/item/gripper/medical(src) - - emag = new /obj/item/reagent_containers/spray(src) - - emag.reagents.add_reagent("facid", 250) - emag.name = "Polyacid spray" - - fix_modules() - -/obj/item/robot_module/medical/respawn_consumable(mob/living/silicon/robot/R) - if(emag) - var/obj/item/reagent_containers/spray/PS = emag - PS.reagents.add_reagent("facid", 2) - ..() - -/obj/item/robot_module/engineering - name = "engineering robot module" - module_type = "Engineer" - subsystems = list(/mob/living/silicon/proc/subsystem_power_monitor) - module_actions = list( - /datum/action/innate/robot_sight/meson, - ) - - stacktypes = list( - /obj/item/stack/sheet/metal/cyborg = 50, - /obj/item/stack/sheet/glass/cyborg = 50, - /obj/item/stack/sheet/rglass/cyborg = 50, - /obj/item/stack/cable_coil/cyborg = 50, - /obj/item/stack/rods/cyborg = 60, - /obj/item/stack/tile/plasteel = 20 - ) - -/obj/item/robot_module/engineering/New() - ..() - modules += new /obj/item/rcd/borg(src) - modules += new /obj/item/rpd(src) - modules += new /obj/item/extinguisher(src) - modules += new /obj/item/weldingtool/largetank/cyborg(src) - modules += new /obj/item/screwdriver/cyborg(src) - modules += new /obj/item/wrench/cyborg(src) - modules += new /obj/item/crowbar/cyborg(src) - modules += new /obj/item/wirecutters/cyborg(src) - modules += new /obj/item/multitool/cyborg(src) - modules += new /obj/item/t_scanner(src) - modules += new /obj/item/analyzer(src) - modules += new /obj/item/holosign_creator/engineering(src) - modules += new /obj/item/gripper(src) - modules += new /obj/item/matter_decompiler(src) - modules += new /obj/item/floor_painter(src) - modules += new /obj/item/areaeditor/blueprints/cyborg(src) - emag = new /obj/item/borg/stun(src) - - for(var/G in stacktypes) //Attempt to unify Engi-Borg material stacks into fewer lines. See Line 492 for example. Variables changed out of paranoia. - var/obj/item/stack/sheet/M = new G(src) - M.amount = stacktypes[G] - modules += M - - fix_modules() - -/obj/item/robot_module/engineering/handle_death() - var/obj/item/gripper/G = locate(/obj/item/gripper) in modules - if(G) - G.drop_item() - -/obj/item/robot_module/security - name = "security robot module" - module_type = "Security" - subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor) - -/obj/item/robot_module/security/New() - ..() - modules += new /obj/item/restraints/handcuffs/cable/zipties/cyborg(src) - modules += new /obj/item/melee/baton/loaded(src) - modules += new /obj/item/gun/energy/disabler/cyborg(src) - modules += new /obj/item/holosign_creator/security(src) - modules += new /obj/item/clothing/mask/gas/sechailer/cyborg(src) - emag = new /obj/item/gun/energy/laser/cyborg(src) - - fix_modules() - -/obj/item/robot_module/janitor - name = "janitorial robot module" - module_type = "Janitor" - -/obj/item/robot_module/janitor/New() - ..() - modules += new /obj/item/soap/nanotrasen(src) - modules += new /obj/item/storage/bag/trash/cyborg(src) - modules += new /obj/item/mop/advanced/cyborg(src) - modules += new /obj/item/lightreplacer/cyborg(src) - modules += new /obj/item/holosign_creator(src) - emag = new /obj/item/reagent_containers/spray(src) - - emag.reagents.add_reagent("lube", 250) - emag.name = "Lube spray" - - fix_modules() - -/obj/item/robot_module/butler - name = "service robot module" - module_type = "Service" - -/obj/item/robot_module/butler/New() - ..() - modules += new /obj/item/reagent_containers/food/drinks/cans/beer(src) - modules += new /obj/item/reagent_containers/food/drinks/cans/cola(src) - modules += new /obj/item/reagent_containers/food/drinks/cans/sodawater(src) - modules += new /obj/item/reagent_containers/food/condiment/enzyme(src) - modules += new /obj/item/reagent_containers/food/drinks/bottle/orangejuice(src) // -0.3 oxy/sec - modules += new /obj/item/reagent_containers/food/drinks/bottle/tomatojuice(src) // -0.2 fire/sec - modules += new /obj/item/reagent_containers/food/drinks/bottle/limejuice(src) // -0.2 tox/sec - modules += new /obj/item/reagent_containers/food/drinks/coffee(src) // -1 paralysis stunned & weakened/sec - modules += new /obj/item/reagent_containers/food/drinks/tea(src) - modules += new /obj/item/reagent_containers/food/drinks/bottle/milk(src) // -0.2 brute/sec - modules += new /obj/item/reagent_containers/food/condiment/sugar(src) - modules += new /obj/item/reagent_containers/food/drinks/ice(src) - modules += new /obj/item/reagent_containers/food/drinks/bottle/cream(src) - - modules += new /obj/item/reagent_containers/food/drinks/bottle/tequila(src) - modules += new /obj/item/reagent_containers/food/drinks/bottle/vodka(src) - modules += new /obj/item/reagent_containers/food/drinks/bottle/whiskey(src) - - modules += new /obj/item/pen(src) - modules += new /obj/item/razor(src) - modules += new /obj/item/instrument/piano_synth(src) - modules += new /obj/item/healthanalyzer/advanced(src) - - var/obj/item/rsf/M = new /obj/item/rsf(src) - M.matter = 30 - modules += M - - modules += new /obj/item/reagent_containers/dropper/cyborg(src) - modules += new /obj/item/lighter/zippo(src) - modules += new /obj/item/storage/bag/tray/cyborg(src) - modules += new /obj/item/reagent_containers/food/drinks/shaker(src) - emag = new /obj/item/reagent_containers/food/drinks/cans/beer(src) - - var/datum/reagents/R = new/datum/reagents(50) - emag.reagents = R - R.my_atom = emag - R.add_reagent("beer2", 50) - emag.name = "Mickey Finn's Special Brew" - - fix_modules() - -/obj/item/robot_module/butler/respawn_consumable(var/mob/living/silicon/robot/R) - var/obj/item/reagent_containers/food/condiment/enzyme/E = locate() in modules - E.reagents.add_reagent("enzyme", 2) - if(emag) - var/obj/item/reagent_containers/food/drinks/cans/beer/B = emag - B.reagents.add_reagent("beer2", 2) - ..() - -/obj/item/robot_module/butler/add_languages(var/mob/living/silicon/robot/R) - //full set of languages - R.add_language("Galactic Common", 1) - R.add_language("Sol Common", 1) - R.add_language("Tradeband", 1) - R.add_language("Gutter", 1) - R.add_language("Sinta'unathi", 1) - R.add_language("Siik'tajr", 1) - R.add_language("Canilunzt", 1) - R.add_language("Skrellian", 1) - R.add_language("Vox-pidgin", 1) - R.add_language("Rootspeak", 1) - R.add_language("Trinary", 1) - R.add_language("Chittin", 1) - R.add_language("Bubblish", 1) - R.add_language("Clownish",1) - R.add_language("Neo-Russkiya", 1) - - -/obj/item/robot_module/miner - name = "miner robot module" - module_type = "Miner" - module_actions = list( - /datum/action/innate/robot_sight/meson, - ) - custom_removals = list("KA modkits") - -/obj/item/robot_module/miner/New() - ..() - modules += new /obj/item/storage/bag/ore/cyborg(src) - modules += new /obj/item/pickaxe/drill/cyborg(src) - modules += new /obj/item/shovel(src) - modules += new /obj/item/weldingtool/mini(src) - modules += new /obj/item/extinguisher/mini(src) - modules += new /obj/item/storage/bag/sheetsnatcher/borg(src) - modules += new /obj/item/t_scanner/adv_mining_scanner/cyborg(src) - modules += new /obj/item/gun/energy/kinetic_accelerator/cyborg(src) - modules += new /obj/item/gps/cyborg(src) - emag = new /obj/item/borg/stun(src) - - fix_modules() - -/obj/item/robot_module/miner/handle_custom_removal(component_id, mob/living/user, obj/item/W) - if(component_id == "KA modkits") - for(var/obj/item/gun/energy/kinetic_accelerator/cyborg/D in src) - D.attackby(W, user) - return TRUE - return ..() - -/obj/item/robot_module/deathsquad - name = "NT advanced combat module" - module_type = "Malf" - module_actions = list( - /datum/action/innate/robot_sight/thermal, - ) - -/obj/item/robot_module/deathsquad/New() - ..() - modules += new /obj/item/melee/energy/sword/cyborg(src) - modules += new /obj/item/gun/energy/pulse/cyborg(src) - modules += new /obj/item/crowbar(src) - emag = null - - fix_modules() - -/obj/item/robot_module/syndicate - name = "syndicate assault robot module" - module_type = "Malf" // cuz it looks cool - -/obj/item/robot_module/syndicate/New() - ..() - modules += new /obj/item/melee/energy/sword/cyborg(src) - modules += new /obj/item/gun/energy/printer(src) - modules += new /obj/item/gun/projectile/revolver/grenadelauncher/multi/cyborg(src) - modules += new /obj/item/card/emag(src) - modules += new /obj/item/crowbar/cyborg(src) - modules += new /obj/item/pinpointer/operative(src) - emag = null - - fix_modules() - -/obj/item/robot_module/syndicate_medical - name = "syndicate medical robot module" - module_type = "Malf" - stacktypes = list( - /obj/item/stack/medical/bruise_pack/advanced = 25, - /obj/item/stack/medical/ointment/advanced = 25, - /obj/item/stack/medical/splint = 25, - /obj/item/stack/nanopaste = 25 - ) - -/obj/item/robot_module/syndicate_medical/New() - ..() - modules += new /obj/item/healthanalyzer/advanced(src) - modules += new /obj/item/reagent_scanner/adv(src) - modules += new /obj/item/bodyanalyzer/borg/syndicate(src) - modules += new /obj/item/borg_defib(src) - modules += new /obj/item/handheld_defibrillator(src) - modules += new /obj/item/roller_holder(src) - modules += new /obj/item/reagent_containers/borghypo/syndicate(src) - modules += new /obj/item/extinguisher/mini(src) - modules += new /obj/item/stack/medical/bruise_pack/advanced(src) - modules += new /obj/item/stack/medical/ointment/advanced(src) - modules += new /obj/item/stack/medical/splint(src) - modules += new /obj/item/stack/nanopaste(src) - modules += new /obj/item/scalpel/laser/laser1(src) - modules += new /obj/item/hemostat(src) - modules += new /obj/item/retractor(src) - modules += new /obj/item/bonegel(src) - modules += new /obj/item/FixOVein(src) - modules += new /obj/item/bonesetter(src) - modules += new /obj/item/surgicaldrill(src) - modules += new /obj/item/gripper/medical(src) - modules += new /obj/item/gun/medbeam(src) - modules += new /obj/item/melee/energy/sword/cyborg/saw(src) //Energy saw -- primary weapon - modules += new /obj/item/card/emag(src) - modules += new /obj/item/crowbar/cyborg(src) - modules += new /obj/item/pinpointer/operative(src) - emag = null - - fix_modules() - -/obj/item/robot_module/syndicate_saboteur - name = "engineering robot module" //to disguise in examine - module_type = "Malf" - - stacktypes = list( - /obj/item/stack/sheet/metal/cyborg = 50, - /obj/item/stack/sheet/glass/cyborg = 50, - /obj/item/stack/sheet/rglass/cyborg = 50, - /obj/item/stack/cable_coil/cyborg = 50, - /obj/item/stack/rods/cyborg = 60, - /obj/item/stack/tile/plasteel = 20 - ) - -/obj/item/robot_module/syndicate_saboteur/New() - ..() - modules += new /obj/item/rcd/borg/syndicate(src) - modules += new /obj/item/rpd(src) - modules += new /obj/item/extinguisher(src) - modules += new /obj/item/weldingtool/largetank/cyborg(src) - modules += new /obj/item/screwdriver/cyborg(src) - modules += new /obj/item/wrench/cyborg(src) - modules += new /obj/item/crowbar/cyborg(src) - modules += new /obj/item/wirecutters/cyborg(src) - modules += new /obj/item/multitool/cyborg(src) - modules += new /obj/item/t_scanner(src) - modules += new /obj/item/analyzer(src) - modules += new /obj/item/gripper(src) - modules += new /obj/item/melee/energy/sword/cyborg(src) - modules += new /obj/item/card/emag(src) - modules += new /obj/item/borg_chameleon(src) - modules += new /obj/item/pinpointer/operative(src) - emag = null - - for(var/T in stacktypes) - var/obj/item/stack/sheet/W = new T(src) - W.amount = stacktypes[T] - modules += W - - fix_modules() - -/obj/item/robot_module/combat - name = "combat robot module" - module_type = "Malf" - module_actions = list( - /datum/action/innate/robot_sight/thermal, - ) - -/obj/item/robot_module/combat/New() - ..() - modules += new /obj/item/restraints/handcuffs/cable/zipties/cyborg(src) - modules += new /obj/item/gun/energy/gun/cyborg(src) - modules += new /obj/item/pickaxe/drill/jackhammer(src) - modules += new /obj/item/borg/combat/shield(src) - modules += new /obj/item/borg/combat/mobility(src) - modules += new /obj/item/wrench/cyborg(src) - emag = new /obj/item/gun/energy/lasercannon/cyborg(src) - - fix_modules() - -/obj/item/robot_module/alien/hunter - name = "alien hunter module" - module_type = "Standard" - module_actions = list( - /datum/action/innate/robot_sight/thermal/alien, - ) - -/obj/item/robot_module/alien/hunter/New() - ..() - modules += new /obj/item/melee/energy/alien/claws(src) - modules += new /obj/item/flash/cyborg/alien(src) - var/obj/item/reagent_containers/spray/alien/stun/S = new /obj/item/reagent_containers/spray/alien/stun(src) - S.reagents.add_reagent("ether",250) //nerfed to sleeptoxin to make it less instant drop. - modules += S - var/obj/item/reagent_containers/spray/alien/smoke/A = new /obj/item/reagent_containers/spray/alien/smoke(src) - S.reagents.add_reagent("water",50) //Water is used as a dummy reagent for the smoke bombs. More of an ammo counter. - modules += A - emag = new /obj/item/reagent_containers/spray/alien/acid(src) - emag.reagents.add_reagent("facid", 125) - emag.reagents.add_reagent("sacid", 125) - - fix_modules() - -/obj/item/robot_module/alien/hunter/add_languages(var/mob/living/silicon/robot/R) - ..() - R.add_language("xenocommon", 1) - -/obj/item/robot_module/drone - name = "drone module" - module_type = "Engineer" - stacktypes = list( - /obj/item/stack/sheet/wood = 10, - /obj/item/stack/sheet/rglass/cyborg = 50, - /obj/item/stack/tile/wood = 20, - /obj/item/stack/rods/cyborg = 60, - /obj/item/stack/tile/plasteel = 20, - /obj/item/stack/sheet/metal/cyborg = 50, - /obj/item/stack/sheet/glass/cyborg = 50, - /obj/item/stack/cable_coil/cyborg = 30 - ) - -/obj/item/robot_module/drone/New() - ..() - modules += new /obj/item/weldingtool/largetank/cyborg(src) - modules += new /obj/item/screwdriver/cyborg(src) - modules += new /obj/item/wrench/cyborg(src) - modules += new /obj/item/crowbar/cyborg(src) - modules += new /obj/item/wirecutters/cyborg(src) - modules += new /obj/item/multitool/cyborg(src) - modules += new /obj/item/lightreplacer/cyborg(src) - modules += new /obj/item/gripper(src) - modules += new /obj/item/matter_decompiler(src) - modules += new /obj/item/reagent_containers/spray/cleaner/drone(src) - modules += new /obj/item/soap(src) - modules += new /obj/item/t_scanner(src) - modules += new /obj/item/rpd(src) - - for(var/T in stacktypes) - var/obj/item/stack/sheet/W = new T(src) - W.amount = stacktypes[T] - modules += W - - fix_modules() - -/obj/item/robot_module/drone/add_default_robot_items() - return - -/obj/item/robot_module/drone/respawn_consumable(mob/living/silicon/robot/R) - var/obj/item/reagent_containers/spray/cleaner/C = locate() in modules - C.reagents.add_reagent("cleaner", 3) - ..() - - -/obj/item/robot_module/drone/handle_death() - var/obj/item/gripper/G = locate(/obj/item/gripper) in modules - if(G) - G.drop_item() - -//checks whether this item is a module of the robot it is located in. -/obj/item/proc/is_robot_module() - if(!istype(loc, /mob/living/silicon/robot)) - return 0 - - var/mob/living/silicon/robot/R = loc - - return (src in R.module.modules) +/obj/item/robot_module + name = "robot module" + icon = 'icons/obj/module.dmi' + icon_state = "std_module" + w_class = 100 + item_state = "electronic" + flags = CONDUCT + + var/list/modules = list() + var/obj/item/emag = null + var/list/subsystems = list() + var/list/module_actions = list() + + var/module_type = "NoMod" // For icon usage + + var/list/stacktypes + var/channels = list() + var/list/custom_removals = list() + + +/obj/item/robot_module/emp_act(severity) + if(modules) + for(var/obj/O in modules) + O.emp_act(severity) + if(emag) + emag.emp_act(severity) + ..() + + +/obj/item/robot_module/New() + ..() + add_default_robot_items() + emag = new /obj/item/toy/sword(src) + emag.name = "Placeholder Emag Item" + +/obj/item/robot_module/Destroy() + QDEL_LIST(modules) + QDEL_NULL(emag) + return ..() + +// By default, all robots will get the items in this proc, unless you override it for your specific module. See: ../robot_module/drone +/obj/item/robot_module/proc/add_default_robot_items() + modules += new /obj/item/flash/cyborg(src) + +/obj/item/robot_module/proc/fix_modules() + for(var/obj/item/I in modules) + I.flags |= NODROP + I.mouse_opacity = MOUSE_OPACITY_OPAQUE + if(emag) + emag.flags |= NODROP + emag.mouse_opacity = MOUSE_OPACITY_OPAQUE + +/obj/item/robot_module/proc/respawn_consumable(mob/living/silicon/robot/R) + if(!stacktypes || !stacktypes.len) + return + + var/stack_respawned = 0 + for(var/T in stacktypes) + var/O = locate(T) in modules + var/obj/item/stack/S = O + + if(!S) + S = new T(src) + modules += S + S.amount = 1 + stack_respawned = 1 + + if(S && S.amount < stacktypes[T]) + S.amount++ + if(stack_respawned && istype(R) && R.hud_used) + R.hud_used.update_robot_modules_display() + +/obj/item/robot_module/proc/rebuild()//Rebuilds the list so it's possible to add/remove items from the module + var/list/temp_list = modules + modules = list() + for(var/obj/O in temp_list) + if(O) + modules += O + +/obj/item/robot_module/proc/add_languages(mob/living/silicon/robot/R) + //full set of languages + R.add_language("Galactic Common", 1) + R.add_language("Sol Common", 1) + R.add_language("Tradeband", 1) + R.add_language("Gutter", 0) + R.add_language("Neo-Russkiya", 0) + R.add_language("Sinta'unathi", 0) + R.add_language("Siik'tajr", 0) + R.add_language("Canilunzt", 0) + R.add_language("Skrellian", 0) + R.add_language("Vox-pidgin", 0) + R.add_language("Rootspeak", 0) + R.add_language("Trinary", 1) + R.add_language("Chittin", 0) + R.add_language("Bubblish", 0) + R.add_language("Orluum", 0) + R.add_language("Clownish",0) + +/obj/item/robot_module/proc/add_subsystems_and_actions(mob/living/silicon/robot/R) + R.verbs |= subsystems + for(var/A in module_actions) + var/datum/action/act = new A() + act.Grant(R) + R.module_actions += act + +/obj/item/robot_module/proc/remove_subsystems_and_actions(mob/living/silicon/robot/R) + R.verbs -= subsystems + for(var/datum/action/A in R.module_actions) + A.Remove(R) + qdel(A) + R.module_actions.Cut() + +// Return true in an overridden subtype to prevent normal removal handling +/obj/item/robot_module/proc/handle_custom_removal(component_id, mob/living/user, obj/item/W) + return FALSE + +/obj/item/robot_module/proc/handle_death(gibbed) + return + +/obj/item/robot_module/standard + name = "standard robot module" + module_type = "Standard" + +/obj/item/robot_module/standard/New() + ..() + modules += new /obj/item/melee/baton/loaded(src) + modules += new /obj/item/extinguisher(src) + modules += new /obj/item/wrench/cyborg(src) + modules += new /obj/item/crowbar/cyborg(src) + modules += new /obj/item/healthanalyzer(src) + emag = new /obj/item/melee/energy/sword/cyborg(src) + + fix_modules() + +/obj/item/robot_module/medical + name = "medical robot module" + module_type = "Medical" + subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor) + stacktypes = list( + /obj/item/stack/medical/bruise_pack/advanced = 6, + /obj/item/stack/medical/ointment/advanced = 6, + /obj/item/stack/medical/splint = 6, + /obj/item/stack/nanopaste = 6 + ) + +/obj/item/robot_module/medical/New() + ..() + modules += new /obj/item/healthanalyzer/advanced(src) + modules += new /obj/item/robotanalyzer(src) + modules += new /obj/item/reagent_scanner/adv(src) + modules += new /obj/item/borg_defib(src) + modules += new /obj/item/handheld_defibrillator(src) + modules += new /obj/item/roller_holder(src) + modules += new /obj/item/reagent_containers/borghypo(src) + modules += new /obj/item/reagent_containers/glass/beaker/large(src) + modules += new /obj/item/reagent_containers/dropper(src) + modules += new /obj/item/reagent_containers/syringe(src) + modules += new /obj/item/extinguisher/mini(src) + modules += new /obj/item/stack/medical/bruise_pack/advanced(src) + modules += new /obj/item/stack/medical/ointment/advanced(src) + modules += new /obj/item/stack/medical/splint(src) + modules += new /obj/item/stack/nanopaste(src) + modules += new /obj/item/scalpel/laser/laser1(src) + modules += new /obj/item/hemostat(src) + modules += new /obj/item/retractor(src) + modules += new /obj/item/bonegel(src) + modules += new /obj/item/FixOVein(src) + modules += new /obj/item/bonesetter(src) + modules += new /obj/item/circular_saw(src) + modules += new /obj/item/surgicaldrill(src) + modules += new /obj/item/gripper/medical(src) + + emag = new /obj/item/reagent_containers/spray(src) + + emag.reagents.add_reagent("facid", 250) + emag.name = "Polyacid spray" + + fix_modules() + +/obj/item/robot_module/medical/respawn_consumable(mob/living/silicon/robot/R) + if(emag) + var/obj/item/reagent_containers/spray/PS = emag + PS.reagents.add_reagent("facid", 2) + ..() + +/obj/item/robot_module/engineering + name = "engineering robot module" + module_type = "Engineer" + subsystems = list(/mob/living/silicon/proc/subsystem_power_monitor) + module_actions = list( + /datum/action/innate/robot_sight/meson, + ) + + stacktypes = list( + /obj/item/stack/sheet/metal/cyborg = 50, + /obj/item/stack/sheet/glass/cyborg = 50, + /obj/item/stack/sheet/rglass/cyborg = 50, + /obj/item/stack/cable_coil/cyborg = 50, + /obj/item/stack/rods/cyborg = 60, + /obj/item/stack/tile/plasteel = 20 + ) + +/obj/item/robot_module/engineering/New() + ..() + modules += new /obj/item/rcd/borg(src) + modules += new /obj/item/rpd(src) + modules += new /obj/item/extinguisher(src) + modules += new /obj/item/weldingtool/largetank/cyborg(src) + modules += new /obj/item/screwdriver/cyborg(src) + modules += new /obj/item/wrench/cyborg(src) + modules += new /obj/item/crowbar/cyborg(src) + modules += new /obj/item/wirecutters/cyborg(src) + modules += new /obj/item/multitool/cyborg(src) + modules += new /obj/item/t_scanner(src) + modules += new /obj/item/analyzer(src) + modules += new /obj/item/holosign_creator/engineering(src) + modules += new /obj/item/gripper(src) + modules += new /obj/item/matter_decompiler(src) + modules += new /obj/item/floor_painter(src) + modules += new /obj/item/areaeditor/blueprints/cyborg(src) + emag = new /obj/item/borg/stun(src) + + for(var/G in stacktypes) //Attempt to unify Engi-Borg material stacks into fewer lines. See Line 492 for example. Variables changed out of paranoia. + var/obj/item/stack/sheet/M = new G(src) + M.amount = stacktypes[G] + modules += M + + fix_modules() + +/obj/item/robot_module/engineering/handle_death() + var/obj/item/gripper/G = locate(/obj/item/gripper) in modules + if(G) + G.drop_item() + +/obj/item/robot_module/security + name = "security robot module" + module_type = "Security" + subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor) + +/obj/item/robot_module/security/New() + ..() + modules += new /obj/item/restraints/handcuffs/cable/zipties/cyborg(src) + modules += new /obj/item/melee/baton/loaded(src) + modules += new /obj/item/gun/energy/disabler/cyborg(src) + modules += new /obj/item/holosign_creator/security(src) + modules += new /obj/item/clothing/mask/gas/sechailer/cyborg(src) + emag = new /obj/item/gun/energy/laser/cyborg(src) + + fix_modules() + +/obj/item/robot_module/janitor + name = "janitorial robot module" + module_type = "Janitor" + +/obj/item/robot_module/janitor/New() + ..() + modules += new /obj/item/soap/nanotrasen(src) + modules += new /obj/item/storage/bag/trash/cyborg(src) + modules += new /obj/item/mop/advanced/cyborg(src) + modules += new /obj/item/lightreplacer/cyborg(src) + modules += new /obj/item/holosign_creator(src) + emag = new /obj/item/reagent_containers/spray(src) + + emag.reagents.add_reagent("lube", 250) + emag.name = "Lube spray" + + fix_modules() + +/obj/item/robot_module/butler + name = "service robot module" + module_type = "Service" + +/obj/item/robot_module/butler/New() + ..() + modules += new /obj/item/reagent_containers/food/drinks/cans/beer(src) + modules += new /obj/item/reagent_containers/food/drinks/cans/cola(src) + modules += new /obj/item/reagent_containers/food/drinks/cans/sodawater(src) + modules += new /obj/item/reagent_containers/food/condiment/enzyme(src) + modules += new /obj/item/reagent_containers/food/drinks/bottle/orangejuice(src) // -0.3 oxy/sec + modules += new /obj/item/reagent_containers/food/drinks/bottle/tomatojuice(src) // -0.2 fire/sec + modules += new /obj/item/reagent_containers/food/drinks/bottle/limejuice(src) // -0.2 tox/sec + modules += new /obj/item/reagent_containers/food/drinks/coffee(src) // -1 paralysis stunned & weakened/sec + modules += new /obj/item/reagent_containers/food/drinks/tea(src) + modules += new /obj/item/reagent_containers/food/drinks/bottle/milk(src) // -0.2 brute/sec + modules += new /obj/item/reagent_containers/food/condiment/sugar(src) + modules += new /obj/item/reagent_containers/food/drinks/ice(src) + modules += new /obj/item/reagent_containers/food/drinks/bottle/cream(src) + + modules += new /obj/item/reagent_containers/food/drinks/bottle/tequila(src) + modules += new /obj/item/reagent_containers/food/drinks/bottle/vodka(src) + modules += new /obj/item/reagent_containers/food/drinks/bottle/whiskey(src) + + modules += new /obj/item/pen(src) + modules += new /obj/item/razor(src) + modules += new /obj/item/instrument/piano_synth(src) + modules += new /obj/item/healthanalyzer/advanced(src) + + var/obj/item/rsf/M = new /obj/item/rsf(src) + M.matter = 30 + modules += M + + modules += new /obj/item/reagent_containers/dropper/cyborg(src) + modules += new /obj/item/lighter/zippo(src) + modules += new /obj/item/storage/bag/tray/cyborg(src) + modules += new /obj/item/reagent_containers/food/drinks/shaker(src) + emag = new /obj/item/reagent_containers/food/drinks/cans/beer(src) + + var/datum/reagents/R = new/datum/reagents(50) + emag.reagents = R + R.my_atom = emag + R.add_reagent("beer2", 50) + emag.name = "Mickey Finn's Special Brew" + + fix_modules() + +/obj/item/robot_module/butler/respawn_consumable(var/mob/living/silicon/robot/R) + var/obj/item/reagent_containers/food/condiment/enzyme/E = locate() in modules + E.reagents.add_reagent("enzyme", 2) + if(emag) + var/obj/item/reagent_containers/food/drinks/cans/beer/B = emag + B.reagents.add_reagent("beer2", 2) + ..() + +/obj/item/robot_module/butler/add_languages(var/mob/living/silicon/robot/R) + //full set of languages + R.add_language("Galactic Common", 1) + R.add_language("Sol Common", 1) + R.add_language("Tradeband", 1) + R.add_language("Gutter", 1) + R.add_language("Sinta'unathi", 1) + R.add_language("Siik'tajr", 1) + R.add_language("Canilunzt", 1) + R.add_language("Skrellian", 1) + R.add_language("Vox-pidgin", 1) + R.add_language("Rootspeak", 1) + R.add_language("Trinary", 1) + R.add_language("Chittin", 1) + R.add_language("Bubblish", 1) + R.add_language("Clownish",1) + R.add_language("Neo-Russkiya", 1) + + +/obj/item/robot_module/miner + name = "miner robot module" + module_type = "Miner" + module_actions = list( + /datum/action/innate/robot_sight/meson, + ) + custom_removals = list("KA modkits") + +/obj/item/robot_module/miner/New() + ..() + modules += new /obj/item/storage/bag/ore/cyborg(src) + modules += new /obj/item/pickaxe/drill/cyborg(src) + modules += new /obj/item/shovel(src) + modules += new /obj/item/weldingtool/mini(src) + modules += new /obj/item/extinguisher/mini(src) + modules += new /obj/item/storage/bag/sheetsnatcher/borg(src) + modules += new /obj/item/t_scanner/adv_mining_scanner/cyborg(src) + modules += new /obj/item/gun/energy/kinetic_accelerator/cyborg(src) + modules += new /obj/item/gps/cyborg(src) + emag = new /obj/item/borg/stun(src) + + fix_modules() + +/obj/item/robot_module/miner/handle_custom_removal(component_id, mob/living/user, obj/item/W) + if(component_id == "KA modkits") + for(var/obj/item/gun/energy/kinetic_accelerator/cyborg/D in src) + D.attackby(W, user) + return TRUE + return ..() + +/obj/item/robot_module/deathsquad + name = "NT advanced combat module" + module_type = "Malf" + module_actions = list( + /datum/action/innate/robot_sight/thermal, + ) + +/obj/item/robot_module/deathsquad/New() + ..() + modules += new /obj/item/melee/energy/sword/cyborg(src) + modules += new /obj/item/gun/energy/pulse/cyborg(src) + modules += new /obj/item/crowbar(src) + emag = null + + fix_modules() + +/obj/item/robot_module/syndicate + name = "syndicate assault robot module" + module_type = "Malf" // cuz it looks cool + +/obj/item/robot_module/syndicate/New() + ..() + modules += new /obj/item/melee/energy/sword/cyborg(src) + modules += new /obj/item/gun/energy/printer(src) + modules += new /obj/item/gun/projectile/revolver/grenadelauncher/multi/cyborg(src) + modules += new /obj/item/card/emag(src) + modules += new /obj/item/crowbar/cyborg(src) + modules += new /obj/item/pinpointer/operative(src) + emag = null + + fix_modules() + +/obj/item/robot_module/syndicate_medical + name = "syndicate medical robot module" + module_type = "Malf" + stacktypes = list( + /obj/item/stack/medical/bruise_pack/advanced = 25, + /obj/item/stack/medical/ointment/advanced = 25, + /obj/item/stack/medical/splint = 25, + /obj/item/stack/nanopaste = 25 + ) + +/obj/item/robot_module/syndicate_medical/New() + ..() + modules += new /obj/item/healthanalyzer/advanced(src) + modules += new /obj/item/reagent_scanner/adv(src) + modules += new /obj/item/bodyanalyzer/borg/syndicate(src) + modules += new /obj/item/borg_defib(src) + modules += new /obj/item/handheld_defibrillator(src) + modules += new /obj/item/roller_holder(src) + modules += new /obj/item/reagent_containers/borghypo/syndicate(src) + modules += new /obj/item/extinguisher/mini(src) + modules += new /obj/item/stack/medical/bruise_pack/advanced(src) + modules += new /obj/item/stack/medical/ointment/advanced(src) + modules += new /obj/item/stack/medical/splint(src) + modules += new /obj/item/stack/nanopaste(src) + modules += new /obj/item/scalpel/laser/laser1(src) + modules += new /obj/item/hemostat(src) + modules += new /obj/item/retractor(src) + modules += new /obj/item/bonegel(src) + modules += new /obj/item/FixOVein(src) + modules += new /obj/item/bonesetter(src) + modules += new /obj/item/surgicaldrill(src) + modules += new /obj/item/gripper/medical(src) + modules += new /obj/item/gun/medbeam(src) + modules += new /obj/item/melee/energy/sword/cyborg/saw(src) //Energy saw -- primary weapon + modules += new /obj/item/card/emag(src) + modules += new /obj/item/crowbar/cyborg(src) + modules += new /obj/item/pinpointer/operative(src) + emag = null + + fix_modules() + +/obj/item/robot_module/syndicate_saboteur + name = "engineering robot module" //to disguise in examine + module_type = "Malf" + + stacktypes = list( + /obj/item/stack/sheet/metal/cyborg = 50, + /obj/item/stack/sheet/glass/cyborg = 50, + /obj/item/stack/sheet/rglass/cyborg = 50, + /obj/item/stack/cable_coil/cyborg = 50, + /obj/item/stack/rods/cyborg = 60, + /obj/item/stack/tile/plasteel = 20 + ) + +/obj/item/robot_module/syndicate_saboteur/New() + ..() + modules += new /obj/item/rcd/borg/syndicate(src) + modules += new /obj/item/rpd(src) + modules += new /obj/item/extinguisher(src) + modules += new /obj/item/weldingtool/largetank/cyborg(src) + modules += new /obj/item/screwdriver/cyborg(src) + modules += new /obj/item/wrench/cyborg(src) + modules += new /obj/item/crowbar/cyborg(src) + modules += new /obj/item/wirecutters/cyborg(src) + modules += new /obj/item/multitool/cyborg(src) + modules += new /obj/item/t_scanner(src) + modules += new /obj/item/analyzer(src) + modules += new /obj/item/gripper(src) + modules += new /obj/item/melee/energy/sword/cyborg(src) + modules += new /obj/item/card/emag(src) + modules += new /obj/item/borg_chameleon(src) + modules += new /obj/item/pinpointer/operative(src) + emag = null + + for(var/T in stacktypes) + var/obj/item/stack/sheet/W = new T(src) + W.amount = stacktypes[T] + modules += W + + fix_modules() + +/obj/item/robot_module/combat + name = "combat robot module" + module_type = "Malf" + module_actions = list( + /datum/action/innate/robot_sight/thermal, + ) + +/obj/item/robot_module/combat/New() + ..() + modules += new /obj/item/restraints/handcuffs/cable/zipties/cyborg(src) + modules += new /obj/item/gun/energy/gun/cyborg(src) + modules += new /obj/item/pickaxe/drill/jackhammer(src) + modules += new /obj/item/borg/combat/shield(src) + modules += new /obj/item/borg/combat/mobility(src) + modules += new /obj/item/wrench/cyborg(src) + emag = new /obj/item/gun/energy/lasercannon/cyborg(src) + + fix_modules() + +/obj/item/robot_module/alien/hunter + name = "alien hunter module" + module_type = "Standard" + module_actions = list( + /datum/action/innate/robot_sight/thermal/alien, + ) + +/obj/item/robot_module/alien/hunter/New() + ..() + modules += new /obj/item/melee/energy/alien/claws(src) + modules += new /obj/item/flash/cyborg/alien(src) + var/obj/item/reagent_containers/spray/alien/stun/S = new /obj/item/reagent_containers/spray/alien/stun(src) + S.reagents.add_reagent("ether",250) //nerfed to sleeptoxin to make it less instant drop. + modules += S + var/obj/item/reagent_containers/spray/alien/smoke/A = new /obj/item/reagent_containers/spray/alien/smoke(src) + S.reagents.add_reagent("water",50) //Water is used as a dummy reagent for the smoke bombs. More of an ammo counter. + modules += A + emag = new /obj/item/reagent_containers/spray/alien/acid(src) + emag.reagents.add_reagent("facid", 125) + emag.reagents.add_reagent("sacid", 125) + + fix_modules() + +/obj/item/robot_module/alien/hunter/add_languages(var/mob/living/silicon/robot/R) + ..() + R.add_language("xenocommon", 1) + +/obj/item/robot_module/drone + name = "drone module" + module_type = "Engineer" + stacktypes = list( + /obj/item/stack/sheet/wood = 10, + /obj/item/stack/sheet/rglass/cyborg = 50, + /obj/item/stack/tile/wood = 20, + /obj/item/stack/rods/cyborg = 60, + /obj/item/stack/tile/plasteel = 20, + /obj/item/stack/sheet/metal/cyborg = 50, + /obj/item/stack/sheet/glass/cyborg = 50, + /obj/item/stack/cable_coil/cyborg = 30 + ) + +/obj/item/robot_module/drone/New() + ..() + modules += new /obj/item/weldingtool/largetank/cyborg(src) + modules += new /obj/item/screwdriver/cyborg(src) + modules += new /obj/item/wrench/cyborg(src) + modules += new /obj/item/crowbar/cyborg(src) + modules += new /obj/item/wirecutters/cyborg(src) + modules += new /obj/item/multitool/cyborg(src) + modules += new /obj/item/lightreplacer/cyborg(src) + modules += new /obj/item/gripper(src) + modules += new /obj/item/matter_decompiler(src) + modules += new /obj/item/reagent_containers/spray/cleaner/drone(src) + modules += new /obj/item/soap(src) + modules += new /obj/item/t_scanner(src) + modules += new /obj/item/rpd(src) + + for(var/T in stacktypes) + var/obj/item/stack/sheet/W = new T(src) + W.amount = stacktypes[T] + modules += W + + fix_modules() + +/obj/item/robot_module/drone/add_default_robot_items() + return + +/obj/item/robot_module/drone/respawn_consumable(mob/living/silicon/robot/R) + var/obj/item/reagent_containers/spray/cleaner/C = locate() in modules + C.reagents.add_reagent("cleaner", 3) + ..() + + +/obj/item/robot_module/drone/handle_death() + var/obj/item/gripper/G = locate(/obj/item/gripper) in modules + if(G) + G.drop_item() + +//checks whether this item is a module of the robot it is located in. +/obj/item/proc/is_robot_module() + if(!istype(loc, /mob/living/silicon/robot)) + return 0 + + var/mob/living/silicon/robot/R = loc + + return (src in R.module.modules) diff --git a/code/modules/mob/living/silicon/robot/robot_movement.dm b/code/modules/mob/living/silicon/robot/robot_movement.dm index e896e7c9473..4b645b02849 100644 --- a/code/modules/mob/living/silicon/robot/robot_movement.dm +++ b/code/modules/mob/living/silicon/robot/robot_movement.dm @@ -21,4 +21,4 @@ /mob/living/silicon/robot/experience_pressure_difference(pressure_difference, direction) if(!magpulse) - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 8092947919f..f75475fa92d 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -1,115 +1,115 @@ -/mob/living/silicon/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) - log_say(multilingual_to_message(message_pieces), src) - if(..()) - return 1 - -/mob/living/silicon/robot/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) - if(..()) - return 1 - if(message_mode) - used_radios += radio - if(!is_component_functioning("radio")) - to_chat(src, "Your radio isn't functional at this time.") - return 0 - if(message_mode == "general") - message_mode = null - return radio.talk_into(src,message_pieces,message_mode,verb) - -/mob/living/silicon/ai/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) - if(..()) - return 1 - if(message_mode == "department") - used_radios += aiRadio - return holopad_talk(message_pieces, verb) - else if(message_mode) - used_radios += aiRadio - if(aiRadio.disabledAi || aiRestorePowerRoutine || stat) - to_chat(src, "System Error - Transceiver Disabled.") - return 0 - if(message_mode == "general") - message_mode = null - return aiRadio.talk_into(src, message_pieces, message_mode, verb) - -/mob/living/silicon/pai/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) - if(..()) - return 1 - else if(message_mode == "whisper") - whisper_say(message_pieces) - return 1 - else if(message_mode) - if(message_mode == "general") - message_mode = null - used_radios += radio - return radio.talk_into(src, message_pieces, message_mode, verb) - -/mob/living/silicon/say_quote(var/text) - var/ending = copytext(text, length(text)) - - if(ending == "?") - return speak_query - else if(ending == "!") - return speak_exclamation - - return speak_statement - -#define IS_AI 1 -#define IS_ROBOT 2 -#define IS_PAI 3 - -/mob/living/silicon/say_understands(var/other,var/datum/language/speaking = null) - //These only pertain to common. Languages are handled by mob/say_understands() - if(!speaking) - if(istype(other, /mob/living/carbon)) - return 1 - if(istype(other, /mob/living/silicon)) - return 1 - if(istype(other, /mob/living/simple_animal/bot)) - return 1 - if(istype(other, /mob/living/carbon/brain)) - return 1 - return ..() - -//For holopads only. Usable by AI. -/mob/living/silicon/ai/proc/holopad_talk(list/message_pieces, verb) - log_say("(HPAD) [multilingual_to_message(message_pieces)]", src) - - var/obj/machinery/hologram/holopad/T = current - if(istype(T) && T.masters[src]) - for(var/mob/M in hearers(T.loc))//The location is the object, default distance. - M.hear_holopad_talk(message_pieces, verb, src) - to_chat(src, "Holopad transmitted, [real_name] [combine_message(message_pieces, verb, src)]") - else - to_chat(src, "No holopad connected.") - return - return 1 - -/mob/living/silicon/ai/proc/holopad_emote(var/message) //This is called when the AI uses the 'me' verb while using a holopad. - message = trim(message) - - if(!message) - return - - var/obj/machinery/hologram/holopad/T = current - if(istype(T) && T.masters[src]) - var/rendered = "[name] [message]" - to_chat(src, "Holopad action relayed, [real_name] [message]") - - for(var/mob/M in viewers(T.loc)) - M.show_message(rendered, 2) - - log_emote("(HPAD) [message]", src) - else //This shouldn't occur, but better safe then sorry. - to_chat(src, "No holopad connected.") - return - return 1 - -/mob/living/silicon/ai/emote(act, type, message, force) - var/obj/machinery/hologram/holopad/T = current - if(istype(T) && T.masters[src])//Is the AI using a holopad? - src.holopad_emote(message) - else //Emote normally, then. - ..() - -#undef IS_AI -#undef IS_ROBOT -#undef IS_PAI +/mob/living/silicon/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) + log_say(multilingual_to_message(message_pieces), src) + if(..()) + return 1 + +/mob/living/silicon/robot/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) + if(..()) + return 1 + if(message_mode) + used_radios += radio + if(!is_component_functioning("radio")) + to_chat(src, "Your radio isn't functional at this time.") + return 0 + if(message_mode == "general") + message_mode = null + return radio.talk_into(src,message_pieces,message_mode,verb) + +/mob/living/silicon/ai/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) + if(..()) + return 1 + if(message_mode == "department") + used_radios += aiRadio + return holopad_talk(message_pieces, verb) + else if(message_mode) + used_radios += aiRadio + if(aiRadio.disabledAi || aiRestorePowerRoutine || stat) + to_chat(src, "System Error - Transceiver Disabled.") + return 0 + if(message_mode == "general") + message_mode = null + return aiRadio.talk_into(src, message_pieces, message_mode, verb) + +/mob/living/silicon/pai/handle_message_mode(message_mode, list/message_pieces, verb, used_radios) + if(..()) + return 1 + else if(message_mode == "whisper") + whisper_say(message_pieces) + return 1 + else if(message_mode) + if(message_mode == "general") + message_mode = null + used_radios += radio + return radio.talk_into(src, message_pieces, message_mode, verb) + +/mob/living/silicon/say_quote(var/text) + var/ending = copytext(text, length(text)) + + if(ending == "?") + return speak_query + else if(ending == "!") + return speak_exclamation + + return speak_statement + +#define IS_AI 1 +#define IS_ROBOT 2 +#define IS_PAI 3 + +/mob/living/silicon/say_understands(var/other,var/datum/language/speaking = null) + //These only pertain to common. Languages are handled by mob/say_understands() + if(!speaking) + if(istype(other, /mob/living/carbon)) + return 1 + if(istype(other, /mob/living/silicon)) + return 1 + if(istype(other, /mob/living/simple_animal/bot)) + return 1 + if(istype(other, /mob/living/carbon/brain)) + return 1 + return ..() + +//For holopads only. Usable by AI. +/mob/living/silicon/ai/proc/holopad_talk(list/message_pieces, verb) + log_say("(HPAD) [multilingual_to_message(message_pieces)]", src) + + var/obj/machinery/hologram/holopad/T = current + if(istype(T) && T.masters[src]) + for(var/mob/M in hearers(T.loc))//The location is the object, default distance. + M.hear_holopad_talk(message_pieces, verb, src) + to_chat(src, "Holopad transmitted, [real_name] [combine_message(message_pieces, verb, src)]") + else + to_chat(src, "No holopad connected.") + return + return 1 + +/mob/living/silicon/ai/proc/holopad_emote(var/message) //This is called when the AI uses the 'me' verb while using a holopad. + message = trim(message) + + if(!message) + return + + var/obj/machinery/hologram/holopad/T = current + if(istype(T) && T.masters[src]) + var/rendered = "[name] [message]" + to_chat(src, "Holopad action relayed, [real_name] [message]") + + for(var/mob/M in viewers(T.loc)) + M.show_message(rendered, 2) + + log_emote("(HPAD) [message]", src) + else //This shouldn't occur, but better safe then sorry. + to_chat(src, "No holopad connected.") + return + return 1 + +/mob/living/silicon/ai/emote(act, type, message, force) + var/obj/machinery/hologram/holopad/T = current + if(istype(T) && T.masters[src])//Is the AI using a holopad? + src.holopad_emote(message) + else //Emote normally, then. + ..() + +#undef IS_AI +#undef IS_ROBOT +#undef IS_PAI diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index e70c00158a7..ffbc375be04 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -1,360 +1,360 @@ -/mob/living/silicon - gender = NEUTER - robot_talk_understand = 1 - voice_name = "synthesized voice" - has_unlimited_silicon_privilege = 1 - var/syndicate = 0 - var/const/MAIN_CHANNEL = "Main Frequency" - var/lawchannel = MAIN_CHANNEL // Default channel on which to state laws - var/list/stating_laws = list()// Channels laws are currently being stated on - var/list/alarms_to_show = list() - var/list/alarms_to_clear = list() - //var/list/hud_list[10] - var/list/speech_synthesizer_langs = list() //which languages can be vocalized by the speech synthesizer - var/list/alarm_handlers = list() // List of alarm handlers this silicon is registered to - var/designation = "" - var/obj/item/camera/siliconcam/aiCamera = null //photography -//Used in say.dm, allows for pAIs to have different say flavor text, as well as silicons, although the latter is not implemented. - var/speak_statement = "states" - var/speak_exclamation = "declares" - var/speak_query = "queries" - var/pose //Yes, now AIs can pose too. - var/death_sound = 'sound/voice/borg_deathsound.ogg' - - //var/sensor_mode = 0 //Determines the current HUD. - - var/next_alarm_notice - var/list/datum/alarm/queued_alarms = new() - - hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD) - - - var/med_hud = DATA_HUD_MEDICAL_ADVANCED //Determines the med hud to use - var/sec_hud = DATA_HUD_SECURITY_ADVANCED //Determines the sec hud to use - var/d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED //There is only one kind of diag hud - - var/obj/item/radio/common_radio - -/mob/living/silicon/New() - GLOB.silicon_mob_list |= src - ..() - var/datum/atom_hud/data/diagnostic/diag_hud = huds[DATA_HUD_DIAGNOSTIC] - diag_hud.add_to_hud(src) - diag_hud_set_status() - diag_hud_set_health() - add_language("Galactic Common") - init_subsystems() - -/mob/living/silicon/med_hud_set_health() - return //we use a different hud - -/mob/living/silicon/med_hud_set_status() - return //we use a different hud - -/mob/living/silicon/Destroy() - GLOB.silicon_mob_list -= src - for(var/datum/alarm_handler/AH in alarm_handlers) - AH.unregister(src) - return ..() - -/mob/living/silicon/rename_character(oldname, newname) - // we actually don't want it changing minds and stuff - if(!newname) - return 0 - - real_name = newname - name = real_name - return 1 - -/mob/living/silicon/proc/show_laws() - return - -/mob/living/silicon/drop_item() - return - -/mob/living/silicon/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE) - return FALSE //So borgs they don't die trying to fix wiring - -/mob/living/silicon/emp_act(severity) - switch(severity) - if(1) - src.take_organ_damage(20) - Stun(8) - if(2) - src.take_organ_damage(10) - Stun(3) - flash_eyes(affect_silicon = 1) - to_chat(src, "*BZZZT*") - to_chat(src, "Warning: Electromagnetic pulse detected.") - ..() - - -/mob/living/silicon/proc/damage_mob(var/brute = 0, var/fire = 0, var/tox = 0) - return - -/mob/living/silicon/can_inject(mob/user, error_msg) - if(error_msg) - to_chat(user, "[p_their(TRUE)] outer shell is too tough.") - return FALSE - -/mob/living/silicon/IsAdvancedToolUser() - return TRUE - -/mob/living/silicon/robot/welder_act(mob/user, obj/item/I) - if(user.a_intent != INTENT_HELP) - return - if(user == src) //No self-repair dummy - return - . = TRUE - if(!getBruteLoss()) - to_chat(user, "Nothing to fix!") - return - else if(!getBruteLoss(TRUE)) - to_chat(user, "The damaged components are beyond saving!") - return - if(!I.use_tool(src, user, volume = I.tool_volume)) - return - adjustBruteLoss(-30) - add_fingerprint(user) - user.visible_message("[user] patches some dents on [src] with [I].") - - -/mob/living/silicon/bullet_act(var/obj/item/projectile/Proj) - - - if(!Proj.nodamage) - switch(Proj.damage_type) - if(BRUTE) - adjustBruteLoss(Proj.damage) - if(BURN) - adjustFireLoss(Proj.damage) - - Proj.on_hit(src,2) - - return 2 - -/mob/living/silicon/apply_effect(var/effect = 0,var/effecttype = STUN, var/blocked = 0, var/negate_armor = 0) - return 0//The only effect that can hit them atm is flashes and they still directly edit so this works for now -/* - if(!effect || (blocked >= 2)) return 0 - switch(effecttype) - if(STUN) - Stun(effect / (blocked + 1)) - if(WEAKEN) - Weaken(effect / (blocked + 1)) - if(PARALYZE) - Paralyse(effect / (blocked + 1)) - if(IRRADIATE) - radiation += min((effect - (effect*getarmor(null, "rad"))), 0)//Rads auto check armor - if(STUTTER) - stuttering = max(stuttering,(effect/(blocked+1))) - if(EYE_BLUR) - eye_blurry = max(eye_blurry,(effect/(blocked+1))) - if(DROWSY) - drowsyness = max(drowsyness,(effect/(blocked+1))) - updatehealth() - return 1*/ - -/proc/islinked(var/mob/living/silicon/robot/bot, var/mob/living/silicon/ai/ai) - if(!istype(bot) || !istype(ai)) - return 0 - if(bot.connected_ai == ai) - return 1 - return 0 - - -// this function shows the health of the pAI in the Status panel -/mob/living/silicon/proc/show_system_integrity() - if(!src.stat) - stat(null, text("System integrity: [round((health/maxHealth)*100)]%")) - else - stat(null, text("Systems nonfunctional")) - - -// This adds the basic clock, shuttle recall timer, and malf_ai info to all silicon lifeforms -/mob/living/silicon/Stat() - ..() - if(statpanel("Status")) - show_stat_emergency_shuttle_eta() - show_system_integrity() - -//Silicon mob language procs - -/mob/living/silicon/can_speak_language(datum/language/speaking) - return universal_speak || (speaking in src.speech_synthesizer_langs) //need speech synthesizer support to vocalize a language - -/mob/living/silicon/add_language(var/language, var/can_speak=1) - if(..(language) && can_speak) - speech_synthesizer_langs.Add(GLOB.all_languages[language]) - return 1 - -/mob/living/silicon/remove_language(var/rem_language) - ..(rem_language) - - for(var/datum/language/L in speech_synthesizer_langs) - if(L.name == rem_language) - speech_synthesizer_langs -= L - -/mob/living/silicon/check_lang_data() - . = "" - - if(default_language) - . += "Current default language: [default_language] - reset

        " - - for(var/datum/language/L in languages) - if(!(L.flags & NONGLOBAL)) - var/default_str - if(L == default_language) - default_str = " - default - reset" - else - default_str = " - set default" - - var/synth = (L in speech_synthesizer_langs) - . += "[L.name] (:[L.key])[synth ? default_str : null]
        Speech Synthesizer: [synth ? "YES" : "NOT SUPPORTED"]
        [L.desc]

        " - - -// this function displays the stations manifest in a separate window -/mob/living/silicon/proc/show_station_manifest() - var/dat - dat += "

        Crew Manifest

        " - if(data_core) - dat += data_core.get_manifest(1) // make it monochrome - dat += "
        " - src << browse(dat, "window=airoster") - onclose(src, "airoster") - -/mob/living/silicon/assess_threat() //Secbots won't hunt silicon units - return -10 - -/mob/living/silicon/verb/pose() - set name = "Set Pose" - set desc = "Sets a description which will be shown when someone examines you." - set category = "IC" - - pose = sanitize(copytext(input(usr, "This is [src]. It is...", "Pose", null) as text, 1, MAX_MESSAGE_LEN)) - -/mob/living/silicon/verb/set_flavor() - set name = "Set Flavour Text" - set desc = "Sets an extended description of your character's features." - set category = "IC" - - update_flavor_text() - -/mob/living/silicon/binarycheck() - return 1 - -/mob/living/silicon/proc/remove_med_sec_hud() - var/datum/atom_hud/secsensor = huds[sec_hud] - var/datum/atom_hud/medsensor = huds[med_hud] - for(var/datum/atom_hud/data/diagnostic/diagsensor in huds) - diagsensor.remove_hud_from(src) - secsensor.remove_hud_from(src) - medsensor.remove_hud_from(src) - - -/mob/living/silicon/proc/add_sec_hud() - var/datum/atom_hud/secsensor = huds[sec_hud] - secsensor.add_hud_to(src) - -/mob/living/silicon/proc/add_med_hud() - var/datum/atom_hud/medsensor = huds[med_hud] - medsensor.add_hud_to(src) - -/mob/living/silicon/proc/add_diag_hud() - for(var/datum/atom_hud/data/diagnostic/diagsensor in huds) - diagsensor.add_hud_to(src) - - -/mob/living/silicon/proc/toggle_sensor_mode() - var/sensor_type = input("Please select sensor type.", "Sensor Integration", null) in list("Security", "Medical","Diagnostic","Disable") - remove_med_sec_hud() - switch(sensor_type) - if("Security") - add_sec_hud() - to_chat(src, "Security records overlay enabled.") - if("Medical") - add_med_hud() - to_chat(src, "Life signs monitor overlay enabled.") - if("Diagnostic") - add_diag_hud() - to_chat(src, "Robotics diagnostic overlay enabled.") - if("Disable") - to_chat(src, "Sensor augmentations disabled.") - -/mob/living/silicon/proc/receive_alarm(var/datum/alarm_handler/alarm_handler, var/datum/alarm/alarm, was_raised) - if(!next_alarm_notice) - next_alarm_notice = world.time + SecondsToTicks(10) - - var/list/alarms = queued_alarms[alarm_handler] - if(was_raised) - // Raised alarms are always set - alarms[alarm] = 1 - else - // Alarms that were raised but then cleared before the next notice are instead removed - if(alarm in alarms) - alarms -= alarm - // And alarms that have only been cleared thus far are set as such - else - alarms[alarm] = -1 - -/mob/living/silicon/proc/process_queued_alarms() - if(next_alarm_notice && (world.time > next_alarm_notice)) - next_alarm_notice = 0 - - var/alarm_raised = 0 - for(var/datum/alarm_handler/AH in queued_alarms) - var/list/alarms = queued_alarms[AH] - var/reported = 0 - for(var/datum/alarm/A in alarms) - if(alarms[A] == 1) - if(!reported) - reported = 1 - to_chat(src, "--- [AH.category] Detected ---") - raised_alarm(A) - - for(var/datum/alarm_handler/AH in queued_alarms) - var/list/alarms = queued_alarms[AH] - var/reported = 0 - for(var/datum/alarm/A in alarms) - if(alarms[A] == -1) - if(!reported) - reported = 1 - to_chat(src, "--- [AH.category] Cleared ---") - to_chat(src, "\The [A.alarm_name()].") - - if(alarm_raised) - to_chat(src, "\[Show Alerts\]") - - for(var/datum/alarm_handler/AH in queued_alarms) - var/list/alarms = queued_alarms[AH] - alarms.Cut() - -/mob/living/silicon/proc/raised_alarm(var/datum/alarm/A) - to_chat(src, "[A.alarm_name()]!") - -/mob/living/silicon/ai/raised_alarm(var/datum/alarm/A) - var/cameratext = "" - for(var/obj/machinery/camera/C in A.cameras()) - cameratext += "[(cameratext == "")? "" : "|"][C.c_tag]" - to_chat(src, "[A.alarm_name()]! ([(cameratext)? cameratext : "No Camera"])") - -/mob/living/silicon/adjustToxLoss(var/amount) - return STATUS_UPDATE_NONE - -/mob/living/silicon/get_access() - return IGNORE_ACCESS //silicons always have access - -/mob/living/silicon/flash_eyes(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /obj/screen/fullscreen/flash/noise) - if(affect_silicon) - return ..() - -/mob/living/silicon/is_mechanical() - return 1 - -/mob/living/silicon/is_literate() - return 1 - -/////////////////////////////////// EAR DAMAGE //////////////////////////////////// -/mob/living/silicon/can_hear() - . = TRUE - +/mob/living/silicon + gender = NEUTER + robot_talk_understand = 1 + voice_name = "synthesized voice" + has_unlimited_silicon_privilege = 1 + var/syndicate = 0 + var/const/MAIN_CHANNEL = "Main Frequency" + var/lawchannel = MAIN_CHANNEL // Default channel on which to state laws + var/list/stating_laws = list()// Channels laws are currently being stated on + var/list/alarms_to_show = list() + var/list/alarms_to_clear = list() + //var/list/hud_list[10] + var/list/speech_synthesizer_langs = list() //which languages can be vocalized by the speech synthesizer + var/list/alarm_handlers = list() // List of alarm handlers this silicon is registered to + var/designation = "" + var/obj/item/camera/siliconcam/aiCamera = null //photography +//Used in say.dm, allows for pAIs to have different say flavor text, as well as silicons, although the latter is not implemented. + var/speak_statement = "states" + var/speak_exclamation = "declares" + var/speak_query = "queries" + var/pose //Yes, now AIs can pose too. + var/death_sound = 'sound/voice/borg_deathsound.ogg' + + //var/sensor_mode = 0 //Determines the current HUD. + + var/next_alarm_notice + var/list/datum/alarm/queued_alarms = new() + + hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD) + + + var/med_hud = DATA_HUD_MEDICAL_ADVANCED //Determines the med hud to use + var/sec_hud = DATA_HUD_SECURITY_ADVANCED //Determines the sec hud to use + var/d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED //There is only one kind of diag hud + + var/obj/item/radio/common_radio + +/mob/living/silicon/New() + GLOB.silicon_mob_list |= src + ..() + var/datum/atom_hud/data/diagnostic/diag_hud = huds[DATA_HUD_DIAGNOSTIC] + diag_hud.add_to_hud(src) + diag_hud_set_status() + diag_hud_set_health() + add_language("Galactic Common") + init_subsystems() + +/mob/living/silicon/med_hud_set_health() + return //we use a different hud + +/mob/living/silicon/med_hud_set_status() + return //we use a different hud + +/mob/living/silicon/Destroy() + GLOB.silicon_mob_list -= src + for(var/datum/alarm_handler/AH in alarm_handlers) + AH.unregister(src) + return ..() + +/mob/living/silicon/rename_character(oldname, newname) + // we actually don't want it changing minds and stuff + if(!newname) + return 0 + + real_name = newname + name = real_name + return 1 + +/mob/living/silicon/proc/show_laws() + return + +/mob/living/silicon/drop_item() + return + +/mob/living/silicon/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE) + return FALSE //So borgs they don't die trying to fix wiring + +/mob/living/silicon/emp_act(severity) + switch(severity) + if(1) + src.take_organ_damage(20) + Stun(8) + if(2) + src.take_organ_damage(10) + Stun(3) + flash_eyes(affect_silicon = 1) + to_chat(src, "*BZZZT*") + to_chat(src, "Warning: Electromagnetic pulse detected.") + ..() + + +/mob/living/silicon/proc/damage_mob(var/brute = 0, var/fire = 0, var/tox = 0) + return + +/mob/living/silicon/can_inject(mob/user, error_msg) + if(error_msg) + to_chat(user, "[p_their(TRUE)] outer shell is too tough.") + return FALSE + +/mob/living/silicon/IsAdvancedToolUser() + return TRUE + +/mob/living/silicon/robot/welder_act(mob/user, obj/item/I) + if(user.a_intent != INTENT_HELP) + return + if(user == src) //No self-repair dummy + return + . = TRUE + if(!getBruteLoss()) + to_chat(user, "Nothing to fix!") + return + else if(!getBruteLoss(TRUE)) + to_chat(user, "The damaged components are beyond saving!") + return + if(!I.use_tool(src, user, volume = I.tool_volume)) + return + adjustBruteLoss(-30) + add_fingerprint(user) + user.visible_message("[user] patches some dents on [src] with [I].") + + +/mob/living/silicon/bullet_act(var/obj/item/projectile/Proj) + + + if(!Proj.nodamage) + switch(Proj.damage_type) + if(BRUTE) + adjustBruteLoss(Proj.damage) + if(BURN) + adjustFireLoss(Proj.damage) + + Proj.on_hit(src,2) + + return 2 + +/mob/living/silicon/apply_effect(var/effect = 0,var/effecttype = STUN, var/blocked = 0, var/negate_armor = 0) + return 0//The only effect that can hit them atm is flashes and they still directly edit so this works for now +/* + if(!effect || (blocked >= 2)) return 0 + switch(effecttype) + if(STUN) + Stun(effect / (blocked + 1)) + if(WEAKEN) + Weaken(effect / (blocked + 1)) + if(PARALYZE) + Paralyse(effect / (blocked + 1)) + if(IRRADIATE) + radiation += min((effect - (effect*getarmor(null, "rad"))), 0)//Rads auto check armor + if(STUTTER) + stuttering = max(stuttering,(effect/(blocked+1))) + if(EYE_BLUR) + eye_blurry = max(eye_blurry,(effect/(blocked+1))) + if(DROWSY) + drowsyness = max(drowsyness,(effect/(blocked+1))) + updatehealth() + return 1*/ + +/proc/islinked(var/mob/living/silicon/robot/bot, var/mob/living/silicon/ai/ai) + if(!istype(bot) || !istype(ai)) + return 0 + if(bot.connected_ai == ai) + return 1 + return 0 + + +// this function shows the health of the pAI in the Status panel +/mob/living/silicon/proc/show_system_integrity() + if(!src.stat) + stat(null, text("System integrity: [round((health/maxHealth)*100)]%")) + else + stat(null, text("Systems nonfunctional")) + + +// This adds the basic clock, shuttle recall timer, and malf_ai info to all silicon lifeforms +/mob/living/silicon/Stat() + ..() + if(statpanel("Status")) + show_stat_emergency_shuttle_eta() + show_system_integrity() + +//Silicon mob language procs + +/mob/living/silicon/can_speak_language(datum/language/speaking) + return universal_speak || (speaking in src.speech_synthesizer_langs) //need speech synthesizer support to vocalize a language + +/mob/living/silicon/add_language(var/language, var/can_speak=1) + if(..(language) && can_speak) + speech_synthesizer_langs.Add(GLOB.all_languages[language]) + return 1 + +/mob/living/silicon/remove_language(var/rem_language) + ..(rem_language) + + for(var/datum/language/L in speech_synthesizer_langs) + if(L.name == rem_language) + speech_synthesizer_langs -= L + +/mob/living/silicon/check_lang_data() + . = "" + + if(default_language) + . += "Current default language: [default_language] - reset

        " + + for(var/datum/language/L in languages) + if(!(L.flags & NONGLOBAL)) + var/default_str + if(L == default_language) + default_str = " - default - reset" + else + default_str = " - set default" + + var/synth = (L in speech_synthesizer_langs) + . += "[L.name] (:[L.key])[synth ? default_str : null]
        Speech Synthesizer: [synth ? "YES" : "NOT SUPPORTED"]
        [L.desc]

        " + + +// this function displays the stations manifest in a separate window +/mob/living/silicon/proc/show_station_manifest() + var/dat + dat += "

        Crew Manifest

        " + if(data_core) + dat += data_core.get_manifest(1) // make it monochrome + dat += "
        " + src << browse(dat, "window=airoster") + onclose(src, "airoster") + +/mob/living/silicon/assess_threat() //Secbots won't hunt silicon units + return -10 + +/mob/living/silicon/verb/pose() + set name = "Set Pose" + set desc = "Sets a description which will be shown when someone examines you." + set category = "IC" + + pose = sanitize(copytext(input(usr, "This is [src]. It is...", "Pose", null) as text, 1, MAX_MESSAGE_LEN)) + +/mob/living/silicon/verb/set_flavor() + set name = "Set Flavour Text" + set desc = "Sets an extended description of your character's features." + set category = "IC" + + update_flavor_text() + +/mob/living/silicon/binarycheck() + return 1 + +/mob/living/silicon/proc/remove_med_sec_hud() + var/datum/atom_hud/secsensor = huds[sec_hud] + var/datum/atom_hud/medsensor = huds[med_hud] + for(var/datum/atom_hud/data/diagnostic/diagsensor in huds) + diagsensor.remove_hud_from(src) + secsensor.remove_hud_from(src) + medsensor.remove_hud_from(src) + + +/mob/living/silicon/proc/add_sec_hud() + var/datum/atom_hud/secsensor = huds[sec_hud] + secsensor.add_hud_to(src) + +/mob/living/silicon/proc/add_med_hud() + var/datum/atom_hud/medsensor = huds[med_hud] + medsensor.add_hud_to(src) + +/mob/living/silicon/proc/add_diag_hud() + for(var/datum/atom_hud/data/diagnostic/diagsensor in huds) + diagsensor.add_hud_to(src) + + +/mob/living/silicon/proc/toggle_sensor_mode() + var/sensor_type = input("Please select sensor type.", "Sensor Integration", null) in list("Security", "Medical","Diagnostic","Disable") + remove_med_sec_hud() + switch(sensor_type) + if("Security") + add_sec_hud() + to_chat(src, "Security records overlay enabled.") + if("Medical") + add_med_hud() + to_chat(src, "Life signs monitor overlay enabled.") + if("Diagnostic") + add_diag_hud() + to_chat(src, "Robotics diagnostic overlay enabled.") + if("Disable") + to_chat(src, "Sensor augmentations disabled.") + +/mob/living/silicon/proc/receive_alarm(var/datum/alarm_handler/alarm_handler, var/datum/alarm/alarm, was_raised) + if(!next_alarm_notice) + next_alarm_notice = world.time + SecondsToTicks(10) + + var/list/alarms = queued_alarms[alarm_handler] + if(was_raised) + // Raised alarms are always set + alarms[alarm] = 1 + else + // Alarms that were raised but then cleared before the next notice are instead removed + if(alarm in alarms) + alarms -= alarm + // And alarms that have only been cleared thus far are set as such + else + alarms[alarm] = -1 + +/mob/living/silicon/proc/process_queued_alarms() + if(next_alarm_notice && (world.time > next_alarm_notice)) + next_alarm_notice = 0 + + var/alarm_raised = 0 + for(var/datum/alarm_handler/AH in queued_alarms) + var/list/alarms = queued_alarms[AH] + var/reported = 0 + for(var/datum/alarm/A in alarms) + if(alarms[A] == 1) + if(!reported) + reported = 1 + to_chat(src, "--- [AH.category] Detected ---") + raised_alarm(A) + + for(var/datum/alarm_handler/AH in queued_alarms) + var/list/alarms = queued_alarms[AH] + var/reported = 0 + for(var/datum/alarm/A in alarms) + if(alarms[A] == -1) + if(!reported) + reported = 1 + to_chat(src, "--- [AH.category] Cleared ---") + to_chat(src, "\The [A.alarm_name()].") + + if(alarm_raised) + to_chat(src, "\[Show Alerts\]") + + for(var/datum/alarm_handler/AH in queued_alarms) + var/list/alarms = queued_alarms[AH] + alarms.Cut() + +/mob/living/silicon/proc/raised_alarm(var/datum/alarm/A) + to_chat(src, "[A.alarm_name()]!") + +/mob/living/silicon/ai/raised_alarm(var/datum/alarm/A) + var/cameratext = "" + for(var/obj/machinery/camera/C in A.cameras()) + cameratext += "[(cameratext == "")? "" : "|"][C.c_tag]" + to_chat(src, "[A.alarm_name()]! ([(cameratext)? cameratext : "No Camera"])") + +/mob/living/silicon/adjustToxLoss(var/amount) + return STATUS_UPDATE_NONE + +/mob/living/silicon/get_access() + return IGNORE_ACCESS //silicons always have access + +/mob/living/silicon/flash_eyes(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /obj/screen/fullscreen/flash/noise) + if(affect_silicon) + return ..() + +/mob/living/silicon/is_mechanical() + return 1 + +/mob/living/silicon/is_literate() + return 1 + +/////////////////////////////////// EAR DAMAGE //////////////////////////////////// +/mob/living/silicon/can_hear() + . = TRUE + diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index 6e94accb100..2cfc5e746dc 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -124,4 +124,4 @@ visual_effect_icon = ATTACK_EFFECT_PUNCH else visual_effect_icon = ATTACK_EFFECT_SMASH - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 29fc03a20b3..98f5a3555f4 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -1109,4 +1109,4 @@ Pass a positive integer as an argument to override a bot's default speed. path.Cut(1, 2) /mob/living/simple_animal/bot/proc/drop_part(obj/item/drop_item, dropzone) - new drop_item(dropzone) \ No newline at end of file + new drop_item(dropzone) diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm index ea4f8e58a69..27ecd8e1569 100644 --- a/code/modules/mob/living/simple_animal/bot/construction.dm +++ b/code/modules/mob/living/simple_animal/bot/construction.dm @@ -623,4 +623,4 @@ var/robot_arm = /obj/item/robot_parts/l_arm qdel(W) var/mob/living/simple_animal/bot/honkbot/A = new /mob/living/simple_animal/bot/honkbot(get_turf(src)) A.robot_arm = robot_arm - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/mob/living/simple_animal/bot/griefsky.dm b/code/modules/mob/living/simple_animal/bot/griefsky.dm index f15eaa19db1..8b0878f7115 100644 --- a/code/modules/mob/living/simple_animal/bot/griefsky.dm +++ b/code/modules/mob/living/simple_animal/bot/griefsky.dm @@ -240,4 +240,4 @@ else return ..() else - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 7f889517d34..d9097a82d2e 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -317,4 +317,4 @@ speak("Honk!") sensor_blink() return - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/simple_animal/bot/syndicate.dm b/code/modules/mob/living/simple_animal/bot/syndicate.dm index 24a9d3c2997..3993339f714 100644 --- a/code/modules/mob/living/simple_animal/bot/syndicate.dm +++ b/code/modules/mob/living/simple_animal/bot/syndicate.dm @@ -211,4 +211,4 @@ return raised_alert = TRUE if(depotarea) - depotarea.increase_alert(reason) \ No newline at end of file + depotarea.increase_alert(reason) diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 9dce0cecb62..ee03c204d48 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -1,405 +1,405 @@ - -/mob/living/simple_animal/hostile/construct - name = "Construct" - real_name = "Construct" - desc = "" - speak_emote = list("hisses") - emote_hear = list("wails","screeches") - response_help = "thinks better of touching" - response_disarm = "flails at" - response_harm = "punches" - icon_dead = "shade_dead" - speed = 0 - a_intent = INTENT_HARM - stop_automated_movement = 1 - status_flags = CANPUSH - attack_sound = 'sound/weapons/punch1.ogg' - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - faction = list("cult") - flying = 1 - pressure_resistance = 100 - universal_speak = 1 - AIStatus = AI_OFF //normal constructs don't have AI - var/const_type = "shade" - var/list/construct_spells = list() - var/playstyle_string = "You are a generic construct! Your job is to not exist, and you should probably adminhelp this." - loot = list(/obj/item/reagent_containers/food/snacks/ectoplasm) - del_on_death = 1 - deathmessage = "collapses in a shattered heap." - -/mob/living/simple_animal/hostile/construct/New() - . = ..() - if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way - name = "[const_type] ([rand(1, 1000)])" - real_name = const_type - icon_living = const_type - icon_state = const_type - else - name = "[SSticker.cultdat.get_name(const_type)] ([rand(1, 1000)])" - real_name = SSticker.cultdat.get_name(const_type) - icon_living = SSticker.cultdat.get_icon(const_type) - icon_state = SSticker.cultdat.get_icon(const_type) - - for(var/spell in construct_spells) - AddSpell(new spell(null)) - - if(SSticker.cultdat?.theme == "blood") - updateglow() - -/mob/living/simple_animal/hostile/construct/death(gibbed) - . = ..() - SSticker.mode.remove_cultist(src.mind, FALSE) - -/mob/living/simple_animal/hostile/construct/examine(mob/user) - . = ..() - - var/msg = "" - if(src.health < src.maxHealth) - msg += "" - if(src.health >= src.maxHealth/2) - msg += "It looks slightly dented.\n" - else - msg += "It looks severely dented!\n" - msg += "" - msg += "*---------*
        " - - . += msg - -/mob/living/simple_animal/hostile/construct/attack_animal(mob/living/simple_animal/M) - if(istype(M, /mob/living/simple_animal/hostile/construct/builder)) - if(health < maxHealth) - adjustBruteLoss(-5) - if(src != M) - Beam(M,icon_state="sendbeam",time=4) - M.visible_message("[M] repairs some of \the [src]'s dents.", \ - "You repair some of [src]'s dents, leaving [src] at [health]/[maxHealth] health.") - else - M.visible_message("[M] repairs some of its own dents.", \ - "You repair some of your own dents, leaving you at [M.health]/[M.maxHealth] health.") - else - if(src != M) - to_chat(M, "You cannot repair [src]'s dents, as it has none!") - else - to_chat(M, "You cannot repair your own dents, as you have none!") - else if(src != M) - return ..() - - -/mob/living/simple_animal/hostile/construct/narsie_act() - return - -/mob/living/simple_animal/hostile/construct/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE) - return FALSE - -/////////////////Juggernaut/////////////// - - - -/mob/living/simple_animal/hostile/construct/armoured - name = "Juggernaut" - real_name = "Juggernaut" - desc = "A possessed suit of armour driven by the will of the restless dead" - icon = 'icons/mob/mob.dmi' - icon_state = "behemoth" - icon_living = "behemoth" - maxHealth = 250 - health = 250 - response_harm = "harmlessly punches" - harm_intent_damage = 0 - obj_damage = 90 - melee_damage_lower = 30 - melee_damage_upper = 30 - attacktext = "smashes their armoured gauntlet into" - speed = 3 - environment_smash = 2 - attack_sound = 'sound/weapons/punch3.ogg' - status_flags = 0 - const_type = "juggernaut" - mob_size = MOB_SIZE_LARGE - construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, /obj/effect/proc_holder/spell/aoe_turf/conjure/lesserforcewall) - force_threshold = 11 - playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand extreme punishment, \ - create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." - - -/mob/living/simple_animal/hostile/construct/armoured/hostile //actually hostile, will move around, hit things - AIStatus = AI_ON - environment_smash = 1 //only token destruction, don't smash the cult wall NO STOP - -/mob/living/simple_animal/hostile/construct/armoured/bullet_act(var/obj/item/projectile/P) - if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) - var/reflectchance = 80 - round(P.damage/3) - if(prob(reflectchance)) - if((P.damage_type == BRUTE || P.damage_type == BURN)) - adjustBruteLoss(P.damage * 0.5) - visible_message("The [P.name] gets reflected by [src]'s shell!", \ - "The [P.name] gets reflected by [src]'s shell!") - - P.reflect_back(src, list(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)) - - return -1 // complete projectile permutation - - return (..(P)) - - - -////////////////////////Wraith///////////////////////////////////////////// - - - -/mob/living/simple_animal/hostile/construct/wraith - name = "Wraith" - real_name = "Wraith" - desc = "A wicked bladed shell contraption piloted by a bound spirit" - icon = 'icons/mob/mob.dmi' - icon_state = "floating" - icon_living = "floating" - maxHealth = 75 - health = 75 - melee_damage_lower = 25 - melee_damage_upper = 25 - attacktext = "slashes" - see_in_dark = 8 - attack_sound = 'sound/weapons/bladeslice.ogg' - const_type = "wraith" - construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) - retreat_distance = 2 //AI wraiths will move in and out of combat - playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls." - -/mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things - AIStatus = AI_ON - -/////////////////////////////Artificer///////////////////////// - - - -/mob/living/simple_animal/hostile/construct/builder - name = "Artificer" - real_name = "Artificer" - desc = "A bulbous construct dedicated to building and maintaining Cult armies." - icon = 'icons/mob/mob.dmi' - icon_state = "artificer" - icon_living = "artificer" - maxHealth = 50 - health = 50 - response_harm = "viciously beats" - harm_intent_damage = 5 - obj_damage = 60 - melee_damage_lower = 5 - melee_damage_upper = 5 - attacktext = "rams" - environment_smash = 2 - retreat_distance = 10 - minimum_distance = 10 //AI artificers will flee like fuck - attack_sound = 'sound/weapons/punch2.ogg' - const_type = "builder" - construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/aoe_turf/conjure/pylon, - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) - - playstyle_string = "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, \ - use magic missile, repair allied constructs (by clicking on them), \ - and, most important of all, create new constructs by producing soulstones to capture souls, \ - and shells to place those soulstones into." - - -/mob/living/simple_animal/hostile/construct/builder/Found(atom/A) //what have we found here? - if(isconstruct(A)) //is it a construct? - var/mob/living/simple_animal/hostile/construct/C = A - if(C.health < C.maxHealth) //is it hurt? let's go heal it if it is - return 1 - else - return 0 - else - return 0 - -/mob/living/simple_animal/hostile/construct/builder/CanAttack(atom/the_target) - if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it - return 0 - if(Found(the_target) || ..()) //If we Found it or Can_Attack it normally, we Can_Attack it as long as it wasn't invisible - return 1 //as a note this shouldn't be added to base hostile mobs because it'll mess up retaliate hostile mobs - -/mob/living/simple_animal/hostile/construct/builder/MoveToTarget(var/list/possible_targets) - ..() - if(isliving(target)) - var/mob/living/L = target - if(isconstruct(L) && L.health >= L.maxHealth) //is this target an unhurt construct? stop trying to heal it - LoseTarget() - return 0 - if(L.health <= melee_damage_lower+melee_damage_upper) //ey bucko you're hurt as fuck let's go hit you - retreat_distance = null - minimum_distance = 1 - -/mob/living/simple_animal/hostile/construct/builder/Aggro() - ..() - if(isconstruct(target)) //oh the target is a construct no need to flee - retreat_distance = null - minimum_distance = 1 - -/mob/living/simple_animal/hostile/construct/builder/LoseAggro() - ..() - retreat_distance = initial(retreat_distance) - minimum_distance = initial(minimum_distance) - -/mob/living/simple_animal/hostile/construct/builder/hostile //actually hostile, will move around, hit things, heal other constructs - AIStatus = AI_ON - environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP - - -/////////////////////////////Behemoth///////////////////////// - - -/mob/living/simple_animal/hostile/construct/behemoth - name = "Behemoth" - real_name = "Behemoth" - desc = "The pinnacle of occult technology, Behemoths are the ultimate weapon in the Cult of Nar-Sie's arsenal." - icon = 'icons/mob/mob.dmi' - icon_state = "behemoth" - icon_living = "behemoth" - maxHealth = 750 - health = 750 - speak_emote = list("rumbles") - response_harm = "harmlessly punches" - harm_intent_damage = 0 - melee_damage_lower = 50 - melee_damage_upper = 50 - attacktext = "brutally crushes" - speed = 5 - environment_smash = 2 - attack_sound = 'sound/weapons/punch4.ogg' - force_threshold = 11 - const_type = "behemoth" - var/energy = 0 - var/max_energy = 1000 - -/mob/living/simple_animal/hostile/construct/behemoth/hostile //actually hostile, will move around, hit things - AIStatus = AI_ON - environment_smash = 1 //only token destruction, don't smash the cult wall NO STOP - -/mob/living/simple_animal/hostile/construct/behemoth/Life(seconds, times_fired) - weakened = 0 - return ..() - - -/////////////////////////////Harvester///////////////////////// - -/mob/living/simple_animal/hostile/construct/harvester - name = "Harvester" - real_name = "Harvester" - desc = "A harbinger of Nar-Sie's enlightenment. It'll be all over soon." - icon = 'icons/mob/mob.dmi' - icon_state = "harvester" - icon_living = "harvester" - maxHealth = 60 - health = 60 - melee_damage_lower = 1 - melee_damage_upper = 5 - attacktext = "prods" - environment_smash = ENVIRONMENT_SMASH_RWALLS - see_in_dark = 8 - attack_sound = 'sound/weapons/tap.ogg' - const_type = "harvester" - construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, - /obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/targeted/smoke/disable) - retreat_distance = 2 //AI harvesters will move in and out of combat, like wraiths, but shittier - playstyle_string = "You are a Harvester. You are not strong, but your powers of domination will assist you in your role: \ - Bring those who still cling to this world of illusion back to the master so they may know Truth." - - -/mob/living/simple_animal/hostile/construct/harvester/Process_Spacemove(var/movement_dir = 0) - return 1 - - -/mob/living/simple_animal/hostile/construct/harvester/hostile //actually hostile, will move around, hit things - AIStatus = AI_ON - environment_smash = 1 //only token destruction, don't smash the cult wall NO STOP - -////////////////Glow//////////////////// -/mob/living/simple_animal/hostile/construct/proc/updateglow() - overlays = 0 - var/overlay_layer = LIGHTING_LAYER + 1 - if(layer != MOB_LAYER) - overlay_layer=TURF_LAYER+0.2 - - overlays += image(icon,"glow-[icon_state]",overlay_layer) - set_light(2, -2, l_color = "#FFFFFF") - -///ui stuff - -/mob/living/simple_animal/hostile/construct/armoured/handle_hud_icons_health() - ..() - if(healths) - switch(health) - if(250 to INFINITY) healths.icon_state = "juggernaut_health0" - if(208 to 249) healths.icon_state = "juggernaut_health1" - if(167 to 207) healths.icon_state = "juggernaut_health2" - if(125 to 166) healths.icon_state = "juggernaut_health3" - if(84 to 124) healths.icon_state = "juggernaut_health4" - if(42 to 83) healths.icon_state = "juggernaut_health5" - if(1 to 41) healths.icon_state = "juggernaut_health6" - else healths.icon_state = "juggernaut_health7" - - -/mob/living/simple_animal/hostile/construct/behemoth/handle_hud_icons_health() - ..() - if(healths) - switch(health) - if(750 to INFINITY) healths.icon_state = "juggernaut_health0" - if(625 to 749) healths.icon_state = "juggernaut_health1" - if(500 to 624) healths.icon_state = "juggernaut_health2" - if(375 to 499) healths.icon_state = "juggernaut_health3" - if(250 to 374) healths.icon_state = "juggernaut_health4" - if(125 to 249) healths.icon_state = "juggernaut_health5" - if(1 to 124) healths.icon_state = "juggernaut_health6" - else healths.icon_state = "juggernaut_health7" - -/mob/living/simple_animal/hostile/construct/builder/handle_hud_icons_health() - ..() - if(healths) - switch(health) - if(50 to INFINITY) healths.icon_state = "artificer_health0" - if(42 to 49) healths.icon_state = "artificer_health1" - if(34 to 41) healths.icon_state = "artificer_health2" - if(26 to 33) healths.icon_state = "artificer_health3" - if(18 to 25) healths.icon_state = "artificer_health4" - if(10 to 17) healths.icon_state = "artificer_health5" - if(1 to 9) healths.icon_state = "artificer_health6" - else healths.icon_state = "artificer_health7" - - - -/mob/living/simple_animal/hostile/construct/wraith/handle_hud_icons_health() - - ..() - if(healths) - switch(health) - if(75 to INFINITY) healths.icon_state = "wraith_health0" - if(62 to 74) healths.icon_state = "wraith_health1" - if(50 to 61) healths.icon_state = "wraith_health2" - if(37 to 49) healths.icon_state = "wraith_health3" - if(25 to 36) healths.icon_state = "wraith_health4" - if(12 to 24) healths.icon_state = "wraith_health5" - if(1 to 11) healths.icon_state = "wraith_health6" - else healths.icon_state = "wraith_health7" - - -/mob/living/simple_animal/hostile/construct/harvester/handle_hud_icons_health() - - ..() - if(healths) - switch(health) - if(150 to INFINITY) healths.icon_state = "harvester_health0" - if(125 to 149) healths.icon_state = "harvester_health1" - if(100 to 124) healths.icon_state = "harvester_health2" - if(75 to 99) healths.icon_state = "harvester_health3" - if(50 to 74) healths.icon_state = "harvester_health4" - if(25 to 49) healths.icon_state = "harvester_health5" - if(1 to 24) healths.icon_state = "harvester_health6" - else healths.icon_state = "harvester_health7" \ No newline at end of file + +/mob/living/simple_animal/hostile/construct + name = "Construct" + real_name = "Construct" + desc = "" + speak_emote = list("hisses") + emote_hear = list("wails","screeches") + response_help = "thinks better of touching" + response_disarm = "flails at" + response_harm = "punches" + icon_dead = "shade_dead" + speed = 0 + a_intent = INTENT_HARM + stop_automated_movement = 1 + status_flags = CANPUSH + attack_sound = 'sound/weapons/punch1.ogg' + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + faction = list("cult") + flying = 1 + pressure_resistance = 100 + universal_speak = 1 + AIStatus = AI_OFF //normal constructs don't have AI + var/const_type = "shade" + var/list/construct_spells = list() + var/playstyle_string = "You are a generic construct! Your job is to not exist, and you should probably adminhelp this." + loot = list(/obj/item/reagent_containers/food/snacks/ectoplasm) + del_on_death = 1 + deathmessage = "collapses in a shattered heap." + +/mob/living/simple_animal/hostile/construct/New() + . = ..() + if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way + name = "[const_type] ([rand(1, 1000)])" + real_name = const_type + icon_living = const_type + icon_state = const_type + else + name = "[SSticker.cultdat.get_name(const_type)] ([rand(1, 1000)])" + real_name = SSticker.cultdat.get_name(const_type) + icon_living = SSticker.cultdat.get_icon(const_type) + icon_state = SSticker.cultdat.get_icon(const_type) + + for(var/spell in construct_spells) + AddSpell(new spell(null)) + + if(SSticker.cultdat?.theme == "blood") + updateglow() + +/mob/living/simple_animal/hostile/construct/death(gibbed) + . = ..() + SSticker.mode.remove_cultist(src.mind, FALSE) + +/mob/living/simple_animal/hostile/construct/examine(mob/user) + . = ..() + + var/msg = "" + if(src.health < src.maxHealth) + msg += "" + if(src.health >= src.maxHealth/2) + msg += "It looks slightly dented.\n" + else + msg += "It looks severely dented!\n" + msg += "" + msg += "*---------*
        " + + . += msg + +/mob/living/simple_animal/hostile/construct/attack_animal(mob/living/simple_animal/M) + if(istype(M, /mob/living/simple_animal/hostile/construct/builder)) + if(health < maxHealth) + adjustBruteLoss(-5) + if(src != M) + Beam(M,icon_state="sendbeam",time=4) + M.visible_message("[M] repairs some of \the [src]'s dents.", \ + "You repair some of [src]'s dents, leaving [src] at [health]/[maxHealth] health.") + else + M.visible_message("[M] repairs some of its own dents.", \ + "You repair some of your own dents, leaving you at [M.health]/[M.maxHealth] health.") + else + if(src != M) + to_chat(M, "You cannot repair [src]'s dents, as it has none!") + else + to_chat(M, "You cannot repair your own dents, as you have none!") + else if(src != M) + return ..() + + +/mob/living/simple_animal/hostile/construct/narsie_act() + return + +/mob/living/simple_animal/hostile/construct/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE) + return FALSE + +/////////////////Juggernaut/////////////// + + + +/mob/living/simple_animal/hostile/construct/armoured + name = "Juggernaut" + real_name = "Juggernaut" + desc = "A possessed suit of armour driven by the will of the restless dead" + icon = 'icons/mob/mob.dmi' + icon_state = "behemoth" + icon_living = "behemoth" + maxHealth = 250 + health = 250 + response_harm = "harmlessly punches" + harm_intent_damage = 0 + obj_damage = 90 + melee_damage_lower = 30 + melee_damage_upper = 30 + attacktext = "smashes their armoured gauntlet into" + speed = 3 + environment_smash = 2 + attack_sound = 'sound/weapons/punch3.ogg' + status_flags = 0 + const_type = "juggernaut" + mob_size = MOB_SIZE_LARGE + construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, /obj/effect/proc_holder/spell/aoe_turf/conjure/lesserforcewall) + force_threshold = 11 + playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand extreme punishment, \ + create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." + + +/mob/living/simple_animal/hostile/construct/armoured/hostile //actually hostile, will move around, hit things + AIStatus = AI_ON + environment_smash = 1 //only token destruction, don't smash the cult wall NO STOP + +/mob/living/simple_animal/hostile/construct/armoured/bullet_act(var/obj/item/projectile/P) + if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) + var/reflectchance = 80 - round(P.damage/3) + if(prob(reflectchance)) + if((P.damage_type == BRUTE || P.damage_type == BURN)) + adjustBruteLoss(P.damage * 0.5) + visible_message("The [P.name] gets reflected by [src]'s shell!", \ + "The [P.name] gets reflected by [src]'s shell!") + + P.reflect_back(src, list(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3)) + + return -1 // complete projectile permutation + + return (..(P)) + + + +////////////////////////Wraith///////////////////////////////////////////// + + + +/mob/living/simple_animal/hostile/construct/wraith + name = "Wraith" + real_name = "Wraith" + desc = "A wicked bladed shell contraption piloted by a bound spirit" + icon = 'icons/mob/mob.dmi' + icon_state = "floating" + icon_living = "floating" + maxHealth = 75 + health = 75 + melee_damage_lower = 25 + melee_damage_upper = 25 + attacktext = "slashes" + see_in_dark = 8 + attack_sound = 'sound/weapons/bladeslice.ogg' + const_type = "wraith" + construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) + retreat_distance = 2 //AI wraiths will move in and out of combat + playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, and even able to phase through walls." + +/mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things + AIStatus = AI_ON + +/////////////////////////////Artificer///////////////////////// + + + +/mob/living/simple_animal/hostile/construct/builder + name = "Artificer" + real_name = "Artificer" + desc = "A bulbous construct dedicated to building and maintaining Cult armies." + icon = 'icons/mob/mob.dmi' + icon_state = "artificer" + icon_living = "artificer" + maxHealth = 50 + health = 50 + response_harm = "viciously beats" + harm_intent_damage = 5 + obj_damage = 60 + melee_damage_lower = 5 + melee_damage_upper = 5 + attacktext = "rams" + environment_smash = 2 + retreat_distance = 10 + minimum_distance = 10 //AI artificers will flee like fuck + attack_sound = 'sound/weapons/punch2.ogg' + const_type = "builder" + construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, + /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, + /obj/effect/proc_holder/spell/aoe_turf/conjure/wall, + /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, + /obj/effect/proc_holder/spell/aoe_turf/conjure/pylon, + /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone, + /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) + + playstyle_string = "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, \ + use magic missile, repair allied constructs (by clicking on them), \ + and, most important of all, create new constructs by producing soulstones to capture souls, \ + and shells to place those soulstones into." + + +/mob/living/simple_animal/hostile/construct/builder/Found(atom/A) //what have we found here? + if(isconstruct(A)) //is it a construct? + var/mob/living/simple_animal/hostile/construct/C = A + if(C.health < C.maxHealth) //is it hurt? let's go heal it if it is + return 1 + else + return 0 + else + return 0 + +/mob/living/simple_animal/hostile/construct/builder/CanAttack(atom/the_target) + if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it + return 0 + if(Found(the_target) || ..()) //If we Found it or Can_Attack it normally, we Can_Attack it as long as it wasn't invisible + return 1 //as a note this shouldn't be added to base hostile mobs because it'll mess up retaliate hostile mobs + +/mob/living/simple_animal/hostile/construct/builder/MoveToTarget(var/list/possible_targets) + ..() + if(isliving(target)) + var/mob/living/L = target + if(isconstruct(L) && L.health >= L.maxHealth) //is this target an unhurt construct? stop trying to heal it + LoseTarget() + return 0 + if(L.health <= melee_damage_lower+melee_damage_upper) //ey bucko you're hurt as fuck let's go hit you + retreat_distance = null + minimum_distance = 1 + +/mob/living/simple_animal/hostile/construct/builder/Aggro() + ..() + if(isconstruct(target)) //oh the target is a construct no need to flee + retreat_distance = null + minimum_distance = 1 + +/mob/living/simple_animal/hostile/construct/builder/LoseAggro() + ..() + retreat_distance = initial(retreat_distance) + minimum_distance = initial(minimum_distance) + +/mob/living/simple_animal/hostile/construct/builder/hostile //actually hostile, will move around, hit things, heal other constructs + AIStatus = AI_ON + environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP + + +/////////////////////////////Behemoth///////////////////////// + + +/mob/living/simple_animal/hostile/construct/behemoth + name = "Behemoth" + real_name = "Behemoth" + desc = "The pinnacle of occult technology, Behemoths are the ultimate weapon in the Cult of Nar-Sie's arsenal." + icon = 'icons/mob/mob.dmi' + icon_state = "behemoth" + icon_living = "behemoth" + maxHealth = 750 + health = 750 + speak_emote = list("rumbles") + response_harm = "harmlessly punches" + harm_intent_damage = 0 + melee_damage_lower = 50 + melee_damage_upper = 50 + attacktext = "brutally crushes" + speed = 5 + environment_smash = 2 + attack_sound = 'sound/weapons/punch4.ogg' + force_threshold = 11 + const_type = "behemoth" + var/energy = 0 + var/max_energy = 1000 + +/mob/living/simple_animal/hostile/construct/behemoth/hostile //actually hostile, will move around, hit things + AIStatus = AI_ON + environment_smash = 1 //only token destruction, don't smash the cult wall NO STOP + +/mob/living/simple_animal/hostile/construct/behemoth/Life(seconds, times_fired) + weakened = 0 + return ..() + + +/////////////////////////////Harvester///////////////////////// + +/mob/living/simple_animal/hostile/construct/harvester + name = "Harvester" + real_name = "Harvester" + desc = "A harbinger of Nar-Sie's enlightenment. It'll be all over soon." + icon = 'icons/mob/mob.dmi' + icon_state = "harvester" + icon_living = "harvester" + maxHealth = 60 + health = 60 + melee_damage_lower = 1 + melee_damage_upper = 5 + attacktext = "prods" + environment_smash = ENVIRONMENT_SMASH_RWALLS + see_in_dark = 8 + attack_sound = 'sound/weapons/tap.ogg' + const_type = "harvester" + construct_spells = list(/obj/effect/proc_holder/spell/targeted/night_vision, + /obj/effect/proc_holder/spell/aoe_turf/conjure/wall, + /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, + /obj/effect/proc_holder/spell/targeted/smoke/disable) + retreat_distance = 2 //AI harvesters will move in and out of combat, like wraiths, but shittier + playstyle_string = "You are a Harvester. You are not strong, but your powers of domination will assist you in your role: \ + Bring those who still cling to this world of illusion back to the master so they may know Truth." + + +/mob/living/simple_animal/hostile/construct/harvester/Process_Spacemove(var/movement_dir = 0) + return 1 + + +/mob/living/simple_animal/hostile/construct/harvester/hostile //actually hostile, will move around, hit things + AIStatus = AI_ON + environment_smash = 1 //only token destruction, don't smash the cult wall NO STOP + +////////////////Glow//////////////////// +/mob/living/simple_animal/hostile/construct/proc/updateglow() + overlays = 0 + var/overlay_layer = LIGHTING_LAYER + 1 + if(layer != MOB_LAYER) + overlay_layer=TURF_LAYER+0.2 + + overlays += image(icon,"glow-[icon_state]",overlay_layer) + set_light(2, -2, l_color = "#FFFFFF") + +///ui stuff + +/mob/living/simple_animal/hostile/construct/armoured/handle_hud_icons_health() + ..() + if(healths) + switch(health) + if(250 to INFINITY) healths.icon_state = "juggernaut_health0" + if(208 to 249) healths.icon_state = "juggernaut_health1" + if(167 to 207) healths.icon_state = "juggernaut_health2" + if(125 to 166) healths.icon_state = "juggernaut_health3" + if(84 to 124) healths.icon_state = "juggernaut_health4" + if(42 to 83) healths.icon_state = "juggernaut_health5" + if(1 to 41) healths.icon_state = "juggernaut_health6" + else healths.icon_state = "juggernaut_health7" + + +/mob/living/simple_animal/hostile/construct/behemoth/handle_hud_icons_health() + ..() + if(healths) + switch(health) + if(750 to INFINITY) healths.icon_state = "juggernaut_health0" + if(625 to 749) healths.icon_state = "juggernaut_health1" + if(500 to 624) healths.icon_state = "juggernaut_health2" + if(375 to 499) healths.icon_state = "juggernaut_health3" + if(250 to 374) healths.icon_state = "juggernaut_health4" + if(125 to 249) healths.icon_state = "juggernaut_health5" + if(1 to 124) healths.icon_state = "juggernaut_health6" + else healths.icon_state = "juggernaut_health7" + +/mob/living/simple_animal/hostile/construct/builder/handle_hud_icons_health() + ..() + if(healths) + switch(health) + if(50 to INFINITY) healths.icon_state = "artificer_health0" + if(42 to 49) healths.icon_state = "artificer_health1" + if(34 to 41) healths.icon_state = "artificer_health2" + if(26 to 33) healths.icon_state = "artificer_health3" + if(18 to 25) healths.icon_state = "artificer_health4" + if(10 to 17) healths.icon_state = "artificer_health5" + if(1 to 9) healths.icon_state = "artificer_health6" + else healths.icon_state = "artificer_health7" + + + +/mob/living/simple_animal/hostile/construct/wraith/handle_hud_icons_health() + + ..() + if(healths) + switch(health) + if(75 to INFINITY) healths.icon_state = "wraith_health0" + if(62 to 74) healths.icon_state = "wraith_health1" + if(50 to 61) healths.icon_state = "wraith_health2" + if(37 to 49) healths.icon_state = "wraith_health3" + if(25 to 36) healths.icon_state = "wraith_health4" + if(12 to 24) healths.icon_state = "wraith_health5" + if(1 to 11) healths.icon_state = "wraith_health6" + else healths.icon_state = "wraith_health7" + + +/mob/living/simple_animal/hostile/construct/harvester/handle_hud_icons_health() + + ..() + if(healths) + switch(health) + if(150 to INFINITY) healths.icon_state = "harvester_health0" + if(125 to 149) healths.icon_state = "harvester_health1" + if(100 to 124) healths.icon_state = "harvester_health2" + if(75 to 99) healths.icon_state = "harvester_health3" + if(50 to 74) healths.icon_state = "harvester_health4" + if(25 to 49) healths.icon_state = "harvester_health5" + if(1 to 24) healths.icon_state = "harvester_health6" + else healths.icon_state = "harvester_health7" diff --git a/code/modules/mob/living/simple_animal/corpse.dm b/code/modules/mob/living/simple_animal/corpse.dm index ca54a3592ec..10460d11e65 100644 --- a/code/modules/mob/living/simple_animal/corpse.dm +++ b/code/modules/mob/living/simple_animal/corpse.dm @@ -1,115 +1,115 @@ -//List of different corpse types -/obj/effect/mob_spawn/human/corpse/syndicatesoldier - name = "Syndicate Operative" - mob_name = "Syndicate Operative" - hair_style = "bald" - facial_hair_style = "shaved" - id_job = "Operative" - id_access_list = list(ACCESS_SYNDICATE) - outfit = /datum/outfit/syndicatesoldiercorpse - -/datum/outfit/syndicatesoldiercorpse - name = "Syndicate Operative Corpse" - uniform = /obj/item/clothing/under/syndicate - suit = /obj/item/clothing/suit/armor/vest - shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/combat - l_ear = /obj/item/radio/headset - mask = /obj/item/clothing/mask/gas - head = /obj/item/clothing/head/helmet/swat - back = /obj/item/storage/backpack - id = /obj/item/card/id - - -/obj/effect/mob_spawn/human/corpse/syndicatecommando - name = "Syndicate Commando" - mob_name = "Syndicate Commando" - hair_style = "bald" - facial_hair_style = "shaved" - id_job = "Operative" - id_access_list = list(ACCESS_SYNDICATE) - outfit = /datum/outfit/syndicatecommandocorpse - -/datum/outfit/syndicatecommandocorpse - name = "Syndicate Commando Corpse" - uniform = /obj/item/clothing/under/syndicate - suit = /obj/item/clothing/suit/space/hardsuit/syndi - shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/combat - l_ear = /obj/item/radio/headset - mask = /obj/item/clothing/mask/gas/syndicate - back = /obj/item/tank/jetpack/oxygen - r_pocket = /obj/item/tank/emergency_oxygen - id = /obj/item/card/id - - -/obj/effect/mob_spawn/human/clown/corpse - roundstart = TRUE - instant = TRUE - -/obj/effect/mob_spawn/human/mime/corpse - roundstart = TRUE - instant = TRUE - -/obj/effect/mob_spawn/human/corpse/pirate - name = "Pirate" - mob_name = "Pirate" - hair_style = "bald" - facial_hair_style = "shaved" - outfit = /datum/outfit/piratecorpse - -/datum/outfit/piratecorpse - name = "Pirate Corpse" - uniform = /obj/item/clothing/under/pirate - shoes = /obj/item/clothing/shoes/jackboots - glasses = /obj/item/clothing/glasses/eyepatch - head = /obj/item/clothing/head/bandana - - -/obj/effect/mob_spawn/human/corpse/pirate/ranged - name = "Pirate Gunner" - mob_name = "Pirate Gunner" - outfit = /datum/outfit/piratecorpse/ranged - -/datum/outfit/piratecorpse/ranged - name = "Pirate Gunner Corpse" - suit = /obj/item/clothing/suit/pirate_black - head = /obj/item/clothing/head/pirate - - -/obj/effect/mob_spawn/human/corpse/russian - name = "Russian" - mob_name = "Russian" - hair_style = "bald" - facial_hair_style = "shaved" - outfit = /datum/outfit/russiancorpse - -/datum/outfit/russiancorpse - name = "Russian Corpse" - uniform = /obj/item/clothing/under/soviet - shoes = /obj/item/clothing/shoes/jackboots - head = /obj/item/clothing/head/bearpelt - - -/obj/effect/mob_spawn/human/corpse/russian/ranged - outfit = /datum/outfit/russiancorpse/ranged - -/datum/outfit/russiancorpse/ranged - name = "Ranged Russian Corpse" - head = /obj/item/clothing/head/ushanka - - -/obj/effect/mob_spawn/human/corpse/wizard - name = "Space Wizard Corpse" - outfit = /datum/outfit/wizardcorpse - -/obj/effect/mob_spawn/human/corpse/clownoff/Initialize() - mob_name = "[pick(GLOB.wizard_first)], [pick(GLOB.wizard_second)]" - ..() - -/datum/outfit/wizardcorpse - name = "Space Wizard Corpse" - uniform = /obj/item/clothing/under/color/lightpurple - suit = /obj/item/clothing/suit/wizrobe - shoes = /obj/item/clothing/shoes/sandal - head = /obj/item/clothing/head/wizard +//List of different corpse types +/obj/effect/mob_spawn/human/corpse/syndicatesoldier + name = "Syndicate Operative" + mob_name = "Syndicate Operative" + hair_style = "bald" + facial_hair_style = "shaved" + id_job = "Operative" + id_access_list = list(ACCESS_SYNDICATE) + outfit = /datum/outfit/syndicatesoldiercorpse + +/datum/outfit/syndicatesoldiercorpse + name = "Syndicate Operative Corpse" + uniform = /obj/item/clothing/under/syndicate + suit = /obj/item/clothing/suit/armor/vest + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + l_ear = /obj/item/radio/headset + mask = /obj/item/clothing/mask/gas + head = /obj/item/clothing/head/helmet/swat + back = /obj/item/storage/backpack + id = /obj/item/card/id + + +/obj/effect/mob_spawn/human/corpse/syndicatecommando + name = "Syndicate Commando" + mob_name = "Syndicate Commando" + hair_style = "bald" + facial_hair_style = "shaved" + id_job = "Operative" + id_access_list = list(ACCESS_SYNDICATE) + outfit = /datum/outfit/syndicatecommandocorpse + +/datum/outfit/syndicatecommandocorpse + name = "Syndicate Commando Corpse" + uniform = /obj/item/clothing/under/syndicate + suit = /obj/item/clothing/suit/space/hardsuit/syndi + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + l_ear = /obj/item/radio/headset + mask = /obj/item/clothing/mask/gas/syndicate + back = /obj/item/tank/jetpack/oxygen + r_pocket = /obj/item/tank/emergency_oxygen + id = /obj/item/card/id + + +/obj/effect/mob_spawn/human/clown/corpse + roundstart = TRUE + instant = TRUE + +/obj/effect/mob_spawn/human/mime/corpse + roundstart = TRUE + instant = TRUE + +/obj/effect/mob_spawn/human/corpse/pirate + name = "Pirate" + mob_name = "Pirate" + hair_style = "bald" + facial_hair_style = "shaved" + outfit = /datum/outfit/piratecorpse + +/datum/outfit/piratecorpse + name = "Pirate Corpse" + uniform = /obj/item/clothing/under/pirate + shoes = /obj/item/clothing/shoes/jackboots + glasses = /obj/item/clothing/glasses/eyepatch + head = /obj/item/clothing/head/bandana + + +/obj/effect/mob_spawn/human/corpse/pirate/ranged + name = "Pirate Gunner" + mob_name = "Pirate Gunner" + outfit = /datum/outfit/piratecorpse/ranged + +/datum/outfit/piratecorpse/ranged + name = "Pirate Gunner Corpse" + suit = /obj/item/clothing/suit/pirate_black + head = /obj/item/clothing/head/pirate + + +/obj/effect/mob_spawn/human/corpse/russian + name = "Russian" + mob_name = "Russian" + hair_style = "bald" + facial_hair_style = "shaved" + outfit = /datum/outfit/russiancorpse + +/datum/outfit/russiancorpse + name = "Russian Corpse" + uniform = /obj/item/clothing/under/soviet + shoes = /obj/item/clothing/shoes/jackboots + head = /obj/item/clothing/head/bearpelt + + +/obj/effect/mob_spawn/human/corpse/russian/ranged + outfit = /datum/outfit/russiancorpse/ranged + +/datum/outfit/russiancorpse/ranged + name = "Ranged Russian Corpse" + head = /obj/item/clothing/head/ushanka + + +/obj/effect/mob_spawn/human/corpse/wizard + name = "Space Wizard Corpse" + outfit = /datum/outfit/wizardcorpse + +/obj/effect/mob_spawn/human/corpse/clownoff/Initialize() + mob_name = "[pick(GLOB.wizard_first)], [pick(GLOB.wizard_second)]" + ..() + +/datum/outfit/wizardcorpse + name = "Space Wizard Corpse" + uniform = /obj/item/clothing/under/color/lightpurple + suit = /obj/item/clothing/suit/wizrobe + shoes = /obj/item/clothing/shoes/sandal + head = /obj/item/clothing/head/wizard diff --git a/code/modules/mob/living/simple_animal/damage_procs.dm b/code/modules/mob/living/simple_animal/damage_procs.dm index 6899b40bea0..6ba69de6a61 100644 --- a/code/modules/mob/living/simple_animal/damage_procs.dm +++ b/code/modules/mob/living/simple_animal/damage_procs.dm @@ -34,4 +34,4 @@ /mob/living/simple_animal/adjustStaminaLoss(amount, updating_health = TRUE) if(damage_coeff[STAMINA]) - return ..(amount*damage_coeff[STAMINA], updating_health) \ No newline at end of file + return ..(amount*damage_coeff[STAMINA], updating_health) diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index f552bb5cebd..24818d07652 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -1,279 +1,279 @@ -//Cat -/mob/living/simple_animal/pet/cat - name = "cat" - desc = "Kitty!!" - icon_state = "cat2" - icon_living = "cat2" - icon_dead = "cat2_dead" - icon_resting = "cat2_rest" - gender = MALE - speak = list("Meow!", "Esp!", "Purr!", "HSSSSS") - speak_emote = list("purrs", "meows") - emote_hear = list("meows", "mews") - emote_see = list("shakes its head", "shivers") - var/meow_sound = 'sound/creatures/cat_meow.ogg' //Used in emote. - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - mob_size = MOB_SIZE_SMALL - animal_species = /mob/living/simple_animal/pet/cat - childtype = list(/mob/living/simple_animal/pet/cat/kitten) - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 3) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - gold_core_spawnable = FRIENDLY_SPAWN - collar_type = "cat" - var/turns_since_scan = 0 - var/mob/living/simple_animal/mouse/movement_target - var/eats_mice = 1 - -//RUNTIME IS ALIVE! SQUEEEEEEEE~ -/mob/living/simple_animal/pet/cat/Runtime - name = "Runtime" - desc = "GCAT" - icon_state = "cat" - icon_living = "cat" - icon_dead = "cat_dead" - icon_resting = "cat_rest" - gender = FEMALE - gold_core_spawnable = NO_SPAWN - unique_pet = TRUE - var/list/family = list() - var/memory_saved = 0 - var/list/children = list() //Actual mob instances of children - var/cats_deployed = 0 - -/mob/living/simple_animal/pet/cat/Runtime/New() - Read_Memory() - ..() - -/mob/living/simple_animal/pet/cat/Runtime/Life(seconds, times_fired) - if(!cats_deployed && SSticker.current_state >= GAME_STATE_SETTING_UP) - Deploy_The_Cats() - if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) - Write_Memory() - ..() - -/mob/living/simple_animal/pet/cat/Runtime/make_babies() - var/mob/baby = ..() - if(baby) - children += baby - return baby - -/mob/living/simple_animal/pet/cat/Runtime/death() - if(can_die() && !memory_saved) - Write_Memory(1) - return ..() - -/mob/living/simple_animal/pet/cat/Runtime/proc/Read_Memory() - var/savefile/S = new /savefile("data/npc_saves/Runtime.sav") - S["family"] >> family - - if(isnull(family)) - family = list() - -/mob/living/simple_animal/pet/cat/Runtime/proc/Write_Memory(dead) - var/savefile/S = new /savefile("data/npc_saves/Runtime.sav") - family = list() - if(!dead) - for(var/mob/living/simple_animal/pet/cat/kitten/C in children) - if(istype(C,type) || C.stat || !C.z || !C.butcher_results) - continue - if(C.type in family) - family[C.type] += 1 - else - family[C.type] = 1 - S["family"] << family - memory_saved = 1 - -/mob/living/simple_animal/pet/cat/Runtime/proc/Deploy_The_Cats() - cats_deployed = 1 - for(var/cat_type in family) - if(family[cat_type] > 0) - for(var/i in 1 to min(family[cat_type],100)) //Limits to about 500 cats, you wouldn't think this would be needed (BUT IT IS) - new cat_type(loc) - - -/mob/living/simple_animal/pet/cat/Life() - ..() - make_babies() - - -/mob/living/simple_animal/pet/cat/handle_automated_action() - if(!stat && !buckled) - if(prob(1)) - custom_emote(1, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) - StartResting() - else if(prob(1)) - custom_emote(1, pick("sits down.", "crouches on its hind legs.", "looks alert.")) - icon_state = "[icon_living]_sit" - collar_type = "[initial(collar_type)]_sit" - resting = TRUE - update_canmove() - else if(prob(1)) - if(resting) - custom_emote(1, pick("gets up and meows.", "walks around.", "stops resting.")) - StopResting() - else - custom_emote(1, pick("grooms its fur.", "twitches its whiskers.", "shakes out its coat.")) - - //MICE! - if(eats_mice && isturf(loc) && !incapacitated()) - for(var/mob/living/simple_animal/mouse/M in view(1, src)) - if(!M.stat && Adjacent(M)) - custom_emote(1, "splats \the [M]!") - M.splat() - movement_target = null - stop_automated_movement = 0 - break - for(var/obj/item/toy/cattoy/T in view(1, src)) - if(T.cooldown < (world.time - 400)) - custom_emote(1, "bats \the [T] around with its paw!") - T.cooldown = world.time - -/mob/living/simple_animal/pet/cat/handle_automated_movement() - . = ..() - if(!stat && !resting && !buckled) - turns_since_scan++ - if(turns_since_scan > 5) - walk_to(src,0) - turns_since_scan = 0 - if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) )) - movement_target = null - stop_automated_movement = 0 - if( !movement_target || !(movement_target.loc in oview(src, 3)) ) - movement_target = null - stop_automated_movement = 0 - for(var/mob/living/simple_animal/mouse/snack in oview(src,3)) - if(isturf(snack.loc) && !snack.stat) - movement_target = snack - break - if(movement_target) - stop_automated_movement = 1 - walk_to(src,movement_target,0,3) - -/mob/living/simple_animal/pet/cat/emote(act, m_type = 1, message = null, force) - if(stat != CONSCIOUS) - return - - var/on_CD = 0 - act = lowertext(act) - switch(act) - if("meow") - on_CD = handle_emote_CD() - if("hiss") - on_CD = handle_emote_CD() - if("purr") - on_CD = handle_emote_CD() - else - on_CD = 0 - - if(!force && on_CD == 1) - return - - switch(act) - if("meow") - message = "[src] [pick(emote_hear)]!" - m_type = 2 //audible - playsound(src, meow_sound, 50, 0.75) - if("hiss") - message = "[src] hisses!" - m_type = 2 - if("purr") - message = "[src] purrs." - m_type = 2 - if("help") - to_chat(src, "scream, meow, hiss, purr") - - ..() - -/mob/living/simple_animal/pet/cat/Proc - name = "Proc" - gender = MALE - gold_core_spawnable = NO_SPAWN - unique_pet = TRUE - -/mob/living/simple_animal/pet/cat/kitten - name = "kitten" - desc = "D'aaawwww" - icon_state = "kitten" - icon_living = "kitten" - icon_dead = "kitten_dead" - icon_resting = null - gender = NEUTER - density = 0 - pass_flags = PASSMOB - collar_type = "kitten" - -/mob/living/simple_animal/pet/cat/Syndi - name = "SyndiCat" - desc = "It's a SyndiCat droid." - icon_state = "Syndicat" - icon_living = "Syndicat" - icon_dead = "Syndicat_dead" - icon_resting = "Syndicat_rest" - meow_sound = null //Need robo-meow. - gender = FEMALE - mutations = list(BREATHLESS) - faction = list("syndicate") - gold_core_spawnable = NO_SPAWN - eats_mice = 0 - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - melee_damage_lower = 5 - melee_damage_upper = 15 - -/mob/living/simple_animal/pet/cat/cak - name = "Keeki" - desc = "It's a cat made out of cake." - icon_state = "cak" - icon_living = "cak" - icon_resting = "cak_rest" - icon_dead = "cak_dead" - health = 50 - maxHealth = 50 - harm_intent_damage = 10 - butcher_results = list( - /obj/item/organ/internal/brain = 1, - /obj/item/organ/internal/heart = 1, - /obj/item/reagent_containers/food/snacks/birthdaycakeslice = 3, - /obj/item/reagent_containers/food/snacks/meat/slab = 2 - ) - response_harm = "takes a bite out of" - attacked_sound = "sound/items/eatfood.ogg" - deathmessage = "loses its false life and collapses!" - death_sound = "bodyfall" - -/mob/living/simple_animal/pet/cat/cak/Life() - ..() - if(stat) - return - if(health < maxHealth) - adjustBruteLoss(-4) - for(var/obj/item/reagent_containers/food/snacks/donut/D in range(1, src)) - if(D.icon_state != "donut2") - D.name = "frosted donut" - D.icon_state = "donut2" - D.reagents.add_reagent("sprinkles", 2) - D.filling_color = "#FF69B4" - -/mob/living/simple_animal/pet/cat/cak/attack_hand(mob/living/L) - ..() - if(L.a_intent == INTENT_HARM && L.reagents && !stat) - L.reagents.add_reagent("nutriment", 0.4) - L.reagents.add_reagent("vitamin", 0.4) - -/mob/living/simple_animal/pet/cat/cak/CheckParts(list/parts) - ..() - var/obj/item/organ/internal/brain/B = locate(/obj/item/organ/internal/brain) in contents - if(!B || !B.brainmob || !B.brainmob.mind) - return - B.brainmob.mind.transfer_to(src) - to_chat(src, "You are a cak! You're a harmless cat/cake hybrid that everyone loves. People can take bites out of you if they're hungry, but you regenerate health \ - so quickly that it generally doesn't matter. You're remarkably resilient to any damage besides this and it's hard for you to really die at all. You should go around and bring happiness and \ - free cake to the station!") - var/new_name = stripped_input(src, "Enter your name, or press \"Cancel\" to stick with Keeki.", "Name Change") - if(new_name) - to_chat(src, "Your name is now \"[new_name]\"!") - name = new_name \ No newline at end of file +//Cat +/mob/living/simple_animal/pet/cat + name = "cat" + desc = "Kitty!!" + icon_state = "cat2" + icon_living = "cat2" + icon_dead = "cat2_dead" + icon_resting = "cat2_rest" + gender = MALE + speak = list("Meow!", "Esp!", "Purr!", "HSSSSS") + speak_emote = list("purrs", "meows") + emote_hear = list("meows", "mews") + emote_see = list("shakes its head", "shivers") + var/meow_sound = 'sound/creatures/cat_meow.ogg' //Used in emote. + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + mob_size = MOB_SIZE_SMALL + animal_species = /mob/living/simple_animal/pet/cat + childtype = list(/mob/living/simple_animal/pet/cat/kitten) + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 3) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + gold_core_spawnable = FRIENDLY_SPAWN + collar_type = "cat" + var/turns_since_scan = 0 + var/mob/living/simple_animal/mouse/movement_target + var/eats_mice = 1 + +//RUNTIME IS ALIVE! SQUEEEEEEEE~ +/mob/living/simple_animal/pet/cat/Runtime + name = "Runtime" + desc = "GCAT" + icon_state = "cat" + icon_living = "cat" + icon_dead = "cat_dead" + icon_resting = "cat_rest" + gender = FEMALE + gold_core_spawnable = NO_SPAWN + unique_pet = TRUE + var/list/family = list() + var/memory_saved = 0 + var/list/children = list() //Actual mob instances of children + var/cats_deployed = 0 + +/mob/living/simple_animal/pet/cat/Runtime/New() + Read_Memory() + ..() + +/mob/living/simple_animal/pet/cat/Runtime/Life(seconds, times_fired) + if(!cats_deployed && SSticker.current_state >= GAME_STATE_SETTING_UP) + Deploy_The_Cats() + if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) + Write_Memory() + ..() + +/mob/living/simple_animal/pet/cat/Runtime/make_babies() + var/mob/baby = ..() + if(baby) + children += baby + return baby + +/mob/living/simple_animal/pet/cat/Runtime/death() + if(can_die() && !memory_saved) + Write_Memory(1) + return ..() + +/mob/living/simple_animal/pet/cat/Runtime/proc/Read_Memory() + var/savefile/S = new /savefile("data/npc_saves/Runtime.sav") + S["family"] >> family + + if(isnull(family)) + family = list() + +/mob/living/simple_animal/pet/cat/Runtime/proc/Write_Memory(dead) + var/savefile/S = new /savefile("data/npc_saves/Runtime.sav") + family = list() + if(!dead) + for(var/mob/living/simple_animal/pet/cat/kitten/C in children) + if(istype(C,type) || C.stat || !C.z || !C.butcher_results) + continue + if(C.type in family) + family[C.type] += 1 + else + family[C.type] = 1 + S["family"] << family + memory_saved = 1 + +/mob/living/simple_animal/pet/cat/Runtime/proc/Deploy_The_Cats() + cats_deployed = 1 + for(var/cat_type in family) + if(family[cat_type] > 0) + for(var/i in 1 to min(family[cat_type],100)) //Limits to about 500 cats, you wouldn't think this would be needed (BUT IT IS) + new cat_type(loc) + + +/mob/living/simple_animal/pet/cat/Life() + ..() + make_babies() + + +/mob/living/simple_animal/pet/cat/handle_automated_action() + if(!stat && !buckled) + if(prob(1)) + custom_emote(1, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) + StartResting() + else if(prob(1)) + custom_emote(1, pick("sits down.", "crouches on its hind legs.", "looks alert.")) + icon_state = "[icon_living]_sit" + collar_type = "[initial(collar_type)]_sit" + resting = TRUE + update_canmove() + else if(prob(1)) + if(resting) + custom_emote(1, pick("gets up and meows.", "walks around.", "stops resting.")) + StopResting() + else + custom_emote(1, pick("grooms its fur.", "twitches its whiskers.", "shakes out its coat.")) + + //MICE! + if(eats_mice && isturf(loc) && !incapacitated()) + for(var/mob/living/simple_animal/mouse/M in view(1, src)) + if(!M.stat && Adjacent(M)) + custom_emote(1, "splats \the [M]!") + M.splat() + movement_target = null + stop_automated_movement = 0 + break + for(var/obj/item/toy/cattoy/T in view(1, src)) + if(T.cooldown < (world.time - 400)) + custom_emote(1, "bats \the [T] around with its paw!") + T.cooldown = world.time + +/mob/living/simple_animal/pet/cat/handle_automated_movement() + . = ..() + if(!stat && !resting && !buckled) + turns_since_scan++ + if(turns_since_scan > 5) + walk_to(src,0) + turns_since_scan = 0 + if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) )) + movement_target = null + stop_automated_movement = 0 + if( !movement_target || !(movement_target.loc in oview(src, 3)) ) + movement_target = null + stop_automated_movement = 0 + for(var/mob/living/simple_animal/mouse/snack in oview(src,3)) + if(isturf(snack.loc) && !snack.stat) + movement_target = snack + break + if(movement_target) + stop_automated_movement = 1 + walk_to(src,movement_target,0,3) + +/mob/living/simple_animal/pet/cat/emote(act, m_type = 1, message = null, force) + if(stat != CONSCIOUS) + return + + var/on_CD = 0 + act = lowertext(act) + switch(act) + if("meow") + on_CD = handle_emote_CD() + if("hiss") + on_CD = handle_emote_CD() + if("purr") + on_CD = handle_emote_CD() + else + on_CD = 0 + + if(!force && on_CD == 1) + return + + switch(act) + if("meow") + message = "[src] [pick(emote_hear)]!" + m_type = 2 //audible + playsound(src, meow_sound, 50, 0.75) + if("hiss") + message = "[src] hisses!" + m_type = 2 + if("purr") + message = "[src] purrs." + m_type = 2 + if("help") + to_chat(src, "scream, meow, hiss, purr") + + ..() + +/mob/living/simple_animal/pet/cat/Proc + name = "Proc" + gender = MALE + gold_core_spawnable = NO_SPAWN + unique_pet = TRUE + +/mob/living/simple_animal/pet/cat/kitten + name = "kitten" + desc = "D'aaawwww" + icon_state = "kitten" + icon_living = "kitten" + icon_dead = "kitten_dead" + icon_resting = null + gender = NEUTER + density = 0 + pass_flags = PASSMOB + collar_type = "kitten" + +/mob/living/simple_animal/pet/cat/Syndi + name = "SyndiCat" + desc = "It's a SyndiCat droid." + icon_state = "Syndicat" + icon_living = "Syndicat" + icon_dead = "Syndicat_dead" + icon_resting = "Syndicat_rest" + meow_sound = null //Need robo-meow. + gender = FEMALE + mutations = list(BREATHLESS) + faction = list("syndicate") + gold_core_spawnable = NO_SPAWN + eats_mice = 0 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + melee_damage_lower = 5 + melee_damage_upper = 15 + +/mob/living/simple_animal/pet/cat/cak + name = "Keeki" + desc = "It's a cat made out of cake." + icon_state = "cak" + icon_living = "cak" + icon_resting = "cak_rest" + icon_dead = "cak_dead" + health = 50 + maxHealth = 50 + harm_intent_damage = 10 + butcher_results = list( + /obj/item/organ/internal/brain = 1, + /obj/item/organ/internal/heart = 1, + /obj/item/reagent_containers/food/snacks/birthdaycakeslice = 3, + /obj/item/reagent_containers/food/snacks/meat/slab = 2 + ) + response_harm = "takes a bite out of" + attacked_sound = "sound/items/eatfood.ogg" + deathmessage = "loses its false life and collapses!" + death_sound = "bodyfall" + +/mob/living/simple_animal/pet/cat/cak/Life() + ..() + if(stat) + return + if(health < maxHealth) + adjustBruteLoss(-4) + for(var/obj/item/reagent_containers/food/snacks/donut/D in range(1, src)) + if(D.icon_state != "donut2") + D.name = "frosted donut" + D.icon_state = "donut2" + D.reagents.add_reagent("sprinkles", 2) + D.filling_color = "#FF69B4" + +/mob/living/simple_animal/pet/cat/cak/attack_hand(mob/living/L) + ..() + if(L.a_intent == INTENT_HARM && L.reagents && !stat) + L.reagents.add_reagent("nutriment", 0.4) + L.reagents.add_reagent("vitamin", 0.4) + +/mob/living/simple_animal/pet/cat/cak/CheckParts(list/parts) + ..() + var/obj/item/organ/internal/brain/B = locate(/obj/item/organ/internal/brain) in contents + if(!B || !B.brainmob || !B.brainmob.mind) + return + B.brainmob.mind.transfer_to(src) + to_chat(src, "You are a cak! You're a harmless cat/cake hybrid that everyone loves. People can take bites out of you if they're hungry, but you regenerate health \ + so quickly that it generally doesn't matter. You're remarkably resilient to any damage besides this and it's hard for you to really die at all. You should go around and bring happiness and \ + free cake to the station!") + var/new_name = stripped_input(src, "Enter your name, or press \"Cancel\" to stick with Keeki.", "Name Change") + if(new_name) + to_chat(src, "Your name is now \"[new_name]\"!") + name = new_name diff --git a/code/modules/mob/living/simple_animal/friendly/crab.dm b/code/modules/mob/living/simple_animal/friendly/crab.dm index 7186eb5f5bf..4a51ce62d4e 100644 --- a/code/modules/mob/living/simple_animal/friendly/crab.dm +++ b/code/modules/mob/living/simple_animal/friendly/crab.dm @@ -1,55 +1,55 @@ -//Look Sir, free crabs! -/mob/living/simple_animal/crab - name = "crab" - desc = "A hard-shelled crustacean. Seems quite content to lounge around all the time." - icon_state = "crab" - icon_living = "crab" - icon_dead = "crab_dead" - speak_emote = list("clicks") - emote_hear = list("clicks") - emote_see = list("clacks") - speak_chance = 1 - turns_per_move = 5 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 1) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "stomps" - stop_automated_movement = 1 - friendly = "pinches" - ventcrawler = 2 - can_hide = 1 - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - -/mob/living/simple_animal/crab/handle_automated_movement() - //CRAB movement - if(!stat) - if(isturf(src.loc) && !resting && !buckled) //This is so it only moves if it's not inside a closet, gentics machine, etc. - turns_since_move++ - if(turns_since_move >= turns_per_move) - var/east_vs_west = pick(4, 8) - if(Process_Spacemove(east_vs_west)) - Move(get_step(src, east_vs_west), east_vs_west) - -//COFFEE! SQUEEEEEEEEE! -/mob/living/simple_animal/crab/Coffee - name = "Coffee" - real_name = "Coffee" - desc = "It's Coffee, the other pet!" - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "stomps" - gold_core_spawnable = NO_SPAWN - unique_pet = TRUE - -/mob/living/simple_animal/crab/evil - name = "Evil Crab" - real_name = "Evil Crab" - desc = "Unnerving, isn't it? It has to be planning something nefarious..." - icon_state = "evilcrab" - icon_living = "evilcrab" - icon_dead = "evilcrab_dead" - response_help = "pokes" - response_disarm = "shoves" - response_harm = "stomps" - gold_core_spawnable = HOSTILE_SPAWN \ No newline at end of file +//Look Sir, free crabs! +/mob/living/simple_animal/crab + name = "crab" + desc = "A hard-shelled crustacean. Seems quite content to lounge around all the time." + icon_state = "crab" + icon_living = "crab" + icon_dead = "crab_dead" + speak_emote = list("clicks") + emote_hear = list("clicks") + emote_see = list("clacks") + speak_chance = 1 + turns_per_move = 5 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 1) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stomps" + stop_automated_movement = 1 + friendly = "pinches" + ventcrawler = 2 + can_hide = 1 + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + +/mob/living/simple_animal/crab/handle_automated_movement() + //CRAB movement + if(!stat) + if(isturf(src.loc) && !resting && !buckled) //This is so it only moves if it's not inside a closet, gentics machine, etc. + turns_since_move++ + if(turns_since_move >= turns_per_move) + var/east_vs_west = pick(4, 8) + if(Process_Spacemove(east_vs_west)) + Move(get_step(src, east_vs_west), east_vs_west) + +//COFFEE! SQUEEEEEEEEE! +/mob/living/simple_animal/crab/Coffee + name = "Coffee" + real_name = "Coffee" + desc = "It's Coffee, the other pet!" + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stomps" + gold_core_spawnable = NO_SPAWN + unique_pet = TRUE + +/mob/living/simple_animal/crab/evil + name = "Evil Crab" + real_name = "Evil Crab" + desc = "Unnerving, isn't it? It has to be planning something nefarious..." + icon_state = "evilcrab" + icon_living = "evilcrab" + icon_dead = "evilcrab_dead" + response_help = "pokes" + response_disarm = "shoves" + response_harm = "stomps" + gold_core_spawnable = HOSTILE_SPAWN diff --git a/code/modules/mob/living/simple_animal/friendly/deer.dm b/code/modules/mob/living/simple_animal/friendly/deer.dm index 2c4c2eda960..a10ec1b7b35 100644 --- a/code/modules/mob/living/simple_animal/friendly/deer.dm +++ b/code/modules/mob/living/simple_animal/friendly/deer.dm @@ -15,4 +15,4 @@ response_disarm = "gently pushes aside" response_harm = "kicks" can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN \ No newline at end of file + gold_core_spawnable = FRIENDLY_SPAWN diff --git a/code/modules/mob/living/simple_animal/friendly/diona.dm b/code/modules/mob/living/simple_animal/friendly/diona.dm index 355d756288b..b4872c9ff87 100644 --- a/code/modules/mob/living/simple_animal/friendly/diona.dm +++ b/code/modules/mob/living/simple_animal/friendly/diona.dm @@ -283,4 +283,4 @@ if("help") to_chat(src, "scream, chirp") - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm index 3717493d26c..4b29e703116 100644 --- a/code/modules/mob/living/simple_animal/friendly/dog.dm +++ b/code/modules/mob/living/simple_animal/friendly/dog.dm @@ -762,4 +762,4 @@ spawn(0) for(var/i in list(1, 2, 4, 8, 4, 2, 1, 2, 4, 8, 4, 2, 1, 2, 4, 8, 4, 2)) dir = i - sleep(1) \ No newline at end of file + sleep(1) diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 6d6b2ced3ab..77c2edbdd53 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -1,448 +1,448 @@ -//goat -/mob/living/simple_animal/hostile/retaliate/goat - name = "goat" - desc = "Not known for their pleasant disposition." - icon_state = "goat" - icon_living = "goat" - icon_dead = "goat_dead" - speak = list("EHEHEHEHEH","eh?") - speak_emote = list("brays") - emote_hear = list("brays") - emote_see = list("shakes its head", "stamps a foot", "glares around") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 4) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - faction = list("neutral") - attack_same = 1 - attacktext = "kicks" - attack_sound = 'sound/weapons/punch1.ogg' - health = 40 - maxHealth = 40 - melee_damage_lower = 1 - melee_damage_upper = 2 - stop_automated_movement_when_pulled = 1 - can_collar = 1 - blood_volume = BLOOD_VOLUME_NORMAL - var/obj/item/udder/udder = null - -/mob/living/simple_animal/hostile/retaliate/goat/New() - udder = new() - . = ..() - -/mob/living/simple_animal/hostile/retaliate/goat/Destroy() - QDEL_NULL(udder) - return ..() - -/mob/living/simple_animal/hostile/retaliate/goat/handle_automated_movement() - . = ..() - //chance to go crazy and start wacking stuff - if(!enemies.len && prob(1)) - Retaliate() - - if(enemies.len && prob(10)) - enemies = list() - LoseTarget() - visible_message("[src] calms down.") - - eat_plants() - if(!pulledby) - for(var/direction in shuffle(list(1, 2, 4, 8, 5, 6, 9, 10))) - var/step = get_step(src, direction) - if(step) - if(locate(/obj/structure/spacevine) in step || locate(/obj/structure/glowshroom) in step) - Move(step, get_dir(src, step)) - -/mob/living/simple_animal/hostile/retaliate/goat/Life(seconds, times_fired) - . = ..() - if(stat == CONSCIOUS) - udder.generateMilk() - -/mob/living/simple_animal/hostile/retaliate/goat/Retaliate() - ..() - visible_message("[src] gets an evil-looking gleam in their eye.") - -/mob/living/simple_animal/hostile/retaliate/goat/Move() - . = ..() - if(!stat) - eat_plants() - -/mob/living/simple_animal/hostile/retaliate/goat/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - if(stat == CONSCIOUS && istype(O, /obj/item/reagent_containers/glass)) - udder.milkAnimal(O, user) - else - return ..() - -/mob/living/simple_animal/hostile/retaliate/goat/proc/eat_plants() - var/eaten = FALSE - var/obj/structure/spacevine/SV = locate(/obj/structure/spacevine) in loc - if(SV) - SV.eat(src) - eaten = TRUE - - var/obj/structure/glowshroom/GS = locate(/obj/structure/glowshroom) in loc - if(GS) - qdel(GS) - eaten = TRUE - - if(eaten && prob(10)) - say("Nom") - -/mob/living/simple_animal/hostile/retaliate/goat/AttackingTarget() - . = ..() - if(. && isdiona(target)) - var/mob/living/carbon/human/H = target - var/obj/item/organ/external/NB = pick(H.bodyparts) - H.visible_message("[src] takes a big chomp out of [H]!", "[src] takes a big chomp out of your [NB.name]!") - NB.droplimb() - -//cow -/mob/living/simple_animal/cow - name = "cow" - desc = "Known for their milk, just don't tip them over." - icon_state = "cow" - icon_living = "cow" - icon_dead = "cow_dead" - icon_gib = "cow_gib" - speak = list("moo?","moo","MOOOOOO") - speak_emote = list("moos","moos hauntingly") - emote_hear = list("brays") - emote_see = list("shakes its head") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 6) - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "kicks the" - attacktext = "kicks" - attack_sound = 'sound/weapons/punch1.ogg' - health = 50 - maxHealth = 50 - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - blood_volume = BLOOD_VOLUME_NORMAL - var/obj/item/udder/udder = null - -/mob/living/simple_animal/cow/Initialize() - udder = new() - . = ..() - -/mob/living/simple_animal/cow/Destroy() - qdel(udder) - udder = null - return ..() - -/mob/living/simple_animal/cow/attackby(obj/item/O, mob/user, params) - if(stat == CONSCIOUS && istype(O, /obj/item/reagent_containers/glass)) - udder.milkAnimal(O, user) - return 1 - else - return ..() - -/mob/living/simple_animal/cow/Life(seconds, times_fired) - . = ..() - if(stat == CONSCIOUS) - udder.generateMilk() - -/mob/living/simple_animal/cow/attack_hand(mob/living/carbon/M as mob) - if(!stat && M.a_intent == INTENT_DISARM && icon_state != icon_dead) - M.visible_message("[M] tips over [src].","You tip over [src].") - Weaken(30) - icon_state = icon_dead - spawn(rand(20,50)) - if(!stat && M) - icon_state = icon_living - var/list/responses = list( "[src] looks at you imploringly.", - "[src] looks at you pleadingly", - "[src] looks at you with a resigned expression.", - "[src] seems resigned to its fate.") - to_chat(M, pick(responses)) - else - ..() - -/mob/living/simple_animal/chick - name = "\improper chick" - desc = "Adorable! They make such a racket though." - icon_state = "chick" - icon_living = "chick" - icon_dead = "chick_dead" - icon_gib = "chick_gib" - gender = FEMALE - speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") - speak_emote = list("cheeps") - emote_hear = list("cheeps") - emote_see = list("pecks at the ground","flaps its tiny wings") - density = 0 - speak_chance = 2 - turns_per_move = 2 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 1) - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "kicks the" - attacktext = "kicks" - health = 3 - maxHealth = 3 - ventcrawler = 2 - var/amount_grown = 0 - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - mob_size = MOB_SIZE_TINY - can_hide = 1 - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - -/mob/living/simple_animal/chick/New() - ..() - pixel_x = rand(-6, 6) - pixel_y = rand(0, 10) - -/mob/living/simple_animal/chick/Life(seconds, times_fired) - . =..() - if(.) - amount_grown += rand(1,2) - if(amount_grown >= 100) - var/mob/living/simple_animal/chicken/C = new /mob/living/simple_animal/chicken(loc) - if(mind) - mind.transfer_to(C) - qdel(src) - -#define MAX_CHICKENS 50 -var/global/chicken_count = 0 - -/mob/living/simple_animal/chicken - name = "\improper chicken" - desc = "Hopefully the eggs are good this season." - gender = FEMALE - icon_state = "chicken_brown" - icon_living = "chicken_brown" - icon_dead = "chicken_brown_dead" - speak = list("Cluck!","BWAAAAARK BWAK BWAK BWAK!","Bwaak bwak.") - speak_emote = list("clucks","croons") - emote_hear = list("clucks") - emote_see = list("pecks at the ground","flaps its wings viciously") - density = 0 - speak_chance = 2 - turns_per_move = 3 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 2) - var/egg_type = /obj/item/reagent_containers/food/snacks/egg - var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "kicks the" - attacktext = "kicks" - health = 15 - maxHealth = 15 - ventcrawler = 2 - var/eggsleft = 0 - var/eggsFertile = TRUE - var/body_color - var/icon_prefix = "chicken" - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - can_hide = 1 - can_collar = 1 - var/list/feedMessages = list("It clucks happily.","It clucks happily.") - var/list/layMessage = EGG_LAYING_MESSAGES - var/list/validColors = list("brown","black","white") - gold_core_spawnable = FRIENDLY_SPAWN - -/mob/living/simple_animal/chicken/New() - ..() - if(!body_color) - body_color = pick(validColors) - icon_state = "[icon_prefix]_[body_color]" - icon_living = "[icon_prefix]_[body_color]" - icon_dead = "[icon_prefix]_[body_color]_dead" - pixel_x = rand(-6, 6) - pixel_y = rand(0, 10) - chicken_count += 1 - -/mob/living/simple_animal/chicken/death(gibbed) - // Only execute the below if we successfully died - . = ..(gibbed) - if(!.) - return - chicken_count -= 1 - -/mob/living/simple_animal/chicken/attackby(obj/item/O, mob/user, params) - if(istype(O, food_type)) //feedin' dem chickens - if(!stat && eggsleft < 8) - var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]" - user.visible_message(feedmsg) - user.drop_item() - qdel(O) - eggsleft += rand(1, 4) - //world << eggsleft - else - to_chat(user, "[name] doesn't seem hungry!") - else - ..() - -/mob/living/simple_animal/chicken/Life(seconds, times_fired) - . = ..() - if((. && prob(3) && eggsleft > 0) && egg_type) - visible_message("[src] [pick(layMessage)]") - eggsleft-- - var/obj/item/E = new egg_type(get_turf(src)) - E.pixel_x = rand(-6,6) - E.pixel_y = rand(-6,6) - if(eggsFertile) - if(chicken_count < MAX_CHICKENS && prob(25)) - START_PROCESSING(SSobj, E) - -/obj/item/reagent_containers/food/snacks/egg/var/amount_grown = 0 -/obj/item/reagent_containers/food/snacks/egg/process() - if(isturf(loc)) - amount_grown += rand(1,2) - if(amount_grown >= 100) - visible_message("[src] hatches with a quiet cracking sound.") - new /mob/living/simple_animal/chick(get_turf(src)) - STOP_PROCESSING(SSobj, src) - qdel(src) - else - STOP_PROCESSING(SSobj, src) - - -/mob/living/simple_animal/pig - name = "pig" - desc = "Oink oink." - icon_state = "pig" - icon_living = "pig" - icon_dead = "pig_dead" - speak = list("oink?","oink","OINK") - speak_emote = list("oinks") -// emote_hear = list("brays") - emote_see = list("rolls around") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/ham = 6) - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "kicks the" - attacktext = "kicks" - health = 50 - maxHealth = 50 - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - blood_volume = BLOOD_VOLUME_NORMAL - -/mob/living/simple_animal/turkey - name = "turkey" - desc = "Benjamin Franklin would be proud." - icon_state = "turkey" - icon_living = "turkey" - icon_dead = "turkey_dead" - icon_resting = "turkey_rest" - speak = list("gobble?","gobble","GOBBLE") - speak_emote = list("gobble") - emote_see = list("struts around") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 4) - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "kicks the" - attacktext = "pecks" - health = 50 - maxHealth = 50 - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - -/mob/living/simple_animal/goose - name = "goose" - desc = "A pretty goose. Would make a nice comforter." - icon_state = "goose" - icon_living = "goose" - icon_dead = "goose_dead" - speak = list("quack?","quack","QUACK") - speak_emote = list("quacks") -// emote_hear = list("brays") - emote_see = list("flaps it's wings") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 6) - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "kicks the" - attacktext = "kicks" - health = 50 - maxHealth = 50 - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - -/mob/living/simple_animal/seal - name = "seal" - desc = "A beautiful white seal." - icon_state = "seal" - icon_living = "seal" - icon_dead = "seal_dead" - speak = list("Urk?","urk","URK") - speak_emote = list("urks") -// emote_hear = list("brays") - emote_see = list("flops around") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 6) - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "kicks the" - attacktext = "kicks" - health = 50 - maxHealth = 50 - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - blood_volume = BLOOD_VOLUME_NORMAL - -/mob/living/simple_animal/walrus - name = "walrus" - desc = "A big brown walrus." - icon_state = "walrus" - icon_living = "walrus" - icon_dead = "walrus_dead" - speak = list("Urk?","urk","URK") - speak_emote = list("urks") -// emote_hear = list("brays") - emote_see = list("flops around") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 6) - response_help = "pets the" - response_disarm = "gently pushes aside the" - response_harm = "kicks the" - attacktext = "kicks" - health = 50 - maxHealth = 50 - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - blood_volume = BLOOD_VOLUME_NORMAL - -/obj/item/udder - name = "udder" - -/obj/item/udder/New() - create_reagents(50) - reagents.add_reagent("milk", 20) - . = ..() - -/obj/item/udder/proc/generateMilk() - if(prob(5)) - reagents.add_reagent("milk", rand(5, 10)) - -/obj/item/udder/proc/milkAnimal(obj/O, mob/user) - var/obj/item/reagent_containers/glass/G = O - if(G.reagents.total_volume >= G.volume) - to_chat(user, "[O] is full.") - return - var/transfered = reagents.trans_to(O, rand(5,10)) - if(transfered) - user.visible_message("[user] milks [src] using \the [O].", "You milk [src] using \the [O].") - else - to_chat(user, "The udder is dry. Wait a bit longer...") \ No newline at end of file +//goat +/mob/living/simple_animal/hostile/retaliate/goat + name = "goat" + desc = "Not known for their pleasant disposition." + icon_state = "goat" + icon_living = "goat" + icon_dead = "goat_dead" + speak = list("EHEHEHEHEH","eh?") + speak_emote = list("brays") + emote_hear = list("brays") + emote_see = list("shakes its head", "stamps a foot", "glares around") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 4) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + faction = list("neutral") + attack_same = 1 + attacktext = "kicks" + attack_sound = 'sound/weapons/punch1.ogg' + health = 40 + maxHealth = 40 + melee_damage_lower = 1 + melee_damage_upper = 2 + stop_automated_movement_when_pulled = 1 + can_collar = 1 + blood_volume = BLOOD_VOLUME_NORMAL + var/obj/item/udder/udder = null + +/mob/living/simple_animal/hostile/retaliate/goat/New() + udder = new() + . = ..() + +/mob/living/simple_animal/hostile/retaliate/goat/Destroy() + QDEL_NULL(udder) + return ..() + +/mob/living/simple_animal/hostile/retaliate/goat/handle_automated_movement() + . = ..() + //chance to go crazy and start wacking stuff + if(!enemies.len && prob(1)) + Retaliate() + + if(enemies.len && prob(10)) + enemies = list() + LoseTarget() + visible_message("[src] calms down.") + + eat_plants() + if(!pulledby) + for(var/direction in shuffle(list(1, 2, 4, 8, 5, 6, 9, 10))) + var/step = get_step(src, direction) + if(step) + if(locate(/obj/structure/spacevine) in step || locate(/obj/structure/glowshroom) in step) + Move(step, get_dir(src, step)) + +/mob/living/simple_animal/hostile/retaliate/goat/Life(seconds, times_fired) + . = ..() + if(stat == CONSCIOUS) + udder.generateMilk() + +/mob/living/simple_animal/hostile/retaliate/goat/Retaliate() + ..() + visible_message("[src] gets an evil-looking gleam in their eye.") + +/mob/living/simple_animal/hostile/retaliate/goat/Move() + . = ..() + if(!stat) + eat_plants() + +/mob/living/simple_animal/hostile/retaliate/goat/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + if(stat == CONSCIOUS && istype(O, /obj/item/reagent_containers/glass)) + udder.milkAnimal(O, user) + else + return ..() + +/mob/living/simple_animal/hostile/retaliate/goat/proc/eat_plants() + var/eaten = FALSE + var/obj/structure/spacevine/SV = locate(/obj/structure/spacevine) in loc + if(SV) + SV.eat(src) + eaten = TRUE + + var/obj/structure/glowshroom/GS = locate(/obj/structure/glowshroom) in loc + if(GS) + qdel(GS) + eaten = TRUE + + if(eaten && prob(10)) + say("Nom") + +/mob/living/simple_animal/hostile/retaliate/goat/AttackingTarget() + . = ..() + if(. && isdiona(target)) + var/mob/living/carbon/human/H = target + var/obj/item/organ/external/NB = pick(H.bodyparts) + H.visible_message("[src] takes a big chomp out of [H]!", "[src] takes a big chomp out of your [NB.name]!") + NB.droplimb() + +//cow +/mob/living/simple_animal/cow + name = "cow" + desc = "Known for their milk, just don't tip them over." + icon_state = "cow" + icon_living = "cow" + icon_dead = "cow_dead" + icon_gib = "cow_gib" + speak = list("moo?","moo","MOOOOOO") + speak_emote = list("moos","moos hauntingly") + emote_hear = list("brays") + emote_see = list("shakes its head") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 6) + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "kicks the" + attacktext = "kicks" + attack_sound = 'sound/weapons/punch1.ogg' + health = 50 + maxHealth = 50 + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + blood_volume = BLOOD_VOLUME_NORMAL + var/obj/item/udder/udder = null + +/mob/living/simple_animal/cow/Initialize() + udder = new() + . = ..() + +/mob/living/simple_animal/cow/Destroy() + qdel(udder) + udder = null + return ..() + +/mob/living/simple_animal/cow/attackby(obj/item/O, mob/user, params) + if(stat == CONSCIOUS && istype(O, /obj/item/reagent_containers/glass)) + udder.milkAnimal(O, user) + return 1 + else + return ..() + +/mob/living/simple_animal/cow/Life(seconds, times_fired) + . = ..() + if(stat == CONSCIOUS) + udder.generateMilk() + +/mob/living/simple_animal/cow/attack_hand(mob/living/carbon/M as mob) + if(!stat && M.a_intent == INTENT_DISARM && icon_state != icon_dead) + M.visible_message("[M] tips over [src].","You tip over [src].") + Weaken(30) + icon_state = icon_dead + spawn(rand(20,50)) + if(!stat && M) + icon_state = icon_living + var/list/responses = list( "[src] looks at you imploringly.", + "[src] looks at you pleadingly", + "[src] looks at you with a resigned expression.", + "[src] seems resigned to its fate.") + to_chat(M, pick(responses)) + else + ..() + +/mob/living/simple_animal/chick + name = "\improper chick" + desc = "Adorable! They make such a racket though." + icon_state = "chick" + icon_living = "chick" + icon_dead = "chick_dead" + icon_gib = "chick_gib" + gender = FEMALE + speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") + speak_emote = list("cheeps") + emote_hear = list("cheeps") + emote_see = list("pecks at the ground","flaps its tiny wings") + density = 0 + speak_chance = 2 + turns_per_move = 2 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 1) + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "kicks the" + attacktext = "kicks" + health = 3 + maxHealth = 3 + ventcrawler = 2 + var/amount_grown = 0 + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + mob_size = MOB_SIZE_TINY + can_hide = 1 + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + +/mob/living/simple_animal/chick/New() + ..() + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + +/mob/living/simple_animal/chick/Life(seconds, times_fired) + . =..() + if(.) + amount_grown += rand(1,2) + if(amount_grown >= 100) + var/mob/living/simple_animal/chicken/C = new /mob/living/simple_animal/chicken(loc) + if(mind) + mind.transfer_to(C) + qdel(src) + +#define MAX_CHICKENS 50 +var/global/chicken_count = 0 + +/mob/living/simple_animal/chicken + name = "\improper chicken" + desc = "Hopefully the eggs are good this season." + gender = FEMALE + icon_state = "chicken_brown" + icon_living = "chicken_brown" + icon_dead = "chicken_brown_dead" + speak = list("Cluck!","BWAAAAARK BWAK BWAK BWAK!","Bwaak bwak.") + speak_emote = list("clucks","croons") + emote_hear = list("clucks") + emote_see = list("pecks at the ground","flaps its wings viciously") + density = 0 + speak_chance = 2 + turns_per_move = 3 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 2) + var/egg_type = /obj/item/reagent_containers/food/snacks/egg + var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "kicks the" + attacktext = "kicks" + health = 15 + maxHealth = 15 + ventcrawler = 2 + var/eggsleft = 0 + var/eggsFertile = TRUE + var/body_color + var/icon_prefix = "chicken" + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + can_hide = 1 + can_collar = 1 + var/list/feedMessages = list("It clucks happily.","It clucks happily.") + var/list/layMessage = EGG_LAYING_MESSAGES + var/list/validColors = list("brown","black","white") + gold_core_spawnable = FRIENDLY_SPAWN + +/mob/living/simple_animal/chicken/New() + ..() + if(!body_color) + body_color = pick(validColors) + icon_state = "[icon_prefix]_[body_color]" + icon_living = "[icon_prefix]_[body_color]" + icon_dead = "[icon_prefix]_[body_color]_dead" + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + chicken_count += 1 + +/mob/living/simple_animal/chicken/death(gibbed) + // Only execute the below if we successfully died + . = ..(gibbed) + if(!.) + return + chicken_count -= 1 + +/mob/living/simple_animal/chicken/attackby(obj/item/O, mob/user, params) + if(istype(O, food_type)) //feedin' dem chickens + if(!stat && eggsleft < 8) + var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]" + user.visible_message(feedmsg) + user.drop_item() + qdel(O) + eggsleft += rand(1, 4) + //world << eggsleft + else + to_chat(user, "[name] doesn't seem hungry!") + else + ..() + +/mob/living/simple_animal/chicken/Life(seconds, times_fired) + . = ..() + if((. && prob(3) && eggsleft > 0) && egg_type) + visible_message("[src] [pick(layMessage)]") + eggsleft-- + var/obj/item/E = new egg_type(get_turf(src)) + E.pixel_x = rand(-6,6) + E.pixel_y = rand(-6,6) + if(eggsFertile) + if(chicken_count < MAX_CHICKENS && prob(25)) + START_PROCESSING(SSobj, E) + +/obj/item/reagent_containers/food/snacks/egg/var/amount_grown = 0 +/obj/item/reagent_containers/food/snacks/egg/process() + if(isturf(loc)) + amount_grown += rand(1,2) + if(amount_grown >= 100) + visible_message("[src] hatches with a quiet cracking sound.") + new /mob/living/simple_animal/chick(get_turf(src)) + STOP_PROCESSING(SSobj, src) + qdel(src) + else + STOP_PROCESSING(SSobj, src) + + +/mob/living/simple_animal/pig + name = "pig" + desc = "Oink oink." + icon_state = "pig" + icon_living = "pig" + icon_dead = "pig_dead" + speak = list("oink?","oink","OINK") + speak_emote = list("oinks") +// emote_hear = list("brays") + emote_see = list("rolls around") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/ham = 6) + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "kicks the" + attacktext = "kicks" + health = 50 + maxHealth = 50 + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + blood_volume = BLOOD_VOLUME_NORMAL + +/mob/living/simple_animal/turkey + name = "turkey" + desc = "Benjamin Franklin would be proud." + icon_state = "turkey" + icon_living = "turkey" + icon_dead = "turkey_dead" + icon_resting = "turkey_rest" + speak = list("gobble?","gobble","GOBBLE") + speak_emote = list("gobble") + emote_see = list("struts around") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 4) + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "kicks the" + attacktext = "pecks" + health = 50 + maxHealth = 50 + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + +/mob/living/simple_animal/goose + name = "goose" + desc = "A pretty goose. Would make a nice comforter." + icon_state = "goose" + icon_living = "goose" + icon_dead = "goose_dead" + speak = list("quack?","quack","QUACK") + speak_emote = list("quacks") +// emote_hear = list("brays") + emote_see = list("flaps it's wings") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 6) + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "kicks the" + attacktext = "kicks" + health = 50 + maxHealth = 50 + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + +/mob/living/simple_animal/seal + name = "seal" + desc = "A beautiful white seal." + icon_state = "seal" + icon_living = "seal" + icon_dead = "seal_dead" + speak = list("Urk?","urk","URK") + speak_emote = list("urks") +// emote_hear = list("brays") + emote_see = list("flops around") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 6) + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "kicks the" + attacktext = "kicks" + health = 50 + maxHealth = 50 + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + blood_volume = BLOOD_VOLUME_NORMAL + +/mob/living/simple_animal/walrus + name = "walrus" + desc = "A big brown walrus." + icon_state = "walrus" + icon_living = "walrus" + icon_dead = "walrus_dead" + speak = list("Urk?","urk","URK") + speak_emote = list("urks") +// emote_hear = list("brays") + emote_see = list("flops around") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 6) + response_help = "pets the" + response_disarm = "gently pushes aside the" + response_harm = "kicks the" + attacktext = "kicks" + health = 50 + maxHealth = 50 + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + blood_volume = BLOOD_VOLUME_NORMAL + +/obj/item/udder + name = "udder" + +/obj/item/udder/New() + create_reagents(50) + reagents.add_reagent("milk", 20) + . = ..() + +/obj/item/udder/proc/generateMilk() + if(prob(5)) + reagents.add_reagent("milk", rand(5, 10)) + +/obj/item/udder/proc/milkAnimal(obj/O, mob/user) + var/obj/item/reagent_containers/glass/G = O + if(G.reagents.total_volume >= G.volume) + to_chat(user, "[O] is full.") + return + var/transfered = reagents.trans_to(O, rand(5,10)) + if(transfered) + user.visible_message("[user] milks [src] using \the [O].", "You milk [src] using \the [O].") + else + to_chat(user, "The udder is dry. Wait a bit longer...") diff --git a/code/modules/mob/living/simple_animal/friendly/fox.dm b/code/modules/mob/living/simple_animal/friendly/fox.dm index ad77fd547a5..9eccc473e6d 100644 --- a/code/modules/mob/living/simple_animal/friendly/fox.dm +++ b/code/modules/mob/living/simple_animal/friendly/fox.dm @@ -40,4 +40,4 @@ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 melee_damage_lower = 10 - melee_damage_upper = 20 \ No newline at end of file + melee_damage_upper = 20 diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm index 2d7426aa451..96688963b73 100644 --- a/code/modules/mob/living/simple_animal/friendly/lizard.dm +++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm @@ -1,25 +1,25 @@ -/mob/living/simple_animal/lizard - name = "Lizard" - desc = "A cute tiny lizard." - icon = 'icons/mob/critter.dmi' - icon_state = "lizard" - icon_living = "lizard" - icon_dead = "lizard-dead" - speak_emote = list("hisses") - health = 5 - maxHealth = 5 - attacktext = "bites" - obj_damage = 0 - melee_damage_lower = 1 - melee_damage_upper = 2 - response_help = "pets" - response_disarm = "shoos" - response_harm = "stomps on" - ventcrawler = 2 - density = 0 - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - can_hide = 1 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 1) - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN +/mob/living/simple_animal/lizard + name = "Lizard" + desc = "A cute tiny lizard." + icon = 'icons/mob/critter.dmi' + icon_state = "lizard" + icon_living = "lizard" + icon_dead = "lizard-dead" + speak_emote = list("hisses") + health = 5 + maxHealth = 5 + attacktext = "bites" + obj_damage = 0 + melee_damage_lower = 1 + melee_damage_upper = 2 + response_help = "pets" + response_disarm = "shoos" + response_harm = "stomps on" + ventcrawler = 2 + density = 0 + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + can_hide = 1 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 1) + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index d6f38d33268..1446ef7d4bb 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -1,236 +1,236 @@ -/mob/living/simple_animal/mouse - name = "mouse" - real_name = "mouse" - desc = "It's a small, disease-ridden rodent." - icon_state = "mouse_gray" - icon_living = "mouse_gray" - icon_dead = "mouse_gray_dead" - icon_resting = "mouse_gray_sleep" - speak = list("Squeek!","SQUEEK!","Squeek?") - speak_emote = list("squeeks","squeaks","squiks") - emote_hear = list("squeeks","squeaks","squiks") - emote_see = list("runs in a circle", "shakes", "scritches at something") - var/squeak_sound = 'sound/creatures/mousesqueak.ogg' - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - maxHealth = 5 - health = 5 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 1) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "stamps on" - density = 0 - ventcrawler = 2 - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - mob_size = MOB_SIZE_TINY - var/mouse_color //brown, gray and white, leave blank for random - layer = MOB_LAYER - atmos_requirements = list("min_oxy" = 16, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 223 //Below -50 Degrees Celcius - maxbodytemp = 323 //Above 50 Degrees Celcius - universal_speak = 0 - can_hide = 1 - holder_type = /obj/item/holder/mouse - can_collar = 1 - gold_core_spawnable = FRIENDLY_SPAWN - var/chew_probability = 1 - -/mob/living/simple_animal/mouse/Initialize(mapload) - . = ..() - AddComponent(/datum/component/squeak, list('sound/creatures/mousesqueak.ogg' = 1), 100) - -/mob/living/simple_animal/mouse/handle_automated_action() - if(prob(chew_probability) && isturf(loc)) - var/turf/simulated/floor/F = get_turf(src) - if(istype(F) && !F.intact) - var/obj/structure/cable/C = locate() in F - if(C && prob(15)) - if(C.avail()) - visible_message("[src] chews through [C]. It's toast!") - playsound(src, 'sound/effects/sparks2.ogg', 100, 1) - C.deconstruct() - toast() // mmmm toasty. - else - C.deconstruct() - visible_message("[src] chews through [C].") - -/mob/living/simple_animal/mouse/handle_automated_speech() - ..() - if(prob(speak_chance) && !incapacitated()) - playsound(src, squeak_sound, 100, 1) - -/mob/living/simple_animal/mouse/handle_automated_movement() - . = ..() - if(resting) - if(prob(1)) - StopResting() - else if(prob(5)) - custom_emote(2, "snuffles") - else if(prob(0.5)) - StartResting() - -/mob/living/simple_animal/mouse/New() - ..() - if(!mouse_color) - mouse_color = pick( list("brown","gray","white") ) - icon_state = "mouse_[mouse_color]" - icon_living = "mouse_[mouse_color]" - icon_dead = "mouse_[mouse_color]_dead" - icon_resting = "mouse_[mouse_color]_sleep" - desc = "It's a small [mouse_color] rodent, often seen hiding in maintenance areas and making a nuisance of itself." - -/mob/living/simple_animal/mouse/proc/splat() - src.health = 0 - src.stat = DEAD - src.icon_dead = "mouse_[mouse_color]_splat" - src.icon_state = "mouse_[mouse_color]_splat" - layer = MOB_LAYER - if(client) - client.time_died_as_mouse = world.time - -/mob/living/simple_animal/mouse/attack_hand(mob/living/carbon/human/M as mob) - if(M.a_intent == INTENT_HELP) - get_scooped(M) - ..() - -/mob/living/simple_animal/mouse/start_pulling(var/atom/movable/AM)//Prevents mouse from pulling things - to_chat(src, "You are too small to pull anything.") - return - -/mob/living/simple_animal/mouse/Crossed(AM as mob|obj, oldloc) - if(ishuman(AM)) - if(!stat) - var/mob/M = AM - to_chat(M, "[bicon(src)] Squeek!") - ..() - -/mob/living/simple_animal/mouse/proc/toast() - add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY) - desc = "It's toast." - death() - -/mob/living/simple_animal/mouse/death(gibbed) - // Only execute the below if we successfully died - playsound(src, squeak_sound, 40, 1) - . = ..(gibbed) - if(!.) - return FALSE - layer = MOB_LAYER - if(client) - client.time_died_as_mouse = world.time - -/mob/living/simple_animal/mouse/emote(act, m_type = 1, message = null, force) - if(stat != CONSCIOUS) - return - - var/on_CD = 0 - act = lowertext(act) - switch(act) - if("squeak") //Mouse time - on_CD = handle_emote_CD() - else - on_CD = 0 - - if(!force && on_CD == 1) - return - - switch(act) - if("squeak") - message = "\The [src] [pick(emote_hear)]!" - m_type = 2 //audible - playsound(src, squeak_sound, 40, 1) - if("help") - to_chat(src, "scream, squeak") - - ..() - -/* - * Mouse types - */ - -/mob/living/simple_animal/mouse/white - mouse_color = "white" - icon_state = "mouse_white" - -/mob/living/simple_animal/mouse/gray - mouse_color = "gray" - icon_state = "mouse_gray" - -/mob/living/simple_animal/mouse/brown - mouse_color = "brown" - icon_state = "mouse_brown" - -//TOM IS ALIVE! SQUEEEEEEEE~K :) -/mob/living/simple_animal/mouse/brown/Tom - name = "Tom" - desc = "Jerry the cat is not amused." - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "splats" - unique_pet = TRUE - gold_core_spawnable = NO_SPAWN - - -/mob/living/simple_animal/mouse/blobinfected - maxHealth = 100 - health = 100 - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - gold_core_spawnable = NO_SPAWN - var/cycles_alive = 0 - var/cycles_limit = 60 - var/has_burst = FALSE - -/mob/living/simple_animal/mouse/blobinfected/Life() - cycles_alive++ - var/timeleft = (cycles_limit - cycles_alive) * 2 - if(ismob(loc)) // if someone ate it, burst immediately - burst(FALSE) - else if(timeleft < 1) // if timer expired, burst. - burst(FALSE) - else if(cycles_alive % 2 == 0) // give the mouse/player a countdown reminder every 2 cycles - to_chat(src, "[timeleft] seconds until you burst, and become a blob...") - return ..() - -/mob/living/simple_animal/mouse/blobinfected/death(gibbed) - burst(gibbed) - return ..(gibbed) - -/mob/living/simple_animal/mouse/blobinfected/proc/burst(gibbed) - if(has_burst) - return FALSE - var/turf/T = get_turf(src) - if(!is_station_level(T.z) || isspaceturf(T)) - to_chat(src, "You feel ready to burst, but this isn't an appropriate place! You must return to the station!") - return FALSE - has_burst = TRUE - var/datum/mind/blobmind = mind - var/client/C = client - if(istype(blobmind) && istype(C)) - blobmind.special_role = SPECIAL_ROLE_BLOB - var/obj/structure/blob/core/core = new(T, 200, C, 3) - core.lateblobtimer() - else - new /obj/structure/blob/core(T) // Ghosts will be prompted to control it. - if(ismob(loc)) // in case some taj/etc ate the mouse. - var/mob/M = loc - M.gib() - if(!gibbed) - gib() - -/mob/living/simple_animal/mouse/blobinfected/get_scooped(mob/living/carbon/grabber) - to_chat(grabber, "You try to pick up [src], but they slip out of your grasp!") - to_chat(src, "[src] tries to pick you up, but you wriggle free of their grasp!") - -/mob/living/simple_animal/mouse/fluff/clockwork - name = "Chip" - real_name = "Chip" - mouse_color = "clockwork" - icon_state = "mouse_clockwork" - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "stamps on" - gold_core_spawnable = NO_SPAWN - can_collar = 0 - butcher_results = list(/obj/item/stack/sheet/metal = 1) +/mob/living/simple_animal/mouse + name = "mouse" + real_name = "mouse" + desc = "It's a small, disease-ridden rodent." + icon_state = "mouse_gray" + icon_living = "mouse_gray" + icon_dead = "mouse_gray_dead" + icon_resting = "mouse_gray_sleep" + speak = list("Squeek!","SQUEEK!","Squeek?") + speak_emote = list("squeeks","squeaks","squiks") + emote_hear = list("squeeks","squeaks","squiks") + emote_see = list("runs in a circle", "shakes", "scritches at something") + var/squeak_sound = 'sound/creatures/mousesqueak.ogg' + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + maxHealth = 5 + health = 5 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat = 1) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stamps on" + density = 0 + ventcrawler = 2 + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + mob_size = MOB_SIZE_TINY + var/mouse_color //brown, gray and white, leave blank for random + layer = MOB_LAYER + atmos_requirements = list("min_oxy" = 16, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 223 //Below -50 Degrees Celcius + maxbodytemp = 323 //Above 50 Degrees Celcius + universal_speak = 0 + can_hide = 1 + holder_type = /obj/item/holder/mouse + can_collar = 1 + gold_core_spawnable = FRIENDLY_SPAWN + var/chew_probability = 1 + +/mob/living/simple_animal/mouse/Initialize(mapload) + . = ..() + AddComponent(/datum/component/squeak, list('sound/creatures/mousesqueak.ogg' = 1), 100) + +/mob/living/simple_animal/mouse/handle_automated_action() + if(prob(chew_probability) && isturf(loc)) + var/turf/simulated/floor/F = get_turf(src) + if(istype(F) && !F.intact) + var/obj/structure/cable/C = locate() in F + if(C && prob(15)) + if(C.avail()) + visible_message("[src] chews through [C]. It's toast!") + playsound(src, 'sound/effects/sparks2.ogg', 100, 1) + C.deconstruct() + toast() // mmmm toasty. + else + C.deconstruct() + visible_message("[src] chews through [C].") + +/mob/living/simple_animal/mouse/handle_automated_speech() + ..() + if(prob(speak_chance) && !incapacitated()) + playsound(src, squeak_sound, 100, 1) + +/mob/living/simple_animal/mouse/handle_automated_movement() + . = ..() + if(resting) + if(prob(1)) + StopResting() + else if(prob(5)) + custom_emote(2, "snuffles") + else if(prob(0.5)) + StartResting() + +/mob/living/simple_animal/mouse/New() + ..() + if(!mouse_color) + mouse_color = pick( list("brown","gray","white") ) + icon_state = "mouse_[mouse_color]" + icon_living = "mouse_[mouse_color]" + icon_dead = "mouse_[mouse_color]_dead" + icon_resting = "mouse_[mouse_color]_sleep" + desc = "It's a small [mouse_color] rodent, often seen hiding in maintenance areas and making a nuisance of itself." + +/mob/living/simple_animal/mouse/proc/splat() + src.health = 0 + src.stat = DEAD + src.icon_dead = "mouse_[mouse_color]_splat" + src.icon_state = "mouse_[mouse_color]_splat" + layer = MOB_LAYER + if(client) + client.time_died_as_mouse = world.time + +/mob/living/simple_animal/mouse/attack_hand(mob/living/carbon/human/M as mob) + if(M.a_intent == INTENT_HELP) + get_scooped(M) + ..() + +/mob/living/simple_animal/mouse/start_pulling(var/atom/movable/AM)//Prevents mouse from pulling things + to_chat(src, "You are too small to pull anything.") + return + +/mob/living/simple_animal/mouse/Crossed(AM as mob|obj, oldloc) + if(ishuman(AM)) + if(!stat) + var/mob/M = AM + to_chat(M, "[bicon(src)] Squeek!") + ..() + +/mob/living/simple_animal/mouse/proc/toast() + add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY) + desc = "It's toast." + death() + +/mob/living/simple_animal/mouse/death(gibbed) + // Only execute the below if we successfully died + playsound(src, squeak_sound, 40, 1) + . = ..(gibbed) + if(!.) + return FALSE + layer = MOB_LAYER + if(client) + client.time_died_as_mouse = world.time + +/mob/living/simple_animal/mouse/emote(act, m_type = 1, message = null, force) + if(stat != CONSCIOUS) + return + + var/on_CD = 0 + act = lowertext(act) + switch(act) + if("squeak") //Mouse time + on_CD = handle_emote_CD() + else + on_CD = 0 + + if(!force && on_CD == 1) + return + + switch(act) + if("squeak") + message = "\The [src] [pick(emote_hear)]!" + m_type = 2 //audible + playsound(src, squeak_sound, 40, 1) + if("help") + to_chat(src, "scream, squeak") + + ..() + +/* + * Mouse types + */ + +/mob/living/simple_animal/mouse/white + mouse_color = "white" + icon_state = "mouse_white" + +/mob/living/simple_animal/mouse/gray + mouse_color = "gray" + icon_state = "mouse_gray" + +/mob/living/simple_animal/mouse/brown + mouse_color = "brown" + icon_state = "mouse_brown" + +//TOM IS ALIVE! SQUEEEEEEEE~K :) +/mob/living/simple_animal/mouse/brown/Tom + name = "Tom" + desc = "Jerry the cat is not amused." + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "splats" + unique_pet = TRUE + gold_core_spawnable = NO_SPAWN + + +/mob/living/simple_animal/mouse/blobinfected + maxHealth = 100 + health = 100 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + gold_core_spawnable = NO_SPAWN + var/cycles_alive = 0 + var/cycles_limit = 60 + var/has_burst = FALSE + +/mob/living/simple_animal/mouse/blobinfected/Life() + cycles_alive++ + var/timeleft = (cycles_limit - cycles_alive) * 2 + if(ismob(loc)) // if someone ate it, burst immediately + burst(FALSE) + else if(timeleft < 1) // if timer expired, burst. + burst(FALSE) + else if(cycles_alive % 2 == 0) // give the mouse/player a countdown reminder every 2 cycles + to_chat(src, "[timeleft] seconds until you burst, and become a blob...") + return ..() + +/mob/living/simple_animal/mouse/blobinfected/death(gibbed) + burst(gibbed) + return ..(gibbed) + +/mob/living/simple_animal/mouse/blobinfected/proc/burst(gibbed) + if(has_burst) + return FALSE + var/turf/T = get_turf(src) + if(!is_station_level(T.z) || isspaceturf(T)) + to_chat(src, "You feel ready to burst, but this isn't an appropriate place! You must return to the station!") + return FALSE + has_burst = TRUE + var/datum/mind/blobmind = mind + var/client/C = client + if(istype(blobmind) && istype(C)) + blobmind.special_role = SPECIAL_ROLE_BLOB + var/obj/structure/blob/core/core = new(T, 200, C, 3) + core.lateblobtimer() + else + new /obj/structure/blob/core(T) // Ghosts will be prompted to control it. + if(ismob(loc)) // in case some taj/etc ate the mouse. + var/mob/M = loc + M.gib() + if(!gibbed) + gib() + +/mob/living/simple_animal/mouse/blobinfected/get_scooped(mob/living/carbon/grabber) + to_chat(grabber, "You try to pick up [src], but they slip out of your grasp!") + to_chat(src, "[src] tries to pick you up, but you wriggle free of their grasp!") + +/mob/living/simple_animal/mouse/fluff/clockwork + name = "Chip" + real_name = "Chip" + mouse_color = "clockwork" + icon_state = "mouse_clockwork" + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stamps on" + gold_core_spawnable = NO_SPAWN + can_collar = 0 + butcher_results = list(/obj/item/stack/sheet/metal = 1) diff --git a/code/modules/mob/living/simple_animal/friendly/penguin.dm b/code/modules/mob/living/simple_animal/friendly/penguin.dm index d2dba6da1c9..dc00e252661 100644 --- a/code/modules/mob/living/simple_animal/friendly/penguin.dm +++ b/code/modules/mob/living/simple_animal/friendly/penguin.dm @@ -64,4 +64,4 @@ icon_dead = "penguin_baby_dead" density = FALSE pass_flags = PASSMOB - mob_size = MOB_SIZE_SMALL \ No newline at end of file + mob_size = MOB_SIZE_SMALL diff --git a/code/modules/mob/living/simple_animal/friendly/pet.dm b/code/modules/mob/living/simple_animal/friendly/pet.dm index 8a9899b7fa9..ac4f3367002 100644 --- a/code/modules/mob/living/simple_animal/friendly/pet.dm +++ b/code/modules/mob/living/simple_animal/friendly/pet.dm @@ -13,4 +13,4 @@ setDir(i) sleep(1) else - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/mob/living/simple_animal/friendly/sloth.dm b/code/modules/mob/living/simple_animal/friendly/sloth.dm index ddf311d62d4..d5b47c189ff 100644 --- a/code/modules/mob/living/simple_animal/friendly/sloth.dm +++ b/code/modules/mob/living/simple_animal/friendly/sloth.dm @@ -29,4 +29,4 @@ name = "Paperwork" desc = "Cargo's pet sloth. About as useful as the rest of the techs." unique_pet = TRUE - gold_core_spawnable = NO_SPAWN \ No newline at end of file + gold_core_spawnable = NO_SPAWN diff --git a/code/modules/mob/living/simple_animal/friendly/snake.dm b/code/modules/mob/living/simple_animal/friendly/snake.dm index 1cd85c1b622..b4a15ba27b4 100644 --- a/code/modules/mob/living/simple_animal/friendly/snake.dm +++ b/code/modules/mob/living/simple_animal/friendly/snake.dm @@ -58,4 +58,4 @@ QDEL_NULL(target) adjustHealth(-2) else - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm index b541d55096a..5a369a043d7 100644 --- a/code/modules/mob/living/simple_animal/hostile/alien.dm +++ b/code/modules/mob/living/simple_animal/hostile/alien.dm @@ -1,164 +1,164 @@ -/mob/living/simple_animal/hostile/alien - name = "alien hunter" - desc = "Hiss!" - icon = 'icons/mob/alien.dmi' - icon_state = "alienh_running" - icon_living = "alienh_running" - icon_dead = "alienh_dead" - icon_gib = "syndicate_gib" - gender = FEMALE - response_help = "pokes" - response_disarm = "shoves" - response_harm = "hits" - speed = 0 - butcher_results = list(/obj/item/reagent_containers/food/snacks/xenomeat = 3, /obj/item/stack/sheet/animalhide/xeno = 1) - maxHealth = 125 - health = 125 - harm_intent_damage = 5 - obj_damage = 60 - melee_damage_lower = 25 - melee_damage_upper = 25 - attacktext = "slashes" - speak_emote = list("hisses") - a_intent = INTENT_HARM - attack_sound = 'sound/weapons/bladeslice.ogg' - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 15 - heat_damage_per_tick = 20 - faction = list("alien") - status_flags = CANPUSH - minbodytemp = 0 - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - gold_core_spawnable = HOSTILE_SPAWN - death_sound = 'sound/voice/hiss6.ogg' - deathmessage = "lets out a waning guttural screech, green blood bubbling from its maw..." - - -/mob/living/simple_animal/hostile/alien/drone - name = "alien drone" - icon_state = "aliend_running" - icon_living = "aliend_running" - icon_dead = "aliend_dead" - melee_damage_lower = 15 - melee_damage_upper = 15 - var/plant_cooldown = 30 - var/plants_off = 0 - -/mob/living/simple_animal/hostile/alien/drone/handle_automated_action() - if(!..()) //AIStatus is off - return - plant_cooldown-- - if(AIStatus == AI_IDLE) - if(!plants_off && prob(10) && plant_cooldown<=0) - plant_cooldown = initial(plant_cooldown) - SpreadPlants() - -/mob/living/simple_animal/hostile/alien/sentinel - name = "alien sentinel" - icon_state = "aliens_running" - icon_living = "aliens_running" - icon_dead = "aliens_dead" - health = 150 - maxHealth = 150 - melee_damage_lower = 15 - melee_damage_upper = 15 - ranged = 1 - retreat_distance = 5 - minimum_distance = 5 - projectiletype = /obj/item/projectile/neurotox - projectilesound = 'sound/weapons/pierce.ogg' - - -/mob/living/simple_animal/hostile/alien/queen - name = "alien queen" - icon_state = "alienq_running" - icon_living = "alienq_running" - icon_dead = "alienq_d" - health = 250 - maxHealth = 250 - melee_damage_lower = 15 - melee_damage_upper = 15 - ranged = 1 - retreat_distance = 5 - minimum_distance = 5 - move_to_delay = 4 - butcher_results = list(/obj/item/reagent_containers/food/snacks/xenomeat = 4, /obj/item/stack/sheet/animalhide/xeno = 1) - projectiletype = /obj/item/projectile/neurotox - projectilesound = 'sound/weapons/pierce.ogg' - status_flags = 0 - var/sterile = 1 - var/plants_off = 0 - var/egg_cooldown = 30 - var/plant_cooldown = 30 - -/mob/living/simple_animal/hostile/alien/queen/handle_automated_action() - if(!..()) - return - egg_cooldown-- - plant_cooldown-- - if(AIStatus == AI_IDLE) - if(!plants_off && prob(10) && plant_cooldown<=0) - plant_cooldown = initial(plant_cooldown) - SpreadPlants() - if(!sterile && prob(10) && egg_cooldown<=0) - egg_cooldown = initial(egg_cooldown) - LayEggs() - -/mob/living/simple_animal/hostile/alien/proc/SpreadPlants() - if(!isturf(loc) || isspaceturf(loc)) - return - if(locate(/obj/structure/alien/weeds/node) in get_turf(src)) - return - visible_message("[src] has planted some alien weeds!") - new /obj/structure/alien/weeds/node(loc) - -/mob/living/simple_animal/hostile/alien/proc/LayEggs() - if(!isturf(loc) || isspaceturf(loc)) - return - if(locate(/obj/structure/alien/egg) in get_turf(src)) - return - visible_message("[src] has laid an egg!") - new /obj/structure/alien/egg(loc) - -/mob/living/simple_animal/hostile/alien/queen/large - name = "alien empress" - icon = 'icons/mob/alienlarge.dmi' - icon_state = "queen_s" - icon_living = "queen_s" - icon_dead = "queen_dead" - move_to_delay = 4 - maxHealth = 400 - health = 400 - butcher_results = list(/obj/item/reagent_containers/food/snacks/xenomeat = 10, /obj/item/stack/sheet/animalhide/xeno = 2) - mob_size = MOB_SIZE_LARGE - gold_core_spawnable = NO_SPAWN - -/obj/item/projectile/neurotox - name = "neurotoxin" - damage = 30 - icon_state = "toxin" - -/mob/living/simple_animal/hostile/alien/maid - name = "lusty xenomorph maid" - melee_damage_lower = 0 - melee_damage_upper = 0 - a_intent = INTENT_HELP - friendly = "caresses" - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - gold_core_spawnable = HOSTILE_SPAWN - icon_state = "maid" - icon_living = "maid" - icon_dead = "maid_dead" - -/mob/living/simple_animal/hostile/alien/maid/AttackingTarget() - if(ismovableatom(target)) - if(istype(target, /obj/effect/decal/cleanable)) - visible_message("\The [src] cleans up \the [target].") - qdel(target) - return TRUE - var/atom/movable/M = target - M.clean_blood() - visible_message("\The [src] polishes \the [target].") - return TRUE +/mob/living/simple_animal/hostile/alien + name = "alien hunter" + desc = "Hiss!" + icon = 'icons/mob/alien.dmi' + icon_state = "alienh_running" + icon_living = "alienh_running" + icon_dead = "alienh_dead" + icon_gib = "syndicate_gib" + gender = FEMALE + response_help = "pokes" + response_disarm = "shoves" + response_harm = "hits" + speed = 0 + butcher_results = list(/obj/item/reagent_containers/food/snacks/xenomeat = 3, /obj/item/stack/sheet/animalhide/xeno = 1) + maxHealth = 125 + health = 125 + harm_intent_damage = 5 + obj_damage = 60 + melee_damage_lower = 25 + melee_damage_upper = 25 + attacktext = "slashes" + speak_emote = list("hisses") + a_intent = INTENT_HARM + attack_sound = 'sound/weapons/bladeslice.ogg' + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + unsuitable_atmos_damage = 15 + heat_damage_per_tick = 20 + faction = list("alien") + status_flags = CANPUSH + minbodytemp = 0 + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + gold_core_spawnable = HOSTILE_SPAWN + death_sound = 'sound/voice/hiss6.ogg' + deathmessage = "lets out a waning guttural screech, green blood bubbling from its maw..." + + +/mob/living/simple_animal/hostile/alien/drone + name = "alien drone" + icon_state = "aliend_running" + icon_living = "aliend_running" + icon_dead = "aliend_dead" + melee_damage_lower = 15 + melee_damage_upper = 15 + var/plant_cooldown = 30 + var/plants_off = 0 + +/mob/living/simple_animal/hostile/alien/drone/handle_automated_action() + if(!..()) //AIStatus is off + return + plant_cooldown-- + if(AIStatus == AI_IDLE) + if(!plants_off && prob(10) && plant_cooldown<=0) + plant_cooldown = initial(plant_cooldown) + SpreadPlants() + +/mob/living/simple_animal/hostile/alien/sentinel + name = "alien sentinel" + icon_state = "aliens_running" + icon_living = "aliens_running" + icon_dead = "aliens_dead" + health = 150 + maxHealth = 150 + melee_damage_lower = 15 + melee_damage_upper = 15 + ranged = 1 + retreat_distance = 5 + minimum_distance = 5 + projectiletype = /obj/item/projectile/neurotox + projectilesound = 'sound/weapons/pierce.ogg' + + +/mob/living/simple_animal/hostile/alien/queen + name = "alien queen" + icon_state = "alienq_running" + icon_living = "alienq_running" + icon_dead = "alienq_d" + health = 250 + maxHealth = 250 + melee_damage_lower = 15 + melee_damage_upper = 15 + ranged = 1 + retreat_distance = 5 + minimum_distance = 5 + move_to_delay = 4 + butcher_results = list(/obj/item/reagent_containers/food/snacks/xenomeat = 4, /obj/item/stack/sheet/animalhide/xeno = 1) + projectiletype = /obj/item/projectile/neurotox + projectilesound = 'sound/weapons/pierce.ogg' + status_flags = 0 + var/sterile = 1 + var/plants_off = 0 + var/egg_cooldown = 30 + var/plant_cooldown = 30 + +/mob/living/simple_animal/hostile/alien/queen/handle_automated_action() + if(!..()) + return + egg_cooldown-- + plant_cooldown-- + if(AIStatus == AI_IDLE) + if(!plants_off && prob(10) && plant_cooldown<=0) + plant_cooldown = initial(plant_cooldown) + SpreadPlants() + if(!sterile && prob(10) && egg_cooldown<=0) + egg_cooldown = initial(egg_cooldown) + LayEggs() + +/mob/living/simple_animal/hostile/alien/proc/SpreadPlants() + if(!isturf(loc) || isspaceturf(loc)) + return + if(locate(/obj/structure/alien/weeds/node) in get_turf(src)) + return + visible_message("[src] has planted some alien weeds!") + new /obj/structure/alien/weeds/node(loc) + +/mob/living/simple_animal/hostile/alien/proc/LayEggs() + if(!isturf(loc) || isspaceturf(loc)) + return + if(locate(/obj/structure/alien/egg) in get_turf(src)) + return + visible_message("[src] has laid an egg!") + new /obj/structure/alien/egg(loc) + +/mob/living/simple_animal/hostile/alien/queen/large + name = "alien empress" + icon = 'icons/mob/alienlarge.dmi' + icon_state = "queen_s" + icon_living = "queen_s" + icon_dead = "queen_dead" + move_to_delay = 4 + maxHealth = 400 + health = 400 + butcher_results = list(/obj/item/reagent_containers/food/snacks/xenomeat = 10, /obj/item/stack/sheet/animalhide/xeno = 2) + mob_size = MOB_SIZE_LARGE + gold_core_spawnable = NO_SPAWN + +/obj/item/projectile/neurotox + name = "neurotoxin" + damage = 30 + icon_state = "toxin" + +/mob/living/simple_animal/hostile/alien/maid + name = "lusty xenomorph maid" + melee_damage_lower = 0 + melee_damage_upper = 0 + a_intent = INTENT_HELP + friendly = "caresses" + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + gold_core_spawnable = HOSTILE_SPAWN + icon_state = "maid" + icon_living = "maid" + icon_dead = "maid_dead" + +/mob/living/simple_animal/hostile/alien/maid/AttackingTarget() + if(ismovableatom(target)) + if(istype(target, /obj/effect/decal/cleanable)) + visible_message("\The [src] cleans up \the [target].") + qdel(target) + return TRUE + var/atom/movable/M = target + M.clean_blood() + visible_message("\The [src] polishes \the [target].") + return TRUE diff --git a/code/modules/mob/living/simple_animal/hostile/bat.dm b/code/modules/mob/living/simple_animal/hostile/bat.dm index ae851fe6dcb..be04fc217e9 100644 --- a/code/modules/mob/living/simple_animal/hostile/bat.dm +++ b/code/modules/mob/living/simple_animal/hostile/bat.dm @@ -73,4 +73,4 @@ pass_flags = PASSTABLE universal_speak = 1 universal_understand = 1 - gold_core_spawnable = NO_SPAWN //badmin only \ No newline at end of file + gold_core_spawnable = NO_SPAWN //badmin only diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm index cfe673a78f1..2530fb4b389 100644 --- a/code/modules/mob/living/simple_animal/hostile/bear.dm +++ b/code/modules/mob/living/simple_animal/hostile/bear.dm @@ -1,56 +1,56 @@ -//Space bears! -/mob/living/simple_animal/hostile/bear - name = "space bear" - desc = "You don't need to be faster than a space bear, you just need to outrun your crewmates." - icon_state = "bear" - icon_living = "bear" - icon_dead = "bear_dead" - icon_gib = "bear_gib" - speak = list("RAWR!","Rawr!","GRR!","Growl!") - speak_emote = list("growls", "roars") - emote_hear = list("rawrs","grumbles","grawls") - emote_see = list("stares ferociously", "stomps") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/bearmeat = 5, /obj/item/clothing/head/bearpelt = 1) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "hits" - stop_automated_movement_when_pulled = 0 - maxHealth = 60 - health = 60 - obj_damage = 60 - melee_damage_lower = 20 - melee_damage_upper = 30 - attacktext = "mauls" - friendly = "bear hugs" - attack_sound = 'sound/weapons/genhit3.ogg' - - //Space bears aren't affected by atmos. - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - - faction = list("russian") - gold_core_spawnable = HOSTILE_SPAWN - -//SPACE BEARS! SQUEEEEEEEE~ OW! FUCK! IT BIT MY HAND OFF!! -/mob/living/simple_animal/hostile/bear/Hudson - name = "Hudson" - desc = "Feared outlaw, this guy is one bad news bear." //I'm sorry... - -/mob/living/simple_animal/hostile/bear/Hudson/New() - ..() - var/unbearable_pun = pick("He's unbearably cute.", "It looks like he is a bearer of bad news.", "Sadly, he is bearly able to comprehend puns.") - desc = "That's Hudson. " + unbearable_pun// I am not sorry for this. - -/mob/living/simple_animal/hostile/bear/Move() - ..() - if(stat != DEAD) - if(loc && istype(loc,/turf/space)) - icon_state = "bear" - else - icon_state = "bearfloor" - -/mob/living/simple_animal/hostile/bear/Process_Spacemove(var/movement_dir = 0) - return 1 //No drifting in space for space bears! +//Space bears! +/mob/living/simple_animal/hostile/bear + name = "space bear" + desc = "You don't need to be faster than a space bear, you just need to outrun your crewmates." + icon_state = "bear" + icon_living = "bear" + icon_dead = "bear_dead" + icon_gib = "bear_gib" + speak = list("RAWR!","Rawr!","GRR!","Growl!") + speak_emote = list("growls", "roars") + emote_hear = list("rawrs","grumbles","grawls") + emote_see = list("stares ferociously", "stomps") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/bearmeat = 5, /obj/item/clothing/head/bearpelt = 1) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "hits" + stop_automated_movement_when_pulled = 0 + maxHealth = 60 + health = 60 + obj_damage = 60 + melee_damage_lower = 20 + melee_damage_upper = 30 + attacktext = "mauls" + friendly = "bear hugs" + attack_sound = 'sound/weapons/genhit3.ogg' + + //Space bears aren't affected by atmos. + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + + faction = list("russian") + gold_core_spawnable = HOSTILE_SPAWN + +//SPACE BEARS! SQUEEEEEEEE~ OW! FUCK! IT BIT MY HAND OFF!! +/mob/living/simple_animal/hostile/bear/Hudson + name = "Hudson" + desc = "Feared outlaw, this guy is one bad news bear." //I'm sorry... + +/mob/living/simple_animal/hostile/bear/Hudson/New() + ..() + var/unbearable_pun = pick("He's unbearably cute.", "It looks like he is a bearer of bad news.", "Sadly, he is bearly able to comprehend puns.") + desc = "That's Hudson. " + unbearable_pun// I am not sorry for this. + +/mob/living/simple_animal/hostile/bear/Move() + ..() + if(stat != DEAD) + if(loc && istype(loc,/turf/space)) + icon_state = "bear" + else + icon_state = "bearfloor" + +/mob/living/simple_animal/hostile/bear/Process_Spacemove(var/movement_dir = 0) + return 1 //No drifting in space for space bears! diff --git a/code/modules/mob/living/simple_animal/hostile/bees.dm b/code/modules/mob/living/simple_animal/hostile/bees.dm index 046bc162d7a..c1e7548d9a3 100644 --- a/code/modules/mob/living/simple_animal/hostile/bees.dm +++ b/code/modules/mob/living/simple_animal/hostile/bees.dm @@ -352,4 +352,4 @@ if(. && target && isliving(target)) var/mob/living/L = target if(L.stat) - LoseTarget() \ No newline at end of file + LoseTarget() diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm index eb2bbddc437..a0cb935ba25 100644 --- a/code/modules/mob/living/simple_animal/hostile/carp.dm +++ b/code/modules/mob/living/simple_animal/hostile/carp.dm @@ -1,171 +1,171 @@ -#define REGENERATION_DELAY 60 // After taking damage, how long it takes for automatic regeneration to begin for megacarps (ty robustin!) - -/mob/living/simple_animal/hostile/carp - name = "space carp" - desc = "A ferocious, fang-bearing creature that resembles a fish." - icon = 'icons/mob/carp.dmi' - icon_state = "base" - icon_living = "base" - icon_dead = "base_dead" - icon_gib = "carp_gib" - speak_chance = 0 - turns_per_move = 5 - butcher_results = list(/obj/item/reagent_containers/food/snacks/carpmeat = 2) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "hits" - emote_taunt = list("gnashes") - taunt_chance = 30 - speed = 0 - maxHealth = 25 - health = 25 - - harm_intent_damage = 8 - obj_damage = 50 - melee_damage_lower = 15 - melee_damage_upper = 15 - attacktext = "bites" - attack_sound = 'sound/weapons/bite.ogg' - speak_emote = list("gnashes") - - //Space carp aren't affected by atmos. - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxbodytemp = 1500 - faction = list("carp") - flying = TRUE - pressure_resistance = 200 - gold_core_spawnable = HOSTILE_SPAWN - - var/random_color = TRUE //if the carp uses random coloring - var/rarechance = 1 //chance for rare color variant - - var/static/list/carp_colors = list(\ - "lightpurple" = "#c3b9f1", \ - "lightpink" = "#da77a8", \ - "green" = "#70ff25", \ - "grape" = "#df0afb", \ - "swamp" = "#e5e75a", \ - "turquoise" = "#04e1ed", \ - "brown" = "#ca805a", \ - "teal" = "#20e28e", \ - "lightblue" = "#4d88cc", \ - "rusty" = "#dd5f34", \ - "beige" = "#bbaeaf", \ - "yellow" = "#f3ca4a", \ - "blue" = "#09bae1", \ - "palegreen" = "#7ef099", \ - ) - var/static/list/carp_colors_rare = list(\ - "silver" = "#fdfbf3", \ - ) - -/mob/living/simple_animal/hostile/carp/Initialize(mapload) - . = ..() - carp_randomify(rarechance) - update_icons() - -/mob/living/simple_animal/hostile/carp/proc/carp_randomify(rarechance) - if(random_color) - var/our_color - if(prob(rarechance)) - our_color = pick(carp_colors_rare) - add_atom_colour(carp_colors_rare[our_color], FIXED_COLOUR_PRIORITY) - else - our_color = pick(carp_colors) - add_atom_colour(carp_colors[our_color], FIXED_COLOUR_PRIORITY) - add_carp_overlay() - -/mob/living/simple_animal/hostile/carp/proc/add_carp_overlay() - if(!random_color) - return - cut_overlays() - var/mutable_appearance/base_overlay = mutable_appearance(icon, "base_mouth") - base_overlay.appearance_flags = RESET_COLOR - add_overlay(base_overlay) - -/mob/living/simple_animal/hostile/carp/proc/add_dead_carp_overlay() - if(!random_color) - return - cut_overlays() - var/mutable_appearance/base_dead_overlay = mutable_appearance(icon, "base_dead_mouth") - base_dead_overlay.appearance_flags = RESET_COLOR - add_overlay(base_dead_overlay) - -/mob/living/simple_animal/hostile/carp/Process_Spacemove(movement_dir = 0) - return TRUE //No drifting in space for space carp! //original comments do not steal - -/mob/living/simple_animal/hostile/carp/AttackingTarget() - . = ..() - if(. && ishuman(target)) - var/mob/living/carbon/human/H = target - H.adjustStaminaLoss(8) - -/mob/living/simple_animal/hostile/carp/death(gibbed) - . = ..() - cut_overlays() - if(!random_color || gibbed) - return - add_dead_carp_overlay() - -/mob/living/simple_animal/hostile/carp/revive() - ..() - regenerate_icons() - -/mob/living/simple_animal/hostile/carp/regenerate_icons() - cut_overlays() - if(!random_color) - return - if(stat != DEAD) - add_carp_overlay() - else - add_dead_carp_overlay() - ..() - -/mob/living/simple_animal/hostile/carp/holocarp - icon_state = "holocarp" - icon_living = "holocarp" - maxbodytemp = INFINITY - gold_core_spawnable = NO_SPAWN - del_on_death = 1 - random_color = FALSE - -/mob/living/simple_animal/hostile/carp/megacarp - icon = 'icons/mob/alienqueen.dmi' - name = "Mega Space Carp" - desc = "A ferocious, fang bearing creature that resembles a shark. This one seems especially ticked off." - icon_state = "megacarp" - icon_living = "megacarp" - icon_dead = "megacarp_dead" - icon_gib = "megacarp_gib" - maxHealth = 20 - health = 20 - pixel_x = -16 - mob_size = MOB_SIZE_LARGE - random_color = FALSE - - obj_damage = 80 - melee_damage_lower = 20 - melee_damage_upper = 20 - - var/regen_cooldown = 0 - -/mob/living/simple_animal/hostile/carp/megacarp/Initialize() - . = ..() - name = "[pick(GLOB.megacarp_first_names)] [pick(GLOB.megacarp_last_names)]" - melee_damage_lower += rand(2, 10) - melee_damage_upper += rand(10, 20) - maxHealth += rand(30, 60) - move_to_delay = rand(3, 7) - -/mob/living/simple_animal/hostile/carp/megacarp/adjustHealth(amount, updating_health = TRUE) - . = ..() - if(.) - regen_cooldown = world.time + REGENERATION_DELAY - -/mob/living/simple_animal/hostile/carp/megacarp/Life() - ..() - if(regen_cooldown < world.time) - heal_overall_damage(4) - -#undef REGENERATION_DELAY \ No newline at end of file +#define REGENERATION_DELAY 60 // After taking damage, how long it takes for automatic regeneration to begin for megacarps (ty robustin!) + +/mob/living/simple_animal/hostile/carp + name = "space carp" + desc = "A ferocious, fang-bearing creature that resembles a fish." + icon = 'icons/mob/carp.dmi' + icon_state = "base" + icon_living = "base" + icon_dead = "base_dead" + icon_gib = "carp_gib" + speak_chance = 0 + turns_per_move = 5 + butcher_results = list(/obj/item/reagent_containers/food/snacks/carpmeat = 2) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "hits" + emote_taunt = list("gnashes") + taunt_chance = 30 + speed = 0 + maxHealth = 25 + health = 25 + + harm_intent_damage = 8 + obj_damage = 50 + melee_damage_lower = 15 + melee_damage_upper = 15 + attacktext = "bites" + attack_sound = 'sound/weapons/bite.ogg' + speak_emote = list("gnashes") + + //Space carp aren't affected by atmos. + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + maxbodytemp = 1500 + faction = list("carp") + flying = TRUE + pressure_resistance = 200 + gold_core_spawnable = HOSTILE_SPAWN + + var/random_color = TRUE //if the carp uses random coloring + var/rarechance = 1 //chance for rare color variant + + var/static/list/carp_colors = list(\ + "lightpurple" = "#c3b9f1", \ + "lightpink" = "#da77a8", \ + "green" = "#70ff25", \ + "grape" = "#df0afb", \ + "swamp" = "#e5e75a", \ + "turquoise" = "#04e1ed", \ + "brown" = "#ca805a", \ + "teal" = "#20e28e", \ + "lightblue" = "#4d88cc", \ + "rusty" = "#dd5f34", \ + "beige" = "#bbaeaf", \ + "yellow" = "#f3ca4a", \ + "blue" = "#09bae1", \ + "palegreen" = "#7ef099", \ + ) + var/static/list/carp_colors_rare = list(\ + "silver" = "#fdfbf3", \ + ) + +/mob/living/simple_animal/hostile/carp/Initialize(mapload) + . = ..() + carp_randomify(rarechance) + update_icons() + +/mob/living/simple_animal/hostile/carp/proc/carp_randomify(rarechance) + if(random_color) + var/our_color + if(prob(rarechance)) + our_color = pick(carp_colors_rare) + add_atom_colour(carp_colors_rare[our_color], FIXED_COLOUR_PRIORITY) + else + our_color = pick(carp_colors) + add_atom_colour(carp_colors[our_color], FIXED_COLOUR_PRIORITY) + add_carp_overlay() + +/mob/living/simple_animal/hostile/carp/proc/add_carp_overlay() + if(!random_color) + return + cut_overlays() + var/mutable_appearance/base_overlay = mutable_appearance(icon, "base_mouth") + base_overlay.appearance_flags = RESET_COLOR + add_overlay(base_overlay) + +/mob/living/simple_animal/hostile/carp/proc/add_dead_carp_overlay() + if(!random_color) + return + cut_overlays() + var/mutable_appearance/base_dead_overlay = mutable_appearance(icon, "base_dead_mouth") + base_dead_overlay.appearance_flags = RESET_COLOR + add_overlay(base_dead_overlay) + +/mob/living/simple_animal/hostile/carp/Process_Spacemove(movement_dir = 0) + return TRUE //No drifting in space for space carp! //original comments do not steal + +/mob/living/simple_animal/hostile/carp/AttackingTarget() + . = ..() + if(. && ishuman(target)) + var/mob/living/carbon/human/H = target + H.adjustStaminaLoss(8) + +/mob/living/simple_animal/hostile/carp/death(gibbed) + . = ..() + cut_overlays() + if(!random_color || gibbed) + return + add_dead_carp_overlay() + +/mob/living/simple_animal/hostile/carp/revive() + ..() + regenerate_icons() + +/mob/living/simple_animal/hostile/carp/regenerate_icons() + cut_overlays() + if(!random_color) + return + if(stat != DEAD) + add_carp_overlay() + else + add_dead_carp_overlay() + ..() + +/mob/living/simple_animal/hostile/carp/holocarp + icon_state = "holocarp" + icon_living = "holocarp" + maxbodytemp = INFINITY + gold_core_spawnable = NO_SPAWN + del_on_death = 1 + random_color = FALSE + +/mob/living/simple_animal/hostile/carp/megacarp + icon = 'icons/mob/alienqueen.dmi' + name = "Mega Space Carp" + desc = "A ferocious, fang bearing creature that resembles a shark. This one seems especially ticked off." + icon_state = "megacarp" + icon_living = "megacarp" + icon_dead = "megacarp_dead" + icon_gib = "megacarp_gib" + maxHealth = 20 + health = 20 + pixel_x = -16 + mob_size = MOB_SIZE_LARGE + random_color = FALSE + + obj_damage = 80 + melee_damage_lower = 20 + melee_damage_upper = 20 + + var/regen_cooldown = 0 + +/mob/living/simple_animal/hostile/carp/megacarp/Initialize() + . = ..() + name = "[pick(GLOB.megacarp_first_names)] [pick(GLOB.megacarp_last_names)]" + melee_damage_lower += rand(2, 10) + melee_damage_upper += rand(10, 20) + maxHealth += rand(30, 60) + move_to_delay = rand(3, 7) + +/mob/living/simple_animal/hostile/carp/megacarp/adjustHealth(amount, updating_health = TRUE) + . = ..() + if(.) + regen_cooldown = world.time + REGENERATION_DELAY + +/mob/living/simple_animal/hostile/carp/megacarp/Life() + ..() + if(regen_cooldown < world.time) + heal_overall_damage(4) + +#undef REGENERATION_DELAY diff --git a/code/modules/mob/living/simple_animal/hostile/creature.dm b/code/modules/mob/living/simple_animal/hostile/creature.dm index 225cbfea5b1..3934a9b1d2f 100644 --- a/code/modules/mob/living/simple_animal/hostile/creature.dm +++ b/code/modules/mob/living/simple_animal/hostile/creature.dm @@ -1,17 +1,17 @@ -/mob/living/simple_animal/hostile/creature - name = "creature" - desc = "A sanity-destroying otherthing." - speak_emote = list("gibbers") - icon_state = "otherthing" - icon_living = "otherthing" - icon_dead = "otherthing-dead" - health = 80 - maxHealth = 80 - obj_damage = 100 - melee_damage_lower = 25 - melee_damage_upper = 50 - attacktext = "chomps" - attack_sound = 'sound/weapons/bite.ogg' - faction = list("creature") - gold_core_spawnable = HOSTILE_SPAWN - +/mob/living/simple_animal/hostile/creature + name = "creature" + desc = "A sanity-destroying otherthing." + speak_emote = list("gibbers") + icon_state = "otherthing" + icon_living = "otherthing" + icon_dead = "otherthing-dead" + health = 80 + maxHealth = 80 + obj_damage = 100 + melee_damage_lower = 25 + melee_damage_upper = 50 + attacktext = "chomps" + attack_sound = 'sound/weapons/bite.ogg' + faction = list("creature") + gold_core_spawnable = HOSTILE_SPAWN + diff --git a/code/modules/mob/living/simple_animal/hostile/deathsquid.dm b/code/modules/mob/living/simple_animal/hostile/deathsquid.dm index 14b66017d41..69f5257d4dd 100644 --- a/code/modules/mob/living/simple_animal/hostile/deathsquid.dm +++ b/code/modules/mob/living/simple_animal/hostile/deathsquid.dm @@ -48,4 +48,4 @@ armour_penetration = 5 melee_damage_lower = 10 melee_damage_upper = 20 - environment_smash = 2 \ No newline at end of file + environment_smash = 2 diff --git a/code/modules/mob/living/simple_animal/hostile/faithless.dm b/code/modules/mob/living/simple_animal/hostile/faithless.dm index 5065ce63545..0ed560f24e9 100644 --- a/code/modules/mob/living/simple_animal/hostile/faithless.dm +++ b/code/modules/mob/living/simple_animal/hostile/faithless.dm @@ -1,41 +1,41 @@ -/mob/living/simple_animal/hostile/faithless - name = "Faithless" - desc = "The Wish Granter's faith in humanity, incarnate" - icon_state = "faithless" - icon_living = "faithless" - icon_dead = "faithless_dead" - speak_chance = 0 - turns_per_move = 5 - response_help = "passes through the" - response_disarm = "shoves" - response_harm = "hits the" - speed = 0 - maxHealth = 80 - health = 80 - obj_damage = 50 - harm_intent_damage = 10 - melee_damage_lower = 15 - melee_damage_upper = 15 - attacktext = "grips" - attack_sound = 'sound/hallucinations/growl1.ogg' - speak_emote = list("growls") - emote_taunt = list("wails") - taunt_chance = 25 - - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - - faction = list("faithless") - gold_core_spawnable = HOSTILE_SPAWN - -/mob/living/simple_animal/hostile/faithless/Process_Spacemove(var/movement_dir = 0) - return 1 - -/mob/living/simple_animal/hostile/faithless/AttackingTarget() - . = ..() - if(. && iscarbon(target)) - var/mob/living/carbon/C = target - if(prob(12)) - C.Weaken(3) - C.visible_message("\The [src] knocks down \the [C]!", \ - "\The [src] knocks you down!") \ No newline at end of file +/mob/living/simple_animal/hostile/faithless + name = "Faithless" + desc = "The Wish Granter's faith in humanity, incarnate" + icon_state = "faithless" + icon_living = "faithless" + icon_dead = "faithless_dead" + speak_chance = 0 + turns_per_move = 5 + response_help = "passes through the" + response_disarm = "shoves" + response_harm = "hits the" + speed = 0 + maxHealth = 80 + health = 80 + obj_damage = 50 + harm_intent_damage = 10 + melee_damage_lower = 15 + melee_damage_upper = 15 + attacktext = "grips" + attack_sound = 'sound/hallucinations/growl1.ogg' + speak_emote = list("growls") + emote_taunt = list("wails") + taunt_chance = 25 + + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + + faction = list("faithless") + gold_core_spawnable = HOSTILE_SPAWN + +/mob/living/simple_animal/hostile/faithless/Process_Spacemove(var/movement_dir = 0) + return 1 + +/mob/living/simple_animal/hostile/faithless/AttackingTarget() + . = ..() + if(. && iscarbon(target)) + var/mob/living/carbon/C = target + if(prob(12)) + C.Weaken(3) + C.visible_message("\The [src] knocks down \the [C]!", \ + "\The [src] knocks you down!") diff --git a/code/modules/mob/living/simple_animal/hostile/feral_cat.dm b/code/modules/mob/living/simple_animal/hostile/feral_cat.dm index 78f5a3ea2b9..4067d552089 100644 --- a/code/modules/mob/living/simple_animal/hostile/feral_cat.dm +++ b/code/modules/mob/living/simple_animal/hostile/feral_cat.dm @@ -25,4 +25,4 @@ faction = list("cat") atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) unsuitable_atmos_damage = 5 - pass_flags = PASSTABLE \ No newline at end of file + pass_flags = PASSTABLE diff --git a/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm b/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm index c3939e41395..77d6807e086 100644 --- a/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm +++ b/code/modules/mob/living/simple_animal/hostile/floorcluwne.dm @@ -426,4 +426,4 @@ #undef STAGE_SPOOK #undef STAGE_TORMENT #undef STAGE_ATTACK -#undef MANIFEST_DELAY \ No newline at end of file +#undef MANIFEST_DELAY diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 0465d137007..b456db6efaf 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -1,246 +1,246 @@ - -#define SPINNING_WEB 1 -#define LAYING_EGGS 2 -#define MOVING_TO_TARGET 3 -#define SPINNING_COCOON 4 - -//basic spider mob, these generally guard nests -/mob/living/simple_animal/hostile/poison/giant_spider - name = "giant spider" - desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes." - icon_state = "guard" - var/butcher_state = 8 // Icon state for dead spider icons - icon_living = "guard" - icon_dead = "guard_dead" - speak_emote = list("chitters") - emote_hear = list("chitters") - speak_chance = 5 - turns_per_move = 5 - see_in_dark = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - butcher_results = list(/obj/item/reagent_containers/food/snacks/spidermeat = 2, /obj/item/reagent_containers/food/snacks/spiderleg = 8) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "hits" - maxHealth = 200 - health = 200 - obj_damage = 60 - melee_damage_lower = 15 - melee_damage_upper = 20 - heat_damage_per_tick = 20 //amount of damage applied if animal's body temperature is higher than maxbodytemp - cold_damage_per_tick = 20 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp - faction = list("spiders") - pass_flags = PASSTABLE - move_to_delay = 6 - attacktext = "bites" - attack_sound = 'sound/weapons/bite.ogg' - gold_core_spawnable = HOSTILE_SPAWN - var/venom_per_bite = 0 // While the /poison/ type path remains as-is for consistency reasons, we're really talking about venom, not poison. - var/busy = 0 - -/mob/living/simple_animal/hostile/poison/giant_spider/AttackingTarget() - // This is placed here, NOT on /poison, because the other subtypes of /poison/ already override AttackingTarget() completely, and as such it would do nothing but confuse people there. - . = ..() - if(. && venom_per_bite > 0 && iscarbon(target) && (!client || a_intent == INTENT_HARM)) - var/mob/living/carbon/C = target - var/inject_target = pick("chest", "head") - if(C.can_inject(null, FALSE, inject_target, FALSE)) - C.reagents.add_reagent("spidertoxin", venom_per_bite) - -//nursemaids - these create webs and eggs -/mob/living/simple_animal/hostile/poison/giant_spider/nurse - desc = "Furry and black, it makes you shudder to look at it. This one has brilliant green eyes." - icon_state = "nurse" - icon_living = "nurse" - icon_dead = "nurse_dead" - butcher_results = list(/obj/item/reagent_containers/food/snacks/spidermeat = 2, /obj/item/reagent_containers/food/snacks/spiderleg = 8, /obj/item/reagent_containers/food/snacks/spidereggs = 4) - - maxHealth = 40 - health = 40 - melee_damage_lower = 5 - melee_damage_upper = 10 - venom_per_bite = 30 - var/atom/cocoon_target - var/fed = 0 - -//hunters have the most poison and move the fastest, so they can find prey -/mob/living/simple_animal/hostile/poison/giant_spider/hunter - desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes." - icon_state = "hunter" - icon_living = "hunter" - icon_dead = "hunter_dead" - maxHealth = 120 - health = 120 - melee_damage_lower = 10 - melee_damage_upper = 20 - venom_per_bite = 10 - move_to_delay = 5 - -/mob/living/simple_animal/hostile/poison/giant_spider/handle_automated_movement() //Hacky and ugly. - . = ..() - if(AIStatus == AI_IDLE) - //1% chance to skitter madly away - if(!busy && prob(1)) - stop_automated_movement = 1 - Goto(pick(urange(20, src, 1)), move_to_delay) - spawn(50) - stop_automated_movement = 0 - walk(src,0) - return 1 - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/proc/GiveUp(C) - spawn(100) - if(busy == MOVING_TO_TARGET) - if(cocoon_target == C && get_dist(src,cocoon_target) > 1) - cocoon_target = null - busy = 0 - stop_automated_movement = 0 - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/handle_automated_movement() //Hacky and ugly. - if(..()) - var/list/can_see = view(src, 10) - if(!busy && prob(30)) //30% chance to stop wandering and do something - //first, check for potential food nearby to cocoon - for(var/mob/living/C in can_see) - if(C.stat && !istype(C, /mob/living/simple_animal/hostile/poison/giant_spider) && !C.anchored) - cocoon_target = C - busy = MOVING_TO_TARGET - Goto(C, move_to_delay) - //give up if we can't reach them after 10 seconds - GiveUp(C) - return - //second, spin a sticky spiderweb on this tile - var/obj/structure/spider/stickyweb/W = locate() in get_turf(src) - if(!W) - Web() - else - //third, lay an egg cluster there - if(fed) - LayEggs() - else - //fourthly, cocoon any nearby items so those pesky pinkskins can't use them - for(var/obj/O in can_see) - if(O.anchored) - continue - - if(isitem(O) || isstructure(O) || ismachinery(O)) - cocoon_target = O - busy = MOVING_TO_TARGET - stop_automated_movement = 1 - Goto(O, move_to_delay) - //give up if we can't reach them after 10 seconds - GiveUp(O) - - else if(busy == MOVING_TO_TARGET && cocoon_target) - if(get_dist(src, cocoon_target) <= 1) - Wrap() - - else - busy = 0 - stop_automated_movement = 0 - -/mob/living/simple_animal/hostile/poison/giant_spider/verb/Web() - set name = "Lay Web" - set category = "Spider" - set desc = "Spread a sticky web to slow down prey." - - var/T = src.loc - - if(busy != SPINNING_WEB) - busy = SPINNING_WEB - src.visible_message("\the [src] begins to secrete a sticky substance.") - stop_automated_movement = 1 - spawn(40) - if(busy == SPINNING_WEB && src.loc == T) - new /obj/structure/spider/stickyweb(T) - busy = 0 - stop_automated_movement = 0 - - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/verb/Wrap() - set name = "Wrap" - set category = "Spider" - set desc = "Wrap up prey to feast upon and objects for safe keeping." - - if(!cocoon_target) - var/list/choices = list() - for(var/mob/living/L in view(1,src)) - if(L == src) - continue - if(Adjacent(L)) - choices += L - for(var/obj/O in src.loc) - if(Adjacent(O)) - choices += O - cocoon_target = input(src,"What do you wish to cocoon?") in null|choices - - if(cocoon_target && busy != SPINNING_COCOON) - busy = SPINNING_COCOON - src.visible_message("\the [src] begins to secrete a sticky substance around \the [cocoon_target].") - stop_automated_movement = 1 - walk(src,0) - spawn(50) - if(busy == SPINNING_COCOON) - if(cocoon_target && istype(cocoon_target.loc, /turf) && get_dist(src,cocoon_target) <= 1) - var/obj/structure/spider/cocoon/C = new(cocoon_target.loc) - var/large_cocoon = 0 - C.pixel_x = cocoon_target.pixel_x - C.pixel_y = cocoon_target.pixel_y - for(var/obj/item/I in C.loc) - I.loc = C - for(var/obj/structure/S in C.loc) - if(!S.anchored) - S.loc = C - large_cocoon = 1 - for(var/obj/machinery/M in C.loc) - if(!M.anchored) - M.loc = C - large_cocoon = 1 - for(var/mob/living/L in C.loc) - if(istype(L, /mob/living/simple_animal/hostile/poison/giant_spider)) - continue - large_cocoon = 1 - L.loc = C - C.pixel_x = L.pixel_x - C.pixel_y = L.pixel_y - fed++ - visible_message("\the [src] sticks a proboscis into \the [L] and sucks a viscous substance out.") - - break - if(large_cocoon) - C.icon_state = pick("cocoon_large1","cocoon_large2","cocoon_large3") - cocoon_target = null - busy = 0 - stop_automated_movement = 0 - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/verb/LayEggs() - set name = "Lay Eggs" - set category = "Spider" - set desc = "Lay a clutch of eggs, but you must wrap a creature for feeding first." - - var/obj/structure/spider/eggcluster/E = locate() in get_turf(src) - if(E) - to_chat(src, "There is already a cluster of eggs here!") - else if(!fed) - to_chat(src, "You are too hungry to do this!") - else if(busy != LAYING_EGGS) - busy = LAYING_EGGS - src.visible_message("\the [src] begins to lay a cluster of eggs.") - stop_automated_movement = 1 - spawn(50) - if(busy == LAYING_EGGS) - E = locate() in get_turf(src) - if(!E) - var/obj/structure/spider/eggcluster/C = new /obj/structure/spider/eggcluster(src.loc) - C.faction = faction.Copy() - C.master_commander = master_commander - if(ckey) - C.player_spiders = 1 - fed-- - busy = 0 - stop_automated_movement = 0 - -#undef SPINNING_WEB -#undef LAYING_EGGS -#undef MOVING_TO_TARGET -#undef SPINNING_COCOON + +#define SPINNING_WEB 1 +#define LAYING_EGGS 2 +#define MOVING_TO_TARGET 3 +#define SPINNING_COCOON 4 + +//basic spider mob, these generally guard nests +/mob/living/simple_animal/hostile/poison/giant_spider + name = "giant spider" + desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes." + icon_state = "guard" + var/butcher_state = 8 // Icon state for dead spider icons + icon_living = "guard" + icon_dead = "guard_dead" + speak_emote = list("chitters") + emote_hear = list("chitters") + speak_chance = 5 + turns_per_move = 5 + see_in_dark = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + butcher_results = list(/obj/item/reagent_containers/food/snacks/spidermeat = 2, /obj/item/reagent_containers/food/snacks/spiderleg = 8) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "hits" + maxHealth = 200 + health = 200 + obj_damage = 60 + melee_damage_lower = 15 + melee_damage_upper = 20 + heat_damage_per_tick = 20 //amount of damage applied if animal's body temperature is higher than maxbodytemp + cold_damage_per_tick = 20 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp + faction = list("spiders") + pass_flags = PASSTABLE + move_to_delay = 6 + attacktext = "bites" + attack_sound = 'sound/weapons/bite.ogg' + gold_core_spawnable = HOSTILE_SPAWN + var/venom_per_bite = 0 // While the /poison/ type path remains as-is for consistency reasons, we're really talking about venom, not poison. + var/busy = 0 + +/mob/living/simple_animal/hostile/poison/giant_spider/AttackingTarget() + // This is placed here, NOT on /poison, because the other subtypes of /poison/ already override AttackingTarget() completely, and as such it would do nothing but confuse people there. + . = ..() + if(. && venom_per_bite > 0 && iscarbon(target) && (!client || a_intent == INTENT_HARM)) + var/mob/living/carbon/C = target + var/inject_target = pick("chest", "head") + if(C.can_inject(null, FALSE, inject_target, FALSE)) + C.reagents.add_reagent("spidertoxin", venom_per_bite) + +//nursemaids - these create webs and eggs +/mob/living/simple_animal/hostile/poison/giant_spider/nurse + desc = "Furry and black, it makes you shudder to look at it. This one has brilliant green eyes." + icon_state = "nurse" + icon_living = "nurse" + icon_dead = "nurse_dead" + butcher_results = list(/obj/item/reagent_containers/food/snacks/spidermeat = 2, /obj/item/reagent_containers/food/snacks/spiderleg = 8, /obj/item/reagent_containers/food/snacks/spidereggs = 4) + + maxHealth = 40 + health = 40 + melee_damage_lower = 5 + melee_damage_upper = 10 + venom_per_bite = 30 + var/atom/cocoon_target + var/fed = 0 + +//hunters have the most poison and move the fastest, so they can find prey +/mob/living/simple_animal/hostile/poison/giant_spider/hunter + desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes." + icon_state = "hunter" + icon_living = "hunter" + icon_dead = "hunter_dead" + maxHealth = 120 + health = 120 + melee_damage_lower = 10 + melee_damage_upper = 20 + venom_per_bite = 10 + move_to_delay = 5 + +/mob/living/simple_animal/hostile/poison/giant_spider/handle_automated_movement() //Hacky and ugly. + . = ..() + if(AIStatus == AI_IDLE) + //1% chance to skitter madly away + if(!busy && prob(1)) + stop_automated_movement = 1 + Goto(pick(urange(20, src, 1)), move_to_delay) + spawn(50) + stop_automated_movement = 0 + walk(src,0) + return 1 + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/proc/GiveUp(C) + spawn(100) + if(busy == MOVING_TO_TARGET) + if(cocoon_target == C && get_dist(src,cocoon_target) > 1) + cocoon_target = null + busy = 0 + stop_automated_movement = 0 + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/handle_automated_movement() //Hacky and ugly. + if(..()) + var/list/can_see = view(src, 10) + if(!busy && prob(30)) //30% chance to stop wandering and do something + //first, check for potential food nearby to cocoon + for(var/mob/living/C in can_see) + if(C.stat && !istype(C, /mob/living/simple_animal/hostile/poison/giant_spider) && !C.anchored) + cocoon_target = C + busy = MOVING_TO_TARGET + Goto(C, move_to_delay) + //give up if we can't reach them after 10 seconds + GiveUp(C) + return + //second, spin a sticky spiderweb on this tile + var/obj/structure/spider/stickyweb/W = locate() in get_turf(src) + if(!W) + Web() + else + //third, lay an egg cluster there + if(fed) + LayEggs() + else + //fourthly, cocoon any nearby items so those pesky pinkskins can't use them + for(var/obj/O in can_see) + if(O.anchored) + continue + + if(isitem(O) || isstructure(O) || ismachinery(O)) + cocoon_target = O + busy = MOVING_TO_TARGET + stop_automated_movement = 1 + Goto(O, move_to_delay) + //give up if we can't reach them after 10 seconds + GiveUp(O) + + else if(busy == MOVING_TO_TARGET && cocoon_target) + if(get_dist(src, cocoon_target) <= 1) + Wrap() + + else + busy = 0 + stop_automated_movement = 0 + +/mob/living/simple_animal/hostile/poison/giant_spider/verb/Web() + set name = "Lay Web" + set category = "Spider" + set desc = "Spread a sticky web to slow down prey." + + var/T = src.loc + + if(busy != SPINNING_WEB) + busy = SPINNING_WEB + src.visible_message("\the [src] begins to secrete a sticky substance.") + stop_automated_movement = 1 + spawn(40) + if(busy == SPINNING_WEB && src.loc == T) + new /obj/structure/spider/stickyweb(T) + busy = 0 + stop_automated_movement = 0 + + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/verb/Wrap() + set name = "Wrap" + set category = "Spider" + set desc = "Wrap up prey to feast upon and objects for safe keeping." + + if(!cocoon_target) + var/list/choices = list() + for(var/mob/living/L in view(1,src)) + if(L == src) + continue + if(Adjacent(L)) + choices += L + for(var/obj/O in src.loc) + if(Adjacent(O)) + choices += O + cocoon_target = input(src,"What do you wish to cocoon?") in null|choices + + if(cocoon_target && busy != SPINNING_COCOON) + busy = SPINNING_COCOON + src.visible_message("\the [src] begins to secrete a sticky substance around \the [cocoon_target].") + stop_automated_movement = 1 + walk(src,0) + spawn(50) + if(busy == SPINNING_COCOON) + if(cocoon_target && istype(cocoon_target.loc, /turf) && get_dist(src,cocoon_target) <= 1) + var/obj/structure/spider/cocoon/C = new(cocoon_target.loc) + var/large_cocoon = 0 + C.pixel_x = cocoon_target.pixel_x + C.pixel_y = cocoon_target.pixel_y + for(var/obj/item/I in C.loc) + I.loc = C + for(var/obj/structure/S in C.loc) + if(!S.anchored) + S.loc = C + large_cocoon = 1 + for(var/obj/machinery/M in C.loc) + if(!M.anchored) + M.loc = C + large_cocoon = 1 + for(var/mob/living/L in C.loc) + if(istype(L, /mob/living/simple_animal/hostile/poison/giant_spider)) + continue + large_cocoon = 1 + L.loc = C + C.pixel_x = L.pixel_x + C.pixel_y = L.pixel_y + fed++ + visible_message("\the [src] sticks a proboscis into \the [L] and sucks a viscous substance out.") + + break + if(large_cocoon) + C.icon_state = pick("cocoon_large1","cocoon_large2","cocoon_large3") + cocoon_target = null + busy = 0 + stop_automated_movement = 0 + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/verb/LayEggs() + set name = "Lay Eggs" + set category = "Spider" + set desc = "Lay a clutch of eggs, but you must wrap a creature for feeding first." + + var/obj/structure/spider/eggcluster/E = locate() in get_turf(src) + if(E) + to_chat(src, "There is already a cluster of eggs here!") + else if(!fed) + to_chat(src, "You are too hungry to do this!") + else if(busy != LAYING_EGGS) + busy = LAYING_EGGS + src.visible_message("\the [src] begins to lay a cluster of eggs.") + stop_automated_movement = 1 + spawn(50) + if(busy == LAYING_EGGS) + E = locate() in get_turf(src) + if(!E) + var/obj/structure/spider/eggcluster/C = new /obj/structure/spider/eggcluster(src.loc) + C.faction = faction.Copy() + C.master_commander = master_commander + if(ckey) + C.player_spiders = 1 + fed-- + busy = 0 + stop_automated_movement = 0 + +#undef SPINNING_WEB +#undef LAYING_EGGS +#undef MOVING_TO_TARGET +#undef SPINNING_COCOON diff --git a/code/modules/mob/living/simple_animal/hostile/hivebot.dm b/code/modules/mob/living/simple_animal/hostile/hivebot.dm index c94648fe641..d2a97949c8e 100644 --- a/code/modules/mob/living/simple_animal/hostile/hivebot.dm +++ b/code/modules/mob/living/simple_animal/hostile/hivebot.dm @@ -1,108 +1,108 @@ -/obj/item/projectile/hivebotbullet - damage = 10 - damage_type = BRUTE - -/mob/living/simple_animal/hostile/hivebot - name = "Hivebot" - desc = "A small robot" - icon = 'icons/mob/hivebot.dmi' - icon_state = "basic" - icon_living = "basic" - icon_dead = "basic" - health = 15 - maxHealth = 15 - melee_damage_lower = 2 - melee_damage_upper = 3 - attacktext = "claws" - attack_sound = 'sound/weapons/bladeslice.ogg' - projectilesound = 'sound/weapons/gunshots/gunshot.ogg' - projectiletype = /obj/item/projectile/hivebotbullet - faction = list("hivebot") - check_friendly_fire = 1 - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - speak_emote = list("states") - gold_core_spawnable = HOSTILE_SPAWN - loot = list(/obj/effect/decal/cleanable/blood/gibs/robot) - deathmessage = "blows apart!" - del_on_death = 1 - -/mob/living/simple_animal/hostile/hivebot/range - name = "Hivebot" - desc = "A smallish robot, this one is armed!" - ranged = 1 - retreat_distance = 5 - minimum_distance = 5 - -/mob/living/simple_animal/hostile/hivebot/rapid - ranged = 1 - rapid = 3 - retreat_distance = 5 - minimum_distance = 5 - -/mob/living/simple_animal/hostile/hivebot/strong - name = "Strong Hivebot" - desc = "A robot, this one is armed and looks tough!" - health = 80 - maxHealth = 80 - ranged = 1 - -/mob/living/simple_animal/hostile/hivebot/death(gibbed) - // Only execute the below if we successfully died - . = ..(gibbed) - if(!.) - return FALSE - do_sparks(3, 1, src) - -/mob/living/simple_animal/hostile/hivebot/tele//this still needs work - name = "Beacon" - desc = "Some odd beacon thing" - icon = 'icons/mob/hivebot.dmi' - icon_state = "def_radar-off" - icon_living = "def_radar-off" - health = 200 - maxHealth = 200 - status_flags = 0 - anchored = 1 - stop_automated_movement = 1 - var/bot_type = "norm" - var/bot_amt = 10 - var/spawn_delay = 600 - var/turn_on = 0 - var/auto_spawn = 1 - proc - warpbots() - - -/mob/living/simple_animal/hostile/hivebot/tele/New() - ..() - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(5, 0, src.loc) - smoke.start() - visible_message("The [src] warps in!") - playsound(src.loc, 'sound/effects/empulse.ogg', 25, 1) - -/mob/living/simple_animal/hostile/hivebot/tele/warpbots() - icon_state = "def_radar" - visible_message("The [src] turns on!") - while(bot_amt > 0) - bot_amt-- - switch(bot_type) - if("norm") - var/mob/living/simple_animal/hostile/hivebot/H = new /mob/living/simple_animal/hostile/hivebot(get_turf(src)) - H.faction = faction - if("range") - var/mob/living/simple_animal/hostile/hivebot/range/R = new /mob/living/simple_animal/hostile/hivebot/range(get_turf(src)) - R.faction = faction - if("rapid") - var/mob/living/simple_animal/hostile/hivebot/rapid/F = new /mob/living/simple_animal/hostile/hivebot/rapid(get_turf(src)) - F.faction = faction - spawn(100) - qdel(src) - return - -/mob/living/simple_animal/hostile/hivebot/tele/handle_automated_action() - if(!..()) - return - if(prob(2))//Might be a bit low, will mess with it likely - warpbots() +/obj/item/projectile/hivebotbullet + damage = 10 + damage_type = BRUTE + +/mob/living/simple_animal/hostile/hivebot + name = "Hivebot" + desc = "A small robot" + icon = 'icons/mob/hivebot.dmi' + icon_state = "basic" + icon_living = "basic" + icon_dead = "basic" + health = 15 + maxHealth = 15 + melee_damage_lower = 2 + melee_damage_upper = 3 + attacktext = "claws" + attack_sound = 'sound/weapons/bladeslice.ogg' + projectilesound = 'sound/weapons/gunshots/gunshot.ogg' + projectiletype = /obj/item/projectile/hivebotbullet + faction = list("hivebot") + check_friendly_fire = 1 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + speak_emote = list("states") + gold_core_spawnable = HOSTILE_SPAWN + loot = list(/obj/effect/decal/cleanable/blood/gibs/robot) + deathmessage = "blows apart!" + del_on_death = 1 + +/mob/living/simple_animal/hostile/hivebot/range + name = "Hivebot" + desc = "A smallish robot, this one is armed!" + ranged = 1 + retreat_distance = 5 + minimum_distance = 5 + +/mob/living/simple_animal/hostile/hivebot/rapid + ranged = 1 + rapid = 3 + retreat_distance = 5 + minimum_distance = 5 + +/mob/living/simple_animal/hostile/hivebot/strong + name = "Strong Hivebot" + desc = "A robot, this one is armed and looks tough!" + health = 80 + maxHealth = 80 + ranged = 1 + +/mob/living/simple_animal/hostile/hivebot/death(gibbed) + // Only execute the below if we successfully died + . = ..(gibbed) + if(!.) + return FALSE + do_sparks(3, 1, src) + +/mob/living/simple_animal/hostile/hivebot/tele//this still needs work + name = "Beacon" + desc = "Some odd beacon thing" + icon = 'icons/mob/hivebot.dmi' + icon_state = "def_radar-off" + icon_living = "def_radar-off" + health = 200 + maxHealth = 200 + status_flags = 0 + anchored = 1 + stop_automated_movement = 1 + var/bot_type = "norm" + var/bot_amt = 10 + var/spawn_delay = 600 + var/turn_on = 0 + var/auto_spawn = 1 + proc + warpbots() + + +/mob/living/simple_animal/hostile/hivebot/tele/New() + ..() + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(5, 0, src.loc) + smoke.start() + visible_message("The [src] warps in!") + playsound(src.loc, 'sound/effects/empulse.ogg', 25, 1) + +/mob/living/simple_animal/hostile/hivebot/tele/warpbots() + icon_state = "def_radar" + visible_message("The [src] turns on!") + while(bot_amt > 0) + bot_amt-- + switch(bot_type) + if("norm") + var/mob/living/simple_animal/hostile/hivebot/H = new /mob/living/simple_animal/hostile/hivebot(get_turf(src)) + H.faction = faction + if("range") + var/mob/living/simple_animal/hostile/hivebot/range/R = new /mob/living/simple_animal/hostile/hivebot/range(get_turf(src)) + R.faction = faction + if("rapid") + var/mob/living/simple_animal/hostile/hivebot/rapid/F = new /mob/living/simple_animal/hostile/hivebot/rapid(get_turf(src)) + F.faction = faction + spawn(100) + qdel(src) + return + +/mob/living/simple_animal/hostile/hivebot/tele/handle_automated_action() + if(!..()) + return + if(prob(2))//Might be a bit low, will mess with it likely + warpbots() diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index c5749e939c4..2881a5945bb 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -1,568 +1,568 @@ -/mob/living/simple_animal/hostile - faction = list("hostile") - stop_automated_movement_when_pulled = 0 - obj_damage = 40 - environment_smash = ENVIRONMENT_SMASH_STRUCTURES //Bitflags. Set to ENVIRONMENT_SMASH_STRUCTURES to break closets,tables,racks, etc; ENVIRONMENT_SMASH_WALLS for walls; ENVIRONMENT_SMASH_RWALLS for rwalls - var/atom/target - var/ranged = FALSE - var/rapid = 0 //How many shots per volley. - var/rapid_fire_delay = 2 //Time between rapid fire shots - - var/dodging = FALSE - var/approaching_target = FALSE //We should dodge now - var/in_melee = FALSE //We should sidestep now - var/dodge_prob = 30 - var/sidestep_per_cycle = 1 //How many sidesteps per npcpool cycle when in melee - - var/projectiletype //set ONLY it and NULLIFY casingtype var, if we have ONLY projectile - var/projectilesound - var/casingtype //set ONLY it and NULLIFY projectiletype, if we have projectile IN CASING - var/move_to_delay = 3 //delay for the automated movement. - var/list/friends = list() - var/list/emote_taunt = list() - var/taunt_chance = 0 - - var/rapid_melee = 1 //Number of melee attacks between each npc pool tick. Spread evenly. - var/melee_queue_distance = 4 //If target is close enough start preparing to hit them if we have rapid_melee enabled - - var/ranged_message = "fires" //Fluff text for ranged mobs - var/ranged_cooldown = 0 //What the current cooldown on ranged attacks is, generally world.time + ranged_cooldown_time - var/ranged_cooldown_time = 30 //How long, in deciseconds, the cooldown of ranged attacks is - var/ranged_ignores_vision = FALSE //if it'll fire ranged attacks even if it lacks vision on its target, only works with environment smash - var/check_friendly_fire = 0 // Should the ranged mob check for friendlies when shooting - var/retreat_distance = null //If our mob runs from players when they're too close, set in tile distance. By default, mobs do not retreat. - var/minimum_distance = 1 //Minimum approach distance, so ranged mobs chase targets down, but still keep their distance set in tiles to the target, set higher to make mobs keep distance - - -//These vars are related to how mobs locate and target - var/robust_searching = 0 //By default, mobs have a simple searching method, set this to 1 for the more scrutinous searching (stat_attack, stat_exclusive, etc), should be disabled on most mobs - var/vision_range = 9 //How big of an area to search for targets in, a vision of 9 attempts to find targets as soon as they walk into screen view - var/aggro_vision_range = 9 //If a mob is aggro, we search in this radius. Defaults to 9 to keep in line with original simple mob aggro radius - var/search_objects = 0 //If we want to consider objects when searching around, set this to 1. If you want to search for objects while also ignoring mobs until hurt, set it to 2. To completely ignore mobs, even when attacked, set it to 3 - var/search_objects_timer_id //Timer for regaining our old search_objects value after being attacked - var/search_objects_regain_time = 30 //the delay between being attacked and gaining our old search_objects value back - var/list/wanted_objects = list() //A typecache of objects types that will be checked against to attack, should we have search_objects enabled - var/stat_attack = CONSCIOUS //Mobs with stat_attack to UNCONSCIOUS will attempt to attack things that are unconscious, Mobs with stat_attack set to DEAD will attempt to attack the dead. - var/stat_exclusive = FALSE //Mobs with this set to TRUE will exclusively attack things defined by stat_attack, stat_attack DEAD means they will only attack corpses - var/attack_same = 0 //Set us to 1 to allow us to attack our own faction - var/atom/targets_from = null //all range/attack/etc. calculations should be done from this atom, defaults to the mob itself, useful for Vehicles and such - var/attack_all_objects = FALSE //if true, equivalent to having a wanted_objects list containing ALL objects. - - var/lose_patience_timer_id //id for a timer to call LoseTarget(), used to stop mobs fixating on a target they can't reach - var/lose_patience_timeout = 300 //30 seconds by default, so there's no major changes to AI behaviour, beyond actually bailing if stuck forever - -/mob/living/simple_animal/hostile/Initialize(mapload) - . = ..() - - if(!targets_from) - targets_from = src - wanted_objects = typecacheof(wanted_objects) - -/mob/living/simple_animal/hostile/Destroy() - targets_from = null - target = null - return ..() - -/mob/living/simple_animal/hostile/Life(seconds, times_fired) - . = ..() - if(!.) - walk(src, 0) - return FALSE - -/mob/living/simple_animal/hostile/handle_automated_action() - if(AIStatus == AI_OFF) - return 0 - var/list/possible_targets = ListTargets() //we look around for potential targets and make it a list for later use. - - if(environment_smash) - EscapeConfinement() - - if(AICanContinue(possible_targets)) - if(!QDELETED(target) && !targets_from.Adjacent(target)) - DestroyPathToTarget() - if(!MoveToTarget(possible_targets)) //if we lose our target - if(AIShouldSleep(possible_targets)) // we try to acquire a new one - toggle_ai(AI_IDLE) // otherwise we go idle - return 1 - -/mob/living/simple_animal/hostile/handle_automated_movement() - . = ..() - if(dodging && target && in_melee && isturf(loc) && isturf(target.loc)) - var/datum/cb = CALLBACK(src,.proc/sidestep) - if(sidestep_per_cycle > 1) //For more than one just spread them equally - this could changed to some sensible distribution later - var/sidestep_delay = SSnpcpool.wait / sidestep_per_cycle - for(var/i in 1 to sidestep_per_cycle) - addtimer(cb, (i - 1) * sidestep_delay) - else //Otherwise randomize it to make the players guessing. - addtimer(cb,rand(1, SSnpcpool.wait)) - -/mob/living/simple_animal/hostile/proc/sidestep() - if(!target || !isturf(target.loc) || !isturf(loc) || stat == DEAD) - return - var/target_dir = get_dir(src, target) - - var/static/list/cardinal_sidestep_directions = list(-90, -45, 0, 45, 90) - var/static/list/diagonal_sidestep_directions = list(-45, 0, 45) - var/chosen_dir = 0 - if (target_dir & (target_dir - 1)) - chosen_dir = pick(diagonal_sidestep_directions) - else - chosen_dir = pick(cardinal_sidestep_directions) - if(chosen_dir) - chosen_dir = turn(target_dir, chosen_dir) - Move(get_step(src, chosen_dir)) - face_atom(target) //Looks better if they keep looking at you when dodging - -/mob/living/simple_animal/hostile/attacked_by(obj/item/I, mob/living/user) - if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client && user) - FindTarget(list(user), 1) - return ..() - -/mob/living/simple_animal/hostile/bullet_act(obj/item/projectile/P) - if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client) - if(P.firer && get_dist(src, P.firer) <= aggro_vision_range) - FindTarget(list(P.firer), 1) - Goto(P.starting, move_to_delay, 3) - return ..() - -//////////////HOSTILE MOB TARGETTING AND AGGRESSION//////////// - -/mob/living/simple_animal/hostile/proc/ListTargets()//Step 1, find out what we can see - if(!search_objects) - . = hearers(vision_range, targets_from) - src //Remove self, so we don't suicide - - var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/spacepod)) - - for(var/HM in typecache_filter_list(range(vision_range, targets_from), hostile_machines)) - if(can_see(targets_from, HM, vision_range)) - . += HM - else - . = oview(vision_range, targets_from) - -/mob/living/simple_animal/hostile/proc/FindTarget(var/list/possible_targets, var/HasTargetsList = 0)//Step 2, filter down possible targets to things we actually care about - . = list() - if(!HasTargetsList) - possible_targets = ListTargets() - for(var/pos_targ in possible_targets) - var/atom/A = pos_targ - if(Found(A))//Just in case people want to override targetting - . = list(A) - break - if(CanAttack(A))//Can we attack it? - . += A - continue - var/Target = PickTarget(.) - GiveTarget(Target) - return Target //We now have a target - -/mob/living/simple_animal/hostile/proc/PossibleThreats() - . = list() - for(var/pos_targ in ListTargets()) - var/atom/A = pos_targ - if(Found(A)) - . = list(A) - break - if(CanAttack(A)) - . += A - continue - -/mob/living/simple_animal/hostile/proc/Found(var/atom/A)//This is here as a potential override to pick a specific target if available - return - -/mob/living/simple_animal/hostile/proc/PickTarget(list/Targets)//Step 3, pick amongst the possible, attackable targets - if(target != null)//If we already have a target, but are told to pick again, calculate the lowest distance between all possible, and pick from the lowest distance targets - for(var/pos_targ in Targets) - var/atom/A = pos_targ - var/target_dist = get_dist(targets_from, target) - var/possible_target_distance = get_dist(targets_from, A) - if(target_dist < possible_target_distance) - Targets -= A - if(!Targets.len)//We didnt find nothin! - return - var/chosen_target = pick(Targets)//Pick the remaining targets (if any) at random - return chosen_target - -// Please do not add one-off mob AIs here, but override this function for your mob -/mob/living/simple_animal/hostile/CanAttack(atom/the_target)//Can we actually attack a possible target? - if(isturf(the_target) || !the_target || the_target.type == /atom/movable/lighting_object) // bail out on invalids - return FALSE - - if(ismob(the_target)) //Target is in godmode, ignore it. - var/mob/M = the_target - if(M.status_flags & GODMODE) - return FALSE - - if(see_invisible < the_target.invisibility) //Target's invisible to us, forget it - return FALSE - if(search_objects < 2) - if(isliving(the_target)) - var/mob/living/L = the_target - var/faction_check = faction_check_mob(L) - if(robust_searching) - if(faction_check && !attack_same) - return FALSE - if(L.stat > stat_attack) - return FALSE - if(L in friends) - return FALSE - else - if((faction_check && !attack_same) || L.stat) - return FALSE - return TRUE - - if(ismecha(the_target)) - var/obj/mecha/M = the_target - if(M.occupant)//Just so we don't attack empty mechs - if(CanAttack(M.occupant)) - return TRUE - - if(isspacepod(the_target)) - var/obj/spacepod/S = the_target - if(S.pilot)//Just so we don't attack empty pods - if(CanAttack(S.pilot)) - return TRUE - - if(istype(the_target, /obj/machinery/porta_turret)) - var/obj/machinery/porta_turret/P = the_target - if(P.faction in faction) - return FALSE - if(!P.raised) //Don't attack invincible turrets - return FALSE - if(P.stat & BROKEN) //Or turrets that are already broken - return FALSE - return TRUE - - if(isobj(the_target)) - if(attack_all_objects || is_type_in_typecache(the_target, wanted_objects)) - return TRUE - - return FALSE - -/mob/living/simple_animal/hostile/proc/GiveTarget(new_target)//Step 4, give us our selected target - target = new_target - LosePatience() - if(target != null) - GainPatience() - Aggro() - return 1 - -//What we do after closing in -/mob/living/simple_animal/hostile/proc/MeleeAction(patience = TRUE) - if(rapid_melee > 1) - var/datum/callback/cb = CALLBACK(src, .proc/CheckAndAttack) - var/delay = SSnpcpool.wait / rapid_melee - for(var/i in 1 to rapid_melee) - addtimer(cb, (i - 1)*delay) - else - AttackingTarget() - if(patience) - GainPatience() - -/mob/living/simple_animal/hostile/proc/CheckAndAttack() - if(target && targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from) && !incapacitated()) - AttackingTarget() - -/mob/living/simple_animal/hostile/proc/MoveToTarget(list/possible_targets)//Step 5, handle movement between us and our target - stop_automated_movement = 1 - if(!target || !CanAttack(target)) - LoseTarget() - return 0 - if(target in possible_targets) - var/turf/T = get_turf(src) - if(target.z != T.z) - LoseTarget() - return 0 - var/target_distance = get_dist(targets_from,target) - if(ranged) //We ranged? Shoot at em - if(!target.Adjacent(targets_from) && ranged_cooldown <= world.time) //But make sure they're not in range for a melee attack and our range attack is off cooldown - OpenFire(target) - if(!Process_Spacemove()) //Drifting - walk(src,0) - return 1 - if(retreat_distance != null) //If we have a retreat distance, check if we need to run from our target - if(target_distance <= retreat_distance) //If target's closer than our retreat distance, run - walk_away(src,target,retreat_distance,move_to_delay) - else - Goto(target,move_to_delay,minimum_distance) //Otherwise, get to our minimum distance so we chase them - else - Goto(target,move_to_delay,minimum_distance) - if(target) - if(targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from)) //If they're next to us, attack - MeleeAction() - else - if(rapid_melee > 1 && target_distance <= melee_queue_distance) - MeleeAction(FALSE) - in_melee = FALSE //If we're just preparing to strike do not enter sidestep mode - return 1 - return 0 - if(environment_smash) - if(target.loc != null && get_dist(targets_from, target.loc) <= vision_range) //We can't see our target, but he's in our vision range still - if(ranged_ignores_vision && ranged_cooldown <= world.time) //we can't see our target... but we can fire at them! - OpenFire(target) - if((environment_smash & ENVIRONMENT_SMASH_WALLS) || (environment_smash & ENVIRONMENT_SMASH_RWALLS)) //If we're capable of smashing through walls, forget about vision completely after finding our target - Goto(target,move_to_delay,minimum_distance) - FindHidden() - return 1 - else - if(FindHidden()) - return 1 - LoseTarget() - return 0 - -/mob/living/simple_animal/hostile/proc/Goto(target, delay, minimum_distance) - if(target == src.target) - approaching_target = TRUE - else - approaching_target = FALSE - walk_to(src, target, minimum_distance, delay) - -/mob/living/simple_animal/hostile/adjustHealth(damage, updating_health = TRUE) - . = ..() - if(!ckey && !stat && search_objects < 3 && damage > 0)//Not unconscious, and we don't ignore mobs - if(search_objects)//Turn off item searching and ignore whatever item we were looking at, we're more concerned with fight or flight - target = null - LoseSearchObjects() - if(AIStatus != AI_ON && AIStatus != AI_OFF) - toggle_ai(AI_ON) - FindTarget() - else if(target != null && prob(40))//No more pulling a mob forever and having a second player attack it, it can switch targets now if it finds a more suitable one - FindTarget() - -/mob/living/simple_animal/hostile/proc/AttackingTarget() - SEND_SIGNAL(src, COMSIG_HOSTILE_ATTACKINGTARGET, target) - in_melee = TRUE - return target.attack_animal(src) - -/mob/living/simple_animal/hostile/proc/Aggro() - vision_range = aggro_vision_range - if(target && emote_taunt.len && prob(taunt_chance)) - emote("me", 1, "[pick(emote_taunt)] at [target].") - taunt_chance = max(taunt_chance-7,2) - -/mob/living/simple_animal/hostile/proc/LoseAggro() - stop_automated_movement = 0 - vision_range = initial(vision_range) - taunt_chance = initial(taunt_chance) - -/mob/living/simple_animal/hostile/proc/LoseTarget() - target = null - approaching_target = FALSE - in_melee = FALSE - walk(src, 0) - LoseAggro() - -//////////////END HOSTILE MOB TARGETTING AND AGGRESSION//////////// - -/mob/living/simple_animal/hostile/death(gibbed) - // Only execute the below if we successfully died - . = ..(gibbed) - if(!.) - return FALSE - LoseTarget() - -/mob/living/simple_animal/hostile/proc/summon_backup(distance) - do_alert_animation(src) - playsound(loc, 'sound/machines/chime.ogg', 50, 1, -1) - for(var/mob/living/simple_animal/hostile/M in oview(distance, targets_from)) - if(faction_check_mob(M, TRUE)) - if(M.AIStatus == AI_OFF) - return - else - M.Goto(src,M.move_to_delay,M.minimum_distance) - -/mob/living/simple_animal/hostile/proc/CheckFriendlyFire(atom/A) - if(check_friendly_fire) - for(var/turf/T in getline(src,A)) // Not 100% reliable but this is faster than simulating actual trajectory - for(var/mob/living/L in T) - if(L == src || L == A) - continue - if(faction_check_mob(L) && !attack_same) - return TRUE - -/mob/living/simple_animal/hostile/proc/OpenFire(atom/A) - if(CheckFriendlyFire(A)) - return - visible_message("[src] [ranged_message] at [A]!") - - - if(rapid > 1) - var/datum/callback/cb = CALLBACK(src, .proc/Shoot, A) - for(var/i in 1 to rapid) - addtimer(cb, (i - 1)*rapid_fire_delay) - else - Shoot(A) - ranged_cooldown = world.time + ranged_cooldown_time - -/mob/living/simple_animal/hostile/proc/Shoot(atom/targeted_atom) - if( QDELETED(targeted_atom) || targeted_atom == targets_from.loc || targeted_atom == targets_from ) - return - var/turf/startloc = get_turf(targets_from) - if(casingtype) - var/obj/item/ammo_casing/casing = new casingtype(startloc) - playsound(src, projectilesound, 100, 1) - casing.fire(targeted_atom, src, zone_override = ran_zone()) - else if(projectiletype) - var/obj/item/projectile/P = new projectiletype(startloc) - playsound(src, projectilesound, 100, 1) - P.current = startloc - P.starting = startloc - P.firer = src - P.yo = targeted_atom.y - startloc.y - P.xo = targeted_atom.x - startloc.x - if(AIStatus != AI_ON)//Don't want mindless mobs to have their movement screwed up firing in space - newtonian_move(get_dir(targeted_atom, targets_from)) - P.original = targeted_atom - P.preparePixelProjectile(targeted_atom, get_turf(targeted_atom), src) - P.fire() - return P - -/mob/living/simple_animal/hostile/proc/CanSmashTurfs(turf/T) - return iswallturf(T) || ismineralturf(T) - -/mob/living/simple_animal/hostile/Move(atom/newloc, dir , step_x , step_y) - if(dodging && approaching_target && prob(dodge_prob) && moving_diagonally == 0 && isturf(loc) && isturf(newloc)) - return dodge(newloc, dir) - else - return ..() - -/mob/living/simple_animal/hostile/proc/dodge(moving_to,move_direction) - //Assuming we move towards the target we want to swerve toward them to get closer - var/cdir = turn(move_direction, 45) - var/ccdir = turn(move_direction, -45) - dodging = FALSE - . = Move(get_step(loc,pick(cdir,ccdir))) - if(!.)//Can't dodge there so we just carry on - . = Move(moving_to,move_direction) - dodging = TRUE - -/mob/living/simple_animal/hostile/proc/DestroyObjectsInDirection(direction) - var/turf/T = get_step(targets_from, direction) - if(QDELETED(T)) - return - if(T.Adjacent(targets_from)) - if(CanSmashTurfs(T)) - T.attack_animal(src) - return - for(var/obj/O in T.contents) - if(!O.Adjacent(targets_from)) - continue - if((ismachinery(O) || isstructure(O)) && O.density && environment_smash >= ENVIRONMENT_SMASH_STRUCTURES && !O.IsObscured()) - O.attack_animal(src) - return - -/mob/living/simple_animal/hostile/proc/DestroyPathToTarget() - if(environment_smash) - EscapeConfinement() - var/dir_to_target = get_dir(targets_from, target) - var/dir_list = list() - if(dir_to_target in diagonals) //it's diagonal, so we need two directions to hit - for(var/direction in cardinal) - if(direction & dir_to_target) - dir_list += direction - else - dir_list += dir_to_target - for(var/direction in dir_list) //now we hit all of the directions we got in this fashion, since it's the only directions we should actually need - DestroyObjectsInDirection(direction) - -/mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with megafauna destroying everything around them - if(environment_smash) - EscapeConfinement() - for(var/dir in cardinal) - DestroyObjectsInDirection(dir) - -/mob/living/simple_animal/hostile/proc/EscapeConfinement() - if(buckled) - buckled.attack_animal(src) - if(!isturf(targets_from.loc) && targets_from.loc != null)//Did someone put us in something? - var/atom/A = targets_from.loc - A.attack_animal(src)//Bang on it till we get out - -/mob/living/simple_animal/hostile/proc/FindHidden() - if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper) || istype(target.loc, /obj/machinery/bodyscanner) || istype(target.loc, /obj/machinery/recharge_station)) - var/atom/A = target.loc - Goto(A,move_to_delay,minimum_distance) - if(A.Adjacent(targets_from)) - A.attack_animal(src) - return 1 - -/mob/living/simple_animal/hostile/RangedAttack(atom/A, params) //Player firing - if(ranged && ranged_cooldown <= world.time) - target = A - OpenFire(A) - ..() - - - -////// AI Status /////// -/mob/living/simple_animal/hostile/proc/AICanContinue(var/list/possible_targets) - switch(AIStatus) - if(AI_ON) - . = 1 - if(AI_IDLE) - if(FindTarget(possible_targets, 1)) - . = 1 - toggle_ai(AI_ON) //Wake up for more than one Life() cycle. - else - . = 0 - -/mob/living/simple_animal/hostile/proc/AIShouldSleep(var/list/possible_targets) - return !FindTarget(possible_targets, 1) - - -//These two procs handle losing our target if we've failed to attack them for -//more than lose_patience_timeout deciseconds, which probably means we're stuck -/mob/living/simple_animal/hostile/proc/GainPatience() - if(lose_patience_timeout) - LosePatience() - lose_patience_timer_id = addtimer(CALLBACK(src, .proc/LoseTarget), lose_patience_timeout, TIMER_STOPPABLE) - - -/mob/living/simple_animal/hostile/proc/LosePatience() - deltimer(lose_patience_timer_id) - - -//These two procs handle losing and regaining search_objects when attacked by a mob -/mob/living/simple_animal/hostile/proc/LoseSearchObjects() - search_objects = 0 - deltimer(search_objects_timer_id) - search_objects_timer_id = addtimer(CALLBACK(src, .proc/RegainSearchObjects), search_objects_regain_time, TIMER_STOPPABLE) - - -/mob/living/simple_animal/hostile/proc/RegainSearchObjects(value) - if(!value) - value = initial(search_objects) - search_objects = value - -/mob/living/simple_animal/hostile/consider_wakeup() - ..() - var/list/tlist - var/turf/T = get_turf(src) - - if(!T) - return - - if(!length(SSmobs.clients_by_zlevel[T.z])) // It's fine to use .len here but doesn't compile on 511 - toggle_ai(AI_Z_OFF) - return - - var/cheap_search = isturf(T) && !is_station_level(T.z) - if(cheap_search) - tlist = ListTargetsLazy(T.z) - else - tlist = ListTargets() - - if(AIStatus == AI_IDLE && FindTarget(tlist, 1)) - if(cheap_search) //Try again with full effort - FindTarget() - toggle_ai(AI_ON) - -/mob/living/simple_animal/hostile/proc/ListTargetsLazy(var/_Z)//Step 1, find out what we can see - var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/spacepod)) - . = list() - for(var/I in SSmobs.clients_by_zlevel[_Z]) - var/mob/M = I - if(get_dist(M, src) < vision_range) - if(isturf(M.loc)) - . += M - else if(M.loc.type in hostile_machines) - . += M.loc \ No newline at end of file +/mob/living/simple_animal/hostile + faction = list("hostile") + stop_automated_movement_when_pulled = 0 + obj_damage = 40 + environment_smash = ENVIRONMENT_SMASH_STRUCTURES //Bitflags. Set to ENVIRONMENT_SMASH_STRUCTURES to break closets,tables,racks, etc; ENVIRONMENT_SMASH_WALLS for walls; ENVIRONMENT_SMASH_RWALLS for rwalls + var/atom/target + var/ranged = FALSE + var/rapid = 0 //How many shots per volley. + var/rapid_fire_delay = 2 //Time between rapid fire shots + + var/dodging = FALSE + var/approaching_target = FALSE //We should dodge now + var/in_melee = FALSE //We should sidestep now + var/dodge_prob = 30 + var/sidestep_per_cycle = 1 //How many sidesteps per npcpool cycle when in melee + + var/projectiletype //set ONLY it and NULLIFY casingtype var, if we have ONLY projectile + var/projectilesound + var/casingtype //set ONLY it and NULLIFY projectiletype, if we have projectile IN CASING + var/move_to_delay = 3 //delay for the automated movement. + var/list/friends = list() + var/list/emote_taunt = list() + var/taunt_chance = 0 + + var/rapid_melee = 1 //Number of melee attacks between each npc pool tick. Spread evenly. + var/melee_queue_distance = 4 //If target is close enough start preparing to hit them if we have rapid_melee enabled + + var/ranged_message = "fires" //Fluff text for ranged mobs + var/ranged_cooldown = 0 //What the current cooldown on ranged attacks is, generally world.time + ranged_cooldown_time + var/ranged_cooldown_time = 30 //How long, in deciseconds, the cooldown of ranged attacks is + var/ranged_ignores_vision = FALSE //if it'll fire ranged attacks even if it lacks vision on its target, only works with environment smash + var/check_friendly_fire = 0 // Should the ranged mob check for friendlies when shooting + var/retreat_distance = null //If our mob runs from players when they're too close, set in tile distance. By default, mobs do not retreat. + var/minimum_distance = 1 //Minimum approach distance, so ranged mobs chase targets down, but still keep their distance set in tiles to the target, set higher to make mobs keep distance + + +//These vars are related to how mobs locate and target + var/robust_searching = 0 //By default, mobs have a simple searching method, set this to 1 for the more scrutinous searching (stat_attack, stat_exclusive, etc), should be disabled on most mobs + var/vision_range = 9 //How big of an area to search for targets in, a vision of 9 attempts to find targets as soon as they walk into screen view + var/aggro_vision_range = 9 //If a mob is aggro, we search in this radius. Defaults to 9 to keep in line with original simple mob aggro radius + var/search_objects = 0 //If we want to consider objects when searching around, set this to 1. If you want to search for objects while also ignoring mobs until hurt, set it to 2. To completely ignore mobs, even when attacked, set it to 3 + var/search_objects_timer_id //Timer for regaining our old search_objects value after being attacked + var/search_objects_regain_time = 30 //the delay between being attacked and gaining our old search_objects value back + var/list/wanted_objects = list() //A typecache of objects types that will be checked against to attack, should we have search_objects enabled + var/stat_attack = CONSCIOUS //Mobs with stat_attack to UNCONSCIOUS will attempt to attack things that are unconscious, Mobs with stat_attack set to DEAD will attempt to attack the dead. + var/stat_exclusive = FALSE //Mobs with this set to TRUE will exclusively attack things defined by stat_attack, stat_attack DEAD means they will only attack corpses + var/attack_same = 0 //Set us to 1 to allow us to attack our own faction + var/atom/targets_from = null //all range/attack/etc. calculations should be done from this atom, defaults to the mob itself, useful for Vehicles and such + var/attack_all_objects = FALSE //if true, equivalent to having a wanted_objects list containing ALL objects. + + var/lose_patience_timer_id //id for a timer to call LoseTarget(), used to stop mobs fixating on a target they can't reach + var/lose_patience_timeout = 300 //30 seconds by default, so there's no major changes to AI behaviour, beyond actually bailing if stuck forever + +/mob/living/simple_animal/hostile/Initialize(mapload) + . = ..() + + if(!targets_from) + targets_from = src + wanted_objects = typecacheof(wanted_objects) + +/mob/living/simple_animal/hostile/Destroy() + targets_from = null + target = null + return ..() + +/mob/living/simple_animal/hostile/Life(seconds, times_fired) + . = ..() + if(!.) + walk(src, 0) + return FALSE + +/mob/living/simple_animal/hostile/handle_automated_action() + if(AIStatus == AI_OFF) + return 0 + var/list/possible_targets = ListTargets() //we look around for potential targets and make it a list for later use. + + if(environment_smash) + EscapeConfinement() + + if(AICanContinue(possible_targets)) + if(!QDELETED(target) && !targets_from.Adjacent(target)) + DestroyPathToTarget() + if(!MoveToTarget(possible_targets)) //if we lose our target + if(AIShouldSleep(possible_targets)) // we try to acquire a new one + toggle_ai(AI_IDLE) // otherwise we go idle + return 1 + +/mob/living/simple_animal/hostile/handle_automated_movement() + . = ..() + if(dodging && target && in_melee && isturf(loc) && isturf(target.loc)) + var/datum/cb = CALLBACK(src,.proc/sidestep) + if(sidestep_per_cycle > 1) //For more than one just spread them equally - this could changed to some sensible distribution later + var/sidestep_delay = SSnpcpool.wait / sidestep_per_cycle + for(var/i in 1 to sidestep_per_cycle) + addtimer(cb, (i - 1) * sidestep_delay) + else //Otherwise randomize it to make the players guessing. + addtimer(cb,rand(1, SSnpcpool.wait)) + +/mob/living/simple_animal/hostile/proc/sidestep() + if(!target || !isturf(target.loc) || !isturf(loc) || stat == DEAD) + return + var/target_dir = get_dir(src, target) + + var/static/list/cardinal_sidestep_directions = list(-90, -45, 0, 45, 90) + var/static/list/diagonal_sidestep_directions = list(-45, 0, 45) + var/chosen_dir = 0 + if (target_dir & (target_dir - 1)) + chosen_dir = pick(diagonal_sidestep_directions) + else + chosen_dir = pick(cardinal_sidestep_directions) + if(chosen_dir) + chosen_dir = turn(target_dir, chosen_dir) + Move(get_step(src, chosen_dir)) + face_atom(target) //Looks better if they keep looking at you when dodging + +/mob/living/simple_animal/hostile/attacked_by(obj/item/I, mob/living/user) + if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client && user) + FindTarget(list(user), 1) + return ..() + +/mob/living/simple_animal/hostile/bullet_act(obj/item/projectile/P) + if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client) + if(P.firer && get_dist(src, P.firer) <= aggro_vision_range) + FindTarget(list(P.firer), 1) + Goto(P.starting, move_to_delay, 3) + return ..() + +//////////////HOSTILE MOB TARGETTING AND AGGRESSION//////////// + +/mob/living/simple_animal/hostile/proc/ListTargets()//Step 1, find out what we can see + if(!search_objects) + . = hearers(vision_range, targets_from) - src //Remove self, so we don't suicide + + var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/spacepod)) + + for(var/HM in typecache_filter_list(range(vision_range, targets_from), hostile_machines)) + if(can_see(targets_from, HM, vision_range)) + . += HM + else + . = oview(vision_range, targets_from) + +/mob/living/simple_animal/hostile/proc/FindTarget(var/list/possible_targets, var/HasTargetsList = 0)//Step 2, filter down possible targets to things we actually care about + . = list() + if(!HasTargetsList) + possible_targets = ListTargets() + for(var/pos_targ in possible_targets) + var/atom/A = pos_targ + if(Found(A))//Just in case people want to override targetting + . = list(A) + break + if(CanAttack(A))//Can we attack it? + . += A + continue + var/Target = PickTarget(.) + GiveTarget(Target) + return Target //We now have a target + +/mob/living/simple_animal/hostile/proc/PossibleThreats() + . = list() + for(var/pos_targ in ListTargets()) + var/atom/A = pos_targ + if(Found(A)) + . = list(A) + break + if(CanAttack(A)) + . += A + continue + +/mob/living/simple_animal/hostile/proc/Found(var/atom/A)//This is here as a potential override to pick a specific target if available + return + +/mob/living/simple_animal/hostile/proc/PickTarget(list/Targets)//Step 3, pick amongst the possible, attackable targets + if(target != null)//If we already have a target, but are told to pick again, calculate the lowest distance between all possible, and pick from the lowest distance targets + for(var/pos_targ in Targets) + var/atom/A = pos_targ + var/target_dist = get_dist(targets_from, target) + var/possible_target_distance = get_dist(targets_from, A) + if(target_dist < possible_target_distance) + Targets -= A + if(!Targets.len)//We didnt find nothin! + return + var/chosen_target = pick(Targets)//Pick the remaining targets (if any) at random + return chosen_target + +// Please do not add one-off mob AIs here, but override this function for your mob +/mob/living/simple_animal/hostile/CanAttack(atom/the_target)//Can we actually attack a possible target? + if(isturf(the_target) || !the_target || the_target.type == /atom/movable/lighting_object) // bail out on invalids + return FALSE + + if(ismob(the_target)) //Target is in godmode, ignore it. + var/mob/M = the_target + if(M.status_flags & GODMODE) + return FALSE + + if(see_invisible < the_target.invisibility) //Target's invisible to us, forget it + return FALSE + if(search_objects < 2) + if(isliving(the_target)) + var/mob/living/L = the_target + var/faction_check = faction_check_mob(L) + if(robust_searching) + if(faction_check && !attack_same) + return FALSE + if(L.stat > stat_attack) + return FALSE + if(L in friends) + return FALSE + else + if((faction_check && !attack_same) || L.stat) + return FALSE + return TRUE + + if(ismecha(the_target)) + var/obj/mecha/M = the_target + if(M.occupant)//Just so we don't attack empty mechs + if(CanAttack(M.occupant)) + return TRUE + + if(isspacepod(the_target)) + var/obj/spacepod/S = the_target + if(S.pilot)//Just so we don't attack empty pods + if(CanAttack(S.pilot)) + return TRUE + + if(istype(the_target, /obj/machinery/porta_turret)) + var/obj/machinery/porta_turret/P = the_target + if(P.faction in faction) + return FALSE + if(!P.raised) //Don't attack invincible turrets + return FALSE + if(P.stat & BROKEN) //Or turrets that are already broken + return FALSE + return TRUE + + if(isobj(the_target)) + if(attack_all_objects || is_type_in_typecache(the_target, wanted_objects)) + return TRUE + + return FALSE + +/mob/living/simple_animal/hostile/proc/GiveTarget(new_target)//Step 4, give us our selected target + target = new_target + LosePatience() + if(target != null) + GainPatience() + Aggro() + return 1 + +//What we do after closing in +/mob/living/simple_animal/hostile/proc/MeleeAction(patience = TRUE) + if(rapid_melee > 1) + var/datum/callback/cb = CALLBACK(src, .proc/CheckAndAttack) + var/delay = SSnpcpool.wait / rapid_melee + for(var/i in 1 to rapid_melee) + addtimer(cb, (i - 1)*delay) + else + AttackingTarget() + if(patience) + GainPatience() + +/mob/living/simple_animal/hostile/proc/CheckAndAttack() + if(target && targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from) && !incapacitated()) + AttackingTarget() + +/mob/living/simple_animal/hostile/proc/MoveToTarget(list/possible_targets)//Step 5, handle movement between us and our target + stop_automated_movement = 1 + if(!target || !CanAttack(target)) + LoseTarget() + return 0 + if(target in possible_targets) + var/turf/T = get_turf(src) + if(target.z != T.z) + LoseTarget() + return 0 + var/target_distance = get_dist(targets_from,target) + if(ranged) //We ranged? Shoot at em + if(!target.Adjacent(targets_from) && ranged_cooldown <= world.time) //But make sure they're not in range for a melee attack and our range attack is off cooldown + OpenFire(target) + if(!Process_Spacemove()) //Drifting + walk(src,0) + return 1 + if(retreat_distance != null) //If we have a retreat distance, check if we need to run from our target + if(target_distance <= retreat_distance) //If target's closer than our retreat distance, run + walk_away(src,target,retreat_distance,move_to_delay) + else + Goto(target,move_to_delay,minimum_distance) //Otherwise, get to our minimum distance so we chase them + else + Goto(target,move_to_delay,minimum_distance) + if(target) + if(targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from)) //If they're next to us, attack + MeleeAction() + else + if(rapid_melee > 1 && target_distance <= melee_queue_distance) + MeleeAction(FALSE) + in_melee = FALSE //If we're just preparing to strike do not enter sidestep mode + return 1 + return 0 + if(environment_smash) + if(target.loc != null && get_dist(targets_from, target.loc) <= vision_range) //We can't see our target, but he's in our vision range still + if(ranged_ignores_vision && ranged_cooldown <= world.time) //we can't see our target... but we can fire at them! + OpenFire(target) + if((environment_smash & ENVIRONMENT_SMASH_WALLS) || (environment_smash & ENVIRONMENT_SMASH_RWALLS)) //If we're capable of smashing through walls, forget about vision completely after finding our target + Goto(target,move_to_delay,minimum_distance) + FindHidden() + return 1 + else + if(FindHidden()) + return 1 + LoseTarget() + return 0 + +/mob/living/simple_animal/hostile/proc/Goto(target, delay, minimum_distance) + if(target == src.target) + approaching_target = TRUE + else + approaching_target = FALSE + walk_to(src, target, minimum_distance, delay) + +/mob/living/simple_animal/hostile/adjustHealth(damage, updating_health = TRUE) + . = ..() + if(!ckey && !stat && search_objects < 3 && damage > 0)//Not unconscious, and we don't ignore mobs + if(search_objects)//Turn off item searching and ignore whatever item we were looking at, we're more concerned with fight or flight + target = null + LoseSearchObjects() + if(AIStatus != AI_ON && AIStatus != AI_OFF) + toggle_ai(AI_ON) + FindTarget() + else if(target != null && prob(40))//No more pulling a mob forever and having a second player attack it, it can switch targets now if it finds a more suitable one + FindTarget() + +/mob/living/simple_animal/hostile/proc/AttackingTarget() + SEND_SIGNAL(src, COMSIG_HOSTILE_ATTACKINGTARGET, target) + in_melee = TRUE + return target.attack_animal(src) + +/mob/living/simple_animal/hostile/proc/Aggro() + vision_range = aggro_vision_range + if(target && emote_taunt.len && prob(taunt_chance)) + emote("me", 1, "[pick(emote_taunt)] at [target].") + taunt_chance = max(taunt_chance-7,2) + +/mob/living/simple_animal/hostile/proc/LoseAggro() + stop_automated_movement = 0 + vision_range = initial(vision_range) + taunt_chance = initial(taunt_chance) + +/mob/living/simple_animal/hostile/proc/LoseTarget() + target = null + approaching_target = FALSE + in_melee = FALSE + walk(src, 0) + LoseAggro() + +//////////////END HOSTILE MOB TARGETTING AND AGGRESSION//////////// + +/mob/living/simple_animal/hostile/death(gibbed) + // Only execute the below if we successfully died + . = ..(gibbed) + if(!.) + return FALSE + LoseTarget() + +/mob/living/simple_animal/hostile/proc/summon_backup(distance) + do_alert_animation(src) + playsound(loc, 'sound/machines/chime.ogg', 50, 1, -1) + for(var/mob/living/simple_animal/hostile/M in oview(distance, targets_from)) + if(faction_check_mob(M, TRUE)) + if(M.AIStatus == AI_OFF) + return + else + M.Goto(src,M.move_to_delay,M.minimum_distance) + +/mob/living/simple_animal/hostile/proc/CheckFriendlyFire(atom/A) + if(check_friendly_fire) + for(var/turf/T in getline(src,A)) // Not 100% reliable but this is faster than simulating actual trajectory + for(var/mob/living/L in T) + if(L == src || L == A) + continue + if(faction_check_mob(L) && !attack_same) + return TRUE + +/mob/living/simple_animal/hostile/proc/OpenFire(atom/A) + if(CheckFriendlyFire(A)) + return + visible_message("[src] [ranged_message] at [A]!") + + + if(rapid > 1) + var/datum/callback/cb = CALLBACK(src, .proc/Shoot, A) + for(var/i in 1 to rapid) + addtimer(cb, (i - 1)*rapid_fire_delay) + else + Shoot(A) + ranged_cooldown = world.time + ranged_cooldown_time + +/mob/living/simple_animal/hostile/proc/Shoot(atom/targeted_atom) + if( QDELETED(targeted_atom) || targeted_atom == targets_from.loc || targeted_atom == targets_from ) + return + var/turf/startloc = get_turf(targets_from) + if(casingtype) + var/obj/item/ammo_casing/casing = new casingtype(startloc) + playsound(src, projectilesound, 100, 1) + casing.fire(targeted_atom, src, zone_override = ran_zone()) + else if(projectiletype) + var/obj/item/projectile/P = new projectiletype(startloc) + playsound(src, projectilesound, 100, 1) + P.current = startloc + P.starting = startloc + P.firer = src + P.yo = targeted_atom.y - startloc.y + P.xo = targeted_atom.x - startloc.x + if(AIStatus != AI_ON)//Don't want mindless mobs to have their movement screwed up firing in space + newtonian_move(get_dir(targeted_atom, targets_from)) + P.original = targeted_atom + P.preparePixelProjectile(targeted_atom, get_turf(targeted_atom), src) + P.fire() + return P + +/mob/living/simple_animal/hostile/proc/CanSmashTurfs(turf/T) + return iswallturf(T) || ismineralturf(T) + +/mob/living/simple_animal/hostile/Move(atom/newloc, dir , step_x , step_y) + if(dodging && approaching_target && prob(dodge_prob) && moving_diagonally == 0 && isturf(loc) && isturf(newloc)) + return dodge(newloc, dir) + else + return ..() + +/mob/living/simple_animal/hostile/proc/dodge(moving_to,move_direction) + //Assuming we move towards the target we want to swerve toward them to get closer + var/cdir = turn(move_direction, 45) + var/ccdir = turn(move_direction, -45) + dodging = FALSE + . = Move(get_step(loc,pick(cdir,ccdir))) + if(!.)//Can't dodge there so we just carry on + . = Move(moving_to,move_direction) + dodging = TRUE + +/mob/living/simple_animal/hostile/proc/DestroyObjectsInDirection(direction) + var/turf/T = get_step(targets_from, direction) + if(QDELETED(T)) + return + if(T.Adjacent(targets_from)) + if(CanSmashTurfs(T)) + T.attack_animal(src) + return + for(var/obj/O in T.contents) + if(!O.Adjacent(targets_from)) + continue + if((ismachinery(O) || isstructure(O)) && O.density && environment_smash >= ENVIRONMENT_SMASH_STRUCTURES && !O.IsObscured()) + O.attack_animal(src) + return + +/mob/living/simple_animal/hostile/proc/DestroyPathToTarget() + if(environment_smash) + EscapeConfinement() + var/dir_to_target = get_dir(targets_from, target) + var/dir_list = list() + if(dir_to_target in diagonals) //it's diagonal, so we need two directions to hit + for(var/direction in cardinal) + if(direction & dir_to_target) + dir_list += direction + else + dir_list += dir_to_target + for(var/direction in dir_list) //now we hit all of the directions we got in this fashion, since it's the only directions we should actually need + DestroyObjectsInDirection(direction) + +/mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with megafauna destroying everything around them + if(environment_smash) + EscapeConfinement() + for(var/dir in cardinal) + DestroyObjectsInDirection(dir) + +/mob/living/simple_animal/hostile/proc/EscapeConfinement() + if(buckled) + buckled.attack_animal(src) + if(!isturf(targets_from.loc) && targets_from.loc != null)//Did someone put us in something? + var/atom/A = targets_from.loc + A.attack_animal(src)//Bang on it till we get out + +/mob/living/simple_animal/hostile/proc/FindHidden() + if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper) || istype(target.loc, /obj/machinery/bodyscanner) || istype(target.loc, /obj/machinery/recharge_station)) + var/atom/A = target.loc + Goto(A,move_to_delay,minimum_distance) + if(A.Adjacent(targets_from)) + A.attack_animal(src) + return 1 + +/mob/living/simple_animal/hostile/RangedAttack(atom/A, params) //Player firing + if(ranged && ranged_cooldown <= world.time) + target = A + OpenFire(A) + ..() + + + +////// AI Status /////// +/mob/living/simple_animal/hostile/proc/AICanContinue(var/list/possible_targets) + switch(AIStatus) + if(AI_ON) + . = 1 + if(AI_IDLE) + if(FindTarget(possible_targets, 1)) + . = 1 + toggle_ai(AI_ON) //Wake up for more than one Life() cycle. + else + . = 0 + +/mob/living/simple_animal/hostile/proc/AIShouldSleep(var/list/possible_targets) + return !FindTarget(possible_targets, 1) + + +//These two procs handle losing our target if we've failed to attack them for +//more than lose_patience_timeout deciseconds, which probably means we're stuck +/mob/living/simple_animal/hostile/proc/GainPatience() + if(lose_patience_timeout) + LosePatience() + lose_patience_timer_id = addtimer(CALLBACK(src, .proc/LoseTarget), lose_patience_timeout, TIMER_STOPPABLE) + + +/mob/living/simple_animal/hostile/proc/LosePatience() + deltimer(lose_patience_timer_id) + + +//These two procs handle losing and regaining search_objects when attacked by a mob +/mob/living/simple_animal/hostile/proc/LoseSearchObjects() + search_objects = 0 + deltimer(search_objects_timer_id) + search_objects_timer_id = addtimer(CALLBACK(src, .proc/RegainSearchObjects), search_objects_regain_time, TIMER_STOPPABLE) + + +/mob/living/simple_animal/hostile/proc/RegainSearchObjects(value) + if(!value) + value = initial(search_objects) + search_objects = value + +/mob/living/simple_animal/hostile/consider_wakeup() + ..() + var/list/tlist + var/turf/T = get_turf(src) + + if(!T) + return + + if(!length(SSmobs.clients_by_zlevel[T.z])) // It's fine to use .len here but doesn't compile on 511 + toggle_ai(AI_Z_OFF) + return + + var/cheap_search = isturf(T) && !is_station_level(T.z) + if(cheap_search) + tlist = ListTargetsLazy(T.z) + else + tlist = ListTargets() + + if(AIStatus == AI_IDLE && FindTarget(tlist, 1)) + if(cheap_search) //Try again with full effort + FindTarget() + toggle_ai(AI_ON) + +/mob/living/simple_animal/hostile/proc/ListTargetsLazy(var/_Z)//Step 1, find out what we can see + var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/spacepod)) + . = list() + for(var/I in SSmobs.clients_by_zlevel[_Z]) + var/mob/M = I + if(get_dist(M, src) < vision_range) + if(isturf(M.loc)) + . += M + else if(M.loc.type in hostile_machines) + . += M.loc diff --git a/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm b/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm index 79d6c42cb11..934e1312606 100644 --- a/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm +++ b/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm @@ -42,4 +42,4 @@ if(prob(15) && iscarbon(target)) var/mob/living/carbon/C = target C.Weaken(3) - C.visible_message("\the [src] knocks down \the [C]!") \ No newline at end of file + C.visible_message("\the [src] knocks down \the [C]!") diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm index 986d503c077..70585b818f5 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm @@ -290,4 +290,4 @@ Difficulty: Medium if(. && prob(12)) INVOKE_ASYNC(src, .proc/dash) -#undef MINER_DASH_RANGE \ No newline at end of file +#undef MINER_DASH_RANGE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index 66799ffc6c6..9fa5abe4695 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -548,4 +548,4 @@ Difficulty: Hard return /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/try_bloodattack() - return \ No newline at end of file + return diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index 8b6d6fed947..e810222e1c4 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -669,4 +669,4 @@ obj/effect/temp_visual/fireball ..(targets, user, 3) /mob/living/simple_animal/hostile/megafauna/dragon/space_dragon/AltClickOn(atom/movable/A) - return \ No newline at end of file + return diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 088d8631e77..24ee584b378 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -737,4 +737,4 @@ Difficulty: Hard icon_state = null gpstag = "Zealous Signal" desc = "Heed its words." - invisibility = 100 \ No newline at end of file + invisibility = 100 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index 81c96135b0e..b3618efff0a 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -158,4 +158,4 @@ /datum/action/innate/megafauna_attack/Activate() M.chosen_attack = chosen_attack_num - to_chat(M, chosen_message) \ No newline at end of file + to_chat(M, chosen_message) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm index f31b6a8619b..696c692a5dc 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm @@ -285,4 +285,4 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa name = "swarmer catwalk" desc = "A catwalk-like mesh, produced by swarmers to allow them to navigate hostile terrain." icon = 'icons/obj/smooth_structures/swarmer_catwalk.dmi' - icon_state = "swarmer_catwalk" \ No newline at end of file + icon_state = "swarmer_catwalk" diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index 761f44d11bd..53f384aee0c 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -1,283 +1,283 @@ -/mob/living/simple_animal/hostile/mimic - name = "crate" - desc = "A rectangular steel crate." - icon = 'icons/obj/crates.dmi' - icon_state = "crate" - icon_living = "crate" - - response_help = "touches the" - response_disarm = "pushes the" - response_harm = "hits the" - speed = 0 - maxHealth = 250 - health = 250 - - harm_intent_damage = 5 - melee_damage_lower = 8 - melee_damage_upper = 12 - attacktext = "attacks" - attack_sound = 'sound/weapons/bite.ogg' - emote_taunt = list("growls") - speak_emote = list("creaks") - taunt_chance = 30 - - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - - faction = list("mimic") - move_to_delay = 9 - - var/is_electronic = 0 - gold_core_spawnable = HOSTILE_SPAWN - del_on_death = 1 - -/mob/living/simple_animal/hostile/mimic/emp_act(severity) - if(is_electronic) - switch(severity) - if(1) - death() - if(2) - adjustBruteLoss(50) - ..(severity) - -// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies. -/mob/living/simple_animal/hostile/mimic/crate - attacktext = "bites" - stop_automated_movement = 1 - wander = 0 - var/attempt_open = 0 - -// Pickup loot -/mob/living/simple_animal/hostile/mimic/crate/Initialize() - ..() - for(var/obj/item/I in loc) - I.loc = src - -/mob/living/simple_animal/hostile/mimic/crate/DestroyPathToTarget() - ..() - if(prob(90)) - icon_state = "[initial(icon_state)]open" - else - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/mimic/crate/ListTargets() - if(attempt_open) - return ..() - return ..(1) - -/mob/living/simple_animal/hostile/mimic/crate/FindTarget() - . = ..() - if(.) - trigger() - -/mob/living/simple_animal/hostile/mimic/crate/AttackingTarget() - . = ..() - if(.) - icon_state = initial(icon_state) - if(prob(15) && iscarbon(target)) - var/mob/living/carbon/C = target - C.Weaken(2) - C.visible_message("\The [src] knocks down \the [C]!", "\The [src] knocks you down!") - -/mob/living/simple_animal/hostile/mimic/crate/proc/trigger() - if(!attempt_open) - visible_message("[src] starts to move!") - attempt_open = 1 - -/mob/living/simple_animal/hostile/mimic/crate/adjustHealth(amount, updating_health = TRUE) - trigger() - . = ..() - -/mob/living/simple_animal/hostile/mimic/crate/LoseTarget() - ..() - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/mimic/crate/death(gibbed) - if(can_die()) - var/obj/structure/closet/crate/C = new(get_turf(src)) - // Put loot in crate - for(var/obj/O in src) - O.forceMove(C) - // due to `del_on_death` - return ..() - -var/global/list/protected_objects = list(/obj/structure/table, /obj/structure/cable, /obj/structure/window) - -/mob/living/simple_animal/hostile/mimic/copy - health = 100 - maxHealth = 100 - var/mob/living/creator = null // the creator - var/destroy_objects = 0 - var/knockdown_people = 0 - var/image/googly_eyes = null - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/hostile/mimic/copy/New(loc, obj/copy, mob/living/creator, destroy_original = 0) - ..(loc) - CopyObject(copy, creator, destroy_original) - -/mob/living/simple_animal/hostile/mimic/copy/Life() - ..() - if(!target && !ckey) //Objects eventually revert to normal if no one is around to terrorize - adjustBruteLoss(1) - for(var/mob/living/M in contents) //a fix for animated statues from the flesh to stone spell - death() - -/mob/living/simple_animal/hostile/mimic/copy/death(gibbed) - if(can_die()) - for(var/atom/movable/M in src) - M.loc = get_turf(src) - // due to `del_on_death` - return ..() - -/mob/living/simple_animal/hostile/mimic/copy/ListTargets() - . = ..() - return . - creator - -/mob/living/simple_animal/hostile/mimic/copy/proc/ChangeOwner(var/mob/owner) - if(owner != creator) - LoseTarget() - creator = owner - faction |= "\ref[owner]" - -/mob/living/simple_animal/hostile/mimic/copy/proc/CheckObject(var/obj/O) - if((istype(O, /obj/item) || istype(O, /obj/structure)) && !is_type_in_list(O, protected_objects)) - return 1 - return 0 - -/mob/living/simple_animal/hostile/mimic/copy/proc/CopyObject(var/obj/O, var/mob/living/user, var/destroy_original = 0) - if(destroy_original || CheckObject(O)) - O.loc = src - name = O.name - desc = O.desc - icon = O.icon - icon_state = O.icon_state - icon_living = icon_state - overlays = O.overlays - googly_eyes = image('icons/mob/mob.dmi',"googly_eyes") - overlays += googly_eyes - if(istype(O, /obj/structure) || istype(O, /obj/machinery)) - health = (anchored * 50) + 50 - destroy_objects = 1 - if(O.density && O.anchored) - knockdown_people = 1 - melee_damage_lower *= 2 - melee_damage_upper *= 2 - if(istype(O, /obj/machinery)) - is_electronic = 1 - else if(istype(O, /obj/item)) - var/obj/item/I = O - health = 15 * I.w_class - melee_damage_lower = 2 + I.force - melee_damage_upper = 2 + I.force - move_to_delay = 2 * I.w_class + 1 - maxHealth = health - if(user) - creator = user - faction += "\ref[creator]" // very unique - if(destroy_original) - qdel(O) - return 1 - -/mob/living/simple_animal/hostile/mimic/copy/DestroySurroundings() - if(destroy_objects) - ..() - -/mob/living/simple_animal/hostile/mimic/copy/AttackingTarget() - . = ..() - if(knockdown_people && . && prob(15) && iscarbon(target)) - var/mob/living/carbon/C = target - C.Weaken(2) - C.visible_message("\The [src] knocks down \the [C]!", "\The [src] knocks you down!") - -/mob/living/simple_animal/hostile/mimic/copy/Aggro() - ..() - googly_eyes.dir = get_dir(src,target) - -/mob/living/simple_animal/hostile/mimic/copy/machine - speak = list("HUMANS ARE IMPERFECT!", "YOU SHALL BE ASSIMILATED!", "YOU ARE HARMING YOURSELF", "You have been deemed hazardous. Will you comply?", \ - "My logic is undeniable.", "One of us.", "FLESH IS WEAK", "THIS ISN'T WAR, THIS IS EXTERMINATION!") - speak_chance = 15 - -/mob/living/simple_animal/hostile/mimic/copy/machine/CanAttack(var/atom/the_target) - if(the_target == creator) // Don't attack our creator AI. - return 0 - if(isrobot(the_target)) - var/mob/living/silicon/robot/R = the_target - if(R.connected_ai == creator) // Only attack robots that aren't synced to our creator AI. - return 0 - return ..() - -/mob/living/simple_animal/hostile/mimic/copy/ranged - var/obj/item/gun/TrueGun = null - var/obj/item/gun/magic/Zapstick - var/obj/item/gun/projectile/Pewgun - var/obj/item/gun/energy/Zapgun - -/mob/living/simple_animal/hostile/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0) - if(..()) - emote_see = list("aims menacingly") - obj_damage = 0 - environment_smash = 0 //needed? seems weird for them to do so - ranged = 1 - retreat_distance = 1 //just enough to shoot - minimum_distance = 6 - var/obj/item/gun/G = O - melee_damage_upper = G.force - melee_damage_lower = G.force - max(0, (G.force / 2)) - move_to_delay = 2 * G.w_class + 1 - projectilesound = G.fire_sound - TrueGun = G - if(istype(G, /obj/item/gun/magic)) - Zapstick = G - var/obj/item/ammo_casing/magic/M = Zapstick.ammo_type - projectiletype = initial(M.projectile_type) - if(istype(G, /obj/item/gun/projectile)) - Pewgun = G - var/obj/item/ammo_box/magazine/M = Pewgun.mag_type - casingtype = initial(M.ammo_type) - if(istype(G, /obj/item/gun/energy)) - Zapgun = G - var/selectfiresetting = Zapgun.select - var/obj/item/ammo_casing/energy/E = Zapgun.ammo_type[selectfiresetting] - projectiletype = initial(E.projectile_type) - -/mob/living/simple_animal/hostile/mimic/copy/ranged/OpenFire(the_target) - if(Zapgun) - if(Zapgun.cell) - var/obj/item/ammo_casing/energy/shot = Zapgun.ammo_type[Zapgun.select] - if(Zapgun.cell.charge >= shot.e_cost) - Zapgun.cell.use(shot.e_cost) - Zapgun.update_icon() - ..() - else if(Zapstick) - if(Zapstick.charges) - Zapstick.charges-- - Zapstick.update_icon() - ..() - else if(Pewgun) - if(Pewgun.chambered) - if(Pewgun.chambered.BB) - qdel(Pewgun.chambered.BB) - Pewgun.chambered.BB = null //because qdel takes too long, ensures icon update - Pewgun.chambered.update_icon() - ..() - else - visible_message("The [src] clears a jam!") - Pewgun.chambered.loc = loc //rip revolver immersions, blame shotgun snowflake procs - Pewgun.chambered = null - if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) - Pewgun.chambered = Pewgun.magazine.get_round(0) - Pewgun.chambered.loc = Pewgun - Pewgun.update_icon() - else if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) //only true for pumpguns i think - Pewgun.chambered = Pewgun.magazine.get_round(0) - Pewgun.chambered.loc = Pewgun - visible_message("The [src] cocks itself!") - else - ranged = 0 //BANZAIIII - retreat_distance = 0 - minimum_distance = 1 - return - icon_state = TrueGun.icon_state - icon_living = TrueGun.icon_state +/mob/living/simple_animal/hostile/mimic + name = "crate" + desc = "A rectangular steel crate." + icon = 'icons/obj/crates.dmi' + icon_state = "crate" + icon_living = "crate" + + response_help = "touches the" + response_disarm = "pushes the" + response_harm = "hits the" + speed = 0 + maxHealth = 250 + health = 250 + + harm_intent_damage = 5 + melee_damage_lower = 8 + melee_damage_upper = 12 + attacktext = "attacks" + attack_sound = 'sound/weapons/bite.ogg' + emote_taunt = list("growls") + speak_emote = list("creaks") + taunt_chance = 30 + + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + + faction = list("mimic") + move_to_delay = 9 + + var/is_electronic = 0 + gold_core_spawnable = HOSTILE_SPAWN + del_on_death = 1 + +/mob/living/simple_animal/hostile/mimic/emp_act(severity) + if(is_electronic) + switch(severity) + if(1) + death() + if(2) + adjustBruteLoss(50) + ..(severity) + +// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies. +/mob/living/simple_animal/hostile/mimic/crate + attacktext = "bites" + stop_automated_movement = 1 + wander = 0 + var/attempt_open = 0 + +// Pickup loot +/mob/living/simple_animal/hostile/mimic/crate/Initialize() + ..() + for(var/obj/item/I in loc) + I.loc = src + +/mob/living/simple_animal/hostile/mimic/crate/DestroyPathToTarget() + ..() + if(prob(90)) + icon_state = "[initial(icon_state)]open" + else + icon_state = initial(icon_state) + +/mob/living/simple_animal/hostile/mimic/crate/ListTargets() + if(attempt_open) + return ..() + return ..(1) + +/mob/living/simple_animal/hostile/mimic/crate/FindTarget() + . = ..() + if(.) + trigger() + +/mob/living/simple_animal/hostile/mimic/crate/AttackingTarget() + . = ..() + if(.) + icon_state = initial(icon_state) + if(prob(15) && iscarbon(target)) + var/mob/living/carbon/C = target + C.Weaken(2) + C.visible_message("\The [src] knocks down \the [C]!", "\The [src] knocks you down!") + +/mob/living/simple_animal/hostile/mimic/crate/proc/trigger() + if(!attempt_open) + visible_message("[src] starts to move!") + attempt_open = 1 + +/mob/living/simple_animal/hostile/mimic/crate/adjustHealth(amount, updating_health = TRUE) + trigger() + . = ..() + +/mob/living/simple_animal/hostile/mimic/crate/LoseTarget() + ..() + icon_state = initial(icon_state) + +/mob/living/simple_animal/hostile/mimic/crate/death(gibbed) + if(can_die()) + var/obj/structure/closet/crate/C = new(get_turf(src)) + // Put loot in crate + for(var/obj/O in src) + O.forceMove(C) + // due to `del_on_death` + return ..() + +var/global/list/protected_objects = list(/obj/structure/table, /obj/structure/cable, /obj/structure/window) + +/mob/living/simple_animal/hostile/mimic/copy + health = 100 + maxHealth = 100 + var/mob/living/creator = null // the creator + var/destroy_objects = 0 + var/knockdown_people = 0 + var/image/googly_eyes = null + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/hostile/mimic/copy/New(loc, obj/copy, mob/living/creator, destroy_original = 0) + ..(loc) + CopyObject(copy, creator, destroy_original) + +/mob/living/simple_animal/hostile/mimic/copy/Life() + ..() + if(!target && !ckey) //Objects eventually revert to normal if no one is around to terrorize + adjustBruteLoss(1) + for(var/mob/living/M in contents) //a fix for animated statues from the flesh to stone spell + death() + +/mob/living/simple_animal/hostile/mimic/copy/death(gibbed) + if(can_die()) + for(var/atom/movable/M in src) + M.loc = get_turf(src) + // due to `del_on_death` + return ..() + +/mob/living/simple_animal/hostile/mimic/copy/ListTargets() + . = ..() + return . - creator + +/mob/living/simple_animal/hostile/mimic/copy/proc/ChangeOwner(var/mob/owner) + if(owner != creator) + LoseTarget() + creator = owner + faction |= "\ref[owner]" + +/mob/living/simple_animal/hostile/mimic/copy/proc/CheckObject(var/obj/O) + if((istype(O, /obj/item) || istype(O, /obj/structure)) && !is_type_in_list(O, protected_objects)) + return 1 + return 0 + +/mob/living/simple_animal/hostile/mimic/copy/proc/CopyObject(var/obj/O, var/mob/living/user, var/destroy_original = 0) + if(destroy_original || CheckObject(O)) + O.loc = src + name = O.name + desc = O.desc + icon = O.icon + icon_state = O.icon_state + icon_living = icon_state + overlays = O.overlays + googly_eyes = image('icons/mob/mob.dmi',"googly_eyes") + overlays += googly_eyes + if(istype(O, /obj/structure) || istype(O, /obj/machinery)) + health = (anchored * 50) + 50 + destroy_objects = 1 + if(O.density && O.anchored) + knockdown_people = 1 + melee_damage_lower *= 2 + melee_damage_upper *= 2 + if(istype(O, /obj/machinery)) + is_electronic = 1 + else if(istype(O, /obj/item)) + var/obj/item/I = O + health = 15 * I.w_class + melee_damage_lower = 2 + I.force + melee_damage_upper = 2 + I.force + move_to_delay = 2 * I.w_class + 1 + maxHealth = health + if(user) + creator = user + faction += "\ref[creator]" // very unique + if(destroy_original) + qdel(O) + return 1 + +/mob/living/simple_animal/hostile/mimic/copy/DestroySurroundings() + if(destroy_objects) + ..() + +/mob/living/simple_animal/hostile/mimic/copy/AttackingTarget() + . = ..() + if(knockdown_people && . && prob(15) && iscarbon(target)) + var/mob/living/carbon/C = target + C.Weaken(2) + C.visible_message("\The [src] knocks down \the [C]!", "\The [src] knocks you down!") + +/mob/living/simple_animal/hostile/mimic/copy/Aggro() + ..() + googly_eyes.dir = get_dir(src,target) + +/mob/living/simple_animal/hostile/mimic/copy/machine + speak = list("HUMANS ARE IMPERFECT!", "YOU SHALL BE ASSIMILATED!", "YOU ARE HARMING YOURSELF", "You have been deemed hazardous. Will you comply?", \ + "My logic is undeniable.", "One of us.", "FLESH IS WEAK", "THIS ISN'T WAR, THIS IS EXTERMINATION!") + speak_chance = 15 + +/mob/living/simple_animal/hostile/mimic/copy/machine/CanAttack(var/atom/the_target) + if(the_target == creator) // Don't attack our creator AI. + return 0 + if(isrobot(the_target)) + var/mob/living/silicon/robot/R = the_target + if(R.connected_ai == creator) // Only attack robots that aren't synced to our creator AI. + return 0 + return ..() + +/mob/living/simple_animal/hostile/mimic/copy/ranged + var/obj/item/gun/TrueGun = null + var/obj/item/gun/magic/Zapstick + var/obj/item/gun/projectile/Pewgun + var/obj/item/gun/energy/Zapgun + +/mob/living/simple_animal/hostile/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0) + if(..()) + emote_see = list("aims menacingly") + obj_damage = 0 + environment_smash = 0 //needed? seems weird for them to do so + ranged = 1 + retreat_distance = 1 //just enough to shoot + minimum_distance = 6 + var/obj/item/gun/G = O + melee_damage_upper = G.force + melee_damage_lower = G.force - max(0, (G.force / 2)) + move_to_delay = 2 * G.w_class + 1 + projectilesound = G.fire_sound + TrueGun = G + if(istype(G, /obj/item/gun/magic)) + Zapstick = G + var/obj/item/ammo_casing/magic/M = Zapstick.ammo_type + projectiletype = initial(M.projectile_type) + if(istype(G, /obj/item/gun/projectile)) + Pewgun = G + var/obj/item/ammo_box/magazine/M = Pewgun.mag_type + casingtype = initial(M.ammo_type) + if(istype(G, /obj/item/gun/energy)) + Zapgun = G + var/selectfiresetting = Zapgun.select + var/obj/item/ammo_casing/energy/E = Zapgun.ammo_type[selectfiresetting] + projectiletype = initial(E.projectile_type) + +/mob/living/simple_animal/hostile/mimic/copy/ranged/OpenFire(the_target) + if(Zapgun) + if(Zapgun.cell) + var/obj/item/ammo_casing/energy/shot = Zapgun.ammo_type[Zapgun.select] + if(Zapgun.cell.charge >= shot.e_cost) + Zapgun.cell.use(shot.e_cost) + Zapgun.update_icon() + ..() + else if(Zapstick) + if(Zapstick.charges) + Zapstick.charges-- + Zapstick.update_icon() + ..() + else if(Pewgun) + if(Pewgun.chambered) + if(Pewgun.chambered.BB) + qdel(Pewgun.chambered.BB) + Pewgun.chambered.BB = null //because qdel takes too long, ensures icon update + Pewgun.chambered.update_icon() + ..() + else + visible_message("The [src] clears a jam!") + Pewgun.chambered.loc = loc //rip revolver immersions, blame shotgun snowflake procs + Pewgun.chambered = null + if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) + Pewgun.chambered = Pewgun.magazine.get_round(0) + Pewgun.chambered.loc = Pewgun + Pewgun.update_icon() + else if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) //only true for pumpguns i think + Pewgun.chambered = Pewgun.magazine.get_round(0) + Pewgun.chambered.loc = Pewgun + visible_message("The [src] cocks itself!") + else + ranged = 0 //BANZAIIII + retreat_distance = 0 + minimum_distance = 1 + return + icon_state = TrueGun.icon_state + icon_living = TrueGun.icon_state diff --git a/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm index ef6e3c540f6..74eaf55b57c 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm @@ -149,4 +149,4 @@ L.apply_status_effect(/datum/status_effect/freon/watcher) /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/tendril - fromtendril = TRUE \ No newline at end of file + fromtendril = TRUE diff --git a/code/modules/mob/living/simple_animal/hostile/mining/gutlunch.dm b/code/modules/mob/living/simple_animal/hostile/mining/gutlunch.dm index 463b6e61e48..2f896f4adb0 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/gutlunch.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/gutlunch.dm @@ -163,4 +163,4 @@ L.faction = faction.Copy() L.setDir(dir) visible_message("[src] grows up into [L].") - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/mob/living/simple_animal/hostile/mining/mining.dm b/code/modules/mob/living/simple_animal/hostile/mining/mining.dm index e1e86536f19..7c7200e8588 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/mining.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/mining.dm @@ -63,4 +63,4 @@ ..(gibbed) /mob/living/simple_animal/hostile/asteroid/proc/spawn_crusher_loot() - butcher_results[crusher_loot] = 1 \ No newline at end of file + butcher_results[crusher_loot] = 1 diff --git a/code/modules/mob/living/simple_animal/hostile/netherworld.dm b/code/modules/mob/living/simple_animal/hostile/netherworld.dm index 92c01d7d72b..e8ce4027b4e 100644 --- a/code/modules/mob/living/simple_animal/hostile/netherworld.dm +++ b/code/modules/mob/living/simple_animal/hostile/netherworld.dm @@ -108,4 +108,4 @@ blank.name = "[M]" blank.desc = "It's [M], but [M.p_their()] flesh has an ashy texture, and [M.p_their()] face is featureless save an eerie smile." visible_message("[M] reemerges from the link!") - qdel(M) \ No newline at end of file + qdel(M) diff --git a/code/modules/mob/living/simple_animal/hostile/pirate.dm b/code/modules/mob/living/simple_animal/hostile/pirate.dm index 4e3f71b30b1..d8d2d55a8ee 100644 --- a/code/modules/mob/living/simple_animal/hostile/pirate.dm +++ b/code/modules/mob/living/simple_animal/hostile/pirate.dm @@ -1,45 +1,45 @@ -/mob/living/simple_animal/hostile/pirate - name = "Pirate" - desc = "Does what he wants cause a pirate is free." - icon = 'icons/mob/simple_human.dmi' - icon_state = "piratemelee" - icon_living = "piratemelee" - icon_dead = "piratemelee_dead" // Does not actually exist. del_on_death. - speak_chance = 0 - turns_per_move = 5 - response_help = "pushes the" - response_disarm = "shoves" - response_harm = "hits the" - speed = 0 - maxHealth = 100 - health = 100 - - harm_intent_damage = 5 - obj_damage = 60 - melee_damage_lower = 30 - melee_damage_upper = 30 - attacktext = "slashes" - attack_sound = 'sound/weapons/bladeslice.ogg' - - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 15 - speak_emote = list("yarrs") - loot = list(/obj/effect/mob_spawn/human/corpse/pirate, - /obj/item/melee/energy/sword/pirate) - del_on_death = 1 - faction = list("pirate") - sentience_type = SENTIENCE_OTHER - -/mob/living/simple_animal/hostile/pirate/ranged - name = "Pirate Gunner" - icon_state = "pirateranged" - icon_living = "pirateranged" - icon_dead = "piratemelee_dead" // Does not actually exist. del_on_death. - projectilesound = 'sound/weapons/laser.ogg' - ranged = 1 - rapid = 2 - retreat_distance = 5 - minimum_distance = 5 - projectiletype = /obj/item/projectile/beam - loot = list(/obj/effect/mob_spawn/human/corpse/pirate/ranged, - /obj/item/gun/energy/laser) \ No newline at end of file +/mob/living/simple_animal/hostile/pirate + name = "Pirate" + desc = "Does what he wants cause a pirate is free." + icon = 'icons/mob/simple_human.dmi' + icon_state = "piratemelee" + icon_living = "piratemelee" + icon_dead = "piratemelee_dead" // Does not actually exist. del_on_death. + speak_chance = 0 + turns_per_move = 5 + response_help = "pushes the" + response_disarm = "shoves" + response_harm = "hits the" + speed = 0 + maxHealth = 100 + health = 100 + + harm_intent_damage = 5 + obj_damage = 60 + melee_damage_lower = 30 + melee_damage_upper = 30 + attacktext = "slashes" + attack_sound = 'sound/weapons/bladeslice.ogg' + + atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + unsuitable_atmos_damage = 15 + speak_emote = list("yarrs") + loot = list(/obj/effect/mob_spawn/human/corpse/pirate, + /obj/item/melee/energy/sword/pirate) + del_on_death = 1 + faction = list("pirate") + sentience_type = SENTIENCE_OTHER + +/mob/living/simple_animal/hostile/pirate/ranged + name = "Pirate Gunner" + icon_state = "pirateranged" + icon_living = "pirateranged" + icon_dead = "piratemelee_dead" // Does not actually exist. del_on_death. + projectilesound = 'sound/weapons/laser.ogg' + ranged = 1 + rapid = 2 + retreat_distance = 5 + minimum_distance = 5 + projectiletype = /obj/item/projectile/beam + loot = list(/obj/effect/mob_spawn/human/corpse/pirate/ranged, + /obj/item/gun/energy/laser) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm index 5a278a65b24..f58f7fcfda2 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm @@ -1,53 +1,53 @@ -/mob/living/simple_animal/hostile/retaliate/clown - name = "Clown" - desc = "A denizen of clown planet" - icon = 'icons/mob/simple_human.dmi' - icon_state = "clown" - icon_living = "clown" - icon_dead = "clown_dead" - icon_gib = "clown_gib" - speak_chance = 0 - turns_per_move = 5 - response_help = "pokes the" - response_disarm = "gently pushes aside the" - response_harm = "hits the" - speak = list("HONK", "Honk!", "Welcome to clown planet!") - emote_see = list("honks") - speak_chance = 1 - a_intent = INTENT_HARM - maxHealth = 75 - health = 75 - speed = 0 - harm_intent_damage = 8 - melee_damage_lower = 10 - melee_damage_upper = 10 - attacktext = "attacks" - attack_sound = 'sound/items/bikehorn.ogg' - obj_damage = 0 - environment_smash = 0 - minbodytemp = 270 - maxbodytemp = 370 - heat_damage_per_tick = 15 //amount of damage applied if animal's body temperature is higher than maxbodytemp - cold_damage_per_tick = 10 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp - unsuitable_atmos_damage = 10 - - -/mob/living/simple_animal/hostile/retaliate/clown/goblin - icon = 'icons/mob/animal.dmi' - name = "clown goblin" - desc = "A tiny walking mask and clown shoes. You want to honk his nose!" - icon_state = "clowngoblin" - icon_living = "clowngoblin" - icon_dead = null - response_help = "honks the" - speak = list("Honk!") - speak_emote = list("sqeaks") - emote_see = list("honks") - maxHealth = 100 - health = 100 - - speed = -1 - turns_per_move = 1 - - del_on_death = TRUE - loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/item/clothing/shoes/clown_shoes) +/mob/living/simple_animal/hostile/retaliate/clown + name = "Clown" + desc = "A denizen of clown planet" + icon = 'icons/mob/simple_human.dmi' + icon_state = "clown" + icon_living = "clown" + icon_dead = "clown_dead" + icon_gib = "clown_gib" + speak_chance = 0 + turns_per_move = 5 + response_help = "pokes the" + response_disarm = "gently pushes aside the" + response_harm = "hits the" + speak = list("HONK", "Honk!", "Welcome to clown planet!") + emote_see = list("honks") + speak_chance = 1 + a_intent = INTENT_HARM + maxHealth = 75 + health = 75 + speed = 0 + harm_intent_damage = 8 + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "attacks" + attack_sound = 'sound/items/bikehorn.ogg' + obj_damage = 0 + environment_smash = 0 + minbodytemp = 270 + maxbodytemp = 370 + heat_damage_per_tick = 15 //amount of damage applied if animal's body temperature is higher than maxbodytemp + cold_damage_per_tick = 10 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp + unsuitable_atmos_damage = 10 + + +/mob/living/simple_animal/hostile/retaliate/clown/goblin + icon = 'icons/mob/animal.dmi' + name = "clown goblin" + desc = "A tiny walking mask and clown shoes. You want to honk his nose!" + icon_state = "clowngoblin" + icon_living = "clowngoblin" + icon_dead = null + response_help = "honks the" + speak = list("Honk!") + speak_emote = list("sqeaks") + emote_see = list("honks") + maxHealth = 100 + health = 100 + + speed = -1 + turns_per_move = 1 + + del_on_death = TRUE + loot = list(/obj/item/clothing/mask/gas/clown_hat, /obj/item/clothing/shoes/clown_shoes) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm index d3bf35671e2..7afaf3679c2 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm @@ -1,56 +1,56 @@ -/mob/living/simple_animal/hostile/retaliate - var/list/enemies = list() - -/mob/living/simple_animal/hostile/retaliate/Found(atom/A) - if(isliving(A)) - var/mob/living/L = A - if(!L.stat) - return L - else - enemies -= L - else if(ismecha(A)) - var/obj/mecha/M = A - if(M.occupant) - return A - else if(isspacepod(A)) - var/obj/spacepod/S = A - if(S.pilot) - return A - -/mob/living/simple_animal/hostile/retaliate/ListTargets() - if(!enemies.len) - return list() - var/list/see = ..() - see &= enemies // Remove all entries that aren't in enemies - return see - -/mob/living/simple_animal/hostile/retaliate/proc/Retaliate() - var/list/around = view(src, vision_range) - - for(var/atom/movable/A in around) - if(A == src) - continue - if(isliving(A)) - var/mob/living/M = A - if(faction_check_mob(M) && attack_same || !faction_check_mob(M)) - enemies |= M - else if(ismecha(A)) - var/obj/mecha/M = A - if(M.occupant) - enemies |= M - enemies |= M.occupant - else if(isspacepod(A)) - var/obj/spacepod/S = A - if(S.pilot) - enemies |= S - enemies |= S.pilot - - for(var/mob/living/simple_animal/hostile/retaliate/H in around) - if(faction_check_mob(H) && !attack_same && !H.attack_same) - H.enemies |= enemies - return 0 - -/mob/living/simple_animal/hostile/retaliate/adjustHealth(amount, updating_health = TRUE) - . = ..() - if(amount > 0 && stat == CONSCIOUS) - Retaliate() \ No newline at end of file +/mob/living/simple_animal/hostile/retaliate + var/list/enemies = list() + +/mob/living/simple_animal/hostile/retaliate/Found(atom/A) + if(isliving(A)) + var/mob/living/L = A + if(!L.stat) + return L + else + enemies -= L + else if(ismecha(A)) + var/obj/mecha/M = A + if(M.occupant) + return A + else if(isspacepod(A)) + var/obj/spacepod/S = A + if(S.pilot) + return A + +/mob/living/simple_animal/hostile/retaliate/ListTargets() + if(!enemies.len) + return list() + var/list/see = ..() + see &= enemies // Remove all entries that aren't in enemies + return see + +/mob/living/simple_animal/hostile/retaliate/proc/Retaliate() + var/list/around = view(src, vision_range) + + for(var/atom/movable/A in around) + if(A == src) + continue + if(isliving(A)) + var/mob/living/M = A + if(faction_check_mob(M) && attack_same || !faction_check_mob(M)) + enemies |= M + else if(ismecha(A)) + var/obj/mecha/M = A + if(M.occupant) + enemies |= M + enemies |= M.occupant + else if(isspacepod(A)) + var/obj/spacepod/S = A + if(S.pilot) + enemies |= S + enemies |= S.pilot + + for(var/mob/living/simple_animal/hostile/retaliate/H in around) + if(faction_check_mob(H) && !attack_same && !H.attack_same) + H.enemies |= enemies + return 0 + +/mob/living/simple_animal/hostile/retaliate/adjustHealth(amount, updating_health = TRUE) + . = ..() + if(amount > 0 && stat == CONSCIOUS) + Retaliate() diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm index 6e05b699ea5..a3e32e9d35e 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/undead.dm @@ -116,4 +116,4 @@ faction = list("undead") loot = list(/obj/effect/decal/cleanable/blood/gibs) - del_on_death = 1 \ No newline at end of file + del_on_death = 1 diff --git a/code/modules/mob/living/simple_animal/hostile/russian.dm b/code/modules/mob/living/simple_animal/hostile/russian.dm index 37ae67cfef5..83c6fa67e1d 100644 --- a/code/modules/mob/living/simple_animal/hostile/russian.dm +++ b/code/modules/mob/living/simple_animal/hostile/russian.dm @@ -1,44 +1,44 @@ -/mob/living/simple_animal/hostile/russian - name = "Russian" - desc = "For the Motherland!" - icon = 'icons/mob/simple_human.dmi' - icon_state = "russianmelee" - icon_living = "russianmelee" - icon_dead = "russianmelee_dead" // Does not actually exist. del_on_death. - icon_gib = "russianmelee_gib" // Does not actually exist. del_on_death. - speak_chance = 0 - turns_per_move = 5 - response_help = "pokes the" - response_disarm = "shoves the" - response_harm = "hits the" - speed = 0 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 15 - melee_damage_upper = 15 - attacktext = "punches" - attack_sound = 'sound/weapons/punch1.ogg' - a_intent = INTENT_HARM - unsuitable_atmos_damage = 15 - faction = list("russian") - status_flags = CANPUSH - loot = list(/obj/effect/mob_spawn/human/corpse/russian, - /obj/item/kitchen/knife) - del_on_death = 1 - sentience_type = SENTIENCE_OTHER - -/mob/living/simple_animal/hostile/russian/ranged - icon_state = "russianranged" - icon_living = "russianranged" - ranged = 1 - retreat_distance = 5 - minimum_distance = 5 - projectilesound = 'sound/weapons/gunshots/gunshot.ogg' - casingtype = /obj/item/ammo_casing/a357 - loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, /obj/item/gun/projectile/revolver/mateba) - -/mob/living/simple_animal/hostile/russian/ranged/mosin - loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, - /obj/item/gun/projectile/shotgun/boltaction) - casingtype = /obj/item/ammo_casing/a762 +/mob/living/simple_animal/hostile/russian + name = "Russian" + desc = "For the Motherland!" + icon = 'icons/mob/simple_human.dmi' + icon_state = "russianmelee" + icon_living = "russianmelee" + icon_dead = "russianmelee_dead" // Does not actually exist. del_on_death. + icon_gib = "russianmelee_gib" // Does not actually exist. del_on_death. + speak_chance = 0 + turns_per_move = 5 + response_help = "pokes the" + response_disarm = "shoves the" + response_harm = "hits the" + speed = 0 + maxHealth = 100 + health = 100 + harm_intent_damage = 5 + melee_damage_lower = 15 + melee_damage_upper = 15 + attacktext = "punches" + attack_sound = 'sound/weapons/punch1.ogg' + a_intent = INTENT_HARM + unsuitable_atmos_damage = 15 + faction = list("russian") + status_flags = CANPUSH + loot = list(/obj/effect/mob_spawn/human/corpse/russian, + /obj/item/kitchen/knife) + del_on_death = 1 + sentience_type = SENTIENCE_OTHER + +/mob/living/simple_animal/hostile/russian/ranged + icon_state = "russianranged" + icon_living = "russianranged" + ranged = 1 + retreat_distance = 5 + minimum_distance = 5 + projectilesound = 'sound/weapons/gunshots/gunshot.ogg' + casingtype = /obj/item/ammo_casing/a357 + loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, /obj/item/gun/projectile/revolver/mateba) + +/mob/living/simple_animal/hostile/russian/ranged/mosin + loot = list(/obj/effect/mob_spawn/human/corpse/russian/ranged, + /obj/item/gun/projectile/shotgun/boltaction) + casingtype = /obj/item/ammo_casing/a762 diff --git a/code/modules/mob/living/simple_animal/hostile/skeleton.dm b/code/modules/mob/living/simple_animal/hostile/skeleton.dm index acfd9050c04..e0e71506bbe 100644 --- a/code/modules/mob/living/simple_animal/hostile/skeleton.dm +++ b/code/modules/mob/living/simple_animal/hostile/skeleton.dm @@ -46,4 +46,4 @@ loot = list(/obj/effect/decal/remains/human, /obj/item/twohanded/spear, /obj/item/clothing/shoes/winterboots, - /obj/item/clothing/suit/hooded/wintercoat) \ No newline at end of file + /obj/item/clothing/suit/hooded/wintercoat) diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm index 513ab39b180..f3389108498 100644 --- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm +++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm @@ -1,367 +1,367 @@ -/mob/living/simple_animal/hostile/syndicate - name = "Syndicate Operative" - desc = "Death to Nanotrasen." - icon = 'icons/mob/simple_human.dmi' - icon_state = "syndicate" - icon_living = "syndicate" - icon_dead = "syndicate_dead" // Does not actually exist. del_on_death. - icon_gib = "syndicate_gib" // Does not actually exist. del_on_death. - speak_chance = 0 - turns_per_move = 5 - response_help = "pokes the" - response_disarm = "shoves the" - response_harm = "hits the" - speed = 0 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 10 - melee_damage_upper = 10 - attacktext = "punches" - attack_sound = 'sound/weapons/punch1.ogg' - a_intent = INTENT_HARM - unsuitable_atmos_damage = 15 - faction = list("syndicate") - check_friendly_fire = 1 - status_flags = CANPUSH - loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier) - del_on_death = 1 - sentience_type = SENTIENCE_OTHER - -///////////////Sword and shield//////////// - -/mob/living/simple_animal/hostile/syndicate/melee - melee_damage_lower = 20 - melee_damage_upper = 25 - icon_state = "syndicate_sword" - icon_living = "syndicate_sword" - attacktext = "slashes" - attack_sound = 'sound/weapons/bladeslice.ogg' - armour_penetration = 28 - status_flags = 0 - loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier, /obj/item/melee/energy/sword/saber/red, /obj/item/shield/energy) - var/melee_block_chance = 20 - var/ranged_block_chance = 35 - -/mob/living/simple_animal/hostile/syndicate/melee/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src) - if(O.force) - if(prob(melee_block_chance)) - visible_message("[src] blocks the [O] with its shield! ") - else - var/damage = O.force - if(O.damtype == STAMINA) - damage = 0 - if(force_threshold && damage < force_threshold) - visible_message("[src] is unharmed by [O]!") - return - adjustHealth(damage) - visible_message("[src] has been attacked with the [O] by [user]. ") - playsound(loc, O.hitsound, 25, 1, -1) - else - to_chat(usr, "This weapon is ineffective, it does no damage.") - visible_message("[user] gently taps [src] with the [O]. ") - - -/mob/living/simple_animal/hostile/syndicate/melee/bullet_act(var/obj/item/projectile/Proj) - if(!Proj) - return - if(prob(ranged_block_chance)) - visible_message("[src] blocks [Proj] with its shield!") - else - if((Proj.damage_type == BRUTE || Proj.damage_type == BURN)) - adjustHealth(Proj.damage) - return 0 - -/mob/living/simple_animal/hostile/syndicate/melee/autogib - loot = list()//no loot, its gonna delete and gib. - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot - name = "Syndicate Operative" - force_threshold = 6 // Prevents people using punches to bypass eshield - robust_searching = 1 // Together with stat_attack, ensures dionae/etc that regen are killed properly - stat_attack = UNCONSCIOUS - universal_speak = 1 - icon_state = "syndicate_swordonly" - icon_living = "syndicate_swordonly" - melee_block_chance = 0 - ranged_block_chance = 0 - del_on_death = 1 - var/area/syndicate_depot/core/depotarea - var/raised_alert = FALSE - var/alert_on_death = FALSE - var/alert_on_timeout = TRUE - var/alert_on_spacing = TRUE - var/alert_on_shield_breach = FALSE - var/seen_enemy = FALSE - var/seen_enemy_name = null - var/seen_revived_enemy = FALSE - var/aggro_cycles = 0 - var/scan_cycles = 0 - var/shield_key = FALSE - var/turf/spawn_turf - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/New() - ..() - name = "[name] [pick(GLOB.last_names)]" - // Do not attempt to move this code to Initialize() or LateInitialize(). Doing so with other objects has caused bugs in the past, because assigning "depotarea" may not work there. - depotarea = areaMaster - spawn_turf = get_turf(src) - - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/ListTargetsLazy() - // The normal ListTargetsLazy ignores walls, which is very bad in the case of depot mobs. So we override it. - return ListTargets() - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/Aggro() - . = ..() - if(!istype(depotarea)) - return - if(target) - if(!seen_enemy) - seen_enemy = TRUE - if(!ranged) - playsound(loc, 'sound/weapons/saberon.ogg', 35, 1) - if(alert_on_shield_breach) - if(depotarea.shield_list.len) - raise_alert("[name] reports that [target] is trying to breach the armory shield!") - alert_on_shield_breach = FALSE - raised_alert = FALSE - alert_on_death = TRUE - if(isliving(target)) - var/mob/living/M = target - depotarea.list_add(M, depotarea.hostile_list) - if(M.mind && M.mind.special_role == SPECIAL_ROLE_TRAITOR) - depotarea.saw_double_agent(M) - depotarea.declare_started() - seen_enemy_name = target.name - if(istype(target, /obj/mecha)) - depotarea.saw_mech(target) - if(istype(target, /obj/spacepod)) - depotarea.saw_pod(target) - if(depotarea.list_includes(target, depotarea.dead_list)) - seen_revived_enemy = TRUE - raise_alert("[name] reports intruder [target] has returned from death!") - depotarea.list_remove(target, depotarea.dead_list) - if(!atoms_share_level(src, target) && prob(20)) - // This prevents someone from aggroing a depot mob, then hiding in a locker, perfectly safe, while the mob stands there getting killed by their friends. - LoseTarget() - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/handle_automated_action() - . = ..() - if(!.) - return - if(!istype(depotarea)) - return - if(seen_enemy) - aggro_cycles++ - if(alert_on_timeout && !raised_alert && aggro_cycles >= 60) - raise_alert("[name] has reported contact with hostile entity: [seen_enemy_name]") - if(scan_cycles >= 15) - scan_cycles = 0 - if(!atoms_share_level(src, spawn_turf)) - if(istype(loc, /obj/structure/closet)) - var/obj/structure/closet/O = loc - forceMove(get_turf(src)) - visible_message("[src] smashes their way out of [O]!") - qdel(O) - raise_alert("[src] reported being trapped in a locker.") - raised_alert = FALSE - return - if(alert_on_spacing) - raise_alert("[src] lost in space.") - death() - return - for(var/mob/living/body in hearers(vision_range, targets_from)) - if(body.stat != DEAD) - continue - if(depotarea.list_includes(body, depotarea.dead_list)) - continue - if(faction_check_mob(body)) - continue - say("Target [body]... terminated.") - depotarea.list_add(body, depotarea.dead_list) - pointed(body) - else - scan_cycles++ - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/AIShouldSleep(var/list/possible_targets) - FindTarget(possible_targets, 1) - return FALSE - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/proc/raise_alert(var/reason) - if(istype(depotarea) && (!raised_alert || seen_revived_enemy) && !depotarea.used_self_destruct) - raised_alert = TRUE - say("Intruder!") - depotarea.increase_alert(reason) - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/death() - if(!istype(depotarea)) - return ..() - if(alert_on_death) - if(seen_enemy_name) - raise_alert("[name] has died in combat with [seen_enemy_name].") - else - raise_alert("[name] has died.") - if(shield_key && depotarea) - depotarea.shields_key_check() - if(depotarea) - depotarea.list_remove(src, depotarea.guard_list) - new /obj/effect/gibspawner/human(get_turf(src)) - return ..() - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/CanPass(atom/movable/mover, turf/target, height=0) - if(isliving(mover)) - var/mob/living/blocker = mover - if(faction_check_mob(blocker)) - return 1 - return ..(mover, target, height) - - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/officer - name = "Syndicate Officer" - icon_state = "syndicate_sword" - icon_living = "syndicate_sword" - melee_block_chance = 20 - ranged_block_chance = 35 - alert_on_death = TRUE - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/officer/Initialize(mapload) - . = ..() - if(prob(50)) - // 50% chance of switching to ranged variant. - // Designed to counter players taking cover behind reinforced plasmasglass. - // Does almost no danage in melee, but decent damage at range, and its shots go through glass. - melee_damage_lower = 10 - melee_damage_upper = 10 - attacktext = "punches" - attack_sound = 'sound/weapons/punch1.ogg' - ranged = 1 - rapid = 3 - retreat_distance = 3 - minimum_distance = 3 - melee_block_chance = 0 - ranged_block_chance = 0 - icon_state = "syndicate_pistol" - icon_living = "syndicate_pistol" - projectiletype = /obj/item/projectile/beam/laser - projectilesound = 'sound/weapons/laser.ogg' - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/armory - name = "Syndicate Quartermaster" - icon_state = "syndicate_stormtrooper_sword" - icon_living = "syndicate_stormtrooper_sword" - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - maxHealth = 200 - health = 200 - melee_block_chance = 40 - alert_on_shield_breach = TRUE - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/armory/Initialize(mapload) - ..() - if(prob(50)) - // 50% chance of switching to extremely dangerous ranged variant - melee_damage_lower = 10 - melee_damage_upper = 10 - attacktext = "punches" - attack_sound = 'sound/weapons/punch1.ogg' - ranged = 1 - retreat_distance = 3 - minimum_distance = 3 - melee_block_chance = 0 - ranged_block_chance = 0 - icon_state = "syndicate_stormtrooper_shotgun" - icon_living = "syndicate_stormtrooper_shotgun" - projectiletype = /obj/item/projectile/bullet/sniper/penetrator // Ignores cover. - projectilesound = 'sound/weapons/gunshots/gunshot_sniper.ogg' - return INITIALIZE_HINT_LATELOAD - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/armory/LateInitialize() - if(istype(depotarea)) - var/list/key_candidates = list() - for(var/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/officer/O in GLOB.living_mob_list) - key_candidates += O - if(key_candidates.len) - var/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/officer/O = pick(key_candidates) - O.shield_key = TRUE - depotarea.shields_up() - - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/space - name = "Syndicate Backup" - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - icon_state = "syndicate_space_sword" - icon_living = "syndicate_space_sword" - speed = 1 - wander = 0 - alert_on_spacing = FALSE - -/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/space/Process_Spacemove(var/movement_dir = 0) - return TRUE - - - -/mob/living/simple_animal/hostile/syndicate/melee/space - name = "Syndicate Commando" - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - icon_state = "syndicate_space_sword" - icon_living = "syndicate_space_sword" - speed = 1 - loot = list(/obj/effect/mob_spawn/human/corpse/syndicatecommando, /obj/item/melee/energy/sword/saber/red, /obj/item/shield/energy) - -/mob/living/simple_animal/hostile/syndicate/melee/space/Process_Spacemove(var/movement_dir = 0) - return TRUE - - -/mob/living/simple_animal/hostile/syndicate/ranged - ranged = 1 - rapid = 2 - retreat_distance = 5 - minimum_distance = 5 - icon_state = "syndicate_smg" - icon_living = "syndicate_smg" - projectilesound = 'sound/weapons/gunshots/gunshot.ogg' - casingtype = /obj/item/ammo_casing/c45 - loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier, /obj/item/gun/projectile/automatic/c20r) - -/mob/living/simple_animal/hostile/syndicate/ranged/space - icon_state = "syndicate_space_smg" - icon_living = "syndicate_space_smg" - name = "Syndicate Commando" - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - speed = 1 - loot = list(/obj/effect/mob_spawn/human/corpse/syndicatecommando, /obj/item/gun/projectile/automatic/c20r) - -/mob/living/simple_animal/hostile/syndicate/ranged/space/Process_Spacemove(var/movement_dir = 0) - return TRUE - -/mob/living/simple_animal/hostile/syndicate/ranged/space/autogib - loot = list()//gonna gibe, no loot. - -/mob/living/simple_animal/hostile/viscerator - name = "viscerator" - desc = "A small, twin-bladed machine capable of inflicting very deadly lacerations." - icon = 'icons/mob/critter.dmi' - icon_state = "viscerator_attack" - icon_living = "viscerator_attack" - pass_flags = PASSTABLE - health = 15 - maxHealth = 15 - obj_damage = 0 - melee_damage_lower = 15 - melee_damage_upper = 15 - attacktext = "cuts" - attack_sound = 'sound/weapons/bladeslice.ogg' - faction = list("syndicate") - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - mob_size = MOB_SIZE_TINY - flying = 1 - gold_core_spawnable = HOSTILE_SPAWN - del_on_death = 1 - deathmessage = "is smashed into pieces!" \ No newline at end of file +/mob/living/simple_animal/hostile/syndicate + name = "Syndicate Operative" + desc = "Death to Nanotrasen." + icon = 'icons/mob/simple_human.dmi' + icon_state = "syndicate" + icon_living = "syndicate" + icon_dead = "syndicate_dead" // Does not actually exist. del_on_death. + icon_gib = "syndicate_gib" // Does not actually exist. del_on_death. + speak_chance = 0 + turns_per_move = 5 + response_help = "pokes the" + response_disarm = "shoves the" + response_harm = "hits the" + speed = 0 + maxHealth = 100 + health = 100 + harm_intent_damage = 5 + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "punches" + attack_sound = 'sound/weapons/punch1.ogg' + a_intent = INTENT_HARM + unsuitable_atmos_damage = 15 + faction = list("syndicate") + check_friendly_fire = 1 + status_flags = CANPUSH + loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier) + del_on_death = 1 + sentience_type = SENTIENCE_OTHER + +///////////////Sword and shield//////////// + +/mob/living/simple_animal/hostile/syndicate/melee + melee_damage_lower = 20 + melee_damage_upper = 25 + icon_state = "syndicate_sword" + icon_living = "syndicate_sword" + attacktext = "slashes" + attack_sound = 'sound/weapons/bladeslice.ogg' + armour_penetration = 28 + status_flags = 0 + loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier, /obj/item/melee/energy/sword/saber/red, /obj/item/shield/energy) + var/melee_block_chance = 20 + var/ranged_block_chance = 35 + +/mob/living/simple_animal/hostile/syndicate/melee/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src) + if(O.force) + if(prob(melee_block_chance)) + visible_message("[src] blocks the [O] with its shield! ") + else + var/damage = O.force + if(O.damtype == STAMINA) + damage = 0 + if(force_threshold && damage < force_threshold) + visible_message("[src] is unharmed by [O]!") + return + adjustHealth(damage) + visible_message("[src] has been attacked with the [O] by [user]. ") + playsound(loc, O.hitsound, 25, 1, -1) + else + to_chat(usr, "This weapon is ineffective, it does no damage.") + visible_message("[user] gently taps [src] with the [O]. ") + + +/mob/living/simple_animal/hostile/syndicate/melee/bullet_act(var/obj/item/projectile/Proj) + if(!Proj) + return + if(prob(ranged_block_chance)) + visible_message("[src] blocks [Proj] with its shield!") + else + if((Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + adjustHealth(Proj.damage) + return 0 + +/mob/living/simple_animal/hostile/syndicate/melee/autogib + loot = list()//no loot, its gonna delete and gib. + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot + name = "Syndicate Operative" + force_threshold = 6 // Prevents people using punches to bypass eshield + robust_searching = 1 // Together with stat_attack, ensures dionae/etc that regen are killed properly + stat_attack = UNCONSCIOUS + universal_speak = 1 + icon_state = "syndicate_swordonly" + icon_living = "syndicate_swordonly" + melee_block_chance = 0 + ranged_block_chance = 0 + del_on_death = 1 + var/area/syndicate_depot/core/depotarea + var/raised_alert = FALSE + var/alert_on_death = FALSE + var/alert_on_timeout = TRUE + var/alert_on_spacing = TRUE + var/alert_on_shield_breach = FALSE + var/seen_enemy = FALSE + var/seen_enemy_name = null + var/seen_revived_enemy = FALSE + var/aggro_cycles = 0 + var/scan_cycles = 0 + var/shield_key = FALSE + var/turf/spawn_turf + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/New() + ..() + name = "[name] [pick(GLOB.last_names)]" + // Do not attempt to move this code to Initialize() or LateInitialize(). Doing so with other objects has caused bugs in the past, because assigning "depotarea" may not work there. + depotarea = areaMaster + spawn_turf = get_turf(src) + + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/ListTargetsLazy() + // The normal ListTargetsLazy ignores walls, which is very bad in the case of depot mobs. So we override it. + return ListTargets() + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/Aggro() + . = ..() + if(!istype(depotarea)) + return + if(target) + if(!seen_enemy) + seen_enemy = TRUE + if(!ranged) + playsound(loc, 'sound/weapons/saberon.ogg', 35, 1) + if(alert_on_shield_breach) + if(depotarea.shield_list.len) + raise_alert("[name] reports that [target] is trying to breach the armory shield!") + alert_on_shield_breach = FALSE + raised_alert = FALSE + alert_on_death = TRUE + if(isliving(target)) + var/mob/living/M = target + depotarea.list_add(M, depotarea.hostile_list) + if(M.mind && M.mind.special_role == SPECIAL_ROLE_TRAITOR) + depotarea.saw_double_agent(M) + depotarea.declare_started() + seen_enemy_name = target.name + if(istype(target, /obj/mecha)) + depotarea.saw_mech(target) + if(istype(target, /obj/spacepod)) + depotarea.saw_pod(target) + if(depotarea.list_includes(target, depotarea.dead_list)) + seen_revived_enemy = TRUE + raise_alert("[name] reports intruder [target] has returned from death!") + depotarea.list_remove(target, depotarea.dead_list) + if(!atoms_share_level(src, target) && prob(20)) + // This prevents someone from aggroing a depot mob, then hiding in a locker, perfectly safe, while the mob stands there getting killed by their friends. + LoseTarget() + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/handle_automated_action() + . = ..() + if(!.) + return + if(!istype(depotarea)) + return + if(seen_enemy) + aggro_cycles++ + if(alert_on_timeout && !raised_alert && aggro_cycles >= 60) + raise_alert("[name] has reported contact with hostile entity: [seen_enemy_name]") + if(scan_cycles >= 15) + scan_cycles = 0 + if(!atoms_share_level(src, spawn_turf)) + if(istype(loc, /obj/structure/closet)) + var/obj/structure/closet/O = loc + forceMove(get_turf(src)) + visible_message("[src] smashes their way out of [O]!") + qdel(O) + raise_alert("[src] reported being trapped in a locker.") + raised_alert = FALSE + return + if(alert_on_spacing) + raise_alert("[src] lost in space.") + death() + return + for(var/mob/living/body in hearers(vision_range, targets_from)) + if(body.stat != DEAD) + continue + if(depotarea.list_includes(body, depotarea.dead_list)) + continue + if(faction_check_mob(body)) + continue + say("Target [body]... terminated.") + depotarea.list_add(body, depotarea.dead_list) + pointed(body) + else + scan_cycles++ + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/AIShouldSleep(var/list/possible_targets) + FindTarget(possible_targets, 1) + return FALSE + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/proc/raise_alert(var/reason) + if(istype(depotarea) && (!raised_alert || seen_revived_enemy) && !depotarea.used_self_destruct) + raised_alert = TRUE + say("Intruder!") + depotarea.increase_alert(reason) + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/death() + if(!istype(depotarea)) + return ..() + if(alert_on_death) + if(seen_enemy_name) + raise_alert("[name] has died in combat with [seen_enemy_name].") + else + raise_alert("[name] has died.") + if(shield_key && depotarea) + depotarea.shields_key_check() + if(depotarea) + depotarea.list_remove(src, depotarea.guard_list) + new /obj/effect/gibspawner/human(get_turf(src)) + return ..() + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/CanPass(atom/movable/mover, turf/target, height=0) + if(isliving(mover)) + var/mob/living/blocker = mover + if(faction_check_mob(blocker)) + return 1 + return ..(mover, target, height) + + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/officer + name = "Syndicate Officer" + icon_state = "syndicate_sword" + icon_living = "syndicate_sword" + melee_block_chance = 20 + ranged_block_chance = 35 + alert_on_death = TRUE + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/officer/Initialize(mapload) + . = ..() + if(prob(50)) + // 50% chance of switching to ranged variant. + // Designed to counter players taking cover behind reinforced plasmasglass. + // Does almost no danage in melee, but decent damage at range, and its shots go through glass. + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "punches" + attack_sound = 'sound/weapons/punch1.ogg' + ranged = 1 + rapid = 3 + retreat_distance = 3 + minimum_distance = 3 + melee_block_chance = 0 + ranged_block_chance = 0 + icon_state = "syndicate_pistol" + icon_living = "syndicate_pistol" + projectiletype = /obj/item/projectile/beam/laser + projectilesound = 'sound/weapons/laser.ogg' + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/armory + name = "Syndicate Quartermaster" + icon_state = "syndicate_stormtrooper_sword" + icon_living = "syndicate_stormtrooper_sword" + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + maxHealth = 200 + health = 200 + melee_block_chance = 40 + alert_on_shield_breach = TRUE + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/armory/Initialize(mapload) + ..() + if(prob(50)) + // 50% chance of switching to extremely dangerous ranged variant + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "punches" + attack_sound = 'sound/weapons/punch1.ogg' + ranged = 1 + retreat_distance = 3 + minimum_distance = 3 + melee_block_chance = 0 + ranged_block_chance = 0 + icon_state = "syndicate_stormtrooper_shotgun" + icon_living = "syndicate_stormtrooper_shotgun" + projectiletype = /obj/item/projectile/bullet/sniper/penetrator // Ignores cover. + projectilesound = 'sound/weapons/gunshots/gunshot_sniper.ogg' + return INITIALIZE_HINT_LATELOAD + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/armory/LateInitialize() + if(istype(depotarea)) + var/list/key_candidates = list() + for(var/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/officer/O in GLOB.living_mob_list) + key_candidates += O + if(key_candidates.len) + var/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/officer/O = pick(key_candidates) + O.shield_key = TRUE + depotarea.shields_up() + + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/space + name = "Syndicate Backup" + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + icon_state = "syndicate_space_sword" + icon_living = "syndicate_space_sword" + speed = 1 + wander = 0 + alert_on_spacing = FALSE + +/mob/living/simple_animal/hostile/syndicate/melee/autogib/depot/space/Process_Spacemove(var/movement_dir = 0) + return TRUE + + + +/mob/living/simple_animal/hostile/syndicate/melee/space + name = "Syndicate Commando" + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + icon_state = "syndicate_space_sword" + icon_living = "syndicate_space_sword" + speed = 1 + loot = list(/obj/effect/mob_spawn/human/corpse/syndicatecommando, /obj/item/melee/energy/sword/saber/red, /obj/item/shield/energy) + +/mob/living/simple_animal/hostile/syndicate/melee/space/Process_Spacemove(var/movement_dir = 0) + return TRUE + + +/mob/living/simple_animal/hostile/syndicate/ranged + ranged = 1 + rapid = 2 + retreat_distance = 5 + minimum_distance = 5 + icon_state = "syndicate_smg" + icon_living = "syndicate_smg" + projectilesound = 'sound/weapons/gunshots/gunshot.ogg' + casingtype = /obj/item/ammo_casing/c45 + loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier, /obj/item/gun/projectile/automatic/c20r) + +/mob/living/simple_animal/hostile/syndicate/ranged/space + icon_state = "syndicate_space_smg" + icon_living = "syndicate_space_smg" + name = "Syndicate Commando" + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + speed = 1 + loot = list(/obj/effect/mob_spawn/human/corpse/syndicatecommando, /obj/item/gun/projectile/automatic/c20r) + +/mob/living/simple_animal/hostile/syndicate/ranged/space/Process_Spacemove(var/movement_dir = 0) + return TRUE + +/mob/living/simple_animal/hostile/syndicate/ranged/space/autogib + loot = list()//gonna gibe, no loot. + +/mob/living/simple_animal/hostile/viscerator + name = "viscerator" + desc = "A small, twin-bladed machine capable of inflicting very deadly lacerations." + icon = 'icons/mob/critter.dmi' + icon_state = "viscerator_attack" + icon_living = "viscerator_attack" + pass_flags = PASSTABLE + health = 15 + maxHealth = 15 + obj_damage = 0 + melee_damage_lower = 15 + melee_damage_upper = 15 + attacktext = "cuts" + attack_sound = 'sound/weapons/bladeslice.ogg' + faction = list("syndicate") + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + mob_size = MOB_SIZE_TINY + flying = 1 + gold_core_spawnable = HOSTILE_SPAWN + del_on_death = 1 + deathmessage = "is smashed into pieces!" diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm index b72ec90cf58..637c1833a89 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm @@ -278,4 +278,4 @@ forceMove(C.loc) C.visible_message("[src] smashes the welded cover off [C]!") return - to_chat(src, "There is no welded vent or scrubber close enough to do this.") \ No newline at end of file + to_chat(src, "There is no welded vent or scrubber close enough to do this.") diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/black.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/black.dm index eae926b88e1..a1bb0fb0ae9 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/black.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/black.dm @@ -56,4 +56,4 @@ var/inject_target = pick("chest","head") if(C.can_inject(null, FALSE, inject_target, FALSE)) to_chat(C, "[src] slices into you!") - C.reagents.add_reagent("terror_black_toxin", 30) \ No newline at end of file + C.reagents.add_reagent("terror_black_toxin", 30) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress.dm index fd30532a7bc..f6dffb79642 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress.dm @@ -110,4 +110,4 @@ /obj/item/projectile/terrorqueenspit/empress damage_type = BURN damage = 30 - bonus_tox = 0 \ No newline at end of file + bonus_tox = 0 diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/ghost.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/ghost.dm index 283cbb6ad0d..24e8012b5ee 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/ghost.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/ghost.dm @@ -43,4 +43,4 @@ return key = user.key for(var/mob/dead/observer/G in GLOB.player_list) - G.show_message("A ghost has taken control of [src]. ([ghost_follow_link(src, ghost=G)]).") \ No newline at end of file + G.show_message("A ghost has taken control of [src]. ([ghost_follow_link(src, ghost=G)]).") diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/gray.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/gray.dm index c06a7c2fee6..544f7201d24 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/gray.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/gray.dm @@ -63,4 +63,4 @@ /obj/structure/spider/terrorweb/gray alpha = 100 name = "transparent web" - desc = "This web is partly transparent, making it harder to see, and easier to get caught by." \ No newline at end of file + desc = "This web is partly transparent, making it harder to see, and easier to get caught by." diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/hive.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/hive.dm index d4b1886e49e..aec7d9bb5ff 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/hive.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/hive.dm @@ -37,4 +37,4 @@ for(var/obj/structure/spider/spiderling/terror_spiderling/L in ts_spiderling_list) if(!L.stillborn && L.grow_as == specific_type && L.z == z) numspiders += 1 - return numspiders \ No newline at end of file + return numspiders diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/purple.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/purple.dm index 92c7688e1e1..7e36b411bae 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/purple.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/purple.dm @@ -94,4 +94,4 @@ name = "thick web" desc = "This web is so thick, most cannot see beyond it." opacity = 1 - max_integrity = 40 \ No newline at end of file + max_integrity = 40 diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm index af4fda64625..cd5322a58a1 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm @@ -353,4 +353,4 @@ var/inject_target = pick("chest","head") if(C.can_inject(null, FALSE, inject_target, FALSE)) C.Hallucinate(400) - C.adjustToxLoss(30) \ No newline at end of file + C.adjustToxLoss(30) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/reproduction.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/reproduction.dm index 4bba74001fd..0cf8355e06e 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/reproduction.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/reproduction.dm @@ -243,4 +243,4 @@ S.enemies = enemies if(spider_growinstantly) S.amount_grown = 250 - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/white.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/white.dm index f62167792a3..919f91742cc 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/white.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/white.dm @@ -68,4 +68,4 @@ var/inject_target = pick("chest","head") if(C.can_inject(null, FALSE, inject_target, FALSE)) to_chat(C, "[src] slices into you!") - new /obj/item/organ/internal/body_egg/terror_eggs(C) \ No newline at end of file + new /obj/item/organ/internal/body_egg/terror_eggs(C) diff --git a/code/modules/mob/living/simple_animal/hostile/tree.dm b/code/modules/mob/living/simple_animal/hostile/tree.dm index 510e609756b..6881e3d7b12 100644 --- a/code/modules/mob/living/simple_animal/hostile/tree.dm +++ b/code/modules/mob/living/simple_animal/hostile/tree.dm @@ -1,45 +1,45 @@ -/mob/living/simple_animal/hostile/tree - name = "pine tree" - desc = "A pissed off tree-like alien. It seems annoyed with the festivities..." - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "pine_1" - icon_living = "pine_1" - icon_dead = "pine_1" - icon_gib = "pine_1" - speak_chance = 0 - turns_per_move = 5 - response_help = "brushes the" - response_disarm = "pushes the" - response_harm = "hits the" - speed = 1 - maxHealth = 250 - health = 250 - mob_size = MOB_SIZE_LARGE - - pixel_x = -16 - - harm_intent_damage = 5 - melee_damage_lower = 8 - melee_damage_upper = 12 - attacktext = "bites" - attack_sound = 'sound/weapons/bite.ogg' - speak_emote = list("pines") - emote_taunt = list("growls") - taunt_chance = 20 - - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 0 - - faction = list("hostile", "winter") - loot = list(/obj/item/stack/sheet/wood) - gold_core_spawnable = HOSTILE_SPAWN - deathmessage = "is hacked into pieces!" - del_on_death = 1 - -/mob/living/simple_animal/hostile/tree/AttackingTarget() - . = ..() - if(. && iscarbon(target)) - var/mob/living/carbon/C = target - if(prob(15)) - C.Weaken(3) - C.visible_message("\the [src] knocks down \the [C]!") \ No newline at end of file +/mob/living/simple_animal/hostile/tree + name = "pine tree" + desc = "A pissed off tree-like alien. It seems annoyed with the festivities..." + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "pine_1" + icon_living = "pine_1" + icon_dead = "pine_1" + icon_gib = "pine_1" + speak_chance = 0 + turns_per_move = 5 + response_help = "brushes the" + response_disarm = "pushes the" + response_harm = "hits the" + speed = 1 + maxHealth = 250 + health = 250 + mob_size = MOB_SIZE_LARGE + + pixel_x = -16 + + harm_intent_damage = 5 + melee_damage_lower = 8 + melee_damage_upper = 12 + attacktext = "bites" + attack_sound = 'sound/weapons/bite.ogg' + speak_emote = list("pines") + emote_taunt = list("growls") + taunt_chance = 20 + + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 0 + + faction = list("hostile", "winter") + loot = list(/obj/item/stack/sheet/wood) + gold_core_spawnable = HOSTILE_SPAWN + deathmessage = "is hacked into pieces!" + del_on_death = 1 + +/mob/living/simple_animal/hostile/tree/AttackingTarget() + . = ..() + if(. && iscarbon(target)) + var/mob/living/carbon/C = target + if(prob(15)) + C.Weaken(3) + C.visible_message("\the [src] knocks down \the [C]!") diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 337b7f093a2..d159f8160c6 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -1,729 +1,729 @@ -/* Parrots! - * Contains - * Defines - * Inventory (headset stuff) - * Attack responces - * AI - * Procs / Verbs (usable by players) - * Sub-types - */ - -/* - * Defines - */ - -//Only a maximum of one action and one intent should be active at any given time. -//Actions -#define PARROT_PERCH 1 //Sitting/sleeping, not moving -#define PARROT_SWOOP 2 //Moving towards or away from a target -#define PARROT_WANDER 4 //Moving without a specific target in mind - -//Intents -#define PARROT_STEAL 8 //Flying towards a target to steal it/from it -#define PARROT_ATTACK 16 //Flying towards a target to attack it -#define PARROT_RETURN 32 //Flying towards its perch -#define PARROT_FLEE 64 //Flying away from its attacker - - -/mob/living/simple_animal/parrot - name = "\improper Parrot" - desc = "The parrot squaks, \"It's a Parrot! BAWWK!\"" - icon = 'icons/mob/animal.dmi' - icon_state = "parrot_fly" - icon_living = "parrot_fly" - icon_dead = "parrot_dead" - pass_flags = PASSTABLE - can_collar = 1 - - var/list/clean_speak = list( - "Hi", - "Hello!", - "Cracker?", - "BAWWWWK george mellons griffing me") - speak_emote = list("squawks","says","yells") - emote_hear = list("squawks","bawks") - emote_see = list("flutters its wings") - - speak_chance = 1//1% (1 in 100) chance every tick; So about once per 150 seconds, assuming an average tick is 1.5s - turns_per_move = 5 - butcher_results = list(/obj/item/reagent_containers/food/snacks/cracker = 3) - - response_help = "pets the" - response_disarm = "gently moves aside the" - response_harm = "swats the" - stop_automated_movement = 1 - universal_speak = 1 - mob_size = MOB_SIZE_SMALL - - var/parrot_state = PARROT_WANDER //Hunt for a perch when created - var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in process_ai() being run every single tick. - var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down - var/parrot_dam_zone = list("chest", "head", "l_arm", "l_leg", "r_arm", "r_leg") //For humans, select a bodypart to attack - - var/parrot_speed = 5 //"Delay in world ticks between movement." according to byond. Yeah, that's BS but it does directly affect movement. Higher number = slower. - var/parrot_been_shot = 0 //Parrots get a speed bonus after being shot. This will deincrement every process_ai() and at 0 the parrot will return to regular speed. - - var/list/speech_buffer = list() - var/list/available_channels = list() - - //Headset for Poly to yell at engineers :) - var/obj/item/radio/headset/ears = null - - //The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from, - //mobs it wants to attack or mobs that have attacked it - var/atom/movable/parrot_interest = null - - //Parrots will generally sit on their pertch unless something catches their eye. - //These vars store their preffered perch and if they dont have one, what they can use as a perch - var/obj/parrot_perch = null - var/obj/desired_perches = list(/obj/structure/computerframe, /obj/structure/displaycase, \ - /obj/structure/filingcabinet, /obj/machinery/teleport, \ - /obj/machinery/suit_storage_unit, /obj/machinery/clonepod, \ - /obj/machinery/dna_scannernew, /obj/machinery/telecomms, \ - /obj/machinery/nuclearbomb, /obj/machinery/particle_accelerator, \ - /obj/machinery/recharge_station, /obj/machinery/smartfridge, \ - /obj/machinery/computer) - - //Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding. - var/obj/item/held_item = null - gold_core_spawnable = FRIENDLY_SPAWN - - -/mob/living/simple_animal/parrot/New() - ..() - GLOB.hear_radio_list += src - if(!ears) - var/headset = pick(/obj/item/radio/headset/headset_sec, \ - /obj/item/radio/headset/headset_eng, \ - /obj/item/radio/headset/headset_med, \ - /obj/item/radio/headset/headset_sci, \ - /obj/item/radio/headset/headset_cargo) - ears = new headset(src) - update_speak() - - parrot_sleep_dur = parrot_sleep_max //In case someone decides to change the max without changing the duration var - - verbs.Add(/mob/living/simple_animal/parrot/proc/steal_from_ground, \ - /mob/living/simple_animal/parrot/proc/steal_from_mob, \ - /mob/living/simple_animal/parrot/verb/drop_held_item_player, \ - /mob/living/simple_animal/parrot/proc/perch_player) - -/mob/living/simple_animal/parrot/Destroy() - GLOB.hear_radio_list -= src - return ..() - -/mob/living/simple_animal/parrot/death(gibbed) - if(can_die()) - if(held_item) - held_item.loc = src.loc - held_item = null - walk(src,0) - return ..() - -/mob/living/simple_animal/parrot/Stat() - ..() - stat("Held Item", held_item) - -/* - * Inventory - */ -/mob/living/simple_animal/parrot/show_inv(mob/user as mob) - user.set_machine(src) - - var/dat = {""} - - dat += "" - if(can_collar) - dat += "" - dat += "" - - dat += {"
        Headset:[(ears && !(ears.flags&ABSTRACT)) ? ears : "Empty"]
         
        Collar:[(pcollar && !(pcollar.flags&ABSTRACT)) ? pcollar : "Empty"]
        - Close - "} - - var/datum/browser/popup = new(user, "mob\ref[src]", "[src]", 440, 500) - popup.set_content(dat) - popup.open() - -/mob/living/simple_animal/parrot/Topic(href, href_list) - - //Can the usr physically do this? - if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) - return - - //Is the usr's mob type able to do this? - if(ishuman(usr) || isrobot(usr)) - if(href_list["remove_inv"]) - var/remove_from = href_list["remove_inv"] - switch(remove_from) - if("ears") - if(ears) - if(available_channels.len) - say("[pick(available_channels)]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - else - say("BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - ears.forceMove(loc) - ears = null - update_speak() - else - to_chat(usr, "There is nothing to remove from its [remove_from].") - return - show_inv(usr) - else if(href_list["add_inv"]) - var/add_to = href_list["add_inv"] - if(!usr.get_active_hand()) - to_chat(usr, "You have nothing in your hand to put on its [add_to].") - return - switch(add_to) - if("ears") - if(ears) - to_chat(usr, "It's already wearing something.") - return - else - var/obj/item/item_to_add = usr.get_active_hand() - if(!item_to_add) - return - - if( !istype(item_to_add, /obj/item/radio/headset) ) - to_chat(usr, "This object won't fit.") - return - - var/obj/item/radio/headset/headset_to_add = item_to_add - - usr.drop_item() - headset_to_add.forceMove(src) - ears = headset_to_add - to_chat(usr, "You fit the headset onto [src].") - - available_channels.Cut() - for(var/ch in headset_to_add.channels) - switch(ch) - if("Engineering") - available_channels.Add(":e") - if("Command") - available_channels.Add(":c") - if("Security") - available_channels.Add(":s") - if("Science") - available_channels.Add(":n") - if("Medical") - available_channels.Add(":m") - if("Mining") - available_channels.Add(":d") - if("Cargo") - available_channels.Add(":q") - - if(headset_to_add.translate_binary) - available_channels.Add(":b") - update_speak() - show_inv(usr) - else - ..() - - -/* - * Attack responces - */ -//Humans, monkeys, aliens -/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M as mob) - ..() - if(client) return - if(!stat && M.a_intent == "hurt") - - icon_state = "parrot_fly" //It is going to be flying regardless of whether it flees or attacks - - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = M - parrot_state = PARROT_SWOOP //The parrot just got hit, it WILL move, now to pick a direction.. - - if(M.health < 50) //Weakened mob? Fight back! - parrot_state |= PARROT_ATTACK - else - parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell! - drop_held_item(0) - return - -//Mobs with objects -/mob/living/simple_animal/parrot/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - ..() - if(!stat && !client && !istype(O, /obj/item/stack/medical)) - if(O.force) - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = user - parrot_state = PARROT_SWOOP | PARROT_FLEE - icon_state = "parrot_fly" - drop_held_item(0) - return - -//Bullets -/mob/living/simple_animal/parrot/bullet_act(var/obj/item/projectile/Proj) - ..() - if(!stat && !client) - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = null - parrot_state = PARROT_WANDER //OWFUCK, Been shot! RUN LIKE HELL! - parrot_been_shot += 5 - icon_state = "parrot_fly" - drop_held_item(0) - return - - -/* - * AI - Not really intelligent, but I'm calling it AI anyway. - */ -/mob/living/simple_animal/parrot/Life(seconds, times_fired) - ..() - - //Sprite and AI update for when a parrot gets pulled - if(pulledby && stat == CONSCIOUS) - icon_state = "parrot_fly" - -/mob/living/simple_animal/parrot/proc/update_speak() - speak.Cut() - - if(available_channels.len && ears) - for(var/possible_phrase in clean_speak) - //50/50 chance to not use the radio at all - speak += "[prob(50) ? pick(available_channels) : ""][possible_phrase]" - - else //If we have no headset or channels to use, dont try to use any! - for(var/possible_phrase in clean_speak) - speak += possible_phrase - -/mob/living/simple_animal/parrot/handle_automated_movement() - if(pulledby) - parrot_state = PARROT_WANDER - return - - if(!isturf(src.loc) || !canmove || buckled) - return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove) - - -//-----SPEECH - /* Parrot speech mimickry! - Phrases that the parrot hears in mob/living/say() get added to speach_buffer. - Every once in a while, the parrot picks one of the lines from the buffer and replaces an element of the 'speech' list. - Then it clears the buffer to make sure they dont magically remember something from hours ago. */ - if(speech_buffer.len && prob(10)) - if(clean_speak.len) - clean_speak -= pick(clean_speak) - - clean_speak += pick(speech_buffer) - speech_buffer.Cut() - - -//-----SLEEPING - if(parrot_state == PARROT_PERCH) - if(parrot_perch && parrot_perch.loc != src.loc) //Make sure someone hasnt moved our perch on us - if(parrot_perch in view(src)) - parrot_state = PARROT_SWOOP | PARROT_RETURN - icon_state = "parrot_fly" - return - else - parrot_state = PARROT_WANDER - icon_state = "parrot_fly" - return - - if(--parrot_sleep_dur) //Zzz - return - - else - //This way we only call the stuff below once every [sleep_max] ticks. - parrot_sleep_dur = parrot_sleep_max - - //Cycle through message modes for the headset - update_speak() - - //Search for item to steal - parrot_interest = search_for_item() - if(parrot_interest) - custom_emote(1,"looks in [parrot_interest]'s direction and takes flight.") - parrot_state = PARROT_SWOOP | PARROT_STEAL - icon_state = "parrot_fly" - return - -//-----WANDERING - This is basically a 'I dont know what to do yet' state - else if(parrot_state == PARROT_WANDER) - //Stop movement, we'll set it later - walk(src, 0) - parrot_interest = null - - //Wander around aimlessly. This will help keep the loops from searches down - //and possibly move the mob into a new are in view of something they can use - if(prob(90)) - step(src, pick(cardinal)) - return - - if(!held_item && !parrot_perch) //If we've got nothing to do.. look for something to do. - var/atom/movable/AM = search_for_perch_and_item() //This handles checking through lists so we know it's either a perch or stealable item - if(AM) - if(istype(AM, /obj/item) || isliving(AM)) //If stealable item - parrot_interest = AM - custom_emote(1,"turns and flies towards [parrot_interest]") - parrot_state = PARROT_SWOOP | PARROT_STEAL - return - else //Else it's a perch - parrot_perch = AM - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - return - - if(parrot_interest && parrot_interest in view(src)) - parrot_state = PARROT_SWOOP | PARROT_STEAL - return - - if(parrot_perch && parrot_perch in view(src)) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - else //Have an item but no perch? Find one! - parrot_perch = search_for_perch() - if(parrot_perch) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return -//-----STEALING - else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL)) - walk(src,0) - if(!parrot_interest || held_item) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - if(!(parrot_interest in view(src))) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - if(in_range(src, parrot_interest)) - - if(isliving(parrot_interest)) - steal_from_mob() - - else //This should ensure that we only grab the item we want, and make sure it's not already collected on our perch - if(!parrot_perch || parrot_interest.loc != parrot_perch.loc) - held_item = parrot_interest - parrot_interest.loc = src - visible_message("[src] grabs the [held_item]!", "You grab the [held_item]!", "You hear the sounds of wings flapping furiously.") - - parrot_interest = null - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - walk_to(src, parrot_interest, 1, parrot_speed) - return - -//-----RETURNING TO PERCH - else if(parrot_state == (PARROT_SWOOP | PARROT_RETURN)) - walk(src, 0) - if(!parrot_perch || !isturf(parrot_perch.loc)) //Make sure the perch exists and somehow isnt inside of something else. - parrot_perch = null - parrot_state = PARROT_WANDER - return - - if(in_range(src, parrot_perch)) - src.loc = parrot_perch.loc - drop_held_item() - parrot_state = PARROT_PERCH - icon_state = "parrot_sit" - return - - walk_to(src, parrot_perch, 1, parrot_speed) - return - -//-----FLEEING - else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE)) - walk(src,0) - if(!parrot_interest || !isliving(parrot_interest)) //Sanity - parrot_state = PARROT_WANDER - - walk_away(src, parrot_interest, 1, parrot_speed-parrot_been_shot) - parrot_been_shot-- - return - -//-----ATTACKING - else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK)) - - //If we're attacking a nothing, an object, a turf or a ghost for some stupid reason, switch to wander - if(!parrot_interest || !isliving(parrot_interest)) - parrot_interest = null - parrot_state = PARROT_WANDER - return - - var/mob/living/L = parrot_interest - - //If the mob is close enough to interact with - if(in_range(src, parrot_interest)) - - //If the mob we've been chasing/attacking dies or falls into crit, check for loot! - if(L.stat) - parrot_interest = null - if(!held_item) - held_item = steal_from_ground() - if(!held_item) - held_item = steal_from_mob() //Apparently it's possible for dead mobs to hang onto items in certain circumstances. - if(parrot_perch in view(src)) //If we have a home nearby, go to it, otherwise find a new home - parrot_state = PARROT_SWOOP | PARROT_RETURN - else - parrot_state = PARROT_WANDER - return - - //Time for the hurt to begin! - var/damage = rand(5,10) - - if(ishuman(parrot_interest)) - var/mob/living/carbon/human/H = parrot_interest - var/obj/item/organ/external/affecting = H.get_organ(ran_zone(pick(parrot_dam_zone))) - - H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, "melee"), sharp = 1) - custom_emote(1, pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons.")) - - else - L.adjustBruteLoss(damage) - custom_emote(1, pick("pecks at [L].", "claws [L].")) - return - - //Otherwise, fly towards the mob! - else - walk_to(src, parrot_interest, 1, parrot_speed) - return -//-----STATE MISHAP - else //This should not happen. If it does lets reset everything and try again - walk(src,0) - parrot_interest = null - parrot_perch = null - drop_held_item() - parrot_state = PARROT_WANDER - return - -/* - * Procs - */ - -/mob/living/simple_animal/parrot/movement_delay() - if(client && stat == CONSCIOUS && parrot_state != "parrot_fly") - icon_state = "parrot_fly" - //Because the most appropriate place to set icon_state is movement_delay(), clearly - return ..() - -/mob/living/simple_animal/parrot/proc/search_for_item() - for(var/atom/movable/AM in view(src)) - //Skip items we already stole or are wearing or are too big - if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) - continue - - if(istype(AM, /obj/item)) - var/obj/item/I = AM - if(I.w_class < WEIGHT_CLASS_SMALL) - return I - - if(iscarbon(AM)) - var/mob/living/carbon/C = AM - if((C.l_hand && C.l_hand.w_class <= WEIGHT_CLASS_SMALL) || (C.r_hand && C.r_hand.w_class <= WEIGHT_CLASS_SMALL)) - return C - return null - -/mob/living/simple_animal/parrot/proc/search_for_perch() - for(var/obj/O in view(src)) - for(var/path in desired_perches) - if(istype(O, path)) - return O - return null - -//This proc was made to save on doing two 'in view' loops seperatly -/mob/living/simple_animal/parrot/proc/search_for_perch_and_item() - for(var/atom/movable/AM in view(src)) - for(var/perch_path in desired_perches) - if(istype(AM, perch_path)) - return AM - - //Skip items we already stole or are wearing or are too big - if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) - continue - - if(istype(AM, /obj/item)) - var/obj/item/I = AM - if(I.w_class <= WEIGHT_CLASS_SMALL) - return I - - if(iscarbon(AM)) - var/mob/living/carbon/C = AM - if(C.l_hand && C.l_hand.w_class <= WEIGHT_CLASS_SMALL || C.r_hand && C.r_hand.w_class <= WEIGHT_CLASS_SMALL) - return C - return null - - -/* - * Verbs - These are actually procs, but can be used as verbs by player-controlled parrots. - */ -/mob/living/simple_animal/parrot/proc/steal_from_ground() - set name = "Steal from ground" - set category = "Parrot" - set desc = "Grabs a nearby item." - - if(stat) - return -1 - - if(held_item) - to_chat(src, "You are already holding the [held_item]") - return 1 - - for(var/obj/item/I in view(1,src)) - //Make sure we're not already holding it and it's small enough - if(I.loc != src && I.w_class <= WEIGHT_CLASS_SMALL) - - //If we have a perch and the item is sitting on it, continue - if(!client && parrot_perch && I.loc == parrot_perch.loc) - continue - - held_item = I - I.loc = src - visible_message("[src] grabs the [held_item]!", "You grab the [held_item]!", "You hear the sounds of wings flapping furiously.") - return held_item - - to_chat(src, "There is nothing of interest to take.") - return 0 - -/mob/living/simple_animal/parrot/proc/steal_from_mob() - set name = "Steal from mob" - set category = "Parrot" - set desc = "Steals an item right out of a person's hand!" - - if(stat) - return -1 - - if(held_item) - to_chat(src, "You are already holding the [held_item]") - return 1 - - var/obj/item/stolen_item = null - - for(var/mob/living/carbon/C in view(1,src)) - if(C.l_hand && C.l_hand.w_class <= WEIGHT_CLASS_SMALL) - stolen_item = C.l_hand - - if(C.r_hand && C.r_hand.w_class <= WEIGHT_CLASS_SMALL) - stolen_item = C.r_hand - - if(stolen_item) - C.unEquip(stolen_item) - held_item = stolen_item - stolen_item.loc = src - visible_message("[src] grabs the [held_item] out of [C]'s hand!", "You snag the [held_item] out of [C]'s hand!", "You hear the sounds of wings flapping furiously.") - return held_item - - to_chat(src, "There is nothing of interest to take.") - return 0 - -/mob/living/simple_animal/parrot/verb/drop_held_item_player() - set name = "Drop held item" - set category = "Parrot" - set desc = "Drop the item you're holding." - - if(stat) - return - - src.drop_held_item() - - return - -/mob/living/simple_animal/parrot/proc/drop_held_item(var/drop_gently = 1) - set name = "Drop held item" - set category = "Parrot" - set desc = "Drop the item you're holding." - - if(stat) - return -1 - - if(!held_item) - to_chat(src, "You have nothing to drop!") - return 0 - - if(!drop_gently) - if(istype(held_item, /obj/item/grenade)) - var/obj/item/grenade/G = held_item - G.loc = src.loc - G.prime() - to_chat(src, "You let go of the [held_item]!") - held_item = null - return 1 - - to_chat(src, "You drop the [held_item].") - - held_item.loc = src.loc - held_item = null - return 1 - -/mob/living/simple_animal/parrot/proc/perch_player() - set name = "Sit" - set category = "Parrot" - set desc = "Sit on a nice comfy perch." - - if(stat || !client) - return - - if(icon_state == "parrot_fly") - for(var/atom/movable/AM in view(src,1)) - for(var/perch_path in desired_perches) - if(istype(AM, perch_path)) - src.loc = AM.loc - icon_state = "parrot_sit" - return - to_chat(src, "There is no perch nearby to sit on.") - return - -/* - * Sub-types - */ -/mob/living/simple_animal/parrot/Poly - name = "Poly" - desc = "Poly the Parrot. An expert on quantum cracker theory." - clean_speak = list( - "Poly wanna cracker!", - "Check the singlo, you chucklefucks!", - "Check the tesla, you shits!", - "STOP HOT-WIRING THE ENGINE, FUCKING CHRIST!", - "Wire the solars, you lazy bums!", - "WHO TOOK THE DAMN HARDSUITS?", - "OH GOD ITS FREE CALL THE SHUTTLE", - "Why are there so many atmos alerts?", - "OH GOD WHY WOULD YOU TURN ON THE PA BEFORE CONTAINMENT IS UP?", - "Remember to lock the emitters!", - "Stop goofing off and repair the goddam station!", - "The singularity is not your friend!", - "What were the wires again?", - "Goddam emaggers!" - ) - unique_pet = TRUE - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/parrot/Poly/New() - ears = new /obj/item/radio/headset/headset_eng(src) - available_channels = list(":e") - ..() - -/mob/living/simple_animal/parrot/handle_message_mode(var/message_mode, list/message_pieces, var/verb, var/used_radios) - if(message_mode && istype(ears)) - ears.talk_into(src, message_pieces, message_mode, verb) - used_radios += ears - -/mob/living/simple_animal/parrot/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency) - if(speaker != src && prob(50)) - parrot_hear(html_decode(multilingual_to_message(message_pieces))) - ..() - - - -/mob/living/simple_animal/parrot/hear_radio(list/message_pieces, var/verb="says", var/part_a, var/part_b, var/mob/speaker = null, var/hard_to_hear = 0, var/atom/follow_target) - if(speaker != src && prob(50)) - parrot_hear(html_decode(multilingual_to_message(message_pieces))) - ..() - - -/mob/living/simple_animal/parrot/proc/parrot_hear(var/message="") - if(!message || stat) - return - speech_buffer.Add(message) +/* Parrots! + * Contains + * Defines + * Inventory (headset stuff) + * Attack responces + * AI + * Procs / Verbs (usable by players) + * Sub-types + */ + +/* + * Defines + */ + +//Only a maximum of one action and one intent should be active at any given time. +//Actions +#define PARROT_PERCH 1 //Sitting/sleeping, not moving +#define PARROT_SWOOP 2 //Moving towards or away from a target +#define PARROT_WANDER 4 //Moving without a specific target in mind + +//Intents +#define PARROT_STEAL 8 //Flying towards a target to steal it/from it +#define PARROT_ATTACK 16 //Flying towards a target to attack it +#define PARROT_RETURN 32 //Flying towards its perch +#define PARROT_FLEE 64 //Flying away from its attacker + + +/mob/living/simple_animal/parrot + name = "\improper Parrot" + desc = "The parrot squaks, \"It's a Parrot! BAWWK!\"" + icon = 'icons/mob/animal.dmi' + icon_state = "parrot_fly" + icon_living = "parrot_fly" + icon_dead = "parrot_dead" + pass_flags = PASSTABLE + can_collar = 1 + + var/list/clean_speak = list( + "Hi", + "Hello!", + "Cracker?", + "BAWWWWK george mellons griffing me") + speak_emote = list("squawks","says","yells") + emote_hear = list("squawks","bawks") + emote_see = list("flutters its wings") + + speak_chance = 1//1% (1 in 100) chance every tick; So about once per 150 seconds, assuming an average tick is 1.5s + turns_per_move = 5 + butcher_results = list(/obj/item/reagent_containers/food/snacks/cracker = 3) + + response_help = "pets the" + response_disarm = "gently moves aside the" + response_harm = "swats the" + stop_automated_movement = 1 + universal_speak = 1 + mob_size = MOB_SIZE_SMALL + + var/parrot_state = PARROT_WANDER //Hunt for a perch when created + var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in process_ai() being run every single tick. + var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down + var/parrot_dam_zone = list("chest", "head", "l_arm", "l_leg", "r_arm", "r_leg") //For humans, select a bodypart to attack + + var/parrot_speed = 5 //"Delay in world ticks between movement." according to byond. Yeah, that's BS but it does directly affect movement. Higher number = slower. + var/parrot_been_shot = 0 //Parrots get a speed bonus after being shot. This will deincrement every process_ai() and at 0 the parrot will return to regular speed. + + var/list/speech_buffer = list() + var/list/available_channels = list() + + //Headset for Poly to yell at engineers :) + var/obj/item/radio/headset/ears = null + + //The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from, + //mobs it wants to attack or mobs that have attacked it + var/atom/movable/parrot_interest = null + + //Parrots will generally sit on their pertch unless something catches their eye. + //These vars store their preffered perch and if they dont have one, what they can use as a perch + var/obj/parrot_perch = null + var/obj/desired_perches = list(/obj/structure/computerframe, /obj/structure/displaycase, \ + /obj/structure/filingcabinet, /obj/machinery/teleport, \ + /obj/machinery/suit_storage_unit, /obj/machinery/clonepod, \ + /obj/machinery/dna_scannernew, /obj/machinery/telecomms, \ + /obj/machinery/nuclearbomb, /obj/machinery/particle_accelerator, \ + /obj/machinery/recharge_station, /obj/machinery/smartfridge, \ + /obj/machinery/computer) + + //Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding. + var/obj/item/held_item = null + gold_core_spawnable = FRIENDLY_SPAWN + + +/mob/living/simple_animal/parrot/New() + ..() + GLOB.hear_radio_list += src + if(!ears) + var/headset = pick(/obj/item/radio/headset/headset_sec, \ + /obj/item/radio/headset/headset_eng, \ + /obj/item/radio/headset/headset_med, \ + /obj/item/radio/headset/headset_sci, \ + /obj/item/radio/headset/headset_cargo) + ears = new headset(src) + update_speak() + + parrot_sleep_dur = parrot_sleep_max //In case someone decides to change the max without changing the duration var + + verbs.Add(/mob/living/simple_animal/parrot/proc/steal_from_ground, \ + /mob/living/simple_animal/parrot/proc/steal_from_mob, \ + /mob/living/simple_animal/parrot/verb/drop_held_item_player, \ + /mob/living/simple_animal/parrot/proc/perch_player) + +/mob/living/simple_animal/parrot/Destroy() + GLOB.hear_radio_list -= src + return ..() + +/mob/living/simple_animal/parrot/death(gibbed) + if(can_die()) + if(held_item) + held_item.loc = src.loc + held_item = null + walk(src,0) + return ..() + +/mob/living/simple_animal/parrot/Stat() + ..() + stat("Held Item", held_item) + +/* + * Inventory + */ +/mob/living/simple_animal/parrot/show_inv(mob/user as mob) + user.set_machine(src) + + var/dat = {""} + + dat += "" + if(can_collar) + dat += "" + dat += "" + + dat += {"
        Headset:[(ears && !(ears.flags&ABSTRACT)) ? ears : "Empty"]
         
        Collar:[(pcollar && !(pcollar.flags&ABSTRACT)) ? pcollar : "Empty"]
        + Close + "} + + var/datum/browser/popup = new(user, "mob\ref[src]", "[src]", 440, 500) + popup.set_content(dat) + popup.open() + +/mob/living/simple_animal/parrot/Topic(href, href_list) + + //Can the usr physically do this? + if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) + return + + //Is the usr's mob type able to do this? + if(ishuman(usr) || isrobot(usr)) + if(href_list["remove_inv"]) + var/remove_from = href_list["remove_inv"] + switch(remove_from) + if("ears") + if(ears) + if(available_channels.len) + say("[pick(available_channels)]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") + else + say("BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") + ears.forceMove(loc) + ears = null + update_speak() + else + to_chat(usr, "There is nothing to remove from its [remove_from].") + return + show_inv(usr) + else if(href_list["add_inv"]) + var/add_to = href_list["add_inv"] + if(!usr.get_active_hand()) + to_chat(usr, "You have nothing in your hand to put on its [add_to].") + return + switch(add_to) + if("ears") + if(ears) + to_chat(usr, "It's already wearing something.") + return + else + var/obj/item/item_to_add = usr.get_active_hand() + if(!item_to_add) + return + + if( !istype(item_to_add, /obj/item/radio/headset) ) + to_chat(usr, "This object won't fit.") + return + + var/obj/item/radio/headset/headset_to_add = item_to_add + + usr.drop_item() + headset_to_add.forceMove(src) + ears = headset_to_add + to_chat(usr, "You fit the headset onto [src].") + + available_channels.Cut() + for(var/ch in headset_to_add.channels) + switch(ch) + if("Engineering") + available_channels.Add(":e") + if("Command") + available_channels.Add(":c") + if("Security") + available_channels.Add(":s") + if("Science") + available_channels.Add(":n") + if("Medical") + available_channels.Add(":m") + if("Mining") + available_channels.Add(":d") + if("Cargo") + available_channels.Add(":q") + + if(headset_to_add.translate_binary) + available_channels.Add(":b") + update_speak() + show_inv(usr) + else + ..() + + +/* + * Attack responces + */ +//Humans, monkeys, aliens +/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M as mob) + ..() + if(client) return + if(!stat && M.a_intent == "hurt") + + icon_state = "parrot_fly" //It is going to be flying regardless of whether it flees or attacks + + if(parrot_state == PARROT_PERCH) + parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched + + parrot_interest = M + parrot_state = PARROT_SWOOP //The parrot just got hit, it WILL move, now to pick a direction.. + + if(M.health < 50) //Weakened mob? Fight back! + parrot_state |= PARROT_ATTACK + else + parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell! + drop_held_item(0) + return + +//Mobs with objects +/mob/living/simple_animal/parrot/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + ..() + if(!stat && !client && !istype(O, /obj/item/stack/medical)) + if(O.force) + if(parrot_state == PARROT_PERCH) + parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched + + parrot_interest = user + parrot_state = PARROT_SWOOP | PARROT_FLEE + icon_state = "parrot_fly" + drop_held_item(0) + return + +//Bullets +/mob/living/simple_animal/parrot/bullet_act(var/obj/item/projectile/Proj) + ..() + if(!stat && !client) + if(parrot_state == PARROT_PERCH) + parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched + + parrot_interest = null + parrot_state = PARROT_WANDER //OWFUCK, Been shot! RUN LIKE HELL! + parrot_been_shot += 5 + icon_state = "parrot_fly" + drop_held_item(0) + return + + +/* + * AI - Not really intelligent, but I'm calling it AI anyway. + */ +/mob/living/simple_animal/parrot/Life(seconds, times_fired) + ..() + + //Sprite and AI update for when a parrot gets pulled + if(pulledby && stat == CONSCIOUS) + icon_state = "parrot_fly" + +/mob/living/simple_animal/parrot/proc/update_speak() + speak.Cut() + + if(available_channels.len && ears) + for(var/possible_phrase in clean_speak) + //50/50 chance to not use the radio at all + speak += "[prob(50) ? pick(available_channels) : ""][possible_phrase]" + + else //If we have no headset or channels to use, dont try to use any! + for(var/possible_phrase in clean_speak) + speak += possible_phrase + +/mob/living/simple_animal/parrot/handle_automated_movement() + if(pulledby) + parrot_state = PARROT_WANDER + return + + if(!isturf(src.loc) || !canmove || buckled) + return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove) + + +//-----SPEECH + /* Parrot speech mimickry! + Phrases that the parrot hears in mob/living/say() get added to speach_buffer. + Every once in a while, the parrot picks one of the lines from the buffer and replaces an element of the 'speech' list. + Then it clears the buffer to make sure they dont magically remember something from hours ago. */ + if(speech_buffer.len && prob(10)) + if(clean_speak.len) + clean_speak -= pick(clean_speak) + + clean_speak += pick(speech_buffer) + speech_buffer.Cut() + + +//-----SLEEPING + if(parrot_state == PARROT_PERCH) + if(parrot_perch && parrot_perch.loc != src.loc) //Make sure someone hasnt moved our perch on us + if(parrot_perch in view(src)) + parrot_state = PARROT_SWOOP | PARROT_RETURN + icon_state = "parrot_fly" + return + else + parrot_state = PARROT_WANDER + icon_state = "parrot_fly" + return + + if(--parrot_sleep_dur) //Zzz + return + + else + //This way we only call the stuff below once every [sleep_max] ticks. + parrot_sleep_dur = parrot_sleep_max + + //Cycle through message modes for the headset + update_speak() + + //Search for item to steal + parrot_interest = search_for_item() + if(parrot_interest) + custom_emote(1,"looks in [parrot_interest]'s direction and takes flight.") + parrot_state = PARROT_SWOOP | PARROT_STEAL + icon_state = "parrot_fly" + return + +//-----WANDERING - This is basically a 'I dont know what to do yet' state + else if(parrot_state == PARROT_WANDER) + //Stop movement, we'll set it later + walk(src, 0) + parrot_interest = null + + //Wander around aimlessly. This will help keep the loops from searches down + //and possibly move the mob into a new are in view of something they can use + if(prob(90)) + step(src, pick(cardinal)) + return + + if(!held_item && !parrot_perch) //If we've got nothing to do.. look for something to do. + var/atom/movable/AM = search_for_perch_and_item() //This handles checking through lists so we know it's either a perch or stealable item + if(AM) + if(istype(AM, /obj/item) || isliving(AM)) //If stealable item + parrot_interest = AM + custom_emote(1,"turns and flies towards [parrot_interest]") + parrot_state = PARROT_SWOOP | PARROT_STEAL + return + else //Else it's a perch + parrot_perch = AM + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + return + + if(parrot_interest && parrot_interest in view(src)) + parrot_state = PARROT_SWOOP | PARROT_STEAL + return + + if(parrot_perch && parrot_perch in view(src)) + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + + else //Have an item but no perch? Find one! + parrot_perch = search_for_perch() + if(parrot_perch) + parrot_state = PARROT_SWOOP | PARROT_RETURN + return +//-----STEALING + else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL)) + walk(src,0) + if(!parrot_interest || held_item) + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + + if(!(parrot_interest in view(src))) + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + + if(in_range(src, parrot_interest)) + + if(isliving(parrot_interest)) + steal_from_mob() + + else //This should ensure that we only grab the item we want, and make sure it's not already collected on our perch + if(!parrot_perch || parrot_interest.loc != parrot_perch.loc) + held_item = parrot_interest + parrot_interest.loc = src + visible_message("[src] grabs the [held_item]!", "You grab the [held_item]!", "You hear the sounds of wings flapping furiously.") + + parrot_interest = null + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + + walk_to(src, parrot_interest, 1, parrot_speed) + return + +//-----RETURNING TO PERCH + else if(parrot_state == (PARROT_SWOOP | PARROT_RETURN)) + walk(src, 0) + if(!parrot_perch || !isturf(parrot_perch.loc)) //Make sure the perch exists and somehow isnt inside of something else. + parrot_perch = null + parrot_state = PARROT_WANDER + return + + if(in_range(src, parrot_perch)) + src.loc = parrot_perch.loc + drop_held_item() + parrot_state = PARROT_PERCH + icon_state = "parrot_sit" + return + + walk_to(src, parrot_perch, 1, parrot_speed) + return + +//-----FLEEING + else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE)) + walk(src,0) + if(!parrot_interest || !isliving(parrot_interest)) //Sanity + parrot_state = PARROT_WANDER + + walk_away(src, parrot_interest, 1, parrot_speed-parrot_been_shot) + parrot_been_shot-- + return + +//-----ATTACKING + else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK)) + + //If we're attacking a nothing, an object, a turf or a ghost for some stupid reason, switch to wander + if(!parrot_interest || !isliving(parrot_interest)) + parrot_interest = null + parrot_state = PARROT_WANDER + return + + var/mob/living/L = parrot_interest + + //If the mob is close enough to interact with + if(in_range(src, parrot_interest)) + + //If the mob we've been chasing/attacking dies or falls into crit, check for loot! + if(L.stat) + parrot_interest = null + if(!held_item) + held_item = steal_from_ground() + if(!held_item) + held_item = steal_from_mob() //Apparently it's possible for dead mobs to hang onto items in certain circumstances. + if(parrot_perch in view(src)) //If we have a home nearby, go to it, otherwise find a new home + parrot_state = PARROT_SWOOP | PARROT_RETURN + else + parrot_state = PARROT_WANDER + return + + //Time for the hurt to begin! + var/damage = rand(5,10) + + if(ishuman(parrot_interest)) + var/mob/living/carbon/human/H = parrot_interest + var/obj/item/organ/external/affecting = H.get_organ(ran_zone(pick(parrot_dam_zone))) + + H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, "melee"), sharp = 1) + custom_emote(1, pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons.")) + + else + L.adjustBruteLoss(damage) + custom_emote(1, pick("pecks at [L].", "claws [L].")) + return + + //Otherwise, fly towards the mob! + else + walk_to(src, parrot_interest, 1, parrot_speed) + return +//-----STATE MISHAP + else //This should not happen. If it does lets reset everything and try again + walk(src,0) + parrot_interest = null + parrot_perch = null + drop_held_item() + parrot_state = PARROT_WANDER + return + +/* + * Procs + */ + +/mob/living/simple_animal/parrot/movement_delay() + if(client && stat == CONSCIOUS && parrot_state != "parrot_fly") + icon_state = "parrot_fly" + //Because the most appropriate place to set icon_state is movement_delay(), clearly + return ..() + +/mob/living/simple_animal/parrot/proc/search_for_item() + for(var/atom/movable/AM in view(src)) + //Skip items we already stole or are wearing or are too big + if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) + continue + + if(istype(AM, /obj/item)) + var/obj/item/I = AM + if(I.w_class < WEIGHT_CLASS_SMALL) + return I + + if(iscarbon(AM)) + var/mob/living/carbon/C = AM + if((C.l_hand && C.l_hand.w_class <= WEIGHT_CLASS_SMALL) || (C.r_hand && C.r_hand.w_class <= WEIGHT_CLASS_SMALL)) + return C + return null + +/mob/living/simple_animal/parrot/proc/search_for_perch() + for(var/obj/O in view(src)) + for(var/path in desired_perches) + if(istype(O, path)) + return O + return null + +//This proc was made to save on doing two 'in view' loops seperatly +/mob/living/simple_animal/parrot/proc/search_for_perch_and_item() + for(var/atom/movable/AM in view(src)) + for(var/perch_path in desired_perches) + if(istype(AM, perch_path)) + return AM + + //Skip items we already stole or are wearing or are too big + if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) + continue + + if(istype(AM, /obj/item)) + var/obj/item/I = AM + if(I.w_class <= WEIGHT_CLASS_SMALL) + return I + + if(iscarbon(AM)) + var/mob/living/carbon/C = AM + if(C.l_hand && C.l_hand.w_class <= WEIGHT_CLASS_SMALL || C.r_hand && C.r_hand.w_class <= WEIGHT_CLASS_SMALL) + return C + return null + + +/* + * Verbs - These are actually procs, but can be used as verbs by player-controlled parrots. + */ +/mob/living/simple_animal/parrot/proc/steal_from_ground() + set name = "Steal from ground" + set category = "Parrot" + set desc = "Grabs a nearby item." + + if(stat) + return -1 + + if(held_item) + to_chat(src, "You are already holding the [held_item]") + return 1 + + for(var/obj/item/I in view(1,src)) + //Make sure we're not already holding it and it's small enough + if(I.loc != src && I.w_class <= WEIGHT_CLASS_SMALL) + + //If we have a perch and the item is sitting on it, continue + if(!client && parrot_perch && I.loc == parrot_perch.loc) + continue + + held_item = I + I.loc = src + visible_message("[src] grabs the [held_item]!", "You grab the [held_item]!", "You hear the sounds of wings flapping furiously.") + return held_item + + to_chat(src, "There is nothing of interest to take.") + return 0 + +/mob/living/simple_animal/parrot/proc/steal_from_mob() + set name = "Steal from mob" + set category = "Parrot" + set desc = "Steals an item right out of a person's hand!" + + if(stat) + return -1 + + if(held_item) + to_chat(src, "You are already holding the [held_item]") + return 1 + + var/obj/item/stolen_item = null + + for(var/mob/living/carbon/C in view(1,src)) + if(C.l_hand && C.l_hand.w_class <= WEIGHT_CLASS_SMALL) + stolen_item = C.l_hand + + if(C.r_hand && C.r_hand.w_class <= WEIGHT_CLASS_SMALL) + stolen_item = C.r_hand + + if(stolen_item) + C.unEquip(stolen_item) + held_item = stolen_item + stolen_item.loc = src + visible_message("[src] grabs the [held_item] out of [C]'s hand!", "You snag the [held_item] out of [C]'s hand!", "You hear the sounds of wings flapping furiously.") + return held_item + + to_chat(src, "There is nothing of interest to take.") + return 0 + +/mob/living/simple_animal/parrot/verb/drop_held_item_player() + set name = "Drop held item" + set category = "Parrot" + set desc = "Drop the item you're holding." + + if(stat) + return + + src.drop_held_item() + + return + +/mob/living/simple_animal/parrot/proc/drop_held_item(var/drop_gently = 1) + set name = "Drop held item" + set category = "Parrot" + set desc = "Drop the item you're holding." + + if(stat) + return -1 + + if(!held_item) + to_chat(src, "You have nothing to drop!") + return 0 + + if(!drop_gently) + if(istype(held_item, /obj/item/grenade)) + var/obj/item/grenade/G = held_item + G.loc = src.loc + G.prime() + to_chat(src, "You let go of the [held_item]!") + held_item = null + return 1 + + to_chat(src, "You drop the [held_item].") + + held_item.loc = src.loc + held_item = null + return 1 + +/mob/living/simple_animal/parrot/proc/perch_player() + set name = "Sit" + set category = "Parrot" + set desc = "Sit on a nice comfy perch." + + if(stat || !client) + return + + if(icon_state == "parrot_fly") + for(var/atom/movable/AM in view(src,1)) + for(var/perch_path in desired_perches) + if(istype(AM, perch_path)) + src.loc = AM.loc + icon_state = "parrot_sit" + return + to_chat(src, "There is no perch nearby to sit on.") + return + +/* + * Sub-types + */ +/mob/living/simple_animal/parrot/Poly + name = "Poly" + desc = "Poly the Parrot. An expert on quantum cracker theory." + clean_speak = list( + "Poly wanna cracker!", + "Check the singlo, you chucklefucks!", + "Check the tesla, you shits!", + "STOP HOT-WIRING THE ENGINE, FUCKING CHRIST!", + "Wire the solars, you lazy bums!", + "WHO TOOK THE DAMN HARDSUITS?", + "OH GOD ITS FREE CALL THE SHUTTLE", + "Why are there so many atmos alerts?", + "OH GOD WHY WOULD YOU TURN ON THE PA BEFORE CONTAINMENT IS UP?", + "Remember to lock the emitters!", + "Stop goofing off and repair the goddam station!", + "The singularity is not your friend!", + "What were the wires again?", + "Goddam emaggers!" + ) + unique_pet = TRUE + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/parrot/Poly/New() + ears = new /obj/item/radio/headset/headset_eng(src) + available_channels = list(":e") + ..() + +/mob/living/simple_animal/parrot/handle_message_mode(var/message_mode, list/message_pieces, var/verb, var/used_radios) + if(message_mode && istype(ears)) + ears.talk_into(src, message_pieces, message_mode, verb) + used_radios += ears + +/mob/living/simple_animal/parrot/hear_say(list/message_pieces, verb = "says", italics = 0, mob/speaker = null, sound/speech_sound, sound_vol, sound_frequency) + if(speaker != src && prob(50)) + parrot_hear(html_decode(multilingual_to_message(message_pieces))) + ..() + + + +/mob/living/simple_animal/parrot/hear_radio(list/message_pieces, var/verb="says", var/part_a, var/part_b, var/mob/speaker = null, var/hard_to_hear = 0, var/atom/follow_target) + if(speaker != src && prob(50)) + parrot_hear(html_decode(multilingual_to_message(message_pieces))) + ..() + + +/mob/living/simple_animal/parrot/proc/parrot_hear(var/message="") + if(!message || stat) + return + speech_buffer.Add(message) diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm index 948c16314c9..663838d3a45 100644 --- a/code/modules/mob/living/simple_animal/shade.dm +++ b/code/modules/mob/living/simple_animal/shade.dm @@ -1,61 +1,61 @@ -/mob/living/simple_animal/shade - name = "Shade" - real_name = "Shade" - desc = "A bound spirit" - icon = 'icons/mob/mob.dmi' - icon_state = "shade" - icon_living = "shade" - icon_dead = "shade_dead" - maxHealth = 50 - health = 50 - speak_emote = list("hisses") - emote_hear = list("wails","screeches") - response_help = "puts their hand through" - response_disarm = "flails at" - response_harm = "punches the" - melee_damage_lower = 5 - melee_damage_upper = 15 - attacktext = "drains the life from" - minbodytemp = 0 - maxbodytemp = 4000 - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - speed = -1 - stop_automated_movement = 1 - status_flags = 0 - faction = list("cult") - status_flags = CANPUSH - loot = list(/obj/item/reagent_containers/food/snacks/ectoplasm) - del_on_death = 1 - deathmessage = "lets out a contented sigh as their form unwinds." - -/mob/living/simple_animal/shade/death(gibbed) - . = ..() - SSticker.mode.remove_cultist(src.mind, FALSE) - -/mob/living/simple_animal/shade/attackby(var/obj/item/O as obj, var/mob/user as mob) //Marker -Agouri - if(istype(O, /obj/item/soulstone)) - O.transfer_soul("SHADE", src, user) - else - if(O.force) - var/damage = O.force - if(O.damtype == STAMINA) - damage = 0 - health -= damage - user.visible_message("[src] has been attacked with the [O] by [user]. ") - else - user.visible_message("[user] gently taps [src] with the [O]. ", "This weapon is ineffective, it does no damage.") - return - -/mob/living/simple_animal/shade/cult/Initialize(mapload) - . = ..() - name = SSticker.cultdat?.shade_name - real_name = SSticker.cultdat?.shade_name - icon_state = SSticker.cultdat?.shade_icon_state - -/mob/living/simple_animal/shade/sword - universal_speak = 1 - faction = list("neutral") - -/mob/living/simple_animal/shade/sword/Initialize(mapload) - .=..() - status_flags |= GODMODE \ No newline at end of file +/mob/living/simple_animal/shade + name = "Shade" + real_name = "Shade" + desc = "A bound spirit" + icon = 'icons/mob/mob.dmi' + icon_state = "shade" + icon_living = "shade" + icon_dead = "shade_dead" + maxHealth = 50 + health = 50 + speak_emote = list("hisses") + emote_hear = list("wails","screeches") + response_help = "puts their hand through" + response_disarm = "flails at" + response_harm = "punches the" + melee_damage_lower = 5 + melee_damage_upper = 15 + attacktext = "drains the life from" + minbodytemp = 0 + maxbodytemp = 4000 + atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + speed = -1 + stop_automated_movement = 1 + status_flags = 0 + faction = list("cult") + status_flags = CANPUSH + loot = list(/obj/item/reagent_containers/food/snacks/ectoplasm) + del_on_death = 1 + deathmessage = "lets out a contented sigh as their form unwinds." + +/mob/living/simple_animal/shade/death(gibbed) + . = ..() + SSticker.mode.remove_cultist(src.mind, FALSE) + +/mob/living/simple_animal/shade/attackby(var/obj/item/O as obj, var/mob/user as mob) //Marker -Agouri + if(istype(O, /obj/item/soulstone)) + O.transfer_soul("SHADE", src, user) + else + if(O.force) + var/damage = O.force + if(O.damtype == STAMINA) + damage = 0 + health -= damage + user.visible_message("[src] has been attacked with the [O] by [user]. ") + else + user.visible_message("[user] gently taps [src] with the [O]. ", "This weapon is ineffective, it does no damage.") + return + +/mob/living/simple_animal/shade/cult/Initialize(mapload) + . = ..() + name = SSticker.cultdat?.shade_name + real_name = SSticker.cultdat?.shade_name + icon_state = SSticker.cultdat?.shade_icon_state + +/mob/living/simple_animal/shade/sword + universal_speak = 1 + faction = list("neutral") + +/mob/living/simple_animal/shade/sword/Initialize(mapload) + .=..() + status_flags |= GODMODE diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 01c1f7cfe43..7665d20f932 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1,615 +1,615 @@ -/mob/living/simple_animal - name = "animal" - icon = 'icons/mob/animal.dmi' - health = 20 - maxHealth = 20 - gender = PLURAL //placeholder - - universal_understand = 1 - universal_speak = 0 - status_flags = CANPUSH - - var/icon_living = "" - var/icon_dead = "" - var/icon_resting = "" - var/icon_gib = null //We only try to show a gibbing animation if this exists. - var/flip_on_death = FALSE //Flip the sprite upside down on death. Mostly here for things lacking custom dead sprites. - - var/list/speak = list() - var/speak_chance = 0 - var/list/emote_hear = list() //Hearable emotes - var/list/emote_see = list() //Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps - - var/turns_per_move = 1 - var/turns_since_move = 0 - var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals. - var/wander = 1 // Does the mob wander around when idle? - var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it. - - //Interaction - var/response_help = "pokes" - var/response_disarm = "shoves" - var/response_harm = "hits" - var/harm_intent_damage = 3 - var/force_threshold = 0 //Minimum force required to deal any damage - - //Temperature effect - var/minbodytemp = 250 - var/maxbodytemp = 350 - var/heat_damage_per_tick = 2 //amount of damage applied if animal's body temperature is higher than maxbodytemp - var/cold_damage_per_tick = 2 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp - - //Healable by medical stacks? Defaults to yes. - var/healable = 1 - - //Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage - var/list/atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) //Leaving something at 0 means it's off - has no maximum - var/unsuitable_atmos_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above - - //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly - var/melee_damage_lower = 0 - var/melee_damage_upper = 0 - var/obj_damage = 0 //how much damage this simple animal does to objects, if any - var/armour_penetration = 0 //How much armour they ignore, as a flat reduction from the targets armour value - var/melee_damage_type = BRUTE //Damage type of a simple mob's melee attack, should it do damage. - var/list/damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) // 1 for full damage , 0 for none , -1 for 1:1 heal from that source - var/attacktext = "attacks" - var/attack_sound = null - var/friendly = "nuzzles" //If the mob does no damage with it's attack - var/environment_smash = ENVIRONMENT_SMASH_NONE //Set to 1 to allow breaking of crates,lockers,racks,tables; 2 for walls; 3 for Rwalls - - var/speed = 1 //LETS SEE IF I CAN SET SPEEDS FOR SIMPLE MOBS WITHOUT DESTROYING EVERYTHING. Higher speed is slower, negative speed is faster - var/can_hide = 0 - - var/obj/item/clothing/accessory/petcollar/pcollar = null - var/collar_type //if the mob has collar sprites, define them. - var/unique_pet = FALSE // if the mob can be renamed - var/can_collar = FALSE // can add collar to mob or not - - //Hot simple_animal baby making vars - var/list/childtype = null - var/next_scan_time = 0 - var/animal_species //Sorry, no spider+corgi buttbabies. - - var/buffed = 0 //In the event that you want to have a buffing effect on the mob, but don't want it to stack with other effects, any outside force that applies a buff to a simple mob should at least set this to 1, so we have something to check against - var/gold_core_spawnable = NO_SPAWN //If the mob can be spawned with a gold slime core. HOSTILE_SPAWN are spawned with plasma, FRIENDLY_SPAWN are spawned with blood - - var/mob/living/carbon/human/master_commander = null //holding var for determining who own/controls a sentient simple animal (for sentience potions). - - var/datum/component/spawner/nest - - var/sentience_type = SENTIENCE_ORGANIC // Sentience type, for slime potions - - var/list/loot = list() //list of things spawned at mob's loc when it dies - var/del_on_death = 0 //causes mob to be deleted on death, useful for mobs that spawn lootable corpses - var/deathmessage = "" - var/death_sound = null //The sound played on death - - var/allow_movement_on_non_turfs = FALSE - - var/attacked_sound = "punch" - - var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever) - var/can_have_ai = TRUE //once we have become sentient, we can never go back - - var/shouldwakeup = FALSE //convenience var for forcibly waking up an idling AI on next check. - - //domestication - var/tame = 0 - - var/my_z // I don't want to confuse this with client registered_z - -/mob/living/simple_animal/Initialize(mapload) - . = ..() - GLOB.simple_animals[AIStatus] += src - if(gender == PLURAL) - gender = pick(MALE, FEMALE) - if(!real_name) - real_name = name - if(!loc) - stack_trace("Simple animal being instantiated in nullspace") - verbs -= /mob/verb/observe - if(!can_hide) - verbs -= /mob/living/simple_animal/verb/hide - if(pcollar) - pcollar = new(src) - regenerate_icons() - -/mob/living/simple_animal/Destroy() - QDEL_NULL(pcollar) - master_commander = null - GLOB.simple_animals[AIStatus] -= src - if(SSnpcpool.state == SS_PAUSED && LAZYLEN(SSnpcpool.currentrun)) - SSnpcpool.currentrun -= src - - if(nest) - nest.spawned_mobs -= src - nest = null - - var/turf/T = get_turf(src) - if (T && AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - - return ..() - -/mob/living/simple_animal/handle_atom_del(atom/A) - if(A == pcollar) - pcollar = null - return ..() - -/mob/living/simple_animal/examine(mob/user) - . = ..() - if(stat == DEAD) - . += "Upon closer examination, [p_they()] appear[p_s()] to be dead." - -/mob/living/simple_animal/updatehealth(reason = "none given") - ..(reason) - health = Clamp(health, 0, maxHealth) - med_hud_set_status() - -/mob/living/simple_animal/StartResting(updating = 1) - ..() - if(icon_resting && stat != DEAD) - icon_state = icon_resting - if(collar_type) - collar_type = "[initial(collar_type)]_rest" - regenerate_icons() - -/mob/living/simple_animal/StopResting(updating = 1) - ..() - if(icon_resting && stat != DEAD) - icon_state = icon_living - if(collar_type) - collar_type = "[initial(collar_type)]" - regenerate_icons() - -/mob/living/simple_animal/update_stat(reason = "none given") - if(status_flags & GODMODE) - return - - ..(reason) - if(stat != DEAD) - if(health < 1) - death() - create_debug_log("died of damage, trigger reason: [reason]") - -/mob/living/simple_animal/proc/handle_automated_action() - set waitfor = FALSE - return - -/mob/living/simple_animal/proc/handle_automated_movement() - set waitfor = FALSE - if(!stop_automated_movement && wander) - if((isturf(loc) || allow_movement_on_non_turfs) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. - turns_since_move++ - if(turns_since_move >= turns_per_move) - if(!(stop_automated_movement_when_pulled && pulledby)) //Soma animals don't move when pulled - var/anydir = pick(cardinal) - if(Process_Spacemove(anydir)) - Move(get_step(src,anydir), anydir) - turns_since_move = 0 - return 1 - -/mob/living/simple_animal/proc/handle_automated_speech(override) - set waitfor = FALSE - if(speak_chance) - if(prob(speak_chance) || override) - if(speak && speak.len) - if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) - var/length = speak.len - if(emote_hear && emote_hear.len) - length += emote_hear.len - if(emote_see && emote_see.len) - length += emote_see.len - var/randomValue = rand(1,length) - if(randomValue <= speak.len) - say(pick(speak)) - else - randomValue -= speak.len - if(emote_see && randomValue <= emote_see.len) - custom_emote(1, pick(emote_see)) - else - custom_emote(2, pick(emote_hear)) - else - say(pick(speak)) - else - if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - custom_emote(1, pick(emote_see)) - if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) - custom_emote(2, pick(emote_hear)) - if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - var/length = emote_hear.len + emote_see.len - var/pick = rand(1,length) - if(pick <= emote_see.len) - custom_emote(1, pick(emote_see)) - else - custom_emote(2,pick(emote_hear)) - - -/mob/living/simple_animal/handle_environment(datum/gas_mixture/environment) - var/atmos_suitable = 1 - - var/areatemp = get_temperature(environment) - - if(abs(areatemp - bodytemperature) > 5 && !(BREATHLESS in mutations)) - var/diff = areatemp - bodytemperature - diff = diff / 5 - bodytemperature += diff - - var/tox = environment.toxins - var/oxy = environment.oxygen - var/n2 = environment.nitrogen - var/co2 = environment.carbon_dioxide - - if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"]) - atmos_suitable = 0 - throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) - else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"]) - atmos_suitable = 0 - throw_alert("too_much_oxy", /obj/screen/alert/too_much_oxy) - else - clear_alert("not_enough_oxy") - clear_alert("too_much_oxy") - - if(atmos_requirements["min_tox"] && tox < atmos_requirements["min_tox"]) - atmos_suitable = 0 - throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox) - else if(atmos_requirements["max_tox"] && tox > atmos_requirements["max_tox"]) - atmos_suitable = 0 - throw_alert("too_much_tox", /obj/screen/alert/too_much_tox) - else - clear_alert("too_much_tox") - clear_alert("not_enough_tox") - - if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"]) - atmos_suitable = 0 - else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"]) - atmos_suitable = 0 - - if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"]) - atmos_suitable = 0 - else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"]) - atmos_suitable = 0 - - if(!atmos_suitable) - adjustHealth(unsuitable_atmos_damage) - - handle_temperature_damage() - -/mob/living/simple_animal/proc/handle_temperature_damage() - if(bodytemperature < minbodytemp) - adjustHealth(cold_damage_per_tick) - else if(bodytemperature > maxbodytemp) - adjustHealth(heat_damage_per_tick) - -/mob/living/simple_animal/gib() - if(icon_gib) - flick(icon_gib, src) - if(butcher_results) - var/atom/Tsec = drop_location() - for(var/path in butcher_results) - for(var/i in 1 to butcher_results[path]) - new path(Tsec) - if(pcollar) - pcollar.forceMove(drop_location()) - pcollar = null - ..() - -/mob/living/simple_animal/emote(act, m_type = 1, message = null, force) - if(stat) - return - act = lowertext(act) - switch(act) //IMPORTANT: Emotes MUST NOT CONFLICT anywhere along the chain. - if("scream") - message = "\The [src] whimpers." - m_type = 2 - if("help") - to_chat(src, "scream") - - ..() - -/mob/living/simple_animal/say_quote(message) - var/verb = "says" - - if(speak_emote.len) - verb = pick(speak_emote) - - return verb - -/mob/living/simple_animal/movement_delay() - . = ..() - - . = speed - - . += config.animal_delay - -/mob/living/simple_animal/Stat() - ..() - if(statpanel("Status")) - stat(null, "Health: [round((health / maxHealth) * 100)]%") - return TRUE - -/mob/living/simple_animal/proc/drop_loot() - if(loot.len) - for(var/i in loot) - new i(loc) - -/mob/living/simple_animal/revive() - ..() - density = initial(density) - -/mob/living/simple_animal/death(gibbed) - // Only execute the below if we successfully died - . = ..() - if(!.) - return FALSE - flying = FALSE - if(nest) - nest.spawned_mobs -= src - nest = null - drop_loot() - if(!gibbed) - if(death_sound) - playsound(get_turf(src),death_sound, 200, 1) - if(deathmessage) - visible_message("\The [src] [deathmessage]") - else if(!del_on_death) - visible_message("\The [src] stops moving...") - if(del_on_death) - //Prevent infinite loops if the mob Destroy() is overridden in such - //a manner as to cause a call to death() again - del_on_death = FALSE - ghostize() - qdel(src) - else - health = 0 - icon_state = icon_dead - if(flip_on_death) - transform = transform.Turn(180) - density = 0 - if(collar_type) - collar_type = "[initial(collar_type)]_dead" - regenerate_icons() - -/mob/living/simple_animal/proc/CanAttack(atom/the_target) - if(see_invisible < the_target.invisibility) - return FALSE - if(ismob(the_target)) - var/mob/M = the_target - if(M.status_flags & GODMODE) - return FALSE - if(isliving(the_target)) - var/mob/living/L = the_target - if(L.stat != CONSCIOUS) - return FALSE - if(ismecha(the_target)) - var/obj/mecha/M = the_target - if(M.occupant) - return FALSE - if(isspacepod(the_target)) - var/obj/spacepod/S = the_target - if(S.pilot) - return FALSE - return TRUE - -/mob/living/simple_animal/handle_fire() - return TRUE - -/mob/living/simple_animal/IgniteMob() - return FALSE - -/mob/living/simple_animal/ExtinguishMob() - return - -/mob/living/simple_animal/revive() - ..() - health = maxHealth - icon = initial(icon) - icon_state = icon_living - density = initial(density) - update_canmove() - flying = initial(flying) - if(collar_type) - collar_type = "[initial(collar_type)]" - regenerate_icons() - -/mob/living/simple_animal/proc/make_babies() // <3 <3 <3 - if(gender != FEMALE || stat || next_scan_time > world.time || !childtype || !animal_species || !SSticker.IsRoundInProgress()) - return FALSE - next_scan_time = world.time + 400 - - var/alone = TRUE - var/mob/living/simple_animal/partner - var/children = 0 - - for(var/mob/M in oview(7, src)) - if(M.stat != CONSCIOUS) //Check if it's conscious FIRST. - continue - else if(istype(M, childtype)) //Check for children SECOND. - children++ - else if(istype(M, animal_species)) - if(M.ckey) - continue - else if(!istype(M, childtype) && M.gender == MALE) //Better safe than sorry ;_; - partner = M - else if(isliving(M) && !faction_check_mob(M)) //shyness check. we're not shy in front of things that share a faction with us. - return //we never mate when not alone, so just abort early - - if(alone && partner && children < 3) - var/childspawn = pickweight(childtype) - var/turf/target = get_turf(loc) - if(target) - return new childspawn(target) - -/mob/living/simple_animal/show_inv(mob/user as mob) - if(!can_collar) - return - - user.set_machine(src) - var/dat = "
        Collar:[(pcollar && !(pcollar.flags & ABSTRACT)) ? pcollar : "Empty"]
        " - dat += "Close" - - var/datum/browser/popup = new(user, "mob\ref[src]", "[src]", 440, 250) - popup.set_content(dat) - popup.open() - -/mob/living/simple_animal/get_item_by_slot(slot_id) - switch(slot_id) - if(slot_collar) - return pcollar - . = ..() - -/mob/living/simple_animal/can_equip(obj/item/I, slot, disable_warning = 0) - // . = ..() // Do not call parent. We do not want animals using their hand slots. - switch(slot) - if(slot_collar) - if(pcollar) - return FALSE - if(!can_collar) - return FALSE - if(!istype(I, /obj/item/clothing/accessory/petcollar)) - return FALSE - return TRUE - -/mob/living/simple_animal/equip_to_slot(obj/item/W, slot) - if(!istype(W)) - return FALSE - - if(!slot) - return FALSE - - W.layer = ABOVE_HUD_LAYER - W.plane = ABOVE_HUD_PLANE - - switch(slot) - if(slot_collar) - add_collar(W) - -/mob/living/simple_animal/unEquip(obj/item/I, force) - . = ..() - if(!. || !I) - return - - if(I == pcollar) - pcollar = null - regenerate_icons() - -/mob/living/simple_animal/get_access() - . = ..() - if(pcollar) - . |= pcollar.GetAccess() - -/mob/living/simple_animal/update_canmove(delay_action_updates = 0) - if(paralysis || stunned || IsWeakened() || stat || resting) - drop_r_hand() - drop_l_hand() - canmove = 0 - else if(buckled) - canmove = 0 - else - canmove = 1 - if(!canmove) - walk(src, 0) //stop mid walk - - update_transform() - if(!delay_action_updates) - update_action_buttons_icon() - return canmove - -/mob/living/simple_animal/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/changed = FALSE - - if(resize != RESIZE_DEFAULT_SIZE) - changed = TRUE - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) - -/mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion - toggle_ai(AI_OFF) - can_have_ai = FALSE - -/mob/living/simple_animal/grant_death_vision() - sight |= SEE_TURFS - sight |= SEE_MOBS - sight |= SEE_OBJS - see_in_dark = 8 - see_invisible = SEE_INVISIBLE_OBSERVER - sync_lighting_plane_alpha() - -/mob/living/simple_animal/update_sight() - if(!client) - return - - if(stat == DEAD) - grant_death_vision() - return - - see_invisible = initial(see_invisible) - see_in_dark = initial(see_in_dark) - sight = initial(sight) - - if(client.eye != src) - var/atom/A = client.eye - if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. - return - - SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) - sync_lighting_plane_alpha() - -/mob/living/simple_animal/proc/toggle_ai(togglestatus) - if(!can_have_ai && (togglestatus != AI_OFF)) - return - if(AIStatus != togglestatus) - if(togglestatus > 0 && togglestatus < 5) - if(togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) - var/turf/T = get_turf(src) - if(AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - else - SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src - GLOB.simple_animals[AIStatus] -= src - GLOB.simple_animals[togglestatus] += src - AIStatus = togglestatus - else - stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") - -/mob/living/simple_animal/proc/consider_wakeup() - if(pulledby || shouldwakeup) - toggle_ai(AI_ON) - -/mob/living/simple_animal/adjustHealth(amount, updating_health = TRUE) - . = ..() - if(!ckey && !stat)//Not unconscious - if(AIStatus == AI_IDLE) - toggle_ai(AI_ON) - -/mob/living/simple_animal/onTransitZ(old_z, new_z) - ..() - if(AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src - toggle_ai(initial(AIStatus)) - -/mob/living/simple_animal/proc/add_collar(obj/item/clothing/accessory/petcollar/P, mob/user) - if(QDELETED(P) || pcollar) - return - if(user && !user.unEquip(P)) - return - P.forceMove(src) - P.equipped(src) - pcollar = P - regenerate_icons() - if(user) - to_chat(user, "You put [P] around [src]'s neck.") - if(P.tagname && !unique_pet) - name = P.tagname - real_name = P.tagname - -/mob/living/simple_animal/regenerate_icons() - if(pcollar && collar_type) - cut_overlays() - add_overlay("[collar_type]collar") - add_overlay("[collar_type]tag") \ No newline at end of file +/mob/living/simple_animal + name = "animal" + icon = 'icons/mob/animal.dmi' + health = 20 + maxHealth = 20 + gender = PLURAL //placeholder + + universal_understand = 1 + universal_speak = 0 + status_flags = CANPUSH + + var/icon_living = "" + var/icon_dead = "" + var/icon_resting = "" + var/icon_gib = null //We only try to show a gibbing animation if this exists. + var/flip_on_death = FALSE //Flip the sprite upside down on death. Mostly here for things lacking custom dead sprites. + + var/list/speak = list() + var/speak_chance = 0 + var/list/emote_hear = list() //Hearable emotes + var/list/emote_see = list() //Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps + + var/turns_per_move = 1 + var/turns_since_move = 0 + var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals. + var/wander = 1 // Does the mob wander around when idle? + var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it. + + //Interaction + var/response_help = "pokes" + var/response_disarm = "shoves" + var/response_harm = "hits" + var/harm_intent_damage = 3 + var/force_threshold = 0 //Minimum force required to deal any damage + + //Temperature effect + var/minbodytemp = 250 + var/maxbodytemp = 350 + var/heat_damage_per_tick = 2 //amount of damage applied if animal's body temperature is higher than maxbodytemp + var/cold_damage_per_tick = 2 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp + + //Healable by medical stacks? Defaults to yes. + var/healable = 1 + + //Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage + var/list/atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) //Leaving something at 0 means it's off - has no maximum + var/unsuitable_atmos_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above + + //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly + var/melee_damage_lower = 0 + var/melee_damage_upper = 0 + var/obj_damage = 0 //how much damage this simple animal does to objects, if any + var/armour_penetration = 0 //How much armour they ignore, as a flat reduction from the targets armour value + var/melee_damage_type = BRUTE //Damage type of a simple mob's melee attack, should it do damage. + var/list/damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) // 1 for full damage , 0 for none , -1 for 1:1 heal from that source + var/attacktext = "attacks" + var/attack_sound = null + var/friendly = "nuzzles" //If the mob does no damage with it's attack + var/environment_smash = ENVIRONMENT_SMASH_NONE //Set to 1 to allow breaking of crates,lockers,racks,tables; 2 for walls; 3 for Rwalls + + var/speed = 1 //LETS SEE IF I CAN SET SPEEDS FOR SIMPLE MOBS WITHOUT DESTROYING EVERYTHING. Higher speed is slower, negative speed is faster + var/can_hide = 0 + + var/obj/item/clothing/accessory/petcollar/pcollar = null + var/collar_type //if the mob has collar sprites, define them. + var/unique_pet = FALSE // if the mob can be renamed + var/can_collar = FALSE // can add collar to mob or not + + //Hot simple_animal baby making vars + var/list/childtype = null + var/next_scan_time = 0 + var/animal_species //Sorry, no spider+corgi buttbabies. + + var/buffed = 0 //In the event that you want to have a buffing effect on the mob, but don't want it to stack with other effects, any outside force that applies a buff to a simple mob should at least set this to 1, so we have something to check against + var/gold_core_spawnable = NO_SPAWN //If the mob can be spawned with a gold slime core. HOSTILE_SPAWN are spawned with plasma, FRIENDLY_SPAWN are spawned with blood + + var/mob/living/carbon/human/master_commander = null //holding var for determining who own/controls a sentient simple animal (for sentience potions). + + var/datum/component/spawner/nest + + var/sentience_type = SENTIENCE_ORGANIC // Sentience type, for slime potions + + var/list/loot = list() //list of things spawned at mob's loc when it dies + var/del_on_death = 0 //causes mob to be deleted on death, useful for mobs that spawn lootable corpses + var/deathmessage = "" + var/death_sound = null //The sound played on death + + var/allow_movement_on_non_turfs = FALSE + + var/attacked_sound = "punch" + + var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever) + var/can_have_ai = TRUE //once we have become sentient, we can never go back + + var/shouldwakeup = FALSE //convenience var for forcibly waking up an idling AI on next check. + + //domestication + var/tame = 0 + + var/my_z // I don't want to confuse this with client registered_z + +/mob/living/simple_animal/Initialize(mapload) + . = ..() + GLOB.simple_animals[AIStatus] += src + if(gender == PLURAL) + gender = pick(MALE, FEMALE) + if(!real_name) + real_name = name + if(!loc) + stack_trace("Simple animal being instantiated in nullspace") + verbs -= /mob/verb/observe + if(!can_hide) + verbs -= /mob/living/simple_animal/verb/hide + if(pcollar) + pcollar = new(src) + regenerate_icons() + +/mob/living/simple_animal/Destroy() + QDEL_NULL(pcollar) + master_commander = null + GLOB.simple_animals[AIStatus] -= src + if(SSnpcpool.state == SS_PAUSED && LAZYLEN(SSnpcpool.currentrun)) + SSnpcpool.currentrun -= src + + if(nest) + nest.spawned_mobs -= src + nest = null + + var/turf/T = get_turf(src) + if (T && AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src + + return ..() + +/mob/living/simple_animal/handle_atom_del(atom/A) + if(A == pcollar) + pcollar = null + return ..() + +/mob/living/simple_animal/examine(mob/user) + . = ..() + if(stat == DEAD) + . += "Upon closer examination, [p_they()] appear[p_s()] to be dead." + +/mob/living/simple_animal/updatehealth(reason = "none given") + ..(reason) + health = Clamp(health, 0, maxHealth) + med_hud_set_status() + +/mob/living/simple_animal/StartResting(updating = 1) + ..() + if(icon_resting && stat != DEAD) + icon_state = icon_resting + if(collar_type) + collar_type = "[initial(collar_type)]_rest" + regenerate_icons() + +/mob/living/simple_animal/StopResting(updating = 1) + ..() + if(icon_resting && stat != DEAD) + icon_state = icon_living + if(collar_type) + collar_type = "[initial(collar_type)]" + regenerate_icons() + +/mob/living/simple_animal/update_stat(reason = "none given") + if(status_flags & GODMODE) + return + + ..(reason) + if(stat != DEAD) + if(health < 1) + death() + create_debug_log("died of damage, trigger reason: [reason]") + +/mob/living/simple_animal/proc/handle_automated_action() + set waitfor = FALSE + return + +/mob/living/simple_animal/proc/handle_automated_movement() + set waitfor = FALSE + if(!stop_automated_movement && wander) + if((isturf(loc) || allow_movement_on_non_turfs) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. + turns_since_move++ + if(turns_since_move >= turns_per_move) + if(!(stop_automated_movement_when_pulled && pulledby)) //Soma animals don't move when pulled + var/anydir = pick(cardinal) + if(Process_Spacemove(anydir)) + Move(get_step(src,anydir), anydir) + turns_since_move = 0 + return 1 + +/mob/living/simple_animal/proc/handle_automated_speech(override) + set waitfor = FALSE + if(speak_chance) + if(prob(speak_chance) || override) + if(speak && speak.len) + if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) + var/length = speak.len + if(emote_hear && emote_hear.len) + length += emote_hear.len + if(emote_see && emote_see.len) + length += emote_see.len + var/randomValue = rand(1,length) + if(randomValue <= speak.len) + say(pick(speak)) + else + randomValue -= speak.len + if(emote_see && randomValue <= emote_see.len) + custom_emote(1, pick(emote_see)) + else + custom_emote(2, pick(emote_hear)) + else + say(pick(speak)) + else + if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + custom_emote(1, pick(emote_see)) + if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) + custom_emote(2, pick(emote_hear)) + if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + var/length = emote_hear.len + emote_see.len + var/pick = rand(1,length) + if(pick <= emote_see.len) + custom_emote(1, pick(emote_see)) + else + custom_emote(2,pick(emote_hear)) + + +/mob/living/simple_animal/handle_environment(datum/gas_mixture/environment) + var/atmos_suitable = 1 + + var/areatemp = get_temperature(environment) + + if(abs(areatemp - bodytemperature) > 5 && !(BREATHLESS in mutations)) + var/diff = areatemp - bodytemperature + diff = diff / 5 + bodytemperature += diff + + var/tox = environment.toxins + var/oxy = environment.oxygen + var/n2 = environment.nitrogen + var/co2 = environment.carbon_dioxide + + if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"]) + atmos_suitable = 0 + throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) + else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"]) + atmos_suitable = 0 + throw_alert("too_much_oxy", /obj/screen/alert/too_much_oxy) + else + clear_alert("not_enough_oxy") + clear_alert("too_much_oxy") + + if(atmos_requirements["min_tox"] && tox < atmos_requirements["min_tox"]) + atmos_suitable = 0 + throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox) + else if(atmos_requirements["max_tox"] && tox > atmos_requirements["max_tox"]) + atmos_suitable = 0 + throw_alert("too_much_tox", /obj/screen/alert/too_much_tox) + else + clear_alert("too_much_tox") + clear_alert("not_enough_tox") + + if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"]) + atmos_suitable = 0 + else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"]) + atmos_suitable = 0 + + if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"]) + atmos_suitable = 0 + else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"]) + atmos_suitable = 0 + + if(!atmos_suitable) + adjustHealth(unsuitable_atmos_damage) + + handle_temperature_damage() + +/mob/living/simple_animal/proc/handle_temperature_damage() + if(bodytemperature < minbodytemp) + adjustHealth(cold_damage_per_tick) + else if(bodytemperature > maxbodytemp) + adjustHealth(heat_damage_per_tick) + +/mob/living/simple_animal/gib() + if(icon_gib) + flick(icon_gib, src) + if(butcher_results) + var/atom/Tsec = drop_location() + for(var/path in butcher_results) + for(var/i in 1 to butcher_results[path]) + new path(Tsec) + if(pcollar) + pcollar.forceMove(drop_location()) + pcollar = null + ..() + +/mob/living/simple_animal/emote(act, m_type = 1, message = null, force) + if(stat) + return + act = lowertext(act) + switch(act) //IMPORTANT: Emotes MUST NOT CONFLICT anywhere along the chain. + if("scream") + message = "\The [src] whimpers." + m_type = 2 + if("help") + to_chat(src, "scream") + + ..() + +/mob/living/simple_animal/say_quote(message) + var/verb = "says" + + if(speak_emote.len) + verb = pick(speak_emote) + + return verb + +/mob/living/simple_animal/movement_delay() + . = ..() + + . = speed + + . += config.animal_delay + +/mob/living/simple_animal/Stat() + ..() + if(statpanel("Status")) + stat(null, "Health: [round((health / maxHealth) * 100)]%") + return TRUE + +/mob/living/simple_animal/proc/drop_loot() + if(loot.len) + for(var/i in loot) + new i(loc) + +/mob/living/simple_animal/revive() + ..() + density = initial(density) + +/mob/living/simple_animal/death(gibbed) + // Only execute the below if we successfully died + . = ..() + if(!.) + return FALSE + flying = FALSE + if(nest) + nest.spawned_mobs -= src + nest = null + drop_loot() + if(!gibbed) + if(death_sound) + playsound(get_turf(src),death_sound, 200, 1) + if(deathmessage) + visible_message("\The [src] [deathmessage]") + else if(!del_on_death) + visible_message("\The [src] stops moving...") + if(del_on_death) + //Prevent infinite loops if the mob Destroy() is overridden in such + //a manner as to cause a call to death() again + del_on_death = FALSE + ghostize() + qdel(src) + else + health = 0 + icon_state = icon_dead + if(flip_on_death) + transform = transform.Turn(180) + density = 0 + if(collar_type) + collar_type = "[initial(collar_type)]_dead" + regenerate_icons() + +/mob/living/simple_animal/proc/CanAttack(atom/the_target) + if(see_invisible < the_target.invisibility) + return FALSE + if(ismob(the_target)) + var/mob/M = the_target + if(M.status_flags & GODMODE) + return FALSE + if(isliving(the_target)) + var/mob/living/L = the_target + if(L.stat != CONSCIOUS) + return FALSE + if(ismecha(the_target)) + var/obj/mecha/M = the_target + if(M.occupant) + return FALSE + if(isspacepod(the_target)) + var/obj/spacepod/S = the_target + if(S.pilot) + return FALSE + return TRUE + +/mob/living/simple_animal/handle_fire() + return TRUE + +/mob/living/simple_animal/IgniteMob() + return FALSE + +/mob/living/simple_animal/ExtinguishMob() + return + +/mob/living/simple_animal/revive() + ..() + health = maxHealth + icon = initial(icon) + icon_state = icon_living + density = initial(density) + update_canmove() + flying = initial(flying) + if(collar_type) + collar_type = "[initial(collar_type)]" + regenerate_icons() + +/mob/living/simple_animal/proc/make_babies() // <3 <3 <3 + if(gender != FEMALE || stat || next_scan_time > world.time || !childtype || !animal_species || !SSticker.IsRoundInProgress()) + return FALSE + next_scan_time = world.time + 400 + + var/alone = TRUE + var/mob/living/simple_animal/partner + var/children = 0 + + for(var/mob/M in oview(7, src)) + if(M.stat != CONSCIOUS) //Check if it's conscious FIRST. + continue + else if(istype(M, childtype)) //Check for children SECOND. + children++ + else if(istype(M, animal_species)) + if(M.ckey) + continue + else if(!istype(M, childtype) && M.gender == MALE) //Better safe than sorry ;_; + partner = M + else if(isliving(M) && !faction_check_mob(M)) //shyness check. we're not shy in front of things that share a faction with us. + return //we never mate when not alone, so just abort early + + if(alone && partner && children < 3) + var/childspawn = pickweight(childtype) + var/turf/target = get_turf(loc) + if(target) + return new childspawn(target) + +/mob/living/simple_animal/show_inv(mob/user as mob) + if(!can_collar) + return + + user.set_machine(src) + var/dat = "
        Collar:[(pcollar && !(pcollar.flags & ABSTRACT)) ? pcollar : "Empty"]
        " + dat += "Close" + + var/datum/browser/popup = new(user, "mob\ref[src]", "[src]", 440, 250) + popup.set_content(dat) + popup.open() + +/mob/living/simple_animal/get_item_by_slot(slot_id) + switch(slot_id) + if(slot_collar) + return pcollar + . = ..() + +/mob/living/simple_animal/can_equip(obj/item/I, slot, disable_warning = 0) + // . = ..() // Do not call parent. We do not want animals using their hand slots. + switch(slot) + if(slot_collar) + if(pcollar) + return FALSE + if(!can_collar) + return FALSE + if(!istype(I, /obj/item/clothing/accessory/petcollar)) + return FALSE + return TRUE + +/mob/living/simple_animal/equip_to_slot(obj/item/W, slot) + if(!istype(W)) + return FALSE + + if(!slot) + return FALSE + + W.layer = ABOVE_HUD_LAYER + W.plane = ABOVE_HUD_PLANE + + switch(slot) + if(slot_collar) + add_collar(W) + +/mob/living/simple_animal/unEquip(obj/item/I, force) + . = ..() + if(!. || !I) + return + + if(I == pcollar) + pcollar = null + regenerate_icons() + +/mob/living/simple_animal/get_access() + . = ..() + if(pcollar) + . |= pcollar.GetAccess() + +/mob/living/simple_animal/update_canmove(delay_action_updates = 0) + if(paralysis || stunned || IsWeakened() || stat || resting) + drop_r_hand() + drop_l_hand() + canmove = 0 + else if(buckled) + canmove = 0 + else + canmove = 1 + if(!canmove) + walk(src, 0) //stop mid walk + + update_transform() + if(!delay_action_updates) + update_action_buttons_icon() + return canmove + +/mob/living/simple_animal/update_transform() + var/matrix/ntransform = matrix(transform) //aka transform.Copy() + var/changed = FALSE + + if(resize != RESIZE_DEFAULT_SIZE) + changed = TRUE + ntransform.Scale(resize) + resize = RESIZE_DEFAULT_SIZE + + if(changed) + animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) + +/mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion + toggle_ai(AI_OFF) + can_have_ai = FALSE + +/mob/living/simple_animal/grant_death_vision() + sight |= SEE_TURFS + sight |= SEE_MOBS + sight |= SEE_OBJS + see_in_dark = 8 + see_invisible = SEE_INVISIBLE_OBSERVER + sync_lighting_plane_alpha() + +/mob/living/simple_animal/update_sight() + if(!client) + return + + if(stat == DEAD) + grant_death_vision() + return + + see_invisible = initial(see_invisible) + see_in_dark = initial(see_in_dark) + sight = initial(sight) + + if(client.eye != src) + var/atom/A = client.eye + if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. + return + + SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT) + sync_lighting_plane_alpha() + +/mob/living/simple_animal/proc/toggle_ai(togglestatus) + if(!can_have_ai && (togglestatus != AI_OFF)) + return + if(AIStatus != togglestatus) + if(togglestatus > 0 && togglestatus < 5) + if(togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) + var/turf/T = get_turf(src) + if(AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src + else + SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src + GLOB.simple_animals[AIStatus] -= src + GLOB.simple_animals[togglestatus] += src + AIStatus = togglestatus + else + stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") + +/mob/living/simple_animal/proc/consider_wakeup() + if(pulledby || shouldwakeup) + toggle_ai(AI_ON) + +/mob/living/simple_animal/adjustHealth(amount, updating_health = TRUE) + . = ..() + if(!ckey && !stat)//Not unconscious + if(AIStatus == AI_IDLE) + toggle_ai(AI_ON) + +/mob/living/simple_animal/onTransitZ(old_z, new_z) + ..() + if(AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src + toggle_ai(initial(AIStatus)) + +/mob/living/simple_animal/proc/add_collar(obj/item/clothing/accessory/petcollar/P, mob/user) + if(QDELETED(P) || pcollar) + return + if(user && !user.unEquip(P)) + return + P.forceMove(src) + P.equipped(src) + pcollar = P + regenerate_icons() + if(user) + to_chat(user, "You put [P] around [src]'s neck.") + if(P.tagname && !unique_pet) + name = P.tagname + real_name = P.tagname + +/mob/living/simple_animal/regenerate_icons() + if(pcollar && collar_type) + cut_overlays() + add_overlay("[collar_type]collar") + add_overlay("[collar_type]tag") diff --git a/code/modules/mob/living/simple_animal/slime/death.dm b/code/modules/mob/living/simple_animal/slime/death.dm index 29a1adbd347..cf72363ac9a 100644 --- a/code/modules/mob/living/simple_animal/slime/death.dm +++ b/code/modules/mob/living/simple_animal/slime/death.dm @@ -29,4 +29,4 @@ /mob/living/simple_animal/slime/gib() death(TRUE) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/mob/living/simple_animal/slime/emote.dm b/code/modules/mob/living/simple_animal/slime/emote.dm index 7fc7f4c5f87..94806ede9d1 100644 --- a/code/modules/mob/living/simple_animal/slime/emote.dm +++ b/code/modules/mob/living/simple_animal/slime/emote.dm @@ -102,4 +102,4 @@ loc.audible_message(message) if(regenerate_icons) - regenerate_icons() \ No newline at end of file + regenerate_icons() diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index efd4c19d55c..deb9981645f 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -614,4 +614,4 @@ return 0 if(holding_still) return 0 - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index f02b3bd5918..339240af529 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -507,4 +507,4 @@ if(buckled) to_chat(src, "I can't vent crawl while feeding...") return - ..() \ No newline at end of file + ..() diff --git a/code/modules/mob/living/stat_states.dm b/code/modules/mob/living/stat_states.dm index 70cced62aab..57fd6e17cbc 100644 --- a/code/modules/mob/living/stat_states.dm +++ b/code/modules/mob/living/stat_states.dm @@ -73,4 +73,4 @@ return 1 /mob/living/proc/check_death_method() - return TRUE \ No newline at end of file + return TRUE diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index 72f136304d7..2e5c6e66137 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -598,4 +598,4 @@ else if(priority_absorb_key["self_message"]) to_chat(src, "[priority_absorb_key["self_message"]]") priority_absorb_key["stuns_absorbed"] += amount - return TRUE \ No newline at end of file + return TRUE diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm index a403aa39244..03b0ff5752b 100644 --- a/code/modules/mob/living/taste.dm +++ b/code/modules/mob/living/taste.dm @@ -27,4 +27,4 @@ // "something indescribable" -> too many tastes, not enough flavor. last_taste_time = world.time - last_taste_text = text_output \ No newline at end of file + last_taste_text = text_output diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 66b4f2f9a16..ada7669a520 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -1,70 +1,70 @@ -//handles setting lastKnownIP and computer_id for use by the ban systems as well as checking for multikeying -/mob/proc/update_Login_details() - //Multikey checks and logging - lastKnownIP = client.address - computer_id = client.computer_id - log_access_in(client) - create_attack_log("Logged in at [atom_loc_line(get_turf(src))]") - if(config.log_access) - for(var/mob/M in GLOB.player_list) - if(M == src) continue - if( M.key && (M.key != key) ) - var/matches - if( (M.lastKnownIP == client.address) ) - matches += "IP ([client.address])" - if( (M.computer_id == client.computer_id) ) - if(matches) matches += " and " - matches += "ID ([client.computer_id])" - if(!config.disable_cid_warn_popup) - spawn() alert("You have logged in already with another key this round, please log out of this one NOW or risk being banned!") - if(matches) - if(M.client) - message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(M)].", 1) - log_adminwarn("Notice: [key_name(src)] has the same [matches] as [key_name(M)].") - else - message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(M)] (no longer logged in). ", 1) - log_adminwarn("Notice: [key_name(src)] has the same [matches] as [key_name(M)] (no longer logged in).") - -/mob/Login() - GLOB.player_list |= src - update_Login_details() - world.update_status() - - client.images = null //remove the images such as AIs being unable to see runes - client.screen = list() //remove hud items just in case - if(client.click_intercept) - client.click_intercept.quit() // Let's not keep any old click_intercepts - - if(!hud_used) - create_mob_hud() - if(hud_used) - hud_used.show_hud(hud_used.hud_version) - - next_move = 1 - sight |= SEE_SELF - ..() - - reset_perspective(loc) - - - if(ckey in GLOB.deadmins) - verbs += /client/proc/readmin - - //Clear ability list and update from mob. - client.verbs -= ability_verbs - - if(abilities) - client.verbs |= abilities - - //HUD updates (antag hud, etc) - //readd this mob's HUDs (antag, med, etc) - reload_huds() - - add_click_catcher() - - if(viewing_alternate_appearances && viewing_alternate_appearances.len) - for(var/datum/alternate_appearance/AA in viewing_alternate_appearances) - AA.display_to(list(src)) - - update_client_colour(0) - update_morgue() +//handles setting lastKnownIP and computer_id for use by the ban systems as well as checking for multikeying +/mob/proc/update_Login_details() + //Multikey checks and logging + lastKnownIP = client.address + computer_id = client.computer_id + log_access_in(client) + create_attack_log("Logged in at [atom_loc_line(get_turf(src))]") + if(config.log_access) + for(var/mob/M in GLOB.player_list) + if(M == src) continue + if( M.key && (M.key != key) ) + var/matches + if( (M.lastKnownIP == client.address) ) + matches += "IP ([client.address])" + if( (M.computer_id == client.computer_id) ) + if(matches) matches += " and " + matches += "ID ([client.computer_id])" + if(!config.disable_cid_warn_popup) + spawn() alert("You have logged in already with another key this round, please log out of this one NOW or risk being banned!") + if(matches) + if(M.client) + message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(M)].", 1) + log_adminwarn("Notice: [key_name(src)] has the same [matches] as [key_name(M)].") + else + message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(M)] (no longer logged in). ", 1) + log_adminwarn("Notice: [key_name(src)] has the same [matches] as [key_name(M)] (no longer logged in).") + +/mob/Login() + GLOB.player_list |= src + update_Login_details() + world.update_status() + + client.images = null //remove the images such as AIs being unable to see runes + client.screen = list() //remove hud items just in case + if(client.click_intercept) + client.click_intercept.quit() // Let's not keep any old click_intercepts + + if(!hud_used) + create_mob_hud() + if(hud_used) + hud_used.show_hud(hud_used.hud_version) + + next_move = 1 + sight |= SEE_SELF + ..() + + reset_perspective(loc) + + + if(ckey in GLOB.deadmins) + verbs += /client/proc/readmin + + //Clear ability list and update from mob. + client.verbs -= ability_verbs + + if(abilities) + client.verbs |= abilities + + //HUD updates (antag hud, etc) + //readd this mob's HUDs (antag, med, etc) + reload_huds() + + add_click_catcher() + + if(viewing_alternate_appearances && viewing_alternate_appearances.len) + for(var/datum/alternate_appearance/AA in viewing_alternate_appearances) + AA.display_to(list(src)) + + update_client_colour(0) + update_morgue() diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index 376b8d46ea5..1e019d16529 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -1,20 +1,20 @@ -/mob/Logout() - SSnanoui.user_logout(src) // this is used to clean up (remove) this user's Nano UIs - unset_machine() - GLOB.player_list -= src - log_access_out(src) - create_attack_log("Logged out at [atom_loc_line(get_turf(src))]") - // `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(admin_datums[ckey] && SSticker && SSticker.current_state == GAME_STATE_PLAYING) - var/datum/admins/temp_admin = admin_datums[ckey] - // Triggers on people with banhammer power only - no mentors tripping the alarm - if(temp_admin.rights & R_BAN) - message_admins("Admin logout: [key_name_admin(src)]") - var/list/admincounter = staff_countup(R_BAN) - if(admincounter[1] == 0) // No active admins - send2irc(config.admin_notify_irc, "[key_name(src)] logged out - No active admins, [admincounter[2]] non-admin staff, [admincounter[3]] inactive staff.") - - ..() - update_morgue() - return 1 +/mob/Logout() + SSnanoui.user_logout(src) // this is used to clean up (remove) this user's Nano UIs + unset_machine() + GLOB.player_list -= src + log_access_out(src) + create_attack_log("Logged out at [atom_loc_line(get_turf(src))]") + // `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(admin_datums[ckey] && SSticker && SSticker.current_state == GAME_STATE_PLAYING) + var/datum/admins/temp_admin = admin_datums[ckey] + // Triggers on people with banhammer power only - no mentors tripping the alarm + if(temp_admin.rights & R_BAN) + message_admins("Admin logout: [key_name_admin(src)]") + var/list/admincounter = staff_countup(R_BAN) + if(admincounter[1] == 0) // No active admins + send2irc(config.admin_notify_irc, "[key_name(src)] logged out - No active admins, [admincounter[2]] non-admin staff, [admincounter[3]] inactive staff.") + + ..() + update_morgue() + return 1 diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 609940bc2a6..65d2cb4788e 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1406,4 +1406,4 @@ var/list/slot_equipment_priority = list( \ ///Force set the mob nutrition /mob/proc/set_nutrition(change) - nutrition = max(0, change) \ No newline at end of file + nutrition = max(0, change) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 4091b8df9d7..5d63b7b4ad9 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -208,4 +208,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 \ No newline at end of file + var/registered_z diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm index 329a01907e2..f595f6b4839 100644 --- a/code/modules/mob/mob_grab.dm +++ b/code/modules/mob/mob_grab.dm @@ -1,452 +1,452 @@ -#define UPGRADE_COOLDOWN 40 -#define UPGRADE_KILL_TIMER 100 - -//times it takes for a mob to eat -#define EAT_TIME_XENO 30 -#define EAT_TIME_FAT 100 - -//time it takes for a mob to be eaten (in deciseconds) (overrides mob eat time) -#define EAT_TIME_ANIMAL 30 - -/obj/item/grab - name = "grab" - flags = NOBLUDGEON | ABSTRACT | DROPDEL - var/obj/screen/grab/hud = null - var/mob/living/affecting = null - var/mob/living/assailant = null - var/state = GRAB_PASSIVE - - var/allow_upgrade = 1 - var/last_upgrade = 0 - var/last_hit_zone = 0 -// var/force_down //determines if the affecting mob will be pinned to the ground //disabled due to balance, kept for an example for any new things. - var/dancing //determines if assailant and affecting keep looking at each other. Basically a wrestling position - - layer = 21 - plane = HUD_PLANE - item_state = "nothing" - icon = 'icons/mob/screen_gen.dmi' - w_class = WEIGHT_CLASS_BULKY - - -/obj/item/grab/New(var/mob/user, var/mob/victim) - ..() - - //Okay, first off, some fucking sanity checking. No user, or no victim, or they are not mobs, no grab. - if(!istype(user) || !istype(victim)) - return - - loc = user - assailant = user - affecting = victim - - if(affecting.anchored) - qdel(src) - return - - affecting.grabbed_by += src - - hud = new /obj/screen/grab(src) - hud.icon_state = "reinforce" - icon_state = "grabbed" - hud.name = "reinforce grab" - hud.master = src - - //check if assailant is grabbed by victim as well - if(assailant.grabbed_by) - for(var/obj/item/grab/G in assailant.grabbed_by) - if(G.assailant == affecting && G.affecting == assailant) - G.dancing = 1 - G.adjust_position() - dancing = 1 - - clean_grabbed_by(assailant, affecting) - adjust_position() - -/obj/item/grab/proc/clean_grabbed_by(var/mob/user, var/mob/victim) //Cleans up any nulls in the grabbed_by list. - if(istype(user)) - - for(var/entry in user.grabbed_by) - if(isnull(entry) || !istype(entry, /obj/item/grab)) //the isnull() here is probably redundant- but it might outperform istype, who knows. Better safe than sorry. - user.grabbed_by -= entry - - if(istype(victim)) - - for(var/entry in victim.grabbed_by) - if(isnull(entry) || !istype(entry, /obj/item/grab)) - victim.grabbed_by -= entry - - return 1 - -//Used by throw code to hand over the mob, instead of throwing the grab. The grab is then deleted by the throw code. -/obj/item/grab/proc/get_mob_if_throwable() - if(affecting && assailant.Adjacent(affecting)) - if(affecting.buckled) - return null - if(state >= GRAB_AGGRESSIVE) - return affecting - return null - -//This makes sure that the grab screen object is displayed in the correct hand. -/obj/item/grab/proc/synch() - if(affecting) - if(assailant.r_hand == src) - hud.screen_loc = ui_rhand - else - hud.screen_loc = ui_lhand - assailant.client.screen += hud - -/obj/item/grab/process() - if(!confirm()) - return //If the confirm fails, the grab is about to be deleted. That means it shouldn't continue processing. - - if(assailant.client) - if(!hud) - return //this somehow can runtime under the right circumstances - assailant.client.screen -= hud - assailant.client.screen += hud - - var/hit_zone = assailant.zone_selected - last_hit_zone = hit_zone - - if(assailant.pulling == affecting) - assailant.stop_pulling() - - if(state <= GRAB_AGGRESSIVE) - allow_upgrade = 1 - //disallow upgrading if we're grabbing more than one person - if((assailant.l_hand && assailant.l_hand != src && istype(assailant.l_hand, /obj/item/grab))) - var/obj/item/grab/G = assailant.l_hand - if(G.affecting != affecting) - allow_upgrade = 0 - if((assailant.r_hand && assailant.r_hand != src && istype(assailant.r_hand, /obj/item/grab))) - var/obj/item/grab/G = assailant.r_hand - if(G.affecting != affecting) - allow_upgrade = 0 - - //disallow upgrading past aggressive if we're being grabbed aggressively - for(var/obj/item/grab/G in affecting.grabbed_by) - if(G == src) continue - if(G.state >= GRAB_AGGRESSIVE) - allow_upgrade = 0 - - if(allow_upgrade) - if(state < GRAB_AGGRESSIVE) - hud.icon_state = "reinforce" - else - hud.icon_state = "reinforce1" - else - hud.icon_state = "!reinforce" - - if(state >= GRAB_AGGRESSIVE) - if(!HAS_TRAIT(assailant, TRAIT_PACIFISM)) - affecting.drop_r_hand() - affecting.drop_l_hand() - - - //var/announce = 0 - //(hit_zone != last_hit_zone) - //announce = 1 - /* if(ishuman(affecting)) - switch(hit_zone) - /*if("mouth") - if(announce) - assailant.visible_message("[assailant] covers [affecting]'s mouth!") - if(affecting.silent < 3) - affecting.silent = 3 - if("eyes") - if(announce) - assailant.visible_message("[assailant] covers [affecting]'s eyes!") - if(affecting.eye_blind < 3) - affecting.eye_blind = 3*///These are being left in the code as an example for adding new hit-zone based things. - - if(force_down) - if(affecting.loc != assailant.loc) - force_down = 0 - else - affecting.Weaken(3) //This is being left in the code as an example of adding a new variable to do something in grab code. - -*/ - - var/breathing_tube = affecting.get_organ_slot("breathing_tube") - - if(state >= GRAB_NECK) - affecting.Stun(5) //It will hamper your voice, being choked and all. - if(isliving(affecting) && !breathing_tube) - var/mob/living/L = affecting - L.adjustOxyLoss(1) - - if(state >= GRAB_KILL) - //affecting.apply_effect(STUTTER, 5) //would do this, but affecting isn't declared as mob/living for some stupid reason. - affecting.Stuttering(5) //It will hamper your voice, being choked and all. - affecting.Weaken(5) //Should keep you down unless you get help. - if(!breathing_tube) - affecting.AdjustLoseBreath(2, bound_lower = 0, bound_upper = 3) - - adjust_position() - - -/obj/item/grab/attack_self(mob/user) - s_click(hud) - -//Updating pixelshift, position and direction -//Gets called on process, when the grab gets upgraded or the assailant moves -/obj/item/grab/proc/adjust_position() - if(affecting.buckled) - return - if(affecting.lying && state != GRAB_KILL) - animate(affecting, pixel_x = 0, pixel_y = 0, 5, 1, LINEAR_EASING) - return //KJK - /* if(force_down) //THIS GOES ABOVE THE RETURN LABELED KJK - affecting.setDir(SOUTH)*///This shows how you can apply special directions based on a variable. //face up - - var/shift = 0 - var/adir = get_dir(assailant, affecting) - affecting.layer = 4 - switch(state) - if(GRAB_PASSIVE) - shift = 8 - if(dancing) //look at partner - shift = 10 - assailant.setDir(get_dir(assailant, affecting)) - if(GRAB_AGGRESSIVE) - shift = 12 - if(GRAB_NECK, GRAB_UPGRADING) - shift = -10 - adir = assailant.dir - affecting.setDir(assailant.dir) - affecting.forceMove(assailant.loc) - if(GRAB_KILL) - shift = 0 - adir = 1 - affecting.setDir(SOUTH)//face up - affecting.forceMove(assailant.loc) - - switch(adir) - if(NORTH) - animate(affecting, pixel_x = 0, pixel_y =-shift, 5, 1, LINEAR_EASING) - affecting.layer = 3.9 - if(SOUTH) - animate(affecting, pixel_x = 0, pixel_y = shift, 5, 1, LINEAR_EASING) - if(WEST) - animate(affecting, pixel_x = shift, pixel_y = 0, 5, 1, LINEAR_EASING) - if(EAST) - animate(affecting, pixel_x =-shift, pixel_y = 0, 5, 1, LINEAR_EASING) - -/obj/item/grab/proc/s_click(obj/screen/S) - if(!affecting) - return - if(state >= GRAB_AGGRESSIVE && HAS_TRAIT(assailant, TRAIT_PACIFISM)) - to_chat(assailant, "You don't want to risk hurting [affecting]!") - return - if(state == GRAB_UPGRADING) - return - if(assailant.next_move > world.time) - return - if(world.time < (last_upgrade + UPGRADE_COOLDOWN)) - return - if(!assailant.canmove || assailant.lying) - qdel(src) - return - - last_upgrade = world.time - - if(state < GRAB_AGGRESSIVE) - if(!allow_upgrade) - return - //if(!affecting.lying) - assailant.visible_message("[assailant] has grabbed [affecting] aggressively (now hands)!") - /* else - assailant.visible_message("[assailant] pins [affecting] down to the ground (now hands)!") - force_down = 1 - affecting.Weaken(3) - step_to(assailant, affecting) - assailant.setDir(EAST) //face the victim - affecting.setDir(SOUTH) //face up //This is an example of a new feature based on the context of the location of the victim. - */ //It means that upgrading while someone is lying on the ground would cause you to go into pin mode. - state = GRAB_AGGRESSIVE - icon_state = "grabbed1" - hud.icon_state = "reinforce1" - add_attack_logs(assailant, affecting, "Aggressively grabbed", ATKLOG_ALL) - else if(state < GRAB_NECK) - if(isslime(affecting)) - to_chat(assailant, "You squeeze [affecting], but nothing interesting happens.") - return - - assailant.visible_message("[assailant] has reinforced [assailant.p_their()] grip on [affecting] (now neck)!") - state = GRAB_NECK - icon_state = "grabbed+1" - assailant.setDir(get_dir(assailant, affecting)) - add_attack_logs(assailant, affecting, "Neck grabbed", ATKLOG_ALL) - if(!iscarbon(assailant)) - affecting.LAssailant = null - else - affecting.LAssailant = assailant - hud.icon_state = "kill" - hud.name = "kill" - affecting.Stun(10) //10 ticks of ensured grab - else if(state < GRAB_UPGRADING) - assailant.visible_message("[assailant] starts to tighten [assailant.p_their()] grip on [affecting]'s neck!") - hud.icon_state = "kill1" - - state = GRAB_KILL - assailant.visible_message("[assailant] has tightened [assailant.p_their()] grip on [affecting]'s neck!") - add_attack_logs(assailant, affecting, "Strangled") - - assailant.next_move = world.time + 10 - if(!affecting.get_organ_slot("breathing_tube")) - affecting.AdjustLoseBreath(1) - affecting.setDir(WEST) - adjust_position() - -//This is used to make sure the victim hasn't managed to yackety sax away before using the grab. -/obj/item/grab/proc/confirm() - if(!assailant || !affecting) - qdel(src) - return 0 - - if(affecting) - if(!isturf(assailant.loc) || ( !isturf(affecting.loc) || assailant.loc != affecting.loc && get_dist(assailant, affecting) > 1) ) - qdel(src) - return 0 - return 1 - - -/obj/item/grab/attack(mob/living/M, mob/living/carbon/user) - if(!affecting) - return - - if(M == affecting) - if(ishuman(M) && ishuman(assailant)) - var/mob/living/carbon/human/affected = affecting - var/mob/living/carbon/human/attacker = assailant - switch(assailant.a_intent) - if(INTENT_HELP) - /*if(force_down) - to_chat(assailant, "You no longer pin [affecting] to the ground.") - force_down = 0 - return*///This is a very basic demonstration of a new feature based on attacking someone with the grab, based on intent. - //This specific example would allow you to stop pinning people to the floor without moving away from them. - return - - if(INTENT_GRAB) - return - - if(INTENT_HARM) //This checks that the user is on harm intent. - if(last_hit_zone == "head") //This checks the hitzone the user has selected. In this specific case, they have the head selected. - if(affecting.lying) - return - assailant.visible_message("[assailant] thrusts [assailant.p_their()] head into [affecting]'s skull!") //A visible message for what is going on. - var/damage = 5 - var/obj/item/clothing/hat = attacker.head - if(istype(hat)) - damage += hat.force * 3 - affecting.apply_damage(damage*rand(90, 110)/100, BRUTE, "head", affected.run_armor_check(affecting, "melee")) - playsound(assailant.loc, "swing_hit", 25, 1, -1) - add_attack_logs(assailant, affecting, "Headbutted") - return - - /*if(last_hit_zone == "eyes") - if(state < GRAB_NECK) - to_chat(assailant, "You require a better grab to do this.") - return - if((affected.head && affected.head.flags_cover & HEADCOVERSEYES) || \ - (affected.wear_mask && affected.wear_mask.flags_cover & MASKCOVERSEYES) || \ - (affected.glasses && affected.glasses.flags_cover & GLASSESCOVERSEYES)) - to_chat(assailant, "You're going to need to remove the eye covering first.") - return - if(!affected.internal_bodyparts_by_name["eyes"]) - to_chat(assailant, "You cannot locate any eyes on [affecting]!") - return - assailant.visible_message("[assailant] presses [assailant.p_their()] fingers into [affecting]'s eyes!") - to_chat(affecting, "You feel immense pain as digits are being pressed into your eyes!") - add_attack_logs(assailant, affecting, "Eye-fucked with their fingers") - var/obj/item/organ/internal/eyes/eyes = affected.get_int_organ(/obj/item/organ/internal/eyes) - eyes.damage += rand(3,4) - if(eyes.damage >= eyes.min_broken_damage) - if(M.stat != 2) - to_chat(M, "You go blind!")*///This is a demonstration of adding a new damaging type based on intent as well as hitzone. - - //This specific example would allow you to squish people's eyes with a GRAB_NECK. - - if(INTENT_DISARM) //This checks that the user is on disarm intent. - /* if(state < GRAB_AGGRESSIVE) - to_chat(assailant, "You require a better grab to do this.") - return - if(!force_down) - assailant.visible_message("[user] is forcing [affecting] to the ground!") - force_down = 1 - affecting.Weaken(3) - affecting.lying = 1 - step_to(assailant, affecting) - assailant.setDir(EAST) //face the victim - affecting.setDir(SOUTH) //face up - return - else - to_chat(assailant, "You are already pinning [affecting] to the ground.") - return*///This is an example of something being done with an agressive grab + disarm intent. - return - - - if(M == assailant && state >= GRAB_AGGRESSIVE) //no eatin unless you have an agressive grab - if(checkvalid(user, affecting)) //wut - var/mob/living/carbon/attacker = user - - if(affecting.buckled) - to_chat(user, "[affecting] is buckled!") - return - - user.visible_message("[user] is attempting to devour \the [affecting]!") - - if(!do_after(user, checktime(user, affecting), target = affecting)) return - - if(affecting.buckled) - to_chat(user, "[affecting] is buckled!") - return - - user.visible_message("[user] devours \the [affecting]!") - if(affecting.mind) - add_attack_logs(attacker, affecting, "Devoured") - - affecting.forceMove(user) - attacker.stomach_contents.Add(affecting) - qdel(src) - -/obj/item/grab/proc/checkvalid(var/mob/attacker, var/mob/prey) //does all the checking for the attack proc to see if a mob can eat another with the grab - if(isalien(attacker) && iscarbon(prey)) //Xenomorphs eating carbon mobs - return 1 - - var/mob/living/carbon/human/H = attacker - if(ishuman(H) && is_type_in_list(prey, H.dna.species.allowed_consumed_mobs)) //species eating of other mobs - return 1 - - return 0 - -/obj/item/grab/proc/checktime(var/mob/attacker, var/mob/prey) //Returns the time the attacker has to wait before they eat the prey - if(isalien(attacker)) - return EAT_TIME_XENO //xenos get a speed boost - - if(istype(prey,/mob/living/simple_animal)) //simple animals get eaten at xeno-eating-speed regardless - return EAT_TIME_ANIMAL - - return EAT_TIME_FAT //if it doesn't fit into the above, it's probably a fat guy, take EAT_TIME_FAT to do it - -/obj/item/grab/Destroy() - if(affecting) - affecting.pixel_x = 0 - affecting.pixel_y = 0 //used to be an animate, not quick enough for del'ing - affecting.layer = initial(affecting.layer) - affecting.grabbed_by -= src - affecting = null - if(assailant) - if(assailant.client) - assailant.client.screen -= hud - assailant = null - QDEL_NULL(hud) - return ..() - - -#undef EAT_TIME_XENO -#undef EAT_TIME_FAT - -#undef EAT_TIME_ANIMAL +#define UPGRADE_COOLDOWN 40 +#define UPGRADE_KILL_TIMER 100 + +//times it takes for a mob to eat +#define EAT_TIME_XENO 30 +#define EAT_TIME_FAT 100 + +//time it takes for a mob to be eaten (in deciseconds) (overrides mob eat time) +#define EAT_TIME_ANIMAL 30 + +/obj/item/grab + name = "grab" + flags = NOBLUDGEON | ABSTRACT | DROPDEL + var/obj/screen/grab/hud = null + var/mob/living/affecting = null + var/mob/living/assailant = null + var/state = GRAB_PASSIVE + + var/allow_upgrade = 1 + var/last_upgrade = 0 + var/last_hit_zone = 0 +// var/force_down //determines if the affecting mob will be pinned to the ground //disabled due to balance, kept for an example for any new things. + var/dancing //determines if assailant and affecting keep looking at each other. Basically a wrestling position + + layer = 21 + plane = HUD_PLANE + item_state = "nothing" + icon = 'icons/mob/screen_gen.dmi' + w_class = WEIGHT_CLASS_BULKY + + +/obj/item/grab/New(var/mob/user, var/mob/victim) + ..() + + //Okay, first off, some fucking sanity checking. No user, or no victim, or they are not mobs, no grab. + if(!istype(user) || !istype(victim)) + return + + loc = user + assailant = user + affecting = victim + + if(affecting.anchored) + qdel(src) + return + + affecting.grabbed_by += src + + hud = new /obj/screen/grab(src) + hud.icon_state = "reinforce" + icon_state = "grabbed" + hud.name = "reinforce grab" + hud.master = src + + //check if assailant is grabbed by victim as well + if(assailant.grabbed_by) + for(var/obj/item/grab/G in assailant.grabbed_by) + if(G.assailant == affecting && G.affecting == assailant) + G.dancing = 1 + G.adjust_position() + dancing = 1 + + clean_grabbed_by(assailant, affecting) + adjust_position() + +/obj/item/grab/proc/clean_grabbed_by(var/mob/user, var/mob/victim) //Cleans up any nulls in the grabbed_by list. + if(istype(user)) + + for(var/entry in user.grabbed_by) + if(isnull(entry) || !istype(entry, /obj/item/grab)) //the isnull() here is probably redundant- but it might outperform istype, who knows. Better safe than sorry. + user.grabbed_by -= entry + + if(istype(victim)) + + for(var/entry in victim.grabbed_by) + if(isnull(entry) || !istype(entry, /obj/item/grab)) + victim.grabbed_by -= entry + + return 1 + +//Used by throw code to hand over the mob, instead of throwing the grab. The grab is then deleted by the throw code. +/obj/item/grab/proc/get_mob_if_throwable() + if(affecting && assailant.Adjacent(affecting)) + if(affecting.buckled) + return null + if(state >= GRAB_AGGRESSIVE) + return affecting + return null + +//This makes sure that the grab screen object is displayed in the correct hand. +/obj/item/grab/proc/synch() + if(affecting) + if(assailant.r_hand == src) + hud.screen_loc = ui_rhand + else + hud.screen_loc = ui_lhand + assailant.client.screen += hud + +/obj/item/grab/process() + if(!confirm()) + return //If the confirm fails, the grab is about to be deleted. That means it shouldn't continue processing. + + if(assailant.client) + if(!hud) + return //this somehow can runtime under the right circumstances + assailant.client.screen -= hud + assailant.client.screen += hud + + var/hit_zone = assailant.zone_selected + last_hit_zone = hit_zone + + if(assailant.pulling == affecting) + assailant.stop_pulling() + + if(state <= GRAB_AGGRESSIVE) + allow_upgrade = 1 + //disallow upgrading if we're grabbing more than one person + if((assailant.l_hand && assailant.l_hand != src && istype(assailant.l_hand, /obj/item/grab))) + var/obj/item/grab/G = assailant.l_hand + if(G.affecting != affecting) + allow_upgrade = 0 + if((assailant.r_hand && assailant.r_hand != src && istype(assailant.r_hand, /obj/item/grab))) + var/obj/item/grab/G = assailant.r_hand + if(G.affecting != affecting) + allow_upgrade = 0 + + //disallow upgrading past aggressive if we're being grabbed aggressively + for(var/obj/item/grab/G in affecting.grabbed_by) + if(G == src) continue + if(G.state >= GRAB_AGGRESSIVE) + allow_upgrade = 0 + + if(allow_upgrade) + if(state < GRAB_AGGRESSIVE) + hud.icon_state = "reinforce" + else + hud.icon_state = "reinforce1" + else + hud.icon_state = "!reinforce" + + if(state >= GRAB_AGGRESSIVE) + if(!HAS_TRAIT(assailant, TRAIT_PACIFISM)) + affecting.drop_r_hand() + affecting.drop_l_hand() + + + //var/announce = 0 + //(hit_zone != last_hit_zone) + //announce = 1 + /* if(ishuman(affecting)) + switch(hit_zone) + /*if("mouth") + if(announce) + assailant.visible_message("[assailant] covers [affecting]'s mouth!") + if(affecting.silent < 3) + affecting.silent = 3 + if("eyes") + if(announce) + assailant.visible_message("[assailant] covers [affecting]'s eyes!") + if(affecting.eye_blind < 3) + affecting.eye_blind = 3*///These are being left in the code as an example for adding new hit-zone based things. + + if(force_down) + if(affecting.loc != assailant.loc) + force_down = 0 + else + affecting.Weaken(3) //This is being left in the code as an example of adding a new variable to do something in grab code. + +*/ + + var/breathing_tube = affecting.get_organ_slot("breathing_tube") + + if(state >= GRAB_NECK) + affecting.Stun(5) //It will hamper your voice, being choked and all. + if(isliving(affecting) && !breathing_tube) + var/mob/living/L = affecting + L.adjustOxyLoss(1) + + if(state >= GRAB_KILL) + //affecting.apply_effect(STUTTER, 5) //would do this, but affecting isn't declared as mob/living for some stupid reason. + affecting.Stuttering(5) //It will hamper your voice, being choked and all. + affecting.Weaken(5) //Should keep you down unless you get help. + if(!breathing_tube) + affecting.AdjustLoseBreath(2, bound_lower = 0, bound_upper = 3) + + adjust_position() + + +/obj/item/grab/attack_self(mob/user) + s_click(hud) + +//Updating pixelshift, position and direction +//Gets called on process, when the grab gets upgraded or the assailant moves +/obj/item/grab/proc/adjust_position() + if(affecting.buckled) + return + if(affecting.lying && state != GRAB_KILL) + animate(affecting, pixel_x = 0, pixel_y = 0, 5, 1, LINEAR_EASING) + return //KJK + /* if(force_down) //THIS GOES ABOVE THE RETURN LABELED KJK + affecting.setDir(SOUTH)*///This shows how you can apply special directions based on a variable. //face up + + var/shift = 0 + var/adir = get_dir(assailant, affecting) + affecting.layer = 4 + switch(state) + if(GRAB_PASSIVE) + shift = 8 + if(dancing) //look at partner + shift = 10 + assailant.setDir(get_dir(assailant, affecting)) + if(GRAB_AGGRESSIVE) + shift = 12 + if(GRAB_NECK, GRAB_UPGRADING) + shift = -10 + adir = assailant.dir + affecting.setDir(assailant.dir) + affecting.forceMove(assailant.loc) + if(GRAB_KILL) + shift = 0 + adir = 1 + affecting.setDir(SOUTH)//face up + affecting.forceMove(assailant.loc) + + switch(adir) + if(NORTH) + animate(affecting, pixel_x = 0, pixel_y =-shift, 5, 1, LINEAR_EASING) + affecting.layer = 3.9 + if(SOUTH) + animate(affecting, pixel_x = 0, pixel_y = shift, 5, 1, LINEAR_EASING) + if(WEST) + animate(affecting, pixel_x = shift, pixel_y = 0, 5, 1, LINEAR_EASING) + if(EAST) + animate(affecting, pixel_x =-shift, pixel_y = 0, 5, 1, LINEAR_EASING) + +/obj/item/grab/proc/s_click(obj/screen/S) + if(!affecting) + return + if(state >= GRAB_AGGRESSIVE && HAS_TRAIT(assailant, TRAIT_PACIFISM)) + to_chat(assailant, "You don't want to risk hurting [affecting]!") + return + if(state == GRAB_UPGRADING) + return + if(assailant.next_move > world.time) + return + if(world.time < (last_upgrade + UPGRADE_COOLDOWN)) + return + if(!assailant.canmove || assailant.lying) + qdel(src) + return + + last_upgrade = world.time + + if(state < GRAB_AGGRESSIVE) + if(!allow_upgrade) + return + //if(!affecting.lying) + assailant.visible_message("[assailant] has grabbed [affecting] aggressively (now hands)!") + /* else + assailant.visible_message("[assailant] pins [affecting] down to the ground (now hands)!") + force_down = 1 + affecting.Weaken(3) + step_to(assailant, affecting) + assailant.setDir(EAST) //face the victim + affecting.setDir(SOUTH) //face up //This is an example of a new feature based on the context of the location of the victim. + */ //It means that upgrading while someone is lying on the ground would cause you to go into pin mode. + state = GRAB_AGGRESSIVE + icon_state = "grabbed1" + hud.icon_state = "reinforce1" + add_attack_logs(assailant, affecting, "Aggressively grabbed", ATKLOG_ALL) + else if(state < GRAB_NECK) + if(isslime(affecting)) + to_chat(assailant, "You squeeze [affecting], but nothing interesting happens.") + return + + assailant.visible_message("[assailant] has reinforced [assailant.p_their()] grip on [affecting] (now neck)!") + state = GRAB_NECK + icon_state = "grabbed+1" + assailant.setDir(get_dir(assailant, affecting)) + add_attack_logs(assailant, affecting, "Neck grabbed", ATKLOG_ALL) + if(!iscarbon(assailant)) + affecting.LAssailant = null + else + affecting.LAssailant = assailant + hud.icon_state = "kill" + hud.name = "kill" + affecting.Stun(10) //10 ticks of ensured grab + else if(state < GRAB_UPGRADING) + assailant.visible_message("[assailant] starts to tighten [assailant.p_their()] grip on [affecting]'s neck!") + hud.icon_state = "kill1" + + state = GRAB_KILL + assailant.visible_message("[assailant] has tightened [assailant.p_their()] grip on [affecting]'s neck!") + add_attack_logs(assailant, affecting, "Strangled") + + assailant.next_move = world.time + 10 + if(!affecting.get_organ_slot("breathing_tube")) + affecting.AdjustLoseBreath(1) + affecting.setDir(WEST) + adjust_position() + +//This is used to make sure the victim hasn't managed to yackety sax away before using the grab. +/obj/item/grab/proc/confirm() + if(!assailant || !affecting) + qdel(src) + return 0 + + if(affecting) + if(!isturf(assailant.loc) || ( !isturf(affecting.loc) || assailant.loc != affecting.loc && get_dist(assailant, affecting) > 1) ) + qdel(src) + return 0 + return 1 + + +/obj/item/grab/attack(mob/living/M, mob/living/carbon/user) + if(!affecting) + return + + if(M == affecting) + if(ishuman(M) && ishuman(assailant)) + var/mob/living/carbon/human/affected = affecting + var/mob/living/carbon/human/attacker = assailant + switch(assailant.a_intent) + if(INTENT_HELP) + /*if(force_down) + to_chat(assailant, "You no longer pin [affecting] to the ground.") + force_down = 0 + return*///This is a very basic demonstration of a new feature based on attacking someone with the grab, based on intent. + //This specific example would allow you to stop pinning people to the floor without moving away from them. + return + + if(INTENT_GRAB) + return + + if(INTENT_HARM) //This checks that the user is on harm intent. + if(last_hit_zone == "head") //This checks the hitzone the user has selected. In this specific case, they have the head selected. + if(affecting.lying) + return + assailant.visible_message("[assailant] thrusts [assailant.p_their()] head into [affecting]'s skull!") //A visible message for what is going on. + var/damage = 5 + var/obj/item/clothing/hat = attacker.head + if(istype(hat)) + damage += hat.force * 3 + affecting.apply_damage(damage*rand(90, 110)/100, BRUTE, "head", affected.run_armor_check(affecting, "melee")) + playsound(assailant.loc, "swing_hit", 25, 1, -1) + add_attack_logs(assailant, affecting, "Headbutted") + return + + /*if(last_hit_zone == "eyes") + if(state < GRAB_NECK) + to_chat(assailant, "You require a better grab to do this.") + return + if((affected.head && affected.head.flags_cover & HEADCOVERSEYES) || \ + (affected.wear_mask && affected.wear_mask.flags_cover & MASKCOVERSEYES) || \ + (affected.glasses && affected.glasses.flags_cover & GLASSESCOVERSEYES)) + to_chat(assailant, "You're going to need to remove the eye covering first.") + return + if(!affected.internal_bodyparts_by_name["eyes"]) + to_chat(assailant, "You cannot locate any eyes on [affecting]!") + return + assailant.visible_message("[assailant] presses [assailant.p_their()] fingers into [affecting]'s eyes!") + to_chat(affecting, "You feel immense pain as digits are being pressed into your eyes!") + add_attack_logs(assailant, affecting, "Eye-fucked with their fingers") + var/obj/item/organ/internal/eyes/eyes = affected.get_int_organ(/obj/item/organ/internal/eyes) + eyes.damage += rand(3,4) + if(eyes.damage >= eyes.min_broken_damage) + if(M.stat != 2) + to_chat(M, "You go blind!")*///This is a demonstration of adding a new damaging type based on intent as well as hitzone. + + //This specific example would allow you to squish people's eyes with a GRAB_NECK. + + if(INTENT_DISARM) //This checks that the user is on disarm intent. + /* if(state < GRAB_AGGRESSIVE) + to_chat(assailant, "You require a better grab to do this.") + return + if(!force_down) + assailant.visible_message("[user] is forcing [affecting] to the ground!") + force_down = 1 + affecting.Weaken(3) + affecting.lying = 1 + step_to(assailant, affecting) + assailant.setDir(EAST) //face the victim + affecting.setDir(SOUTH) //face up + return + else + to_chat(assailant, "You are already pinning [affecting] to the ground.") + return*///This is an example of something being done with an agressive grab + disarm intent. + return + + + if(M == assailant && state >= GRAB_AGGRESSIVE) //no eatin unless you have an agressive grab + if(checkvalid(user, affecting)) //wut + var/mob/living/carbon/attacker = user + + if(affecting.buckled) + to_chat(user, "[affecting] is buckled!") + return + + user.visible_message("[user] is attempting to devour \the [affecting]!") + + if(!do_after(user, checktime(user, affecting), target = affecting)) return + + if(affecting.buckled) + to_chat(user, "[affecting] is buckled!") + return + + user.visible_message("[user] devours \the [affecting]!") + if(affecting.mind) + add_attack_logs(attacker, affecting, "Devoured") + + affecting.forceMove(user) + attacker.stomach_contents.Add(affecting) + qdel(src) + +/obj/item/grab/proc/checkvalid(var/mob/attacker, var/mob/prey) //does all the checking for the attack proc to see if a mob can eat another with the grab + if(isalien(attacker) && iscarbon(prey)) //Xenomorphs eating carbon mobs + return 1 + + var/mob/living/carbon/human/H = attacker + if(ishuman(H) && is_type_in_list(prey, H.dna.species.allowed_consumed_mobs)) //species eating of other mobs + return 1 + + return 0 + +/obj/item/grab/proc/checktime(var/mob/attacker, var/mob/prey) //Returns the time the attacker has to wait before they eat the prey + if(isalien(attacker)) + return EAT_TIME_XENO //xenos get a speed boost + + if(istype(prey,/mob/living/simple_animal)) //simple animals get eaten at xeno-eating-speed regardless + return EAT_TIME_ANIMAL + + return EAT_TIME_FAT //if it doesn't fit into the above, it's probably a fat guy, take EAT_TIME_FAT to do it + +/obj/item/grab/Destroy() + if(affecting) + affecting.pixel_x = 0 + affecting.pixel_y = 0 //used to be an animate, not quick enough for del'ing + affecting.layer = initial(affecting.layer) + affecting.grabbed_by -= src + affecting = null + if(assailant) + if(assailant.client) + assailant.client.screen -= hud + assailant = null + QDEL_NULL(hud) + return ..() + + +#undef EAT_TIME_XENO +#undef EAT_TIME_FAT + +#undef EAT_TIME_ANIMAL diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index e2e5de12e31..a894ad7cd07 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -1,686 +1,686 @@ -/proc/issmall(A) - if(A && istype(A, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = A - if(H.dna.species && H.dna.species.is_small) - return 1 - return 0 - -/proc/ispet(A) - if(istype(A, /mob/living/simple_animal)) - var/mob/living/simple_animal/SA = A - if(SA.can_collar) - return 1 - return 0 - -/mob/proc/isSynthetic() - return 0 - -/mob/living/carbon/human/isSynthetic() - if(ismachine(src)) - return TRUE - return FALSE - -/mob/proc/get_screen_colour() - -/mob/proc/update_client_colour(var/time = 10) //Update the mob's client.color with an animation the specified time in length. - if(!client) //No client_colour without client. If the player logs back in they'll be back through here anyway. - return - client.colour_transition(get_screen_colour(), time = time) //Get the colour matrix we're going to transition to depending on relevance (magic glasses first, eyes second). - -/mob/living/carbon/human/get_screen_colour() //Fetch the colour matrix from wherever (e.g. eyes) so it can be compared to client.color. - . = ..() - if(.) - return . - - var/obj/item/clothing/glasses/worn_glasses = glasses - var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) - if(istype(worn_glasses) && worn_glasses.color_view) //Check to see if they got those magic glasses and they're augmenting the colour of what the wearer sees. If they're not, color_view should be null. - return worn_glasses.color_view - else if(eyes) //If they're not, check to see if their eyes got one of them there colour matrices. Will be null if eyes are robotic/the mob isn't colourblind and they have no default colour matrix. - return eyes.get_colourmatrix() - -/proc/ismindshielded(A) //Checks to see if the person contains a mindshield implant, then checks that the implant is actually inside of them - for(var/obj/item/implant/mindshield/L in A) - if(L && L.implanted) - return 1 - return 0 - -/proc/ismindslave(A) //Checks to see if the person contains a mindslave implant, then checks that the implant is actually inside of them - for(var/obj/item/implant/traitor/T in A) - if(T && T.implanted) - return 1 - return 0 - -/proc/isLivingSSD(mob/M) - return istype(M) && M.player_logged && M.stat != DEAD - -/proc/isAntag(A) - if(istype(A, /mob/living/carbon)) - var/mob/living/carbon/C = A - if(C.mind && C.mind.special_role) - return 1 - return 0 - -/proc/isNonCrewAntag(A) - if(!isAntag(A)) - return 0 - - var/mob/living/carbon/C = A - var/special_role = C.mind.special_role - var/list/crew_roles = list( - SPECIAL_ROLE_BLOB, - SPECIAL_ROLE_CULTIST, - SPECIAL_ROLE_CHANGELING, - SPECIAL_ROLE_ERT, - SPECIAL_ROLE_HEAD_REV, - SPECIAL_ROLE_REV, - SPECIAL_ROLE_SHADOWLING, - SPECIAL_ROLE_SHADOWLING_THRALL, - SPECIAL_ROLE_TRAITOR, - SPECIAL_ROLE_VAMPIRE, - SPECIAL_ROLE_VAMPIRE_THRALL - ) - if(special_role in crew_roles) - return 0 - - return 1 - -/proc/cannotPossess(A) - var/mob/dead/observer/G = A - if(G.has_enabled_antagHUD == 1 && config.antag_hud_restricted) - return 1 - return 0 - - -/proc/iscuffed(A) - if(istype(A, /mob/living/carbon)) - var/mob/living/carbon/C = A - if(C.handcuffed) - return 1 - return 0 - -/proc/hassensorlevel(A, var/level) - var/mob/living/carbon/human/H = A - if(istype(H) && istype(H.w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = H.w_uniform - return U.sensor_mode >= level - return 0 - -/proc/getsensorlevel(A) - var/mob/living/carbon/human/H = A - if(istype(H) && istype(H.w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = H.w_uniform - return U.sensor_mode - return SUIT_SENSOR_OFF - -/proc/offer_control(mob/M) - to_chat(M, "Control of your mob has been offered to dead players.") - log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.") - var/minhours = input(usr, "Minimum hours required to play [M]?", "Set Min Hrs", 10) as num - message_admins("[key_name_admin(usr)] has offered control of ([key_name_admin(M)]) to ghosts with [minhours] hrs playtime") - var/question = "Do you want to play as [M.real_name ? M.real_name : M.name][M.job ? " ([M.job])" : ""]" - if(alert("Do you want to show the antag status?","Show antag status","Yes","No") == "Yes") - question += ", [M.mind?.special_role ? M.mind?.special_role : "No special role"]" - var/list/mob/dead/observer/candidates = pollCandidates("[question]?", poll_time = 100, min_hours = minhours) - var/mob/dead/observer/theghost = null - - if(LAZYLEN(candidates)) - theghost = pick(candidates) - to_chat(M, "Your mob has been taken over by a ghost!") - message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)])") - M.ghostize() - M.key = theghost.key - else - to_chat(M, "There were no ghosts willing to take control.") - message_admins("No ghosts were willing to take control of [key_name_admin(M)])") - -/proc/check_zone(zone) - if(!zone) return "chest" - switch(zone) - if("eyes") - zone = "head" - if("mouth") - zone = "head" - return zone - -// Returns zone with a certain probability. -// If the probability misses, returns "chest" instead. -// If "chest" was passed in as zone, then on a "miss" will return "head", "l_arm", or "r_arm" -// Do not use this if someone is intentionally trying to hit a specific body part. -// Use get_zone_with_miss_chance() for that. -/proc/ran_zone(zone, probability = 80) - - zone = check_zone(zone) - - if(prob(probability)) - return zone - - var/t = rand(1, 18) // randomly pick a different zone, or maybe the same one - switch(t) - if(1) return "head" - if(2) return "chest" - if(3 to 4) return "l_arm" - if(5 to 6) return "l_hand" - if(7 to 8) return "r_arm" - if(9 to 10) return "r_hand" - if(11 to 12) return "l_leg" - if(13 to 14) return "l_foot" - if(15 to 16) return "r_leg" - if(17 to 18) return "r_foot" - - return zone - -/proc/above_neck(zone) - var/list/zones = list("head", "mouth", "eyes") - if(zones.Find(zone)) - return 1 - else - return 0 - -/proc/stars(n, pr) - if(pr == null) - pr = 25 - if(pr <= 0) - return null - else - if(pr >= 100) - return n - var/te = n - var/t = "" - n = length(n) - var/p = null - p = 1 - while(p <= n) - if((copytext(te, p, p + 1) == " " || prob(pr))) - t = text("[][]", t, copytext(te, p, p + 1)) - else - t = text("[]*", t) - p++ - return t - -/proc/stars_all(list/message_pieces, pr) - for(var/datum/multilingual_say_piece/S in message_pieces) - S.message = stars(S.message, pr) - -/proc/slur(phrase, var/list/slurletters = ("'"))//use a different list as an input if you want to make robots slur with $#@%! characters - phrase = html_decode(phrase) - var/leng=length(phrase) - var/counter=length(phrase) - var/newphrase="" - var/newletter="" - while(counter>=1) - newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) - if(rand(1,3)==3) - if(lowertext(newletter)=="o") newletter="u" - if(lowertext(newletter)=="s") newletter="ch" - if(lowertext(newletter)=="a") newletter="ah" - if(lowertext(newletter)=="c") newletter="k" - switch(rand(1,15)) - if(1,3,5,8) newletter="[lowertext(newletter)]" - if(2,4,6,15) newletter="[uppertext(newletter)]" - if(7) newletter+=pick(slurletters) - //if(9,10) newletter="[newletter]" - //if(11,12) newletter="[newletter]" - //if(13) newletter="[newletter]" - newphrase+="[newletter]" - counter-=1 - return newphrase - -/proc/stutter(n) - var/te = html_decode(n) - var/t = ""//placed before the message. Not really sure what it's for. - n = length(n)//length of the entire word - var/p = null - p = 1//1 is the start of any word - while(p <= n)//while P, which starts at 1 is less or equal to N which is the length. - var/n_letter = copytext(te, p, p + 1)//copies text from a certain distance. In this case, only one letter at a time. - if(prob(80) && (ckey(n_letter) in list("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"))) - if(prob(10)) - n_letter = text("[n_letter]-[n_letter]-[n_letter]-[n_letter]")//replaces the current letter with this instead. - else - if(prob(20)) - n_letter = text("[n_letter]-[n_letter]-[n_letter]") - else - if(prob(5)) - n_letter = null - else - n_letter = text("[n_letter]-[n_letter]") - t = text("[t][n_letter]")//since the above is ran through for each letter, the text just adds up back to the original word. - p++//for each letter p is increased to find where the next letter will be. - return sanitize(copytext(t,1,MAX_MESSAGE_LEN)) - -/proc/robostutter(n) //for robutts - var/te = html_decode(n) - var/t = ""//placed before the message. Not really sure what it's for. - n = length(n)//length of the entire word - var/p = null - p = 1//1 is the start of any word - while(p <= n)//while P, which starts at 1 is less or equal to N which is the length. - var/robotletter = pick("@", "!", "#", "$", "%", "&", "?") //for beep boop - var/n_letter = copytext(te, p, p + 1)//copies text from a certain distance. In this case, only one letter at a time. - if(prob(80) && (ckey(n_letter) in list("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"))) - if(prob(10)) - n_letter = text("[n_letter]-[robotletter]-[n_letter]-[n_letter]")//replaces the current letter with this instead. - else - if(prob(20)) - n_letter = text("[n_letter]-[robotletter]-[n_letter]") - else - if(prob(5)) - n_letter = robotletter - else - n_letter = text("[n_letter]-[n_letter]") - t = text("[t][n_letter]")//since the above is ran through for each letter, the text just adds up back to the original word. - p++//for each letter p is increased to find where the next letter will be. - return sanitize(copytext(t,1,MAX_MESSAGE_LEN)) - - -/proc/Gibberish(t, p)//t is the inputted message, and any value higher than 70 for p will cause letters to be replaced instead of added - /* Turn text into complete gibberish! */ - var/returntext = "" - for(var/i = 1, i <= length(t), i++) - - var/letter = copytext(t, i, i+1) - if(prob(50)) - if(p >= 70) - letter = "" - - for(var/j = 1, j <= rand(0, 2), j++) - letter += pick("#","@","*","&","%","$","/", "<", ">", ";","*","*","*","*","*","*","*") - - returntext += letter - - return returntext - -/proc/Gibberish_all(list/message_pieces, p) - for(var/datum/multilingual_say_piece/S in message_pieces) - S.message = Gibberish(S.message, p) - - -proc/muffledspeech(phrase) - phrase = html_decode(phrase) - var/leng=length(phrase) - var/counter=length(phrase) - var/newphrase="" - var/newletter="" - while(counter>=1) - newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) - if(newletter in list(" ", "!", "?", ".", ",")) - //do nothing - else if(lowertext(newletter) in list("a", "e", "i", "o", "u", "y")) - newletter = "ph" - else - newletter = "m" - newphrase+="[newletter]" - counter-=1 - return newphrase - -/proc/muffledspeech_all(list/message_pieces) - for(var/datum/multilingual_say_piece/S in message_pieces) - S.message = muffledspeech(S.message) - - -/proc/shake_camera(mob/M, duration, strength=1) - if(!M || !M.client || M.shakecamera) - return - M.shakecamera = 1 - spawn(1) - - var/atom/oldeye=M.client.eye - var/aiEyeFlag = 0 - if(istype(oldeye, /mob/camera/aiEye)) - aiEyeFlag = 1 - - var/x - for(x=0; x
        ") - return - else - if(alert(src, "You sure you want to sleep for a while?", "Sleep", "Yes", "No") == "Yes") - SetSleeping(20) //Short nap - -/mob/living/verb/lay_down() - set name = "Rest" - set category = "IC" - - if(!resting) - client.move_delay = world.time + 20 - to_chat(src, "You are now resting.") - StartResting() - else if(resting) - to_chat(src, "You are now getting up.") - StopResting() - -/proc/get_multitool(mob/user as mob) - // Get tool - var/obj/item/multitool/P - if(isrobot(user) || ishuman(user)) - P = user.get_active_hand() - else if(isAI(user)) - var/mob/living/silicon/ai/AI=user - P = AI.aiMulti - - if(!istype(P)) - return null - return P - -/proc/get_both_hands(mob/living/carbon/M) - return list(M.l_hand, M.r_hand) - - -//Direct dead say used both by emote and say -//It is somewhat messy. I don't know what to do. -//I know you can't see the change, but I rewrote the name code. It is significantly less messy now -/proc/say_dead_direct(var/message, var/mob/subject = null) - var/name - var/keyname - if(subject && subject.client) - var/client/C = subject.client - keyname = (C.holder && C.holder.fakekey) ? C.holder.fakekey : C.key - if(C.mob) //Most of the time this is the dead/observer mob; we can totally use him if there is no better name - var/mindname - var/realname = C.mob.real_name - if(C.mob.mind) - mindname = C.mob.mind.name - if(C.mob.mind.original && C.mob.mind.original.real_name) - realname = C.mob.mind.original.real_name - if(mindname && mindname != realname) - name = "[realname] died as [mindname]" - else - name = realname - - for(var/mob/M in GLOB.player_list) - if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || check_rights(R_ADMIN|R_MOD,0,M)) && M.get_preference(CHAT_DEAD)) - var/follow - var/lname - if(subject) - if(subject != M) - follow = "([ghost_follow_link(subject, ghost=M)]) " - if(M.stat != DEAD && check_rights(R_ADMIN|R_MOD,0,M)) - follow = "([admin_jump_link(subject)]) " - var/mob/dead/observer/DM - if(istype(subject, /mob/dead/observer)) - DM = subject - if(check_rights(R_ADMIN|R_MOD,0,M)) // What admins see - lname = "[keyname][(DM && DM.client && DM.client.prefs.ghost_anonsay) ? "*" : (DM ? "" : "^")] ([name])" - else - if(DM && DM.client && DM.client.prefs.ghost_anonsay) // If the person is actually observer they have the option to be anonymous - lname = "Ghost of [name]" - else if(DM) // Non-anons - lname = "[keyname] ([name])" - else // Everyone else (dead people who didn't ghost yet, etc.) - lname = name - lname = "[lname] " - to_chat(M, "[lname][follow][message]") - -/proc/notify_ghosts(message, ghost_sound = null, enter_link = null, title = null, atom/source = null, image/alert_overlay = null, flashwindow = TRUE, var/action = NOTIFY_JUMP) //Easy notification of ghosts. - for(var/mob/dead/observer/O in GLOB.player_list) - if(O.client) - to_chat(O, "[message][(enter_link) ? " [enter_link]" : ""]") - if(ghost_sound) - O << sound(ghost_sound) - if(flashwindow) - window_flash(O.client) - if(source) - var/obj/screen/alert/notify_action/A = O.throw_alert("\ref[source]_notify_action", /obj/screen/alert/notify_action) - if(A) - if(O.client.prefs && O.client.prefs.UI_style) - A.icon = ui_style2icon(O.client.prefs.UI_style) - if(title) - A.name = title - A.desc = message - A.action = action - A.target = source - if(!alert_overlay) - var/old_layer = source.layer - var/old_plane = source.plane - source.layer = FLOAT_LAYER - source.plane = FLOAT_PLANE - A.overlays += source - source.layer = old_layer - source.plane = old_plane - else - alert_overlay.layer = FLOAT_LAYER - alert_overlay.plane = FLOAT_PLANE - A.overlays += alert_overlay - -/mob/proc/switch_to_camera(var/obj/machinery/camera/C) - if(!C.can_use() || stat || (get_dist(C, src) > 1 || machine != src || !has_vision() || !canmove)) - return 0 - check_eye(src) - return 1 - -/mob/proc/rename_character(oldname, newname) - if(!newname) - return 0 - real_name = newname - name = newname - if(mind) - mind.name = newname - if(dna) - dna.real_name = real_name - - if(oldname) - //update the datacore records! This is goig to be a bit costly. - for(var/list/L in list(data_core.general,data_core.medical,data_core.security,data_core.locked)) - for(var/datum/data/record/R in L) - if(R.fields["name"] == oldname) - R.fields["name"] = newname - break - - //update our pda and id if we have them on our person - var/list/searching = GetAllContents(searchDepth = 3) - var/search_id = 1 - var/search_pda = 1 - - for(var/A in searching) - if( search_id && istype(A,/obj/item/card/id) ) - var/obj/item/card/id/ID = A - if(ID.registered_name == oldname) - ID.registered_name = newname - ID.name = "[newname]'s ID Card ([ID.assignment])" - ID.RebuildHTML() - if(!search_pda) break - search_id = 0 - - else if( search_pda && istype(A,/obj/item/pda) ) - var/obj/item/pda/PDA = A - if(PDA.owner == oldname) - PDA.owner = newname - PDA.name = "PDA-[newname] ([PDA.ownjob])" - if(!search_id) break - search_pda = 0 - - //Fixes renames not being reflected in objective text - var/length - var/pos - for(var/datum/objective/objective in GLOB.all_objectives) - if(!mind || objective.target != mind) - continue - length = length(oldname) - pos = findtextEx(objective.explanation_text, oldname) - objective.explanation_text = copytext(objective.explanation_text, 1, pos)+newname+copytext(objective.explanation_text, pos+length) - return 1 - -/mob/proc/rename_self(var/role, var/allow_numbers = FALSE, var/force = FALSE) - spawn(0) - var/oldname = real_name - - var/time_passed = world.time - var/newname - - for(var/i=1,i<=3,i++) //we get 3 attempts to pick a suitable name. - if(force) - newname = clean_input("Pick a new name.", "Name Change", oldname, src) - else - newname = clean_input("You are a [role]. Would you like to change your name to something else? (You have 3 minutes to select a new name.)", "Name Change", oldname, src) - if(((world.time - time_passed) > 1800) && !force) - alert(src, "Unfortunately, more than 3 minutes have passed for selecting your name. If you are a robot, use the Namepick verb; otherwise, adminhelp.", "Name Change") - return //took too long - newname = reject_bad_name(newname,allow_numbers) //returns null if the name doesn't meet some basic requirements. Tidies up a few other things like bad-characters. - - for(var/mob/living/M in GLOB.player_list) - if(M == src) - continue - if(!newname || M.real_name == newname) - newname = null - break - if(newname) - break //That's a suitable name! - to_chat(src, "Sorry, that [role]-name wasn't appropriate, please try another. It's possibly too long/short, has bad characters or is already taken.") - - if(!newname) //we'll stick with the oldname then - return - - rename_character(oldname, newname) - -/proc/cultslur(n) // Inflicted on victims of a stun talisman - var/phrase = html_decode(n) - var/leng = length(phrase) - var/counter=length(phrase) - var/newphrase="" - var/newletter="" - while(counter>=1) - newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) - if(rand(1,2)==2) - if(lowertext(newletter)=="o") - newletter="u" - if(lowertext(newletter)=="t") - newletter="ch" - if(lowertext(newletter)=="a") - newletter="ah" - if(lowertext(newletter)=="u") - newletter="oo" - if(lowertext(newletter)=="c") - newletter=" NAR " - if(lowertext(newletter)=="s") - newletter=" SIE " - if(rand(1,4)==4) - if(newletter==" ") - newletter=" no hope... " - if(newletter=="H") - newletter=" IT COMES... " - - switch(rand(1,15)) - if(1) - newletter="'" - if(2) - newletter+="agn" - if(3) - newletter="fth" - if(4) - newletter="nglu" - if(5) - newletter="glor" - newphrase+="[newletter]";counter-=1 - return newphrase - -/mob/proc/get_preference(toggleflag) - if(!client) - return FALSE - if(!client.prefs) - log_runtime(EXCEPTION("Mob '[src]', ckey '[ckey]' is missing a prefs datum on the client!")) - return FALSE - // Cast to 1/0 - return !!(client.prefs.toggles & toggleflag) - -// Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense. -// A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled -// Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible" -// Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role -// This also does some admin notification and logging as well -/mob/proc/has_valid_preferences() - if(!client) - return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. - if(client.prefs.alternate_option != RETURN_TO_LOBBY) - return TRUE - // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. - var/has_antags = FALSE - if(client.prefs.be_special.len > 0) - has_antags = TRUE - if(!client.prefs.check_any_job()) - to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.") - if(has_antags) - log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.") - message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") - return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well - return TRUE - -#define isterrorspider(A) (istype((A), /mob/living/simple_animal/hostile/poison/terror_spider)) +/proc/issmall(A) + if(A && istype(A, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = A + if(H.dna.species && H.dna.species.is_small) + return 1 + return 0 + +/proc/ispet(A) + if(istype(A, /mob/living/simple_animal)) + var/mob/living/simple_animal/SA = A + if(SA.can_collar) + return 1 + return 0 + +/mob/proc/isSynthetic() + return 0 + +/mob/living/carbon/human/isSynthetic() + if(ismachine(src)) + return TRUE + return FALSE + +/mob/proc/get_screen_colour() + +/mob/proc/update_client_colour(var/time = 10) //Update the mob's client.color with an animation the specified time in length. + if(!client) //No client_colour without client. If the player logs back in they'll be back through here anyway. + return + client.colour_transition(get_screen_colour(), time = time) //Get the colour matrix we're going to transition to depending on relevance (magic glasses first, eyes second). + +/mob/living/carbon/human/get_screen_colour() //Fetch the colour matrix from wherever (e.g. eyes) so it can be compared to client.color. + . = ..() + if(.) + return . + + var/obj/item/clothing/glasses/worn_glasses = glasses + var/obj/item/organ/internal/eyes/eyes = get_int_organ(/obj/item/organ/internal/eyes) + if(istype(worn_glasses) && worn_glasses.color_view) //Check to see if they got those magic glasses and they're augmenting the colour of what the wearer sees. If they're not, color_view should be null. + return worn_glasses.color_view + else if(eyes) //If they're not, check to see if their eyes got one of them there colour matrices. Will be null if eyes are robotic/the mob isn't colourblind and they have no default colour matrix. + return eyes.get_colourmatrix() + +/proc/ismindshielded(A) //Checks to see if the person contains a mindshield implant, then checks that the implant is actually inside of them + for(var/obj/item/implant/mindshield/L in A) + if(L && L.implanted) + return 1 + return 0 + +/proc/ismindslave(A) //Checks to see if the person contains a mindslave implant, then checks that the implant is actually inside of them + for(var/obj/item/implant/traitor/T in A) + if(T && T.implanted) + return 1 + return 0 + +/proc/isLivingSSD(mob/M) + return istype(M) && M.player_logged && M.stat != DEAD + +/proc/isAntag(A) + if(istype(A, /mob/living/carbon)) + var/mob/living/carbon/C = A + if(C.mind && C.mind.special_role) + return 1 + return 0 + +/proc/isNonCrewAntag(A) + if(!isAntag(A)) + return 0 + + var/mob/living/carbon/C = A + var/special_role = C.mind.special_role + var/list/crew_roles = list( + SPECIAL_ROLE_BLOB, + SPECIAL_ROLE_CULTIST, + SPECIAL_ROLE_CHANGELING, + SPECIAL_ROLE_ERT, + SPECIAL_ROLE_HEAD_REV, + SPECIAL_ROLE_REV, + SPECIAL_ROLE_SHADOWLING, + SPECIAL_ROLE_SHADOWLING_THRALL, + SPECIAL_ROLE_TRAITOR, + SPECIAL_ROLE_VAMPIRE, + SPECIAL_ROLE_VAMPIRE_THRALL + ) + if(special_role in crew_roles) + return 0 + + return 1 + +/proc/cannotPossess(A) + var/mob/dead/observer/G = A + if(G.has_enabled_antagHUD == 1 && config.antag_hud_restricted) + return 1 + return 0 + + +/proc/iscuffed(A) + if(istype(A, /mob/living/carbon)) + var/mob/living/carbon/C = A + if(C.handcuffed) + return 1 + return 0 + +/proc/hassensorlevel(A, var/level) + var/mob/living/carbon/human/H = A + if(istype(H) && istype(H.w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = H.w_uniform + return U.sensor_mode >= level + return 0 + +/proc/getsensorlevel(A) + var/mob/living/carbon/human/H = A + if(istype(H) && istype(H.w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = H.w_uniform + return U.sensor_mode + return SUIT_SENSOR_OFF + +/proc/offer_control(mob/M) + to_chat(M, "Control of your mob has been offered to dead players.") + log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.") + var/minhours = input(usr, "Minimum hours required to play [M]?", "Set Min Hrs", 10) as num + message_admins("[key_name_admin(usr)] has offered control of ([key_name_admin(M)]) to ghosts with [minhours] hrs playtime") + var/question = "Do you want to play as [M.real_name ? M.real_name : M.name][M.job ? " ([M.job])" : ""]" + if(alert("Do you want to show the antag status?","Show antag status","Yes","No") == "Yes") + question += ", [M.mind?.special_role ? M.mind?.special_role : "No special role"]" + var/list/mob/dead/observer/candidates = pollCandidates("[question]?", poll_time = 100, min_hours = minhours) + var/mob/dead/observer/theghost = null + + if(LAZYLEN(candidates)) + theghost = pick(candidates) + to_chat(M, "Your mob has been taken over by a ghost!") + message_admins("[key_name_admin(theghost)] has taken control of ([key_name_admin(M)])") + M.ghostize() + M.key = theghost.key + else + to_chat(M, "There were no ghosts willing to take control.") + message_admins("No ghosts were willing to take control of [key_name_admin(M)])") + +/proc/check_zone(zone) + if(!zone) return "chest" + switch(zone) + if("eyes") + zone = "head" + if("mouth") + zone = "head" + return zone + +// Returns zone with a certain probability. +// If the probability misses, returns "chest" instead. +// If "chest" was passed in as zone, then on a "miss" will return "head", "l_arm", or "r_arm" +// Do not use this if someone is intentionally trying to hit a specific body part. +// Use get_zone_with_miss_chance() for that. +/proc/ran_zone(zone, probability = 80) + + zone = check_zone(zone) + + if(prob(probability)) + return zone + + var/t = rand(1, 18) // randomly pick a different zone, or maybe the same one + switch(t) + if(1) return "head" + if(2) return "chest" + if(3 to 4) return "l_arm" + if(5 to 6) return "l_hand" + if(7 to 8) return "r_arm" + if(9 to 10) return "r_hand" + if(11 to 12) return "l_leg" + if(13 to 14) return "l_foot" + if(15 to 16) return "r_leg" + if(17 to 18) return "r_foot" + + return zone + +/proc/above_neck(zone) + var/list/zones = list("head", "mouth", "eyes") + if(zones.Find(zone)) + return 1 + else + return 0 + +/proc/stars(n, pr) + if(pr == null) + pr = 25 + if(pr <= 0) + return null + else + if(pr >= 100) + return n + var/te = n + var/t = "" + n = length(n) + var/p = null + p = 1 + while(p <= n) + if((copytext(te, p, p + 1) == " " || prob(pr))) + t = text("[][]", t, copytext(te, p, p + 1)) + else + t = text("[]*", t) + p++ + return t + +/proc/stars_all(list/message_pieces, pr) + for(var/datum/multilingual_say_piece/S in message_pieces) + S.message = stars(S.message, pr) + +/proc/slur(phrase, var/list/slurletters = ("'"))//use a different list as an input if you want to make robots slur with $#@%! characters + phrase = html_decode(phrase) + var/leng=length(phrase) + var/counter=length(phrase) + var/newphrase="" + var/newletter="" + while(counter>=1) + newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) + if(rand(1,3)==3) + if(lowertext(newletter)=="o") newletter="u" + if(lowertext(newletter)=="s") newletter="ch" + if(lowertext(newletter)=="a") newletter="ah" + if(lowertext(newletter)=="c") newletter="k" + switch(rand(1,15)) + if(1,3,5,8) newletter="[lowertext(newletter)]" + if(2,4,6,15) newletter="[uppertext(newletter)]" + if(7) newletter+=pick(slurletters) + //if(9,10) newletter="[newletter]" + //if(11,12) newletter="[newletter]" + //if(13) newletter="[newletter]" + newphrase+="[newletter]" + counter-=1 + return newphrase + +/proc/stutter(n) + var/te = html_decode(n) + var/t = ""//placed before the message. Not really sure what it's for. + n = length(n)//length of the entire word + var/p = null + p = 1//1 is the start of any word + while(p <= n)//while P, which starts at 1 is less or equal to N which is the length. + var/n_letter = copytext(te, p, p + 1)//copies text from a certain distance. In this case, only one letter at a time. + if(prob(80) && (ckey(n_letter) in list("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"))) + if(prob(10)) + n_letter = text("[n_letter]-[n_letter]-[n_letter]-[n_letter]")//replaces the current letter with this instead. + else + if(prob(20)) + n_letter = text("[n_letter]-[n_letter]-[n_letter]") + else + if(prob(5)) + n_letter = null + else + n_letter = text("[n_letter]-[n_letter]") + t = text("[t][n_letter]")//since the above is ran through for each letter, the text just adds up back to the original word. + p++//for each letter p is increased to find where the next letter will be. + return sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + +/proc/robostutter(n) //for robutts + var/te = html_decode(n) + var/t = ""//placed before the message. Not really sure what it's for. + n = length(n)//length of the entire word + var/p = null + p = 1//1 is the start of any word + while(p <= n)//while P, which starts at 1 is less or equal to N which is the length. + var/robotletter = pick("@", "!", "#", "$", "%", "&", "?") //for beep boop + var/n_letter = copytext(te, p, p + 1)//copies text from a certain distance. In this case, only one letter at a time. + if(prob(80) && (ckey(n_letter) in list("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"))) + if(prob(10)) + n_letter = text("[n_letter]-[robotletter]-[n_letter]-[n_letter]")//replaces the current letter with this instead. + else + if(prob(20)) + n_letter = text("[n_letter]-[robotletter]-[n_letter]") + else + if(prob(5)) + n_letter = robotletter + else + n_letter = text("[n_letter]-[n_letter]") + t = text("[t][n_letter]")//since the above is ran through for each letter, the text just adds up back to the original word. + p++//for each letter p is increased to find where the next letter will be. + return sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + + +/proc/Gibberish(t, p)//t is the inputted message, and any value higher than 70 for p will cause letters to be replaced instead of added + /* Turn text into complete gibberish! */ + var/returntext = "" + for(var/i = 1, i <= length(t), i++) + + var/letter = copytext(t, i, i+1) + if(prob(50)) + if(p >= 70) + letter = "" + + for(var/j = 1, j <= rand(0, 2), j++) + letter += pick("#","@","*","&","%","$","/", "<", ">", ";","*","*","*","*","*","*","*") + + returntext += letter + + return returntext + +/proc/Gibberish_all(list/message_pieces, p) + for(var/datum/multilingual_say_piece/S in message_pieces) + S.message = Gibberish(S.message, p) + + +proc/muffledspeech(phrase) + phrase = html_decode(phrase) + var/leng=length(phrase) + var/counter=length(phrase) + var/newphrase="" + var/newletter="" + while(counter>=1) + newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) + if(newletter in list(" ", "!", "?", ".", ",")) + //do nothing + else if(lowertext(newletter) in list("a", "e", "i", "o", "u", "y")) + newletter = "ph" + else + newletter = "m" + newphrase+="[newletter]" + counter-=1 + return newphrase + +/proc/muffledspeech_all(list/message_pieces) + for(var/datum/multilingual_say_piece/S in message_pieces) + S.message = muffledspeech(S.message) + + +/proc/shake_camera(mob/M, duration, strength=1) + if(!M || !M.client || M.shakecamera) + return + M.shakecamera = 1 + spawn(1) + + var/atom/oldeye=M.client.eye + var/aiEyeFlag = 0 + if(istype(oldeye, /mob/camera/aiEye)) + aiEyeFlag = 1 + + var/x + for(x=0; x
        ") + return + else + if(alert(src, "You sure you want to sleep for a while?", "Sleep", "Yes", "No") == "Yes") + SetSleeping(20) //Short nap + +/mob/living/verb/lay_down() + set name = "Rest" + set category = "IC" + + if(!resting) + client.move_delay = world.time + 20 + to_chat(src, "You are now resting.") + StartResting() + else if(resting) + to_chat(src, "You are now getting up.") + StopResting() + +/proc/get_multitool(mob/user as mob) + // Get tool + var/obj/item/multitool/P + if(isrobot(user) || ishuman(user)) + P = user.get_active_hand() + else if(isAI(user)) + var/mob/living/silicon/ai/AI=user + P = AI.aiMulti + + if(!istype(P)) + return null + return P + +/proc/get_both_hands(mob/living/carbon/M) + return list(M.l_hand, M.r_hand) + + +//Direct dead say used both by emote and say +//It is somewhat messy. I don't know what to do. +//I know you can't see the change, but I rewrote the name code. It is significantly less messy now +/proc/say_dead_direct(var/message, var/mob/subject = null) + var/name + var/keyname + if(subject && subject.client) + var/client/C = subject.client + keyname = (C.holder && C.holder.fakekey) ? C.holder.fakekey : C.key + if(C.mob) //Most of the time this is the dead/observer mob; we can totally use him if there is no better name + var/mindname + var/realname = C.mob.real_name + if(C.mob.mind) + mindname = C.mob.mind.name + if(C.mob.mind.original && C.mob.mind.original.real_name) + realname = C.mob.mind.original.real_name + if(mindname && mindname != realname) + name = "[realname] died as [mindname]" + else + name = realname + + for(var/mob/M in GLOB.player_list) + if(M.client && ((!istype(M, /mob/new_player) && M.stat == DEAD) || check_rights(R_ADMIN|R_MOD,0,M)) && M.get_preference(CHAT_DEAD)) + var/follow + var/lname + if(subject) + if(subject != M) + follow = "([ghost_follow_link(subject, ghost=M)]) " + if(M.stat != DEAD && check_rights(R_ADMIN|R_MOD,0,M)) + follow = "([admin_jump_link(subject)]) " + var/mob/dead/observer/DM + if(istype(subject, /mob/dead/observer)) + DM = subject + if(check_rights(R_ADMIN|R_MOD,0,M)) // What admins see + lname = "[keyname][(DM && DM.client && DM.client.prefs.ghost_anonsay) ? "*" : (DM ? "" : "^")] ([name])" + else + if(DM && DM.client && DM.client.prefs.ghost_anonsay) // If the person is actually observer they have the option to be anonymous + lname = "Ghost of [name]" + else if(DM) // Non-anons + lname = "[keyname] ([name])" + else // Everyone else (dead people who didn't ghost yet, etc.) + lname = name + lname = "[lname] " + to_chat(M, "[lname][follow][message]") + +/proc/notify_ghosts(message, ghost_sound = null, enter_link = null, title = null, atom/source = null, image/alert_overlay = null, flashwindow = TRUE, var/action = NOTIFY_JUMP) //Easy notification of ghosts. + for(var/mob/dead/observer/O in GLOB.player_list) + if(O.client) + to_chat(O, "[message][(enter_link) ? " [enter_link]" : ""]") + if(ghost_sound) + O << sound(ghost_sound) + if(flashwindow) + window_flash(O.client) + if(source) + var/obj/screen/alert/notify_action/A = O.throw_alert("\ref[source]_notify_action", /obj/screen/alert/notify_action) + if(A) + if(O.client.prefs && O.client.prefs.UI_style) + A.icon = ui_style2icon(O.client.prefs.UI_style) + if(title) + A.name = title + A.desc = message + A.action = action + A.target = source + if(!alert_overlay) + var/old_layer = source.layer + var/old_plane = source.plane + source.layer = FLOAT_LAYER + source.plane = FLOAT_PLANE + A.overlays += source + source.layer = old_layer + source.plane = old_plane + else + alert_overlay.layer = FLOAT_LAYER + alert_overlay.plane = FLOAT_PLANE + A.overlays += alert_overlay + +/mob/proc/switch_to_camera(var/obj/machinery/camera/C) + if(!C.can_use() || stat || (get_dist(C, src) > 1 || machine != src || !has_vision() || !canmove)) + return 0 + check_eye(src) + return 1 + +/mob/proc/rename_character(oldname, newname) + if(!newname) + return 0 + real_name = newname + name = newname + if(mind) + mind.name = newname + if(dna) + dna.real_name = real_name + + if(oldname) + //update the datacore records! This is goig to be a bit costly. + for(var/list/L in list(data_core.general,data_core.medical,data_core.security,data_core.locked)) + for(var/datum/data/record/R in L) + if(R.fields["name"] == oldname) + R.fields["name"] = newname + break + + //update our pda and id if we have them on our person + var/list/searching = GetAllContents(searchDepth = 3) + var/search_id = 1 + var/search_pda = 1 + + for(var/A in searching) + if( search_id && istype(A,/obj/item/card/id) ) + var/obj/item/card/id/ID = A + if(ID.registered_name == oldname) + ID.registered_name = newname + ID.name = "[newname]'s ID Card ([ID.assignment])" + ID.RebuildHTML() + if(!search_pda) break + search_id = 0 + + else if( search_pda && istype(A,/obj/item/pda) ) + var/obj/item/pda/PDA = A + if(PDA.owner == oldname) + PDA.owner = newname + PDA.name = "PDA-[newname] ([PDA.ownjob])" + if(!search_id) break + search_pda = 0 + + //Fixes renames not being reflected in objective text + var/length + var/pos + for(var/datum/objective/objective in GLOB.all_objectives) + if(!mind || objective.target != mind) + continue + length = length(oldname) + pos = findtextEx(objective.explanation_text, oldname) + objective.explanation_text = copytext(objective.explanation_text, 1, pos)+newname+copytext(objective.explanation_text, pos+length) + return 1 + +/mob/proc/rename_self(var/role, var/allow_numbers = FALSE, var/force = FALSE) + spawn(0) + var/oldname = real_name + + var/time_passed = world.time + var/newname + + for(var/i=1,i<=3,i++) //we get 3 attempts to pick a suitable name. + if(force) + newname = clean_input("Pick a new name.", "Name Change", oldname, src) + else + newname = clean_input("You are a [role]. Would you like to change your name to something else? (You have 3 minutes to select a new name.)", "Name Change", oldname, src) + if(((world.time - time_passed) > 1800) && !force) + alert(src, "Unfortunately, more than 3 minutes have passed for selecting your name. If you are a robot, use the Namepick verb; otherwise, adminhelp.", "Name Change") + return //took too long + newname = reject_bad_name(newname,allow_numbers) //returns null if the name doesn't meet some basic requirements. Tidies up a few other things like bad-characters. + + for(var/mob/living/M in GLOB.player_list) + if(M == src) + continue + if(!newname || M.real_name == newname) + newname = null + break + if(newname) + break //That's a suitable name! + to_chat(src, "Sorry, that [role]-name wasn't appropriate, please try another. It's possibly too long/short, has bad characters or is already taken.") + + if(!newname) //we'll stick with the oldname then + return + + rename_character(oldname, newname) + +/proc/cultslur(n) // Inflicted on victims of a stun talisman + var/phrase = html_decode(n) + var/leng = length(phrase) + var/counter=length(phrase) + var/newphrase="" + var/newletter="" + while(counter>=1) + newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) + if(rand(1,2)==2) + if(lowertext(newletter)=="o") + newletter="u" + if(lowertext(newletter)=="t") + newletter="ch" + if(lowertext(newletter)=="a") + newletter="ah" + if(lowertext(newletter)=="u") + newletter="oo" + if(lowertext(newletter)=="c") + newletter=" NAR " + if(lowertext(newletter)=="s") + newletter=" SIE " + if(rand(1,4)==4) + if(newletter==" ") + newletter=" no hope... " + if(newletter=="H") + newletter=" IT COMES... " + + switch(rand(1,15)) + if(1) + newletter="'" + if(2) + newletter+="agn" + if(3) + newletter="fth" + if(4) + newletter="nglu" + if(5) + newletter="glor" + newphrase+="[newletter]";counter-=1 + return newphrase + +/mob/proc/get_preference(toggleflag) + if(!client) + return FALSE + if(!client.prefs) + log_runtime(EXCEPTION("Mob '[src]', ckey '[ckey]' is missing a prefs datum on the client!")) + return FALSE + // Cast to 1/0 + return !!(client.prefs.toggles & toggleflag) + +// Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense. +// A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled +// Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible" +// Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role +// This also does some admin notification and logging as well +/mob/proc/has_valid_preferences() + if(!client) + return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe. + if(client.prefs.alternate_option != RETURN_TO_LOBBY) + return TRUE + // If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so. + var/has_antags = FALSE + if(client.prefs.be_special.len > 0) + has_antags = TRUE + if(!client.prefs.check_any_job()) + to_chat(src, "You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.") + if(has_antags) + log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.") + message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") + return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well + return TRUE + +#define isterrorspider(A) (istype((A), /mob/living/simple_animal/hostile/poison/terror_spider)) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 94f1e2048db..48f149fa096 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -1,509 +1,509 @@ -/mob/CanPass(atom/movable/mover, turf/target, height=0) - if(height==0) - return 1 - if(istype(mover, /obj/item/projectile)) - return (!density || lying) - if(mover.throwing) - return (!density || lying || (mover.throwing.thrower == src)) - if(mover.checkpass(PASSMOB)) - return 1 - if(buckled == mover) - return TRUE - if(ismob(mover)) - var/mob/moving_mob = mover - if((other_mobs && moving_mob.other_mobs)) - return TRUE - if(mover in buckled_mobs) - return TRUE - return (!mover.density || !density || lying) - - -/client/verb/toggle_throw_mode() - set hidden = 1 - if(iscarbon(mob)) - var/mob/living/carbon/C = mob - C.toggle_throw_mode() - else - to_chat(usr, "This mob type cannot throw items.") - - -/client/verb/drop_item() - set hidden = 1 - if(!isrobot(mob)) - mob.drop_item_v() - return - - -/* /client/Center() - /* No 3D movement in 2D spessman game. dir 16 is Z Up - if(isobj(mob.loc)) - var/obj/O = mob.loc - if(mob.canmove) - return O.relaymove(mob, 16) - */ - return - */ - - -/client/proc/Move_object(direct) - if(mob && mob.control_object) - if(mob.control_object.density) - step(mob.control_object, direct) - if(!mob.control_object) - return - mob.control_object.setDir(direct) - else - mob.control_object.forceMove(get_step(mob.control_object, direct)) - return - -#define MOVEMENT_DELAY_BUFFER 0.75 -#define MOVEMENT_DELAY_BUFFER_DELTA 1.25 -/client/Move(n, direct) - if(world.time < move_delay) - return - else - next_move_dir_add = 0 - next_move_dir_sub = 0 - var/old_move_delay = move_delay - move_delay = world.time + world.tick_lag //this is here because Move() can now be called multiple times per tick - if(!mob || !mob.loc) - return 0 - - if(!n || !direct) // why did we never check this before? - return FALSE - - if(mob.notransform) - return 0 //This is sota the goto stop mobs from moving var - - if(mob.control_object) - return Move_object(direct) - - if(!isliving(mob)) - return mob.Move(n, direct) - - if(mob.stat == DEAD) - mob.ghostize() - return 0 - - if(moving) - return 0 - - if(isliving(mob)) - var/mob/living/L = mob - if(L.incorporeal_move)//Move though walls - Process_Incorpmove(direct) - return - - if(mob.remote_control) //we're controlling something, our movement is relayed to it - return mob.remote_control.relaymove(mob, direct) - - if(isAI(mob)) - if(istype(mob.loc, /obj/item/aicard)) - var/obj/O = mob.loc - return O.relaymove(mob, direct) // aicards have special relaymove stuff - return AIMove(n, direct, mob) - - - if(Process_Grab()) - return - - if(mob.buckled) //if we're buckled to something, tell it we moved. - return mob.buckled.relaymove(mob, direct) - - if(!mob.canmove) - return - - if(!mob.lastarea) - mob.lastarea = get_area(mob.loc) - - if(isobj(mob.loc) || ismob(mob.loc)) //Inside an object, tell it we moved - var/atom/O = mob.loc - return O.relaymove(mob, direct) - - if(!mob.Process_Spacemove(direct)) - return 0 - - if(mob.restrained()) // Why being pulled while cuffed prevents you from moving - for(var/mob/M in orange(1, mob)) - if(M.pulling == mob) - if(!M.incapacitated() && mob.Adjacent(M)) - to_chat(src, "You're restrained! You can't move!") - move_delay = world.time + 10 - return 0 - else - M.stop_pulling() - - - //We are now going to move - moving = 1 - var/delay = mob.movement_delay() - if(old_move_delay + (delay * MOVEMENT_DELAY_BUFFER_DELTA) + MOVEMENT_DELAY_BUFFER > world.time) - move_delay = old_move_delay - else - move_delay = world.time - mob.last_movement = world.time - - if(locate(/obj/item/grab, mob)) - move_delay = max(move_delay, world.time + 7) - var/list/L = mob.ret_grab() - if(istype(L, /list)) - if(L.len == 2) - L -= mob - var/mob/M = L[1] - if(M) - if((get_dist(mob, M) <= 1 || M.loc == mob.loc)) - var/turf/prev_loc = mob.loc - . = ..() - if(M && isturf(M.loc)) // Mob may get deleted during parent call - var/diag = get_dir(mob, M) - if((diag - 1) & diag) - else - diag = null - if((get_dist(mob, M) > 1 || diag)) - step(M, get_dir(M.loc, prev_loc)) - else - for(var/mob/M in L) - M.other_mobs = 1 - if(mob != M) - M.animate_movement = 3 - for(var/mob/M in L) - spawn(0) - step(M, direct) - return - spawn(1) - M.other_mobs = null - M.animate_movement = 2 - return - - else if(mob.confused) - var/newdir = 0 - if(mob.confused > 40) - newdir = pick(alldirs) - else if(prob(mob.confused * 1.5)) - newdir = angle2dir(dir2angle(direct) + pick(90, -90)) - else if(prob(mob.confused * 3)) - newdir = angle2dir(dir2angle(direct) + pick(45, -45)) - if(newdir) - direct = newdir - n = get_step(mob, direct) - - . = ..() - mob.setDir(direct) - - for(var/obj/item/grab/G in mob) - if(G.state == GRAB_NECK) - mob.setDir(angle2dir((dir2angle(direct) + 202.5) % 365)) - G.adjust_position() - for(var/obj/item/grab/G in mob.grabbed_by) - G.adjust_position() - if((direct & (direct - 1)) && mob.loc == n) //moved diagonally successfully - delay = mob.movement_delay() * 2 - move_delay += delay - moving = 0 - if(mob && .) - if(mob.throwing) - mob.throwing.finalize(FALSE) - - for(var/obj/O in mob) - O.on_mob_move(direct, mob) - - - - - -///Process_Grab() -///Called by client/Move() -///Checks to see if you are being grabbed and if so attemps to break it -/client/proc/Process_Grab() - if(mob.grabbed_by.len) - var/list/grabbing = list() - - if(istype(mob.l_hand, /obj/item/grab)) - var/obj/item/grab/G = mob.l_hand - grabbing += G.affecting - - if(istype(mob.r_hand, /obj/item/grab)) - var/obj/item/grab/G = mob.r_hand - grabbing += G.affecting - - for(var/X in mob.grabbed_by) - var/obj/item/grab/G = X - switch(G.state) - - if(GRAB_PASSIVE) - if(!grabbing.Find(G.assailant)) //moving always breaks a passive grab unless we are also grabbing our grabber. - qdel(G) - - if(GRAB_AGGRESSIVE) - move_delay = world.time + 10 - if(!prob(25)) - return 1 - mob.visible_message("[mob] has broken free of [G.assailant]'s grip!") - qdel(G) - - if(GRAB_NECK) - move_delay = world.time + 10 - if(!prob(5)) - return 1 - mob.visible_message("[mob] has broken free of [G.assailant]'s headlock!") - qdel(G) - return 0 - - -///Process_Incorpmove -///Called by client/Move() -///Allows mobs to run though walls -/client/proc/Process_Incorpmove(direct) - var/turf/mobloc = get_turf(mob) - if(!isliving(mob)) - return - var/mob/living/L = mob - switch(L.incorporeal_move) - if(1) - L.forceMove(get_step(L, direct)) - L.dir = direct - if(2) - if(prob(50)) - var/locx - var/locy - switch(direct) - if(NORTH) - locx = mobloc.x - locy = (mobloc.y+2) - if(locy>world.maxy) - return - if(SOUTH) - locx = mobloc.x - locy = (mobloc.y-2) - if(locy<1) - return - if(EAST) - locy = mobloc.y - locx = (mobloc.x+2) - if(locx>world.maxx) - return - if(WEST) - locy = mobloc.y - locx = (mobloc.x-2) - if(locx<1) - return - else - return - L.forceMove(locate(locx,locy,mobloc.z)) - spawn(0) - var/limit = 2//For only two trailing shadows. - for(var/turf/T in getline(mobloc, L.loc)) - new /obj/effect/temp_visual/dir_setting/ninja/shadow(T, L.dir) - limit-- - if(limit<=0) - break - else - new /obj/effect/temp_visual/dir_setting/ninja/shadow(mobloc, L.dir) - L.forceMove(get_step(L, direct)) - L.dir = direct - if(3) //Incorporeal move, but blocked by holy-watered tiles - var/turf/simulated/floor/stepTurf = get_step(L, direct) - if(stepTurf.flags & NOJAUNT) - to_chat(L, "Holy energies block your path.") - L.notransform = 1 - spawn(2) - L.notransform = 0 - else - L.forceMove(get_step(L, direct)) - L.dir = direct - return 1 - - -///Process_Spacemove -///Called by /client/Move() -///For moving in space -///Return 1 for movement 0 for none -/mob/Process_Spacemove(movement_dir = 0) - if(..()) - return 1 - var/atom/movable/backup = get_spacemove_backup() - if(backup) - if(istype(backup) && movement_dir && !backup.anchored) - var/opposite_dir = turn(movement_dir, 180) - if(backup.newtonian_move(opposite_dir)) //You're pushing off something movable, so it moves - to_chat(src, "You push off of [backup] to propel yourself.") - return 1 - return 0 - -/mob/get_spacemove_backup() - for(var/A in orange(1, get_turf(src))) - if(isarea(A)) - continue - else if(isturf(A)) - var/turf/turf = A - if(istype(turf, /turf/space)) - continue - if(!turf.density && !mob_negates_gravity()) - continue - return A - else - var/atom/movable/AM = A - if(AM == buckled) //Kind of unnecessary but let's just be sure - continue - if(!AM.CanPass(src) || AM.density) - if(AM.anchored) - return AM - if(pulling == AM) - continue - . = AM - - -/mob/proc/mob_has_gravity(turf/T) - return has_gravity(src, T) - -/mob/proc/mob_negates_gravity() - return 0 - -/mob/proc/Move_Pulled(atom/A) - if(!canmove || restrained() || !pulling) - return - if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src)) - stop_pulling() - return - if(isliving(pulling)) - var/mob/living/L = pulling - if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it - stop_pulling() - return - if(A == loc && pulling.density) - return - if(!Process_Spacemove(get_dir(pulling.loc, A))) - return - if(ismob(pulling)) - var/mob/M = pulling - var/atom/movable/t = M.pulling - M.stop_pulling() - step(pulling, get_dir(pulling.loc, A)) - if(M) - M.start_pulling(t) - else - step(pulling, get_dir(pulling.loc, A)) - return - -/mob/proc/update_gravity() - return -/client/proc/check_has_body_select() - return mob && mob.hud_used && mob.hud_used.zone_select && istype(mob.hud_used.zone_select, /obj/screen/zone_sel) - -/client/verb/body_toggle_head() - set name = "body-toggle-head" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/next_in_line - switch(mob.zone_selected) - if(BODY_ZONE_HEAD) - next_in_line = BODY_ZONE_PRECISE_EYES - if(BODY_ZONE_PRECISE_EYES) - next_in_line = BODY_ZONE_PRECISE_MOUTH - else - next_in_line = BODY_ZONE_HEAD - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(next_in_line, mob) - -/client/verb/body_r_arm() - set name = "body-r-arm" - set hidden = 1 - if(!check_has_body_select()) - return - - var/next_in_line - if(mob.zone_selected == BODY_ZONE_R_ARM) - next_in_line = BODY_ZONE_PRECISE_R_HAND - else - next_in_line = BODY_ZONE_R_ARM - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(next_in_line, mob) - -/client/verb/body_chest() - set name = "body-chest" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(BODY_ZONE_CHEST, mob) - -/client/verb/body_l_arm() - set name = "body-l-arm" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/next_in_line - if(mob.zone_selected == BODY_ZONE_L_ARM) - next_in_line = BODY_ZONE_PRECISE_L_HAND - else - next_in_line = BODY_ZONE_L_ARM - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(next_in_line, mob) - -/client/verb/body_r_leg() - set name = "body-r-leg" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/next_in_line - if(mob.zone_selected == BODY_ZONE_R_LEG) - next_in_line = BODY_ZONE_PRECISE_R_FOOT - else - next_in_line = BODY_ZONE_R_LEG - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(next_in_line, mob) - -/client/verb/body_groin() - set name = "body-groin" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(BODY_ZONE_PRECISE_GROIN, mob) - -/client/verb/body_l_leg() - set name = "body-l-leg" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/next_in_line - if(mob.zone_selected == BODY_ZONE_L_LEG) - next_in_line = BODY_ZONE_PRECISE_L_FOOT - else - next_in_line = BODY_ZONE_L_LEG - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(next_in_line, mob) - -/client/verb/toggle_walk_run() - set name = "toggle-walk-run" - set hidden = TRUE - set instant = TRUE - if(mob) - mob.toggle_move_intent(usr) - -/mob/proc/toggle_move_intent(mob/user) - if(m_intent == MOVE_INTENT_RUN) - m_intent = MOVE_INTENT_WALK - else - m_intent = MOVE_INTENT_RUN - if(hud_used && hud_used.static_inventory) - for(var/obj/screen/mov_intent/selector in hud_used.static_inventory) - selector.update_icon(src) +/mob/CanPass(atom/movable/mover, turf/target, height=0) + if(height==0) + return 1 + if(istype(mover, /obj/item/projectile)) + return (!density || lying) + if(mover.throwing) + return (!density || lying || (mover.throwing.thrower == src)) + if(mover.checkpass(PASSMOB)) + return 1 + if(buckled == mover) + return TRUE + if(ismob(mover)) + var/mob/moving_mob = mover + if((other_mobs && moving_mob.other_mobs)) + return TRUE + if(mover in buckled_mobs) + return TRUE + return (!mover.density || !density || lying) + + +/client/verb/toggle_throw_mode() + set hidden = 1 + if(iscarbon(mob)) + var/mob/living/carbon/C = mob + C.toggle_throw_mode() + else + to_chat(usr, "This mob type cannot throw items.") + + +/client/verb/drop_item() + set hidden = 1 + if(!isrobot(mob)) + mob.drop_item_v() + return + + +/* /client/Center() + /* No 3D movement in 2D spessman game. dir 16 is Z Up + if(isobj(mob.loc)) + var/obj/O = mob.loc + if(mob.canmove) + return O.relaymove(mob, 16) + */ + return + */ + + +/client/proc/Move_object(direct) + if(mob && mob.control_object) + if(mob.control_object.density) + step(mob.control_object, direct) + if(!mob.control_object) + return + mob.control_object.setDir(direct) + else + mob.control_object.forceMove(get_step(mob.control_object, direct)) + return + +#define MOVEMENT_DELAY_BUFFER 0.75 +#define MOVEMENT_DELAY_BUFFER_DELTA 1.25 +/client/Move(n, direct) + if(world.time < move_delay) + return + else + next_move_dir_add = 0 + next_move_dir_sub = 0 + var/old_move_delay = move_delay + move_delay = world.time + world.tick_lag //this is here because Move() can now be called multiple times per tick + if(!mob || !mob.loc) + return 0 + + if(!n || !direct) // why did we never check this before? + return FALSE + + if(mob.notransform) + return 0 //This is sota the goto stop mobs from moving var + + if(mob.control_object) + return Move_object(direct) + + if(!isliving(mob)) + return mob.Move(n, direct) + + if(mob.stat == DEAD) + mob.ghostize() + return 0 + + if(moving) + return 0 + + if(isliving(mob)) + var/mob/living/L = mob + if(L.incorporeal_move)//Move though walls + Process_Incorpmove(direct) + return + + if(mob.remote_control) //we're controlling something, our movement is relayed to it + return mob.remote_control.relaymove(mob, direct) + + if(isAI(mob)) + if(istype(mob.loc, /obj/item/aicard)) + var/obj/O = mob.loc + return O.relaymove(mob, direct) // aicards have special relaymove stuff + return AIMove(n, direct, mob) + + + if(Process_Grab()) + return + + if(mob.buckled) //if we're buckled to something, tell it we moved. + return mob.buckled.relaymove(mob, direct) + + if(!mob.canmove) + return + + if(!mob.lastarea) + mob.lastarea = get_area(mob.loc) + + if(isobj(mob.loc) || ismob(mob.loc)) //Inside an object, tell it we moved + var/atom/O = mob.loc + return O.relaymove(mob, direct) + + if(!mob.Process_Spacemove(direct)) + return 0 + + if(mob.restrained()) // Why being pulled while cuffed prevents you from moving + for(var/mob/M in orange(1, mob)) + if(M.pulling == mob) + if(!M.incapacitated() && mob.Adjacent(M)) + to_chat(src, "You're restrained! You can't move!") + move_delay = world.time + 10 + return 0 + else + M.stop_pulling() + + + //We are now going to move + moving = 1 + var/delay = mob.movement_delay() + if(old_move_delay + (delay * MOVEMENT_DELAY_BUFFER_DELTA) + MOVEMENT_DELAY_BUFFER > world.time) + move_delay = old_move_delay + else + move_delay = world.time + mob.last_movement = world.time + + if(locate(/obj/item/grab, mob)) + move_delay = max(move_delay, world.time + 7) + var/list/L = mob.ret_grab() + if(istype(L, /list)) + if(L.len == 2) + L -= mob + var/mob/M = L[1] + if(M) + if((get_dist(mob, M) <= 1 || M.loc == mob.loc)) + var/turf/prev_loc = mob.loc + . = ..() + if(M && isturf(M.loc)) // Mob may get deleted during parent call + var/diag = get_dir(mob, M) + if((diag - 1) & diag) + else + diag = null + if((get_dist(mob, M) > 1 || diag)) + step(M, get_dir(M.loc, prev_loc)) + else + for(var/mob/M in L) + M.other_mobs = 1 + if(mob != M) + M.animate_movement = 3 + for(var/mob/M in L) + spawn(0) + step(M, direct) + return + spawn(1) + M.other_mobs = null + M.animate_movement = 2 + return + + else if(mob.confused) + var/newdir = 0 + if(mob.confused > 40) + newdir = pick(alldirs) + else if(prob(mob.confused * 1.5)) + newdir = angle2dir(dir2angle(direct) + pick(90, -90)) + else if(prob(mob.confused * 3)) + newdir = angle2dir(dir2angle(direct) + pick(45, -45)) + if(newdir) + direct = newdir + n = get_step(mob, direct) + + . = ..() + mob.setDir(direct) + + for(var/obj/item/grab/G in mob) + if(G.state == GRAB_NECK) + mob.setDir(angle2dir((dir2angle(direct) + 202.5) % 365)) + G.adjust_position() + for(var/obj/item/grab/G in mob.grabbed_by) + G.adjust_position() + if((direct & (direct - 1)) && mob.loc == n) //moved diagonally successfully + delay = mob.movement_delay() * 2 + move_delay += delay + moving = 0 + if(mob && .) + if(mob.throwing) + mob.throwing.finalize(FALSE) + + for(var/obj/O in mob) + O.on_mob_move(direct, mob) + + + + + +///Process_Grab() +///Called by client/Move() +///Checks to see if you are being grabbed and if so attemps to break it +/client/proc/Process_Grab() + if(mob.grabbed_by.len) + var/list/grabbing = list() + + if(istype(mob.l_hand, /obj/item/grab)) + var/obj/item/grab/G = mob.l_hand + grabbing += G.affecting + + if(istype(mob.r_hand, /obj/item/grab)) + var/obj/item/grab/G = mob.r_hand + grabbing += G.affecting + + for(var/X in mob.grabbed_by) + var/obj/item/grab/G = X + switch(G.state) + + if(GRAB_PASSIVE) + if(!grabbing.Find(G.assailant)) //moving always breaks a passive grab unless we are also grabbing our grabber. + qdel(G) + + if(GRAB_AGGRESSIVE) + move_delay = world.time + 10 + if(!prob(25)) + return 1 + mob.visible_message("[mob] has broken free of [G.assailant]'s grip!") + qdel(G) + + if(GRAB_NECK) + move_delay = world.time + 10 + if(!prob(5)) + return 1 + mob.visible_message("[mob] has broken free of [G.assailant]'s headlock!") + qdel(G) + return 0 + + +///Process_Incorpmove +///Called by client/Move() +///Allows mobs to run though walls +/client/proc/Process_Incorpmove(direct) + var/turf/mobloc = get_turf(mob) + if(!isliving(mob)) + return + var/mob/living/L = mob + switch(L.incorporeal_move) + if(1) + L.forceMove(get_step(L, direct)) + L.dir = direct + if(2) + if(prob(50)) + var/locx + var/locy + switch(direct) + if(NORTH) + locx = mobloc.x + locy = (mobloc.y+2) + if(locy>world.maxy) + return + if(SOUTH) + locx = mobloc.x + locy = (mobloc.y-2) + if(locy<1) + return + if(EAST) + locy = mobloc.y + locx = (mobloc.x+2) + if(locx>world.maxx) + return + if(WEST) + locy = mobloc.y + locx = (mobloc.x-2) + if(locx<1) + return + else + return + L.forceMove(locate(locx,locy,mobloc.z)) + spawn(0) + var/limit = 2//For only two trailing shadows. + for(var/turf/T in getline(mobloc, L.loc)) + new /obj/effect/temp_visual/dir_setting/ninja/shadow(T, L.dir) + limit-- + if(limit<=0) + break + else + new /obj/effect/temp_visual/dir_setting/ninja/shadow(mobloc, L.dir) + L.forceMove(get_step(L, direct)) + L.dir = direct + if(3) //Incorporeal move, but blocked by holy-watered tiles + var/turf/simulated/floor/stepTurf = get_step(L, direct) + if(stepTurf.flags & NOJAUNT) + to_chat(L, "Holy energies block your path.") + L.notransform = 1 + spawn(2) + L.notransform = 0 + else + L.forceMove(get_step(L, direct)) + L.dir = direct + return 1 + + +///Process_Spacemove +///Called by /client/Move() +///For moving in space +///Return 1 for movement 0 for none +/mob/Process_Spacemove(movement_dir = 0) + if(..()) + return 1 + var/atom/movable/backup = get_spacemove_backup() + if(backup) + if(istype(backup) && movement_dir && !backup.anchored) + var/opposite_dir = turn(movement_dir, 180) + if(backup.newtonian_move(opposite_dir)) //You're pushing off something movable, so it moves + to_chat(src, "You push off of [backup] to propel yourself.") + return 1 + return 0 + +/mob/get_spacemove_backup() + for(var/A in orange(1, get_turf(src))) + if(isarea(A)) + continue + else if(isturf(A)) + var/turf/turf = A + if(istype(turf, /turf/space)) + continue + if(!turf.density && !mob_negates_gravity()) + continue + return A + else + var/atom/movable/AM = A + if(AM == buckled) //Kind of unnecessary but let's just be sure + continue + if(!AM.CanPass(src) || AM.density) + if(AM.anchored) + return AM + if(pulling == AM) + continue + . = AM + + +/mob/proc/mob_has_gravity(turf/T) + return has_gravity(src, T) + +/mob/proc/mob_negates_gravity() + return 0 + +/mob/proc/Move_Pulled(atom/A) + if(!canmove || restrained() || !pulling) + return + if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src)) + stop_pulling() + return + if(isliving(pulling)) + var/mob/living/L = pulling + if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it + stop_pulling() + return + if(A == loc && pulling.density) + return + if(!Process_Spacemove(get_dir(pulling.loc, A))) + return + if(ismob(pulling)) + var/mob/M = pulling + var/atom/movable/t = M.pulling + M.stop_pulling() + step(pulling, get_dir(pulling.loc, A)) + if(M) + M.start_pulling(t) + else + step(pulling, get_dir(pulling.loc, A)) + return + +/mob/proc/update_gravity() + return +/client/proc/check_has_body_select() + return mob && mob.hud_used && mob.hud_used.zone_select && istype(mob.hud_used.zone_select, /obj/screen/zone_sel) + +/client/verb/body_toggle_head() + set name = "body-toggle-head" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/next_in_line + switch(mob.zone_selected) + if(BODY_ZONE_HEAD) + next_in_line = BODY_ZONE_PRECISE_EYES + if(BODY_ZONE_PRECISE_EYES) + next_in_line = BODY_ZONE_PRECISE_MOUTH + else + next_in_line = BODY_ZONE_HEAD + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(next_in_line, mob) + +/client/verb/body_r_arm() + set name = "body-r-arm" + set hidden = 1 + if(!check_has_body_select()) + return + + var/next_in_line + if(mob.zone_selected == BODY_ZONE_R_ARM) + next_in_line = BODY_ZONE_PRECISE_R_HAND + else + next_in_line = BODY_ZONE_R_ARM + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(next_in_line, mob) + +/client/verb/body_chest() + set name = "body-chest" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(BODY_ZONE_CHEST, mob) + +/client/verb/body_l_arm() + set name = "body-l-arm" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/next_in_line + if(mob.zone_selected == BODY_ZONE_L_ARM) + next_in_line = BODY_ZONE_PRECISE_L_HAND + else + next_in_line = BODY_ZONE_L_ARM + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(next_in_line, mob) + +/client/verb/body_r_leg() + set name = "body-r-leg" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/next_in_line + if(mob.zone_selected == BODY_ZONE_R_LEG) + next_in_line = BODY_ZONE_PRECISE_R_FOOT + else + next_in_line = BODY_ZONE_R_LEG + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(next_in_line, mob) + +/client/verb/body_groin() + set name = "body-groin" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(BODY_ZONE_PRECISE_GROIN, mob) + +/client/verb/body_l_leg() + set name = "body-l-leg" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/next_in_line + if(mob.zone_selected == BODY_ZONE_L_LEG) + next_in_line = BODY_ZONE_PRECISE_L_FOOT + else + next_in_line = BODY_ZONE_L_LEG + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(next_in_line, mob) + +/client/verb/toggle_walk_run() + set name = "toggle-walk-run" + set hidden = TRUE + set instant = TRUE + if(mob) + mob.toggle_move_intent(usr) + +/mob/proc/toggle_move_intent(mob/user) + if(m_intent == MOVE_INTENT_RUN) + m_intent = MOVE_INTENT_WALK + else + m_intent = MOVE_INTENT_RUN + if(hud_used && hud_used.static_inventory) + for(var/obj/screen/mov_intent/selector in hud_used.static_inventory) + selector.update_icon(src) diff --git a/code/modules/mob/new_player/logout.dm b/code/modules/mob/new_player/logout.dm index ad1c9b3098d..de6f38f337f 100644 --- a/code/modules/mob/new_player/logout.dm +++ b/code/modules/mob/new_player/logout.dm @@ -1,7 +1,7 @@ -/mob/new_player/Logout() - ready = 0 - ..() - if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to. - key = null//We null their key before deleting the mob, so they are properly kicked out. - qdel(src) - return \ No newline at end of file +/mob/new_player/Logout() + ready = 0 + ..() + if(!spawning)//Here so that if they are spawning and log out, the other procs can play out and they will have a mob to come back to. + key = null//We null their key before deleting the mob, so they are properly kicked out. + qdel(src) + return diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index fc246f661c3..6c9c04fded8 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -1,619 +1,619 @@ -/mob/new_player - var/ready = 0 - var/spawning = 0 //Referenced when you want to delete the new_player later on in the code. - var/totalPlayers = 0 //Player counts for the Lobby tab - var/totalPlayersReady = 0 - var/tos_consent = FALSE - universal_speak = 1 - - invisibility = 101 - - density = 0 - stat = 2 - canmove = 0 - -/mob/new_player/New() - GLOB.mob_list += src - -/mob/new_player/verb/new_player_panel() - set src = usr - - if(handle_tos_consent()) - new_player_panel_proc() - -/mob/new_player/proc/handle_tos_consent() - if(!GLOB.join_tos) - tos_consent = TRUE - return TRUE - - establish_db_connection() - if(!dbcon.IsConnected()) - tos_consent = TRUE - return TRUE - - var/DBQuery/query = dbcon.NewQuery("SELECT * FROM [format_table_name("privacy")] WHERE ckey='[src.ckey]' AND consent=1") - query.Execute() - while(query.NextRow()) - tos_consent = TRUE - return TRUE - - privacy_consent() - return FALSE - -/mob/new_player/proc/privacy_consent() - src << browse(null, "window=playersetup") - var/output = GLOB.join_tos - output += "

        I consent" - output += "

        I DO NOT consent" - src << browse(output,"window=privacy_consent;size=500x300") - var/datum/browser/popup = new(src, "privacy_consent", "

        Privacy Consent
        ", 500, 400) - popup.set_window_options("can_close=0") - popup.set_content(output) - popup.open(0) - return - - -/mob/new_player/proc/new_player_panel_proc() - var/real_name = client.prefs.real_name - if(client.prefs.randomslot) - real_name = "Random Character Slot" - var/output = "

        Setup Character
        [real_name]

        " - - if(!SSticker || SSticker.current_state <= GAME_STATE_PREGAME) - if(!ready) output += "

        Declare Ready

        " - else output += "

        You are ready (Cancel)

        " - else - output += "

        View the Crew Manifest

        " - output += "

        Join Game!

        " - - var/list/antags = client.prefs.be_special - if(antags && antags.len) - if(!client.skip_antag) output += "

        Global Antag Candidacy" - else output += "

        Global Antag Candidacy" - output += "
        You are [client.skip_antag ? "ineligible" : "eligible"] for all antag roles.

        " - - output += "

        Observe

        " - - if(GLOB.join_tos) - output += "

        Terms of Service

        " - - if(!IsGuestKey(src.key)) - establish_db_connection() - - if(dbcon.IsConnected() && client.can_vote()) - var/isadmin = 0 - if(src.client && src.client.holder) - isadmin = 1 - var/DBQuery/query = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[ckey]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[ckey]\")") - query.Execute() - var/newpoll = 0 - while(query.NextRow()) - newpoll = 1 - break - - if(newpoll) - output += "

        Show Player Polls (NEW!)

        " - else - output += "

        Show Player Polls

        " - - output += "
        " - - var/datum/browser/popup = new(src, "playersetup", "
        New Player Options
        ", 220, 290) - popup.set_window_options("can_close=0") - popup.set_content(output) - popup.open(0) - return - -/mob/new_player/Stat() - statpanel("Lobby") - if(client.statpanel=="Lobby" && SSticker) - if(SSticker.hide_mode) - stat("Game Mode:", "Secret") - else - if(SSticker.hide_mode == 0) - stat("Game Mode:", "[master_mode]") // Old setting for showing the game mode - else - stat("Game Mode: ", "Secret") - - if((SSticker.current_state == GAME_STATE_PREGAME) && going) - stat("Time To Start:", round(SSticker.pregame_timeleft/10)) - if((SSticker.current_state == GAME_STATE_PREGAME) && !going) - stat("Time To Start:", "DELAYED") - - if(SSticker.current_state == GAME_STATE_PREGAME) - stat("Players:", "[totalPlayers]") - if(check_rights(R_ADMIN, 0, src)) - stat("Players Ready:", "[totalPlayersReady]") - totalPlayers = 0 - totalPlayersReady = 0 - for(var/mob/new_player/player in GLOB.player_list) - if(check_rights(R_ADMIN, 0, src)) - stat("[player.key]", (player.ready)?("(Playing)"):(null)) - totalPlayers++ - if(player.ready) - totalPlayersReady++ - - ..() - - statpanel("Status") - - -/mob/new_player/Topic(href, href_list[]) - if(!client) return 0 - - if(href_list["consent_signed"]) - var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") - var/DBQuery/query = dbcon.NewQuery("REPLACE INTO [format_table_name("privacy")] (ckey, datetime, consent) VALUES ('[ckey]', '[sqltime]', 1)") - query.Execute() - src << browse(null, "window=privacy_consent") - tos_consent = 1 - new_player_panel_proc() - if(href_list["consent_rejected"]) - tos_consent = 0 - to_chat(usr, "You must consent to the terms of service before you can join!") - var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") - var/DBQuery/query = dbcon.NewQuery("REPLACE INTO [format_table_name("privacy")] (ckey, datetime, consent) VALUES ('[ckey]', '[sqltime]', 0)") - query.Execute() - - if(href_list["show_preferences"]) - client.prefs.ShowChoices(src) - return 1 - - if(href_list["ready"]) - if(!tos_consent) - to_chat(usr, "You must consent to the terms of service before you can join!") - return 0 - ready = !ready - new_player_panel_proc() - - if(href_list["skip_antag"]) - client.skip_antag = !client.skip_antag - new_player_panel_proc() - - if(href_list["refresh"]) - src << browse(null, "window=playersetup") //closes the player setup window - new_player_panel_proc() - - if(href_list["observe"]) - if(!tos_consent) - to_chat(usr, "You must consent to the terms of service before you can join!") - return 0 - - if(alert(src,"Are you sure you wish to observe? You cannot normally join the round after doing this!","Player Setup","Yes","No") == "Yes") - if(!client) - return 1 - var/mob/dead/observer/observer = new() - src << browse(null, "window=playersetup") - spawning = 1 - stop_sound_channel(CHANNEL_LOBBYMUSIC) - - - observer.started_as_observer = 1 - close_spawn_windows() - var/obj/O = locate("landmark*Observer-Start") - to_chat(src, "Now teleporting.") - observer.forceMove(O.loc) - observer.timeofdeath = world.time // Set the time of death so that the respawn timer works correctly. - client.prefs.update_preview_icon(1) - observer.icon = client.prefs.preview_icon - observer.alpha = 127 - - if(client.prefs.be_random_name) - client.prefs.real_name = random_name(client.prefs.gender,client.prefs.species) - observer.real_name = client.prefs.real_name - observer.name = observer.real_name - if(!client.holder && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed. - observer.verbs -= /mob/dead/observer/verb/toggle_antagHUD // Poor guys, don't know what they are missing! - observer.key = key - GLOB.respawnable_list += observer - qdel(src) - return 1 - if(href_list["tos"]) - privacy_consent() - return 0 - - if(href_list["late_join"]) - if(!tos_consent) - to_chat(usr, "You must consent to the terms of service before you can join!") - return 0 - if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING) - to_chat(usr, "The round is either not ready, or has already finished...") - return - if(client.prefs.species in GLOB.whitelisted_species) - - if(!is_alien_whitelisted(src, client.prefs.species) && config.usealienwhitelist) - to_chat(src, alert("You are currently not whitelisted to play [client.prefs.species].")) - return 0 - - LateChoices() - - if(href_list["manifest"]) - ViewManifest() - - if(href_list["SelectedJob"]) - - if(!enter_allowed) - to_chat(usr, "There is an administrative lock on entering the game!") - return - - if(client.prefs.randomslot) - client.prefs.load_random_character_slot(client) - - if(client.prefs.species in GLOB.whitelisted_species) - if(!is_alien_whitelisted(src, client.prefs.species) && config.usealienwhitelist) - to_chat(src, alert("You are currently not whitelisted to play [client.prefs.species].")) - return 0 - - AttemptLateSpawn(href_list["SelectedJob"],client.prefs.spawnpoint) - return - - if(!ready && href_list["preference"]) - if(client) - client.prefs.process_link(src, href_list) - else if(!href_list["late_join"]) - new_player_panel() - -/mob/new_player/proc/IsJobAvailable(rank) - var/datum/job/job = SSjobs.GetJob(rank) - if(!job) return 0 - if(!job.is_position_available()) return 0 - if(jobban_isbanned(src,rank)) return 0 - if(!is_job_whitelisted(src, rank)) return 0 - if(!job.player_old_enough(client)) return 0 - if(job.admin_only && !(check_rights(R_EVENT, 0))) return 0 - if(job.available_in_playtime(client)) - return 0 - - if(config.assistantlimit) - if(job.title == "Civilian") - var/count = 0 - var/datum/job/officer = SSjobs.GetJob("Security Officer") - var/datum/job/warden = SSjobs.GetJob("Warden") - var/datum/job/hos = SSjobs.GetJob("Head of Security") - count += (officer.current_positions + warden.current_positions + hos.current_positions) - if(job.current_positions > (config.assistantratio * count)) - if(count >= 5) // if theres more than 5 security on the station just let assistants join regardless, they should be able to handle the tide - return 1 - return 0 - return 1 - -/mob/new_player/proc/IsAdminJob(rank) - var/datum/job/job = SSjobs.GetJob(rank) - if(job.admin_only) - return 1 - else - return 0 - -/mob/new_player/proc/IsERTSpawnJob(rank) - var/datum/job/job = SSjobs.GetJob(rank) - if(job.spawn_ert) - return 1 - else - return 0 - -/mob/new_player/proc/IsSyndicateCommand(rank) - var/datum/job/job = SSjobs.GetJob(rank) - if(job.syndicate_command) - return 1 - else - return 0 - -/mob/new_player/proc/AttemptLateSpawn(rank,var/spawning_at) - if(src != usr) - return 0 - if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING) - to_chat(usr, "The round is either not ready, or has already finished...") - return 0 - if(!enter_allowed) - to_chat(usr, "There is an administrative lock on entering the game!") - return 0 - if(!IsJobAvailable(rank)) - to_chat(src, alert("[rank] is not available. Please try another.")) - return 0 - var/datum/job/thisjob = SSjobs.GetJob(rank) - if(thisjob.barred_by_disability(client)) - to_chat(src, alert("[rank] is not available due to your character's disability. Please try another.")) - return 0 - - SSjobs.AssignRole(src, rank, 1) - - var/mob/living/character = create_character() //creates the human and transfers vars and mind - character = SSjobs.AssignRank(character, rank, 1) //equips the human - - // AIs don't need a spawnpoint, they must spawn at an empty core - if(character.mind.assigned_role == "AI") - var/mob/living/silicon/ai/ai_character = character.AIize() // AIize the character, but don't move them yet - - // IsJobAvailable for AI checks that there is an empty core available in this list - ai_character.moveToEmptyCore() - AnnounceCyborg(ai_character, rank, "has been downloaded to the empty core in \the [get_area(ai_character)]") - - SSticker.mode.latespawn(ai_character) - qdel(src) - return - - //Find our spawning point. - var/join_message - var/datum/spawnpoint/S - - if(IsAdminJob(rank)) - if(IsERTSpawnJob(rank)) - character.loc = pick(ertdirector) - else if(IsSyndicateCommand(rank)) - character.loc = pick(syndicateofficer) - else - character.forceMove(pick(aroomwarp)) - join_message = "has arrived" - else - if(spawning_at) - S = spawntypes[spawning_at] - if(S && istype(S)) - if(S.check_job_spawning(rank)) - character.forceMove(pick(S.turfs)) - join_message = S.msg - else - to_chat(character, "Your chosen spawnpoint ([S.display_name]) is unavailable for your chosen job. Spawning you at the Arrivals shuttle instead.") - character.forceMove(pick(latejoin)) - join_message = "has arrived on the station" - else - character.forceMove(pick(latejoin)) - join_message = "has arrived on the station" - - character.lastarea = get_area(loc) - // Moving wheelchair if they have one - if(character.buckled && istype(character.buckled, /obj/structure/chair/wheelchair)) - character.buckled.forceMove(character.loc) - character.buckled.dir = character.dir - - character = SSjobs.EquipRank(character, rank, 1) //equips the human - EquipCustomItems(character) - - SSticker.mode.latespawn(character) - - if(character.mind.assigned_role == "Cyborg") - AnnounceCyborg(character, rank, join_message) - else - SSticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn - if(!IsAdminJob(rank)) - data_core.manifest_inject(character) - AnnounceArrival(character, rank, join_message) - AddEmploymentContract(character) - - if(GLOB.summon_guns_triggered) - give_guns(character) - if(GLOB.summon_magic_triggered) - give_magic(character) - - if(!thisjob.is_position_available() && thisjob in SSjobs.prioritized_jobs) - SSjobs.prioritized_jobs -= thisjob - qdel(src) - - -/mob/new_player/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank, var/join_message) - if(SSticker.current_state == GAME_STATE_PLAYING) - var/ailist[] = list() - for(var/mob/living/silicon/ai/A in GLOB.living_mob_list) - ailist += A - if(ailist.len) - var/mob/living/silicon/ai/announcer = pick(ailist) - if(character.mind) - if((character.mind.assigned_role != "Cyborg") && (character.mind.assigned_role != character.mind.special_role)) - if(character.mind.role_alt_title) - rank = character.mind.role_alt_title - var/arrivalmessage = announcer.arrivalmsg - arrivalmessage = replacetext(arrivalmessage,"$name",character.real_name) - arrivalmessage = replacetext(arrivalmessage,"$rank",rank ? "[rank]" : "visitor") - arrivalmessage = replacetext(arrivalmessage,"$species",character.dna.species.name) - arrivalmessage = replacetext(arrivalmessage,"$age",num2text(character.age)) - arrivalmessage = replacetext(arrivalmessage,"$gender",character.gender == FEMALE ? "Female" : "Male") - announcer.say(";[arrivalmessage]") - else - if(character.mind) - if((character.mind.assigned_role != "Cyborg") && (character.mind.assigned_role != character.mind.special_role)) - if(character.mind.role_alt_title) - rank = character.mind.role_alt_title - global_announcer.autosay("[character.real_name],[rank ? " [rank]," : " visitor," ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") - -/mob/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) - spawn(30) - for(var/C in employmentCabinets) - var/obj/structure/filingcabinet/employment/employmentCabinet = C - if(!employmentCabinet.virgin) - employmentCabinet.addFile(employee) - -/mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message) - if(SSticker.current_state == GAME_STATE_PLAYING) - var/ailist[] = list() - for(var/mob/living/silicon/ai/A in GLOB.living_mob_list) - ailist += A - if(ailist.len) - var/mob/living/silicon/ai/announcer = pick(ailist) - if(character.mind) - if(character.mind.assigned_role != character.mind.special_role) - var/arrivalmessage = "A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"]." - announcer.say(";[arrivalmessage]") - else - if(character.mind) - if(character.mind.assigned_role != character.mind.special_role) - // can't use their name here, since cyborg namepicking is done post-spawn, so we'll just say "A new Cyborg has arrived"/"A new Android has arrived"/etc. - global_announcer.autosay("A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") - -/mob/new_player/proc/LateChoices() - var/mills = ROUND_TIME // 1/10 of a second, not real milliseconds but whatever - //var/secs = ((mills % 36000) % 600) / 10 //Not really needed, but I'll leave it here for refrence.. or something - var/mins = (mills % 36000) / 600 - var/hours = mills / 36000 - - var/dat = "
        " - dat += "Round Duration: [round(hours)]h [round(mins)]m
        " - - if(SSshuttle.emergency.mode >= SHUTTLE_ESCAPE) - dat += "The station has been evacuated.
        " - else if(SSshuttle.emergency.mode >= SHUTTLE_CALL) - dat += "The station is currently undergoing evacuation procedures.
        " - - if(length(SSjobs.prioritized_jobs)) - dat += "The station has flagged these jobs as high priority: " - var/amt = length(SSjobs.prioritized_jobs) - var/amt_count - for(var/datum/job/a in SSjobs.prioritized_jobs) - amt_count++ - if(amt_count != amt) - dat += " [a.title], " - else - dat += " [a.title].
        " - - - var/num_jobs_available = 0 - var/list/activePlayers = list() - var/list/categorizedJobs = list( - "Command" = list(jobs = list(), titles = command_positions, color = "#aac1ee"), - "Engineering" = list(jobs = list(), titles = engineering_positions, color = "#ffd699"), - "Security" = list(jobs = list(), titles = security_positions, color = "#ff9999"), - "Miscellaneous" = list(jobs = list(), titles = list(), color = "#ffffff", colBreak = 1), - "Synthetic" = list(jobs = list(), titles = nonhuman_positions, color = "#ccffcc"), - "Support / Service" = list(jobs = list(), titles = service_positions, color = "#cccccc"), - "Medical" = list(jobs = list(), titles = medical_positions, color = "#99ffe6", colBreak = 1), - "Science" = list(jobs = list(), titles = science_positions, color = "#e6b3e6"), - "Supply" = list(jobs = list(), titles = supply_positions, color = "#ead4ae"), - ) - for(var/datum/job/job in SSjobs.occupations) - if(job && IsJobAvailable(job.title) && !job.barred_by_disability(client)) - num_jobs_available++ - activePlayers[job] = 0 - var/categorized = 0 - // Only players with the job assigned and AFK for less than 10 minutes count as active - for(var/mob/M in GLOB.player_list) if(M.mind && M.client && M.mind.assigned_role == job.title && M.client.inactivity <= 10 MINUTES) - activePlayers[job]++ - for(var/jobcat in categorizedJobs) - var/list/jobs = categorizedJobs[jobcat]["jobs"] - if(job.title in categorizedJobs[jobcat]["titles"]) - categorized = 1 - if(jobcat == "Command") // Put captain at top of command jobs - if(job.title == "Captain") - jobs.Insert(1, job) - else - jobs += job - else // Put heads at top of non-command jobs - if(job.title in command_positions) - jobs.Insert(1, job) - else - jobs += job - if(!categorized) - categorizedJobs["Miscellaneous"]["jobs"] += job - - if(num_jobs_available) - dat += "Choose from the following open positions:

        " - dat += "
        " - for(var/jobcat in categorizedJobs) - if(categorizedJobs[jobcat]["colBreak"]) - dat += "" - if(length(categorizedJobs[jobcat]["jobs"]) < 1) - continue - var/color = categorizedJobs[jobcat]["color"] - dat += "
        " - dat += "[jobcat]" - for(var/datum/job/job in categorizedJobs[jobcat]["jobs"]) - if(job in SSjobs.prioritized_jobs) - dat += "[job.title] ([job.current_positions]) (Active: [activePlayers[job]])
        " - else - dat += "[job.title] ([job.current_positions]) (Active: [activePlayers[job]])
        " - dat += "

        " - - dat += "
        " - else - dat += "

        Unfortunately, there are no job slots free currently.
        Wait a few minutes, then try again.
        Or, try observing the round.
        " - // Removing the old window method but leaving it here for reference -// src << browse(dat, "window=latechoices;size=300x640;can_close=1") - // Added the new browser window method - var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 900, 600) - popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') - popup.add_script("delay_interactivity", 'html/browser/delay_interactivity.js') - popup.set_content(dat) - popup.open(0) // 0 is passed to open so that it doesn't use the onclose() proc - -/mob/new_player/proc/create_character() - spawning = 1 - close_spawn_windows() - - check_prefs_are_sane() - var/mob/living/carbon/human/new_character = new(loc) - new_character.lastarea = get_area(loc) - - if(SSticker.random_players || appearance_isbanned(new_character)) - client.prefs.random_character() - client.prefs.real_name = random_name(client.prefs.gender) - client.prefs.copy_to(new_character) - - stop_sound_channel(CHANNEL_LOBBYMUSIC) - - - if(mind) - mind.active = 0 //we wish to transfer the key manually - if(mind.assigned_role == "Clown") //give them a clownname if they are a clown - new_character.real_name = pick(GLOB.clown_names) //I hate this being here of all places but unfortunately dna is based on real_name! - new_character.rename_self("clown") - else if(mind.assigned_role == "Mime") - new_character.real_name = pick(GLOB.mime_names) - new_character.rename_self("mime") - mind.original = new_character - mind.transfer_to(new_character) //won't transfer key since the mind is not active - - - new_character.key = key //Manually transfer the key to log them in - - return new_character - -// This is to check that the player only has preferences set that they're supposed to -/mob/new_player/proc/check_prefs_are_sane() - var/datum/species/chosen_species - if(client.prefs.species) - chosen_species = GLOB.all_species[client.prefs.species] - if(!(chosen_species && (is_species_whitelisted(chosen_species) || has_admin_rights()))) - // Have to recheck admin due to no usr at roundstart. Latejoins are fine though. - log_runtime(EXCEPTION("[src] had species [client.prefs.species], though they weren't supposed to. Setting to Human."), src) - client.prefs.species = "Human" - - var/datum/language/chosen_language - if(client.prefs.language) - chosen_language = GLOB.all_languages[client.prefs.language] - if((chosen_language == null && client.prefs.language != "None") || (chosen_language && chosen_language.flags & RESTRICTED)) - log_runtime(EXCEPTION("[src] had language [client.prefs.language], though they weren't supposed to. Setting to None."), src) - client.prefs.language = "None" - -/mob/new_player/proc/ViewManifest() - var/dat = "" - dat += "

        Crew Manifest

        " - dat += data_core.get_manifest(OOC = 1) - - src << browse(dat, "window=manifest;size=370x420;can_close=1") - -/mob/new_player/Move() - return 0 - - -/mob/new_player/proc/close_spawn_windows() - src << browse(null, "window=latechoices") //closes late choices window - src << browse(null, "window=playersetup") //closes the player setup window - src << browse(null, "window=preferences") //closes job selection - src << browse(null, "window=mob_occupation") - src << browse(null, "window=latechoices") //closes late job selection - - -/mob/new_player/proc/has_admin_rights() - return check_rights(R_ADMIN, 0, src) - -/mob/new_player/proc/is_species_whitelisted(datum/species/S) - if(!S) return 1 - return is_alien_whitelisted(src, S.name) || !config.usealienwhitelist || !(IS_WHITELISTED in S.species_traits) - -/mob/new_player/get_gender() - if(!client || !client.prefs) ..() - return client.prefs.gender - -/mob/new_player/is_ready() - return ready && ..() - -// No hearing announcements -/mob/new_player/can_hear() - return 0 +/mob/new_player + var/ready = 0 + var/spawning = 0 //Referenced when you want to delete the new_player later on in the code. + var/totalPlayers = 0 //Player counts for the Lobby tab + var/totalPlayersReady = 0 + var/tos_consent = FALSE + universal_speak = 1 + + invisibility = 101 + + density = 0 + stat = 2 + canmove = 0 + +/mob/new_player/New() + GLOB.mob_list += src + +/mob/new_player/verb/new_player_panel() + set src = usr + + if(handle_tos_consent()) + new_player_panel_proc() + +/mob/new_player/proc/handle_tos_consent() + if(!GLOB.join_tos) + tos_consent = TRUE + return TRUE + + establish_db_connection() + if(!dbcon.IsConnected()) + tos_consent = TRUE + return TRUE + + var/DBQuery/query = dbcon.NewQuery("SELECT * FROM [format_table_name("privacy")] WHERE ckey='[src.ckey]' AND consent=1") + query.Execute() + while(query.NextRow()) + tos_consent = TRUE + return TRUE + + privacy_consent() + return FALSE + +/mob/new_player/proc/privacy_consent() + src << browse(null, "window=playersetup") + var/output = GLOB.join_tos + output += "

        I consent" + output += "

        I DO NOT consent" + src << browse(output,"window=privacy_consent;size=500x300") + var/datum/browser/popup = new(src, "privacy_consent", "

        Privacy Consent
        ", 500, 400) + popup.set_window_options("can_close=0") + popup.set_content(output) + popup.open(0) + return + + +/mob/new_player/proc/new_player_panel_proc() + var/real_name = client.prefs.real_name + if(client.prefs.randomslot) + real_name = "Random Character Slot" + var/output = "

        Setup Character
        [real_name]

        " + + if(!SSticker || SSticker.current_state <= GAME_STATE_PREGAME) + if(!ready) output += "

        Declare Ready

        " + else output += "

        You are ready (Cancel)

        " + else + output += "

        View the Crew Manifest

        " + output += "

        Join Game!

        " + + var/list/antags = client.prefs.be_special + if(antags && antags.len) + if(!client.skip_antag) output += "

        Global Antag Candidacy" + else output += "

        Global Antag Candidacy" + output += "
        You are [client.skip_antag ? "ineligible" : "eligible"] for all antag roles.

        " + + output += "

        Observe

        " + + if(GLOB.join_tos) + output += "

        Terms of Service

        " + + if(!IsGuestKey(src.key)) + establish_db_connection() + + if(dbcon.IsConnected() && client.can_vote()) + var/isadmin = 0 + if(src.client && src.client.holder) + isadmin = 1 + var/DBQuery/query = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[ckey]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[ckey]\")") + query.Execute() + var/newpoll = 0 + while(query.NextRow()) + newpoll = 1 + break + + if(newpoll) + output += "

        Show Player Polls (NEW!)

        " + else + output += "

        Show Player Polls

        " + + output += "
        " + + var/datum/browser/popup = new(src, "playersetup", "
        New Player Options
        ", 220, 290) + popup.set_window_options("can_close=0") + popup.set_content(output) + popup.open(0) + return + +/mob/new_player/Stat() + statpanel("Lobby") + if(client.statpanel=="Lobby" && SSticker) + if(SSticker.hide_mode) + stat("Game Mode:", "Secret") + else + if(SSticker.hide_mode == 0) + stat("Game Mode:", "[master_mode]") // Old setting for showing the game mode + else + stat("Game Mode: ", "Secret") + + if((SSticker.current_state == GAME_STATE_PREGAME) && going) + stat("Time To Start:", round(SSticker.pregame_timeleft/10)) + if((SSticker.current_state == GAME_STATE_PREGAME) && !going) + stat("Time To Start:", "DELAYED") + + if(SSticker.current_state == GAME_STATE_PREGAME) + stat("Players:", "[totalPlayers]") + if(check_rights(R_ADMIN, 0, src)) + stat("Players Ready:", "[totalPlayersReady]") + totalPlayers = 0 + totalPlayersReady = 0 + for(var/mob/new_player/player in GLOB.player_list) + if(check_rights(R_ADMIN, 0, src)) + stat("[player.key]", (player.ready)?("(Playing)"):(null)) + totalPlayers++ + if(player.ready) + totalPlayersReady++ + + ..() + + statpanel("Status") + + +/mob/new_player/Topic(href, href_list[]) + if(!client) return 0 + + if(href_list["consent_signed"]) + var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") + var/DBQuery/query = dbcon.NewQuery("REPLACE INTO [format_table_name("privacy")] (ckey, datetime, consent) VALUES ('[ckey]', '[sqltime]', 1)") + query.Execute() + src << browse(null, "window=privacy_consent") + tos_consent = 1 + new_player_panel_proc() + if(href_list["consent_rejected"]) + tos_consent = 0 + to_chat(usr, "You must consent to the terms of service before you can join!") + var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") + var/DBQuery/query = dbcon.NewQuery("REPLACE INTO [format_table_name("privacy")] (ckey, datetime, consent) VALUES ('[ckey]', '[sqltime]', 0)") + query.Execute() + + if(href_list["show_preferences"]) + client.prefs.ShowChoices(src) + return 1 + + if(href_list["ready"]) + if(!tos_consent) + to_chat(usr, "You must consent to the terms of service before you can join!") + return 0 + ready = !ready + new_player_panel_proc() + + if(href_list["skip_antag"]) + client.skip_antag = !client.skip_antag + new_player_panel_proc() + + if(href_list["refresh"]) + src << browse(null, "window=playersetup") //closes the player setup window + new_player_panel_proc() + + if(href_list["observe"]) + if(!tos_consent) + to_chat(usr, "You must consent to the terms of service before you can join!") + return 0 + + if(alert(src,"Are you sure you wish to observe? You cannot normally join the round after doing this!","Player Setup","Yes","No") == "Yes") + if(!client) + return 1 + var/mob/dead/observer/observer = new() + src << browse(null, "window=playersetup") + spawning = 1 + stop_sound_channel(CHANNEL_LOBBYMUSIC) + + + observer.started_as_observer = 1 + close_spawn_windows() + var/obj/O = locate("landmark*Observer-Start") + to_chat(src, "Now teleporting.") + observer.forceMove(O.loc) + observer.timeofdeath = world.time // Set the time of death so that the respawn timer works correctly. + client.prefs.update_preview_icon(1) + observer.icon = client.prefs.preview_icon + observer.alpha = 127 + + if(client.prefs.be_random_name) + client.prefs.real_name = random_name(client.prefs.gender,client.prefs.species) + observer.real_name = client.prefs.real_name + observer.name = observer.real_name + if(!client.holder && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed. + observer.verbs -= /mob/dead/observer/verb/toggle_antagHUD // Poor guys, don't know what they are missing! + observer.key = key + GLOB.respawnable_list += observer + qdel(src) + return 1 + if(href_list["tos"]) + privacy_consent() + return 0 + + if(href_list["late_join"]) + if(!tos_consent) + to_chat(usr, "You must consent to the terms of service before you can join!") + return 0 + if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING) + to_chat(usr, "The round is either not ready, or has already finished...") + return + if(client.prefs.species in GLOB.whitelisted_species) + + if(!is_alien_whitelisted(src, client.prefs.species) && config.usealienwhitelist) + to_chat(src, alert("You are currently not whitelisted to play [client.prefs.species].")) + return 0 + + LateChoices() + + if(href_list["manifest"]) + ViewManifest() + + if(href_list["SelectedJob"]) + + if(!enter_allowed) + to_chat(usr, "There is an administrative lock on entering the game!") + return + + if(client.prefs.randomslot) + client.prefs.load_random_character_slot(client) + + if(client.prefs.species in GLOB.whitelisted_species) + if(!is_alien_whitelisted(src, client.prefs.species) && config.usealienwhitelist) + to_chat(src, alert("You are currently not whitelisted to play [client.prefs.species].")) + return 0 + + AttemptLateSpawn(href_list["SelectedJob"],client.prefs.spawnpoint) + return + + if(!ready && href_list["preference"]) + if(client) + client.prefs.process_link(src, href_list) + else if(!href_list["late_join"]) + new_player_panel() + +/mob/new_player/proc/IsJobAvailable(rank) + var/datum/job/job = SSjobs.GetJob(rank) + if(!job) return 0 + if(!job.is_position_available()) return 0 + if(jobban_isbanned(src,rank)) return 0 + if(!is_job_whitelisted(src, rank)) return 0 + if(!job.player_old_enough(client)) return 0 + if(job.admin_only && !(check_rights(R_EVENT, 0))) return 0 + if(job.available_in_playtime(client)) + return 0 + + if(config.assistantlimit) + if(job.title == "Civilian") + var/count = 0 + var/datum/job/officer = SSjobs.GetJob("Security Officer") + var/datum/job/warden = SSjobs.GetJob("Warden") + var/datum/job/hos = SSjobs.GetJob("Head of Security") + count += (officer.current_positions + warden.current_positions + hos.current_positions) + if(job.current_positions > (config.assistantratio * count)) + if(count >= 5) // if theres more than 5 security on the station just let assistants join regardless, they should be able to handle the tide + return 1 + return 0 + return 1 + +/mob/new_player/proc/IsAdminJob(rank) + var/datum/job/job = SSjobs.GetJob(rank) + if(job.admin_only) + return 1 + else + return 0 + +/mob/new_player/proc/IsERTSpawnJob(rank) + var/datum/job/job = SSjobs.GetJob(rank) + if(job.spawn_ert) + return 1 + else + return 0 + +/mob/new_player/proc/IsSyndicateCommand(rank) + var/datum/job/job = SSjobs.GetJob(rank) + if(job.syndicate_command) + return 1 + else + return 0 + +/mob/new_player/proc/AttemptLateSpawn(rank,var/spawning_at) + if(src != usr) + return 0 + if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING) + to_chat(usr, "The round is either not ready, or has already finished...") + return 0 + if(!enter_allowed) + to_chat(usr, "There is an administrative lock on entering the game!") + return 0 + if(!IsJobAvailable(rank)) + to_chat(src, alert("[rank] is not available. Please try another.")) + return 0 + var/datum/job/thisjob = SSjobs.GetJob(rank) + if(thisjob.barred_by_disability(client)) + to_chat(src, alert("[rank] is not available due to your character's disability. Please try another.")) + return 0 + + SSjobs.AssignRole(src, rank, 1) + + var/mob/living/character = create_character() //creates the human and transfers vars and mind + character = SSjobs.AssignRank(character, rank, 1) //equips the human + + // AIs don't need a spawnpoint, they must spawn at an empty core + if(character.mind.assigned_role == "AI") + var/mob/living/silicon/ai/ai_character = character.AIize() // AIize the character, but don't move them yet + + // IsJobAvailable for AI checks that there is an empty core available in this list + ai_character.moveToEmptyCore() + AnnounceCyborg(ai_character, rank, "has been downloaded to the empty core in \the [get_area(ai_character)]") + + SSticker.mode.latespawn(ai_character) + qdel(src) + return + + //Find our spawning point. + var/join_message + var/datum/spawnpoint/S + + if(IsAdminJob(rank)) + if(IsERTSpawnJob(rank)) + character.loc = pick(ertdirector) + else if(IsSyndicateCommand(rank)) + character.loc = pick(syndicateofficer) + else + character.forceMove(pick(aroomwarp)) + join_message = "has arrived" + else + if(spawning_at) + S = spawntypes[spawning_at] + if(S && istype(S)) + if(S.check_job_spawning(rank)) + character.forceMove(pick(S.turfs)) + join_message = S.msg + else + to_chat(character, "Your chosen spawnpoint ([S.display_name]) is unavailable for your chosen job. Spawning you at the Arrivals shuttle instead.") + character.forceMove(pick(latejoin)) + join_message = "has arrived on the station" + else + character.forceMove(pick(latejoin)) + join_message = "has arrived on the station" + + character.lastarea = get_area(loc) + // Moving wheelchair if they have one + if(character.buckled && istype(character.buckled, /obj/structure/chair/wheelchair)) + character.buckled.forceMove(character.loc) + character.buckled.dir = character.dir + + character = SSjobs.EquipRank(character, rank, 1) //equips the human + EquipCustomItems(character) + + SSticker.mode.latespawn(character) + + if(character.mind.assigned_role == "Cyborg") + AnnounceCyborg(character, rank, join_message) + else + SSticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn + if(!IsAdminJob(rank)) + data_core.manifest_inject(character) + AnnounceArrival(character, rank, join_message) + AddEmploymentContract(character) + + if(GLOB.summon_guns_triggered) + give_guns(character) + if(GLOB.summon_magic_triggered) + give_magic(character) + + if(!thisjob.is_position_available() && thisjob in SSjobs.prioritized_jobs) + SSjobs.prioritized_jobs -= thisjob + qdel(src) + + +/mob/new_player/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank, var/join_message) + if(SSticker.current_state == GAME_STATE_PLAYING) + var/ailist[] = list() + for(var/mob/living/silicon/ai/A in GLOB.living_mob_list) + ailist += A + if(ailist.len) + var/mob/living/silicon/ai/announcer = pick(ailist) + if(character.mind) + if((character.mind.assigned_role != "Cyborg") && (character.mind.assigned_role != character.mind.special_role)) + if(character.mind.role_alt_title) + rank = character.mind.role_alt_title + var/arrivalmessage = announcer.arrivalmsg + arrivalmessage = replacetext(arrivalmessage,"$name",character.real_name) + arrivalmessage = replacetext(arrivalmessage,"$rank",rank ? "[rank]" : "visitor") + arrivalmessage = replacetext(arrivalmessage,"$species",character.dna.species.name) + arrivalmessage = replacetext(arrivalmessage,"$age",num2text(character.age)) + arrivalmessage = replacetext(arrivalmessage,"$gender",character.gender == FEMALE ? "Female" : "Male") + announcer.say(";[arrivalmessage]") + else + if(character.mind) + if((character.mind.assigned_role != "Cyborg") && (character.mind.assigned_role != character.mind.special_role)) + if(character.mind.role_alt_title) + rank = character.mind.role_alt_title + global_announcer.autosay("[character.real_name],[rank ? " [rank]," : " visitor," ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") + +/mob/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee) + spawn(30) + for(var/C in employmentCabinets) + var/obj/structure/filingcabinet/employment/employmentCabinet = C + if(!employmentCabinet.virgin) + employmentCabinet.addFile(employee) + +/mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message) + if(SSticker.current_state == GAME_STATE_PLAYING) + var/ailist[] = list() + for(var/mob/living/silicon/ai/A in GLOB.living_mob_list) + ailist += A + if(ailist.len) + var/mob/living/silicon/ai/announcer = pick(ailist) + if(character.mind) + if(character.mind.assigned_role != character.mind.special_role) + var/arrivalmessage = "A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"]." + announcer.say(";[arrivalmessage]") + else + if(character.mind) + if(character.mind.assigned_role != character.mind.special_role) + // can't use their name here, since cyborg namepicking is done post-spawn, so we'll just say "A new Cyborg has arrived"/"A new Android has arrived"/etc. + global_announcer.autosay("A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") + +/mob/new_player/proc/LateChoices() + var/mills = ROUND_TIME // 1/10 of a second, not real milliseconds but whatever + //var/secs = ((mills % 36000) % 600) / 10 //Not really needed, but I'll leave it here for refrence.. or something + var/mins = (mills % 36000) / 600 + var/hours = mills / 36000 + + var/dat = "
        " + dat += "Round Duration: [round(hours)]h [round(mins)]m
        " + + if(SSshuttle.emergency.mode >= SHUTTLE_ESCAPE) + dat += "The station has been evacuated.
        " + else if(SSshuttle.emergency.mode >= SHUTTLE_CALL) + dat += "The station is currently undergoing evacuation procedures.
        " + + if(length(SSjobs.prioritized_jobs)) + dat += "The station has flagged these jobs as high priority: " + var/amt = length(SSjobs.prioritized_jobs) + var/amt_count + for(var/datum/job/a in SSjobs.prioritized_jobs) + amt_count++ + if(amt_count != amt) + dat += " [a.title], " + else + dat += " [a.title].
        " + + + var/num_jobs_available = 0 + var/list/activePlayers = list() + var/list/categorizedJobs = list( + "Command" = list(jobs = list(), titles = command_positions, color = "#aac1ee"), + "Engineering" = list(jobs = list(), titles = engineering_positions, color = "#ffd699"), + "Security" = list(jobs = list(), titles = security_positions, color = "#ff9999"), + "Miscellaneous" = list(jobs = list(), titles = list(), color = "#ffffff", colBreak = 1), + "Synthetic" = list(jobs = list(), titles = nonhuman_positions, color = "#ccffcc"), + "Support / Service" = list(jobs = list(), titles = service_positions, color = "#cccccc"), + "Medical" = list(jobs = list(), titles = medical_positions, color = "#99ffe6", colBreak = 1), + "Science" = list(jobs = list(), titles = science_positions, color = "#e6b3e6"), + "Supply" = list(jobs = list(), titles = supply_positions, color = "#ead4ae"), + ) + for(var/datum/job/job in SSjobs.occupations) + if(job && IsJobAvailable(job.title) && !job.barred_by_disability(client)) + num_jobs_available++ + activePlayers[job] = 0 + var/categorized = 0 + // Only players with the job assigned and AFK for less than 10 minutes count as active + for(var/mob/M in GLOB.player_list) if(M.mind && M.client && M.mind.assigned_role == job.title && M.client.inactivity <= 10 MINUTES) + activePlayers[job]++ + for(var/jobcat in categorizedJobs) + var/list/jobs = categorizedJobs[jobcat]["jobs"] + if(job.title in categorizedJobs[jobcat]["titles"]) + categorized = 1 + if(jobcat == "Command") // Put captain at top of command jobs + if(job.title == "Captain") + jobs.Insert(1, job) + else + jobs += job + else // Put heads at top of non-command jobs + if(job.title in command_positions) + jobs.Insert(1, job) + else + jobs += job + if(!categorized) + categorizedJobs["Miscellaneous"]["jobs"] += job + + if(num_jobs_available) + dat += "Choose from the following open positions:

        " + dat += "
        " + for(var/jobcat in categorizedJobs) + if(categorizedJobs[jobcat]["colBreak"]) + dat += "" + if(length(categorizedJobs[jobcat]["jobs"]) < 1) + continue + var/color = categorizedJobs[jobcat]["color"] + dat += "
        " + dat += "[jobcat]" + for(var/datum/job/job in categorizedJobs[jobcat]["jobs"]) + if(job in SSjobs.prioritized_jobs) + dat += "[job.title] ([job.current_positions]) (Active: [activePlayers[job]])
        " + else + dat += "[job.title] ([job.current_positions]) (Active: [activePlayers[job]])
        " + dat += "

        " + + dat += "
        " + else + dat += "

        Unfortunately, there are no job slots free currently.
        Wait a few minutes, then try again.
        Or, try observing the round.
        " + // Removing the old window method but leaving it here for reference +// src << browse(dat, "window=latechoices;size=300x640;can_close=1") + // Added the new browser window method + var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 900, 600) + popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') + popup.add_script("delay_interactivity", 'html/browser/delay_interactivity.js') + popup.set_content(dat) + popup.open(0) // 0 is passed to open so that it doesn't use the onclose() proc + +/mob/new_player/proc/create_character() + spawning = 1 + close_spawn_windows() + + check_prefs_are_sane() + var/mob/living/carbon/human/new_character = new(loc) + new_character.lastarea = get_area(loc) + + if(SSticker.random_players || appearance_isbanned(new_character)) + client.prefs.random_character() + client.prefs.real_name = random_name(client.prefs.gender) + client.prefs.copy_to(new_character) + + stop_sound_channel(CHANNEL_LOBBYMUSIC) + + + if(mind) + mind.active = 0 //we wish to transfer the key manually + if(mind.assigned_role == "Clown") //give them a clownname if they are a clown + new_character.real_name = pick(GLOB.clown_names) //I hate this being here of all places but unfortunately dna is based on real_name! + new_character.rename_self("clown") + else if(mind.assigned_role == "Mime") + new_character.real_name = pick(GLOB.mime_names) + new_character.rename_self("mime") + mind.original = new_character + mind.transfer_to(new_character) //won't transfer key since the mind is not active + + + new_character.key = key //Manually transfer the key to log them in + + return new_character + +// This is to check that the player only has preferences set that they're supposed to +/mob/new_player/proc/check_prefs_are_sane() + var/datum/species/chosen_species + if(client.prefs.species) + chosen_species = GLOB.all_species[client.prefs.species] + if(!(chosen_species && (is_species_whitelisted(chosen_species) || has_admin_rights()))) + // Have to recheck admin due to no usr at roundstart. Latejoins are fine though. + log_runtime(EXCEPTION("[src] had species [client.prefs.species], though they weren't supposed to. Setting to Human."), src) + client.prefs.species = "Human" + + var/datum/language/chosen_language + if(client.prefs.language) + chosen_language = GLOB.all_languages[client.prefs.language] + if((chosen_language == null && client.prefs.language != "None") || (chosen_language && chosen_language.flags & RESTRICTED)) + log_runtime(EXCEPTION("[src] had language [client.prefs.language], though they weren't supposed to. Setting to None."), src) + client.prefs.language = "None" + +/mob/new_player/proc/ViewManifest() + var/dat = "" + dat += "

        Crew Manifest

        " + dat += data_core.get_manifest(OOC = 1) + + src << browse(dat, "window=manifest;size=370x420;can_close=1") + +/mob/new_player/Move() + return 0 + + +/mob/new_player/proc/close_spawn_windows() + src << browse(null, "window=latechoices") //closes late choices window + src << browse(null, "window=playersetup") //closes the player setup window + src << browse(null, "window=preferences") //closes job selection + src << browse(null, "window=mob_occupation") + src << browse(null, "window=latechoices") //closes late job selection + + +/mob/new_player/proc/has_admin_rights() + return check_rights(R_ADMIN, 0, src) + +/mob/new_player/proc/is_species_whitelisted(datum/species/S) + if(!S) return 1 + return is_alien_whitelisted(src, S.name) || !config.usealienwhitelist || !(IS_WHITELISTED in S.species_traits) + +/mob/new_player/get_gender() + if(!client || !client.prefs) ..() + return client.prefs.gender + +/mob/new_player/is_ready() + return ready && ..() + +// No hearing announcements +/mob/new_player/can_hear() + return 0 diff --git a/code/modules/mob/new_player/poll.dm b/code/modules/mob/new_player/poll.dm index ecd690c871d..ad73451b645 100644 --- a/code/modules/mob/new_player/poll.dm +++ b/code/modules/mob/new_player/poll.dm @@ -1,661 +1,661 @@ -/datum/polloption - var/optionid - var/optiontext - -/client/verb/polls_verb() - set name = "Show Player Polls" - set category = "OOC" - handle_player_polling() - -/client/proc/can_vote() - return istext(player_age) || player_age >= 30 - -/client/proc/handle_player_polling() - establish_db_connection() - if(dbcon.IsConnected()) - var/isadmin = 0 - if(holder) - isadmin = 1 - - var/DBQuery/select_query = dbcon.NewQuery("SELECT id, question, (id IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = '[ckey]') OR id IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = '[ckey]')) AS voted FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime") - select_query.Execute() - - var/output = "
        Player polls" - if(check_rights(R_SERVER)) - output += "(Create new poll)" - output +="
        " - - output += "" - var/color1 = "#ececec" - var/color2 = "#e2e2e2" - var/i = 0 - - output += "" - while(select_query.NextRow()) - var/pollid = select_query.item[1] - var/pollquestion = select_query.item[2] - var/voted = text2num(select_query.item[3]) - output += "" - if(can_vote() && !voted) - output += "" - i++ - - // Show expired polls. Non admins can view admin polls at this stage - // (just like tgstation's web interface so don't complain) - // (Also why was there no ingame interface tg not having an ingame - // interface is retarded because it cucks downstreams) - select_query = dbcon.NewQuery("SELECT id, question FROM [format_table_name("poll_question")] WHERE Now() > endtime ORDER BY id DESC") - select_query.Execute() - - output += "" - while(select_query.NextRow()) - var/pollid = select_query.item[1] - var/pollquestion = select_query.item[2] - output += "" - - output += "
        Active Polls
        [pollquestion]
        [poll_player(pollid, 1)]
        Expired Polls
        [pollquestion]
        " - - src << browse(output,"window=playerpolllist;size=500x300") - -/client/proc/poll_results(var/pollid = -1) - if(pollid == -1) - return - establish_db_connection() - if(!dbcon.IsConnected()) - return - var/DBQuery/select_query = dbcon.NewQuery("SELECT polltype, question, adminonly, multiplechoiceoptions, starttime, endtime FROM [format_table_name("poll_question")] WHERE id = [pollid] AND endtime < Now()") - select_query.Execute() - var/question = "" - var/polltype = "" - var/adminonly = 0 - var/multiplechoiceoptions = 0 - var/starttime = "" - var/endtime = "" - var/found = 0 - while(select_query.NextRow()) - polltype = select_query.item[1] - question = select_query.item[2] - adminonly = text2num(select_query.item[3]) - multiplechoiceoptions = text2num(select_query.item[4]) - starttime = select_query.item[5] - endtime = select_query.item[6] - found = 1 - break - if(!found) - to_chat(src, "Poll question details not found. (Maybe the poll isn't expired yet?)") - return - - if(polltype == POLLTYPE_MULTI) - question += " (Choose up to [multiplechoiceoptions] options)" - if(adminonly) - question = "(Admin only poll) " + question - - var output = "" - if(polltype == POLLTYPE_MULTI || polltype == POLLTYPE_OPTION) - select_query = dbcon.NewQuery("SELECT text, percentagecalc, (SELECT COUNT(optionid) FROM [format_table_name("poll_vote")] WHERE optionid = poll_option.id GROUP BY optionid) AS votecount FROM [format_table_name("poll_option")] WHERE pollid = [pollid]"); - select_query.Execute() - var/list/options = list() - var/total_votes = 1 - var/total_percent_votes = 1 - var/max_votes = 1 - while(select_query.NextRow()) - var/text = select_query.item[1] - var/percentagecalc = select_query.item[2] - var/votecount = text2num(select_query.item[3]) - if(percentagecalc) - total_percent_votes += votecount - total_votes += votecount - if(votecount > max_votes) - max_votes = votecount - options[++options.len] = list(text, percentagecalc, votecount) - // fuck ie. - output += {" - - - - - - " - for(var/list/option in options) - var/bar_width = option[3] * 390 / max_votes - var/percentage = option[2] ? "[round(option[3] * 100 / total_percent_votes)]%" : "N/A" - output += "" - output += "" - output += "" - output += "" - output += "
        [question]
        [starttime] - [endtime]
        "} - var/list/colors = list("#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494", "#b3b3b3") - var/color_index = 0 - for(var/list/option in options) - var/bar_width = option[3] * 700 / total_votes - var/percentage = option[2] ? "[round(option[3] * 100 / total_percent_votes)]%" : "N/A" - color_index++ - if(color_index > colors.len) - color_index = 1 - output += "
        " - output += "

        (Hover over the colored bar to read description)
        [option[1]][option[3]][percentage]
        " - if(polltype == POLLTYPE_RATING) - output += {" - - - - "} - select_query = dbcon.NewQuery("SELECT id, text, (SELECT AVG(rating) FROM [format_table_name("poll_vote")] WHERE optionid = poll_option.id AND rating != 'abstain') AS avgrating, (SELECT COUNT(rating) FROM [format_table_name("poll_vote")] WHERE optionid = poll_option.id AND rating != 'abstain') AS countvotes, minval, maxval FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - select_query.Execute() - while(select_query.NextRow()) - output += {" - - " - output += "
        [question]
        [starttime] - [endtime]
        [select_query.item[2]] - N = [select_query.item[4]] - AVG = [select_query.item[3]] - "} - var/optionid = select_query.item[1] - var/totalvotes = text2num(select_query.item[4]) - var/minval = text2num(select_query.item[5]) - var/maxval = text2num(select_query.item[6]) - var/maxvote = 1 - var/list/votecounts = list() - for(var/I in minval to maxval) - var/DBQuery/rating_query = dbcon.NewQuery("SELECT COUNT(rating) AS countrating FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND rating = [I] GROUP BY rating") - rating_query.Execute() - var/votecount = 0 - while(rating_query.NextRow()) - votecount = text2num(rating_query.item[1]) - votecounts["[I]"] = votecount - if(votecount > maxvote) - maxvote = votecount - for(var/I in minval to maxval) - var/votecount = votecounts["[I]"] - var/bar_width = votecount * 200 / maxvote - output += {" - - - - - - "} - output += "
        [I][votecount]([votecount / totalvotes]%)
        " - if(polltype == POLLTYPE_TEXT) - select_query = dbcon.NewQuery("SELECT replytext, COUNT(replytext) AS countresponse, GROUP_CONCAT(DISTINCT ckey SEPARATOR ', ') as ckeys FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] GROUP BY replytext ORDER BY countresponse DESC"); - select_query.Execute() - output += {" - - - - "} - while(select_query.NextRow()) - var/replytext = select_query.item[1] - var/countresponse = select_query.item[2] - var/ckeys = select_query.item[3] - output += {" - - - - "} - output += "
        [question]
        [starttime] - [endtime]
        [ckeys] ([countresponse] player\s) responded with:[replytext]
        " - output += "" - - src << browse(output,"window=pollresults;size=950x500") - -/client/proc/poll_player(var/pollid = -1, var/inline = 0) - if(pollid == -1) return - establish_db_connection() - if(dbcon.IsConnected()) - - var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime [holder ? "AND adminonly = 0" : ""]") - select_query.Execute() - - var/pollstarttime = "" - var/pollendtime = "" - var/pollquestion = "" - var/polltype = "" - var/found = 0 - var/multiplechoiceoptions = 0 - var/canvote = can_vote() - - while(select_query.NextRow()) - pollstarttime = select_query.item[1] - pollendtime = select_query.item[2] - pollquestion = select_query.item[3] - polltype = select_query.item[4] - found = 1 - break - - if(!found) - to_chat(usr, "Poll question details not found. (Maybe you do not have access?)") - return - - switch(polltype) - //Polls that have enumerated options - if(POLLTYPE_OPTION) - var/DBQuery/voted_query = dbcon.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - voted_query.Execute() - - var/voted = 0 // If the can't vote then consider them voted - var/votedoptionid = 0 - while(voted_query.NextRow()) - votedoptionid = text2num(voted_query.item[1]) - voted = 1 - break - - var/list/datum/polloption/options = list() - - var/DBQuery/options_query = dbcon.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - options_query.Execute() - while(options_query.NextRow()) - var/datum/polloption/PO = new() - PO.optionid = text2num(options_query.item[1]) - PO.optiontext = options_query.item[2] - options += PO - - var/output - if(!inline) - output += "
        Player poll" - output +="
        " - output += "Question: [pollquestion]
        " - output += "Poll runs from [pollstarttime] until [pollendtime]

        " - - if(canvote && !voted) //Only make this a form if we have not voted yet - output += "

        " - output += "" - output += "" - - output += "
        " - for(var/datum/polloption/O in options) - if(O.optionid && O.optiontext) - if(voted || !canvote) - if(votedoptionid == O.optionid) - output += "[O.optiontext]
        " - else - output += "[O.optiontext]
        " - else - output += " [O.optiontext]
        " - output += "
        " - - if(canvote && !voted) //Only make this a form if we have not voted yet - output += "

        " - output += "

        " - - output += "
        " - - if(inline) - return output - else - src << browse(output,"window=playerpoll;size=500x250") - - //Polls with a text input - if(POLLTYPE_TEXT) - var/DBQuery/voted_query = dbcon.NewQuery("SELECT replytext FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - voted_query.Execute() - - var/voted = 0 - var/vote_text = "" - while(voted_query.NextRow()) - vote_text = voted_query.item[1] - voted = 1 - break - - - var/output - if(!inline) - output += "
        Player poll" - output +="
        " - output += "Question: [pollquestion]
        " - output += "Feedback gathering runs from [pollstarttime] until [pollendtime]

        " - - if(canvote && !voted) //Only make this a form if we have not voted yet - output += "

        " - output += "" - output += "" - - output += "Please provide feedback below. You can use any letters of the English alphabet, numbers and the symbols: . , ! ? : ; -
        " - output += "" - - output += "

        " - output += "

        " - - output += "
        " - output += "" - output += "" - output += "" - output += "" - output += "
        " - else - output += "[vote_text]" - - if(inline) - return output - else - src << browse(output,"window=playerpoll;size=500x500") - - //Polls with a text input - if(POLLTYPE_RATING) - var/DBQuery/voted_query = dbcon.NewQuery("SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, erro_poll_vote v WHERE o.pollid = [pollid] AND v.ckey = '[ckey]' AND o.id = v.optionid") - voted_query.Execute() - - var/output - if(!inline) - output += "
        Player poll" - output +="
        " - output += "Question: [pollquestion]
        " - output += "Poll runs from [pollstarttime] until [pollendtime]

        " - - var/voted = 0 - while(voted_query.NextRow()) - voted = 1 - - var/optiontext = voted_query.item[1] - var/rating = voted_query.item[2] - - output += "
        [optiontext] - [rating]" - - if(canvote && !voted) //Only make this a form if we have not voted yet - output += "

        " - output += "" - output += "" - - var/minid = 999999 - var/maxid = 0 - - var/DBQuery/option_query = dbcon.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - option_query.Execute() - while(option_query.NextRow()) - var/optionid = text2num(option_query.item[1]) - var/optiontext = option_query.item[2] - var/minvalue = text2num(option_query.item[3]) - var/maxvalue = text2num(option_query.item[4]) - var/descmin = option_query.item[5] - var/descmid = option_query.item[6] - var/descmax = option_query.item[7] - - if(optionid < minid) - minid = optionid - if(optionid > maxid) - maxid = optionid - - var/midvalue = round( (maxvalue + minvalue) / 2) - - if(isnull(minvalue) || isnull(maxvalue) || (minvalue == maxvalue)) - continue - - output += "
        [optiontext]: " - - output += "" - output += "" - - output += "

        " - output += "

        " - - if(inline) - return output - else - src << browse(output,"window=playerpoll;size=500x500") - if(POLLTYPE_MULTI) - var/DBQuery/voted_query = dbcon.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - voted_query.Execute() - - var/list/votedfor = list() - var/voted = 0 - while(voted_query.NextRow()) - votedfor.Add(text2num(voted_query.item[1])) - voted = 1 - - var/list/datum/polloption/options = list() - var/maxoptionid = 0 - var/minoptionid = 0 - - var/DBQuery/options_query = dbcon.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - options_query.Execute() - while(options_query.NextRow()) - var/datum/polloption/PO = new() - PO.optionid = text2num(options_query.item[1]) - PO.optiontext = options_query.item[2] - if(PO.optionid > maxoptionid) - maxoptionid = PO.optionid - if(PO.optionid < minoptionid || !minoptionid) - minoptionid = PO.optionid - options += PO - - - if(select_query.item[5]) - multiplechoiceoptions = text2num(select_query.item[5]) - - var/output - if(!inline) - output += "
        Player poll" - output +="
        " - output += "Question: [pollquestion]
        You can select up to [multiplechoiceoptions] options. If you select more, the first [multiplechoiceoptions] will be saved.
        " - output += "Poll runs from [pollstarttime] until [pollendtime]

        " - - if(canvote && !voted) //Only make this a form if we have not voted yet - output += "

        " - output += "" - output += "" - output += "" - output += "" - - output += "
        " - for(var/datum/polloption/O in options) - if(O.optionid && O.optiontext) - if(canvote && voted) - if(O.optionid in votedfor) - output += "[O.optiontext]
        " - else - output += "[O.optiontext]
        " - else - output += " [O.optiontext]
        " - output += "
        " - - if(canvote && !voted) //Only make this a form if we have not voted yet - output += "

        " - output += "

        " - - output += "
        " - - if(inline) - return output - else - src << browse(output,"window=playerpoll;size=600x250") - return - -/client/proc/vote_on_poll(var/pollid = -1, var/optionid = -1, var/multichoice = 0) - if(pollid == -1 || optionid == -1) - return - - if(!isnum(pollid) || !isnum(optionid)) - return - establish_db_connection() - if(dbcon.IsConnected()) - - var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime [holder ? "AND adminonly = 0" : ""]") - select_query.Execute() - - var/validpoll = 0 - var/multiplechoiceoptions = 0 - - while(select_query.NextRow()) - if(select_query.item[4] != POLLTYPE_OPTION && select_query.item[4] != POLLTYPE_MULTI) - return - validpoll = 1 - if(select_query.item[5]) - multiplechoiceoptions = text2num(select_query.item[5]) - break - - if(!validpoll) - to_chat(usr, "Poll is not valid.") - return - - var/DBQuery/select_query2 = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE id = [optionid] AND pollid = [pollid]") - select_query2.Execute() - - var/validoption = 0 - - while(select_query2.NextRow()) - validoption = 1 - break - - if(!validoption) - to_chat(usr, "Poll option is not valid.") - return - - var/alreadyvoted = 0 - - var/DBQuery/voted_query = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - voted_query.Execute() - - while(voted_query.NextRow()) - alreadyvoted += 1 - if(!multichoice) - break - - if(!multichoice && alreadyvoted) - to_chat(usr, "You already voted in this poll.") - return - - if(multichoice && (alreadyvoted >= multiplechoiceoptions)) - to_chat(usr, "You already have more than [multiplechoiceoptions] logged votes on this poll. Enough is enough. Contact the database admin if this is an error.") - return - - var/adminrank = "Player" - if(usr && usr.client && usr.client.holder) - adminrank = usr.client.holder.rank - - - var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("poll_vote")] (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank) VALUES (null, Now(), [pollid], [optionid], '[ckey]', '[usr.client.address]', '[adminrank]')") - insert_query.Execute() - - to_chat(usr, "Vote successful.") - usr << browse(null,"window=playerpoll") - - -/client/proc/log_text_poll_reply(var/pollid = -1, var/replytext = "") - if(pollid == -1 || replytext == "") - return - - if(!isnum(pollid) || !istext(replytext)) - return - establish_db_connection() - if(dbcon.IsConnected()) - - var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime [holder ? "AND adminonly = 0" : ""]") - select_query.Execute() - - var/validpoll = 0 - - while(select_query.NextRow()) - if(select_query.item[4] != POLLTYPE_TEXT) - return - validpoll = 1 - break - - if(!validpoll) - to_chat(usr, "Poll is not valid.") - return - - var/alreadyvoted = 0 - - var/DBQuery/voted_query = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - voted_query.Execute() - - while(voted_query.NextRow()) - alreadyvoted = 1 - break - - if(alreadyvoted) - to_chat(usr, "You already sent your feedback for this poll.") - return - - var/adminrank = "Player" - if(usr && usr.client && usr.client.holder) - adminrank = usr.client.holder.rank - - - replytext = replacetext(replytext, "%BR%", "") - replytext = replacetext(replytext, "\n", "%BR%") - var/text_pass = reject_bad_text(replytext,8000) - replytext = replacetext(replytext, "%BR%", "
        ") - - if(!text_pass) - to_chat(usr, "The text you entered was blank, contained illegal characters or was too long. Please correct the text and submit again.") - return - - var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("poll_textreply")] (id ,datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (null, Now(), [pollid], '[ckey]', '[usr.client.address]', '[replytext]', '[adminrank]')") - insert_query.Execute() - - to_chat(usr, "Feedback logging successful.") - usr << browse(null,"window=playerpoll") - - -/client/proc/vote_on_numval_poll(var/pollid = -1, var/optionid = -1, var/rating = null) - if(pollid == -1 || optionid == -1) - return - - if(!isnum(pollid) || !isnum(optionid)) - return - establish_db_connection() - if(dbcon.IsConnected()) - - var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime [holder ? "AND adminonly = 0" : ""]") - select_query.Execute() - - var/validpoll = 0 - - while(select_query.NextRow()) - if(select_query.item[4] != POLLTYPE_RATING) - return - validpoll = 1 - break - - if(!validpoll) - to_chat(usr, "Poll is not valid.") - return - - var/DBQuery/select_query2 = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE id = [optionid] AND pollid = [pollid]") - select_query2.Execute() - - var/validoption = 0 - - while(select_query2.NextRow()) - validoption = 1 - break - - if(!validoption) - to_chat(usr, "Poll option is not valid.") - return - - var/alreadyvoted = 0 - - var/DBQuery/voted_query = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND ckey = '[ckey]'") - voted_query.Execute() - - while(voted_query.NextRow()) - alreadyvoted = 1 - break - - if(alreadyvoted) - to_chat(usr, "You already voted in this poll.") - return - - var/adminrank = "Player" - if(usr && usr.client && usr.client.holder) - adminrank = usr.client.holder.rank - - - var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("poll_vote")] (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (null, Now(), [pollid], [optionid], '[ckey]', '[usr.client.address]', '[adminrank]', [(isnull(rating)) ? "null" : rating])") - insert_query.Execute() - - to_chat(usr, "Vote successful.") - usr << browse(null,"window=playerpoll") \ No newline at end of file +/datum/polloption + var/optionid + var/optiontext + +/client/verb/polls_verb() + set name = "Show Player Polls" + set category = "OOC" + handle_player_polling() + +/client/proc/can_vote() + return istext(player_age) || player_age >= 30 + +/client/proc/handle_player_polling() + establish_db_connection() + if(dbcon.IsConnected()) + var/isadmin = 0 + if(holder) + isadmin = 1 + + var/DBQuery/select_query = dbcon.NewQuery("SELECT id, question, (id IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = '[ckey]') OR id IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = '[ckey]')) AS voted FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime") + select_query.Execute() + + var/output = "
        Player polls" + if(check_rights(R_SERVER)) + output += "(Create new poll)" + output +="
        " + + output += "" + var/color1 = "#ececec" + var/color2 = "#e2e2e2" + var/i = 0 + + output += "" + while(select_query.NextRow()) + var/pollid = select_query.item[1] + var/pollquestion = select_query.item[2] + var/voted = text2num(select_query.item[3]) + output += "" + if(can_vote() && !voted) + output += "" + i++ + + // Show expired polls. Non admins can view admin polls at this stage + // (just like tgstation's web interface so don't complain) + // (Also why was there no ingame interface tg not having an ingame + // interface is retarded because it cucks downstreams) + select_query = dbcon.NewQuery("SELECT id, question FROM [format_table_name("poll_question")] WHERE Now() > endtime ORDER BY id DESC") + select_query.Execute() + + output += "" + while(select_query.NextRow()) + var/pollid = select_query.item[1] + var/pollquestion = select_query.item[2] + output += "" + + output += "
        Active Polls
        [pollquestion]
        [poll_player(pollid, 1)]
        Expired Polls
        [pollquestion]
        " + + src << browse(output,"window=playerpolllist;size=500x300") + +/client/proc/poll_results(var/pollid = -1) + if(pollid == -1) + return + establish_db_connection() + if(!dbcon.IsConnected()) + return + var/DBQuery/select_query = dbcon.NewQuery("SELECT polltype, question, adminonly, multiplechoiceoptions, starttime, endtime FROM [format_table_name("poll_question")] WHERE id = [pollid] AND endtime < Now()") + select_query.Execute() + var/question = "" + var/polltype = "" + var/adminonly = 0 + var/multiplechoiceoptions = 0 + var/starttime = "" + var/endtime = "" + var/found = 0 + while(select_query.NextRow()) + polltype = select_query.item[1] + question = select_query.item[2] + adminonly = text2num(select_query.item[3]) + multiplechoiceoptions = text2num(select_query.item[4]) + starttime = select_query.item[5] + endtime = select_query.item[6] + found = 1 + break + if(!found) + to_chat(src, "Poll question details not found. (Maybe the poll isn't expired yet?)") + return + + if(polltype == POLLTYPE_MULTI) + question += " (Choose up to [multiplechoiceoptions] options)" + if(adminonly) + question = "(Admin only poll) " + question + + var output = "" + if(polltype == POLLTYPE_MULTI || polltype == POLLTYPE_OPTION) + select_query = dbcon.NewQuery("SELECT text, percentagecalc, (SELECT COUNT(optionid) FROM [format_table_name("poll_vote")] WHERE optionid = poll_option.id GROUP BY optionid) AS votecount FROM [format_table_name("poll_option")] WHERE pollid = [pollid]"); + select_query.Execute() + var/list/options = list() + var/total_votes = 1 + var/total_percent_votes = 1 + var/max_votes = 1 + while(select_query.NextRow()) + var/text = select_query.item[1] + var/percentagecalc = select_query.item[2] + var/votecount = text2num(select_query.item[3]) + if(percentagecalc) + total_percent_votes += votecount + total_votes += votecount + if(votecount > max_votes) + max_votes = votecount + options[++options.len] = list(text, percentagecalc, votecount) + // fuck ie. + output += {" + + + + + + " + for(var/list/option in options) + var/bar_width = option[3] * 390 / max_votes + var/percentage = option[2] ? "[round(option[3] * 100 / total_percent_votes)]%" : "N/A" + output += "" + output += "" + output += "" + output += "" + output += "
        [question]
        [starttime] - [endtime]
        "} + var/list/colors = list("#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494", "#b3b3b3") + var/color_index = 0 + for(var/list/option in options) + var/bar_width = option[3] * 700 / total_votes + var/percentage = option[2] ? "[round(option[3] * 100 / total_percent_votes)]%" : "N/A" + color_index++ + if(color_index > colors.len) + color_index = 1 + output += "
        " + output += "

        (Hover over the colored bar to read description)
        [option[1]][option[3]][percentage]
        " + if(polltype == POLLTYPE_RATING) + output += {" + + + + "} + select_query = dbcon.NewQuery("SELECT id, text, (SELECT AVG(rating) FROM [format_table_name("poll_vote")] WHERE optionid = poll_option.id AND rating != 'abstain') AS avgrating, (SELECT COUNT(rating) FROM [format_table_name("poll_vote")] WHERE optionid = poll_option.id AND rating != 'abstain') AS countvotes, minval, maxval FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + select_query.Execute() + while(select_query.NextRow()) + output += {" + + " + output += "
        [question]
        [starttime] - [endtime]
        [select_query.item[2]] + N = [select_query.item[4]] + AVG = [select_query.item[3]] + "} + var/optionid = select_query.item[1] + var/totalvotes = text2num(select_query.item[4]) + var/minval = text2num(select_query.item[5]) + var/maxval = text2num(select_query.item[6]) + var/maxvote = 1 + var/list/votecounts = list() + for(var/I in minval to maxval) + var/DBQuery/rating_query = dbcon.NewQuery("SELECT COUNT(rating) AS countrating FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND rating = [I] GROUP BY rating") + rating_query.Execute() + var/votecount = 0 + while(rating_query.NextRow()) + votecount = text2num(rating_query.item[1]) + votecounts["[I]"] = votecount + if(votecount > maxvote) + maxvote = votecount + for(var/I in minval to maxval) + var/votecount = votecounts["[I]"] + var/bar_width = votecount * 200 / maxvote + output += {" + + + + + + "} + output += "
        [I][votecount]([votecount / totalvotes]%)
        " + if(polltype == POLLTYPE_TEXT) + select_query = dbcon.NewQuery("SELECT replytext, COUNT(replytext) AS countresponse, GROUP_CONCAT(DISTINCT ckey SEPARATOR ', ') as ckeys FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] GROUP BY replytext ORDER BY countresponse DESC"); + select_query.Execute() + output += {" + + + + "} + while(select_query.NextRow()) + var/replytext = select_query.item[1] + var/countresponse = select_query.item[2] + var/ckeys = select_query.item[3] + output += {" + + + + "} + output += "
        [question]
        [starttime] - [endtime]
        [ckeys] ([countresponse] player\s) responded with:[replytext]
        " + output += "" + + src << browse(output,"window=pollresults;size=950x500") + +/client/proc/poll_player(var/pollid = -1, var/inline = 0) + if(pollid == -1) return + establish_db_connection() + if(dbcon.IsConnected()) + + var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime [holder ? "AND adminonly = 0" : ""]") + select_query.Execute() + + var/pollstarttime = "" + var/pollendtime = "" + var/pollquestion = "" + var/polltype = "" + var/found = 0 + var/multiplechoiceoptions = 0 + var/canvote = can_vote() + + while(select_query.NextRow()) + pollstarttime = select_query.item[1] + pollendtime = select_query.item[2] + pollquestion = select_query.item[3] + polltype = select_query.item[4] + found = 1 + break + + if(!found) + to_chat(usr, "Poll question details not found. (Maybe you do not have access?)") + return + + switch(polltype) + //Polls that have enumerated options + if(POLLTYPE_OPTION) + var/DBQuery/voted_query = dbcon.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + voted_query.Execute() + + var/voted = 0 // If the can't vote then consider them voted + var/votedoptionid = 0 + while(voted_query.NextRow()) + votedoptionid = text2num(voted_query.item[1]) + voted = 1 + break + + var/list/datum/polloption/options = list() + + var/DBQuery/options_query = dbcon.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + options_query.Execute() + while(options_query.NextRow()) + var/datum/polloption/PO = new() + PO.optionid = text2num(options_query.item[1]) + PO.optiontext = options_query.item[2] + options += PO + + var/output + if(!inline) + output += "
        Player poll" + output +="
        " + output += "Question: [pollquestion]
        " + output += "Poll runs from [pollstarttime] until [pollendtime]

        " + + if(canvote && !voted) //Only make this a form if we have not voted yet + output += "

        " + output += "" + output += "" + + output += "
        " + for(var/datum/polloption/O in options) + if(O.optionid && O.optiontext) + if(voted || !canvote) + if(votedoptionid == O.optionid) + output += "[O.optiontext]
        " + else + output += "[O.optiontext]
        " + else + output += " [O.optiontext]
        " + output += "
        " + + if(canvote && !voted) //Only make this a form if we have not voted yet + output += "

        " + output += "

        " + + output += "
        " + + if(inline) + return output + else + src << browse(output,"window=playerpoll;size=500x250") + + //Polls with a text input + if(POLLTYPE_TEXT) + var/DBQuery/voted_query = dbcon.NewQuery("SELECT replytext FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + voted_query.Execute() + + var/voted = 0 + var/vote_text = "" + while(voted_query.NextRow()) + vote_text = voted_query.item[1] + voted = 1 + break + + + var/output + if(!inline) + output += "
        Player poll" + output +="
        " + output += "Question: [pollquestion]
        " + output += "Feedback gathering runs from [pollstarttime] until [pollendtime]

        " + + if(canvote && !voted) //Only make this a form if we have not voted yet + output += "

        " + output += "" + output += "" + + output += "Please provide feedback below. You can use any letters of the English alphabet, numbers and the symbols: . , ! ? : ; -
        " + output += "" + + output += "

        " + output += "

        " + + output += "
        " + output += "" + output += "" + output += "" + output += "" + output += "
        " + else + output += "[vote_text]" + + if(inline) + return output + else + src << browse(output,"window=playerpoll;size=500x500") + + //Polls with a text input + if(POLLTYPE_RATING) + var/DBQuery/voted_query = dbcon.NewQuery("SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, erro_poll_vote v WHERE o.pollid = [pollid] AND v.ckey = '[ckey]' AND o.id = v.optionid") + voted_query.Execute() + + var/output + if(!inline) + output += "
        Player poll" + output +="
        " + output += "Question: [pollquestion]
        " + output += "Poll runs from [pollstarttime] until [pollendtime]

        " + + var/voted = 0 + while(voted_query.NextRow()) + voted = 1 + + var/optiontext = voted_query.item[1] + var/rating = voted_query.item[2] + + output += "
        [optiontext] - [rating]" + + if(canvote && !voted) //Only make this a form if we have not voted yet + output += "

        " + output += "" + output += "" + + var/minid = 999999 + var/maxid = 0 + + var/DBQuery/option_query = dbcon.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + option_query.Execute() + while(option_query.NextRow()) + var/optionid = text2num(option_query.item[1]) + var/optiontext = option_query.item[2] + var/minvalue = text2num(option_query.item[3]) + var/maxvalue = text2num(option_query.item[4]) + var/descmin = option_query.item[5] + var/descmid = option_query.item[6] + var/descmax = option_query.item[7] + + if(optionid < minid) + minid = optionid + if(optionid > maxid) + maxid = optionid + + var/midvalue = round( (maxvalue + minvalue) / 2) + + if(isnull(minvalue) || isnull(maxvalue) || (minvalue == maxvalue)) + continue + + output += "
        [optiontext]: " + + output += "" + output += "" + + output += "

        " + output += "

        " + + if(inline) + return output + else + src << browse(output,"window=playerpoll;size=500x500") + if(POLLTYPE_MULTI) + var/DBQuery/voted_query = dbcon.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + voted_query.Execute() + + var/list/votedfor = list() + var/voted = 0 + while(voted_query.NextRow()) + votedfor.Add(text2num(voted_query.item[1])) + voted = 1 + + var/list/datum/polloption/options = list() + var/maxoptionid = 0 + var/minoptionid = 0 + + var/DBQuery/options_query = dbcon.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + options_query.Execute() + while(options_query.NextRow()) + var/datum/polloption/PO = new() + PO.optionid = text2num(options_query.item[1]) + PO.optiontext = options_query.item[2] + if(PO.optionid > maxoptionid) + maxoptionid = PO.optionid + if(PO.optionid < minoptionid || !minoptionid) + minoptionid = PO.optionid + options += PO + + + if(select_query.item[5]) + multiplechoiceoptions = text2num(select_query.item[5]) + + var/output + if(!inline) + output += "
        Player poll" + output +="
        " + output += "Question: [pollquestion]
        You can select up to [multiplechoiceoptions] options. If you select more, the first [multiplechoiceoptions] will be saved.
        " + output += "Poll runs from [pollstarttime] until [pollendtime]

        " + + if(canvote && !voted) //Only make this a form if we have not voted yet + output += "

        " + output += "" + output += "" + output += "" + output += "" + + output += "
        " + for(var/datum/polloption/O in options) + if(O.optionid && O.optiontext) + if(canvote && voted) + if(O.optionid in votedfor) + output += "[O.optiontext]
        " + else + output += "[O.optiontext]
        " + else + output += " [O.optiontext]
        " + output += "
        " + + if(canvote && !voted) //Only make this a form if we have not voted yet + output += "

        " + output += "

        " + + output += "
        " + + if(inline) + return output + else + src << browse(output,"window=playerpoll;size=600x250") + return + +/client/proc/vote_on_poll(var/pollid = -1, var/optionid = -1, var/multichoice = 0) + if(pollid == -1 || optionid == -1) + return + + if(!isnum(pollid) || !isnum(optionid)) + return + establish_db_connection() + if(dbcon.IsConnected()) + + var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime [holder ? "AND adminonly = 0" : ""]") + select_query.Execute() + + var/validpoll = 0 + var/multiplechoiceoptions = 0 + + while(select_query.NextRow()) + if(select_query.item[4] != POLLTYPE_OPTION && select_query.item[4] != POLLTYPE_MULTI) + return + validpoll = 1 + if(select_query.item[5]) + multiplechoiceoptions = text2num(select_query.item[5]) + break + + if(!validpoll) + to_chat(usr, "Poll is not valid.") + return + + var/DBQuery/select_query2 = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE id = [optionid] AND pollid = [pollid]") + select_query2.Execute() + + var/validoption = 0 + + while(select_query2.NextRow()) + validoption = 1 + break + + if(!validoption) + to_chat(usr, "Poll option is not valid.") + return + + var/alreadyvoted = 0 + + var/DBQuery/voted_query = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + voted_query.Execute() + + while(voted_query.NextRow()) + alreadyvoted += 1 + if(!multichoice) + break + + if(!multichoice && alreadyvoted) + to_chat(usr, "You already voted in this poll.") + return + + if(multichoice && (alreadyvoted >= multiplechoiceoptions)) + to_chat(usr, "You already have more than [multiplechoiceoptions] logged votes on this poll. Enough is enough. Contact the database admin if this is an error.") + return + + var/adminrank = "Player" + if(usr && usr.client && usr.client.holder) + adminrank = usr.client.holder.rank + + + var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("poll_vote")] (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank) VALUES (null, Now(), [pollid], [optionid], '[ckey]', '[usr.client.address]', '[adminrank]')") + insert_query.Execute() + + to_chat(usr, "Vote successful.") + usr << browse(null,"window=playerpoll") + + +/client/proc/log_text_poll_reply(var/pollid = -1, var/replytext = "") + if(pollid == -1 || replytext == "") + return + + if(!isnum(pollid) || !istext(replytext)) + return + establish_db_connection() + if(dbcon.IsConnected()) + + var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime [holder ? "AND adminonly = 0" : ""]") + select_query.Execute() + + var/validpoll = 0 + + while(select_query.NextRow()) + if(select_query.item[4] != POLLTYPE_TEXT) + return + validpoll = 1 + break + + if(!validpoll) + to_chat(usr, "Poll is not valid.") + return + + var/alreadyvoted = 0 + + var/DBQuery/voted_query = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + voted_query.Execute() + + while(voted_query.NextRow()) + alreadyvoted = 1 + break + + if(alreadyvoted) + to_chat(usr, "You already sent your feedback for this poll.") + return + + var/adminrank = "Player" + if(usr && usr.client && usr.client.holder) + adminrank = usr.client.holder.rank + + + replytext = replacetext(replytext, "%BR%", "") + replytext = replacetext(replytext, "\n", "%BR%") + var/text_pass = reject_bad_text(replytext,8000) + replytext = replacetext(replytext, "%BR%", "
        ") + + if(!text_pass) + to_chat(usr, "The text you entered was blank, contained illegal characters or was too long. Please correct the text and submit again.") + return + + var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("poll_textreply")] (id ,datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (null, Now(), [pollid], '[ckey]', '[usr.client.address]', '[replytext]', '[adminrank]')") + insert_query.Execute() + + to_chat(usr, "Feedback logging successful.") + usr << browse(null,"window=playerpoll") + + +/client/proc/vote_on_numval_poll(var/pollid = -1, var/optionid = -1, var/rating = null) + if(pollid == -1 || optionid == -1) + return + + if(!isnum(pollid) || !isnum(optionid)) + return + establish_db_connection() + if(dbcon.IsConnected()) + + var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime [holder ? "AND adminonly = 0" : ""]") + select_query.Execute() + + var/validpoll = 0 + + while(select_query.NextRow()) + if(select_query.item[4] != POLLTYPE_RATING) + return + validpoll = 1 + break + + if(!validpoll) + to_chat(usr, "Poll is not valid.") + return + + var/DBQuery/select_query2 = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE id = [optionid] AND pollid = [pollid]") + select_query2.Execute() + + var/validoption = 0 + + while(select_query2.NextRow()) + validoption = 1 + break + + if(!validoption) + to_chat(usr, "Poll option is not valid.") + return + + var/alreadyvoted = 0 + + var/DBQuery/voted_query = dbcon.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND ckey = '[ckey]'") + voted_query.Execute() + + while(voted_query.NextRow()) + alreadyvoted = 1 + break + + if(alreadyvoted) + to_chat(usr, "You already voted in this poll.") + return + + var/adminrank = "Player" + if(usr && usr.client && usr.client.holder) + adminrank = usr.client.holder.rank + + + var/DBQuery/insert_query = dbcon.NewQuery("INSERT INTO [format_table_name("poll_vote")] (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (null, Now(), [pollid], [optionid], '[ckey]', '[usr.client.address]', '[adminrank]', [(isnull(rating)) ? "null" : rating])") + insert_query.Execute() + + to_chat(usr, "Vote successful.") + usr << browse(null,"window=playerpoll") diff --git a/code/modules/mob/new_player/preferences_setup.dm b/code/modules/mob/new_player/preferences_setup.dm index 4382e209a82..3489e56be54 100644 --- a/code/modules/mob/new_player/preferences_setup.dm +++ b/code/modules/mob/new_player/preferences_setup.dm @@ -1,957 +1,957 @@ -/datum/preferences - //The mob should have a gender you want before running this proc. Will run fine without H -/datum/preferences/proc/random_character(gender_override) - var/datum/species/S = GLOB.all_species[species] - if(!istype(S)) //The species was invalid. Set the species to the default, fetch the datum for that species and generate a random character. - species = initial(species) - S = GLOB.all_species[species] - var/datum/robolimb/robohead - - if(S.bodyflags & ALL_RPARTS) - var/head_model = "[!rlimb_data["head"] ? "Morpheus Cyberkinetics" : rlimb_data["head"]]" - robohead = all_robolimbs[head_model] - if(gender_override) - gender = gender_override - else - gender = pick(MALE, FEMALE) - underwear = random_underwear(gender, species) - undershirt = random_undershirt(gender, species) - socks = random_socks(gender, species) - if(body_accessory_by_species[species]) - body_accessory = random_body_accessory(species) - if(body_accessory == "None") //Required to prevent a bug where the information/icons in the character preferences screen wouldn't update despite the data being changed. - body_accessory = null - if(S.bodyflags & (HAS_SKIN_TONE|HAS_ICON_SKIN_TONE)) - s_tone = random_skin_tone(species) - h_style = random_hair_style(gender, species, robohead) - f_style = random_facial_hair_style(gender, species, robohead) - if(species in list("Human", "Unathi", "Tajaran", "Skrell", "Machine", "Wryn", "Vulpkanin", "Vox")) - randomize_hair_color("hair") - randomize_hair_color("facial") - if(S.bodyflags & HAS_HEAD_ACCESSORY) - ha_style = random_head_accessory(species) - hacc_colour = randomize_skin_color(1) - if(S.bodyflags & HAS_HEAD_MARKINGS) - m_styles["head"] = random_marking_style("head", species, robohead, null, alt_head) - m_colours["head"] = randomize_skin_color(1) - if(S.bodyflags & HAS_BODY_MARKINGS) - m_styles["body"] = random_marking_style("body", species) - m_colours["body"] = randomize_skin_color(1) - if(S.bodyflags & HAS_TAIL_MARKINGS) //Species with tail markings. - m_styles["tail"] = random_marking_style("tail", species, null, body_accessory) - m_colours["tail"] = randomize_skin_color(1) - if(!(S.bodyflags & ALL_RPARTS)) - randomize_eyes_color() - if(S.bodyflags & HAS_SKIN_COLOR) - randomize_skin_color() - backbag = 2 - age = rand(AGE_MIN, AGE_MAX) - - -/datum/preferences/proc/randomize_hair_color(var/target = "hair") - if(prob (75) && target == "facial") // Chance to inherit hair color - f_colour = h_colour - return - - var/red - var/green - var/blue - - var/col = pick ("blonde", "black", "chestnut", "copper", "brown", "wheat", "old", "punk") - switch(col) - if("blonde") - red = 255 - green = 255 - blue = 0 - if("black") - red = 0 - green = 0 - blue = 0 - if("chestnut") - red = 153 - green = 102 - blue = 51 - if("copper") - red = 255 - green = 153 - blue = 0 - if("brown") - red = 102 - green = 51 - blue = 0 - if("wheat") - red = 255 - green = 255 - blue = 153 - if("old") - red = rand (100, 255) - green = red - blue = red - if("punk") - red = rand (0, 255) - green = rand (0, 255) - blue = rand (0, 255) - - red = max(min(red + rand (-25, 25), 255), 0) - green = max(min(green + rand (-25, 25), 255), 0) - blue = max(min(blue + rand (-25, 25), 255), 0) - - switch(target) - if("hair") - h_colour = rgb(red, green, blue) - if("facial") - f_colour = rgb(red, green, blue) - -/datum/preferences/proc/randomize_eyes_color() - var/red - var/green - var/blue - - var/col = pick ("black", "grey", "brown", "chestnut", "blue", "lightblue", "green", "albino") - switch(col) - if("black") - red = 0 - green = 0 - blue = 0 - if("grey") - red = rand (100, 200) - green = red - blue = red - if("brown") - red = 102 - green = 51 - blue = 0 - if("chestnut") - red = 153 - green = 102 - blue = 0 - if("blue") - red = 51 - green = 102 - blue = 204 - if("lightblue") - red = 102 - green = 204 - blue = 255 - if("green") - red = 0 - green = 102 - blue = 0 - if("albino") - red = rand (200, 255) - green = rand (0, 150) - blue = rand (0, 150) - - red = max(min(red + rand (-25, 25), 255), 0) - green = max(min(green + rand (-25, 25), 255), 0) - blue = max(min(blue + rand (-25, 25), 255), 0) - - e_colour = rgb(red, green, blue) - -/datum/preferences/proc/randomize_skin_color(var/pass_on) - var/red - var/green - var/blue - - var/col = pick ("black", "grey", "brown", "chestnut", "blue", "lightblue", "green", "albino") - switch(col) - if("black") - red = 0 - green = 0 - blue = 0 - if("grey") - red = rand (100, 200) - green = red - blue = red - if("brown") - red = 102 - green = 51 - blue = 0 - if("chestnut") - red = 153 - green = 102 - blue = 0 - if("blue") - red = 51 - green = 102 - blue = 204 - if("lightblue") - red = 102 - green = 204 - blue = 255 - if("green") - red = 0 - green = 102 - blue = 0 - if("albino") - red = rand (200, 255) - green = rand (0, 150) - blue = rand (0, 150) - - red = max(min(red + rand (-25, 25), 255), 0) - green = max(min(green + rand (-25, 25), 255), 0) - blue = max(min(blue + rand (-25, 25), 255), 0) - - if(pass_on) - return rgb(red, green, blue) - else - s_colour = rgb(red, green, blue) - -/datum/preferences/proc/blend_backpack(var/icon/clothes_s,var/backbag,var/satchel,var/backpack="backpack") - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', backpack), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', satchel), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - return clothes_s - -/datum/preferences/proc/update_preview_icon(var/for_observer=0) //seriously. This is horrendous. - qdel(preview_icon_front) - qdel(preview_icon_side) - qdel(preview_icon) - - var/g = "m" - if(gender == FEMALE) g = "f" - - var/icon/icobase - var/datum/species/current_species = GLOB.all_species[species] - - //Icon-based species colour. - var/coloured_tail - if(current_species) - if(current_species.bodyflags & HAS_ICON_SKIN_TONE) //Handling species-specific icon-based skin tones by flagged race. - var/mob/living/carbon/human/H = new - H.dna.species = current_species - H.s_tone = s_tone - H.dna.species.updatespeciescolor(H, 0) //The mob's species wasn't set, so it's almost certainly different than the character's species at the moment. Thus, we need to be owner-insensitive. - var/obj/item/organ/external/chest/C = H.get_organ("chest") - icobase = C.icobase ? C.icobase : C.dna.species.icobase - if(H.dna.species.bodyflags & HAS_TAIL) - coloured_tail = H.tail ? H.tail : H.dna.species.tail - - qdel(H) - else - icobase = current_species.icobase - else - icobase = 'icons/mob/human_races/r_human.dmi' - - var/fat="" - if(disabilities & DISABILITY_FLAG_FAT && (CAN_BE_FAT in current_species.species_traits)) - fat="_fat" - preview_icon = new /icon(icobase, "torso_[g][fat]") - preview_icon.Blend(new /icon(icobase, "groin_[g]"), ICON_OVERLAY) - var/head = "head" - if(alt_head && current_species.bodyflags & HAS_ALT_HEADS) - var/datum/sprite_accessory/alt_heads/H = GLOB.alt_heads_list[alt_head] - if(H.icon_state) - head = H.icon_state - preview_icon.Blend(new /icon(icobase, "[head]_[g]"), ICON_OVERLAY) - - for(var/name in list("chest", "groin", "head", "r_arm", "r_hand", "r_leg", "r_foot", "l_leg", "l_foot", "l_arm", "l_hand")) - if(organ_data[name] == "amputated") continue - if(organ_data[name] == "cyborg") - var/datum/robolimb/R - if(rlimb_data[name]) R = all_robolimbs[rlimb_data[name]] - if(!R) R = basic_robolimb - if(name == "chest") - name = "torso" - preview_icon.Blend(icon(R.icon, "[name]"), ICON_OVERLAY) // This doesn't check gendered_icon. Not an issue while only limbs can be robotic. - continue - preview_icon.Blend(new /icon(icobase, "[name]"), ICON_OVERLAY) - - // Skin color - if(current_species && (current_species.bodyflags & HAS_SKIN_COLOR)) - preview_icon.Blend(s_colour, ICON_ADD) - - // Skin tone - if(current_species && (current_species.bodyflags & HAS_SKIN_TONE)) - if(s_tone >= 0) - preview_icon.Blend(rgb(s_tone, s_tone, s_tone), ICON_ADD) - else - preview_icon.Blend(rgb(-s_tone, -s_tone, -s_tone), ICON_SUBTRACT) - - //Tail - if(current_species && (current_species.bodyflags & HAS_TAIL)) - var/tail_icon - var/tail_icon_state - var/tail_shift_x - var/tail_shift_y - var/blend_mode = ICON_ADD - - if(body_accessory) - var/datum/body_accessory/accessory = body_accessory_by_name[body_accessory] - tail_icon = accessory.icon - tail_icon_state = accessory.icon_state - if(accessory.blend_mode) - blend_mode = accessory.blend_mode - if(accessory.pixel_x_offset) - tail_shift_x = accessory.pixel_x_offset - if(accessory.pixel_y_offset) - tail_shift_y = accessory.pixel_y_offset - else - tail_icon = "icons/effects/species.dmi" - if(coloured_tail) - tail_icon_state = "[coloured_tail]_s" - else - tail_icon_state = "[current_species.tail]_s" - - var/icon/temp = new/icon("icon" = tail_icon, "icon_state" = tail_icon_state) - if(tail_shift_x) - temp.Shift(EAST, tail_shift_x) - if(tail_shift_y) - temp.Shift(NORTH, tail_shift_y) - - if(current_species && (current_species.bodyflags & HAS_SKIN_COLOR)) - temp.Blend(s_colour, blend_mode) - - if(current_species && (current_species.bodyflags & HAS_TAIL_MARKINGS)) - var/tail_marking = m_styles["tail"] - var/datum/sprite_accessory/tail_marking_style = GLOB.marking_styles_list[tail_marking] - if(tail_marking_style && tail_marking_style.species_allowed) - var/icon/t_marking_s = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]_s") - t_marking_s.Blend(m_colours["tail"], ICON_ADD) - temp.Blend(t_marking_s, ICON_OVERLAY) - - preview_icon.Blend(temp, ICON_OVERLAY) - - //Markings - if(current_species && ((current_species.bodyflags & HAS_HEAD_MARKINGS) || (current_species.bodyflags & HAS_BODY_MARKINGS))) - if(current_species.bodyflags & HAS_BODY_MARKINGS) //Body markings. - var/body_marking = m_styles["body"] - var/datum/sprite_accessory/body_marking_style = GLOB.marking_styles_list[body_marking] - if(body_marking_style && body_marking_style.species_allowed) - var/icon/b_marking_s = new/icon("icon" = body_marking_style.icon, "icon_state" = "[body_marking_style.icon_state]_s") - b_marking_s.Blend(m_colours["body"], ICON_ADD) - preview_icon.Blend(b_marking_s, ICON_OVERLAY) - if(current_species.bodyflags & HAS_HEAD_MARKINGS) //Head markings. - var/head_marking = m_styles["head"] - var/datum/sprite_accessory/head_marking_style = GLOB.marking_styles_list[head_marking] - if(head_marking_style && head_marking_style.species_allowed) - var/icon/h_marking_s = new/icon("icon" = head_marking_style.icon, "icon_state" = "[head_marking_style.icon_state]_s") - h_marking_s.Blend(m_colours["head"], ICON_ADD) - preview_icon.Blend(h_marking_s, ICON_OVERLAY) - - - var/icon/face_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "bald_s") - if(!(current_species.bodyflags & NO_EYES)) - var/icon/eyes_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = current_species ? current_species.eyes : "eyes_s") - eyes_s.Blend(e_colour, ICON_ADD) - face_s.Blend(eyes_s, ICON_OVERLAY) - - - var/datum/sprite_accessory/hair_style = GLOB.hair_styles_full_list[h_style] - if(hair_style) - var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s") - if(current_species.name == "Slime People") // whee I am part of the problem - hair_s.Blend("[s_colour]A0", ICON_ADD) - else if(hair_style.do_colouration) - hair_s.Blend(h_colour, ICON_ADD) - - if(hair_style.secondary_theme) - var/icon/hair_secondary_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_[hair_style.secondary_theme]_s") - if(!hair_style.no_sec_colour && hair_style.do_colouration ) - hair_secondary_s.Blend(h_sec_colour, ICON_ADD) - hair_s.Blend(hair_secondary_s, ICON_OVERLAY) - - face_s.Blend(hair_s, ICON_OVERLAY) - - //Head Accessory - if(current_species && (current_species.bodyflags & HAS_HEAD_ACCESSORY)) - var/datum/sprite_accessory/head_accessory_style = GLOB.head_accessory_styles_list[ha_style] - if(head_accessory_style && head_accessory_style.species_allowed) - var/icon/head_accessory_s = new/icon("icon" = head_accessory_style.icon, "icon_state" = "[head_accessory_style.icon_state]_s") - head_accessory_s.Blend(hacc_colour, ICON_ADD) - face_s.Blend(head_accessory_s, ICON_OVERLAY) - - var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[f_style] - if(facial_hair_style && facial_hair_style.species_allowed) - var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s") - if(current_species.name == "Slime People") // whee I am part of the problem - facial_s.Blend("[s_colour]A0", ICON_ADD) - else if(facial_hair_style.do_colouration) - facial_s.Blend(f_colour, ICON_ADD) - - if(facial_hair_style.secondary_theme) - var/icon/facial_secondary_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_[facial_hair_style.secondary_theme]_s") - if(!facial_hair_style.no_sec_colour && facial_hair_style.do_colouration) - facial_secondary_s.Blend(f_sec_colour, ICON_ADD) - facial_s.Blend(facial_secondary_s, ICON_OVERLAY) - - face_s.Blend(facial_s, ICON_OVERLAY) - - var/icon/underwear_s = null - if(underwear && (current_species.clothing_flags & HAS_UNDERWEAR)) - var/datum/sprite_accessory/underwear/U = GLOB.underwear_list[underwear] - if(U) - var/u_icon = U.sprite_sheets && (current_species.name in U.sprite_sheets) ? U.sprite_sheets[current_species.name] : U.icon //Species-fit the undergarment. - underwear_s = new/icon(u_icon, "uw_[U.icon_state]_s", ICON_OVERLAY) - - var/icon/undershirt_s = null - if(undershirt && (current_species.clothing_flags & HAS_UNDERSHIRT)) - var/datum/sprite_accessory/undershirt/U2 = GLOB.undershirt_list[undershirt] - if(U2) - var/u2_icon = U2.sprite_sheets && (current_species.name in U2.sprite_sheets) ? U2.sprite_sheets[current_species.name] : U2.icon - undershirt_s = new/icon(u2_icon, "us_[U2.icon_state]_s", ICON_OVERLAY) - - var/icon/socks_s = null - if(socks && (current_species.clothing_flags & HAS_SOCKS)) - var/datum/sprite_accessory/socks/U3 = GLOB.socks_list[socks] - if(U3) - var/u3_icon = U3.sprite_sheets && (current_species.name in U3.sprite_sheets) ? U3.sprite_sheets[current_species.name] : U3.icon - socks_s = new/icon(u3_icon, "sk_[U3.icon_state]_s", ICON_OVERLAY) - - var/icon/clothes_s = null - var/uniform_dmi='icons/mob/uniform.dmi' - if(disabilities&DISABILITY_FLAG_FAT) - uniform_dmi='icons/mob/uniform_fat.dmi' - if(job_support_low & JOB_CIVILIAN)//This gives the preview icon clothes depending on which job(if any) is set to 'high' - clothes_s = new /icon(uniform_dmi, "grey_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if(backbag == 2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - else if(backbag == 3 || backbag == 4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - - else if(job_support_high)//I hate how this looks, but there's no reason to go through this switch if it's empty - switch(job_support_high) - if(JOB_HOP) - clothes_s = new /icon(uniform_dmi, "hop_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "ianshirt"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_BARTENDER) - clothes_s = new /icon(uniform_dmi, "ba_suit_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "tophat"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_BOTANIST) - clothes_s = new /icon(uniform_dmi, "hydroponics_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "ggloves"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "apron"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "nymph"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-hyd"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CHEF) - clothes_s = new /icon(uniform_dmi, "chef_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "chef"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "apronchef"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_JANITOR) - clothes_s = new /icon(uniform_dmi, "janitor_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "bio_janitor"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_LIBRARIAN) - clothes_s = new /icon(uniform_dmi, "red_suit_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "hairflower"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_QUARTERMASTER) - clothes_s = new /icon(uniform_dmi, "qm_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "poncho"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CARGOTECH) - clothes_s = new /icon(uniform_dmi, "cargotech_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "flat_cap"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_MINER) - clothes_s = new /icon(uniform_dmi, "miner_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "bearpelt"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-eng"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_LAWYER) - clothes_s = new /icon(uniform_dmi, "internalaffairs_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/inhands/items_righthand.dmi', "briefcase"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "suitjacket_blue"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CHAPLAIN) - clothes_s = new /icon(uniform_dmi, "chapblack_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "imperium_monk"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CLOWN) - clothes_s = new /icon(uniform_dmi, "clown_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "clown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/mask.dmi', "clown"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "clownpack"), ICON_OVERLAY) - if(JOB_MIME) - clothes_s = new /icon(uniform_dmi, "mime_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "lgloves"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/mask.dmi', "mime"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "beret"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "suspenders"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - - else if(job_medsci_high) - switch(job_medsci_high) - if(JOB_RD) - clothes_s = new /icon(uniform_dmi, "director_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "petehat"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-tox"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_SCIENTIST) - clothes_s = new /icon(uniform_dmi, "toxinswhite_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_tox_open"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "metroid"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-tox"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CHEMIST) - clothes_s = new /icon(uniform_dmi, "chemistrywhite_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labgreen"), ICON_OVERLAY) - else - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_chem_open"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-chem"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CMO) - clothes_s = new /icon(uniform_dmi, "cmo_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "bio_cmo"), ICON_OVERLAY) - else - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_cmo_open"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_DOCTOR) - clothes_s = new /icon(uniform_dmi, "medical_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "surgeon"), ICON_OVERLAY) - else - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CORONER) - clothes_s = new /icon(uniform_dmi, "medical_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "mortician"), ICON_OVERLAY) - else - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_GENETICIST) - clothes_s = new /icon(uniform_dmi, "geneticswhite_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "monkeysuit"), ICON_OVERLAY) - else - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_gen_open"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-gen"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_VIROLOGIST) - clothes_s = new /icon(uniform_dmi, "virologywhite_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/mask.dmi', "sterile"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_vir_open"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "plaguedoctor"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-vir"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_PSYCHIATRIST) - clothes_s = new /icon(uniform_dmi, "psych_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_PARAMEDIC) - clothes_s = new /icon(uniform_dmi, "paramedic_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/mask.dmi', "cigoff"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "bluesoft"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) - if(JOB_ROBOTICIST) - clothes_s = new /icon(uniform_dmi, "robotics_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/inhands/items_righthand.dmi', "toolbox_blue"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - - else if(job_engsec_high) - switch(job_engsec_high) - if(JOB_CAPTAIN) - clothes_s = new /icon(uniform_dmi, "captain_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "centcomcaptain"), ICON_OVERLAY) - else - clothes_s.Blend(new /icon('icons/mob/head.dmi', "captain"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-cap"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_HOS) - clothes_s = new /icon(uniform_dmi, "hosred_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "beret_hos"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-sec"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_WARDEN) - clothes_s = new /icon('icons/mob/uniform.dmi', "warden_s") - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "slippers_worn"), ICON_OVERLAY) - else - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-sec"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_DETECTIVE) - clothes_s = new /icon(uniform_dmi, "detective_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/mask.dmi', "cigaron"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "detective"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "detective"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_OFFICER) - clothes_s = new /icon(uniform_dmi, "secred_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "beret_officer"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-sec"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CHIEF) - clothes_s = new /icon(uniform_dmi, "chief_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "hardhat0_white"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/inhands/items_righthand.dmi', "blueprints"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "engiepack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-eng"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_ENGINEER) - clothes_s = new /icon(uniform_dmi, "engine_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "hardhat0_yellow"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "hazard"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "engiepack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-eng"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_ATMOSTECH) - clothes_s = new /icon(uniform_dmi, "atmos_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) - if(prob(1)) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "firesuit"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - - if(JOB_AI)//Gives AI and borgs assistant-wear, so they can still customize their character - clothes_s = new /icon(uniform_dmi, "grey_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "straight_jacket"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "cardborg_h"), ICON_OVERLAY) - if(backbag == 2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - else if(backbag == 3 || backbag == 4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_CYBORG) - clothes_s = new /icon(uniform_dmi, "grey_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "cardborg"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "cardborg_h"), ICON_OVERLAY) - if(backbag == 2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - else if(backbag == 3 || backbag == 4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - else if(job_karma_high) - switch(job_karma_high) - if(JOB_MECHANIC) - clothes_s = new /icon(uniform_dmi, "mechanic_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "hardhat0_yellow"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "engiepack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-eng"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_PILOT) - clothes_s = new /icon(uniform_dmi, "secred_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "bomber"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-sec"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_BRIGDOC) - clothes_s = new /icon(uniform_dmi, "medical_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "fr_jacket_open"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_NANO) - clothes_s = new /icon(uniform_dmi, "officer_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_BLUESHIELD) - clothes_s = new /icon(uniform_dmi, "officer_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/hands.dmi', "swat_gl"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "blueshield"), ICON_OVERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - if(JOB_JUDGE) - clothes_s = new /icon(uniform_dmi, "really_black_suit_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/head.dmi', "mercy_hood"), ICON_UNDERLAY) - clothes_s.Blend(new /icon('icons/mob/suit.dmi', "judge"), ICON_UNDERLAY) - switch(backbag) - if(2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - if(3) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) - if(4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - - if(disabilities & NEARSIGHTED) - preview_icon.Blend(new /icon('icons/mob/eyes.dmi', "glasses"), ICON_OVERLAY) - - // Observers get tourist outfit. - if(for_observer) - clothes_s = new /icon(uniform_dmi, "tourist_s") - clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) - if(backbag == 2) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) - else if(backbag == 3 || backbag == 4) - clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) - - if(underwear_s) - preview_icon.Blend(underwear_s, ICON_OVERLAY) - if(undershirt_s) - preview_icon.Blend(undershirt_s, ICON_OVERLAY) - if(socks_s) - preview_icon.Blend(socks_s, ICON_OVERLAY) - if(clothes_s) - preview_icon.Blend(clothes_s, ICON_OVERLAY) - preview_icon.Blend(face_s, ICON_OVERLAY) - preview_icon_front = new(preview_icon, dir = SOUTH) - preview_icon_side = new(preview_icon, dir = WEST) - - qdel(face_s) - qdel(underwear_s) - qdel(undershirt_s) - qdel(socks_s) - qdel(clothes_s) +/datum/preferences + //The mob should have a gender you want before running this proc. Will run fine without H +/datum/preferences/proc/random_character(gender_override) + var/datum/species/S = GLOB.all_species[species] + if(!istype(S)) //The species was invalid. Set the species to the default, fetch the datum for that species and generate a random character. + species = initial(species) + S = GLOB.all_species[species] + var/datum/robolimb/robohead + + if(S.bodyflags & ALL_RPARTS) + var/head_model = "[!rlimb_data["head"] ? "Morpheus Cyberkinetics" : rlimb_data["head"]]" + robohead = all_robolimbs[head_model] + if(gender_override) + gender = gender_override + else + gender = pick(MALE, FEMALE) + underwear = random_underwear(gender, species) + undershirt = random_undershirt(gender, species) + socks = random_socks(gender, species) + if(body_accessory_by_species[species]) + body_accessory = random_body_accessory(species) + if(body_accessory == "None") //Required to prevent a bug where the information/icons in the character preferences screen wouldn't update despite the data being changed. + body_accessory = null + if(S.bodyflags & (HAS_SKIN_TONE|HAS_ICON_SKIN_TONE)) + s_tone = random_skin_tone(species) + h_style = random_hair_style(gender, species, robohead) + f_style = random_facial_hair_style(gender, species, robohead) + if(species in list("Human", "Unathi", "Tajaran", "Skrell", "Machine", "Wryn", "Vulpkanin", "Vox")) + randomize_hair_color("hair") + randomize_hair_color("facial") + if(S.bodyflags & HAS_HEAD_ACCESSORY) + ha_style = random_head_accessory(species) + hacc_colour = randomize_skin_color(1) + if(S.bodyflags & HAS_HEAD_MARKINGS) + m_styles["head"] = random_marking_style("head", species, robohead, null, alt_head) + m_colours["head"] = randomize_skin_color(1) + if(S.bodyflags & HAS_BODY_MARKINGS) + m_styles["body"] = random_marking_style("body", species) + m_colours["body"] = randomize_skin_color(1) + if(S.bodyflags & HAS_TAIL_MARKINGS) //Species with tail markings. + m_styles["tail"] = random_marking_style("tail", species, null, body_accessory) + m_colours["tail"] = randomize_skin_color(1) + if(!(S.bodyflags & ALL_RPARTS)) + randomize_eyes_color() + if(S.bodyflags & HAS_SKIN_COLOR) + randomize_skin_color() + backbag = 2 + age = rand(AGE_MIN, AGE_MAX) + + +/datum/preferences/proc/randomize_hair_color(var/target = "hair") + if(prob (75) && target == "facial") // Chance to inherit hair color + f_colour = h_colour + return + + var/red + var/green + var/blue + + var/col = pick ("blonde", "black", "chestnut", "copper", "brown", "wheat", "old", "punk") + switch(col) + if("blonde") + red = 255 + green = 255 + blue = 0 + if("black") + red = 0 + green = 0 + blue = 0 + if("chestnut") + red = 153 + green = 102 + blue = 51 + if("copper") + red = 255 + green = 153 + blue = 0 + if("brown") + red = 102 + green = 51 + blue = 0 + if("wheat") + red = 255 + green = 255 + blue = 153 + if("old") + red = rand (100, 255) + green = red + blue = red + if("punk") + red = rand (0, 255) + green = rand (0, 255) + blue = rand (0, 255) + + red = max(min(red + rand (-25, 25), 255), 0) + green = max(min(green + rand (-25, 25), 255), 0) + blue = max(min(blue + rand (-25, 25), 255), 0) + + switch(target) + if("hair") + h_colour = rgb(red, green, blue) + if("facial") + f_colour = rgb(red, green, blue) + +/datum/preferences/proc/randomize_eyes_color() + var/red + var/green + var/blue + + var/col = pick ("black", "grey", "brown", "chestnut", "blue", "lightblue", "green", "albino") + switch(col) + if("black") + red = 0 + green = 0 + blue = 0 + if("grey") + red = rand (100, 200) + green = red + blue = red + if("brown") + red = 102 + green = 51 + blue = 0 + if("chestnut") + red = 153 + green = 102 + blue = 0 + if("blue") + red = 51 + green = 102 + blue = 204 + if("lightblue") + red = 102 + green = 204 + blue = 255 + if("green") + red = 0 + green = 102 + blue = 0 + if("albino") + red = rand (200, 255) + green = rand (0, 150) + blue = rand (0, 150) + + red = max(min(red + rand (-25, 25), 255), 0) + green = max(min(green + rand (-25, 25), 255), 0) + blue = max(min(blue + rand (-25, 25), 255), 0) + + e_colour = rgb(red, green, blue) + +/datum/preferences/proc/randomize_skin_color(var/pass_on) + var/red + var/green + var/blue + + var/col = pick ("black", "grey", "brown", "chestnut", "blue", "lightblue", "green", "albino") + switch(col) + if("black") + red = 0 + green = 0 + blue = 0 + if("grey") + red = rand (100, 200) + green = red + blue = red + if("brown") + red = 102 + green = 51 + blue = 0 + if("chestnut") + red = 153 + green = 102 + blue = 0 + if("blue") + red = 51 + green = 102 + blue = 204 + if("lightblue") + red = 102 + green = 204 + blue = 255 + if("green") + red = 0 + green = 102 + blue = 0 + if("albino") + red = rand (200, 255) + green = rand (0, 150) + blue = rand (0, 150) + + red = max(min(red + rand (-25, 25), 255), 0) + green = max(min(green + rand (-25, 25), 255), 0) + blue = max(min(blue + rand (-25, 25), 255), 0) + + if(pass_on) + return rgb(red, green, blue) + else + s_colour = rgb(red, green, blue) + +/datum/preferences/proc/blend_backpack(var/icon/clothes_s,var/backbag,var/satchel,var/backpack="backpack") + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', backpack), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', satchel), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + return clothes_s + +/datum/preferences/proc/update_preview_icon(var/for_observer=0) //seriously. This is horrendous. + qdel(preview_icon_front) + qdel(preview_icon_side) + qdel(preview_icon) + + var/g = "m" + if(gender == FEMALE) g = "f" + + var/icon/icobase + var/datum/species/current_species = GLOB.all_species[species] + + //Icon-based species colour. + var/coloured_tail + if(current_species) + if(current_species.bodyflags & HAS_ICON_SKIN_TONE) //Handling species-specific icon-based skin tones by flagged race. + var/mob/living/carbon/human/H = new + H.dna.species = current_species + H.s_tone = s_tone + H.dna.species.updatespeciescolor(H, 0) //The mob's species wasn't set, so it's almost certainly different than the character's species at the moment. Thus, we need to be owner-insensitive. + var/obj/item/organ/external/chest/C = H.get_organ("chest") + icobase = C.icobase ? C.icobase : C.dna.species.icobase + if(H.dna.species.bodyflags & HAS_TAIL) + coloured_tail = H.tail ? H.tail : H.dna.species.tail + + qdel(H) + else + icobase = current_species.icobase + else + icobase = 'icons/mob/human_races/r_human.dmi' + + var/fat="" + if(disabilities & DISABILITY_FLAG_FAT && (CAN_BE_FAT in current_species.species_traits)) + fat="_fat" + preview_icon = new /icon(icobase, "torso_[g][fat]") + preview_icon.Blend(new /icon(icobase, "groin_[g]"), ICON_OVERLAY) + var/head = "head" + if(alt_head && current_species.bodyflags & HAS_ALT_HEADS) + var/datum/sprite_accessory/alt_heads/H = GLOB.alt_heads_list[alt_head] + if(H.icon_state) + head = H.icon_state + preview_icon.Blend(new /icon(icobase, "[head]_[g]"), ICON_OVERLAY) + + for(var/name in list("chest", "groin", "head", "r_arm", "r_hand", "r_leg", "r_foot", "l_leg", "l_foot", "l_arm", "l_hand")) + if(organ_data[name] == "amputated") continue + if(organ_data[name] == "cyborg") + var/datum/robolimb/R + if(rlimb_data[name]) R = all_robolimbs[rlimb_data[name]] + if(!R) R = basic_robolimb + if(name == "chest") + name = "torso" + preview_icon.Blend(icon(R.icon, "[name]"), ICON_OVERLAY) // This doesn't check gendered_icon. Not an issue while only limbs can be robotic. + continue + preview_icon.Blend(new /icon(icobase, "[name]"), ICON_OVERLAY) + + // Skin color + if(current_species && (current_species.bodyflags & HAS_SKIN_COLOR)) + preview_icon.Blend(s_colour, ICON_ADD) + + // Skin tone + if(current_species && (current_species.bodyflags & HAS_SKIN_TONE)) + if(s_tone >= 0) + preview_icon.Blend(rgb(s_tone, s_tone, s_tone), ICON_ADD) + else + preview_icon.Blend(rgb(-s_tone, -s_tone, -s_tone), ICON_SUBTRACT) + + //Tail + if(current_species && (current_species.bodyflags & HAS_TAIL)) + var/tail_icon + var/tail_icon_state + var/tail_shift_x + var/tail_shift_y + var/blend_mode = ICON_ADD + + if(body_accessory) + var/datum/body_accessory/accessory = body_accessory_by_name[body_accessory] + tail_icon = accessory.icon + tail_icon_state = accessory.icon_state + if(accessory.blend_mode) + blend_mode = accessory.blend_mode + if(accessory.pixel_x_offset) + tail_shift_x = accessory.pixel_x_offset + if(accessory.pixel_y_offset) + tail_shift_y = accessory.pixel_y_offset + else + tail_icon = "icons/effects/species.dmi" + if(coloured_tail) + tail_icon_state = "[coloured_tail]_s" + else + tail_icon_state = "[current_species.tail]_s" + + var/icon/temp = new/icon("icon" = tail_icon, "icon_state" = tail_icon_state) + if(tail_shift_x) + temp.Shift(EAST, tail_shift_x) + if(tail_shift_y) + temp.Shift(NORTH, tail_shift_y) + + if(current_species && (current_species.bodyflags & HAS_SKIN_COLOR)) + temp.Blend(s_colour, blend_mode) + + if(current_species && (current_species.bodyflags & HAS_TAIL_MARKINGS)) + var/tail_marking = m_styles["tail"] + var/datum/sprite_accessory/tail_marking_style = GLOB.marking_styles_list[tail_marking] + if(tail_marking_style && tail_marking_style.species_allowed) + var/icon/t_marking_s = new/icon("icon" = tail_marking_style.icon, "icon_state" = "[tail_marking_style.icon_state]_s") + t_marking_s.Blend(m_colours["tail"], ICON_ADD) + temp.Blend(t_marking_s, ICON_OVERLAY) + + preview_icon.Blend(temp, ICON_OVERLAY) + + //Markings + if(current_species && ((current_species.bodyflags & HAS_HEAD_MARKINGS) || (current_species.bodyflags & HAS_BODY_MARKINGS))) + if(current_species.bodyflags & HAS_BODY_MARKINGS) //Body markings. + var/body_marking = m_styles["body"] + var/datum/sprite_accessory/body_marking_style = GLOB.marking_styles_list[body_marking] + if(body_marking_style && body_marking_style.species_allowed) + var/icon/b_marking_s = new/icon("icon" = body_marking_style.icon, "icon_state" = "[body_marking_style.icon_state]_s") + b_marking_s.Blend(m_colours["body"], ICON_ADD) + preview_icon.Blend(b_marking_s, ICON_OVERLAY) + if(current_species.bodyflags & HAS_HEAD_MARKINGS) //Head markings. + var/head_marking = m_styles["head"] + var/datum/sprite_accessory/head_marking_style = GLOB.marking_styles_list[head_marking] + if(head_marking_style && head_marking_style.species_allowed) + var/icon/h_marking_s = new/icon("icon" = head_marking_style.icon, "icon_state" = "[head_marking_style.icon_state]_s") + h_marking_s.Blend(m_colours["head"], ICON_ADD) + preview_icon.Blend(h_marking_s, ICON_OVERLAY) + + + var/icon/face_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "bald_s") + if(!(current_species.bodyflags & NO_EYES)) + var/icon/eyes_s = new/icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = current_species ? current_species.eyes : "eyes_s") + eyes_s.Blend(e_colour, ICON_ADD) + face_s.Blend(eyes_s, ICON_OVERLAY) + + + var/datum/sprite_accessory/hair_style = GLOB.hair_styles_full_list[h_style] + if(hair_style) + var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s") + if(current_species.name == "Slime People") // whee I am part of the problem + hair_s.Blend("[s_colour]A0", ICON_ADD) + else if(hair_style.do_colouration) + hair_s.Blend(h_colour, ICON_ADD) + + if(hair_style.secondary_theme) + var/icon/hair_secondary_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_[hair_style.secondary_theme]_s") + if(!hair_style.no_sec_colour && hair_style.do_colouration ) + hair_secondary_s.Blend(h_sec_colour, ICON_ADD) + hair_s.Blend(hair_secondary_s, ICON_OVERLAY) + + face_s.Blend(hair_s, ICON_OVERLAY) + + //Head Accessory + if(current_species && (current_species.bodyflags & HAS_HEAD_ACCESSORY)) + var/datum/sprite_accessory/head_accessory_style = GLOB.head_accessory_styles_list[ha_style] + if(head_accessory_style && head_accessory_style.species_allowed) + var/icon/head_accessory_s = new/icon("icon" = head_accessory_style.icon, "icon_state" = "[head_accessory_style.icon_state]_s") + head_accessory_s.Blend(hacc_colour, ICON_ADD) + face_s.Blend(head_accessory_s, ICON_OVERLAY) + + var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[f_style] + if(facial_hair_style && facial_hair_style.species_allowed) + var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s") + if(current_species.name == "Slime People") // whee I am part of the problem + facial_s.Blend("[s_colour]A0", ICON_ADD) + else if(facial_hair_style.do_colouration) + facial_s.Blend(f_colour, ICON_ADD) + + if(facial_hair_style.secondary_theme) + var/icon/facial_secondary_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_[facial_hair_style.secondary_theme]_s") + if(!facial_hair_style.no_sec_colour && facial_hair_style.do_colouration) + facial_secondary_s.Blend(f_sec_colour, ICON_ADD) + facial_s.Blend(facial_secondary_s, ICON_OVERLAY) + + face_s.Blend(facial_s, ICON_OVERLAY) + + var/icon/underwear_s = null + if(underwear && (current_species.clothing_flags & HAS_UNDERWEAR)) + var/datum/sprite_accessory/underwear/U = GLOB.underwear_list[underwear] + if(U) + var/u_icon = U.sprite_sheets && (current_species.name in U.sprite_sheets) ? U.sprite_sheets[current_species.name] : U.icon //Species-fit the undergarment. + underwear_s = new/icon(u_icon, "uw_[U.icon_state]_s", ICON_OVERLAY) + + var/icon/undershirt_s = null + if(undershirt && (current_species.clothing_flags & HAS_UNDERSHIRT)) + var/datum/sprite_accessory/undershirt/U2 = GLOB.undershirt_list[undershirt] + if(U2) + var/u2_icon = U2.sprite_sheets && (current_species.name in U2.sprite_sheets) ? U2.sprite_sheets[current_species.name] : U2.icon + undershirt_s = new/icon(u2_icon, "us_[U2.icon_state]_s", ICON_OVERLAY) + + var/icon/socks_s = null + if(socks && (current_species.clothing_flags & HAS_SOCKS)) + var/datum/sprite_accessory/socks/U3 = GLOB.socks_list[socks] + if(U3) + var/u3_icon = U3.sprite_sheets && (current_species.name in U3.sprite_sheets) ? U3.sprite_sheets[current_species.name] : U3.icon + socks_s = new/icon(u3_icon, "sk_[U3.icon_state]_s", ICON_OVERLAY) + + var/icon/clothes_s = null + var/uniform_dmi='icons/mob/uniform.dmi' + if(disabilities&DISABILITY_FLAG_FAT) + uniform_dmi='icons/mob/uniform_fat.dmi' + if(job_support_low & JOB_CIVILIAN)//This gives the preview icon clothes depending on which job(if any) is set to 'high' + clothes_s = new /icon(uniform_dmi, "grey_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if(backbag == 2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + else if(backbag == 3 || backbag == 4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + + else if(job_support_high)//I hate how this looks, but there's no reason to go through this switch if it's empty + switch(job_support_high) + if(JOB_HOP) + clothes_s = new /icon(uniform_dmi, "hop_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "ianshirt"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_BARTENDER) + clothes_s = new /icon(uniform_dmi, "ba_suit_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "tophat"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_BOTANIST) + clothes_s = new /icon(uniform_dmi, "hydroponics_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "ggloves"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "apron"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "nymph"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-hyd"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CHEF) + clothes_s = new /icon(uniform_dmi, "chef_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "chef"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "apronchef"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_JANITOR) + clothes_s = new /icon(uniform_dmi, "janitor_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "bio_janitor"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_LIBRARIAN) + clothes_s = new /icon(uniform_dmi, "red_suit_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "hairflower"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_QUARTERMASTER) + clothes_s = new /icon(uniform_dmi, "qm_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "poncho"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CARGOTECH) + clothes_s = new /icon(uniform_dmi, "cargotech_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "flat_cap"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_MINER) + clothes_s = new /icon(uniform_dmi, "miner_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "bearpelt"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-eng"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_LAWYER) + clothes_s = new /icon(uniform_dmi, "internalaffairs_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/inhands/items_righthand.dmi', "briefcase"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "suitjacket_blue"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CHAPLAIN) + clothes_s = new /icon(uniform_dmi, "chapblack_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "imperium_monk"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CLOWN) + clothes_s = new /icon(uniform_dmi, "clown_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "clown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/mask.dmi', "clown"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "clownpack"), ICON_OVERLAY) + if(JOB_MIME) + clothes_s = new /icon(uniform_dmi, "mime_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "lgloves"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/mask.dmi', "mime"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "beret"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "suspenders"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + + else if(job_medsci_high) + switch(job_medsci_high) + if(JOB_RD) + clothes_s = new /icon(uniform_dmi, "director_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "petehat"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-tox"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_SCIENTIST) + clothes_s = new /icon(uniform_dmi, "toxinswhite_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_tox_open"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "metroid"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-tox"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CHEMIST) + clothes_s = new /icon(uniform_dmi, "chemistrywhite_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labgreen"), ICON_OVERLAY) + else + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_chem_open"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-chem"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CMO) + clothes_s = new /icon(uniform_dmi, "cmo_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "bio_cmo"), ICON_OVERLAY) + else + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_cmo_open"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_DOCTOR) + clothes_s = new /icon(uniform_dmi, "medical_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "surgeon"), ICON_OVERLAY) + else + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CORONER) + clothes_s = new /icon(uniform_dmi, "medical_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "mortician"), ICON_OVERLAY) + else + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_GENETICIST) + clothes_s = new /icon(uniform_dmi, "geneticswhite_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "monkeysuit"), ICON_OVERLAY) + else + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_gen_open"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-gen"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_VIROLOGIST) + clothes_s = new /icon(uniform_dmi, "virologywhite_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/mask.dmi', "sterile"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_vir_open"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "plaguedoctor"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-vir"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_PSYCHIATRIST) + clothes_s = new /icon(uniform_dmi, "psych_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_PARAMEDIC) + clothes_s = new /icon(uniform_dmi, "paramedic_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/mask.dmi', "cigoff"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "bluesoft"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) + if(JOB_ROBOTICIST) + clothes_s = new /icon(uniform_dmi, "robotics_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "labcoat_open"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/inhands/items_righthand.dmi', "toolbox_blue"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + + else if(job_engsec_high) + switch(job_engsec_high) + if(JOB_CAPTAIN) + clothes_s = new /icon(uniform_dmi, "captain_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "centcomcaptain"), ICON_OVERLAY) + else + clothes_s.Blend(new /icon('icons/mob/head.dmi', "captain"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-cap"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_HOS) + clothes_s = new /icon(uniform_dmi, "hosred_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "beret_hos"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-sec"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_WARDEN) + clothes_s = new /icon('icons/mob/uniform.dmi', "warden_s") + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "slippers_worn"), ICON_OVERLAY) + else + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-sec"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_DETECTIVE) + clothes_s = new /icon(uniform_dmi, "detective_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/mask.dmi', "cigaron"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "detective"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "detective"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_OFFICER) + clothes_s = new /icon(uniform_dmi, "secred_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "beret_officer"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-sec"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CHIEF) + clothes_s = new /icon(uniform_dmi, "chief_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "brown"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "hardhat0_white"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/inhands/items_righthand.dmi', "blueprints"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "engiepack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-eng"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_ENGINEER) + clothes_s = new /icon(uniform_dmi, "engine_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "hardhat0_yellow"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "hazard"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "engiepack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-eng"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_ATMOSTECH) + clothes_s = new /icon(uniform_dmi, "atmos_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "bgloves"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) + if(prob(1)) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "firesuit"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + + if(JOB_AI)//Gives AI and borgs assistant-wear, so they can still customize their character + clothes_s = new /icon(uniform_dmi, "grey_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "straight_jacket"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "cardborg_h"), ICON_OVERLAY) + if(backbag == 2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + else if(backbag == 3 || backbag == 4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_CYBORG) + clothes_s = new /icon(uniform_dmi, "grey_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "cardborg"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "cardborg_h"), ICON_OVERLAY) + if(backbag == 2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + else if(backbag == 3 || backbag == 4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + else if(job_karma_high) + switch(job_karma_high) + if(JOB_MECHANIC) + clothes_s = new /icon(uniform_dmi, "mechanic_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "orange"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/belt.dmi', "utility"), ICON_OVERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "hardhat0_yellow"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "engiepack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-eng"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_PILOT) + clothes_s = new /icon(uniform_dmi, "secred_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "bomber"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-sec"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_BRIGDOC) + clothes_s = new /icon(uniform_dmi, "medical_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "white"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "fr_jacket_open"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "medicalpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-med"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_NANO) + clothes_s = new /icon(uniform_dmi, "officer_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_BLUESHIELD) + clothes_s = new /icon(uniform_dmi, "officer_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "jackboots"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/hands.dmi', "swat_gl"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "blueshield"), ICON_OVERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "securitypack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + if(JOB_JUDGE) + clothes_s = new /icon(uniform_dmi, "really_black_suit_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "laceups"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/head.dmi', "mercy_hood"), ICON_UNDERLAY) + clothes_s.Blend(new /icon('icons/mob/suit.dmi', "judge"), ICON_UNDERLAY) + switch(backbag) + if(2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + if(3) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel-norm"), ICON_OVERLAY) + if(4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + + if(disabilities & NEARSIGHTED) + preview_icon.Blend(new /icon('icons/mob/eyes.dmi', "glasses"), ICON_OVERLAY) + + // Observers get tourist outfit. + if(for_observer) + clothes_s = new /icon(uniform_dmi, "tourist_s") + clothes_s.Blend(new /icon('icons/mob/feet.dmi', "black"), ICON_UNDERLAY) + if(backbag == 2) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "backpack"), ICON_OVERLAY) + else if(backbag == 3 || backbag == 4) + clothes_s.Blend(new /icon('icons/mob/back.dmi', "satchel"), ICON_OVERLAY) + + if(underwear_s) + preview_icon.Blend(underwear_s, ICON_OVERLAY) + if(undershirt_s) + preview_icon.Blend(undershirt_s, ICON_OVERLAY) + if(socks_s) + preview_icon.Blend(socks_s, ICON_OVERLAY) + if(clothes_s) + preview_icon.Blend(clothes_s, ICON_OVERLAY) + preview_icon.Blend(face_s, ICON_OVERLAY) + preview_icon_front = new(preview_icon, dir = SOUTH) + preview_icon_side = new(preview_icon, dir = WEST) + + qdel(face_s) + qdel(underwear_s) + qdel(undershirt_s) + qdel(socks_s) + qdel(clothes_s) diff --git a/code/modules/mob/new_player/sprite_accessories/diona/diona_hair.dm b/code/modules/mob/new_player/sprite_accessories/diona/diona_hair.dm index 3ab33449080..37bb1bdd1ee 100644 --- a/code/modules/mob/new_player/sprite_accessories/diona/diona_hair.dm +++ b/code/modules/mob/new_player/sprite_accessories/diona/diona_hair.dm @@ -56,4 +56,4 @@ /datum/sprite_accessory/hair/diona/diona_wildflow name = "Wildflowers" - icon_state = "wildflower" \ No newline at end of file + icon_state = "wildflower" diff --git a/code/modules/mob/new_player/sprite_accessories/drask/drask_body_markings.dm b/code/modules/mob/new_player/sprite_accessories/drask/drask_body_markings.dm index abc16905788..da8d8c9160f 100644 --- a/code/modules/mob/new_player/sprite_accessories/drask/drask_body_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/drask/drask_body_markings.dm @@ -4,4 +4,4 @@ /datum/sprite_accessory/body_markings/drask/arm_spines_drask icon = 'icons/mob/sprite_accessories/drask/drask_body_markings.dmi' name = "Drask Arm Spines" - icon_state = "armspines" \ No newline at end of file + icon_state = "armspines" diff --git a/code/modules/mob/new_player/sprite_accessories/human/human_body_markings.dm b/code/modules/mob/new_player/sprite_accessories/human/human_body_markings.dm index 9cef4230139..9dd5345d483 100644 --- a/code/modules/mob/new_player/sprite_accessories/human/human_body_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/human/human_body_markings.dm @@ -32,4 +32,4 @@ /datum/sprite_accessory/body_markings/tattoo/nightling name = "Nightling Tattoo" - icon_state = "nightling" \ No newline at end of file + icon_state = "nightling" diff --git a/code/modules/mob/new_player/sprite_accessories/human/human_facial_hair.dm b/code/modules/mob/new_player/sprite_accessories/human/human_facial_hair.dm index e3136011393..d0a3d369c4d 100644 --- a/code/modules/mob/new_player/sprite_accessories/human/human_facial_hair.dm +++ b/code/modules/mob/new_player/sprite_accessories/human/human_facial_hair.dm @@ -102,4 +102,4 @@ icon_state = "unshaven" ////////////////////////////// //////END VG HAIRSTYLES/////// -////////////////////////////// \ No newline at end of file +////////////////////////////// diff --git a/code/modules/mob/new_player/sprite_accessories/human/human_hair.dm b/code/modules/mob/new_player/sprite_accessories/human/human_hair.dm index bc9931a8782..f9e1ee1cc49 100644 --- a/code/modules/mob/new_player/sprite_accessories/human/human_hair.dm +++ b/code/modules/mob/new_player/sprite_accessories/human/human_hair.dm @@ -755,4 +755,4 @@ /datum/sprite_accessory/hair/fluff/pinapple_fluff_hair //Pineapple Salad hair fluff its for a slime..has to go under human name = "Sasook Hair" icon_state = "psalad_fluff_hair" - species_allowed = list("Slime People") \ No newline at end of file + species_allowed = list("Slime People") diff --git a/code/modules/mob/new_player/sprite_accessories/ipc/ipc_face.dm b/code/modules/mob/new_player/sprite_accessories/ipc/ipc_face.dm index 85e0f708aaf..8910a8f5229 100644 --- a/code/modules/mob/new_player/sprite_accessories/ipc/ipc_face.dm +++ b/code/modules/mob/new_player/sprite_accessories/ipc/ipc_face.dm @@ -132,4 +132,4 @@ /datum/sprite_accessory/hair/ipc/fluff/lumi_waiting //Lumi Fluff hair name = "Lumi Waiting" - icon_state = "lumi_waiting" \ No newline at end of file + icon_state = "lumi_waiting" diff --git a/code/modules/mob/new_player/sprite_accessories/ipc/ipc_optics.dm b/code/modules/mob/new_player/sprite_accessories/ipc/ipc_optics.dm index ff157a25d7d..1a0e2756065 100644 --- a/code/modules/mob/new_player/sprite_accessories/ipc/ipc_optics.dm +++ b/code/modules/mob/new_player/sprite_accessories/ipc/ipc_optics.dm @@ -23,4 +23,4 @@ /datum/sprite_accessory/body_markings/head/optics/xion_alt name = "Xion Alt. Optics" icon_state = "xion_alt_optics" - models_allowed = list("Xion Manufacturing Group alt.") \ No newline at end of file + models_allowed = list("Xion Manufacturing Group alt.") diff --git a/code/modules/mob/new_player/sprite_accessories/kidan/kidan_body_markings.dm b/code/modules/mob/new_player/sprite_accessories/kidan/kidan_body_markings.dm index 622ba3736f4..ba8d90fb720 100644 --- a/code/modules/mob/new_player/sprite_accessories/kidan/kidan_body_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/kidan/kidan_body_markings.dm @@ -4,4 +4,4 @@ /datum/sprite_accessory/body_markings/kidan/outline_kid name = "Kidan Outline" - icon_state = "outline" \ No newline at end of file + icon_state = "outline" diff --git a/code/modules/mob/new_player/sprite_accessories/kidan/kidan_hair.dm b/code/modules/mob/new_player/sprite_accessories/kidan/kidan_hair.dm index cf2bd865e26..795c58622be 100644 --- a/code/modules/mob/new_player/sprite_accessories/kidan/kidan_hair.dm +++ b/code/modules/mob/new_player/sprite_accessories/kidan/kidan_hair.dm @@ -31,4 +31,4 @@ /datum/sprite_accessory/hair/kidan/kidan_tall_horns name = "Tall Horns" - icon_state = "tall_horns" \ No newline at end of file + icon_state = "tall_horns" diff --git a/code/modules/mob/new_player/sprite_accessories/kidan/kidan_head_accessories.dm b/code/modules/mob/new_player/sprite_accessories/kidan/kidan_head_accessories.dm index 521daa2023f..aa938926eb9 100644 --- a/code/modules/mob/new_player/sprite_accessories/kidan/kidan_head_accessories.dm +++ b/code/modules/mob/new_player/sprite_accessories/kidan/kidan_head_accessories.dm @@ -53,4 +53,4 @@ /datum/sprite_accessory/head_accessory/kidan/kidan_very_short name = "Very Short" icon_state = "very_short" -// End \ No newline at end of file +// End diff --git a/code/modules/mob/new_player/sprite_accessories/nucleation/nucleation_face.dm b/code/modules/mob/new_player/sprite_accessories/nucleation/nucleation_face.dm index 87a6f31cc31..3218a15357a 100644 --- a/code/modules/mob/new_player/sprite_accessories/nucleation/nucleation_face.dm +++ b/code/modules/mob/new_player/sprite_accessories/nucleation/nucleation_face.dm @@ -29,4 +29,4 @@ /datum/sprite_accessory/hair/nucleation/nuc_neutron name = "Nucleation Neutron Bomb" - icon_state = "neutron" \ No newline at end of file + icon_state = "neutron" diff --git a/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_body_markings.dm b/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_body_markings.dm index decbcce8578..572bd5d556b 100644 --- a/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_body_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_body_markings.dm @@ -20,4 +20,4 @@ /datum/sprite_accessory/body_markings/tajara/patchy_taj name = "Tajaran Patches" - icon_state = "patch" \ No newline at end of file + icon_state = "patch" diff --git a/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_facial_hair.dm b/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_facial_hair.dm index 794b89c8d74..3e9ae78ef42 100644 --- a/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_facial_hair.dm +++ b/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_facial_hair.dm @@ -28,4 +28,4 @@ /datum/sprite_accessory/facial_hair/tajara/taj_smallstache name = "Tajara Smallstache" - icon_state = "facial_smallstache" \ No newline at end of file + icon_state = "facial_smallstache" diff --git a/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_hair.dm b/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_hair.dm index 7b55508877c..03cb618ee0b 100644 --- a/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_hair.dm +++ b/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_hair.dm @@ -78,4 +78,4 @@ /datum/sprite_accessory/hair/tajara/taj_hair_fingercurl name = "Tajara Finger Curls" icon_state = "fingerwave" - glasses_over = null \ No newline at end of file + glasses_over = null diff --git a/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_head_markings.dm b/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_head_markings.dm index 88d534bb3c2..a3afa6823b4 100644 --- a/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_head_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/tajaran/tajaran_head_markings.dm @@ -41,4 +41,4 @@ /datum/sprite_accessory/body_markings/head/tajara/patchy_taj //Companion marking for Tajaran Patches. name = "Tajaran Patches Head" - icon_state = "patch" \ No newline at end of file + icon_state = "patch" diff --git a/code/modules/mob/new_player/sprite_accessories/unathi/unathi_alt_heads.dm b/code/modules/mob/new_player/sprite_accessories/unathi/unathi_alt_heads.dm index 7b16b9fab10..aa8c992e014 100644 --- a/code/modules/mob/new_player/sprite_accessories/unathi/unathi_alt_heads.dm +++ b/code/modules/mob/new_player/sprite_accessories/unathi/unathi_alt_heads.dm @@ -2,4 +2,4 @@ name = "Unathi Sharp Snout" species_allowed = list("Unathi") icon_state = "head_sharp" - suffix = "sharp" \ No newline at end of file + suffix = "sharp" diff --git a/code/modules/mob/new_player/sprite_accessories/unathi/unathi_body_markings.dm b/code/modules/mob/new_player/sprite_accessories/unathi/unathi_body_markings.dm index e65fe303d36..a37b65c4561 100644 --- a/code/modules/mob/new_player/sprite_accessories/unathi/unathi_body_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/unathi/unathi_body_markings.dm @@ -16,4 +16,4 @@ /datum/sprite_accessory/body_markings/unathi/points_una name = "Unathi Points" - icon_state = "points" \ No newline at end of file + icon_state = "points" diff --git a/code/modules/mob/new_player/sprite_accessories/unathi/unathi_facial_hair.dm b/code/modules/mob/new_player/sprite_accessories/unathi/unathi_facial_hair.dm index 7cba458e2b0..16b9a13fae4 100644 --- a/code/modules/mob/new_player/sprite_accessories/unathi/unathi_facial_hair.dm +++ b/code/modules/mob/new_player/sprite_accessories/unathi/unathi_facial_hair.dm @@ -73,4 +73,4 @@ /datum/sprite_accessory/facial_hair/unathi/una_spikes name = "Spikes" icon = 'icons/mob/sprite_accessories/unathi/unathi_hair.dmi' - icon_state = "spikes_horns" \ No newline at end of file + icon_state = "spikes_horns" diff --git a/code/modules/mob/new_player/sprite_accessories/unathi/unathi_head_accessories.dm b/code/modules/mob/new_player/sprite_accessories/unathi/unathi_head_accessories.dm index 0cab3238016..52968c00219 100644 --- a/code/modules/mob/new_player/sprite_accessories/unathi/unathi_head_accessories.dm +++ b/code/modules/mob/new_player/sprite_accessories/unathi/unathi_head_accessories.dm @@ -51,4 +51,4 @@ /datum/sprite_accessory/head_accessory/unathi/spikes name = "Spikes" - icon_state = "spikes_horns" \ No newline at end of file + icon_state = "spikes_horns" diff --git a/code/modules/mob/new_player/sprite_accessories/vox/vox_body_markings.dm b/code/modules/mob/new_player/sprite_accessories/vox/vox_body_markings.dm index 563b39356d9..1bf69829bef 100644 --- a/code/modules/mob/new_player/sprite_accessories/vox/vox_body_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/vox/vox_body_markings.dm @@ -16,4 +16,4 @@ /datum/sprite_accessory/body_markings/tattoo/vox/tiger_body_vox name = "Vox Tiger-stripe Tattoo" - icon_state = "tiger" \ No newline at end of file + icon_state = "tiger" diff --git a/code/modules/mob/new_player/sprite_accessories/vox/vox_facial_hair.dm b/code/modules/mob/new_player/sprite_accessories/vox/vox_facial_hair.dm index 18d212de255..ae189382669 100644 --- a/code/modules/mob/new_player/sprite_accessories/vox/vox_facial_hair.dm +++ b/code/modules/mob/new_player/sprite_accessories/vox/vox_facial_hair.dm @@ -17,4 +17,4 @@ /datum/sprite_accessory/facial_hair/vox/vox_beard name = "Vox Quill Beard" - icon_state = "beard" \ No newline at end of file + icon_state = "beard" diff --git a/code/modules/mob/new_player/sprite_accessories/vox/vox_tail_markings.dm b/code/modules/mob/new_player/sprite_accessories/vox/vox_tail_markings.dm index e77c6f7e19d..6a3d50317ac 100644 --- a/code/modules/mob/new_player/sprite_accessories/vox/vox_tail_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/vox/vox_tail_markings.dm @@ -12,4 +12,4 @@ /datum/sprite_accessory/body_markings/tail/vox/vox_stripe name = "Vox Tail Stripe" - icon_state = "stripe" \ No newline at end of file + icon_state = "stripe" diff --git a/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_head_accessories.dm b/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_head_accessories.dm index 66edd5974fc..047fa6cd73d 100644 --- a/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_head_accessories.dm +++ b/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_head_accessories.dm @@ -54,4 +54,4 @@ /datum/sprite_accessory/head_accessory/vulpkanin/vulp_slash name = "Slash" - icon_state = "slash" \ No newline at end of file + icon_state = "slash" diff --git a/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_head_markings.dm b/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_head_markings.dm index 47253385ba1..c04f7061efc 100644 --- a/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_head_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_head_markings.dm @@ -32,4 +32,4 @@ /datum/sprite_accessory/body_markings/head/vulpkanin/points_sharp_vulp //Companion marking for Vulpkanin Points Sharp. name = "Vulpkanin Points Head 2" - icon_state = "points_sharp" \ No newline at end of file + icon_state = "points_sharp" diff --git a/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_tail_markings.dm b/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_tail_markings.dm index 05060d3c676..bceaa8ae729 100644 --- a/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_tail_markings.dm +++ b/code/modules/mob/new_player/sprite_accessories/vulpkanin/vulpkanin_tail_markings.dm @@ -28,4 +28,4 @@ /datum/sprite_accessory/body_markings/tail/vulpkanin/vulp_hybrid_silverf name = "Vulpkanin Bushy Straight Tail Black Fade White Tip" tails_allowed = list("Straight Bushy Tail") - icon_state = "sbsilverfade" \ No newline at end of file + icon_state = "sbsilverfade" diff --git a/code/modules/mob/new_player/sprite_accessories/wryn/wryn_face.dm b/code/modules/mob/new_player/sprite_accessories/wryn/wryn_face.dm index 38d17ea2950..668808de217 100644 --- a/code/modules/mob/new_player/sprite_accessories/wryn/wryn_face.dm +++ b/code/modules/mob/new_player/sprite_accessories/wryn/wryn_face.dm @@ -5,4 +5,4 @@ /datum/sprite_accessory/hair/wryn/wry_antennae_default name = "Antennae" - icon_state = "antennae" \ No newline at end of file + icon_state = "antennae" diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 53660f5b55d..e218ce3c25d 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -1,213 +1,213 @@ - -#define ILLEGAL_CHARACTERS_LIST list("<" = "", ">" = "", "^" = "", \ - "\[" = "", "]" = "", "{" = "", "}" = "") - -/mob/proc/say() - return - -/mob/verb/whisper(message as text) - set name = "Whisper" - set category = "IC" - return - -/mob/verb/say_verb(message as text) - set name = "Say" - set category = "IC" - - //Let's try to make users fix their errors - we try to detect single, out-of-place letters and 'unintended' words - /* - var/first_letter = copytext(message,1,2) - if((copytext(message,2,3) == " " && first_letter != "I" && first_letter != "A" && first_letter != ";") || cmptext(copytext(message,1,5), "say ") || cmptext(copytext(message,1,4), "me ") || cmptext(copytext(message,1,6), "looc ") || cmptext(copytext(message,1,5), "ooc ") || cmptext(copytext(message,2,6), "say ")) - var/response = alert(usr, "Do you really want to say this using the *say* verb?\n\n[message]\n", "Confirm your message", "Yes", "Edit message", "No") - if(response == "Edit message") - message = input(usr, "Please edit your message carefully:", "Edit message", message) - if(!message) - return - else if(response == "No") - return - */ - message = replace_characters(message, ILLEGAL_CHARACTERS_LIST) - set_typing_indicator(0) - usr.say(message) - - -/mob/verb/me_verb(message as text) - set name = "Me" - set category = "IC" - - message = strip_html_properly(message) - - set_typing_indicator(0) - if(use_me) - custom_emote(usr.emote_type, message) - else - usr.emote(message) - - -/mob/proc/say_dead(var/message) - if(!(client && client.holder)) - if(!config.dsay_allowed) - to_chat(src, "Deadchat is globally muted.") - return - - if(client && !(client.prefs.toggles & CHAT_DEAD)) - to_chat(usr, "You have deadchat muted.") - return - - say_dead_direct("[pick("complains", "moans", "whines", "laments", "blubbers", "salts")], \"[message]\"", src) - -/mob/proc/say_understands(var/mob/other, var/datum/language/speaking = null) - if(stat == DEAD) - return 1 - - //Universal speak makes everything understandable, for obvious reasons. - if(universal_speak || universal_understand) - return 1 - - //Languages are handled after. - if(!speaking) - if(!other) - return 1 - if(other.universal_speak) - return 1 - if(isAI(src) && ispAI(other)) - return 1 - if(istype(other, src.type) || istype(src, other.type)) - return 1 - return 0 - - if(speaking.flags & INNATE) - return 1 - - //Language check. - for(var/datum/language/L in languages) - if(speaking.name == L.name) - return 1 - - return 0 - - -/mob/proc/say_quote(var/message, var/datum/language/speaking = null) - var/verb = "says" - var/ending = copytext(message, length(message)) - - if(speaking) - verb = speaking.get_spoken_verb(ending) - else - if(ending == "!") - verb = pick("exclaims", "shouts", "yells") - else if(ending == "?") - verb = "asks" - return verb - - -/mob/proc/emote(act, type, message, force) - if(act == "me") - return custom_emote(type, message) - - -/mob/proc/get_ear() - // returns an atom representing a location on the map from which this - // mob can hear things - - // should be overloaded for all mobs whose "ear" is separate from their "mob" - - return get_turf(src) - -/mob/proc/say_test(var/text) - var/ending = copytext(text, length(text)) - if(ending == "?") - return "1" - else if(ending == "!") - return "2" - return "0" - -//parses the message mode code (e.g. :h, :w) from text, such as that supplied to say. -//returns the message mode string or null for no message mode. -//standard mode is the mode returned for the special ';' radio code. -/mob/proc/parse_message_mode(var/message, var/standard_mode = "headset") - if(length(message) >= 1 && copytext(message, 1, 2) == ";") - return standard_mode - - if(length(message) >= 2) - var/channel_prefix = copytext(message, 1 ,3) - return department_radio_keys[channel_prefix] - - return null - -/datum/multilingual_say_piece - var/datum/language/speaking = null - var/message = "" - -/datum/multilingual_say_piece/New(datum/language/new_speaking, new_message) - . = ..() - speaking = new_speaking - if(new_message) - message = new_message - -/mob/proc/find_valid_prefixes(message) - var/list/prefixes = list() // [["Common", start, end], ["Gutter", start, end]] - for(var/i in 1 to length(message)) - var/selection = trim_right(lowertext(copytext(message, i, i + 3))) - var/datum/language/L = GLOB.language_keys[selection] - if(L != null && can_speak_language(L)) // What the fuck... remove the L != null check if you ever find out what the fuck is adding `null` to the languages list on absolutely random mobs... seriously what the hell... - prefixes[++prefixes.len] = list(L, i, i + length(selection)) - else if(!L && i == 1) - prefixes[++prefixes.len] = list(get_default_language(), i, i) - else - return prefixes - -/proc/strip_prefixes(message) - . = "" - var/last_index = 1 - for(var/i in 1 to length(message)) - var/selection = trim_right(lowertext(copytext(message, i, i + 3))) - var/datum/language/L = GLOB.language_keys[selection] - if(L) - . += copytext(message, last_index, i) - last_index = i + 3 - if(i + 1 > length(message)) - . += copytext(message, last_index) - -// this returns a structured message with language sections -// list(/datum/multilingual_say_piece(common, "hi"), /datum/multilingual_say_piece(farwa, "squik"), /datum/multilingual_say_piece(common, "meow!")) -/mob/proc/parse_languages(message) - . = list() - - // Noise language is a snowflake - if(copytext(message, 1, 2) == "!" && length(message) > 1) - return list(new /datum/multilingual_say_piece(GLOB.all_languages["Noise"], trim(strip_prefixes(copytext(message, 2))))) - - // Scan the message for prefixes - var/list/prefix_locations = find_valid_prefixes(message) - if(!LAZYLEN(prefix_locations)) // There are no prefixes... or at least, no _valid_ prefixes. - . += new /datum/multilingual_say_piece(get_default_language(), trim(strip_prefixes(message))) // So we'll just strip those pesky things and still make the message. - - for(var/i in 1 to length(prefix_locations)) - var/current = prefix_locations[i] // ["Common", keypos] - - // There are a few things that will make us want to ignore all other languages in - namely, HIVEMIND languages. - var/datum/language/L = current[1] - if(L && L.flags & HIVEMIND) - . = new /datum/multilingual_say_piece(L, trim(strip_prefixes(message))) - break - - if(i + 1 > length(prefix_locations)) // We are out of lookaheads, that means the rest of the message is in cur lang - var/spoke_message = handle_autohiss(trim(copytext(message, current[3])), L) - . += new /datum/multilingual_say_piece(current[1], spoke_message) - else - var/next = prefix_locations[i + 1] // We look ahead at the next message to see where we need to stop. - var/spoke_message = handle_autohiss(trim(copytext(message, current[3], next[2])), L) - . += new /datum/multilingual_say_piece(current[1], spoke_message) - -/* These are here purely because it would be hell to try to convert everything over to using the multi-lingual system at once */ -/proc/message_to_multilingual(message, datum/language/speaking = null) - . = list(new /datum/multilingual_say_piece(speaking, message)) - -/proc/multilingual_to_message(list/message_pieces) - . = "" - for(var/datum/multilingual_say_piece/S in message_pieces) - . += S.message + " " - . = trim_right(.) - -#undef ILLEGAL_CHARACTERS_LIST + +#define ILLEGAL_CHARACTERS_LIST list("<" = "", ">" = "", "^" = "", \ + "\[" = "", "]" = "", "{" = "", "}" = "") + +/mob/proc/say() + return + +/mob/verb/whisper(message as text) + set name = "Whisper" + set category = "IC" + return + +/mob/verb/say_verb(message as text) + set name = "Say" + set category = "IC" + + //Let's try to make users fix their errors - we try to detect single, out-of-place letters and 'unintended' words + /* + var/first_letter = copytext(message,1,2) + if((copytext(message,2,3) == " " && first_letter != "I" && first_letter != "A" && first_letter != ";") || cmptext(copytext(message,1,5), "say ") || cmptext(copytext(message,1,4), "me ") || cmptext(copytext(message,1,6), "looc ") || cmptext(copytext(message,1,5), "ooc ") || cmptext(copytext(message,2,6), "say ")) + var/response = alert(usr, "Do you really want to say this using the *say* verb?\n\n[message]\n", "Confirm your message", "Yes", "Edit message", "No") + if(response == "Edit message") + message = input(usr, "Please edit your message carefully:", "Edit message", message) + if(!message) + return + else if(response == "No") + return + */ + message = replace_characters(message, ILLEGAL_CHARACTERS_LIST) + set_typing_indicator(0) + usr.say(message) + + +/mob/verb/me_verb(message as text) + set name = "Me" + set category = "IC" + + message = strip_html_properly(message) + + set_typing_indicator(0) + if(use_me) + custom_emote(usr.emote_type, message) + else + usr.emote(message) + + +/mob/proc/say_dead(var/message) + if(!(client && client.holder)) + if(!config.dsay_allowed) + to_chat(src, "Deadchat is globally muted.") + return + + if(client && !(client.prefs.toggles & CHAT_DEAD)) + to_chat(usr, "You have deadchat muted.") + return + + say_dead_direct("[pick("complains", "moans", "whines", "laments", "blubbers", "salts")], \"[message]\"", src) + +/mob/proc/say_understands(var/mob/other, var/datum/language/speaking = null) + if(stat == DEAD) + return 1 + + //Universal speak makes everything understandable, for obvious reasons. + if(universal_speak || universal_understand) + return 1 + + //Languages are handled after. + if(!speaking) + if(!other) + return 1 + if(other.universal_speak) + return 1 + if(isAI(src) && ispAI(other)) + return 1 + if(istype(other, src.type) || istype(src, other.type)) + return 1 + return 0 + + if(speaking.flags & INNATE) + return 1 + + //Language check. + for(var/datum/language/L in languages) + if(speaking.name == L.name) + return 1 + + return 0 + + +/mob/proc/say_quote(var/message, var/datum/language/speaking = null) + var/verb = "says" + var/ending = copytext(message, length(message)) + + if(speaking) + verb = speaking.get_spoken_verb(ending) + else + if(ending == "!") + verb = pick("exclaims", "shouts", "yells") + else if(ending == "?") + verb = "asks" + return verb + + +/mob/proc/emote(act, type, message, force) + if(act == "me") + return custom_emote(type, message) + + +/mob/proc/get_ear() + // returns an atom representing a location on the map from which this + // mob can hear things + + // should be overloaded for all mobs whose "ear" is separate from their "mob" + + return get_turf(src) + +/mob/proc/say_test(var/text) + var/ending = copytext(text, length(text)) + if(ending == "?") + return "1" + else if(ending == "!") + return "2" + return "0" + +//parses the message mode code (e.g. :h, :w) from text, such as that supplied to say. +//returns the message mode string or null for no message mode. +//standard mode is the mode returned for the special ';' radio code. +/mob/proc/parse_message_mode(var/message, var/standard_mode = "headset") + if(length(message) >= 1 && copytext(message, 1, 2) == ";") + return standard_mode + + if(length(message) >= 2) + var/channel_prefix = copytext(message, 1 ,3) + return department_radio_keys[channel_prefix] + + return null + +/datum/multilingual_say_piece + var/datum/language/speaking = null + var/message = "" + +/datum/multilingual_say_piece/New(datum/language/new_speaking, new_message) + . = ..() + speaking = new_speaking + if(new_message) + message = new_message + +/mob/proc/find_valid_prefixes(message) + var/list/prefixes = list() // [["Common", start, end], ["Gutter", start, end]] + for(var/i in 1 to length(message)) + var/selection = trim_right(lowertext(copytext(message, i, i + 3))) + var/datum/language/L = GLOB.language_keys[selection] + if(L != null && can_speak_language(L)) // What the fuck... remove the L != null check if you ever find out what the fuck is adding `null` to the languages list on absolutely random mobs... seriously what the hell... + prefixes[++prefixes.len] = list(L, i, i + length(selection)) + else if(!L && i == 1) + prefixes[++prefixes.len] = list(get_default_language(), i, i) + else + return prefixes + +/proc/strip_prefixes(message) + . = "" + var/last_index = 1 + for(var/i in 1 to length(message)) + var/selection = trim_right(lowertext(copytext(message, i, i + 3))) + var/datum/language/L = GLOB.language_keys[selection] + if(L) + . += copytext(message, last_index, i) + last_index = i + 3 + if(i + 1 > length(message)) + . += copytext(message, last_index) + +// this returns a structured message with language sections +// list(/datum/multilingual_say_piece(common, "hi"), /datum/multilingual_say_piece(farwa, "squik"), /datum/multilingual_say_piece(common, "meow!")) +/mob/proc/parse_languages(message) + . = list() + + // Noise language is a snowflake + if(copytext(message, 1, 2) == "!" && length(message) > 1) + return list(new /datum/multilingual_say_piece(GLOB.all_languages["Noise"], trim(strip_prefixes(copytext(message, 2))))) + + // Scan the message for prefixes + var/list/prefix_locations = find_valid_prefixes(message) + if(!LAZYLEN(prefix_locations)) // There are no prefixes... or at least, no _valid_ prefixes. + . += new /datum/multilingual_say_piece(get_default_language(), trim(strip_prefixes(message))) // So we'll just strip those pesky things and still make the message. + + for(var/i in 1 to length(prefix_locations)) + var/current = prefix_locations[i] // ["Common", keypos] + + // There are a few things that will make us want to ignore all other languages in - namely, HIVEMIND languages. + var/datum/language/L = current[1] + if(L && L.flags & HIVEMIND) + . = new /datum/multilingual_say_piece(L, trim(strip_prefixes(message))) + break + + if(i + 1 > length(prefix_locations)) // We are out of lookaheads, that means the rest of the message is in cur lang + var/spoke_message = handle_autohiss(trim(copytext(message, current[3])), L) + . += new /datum/multilingual_say_piece(current[1], spoke_message) + else + var/next = prefix_locations[i + 1] // We look ahead at the next message to see where we need to stop. + var/spoke_message = handle_autohiss(trim(copytext(message, current[3], next[2])), L) + . += new /datum/multilingual_say_piece(current[1], spoke_message) + +/* These are here purely because it would be hell to try to convert everything over to using the multi-lingual system at once */ +/proc/message_to_multilingual(message, datum/language/speaking = null) + . = list(new /datum/multilingual_say_piece(speaking, message)) + +/proc/multilingual_to_message(list/message_pieces) + . = "" + for(var/datum/multilingual_say_piece/S in message_pieces) + . += S.message + " " + . = trim_right(.) + +#undef ILLEGAL_CHARACTERS_LIST diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm index 6716ea0e4a9..0aeb9beef84 100644 --- a/code/modules/mob/status_procs.dm +++ b/code/modules/mob/status_procs.dm @@ -204,4 +204,4 @@ /mob/proc/adjust_bodytemperature(amount, min_temp = 0, max_temp = INFINITY) if(bodytemperature >= min_temp && bodytemperature <= max_temp) - bodytemperature = Clamp(bodytemperature + amount, min_temp, max_temp) \ No newline at end of file + bodytemperature = Clamp(bodytemperature + amount, min_temp, max_temp) diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 52b3bb61b27..fe19d366550 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -1,301 +1,301 @@ -/mob/living/carbon/human/proc/monkeyize() - var/mob/H = src - H.dna.SetSEState(MONKEYBLOCK,1) - genemutcheck(H,MONKEYBLOCK,null,MUTCHK_FORCED) - -/mob/new_player/AIize() - spawning = 1 - return ..() - -/mob/living/carbon/AIize() - if(notransform) - return - for(var/obj/item/W in src) - unEquip(W) - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - return ..() - -/mob/proc/AIize() - if(client) - stop_sound_channel(CHANNEL_LOBBYMUSIC) - - var/mob/living/silicon/ai/O = new (loc,,,1)//No MMI but safety is in effect. - O.invisibility = 0 - O.aiRestorePowerRoutine = 0 - - if(mind) - mind.transfer_to(O) - O.mind.original = O - else - O.key = key - - O.on_mob_init() - - O.add_ai_verbs() - - O.rename_self("AI",1) - - spawn() - qdel(src) - return O - - - -//human -> robot -/mob/living/carbon/human/proc/Robotize() - if(notransform) - return - for(var/obj/item/W in src) - unEquip(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - for(var/t in bodyparts) - qdel(t) - for(var/i in internal_organs) - qdel(i) - - var/mob/living/silicon/robot/O = new /mob/living/silicon/robot( loc ) - - // cyborgs produced by Robotize get an automatic power cell - O.cell = new /obj/item/stock_parts/cell/high(O) - - O.gender = gender - O.invisibility = 0 - - if(mind) //TODO - mind.transfer_to(O) - if(O.mind.assigned_role == "Cyborg") - O.mind.original = O - else if(mind && mind.special_role) - O.mind.store_memory("In case you look at this after being borged, the objectives are only here until I find a way to make them not show up for you, as I can't simply delete them without screwing up round-end reporting. --NeoFite") - else - O.key = key - - O.loc = loc - O.job = "Cyborg" - O.notify_ai(1) - - if(O.mind && O.mind.assigned_role == "Cyborg") - if(O.mind.role_alt_title == "Android") - O.mmi = new /obj/item/mmi/robotic_brain(O) - else if(O.mind.role_alt_title == "Robot") - O.mmi = null //Robots do not have removable brains. - else - O.mmi = new /obj/item/mmi(O) - - if(O.mmi) O.mmi.transfer_identity(src) //Does not transfer key/client. - - O.update_pipe_vision() - - O.Namepick() - - spawn(0)//To prevent the proc from returning null. - qdel(src) - return O - -//human -> alien -/mob/living/carbon/human/proc/Alienize() - if(notransform) - return - for(var/obj/item/W in src) - unEquip(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - for(var/t in bodyparts) - qdel(t) - - var/alien_caste = pick("Hunter","Sentinel","Drone") - var/mob/living/carbon/alien/humanoid/new_xeno - switch(alien_caste) - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(loc) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(loc) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(loc) - - new_xeno.a_intent = INTENT_HARM - new_xeno.key = key - - to_chat(new_xeno, "You are now an alien.") - new_xeno.update_pipe_vision() - spawn(0)//To prevent the proc from returning null. - qdel(src) - return - -/mob/living/carbon/human/proc/slimeize(reproduce as num) - if(notransform) - return - notransform = TRUE - canmove = FALSE - for(var/obj/item/I in src) - unEquip(I) - regenerate_icons() - icon = null - invisibility = INVISIBILITY_MAXIMUM - for(var/t in bodyparts) - qdel(t) - - var/mob/living/simple_animal/slime/new_slime - if(reproduce) - var/number = pick(14;2,3,4) //reproduce (has a small chance of producing 3 or 4 offspring) - var/list/babies = list() - for(var/i=1,i<=number,i++) - var/mob/living/simple_animal/slime/M = new/mob/living/simple_animal/slime(loc) - M.set_nutrition(round(nutrition / number)) - step_away(M,src) - babies += M - new_slime = pick(babies) - else - new_slime = new /mob/living/simple_animal/slime(loc) - new_slime.a_intent = INTENT_HARM - new_slime.key = key - - to_chat(new_slime, "You are now a slime. Skreee!") - new_slime.update_pipe_vision() - . = new_slime - qdel(src) - -/mob/living/carbon/human/proc/corgize() - if(notransform) - return - for(var/obj/item/W in src) - unEquip(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - for(var/t in bodyparts) //this really should not be necessary - qdel(t) - - var/mob/living/simple_animal/pet/dog/corgi/new_corgi = new /mob/living/simple_animal/pet/dog/corgi (loc) - new_corgi.key = key - - to_chat(new_corgi, "You are now a Corgi. Yap Yap!") - new_corgi.update_pipe_vision() - spawn(0)//To prevent the proc from returning null. - qdel(src) - return - -/mob/living/carbon/human/Animalize() - - var/list/mobtypes = typesof(/mob/living/simple_animal) - var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes - - if(notransform) - return - for(var/obj/item/W in src) - unEquip(W) - - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - - for(var/t in bodyparts) - qdel(t) - - var/mob/new_mob = new mobpath(src.loc) - - new_mob.key = key - new_mob.a_intent = INTENT_HARM - - - to_chat(new_mob, "You suddenly feel more... animalistic.") - new_mob.update_pipe_vision() - spawn() - qdel(src) - return - -/mob/proc/Animalize() - - var/list/mobtypes = typesof(/mob/living/simple_animal) - var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes - - var/mob/new_mob = new mobpath(src.loc) - - new_mob.key = key - new_mob.a_intent = INTENT_HARM - to_chat(new_mob, "You feel more... animalistic") - new_mob.update_pipe_vision() - - qdel(src) - - -/mob/living/carbon/human/proc/paize(var/name) - if(notransform) - return - for(var/obj/item/W in src) - unEquip(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = 101 - for(var/t in bodyparts) //this really should not be necessary - qdel(t) - - var/obj/item/paicard/card = new(loc) - var/mob/living/silicon/pai/pai = new(card) - pai.key = key - card.setPersonality(pai) - - pai.name = name - pai.real_name = name - card.name = name - - to_chat(pai, "You have become a pAI! Your name is [pai.name].") - pai.update_pipe_vision() - spawn(0)//To prevent the proc from returning null. - qdel(src) - return - -/mob/proc/safe_respawn(var/MP) - if(!MP) - return 0 - - if(!GAMEMODE_IS_NUCLEAR) - if(ispath(MP, /mob/living/simple_animal/pet/cat/Syndi)) - return 0 - if(ispath(MP, /mob/living/simple_animal/pet/cat)) - return 1 - if(ispath(MP, /mob/living/simple_animal/pet/dog/corgi)) - return 1 - if(ispath(MP, /mob/living/simple_animal/crab)) - return 1 - if(ispath(MP, /mob/living/simple_animal/chicken)) - return 1 - if(ispath(MP, /mob/living/simple_animal/cow)) - return 1 - if(ispath(MP, /mob/living/simple_animal/parrot)) - return 1 - if(!GAMEMODE_IS_NUCLEAR) - if(ispath(MP, /mob/living/simple_animal/pet/dog/fox/Syndifox)) - return 0 - if(ispath(MP, /mob/living/simple_animal/pet/dog/fox)) - return 1 - if(ispath(MP, /mob/living/simple_animal/chick)) - return 1 - if(ispath(MP, /mob/living/simple_animal/pet/dog/pug)) - return 1 - if(ispath(MP, /mob/living/simple_animal/butterfly)) - return 1 - - if(ispath(MP, /mob/living/simple_animal/borer) && !jobban_isbanned(src, ROLE_BORER) && !jobban_isbanned(src, "Syndicate")) - return 1 - - if(ispath(MP, /mob/living/simple_animal/diona) && !jobban_isbanned(src, ROLE_NYMPH)) - return 1 - - return 0 +/mob/living/carbon/human/proc/monkeyize() + var/mob/H = src + H.dna.SetSEState(MONKEYBLOCK,1) + genemutcheck(H,MONKEYBLOCK,null,MUTCHK_FORCED) + +/mob/new_player/AIize() + spawning = 1 + return ..() + +/mob/living/carbon/AIize() + if(notransform) + return + for(var/obj/item/W in src) + unEquip(W) + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + return ..() + +/mob/proc/AIize() + if(client) + stop_sound_channel(CHANNEL_LOBBYMUSIC) + + var/mob/living/silicon/ai/O = new (loc,,,1)//No MMI but safety is in effect. + O.invisibility = 0 + O.aiRestorePowerRoutine = 0 + + if(mind) + mind.transfer_to(O) + O.mind.original = O + else + O.key = key + + O.on_mob_init() + + O.add_ai_verbs() + + O.rename_self("AI",1) + + spawn() + qdel(src) + return O + + + +//human -> robot +/mob/living/carbon/human/proc/Robotize() + if(notransform) + return + for(var/obj/item/W in src) + unEquip(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + for(var/t in bodyparts) + qdel(t) + for(var/i in internal_organs) + qdel(i) + + var/mob/living/silicon/robot/O = new /mob/living/silicon/robot( loc ) + + // cyborgs produced by Robotize get an automatic power cell + O.cell = new /obj/item/stock_parts/cell/high(O) + + O.gender = gender + O.invisibility = 0 + + if(mind) //TODO + mind.transfer_to(O) + if(O.mind.assigned_role == "Cyborg") + O.mind.original = O + else if(mind && mind.special_role) + O.mind.store_memory("In case you look at this after being borged, the objectives are only here until I find a way to make them not show up for you, as I can't simply delete them without screwing up round-end reporting. --NeoFite") + else + O.key = key + + O.loc = loc + O.job = "Cyborg" + O.notify_ai(1) + + if(O.mind && O.mind.assigned_role == "Cyborg") + if(O.mind.role_alt_title == "Android") + O.mmi = new /obj/item/mmi/robotic_brain(O) + else if(O.mind.role_alt_title == "Robot") + O.mmi = null //Robots do not have removable brains. + else + O.mmi = new /obj/item/mmi(O) + + if(O.mmi) O.mmi.transfer_identity(src) //Does not transfer key/client. + + O.update_pipe_vision() + + O.Namepick() + + spawn(0)//To prevent the proc from returning null. + qdel(src) + return O + +//human -> alien +/mob/living/carbon/human/proc/Alienize() + if(notransform) + return + for(var/obj/item/W in src) + unEquip(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + for(var/t in bodyparts) + qdel(t) + + var/alien_caste = pick("Hunter","Sentinel","Drone") + var/mob/living/carbon/alien/humanoid/new_xeno + switch(alien_caste) + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(loc) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(loc) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(loc) + + new_xeno.a_intent = INTENT_HARM + new_xeno.key = key + + to_chat(new_xeno, "You are now an alien.") + new_xeno.update_pipe_vision() + spawn(0)//To prevent the proc from returning null. + qdel(src) + return + +/mob/living/carbon/human/proc/slimeize(reproduce as num) + if(notransform) + return + notransform = TRUE + canmove = FALSE + for(var/obj/item/I in src) + unEquip(I) + regenerate_icons() + icon = null + invisibility = INVISIBILITY_MAXIMUM + for(var/t in bodyparts) + qdel(t) + + var/mob/living/simple_animal/slime/new_slime + if(reproduce) + var/number = pick(14;2,3,4) //reproduce (has a small chance of producing 3 or 4 offspring) + var/list/babies = list() + for(var/i=1,i<=number,i++) + var/mob/living/simple_animal/slime/M = new/mob/living/simple_animal/slime(loc) + M.set_nutrition(round(nutrition / number)) + step_away(M,src) + babies += M + new_slime = pick(babies) + else + new_slime = new /mob/living/simple_animal/slime(loc) + new_slime.a_intent = INTENT_HARM + new_slime.key = key + + to_chat(new_slime, "You are now a slime. Skreee!") + new_slime.update_pipe_vision() + . = new_slime + qdel(src) + +/mob/living/carbon/human/proc/corgize() + if(notransform) + return + for(var/obj/item/W in src) + unEquip(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + for(var/t in bodyparts) //this really should not be necessary + qdel(t) + + var/mob/living/simple_animal/pet/dog/corgi/new_corgi = new /mob/living/simple_animal/pet/dog/corgi (loc) + new_corgi.key = key + + to_chat(new_corgi, "You are now a Corgi. Yap Yap!") + new_corgi.update_pipe_vision() + spawn(0)//To prevent the proc from returning null. + qdel(src) + return + +/mob/living/carbon/human/Animalize() + + var/list/mobtypes = typesof(/mob/living/simple_animal) + var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes + + if(notransform) + return + for(var/obj/item/W in src) + unEquip(W) + + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + + for(var/t in bodyparts) + qdel(t) + + var/mob/new_mob = new mobpath(src.loc) + + new_mob.key = key + new_mob.a_intent = INTENT_HARM + + + to_chat(new_mob, "You suddenly feel more... animalistic.") + new_mob.update_pipe_vision() + spawn() + qdel(src) + return + +/mob/proc/Animalize() + + var/list/mobtypes = typesof(/mob/living/simple_animal) + var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") in mobtypes + + var/mob/new_mob = new mobpath(src.loc) + + new_mob.key = key + new_mob.a_intent = INTENT_HARM + to_chat(new_mob, "You feel more... animalistic") + new_mob.update_pipe_vision() + + qdel(src) + + +/mob/living/carbon/human/proc/paize(var/name) + if(notransform) + return + for(var/obj/item/W in src) + unEquip(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = 101 + for(var/t in bodyparts) //this really should not be necessary + qdel(t) + + var/obj/item/paicard/card = new(loc) + var/mob/living/silicon/pai/pai = new(card) + pai.key = key + card.setPersonality(pai) + + pai.name = name + pai.real_name = name + card.name = name + + to_chat(pai, "You have become a pAI! Your name is [pai.name].") + pai.update_pipe_vision() + spawn(0)//To prevent the proc from returning null. + qdel(src) + return + +/mob/proc/safe_respawn(var/MP) + if(!MP) + return 0 + + if(!GAMEMODE_IS_NUCLEAR) + if(ispath(MP, /mob/living/simple_animal/pet/cat/Syndi)) + return 0 + if(ispath(MP, /mob/living/simple_animal/pet/cat)) + return 1 + if(ispath(MP, /mob/living/simple_animal/pet/dog/corgi)) + return 1 + if(ispath(MP, /mob/living/simple_animal/crab)) + return 1 + if(ispath(MP, /mob/living/simple_animal/chicken)) + return 1 + if(ispath(MP, /mob/living/simple_animal/cow)) + return 1 + if(ispath(MP, /mob/living/simple_animal/parrot)) + return 1 + if(!GAMEMODE_IS_NUCLEAR) + if(ispath(MP, /mob/living/simple_animal/pet/dog/fox/Syndifox)) + return 0 + if(ispath(MP, /mob/living/simple_animal/pet/dog/fox)) + return 1 + if(ispath(MP, /mob/living/simple_animal/chick)) + return 1 + if(ispath(MP, /mob/living/simple_animal/pet/dog/pug)) + return 1 + if(ispath(MP, /mob/living/simple_animal/butterfly)) + return 1 + + if(ispath(MP, /mob/living/simple_animal/borer) && !jobban_isbanned(src, ROLE_BORER) && !jobban_isbanned(src, "Syndicate")) + return 1 + + if(ispath(MP, /mob/living/simple_animal/diona) && !jobban_isbanned(src, ROLE_NYMPH)) + return 1 + + return 0 diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm index 9ad5449ee8b..1dd4c9f8ccb 100644 --- a/code/modules/mob/typing_indicator.dm +++ b/code/modules/mob/typing_indicator.dm @@ -90,4 +90,4 @@ var/global/image/typing_indicator if(prefs.toggles & SHOW_TYPING) if(istype(mob)) mob.set_typing_indicator(0) - feedback_add_details("admin_verb","TID") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! \ No newline at end of file + feedback_add_details("admin_verb","TID") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/mob/update_icons.dm b/code/modules/mob/update_icons.dm index 8a1f1b04cb1..0efe10b40cf 100644 --- a/code/modules/mob/update_icons.dm +++ b/code/modules/mob/update_icons.dm @@ -1,68 +1,68 @@ -//Most of these are defined at this level to reduce on checks elsewhere in the code. -//Having them here also makes for a nice reference list of the various overlay-updating procs available - -/mob/proc/regenerate_icons() //TODO: phase this out completely if possible - return - -/mob/proc/update_icons() - return - -/mob/proc/update_inv_handcuffed() - return - -/mob/proc/update_inv_legcuffed() - return - -/mob/proc/update_inv_back() - return - -/mob/proc/update_inv_l_hand() - return - -/mob/proc/update_inv_r_hand() - return - -/mob/proc/update_inv_wear_mask() - return - -/mob/proc/update_inv_wear_suit() - return - -/mob/proc/update_inv_w_uniform() - return - -/mob/proc/update_inv_belt() - return - -/mob/proc/update_inv_head() - return - -/mob/proc/update_inv_gloves() - return - -/mob/proc/update_mutations() - return - -/mob/proc/update_inv_wear_id() - return - -/mob/proc/update_inv_shoes() - return - -/mob/proc/update_inv_glasses() - return - -/mob/proc/update_inv_s_store() - return - -/mob/proc/update_inv_pockets() - return - -/mob/proc/update_inv_wear_pda() - return - -/mob/proc/update_inv_ears() - return - -/mob/proc/update_transform() - return \ No newline at end of file +//Most of these are defined at this level to reduce on checks elsewhere in the code. +//Having them here also makes for a nice reference list of the various overlay-updating procs available + +/mob/proc/regenerate_icons() //TODO: phase this out completely if possible + return + +/mob/proc/update_icons() + return + +/mob/proc/update_inv_handcuffed() + return + +/mob/proc/update_inv_legcuffed() + return + +/mob/proc/update_inv_back() + return + +/mob/proc/update_inv_l_hand() + return + +/mob/proc/update_inv_r_hand() + return + +/mob/proc/update_inv_wear_mask() + return + +/mob/proc/update_inv_wear_suit() + return + +/mob/proc/update_inv_w_uniform() + return + +/mob/proc/update_inv_belt() + return + +/mob/proc/update_inv_head() + return + +/mob/proc/update_inv_gloves() + return + +/mob/proc/update_mutations() + return + +/mob/proc/update_inv_wear_id() + return + +/mob/proc/update_inv_shoes() + return + +/mob/proc/update_inv_glasses() + return + +/mob/proc/update_inv_s_store() + return + +/mob/proc/update_inv_pockets() + return + +/mob/proc/update_inv_wear_pda() + return + +/mob/proc/update_inv_ears() + return + +/mob/proc/update_transform() + return diff --git a/code/modules/modular_computers/computers/item/laptop_presets.dm b/code/modules/modular_computers/computers/item/laptop_presets.dm index e492712d6e4..43720dfa2fc 100644 --- a/code/modules/modular_computers/computers/item/laptop_presets.dm +++ b/code/modules/modular_computers/computers/item/laptop_presets.dm @@ -18,4 +18,4 @@ /obj/item/modular_computer/laptop/preset/civillian/install_programs() var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD] hard_drive.store_file(new/datum/computer_file/program/chatclient()) - hard_drive.store_file(new/datum/computer_file/program/nttransfer()) \ No newline at end of file + hard_drive.store_file(new/datum/computer_file/program/nttransfer()) diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm index 417b98f15bf..7bd5be3cf81 100644 --- a/code/modules/modular_computers/computers/item/processor.dm +++ b/code/modules/modular_computers/computers/item/processor.dm @@ -71,4 +71,4 @@ if(MC_SDD) machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_disk if(MC_AI) - machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_card \ No newline at end of file + machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_card diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm index 7547c797e25..f0a044b1d36 100644 --- a/code/modules/modular_computers/computers/item/tablet.dm +++ b/code/modules/modular_computers/computers/item/tablet.dm @@ -9,4 +9,4 @@ max_hardware_size = 1 w_class = WEIGHT_CLASS_SMALL steel_sheet_cost = 1 - slot_flags = SLOT_ID | SLOT_BELT \ No newline at end of file + slot_flags = SLOT_ID | SLOT_BELT diff --git a/code/modules/modular_computers/computers/machinery/console_presets.dm b/code/modules/modular_computers/computers/machinery/console_presets.dm index 5dd324945fa..4f1bd269337 100644 --- a/code/modules/modular_computers/computers/machinery/console_presets.dm +++ b/code/modules/modular_computers/computers/machinery/console_presets.dm @@ -73,4 +73,4 @@ /obj/machinery/modular_computer/console/preset/civilian/install_programs() var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD] hard_drive.store_file(new/datum/computer_file/program/chatclient()) - hard_drive.store_file(new/datum/computer_file/program/nttransfer()) \ No newline at end of file + hard_drive.store_file(new/datum/computer_file/program/nttransfer()) diff --git a/code/modules/modular_computers/computers/machinery/modular_console.dm b/code/modules/modular_computers/computers/machinery/modular_console.dm index aeb4c18bc2d..e16ce82702c 100644 --- a/code/modules/modular_computers/computers/machinery/modular_console.dm +++ b/code/modules/modular_computers/computers/machinery/modular_console.dm @@ -50,4 +50,4 @@ if(cpu) cpu.screen_on = 1 - update_icon() \ No newline at end of file + update_icon() diff --git a/code/modules/modular_computers/file_system/computer_file.dm b/code/modules/modular_computers/file_system/computer_file.dm index f3b5a9659b2..a93aaf2412e 100644 --- a/code/modules/modular_computers/file_system/computer_file.dm +++ b/code/modules/modular_computers/file_system/computer_file.dm @@ -47,4 +47,4 @@ var/global/file_uid = 0 input_password = sanitize(input(user, "Please enter a password to access file '[filename]':")) if(input_password == password) return TRUE - return FALSE \ No newline at end of file + return FALSE diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm index 8aa80631641..22b0fa1a511 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm @@ -100,4 +100,4 @@ data["relays"] += list(list("id" = R.uid)) data["focus"] = target ? target.uid : null - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm index 22df710c6f8..42f722af799 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm @@ -71,4 +71,4 @@ data["armed"] = armed - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/command/card.dm b/code/modules/modular_computers/file_system/programs/command/card.dm index 686d3a87f6e..f211ee6fd49 100644 --- a/code/modules/modular_computers/file_system/programs/command/card.dm +++ b/code/modules/modular_computers/file_system/programs/command/card.dm @@ -481,4 +481,4 @@ data["regions"] = regions - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/command/comms.dm b/code/modules/modular_computers/file_system/programs/command/comms.dm index c471d49a039..eb6f079d078 100644 --- a/code/modules/modular_computers/file_system/programs/command/comms.dm +++ b/code/modules/modular_computers/file_system/programs/command/comms.dm @@ -382,4 +382,4 @@ to_chat(usr, "Nano-Mob Hunter GO! game server is offline for extended maintenance. Contact your Central Command administrators for more info if desired.") SSnanoui.update_uis(src) - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/modular_computers/file_system/programs/engineering/alarm.dm b/code/modules/modular_computers/file_system/programs/engineering/alarm.dm index 226511e2c09..8cb47337a6d 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/alarm.dm @@ -68,4 +68,4 @@ "lost_sources" = lost_sources.len ? sanitize(english_list(lost_sources, nothing_text = "", and_text = ", ")) : "")) data["categories"] = categories - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm b/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm index c30c7dbbd1e..0695c496541 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm @@ -45,4 +45,4 @@ data["powerdemand"] = powernet.load data["apcs"] = apc_repository.apc_data(powernet) - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/generic/file_browser.dm b/code/modules/modular_computers/file_system/programs/generic/file_browser.dm index 23b2cdab7ab..e7edc437639 100644 --- a/code/modules/modular_computers/file_system/programs/generic/file_browser.dm +++ b/code/modules/modular_computers/file_system/programs/generic/file_browser.dm @@ -225,4 +225,4 @@ if(!F || F.undeletable) return 1 if(F.can_access_file(usr)) - F.password = "" \ No newline at end of file + F.password = "" diff --git a/code/modules/modular_computers/file_system/programs/generic/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/generic/ntdownloader.dm index d8278081ffa..c0447b591bf 100644 --- a/code/modules/modular_computers/file_system/programs/generic/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/generic/ntdownloader.dm @@ -175,4 +175,4 @@ /datum/computer_file/program/ntnetdownload/kill_program(forced) abort_file_download() - return ..(forced) \ No newline at end of file + return ..(forced) diff --git a/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm b/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm index ef26a35730c..184b2cf300f 100644 --- a/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm +++ b/code/modules/modular_computers/file_system/programs/generic/nttransfer.dm @@ -192,4 +192,4 @@ var/global/nttransfer_uid = 0 ))) data["servers"] = all_servers - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/research/airestorer.dm b/code/modules/modular_computers/file_system/programs/research/airestorer.dm index b45245a3441..d5ff31af66b 100644 --- a/code/modules/modular_computers/file_system/programs/research/airestorer.dm +++ b/code/modules/modular_computers/file_system/programs/research/airestorer.dm @@ -134,4 +134,4 @@ /datum/computer_file/program/aidiag/kill_program(forced) restoring = FALSE - return ..(forced) \ No newline at end of file + return ..(forced) diff --git a/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm index 1538fe3cc01..6f3f41df272 100644 --- a/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm +++ b/code/modules/modular_computers/file_system/programs/research/ntmonitor.dm @@ -87,4 +87,4 @@ data["ntnetlogs"] += list(list("entry" = i)) data["ntnetmaxlogs"] = ntnet_global.setting_maxlogcount - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/hardware/CPU.dm b/code/modules/modular_computers/hardware/CPU.dm index fa6443240ec..02e7d41d512 100644 --- a/code/modules/modular_computers/hardware/CPU.dm +++ b/code/modules/modular_computers/hardware/CPU.dm @@ -41,4 +41,4 @@ w_class = WEIGHT_CLASS_TINY power_usage = 75 max_idle_programs = 2 - origin_tech = "programming=4;engineering=3" \ No newline at end of file + origin_tech = "programming=4;engineering=3" diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm index 1a112b248a5..1b97ae5ede8 100644 --- a/code/modules/modular_computers/hardware/ai_slot.dm +++ b/code/modules/modular_computers/hardware/ai_slot.dm @@ -77,4 +77,4 @@ if(istype(I, /obj/item/screwdriver)) to_chat(user, "You press down on the manual eject button with \the [I].") try_eject(0, user, 1) - return \ No newline at end of file + return diff --git a/code/modules/modular_computers/hardware/battery_module.dm b/code/modules/modular_computers/hardware/battery_module.dm index 98f1ab5667e..7cec597dc13 100644 --- a/code/modules/modular_computers/hardware/battery_module.dm +++ b/code/modules/modular_computers/hardware/battery_module.dm @@ -106,4 +106,4 @@ desc = "A tiny power cell, commonly seen in low-end portable microcomputers." icon_state = "cell_micro" w_class = WEIGHT_CLASS_TINY - maxcharge = 300 \ No newline at end of file + maxcharge = 300 diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm index 82c4aa5f62c..ce7fc997192 100644 --- a/code/modules/modular_computers/hardware/hard_drive.dm +++ b/code/modules/modular_computers/hardware/hard_drive.dm @@ -170,4 +170,4 @@ origin_tech = "programming=1;engineering=1" max_capacity = 32 icon_state = "ssd_micro" - w_class = WEIGHT_CLASS_TINY \ No newline at end of file + w_class = WEIGHT_CLASS_TINY diff --git a/code/modules/modular_computers/hardware/printer.dm b/code/modules/modular_computers/hardware/printer.dm index 0db45f33e6e..49c5d72a6f1 100644 --- a/code/modules/modular_computers/hardware/printer.dm +++ b/code/modules/modular_computers/hardware/printer.dm @@ -67,4 +67,4 @@ icon_state = "printer_mini" w_class = WEIGHT_CLASS_TINY stored_paper = 5 - max_paper = 15 \ No newline at end of file + max_paper = 15 diff --git a/code/modules/nano/interaction/not_incapacitated.dm b/code/modules/nano/interaction/not_incapacitated.dm index a0116409c90..db12edf3119 100644 --- a/code/modules/nano/interaction/not_incapacitated.dm +++ b/code/modules/nano/interaction/not_incapacitated.dm @@ -22,4 +22,4 @@ return STATUS_CLOSE if(user.incapacitated() || user.lying || (turf_check && !isturf(user.loc))) return STATUS_DISABLED - return STATUS_INTERACTIVE \ No newline at end of file + return STATUS_INTERACTIVE diff --git a/code/modules/nano/nanomapgen.dm b/code/modules/nano/nanomapgen.dm index 10bf8f288a6..be9c5bbe2b3 100644 --- a/code/modules/nano/nanomapgen.dm +++ b/code/modules/nano/nanomapgen.dm @@ -87,4 +87,4 @@ if(Tile.Width() != NANOMAP_MAX_ICON_DIMENSION || Tile.Height() != NANOMAP_MAX_ICON_DIMENSION) return NANOMAP_BADOUTPUT - return NANOMAP_SUCCESS \ No newline at end of file + return NANOMAP_SUCCESS diff --git a/code/modules/ninja/Ninja_Readme.dm b/code/modules/ninja/Ninja_Readme.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/Ninja_Readme.dm +++ b/code/modules/ninja/Ninja_Readme.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/__ninjaDefines.dm b/code/modules/ninja/__ninjaDefines.dm index a8dc65f9f13..4c3562273cc 100644 --- a/code/modules/ninja/__ninjaDefines.dm +++ b/code/modules/ninja/__ninjaDefines.dm @@ -10,4 +10,4 @@ Contents: //ninjacost() specificCheck defines #define N_STEALTH_CANCEL 1 #define N_SMOKE_BOMB 2 -#define N_ADRENALINE 3 \ No newline at end of file +#define N_ADRENALINE 3 diff --git a/code/modules/ninja/admin_ninja_verbs.dm b/code/modules/ninja/admin_ninja_verbs.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/admin_ninja_verbs.dm +++ b/code/modules/ninja/admin_ninja_verbs.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/energy_katana.dm b/code/modules/ninja/energy_katana.dm index ee851253449..cf694e59d83 100644 --- a/code/modules/ninja/energy_katana.dm +++ b/code/modules/ninja/energy_katana.dm @@ -75,4 +75,4 @@ /obj/item/katana/energy/Destroy() QDEL_NULL(spark_system) - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/ninja/ninja_event.dm b/code/modules/ninja/ninja_event.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/ninja_event.dm +++ b/code/modules/ninja/ninja_event.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/n_suit_verbs/energy_net_nets.dm b/code/modules/ninja/suit/n_suit_verbs/energy_net_nets.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/n_suit_verbs/energy_net_nets.dm +++ b/code/modules/ninja/suit/n_suit_verbs/energy_net_nets.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_cost_check.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_cost_check.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_cost_check.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_cost_check.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_empulse.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_empulse.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_empulse.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_empulse.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_net.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_net.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_net.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_net.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_smoke.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_smoke.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_smoke.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_smoke.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm index c7ca781c04e..54a7f4e4206 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm @@ -20,4 +20,4 @@ /obj/item/gun/energy/shuriken/proc/throw() qdel(src) - return \ No newline at end of file + return diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_teleporting.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_teleporting.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_teleporting.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_teleporting.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/ninja/suit/suit_process.dm b/code/modules/ninja/suit/suit_process.dm index 0ffdd02fcbc..70b786d12ed 100644 --- a/code/modules/ninja/suit/suit_process.dm +++ b/code/modules/ninja/suit/suit_process.dm @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/code/modules/paperwork/contract.dm b/code/modules/paperwork/contract.dm index c32e1fc8a88..25e72610237 100644 --- a/code/modules/paperwork/contract.dm +++ b/code/modules/paperwork/contract.dm @@ -329,4 +329,4 @@ if(!istype(user) || !user.mind) return -1 user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_friend(null)) - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm index df90d086368..2f3088c5484 100644 --- a/code/modules/paperwork/faxmachine.dm +++ b/code/modules/paperwork/faxmachine.dm @@ -344,4 +344,4 @@ var/global/list/fax_blacklist = list() if(scan) scan.forceMove(get_turf(src)) var/mob/living/simple_animal/hostile/mimic/copy/M = new(loc, src, null, 1) // it will delete src on creation and override any machine checks - M.name = "angry fax machine" \ No newline at end of file + M.name = "angry fax machine" diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 75d6d9084ca..46376c1828d 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -725,4 +725,4 @@ var/mytech = pick(possible_techs) var/mylevel = rand(7, 9) origin_tech = "[mytech]=[mylevel]" - name = "research notes - [mytech] [mylevel]" \ No newline at end of file + name = "research notes - [mytech] [mylevel]" diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 4129ddf30ad..159b224e384 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -1,580 +1,580 @@ -/* Photography! - * Contains: - * Camera - * Camera Film - * Photos - * Photo Albums - */ - -/******* -* film * -*******/ -/obj/item/camera_film - name = "film cartridge" - icon = 'icons/obj/items.dmi' - desc = "A camera film cartridge. Insert it into a camera to reload it." - icon_state = "film" - item_state = "electropack" - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - - -/******** -* photo * -********/ -/obj/item/photo - name = "photo" - icon = 'icons/obj/items.dmi' - icon_state = "photo" - item_state = "paper" - w_class = WEIGHT_CLASS_SMALL - resistance_flags = FLAMMABLE - max_integrity = 50 - var/blueprints = 0 // Does this have the blueprints? - var/icon/img //Big photo image - var/scribble //Scribble on the back. - var/icon/tiny - var/photo_size = 3 - -/obj/item/photo/attack_self(mob/user as mob) - user.examinate(src) - -/obj/item/photo/attackby(obj/item/P as obj, mob/user as mob, params) - if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) - var/txt = sanitize(input(user, "What would you like to write on the back?", "Photo Writing", null) as text) - txt = copytext(txt, 1, 128) - if(loc == user && user.stat == 0) - scribble = txt - else if(istype(P, /obj/item/lighter)) - burnphoto(P, user) - ..() - -/obj/item/photo/proc/burnphoto(obj/item/lighter/P, mob/user) - var/class = "" - - if(P.lit && !user.restrained()) - if(istype(P, /obj/item/lighter/zippo)) - class = "" - - user.visible_message("[class][user] holds \the [P] up to \the [src], it looks like [user.p_theyre()] trying to burn it!", \ - "[class]You hold [P] up to [src], burning it slowly.") - - spawn(20) - if(get_dist(src, user) < 2 && user.get_active_hand() == P && P.lit) - user.visible_message("[class][user] burns right through \the [src], turning it to ash. It flutters through the air before settling on the floor in a heap.", \ - "[class]You burn right through \the [src], turning it to ash. It flutters through the air before settling on the floor in a heap.") - - if(user.is_in_inactive_hand(src)) - user.unEquip(src) - - new /obj/effect/decal/cleanable/ash(get_turf(src)) - qdel(src) - - else - to_chat(user, "You must hold \the [P] steady to burn \the [src].") - -/obj/item/photo/examine(mob/user) - . = ..() - if(in_range(user, src) || isobserver(user)) - show(user) - else - . += "It is too far away." - -/obj/item/photo/proc/show(mob/user as mob) - var/icon/img_shown = new/icon(img) - var/colormatrix = user.get_screen_colour() - // Apply colorblindness effects, if any. - if(islist(colormatrix)) - img_shown.MapColors( - colormatrix[1], colormatrix[2], colormatrix[3], - colormatrix[4], colormatrix[5], colormatrix[6], - colormatrix[7], colormatrix[8], colormatrix[9], - ) - usr << browse_rsc(img_shown, "tmp_photo.png") - usr << browse("[name]" \ - + "" \ - + "" \ - + "[scribble ? "
        Written on the back:
        [scribble]" : ""]"\ - + "", "window=Photo[UID()];size=[64*photo_size]x[scribble ? 400 : 64*photo_size]") - onclose(usr, "Photo[UID()]") - return - -/obj/item/photo/verb/rename() - set name = "Rename photo" - set category = "Object" - set src in usr - - var/n_name = sanitize(copytext(input(usr, "What would you like to label the photo?", "Photo Labelling", name) as text, 1, MAX_MESSAGE_LEN)) - //loc.loc check is for making possible renaming photos in clipboards - if(( (loc == usr || (loc.loc && loc.loc == usr)) && usr.stat == 0)) - name = "[(n_name ? text("[n_name]") : "photo")]" - add_fingerprint(usr) - return - - -/************** -* photo album * -**************/ -/obj/item/storage/photo_album - name = "Photo album" - icon = 'icons/obj/items.dmi' - icon_state = "album" - item_state = "briefcase" - can_hold = list(/obj/item/photo) - resistance_flags = FLAMMABLE - -/obj/item/storage/photo_album/MouseDrop(obj/over_object as obj) - - if((istype(usr, /mob/living/carbon/human))) - var/mob/M = usr - if(!( istype(over_object, /obj/screen) )) - return ..() - playsound(loc, "rustle", 50, 1, -5) - if((!( M.restrained() ) && !( M.stat ) && M.back == src)) - switch(over_object.name) - if("r_hand") - M.unEquip(src) - M.put_in_r_hand(src) - if("l_hand") - M.unEquip(src) - M.put_in_l_hand(src) - add_fingerprint(usr) - return - if(over_object == usr && in_range(src, usr) || usr.contents.Find(src)) - if(usr.s_active) - usr.s_active.close(usr) - show_to(usr) - return - return - -/********* -* camera * -*********/ -/obj/item/camera - name = "camera" - icon = 'icons/obj/items.dmi' - desc = "A polaroid camera. 10 photos left." - icon_state = "camera" - item_state = "electropack" - w_class = WEIGHT_CLASS_SMALL - slot_flags = SLOT_BELT - var/list/matter = list("metal" = 2000) - var/pictures_max = 10 - var/pictures_left = 10 - var/on = 1 - var/blueprints = 0 - var/icon_on = "camera" - var/icon_off = "camera_off" - var/size = 3 - var/see_ghosts = 0 //for the spoop of it - - -/obj/item/camera/spooky/CheckParts(list/parts_list) - ..() - var/obj/item/camera/C = locate(/obj/item/camera) in contents - if(C) - pictures_max = C.pictures_max - pictures_left = C.pictures_left - visible_message("[C] has been imbued with godlike power!") - qdel(C) - - -var/list/SpookyGhosts = list("ghost","shade","shade2","ghost-narsie","horror","shadow","ghostian2") - -/obj/item/camera/spooky - name = "camera obscura" - desc = "A polaroid camera, some say it can see ghosts!" - see_ghosts = 1 - -/obj/item/camera/verb/change_size() - set name = "Set Photo Focus" - set category = "Object" - var/nsize = input("Photo Size","Pick a size of resulting photo.") as null|anything in list(1,3,5,7) - if(nsize) - size = nsize - to_chat(usr, "Camera will now take [size]x[size] photos.") - -/obj/item/camera/attack(mob/living/carbon/human/M as mob, mob/user as mob) - return - -/obj/item/camera/attack_self(mob/user as mob) - on = !on - if(on) - src.icon_state = icon_on - else - src.icon_state = icon_off - to_chat(user, "You switch the camera [on ? "on" : "off"].") - return - -/obj/item/camera/attackby(obj/item/I as obj, mob/user as mob, params) - if(istype(I, /obj/item/camera_film)) - if(pictures_left) - to_chat(user, "[src] still has some film in it!") - return - to_chat(user, "You insert [I] into [src].") - user.drop_item() - qdel(I) - pictures_left = pictures_max - return - ..() - - -/obj/item/camera/proc/get_icon(list/turfs, turf/center, mob/user) - - //Bigger icon base to capture those icons that were shifted to the next tile - //i.e. pretty much all wall-mounted machinery - var/icon/res = icon('icons/effects/96x96.dmi', "") - res.Scale(size*32, size*32) - // Initialize the photograph to black. - res.Blend("#000", ICON_OVERLAY) - - var/atoms[] = list() - for(var/turf/the_turf in turfs) - // Add ourselves to the list of stuff to draw - atoms.Add(the_turf); - // As well as anything that isn't invisible. - for(var/atom/A in the_turf) - if(A.invisibility) - if(see_ghosts && istype(A,/mob/dead/observer)) - var/mob/dead/observer/O = A - if(O.following) - continue - if(user.mind && !(user.mind.assigned_role == "Chaplain")) - atoms.Add(image('icons/mob/mob.dmi', O.loc, pick(SpookyGhosts), 4, SOUTH)) - else - atoms.Add(image('icons/mob/mob.dmi', O.loc, "ghost", 4, SOUTH)) - else//its not a ghost - continue - else//not invisable, not a spookyghost add it. - var/disguised = null - if(user.viewing_alternate_appearances && user.viewing_alternate_appearances.len && ishuman(A) && A.alternate_appearances && A.alternate_appearances.len) //This whole thing and the stuff below just checks if the atom is a Solid Snake cosplayer. - for(var/datum/alternate_appearance/alt_appearance in user.viewing_alternate_appearances) - if(alt_appearance.owner == A) //If it turns out they are, don't blow their cover. That'd be rude. - atoms.Add(image(alt_appearance.img, A.loc, layer = 4, dir = A.dir)) //Render their disguise. - atoms.Remove(A) //Don't blow their cover. - disguised = 1 - continue - if(!disguised) //If they aren't, treat them normally. - atoms.Add(A) - - - // Sort the atoms into their layers - var/list/sorted = sort_atoms_by_layer(atoms) - var/center_offset = (size-1)/2 * 32 + 1 - for(var/i; i <= sorted.len; i++) - var/atom/A = sorted[i] - if(A) - var/icon/img = getFlatIcon(A)//build_composite_icon(A) - if(istype(A, /obj/item/areaeditor/blueprints)) - blueprints = 1 - - // If what we got back is actually a picture, draw it. - if(istype(img, /icon)) - // Check if we're looking at a mob that's lying down - if(istype(A, /mob/living) && A:lying) - // If they are, apply that effect to their picture. - img.BecomeLying() - // Calculate where we are relative to the center of the photo - var/xoff = (A.x - center.x) * 32 + center_offset - var/yoff = (A.y - center.y) * 32 + center_offset - if(istype(A,/atom/movable)) - xoff+=A:step_x - yoff+=A:step_y - res.Blend(img, blendMode2iconMode(A.blend_mode), A.pixel_x + xoff, A.pixel_y + yoff) - - // Lastly, render any contained effects on top. - for(var/turf/the_turf in turfs) - // Calculate where we are relative to the center of the photo - var/xoff = (the_turf.x - center.x) * 32 + center_offset - var/yoff = (the_turf.y - center.y) * 32 + center_offset - res.Blend(getFlatIcon(the_turf.loc), blendMode2iconMode(the_turf.blend_mode),xoff,yoff) - return res - - -/obj/item/camera/proc/get_mobs(turf/the_turf as turf) - var/mob_detail - for(var/mob/M in the_turf) - if(M.invisibility) - if(see_ghosts && istype(M,/mob/dead/observer)) - var/mob/dead/observer/O = M - if(O.following) - continue - if(!mob_detail) - mob_detail = "You can see a g-g-g-g-ghooooost! " - else - mob_detail += "You can also see a g-g-g-g-ghooooost!" - else - continue - - var/holding = null - - if(istype(M, /mob/living/carbon)) - var/mob/living/carbon/A = M - if(A.l_hand || A.r_hand) - if(A.l_hand) holding = "They are holding \a [A.l_hand]" - if(A.r_hand) - if(holding) - holding += " and \a [A.r_hand]" - else - holding = "They are holding \a [A.r_hand]" - - if(!mob_detail) - mob_detail = "You can see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]. " - else - mob_detail += "You can also see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]." - return mob_detail - -/obj/item/camera/afterattack(atom/target as mob|obj|turf|area, mob/user as mob, flag) - if(!on || !pictures_left || ismob(target.loc)) return - captureimage(target, user, flag) - - playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3) - - pictures_left-- - desc = "A polaroid camera. It has [pictures_left] photos left." - to_chat(user, "[pictures_left] photos left.") - icon_state = icon_off - on = 0 - if(istype(src,/obj/item/camera/spooky)) - if(user.mind && user.mind.assigned_role == "Chaplain" && see_ghosts) - if(prob(24)) - handle_haunt(user) - spawn(64) - icon_state = icon_on - on = 1 - -/obj/item/camera/proc/can_capture_turf(turf/T, mob/user) - var/viewer = user - if(user.client) //To make shooting through security cameras possible - viewer = user.client.eye - var/can_see = (T in view(viewer)) //No x-ray vision cameras. - return can_see - -/obj/item/camera/proc/captureimage(atom/target, mob/user, flag) - var/x_c = target.x - (size-1)/2 - var/y_c = target.y + (size-1)/2 - var/z_c = target.z - var/list/turfs = list() - var/mobs = "" - for(var/i = 1; i <= size; i++) - for(var/j = 1; j <= size; j++) - var/turf/T = locate(x_c, y_c, z_c) - if(can_capture_turf(T, user)) - turfs.Add(T) - mobs += get_mobs(T) - x_c++ - y_c-- - x_c = x_c - size - - var/datum/picture/P = createpicture(target, user, turfs, mobs, flag, blueprints) - printpicture(user, P) - -/obj/item/camera/proc/createpicture(atom/target, mob/user, list/turfs, mobs, flag) - var/icon/photoimage = get_icon(turfs, target, user) - - var/icon/small_img = icon(photoimage) - var/icon/tiny_img = icon(photoimage) - var/icon/ic = icon('icons/obj/items.dmi',"photo") - var/icon/pc = icon('icons/obj/bureaucracy.dmi', "photo") - small_img.Scale(8, 8) - tiny_img.Scale(4, 4) - ic.Blend(small_img,ICON_OVERLAY, 10, 13) - pc.Blend(tiny_img,ICON_OVERLAY, 12, 19) - - var/datum/picture/P = new() - if(istype(src,/obj/item/camera/digital)) - P.fields["name"] = input(user,"Name photo:","photo") - P.name = P.fields["name"]//So the name is displayed on the print/delete list. - else - P.fields["name"] = "photo" - P.fields["author"] = user - P.fields["icon"] = ic - P.fields["tiny"] = pc - P.fields["img"] = photoimage - P.fields["desc"] = mobs - P.fields["pixel_x"] = rand(-10, 10) - P.fields["pixel_y"] = rand(-10, 10) - P.fields["size"] = size - - return P - -/obj/item/camera/proc/printpicture(mob/user, var/datum/picture/P) - var/obj/item/photo/Photo = new/obj/item/photo() - Photo.loc = user.loc - if(!user.get_inactive_hand()) - user.put_in_inactive_hand(Photo) - - if(blueprints) - Photo.blueprints = 1 - blueprints = 0 - Photo.construct(P) - -/obj/item/photo/proc/construct(var/datum/picture/P) - name = P.fields["name"] - icon = P.fields["icon"] - tiny = P.fields["tiny"] - img = P.fields["img"] - desc = P.fields["desc"] - pixel_x = P.fields["pixel_x"] - pixel_y = P.fields["pixel_y"] - photo_size = P.fields["size"] - -/obj/item/photo/proc/copy() - var/obj/item/photo/p = new/obj/item/photo() - - p.icon = icon(icon, icon_state) - p.img = icon(img) - p.tiny = icon(tiny) - p.name = name - p.desc = desc - p.scribble = scribble - p.blueprints = blueprints - - return p - -/***************** -* digital camera * -******************/ -/obj/item/camera/digital - name = "digital camera" - desc = "A digital camera. A small screen shows there is space for 10 photos left." - var/list/datum/picture/saved_pictures = list() - pictures_left = 30 - var/max_storage = 10 - -/obj/item/camera/digital/afterattack(atom/target as mob|obj|turf|area, mob/user as mob, flag) - if(!on || !pictures_left || ismob(target.loc)) return - captureimage(target, user, flag) - - playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3) - - desc = "A digital camera. A small screen shows that there are currently [saved_pictures.len] pictures stored." - icon_state = icon_off - on = 0 - spawn(64) - icon_state = icon_on - on = 1 - -/obj/item/camera/digital/captureimage(atom/target, mob/user, flag) - if(saved_pictures.len >= max_storage) - to_chat(user, "Maximum photo storage capacity reached.") - return - to_chat(user, "Picture saved.") - var/x_c = target.x - (size-1)/2 - var/y_c = target.y + (size-1)/2 - var/z_c = target.z - var/list/turfs = list() - var/mobs = "" - for(var/i = 1; i <= size; i++) - for(var/j = 1; j <= size; j++) - var/turf/T = locate(x_c, y_c, z_c) - if(can_capture_turf(T, user)) - turfs.Add(T) - mobs += get_mobs(T) - x_c++ - y_c-- - x_c = x_c - size - - var/datum/picture/P = createpicture(target, user, turfs, mobs, flag) - saved_pictures += P - -/obj/item/camera/digital/verb/print_picture() - set name = "Print picture" - set category = "Object" - set src in usr - - if(saved_pictures.len == 0) - to_chat(usr, "No images saved.") - return - if(pictures_left == 0) - to_chat(usr, "There is no film left to print.") - return - - var/datum/picture/P = null - P = input("Select image to print:",P) as null|anything in saved_pictures - if(P) - printpicture(usr,P) - pictures_left -- - -/obj/item/camera/digital/verb/delete_picture() - set name = "Delete picture" - set category = "Object" - set src in usr - - if(saved_pictures.len == 0) - to_chat(usr, "No images saved") - return - var/datum/picture/P = null - P = input("Select image to delete:",P) as null|anything in saved_pictures - if(P) - saved_pictures -= P - -/************** -*video camera * -***************/ - -/obj/item/videocam - name = "video camera" - icon = 'icons/obj/items.dmi' - desc = "video camera that can send live feed to the entertainment network." - icon_state = "videocam" - item_state = "videocam" - w_class = WEIGHT_CLASS_SMALL - slot_flags = SLOT_BELT - materials = list(MAT_METAL=2000) - var/on = 0 - var/obj/machinery/camera/camera - var/icon_on = "videocam_on" - var/icon_off = "videocam" - var/canhear_range = 7 - -/obj/item/videocam/attack_self(mob/user) - on = !on - if(camera) - if(on==0) - src.icon_state = icon_off - camera.c_tag = null - camera.network = list() - else - src.icon_state = icon_on - camera.network = list("news") - camera.c_tag = user.name - else - - src.icon_state = icon_on - camera = new /obj/machinery/camera(src) - camera.network = list("news") - cameranet.removeCamera(camera) - camera.c_tag = user.name - to_chat(user, "You switch the camera [on ? "on" : "off"].") - -/obj/item/videocam/examine(mob/user) - . = ..() - if(in_range(user, src)) - . += "This video camera can send live feeds to the entertainment network. It's [camera ? "" : "in"]active." - -/obj/item/videocam/hear_talk(mob/M as mob, list/message_pieces) - var/msg = multilingual_to_message(message_pieces) - if(camera && on) - if(get_dist(src, M) <= canhear_range) - talk_into(M, msg) - for(var/obj/machinery/computer/security/telescreen/T in GLOB.machines) - if(T.watchers[M] == camera) - T.audible_message("(Newscaster) [M] says, '[msg]'", hearing_distance = 2) - -/obj/item/videocam/hear_message(mob/M as mob, msg) - if(camera && on) - for(var/obj/machinery/computer/security/telescreen/T in GLOB.machines) - if(T.watchers[M] == camera) - T.audible_message("(Newscaster) [M] [msg]", hearing_distance = 2) - - -///hauntings, like hallucinations but more spooky - -/obj/item/camera/proc/handle_haunt(mob/user as mob) - var/list/creepyasssounds = list('sound/effects/ghost.ogg', 'sound/effects/ghost2.ogg', 'sound/effects/heartbeat.ogg', 'sound/effects/screech.ogg',\ - 'sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/behind_you2.ogg', 'sound/hallucinations/far_noise.ogg', 'sound/hallucinations/growl1.ogg', 'sound/hallucinations/growl2.ogg',\ - 'sound/hallucinations/growl3.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg', 'sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg',\ - 'sound/hallucinations/look_up1.ogg', 'sound/hallucinations/look_up2.ogg', 'sound/hallucinations/over_here1.ogg', 'sound/hallucinations/over_here2.ogg', 'sound/hallucinations/over_here3.ogg',\ - 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg', 'sound/hallucinations/veryfar_noise.ogg', 'sound/hallucinations/wail.ogg') - user << pick(creepyasssounds) +/* Photography! + * Contains: + * Camera + * Camera Film + * Photos + * Photo Albums + */ + +/******* +* film * +*******/ +/obj/item/camera_film + name = "film cartridge" + icon = 'icons/obj/items.dmi' + desc = "A camera film cartridge. Insert it into a camera to reload it." + icon_state = "film" + item_state = "electropack" + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + + +/******** +* photo * +********/ +/obj/item/photo + name = "photo" + icon = 'icons/obj/items.dmi' + icon_state = "photo" + item_state = "paper" + w_class = WEIGHT_CLASS_SMALL + resistance_flags = FLAMMABLE + max_integrity = 50 + var/blueprints = 0 // Does this have the blueprints? + var/icon/img //Big photo image + var/scribble //Scribble on the back. + var/icon/tiny + var/photo_size = 3 + +/obj/item/photo/attack_self(mob/user as mob) + user.examinate(src) + +/obj/item/photo/attackby(obj/item/P as obj, mob/user as mob, params) + if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) + var/txt = sanitize(input(user, "What would you like to write on the back?", "Photo Writing", null) as text) + txt = copytext(txt, 1, 128) + if(loc == user && user.stat == 0) + scribble = txt + else if(istype(P, /obj/item/lighter)) + burnphoto(P, user) + ..() + +/obj/item/photo/proc/burnphoto(obj/item/lighter/P, mob/user) + var/class = "" + + if(P.lit && !user.restrained()) + if(istype(P, /obj/item/lighter/zippo)) + class = "" + + user.visible_message("[class][user] holds \the [P] up to \the [src], it looks like [user.p_theyre()] trying to burn it!", \ + "[class]You hold [P] up to [src], burning it slowly.") + + spawn(20) + if(get_dist(src, user) < 2 && user.get_active_hand() == P && P.lit) + user.visible_message("[class][user] burns right through \the [src], turning it to ash. It flutters through the air before settling on the floor in a heap.", \ + "[class]You burn right through \the [src], turning it to ash. It flutters through the air before settling on the floor in a heap.") + + if(user.is_in_inactive_hand(src)) + user.unEquip(src) + + new /obj/effect/decal/cleanable/ash(get_turf(src)) + qdel(src) + + else + to_chat(user, "You must hold \the [P] steady to burn \the [src].") + +/obj/item/photo/examine(mob/user) + . = ..() + if(in_range(user, src) || isobserver(user)) + show(user) + else + . += "It is too far away." + +/obj/item/photo/proc/show(mob/user as mob) + var/icon/img_shown = new/icon(img) + var/colormatrix = user.get_screen_colour() + // Apply colorblindness effects, if any. + if(islist(colormatrix)) + img_shown.MapColors( + colormatrix[1], colormatrix[2], colormatrix[3], + colormatrix[4], colormatrix[5], colormatrix[6], + colormatrix[7], colormatrix[8], colormatrix[9], + ) + usr << browse_rsc(img_shown, "tmp_photo.png") + usr << browse("[name]" \ + + "" \ + + "" \ + + "[scribble ? "
        Written on the back:
        [scribble]" : ""]"\ + + "", "window=Photo[UID()];size=[64*photo_size]x[scribble ? 400 : 64*photo_size]") + onclose(usr, "Photo[UID()]") + return + +/obj/item/photo/verb/rename() + set name = "Rename photo" + set category = "Object" + set src in usr + + var/n_name = sanitize(copytext(input(usr, "What would you like to label the photo?", "Photo Labelling", name) as text, 1, MAX_MESSAGE_LEN)) + //loc.loc check is for making possible renaming photos in clipboards + if(( (loc == usr || (loc.loc && loc.loc == usr)) && usr.stat == 0)) + name = "[(n_name ? text("[n_name]") : "photo")]" + add_fingerprint(usr) + return + + +/************** +* photo album * +**************/ +/obj/item/storage/photo_album + name = "Photo album" + icon = 'icons/obj/items.dmi' + icon_state = "album" + item_state = "briefcase" + can_hold = list(/obj/item/photo) + resistance_flags = FLAMMABLE + +/obj/item/storage/photo_album/MouseDrop(obj/over_object as obj) + + if((istype(usr, /mob/living/carbon/human))) + var/mob/M = usr + if(!( istype(over_object, /obj/screen) )) + return ..() + playsound(loc, "rustle", 50, 1, -5) + if((!( M.restrained() ) && !( M.stat ) && M.back == src)) + switch(over_object.name) + if("r_hand") + M.unEquip(src) + M.put_in_r_hand(src) + if("l_hand") + M.unEquip(src) + M.put_in_l_hand(src) + add_fingerprint(usr) + return + if(over_object == usr && in_range(src, usr) || usr.contents.Find(src)) + if(usr.s_active) + usr.s_active.close(usr) + show_to(usr) + return + return + +/********* +* camera * +*********/ +/obj/item/camera + name = "camera" + icon = 'icons/obj/items.dmi' + desc = "A polaroid camera. 10 photos left." + icon_state = "camera" + item_state = "electropack" + w_class = WEIGHT_CLASS_SMALL + slot_flags = SLOT_BELT + var/list/matter = list("metal" = 2000) + var/pictures_max = 10 + var/pictures_left = 10 + var/on = 1 + var/blueprints = 0 + var/icon_on = "camera" + var/icon_off = "camera_off" + var/size = 3 + var/see_ghosts = 0 //for the spoop of it + + +/obj/item/camera/spooky/CheckParts(list/parts_list) + ..() + var/obj/item/camera/C = locate(/obj/item/camera) in contents + if(C) + pictures_max = C.pictures_max + pictures_left = C.pictures_left + visible_message("[C] has been imbued with godlike power!") + qdel(C) + + +var/list/SpookyGhosts = list("ghost","shade","shade2","ghost-narsie","horror","shadow","ghostian2") + +/obj/item/camera/spooky + name = "camera obscura" + desc = "A polaroid camera, some say it can see ghosts!" + see_ghosts = 1 + +/obj/item/camera/verb/change_size() + set name = "Set Photo Focus" + set category = "Object" + var/nsize = input("Photo Size","Pick a size of resulting photo.") as null|anything in list(1,3,5,7) + if(nsize) + size = nsize + to_chat(usr, "Camera will now take [size]x[size] photos.") + +/obj/item/camera/attack(mob/living/carbon/human/M as mob, mob/user as mob) + return + +/obj/item/camera/attack_self(mob/user as mob) + on = !on + if(on) + src.icon_state = icon_on + else + src.icon_state = icon_off + to_chat(user, "You switch the camera [on ? "on" : "off"].") + return + +/obj/item/camera/attackby(obj/item/I as obj, mob/user as mob, params) + if(istype(I, /obj/item/camera_film)) + if(pictures_left) + to_chat(user, "[src] still has some film in it!") + return + to_chat(user, "You insert [I] into [src].") + user.drop_item() + qdel(I) + pictures_left = pictures_max + return + ..() + + +/obj/item/camera/proc/get_icon(list/turfs, turf/center, mob/user) + + //Bigger icon base to capture those icons that were shifted to the next tile + //i.e. pretty much all wall-mounted machinery + var/icon/res = icon('icons/effects/96x96.dmi', "") + res.Scale(size*32, size*32) + // Initialize the photograph to black. + res.Blend("#000", ICON_OVERLAY) + + var/atoms[] = list() + for(var/turf/the_turf in turfs) + // Add ourselves to the list of stuff to draw + atoms.Add(the_turf); + // As well as anything that isn't invisible. + for(var/atom/A in the_turf) + if(A.invisibility) + if(see_ghosts && istype(A,/mob/dead/observer)) + var/mob/dead/observer/O = A + if(O.following) + continue + if(user.mind && !(user.mind.assigned_role == "Chaplain")) + atoms.Add(image('icons/mob/mob.dmi', O.loc, pick(SpookyGhosts), 4, SOUTH)) + else + atoms.Add(image('icons/mob/mob.dmi', O.loc, "ghost", 4, SOUTH)) + else//its not a ghost + continue + else//not invisable, not a spookyghost add it. + var/disguised = null + if(user.viewing_alternate_appearances && user.viewing_alternate_appearances.len && ishuman(A) && A.alternate_appearances && A.alternate_appearances.len) //This whole thing and the stuff below just checks if the atom is a Solid Snake cosplayer. + for(var/datum/alternate_appearance/alt_appearance in user.viewing_alternate_appearances) + if(alt_appearance.owner == A) //If it turns out they are, don't blow their cover. That'd be rude. + atoms.Add(image(alt_appearance.img, A.loc, layer = 4, dir = A.dir)) //Render their disguise. + atoms.Remove(A) //Don't blow their cover. + disguised = 1 + continue + if(!disguised) //If they aren't, treat them normally. + atoms.Add(A) + + + // Sort the atoms into their layers + var/list/sorted = sort_atoms_by_layer(atoms) + var/center_offset = (size-1)/2 * 32 + 1 + for(var/i; i <= sorted.len; i++) + var/atom/A = sorted[i] + if(A) + var/icon/img = getFlatIcon(A)//build_composite_icon(A) + if(istype(A, /obj/item/areaeditor/blueprints)) + blueprints = 1 + + // If what we got back is actually a picture, draw it. + if(istype(img, /icon)) + // Check if we're looking at a mob that's lying down + if(istype(A, /mob/living) && A:lying) + // If they are, apply that effect to their picture. + img.BecomeLying() + // Calculate where we are relative to the center of the photo + var/xoff = (A.x - center.x) * 32 + center_offset + var/yoff = (A.y - center.y) * 32 + center_offset + if(istype(A,/atom/movable)) + xoff+=A:step_x + yoff+=A:step_y + res.Blend(img, blendMode2iconMode(A.blend_mode), A.pixel_x + xoff, A.pixel_y + yoff) + + // Lastly, render any contained effects on top. + for(var/turf/the_turf in turfs) + // Calculate where we are relative to the center of the photo + var/xoff = (the_turf.x - center.x) * 32 + center_offset + var/yoff = (the_turf.y - center.y) * 32 + center_offset + res.Blend(getFlatIcon(the_turf.loc), blendMode2iconMode(the_turf.blend_mode),xoff,yoff) + return res + + +/obj/item/camera/proc/get_mobs(turf/the_turf as turf) + var/mob_detail + for(var/mob/M in the_turf) + if(M.invisibility) + if(see_ghosts && istype(M,/mob/dead/observer)) + var/mob/dead/observer/O = M + if(O.following) + continue + if(!mob_detail) + mob_detail = "You can see a g-g-g-g-ghooooost! " + else + mob_detail += "You can also see a g-g-g-g-ghooooost!" + else + continue + + var/holding = null + + if(istype(M, /mob/living/carbon)) + var/mob/living/carbon/A = M + if(A.l_hand || A.r_hand) + if(A.l_hand) holding = "They are holding \a [A.l_hand]" + if(A.r_hand) + if(holding) + holding += " and \a [A.r_hand]" + else + holding = "They are holding \a [A.r_hand]" + + if(!mob_detail) + mob_detail = "You can see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]. " + else + mob_detail += "You can also see [A] on the photo[A:health < 75 ? " - [A] looks hurt":""].[holding ? " [holding]":"."]." + return mob_detail + +/obj/item/camera/afterattack(atom/target as mob|obj|turf|area, mob/user as mob, flag) + if(!on || !pictures_left || ismob(target.loc)) return + captureimage(target, user, flag) + + playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3) + + pictures_left-- + desc = "A polaroid camera. It has [pictures_left] photos left." + to_chat(user, "[pictures_left] photos left.") + icon_state = icon_off + on = 0 + if(istype(src,/obj/item/camera/spooky)) + if(user.mind && user.mind.assigned_role == "Chaplain" && see_ghosts) + if(prob(24)) + handle_haunt(user) + spawn(64) + icon_state = icon_on + on = 1 + +/obj/item/camera/proc/can_capture_turf(turf/T, mob/user) + var/viewer = user + if(user.client) //To make shooting through security cameras possible + viewer = user.client.eye + var/can_see = (T in view(viewer)) //No x-ray vision cameras. + return can_see + +/obj/item/camera/proc/captureimage(atom/target, mob/user, flag) + var/x_c = target.x - (size-1)/2 + var/y_c = target.y + (size-1)/2 + var/z_c = target.z + var/list/turfs = list() + var/mobs = "" + for(var/i = 1; i <= size; i++) + for(var/j = 1; j <= size; j++) + var/turf/T = locate(x_c, y_c, z_c) + if(can_capture_turf(T, user)) + turfs.Add(T) + mobs += get_mobs(T) + x_c++ + y_c-- + x_c = x_c - size + + var/datum/picture/P = createpicture(target, user, turfs, mobs, flag, blueprints) + printpicture(user, P) + +/obj/item/camera/proc/createpicture(atom/target, mob/user, list/turfs, mobs, flag) + var/icon/photoimage = get_icon(turfs, target, user) + + var/icon/small_img = icon(photoimage) + var/icon/tiny_img = icon(photoimage) + var/icon/ic = icon('icons/obj/items.dmi',"photo") + var/icon/pc = icon('icons/obj/bureaucracy.dmi', "photo") + small_img.Scale(8, 8) + tiny_img.Scale(4, 4) + ic.Blend(small_img,ICON_OVERLAY, 10, 13) + pc.Blend(tiny_img,ICON_OVERLAY, 12, 19) + + var/datum/picture/P = new() + if(istype(src,/obj/item/camera/digital)) + P.fields["name"] = input(user,"Name photo:","photo") + P.name = P.fields["name"]//So the name is displayed on the print/delete list. + else + P.fields["name"] = "photo" + P.fields["author"] = user + P.fields["icon"] = ic + P.fields["tiny"] = pc + P.fields["img"] = photoimage + P.fields["desc"] = mobs + P.fields["pixel_x"] = rand(-10, 10) + P.fields["pixel_y"] = rand(-10, 10) + P.fields["size"] = size + + return P + +/obj/item/camera/proc/printpicture(mob/user, var/datum/picture/P) + var/obj/item/photo/Photo = new/obj/item/photo() + Photo.loc = user.loc + if(!user.get_inactive_hand()) + user.put_in_inactive_hand(Photo) + + if(blueprints) + Photo.blueprints = 1 + blueprints = 0 + Photo.construct(P) + +/obj/item/photo/proc/construct(var/datum/picture/P) + name = P.fields["name"] + icon = P.fields["icon"] + tiny = P.fields["tiny"] + img = P.fields["img"] + desc = P.fields["desc"] + pixel_x = P.fields["pixel_x"] + pixel_y = P.fields["pixel_y"] + photo_size = P.fields["size"] + +/obj/item/photo/proc/copy() + var/obj/item/photo/p = new/obj/item/photo() + + p.icon = icon(icon, icon_state) + p.img = icon(img) + p.tiny = icon(tiny) + p.name = name + p.desc = desc + p.scribble = scribble + p.blueprints = blueprints + + return p + +/***************** +* digital camera * +******************/ +/obj/item/camera/digital + name = "digital camera" + desc = "A digital camera. A small screen shows there is space for 10 photos left." + var/list/datum/picture/saved_pictures = list() + pictures_left = 30 + var/max_storage = 10 + +/obj/item/camera/digital/afterattack(atom/target as mob|obj|turf|area, mob/user as mob, flag) + if(!on || !pictures_left || ismob(target.loc)) return + captureimage(target, user, flag) + + playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3) + + desc = "A digital camera. A small screen shows that there are currently [saved_pictures.len] pictures stored." + icon_state = icon_off + on = 0 + spawn(64) + icon_state = icon_on + on = 1 + +/obj/item/camera/digital/captureimage(atom/target, mob/user, flag) + if(saved_pictures.len >= max_storage) + to_chat(user, "Maximum photo storage capacity reached.") + return + to_chat(user, "Picture saved.") + var/x_c = target.x - (size-1)/2 + var/y_c = target.y + (size-1)/2 + var/z_c = target.z + var/list/turfs = list() + var/mobs = "" + for(var/i = 1; i <= size; i++) + for(var/j = 1; j <= size; j++) + var/turf/T = locate(x_c, y_c, z_c) + if(can_capture_turf(T, user)) + turfs.Add(T) + mobs += get_mobs(T) + x_c++ + y_c-- + x_c = x_c - size + + var/datum/picture/P = createpicture(target, user, turfs, mobs, flag) + saved_pictures += P + +/obj/item/camera/digital/verb/print_picture() + set name = "Print picture" + set category = "Object" + set src in usr + + if(saved_pictures.len == 0) + to_chat(usr, "No images saved.") + return + if(pictures_left == 0) + to_chat(usr, "There is no film left to print.") + return + + var/datum/picture/P = null + P = input("Select image to print:",P) as null|anything in saved_pictures + if(P) + printpicture(usr,P) + pictures_left -- + +/obj/item/camera/digital/verb/delete_picture() + set name = "Delete picture" + set category = "Object" + set src in usr + + if(saved_pictures.len == 0) + to_chat(usr, "No images saved") + return + var/datum/picture/P = null + P = input("Select image to delete:",P) as null|anything in saved_pictures + if(P) + saved_pictures -= P + +/************** +*video camera * +***************/ + +/obj/item/videocam + name = "video camera" + icon = 'icons/obj/items.dmi' + desc = "video camera that can send live feed to the entertainment network." + icon_state = "videocam" + item_state = "videocam" + w_class = WEIGHT_CLASS_SMALL + slot_flags = SLOT_BELT + materials = list(MAT_METAL=2000) + var/on = 0 + var/obj/machinery/camera/camera + var/icon_on = "videocam_on" + var/icon_off = "videocam" + var/canhear_range = 7 + +/obj/item/videocam/attack_self(mob/user) + on = !on + if(camera) + if(on==0) + src.icon_state = icon_off + camera.c_tag = null + camera.network = list() + else + src.icon_state = icon_on + camera.network = list("news") + camera.c_tag = user.name + else + + src.icon_state = icon_on + camera = new /obj/machinery/camera(src) + camera.network = list("news") + cameranet.removeCamera(camera) + camera.c_tag = user.name + to_chat(user, "You switch the camera [on ? "on" : "off"].") + +/obj/item/videocam/examine(mob/user) + . = ..() + if(in_range(user, src)) + . += "This video camera can send live feeds to the entertainment network. It's [camera ? "" : "in"]active." + +/obj/item/videocam/hear_talk(mob/M as mob, list/message_pieces) + var/msg = multilingual_to_message(message_pieces) + if(camera && on) + if(get_dist(src, M) <= canhear_range) + talk_into(M, msg) + for(var/obj/machinery/computer/security/telescreen/T in GLOB.machines) + if(T.watchers[M] == camera) + T.audible_message("(Newscaster) [M] says, '[msg]'", hearing_distance = 2) + +/obj/item/videocam/hear_message(mob/M as mob, msg) + if(camera && on) + for(var/obj/machinery/computer/security/telescreen/T in GLOB.machines) + if(T.watchers[M] == camera) + T.audible_message("(Newscaster) [M] [msg]", hearing_distance = 2) + + +///hauntings, like hallucinations but more spooky + +/obj/item/camera/proc/handle_haunt(mob/user as mob) + var/list/creepyasssounds = list('sound/effects/ghost.ogg', 'sound/effects/ghost2.ogg', 'sound/effects/heartbeat.ogg', 'sound/effects/screech.ogg',\ + 'sound/hallucinations/behind_you1.ogg', 'sound/hallucinations/behind_you2.ogg', 'sound/hallucinations/far_noise.ogg', 'sound/hallucinations/growl1.ogg', 'sound/hallucinations/growl2.ogg',\ + 'sound/hallucinations/growl3.ogg', 'sound/hallucinations/im_here1.ogg', 'sound/hallucinations/im_here2.ogg', 'sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg',\ + 'sound/hallucinations/look_up1.ogg', 'sound/hallucinations/look_up2.ogg', 'sound/hallucinations/over_here1.ogg', 'sound/hallucinations/over_here2.ogg', 'sound/hallucinations/over_here3.ogg',\ + 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg', 'sound/hallucinations/veryfar_noise.ogg', 'sound/hallucinations/wail.ogg') + user << pick(creepyasssounds) diff --git a/code/modules/pda/PDA.dm b/code/modules/pda/PDA.dm index b2cf43e5b84..4030924a8ac 100755 --- a/code/modules/pda/PDA.dm +++ b/code/modules/pda/PDA.dm @@ -1,520 +1,520 @@ - -//The advanced pea-green monochrome lcd of tomorrow. - -var/global/list/obj/item/pda/PDAs = list() - - -/obj/item/pda - name = "PDA" - desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." - icon = 'icons/obj/pda.dmi' - icon_state = "pda" - item_state = "electronic" - w_class = WEIGHT_CLASS_TINY - slot_flags = SLOT_ID | SLOT_BELT | SLOT_PDA - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - origin_tech = "programming=2" - - //Main variables - var/owner = null - var/default_cartridge = 0 // Access level defined by cartridge - var/obj/item/cartridge/cartridge = null //current cartridge - var/datum/data/pda/app/current_app = null - var/datum/data/pda/app/lastapp = null - var/ui_tick = 0 - - //Secondary variables - var/model_name = "Thinktronic 5230 Personal Data Assistant" - var/datum/data/pda/utility/scanmode/scanmode = null - - var/lock_code = "" // Lockcode to unlock uplink - var/honkamt = 0 //How many honks left when infected with honk.exe - var/mimeamt = 0 //How many silence left when infected with mime.exe - var/detonate = 1 // Can the PDA be blown up? - var/ttone = "beep" //The ringtone! - var/list/ttone_sound = list("beep" = 'sound/machines/twobeep.ogg', - "boom" = 'sound/effects/explosionfar.ogg', - "slip" = 'sound/misc/slip.ogg', - "honk" = 'sound/items/bikehorn.ogg', - "SKREE" = 'sound/voice/shriek1.ogg', - "holy" = 'sound/items/PDA/ambicha4-short.ogg', - "xeno" = 'sound/voice/hiss1.ogg') - - var/list/programs = list( - new/datum/data/pda/app/main_menu, - new/datum/data/pda/app/notekeeper, - new/datum/data/pda/app/messenger, - new/datum/data/pda/app/manifest, - new/datum/data/pda/app/atmos_scanner, - new/datum/data/pda/utility/scanmode/notes, - new/datum/data/pda/utility/flashlight) - var/list/shortcut_cache = list() - var/list/shortcut_cat_order = list() - var/list/notifying_programs = list() - - var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. - var/ownjob = null //related to above - var/ownrank = null // this one is rank, never alt title - - var/obj/item/paicard/pai = null // A slot for a personal AI device - var/retro_mode = 0 - - -/* - * The Actual PDA - */ -/obj/item/pda/Initialize(mapload) - . = ..() - PDAs += src - PDAs = sortAtom(PDAs) - update_programs() - if(default_cartridge) - cartridge = new default_cartridge(src) - cartridge.update_programs(src) - new /obj/item/pen(src) - start_program(find_program(/datum/data/pda/app/main_menu)) - -/obj/item/pda/proc/can_use() - if(!ismob(loc)) - return 0 - - var/mob/M = loc - if(M.incapacitated()) - return 0 - if((src in M.contents) || ( istype(loc, /turf) && in_range(src, M) )) - return 1 - else - return 0 - -/obj/item/pda/GetAccess() - if(id) - return id.GetAccess() - else - return ..() - -/obj/item/pda/GetID() - return id - -/obj/item/pda/MouseDrop(obj/over_object as obj, src_location, over_location) - var/mob/M = usr - if((!istype(over_object, /obj/screen)) && can_use()) - return attack_self(M) - -/obj/item/pda/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = inventory_state) - ui_tick++ - var/datum/nanoui/old_ui = SSnanoui.get_open_ui(user, src, "main") - var/auto_update = 1 - if(!current_app) - return - - if(current_app.update == PDA_APP_NOUPDATE && current_app == lastapp) - auto_update = 0 - if(old_ui && (current_app == lastapp && ui_tick % 5 && current_app.update == PDA_APP_UPDATE_SLOW)) - return - - lastapp = current_app - - var/title = "Personal Data Assistant" - - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "pda.tmpl", title, 630, 600, state = state) - ui.set_state_key("pda") - - // open the new ui window - ui.open() - - // auto update every Master Controller tick - ui.set_auto_update(auto_update) - -/obj/item/pda/ui_data(mob/user, ui_key = "main", datum/topic_state/state = inventory_state) - var/data[0] - - data["owner"] = owner // Who is your daddy... - data["ownjob"] = ownjob // ...and what does he do? - - // update list of shortcuts, only if they changed - if(!shortcut_cache.len) - shortcut_cache = list() - shortcut_cat_order = list() - var/prog_list = programs.Copy() - if(cartridge) - prog_list |= cartridge.programs - - for(var/A in prog_list) - var/datum/data/pda/P = A - - if(P.hidden) - continue - var/list/cat - if(P.category in shortcut_cache) - cat = shortcut_cache[P.category] - else - cat = list() - shortcut_cache[P.category] = cat - shortcut_cat_order += P.category - cat |= list(list(name = P.name, icon = P.icon, notify_icon = P.notify_icon, ref = "\ref[P]")) - - // force the order of a few core categories - shortcut_cat_order = list("General") \ - + sortList(shortcut_cat_order - list("General", "Scanners", "Utilities")) \ - + list("Scanners", "Utilities") - - data["idInserted"] = (id ? 1 : 0) - data["idLink"] = (id ? text("[id.registered_name], [id.assignment]") : "--------") - - data["useRetro"] = retro_mode - - data["cartridge_name"] = cartridge ? cartridge.name : "" - data["stationTime"] = station_time_timestamp() - - data["app"] = list() - current_app.update_ui(user, data) - data["app"] |= list( - "name" = current_app.title, - "icon" = current_app.icon, - "template" = current_app.template, - "has_back" = current_app.has_back) - - return data - -/obj/item/pda/attack_self(mob/user as mob) - user.set_machine(src) - if(active_uplink_check(user)) - return - ui_interact(user) //NanoUI requires this proc - -/obj/item/pda/proc/start_program(datum/data/pda/P) - if(P && ((P in programs) || (cartridge && (P in cartridge.programs)))) - return P.start() - return 0 - -/obj/item/pda/proc/find_program(type) - var/datum/data/pda/A = locate(type) in programs - if(A) - return A - if(cartridge) - A = locate(type) in cartridge.programs - if(A) - return A - return null - -// force the cache to rebuild on update_ui -/obj/item/pda/proc/update_shortcuts() - shortcut_cache.Cut() - -/obj/item/pda/proc/update_programs() - for(var/A in programs) - var/datum/data/pda/P = A - P.pda = src - -/obj/item/pda/Topic(href, href_list) - . = ..() - if(.) - return - - var/mob/user = usr - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") - var/mob/living/U = usr - if(usr.stat == DEAD) - return 0 - if(!can_use()) //Why reinvent the wheel? There's a proc that does exactly that. - U.unset_machine() - if(ui) - ui.close() - return 0 - - add_fingerprint(U) - U.set_machine(src) - - if(href_list["radiomenu"] && !isnull(cartridge) && !isnull(cartridge.radio)) - cartridge.radio.Topic(href, href_list) - return 1 - - . = 1 - - switch(href_list["choice"]) - if("Home")//Go home, largely replaces the old Return - var/datum/data/pda/app/main_menu/A = find_program(/datum/data/pda/app/main_menu) - if(A) - start_program(A) - if("StartProgram") - if(href_list["program"]) - var/datum/data/pda/app/A = locate(href_list["program"]) - if(A) - start_program(A) - if("Eject")//Ejects the cart, only done from hub. - if(!isnull(cartridge)) - var/turf/T = loc - if(ismob(T)) - T = T.loc - var/obj/item/cartridge/C = cartridge - C.forceMove(T) - if(scanmode in C.programs) - scanmode = null - if(current_app in C.programs) - start_program(find_program(/datum/data/pda/app/main_menu)) - if(C.radio) - C.radio.hostpda = null - for(var/datum/data/pda/P in notifying_programs) - if(P in C.programs) - P.unnotify() - cartridge = null - update_shortcuts() - if("Authenticate")//Checks for ID - id_check(usr, 1) - if("Retro") - retro_mode = !retro_mode - if("Ringtone") - return set_ringtone() - else - if(current_app) - . = current_app.Topic(href, href_list) - -//EXTRA FUNCTIONS=================================== - if((honkamt > 0) && (prob(60)))//For clown virus. - honkamt-- - playsound(loc, 'sound/items/bikehorn.ogg', 30, 1) - - return // return 1 tells it to refresh the UI in NanoUI - -/obj/item/pda/proc/close(mob/user) - var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") - ui.close() - -/obj/item/pda/verb/verb_reset_pda() - set category = "Object" - set name = "Reset PDA" - set src in usr - - if(issilicon(usr)) - return - - if(can_use(usr)) - start_program(find_program(/datum/data/pda/app/main_menu)) - notifying_programs.Cut() - overlays -= image('icons/obj/pda.dmi', "pda-r") - to_chat(usr, "You press the reset button on \the [src].") - else - to_chat(usr, "You cannot do this while restrained.") - -/obj/item/pda/AltClick(mob/user) - ..() - if(issilicon(user)) - return - - if(can_use(user)) - if(id) - remove_id(user) - else - to_chat(user, "This PDA does not have an ID in it!") - -/obj/item/pda/CtrlClick(mob/user) - ..() - if(issilicon(user)) - return - - if(can_use(user)) - remove_pen(user) - -/obj/item/pda/proc/remove_id(mob/user) - if(id) - if(ismob(loc)) - var/mob/M = loc - M.put_in_hands(id) - to_chat(user, "You remove the ID from the [name].") - else - id.forceMove(get_turf(src)) - overlays -= image('icons/goonstation/objects/pda_overlay.dmi', id.icon_state) - id = null - -/obj/item/pda/verb/verb_remove_id() - set category = "Object" - set name = "Remove id" - set src in usr - - if(issilicon(usr)) - return - - if( can_use(usr) ) - if(id) - remove_id(usr) - else - to_chat(usr, "This PDA does not have an ID in it.") - else - to_chat(usr, "You cannot do this while restrained.") - -/obj/item/pda/verb/verb_remove_pen() - set category = "Object" - set name = "Remove pen" - set src in usr - remove_pen(usr) - -/obj/item/pda/proc/remove_pen(mob/user) - - if(issilicon(user)) - return - - if( can_use(user) ) - var/obj/item/pen/O = locate() in src - if(O) - to_chat(user, "You remove the [O] from [src].") - if(istype(loc, /mob)) - var/mob/M = loc - if(M.get_active_hand() == null) - M.put_in_hands(O) - return - O.forceMove(get_turf(src)) - else - to_chat(user, "This PDA does not have a pen in it.") - else - to_chat(user, "You cannot do this while restrained.") - -/obj/item/pda/proc/id_check(mob/user as mob, choice as num)//To check for IDs; 1 for in-pda use, 2 for out of pda use. - if(choice == 1) - if(id) - remove_id(user) - else - var/obj/item/I = user.get_active_hand() - if(istype(I, /obj/item/card/id)) - user.drop_item() - I.forceMove(src) - id = I - else - var/obj/item/card/I = user.get_active_hand() - if(istype(I, /obj/item/card/id) && I:registered_name) - var/obj/old_id = id - user.drop_item() - I.forceMove(src) - id = I - user.put_in_hands(old_id) - return - -/obj/item/pda/attackby(obj/item/C as obj, mob/user as mob, params) - ..() - if(istype(C, /obj/item/cartridge) && !cartridge) - cartridge = C - user.drop_item() - cartridge.forceMove(src) - cartridge.update_programs(src) - update_shortcuts() - to_chat(user, "You insert [cartridge] into [src].") - if(cartridge.radio) - cartridge.radio.hostpda = src - - else if(istype(C, /obj/item/card/id)) - var/obj/item/card/id/idcard = C - if(!idcard.registered_name) - to_chat(user, "\The [src] rejects the ID.") - return - if(!owner) - owner = idcard.registered_name - ownjob = idcard.assignment - ownrank = idcard.rank - name = "PDA-[owner] ([ownjob])" - to_chat(user, "Card scanned.") - else - //Basic safety check. If either both objects are held by user or PDA is on ground and card is in hand. - if(((src in user.contents) && (C in user.contents)) || (istype(loc, /turf) && in_range(src, user) && (C in user.contents)) ) - if( can_use(user) )//If they can still act. - id_check(user, 2) - to_chat(user, "You put the ID into \the [src]'s slot.
        You can remove it with ALT click.
        ") - overlays += image('icons/goonstation/objects/pda_overlay.dmi', C.icon_state) - - else if(istype(C, /obj/item/paicard) && !src.pai) - user.drop_item() - C.forceMove(src) - pai = C - to_chat(user, "You slot \the [C] into [src].") - else if(istype(C, /obj/item/pen)) - var/obj/item/pen/O = locate() in src - if(O) - to_chat(user, "There is already a pen in \the [src].") - else - user.drop_item() - C.forceMove(src) - to_chat(user, "You slide \the [C] into \the [src].") - else if(istype(C, /obj/item/nanomob_card)) - if(cartridge && istype(cartridge, /obj/item/cartridge/mob_hunt_game)) - cartridge.attackby(C, user, params) - -/obj/item/pda/attack(mob/living/C as mob, mob/living/user as mob) - if(istype(C, /mob/living/carbon) && scanmode) - scanmode.scan_mob(C, user) - -/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user as mob, proximity) - if(proximity && scanmode) - scanmode.scan_atom(A, user) - -/obj/item/pda/proc/explode() //This needs tuning. - if(!detonate) - return - var/turf/T = get_turf(src.loc) - - if(ismob(loc)) - var/mob/M = loc - M.show_message("Your [src] explodes!", 1) - - if(T) - T.hotspot_expose(700,125) - - explosion(T, -1, -1, 2, 3) - qdel(src) - return - -/obj/item/pda/Destroy() - PDAs -= src - var/T = get_turf(loc) - if(id) - id.forceMove(T) - if(pai) - pai.forceMove(T) - current_app = null - scanmode = null - QDEL_LIST(programs) - QDEL_NULL(cartridge) - return ..() - -// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP -/obj/item/pda/emp_act(severity) - for(var/atom/A in src) - A.emp_act(severity) - -/obj/item/pda/proc/play_ringtone() - var/S - - if(ttone in ttone_sound) - S = ttone_sound[ttone] - else - S = 'sound/machines/twobeep.ogg' - playsound(loc, S, 50, 1) - for(var/mob/O in hearers(3, loc)) - O.show_message(text("[bicon(src)] *[ttone]*")) - -/obj/item/pda/proc/set_ringtone() - var/t = input("Please enter new ringtone", name, ttone) as text - if(in_range(src, usr) && loc == usr) - if(t) - if(hidden_uplink && hidden_uplink.check_trigger(usr, lowertext(t), lowertext(lock_code))) - to_chat(usr, "The PDA softly beeps.") - close(usr) - else - t = sanitize(copytext(t, 1, 20)) - ttone = t - return 1 - else - close(usr) - return 0 - -/obj/item/pda/process() - if(current_app) - current_app.program_process() - -/obj/item/pda/extinguish_light() - var/datum/data/pda/utility/flashlight/FL = find_program(/datum/data/pda/utility/flashlight) - if(FL && FL.fon) - FL.start() + +//The advanced pea-green monochrome lcd of tomorrow. + +var/global/list/obj/item/pda/PDAs = list() + + +/obj/item/pda + name = "PDA" + desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." + icon = 'icons/obj/pda.dmi' + icon_state = "pda" + item_state = "electronic" + w_class = WEIGHT_CLASS_TINY + slot_flags = SLOT_ID | SLOT_BELT | SLOT_PDA + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + origin_tech = "programming=2" + + //Main variables + var/owner = null + var/default_cartridge = 0 // Access level defined by cartridge + var/obj/item/cartridge/cartridge = null //current cartridge + var/datum/data/pda/app/current_app = null + var/datum/data/pda/app/lastapp = null + var/ui_tick = 0 + + //Secondary variables + var/model_name = "Thinktronic 5230 Personal Data Assistant" + var/datum/data/pda/utility/scanmode/scanmode = null + + var/lock_code = "" // Lockcode to unlock uplink + var/honkamt = 0 //How many honks left when infected with honk.exe + var/mimeamt = 0 //How many silence left when infected with mime.exe + var/detonate = 1 // Can the PDA be blown up? + var/ttone = "beep" //The ringtone! + var/list/ttone_sound = list("beep" = 'sound/machines/twobeep.ogg', + "boom" = 'sound/effects/explosionfar.ogg', + "slip" = 'sound/misc/slip.ogg', + "honk" = 'sound/items/bikehorn.ogg', + "SKREE" = 'sound/voice/shriek1.ogg', + "holy" = 'sound/items/PDA/ambicha4-short.ogg', + "xeno" = 'sound/voice/hiss1.ogg') + + var/list/programs = list( + new/datum/data/pda/app/main_menu, + new/datum/data/pda/app/notekeeper, + new/datum/data/pda/app/messenger, + new/datum/data/pda/app/manifest, + new/datum/data/pda/app/atmos_scanner, + new/datum/data/pda/utility/scanmode/notes, + new/datum/data/pda/utility/flashlight) + var/list/shortcut_cache = list() + var/list/shortcut_cat_order = list() + var/list/notifying_programs = list() + + var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. + var/ownjob = null //related to above + var/ownrank = null // this one is rank, never alt title + + var/obj/item/paicard/pai = null // A slot for a personal AI device + var/retro_mode = 0 + + +/* + * The Actual PDA + */ +/obj/item/pda/Initialize(mapload) + . = ..() + PDAs += src + PDAs = sortAtom(PDAs) + update_programs() + if(default_cartridge) + cartridge = new default_cartridge(src) + cartridge.update_programs(src) + new /obj/item/pen(src) + start_program(find_program(/datum/data/pda/app/main_menu)) + +/obj/item/pda/proc/can_use() + if(!ismob(loc)) + return 0 + + var/mob/M = loc + if(M.incapacitated()) + return 0 + if((src in M.contents) || ( istype(loc, /turf) && in_range(src, M) )) + return 1 + else + return 0 + +/obj/item/pda/GetAccess() + if(id) + return id.GetAccess() + else + return ..() + +/obj/item/pda/GetID() + return id + +/obj/item/pda/MouseDrop(obj/over_object as obj, src_location, over_location) + var/mob/M = usr + if((!istype(over_object, /obj/screen)) && can_use()) + return attack_self(M) + +/obj/item/pda/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = inventory_state) + ui_tick++ + var/datum/nanoui/old_ui = SSnanoui.get_open_ui(user, src, "main") + var/auto_update = 1 + if(!current_app) + return + + if(current_app.update == PDA_APP_NOUPDATE && current_app == lastapp) + auto_update = 0 + if(old_ui && (current_app == lastapp && ui_tick % 5 && current_app.update == PDA_APP_UPDATE_SLOW)) + return + + lastapp = current_app + + var/title = "Personal Data Assistant" + + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "pda.tmpl", title, 630, 600, state = state) + ui.set_state_key("pda") + + // open the new ui window + ui.open() + + // auto update every Master Controller tick + ui.set_auto_update(auto_update) + +/obj/item/pda/ui_data(mob/user, ui_key = "main", datum/topic_state/state = inventory_state) + var/data[0] + + data["owner"] = owner // Who is your daddy... + data["ownjob"] = ownjob // ...and what does he do? + + // update list of shortcuts, only if they changed + if(!shortcut_cache.len) + shortcut_cache = list() + shortcut_cat_order = list() + var/prog_list = programs.Copy() + if(cartridge) + prog_list |= cartridge.programs + + for(var/A in prog_list) + var/datum/data/pda/P = A + + if(P.hidden) + continue + var/list/cat + if(P.category in shortcut_cache) + cat = shortcut_cache[P.category] + else + cat = list() + shortcut_cache[P.category] = cat + shortcut_cat_order += P.category + cat |= list(list(name = P.name, icon = P.icon, notify_icon = P.notify_icon, ref = "\ref[P]")) + + // force the order of a few core categories + shortcut_cat_order = list("General") \ + + sortList(shortcut_cat_order - list("General", "Scanners", "Utilities")) \ + + list("Scanners", "Utilities") + + data["idInserted"] = (id ? 1 : 0) + data["idLink"] = (id ? text("[id.registered_name], [id.assignment]") : "--------") + + data["useRetro"] = retro_mode + + data["cartridge_name"] = cartridge ? cartridge.name : "" + data["stationTime"] = station_time_timestamp() + + data["app"] = list() + current_app.update_ui(user, data) + data["app"] |= list( + "name" = current_app.title, + "icon" = current_app.icon, + "template" = current_app.template, + "has_back" = current_app.has_back) + + return data + +/obj/item/pda/attack_self(mob/user as mob) + user.set_machine(src) + if(active_uplink_check(user)) + return + ui_interact(user) //NanoUI requires this proc + +/obj/item/pda/proc/start_program(datum/data/pda/P) + if(P && ((P in programs) || (cartridge && (P in cartridge.programs)))) + return P.start() + return 0 + +/obj/item/pda/proc/find_program(type) + var/datum/data/pda/A = locate(type) in programs + if(A) + return A + if(cartridge) + A = locate(type) in cartridge.programs + if(A) + return A + return null + +// force the cache to rebuild on update_ui +/obj/item/pda/proc/update_shortcuts() + shortcut_cache.Cut() + +/obj/item/pda/proc/update_programs() + for(var/A in programs) + var/datum/data/pda/P = A + P.pda = src + +/obj/item/pda/Topic(href, href_list) + . = ..() + if(.) + return + + var/mob/user = usr + var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") + var/mob/living/U = usr + if(usr.stat == DEAD) + return 0 + if(!can_use()) //Why reinvent the wheel? There's a proc that does exactly that. + U.unset_machine() + if(ui) + ui.close() + return 0 + + add_fingerprint(U) + U.set_machine(src) + + if(href_list["radiomenu"] && !isnull(cartridge) && !isnull(cartridge.radio)) + cartridge.radio.Topic(href, href_list) + return 1 + + . = 1 + + switch(href_list["choice"]) + if("Home")//Go home, largely replaces the old Return + var/datum/data/pda/app/main_menu/A = find_program(/datum/data/pda/app/main_menu) + if(A) + start_program(A) + if("StartProgram") + if(href_list["program"]) + var/datum/data/pda/app/A = locate(href_list["program"]) + if(A) + start_program(A) + if("Eject")//Ejects the cart, only done from hub. + if(!isnull(cartridge)) + var/turf/T = loc + if(ismob(T)) + T = T.loc + var/obj/item/cartridge/C = cartridge + C.forceMove(T) + if(scanmode in C.programs) + scanmode = null + if(current_app in C.programs) + start_program(find_program(/datum/data/pda/app/main_menu)) + if(C.radio) + C.radio.hostpda = null + for(var/datum/data/pda/P in notifying_programs) + if(P in C.programs) + P.unnotify() + cartridge = null + update_shortcuts() + if("Authenticate")//Checks for ID + id_check(usr, 1) + if("Retro") + retro_mode = !retro_mode + if("Ringtone") + return set_ringtone() + else + if(current_app) + . = current_app.Topic(href, href_list) + +//EXTRA FUNCTIONS=================================== + if((honkamt > 0) && (prob(60)))//For clown virus. + honkamt-- + playsound(loc, 'sound/items/bikehorn.ogg', 30, 1) + + return // return 1 tells it to refresh the UI in NanoUI + +/obj/item/pda/proc/close(mob/user) + var/datum/nanoui/ui = SSnanoui.get_open_ui(user, src, "main") + ui.close() + +/obj/item/pda/verb/verb_reset_pda() + set category = "Object" + set name = "Reset PDA" + set src in usr + + if(issilicon(usr)) + return + + if(can_use(usr)) + start_program(find_program(/datum/data/pda/app/main_menu)) + notifying_programs.Cut() + overlays -= image('icons/obj/pda.dmi', "pda-r") + to_chat(usr, "You press the reset button on \the [src].") + else + to_chat(usr, "You cannot do this while restrained.") + +/obj/item/pda/AltClick(mob/user) + ..() + if(issilicon(user)) + return + + if(can_use(user)) + if(id) + remove_id(user) + else + to_chat(user, "This PDA does not have an ID in it!") + +/obj/item/pda/CtrlClick(mob/user) + ..() + if(issilicon(user)) + return + + if(can_use(user)) + remove_pen(user) + +/obj/item/pda/proc/remove_id(mob/user) + if(id) + if(ismob(loc)) + var/mob/M = loc + M.put_in_hands(id) + to_chat(user, "You remove the ID from the [name].") + else + id.forceMove(get_turf(src)) + overlays -= image('icons/goonstation/objects/pda_overlay.dmi', id.icon_state) + id = null + +/obj/item/pda/verb/verb_remove_id() + set category = "Object" + set name = "Remove id" + set src in usr + + if(issilicon(usr)) + return + + if( can_use(usr) ) + if(id) + remove_id(usr) + else + to_chat(usr, "This PDA does not have an ID in it.") + else + to_chat(usr, "You cannot do this while restrained.") + +/obj/item/pda/verb/verb_remove_pen() + set category = "Object" + set name = "Remove pen" + set src in usr + remove_pen(usr) + +/obj/item/pda/proc/remove_pen(mob/user) + + if(issilicon(user)) + return + + if( can_use(user) ) + var/obj/item/pen/O = locate() in src + if(O) + to_chat(user, "You remove the [O] from [src].") + if(istype(loc, /mob)) + var/mob/M = loc + if(M.get_active_hand() == null) + M.put_in_hands(O) + return + O.forceMove(get_turf(src)) + else + to_chat(user, "This PDA does not have a pen in it.") + else + to_chat(user, "You cannot do this while restrained.") + +/obj/item/pda/proc/id_check(mob/user as mob, choice as num)//To check for IDs; 1 for in-pda use, 2 for out of pda use. + if(choice == 1) + if(id) + remove_id(user) + else + var/obj/item/I = user.get_active_hand() + if(istype(I, /obj/item/card/id)) + user.drop_item() + I.forceMove(src) + id = I + else + var/obj/item/card/I = user.get_active_hand() + if(istype(I, /obj/item/card/id) && I:registered_name) + var/obj/old_id = id + user.drop_item() + I.forceMove(src) + id = I + user.put_in_hands(old_id) + return + +/obj/item/pda/attackby(obj/item/C as obj, mob/user as mob, params) + ..() + if(istype(C, /obj/item/cartridge) && !cartridge) + cartridge = C + user.drop_item() + cartridge.forceMove(src) + cartridge.update_programs(src) + update_shortcuts() + to_chat(user, "You insert [cartridge] into [src].") + if(cartridge.radio) + cartridge.radio.hostpda = src + + else if(istype(C, /obj/item/card/id)) + var/obj/item/card/id/idcard = C + if(!idcard.registered_name) + to_chat(user, "\The [src] rejects the ID.") + return + if(!owner) + owner = idcard.registered_name + ownjob = idcard.assignment + ownrank = idcard.rank + name = "PDA-[owner] ([ownjob])" + to_chat(user, "Card scanned.") + else + //Basic safety check. If either both objects are held by user or PDA is on ground and card is in hand. + if(((src in user.contents) && (C in user.contents)) || (istype(loc, /turf) && in_range(src, user) && (C in user.contents)) ) + if( can_use(user) )//If they can still act. + id_check(user, 2) + to_chat(user, "You put the ID into \the [src]'s slot.
        You can remove it with ALT click.
        ") + overlays += image('icons/goonstation/objects/pda_overlay.dmi', C.icon_state) + + else if(istype(C, /obj/item/paicard) && !src.pai) + user.drop_item() + C.forceMove(src) + pai = C + to_chat(user, "You slot \the [C] into [src].") + else if(istype(C, /obj/item/pen)) + var/obj/item/pen/O = locate() in src + if(O) + to_chat(user, "There is already a pen in \the [src].") + else + user.drop_item() + C.forceMove(src) + to_chat(user, "You slide \the [C] into \the [src].") + else if(istype(C, /obj/item/nanomob_card)) + if(cartridge && istype(cartridge, /obj/item/cartridge/mob_hunt_game)) + cartridge.attackby(C, user, params) + +/obj/item/pda/attack(mob/living/C as mob, mob/living/user as mob) + if(istype(C, /mob/living/carbon) && scanmode) + scanmode.scan_mob(C, user) + +/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user as mob, proximity) + if(proximity && scanmode) + scanmode.scan_atom(A, user) + +/obj/item/pda/proc/explode() //This needs tuning. + if(!detonate) + return + var/turf/T = get_turf(src.loc) + + if(ismob(loc)) + var/mob/M = loc + M.show_message("Your [src] explodes!", 1) + + if(T) + T.hotspot_expose(700,125) + + explosion(T, -1, -1, 2, 3) + qdel(src) + return + +/obj/item/pda/Destroy() + PDAs -= src + var/T = get_turf(loc) + if(id) + id.forceMove(T) + if(pai) + pai.forceMove(T) + current_app = null + scanmode = null + QDEL_LIST(programs) + QDEL_NULL(cartridge) + return ..() + +// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP +/obj/item/pda/emp_act(severity) + for(var/atom/A in src) + A.emp_act(severity) + +/obj/item/pda/proc/play_ringtone() + var/S + + if(ttone in ttone_sound) + S = ttone_sound[ttone] + else + S = 'sound/machines/twobeep.ogg' + playsound(loc, S, 50, 1) + for(var/mob/O in hearers(3, loc)) + O.show_message(text("[bicon(src)] *[ttone]*")) + +/obj/item/pda/proc/set_ringtone() + var/t = input("Please enter new ringtone", name, ttone) as text + if(in_range(src, usr) && loc == usr) + if(t) + if(hidden_uplink && hidden_uplink.check_trigger(usr, lowertext(t), lowertext(lock_code))) + to_chat(usr, "The PDA softly beeps.") + close(usr) + else + t = sanitize(copytext(t, 1, 20)) + ttone = t + return 1 + else + close(usr) + return 0 + +/obj/item/pda/process() + if(current_app) + current_app.program_process() + +/obj/item/pda/extinguish_light() + var/datum/data/pda/utility/flashlight/FL = find_program(/datum/data/pda/utility/flashlight) + if(FL && FL.fon) + FL.start() diff --git a/code/modules/pda/ai.dm b/code/modules/pda/ai.dm index 5860ff660f3..ac4e6c9fd6b 100644 --- a/code/modules/pda/ai.dm +++ b/code/modules/pda/ai.dm @@ -101,4 +101,4 @@ if(!pAI.software["messenger"]) to_chat(usr, "You have not purchased the digital messenger!") return 0 - return ..() && !pAI.silence_time \ No newline at end of file + return ..() && !pAI.silence_time diff --git a/code/modules/pda/cart.dm b/code/modules/pda/cart.dm index 7b7ec95b0e3..6339f5375d7 100644 --- a/code/modules/pda/cart.dm +++ b/code/modules/pda/cart.dm @@ -1,325 +1,325 @@ -/obj/item/cartridge - name = "generic cartridge" - desc = "A data cartridge for portable microcomputers." - icon = 'icons/obj/pda.dmi' - icon_state = "cart" - item_state = "electronic" - w_class = WEIGHT_CLASS_TINY - - var/obj/item/integrated_radio/radio = null - - var/charges = 0 - - var/list/stored_data = list() - var/list/programs = list() - var/list/messenger_plugins = list() - -/obj/item/cartridge/Destroy() - QDEL_NULL(radio) - QDEL_LIST(programs) - QDEL_LIST(messenger_plugins) - return ..() - -/obj/item/cartridge/proc/update_programs(obj/item/pda/pda) - for(var/A in programs) - var/datum/data/pda/P = A - P.pda = pda - for(var/A in messenger_plugins) - var/datum/data/pda/messenger_plugin/P = A - P.pda = pda - -/obj/item/cartridge/engineering - name = "Power-ON Cartridge" - icon_state = "cart-e" - programs = list( - new/datum/data/pda/app/power, - new/datum/data/pda/utility/scanmode/halogen) - -/obj/item/cartridge/atmos - name = "BreatheDeep Cartridge" - icon_state = "cart-a" - programs = list(new/datum/data/pda/utility/scanmode/gas) - -/obj/item/cartridge/medical - name = "Med-U Cartridge" - icon_state = "cart-m" - programs = list( - new/datum/data/pda/app/crew_records/medical, - new/datum/data/pda/utility/scanmode/medical) - -/obj/item/cartridge/chemistry - name = "ChemWhiz Cartridge" - icon_state = "cart-chem" - programs = list(new/datum/data/pda/utility/scanmode/reagent) - -/obj/item/cartridge/security - name = "R.O.B.U.S.T. Cartridge" - icon_state = "cart-s" - programs = list( - new/datum/data/pda/app/crew_records/security, - new/datum/data/pda/app/secbot_control) - -/obj/item/cartridge/security/Initialize(mapload) - . = ..() - radio = new /obj/item/integrated_radio/beepsky(src) - -/obj/item/cartridge/detective - name = "D.E.T.E.C.T. Cartridge" - icon_state = "cart-s" - programs = list( - new/datum/data/pda/app/crew_records/medical, - new/datum/data/pda/utility/scanmode/medical, - - new/datum/data/pda/app/crew_records/security) - - -/obj/item/cartridge/janitor - name = "CustodiPRO Cartridge" - desc = "The ultimate in clean-room design." - icon_state = "cart-j" - programs = list(new/datum/data/pda/app/janitor) - -/obj/item/cartridge/lawyer - name = "P.R.O.V.E. Cartridge" - icon_state = "cart-s" - programs = list(new/datum/data/pda/app/crew_records/security) - -/obj/item/cartridge/clown - name = "Honkworks 5.0" - icon_state = "cart-clown" - charges = 5 - programs = list(new/datum/data/pda/utility/honk) - messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/clown) - -/obj/item/cartridge/mime - name = "Gestur-O 1000" - icon_state = "cart-mi" - charges = 5 - messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/mime) - -/* -/obj/item/cartridge/botanist - name = "Green Thumb v4.20" - icon_state = "cart-b" - access_flora = 1 -*/ - -/obj/item/cartridge/signal - name = "generic signaler cartridge" - desc = "A data cartridge with an integrated radio signaler module." - programs = list(new/datum/data/pda/app/signaller) - -/obj/item/cartridge/signal/Initialize(mapload) - . = ..() - radio = new /obj/item/integrated_radio/signal(src) - -/obj/item/cartridge/signal/toxins - name = "Signal Ace 2" - desc = "Complete with integrated radio signaler!" - icon_state = "cart-tox" - programs = list( - new/datum/data/pda/utility/scanmode/gas, - - new/datum/data/pda/utility/scanmode/reagent, - - new/datum/data/pda/app/signaller) - -/obj/item/cartridge/quartermaster - name = "Space Parts & Space Vendors Cartridge" - desc = "Perfect for the Quartermaster on the go!" - icon_state = "cart-q" - programs = list( - new/datum/data/pda/app/supply, - new/datum/data/pda/app/mule_control) - -/obj/item/cartridge/quartermaster/Initialize(mapload) - . = ..() - radio = new /obj/item/integrated_radio/mule(src) - -/obj/item/cartridge/head - name = "Easy-Record DELUXE" - icon_state = "cart-h" - programs = list(new/datum/data/pda/app/status_display) - -/obj/item/cartridge/hop - name = "HumanResources9001" - icon_state = "cart-h" - programs = list( - new/datum/data/pda/app/crew_records/security, - - new/datum/data/pda/app/janitor, - - new/datum/data/pda/app/supply, - new/datum/data/pda/app/mule_control, - - new/datum/data/pda/app/status_display) - -/obj/item/cartridge/hop/Initialize(mapload) - . = ..() - radio = new /obj/item/integrated_radio/mule(src) - -/obj/item/cartridge/hos - name = "R.O.B.U.S.T. DELUXE" - icon_state = "cart-hos" - programs = list( - new/datum/data/pda/app/crew_records/security, - new/datum/data/pda/app/secbot_control, - - new/datum/data/pda/app/status_display) - -/obj/item/cartridge/hos/Initialize(mapload) - . = ..() - radio = new /obj/item/integrated_radio/beepsky(src) - -/obj/item/cartridge/ce - name = "Power-On DELUXE" - icon_state = "cart-ce" - programs = list( - new/datum/data/pda/app/power, - new/datum/data/pda/utility/scanmode/halogen, - - new/datum/data/pda/utility/scanmode/gas, - - new/datum/data/pda/app/status_display) - -/obj/item/cartridge/cmo - name = "Med-U DELUXE" - icon_state = "cart-cmo" - programs = list( - new/datum/data/pda/app/crew_records/medical, - new/datum/data/pda/utility/scanmode/medical, - - new/datum/data/pda/utility/scanmode/reagent, - - new/datum/data/pda/app/status_display) - -/obj/item/cartridge/rd - name = "Signal Ace DELUXE" - icon_state = "cart-rd" - programs = list( - new/datum/data/pda/utility/scanmode/gas, - - new/datum/data/pda/utility/scanmode/reagent, - - new/datum/data/pda/app/signaller, - - new/datum/data/pda/app/status_display) - -/obj/item/cartridge/rd/Initialize(mapload) - . = ..() - radio = new /obj/item/integrated_radio/signal(src) - -/obj/item/cartridge/captain - name = "Value-PAK Cartridge" - desc = "Now with 200% more value!" - icon_state = "cart-c" - programs = list( - new/datum/data/pda/app/power, - new/datum/data/pda/utility/scanmode/halogen, - - new/datum/data/pda/utility/scanmode/gas, - - new/datum/data/pda/app/crew_records/medical, - new/datum/data/pda/utility/scanmode/medical, - - new/datum/data/pda/utility/scanmode/reagent, - - new/datum/data/pda/app/crew_records/security, - new/datum/data/pda/app/secbot_control, - - new/datum/data/pda/app/janitor, - - new/datum/data/pda/app/supply, - new/datum/data/pda/app/mule_control, - - new/datum/data/pda/app/status_display) - -/obj/item/cartridge/captain/Initialize(mapload) - . = ..() - radio = new /obj/item/integrated_radio/beepsky(src) - -/obj/item/cartridge/supervisor - name = "Easy-Record DELUXE" - icon_state = "cart-h" - programs = list( - new/datum/data/pda/app/crew_records/security, - - new/datum/data/pda/app/status_display) - -/obj/item/cartridge/centcom - name = "Value-PAK Cartridge" - desc = "Now with 200% more value!" - icon_state = "cart-c" - programs = list( - new/datum/data/pda/app/power, - new/datum/data/pda/utility/scanmode/halogen, - - new/datum/data/pda/utility/scanmode/gas, - - new/datum/data/pda/app/crew_records/medical, - new/datum/data/pda/utility/scanmode/medical, - - new/datum/data/pda/utility/scanmode/reagent, - - new/datum/data/pda/app/crew_records/security, - new/datum/data/pda/app/secbot_control, - - new/datum/data/pda/app/janitor, - - new/datum/data/pda/app/supply, - new/datum/data/pda/app/mule_control, - - new/datum/data/pda/app/status_display) - -/obj/item/cartridge/centcom/Initialize(mapload) - . = ..() - radio = new /obj/item/integrated_radio/beepsky(src) - -/obj/item/cartridge/syndicate - name = "Detomatix Cartridge" - icon_state = "cart" - var/initial_remote_door_id = "smindicate" //Make sure this matches the syndicate shuttle's shield/door id!! //don't ask about the name, testing. - charges = 4 - programs = list(new/datum/data/pda/utility/toggle_door) - messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/detonate) - -/obj/item/cartridge/syndicate/Initialize(mapload) - . = ..() - var/datum/data/pda/utility/toggle_door/D = programs[1] - if(istype(D)) - D.remote_door_id = initial_remote_door_id - -/obj/item/cartridge/frame - name = "F.R.A.M.E. cartridge" - icon_state = "cart" - charges = 5 - var/telecrystals = 0 - messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/frame) - -/obj/item/cartridge/mob_hunt_game - name = "Nano-Mob Hunter GO! Cartridge" - desc = "The hit new PDA game that lets you track down and capture your favorite Nano-Mobs living in your world!" - icon_state = "cart-eye" - programs = list(new/datum/data/pda/app/mob_hunter_game) - var/emagged = 0 - -/obj/item/cartridge/mob_hunt_game/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/nanomob_card)) - var/obj/item/nanomob_card/card = O - var/datum/data/pda/app/mob_hunter_game/my_game = programs[1] - if(my_game.register_capture(card.mob_data)) - to_chat(user, "Transfer successful!") - qdel(card) - else - to_chat(user, "Transfer failed. Could not read mob data from card.") - else - ..() - -/obj/item/cartridge/mob_hunt_game/emag_act(mob/user) - if(!emagged) - emagged = 1 - var/datum/data/pda/app/mob_hunter_game/my_game = programs[1] - my_game.hacked = 1 - to_chat(user, "TR4P_M45T3R.mod successfully initialized. ToS violated. User Agreement nullified. Gotta pwn them all.") - to_chat(user, "You can now create trapped versions of any mob in your collection that will damage hunters who attempt to capture it.") - description_antag = "This copy of Nano-Mob Hunter GO! has been hacked to allow the creation of trap mobs which will cause any PDA that attempts to capture it to shock anyone holding it. Hacked copies of the game will not trigger the trap." \ No newline at end of file +/obj/item/cartridge + name = "generic cartridge" + desc = "A data cartridge for portable microcomputers." + icon = 'icons/obj/pda.dmi' + icon_state = "cart" + item_state = "electronic" + w_class = WEIGHT_CLASS_TINY + + var/obj/item/integrated_radio/radio = null + + var/charges = 0 + + var/list/stored_data = list() + var/list/programs = list() + var/list/messenger_plugins = list() + +/obj/item/cartridge/Destroy() + QDEL_NULL(radio) + QDEL_LIST(programs) + QDEL_LIST(messenger_plugins) + return ..() + +/obj/item/cartridge/proc/update_programs(obj/item/pda/pda) + for(var/A in programs) + var/datum/data/pda/P = A + P.pda = pda + for(var/A in messenger_plugins) + var/datum/data/pda/messenger_plugin/P = A + P.pda = pda + +/obj/item/cartridge/engineering + name = "Power-ON Cartridge" + icon_state = "cart-e" + programs = list( + new/datum/data/pda/app/power, + new/datum/data/pda/utility/scanmode/halogen) + +/obj/item/cartridge/atmos + name = "BreatheDeep Cartridge" + icon_state = "cart-a" + programs = list(new/datum/data/pda/utility/scanmode/gas) + +/obj/item/cartridge/medical + name = "Med-U Cartridge" + icon_state = "cart-m" + programs = list( + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical) + +/obj/item/cartridge/chemistry + name = "ChemWhiz Cartridge" + icon_state = "cart-chem" + programs = list(new/datum/data/pda/utility/scanmode/reagent) + +/obj/item/cartridge/security + name = "R.O.B.U.S.T. Cartridge" + icon_state = "cart-s" + programs = list( + new/datum/data/pda/app/crew_records/security, + new/datum/data/pda/app/secbot_control) + +/obj/item/cartridge/security/Initialize(mapload) + . = ..() + radio = new /obj/item/integrated_radio/beepsky(src) + +/obj/item/cartridge/detective + name = "D.E.T.E.C.T. Cartridge" + icon_state = "cart-s" + programs = list( + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + + new/datum/data/pda/app/crew_records/security) + + +/obj/item/cartridge/janitor + name = "CustodiPRO Cartridge" + desc = "The ultimate in clean-room design." + icon_state = "cart-j" + programs = list(new/datum/data/pda/app/janitor) + +/obj/item/cartridge/lawyer + name = "P.R.O.V.E. Cartridge" + icon_state = "cart-s" + programs = list(new/datum/data/pda/app/crew_records/security) + +/obj/item/cartridge/clown + name = "Honkworks 5.0" + icon_state = "cart-clown" + charges = 5 + programs = list(new/datum/data/pda/utility/honk) + messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/clown) + +/obj/item/cartridge/mime + name = "Gestur-O 1000" + icon_state = "cart-mi" + charges = 5 + messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/mime) + +/* +/obj/item/cartridge/botanist + name = "Green Thumb v4.20" + icon_state = "cart-b" + access_flora = 1 +*/ + +/obj/item/cartridge/signal + name = "generic signaler cartridge" + desc = "A data cartridge with an integrated radio signaler module." + programs = list(new/datum/data/pda/app/signaller) + +/obj/item/cartridge/signal/Initialize(mapload) + . = ..() + radio = new /obj/item/integrated_radio/signal(src) + +/obj/item/cartridge/signal/toxins + name = "Signal Ace 2" + desc = "Complete with integrated radio signaler!" + icon_state = "cart-tox" + programs = list( + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/signaller) + +/obj/item/cartridge/quartermaster + name = "Space Parts & Space Vendors Cartridge" + desc = "Perfect for the Quartermaster on the go!" + icon_state = "cart-q" + programs = list( + new/datum/data/pda/app/supply, + new/datum/data/pda/app/mule_control) + +/obj/item/cartridge/quartermaster/Initialize(mapload) + . = ..() + radio = new /obj/item/integrated_radio/mule(src) + +/obj/item/cartridge/head + name = "Easy-Record DELUXE" + icon_state = "cart-h" + programs = list(new/datum/data/pda/app/status_display) + +/obj/item/cartridge/hop + name = "HumanResources9001" + icon_state = "cart-h" + programs = list( + new/datum/data/pda/app/crew_records/security, + + new/datum/data/pda/app/janitor, + + new/datum/data/pda/app/supply, + new/datum/data/pda/app/mule_control, + + new/datum/data/pda/app/status_display) + +/obj/item/cartridge/hop/Initialize(mapload) + . = ..() + radio = new /obj/item/integrated_radio/mule(src) + +/obj/item/cartridge/hos + name = "R.O.B.U.S.T. DELUXE" + icon_state = "cart-hos" + programs = list( + new/datum/data/pda/app/crew_records/security, + new/datum/data/pda/app/secbot_control, + + new/datum/data/pda/app/status_display) + +/obj/item/cartridge/hos/Initialize(mapload) + . = ..() + radio = new /obj/item/integrated_radio/beepsky(src) + +/obj/item/cartridge/ce + name = "Power-On DELUXE" + icon_state = "cart-ce" + programs = list( + new/datum/data/pda/app/power, + new/datum/data/pda/utility/scanmode/halogen, + + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/app/status_display) + +/obj/item/cartridge/cmo + name = "Med-U DELUXE" + icon_state = "cart-cmo" + programs = list( + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/status_display) + +/obj/item/cartridge/rd + name = "Signal Ace DELUXE" + icon_state = "cart-rd" + programs = list( + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/signaller, + + new/datum/data/pda/app/status_display) + +/obj/item/cartridge/rd/Initialize(mapload) + . = ..() + radio = new /obj/item/integrated_radio/signal(src) + +/obj/item/cartridge/captain + name = "Value-PAK Cartridge" + desc = "Now with 200% more value!" + icon_state = "cart-c" + programs = list( + new/datum/data/pda/app/power, + new/datum/data/pda/utility/scanmode/halogen, + + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/crew_records/security, + new/datum/data/pda/app/secbot_control, + + new/datum/data/pda/app/janitor, + + new/datum/data/pda/app/supply, + new/datum/data/pda/app/mule_control, + + new/datum/data/pda/app/status_display) + +/obj/item/cartridge/captain/Initialize(mapload) + . = ..() + radio = new /obj/item/integrated_radio/beepsky(src) + +/obj/item/cartridge/supervisor + name = "Easy-Record DELUXE" + icon_state = "cart-h" + programs = list( + new/datum/data/pda/app/crew_records/security, + + new/datum/data/pda/app/status_display) + +/obj/item/cartridge/centcom + name = "Value-PAK Cartridge" + desc = "Now with 200% more value!" + icon_state = "cart-c" + programs = list( + new/datum/data/pda/app/power, + new/datum/data/pda/utility/scanmode/halogen, + + new/datum/data/pda/utility/scanmode/gas, + + new/datum/data/pda/app/crew_records/medical, + new/datum/data/pda/utility/scanmode/medical, + + new/datum/data/pda/utility/scanmode/reagent, + + new/datum/data/pda/app/crew_records/security, + new/datum/data/pda/app/secbot_control, + + new/datum/data/pda/app/janitor, + + new/datum/data/pda/app/supply, + new/datum/data/pda/app/mule_control, + + new/datum/data/pda/app/status_display) + +/obj/item/cartridge/centcom/Initialize(mapload) + . = ..() + radio = new /obj/item/integrated_radio/beepsky(src) + +/obj/item/cartridge/syndicate + name = "Detomatix Cartridge" + icon_state = "cart" + var/initial_remote_door_id = "smindicate" //Make sure this matches the syndicate shuttle's shield/door id!! //don't ask about the name, testing. + charges = 4 + programs = list(new/datum/data/pda/utility/toggle_door) + messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/detonate) + +/obj/item/cartridge/syndicate/Initialize(mapload) + . = ..() + var/datum/data/pda/utility/toggle_door/D = programs[1] + if(istype(D)) + D.remote_door_id = initial_remote_door_id + +/obj/item/cartridge/frame + name = "F.R.A.M.E. cartridge" + icon_state = "cart" + charges = 5 + var/telecrystals = 0 + messenger_plugins = list(new/datum/data/pda/messenger_plugin/virus/frame) + +/obj/item/cartridge/mob_hunt_game + name = "Nano-Mob Hunter GO! Cartridge" + desc = "The hit new PDA game that lets you track down and capture your favorite Nano-Mobs living in your world!" + icon_state = "cart-eye" + programs = list(new/datum/data/pda/app/mob_hunter_game) + var/emagged = 0 + +/obj/item/cartridge/mob_hunt_game/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/nanomob_card)) + var/obj/item/nanomob_card/card = O + var/datum/data/pda/app/mob_hunter_game/my_game = programs[1] + if(my_game.register_capture(card.mob_data)) + to_chat(user, "Transfer successful!") + qdel(card) + else + to_chat(user, "Transfer failed. Could not read mob data from card.") + else + ..() + +/obj/item/cartridge/mob_hunt_game/emag_act(mob/user) + if(!emagged) + emagged = 1 + var/datum/data/pda/app/mob_hunter_game/my_game = programs[1] + my_game.hacked = 1 + to_chat(user, "TR4P_M45T3R.mod successfully initialized. ToS violated. User Agreement nullified. Gotta pwn them all.") + to_chat(user, "You can now create trapped versions of any mob in your collection that will damage hunters who attempt to capture it.") + description_antag = "This copy of Nano-Mob Hunter GO! has been hacked to allow the creation of trap mobs which will cause any PDA that attempts to capture it to shock anyone holding it. Hacked copies of the game will not trigger the trap." diff --git a/code/modules/pda/core_apps.dm b/code/modules/pda/core_apps.dm index 2b5ce32355d..cd86e620970 100644 --- a/code/modules/pda/core_apps.dm +++ b/code/modules/pda/core_apps.dm @@ -108,4 +108,4 @@ "reading" = 1 ) if(isnull(data["aircontents"])) - data["aircontents"] = list("reading" = 0) \ No newline at end of file + data["aircontents"] = list("reading" = 0) diff --git a/code/modules/pda/messenger_plugins.dm b/code/modules/pda/messenger_plugins.dm index 2666b82c5df..c69d8890164 100644 --- a/code/modules/pda/messenger_plugins.dm +++ b/code/modules/pda/messenger_plugins.dm @@ -88,4 +88,4 @@ var/obj/item/cartridge/frame/parent_cart = pda.cartridge P.hidden_uplink.uses = parent_cart.telecrystals parent_cart.telecrystals = 0 - P.hidden_uplink.active = TRUE \ No newline at end of file + P.hidden_uplink.active = TRUE diff --git a/code/modules/pda/mob_hunt_game_app.dm b/code/modules/pda/mob_hunt_game_app.dm index 2bb28ba0264..c48ebdd4e9e 100644 --- a/code/modules/pda/mob_hunt_game_app.dm +++ b/code/modules/pda/mob_hunt_game_app.dm @@ -173,4 +173,4 @@ if("Transfer") print_card() if("Set_Trap") - set_trap() \ No newline at end of file + set_trap() diff --git a/code/modules/pda/radio.dm b/code/modules/pda/radio.dm index a49305598e6..3a2f4901caa 100644 --- a/code/modules/pda/radio.dm +++ b/code/modules/pda/radio.dm @@ -1,208 +1,208 @@ -//TODO convert this crap over to proper radios or find a way to utilize regualr radios for this object, this thing needs to go. - -/obj/item/integrated_radio - name = "\improper PDA radio module" - desc = "An electronic radio system of Nanotrasen origin." - icon = 'icons/obj/module.dmi' - icon_state = "power_mod" - var/obj/item/pda/hostpda = null - var/list/botlist = null // list of bots - var/mob/living/simple_animal/bot/active // the active bot; if null, show bot list - var/list/botstatus // the status signal sent by the bot - var/bot_type //The type of bot it is. - var/bot_filter //Determines which radio filter to use. - - var/control_freq = 1447 - - var/on = 0 //Are we currently active?? - var/menu_message = "" - -/obj/item/integrated_radio/Initialize(mapload) - . = ..() - if(istype(loc.loc, /obj/item/pda)) - hostpda = loc.loc - if(bot_filter) - add_to_radio(bot_filter) - -/obj/item/integrated_radio/Destroy() - if(SSradio) - SSradio.remove_object(src, control_freq) - hostpda = null - return ..() - -/obj/item/integrated_radio/proc/post_signal(var/freq, var/key, var/value, var/key2, var/value2, var/key3, var/value3,var/key4, var/value4, s_filter) - -// to_chat(world, "Post: [freq]: [key]=[value], [key2]=[value2]") - var/datum/radio_frequency/frequency = SSradio.return_frequency(freq) - - if(!frequency) - return - - var/datum/signal/signal = new() - signal.source = src - signal.transmission_method = 1 - signal.data[key] = value - if(key2) - signal.data[key2] = value2 - if(key3) - signal.data[key3] = value3 - if(key4) - signal.data[key4] = value4 - - frequency.post_signal(src, signal, filter = s_filter) - -/obj/item/integrated_radio/receive_signal(datum/signal/signal) - if(bot_type && istype(signal.source, /obj/machinery/bot_core) && signal.data["type"] == bot_type) - if(!botlist) - botlist = new() - - var/obj/machinery/bot_core/core = signal.source - - if(istype(core) && !(core.owner in botlist)) - botlist += core.owner - - if(active == core.owner) - var/list/b = signal.data - botstatus = b.Copy() - -/obj/item/integrated_radio/Topic(href, href_list) - ..() - switch(href_list["op"]) - if("control") - active = locate(href_list["bot"]) - spawn(0) - post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) - - if("scanbots") // find all bots - botlist = null - spawn(0) - post_signal(control_freq, "command", "bot_status", s_filter = bot_filter) - - if("botlist") - active = null - - if("stop", "go", "home") - spawn(0) - post_signal(control_freq, "command", href_list["op"], "active", active, s_filter = bot_filter) - post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) - - if("summon") - spawn(0) - post_signal(control_freq, "command", "summon", "active", active, "target", get_turf(hostpda), "useraccess", hostpda.GetAccess(), "user", usr, s_filter = bot_filter) - post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) - -/obj/item/integrated_radio/proc/add_to_radio(bot_filter) //Master filter control for bots. Must be placed in the bot's local New() to support map spawned bots. - if(SSradio) - SSradio.add_object(src, control_freq, filter = bot_filter) - -/obj/item/integrated_radio/honkbot - bot_filter = RADIO_HONKBOT - bot_type = HONK_BOT - -/obj/item/integrated_radio/beepsky - bot_filter = RADIO_SECBOT - bot_type = SEC_BOT - -/obj/item/integrated_radio/medbot - bot_filter = RADIO_MEDBOT - bot_type = MED_BOT - -/obj/item/integrated_radio/floorbot - bot_filter = RADIO_FLOORBOT - bot_type = FLOOR_BOT - -/obj/item/integrated_radio/cleanbot - bot_filter = RADIO_CLEANBOT - bot_type = CLEAN_BOT - -/obj/item/integrated_radio/mule - bot_filter = RADIO_MULEBOT - bot_type = MULE_BOT - -/obj/item/integrated_radio/mule/Topic(href, href_list) - ..() - switch(href_list["op"]) - if("start") - spawn(0) - post_signal(control_freq, "command", "start", "active", active, s_filter = RADIO_MULEBOT) - - if("unload") - spawn(0) - post_signal(control_freq, "command", "unload", "active", active, s_filter = RADIO_MULEBOT) - - if("setdest") - if(GLOB.deliverybeacons) - var/dest = input("Select Bot Destination", "Mulebot [active.suffix] Interlink", active.destination) as null|anything in GLOB.deliverybeacontags - if(dest) - spawn(0) - post_signal(control_freq, "command", "target", "active", active, "destination", dest, s_filter = RADIO_MULEBOT) - - if("retoff") - spawn(0) - post_signal(control_freq, "command", "autoret", "active", active, "value", 0, s_filter = RADIO_MULEBOT) - - if("reton") - spawn(0) - post_signal(control_freq, "command", "autoret", "active", active, "value", 1, s_filter = RADIO_MULEBOT) - - if("pickoff") - spawn(0) - post_signal(control_freq, "command", "autopick", "active", active, "value", 0, s_filter = RADIO_MULEBOT) - - if("pickon") - spawn(0) - post_signal(control_freq, "command", "autopick", "active", active, "value", 1, s_filter = RADIO_MULEBOT) - - spawn(10) - post_signal(control_freq, "command", "bot_status", "active", active, s_filter = RADIO_MULEBOT) - - - -/* - * Radio Cartridge, essentially a signaler. - */ - - -/obj/item/integrated_radio/signal - var/frequency = RSD_FREQ - var/code = 30.0 - var/last_transmission - var/datum/radio_frequency/radio_connection - -/obj/item/integrated_radio/signal/Destroy() - if(SSradio) - SSradio.remove_object(src, frequency) - radio_connection = null - return ..() - -/obj/item/integrated_radio/signal/Initialize(mapload) - . = ..() - if(!SSradio) - return - if(src.frequency < PUBLIC_LOW_FREQ || src.frequency > PUBLIC_HIGH_FREQ) - src.frequency = sanitize_frequency(src.frequency) - - set_frequency(frequency) - -/obj/item/integrated_radio/signal/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency) - -/obj/item/integrated_radio/signal/proc/send_signal(message="ACTIVATE") - - if(last_transmission && world.time < (last_transmission + 5)) - return - last_transmission = world.time - - var/time = time2text(world.realtime,"hh:mm:ss") - var/turf/T = get_turf(src) - lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") - - var/datum/signal/signal = new - signal.source = src - signal.encryption = code - signal.data["message"] = message - - spawn(0) - radio_connection.post_signal(src, signal) \ No newline at end of file +//TODO convert this crap over to proper radios or find a way to utilize regualr radios for this object, this thing needs to go. + +/obj/item/integrated_radio + name = "\improper PDA radio module" + desc = "An electronic radio system of Nanotrasen origin." + icon = 'icons/obj/module.dmi' + icon_state = "power_mod" + var/obj/item/pda/hostpda = null + var/list/botlist = null // list of bots + var/mob/living/simple_animal/bot/active // the active bot; if null, show bot list + var/list/botstatus // the status signal sent by the bot + var/bot_type //The type of bot it is. + var/bot_filter //Determines which radio filter to use. + + var/control_freq = 1447 + + var/on = 0 //Are we currently active?? + var/menu_message = "" + +/obj/item/integrated_radio/Initialize(mapload) + . = ..() + if(istype(loc.loc, /obj/item/pda)) + hostpda = loc.loc + if(bot_filter) + add_to_radio(bot_filter) + +/obj/item/integrated_radio/Destroy() + if(SSradio) + SSradio.remove_object(src, control_freq) + hostpda = null + return ..() + +/obj/item/integrated_radio/proc/post_signal(var/freq, var/key, var/value, var/key2, var/value2, var/key3, var/value3,var/key4, var/value4, s_filter) + +// to_chat(world, "Post: [freq]: [key]=[value], [key2]=[value2]") + var/datum/radio_frequency/frequency = SSradio.return_frequency(freq) + + if(!frequency) + return + + var/datum/signal/signal = new() + signal.source = src + signal.transmission_method = 1 + signal.data[key] = value + if(key2) + signal.data[key2] = value2 + if(key3) + signal.data[key3] = value3 + if(key4) + signal.data[key4] = value4 + + frequency.post_signal(src, signal, filter = s_filter) + +/obj/item/integrated_radio/receive_signal(datum/signal/signal) + if(bot_type && istype(signal.source, /obj/machinery/bot_core) && signal.data["type"] == bot_type) + if(!botlist) + botlist = new() + + var/obj/machinery/bot_core/core = signal.source + + if(istype(core) && !(core.owner in botlist)) + botlist += core.owner + + if(active == core.owner) + var/list/b = signal.data + botstatus = b.Copy() + +/obj/item/integrated_radio/Topic(href, href_list) + ..() + switch(href_list["op"]) + if("control") + active = locate(href_list["bot"]) + spawn(0) + post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) + + if("scanbots") // find all bots + botlist = null + spawn(0) + post_signal(control_freq, "command", "bot_status", s_filter = bot_filter) + + if("botlist") + active = null + + if("stop", "go", "home") + spawn(0) + post_signal(control_freq, "command", href_list["op"], "active", active, s_filter = bot_filter) + post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) + + if("summon") + spawn(0) + post_signal(control_freq, "command", "summon", "active", active, "target", get_turf(hostpda), "useraccess", hostpda.GetAccess(), "user", usr, s_filter = bot_filter) + post_signal(control_freq, "command", "bot_status", "active", active, s_filter = bot_filter) + +/obj/item/integrated_radio/proc/add_to_radio(bot_filter) //Master filter control for bots. Must be placed in the bot's local New() to support map spawned bots. + if(SSradio) + SSradio.add_object(src, control_freq, filter = bot_filter) + +/obj/item/integrated_radio/honkbot + bot_filter = RADIO_HONKBOT + bot_type = HONK_BOT + +/obj/item/integrated_radio/beepsky + bot_filter = RADIO_SECBOT + bot_type = SEC_BOT + +/obj/item/integrated_radio/medbot + bot_filter = RADIO_MEDBOT + bot_type = MED_BOT + +/obj/item/integrated_radio/floorbot + bot_filter = RADIO_FLOORBOT + bot_type = FLOOR_BOT + +/obj/item/integrated_radio/cleanbot + bot_filter = RADIO_CLEANBOT + bot_type = CLEAN_BOT + +/obj/item/integrated_radio/mule + bot_filter = RADIO_MULEBOT + bot_type = MULE_BOT + +/obj/item/integrated_radio/mule/Topic(href, href_list) + ..() + switch(href_list["op"]) + if("start") + spawn(0) + post_signal(control_freq, "command", "start", "active", active, s_filter = RADIO_MULEBOT) + + if("unload") + spawn(0) + post_signal(control_freq, "command", "unload", "active", active, s_filter = RADIO_MULEBOT) + + if("setdest") + if(GLOB.deliverybeacons) + var/dest = input("Select Bot Destination", "Mulebot [active.suffix] Interlink", active.destination) as null|anything in GLOB.deliverybeacontags + if(dest) + spawn(0) + post_signal(control_freq, "command", "target", "active", active, "destination", dest, s_filter = RADIO_MULEBOT) + + if("retoff") + spawn(0) + post_signal(control_freq, "command", "autoret", "active", active, "value", 0, s_filter = RADIO_MULEBOT) + + if("reton") + spawn(0) + post_signal(control_freq, "command", "autoret", "active", active, "value", 1, s_filter = RADIO_MULEBOT) + + if("pickoff") + spawn(0) + post_signal(control_freq, "command", "autopick", "active", active, "value", 0, s_filter = RADIO_MULEBOT) + + if("pickon") + spawn(0) + post_signal(control_freq, "command", "autopick", "active", active, "value", 1, s_filter = RADIO_MULEBOT) + + spawn(10) + post_signal(control_freq, "command", "bot_status", "active", active, s_filter = RADIO_MULEBOT) + + + +/* + * Radio Cartridge, essentially a signaler. + */ + + +/obj/item/integrated_radio/signal + var/frequency = RSD_FREQ + var/code = 30.0 + var/last_transmission + var/datum/radio_frequency/radio_connection + +/obj/item/integrated_radio/signal/Destroy() + if(SSradio) + SSradio.remove_object(src, frequency) + radio_connection = null + return ..() + +/obj/item/integrated_radio/signal/Initialize(mapload) + . = ..() + if(!SSradio) + return + if(src.frequency < PUBLIC_LOW_FREQ || src.frequency > PUBLIC_HIGH_FREQ) + src.frequency = sanitize_frequency(src.frequency) + + set_frequency(frequency) + +/obj/item/integrated_radio/signal/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency) + +/obj/item/integrated_radio/signal/proc/send_signal(message="ACTIVATE") + + if(last_transmission && world.time < (last_transmission + 5)) + return + last_transmission = world.time + + var/time = time2text(world.realtime,"hh:mm:ss") + var/turf/T = get_turf(src) + lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") + + var/datum/signal/signal = new + signal.source = src + signal.encryption = code + signal.data["message"] = message + + spawn(0) + radio_connection.post_signal(src, signal) diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 94978eec265..3757d7e8cd1 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -1,1384 +1,1384 @@ -#define APC_WIRE_IDSCAN 1 -#define APC_WIRE_MAIN_POWER1 2 -#define APC_WIRE_MAIN_POWER2 3 -#define APC_WIRE_AI_CONTROL 4 - -//update_state -#define UPSTATE_CELL_IN 1 -#define UPSTATE_OPENED1 2 -#define UPSTATE_OPENED2 4 -#define UPSTATE_MAINT 8 -#define UPSTATE_BROKE 16 -#define UPSTATE_BLUESCREEN 32 -#define UPSTATE_WIREEXP 64 -#define UPSTATE_ALLGOOD 128 - -//update_overlay -#define APC_UPOVERLAY_CHARGEING0 1 -#define APC_UPOVERLAY_CHARGEING1 2 -#define APC_UPOVERLAY_CHARGEING2 4 -#define APC_UPOVERLAY_EQUIPMENT0 8 -#define APC_UPOVERLAY_EQUIPMENT1 16 -#define APC_UPOVERLAY_EQUIPMENT2 32 -#define APC_UPOVERLAY_LIGHTING0 64 -#define APC_UPOVERLAY_LIGHTING1 128 -#define APC_UPOVERLAY_LIGHTING2 256 -#define APC_UPOVERLAY_ENVIRON0 512 -#define APC_UPOVERLAY_ENVIRON1 1024 -#define APC_UPOVERLAY_ENVIRON2 2048 -#define APC_UPOVERLAY_LOCKED 4096 - -#define APC_UPDATE_ICON_COOLDOWN 200 // 20 seconds - -// APC malf status -#define APC_MALF_NOT_HACKED 1 -#define APC_MALF_HACKED 2 // APC hacked by user, and user is in its core. -#define APC_MALF_SHUNTED_HERE 3 // User is shunted in this APC -#define APC_MALF_SHUNTED_OTHER 4 // User is shunted in another APC - -// the Area Power Controller (APC), formerly Power Distribution Unit (PDU) -// one per area, needs wire conection to power network through a terminal - -// controls power to devices in that area -// may be opened to change power cell -// three different channels (lighting/equipment/environ) - may each be set to on, off, or auto - - -//NOTE: STUFF STOLEN FROM AIRLOCK.DM thx - - -/obj/machinery/power/apc - name = "area power controller" - desc = "A control terminal for the area electrical systems." - icon_state = "apc0" - use_power = NO_POWER_USE - max_integrity = 200 - integrity_failure = 50 - resistance_flags = FIRE_PROOF - req_access = list(ACCESS_ENGINE_EQUIP) - siemens_strength = 1 - damage_deflection = 10 - var/area/area - var/areastring = null - var/obj/item/stock_parts/cell/cell - var/start_charge = 90 // initial cell charge % - var/cell_type = 2500 //Base cell has 2500 capacity. Enter the path of a different cell you want to use. cell determines charge rates, max capacity, ect. These can also be changed with other APC vars, but isn't recommended to minimize the risk of accidental usage of dirty editted APCs - var/opened = 0 //0=closed, 1=opened, 2=cover removed - var/shorted = 0 - var/lighting = 3 - var/equipment = 3 - var/environ = 3 - var/operating = 1 - var/charging = 0 - var/chargemode = 1 - var/chargecount = 0 - var/locked = 1 - var/coverlocked = 1 - var/aidisabled = 0 - var/tdir = null - var/obj/machinery/power/terminal/terminal = null - var/lastused_light = 0 - var/lastused_equip = 0 - var/lastused_environ = 0 - var/lastused_total = 0 - var/main_status = 0 - powernet = 0 // set so that APCs aren't found as powernet nodes //Hackish, Horrible, was like this before I changed it :( - var/malfhack = 0 //New var for my changes to AI malf. --NeoFite - var/mob/living/silicon/ai/malfai = null //See above --NeoFite - var/debug= 0 - var/autoflag= 0 // 0 = off, 1= eqp and lights off, 2 = eqp off, 3 = all on. -// luminosity = 1 - var/has_electronics = 0 // 0 - none, 1 - plugged in, 2 - secured by screwdriver - var/overload = 1 //used for the Blackout malf module - var/beenhit = 0 // used for counting how many times it has been hit, used for Aliens at the moment - var/mob/living/silicon/ai/occupier = null - var/longtermpower = 10 - var/update_state = -1 - var/update_overlay = -1 - var/global/status_overlays = 0 - var/updating_icon = 0 - var/datum/wires/apc/wires = null - //var/debug = 0 - var/global/list/status_overlays_lock - var/global/list/status_overlays_charging - var/global/list/status_overlays_equipment - var/global/list/status_overlays_lighting - var/global/list/status_overlays_environ - var/indestructible = 0 // If set, prevents aliens from destroying it - var/keep_preset_name = 0 - - var/report_power_alarm = TRUE - - var/shock_proof = 0 //if set to 1, this APC will not arc bolts of electricity if it's overloaded. - - // Nightshift - var/nightshift_lights = FALSE - var/last_nightshift_switch = 0 - -/obj/machinery/power/apc/worn_out - name = "\improper Worn out APC" - keep_preset_name = 1 - locked = 0 - environ = 0 - equipment = 0 - lighting = 0 - operating = 0 - -/obj/machinery/power/apc/noalarm - report_power_alarm = FALSE - -/obj/machinery/power/apc/syndicate //general syndicate access - req_access = list(ACCESS_SYNDICATE) - report_power_alarm = FALSE - -/obj/item/apc_electronics - name = "power control module" - desc = "Heavy-duty switching circuits for power control." - icon = 'icons/obj/module.dmi' - icon_state = "power_mod" - w_class = WEIGHT_CLASS_SMALL - origin_tech = "engineering=2;programming=1" - item_state = "electronic" - flags = CONDUCT - usesound = 'sound/items/deconstruct.ogg' - toolspeed = 1 - -/obj/machinery/power/apc/get_cell() - return cell - -/obj/machinery/power/apc/connect_to_network() - //Override because the APC does not directly connect to the network; it goes through a terminal. - //The terminal is what the power computer looks for anyway. - if(terminal) - terminal.connect_to_network() - -/obj/machinery/power/apc/New(turf/loc, ndir, building = 0) - if(!armor) - armor = list("melee" = 20, "bullet" = 20, "laser" = 10, "energy" = 100, "bomb" = 30, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 50) - ..() - GLOB.apcs += src - GLOB.apcs = sortAtom(GLOB.apcs) - - wires = new(src) - // offset 24 pixels in direction of dir - // this allows the APC to be embedded in a wall, yet still inside an area - if(building) - setDir(ndir) - tdir = dir // to fix Vars bug - setDir(SOUTH) - - pixel_x = (src.tdir & 3)? 0 : (src.tdir == 4 ? 24 : -24) - pixel_y = (src.tdir & 3)? (src.tdir ==1 ? 24 : -24) : 0 - if(building) - area = get_area(src) - area.apc |= src - opened = 1 - operating = 0 - name = "[area.name] APC" - stat |= MAINT - update_icon() - addtimer(CALLBACK(src, .proc/update), 5) - -/obj/machinery/power/apc/Destroy() - GLOB.apcs -= src - if(malfai && operating) - malfai.malf_picker.processing_time = Clamp(malfai.malf_picker.processing_time - 10,0,1000) - area.power_light = 0 - area.power_equip = 0 - area.power_environ = 0 - area.power_change() - if(occupier) - malfvacate(1) - QDEL_NULL(wires) - QDEL_NULL(cell) - if(terminal) - disconnect_terminal() - area.apc -= src - return ..() - -/obj/machinery/power/apc/proc/make_terminal() - // create a terminal object at the same position as original turf loc - // wires will attach to this - terminal = new/obj/machinery/power/terminal(src.loc) - terminal.setDir(tdir) - terminal.master = src - -/obj/machinery/power/apc/Initialize(mapload) - . = ..() - if(!mapload) - return - has_electronics = 2 //installed and secured - // is starting with a power cell installed, create it and set its charge level - if(cell_type) - cell = new/obj/item/stock_parts/cell/upgraded(src) - cell.maxcharge = cell_type // cell_type is maximum charge (old default was 1000 or 2500 (values one and two respectively) - cell.charge = start_charge * cell.maxcharge / 100 // (convert percentage to actual value) - - var/area/A = get_area(src) - - - //if area isn't specified use current - if(keep_preset_name) - if(isarea(A)) - area = A - // no-op, keep the name - else if(isarea(A) && src.areastring == null) - area = A - name = "\improper [area.name] APC" - else - area = get_area_name(areastring) - name = "\improper [area.name] APC" - area.apc |= src - - update_icon() - - make_terminal() - - addtimer(CALLBACK(src, .proc/update), 5) - -/obj/machinery/power/apc/examine(mob/user) - . = ..() - if(in_range(user, src)) - if(stat & BROKEN) - . += "Looks broken." - else if(opened) - if(has_electronics && terminal) - . += "The cover is [opened==2?"removed":"open"] and the power cell is [ cell ? "installed" : "missing"]." - else if(!has_electronics && terminal) - . += "There are some wires but no electronics." - else if(has_electronics && !terminal) - . += "Electronics installed but not wired." - else /* if(!has_electronics && !terminal) */ - . += "There is no electronics nor connected wires." - else - if(stat & MAINT) - . += "The cover is closed. Something wrong with it: it doesn't work." - else if(malfhack) - . += "The cover is broken. It may be hard to force it open." - else - . += "The cover is closed." - -// update the APC icon to show the three base states -// also add overlays for indicator lights -/obj/machinery/power/apc/update_icon(force_update = FALSE) - - if(!status_overlays || force_update) - status_overlays = 1 - status_overlays_lock = new - status_overlays_charging = new - status_overlays_equipment = new - status_overlays_lighting = new - status_overlays_environ = new - - status_overlays_lock.len = 2 - status_overlays_charging.len = 3 - status_overlays_equipment.len = 4 - status_overlays_lighting.len = 4 - status_overlays_environ.len = 4 - - status_overlays_lock[1] = image(icon, "apcox-0") // 0=blue 1=red - status_overlays_lock[2] = image(icon, "apcox-1") - - status_overlays_charging[1] = image(icon, "apco3-0") - status_overlays_charging[2] = image(icon, "apco3-1") - status_overlays_charging[3] = image(icon, "apco3-2") - - status_overlays_equipment[1] = image(icon, "apco0-0") // 0=red, 1=green, 2=blue - status_overlays_equipment[2] = image(icon, "apco0-1") - status_overlays_equipment[3] = image(icon, "apco0-2") - status_overlays_equipment[4] = image(icon, "apco0-3") - - status_overlays_lighting[1] = image(icon, "apco1-0") - status_overlays_lighting[2] = image(icon, "apco1-1") - status_overlays_lighting[3] = image(icon, "apco1-2") - status_overlays_lighting[4] = image(icon, "apco1-3") - - status_overlays_environ[1] = image(icon, "apco2-0") - status_overlays_environ[2] = image(icon, "apco2-1") - status_overlays_environ[3] = image(icon, "apco2-2") - status_overlays_environ[4] = image(icon, "apco2-3") - - - - var/update = check_updates() //returns 0 if no need to update icons. - // 1 if we need to update the icon_state - // 2 if we need to update the overlays - if(!update && !force_update) - return - - if(force_update || update & 1) // Updating the icon state - if(update_state & UPSTATE_ALLGOOD) - icon_state = "apc0" - else if(update_state & (UPSTATE_OPENED1|UPSTATE_OPENED2)) - var/basestate = "apc[ cell ? "2" : "1" ]" - if(update_state & UPSTATE_OPENED1) - if(update_state & (UPSTATE_MAINT|UPSTATE_BROKE)) - icon_state = "apcmaint" //disabled APC cannot hold cell - else - icon_state = basestate - else if(update_state & UPSTATE_OPENED2) - icon_state = "[basestate]-nocover" - else if(update_state & UPSTATE_BROKE) - icon_state = "apc-b" - else if(update_state & UPSTATE_BLUESCREEN) - icon_state = "apcemag" - else if(update_state & UPSTATE_WIREEXP) - icon_state = "apcewires" - - - - if(!(update_state & UPSTATE_ALLGOOD)) - if(overlays.len) - overlays = 0 - return - - - - if(force_update || update & 2) - - if(overlays.len) - overlays.len = 0 - - if(!(stat & (BROKEN|MAINT)) && update_state & UPSTATE_ALLGOOD) - overlays += status_overlays_lock[locked+1] - overlays += status_overlays_charging[charging+1] - if(operating) - overlays += status_overlays_equipment[equipment+1] - overlays += status_overlays_lighting[lighting+1] - overlays += status_overlays_environ[environ+1] - - -/obj/machinery/power/apc/proc/check_updates() - - var/last_update_state = update_state - var/last_update_overlay = update_overlay - update_state = 0 - update_overlay = 0 - - if(cell) - update_state |= UPSTATE_CELL_IN - if(stat & BROKEN) - update_state |= UPSTATE_BROKE - if(stat & MAINT) - update_state |= UPSTATE_MAINT - if(opened) - if(opened==1) - update_state |= UPSTATE_OPENED1 - if(opened==2) - update_state |= UPSTATE_OPENED2 - else if(emagged || malfai) - update_state |= UPSTATE_BLUESCREEN - else if(panel_open) - update_state |= UPSTATE_WIREEXP - if(update_state <= 1) - update_state |= UPSTATE_ALLGOOD - - if(update_state & UPSTATE_ALLGOOD) - if(locked) - update_overlay |= APC_UPOVERLAY_LOCKED - - if(!charging) - update_overlay |= APC_UPOVERLAY_CHARGEING0 - else if(charging == 1) - update_overlay |= APC_UPOVERLAY_CHARGEING1 - else if(charging == 2) - update_overlay |= APC_UPOVERLAY_CHARGEING2 - - if(!equipment) - update_overlay |= APC_UPOVERLAY_EQUIPMENT0 - else if(equipment == 1) - update_overlay |= APC_UPOVERLAY_EQUIPMENT1 - else if(equipment == 2) - update_overlay |= APC_UPOVERLAY_EQUIPMENT2 - - if(!lighting) - update_overlay |= APC_UPOVERLAY_LIGHTING0 - else if(lighting == 1) - update_overlay |= APC_UPOVERLAY_LIGHTING1 - else if(lighting == 2) - update_overlay |= APC_UPOVERLAY_LIGHTING2 - - if(!environ) - update_overlay |= APC_UPOVERLAY_ENVIRON0 - else if(environ==1) - update_overlay |= APC_UPOVERLAY_ENVIRON1 - else if(environ==2) - update_overlay |= APC_UPOVERLAY_ENVIRON2 - - var/results = 0 - if(last_update_state == update_state && last_update_overlay == update_overlay) - return 0 - if(last_update_state != update_state) - results += 1 - if(last_update_overlay != update_overlay) - results += 2 - return results - -// Used in process so it doesn't update the icon too much -/obj/machinery/power/apc/proc/queue_icon_update() - - if(!updating_icon) - updating_icon = 1 - // Start the update - spawn(APC_UPDATE_ICON_COOLDOWN) - update_icon() - updating_icon = 0 - -/obj/machinery/power/apc/get_spooked(second_pass = FALSE) - if(opened || panel_open) - return - if(stat & (NOPOWER | BROKEN)) - return - if(!second_pass) //The first time, we just cut overlays - addtimer(CALLBACK(src, .get_spooked, TRUE), 1) - cut_overlays() - else - flick("apcemag", src) //Second time we cause the APC to update its icon, then add a timer to update icon later - addtimer(CALLBACK(src, .proc/update_icon, TRUE), 10) - -//attack with an item - open/close cover, insert cell, or (un)lock interface -/obj/machinery/power/apc/attackby(obj/item/W, mob/living/user, params) - - if(issilicon(user) && get_dist(src,user)>1) - return src.attack_hand(user) - - else if (istype(W, /obj/item/stock_parts/cell) && opened) // trying to put a cell inside - if(cell) - to_chat(user, "There is a power cell already installed!") - return - else - if(stat & MAINT) - to_chat(user, "There is no connector for your power cell!") - return - if(!user.drop_item()) - return - W.forceMove(src) - cell = W - user.visible_message(\ - "[user.name] has inserted the power cell to [src.name]!",\ - "You insert the power cell.") - chargecount = 0 - update_icon() - - else if(W.GetID()) // trying to unlock the interface with an ID card - togglelock(user) - - else if(istype(W, /obj/item/stack/cable_coil) && opened) - var/turf/host_turf = get_turf(src) - if(!host_turf) - throw EXCEPTION("attackby on APC when it's not on a turf") - return - if(host_turf.intact) - to_chat(user, "You must remove the floor plating in front of the APC first!") - return - else if(terminal) // it already have terminal - to_chat(user, "This APC is already wired!") - return - else if(has_electronics == 0) - to_chat(user, "There is nothing to wire!") - return - - var/obj/item/stack/cable_coil/C = W - if(C.get_amount() < 10) - to_chat(user, "You need ten lengths of cable for APC!") - return - user.visible_message("[user.name] adds cables to the APC frame.", \ - "You start adding cables to the APC frame...") - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) - if(do_after(user, 20, target = src)) - if(C.get_amount() < 10 || !C) - return - if(C.get_amount() >= 10 && !terminal && opened && has_electronics > 0) - var/turf/T = get_turf(src) - var/obj/structure/cable/N = T.get_cable_node() - if(prob(50) && electrocute_mob(usr, N, N, 1, TRUE)) - do_sparks(5, TRUE, src) - return - C.use(10) - to_chat(user, "You add cables to the APC frame.") - make_terminal() - terminal.connect_to_network() - - else if(istype(W, /obj/item/apc_electronics) && opened) - if(has_electronics!=0) // there are already electronicks inside - to_chat(user, "You cannot put the board inside, there already is one!") - return - else if(stat & BROKEN) - to_chat(user, "You cannot put the board inside, the frame is damaged!") - return - - user.visible_message("[user.name] inserts the power control board into [src].", \ - "You start to insert the power control board into the frame...") - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) - if(do_after(user, 10, target = src)) - if(has_electronics==0) - has_electronics = 1 - locked = FALSE - to_chat(user, "You place the power control board inside the frame.") - qdel(W) - - else if(istype(W, /obj/item/mounted/frame/apc_frame) && opened) - if(!(stat & BROKEN || opened==2 || obj_integrity < max_integrity)) // There is nothing to repair - to_chat(user, "You found no reason for repairing this APC") - return - if(!(stat & BROKEN) && opened==2) // Cover is the only thing broken, we do not need to remove elctronicks to replace cover - user.visible_message("[user.name] replaces missing APC's cover.",\ - "You begin to replace APC's cover...") - if(do_after(user, 20, target = src)) // replacing cover is quicker than replacing whole frame - to_chat(user, "You replace missing APC's cover.") - qdel(W) - opened = 1 - update_icon() - return - if(has_electronics) - to_chat(user, "You cannot repair this APC until you remove the electronics still inside!") - return - user.visible_message("[user.name] replaces the damaged APC frame with a new one.",\ - "You begin to replace the damaged APC frame...") - if(do_after(user, 50, target = src)) - to_chat(user, "You replace the damaged APC frame with a new one.") - qdel(W) - stat &= ~BROKEN - obj_integrity = max_integrity - if(opened==2) - opened = 1 - update_icon() - return - else - return ..() - - -/obj/machinery/power/apc/crowbar_act(mob/living/user, obj/item/I) - . = TRUE - if(!I.tool_start_check(user, 0)) - return - if(opened) // a) on open apc - if(has_electronics==1) - if(terminal) - to_chat(user, "Disconnect the wires first!") - return - to_chat(user, "You are trying to remove the power control board..." ) - if(I.use_tool(src, user, 50, volume = I.tool_volume)) - if(has_electronics==1) - has_electronics = 0 - if(stat & BROKEN) - user.visible_message(\ - "[user.name] has broken the power control board inside [src.name]!", - "You break the charred power control board and remove the remains.", - "You hear a crack.") - return - //SSticker.mode:apcs-- //XSI said no and I agreed. -rastaf0 - else if(emagged) // We emag board, not APC's frame - emagged = FALSE - user.visible_message( - "[user.name] has discarded emaged power control board from [src.name]!", - "You discarded shorten board.") - return - else if(malfhack) // AI hacks board, not APC's frame - user.visible_message(\ - "[user.name] has discarded strangely programmed power control board from [src.name]!", - "You discarded strangely programmed board.") - malfai = null - malfhack = 0 - return - else - user.visible_message(\ - "[user.name] has removed the power control board from [src.name]!", - "You remove the power control board.") - new /obj/item/apc_electronics(loc) - return - else if(opened!=2) //cover isn't removed - opened = 0 - coverlocked = TRUE //closing cover relocks it - update_icon() - return - else if(!(stat & BROKEN)) // b) on closed and not broken APC - if(coverlocked && !(stat & MAINT)) // locked... - to_chat(user, "The cover is locked and cannot be opened!") - return - else if(panel_open) // wires are exposed - to_chat(user, "Exposed wires prevents you from opening it!") - return - else - opened = 1 - update_icon() - -/obj/machinery/power/apc/screwdriver_act(mob/living/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - else if(opened) - if(cell && !(stat & MAINT)) - to_chat(user, "Close the APC first!") //Less hints more mystery! - return - else - if(has_electronics==1) - has_electronics = 2 - stat &= ~MAINT - to_chat(user, "You screw the circuit electronics into place.") - else if(has_electronics==2) - has_electronics = 1 - stat |= MAINT - to_chat(user, "You unfasten the electronics.") - else - to_chat(user, "There is nothing to secure!") - return - update_icon() - else if(emagged) - to_chat(user, "The interface is broken!") - else - panel_open = !panel_open - to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"]") - update_icon() - - -/obj/machinery/power/apc/wirecutter_act(mob/living/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(panel_open && !opened) - wires.Interact(user) - else if(terminal && opened) - terminal.dismantle(user, I) - -/obj/machinery/power/apc/multitool_act(mob/living/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(panel_open && !opened) - wires.Interact(user) - -/obj/machinery/power/apc/proc/togglelock(mob/living/user) - if(emagged) - to_chat(user, "The interface is broken!") - else if(opened) - to_chat(user, "You must close the cover to swipe an ID card!") - else if(panel_open) - to_chat(user, "You must close the panel!") - else if(stat & (BROKEN|MAINT)) - to_chat(user, "Nothing happens!") - else - if(allowed(usr) && !isWireCut(APC_WIRE_IDSCAN) && !malfhack) - locked = !locked - to_chat(user, "You [ locked ? "lock" : "unlock"] the APC interface.") - update_icon() - else - to_chat(user, "Access denied.") - -/obj/machinery/power/apc/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(stat & BROKEN) - return damage_amount - . = ..() - -/obj/machinery/power/apc/obj_break(damage_flag) - if(!(flags & NODECONSTRUCT)) - set_broken() - -/obj/machinery/power/apc/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(!(stat & BROKEN)) - set_broken() - if(opened != 2) - opened = 2 - coverlocked = FALSE - visible_message("The APC cover is knocked down!") - update_icon() - -/obj/machinery/power/apc/welder_act(mob/user, obj/item/I) - if(!opened || has_electronics || terminal) - return - . = TRUE - if(!I.tool_use_check(user, 3)) - return - WELDER_ATTEMPT_SLICING_MESSAGE - if(I.use_tool(src, user, 50, amount = 3, volume = I.tool_volume)) - if((stat & BROKEN) || opened==2) - new /obj/item/stack/sheet/metal(loc) - user.visible_message(\ - "[user.name] has cut [src] apart with [I].",\ - "You disassembled the broken APC frame.") - else - new /obj/item/mounted/frame/apc_frame(loc) - user.visible_message(\ - "[user.name] has cut [src] from the wall with [I].",\ - "You cut the APC frame from the wall.") - qdel(src) - -/obj/machinery/power/apc/emag_act(user as mob) - if(!(emagged || malfhack)) // trying to unlock with an emag card - if(opened) - to_chat(user, "You must close the cover to swipe an ID card.") - else if(panel_open) - to_chat(user, "You must close the panel first.") - else if(stat & (BROKEN|MAINT)) - to_chat(user, "Nothing happens.") - else - flick("apc-spark", src) - emagged = 1 - locked = 0 - to_chat(user, "You emag the APC interface.") - update_icon() - -// attack with hand - remove cell (if cover open) or interact with the APC -/obj/machinery/power/apc/attack_hand(mob/user) -// if(!can_use(user)) This already gets called in interact() and in topic() -// return - if(!user) - return - src.add_fingerprint(user) - - if(usr == user && opened && (!issilicon(user))) - if(cell) - if(issilicon(user)) - cell.loc=src.loc // Drop it, whoops. - else - user.put_in_hands(cell) - cell.add_fingerprint(user) - cell.update_icon() - - src.cell = null - user.visible_message("[user.name] removes the power cell from [src.name]!", "You remove the power cell.") -// to_chat(user, "You remove the power cell.") - charging = 0 - src.update_icon() - return - if(stat & (BROKEN|MAINT)) - return - - src.interact(user) - -/obj/machinery/power/apc/attack_ghost(mob/user) - if(panel_open) - wires.Interact(user) - return ui_interact(user) - -/obj/machinery/power/apc/interact(mob/user) - if(!user) - return - - if(panel_open) - wires.Interact(user) - - return ui_interact(user) - - -/obj/machinery/power/apc/proc/get_malf_status(mob/living/silicon/ai/malf) - if(!istype(malf)) - return FALSE - - // Only if they're a traitor OR they have the malf picker from the combat module - if(!malf.mind.has_antag_datum(/datum/antagonist/traitor) && !malf.malf_picker) - return FALSE - - if(malfai == (malf.parent || malf)) - if(occupier == malf) - return APC_MALF_SHUNTED_HERE - else if(istype(malf.loc, /obj/machinery/power/apc)) - return APC_MALF_SHUNTED_OTHER - else - return APC_MALF_HACKED - else - return APC_MALF_NOT_HACKED - -/obj/machinery/power/apc/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(!user) - return - - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new one - ui = new(user, src, ui_key, "apc.tmpl", "[area.name] - APC", 510, issilicon(user) ? 535 : 460) - ui.open() - // Auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/machinery/power/apc/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - data["locked"] = is_locked(user) - data["isOperating"] = operating - data["externalPower"] = main_status - data["powerCellStatus"] = cell ? cell.percent() : null - data["chargeMode"] = chargemode - data["chargingStatus"] = charging - data["totalLoad"] = round(lastused_equip + lastused_light + lastused_environ) - data["coverLocked"] = coverlocked - data["siliconUser"] = istype(user, /mob/living/silicon) - data["siliconLock"] = locked - data["malfStatus"] = get_malf_status(user) - data["nightshiftLights"] = nightshift_lights - - var/powerChannels[0] - powerChannels[++powerChannels.len] = list( - "title" = "Equipment", - "powerLoad" = round(lastused_equip), - "status" = equipment, - "topicParams" = list( - "auto" = list("eqp" = 3), - "on" = list("eqp" = 2), - "off" = list("eqp" = 1) - ) - ) - powerChannels[++powerChannels.len] = list( - "title" = "Lighting", - "powerLoad" = round(lastused_light), - "status" = lighting, - "topicParams" = list( - "auto" = list("lgt" = 3), - "on" = list("lgt" = 2), - "off" = list("lgt" = 1) - ) - ) - powerChannels[++powerChannels.len] = list( - "title" = "Environment", - "powerLoad" = round(lastused_environ), - "status" = environ, - "topicParams" = list( - "auto" = list("env" = 3), - "on" = list("env" = 2), - "off" = list("env" = 1) - ) - ) - - data["powerChannels"] = powerChannels - - return data - -/obj/machinery/power/apc/proc/report() - return "[area.name] : [equipment]/[lighting]/[environ] ([lastused_equip+lastused_light+lastused_environ]) : [cell? cell.percent() : "N/C"] ([charging])" - -/obj/machinery/power/apc/proc/update() - if(operating && !shorted) - area.power_light = (lighting > 1) - area.power_equip = (equipment > 1) - area.power_environ = (environ > 1) -// if(area.name == "AI Chamber") -// spawn(10) -// to_chat(world, " [area.name] [area.power_equip]") - else - area.power_light = 0 - area.power_equip = 0 - area.power_environ = 0 -// if(area.name == "AI Chamber") -// to_chat(world, "[area.power_equip]") - area.power_change() - -/obj/machinery/power/apc/proc/isWireCut(var/wireIndex) - return wires.IsIndexCut(wireIndex) - - -/obj/machinery/power/apc/proc/can_use(var/mob/user, var/loud = 0) //used by attack_hand() and Topic() - if(user.can_admin_interact()) - return 1 - - autoflag = 5 - if(istype(user, /mob/living/silicon)) - var/mob/living/silicon/ai/AI = user - var/mob/living/silicon/robot/robot = user - if( \ - src.aidisabled || \ - malfhack && istype(malfai) && \ - ( \ - (istype(AI) && (malfai!=AI && malfai != AI.parent)) || \ - (istype(robot) && (robot in malfai.connected_robots)) \ - ) \ - ) - if(!loud) - to_chat(user, "\The [src] has AI control disabled!") - user << browse(null, "window=apc") - user.unset_machine() - return 0 - else - if((!in_range(src, user) || !istype(src.loc, /turf))) - return 0 - - var/mob/living/carbon/human/H = user - if(istype(H)) - if(H.getBrainLoss() >= 60) - for(var/mob/M in viewers(src, null)) - to_chat(M, "[H] stares cluelessly at [src] and drools.") - return 0 - else if(prob(H.getBrainLoss())) - to_chat(user, "You momentarily forget how to use [src].") - return 0 - return 1 - -/obj/machinery/power/apc/proc/is_authenticated(mob/user as mob) - if(user.can_admin_interact()) - return 1 - if(isAI(user) || isrobot(user)) - return 1 - else - return !locked - -/obj/machinery/power/apc/proc/is_locked(mob/user as mob) - if(user.can_admin_interact()) - return 0 - if(isAI(user) || isrobot(user)) - return 0 - else - return locked - -/obj/machinery/power/apc/Topic(href, href_list, var/usingUI = 1) - if(..()) - return 1 - - if(!can_use(usr, 1)) - return 1 - - if(href_list["lock"]) - if(!is_authenticated(usr)) - return - - coverlocked = !coverlocked - - else if(href_list["breaker"]) - if(!is_authenticated(usr)) - return - - toggle_breaker() - - else if(href_list["toggle_nightshift"]) - if(!is_authenticated(usr)) - return - - if(last_nightshift_switch > world.time + 100) // don't spam... - to_chat(usr, "[src]'s night lighting circuit breaker is still cycling!") - return - last_nightshift_switch = world.time - set_nightshift(!nightshift_lights) - - else if(href_list["cmode"]) - if(!is_authenticated(usr)) - return - - chargemode = !chargemode - if(!chargemode) - charging = 0 - update_icon() - - else if(href_list["eqp"]) - if(!is_authenticated(usr)) - return - - var/val = text2num(href_list["eqp"]) - equipment = setsubsystem(val) - update_icon() - update() - - else if(href_list["lgt"]) - if(!is_authenticated(usr)) - return - - var/val = text2num(href_list["lgt"]) - lighting = setsubsystem(val) - update_icon() - update() - - else if(href_list["env"]) - if(!is_authenticated(usr)) - return - - var/val = text2num(href_list["env"]) - environ = setsubsystem(val) - update_icon() - update() - else if( href_list["close"] ) - SSnanoui.close_user_uis(usr, src) - - return 0 - else if(href_list["close2"]) - usr << browse(null, "window=apcwires") - - return 0 - - else if(href_list["overload"]) - if(issilicon(usr) && !aidisabled) - overload_lighting() - - else if(href_list["malfhack"]) - if(get_malf_status(usr)) - malfhack(usr) - - else if(href_list["occupyapc"]) - if(get_malf_status(usr)) - malfoccupy(usr) - - else if(href_list["deoccupyapc"]) - if(get_malf_status(usr)) - malfvacate() - - else if(href_list["toggleaccess"]) - if(istype(usr, /mob/living/silicon)) - if(emagged || aidisabled || (stat & (BROKEN|MAINT))) - to_chat(usr, "The APC does not respond to the command.") - else - locked = !locked - update_icon() - - return 0 - -/obj/machinery/power/apc/proc/toggle_breaker() - operating = !operating - update() - update_icon() - -/obj/machinery/power/apc/proc/malfhack(mob/living/silicon/ai/malf) - if(!istype(malf)) - return - if(get_malf_status(malf) != APC_MALF_NOT_HACKED) - return - if(malf.malfhacking) - to_chat(malf, "You are already hacking an APC.") - return - to_chat(malf, "Beginning override of APC systems. This takes some time, and you cannot perform other actions during the process.") - malf.malfhack = src - malf.malfhacking = addtimer(CALLBACK(malf, /mob/living/silicon/ai/.proc/malfhacked, src), 600, TIMER_STOPPABLE) - var/obj/screen/alert/hackingapc/A - A = malf.throw_alert("hackingapc", /obj/screen/alert/hackingapc) - A.target = src - -/obj/machinery/power/apc/proc/malfoccupy(mob/living/silicon/ai/malf) - if(!istype(malf)) - return - if(istype(malf.loc, /obj/machinery/power/apc)) // Already in an APC - to_chat(malf, "You must evacuate your current APC first!") - return - if(!malf.can_shunt) - to_chat(malf, "You cannot shunt!") - return - if(!is_station_level(src.z)) - return - occupier = new /mob/living/silicon/ai(src,malf.laws,null,1) - occupier.adjustOxyLoss(malf.getOxyLoss()) - if(!findtext(occupier.name, "APC Copy")) - occupier.name = "[malf.name] APC Copy" - if(malf.parent) - occupier.parent = malf.parent - else - occupier.parent = malf - malf.shunted = 1 - malf.mind.transfer_to(occupier) - occupier.eyeobj.name = "[occupier.name] (AI Eye)" - if(malf.parent) - qdel(malf) - occupier.verbs += /mob/living/silicon/ai/proc/corereturn - occupier.cancel_camera() - if((seclevel2num(get_security_level()) == SEC_LEVEL_DELTA) && malf.nuking) - for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) - point.the_disk = src //the pinpointer will detect the shunted AI - -/obj/machinery/power/apc/proc/malfvacate(forced) - if(!occupier) - return - if(occupier.parent && occupier.parent.stat != DEAD) - occupier.mind.transfer_to(occupier.parent) - occupier.parent.shunted = 0 - occupier.parent.adjustOxyLoss(occupier.getOxyLoss()) - occupier.parent.cancel_camera() - qdel(occupier) - if(seclevel2num(get_security_level()) == SEC_LEVEL_DELTA) - for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) - for(var/mob/living/silicon/ai/A in ai_list) - if((A.stat != DEAD) && A.nuking) - point.the_disk = A //The pinpointer tracks the AI back into its core. - else - to_chat(occupier, "Primary core damaged, unable to return core processes.") - if(forced) - occupier.loc = loc - occupier.death() - occupier.gib() - for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) - point.the_disk = null //Pinpointers go back to tracking the nuke disk - -/obj/machinery/power/apc/proc/ion_act() - //intended to be exactly the same as an AI malf attack - if(!src.malfhack && is_station_level(src.z)) - if(prob(3)) - src.locked = 1 - if(src.cell.charge > 0) - src.cell.charge = 0 - cell.corrupt() - src.malfhack = 1 - update_icon() - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(3, 0, src.loc) - smoke.attach(src) - smoke.start() - do_sparks(3, 1, src) - for(var/mob/M in viewers(src)) - M.show_message("The [src.name] suddenly lets out a blast of smoke and some sparks!", 3, "You hear sizzling electronics.", 2) - - -/obj/machinery/power/apc/surplus() - if(terminal) - return terminal.surplus() - else - return 0 - -/obj/machinery/power/apc/add_load(amount) - if(terminal && terminal.powernet) - terminal.add_load(amount) - -/obj/machinery/power/apc/avail() - if(terminal) - return terminal.avail() - else - return 0 - -/obj/machinery/power/apc/process() - if(stat & (BROKEN|MAINT)) - return - if(!area.requires_power) - return - - lastused_light = area.usage(STATIC_LIGHT) - lastused_light += area.usage(LIGHT) - lastused_equip = area.usage(EQUIP) - lastused_equip += area.usage(STATIC_EQUIP) - lastused_environ = area.usage(ENVIRON) - lastused_environ += area.usage(STATIC_ENVIRON) - area.clear_usage() - - lastused_total = lastused_light + lastused_equip + lastused_environ - - //store states to update icon if any change - var/last_lt = lighting - var/last_eq = equipment - var/last_en = environ - var/last_ch = charging - - var/excess = surplus() - - if(!src.avail()) - main_status = 0 - else if(excess < 0) - main_status = 1 - else - main_status = 2 - - if(debug) - log_debug("Status: [main_status] - Excess: [excess] - Last Equip: [lastused_equip] - Last Light: [lastused_light] - Longterm: [longtermpower]") - - if(cell && !shorted) - // draw power from cell as before to power the area - var/cellused = min(cell.charge, GLOB.CELLRATE * lastused_total) // clamp deduction to a max, amount left in cell - cell.use(cellused) - - if(excess > lastused_total) // if power excess recharge the cell - // by the same amount just used - cell.give(cellused) - add_load(cellused/GLOB.CELLRATE) // add the load used to recharge the cell - - - else // no excess, and not enough per-apc - if((cell.charge/GLOB.CELLRATE + excess) >= lastused_total) // can we draw enough from cell+grid to cover last usage? - cell.charge = min(cell.maxcharge, cell.charge + GLOB.CELLRATE * excess) //recharge with what we can - add_load(excess) // so draw what we can from the grid - charging = 0 - - else // not enough power available to run the last tick! - charging = 0 - chargecount = 0 - // This turns everything off in the case that there is still a charge left on the battery, just not enough to run the room. - equipment = autoset(equipment, 0) - lighting = autoset(lighting, 0) - environ = autoset(environ, 0) - autoflag = 0 - - - // Set channels depending on how much charge we have left - - // Allow the APC to operate as normal if the cell can charge - if(charging && longtermpower < 10) - longtermpower += 1 - else if(longtermpower > -10) - longtermpower -= 2 - - if(cell.charge >= 1250 || longtermpower > 0) // Put most likely at the top so we don't check it last, effeciency 101 - if(autoflag != 3) - equipment = autoset(equipment, 1) - lighting = autoset(lighting, 1) - environ = autoset(environ, 1) - autoflag = 3 - if(report_power_alarm && is_station_contact(z)) - SSalarms.power_alarm.clearAlarm(loc, src) - else if(cell.charge < 1250 && cell.charge > 750 && longtermpower < 0) // <30%, turn off equipment - if(autoflag != 2) - equipment = autoset(equipment, 2) - lighting = autoset(lighting, 1) - environ = autoset(environ, 1) - if(report_power_alarm && is_station_contact(z)) - SSalarms.power_alarm.triggerAlarm(loc, src) - autoflag = 2 - else if(cell.charge < 750 && cell.charge > 10) // <15%, turn off lighting & equipment - if((autoflag > 1 && longtermpower < 0) || (autoflag > 1 && longtermpower >= 0)) - equipment = autoset(equipment, 2) - lighting = autoset(lighting, 2) - environ = autoset(environ, 1) - if(report_power_alarm && is_station_contact(z)) - SSalarms.power_alarm.triggerAlarm(loc, src) - autoflag = 1 - else if(cell.charge <= 0) // zero charge, turn all off - if(autoflag != 0) - equipment = autoset(equipment, 0) - lighting = autoset(lighting, 0) - environ = autoset(environ, 0) - if(report_power_alarm && is_station_contact(z)) - SSalarms.power_alarm.triggerAlarm(loc, src) - autoflag = 0 - - // now trickle-charge the cell - if(chargemode && charging == 1 && operating) - if(excess > 0) // check to make sure we have enough to charge - // Max charge is capped to % per second constant - var/ch = min(excess*GLOB.CELLRATE, cell.maxcharge*GLOB.CHARGELEVEL) - add_load(ch/GLOB.CELLRATE) // Removes the power we're taking from the grid - cell.give(ch) // actually recharge the cell - - else - charging = 0 // stop charging - chargecount = 0 - - // show cell as fully charged if so - if(cell.charge >= cell.maxcharge) - cell.charge = cell.maxcharge - charging = 2 - - if(chargemode) - if(!charging) - if(excess > cell.maxcharge*GLOB.CHARGELEVEL) - chargecount++ - else - chargecount = 0 - - if(chargecount == 10) - - chargecount = 0 - charging = 1 - - else // chargemode off - charging = 0 - chargecount = 0 - - if(excess >= 5000000 && !shock_proof) //If there's more than 5,000,000 watts in the grid, start arcing and shocking people. - if(prob(5)) - var/list/shock_mobs = list() - for(var/C in view(get_turf(src), 5)) //We only want to shock a single random mob in range, not every one. - if(isliving(C)) - shock_mobs += C - if(shock_mobs.len) - var/mob/living/L = pick(shock_mobs) - L.electrocute_act(rand(5, 25), "electrical arc") - playsound(get_turf(L), 'sound/effects/eleczap.ogg', 75, 1) - Beam(L, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5) - - else // no cell, switch everything off - - charging = 0 - chargecount = 0 - equipment = autoset(equipment, 0) - lighting = autoset(lighting, 0) - environ = autoset(environ, 0) - if(report_power_alarm && is_station_contact(z)) - SSalarms.power_alarm.triggerAlarm(loc, src) - autoflag = 0 - - // update icon & area power if anything changed - - if(last_lt != lighting || last_eq != equipment || last_en != environ) - queue_icon_update() - update() - else if(last_ch != charging) - queue_icon_update() - -/obj/machinery/power/apc/proc/autoset(var/val, var/on) - if(on==0) - if(val==2) // if on, return off - return 0 - else if(val==3) // if auto-on, return auto-off - return 1 - - else if(on==1) - if(val==1) // if auto-off, return auto-on - return 3 - - else if(on==2) - if(val==3) // if auto-on, return auto-off - return 1 - - return val - -// damage and destruction acts - -/obj/machinery/power/apc/emp_act(severity) - if(cell) - cell.emp_act(severity) - if(occupier) - occupier.emp_act(severity) - lighting = 0 - equipment = 0 - environ = 0 - update_icon() - update() - spawn(600) - equipment = 3 - environ = 3 - update_icon() - update() - ..() - -/obj/machinery/power/apc/blob_act(obj/structure/blob/B) - set_broken() - -/obj/machinery/power/apc/disconnect_terminal() - if(terminal) - terminal.master = null - terminal = null - -/obj/machinery/power/apc/proc/set_broken() - if(malfai && operating) - malfai.malf_picker.processing_time = Clamp(malfai.malf_picker.processing_time - 10,0,1000) - stat |= BROKEN - operating = 0 - if(occupier) - malfvacate(1) - update_icon() - update() - -// overload all the lights in this APC area - -/obj/machinery/power/apc/proc/overload_lighting(chance = 100) - if(!operating || shorted) - return - if(cell && cell.charge >= 20) - cell.use(20) - spawn(0) - for(var/obj/machinery/light/L in area) - if(prob(chance)) - L.break_light_tube(0, 1) - stoplag() - -/obj/machinery/power/apc/proc/null_charge() - for(var/obj/machinery/light/L in area) - L.break_light_tube(0, 1) - stoplag() - -/obj/machinery/power/apc/proc/setsubsystem(val) - if(cell && cell.charge > 0) - return (val==1) ? 0 : val - else if(val == 3) - return 1 - else - return 0 - -/obj/machinery/power/apc/proc/set_nightshift(on) - set waitfor = FALSE - nightshift_lights = on - for(var/obj/machinery/light/L in area) - if(L.nightshift_allowed) - L.nightshift_enabled = nightshift_lights - L.update(FALSE) - CHECK_TICK - -#undef APC_UPDATE_ICON_COOLDOWN +#define APC_WIRE_IDSCAN 1 +#define APC_WIRE_MAIN_POWER1 2 +#define APC_WIRE_MAIN_POWER2 3 +#define APC_WIRE_AI_CONTROL 4 + +//update_state +#define UPSTATE_CELL_IN 1 +#define UPSTATE_OPENED1 2 +#define UPSTATE_OPENED2 4 +#define UPSTATE_MAINT 8 +#define UPSTATE_BROKE 16 +#define UPSTATE_BLUESCREEN 32 +#define UPSTATE_WIREEXP 64 +#define UPSTATE_ALLGOOD 128 + +//update_overlay +#define APC_UPOVERLAY_CHARGEING0 1 +#define APC_UPOVERLAY_CHARGEING1 2 +#define APC_UPOVERLAY_CHARGEING2 4 +#define APC_UPOVERLAY_EQUIPMENT0 8 +#define APC_UPOVERLAY_EQUIPMENT1 16 +#define APC_UPOVERLAY_EQUIPMENT2 32 +#define APC_UPOVERLAY_LIGHTING0 64 +#define APC_UPOVERLAY_LIGHTING1 128 +#define APC_UPOVERLAY_LIGHTING2 256 +#define APC_UPOVERLAY_ENVIRON0 512 +#define APC_UPOVERLAY_ENVIRON1 1024 +#define APC_UPOVERLAY_ENVIRON2 2048 +#define APC_UPOVERLAY_LOCKED 4096 + +#define APC_UPDATE_ICON_COOLDOWN 200 // 20 seconds + +// APC malf status +#define APC_MALF_NOT_HACKED 1 +#define APC_MALF_HACKED 2 // APC hacked by user, and user is in its core. +#define APC_MALF_SHUNTED_HERE 3 // User is shunted in this APC +#define APC_MALF_SHUNTED_OTHER 4 // User is shunted in another APC + +// the Area Power Controller (APC), formerly Power Distribution Unit (PDU) +// one per area, needs wire conection to power network through a terminal + +// controls power to devices in that area +// may be opened to change power cell +// three different channels (lighting/equipment/environ) - may each be set to on, off, or auto + + +//NOTE: STUFF STOLEN FROM AIRLOCK.DM thx + + +/obj/machinery/power/apc + name = "area power controller" + desc = "A control terminal for the area electrical systems." + icon_state = "apc0" + use_power = NO_POWER_USE + max_integrity = 200 + integrity_failure = 50 + resistance_flags = FIRE_PROOF + req_access = list(ACCESS_ENGINE_EQUIP) + siemens_strength = 1 + damage_deflection = 10 + var/area/area + var/areastring = null + var/obj/item/stock_parts/cell/cell + var/start_charge = 90 // initial cell charge % + var/cell_type = 2500 //Base cell has 2500 capacity. Enter the path of a different cell you want to use. cell determines charge rates, max capacity, ect. These can also be changed with other APC vars, but isn't recommended to minimize the risk of accidental usage of dirty editted APCs + var/opened = 0 //0=closed, 1=opened, 2=cover removed + var/shorted = 0 + var/lighting = 3 + var/equipment = 3 + var/environ = 3 + var/operating = 1 + var/charging = 0 + var/chargemode = 1 + var/chargecount = 0 + var/locked = 1 + var/coverlocked = 1 + var/aidisabled = 0 + var/tdir = null + var/obj/machinery/power/terminal/terminal = null + var/lastused_light = 0 + var/lastused_equip = 0 + var/lastused_environ = 0 + var/lastused_total = 0 + var/main_status = 0 + powernet = 0 // set so that APCs aren't found as powernet nodes //Hackish, Horrible, was like this before I changed it :( + var/malfhack = 0 //New var for my changes to AI malf. --NeoFite + var/mob/living/silicon/ai/malfai = null //See above --NeoFite + var/debug= 0 + var/autoflag= 0 // 0 = off, 1= eqp and lights off, 2 = eqp off, 3 = all on. +// luminosity = 1 + var/has_electronics = 0 // 0 - none, 1 - plugged in, 2 - secured by screwdriver + var/overload = 1 //used for the Blackout malf module + var/beenhit = 0 // used for counting how many times it has been hit, used for Aliens at the moment + var/mob/living/silicon/ai/occupier = null + var/longtermpower = 10 + var/update_state = -1 + var/update_overlay = -1 + var/global/status_overlays = 0 + var/updating_icon = 0 + var/datum/wires/apc/wires = null + //var/debug = 0 + var/global/list/status_overlays_lock + var/global/list/status_overlays_charging + var/global/list/status_overlays_equipment + var/global/list/status_overlays_lighting + var/global/list/status_overlays_environ + var/indestructible = 0 // If set, prevents aliens from destroying it + var/keep_preset_name = 0 + + var/report_power_alarm = TRUE + + var/shock_proof = 0 //if set to 1, this APC will not arc bolts of electricity if it's overloaded. + + // Nightshift + var/nightshift_lights = FALSE + var/last_nightshift_switch = 0 + +/obj/machinery/power/apc/worn_out + name = "\improper Worn out APC" + keep_preset_name = 1 + locked = 0 + environ = 0 + equipment = 0 + lighting = 0 + operating = 0 + +/obj/machinery/power/apc/noalarm + report_power_alarm = FALSE + +/obj/machinery/power/apc/syndicate //general syndicate access + req_access = list(ACCESS_SYNDICATE) + report_power_alarm = FALSE + +/obj/item/apc_electronics + name = "power control module" + desc = "Heavy-duty switching circuits for power control." + icon = 'icons/obj/module.dmi' + icon_state = "power_mod" + w_class = WEIGHT_CLASS_SMALL + origin_tech = "engineering=2;programming=1" + item_state = "electronic" + flags = CONDUCT + usesound = 'sound/items/deconstruct.ogg' + toolspeed = 1 + +/obj/machinery/power/apc/get_cell() + return cell + +/obj/machinery/power/apc/connect_to_network() + //Override because the APC does not directly connect to the network; it goes through a terminal. + //The terminal is what the power computer looks for anyway. + if(terminal) + terminal.connect_to_network() + +/obj/machinery/power/apc/New(turf/loc, ndir, building = 0) + if(!armor) + armor = list("melee" = 20, "bullet" = 20, "laser" = 10, "energy" = 100, "bomb" = 30, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 50) + ..() + GLOB.apcs += src + GLOB.apcs = sortAtom(GLOB.apcs) + + wires = new(src) + // offset 24 pixels in direction of dir + // this allows the APC to be embedded in a wall, yet still inside an area + if(building) + setDir(ndir) + tdir = dir // to fix Vars bug + setDir(SOUTH) + + pixel_x = (src.tdir & 3)? 0 : (src.tdir == 4 ? 24 : -24) + pixel_y = (src.tdir & 3)? (src.tdir ==1 ? 24 : -24) : 0 + if(building) + area = get_area(src) + area.apc |= src + opened = 1 + operating = 0 + name = "[area.name] APC" + stat |= MAINT + update_icon() + addtimer(CALLBACK(src, .proc/update), 5) + +/obj/machinery/power/apc/Destroy() + GLOB.apcs -= src + if(malfai && operating) + malfai.malf_picker.processing_time = Clamp(malfai.malf_picker.processing_time - 10,0,1000) + area.power_light = 0 + area.power_equip = 0 + area.power_environ = 0 + area.power_change() + if(occupier) + malfvacate(1) + QDEL_NULL(wires) + QDEL_NULL(cell) + if(terminal) + disconnect_terminal() + area.apc -= src + return ..() + +/obj/machinery/power/apc/proc/make_terminal() + // create a terminal object at the same position as original turf loc + // wires will attach to this + terminal = new/obj/machinery/power/terminal(src.loc) + terminal.setDir(tdir) + terminal.master = src + +/obj/machinery/power/apc/Initialize(mapload) + . = ..() + if(!mapload) + return + has_electronics = 2 //installed and secured + // is starting with a power cell installed, create it and set its charge level + if(cell_type) + cell = new/obj/item/stock_parts/cell/upgraded(src) + cell.maxcharge = cell_type // cell_type is maximum charge (old default was 1000 or 2500 (values one and two respectively) + cell.charge = start_charge * cell.maxcharge / 100 // (convert percentage to actual value) + + var/area/A = get_area(src) + + + //if area isn't specified use current + if(keep_preset_name) + if(isarea(A)) + area = A + // no-op, keep the name + else if(isarea(A) && src.areastring == null) + area = A + name = "\improper [area.name] APC" + else + area = get_area_name(areastring) + name = "\improper [area.name] APC" + area.apc |= src + + update_icon() + + make_terminal() + + addtimer(CALLBACK(src, .proc/update), 5) + +/obj/machinery/power/apc/examine(mob/user) + . = ..() + if(in_range(user, src)) + if(stat & BROKEN) + . += "Looks broken." + else if(opened) + if(has_electronics && terminal) + . += "The cover is [opened==2?"removed":"open"] and the power cell is [ cell ? "installed" : "missing"]." + else if(!has_electronics && terminal) + . += "There are some wires but no electronics." + else if(has_electronics && !terminal) + . += "Electronics installed but not wired." + else /* if(!has_electronics && !terminal) */ + . += "There is no electronics nor connected wires." + else + if(stat & MAINT) + . += "The cover is closed. Something wrong with it: it doesn't work." + else if(malfhack) + . += "The cover is broken. It may be hard to force it open." + else + . += "The cover is closed." + +// update the APC icon to show the three base states +// also add overlays for indicator lights +/obj/machinery/power/apc/update_icon(force_update = FALSE) + + if(!status_overlays || force_update) + status_overlays = 1 + status_overlays_lock = new + status_overlays_charging = new + status_overlays_equipment = new + status_overlays_lighting = new + status_overlays_environ = new + + status_overlays_lock.len = 2 + status_overlays_charging.len = 3 + status_overlays_equipment.len = 4 + status_overlays_lighting.len = 4 + status_overlays_environ.len = 4 + + status_overlays_lock[1] = image(icon, "apcox-0") // 0=blue 1=red + status_overlays_lock[2] = image(icon, "apcox-1") + + status_overlays_charging[1] = image(icon, "apco3-0") + status_overlays_charging[2] = image(icon, "apco3-1") + status_overlays_charging[3] = image(icon, "apco3-2") + + status_overlays_equipment[1] = image(icon, "apco0-0") // 0=red, 1=green, 2=blue + status_overlays_equipment[2] = image(icon, "apco0-1") + status_overlays_equipment[3] = image(icon, "apco0-2") + status_overlays_equipment[4] = image(icon, "apco0-3") + + status_overlays_lighting[1] = image(icon, "apco1-0") + status_overlays_lighting[2] = image(icon, "apco1-1") + status_overlays_lighting[3] = image(icon, "apco1-2") + status_overlays_lighting[4] = image(icon, "apco1-3") + + status_overlays_environ[1] = image(icon, "apco2-0") + status_overlays_environ[2] = image(icon, "apco2-1") + status_overlays_environ[3] = image(icon, "apco2-2") + status_overlays_environ[4] = image(icon, "apco2-3") + + + + var/update = check_updates() //returns 0 if no need to update icons. + // 1 if we need to update the icon_state + // 2 if we need to update the overlays + if(!update && !force_update) + return + + if(force_update || update & 1) // Updating the icon state + if(update_state & UPSTATE_ALLGOOD) + icon_state = "apc0" + else if(update_state & (UPSTATE_OPENED1|UPSTATE_OPENED2)) + var/basestate = "apc[ cell ? "2" : "1" ]" + if(update_state & UPSTATE_OPENED1) + if(update_state & (UPSTATE_MAINT|UPSTATE_BROKE)) + icon_state = "apcmaint" //disabled APC cannot hold cell + else + icon_state = basestate + else if(update_state & UPSTATE_OPENED2) + icon_state = "[basestate]-nocover" + else if(update_state & UPSTATE_BROKE) + icon_state = "apc-b" + else if(update_state & UPSTATE_BLUESCREEN) + icon_state = "apcemag" + else if(update_state & UPSTATE_WIREEXP) + icon_state = "apcewires" + + + + if(!(update_state & UPSTATE_ALLGOOD)) + if(overlays.len) + overlays = 0 + return + + + + if(force_update || update & 2) + + if(overlays.len) + overlays.len = 0 + + if(!(stat & (BROKEN|MAINT)) && update_state & UPSTATE_ALLGOOD) + overlays += status_overlays_lock[locked+1] + overlays += status_overlays_charging[charging+1] + if(operating) + overlays += status_overlays_equipment[equipment+1] + overlays += status_overlays_lighting[lighting+1] + overlays += status_overlays_environ[environ+1] + + +/obj/machinery/power/apc/proc/check_updates() + + var/last_update_state = update_state + var/last_update_overlay = update_overlay + update_state = 0 + update_overlay = 0 + + if(cell) + update_state |= UPSTATE_CELL_IN + if(stat & BROKEN) + update_state |= UPSTATE_BROKE + if(stat & MAINT) + update_state |= UPSTATE_MAINT + if(opened) + if(opened==1) + update_state |= UPSTATE_OPENED1 + if(opened==2) + update_state |= UPSTATE_OPENED2 + else if(emagged || malfai) + update_state |= UPSTATE_BLUESCREEN + else if(panel_open) + update_state |= UPSTATE_WIREEXP + if(update_state <= 1) + update_state |= UPSTATE_ALLGOOD + + if(update_state & UPSTATE_ALLGOOD) + if(locked) + update_overlay |= APC_UPOVERLAY_LOCKED + + if(!charging) + update_overlay |= APC_UPOVERLAY_CHARGEING0 + else if(charging == 1) + update_overlay |= APC_UPOVERLAY_CHARGEING1 + else if(charging == 2) + update_overlay |= APC_UPOVERLAY_CHARGEING2 + + if(!equipment) + update_overlay |= APC_UPOVERLAY_EQUIPMENT0 + else if(equipment == 1) + update_overlay |= APC_UPOVERLAY_EQUIPMENT1 + else if(equipment == 2) + update_overlay |= APC_UPOVERLAY_EQUIPMENT2 + + if(!lighting) + update_overlay |= APC_UPOVERLAY_LIGHTING0 + else if(lighting == 1) + update_overlay |= APC_UPOVERLAY_LIGHTING1 + else if(lighting == 2) + update_overlay |= APC_UPOVERLAY_LIGHTING2 + + if(!environ) + update_overlay |= APC_UPOVERLAY_ENVIRON0 + else if(environ==1) + update_overlay |= APC_UPOVERLAY_ENVIRON1 + else if(environ==2) + update_overlay |= APC_UPOVERLAY_ENVIRON2 + + var/results = 0 + if(last_update_state == update_state && last_update_overlay == update_overlay) + return 0 + if(last_update_state != update_state) + results += 1 + if(last_update_overlay != update_overlay) + results += 2 + return results + +// Used in process so it doesn't update the icon too much +/obj/machinery/power/apc/proc/queue_icon_update() + + if(!updating_icon) + updating_icon = 1 + // Start the update + spawn(APC_UPDATE_ICON_COOLDOWN) + update_icon() + updating_icon = 0 + +/obj/machinery/power/apc/get_spooked(second_pass = FALSE) + if(opened || panel_open) + return + if(stat & (NOPOWER | BROKEN)) + return + if(!second_pass) //The first time, we just cut overlays + addtimer(CALLBACK(src, .get_spooked, TRUE), 1) + cut_overlays() + else + flick("apcemag", src) //Second time we cause the APC to update its icon, then add a timer to update icon later + addtimer(CALLBACK(src, .proc/update_icon, TRUE), 10) + +//attack with an item - open/close cover, insert cell, or (un)lock interface +/obj/machinery/power/apc/attackby(obj/item/W, mob/living/user, params) + + if(issilicon(user) && get_dist(src,user)>1) + return src.attack_hand(user) + + else if (istype(W, /obj/item/stock_parts/cell) && opened) // trying to put a cell inside + if(cell) + to_chat(user, "There is a power cell already installed!") + return + else + if(stat & MAINT) + to_chat(user, "There is no connector for your power cell!") + return + if(!user.drop_item()) + return + W.forceMove(src) + cell = W + user.visible_message(\ + "[user.name] has inserted the power cell to [src.name]!",\ + "You insert the power cell.") + chargecount = 0 + update_icon() + + else if(W.GetID()) // trying to unlock the interface with an ID card + togglelock(user) + + else if(istype(W, /obj/item/stack/cable_coil) && opened) + var/turf/host_turf = get_turf(src) + if(!host_turf) + throw EXCEPTION("attackby on APC when it's not on a turf") + return + if(host_turf.intact) + to_chat(user, "You must remove the floor plating in front of the APC first!") + return + else if(terminal) // it already have terminal + to_chat(user, "This APC is already wired!") + return + else if(has_electronics == 0) + to_chat(user, "There is nothing to wire!") + return + + var/obj/item/stack/cable_coil/C = W + if(C.get_amount() < 10) + to_chat(user, "You need ten lengths of cable for APC!") + return + user.visible_message("[user.name] adds cables to the APC frame.", \ + "You start adding cables to the APC frame...") + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + if(do_after(user, 20, target = src)) + if(C.get_amount() < 10 || !C) + return + if(C.get_amount() >= 10 && !terminal && opened && has_electronics > 0) + var/turf/T = get_turf(src) + var/obj/structure/cable/N = T.get_cable_node() + if(prob(50) && electrocute_mob(usr, N, N, 1, TRUE)) + do_sparks(5, TRUE, src) + return + C.use(10) + to_chat(user, "You add cables to the APC frame.") + make_terminal() + terminal.connect_to_network() + + else if(istype(W, /obj/item/apc_electronics) && opened) + if(has_electronics!=0) // there are already electronicks inside + to_chat(user, "You cannot put the board inside, there already is one!") + return + else if(stat & BROKEN) + to_chat(user, "You cannot put the board inside, the frame is damaged!") + return + + user.visible_message("[user.name] inserts the power control board into [src].", \ + "You start to insert the power control board into the frame...") + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + if(do_after(user, 10, target = src)) + if(has_electronics==0) + has_electronics = 1 + locked = FALSE + to_chat(user, "You place the power control board inside the frame.") + qdel(W) + + else if(istype(W, /obj/item/mounted/frame/apc_frame) && opened) + if(!(stat & BROKEN || opened==2 || obj_integrity < max_integrity)) // There is nothing to repair + to_chat(user, "You found no reason for repairing this APC") + return + if(!(stat & BROKEN) && opened==2) // Cover is the only thing broken, we do not need to remove elctronicks to replace cover + user.visible_message("[user.name] replaces missing APC's cover.",\ + "You begin to replace APC's cover...") + if(do_after(user, 20, target = src)) // replacing cover is quicker than replacing whole frame + to_chat(user, "You replace missing APC's cover.") + qdel(W) + opened = 1 + update_icon() + return + if(has_electronics) + to_chat(user, "You cannot repair this APC until you remove the electronics still inside!") + return + user.visible_message("[user.name] replaces the damaged APC frame with a new one.",\ + "You begin to replace the damaged APC frame...") + if(do_after(user, 50, target = src)) + to_chat(user, "You replace the damaged APC frame with a new one.") + qdel(W) + stat &= ~BROKEN + obj_integrity = max_integrity + if(opened==2) + opened = 1 + update_icon() + return + else + return ..() + + +/obj/machinery/power/apc/crowbar_act(mob/living/user, obj/item/I) + . = TRUE + if(!I.tool_start_check(user, 0)) + return + if(opened) // a) on open apc + if(has_electronics==1) + if(terminal) + to_chat(user, "Disconnect the wires first!") + return + to_chat(user, "You are trying to remove the power control board..." ) + if(I.use_tool(src, user, 50, volume = I.tool_volume)) + if(has_electronics==1) + has_electronics = 0 + if(stat & BROKEN) + user.visible_message(\ + "[user.name] has broken the power control board inside [src.name]!", + "You break the charred power control board and remove the remains.", + "You hear a crack.") + return + //SSticker.mode:apcs-- //XSI said no and I agreed. -rastaf0 + else if(emagged) // We emag board, not APC's frame + emagged = FALSE + user.visible_message( + "[user.name] has discarded emaged power control board from [src.name]!", + "You discarded shorten board.") + return + else if(malfhack) // AI hacks board, not APC's frame + user.visible_message(\ + "[user.name] has discarded strangely programmed power control board from [src.name]!", + "You discarded strangely programmed board.") + malfai = null + malfhack = 0 + return + else + user.visible_message(\ + "[user.name] has removed the power control board from [src.name]!", + "You remove the power control board.") + new /obj/item/apc_electronics(loc) + return + else if(opened!=2) //cover isn't removed + opened = 0 + coverlocked = TRUE //closing cover relocks it + update_icon() + return + else if(!(stat & BROKEN)) // b) on closed and not broken APC + if(coverlocked && !(stat & MAINT)) // locked... + to_chat(user, "The cover is locked and cannot be opened!") + return + else if(panel_open) // wires are exposed + to_chat(user, "Exposed wires prevents you from opening it!") + return + else + opened = 1 + update_icon() + +/obj/machinery/power/apc/screwdriver_act(mob/living/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + else if(opened) + if(cell && !(stat & MAINT)) + to_chat(user, "Close the APC first!") //Less hints more mystery! + return + else + if(has_electronics==1) + has_electronics = 2 + stat &= ~MAINT + to_chat(user, "You screw the circuit electronics into place.") + else if(has_electronics==2) + has_electronics = 1 + stat |= MAINT + to_chat(user, "You unfasten the electronics.") + else + to_chat(user, "There is nothing to secure!") + return + update_icon() + else if(emagged) + to_chat(user, "The interface is broken!") + else + panel_open = !panel_open + to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"]") + update_icon() + + +/obj/machinery/power/apc/wirecutter_act(mob/living/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(panel_open && !opened) + wires.Interact(user) + else if(terminal && opened) + terminal.dismantle(user, I) + +/obj/machinery/power/apc/multitool_act(mob/living/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(panel_open && !opened) + wires.Interact(user) + +/obj/machinery/power/apc/proc/togglelock(mob/living/user) + if(emagged) + to_chat(user, "The interface is broken!") + else if(opened) + to_chat(user, "You must close the cover to swipe an ID card!") + else if(panel_open) + to_chat(user, "You must close the panel!") + else if(stat & (BROKEN|MAINT)) + to_chat(user, "Nothing happens!") + else + if(allowed(usr) && !isWireCut(APC_WIRE_IDSCAN) && !malfhack) + locked = !locked + to_chat(user, "You [ locked ? "lock" : "unlock"] the APC interface.") + update_icon() + else + to_chat(user, "Access denied.") + +/obj/machinery/power/apc/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(stat & BROKEN) + return damage_amount + . = ..() + +/obj/machinery/power/apc/obj_break(damage_flag) + if(!(flags & NODECONSTRUCT)) + set_broken() + +/obj/machinery/power/apc/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(!(stat & BROKEN)) + set_broken() + if(opened != 2) + opened = 2 + coverlocked = FALSE + visible_message("The APC cover is knocked down!") + update_icon() + +/obj/machinery/power/apc/welder_act(mob/user, obj/item/I) + if(!opened || has_electronics || terminal) + return + . = TRUE + if(!I.tool_use_check(user, 3)) + return + WELDER_ATTEMPT_SLICING_MESSAGE + if(I.use_tool(src, user, 50, amount = 3, volume = I.tool_volume)) + if((stat & BROKEN) || opened==2) + new /obj/item/stack/sheet/metal(loc) + user.visible_message(\ + "[user.name] has cut [src] apart with [I].",\ + "You disassembled the broken APC frame.") + else + new /obj/item/mounted/frame/apc_frame(loc) + user.visible_message(\ + "[user.name] has cut [src] from the wall with [I].",\ + "You cut the APC frame from the wall.") + qdel(src) + +/obj/machinery/power/apc/emag_act(user as mob) + if(!(emagged || malfhack)) // trying to unlock with an emag card + if(opened) + to_chat(user, "You must close the cover to swipe an ID card.") + else if(panel_open) + to_chat(user, "You must close the panel first.") + else if(stat & (BROKEN|MAINT)) + to_chat(user, "Nothing happens.") + else + flick("apc-spark", src) + emagged = 1 + locked = 0 + to_chat(user, "You emag the APC interface.") + update_icon() + +// attack with hand - remove cell (if cover open) or interact with the APC +/obj/machinery/power/apc/attack_hand(mob/user) +// if(!can_use(user)) This already gets called in interact() and in topic() +// return + if(!user) + return + src.add_fingerprint(user) + + if(usr == user && opened && (!issilicon(user))) + if(cell) + if(issilicon(user)) + cell.loc=src.loc // Drop it, whoops. + else + user.put_in_hands(cell) + cell.add_fingerprint(user) + cell.update_icon() + + src.cell = null + user.visible_message("[user.name] removes the power cell from [src.name]!", "You remove the power cell.") +// to_chat(user, "You remove the power cell.") + charging = 0 + src.update_icon() + return + if(stat & (BROKEN|MAINT)) + return + + src.interact(user) + +/obj/machinery/power/apc/attack_ghost(mob/user) + if(panel_open) + wires.Interact(user) + return ui_interact(user) + +/obj/machinery/power/apc/interact(mob/user) + if(!user) + return + + if(panel_open) + wires.Interact(user) + + return ui_interact(user) + + +/obj/machinery/power/apc/proc/get_malf_status(mob/living/silicon/ai/malf) + if(!istype(malf)) + return FALSE + + // Only if they're a traitor OR they have the malf picker from the combat module + if(!malf.mind.has_antag_datum(/datum/antagonist/traitor) && !malf.malf_picker) + return FALSE + + if(malfai == (malf.parent || malf)) + if(occupier == malf) + return APC_MALF_SHUNTED_HERE + else if(istype(malf.loc, /obj/machinery/power/apc)) + return APC_MALF_SHUNTED_OTHER + else + return APC_MALF_HACKED + else + return APC_MALF_NOT_HACKED + +/obj/machinery/power/apc/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(!user) + return + + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new one + ui = new(user, src, ui_key, "apc.tmpl", "[area.name] - APC", 510, issilicon(user) ? 535 : 460) + ui.open() + // Auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/machinery/power/apc/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + data["locked"] = is_locked(user) + data["isOperating"] = operating + data["externalPower"] = main_status + data["powerCellStatus"] = cell ? cell.percent() : null + data["chargeMode"] = chargemode + data["chargingStatus"] = charging + data["totalLoad"] = round(lastused_equip + lastused_light + lastused_environ) + data["coverLocked"] = coverlocked + data["siliconUser"] = istype(user, /mob/living/silicon) + data["siliconLock"] = locked + data["malfStatus"] = get_malf_status(user) + data["nightshiftLights"] = nightshift_lights + + var/powerChannels[0] + powerChannels[++powerChannels.len] = list( + "title" = "Equipment", + "powerLoad" = round(lastused_equip), + "status" = equipment, + "topicParams" = list( + "auto" = list("eqp" = 3), + "on" = list("eqp" = 2), + "off" = list("eqp" = 1) + ) + ) + powerChannels[++powerChannels.len] = list( + "title" = "Lighting", + "powerLoad" = round(lastused_light), + "status" = lighting, + "topicParams" = list( + "auto" = list("lgt" = 3), + "on" = list("lgt" = 2), + "off" = list("lgt" = 1) + ) + ) + powerChannels[++powerChannels.len] = list( + "title" = "Environment", + "powerLoad" = round(lastused_environ), + "status" = environ, + "topicParams" = list( + "auto" = list("env" = 3), + "on" = list("env" = 2), + "off" = list("env" = 1) + ) + ) + + data["powerChannels"] = powerChannels + + return data + +/obj/machinery/power/apc/proc/report() + return "[area.name] : [equipment]/[lighting]/[environ] ([lastused_equip+lastused_light+lastused_environ]) : [cell? cell.percent() : "N/C"] ([charging])" + +/obj/machinery/power/apc/proc/update() + if(operating && !shorted) + area.power_light = (lighting > 1) + area.power_equip = (equipment > 1) + area.power_environ = (environ > 1) +// if(area.name == "AI Chamber") +// spawn(10) +// to_chat(world, " [area.name] [area.power_equip]") + else + area.power_light = 0 + area.power_equip = 0 + area.power_environ = 0 +// if(area.name == "AI Chamber") +// to_chat(world, "[area.power_equip]") + area.power_change() + +/obj/machinery/power/apc/proc/isWireCut(var/wireIndex) + return wires.IsIndexCut(wireIndex) + + +/obj/machinery/power/apc/proc/can_use(var/mob/user, var/loud = 0) //used by attack_hand() and Topic() + if(user.can_admin_interact()) + return 1 + + autoflag = 5 + if(istype(user, /mob/living/silicon)) + var/mob/living/silicon/ai/AI = user + var/mob/living/silicon/robot/robot = user + if( \ + src.aidisabled || \ + malfhack && istype(malfai) && \ + ( \ + (istype(AI) && (malfai!=AI && malfai != AI.parent)) || \ + (istype(robot) && (robot in malfai.connected_robots)) \ + ) \ + ) + if(!loud) + to_chat(user, "\The [src] has AI control disabled!") + user << browse(null, "window=apc") + user.unset_machine() + return 0 + else + if((!in_range(src, user) || !istype(src.loc, /turf))) + return 0 + + var/mob/living/carbon/human/H = user + if(istype(H)) + if(H.getBrainLoss() >= 60) + for(var/mob/M in viewers(src, null)) + to_chat(M, "[H] stares cluelessly at [src] and drools.") + return 0 + else if(prob(H.getBrainLoss())) + to_chat(user, "You momentarily forget how to use [src].") + return 0 + return 1 + +/obj/machinery/power/apc/proc/is_authenticated(mob/user as mob) + if(user.can_admin_interact()) + return 1 + if(isAI(user) || isrobot(user)) + return 1 + else + return !locked + +/obj/machinery/power/apc/proc/is_locked(mob/user as mob) + if(user.can_admin_interact()) + return 0 + if(isAI(user) || isrobot(user)) + return 0 + else + return locked + +/obj/machinery/power/apc/Topic(href, href_list, var/usingUI = 1) + if(..()) + return 1 + + if(!can_use(usr, 1)) + return 1 + + if(href_list["lock"]) + if(!is_authenticated(usr)) + return + + coverlocked = !coverlocked + + else if(href_list["breaker"]) + if(!is_authenticated(usr)) + return + + toggle_breaker() + + else if(href_list["toggle_nightshift"]) + if(!is_authenticated(usr)) + return + + if(last_nightshift_switch > world.time + 100) // don't spam... + to_chat(usr, "[src]'s night lighting circuit breaker is still cycling!") + return + last_nightshift_switch = world.time + set_nightshift(!nightshift_lights) + + else if(href_list["cmode"]) + if(!is_authenticated(usr)) + return + + chargemode = !chargemode + if(!chargemode) + charging = 0 + update_icon() + + else if(href_list["eqp"]) + if(!is_authenticated(usr)) + return + + var/val = text2num(href_list["eqp"]) + equipment = setsubsystem(val) + update_icon() + update() + + else if(href_list["lgt"]) + if(!is_authenticated(usr)) + return + + var/val = text2num(href_list["lgt"]) + lighting = setsubsystem(val) + update_icon() + update() + + else if(href_list["env"]) + if(!is_authenticated(usr)) + return + + var/val = text2num(href_list["env"]) + environ = setsubsystem(val) + update_icon() + update() + else if( href_list["close"] ) + SSnanoui.close_user_uis(usr, src) + + return 0 + else if(href_list["close2"]) + usr << browse(null, "window=apcwires") + + return 0 + + else if(href_list["overload"]) + if(issilicon(usr) && !aidisabled) + overload_lighting() + + else if(href_list["malfhack"]) + if(get_malf_status(usr)) + malfhack(usr) + + else if(href_list["occupyapc"]) + if(get_malf_status(usr)) + malfoccupy(usr) + + else if(href_list["deoccupyapc"]) + if(get_malf_status(usr)) + malfvacate() + + else if(href_list["toggleaccess"]) + if(istype(usr, /mob/living/silicon)) + if(emagged || aidisabled || (stat & (BROKEN|MAINT))) + to_chat(usr, "The APC does not respond to the command.") + else + locked = !locked + update_icon() + + return 0 + +/obj/machinery/power/apc/proc/toggle_breaker() + operating = !operating + update() + update_icon() + +/obj/machinery/power/apc/proc/malfhack(mob/living/silicon/ai/malf) + if(!istype(malf)) + return + if(get_malf_status(malf) != APC_MALF_NOT_HACKED) + return + if(malf.malfhacking) + to_chat(malf, "You are already hacking an APC.") + return + to_chat(malf, "Beginning override of APC systems. This takes some time, and you cannot perform other actions during the process.") + malf.malfhack = src + malf.malfhacking = addtimer(CALLBACK(malf, /mob/living/silicon/ai/.proc/malfhacked, src), 600, TIMER_STOPPABLE) + var/obj/screen/alert/hackingapc/A + A = malf.throw_alert("hackingapc", /obj/screen/alert/hackingapc) + A.target = src + +/obj/machinery/power/apc/proc/malfoccupy(mob/living/silicon/ai/malf) + if(!istype(malf)) + return + if(istype(malf.loc, /obj/machinery/power/apc)) // Already in an APC + to_chat(malf, "You must evacuate your current APC first!") + return + if(!malf.can_shunt) + to_chat(malf, "You cannot shunt!") + return + if(!is_station_level(src.z)) + return + occupier = new /mob/living/silicon/ai(src,malf.laws,null,1) + occupier.adjustOxyLoss(malf.getOxyLoss()) + if(!findtext(occupier.name, "APC Copy")) + occupier.name = "[malf.name] APC Copy" + if(malf.parent) + occupier.parent = malf.parent + else + occupier.parent = malf + malf.shunted = 1 + malf.mind.transfer_to(occupier) + occupier.eyeobj.name = "[occupier.name] (AI Eye)" + if(malf.parent) + qdel(malf) + occupier.verbs += /mob/living/silicon/ai/proc/corereturn + occupier.cancel_camera() + if((seclevel2num(get_security_level()) == SEC_LEVEL_DELTA) && malf.nuking) + for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) + point.the_disk = src //the pinpointer will detect the shunted AI + +/obj/machinery/power/apc/proc/malfvacate(forced) + if(!occupier) + return + if(occupier.parent && occupier.parent.stat != DEAD) + occupier.mind.transfer_to(occupier.parent) + occupier.parent.shunted = 0 + occupier.parent.adjustOxyLoss(occupier.getOxyLoss()) + occupier.parent.cancel_camera() + qdel(occupier) + if(seclevel2num(get_security_level()) == SEC_LEVEL_DELTA) + for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) + for(var/mob/living/silicon/ai/A in ai_list) + if((A.stat != DEAD) && A.nuking) + point.the_disk = A //The pinpointer tracks the AI back into its core. + else + to_chat(occupier, "Primary core damaged, unable to return core processes.") + if(forced) + occupier.loc = loc + occupier.death() + occupier.gib() + for(var/obj/item/pinpointer/point in GLOB.pinpointer_list) + point.the_disk = null //Pinpointers go back to tracking the nuke disk + +/obj/machinery/power/apc/proc/ion_act() + //intended to be exactly the same as an AI malf attack + if(!src.malfhack && is_station_level(src.z)) + if(prob(3)) + src.locked = 1 + if(src.cell.charge > 0) + src.cell.charge = 0 + cell.corrupt() + src.malfhack = 1 + update_icon() + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(3, 0, src.loc) + smoke.attach(src) + smoke.start() + do_sparks(3, 1, src) + for(var/mob/M in viewers(src)) + M.show_message("The [src.name] suddenly lets out a blast of smoke and some sparks!", 3, "You hear sizzling electronics.", 2) + + +/obj/machinery/power/apc/surplus() + if(terminal) + return terminal.surplus() + else + return 0 + +/obj/machinery/power/apc/add_load(amount) + if(terminal && terminal.powernet) + terminal.add_load(amount) + +/obj/machinery/power/apc/avail() + if(terminal) + return terminal.avail() + else + return 0 + +/obj/machinery/power/apc/process() + if(stat & (BROKEN|MAINT)) + return + if(!area.requires_power) + return + + lastused_light = area.usage(STATIC_LIGHT) + lastused_light += area.usage(LIGHT) + lastused_equip = area.usage(EQUIP) + lastused_equip += area.usage(STATIC_EQUIP) + lastused_environ = area.usage(ENVIRON) + lastused_environ += area.usage(STATIC_ENVIRON) + area.clear_usage() + + lastused_total = lastused_light + lastused_equip + lastused_environ + + //store states to update icon if any change + var/last_lt = lighting + var/last_eq = equipment + var/last_en = environ + var/last_ch = charging + + var/excess = surplus() + + if(!src.avail()) + main_status = 0 + else if(excess < 0) + main_status = 1 + else + main_status = 2 + + if(debug) + log_debug("Status: [main_status] - Excess: [excess] - Last Equip: [lastused_equip] - Last Light: [lastused_light] - Longterm: [longtermpower]") + + if(cell && !shorted) + // draw power from cell as before to power the area + var/cellused = min(cell.charge, GLOB.CELLRATE * lastused_total) // clamp deduction to a max, amount left in cell + cell.use(cellused) + + if(excess > lastused_total) // if power excess recharge the cell + // by the same amount just used + cell.give(cellused) + add_load(cellused/GLOB.CELLRATE) // add the load used to recharge the cell + + + else // no excess, and not enough per-apc + if((cell.charge/GLOB.CELLRATE + excess) >= lastused_total) // can we draw enough from cell+grid to cover last usage? + cell.charge = min(cell.maxcharge, cell.charge + GLOB.CELLRATE * excess) //recharge with what we can + add_load(excess) // so draw what we can from the grid + charging = 0 + + else // not enough power available to run the last tick! + charging = 0 + chargecount = 0 + // This turns everything off in the case that there is still a charge left on the battery, just not enough to run the room. + equipment = autoset(equipment, 0) + lighting = autoset(lighting, 0) + environ = autoset(environ, 0) + autoflag = 0 + + + // Set channels depending on how much charge we have left + + // Allow the APC to operate as normal if the cell can charge + if(charging && longtermpower < 10) + longtermpower += 1 + else if(longtermpower > -10) + longtermpower -= 2 + + if(cell.charge >= 1250 || longtermpower > 0) // Put most likely at the top so we don't check it last, effeciency 101 + if(autoflag != 3) + equipment = autoset(equipment, 1) + lighting = autoset(lighting, 1) + environ = autoset(environ, 1) + autoflag = 3 + if(report_power_alarm && is_station_contact(z)) + SSalarms.power_alarm.clearAlarm(loc, src) + else if(cell.charge < 1250 && cell.charge > 750 && longtermpower < 0) // <30%, turn off equipment + if(autoflag != 2) + equipment = autoset(equipment, 2) + lighting = autoset(lighting, 1) + environ = autoset(environ, 1) + if(report_power_alarm && is_station_contact(z)) + SSalarms.power_alarm.triggerAlarm(loc, src) + autoflag = 2 + else if(cell.charge < 750 && cell.charge > 10) // <15%, turn off lighting & equipment + if((autoflag > 1 && longtermpower < 0) || (autoflag > 1 && longtermpower >= 0)) + equipment = autoset(equipment, 2) + lighting = autoset(lighting, 2) + environ = autoset(environ, 1) + if(report_power_alarm && is_station_contact(z)) + SSalarms.power_alarm.triggerAlarm(loc, src) + autoflag = 1 + else if(cell.charge <= 0) // zero charge, turn all off + if(autoflag != 0) + equipment = autoset(equipment, 0) + lighting = autoset(lighting, 0) + environ = autoset(environ, 0) + if(report_power_alarm && is_station_contact(z)) + SSalarms.power_alarm.triggerAlarm(loc, src) + autoflag = 0 + + // now trickle-charge the cell + if(chargemode && charging == 1 && operating) + if(excess > 0) // check to make sure we have enough to charge + // Max charge is capped to % per second constant + var/ch = min(excess*GLOB.CELLRATE, cell.maxcharge*GLOB.CHARGELEVEL) + add_load(ch/GLOB.CELLRATE) // Removes the power we're taking from the grid + cell.give(ch) // actually recharge the cell + + else + charging = 0 // stop charging + chargecount = 0 + + // show cell as fully charged if so + if(cell.charge >= cell.maxcharge) + cell.charge = cell.maxcharge + charging = 2 + + if(chargemode) + if(!charging) + if(excess > cell.maxcharge*GLOB.CHARGELEVEL) + chargecount++ + else + chargecount = 0 + + if(chargecount == 10) + + chargecount = 0 + charging = 1 + + else // chargemode off + charging = 0 + chargecount = 0 + + if(excess >= 5000000 && !shock_proof) //If there's more than 5,000,000 watts in the grid, start arcing and shocking people. + if(prob(5)) + var/list/shock_mobs = list() + for(var/C in view(get_turf(src), 5)) //We only want to shock a single random mob in range, not every one. + if(isliving(C)) + shock_mobs += C + if(shock_mobs.len) + var/mob/living/L = pick(shock_mobs) + L.electrocute_act(rand(5, 25), "electrical arc") + playsound(get_turf(L), 'sound/effects/eleczap.ogg', 75, 1) + Beam(L, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5) + + else // no cell, switch everything off + + charging = 0 + chargecount = 0 + equipment = autoset(equipment, 0) + lighting = autoset(lighting, 0) + environ = autoset(environ, 0) + if(report_power_alarm && is_station_contact(z)) + SSalarms.power_alarm.triggerAlarm(loc, src) + autoflag = 0 + + // update icon & area power if anything changed + + if(last_lt != lighting || last_eq != equipment || last_en != environ) + queue_icon_update() + update() + else if(last_ch != charging) + queue_icon_update() + +/obj/machinery/power/apc/proc/autoset(var/val, var/on) + if(on==0) + if(val==2) // if on, return off + return 0 + else if(val==3) // if auto-on, return auto-off + return 1 + + else if(on==1) + if(val==1) // if auto-off, return auto-on + return 3 + + else if(on==2) + if(val==3) // if auto-on, return auto-off + return 1 + + return val + +// damage and destruction acts + +/obj/machinery/power/apc/emp_act(severity) + if(cell) + cell.emp_act(severity) + if(occupier) + occupier.emp_act(severity) + lighting = 0 + equipment = 0 + environ = 0 + update_icon() + update() + spawn(600) + equipment = 3 + environ = 3 + update_icon() + update() + ..() + +/obj/machinery/power/apc/blob_act(obj/structure/blob/B) + set_broken() + +/obj/machinery/power/apc/disconnect_terminal() + if(terminal) + terminal.master = null + terminal = null + +/obj/machinery/power/apc/proc/set_broken() + if(malfai && operating) + malfai.malf_picker.processing_time = Clamp(malfai.malf_picker.processing_time - 10,0,1000) + stat |= BROKEN + operating = 0 + if(occupier) + malfvacate(1) + update_icon() + update() + +// overload all the lights in this APC area + +/obj/machinery/power/apc/proc/overload_lighting(chance = 100) + if(!operating || shorted) + return + if(cell && cell.charge >= 20) + cell.use(20) + spawn(0) + for(var/obj/machinery/light/L in area) + if(prob(chance)) + L.break_light_tube(0, 1) + stoplag() + +/obj/machinery/power/apc/proc/null_charge() + for(var/obj/machinery/light/L in area) + L.break_light_tube(0, 1) + stoplag() + +/obj/machinery/power/apc/proc/setsubsystem(val) + if(cell && cell.charge > 0) + return (val==1) ? 0 : val + else if(val == 3) + return 1 + else + return 0 + +/obj/machinery/power/apc/proc/set_nightshift(on) + set waitfor = FALSE + nightshift_lights = on + for(var/obj/machinery/light/L in area) + if(L.nightshift_allowed) + L.nightshift_enabled = nightshift_lights + L.update(FALSE) + CHECK_TICK + +#undef APC_UPDATE_ICON_COOLDOWN diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index 9f48d451f36..7260c836d78 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -1,867 +1,867 @@ -#define HEALPERCABLE 3 -#define MAXCABLEPERHEAL 8 -/////////////////////////////// -//CABLE STRUCTURE -/////////////////////////////// - - -//////////////////////////////// -// Definitions -//////////////////////////////// - -/* Cable directions (d1 and d2) - - - 9 1 5 - \ | / - 8 - 0 - 4 - / | \ - 10 2 6 - -If d1 = 0 and d2 = 0, there's no cable -If d1 = 0 and d2 = dir, it's a O-X cable, getting from the center of the tile to dir (knot cable) -If d1 = dir1 and d2 = dir2, it's a full X-X cable, getting from dir1 to dir2 -By design, d1 is the smallest direction and d2 is the highest -*/ - -/obj/structure/cable - level = 1 - anchored = 1 - on_blueprints = TRUE - var/datum/powernet/powernet - name = "power cable" - desc = "A flexible superconducting cable for heavy-duty power transfer" - icon = 'icons/obj/power_cond/power_cond_white.dmi' - icon_state = "0-1" - var/d1 = 0 - var/d2 = 1 - layer = WIRE_LAYER //Just below unary stuff, which is at 2.45 and above pipes, which are at 2.4 - color = COLOR_RED - -/obj/structure/cable/yellow - color = COLOR_YELLOW - -/obj/structure/cable/green - color = COLOR_GREEN - -/obj/structure/cable/blue - color = COLOR_BLUE - -/obj/structure/cable/pink - color = COLOR_PINK - -/obj/structure/cable/orange - color = COLOR_ORANGE - -/obj/structure/cable/cyan - color = COLOR_CYAN - -/obj/structure/cable/white - color = COLOR_WHITE - -/obj/structure/cable/Initialize(mapload) - . = ..() - - // ensure d1 & d2 reflect the icon_state for entering and exiting cable - var/dash = findtext(icon_state, "-") - d1 = text2num(copytext( icon_state, 1, dash )) - d2 = text2num(copytext( icon_state, dash+1 )) - - var/turf/T = get_turf(src) // hide if turf is not intact - if(level == 1) - hide(T.intact) - LAZYADD(GLOB.cable_list, src) //add it to the global cable list - -/obj/structure/cable/Destroy() // called when a cable is deleted - if(powernet) - cut_cable_from_powernet() // update the powernets - LAZYREMOVE(GLOB.cable_list, src) //remove it from global cable list - return ..() // then go ahead and delete the cable - -/obj/structure/cable/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - var/turf/T = get_turf(src) - if(d1) // 0-X cables are 1 unit, X-X cables are 2 units long - new/obj/item/stack/cable_coil(T, 2, paramcolor = color) - else - new/obj/item/stack/cable_coil(T, 1, paramcolor = color) - qdel(src) - -/////////////////////////////////// -// General procedures -/////////////////////////////////// - -//If underfloor, hide the cable -/obj/structure/cable/hide(i) - - if(level == 1 && isturf(loc)) - invisibility = i ? 101 : 0 - updateicon() - -/obj/structure/cable/proc/updateicon() - if(invisibility) - icon_state = "[d1]-[d2]-f" - else - icon_state = "[d1]-[d2]" - - -//////////////////////////////////////////// -// Power related -/////////////////////////////////////////// - -// All power generation handled in add_avail() -// Machines should use add_load(), surplus(), avail() -// Non-machines should use add_delayedload(), delayed_surplus(), newavail() - -/obj/structure/cable/proc/add_avail(amount) - if(powernet) - powernet.newavail += amount - -/obj/structure/cable/proc/add_load(amount) - if(powernet) - powernet.load += amount - -/obj/structure/cable/proc/surplus() - if(powernet) - return Clamp(powernet.avail-powernet.load, 0, powernet.avail) - else - return 0 - -/obj/structure/cable/proc/avail() - if(powernet) - return powernet.avail - else - return 0 - -/obj/structure/cable/proc/add_delayedload(amount) - if(powernet) - powernet.delayedload += amount - -/obj/structure/cable/proc/delayed_surplus() - if(powernet) - return Clamp(powernet.newavail - powernet.delayedload, 0, powernet.newavail) - else - return 0 - -/obj/structure/cable/proc/newavail() - if(powernet) - return powernet.newavail - else - return 0 - -//Telekinesis has no effect on a cable -/obj/structure/cable/attack_tk(mob/user) - return - -// Items usable on a cable : -// - Wirecutters : cut it duh ! -// - Cable coil : merge cables -// - Multitool : get the power currently passing through the cable -// -/obj/structure/cable/attackby(obj/item/W, mob/user) - var/turf/T = get_turf(src) - if(T.intact) - return - - else if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/coil = W - if(coil.get_amount() < 1) - to_chat(user, "Not enough cable!") - return - coil.cable_join(src, user) - - else if(istype(W, /obj/item/twohanded/rcl)) - var/obj/item/twohanded/rcl/R = W - if(R.loaded) - R.loaded.cable_join(src, user) - R.is_empty(user) - - else if(istype(W, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/C = W - cable_color(C.colourName) - - else - if(W.flags & CONDUCT) - shock(user, 50, 0.7) - - add_fingerprint(user) - -/obj/structure/cable/multitool_act(mob/user, obj/item/I) - . = TRUE - var/turf/T = get_turf(src) - if(T.intact) - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(powernet && (powernet.avail > 0)) // is it powered? - to_chat(user, "Total power: [DisplayPower(powernet.avail)]\nLoad: [DisplayPower(powernet.load)]\nExcess power: [DisplayPower(surplus())]") - else - to_chat(user, "The cable is not powered.") - shock(user, 5, 0.2) - -/obj/structure/cable/wirecutter_act(mob/user, obj/item/I) - . = TRUE - var/turf/T = get_turf(src) - if(T.intact) - return - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(shock(user, 50)) - return - user.visible_message("[user] cuts the cable.", "You cut the cable.") - investigate_log("was cut by [key_name(usr, 1)] in [get_area(user)]([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])","wires") - deconstruct() - -// shock the user with probability prb -/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1) - if(!prob(prb)) - return FALSE - if(electrocute_mob(user, powernet, src, siemens_coeff)) - do_sparks(5, 1, src) - return TRUE - else - return FALSE - -/obj/structure/cable/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -obj/structure/cable/proc/cable_color(var/colorC) - if(colorC) - if(colorC == "rainbow") - color = color_rainbow() - else - color = colorC - else - color = "#DD0000" - -/obj/structure/cable/proc/color_rainbow() - color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) - return color - -///////////////////////////////////////////////// -// Cable laying helpers -//////////////////////////////////////////////// - -//handles merging diagonally matching cables -//for info : direction^3 is flipping horizontally, direction^12 is flipping vertically -/obj/structure/cable/proc/mergeDiagonalsNetworks(direction) - - //search for and merge diagonally matching cables from the first direction component (north/south) - var/turf/T = get_step(src, direction&3)//go north/south - - for(var/obj/structure/cable/C in T) - - if(!C) - continue - - if(src == C) - continue - - if(C.d1 == (direction^3) || C.d2 == (direction^3)) //we've got a diagonally matching cable - if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) - var/datum/powernet/newPN = new() - newPN.add_cable(C) - - if(powernet) //if we already have a powernet, then merge the two powernets - merge_powernets(powernet,C.powernet) - else - C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet - - //the same from the second direction component (east/west) - T = get_step(src, direction&12)//go east/west - - for(var/obj/structure/cable/C in T) - - if(!C) - continue - - if(src == C) - continue - if(C.d1 == (direction^12) || C.d2 == (direction^12)) //we've got a diagonally matching cable - if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) - var/datum/powernet/newPN = new() - newPN.add_cable(C) - - if(powernet) //if we already have a powernet, then merge the two powernets - merge_powernets(powernet,C.powernet) - else - C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet - -// merge with the powernets of power objects in the given direction -/obj/structure/cable/proc/mergeConnectedNetworks(direction) - - var/fdir = (!direction)? 0 : turn(direction, 180) //flip the direction, to match with the source position on its turf - - if(!(d1 == direction || d2 == direction)) //if the cable is not pointed in this direction, do nothing - return - - var/turf/TB = get_step(src, direction) - - for(var/obj/structure/cable/C in TB) - - if(!C) - continue - - if(src == C) - continue - - if(C.d1 == fdir || C.d2 == fdir) //we've got a matching cable in the neighbor turf - if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) - var/datum/powernet/newPN = new() - newPN.add_cable(C) - - if(powernet) //if we already have a powernet, then merge the two powernets - merge_powernets(powernet,C.powernet) - else - C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet - -// merge with the powernets of power objects in the source turf -/obj/structure/cable/proc/mergeConnectedNetworksOnTurf() - var/list/to_connect = list() - - if(!powernet) //if we somehow have no powernet, make one (should not happen for cables) - var/datum/powernet/newPN = new() - newPN.add_cable(src) - - //first let's add turf cables to our powernet - //then we'll connect machines on turf with a node cable is present - for(var/AM in loc) - if(istype(AM, /obj/structure/cable)) - var/obj/structure/cable/C = AM - if(C.d1 == d1 || C.d2 == d1 || C.d1 == d2 || C.d2 == d2) //only connected if they have a common direction - if(C.powernet == powernet) - continue - if(C.powernet) - merge_powernets(powernet, C.powernet) - else - powernet.add_cable(C) //the cable was powernetless, let's just add it to our powernet - - else if(istype(AM, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/N = AM - if(!N.terminal) - continue // APC are connected through their terminal - - if(N.terminal.powernet == powernet) - continue - - to_connect += N.terminal //we'll connect the machines after all cables are merged - - else if(istype(AM, /obj/machinery/power)) //other power machines - var/obj/machinery/power/M = AM - - if(M.powernet == powernet) - continue - - to_connect += M //we'll connect the machines after all cables are merged - - //now that cables are done, let's connect found machines - for(var/obj/machinery/power/PM in to_connect) - if(!PM.connect_to_network()) - PM.disconnect_from_network() //if we somehow can't connect the machine to the new powernet, remove it from the old nonetheless - -////////////////////////////////////////////// -// Powernets handling helpers -////////////////////////////////////////////// - -//if powernetless_only = 1, will only get connections without powernet -/obj/structure/cable/proc/get_connections(powernetless_only = 0) - . = list() // this will be a list of all connected power objects - var/turf/T - - //get matching cables from the first direction - if(d1) //if not a node cable - T = get_step(src, d1) - if(T) - . += power_list(T, src, turn(d1, 180), powernetless_only) //get adjacents matching cables - - if(d1&(d1-1)) //diagonal direction, must check the 4 possibles adjacents tiles - T = get_step(src,d1&3) // go north/south - if(T) - . += power_list(T, src, d1 ^ 3, powernetless_only) //get diagonally matching cables - T = get_step(src,d1&12) // go east/west - if(T) - . += power_list(T, src, d1 ^ 12, powernetless_only) //get diagonally matching cables - - . += power_list(loc, src, d1, powernetless_only) //get on turf matching cables - - //do the same on the second direction (which can't be 0) - T = get_step(src, d2) - if(T) - . += power_list(T, src, turn(d2, 180), powernetless_only) //get adjacents matching cables - - if(d2&(d2-1)) //diagonal direction, must check the 4 possibles adjacents tiles - T = get_step(src,d2&3) // go north/south - if(T) - . += power_list(T, src, d2 ^ 3, powernetless_only) //get diagonally matching cables - T = get_step(src,d2&12) // go east/west - if(T) - . += power_list(T, src, d2 ^ 12, powernetless_only) //get diagonally matching cables - . += power_list(loc, src, d2, powernetless_only) //get on turf matching cables - - return . - -//should be called after placing a cable which extends another cable, creating a "smooth" cable that no longer terminates in the centre of a turf. -//needed as this can, unlike other placements, disconnect cables -/obj/structure/cable/proc/denode() - var/turf/T1 = loc - if(!T1) - return - - var/list/powerlist = power_list(T1,src,0,0) //find the other cables that ended in the centre of the turf, with or without a powernet - if(powerlist.len>0) - var/datum/powernet/PN = new() - propagate_network(powerlist[1],PN) //propagates the new powernet beginning at the source cable - - if(PN.is_empty()) //can happen with machines made nodeless when smoothing cables - qdel(PN) - -/obj/structure/cable/proc/auto_propogate_cut_cable(obj/O) - if(O && !QDELETED(O)) - var/datum/powernet/newPN = new()// creates a new powernet... - propagate_network(O, newPN)//... and propagates it to the other side of the cable - -// cut the cable's powernet at this cable and updates the powergrid -/obj/structure/cable/proc/cut_cable_from_powernet(remove=TRUE) - var/turf/T1 = loc - var/list/P_list - if(!T1) - return - if(d1) - T1 = get_step(T1, d1) - P_list = power_list(T1, src, turn(d1,180),0,cable_only = 1) // what adjacently joins on to cut cable... - - P_list += power_list(loc, src, d1, 0, cable_only = 1)//... and on turf - - - if(P_list.len == 0)//if nothing in both list, then the cable was a lone cable, just delete it and its powernet - powernet.remove_cable(src) - - for(var/obj/machinery/power/P in T1)//check if it was powering a machine - if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to - P.disconnect_from_network() //remove from current network (and delete powernet) - return - - var/obj/O = P_list[1] - // remove the cut cable from its turf and powernet, so that it doesn't get count in propagate_network worklist - if(remove) - loc = null - powernet.remove_cable(src) //remove the cut cable from its powernet - - // queue it to rebuild - SSmachines.deferred_powernet_rebuilds += O -// addtimer(CALLBACK(O, .proc/auto_propogate_cut_cable, O), 0) //so we don't rebuild the network X times when singulo/explosion destroys a line of X cables - - // Disconnect machines connected to nodes - if(d1 == 0) // if we cut a node (O-X) cable - for(var/obj/machinery/power/P in T1) - if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to - P.disconnect_from_network() //remove from current network - - -/////////////////////////////////////////////// -// The cable coil object, used for laying cable -/////////////////////////////////////////////// - -//////////////////////////////// -// Definitions -//////////////////////////////// - -GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe("cable restraints", /obj/item/restraints/handcuffs/cable, 15))) - -/obj/item/stack/cable_coil - name = "cable coil" - singular_name = "cable" - icon = 'icons/obj/power.dmi' - icon_state = "coil" - item_state = "coil_red" - amount = MAXCOIL - max_amount = MAXCOIL - merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves - color = COLOR_RED - desc = "A coil of power cable." - throwforce = 10 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 2 - throw_range = 5 - materials = list(MAT_METAL=10, MAT_GLASS=5) - flags = CONDUCT - slot_flags = SLOT_BELT - item_state = "coil" - attack_verb = list("whipped", "lashed", "disciplined", "flogged") - usesound = 'sound/items/deconstruct.ogg' - toolspeed = 1 - -/obj/item/stack/cable_coil/suicide_act(mob/user) - if(locate(/obj/structure/chair/stool) in user.loc) - user.visible_message("[user] is making a noose with the [name]! It looks like [user.p_theyre()] trying to commit suicide.") - else - user.visible_message("[user] is strangling [user.p_them()]self with the [name]! It looks like [user.p_theyre()] trying to commit suicide.") - return OXYLOSS - -/obj/item/stack/cable_coil/New(loc, length = MAXCOIL, paramcolor = null) - ..() - amount = length - if(paramcolor) - color = paramcolor - pixel_x = rand(-2,2) - pixel_y = rand(-2,2) - update_icon() - recipes = GLOB.cable_coil_recipes - update_wclass() - -/////////////////////////////////// -// General procedures -/////////////////////////////////// -//you can use wires to heal robotics -/obj/item/stack/cable_coil/attack(mob/M, mob/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/S = H.bodyparts_by_name[user.zone_selected] - - if(!S) - return - if(!S.is_robotic() || user.a_intent != INTENT_HELP || S.open == 2) - return ..() - - if(S.burn_dam > ROBOLIMB_SELF_REPAIR_CAP) - to_chat(user, "The damage is far too severe to patch over externally.") - return - - if(!S.burn_dam) - to_chat(user, "Nothing to fix!") - return - - if(H == user) - if(!do_mob(user, H, 10)) - return 0 - var/cable_used = 0 - var/childlist - if(!isnull(S.children)) - childlist = S.children.Copy() - var/parenthealed = FALSE - while(cable_used <= MAXCABLEPERHEAL && amount >= 1) - var/obj/item/organ/external/E - if(S.burn_dam) - E = S - else if(LAZYLEN(childlist)) - E = pick_n_take(childlist) - if(!E.burn_dam || !E.is_robotic()) - continue - else if(S.parent && !parenthealed) - E = S.parent - parenthealed = TRUE - if(!E.burn_dam || !E.is_robotic()) - break - else - break - while(cable_used <= MAXCABLEPERHEAL && E.burn_dam && amount >= 1) - use(1) - cable_used += 1 - E.heal_damage(0, HEALPERCABLE, 0, 1) - user.visible_message("\The [user] repairs some burn damage on \the [M]'s [E.name] with \the [src].") - return 1 - - else - return ..() - -/obj/item/stack/cable_coil/split() - var/obj/item/stack/cable_coil/C = ..() - C.color = color - return C - -/obj/item/stack/cable_coil/update_icon() - if(!color) - color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_ORANGE, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) - if(amount == 1) - icon_state = "coil1" - name = "cable piece" - else if(amount == 2) - icon_state = "coil2" - name = "cable piece" - else - icon_state = "coil" - name = "cable coil" - ..() - -/obj/item/stack/cable_coil/proc/update_wclass() - if(amount == 1) - w_class = WEIGHT_CLASS_TINY - else - w_class = WEIGHT_CLASS_SMALL - -/obj/item/stack/cable_coil/examine(mob/user) - . = ..() - if(in_range(user, src)) - if(get_amount() == 1) - . += "A short piece of power cable." - else if(get_amount() == 2) - . += "A piece of power cable." - else - . += "A coil of power cable. There are [get_amount()] lengths of cable in the coil." - -// Items usable on a cable coil : -// - Wirecutters : cut them duh ! -// - Cable coil : merge cables -/obj/item/stack/cable_coil/attackby(obj/item/W, mob/user) - ..() - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/C = W - // Cable merging is handled by parent proc - if(C.amount >= MAXCOIL) - to_chat(user, "The coil is as long as it will get.") - return - if( (C.amount + src.amount <= MAXCOIL) ) - to_chat(user, "You join the cable coils together.") - return - else - to_chat(user, "You transfer [get_amount_transferred()] length\s of cable from one coil to the other.") - return - - if(istype(W, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/C = W - cable_color(C.colourName) - -/////////////////////////////////////////////// -// Cable laying procedures -////////////////////////////////////////////// - -/obj/item/stack/cable_coil/proc/get_new_cable(location) - var/obj/structure/cable/C = new(location) - C.cable_color(color) - - return C - -// called when cable_coil is clicked on a turf/simulated/floor -/obj/item/stack/cable_coil/proc/place_turf(turf/T, mob/user, dirnew) - if(!isturf(user.loc)) - return - - if(!isturf(T) || T.intact || !T.can_have_cabling()) - to_chat(user, "You can only lay cables on catwalks and plating!") - return - - if(get_amount() < 1) // Out of cable - to_chat(user, "There is no cable left!") - return - - if(get_dist(T,user) > 1) // Too far - to_chat(user, "You can't lay cable at a place that far away!") - return - - var/dirn - if(!dirnew) //If we weren't given a direction, come up with one! (Called as null from catwalk.dm and floor.dm) - if(user.loc == T) - dirn = user.dir //If laying on the tile we're on, lay in the direction we're facing - else - dirn = get_dir(T, user) - else - dirn = dirnew - - for(var/obj/structure/cable/LC in T) - if(LC.d2 == dirn && LC.d1 == 0) - to_chat(user, "There's already a cable at that position!") - return - - var/obj/structure/cable/C = get_new_cable(T) - - //set up the new cable - C.d1 = 0 //it's a O-X node cable - C.d2 = dirn - C.add_fingerprint(user) - C.updateicon() - - //create a new powernet with the cable, if needed it will be merged later - var/datum/powernet/PN = new() - PN.add_cable(C) - - C.mergeConnectedNetworks(C.d2) //merge the powernet with adjacents powernets - C.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets - - if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions - C.mergeDiagonalsNetworks(C.d2) - - use(1) - - if(C.shock(user, 50)) - if(prob(50)) //fail - new /obj/item/stack/cable_coil(get_turf(C), 1, paramcolor = C.color) - C.deconstruct() - - return C - -// called when cable_coil is click on an installed obj/cable -// or click on a turf that already contains a "node" cable -/obj/item/stack/cable_coil/proc/cable_join(obj/structure/cable/C, mob/user) - var/turf/U = user.loc - if(!isturf(U)) - return - - var/turf/T = get_turf(C) - - if(!isturf(T) || T.intact) // sanity checks, also stop use interacting with T-scanner revealed cable - return - - if(get_dist(C, user) > 1) // make sure it's close enough - to_chat(user, "You can't lay cable at a place that far away!") - return - - - if(U == T) //if clicked on the turf we're standing on, try to put a cable in the direction we're facing - place_turf(T,user) - return - - var/dirn = get_dir(C, user) - - // one end of the clicked cable is pointing towards us - if(C.d1 == dirn || C.d2 == dirn) - if(U.intact) // can't place a cable if the floor is complete - to_chat(user, "You can't lay cable there unless the floor tiles are removed!") - return - else - // cable is pointing at us, we're standing on an open tile - // so create a stub pointing at the clicked cable on our tile - - var/fdirn = turn(dirn, 180) // the opposite direction - - for(var/obj/structure/cable/LC in U) // check to make sure there's not a cable there already - if(LC.d1 == fdirn || LC.d2 == fdirn) - to_chat(user, "There's already a cable at that position!") - return - - var/obj/structure/cable/NC = get_new_cable (U) - - NC.d1 = 0 - NC.d2 = fdirn - NC.add_fingerprint(user) - NC.update_icon() - - //create a new powernet with the cable, if needed it will be merged later - var/datum/powernet/newPN = new() - newPN.add_cable(NC) - - NC.mergeConnectedNetworks(NC.d2) //merge the powernet with adjacents powernets - NC.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets - - if(NC.d2 & (NC.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions - NC.mergeDiagonalsNetworks(NC.d2) - - use(1) - - if(NC.shock(user, 50)) - if(prob(50)) //fail - NC.deconstruct() - return - - // exisiting cable doesn't point at our position, so see if it's a stub - else if(C.d1 == 0) - // if so, make it a full cable pointing from it's old direction to our dirn - var/nd1 = C.d2 // these will be the new directions - var/nd2 = dirn - - - if(nd1 > nd2) // swap directions to match icons/states - nd1 = dirn - nd2 = C.d2 - - - for(var/obj/structure/cable/LC in T) // check to make sure there's no matching cable - if(LC == C) // skip the cable we're interacting with - continue - if((LC.d1 == nd1 && LC.d2 == nd2) || (LC.d1 == nd2 && LC.d2 == nd1) ) // make sure no cable matches either direction - to_chat(user, "There's already a cable at that position!") - return - - - C.cable_color(color) - - C.d1 = nd1 - C.d2 = nd2 - - C.add_fingerprint() - C.updateicon() - - - C.mergeConnectedNetworks(C.d1) //merge the powernets... - C.mergeConnectedNetworks(C.d2) //...in the two new cable directions - C.mergeConnectedNetworksOnTurf() - - if(C.d1 & (C.d1 - 1))// if the cable is layed diagonally, check the others 2 possible directions - C.mergeDiagonalsNetworks(C.d1) - - if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions - C.mergeDiagonalsNetworks(C.d2) - - use(1) - - if(C.shock(user, 50)) - if(prob(50)) //fail - C.deconstruct() - return - - C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets. - return - -////////////////////////////// -// Misc. -///////////////////////////// - -/obj/item/stack/cable_coil/cut - item_state = "coil2" - -/obj/item/stack/cable_coil/cut/Initialize(mapload) - . = ..() - src.amount = rand(1,2) - pixel_x = rand(-2,2) - pixel_y = rand(-2,2) - update_icon() - update_wclass() - -/obj/item/stack/cable_coil/yellow - color = COLOR_YELLOW - -/obj/item/stack/cable_coil/blue - color = COLOR_BLUE - -/obj/item/stack/cable_coil/green - color = COLOR_GREEN - -/obj/item/stack/cable_coil/pink - color = COLOR_PINK - -/obj/item/stack/cable_coil/orange - color = COLOR_ORANGE - -/obj/item/stack/cable_coil/cyan - color = COLOR_CYAN - -/obj/item/stack/cable_coil/white - color = COLOR_WHITE - -/obj/item/stack/cable_coil/random/New() - color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) - ..() - -/obj/item/stack/cable_coil/proc/cable_color(var/colorC) - if(colorC) - if(colorC == "rainbow") - colorC = color_rainbow() - color = colorC - else - color = COLOR_RED - -/obj/item/stack/cable_coil/proc/color_rainbow() - color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) - return color - -/obj/item/stack/cable_coil/cyborg - name = "cyborg cable coil" - -/obj/item/stack/cable_coil/cyborg/attack_self(mob/user) - var/cablecolor = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white") - color = cablecolor - update_icon() - -#undef MAXCABLEPERHEAL -#undef HEALPERCABLE +#define HEALPERCABLE 3 +#define MAXCABLEPERHEAL 8 +/////////////////////////////// +//CABLE STRUCTURE +/////////////////////////////// + + +//////////////////////////////// +// Definitions +//////////////////////////////// + +/* Cable directions (d1 and d2) + + + 9 1 5 + \ | / + 8 - 0 - 4 + / | \ + 10 2 6 + +If d1 = 0 and d2 = 0, there's no cable +If d1 = 0 and d2 = dir, it's a O-X cable, getting from the center of the tile to dir (knot cable) +If d1 = dir1 and d2 = dir2, it's a full X-X cable, getting from dir1 to dir2 +By design, d1 is the smallest direction and d2 is the highest +*/ + +/obj/structure/cable + level = 1 + anchored = 1 + on_blueprints = TRUE + var/datum/powernet/powernet + name = "power cable" + desc = "A flexible superconducting cable for heavy-duty power transfer" + icon = 'icons/obj/power_cond/power_cond_white.dmi' + icon_state = "0-1" + var/d1 = 0 + var/d2 = 1 + layer = WIRE_LAYER //Just below unary stuff, which is at 2.45 and above pipes, which are at 2.4 + color = COLOR_RED + +/obj/structure/cable/yellow + color = COLOR_YELLOW + +/obj/structure/cable/green + color = COLOR_GREEN + +/obj/structure/cable/blue + color = COLOR_BLUE + +/obj/structure/cable/pink + color = COLOR_PINK + +/obj/structure/cable/orange + color = COLOR_ORANGE + +/obj/structure/cable/cyan + color = COLOR_CYAN + +/obj/structure/cable/white + color = COLOR_WHITE + +/obj/structure/cable/Initialize(mapload) + . = ..() + + // ensure d1 & d2 reflect the icon_state for entering and exiting cable + var/dash = findtext(icon_state, "-") + d1 = text2num(copytext( icon_state, 1, dash )) + d2 = text2num(copytext( icon_state, dash+1 )) + + var/turf/T = get_turf(src) // hide if turf is not intact + if(level == 1) + hide(T.intact) + LAZYADD(GLOB.cable_list, src) //add it to the global cable list + +/obj/structure/cable/Destroy() // called when a cable is deleted + if(powernet) + cut_cable_from_powernet() // update the powernets + LAZYREMOVE(GLOB.cable_list, src) //remove it from global cable list + return ..() // then go ahead and delete the cable + +/obj/structure/cable/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + var/turf/T = get_turf(src) + if(d1) // 0-X cables are 1 unit, X-X cables are 2 units long + new/obj/item/stack/cable_coil(T, 2, paramcolor = color) + else + new/obj/item/stack/cable_coil(T, 1, paramcolor = color) + qdel(src) + +/////////////////////////////////// +// General procedures +/////////////////////////////////// + +//If underfloor, hide the cable +/obj/structure/cable/hide(i) + + if(level == 1 && isturf(loc)) + invisibility = i ? 101 : 0 + updateicon() + +/obj/structure/cable/proc/updateicon() + if(invisibility) + icon_state = "[d1]-[d2]-f" + else + icon_state = "[d1]-[d2]" + + +//////////////////////////////////////////// +// Power related +/////////////////////////////////////////// + +// All power generation handled in add_avail() +// Machines should use add_load(), surplus(), avail() +// Non-machines should use add_delayedload(), delayed_surplus(), newavail() + +/obj/structure/cable/proc/add_avail(amount) + if(powernet) + powernet.newavail += amount + +/obj/structure/cable/proc/add_load(amount) + if(powernet) + powernet.load += amount + +/obj/structure/cable/proc/surplus() + if(powernet) + return Clamp(powernet.avail-powernet.load, 0, powernet.avail) + else + return 0 + +/obj/structure/cable/proc/avail() + if(powernet) + return powernet.avail + else + return 0 + +/obj/structure/cable/proc/add_delayedload(amount) + if(powernet) + powernet.delayedload += amount + +/obj/structure/cable/proc/delayed_surplus() + if(powernet) + return Clamp(powernet.newavail - powernet.delayedload, 0, powernet.newavail) + else + return 0 + +/obj/structure/cable/proc/newavail() + if(powernet) + return powernet.newavail + else + return 0 + +//Telekinesis has no effect on a cable +/obj/structure/cable/attack_tk(mob/user) + return + +// Items usable on a cable : +// - Wirecutters : cut it duh ! +// - Cable coil : merge cables +// - Multitool : get the power currently passing through the cable +// +/obj/structure/cable/attackby(obj/item/W, mob/user) + var/turf/T = get_turf(src) + if(T.intact) + return + + else if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil = W + if(coil.get_amount() < 1) + to_chat(user, "Not enough cable!") + return + coil.cable_join(src, user) + + else if(istype(W, /obj/item/twohanded/rcl)) + var/obj/item/twohanded/rcl/R = W + if(R.loaded) + R.loaded.cable_join(src, user) + R.is_empty(user) + + else if(istype(W, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/C = W + cable_color(C.colourName) + + else + if(W.flags & CONDUCT) + shock(user, 50, 0.7) + + add_fingerprint(user) + +/obj/structure/cable/multitool_act(mob/user, obj/item/I) + . = TRUE + var/turf/T = get_turf(src) + if(T.intact) + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(powernet && (powernet.avail > 0)) // is it powered? + to_chat(user, "Total power: [DisplayPower(powernet.avail)]\nLoad: [DisplayPower(powernet.load)]\nExcess power: [DisplayPower(surplus())]") + else + to_chat(user, "The cable is not powered.") + shock(user, 5, 0.2) + +/obj/structure/cable/wirecutter_act(mob/user, obj/item/I) + . = TRUE + var/turf/T = get_turf(src) + if(T.intact) + return + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(shock(user, 50)) + return + user.visible_message("[user] cuts the cable.", "You cut the cable.") + investigate_log("was cut by [key_name(usr, 1)] in [get_area(user)]([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])","wires") + deconstruct() + +// shock the user with probability prb +/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1) + if(!prob(prb)) + return FALSE + if(electrocute_mob(user, powernet, src, siemens_coeff)) + do_sparks(5, 1, src) + return TRUE + else + return FALSE + +/obj/structure/cable/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +obj/structure/cable/proc/cable_color(var/colorC) + if(colorC) + if(colorC == "rainbow") + color = color_rainbow() + else + color = colorC + else + color = "#DD0000" + +/obj/structure/cable/proc/color_rainbow() + color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) + return color + +///////////////////////////////////////////////// +// Cable laying helpers +//////////////////////////////////////////////// + +//handles merging diagonally matching cables +//for info : direction^3 is flipping horizontally, direction^12 is flipping vertically +/obj/structure/cable/proc/mergeDiagonalsNetworks(direction) + + //search for and merge diagonally matching cables from the first direction component (north/south) + var/turf/T = get_step(src, direction&3)//go north/south + + for(var/obj/structure/cable/C in T) + + if(!C) + continue + + if(src == C) + continue + + if(C.d1 == (direction^3) || C.d2 == (direction^3)) //we've got a diagonally matching cable + if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) + var/datum/powernet/newPN = new() + newPN.add_cable(C) + + if(powernet) //if we already have a powernet, then merge the two powernets + merge_powernets(powernet,C.powernet) + else + C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet + + //the same from the second direction component (east/west) + T = get_step(src, direction&12)//go east/west + + for(var/obj/structure/cable/C in T) + + if(!C) + continue + + if(src == C) + continue + if(C.d1 == (direction^12) || C.d2 == (direction^12)) //we've got a diagonally matching cable + if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) + var/datum/powernet/newPN = new() + newPN.add_cable(C) + + if(powernet) //if we already have a powernet, then merge the two powernets + merge_powernets(powernet,C.powernet) + else + C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet + +// merge with the powernets of power objects in the given direction +/obj/structure/cable/proc/mergeConnectedNetworks(direction) + + var/fdir = (!direction)? 0 : turn(direction, 180) //flip the direction, to match with the source position on its turf + + if(!(d1 == direction || d2 == direction)) //if the cable is not pointed in this direction, do nothing + return + + var/turf/TB = get_step(src, direction) + + for(var/obj/structure/cable/C in TB) + + if(!C) + continue + + if(src == C) + continue + + if(C.d1 == fdir || C.d2 == fdir) //we've got a matching cable in the neighbor turf + if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) + var/datum/powernet/newPN = new() + newPN.add_cable(C) + + if(powernet) //if we already have a powernet, then merge the two powernets + merge_powernets(powernet,C.powernet) + else + C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet + +// merge with the powernets of power objects in the source turf +/obj/structure/cable/proc/mergeConnectedNetworksOnTurf() + var/list/to_connect = list() + + if(!powernet) //if we somehow have no powernet, make one (should not happen for cables) + var/datum/powernet/newPN = new() + newPN.add_cable(src) + + //first let's add turf cables to our powernet + //then we'll connect machines on turf with a node cable is present + for(var/AM in loc) + if(istype(AM, /obj/structure/cable)) + var/obj/structure/cable/C = AM + if(C.d1 == d1 || C.d2 == d1 || C.d1 == d2 || C.d2 == d2) //only connected if they have a common direction + if(C.powernet == powernet) + continue + if(C.powernet) + merge_powernets(powernet, C.powernet) + else + powernet.add_cable(C) //the cable was powernetless, let's just add it to our powernet + + else if(istype(AM, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/N = AM + if(!N.terminal) + continue // APC are connected through their terminal + + if(N.terminal.powernet == powernet) + continue + + to_connect += N.terminal //we'll connect the machines after all cables are merged + + else if(istype(AM, /obj/machinery/power)) //other power machines + var/obj/machinery/power/M = AM + + if(M.powernet == powernet) + continue + + to_connect += M //we'll connect the machines after all cables are merged + + //now that cables are done, let's connect found machines + for(var/obj/machinery/power/PM in to_connect) + if(!PM.connect_to_network()) + PM.disconnect_from_network() //if we somehow can't connect the machine to the new powernet, remove it from the old nonetheless + +////////////////////////////////////////////// +// Powernets handling helpers +////////////////////////////////////////////// + +//if powernetless_only = 1, will only get connections without powernet +/obj/structure/cable/proc/get_connections(powernetless_only = 0) + . = list() // this will be a list of all connected power objects + var/turf/T + + //get matching cables from the first direction + if(d1) //if not a node cable + T = get_step(src, d1) + if(T) + . += power_list(T, src, turn(d1, 180), powernetless_only) //get adjacents matching cables + + if(d1&(d1-1)) //diagonal direction, must check the 4 possibles adjacents tiles + T = get_step(src,d1&3) // go north/south + if(T) + . += power_list(T, src, d1 ^ 3, powernetless_only) //get diagonally matching cables + T = get_step(src,d1&12) // go east/west + if(T) + . += power_list(T, src, d1 ^ 12, powernetless_only) //get diagonally matching cables + + . += power_list(loc, src, d1, powernetless_only) //get on turf matching cables + + //do the same on the second direction (which can't be 0) + T = get_step(src, d2) + if(T) + . += power_list(T, src, turn(d2, 180), powernetless_only) //get adjacents matching cables + + if(d2&(d2-1)) //diagonal direction, must check the 4 possibles adjacents tiles + T = get_step(src,d2&3) // go north/south + if(T) + . += power_list(T, src, d2 ^ 3, powernetless_only) //get diagonally matching cables + T = get_step(src,d2&12) // go east/west + if(T) + . += power_list(T, src, d2 ^ 12, powernetless_only) //get diagonally matching cables + . += power_list(loc, src, d2, powernetless_only) //get on turf matching cables + + return . + +//should be called after placing a cable which extends another cable, creating a "smooth" cable that no longer terminates in the centre of a turf. +//needed as this can, unlike other placements, disconnect cables +/obj/structure/cable/proc/denode() + var/turf/T1 = loc + if(!T1) + return + + var/list/powerlist = power_list(T1,src,0,0) //find the other cables that ended in the centre of the turf, with or without a powernet + if(powerlist.len>0) + var/datum/powernet/PN = new() + propagate_network(powerlist[1],PN) //propagates the new powernet beginning at the source cable + + if(PN.is_empty()) //can happen with machines made nodeless when smoothing cables + qdel(PN) + +/obj/structure/cable/proc/auto_propogate_cut_cable(obj/O) + if(O && !QDELETED(O)) + var/datum/powernet/newPN = new()// creates a new powernet... + propagate_network(O, newPN)//... and propagates it to the other side of the cable + +// cut the cable's powernet at this cable and updates the powergrid +/obj/structure/cable/proc/cut_cable_from_powernet(remove=TRUE) + var/turf/T1 = loc + var/list/P_list + if(!T1) + return + if(d1) + T1 = get_step(T1, d1) + P_list = power_list(T1, src, turn(d1,180),0,cable_only = 1) // what adjacently joins on to cut cable... + + P_list += power_list(loc, src, d1, 0, cable_only = 1)//... and on turf + + + if(P_list.len == 0)//if nothing in both list, then the cable was a lone cable, just delete it and its powernet + powernet.remove_cable(src) + + for(var/obj/machinery/power/P in T1)//check if it was powering a machine + if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to + P.disconnect_from_network() //remove from current network (and delete powernet) + return + + var/obj/O = P_list[1] + // remove the cut cable from its turf and powernet, so that it doesn't get count in propagate_network worklist + if(remove) + loc = null + powernet.remove_cable(src) //remove the cut cable from its powernet + + // queue it to rebuild + SSmachines.deferred_powernet_rebuilds += O +// addtimer(CALLBACK(O, .proc/auto_propogate_cut_cable, O), 0) //so we don't rebuild the network X times when singulo/explosion destroys a line of X cables + + // Disconnect machines connected to nodes + if(d1 == 0) // if we cut a node (O-X) cable + for(var/obj/machinery/power/P in T1) + if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to + P.disconnect_from_network() //remove from current network + + +/////////////////////////////////////////////// +// The cable coil object, used for laying cable +/////////////////////////////////////////////// + +//////////////////////////////// +// Definitions +//////////////////////////////// + +GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe("cable restraints", /obj/item/restraints/handcuffs/cable, 15))) + +/obj/item/stack/cable_coil + name = "cable coil" + singular_name = "cable" + icon = 'icons/obj/power.dmi' + icon_state = "coil" + item_state = "coil_red" + amount = MAXCOIL + max_amount = MAXCOIL + merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves + color = COLOR_RED + desc = "A coil of power cable." + throwforce = 10 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 2 + throw_range = 5 + materials = list(MAT_METAL=10, MAT_GLASS=5) + flags = CONDUCT + slot_flags = SLOT_BELT + item_state = "coil" + attack_verb = list("whipped", "lashed", "disciplined", "flogged") + usesound = 'sound/items/deconstruct.ogg' + toolspeed = 1 + +/obj/item/stack/cable_coil/suicide_act(mob/user) + if(locate(/obj/structure/chair/stool) in user.loc) + user.visible_message("[user] is making a noose with the [name]! It looks like [user.p_theyre()] trying to commit suicide.") + else + user.visible_message("[user] is strangling [user.p_them()]self with the [name]! It looks like [user.p_theyre()] trying to commit suicide.") + return OXYLOSS + +/obj/item/stack/cable_coil/New(loc, length = MAXCOIL, paramcolor = null) + ..() + amount = length + if(paramcolor) + color = paramcolor + pixel_x = rand(-2,2) + pixel_y = rand(-2,2) + update_icon() + recipes = GLOB.cable_coil_recipes + update_wclass() + +/////////////////////////////////// +// General procedures +/////////////////////////////////// +//you can use wires to heal robotics +/obj/item/stack/cable_coil/attack(mob/M, mob/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/S = H.bodyparts_by_name[user.zone_selected] + + if(!S) + return + if(!S.is_robotic() || user.a_intent != INTENT_HELP || S.open == 2) + return ..() + + if(S.burn_dam > ROBOLIMB_SELF_REPAIR_CAP) + to_chat(user, "The damage is far too severe to patch over externally.") + return + + if(!S.burn_dam) + to_chat(user, "Nothing to fix!") + return + + if(H == user) + if(!do_mob(user, H, 10)) + return 0 + var/cable_used = 0 + var/childlist + if(!isnull(S.children)) + childlist = S.children.Copy() + var/parenthealed = FALSE + while(cable_used <= MAXCABLEPERHEAL && amount >= 1) + var/obj/item/organ/external/E + if(S.burn_dam) + E = S + else if(LAZYLEN(childlist)) + E = pick_n_take(childlist) + if(!E.burn_dam || !E.is_robotic()) + continue + else if(S.parent && !parenthealed) + E = S.parent + parenthealed = TRUE + if(!E.burn_dam || !E.is_robotic()) + break + else + break + while(cable_used <= MAXCABLEPERHEAL && E.burn_dam && amount >= 1) + use(1) + cable_used += 1 + E.heal_damage(0, HEALPERCABLE, 0, 1) + user.visible_message("\The [user] repairs some burn damage on \the [M]'s [E.name] with \the [src].") + return 1 + + else + return ..() + +/obj/item/stack/cable_coil/split() + var/obj/item/stack/cable_coil/C = ..() + C.color = color + return C + +/obj/item/stack/cable_coil/update_icon() + if(!color) + color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_ORANGE, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) + if(amount == 1) + icon_state = "coil1" + name = "cable piece" + else if(amount == 2) + icon_state = "coil2" + name = "cable piece" + else + icon_state = "coil" + name = "cable coil" + ..() + +/obj/item/stack/cable_coil/proc/update_wclass() + if(amount == 1) + w_class = WEIGHT_CLASS_TINY + else + w_class = WEIGHT_CLASS_SMALL + +/obj/item/stack/cable_coil/examine(mob/user) + . = ..() + if(in_range(user, src)) + if(get_amount() == 1) + . += "A short piece of power cable." + else if(get_amount() == 2) + . += "A piece of power cable." + else + . += "A coil of power cable. There are [get_amount()] lengths of cable in the coil." + +// Items usable on a cable coil : +// - Wirecutters : cut them duh ! +// - Cable coil : merge cables +/obj/item/stack/cable_coil/attackby(obj/item/W, mob/user) + ..() + if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/C = W + // Cable merging is handled by parent proc + if(C.amount >= MAXCOIL) + to_chat(user, "The coil is as long as it will get.") + return + if( (C.amount + src.amount <= MAXCOIL) ) + to_chat(user, "You join the cable coils together.") + return + else + to_chat(user, "You transfer [get_amount_transferred()] length\s of cable from one coil to the other.") + return + + if(istype(W, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/C = W + cable_color(C.colourName) + +/////////////////////////////////////////////// +// Cable laying procedures +////////////////////////////////////////////// + +/obj/item/stack/cable_coil/proc/get_new_cable(location) + var/obj/structure/cable/C = new(location) + C.cable_color(color) + + return C + +// called when cable_coil is clicked on a turf/simulated/floor +/obj/item/stack/cable_coil/proc/place_turf(turf/T, mob/user, dirnew) + if(!isturf(user.loc)) + return + + if(!isturf(T) || T.intact || !T.can_have_cabling()) + to_chat(user, "You can only lay cables on catwalks and plating!") + return + + if(get_amount() < 1) // Out of cable + to_chat(user, "There is no cable left!") + return + + if(get_dist(T,user) > 1) // Too far + to_chat(user, "You can't lay cable at a place that far away!") + return + + var/dirn + if(!dirnew) //If we weren't given a direction, come up with one! (Called as null from catwalk.dm and floor.dm) + if(user.loc == T) + dirn = user.dir //If laying on the tile we're on, lay in the direction we're facing + else + dirn = get_dir(T, user) + else + dirn = dirnew + + for(var/obj/structure/cable/LC in T) + if(LC.d2 == dirn && LC.d1 == 0) + to_chat(user, "There's already a cable at that position!") + return + + var/obj/structure/cable/C = get_new_cable(T) + + //set up the new cable + C.d1 = 0 //it's a O-X node cable + C.d2 = dirn + C.add_fingerprint(user) + C.updateicon() + + //create a new powernet with the cable, if needed it will be merged later + var/datum/powernet/PN = new() + PN.add_cable(C) + + C.mergeConnectedNetworks(C.d2) //merge the powernet with adjacents powernets + C.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets + + if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions + C.mergeDiagonalsNetworks(C.d2) + + use(1) + + if(C.shock(user, 50)) + if(prob(50)) //fail + new /obj/item/stack/cable_coil(get_turf(C), 1, paramcolor = C.color) + C.deconstruct() + + return C + +// called when cable_coil is click on an installed obj/cable +// or click on a turf that already contains a "node" cable +/obj/item/stack/cable_coil/proc/cable_join(obj/structure/cable/C, mob/user) + var/turf/U = user.loc + if(!isturf(U)) + return + + var/turf/T = get_turf(C) + + if(!isturf(T) || T.intact) // sanity checks, also stop use interacting with T-scanner revealed cable + return + + if(get_dist(C, user) > 1) // make sure it's close enough + to_chat(user, "You can't lay cable at a place that far away!") + return + + + if(U == T) //if clicked on the turf we're standing on, try to put a cable in the direction we're facing + place_turf(T,user) + return + + var/dirn = get_dir(C, user) + + // one end of the clicked cable is pointing towards us + if(C.d1 == dirn || C.d2 == dirn) + if(U.intact) // can't place a cable if the floor is complete + to_chat(user, "You can't lay cable there unless the floor tiles are removed!") + return + else + // cable is pointing at us, we're standing on an open tile + // so create a stub pointing at the clicked cable on our tile + + var/fdirn = turn(dirn, 180) // the opposite direction + + for(var/obj/structure/cable/LC in U) // check to make sure there's not a cable there already + if(LC.d1 == fdirn || LC.d2 == fdirn) + to_chat(user, "There's already a cable at that position!") + return + + var/obj/structure/cable/NC = get_new_cable (U) + + NC.d1 = 0 + NC.d2 = fdirn + NC.add_fingerprint(user) + NC.update_icon() + + //create a new powernet with the cable, if needed it will be merged later + var/datum/powernet/newPN = new() + newPN.add_cable(NC) + + NC.mergeConnectedNetworks(NC.d2) //merge the powernet with adjacents powernets + NC.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets + + if(NC.d2 & (NC.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions + NC.mergeDiagonalsNetworks(NC.d2) + + use(1) + + if(NC.shock(user, 50)) + if(prob(50)) //fail + NC.deconstruct() + return + + // exisiting cable doesn't point at our position, so see if it's a stub + else if(C.d1 == 0) + // if so, make it a full cable pointing from it's old direction to our dirn + var/nd1 = C.d2 // these will be the new directions + var/nd2 = dirn + + + if(nd1 > nd2) // swap directions to match icons/states + nd1 = dirn + nd2 = C.d2 + + + for(var/obj/structure/cable/LC in T) // check to make sure there's no matching cable + if(LC == C) // skip the cable we're interacting with + continue + if((LC.d1 == nd1 && LC.d2 == nd2) || (LC.d1 == nd2 && LC.d2 == nd1) ) // make sure no cable matches either direction + to_chat(user, "There's already a cable at that position!") + return + + + C.cable_color(color) + + C.d1 = nd1 + C.d2 = nd2 + + C.add_fingerprint() + C.updateicon() + + + C.mergeConnectedNetworks(C.d1) //merge the powernets... + C.mergeConnectedNetworks(C.d2) //...in the two new cable directions + C.mergeConnectedNetworksOnTurf() + + if(C.d1 & (C.d1 - 1))// if the cable is layed diagonally, check the others 2 possible directions + C.mergeDiagonalsNetworks(C.d1) + + if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions + C.mergeDiagonalsNetworks(C.d2) + + use(1) + + if(C.shock(user, 50)) + if(prob(50)) //fail + C.deconstruct() + return + + C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets. + return + +////////////////////////////// +// Misc. +///////////////////////////// + +/obj/item/stack/cable_coil/cut + item_state = "coil2" + +/obj/item/stack/cable_coil/cut/Initialize(mapload) + . = ..() + src.amount = rand(1,2) + pixel_x = rand(-2,2) + pixel_y = rand(-2,2) + update_icon() + update_wclass() + +/obj/item/stack/cable_coil/yellow + color = COLOR_YELLOW + +/obj/item/stack/cable_coil/blue + color = COLOR_BLUE + +/obj/item/stack/cable_coil/green + color = COLOR_GREEN + +/obj/item/stack/cable_coil/pink + color = COLOR_PINK + +/obj/item/stack/cable_coil/orange + color = COLOR_ORANGE + +/obj/item/stack/cable_coil/cyan + color = COLOR_CYAN + +/obj/item/stack/cable_coil/white + color = COLOR_WHITE + +/obj/item/stack/cable_coil/random/New() + color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) + ..() + +/obj/item/stack/cable_coil/proc/cable_color(var/colorC) + if(colorC) + if(colorC == "rainbow") + colorC = color_rainbow() + color = colorC + else + color = COLOR_RED + +/obj/item/stack/cable_coil/proc/color_rainbow() + color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) + return color + +/obj/item/stack/cable_coil/cyborg + name = "cyborg cable coil" + +/obj/item/stack/cable_coil/cyborg/attack_self(mob/user) + var/cablecolor = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white") + color = cablecolor + update_icon() + +#undef MAXCABLEPERHEAL +#undef HEALPERCABLE diff --git a/code/modules/power/cable_heavyduty.dm b/code/modules/power/cable_heavyduty.dm index 03b6aafed28..2519dbcd198 100644 --- a/code/modules/power/cable_heavyduty.dm +++ b/code/modules/power/cable_heavyduty.dm @@ -25,4 +25,4 @@ ..() /obj/structure/cable/heavyduty/cable_color(var/colorC) - return \ No newline at end of file + return diff --git a/code/modules/power/cable_logic.dm b/code/modules/power/cable_logic.dm index b262b6fc3b8..09d10320fd5 100644 --- a/code/modules/power/cable_logic.dm +++ b/code/modules/power/cable_logic.dm @@ -1,292 +1,292 @@ -#define LOGIC_HIGH 5 - -//Indicators only have one input and no outputs -/obj/machinery/logic/indicator - //Input is searched from the 'dir' direction - var/obj/structure/cable/input - -/obj/machinery/logic/indicator/process() - if(input) - return 1 - - - if(!input) - var/turf/T = get_step(src, dir) - if(T) - var/inv_dir = turn(dir, 180) - for(var/obj/structure/cable/C in T) - if(C.d1 == inv_dir || C.d2 == inv_dir) - input = C - return 1 - - return 0 //If it gets to here, it means no suitable wire to link to was found. - -/obj/machinery/logic/indicator/bulb - icon = 'icons/obj/lighting.dmi' - icon_state = "bulb0" - -/obj/machinery/logic/indicator/bulb/process() - if(!..()) //Parent proc checks if input1 exists. - return - - var/datum/powernet/pn_input = input.powernet - if(!pn_input) - return - - if(pn_input.avail >= LOGIC_HIGH) - icon_state = "bulb1" - else - icon_state = "bulb0" - - - - -//Sensors only have one output and no inputs -/obj/machinery/logic/sensor - //Output is searched from the 'dir' direction - var/obj/structure/cable/output - -/obj/machinery/logic/sensor/process() - if(output) - return 1 - - if(!output) - var/turf/T = get_step(src, dir) - if(T) - var/inv_dir = turn(dir, 180) - for(var/obj/structure/cable/C in T) - if(C.d1 == inv_dir || C.d2 == inv_dir) - output = C - return 1 - - return 0 //If it gets to here, it means no suitable wire to link to was found. - -//Constant high generator. This will continue to send a signal of LOGIC_HIGH as long as it exists. -/obj/machinery/logic/sensor/constant_high - icon = 'icons/obj/atmospherics/outlet_injector.dmi' - icon_state = "off" - -/obj/machinery/logic/sensor/constant_high/process() - if(!..()) //Parent proc checks if input1 exists. - return - - var/datum/powernet/pn_output = output.powernet - if(!pn_output) - return - - pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) - - - - -//ONE INPUT logic elements have one input and one output -/obj/machinery/logic/oneinput - var/dir_input = 2 - var/dir_output = 1 - var/obj/structure/cable/input - var/obj/structure/cable/output - icon = 'icons/atmos/heat.dmi' - icon_state = "intact" - -/obj/machinery/logic/oneinput/process() - if(input && output) - return 1 - - if(!dir_input || !dir_output) - return 0 - - if(!input) - var/turf/T = get_step(src, dir_input) - if(T) - var/inv_dir = turn(dir_input, 180) - for(var/obj/structure/cable/C in T) - if(C.d1 == inv_dir || C.d2 == inv_dir) - input = C - - if(!output) - var/turf/T = get_step(src, dir_output) - if(T) - var/inv_dir = turn(dir_output, 180) - for(var/obj/structure/cable/C in T) - if(C.d1 == inv_dir || C.d2 == inv_dir) - output = C - - return 0 //On the process() call, where everything is still being searched for, it returns 0. It will return 1 on the next process() call. - -//NOT GATE -/obj/machinery/logic/oneinput/not/process() - if(!..()) //Parent proc checks if input1, input2 and output exist. - return - - var/datum/powernet/pn_input = input.powernet - - if(!pn_input) - return - - var/datum/powernet/pn_output = output.powernet - if(!pn_output) - return - - if( !(pn_input.avail >= LOGIC_HIGH)) - pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. - else - pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 - - - - - - - - - -//TWO INPUT logic elements have two inputs and one output -/obj/machinery/logic/twoinput - var/dir_input1 = 2 - var/dir_input2 = 8 - var/dir_output = 1 - var/obj/structure/cable/input1 - var/obj/structure/cable/input2 - var/obj/structure/cable/output - icon = 'icons/obj/atmospherics/mixer.dmi' - icon_state = "intact_off" - -/obj/machinery/logic/twoinput/process() - if(input1 && input2 && output) - return 1 - - if(!dir_input1 || !dir_input2 || !dir_output) - return 0 - - if(!input1) - var/turf/T = get_step(src, dir_input1) - if(T) - var/inv_dir = turn(dir_input1, 180) - for(var/obj/structure/cable/C in T) - if(C.d1 == inv_dir || C.d2 == inv_dir) - input1 = C - - if(!input2) - var/turf/T = get_step(src, dir_input2) - if(T) - var/inv_dir = turn(dir_input2, 180) - for(var/obj/structure/cable/C in T) - if(C.d1 == inv_dir || C.d2 == inv_dir) - input2 = C - - if(!output) - var/turf/T = get_step(src, dir_output) - if(T) - var/inv_dir = turn(dir_output, 180) - for(var/obj/structure/cable/C in T) - if(C.d1 == inv_dir || C.d2 == inv_dir) - output = C - - return 0 //On the process() call, where everything is still being searched for, it returns 0. It will return 1 on the next process() call. - -//AND GATE -/obj/machinery/logic/twoinput/and/process() - if(!..()) //Parent proc checks if input1, input2 and output exist. - return - - var/datum/powernet/pn_input1 = input1.powernet - var/datum/powernet/pn_input2 = input2.powernet - - if(!pn_input1 || !pn_input2) - return - - var/datum/powernet/pn_output = output.powernet - if(!pn_output) - return - - if( (pn_input1.avail >= LOGIC_HIGH) && (pn_input2.avail >= LOGIC_HIGH) ) - pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. - else - pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 - -//OR GATE -/obj/machinery/logic/twoinput/or/process() - if(!..()) //Parent proc checks if input1, input2 and output exist. - return - - var/datum/powernet/pn_input1 = input1.powernet - var/datum/powernet/pn_input2 = input2.powernet - - if(!pn_input1 || !pn_input2) - return - - var/datum/powernet/pn_output = output.powernet - if(!pn_output) - return - - if( (pn_input1.avail >= LOGIC_HIGH) || (pn_input2.avail >= LOGIC_HIGH) ) - pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. - else - pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 - -//XOR GATE -/obj/machinery/logic/twoinput/xor/process() - if(!..()) //Parent proc checks if input1, input2 and output exist. - return - - var/datum/powernet/pn_input1 = input1.powernet - var/datum/powernet/pn_input2 = input2.powernet - - if(!pn_input1 || !pn_input2) - return - - var/datum/powernet/pn_output = output.powernet - if(!pn_output) - return - - if( (pn_input1.avail >= LOGIC_HIGH) != (pn_input2.avail >= LOGIC_HIGH) ) - pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. - else - pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 - -//XNOR GATE (EQUIVALENCE) -/obj/machinery/logic/twoinput/xnor/process() - if(!..()) //Parent proc checks if input1, input2 and output exist. - return - - var/datum/powernet/pn_input1 = input1.powernet - var/datum/powernet/pn_input2 = input2.powernet - - if(!pn_input1 || !pn_input2) - return - - var/datum/powernet/pn_output = output.powernet - if(!pn_output) - return - - if( (pn_input1.avail >= LOGIC_HIGH) == (pn_input2.avail >= LOGIC_HIGH) ) - pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. - else - pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 - -#define RELAY_POWER_TRANSFER 2000 //How much power a relay transfers through. - -//RELAY - input1 governs the flow from input2 to output -/obj/machinery/logic/twoinput/relay/process() - if(!..()) //Parent proc checks if input1, input2 and output exist. - return - - var/datum/powernet/pn_input1 = input1.powernet - - if(!pn_input1) - return - - if( pn_input1.avail >= LOGIC_HIGH ) - var/datum/powernet/pn_input2 = input2.powernet - var/datum/powernet/pn_output = output.powernet - - if(!pn_output) - return - - if(pn_input2.avail >= RELAY_POWER_TRANSFER) - pn_input2.load += RELAY_POWER_TRANSFER - pn_output.newavail += RELAY_POWER_TRANSFER - - -#undef RELAY_POWER_TRANSFER -#undef LOGIC_HIGH \ No newline at end of file +#define LOGIC_HIGH 5 + +//Indicators only have one input and no outputs +/obj/machinery/logic/indicator + //Input is searched from the 'dir' direction + var/obj/structure/cable/input + +/obj/machinery/logic/indicator/process() + if(input) + return 1 + + + if(!input) + var/turf/T = get_step(src, dir) + if(T) + var/inv_dir = turn(dir, 180) + for(var/obj/structure/cable/C in T) + if(C.d1 == inv_dir || C.d2 == inv_dir) + input = C + return 1 + + return 0 //If it gets to here, it means no suitable wire to link to was found. + +/obj/machinery/logic/indicator/bulb + icon = 'icons/obj/lighting.dmi' + icon_state = "bulb0" + +/obj/machinery/logic/indicator/bulb/process() + if(!..()) //Parent proc checks if input1 exists. + return + + var/datum/powernet/pn_input = input.powernet + if(!pn_input) + return + + if(pn_input.avail >= LOGIC_HIGH) + icon_state = "bulb1" + else + icon_state = "bulb0" + + + + +//Sensors only have one output and no inputs +/obj/machinery/logic/sensor + //Output is searched from the 'dir' direction + var/obj/structure/cable/output + +/obj/machinery/logic/sensor/process() + if(output) + return 1 + + if(!output) + var/turf/T = get_step(src, dir) + if(T) + var/inv_dir = turn(dir, 180) + for(var/obj/structure/cable/C in T) + if(C.d1 == inv_dir || C.d2 == inv_dir) + output = C + return 1 + + return 0 //If it gets to here, it means no suitable wire to link to was found. + +//Constant high generator. This will continue to send a signal of LOGIC_HIGH as long as it exists. +/obj/machinery/logic/sensor/constant_high + icon = 'icons/obj/atmospherics/outlet_injector.dmi' + icon_state = "off" + +/obj/machinery/logic/sensor/constant_high/process() + if(!..()) //Parent proc checks if input1 exists. + return + + var/datum/powernet/pn_output = output.powernet + if(!pn_output) + return + + pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) + + + + +//ONE INPUT logic elements have one input and one output +/obj/machinery/logic/oneinput + var/dir_input = 2 + var/dir_output = 1 + var/obj/structure/cable/input + var/obj/structure/cable/output + icon = 'icons/atmos/heat.dmi' + icon_state = "intact" + +/obj/machinery/logic/oneinput/process() + if(input && output) + return 1 + + if(!dir_input || !dir_output) + return 0 + + if(!input) + var/turf/T = get_step(src, dir_input) + if(T) + var/inv_dir = turn(dir_input, 180) + for(var/obj/structure/cable/C in T) + if(C.d1 == inv_dir || C.d2 == inv_dir) + input = C + + if(!output) + var/turf/T = get_step(src, dir_output) + if(T) + var/inv_dir = turn(dir_output, 180) + for(var/obj/structure/cable/C in T) + if(C.d1 == inv_dir || C.d2 == inv_dir) + output = C + + return 0 //On the process() call, where everything is still being searched for, it returns 0. It will return 1 on the next process() call. + +//NOT GATE +/obj/machinery/logic/oneinput/not/process() + if(!..()) //Parent proc checks if input1, input2 and output exist. + return + + var/datum/powernet/pn_input = input.powernet + + if(!pn_input) + return + + var/datum/powernet/pn_output = output.powernet + if(!pn_output) + return + + if( !(pn_input.avail >= LOGIC_HIGH)) + pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. + else + pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 + + + + + + + + + +//TWO INPUT logic elements have two inputs and one output +/obj/machinery/logic/twoinput + var/dir_input1 = 2 + var/dir_input2 = 8 + var/dir_output = 1 + var/obj/structure/cable/input1 + var/obj/structure/cable/input2 + var/obj/structure/cable/output + icon = 'icons/obj/atmospherics/mixer.dmi' + icon_state = "intact_off" + +/obj/machinery/logic/twoinput/process() + if(input1 && input2 && output) + return 1 + + if(!dir_input1 || !dir_input2 || !dir_output) + return 0 + + if(!input1) + var/turf/T = get_step(src, dir_input1) + if(T) + var/inv_dir = turn(dir_input1, 180) + for(var/obj/structure/cable/C in T) + if(C.d1 == inv_dir || C.d2 == inv_dir) + input1 = C + + if(!input2) + var/turf/T = get_step(src, dir_input2) + if(T) + var/inv_dir = turn(dir_input2, 180) + for(var/obj/structure/cable/C in T) + if(C.d1 == inv_dir || C.d2 == inv_dir) + input2 = C + + if(!output) + var/turf/T = get_step(src, dir_output) + if(T) + var/inv_dir = turn(dir_output, 180) + for(var/obj/structure/cable/C in T) + if(C.d1 == inv_dir || C.d2 == inv_dir) + output = C + + return 0 //On the process() call, where everything is still being searched for, it returns 0. It will return 1 on the next process() call. + +//AND GATE +/obj/machinery/logic/twoinput/and/process() + if(!..()) //Parent proc checks if input1, input2 and output exist. + return + + var/datum/powernet/pn_input1 = input1.powernet + var/datum/powernet/pn_input2 = input2.powernet + + if(!pn_input1 || !pn_input2) + return + + var/datum/powernet/pn_output = output.powernet + if(!pn_output) + return + + if( (pn_input1.avail >= LOGIC_HIGH) && (pn_input2.avail >= LOGIC_HIGH) ) + pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. + else + pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 + +//OR GATE +/obj/machinery/logic/twoinput/or/process() + if(!..()) //Parent proc checks if input1, input2 and output exist. + return + + var/datum/powernet/pn_input1 = input1.powernet + var/datum/powernet/pn_input2 = input2.powernet + + if(!pn_input1 || !pn_input2) + return + + var/datum/powernet/pn_output = output.powernet + if(!pn_output) + return + + if( (pn_input1.avail >= LOGIC_HIGH) || (pn_input2.avail >= LOGIC_HIGH) ) + pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. + else + pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 + +//XOR GATE +/obj/machinery/logic/twoinput/xor/process() + if(!..()) //Parent proc checks if input1, input2 and output exist. + return + + var/datum/powernet/pn_input1 = input1.powernet + var/datum/powernet/pn_input2 = input2.powernet + + if(!pn_input1 || !pn_input2) + return + + var/datum/powernet/pn_output = output.powernet + if(!pn_output) + return + + if( (pn_input1.avail >= LOGIC_HIGH) != (pn_input2.avail >= LOGIC_HIGH) ) + pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. + else + pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 + +//XNOR GATE (EQUIVALENCE) +/obj/machinery/logic/twoinput/xnor/process() + if(!..()) //Parent proc checks if input1, input2 and output exist. + return + + var/datum/powernet/pn_input1 = input1.powernet + var/datum/powernet/pn_input2 = input2.powernet + + if(!pn_input1 || !pn_input2) + return + + var/datum/powernet/pn_output = output.powernet + if(!pn_output) + return + + if( (pn_input1.avail >= LOGIC_HIGH) == (pn_input2.avail >= LOGIC_HIGH) ) + pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before. + else + pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5 + +#define RELAY_POWER_TRANSFER 2000 //How much power a relay transfers through. + +//RELAY - input1 governs the flow from input2 to output +/obj/machinery/logic/twoinput/relay/process() + if(!..()) //Parent proc checks if input1, input2 and output exist. + return + + var/datum/powernet/pn_input1 = input1.powernet + + if(!pn_input1) + return + + if( pn_input1.avail >= LOGIC_HIGH ) + var/datum/powernet/pn_input2 = input2.powernet + var/datum/powernet/pn_output = output.powernet + + if(!pn_output) + return + + if(pn_input2.avail >= RELAY_POWER_TRANSFER) + pn_input2.load += RELAY_POWER_TRANSFER + pn_output.newavail += RELAY_POWER_TRANSFER + + +#undef RELAY_POWER_TRANSFER +#undef LOGIC_HIGH diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index 588ea6b231f..24f55d34454 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -1,356 +1,356 @@ -/obj/item/stock_parts/cell - name = "power cell" - desc = "A rechargeable electrochemical power cell." - icon = 'icons/obj/power.dmi' - icon_state = "cell" - item_state = "cell" - origin_tech = "powerstorage=1" - force = 5 - throwforce = 5 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - var/charge = 0 // note %age conveted to actual charge in New - var/maxcharge = 1000 - materials = list(MAT_METAL = 700, MAT_GLASS = 50) - var/rigged = FALSE // true if rigged to explode - var/chargerate = 100 //how much power is given every tick in a recharger - var/self_recharge = 0 //does it self recharge, over time, or not? - var/ratingdesc = TRUE - var/grown_battery = FALSE // If it's a grown that acts as a battery, add a wire overlay to it. - -/obj/item/stock_parts/cell/get_cell() - return src - -/obj/item/stock_parts/cell/New() - ..() - START_PROCESSING(SSobj, src) - charge = maxcharge - if(ratingdesc) - desc += " This one has a power rating of [DisplayPower(maxcharge)], and you should not swallow it." - update_icon() - -/obj/item/stock_parts/cell/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/stock_parts/cell/vv_edit_var(var_name, var_value) - switch(var_name) - if("self_recharge") - if(var_value) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/stock_parts/cell/process() - if(self_recharge) - give(chargerate * 0.25) - else - return PROCESS_KILL - -/obj/item/stock_parts/cell/update_icon() - overlays.Cut() - if(grown_battery) - overlays += image('icons/obj/power.dmi', "grown_wires") - if(charge < 0.01) - return - else if(charge/maxcharge >=0.995) - overlays += "cell-o2" - else - overlays += "cell-o1" - -/obj/item/stock_parts/cell/proc/percent() // return % charge of cell - return 100 * charge / maxcharge - -// use power from a cell -/obj/item/stock_parts/cell/use(amount) - if(rigged && amount > 0) - explode() - return 0 - if(charge < amount) - return 0 - charge = (charge - amount) - return 1 - -// recharge the cell -/obj/item/stock_parts/cell/proc/give(amount) - if(rigged && amount > 0) - explode() - return 0 - if(maxcharge < amount) - amount = maxcharge - var/power_used = min(maxcharge - charge, amount) - charge += power_used - return power_used - -/obj/item/stock_parts/cell/examine(mob/user) - . = ..() - if(rigged) - . += "This power cell seems to be faulty!" - else - . += "The charge meter reads [round(percent() )]%." - -/obj/item/stock_parts/cell/suicide_act(mob/user) - to_chat(viewers(user), "[user] is licking the electrodes of the [src]! It looks like [user.p_theyre()] trying to commit suicide.") - return FIRELOSS - -/obj/item/stock_parts/cell/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/reagent_containers/syringe)) - var/obj/item/reagent_containers/syringe/S = W - - to_chat(user, "You inject the solution into the power cell.") - - if(S.reagents.has_reagent("plasma", 5) || S.reagents.has_reagent("plasma_dust", 5)) - - rigged = TRUE - - log_admin("LOG: [key_name(user)] injected a power cell with plasma, rigging it to explode.") - message_admins("LOG: [key_name_admin(user)] injected a power cell with plasma, rigging it to explode.") - - S.reagents.clear_reagents() - else - return ..() - - -/obj/item/stock_parts/cell/proc/explode() - var/turf/T = get_turf(loc) - if(charge == 0) - return - var/devastation_range = -1 //round(charge/11000) - var/heavy_impact_range = round(sqrt(charge) / 60) - var/light_impact_range = round(sqrt(charge) / 30) - var/flash_range = light_impact_range - if(light_impact_range == 0) - rigged = FALSE - corrupt() - return - //explosion(T, 0, 1, 2, 2) - log_admin("LOG: Rigged power cell explosion, last touched by [fingerprintslast]") - message_admins("LOG: Rigged power cell explosion, last touched by [fingerprintslast]") - - explosion(T, devastation_range, heavy_impact_range, light_impact_range, flash_range) - qdel(src) - -/obj/item/stock_parts/cell/proc/corrupt() - charge /= 2 - maxcharge = max(maxcharge / 2, chargerate) - if(prob(10)) - rigged = TRUE //broken batterys are dangerous - -/obj/item/stock_parts/cell/emp_act(severity) - charge -= 1000 / severity - if(charge < 0) - charge = 0 - ..() - -/obj/item/stock_parts/cell/ex_act(severity) - ..() - if(!QDELETED(src)) - switch(severity) - if(2) - if(prob(50)) - corrupt() - if(3) - if(prob(25)) - corrupt() - -/obj/item/stock_parts/cell/blob_act(obj/structure/blob/B) - ex_act(EXPLODE_DEVASTATE) - -/obj/item/stock_parts/cell/proc/get_electrocute_damage() - if(charge >= 1000) - return Clamp(20 + round(charge / 25000), 20, 195) + rand(-5, 5) - else - return 0 - -// Cell variants -/obj/item/stock_parts/cell/empty/New() - ..() - charge = 0 - -/obj/item/stock_parts/cell/crap - name = "\improper Nanotrasen brand rechargeable AA battery" - desc = "You can't top the plasma top." //TOTALLY TRADEMARK INFRINGEMENT - maxcharge = 500 - materials = list(MAT_GLASS = 40) - rating = 2 - -/obj/item/stock_parts/cell/crap/empty/New() - ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/upgraded - name = "upgraded power cell" - desc = "A power cell with a slightly higher capacity than normal!" - maxcharge = 2500 - materials = list(MAT_GLASS = 50) - rating = 2 - chargerate = 1000 - -/obj/item/stock_parts/cell/upgraded/plus - name = "upgraded power cell+" - desc = "A power cell with an even higher capacity than the base model!" - maxcharge = 5000 - -/obj/item/stock_parts/cell/secborg - name = "security borg rechargeable D battery" - origin_tech = null - maxcharge = 600 //600 max charge / 100 charge per shot = six shots - materials = list(MAT_GLASS = 40) - rating = 2.5 - -/obj/item/stock_parts/cell/secborg/empty/New() - ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/pulse //200 pulse shots - name = "pulse rifle power cell" - maxcharge = 40000 - rating = 3 - chargerate = 1500 - -/obj/item/stock_parts/cell/pulse/carbine //25 pulse shots - name = "pulse carbine power cell" - maxcharge = 5000 - -/obj/item/stock_parts/cell/pulse/pistol //10 pulse shots - name = "pulse pistol power cell" - maxcharge = 2000 - -/obj/item/stock_parts/cell/high - name = "high-capacity power cell" - origin_tech = "powerstorage=2" - icon_state = "hcell" - maxcharge = 10000 - materials = list(MAT_GLASS = 60) - rating = 3 - chargerate = 1500 - -/obj/item/stock_parts/cell/high/plus - name = "high-capacity power cell+" - desc = "Where did these come from?" - icon_state = "h+cell" - maxcharge = 15000 - chargerate = 2250 - -/obj/item/stock_parts/cell/high/empty/New() - ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/super - name = "super-capacity power cell" - origin_tech = "powerstorage=3;materials=3" - icon_state = "scell" - maxcharge = 20000 - materials = list(MAT_GLASS = 300) - rating = 4 - chargerate = 2000 - -/obj/item/stock_parts/cell/super/empty/New() - ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/hyper - name = "hyper-capacity power cell" - origin_tech = "powerstorage=4;engineering=4;materials=4" - icon_state = "hpcell" - maxcharge = 30000 - materials = list(MAT_GLASS = 400) - rating = 5 - chargerate = 3000 - -/obj/item/stock_parts/cell/hyper/empty/New() - ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/bluespace - name = "bluespace power cell" - desc = "A rechargeable transdimensional power cell." - origin_tech = "powerstorage=5;bluespace=4;materials=4;engineering=4" - icon_state = "bscell" - maxcharge = 40000 - materials = list(MAT_GLASS = 600) - rating = 6 - chargerate = 4000 - -/obj/item/stock_parts/cell/bluespace/empty/New() - ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/infinite - name = "infinite-capacity power cell!" - icon_state = "icell" - origin_tech = "powerstorage=7" - maxcharge = 30000 - materials = list(MAT_GLASS=1000) - rating = 6 - chargerate = 30000 - -/obj/item/stock_parts/cell/infinite/use() - return 1 - -/obj/item/stock_parts/cell/infinite/abductor - name = "void core" - desc = "An alien power cell that produces energy seemingly out of nowhere." - icon = 'icons/obj/abductor.dmi' - icon_state = "cell" - maxcharge = 50000 - rating = 12 - ratingdesc = FALSE - -/obj/item/stock_parts/cell/infinite/abductor/update_icon() - return - - -/obj/item/stock_parts/cell/potato - name = "potato battery" - desc = "A rechargeable starch based power cell." - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "potato" - origin_tech = "powerstorage=1;biotech=1" - charge = 100 - maxcharge = 300 - materials = list() - rating = 1 - grown_battery = TRUE //it has the overlays for wires - -/obj/item/stock_parts/cell/high/slime - name = "charged slime core" - desc = "A yellow slime core infused with plasma, it crackles with power." - origin_tech = "powerstorage=5;biotech=4" - icon = 'icons/mob/slimes.dmi' - icon_state = "yellow slime extract" - materials = list() - rating = 5 //self-recharge makes these desirable - self_recharge = 1 // Infused slime cores self-recharge, over time - chargerate = 500 - -/obj/item/stock_parts/cell/emproof - name = "\improper EMP-proof cell" - desc = "An EMP-proof cell." - maxcharge = 500 - rating = 3 - -/obj/item/stock_parts/cell/emproof/empty/New() - ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/emproof/emp_act(severity) - return - -/obj/item/stock_parts/cell/emproof/corrupt() - return - -/obj/item/stock_parts/cell/ninja - name = "spider-clan power cell" - desc = "A standard ninja-suit power cell." - maxcharge = 10000 - materials = list(MAT_GLASS = 60) \ No newline at end of file +/obj/item/stock_parts/cell + name = "power cell" + desc = "A rechargeable electrochemical power cell." + icon = 'icons/obj/power.dmi' + icon_state = "cell" + item_state = "cell" + origin_tech = "powerstorage=1" + force = 5 + throwforce = 5 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + var/charge = 0 // note %age conveted to actual charge in New + var/maxcharge = 1000 + materials = list(MAT_METAL = 700, MAT_GLASS = 50) + var/rigged = FALSE // true if rigged to explode + var/chargerate = 100 //how much power is given every tick in a recharger + var/self_recharge = 0 //does it self recharge, over time, or not? + var/ratingdesc = TRUE + var/grown_battery = FALSE // If it's a grown that acts as a battery, add a wire overlay to it. + +/obj/item/stock_parts/cell/get_cell() + return src + +/obj/item/stock_parts/cell/New() + ..() + START_PROCESSING(SSobj, src) + charge = maxcharge + if(ratingdesc) + desc += " This one has a power rating of [DisplayPower(maxcharge)], and you should not swallow it." + update_icon() + +/obj/item/stock_parts/cell/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/stock_parts/cell/vv_edit_var(var_name, var_value) + switch(var_name) + if("self_recharge") + if(var_value) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/stock_parts/cell/process() + if(self_recharge) + give(chargerate * 0.25) + else + return PROCESS_KILL + +/obj/item/stock_parts/cell/update_icon() + overlays.Cut() + if(grown_battery) + overlays += image('icons/obj/power.dmi', "grown_wires") + if(charge < 0.01) + return + else if(charge/maxcharge >=0.995) + overlays += "cell-o2" + else + overlays += "cell-o1" + +/obj/item/stock_parts/cell/proc/percent() // return % charge of cell + return 100 * charge / maxcharge + +// use power from a cell +/obj/item/stock_parts/cell/use(amount) + if(rigged && amount > 0) + explode() + return 0 + if(charge < amount) + return 0 + charge = (charge - amount) + return 1 + +// recharge the cell +/obj/item/stock_parts/cell/proc/give(amount) + if(rigged && amount > 0) + explode() + return 0 + if(maxcharge < amount) + amount = maxcharge + var/power_used = min(maxcharge - charge, amount) + charge += power_used + return power_used + +/obj/item/stock_parts/cell/examine(mob/user) + . = ..() + if(rigged) + . += "This power cell seems to be faulty!" + else + . += "The charge meter reads [round(percent() )]%." + +/obj/item/stock_parts/cell/suicide_act(mob/user) + to_chat(viewers(user), "[user] is licking the electrodes of the [src]! It looks like [user.p_theyre()] trying to commit suicide.") + return FIRELOSS + +/obj/item/stock_parts/cell/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/reagent_containers/syringe)) + var/obj/item/reagent_containers/syringe/S = W + + to_chat(user, "You inject the solution into the power cell.") + + if(S.reagents.has_reagent("plasma", 5) || S.reagents.has_reagent("plasma_dust", 5)) + + rigged = TRUE + + log_admin("LOG: [key_name(user)] injected a power cell with plasma, rigging it to explode.") + message_admins("LOG: [key_name_admin(user)] injected a power cell with plasma, rigging it to explode.") + + S.reagents.clear_reagents() + else + return ..() + + +/obj/item/stock_parts/cell/proc/explode() + var/turf/T = get_turf(loc) + if(charge == 0) + return + var/devastation_range = -1 //round(charge/11000) + var/heavy_impact_range = round(sqrt(charge) / 60) + var/light_impact_range = round(sqrt(charge) / 30) + var/flash_range = light_impact_range + if(light_impact_range == 0) + rigged = FALSE + corrupt() + return + //explosion(T, 0, 1, 2, 2) + log_admin("LOG: Rigged power cell explosion, last touched by [fingerprintslast]") + message_admins("LOG: Rigged power cell explosion, last touched by [fingerprintslast]") + + explosion(T, devastation_range, heavy_impact_range, light_impact_range, flash_range) + qdel(src) + +/obj/item/stock_parts/cell/proc/corrupt() + charge /= 2 + maxcharge = max(maxcharge / 2, chargerate) + if(prob(10)) + rigged = TRUE //broken batterys are dangerous + +/obj/item/stock_parts/cell/emp_act(severity) + charge -= 1000 / severity + if(charge < 0) + charge = 0 + ..() + +/obj/item/stock_parts/cell/ex_act(severity) + ..() + if(!QDELETED(src)) + switch(severity) + if(2) + if(prob(50)) + corrupt() + if(3) + if(prob(25)) + corrupt() + +/obj/item/stock_parts/cell/blob_act(obj/structure/blob/B) + ex_act(EXPLODE_DEVASTATE) + +/obj/item/stock_parts/cell/proc/get_electrocute_damage() + if(charge >= 1000) + return Clamp(20 + round(charge / 25000), 20, 195) + rand(-5, 5) + else + return 0 + +// Cell variants +/obj/item/stock_parts/cell/empty/New() + ..() + charge = 0 + +/obj/item/stock_parts/cell/crap + name = "\improper Nanotrasen brand rechargeable AA battery" + desc = "You can't top the plasma top." //TOTALLY TRADEMARK INFRINGEMENT + maxcharge = 500 + materials = list(MAT_GLASS = 40) + rating = 2 + +/obj/item/stock_parts/cell/crap/empty/New() + ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/upgraded + name = "upgraded power cell" + desc = "A power cell with a slightly higher capacity than normal!" + maxcharge = 2500 + materials = list(MAT_GLASS = 50) + rating = 2 + chargerate = 1000 + +/obj/item/stock_parts/cell/upgraded/plus + name = "upgraded power cell+" + desc = "A power cell with an even higher capacity than the base model!" + maxcharge = 5000 + +/obj/item/stock_parts/cell/secborg + name = "security borg rechargeable D battery" + origin_tech = null + maxcharge = 600 //600 max charge / 100 charge per shot = six shots + materials = list(MAT_GLASS = 40) + rating = 2.5 + +/obj/item/stock_parts/cell/secborg/empty/New() + ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/pulse //200 pulse shots + name = "pulse rifle power cell" + maxcharge = 40000 + rating = 3 + chargerate = 1500 + +/obj/item/stock_parts/cell/pulse/carbine //25 pulse shots + name = "pulse carbine power cell" + maxcharge = 5000 + +/obj/item/stock_parts/cell/pulse/pistol //10 pulse shots + name = "pulse pistol power cell" + maxcharge = 2000 + +/obj/item/stock_parts/cell/high + name = "high-capacity power cell" + origin_tech = "powerstorage=2" + icon_state = "hcell" + maxcharge = 10000 + materials = list(MAT_GLASS = 60) + rating = 3 + chargerate = 1500 + +/obj/item/stock_parts/cell/high/plus + name = "high-capacity power cell+" + desc = "Where did these come from?" + icon_state = "h+cell" + maxcharge = 15000 + chargerate = 2250 + +/obj/item/stock_parts/cell/high/empty/New() + ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/super + name = "super-capacity power cell" + origin_tech = "powerstorage=3;materials=3" + icon_state = "scell" + maxcharge = 20000 + materials = list(MAT_GLASS = 300) + rating = 4 + chargerate = 2000 + +/obj/item/stock_parts/cell/super/empty/New() + ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/hyper + name = "hyper-capacity power cell" + origin_tech = "powerstorage=4;engineering=4;materials=4" + icon_state = "hpcell" + maxcharge = 30000 + materials = list(MAT_GLASS = 400) + rating = 5 + chargerate = 3000 + +/obj/item/stock_parts/cell/hyper/empty/New() + ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/bluespace + name = "bluespace power cell" + desc = "A rechargeable transdimensional power cell." + origin_tech = "powerstorage=5;bluespace=4;materials=4;engineering=4" + icon_state = "bscell" + maxcharge = 40000 + materials = list(MAT_GLASS = 600) + rating = 6 + chargerate = 4000 + +/obj/item/stock_parts/cell/bluespace/empty/New() + ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/infinite + name = "infinite-capacity power cell!" + icon_state = "icell" + origin_tech = "powerstorage=7" + maxcharge = 30000 + materials = list(MAT_GLASS=1000) + rating = 6 + chargerate = 30000 + +/obj/item/stock_parts/cell/infinite/use() + return 1 + +/obj/item/stock_parts/cell/infinite/abductor + name = "void core" + desc = "An alien power cell that produces energy seemingly out of nowhere." + icon = 'icons/obj/abductor.dmi' + icon_state = "cell" + maxcharge = 50000 + rating = 12 + ratingdesc = FALSE + +/obj/item/stock_parts/cell/infinite/abductor/update_icon() + return + + +/obj/item/stock_parts/cell/potato + name = "potato battery" + desc = "A rechargeable starch based power cell." + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "potato" + origin_tech = "powerstorage=1;biotech=1" + charge = 100 + maxcharge = 300 + materials = list() + rating = 1 + grown_battery = TRUE //it has the overlays for wires + +/obj/item/stock_parts/cell/high/slime + name = "charged slime core" + desc = "A yellow slime core infused with plasma, it crackles with power." + origin_tech = "powerstorage=5;biotech=4" + icon = 'icons/mob/slimes.dmi' + icon_state = "yellow slime extract" + materials = list() + rating = 5 //self-recharge makes these desirable + self_recharge = 1 // Infused slime cores self-recharge, over time + chargerate = 500 + +/obj/item/stock_parts/cell/emproof + name = "\improper EMP-proof cell" + desc = "An EMP-proof cell." + maxcharge = 500 + rating = 3 + +/obj/item/stock_parts/cell/emproof/empty/New() + ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/emproof/emp_act(severity) + return + +/obj/item/stock_parts/cell/emproof/corrupt() + return + +/obj/item/stock_parts/cell/ninja + name = "spider-clan power cell" + desc = "A standard ninja-suit power cell." + maxcharge = 10000 + materials = list(MAT_GLASS = 60) diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm index 4a88e3363fc..4a2d8b66518 100644 --- a/code/modules/power/generator.dm +++ b/code/modules/power/generator.dm @@ -1,250 +1,250 @@ -/obj/machinery/power/generator - name = "thermoelectric generator" - desc = "It's a high efficiency thermoelectric generator." - icon_state = "teg" - anchored = 0 - density = 1 - use_power = NO_POWER_USE - - var/obj/machinery/atmospherics/binary/circulator/cold_circ - var/obj/machinery/atmospherics/binary/circulator/hot_circ - - var/cold_dir = WEST - var/hot_dir = EAST - - var/lastgen = 0 - var/lastgenlev = -1 - var/lastcirc = "00" - -/obj/machinery/power/generator/New() - ..() - update_desc() - -/obj/machinery/power/generator/proc/update_desc() - desc = initial(desc) + " Its cold circulator is located on the [dir2text(cold_dir)] side, and its heat circulator is located on the [dir2text(hot_dir)] side." - -/obj/machinery/power/generator/Destroy() - disconnect() - return ..() - -/obj/machinery/power/generator/proc/disconnect() - if(cold_circ) - cold_circ.generator = null - if(hot_circ) - hot_circ.generator = null - if(powernet) - disconnect_from_network() - -/obj/machinery/power/generator/Initialize() - ..() - connect() - -/obj/machinery/power/generator/proc/connect() - connect_to_network() - - var/obj/machinery/atmospherics/binary/circulator/circpath = /obj/machinery/atmospherics/binary/circulator - cold_circ = locate(circpath) in get_step(src, cold_dir) - hot_circ = locate(circpath) in get_step(src, hot_dir) - - if(cold_circ && cold_circ.side == cold_dir) - cold_circ.generator = src - cold_circ.update_icon() - else - cold_circ = null - - if(hot_circ && hot_circ.side == hot_dir) - hot_circ.generator = src - hot_circ.update_icon() - else - hot_circ = null - - power_change() - update_icon() - updateDialog() - -/obj/machinery/power/generator/power_change() - if(!anchored) - stat |= NOPOWER - else - ..() - -/obj/machinery/power/generator/update_icon() - if(stat & (NOPOWER|BROKEN)) - overlays.Cut() - else - overlays.Cut() - - if(lastgenlev != 0) - overlays += image('icons/obj/power.dmi', "teg-op[lastgenlev]") - - overlays += image('icons/obj/power.dmi', "teg-oc[lastcirc]") - -/obj/machinery/power/generator/process() - if(stat & (NOPOWER|BROKEN)) - return - - if(!cold_circ || !hot_circ) - return - - lastgen = 0 - - if(powernet) - - //log_debug("cold_circ and hot_circ pass") - - var/datum/gas_mixture/cold_air = cold_circ.return_transfer_air() - var/datum/gas_mixture/hot_air = hot_circ.return_transfer_air() - - //log_debug("hot_air = [hot_air]; cold_air = [cold_air];") - - if(cold_air && hot_air) - - //log_debug("hot_air = [hot_air] temperature = [hot_air.temperature]; cold_air = [cold_air] temperature = [hot_air.temperature];") - - //log_debug("coldair and hotair pass") - var/cold_air_heat_capacity = cold_air.heat_capacity() - var/hot_air_heat_capacity = hot_air.heat_capacity() - - var/delta_temperature = hot_air.temperature - cold_air.temperature - - //log_debug("delta_temperature = [delta_temperature]; cold_air_heat_capacity = [cold_air_heat_capacity]; hot_air_heat_capacity = [hot_air_heat_capacity]") - - if(delta_temperature > 0 && cold_air_heat_capacity > 0 && hot_air_heat_capacity > 0) - var/efficiency = 0.65 - - var/energy_transfer = delta_temperature * hot_air_heat_capacity * cold_air_heat_capacity / (hot_air_heat_capacity + cold_air_heat_capacity) - - var/heat = energy_transfer * (1 - efficiency) - lastgen = energy_transfer * efficiency - - //log_debug("lastgen = [lastgen]; heat = [heat]; delta_temperature = [delta_temperature]; hot_air_heat_capacity = [hot_air_heat_capacity]; cold_air_heat_capacity = [cold_air_heat_capacity];") - - hot_air.temperature = hot_air.temperature - energy_transfer / hot_air_heat_capacity - cold_air.temperature = cold_air.temperature + heat / cold_air_heat_capacity - - //log_debug("POWER: [lastgen] W generated at [efficiency * 100]% efficiency and sinks sizes [cold_air_heat_capacity], [hot_air_heat_capacity]") - - add_avail(lastgen) - // update icon overlays only if displayed level has changed - - if(hot_air) - var/datum/gas_mixture/hot_circ_air1 = hot_circ.get_outlet_air() - hot_circ_air1.merge(hot_air) - - if(cold_air) - var/datum/gas_mixture/cold_circ_air1 = cold_circ.get_outlet_air() - cold_circ_air1.merge(cold_air) - - var/genlev = max(0, min( round(11 * lastgen / 100000), 11)) - var/circ = "[cold_circ && cold_circ.last_pressure_delta > 0 ? "1" : "0"][hot_circ && hot_circ.last_pressure_delta > 0 ? "1" : "0"]" - if((genlev != lastgenlev) || (circ != lastcirc)) - lastgenlev = genlev - lastcirc = circ - update_icon() - - updateDialog() - -/obj/machinery/power/generator/attack_ai(mob/user) - return attack_hand(user) - -/obj/machinery/power/generator/attack_ghost(mob/user) - if(stat & (NOPOWER|BROKEN)) - return - interact(user) - -/obj/machinery/power/generator/attack_hand(mob/user) - if(..()) - user << browse(null, "window=teg") - return - interact(user) - -/obj/machinery/power/generator/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(cold_dir == WEST) - cold_dir = EAST - hot_dir = WEST - else if(cold_dir == NORTH) - cold_dir = SOUTH - hot_dir = NORTH - else if(cold_dir == EAST) - cold_dir = WEST - hot_dir = EAST - else - cold_dir = NORTH - hot_dir = SOUTH - connect() - to_chat(user, "You reverse the generator's circulator settings. The cold circulator is now on the [dir2text(cold_dir)] side, and the heat circulator is now on the [dir2text(hot_dir)] side.") - update_desc() - -/obj/machinery/power/generator/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - anchored = !anchored - if(!anchored) - disconnect() - power_change() - else - connect() - to_chat(user, "You [anchored ? "secure" : "unsecure"] the bolts holding [src] to the floor.") - -/obj/machinery/power/generator/proc/get_menu(include_link = 1) - var/t = "" - if(!powernet) - t += "Unable to connect to the power network!" - t += "
        Retry" - else if(cold_circ && hot_circ) - var/datum/gas_mixture/cold_circ_air1 = cold_circ.get_outlet_air() - var/datum/gas_mixture/cold_circ_air2 = cold_circ.get_inlet_air() - var/datum/gas_mixture/hot_circ_air1 = hot_circ.get_outlet_air() - var/datum/gas_mixture/hot_circ_air2 = hot_circ.get_inlet_air() - - t += "
        " - - t += "Output: [round(lastgen)] W" - - t += "
        " - - t += "Cold loop
        " - t += "Temperature Inlet: [round(cold_circ_air2.temperature, 0.1)] K / Outlet: [round(cold_circ_air1.temperature, 0.1)] K
        " - t += "Pressure Inlet: [round(cold_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(cold_circ_air1.return_pressure(), 0.1)] kPa
        " - - t += "Hot loop
        " - t += "Temperature Inlet: [round(hot_circ_air2.temperature, 0.1)] K / Outlet: [round(hot_circ_air1.temperature, 0.1)] K
        " - t += "Pressure Inlet: [round(hot_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(hot_circ_air1.return_pressure(), 0.1)] kPa
        " - - t += "
        " - else - t += "Unable to locate all parts!" - t += "
        Retry" - if(include_link) - t += "
        Close" - - return t - -/obj/machinery/power/generator/interact(mob/user) - user.set_machine(src) - - var/datum/browser/popup = new(user, "teg", "Thermo-Electric Generator", 460, 300) - popup.set_content(get_menu()) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - return 1 - -/obj/machinery/power/generator/Topic(href, href_list) - if(..()) - return 0 - if( href_list["close"] ) - usr << browse(null, "window=teg") - usr.unset_machine() - return 0 - if( href_list["check"] ) - if(!powernet || !cold_circ || !hot_circ) - connect() - return 1 - -/obj/machinery/power/generator/power_change() - ..() - update_icon() +/obj/machinery/power/generator + name = "thermoelectric generator" + desc = "It's a high efficiency thermoelectric generator." + icon_state = "teg" + anchored = 0 + density = 1 + use_power = NO_POWER_USE + + var/obj/machinery/atmospherics/binary/circulator/cold_circ + var/obj/machinery/atmospherics/binary/circulator/hot_circ + + var/cold_dir = WEST + var/hot_dir = EAST + + var/lastgen = 0 + var/lastgenlev = -1 + var/lastcirc = "00" + +/obj/machinery/power/generator/New() + ..() + update_desc() + +/obj/machinery/power/generator/proc/update_desc() + desc = initial(desc) + " Its cold circulator is located on the [dir2text(cold_dir)] side, and its heat circulator is located on the [dir2text(hot_dir)] side." + +/obj/machinery/power/generator/Destroy() + disconnect() + return ..() + +/obj/machinery/power/generator/proc/disconnect() + if(cold_circ) + cold_circ.generator = null + if(hot_circ) + hot_circ.generator = null + if(powernet) + disconnect_from_network() + +/obj/machinery/power/generator/Initialize() + ..() + connect() + +/obj/machinery/power/generator/proc/connect() + connect_to_network() + + var/obj/machinery/atmospherics/binary/circulator/circpath = /obj/machinery/atmospherics/binary/circulator + cold_circ = locate(circpath) in get_step(src, cold_dir) + hot_circ = locate(circpath) in get_step(src, hot_dir) + + if(cold_circ && cold_circ.side == cold_dir) + cold_circ.generator = src + cold_circ.update_icon() + else + cold_circ = null + + if(hot_circ && hot_circ.side == hot_dir) + hot_circ.generator = src + hot_circ.update_icon() + else + hot_circ = null + + power_change() + update_icon() + updateDialog() + +/obj/machinery/power/generator/power_change() + if(!anchored) + stat |= NOPOWER + else + ..() + +/obj/machinery/power/generator/update_icon() + if(stat & (NOPOWER|BROKEN)) + overlays.Cut() + else + overlays.Cut() + + if(lastgenlev != 0) + overlays += image('icons/obj/power.dmi', "teg-op[lastgenlev]") + + overlays += image('icons/obj/power.dmi', "teg-oc[lastcirc]") + +/obj/machinery/power/generator/process() + if(stat & (NOPOWER|BROKEN)) + return + + if(!cold_circ || !hot_circ) + return + + lastgen = 0 + + if(powernet) + + //log_debug("cold_circ and hot_circ pass") + + var/datum/gas_mixture/cold_air = cold_circ.return_transfer_air() + var/datum/gas_mixture/hot_air = hot_circ.return_transfer_air() + + //log_debug("hot_air = [hot_air]; cold_air = [cold_air];") + + if(cold_air && hot_air) + + //log_debug("hot_air = [hot_air] temperature = [hot_air.temperature]; cold_air = [cold_air] temperature = [hot_air.temperature];") + + //log_debug("coldair and hotair pass") + var/cold_air_heat_capacity = cold_air.heat_capacity() + var/hot_air_heat_capacity = hot_air.heat_capacity() + + var/delta_temperature = hot_air.temperature - cold_air.temperature + + //log_debug("delta_temperature = [delta_temperature]; cold_air_heat_capacity = [cold_air_heat_capacity]; hot_air_heat_capacity = [hot_air_heat_capacity]") + + if(delta_temperature > 0 && cold_air_heat_capacity > 0 && hot_air_heat_capacity > 0) + var/efficiency = 0.65 + + var/energy_transfer = delta_temperature * hot_air_heat_capacity * cold_air_heat_capacity / (hot_air_heat_capacity + cold_air_heat_capacity) + + var/heat = energy_transfer * (1 - efficiency) + lastgen = energy_transfer * efficiency + + //log_debug("lastgen = [lastgen]; heat = [heat]; delta_temperature = [delta_temperature]; hot_air_heat_capacity = [hot_air_heat_capacity]; cold_air_heat_capacity = [cold_air_heat_capacity];") + + hot_air.temperature = hot_air.temperature - energy_transfer / hot_air_heat_capacity + cold_air.temperature = cold_air.temperature + heat / cold_air_heat_capacity + + //log_debug("POWER: [lastgen] W generated at [efficiency * 100]% efficiency and sinks sizes [cold_air_heat_capacity], [hot_air_heat_capacity]") + + add_avail(lastgen) + // update icon overlays only if displayed level has changed + + if(hot_air) + var/datum/gas_mixture/hot_circ_air1 = hot_circ.get_outlet_air() + hot_circ_air1.merge(hot_air) + + if(cold_air) + var/datum/gas_mixture/cold_circ_air1 = cold_circ.get_outlet_air() + cold_circ_air1.merge(cold_air) + + var/genlev = max(0, min( round(11 * lastgen / 100000), 11)) + var/circ = "[cold_circ && cold_circ.last_pressure_delta > 0 ? "1" : "0"][hot_circ && hot_circ.last_pressure_delta > 0 ? "1" : "0"]" + if((genlev != lastgenlev) || (circ != lastcirc)) + lastgenlev = genlev + lastcirc = circ + update_icon() + + updateDialog() + +/obj/machinery/power/generator/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/power/generator/attack_ghost(mob/user) + if(stat & (NOPOWER|BROKEN)) + return + interact(user) + +/obj/machinery/power/generator/attack_hand(mob/user) + if(..()) + user << browse(null, "window=teg") + return + interact(user) + +/obj/machinery/power/generator/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(cold_dir == WEST) + cold_dir = EAST + hot_dir = WEST + else if(cold_dir == NORTH) + cold_dir = SOUTH + hot_dir = NORTH + else if(cold_dir == EAST) + cold_dir = WEST + hot_dir = EAST + else + cold_dir = NORTH + hot_dir = SOUTH + connect() + to_chat(user, "You reverse the generator's circulator settings. The cold circulator is now on the [dir2text(cold_dir)] side, and the heat circulator is now on the [dir2text(hot_dir)] side.") + update_desc() + +/obj/machinery/power/generator/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + anchored = !anchored + if(!anchored) + disconnect() + power_change() + else + connect() + to_chat(user, "You [anchored ? "secure" : "unsecure"] the bolts holding [src] to the floor.") + +/obj/machinery/power/generator/proc/get_menu(include_link = 1) + var/t = "" + if(!powernet) + t += "Unable to connect to the power network!" + t += "
        Retry" + else if(cold_circ && hot_circ) + var/datum/gas_mixture/cold_circ_air1 = cold_circ.get_outlet_air() + var/datum/gas_mixture/cold_circ_air2 = cold_circ.get_inlet_air() + var/datum/gas_mixture/hot_circ_air1 = hot_circ.get_outlet_air() + var/datum/gas_mixture/hot_circ_air2 = hot_circ.get_inlet_air() + + t += "
        " + + t += "Output: [round(lastgen)] W" + + t += "
        " + + t += "Cold loop
        " + t += "Temperature Inlet: [round(cold_circ_air2.temperature, 0.1)] K / Outlet: [round(cold_circ_air1.temperature, 0.1)] K
        " + t += "Pressure Inlet: [round(cold_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(cold_circ_air1.return_pressure(), 0.1)] kPa
        " + + t += "Hot loop
        " + t += "Temperature Inlet: [round(hot_circ_air2.temperature, 0.1)] K / Outlet: [round(hot_circ_air1.temperature, 0.1)] K
        " + t += "Pressure Inlet: [round(hot_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(hot_circ_air1.return_pressure(), 0.1)] kPa
        " + + t += "
        " + else + t += "Unable to locate all parts!" + t += "
        Retry" + if(include_link) + t += "
        Close" + + return t + +/obj/machinery/power/generator/interact(mob/user) + user.set_machine(src) + + var/datum/browser/popup = new(user, "teg", "Thermo-Electric Generator", 460, 300) + popup.set_content(get_menu()) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + return 1 + +/obj/machinery/power/generator/Topic(href, href_list) + if(..()) + return 0 + if( href_list["close"] ) + usr << browse(null, "window=teg") + usr.unset_machine() + return 0 + if( href_list["check"] ) + if(!powernet || !cold_circ || !hot_circ) + connect() + return 1 + +/obj/machinery/power/generator/power_change() + ..() + update_icon() diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index d02263d8ed8..78c2561ae7d 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -1,740 +1,740 @@ -// The lighting system -// -// consists of light fixtures (/obj/machinery/light) and light tube/bulb items (/obj/item/light) - - -// status values shared between lighting fixtures and items -#define LIGHT_OK 0 -#define LIGHT_EMPTY 1 -#define LIGHT_BROKEN 2 -#define LIGHT_BURNED 3 - -/obj/machinery/light_construct - name = "light fixture frame" - desc = "A light fixture under construction." - icon = 'icons/obj/lighting.dmi' - icon_state = "tube-construct-stage1" - anchored = 1 - layer = 5 - max_integrity = 200 - armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - var/stage = 1 - var/fixture_type = "tube" - var/sheets_refunded = 2 - var/obj/machinery/light/newlight = null - -/obj/machinery/light_construct/New() - ..() - if(fixture_type == "bulb") - icon_state = "bulb-construct-stage1" - -/obj/machinery/light_construct/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 2) - switch(stage) - if(1) - . += "It's an empty frame." - if(2) - . += "It's wired." - if(3) - . += "The casing is closed." - -/obj/machinery/light_construct/attackby(obj/item/W as obj, mob/living/user as mob, params) - src.add_fingerprint(user) - if(istype(W, /obj/item/wrench)) - if(src.stage == 1) - playsound(src.loc, W.usesound, 75, 1) - to_chat(usr, "You begin deconstructing [src].") - if(!do_after(usr, 30 * W.toolspeed, target = src)) - return - new /obj/item/stack/sheet/metal( get_turf(src.loc), sheets_refunded ) - user.visible_message("[user.name] deconstructs [src].", \ - "You deconstruct [src].", "You hear a noise.") - playsound(src.loc, W.usesound, 75, 1) - qdel(src) - if(src.stage == 2) - to_chat(usr, "You have to remove the wires first.") - return - - if(src.stage == 3) - to_chat(usr, "You have to unscrew the case first.") - return - - if(istype(W, /obj/item/wirecutters)) - if(src.stage != 2) return - src.stage = 1 - switch(fixture_type) - if("tube") - src.icon_state = "tube-construct-stage1" - if("bulb") - src.icon_state = "bulb-construct-stage1" - new /obj/item/stack/cable_coil(get_turf(src.loc), 1, paramcolor = COLOR_RED) - user.visible_message("[user.name] removes the wiring from [src].", \ - "You remove the wiring from [src].", "You hear a noise.") - playsound(loc, W.usesound, 100, 1) - return - - if(istype(W, /obj/item/stack/cable_coil)) - if(src.stage != 1) return - var/obj/item/stack/cable_coil/coil = W - coil.use(1) - switch(fixture_type) - if("tube") - src.icon_state = "tube-construct-stage2" - if("bulb") - src.icon_state = "bulb-construct-stage2" - src.stage = 2 - playsound(loc, coil.usesound, 50, 1) - user.visible_message("[user.name] adds wires to [src].", \ - "You add wires to [src].") - return - - if(istype(W, /obj/item/screwdriver)) - if(src.stage == 2) - switch(fixture_type) - if("tube") - src.icon_state = "tube-empty" - if("bulb") - src.icon_state = "bulb-empty" - src.stage = 3 - user.visible_message("[user.name] closes [src]'s casing.", \ - "You close [src]'s casing.", "You hear a noise.") - playsound(src.loc, W.usesound, 75, 1) - - switch(fixture_type) - - if("tube") - newlight = new /obj/machinery/light/built(src.loc) - if("bulb") - newlight = new /obj/machinery/light/small/built(src.loc) - - newlight.dir = src.dir - src.transfer_fingerprints_to(newlight) - qdel(src) - return - else - return ..() - -/obj/machinery/light_construct/blob_act(obj/structure/blob/B) - if(B && B.loc == loc) - qdel(src) - -/obj/machinery/light_construct/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/sheet/metal(loc, sheets_refunded) - qdel(src) - -/obj/machinery/light_construct/small - name = "small light fixture frame" - desc = "A small light fixture under construction." - icon = 'icons/obj/lighting.dmi' - icon_state = "bulb-construct-stage1" - anchored = 1 - layer = 5 - stage = 1 - fixture_type = "bulb" - sheets_refunded = 1 - - -// the standard tube light fixture -/obj/machinery/light - name = "light fixture" - icon = 'icons/obj/lighting.dmi' - var/base_state = "tube" // base description and icon_state - icon_state = "tube1" - desc = "A lighting fixture." - anchored = 1 - layer = 5 // They were appearing under mobs which is a little weird - Ostaf - max_integrity = 100 - use_power = ACTIVE_POWER_USE - idle_power_usage = 2 - active_power_usage = 20 - power_channel = LIGHT //Lights are calc'd via area so they dont need to be in the machine list - var/on = FALSE // 1 if on, 0 if off - var/on_gs = 0 - var/static_power_used = 0 - var/brightness_range = 8 // luminosity when on, also used in power calculation - var/brightness_power = 1 - var/brightness_color = "#FFFFFF" - var/status = LIGHT_OK // LIGHT_OK, _EMPTY, _BURNED or _BROKEN - var/flickering = 0 - var/light_type = /obj/item/light/tube // the type of light item - var/fitting = "tube" - var/switchcount = 0 // count of number of times switched on/off - // this is used to calc the probability the light burns out - - var/rigged = 0 // true if rigged to explode - var/lightmaterials = list(MAT_GLASS=100) //stores the materials the light is made of to stop infinite glass exploit - - var/nightshift_enabled = FALSE //Currently in night shift mode? - var/nightshift_allowed = TRUE //Set to FALSE to never let this light get switched to night mode. - var/nightshift_light_range = 8 - var/nightshift_light_power = 0.45 - var/nightshift_light_color = "#FFDDCC" - -// the smaller bulb light fixture - -/obj/machinery/light/small - icon_state = "bulb1" - base_state = "bulb" - fitting = "bulb" - brightness_range = 4 - brightness_color = "#a0a080" - nightshift_light_range = 4 - desc = "A small lighting fixture." - light_type = /obj/item/light/bulb - -/obj/machinery/light/spot - name = "spotlight" - fitting = "large tube" - light_type = /obj/item/light/tube/large - brightness_range = 12 - brightness_power = 4 - -/obj/machinery/light/built/New() - status = LIGHT_EMPTY - update(0) - ..() - -/obj/machinery/light/small/built/New() - status = LIGHT_EMPTY - update(0) - ..() - -// create a new lighting fixture -/obj/machinery/light/New() - ..() - spawn(2) - var/area/A = get_area(src) - if(A && !A.requires_power) - on = 1 - - switch(fitting) - if("tube") - brightness_range = 8 - if(prob(2)) - break_light_tube(1) - if("bulb") - brightness_range = 4 - brightness_color = "#a0a080" - if(prob(5)) - break_light_tube() - spawn(1) - update(0) - -/obj/machinery/light/Destroy() - var/area/A = get_area(src) - if(A) - on = FALSE -// A.update_lights() - return ..() - -/obj/machinery/light/update_icon() - - switch(status) // set icon_states - if(LIGHT_OK) - icon_state = "[base_state][on]" - if(LIGHT_EMPTY) - icon_state = "[base_state]-empty" - on = FALSE - if(LIGHT_BURNED) - icon_state = "[base_state]-burned" - on = FALSE - if(LIGHT_BROKEN) - icon_state = "[base_state]-broken" - on = FALSE - return - -/obj/machinery/light/get_spooked() - flicker() - -// update the icon_state and luminosity of the light depending on its state -/obj/machinery/light/proc/update(var/trigger = TRUE) - switch(status) - if(LIGHT_BROKEN, LIGHT_BURNED, LIGHT_EMPTY) - on = FALSE - update_icon() - if(on) - var/BR = nightshift_enabled ? nightshift_light_range : brightness_range - var/PO = nightshift_enabled ? nightshift_light_power : brightness_power - var/CO = nightshift_enabled ? nightshift_light_color : brightness_color - var/matching = light_range == BR && light_power == PO && light_color == CO - if(!matching) - switchcount++ - if(rigged) - if(status == LIGHT_OK && trigger) - log_admin("LOG: Rigged light explosion, last touched by [fingerprintslast]") - message_admins("LOG: Rigged light explosion, last touched by [fingerprintslast]") - explode() - else if(prob(min(60, switchcount * switchcount * 0.01))) - if(status == LIGHT_OK && trigger) - status = LIGHT_BURNED - icon_state = "[base_state]-burned" - on = FALSE - set_light(0) - else - use_power = ACTIVE_POWER_USE - set_light(BR, PO, CO) - else - use_power = IDLE_POWER_USE - set_light(0) - - active_power_usage = (brightness_range * 10) - if(on != on_gs) - on_gs = on - if(on) - static_power_used = brightness_range * 20 //20W per unit luminosity - addStaticPower(static_power_used, STATIC_LIGHT) - else - removeStaticPower(static_power_used, STATIC_LIGHT) - -// attempt to set the light's on/off status -// will not switch on if broken/burned/empty -/obj/machinery/light/proc/seton(var/s) - on = (s && status == LIGHT_OK) - update() - -// examine verb -/obj/machinery/light/examine(mob/user) - . = ..() - if(in_range(user, src)) - switch(status) - if(LIGHT_OK) - . += "[desc] It is turned [on? "on" : "off"]." - if(LIGHT_EMPTY) - . += "[desc] The [fitting] has been removed." - if(LIGHT_BURNED) - . += "[desc] The [fitting] is burnt out." - if(LIGHT_BROKEN) - . += "[desc] The [fitting] has been smashed." - - - -// attack with item - insert light (if right type), otherwise try to break the light - -/obj/machinery/light/attackby(obj/item/W, mob/living/user, params) - user.changeNext_move(CLICK_CD_MELEE) // This is an ugly hack and I hate it forever - //Light replacer code - if(istype(W, /obj/item/lightreplacer)) - var/obj/item/lightreplacer/LR = W - LR.ReplaceLight(src, user) - - // attempt to insert light - else if(istype(W, /obj/item/light)) - if(status != LIGHT_EMPTY) - to_chat(user, "There is a [fitting] already inserted.") - else - src.add_fingerprint(user) - var/obj/item/light/L = W - if(istype(L, light_type)) - status = L.status - to_chat(user, "You insert the [L.name].") - switchcount = L.switchcount - rigged = L.rigged - brightness_range = L.brightness_range - brightness_power = L.brightness_power - brightness_color = L.brightness_color - lightmaterials = L.materials - on = has_power() - update() - - user.drop_item() //drop the item to update overlays and such - qdel(L) - - if(on && rigged) - - log_admin("LOG: Rigged light explosion, last touched by [fingerprintslast]") - message_admins("LOG: Rigged light explosion, last touched by [fingerprintslast]") - - explode() - else - to_chat(user, "This type of light requires a [fitting].") - return - - // attempt to break the light - //If xenos decide they want to smash a light bulb with a toolbox, who am I to stop them? /N - - else if(status != LIGHT_BROKEN && status != LIGHT_EMPTY) - - user.do_attack_animation(src) - if(prob(1+W.force * 5)) - - to_chat(user, "You hit the light, and it smashes!") - for(var/mob/M in viewers(src)) - if(M == user) - continue - M.show_message("[user.name] smashed the light!", 3, "You hear a tinkle of breaking glass", 2) - if(on && (W.flags & CONDUCT)) - if(prob(12)) - electrocute_mob(user, get_area(src), src, 0.3, TRUE) - break_light_tube() - - else - user.visible_message("[user.name] hits the light.") - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1) - - // attempt to stick weapon into light socket - else if(status == LIGHT_EMPTY) - if(istype(W, /obj/item/screwdriver)) //If it's a screwdriver open it. - playsound(src.loc, W.usesound, 75, 1) - user.visible_message("[user.name] opens [src]'s casing.", \ - "You open [src]'s casing.", "You hear a noise.") - deconstruct() - return - - to_chat(user, "You stick \the [W] into the light socket!") - if(has_power() && (W.flags & CONDUCT)) - do_sparks(3, 1, src) - if(prob(75)) - electrocute_mob(user, get_area(src), src, rand(0.7, 1), TRUE) - else - return ..() - -/obj/machinery/light/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - var/obj/machinery/light_construct/newlight = null - var/cur_stage = 2 - if(!disassembled) - cur_stage = 1 - switch(fitting) - if("tube") - newlight = new /obj/machinery/light_construct(src.loc) - newlight.icon_state = "tube-construct-stage2" - - if("bulb") - newlight = new /obj/machinery/light_construct/small(src.loc) - newlight.icon_state = "bulb-construct-stage2" - newlight.setDir(src.dir) - newlight.stage = cur_stage - if(!disassembled) - newlight.obj_integrity = newlight.max_integrity * 0.5 - if(status != LIGHT_BROKEN) - break_light_tube() - if(status != LIGHT_EMPTY) - drop_light_tube() - new /obj/item/stack/cable_coil(loc, 1, "red") - transfer_fingerprints_to(newlight) - qdel(src) - -/obj/machinery/light/attacked_by(obj/item/I, mob/living/user) - ..() - if(status == LIGHT_BROKEN || status == LIGHT_EMPTY) - if(on && (I.flags & CONDUCT)) - if(prob(12)) - electrocute_mob(user, get_area(src), src, 0.3, TRUE) - -/obj/machinery/light/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - . = ..() - if(. && !QDELETED(src)) - if(prob(damage_amount * 5)) - break_light_tube() - -/obj/machinery/light/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - switch(status) - if(LIGHT_EMPTY) - playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) - if(LIGHT_BROKEN) - playsound(loc, 'sound/effects/hit_on_shattered_glass.ogg', 90, TRUE) - else - playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -// returns whether this light has power -// true if area has power and lightswitch is on -/obj/machinery/light/proc/has_power() - var/area/A = get_area(src) - return A.lightswitch && A.power_light - -/obj/machinery/light/proc/flicker(var/amount = rand(10, 20)) - if(flickering) return - flickering = 1 - spawn(0) - if(on && status == LIGHT_OK) - for(var/i = 0; i < amount; i++) - if(status != LIGHT_OK) break - on = !on - update(0) - sleep(rand(5, 15)) - on = (status == LIGHT_OK) - update(0) - flickering = 0 - -// ai attack - make lights flicker, because why not -/obj/machinery/light/attack_ai(mob/user) - src.flicker(1) - -// attack with hand - remove tube/bulb -// if hands aren't protected and the light is on, burn the player - -/obj/machinery/light/attack_hand(mob/user) - user.changeNext_move(CLICK_CD_MELEE) - add_fingerprint(user) - - if(status == LIGHT_EMPTY) - to_chat(user, "There is no [fitting] in this light.") - return - - // make it burn hands if not wearing fire-insulated gloves - if(on) - var/prot = 0 - var/mob/living/carbon/human/H = user - - if(istype(H)) - if(H.gloves) - var/obj/item/clothing/gloves/G = H.gloves - if(G.max_heat_protection_temperature) - prot = (G.max_heat_protection_temperature > 360) - else - prot = 1 - - if(prot > 0 || (HEATRES in user.mutations)) - to_chat(user, "You remove the light [fitting]") - else if(TK in user.mutations) - to_chat(user, "You telekinetically remove the light [fitting].") - else - if(user.a_intent == INTENT_DISARM || user.a_intent == INTENT_GRAB) - to_chat(user, "You try to remove the light [fitting], but you burn your hand on it!") - - var/obj/item/organ/external/affecting = H.get_organ("[user.hand ? "l" : "r" ]_hand") - if(affecting.receive_damage( 0, 5 )) // 5 burn damage - H.UpdateDamageIcon() - H.updatehealth() - return - else - to_chat(user, "You try to remove the light [fitting], but it's too hot to touch!") - return - else - to_chat(user, "You remove the light [fitting].") - // create a light tube/bulb item and put it in the user's hand - drop_light_tube(user) - -// break the light and make sparks if was on - -/obj/machinery/light/proc/drop_light_tube(mob/user) - var/obj/item/light/L = new light_type() - L.status = status - L.rigged = rigged - L.brightness_range = brightness_range - L.brightness_power = brightness_power - L.brightness_color = brightness_color - L.materials = lightmaterials - - // light item inherits the switchcount, then zero it - L.switchcount = switchcount - switchcount = 0 - - L.update() - L.forceMove(loc) - - if(user) //puts it in our active hand - L.add_fingerprint(user) - user.put_in_active_hand(L) - - status = LIGHT_EMPTY - update() - return L - -/obj/machinery/light/attack_tk(mob/user) - if(status == LIGHT_EMPTY) - to_chat(user, "There is no [fitting] in this light.") - return - - to_chat(user, "You telekinetically remove the light [fitting].") - // create a light tube/bulb item and put it in the user's hand - var/obj/item/light/L = drop_light_tube() - L.attack_tk(user) - -/obj/machinery/light/proc/break_light_tube(skip_sound_and_sparks = 0, overloaded = 0) - if(status == LIGHT_EMPTY || status == LIGHT_BROKEN) - return - - if(!skip_sound_and_sparks) - if(status == LIGHT_OK || status == LIGHT_BURNED) - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1) - if(on || overloaded) - do_sparks(3, 1, src) - status = LIGHT_BROKEN - update() - -/obj/machinery/light/proc/fix() - if(status == LIGHT_OK) - return - status = LIGHT_OK - on = 1 - update() - -/obj/machinery/light/tesla_act(power, explosive = FALSE) - if(explosive) - explosion(loc,0,0,0,flame_range = 5, adminlog = 0) - qdel(src) - -// timed process -// use power - -// called when area power state changes -/obj/machinery/light/power_change() - var/area/A = get_area(src) - if(A) - seton(A.lightswitch && A.power_light) - -// called when on fire - -/obj/machinery/light/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(prob(max(0, exposed_temperature - 673))) //0% at <400C, 100% at >500C - break_light_tube() - -// explode the light - -/obj/machinery/light/proc/explode() - var/turf/T = get_turf(src.loc) - spawn(0) - break_light_tube() // break it first to give a warning - sleep(2) - explosion(T, 0, 0, 2, 2) - sleep(1) - qdel(src) - -// the light item -// can be tube or bulb subtypes -// will fit into empty /obj/machinery/light of the corresponding type - -/obj/item/light - icon = 'icons/obj/lighting.dmi' - force = 2 - throwforce = 5 - w_class = WEIGHT_CLASS_TINY - var/status = 0 // LIGHT_OK, LIGHT_BURNED or LIGHT_BROKEN - var/base_state - var/switchcount = 0 // number of times switched - materials = list(MAT_GLASS=100) - var/rigged = 0 // true if rigged to explode - var/brightness_range = 2 //how much light it gives off - var/brightness_power = 1 - var/brightness_color = null - -/obj/item/light/ComponentInitialize() - . = ..() - AddComponent(/datum/component/caltrop, force) - -/obj/item/light/Crossed(mob/living/L) - if(istype(L) && has_gravity(loc)) - if(L.incorporeal_move || L.flying) - return - playsound(loc, 'sound/effects/glass_step.ogg', 50, TRUE) - if(status == LIGHT_BURNED || status == LIGHT_OK) - shatter() - return ..() - -/obj/item/light/tube - name = "light tube" - desc = "A replacement light tube." - icon_state = "ltube" - base_state = "ltube" - item_state = "c_tube" - brightness_range = 8 - -/obj/item/light/tube/large - w_class = WEIGHT_CLASS_SMALL - name = "large light tube" - brightness_range = 15 - brightness_power = 2 - -/obj/item/light/bulb - name = "light bulb" - desc = "A replacement light bulb." - icon_state = "lbulb" - base_state = "lbulb" - item_state = "contvapour" - brightness_range = 5 - brightness_color = "#a0a080" - -/obj/item/light/throw_impact(atom/hit_atom) - ..() - shatter() - -/obj/item/light/bulb/fire - name = "fire bulb" - desc = "A replacement fire bulb." - icon_state = "fbulb" - base_state = "fbulb" - item_state = "egg4" - brightness_range = 5 - -// update the icon state and description of the light - -/obj/item/light/proc/update() - switch(status) - if(LIGHT_OK) - icon_state = base_state - desc = "A replacement [name]." - if(LIGHT_BURNED) - icon_state = "[base_state]-burned" - desc = "A burnt-out [name]." - if(LIGHT_BROKEN) - icon_state = "[base_state]-broken" - desc = "A broken [name]." - - -/obj/item/light/New() - ..() - switch(name) - if("light tube") - brightness_range = rand(6,9) - if("light bulb") - brightness_range = rand(4,6) - update() - - -// attack bulb/tube with object -// if a syringe, can inject plasma to make it explode -/obj/item/light/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/syringe)) - var/obj/item/reagent_containers/syringe/S = I - - to_chat(user, "You inject the solution into the [src].") - - if(S.reagents.has_reagent("plasma", 5) || S.reagents.has_reagent("plasma_dust", 5)) - - log_admin("LOG: [key_name(user)] injected a light with plasma, rigging it to explode.") - message_admins("LOG: [key_name_admin(user)] injected a light with plasma, rigging it to explode.") - - rigged = 1 - - S.reagents.clear_reagents() - else - return ..() - -/obj/item/light/attack(mob/living/M, mob/living/user, def_zone) - ..() - shatter() - -/obj/item/light/attack_obj(obj/O, mob/living/user) - ..() - shatter() - -/obj/item/light/proc/shatter() - if(status == LIGHT_OK || status == LIGHT_BURNED) - src.visible_message("[name] shatters.","You hear a small glass object shatter.") - status = LIGHT_BROKEN - force = 5 - sharp = 1 - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1) - update() - -/obj/item/light/suicide_act(mob/living/carbon/human/user) - user.visible_message("[user] touches [src], burning [user.p_their()] hands off!", "You touch [src], burning your hands off!") - - for(var/oname in list("l_hand", "r_hand")) - var/obj/item/organ/external/limb = user.get_organ(oname) - if(limb) - limb.droplimb(0, DROPLIMB_BURN) - return FIRELOSS - -/obj/machinery/light/extinguish_light() - on = FALSE - visible_message("[src] flickers and falls dark.") - update(0) +// The lighting system +// +// consists of light fixtures (/obj/machinery/light) and light tube/bulb items (/obj/item/light) + + +// status values shared between lighting fixtures and items +#define LIGHT_OK 0 +#define LIGHT_EMPTY 1 +#define LIGHT_BROKEN 2 +#define LIGHT_BURNED 3 + +/obj/machinery/light_construct + name = "light fixture frame" + desc = "A light fixture under construction." + icon = 'icons/obj/lighting.dmi' + icon_state = "tube-construct-stage1" + anchored = 1 + layer = 5 + max_integrity = 200 + armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + var/stage = 1 + var/fixture_type = "tube" + var/sheets_refunded = 2 + var/obj/machinery/light/newlight = null + +/obj/machinery/light_construct/New() + ..() + if(fixture_type == "bulb") + icon_state = "bulb-construct-stage1" + +/obj/machinery/light_construct/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 2) + switch(stage) + if(1) + . += "It's an empty frame." + if(2) + . += "It's wired." + if(3) + . += "The casing is closed." + +/obj/machinery/light_construct/attackby(obj/item/W as obj, mob/living/user as mob, params) + src.add_fingerprint(user) + if(istype(W, /obj/item/wrench)) + if(src.stage == 1) + playsound(src.loc, W.usesound, 75, 1) + to_chat(usr, "You begin deconstructing [src].") + if(!do_after(usr, 30 * W.toolspeed, target = src)) + return + new /obj/item/stack/sheet/metal( get_turf(src.loc), sheets_refunded ) + user.visible_message("[user.name] deconstructs [src].", \ + "You deconstruct [src].", "You hear a noise.") + playsound(src.loc, W.usesound, 75, 1) + qdel(src) + if(src.stage == 2) + to_chat(usr, "You have to remove the wires first.") + return + + if(src.stage == 3) + to_chat(usr, "You have to unscrew the case first.") + return + + if(istype(W, /obj/item/wirecutters)) + if(src.stage != 2) return + src.stage = 1 + switch(fixture_type) + if("tube") + src.icon_state = "tube-construct-stage1" + if("bulb") + src.icon_state = "bulb-construct-stage1" + new /obj/item/stack/cable_coil(get_turf(src.loc), 1, paramcolor = COLOR_RED) + user.visible_message("[user.name] removes the wiring from [src].", \ + "You remove the wiring from [src].", "You hear a noise.") + playsound(loc, W.usesound, 100, 1) + return + + if(istype(W, /obj/item/stack/cable_coil)) + if(src.stage != 1) return + var/obj/item/stack/cable_coil/coil = W + coil.use(1) + switch(fixture_type) + if("tube") + src.icon_state = "tube-construct-stage2" + if("bulb") + src.icon_state = "bulb-construct-stage2" + src.stage = 2 + playsound(loc, coil.usesound, 50, 1) + user.visible_message("[user.name] adds wires to [src].", \ + "You add wires to [src].") + return + + if(istype(W, /obj/item/screwdriver)) + if(src.stage == 2) + switch(fixture_type) + if("tube") + src.icon_state = "tube-empty" + if("bulb") + src.icon_state = "bulb-empty" + src.stage = 3 + user.visible_message("[user.name] closes [src]'s casing.", \ + "You close [src]'s casing.", "You hear a noise.") + playsound(src.loc, W.usesound, 75, 1) + + switch(fixture_type) + + if("tube") + newlight = new /obj/machinery/light/built(src.loc) + if("bulb") + newlight = new /obj/machinery/light/small/built(src.loc) + + newlight.dir = src.dir + src.transfer_fingerprints_to(newlight) + qdel(src) + return + else + return ..() + +/obj/machinery/light_construct/blob_act(obj/structure/blob/B) + if(B && B.loc == loc) + qdel(src) + +/obj/machinery/light_construct/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/sheet/metal(loc, sheets_refunded) + qdel(src) + +/obj/machinery/light_construct/small + name = "small light fixture frame" + desc = "A small light fixture under construction." + icon = 'icons/obj/lighting.dmi' + icon_state = "bulb-construct-stage1" + anchored = 1 + layer = 5 + stage = 1 + fixture_type = "bulb" + sheets_refunded = 1 + + +// the standard tube light fixture +/obj/machinery/light + name = "light fixture" + icon = 'icons/obj/lighting.dmi' + var/base_state = "tube" // base description and icon_state + icon_state = "tube1" + desc = "A lighting fixture." + anchored = 1 + layer = 5 // They were appearing under mobs which is a little weird - Ostaf + max_integrity = 100 + use_power = ACTIVE_POWER_USE + idle_power_usage = 2 + active_power_usage = 20 + power_channel = LIGHT //Lights are calc'd via area so they dont need to be in the machine list + var/on = FALSE // 1 if on, 0 if off + var/on_gs = 0 + var/static_power_used = 0 + var/brightness_range = 8 // luminosity when on, also used in power calculation + var/brightness_power = 1 + var/brightness_color = "#FFFFFF" + var/status = LIGHT_OK // LIGHT_OK, _EMPTY, _BURNED or _BROKEN + var/flickering = 0 + var/light_type = /obj/item/light/tube // the type of light item + var/fitting = "tube" + var/switchcount = 0 // count of number of times switched on/off + // this is used to calc the probability the light burns out + + var/rigged = 0 // true if rigged to explode + var/lightmaterials = list(MAT_GLASS=100) //stores the materials the light is made of to stop infinite glass exploit + + var/nightshift_enabled = FALSE //Currently in night shift mode? + var/nightshift_allowed = TRUE //Set to FALSE to never let this light get switched to night mode. + var/nightshift_light_range = 8 + var/nightshift_light_power = 0.45 + var/nightshift_light_color = "#FFDDCC" + +// the smaller bulb light fixture + +/obj/machinery/light/small + icon_state = "bulb1" + base_state = "bulb" + fitting = "bulb" + brightness_range = 4 + brightness_color = "#a0a080" + nightshift_light_range = 4 + desc = "A small lighting fixture." + light_type = /obj/item/light/bulb + +/obj/machinery/light/spot + name = "spotlight" + fitting = "large tube" + light_type = /obj/item/light/tube/large + brightness_range = 12 + brightness_power = 4 + +/obj/machinery/light/built/New() + status = LIGHT_EMPTY + update(0) + ..() + +/obj/machinery/light/small/built/New() + status = LIGHT_EMPTY + update(0) + ..() + +// create a new lighting fixture +/obj/machinery/light/New() + ..() + spawn(2) + var/area/A = get_area(src) + if(A && !A.requires_power) + on = 1 + + switch(fitting) + if("tube") + brightness_range = 8 + if(prob(2)) + break_light_tube(1) + if("bulb") + brightness_range = 4 + brightness_color = "#a0a080" + if(prob(5)) + break_light_tube() + spawn(1) + update(0) + +/obj/machinery/light/Destroy() + var/area/A = get_area(src) + if(A) + on = FALSE +// A.update_lights() + return ..() + +/obj/machinery/light/update_icon() + + switch(status) // set icon_states + if(LIGHT_OK) + icon_state = "[base_state][on]" + if(LIGHT_EMPTY) + icon_state = "[base_state]-empty" + on = FALSE + if(LIGHT_BURNED) + icon_state = "[base_state]-burned" + on = FALSE + if(LIGHT_BROKEN) + icon_state = "[base_state]-broken" + on = FALSE + return + +/obj/machinery/light/get_spooked() + flicker() + +// update the icon_state and luminosity of the light depending on its state +/obj/machinery/light/proc/update(var/trigger = TRUE) + switch(status) + if(LIGHT_BROKEN, LIGHT_BURNED, LIGHT_EMPTY) + on = FALSE + update_icon() + if(on) + var/BR = nightshift_enabled ? nightshift_light_range : brightness_range + var/PO = nightshift_enabled ? nightshift_light_power : brightness_power + var/CO = nightshift_enabled ? nightshift_light_color : brightness_color + var/matching = light_range == BR && light_power == PO && light_color == CO + if(!matching) + switchcount++ + if(rigged) + if(status == LIGHT_OK && trigger) + log_admin("LOG: Rigged light explosion, last touched by [fingerprintslast]") + message_admins("LOG: Rigged light explosion, last touched by [fingerprintslast]") + explode() + else if(prob(min(60, switchcount * switchcount * 0.01))) + if(status == LIGHT_OK && trigger) + status = LIGHT_BURNED + icon_state = "[base_state]-burned" + on = FALSE + set_light(0) + else + use_power = ACTIVE_POWER_USE + set_light(BR, PO, CO) + else + use_power = IDLE_POWER_USE + set_light(0) + + active_power_usage = (brightness_range * 10) + if(on != on_gs) + on_gs = on + if(on) + static_power_used = brightness_range * 20 //20W per unit luminosity + addStaticPower(static_power_used, STATIC_LIGHT) + else + removeStaticPower(static_power_used, STATIC_LIGHT) + +// attempt to set the light's on/off status +// will not switch on if broken/burned/empty +/obj/machinery/light/proc/seton(var/s) + on = (s && status == LIGHT_OK) + update() + +// examine verb +/obj/machinery/light/examine(mob/user) + . = ..() + if(in_range(user, src)) + switch(status) + if(LIGHT_OK) + . += "[desc] It is turned [on? "on" : "off"]." + if(LIGHT_EMPTY) + . += "[desc] The [fitting] has been removed." + if(LIGHT_BURNED) + . += "[desc] The [fitting] is burnt out." + if(LIGHT_BROKEN) + . += "[desc] The [fitting] has been smashed." + + + +// attack with item - insert light (if right type), otherwise try to break the light + +/obj/machinery/light/attackby(obj/item/W, mob/living/user, params) + user.changeNext_move(CLICK_CD_MELEE) // This is an ugly hack and I hate it forever + //Light replacer code + if(istype(W, /obj/item/lightreplacer)) + var/obj/item/lightreplacer/LR = W + LR.ReplaceLight(src, user) + + // attempt to insert light + else if(istype(W, /obj/item/light)) + if(status != LIGHT_EMPTY) + to_chat(user, "There is a [fitting] already inserted.") + else + src.add_fingerprint(user) + var/obj/item/light/L = W + if(istype(L, light_type)) + status = L.status + to_chat(user, "You insert the [L.name].") + switchcount = L.switchcount + rigged = L.rigged + brightness_range = L.brightness_range + brightness_power = L.brightness_power + brightness_color = L.brightness_color + lightmaterials = L.materials + on = has_power() + update() + + user.drop_item() //drop the item to update overlays and such + qdel(L) + + if(on && rigged) + + log_admin("LOG: Rigged light explosion, last touched by [fingerprintslast]") + message_admins("LOG: Rigged light explosion, last touched by [fingerprintslast]") + + explode() + else + to_chat(user, "This type of light requires a [fitting].") + return + + // attempt to break the light + //If xenos decide they want to smash a light bulb with a toolbox, who am I to stop them? /N + + else if(status != LIGHT_BROKEN && status != LIGHT_EMPTY) + + user.do_attack_animation(src) + if(prob(1+W.force * 5)) + + to_chat(user, "You hit the light, and it smashes!") + for(var/mob/M in viewers(src)) + if(M == user) + continue + M.show_message("[user.name] smashed the light!", 3, "You hear a tinkle of breaking glass", 2) + if(on && (W.flags & CONDUCT)) + if(prob(12)) + electrocute_mob(user, get_area(src), src, 0.3, TRUE) + break_light_tube() + + else + user.visible_message("[user.name] hits the light.") + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1) + + // attempt to stick weapon into light socket + else if(status == LIGHT_EMPTY) + if(istype(W, /obj/item/screwdriver)) //If it's a screwdriver open it. + playsound(src.loc, W.usesound, 75, 1) + user.visible_message("[user.name] opens [src]'s casing.", \ + "You open [src]'s casing.", "You hear a noise.") + deconstruct() + return + + to_chat(user, "You stick \the [W] into the light socket!") + if(has_power() && (W.flags & CONDUCT)) + do_sparks(3, 1, src) + if(prob(75)) + electrocute_mob(user, get_area(src), src, rand(0.7, 1), TRUE) + else + return ..() + +/obj/machinery/light/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + var/obj/machinery/light_construct/newlight = null + var/cur_stage = 2 + if(!disassembled) + cur_stage = 1 + switch(fitting) + if("tube") + newlight = new /obj/machinery/light_construct(src.loc) + newlight.icon_state = "tube-construct-stage2" + + if("bulb") + newlight = new /obj/machinery/light_construct/small(src.loc) + newlight.icon_state = "bulb-construct-stage2" + newlight.setDir(src.dir) + newlight.stage = cur_stage + if(!disassembled) + newlight.obj_integrity = newlight.max_integrity * 0.5 + if(status != LIGHT_BROKEN) + break_light_tube() + if(status != LIGHT_EMPTY) + drop_light_tube() + new /obj/item/stack/cable_coil(loc, 1, "red") + transfer_fingerprints_to(newlight) + qdel(src) + +/obj/machinery/light/attacked_by(obj/item/I, mob/living/user) + ..() + if(status == LIGHT_BROKEN || status == LIGHT_EMPTY) + if(on && (I.flags & CONDUCT)) + if(prob(12)) + electrocute_mob(user, get_area(src), src, 0.3, TRUE) + +/obj/machinery/light/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + . = ..() + if(. && !QDELETED(src)) + if(prob(damage_amount * 5)) + break_light_tube() + +/obj/machinery/light/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + switch(status) + if(LIGHT_EMPTY) + playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) + if(LIGHT_BROKEN) + playsound(loc, 'sound/effects/hit_on_shattered_glass.ogg', 90, TRUE) + else + playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +// returns whether this light has power +// true if area has power and lightswitch is on +/obj/machinery/light/proc/has_power() + var/area/A = get_area(src) + return A.lightswitch && A.power_light + +/obj/machinery/light/proc/flicker(var/amount = rand(10, 20)) + if(flickering) return + flickering = 1 + spawn(0) + if(on && status == LIGHT_OK) + for(var/i = 0; i < amount; i++) + if(status != LIGHT_OK) break + on = !on + update(0) + sleep(rand(5, 15)) + on = (status == LIGHT_OK) + update(0) + flickering = 0 + +// ai attack - make lights flicker, because why not +/obj/machinery/light/attack_ai(mob/user) + src.flicker(1) + +// attack with hand - remove tube/bulb +// if hands aren't protected and the light is on, burn the player + +/obj/machinery/light/attack_hand(mob/user) + user.changeNext_move(CLICK_CD_MELEE) + add_fingerprint(user) + + if(status == LIGHT_EMPTY) + to_chat(user, "There is no [fitting] in this light.") + return + + // make it burn hands if not wearing fire-insulated gloves + if(on) + var/prot = 0 + var/mob/living/carbon/human/H = user + + if(istype(H)) + if(H.gloves) + var/obj/item/clothing/gloves/G = H.gloves + if(G.max_heat_protection_temperature) + prot = (G.max_heat_protection_temperature > 360) + else + prot = 1 + + if(prot > 0 || (HEATRES in user.mutations)) + to_chat(user, "You remove the light [fitting]") + else if(TK in user.mutations) + to_chat(user, "You telekinetically remove the light [fitting].") + else + if(user.a_intent == INTENT_DISARM || user.a_intent == INTENT_GRAB) + to_chat(user, "You try to remove the light [fitting], but you burn your hand on it!") + + var/obj/item/organ/external/affecting = H.get_organ("[user.hand ? "l" : "r" ]_hand") + if(affecting.receive_damage( 0, 5 )) // 5 burn damage + H.UpdateDamageIcon() + H.updatehealth() + return + else + to_chat(user, "You try to remove the light [fitting], but it's too hot to touch!") + return + else + to_chat(user, "You remove the light [fitting].") + // create a light tube/bulb item and put it in the user's hand + drop_light_tube(user) + +// break the light and make sparks if was on + +/obj/machinery/light/proc/drop_light_tube(mob/user) + var/obj/item/light/L = new light_type() + L.status = status + L.rigged = rigged + L.brightness_range = brightness_range + L.brightness_power = brightness_power + L.brightness_color = brightness_color + L.materials = lightmaterials + + // light item inherits the switchcount, then zero it + L.switchcount = switchcount + switchcount = 0 + + L.update() + L.forceMove(loc) + + if(user) //puts it in our active hand + L.add_fingerprint(user) + user.put_in_active_hand(L) + + status = LIGHT_EMPTY + update() + return L + +/obj/machinery/light/attack_tk(mob/user) + if(status == LIGHT_EMPTY) + to_chat(user, "There is no [fitting] in this light.") + return + + to_chat(user, "You telekinetically remove the light [fitting].") + // create a light tube/bulb item and put it in the user's hand + var/obj/item/light/L = drop_light_tube() + L.attack_tk(user) + +/obj/machinery/light/proc/break_light_tube(skip_sound_and_sparks = 0, overloaded = 0) + if(status == LIGHT_EMPTY || status == LIGHT_BROKEN) + return + + if(!skip_sound_and_sparks) + if(status == LIGHT_OK || status == LIGHT_BURNED) + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1) + if(on || overloaded) + do_sparks(3, 1, src) + status = LIGHT_BROKEN + update() + +/obj/machinery/light/proc/fix() + if(status == LIGHT_OK) + return + status = LIGHT_OK + on = 1 + update() + +/obj/machinery/light/tesla_act(power, explosive = FALSE) + if(explosive) + explosion(loc,0,0,0,flame_range = 5, adminlog = 0) + qdel(src) + +// timed process +// use power + +// called when area power state changes +/obj/machinery/light/power_change() + var/area/A = get_area(src) + if(A) + seton(A.lightswitch && A.power_light) + +// called when on fire + +/obj/machinery/light/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(prob(max(0, exposed_temperature - 673))) //0% at <400C, 100% at >500C + break_light_tube() + +// explode the light + +/obj/machinery/light/proc/explode() + var/turf/T = get_turf(src.loc) + spawn(0) + break_light_tube() // break it first to give a warning + sleep(2) + explosion(T, 0, 0, 2, 2) + sleep(1) + qdel(src) + +// the light item +// can be tube or bulb subtypes +// will fit into empty /obj/machinery/light of the corresponding type + +/obj/item/light + icon = 'icons/obj/lighting.dmi' + force = 2 + throwforce = 5 + w_class = WEIGHT_CLASS_TINY + var/status = 0 // LIGHT_OK, LIGHT_BURNED or LIGHT_BROKEN + var/base_state + var/switchcount = 0 // number of times switched + materials = list(MAT_GLASS=100) + var/rigged = 0 // true if rigged to explode + var/brightness_range = 2 //how much light it gives off + var/brightness_power = 1 + var/brightness_color = null + +/obj/item/light/ComponentInitialize() + . = ..() + AddComponent(/datum/component/caltrop, force) + +/obj/item/light/Crossed(mob/living/L) + if(istype(L) && has_gravity(loc)) + if(L.incorporeal_move || L.flying) + return + playsound(loc, 'sound/effects/glass_step.ogg', 50, TRUE) + if(status == LIGHT_BURNED || status == LIGHT_OK) + shatter() + return ..() + +/obj/item/light/tube + name = "light tube" + desc = "A replacement light tube." + icon_state = "ltube" + base_state = "ltube" + item_state = "c_tube" + brightness_range = 8 + +/obj/item/light/tube/large + w_class = WEIGHT_CLASS_SMALL + name = "large light tube" + brightness_range = 15 + brightness_power = 2 + +/obj/item/light/bulb + name = "light bulb" + desc = "A replacement light bulb." + icon_state = "lbulb" + base_state = "lbulb" + item_state = "contvapour" + brightness_range = 5 + brightness_color = "#a0a080" + +/obj/item/light/throw_impact(atom/hit_atom) + ..() + shatter() + +/obj/item/light/bulb/fire + name = "fire bulb" + desc = "A replacement fire bulb." + icon_state = "fbulb" + base_state = "fbulb" + item_state = "egg4" + brightness_range = 5 + +// update the icon state and description of the light + +/obj/item/light/proc/update() + switch(status) + if(LIGHT_OK) + icon_state = base_state + desc = "A replacement [name]." + if(LIGHT_BURNED) + icon_state = "[base_state]-burned" + desc = "A burnt-out [name]." + if(LIGHT_BROKEN) + icon_state = "[base_state]-broken" + desc = "A broken [name]." + + +/obj/item/light/New() + ..() + switch(name) + if("light tube") + brightness_range = rand(6,9) + if("light bulb") + brightness_range = rand(4,6) + update() + + +// attack bulb/tube with object +// if a syringe, can inject plasma to make it explode +/obj/item/light/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/syringe)) + var/obj/item/reagent_containers/syringe/S = I + + to_chat(user, "You inject the solution into the [src].") + + if(S.reagents.has_reagent("plasma", 5) || S.reagents.has_reagent("plasma_dust", 5)) + + log_admin("LOG: [key_name(user)] injected a light with plasma, rigging it to explode.") + message_admins("LOG: [key_name_admin(user)] injected a light with plasma, rigging it to explode.") + + rigged = 1 + + S.reagents.clear_reagents() + else + return ..() + +/obj/item/light/attack(mob/living/M, mob/living/user, def_zone) + ..() + shatter() + +/obj/item/light/attack_obj(obj/O, mob/living/user) + ..() + shatter() + +/obj/item/light/proc/shatter() + if(status == LIGHT_OK || status == LIGHT_BURNED) + src.visible_message("[name] shatters.","You hear a small glass object shatter.") + status = LIGHT_BROKEN + force = 5 + sharp = 1 + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1) + update() + +/obj/item/light/suicide_act(mob/living/carbon/human/user) + user.visible_message("[user] touches [src], burning [user.p_their()] hands off!", "You touch [src], burning your hands off!") + + for(var/oname in list("l_hand", "r_hand")) + var/obj/item/organ/external/limb = user.get_organ(oname) + if(limb) + limb.droplimb(0, DROPLIMB_BURN) + return FIRELOSS + +/obj/machinery/light/extinguish_light() + on = FALSE + visible_message("[src] flickers and falls dark.") + update(0) diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm index 05150c7389d..54b81cb9272 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -1,459 +1,459 @@ -//Baseline portable generator. Has all the default handling. Not intended to be used on it's own (since it generates unlimited power). -/obj/machinery/power/port_gen - name = "Placeholder Generator" //seriously, don't use this. It can't be anchored without VV magic. - desc = "A portable generator for emergency backup power" - icon = 'icons/obj/power.dmi' - icon_state = "portgen0_0" - density = 1 - anchored = 0 - use_power = NO_POWER_USE - - var/active = 0 - var/power_gen = 5000 - var/open = 0 - var/recent_fault = 0 - var/power_output = 1 - var/base_icon = "portgen0" - -/obj/machinery/power/port_gen/proc/IsBroken() - return (stat & (BROKEN|EMPED)) - -/obj/machinery/power/port_gen/proc/HasFuel() //Placeholder for fuel check. - return 1 - -/obj/machinery/power/port_gen/proc/UseFuel() //Placeholder for fuel use. - return - -/obj/machinery/power/port_gen/proc/DropFuel() - return - -/obj/machinery/power/port_gen/proc/handleInactive() - return - -/obj/machinery/power/port_gen/update_icon() - icon_state = "[base_icon]_[active]" - -/obj/machinery/power/port_gen/process() - if(active && HasFuel() && !IsBroken() && anchored && powernet) - add_avail(power_gen * power_output) - UseFuel() - else - active = 0 - handleInactive() - update_icon() - -/obj/machinery/power/powered() - return 1 //doesn't require an external power source - -/obj/machinery/power/port_gen/attack_hand(mob/user as mob) - if(..()) - return - if(!anchored) - return - -/obj/machinery/power/port_gen/examine(mob/user) - . = ..() - if(!in_range(user, src)) - if(active) - . += "The generator is on." - else - . += "The generator is off." - -/obj/machinery/power/port_gen/emp_act(severity) - var/duration = 6000 //ten minutes - switch(severity) - if(1) - stat &= BROKEN - if(prob(75)) explode() - if(2) - if(prob(25)) stat &= BROKEN - if(prob(10)) explode() - if(3) - if(prob(10)) stat &= BROKEN - duration = 300 - - stat |= EMPED - if(duration) - spawn(duration) - stat &= ~EMPED - -/obj/machinery/power/port_gen/proc/explode() - explosion(src.loc, -1, 3, 5, -1) - qdel(src) - -#define TEMPERATURE_DIVISOR 40 -#define TEMPERATURE_CHANGE_MAX 20 - -//A power generator that runs on solid plasma sheets. -/obj/machinery/power/port_gen/pacman - name = "\improper P.A.C.M.A.N.-type Portable Generator" - desc = "A power generator that runs on solid plasma sheets. Rated for 80 kW max safe output." - - var/sheet_name = "Plasma Sheets" - var/sheet_path = /obj/item/stack/sheet/mineral/plasma - var/board_path = /obj/item/circuitboard/pacman - - /* - These values were chosen so that the generator can run safely up to 80 kW - A full 50 plasma sheet stack should last 20 minutes at power_output = 4 - temperature_gain and max_temperature are set so that the max safe power level is 4. - Setting to 5 or higher can only be done temporarily before the generator overheats. - */ - power_gen = 20000 //Watts output per power_output level - var/max_power_output = 5 //The maximum power setting without emagging. - var/max_safe_output = 4 // For UI use, maximal output that won't cause overheat. - var/time_per_sheet = 96 //fuel efficiency - how long 1 sheet lasts at power level 1 - var/max_sheets = 100 //max capacity of the hopper - var/max_temperature = 300 //max temperature before overheating increases - var/temperature_gain = 50 //how much the temperature increases per power output level, in degrees per level - - var/sheets = 0 //How many sheets of material are loaded in the generator - var/sheet_left = 0 //How much is left of the current sheet - var/temperature = 0 //The current temperature - var/overheating = 0 //if this gets high enough the generator explodes - -/obj/machinery/power/port_gen/pacman/Initialize() - ..() - if(anchored) - connect_to_network() - -/obj/machinery/power/port_gen/pacman/New() - ..() - component_parts = list() - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/micro_laser(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stock_parts/capacitor(null) - component_parts += new board_path(null) - RefreshParts() - -/obj/machinery/power/port_gen/pacman/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stock_parts/capacitor/super(null) - component_parts += new board_path(null) - RefreshParts() - -/obj/machinery/power/port_gen/pacman/Destroy() - DropFuel() - return ..() - -/obj/machinery/power/port_gen/pacman/RefreshParts() - var/temp_rating = 0 - for(var/obj/item/stock_parts/SP in component_parts) - if(istype(SP, /obj/item/stock_parts/matter_bin)) - max_sheets = SP.rating * SP.rating * 50 - else if(istype(SP, /obj/item/stock_parts/micro_laser) || istype(SP, /obj/item/stock_parts/capacitor)) - temp_rating += SP.rating - - power_gen = round(initial(power_gen) * (max(2, temp_rating) / 2)) - -/obj/machinery/power/port_gen/pacman/examine(mob/user) - . = ..() - . += "\The [src] appears to be producing [power_gen*power_output] W." - . += "There [sheets == 1 ? "is" : "are"] [sheets] sheet\s left in the hopper." - if(IsBroken()) - . += "\The [src] seems to have broken down." - if(overheating) - . += "\The [src] is overheating!" - -/obj/machinery/power/port_gen/pacman/HasFuel() - var/needed_sheets = power_output / time_per_sheet - if(sheets >= needed_sheets - sheet_left) - return 1 - return 0 - -//Removes one stack's worth of material from the generator. -/obj/machinery/power/port_gen/pacman/DropFuel() - if(sheets) - var/obj/item/stack/sheet/mineral/S = new sheet_path(loc) - var/amount = min(sheets, S.max_amount) - S.amount = amount - sheets -= amount - -/obj/machinery/power/port_gen/pacman/UseFuel() - - //how much material are we using this iteration? - var/needed_sheets = power_output / time_per_sheet - - //HasFuel() should guarantee us that there is enough fuel left, so no need to check that - //the only thing we need to worry about is if we are going to rollover to the next sheet - if(needed_sheets > sheet_left) - sheets-- - sheet_left = (1 + sheet_left) - needed_sheets - else - sheet_left -= needed_sheets - - //calculate the "target" temperature range - //This should probably depend on the external temperature somehow, but whatever. - var/lower_limit = 56 + power_output * temperature_gain - var/upper_limit = 76 + power_output * temperature_gain - - /* - Hot or cold environments can affect the equilibrium temperature - The lower the pressure the less effect it has. I guess it cools using a radiator or something when in vacuum. - Gives traitors more opportunities to sabotage the generator or allows enterprising engineers to build additional - cooling in order to get more power out. - */ - var/datum/gas_mixture/environment = loc.return_air() - if(environment) - var/ratio = min(environment.return_pressure()/ONE_ATMOSPHERE, 1) - var/ambient = environment.temperature - T20C - lower_limit += ambient*ratio - upper_limit += ambient*ratio - - var/average = (upper_limit + lower_limit)/2 - - //calculate the temperature increase - var/bias = 0 - if(temperature < lower_limit) - bias = min(round((average - temperature)/TEMPERATURE_DIVISOR, 1), TEMPERATURE_CHANGE_MAX) - else if(temperature > upper_limit) - bias = max(round((temperature - average)/TEMPERATURE_DIVISOR, 1), -TEMPERATURE_CHANGE_MAX) - - //limit temperature increase so that it cannot raise temperature above upper_limit, - //or if it is already above upper_limit, limit the increase to 0. - var/inc_limit = max(upper_limit - temperature, 0) - var/dec_limit = min(temperature - lower_limit, 0) - temperature += between(dec_limit, rand(-7 + bias, 7 + bias), inc_limit) - - if(temperature > max_temperature) - overheat() - else if(overheating > 0) - overheating-- - -/obj/machinery/power/port_gen/pacman/handleInactive() - var/cooling_temperature = 20 - var/datum/gas_mixture/environment = loc.return_air() - if(environment) - var/ratio = min(environment.return_pressure()/ONE_ATMOSPHERE, 1) - var/ambient = environment.temperature - T20C - cooling_temperature += ambient*ratio - - if(temperature > cooling_temperature) - var/temp_loss = (temperature - cooling_temperature)/TEMPERATURE_DIVISOR - temp_loss = between(2, round(temp_loss, 1), TEMPERATURE_CHANGE_MAX) - temperature = max(temperature - temp_loss, cooling_temperature) - SSnanoui.update_uis(src) - - if(overheating) - overheating-- - -/obj/machinery/power/port_gen/pacman/proc/overheat() - overheating++ - if(overheating > 60) - explode() - -/obj/machinery/power/port_gen/pacman/explode() - //Vapourize all the plasma - //When ground up in a grinder, 1 sheet produces 20 u of plasma -- Chemistry-Machinery.dm - //1 mol = 10 u? I dunno. 1 mol of carbon is definitely bigger than a pill - /*var/plasma = (sheets+sheet_left)*20 - var/datum/gas_mixture/environment = loc.return_air() - if(environment) - environment.adjust_gas("plasma", plasma/10, temperature + T0C)*/ - - sheets = 0 - sheet_left = 0 - ..() - -/obj/machinery/power/port_gen/pacman/emag_act(var/remaining_charges, var/mob/user) - if(active && prob(25)) - explode() //if they're foolish enough to emag while it's running - - if(!emagged) - emagged = 1 - return 1 - -/obj/machinery/power/port_gen/pacman/attackby(var/obj/item/O as obj, var/mob/user as mob) - if(istype(O, sheet_path)) - var/obj/item/stack/addstack = O - var/amount = min((max_sheets - sheets), addstack.amount) - if(amount < 1) - to_chat(user, "The [src.name] is full!") - return - to_chat(user, "You add [amount] sheet\s to the [src.name].") - sheets += amount - addstack.use(amount) - SSnanoui.update_uis(src) - return - else if(!active) - if(istype(O, /obj/item/wrench)) - - if(!anchored) - connect_to_network() - to_chat(user, "You secure the generator to the floor.") - else - disconnect_from_network() - to_chat(user, "You unsecure the generator from the floor.") - - playsound(src.loc, O.usesound, 50, 1) - anchored = !anchored - - else if(istype(O, /obj/item/screwdriver)) - panel_open = !panel_open - playsound(src.loc, O.usesound, 50, 1) - if(panel_open) - to_chat(user, "You open the access panel.") - else - to_chat(user, "You close the access panel.") - else if(istype(O, /obj/item/storage/part_replacer) && panel_open) - exchange_parts(user, O) - return - else if(istype(O, /obj/item/crowbar) && panel_open) - default_deconstruction_crowbar(user, O) - else - return ..() - -/obj/machinery/power/port_gen/pacman/attack_hand(mob/user as mob) - ..() - if(!anchored) - return - ui_interact(user) - -/obj/machinery/power/port_gen/pacman/attack_ai(var/mob/user as mob) - src.add_hiddenprint(user) - return src.attack_hand(user) - -/obj/machinery/power/port_gen/pacman/attack_ghost(var/mob/user) - return src.attack_hand(user) - -/obj/machinery/power/port_gen/pacman/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(IsBroken()) - return - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "pacman.tmpl", src.name, 500, 560) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/power/port_gen/pacman/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - data["active"] = active - if(istype(user, /mob/living/silicon/ai)) - data["is_ai"] = 1 - else if(istype(user, /mob/living/silicon/robot) && !Adjacent(user)) - data["is_ai"] = 1 - else - data["is_ai"] = 0 - - data["output_set"] = power_output - data["output_max"] = max_power_output - data["output_safe"] = max_safe_output - data["output_watts"] = power_output * power_gen - data["temperature_current"] = src.temperature - data["temperature_max"] = src.max_temperature - data["temperature_overheat"] = overheating - // 1 sheet = 1000cm3? - data["fuel_stored"] = round((sheets * 1000) + (sheet_left * 1000)) - data["fuel_capacity"] = round(max_sheets * 1000, 0.1) - data["fuel_usage"] = active ? round((power_output / time_per_sheet) * 1000) : 0 - data["fuel_type"] = sheet_name - - return data - -/obj/machinery/power/port_gen/pacman/Topic(href, href_list) - if(..()) - return - - src.add_fingerprint(usr) - if(href_list["action"]) - if(href_list["action"] == "enable") - if(!active && HasFuel() && !IsBroken()) - active = 1 - update_icon() - if(href_list["action"] == "disable") - if(active) - active = 0 - update_icon() - if(href_list["action"] == "eject") - if(!active) - DropFuel() - if(href_list["action"] == "lower_power") - if(power_output > 1) - power_output-- - if(href_list["action"] == "higher_power") - if(power_output < max_power_output || (emagged && power_output < round(max_power_output*2.5))) - power_output++ - - SSnanoui.update_uis(src) - -/obj/machinery/power/port_gen/pacman/super - name = "S.U.P.E.R.P.A.C.M.A.N.-type Portable Generator" - desc = "A power generator that utilizes uranium sheets as fuel. Can run for much longer than the standard PACMAN type generators. Rated for 80 kW max safe output." - icon_state = "portgen1_0" - base_icon = "portgen1" - sheet_path = /obj/item/stack/sheet/mineral/uranium - sheet_name = "Uranium Sheets" - time_per_sheet = 576 //same power output, but a 50 sheet stack will last 2 hours at max safe power - board_path = /obj/item/circuitboard/pacman/super - -/obj/machinery/power/port_gen/pacman/super/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stock_parts/capacitor/super(null) - component_parts += new board_path(null) - RefreshParts() - -/obj/machinery/power/port_gen/pacman/super/UseFuel() - //produces a tiny amount of radiation when in use - if(prob(2*power_output)) - for(var/mob/living/L in range(src, 5)) - L.apply_effect(1, IRRADIATE) //should amount to ~5 rads per minute at max safe power - ..() - -/obj/machinery/power/port_gen/pacman/super/explode() - //a nice burst of radiation - var/rads = 50 + (sheets + sheet_left)*1.5 - for(var/mob/living/L in range(src, 10)) - //should really fall with the square of the distance, but that makes the rads value drop too fast - //I dunno, maybe physics works different when you live in 2D -- SM radiation also works like this, apparently - L.apply_effect(max(20, round(rads/get_dist(L,src))), IRRADIATE) - - explosion(src.loc, 3, 3, 5, 3) - qdel(src) - -/obj/machinery/power/port_gen/pacman/mrs - name = "M.R.S.P.A.C.M.A.N.-type Portable Generator" - desc = "An advanced power generator that runs on diamonds. Rated for 200 kW maximum safe output!" - icon_state = "portgen2_0" - base_icon = "portgen2" - sheet_path = /obj/item/stack/sheet/mineral/diamond - sheet_name = "Diamond Sheets" - - //I don't think tritium has any other use, so we might as well make this rewarding for players - //max safe power output (power level = 8) is 200 kW and lasts for 1 hour - 3 or 4 of these could power the station - power_gen = 25000 //watts - max_power_output = 10 - max_safe_output = 8 - time_per_sheet = 576 - max_temperature = 800 - temperature_gain = 90 - board_path = /obj/item/circuitboard/pacman/mrs - -/obj/machinery/power/port_gen/pacman/mrs/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stack/cable_coil(null, 1) - component_parts += new /obj/item/stock_parts/capacitor/super(null) - component_parts += new board_path(null) - RefreshParts() - -/obj/machinery/power/port_gen/pacman/mrs/explode() - //no special effects, but the explosion is pretty big (same as a supermatter shard). - explosion(src.loc, 3, 6, 12, 16, 1) - qdel(src) +//Baseline portable generator. Has all the default handling. Not intended to be used on it's own (since it generates unlimited power). +/obj/machinery/power/port_gen + name = "Placeholder Generator" //seriously, don't use this. It can't be anchored without VV magic. + desc = "A portable generator for emergency backup power" + icon = 'icons/obj/power.dmi' + icon_state = "portgen0_0" + density = 1 + anchored = 0 + use_power = NO_POWER_USE + + var/active = 0 + var/power_gen = 5000 + var/open = 0 + var/recent_fault = 0 + var/power_output = 1 + var/base_icon = "portgen0" + +/obj/machinery/power/port_gen/proc/IsBroken() + return (stat & (BROKEN|EMPED)) + +/obj/machinery/power/port_gen/proc/HasFuel() //Placeholder for fuel check. + return 1 + +/obj/machinery/power/port_gen/proc/UseFuel() //Placeholder for fuel use. + return + +/obj/machinery/power/port_gen/proc/DropFuel() + return + +/obj/machinery/power/port_gen/proc/handleInactive() + return + +/obj/machinery/power/port_gen/update_icon() + icon_state = "[base_icon]_[active]" + +/obj/machinery/power/port_gen/process() + if(active && HasFuel() && !IsBroken() && anchored && powernet) + add_avail(power_gen * power_output) + UseFuel() + else + active = 0 + handleInactive() + update_icon() + +/obj/machinery/power/powered() + return 1 //doesn't require an external power source + +/obj/machinery/power/port_gen/attack_hand(mob/user as mob) + if(..()) + return + if(!anchored) + return + +/obj/machinery/power/port_gen/examine(mob/user) + . = ..() + if(!in_range(user, src)) + if(active) + . += "The generator is on." + else + . += "The generator is off." + +/obj/machinery/power/port_gen/emp_act(severity) + var/duration = 6000 //ten minutes + switch(severity) + if(1) + stat &= BROKEN + if(prob(75)) explode() + if(2) + if(prob(25)) stat &= BROKEN + if(prob(10)) explode() + if(3) + if(prob(10)) stat &= BROKEN + duration = 300 + + stat |= EMPED + if(duration) + spawn(duration) + stat &= ~EMPED + +/obj/machinery/power/port_gen/proc/explode() + explosion(src.loc, -1, 3, 5, -1) + qdel(src) + +#define TEMPERATURE_DIVISOR 40 +#define TEMPERATURE_CHANGE_MAX 20 + +//A power generator that runs on solid plasma sheets. +/obj/machinery/power/port_gen/pacman + name = "\improper P.A.C.M.A.N.-type Portable Generator" + desc = "A power generator that runs on solid plasma sheets. Rated for 80 kW max safe output." + + var/sheet_name = "Plasma Sheets" + var/sheet_path = /obj/item/stack/sheet/mineral/plasma + var/board_path = /obj/item/circuitboard/pacman + + /* + These values were chosen so that the generator can run safely up to 80 kW + A full 50 plasma sheet stack should last 20 minutes at power_output = 4 + temperature_gain and max_temperature are set so that the max safe power level is 4. + Setting to 5 or higher can only be done temporarily before the generator overheats. + */ + power_gen = 20000 //Watts output per power_output level + var/max_power_output = 5 //The maximum power setting without emagging. + var/max_safe_output = 4 // For UI use, maximal output that won't cause overheat. + var/time_per_sheet = 96 //fuel efficiency - how long 1 sheet lasts at power level 1 + var/max_sheets = 100 //max capacity of the hopper + var/max_temperature = 300 //max temperature before overheating increases + var/temperature_gain = 50 //how much the temperature increases per power output level, in degrees per level + + var/sheets = 0 //How many sheets of material are loaded in the generator + var/sheet_left = 0 //How much is left of the current sheet + var/temperature = 0 //The current temperature + var/overheating = 0 //if this gets high enough the generator explodes + +/obj/machinery/power/port_gen/pacman/Initialize() + ..() + if(anchored) + connect_to_network() + +/obj/machinery/power/port_gen/pacman/New() + ..() + component_parts = list() + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/micro_laser(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stock_parts/capacitor(null) + component_parts += new board_path(null) + RefreshParts() + +/obj/machinery/power/port_gen/pacman/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stock_parts/capacitor/super(null) + component_parts += new board_path(null) + RefreshParts() + +/obj/machinery/power/port_gen/pacman/Destroy() + DropFuel() + return ..() + +/obj/machinery/power/port_gen/pacman/RefreshParts() + var/temp_rating = 0 + for(var/obj/item/stock_parts/SP in component_parts) + if(istype(SP, /obj/item/stock_parts/matter_bin)) + max_sheets = SP.rating * SP.rating * 50 + else if(istype(SP, /obj/item/stock_parts/micro_laser) || istype(SP, /obj/item/stock_parts/capacitor)) + temp_rating += SP.rating + + power_gen = round(initial(power_gen) * (max(2, temp_rating) / 2)) + +/obj/machinery/power/port_gen/pacman/examine(mob/user) + . = ..() + . += "\The [src] appears to be producing [power_gen*power_output] W." + . += "There [sheets == 1 ? "is" : "are"] [sheets] sheet\s left in the hopper." + if(IsBroken()) + . += "\The [src] seems to have broken down." + if(overheating) + . += "\The [src] is overheating!" + +/obj/machinery/power/port_gen/pacman/HasFuel() + var/needed_sheets = power_output / time_per_sheet + if(sheets >= needed_sheets - sheet_left) + return 1 + return 0 + +//Removes one stack's worth of material from the generator. +/obj/machinery/power/port_gen/pacman/DropFuel() + if(sheets) + var/obj/item/stack/sheet/mineral/S = new sheet_path(loc) + var/amount = min(sheets, S.max_amount) + S.amount = amount + sheets -= amount + +/obj/machinery/power/port_gen/pacman/UseFuel() + + //how much material are we using this iteration? + var/needed_sheets = power_output / time_per_sheet + + //HasFuel() should guarantee us that there is enough fuel left, so no need to check that + //the only thing we need to worry about is if we are going to rollover to the next sheet + if(needed_sheets > sheet_left) + sheets-- + sheet_left = (1 + sheet_left) - needed_sheets + else + sheet_left -= needed_sheets + + //calculate the "target" temperature range + //This should probably depend on the external temperature somehow, but whatever. + var/lower_limit = 56 + power_output * temperature_gain + var/upper_limit = 76 + power_output * temperature_gain + + /* + Hot or cold environments can affect the equilibrium temperature + The lower the pressure the less effect it has. I guess it cools using a radiator or something when in vacuum. + Gives traitors more opportunities to sabotage the generator or allows enterprising engineers to build additional + cooling in order to get more power out. + */ + var/datum/gas_mixture/environment = loc.return_air() + if(environment) + var/ratio = min(environment.return_pressure()/ONE_ATMOSPHERE, 1) + var/ambient = environment.temperature - T20C + lower_limit += ambient*ratio + upper_limit += ambient*ratio + + var/average = (upper_limit + lower_limit)/2 + + //calculate the temperature increase + var/bias = 0 + if(temperature < lower_limit) + bias = min(round((average - temperature)/TEMPERATURE_DIVISOR, 1), TEMPERATURE_CHANGE_MAX) + else if(temperature > upper_limit) + bias = max(round((temperature - average)/TEMPERATURE_DIVISOR, 1), -TEMPERATURE_CHANGE_MAX) + + //limit temperature increase so that it cannot raise temperature above upper_limit, + //or if it is already above upper_limit, limit the increase to 0. + var/inc_limit = max(upper_limit - temperature, 0) + var/dec_limit = min(temperature - lower_limit, 0) + temperature += between(dec_limit, rand(-7 + bias, 7 + bias), inc_limit) + + if(temperature > max_temperature) + overheat() + else if(overheating > 0) + overheating-- + +/obj/machinery/power/port_gen/pacman/handleInactive() + var/cooling_temperature = 20 + var/datum/gas_mixture/environment = loc.return_air() + if(environment) + var/ratio = min(environment.return_pressure()/ONE_ATMOSPHERE, 1) + var/ambient = environment.temperature - T20C + cooling_temperature += ambient*ratio + + if(temperature > cooling_temperature) + var/temp_loss = (temperature - cooling_temperature)/TEMPERATURE_DIVISOR + temp_loss = between(2, round(temp_loss, 1), TEMPERATURE_CHANGE_MAX) + temperature = max(temperature - temp_loss, cooling_temperature) + SSnanoui.update_uis(src) + + if(overheating) + overheating-- + +/obj/machinery/power/port_gen/pacman/proc/overheat() + overheating++ + if(overheating > 60) + explode() + +/obj/machinery/power/port_gen/pacman/explode() + //Vapourize all the plasma + //When ground up in a grinder, 1 sheet produces 20 u of plasma -- Chemistry-Machinery.dm + //1 mol = 10 u? I dunno. 1 mol of carbon is definitely bigger than a pill + /*var/plasma = (sheets+sheet_left)*20 + var/datum/gas_mixture/environment = loc.return_air() + if(environment) + environment.adjust_gas("plasma", plasma/10, temperature + T0C)*/ + + sheets = 0 + sheet_left = 0 + ..() + +/obj/machinery/power/port_gen/pacman/emag_act(var/remaining_charges, var/mob/user) + if(active && prob(25)) + explode() //if they're foolish enough to emag while it's running + + if(!emagged) + emagged = 1 + return 1 + +/obj/machinery/power/port_gen/pacman/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(istype(O, sheet_path)) + var/obj/item/stack/addstack = O + var/amount = min((max_sheets - sheets), addstack.amount) + if(amount < 1) + to_chat(user, "The [src.name] is full!") + return + to_chat(user, "You add [amount] sheet\s to the [src.name].") + sheets += amount + addstack.use(amount) + SSnanoui.update_uis(src) + return + else if(!active) + if(istype(O, /obj/item/wrench)) + + if(!anchored) + connect_to_network() + to_chat(user, "You secure the generator to the floor.") + else + disconnect_from_network() + to_chat(user, "You unsecure the generator from the floor.") + + playsound(src.loc, O.usesound, 50, 1) + anchored = !anchored + + else if(istype(O, /obj/item/screwdriver)) + panel_open = !panel_open + playsound(src.loc, O.usesound, 50, 1) + if(panel_open) + to_chat(user, "You open the access panel.") + else + to_chat(user, "You close the access panel.") + else if(istype(O, /obj/item/storage/part_replacer) && panel_open) + exchange_parts(user, O) + return + else if(istype(O, /obj/item/crowbar) && panel_open) + default_deconstruction_crowbar(user, O) + else + return ..() + +/obj/machinery/power/port_gen/pacman/attack_hand(mob/user as mob) + ..() + if(!anchored) + return + ui_interact(user) + +/obj/machinery/power/port_gen/pacman/attack_ai(var/mob/user as mob) + src.add_hiddenprint(user) + return src.attack_hand(user) + +/obj/machinery/power/port_gen/pacman/attack_ghost(var/mob/user) + return src.attack_hand(user) + +/obj/machinery/power/port_gen/pacman/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(IsBroken()) + return + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "pacman.tmpl", src.name, 500, 560) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/power/port_gen/pacman/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + data["active"] = active + if(istype(user, /mob/living/silicon/ai)) + data["is_ai"] = 1 + else if(istype(user, /mob/living/silicon/robot) && !Adjacent(user)) + data["is_ai"] = 1 + else + data["is_ai"] = 0 + + data["output_set"] = power_output + data["output_max"] = max_power_output + data["output_safe"] = max_safe_output + data["output_watts"] = power_output * power_gen + data["temperature_current"] = src.temperature + data["temperature_max"] = src.max_temperature + data["temperature_overheat"] = overheating + // 1 sheet = 1000cm3? + data["fuel_stored"] = round((sheets * 1000) + (sheet_left * 1000)) + data["fuel_capacity"] = round(max_sheets * 1000, 0.1) + data["fuel_usage"] = active ? round((power_output / time_per_sheet) * 1000) : 0 + data["fuel_type"] = sheet_name + + return data + +/obj/machinery/power/port_gen/pacman/Topic(href, href_list) + if(..()) + return + + src.add_fingerprint(usr) + if(href_list["action"]) + if(href_list["action"] == "enable") + if(!active && HasFuel() && !IsBroken()) + active = 1 + update_icon() + if(href_list["action"] == "disable") + if(active) + active = 0 + update_icon() + if(href_list["action"] == "eject") + if(!active) + DropFuel() + if(href_list["action"] == "lower_power") + if(power_output > 1) + power_output-- + if(href_list["action"] == "higher_power") + if(power_output < max_power_output || (emagged && power_output < round(max_power_output*2.5))) + power_output++ + + SSnanoui.update_uis(src) + +/obj/machinery/power/port_gen/pacman/super + name = "S.U.P.E.R.P.A.C.M.A.N.-type Portable Generator" + desc = "A power generator that utilizes uranium sheets as fuel. Can run for much longer than the standard PACMAN type generators. Rated for 80 kW max safe output." + icon_state = "portgen1_0" + base_icon = "portgen1" + sheet_path = /obj/item/stack/sheet/mineral/uranium + sheet_name = "Uranium Sheets" + time_per_sheet = 576 //same power output, but a 50 sheet stack will last 2 hours at max safe power + board_path = /obj/item/circuitboard/pacman/super + +/obj/machinery/power/port_gen/pacman/super/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stock_parts/capacitor/super(null) + component_parts += new board_path(null) + RefreshParts() + +/obj/machinery/power/port_gen/pacman/super/UseFuel() + //produces a tiny amount of radiation when in use + if(prob(2*power_output)) + for(var/mob/living/L in range(src, 5)) + L.apply_effect(1, IRRADIATE) //should amount to ~5 rads per minute at max safe power + ..() + +/obj/machinery/power/port_gen/pacman/super/explode() + //a nice burst of radiation + var/rads = 50 + (sheets + sheet_left)*1.5 + for(var/mob/living/L in range(src, 10)) + //should really fall with the square of the distance, but that makes the rads value drop too fast + //I dunno, maybe physics works different when you live in 2D -- SM radiation also works like this, apparently + L.apply_effect(max(20, round(rads/get_dist(L,src))), IRRADIATE) + + explosion(src.loc, 3, 3, 5, 3) + qdel(src) + +/obj/machinery/power/port_gen/pacman/mrs + name = "M.R.S.P.A.C.M.A.N.-type Portable Generator" + desc = "An advanced power generator that runs on diamonds. Rated for 200 kW maximum safe output!" + icon_state = "portgen2_0" + base_icon = "portgen2" + sheet_path = /obj/item/stack/sheet/mineral/diamond + sheet_name = "Diamond Sheets" + + //I don't think tritium has any other use, so we might as well make this rewarding for players + //max safe power output (power level = 8) is 200 kW and lasts for 1 hour - 3 or 4 of these could power the station + power_gen = 25000 //watts + max_power_output = 10 + max_safe_output = 8 + time_per_sheet = 576 + max_temperature = 800 + temperature_gain = 90 + board_path = /obj/item/circuitboard/pacman/mrs + +/obj/machinery/power/port_gen/pacman/mrs/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stack/cable_coil(null, 1) + component_parts += new /obj/item/stock_parts/capacitor/super(null) + component_parts += new board_path(null) + RefreshParts() + +/obj/machinery/power/port_gen/pacman/mrs/explode() + //no special effects, but the explosion is pretty big (same as a supermatter shard). + explosion(src.loc, 3, 6, 12, 16, 1) + qdel(src) diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index 33741d1475b..7b43291b5a2 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -1,380 +1,380 @@ -////////////////////////////// -// POWER MACHINERY BASE CLASS -////////////////////////////// - -///////////////////////////// -// Definitions -///////////////////////////// - -/obj/machinery/power - name = null - icon = 'icons/obj/power.dmi' - anchored = TRUE - on_blueprints = TRUE - var/datum/powernet/powernet = null - use_power = NO_POWER_USE - idle_power_usage = 0 - active_power_usage = 0 - -/obj/machinery/power/Destroy() - disconnect_from_network() - return ..() - -/////////////////////////////// -// General procedures -////////////////////////////// - -// common helper procs for all power machines -// All power generation handled in add_avail() -// Machines should use add_load(), surplus(), avail() -// Non-machines should use add_delayedload(), delayed_surplus(), newavail() - -/obj/machinery/power/proc/add_avail(amount) - if(powernet) - powernet.newavail += amount - return TRUE - else - return FALSE - -/obj/machinery/power/proc/add_load(amount) - if(powernet) - powernet.load += amount - -/obj/machinery/power/proc/surplus() - if(powernet) - return Clamp(powernet.avail-powernet.load, 0, powernet.avail) - else - return 0 - -/obj/machinery/power/proc/avail() - if(powernet) - return powernet.avail - else - return 0 - -/obj/machinery/power/proc/add_delayedload(amount) - if(powernet) - powernet.delayedload += amount - -/obj/machinery/power/proc/delayed_surplus() - if(powernet) - return Clamp(powernet.newavail - powernet.delayedload, 0, powernet.newavail) - else - return 0 - -/obj/machinery/power/proc/newavail() - if(powernet) - return powernet.newavail - else - return 0 - -/obj/machinery/power/proc/disconnect_terminal() // machines without a terminal will just return, no harm no fowl. - return - -// returns true if the area has power on given channel (or doesn't require power). -// defaults to power_channel -/obj/machinery/proc/powered(var/chan = -1) // defaults to power_channel - if(!loc) - return FALSE - if(!use_power) - return TRUE - - var/area/A = get_area(src) // make sure it's in an area - if(!A) - return FALSE // if not, then not powered - if(chan == -1) - chan = power_channel - return A.powered(chan) // return power status of the area - -// increment the power usage stats for an area -/obj/machinery/proc/use_power(amount, chan = -1) // defaults to power_channel - var/area/A = get_area(src) // make sure it's in an area - if(!A) - return - if(chan == -1) - chan = power_channel - A.use_power(amount, chan) - -/obj/machinery/proc/addStaticPower(value, powerchannel) - var/area/A = get_area(src) - if(!A) - return - A.addStaticPower(value, powerchannel) - -/obj/machinery/proc/removeStaticPower(value, powerchannel) - addStaticPower(-value, powerchannel) - -/obj/machinery/proc/power_change() // called whenever the power settings of the containing area change - // by default, check equipment channel & set flag - // can override if needed - if(powered(power_channel)) - stat &= ~NOPOWER - else - - stat |= NOPOWER - return - -// connect the machine to a powernet if a node cable is present on the turf -/obj/machinery/power/proc/connect_to_network() - var/turf/T = src.loc - if(!T || !istype(T)) - return FALSE - - var/obj/structure/cable/C = T.get_cable_node() //check if we have a node cable on the machine turf, the first found is picked - if(!C || !C.powernet) - return FALSE - - C.powernet.add_machine(src) - return TRUE - -// remove and disconnect the machine from its current powernet -/obj/machinery/power/proc/disconnect_from_network() - if(!powernet) - return FALSE - powernet.remove_machine(src) - return TRUE - -// attach a wire to a power machine - leads from the turf you are standing on -//almost never called, overwritten by all power machines but terminal and generator -/obj/machinery/power/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/coil = I - var/turf/T = user.loc - if(T.intact || !isfloorturf(T)) - return - if(get_dist(src, user) > 1) - return - coil.place_turf(T, user) - else - return ..() - - -/////////////////////////////////////////// -// Powernet handling helpers -////////////////////////////////////////// - -//returns all the cables WITHOUT a powernet in neighbors turfs, -//pointing towards the turf the machine is located at -/obj/machinery/power/proc/get_connections() - - . = list() - - var/cdir - var/turf/T - - for(var/card in cardinal) - T = get_step(loc,card) - cdir = get_dir(T,loc) - - for(var/obj/structure/cable/C in T) - if(C.powernet) - continue - if(C.d1 == cdir || C.d2 == cdir) - . += C - return . - -//returns all the cables in neighbors turfs, -//pointing towards the turf the machine is located at -/obj/machinery/power/proc/get_marked_connections() - - . = list() - - var/cdir - var/turf/T - - for(var/card in cardinal) - T = get_step(loc,card) - cdir = get_dir(T,loc) - - for(var/obj/structure/cable/C in T) - if(C.d1 == cdir || C.d2 == cdir) - . += C - return . - -//returns all the NODES (O-X) cables WITHOUT a powernet in the turf the machine is located at -/obj/machinery/power/proc/get_indirect_connections() - . = list() - for(var/obj/structure/cable/C in loc) - if(C.powernet) - continue - if(C.d1 == 0) // the cable is a node cable - . += C - return . - -/////////////////////////////////////////// -// GLOBAL PROCS for powernets handling -////////////////////////////////////////// - - -// returns a list of all power-related objects (nodes, cable, junctions) in turf, -// excluding source, that match the direction d -// if unmarked==1, only return those with no powernet -/proc/power_list(turf/T, source, d, unmarked=0, cable_only = 0) - . = list() - - for(var/AM in T) - if(AM == source) - continue //we don't want to return source - - if(!cable_only && istype(AM, /obj/machinery/power)) - var/obj/machinery/power/P = AM - if(P.powernet == 0) - continue // exclude APCs which have powernet=0 - - if(!unmarked || !P.powernet) //if unmarked=1 we only return things with no powernet - if(d == 0) - . += P - - else if(istype(AM, /obj/structure/cable)) - var/obj/structure/cable/C = AM - - if(!unmarked || !C.powernet) - if(C.d1 == d || C.d2 == d) - . += C - return . - -//remove the old powernet and replace it with a new one throughout the network. -/proc/propagate_network(obj/O, datum/powernet/PN) - var/list/worklist = list() - var/list/found_machines = list() - var/index = 1 - var/obj/P = null - - worklist+=O //start propagating from the passed object - - while(index<=worklist.len) //until we've exhausted all power objects - P = worklist[index] //get the next power object found - index++ - - if(istype(P, /obj/structure/cable)) - var/obj/structure/cable/C = P - if(C.powernet != PN) //add it to the powernet, if it isn't already there - PN.add_cable(C) - worklist |= C.get_connections() //get adjacents power objects, with or without a powernet - - else if(P.anchored && istype(P, /obj/machinery/power)) - var/obj/machinery/power/M = P - found_machines |= M //we wait until the powernet is fully propagates to connect the machines - - else - continue - - //now that the powernet is set, connect found machines to it - for(var/obj/machinery/power/PM in found_machines) - if(!PM.connect_to_network()) //couldn't find a node on its turf... - PM.disconnect_from_network() //... so disconnect if already on a powernet - - -//Merge two powernets, the bigger (in cable length term) absorbing the other -/proc/merge_powernets(datum/powernet/net1, datum/powernet/net2) - if(!net1 || !net2) //if one of the powernet doesn't exist, return - return - - if(net1 == net2) //don't merge same powernets - return - - //We assume net1 is larger. If net2 is in fact larger we are just going to make them switch places to reduce on code. - if(net1.cables.len < net2.cables.len) //net2 is larger than net1. Let's switch them around - var/temp = net1 - net1 = net2 - net2 = temp - - //merge net2 into net1 - for(var/obj/structure/cable/Cable in net2.cables) //merge cables - net1.add_cable(Cable) - - for(var/obj/machinery/power/Node in net2.nodes) //merge power machines - if(!Node.connect_to_network()) - Node.disconnect_from_network() //if somehow we can't connect the machine to the new powernet, disconnect it from the old nonetheless - - return net1 - -//Determines how strong could be shock, deals damage to mob, uses power. -//M is a mob who touched wire/whatever -//power_source is a source of electricity, can be powercell, area, apc, cable, powernet or null -//source is an object caused electrocuting (airlock, grille, etc) -//No animations will be performed by this proc. -/proc/electrocute_mob(mob/living/M, power_source, obj/source, siemens_coeff = 1, dist_check = FALSE) - if(!M || ismecha(M.loc)) - return FALSE //feckin mechs are dumb - if(dist_check) - if(!in_range(source, M)) - return FALSE - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.gloves) - var/obj/item/clothing/gloves/G = H.gloves - if(G.siemens_coefficient == 0) - return FALSE //to avoid spamming with insulated glvoes on - - var/area/source_area - if(istype(power_source, /area)) - source_area = power_source - power_source = source_area.get_apc() - if(istype(power_source, /obj/structure/cable)) - var/obj/structure/cable/Cable = power_source - power_source = Cable.powernet - - var/datum/powernet/PN - var/obj/item/stock_parts/cell/cell - - if(istype(power_source, /datum/powernet)) - PN = power_source - else if(istype(power_source, /obj/item/stock_parts/cell)) - cell = power_source - else if(istype(power_source, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/apc = power_source - cell = apc.cell - if(apc.terminal) - PN = apc.terminal.powernet - else if(!power_source) - return 0 - else - log_admin("ERROR: /proc/electrocute_mob([M], [power_source], [source]): wrong power_source") - return 0 - if(!cell && !PN) - return 0 - var/PN_damage = 0 - var/cell_damage = 0 - if(PN) - PN_damage = PN.get_electrocute_damage() - if(cell) - cell_damage = cell.get_electrocute_damage() - var/shock_damage = 0 - if(PN_damage >= cell_damage) - power_source = PN - shock_damage = PN_damage - else - power_source = cell - shock_damage = cell_damage - var/drained_hp = M.electrocute_act(shock_damage, source, siemens_coeff) //zzzzzzap! - var/drained_energy = drained_hp*20 - - if(source_area) - source_area.use_power(drained_energy/GLOB.CELLRATE) - else if(istype(power_source, /datum/powernet)) - var/drained_power = drained_energy/GLOB.CELLRATE //convert from "joules" to "watts" - PN.delayedload += (min(drained_power, max(PN.newavail - PN.delayedload, 0))) - else if (istype(power_source, /obj/item/stock_parts/cell)) - cell.use(drained_energy) - return drained_energy - -//////////////////////////////////////////////// -// Misc. -/////////////////////////////////////////////// - - -// return a knot cable (O-X) if one is present in the turf -// null if there's none -/turf/proc/get_cable_node() - if(!can_have_cabling()) - return null - for(var/obj/structure/cable/C in src) - if(C.d1 == 0) - return C - return null - -/area/proc/get_apc() - for(var/obj/machinery/power/apc/APC in GLOB.apcs) - if(APC.area == src) - return APC \ No newline at end of file +////////////////////////////// +// POWER MACHINERY BASE CLASS +////////////////////////////// + +///////////////////////////// +// Definitions +///////////////////////////// + +/obj/machinery/power + name = null + icon = 'icons/obj/power.dmi' + anchored = TRUE + on_blueprints = TRUE + var/datum/powernet/powernet = null + use_power = NO_POWER_USE + idle_power_usage = 0 + active_power_usage = 0 + +/obj/machinery/power/Destroy() + disconnect_from_network() + return ..() + +/////////////////////////////// +// General procedures +////////////////////////////// + +// common helper procs for all power machines +// All power generation handled in add_avail() +// Machines should use add_load(), surplus(), avail() +// Non-machines should use add_delayedload(), delayed_surplus(), newavail() + +/obj/machinery/power/proc/add_avail(amount) + if(powernet) + powernet.newavail += amount + return TRUE + else + return FALSE + +/obj/machinery/power/proc/add_load(amount) + if(powernet) + powernet.load += amount + +/obj/machinery/power/proc/surplus() + if(powernet) + return Clamp(powernet.avail-powernet.load, 0, powernet.avail) + else + return 0 + +/obj/machinery/power/proc/avail() + if(powernet) + return powernet.avail + else + return 0 + +/obj/machinery/power/proc/add_delayedload(amount) + if(powernet) + powernet.delayedload += amount + +/obj/machinery/power/proc/delayed_surplus() + if(powernet) + return Clamp(powernet.newavail - powernet.delayedload, 0, powernet.newavail) + else + return 0 + +/obj/machinery/power/proc/newavail() + if(powernet) + return powernet.newavail + else + return 0 + +/obj/machinery/power/proc/disconnect_terminal() // machines without a terminal will just return, no harm no fowl. + return + +// returns true if the area has power on given channel (or doesn't require power). +// defaults to power_channel +/obj/machinery/proc/powered(var/chan = -1) // defaults to power_channel + if(!loc) + return FALSE + if(!use_power) + return TRUE + + var/area/A = get_area(src) // make sure it's in an area + if(!A) + return FALSE // if not, then not powered + if(chan == -1) + chan = power_channel + return A.powered(chan) // return power status of the area + +// increment the power usage stats for an area +/obj/machinery/proc/use_power(amount, chan = -1) // defaults to power_channel + var/area/A = get_area(src) // make sure it's in an area + if(!A) + return + if(chan == -1) + chan = power_channel + A.use_power(amount, chan) + +/obj/machinery/proc/addStaticPower(value, powerchannel) + var/area/A = get_area(src) + if(!A) + return + A.addStaticPower(value, powerchannel) + +/obj/machinery/proc/removeStaticPower(value, powerchannel) + addStaticPower(-value, powerchannel) + +/obj/machinery/proc/power_change() // called whenever the power settings of the containing area change + // by default, check equipment channel & set flag + // can override if needed + if(powered(power_channel)) + stat &= ~NOPOWER + else + + stat |= NOPOWER + return + +// connect the machine to a powernet if a node cable is present on the turf +/obj/machinery/power/proc/connect_to_network() + var/turf/T = src.loc + if(!T || !istype(T)) + return FALSE + + var/obj/structure/cable/C = T.get_cable_node() //check if we have a node cable on the machine turf, the first found is picked + if(!C || !C.powernet) + return FALSE + + C.powernet.add_machine(src) + return TRUE + +// remove and disconnect the machine from its current powernet +/obj/machinery/power/proc/disconnect_from_network() + if(!powernet) + return FALSE + powernet.remove_machine(src) + return TRUE + +// attach a wire to a power machine - leads from the turf you are standing on +//almost never called, overwritten by all power machines but terminal and generator +/obj/machinery/power/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil = I + var/turf/T = user.loc + if(T.intact || !isfloorturf(T)) + return + if(get_dist(src, user) > 1) + return + coil.place_turf(T, user) + else + return ..() + + +/////////////////////////////////////////// +// Powernet handling helpers +////////////////////////////////////////// + +//returns all the cables WITHOUT a powernet in neighbors turfs, +//pointing towards the turf the machine is located at +/obj/machinery/power/proc/get_connections() + + . = list() + + var/cdir + var/turf/T + + for(var/card in cardinal) + T = get_step(loc,card) + cdir = get_dir(T,loc) + + for(var/obj/structure/cable/C in T) + if(C.powernet) + continue + if(C.d1 == cdir || C.d2 == cdir) + . += C + return . + +//returns all the cables in neighbors turfs, +//pointing towards the turf the machine is located at +/obj/machinery/power/proc/get_marked_connections() + + . = list() + + var/cdir + var/turf/T + + for(var/card in cardinal) + T = get_step(loc,card) + cdir = get_dir(T,loc) + + for(var/obj/structure/cable/C in T) + if(C.d1 == cdir || C.d2 == cdir) + . += C + return . + +//returns all the NODES (O-X) cables WITHOUT a powernet in the turf the machine is located at +/obj/machinery/power/proc/get_indirect_connections() + . = list() + for(var/obj/structure/cable/C in loc) + if(C.powernet) + continue + if(C.d1 == 0) // the cable is a node cable + . += C + return . + +/////////////////////////////////////////// +// GLOBAL PROCS for powernets handling +////////////////////////////////////////// + + +// returns a list of all power-related objects (nodes, cable, junctions) in turf, +// excluding source, that match the direction d +// if unmarked==1, only return those with no powernet +/proc/power_list(turf/T, source, d, unmarked=0, cable_only = 0) + . = list() + + for(var/AM in T) + if(AM == source) + continue //we don't want to return source + + if(!cable_only && istype(AM, /obj/machinery/power)) + var/obj/machinery/power/P = AM + if(P.powernet == 0) + continue // exclude APCs which have powernet=0 + + if(!unmarked || !P.powernet) //if unmarked=1 we only return things with no powernet + if(d == 0) + . += P + + else if(istype(AM, /obj/structure/cable)) + var/obj/structure/cable/C = AM + + if(!unmarked || !C.powernet) + if(C.d1 == d || C.d2 == d) + . += C + return . + +//remove the old powernet and replace it with a new one throughout the network. +/proc/propagate_network(obj/O, datum/powernet/PN) + var/list/worklist = list() + var/list/found_machines = list() + var/index = 1 + var/obj/P = null + + worklist+=O //start propagating from the passed object + + while(index<=worklist.len) //until we've exhausted all power objects + P = worklist[index] //get the next power object found + index++ + + if(istype(P, /obj/structure/cable)) + var/obj/structure/cable/C = P + if(C.powernet != PN) //add it to the powernet, if it isn't already there + PN.add_cable(C) + worklist |= C.get_connections() //get adjacents power objects, with or without a powernet + + else if(P.anchored && istype(P, /obj/machinery/power)) + var/obj/machinery/power/M = P + found_machines |= M //we wait until the powernet is fully propagates to connect the machines + + else + continue + + //now that the powernet is set, connect found machines to it + for(var/obj/machinery/power/PM in found_machines) + if(!PM.connect_to_network()) //couldn't find a node on its turf... + PM.disconnect_from_network() //... so disconnect if already on a powernet + + +//Merge two powernets, the bigger (in cable length term) absorbing the other +/proc/merge_powernets(datum/powernet/net1, datum/powernet/net2) + if(!net1 || !net2) //if one of the powernet doesn't exist, return + return + + if(net1 == net2) //don't merge same powernets + return + + //We assume net1 is larger. If net2 is in fact larger we are just going to make them switch places to reduce on code. + if(net1.cables.len < net2.cables.len) //net2 is larger than net1. Let's switch them around + var/temp = net1 + net1 = net2 + net2 = temp + + //merge net2 into net1 + for(var/obj/structure/cable/Cable in net2.cables) //merge cables + net1.add_cable(Cable) + + for(var/obj/machinery/power/Node in net2.nodes) //merge power machines + if(!Node.connect_to_network()) + Node.disconnect_from_network() //if somehow we can't connect the machine to the new powernet, disconnect it from the old nonetheless + + return net1 + +//Determines how strong could be shock, deals damage to mob, uses power. +//M is a mob who touched wire/whatever +//power_source is a source of electricity, can be powercell, area, apc, cable, powernet or null +//source is an object caused electrocuting (airlock, grille, etc) +//No animations will be performed by this proc. +/proc/electrocute_mob(mob/living/M, power_source, obj/source, siemens_coeff = 1, dist_check = FALSE) + if(!M || ismecha(M.loc)) + return FALSE //feckin mechs are dumb + if(dist_check) + if(!in_range(source, M)) + return FALSE + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.gloves) + var/obj/item/clothing/gloves/G = H.gloves + if(G.siemens_coefficient == 0) + return FALSE //to avoid spamming with insulated glvoes on + + var/area/source_area + if(istype(power_source, /area)) + source_area = power_source + power_source = source_area.get_apc() + if(istype(power_source, /obj/structure/cable)) + var/obj/structure/cable/Cable = power_source + power_source = Cable.powernet + + var/datum/powernet/PN + var/obj/item/stock_parts/cell/cell + + if(istype(power_source, /datum/powernet)) + PN = power_source + else if(istype(power_source, /obj/item/stock_parts/cell)) + cell = power_source + else if(istype(power_source, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/apc = power_source + cell = apc.cell + if(apc.terminal) + PN = apc.terminal.powernet + else if(!power_source) + return 0 + else + log_admin("ERROR: /proc/electrocute_mob([M], [power_source], [source]): wrong power_source") + return 0 + if(!cell && !PN) + return 0 + var/PN_damage = 0 + var/cell_damage = 0 + if(PN) + PN_damage = PN.get_electrocute_damage() + if(cell) + cell_damage = cell.get_electrocute_damage() + var/shock_damage = 0 + if(PN_damage >= cell_damage) + power_source = PN + shock_damage = PN_damage + else + power_source = cell + shock_damage = cell_damage + var/drained_hp = M.electrocute_act(shock_damage, source, siemens_coeff) //zzzzzzap! + var/drained_energy = drained_hp*20 + + if(source_area) + source_area.use_power(drained_energy/GLOB.CELLRATE) + else if(istype(power_source, /datum/powernet)) + var/drained_power = drained_energy/GLOB.CELLRATE //convert from "joules" to "watts" + PN.delayedload += (min(drained_power, max(PN.newavail - PN.delayedload, 0))) + else if (istype(power_source, /obj/item/stock_parts/cell)) + cell.use(drained_energy) + return drained_energy + +//////////////////////////////////////////////// +// Misc. +/////////////////////////////////////////////// + + +// return a knot cable (O-X) if one is present in the turf +// null if there's none +/turf/proc/get_cable_node() + if(!can_have_cabling()) + return null + for(var/obj/structure/cable/C in src) + if(C.d1 == 0) + return C + return null + +/area/proc/get_apc() + for(var/obj/machinery/power/apc/APC in GLOB.apcs) + if(APC.area == src) + return APC diff --git a/code/modules/power/powernet.dm b/code/modules/power/powernet.dm index b64481b1104..a4ab64a2099 100644 --- a/code/modules/power/powernet.dm +++ b/code/modules/power/powernet.dm @@ -99,4 +99,4 @@ if(avail >= 1000) return Clamp(20 + round(avail / 25000), 20, 195) + rand(-5, 5) else - return 0 \ No newline at end of file + return 0 diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm index 711ffbc8f44..870a42a1f7c 100644 --- a/code/modules/power/singularity/collector.dm +++ b/code/modules/power/singularity/collector.dm @@ -1,148 +1,148 @@ -var/global/list/rad_collectors = list() - -/obj/machinery/power/rad_collector - name = "Radiation Collector Array" - desc = "A device which uses Hawking Radiation and plasma to produce power." - icon = 'icons/obj/singularity.dmi' - icon_state = "ca" - anchored = 0 - density = 1 - req_access = list(ACCESS_ENGINE_EQUIP) -// use_power = NO_POWER_USE - max_integrity = 350 - integrity_failure = 80 - var/obj/item/tank/plasma/P = null - var/last_power = 0 - var/active = 0 - var/locked = 0 - var/drainratio = 1 - -/obj/machinery/power/rad_collector/Initialize(mapload) - . = ..() - rad_collectors += src - -/obj/machinery/power/rad_collector/Destroy() - rad_collectors -= src - return ..() - -/obj/machinery/power/rad_collector/process() - if(P) - if(P.air_contents.toxins <= 0) - investigate_log("out of fuel.","singulo") - P.air_contents.toxins = 0 - eject() - else - P.air_contents.toxins -= 0.001*drainratio - return - - -/obj/machinery/power/rad_collector/attack_hand(mob/user as mob) - if(anchored) - if(!src.locked) - toggle_power() - user.visible_message("[user.name] turns the [src.name] [active? "on":"off"].", \ - "You turn the [src.name] [active? "on":"off"].") - investigate_log("turned [active?"on":"off"] by [user.key]. [P?"Fuel: [round(P.air_contents.toxins/0.29)]%":"It is empty"].","singulo") - return - else - to_chat(user, "The controls are locked!") - return - - -/obj/machinery/power/rad_collector/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/multitool)) - to_chat(user, "The [W.name] detects that [last_power]W were recently produced.") - return 1 - else if(istype(W, /obj/item/analyzer) && P) - atmosanalyzer_scan(P.air_contents, user) - else if(istype(W, /obj/item/tank/plasma)) - if(!src.anchored) - to_chat(user, "The [src] needs to be secured to the floor first.") - return 1 - if(src.P) - to_chat(user, "There's already a plasma tank loaded.") - return 1 - user.drop_item() - src.P = W - W.loc = src - update_icons() - else if(istype(W, /obj/item/crowbar)) - if(P && !src.locked) - eject() - return 1 - else if(istype(W, /obj/item/wrench)) - if(P) - to_chat(user, "Remove the plasma tank first.") - return 1 - playsound(src.loc, W.usesound, 75, 1) - src.anchored = !src.anchored - user.visible_message("[user.name] [anchored? "secures":"unsecures"] the [src.name].", \ - "You [anchored? "secure":"undo"] the external bolts.", \ - "You hear a ratchet") - if(anchored) - connect_to_network() - else - disconnect_from_network() - else if(istype(W, /obj/item/card/id)||istype(W, /obj/item/pda)) - if(src.allowed(user)) - if(active) - src.locked = !src.locked - to_chat(user, "The controls are now [src.locked ? "locked." : "unlocked."]") - else - src.locked = 0 //just in case it somehow gets locked - to_chat(user, "The controls can only be locked when the [src] is active") - else - to_chat(user, "Access denied!") - return 1 - else - return ..() - -/obj/machinery/power/rad_collector/obj_break(damage_flag) - if(!(stat & BROKEN) && !(flags & NODECONSTRUCT)) - eject() - stat |= BROKEN - -/obj/machinery/power/rad_collector/proc/eject() - locked = 0 - var/obj/item/tank/plasma/Z = src.P - if(!Z) - return - Z.loc = get_turf(src) - Z.layer = initial(Z.layer) - Z.plane = initial(Z.plane) - src.P = null - if(active) - toggle_power() - else - update_icons() - -/obj/machinery/power/rad_collector/proc/receive_pulse(var/pulse_strength) - if(P && active) - var/power_produced = 0 - power_produced = P.air_contents.toxins*pulse_strength*20 - add_avail(power_produced) - last_power = power_produced - return - return - - -/obj/machinery/power/rad_collector/proc/update_icons() - overlays.Cut() - if(P) - overlays += image('icons/obj/singularity.dmi', "ptank") - if(stat & (NOPOWER|BROKEN)) - return - if(active) - overlays += image('icons/obj/singularity.dmi', "on") - - -/obj/machinery/power/rad_collector/proc/toggle_power() - active = !active - if(active) - icon_state = "ca_on" - flick("ca_active", src) - else - icon_state = "ca" - flick("ca_deactive", src) - update_icons() - return +var/global/list/rad_collectors = list() + +/obj/machinery/power/rad_collector + name = "Radiation Collector Array" + desc = "A device which uses Hawking Radiation and plasma to produce power." + icon = 'icons/obj/singularity.dmi' + icon_state = "ca" + anchored = 0 + density = 1 + req_access = list(ACCESS_ENGINE_EQUIP) +// use_power = NO_POWER_USE + max_integrity = 350 + integrity_failure = 80 + var/obj/item/tank/plasma/P = null + var/last_power = 0 + var/active = 0 + var/locked = 0 + var/drainratio = 1 + +/obj/machinery/power/rad_collector/Initialize(mapload) + . = ..() + rad_collectors += src + +/obj/machinery/power/rad_collector/Destroy() + rad_collectors -= src + return ..() + +/obj/machinery/power/rad_collector/process() + if(P) + if(P.air_contents.toxins <= 0) + investigate_log("out of fuel.","singulo") + P.air_contents.toxins = 0 + eject() + else + P.air_contents.toxins -= 0.001*drainratio + return + + +/obj/machinery/power/rad_collector/attack_hand(mob/user as mob) + if(anchored) + if(!src.locked) + toggle_power() + user.visible_message("[user.name] turns the [src.name] [active? "on":"off"].", \ + "You turn the [src.name] [active? "on":"off"].") + investigate_log("turned [active?"on":"off"] by [user.key]. [P?"Fuel: [round(P.air_contents.toxins/0.29)]%":"It is empty"].","singulo") + return + else + to_chat(user, "The controls are locked!") + return + + +/obj/machinery/power/rad_collector/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/multitool)) + to_chat(user, "The [W.name] detects that [last_power]W were recently produced.") + return 1 + else if(istype(W, /obj/item/analyzer) && P) + atmosanalyzer_scan(P.air_contents, user) + else if(istype(W, /obj/item/tank/plasma)) + if(!src.anchored) + to_chat(user, "The [src] needs to be secured to the floor first.") + return 1 + if(src.P) + to_chat(user, "There's already a plasma tank loaded.") + return 1 + user.drop_item() + src.P = W + W.loc = src + update_icons() + else if(istype(W, /obj/item/crowbar)) + if(P && !src.locked) + eject() + return 1 + else if(istype(W, /obj/item/wrench)) + if(P) + to_chat(user, "Remove the plasma tank first.") + return 1 + playsound(src.loc, W.usesound, 75, 1) + src.anchored = !src.anchored + user.visible_message("[user.name] [anchored? "secures":"unsecures"] the [src.name].", \ + "You [anchored? "secure":"undo"] the external bolts.", \ + "You hear a ratchet") + if(anchored) + connect_to_network() + else + disconnect_from_network() + else if(istype(W, /obj/item/card/id)||istype(W, /obj/item/pda)) + if(src.allowed(user)) + if(active) + src.locked = !src.locked + to_chat(user, "The controls are now [src.locked ? "locked." : "unlocked."]") + else + src.locked = 0 //just in case it somehow gets locked + to_chat(user, "The controls can only be locked when the [src] is active") + else + to_chat(user, "Access denied!") + return 1 + else + return ..() + +/obj/machinery/power/rad_collector/obj_break(damage_flag) + if(!(stat & BROKEN) && !(flags & NODECONSTRUCT)) + eject() + stat |= BROKEN + +/obj/machinery/power/rad_collector/proc/eject() + locked = 0 + var/obj/item/tank/plasma/Z = src.P + if(!Z) + return + Z.loc = get_turf(src) + Z.layer = initial(Z.layer) + Z.plane = initial(Z.plane) + src.P = null + if(active) + toggle_power() + else + update_icons() + +/obj/machinery/power/rad_collector/proc/receive_pulse(var/pulse_strength) + if(P && active) + var/power_produced = 0 + power_produced = P.air_contents.toxins*pulse_strength*20 + add_avail(power_produced) + last_power = power_produced + return + return + + +/obj/machinery/power/rad_collector/proc/update_icons() + overlays.Cut() + if(P) + overlays += image('icons/obj/singularity.dmi', "ptank") + if(stat & (NOPOWER|BROKEN)) + return + if(active) + overlays += image('icons/obj/singularity.dmi', "on") + + +/obj/machinery/power/rad_collector/proc/toggle_power() + active = !active + if(active) + icon_state = "ca_on" + flick("ca_active", src) + else + icon_state = "ca" + flick("ca_deactive", src) + update_icons() + return diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/singularity/containment_field.dm index d28518ae32b..70ef60b11d6 100644 --- a/code/modules/power/singularity/containment_field.dm +++ b/code/modules/power/singularity/containment_field.dm @@ -1,126 +1,126 @@ -/obj/machinery/field/containment - name = "Containment Field" - desc = "An energy field." - icon = 'icons/obj/singularity.dmi' - icon_state = "Contain_F" - anchored = 1 - density = 0 - move_resist = INFINITY - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - use_power = NO_POWER_USE - light_range = 4 - layer = OBJ_LAYER + 0.1 - var/obj/machinery/field/generator/FG1 = null - var/obj/machinery/field/generator/FG2 = null - -/obj/machinery/field/containment/Destroy() - FG1.fields -= src - FG2.fields -= src - return ..() - -/obj/machinery/field/containment/attack_hand(mob/user) - if(get_dist(src, user) > 1) - return 0 - else - shock_field(user) - return 1 - -/obj/machinery/field/containment/attackby(obj/item/W, mob/user, params) - shock(user) - return TRUE - -/obj/machinery/field/containment/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BURN) - playsound(loc, 'sound/effects/empulse.ogg', 75, TRUE) - if(BRUTE) - playsound(loc, 'sound/effects/empulse.ogg', 75, TRUE) - -/obj/machinery/field/containment/blob_act(obj/structure/blob/B) - return FALSE - - -/obj/machinery/field/containment/ex_act(severity) - return 0 - -/obj/machinery/field/containment/attack_animal(mob/living/simple_animal/M) - if(!FG1 || !FG2) - qdel(src) - return - if(ismegafauna(M)) - M.visible_message("[M] glows fiercely as the containment field flickers out!") - FG1.calc_power(INFINITY) //rip that 'containment' field - M.adjustHealth(-M.obj_damage) - else - ..() - -/obj/machinery/field/containment/Crossed(mob/mover, oldloc) - if(isliving(mover)) - shock_field(mover) - - if(istype(mover, /obj/machinery) || istype(mover, /obj/structure) || istype(mover, /obj/mecha)) - bump_field(mover) - -/obj/machinery/field/containment/proc/set_master(master1,master2) - if(!master1 || !master2) - return 0 - FG1 = master1 - FG2 = master2 - return 1 - -/obj/machinery/field/containment/shock_field(mob/living/user) - if(!FG1 || !FG2) - qdel(src) - return 0 - ..() - -/obj/machinery/field/containment/Move() - qdel(src) - -// Abstract Field Class -// Used for overriding certain procs - -/obj/machinery/field - var/hasShocked = 0 //Used to add a delay between shocks. In some cases this used to crash servers by spawning hundreds of sparks every second. - -/obj/machinery/field/CanPass(atom/movable/mover, turf/target, height=0) - if(hasShocked) - return 0 - if(isliving(mover)) // Don't let mobs through - shock_field(mover) - return 0 - if(istype(mover, /obj/machinery) || istype(mover, /obj/structure) || istype(mover, /obj/mecha)) - bump_field(mover) - return 0 - return ..() - -/obj/machinery/field/proc/shock_field(mob/living/user) - if(isliving(user)) - var/shock_damage = min(rand(30,40),rand(30,40)) - - if(isliving(user) && !issilicon(user)) - var/stun = min(shock_damage, 15) - user.Stun(stun) - user.Weaken(10) - user.electrocute_act(shock_damage, src, 1) - - else if(issilicon(user)) - if(prob(20)) - user.Stun(2) - user.take_overall_damage(0, shock_damage) - user.visible_message("[user.name] was shocked by the [src.name]!", \ - "Energy pulse detected, system damaged!", \ - "You hear an electrical crack.") - - user.updatehealth() - bump_field(user) - -/obj/machinery/field/proc/bump_field(atom/movable/AM as mob|obj) - if(hasShocked) - return 0 - hasShocked = 1 - do_sparks(5, 1, AM.loc) - var/atom/target = get_edge_target_turf(AM, get_dir(src, get_step_away(AM, src))) - AM.throw_at(target, 200, 4) - spawn(5) - hasShocked = 0 +/obj/machinery/field/containment + name = "Containment Field" + desc = "An energy field." + icon = 'icons/obj/singularity.dmi' + icon_state = "Contain_F" + anchored = 1 + density = 0 + move_resist = INFINITY + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + use_power = NO_POWER_USE + light_range = 4 + layer = OBJ_LAYER + 0.1 + var/obj/machinery/field/generator/FG1 = null + var/obj/machinery/field/generator/FG2 = null + +/obj/machinery/field/containment/Destroy() + FG1.fields -= src + FG2.fields -= src + return ..() + +/obj/machinery/field/containment/attack_hand(mob/user) + if(get_dist(src, user) > 1) + return 0 + else + shock_field(user) + return 1 + +/obj/machinery/field/containment/attackby(obj/item/W, mob/user, params) + shock(user) + return TRUE + +/obj/machinery/field/containment/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BURN) + playsound(loc, 'sound/effects/empulse.ogg', 75, TRUE) + if(BRUTE) + playsound(loc, 'sound/effects/empulse.ogg', 75, TRUE) + +/obj/machinery/field/containment/blob_act(obj/structure/blob/B) + return FALSE + + +/obj/machinery/field/containment/ex_act(severity) + return 0 + +/obj/machinery/field/containment/attack_animal(mob/living/simple_animal/M) + if(!FG1 || !FG2) + qdel(src) + return + if(ismegafauna(M)) + M.visible_message("[M] glows fiercely as the containment field flickers out!") + FG1.calc_power(INFINITY) //rip that 'containment' field + M.adjustHealth(-M.obj_damage) + else + ..() + +/obj/machinery/field/containment/Crossed(mob/mover, oldloc) + if(isliving(mover)) + shock_field(mover) + + if(istype(mover, /obj/machinery) || istype(mover, /obj/structure) || istype(mover, /obj/mecha)) + bump_field(mover) + +/obj/machinery/field/containment/proc/set_master(master1,master2) + if(!master1 || !master2) + return 0 + FG1 = master1 + FG2 = master2 + return 1 + +/obj/machinery/field/containment/shock_field(mob/living/user) + if(!FG1 || !FG2) + qdel(src) + return 0 + ..() + +/obj/machinery/field/containment/Move() + qdel(src) + +// Abstract Field Class +// Used for overriding certain procs + +/obj/machinery/field + var/hasShocked = 0 //Used to add a delay between shocks. In some cases this used to crash servers by spawning hundreds of sparks every second. + +/obj/machinery/field/CanPass(atom/movable/mover, turf/target, height=0) + if(hasShocked) + return 0 + if(isliving(mover)) // Don't let mobs through + shock_field(mover) + return 0 + if(istype(mover, /obj/machinery) || istype(mover, /obj/structure) || istype(mover, /obj/mecha)) + bump_field(mover) + return 0 + return ..() + +/obj/machinery/field/proc/shock_field(mob/living/user) + if(isliving(user)) + var/shock_damage = min(rand(30,40),rand(30,40)) + + if(isliving(user) && !issilicon(user)) + var/stun = min(shock_damage, 15) + user.Stun(stun) + user.Weaken(10) + user.electrocute_act(shock_damage, src, 1) + + else if(issilicon(user)) + if(prob(20)) + user.Stun(2) + user.take_overall_damage(0, shock_damage) + user.visible_message("[user.name] was shocked by the [src.name]!", \ + "Energy pulse detected, system damaged!", \ + "You hear an electrical crack.") + + user.updatehealth() + bump_field(user) + +/obj/machinery/field/proc/bump_field(atom/movable/AM as mob|obj) + if(hasShocked) + return 0 + hasShocked = 1 + do_sparks(5, 1, AM.loc) + var/atom/target = get_edge_target_turf(AM, get_dir(src, get_step_away(AM, src))) + AM.throw_at(target, 200, 4) + spawn(5) + hasShocked = 0 diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 2b44be14a13..9579fcc6bf4 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -1,346 +1,346 @@ -/obj/machinery/power/emitter - name = "Emitter" - desc = "A heavy duty industrial laser" - icon = 'icons/obj/singularity.dmi' - icon_state = "emitter" - anchored = 0 - density = 1 - req_access = list(ACCESS_ENGINE_EQUIP) - - use_power = NO_POWER_USE - idle_power_usage = 10 - active_power_usage = 300 - - var/active = 0 - var/powered = 0 - var/fire_delay = 100 - var/maximum_fire_delay = 100 - var/minimum_fire_delay = 20 - var/last_shot = 0 - var/shot_number = 0 - var/state = 0 - var/locked = 0 - - var/frequency = 0 - var/id_tag = null - var/projectile_type = /obj/item/projectile/beam/emitter - var/projectile_sound = 'sound/weapons/emitter.ogg' - var/datum/radio_frequency/radio_connection - var/datum/effect_system/spark_spread/sparks - -/obj/machinery/power/emitter/Initialize(mapload) - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/emitter(null) - component_parts += new /obj/item/stock_parts/micro_laser(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - RefreshParts() - if(state == 2 && anchored) - connect_to_network() - sparks = new - sparks.attach(src) - sparks.set_up(5, 1, src) - if(frequency) - set_frequency(frequency) - -/obj/machinery/power/emitter/RefreshParts() - var/max_firedelay = 120 - var/firedelay = 120 - var/min_firedelay = 24 - var/power_usage = 350 - for(var/obj/item/stock_parts/micro_laser/L in component_parts) - max_firedelay -= 20 * L.rating - min_firedelay -= 4 * L.rating - firedelay -= 20 * L.rating - maximum_fire_delay = max_firedelay - minimum_fire_delay = min_firedelay - fire_delay = firedelay - for(var/obj/item/stock_parts/manipulator/M in component_parts) - power_usage -= 50 * M.rating - active_power_usage = power_usage - - //Radio remote control -/obj/machinery/power/emitter/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - if(frequency) - radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) - - -/obj/machinery/power/emitter/verb/rotate() - set name = "Rotate" - set category = "Object" - set src in oview(1) - - if(src.anchored || usr:stat) - to_chat(usr, "It is fastened to the floor!") - return 0 - src.dir = turn(src.dir, 90) - return 1 - -/obj/machinery/power/emitter/AltClick(mob/user) - if(user.incapacitated()) - to_chat(user, "You can't do that right now!") - return - if(!Adjacent(user)) - return - rotate() - -/obj/machinery/power/emitter/multitool_menu(var/mob/user,var/obj/item/multitool/P) - return {" - - "} - -/obj/machinery/power/emitter/receive_signal(datum/signal/signal) - if(!signal.data["tag"] || (signal.data["tag"] != id_tag)) - return 0 - - var/on=0 - switch(signal.data["command"]) - if("on") - on=1 - - if("off") - on=0 - - if("set") - on = signal.data["state"] > 0 - - if("toggle") - on = !active - - if(anchored && state == 2 && on != active) - active=on - var/statestr=on?"on":"off" - // Spammy message_admins("Emitter turned [statestr] by radio signal ([signal.data["command"]] @ [frequency]) in [formatJumpTo(src)]",0,1) - log_game("Emitter turned [statestr] by radio signal ([signal.data["command"]] @ [frequency]) in ([x], [y], [z]) AAC prints: [jointext(signal.data["hiddenprints"], "")]") - investigate_log("turned [statestr] by radio signal ([signal.data["command"]] @ [frequency]) AAC prints: [jointext(signal.data["hiddenprints"], "")]","singulo") - update_icon() - -/obj/machinery/power/emitter/Destroy() - if(SSradio) - SSradio.remove_object(src, frequency) - radio_connection = null - msg_admin_attack("Emitter deleted at ([x],[y],[z] - [ADMIN_JMP(src)])", ATKLOG_FEW) - log_game("Emitter deleted at ([x],[y],[z])") - investigate_log("deleted at ([x],[y],[z])","singulo") - QDEL_NULL(sparks) - return ..() - -/obj/machinery/power/emitter/update_icon() - if(active && powernet && avail(active_power_usage)) - icon_state = "emitter_+a" - else - icon_state = "emitter" - - -/obj/machinery/power/emitter/attack_hand(mob/user as mob) - src.add_fingerprint(user) - if(state == 2) - if(!powernet) - to_chat(user, "The emitter isn't connected to a wire.") - return 1 - if(!src.locked) - if(src.active==1) - src.active = 0 - to_chat(user, "You turn off the [src].") - message_admins("Emitter turned off by [key_name_admin(user)] in ([x], [y], [z] - JMP)",0,1) - log_game("Emitter turned off by [key_name(user)] in [x], [y], [z]") - investigate_log("turned off by [key_name(usr)]","singulo") - else - src.active = 1 - to_chat(user, "You turn on the [src].") - src.shot_number = 0 - src.fire_delay = maximum_fire_delay - message_admins("Emitter turned on by [key_name_admin(user)] in ([x], [y], [z] - JMP)",0,1) - log_game("Emitter turned on by [key_name(user)] in [x], [y], [z]") - investigate_log("turned on by [key_name(usr)]","singulo") - update_icon() - else - to_chat(user, "The controls are locked!") - else - to_chat(user, "The [src] needs to be firmly secured to the floor first.") - return 1 - - -/obj/machinery/power/emitter/emp_act(var/severity)//Emitters are hardened but still might have issues -// add_load(1000) -/* if((severity == 1)&&prob(1)&&prob(1)) - if(src.active) - src.active = 0 - src.use_power = IDLE_POWER_USE */ - return 1 - -/obj/machinery/power/emitter/attack_animal(mob/living/simple_animal/M) - if(ismegafauna(M) && anchored) - state = 0 - anchored = FALSE - M.visible_message("[M] rips [src] free from its moorings!") - else - ..() - if(!anchored) - step(src, get_dir(M, src)) - -/obj/machinery/power/emitter/process() - if(stat & (BROKEN)) - return - if(src.state != 2 || (!powernet && active_power_usage)) - src.active = 0 - update_icon() - return - if(active == TRUE) - if(!active_power_usage || surplus() >= active_power_usage) - add_load(active_power_usage) - if(!powered) - powered = 1 - update_icon() - investigate_log("regained power and turned on","singulo") - else - if(powered) - powered = 0 - update_icon() - investigate_log("lost power and turned off","singulo") - return - - if(!check_delay()) - return FALSE - fire_beam() - -/obj/machinery/power/emitter/proc/check_delay() - if((last_shot + fire_delay) <= world.time) - return TRUE - return FALSE - -/obj/machinery/power/emitter/proc/fire_beam() - var/obj/item/projectile/P = new projectile_type(get_turf(src)) - playsound(get_turf(src), projectile_sound, 50, TRUE) - if(prob(35)) - sparks.start() - switch(dir) - if(NORTH) - P.yo = 20 - P.xo = 0 - if(NORTHEAST) - P.yo = 20 - P.xo = 20 - if(EAST) - P.yo = 0 - P.xo = 20 - if(SOUTHEAST) - P.yo = -20 - P.xo = 20 - if(WEST) - P.yo = 0 - P.xo = -20 - if(SOUTHWEST) - P.yo = -20 - P.xo = -20 - if(NORTHWEST) - P.yo = 20 - P.xo = -20 - else // Any other - P.yo = -20 - P.xo = 0 - - last_shot = world.time - if(shot_number < 3) - fire_delay = 20 - shot_number ++ - else - fire_delay = rand(minimum_fire_delay, maximum_fire_delay) - shot_number = 0 - P.setDir(dir) - P.starting = loc - P.Angle = null - P.fire() - return P - -/obj/machinery/power/emitter/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/multitool)) - update_multitool_menu(user) - return 1 - - if(istype(W, /obj/item/wrench)) - if(active) - to_chat(user, "Turn off the [src] first.") - return - switch(state) - if(0) - state = 1 - playsound(src.loc, W.usesound, 75, 1) - user.visible_message("[user.name] secures [src.name] to the floor.", \ - "You secure the external reinforcing bolts to the floor.", \ - "You hear a ratchet") - src.anchored = 1 - if(1) - state = 0 - playsound(src.loc,W.usesound, 75, 1) - user.visible_message("[user.name] unsecures [src.name] reinforcing bolts from the floor.", \ - "You undo the external reinforcing bolts.", \ - "You hear a ratchet") - src.anchored = 0 - if(2) - to_chat(user, "The [src.name] needs to be unwelded from the floor.") - return - - if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) - if(emagged) - to_chat(user, "The lock seems to be broken") - return - if(src.allowed(user)) - if(active) - src.locked = !src.locked - to_chat(user, "The controls are now [src.locked ? "locked." : "unlocked."]") - else - src.locked = 0 //just in case it somehow gets locked - to_chat(user, "The controls can only be locked when the [src] is online") - else - to_chat(user, "Access denied.") - return - - if(default_deconstruction_screwdriver(user, "emitter_open", "emitter", W)) - return - - if(exchange_parts(user, W)) - return - - if(default_deconstruction_crowbar(user, W)) - return - - return ..() - -/obj/machinery/power/emitter/emag_act(var/mob/living/user as mob) - if(!emagged) - locked = 0 - emagged = 1 - if(user) - user.visible_message("[user.name] emags the [src.name].","You short out the lock.") - - -/obj/machinery/power/emitter/welder_act(mob/user, obj/item/I) - if(active) - to_chat(user, "Turn off [src] first.") - return - if(state == 0) - to_chat(user, "[src] needs to be wrenched to the floor.") - return - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(state == 1) - WELDER_ATTEMPT_FLOOR_WELD_MESSAGE - else if(state == 2) - WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE - if(!I.use_tool(src, user, 20, volume = I.tool_volume)) - return - if(state == 1) - WELDER_FLOOR_WELD_SUCCESS_MESSAGE - connect_to_network() - state = 2 - else if(state == 2) - WELDER_FLOOR_SLICE_SUCCESS_MESSAGE - disconnect_from_network() - state = 1 +/obj/machinery/power/emitter + name = "Emitter" + desc = "A heavy duty industrial laser" + icon = 'icons/obj/singularity.dmi' + icon_state = "emitter" + anchored = 0 + density = 1 + req_access = list(ACCESS_ENGINE_EQUIP) + + use_power = NO_POWER_USE + idle_power_usage = 10 + active_power_usage = 300 + + var/active = 0 + var/powered = 0 + var/fire_delay = 100 + var/maximum_fire_delay = 100 + var/minimum_fire_delay = 20 + var/last_shot = 0 + var/shot_number = 0 + var/state = 0 + var/locked = 0 + + var/frequency = 0 + var/id_tag = null + var/projectile_type = /obj/item/projectile/beam/emitter + var/projectile_sound = 'sound/weapons/emitter.ogg' + var/datum/radio_frequency/radio_connection + var/datum/effect_system/spark_spread/sparks + +/obj/machinery/power/emitter/Initialize(mapload) + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/emitter(null) + component_parts += new /obj/item/stock_parts/micro_laser(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + RefreshParts() + if(state == 2 && anchored) + connect_to_network() + sparks = new + sparks.attach(src) + sparks.set_up(5, 1, src) + if(frequency) + set_frequency(frequency) + +/obj/machinery/power/emitter/RefreshParts() + var/max_firedelay = 120 + var/firedelay = 120 + var/min_firedelay = 24 + var/power_usage = 350 + for(var/obj/item/stock_parts/micro_laser/L in component_parts) + max_firedelay -= 20 * L.rating + min_firedelay -= 4 * L.rating + firedelay -= 20 * L.rating + maximum_fire_delay = max_firedelay + minimum_fire_delay = min_firedelay + fire_delay = firedelay + for(var/obj/item/stock_parts/manipulator/M in component_parts) + power_usage -= 50 * M.rating + active_power_usage = power_usage + + //Radio remote control +/obj/machinery/power/emitter/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) + + +/obj/machinery/power/emitter/verb/rotate() + set name = "Rotate" + set category = "Object" + set src in oview(1) + + if(src.anchored || usr:stat) + to_chat(usr, "It is fastened to the floor!") + return 0 + src.dir = turn(src.dir, 90) + return 1 + +/obj/machinery/power/emitter/AltClick(mob/user) + if(user.incapacitated()) + to_chat(user, "You can't do that right now!") + return + if(!Adjacent(user)) + return + rotate() + +/obj/machinery/power/emitter/multitool_menu(var/mob/user,var/obj/item/multitool/P) + return {" + + "} + +/obj/machinery/power/emitter/receive_signal(datum/signal/signal) + if(!signal.data["tag"] || (signal.data["tag"] != id_tag)) + return 0 + + var/on=0 + switch(signal.data["command"]) + if("on") + on=1 + + if("off") + on=0 + + if("set") + on = signal.data["state"] > 0 + + if("toggle") + on = !active + + if(anchored && state == 2 && on != active) + active=on + var/statestr=on?"on":"off" + // Spammy message_admins("Emitter turned [statestr] by radio signal ([signal.data["command"]] @ [frequency]) in [formatJumpTo(src)]",0,1) + log_game("Emitter turned [statestr] by radio signal ([signal.data["command"]] @ [frequency]) in ([x], [y], [z]) AAC prints: [jointext(signal.data["hiddenprints"], "")]") + investigate_log("turned [statestr] by radio signal ([signal.data["command"]] @ [frequency]) AAC prints: [jointext(signal.data["hiddenprints"], "")]","singulo") + update_icon() + +/obj/machinery/power/emitter/Destroy() + if(SSradio) + SSradio.remove_object(src, frequency) + radio_connection = null + msg_admin_attack("Emitter deleted at ([x],[y],[z] - [ADMIN_JMP(src)])", ATKLOG_FEW) + log_game("Emitter deleted at ([x],[y],[z])") + investigate_log("deleted at ([x],[y],[z])","singulo") + QDEL_NULL(sparks) + return ..() + +/obj/machinery/power/emitter/update_icon() + if(active && powernet && avail(active_power_usage)) + icon_state = "emitter_+a" + else + icon_state = "emitter" + + +/obj/machinery/power/emitter/attack_hand(mob/user as mob) + src.add_fingerprint(user) + if(state == 2) + if(!powernet) + to_chat(user, "The emitter isn't connected to a wire.") + return 1 + if(!src.locked) + if(src.active==1) + src.active = 0 + to_chat(user, "You turn off the [src].") + message_admins("Emitter turned off by [key_name_admin(user)] in ([x], [y], [z] - JMP)",0,1) + log_game("Emitter turned off by [key_name(user)] in [x], [y], [z]") + investigate_log("turned off by [key_name(usr)]","singulo") + else + src.active = 1 + to_chat(user, "You turn on the [src].") + src.shot_number = 0 + src.fire_delay = maximum_fire_delay + message_admins("Emitter turned on by [key_name_admin(user)] in ([x], [y], [z] - JMP)",0,1) + log_game("Emitter turned on by [key_name(user)] in [x], [y], [z]") + investigate_log("turned on by [key_name(usr)]","singulo") + update_icon() + else + to_chat(user, "The controls are locked!") + else + to_chat(user, "The [src] needs to be firmly secured to the floor first.") + return 1 + + +/obj/machinery/power/emitter/emp_act(var/severity)//Emitters are hardened but still might have issues +// add_load(1000) +/* if((severity == 1)&&prob(1)&&prob(1)) + if(src.active) + src.active = 0 + src.use_power = IDLE_POWER_USE */ + return 1 + +/obj/machinery/power/emitter/attack_animal(mob/living/simple_animal/M) + if(ismegafauna(M) && anchored) + state = 0 + anchored = FALSE + M.visible_message("[M] rips [src] free from its moorings!") + else + ..() + if(!anchored) + step(src, get_dir(M, src)) + +/obj/machinery/power/emitter/process() + if(stat & (BROKEN)) + return + if(src.state != 2 || (!powernet && active_power_usage)) + src.active = 0 + update_icon() + return + if(active == TRUE) + if(!active_power_usage || surplus() >= active_power_usage) + add_load(active_power_usage) + if(!powered) + powered = 1 + update_icon() + investigate_log("regained power and turned on","singulo") + else + if(powered) + powered = 0 + update_icon() + investigate_log("lost power and turned off","singulo") + return + + if(!check_delay()) + return FALSE + fire_beam() + +/obj/machinery/power/emitter/proc/check_delay() + if((last_shot + fire_delay) <= world.time) + return TRUE + return FALSE + +/obj/machinery/power/emitter/proc/fire_beam() + var/obj/item/projectile/P = new projectile_type(get_turf(src)) + playsound(get_turf(src), projectile_sound, 50, TRUE) + if(prob(35)) + sparks.start() + switch(dir) + if(NORTH) + P.yo = 20 + P.xo = 0 + if(NORTHEAST) + P.yo = 20 + P.xo = 20 + if(EAST) + P.yo = 0 + P.xo = 20 + if(SOUTHEAST) + P.yo = -20 + P.xo = 20 + if(WEST) + P.yo = 0 + P.xo = -20 + if(SOUTHWEST) + P.yo = -20 + P.xo = -20 + if(NORTHWEST) + P.yo = 20 + P.xo = -20 + else // Any other + P.yo = -20 + P.xo = 0 + + last_shot = world.time + if(shot_number < 3) + fire_delay = 20 + shot_number ++ + else + fire_delay = rand(minimum_fire_delay, maximum_fire_delay) + shot_number = 0 + P.setDir(dir) + P.starting = loc + P.Angle = null + P.fire() + return P + +/obj/machinery/power/emitter/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/multitool)) + update_multitool_menu(user) + return 1 + + if(istype(W, /obj/item/wrench)) + if(active) + to_chat(user, "Turn off the [src] first.") + return + switch(state) + if(0) + state = 1 + playsound(src.loc, W.usesound, 75, 1) + user.visible_message("[user.name] secures [src.name] to the floor.", \ + "You secure the external reinforcing bolts to the floor.", \ + "You hear a ratchet") + src.anchored = 1 + if(1) + state = 0 + playsound(src.loc,W.usesound, 75, 1) + user.visible_message("[user.name] unsecures [src.name] reinforcing bolts from the floor.", \ + "You undo the external reinforcing bolts.", \ + "You hear a ratchet") + src.anchored = 0 + if(2) + to_chat(user, "The [src.name] needs to be unwelded from the floor.") + return + + if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) + if(emagged) + to_chat(user, "The lock seems to be broken") + return + if(src.allowed(user)) + if(active) + src.locked = !src.locked + to_chat(user, "The controls are now [src.locked ? "locked." : "unlocked."]") + else + src.locked = 0 //just in case it somehow gets locked + to_chat(user, "The controls can only be locked when the [src] is online") + else + to_chat(user, "Access denied.") + return + + if(default_deconstruction_screwdriver(user, "emitter_open", "emitter", W)) + return + + if(exchange_parts(user, W)) + return + + if(default_deconstruction_crowbar(user, W)) + return + + return ..() + +/obj/machinery/power/emitter/emag_act(var/mob/living/user as mob) + if(!emagged) + locked = 0 + emagged = 1 + if(user) + user.visible_message("[user.name] emags the [src.name].","You short out the lock.") + + +/obj/machinery/power/emitter/welder_act(mob/user, obj/item/I) + if(active) + to_chat(user, "Turn off [src] first.") + return + if(state == 0) + to_chat(user, "[src] needs to be wrenched to the floor.") + return + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(state == 1) + WELDER_ATTEMPT_FLOOR_WELD_MESSAGE + else if(state == 2) + WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE + if(!I.use_tool(src, user, 20, volume = I.tool_volume)) + return + if(state == 1) + WELDER_FLOOR_WELD_SUCCESS_MESSAGE + connect_to_network() + state = 2 + else if(state == 2) + WELDER_FLOOR_SLICE_SUCCESS_MESSAGE + disconnect_from_network() + state = 1 diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm index 7dcd7c71135..62f2ed36065 100644 --- a/code/modules/power/singularity/field_generator.dm +++ b/code/modules/power/singularity/field_generator.dm @@ -1,337 +1,337 @@ -/* -field_generator power level display - The icon used for the field_generator need to have 'num_power_levels' number of icon states - named 'Field_Gen +p[num]' where 'num' ranges from 1 to 'num_power_levels' - - The power level is displayed using overlays. The current displayed power level is stored in 'powerlevel'. - The overlay in use and the powerlevel variable must be kept in sync. A powerlevel equal to 0 means that - no power level overlay is currently in the overlays list. - -Aygar -*/ - -#define field_generator_max_power 250 - -#define FG_UNSECURED 0 -#define FG_SECURED 1 -#define FG_WELDED 2 - -#define FG_OFFLINE 0 -#define FG_CHARGING 1 -#define FG_ONLINE 2 - -/obj/machinery/field/generator - name = "Field Generator" - desc = "A large thermal battery that projects a high amount of energy when powered." - icon = 'icons/obj/machines/field_generator.dmi' - icon_state = "Field_Gen" - anchored = 0 - density = 1 - use_power = NO_POWER_USE - max_integrity = 500 - //100% immune to lasers and energy projectiles since it absorbs their energy. - armor = list("melee" = 25, "bullet" = 10, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) - var/const/num_power_levels = 6 // Total number of power level icon has - var/power_level = 0 - var/active = FG_OFFLINE - var/power = 20 // Current amount of power - var/state = FG_UNSECURED - var/warming_up = 0 - var/list/obj/machinery/field/containment/fields - var/list/obj/machinery/field/generator/connected_gens - var/clean_up = 0 - -/obj/machinery/field/generator/update_icon() - overlays.Cut() - if(warming_up) - overlays += "+a[warming_up]" - if(fields.len) - overlays += "+on" - if(power_level) - overlays += "+p[power_level]" - - -/obj/machinery/field/generator/Initialize(mapload) - . = ..() - fields = list() - connected_gens = list() - - -/obj/machinery/field/generator/process() - if(active == FG_ONLINE) - calc_power() - -/obj/machinery/field/generator/attack_hand(mob/user) - if(state == FG_WELDED) - if(get_dist(src, user) <= 1)//Need to actually touch the thing to turn it on - if(active >= FG_CHARGING) - to_chat(user, "You are unable to turn off the [name] once it is online!") - return 1 - else - user.visible_message("[user.name] turns on the [name].", \ - "You turn on the [name].", \ - "You hear heavy droning.") - turn_on() - investigate_log("activated by [user.key].","singulo") - - add_fingerprint(user) - else - to_chat(user, "[src] needs to be firmly secured to the floor first!") - - -/obj/machinery/field/generator/attackby(obj/item/W, mob/user, params) - if(active) - to_chat(user, "[src] needs to be off!") - return - else if(istype(W, /obj/item/wrench)) - switch(state) - if(FG_UNSECURED) - if(isinspace()) return - state = FG_SECURED - playsound(loc, W.usesound, 75, 1) - user.visible_message("[user.name] secures [name] to the floor.", \ - "You secure the external reinforcing bolts to the floor.", \ - "You hear ratchet.") - anchored = 1 - if(FG_SECURED) - state = FG_UNSECURED - playsound(loc, W.usesound, 75, 1) - user.visible_message("[user.name] unsecures [name] reinforcing bolts from the floor.", \ - "You undo the external reinforcing bolts.", \ - "You hear ratchet.") - anchored = 0 - if(FG_WELDED) - to_chat(user, "The [name] needs to be unwelded from the floor!") - else - return ..() - - -/obj/machinery/field/generator/welder_act(mob/user, obj/item/I) - . = TRUE - if(state == FG_UNSECURED) - to_chat(user, "[src] needs to be wrenched to the floor!") - return - if(!I.tool_use_check(user, 0)) - return - if(state == FG_SECURED) - WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE - else if(state == FG_WELDED) - WELDER_ATTEMPT_FLOOR_WELD_MESSAGE - if(I.use_tool(src, user, 20, volume = I.tool_volume)) - if(state == FG_SECURED) - WELDER_FLOOR_SLICE_SUCCESS_MESSAGE - state = FG_WELDED - else if(state == FG_WELDED) - WELDER_FLOOR_WELD_SUCCESS_MESSAGE - state = FG_SECURED - -/obj/machinery/field/generator/emp_act() - return 0 - -/obj/machinery/field/generator/attack_animal(mob/living/simple_animal/M) - if(M.environment_smash & ENVIRONMENT_SMASH_RWALLS && active == FG_OFFLINE && state != FG_UNSECURED) - state = FG_UNSECURED - anchored = FALSE - M.visible_message("[M] rips [src] free from its moorings!") - else - ..() - if(!anchored) - step(src, get_dir(M, src)) - -/obj/machinery/field/generator/blob_act(obj/structure/blob/B) - if(active) - return 0 - else - ..() - -/obj/machinery/field/generator/bullet_act(obj/item/projectile/Proj) - if(Proj.flag != "bullet") - power = min(power + Proj.damage, field_generator_max_power) - check_power_level() - return 0 - - -/obj/machinery/field/generator/Destroy() - cleanup() - return ..() - - -/obj/machinery/field/generator/proc/check_power_level() - var/new_level = round(num_power_levels * power / field_generator_max_power) - if(new_level != power_level) - power_level = new_level - update_icon() - -/obj/machinery/field/generator/proc/turn_off() - active = FG_OFFLINE - spawn(1) - cleanup() - while(warming_up > 0 && !active) - sleep(50) - warming_up-- - update_icon() - -/obj/machinery/field/generator/proc/turn_on() - active = FG_CHARGING - spawn(1) - while(warming_up < 3 && active) - sleep(50) - warming_up++ - update_icon() - if(warming_up >= 3) - start_fields() - - -/obj/machinery/field/generator/proc/calc_power() - var/power_draw = 2 + fields.len - - if(draw_power(round(power_draw/2, 1))) - check_power_level() - return 1 - else - visible_message("The [name] shuts down!", "You hear something shutting down.") - turn_off() - investigate_log("ran out of power and deactivated","singulo") - power = 0 - check_power_level() - return 0 - -//This could likely be better, it tends to start loopin if you have a complex generator loop setup. Still works well enough to run the engine fields will likely recode the field gens and fields sometime -Mport -/obj/machinery/field/generator/proc/draw_power(draw = 0, failsafe = 0, obj/machinery/field/generator/G = null, obj/machinery/field/generator/last = null) - if((G && (G == src)) || (failsafe >= 8))//Loopin, set fail - return 0 - else - failsafe++ - - if(power >= draw)//We have enough power - power -= draw - return 1 - - else//Need more power - draw -= power - power = 0 - for(var/CG in connected_gens) - var/obj/machinery/field/generator/FG = CG - if(FG == last)//We just asked you - continue - if(G)//Another gen is askin for power and we dont have it - if(FG.draw_power(draw,failsafe,G,src))//Can you take the load - return 1 - else - return 0 - else//We are askin another for power - if(FG.draw_power(draw,failsafe,src,src)) - return 1 - else - return 0 - - -/obj/machinery/field/generator/proc/start_fields() - if(state != FG_WELDED || !anchored) - turn_off() - return - spawn(1) - setup_field(1) - spawn(2) - setup_field(2) - spawn(3) - setup_field(4) - spawn(4) - setup_field(8) - spawn(5) - active = FG_ONLINE - - -/obj/machinery/field/generator/proc/setup_field(NSEW) - var/turf/T = loc - if(!istype(T)) - return 0 - - var/obj/machinery/field/generator/G = null - var/steps = 0 - if(!NSEW)//Make sure its ran right - return 0 - for(var/dist in 0 to 7) // checks out to 8 tiles away for another generator - T = get_step(T, NSEW) - if(T.density)//We cant shoot a field though this - return 0 - - G = locate(/obj/machinery/field/generator) in T - if(G) - steps -= 1 - if(!G.active) - return 0 - break - - for(var/TC in T.contents) - var/atom/A = TC - if(ismob(A)) - continue - if(A.density) - return 0 - - steps++ - - if(!G) - return 0 - - T = loc - for(var/dist in 0 to steps) // creates each field tile - var/field_dir = get_dir(T,get_step(G.loc, NSEW)) - T = get_step(T, NSEW) - if(!locate(/obj/machinery/field/containment) in T) - var/obj/machinery/field/containment/CF = new/obj/machinery/field/containment() - CF.set_master(src,G) - CF.loc = T - CF.dir = field_dir - fields += CF - G.fields += CF - for(var/mob/living/L in T) - CF.Crossed(L, null) - - connected_gens |= G - G.connected_gens |= src - update_icon() - - -/obj/machinery/field/generator/proc/cleanup() - clean_up = 1 - for(var/F in fields) - qdel(F) - - for(var/CG in connected_gens) - var/obj/machinery/field/generator/FG = CG - FG.connected_gens -= src - if(!FG.clean_up)//Makes the other gens clean up as well - FG.cleanup() - connected_gens -= FG - clean_up = 0 - update_icon() - - //This is here to help fight the "hurr durr, release singulo cos nobody will notice before the - //singulo eats the evidence". It's not fool-proof but better than nothing. - //I want to avoid using global variables. - spawn(1) - var/temp = 1 //stops spam - for(var/obj/singularity/O in GLOB.singularities) - if(O.last_warning && temp) - if((world.time - O.last_warning) > 50) //to stop message-spam - temp = 0 - message_admins("A singulo exists and a containment field has failed. Location: [get_area(src)] (JMP)",1) - investigate_log("has failed whilst a singulo exists.","singulo") - O.last_warning = world.time - -/obj/machinery/field/generator/shock_field(mob/living/user) - if(fields.len) - ..() - -/obj/machinery/field/generator/bump_field(atom/movable/AM as mob|obj) - if(fields.len) - ..() - -#undef FG_UNSECURED -#undef FG_SECURED -#undef FG_WELDED - -#undef FG_OFFLINE -#undef FG_CHARGING -#undef FG_ONLINE +/* +field_generator power level display + The icon used for the field_generator need to have 'num_power_levels' number of icon states + named 'Field_Gen +p[num]' where 'num' ranges from 1 to 'num_power_levels' + + The power level is displayed using overlays. The current displayed power level is stored in 'powerlevel'. + The overlay in use and the powerlevel variable must be kept in sync. A powerlevel equal to 0 means that + no power level overlay is currently in the overlays list. + -Aygar +*/ + +#define field_generator_max_power 250 + +#define FG_UNSECURED 0 +#define FG_SECURED 1 +#define FG_WELDED 2 + +#define FG_OFFLINE 0 +#define FG_CHARGING 1 +#define FG_ONLINE 2 + +/obj/machinery/field/generator + name = "Field Generator" + desc = "A large thermal battery that projects a high amount of energy when powered." + icon = 'icons/obj/machines/field_generator.dmi' + icon_state = "Field_Gen" + anchored = 0 + density = 1 + use_power = NO_POWER_USE + max_integrity = 500 + //100% immune to lasers and energy projectiles since it absorbs their energy. + armor = list("melee" = 25, "bullet" = 10, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) + var/const/num_power_levels = 6 // Total number of power level icon has + var/power_level = 0 + var/active = FG_OFFLINE + var/power = 20 // Current amount of power + var/state = FG_UNSECURED + var/warming_up = 0 + var/list/obj/machinery/field/containment/fields + var/list/obj/machinery/field/generator/connected_gens + var/clean_up = 0 + +/obj/machinery/field/generator/update_icon() + overlays.Cut() + if(warming_up) + overlays += "+a[warming_up]" + if(fields.len) + overlays += "+on" + if(power_level) + overlays += "+p[power_level]" + + +/obj/machinery/field/generator/Initialize(mapload) + . = ..() + fields = list() + connected_gens = list() + + +/obj/machinery/field/generator/process() + if(active == FG_ONLINE) + calc_power() + +/obj/machinery/field/generator/attack_hand(mob/user) + if(state == FG_WELDED) + if(get_dist(src, user) <= 1)//Need to actually touch the thing to turn it on + if(active >= FG_CHARGING) + to_chat(user, "You are unable to turn off the [name] once it is online!") + return 1 + else + user.visible_message("[user.name] turns on the [name].", \ + "You turn on the [name].", \ + "You hear heavy droning.") + turn_on() + investigate_log("activated by [user.key].","singulo") + + add_fingerprint(user) + else + to_chat(user, "[src] needs to be firmly secured to the floor first!") + + +/obj/machinery/field/generator/attackby(obj/item/W, mob/user, params) + if(active) + to_chat(user, "[src] needs to be off!") + return + else if(istype(W, /obj/item/wrench)) + switch(state) + if(FG_UNSECURED) + if(isinspace()) return + state = FG_SECURED + playsound(loc, W.usesound, 75, 1) + user.visible_message("[user.name] secures [name] to the floor.", \ + "You secure the external reinforcing bolts to the floor.", \ + "You hear ratchet.") + anchored = 1 + if(FG_SECURED) + state = FG_UNSECURED + playsound(loc, W.usesound, 75, 1) + user.visible_message("[user.name] unsecures [name] reinforcing bolts from the floor.", \ + "You undo the external reinforcing bolts.", \ + "You hear ratchet.") + anchored = 0 + if(FG_WELDED) + to_chat(user, "The [name] needs to be unwelded from the floor!") + else + return ..() + + +/obj/machinery/field/generator/welder_act(mob/user, obj/item/I) + . = TRUE + if(state == FG_UNSECURED) + to_chat(user, "[src] needs to be wrenched to the floor!") + return + if(!I.tool_use_check(user, 0)) + return + if(state == FG_SECURED) + WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE + else if(state == FG_WELDED) + WELDER_ATTEMPT_FLOOR_WELD_MESSAGE + if(I.use_tool(src, user, 20, volume = I.tool_volume)) + if(state == FG_SECURED) + WELDER_FLOOR_SLICE_SUCCESS_MESSAGE + state = FG_WELDED + else if(state == FG_WELDED) + WELDER_FLOOR_WELD_SUCCESS_MESSAGE + state = FG_SECURED + +/obj/machinery/field/generator/emp_act() + return 0 + +/obj/machinery/field/generator/attack_animal(mob/living/simple_animal/M) + if(M.environment_smash & ENVIRONMENT_SMASH_RWALLS && active == FG_OFFLINE && state != FG_UNSECURED) + state = FG_UNSECURED + anchored = FALSE + M.visible_message("[M] rips [src] free from its moorings!") + else + ..() + if(!anchored) + step(src, get_dir(M, src)) + +/obj/machinery/field/generator/blob_act(obj/structure/blob/B) + if(active) + return 0 + else + ..() + +/obj/machinery/field/generator/bullet_act(obj/item/projectile/Proj) + if(Proj.flag != "bullet") + power = min(power + Proj.damage, field_generator_max_power) + check_power_level() + return 0 + + +/obj/machinery/field/generator/Destroy() + cleanup() + return ..() + + +/obj/machinery/field/generator/proc/check_power_level() + var/new_level = round(num_power_levels * power / field_generator_max_power) + if(new_level != power_level) + power_level = new_level + update_icon() + +/obj/machinery/field/generator/proc/turn_off() + active = FG_OFFLINE + spawn(1) + cleanup() + while(warming_up > 0 && !active) + sleep(50) + warming_up-- + update_icon() + +/obj/machinery/field/generator/proc/turn_on() + active = FG_CHARGING + spawn(1) + while(warming_up < 3 && active) + sleep(50) + warming_up++ + update_icon() + if(warming_up >= 3) + start_fields() + + +/obj/machinery/field/generator/proc/calc_power() + var/power_draw = 2 + fields.len + + if(draw_power(round(power_draw/2, 1))) + check_power_level() + return 1 + else + visible_message("The [name] shuts down!", "You hear something shutting down.") + turn_off() + investigate_log("ran out of power and deactivated","singulo") + power = 0 + check_power_level() + return 0 + +//This could likely be better, it tends to start loopin if you have a complex generator loop setup. Still works well enough to run the engine fields will likely recode the field gens and fields sometime -Mport +/obj/machinery/field/generator/proc/draw_power(draw = 0, failsafe = 0, obj/machinery/field/generator/G = null, obj/machinery/field/generator/last = null) + if((G && (G == src)) || (failsafe >= 8))//Loopin, set fail + return 0 + else + failsafe++ + + if(power >= draw)//We have enough power + power -= draw + return 1 + + else//Need more power + draw -= power + power = 0 + for(var/CG in connected_gens) + var/obj/machinery/field/generator/FG = CG + if(FG == last)//We just asked you + continue + if(G)//Another gen is askin for power and we dont have it + if(FG.draw_power(draw,failsafe,G,src))//Can you take the load + return 1 + else + return 0 + else//We are askin another for power + if(FG.draw_power(draw,failsafe,src,src)) + return 1 + else + return 0 + + +/obj/machinery/field/generator/proc/start_fields() + if(state != FG_WELDED || !anchored) + turn_off() + return + spawn(1) + setup_field(1) + spawn(2) + setup_field(2) + spawn(3) + setup_field(4) + spawn(4) + setup_field(8) + spawn(5) + active = FG_ONLINE + + +/obj/machinery/field/generator/proc/setup_field(NSEW) + var/turf/T = loc + if(!istype(T)) + return 0 + + var/obj/machinery/field/generator/G = null + var/steps = 0 + if(!NSEW)//Make sure its ran right + return 0 + for(var/dist in 0 to 7) // checks out to 8 tiles away for another generator + T = get_step(T, NSEW) + if(T.density)//We cant shoot a field though this + return 0 + + G = locate(/obj/machinery/field/generator) in T + if(G) + steps -= 1 + if(!G.active) + return 0 + break + + for(var/TC in T.contents) + var/atom/A = TC + if(ismob(A)) + continue + if(A.density) + return 0 + + steps++ + + if(!G) + return 0 + + T = loc + for(var/dist in 0 to steps) // creates each field tile + var/field_dir = get_dir(T,get_step(G.loc, NSEW)) + T = get_step(T, NSEW) + if(!locate(/obj/machinery/field/containment) in T) + var/obj/machinery/field/containment/CF = new/obj/machinery/field/containment() + CF.set_master(src,G) + CF.loc = T + CF.dir = field_dir + fields += CF + G.fields += CF + for(var/mob/living/L in T) + CF.Crossed(L, null) + + connected_gens |= G + G.connected_gens |= src + update_icon() + + +/obj/machinery/field/generator/proc/cleanup() + clean_up = 1 + for(var/F in fields) + qdel(F) + + for(var/CG in connected_gens) + var/obj/machinery/field/generator/FG = CG + FG.connected_gens -= src + if(!FG.clean_up)//Makes the other gens clean up as well + FG.cleanup() + connected_gens -= FG + clean_up = 0 + update_icon() + + //This is here to help fight the "hurr durr, release singulo cos nobody will notice before the + //singulo eats the evidence". It's not fool-proof but better than nothing. + //I want to avoid using global variables. + spawn(1) + var/temp = 1 //stops spam + for(var/obj/singularity/O in GLOB.singularities) + if(O.last_warning && temp) + if((world.time - O.last_warning) > 50) //to stop message-spam + temp = 0 + message_admins("A singulo exists and a containment field has failed. Location: [get_area(src)] (JMP)",1) + investigate_log("has failed whilst a singulo exists.","singulo") + O.last_warning = world.time + +/obj/machinery/field/generator/shock_field(mob/living/user) + if(fields.len) + ..() + +/obj/machinery/field/generator/bump_field(atom/movable/AM as mob|obj) + if(fields.len) + ..() + +#undef FG_UNSECURED +#undef FG_SECURED +#undef FG_WELDED + +#undef FG_OFFLINE +#undef FG_CHARGING +#undef FG_ONLINE diff --git a/code/modules/power/singularity/generator.dm b/code/modules/power/singularity/generator.dm index c5494f372e7..9bed8105a3e 100644 --- a/code/modules/power/singularity/generator.dm +++ b/code/modules/power/singularity/generator.dm @@ -1,38 +1,38 @@ -/////SINGULARITY SPAWNER -/obj/machinery/the_singularitygen - name = "Gravitational Singularity Generator" - desc = "An odd device which produces a Gravitational Singularity when set up." - icon = 'icons/obj/singularity.dmi' - icon_state = "TheSingGen" - anchored = 0 - density = 1 - use_power = NO_POWER_USE - resistance_flags = FIRE_PROOF - var/energy = 0 - var/creation_type = /obj/singularity - -/obj/machinery/the_singularitygen/process() - var/turf/T = get_turf(src) - if(src.energy >= 200) - message_admins("A [creation_type] has been created at [x], [y], [z] (JMP)") - investigate_log("A [creation_type] has been created at [x], [y], [z]","singulo") - - var/obj/singularity/S = new creation_type(T, 50) - transfer_fingerprints_to(S) - if(src) qdel(src) - -/obj/machinery/the_singularitygen/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/wrench)) - anchored = !anchored - playsound(src.loc, W.usesound, 75, 1) - if(anchored) - user.visible_message("[user.name] secures [src.name] to the floor.", \ - "You secure the [src.name] to the floor.", \ - "You hear a ratchet") - src.add_hiddenprint(user) - else - user.visible_message("[user.name] unsecures [src.name] from the floor.", \ - "You unsecure the [src.name] from the floor.", \ - "You hear a ratchet") - return - return ..() +/////SINGULARITY SPAWNER +/obj/machinery/the_singularitygen + name = "Gravitational Singularity Generator" + desc = "An odd device which produces a Gravitational Singularity when set up." + icon = 'icons/obj/singularity.dmi' + icon_state = "TheSingGen" + anchored = 0 + density = 1 + use_power = NO_POWER_USE + resistance_flags = FIRE_PROOF + var/energy = 0 + var/creation_type = /obj/singularity + +/obj/machinery/the_singularitygen/process() + var/turf/T = get_turf(src) + if(src.energy >= 200) + message_admins("A [creation_type] has been created at [x], [y], [z] (JMP)") + investigate_log("A [creation_type] has been created at [x], [y], [z]","singulo") + + var/obj/singularity/S = new creation_type(T, 50) + transfer_fingerprints_to(S) + if(src) qdel(src) + +/obj/machinery/the_singularitygen/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/wrench)) + anchored = !anchored + playsound(src.loc, W.usesound, 75, 1) + if(anchored) + user.visible_message("[user.name] secures [src.name] to the floor.", \ + "You secure the [src.name] to the floor.", \ + "You hear a ratchet") + src.add_hiddenprint(user) + else + user.visible_message("[user.name] unsecures [src.name] from the floor.", \ + "You unsecure the [src.name] from the floor.", \ + "You hear a ratchet") + return + return ..() diff --git a/code/modules/power/singularity/investigate.dm b/code/modules/power/singularity/investigate.dm index 6e22b1ad118..43e8c9f8a8b 100644 --- a/code/modules/power/singularity/investigate.dm +++ b/code/modules/power/singularity/investigate.dm @@ -1,4 +1,4 @@ /area/engine/engineering/power_alert(var/alarming) if(alarming) investigate_log("has a power alarm!","singulo") - ..() \ No newline at end of file + ..() diff --git a/code/modules/power/singularity/particle_accelerator/particle.dm b/code/modules/power/singularity/particle_accelerator/particle.dm index c7fcfcda5e7..e409ccc7602 100644 --- a/code/modules/power/singularity/particle_accelerator/particle.dm +++ b/code/modules/power/singularity/particle_accelerator/particle.dm @@ -1,59 +1,59 @@ -/obj/effect/accelerated_particle - name = "Accelerated Particles" - desc = "Small things moving very fast." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "particle" - anchored = TRUE - density = FALSE - var/movement_range = 10 - var/energy = 10 - var/speed = 1 - -/obj/effect/accelerated_particle/weak - movement_range = 8 - energy = 5 - -/obj/effect/accelerated_particle/strong - movement_range = 15 - energy = 15 - -/obj/effect/accelerated_particle/powerful - movement_range = 20 - energy = 50 - - -/obj/effect/accelerated_particle/Initialize(loc) - . = ..() - addtimer(CALLBACK(src, .proc/propagate), 1) - RegisterSignal(src, COMSIG_CROSSED_MOVABLE, .proc/try_irradiate) - RegisterSignal(src, COMSIG_MOVABLE_CROSSED, .proc/try_irradiate) - QDEL_IN(src, movement_range) - -/obj/effect/accelerated_particle/proc/try_irradiate(src, atom/A) - if(isliving(A)) - var/mob/living/L = A - L.apply_effect((energy * 6), IRRADIATE, 0) - else if(istype(A, /obj/machinery/the_singularitygen)) - var/obj/machinery/the_singularitygen/S = A - S.energy += energy - else if(istype(A, /obj/structure/blob)) - var/obj/structure/blob/B = A - B.take_damage(energy * 0.6) - movement_range = 0 - -/obj/effect/accelerated_particle/Bump(obj/singularity/S) - if(!istype(S)) - return ..() - S.energy += energy - - -/obj/effect/accelerated_particle/ex_act(severity) - qdel(src) - -/obj/effect/accelerated_particle/singularity_pull() - return - -/obj/effect/accelerated_particle/proc/propagate() - addtimer(CALLBACK(src, .proc/propagate), 1) - if(!step(src,dir)) - forceMove(get_step(src, dir)) +/obj/effect/accelerated_particle + name = "Accelerated Particles" + desc = "Small things moving very fast." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "particle" + anchored = TRUE + density = FALSE + var/movement_range = 10 + var/energy = 10 + var/speed = 1 + +/obj/effect/accelerated_particle/weak + movement_range = 8 + energy = 5 + +/obj/effect/accelerated_particle/strong + movement_range = 15 + energy = 15 + +/obj/effect/accelerated_particle/powerful + movement_range = 20 + energy = 50 + + +/obj/effect/accelerated_particle/Initialize(loc) + . = ..() + addtimer(CALLBACK(src, .proc/propagate), 1) + RegisterSignal(src, COMSIG_CROSSED_MOVABLE, .proc/try_irradiate) + RegisterSignal(src, COMSIG_MOVABLE_CROSSED, .proc/try_irradiate) + QDEL_IN(src, movement_range) + +/obj/effect/accelerated_particle/proc/try_irradiate(src, atom/A) + if(isliving(A)) + var/mob/living/L = A + L.apply_effect((energy * 6), IRRADIATE, 0) + else if(istype(A, /obj/machinery/the_singularitygen)) + var/obj/machinery/the_singularitygen/S = A + S.energy += energy + else if(istype(A, /obj/structure/blob)) + var/obj/structure/blob/B = A + B.take_damage(energy * 0.6) + movement_range = 0 + +/obj/effect/accelerated_particle/Bump(obj/singularity/S) + if(!istype(S)) + return ..() + S.energy += energy + + +/obj/effect/accelerated_particle/ex_act(severity) + qdel(src) + +/obj/effect/accelerated_particle/singularity_pull() + return + +/obj/effect/accelerated_particle/proc/propagate() + addtimer(CALLBACK(src, .proc/propagate), 1) + if(!step(src,dir)) + forceMove(get_step(src, dir)) diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm index 059f5e78b7b..20113d61a35 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm @@ -1,360 +1,360 @@ -/*Composed of 7 parts -3 Particle emitters -proc -emit_particle() - -1 power box -the only part of this thing that uses power, can hack to mess with the pa/make it better - -1 fuel chamber -contains procs for mixing gas and whatever other fuel it uses -mix_gas() - -1 gas holder WIP -acts like a tank valve on the ground that you wrench gas tanks onto -proc -extract_gas() -return_gas() -attach_tank() -remove_tank() -get_available_mix() - -1 End Cap - -1 Control computer -interface for the pa, acts like a computer with an html menu for diff parts and a status report -all other parts contain only a ref to this -a /machine/, tells the others to do work -contains ref for all parts -proc -process() -check_build() - -Setup map - |EC| -CC|FC| - |PB| -PE|PE|PE - - -Icon Addemdum -Icon system is much more robust, and the icons are all variable based. -Each part has a reference string, powered, strength, and contruction values. -Using this the update_icon() proc is simplified a bit (using for absolutely was problematic with naming), -so the icon_state comes out be: -"[reference][strength]", with a switch controlling construction_states and ensuring that it doesn't -power on while being contructed, and all these variables are set by the computer through it's scan list -Essential order of the icons: -Standard - [reference] -Wrenched - [reference] -Wired - [reference]w -Closed - [reference]c -Powered - [reference]p[strength] -Strength being set by the computer and a null strength (Computer is powered off or inactive) returns a 'null', counting as empty -So, hopefully this is helpful if any more icons are to be added/changed/wondering what the hell is going on here - -*/ -#define ACCELERATOR_UNWRENCHED 0 -#define ACCELERATOR_WRENCHED 1 -#define ACCELERATOR_WIRED 2 -#define ACCELERATOR_READY 3 - -/obj/structure/particle_accelerator - name = "Particle Accelerator" - desc = "Part of a Particle Accelerator." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "none" - anchored = 0 - density = 1 - max_integrity = 500 - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 80) - var/obj/machinery/particle_accelerator/control_box/master = null - var/construction_state = 0 - var/reference = null - var/powered = 0 - var/strength = null - var/desc_holder = null - -/obj/structure/particle_accelerator/Destroy() - construction_state = 0 - if(master) - master.part_scan() - return ..() - -/obj/structure/particle_accelerator/end_cap - name = "Alpha Particle Generation Array" - desc_holder = "This is where Alpha particles are generated from \[REDACTED\]" - icon_state = "end_cap" - reference = "end_cap" - -/obj/structure/particle_accelerator/update_icon() - ..() - return - - -/obj/structure/particle_accelerator/verb/rotate() - set name = "Rotate Clockwise" - set category = "Object" - set src in oview(1) - - if(usr.stat || !usr.canmove || usr.restrained()) - return - if(anchored) - to_chat(usr, "It is fastened to the floor!") - return 0 - dir = turn(dir, 270) - return 1 - -/obj/structure/particle_accelerator/AltClick(mob/user) - if(user.incapacitated()) - to_chat(user, "You can't do that right now!") - return - if(!Adjacent(user)) - return - rotate() - -/obj/structure/particle_accelerator/verb/rotateccw() - set name = "Rotate Counter Clockwise" - set category = "Object" - set src in oview(1) - - if(usr.stat || !usr.canmove || usr.restrained()) - return - if(anchored) - to_chat(usr, "It is fastened to the floor!") - return 0 - dir = turn(dir, 90) - return 1 - -/obj/structure/particle_accelerator/examine(mob/user) - switch(construction_state) - if(0) - desc = text("A [name], looks like it's not attached to the flooring") - if(1) - desc = text("A [name], it is missing some cables") - if(2) - desc = text("A [name], the panel is open") - if(3) - desc = text("The [name] is assembled") - if(powered) - desc = desc_holder - . = ..() - -/obj/structure/particle_accelerator/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - new /obj/item/stack/sheet/metal (loc, 5) - qdel(src) - -/obj/structure/particle_accelerator/Move() - . = ..() - if(master && master.active) - master.toggle_power() - investigate_log("was moved whilst active; it powered down.","singulo") - -/obj/machinery/particle_accelerator/control_box/blob_act(obj/structure/blob/B) - if(prob(50)) - qdel(src) - -/obj/structure/particle_accelerator/update_icon() - switch(construction_state) - if(0,1) - icon_state="[reference]" - if(2) - icon_state="[reference]w" - if(3) - if(powered) - icon_state="[reference]p[strength]" - else - icon_state="[reference]c" - return - -/obj/structure/particle_accelerator/proc/update_state() - if(master) - master.update_state() - return 0 - - -/obj/structure/particle_accelerator/proc/report_ready(var/obj/O) - if(O && (O == master)) - if(construction_state >= 3) - return 1 - return 0 - - -/obj/structure/particle_accelerator/proc/report_master() - if(master) - return master - return 0 - - -/obj/structure/particle_accelerator/proc/connect_master(var/obj/O) - if(O && istype(O,/obj/machinery/particle_accelerator/control_box)) - if(O.dir == dir) - master = O - return 1 - return 0 - -/obj/structure/particle_accelerator/attackby(obj/item/W, mob/user, params) - if(!iscoil(W)) - return ..() - if(construction_state == ACCELERATOR_WRENCHED) - var/obj/item/stack/cable_coil/C = W - if(C.use(1)) - playsound(loc, C.usesound, 50, 1) - user.visible_message("[user.name] adds wires to the [name].", \ - "You add some wires.") - construction_state = ACCELERATOR_WIRED - update_icon() - -/obj/structure/particle_accelerator/screwdriver_act(mob/user, obj/item/I) - if(construction_state != ACCELERATOR_WIRED && construction_state != ACCELERATOR_READY) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(construction_state == ACCELERATOR_WIRED) - SCREWDRIVER_CLOSE_PANEL_MESSAGE - construction_state = ACCELERATOR_READY - - else - construction_state = ACCELERATOR_WIRED - SCREWDRIVER_OPEN_PANEL_MESSAGE - update_state() - update_icon() - -/obj/structure/particle_accelerator/wirecutter_act(mob/user, obj/item/I) - if(construction_state != ACCELERATOR_WIRED) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - WIRECUTTER_SNIP_MESSAGE - construction_state = ACCELERATOR_WRENCHED - -/obj/structure/particle_accelerator/wrench_act(mob/user, obj/item/I) - if(construction_state != ACCELERATOR_UNWRENCHED && construction_state != ACCELERATOR_WRENCHED) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(construction_state == ACCELERATOR_UNWRENCHED) - anchored = TRUE - WRENCH_ANCHOR_MESSAGE - construction_state = ACCELERATOR_WRENCHED - else - anchored = FALSE - WRENCH_UNANCHOR_MESSAGE - construction_state = ACCELERATOR_UNWRENCHED - update_icon() - - -/obj/machinery/particle_accelerator - name = "Particle Accelerator" - desc = "Part of a Particle Accelerator." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "none" - anchored = 0 - density = 1 - use_power = NO_POWER_USE - idle_power_usage = 0 - active_power_usage = 0 - var/construction_state = 0 - var/active = 0 - var/reference = null - var/powered = null - var/strength = 0 - var/desc_holder = null - - -/obj/machinery/particle_accelerator/verb/rotate() - set name = "Rotate Clockwise" - set category = "Object" - set src in oview(1) - - if(usr.stat || !usr.canmove || usr.restrained()) - return - if(anchored) - to_chat(usr, "It is fastened to the floor!") - return 0 - dir = turn(dir, 270) - return 1 - -/obj/machinery/particle_accelerator/verb/rotateccw() - set name = "Rotate Counter-Clockwise" - set category = "Object" - set src in oview(1) - - if(usr.stat || !usr.canmove || usr.restrained()) - return - if(anchored) - to_chat(usr, "It is fastened to the floor!") - return 0 - dir = turn(dir, 90) - return 1 - -/obj/machinery/particle_accelerator/update_icon() - return - -/obj/machinery/particle_accelerator/attackby(obj/item/W, mob/user, params) - if(!iscoil(W)) - return ..() - if(construction_state == ACCELERATOR_WRENCHED) - var/obj/item/stack/cable_coil/C = W - if(C.use(1)) - playsound(loc, C.usesound, 50, 1) - user.visible_message("[user.name] adds wires to the [name].", \ - "You add some wires.") - construction_state = ACCELERATOR_WIRED - update_icon() - -/obj/machinery/particle_accelerator/screwdriver_act(mob/user, obj/item/I) - if(construction_state != ACCELERATOR_WIRED && construction_state != ACCELERATOR_READY) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(construction_state == ACCELERATOR_WIRED) - SCREWDRIVER_CLOSE_PANEL_MESSAGE - construction_state = ACCELERATOR_READY - use_power = IDLE_POWER_USE - else - construction_state = ACCELERATOR_WIRED - SCREWDRIVER_OPEN_PANEL_MESSAGE - use_power = NO_POWER_USE - update_state() - update_icon() - -/obj/machinery/particle_accelerator/wirecutter_act(mob/user, obj/item/I) - if(construction_state != ACCELERATOR_WIRED) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - WIRECUTTER_SNIP_MESSAGE - construction_state = ACCELERATOR_WRENCHED - -/obj/machinery/particle_accelerator/wrench_act(mob/user, obj/item/I) - if(construction_state != ACCELERATOR_UNWRENCHED && construction_state != ACCELERATOR_WRENCHED) - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(construction_state == ACCELERATOR_UNWRENCHED) - anchored = TRUE - WRENCH_ANCHOR_MESSAGE - construction_state = ACCELERATOR_WRENCHED - else - anchored = FALSE - WRENCH_UNANCHOR_MESSAGE - construction_state = ACCELERATOR_UNWRENCHED - update_icon() - - -/obj/machinery/particle_accelerator/proc/update_state() - return 0 - - -#undef ACCELERATOR_UNWRENCHED -#undef ACCELERATOR_WRENCHED -#undef ACCELERATOR_WIRED -#undef ACCELERATOR_READY +/*Composed of 7 parts +3 Particle emitters +proc +emit_particle() + +1 power box +the only part of this thing that uses power, can hack to mess with the pa/make it better + +1 fuel chamber +contains procs for mixing gas and whatever other fuel it uses +mix_gas() + +1 gas holder WIP +acts like a tank valve on the ground that you wrench gas tanks onto +proc +extract_gas() +return_gas() +attach_tank() +remove_tank() +get_available_mix() + +1 End Cap + +1 Control computer +interface for the pa, acts like a computer with an html menu for diff parts and a status report +all other parts contain only a ref to this +a /machine/, tells the others to do work +contains ref for all parts +proc +process() +check_build() + +Setup map + |EC| +CC|FC| + |PB| +PE|PE|PE + + +Icon Addemdum +Icon system is much more robust, and the icons are all variable based. +Each part has a reference string, powered, strength, and contruction values. +Using this the update_icon() proc is simplified a bit (using for absolutely was problematic with naming), +so the icon_state comes out be: +"[reference][strength]", with a switch controlling construction_states and ensuring that it doesn't +power on while being contructed, and all these variables are set by the computer through it's scan list +Essential order of the icons: +Standard - [reference] +Wrenched - [reference] +Wired - [reference]w +Closed - [reference]c +Powered - [reference]p[strength] +Strength being set by the computer and a null strength (Computer is powered off or inactive) returns a 'null', counting as empty +So, hopefully this is helpful if any more icons are to be added/changed/wondering what the hell is going on here + +*/ +#define ACCELERATOR_UNWRENCHED 0 +#define ACCELERATOR_WRENCHED 1 +#define ACCELERATOR_WIRED 2 +#define ACCELERATOR_READY 3 + +/obj/structure/particle_accelerator + name = "Particle Accelerator" + desc = "Part of a Particle Accelerator." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "none" + anchored = 0 + density = 1 + max_integrity = 500 + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 80) + var/obj/machinery/particle_accelerator/control_box/master = null + var/construction_state = 0 + var/reference = null + var/powered = 0 + var/strength = null + var/desc_holder = null + +/obj/structure/particle_accelerator/Destroy() + construction_state = 0 + if(master) + master.part_scan() + return ..() + +/obj/structure/particle_accelerator/end_cap + name = "Alpha Particle Generation Array" + desc_holder = "This is where Alpha particles are generated from \[REDACTED\]" + icon_state = "end_cap" + reference = "end_cap" + +/obj/structure/particle_accelerator/update_icon() + ..() + return + + +/obj/structure/particle_accelerator/verb/rotate() + set name = "Rotate Clockwise" + set category = "Object" + set src in oview(1) + + if(usr.stat || !usr.canmove || usr.restrained()) + return + if(anchored) + to_chat(usr, "It is fastened to the floor!") + return 0 + dir = turn(dir, 270) + return 1 + +/obj/structure/particle_accelerator/AltClick(mob/user) + if(user.incapacitated()) + to_chat(user, "You can't do that right now!") + return + if(!Adjacent(user)) + return + rotate() + +/obj/structure/particle_accelerator/verb/rotateccw() + set name = "Rotate Counter Clockwise" + set category = "Object" + set src in oview(1) + + if(usr.stat || !usr.canmove || usr.restrained()) + return + if(anchored) + to_chat(usr, "It is fastened to the floor!") + return 0 + dir = turn(dir, 90) + return 1 + +/obj/structure/particle_accelerator/examine(mob/user) + switch(construction_state) + if(0) + desc = text("A [name], looks like it's not attached to the flooring") + if(1) + desc = text("A [name], it is missing some cables") + if(2) + desc = text("A [name], the panel is open") + if(3) + desc = text("The [name] is assembled") + if(powered) + desc = desc_holder + . = ..() + +/obj/structure/particle_accelerator/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + new /obj/item/stack/sheet/metal (loc, 5) + qdel(src) + +/obj/structure/particle_accelerator/Move() + . = ..() + if(master && master.active) + master.toggle_power() + investigate_log("was moved whilst active; it powered down.","singulo") + +/obj/machinery/particle_accelerator/control_box/blob_act(obj/structure/blob/B) + if(prob(50)) + qdel(src) + +/obj/structure/particle_accelerator/update_icon() + switch(construction_state) + if(0,1) + icon_state="[reference]" + if(2) + icon_state="[reference]w" + if(3) + if(powered) + icon_state="[reference]p[strength]" + else + icon_state="[reference]c" + return + +/obj/structure/particle_accelerator/proc/update_state() + if(master) + master.update_state() + return 0 + + +/obj/structure/particle_accelerator/proc/report_ready(var/obj/O) + if(O && (O == master)) + if(construction_state >= 3) + return 1 + return 0 + + +/obj/structure/particle_accelerator/proc/report_master() + if(master) + return master + return 0 + + +/obj/structure/particle_accelerator/proc/connect_master(var/obj/O) + if(O && istype(O,/obj/machinery/particle_accelerator/control_box)) + if(O.dir == dir) + master = O + return 1 + return 0 + +/obj/structure/particle_accelerator/attackby(obj/item/W, mob/user, params) + if(!iscoil(W)) + return ..() + if(construction_state == ACCELERATOR_WRENCHED) + var/obj/item/stack/cable_coil/C = W + if(C.use(1)) + playsound(loc, C.usesound, 50, 1) + user.visible_message("[user.name] adds wires to the [name].", \ + "You add some wires.") + construction_state = ACCELERATOR_WIRED + update_icon() + +/obj/structure/particle_accelerator/screwdriver_act(mob/user, obj/item/I) + if(construction_state != ACCELERATOR_WIRED && construction_state != ACCELERATOR_READY) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(construction_state == ACCELERATOR_WIRED) + SCREWDRIVER_CLOSE_PANEL_MESSAGE + construction_state = ACCELERATOR_READY + + else + construction_state = ACCELERATOR_WIRED + SCREWDRIVER_OPEN_PANEL_MESSAGE + update_state() + update_icon() + +/obj/structure/particle_accelerator/wirecutter_act(mob/user, obj/item/I) + if(construction_state != ACCELERATOR_WIRED) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + WIRECUTTER_SNIP_MESSAGE + construction_state = ACCELERATOR_WRENCHED + +/obj/structure/particle_accelerator/wrench_act(mob/user, obj/item/I) + if(construction_state != ACCELERATOR_UNWRENCHED && construction_state != ACCELERATOR_WRENCHED) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(construction_state == ACCELERATOR_UNWRENCHED) + anchored = TRUE + WRENCH_ANCHOR_MESSAGE + construction_state = ACCELERATOR_WRENCHED + else + anchored = FALSE + WRENCH_UNANCHOR_MESSAGE + construction_state = ACCELERATOR_UNWRENCHED + update_icon() + + +/obj/machinery/particle_accelerator + name = "Particle Accelerator" + desc = "Part of a Particle Accelerator." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "none" + anchored = 0 + density = 1 + use_power = NO_POWER_USE + idle_power_usage = 0 + active_power_usage = 0 + var/construction_state = 0 + var/active = 0 + var/reference = null + var/powered = null + var/strength = 0 + var/desc_holder = null + + +/obj/machinery/particle_accelerator/verb/rotate() + set name = "Rotate Clockwise" + set category = "Object" + set src in oview(1) + + if(usr.stat || !usr.canmove || usr.restrained()) + return + if(anchored) + to_chat(usr, "It is fastened to the floor!") + return 0 + dir = turn(dir, 270) + return 1 + +/obj/machinery/particle_accelerator/verb/rotateccw() + set name = "Rotate Counter-Clockwise" + set category = "Object" + set src in oview(1) + + if(usr.stat || !usr.canmove || usr.restrained()) + return + if(anchored) + to_chat(usr, "It is fastened to the floor!") + return 0 + dir = turn(dir, 90) + return 1 + +/obj/machinery/particle_accelerator/update_icon() + return + +/obj/machinery/particle_accelerator/attackby(obj/item/W, mob/user, params) + if(!iscoil(W)) + return ..() + if(construction_state == ACCELERATOR_WRENCHED) + var/obj/item/stack/cable_coil/C = W + if(C.use(1)) + playsound(loc, C.usesound, 50, 1) + user.visible_message("[user.name] adds wires to the [name].", \ + "You add some wires.") + construction_state = ACCELERATOR_WIRED + update_icon() + +/obj/machinery/particle_accelerator/screwdriver_act(mob/user, obj/item/I) + if(construction_state != ACCELERATOR_WIRED && construction_state != ACCELERATOR_READY) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(construction_state == ACCELERATOR_WIRED) + SCREWDRIVER_CLOSE_PANEL_MESSAGE + construction_state = ACCELERATOR_READY + use_power = IDLE_POWER_USE + else + construction_state = ACCELERATOR_WIRED + SCREWDRIVER_OPEN_PANEL_MESSAGE + use_power = NO_POWER_USE + update_state() + update_icon() + +/obj/machinery/particle_accelerator/wirecutter_act(mob/user, obj/item/I) + if(construction_state != ACCELERATOR_WIRED) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + WIRECUTTER_SNIP_MESSAGE + construction_state = ACCELERATOR_WRENCHED + +/obj/machinery/particle_accelerator/wrench_act(mob/user, obj/item/I) + if(construction_state != ACCELERATOR_UNWRENCHED && construction_state != ACCELERATOR_WRENCHED) + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(construction_state == ACCELERATOR_UNWRENCHED) + anchored = TRUE + WRENCH_ANCHOR_MESSAGE + construction_state = ACCELERATOR_WRENCHED + else + anchored = FALSE + WRENCH_UNANCHOR_MESSAGE + construction_state = ACCELERATOR_UNWRENCHED + update_icon() + + +/obj/machinery/particle_accelerator/proc/update_state() + return 0 + + +#undef ACCELERATOR_UNWRENCHED +#undef ACCELERATOR_WRENCHED +#undef ACCELERATOR_WIRED +#undef ACCELERATOR_READY diff --git a/code/modules/power/singularity/particle_accelerator/particle_chamber.dm b/code/modules/power/singularity/particle_accelerator/particle_chamber.dm index 411c039d480..c3c058a5289 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_chamber.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_chamber.dm @@ -1,10 +1,10 @@ -/obj/structure/particle_accelerator/fuel_chamber - name = "EM Acceleration Chamber" - desc_holder = "This part is where the Alpha particles are accelerated to radical speeds." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "fuel_chamber" - reference = "fuel_chamber" - -/obj/structure/particle_accelerator/fuel_chamber/update_icon() - ..() - return \ No newline at end of file +/obj/structure/particle_accelerator/fuel_chamber + name = "EM Acceleration Chamber" + desc_holder = "This part is where the Alpha particles are accelerated to radical speeds." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "fuel_chamber" + reference = "fuel_chamber" + +/obj/structure/particle_accelerator/fuel_chamber/update_icon() + ..() + return diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm index 80de53fe22d..f1a7f130592 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_control.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm @@ -1,279 +1,279 @@ -/obj/machinery/particle_accelerator/control_box - name = "Particle Accelerator Control Console" - desc = "This part controls the density of the particles." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "control_box" - reference = "control_box" - anchored = 0 - density = 1 - use_power = NO_POWER_USE - idle_power_usage = 500 - active_power_usage = 10000 - construction_state = 0 - active = 0 - dir = 1 - var/strength_upper_limit = 2 - var/interface_control = 1 - var/list/obj/structure/particle_accelerator/connected_parts - var/assembled = 0 - var/parts = null - var/datum/wires/particle_acc/control_box/wires = null - -/obj/machinery/particle_accelerator/control_box/Initialize(mapload) - . = ..() - wires = new(src) - connected_parts = list() - update_icon() - -/obj/machinery/particle_accelerator/control_box/Destroy() - if(active) - toggle_power() - QDEL_NULL(wires) - return ..() - -/obj/machinery/particle_accelerator/control_box/attack_ghost(user as mob) - return attack_hand(user) - -/obj/machinery/particle_accelerator/control_box/attack_hand(mob/user as mob) - if(construction_state >= 3) - interact(user) - else if(construction_state == 2) // Wires exposed - wires.Interact(user) - -/obj/machinery/particle_accelerator/control_box/update_state() - if(construction_state < 3) - use_power = NO_POWER_USE - assembled = 0 - active = 0 - for(var/obj/structure/particle_accelerator/part in connected_parts) - part.strength = null - part.powered = 0 - part.update_icon() - connected_parts = list() - return - if(!part_scan()) - use_power = IDLE_POWER_USE - active = 0 - connected_parts = list() - - return - -/obj/machinery/particle_accelerator/control_box/update_icon() - if(active) - icon_state = "[reference]p[strength]" - else - if(stat & NOPOWER) - icon_state = "[reference]w" - return - else if(use_power && assembled) - icon_state = "[reference]p" - else - switch(construction_state) - if(0) - icon_state = "[reference]" - if(1) - icon_state = "[reference]" - if(2) - icon_state = "[reference]w" - else - icon_state = "[reference]c" - return - -/obj/machinery/particle_accelerator/control_box/Topic(href, href_list) - if(..(href, href_list)) - return 1 - - if(!interface_control) - to_chat(usr, "ERROR: Request timed out. Check wire contacts.") - return - - if(href_list["close"]) - usr << browse(null, "window=pacontrol") - usr.unset_machine() - return - if(href_list["togglep"]) - if(!wires.IsIndexCut(PARTICLE_TOGGLE_WIRE)) - toggle_power() - - else if(href_list["scan"]) - part_scan() - - else if(href_list["strengthup"]) - if(!wires.IsIndexCut(PARTICLE_STRENGTH_WIRE)) - add_strength() - - else if(href_list["strengthdown"]) - if(!wires.IsIndexCut(PARTICLE_STRENGTH_WIRE)) - remove_strength() - - updateDialog() - update_icon() - return - - -/obj/machinery/particle_accelerator/control_box/proc/strength_change() - for(var/obj/structure/particle_accelerator/part in connected_parts) - part.strength = strength - part.update_icon() - -/obj/machinery/particle_accelerator/control_box/proc/add_strength(var/s) - if(assembled) - strength++ - if(strength > strength_upper_limit) - strength = strength_upper_limit - else - message_admins("PA Control Computer increased to [strength] by [key_name_admin(usr)] in ([x],[y],[z] - JMP)",0,1) - log_game("PA Control Computer increased to [strength] by [key_name(usr)] in ([x],[y],[z])") - investigate_log("increased to [strength] by [key_name(usr)]","singulo") - use_log += text("\[[time_stamp()]\] [usr.name] ([key_name(usr)]) has increased the PA Control Computer to [strength].") - - investigate_log("increased to [strength] by [usr.key]","singulo") - strength_change() - -/obj/machinery/particle_accelerator/control_box/proc/remove_strength(var/s) - if(assembled) - strength-- - if(strength < 0) - strength = 0 - else - message_admins("PA Control Computer decreased to [strength] by [key_name_admin(usr)] in ([x],[y],[z] - JMP)",0,1) - log_game("PA Control Computer decreased to [strength] by [key_name(usr)] in ([x],[y],[z])") - investigate_log("decreased to [strength] by [key_name(usr)]","singulo") - use_log += text("\[[time_stamp()]\] [usr.name] ([key_name(usr)]) has decreased the PA Control Computer to [strength].") - - strength_change() - -/obj/machinery/particle_accelerator/control_box/power_change() - ..() - if(stat & NOPOWER) - active = 0 - use_power = NO_POWER_USE - else if(!stat && construction_state <= 3) - use_power = IDLE_POWER_USE - update_icon() - - if((stat & NOPOWER) || (!stat && construction_state <= 3)) //Only update the part icons if something's changed (i.e. any of the above condition sets are met). - for(var/obj/structure/particle_accelerator/part in connected_parts) - part.strength = null - part.powered = 0 - part.update_icon() - return - - -/obj/machinery/particle_accelerator/control_box/process() - if(active) - //a part is missing! - if(length(connected_parts) < 6) - investigate_log("lost a connected part; It powered down.","singulo") - toggle_power() - return - //emit some particles - for(var/obj/structure/particle_accelerator/particle_emitter/PE in connected_parts) - if(PE) - PE.emit_particle(strength) - return - - -/obj/machinery/particle_accelerator/control_box/proc/part_scan() - for(var/obj/structure/particle_accelerator/fuel_chamber/F in orange(1,src)) - dir = F.dir - connected_parts = list() - var/tally = 0 - var/ldir = turn(dir,-90) - var/rdir = turn(dir,90) - var/odir = turn(dir,180) - var/turf/T = loc - T = get_step(T,rdir) - if(check_part(T,/obj/structure/particle_accelerator/fuel_chamber)) - tally++ - T = get_step(T,odir) - if(check_part(T,/obj/structure/particle_accelerator/end_cap)) - tally++ - T = get_step(T,dir) - T = get_step(T,dir) - if(check_part(T,/obj/structure/particle_accelerator/power_box)) - tally++ - T = get_step(T,dir) - if(check_part(T,/obj/structure/particle_accelerator/particle_emitter/center)) - tally++ - T = get_step(T,ldir) - if(check_part(T,/obj/structure/particle_accelerator/particle_emitter/left)) - tally++ - T = get_step(T,rdir) - T = get_step(T,rdir) - if(check_part(T,/obj/structure/particle_accelerator/particle_emitter/right)) - tally++ - if(tally >= 6) - assembled = 1 - return 1 - else - assembled = 0 - return 0 - - -/obj/machinery/particle_accelerator/control_box/proc/check_part(var/turf/T, var/type) - if(!(T)||!(type)) - return 0 - var/obj/structure/particle_accelerator/PA = locate(/obj/structure/particle_accelerator) in T - if(istype(PA, type)) - if(PA.connect_master(src)) - if(PA.report_ready(src)) - connected_parts.Add(PA) - return 1 - return 0 - - -/obj/machinery/particle_accelerator/control_box/proc/toggle_power() - active = !active - investigate_log("turned [active?"ON":"OFF"] by [usr ? usr.key : "outside forces"]","singulo") - if(active) - msg_admin_attack("PA Control Computer turned ON by [key_name_admin(usr)]", ATKLOG_FEW) - log_game("PA Control Computer turned ON by [key_name(usr)] in ([x],[y],[z])") - use_log += text("\[[time_stamp()]\] [key_name(usr)] has turned on the PA Control Computer.") - if(active) - use_power = ACTIVE_POWER_USE - for(var/obj/structure/particle_accelerator/part in connected_parts) - part.strength = strength - part.powered = 1 - part.update_icon() - else - use_power = IDLE_POWER_USE - for(var/obj/structure/particle_accelerator/part in connected_parts) - part.strength = null - part.powered = 0 - part.update_icon() - return 1 - - -/obj/machinery/particle_accelerator/control_box/interact(mob/user) - if(((get_dist(src, user) > 1) && !isobserver(user)) || (stat & (BROKEN|NOPOWER))) - if(!istype(user, /mob/living/silicon)) - user.unset_machine() - user << browse(null, "window=pacontrol") - return - user.set_machine(src) - - var/dat = "" - dat += "Close

        " - dat += "

        Status

        " - if(!assembled) - dat += "Unable to detect all parts!
        " - dat += "Run Scan

        " - else - dat += "All parts in place.

        " - dat += "Power:" - if(active) - dat += "On
        " - else - dat += "Off
        " - dat += "Toggle Power

        " - dat += "Particle Strength: [strength] " - dat += "--|++

        " - - //user << browse(dat, "window=pacontrol;size=420x500") - //onclose(user, "pacontrol") - var/datum/browser/popup = new(user, "pacontrol", name, 420, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - return +/obj/machinery/particle_accelerator/control_box + name = "Particle Accelerator Control Console" + desc = "This part controls the density of the particles." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "control_box" + reference = "control_box" + anchored = 0 + density = 1 + use_power = NO_POWER_USE + idle_power_usage = 500 + active_power_usage = 10000 + construction_state = 0 + active = 0 + dir = 1 + var/strength_upper_limit = 2 + var/interface_control = 1 + var/list/obj/structure/particle_accelerator/connected_parts + var/assembled = 0 + var/parts = null + var/datum/wires/particle_acc/control_box/wires = null + +/obj/machinery/particle_accelerator/control_box/Initialize(mapload) + . = ..() + wires = new(src) + connected_parts = list() + update_icon() + +/obj/machinery/particle_accelerator/control_box/Destroy() + if(active) + toggle_power() + QDEL_NULL(wires) + return ..() + +/obj/machinery/particle_accelerator/control_box/attack_ghost(user as mob) + return attack_hand(user) + +/obj/machinery/particle_accelerator/control_box/attack_hand(mob/user as mob) + if(construction_state >= 3) + interact(user) + else if(construction_state == 2) // Wires exposed + wires.Interact(user) + +/obj/machinery/particle_accelerator/control_box/update_state() + if(construction_state < 3) + use_power = NO_POWER_USE + assembled = 0 + active = 0 + for(var/obj/structure/particle_accelerator/part in connected_parts) + part.strength = null + part.powered = 0 + part.update_icon() + connected_parts = list() + return + if(!part_scan()) + use_power = IDLE_POWER_USE + active = 0 + connected_parts = list() + + return + +/obj/machinery/particle_accelerator/control_box/update_icon() + if(active) + icon_state = "[reference]p[strength]" + else + if(stat & NOPOWER) + icon_state = "[reference]w" + return + else if(use_power && assembled) + icon_state = "[reference]p" + else + switch(construction_state) + if(0) + icon_state = "[reference]" + if(1) + icon_state = "[reference]" + if(2) + icon_state = "[reference]w" + else + icon_state = "[reference]c" + return + +/obj/machinery/particle_accelerator/control_box/Topic(href, href_list) + if(..(href, href_list)) + return 1 + + if(!interface_control) + to_chat(usr, "ERROR: Request timed out. Check wire contacts.") + return + + if(href_list["close"]) + usr << browse(null, "window=pacontrol") + usr.unset_machine() + return + if(href_list["togglep"]) + if(!wires.IsIndexCut(PARTICLE_TOGGLE_WIRE)) + toggle_power() + + else if(href_list["scan"]) + part_scan() + + else if(href_list["strengthup"]) + if(!wires.IsIndexCut(PARTICLE_STRENGTH_WIRE)) + add_strength() + + else if(href_list["strengthdown"]) + if(!wires.IsIndexCut(PARTICLE_STRENGTH_WIRE)) + remove_strength() + + updateDialog() + update_icon() + return + + +/obj/machinery/particle_accelerator/control_box/proc/strength_change() + for(var/obj/structure/particle_accelerator/part in connected_parts) + part.strength = strength + part.update_icon() + +/obj/machinery/particle_accelerator/control_box/proc/add_strength(var/s) + if(assembled) + strength++ + if(strength > strength_upper_limit) + strength = strength_upper_limit + else + message_admins("PA Control Computer increased to [strength] by [key_name_admin(usr)] in ([x],[y],[z] - JMP)",0,1) + log_game("PA Control Computer increased to [strength] by [key_name(usr)] in ([x],[y],[z])") + investigate_log("increased to [strength] by [key_name(usr)]","singulo") + use_log += text("\[[time_stamp()]\] [usr.name] ([key_name(usr)]) has increased the PA Control Computer to [strength].") + + investigate_log("increased to [strength] by [usr.key]","singulo") + strength_change() + +/obj/machinery/particle_accelerator/control_box/proc/remove_strength(var/s) + if(assembled) + strength-- + if(strength < 0) + strength = 0 + else + message_admins("PA Control Computer decreased to [strength] by [key_name_admin(usr)] in ([x],[y],[z] - JMP)",0,1) + log_game("PA Control Computer decreased to [strength] by [key_name(usr)] in ([x],[y],[z])") + investigate_log("decreased to [strength] by [key_name(usr)]","singulo") + use_log += text("\[[time_stamp()]\] [usr.name] ([key_name(usr)]) has decreased the PA Control Computer to [strength].") + + strength_change() + +/obj/machinery/particle_accelerator/control_box/power_change() + ..() + if(stat & NOPOWER) + active = 0 + use_power = NO_POWER_USE + else if(!stat && construction_state <= 3) + use_power = IDLE_POWER_USE + update_icon() + + if((stat & NOPOWER) || (!stat && construction_state <= 3)) //Only update the part icons if something's changed (i.e. any of the above condition sets are met). + for(var/obj/structure/particle_accelerator/part in connected_parts) + part.strength = null + part.powered = 0 + part.update_icon() + return + + +/obj/machinery/particle_accelerator/control_box/process() + if(active) + //a part is missing! + if(length(connected_parts) < 6) + investigate_log("lost a connected part; It powered down.","singulo") + toggle_power() + return + //emit some particles + for(var/obj/structure/particle_accelerator/particle_emitter/PE in connected_parts) + if(PE) + PE.emit_particle(strength) + return + + +/obj/machinery/particle_accelerator/control_box/proc/part_scan() + for(var/obj/structure/particle_accelerator/fuel_chamber/F in orange(1,src)) + dir = F.dir + connected_parts = list() + var/tally = 0 + var/ldir = turn(dir,-90) + var/rdir = turn(dir,90) + var/odir = turn(dir,180) + var/turf/T = loc + T = get_step(T,rdir) + if(check_part(T,/obj/structure/particle_accelerator/fuel_chamber)) + tally++ + T = get_step(T,odir) + if(check_part(T,/obj/structure/particle_accelerator/end_cap)) + tally++ + T = get_step(T,dir) + T = get_step(T,dir) + if(check_part(T,/obj/structure/particle_accelerator/power_box)) + tally++ + T = get_step(T,dir) + if(check_part(T,/obj/structure/particle_accelerator/particle_emitter/center)) + tally++ + T = get_step(T,ldir) + if(check_part(T,/obj/structure/particle_accelerator/particle_emitter/left)) + tally++ + T = get_step(T,rdir) + T = get_step(T,rdir) + if(check_part(T,/obj/structure/particle_accelerator/particle_emitter/right)) + tally++ + if(tally >= 6) + assembled = 1 + return 1 + else + assembled = 0 + return 0 + + +/obj/machinery/particle_accelerator/control_box/proc/check_part(var/turf/T, var/type) + if(!(T)||!(type)) + return 0 + var/obj/structure/particle_accelerator/PA = locate(/obj/structure/particle_accelerator) in T + if(istype(PA, type)) + if(PA.connect_master(src)) + if(PA.report_ready(src)) + connected_parts.Add(PA) + return 1 + return 0 + + +/obj/machinery/particle_accelerator/control_box/proc/toggle_power() + active = !active + investigate_log("turned [active?"ON":"OFF"] by [usr ? usr.key : "outside forces"]","singulo") + if(active) + msg_admin_attack("PA Control Computer turned ON by [key_name_admin(usr)]", ATKLOG_FEW) + log_game("PA Control Computer turned ON by [key_name(usr)] in ([x],[y],[z])") + use_log += text("\[[time_stamp()]\] [key_name(usr)] has turned on the PA Control Computer.") + if(active) + use_power = ACTIVE_POWER_USE + for(var/obj/structure/particle_accelerator/part in connected_parts) + part.strength = strength + part.powered = 1 + part.update_icon() + else + use_power = IDLE_POWER_USE + for(var/obj/structure/particle_accelerator/part in connected_parts) + part.strength = null + part.powered = 0 + part.update_icon() + return 1 + + +/obj/machinery/particle_accelerator/control_box/interact(mob/user) + if(((get_dist(src, user) > 1) && !isobserver(user)) || (stat & (BROKEN|NOPOWER))) + if(!istype(user, /mob/living/silicon)) + user.unset_machine() + user << browse(null, "window=pacontrol") + return + user.set_machine(src) + + var/dat = "" + dat += "Close

        " + dat += "

        Status

        " + if(!assembled) + dat += "Unable to detect all parts!
        " + dat += "Run Scan

        " + else + dat += "All parts in place.

        " + dat += "Power:" + if(active) + dat += "On
        " + else + dat += "Off
        " + dat += "Toggle Power

        " + dat += "Particle Strength: [strength] " + dat += "--|++

        " + + //user << browse(dat, "window=pacontrol;size=420x500") + //onclose(user, "pacontrol") + var/datum/browser/popup = new(user, "pacontrol", name, 420, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + return diff --git a/code/modules/power/singularity/particle_accelerator/particle_emitter.dm b/code/modules/power/singularity/particle_accelerator/particle_emitter.dm index c2a1ccfa8da..a3a7d4b2cfc 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_emitter.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_emitter.dm @@ -1,48 +1,48 @@ -/obj/structure/particle_accelerator/particle_emitter - name = "EM Containment Grid" - desc_holder = "This part launches the Alpha particles. You might not want to stand near this end." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "none" - var/fire_delay = 50 - var/last_shot = 0 - -/obj/structure/particle_accelerator/particle_emitter/center - icon_state = "emitter_center" - reference = "emitter_center" - -/obj/structure/particle_accelerator/particle_emitter/left - icon_state = "emitter_left" - reference = "emitter_left" - -/obj/structure/particle_accelerator/particle_emitter/right - icon_state = "emitter_right" - reference = "emitter_right" - -/obj/structure/particle_accelerator/particle_emitter/update_icon() - ..() - return - -/obj/structure/particle_accelerator/particle_emitter/proc/set_delay(var/delay) - if(delay && delay >= 0) - fire_delay = delay - return 1 - return 0 - - -/obj/structure/particle_accelerator/particle_emitter/proc/emit_particle(strength = 0) - if((last_shot + fire_delay) <= world.time) - last_shot = world.time - var/turf/T = get_turf(src) - var/obj/effect/accelerated_particle/P - switch(strength) - if(0) - P = new/obj/effect/accelerated_particle/weak(T) - if(1) - P = new/obj/effect/accelerated_particle(T) - if(2) - P = new/obj/effect/accelerated_particle/strong(T) - if(3) - P = new/obj/effect/accelerated_particle/powerful(T) - P.setDir(dir) - return TRUE - return FALSE \ No newline at end of file +/obj/structure/particle_accelerator/particle_emitter + name = "EM Containment Grid" + desc_holder = "This part launches the Alpha particles. You might not want to stand near this end." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "none" + var/fire_delay = 50 + var/last_shot = 0 + +/obj/structure/particle_accelerator/particle_emitter/center + icon_state = "emitter_center" + reference = "emitter_center" + +/obj/structure/particle_accelerator/particle_emitter/left + icon_state = "emitter_left" + reference = "emitter_left" + +/obj/structure/particle_accelerator/particle_emitter/right + icon_state = "emitter_right" + reference = "emitter_right" + +/obj/structure/particle_accelerator/particle_emitter/update_icon() + ..() + return + +/obj/structure/particle_accelerator/particle_emitter/proc/set_delay(var/delay) + if(delay && delay >= 0) + fire_delay = delay + return 1 + return 0 + + +/obj/structure/particle_accelerator/particle_emitter/proc/emit_particle(strength = 0) + if((last_shot + fire_delay) <= world.time) + last_shot = world.time + var/turf/T = get_turf(src) + var/obj/effect/accelerated_particle/P + switch(strength) + if(0) + P = new/obj/effect/accelerated_particle/weak(T) + if(1) + P = new/obj/effect/accelerated_particle(T) + if(2) + P = new/obj/effect/accelerated_particle/strong(T) + if(3) + P = new/obj/effect/accelerated_particle/powerful(T) + P.setDir(dir) + return TRUE + return FALSE diff --git a/code/modules/power/singularity/particle_accelerator/particle_power.dm b/code/modules/power/singularity/particle_accelerator/particle_power.dm index 75ae7eb3263..527c745436e 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_power.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_power.dm @@ -1,6 +1,6 @@ -/obj/structure/particle_accelerator/power_box - name = "Particle Focusing EM Lens" - desc_holder = "This part uses electromagnetic waves to focus the Alpha particles." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "power_box" - reference = "power_box" \ No newline at end of file +/obj/structure/particle_accelerator/power_box + name = "Particle Focusing EM Lens" + desc_holder = "This part uses electromagnetic waves to focus the Alpha particles." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "power_box" + reference = "power_box" diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index c67e676ee13..fcc37bf943b 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -1,443 +1,443 @@ -/obj/singularity - name = "gravitational singularity" - desc = "A gravitational singularity." - icon = 'icons/obj/singularity.dmi' - icon_state = "singularity_s1" - anchored = 1 - density = 1 - layer = MASSIVE_OBJ_LAYER - light_range = 6 - appearance_flags = 0 - var/current_size = 1 - var/allowed_size = 1 - var/contained = 1 //Are we going to move around? - var/energy = 100 //How strong are we? - var/dissipate = 1 //Do we lose energy over time? - var/dissipate_delay = 10 - var/dissipate_track = 0 - var/dissipate_strength = 1 //How much energy do we lose? - var/move_self = 1 //Do we move on our own? - var/grav_pull = 4 //How many tiles out do we pull? - move_resist = INFINITY //no, you don't get to push the singulo. Not even you OP wizard gateway statues - var/consume_range = 0 //How many tiles out do we eat - var/event_chance = 15 //Prob for event each tick - var/target = null //its target. moves towards the target if it has one - var/last_failed_movement = 0//Will not move in the same dir if it couldnt before, will help with the getting stuck on fields thing - var/last_warning - var/consumedSupermatter = 0 //If the singularity has eaten a supermatter shard and can go to stage six - allow_spin = 0 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF - -/obj/singularity/New(loc, var/starting_energy = 50, var/temp = 0) - //CARN: admin-alert for chuckle-fuckery. - admin_investigate_setup() - - src.energy = starting_energy - ..() - START_PROCESSING(SSobj, src) - GLOB.poi_list |= src - GLOB.singularities += src - for(var/obj/machinery/power/singularity_beacon/singubeacon in GLOB.machines) - if(singubeacon.active) - target = singubeacon - break - -/obj/singularity/Destroy() - STOP_PROCESSING(SSobj, src) - GLOB.poi_list.Remove(src) - GLOB.singularities -= src - target = null - return ..() - -/obj/singularity/Move(atom/newloc, direct) - if(current_size >= STAGE_FIVE || check_turfs_in(direct)) - last_failed_movement = 0//Reset this because we moved - return ..() - else - last_failed_movement = direct - return 0 - - -/obj/singularity/attack_hand(mob/user) - consume(user) - return 1 - -/obj/singularity/attack_alien(mob/user) - consume(user) - -/obj/singularity/attack_animal(mob/user) - consume(user) - -/obj/singularity/attackby(obj/item/W, mob/user, params) - consume(user) - return 1 - -/obj/singularity/Process_Spacemove() //The singularity stops drifting for no man! - return 0 - -/obj/singularity/blob_act(obj/structure/blob/B) - return - -/obj/singularity/ex_act(severity) - switch(severity) - if(1) - if(current_size <= STAGE_TWO) - investigate_log("has been destroyed by a heavy explosion.","singulo") - qdel(src) - return - else - energy -= round(((energy+1)/2),1) - if(2) - energy -= round(((energy+1)/3),1) - if(3) - energy -= round(((energy+1)/4),1) - return - - -/obj/singularity/bullet_act(obj/item/projectile/P) - qdel(P) - return 0 //Will there be an impact? Who knows. Will we see it? No. - - -/obj/singularity/Bump(atom/A) - consume(A) - return - - -/obj/singularity/Bumped(atom/A) - consume(A) - return - - -/obj/singularity/process() - if(allowed_size >= STAGE_TWO) - // Start moving even before we reach "true" stage two. - // If we are stage one and are sufficiently energetic to be allowed to 2, - // it might mean we are stuck in a corner somewere. So move around to try to expand. - move() - if(current_size >= STAGE_TWO) - pulse() - if(prob(event_chance))//Chance for it to run a special event TODO:Come up with one or two more that fit - event() - eat() - dissipate() - check_energy() - - return - - -/obj/singularity/attack_ai() //to prevent ais from gibbing themselves when they click on one. - return - - -/obj/singularity/proc/admin_investigate_setup() - last_warning = world.time - var/count = locate(/obj/machinery/field/containment) in urange(30, src, 1) - if(!count) - message_admins("A singularity has been created without containment fields active at [x], [y], [z] (JMP)") - investigate_log("was created. [count?"":"No containment fields were active"]","singulo") - -/obj/singularity/proc/dissipate() - if(!dissipate) - return - if(dissipate_track >= dissipate_delay) - src.energy -= dissipate_strength - dissipate_track = 0 - else - dissipate_track++ - - -/obj/singularity/proc/expand(force_size = 0) - var/temp_allowed_size = src.allowed_size - if(force_size) - temp_allowed_size = force_size - if(temp_allowed_size >= STAGE_SIX && !consumedSupermatter) - temp_allowed_size = STAGE_FIVE - switch(temp_allowed_size) - if(STAGE_ONE) - current_size = STAGE_ONE - icon = 'icons/obj/singularity.dmi' - icon_state = "singularity_s1" - pixel_x = 0 - pixel_y = 0 - grav_pull = 4 - consume_range = 0 - dissipate_delay = 10 - dissipate_track = 0 - dissipate_strength = 1 - if(STAGE_TWO) - if((check_turfs_in(1,1))&&(check_turfs_in(2,1))&&(check_turfs_in(4,1))&&(check_turfs_in(8,1))) - current_size = STAGE_TWO - icon = 'icons/effects/96x96.dmi' - icon_state = "singularity_s3" - pixel_x = -32 - pixel_y = -32 - grav_pull = 6 - consume_range = 1 - dissipate_delay = 5 - dissipate_track = 0 - dissipate_strength = 5 - if(STAGE_THREE) - if((check_turfs_in(1,2))&&(check_turfs_in(2,2))&&(check_turfs_in(4,2))&&(check_turfs_in(8,2))) - current_size = STAGE_THREE - icon = 'icons/effects/160x160.dmi' - icon_state = "singularity_s5" - pixel_x = -64 - pixel_y = -64 - grav_pull = 8 - consume_range = 2 - dissipate_delay = 4 - dissipate_track = 0 - dissipate_strength = 20 - if(STAGE_FOUR) - if((check_turfs_in(1,3))&&(check_turfs_in(2,3))&&(check_turfs_in(4,3))&&(check_turfs_in(8,3))) - current_size = STAGE_FOUR - icon = 'icons/effects/224x224.dmi' - icon_state = "singularity_s7" - pixel_x = -96 - pixel_y = -96 - grav_pull = 10 - consume_range = 3 - dissipate_delay = 10 - dissipate_track = 0 - dissipate_strength = 10 - if(STAGE_FIVE)//this one also lacks a check for gens because it eats everything - current_size = STAGE_FIVE - icon = 'icons/effects/288x288.dmi' - icon_state = "singularity_s9" - pixel_x = -128 - pixel_y = -128 - grav_pull = 10 - consume_range = 4 - dissipate = 0 //It cant go smaller due to e loss - if(STAGE_SIX) //This only happens if a stage 5 singulo consumes a supermatter shard. - current_size = STAGE_SIX - icon = 'icons/effects/352x352.dmi' - icon_state = "singularity_s11" - pixel_x = -160 - pixel_y = -160 - grav_pull = 15 - consume_range = 5 - dissipate = 0 - if(current_size == allowed_size) - investigate_log("grew to size [current_size]","singulo") - return 1 - else if(current_size < (--temp_allowed_size)) - expand(temp_allowed_size) - else - return 0 - - -/obj/singularity/proc/check_energy() - if(energy <= 0) - investigate_log("collapsed.","singulo") - qdel(src) - return 0 - switch(energy)//Some of these numbers might need to be changed up later -Mport - if(1 to 199) - allowed_size = STAGE_ONE - if(200 to 499) - allowed_size = STAGE_TWO - if(500 to 999) - allowed_size = STAGE_THREE - if(1000 to 1999) - allowed_size = STAGE_FOUR - if(2000 to INFINITY) - if(energy >= 3000 && consumedSupermatter) - allowed_size = STAGE_SIX - else - allowed_size = STAGE_FIVE - if(current_size != allowed_size) - expand() - return 1 - - -/obj/singularity/proc/eat() - set background = BACKGROUND_ENABLED - for(var/tile in spiral_range_turfs(grav_pull, src)) - var/turf/T = tile - if(!T || !isturf(loc)) - continue - if(get_dist(T, src) > consume_range) - T.singularity_pull(src, current_size) - else - consume(T) - for(var/thing in T) - if(isturf(loc) && thing != src) - var/atom/movable/X = thing - if(get_dist(X, src) > consume_range) - X.singularity_pull(src, current_size) - else - consume(X) - CHECK_TICK - - -/obj/singularity/proc/consume(atom/A) - var/gain = A.singularity_act(current_size) - src.energy += gain - if(istype(A, /obj/machinery/power/supermatter_shard) && !consumedSupermatter) - desc = "[initial(desc)] It glows fiercely with inner fire." - name = "supermatter-charged [initial(name)]" - consumedSupermatter = 1 - set_light(10) - return - - -/obj/singularity/proc/move(force_move = 0) - if(!move_self) - return 0 - - var/movement_dir = pick(alldirs - last_failed_movement) - - if(force_move) - movement_dir = force_move - - if(target && prob(60)) - movement_dir = get_dir(src,target) //moves to a singulo beacon, if there is one - - step(src, movement_dir) - - -/obj/singularity/proc/check_turfs_in(direction = 0, step = 0) - if(!direction) - return 0 - var/steps = 0 - if(!step) - switch(current_size) - if(STAGE_ONE) - steps = 1 - if(STAGE_TWO) - steps = 3//Yes this is right - if(STAGE_THREE) - steps = 3 - if(STAGE_FOUR) - steps = 4 - if(STAGE_FIVE) - steps = 5 - else - steps = step - var/list/turfs = list() - var/turf/T = src.loc - for(var/i = 1 to steps) - T = get_step(T,direction) - if(!isturf(T)) - return 0 - turfs.Add(T) - var/dir2 = 0 - var/dir3 = 0 - switch(direction) - if(NORTH||SOUTH) - dir2 = 4 - dir3 = 8 - if(EAST||WEST) - dir2 = 1 - dir3 = 2 - var/turf/T2 = T - for(var/j = 1 to steps-1) - T2 = get_step(T2,dir2) - if(!isturf(T2)) - return 0 - turfs.Add(T2) - for(var/k = 1 to steps-1) - T = get_step(T,dir3) - if(!isturf(T)) - return 0 - turfs.Add(T) - for(var/turf/T3 in turfs) - if(isnull(T3)) - continue - if(!can_move(T3)) - return 0 - return 1 - - -/obj/singularity/proc/can_move(turf/T) - if(!T) - return 0 - if((locate(/obj/machinery/field/containment) in T)||(locate(/obj/machinery/shieldwall) in T)) - return 0 - else if(locate(/obj/machinery/field/generator) in T) - var/obj/machinery/field/generator/G = locate(/obj/machinery/field/generator) in T - if(G && G.active) - return 0 - else if(locate(/obj/machinery/shieldwallgen) in T) - var/obj/machinery/shieldwallgen/S = locate(/obj/machinery/shieldwallgen) in T - if(S && S.active) - return 0 - return 1 - - -/obj/singularity/proc/event() - var/numb = pick(1,2,3,4,5,6) - switch(numb) - if(1)//EMP - emp_area() - if(2,3)//tox damage all carbon mobs in area - toxmob() - if(4)//Stun mobs who lack optic scanners - mezzer() - if(5,6) //Sets all nearby mobs on fire - if(current_size < STAGE_SIX) - return 0 - combust_mobs() - else - return 0 - return 1 - - -/obj/singularity/proc/toxmob() - var/toxrange = 10 - var/radiation = 15 - var/radiationmin = 3 - if(energy>200) - radiation += round((energy-150)/10,1) - radiationmin = round((radiation/5),1) - for(var/mob/living/M in view(toxrange, src.loc)) - M.apply_effect(rand(radiationmin,radiation), IRRADIATE) - - -/obj/singularity/proc/combust_mobs() - for(var/mob/living/carbon/C in urange(20, src, 1)) - C.visible_message("[C]'s skin bursts into flame!", \ - "You feel an inner fire as your skin bursts into flames!") - C.adjust_fire_stacks(5) - C.IgniteMob() - return - - -/obj/singularity/proc/mezzer() - for(var/mob/living/carbon/M in oviewers(8, src)) - if(istype(M, /mob/living/carbon/brain)) //Ignore brains - continue - - if(M.stat == CONSCIOUS) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(istype(H.glasses, /obj/item/clothing/glasses/meson)) - var/obj/item/clothing/glasses/meson/MS = H.glasses - if(MS.vision_flags == SEE_TURFS) - to_chat(H, "You look directly into the [src.name], good thing you had your protective eyewear on!") - return - - M.apply_effect(3, STUN) - M.visible_message("[M] stares blankly at the [src.name]!", \ - "You look directly into the [src.name] and feel weak.") - return - - -/obj/singularity/proc/emp_area() - empulse(src, 8, 10) - return - - -/obj/singularity/proc/pulse() - for(var/obj/machinery/power/rad_collector/R in rad_collectors) - if(R.z == z && get_dist(R, src) <= 15) // Better than using orange() every process - R.receive_pulse(energy) - -/obj/singularity/singularity_act() - var/gain = (energy/2) - var/dist = max((current_size - 2),1) - explosion(src.loc,(dist),(dist*2),(dist*4)) - qdel(src) - return(gain) +/obj/singularity + name = "gravitational singularity" + desc = "A gravitational singularity." + icon = 'icons/obj/singularity.dmi' + icon_state = "singularity_s1" + anchored = 1 + density = 1 + layer = MASSIVE_OBJ_LAYER + light_range = 6 + appearance_flags = 0 + var/current_size = 1 + var/allowed_size = 1 + var/contained = 1 //Are we going to move around? + var/energy = 100 //How strong are we? + var/dissipate = 1 //Do we lose energy over time? + var/dissipate_delay = 10 + var/dissipate_track = 0 + var/dissipate_strength = 1 //How much energy do we lose? + var/move_self = 1 //Do we move on our own? + var/grav_pull = 4 //How many tiles out do we pull? + move_resist = INFINITY //no, you don't get to push the singulo. Not even you OP wizard gateway statues + var/consume_range = 0 //How many tiles out do we eat + var/event_chance = 15 //Prob for event each tick + var/target = null //its target. moves towards the target if it has one + var/last_failed_movement = 0//Will not move in the same dir if it couldnt before, will help with the getting stuck on fields thing + var/last_warning + var/consumedSupermatter = 0 //If the singularity has eaten a supermatter shard and can go to stage six + allow_spin = 0 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF + +/obj/singularity/New(loc, var/starting_energy = 50, var/temp = 0) + //CARN: admin-alert for chuckle-fuckery. + admin_investigate_setup() + + src.energy = starting_energy + ..() + START_PROCESSING(SSobj, src) + GLOB.poi_list |= src + GLOB.singularities += src + for(var/obj/machinery/power/singularity_beacon/singubeacon in GLOB.machines) + if(singubeacon.active) + target = singubeacon + break + +/obj/singularity/Destroy() + STOP_PROCESSING(SSobj, src) + GLOB.poi_list.Remove(src) + GLOB.singularities -= src + target = null + return ..() + +/obj/singularity/Move(atom/newloc, direct) + if(current_size >= STAGE_FIVE || check_turfs_in(direct)) + last_failed_movement = 0//Reset this because we moved + return ..() + else + last_failed_movement = direct + return 0 + + +/obj/singularity/attack_hand(mob/user) + consume(user) + return 1 + +/obj/singularity/attack_alien(mob/user) + consume(user) + +/obj/singularity/attack_animal(mob/user) + consume(user) + +/obj/singularity/attackby(obj/item/W, mob/user, params) + consume(user) + return 1 + +/obj/singularity/Process_Spacemove() //The singularity stops drifting for no man! + return 0 + +/obj/singularity/blob_act(obj/structure/blob/B) + return + +/obj/singularity/ex_act(severity) + switch(severity) + if(1) + if(current_size <= STAGE_TWO) + investigate_log("has been destroyed by a heavy explosion.","singulo") + qdel(src) + return + else + energy -= round(((energy+1)/2),1) + if(2) + energy -= round(((energy+1)/3),1) + if(3) + energy -= round(((energy+1)/4),1) + return + + +/obj/singularity/bullet_act(obj/item/projectile/P) + qdel(P) + return 0 //Will there be an impact? Who knows. Will we see it? No. + + +/obj/singularity/Bump(atom/A) + consume(A) + return + + +/obj/singularity/Bumped(atom/A) + consume(A) + return + + +/obj/singularity/process() + if(allowed_size >= STAGE_TWO) + // Start moving even before we reach "true" stage two. + // If we are stage one and are sufficiently energetic to be allowed to 2, + // it might mean we are stuck in a corner somewere. So move around to try to expand. + move() + if(current_size >= STAGE_TWO) + pulse() + if(prob(event_chance))//Chance for it to run a special event TODO:Come up with one or two more that fit + event() + eat() + dissipate() + check_energy() + + return + + +/obj/singularity/attack_ai() //to prevent ais from gibbing themselves when they click on one. + return + + +/obj/singularity/proc/admin_investigate_setup() + last_warning = world.time + var/count = locate(/obj/machinery/field/containment) in urange(30, src, 1) + if(!count) + message_admins("A singularity has been created without containment fields active at [x], [y], [z] (JMP)") + investigate_log("was created. [count?"":"No containment fields were active"]","singulo") + +/obj/singularity/proc/dissipate() + if(!dissipate) + return + if(dissipate_track >= dissipate_delay) + src.energy -= dissipate_strength + dissipate_track = 0 + else + dissipate_track++ + + +/obj/singularity/proc/expand(force_size = 0) + var/temp_allowed_size = src.allowed_size + if(force_size) + temp_allowed_size = force_size + if(temp_allowed_size >= STAGE_SIX && !consumedSupermatter) + temp_allowed_size = STAGE_FIVE + switch(temp_allowed_size) + if(STAGE_ONE) + current_size = STAGE_ONE + icon = 'icons/obj/singularity.dmi' + icon_state = "singularity_s1" + pixel_x = 0 + pixel_y = 0 + grav_pull = 4 + consume_range = 0 + dissipate_delay = 10 + dissipate_track = 0 + dissipate_strength = 1 + if(STAGE_TWO) + if((check_turfs_in(1,1))&&(check_turfs_in(2,1))&&(check_turfs_in(4,1))&&(check_turfs_in(8,1))) + current_size = STAGE_TWO + icon = 'icons/effects/96x96.dmi' + icon_state = "singularity_s3" + pixel_x = -32 + pixel_y = -32 + grav_pull = 6 + consume_range = 1 + dissipate_delay = 5 + dissipate_track = 0 + dissipate_strength = 5 + if(STAGE_THREE) + if((check_turfs_in(1,2))&&(check_turfs_in(2,2))&&(check_turfs_in(4,2))&&(check_turfs_in(8,2))) + current_size = STAGE_THREE + icon = 'icons/effects/160x160.dmi' + icon_state = "singularity_s5" + pixel_x = -64 + pixel_y = -64 + grav_pull = 8 + consume_range = 2 + dissipate_delay = 4 + dissipate_track = 0 + dissipate_strength = 20 + if(STAGE_FOUR) + if((check_turfs_in(1,3))&&(check_turfs_in(2,3))&&(check_turfs_in(4,3))&&(check_turfs_in(8,3))) + current_size = STAGE_FOUR + icon = 'icons/effects/224x224.dmi' + icon_state = "singularity_s7" + pixel_x = -96 + pixel_y = -96 + grav_pull = 10 + consume_range = 3 + dissipate_delay = 10 + dissipate_track = 0 + dissipate_strength = 10 + if(STAGE_FIVE)//this one also lacks a check for gens because it eats everything + current_size = STAGE_FIVE + icon = 'icons/effects/288x288.dmi' + icon_state = "singularity_s9" + pixel_x = -128 + pixel_y = -128 + grav_pull = 10 + consume_range = 4 + dissipate = 0 //It cant go smaller due to e loss + if(STAGE_SIX) //This only happens if a stage 5 singulo consumes a supermatter shard. + current_size = STAGE_SIX + icon = 'icons/effects/352x352.dmi' + icon_state = "singularity_s11" + pixel_x = -160 + pixel_y = -160 + grav_pull = 15 + consume_range = 5 + dissipate = 0 + if(current_size == allowed_size) + investigate_log("grew to size [current_size]","singulo") + return 1 + else if(current_size < (--temp_allowed_size)) + expand(temp_allowed_size) + else + return 0 + + +/obj/singularity/proc/check_energy() + if(energy <= 0) + investigate_log("collapsed.","singulo") + qdel(src) + return 0 + switch(energy)//Some of these numbers might need to be changed up later -Mport + if(1 to 199) + allowed_size = STAGE_ONE + if(200 to 499) + allowed_size = STAGE_TWO + if(500 to 999) + allowed_size = STAGE_THREE + if(1000 to 1999) + allowed_size = STAGE_FOUR + if(2000 to INFINITY) + if(energy >= 3000 && consumedSupermatter) + allowed_size = STAGE_SIX + else + allowed_size = STAGE_FIVE + if(current_size != allowed_size) + expand() + return 1 + + +/obj/singularity/proc/eat() + set background = BACKGROUND_ENABLED + for(var/tile in spiral_range_turfs(grav_pull, src)) + var/turf/T = tile + if(!T || !isturf(loc)) + continue + if(get_dist(T, src) > consume_range) + T.singularity_pull(src, current_size) + else + consume(T) + for(var/thing in T) + if(isturf(loc) && thing != src) + var/atom/movable/X = thing + if(get_dist(X, src) > consume_range) + X.singularity_pull(src, current_size) + else + consume(X) + CHECK_TICK + + +/obj/singularity/proc/consume(atom/A) + var/gain = A.singularity_act(current_size) + src.energy += gain + if(istype(A, /obj/machinery/power/supermatter_shard) && !consumedSupermatter) + desc = "[initial(desc)] It glows fiercely with inner fire." + name = "supermatter-charged [initial(name)]" + consumedSupermatter = 1 + set_light(10) + return + + +/obj/singularity/proc/move(force_move = 0) + if(!move_self) + return 0 + + var/movement_dir = pick(alldirs - last_failed_movement) + + if(force_move) + movement_dir = force_move + + if(target && prob(60)) + movement_dir = get_dir(src,target) //moves to a singulo beacon, if there is one + + step(src, movement_dir) + + +/obj/singularity/proc/check_turfs_in(direction = 0, step = 0) + if(!direction) + return 0 + var/steps = 0 + if(!step) + switch(current_size) + if(STAGE_ONE) + steps = 1 + if(STAGE_TWO) + steps = 3//Yes this is right + if(STAGE_THREE) + steps = 3 + if(STAGE_FOUR) + steps = 4 + if(STAGE_FIVE) + steps = 5 + else + steps = step + var/list/turfs = list() + var/turf/T = src.loc + for(var/i = 1 to steps) + T = get_step(T,direction) + if(!isturf(T)) + return 0 + turfs.Add(T) + var/dir2 = 0 + var/dir3 = 0 + switch(direction) + if(NORTH||SOUTH) + dir2 = 4 + dir3 = 8 + if(EAST||WEST) + dir2 = 1 + dir3 = 2 + var/turf/T2 = T + for(var/j = 1 to steps-1) + T2 = get_step(T2,dir2) + if(!isturf(T2)) + return 0 + turfs.Add(T2) + for(var/k = 1 to steps-1) + T = get_step(T,dir3) + if(!isturf(T)) + return 0 + turfs.Add(T) + for(var/turf/T3 in turfs) + if(isnull(T3)) + continue + if(!can_move(T3)) + return 0 + return 1 + + +/obj/singularity/proc/can_move(turf/T) + if(!T) + return 0 + if((locate(/obj/machinery/field/containment) in T)||(locate(/obj/machinery/shieldwall) in T)) + return 0 + else if(locate(/obj/machinery/field/generator) in T) + var/obj/machinery/field/generator/G = locate(/obj/machinery/field/generator) in T + if(G && G.active) + return 0 + else if(locate(/obj/machinery/shieldwallgen) in T) + var/obj/machinery/shieldwallgen/S = locate(/obj/machinery/shieldwallgen) in T + if(S && S.active) + return 0 + return 1 + + +/obj/singularity/proc/event() + var/numb = pick(1,2,3,4,5,6) + switch(numb) + if(1)//EMP + emp_area() + if(2,3)//tox damage all carbon mobs in area + toxmob() + if(4)//Stun mobs who lack optic scanners + mezzer() + if(5,6) //Sets all nearby mobs on fire + if(current_size < STAGE_SIX) + return 0 + combust_mobs() + else + return 0 + return 1 + + +/obj/singularity/proc/toxmob() + var/toxrange = 10 + var/radiation = 15 + var/radiationmin = 3 + if(energy>200) + radiation += round((energy-150)/10,1) + radiationmin = round((radiation/5),1) + for(var/mob/living/M in view(toxrange, src.loc)) + M.apply_effect(rand(radiationmin,radiation), IRRADIATE) + + +/obj/singularity/proc/combust_mobs() + for(var/mob/living/carbon/C in urange(20, src, 1)) + C.visible_message("[C]'s skin bursts into flame!", \ + "You feel an inner fire as your skin bursts into flames!") + C.adjust_fire_stacks(5) + C.IgniteMob() + return + + +/obj/singularity/proc/mezzer() + for(var/mob/living/carbon/M in oviewers(8, src)) + if(istype(M, /mob/living/carbon/brain)) //Ignore brains + continue + + if(M.stat == CONSCIOUS) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(istype(H.glasses, /obj/item/clothing/glasses/meson)) + var/obj/item/clothing/glasses/meson/MS = H.glasses + if(MS.vision_flags == SEE_TURFS) + to_chat(H, "You look directly into the [src.name], good thing you had your protective eyewear on!") + return + + M.apply_effect(3, STUN) + M.visible_message("[M] stares blankly at the [src.name]!", \ + "You look directly into the [src.name] and feel weak.") + return + + +/obj/singularity/proc/emp_area() + empulse(src, 8, 10) + return + + +/obj/singularity/proc/pulse() + for(var/obj/machinery/power/rad_collector/R in rad_collectors) + if(R.z == z && get_dist(R, src) <= 15) // Better than using orange() every process + R.receive_pulse(energy) + +/obj/singularity/singularity_act() + var/gain = (energy/2) + var/dist = max((current_size - 2),1) + explosion(src.loc,(dist),(dist*2),(dist*4)) + qdel(src) + return(gain) diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm index 031ef83a264..05451033703 100644 --- a/code/modules/power/smes.dm +++ b/code/modules/power/smes.dm @@ -1,493 +1,493 @@ -// the SMES -// stores power - -#define SMESMAXCHARGELEVEL 200000 -#define SMESMAXOUTPUT 200000 -#define SMESRATE 0.05 // rate of internal charge to external power - - - -/obj/machinery/power/smes - name = "power storage unit" - desc = "A high-capacity superconducting magnetic energy storage (SMES) unit." - icon_state = "smes" - density = TRUE - use_power = NO_POWER_USE - - var/capacity = 5e6 // maximum charge - var/charge = 0 // actual charge - - var/input_attempt = TRUE // 1 = attempting to charge, 0 = not attempting to charge - var/inputting = TRUE // 1 = actually inputting, 0 = not inputting - var/input_level = 50000 // amount of power the SMES attempts to charge by - var/input_level_max = 200000 // cap on input_level - var/input_available = 0 // amount of charge available from input last tick - - var/output_attempt = TRUE // 1 = attempting to output, 0 = not attempting to output - var/outputting = TRUE // 1 = actually outputting, 0 = not outputting - var/output_level = 50000 // amount of power the SMES attempts to output - var/output_level_max = 200000 // cap on output_level - var/output_used = 0 // amount of power actually outputted. may be less than output_level if the powernet returns excess power - - //Holders for powerout event. - var/last_output_attempt = 0 - var/last_input_attempt = 0 - var/last_charge = 0 - - var/name_tag = null - var/obj/machinery/power/terminal/terminal = null - -/obj/machinery/power/smes/Initialize(mapload) - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/smes(null) - component_parts += new /obj/item/stock_parts/cell/high(null) - component_parts += new /obj/item/stock_parts/cell/high(null) - component_parts += new /obj/item/stock_parts/cell/high(null) - component_parts += new /obj/item/stock_parts/cell/high(null) - component_parts += new /obj/item/stock_parts/cell/high(null) - component_parts += new /obj/item/stock_parts/capacitor(null) - component_parts += new /obj/item/stack/cable_coil(null, 5) - RefreshParts() - - dir_loop: - for(var/d in cardinal) - var/turf/T = get_step(src, d) - for(var/obj/machinery/power/terminal/term in T) - if(term && term.dir == turn(d, 180)) - terminal = term - break dir_loop - - if(!terminal) - stat |= BROKEN - return - terminal.master = src - update_icon() - -/obj/machinery/power/smes/upgraded/Initialize(mapload) - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/smes(null) - component_parts += new /obj/item/stock_parts/cell/hyper(null) - component_parts += new /obj/item/stock_parts/cell/hyper(null) - component_parts += new /obj/item/stock_parts/cell/hyper(null) - component_parts += new /obj/item/stock_parts/cell/hyper(null) - component_parts += new /obj/item/stock_parts/cell/hyper(null) - component_parts += new /obj/item/stock_parts/capacitor/super(null) - component_parts += new /obj/item/stack/cable_coil(null, 5) - RefreshParts() - -/obj/machinery/power/smes/RefreshParts() - var/IO = 0 - var/C = 0 - for(var/obj/item/stock_parts/capacitor/CP in component_parts) - IO += CP.rating - input_level_max = 200000 * IO - output_level_max = 200000 * IO - for(var/obj/item/stock_parts/cell/PC in component_parts) - C += PC.maxcharge - capacity = C / (15000) * 1e6 - -/obj/machinery/power/smes/update_icon() - overlays.Cut() - if(stat & BROKEN) return - - overlays += image('icons/obj/power.dmi', "smes-op[outputting]") - - if(inputting == 2) - overlays += image('icons/obj/power.dmi', "smes-oc2") - else if(inputting == 1) - overlays += image('icons/obj/power.dmi', "smes-oc1") - else - if(input_attempt) - overlays += image('icons/obj/power.dmi', "smes-oc0") - - var/clevel = chargedisplay() - if(clevel>0) - overlays += image('icons/obj/power.dmi', "smes-og[clevel]") - return - -/obj/machinery/power/smes/attackby(obj/item/I, mob/user, params) - //opening using screwdriver - if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I)) - update_icon() - return - - //changing direction using wrench - if(default_change_direction_wrench(user, I)) - terminal = null - var/turf/T = get_step(src, dir) - for(var/obj/machinery/power/terminal/term in T) - if(term && term.dir == turn(dir, 180)) - terminal = term - terminal.master = src - to_chat(user, "Terminal found.") - break - if(!terminal) - to_chat(user, "No power source found.") - return - stat &= ~BROKEN - update_icon() - return - - //exchanging parts using the RPE - if(exchange_parts(user, I)) - return - - //building and linking a terminal - if(istype(I, /obj/item/stack/cable_coil)) - var/dir = get_dir(user,src) - if(dir & (dir-1))//we don't want diagonal click - return - - if(terminal) //is there already a terminal ? - to_chat(user, "This SMES already has a power terminal!") - return - - if(!panel_open) //is the panel open ? - to_chat(user, "You must open the maintenance panel first!") - return - - var/turf/T = get_turf(user) - if(T.intact) //is the floor plating removed ? - to_chat(user, "You must first remove the floor plating!") - return - - var/obj/item/stack/cable_coil/C = I - if(C.amount < 10) - to_chat(user, "You need more wires.") - return - - if(user.loc == loc) - to_chat(user, "You must not be on the same tile as the [src].") - return - - //Direction the terminal will face to - var/tempDir = get_dir(user, src) - switch(tempDir) - if(NORTHEAST, SOUTHEAST) - tempDir = EAST - if(NORTHWEST, SOUTHWEST) - tempDir = WEST - var/turf/tempLoc = get_step(src, reverse_direction(tempDir)) - if(istype(tempLoc, /turf/space)) - to_chat(user, "You can't build a terminal on space.") - return - else if(istype(tempLoc)) - if(tempLoc.intact) - to_chat(user, "You must remove the floor plating first.") - return - - to_chat(user, "You start adding cable to the [src].") - playsound(loc, C.usesound, 50, 1) - - if(do_after(user, 50, target = src)) - if(!terminal && panel_open) - T = get_turf(user) - var/obj/structure/cable/N = T.get_cable_node() //get the connecting node cable, if there's one - if(prob(50) && electrocute_mob(usr, N, N, 1, TRUE)) //animate the electrocution if uncautious and unlucky - do_sparks(5, 1, src) - return - - C.use(10) // make sure the cable gets used up - user.visible_message(\ - "[user.name] adds the cables and connects the power terminal.",\ - "You add the cables and connect the power terminal.") - - make_terminal(user, tempDir, tempLoc) - terminal.connect_to_network() - return - - //disassembling the terminal - if(istype(I, /obj/item/wirecutters) && terminal && panel_open) - var/turf/T = get_turf(terminal) - if(T.intact) //is the floor plating removed ? - to_chat(user, "You must first expose the power terminal!") - return - - to_chat(user, "You begin to dismantle the power terminal...") - playsound(src.loc, I.usesound, 50, 1) - - if(do_after(user, 50 * I.toolspeed, target = src)) - if(terminal && panel_open) - if(prob(50) && electrocute_mob(usr, terminal.powernet, terminal, 1, TRUE)) //animate the electrocution if uncautious and unlucky - do_sparks(5, 1, src) - return - - //give the wires back and delete the terminal - new /obj/item/stack/cable_coil(T,10) - user.visible_message(\ - "[user.name] cuts the cables and dismantles the power terminal.",\ - "You cut the cables and dismantle the power terminal.") - inputting = 0 //stop inputting, since we have don't have a terminal anymore - qdel(terminal) - return - - //crowbarring it ! - if(default_deconstruction_crowbar(user, I)) - return - return ..() - -/obj/machinery/power/smes/disconnect_terminal() - if(terminal) - terminal.master = null - terminal = null - return 1 - return 0 - -/obj/machinery/power/smes/proc/make_terminal(user, tempDir, tempLoc) - // create a terminal object at the same position as original turf loc - // wires will attach to this - terminal = new /obj/machinery/power/terminal(tempLoc) - terminal.dir = tempDir - terminal.master = src - -/obj/machinery/power/smes/Destroy() - if(SSticker && SSticker.current_state == GAME_STATE_PLAYING) - var/area/area = get_area(src) - if(area) - message_admins("SMES deleted at ([area.name])") - log_game("SMES deleted at ([area.name])") - investigate_log("deleted at ([area.name])","singulo") - if(terminal) - disconnect_terminal() - return ..() - - return round(5.5*charge/(capacity ? capacity : 5e6)) - -/obj/machinery/power/smes/proc/chargedisplay() - return round(5.5*charge/(capacity ? capacity : 5e6)) - -/obj/machinery/power/smes/process() - if(stat & BROKEN) - return - - //store machine state to see if we need to update the icon overlays - var/last_disp = chargedisplay() - var/last_chrg = inputting - var/last_onln = outputting - - //inputting - if(terminal && input_attempt) - input_available = terminal.surplus() - - if(inputting) - if(input_available > 0) // if there's power available, try to charge - - var/load = min(min((capacity-charge)/SMESRATE, input_level), input_available) // charge at set rate, limited to spare capacity - - charge += load * SMESRATE // increase the charge - - terminal.add_load(load) // add the load to the terminal side network - - else // if not enough capcity - inputting = FALSE // stop inputting - - else - if(input_attempt && input_available > 0) - inputting = TRUE - else - inputting = FALSE - - //outputting - if(output_attempt) - if(outputting) - output_used = min( charge/SMESRATE, output_level) //limit output to that stored - - if (add_avail(output_used)) // add output to powernet if it exists (smes side) - charge -= output_used*SMESRATE // reduce the storage (may be recovered in /restore() if excessive) - else - outputting = FALSE - - if(output_used < 0.0001) // either from no charge or set to 0 - outputting = FALSE - investigate_log("lost power and turned off", "singulo") - else if(output_attempt && charge > output_level && output_level > 0) - outputting = TRUE - else - output_used = 0 - else - outputting = FALSE - - // only update icon if state changed - if(last_disp != chargedisplay() || last_chrg != inputting || last_onln != outputting) - update_icon() - - - -// called after all power processes are finished -// restores charge level to smes if there was excess this ptick -/obj/machinery/power/smes/proc/restore() - if(stat & BROKEN) - return - - if(!outputting) - output_used = 0 - return - - var/excess = powernet.netexcess // this was how much wasn't used on the network last ptick, minus any removed by other SMESes - - excess = min(output_used, excess) // clamp it to how much was actually output by this SMES last ptick - - excess = min((capacity-charge)/SMESRATE, excess) // for safety, also limit recharge by space capacity of SMES (shouldn't happen) - - // now recharge this amount - - var/clev = chargedisplay() - - charge += excess * SMESRATE // restore unused power - powernet.netexcess -= excess // remove the excess from the powernet, so later SMESes don't try to use it - - output_used -= excess - - if(clev != chargedisplay() ) //if needed updates the icons overlay - update_icon() - return - -/obj/machinery/power/smes/attack_ai(mob/user) - add_hiddenprint(user) - ui_interact(user) - -/obj/machinery/power/smes/attack_ghost(mob/user) - ui_interact(user) - -/obj/machinery/power/smes/attack_hand(mob/user) - add_fingerprint(user) - ui_interact(user) - -/obj/machinery/power/smes/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(stat & BROKEN) - return - - - // update the ui if it exists, returns null if no ui is passed/found - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "smes.tmpl", "SMES Power Storage Unit", 540, 380) - // open the new ui window - ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/machinery/power/smes/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - data["nameTag"] = name_tag - data["storedCapacity"] = round(100.0*charge/capacity, 0.1) - data["charging"] = inputting - data["chargeMode"] = input_attempt - data["chargeLevel"] = input_level - data["chargeMax"] = input_level_max - data["outputOnline"] = output_attempt - data["outputLevel"] = output_level - data["outputMax"] = output_level_max - data["outputLoad"] = round(output_used) - - if(outputting) - data["outputting"] = 2 // smes is outputting - else if(!outputting && output_attempt) - data["outputting"] = 1 // smes is online but not outputting because it's charge level is too low - else - data["outputting"] = 0 // smes is not outputting - - return data - -/obj/machinery/power/smes/Topic(href, href_list) - if(..()) - return 1 - - if( href_list["cmode"] ) - inputting(!input_attempt) - update_icon() - - else if( href_list["online"] ) - outputting(!output_attempt) - update_icon() - - else if( href_list["input"] ) - switch( href_list["input"] ) - if("min") - input_level = 0 - if("max") - input_level = input_level_max - if("set") - input_level = input(usr, "Enter new input level (0-[input_level_max])", "SMES Input Power Control", input_level) as num - input_level = max(0, min(input_level_max, input_level)) // clamp to range - - else if( href_list["output"] ) - switch( href_list["output"] ) - if("min") - output_level = 0 - if("max") - output_level = output_level_max - if("set") - output_level = input(usr, "Enter new output level (0-[output_level_max])", "SMES Output Power Control", output_level) as num - output_level = max(0, min(output_level_max, output_level)) // clamp to range - - investigate_log("input/output; [input_level>output_level?"":""][input_level]/[output_level] | Output-mode: [output_attempt?"on":"off"] | Input-mode: [input_attempt?"auto":"off"] by [usr.key]","singulo") - - return 1 - -/obj/machinery/power/smes/proc/ion_act() - if(is_station_level(src.z)) - if(prob(1)) //explosion - for(var/mob/M in viewers(src)) - M.show_message("The [src.name] is making strange noises!", 3, "You hear sizzling electronics.", 2) - sleep(10*pick(4,5,6,7,10,14)) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(3, 0, src.loc) - smoke.attach(src) - smoke.start() - explosion(src.loc, -1, 0, 1, 3, 1, 0) - qdel(src) - return - if(prob(15)) //Power drain - do_sparks(3, 1, src) - if(prob(50)) - emp_act(1) - else - emp_act(2) - if(prob(5)) //smoke only - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(3, 0, src.loc) - smoke.attach(src) - smoke.start() - -/obj/machinery/power/smes/proc/inputting(var/do_input) - input_attempt = do_input - if(!input_attempt) - inputting = 0 - -/obj/machinery/power/smes/proc/outputting(var/do_output) - output_attempt = do_output - if(!output_attempt) - outputting = 0 - -/obj/machinery/power/smes/emp_act(severity) - inputting(rand(0,1)) - outputting(rand(0,1)) - output_level = rand(0, output_level_max) - input_level = rand(0, input_level_max) - charge -= 1e6/severity - if(charge < 0) - charge = 0 - update_icon() - ..() - -/obj/machinery/power/smes/engineering - charge = 2e6 // Engineering starts with some charge for singulo - -/obj/machinery/power/smes/magical - name = "magical power storage unit" - desc = "A high-capacity superconducting magnetic energy storage (SMES) unit. Magically produces power." - capacity = 9000000 - output_level = 250000 - -/obj/machinery/power/smes/magical/process() - capacity = INFINITY - charge = INFINITY - ..() - -#undef SMESRATE +// the SMES +// stores power + +#define SMESMAXCHARGELEVEL 200000 +#define SMESMAXOUTPUT 200000 +#define SMESRATE 0.05 // rate of internal charge to external power + + + +/obj/machinery/power/smes + name = "power storage unit" + desc = "A high-capacity superconducting magnetic energy storage (SMES) unit." + icon_state = "smes" + density = TRUE + use_power = NO_POWER_USE + + var/capacity = 5e6 // maximum charge + var/charge = 0 // actual charge + + var/input_attempt = TRUE // 1 = attempting to charge, 0 = not attempting to charge + var/inputting = TRUE // 1 = actually inputting, 0 = not inputting + var/input_level = 50000 // amount of power the SMES attempts to charge by + var/input_level_max = 200000 // cap on input_level + var/input_available = 0 // amount of charge available from input last tick + + var/output_attempt = TRUE // 1 = attempting to output, 0 = not attempting to output + var/outputting = TRUE // 1 = actually outputting, 0 = not outputting + var/output_level = 50000 // amount of power the SMES attempts to output + var/output_level_max = 200000 // cap on output_level + var/output_used = 0 // amount of power actually outputted. may be less than output_level if the powernet returns excess power + + //Holders for powerout event. + var/last_output_attempt = 0 + var/last_input_attempt = 0 + var/last_charge = 0 + + var/name_tag = null + var/obj/machinery/power/terminal/terminal = null + +/obj/machinery/power/smes/Initialize(mapload) + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/smes(null) + component_parts += new /obj/item/stock_parts/cell/high(null) + component_parts += new /obj/item/stock_parts/cell/high(null) + component_parts += new /obj/item/stock_parts/cell/high(null) + component_parts += new /obj/item/stock_parts/cell/high(null) + component_parts += new /obj/item/stock_parts/cell/high(null) + component_parts += new /obj/item/stock_parts/capacitor(null) + component_parts += new /obj/item/stack/cable_coil(null, 5) + RefreshParts() + + dir_loop: + for(var/d in cardinal) + var/turf/T = get_step(src, d) + for(var/obj/machinery/power/terminal/term in T) + if(term && term.dir == turn(d, 180)) + terminal = term + break dir_loop + + if(!terminal) + stat |= BROKEN + return + terminal.master = src + update_icon() + +/obj/machinery/power/smes/upgraded/Initialize(mapload) + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/smes(null) + component_parts += new /obj/item/stock_parts/cell/hyper(null) + component_parts += new /obj/item/stock_parts/cell/hyper(null) + component_parts += new /obj/item/stock_parts/cell/hyper(null) + component_parts += new /obj/item/stock_parts/cell/hyper(null) + component_parts += new /obj/item/stock_parts/cell/hyper(null) + component_parts += new /obj/item/stock_parts/capacitor/super(null) + component_parts += new /obj/item/stack/cable_coil(null, 5) + RefreshParts() + +/obj/machinery/power/smes/RefreshParts() + var/IO = 0 + var/C = 0 + for(var/obj/item/stock_parts/capacitor/CP in component_parts) + IO += CP.rating + input_level_max = 200000 * IO + output_level_max = 200000 * IO + for(var/obj/item/stock_parts/cell/PC in component_parts) + C += PC.maxcharge + capacity = C / (15000) * 1e6 + +/obj/machinery/power/smes/update_icon() + overlays.Cut() + if(stat & BROKEN) return + + overlays += image('icons/obj/power.dmi', "smes-op[outputting]") + + if(inputting == 2) + overlays += image('icons/obj/power.dmi', "smes-oc2") + else if(inputting == 1) + overlays += image('icons/obj/power.dmi', "smes-oc1") + else + if(input_attempt) + overlays += image('icons/obj/power.dmi', "smes-oc0") + + var/clevel = chargedisplay() + if(clevel>0) + overlays += image('icons/obj/power.dmi', "smes-og[clevel]") + return + +/obj/machinery/power/smes/attackby(obj/item/I, mob/user, params) + //opening using screwdriver + if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I)) + update_icon() + return + + //changing direction using wrench + if(default_change_direction_wrench(user, I)) + terminal = null + var/turf/T = get_step(src, dir) + for(var/obj/machinery/power/terminal/term in T) + if(term && term.dir == turn(dir, 180)) + terminal = term + terminal.master = src + to_chat(user, "Terminal found.") + break + if(!terminal) + to_chat(user, "No power source found.") + return + stat &= ~BROKEN + update_icon() + return + + //exchanging parts using the RPE + if(exchange_parts(user, I)) + return + + //building and linking a terminal + if(istype(I, /obj/item/stack/cable_coil)) + var/dir = get_dir(user,src) + if(dir & (dir-1))//we don't want diagonal click + return + + if(terminal) //is there already a terminal ? + to_chat(user, "This SMES already has a power terminal!") + return + + if(!panel_open) //is the panel open ? + to_chat(user, "You must open the maintenance panel first!") + return + + var/turf/T = get_turf(user) + if(T.intact) //is the floor plating removed ? + to_chat(user, "You must first remove the floor plating!") + return + + var/obj/item/stack/cable_coil/C = I + if(C.amount < 10) + to_chat(user, "You need more wires.") + return + + if(user.loc == loc) + to_chat(user, "You must not be on the same tile as the [src].") + return + + //Direction the terminal will face to + var/tempDir = get_dir(user, src) + switch(tempDir) + if(NORTHEAST, SOUTHEAST) + tempDir = EAST + if(NORTHWEST, SOUTHWEST) + tempDir = WEST + var/turf/tempLoc = get_step(src, reverse_direction(tempDir)) + if(istype(tempLoc, /turf/space)) + to_chat(user, "You can't build a terminal on space.") + return + else if(istype(tempLoc)) + if(tempLoc.intact) + to_chat(user, "You must remove the floor plating first.") + return + + to_chat(user, "You start adding cable to the [src].") + playsound(loc, C.usesound, 50, 1) + + if(do_after(user, 50, target = src)) + if(!terminal && panel_open) + T = get_turf(user) + var/obj/structure/cable/N = T.get_cable_node() //get the connecting node cable, if there's one + if(prob(50) && electrocute_mob(usr, N, N, 1, TRUE)) //animate the electrocution if uncautious and unlucky + do_sparks(5, 1, src) + return + + C.use(10) // make sure the cable gets used up + user.visible_message(\ + "[user.name] adds the cables and connects the power terminal.",\ + "You add the cables and connect the power terminal.") + + make_terminal(user, tempDir, tempLoc) + terminal.connect_to_network() + return + + //disassembling the terminal + if(istype(I, /obj/item/wirecutters) && terminal && panel_open) + var/turf/T = get_turf(terminal) + if(T.intact) //is the floor plating removed ? + to_chat(user, "You must first expose the power terminal!") + return + + to_chat(user, "You begin to dismantle the power terminal...") + playsound(src.loc, I.usesound, 50, 1) + + if(do_after(user, 50 * I.toolspeed, target = src)) + if(terminal && panel_open) + if(prob(50) && electrocute_mob(usr, terminal.powernet, terminal, 1, TRUE)) //animate the electrocution if uncautious and unlucky + do_sparks(5, 1, src) + return + + //give the wires back and delete the terminal + new /obj/item/stack/cable_coil(T,10) + user.visible_message(\ + "[user.name] cuts the cables and dismantles the power terminal.",\ + "You cut the cables and dismantle the power terminal.") + inputting = 0 //stop inputting, since we have don't have a terminal anymore + qdel(terminal) + return + + //crowbarring it ! + if(default_deconstruction_crowbar(user, I)) + return + return ..() + +/obj/machinery/power/smes/disconnect_terminal() + if(terminal) + terminal.master = null + terminal = null + return 1 + return 0 + +/obj/machinery/power/smes/proc/make_terminal(user, tempDir, tempLoc) + // create a terminal object at the same position as original turf loc + // wires will attach to this + terminal = new /obj/machinery/power/terminal(tempLoc) + terminal.dir = tempDir + terminal.master = src + +/obj/machinery/power/smes/Destroy() + if(SSticker && SSticker.current_state == GAME_STATE_PLAYING) + var/area/area = get_area(src) + if(area) + message_admins("SMES deleted at ([area.name])") + log_game("SMES deleted at ([area.name])") + investigate_log("deleted at ([area.name])","singulo") + if(terminal) + disconnect_terminal() + return ..() + + return round(5.5*charge/(capacity ? capacity : 5e6)) + +/obj/machinery/power/smes/proc/chargedisplay() + return round(5.5*charge/(capacity ? capacity : 5e6)) + +/obj/machinery/power/smes/process() + if(stat & BROKEN) + return + + //store machine state to see if we need to update the icon overlays + var/last_disp = chargedisplay() + var/last_chrg = inputting + var/last_onln = outputting + + //inputting + if(terminal && input_attempt) + input_available = terminal.surplus() + + if(inputting) + if(input_available > 0) // if there's power available, try to charge + + var/load = min(min((capacity-charge)/SMESRATE, input_level), input_available) // charge at set rate, limited to spare capacity + + charge += load * SMESRATE // increase the charge + + terminal.add_load(load) // add the load to the terminal side network + + else // if not enough capcity + inputting = FALSE // stop inputting + + else + if(input_attempt && input_available > 0) + inputting = TRUE + else + inputting = FALSE + + //outputting + if(output_attempt) + if(outputting) + output_used = min( charge/SMESRATE, output_level) //limit output to that stored + + if (add_avail(output_used)) // add output to powernet if it exists (smes side) + charge -= output_used*SMESRATE // reduce the storage (may be recovered in /restore() if excessive) + else + outputting = FALSE + + if(output_used < 0.0001) // either from no charge or set to 0 + outputting = FALSE + investigate_log("lost power and turned off", "singulo") + else if(output_attempt && charge > output_level && output_level > 0) + outputting = TRUE + else + output_used = 0 + else + outputting = FALSE + + // only update icon if state changed + if(last_disp != chargedisplay() || last_chrg != inputting || last_onln != outputting) + update_icon() + + + +// called after all power processes are finished +// restores charge level to smes if there was excess this ptick +/obj/machinery/power/smes/proc/restore() + if(stat & BROKEN) + return + + if(!outputting) + output_used = 0 + return + + var/excess = powernet.netexcess // this was how much wasn't used on the network last ptick, minus any removed by other SMESes + + excess = min(output_used, excess) // clamp it to how much was actually output by this SMES last ptick + + excess = min((capacity-charge)/SMESRATE, excess) // for safety, also limit recharge by space capacity of SMES (shouldn't happen) + + // now recharge this amount + + var/clev = chargedisplay() + + charge += excess * SMESRATE // restore unused power + powernet.netexcess -= excess // remove the excess from the powernet, so later SMESes don't try to use it + + output_used -= excess + + if(clev != chargedisplay() ) //if needed updates the icons overlay + update_icon() + return + +/obj/machinery/power/smes/attack_ai(mob/user) + add_hiddenprint(user) + ui_interact(user) + +/obj/machinery/power/smes/attack_ghost(mob/user) + ui_interact(user) + +/obj/machinery/power/smes/attack_hand(mob/user) + add_fingerprint(user) + ui_interact(user) + +/obj/machinery/power/smes/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(stat & BROKEN) + return + + + // update the ui if it exists, returns null if no ui is passed/found + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // the ui does not exist, so we'll create a new() one + // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm + ui = new(user, src, ui_key, "smes.tmpl", "SMES Power Storage Unit", 540, 380) + // open the new ui window + ui.open() + // auto update every Master Controller tick + ui.set_auto_update(1) + +/obj/machinery/power/smes/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + data["nameTag"] = name_tag + data["storedCapacity"] = round(100.0*charge/capacity, 0.1) + data["charging"] = inputting + data["chargeMode"] = input_attempt + data["chargeLevel"] = input_level + data["chargeMax"] = input_level_max + data["outputOnline"] = output_attempt + data["outputLevel"] = output_level + data["outputMax"] = output_level_max + data["outputLoad"] = round(output_used) + + if(outputting) + data["outputting"] = 2 // smes is outputting + else if(!outputting && output_attempt) + data["outputting"] = 1 // smes is online but not outputting because it's charge level is too low + else + data["outputting"] = 0 // smes is not outputting + + return data + +/obj/machinery/power/smes/Topic(href, href_list) + if(..()) + return 1 + + if( href_list["cmode"] ) + inputting(!input_attempt) + update_icon() + + else if( href_list["online"] ) + outputting(!output_attempt) + update_icon() + + else if( href_list["input"] ) + switch( href_list["input"] ) + if("min") + input_level = 0 + if("max") + input_level = input_level_max + if("set") + input_level = input(usr, "Enter new input level (0-[input_level_max])", "SMES Input Power Control", input_level) as num + input_level = max(0, min(input_level_max, input_level)) // clamp to range + + else if( href_list["output"] ) + switch( href_list["output"] ) + if("min") + output_level = 0 + if("max") + output_level = output_level_max + if("set") + output_level = input(usr, "Enter new output level (0-[output_level_max])", "SMES Output Power Control", output_level) as num + output_level = max(0, min(output_level_max, output_level)) // clamp to range + + investigate_log("input/output; [input_level>output_level?"":""][input_level]/[output_level] | Output-mode: [output_attempt?"on":"off"] | Input-mode: [input_attempt?"auto":"off"] by [usr.key]","singulo") + + return 1 + +/obj/machinery/power/smes/proc/ion_act() + if(is_station_level(src.z)) + if(prob(1)) //explosion + for(var/mob/M in viewers(src)) + M.show_message("The [src.name] is making strange noises!", 3, "You hear sizzling electronics.", 2) + sleep(10*pick(4,5,6,7,10,14)) + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(3, 0, src.loc) + smoke.attach(src) + smoke.start() + explosion(src.loc, -1, 0, 1, 3, 1, 0) + qdel(src) + return + if(prob(15)) //Power drain + do_sparks(3, 1, src) + if(prob(50)) + emp_act(1) + else + emp_act(2) + if(prob(5)) //smoke only + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(3, 0, src.loc) + smoke.attach(src) + smoke.start() + +/obj/machinery/power/smes/proc/inputting(var/do_input) + input_attempt = do_input + if(!input_attempt) + inputting = 0 + +/obj/machinery/power/smes/proc/outputting(var/do_output) + output_attempt = do_output + if(!output_attempt) + outputting = 0 + +/obj/machinery/power/smes/emp_act(severity) + inputting(rand(0,1)) + outputting(rand(0,1)) + output_level = rand(0, output_level_max) + input_level = rand(0, input_level_max) + charge -= 1e6/severity + if(charge < 0) + charge = 0 + update_icon() + ..() + +/obj/machinery/power/smes/engineering + charge = 2e6 // Engineering starts with some charge for singulo + +/obj/machinery/power/smes/magical + name = "magical power storage unit" + desc = "A high-capacity superconducting magnetic energy storage (SMES) unit. Magically produces power." + capacity = 9000000 + output_level = 250000 + +/obj/machinery/power/smes/magical/process() + capacity = INFINITY + charge = INFINITY + ..() + +#undef SMESRATE diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index 7d830d69321..3a1f13bd10e 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -1,521 +1,521 @@ -#define SOLAR_MAX_DIST 40 -#define SOLARGENRATE 1500 - -/obj/machinery/power/solar - name = "solar panel" - desc = "A solar panel. Generates electricity when in contact with sunlight." - icon = 'icons/obj/power.dmi' - icon_state = "sp_base" - density = TRUE - use_power = NO_POWER_USE - idle_power_usage = 0 - active_power_usage = 0 - max_integrity = 150 - integrity_failure = 50 - var/id = 0 - var/obscured = 0 - var/sunfrac = 0 - var/adir = SOUTH // actual dir - var/ndir = SOUTH // target dir - var/turn_angle = 0 - var/obj/machinery/power/solar_control/control = null - -/obj/machinery/power/solar/Initialize(mapload, obj/item/solar_assembly/S) - . = ..() - Make(S) - connect_to_network() - -/obj/machinery/power/solar/Destroy() - unset_control() //remove from control computer - return ..() - -//set the control of the panel to a given computer if closer than SOLAR_MAX_DIST -/obj/machinery/power/solar/proc/set_control(var/obj/machinery/power/solar_control/SC) - if(!SC || (get_dist(src, SC) > SOLAR_MAX_DIST)) - return 0 - control = SC - SC.connected_panels |= src - return 1 - -//set the control of the panel to null and removes it from the control list of the previous control computer if needed -/obj/machinery/power/solar/proc/unset_control() - if(control) - control.connected_panels.Remove(src) - control = null - -/obj/machinery/power/solar/proc/Make(var/obj/item/solar_assembly/S) - if(!S) - S = new /obj/item/solar_assembly(src) - S.glass_type = /obj/item/stack/sheet/glass - S.anchored = 1 - S.loc = src - if(S.glass_type == /obj/item/stack/sheet/rglass) //if the panel is in reinforced glass - max_integrity *= 2 //this need to be placed here, because panels already on the map don't have an assembly linked to - obj_integrity = max_integrity - update_icon() - - -/obj/machinery/power/solar/crowbar_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - playsound(loc, 'sound/machines/click.ogg', 50, 1) - user.visible_message("[user] begins to take the glass off the solar panel.", "You begin to take the glass off the solar panel...") - if(I.use_tool(src, user, 50, volume = I.tool_volume)) - user.visible_message("[user] takes the glass off the solar panel.", "You take the glass off the solar panel.") - deconstruct(TRUE) - -/obj/machinery/power/solar/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(stat & BROKEN) - playsound(loc, 'sound/effects/hit_on_shattered_glass.ogg', 60, TRUE) - else - playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) - if(BURN) - playsound(loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/machinery/power/solar/obj_break(damage_flag) - if(!(stat & BROKEN) && !(flags & NODECONSTRUCT)) - playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE) - stat |= BROKEN - unset_control() - update_icon() - -/obj/machinery/power/solar/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(disassembled) - var/obj/item/solar_assembly/S = locate() in src - if(S) - S.forceMove(loc) - S.give_glass(stat & BROKEN) - else - playsound(src, "shatter", 70, TRUE) - new /obj/item/shard(src.loc) - new /obj/item/shard(src.loc) - qdel(src) - -/obj/machinery/power/solar/update_icon() - ..() - overlays.Cut() - if(stat & BROKEN) - overlays += image('icons/obj/power.dmi', icon_state = "solar_panel-b", layer = FLY_LAYER) - else - overlays += image('icons/obj/power.dmi', icon_state = "solar_panel", layer = FLY_LAYER) - src.dir = angle2dir(adir) - return - -//calculates the fraction of the sunlight that the panel recieves -/obj/machinery/power/solar/proc/update_solar_exposure() - if(obscured) - sunfrac = 0 - return - - //find the smaller angle between the direction the panel is facing and the direction of the sun (the sign is not important here) - var/p_angle = min(abs(adir - SSsun.angle), 360 - abs(adir - SSsun.angle)) - - if(p_angle > 90) // if facing more than 90deg from sun, zero output - sunfrac = 0 - return - - sunfrac = cos(p_angle) ** 2 - //isn't the power received from the incoming light proportionnal to cos(p_angle) (Lambert's cosine law) rather than cos(p_angle)^2 ? - -/obj/machinery/power/solar/process()//TODO: remove/add this from machines to save on processing as needed ~Carn PRIORITY - if(stat & BROKEN) - return - if(!control) //if there's no sun or the panel is not linked to a solar control computer, no need to proceed - return - - if(powernet) - if(powernet == control.powernet)//check if the panel is still connected to the computer - if(obscured) //get no light from the sun, so don't generate power - return - var/sgen = SOLARGENRATE * sunfrac - add_avail(sgen) - control.gen += sgen - else //if we're no longer on the same powernet, remove from control computer - unset_control() - -/obj/machinery/power/solar/proc/broken() - . = (!(stat & BROKEN)) - stat |= BROKEN - unset_control() - update_icon() - -/obj/machinery/power/solar/fake/New(var/turf/loc, var/obj/item/solar_assembly/S) - ..(loc, S, 0) - -/obj/machinery/power/solar/fake/process() - . = PROCESS_KILL - return - -//trace towards sun to see if we're in shadow -/obj/machinery/power/solar/proc/occlusion() - - var/ax = x // start at the solar panel - var/ay = y - var/turf/T = null - var/dx = SSsun.dx - var/dy = SSsun.dy - - for(var/i = 1 to 20) // 20 steps is enough - ax += dx // do step - ay += dy - - T = locate( round(ax,0.5),round(ay,0.5),z) - - if(T.x == 1 || T.x==world.maxx || T.y==1 || T.y==world.maxy) // not obscured if we reach the edge - break - - if(T.density) // if we hit a solid turf, panel is obscured - obscured = 1 - return - - obscured = 0 // if hit the edge or stepped 20 times, not obscured - update_solar_exposure() - - -// -// Solar Assembly - For construction of solar arrays. -// - -/obj/item/solar_assembly - name = "solar panel assembly" - desc = "A solar panel assembly kit, allows constructions of a solar panel, or with a tracking circuit board, a solar tracker" - icon = 'icons/obj/power.dmi' - icon_state = "sp_base" - item_state = "electropack" - w_class = WEIGHT_CLASS_BULKY // Pretty big! - anchored = 0 - var/tracker = 0 - var/glass_type = null - -/obj/item/solar_assembly/attack_hand(var/mob/user) - if(!anchored && isturf(loc)) // You can't pick it up - ..() - -// Give back the glass type we were supplied with -/obj/item/solar_assembly/proc/give_glass() - if(glass_type) - var/obj/item/stack/sheet/S = new glass_type(src.loc) - S.amount = 2 - glass_type = null - - -/obj/item/solar_assembly/attackby(var/obj/item/W, var/mob/user, params) - - if(!anchored && isturf(loc)) - if(istype(W, /obj/item/wrench)) - anchored = 1 - user.visible_message("[user] wrenches the solar assembly into place.", "You wrench the solar assembly into place.") - playsound(src.loc, W.usesound, 50, 1) - return 1 - else - if(istype(W, /obj/item/wrench)) - anchored = 0 - user.visible_message("[user] unwrenches the solar assembly from its place.", "You unwrench the solar assembly from its place.") - playsound(src.loc, W.usesound, 50, 1) - return 1 - - if(istype(W, /obj/item/stack/sheet/glass) || istype(W, /obj/item/stack/sheet/rglass)) - var/obj/item/stack/sheet/S = W - if(S.use(2)) - glass_type = W.type - playsound(loc, S.usesound, 50, 1) - user.visible_message("[user] places the glass on the solar assembly.", "You place the glass on the solar assembly.") - if(tracker) - new /obj/machinery/power/tracker(get_turf(src), src) - else - new /obj/machinery/power/solar(get_turf(src), src) - else - to_chat(user, "You need two sheets of glass to put them into a solar panel.") - return - return 1 - - if(!tracker) - if(istype(W, /obj/item/tracker_electronics)) - if(!user.drop_item()) - return - tracker = 1 - qdel(W) - user.visible_message("[user] inserts the electronics into the solar assembly.", "You insert the electronics into the solar assembly.") - return 1 - else if(istype(W, /obj/item/crowbar)) - new /obj/item/tracker_electronics(src.loc) - tracker = 0 - playsound(loc, W.usesound, 50, 1) - user.visible_message("[user] takes out the electronics from the solar assembly.", "You take out the electronics from the solar assembly.") - return 1 - else - return ..() - -// -// Solar Control Computer -// - -/obj/machinery/power/solar_control - name = "solar panel control" - desc = "A controller for solar panel arrays." - icon = 'icons/obj/computer.dmi' - icon_state = "computer" - anchored = 1 - density = 1 - use_power = IDLE_POWER_USE - idle_power_usage = 250 - max_integrity = 200 - integrity_failure = 100 - var/icon_screen = "solar" - var/icon_keyboard = "power_key" - var/id = 0 - var/cdir = 0 - var/targetdir = 0 // target angle in manual tracking (since it updates every game minute) - var/gen = 0 - var/lastgen = 0 - var/track = 0 // 0= off 1=timed 2=auto (tracker) - var/trackrate = 600 // 300-900 seconds - var/nexttime = 0 // time for a panel to rotate of 1? in manual tracking - var/autostart = 0 // Automatically search for connected devices - var/obj/machinery/power/tracker/connected_tracker = null - var/list/connected_panels = list() - -// Used for mapping in solar array which automatically starts itself (telecomms, for example) -/obj/machinery/power/solar_control/autostart - track = 2 // Auto tracking mode - autostart = 1 // Automatically start - -/obj/machinery/power/solar_control/Initialize() - ..() - if(!powernet) - return - connect_to_network() - set_panels(cdir) - if(autostart) - src.search_for_connected() - if(connected_tracker && track == 2) - connected_tracker.set_angle(SSsun.angle) - set_panels(cdir) - -/obj/machinery/power/solar_control/Destroy() - for(var/obj/machinery/power/solar/M in connected_panels) - M.unset_control() - if(connected_tracker) - connected_tracker.unset_control() - return ..() - -/obj/machinery/power/solar_control/disconnect_from_network() - ..() - SSsun.solars.Remove(src) - -/obj/machinery/power/solar_control/connect_to_network() - var/to_return = ..() - if(powernet) //if connected and not already in solar list... - SSsun.solars |= src //... add it - return to_return - -//search for unconnected panels and trackers in the computer powernet and connect them -/obj/machinery/power/solar_control/proc/search_for_connected() - if(powernet) - for(var/obj/machinery/power/M in powernet.nodes) - if(istype(M, /obj/machinery/power/solar)) - var/obj/machinery/power/solar/S = M - if(!S.control) //i.e unconnected - S.set_control(src) - else if(istype(M, /obj/machinery/power/tracker)) - if(!connected_tracker) //if there's already a tracker connected to the computer don't add another - var/obj/machinery/power/tracker/T = M - if(!T.control) //i.e unconnected - T.set_control(src) - -//called by the sun controller, update the facing angle (either manually or via tracking) and rotates the panels accordingly -/obj/machinery/power/solar_control/proc/update() - if(stat & (NOPOWER | BROKEN)) - return - - switch(track) - if(1) - if(trackrate) //we're manual tracking. If we set a rotation speed... - cdir = targetdir //...the current direction is the targetted one (and rotates panels to it) - if(2) // auto-tracking - if(connected_tracker) - connected_tracker.set_angle(SSsun.angle) - - set_panels(cdir) - updateDialog() - -/obj/machinery/power/solar_control/update_icon() - overlays.Cut() - if(stat & NOPOWER) - overlays += "[icon_keyboard]_off" - return - overlays += icon_keyboard - if(stat & BROKEN) - overlays += "[icon_state]_broken" - else - overlays += icon_screen - if(cdir > -1) - overlays += image('icons/obj/computer.dmi', "solcon-o", FLY_LAYER, angle2dir(cdir)) - -/obj/machinery/power/solar_control/attack_ai(mob/user as mob) - src.add_hiddenprint(user) - ui_interact(user) - -/obj/machinery/power/solar_control/attack_ghost(mob/user as mob) - ui_interact(user) - -/obj/machinery/power/solar_control/attack_hand(mob/user) - if(..(user)) - return 1 - - if(stat & BROKEN) - return - - ui_interact(user) - -/obj/machinery/power/solar_control/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "solar_control.tmpl", name, 490, 420) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/power/solar_control/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - data["generated"] = round(lastgen) - data["angle"] = cdir - data["direction"] = angle2text(cdir) - - data["tracking_state"] = track - data["tracking_rate"] = trackrate - data["rotating_way"] = (trackrate<0 ? "CCW" : "CW") - - data["connected_panels"] = connected_panels.len - data["connected_tracker"] = (connected_tracker ? 1 : 0) - - return data - -/obj/machinery/power/solar_control/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/screwdriver)) - playsound(src.loc, I.usesound, 50, 1) - if(do_after(user, 20 * I.toolspeed, target = src)) - if(src.stat & BROKEN) - to_chat(user, "The broken glass falls out.") - var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc ) - new /obj/item/shard( src.loc ) - var/obj/item/circuitboard/solar_control/M = new /obj/item/circuitboard/solar_control( A ) - for(var/obj/C in src) - C.loc = src.loc - A.circuit = M - A.state = 3 - A.icon_state = "3" - A.anchored = 1 - qdel(src) - else - to_chat(user, "You disconnect the monitor.") - var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc ) - var/obj/item/circuitboard/solar_control/M = new /obj/item/circuitboard/solar_control( A ) - for(var/obj/C in src) - C.loc = src.loc - A.circuit = M - A.state = 4 - A.icon_state = "4" - A.anchored = 1 - qdel(src) - else - return ..() - -/obj/machinery/power/solar_control/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(stat & BROKEN) - playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) - else - playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) - -/obj/machinery/power/solar_control/obj_break(damage_flag) - if(!(stat & BROKEN) && !(flags & NODECONSTRUCT)) - playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE) - stat |= BROKEN - update_icon() - -/obj/machinery/power/solar_control/process() - lastgen = gen - gen = 0 - - if(stat & (NOPOWER | BROKEN)) - return - - if(connected_tracker) //NOTE : handled here so that we don't add trackers to the processing list - if(connected_tracker.powernet != powernet) - connected_tracker.unset_control() - - if(track==1 && trackrate) //manual tracking and set a rotation speed - if(nexttime <= world.time) //every time we need to increase/decrease the angle by 1?... - targetdir = (targetdir + trackrate/abs(trackrate) + 360) % 360 //... do it - nexttime += 36000/abs(trackrate) //reset the counter for the next 1? - -/obj/machinery/power/solar_control/Topic(href, href_list) - if(..()) - return - - if(href_list["rate_control"]) - if(href_list["cdir"]) - src.cdir = dd_range(0,359,(360+src.cdir+text2num(href_list["cdir"]))%360) - src.targetdir = src.cdir - if(track == 2) //manual update, so losing auto-tracking - track = 0 - spawn(1) - set_panels(cdir) - if(href_list["tdir"]) - src.trackrate = dd_range(-7200,7200,src.trackrate+text2num(href_list["tdir"])) - if(src.trackrate) nexttime = world.time + 36000/abs(trackrate) - - if(href_list["track"]) - track = text2num(href_list["track"]) - if(track == 2) - if(connected_tracker) - connected_tracker.set_angle(SSsun.angle) - set_panels(cdir) - else if(track == 1) //begin manual tracking - src.targetdir = src.cdir - if(src.trackrate) nexttime = world.time + 36000/abs(trackrate) - set_panels(targetdir) - - if(href_list["search_connected"]) - search_for_connected() - if(connected_tracker && track == 2) - connected_tracker.set_angle(SSsun.angle) - set_panels(cdir) - - return - -//rotates the panel to the passed angle -/obj/machinery/power/solar_control/proc/set_panels(var/cdir) - - for(var/obj/machinery/power/solar/S in connected_panels) - S.adir = cdir //instantly rotates the panel - S.occlusion()//and - S.update_icon() //update it - - update_icon() - - -/obj/machinery/power/solar_control/power_change() - ..() - update_icon() - - -/obj/machinery/power/solar_control/proc/broken() - stat |= BROKEN - update_icon() - -// -// MISC -// - -/obj/item/paper/solar - name = "paper- 'Going green! Setup your own solar array instructions.'" - info = "

        Welcome

        At greencorps we love the environment, and space. With this package you are able to help mother nature and produce energy without any usage of fossil fuel or plasma! Singularity energy is dangerous while solar energy is safe, which is why it's better. Now here is how you setup your own solar array.

        You can make a solar panel by wrenching the solar assembly onto a cable node. Adding a glass panel, reinforced or regular glass will do, will finish the construction of your solar panel. It is that easy!

        Now after setting up 19 more of these solar panels you will want to create a solar tracker to keep track of our mother nature's gift, the sun. These are the same steps as before except you insert the tracker equipment circuit into the assembly before performing the final step of adding the glass. You now have a tracker! Now the last step is to add a computer to calculate the sun's movements and to send commands to the solar panels to change direction with the sun. Setting up the solar computer is the same as setting up any computer, so you should have no trouble in doing that. You do need to put a wire node under the computer, and the wire needs to be connected to the tracker.

        Congratulations, you should have a working solar array. If you are having trouble, here are some tips. Make sure all solar equipment are on a cable node, even the computer. You can always deconstruct your creations if you make a mistake.

        That's all to it, be safe, be green!

        " +#define SOLAR_MAX_DIST 40 +#define SOLARGENRATE 1500 + +/obj/machinery/power/solar + name = "solar panel" + desc = "A solar panel. Generates electricity when in contact with sunlight." + icon = 'icons/obj/power.dmi' + icon_state = "sp_base" + density = TRUE + use_power = NO_POWER_USE + idle_power_usage = 0 + active_power_usage = 0 + max_integrity = 150 + integrity_failure = 50 + var/id = 0 + var/obscured = 0 + var/sunfrac = 0 + var/adir = SOUTH // actual dir + var/ndir = SOUTH // target dir + var/turn_angle = 0 + var/obj/machinery/power/solar_control/control = null + +/obj/machinery/power/solar/Initialize(mapload, obj/item/solar_assembly/S) + . = ..() + Make(S) + connect_to_network() + +/obj/machinery/power/solar/Destroy() + unset_control() //remove from control computer + return ..() + +//set the control of the panel to a given computer if closer than SOLAR_MAX_DIST +/obj/machinery/power/solar/proc/set_control(var/obj/machinery/power/solar_control/SC) + if(!SC || (get_dist(src, SC) > SOLAR_MAX_DIST)) + return 0 + control = SC + SC.connected_panels |= src + return 1 + +//set the control of the panel to null and removes it from the control list of the previous control computer if needed +/obj/machinery/power/solar/proc/unset_control() + if(control) + control.connected_panels.Remove(src) + control = null + +/obj/machinery/power/solar/proc/Make(var/obj/item/solar_assembly/S) + if(!S) + S = new /obj/item/solar_assembly(src) + S.glass_type = /obj/item/stack/sheet/glass + S.anchored = 1 + S.loc = src + if(S.glass_type == /obj/item/stack/sheet/rglass) //if the panel is in reinforced glass + max_integrity *= 2 //this need to be placed here, because panels already on the map don't have an assembly linked to + obj_integrity = max_integrity + update_icon() + + +/obj/machinery/power/solar/crowbar_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + playsound(loc, 'sound/machines/click.ogg', 50, 1) + user.visible_message("[user] begins to take the glass off the solar panel.", "You begin to take the glass off the solar panel...") + if(I.use_tool(src, user, 50, volume = I.tool_volume)) + user.visible_message("[user] takes the glass off the solar panel.", "You take the glass off the solar panel.") + deconstruct(TRUE) + +/obj/machinery/power/solar/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(stat & BROKEN) + playsound(loc, 'sound/effects/hit_on_shattered_glass.ogg', 60, TRUE) + else + playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) + if(BURN) + playsound(loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/machinery/power/solar/obj_break(damage_flag) + if(!(stat & BROKEN) && !(flags & NODECONSTRUCT)) + playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE) + stat |= BROKEN + unset_control() + update_icon() + +/obj/machinery/power/solar/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(disassembled) + var/obj/item/solar_assembly/S = locate() in src + if(S) + S.forceMove(loc) + S.give_glass(stat & BROKEN) + else + playsound(src, "shatter", 70, TRUE) + new /obj/item/shard(src.loc) + new /obj/item/shard(src.loc) + qdel(src) + +/obj/machinery/power/solar/update_icon() + ..() + overlays.Cut() + if(stat & BROKEN) + overlays += image('icons/obj/power.dmi', icon_state = "solar_panel-b", layer = FLY_LAYER) + else + overlays += image('icons/obj/power.dmi', icon_state = "solar_panel", layer = FLY_LAYER) + src.dir = angle2dir(adir) + return + +//calculates the fraction of the sunlight that the panel recieves +/obj/machinery/power/solar/proc/update_solar_exposure() + if(obscured) + sunfrac = 0 + return + + //find the smaller angle between the direction the panel is facing and the direction of the sun (the sign is not important here) + var/p_angle = min(abs(adir - SSsun.angle), 360 - abs(adir - SSsun.angle)) + + if(p_angle > 90) // if facing more than 90deg from sun, zero output + sunfrac = 0 + return + + sunfrac = cos(p_angle) ** 2 + //isn't the power received from the incoming light proportionnal to cos(p_angle) (Lambert's cosine law) rather than cos(p_angle)^2 ? + +/obj/machinery/power/solar/process()//TODO: remove/add this from machines to save on processing as needed ~Carn PRIORITY + if(stat & BROKEN) + return + if(!control) //if there's no sun or the panel is not linked to a solar control computer, no need to proceed + return + + if(powernet) + if(powernet == control.powernet)//check if the panel is still connected to the computer + if(obscured) //get no light from the sun, so don't generate power + return + var/sgen = SOLARGENRATE * sunfrac + add_avail(sgen) + control.gen += sgen + else //if we're no longer on the same powernet, remove from control computer + unset_control() + +/obj/machinery/power/solar/proc/broken() + . = (!(stat & BROKEN)) + stat |= BROKEN + unset_control() + update_icon() + +/obj/machinery/power/solar/fake/New(var/turf/loc, var/obj/item/solar_assembly/S) + ..(loc, S, 0) + +/obj/machinery/power/solar/fake/process() + . = PROCESS_KILL + return + +//trace towards sun to see if we're in shadow +/obj/machinery/power/solar/proc/occlusion() + + var/ax = x // start at the solar panel + var/ay = y + var/turf/T = null + var/dx = SSsun.dx + var/dy = SSsun.dy + + for(var/i = 1 to 20) // 20 steps is enough + ax += dx // do step + ay += dy + + T = locate( round(ax,0.5),round(ay,0.5),z) + + if(T.x == 1 || T.x==world.maxx || T.y==1 || T.y==world.maxy) // not obscured if we reach the edge + break + + if(T.density) // if we hit a solid turf, panel is obscured + obscured = 1 + return + + obscured = 0 // if hit the edge or stepped 20 times, not obscured + update_solar_exposure() + + +// +// Solar Assembly - For construction of solar arrays. +// + +/obj/item/solar_assembly + name = "solar panel assembly" + desc = "A solar panel assembly kit, allows constructions of a solar panel, or with a tracking circuit board, a solar tracker" + icon = 'icons/obj/power.dmi' + icon_state = "sp_base" + item_state = "electropack" + w_class = WEIGHT_CLASS_BULKY // Pretty big! + anchored = 0 + var/tracker = 0 + var/glass_type = null + +/obj/item/solar_assembly/attack_hand(var/mob/user) + if(!anchored && isturf(loc)) // You can't pick it up + ..() + +// Give back the glass type we were supplied with +/obj/item/solar_assembly/proc/give_glass() + if(glass_type) + var/obj/item/stack/sheet/S = new glass_type(src.loc) + S.amount = 2 + glass_type = null + + +/obj/item/solar_assembly/attackby(var/obj/item/W, var/mob/user, params) + + if(!anchored && isturf(loc)) + if(istype(W, /obj/item/wrench)) + anchored = 1 + user.visible_message("[user] wrenches the solar assembly into place.", "You wrench the solar assembly into place.") + playsound(src.loc, W.usesound, 50, 1) + return 1 + else + if(istype(W, /obj/item/wrench)) + anchored = 0 + user.visible_message("[user] unwrenches the solar assembly from its place.", "You unwrench the solar assembly from its place.") + playsound(src.loc, W.usesound, 50, 1) + return 1 + + if(istype(W, /obj/item/stack/sheet/glass) || istype(W, /obj/item/stack/sheet/rglass)) + var/obj/item/stack/sheet/S = W + if(S.use(2)) + glass_type = W.type + playsound(loc, S.usesound, 50, 1) + user.visible_message("[user] places the glass on the solar assembly.", "You place the glass on the solar assembly.") + if(tracker) + new /obj/machinery/power/tracker(get_turf(src), src) + else + new /obj/machinery/power/solar(get_turf(src), src) + else + to_chat(user, "You need two sheets of glass to put them into a solar panel.") + return + return 1 + + if(!tracker) + if(istype(W, /obj/item/tracker_electronics)) + if(!user.drop_item()) + return + tracker = 1 + qdel(W) + user.visible_message("[user] inserts the electronics into the solar assembly.", "You insert the electronics into the solar assembly.") + return 1 + else if(istype(W, /obj/item/crowbar)) + new /obj/item/tracker_electronics(src.loc) + tracker = 0 + playsound(loc, W.usesound, 50, 1) + user.visible_message("[user] takes out the electronics from the solar assembly.", "You take out the electronics from the solar assembly.") + return 1 + else + return ..() + +// +// Solar Control Computer +// + +/obj/machinery/power/solar_control + name = "solar panel control" + desc = "A controller for solar panel arrays." + icon = 'icons/obj/computer.dmi' + icon_state = "computer" + anchored = 1 + density = 1 + use_power = IDLE_POWER_USE + idle_power_usage = 250 + max_integrity = 200 + integrity_failure = 100 + var/icon_screen = "solar" + var/icon_keyboard = "power_key" + var/id = 0 + var/cdir = 0 + var/targetdir = 0 // target angle in manual tracking (since it updates every game minute) + var/gen = 0 + var/lastgen = 0 + var/track = 0 // 0= off 1=timed 2=auto (tracker) + var/trackrate = 600 // 300-900 seconds + var/nexttime = 0 // time for a panel to rotate of 1? in manual tracking + var/autostart = 0 // Automatically search for connected devices + var/obj/machinery/power/tracker/connected_tracker = null + var/list/connected_panels = list() + +// Used for mapping in solar array which automatically starts itself (telecomms, for example) +/obj/machinery/power/solar_control/autostart + track = 2 // Auto tracking mode + autostart = 1 // Automatically start + +/obj/machinery/power/solar_control/Initialize() + ..() + if(!powernet) + return + connect_to_network() + set_panels(cdir) + if(autostart) + src.search_for_connected() + if(connected_tracker && track == 2) + connected_tracker.set_angle(SSsun.angle) + set_panels(cdir) + +/obj/machinery/power/solar_control/Destroy() + for(var/obj/machinery/power/solar/M in connected_panels) + M.unset_control() + if(connected_tracker) + connected_tracker.unset_control() + return ..() + +/obj/machinery/power/solar_control/disconnect_from_network() + ..() + SSsun.solars.Remove(src) + +/obj/machinery/power/solar_control/connect_to_network() + var/to_return = ..() + if(powernet) //if connected and not already in solar list... + SSsun.solars |= src //... add it + return to_return + +//search for unconnected panels and trackers in the computer powernet and connect them +/obj/machinery/power/solar_control/proc/search_for_connected() + if(powernet) + for(var/obj/machinery/power/M in powernet.nodes) + if(istype(M, /obj/machinery/power/solar)) + var/obj/machinery/power/solar/S = M + if(!S.control) //i.e unconnected + S.set_control(src) + else if(istype(M, /obj/machinery/power/tracker)) + if(!connected_tracker) //if there's already a tracker connected to the computer don't add another + var/obj/machinery/power/tracker/T = M + if(!T.control) //i.e unconnected + T.set_control(src) + +//called by the sun controller, update the facing angle (either manually or via tracking) and rotates the panels accordingly +/obj/machinery/power/solar_control/proc/update() + if(stat & (NOPOWER | BROKEN)) + return + + switch(track) + if(1) + if(trackrate) //we're manual tracking. If we set a rotation speed... + cdir = targetdir //...the current direction is the targetted one (and rotates panels to it) + if(2) // auto-tracking + if(connected_tracker) + connected_tracker.set_angle(SSsun.angle) + + set_panels(cdir) + updateDialog() + +/obj/machinery/power/solar_control/update_icon() + overlays.Cut() + if(stat & NOPOWER) + overlays += "[icon_keyboard]_off" + return + overlays += icon_keyboard + if(stat & BROKEN) + overlays += "[icon_state]_broken" + else + overlays += icon_screen + if(cdir > -1) + overlays += image('icons/obj/computer.dmi', "solcon-o", FLY_LAYER, angle2dir(cdir)) + +/obj/machinery/power/solar_control/attack_ai(mob/user as mob) + src.add_hiddenprint(user) + ui_interact(user) + +/obj/machinery/power/solar_control/attack_ghost(mob/user as mob) + ui_interact(user) + +/obj/machinery/power/solar_control/attack_hand(mob/user) + if(..(user)) + return 1 + + if(stat & BROKEN) + return + + ui_interact(user) + +/obj/machinery/power/solar_control/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "solar_control.tmpl", name, 490, 420) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/power/solar_control/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + data["generated"] = round(lastgen) + data["angle"] = cdir + data["direction"] = angle2text(cdir) + + data["tracking_state"] = track + data["tracking_rate"] = trackrate + data["rotating_way"] = (trackrate<0 ? "CCW" : "CW") + + data["connected_panels"] = connected_panels.len + data["connected_tracker"] = (connected_tracker ? 1 : 0) + + return data + +/obj/machinery/power/solar_control/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/screwdriver)) + playsound(src.loc, I.usesound, 50, 1) + if(do_after(user, 20 * I.toolspeed, target = src)) + if(src.stat & BROKEN) + to_chat(user, "The broken glass falls out.") + var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc ) + new /obj/item/shard( src.loc ) + var/obj/item/circuitboard/solar_control/M = new /obj/item/circuitboard/solar_control( A ) + for(var/obj/C in src) + C.loc = src.loc + A.circuit = M + A.state = 3 + A.icon_state = "3" + A.anchored = 1 + qdel(src) + else + to_chat(user, "You disconnect the monitor.") + var/obj/structure/computerframe/A = new /obj/structure/computerframe( src.loc ) + var/obj/item/circuitboard/solar_control/M = new /obj/item/circuitboard/solar_control( A ) + for(var/obj/C in src) + C.loc = src.loc + A.circuit = M + A.state = 4 + A.icon_state = "4" + A.anchored = 1 + qdel(src) + else + return ..() + +/obj/machinery/power/solar_control/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(stat & BROKEN) + playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE) + else + playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE) + +/obj/machinery/power/solar_control/obj_break(damage_flag) + if(!(stat & BROKEN) && !(flags & NODECONSTRUCT)) + playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE) + stat |= BROKEN + update_icon() + +/obj/machinery/power/solar_control/process() + lastgen = gen + gen = 0 + + if(stat & (NOPOWER | BROKEN)) + return + + if(connected_tracker) //NOTE : handled here so that we don't add trackers to the processing list + if(connected_tracker.powernet != powernet) + connected_tracker.unset_control() + + if(track==1 && trackrate) //manual tracking and set a rotation speed + if(nexttime <= world.time) //every time we need to increase/decrease the angle by 1?... + targetdir = (targetdir + trackrate/abs(trackrate) + 360) % 360 //... do it + nexttime += 36000/abs(trackrate) //reset the counter for the next 1? + +/obj/machinery/power/solar_control/Topic(href, href_list) + if(..()) + return + + if(href_list["rate_control"]) + if(href_list["cdir"]) + src.cdir = dd_range(0,359,(360+src.cdir+text2num(href_list["cdir"]))%360) + src.targetdir = src.cdir + if(track == 2) //manual update, so losing auto-tracking + track = 0 + spawn(1) + set_panels(cdir) + if(href_list["tdir"]) + src.trackrate = dd_range(-7200,7200,src.trackrate+text2num(href_list["tdir"])) + if(src.trackrate) nexttime = world.time + 36000/abs(trackrate) + + if(href_list["track"]) + track = text2num(href_list["track"]) + if(track == 2) + if(connected_tracker) + connected_tracker.set_angle(SSsun.angle) + set_panels(cdir) + else if(track == 1) //begin manual tracking + src.targetdir = src.cdir + if(src.trackrate) nexttime = world.time + 36000/abs(trackrate) + set_panels(targetdir) + + if(href_list["search_connected"]) + search_for_connected() + if(connected_tracker && track == 2) + connected_tracker.set_angle(SSsun.angle) + set_panels(cdir) + + return + +//rotates the panel to the passed angle +/obj/machinery/power/solar_control/proc/set_panels(var/cdir) + + for(var/obj/machinery/power/solar/S in connected_panels) + S.adir = cdir //instantly rotates the panel + S.occlusion()//and + S.update_icon() //update it + + update_icon() + + +/obj/machinery/power/solar_control/power_change() + ..() + update_icon() + + +/obj/machinery/power/solar_control/proc/broken() + stat |= BROKEN + update_icon() + +// +// MISC +// + +/obj/item/paper/solar + name = "paper- 'Going green! Setup your own solar array instructions.'" + info = "

        Welcome

        At greencorps we love the environment, and space. With this package you are able to help mother nature and produce energy without any usage of fossil fuel or plasma! Singularity energy is dangerous while solar energy is safe, which is why it's better. Now here is how you setup your own solar array.

        You can make a solar panel by wrenching the solar assembly onto a cable node. Adding a glass panel, reinforced or regular glass will do, will finish the construction of your solar panel. It is that easy!

        Now after setting up 19 more of these solar panels you will want to create a solar tracker to keep track of our mother nature's gift, the sun. These are the same steps as before except you insert the tracker equipment circuit into the assembly before performing the final step of adding the glass. You now have a tracker! Now the last step is to add a computer to calculate the sun's movements and to send commands to the solar panels to change direction with the sun. Setting up the solar computer is the same as setting up any computer, so you should have no trouble in doing that. You do need to put a wire node under the computer, and the wire needs to be connected to the tracker.

        Congratulations, you should have a working solar array. If you are having trouble, here are some tips. Make sure all solar equipment are on a cable node, even the computer. You can always deconstruct your creations if you make a mistake.

        That's all to it, be safe, be green!

        " diff --git a/code/modules/power/terminal.dm b/code/modules/power/terminal.dm index 058872cde6d..e66128ba724 100644 --- a/code/modules/power/terminal.dm +++ b/code/modules/power/terminal.dm @@ -1,76 +1,76 @@ -// the underfloor wiring terminal for the APC -// autogenerated when an APC is placed -// all conduit connects go to this object instead of the APC -// using this solves the problem of having the APC in a wall yet also inside an area - -/obj/machinery/power/terminal - name = "terminal" - icon_state = "term" - desc = "It's an underfloor wiring terminal for power equipment." - level = 1 - layer = WIRE_TERMINAL_LAYER //a bit above wires - var/obj/machinery/power/master = null - - -/obj/machinery/power/terminal/Initialize(mapload) - . = ..() - var/turf/T = get_turf(src) - if(level == 1) - hide(T.intact) - -/obj/machinery/power/terminal/Destroy() - if(master) - master.disconnect_terminal() - master = null - return ..() - - -/obj/machinery/power/terminal/hide(i) - if(i) - invisibility = 101 - icon_state = "term-f" - else - invisibility = 0 - icon_state = "term" - -/obj/machinery/power/proc/can_terminal_dismantle() - . = 0 - -/obj/machinery/power/apc/can_terminal_dismantle() - . = 0 - if(opened) - . = 1 - -/obj/machinery/power/smes/can_terminal_dismantle() - . = 0 - if(panel_open) - . = 1 - - -/obj/machinery/power/terminal/proc/dismantle(mob/living/user, obj/item/W) - if(isturf(loc)) - var/turf/T = loc - if(T.intact) - to_chat(user, "You must first expose the power terminal!") - return - - if(!master || master.can_terminal_dismantle()) - user.visible_message("[user.name] dismantles the power terminal from [master].", \ - "You begin to cut the cables...") - - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) - if(do_after(user, 50*W.toolspeed, target = src)) - if(!master || master.can_terminal_dismantle()) - if(prob(50) && electrocute_mob(user, powernet, src, 1, TRUE)) - do_sparks(5, TRUE, master) - return - new /obj/item/stack/cable_coil(loc, 10) - to_chat(user, "You cut the cables and dismantle the power terminal.") - qdel(src) - - -/obj/machinery/power/terminal/attackby(obj/item/W, mob/living/user, params) - if(istype(W, /obj/item/wirecutters)) - dismantle(user, W) - else - return ..() \ No newline at end of file +// the underfloor wiring terminal for the APC +// autogenerated when an APC is placed +// all conduit connects go to this object instead of the APC +// using this solves the problem of having the APC in a wall yet also inside an area + +/obj/machinery/power/terminal + name = "terminal" + icon_state = "term" + desc = "It's an underfloor wiring terminal for power equipment." + level = 1 + layer = WIRE_TERMINAL_LAYER //a bit above wires + var/obj/machinery/power/master = null + + +/obj/machinery/power/terminal/Initialize(mapload) + . = ..() + var/turf/T = get_turf(src) + if(level == 1) + hide(T.intact) + +/obj/machinery/power/terminal/Destroy() + if(master) + master.disconnect_terminal() + master = null + return ..() + + +/obj/machinery/power/terminal/hide(i) + if(i) + invisibility = 101 + icon_state = "term-f" + else + invisibility = 0 + icon_state = "term" + +/obj/machinery/power/proc/can_terminal_dismantle() + . = 0 + +/obj/machinery/power/apc/can_terminal_dismantle() + . = 0 + if(opened) + . = 1 + +/obj/machinery/power/smes/can_terminal_dismantle() + . = 0 + if(panel_open) + . = 1 + + +/obj/machinery/power/terminal/proc/dismantle(mob/living/user, obj/item/W) + if(isturf(loc)) + var/turf/T = loc + if(T.intact) + to_chat(user, "You must first expose the power terminal!") + return + + if(!master || master.can_terminal_dismantle()) + user.visible_message("[user.name] dismantles the power terminal from [master].", \ + "You begin to cut the cables...") + + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + if(do_after(user, 50*W.toolspeed, target = src)) + if(!master || master.can_terminal_dismantle()) + if(prob(50) && electrocute_mob(user, powernet, src, 1, TRUE)) + do_sparks(5, TRUE, master) + return + new /obj/item/stack/cable_coil(loc, 10) + to_chat(user, "You cut the cables and dismantle the power terminal.") + qdel(src) + + +/obj/machinery/power/terminal/attackby(obj/item/W, mob/living/user, params) + if(istype(W, /obj/item/wirecutters)) + dismantle(user, W) + else + return ..() diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm index 7d7910b45d4..a3a8bb42ec6 100644 --- a/code/modules/power/tesla/energy_ball.dm +++ b/code/modules/power/tesla/energy_ball.dm @@ -288,4 +288,4 @@ closest_blob.tesla_act(power, explosive) else if(closest_structure) - closest_structure.tesla_act(power, explosive) \ No newline at end of file + closest_structure.tesla_act(power, explosive) diff --git a/code/modules/power/tesla/generator.dm b/code/modules/power/tesla/generator.dm index 53eea847ad9..63bce099fab 100644 --- a/code/modules/power/tesla/generator.dm +++ b/code/modules/power/tesla/generator.dm @@ -7,4 +7,4 @@ /obj/machinery/the_singularitygen/tesla/tesla_act(power, explosive = FALSE) if(explosive) - energy += power \ No newline at end of file + energy += power diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm index 69dc8c4fd03..c8964c7ec34 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/tracker.dm @@ -1,100 +1,100 @@ -//Solar tracker - -//Machine that tracks the sun and reports it's direction to the solar controllers -//As long as this is working, solar panels on same powernet will track automatically - -/obj/machinery/power/tracker - name = "solar tracker" - desc = "A solar directional tracker." - icon = 'icons/obj/power.dmi' - icon_state = "tracker" - density = TRUE - use_power = NO_POWER_USE - max_integrity = 250 - integrity_failure = 50 - - var/id = 0 - var/sun_angle = 0 // sun angle as set by sun datum - var/obj/machinery/power/solar_control/control = null - -/obj/machinery/power/tracker/Initialize(mapload, obj/item/solar_assembly/S) - . = ..() - Make(S) - connect_to_network() - -/obj/machinery/power/tracker/Destroy() - unset_control() //remove from control computer - return ..() - -//set the control of the tracker to a given computer if closer than SOLAR_MAX_DIST -/obj/machinery/power/tracker/proc/set_control(obj/machinery/power/solar_control/SC) - if(SC && (get_dist(src, SC) > SOLAR_MAX_DIST)) - return 0 - control = SC - SC.connected_tracker = src - return 1 - -//set the control of the tracker to null and removes it from the previous control computer if needed -/obj/machinery/power/tracker/proc/unset_control() - if(control) - control.connected_tracker = null - control = null - -/obj/machinery/power/tracker/proc/Make(obj/item/solar_assembly/S) - if(!S) - S = new /obj/item/solar_assembly(src) - S.glass_type = /obj/item/stack/sheet/glass - S.tracker = 1 - S.anchored = TRUE - S.forceMove(src) - update_icon() - -//updates the tracker icon and the facing angle for the control computer -/obj/machinery/power/tracker/proc/set_angle(angle) - sun_angle = angle - - //set icon dir to show sun illumination - setDir(turn(NORTH, -angle - 22.5)) // 22.5 deg bias ensures, e.g. 67.5-112.5 is EAST - - if(powernet && (powernet == control.powernet)) //update if we're still in the same powernet - control.cdir = angle - -/obj/machinery/power/tracker/crowbar_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - playsound(src.loc, 'sound/machines/click.ogg', 50, 1) - user.visible_message("[user] begins to take the glass off the solar tracker.") - if(I.use_tool(src, user, 50, volume = I.tool_volume)) - user.visible_message("[user] takes the glass off the tracker.") - deconstruct(TRUE) - - -/obj/machinery/power/tracker/obj_break(damage_flag) - if(!(stat & BROKEN) && !(flags & NODECONSTRUCT)) - playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE) - stat |= BROKEN - unset_control() - -/obj/machinery/power/solar/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(disassembled) - var/obj/item/solar_assembly/S = locate() in src - if(S) - S.forceMove(loc) - S.give_glass(stat & BROKEN) - else - playsound(src, "shatter", 70, TRUE) - new /obj/item/shard(src.loc) - new /obj/item/shard(src.loc) - qdel(src) - -// Tracker Electronic - -/obj/item/tracker_electronics - - name = "tracker electronics" - icon = 'icons/obj/doors/door_assembly.dmi' - icon_state = "door_electronics" - w_class = WEIGHT_CLASS_SMALL - origin_tech = "engineering=2;programming=1" +//Solar tracker + +//Machine that tracks the sun and reports it's direction to the solar controllers +//As long as this is working, solar panels on same powernet will track automatically + +/obj/machinery/power/tracker + name = "solar tracker" + desc = "A solar directional tracker." + icon = 'icons/obj/power.dmi' + icon_state = "tracker" + density = TRUE + use_power = NO_POWER_USE + max_integrity = 250 + integrity_failure = 50 + + var/id = 0 + var/sun_angle = 0 // sun angle as set by sun datum + var/obj/machinery/power/solar_control/control = null + +/obj/machinery/power/tracker/Initialize(mapload, obj/item/solar_assembly/S) + . = ..() + Make(S) + connect_to_network() + +/obj/machinery/power/tracker/Destroy() + unset_control() //remove from control computer + return ..() + +//set the control of the tracker to a given computer if closer than SOLAR_MAX_DIST +/obj/machinery/power/tracker/proc/set_control(obj/machinery/power/solar_control/SC) + if(SC && (get_dist(src, SC) > SOLAR_MAX_DIST)) + return 0 + control = SC + SC.connected_tracker = src + return 1 + +//set the control of the tracker to null and removes it from the previous control computer if needed +/obj/machinery/power/tracker/proc/unset_control() + if(control) + control.connected_tracker = null + control = null + +/obj/machinery/power/tracker/proc/Make(obj/item/solar_assembly/S) + if(!S) + S = new /obj/item/solar_assembly(src) + S.glass_type = /obj/item/stack/sheet/glass + S.tracker = 1 + S.anchored = TRUE + S.forceMove(src) + update_icon() + +//updates the tracker icon and the facing angle for the control computer +/obj/machinery/power/tracker/proc/set_angle(angle) + sun_angle = angle + + //set icon dir to show sun illumination + setDir(turn(NORTH, -angle - 22.5)) // 22.5 deg bias ensures, e.g. 67.5-112.5 is EAST + + if(powernet && (powernet == control.powernet)) //update if we're still in the same powernet + control.cdir = angle + +/obj/machinery/power/tracker/crowbar_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + playsound(src.loc, 'sound/machines/click.ogg', 50, 1) + user.visible_message("[user] begins to take the glass off the solar tracker.") + if(I.use_tool(src, user, 50, volume = I.tool_volume)) + user.visible_message("[user] takes the glass off the tracker.") + deconstruct(TRUE) + + +/obj/machinery/power/tracker/obj_break(damage_flag) + if(!(stat & BROKEN) && !(flags & NODECONSTRUCT)) + playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE) + stat |= BROKEN + unset_control() + +/obj/machinery/power/solar/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(disassembled) + var/obj/item/solar_assembly/S = locate() in src + if(S) + S.forceMove(loc) + S.give_glass(stat & BROKEN) + else + playsound(src, "shatter", 70, TRUE) + new /obj/item/shard(src.loc) + new /obj/item/shard(src.loc) + qdel(src) + +// Tracker Electronic + +/obj/item/tracker_electronics + + name = "tracker electronics" + icon = 'icons/obj/doors/door_assembly.dmi' + icon_state = "door_electronics" + w_class = WEIGHT_CLASS_SMALL + origin_tech = "engineering=2;programming=1" diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm index 15a9dcbdb84..97499ba6b3b 100644 --- a/code/modules/power/turbine.dm +++ b/code/modules/power/turbine.dm @@ -1,405 +1,405 @@ -// TURBINE v2 AKA rev4407 Engine reborn! - -// How to use it? - Mappers -// -// This is a very good power generating mechanism. All you need is a blast furnace with soaring flames and output. -// Not everything is included yet so the turbine can run out of fuel quiet quickly. The best thing about the turbine is that even -// though something is on fire that passes through it, it won't be on fire as it passes out of it. So the exhaust fumes can still -// containt unreacted fuel - plasma and oxygen that needs to be filtered out and re-routed back. This of course requires smart piping -// For a computer to work with the turbine the compressor requires a comp_id matching with the turbine computer's id. This will be -// subjected to a change in the near future mind you. Right now this method of generating power is a good backup but don't expect it -// become a main power source unless some work is done. Have fun. At 50k RPM it generates 60k power. So more than one turbine is needed! -// -// - Numbers -// -// Example setup S - sparker -// B - Blast doors into space for venting -// *BBB****BBB* C - Compressor -// S CT * T - Turbine -// * ^ * * V * D - Doors with firedoor -// **|***D**|** ^ - Fuel feed (Not vent, but a gas outlet) -// | | V - Suction vent (Like the ones in atmos -// - - -/obj/machinery/power/compressor - name = "compressor" - desc = "The compressor stage of a gas turbine generator." - icon = 'icons/obj/pipes.dmi' - icon_state = "compressor" - anchored = 1 - density = 1 - resistance_flags = FIRE_PROOF - var/obj/machinery/power/turbine/turbine - var/datum/gas_mixture/gas_contained - var/turf/simulated/inturf - var/starter = 0 - var/rpm = 0 - var/rpmtarget = 0 - var/capacity = 1e6 - var/comp_id = 0 - var/efficiency - - -/obj/machinery/power/turbine - name = "gas turbine generator" - desc = "A gas turbine used for backup power generation." - icon = 'icons/obj/pipes.dmi' - icon_state = "turbine" - anchored = 1 - density = 1 - resistance_flags = FIRE_PROOF - var/opened = 0 - var/obj/machinery/power/compressor/compressor - var/turf/simulated/outturf - var/lastgen - var/productivity = 1 - -/obj/machinery/computer/turbine_computer - name = "gas turbine control computer" - desc = "A computer to remotely control a gas turbine" - icon_screen = "turbinecomp" - icon_keyboard = "tech_key" - circuit = /obj/item/circuitboard/turbine_computer - var/obj/machinery/power/compressor/compressor - var/id = 0 - -// the inlet stage of the gas turbine electricity generator - -/obj/machinery/power/compressor/Initialize(mapload) - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/power_compressor(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stack/cable_coil(null, 5) - RefreshParts() -// The inlet of the compressor is the direction it faces - - gas_contained = new - inturf = get_step(src, dir) - locate_machinery() - if(!turbine) - stat |= BROKEN - - -#define COMPFRICTION 5e5 -#define COMPSTARTERLOAD 2800 - - -// Crucial to make things work!!!! -// OLD FIX - explanation given down below. -// /obj/machinery/power/compressor/CanPass(atom/movable/mover, turf/target, height=0) -// return !density - -/obj/machinery/power/compressor/locate_machinery() - if(turbine) - return - turbine = locate() in get_step(src, get_dir(inturf, src)) - if(turbine) - turbine.locate_machinery() - -/obj/machinery/power/compressor/RefreshParts() - var/E = 0 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - E += M.rating - efficiency = E / 6 - -/obj/machinery/power/compressor/attackby(obj/item/I, mob/user, params) - if(default_change_direction_wrench(user, I)) - turbine = null - inturf = get_step(src, dir) - locate_machinery() - if(turbine) - to_chat(user, "Turbine connected.") - stat &= ~BROKEN - else - to_chat(user, "Turbine not connected.") - stat |= BROKEN - return - - if(exchange_parts(user, I)) - return - - - return ..() - -/obj/machinery/power/compressor/crowbar_act(mob/user, obj/item/I) - if(default_deconstruction_crowbar(user, I)) - return TRUE - -/obj/machinery/power/compressor/screwdriver_act(mob/user, obj/item/I) - if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I)) - return TRUE - -/obj/machinery/power/compressor/CanAtmosPass(turf/T) - return !density - -/obj/machinery/power/compressor/process() - if(!turbine) - stat = BROKEN - if(stat & BROKEN || panel_open) - return - if(!starter) - return - overlays.Cut() - - rpm = 0.9* rpm + 0.1 * rpmtarget - var/datum/gas_mixture/environment = inturf.return_air() - - // It's a simplified version taking only 1/10 of the moles from the turf nearby. It should be later changed into a better version - - var/transfer_moles = environment.total_moles()/10 - //var/transfer_moles = rpm/10000*capacity - var/datum/gas_mixture/removed = inturf.remove_air(transfer_moles) - gas_contained.merge(removed) - -// RPM function to include compression friction - be advised that too low/high of a compfriction value can make things screwy - - rpm = max(0, rpm - (rpm*rpm)/(COMPFRICTION*efficiency)) - - - if(starter && !(stat & NOPOWER)) - use_power(2800) - if(rpm<1000) - rpmtarget = 1000 - else - if(rpm<1000) - rpmtarget = 0 - - - if(rpm>50000) - overlays += image('icons/obj/pipes.dmi', "comp-o4", FLY_LAYER) - else if(rpm>10000) - overlays += image('icons/obj/pipes.dmi', "comp-o3", FLY_LAYER) - else if(rpm>2000) - overlays += image('icons/obj/pipes.dmi', "comp-o2", FLY_LAYER) - else if(rpm>500) - overlays += image('icons/obj/pipes.dmi', "comp-o1", FLY_LAYER) - //TODO: DEFERRED - -// These are crucial to working of a turbine - the stats modify the power output. TurbGenQ modifies how much raw energy can you get from -// rpms, TurbGenG modifies the shape of the curve - the lower the value the less straight the curve is. - -#define TURBPRES 9000000 -#define TURBGENQ 100000 -#define TURBGENG 0.5 - -/obj/machinery/power/turbine/Initialize(mapload) - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/power_turbine(src) - component_parts += new /obj/item/stock_parts/capacitor(src) - component_parts += new /obj/item/stock_parts/capacitor(src) - component_parts += new /obj/item/stock_parts/capacitor(src) - component_parts += new /obj/item/stock_parts/capacitor(src) - component_parts += new /obj/item/stock_parts/capacitor(src) - component_parts += new /obj/item/stock_parts/capacitor(src) - component_parts += new /obj/item/stack/cable_coil(src, 5) - RefreshParts() -// The outlet is pointed at the direction of the turbine component - - outturf = get_step(src, dir) - locate_machinery() - if(!compressor) - stat |= BROKEN - -/obj/machinery/power/turbine/RefreshParts() - var/P = 0 - for(var/obj/item/stock_parts/capacitor/C in component_parts) - P += C.rating - productivity = P / 6 - -/obj/machinery/power/turbine/locate_machinery() - if(compressor) - return - compressor = locate() in get_step(src, get_dir(outturf, src)) - if(compressor) - compressor.locate_machinery() - -/obj/machinery/power/turbine/CanAtmosPass(turf/T) - return !density - -/obj/machinery/power/turbine/process() - - if(!compressor) - stat = BROKEN - - if((stat & BROKEN) || panel_open) - return - if(!compressor.starter) - return - overlays.Cut() - - // This is the power generation function. If anything is needed it's good to plot it in EXCEL before modifying - // the TURBGENQ and TURBGENG values - - lastgen = ((compressor.rpm / TURBGENQ)**TURBGENG) * TURBGENQ * productivity - - add_avail(lastgen) - - // Weird function but it works. Should be something else... - - var/newrpm = ((compressor.gas_contained.temperature) * compressor.gas_contained.total_moles())/4 - - newrpm = max(0, newrpm) - - if(!compressor.starter || newrpm > 1000) - compressor.rpmtarget = newrpm - - if(compressor.gas_contained.total_moles()>0) - var/oamount = min(compressor.gas_contained.total_moles(), (compressor.rpm+100)/35000*compressor.capacity) - var/datum/gas_mixture/removed = compressor.gas_contained.remove(oamount) - outturf.assume_air(removed) - -// If it works, put an overlay that it works! - - if(lastgen > 100) - overlays += image('icons/obj/pipes.dmi', "turb-o", FLY_LAYER) - - updateDialog() - -/obj/machinery/power/turbine/attack_hand(mob/user) - - if(..()) - return - - interact(user) - -/obj/machinery/power/turbine/attackby(obj/item/I, mob/user, params) - if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I)) - return - - if(default_change_direction_wrench(user, I)) - compressor = null - outturf = get_step(src, dir) - locate_machinery() - if(compressor) - to_chat(user, "Compressor connected.") - stat &= ~BROKEN - else - to_chat(user, "Compressor not connected.") - stat |= BROKEN - return - - if(exchange_parts(user, I)) - return - - if(default_deconstruction_crowbar(user, I)) - return - return ..() - -/obj/machinery/power/turbine/interact(mob/user) - - if( !Adjacent(user) || (stat & (NOPOWER|BROKEN)) && (!istype(user, /mob/living/silicon)) ) - user.unset_machine(src) - user << browse(null, "window=turbine") - return - - var/t = "Gas Turbine Generator
        "
        -
        -	t += "Generated power : [round(lastgen)] W

        " - - t += "Turbine: [round(compressor.rpm)] RPM
        " - - t += "Starter: [ compressor.starter ? "Off On" : "Off On"]" - - t += "

        Close" - - t += "
        " - var/datum/browser/popup = new(user, "turbine", name) - popup.set_content(t) - popup.open() - - return - -/obj/machinery/power/turbine/Topic(href, href_list) - if(..()) - return - - if( href_list["close"] ) - usr << browse(null, "window=turbine") - usr.unset_machine(src) - return - - else if( href_list["str"] ) - if(compressor) - compressor.starter = !compressor.starter - - updateDialog() - - - - - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// COMPUTER NEEDS A SERIOUS REWRITE. - - - -/obj/machinery/computer/turbine_computer/Initialize() - ..() - spawn(10) - locate_machinery() - -/obj/machinery/computer/turbine_computer/locate_machinery() - compressor = locate(/obj/machinery/power/compressor) in range(5, src) - -/obj/machinery/computer/turbine_computer/attack_hand(var/mob/user as mob) - if(..()) - return - - interact(user) - -/obj/machinery/computer/turbine_computer/interact(mob/user) - - var/dat - if(compressor && compressor.turbine) - dat += "
        Gas turbine remote control system
        " - if(compressor.stat || compressor.turbine.stat) - dat += "[compressor.stat ? "Compressor is inoperable
        " : "Turbine is inoperable"]" - else - dat += {"Turbine status: [ src.compressor.starter ? "Off On" : "Off On"] - \n
        - \nTurbine speed: [src.compressor.rpm]rpm
        - \nPower currently being generated: [src.compressor.turbine.lastgen]W
        - \nInternal gas temperature: [src.compressor.gas_contained.temperature]K
        - \n
        Close - \n
        - \n"} - else - dat += "There is [!compressor ? "no compressor" : " compressor[!compressor.turbine ? " but no turbine" : ""]"].
        " - if(!compressor) - dat += "Search for compressor" - - var/datum/browser/popup = new(user, "turbinecomputer", name) - popup.set_content(dat) - popup.open() - return - -/obj/machinery/computer/turbine_computer/Topic(href, href_list) - if(..()) - return - - else if( href_list["str"] ) - if(compressor && compressor.turbine) - compressor.starter = !compressor.starter - else if( href_list["close"] ) - usr << browse(null, "window=turbinecomputer") - usr.unset_machine(src) - return - else if(href_list["search"]) - locate_machinery() - - src.updateUsrDialog() - return - -/obj/machinery/computer/turbine_computer/process() - src.updateDialog() - return +// TURBINE v2 AKA rev4407 Engine reborn! + +// How to use it? - Mappers +// +// This is a very good power generating mechanism. All you need is a blast furnace with soaring flames and output. +// Not everything is included yet so the turbine can run out of fuel quiet quickly. The best thing about the turbine is that even +// though something is on fire that passes through it, it won't be on fire as it passes out of it. So the exhaust fumes can still +// containt unreacted fuel - plasma and oxygen that needs to be filtered out and re-routed back. This of course requires smart piping +// For a computer to work with the turbine the compressor requires a comp_id matching with the turbine computer's id. This will be +// subjected to a change in the near future mind you. Right now this method of generating power is a good backup but don't expect it +// become a main power source unless some work is done. Have fun. At 50k RPM it generates 60k power. So more than one turbine is needed! +// +// - Numbers +// +// Example setup S - sparker +// B - Blast doors into space for venting +// *BBB****BBB* C - Compressor +// S CT * T - Turbine +// * ^ * * V * D - Doors with firedoor +// **|***D**|** ^ - Fuel feed (Not vent, but a gas outlet) +// | | V - Suction vent (Like the ones in atmos +// + + +/obj/machinery/power/compressor + name = "compressor" + desc = "The compressor stage of a gas turbine generator." + icon = 'icons/obj/pipes.dmi' + icon_state = "compressor" + anchored = 1 + density = 1 + resistance_flags = FIRE_PROOF + var/obj/machinery/power/turbine/turbine + var/datum/gas_mixture/gas_contained + var/turf/simulated/inturf + var/starter = 0 + var/rpm = 0 + var/rpmtarget = 0 + var/capacity = 1e6 + var/comp_id = 0 + var/efficiency + + +/obj/machinery/power/turbine + name = "gas turbine generator" + desc = "A gas turbine used for backup power generation." + icon = 'icons/obj/pipes.dmi' + icon_state = "turbine" + anchored = 1 + density = 1 + resistance_flags = FIRE_PROOF + var/opened = 0 + var/obj/machinery/power/compressor/compressor + var/turf/simulated/outturf + var/lastgen + var/productivity = 1 + +/obj/machinery/computer/turbine_computer + name = "gas turbine control computer" + desc = "A computer to remotely control a gas turbine" + icon_screen = "turbinecomp" + icon_keyboard = "tech_key" + circuit = /obj/item/circuitboard/turbine_computer + var/obj/machinery/power/compressor/compressor + var/id = 0 + +// the inlet stage of the gas turbine electricity generator + +/obj/machinery/power/compressor/Initialize(mapload) + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/power_compressor(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stack/cable_coil(null, 5) + RefreshParts() +// The inlet of the compressor is the direction it faces + + gas_contained = new + inturf = get_step(src, dir) + locate_machinery() + if(!turbine) + stat |= BROKEN + + +#define COMPFRICTION 5e5 +#define COMPSTARTERLOAD 2800 + + +// Crucial to make things work!!!! +// OLD FIX - explanation given down below. +// /obj/machinery/power/compressor/CanPass(atom/movable/mover, turf/target, height=0) +// return !density + +/obj/machinery/power/compressor/locate_machinery() + if(turbine) + return + turbine = locate() in get_step(src, get_dir(inturf, src)) + if(turbine) + turbine.locate_machinery() + +/obj/machinery/power/compressor/RefreshParts() + var/E = 0 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + E += M.rating + efficiency = E / 6 + +/obj/machinery/power/compressor/attackby(obj/item/I, mob/user, params) + if(default_change_direction_wrench(user, I)) + turbine = null + inturf = get_step(src, dir) + locate_machinery() + if(turbine) + to_chat(user, "Turbine connected.") + stat &= ~BROKEN + else + to_chat(user, "Turbine not connected.") + stat |= BROKEN + return + + if(exchange_parts(user, I)) + return + + + return ..() + +/obj/machinery/power/compressor/crowbar_act(mob/user, obj/item/I) + if(default_deconstruction_crowbar(user, I)) + return TRUE + +/obj/machinery/power/compressor/screwdriver_act(mob/user, obj/item/I) + if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I)) + return TRUE + +/obj/machinery/power/compressor/CanAtmosPass(turf/T) + return !density + +/obj/machinery/power/compressor/process() + if(!turbine) + stat = BROKEN + if(stat & BROKEN || panel_open) + return + if(!starter) + return + overlays.Cut() + + rpm = 0.9* rpm + 0.1 * rpmtarget + var/datum/gas_mixture/environment = inturf.return_air() + + // It's a simplified version taking only 1/10 of the moles from the turf nearby. It should be later changed into a better version + + var/transfer_moles = environment.total_moles()/10 + //var/transfer_moles = rpm/10000*capacity + var/datum/gas_mixture/removed = inturf.remove_air(transfer_moles) + gas_contained.merge(removed) + +// RPM function to include compression friction - be advised that too low/high of a compfriction value can make things screwy + + rpm = max(0, rpm - (rpm*rpm)/(COMPFRICTION*efficiency)) + + + if(starter && !(stat & NOPOWER)) + use_power(2800) + if(rpm<1000) + rpmtarget = 1000 + else + if(rpm<1000) + rpmtarget = 0 + + + if(rpm>50000) + overlays += image('icons/obj/pipes.dmi', "comp-o4", FLY_LAYER) + else if(rpm>10000) + overlays += image('icons/obj/pipes.dmi', "comp-o3", FLY_LAYER) + else if(rpm>2000) + overlays += image('icons/obj/pipes.dmi', "comp-o2", FLY_LAYER) + else if(rpm>500) + overlays += image('icons/obj/pipes.dmi', "comp-o1", FLY_LAYER) + //TODO: DEFERRED + +// These are crucial to working of a turbine - the stats modify the power output. TurbGenQ modifies how much raw energy can you get from +// rpms, TurbGenG modifies the shape of the curve - the lower the value the less straight the curve is. + +#define TURBPRES 9000000 +#define TURBGENQ 100000 +#define TURBGENG 0.5 + +/obj/machinery/power/turbine/Initialize(mapload) + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/power_turbine(src) + component_parts += new /obj/item/stock_parts/capacitor(src) + component_parts += new /obj/item/stock_parts/capacitor(src) + component_parts += new /obj/item/stock_parts/capacitor(src) + component_parts += new /obj/item/stock_parts/capacitor(src) + component_parts += new /obj/item/stock_parts/capacitor(src) + component_parts += new /obj/item/stock_parts/capacitor(src) + component_parts += new /obj/item/stack/cable_coil(src, 5) + RefreshParts() +// The outlet is pointed at the direction of the turbine component + + outturf = get_step(src, dir) + locate_machinery() + if(!compressor) + stat |= BROKEN + +/obj/machinery/power/turbine/RefreshParts() + var/P = 0 + for(var/obj/item/stock_parts/capacitor/C in component_parts) + P += C.rating + productivity = P / 6 + +/obj/machinery/power/turbine/locate_machinery() + if(compressor) + return + compressor = locate() in get_step(src, get_dir(outturf, src)) + if(compressor) + compressor.locate_machinery() + +/obj/machinery/power/turbine/CanAtmosPass(turf/T) + return !density + +/obj/machinery/power/turbine/process() + + if(!compressor) + stat = BROKEN + + if((stat & BROKEN) || panel_open) + return + if(!compressor.starter) + return + overlays.Cut() + + // This is the power generation function. If anything is needed it's good to plot it in EXCEL before modifying + // the TURBGENQ and TURBGENG values + + lastgen = ((compressor.rpm / TURBGENQ)**TURBGENG) * TURBGENQ * productivity + + add_avail(lastgen) + + // Weird function but it works. Should be something else... + + var/newrpm = ((compressor.gas_contained.temperature) * compressor.gas_contained.total_moles())/4 + + newrpm = max(0, newrpm) + + if(!compressor.starter || newrpm > 1000) + compressor.rpmtarget = newrpm + + if(compressor.gas_contained.total_moles()>0) + var/oamount = min(compressor.gas_contained.total_moles(), (compressor.rpm+100)/35000*compressor.capacity) + var/datum/gas_mixture/removed = compressor.gas_contained.remove(oamount) + outturf.assume_air(removed) + +// If it works, put an overlay that it works! + + if(lastgen > 100) + overlays += image('icons/obj/pipes.dmi', "turb-o", FLY_LAYER) + + updateDialog() + +/obj/machinery/power/turbine/attack_hand(mob/user) + + if(..()) + return + + interact(user) + +/obj/machinery/power/turbine/attackby(obj/item/I, mob/user, params) + if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I)) + return + + if(default_change_direction_wrench(user, I)) + compressor = null + outturf = get_step(src, dir) + locate_machinery() + if(compressor) + to_chat(user, "Compressor connected.") + stat &= ~BROKEN + else + to_chat(user, "Compressor not connected.") + stat |= BROKEN + return + + if(exchange_parts(user, I)) + return + + if(default_deconstruction_crowbar(user, I)) + return + return ..() + +/obj/machinery/power/turbine/interact(mob/user) + + if( !Adjacent(user) || (stat & (NOPOWER|BROKEN)) && (!istype(user, /mob/living/silicon)) ) + user.unset_machine(src) + user << browse(null, "window=turbine") + return + + var/t = "Gas Turbine Generator
        "
        +
        +	t += "Generated power : [round(lastgen)] W

        " + + t += "Turbine: [round(compressor.rpm)] RPM
        " + + t += "Starter: [ compressor.starter ? "Off On" : "Off On"]" + + t += "

        Close" + + t += "
        " + var/datum/browser/popup = new(user, "turbine", name) + popup.set_content(t) + popup.open() + + return + +/obj/machinery/power/turbine/Topic(href, href_list) + if(..()) + return + + if( href_list["close"] ) + usr << browse(null, "window=turbine") + usr.unset_machine(src) + return + + else if( href_list["str"] ) + if(compressor) + compressor.starter = !compressor.starter + + updateDialog() + + + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// COMPUTER NEEDS A SERIOUS REWRITE. + + + +/obj/machinery/computer/turbine_computer/Initialize() + ..() + spawn(10) + locate_machinery() + +/obj/machinery/computer/turbine_computer/locate_machinery() + compressor = locate(/obj/machinery/power/compressor) in range(5, src) + +/obj/machinery/computer/turbine_computer/attack_hand(var/mob/user as mob) + if(..()) + return + + interact(user) + +/obj/machinery/computer/turbine_computer/interact(mob/user) + + var/dat + if(compressor && compressor.turbine) + dat += "
        Gas turbine remote control system
        " + if(compressor.stat || compressor.turbine.stat) + dat += "[compressor.stat ? "Compressor is inoperable
        " : "Turbine is inoperable"]" + else + dat += {"Turbine status: [ src.compressor.starter ? "Off On" : "Off On"] + \n
        + \nTurbine speed: [src.compressor.rpm]rpm
        + \nPower currently being generated: [src.compressor.turbine.lastgen]W
        + \nInternal gas temperature: [src.compressor.gas_contained.temperature]K
        + \n
        Close + \n
        + \n"} + else + dat += "There is [!compressor ? "no compressor" : " compressor[!compressor.turbine ? " but no turbine" : ""]"].
        " + if(!compressor) + dat += "Search for compressor" + + var/datum/browser/popup = new(user, "turbinecomputer", name) + popup.set_content(dat) + popup.open() + return + +/obj/machinery/computer/turbine_computer/Topic(href, href_list) + if(..()) + return + + else if( href_list["str"] ) + if(compressor && compressor.turbine) + compressor.starter = !compressor.starter + else if( href_list["close"] ) + usr << browse(null, "window=turbinecomputer") + usr.unset_machine(src) + return + else if(href_list["search"]) + locate_machinery() + + src.updateUsrDialog() + return + +/obj/machinery/computer/turbine_computer/process() + src.updateDialog() + return diff --git a/code/modules/procedural_mapping/mapGeneratorModule.dm b/code/modules/procedural_mapping/mapGeneratorModule.dm index 030c61e972a..e8db8c8390b 100644 --- a/code/modules/procedural_mapping/mapGeneratorModule.dm +++ b/code/modules/procedural_mapping/mapGeneratorModule.dm @@ -142,4 +142,4 @@ /datum/mapGeneratorModule/denseLayer clusterCheckFlags = CLUSTER_CHECK_NONE spawnableAtoms = list(/atom = 75) - spawnableTurfs = list(/turf = 75) \ No newline at end of file + spawnableTurfs = list(/turf = 75) diff --git a/code/modules/procedural_mapping/mapGeneratorReadme.dm b/code/modules/procedural_mapping/mapGeneratorReadme.dm index ae656b5b30b..ce34e3729cb 100644 --- a/code/modules/procedural_mapping/mapGeneratorReadme.dm +++ b/code/modules/procedural_mapping/mapGeneratorReadme.dm @@ -143,4 +143,4 @@ Variable Breakdown (For Mappers): -*/ \ No newline at end of file +*/ diff --git a/code/modules/procedural_mapping/mapGenerators/asteroid.dm b/code/modules/procedural_mapping/mapGenerators/asteroid.dm index 10fe0fc9332..90ca219588e 100644 --- a/code/modules/procedural_mapping/mapGenerators/asteroid.dm +++ b/code/modules/procedural_mapping/mapGenerators/asteroid.dm @@ -42,4 +42,4 @@ /datum/mapGeneratorModule/splatterLayer/asteroidMonsters) /datum/mapGenerator/asteroid/filled - modules = list(/datum/mapGeneratorModule/bottomLayer/asteroidWalls) \ No newline at end of file + modules = list(/datum/mapGeneratorModule/bottomLayer/asteroidWalls) diff --git a/code/modules/procedural_mapping/mapGenerators/lava_river.dm b/code/modules/procedural_mapping/mapGenerators/lava_river.dm index 86f9bd68baf..19a61aaabe6 100644 --- a/code/modules/procedural_mapping/mapGenerators/lava_river.dm +++ b/code/modules/procedural_mapping/mapGenerators/lava_river.dm @@ -22,4 +22,4 @@ var/datum/mapGenerator/lavaland/L = mother if(!istype(L)) return - spawn_rivers(L.start_z, river_nodes, river_type, min_x = L.min_x, min_y = L.min_y, max_x = L.max_x, max_y = L.max_y) \ No newline at end of file + spawn_rivers(L.start_z, river_nodes, river_type, min_x = L.min_x, min_y = L.min_y, max_x = L.max_x, max_y = L.max_y) diff --git a/code/modules/procedural_mapping/mapGenerators/lavaland.dm b/code/modules/procedural_mapping/mapGenerators/lavaland.dm index c65a0d84d71..a0ba2ae816e 100644 --- a/code/modules/procedural_mapping/mapGenerators/lavaland.dm +++ b/code/modules/procedural_mapping/mapGenerators/lavaland.dm @@ -26,4 +26,4 @@ modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_mineral/dense) /datum/mapGenerator/lavaland/normal_ores - modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_mineral) \ No newline at end of file + modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_mineral) diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index d2df6b4a03b..f8b13b47874 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -1,222 +1,222 @@ -/obj/item/ammo_casing - name = "bullet casing" - desc = "A bullet casing." - icon = 'icons/obj/ammo.dmi' - icon_state = "s-casing" - flags = CONDUCT - slot_flags = SLOT_BELT - throwforce = 1 - w_class = WEIGHT_CLASS_TINY - var/fire_sound = null //What sound should play when this ammo is fired - var/drop_sound = "casingdrop" //What sound should play when this ammo hits the ground - var/caliber = null //Which kind of guns it can be loaded into - var/projectile_type = null //The bullet type to create when New() is called - var/obj/item/projectile/BB = null //The loaded bullet - var/pellets = 1 //Pellets for spreadshot - var/variance = 0 //Variance for inaccuracy fundamental to the casing - var/delay = 0 //Delay for energy weapons - var/randomspread = 0 //Randomspread for automatics - var/click_cooldown_override = 0 //Override this to make your gun have a faster fire rate, in tenths of a second. 4 is the default gun cooldown. - var/harmful = TRUE //pacifism check for boolet, set to FALSE if bullet is non-lethal - -/obj/item/ammo_casing/New() - ..() - if(projectile_type) - BB = new projectile_type(src) - pixel_x = rand(-10.0, 10) - pixel_y = rand(-10.0, 10) - dir = pick(alldirs) - update_icon() - -/obj/item/ammo_casing/update_icon() - ..() - icon_state = "[initial(icon_state)][BB ? "-live" : ""]" - desc = "[initial(desc)][BB ? "" : " This one is spent."]" - -/obj/item/ammo_casing/proc/newshot(params) //For energy weapons, shotgun shells and wands (!). - if(!BB) - BB = new projectile_type(src, params) - return - -/obj/item/ammo_casing/attackby(obj/item/I as obj, mob/user as mob, params) - if(istype(I, /obj/item/ammo_box)) - var/obj/item/ammo_box/box = I - if(isturf(loc)) - var/boolets = 0 - for(var/obj/item/ammo_casing/bullet in loc) - if(box.stored_ammo.len >= box.max_ammo) - break - if(bullet.BB) - if(box.give_round(bullet, 0)) - boolets++ - else - continue - if(boolets > 0) - box.update_icon() - to_chat(user, "You collect [boolets] shell\s. [box] now contains [box.stored_ammo.len] shell\s.") - playsound(src, 'sound/weapons/gun_interactions/bulletinsert.ogg', 50, 1) - else - to_chat(user, "You fail to collect anything!") - else - if(istype(I, /obj/item/screwdriver)) - if(BB) - if(initial(BB.name) == "bullet") - var/tmp_label = "" - var/label_text = sanitize(input(user, "Inscribe some text into \the [initial(BB.name)]","Inscription",tmp_label)) - if(length(label_text) > 20) - to_chat(user, "The inscription can be at most 20 characters long.") - else - if(label_text == "") - to_chat(user, "You scratch the inscription off of [initial(BB)].") - BB.name = initial(BB.name) - else - to_chat(user, "You inscribe \"[label_text]\" into \the [initial(BB.name)].") - BB.name = "[initial(BB.name)] \"[label_text]\"" - else - to_chat(user, "You can only inscribe a metal bullet.")//because inscribing beanbags is silly - - else - to_chat(user, "There is no bullet in the casing to inscribe anything into.") - ..() - -//Boxes of ammo -/obj/item/ammo_box - name = "ammo box (generic)" - desc = "A box of ammo?" - icon_state = "357" - icon = 'icons/obj/ammo.dmi' - flags = CONDUCT - slot_flags = SLOT_BELT - item_state = "syringe_kit" - materials = list(MAT_METAL = 30000) - throwforce = 2 - w_class = WEIGHT_CLASS_TINY - throw_speed = 4 - throw_range = 10 - var/list/stored_ammo = list() - var/ammo_type = /obj/item/ammo_casing - var/max_ammo = 7 - var/multiple_sprites = 0 - var/caliber - var/multiload = 1 - var/list/initial_mats //For calculating refund values. - -/obj/item/ammo_box/New() - ..() - for(var/i in 1 to max_ammo) - stored_ammo += new ammo_type(src) - update_icon() - initial_mats = materials.Copy() - update_mat_value() - -/obj/item/ammo_box/Destroy() - QDEL_LIST(stored_ammo) - stored_ammo = null - return ..() - -/obj/item/ammo_box/proc/get_round(keep = 0) - if(!stored_ammo.len) - return null - else - var/b = stored_ammo[stored_ammo.len] - stored_ammo -= b - if(keep) - stored_ammo.Insert(1,b) - update_mat_value() - return b - -/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0) - // Boxes don't have a caliber type, magazines do. Not sure if it's intended or not, but if we fail to find a caliber, then we fall back to ammo_type. - if(!R || (caliber && R.caliber != caliber) || (!caliber && R.type != ammo_type)) - return 0 - - if(stored_ammo.len < max_ammo) - stored_ammo += R - R.loc = src - playsound(src, 'sound/weapons/gun_interactions/bulletinsert.ogg', 50, 1) - update_mat_value() - return 1 - //for accessibles magazines (e.g internal ones) when full, start replacing spent ammo - else if(replace_spent) - for(var/obj/item/ammo_casing/AC in stored_ammo) - if(!AC.BB)//found a spent ammo - stored_ammo -= AC - AC.loc = get_turf(loc) - - stored_ammo += R - R.loc = src - playsound(src, 'sound/weapons/gun_interactions/shotguninsert.ogg', 50, 1) - update_mat_value() - return 1 - - return 0 - -/obj/item/ammo_box/proc/can_load(mob/user) - return 1 - -/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = 0, replace_spent = 0) - var/num_loaded = 0 - if(!can_load(user)) - return - if(istype(A, /obj/item/ammo_box)) - var/obj/item/ammo_box/AM = A - for(var/obj/item/ammo_casing/AC in AM.stored_ammo) - var/did_load = give_round(AC, replace_spent) - if(did_load) - AM.stored_ammo -= AC - num_loaded++ - if(!multiload || !did_load) - break - if(istype(A, /obj/item/ammo_casing)) - var/obj/item/ammo_casing/AC = A - if(give_round(AC, replace_spent)) - user.drop_item() - AC.loc = src - num_loaded++ - if(num_loaded) - if(!silent) - to_chat(user, "You load [num_loaded] shell\s into \the [src]!") - playsound(src, 'sound/weapons/gun_interactions/shotguninsert.ogg', 50, 1) - A.update_icon() - update_icon() - - return num_loaded - -/obj/item/ammo_box/attack_self(mob/user as mob) - var/obj/item/ammo_casing/A = get_round() - if(A) - user.put_in_hands(A) - playsound(src, 'sound/weapons/gun_interactions/remove_bullet.ogg', 50, 1) - to_chat(user, "You remove a round from \the [src]!") - update_icon() - -/obj/item/ammo_box/update_icon() - switch(multiple_sprites) - if(1) - icon_state = "[initial(icon_state)]-[stored_ammo.len]" - if(2) - icon_state = "[initial(icon_state)]-[stored_ammo.len ? "[max_ammo]" : "0"]" - desc = "[initial(desc)] There are [stored_ammo.len] shell\s left!" - -/obj/item/ammo_box/proc/update_mat_value() - var/num_ammo = 0 - for(var/B in stored_ammo) - var/obj/item/ammo_casing/AC = B - if(!AC.BB) //Skip any casing which are empty - continue - num_ammo++ - for(var/M in initial_mats) //In case we have multiple types of materials - var/materials_per = initial_mats[M] / max_ammo - - var/value = max(materials_per * num_ammo, 500) //Enforce a minimum of 500 units even if empty. - materials[M] = value - -//Behavior for magazines -/obj/item/ammo_box/magazine/proc/ammo_count() - return stored_ammo.len - -/obj/item/ammo_box/magazine/proc/empty_magazine() - var/turf_mag = get_turf(src) - for(var/obj/item/ammo in stored_ammo) - ammo.forceMove(turf_mag) - stored_ammo -= ammo +/obj/item/ammo_casing + name = "bullet casing" + desc = "A bullet casing." + icon = 'icons/obj/ammo.dmi' + icon_state = "s-casing" + flags = CONDUCT + slot_flags = SLOT_BELT + throwforce = 1 + w_class = WEIGHT_CLASS_TINY + var/fire_sound = null //What sound should play when this ammo is fired + var/drop_sound = "casingdrop" //What sound should play when this ammo hits the ground + var/caliber = null //Which kind of guns it can be loaded into + var/projectile_type = null //The bullet type to create when New() is called + var/obj/item/projectile/BB = null //The loaded bullet + var/pellets = 1 //Pellets for spreadshot + var/variance = 0 //Variance for inaccuracy fundamental to the casing + var/delay = 0 //Delay for energy weapons + var/randomspread = 0 //Randomspread for automatics + var/click_cooldown_override = 0 //Override this to make your gun have a faster fire rate, in tenths of a second. 4 is the default gun cooldown. + var/harmful = TRUE //pacifism check for boolet, set to FALSE if bullet is non-lethal + +/obj/item/ammo_casing/New() + ..() + if(projectile_type) + BB = new projectile_type(src) + pixel_x = rand(-10.0, 10) + pixel_y = rand(-10.0, 10) + dir = pick(alldirs) + update_icon() + +/obj/item/ammo_casing/update_icon() + ..() + icon_state = "[initial(icon_state)][BB ? "-live" : ""]" + desc = "[initial(desc)][BB ? "" : " This one is spent."]" + +/obj/item/ammo_casing/proc/newshot(params) //For energy weapons, shotgun shells and wands (!). + if(!BB) + BB = new projectile_type(src, params) + return + +/obj/item/ammo_casing/attackby(obj/item/I as obj, mob/user as mob, params) + if(istype(I, /obj/item/ammo_box)) + var/obj/item/ammo_box/box = I + if(isturf(loc)) + var/boolets = 0 + for(var/obj/item/ammo_casing/bullet in loc) + if(box.stored_ammo.len >= box.max_ammo) + break + if(bullet.BB) + if(box.give_round(bullet, 0)) + boolets++ + else + continue + if(boolets > 0) + box.update_icon() + to_chat(user, "You collect [boolets] shell\s. [box] now contains [box.stored_ammo.len] shell\s.") + playsound(src, 'sound/weapons/gun_interactions/bulletinsert.ogg', 50, 1) + else + to_chat(user, "You fail to collect anything!") + else + if(istype(I, /obj/item/screwdriver)) + if(BB) + if(initial(BB.name) == "bullet") + var/tmp_label = "" + var/label_text = sanitize(input(user, "Inscribe some text into \the [initial(BB.name)]","Inscription",tmp_label)) + if(length(label_text) > 20) + to_chat(user, "The inscription can be at most 20 characters long.") + else + if(label_text == "") + to_chat(user, "You scratch the inscription off of [initial(BB)].") + BB.name = initial(BB.name) + else + to_chat(user, "You inscribe \"[label_text]\" into \the [initial(BB.name)].") + BB.name = "[initial(BB.name)] \"[label_text]\"" + else + to_chat(user, "You can only inscribe a metal bullet.")//because inscribing beanbags is silly + + else + to_chat(user, "There is no bullet in the casing to inscribe anything into.") + ..() + +//Boxes of ammo +/obj/item/ammo_box + name = "ammo box (generic)" + desc = "A box of ammo?" + icon_state = "357" + icon = 'icons/obj/ammo.dmi' + flags = CONDUCT + slot_flags = SLOT_BELT + item_state = "syringe_kit" + materials = list(MAT_METAL = 30000) + throwforce = 2 + w_class = WEIGHT_CLASS_TINY + throw_speed = 4 + throw_range = 10 + var/list/stored_ammo = list() + var/ammo_type = /obj/item/ammo_casing + var/max_ammo = 7 + var/multiple_sprites = 0 + var/caliber + var/multiload = 1 + var/list/initial_mats //For calculating refund values. + +/obj/item/ammo_box/New() + ..() + for(var/i in 1 to max_ammo) + stored_ammo += new ammo_type(src) + update_icon() + initial_mats = materials.Copy() + update_mat_value() + +/obj/item/ammo_box/Destroy() + QDEL_LIST(stored_ammo) + stored_ammo = null + return ..() + +/obj/item/ammo_box/proc/get_round(keep = 0) + if(!stored_ammo.len) + return null + else + var/b = stored_ammo[stored_ammo.len] + stored_ammo -= b + if(keep) + stored_ammo.Insert(1,b) + update_mat_value() + return b + +/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0) + // Boxes don't have a caliber type, magazines do. Not sure if it's intended or not, but if we fail to find a caliber, then we fall back to ammo_type. + if(!R || (caliber && R.caliber != caliber) || (!caliber && R.type != ammo_type)) + return 0 + + if(stored_ammo.len < max_ammo) + stored_ammo += R + R.loc = src + playsound(src, 'sound/weapons/gun_interactions/bulletinsert.ogg', 50, 1) + update_mat_value() + return 1 + //for accessibles magazines (e.g internal ones) when full, start replacing spent ammo + else if(replace_spent) + for(var/obj/item/ammo_casing/AC in stored_ammo) + if(!AC.BB)//found a spent ammo + stored_ammo -= AC + AC.loc = get_turf(loc) + + stored_ammo += R + R.loc = src + playsound(src, 'sound/weapons/gun_interactions/shotguninsert.ogg', 50, 1) + update_mat_value() + return 1 + + return 0 + +/obj/item/ammo_box/proc/can_load(mob/user) + return 1 + +/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = 0, replace_spent = 0) + var/num_loaded = 0 + if(!can_load(user)) + return + if(istype(A, /obj/item/ammo_box)) + var/obj/item/ammo_box/AM = A + for(var/obj/item/ammo_casing/AC in AM.stored_ammo) + var/did_load = give_round(AC, replace_spent) + if(did_load) + AM.stored_ammo -= AC + num_loaded++ + if(!multiload || !did_load) + break + if(istype(A, /obj/item/ammo_casing)) + var/obj/item/ammo_casing/AC = A + if(give_round(AC, replace_spent)) + user.drop_item() + AC.loc = src + num_loaded++ + if(num_loaded) + if(!silent) + to_chat(user, "You load [num_loaded] shell\s into \the [src]!") + playsound(src, 'sound/weapons/gun_interactions/shotguninsert.ogg', 50, 1) + A.update_icon() + update_icon() + + return num_loaded + +/obj/item/ammo_box/attack_self(mob/user as mob) + var/obj/item/ammo_casing/A = get_round() + if(A) + user.put_in_hands(A) + playsound(src, 'sound/weapons/gun_interactions/remove_bullet.ogg', 50, 1) + to_chat(user, "You remove a round from \the [src]!") + update_icon() + +/obj/item/ammo_box/update_icon() + switch(multiple_sprites) + if(1) + icon_state = "[initial(icon_state)]-[stored_ammo.len]" + if(2) + icon_state = "[initial(icon_state)]-[stored_ammo.len ? "[max_ammo]" : "0"]" + desc = "[initial(desc)] There are [stored_ammo.len] shell\s left!" + +/obj/item/ammo_box/proc/update_mat_value() + var/num_ammo = 0 + for(var/B in stored_ammo) + var/obj/item/ammo_casing/AC = B + if(!AC.BB) //Skip any casing which are empty + continue + num_ammo++ + for(var/M in initial_mats) //In case we have multiple types of materials + var/materials_per = initial_mats[M] / max_ammo + + var/value = max(materials_per * num_ammo, 500) //Enforce a minimum of 500 units even if empty. + materials[M] = value + +//Behavior for magazines +/obj/item/ammo_box/magazine/proc/ammo_count() + return stored_ammo.len + +/obj/item/ammo_box/magazine/proc/empty_magazine() + var/turf_mag = get_turf(src) + for(var/obj/item/ammo in stored_ammo) + ammo.forceMove(turf_mag) + stored_ammo -= ammo diff --git a/code/modules/projectiles/ammunition/boxes.dm b/code/modules/projectiles/ammunition/boxes.dm index ae88bc9db61..4484be63c83 100644 --- a/code/modules/projectiles/ammunition/boxes.dm +++ b/code/modules/projectiles/ammunition/boxes.dm @@ -1,148 +1,148 @@ -/obj/item/ammo_box/a357 - name = "speed loader (.357)" - desc = "Designed to quickly reload revolvers." - icon_state = "357" - ammo_type = /obj/item/ammo_casing/a357 - max_ammo = 7 - multiple_sprites = 1 - -/obj/item/ammo_box/c38 - name = "speed loader (.38)" - desc = "Designed to quickly reload revolvers." - icon_state = "38" - ammo_type = /obj/item/ammo_casing/c38 - max_ammo = 6 - multiple_sprites = 1 - -/obj/item/ammo_box/c9mm - name = "ammo box (9mm)" - icon_state = "9mmbox" - origin_tech = "combat=2" - ammo_type = /obj/item/ammo_casing/c9mm - max_ammo = 30 - -/obj/item/ammo_box/c10mm - name = "ammo box (10mm)" - icon_state = "10mmbox" - origin_tech = "combat=2" - ammo_type = /obj/item/ammo_casing/c10mm - max_ammo = 20 - -/obj/item/ammo_box/c45 - name = "ammo box (.45)" - icon_state = "45box" - origin_tech = "combat=2" - ammo_type = /obj/item/ammo_casing/c45 - max_ammo = 20 - -/obj/item/ammo_box/rubber45 - name = "ammo box (.45 rubber)" - icon_state = "45box-r" - ammo_type = /obj/item/ammo_casing/rubber45 - max_ammo = 16 - -/obj/item/ammo_box/a40mm - name = "ammo box (40mm grenades)" - icon_state = "40mm" - ammo_type = /obj/item/ammo_casing/a40mm - max_ammo = 4 - multiple_sprites = 1 - -/obj/item/ammo_box/a762 - name = "stripper clip (7.62mm)" - desc = "A stripper clip." - icon_state = "762" - ammo_type = /obj/item/ammo_casing/a762 - max_ammo = 5 - multiple_sprites = 1 - -/obj/item/ammo_box/n762 - name = "ammo box (7.62x38mmR)" - icon_state = "riflebox" - origin_tech = "combat=2" - ammo_type = /obj/item/ammo_casing/n762 - max_ammo = 14 - -/obj/item/ammo_box/shotgun - name = "Shotgun Speedloader (slug)" - icon_state = "slugloader" - origin_tech = "combat=2" - ammo_type = /obj/item/ammo_casing/shotgun - max_ammo = 7 - materials = list(MAT_METAL=28000) - multiple_sprites = 1 - -/obj/item/ammo_box/shotgun/buck - name = "Shotgun Speedloader (buckshot)" - icon_state = "buckloader" - ammo_type = /obj/item/ammo_casing/shotgun/buckshot - - -/obj/item/ammo_box/shotgun/dragonsbreath - name = "Shotgun Speedloader (dragonsbreath)" - icon_state = "dragonsbreathloader" - ammo_type = /obj/item/ammo_casing/shotgun/incendiary/dragonsbreath - - -/obj/item/ammo_box/shotgun/stun - name = "Shotgun Speedloader (stun shells)" - icon_state = "stunloader" - ammo_type = /obj/item/ammo_casing/shotgun/stunslug - - -/obj/item/ammo_box/shotgun/beanbag - name = "Shotgun Speedloader (beanbag shells)" - icon_state = "beanbagloader" - ammo_type = /obj/item/ammo_casing/shotgun/beanbag - materials = list(MAT_METAL=1750) - - -/obj/item/ammo_box/shotgun/rubbershot - name = "Shotgun Speedloader (rubbershot shells)" - icon_state = "rubbershotloader" - ammo_type = /obj/item/ammo_casing/shotgun/rubbershot - materials = list(MAT_METAL=1750) - - -/obj/item/ammo_box/shotgun/tranquilizer - name = "Shotgun Speedloader (tranquilizer darts)" - icon_state = "tranqloader" - ammo_type = /obj/item/ammo_casing/shotgun/tranquilizer - materials = list(MAT_METAL=1750) - - -//FOAM DARTS -/obj/item/ammo_box/foambox - name = "ammo box (Foam Darts)" - icon = 'icons/obj/guns/toy.dmi' - icon_state = "foambox" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart - max_ammo = 40 - materials = list(MAT_METAL = 500) - -/obj/item/ammo_box/foambox/riot - icon_state = "foambox_riot" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot - materials = list(MAT_METAL = 50000) - -/obj/item/ammo_box/foambox/sniper - name = "ammo box (Foam Sniper Darts)" - icon = 'icons/obj/guns/toy.dmi' - icon_state = "foambox_sniper" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/sniper - max_ammo = 40 - materials = list(MAT_METAL = 900) - -/obj/item/ammo_box/foambox/sniper/riot - icon_state = "foambox_sniper_riot" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/sniper/riot - materials = list(MAT_METAL = 90000) - - - -/obj/item/ammo_box/caps - name = "speed loader (caps)" - icon_state = "357" - ammo_type = /obj/item/ammo_casing/cap - max_ammo = 7 - multiple_sprites = 1 +/obj/item/ammo_box/a357 + name = "speed loader (.357)" + desc = "Designed to quickly reload revolvers." + icon_state = "357" + ammo_type = /obj/item/ammo_casing/a357 + max_ammo = 7 + multiple_sprites = 1 + +/obj/item/ammo_box/c38 + name = "speed loader (.38)" + desc = "Designed to quickly reload revolvers." + icon_state = "38" + ammo_type = /obj/item/ammo_casing/c38 + max_ammo = 6 + multiple_sprites = 1 + +/obj/item/ammo_box/c9mm + name = "ammo box (9mm)" + icon_state = "9mmbox" + origin_tech = "combat=2" + ammo_type = /obj/item/ammo_casing/c9mm + max_ammo = 30 + +/obj/item/ammo_box/c10mm + name = "ammo box (10mm)" + icon_state = "10mmbox" + origin_tech = "combat=2" + ammo_type = /obj/item/ammo_casing/c10mm + max_ammo = 20 + +/obj/item/ammo_box/c45 + name = "ammo box (.45)" + icon_state = "45box" + origin_tech = "combat=2" + ammo_type = /obj/item/ammo_casing/c45 + max_ammo = 20 + +/obj/item/ammo_box/rubber45 + name = "ammo box (.45 rubber)" + icon_state = "45box-r" + ammo_type = /obj/item/ammo_casing/rubber45 + max_ammo = 16 + +/obj/item/ammo_box/a40mm + name = "ammo box (40mm grenades)" + icon_state = "40mm" + ammo_type = /obj/item/ammo_casing/a40mm + max_ammo = 4 + multiple_sprites = 1 + +/obj/item/ammo_box/a762 + name = "stripper clip (7.62mm)" + desc = "A stripper clip." + icon_state = "762" + ammo_type = /obj/item/ammo_casing/a762 + max_ammo = 5 + multiple_sprites = 1 + +/obj/item/ammo_box/n762 + name = "ammo box (7.62x38mmR)" + icon_state = "riflebox" + origin_tech = "combat=2" + ammo_type = /obj/item/ammo_casing/n762 + max_ammo = 14 + +/obj/item/ammo_box/shotgun + name = "Shotgun Speedloader (slug)" + icon_state = "slugloader" + origin_tech = "combat=2" + ammo_type = /obj/item/ammo_casing/shotgun + max_ammo = 7 + materials = list(MAT_METAL=28000) + multiple_sprites = 1 + +/obj/item/ammo_box/shotgun/buck + name = "Shotgun Speedloader (buckshot)" + icon_state = "buckloader" + ammo_type = /obj/item/ammo_casing/shotgun/buckshot + + +/obj/item/ammo_box/shotgun/dragonsbreath + name = "Shotgun Speedloader (dragonsbreath)" + icon_state = "dragonsbreathloader" + ammo_type = /obj/item/ammo_casing/shotgun/incendiary/dragonsbreath + + +/obj/item/ammo_box/shotgun/stun + name = "Shotgun Speedloader (stun shells)" + icon_state = "stunloader" + ammo_type = /obj/item/ammo_casing/shotgun/stunslug + + +/obj/item/ammo_box/shotgun/beanbag + name = "Shotgun Speedloader (beanbag shells)" + icon_state = "beanbagloader" + ammo_type = /obj/item/ammo_casing/shotgun/beanbag + materials = list(MAT_METAL=1750) + + +/obj/item/ammo_box/shotgun/rubbershot + name = "Shotgun Speedloader (rubbershot shells)" + icon_state = "rubbershotloader" + ammo_type = /obj/item/ammo_casing/shotgun/rubbershot + materials = list(MAT_METAL=1750) + + +/obj/item/ammo_box/shotgun/tranquilizer + name = "Shotgun Speedloader (tranquilizer darts)" + icon_state = "tranqloader" + ammo_type = /obj/item/ammo_casing/shotgun/tranquilizer + materials = list(MAT_METAL=1750) + + +//FOAM DARTS +/obj/item/ammo_box/foambox + name = "ammo box (Foam Darts)" + icon = 'icons/obj/guns/toy.dmi' + icon_state = "foambox" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart + max_ammo = 40 + materials = list(MAT_METAL = 500) + +/obj/item/ammo_box/foambox/riot + icon_state = "foambox_riot" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot + materials = list(MAT_METAL = 50000) + +/obj/item/ammo_box/foambox/sniper + name = "ammo box (Foam Sniper Darts)" + icon = 'icons/obj/guns/toy.dmi' + icon_state = "foambox_sniper" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/sniper + max_ammo = 40 + materials = list(MAT_METAL = 900) + +/obj/item/ammo_box/foambox/sniper/riot + icon_state = "foambox_sniper_riot" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/sniper/riot + materials = list(MAT_METAL = 90000) + + + +/obj/item/ammo_box/caps + name = "speed loader (caps)" + icon_state = "357" + ammo_type = /obj/item/ammo_casing/cap + max_ammo = 7 + multiple_sprites = 1 diff --git a/code/modules/projectiles/ammunition/magazines.dm b/code/modules/projectiles/ammunition/magazines.dm index d756dda4f41..5890110b635 100644 --- a/code/modules/projectiles/ammunition/magazines.dm +++ b/code/modules/projectiles/ammunition/magazines.dm @@ -506,4 +506,4 @@ icon_state = "c20r45-[round(ammo_count(),2)]" /obj/item/ammo_box/magazine/toy/smgm45/riot - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot \ No newline at end of file + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index d4a333c495d..411a460ac62 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1,524 +1,524 @@ -/obj/item/gun - name = "gun" - desc = "It's a gun. It's pretty terrible, though." - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "detective" - item_state = "gun" - flags = CONDUCT - slot_flags = SLOT_BELT - materials = list(MAT_METAL=2000) - w_class = WEIGHT_CLASS_NORMAL - throwforce = 5 - throw_speed = 3 - throw_range = 5 - force = 5 - origin_tech = "combat=1" - needs_permit = 1 - attack_verb = list("struck", "hit", "bashed") - - var/fire_sound = "gunshot" - var/magin_sound = 'sound/weapons/gun_interactions/smg_magin.ogg' - var/magout_sound = 'sound/weapons/gun_interactions/smg_magout.ogg' - var/fire_sound_text = "gunshot" //the fire sound that shows in chat messages: laser blast, gunshot, etc. - var/suppressed = 0 //whether or not a message is displayed when fired - var/can_suppress = 0 - var/can_unsuppress = 1 - var/recoil = 0 //boom boom shake the room - var/clumsy_check = 1 - var/obj/item/ammo_casing/chambered = null - var/trigger_guard = TRIGGER_GUARD_NORMAL //trigger guard on the weapon, hulks can't fire them with their big meaty fingers - var/sawn_desc = null //description change if weapon is sawn-off - var/sawn_state = SAWN_INTACT - var/burst_size = 1 //how large a burst is - var/fire_delay = 0 //rate of fire for burst firing and semi auto - var/firing_burst = 0 //Prevent the weapon from firing again while already firing - var/semicd = 0 //cooldown handler - var/weapon_weight = WEAPON_LIGHT - var/list/restricted_species - - var/spread = 0 - var/randomspread = 1 - - var/unique_rename = 0 //allows renaming with a pen - var/unique_reskin = 0 //allows one-time reskinning - var/current_skin = null //the skin choice if we had a reskin - var/list/options = list() - - lefthand_file = 'icons/mob/inhands/guns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/guns_righthand.dmi' - - var/obj/item/flashlight/gun_light = null - var/can_flashlight = 0 - - var/can_bayonet = FALSE //if a bayonet can be added or removed if it already has one. - var/obj/item/kitchen/knife/bayonet - var/mutable_appearance/knife_overlay - var/knife_x_offset = 0 - var/knife_y_offset = 0 - - var/list/upgrades = list() - - var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite - var/ammo_y_offset = 0 - var/flight_x_offset = 0 - var/flight_y_offset = 0 - - //Zooming - var/zoomable = FALSE //whether the gun generates a Zoom action on creation - var/zoomed = FALSE //Zoom toggle - var/zoom_amt = 3 //Distance in TURFs to move the user's screen forward (the "zoom" effect) - var/datum/action/toggle_scope_zoom/azoom - -/obj/item/gun/New() - ..() - if(gun_light) - verbs += /obj/item/gun/proc/toggle_gunlight - build_zooming() - -/obj/item/gun/Destroy() - QDEL_NULL(bayonet) - return ..() - -/obj/item/gun/handle_atom_del(atom/A) - if(A == bayonet) - clear_bayonet() - return ..() - -/obj/item/gun/examine(mob/user) - . = ..() - if(unique_reskin && !current_skin) - . += "Alt-click it to reskin it." - if(unique_rename) - . += "Use a pen on it to rename it." - if(bayonet) - . += "It has \a [bayonet] [can_bayonet ? "" : "permanently "]affixed to it." - if(can_bayonet) //if it has a bayonet and this is false, the bayonet is permanent. - . += "[bayonet] looks like it can be unscrewed from [src]." - else if(can_bayonet) - . += "It has a bayonet lug on it." - -/obj/item/gun/proc/process_chamber() - return 0 - -//check if there's enough ammo/energy/whatever to shoot one time -//i.e if clicking would make it shoot -/obj/item/gun/proc/can_shoot() - return 1 - -/obj/item/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj) - to_chat(user, "*click*") - playsound(user, 'sound/weapons/empty.ogg', 100, 1) - -/obj/item/gun/proc/shoot_live_shot(mob/living/user as mob|obj, pointblank = 0, mob/pbtarget = null, message = 1) - if(recoil) - shake_camera(user, recoil + 1, recoil) - - if(suppressed) - playsound(user, fire_sound, 10, 1) - else - playsound(user, fire_sound, 50, 1) - if(!message) - return - if(pointblank) - user.visible_message("[user] fires [src] point blank at [pbtarget]!", "You fire [src] point blank at [pbtarget]!", "You hear \a [fire_sound_text]!") - else - user.visible_message("[user] fires [src]!", "You fire [src]!", "You hear \a [fire_sound_text]!") - -/obj/item/gun/emp_act(severity) - for(var/obj/O in contents) - O.emp_act(severity) - -/obj/item/gun/afterattack(atom/target, mob/living/user, flag, params) - if(firing_burst) - return - if(flag) //It's adjacent, is the user, or is on the user's person - if(target in user.contents) //can't shoot stuff inside us. - return - if(!ismob(target) || user.a_intent == INTENT_HARM) //melee attack - return - if(target == user && user.zone_selected != "mouth") //so we can't shoot ourselves (unless mouth selected) - return - - if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can. - var/mob/living/L = user - if(!can_trigger_gun(L)) - return - - if(!can_shoot()) //Just because you can pull the trigger doesn't mean it can't shoot. - shoot_with_empty_chamber(user) - return - - if(flag) - if(user.zone_selected == "mouth") - handle_suicide(user, target, params) - return - - - //Exclude lasertag guns from the CLUMSY check. - if(clumsy_check) - if(istype(user)) - if((CLUMSY in user.mutations) && prob(40)) - to_chat(user, "You shoot yourself in the foot with \the [src]!") - var/shot_leg = pick("l_foot", "r_foot") - process_fire(user, user, 0, params, zone_override = shot_leg) - user.drop_item() - return - - if(weapon_weight == WEAPON_HEAVY && user.get_inactive_hand()) - to_chat(user, "You need both hands free to fire \the [src]!") - return - - //DUAL WIELDING - var/bonus_spread = 0 - var/loop_counter = 0 - if(ishuman(user) && user.a_intent == INTENT_HARM) - var/mob/living/carbon/human/H = user - for(var/obj/item/gun/G in get_both_hands(H)) - if(G == src || G.weapon_weight >= WEAPON_MEDIUM) - continue - else if(G.can_trigger_gun(user)) - bonus_spread += 24 * G.weapon_weight - loop_counter++ - addtimer(CALLBACK(G, .proc/process_fire, target, user, 1, params, null, bonus_spread), loop_counter) - - process_fire(target,user,1,params, null, bonus_spread) - -/obj/item/gun/proc/can_trigger_gun(mob/living/user) - if(!user.can_use_guns(src)) - return 0 - if(restricted_species && restricted_species.len && !is_type_in_list(user.dna.species, restricted_species)) - to_chat(user, "[src] is incompatible with your biology!") - return 0 - return 1 - -/obj/item/gun/proc/newshot() - return - -/obj/item/gun/proc/process_fire(atom/target as mob|obj|turf, mob/living/user as mob|obj, message = 1, params, zone_override, bonus_spread = 0) - add_fingerprint(user) - - if(semicd) - return - - var/sprd = 0 - var/randomized_gun_spread = 0 - if(spread) - randomized_gun_spread = rand(0,spread) - var/randomized_bonus_spread = rand(0, bonus_spread) - - if(burst_size > 1) - if(chambered && chambered.harmful) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. - to_chat(user, "[src] is lethally chambered! You don't want to risk harming anyone...") - return - firing_burst = 1 - for(var/i = 1 to burst_size) - if(!user) - break - if(!issilicon(user)) - if( i>1 && !(src in get_both_hands(user))) //for burst firing - break - if(chambered) - if(randomspread) - sprd = round((rand() - 0.5) * (randomized_gun_spread + randomized_bonus_spread)) - else - sprd = round((i / burst_size - 0.5) * (randomized_gun_spread + randomized_bonus_spread)) - if(!chambered.fire(target, user, params, ,suppressed, zone_override, sprd)) - shoot_with_empty_chamber(user) - break - else - if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot - shoot_live_shot(user, 1, target, message) - else - shoot_live_shot(user, 0, target, message) - else - shoot_with_empty_chamber(user) - break - process_chamber() - update_icon() - sleep(fire_delay) - firing_burst = 0 - else - if(chambered) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. - if(chambered.harmful) // Is the bullet chambered harmful? - to_chat(user, "[src] is lethally chambered! You don't want to risk harming anyone...") - return - sprd = round((pick(1,-1)) * (randomized_gun_spread + randomized_bonus_spread)) - if(!chambered.fire(target, user, params, , suppressed, zone_override, sprd)) - shoot_with_empty_chamber(user) - return - else - if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot - shoot_live_shot(user, 1, target, message) - else - shoot_live_shot(user, 0, target, message) - else - shoot_with_empty_chamber(user) - return - process_chamber() - update_icon() - semicd = 1 - spawn(fire_delay) - semicd = 0 - - if(user) - if(user.hand) - user.update_inv_l_hand() - else - user.update_inv_r_hand() - feedback_add_details("gun_fired","[type]") - -/obj/item/gun/attack(mob/M, mob/user) - if(user.a_intent == INTENT_HARM) //Flogging - if(bayonet) - M.attackby(bayonet, user) - else - return ..() - -/obj/item/gun/attack_obj(obj/O, mob/user) - if(user.a_intent == INTENT_HARM) - if(bayonet) - O.attackby(bayonet, user) - return - return ..() - -/obj/item/gun/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/flashlight/seclite)) - var/obj/item/flashlight/seclite/S = I - if(can_flashlight) - if(!gun_light) - if(!user.unEquip(I)) - return - to_chat(user, "You click [S] into place on [src].") - if(S.on) - set_light(0) - gun_light = S - I.loc = src - update_icon() - update_gun_light(user) - var/datum/action/A = new /datum/action/item_action/toggle_gunlight(src) - if(loc == user) - A.Grant(user) - - if(unique_rename) - if(istype(I, /obj/item/pen)) - rename_gun(user) - if(istype(I, /obj/item/kitchen/knife)) - var/obj/item/kitchen/knife/K = I - if(!can_bayonet || !K.bayonet || bayonet) //ensure the gun has an attachment point available, and that the knife is compatible with it. - return ..() - if(!user.drop_item()) - return - K.forceMove(src) - to_chat(user, "You attach [K] to [src]'s bayonet lug.") - bayonet = K - var/state = "bayonet" //Generic state. - if(bayonet.icon_state in icon_states('icons/obj/guns/bayonets.dmi')) //Snowflake state? - state = bayonet.icon_state - var/icon/bayonet_icons = 'icons/obj/guns/bayonets.dmi' - knife_overlay = mutable_appearance(bayonet_icons, state) - knife_overlay.pixel_x = knife_x_offset - knife_overlay.pixel_y = knife_y_offset - overlays += knife_overlay - else - return ..() - -/obj/item/gun/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(gun_light && can_flashlight) - for(var/obj/item/flashlight/seclite/S in src) - to_chat(user, "You unscrew the seclite from [src].") - gun_light = null - S.loc = get_turf(user) - update_gun_light(user) - S.update_brightness(user) - update_icon() - for(var/datum/action/item_action/toggle_gunlight/TGL in actions) - qdel(TGL) - else if(bayonet && can_bayonet) //if it has a bayonet, and the bayonet can be removed - bayonet.forceMove(get_turf(user)) - clear_bayonet() - -/obj/item/gun/proc/toggle_gunlight() - set name = "Toggle Gun Light" - set category = "Object" - set desc = "Click to toggle your weapon's attached flashlight." - - if(!gun_light) - return - - var/mob/living/carbon/human/user = usr - if(!isturf(user.loc)) - to_chat(user, "You cannot turn the light on while in this [user.loc]!") - gun_light.on = !gun_light.on - to_chat(user, "You toggle the gun light [gun_light.on ? "on":"off"].") - - playsound(user, 'sound/weapons/empty.ogg', 100, 1) - update_gun_light(user) - -/obj/item/gun/proc/update_gun_light(mob/user = null) - if(gun_light) - if(gun_light.on) - set_light(gun_light.brightness_on) - else - set_light(0) - update_icon() - else - set_light(0) - - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/gun/proc/clear_bayonet() - if(!bayonet) - return - bayonet = null - if(knife_overlay) - overlays -= knife_overlay - knife_overlay = null - return TRUE - -/obj/item/gun/extinguish_light() - if(gun_light.on) - toggle_gunlight() - visible_message("[src]'s light fades and turns off.") - -/obj/item/gun/pickup(mob/user) - . = ..() - if(azoom) - azoom.Grant(user) - -/obj/item/gun/dropped(mob/user) - ..() - zoom(user,FALSE) - if(azoom) - azoom.Remove(user) - -/obj/item/gun/AltClick(mob/user) - ..() - if(user.incapacitated()) - to_chat(user, "You can't do that right now!") - return - if(unique_reskin && !current_skin && loc == user) - reskin_gun(user) - -/obj/item/gun/proc/reskin_gun(mob/M) - var/choice = input(M,"Warning, you can only reskin your weapon once!","Reskin Gun") in options - - if(src && choice && !current_skin && !M.incapacitated() && in_range(M,src)) - if(options[choice] == null) - return - current_skin = options[choice] - to_chat(M, "Your gun is now skinned as [choice]. Say hello to your new friend.") - update_icon() - -/obj/item/gun/proc/rename_gun(mob/M) - var/input = stripped_input(M,"What do you want to name the gun?", ,"", MAX_NAME_LEN) - if(src && input && !M.stat && in_range(M,src) && !M.restrained() && M.canmove) - name = input - to_chat(M, "You name the gun [input]. Say hello to your new friend.") - return - -/obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params) - if(!ishuman(user) || !ishuman(target)) - return - - if(semicd) - return - - if(user == target) - target.visible_message("[user] sticks [src] in [user.p_their()] mouth, ready to pull the trigger...", \ - "You stick [src] in your mouth, ready to pull the trigger...") - else - target.visible_message("[user] points [src] at [target]'s head, ready to pull the trigger...", \ - "[user] points [src] at your head, ready to pull the trigger...") - - semicd = 1 - - if(!do_mob(user, target, 120) || user.zone_selected != "mouth") - if(user) - if(user == target) - user.visible_message("[user] decided life was worth living.") - else if(target && target.Adjacent(user)) - target.visible_message("[user] has decided to spare [target]'s life.", "[user] has decided to spare your life!") - semicd = 0 - return - - semicd = 0 - - target.visible_message("[user] pulls the trigger!", "[user] pulls the trigger!") - - if(chambered && chambered.BB) - chambered.BB.damage *= 5 - - process_fire(target, user, 1, params) - -/obj/item/gun/proc/isHandgun() - return 1 - -///////////// -// ZOOMING // -///////////// - -/datum/action/toggle_scope_zoom - name = "Toggle Scope" - check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_LYING - button_icon_state = "sniper_zoom" - var/obj/item/gun/gun = null - -/datum/action/toggle_scope_zoom/Trigger() - gun.zoom(owner) - -/datum/action/toggle_scope_zoom/IsAvailable() - . = ..() - if(!. && gun) - gun.zoom(owner, FALSE) - -/datum/action/toggle_scope_zoom/Remove(mob/living/L) - gun.zoom(L, FALSE) - ..() - -/obj/item/gun/proc/zoom(mob/living/user, forced_zoom) - if(!user || !user.client) - return - - switch(forced_zoom) - if(FALSE) - zoomed = FALSE - if(TRUE) - zoomed = TRUE - else - zoomed = !zoomed - - if(zoomed) - var/_x = 0 - var/_y = 0 - switch(user.dir) - if(NORTH) - _y = zoom_amt - if(EAST) - _x = zoom_amt - if(SOUTH) - _y = -zoom_amt - if(WEST) - _x = -zoom_amt - - user.client.pixel_x = world.icon_size*_x - user.client.pixel_y = world.icon_size*_y - else - user.client.pixel_x = 0 - user.client.pixel_y = 0 - - -//Proc, so that gun accessories/scopes/etc. can easily add zooming. -/obj/item/gun/proc/build_zooming() - if(azoom) - return - - if(zoomable) - azoom = new() - azoom.gun = src +/obj/item/gun + name = "gun" + desc = "It's a gun. It's pretty terrible, though." + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "detective" + item_state = "gun" + flags = CONDUCT + slot_flags = SLOT_BELT + materials = list(MAT_METAL=2000) + w_class = WEIGHT_CLASS_NORMAL + throwforce = 5 + throw_speed = 3 + throw_range = 5 + force = 5 + origin_tech = "combat=1" + needs_permit = 1 + attack_verb = list("struck", "hit", "bashed") + + var/fire_sound = "gunshot" + var/magin_sound = 'sound/weapons/gun_interactions/smg_magin.ogg' + var/magout_sound = 'sound/weapons/gun_interactions/smg_magout.ogg' + var/fire_sound_text = "gunshot" //the fire sound that shows in chat messages: laser blast, gunshot, etc. + var/suppressed = 0 //whether or not a message is displayed when fired + var/can_suppress = 0 + var/can_unsuppress = 1 + var/recoil = 0 //boom boom shake the room + var/clumsy_check = 1 + var/obj/item/ammo_casing/chambered = null + var/trigger_guard = TRIGGER_GUARD_NORMAL //trigger guard on the weapon, hulks can't fire them with their big meaty fingers + var/sawn_desc = null //description change if weapon is sawn-off + var/sawn_state = SAWN_INTACT + var/burst_size = 1 //how large a burst is + var/fire_delay = 0 //rate of fire for burst firing and semi auto + var/firing_burst = 0 //Prevent the weapon from firing again while already firing + var/semicd = 0 //cooldown handler + var/weapon_weight = WEAPON_LIGHT + var/list/restricted_species + + var/spread = 0 + var/randomspread = 1 + + var/unique_rename = 0 //allows renaming with a pen + var/unique_reskin = 0 //allows one-time reskinning + var/current_skin = null //the skin choice if we had a reskin + var/list/options = list() + + lefthand_file = 'icons/mob/inhands/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/guns_righthand.dmi' + + var/obj/item/flashlight/gun_light = null + var/can_flashlight = 0 + + var/can_bayonet = FALSE //if a bayonet can be added or removed if it already has one. + var/obj/item/kitchen/knife/bayonet + var/mutable_appearance/knife_overlay + var/knife_x_offset = 0 + var/knife_y_offset = 0 + + var/list/upgrades = list() + + var/ammo_x_offset = 0 //used for positioning ammo count overlay on sprite + var/ammo_y_offset = 0 + var/flight_x_offset = 0 + var/flight_y_offset = 0 + + //Zooming + var/zoomable = FALSE //whether the gun generates a Zoom action on creation + var/zoomed = FALSE //Zoom toggle + var/zoom_amt = 3 //Distance in TURFs to move the user's screen forward (the "zoom" effect) + var/datum/action/toggle_scope_zoom/azoom + +/obj/item/gun/New() + ..() + if(gun_light) + verbs += /obj/item/gun/proc/toggle_gunlight + build_zooming() + +/obj/item/gun/Destroy() + QDEL_NULL(bayonet) + return ..() + +/obj/item/gun/handle_atom_del(atom/A) + if(A == bayonet) + clear_bayonet() + return ..() + +/obj/item/gun/examine(mob/user) + . = ..() + if(unique_reskin && !current_skin) + . += "Alt-click it to reskin it." + if(unique_rename) + . += "Use a pen on it to rename it." + if(bayonet) + . += "It has \a [bayonet] [can_bayonet ? "" : "permanently "]affixed to it." + if(can_bayonet) //if it has a bayonet and this is false, the bayonet is permanent. + . += "[bayonet] looks like it can be unscrewed from [src]." + else if(can_bayonet) + . += "It has a bayonet lug on it." + +/obj/item/gun/proc/process_chamber() + return 0 + +//check if there's enough ammo/energy/whatever to shoot one time +//i.e if clicking would make it shoot +/obj/item/gun/proc/can_shoot() + return 1 + +/obj/item/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj) + to_chat(user, "*click*") + playsound(user, 'sound/weapons/empty.ogg', 100, 1) + +/obj/item/gun/proc/shoot_live_shot(mob/living/user as mob|obj, pointblank = 0, mob/pbtarget = null, message = 1) + if(recoil) + shake_camera(user, recoil + 1, recoil) + + if(suppressed) + playsound(user, fire_sound, 10, 1) + else + playsound(user, fire_sound, 50, 1) + if(!message) + return + if(pointblank) + user.visible_message("[user] fires [src] point blank at [pbtarget]!", "You fire [src] point blank at [pbtarget]!", "You hear \a [fire_sound_text]!") + else + user.visible_message("[user] fires [src]!", "You fire [src]!", "You hear \a [fire_sound_text]!") + +/obj/item/gun/emp_act(severity) + for(var/obj/O in contents) + O.emp_act(severity) + +/obj/item/gun/afterattack(atom/target, mob/living/user, flag, params) + if(firing_burst) + return + if(flag) //It's adjacent, is the user, or is on the user's person + if(target in user.contents) //can't shoot stuff inside us. + return + if(!ismob(target) || user.a_intent == INTENT_HARM) //melee attack + return + if(target == user && user.zone_selected != "mouth") //so we can't shoot ourselves (unless mouth selected) + return + + if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can. + var/mob/living/L = user + if(!can_trigger_gun(L)) + return + + if(!can_shoot()) //Just because you can pull the trigger doesn't mean it can't shoot. + shoot_with_empty_chamber(user) + return + + if(flag) + if(user.zone_selected == "mouth") + handle_suicide(user, target, params) + return + + + //Exclude lasertag guns from the CLUMSY check. + if(clumsy_check) + if(istype(user)) + if((CLUMSY in user.mutations) && prob(40)) + to_chat(user, "You shoot yourself in the foot with \the [src]!") + var/shot_leg = pick("l_foot", "r_foot") + process_fire(user, user, 0, params, zone_override = shot_leg) + user.drop_item() + return + + if(weapon_weight == WEAPON_HEAVY && user.get_inactive_hand()) + to_chat(user, "You need both hands free to fire \the [src]!") + return + + //DUAL WIELDING + var/bonus_spread = 0 + var/loop_counter = 0 + if(ishuman(user) && user.a_intent == INTENT_HARM) + var/mob/living/carbon/human/H = user + for(var/obj/item/gun/G in get_both_hands(H)) + if(G == src || G.weapon_weight >= WEAPON_MEDIUM) + continue + else if(G.can_trigger_gun(user)) + bonus_spread += 24 * G.weapon_weight + loop_counter++ + addtimer(CALLBACK(G, .proc/process_fire, target, user, 1, params, null, bonus_spread), loop_counter) + + process_fire(target,user,1,params, null, bonus_spread) + +/obj/item/gun/proc/can_trigger_gun(mob/living/user) + if(!user.can_use_guns(src)) + return 0 + if(restricted_species && restricted_species.len && !is_type_in_list(user.dna.species, restricted_species)) + to_chat(user, "[src] is incompatible with your biology!") + return 0 + return 1 + +/obj/item/gun/proc/newshot() + return + +/obj/item/gun/proc/process_fire(atom/target as mob|obj|turf, mob/living/user as mob|obj, message = 1, params, zone_override, bonus_spread = 0) + add_fingerprint(user) + + if(semicd) + return + + var/sprd = 0 + var/randomized_gun_spread = 0 + if(spread) + randomized_gun_spread = rand(0,spread) + var/randomized_bonus_spread = rand(0, bonus_spread) + + if(burst_size > 1) + if(chambered && chambered.harmful) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. + to_chat(user, "[src] is lethally chambered! You don't want to risk harming anyone...") + return + firing_burst = 1 + for(var/i = 1 to burst_size) + if(!user) + break + if(!issilicon(user)) + if( i>1 && !(src in get_both_hands(user))) //for burst firing + break + if(chambered) + if(randomspread) + sprd = round((rand() - 0.5) * (randomized_gun_spread + randomized_bonus_spread)) + else + sprd = round((i / burst_size - 0.5) * (randomized_gun_spread + randomized_bonus_spread)) + if(!chambered.fire(target, user, params, ,suppressed, zone_override, sprd)) + shoot_with_empty_chamber(user) + break + else + if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot + shoot_live_shot(user, 1, target, message) + else + shoot_live_shot(user, 0, target, message) + else + shoot_with_empty_chamber(user) + break + process_chamber() + update_icon() + sleep(fire_delay) + firing_burst = 0 + else + if(chambered) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) // If the user has the pacifist trait, then they won't be able to fire [src] if the round chambered inside of [src] is lethal. + if(chambered.harmful) // Is the bullet chambered harmful? + to_chat(user, "[src] is lethally chambered! You don't want to risk harming anyone...") + return + sprd = round((pick(1,-1)) * (randomized_gun_spread + randomized_bonus_spread)) + if(!chambered.fire(target, user, params, , suppressed, zone_override, sprd)) + shoot_with_empty_chamber(user) + return + else + if(get_dist(user, target) <= 1) //Making sure whether the target is in vicinity for the pointblank shot + shoot_live_shot(user, 1, target, message) + else + shoot_live_shot(user, 0, target, message) + else + shoot_with_empty_chamber(user) + return + process_chamber() + update_icon() + semicd = 1 + spawn(fire_delay) + semicd = 0 + + if(user) + if(user.hand) + user.update_inv_l_hand() + else + user.update_inv_r_hand() + feedback_add_details("gun_fired","[type]") + +/obj/item/gun/attack(mob/M, mob/user) + if(user.a_intent == INTENT_HARM) //Flogging + if(bayonet) + M.attackby(bayonet, user) + else + return ..() + +/obj/item/gun/attack_obj(obj/O, mob/user) + if(user.a_intent == INTENT_HARM) + if(bayonet) + O.attackby(bayonet, user) + return + return ..() + +/obj/item/gun/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/flashlight/seclite)) + var/obj/item/flashlight/seclite/S = I + if(can_flashlight) + if(!gun_light) + if(!user.unEquip(I)) + return + to_chat(user, "You click [S] into place on [src].") + if(S.on) + set_light(0) + gun_light = S + I.loc = src + update_icon() + update_gun_light(user) + var/datum/action/A = new /datum/action/item_action/toggle_gunlight(src) + if(loc == user) + A.Grant(user) + + if(unique_rename) + if(istype(I, /obj/item/pen)) + rename_gun(user) + if(istype(I, /obj/item/kitchen/knife)) + var/obj/item/kitchen/knife/K = I + if(!can_bayonet || !K.bayonet || bayonet) //ensure the gun has an attachment point available, and that the knife is compatible with it. + return ..() + if(!user.drop_item()) + return + K.forceMove(src) + to_chat(user, "You attach [K] to [src]'s bayonet lug.") + bayonet = K + var/state = "bayonet" //Generic state. + if(bayonet.icon_state in icon_states('icons/obj/guns/bayonets.dmi')) //Snowflake state? + state = bayonet.icon_state + var/icon/bayonet_icons = 'icons/obj/guns/bayonets.dmi' + knife_overlay = mutable_appearance(bayonet_icons, state) + knife_overlay.pixel_x = knife_x_offset + knife_overlay.pixel_y = knife_y_offset + overlays += knife_overlay + else + return ..() + +/obj/item/gun/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(gun_light && can_flashlight) + for(var/obj/item/flashlight/seclite/S in src) + to_chat(user, "You unscrew the seclite from [src].") + gun_light = null + S.loc = get_turf(user) + update_gun_light(user) + S.update_brightness(user) + update_icon() + for(var/datum/action/item_action/toggle_gunlight/TGL in actions) + qdel(TGL) + else if(bayonet && can_bayonet) //if it has a bayonet, and the bayonet can be removed + bayonet.forceMove(get_turf(user)) + clear_bayonet() + +/obj/item/gun/proc/toggle_gunlight() + set name = "Toggle Gun Light" + set category = "Object" + set desc = "Click to toggle your weapon's attached flashlight." + + if(!gun_light) + return + + var/mob/living/carbon/human/user = usr + if(!isturf(user.loc)) + to_chat(user, "You cannot turn the light on while in this [user.loc]!") + gun_light.on = !gun_light.on + to_chat(user, "You toggle the gun light [gun_light.on ? "on":"off"].") + + playsound(user, 'sound/weapons/empty.ogg', 100, 1) + update_gun_light(user) + +/obj/item/gun/proc/update_gun_light(mob/user = null) + if(gun_light) + if(gun_light.on) + set_light(gun_light.brightness_on) + else + set_light(0) + update_icon() + else + set_light(0) + + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/gun/proc/clear_bayonet() + if(!bayonet) + return + bayonet = null + if(knife_overlay) + overlays -= knife_overlay + knife_overlay = null + return TRUE + +/obj/item/gun/extinguish_light() + if(gun_light.on) + toggle_gunlight() + visible_message("[src]'s light fades and turns off.") + +/obj/item/gun/pickup(mob/user) + . = ..() + if(azoom) + azoom.Grant(user) + +/obj/item/gun/dropped(mob/user) + ..() + zoom(user,FALSE) + if(azoom) + azoom.Remove(user) + +/obj/item/gun/AltClick(mob/user) + ..() + if(user.incapacitated()) + to_chat(user, "You can't do that right now!") + return + if(unique_reskin && !current_skin && loc == user) + reskin_gun(user) + +/obj/item/gun/proc/reskin_gun(mob/M) + var/choice = input(M,"Warning, you can only reskin your weapon once!","Reskin Gun") in options + + if(src && choice && !current_skin && !M.incapacitated() && in_range(M,src)) + if(options[choice] == null) + return + current_skin = options[choice] + to_chat(M, "Your gun is now skinned as [choice]. Say hello to your new friend.") + update_icon() + +/obj/item/gun/proc/rename_gun(mob/M) + var/input = stripped_input(M,"What do you want to name the gun?", ,"", MAX_NAME_LEN) + if(src && input && !M.stat && in_range(M,src) && !M.restrained() && M.canmove) + name = input + to_chat(M, "You name the gun [input]. Say hello to your new friend.") + return + +/obj/item/gun/proc/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params) + if(!ishuman(user) || !ishuman(target)) + return + + if(semicd) + return + + if(user == target) + target.visible_message("[user] sticks [src] in [user.p_their()] mouth, ready to pull the trigger...", \ + "You stick [src] in your mouth, ready to pull the trigger...") + else + target.visible_message("[user] points [src] at [target]'s head, ready to pull the trigger...", \ + "[user] points [src] at your head, ready to pull the trigger...") + + semicd = 1 + + if(!do_mob(user, target, 120) || user.zone_selected != "mouth") + if(user) + if(user == target) + user.visible_message("[user] decided life was worth living.") + else if(target && target.Adjacent(user)) + target.visible_message("[user] has decided to spare [target]'s life.", "[user] has decided to spare your life!") + semicd = 0 + return + + semicd = 0 + + target.visible_message("[user] pulls the trigger!", "[user] pulls the trigger!") + + if(chambered && chambered.BB) + chambered.BB.damage *= 5 + + process_fire(target, user, 1, params) + +/obj/item/gun/proc/isHandgun() + return 1 + +///////////// +// ZOOMING // +///////////// + +/datum/action/toggle_scope_zoom + name = "Toggle Scope" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_LYING + button_icon_state = "sniper_zoom" + var/obj/item/gun/gun = null + +/datum/action/toggle_scope_zoom/Trigger() + gun.zoom(owner) + +/datum/action/toggle_scope_zoom/IsAvailable() + . = ..() + if(!. && gun) + gun.zoom(owner, FALSE) + +/datum/action/toggle_scope_zoom/Remove(mob/living/L) + gun.zoom(L, FALSE) + ..() + +/obj/item/gun/proc/zoom(mob/living/user, forced_zoom) + if(!user || !user.client) + return + + switch(forced_zoom) + if(FALSE) + zoomed = FALSE + if(TRUE) + zoomed = TRUE + else + zoomed = !zoomed + + if(zoomed) + var/_x = 0 + var/_y = 0 + switch(user.dir) + if(NORTH) + _y = zoom_amt + if(EAST) + _x = zoom_amt + if(SOUTH) + _y = -zoom_amt + if(WEST) + _x = -zoom_amt + + user.client.pixel_x = world.icon_size*_x + user.client.pixel_y = world.icon_size*_y + else + user.client.pixel_x = 0 + user.client.pixel_y = 0 + + +//Proc, so that gun accessories/scopes/etc. can easily add zooming. +/obj/item/gun/proc/build_zooming() + if(azoom) + return + + if(zoomable) + azoom = new() + azoom.gun = src diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm index a686eda11f0..d17da9a6d99 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -1,220 +1,220 @@ -/obj/item/gun/energy - icon_state = "energy" - name = "energy gun" - desc = "A basic energy-based gun." - icon = 'icons/obj/guns/energy.dmi' - fire_sound_text = "laser blast" - - var/obj/item/stock_parts/cell/cell //What type of power cell this uses - var/cell_type = /obj/item/stock_parts/cell - var/modifystate = 0 - var/list/ammo_type = list(/obj/item/ammo_casing/energy) - var/select = 1 //The state of the select fire switch. Determines from the ammo_type list what kind of shot is fired next. - var/can_charge = 1 - var/charge_sections = 4 - ammo_x_offset = 2 - var/shaded_charge = 0 //if this gun uses a stateful charge bar for more detail - var/selfcharge = 0 - var/use_external_power = 0 //if set, the weapon will look for an external power source to draw from, otherwise it recharges magically - var/charge_tick = 0 - var/charge_delay = 4 - -/obj/item/gun/energy/emp_act(severity) - cell.use(round(cell.charge / severity)) - if(chambered)//phil235 - if(chambered.BB) - qdel(chambered.BB) - chambered.BB = null - chambered = null - newshot() //phil235 - update_icon() - -/obj/item/gun/energy/get_cell() - return cell - -/obj/item/gun/energy/New() - ..() - if(cell_type) - cell = new cell_type(src) - else - cell = new(src) - cell.give(cell.maxcharge) - update_ammo_types() - on_recharge() - if(selfcharge) - START_PROCESSING(SSobj, src) - update_icon() - -/obj/item/gun/energy/proc/update_ammo_types() - var/obj/item/ammo_casing/energy/shot - for(var/i = 1, i <= ammo_type.len, i++) - var/shottype = ammo_type[i] - shot = new shottype(src) - ammo_type[i] = shot - shot = ammo_type[select] - fire_sound = shot.fire_sound - fire_delay = shot.delay - -/obj/item/gun/energy/Destroy() - if(selfcharge) - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/gun/energy/process() - if(selfcharge) //Every [recharge_time] ticks, recharge a shot for the cyborg - charge_tick++ - if(charge_tick < charge_delay) - return - charge_tick = 0 - if(!cell) - return // check if we actually need to recharge - var/obj/item/ammo_casing/energy/E = ammo_type[select] - if(use_external_power) - var/obj/item/stock_parts/cell/external = get_external_cell() - if(!external || !external.use(E.e_cost)) //Take power from the borg... - return //Note, uses /10 because of shitty mods to the cell system - cell.give(100) //... to recharge the shot - on_recharge() - update_icon() - -/obj/item/gun/energy/proc/on_recharge() - newshot() - -/obj/item/gun/energy/attack_self(mob/living/user as mob) - if(ammo_type.len > 1) - select_fire(user) - update_icon() - if(istype(user,/mob/living/carbon/human)) //This has to be here or else if you toggle modes by clicking the gun in hand - var/mob/living/carbon/human/H = user //Otherwise the mob icon doesn't update, blame shitty human update_icons() code - H.update_inv_l_hand() - H.update_inv_r_hand() - -/obj/item/gun/energy/can_shoot() - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - return cell.charge >= shot.e_cost - -/obj/item/gun/energy/newshot() - if(!ammo_type || !cell) - return - if(!chambered) - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - if(cell.charge >= shot.e_cost) //if there's enough power in the WEAPON'S cell... - chambered = shot //...prepare a new shot based on the current ammo type selected - if(!chambered.BB) - chambered.newshot() - -/obj/item/gun/energy/process_chamber() - if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired... - var/obj/item/ammo_casing/energy/shot = chambered - cell.use(shot.e_cost)//... drain the cell cell - robocharge() - chambered = null //either way, released the prepared shot - newshot() - -/obj/item/gun/energy/process_fire(atom/target, mob/living/user, message = 1, params, zone_override, bonus_spread = 0) - if(!chambered && can_shoot()) - process_chamber() - return ..() - -/obj/item/gun/energy/proc/select_fire(mob/living/user) - select++ - if(select > ammo_type.len) - select = 1 - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - fire_sound = shot.fire_sound - fire_delay = shot.delay - if(shot.select_name) - to_chat(user, "[src] is now set to [shot.select_name].") - if(chambered)//phil235 - if(chambered.BB) - qdel(chambered.BB) - chambered.BB = null - chambered = null - newshot() - update_icon() - return - -/obj/item/gun/energy/update_icon() - overlays.Cut() - var/ratio = Ceiling((cell.charge / cell.maxcharge) * charge_sections) - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - var/iconState = "[icon_state]_charge" - var/itemState = null - if(!initial(item_state)) - itemState = icon_state - if(modifystate) - overlays += "[icon_state]_[shot.select_name]" - iconState += "_[shot.select_name]" - if(itemState) - itemState += "[shot.select_name]" - if(cell.charge < shot.e_cost) - overlays += "[icon_state]_empty" - else - if(!shaded_charge) - for(var/i = ratio, i >= 1, i--) - overlays += image(icon = icon, icon_state = iconState, pixel_x = ammo_x_offset * (i -1)) - else - overlays += image(icon = icon, icon_state = "[icon_state]_[modifystate ? "[shot.select_name]_" : ""]charge[ratio]") - if(gun_light && can_flashlight) - var/iconF = "flight" - if(gun_light.on) - iconF = "flight_on" - overlays += image(icon = icon, icon_state = iconF, pixel_x = flight_x_offset, pixel_y = flight_y_offset) - if(bayonet && can_bayonet) - overlays += knife_overlay - if(itemState) - itemState += "[ratio]" - item_state = itemState - -/obj/item/gun/energy/ui_action_click() - toggle_gunlight() - -/obj/item/gun/energy/suicide_act(mob/user) - if(can_shoot()) - user.visible_message("[user] is putting the barrel of the [name] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide.") - sleep(25) - if(user.l_hand == src || user.r_hand == src) - user.visible_message("[user] melts [user.p_their()] face off with the [name]!") - playsound(loc, fire_sound, 50, 1, -1) - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - cell.use(shot.e_cost) - update_icon() - return FIRELOSS - else - user.visible_message("[user] panics and starts choking to death!") - return OXYLOSS - else - user.visible_message("[user] is pretending to blow [user.p_their()] brains out with the [name]! It looks like [user.p_theyre()] trying to commit suicide!
        ") - playsound(loc, 'sound/weapons/empty.ogg', 50, 1, -1) - return OXYLOSS - -/obj/item/gun/energy/vv_edit_var(var_name, var_value) - switch(var_name) - if("selfcharge") - if(var_value) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/gun/energy/proc/robocharge() - if(cell.charge == cell.maxcharge) - // No point in recharging a weapon's cell that is already at 100%. That would just waste borg cell power for no reason. - return - if(isrobot(loc)) - var/mob/living/silicon/robot/R = loc - if(R && R.cell) - var/obj/item/ammo_casing/energy/shot = ammo_type[select] //Necessary to find cost of shot - if(R.cell.use(shot.e_cost)) //Take power from the borg... - cell.give(shot.e_cost) //... to recharge the shot - -/obj/item/gun/energy/proc/get_external_cell() - if(istype(loc, /obj/item/rig_module)) - var/obj/item/rig_module/module = loc - if(module.holder && module.holder.wearer) - var/mob/living/carbon/human/H = module.holder.wearer - if(istype(H) && H.back) - var/obj/item/rig/suit = H.back - if(istype(suit)) - return suit.cell - return null +/obj/item/gun/energy + icon_state = "energy" + name = "energy gun" + desc = "A basic energy-based gun." + icon = 'icons/obj/guns/energy.dmi' + fire_sound_text = "laser blast" + + var/obj/item/stock_parts/cell/cell //What type of power cell this uses + var/cell_type = /obj/item/stock_parts/cell + var/modifystate = 0 + var/list/ammo_type = list(/obj/item/ammo_casing/energy) + var/select = 1 //The state of the select fire switch. Determines from the ammo_type list what kind of shot is fired next. + var/can_charge = 1 + var/charge_sections = 4 + ammo_x_offset = 2 + var/shaded_charge = 0 //if this gun uses a stateful charge bar for more detail + var/selfcharge = 0 + var/use_external_power = 0 //if set, the weapon will look for an external power source to draw from, otherwise it recharges magically + var/charge_tick = 0 + var/charge_delay = 4 + +/obj/item/gun/energy/emp_act(severity) + cell.use(round(cell.charge / severity)) + if(chambered)//phil235 + if(chambered.BB) + qdel(chambered.BB) + chambered.BB = null + chambered = null + newshot() //phil235 + update_icon() + +/obj/item/gun/energy/get_cell() + return cell + +/obj/item/gun/energy/New() + ..() + if(cell_type) + cell = new cell_type(src) + else + cell = new(src) + cell.give(cell.maxcharge) + update_ammo_types() + on_recharge() + if(selfcharge) + START_PROCESSING(SSobj, src) + update_icon() + +/obj/item/gun/energy/proc/update_ammo_types() + var/obj/item/ammo_casing/energy/shot + for(var/i = 1, i <= ammo_type.len, i++) + var/shottype = ammo_type[i] + shot = new shottype(src) + ammo_type[i] = shot + shot = ammo_type[select] + fire_sound = shot.fire_sound + fire_delay = shot.delay + +/obj/item/gun/energy/Destroy() + if(selfcharge) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/gun/energy/process() + if(selfcharge) //Every [recharge_time] ticks, recharge a shot for the cyborg + charge_tick++ + if(charge_tick < charge_delay) + return + charge_tick = 0 + if(!cell) + return // check if we actually need to recharge + var/obj/item/ammo_casing/energy/E = ammo_type[select] + if(use_external_power) + var/obj/item/stock_parts/cell/external = get_external_cell() + if(!external || !external.use(E.e_cost)) //Take power from the borg... + return //Note, uses /10 because of shitty mods to the cell system + cell.give(100) //... to recharge the shot + on_recharge() + update_icon() + +/obj/item/gun/energy/proc/on_recharge() + newshot() + +/obj/item/gun/energy/attack_self(mob/living/user as mob) + if(ammo_type.len > 1) + select_fire(user) + update_icon() + if(istype(user,/mob/living/carbon/human)) //This has to be here or else if you toggle modes by clicking the gun in hand + var/mob/living/carbon/human/H = user //Otherwise the mob icon doesn't update, blame shitty human update_icons() code + H.update_inv_l_hand() + H.update_inv_r_hand() + +/obj/item/gun/energy/can_shoot() + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + return cell.charge >= shot.e_cost + +/obj/item/gun/energy/newshot() + if(!ammo_type || !cell) + return + if(!chambered) + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + if(cell.charge >= shot.e_cost) //if there's enough power in the WEAPON'S cell... + chambered = shot //...prepare a new shot based on the current ammo type selected + if(!chambered.BB) + chambered.newshot() + +/obj/item/gun/energy/process_chamber() + if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired... + var/obj/item/ammo_casing/energy/shot = chambered + cell.use(shot.e_cost)//... drain the cell cell + robocharge() + chambered = null //either way, released the prepared shot + newshot() + +/obj/item/gun/energy/process_fire(atom/target, mob/living/user, message = 1, params, zone_override, bonus_spread = 0) + if(!chambered && can_shoot()) + process_chamber() + return ..() + +/obj/item/gun/energy/proc/select_fire(mob/living/user) + select++ + if(select > ammo_type.len) + select = 1 + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + fire_sound = shot.fire_sound + fire_delay = shot.delay + if(shot.select_name) + to_chat(user, "[src] is now set to [shot.select_name].") + if(chambered)//phil235 + if(chambered.BB) + qdel(chambered.BB) + chambered.BB = null + chambered = null + newshot() + update_icon() + return + +/obj/item/gun/energy/update_icon() + overlays.Cut() + var/ratio = Ceiling((cell.charge / cell.maxcharge) * charge_sections) + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + var/iconState = "[icon_state]_charge" + var/itemState = null + if(!initial(item_state)) + itemState = icon_state + if(modifystate) + overlays += "[icon_state]_[shot.select_name]" + iconState += "_[shot.select_name]" + if(itemState) + itemState += "[shot.select_name]" + if(cell.charge < shot.e_cost) + overlays += "[icon_state]_empty" + else + if(!shaded_charge) + for(var/i = ratio, i >= 1, i--) + overlays += image(icon = icon, icon_state = iconState, pixel_x = ammo_x_offset * (i -1)) + else + overlays += image(icon = icon, icon_state = "[icon_state]_[modifystate ? "[shot.select_name]_" : ""]charge[ratio]") + if(gun_light && can_flashlight) + var/iconF = "flight" + if(gun_light.on) + iconF = "flight_on" + overlays += image(icon = icon, icon_state = iconF, pixel_x = flight_x_offset, pixel_y = flight_y_offset) + if(bayonet && can_bayonet) + overlays += knife_overlay + if(itemState) + itemState += "[ratio]" + item_state = itemState + +/obj/item/gun/energy/ui_action_click() + toggle_gunlight() + +/obj/item/gun/energy/suicide_act(mob/user) + if(can_shoot()) + user.visible_message("[user] is putting the barrel of the [name] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide.") + sleep(25) + if(user.l_hand == src || user.r_hand == src) + user.visible_message("[user] melts [user.p_their()] face off with the [name]!") + playsound(loc, fire_sound, 50, 1, -1) + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + cell.use(shot.e_cost) + update_icon() + return FIRELOSS + else + user.visible_message("[user] panics and starts choking to death!") + return OXYLOSS + else + user.visible_message("[user] is pretending to blow [user.p_their()] brains out with the [name]! It looks like [user.p_theyre()] trying to commit suicide!
        ") + playsound(loc, 'sound/weapons/empty.ogg', 50, 1, -1) + return OXYLOSS + +/obj/item/gun/energy/vv_edit_var(var_name, var_value) + switch(var_name) + if("selfcharge") + if(var_value) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/gun/energy/proc/robocharge() + if(cell.charge == cell.maxcharge) + // No point in recharging a weapon's cell that is already at 100%. That would just waste borg cell power for no reason. + return + if(isrobot(loc)) + var/mob/living/silicon/robot/R = loc + if(R && R.cell) + var/obj/item/ammo_casing/energy/shot = ammo_type[select] //Necessary to find cost of shot + if(R.cell.use(shot.e_cost)) //Take power from the borg... + cell.give(shot.e_cost) //... to recharge the shot + +/obj/item/gun/energy/proc/get_external_cell() + if(istype(loc, /obj/item/rig_module)) + var/obj/item/rig_module/module = loc + if(module.holder && module.holder.wearer) + var/mob/living/carbon/human/H = module.holder.wearer + if(istype(H) && H.back) + var/obj/item/rig/suit = H.back + if(istype(suit)) + return suit.cell + return null diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index 518caa8ba70..ecf5eeed77e 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -599,4 +599,4 @@ desc = "Causes kinetic accelerator bolts to have an adjustable-colored tracer trail and explosion. Use in-hand to change color." /obj/item/borg/upgrade/modkit/tracer/adjustable/attack_self(mob/user) - bolt_color = input(user,"","Choose Color",bolt_color) as color|null \ No newline at end of file + bolt_color = input(user,"","Choose Color",bolt_color) as color|null diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 8635e08dfba..9d4cfaee3d4 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -1,159 +1,159 @@ -/obj/item/gun/energy/laser - name = "laser gun" - desc = "A basic energy-based laser gun that fires concentrated beams of light which pass through glass and thin metal." - icon_state = "laser" - item_state = "laser" - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=2000) - origin_tech = "combat=4;magnets=2" - ammo_type = list(/obj/item/ammo_casing/energy/lasergun) - ammo_x_offset = 1 - shaded_charge = 1 - -/obj/item/gun/energy/laser/practice - name = "practice laser gun" - desc = "A modified version of the basic laser gun, this one fires less concentrated energy bolts designed for target practice." - origin_tech = "combat=2;magnets=2" - ammo_type = list(/obj/item/ammo_casing/energy/laser/practice) - clumsy_check = 0 - needs_permit = 0 - -/obj/item/gun/energy/laser/retro - name ="retro laser gun" - icon_state = "retro" - desc = "An older model of the basic lasergun, no longer used by Nanotrasen's private security or military forces. Nevertheless, it is still quite deadly and easy to maintain, making it a favorite amongst pirates and other outlaws." - ammo_x_offset = 3 - -/obj/item/gun/energy/laser/captain - name = "antique laser gun" - icon_state = "caplaser" - item_state = "caplaser" - desc = "This is an antique laser gun. All craftsmanship is of the highest quality. It is decorated with assistant leather and chrome. The object menaces with spikes of energy. On the item is an image of Space Station 13. The station is exploding." - force = 10 - origin_tech = null - ammo_x_offset = 3 - selfcharge = 1 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/gun/energy/laser/captain/scattershot - name = "scatter shot laser rifle" - icon_state = "lasercannon" - item_state = "laser" - desc = "An industrial-grade heavy-duty laser rifle with a modified laser lense to scatter its shot into multiple smaller lasers. The inner-core can self-charge for theorically infinite use." - origin_tech = "combat=5;materials=4;powerstorage=4" - ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter, /obj/item/ammo_casing/energy/laser) - shaded_charge = 0 - -/obj/item/gun/energy/laser/cyborg - can_charge = 0 - desc = "An energy-based laser gun that draws power from the cyborg's internal energy cell directly. So this is what freedom looks like?" - ammo_type = list(/obj/item/ammo_casing/energy/laser/cyborg) - origin_tech = null - -/obj/item/gun/energy/laser/cyborg/newshot() - ..() - robocharge() - -/obj/item/gun/energy/laser/cyborg/emp_act() - return - -/obj/item/gun/energy/laser/scatter - name = "scatter laser gun" - desc = "A laser gun equipped with a refraction kit that spreads bolts." - ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter, /obj/item/ammo_casing/energy/laser) - -///Laser Cannon - -/obj/item/gun/energy/lasercannon - name = "accelerator laser cannon" - desc = "An advanced laser cannon that does more damage the farther away the target is." - icon_state = "lasercannon" - item_state = "laser" - w_class = WEIGHT_CLASS_BULKY - force = 10 - flags = CONDUCT - slot_flags = SLOT_BACK - origin_tech = "combat=4;magnets=4;powerstorage=3" - ammo_type = list(/obj/item/ammo_casing/energy/laser/accelerator) - ammo_x_offset = 3 - -/obj/item/gun/energy/lasercannon/isHandgun() - return 0 - -/obj/item/ammo_casing/energy/laser/accelerator - projectile_type = /obj/item/projectile/beam/laser/accelerator - select_name = "accelerator" - fire_sound = 'sound/weapons/lasercannonfire.ogg' - -/obj/item/projectile/beam/laser/accelerator - name = "accelerator laser" - icon_state = "heavylaser" - range = 255 - damage = 6 - -/obj/item/projectile/beam/laser/accelerator/Range() - ..() - damage = min(damage+7, 100) - -/obj/item/gun/energy/lasercannon/mounted - name = "mounted laser cannon" - selfcharge = 1 - use_external_power = 1 - charge_delay = 10 - -/obj/item/gun/energy/lasercannon/cyborg - -/obj/item/gun/energy/lasercannon/cyborg/newshot() - ..() - robocharge() - -/obj/item/gun/energy/lasercannon/cyborg/emp_act() - return - -/obj/item/gun/energy/xray - name = "xray laser gun" - desc = "A high-power laser gun capable of expelling concentrated xray blasts. These blasts will penetrate solid objects, but will decrease in power the longer they have to travel." - icon_state = "xray" - origin_tech = "combat=6;materials=4;magnets=4;syndicate=1" - ammo_type = list(/obj/item/ammo_casing/energy/xray) - -/obj/item/gun/energy/immolator - name = "Immolator laser gun" - desc = "A modified laser gun, shooting highly concetrated beams with higher intensity that ignites the target, for the cost of draining more power per shot" - icon_state = "immolator" - item_state = "laser" - ammo_type = list(/obj/item/ammo_casing/energy/immolator) - origin_tech = "combat=4;magnets=4;powerstorage=3" - shaded_charge = 1 - -/obj/item/gun/energy/immolator/multi - name = "multi lens immolator cannon" - desc = "A large laser cannon, similar to the Immolator Laser, with toggleable firemodes. It is frequently used by military-like forces through Nanotrasen." - icon_state = "multilensimmolator" - ammo_type = list(/obj/item/ammo_casing/energy/immolator/strong, /obj/item/ammo_casing/energy/immolator/scatter) - origin_tech = "combat=5;magnets=5;powerstorage=4" - -/obj/item/gun/energy/immolator/multi/update_icon() - ..() - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - var/append = shot.select_name - overlays += image(icon = icon, icon_state = "multilensimmolator-[append]") - -////////Laser Tag//////////////////// - -/obj/item/gun/energy/laser/tag - name = "laser tag gun" - desc = "Standard issue weapon of the Imperial Guard" - origin_tech = "combat=2;magnets=2" - clumsy_check = 0 - needs_permit = 0 - ammo_x_offset = 2 - selfcharge = 1 - -/obj/item/gun/energy/laser/tag/blue - icon_state = "bluetag" - ammo_type = list(/obj/item/ammo_casing/energy/laser/bluetag) - -/obj/item/gun/energy/laser/tag/red - icon_state = "redtag" - ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag) +/obj/item/gun/energy/laser + name = "laser gun" + desc = "A basic energy-based laser gun that fires concentrated beams of light which pass through glass and thin metal." + icon_state = "laser" + item_state = "laser" + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=2000) + origin_tech = "combat=4;magnets=2" + ammo_type = list(/obj/item/ammo_casing/energy/lasergun) + ammo_x_offset = 1 + shaded_charge = 1 + +/obj/item/gun/energy/laser/practice + name = "practice laser gun" + desc = "A modified version of the basic laser gun, this one fires less concentrated energy bolts designed for target practice." + origin_tech = "combat=2;magnets=2" + ammo_type = list(/obj/item/ammo_casing/energy/laser/practice) + clumsy_check = 0 + needs_permit = 0 + +/obj/item/gun/energy/laser/retro + name ="retro laser gun" + icon_state = "retro" + desc = "An older model of the basic lasergun, no longer used by Nanotrasen's private security or military forces. Nevertheless, it is still quite deadly and easy to maintain, making it a favorite amongst pirates and other outlaws." + ammo_x_offset = 3 + +/obj/item/gun/energy/laser/captain + name = "antique laser gun" + icon_state = "caplaser" + item_state = "caplaser" + desc = "This is an antique laser gun. All craftsmanship is of the highest quality. It is decorated with assistant leather and chrome. The object menaces with spikes of energy. On the item is an image of Space Station 13. The station is exploding." + force = 10 + origin_tech = null + ammo_x_offset = 3 + selfcharge = 1 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/gun/energy/laser/captain/scattershot + name = "scatter shot laser rifle" + icon_state = "lasercannon" + item_state = "laser" + desc = "An industrial-grade heavy-duty laser rifle with a modified laser lense to scatter its shot into multiple smaller lasers. The inner-core can self-charge for theorically infinite use." + origin_tech = "combat=5;materials=4;powerstorage=4" + ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter, /obj/item/ammo_casing/energy/laser) + shaded_charge = 0 + +/obj/item/gun/energy/laser/cyborg + can_charge = 0 + desc = "An energy-based laser gun that draws power from the cyborg's internal energy cell directly. So this is what freedom looks like?" + ammo_type = list(/obj/item/ammo_casing/energy/laser/cyborg) + origin_tech = null + +/obj/item/gun/energy/laser/cyborg/newshot() + ..() + robocharge() + +/obj/item/gun/energy/laser/cyborg/emp_act() + return + +/obj/item/gun/energy/laser/scatter + name = "scatter laser gun" + desc = "A laser gun equipped with a refraction kit that spreads bolts." + ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter, /obj/item/ammo_casing/energy/laser) + +///Laser Cannon + +/obj/item/gun/energy/lasercannon + name = "accelerator laser cannon" + desc = "An advanced laser cannon that does more damage the farther away the target is." + icon_state = "lasercannon" + item_state = "laser" + w_class = WEIGHT_CLASS_BULKY + force = 10 + flags = CONDUCT + slot_flags = SLOT_BACK + origin_tech = "combat=4;magnets=4;powerstorage=3" + ammo_type = list(/obj/item/ammo_casing/energy/laser/accelerator) + ammo_x_offset = 3 + +/obj/item/gun/energy/lasercannon/isHandgun() + return 0 + +/obj/item/ammo_casing/energy/laser/accelerator + projectile_type = /obj/item/projectile/beam/laser/accelerator + select_name = "accelerator" + fire_sound = 'sound/weapons/lasercannonfire.ogg' + +/obj/item/projectile/beam/laser/accelerator + name = "accelerator laser" + icon_state = "heavylaser" + range = 255 + damage = 6 + +/obj/item/projectile/beam/laser/accelerator/Range() + ..() + damage = min(damage+7, 100) + +/obj/item/gun/energy/lasercannon/mounted + name = "mounted laser cannon" + selfcharge = 1 + use_external_power = 1 + charge_delay = 10 + +/obj/item/gun/energy/lasercannon/cyborg + +/obj/item/gun/energy/lasercannon/cyborg/newshot() + ..() + robocharge() + +/obj/item/gun/energy/lasercannon/cyborg/emp_act() + return + +/obj/item/gun/energy/xray + name = "xray laser gun" + desc = "A high-power laser gun capable of expelling concentrated xray blasts. These blasts will penetrate solid objects, but will decrease in power the longer they have to travel." + icon_state = "xray" + origin_tech = "combat=6;materials=4;magnets=4;syndicate=1" + ammo_type = list(/obj/item/ammo_casing/energy/xray) + +/obj/item/gun/energy/immolator + name = "Immolator laser gun" + desc = "A modified laser gun, shooting highly concetrated beams with higher intensity that ignites the target, for the cost of draining more power per shot" + icon_state = "immolator" + item_state = "laser" + ammo_type = list(/obj/item/ammo_casing/energy/immolator) + origin_tech = "combat=4;magnets=4;powerstorage=3" + shaded_charge = 1 + +/obj/item/gun/energy/immolator/multi + name = "multi lens immolator cannon" + desc = "A large laser cannon, similar to the Immolator Laser, with toggleable firemodes. It is frequently used by military-like forces through Nanotrasen." + icon_state = "multilensimmolator" + ammo_type = list(/obj/item/ammo_casing/energy/immolator/strong, /obj/item/ammo_casing/energy/immolator/scatter) + origin_tech = "combat=5;magnets=5;powerstorage=4" + +/obj/item/gun/energy/immolator/multi/update_icon() + ..() + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + var/append = shot.select_name + overlays += image(icon = icon, icon_state = "multilensimmolator-[append]") + +////////Laser Tag//////////////////// + +/obj/item/gun/energy/laser/tag + name = "laser tag gun" + desc = "Standard issue weapon of the Imperial Guard" + origin_tech = "combat=2;magnets=2" + clumsy_check = 0 + needs_permit = 0 + ammo_x_offset = 2 + selfcharge = 1 + +/obj/item/gun/energy/laser/tag/blue + icon_state = "bluetag" + ammo_type = list(/obj/item/ammo_casing/energy/laser/bluetag) + +/obj/item/gun/energy/laser/tag/red + icon_state = "redtag" + ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag) diff --git a/code/modules/projectiles/guns/energy/nuclear.dm b/code/modules/projectiles/guns/energy/nuclear.dm index 4663b2d5c04..09b2400c165 100644 --- a/code/modules/projectiles/guns/energy/nuclear.dm +++ b/code/modules/projectiles/guns/energy/nuclear.dm @@ -1,99 +1,99 @@ -/obj/item/gun/energy/gun - name = "energy gun" - desc = "A basic energy-based gun with two settings: kill and disable." - icon_state = "energy" - item_state = null //so the human update icon uses the icon_state instead. - ammo_type = list(/obj/item/ammo_casing/energy/disabler, /obj/item/ammo_casing/energy/laser) - origin_tech = "combat=4;magnets=3" - modifystate = 2 - can_flashlight = 1 - ammo_x_offset = 3 - flight_x_offset = 15 - flight_y_offset = 10 - -/obj/item/gun/energy/gun/cyborg - desc = "An energy-based laser gun that draws power from the cyborg's internal energy cell directly. So this is what freedom looks like?" - -/obj/item/gun/energy/gun/cyborg/newshot() - ..() - robocharge() - -/obj/item/gun/energy/gun/cyborg/emp_act() - return - -/obj/item/gun/energy/gun/mounted - name = "mounted energy gun" - selfcharge = 1 - use_external_power = 1 - -/obj/item/gun/energy/gun/mini - name = "miniature energy gun" - desc = "A small, pistol-sized energy gun with a built-in flashlight. It has two settings: disable and kill." - icon_state = "mini" - w_class = WEIGHT_CLASS_SMALL - ammo_x_offset = 2 - charge_sections = 3 - can_flashlight = 0 // Can't attach or detach the flashlight, and override it's icon update - actions_types = list(/datum/action/item_action/toggle_gunlight) - -/obj/item/gun/energy/gun/mini/New() - gun_light = new /obj/item/flashlight/seclite(src) - ..() - cell.maxcharge = 600 - cell.charge = 600 - -/obj/item/gun/energy/gun/mini/update_icon() - ..() - if(gun_light && gun_light.on) - overlays += "mini-light" - -/obj/item/gun/energy/gun/hos - name = "\improper X-01 MultiPhase Energy Gun" - desc = "This is an expensive, modern recreation of an antique laser gun. This gun has several unique firemodes, but lacks the ability to recharge over time." - icon_state = "hoslaser" - origin_tech = null - force = 10 - ammo_type = list(/obj/item/ammo_casing/energy/electrode/hos, /obj/item/ammo_casing/energy/laser/hos, /obj/item/ammo_casing/energy/disabler) - ammo_x_offset = 4 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/gun/energy/gun/blueshield - name = "advanced stun revolver" - desc = "An advanced stun revolver with the capacity to shoot both electrodes and lasers." - icon_state = "bsgun" - item_state = "gun" - force = 7 - ammo_type = list(/obj/item/ammo_casing/energy/electrode/hos, /obj/item/ammo_casing/energy/laser/hos) - ammo_x_offset = 1 - shaded_charge = 1 - -/obj/item/gun/energy/gun/blueshield/pdw9 - name = "PDW-9 taser pistol" - desc = "A military grade sidearm, used by many militia forces throughout the local sector." - icon_state = "pdw9pistol" - -/obj/item/gun/energy/gun/turret - name = "hybrid turret gun" - desc = "A heavy hybrid energy cannon with two settings: Stun and kill." - icon_state = "turretlaser" - item_state = "turretlaser" - slot_flags = null - w_class = WEIGHT_CLASS_HUGE - ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) - weapon_weight = WEAPON_HEAVY - can_flashlight = 0 - trigger_guard = TRIGGER_GUARD_NONE - ammo_x_offset = 2 - -/obj/item/gun/energy/gun/nuclear - name = "advanced energy gun" - desc = "An energy gun with an experimental miniaturized nuclear reactor that automatically charges the internal power cell." - icon_state = "nucgun" - item_state = "nucgun" - origin_tech = "combat=4;magnets=4;powerstorage=4" - var/fail_tick = 0 - charge_delay = 5 - can_charge = 0 - ammo_x_offset = 1 - ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser, /obj/item/ammo_casing/energy/disabler) - selfcharge = 1 +/obj/item/gun/energy/gun + name = "energy gun" + desc = "A basic energy-based gun with two settings: kill and disable." + icon_state = "energy" + item_state = null //so the human update icon uses the icon_state instead. + ammo_type = list(/obj/item/ammo_casing/energy/disabler, /obj/item/ammo_casing/energy/laser) + origin_tech = "combat=4;magnets=3" + modifystate = 2 + can_flashlight = 1 + ammo_x_offset = 3 + flight_x_offset = 15 + flight_y_offset = 10 + +/obj/item/gun/energy/gun/cyborg + desc = "An energy-based laser gun that draws power from the cyborg's internal energy cell directly. So this is what freedom looks like?" + +/obj/item/gun/energy/gun/cyborg/newshot() + ..() + robocharge() + +/obj/item/gun/energy/gun/cyborg/emp_act() + return + +/obj/item/gun/energy/gun/mounted + name = "mounted energy gun" + selfcharge = 1 + use_external_power = 1 + +/obj/item/gun/energy/gun/mini + name = "miniature energy gun" + desc = "A small, pistol-sized energy gun with a built-in flashlight. It has two settings: disable and kill." + icon_state = "mini" + w_class = WEIGHT_CLASS_SMALL + ammo_x_offset = 2 + charge_sections = 3 + can_flashlight = 0 // Can't attach or detach the flashlight, and override it's icon update + actions_types = list(/datum/action/item_action/toggle_gunlight) + +/obj/item/gun/energy/gun/mini/New() + gun_light = new /obj/item/flashlight/seclite(src) + ..() + cell.maxcharge = 600 + cell.charge = 600 + +/obj/item/gun/energy/gun/mini/update_icon() + ..() + if(gun_light && gun_light.on) + overlays += "mini-light" + +/obj/item/gun/energy/gun/hos + name = "\improper X-01 MultiPhase Energy Gun" + desc = "This is an expensive, modern recreation of an antique laser gun. This gun has several unique firemodes, but lacks the ability to recharge over time." + icon_state = "hoslaser" + origin_tech = null + force = 10 + ammo_type = list(/obj/item/ammo_casing/energy/electrode/hos, /obj/item/ammo_casing/energy/laser/hos, /obj/item/ammo_casing/energy/disabler) + ammo_x_offset = 4 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/gun/energy/gun/blueshield + name = "advanced stun revolver" + desc = "An advanced stun revolver with the capacity to shoot both electrodes and lasers." + icon_state = "bsgun" + item_state = "gun" + force = 7 + ammo_type = list(/obj/item/ammo_casing/energy/electrode/hos, /obj/item/ammo_casing/energy/laser/hos) + ammo_x_offset = 1 + shaded_charge = 1 + +/obj/item/gun/energy/gun/blueshield/pdw9 + name = "PDW-9 taser pistol" + desc = "A military grade sidearm, used by many militia forces throughout the local sector." + icon_state = "pdw9pistol" + +/obj/item/gun/energy/gun/turret + name = "hybrid turret gun" + desc = "A heavy hybrid energy cannon with two settings: Stun and kill." + icon_state = "turretlaser" + item_state = "turretlaser" + slot_flags = null + w_class = WEIGHT_CLASS_HUGE + ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) + weapon_weight = WEAPON_HEAVY + can_flashlight = 0 + trigger_guard = TRIGGER_GUARD_NONE + ammo_x_offset = 2 + +/obj/item/gun/energy/gun/nuclear + name = "advanced energy gun" + desc = "An energy gun with an experimental miniaturized nuclear reactor that automatically charges the internal power cell." + icon_state = "nucgun" + item_state = "nucgun" + origin_tech = "combat=4;magnets=4;powerstorage=4" + var/fail_tick = 0 + charge_delay = 5 + can_charge = 0 + ammo_x_offset = 1 + ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser, /obj/item/ammo_casing/energy/disabler) + selfcharge = 1 diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm index 1a5766dc900..c773cb3563c 100644 --- a/code/modules/projectiles/guns/energy/pulse.dm +++ b/code/modules/projectiles/guns/energy/pulse.dm @@ -1,85 +1,85 @@ -/obj/item/gun/energy/pulse - name = "pulse rifle" - desc = "A heavy-duty, multifaceted energy rifle with three modes. Preferred by front-line combat personnel." - icon_state = "pulse" - item_state = null - w_class = WEIGHT_CLASS_BULKY - force = 10 - flags = CONDUCT - slot_flags = SLOT_BACK - ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse, /obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) - cell_type = /obj/item/stock_parts/cell/pulse - -/obj/item/gun/energy/pulse/emp_act(severity) - return - -/obj/item/gun/energy/pulse/isHandgun() - return 0 - -/obj/item/gun/energy/pulse/cyborg - -/obj/item/gun/energy/pulse/cyborg/newshot() - ..() - robocharge() - -/obj/item/gun/energy/pulse/carbine - name = "pulse carbine" - desc = "A compact variant of the pulse rifle with less firepower but easier storage." - w_class = WEIGHT_CLASS_NORMAL - slot_flags = SLOT_BELT - icon_state = "pulse_carbine" - item_state = "pulse" - cell_type = /obj/item/stock_parts/cell/pulse/carbine - can_flashlight = 1 - flight_x_offset = 18 - flight_y_offset = 12 - -/obj/item/gun/energy/pulse/pistol - name = "pulse pistol" - desc = "A pulse rifle in an easily concealed handgun package with low capacity." - w_class = WEIGHT_CLASS_SMALL - slot_flags = SLOT_BELT - icon_state = "pulse_pistol" - item_state = "gun" - cell_type = /obj/item/stock_parts/cell/pulse/pistol - can_charge = 0 - -/obj/item/gun/energy/pulse/pistol/isHandgun() - return 1 - -/obj/item/gun/energy/pulse/destroyer - name = "pulse destroyer" - desc = "A heavy-duty, pulse-based energy weapon." - cell_type = /obj/item/stock_parts/cell/infinite - ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse) - -/obj/item/gun/energy/pulse/destroyer/attack_self(mob/living/user) - to_chat(user, "[name] has three settings, and they are all DESTROY.") - -/obj/item/gun/energy/pulse/destroyer/annihilator - name = "pulse ANNIHILATOR" - desc = "For when the situation calls for a little more than a pulse destroyer." - ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter/pulse) - -/obj/item/gun/energy/pulse/pistol/m1911 - name = "\improper M1911-P" - desc = "A compact pulse core in a classic handgun frame for Nanotrasen officers. It's not the size of the gun, it's the size of the hole it puts through people." - icon_state = "m1911" - item_state = "gun" - cell_type = /obj/item/stock_parts/cell/infinite - -/obj/item/gun/energy/pulse/pistol/m1911/isHandgun() - return 1 - -/obj/item/gun/energy/pulse/turret - name = "pulse turret gun" - desc = "A heavy, turret-mounted pulse energy cannon." - icon_state = "turretlaser" - item_state = "turretlaser" - slot_flags = null - w_class = WEIGHT_CLASS_HUGE - ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser/pulse) - weapon_weight = WEAPON_MEDIUM - can_flashlight = 0 - trigger_guard = TRIGGER_GUARD_NONE - ammo_x_offset = 2 +/obj/item/gun/energy/pulse + name = "pulse rifle" + desc = "A heavy-duty, multifaceted energy rifle with three modes. Preferred by front-line combat personnel." + icon_state = "pulse" + item_state = null + w_class = WEIGHT_CLASS_BULKY + force = 10 + flags = CONDUCT + slot_flags = SLOT_BACK + ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse, /obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) + cell_type = /obj/item/stock_parts/cell/pulse + +/obj/item/gun/energy/pulse/emp_act(severity) + return + +/obj/item/gun/energy/pulse/isHandgun() + return 0 + +/obj/item/gun/energy/pulse/cyborg + +/obj/item/gun/energy/pulse/cyborg/newshot() + ..() + robocharge() + +/obj/item/gun/energy/pulse/carbine + name = "pulse carbine" + desc = "A compact variant of the pulse rifle with less firepower but easier storage." + w_class = WEIGHT_CLASS_NORMAL + slot_flags = SLOT_BELT + icon_state = "pulse_carbine" + item_state = "pulse" + cell_type = /obj/item/stock_parts/cell/pulse/carbine + can_flashlight = 1 + flight_x_offset = 18 + flight_y_offset = 12 + +/obj/item/gun/energy/pulse/pistol + name = "pulse pistol" + desc = "A pulse rifle in an easily concealed handgun package with low capacity." + w_class = WEIGHT_CLASS_SMALL + slot_flags = SLOT_BELT + icon_state = "pulse_pistol" + item_state = "gun" + cell_type = /obj/item/stock_parts/cell/pulse/pistol + can_charge = 0 + +/obj/item/gun/energy/pulse/pistol/isHandgun() + return 1 + +/obj/item/gun/energy/pulse/destroyer + name = "pulse destroyer" + desc = "A heavy-duty, pulse-based energy weapon." + cell_type = /obj/item/stock_parts/cell/infinite + ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse) + +/obj/item/gun/energy/pulse/destroyer/attack_self(mob/living/user) + to_chat(user, "[name] has three settings, and they are all DESTROY.") + +/obj/item/gun/energy/pulse/destroyer/annihilator + name = "pulse ANNIHILATOR" + desc = "For when the situation calls for a little more than a pulse destroyer." + ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter/pulse) + +/obj/item/gun/energy/pulse/pistol/m1911 + name = "\improper M1911-P" + desc = "A compact pulse core in a classic handgun frame for Nanotrasen officers. It's not the size of the gun, it's the size of the hole it puts through people." + icon_state = "m1911" + item_state = "gun" + cell_type = /obj/item/stock_parts/cell/infinite + +/obj/item/gun/energy/pulse/pistol/m1911/isHandgun() + return 1 + +/obj/item/gun/energy/pulse/turret + name = "pulse turret gun" + desc = "A heavy, turret-mounted pulse energy cannon." + icon_state = "turretlaser" + item_state = "turretlaser" + slot_flags = null + w_class = WEIGHT_CLASS_HUGE + ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser/pulse) + weapon_weight = WEAPON_MEDIUM + can_flashlight = 0 + trigger_guard = TRIGGER_GUARD_NONE + ammo_x_offset = 2 diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index 0572ce36f76..7705f3eb66c 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -1,519 +1,519 @@ -// Ion Rifles // -/obj/item/gun/energy/ionrifle - name = "ion rifle" - desc = "A man portable anti-armor weapon designed to disable mechanical threats" - icon_state = "ionrifle" - item_state = null //so the human update icon uses the icon_state instead. - fire_sound = 'sound/weapons/ionrifle.ogg' - origin_tech = "combat=4;magnets=4" - w_class = WEIGHT_CLASS_HUGE - flags = CONDUCT - slot_flags = SLOT_BACK - ammo_type = list(/obj/item/ammo_casing/energy/ion) - ammo_x_offset = 3 - flight_x_offset = 17 - flight_y_offset = 9 - -/obj/item/gun/energy/ionrifle/emp_act(severity) - return - -/obj/item/gun/energy/ionrifle/isHandgun() - return 0 - -/obj/item/gun/energy/ionrifle/carbine - name = "ion carbine" - desc = "The MK.II Prototype Ion Projector is a lightweight carbine version of the larger ion rifle, built to be ergonomic and efficient." - icon_state = "ioncarbine" - w_class = WEIGHT_CLASS_NORMAL - slot_flags = SLOT_BELT - ammo_x_offset = 2 - flight_x_offset = 18 - flight_y_offset = 11 - -// Decloner // -/obj/item/gun/energy/decloner - name = "biological demolecularisor" - desc = "A gun that discharges high amounts of controlled radiation to slowly break a target into component elements." - icon_state = "decloner" - fire_sound = 'sound/weapons/pulse3.ogg' - origin_tech = "combat=4;materials=4;biotech=5;plasmatech=6" - ammo_type = list(/obj/item/ammo_casing/energy/declone) - ammo_x_offset = 1 - -/obj/item/gun/energy/decloner/update_icon() - ..() - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - if(cell.charge > shot.e_cost) - overlays += "decloner_spin" - -// Flora Gun // -/obj/item/gun/energy/floragun - name = "floral somatoray" - desc = "A tool that discharges controlled radiation which induces mutation in plant cells." - icon_state = "flora" - item_state = "gun" - fire_sound = 'sound/effects/stealthoff.ogg' - ammo_type = list(/obj/item/ammo_casing/energy/flora/yield, /obj/item/ammo_casing/energy/flora/mut) - origin_tech = "materials=2;biotech=4" - modifystate = 1 - ammo_x_offset = 1 - selfcharge = 1 - -// Meteor Gun // -/obj/item/gun/energy/meteorgun - name = "meteor gun" - desc = "For the love of god, make sure you're aiming this the right way!" - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "riotgun" - item_state = "c20r" - fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' - w_class = WEIGHT_CLASS_BULKY - ammo_type = list(/obj/item/ammo_casing/energy/meteor) - cell_type = /obj/item/stock_parts/cell/potato - clumsy_check = 0 //Admin spawn only, might as well let clowns use it. - selfcharge = 1 - -/obj/item/gun/energy/meteorgun/pen - name = "meteor pen" - desc = "The pen is mightier than the sword." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "pen" - item_state = "pen" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - -// Mind Flayer // -/obj/item/gun/energy/mindflayer - name = "\improper Mind Flayer" - desc = "A prototype weapon recovered from the ruins of Research-Station Epsilon." - icon_state = "xray" - item_state = null - ammo_type = list(/obj/item/ammo_casing/energy/mindflayer) - ammo_x_offset = 2 - -// Energy Crossbows // -/obj/item/gun/energy/kinetic_accelerator/crossbow - name = "mini energy crossbow" - desc = "A weapon favored by syndicate stealth specialists." - icon_state = "crossbow" - item_state = "crossbow" - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=2000) - origin_tech = "combat=4;magnets=4;syndicate=5" - suppressed = 1 - ammo_type = list(/obj/item/ammo_casing/energy/bolt) - weapon_weight = WEAPON_LIGHT - unique_rename = 0 - overheat_time = 20 - holds_charge = TRUE - unique_frequency = TRUE - can_flashlight = 0 - max_mod_capacity = 0 - empty_state = null - -/obj/item/gun/energy/kinetic_accelerator/crossbow/ninja - name = "energy dart thrower" - ammo_type = list(/obj/item/ammo_casing/energy/dart) - -/obj/item/gun/energy/kinetic_accelerator/crossbow/large - name = "energy crossbow" - desc = "A reverse engineered weapon using syndicate technology." - icon_state = "crossbowlarge" - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=4000) - origin_tech = "combat=4;magnets=4;syndicate=2" - suppressed = 0 - ammo_type = list(/obj/item/ammo_casing/energy/bolt/large) - -/obj/item/gun/energy/kinetic_accelerator/crossbow/large/cyborg - desc = "One and done!" - icon_state = "crossbowlarge" - origin_tech = null - materials = list() - -/obj/item/gun/energy/kinetic_accelerator/suicide_act(mob/user) - if(!suppressed) - playsound(loc, 'sound/weapons/kenetic_reload.ogg', 60, 1) - user.visible_message("[user] cocks the [name] and pretends to blow [user.p_their()] brains out! It looks like [user.p_theyre()] trying to commit suicide!") - shoot_live_shot() - return OXYLOSS - -// Plasma Cutters // -/obj/item/gun/energy/plasmacutter - name = "plasma cutter" - desc = "A mining tool capable of expelling concentrated plasma bursts. You could use it to cut limbs off of xenos! Or, you know, mine stuff." - icon_state = "plasmacutter" - item_state = "plasmacutter" - modifystate = -1 - origin_tech = "combat=1;materials=3;magnets=2;plasmatech=3;engineering=1" - ammo_type = list(/obj/item/ammo_casing/energy/plasma) - fire_sound = 'sound/weapons/laser.ogg' - usesound = 'sound/items/welder.ogg' - toolspeed = 1 - container_type = OPENCONTAINER - flags = CONDUCT - attack_verb = list("attacked", "slashed", "cut", "sliced") - force = 12 - sharp = 1 - can_charge = 0 - -/obj/item/gun/energy/plasmacutter/examine(mob/user) - . = ..() - if(cell) - . += "[src] is [round(cell.percent())]% charged." - -/obj/item/gun/energy/plasmacutter/attackby(obj/item/A, mob/user) - if(istype(A, /obj/item/stack/sheet/mineral/plasma)) - if(cell.charge >= cell.maxcharge) - to_chat(user,"[src] is already fully charged.") - return - var/obj/item/stack/sheet/S = A - S.use(1) - cell.give(1000) - on_recharge() - to_chat(user, "You insert [A] in [src], recharging it.") - else if(istype(A, /obj/item/stack/ore/plasma)) - if(cell.charge >= cell.maxcharge) - to_chat(user,"[src] is already fully charged.") - return - var/obj/item/stack/ore/S = A - S.use(1) - cell.give(500) - on_recharge() - to_chat(user, "You insert [A] in [src], recharging it.") - else - return ..() - -/obj/item/gun/energy/plasmacutter/update_icon() - return - -/obj/item/gun/energy/plasmacutter/adv - name = "advanced plasma cutter" - icon_state = "adv_plasmacutter" - modifystate = "adv_plasmacutter" - origin_tech = "combat=3;materials=4;magnets=3;plasmatech=4;engineering=2" - ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv) - force = 15 - -// Wormhole Projectors // -/obj/item/gun/energy/wormhole_projector - name = "bluespace wormhole projector" - desc = "A projector that emits high density quantum-coupled bluespace beams." - ammo_type = list(/obj/item/ammo_casing/energy/wormhole, /obj/item/ammo_casing/energy/wormhole/orange) - item_state = null - icon_state = "wormhole_projector1" - origin_tech = "combat=4;bluespace=6;plasmatech=4;engineering=4" - var/obj/effect/portal/blue - var/obj/effect/portal/orange - - -/obj/item/gun/energy/wormhole_projector/update_icon() - icon_state = "wormhole_projector[select]" - item_state = icon_state - return - -/obj/item/gun/energy/wormhole_projector/process_chamber() - ..() - select_fire(usr) - -/obj/item/gun/energy/wormhole_projector/portal_destroyed(obj/effect/portal/P) - if(P.icon_state == "portal") - blue = null - if(orange) - orange.target = null - else - orange = null - if(blue) - blue.target = null - -/obj/item/gun/energy/wormhole_projector/proc/create_portal(obj/item/projectile/beam/wormhole/W) - var/obj/effect/portal/P = new /obj/effect/portal(get_turf(W), null, src) - P.precision = 0 - P.failchance = 0 - P.can_multitool_to_remove = 1 - if(W.name == "bluespace beam") - qdel(blue) - blue = P - else - qdel(orange) - P.icon_state = "portal1" - orange = P - if(orange && blue) - blue.target = get_turf(orange) - orange.target = get_turf(blue) - -/* 3d printer 'pseudo guns' for borgs */ -/obj/item/gun/energy/printer - name = "cyborg lmg" - desc = "A machinegun that fires 3d-printed flachettes slowly regenerated using a cyborg's internal power source." - icon_state = "l6closed0" - icon = 'icons/obj/guns/projectile.dmi' - cell_type = /obj/item/stock_parts/cell/secborg - ammo_type = list(/obj/item/ammo_casing/energy/c3dbullet) - can_charge = 0 - -/obj/item/gun/energy/printer/update_icon() - return - -/obj/item/gun/energy/printer/emp_act() - return - -// Instakill Lasers // -/obj/item/gun/energy/laser/instakill - name = "instakill rifle" - icon_state = "instagib" - item_state = "instagib" - desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit." - ammo_type = list(/obj/item/ammo_casing/energy/instakill) - force = 60 - origin_tech = "combat=7;magnets=6" - -/obj/item/gun/energy/laser/instakill/emp_act() //implying you could stop the instagib - return - -/obj/item/gun/energy/laser/instakill/red - desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit. This one has a red design." - icon_state = "instagibred" - item_state = "instagibred" - ammo_type = list(/obj/item/ammo_casing/energy/instakill/red) - -/obj/item/gun/energy/laser/instakill/blue - desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit. This one has a blue design." - icon_state = "instagibblue" - item_state = "instagibblue" - ammo_type = list(/obj/item/ammo_casing/energy/instakill/blue) - -// HONK Rifle // -/obj/item/gun/energy/clown - name = "HONK Rifle" - desc = "Clown Planet's finest." - icon_state = "disabler" - ammo_type = list(/obj/item/ammo_casing/energy/clown) - clumsy_check = 0 - selfcharge = 1 - ammo_x_offset = 3 - -/obj/item/gun/energy/toxgun - name = "plasma pistol" - desc = "A specialized firearm designed to fire lethal bolts of toxins." - icon_state = "toxgun" - fire_sound = 'sound/effects/stealthoff.ogg' - - w_class = WEIGHT_CLASS_NORMAL - origin_tech = "combat=4;magnets=4;powerstorage=3" - ammo_type = list(/obj/item/ammo_casing/energy/toxplasma) - shaded_charge = 1 - -// Energy Sniper // -/obj/item/gun/energy/sniperrifle - name = "L.W.A.P. Sniper Rifle" - desc = "A rifle constructed of lightweight materials, fitted with a SMART aiming-system scope." - icon_state = "esniper" - origin_tech = "combat=6;materials=5;powerstorage=4" - ammo_type = list(/obj/item/ammo_casing/energy/sniper) - slot_flags = SLOT_BACK - w_class = WEIGHT_CLASS_BULKY - zoomable = TRUE - zoom_amt = 7 //Long range, enough to see in front of you, but no tiles behind you. - shaded_charge = 1 - -// Temperature Gun // -/obj/item/gun/energy/temperature - name = "temperature gun" - icon = 'icons/obj/guns/gun_temperature.dmi' - icon_state = "tempgun_4" - item_state = "tempgun_4" - slot_flags = SLOT_BACK - w_class = WEIGHT_CLASS_BULKY - fire_sound = 'sound/weapons/pulse3.ogg' - desc = "A gun that changes the body temperature of its targets." - var/temperature = 300 - var/target_temperature = 300 - origin_tech = "combat=4;materials=4;powerstorage=3;magnets=2" - - ammo_type = list(/obj/item/ammo_casing/energy/temp) - selfcharge = 1 - - var/powercost = "" - var/powercostcolor = "" - - var/emagged = 0 //ups the temperature cap from 500 to 1000, targets hit by beams over 500 Kelvin will burst into flames - var/dat = "" - -/obj/item/gun/energy/temperature/New() - ..() - update_icon() - START_PROCESSING(SSobj, src) - - -/obj/item/gun/energy/temperature/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/gun/energy/temperature/newshot() - ..() - -/obj/item/gun/energy/temperature/attack_self(mob/living/user as mob) - user.set_machine(src) - update_dat() - user << browse("Temperature Gun Configuration
        [dat]", "window=tempgun;size=510x120") - onclose(user, "tempgun") - -/obj/item/gun/energy/temperature/emag_act(mob/user) - if(!emagged) - emagged = TRUE - to_chat(user, "You double the gun's temperature cap! Targets hit by searing beams will burst into flames!") - desc = "A gun that changes the body temperature of its targets. Its temperature cap has been hacked." - -/obj/item/gun/energy/temperature/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - add_fingerprint(usr) - - if(href_list["temp"]) - var/amount = text2num(href_list["temp"]) - if(amount > 0) - target_temperature = min((500 + 500*emagged), target_temperature+amount) - else - target_temperature = max(0, target_temperature+amount) - if(istype(loc, /mob)) - attack_self(loc) - add_fingerprint(usr) - return - -/obj/item/gun/energy/temperature/process() - ..() - var/obj/item/ammo_casing/energy/temp/T = ammo_type[select] - T.temp = temperature - switch(temperature) - if(0 to 100) - T.e_cost = 300 - powercost = "High" - if(100 to 250) - T.e_cost = 200 - powercost = "Medium" - if(251 to 300) - T.e_cost = 100 - powercost = "Low" - if(301 to 400) - T.e_cost = 200 - powercost = "Medium" - if(401 to 1000) - T.e_cost = 300 - powercost = "High" - switch(powercost) - if("High") - powercostcolor = "orange" - if("Medium") - powercostcolor = "green" - else - powercostcolor = "blue" - if(target_temperature != temperature) - var/difference = abs(target_temperature - temperature) - if(difference >= (10 + 40*emagged)) //so emagged temp guns adjust their temperature much more quickly - if(target_temperature < temperature) - temperature -= (10 + 40*emagged) - else - temperature += (10 + 40*emagged) - else - temperature = target_temperature - update_icon() - - if(istype(loc, /mob/living/carbon)) - var /mob/living/carbon/M = loc - if(src == M.machine) - update_dat() - M << browse("Temperature Gun Configuration
        [dat]", "window=tempgun;size=510x102") - return - -/obj/item/gun/energy/temperature/proc/update_dat() - dat = "" - dat += "Current output temperature: " - if(temperature > 500) - dat += "[temperature] ([round(temperature-T0C)]°C)" - dat += " SEARING!" - else if(temperature > (T0C + 50)) - dat += "[temperature] ([round(temperature-T0C)]°C)" - else if(temperature > (T0C - 50)) - dat += "[temperature] ([round(temperature-T0C)]°C)" - else - dat += "[temperature] ([round(temperature-T0C)]°C)" - dat += "
        " - dat += "Target output temperature: " //might be string idiocy, but at least it's easy to read - dat += "- " - dat += "- " - dat += "- " - dat += "[target_temperature] " - dat += "+ " - dat += "+ " - dat += "+" - dat += "
        " - dat += "Power cost: " - dat += "[powercost]" - -/obj/item/gun/energy/temperature/proc/update_temperature() - switch(temperature) - if(501 to INFINITY) - item_state = "tempgun_8" - if(400 to 500) - item_state = "tempgun_7" - if(360 to 400) - item_state = "tempgun_6" - if(335 to 360) - item_state = "tempgun_5" - if(295 to 335) - item_state = "tempgun_4" - if(260 to 295) - item_state = "tempgun_3" - if(200 to 260) - item_state = "tempgun_2" - if(120 to 260) - item_state = "tempgun_1" - if(-INFINITY to 120) - item_state = "tempgun_0" - icon_state = item_state - -/obj/item/gun/energy/temperature/update_icon() - overlays = 0 - update_temperature() - update_user() - update_charge() - -/obj/item/gun/energy/temperature/proc/update_user() - if(istype(loc,/mob/living/carbon)) - var/mob/living/carbon/M = loc - M.update_inv_back() - M.update_inv_l_hand() - M.update_inv_r_hand() - -/obj/item/gun/energy/temperature/proc/update_charge() - var/charge = cell.charge - switch(charge) - if(900 to INFINITY) overlays += "900" - if(800 to 900) overlays += "800" - if(700 to 800) overlays += "700" - if(600 to 700) overlays += "600" - if(500 to 600) overlays += "500" - if(400 to 500) overlays += "400" - if(300 to 400) overlays += "300" - if(200 to 300) overlays += "200" - if(100 to 202) overlays += "100" - if(-INFINITY to 100) overlays += "0" - -// Mimic Gun // -/obj/item/gun/energy/mimicgun - name = "mimic gun" - desc = "A self-defense weapon that exhausts organic targets, weakening them until they collapse. Why does this one have teeth?" - icon_state = "disabler" - ammo_type = list(/obj/item/ammo_casing/energy/mimic) - clumsy_check = 0 //Admin spawn only, might as well let clowns use it. - selfcharge = 1 - ammo_x_offset = 3 - var/mimic_type = /obj/item/gun/projectile/automatic/pistol //Setting this to the mimicgun type does exactly what you think it will. - -/obj/item/gun/energy/mimicgun/newshot() - var/obj/item/ammo_casing/energy/mimic/M = ammo_type[select] - M.mimic_type = mimic_type - ..() +// Ion Rifles // +/obj/item/gun/energy/ionrifle + name = "ion rifle" + desc = "A man portable anti-armor weapon designed to disable mechanical threats" + icon_state = "ionrifle" + item_state = null //so the human update icon uses the icon_state instead. + fire_sound = 'sound/weapons/ionrifle.ogg' + origin_tech = "combat=4;magnets=4" + w_class = WEIGHT_CLASS_HUGE + flags = CONDUCT + slot_flags = SLOT_BACK + ammo_type = list(/obj/item/ammo_casing/energy/ion) + ammo_x_offset = 3 + flight_x_offset = 17 + flight_y_offset = 9 + +/obj/item/gun/energy/ionrifle/emp_act(severity) + return + +/obj/item/gun/energy/ionrifle/isHandgun() + return 0 + +/obj/item/gun/energy/ionrifle/carbine + name = "ion carbine" + desc = "The MK.II Prototype Ion Projector is a lightweight carbine version of the larger ion rifle, built to be ergonomic and efficient." + icon_state = "ioncarbine" + w_class = WEIGHT_CLASS_NORMAL + slot_flags = SLOT_BELT + ammo_x_offset = 2 + flight_x_offset = 18 + flight_y_offset = 11 + +// Decloner // +/obj/item/gun/energy/decloner + name = "biological demolecularisor" + desc = "A gun that discharges high amounts of controlled radiation to slowly break a target into component elements." + icon_state = "decloner" + fire_sound = 'sound/weapons/pulse3.ogg' + origin_tech = "combat=4;materials=4;biotech=5;plasmatech=6" + ammo_type = list(/obj/item/ammo_casing/energy/declone) + ammo_x_offset = 1 + +/obj/item/gun/energy/decloner/update_icon() + ..() + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + if(cell.charge > shot.e_cost) + overlays += "decloner_spin" + +// Flora Gun // +/obj/item/gun/energy/floragun + name = "floral somatoray" + desc = "A tool that discharges controlled radiation which induces mutation in plant cells." + icon_state = "flora" + item_state = "gun" + fire_sound = 'sound/effects/stealthoff.ogg' + ammo_type = list(/obj/item/ammo_casing/energy/flora/yield, /obj/item/ammo_casing/energy/flora/mut) + origin_tech = "materials=2;biotech=4" + modifystate = 1 + ammo_x_offset = 1 + selfcharge = 1 + +// Meteor Gun // +/obj/item/gun/energy/meteorgun + name = "meteor gun" + desc = "For the love of god, make sure you're aiming this the right way!" + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "riotgun" + item_state = "c20r" + fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' + w_class = WEIGHT_CLASS_BULKY + ammo_type = list(/obj/item/ammo_casing/energy/meteor) + cell_type = /obj/item/stock_parts/cell/potato + clumsy_check = 0 //Admin spawn only, might as well let clowns use it. + selfcharge = 1 + +/obj/item/gun/energy/meteorgun/pen + name = "meteor pen" + desc = "The pen is mightier than the sword." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "pen" + item_state = "pen" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + +// Mind Flayer // +/obj/item/gun/energy/mindflayer + name = "\improper Mind Flayer" + desc = "A prototype weapon recovered from the ruins of Research-Station Epsilon." + icon_state = "xray" + item_state = null + ammo_type = list(/obj/item/ammo_casing/energy/mindflayer) + ammo_x_offset = 2 + +// Energy Crossbows // +/obj/item/gun/energy/kinetic_accelerator/crossbow + name = "mini energy crossbow" + desc = "A weapon favored by syndicate stealth specialists." + icon_state = "crossbow" + item_state = "crossbow" + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=2000) + origin_tech = "combat=4;magnets=4;syndicate=5" + suppressed = 1 + ammo_type = list(/obj/item/ammo_casing/energy/bolt) + weapon_weight = WEAPON_LIGHT + unique_rename = 0 + overheat_time = 20 + holds_charge = TRUE + unique_frequency = TRUE + can_flashlight = 0 + max_mod_capacity = 0 + empty_state = null + +/obj/item/gun/energy/kinetic_accelerator/crossbow/ninja + name = "energy dart thrower" + ammo_type = list(/obj/item/ammo_casing/energy/dart) + +/obj/item/gun/energy/kinetic_accelerator/crossbow/large + name = "energy crossbow" + desc = "A reverse engineered weapon using syndicate technology." + icon_state = "crossbowlarge" + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=4000) + origin_tech = "combat=4;magnets=4;syndicate=2" + suppressed = 0 + ammo_type = list(/obj/item/ammo_casing/energy/bolt/large) + +/obj/item/gun/energy/kinetic_accelerator/crossbow/large/cyborg + desc = "One and done!" + icon_state = "crossbowlarge" + origin_tech = null + materials = list() + +/obj/item/gun/energy/kinetic_accelerator/suicide_act(mob/user) + if(!suppressed) + playsound(loc, 'sound/weapons/kenetic_reload.ogg', 60, 1) + user.visible_message("[user] cocks the [name] and pretends to blow [user.p_their()] brains out! It looks like [user.p_theyre()] trying to commit suicide!
        ") + shoot_live_shot() + return OXYLOSS + +// Plasma Cutters // +/obj/item/gun/energy/plasmacutter + name = "plasma cutter" + desc = "A mining tool capable of expelling concentrated plasma bursts. You could use it to cut limbs off of xenos! Or, you know, mine stuff." + icon_state = "plasmacutter" + item_state = "plasmacutter" + modifystate = -1 + origin_tech = "combat=1;materials=3;magnets=2;plasmatech=3;engineering=1" + ammo_type = list(/obj/item/ammo_casing/energy/plasma) + fire_sound = 'sound/weapons/laser.ogg' + usesound = 'sound/items/welder.ogg' + toolspeed = 1 + container_type = OPENCONTAINER + flags = CONDUCT + attack_verb = list("attacked", "slashed", "cut", "sliced") + force = 12 + sharp = 1 + can_charge = 0 + +/obj/item/gun/energy/plasmacutter/examine(mob/user) + . = ..() + if(cell) + . += "[src] is [round(cell.percent())]% charged." + +/obj/item/gun/energy/plasmacutter/attackby(obj/item/A, mob/user) + if(istype(A, /obj/item/stack/sheet/mineral/plasma)) + if(cell.charge >= cell.maxcharge) + to_chat(user,"[src] is already fully charged.") + return + var/obj/item/stack/sheet/S = A + S.use(1) + cell.give(1000) + on_recharge() + to_chat(user, "You insert [A] in [src], recharging it.") + else if(istype(A, /obj/item/stack/ore/plasma)) + if(cell.charge >= cell.maxcharge) + to_chat(user,"[src] is already fully charged.") + return + var/obj/item/stack/ore/S = A + S.use(1) + cell.give(500) + on_recharge() + to_chat(user, "You insert [A] in [src], recharging it.") + else + return ..() + +/obj/item/gun/energy/plasmacutter/update_icon() + return + +/obj/item/gun/energy/plasmacutter/adv + name = "advanced plasma cutter" + icon_state = "adv_plasmacutter" + modifystate = "adv_plasmacutter" + origin_tech = "combat=3;materials=4;magnets=3;plasmatech=4;engineering=2" + ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv) + force = 15 + +// Wormhole Projectors // +/obj/item/gun/energy/wormhole_projector + name = "bluespace wormhole projector" + desc = "A projector that emits high density quantum-coupled bluespace beams." + ammo_type = list(/obj/item/ammo_casing/energy/wormhole, /obj/item/ammo_casing/energy/wormhole/orange) + item_state = null + icon_state = "wormhole_projector1" + origin_tech = "combat=4;bluespace=6;plasmatech=4;engineering=4" + var/obj/effect/portal/blue + var/obj/effect/portal/orange + + +/obj/item/gun/energy/wormhole_projector/update_icon() + icon_state = "wormhole_projector[select]" + item_state = icon_state + return + +/obj/item/gun/energy/wormhole_projector/process_chamber() + ..() + select_fire(usr) + +/obj/item/gun/energy/wormhole_projector/portal_destroyed(obj/effect/portal/P) + if(P.icon_state == "portal") + blue = null + if(orange) + orange.target = null + else + orange = null + if(blue) + blue.target = null + +/obj/item/gun/energy/wormhole_projector/proc/create_portal(obj/item/projectile/beam/wormhole/W) + var/obj/effect/portal/P = new /obj/effect/portal(get_turf(W), null, src) + P.precision = 0 + P.failchance = 0 + P.can_multitool_to_remove = 1 + if(W.name == "bluespace beam") + qdel(blue) + blue = P + else + qdel(orange) + P.icon_state = "portal1" + orange = P + if(orange && blue) + blue.target = get_turf(orange) + orange.target = get_turf(blue) + +/* 3d printer 'pseudo guns' for borgs */ +/obj/item/gun/energy/printer + name = "cyborg lmg" + desc = "A machinegun that fires 3d-printed flachettes slowly regenerated using a cyborg's internal power source." + icon_state = "l6closed0" + icon = 'icons/obj/guns/projectile.dmi' + cell_type = /obj/item/stock_parts/cell/secborg + ammo_type = list(/obj/item/ammo_casing/energy/c3dbullet) + can_charge = 0 + +/obj/item/gun/energy/printer/update_icon() + return + +/obj/item/gun/energy/printer/emp_act() + return + +// Instakill Lasers // +/obj/item/gun/energy/laser/instakill + name = "instakill rifle" + icon_state = "instagib" + item_state = "instagib" + desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit." + ammo_type = list(/obj/item/ammo_casing/energy/instakill) + force = 60 + origin_tech = "combat=7;magnets=6" + +/obj/item/gun/energy/laser/instakill/emp_act() //implying you could stop the instagib + return + +/obj/item/gun/energy/laser/instakill/red + desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit. This one has a red design." + icon_state = "instagibred" + item_state = "instagibred" + ammo_type = list(/obj/item/ammo_casing/energy/instakill/red) + +/obj/item/gun/energy/laser/instakill/blue + desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit. This one has a blue design." + icon_state = "instagibblue" + item_state = "instagibblue" + ammo_type = list(/obj/item/ammo_casing/energy/instakill/blue) + +// HONK Rifle // +/obj/item/gun/energy/clown + name = "HONK Rifle" + desc = "Clown Planet's finest." + icon_state = "disabler" + ammo_type = list(/obj/item/ammo_casing/energy/clown) + clumsy_check = 0 + selfcharge = 1 + ammo_x_offset = 3 + +/obj/item/gun/energy/toxgun + name = "plasma pistol" + desc = "A specialized firearm designed to fire lethal bolts of toxins." + icon_state = "toxgun" + fire_sound = 'sound/effects/stealthoff.ogg' + + w_class = WEIGHT_CLASS_NORMAL + origin_tech = "combat=4;magnets=4;powerstorage=3" + ammo_type = list(/obj/item/ammo_casing/energy/toxplasma) + shaded_charge = 1 + +// Energy Sniper // +/obj/item/gun/energy/sniperrifle + name = "L.W.A.P. Sniper Rifle" + desc = "A rifle constructed of lightweight materials, fitted with a SMART aiming-system scope." + icon_state = "esniper" + origin_tech = "combat=6;materials=5;powerstorage=4" + ammo_type = list(/obj/item/ammo_casing/energy/sniper) + slot_flags = SLOT_BACK + w_class = WEIGHT_CLASS_BULKY + zoomable = TRUE + zoom_amt = 7 //Long range, enough to see in front of you, but no tiles behind you. + shaded_charge = 1 + +// Temperature Gun // +/obj/item/gun/energy/temperature + name = "temperature gun" + icon = 'icons/obj/guns/gun_temperature.dmi' + icon_state = "tempgun_4" + item_state = "tempgun_4" + slot_flags = SLOT_BACK + w_class = WEIGHT_CLASS_BULKY + fire_sound = 'sound/weapons/pulse3.ogg' + desc = "A gun that changes the body temperature of its targets." + var/temperature = 300 + var/target_temperature = 300 + origin_tech = "combat=4;materials=4;powerstorage=3;magnets=2" + + ammo_type = list(/obj/item/ammo_casing/energy/temp) + selfcharge = 1 + + var/powercost = "" + var/powercostcolor = "" + + var/emagged = 0 //ups the temperature cap from 500 to 1000, targets hit by beams over 500 Kelvin will burst into flames + var/dat = "" + +/obj/item/gun/energy/temperature/New() + ..() + update_icon() + START_PROCESSING(SSobj, src) + + +/obj/item/gun/energy/temperature/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/gun/energy/temperature/newshot() + ..() + +/obj/item/gun/energy/temperature/attack_self(mob/living/user as mob) + user.set_machine(src) + update_dat() + user << browse("Temperature Gun Configuration
        [dat]", "window=tempgun;size=510x120") + onclose(user, "tempgun") + +/obj/item/gun/energy/temperature/emag_act(mob/user) + if(!emagged) + emagged = TRUE + to_chat(user, "You double the gun's temperature cap! Targets hit by searing beams will burst into flames!") + desc = "A gun that changes the body temperature of its targets. Its temperature cap has been hacked." + +/obj/item/gun/energy/temperature/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + add_fingerprint(usr) + + if(href_list["temp"]) + var/amount = text2num(href_list["temp"]) + if(amount > 0) + target_temperature = min((500 + 500*emagged), target_temperature+amount) + else + target_temperature = max(0, target_temperature+amount) + if(istype(loc, /mob)) + attack_self(loc) + add_fingerprint(usr) + return + +/obj/item/gun/energy/temperature/process() + ..() + var/obj/item/ammo_casing/energy/temp/T = ammo_type[select] + T.temp = temperature + switch(temperature) + if(0 to 100) + T.e_cost = 300 + powercost = "High" + if(100 to 250) + T.e_cost = 200 + powercost = "Medium" + if(251 to 300) + T.e_cost = 100 + powercost = "Low" + if(301 to 400) + T.e_cost = 200 + powercost = "Medium" + if(401 to 1000) + T.e_cost = 300 + powercost = "High" + switch(powercost) + if("High") + powercostcolor = "orange" + if("Medium") + powercostcolor = "green" + else + powercostcolor = "blue" + if(target_temperature != temperature) + var/difference = abs(target_temperature - temperature) + if(difference >= (10 + 40*emagged)) //so emagged temp guns adjust their temperature much more quickly + if(target_temperature < temperature) + temperature -= (10 + 40*emagged) + else + temperature += (10 + 40*emagged) + else + temperature = target_temperature + update_icon() + + if(istype(loc, /mob/living/carbon)) + var /mob/living/carbon/M = loc + if(src == M.machine) + update_dat() + M << browse("Temperature Gun Configuration
        [dat]", "window=tempgun;size=510x102") + return + +/obj/item/gun/energy/temperature/proc/update_dat() + dat = "" + dat += "Current output temperature: " + if(temperature > 500) + dat += "[temperature] ([round(temperature-T0C)]°C)" + dat += " SEARING!" + else if(temperature > (T0C + 50)) + dat += "[temperature] ([round(temperature-T0C)]°C)" + else if(temperature > (T0C - 50)) + dat += "[temperature] ([round(temperature-T0C)]°C)" + else + dat += "[temperature] ([round(temperature-T0C)]°C)" + dat += "
        " + dat += "Target output temperature: " //might be string idiocy, but at least it's easy to read + dat += "- " + dat += "- " + dat += "- " + dat += "[target_temperature] " + dat += "+ " + dat += "+ " + dat += "+" + dat += "
        " + dat += "Power cost: " + dat += "[powercost]" + +/obj/item/gun/energy/temperature/proc/update_temperature() + switch(temperature) + if(501 to INFINITY) + item_state = "tempgun_8" + if(400 to 500) + item_state = "tempgun_7" + if(360 to 400) + item_state = "tempgun_6" + if(335 to 360) + item_state = "tempgun_5" + if(295 to 335) + item_state = "tempgun_4" + if(260 to 295) + item_state = "tempgun_3" + if(200 to 260) + item_state = "tempgun_2" + if(120 to 260) + item_state = "tempgun_1" + if(-INFINITY to 120) + item_state = "tempgun_0" + icon_state = item_state + +/obj/item/gun/energy/temperature/update_icon() + overlays = 0 + update_temperature() + update_user() + update_charge() + +/obj/item/gun/energy/temperature/proc/update_user() + if(istype(loc,/mob/living/carbon)) + var/mob/living/carbon/M = loc + M.update_inv_back() + M.update_inv_l_hand() + M.update_inv_r_hand() + +/obj/item/gun/energy/temperature/proc/update_charge() + var/charge = cell.charge + switch(charge) + if(900 to INFINITY) overlays += "900" + if(800 to 900) overlays += "800" + if(700 to 800) overlays += "700" + if(600 to 700) overlays += "600" + if(500 to 600) overlays += "500" + if(400 to 500) overlays += "400" + if(300 to 400) overlays += "300" + if(200 to 300) overlays += "200" + if(100 to 202) overlays += "100" + if(-INFINITY to 100) overlays += "0" + +// Mimic Gun // +/obj/item/gun/energy/mimicgun + name = "mimic gun" + desc = "A self-defense weapon that exhausts organic targets, weakening them until they collapse. Why does this one have teeth?" + icon_state = "disabler" + ammo_type = list(/obj/item/ammo_casing/energy/mimic) + clumsy_check = 0 //Admin spawn only, might as well let clowns use it. + selfcharge = 1 + ammo_x_offset = 3 + var/mimic_type = /obj/item/gun/projectile/automatic/pistol //Setting this to the mimicgun type does exactly what you think it will. + +/obj/item/gun/energy/mimicgun/newshot() + var/obj/item/ammo_casing/energy/mimic/M = ammo_type[select] + M.mimic_type = mimic_type + ..() diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm index 5f06a0fa11e..70bb8be7e46 100644 --- a/code/modules/projectiles/guns/energy/stun.dm +++ b/code/modules/projectiles/guns/energy/stun.dm @@ -1,60 +1,60 @@ -/obj/item/gun/energy/taser - name = "taser gun" - desc = "A small, low capacity gun used for non-lethal takedowns." - icon_state = "taser" - item_state = null //so the human update icon uses the icon_state instead. - origin_tech = "combat=3" - ammo_type = list(/obj/item/ammo_casing/energy/electrode) - ammo_x_offset = 3 - -/obj/item/gun/energy/taser/mounted - name = "mounted taser gun" - selfcharge = 1 - use_external_power = 1 - -/obj/item/gun/energy/shock_revolver - name = "tesla revolver" - desc = "A high-tech revolver that fires internal, reusable shock cartridges in a revolving cylinder. The cartridges can be recharged using conventional rechargers." - icon_state = "stunrevolver" - item_state = "gun" - origin_tech = "combat=4;materials=4;powerstorage=4" - ammo_type = list(/obj/item/ammo_casing/energy/shock_revolver) - can_flashlight = 0 - shaded_charge = 1 - -/obj/item/gun/energy/gun/advtaser - name = "hybrid taser" - desc = "A dual-mode taser designed to fire both short-range high-power electrodes and long-range disabler beams." - icon_state = "advtaser" - ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/disabler) - origin_tech = "combat=4" - ammo_x_offset = 2 - -/obj/item/gun/energy/gun/advtaser/cyborg - name = "cyborg taser" - desc = "An integrated hybrid taser that draws directly from a cyborg's power cell. The weapon contains a limiter to prevent the cyborg's power cell from overheating." - can_flashlight = 0 - can_charge = 0 - -/obj/item/gun/energy/gun/advtaser/cyborg/newshot() - ..() - robocharge() - -/obj/item/gun/energy/disabler - name = "disabler" - desc = "A self-defense weapon that exhausts organic targets, weakening them until they collapse." - icon_state = "disabler" - item_state = null - origin_tech = "combat=3" - ammo_type = list(/obj/item/ammo_casing/energy/disabler) - ammo_x_offset = 3 - -/obj/item/gun/energy/disabler/cyborg - name = "cyborg disabler" - desc = "An integrated disabler that draws from a cyborg's power cell. This weapon contains a limiter to prevent the cyborg's power cell from overheating." - ammo_type = list(/obj/item/ammo_casing/energy/disabler/cyborg) - can_charge = 0 - -/obj/item/gun/energy/disabler/cyborg/newshot() - ..() - robocharge() +/obj/item/gun/energy/taser + name = "taser gun" + desc = "A small, low capacity gun used for non-lethal takedowns." + icon_state = "taser" + item_state = null //so the human update icon uses the icon_state instead. + origin_tech = "combat=3" + ammo_type = list(/obj/item/ammo_casing/energy/electrode) + ammo_x_offset = 3 + +/obj/item/gun/energy/taser/mounted + name = "mounted taser gun" + selfcharge = 1 + use_external_power = 1 + +/obj/item/gun/energy/shock_revolver + name = "tesla revolver" + desc = "A high-tech revolver that fires internal, reusable shock cartridges in a revolving cylinder. The cartridges can be recharged using conventional rechargers." + icon_state = "stunrevolver" + item_state = "gun" + origin_tech = "combat=4;materials=4;powerstorage=4" + ammo_type = list(/obj/item/ammo_casing/energy/shock_revolver) + can_flashlight = 0 + shaded_charge = 1 + +/obj/item/gun/energy/gun/advtaser + name = "hybrid taser" + desc = "A dual-mode taser designed to fire both short-range high-power electrodes and long-range disabler beams." + icon_state = "advtaser" + ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/disabler) + origin_tech = "combat=4" + ammo_x_offset = 2 + +/obj/item/gun/energy/gun/advtaser/cyborg + name = "cyborg taser" + desc = "An integrated hybrid taser that draws directly from a cyborg's power cell. The weapon contains a limiter to prevent the cyborg's power cell from overheating." + can_flashlight = 0 + can_charge = 0 + +/obj/item/gun/energy/gun/advtaser/cyborg/newshot() + ..() + robocharge() + +/obj/item/gun/energy/disabler + name = "disabler" + desc = "A self-defense weapon that exhausts organic targets, weakening them until they collapse." + icon_state = "disabler" + item_state = null + origin_tech = "combat=3" + ammo_type = list(/obj/item/ammo_casing/energy/disabler) + ammo_x_offset = 3 + +/obj/item/gun/energy/disabler/cyborg + name = "cyborg disabler" + desc = "An integrated disabler that draws from a cyborg's power cell. This weapon contains a limiter to prevent the cyborg's power cell from overheating." + ammo_type = list(/obj/item/ammo_casing/energy/disabler/cyborg) + can_charge = 0 + +/obj/item/gun/energy/disabler/cyborg/newshot() + ..() + robocharge() diff --git a/code/modules/projectiles/guns/misc/blastcannon.dm b/code/modules/projectiles/guns/misc/blastcannon.dm index 89cceab6cd3..6b29a203c02 100644 --- a/code/modules/projectiles/guns/misc/blastcannon.dm +++ b/code/modules/projectiles/guns/misc/blastcannon.dm @@ -127,4 +127,4 @@ lightr = max(lightr - 1, 0) /obj/item/projectile/blastwave/ex_act() - return \ No newline at end of file + return diff --git a/code/modules/projectiles/guns/mounted.dm b/code/modules/projectiles/guns/mounted.dm index d024c596fe1..162cc541fd2 100644 --- a/code/modules/projectiles/guns/mounted.dm +++ b/code/modules/projectiles/guns/mounted.dm @@ -17,4 +17,4 @@ item_state = "armcannonlase" force = 5 selfcharge = 1 - trigger_guard = TRIGGER_GUARD_ALLOW_ALL \ No newline at end of file + trigger_guard = TRIGGER_GUARD_ALLOW_ALL diff --git a/code/modules/projectiles/guns/projectile.dm b/code/modules/projectiles/guns/projectile.dm index 133331f8e44..25db1f10dcb 100644 --- a/code/modules/projectiles/guns/projectile.dm +++ b/code/modules/projectiles/guns/projectile.dm @@ -1,214 +1,214 @@ -/obj/item/gun/projectile - desc = "Now comes in flavors like GUN. Uses 10mm ammo, for some reason" - name = "projectile gun" - icon_state = "pistol" - origin_tech = "combat=2;materials=2" - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=1000) - - var/mag_type = /obj/item/ammo_box/magazine/m10mm //Removes the need for max_ammo and caliber info - var/obj/item/ammo_box/magazine/magazine - var/can_tactical = FALSE //check to see if the gun can tactically reload - -/obj/item/gun/projectile/New() - ..() - if(!magazine) - magazine = new mag_type(src) - chamber_round() - update_icon() - return - -/obj/item/gun/projectile/update_icon() - ..() - if(current_skin) - icon_state = "[current_skin][suppressed ? "-suppressed" : ""][sawn_state ? "-sawn" : ""]" - else - icon_state = "[initial(icon_state)][suppressed ? "-suppressed" : ""][sawn_state ? "-sawn" : ""]" - if(bayonet && can_bayonet) - overlays += knife_overlay - -/obj/item/gun/projectile/process_chamber(eject_casing = 1, empty_chamber = 1) - var/obj/item/ammo_casing/AC = chambered //Find chambered round - if(isnull(AC) || !istype(AC)) - chamber_round() - return - if(eject_casing) - AC.loc = get_turf(src) //Eject casing onto ground. - AC.SpinAnimation(10, 1) //next gen special effects - playsound(src, chambered.drop_sound, 100, 1) - if(empty_chamber) - chambered = null - chamber_round() - return - -/obj/item/gun/projectile/proc/chamber_round() - if(chambered || !magazine) - return - else if(magazine.ammo_count()) - chambered = magazine.get_round() - chambered.loc = src - return - -/obj/item/gun/projectile/can_shoot() - if(!magazine || !magazine.ammo_count(0)) - return 0 - return 1 - -/obj/item/gun/projectile/proc/can_reload() - return !magazine - -/obj/item/gun/projectile/proc/reload(obj/item/ammo_box/magazine/AM, mob/user as mob) - user.remove_from_mob(AM) - magazine = AM - magazine.loc = src - playsound(src, magin_sound, 50, 1) - chamber_round() - AM.update_icon() - update_icon() - return - -/obj/item/gun/projectile/attackby(var/obj/item/A as obj, mob/user as mob, params) - if(istype(A, /obj/item/ammo_box/magazine)) - var/obj/item/ammo_box/magazine/AM = A - if(istype(AM, mag_type)) - if(can_reload()) - reload(AM, user) - to_chat(user, "You load a new magazine into \the [src].") - return TRUE - else if(!can_tactical) - to_chat(user, "There's already a magazine in \the [src].") - return TRUE - else - to_chat(user, "You perform a tactical reload on \the [src], replacing the magazine.") - magazine.loc = get_turf(loc) - magazine.update_icon() - magazine = null - reload(AM, user) - return TRUE - else - to_chat(user, "You can't put this type of ammo in \the [src].") - return TRUE - if(istype(A, /obj/item/suppressor)) - var/obj/item/suppressor/S = A - if(can_suppress) - if(!suppressed) - if(!user.unEquip(A)) - return - to_chat(user, "You screw [S] onto [src].") - suppressed = A - S.oldsound = fire_sound - S.initial_w_class = w_class - fire_sound = 'sound/weapons/gunshots/gunshot_silenced.ogg' - w_class = WEIGHT_CLASS_NORMAL //so pistols do not fit in pockets when suppressed - A.loc = src - update_icon() - return - else - to_chat(user, "[src] already has a suppressor.") - return - else - to_chat(user, "You can't seem to figure out how to fit [S] on [src].") - return - else - return ..() - -/obj/item/gun/projectile/attack_hand(mob/user) - if(loc == user) - if(suppressed && can_unsuppress) - var/obj/item/suppressor/S = suppressed - if(user.l_hand != src && user.r_hand != src) - ..() - return - to_chat(user, "You unscrew [suppressed] from [src].") - user.put_in_hands(suppressed) - fire_sound = S.oldsound - w_class = S.initial_w_class - suppressed = 0 - update_icon() - return - ..() - -/obj/item/gun/projectile/attack_self(mob/living/user as mob) - var/obj/item/ammo_casing/AC = chambered //Find chambered round - if(magazine) - magazine.loc = get_turf(loc) - user.put_in_hands(magazine) - magazine.update_icon() - magazine = null - to_chat(user, "You pull the magazine out of \the [src]!") - playsound(src, magout_sound, 50, 1) - else if(chambered) - AC.loc = get_turf(src) - AC.SpinAnimation(10, 1) - chambered = null - to_chat(user, "You unload the round from \the [src]'s chamber.") - playsound(src, 'sound/weapons/gun_interactions/remove_bullet.ogg', 50, 1) - else - to_chat(user, "There's no magazine in \the [src].") - update_icon() - return - -/obj/item/gun/projectile/examine(mob/user) - . = ..() - . += "Has [get_ammo()] round\s remaining." - -/obj/item/gun/projectile/proc/get_ammo(countchambered = 1) - var/boolets = 0 //mature var names for mature people - if(chambered && countchambered) - boolets++ - if(magazine) - boolets += magazine.ammo_count() - return boolets - -/obj/item/gun/projectile/suicide_act(mob/user) - if(chambered && chambered.BB && !chambered.BB.nodamage) - user.visible_message("[user] is putting the barrel of the [name] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide.") - sleep(25) - if(user.l_hand == src || user.r_hand == src) - process_fire(user, user, 0, zone_override = "head") - user.visible_message("[user] blows [user.p_their()] brains out with the [name]!") - return BRUTELOSS - else - user.visible_message("[user] panics and starts choking to death!") - return OXYLOSS - else - user.visible_message("[user] is pretending to blow [user.p_their()] brains out with the [name]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, 'sound/weapons/empty.ogg', 50, 1, -1) - return OXYLOSS - -/obj/item/gun/projectile/proc/sawoff(mob/user) - if(sawn_state == SAWN_OFF) - to_chat(user, "\The [src] is already shortened!") - return - if(bayonet) - to_chat(user, "You cannot saw-off [src] with [bayonet] attached!") - return - user.changeNext_move(CLICK_CD_MELEE) - user.visible_message("[user] begins to shorten \the [src].", "You begin to shorten \the [src]...") - - //if there's any live ammo inside the gun, makes it go off - if(blow_up(user)) - user.visible_message("\The [src] goes off!", "\The [src] goes off in your face!") - return - - if(do_after(user, 30, target = src)) - if(sawn_state == SAWN_OFF) - return - user.visible_message("[user] shortens \the [src]!", "You shorten \the [src].") - name = "sawn-off [name]" - desc = sawn_desc - w_class = WEIGHT_CLASS_NORMAL - item_state = "gun"//phil235 is it different with different skin? - slot_flags &= ~SLOT_BACK //you can't sling it on your back - slot_flags |= SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally) - sawn_state = SAWN_OFF - update_icon() - return 1 - -// Sawing guns related proc -/obj/item/gun/projectile/proc/blow_up(mob/user) - . = 0 - for(var/obj/item/ammo_casing/AC in magazine.stored_ammo) - if(AC.BB) - process_fire(user, user,0) - . = 1 \ No newline at end of file +/obj/item/gun/projectile + desc = "Now comes in flavors like GUN. Uses 10mm ammo, for some reason" + name = "projectile gun" + icon_state = "pistol" + origin_tech = "combat=2;materials=2" + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=1000) + + var/mag_type = /obj/item/ammo_box/magazine/m10mm //Removes the need for max_ammo and caliber info + var/obj/item/ammo_box/magazine/magazine + var/can_tactical = FALSE //check to see if the gun can tactically reload + +/obj/item/gun/projectile/New() + ..() + if(!magazine) + magazine = new mag_type(src) + chamber_round() + update_icon() + return + +/obj/item/gun/projectile/update_icon() + ..() + if(current_skin) + icon_state = "[current_skin][suppressed ? "-suppressed" : ""][sawn_state ? "-sawn" : ""]" + else + icon_state = "[initial(icon_state)][suppressed ? "-suppressed" : ""][sawn_state ? "-sawn" : ""]" + if(bayonet && can_bayonet) + overlays += knife_overlay + +/obj/item/gun/projectile/process_chamber(eject_casing = 1, empty_chamber = 1) + var/obj/item/ammo_casing/AC = chambered //Find chambered round + if(isnull(AC) || !istype(AC)) + chamber_round() + return + if(eject_casing) + AC.loc = get_turf(src) //Eject casing onto ground. + AC.SpinAnimation(10, 1) //next gen special effects + playsound(src, chambered.drop_sound, 100, 1) + if(empty_chamber) + chambered = null + chamber_round() + return + +/obj/item/gun/projectile/proc/chamber_round() + if(chambered || !magazine) + return + else if(magazine.ammo_count()) + chambered = magazine.get_round() + chambered.loc = src + return + +/obj/item/gun/projectile/can_shoot() + if(!magazine || !magazine.ammo_count(0)) + return 0 + return 1 + +/obj/item/gun/projectile/proc/can_reload() + return !magazine + +/obj/item/gun/projectile/proc/reload(obj/item/ammo_box/magazine/AM, mob/user as mob) + user.remove_from_mob(AM) + magazine = AM + magazine.loc = src + playsound(src, magin_sound, 50, 1) + chamber_round() + AM.update_icon() + update_icon() + return + +/obj/item/gun/projectile/attackby(var/obj/item/A as obj, mob/user as mob, params) + if(istype(A, /obj/item/ammo_box/magazine)) + var/obj/item/ammo_box/magazine/AM = A + if(istype(AM, mag_type)) + if(can_reload()) + reload(AM, user) + to_chat(user, "You load a new magazine into \the [src].") + return TRUE + else if(!can_tactical) + to_chat(user, "There's already a magazine in \the [src].") + return TRUE + else + to_chat(user, "You perform a tactical reload on \the [src], replacing the magazine.") + magazine.loc = get_turf(loc) + magazine.update_icon() + magazine = null + reload(AM, user) + return TRUE + else + to_chat(user, "You can't put this type of ammo in \the [src].") + return TRUE + if(istype(A, /obj/item/suppressor)) + var/obj/item/suppressor/S = A + if(can_suppress) + if(!suppressed) + if(!user.unEquip(A)) + return + to_chat(user, "You screw [S] onto [src].") + suppressed = A + S.oldsound = fire_sound + S.initial_w_class = w_class + fire_sound = 'sound/weapons/gunshots/gunshot_silenced.ogg' + w_class = WEIGHT_CLASS_NORMAL //so pistols do not fit in pockets when suppressed + A.loc = src + update_icon() + return + else + to_chat(user, "[src] already has a suppressor.") + return + else + to_chat(user, "You can't seem to figure out how to fit [S] on [src].") + return + else + return ..() + +/obj/item/gun/projectile/attack_hand(mob/user) + if(loc == user) + if(suppressed && can_unsuppress) + var/obj/item/suppressor/S = suppressed + if(user.l_hand != src && user.r_hand != src) + ..() + return + to_chat(user, "You unscrew [suppressed] from [src].") + user.put_in_hands(suppressed) + fire_sound = S.oldsound + w_class = S.initial_w_class + suppressed = 0 + update_icon() + return + ..() + +/obj/item/gun/projectile/attack_self(mob/living/user as mob) + var/obj/item/ammo_casing/AC = chambered //Find chambered round + if(magazine) + magazine.loc = get_turf(loc) + user.put_in_hands(magazine) + magazine.update_icon() + magazine = null + to_chat(user, "You pull the magazine out of \the [src]!") + playsound(src, magout_sound, 50, 1) + else if(chambered) + AC.loc = get_turf(src) + AC.SpinAnimation(10, 1) + chambered = null + to_chat(user, "You unload the round from \the [src]'s chamber.") + playsound(src, 'sound/weapons/gun_interactions/remove_bullet.ogg', 50, 1) + else + to_chat(user, "There's no magazine in \the [src].") + update_icon() + return + +/obj/item/gun/projectile/examine(mob/user) + . = ..() + . += "Has [get_ammo()] round\s remaining." + +/obj/item/gun/projectile/proc/get_ammo(countchambered = 1) + var/boolets = 0 //mature var names for mature people + if(chambered && countchambered) + boolets++ + if(magazine) + boolets += magazine.ammo_count() + return boolets + +/obj/item/gun/projectile/suicide_act(mob/user) + if(chambered && chambered.BB && !chambered.BB.nodamage) + user.visible_message("[user] is putting the barrel of the [name] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide.") + sleep(25) + if(user.l_hand == src || user.r_hand == src) + process_fire(user, user, 0, zone_override = "head") + user.visible_message("[user] blows [user.p_their()] brains out with the [name]!") + return BRUTELOSS + else + user.visible_message("[user] panics and starts choking to death!") + return OXYLOSS + else + user.visible_message("[user] is pretending to blow [user.p_their()] brains out with the [name]! It looks like [user.p_theyre()] trying to commit suicide!
        ") + playsound(loc, 'sound/weapons/empty.ogg', 50, 1, -1) + return OXYLOSS + +/obj/item/gun/projectile/proc/sawoff(mob/user) + if(sawn_state == SAWN_OFF) + to_chat(user, "\The [src] is already shortened!") + return + if(bayonet) + to_chat(user, "You cannot saw-off [src] with [bayonet] attached!") + return + user.changeNext_move(CLICK_CD_MELEE) + user.visible_message("[user] begins to shorten \the [src].", "You begin to shorten \the [src]...") + + //if there's any live ammo inside the gun, makes it go off + if(blow_up(user)) + user.visible_message("\The [src] goes off!", "\The [src] goes off in your face!") + return + + if(do_after(user, 30, target = src)) + if(sawn_state == SAWN_OFF) + return + user.visible_message("[user] shortens \the [src]!", "You shorten \the [src].") + name = "sawn-off [name]" + desc = sawn_desc + w_class = WEIGHT_CLASS_NORMAL + item_state = "gun"//phil235 is it different with different skin? + slot_flags &= ~SLOT_BACK //you can't sling it on your back + slot_flags |= SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally) + sawn_state = SAWN_OFF + update_icon() + return 1 + +// Sawing guns related proc +/obj/item/gun/projectile/proc/blow_up(mob/user) + . = 0 + for(var/obj/item/ammo_casing/AC in magazine.stored_ammo) + if(AC.BB) + process_fire(user, user,0) + . = 1 diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm index 6601f75d52b..f2ce54bbd52 100644 --- a/code/modules/projectiles/guns/projectile/automatic.dm +++ b/code/modules/projectiles/guns/projectile/automatic.dm @@ -1,306 +1,306 @@ -/obj/item/gun/projectile/automatic - w_class = WEIGHT_CLASS_NORMAL - var/alarmed = 0 - var/select = 1 - can_tactical = TRUE - can_suppress = 1 - burst_size = 3 - fire_delay = 2 - actions_types = list(/datum/action/item_action/toggle_firemode) - -/obj/item/gun/projectile/automatic/isHandgun() - return 0 - -/obj/item/gun/projectile/automatic/update_icon() - ..() - overlays.Cut() - if(!select) - overlays += "[initial(icon_state)]semi" - if(select == 1) - overlays += "[initial(icon_state)]burst" - icon_state = "[initial(icon_state)][magazine ? "-[magazine.max_ammo]" : ""][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]" - if(bayonet && can_bayonet) - overlays += knife_overlay - -/obj/item/gun/projectile/automatic/attackby(var/obj/item/A as obj, mob/user as mob, params) - . = ..() - if(.) - if(alarmed) // Did the empty clip alarm go off already? - alarmed = 0 // Reset the alarm once a magazine is loaded - return - if(istype(A, /obj/item/ammo_box/magazine)) - var/obj/item/ammo_box/magazine/AM = A - if(istype(AM, mag_type)) - if(magazine) - to_chat(user, "You perform a tactical reload on \the [src], replacing the magazine.") - magazine.loc = get_turf(loc) - magazine.update_icon() - magazine = null - else - to_chat(user, "You insert the magazine into \the [src].") - if(alarmed) - alarmed = 0 - user.remove_from_mob(AM) - magazine = AM - magazine.loc = src - chamber_round() - A.update_icon() - update_icon() - return 1 - -/obj/item/gun/projectile/automatic/ui_action_click() - burst_select() - -/obj/item/gun/projectile/automatic/proc/burst_select() - var/mob/living/carbon/human/user = usr - select = !select - if(!select) - burst_size = 1 - fire_delay = 0 - to_chat(user, "You switch to semi-automatic.") - else - burst_size = initial(burst_size) - fire_delay = initial(fire_delay) - to_chat(user, "You switch to [burst_size] round burst.") - - playsound(user, 'sound/weapons/gun_interactions/selector.ogg', 100, 1) - update_icon() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/gun/projectile/automatic/can_shoot() - return get_ammo() - -/obj/item/gun/projectile/automatic/proc/empty_alarm() - if(!chambered && !get_ammo() && !alarmed) - playsound(loc, 'sound/weapons/smg_empty_alarm.ogg', 40, 1) - update_icon() - alarmed = 1 - -//Saber SMG// -/obj/item/gun/projectile/automatic/proto - name = "\improper Nanotrasen Saber SMG" - desc = "A prototype three-round burst 9mm submachine gun, designated 'SABR'. Has a threaded barrel for suppressors." - icon_state = "saber" - mag_type = /obj/item/ammo_box/magazine/smgm9mm - origin_tech = "combat=4;materials=2" - fire_sound = 'sound/weapons/gunshots/gunshot_pistol.ogg' - -//C-20r SMG// -/obj/item/gun/projectile/automatic/c20r - name = "\improper C-20r SMG" - desc = "A two-round burst .45 SMG, designated 'C-20r'. Has a 'Scarborough Arms - Per falcis, per pravitas' buttstamp." - icon_state = "c20r" - item_state = "c20r" - origin_tech = "combat=5;materials=2;syndicate=6" - mag_type = /obj/item/ammo_box/magazine/smgm45 - fire_sound = 'sound/weapons/gunshots/gunshot_smg.ogg' - fire_delay = 2 - burst_size = 2 - can_bayonet = TRUE - knife_x_offset = 26 - knife_y_offset = 12 - -/obj/item/gun/projectile/automatic/c20r/New() - ..() - update_icon() - -/obj/item/gun/projectile/automatic/c20r/afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) - ..() - empty_alarm() - -/obj/item/gun/projectile/automatic/c20r/update_icon() - ..() - icon_state = "c20r[magazine ? "-[Ceiling(get_ammo(0)/4)*4]" : ""][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]" - -//WT550// -/obj/item/gun/projectile/automatic/wt550 - name = "security auto rifle" - desc = "An outdated personal defense weapon utilized by law enforcement. The WT-550 Automatic Rifle fires 4.6x30mm rounds." - icon_state = "wt550" - item_state = "arg" - mag_type = /obj/item/ammo_box/magazine/wt550m9 - fire_sound = 'sound/weapons/gunshots/gunshot_rifle.ogg' - magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' - magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' - fire_delay = 2 - can_suppress = 0 - burst_size = 1 - actions_types = list() - can_bayonet = TRUE - knife_x_offset = 25 - knife_y_offset = 12 - -/obj/item/gun/projectile/automatic/wt550/update_icon() - ..() - icon_state = "wt550[magazine ? "-[Ceiling(get_ammo(0)/4)*4]" : ""]" - -//Type-U3 Uzi// -/obj/item/gun/projectile/automatic/mini_uzi - name = "\improper 'Type U3' Uzi" - desc = "A lightweight, burst-fire submachine gun, for when you really want someone dead. Uses 9mm rounds." - icon_state = "mini-uzi" - origin_tech = "combat=4;materials=2;syndicate=4" - mag_type = /obj/item/ammo_box/magazine/uzim9mm - fire_sound = 'sound/weapons/gunshots/gunshot_pistol.ogg' - burst_size = 2 - -//M-90gl Carbine// -/obj/item/gun/projectile/automatic/m90 - name = "\improper M-90gl Carbine" - desc = "A three-round burst 5.56 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher which can be toggled on and off." - icon_state = "m90" - item_state = "m90-4" - origin_tech = "combat=5;materials=2;syndicate=6" - mag_type = /obj/item/ammo_box/magazine/m556 - fire_sound = 'sound/weapons/gunshots/gunshot_rifle.ogg' - magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' - magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' - can_suppress = 0 - var/obj/item/gun/projectile/revolver/grenadelauncher/underbarrel - burst_size = 3 - fire_delay = 2 - -/obj/item/gun/projectile/automatic/m90/New() - ..() - underbarrel = new /obj/item/gun/projectile/revolver/grenadelauncher(src) - update_icon() - -/obj/item/gun/projectile/automatic/m90/afterattack(var/atom/target, var/mob/living/user, flag, params) - if(select == 2) - underbarrel.afterattack(target, user, flag, params) - else - ..() - return - -/obj/item/gun/projectile/automatic/m90/attackby(var/obj/item/A, mob/user, params) - if(istype(A, /obj/item/ammo_casing)) - if(istype(A, underbarrel.magazine.ammo_type)) - underbarrel.attack_self() - underbarrel.attackby(A, user, params) - else - return ..() - -/obj/item/gun/projectile/automatic/m90/update_icon() - ..() - overlays.Cut() - switch(select) - if(0) - overlays += "[initial(icon_state)]semi" - if(1) - overlays += "[initial(icon_state)]burst" - if(2) - overlays += "[initial(icon_state)]gren" - icon_state = "[initial(icon_state)][magazine ? "" : "-e"]" - if(magazine) - overlays += image(icon = icon, icon_state = "m90-[Ceiling(get_ammo(0)/6)*6]") - item_state = "m90-[Ceiling(get_ammo(0)/7.5)]" - else - item_state = "m90-0" - return - -/obj/item/gun/projectile/automatic/m90/burst_select() - var/mob/living/carbon/human/user = usr - switch(select) - if(0) - select = 1 - burst_size = initial(burst_size) - fire_delay = initial(fire_delay) - to_chat(user, "You switch to [burst_size] round burst.") - if(1) - select = 2 - to_chat(user, "You switch to grenades.") - if(2) - select = 0 - burst_size = 1 - fire_delay = 0 - to_chat(user, "You switch to semi-auto.") - playsound(user, 'sound/weapons/gun_interactions/selector.ogg', 100, 1) - update_icon() - -//Tommy Gun// -/obj/item/gun/projectile/automatic/tommygun - name = "\improper Thompson SMG" - desc = "A genuine 'Chicago Typewriter'." - icon_state = "tommygun" - item_state = "shotgun" - w_class = WEIGHT_CLASS_HUGE - slot_flags = 0 - origin_tech = "combat=5;materials=1;syndicate=3" - mag_type = /obj/item/ammo_box/magazine/tommygunm45 - fire_sound = 'sound/weapons/gunshots/gunshot_smg.ogg' - can_suppress = 0 - burst_size = 4 - fire_delay = 1 - -//ARG Assault Rifle// -/obj/item/gun/projectile/automatic/ar - name = "ARG" - desc = "A robust assault rile used by Nanotrasen fighting forces." - icon_state = "arg" - item_state = "arg" - slot_flags = 0 - origin_tech = "combat=6;engineering=4" - mag_type = /obj/item/ammo_box/magazine/m556 - fire_sound = 'sound/weapons/gunshots/gunshot_mg.ogg' - magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' - magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' - can_suppress = 0 - burst_size = 3 - fire_delay = 1 - -// Bulldog shotgun // -/obj/item/gun/projectile/automatic/shotgun/bulldog - name = "\improper 'Bulldog' Shotgun" - desc = "A compact, mag-fed semi-automatic shotgun for combat in narrow corridors, nicknamed 'Bulldog' by boarding parties. Compatible only with specialized 8-round drum magazines." - icon_state = "bulldog" - item_state = "bulldog" - w_class = WEIGHT_CLASS_NORMAL - origin_tech = "combat=6;materials=4;syndicate=6" - mag_type = /obj/item/ammo_box/magazine/m12g - fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' - magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' - magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' - can_suppress = 0 - burst_size = 1 - fire_delay = 0 - actions_types = list() - -/obj/item/gun/projectile/automatic/shotgun/bulldog/New() - ..() - update_icon() - -/obj/item/gun/projectile/automatic/shotgun/bulldog/proc/update_magazine() - if(magazine) - overlays.Cut() - overlays += "[magazine.icon_state]" - return - -/obj/item/gun/projectile/automatic/shotgun/bulldog/update_icon() - overlays.Cut() - update_magazine() - icon_state = "bulldog[chambered ? "" : "-e"]" - -/obj/item/gun/projectile/automatic/shotgun/bulldog/afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) - ..() - empty_alarm() - -//Laser carbine// -/obj/item/gun/projectile/automatic/lasercarbine - name = "\improper IK-60 Laser Carbine" - desc = "A short, compact carbine like rifle, relying more on battery cartridges rather than a built in power cell. Utilized by the Nanotrasen Navy for combat operations." - icon_state = "lasercarbine" - item_state = "laser" - w_class = WEIGHT_CLASS_NORMAL - origin_tech = "combat=4;materials=2" - mag_type = /obj/item/ammo_box/magazine/laser - fire_sound = 'sound/weapons/gunshots/gunshot_lascarbine.ogg' - magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' - magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' - can_suppress = 0 - burst_size = 2 - -/obj/item/gun/projectile/automatic/lasercarbine/update_icon() - ..() - icon_state = "lasercarbine[magazine ? "-[Ceiling(get_ammo(0)/5)*5]" : ""]" +/obj/item/gun/projectile/automatic + w_class = WEIGHT_CLASS_NORMAL + var/alarmed = 0 + var/select = 1 + can_tactical = TRUE + can_suppress = 1 + burst_size = 3 + fire_delay = 2 + actions_types = list(/datum/action/item_action/toggle_firemode) + +/obj/item/gun/projectile/automatic/isHandgun() + return 0 + +/obj/item/gun/projectile/automatic/update_icon() + ..() + overlays.Cut() + if(!select) + overlays += "[initial(icon_state)]semi" + if(select == 1) + overlays += "[initial(icon_state)]burst" + icon_state = "[initial(icon_state)][magazine ? "-[magazine.max_ammo]" : ""][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]" + if(bayonet && can_bayonet) + overlays += knife_overlay + +/obj/item/gun/projectile/automatic/attackby(var/obj/item/A as obj, mob/user as mob, params) + . = ..() + if(.) + if(alarmed) // Did the empty clip alarm go off already? + alarmed = 0 // Reset the alarm once a magazine is loaded + return + if(istype(A, /obj/item/ammo_box/magazine)) + var/obj/item/ammo_box/magazine/AM = A + if(istype(AM, mag_type)) + if(magazine) + to_chat(user, "You perform a tactical reload on \the [src], replacing the magazine.") + magazine.loc = get_turf(loc) + magazine.update_icon() + magazine = null + else + to_chat(user, "You insert the magazine into \the [src].") + if(alarmed) + alarmed = 0 + user.remove_from_mob(AM) + magazine = AM + magazine.loc = src + chamber_round() + A.update_icon() + update_icon() + return 1 + +/obj/item/gun/projectile/automatic/ui_action_click() + burst_select() + +/obj/item/gun/projectile/automatic/proc/burst_select() + var/mob/living/carbon/human/user = usr + select = !select + if(!select) + burst_size = 1 + fire_delay = 0 + to_chat(user, "You switch to semi-automatic.") + else + burst_size = initial(burst_size) + fire_delay = initial(fire_delay) + to_chat(user, "You switch to [burst_size] round burst.") + + playsound(user, 'sound/weapons/gun_interactions/selector.ogg', 100, 1) + update_icon() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/gun/projectile/automatic/can_shoot() + return get_ammo() + +/obj/item/gun/projectile/automatic/proc/empty_alarm() + if(!chambered && !get_ammo() && !alarmed) + playsound(loc, 'sound/weapons/smg_empty_alarm.ogg', 40, 1) + update_icon() + alarmed = 1 + +//Saber SMG// +/obj/item/gun/projectile/automatic/proto + name = "\improper Nanotrasen Saber SMG" + desc = "A prototype three-round burst 9mm submachine gun, designated 'SABR'. Has a threaded barrel for suppressors." + icon_state = "saber" + mag_type = /obj/item/ammo_box/magazine/smgm9mm + origin_tech = "combat=4;materials=2" + fire_sound = 'sound/weapons/gunshots/gunshot_pistol.ogg' + +//C-20r SMG// +/obj/item/gun/projectile/automatic/c20r + name = "\improper C-20r SMG" + desc = "A two-round burst .45 SMG, designated 'C-20r'. Has a 'Scarborough Arms - Per falcis, per pravitas' buttstamp." + icon_state = "c20r" + item_state = "c20r" + origin_tech = "combat=5;materials=2;syndicate=6" + mag_type = /obj/item/ammo_box/magazine/smgm45 + fire_sound = 'sound/weapons/gunshots/gunshot_smg.ogg' + fire_delay = 2 + burst_size = 2 + can_bayonet = TRUE + knife_x_offset = 26 + knife_y_offset = 12 + +/obj/item/gun/projectile/automatic/c20r/New() + ..() + update_icon() + +/obj/item/gun/projectile/automatic/c20r/afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) + ..() + empty_alarm() + +/obj/item/gun/projectile/automatic/c20r/update_icon() + ..() + icon_state = "c20r[magazine ? "-[Ceiling(get_ammo(0)/4)*4]" : ""][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]" + +//WT550// +/obj/item/gun/projectile/automatic/wt550 + name = "security auto rifle" + desc = "An outdated personal defense weapon utilized by law enforcement. The WT-550 Automatic Rifle fires 4.6x30mm rounds." + icon_state = "wt550" + item_state = "arg" + mag_type = /obj/item/ammo_box/magazine/wt550m9 + fire_sound = 'sound/weapons/gunshots/gunshot_rifle.ogg' + magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' + magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' + fire_delay = 2 + can_suppress = 0 + burst_size = 1 + actions_types = list() + can_bayonet = TRUE + knife_x_offset = 25 + knife_y_offset = 12 + +/obj/item/gun/projectile/automatic/wt550/update_icon() + ..() + icon_state = "wt550[magazine ? "-[Ceiling(get_ammo(0)/4)*4]" : ""]" + +//Type-U3 Uzi// +/obj/item/gun/projectile/automatic/mini_uzi + name = "\improper 'Type U3' Uzi" + desc = "A lightweight, burst-fire submachine gun, for when you really want someone dead. Uses 9mm rounds." + icon_state = "mini-uzi" + origin_tech = "combat=4;materials=2;syndicate=4" + mag_type = /obj/item/ammo_box/magazine/uzim9mm + fire_sound = 'sound/weapons/gunshots/gunshot_pistol.ogg' + burst_size = 2 + +//M-90gl Carbine// +/obj/item/gun/projectile/automatic/m90 + name = "\improper M-90gl Carbine" + desc = "A three-round burst 5.56 toploading carbine, designated 'M-90gl'. Has an attached underbarrel grenade launcher which can be toggled on and off." + icon_state = "m90" + item_state = "m90-4" + origin_tech = "combat=5;materials=2;syndicate=6" + mag_type = /obj/item/ammo_box/magazine/m556 + fire_sound = 'sound/weapons/gunshots/gunshot_rifle.ogg' + magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' + magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' + can_suppress = 0 + var/obj/item/gun/projectile/revolver/grenadelauncher/underbarrel + burst_size = 3 + fire_delay = 2 + +/obj/item/gun/projectile/automatic/m90/New() + ..() + underbarrel = new /obj/item/gun/projectile/revolver/grenadelauncher(src) + update_icon() + +/obj/item/gun/projectile/automatic/m90/afterattack(var/atom/target, var/mob/living/user, flag, params) + if(select == 2) + underbarrel.afterattack(target, user, flag, params) + else + ..() + return + +/obj/item/gun/projectile/automatic/m90/attackby(var/obj/item/A, mob/user, params) + if(istype(A, /obj/item/ammo_casing)) + if(istype(A, underbarrel.magazine.ammo_type)) + underbarrel.attack_self() + underbarrel.attackby(A, user, params) + else + return ..() + +/obj/item/gun/projectile/automatic/m90/update_icon() + ..() + overlays.Cut() + switch(select) + if(0) + overlays += "[initial(icon_state)]semi" + if(1) + overlays += "[initial(icon_state)]burst" + if(2) + overlays += "[initial(icon_state)]gren" + icon_state = "[initial(icon_state)][magazine ? "" : "-e"]" + if(magazine) + overlays += image(icon = icon, icon_state = "m90-[Ceiling(get_ammo(0)/6)*6]") + item_state = "m90-[Ceiling(get_ammo(0)/7.5)]" + else + item_state = "m90-0" + return + +/obj/item/gun/projectile/automatic/m90/burst_select() + var/mob/living/carbon/human/user = usr + switch(select) + if(0) + select = 1 + burst_size = initial(burst_size) + fire_delay = initial(fire_delay) + to_chat(user, "You switch to [burst_size] round burst.") + if(1) + select = 2 + to_chat(user, "You switch to grenades.") + if(2) + select = 0 + burst_size = 1 + fire_delay = 0 + to_chat(user, "You switch to semi-auto.") + playsound(user, 'sound/weapons/gun_interactions/selector.ogg', 100, 1) + update_icon() + +//Tommy Gun// +/obj/item/gun/projectile/automatic/tommygun + name = "\improper Thompson SMG" + desc = "A genuine 'Chicago Typewriter'." + icon_state = "tommygun" + item_state = "shotgun" + w_class = WEIGHT_CLASS_HUGE + slot_flags = 0 + origin_tech = "combat=5;materials=1;syndicate=3" + mag_type = /obj/item/ammo_box/magazine/tommygunm45 + fire_sound = 'sound/weapons/gunshots/gunshot_smg.ogg' + can_suppress = 0 + burst_size = 4 + fire_delay = 1 + +//ARG Assault Rifle// +/obj/item/gun/projectile/automatic/ar + name = "ARG" + desc = "A robust assault rile used by Nanotrasen fighting forces." + icon_state = "arg" + item_state = "arg" + slot_flags = 0 + origin_tech = "combat=6;engineering=4" + mag_type = /obj/item/ammo_box/magazine/m556 + fire_sound = 'sound/weapons/gunshots/gunshot_mg.ogg' + magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' + magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' + can_suppress = 0 + burst_size = 3 + fire_delay = 1 + +// Bulldog shotgun // +/obj/item/gun/projectile/automatic/shotgun/bulldog + name = "\improper 'Bulldog' Shotgun" + desc = "A compact, mag-fed semi-automatic shotgun for combat in narrow corridors, nicknamed 'Bulldog' by boarding parties. Compatible only with specialized 8-round drum magazines." + icon_state = "bulldog" + item_state = "bulldog" + w_class = WEIGHT_CLASS_NORMAL + origin_tech = "combat=6;materials=4;syndicate=6" + mag_type = /obj/item/ammo_box/magazine/m12g + fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' + magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' + magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' + can_suppress = 0 + burst_size = 1 + fire_delay = 0 + actions_types = list() + +/obj/item/gun/projectile/automatic/shotgun/bulldog/New() + ..() + update_icon() + +/obj/item/gun/projectile/automatic/shotgun/bulldog/proc/update_magazine() + if(magazine) + overlays.Cut() + overlays += "[magazine.icon_state]" + return + +/obj/item/gun/projectile/automatic/shotgun/bulldog/update_icon() + overlays.Cut() + update_magazine() + icon_state = "bulldog[chambered ? "" : "-e"]" + +/obj/item/gun/projectile/automatic/shotgun/bulldog/afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) + ..() + empty_alarm() + +//Laser carbine// +/obj/item/gun/projectile/automatic/lasercarbine + name = "\improper IK-60 Laser Carbine" + desc = "A short, compact carbine like rifle, relying more on battery cartridges rather than a built in power cell. Utilized by the Nanotrasen Navy for combat operations." + icon_state = "lasercarbine" + item_state = "laser" + w_class = WEIGHT_CLASS_NORMAL + origin_tech = "combat=4;materials=2" + mag_type = /obj/item/ammo_box/magazine/laser + fire_sound = 'sound/weapons/gunshots/gunshot_lascarbine.ogg' + magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg' + magout_sound = 'sound/weapons/gun_interactions/batrifle_magout.ogg' + can_suppress = 0 + burst_size = 2 + +/obj/item/gun/projectile/automatic/lasercarbine/update_icon() + ..() + icon_state = "lasercarbine[magazine ? "-[Ceiling(get_ammo(0)/5)*5]" : ""]" diff --git a/code/modules/projectiles/guns/projectile/pistol.dm b/code/modules/projectiles/guns/projectile/pistol.dm index 64d0c2d7a67..1314410cd36 100644 --- a/code/modules/projectiles/guns/projectile/pistol.dm +++ b/code/modules/projectiles/guns/projectile/pistol.dm @@ -1,118 +1,118 @@ -//Stetchkin// -/obj/item/gun/projectile/automatic/pistol - name = "stechkin pistol" - desc = "A small, easily concealable 10mm handgun. Has a threaded barrel for suppressors." - icon_state = "pistol" - w_class = WEIGHT_CLASS_SMALL - origin_tech = "combat=3;materials=2;syndicate=4" - mag_type = /obj/item/ammo_box/magazine/m10mm - fire_sound = 'sound/weapons/gunshots/gunshot_pistol.ogg' - magin_sound = 'sound/weapons/gun_interactions/pistol_magin.ogg' - magout_sound = 'sound/weapons/gun_interactions/pistol_magout.ogg' - can_suppress = 1 - burst_size = 1 - fire_delay = 0 - actions_types = list() - -/obj/item/gun/projectile/automatic/pistol/isHandgun() - return 1 - -/obj/item/gun/projectile/automatic/pistol/update_icon() - ..() - icon_state = "[initial(icon_state)][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]" - return - -//M1911// -/obj/item/gun/projectile/automatic/pistol/m1911 - name = "\improper M1911" - desc = "A classic .45 handgun with a small magazine capacity." - icon_state = "m1911" - w_class = WEIGHT_CLASS_NORMAL - mag_type = /obj/item/ammo_box/magazine/m45 - can_suppress = 0 - -//Enforcer// -/obj/item/gun/projectile/automatic/pistol/enforcer - name = "Enforcer" - desc = "A pistol of modern design." - icon_state = "enforcer_grey" - force = 10 - mag_type = /obj/item/ammo_box/magazine/enforcer - can_suppress = TRUE - unique_reskin = TRUE - can_flashlight = TRUE - -/obj/item/gun/projectile/automatic/pistol/enforcer/New() - ..() - options["Grey slide"] = "enforcer_grey" - options["Red slide"] = "enforcer_red" - options["Green slide"] = "enforcer_green" - options["Tan slide"] = "enforcer_tan" - options["Black slide"] = "enforcer_black" - options["Green Handle"] = "enforcer_greengrip" - options["Tan Handle"] = "enforcer_tangrip" - options["Red Handle"] = "enforcer_redgrip" - options["Cancel"] = null - -/obj/item/gun/projectile/automatic/pistol/enforcer/update_icon() - ..() - if(current_skin) - icon_state = "[current_skin][chambered ? "" : "-e"]" - else - icon_state = "[initial(icon_state)][chambered ? "" : "-e"]" - overlays.Cut() - if(suppressed) - overlays += image(icon = icon, icon_state = "enforcer_supp", pixel_x = 4) - if(gun_light) - var/iconF = "Enforcer_light" - if(gun_light.on) - iconF = "Enforcer_light-on" - overlays += image(icon = icon, icon_state = iconF, pixel_x = 0) - -/obj/item/gun/projectile/automatic/pistol/enforcer/ui_action_click() - toggle_gunlight() - -/obj/item/gun/projectile/automatic/pistol/enforcer/lethal - -/obj/item/gun/projectile/automatic/pistol/enforcer/lethal/New() - magazine = new/obj/item/ammo_box/magazine/enforcer/lethal - ..() - -//Desert Eagle// -/obj/item/gun/projectile/automatic/pistol/deagle - name = "desert eagle" - desc = "A robust .50 AE handgun." - icon_state = "deagle" - force = 14.0 - mag_type = /obj/item/ammo_box/magazine/m50 - fire_sound = 'sound/weapons/gunshots/gunshot_pistolH.ogg' - magin_sound = 'sound/weapons/gun_interactions/hpistol_magin.ogg' - magout_sound = 'sound/weapons/gun_interactions/hpistol_magout.ogg' - can_suppress = 0 - -/obj/item/gun/projectile/automatic/pistol/deagle/update_icon() - ..() - icon_state = "[initial(icon_state)][magazine ? "" : "-e"]" - -/obj/item/gun/projectile/automatic/pistol/deagle/gold - desc = "A gold plated desert eagle folded over a million times by superior martian gunsmiths. Uses .50 AE ammo." - icon_state = "deagleg" - item_state = "deagleg" - -/obj/item/gun/projectile/automatic/pistol/deagle/camo - desc = "A Deagle brand Deagle for operators operating operationally. Uses .50 AE ammo." - icon_state = "deaglecamo" - item_state = "deagleg" - -//APS Pistol// -/obj/item/gun/projectile/automatic/pistol/APS - name = "stechkin APS pistol" - desc = "The original russian version of a widely used Syndicate sidearm. Uses 9mm ammo." - icon_state = "aps" - w_class = WEIGHT_CLASS_NORMAL - origin_tech = "combat=3;materials=2;syndicate=3" - mag_type = /obj/item/ammo_box/magazine/pistolm9mm - can_suppress = 0 - burst_size = 3 - fire_delay = 2 - actions_types = list(/datum/action/item_action/toggle_firemode) +//Stetchkin// +/obj/item/gun/projectile/automatic/pistol + name = "stechkin pistol" + desc = "A small, easily concealable 10mm handgun. Has a threaded barrel for suppressors." + icon_state = "pistol" + w_class = WEIGHT_CLASS_SMALL + origin_tech = "combat=3;materials=2;syndicate=4" + mag_type = /obj/item/ammo_box/magazine/m10mm + fire_sound = 'sound/weapons/gunshots/gunshot_pistol.ogg' + magin_sound = 'sound/weapons/gun_interactions/pistol_magin.ogg' + magout_sound = 'sound/weapons/gun_interactions/pistol_magout.ogg' + can_suppress = 1 + burst_size = 1 + fire_delay = 0 + actions_types = list() + +/obj/item/gun/projectile/automatic/pistol/isHandgun() + return 1 + +/obj/item/gun/projectile/automatic/pistol/update_icon() + ..() + icon_state = "[initial(icon_state)][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]" + return + +//M1911// +/obj/item/gun/projectile/automatic/pistol/m1911 + name = "\improper M1911" + desc = "A classic .45 handgun with a small magazine capacity." + icon_state = "m1911" + w_class = WEIGHT_CLASS_NORMAL + mag_type = /obj/item/ammo_box/magazine/m45 + can_suppress = 0 + +//Enforcer// +/obj/item/gun/projectile/automatic/pistol/enforcer + name = "Enforcer" + desc = "A pistol of modern design." + icon_state = "enforcer_grey" + force = 10 + mag_type = /obj/item/ammo_box/magazine/enforcer + can_suppress = TRUE + unique_reskin = TRUE + can_flashlight = TRUE + +/obj/item/gun/projectile/automatic/pistol/enforcer/New() + ..() + options["Grey slide"] = "enforcer_grey" + options["Red slide"] = "enforcer_red" + options["Green slide"] = "enforcer_green" + options["Tan slide"] = "enforcer_tan" + options["Black slide"] = "enforcer_black" + options["Green Handle"] = "enforcer_greengrip" + options["Tan Handle"] = "enforcer_tangrip" + options["Red Handle"] = "enforcer_redgrip" + options["Cancel"] = null + +/obj/item/gun/projectile/automatic/pistol/enforcer/update_icon() + ..() + if(current_skin) + icon_state = "[current_skin][chambered ? "" : "-e"]" + else + icon_state = "[initial(icon_state)][chambered ? "" : "-e"]" + overlays.Cut() + if(suppressed) + overlays += image(icon = icon, icon_state = "enforcer_supp", pixel_x = 4) + if(gun_light) + var/iconF = "Enforcer_light" + if(gun_light.on) + iconF = "Enforcer_light-on" + overlays += image(icon = icon, icon_state = iconF, pixel_x = 0) + +/obj/item/gun/projectile/automatic/pistol/enforcer/ui_action_click() + toggle_gunlight() + +/obj/item/gun/projectile/automatic/pistol/enforcer/lethal + +/obj/item/gun/projectile/automatic/pistol/enforcer/lethal/New() + magazine = new/obj/item/ammo_box/magazine/enforcer/lethal + ..() + +//Desert Eagle// +/obj/item/gun/projectile/automatic/pistol/deagle + name = "desert eagle" + desc = "A robust .50 AE handgun." + icon_state = "deagle" + force = 14.0 + mag_type = /obj/item/ammo_box/magazine/m50 + fire_sound = 'sound/weapons/gunshots/gunshot_pistolH.ogg' + magin_sound = 'sound/weapons/gun_interactions/hpistol_magin.ogg' + magout_sound = 'sound/weapons/gun_interactions/hpistol_magout.ogg' + can_suppress = 0 + +/obj/item/gun/projectile/automatic/pistol/deagle/update_icon() + ..() + icon_state = "[initial(icon_state)][magazine ? "" : "-e"]" + +/obj/item/gun/projectile/automatic/pistol/deagle/gold + desc = "A gold plated desert eagle folded over a million times by superior martian gunsmiths. Uses .50 AE ammo." + icon_state = "deagleg" + item_state = "deagleg" + +/obj/item/gun/projectile/automatic/pistol/deagle/camo + desc = "A Deagle brand Deagle for operators operating operationally. Uses .50 AE ammo." + icon_state = "deaglecamo" + item_state = "deagleg" + +//APS Pistol// +/obj/item/gun/projectile/automatic/pistol/APS + name = "stechkin APS pistol" + desc = "The original russian version of a widely used Syndicate sidearm. Uses 9mm ammo." + icon_state = "aps" + w_class = WEIGHT_CLASS_NORMAL + origin_tech = "combat=3;materials=2;syndicate=3" + mag_type = /obj/item/ammo_box/magazine/pistolm9mm + can_suppress = 0 + burst_size = 3 + fire_delay = 2 + actions_types = list(/datum/action/item_action/toggle_firemode) diff --git a/code/modules/projectiles/guns/projectile/revolver.dm b/code/modules/projectiles/guns/projectile/revolver.dm index b29544c5755..642e0fabc3d 100644 --- a/code/modules/projectiles/guns/projectile/revolver.dm +++ b/code/modules/projectiles/guns/projectile/revolver.dm @@ -1,475 +1,475 @@ -/obj/item/gun/projectile/revolver - name = "\improper .357 revolver" - desc = "A suspicious revolver. Uses .357 ammo." - icon_state = "revolver" - mag_type = /obj/item/ammo_box/magazine/internal/cylinder - origin_tech = "combat=3;materials=2" - fire_sound = 'sound/weapons/gunshots/gunshot_strong.ogg' - -/obj/item/gun/projectile/revolver/New() - ..() - if(!istype(magazine, /obj/item/ammo_box/magazine/internal/cylinder)) - verbs -= /obj/item/gun/projectile/revolver/verb/spin - -/obj/item/gun/projectile/revolver/chamber_round(var/spin = 1) - if(spin) - chambered = magazine.get_round(1) - else - chambered = magazine.stored_ammo[1] - return - -/obj/item/gun/projectile/revolver/shoot_with_empty_chamber(mob/living/user as mob|obj) - ..() - chamber_round(1) - -/obj/item/gun/projectile/revolver/process_chamber() - return ..(0, 1) - -/obj/item/gun/projectile/revolver/attackby(obj/item/A, mob/user, params) - . = ..() - if(.) - return - var/num_loaded = magazine.attackby(A, user, params, 1) - if(num_loaded) - to_chat(user, "You load [num_loaded] shell\s into \the [src].") - A.update_icon() - update_icon() - chamber_round(0) - -/obj/item/gun/projectile/revolver/attack_self(mob/living/user) - var/num_unloaded = 0 - chambered = null - while(get_ammo() > 0) - var/obj/item/ammo_casing/CB - CB = magazine.get_round(0) - if(CB) - CB.loc = get_turf(loc) - CB.SpinAnimation(10, 1) - CB.update_icon() - playsound(get_turf(CB), "casingdrop", 60, 1) - num_unloaded++ - if(num_unloaded) - to_chat(user, "You unload [num_unloaded] shell\s from [src].") - else - to_chat(user, "[src] is empty!") - -/obj/item/gun/projectile/revolver/verb/spin() - set name = "Spin Chamber" - set category = "Object" - set desc = "Click to spin your revolver's chamber." - - var/mob/M = usr - - if(M.stat || !in_range(M,src)) - return - - if(istype(magazine, /obj/item/ammo_box/magazine/internal/cylinder)) - var/obj/item/ammo_box/magazine/internal/cylinder/C = magazine - C.spin() - chamber_round(0) - playsound(loc, 'sound/weapons/revolver_spin.ogg', 50, 1) - usr.visible_message("[usr] spins [src]'s chamber.", "You spin [src]'s chamber.") - else - verbs -= /obj/item/gun/projectile/revolver/verb/spin - -/obj/item/gun/projectile/revolver/can_shoot() - return get_ammo(0,0) - -/obj/item/gun/projectile/revolver/get_ammo(countchambered = 0, countempties = 1) - var/boolets = 0 //mature var names for mature people - if(chambered && countchambered) - boolets++ - if(magazine) - boolets += magazine.ammo_count(countempties) - return boolets - -/obj/item/gun/projectile/revolver/examine(mob/user) - . = ..() - . += "[get_ammo(0,0)] of those are live rounds." - -/obj/item/gun/projectile/revolver/detective - desc = "A cheap Martian knock-off of a classic law enforcement firearm. Uses .38-special rounds." - name = "\improper .38 Mars Special" - icon_state = "detective" - mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rev38 - unique_rename = 1 - unique_reskin = 1 - -/obj/item/gun/projectile/revolver/detective/New() - ..() - options["The Original"] = "detective" - options["Leopard Spots"] = "detective_leopard" - options["Black Panther"] = "detective_panther" - options["Gold Trim"] = "detective_gold" - options["The Peacemaker"] = "detective_peacemaker" - options["Cancel"] = null - -/obj/item/gun/projectile/revolver/detective/process_fire(atom/target as mob|obj|turf, mob/living/user as mob|obj, message = 1, params, zone_override = "") - if(magazine.caliber != initial(magazine.caliber)) - if(prob(70 - (magazine.ammo_count() * 10))) //minimum probability of 10, maximum of 60 - playsound(user, fire_sound, 50, 1) - to_chat(user, "[src] blows up in your face!") - user.take_organ_damage(0,20) - user.unEquip(src) - return 0 - ..() - -/obj/item/gun/projectile/revolver/detective/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(magazine.caliber == "38") - to_chat(user, "You begin to reinforce the barrel of [src]...") - if(magazine.ammo_count()) - afterattack(user, user) //you know the drill - user.visible_message("[src] goes off!", "[src] goes off in your face!") - return - if(!I.use_tool(src, user, 30, volume = I.tool_volume)) - return - if(magazine.ammo_count()) - to_chat(user, "You can't modify it!") - return - magazine.caliber = "357" - desc = "The barrel and chamber assembly seems to have been modified." - to_chat(user, "You reinforce the barrel of [src]. Now it will fire .357 rounds.") - else - to_chat(user, "You begin to revert the modifications to [src]...") - if(magazine.ammo_count()) - afterattack(user, user) //and again - user.visible_message("[src] goes off!", "[src] goes off in your face!") - return - if(!I.use_tool(src, user, 30, volume = I.tool_volume)) - return - if(magazine.ammo_count()) - to_chat(user, "You can't modify it!") - return - magazine.caliber = "38" - desc = initial(desc) - to_chat(user, "You remove the modifications on [src]. Now it will fire .38 rounds.") - -/obj/item/gun/projectile/revolver/fingergun //Summoned by the Finger Gun spell, from advanced mimery traitor item - name = "\improper finger gun" - desc = "Bang bang bang!" - icon_state = "fingergun" - mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rev38/invisible - origin_tech = "" - flags = ABSTRACT | NODROP | DROPDEL - slot_flags = null - fire_sound = null - fire_sound_text = null - lefthand_file = null - righthand_file = null - clumsy_check = 0 //Stole your uplink! Honk! - needs_permit = 0 //go away beepsky - -/obj/item/gun/projectile/revolver/fingergun/fake - desc = "Pew pew pew!" - mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rev38/invisible/fake - -/obj/item/gun/projectile/revolver/fingergun/New() - ..() - verbs -= /obj/item/gun/projectile/revolver/verb/spin - -/obj/item/gun/projectile/revolver/fingergun/shoot_with_empty_chamber(/*mob/living/user as mob|obj*/) - to_chat(usr, "You are out of ammo! You holster your fingers.") - qdel(src) - return - -/obj/item/gun/projectile/revolver/fingergun/afterattack(atom/target, mob/living/user, flag, params) - if(!user.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first. Use your fingers if you wish to holster them.") - return - ..() - -/obj/item/gun/projectile/revolver/fingergun/attackby(obj/item/A, mob/user, params) - return - -/obj/item/gun/projectile/revolver/fingergun/attack_self(mob/living/user) - to_chat(usr, "You holster your fingers. Another time.") - qdel(src) - return - -/obj/item/gun/projectile/revolver/mateba - name = "\improper Unica 6 auto-revolver" - desc = "A retro high-powered autorevolver typically used by officers of the New Russia military. Uses .357 ammo." //>10mm hole >.357 - icon_state = "mateba" - -/obj/item/gun/projectile/revolver/golden - name = "\improper Golden revolver" - desc = "This ain't no game, ain't never been no show, And I'll gladly gun down the oldest lady you know. Uses .357 ammo." - icon_state = "goldrevolver" - fire_sound = 'sound/weapons/resonator_blast.ogg' - recoil = 8 - -/obj/item/gun/projectile/revolver/nagant - name = "nagant revolver" - desc = "An old model of revolver that originated in Russia. Able to be suppressed. Uses 7.62x38mmR ammo." - icon_state = "nagant" - origin_tech = "combat=3" - can_suppress = 1 - mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rev762 - -// A gun to play Russian Roulette! -// You can spin the chamber to randomize the position of the bullet. - -/obj/item/gun/projectile/revolver/russian - name = "\improper Russian Revolver" - desc = "A Russian-made revolver for drinking games. Uses .357 ammo, and has a mechanism that spins the chamber before each trigger pull." - origin_tech = "combat=2;materials=2" - mag_type = /obj/item/ammo_box/magazine/internal/rus357 - var/spun = 0 - - -/obj/item/gun/projectile/revolver/russian/New() - ..() - Spin() - update_icon() - -/obj/item/gun/projectile/revolver/russian/proc/Spin() - chambered = null - var/random = rand(1, magazine.max_ammo) - if(random <= get_ammo(0,0)) - chamber_round() - spun = 1 - -/obj/item/gun/projectile/revolver/russian/attackby(obj/item/A, mob/user, params) - var/num_loaded = ..() - if(num_loaded) - user.visible_message("[user] loads a single bullet into the revolver and spins the chamber.", "You load a single bullet into the chamber and spin it.") - else - user.visible_message("[user] spins the chamber of the revolver.", "You spin the revolver's chamber.") - if(get_ammo() > 0) - Spin() - update_icon() - A.update_icon() - return - -/obj/item/gun/projectile/revolver/russian/attack_self(mob/user) - if(!spun && can_shoot()) - user.visible_message("[user] spins the chamber of the revolver.", "You spin the revolver's chamber.") - Spin() - else - var/num_unloaded = 0 - while(get_ammo() > 0) - var/obj/item/ammo_casing/CB - CB = magazine.get_round() - chambered = null - CB.loc = get_turf(loc) - CB.update_icon() - playsound(get_turf(CB), "casingdrop", 60, 1) - num_unloaded++ - if(num_unloaded) - to_chat(user, "You unload [num_unloaded] shell\s from [src].") - else - to_chat(user, "[src] is empty.") - -/obj/item/gun/projectile/revolver/russian/afterattack(atom/target as mob|obj|turf, mob/living/user as mob|obj, flag, params) - if(flag) - if(!(target in user.contents) && ismob(target)) - if(user.a_intent == INTENT_HARM) // Flogging action - return - - if(isliving(user)) - if(!can_trigger_gun(user)) - return - if(target != user) - if(ismob(target)) - to_chat(user, "A mechanism prevents you from shooting anyone but yourself!") - return - - if(ishuman(user)) - if(!spun) - to_chat(user, "You need to spin the revolver's chamber first!") - return - - spun = 0 - - if(chambered) - var/obj/item/ammo_casing/AC = chambered - if(AC.fire(user, user)) - playsound(user, fire_sound, 50, 1) - var/zone = check_zone(user.zone_selected) - if(zone == "head" || zone == "eyes" || zone == "mouth") - shoot_self(user, zone) - else - user.visible_message("[user.name] cowardly fires [src] at [user.p_their()] [zone]!", "You cowardly fire [src] at your [zone]!", "You hear a gunshot!") - return - - user.visible_message("*click*") - playsound(user, 'sound/weapons/empty.ogg', 100, 1) - -/obj/item/gun/projectile/revolver/russian/proc/shoot_self(mob/living/carbon/human/user, affecting = "head") - user.apply_damage(300, BRUTE, affecting) - user.visible_message("[user.name] fires [src] at [user.p_their()] head!", "You fire [src] at your head!", "You hear a gunshot!") - -/obj/item/gun/projectile/revolver/russian/soul - name = "cursed Russian revolver" - desc = "To play with this revolver requires wagering your very soul." - -/obj/item/gun/projectile/revolver/russian/soul/shoot_self(mob/living/user) - ..() - var/obj/item/soulstone/anybody/SS = new /obj/item/soulstone/anybody(get_turf(src)) - if(!SS.transfer_soul("FORCE", user)) //Something went wrong - qdel(SS) - return - user.visible_message("[user.name]'s soul is captured by \the [src]!", "You've lost the gamble! Your soul is forfeit!") - -/obj/item/gun/projectile/revolver/capgun - name = "cap gun" - desc = "Looks almost like the real thing! Ages 8 and up." - origin_tech = null - mag_type = /obj/item/ammo_box/magazine/internal/cylinder/cap - -///////////////////////////// -// DOUBLE BARRELED SHOTGUN // -///////////////////////////// - -/obj/item/gun/projectile/revolver/doublebarrel - name = "double-barreled shotgun" - desc = "A true classic." - icon_state = "dshotgun" - item_state = "shotgun" - w_class = WEIGHT_CLASS_BULKY - force = 10 - flags = CONDUCT - slot_flags = SLOT_BACK - mag_type = /obj/item/ammo_box/magazine/internal/shot/dual - fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' - sawn_desc = "Omar's coming!" - unique_rename = 1 - unique_reskin = 1 - -/obj/item/gun/projectile/revolver/doublebarrel/New() - ..() - options["Default"] = "dshotgun" - options["Dark Red Finish"] = "dshotgun-d" - options["Ash"] = "dshotgun-f" - options["Faded Grey"] = "dshotgun-g" - options["Maple"] = "dshotgun-l" - options["Rosewood"] = "dshotgun-p" - options["Cancel"] = null - -/obj/item/gun/projectile/revolver/doublebarrel/attackby(obj/item/A, mob/user, params) - if(istype(A, /obj/item/ammo_box) || istype(A, /obj/item/ammo_casing)) - chamber_round() - if(istype(A, /obj/item/melee/energy)) - var/obj/item/melee/energy/W = A - if(W.active) - sawoff(user) - if(istype(A, /obj/item/circular_saw) || istype(A, /obj/item/gun/energy/plasmacutter)) - sawoff(user) - else - return ..() - -/obj/item/gun/projectile/revolver/doublebarrel/attack_self(mob/living/user) - var/num_unloaded = 0 - while(get_ammo() > 0) - var/obj/item/ammo_casing/CB - CB = magazine.get_round(0) - chambered = null - CB.loc = get_turf(loc) - CB.SpinAnimation(10, 1) - CB.update_icon() - playsound(get_turf(CB), 'sound/weapons/gun_interactions/shotgun_fall.ogg', 70, 1) - num_unloaded++ - if(num_unloaded) - to_chat(user, "You break open \the [src] and unload [num_unloaded] shell\s.") - else - to_chat(user, "[src] is empty.") - -/obj/item/gun/projectile/revolver/doublebarrel/isHandgun() //contrary to popular opinion, double barrels are not, shockingly, handguns - return 0 - -// IMPROVISED SHOTGUN // - -/obj/item/gun/projectile/revolver/doublebarrel/improvised - name = "improvised shotgun" - desc = "Essentially a tube that aims shotgun shells." - icon_state = "ishotgun" - item_state = "shotgun" - w_class = WEIGHT_CLASS_BULKY - force = 10 - slot_flags = null - mag_type = /obj/item/ammo_box/magazine/internal/shot/improvised - fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' - sawn_desc = "I'm just here for the gasoline." - unique_rename = 0 - unique_reskin = 0 - var/slung = 0 - -/obj/item/gun/projectile/revolver/doublebarrel/improvised/attackby(obj/item/A, mob/user, params) - if(istype(A, /obj/item/stack/cable_coil) && !sawn_state) - var/obj/item/stack/cable_coil/C = A - if(C.use(10)) - slot_flags = SLOT_BACK - icon_state = "ishotgunsling" - to_chat(user, "You tie the lengths of cable to the shotgun, making a sling.") - slung = 1 - update_icon() - else - to_chat(user, "You need at least ten lengths of cable if you want to make a sling.") - return - else - return ..() - -/obj/item/gun/projectile/revolver/doublebarrel/improvised/update_icon() - ..() - if(slung && (slot_flags & SLOT_BELT) ) - slung = 0 - icon_state = "ishotgun-sawn" - -/obj/item/gun/projectile/revolver/doublebarrel/improvised/sawoff(mob/user) - . = ..() - if(. && slung) //sawing off the gun removes the sling - new /obj/item/stack/cable_coil(get_turf(src), 10) - slung = 0 - update_icon() - -//caneshotgun - -/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane - name = "cane" - desc = "A cane used by a true gentleman. Or a clown." - icon = 'icons/obj/items.dmi' - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - icon_state = "cane" - item_state = "stick" - sawn_state = SAWN_OFF - w_class = WEIGHT_CLASS_SMALL - force = 10 - can_unsuppress = 0 - slot_flags = null - origin_tech = "" // NO GIVAWAYS - mag_type = /obj/item/ammo_box/magazine/internal/shot/improvised/cane - sawn_desc = "I'm sorry, but why did you saw your cane in the first place?" - attack_verb = list("bludgeoned", "whacked", "disciplined", "thrashed") - fire_sound = 'sound/weapons/gunshots/gunshot_silenced.ogg' - suppressed = 1 - needs_permit = 0 //its just a cane beepsky..... - -/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane/is_crutch() - return 1 - -/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane/update_icon() - return - -/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane/attackby(obj/item/A, mob/user, params) - if(istype(A, /obj/item/stack/cable_coil)) - return - else - return ..() - -/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane/examine(mob/user) // HAD TO REPEAT EXAMINE CODE BECAUSE GUN CODE DOESNT STEALTH - var/f_name = "\a [src]." - if(blood_DNA && !istype(src, /obj/effect/decal)) - if(gender == PLURAL) - f_name = "some " - else - f_name = "a " - f_name += "blood-stained [name]!" - - . = list("[bicon(src)] That's [f_name]") - - if(desc) - . += desc +/obj/item/gun/projectile/revolver + name = "\improper .357 revolver" + desc = "A suspicious revolver. Uses .357 ammo." + icon_state = "revolver" + mag_type = /obj/item/ammo_box/magazine/internal/cylinder + origin_tech = "combat=3;materials=2" + fire_sound = 'sound/weapons/gunshots/gunshot_strong.ogg' + +/obj/item/gun/projectile/revolver/New() + ..() + if(!istype(magazine, /obj/item/ammo_box/magazine/internal/cylinder)) + verbs -= /obj/item/gun/projectile/revolver/verb/spin + +/obj/item/gun/projectile/revolver/chamber_round(var/spin = 1) + if(spin) + chambered = magazine.get_round(1) + else + chambered = magazine.stored_ammo[1] + return + +/obj/item/gun/projectile/revolver/shoot_with_empty_chamber(mob/living/user as mob|obj) + ..() + chamber_round(1) + +/obj/item/gun/projectile/revolver/process_chamber() + return ..(0, 1) + +/obj/item/gun/projectile/revolver/attackby(obj/item/A, mob/user, params) + . = ..() + if(.) + return + var/num_loaded = magazine.attackby(A, user, params, 1) + if(num_loaded) + to_chat(user, "You load [num_loaded] shell\s into \the [src].") + A.update_icon() + update_icon() + chamber_round(0) + +/obj/item/gun/projectile/revolver/attack_self(mob/living/user) + var/num_unloaded = 0 + chambered = null + while(get_ammo() > 0) + var/obj/item/ammo_casing/CB + CB = magazine.get_round(0) + if(CB) + CB.loc = get_turf(loc) + CB.SpinAnimation(10, 1) + CB.update_icon() + playsound(get_turf(CB), "casingdrop", 60, 1) + num_unloaded++ + if(num_unloaded) + to_chat(user, "You unload [num_unloaded] shell\s from [src].") + else + to_chat(user, "[src] is empty!") + +/obj/item/gun/projectile/revolver/verb/spin() + set name = "Spin Chamber" + set category = "Object" + set desc = "Click to spin your revolver's chamber." + + var/mob/M = usr + + if(M.stat || !in_range(M,src)) + return + + if(istype(magazine, /obj/item/ammo_box/magazine/internal/cylinder)) + var/obj/item/ammo_box/magazine/internal/cylinder/C = magazine + C.spin() + chamber_round(0) + playsound(loc, 'sound/weapons/revolver_spin.ogg', 50, 1) + usr.visible_message("[usr] spins [src]'s chamber.", "You spin [src]'s chamber.") + else + verbs -= /obj/item/gun/projectile/revolver/verb/spin + +/obj/item/gun/projectile/revolver/can_shoot() + return get_ammo(0,0) + +/obj/item/gun/projectile/revolver/get_ammo(countchambered = 0, countempties = 1) + var/boolets = 0 //mature var names for mature people + if(chambered && countchambered) + boolets++ + if(magazine) + boolets += magazine.ammo_count(countempties) + return boolets + +/obj/item/gun/projectile/revolver/examine(mob/user) + . = ..() + . += "[get_ammo(0,0)] of those are live rounds." + +/obj/item/gun/projectile/revolver/detective + desc = "A cheap Martian knock-off of a classic law enforcement firearm. Uses .38-special rounds." + name = "\improper .38 Mars Special" + icon_state = "detective" + mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rev38 + unique_rename = 1 + unique_reskin = 1 + +/obj/item/gun/projectile/revolver/detective/New() + ..() + options["The Original"] = "detective" + options["Leopard Spots"] = "detective_leopard" + options["Black Panther"] = "detective_panther" + options["Gold Trim"] = "detective_gold" + options["The Peacemaker"] = "detective_peacemaker" + options["Cancel"] = null + +/obj/item/gun/projectile/revolver/detective/process_fire(atom/target as mob|obj|turf, mob/living/user as mob|obj, message = 1, params, zone_override = "") + if(magazine.caliber != initial(magazine.caliber)) + if(prob(70 - (magazine.ammo_count() * 10))) //minimum probability of 10, maximum of 60 + playsound(user, fire_sound, 50, 1) + to_chat(user, "[src] blows up in your face!") + user.take_organ_damage(0,20) + user.unEquip(src) + return 0 + ..() + +/obj/item/gun/projectile/revolver/detective/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + if(magazine.caliber == "38") + to_chat(user, "You begin to reinforce the barrel of [src]...") + if(magazine.ammo_count()) + afterattack(user, user) //you know the drill + user.visible_message("[src] goes off!", "[src] goes off in your face!") + return + if(!I.use_tool(src, user, 30, volume = I.tool_volume)) + return + if(magazine.ammo_count()) + to_chat(user, "You can't modify it!") + return + magazine.caliber = "357" + desc = "The barrel and chamber assembly seems to have been modified." + to_chat(user, "You reinforce the barrel of [src]. Now it will fire .357 rounds.") + else + to_chat(user, "You begin to revert the modifications to [src]...") + if(magazine.ammo_count()) + afterattack(user, user) //and again + user.visible_message("[src] goes off!", "[src] goes off in your face!") + return + if(!I.use_tool(src, user, 30, volume = I.tool_volume)) + return + if(magazine.ammo_count()) + to_chat(user, "You can't modify it!") + return + magazine.caliber = "38" + desc = initial(desc) + to_chat(user, "You remove the modifications on [src]. Now it will fire .38 rounds.") + +/obj/item/gun/projectile/revolver/fingergun //Summoned by the Finger Gun spell, from advanced mimery traitor item + name = "\improper finger gun" + desc = "Bang bang bang!" + icon_state = "fingergun" + mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rev38/invisible + origin_tech = "" + flags = ABSTRACT | NODROP | DROPDEL + slot_flags = null + fire_sound = null + fire_sound_text = null + lefthand_file = null + righthand_file = null + clumsy_check = 0 //Stole your uplink! Honk! + needs_permit = 0 //go away beepsky + +/obj/item/gun/projectile/revolver/fingergun/fake + desc = "Pew pew pew!" + mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rev38/invisible/fake + +/obj/item/gun/projectile/revolver/fingergun/New() + ..() + verbs -= /obj/item/gun/projectile/revolver/verb/spin + +/obj/item/gun/projectile/revolver/fingergun/shoot_with_empty_chamber(/*mob/living/user as mob|obj*/) + to_chat(usr, "You are out of ammo! You holster your fingers.") + qdel(src) + return + +/obj/item/gun/projectile/revolver/fingergun/afterattack(atom/target, mob/living/user, flag, params) + if(!user.mind.miming) + to_chat(usr, "You must dedicate yourself to silence first. Use your fingers if you wish to holster them.") + return + ..() + +/obj/item/gun/projectile/revolver/fingergun/attackby(obj/item/A, mob/user, params) + return + +/obj/item/gun/projectile/revolver/fingergun/attack_self(mob/living/user) + to_chat(usr, "You holster your fingers. Another time.") + qdel(src) + return + +/obj/item/gun/projectile/revolver/mateba + name = "\improper Unica 6 auto-revolver" + desc = "A retro high-powered autorevolver typically used by officers of the New Russia military. Uses .357 ammo." //>10mm hole >.357 + icon_state = "mateba" + +/obj/item/gun/projectile/revolver/golden + name = "\improper Golden revolver" + desc = "This ain't no game, ain't never been no show, And I'll gladly gun down the oldest lady you know. Uses .357 ammo." + icon_state = "goldrevolver" + fire_sound = 'sound/weapons/resonator_blast.ogg' + recoil = 8 + +/obj/item/gun/projectile/revolver/nagant + name = "nagant revolver" + desc = "An old model of revolver that originated in Russia. Able to be suppressed. Uses 7.62x38mmR ammo." + icon_state = "nagant" + origin_tech = "combat=3" + can_suppress = 1 + mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rev762 + +// A gun to play Russian Roulette! +// You can spin the chamber to randomize the position of the bullet. + +/obj/item/gun/projectile/revolver/russian + name = "\improper Russian Revolver" + desc = "A Russian-made revolver for drinking games. Uses .357 ammo, and has a mechanism that spins the chamber before each trigger pull." + origin_tech = "combat=2;materials=2" + mag_type = /obj/item/ammo_box/magazine/internal/rus357 + var/spun = 0 + + +/obj/item/gun/projectile/revolver/russian/New() + ..() + Spin() + update_icon() + +/obj/item/gun/projectile/revolver/russian/proc/Spin() + chambered = null + var/random = rand(1, magazine.max_ammo) + if(random <= get_ammo(0,0)) + chamber_round() + spun = 1 + +/obj/item/gun/projectile/revolver/russian/attackby(obj/item/A, mob/user, params) + var/num_loaded = ..() + if(num_loaded) + user.visible_message("[user] loads a single bullet into the revolver and spins the chamber.", "You load a single bullet into the chamber and spin it.") + else + user.visible_message("[user] spins the chamber of the revolver.", "You spin the revolver's chamber.") + if(get_ammo() > 0) + Spin() + update_icon() + A.update_icon() + return + +/obj/item/gun/projectile/revolver/russian/attack_self(mob/user) + if(!spun && can_shoot()) + user.visible_message("[user] spins the chamber of the revolver.", "You spin the revolver's chamber.") + Spin() + else + var/num_unloaded = 0 + while(get_ammo() > 0) + var/obj/item/ammo_casing/CB + CB = magazine.get_round() + chambered = null + CB.loc = get_turf(loc) + CB.update_icon() + playsound(get_turf(CB), "casingdrop", 60, 1) + num_unloaded++ + if(num_unloaded) + to_chat(user, "You unload [num_unloaded] shell\s from [src].") + else + to_chat(user, "[src] is empty.") + +/obj/item/gun/projectile/revolver/russian/afterattack(atom/target as mob|obj|turf, mob/living/user as mob|obj, flag, params) + if(flag) + if(!(target in user.contents) && ismob(target)) + if(user.a_intent == INTENT_HARM) // Flogging action + return + + if(isliving(user)) + if(!can_trigger_gun(user)) + return + if(target != user) + if(ismob(target)) + to_chat(user, "A mechanism prevents you from shooting anyone but yourself!") + return + + if(ishuman(user)) + if(!spun) + to_chat(user, "You need to spin the revolver's chamber first!") + return + + spun = 0 + + if(chambered) + var/obj/item/ammo_casing/AC = chambered + if(AC.fire(user, user)) + playsound(user, fire_sound, 50, 1) + var/zone = check_zone(user.zone_selected) + if(zone == "head" || zone == "eyes" || zone == "mouth") + shoot_self(user, zone) + else + user.visible_message("[user.name] cowardly fires [src] at [user.p_their()] [zone]!", "You cowardly fire [src] at your [zone]!", "You hear a gunshot!") + return + + user.visible_message("*click*") + playsound(user, 'sound/weapons/empty.ogg', 100, 1) + +/obj/item/gun/projectile/revolver/russian/proc/shoot_self(mob/living/carbon/human/user, affecting = "head") + user.apply_damage(300, BRUTE, affecting) + user.visible_message("[user.name] fires [src] at [user.p_their()] head!", "You fire [src] at your head!", "You hear a gunshot!") + +/obj/item/gun/projectile/revolver/russian/soul + name = "cursed Russian revolver" + desc = "To play with this revolver requires wagering your very soul." + +/obj/item/gun/projectile/revolver/russian/soul/shoot_self(mob/living/user) + ..() + var/obj/item/soulstone/anybody/SS = new /obj/item/soulstone/anybody(get_turf(src)) + if(!SS.transfer_soul("FORCE", user)) //Something went wrong + qdel(SS) + return + user.visible_message("[user.name]'s soul is captured by \the [src]!", "You've lost the gamble! Your soul is forfeit!") + +/obj/item/gun/projectile/revolver/capgun + name = "cap gun" + desc = "Looks almost like the real thing! Ages 8 and up." + origin_tech = null + mag_type = /obj/item/ammo_box/magazine/internal/cylinder/cap + +///////////////////////////// +// DOUBLE BARRELED SHOTGUN // +///////////////////////////// + +/obj/item/gun/projectile/revolver/doublebarrel + name = "double-barreled shotgun" + desc = "A true classic." + icon_state = "dshotgun" + item_state = "shotgun" + w_class = WEIGHT_CLASS_BULKY + force = 10 + flags = CONDUCT + slot_flags = SLOT_BACK + mag_type = /obj/item/ammo_box/magazine/internal/shot/dual + fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' + sawn_desc = "Omar's coming!" + unique_rename = 1 + unique_reskin = 1 + +/obj/item/gun/projectile/revolver/doublebarrel/New() + ..() + options["Default"] = "dshotgun" + options["Dark Red Finish"] = "dshotgun-d" + options["Ash"] = "dshotgun-f" + options["Faded Grey"] = "dshotgun-g" + options["Maple"] = "dshotgun-l" + options["Rosewood"] = "dshotgun-p" + options["Cancel"] = null + +/obj/item/gun/projectile/revolver/doublebarrel/attackby(obj/item/A, mob/user, params) + if(istype(A, /obj/item/ammo_box) || istype(A, /obj/item/ammo_casing)) + chamber_round() + if(istype(A, /obj/item/melee/energy)) + var/obj/item/melee/energy/W = A + if(W.active) + sawoff(user) + if(istype(A, /obj/item/circular_saw) || istype(A, /obj/item/gun/energy/plasmacutter)) + sawoff(user) + else + return ..() + +/obj/item/gun/projectile/revolver/doublebarrel/attack_self(mob/living/user) + var/num_unloaded = 0 + while(get_ammo() > 0) + var/obj/item/ammo_casing/CB + CB = magazine.get_round(0) + chambered = null + CB.loc = get_turf(loc) + CB.SpinAnimation(10, 1) + CB.update_icon() + playsound(get_turf(CB), 'sound/weapons/gun_interactions/shotgun_fall.ogg', 70, 1) + num_unloaded++ + if(num_unloaded) + to_chat(user, "You break open \the [src] and unload [num_unloaded] shell\s.") + else + to_chat(user, "[src] is empty.") + +/obj/item/gun/projectile/revolver/doublebarrel/isHandgun() //contrary to popular opinion, double barrels are not, shockingly, handguns + return 0 + +// IMPROVISED SHOTGUN // + +/obj/item/gun/projectile/revolver/doublebarrel/improvised + name = "improvised shotgun" + desc = "Essentially a tube that aims shotgun shells." + icon_state = "ishotgun" + item_state = "shotgun" + w_class = WEIGHT_CLASS_BULKY + force = 10 + slot_flags = null + mag_type = /obj/item/ammo_box/magazine/internal/shot/improvised + fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' + sawn_desc = "I'm just here for the gasoline." + unique_rename = 0 + unique_reskin = 0 + var/slung = 0 + +/obj/item/gun/projectile/revolver/doublebarrel/improvised/attackby(obj/item/A, mob/user, params) + if(istype(A, /obj/item/stack/cable_coil) && !sawn_state) + var/obj/item/stack/cable_coil/C = A + if(C.use(10)) + slot_flags = SLOT_BACK + icon_state = "ishotgunsling" + to_chat(user, "You tie the lengths of cable to the shotgun, making a sling.") + slung = 1 + update_icon() + else + to_chat(user, "You need at least ten lengths of cable if you want to make a sling.") + return + else + return ..() + +/obj/item/gun/projectile/revolver/doublebarrel/improvised/update_icon() + ..() + if(slung && (slot_flags & SLOT_BELT) ) + slung = 0 + icon_state = "ishotgun-sawn" + +/obj/item/gun/projectile/revolver/doublebarrel/improvised/sawoff(mob/user) + . = ..() + if(. && slung) //sawing off the gun removes the sling + new /obj/item/stack/cable_coil(get_turf(src), 10) + slung = 0 + update_icon() + +//caneshotgun + +/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane + name = "cane" + desc = "A cane used by a true gentleman. Or a clown." + icon = 'icons/obj/items.dmi' + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + icon_state = "cane" + item_state = "stick" + sawn_state = SAWN_OFF + w_class = WEIGHT_CLASS_SMALL + force = 10 + can_unsuppress = 0 + slot_flags = null + origin_tech = "" // NO GIVAWAYS + mag_type = /obj/item/ammo_box/magazine/internal/shot/improvised/cane + sawn_desc = "I'm sorry, but why did you saw your cane in the first place?" + attack_verb = list("bludgeoned", "whacked", "disciplined", "thrashed") + fire_sound = 'sound/weapons/gunshots/gunshot_silenced.ogg' + suppressed = 1 + needs_permit = 0 //its just a cane beepsky..... + +/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane/is_crutch() + return 1 + +/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane/update_icon() + return + +/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane/attackby(obj/item/A, mob/user, params) + if(istype(A, /obj/item/stack/cable_coil)) + return + else + return ..() + +/obj/item/gun/projectile/revolver/doublebarrel/improvised/cane/examine(mob/user) // HAD TO REPEAT EXAMINE CODE BECAUSE GUN CODE DOESNT STEALTH + var/f_name = "\a [src]." + if(blood_DNA && !istype(src, /obj/effect/decal)) + if(gender == PLURAL) + f_name = "some " + else + f_name = "a " + f_name += "blood-stained [name]!" + + . = list("[bicon(src)] That's [f_name]") + + if(desc) + . += desc diff --git a/code/modules/projectiles/guns/projectile/shotgun.dm b/code/modules/projectiles/guns/projectile/shotgun.dm index ee6b013af0d..17a55722d2c 100644 --- a/code/modules/projectiles/guns/projectile/shotgun.dm +++ b/code/modules/projectiles/guns/projectile/shotgun.dm @@ -1,331 +1,331 @@ -/obj/item/gun/projectile/shotgun - name = "shotgun" - desc = "A traditional shotgun with wood furniture and a four-shell capacity underneath." - icon_state = "shotgun" - item_state = "shotgun" - w_class = WEIGHT_CLASS_BULKY - force = 10 - flags = CONDUCT - slot_flags = SLOT_BACK - origin_tech = "combat=4;materials=2" - mag_type = /obj/item/ammo_box/magazine/internal/shot - fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' - var/recentpump = 0 // to prevent spammage - weapon_weight = WEAPON_MEDIUM - -/obj/item/gun/projectile/shotgun/attackby(obj/item/A, mob/user, params) - . = ..() - if(.) - return - var/num_loaded = magazine.attackby(A, user, params, 1) - if(num_loaded) - to_chat(user, "You load [num_loaded] shell\s into \the [src]!") - A.update_icon() - update_icon() - - -/obj/item/gun/projectile/shotgun/process_chamber() - return ..(0, 0) - -/obj/item/gun/projectile/shotgun/chamber_round() - return - -/obj/item/gun/projectile/shotgun/can_shoot() - if(!chambered) - return 0 - return (chambered.BB ? 1 : 0) - -/obj/item/gun/projectile/shotgun/attack_self(mob/living/user) - if(recentpump) - return - pump(user) - recentpump = 1 - spawn(10) - recentpump = 0 - return - - -/obj/item/gun/projectile/shotgun/proc/pump(mob/M) - playsound(M, 'sound/weapons/gun_interactions/shotgunpump.ogg', 60, 1) - pump_unload(M) - pump_reload(M) - update_icon() //I.E. fix the desc - return 1 - -/obj/item/gun/projectile/shotgun/proc/pump_unload(mob/M) - if(chambered)//We have a shell in the chamber - chambered.loc = get_turf(src)//Eject casing - chambered.SpinAnimation(5, 1) - playsound(src, chambered.drop_sound, 60, 1) - chambered = null - -/obj/item/gun/projectile/shotgun/proc/pump_reload(mob/M) - if(!magazine.ammo_count()) - return 0 - var/obj/item/ammo_casing/AC = magazine.get_round() //load next casing. - chambered = AC - -/obj/item/gun/projectile/shotgun/examine(mob/user) - . = ..() - if(chambered) - . += "A [chambered.BB ? "live" : "spent"] one is in the chamber." - -/obj/item/gun/projectile/shotgun/isHandgun() //You cannot, in fact, holster a shotgun. - return 0 - -/obj/item/gun/projectile/shotgun/lethal - mag_type = /obj/item/ammo_box/magazine/internal/shot/lethal - -// RIOT SHOTGUN // - -/obj/item/gun/projectile/shotgun/riot //for spawn in the armory - name = "riot shotgun" - desc = "A sturdy shotgun with a longer magazine and a fixed tactical stock designed for non-lethal riot control." - icon_state = "riotshotgun" - mag_type = /obj/item/ammo_box/magazine/internal/shot/riot - sawn_desc = "Come with me if you want to live." - sawn_state = SAWN_INTACT - -/obj/item/gun/projectile/shotgun/riot/attackby(obj/item/A, mob/user, params) - if(istype(A, /obj/item/circular_saw) || istype(A, /obj/item/gun/energy/plasmacutter)) - sawoff(user) - if(istype(A, /obj/item/melee/energy)) - var/obj/item/melee/energy/W = A - if(W.active) - sawoff(user) - if(istype(A, /obj/item/pipe)) - unsaw(A, user) - else - return ..() - -/obj/item/gun/projectile/shotgun/riot/sawoff(mob/user) - if(sawn_state == SAWN_OFF) - to_chat(user, "[src] has already been shortened!") - return - if(istype(loc, /obj/item/storage)) //To prevent inventory exploits - to_chat(user, "How do you plan to modify [src] while it's in a bag.") - return - if(chambered) //if the gun is chambering live ammo, shoot self, if chambering empty ammo, 'click' - if(chambered.BB) - afterattack(user, user) - user.visible_message("\The [src] goes off!", "\The [src] goes off in your face!") - return - else - afterattack(user, user) - user.visible_message("The [src] goes click!", "The [src] you are holding goes click.") - if(magazine.ammo_count()) //Spill the mag onto the floor - user.visible_message("[user.name] opens [src] up and the shells go goes flying around!", "You open [src] up and the shells go goes flying everywhere!!") - while(get_ammo(0) > 0) - var/obj/item/ammo_casing/CB - CB = magazine.get_round(0) - if(CB) - CB.loc = get_turf(loc) - CB.update_icon() - - if(do_after(user, 30, target = src)) - user.visible_message("[user] shortens \the [src]!", "You shorten \the [src].") - post_sawoff() - return 1 - - -/obj/item/gun/projectile/shotgun/riot/proc/post_sawoff() - name = "assault shotgun" - desc = sawn_desc - w_class = WEIGHT_CLASS_NORMAL - current_skin = "riotshotgun-short" - item_state = "gun" //phil235 is it different with different skin? - slot_flags &= ~SLOT_BACK //you can't sling it on your back - slot_flags |= SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally) - sawn_state = SAWN_OFF - magazine.max_ammo = 3 - update_icon() - - -/obj/item/gun/projectile/shotgun/riot/proc/unsaw(obj/item/A, mob/user) - if(sawn_state == SAWN_INTACT) - to_chat(user, "[src] has not been shortened!") - return - if(istype(loc, /obj/item/storage)) //To prevent inventory exploits - to_chat(user, "How do you plan to modify [src] while it's in a bag.") - return - if(chambered) //if the gun is chambering live ammo, shoot self, if chambering empty ammo, 'click' - if(chambered.BB) - afterattack(user, user) - user.visible_message("\The [src] goes off!", "\The [src] goes off in your face!") - return - else - afterattack(user, user) - user.visible_message("The [src] goes click!", "The [src] you are holding goes click.") - if(magazine.ammo_count()) //Spill the mag onto the floor - user.visible_message("[user.name] opens [src] up and the shells go goes flying around!", "You open [src] up and the shells go goes flying everywhere!!") - while(get_ammo() > 0) - var/obj/item/ammo_casing/CB - CB = magazine.get_round(0) - if(CB) - CB.loc = get_turf(loc) - CB.update_icon() - - if(do_after(user, 30, target = src)) - qdel(A) - user.visible_message("[user] lengthens [src]!", "You lengthen [src].") - post_unsaw(user) - return 1 - -/obj/item/gun/projectile/shotgun/riot/proc/post_unsaw() - name = initial(name) - desc = initial(desc) - w_class = initial(w_class) - current_skin = "riotshotgun" - item_state = initial(item_state) - slot_flags &= ~SLOT_BELT - slot_flags |= SLOT_BACK - sawn_state = SAWN_INTACT - magazine.max_ammo = 6 - update_icon() - -/obj/item/gun/projectile/shotgun/riot/update_icon() //Can't use the old proc as it makes it go to riotshotgun-short_sawn - ..() - if(current_skin) - icon_state = "[current_skin]" - else - icon_state = "[initial(icon_state)]" - -/obj/item/gun/projectile/shotgun/riot/short - mag_type = /obj/item/ammo_box/magazine/internal/shot/riot/short - -/obj/item/gun/projectile/shotgun/riot/short/New() - ..() - post_sawoff() - - - -/////////////////////// -// BOLT ACTION RIFLE // -/////////////////////// - -/obj/item/gun/projectile/shotgun/boltaction - name = "\improper Mosin Nagant" - desc = "This piece of junk looks like something that could have been used 700 years ago." - icon_state = "moistnugget" - item_state = "moistnugget" - slot_flags = 0 //no SLOT_BACK sprite, alas - mag_type = /obj/item/ammo_box/magazine/internal/boltaction - fire_sound = 'sound/weapons/gunshots/gunshot_rifle.ogg' - var/bolt_open = 0 - can_bayonet = TRUE - knife_x_offset = 27 - knife_y_offset = 13 - -/obj/item/gun/projectile/shotgun/boltaction/pump(mob/M) - playsound(M, 'sound/weapons/gun_interactions/rifle_load.ogg', 60, 1) - if(bolt_open) - pump_reload(M) - else - pump_unload(M) - bolt_open = !bolt_open - update_icon() //I.E. fix the desc - return 1 - -/obj/item/gun/projectile/shotgun/blow_up(mob/user) - . = 0 - if(chambered && chambered.BB) - process_fire(user, user,0) - . = 1 - -/obj/item/gun/projectile/shotgun/boltaction/attackby(obj/item/A, mob/user, params) - if(!bolt_open) - to_chat(user, "The bolt is closed!") - return - . = ..() - -/obj/item/gun/projectile/shotgun/boltaction/examine(mob/user) - . = ..() - . += "The bolt is [bolt_open ? "open" : "closed"]." - -/obj/item/gun/projectile/shotgun/boltaction/enchanted - name = "enchanted bolt action rifle" - desc = "Careful not to lose your head." - var/guns_left = 30 - mag_type = /obj/item/ammo_box/magazine/internal/boltaction/enchanted - can_bayonet = FALSE - -/obj/item/gun/projectile/shotgun/boltaction/enchanted/New() - ..() - bolt_open = 1 - pump() - -/obj/item/gun/projectile/shotgun/boltaction/enchanted/dropped() - ..() - guns_left = 0 - -/obj/item/gun/projectile/shotgun/boltaction/enchanted/shoot_live_shot(mob/living/user as mob|obj, pointblank = 0, mob/pbtarget = null, message = 1) - ..() - if(guns_left) - var/obj/item/gun/projectile/shotgun/boltaction/enchanted/GUN = new - GUN.guns_left = guns_left - 1 - user.drop_item() - user.swap_hand() - user.put_in_hands(GUN) - else - user.drop_item() - spawn(0) - throw_at(pick(oview(7,get_turf(user))),1,1) - user.visible_message("[user] tosses aside the spent rifle!") - -// Automatic Shotguns// - -/obj/item/gun/projectile/shotgun/automatic - -/obj/item/gun/projectile/shotgun/automatic/shoot_live_shot(mob/living/user as mob|obj) - ..() - pump(user) - -/obj/item/gun/projectile/shotgun/automatic/combat - name = "combat shotgun" - desc = "A semi automatic shotgun with tactical furniture and a six-shell capacity underneath." - icon_state = "cshotgun" - origin_tech = "combat=6" - mag_type = /obj/item/ammo_box/magazine/internal/shot/com - w_class = WEIGHT_CLASS_HUGE - -//Dual Feed Shotgun - -/obj/item/gun/projectile/shotgun/automatic/dual_tube - name = "cycler shotgun" - desc = "An advanced shotgun with two separate magazine tubes, allowing you to quickly toggle between ammo types." - icon_state = "cycler" - origin_tech = "combat=4;materials=2" - mag_type = /obj/item/ammo_box/magazine/internal/shot/tube - w_class = WEIGHT_CLASS_HUGE - var/toggled = 0 - var/obj/item/ammo_box/magazine/internal/shot/alternate_magazine - -/obj/item/gun/projectile/shotgun/automatic/dual_tube/New() - ..() - if(!alternate_magazine) - alternate_magazine = new mag_type(src) - -/obj/item/gun/projectile/shotgun/automatic/dual_tube/attack_self(mob/living/user) - if(!chambered && magazine.contents.len) - pump() - else - toggle_tube(user) - -/obj/item/gun/projectile/shotgun/automatic/dual_tube/proc/toggle_tube(mob/living/user) - var/current_mag = magazine - var/alt_mag = alternate_magazine - magazine = alt_mag - alternate_magazine = current_mag - toggled = !toggled - if(toggled) - to_chat(user, "You switch to tube B.") - else - to_chat(user, "You switch to tube A.") - playsound(user, 'sound/weapons/gun_interactions/selector.ogg', 100, 1) - -/obj/item/gun/projectile/shotgun/automatic/dual_tube/AltClick(mob/living/user) - if(user.incapacitated() || !Adjacent(user) || !istype(user)) - return - pump() - -// DOUBLE BARRELED SHOTGUN, IMPROVISED SHOTGUN, and CANE SHOTGUN are in revolver.dm +/obj/item/gun/projectile/shotgun + name = "shotgun" + desc = "A traditional shotgun with wood furniture and a four-shell capacity underneath." + icon_state = "shotgun" + item_state = "shotgun" + w_class = WEIGHT_CLASS_BULKY + force = 10 + flags = CONDUCT + slot_flags = SLOT_BACK + origin_tech = "combat=4;materials=2" + mag_type = /obj/item/ammo_box/magazine/internal/shot + fire_sound = 'sound/weapons/gunshots/gunshot_shotgun.ogg' + var/recentpump = 0 // to prevent spammage + weapon_weight = WEAPON_MEDIUM + +/obj/item/gun/projectile/shotgun/attackby(obj/item/A, mob/user, params) + . = ..() + if(.) + return + var/num_loaded = magazine.attackby(A, user, params, 1) + if(num_loaded) + to_chat(user, "You load [num_loaded] shell\s into \the [src]!") + A.update_icon() + update_icon() + + +/obj/item/gun/projectile/shotgun/process_chamber() + return ..(0, 0) + +/obj/item/gun/projectile/shotgun/chamber_round() + return + +/obj/item/gun/projectile/shotgun/can_shoot() + if(!chambered) + return 0 + return (chambered.BB ? 1 : 0) + +/obj/item/gun/projectile/shotgun/attack_self(mob/living/user) + if(recentpump) + return + pump(user) + recentpump = 1 + spawn(10) + recentpump = 0 + return + + +/obj/item/gun/projectile/shotgun/proc/pump(mob/M) + playsound(M, 'sound/weapons/gun_interactions/shotgunpump.ogg', 60, 1) + pump_unload(M) + pump_reload(M) + update_icon() //I.E. fix the desc + return 1 + +/obj/item/gun/projectile/shotgun/proc/pump_unload(mob/M) + if(chambered)//We have a shell in the chamber + chambered.loc = get_turf(src)//Eject casing + chambered.SpinAnimation(5, 1) + playsound(src, chambered.drop_sound, 60, 1) + chambered = null + +/obj/item/gun/projectile/shotgun/proc/pump_reload(mob/M) + if(!magazine.ammo_count()) + return 0 + var/obj/item/ammo_casing/AC = magazine.get_round() //load next casing. + chambered = AC + +/obj/item/gun/projectile/shotgun/examine(mob/user) + . = ..() + if(chambered) + . += "A [chambered.BB ? "live" : "spent"] one is in the chamber." + +/obj/item/gun/projectile/shotgun/isHandgun() //You cannot, in fact, holster a shotgun. + return 0 + +/obj/item/gun/projectile/shotgun/lethal + mag_type = /obj/item/ammo_box/magazine/internal/shot/lethal + +// RIOT SHOTGUN // + +/obj/item/gun/projectile/shotgun/riot //for spawn in the armory + name = "riot shotgun" + desc = "A sturdy shotgun with a longer magazine and a fixed tactical stock designed for non-lethal riot control." + icon_state = "riotshotgun" + mag_type = /obj/item/ammo_box/magazine/internal/shot/riot + sawn_desc = "Come with me if you want to live." + sawn_state = SAWN_INTACT + +/obj/item/gun/projectile/shotgun/riot/attackby(obj/item/A, mob/user, params) + if(istype(A, /obj/item/circular_saw) || istype(A, /obj/item/gun/energy/plasmacutter)) + sawoff(user) + if(istype(A, /obj/item/melee/energy)) + var/obj/item/melee/energy/W = A + if(W.active) + sawoff(user) + if(istype(A, /obj/item/pipe)) + unsaw(A, user) + else + return ..() + +/obj/item/gun/projectile/shotgun/riot/sawoff(mob/user) + if(sawn_state == SAWN_OFF) + to_chat(user, "[src] has already been shortened!") + return + if(istype(loc, /obj/item/storage)) //To prevent inventory exploits + to_chat(user, "How do you plan to modify [src] while it's in a bag.") + return + if(chambered) //if the gun is chambering live ammo, shoot self, if chambering empty ammo, 'click' + if(chambered.BB) + afterattack(user, user) + user.visible_message("\The [src] goes off!", "\The [src] goes off in your face!") + return + else + afterattack(user, user) + user.visible_message("The [src] goes click!", "The [src] you are holding goes click.") + if(magazine.ammo_count()) //Spill the mag onto the floor + user.visible_message("[user.name] opens [src] up and the shells go goes flying around!", "You open [src] up and the shells go goes flying everywhere!!") + while(get_ammo(0) > 0) + var/obj/item/ammo_casing/CB + CB = magazine.get_round(0) + if(CB) + CB.loc = get_turf(loc) + CB.update_icon() + + if(do_after(user, 30, target = src)) + user.visible_message("[user] shortens \the [src]!", "You shorten \the [src].") + post_sawoff() + return 1 + + +/obj/item/gun/projectile/shotgun/riot/proc/post_sawoff() + name = "assault shotgun" + desc = sawn_desc + w_class = WEIGHT_CLASS_NORMAL + current_skin = "riotshotgun-short" + item_state = "gun" //phil235 is it different with different skin? + slot_flags &= ~SLOT_BACK //you can't sling it on your back + slot_flags |= SLOT_BELT //but you can wear it on your belt (poorly concealed under a trenchcoat, ideally) + sawn_state = SAWN_OFF + magazine.max_ammo = 3 + update_icon() + + +/obj/item/gun/projectile/shotgun/riot/proc/unsaw(obj/item/A, mob/user) + if(sawn_state == SAWN_INTACT) + to_chat(user, "[src] has not been shortened!") + return + if(istype(loc, /obj/item/storage)) //To prevent inventory exploits + to_chat(user, "How do you plan to modify [src] while it's in a bag.") + return + if(chambered) //if the gun is chambering live ammo, shoot self, if chambering empty ammo, 'click' + if(chambered.BB) + afterattack(user, user) + user.visible_message("\The [src] goes off!", "\The [src] goes off in your face!") + return + else + afterattack(user, user) + user.visible_message("The [src] goes click!", "The [src] you are holding goes click.") + if(magazine.ammo_count()) //Spill the mag onto the floor + user.visible_message("[user.name] opens [src] up and the shells go goes flying around!", "You open [src] up and the shells go goes flying everywhere!!") + while(get_ammo() > 0) + var/obj/item/ammo_casing/CB + CB = magazine.get_round(0) + if(CB) + CB.loc = get_turf(loc) + CB.update_icon() + + if(do_after(user, 30, target = src)) + qdel(A) + user.visible_message("[user] lengthens [src]!", "You lengthen [src].") + post_unsaw(user) + return 1 + +/obj/item/gun/projectile/shotgun/riot/proc/post_unsaw() + name = initial(name) + desc = initial(desc) + w_class = initial(w_class) + current_skin = "riotshotgun" + item_state = initial(item_state) + slot_flags &= ~SLOT_BELT + slot_flags |= SLOT_BACK + sawn_state = SAWN_INTACT + magazine.max_ammo = 6 + update_icon() + +/obj/item/gun/projectile/shotgun/riot/update_icon() //Can't use the old proc as it makes it go to riotshotgun-short_sawn + ..() + if(current_skin) + icon_state = "[current_skin]" + else + icon_state = "[initial(icon_state)]" + +/obj/item/gun/projectile/shotgun/riot/short + mag_type = /obj/item/ammo_box/magazine/internal/shot/riot/short + +/obj/item/gun/projectile/shotgun/riot/short/New() + ..() + post_sawoff() + + + +/////////////////////// +// BOLT ACTION RIFLE // +/////////////////////// + +/obj/item/gun/projectile/shotgun/boltaction + name = "\improper Mosin Nagant" + desc = "This piece of junk looks like something that could have been used 700 years ago." + icon_state = "moistnugget" + item_state = "moistnugget" + slot_flags = 0 //no SLOT_BACK sprite, alas + mag_type = /obj/item/ammo_box/magazine/internal/boltaction + fire_sound = 'sound/weapons/gunshots/gunshot_rifle.ogg' + var/bolt_open = 0 + can_bayonet = TRUE + knife_x_offset = 27 + knife_y_offset = 13 + +/obj/item/gun/projectile/shotgun/boltaction/pump(mob/M) + playsound(M, 'sound/weapons/gun_interactions/rifle_load.ogg', 60, 1) + if(bolt_open) + pump_reload(M) + else + pump_unload(M) + bolt_open = !bolt_open + update_icon() //I.E. fix the desc + return 1 + +/obj/item/gun/projectile/shotgun/blow_up(mob/user) + . = 0 + if(chambered && chambered.BB) + process_fire(user, user,0) + . = 1 + +/obj/item/gun/projectile/shotgun/boltaction/attackby(obj/item/A, mob/user, params) + if(!bolt_open) + to_chat(user, "The bolt is closed!") + return + . = ..() + +/obj/item/gun/projectile/shotgun/boltaction/examine(mob/user) + . = ..() + . += "The bolt is [bolt_open ? "open" : "closed"]." + +/obj/item/gun/projectile/shotgun/boltaction/enchanted + name = "enchanted bolt action rifle" + desc = "Careful not to lose your head." + var/guns_left = 30 + mag_type = /obj/item/ammo_box/magazine/internal/boltaction/enchanted + can_bayonet = FALSE + +/obj/item/gun/projectile/shotgun/boltaction/enchanted/New() + ..() + bolt_open = 1 + pump() + +/obj/item/gun/projectile/shotgun/boltaction/enchanted/dropped() + ..() + guns_left = 0 + +/obj/item/gun/projectile/shotgun/boltaction/enchanted/shoot_live_shot(mob/living/user as mob|obj, pointblank = 0, mob/pbtarget = null, message = 1) + ..() + if(guns_left) + var/obj/item/gun/projectile/shotgun/boltaction/enchanted/GUN = new + GUN.guns_left = guns_left - 1 + user.drop_item() + user.swap_hand() + user.put_in_hands(GUN) + else + user.drop_item() + spawn(0) + throw_at(pick(oview(7,get_turf(user))),1,1) + user.visible_message("[user] tosses aside the spent rifle!") + +// Automatic Shotguns// + +/obj/item/gun/projectile/shotgun/automatic + +/obj/item/gun/projectile/shotgun/automatic/shoot_live_shot(mob/living/user as mob|obj) + ..() + pump(user) + +/obj/item/gun/projectile/shotgun/automatic/combat + name = "combat shotgun" + desc = "A semi automatic shotgun with tactical furniture and a six-shell capacity underneath." + icon_state = "cshotgun" + origin_tech = "combat=6" + mag_type = /obj/item/ammo_box/magazine/internal/shot/com + w_class = WEIGHT_CLASS_HUGE + +//Dual Feed Shotgun + +/obj/item/gun/projectile/shotgun/automatic/dual_tube + name = "cycler shotgun" + desc = "An advanced shotgun with two separate magazine tubes, allowing you to quickly toggle between ammo types." + icon_state = "cycler" + origin_tech = "combat=4;materials=2" + mag_type = /obj/item/ammo_box/magazine/internal/shot/tube + w_class = WEIGHT_CLASS_HUGE + var/toggled = 0 + var/obj/item/ammo_box/magazine/internal/shot/alternate_magazine + +/obj/item/gun/projectile/shotgun/automatic/dual_tube/New() + ..() + if(!alternate_magazine) + alternate_magazine = new mag_type(src) + +/obj/item/gun/projectile/shotgun/automatic/dual_tube/attack_self(mob/living/user) + if(!chambered && magazine.contents.len) + pump() + else + toggle_tube(user) + +/obj/item/gun/projectile/shotgun/automatic/dual_tube/proc/toggle_tube(mob/living/user) + var/current_mag = magazine + var/alt_mag = alternate_magazine + magazine = alt_mag + alternate_magazine = current_mag + toggled = !toggled + if(toggled) + to_chat(user, "You switch to tube B.") + else + to_chat(user, "You switch to tube A.") + playsound(user, 'sound/weapons/gun_interactions/selector.ogg', 100, 1) + +/obj/item/gun/projectile/shotgun/automatic/dual_tube/AltClick(mob/living/user) + if(user.incapacitated() || !Adjacent(user) || !istype(user)) + return + pump() + +// DOUBLE BARRELED SHOTGUN, IMPROVISED SHOTGUN, and CANE SHOTGUN are in revolver.dm diff --git a/code/modules/projectiles/guns/projectile/sniper.dm b/code/modules/projectiles/guns/projectile/sniper.dm index de3f4c02a87..09d9ec15aed 100644 --- a/code/modules/projectiles/guns/projectile/sniper.dm +++ b/code/modules/projectiles/guns/projectile/sniper.dm @@ -208,4 +208,4 @@ else if(ammo) overlays += image('icons/obj/ammo.dmi', icon_state = ".50mag-f") else - icon_state = "[initial(icon_state)]" \ No newline at end of file + icon_state = "[initial(icon_state)]" diff --git a/code/modules/projectiles/guns/projectile/toy.dm b/code/modules/projectiles/guns/projectile/toy.dm index e9625e69795..55a9aa79c6b 100644 --- a/code/modules/projectiles/guns/projectile/toy.dm +++ b/code/modules/projectiles/guns/projectile/toy.dm @@ -133,4 +133,4 @@ mag_type = /obj/item/ammo_box/magazine/toy/sniper_rounds /obj/item/gun/projectile/automatic/sniper_rifle/toy/process_chamber(eject_casing = 0, empty_chamber = 1) - ..() \ No newline at end of file + ..() diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index d31231823f8..626bcaf05a8 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1,355 +1,355 @@ -/obj/item/projectile - name = "projectile" - icon = 'icons/obj/projectiles.dmi' - icon_state = "bullet" - density = 0 - resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - anchored = 1 //There's a reason this is here, Mport. God fucking damn it -Agouri. Find&Fix by Pete. The reason this is here is to stop the curving of emitter shots. - flags = ABSTRACT - pass_flags = PASSTABLE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - hitsound = 'sound/weapons/pierce.ogg' - var/hitsound_wall = "" - var/def_zone = "" //Aiming at - var/mob/firer = null//Who shot it - var/obj/item/ammo_casing/ammo_casing = null - var/suppressed = 0 //Attack message - var/yo = null - var/xo = null - var/current = null - var/atom/original = null // the original target clicked - var/turf/starting = null // the projectile's starting turf - var/list/permutated = list() // we've passed through these atoms, don't try to hit them again - var/paused = FALSE //for suspending the projectile midair - var/p_x = 16 - var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center - var/speed = 1 //Amount of deciseconds it takes for projectile to travel - var/Angle = null - var/spread = 0 //amount (in degrees) of projectile spread - var/legacy = FALSE //legacy projectile system - animate_movement = 0 - - var/ignore_source_check = FALSE - - var/damage = 10 - var/tile_dropoff = 0 //how much damage should be decremented as the bullet moves - var/tile_dropoff_s = 0 //same as above but for stamina - var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here - var/nodamage = FALSE //Determines if the projectile will skip any damage inflictions - var/flag = "bullet" //Defines what armor to use when it hits things. Must be set to bullet, laser, energy,or bomb //Cael - bio and rad are also valid - var/projectile_type = "/obj/item/projectile" - var/range = 50 //This will de-increment every step. When 0, it will delete the projectile. - var/is_reflectable = FALSE // Can it be reflected or not? - var/alwayslog = FALSE // ALWAYS log this projectile on hit even if it doesn't hit a living target. Useful for AOE explosion / EMP. - //Effects - var/stun = 0 - var/weaken = 0 - var/paralyze = 0 - var/irradiate = 0 - var/stutter = 0 - var/slur = 0 - var/eyeblur = 0 - var/drowsy = 0 - var/stamina = 0 - var/jitter = 0 - var/forcedodge = 0 //to pass through everything - var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all. - var/impact_effect_type //what type of impact effect to show when hitting something - var/ricochets = 0 - var/ricochets_max = 2 - var/ricochet_chance = 30 - - var/log_override = FALSE //whether print to admin attack logs or just keep it in the diary - -/obj/item/projectile/New() - permutated = list() - return ..() - -/obj/item/projectile/proc/Range() - range-- - if(damage && tile_dropoff) - damage = max(0, damage - tile_dropoff) // decrement projectile damage based on dropoff value for each tile it moves - if(stamina && tile_dropoff_s) - stamina = max(0, stamina - tile_dropoff_s) // as above, but with stamina - if(range <= 0 && loc) - on_range() - if(!damage && !stamina && (tile_dropoff || tile_dropoff_s)) - on_range() - -/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range - qdel(src) - -/obj/item/projectile/proc/prehit(atom/target) - return TRUE - -/obj/item/projectile/proc/on_hit(atom/target, blocked = 0, hit_zone) - var/turf/target_loca = get_turf(target) - var/hitx - var/hity - if(target == original) - hitx = target.pixel_x + p_x - 16 - hity = target.pixel_y + p_y - 16 - else - hitx = target.pixel_x + rand(-8, 8) - hity = target.pixel_y + rand(-8, 8) - if(!nodamage && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75)) - var/turf/simulated/wall/W = target_loca - if(impact_effect_type) - new impact_effect_type(target_loca, hitx, hity) - - W.add_dent(WALL_DENT_SHOT, hitx, hity) - return 0 - if(alwayslog) - add_attack_logs(firer, target, "Shot with a [type]") - if(!isliving(target)) - if(impact_effect_type) - new impact_effect_type(target_loca, hitx, hity) - return 0 - var/mob/living/L = target - var/mob/living/carbon/human/H - if(blocked < 100) // not completely blocked - if(damage && L.blood_volume && damage_type == BRUTE) - var/splatter_dir = dir - if(starting) - splatter_dir = get_dir(starting, target_loca) - if(isalien(L)) - new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir) - else - var/blood_color = "#C80000" - if(ishuman(target)) - H = target - blood_color = H.dna.species.blood_color - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, blood_color) - if(prob(33)) - var/list/shift = list("x" = 0, "y" = 0) - var/turf/step_over = get_step(target_loca, splatter_dir) - - if(get_splatter_blockage(step_over, target, splatter_dir, target_loca)) //If you can't cross the tile or any of its relevant obstacles... - shift = pixel_shift_dir(splatter_dir) //Pixel shift the blood there instead (so you can't see wallsplatter through walls). - else - target_loca = step_over - L.add_splatter_floor(target_loca, shift_x = shift["x"], shift_y = shift["y"]) - if(istype(H)) - for(var/mob/living/carbon/human/M in step_over) //Bloody the mobs who're infront of the spray. - M.bloody_hands(H) - /* Uncomment when bloody_body stops randomly not transferring blood colour. - M.bloody_body(H) */ - else if(impact_effect_type) - new impact_effect_type(target_loca, hitx, hity) - var/organ_hit_text = "" - if(L.has_limbs) - organ_hit_text = " in \the [parse_zone(def_zone)]" - if(suppressed) - playsound(loc, hitsound, 5, 1, -1) - to_chat(L, "You're shot by \a [src][organ_hit_text]!") - else - if(hitsound) - var/volume = vol_by_damage() - playsound(loc, hitsound, volume, 1, -1) - L.visible_message("[L] is hit by \a [src][organ_hit_text]!", \ - "[L] is hit by \a [src][organ_hit_text]!") //X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter - - var/reagent_note - var/has_reagents = FALSE - if(reagents && reagents.reagent_list) - reagent_note = " REAGENTS:" - for(var/datum/reagent/R in reagents.reagent_list) - reagent_note += R.id + " (" - reagent_note += num2text(R.volume) + ") " - has_reagents = TRUE - if(!log_override && firer && !alwayslog) - if(has_reagents) - add_attack_logs(firer, L, "Shot with a [type] (containing [reagent_note])") - else - add_attack_logs(firer, L, "Shot with a [type]") - return L.apply_effects(stun, weaken, paralyze, irradiate, slur, stutter, eyeblur, drowsy, blocked, stamina, jitter) - -/obj/item/projectile/proc/get_splatter_blockage(var/turf/step_over, var/atom/target, var/splatter_dir, var/target_loca) //Check whether the place we want to splatter blood is blocked (i.e. by windows). - var/turf/step_cardinal = !(splatter_dir in list(NORTH, SOUTH, EAST, WEST)) ? get_step(target_loca, get_cardinal_dir(target_loca, step_over)) : null - - if(step_over.density && !step_over.CanPass(target, step_over, 1)) //Preliminary simple check. - return TRUE - for(var/atom/movable/border_obstacle in step_over) //Check to see if we're blocked by a (non-full) window or some such. Do deeper investigation if we're splattering blood diagonally. - if(border_obstacle.flags&ON_BORDER && get_dir(step_cardinal ? step_cardinal : target_loca, step_over) == turn(border_obstacle.dir, 180)) - return TRUE - -/obj/item/projectile/proc/vol_by_damage() - if(damage) - return Clamp((damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then clamp the value between 30 and 100 - else - return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume - -/obj/item/projectile/Bump(atom/A, yes) - if(!yes) //prevents double bumps. - return - - if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max) - ricochets++ - if(A.handle_ricochet(src)) - on_ricochet(A) - ignore_source_check = TRUE - range = initial(range) - return TRUE - if(firer && !ignore_source_check) - if(A == firer || (A == firer.loc && ismecha(A))) //cannot shoot yourself or your mech - loc = A.loc - return 0 - - var/distance = get_dist(get_turf(A), starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations. - def_zone = ran_zone(def_zone, max(100-(7*distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use. - - if(isturf(A) && hitsound_wall) - var/volume = Clamp(vol_by_damage() + 20, 0, 100) - if(suppressed) - volume = 5 - playsound(loc, hitsound_wall, volume, 1, -1) - else if(ishuman(A)) - var/mob/living/carbon/human/H = A - var/obj/item/organ/external/organ = H.get_organ(check_zone(def_zone)) - if(isnull(organ)) - return - - var/turf/target_turf = get_turf(A) - prehit(A) - var/permutation = A.bullet_act(src, def_zone) // searches for return value, could be deleted after run so check A isn't null - if(permutation == -1 || forcedodge)// the bullet passes through a dense object! - loc = target_turf - if(A) - permutated.Add(A) - return 0 - else - if(A && A.density && !ismob(A) && !(A.flags & ON_BORDER)) //if we hit a dense non-border obj or dense turf then we also hit one of the mobs on that tile. - var/list/mobs_list = list() - for(var/mob/living/L in target_turf) - mobs_list += L - if(mobs_list.len) - var/mob/living/picked_mob = pick(mobs_list) - prehit(picked_mob) - picked_mob.bullet_act(src, def_zone) - qdel(src) - -/obj/item/projectile/Process_Spacemove(var/movement_dir = 0) - return 1 //Bullets don't drift in space - -/obj/item/projectile/proc/fire(var/setAngle) - if(setAngle) - Angle = setAngle - if(!legacy) //new projectiles - set waitfor = 0 - while(loc) - if(!paused) - if((!( current ) || loc == current)) - current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) - if(isnull(Angle)) - Angle=round(Get_Angle(src,current)) - if(spread) - Angle += (rand() - 0.5) * spread - var/matrix/M = new - M.Turn(Angle) - transform = M - - var/Pixel_x=round(sin(Angle)+16*sin(Angle)*2) - var/Pixel_y=round(cos(Angle)+16*cos(Angle)*2) - var/pixel_x_offset = pixel_x + Pixel_x - var/pixel_y_offset = pixel_y + Pixel_y - var/new_x = x - var/new_y = y - - while(pixel_x_offset > 16) - pixel_x_offset -= 32 - pixel_x -= 32 - new_x++// x++ - while(pixel_x_offset < -16) - pixel_x_offset += 32 - pixel_x += 32 - new_x-- - - while(pixel_y_offset > 16) - pixel_y_offset -= 32 - pixel_y -= 32 - new_y++ - while(pixel_y_offset < -16) - pixel_y_offset += 32 - pixel_y += 32 - new_y-- - - speed = round(speed) - step_towards(src, locate(new_x, new_y, z)) - if(speed <= 1) - pixel_x = pixel_x_offset - pixel_y = pixel_y_offset - else - animate(src, pixel_x = pixel_x_offset, pixel_y = pixel_y_offset, time = max(1, (speed <= 3 ? speed - 1 : speed))) - - if(original && (original.layer>=2.75) || ismob(original)) - if(loc == get_turf(original)) - if(!(original in permutated)) - Bump(original, 1) - Range() - sleep(max(1, speed)) - else //old projectile system - set waitfor = 0 - while(loc) - if(!paused) - if((!( current ) || loc == current)) - current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) - step_towards(src, current) - if(original && (original.layer>=2.75) || ismob(original)) - if(loc == get_turf(original)) - if(!(original in permutated)) - Bump(original, 1) - Range() - sleep(1) - -obj/item/projectile/proc/reflect_back(atom/source, list/position_modifiers = list(0, 0, 0, 0, 0, -1, 1, -2, 2)) - if(starting) - var/new_x = starting.x + pick(position_modifiers) - var/new_y = starting.y + pick(position_modifiers) - var/turf/curloc = get_turf(source) - - if(ismob(source)) - firer = source // The reflecting mob will be the new firer - else - firer = null // Reflected by something other than a mob so firer will be null - - // redirect the projectile - original = locate(new_x, new_y, z) - starting = curloc - current = curloc - yo = new_y - curloc.y - xo = new_x - curloc.x - Angle = null // Will be calculated in fire() - -obj/item/projectile/Crossed(atom/movable/AM, oldloc) //A mob moving on a tile with a projectile is hit by it. - ..() - if(isliving(AM) && AM.density && !checkpass(PASSMOB)) - Bump(AM, 1) - -/obj/item/projectile/Destroy() - ammo_casing = null - return ..() - -/obj/item/projectile/proc/dumbfire(var/dir) - current = get_ranged_target_turf(src, dir, world.maxx) //world.maxx is the range. Not sure how to handle this better. - fire() - - -/obj/item/projectile/proc/on_ricochet(atom/A) - return - -/obj/item/projectile/proc/check_ricochet() - if(prob(ricochet_chance)) - return TRUE - return FALSE - -/obj/item/projectile/proc/check_ricochet_flag(atom/A) - if(A.flags_2 & CHECK_RICOCHET_2) - return TRUE - return FALSE - -/obj/item/projectile/proc/setAngle(new_angle) //wrapper for overrides. - Angle = new_angle - return TRUE - -/obj/item/projectile/experience_pressure_difference() - return \ No newline at end of file +/obj/item/projectile + name = "projectile" + icon = 'icons/obj/projectiles.dmi' + icon_state = "bullet" + density = 0 + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + anchored = 1 //There's a reason this is here, Mport. God fucking damn it -Agouri. Find&Fix by Pete. The reason this is here is to stop the curving of emitter shots. + flags = ABSTRACT + pass_flags = PASSTABLE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + hitsound = 'sound/weapons/pierce.ogg' + var/hitsound_wall = "" + var/def_zone = "" //Aiming at + var/mob/firer = null//Who shot it + var/obj/item/ammo_casing/ammo_casing = null + var/suppressed = 0 //Attack message + var/yo = null + var/xo = null + var/current = null + var/atom/original = null // the original target clicked + var/turf/starting = null // the projectile's starting turf + var/list/permutated = list() // we've passed through these atoms, don't try to hit them again + var/paused = FALSE //for suspending the projectile midair + var/p_x = 16 + var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center + var/speed = 1 //Amount of deciseconds it takes for projectile to travel + var/Angle = null + var/spread = 0 //amount (in degrees) of projectile spread + var/legacy = FALSE //legacy projectile system + animate_movement = 0 + + var/ignore_source_check = FALSE + + var/damage = 10 + var/tile_dropoff = 0 //how much damage should be decremented as the bullet moves + var/tile_dropoff_s = 0 //same as above but for stamina + var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here + var/nodamage = FALSE //Determines if the projectile will skip any damage inflictions + var/flag = "bullet" //Defines what armor to use when it hits things. Must be set to bullet, laser, energy,or bomb //Cael - bio and rad are also valid + var/projectile_type = "/obj/item/projectile" + var/range = 50 //This will de-increment every step. When 0, it will delete the projectile. + var/is_reflectable = FALSE // Can it be reflected or not? + var/alwayslog = FALSE // ALWAYS log this projectile on hit even if it doesn't hit a living target. Useful for AOE explosion / EMP. + //Effects + var/stun = 0 + var/weaken = 0 + var/paralyze = 0 + var/irradiate = 0 + var/stutter = 0 + var/slur = 0 + var/eyeblur = 0 + var/drowsy = 0 + var/stamina = 0 + var/jitter = 0 + var/forcedodge = 0 //to pass through everything + var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all. + var/impact_effect_type //what type of impact effect to show when hitting something + var/ricochets = 0 + var/ricochets_max = 2 + var/ricochet_chance = 30 + + var/log_override = FALSE //whether print to admin attack logs or just keep it in the diary + +/obj/item/projectile/New() + permutated = list() + return ..() + +/obj/item/projectile/proc/Range() + range-- + if(damage && tile_dropoff) + damage = max(0, damage - tile_dropoff) // decrement projectile damage based on dropoff value for each tile it moves + if(stamina && tile_dropoff_s) + stamina = max(0, stamina - tile_dropoff_s) // as above, but with stamina + if(range <= 0 && loc) + on_range() + if(!damage && !stamina && (tile_dropoff || tile_dropoff_s)) + on_range() + +/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range + qdel(src) + +/obj/item/projectile/proc/prehit(atom/target) + return TRUE + +/obj/item/projectile/proc/on_hit(atom/target, blocked = 0, hit_zone) + var/turf/target_loca = get_turf(target) + var/hitx + var/hity + if(target == original) + hitx = target.pixel_x + p_x - 16 + hity = target.pixel_y + p_y - 16 + else + hitx = target.pixel_x + rand(-8, 8) + hity = target.pixel_y + rand(-8, 8) + if(!nodamage && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75)) + var/turf/simulated/wall/W = target_loca + if(impact_effect_type) + new impact_effect_type(target_loca, hitx, hity) + + W.add_dent(WALL_DENT_SHOT, hitx, hity) + return 0 + if(alwayslog) + add_attack_logs(firer, target, "Shot with a [type]") + if(!isliving(target)) + if(impact_effect_type) + new impact_effect_type(target_loca, hitx, hity) + return 0 + var/mob/living/L = target + var/mob/living/carbon/human/H + if(blocked < 100) // not completely blocked + if(damage && L.blood_volume && damage_type == BRUTE) + var/splatter_dir = dir + if(starting) + splatter_dir = get_dir(starting, target_loca) + if(isalien(L)) + new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir) + else + var/blood_color = "#C80000" + if(ishuman(target)) + H = target + blood_color = H.dna.species.blood_color + new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, blood_color) + if(prob(33)) + var/list/shift = list("x" = 0, "y" = 0) + var/turf/step_over = get_step(target_loca, splatter_dir) + + if(get_splatter_blockage(step_over, target, splatter_dir, target_loca)) //If you can't cross the tile or any of its relevant obstacles... + shift = pixel_shift_dir(splatter_dir) //Pixel shift the blood there instead (so you can't see wallsplatter through walls). + else + target_loca = step_over + L.add_splatter_floor(target_loca, shift_x = shift["x"], shift_y = shift["y"]) + if(istype(H)) + for(var/mob/living/carbon/human/M in step_over) //Bloody the mobs who're infront of the spray. + M.bloody_hands(H) + /* Uncomment when bloody_body stops randomly not transferring blood colour. + M.bloody_body(H) */ + else if(impact_effect_type) + new impact_effect_type(target_loca, hitx, hity) + var/organ_hit_text = "" + if(L.has_limbs) + organ_hit_text = " in \the [parse_zone(def_zone)]" + if(suppressed) + playsound(loc, hitsound, 5, 1, -1) + to_chat(L, "You're shot by \a [src][organ_hit_text]!") + else + if(hitsound) + var/volume = vol_by_damage() + playsound(loc, hitsound, volume, 1, -1) + L.visible_message("[L] is hit by \a [src][organ_hit_text]!", \ + "[L] is hit by \a [src][organ_hit_text]!") //X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter + + var/reagent_note + var/has_reagents = FALSE + if(reagents && reagents.reagent_list) + reagent_note = " REAGENTS:" + for(var/datum/reagent/R in reagents.reagent_list) + reagent_note += R.id + " (" + reagent_note += num2text(R.volume) + ") " + has_reagents = TRUE + if(!log_override && firer && !alwayslog) + if(has_reagents) + add_attack_logs(firer, L, "Shot with a [type] (containing [reagent_note])") + else + add_attack_logs(firer, L, "Shot with a [type]") + return L.apply_effects(stun, weaken, paralyze, irradiate, slur, stutter, eyeblur, drowsy, blocked, stamina, jitter) + +/obj/item/projectile/proc/get_splatter_blockage(var/turf/step_over, var/atom/target, var/splatter_dir, var/target_loca) //Check whether the place we want to splatter blood is blocked (i.e. by windows). + var/turf/step_cardinal = !(splatter_dir in list(NORTH, SOUTH, EAST, WEST)) ? get_step(target_loca, get_cardinal_dir(target_loca, step_over)) : null + + if(step_over.density && !step_over.CanPass(target, step_over, 1)) //Preliminary simple check. + return TRUE + for(var/atom/movable/border_obstacle in step_over) //Check to see if we're blocked by a (non-full) window or some such. Do deeper investigation if we're splattering blood diagonally. + if(border_obstacle.flags&ON_BORDER && get_dir(step_cardinal ? step_cardinal : target_loca, step_over) == turn(border_obstacle.dir, 180)) + return TRUE + +/obj/item/projectile/proc/vol_by_damage() + if(damage) + return Clamp((damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then clamp the value between 30 and 100 + else + return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume + +/obj/item/projectile/Bump(atom/A, yes) + if(!yes) //prevents double bumps. + return + + if(check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max) + ricochets++ + if(A.handle_ricochet(src)) + on_ricochet(A) + ignore_source_check = TRUE + range = initial(range) + return TRUE + if(firer && !ignore_source_check) + if(A == firer || (A == firer.loc && ismecha(A))) //cannot shoot yourself or your mech + loc = A.loc + return 0 + + var/distance = get_dist(get_turf(A), starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations. + def_zone = ran_zone(def_zone, max(100-(7*distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use. + + if(isturf(A) && hitsound_wall) + var/volume = Clamp(vol_by_damage() + 20, 0, 100) + if(suppressed) + volume = 5 + playsound(loc, hitsound_wall, volume, 1, -1) + else if(ishuman(A)) + var/mob/living/carbon/human/H = A + var/obj/item/organ/external/organ = H.get_organ(check_zone(def_zone)) + if(isnull(organ)) + return + + var/turf/target_turf = get_turf(A) + prehit(A) + var/permutation = A.bullet_act(src, def_zone) // searches for return value, could be deleted after run so check A isn't null + if(permutation == -1 || forcedodge)// the bullet passes through a dense object! + loc = target_turf + if(A) + permutated.Add(A) + return 0 + else + if(A && A.density && !ismob(A) && !(A.flags & ON_BORDER)) //if we hit a dense non-border obj or dense turf then we also hit one of the mobs on that tile. + var/list/mobs_list = list() + for(var/mob/living/L in target_turf) + mobs_list += L + if(mobs_list.len) + var/mob/living/picked_mob = pick(mobs_list) + prehit(picked_mob) + picked_mob.bullet_act(src, def_zone) + qdel(src) + +/obj/item/projectile/Process_Spacemove(var/movement_dir = 0) + return 1 //Bullets don't drift in space + +/obj/item/projectile/proc/fire(var/setAngle) + if(setAngle) + Angle = setAngle + if(!legacy) //new projectiles + set waitfor = 0 + while(loc) + if(!paused) + if((!( current ) || loc == current)) + current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) + if(isnull(Angle)) + Angle=round(Get_Angle(src,current)) + if(spread) + Angle += (rand() - 0.5) * spread + var/matrix/M = new + M.Turn(Angle) + transform = M + + var/Pixel_x=round(sin(Angle)+16*sin(Angle)*2) + var/Pixel_y=round(cos(Angle)+16*cos(Angle)*2) + var/pixel_x_offset = pixel_x + Pixel_x + var/pixel_y_offset = pixel_y + Pixel_y + var/new_x = x + var/new_y = y + + while(pixel_x_offset > 16) + pixel_x_offset -= 32 + pixel_x -= 32 + new_x++// x++ + while(pixel_x_offset < -16) + pixel_x_offset += 32 + pixel_x += 32 + new_x-- + + while(pixel_y_offset > 16) + pixel_y_offset -= 32 + pixel_y -= 32 + new_y++ + while(pixel_y_offset < -16) + pixel_y_offset += 32 + pixel_y += 32 + new_y-- + + speed = round(speed) + step_towards(src, locate(new_x, new_y, z)) + if(speed <= 1) + pixel_x = pixel_x_offset + pixel_y = pixel_y_offset + else + animate(src, pixel_x = pixel_x_offset, pixel_y = pixel_y_offset, time = max(1, (speed <= 3 ? speed - 1 : speed))) + + if(original && (original.layer>=2.75) || ismob(original)) + if(loc == get_turf(original)) + if(!(original in permutated)) + Bump(original, 1) + Range() + sleep(max(1, speed)) + else //old projectile system + set waitfor = 0 + while(loc) + if(!paused) + if((!( current ) || loc == current)) + current = locate(Clamp(x+xo,1,world.maxx),Clamp(y+yo,1,world.maxy),z) + step_towards(src, current) + if(original && (original.layer>=2.75) || ismob(original)) + if(loc == get_turf(original)) + if(!(original in permutated)) + Bump(original, 1) + Range() + sleep(1) + +obj/item/projectile/proc/reflect_back(atom/source, list/position_modifiers = list(0, 0, 0, 0, 0, -1, 1, -2, 2)) + if(starting) + var/new_x = starting.x + pick(position_modifiers) + var/new_y = starting.y + pick(position_modifiers) + var/turf/curloc = get_turf(source) + + if(ismob(source)) + firer = source // The reflecting mob will be the new firer + else + firer = null // Reflected by something other than a mob so firer will be null + + // redirect the projectile + original = locate(new_x, new_y, z) + starting = curloc + current = curloc + yo = new_y - curloc.y + xo = new_x - curloc.x + Angle = null // Will be calculated in fire() + +obj/item/projectile/Crossed(atom/movable/AM, oldloc) //A mob moving on a tile with a projectile is hit by it. + ..() + if(isliving(AM) && AM.density && !checkpass(PASSMOB)) + Bump(AM, 1) + +/obj/item/projectile/Destroy() + ammo_casing = null + return ..() + +/obj/item/projectile/proc/dumbfire(var/dir) + current = get_ranged_target_turf(src, dir, world.maxx) //world.maxx is the range. Not sure how to handle this better. + fire() + + +/obj/item/projectile/proc/on_ricochet(atom/A) + return + +/obj/item/projectile/proc/check_ricochet() + if(prob(ricochet_chance)) + return TRUE + return FALSE + +/obj/item/projectile/proc/check_ricochet_flag(atom/A) + if(A.flags_2 & CHECK_RICOCHET_2) + return TRUE + return FALSE + +/obj/item/projectile/proc/setAngle(new_angle) //wrapper for overrides. + Angle = new_angle + return TRUE + +/obj/item/projectile/experience_pressure_difference() + return diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 814ac3d7e46..13f959be590 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -1,171 +1,171 @@ -/obj/item/projectile/beam - name = "laser" - icon_state = "laser" - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - damage = 20 - damage_type = BURN - hitsound = 'sound/weapons/sear.ogg' - hitsound_wall = 'sound/weapons/effects/searwall.ogg' - flag = "laser" - eyeblur = 2 - impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser - is_reflectable = TRUE - light_range = 2 - light_color = LIGHT_COLOR_RED - ricochets_max = 50 //Honk! - ricochet_chance = 80 - -/obj/item/projectile/beam/laser - -/obj/item/projectile/beam/laser/heavylaser - name = "heavy laser" - icon_state = "heavylaser" - damage = 40 - -/obj/item/projectile/beam/practice - name = "practice laser" - damage = 0 - nodamage = 1 - log_override = TRUE - -/obj/item/projectile/beam/scatter - name = "laser pellet" - icon_state = "scatterlaser" - damage = 5 - -/obj/item/projectile/beam/xray - name = "xray beam" - icon_state = "xray" - damage = 15 - tile_dropoff = 0.75 - irradiate = 30 - forcedodge = 1 - range = 15 - impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser - light_color = LIGHT_COLOR_GREEN - -/obj/item/projectile/beam/disabler - name = "disabler beam" - icon_state = "omnilaser" - damage = 30 - damage_type = STAMINA - flag = "energy" - hitsound = 'sound/weapons/tap.ogg' - eyeblur = 0 - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - light_color = LIGHT_COLOR_CYAN - -/obj/item/projectile/beam/pulse - name = "pulse" - icon_state = "u_laser" - damage = 50 - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - light_color = LIGHT_COLOR_DARKBLUE - -/obj/item/projectile/beam/pulse/on_hit(var/atom/target, var/blocked = 0) - if(istype(target,/turf/)||istype(target,/obj/structure/)) - target.ex_act(2) - ..() - -/obj/item/projectile/beam/pulse/shot - damage = 40 - -/obj/item/projectile/beam/emitter - name = "emitter beam" - icon_state = "emitter" - damage = 30 - impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser - light_color = LIGHT_COLOR_GREEN - -/obj/item/projectile/beam/emitter/singularity_pull() - return //don't want the emitters to miss - -/obj/item/projectile/beam/lasertag - name = "laser tag beam" - icon_state = "omnilaser" - hitsound = 'sound/weapons/tap.ogg' - nodamage = 1 - damage_type = STAMINA - flag = "laser" - var/suit_types = list(/obj/item/clothing/suit/redtag, /obj/item/clothing/suit/bluetag) - log_override = TRUE - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - light_color = LIGHT_COLOR_DARKBLUE - -/obj/item/projectile/beam/lasertag/on_hit(atom/target, blocked = 0) - . = ..() - if(ishuman(target)) - var/mob/living/carbon/human/M = target - if(istype(M.wear_suit)) - if(M.wear_suit.type in suit_types) - M.adjustStaminaLoss(34) - return 1 - -/obj/item/projectile/beam/lasertag/omni - name = "laser tag beam" - icon_state = "omnilaser" - -/obj/item/projectile/beam/lasertag/redtag - icon_state = "laser" - suit_types = list(/obj/item/clothing/suit/bluetag) - impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser - light_color = LIGHT_COLOR_RED - -/obj/item/projectile/beam/lasertag/bluetag - icon_state = "bluelaser" - suit_types = list(/obj/item/clothing/suit/redtag) - -/obj/item/projectile/beam/sniper - name = "sniper beam" - icon_state = "sniperlaser" - damage = 60 - stun = 5 - weaken = 5 - stutter = 5 - impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser - light_color = LIGHT_COLOR_PINK - -/obj/item/projectile/beam/immolator - name = "immolation beam" - -/obj/item/projectile/beam/immolator/strong - name = "heavy immolation beam" - damage = 45 - icon_state = "heavylaser" - -/obj/item/projectile/beam/immolator/weak - name = "light immolation beam" - damage = 8 - icon_state = "scatterlaser" - -/obj/item/projectile/beam/immolator/on_hit(var/atom/target, var/blocked = 0) - . = ..() - if(istype(target, /mob/living/carbon)) - var/mob/living/carbon/M = target - M.adjust_fire_stacks(1) - M.IgniteMob() - -/obj/item/projectile/beam/instakill - name = "instagib laser" - icon_state = "purple_laser" - damage = 200 - damage_type = BURN - impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser - light_color = LIGHT_COLOR_PURPLE - -/obj/item/projectile/beam/instakill/blue - icon_state = "blue_laser" - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - light_color = LIGHT_COLOR_DARKBLUE - -/obj/item/projectile/beam/instakill/red - icon_state = "red_laser" - impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser - light_color = LIGHT_COLOR_RED - -/obj/item/projectile/beam/instakill/on_hit(atom/target) - . = ..() - if(isliving(target)) - var/mob/living/L = target - L.visible_message("[L] explodes!") - L.gib() \ No newline at end of file +/obj/item/projectile/beam + name = "laser" + icon_state = "laser" + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + damage = 20 + damage_type = BURN + hitsound = 'sound/weapons/sear.ogg' + hitsound_wall = 'sound/weapons/effects/searwall.ogg' + flag = "laser" + eyeblur = 2 + impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser + is_reflectable = TRUE + light_range = 2 + light_color = LIGHT_COLOR_RED + ricochets_max = 50 //Honk! + ricochet_chance = 80 + +/obj/item/projectile/beam/laser + +/obj/item/projectile/beam/laser/heavylaser + name = "heavy laser" + icon_state = "heavylaser" + damage = 40 + +/obj/item/projectile/beam/practice + name = "practice laser" + damage = 0 + nodamage = 1 + log_override = TRUE + +/obj/item/projectile/beam/scatter + name = "laser pellet" + icon_state = "scatterlaser" + damage = 5 + +/obj/item/projectile/beam/xray + name = "xray beam" + icon_state = "xray" + damage = 15 + tile_dropoff = 0.75 + irradiate = 30 + forcedodge = 1 + range = 15 + impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser + light_color = LIGHT_COLOR_GREEN + +/obj/item/projectile/beam/disabler + name = "disabler beam" + icon_state = "omnilaser" + damage = 30 + damage_type = STAMINA + flag = "energy" + hitsound = 'sound/weapons/tap.ogg' + eyeblur = 0 + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_CYAN + +/obj/item/projectile/beam/pulse + name = "pulse" + icon_state = "u_laser" + damage = 50 + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_DARKBLUE + +/obj/item/projectile/beam/pulse/on_hit(var/atom/target, var/blocked = 0) + if(istype(target,/turf/)||istype(target,/obj/structure/)) + target.ex_act(2) + ..() + +/obj/item/projectile/beam/pulse/shot + damage = 40 + +/obj/item/projectile/beam/emitter + name = "emitter beam" + icon_state = "emitter" + damage = 30 + impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser + light_color = LIGHT_COLOR_GREEN + +/obj/item/projectile/beam/emitter/singularity_pull() + return //don't want the emitters to miss + +/obj/item/projectile/beam/lasertag + name = "laser tag beam" + icon_state = "omnilaser" + hitsound = 'sound/weapons/tap.ogg' + nodamage = 1 + damage_type = STAMINA + flag = "laser" + var/suit_types = list(/obj/item/clothing/suit/redtag, /obj/item/clothing/suit/bluetag) + log_override = TRUE + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_DARKBLUE + +/obj/item/projectile/beam/lasertag/on_hit(atom/target, blocked = 0) + . = ..() + if(ishuman(target)) + var/mob/living/carbon/human/M = target + if(istype(M.wear_suit)) + if(M.wear_suit.type in suit_types) + M.adjustStaminaLoss(34) + return 1 + +/obj/item/projectile/beam/lasertag/omni + name = "laser tag beam" + icon_state = "omnilaser" + +/obj/item/projectile/beam/lasertag/redtag + icon_state = "laser" + suit_types = list(/obj/item/clothing/suit/bluetag) + impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser + light_color = LIGHT_COLOR_RED + +/obj/item/projectile/beam/lasertag/bluetag + icon_state = "bluelaser" + suit_types = list(/obj/item/clothing/suit/redtag) + +/obj/item/projectile/beam/sniper + name = "sniper beam" + icon_state = "sniperlaser" + damage = 60 + stun = 5 + weaken = 5 + stutter = 5 + impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser + light_color = LIGHT_COLOR_PINK + +/obj/item/projectile/beam/immolator + name = "immolation beam" + +/obj/item/projectile/beam/immolator/strong + name = "heavy immolation beam" + damage = 45 + icon_state = "heavylaser" + +/obj/item/projectile/beam/immolator/weak + name = "light immolation beam" + damage = 8 + icon_state = "scatterlaser" + +/obj/item/projectile/beam/immolator/on_hit(var/atom/target, var/blocked = 0) + . = ..() + if(istype(target, /mob/living/carbon)) + var/mob/living/carbon/M = target + M.adjust_fire_stacks(1) + M.IgniteMob() + +/obj/item/projectile/beam/instakill + name = "instagib laser" + icon_state = "purple_laser" + damage = 200 + damage_type = BURN + impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser + light_color = LIGHT_COLOR_PURPLE + +/obj/item/projectile/beam/instakill/blue + icon_state = "blue_laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_DARKBLUE + +/obj/item/projectile/beam/instakill/red + icon_state = "red_laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser + light_color = LIGHT_COLOR_RED + +/obj/item/projectile/beam/instakill/on_hit(atom/target) + . = ..() + if(isliving(target)) + var/mob/living/L = target + L.visible_message("[L] explodes!") + L.gib() diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index 6303178217c..2323aa06cf4 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -1,300 +1,300 @@ -/obj/item/projectile/bullet - name = "bullet" - icon_state = "bullet" - damage = 60 - damage_type = BRUTE - flag = "bullet" - hitsound_wall = "ricochet" - impact_effect_type = /obj/effect/temp_visual/impact_effect - -/obj/item/projectile/bullet/weakbullet //beanbag, heavy stamina damage - name = "beanbag slug" - damage = 5 - stamina = 80 - -/obj/item/projectile/bullet/weakbullet/booze - -/obj/item/projectile/bullet/weakbullet/booze/on_hit(atom/target, blocked = 0) - if(..(target, blocked)) - var/mob/living/M = target - M.AdjustDizzy(20) - M.AdjustSlur(20) - M.AdjustConfused(20) - M.AdjustEyeBlurry(20) - M.AdjustDrowsy(20) - for(var/datum/reagent/consumable/ethanol/A in M.reagents.reagent_list) - M.AdjustParalysis(2) - M.AdjustDizzy(10) - M.AdjustSlur(10) - M.AdjustConfused(10) - M.AdjustEyeBlurry(10) - M.AdjustDrowsy(10) - A.volume += 5 //Because we can - -/obj/item/projectile/bullet/weakbullet2 //detective revolver instastuns, but multiple shots are better for keeping punks down - name = "rubber bullet" - damage = 5 - weaken = 3 - stamina = 60 - icon_state = "bullet-r" - -/obj/item/projectile/bullet/weakbullet2/invisible //finger gun bullets - name = "invisible bullet" - damage = 0 - icon_state = null - hitsound_wall = null - -/obj/item/projectile/bullet/weakbullet2/invisible/fake - weaken = 0 - stamina = 0 - nodamage = 1 - log_override = TRUE - -/obj/item/projectile/bullet/weakbullet3 - damage = 20 - -/obj/item/projectile/bullet/weakbullet4 - name = "rubber bullet" - damage = 5 - stamina = 30 - icon_state = "bullet-r" - -/obj/item/projectile/bullet/toxinbullet - damage = 15 - damage_type = TOX - -/obj/item/projectile/bullet/incendiary - -/obj/item/projectile/bullet/incendiary/on_hit(var/atom/target, var/blocked = 0) - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/M = target - M.adjust_fire_stacks(4) - M.IgniteMob() - -/obj/item/projectile/bullet/incendiary/firebullet - damage = 10 - -/obj/item/projectile/bullet/armourpiercing - damage = 17 - armour_penetration = 10 - -/obj/item/projectile/bullet/pellet - name = "pellet" - damage = 12.5 - tile_dropoff = 0.75 - tile_dropoff_s = 1.25 - -/obj/item/projectile/bullet/pellet/rubber - name = "rubber pellet" - damage = 3 - stamina = 25 - icon_state = "bullet-r" - -/obj/item/projectile/bullet/pellet/weak - tile_dropoff = 0.55 //Come on it does 6 damage don't be like that. - damage = 6 - -/obj/item/projectile/bullet/pellet/weak/New() - range = rand(1, 8) - ..() - -/obj/item/projectile/bullet/pellet/weak/on_range() - do_sparks(1, 1, src) - ..() - -/obj/item/projectile/bullet/pellet/overload - damage = 3 - -/obj/item/projectile/bullet/pellet/overload/New() - range = rand(1, 10) - ..() - -/obj/item/projectile/bullet/pellet/assassination - damage = 12 - tile_dropoff = 1 // slightly less damage and greater damage falloff compared to normal buckshot - -/obj/item/projectile/bullet/pellet/assassination/on_hit(atom/target, blocked = 0) - if(..(target, blocked)) - var/mob/living/M = target - M.AdjustSilence(2) // HELP MIME KILLING ME IN MAINT - -/obj/item/projectile/bullet/pellet/overload/on_hit(atom/target, blocked = 0) - ..() - explosion(target, 0, 0, 2) - -/obj/item/projectile/bullet/pellet/overload/on_range() - explosion(src, 0, 0, 2) - do_sparks(3, 3, src) - ..() - -/obj/item/projectile/bullet/midbullet - damage = 20 - stamina = 65 //two rounds from the c20r knocks people down - -/obj/item/projectile/bullet/midbullet_r - damage = 5 - stamina = 75 //Still two rounds to knock people down - -/obj/item/projectile/bullet/midbullet2 - damage = 25 - -/obj/item/projectile/bullet/midbullet3 - damage = 30 - -/obj/item/projectile/bullet/midbullet3/hp - damage = 40 - armour_penetration = -50 - -/obj/item/projectile/bullet/midbullet3/ap - damage = 27 - armour_penetration = 40 - -/obj/item/projectile/bullet/midbullet3/fire/on_hit(atom/target, blocked = 0) - if(..(target, blocked)) - var/mob/living/M = target - M.adjust_fire_stacks(1) - M.IgniteMob() - -/obj/item/projectile/bullet/heavybullet - damage = 35 - -/obj/item/projectile/bullet/stunshot//taser slugs for shotguns, nothing special - name = "stunshot" - damage = 5 - stun = 5 - weaken = 5 - stutter = 5 - jitter = 20 - range = 7 - icon_state = "spark" - color = "#FFFF00" - -/obj/item/projectile/bullet/incendiary/shell - name = "incendiary slug" - damage = 20 - -/obj/item/projectile/bullet/incendiary/shell/Move() - ..() - var/turf/location = get_turf(src) - if(location) - new /obj/effect/hotspot(location) - location.hotspot_expose(700, 50, 1) - -/obj/item/projectile/bullet/incendiary/shell/dragonsbreath - name = "dragonsbreath round" - damage = 5 - -/obj/item/projectile/bullet/meteorshot - name = "meteor" - icon = 'icons/obj/meteor.dmi' - icon_state = "dust" - damage = 30 - weaken = 8 - stun = 8 - hitsound = 'sound/effects/meteorimpact.ogg' - -/obj/item/projectile/bullet/meteorshot/on_hit(var/atom/target, var/blocked = 0) - ..() - if(istype(target, /atom/movable)) - var/atom/movable/M = target - var/atom/throw_target = get_edge_target_turf(M, get_dir(src, get_step_away(M, src))) - M.throw_at(throw_target, 3, 2) - -/obj/item/projectile/bullet/meteorshot/New() - ..() - SpinAnimation() - -/obj/item/projectile/bullet/meteorshot/weak - damage = 10 - weaken = 4 - stun = 4 - -/obj/item/projectile/bullet/mime - damage = 0 - stun = 5 - weaken = 5 - slur = 20 - stutter = 20 - -/obj/item/projectile/bullet/mime/on_hit(var/atom/target, var/blocked = 0) - ..(target, blocked) - if(iscarbon(target)) - var/mob/living/carbon/M = target - M.Silence(10) - else if(istype(target, /obj/mecha/combat/honker)) - var/obj/mecha/chassis = target - chassis.occupant_message("A mimetech anti-honk bullet has hit \the [chassis]!") - chassis.use_power(chassis.get_charge() / 2) - for(var/obj/item/mecha_parts/mecha_equipment/weapon/honker in chassis.equipment) - honker.set_ready_state(0) - -/obj/item/projectile/bullet/dart - name = "dart" - icon_state = "cbbolt" - damage = 6 - var/piercing = FALSE - -/obj/item/projectile/bullet/dart/New() - ..() - create_reagents(50) - reagents.set_reacting(FALSE) - -/obj/item/projectile/bullet/dart/on_hit(var/atom/target, var/blocked = 0, var/hit_zone) - if(iscarbon(target)) - var/mob/living/carbon/M = target - if(blocked != 100) - if(M.can_inject(null, FALSE, hit_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body. - ..() - reagents.trans_to(M, reagents.total_volume) - return 1 - else - blocked = 100 - target.visible_message("The [name] was deflected!", \ - "You were protected against the [name]!") - ..(target, blocked, hit_zone) - reagents.set_reacting(TRUE) - reagents.handle_reactions() - return 1 - -/obj/item/projectile/bullet/dart/metalfoam - -/obj/item/projectile/bullet/dart/metalfoam/New() - ..() - reagents.add_reagent("aluminum", 15) - reagents.add_reagent("fluorosurfactant", 5) - reagents.add_reagent("sacid", 5) - -//This one is for future syringe guns update -/obj/item/projectile/bullet/dart/syringe - name = "syringe" - icon = 'icons/obj/chemical.dmi' - icon_state = "syringeproj" - -/obj/item/projectile/bullet/dart/syringe/tranquilizer - -/obj/item/projectile/bullet/dart/syringe/tranquilizer/New() - ..() - reagents.add_reagent("haloperidol", 15) - -/obj/item/projectile/bullet/neurotoxin - name = "neurotoxin spit" - icon_state = "neurotoxin" - damage = 5 - damage_type = TOX - weaken = 5 - -/obj/item/projectile/bullet/neurotoxin/on_hit(var/atom/target, var/blocked = 0) - if(isalien(target)) - weaken = 0 - nodamage = 1 - . = ..() // Execute the rest of the code. - -/obj/item/projectile/bullet/cap - name = "cap" - damage = 0 - nodamage = 1 - -/obj/item/projectile/bullet/cap/fire() - loc = null - qdel(src) +/obj/item/projectile/bullet + name = "bullet" + icon_state = "bullet" + damage = 60 + damage_type = BRUTE + flag = "bullet" + hitsound_wall = "ricochet" + impact_effect_type = /obj/effect/temp_visual/impact_effect + +/obj/item/projectile/bullet/weakbullet //beanbag, heavy stamina damage + name = "beanbag slug" + damage = 5 + stamina = 80 + +/obj/item/projectile/bullet/weakbullet/booze + +/obj/item/projectile/bullet/weakbullet/booze/on_hit(atom/target, blocked = 0) + if(..(target, blocked)) + var/mob/living/M = target + M.AdjustDizzy(20) + M.AdjustSlur(20) + M.AdjustConfused(20) + M.AdjustEyeBlurry(20) + M.AdjustDrowsy(20) + for(var/datum/reagent/consumable/ethanol/A in M.reagents.reagent_list) + M.AdjustParalysis(2) + M.AdjustDizzy(10) + M.AdjustSlur(10) + M.AdjustConfused(10) + M.AdjustEyeBlurry(10) + M.AdjustDrowsy(10) + A.volume += 5 //Because we can + +/obj/item/projectile/bullet/weakbullet2 //detective revolver instastuns, but multiple shots are better for keeping punks down + name = "rubber bullet" + damage = 5 + weaken = 3 + stamina = 60 + icon_state = "bullet-r" + +/obj/item/projectile/bullet/weakbullet2/invisible //finger gun bullets + name = "invisible bullet" + damage = 0 + icon_state = null + hitsound_wall = null + +/obj/item/projectile/bullet/weakbullet2/invisible/fake + weaken = 0 + stamina = 0 + nodamage = 1 + log_override = TRUE + +/obj/item/projectile/bullet/weakbullet3 + damage = 20 + +/obj/item/projectile/bullet/weakbullet4 + name = "rubber bullet" + damage = 5 + stamina = 30 + icon_state = "bullet-r" + +/obj/item/projectile/bullet/toxinbullet + damage = 15 + damage_type = TOX + +/obj/item/projectile/bullet/incendiary + +/obj/item/projectile/bullet/incendiary/on_hit(var/atom/target, var/blocked = 0) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.adjust_fire_stacks(4) + M.IgniteMob() + +/obj/item/projectile/bullet/incendiary/firebullet + damage = 10 + +/obj/item/projectile/bullet/armourpiercing + damage = 17 + armour_penetration = 10 + +/obj/item/projectile/bullet/pellet + name = "pellet" + damage = 12.5 + tile_dropoff = 0.75 + tile_dropoff_s = 1.25 + +/obj/item/projectile/bullet/pellet/rubber + name = "rubber pellet" + damage = 3 + stamina = 25 + icon_state = "bullet-r" + +/obj/item/projectile/bullet/pellet/weak + tile_dropoff = 0.55 //Come on it does 6 damage don't be like that. + damage = 6 + +/obj/item/projectile/bullet/pellet/weak/New() + range = rand(1, 8) + ..() + +/obj/item/projectile/bullet/pellet/weak/on_range() + do_sparks(1, 1, src) + ..() + +/obj/item/projectile/bullet/pellet/overload + damage = 3 + +/obj/item/projectile/bullet/pellet/overload/New() + range = rand(1, 10) + ..() + +/obj/item/projectile/bullet/pellet/assassination + damage = 12 + tile_dropoff = 1 // slightly less damage and greater damage falloff compared to normal buckshot + +/obj/item/projectile/bullet/pellet/assassination/on_hit(atom/target, blocked = 0) + if(..(target, blocked)) + var/mob/living/M = target + M.AdjustSilence(2) // HELP MIME KILLING ME IN MAINT + +/obj/item/projectile/bullet/pellet/overload/on_hit(atom/target, blocked = 0) + ..() + explosion(target, 0, 0, 2) + +/obj/item/projectile/bullet/pellet/overload/on_range() + explosion(src, 0, 0, 2) + do_sparks(3, 3, src) + ..() + +/obj/item/projectile/bullet/midbullet + damage = 20 + stamina = 65 //two rounds from the c20r knocks people down + +/obj/item/projectile/bullet/midbullet_r + damage = 5 + stamina = 75 //Still two rounds to knock people down + +/obj/item/projectile/bullet/midbullet2 + damage = 25 + +/obj/item/projectile/bullet/midbullet3 + damage = 30 + +/obj/item/projectile/bullet/midbullet3/hp + damage = 40 + armour_penetration = -50 + +/obj/item/projectile/bullet/midbullet3/ap + damage = 27 + armour_penetration = 40 + +/obj/item/projectile/bullet/midbullet3/fire/on_hit(atom/target, blocked = 0) + if(..(target, blocked)) + var/mob/living/M = target + M.adjust_fire_stacks(1) + M.IgniteMob() + +/obj/item/projectile/bullet/heavybullet + damage = 35 + +/obj/item/projectile/bullet/stunshot//taser slugs for shotguns, nothing special + name = "stunshot" + damage = 5 + stun = 5 + weaken = 5 + stutter = 5 + jitter = 20 + range = 7 + icon_state = "spark" + color = "#FFFF00" + +/obj/item/projectile/bullet/incendiary/shell + name = "incendiary slug" + damage = 20 + +/obj/item/projectile/bullet/incendiary/shell/Move() + ..() + var/turf/location = get_turf(src) + if(location) + new /obj/effect/hotspot(location) + location.hotspot_expose(700, 50, 1) + +/obj/item/projectile/bullet/incendiary/shell/dragonsbreath + name = "dragonsbreath round" + damage = 5 + +/obj/item/projectile/bullet/meteorshot + name = "meteor" + icon = 'icons/obj/meteor.dmi' + icon_state = "dust" + damage = 30 + weaken = 8 + stun = 8 + hitsound = 'sound/effects/meteorimpact.ogg' + +/obj/item/projectile/bullet/meteorshot/on_hit(var/atom/target, var/blocked = 0) + ..() + if(istype(target, /atom/movable)) + var/atom/movable/M = target + var/atom/throw_target = get_edge_target_turf(M, get_dir(src, get_step_away(M, src))) + M.throw_at(throw_target, 3, 2) + +/obj/item/projectile/bullet/meteorshot/New() + ..() + SpinAnimation() + +/obj/item/projectile/bullet/meteorshot/weak + damage = 10 + weaken = 4 + stun = 4 + +/obj/item/projectile/bullet/mime + damage = 0 + stun = 5 + weaken = 5 + slur = 20 + stutter = 20 + +/obj/item/projectile/bullet/mime/on_hit(var/atom/target, var/blocked = 0) + ..(target, blocked) + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.Silence(10) + else if(istype(target, /obj/mecha/combat/honker)) + var/obj/mecha/chassis = target + chassis.occupant_message("A mimetech anti-honk bullet has hit \the [chassis]!") + chassis.use_power(chassis.get_charge() / 2) + for(var/obj/item/mecha_parts/mecha_equipment/weapon/honker in chassis.equipment) + honker.set_ready_state(0) + +/obj/item/projectile/bullet/dart + name = "dart" + icon_state = "cbbolt" + damage = 6 + var/piercing = FALSE + +/obj/item/projectile/bullet/dart/New() + ..() + create_reagents(50) + reagents.set_reacting(FALSE) + +/obj/item/projectile/bullet/dart/on_hit(var/atom/target, var/blocked = 0, var/hit_zone) + if(iscarbon(target)) + var/mob/living/carbon/M = target + if(blocked != 100) + if(M.can_inject(null, FALSE, hit_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body. + ..() + reagents.trans_to(M, reagents.total_volume) + return 1 + else + blocked = 100 + target.visible_message("The [name] was deflected!", \ + "You were protected against the [name]!") + ..(target, blocked, hit_zone) + reagents.set_reacting(TRUE) + reagents.handle_reactions() + return 1 + +/obj/item/projectile/bullet/dart/metalfoam + +/obj/item/projectile/bullet/dart/metalfoam/New() + ..() + reagents.add_reagent("aluminum", 15) + reagents.add_reagent("fluorosurfactant", 5) + reagents.add_reagent("sacid", 5) + +//This one is for future syringe guns update +/obj/item/projectile/bullet/dart/syringe + name = "syringe" + icon = 'icons/obj/chemical.dmi' + icon_state = "syringeproj" + +/obj/item/projectile/bullet/dart/syringe/tranquilizer + +/obj/item/projectile/bullet/dart/syringe/tranquilizer/New() + ..() + reagents.add_reagent("haloperidol", 15) + +/obj/item/projectile/bullet/neurotoxin + name = "neurotoxin spit" + icon_state = "neurotoxin" + damage = 5 + damage_type = TOX + weaken = 5 + +/obj/item/projectile/bullet/neurotoxin/on_hit(var/atom/target, var/blocked = 0) + if(isalien(target)) + weaken = 0 + nodamage = 1 + . = ..() // Execute the rest of the code. + +/obj/item/projectile/bullet/cap + name = "cap" + damage = 0 + nodamage = 1 + +/obj/item/projectile/bullet/cap/fire() + loc = null + qdel(src) diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm index e1ca470c95a..1781e1970eb 100644 --- a/code/modules/projectiles/projectile/energy.dm +++ b/code/modules/projectiles/projectile/energy.dm @@ -1,97 +1,97 @@ -/obj/item/projectile/energy - name = "energy" - icon_state = "spark" - damage = 0 - damage_type = BURN - flag = "energy" - is_reflectable = TRUE - -/obj/item/projectile/energy/electrode - name = "electrode" - icon_state = "spark" - color = "#FFFF00" - nodamage = 1 - stun = 5 - weaken = 5 - stutter = 5 - jitter = 20 - hitsound = 'sound/weapons/tase.ogg' - range = 7 - //Damage will be handled on the MOB side, to prevent window shattering. - -/obj/item/projectile/energy/electrode/on_hit(var/atom/target, var/blocked = 0) - . = ..() - if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks! - do_sparks(1, 1, src) - else if(iscarbon(target)) - var/mob/living/carbon/C = target - if(HULK in C.mutations) - C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" )) - else if(C.status_flags & CANWEAKEN) - spawn(5) - C.do_jitter_animation(jitter) - -/obj/item/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet - do_sparks(1, 1, src) - ..() - -/obj/item/projectile/energy/declone - name = "declone" - icon_state = "declone" - damage = 20 - damage_type = CLONE - irradiate = 10 - impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser - -/obj/item/projectile/energy/dart - name = "dart" - icon_state = "toxin" - damage = 5 - damage_type = TOX - weaken = 5 - range = 7 - -/obj/item/projectile/energy/shuriken - name = "shuriken" - icon_state = "toxin" - damage = 10 - damage_type = TOX - weaken = 5 - stutter = 5 - -/obj/item/projectile/energy/bolt - name = "bolt" - icon_state = "cbbolt" - damage = 15 - damage_type = TOX - nodamage = 0 - weaken = 5 - stutter = 5 - -/obj/item/projectile/energy/bolt/large - damage = 20 - -/obj/item/projectile/energy/shock_revolver - name = "shock bolt" - icon_state = "purple_laser" - impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser - var/chain - -/obj/item/ammo_casing/energy/shock_revolver/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") - ..() - var/obj/item/projectile/energy/shock_revolver/P = BB - spawn(1) - P.chain = P.Beam(user,icon_state="purple_lightning",icon = 'icons/effects/effects.dmi',time=1000, maxdistance = 30) - -/obj/item/projectile/energy/shock_revolver/on_hit(atom/target) - . = ..() - if(isliving(target)) - tesla_zap(src, 3, 10000) - qdel(chain) - -/obj/item/projectile/energy/toxplasma - name = "plasma bolt" - icon_state = "energy" - damage = 20 - damage_type = TOX - irradiate = 20 +/obj/item/projectile/energy + name = "energy" + icon_state = "spark" + damage = 0 + damage_type = BURN + flag = "energy" + is_reflectable = TRUE + +/obj/item/projectile/energy/electrode + name = "electrode" + icon_state = "spark" + color = "#FFFF00" + nodamage = 1 + stun = 5 + weaken = 5 + stutter = 5 + jitter = 20 + hitsound = 'sound/weapons/tase.ogg' + range = 7 + //Damage will be handled on the MOB side, to prevent window shattering. + +/obj/item/projectile/energy/electrode/on_hit(var/atom/target, var/blocked = 0) + . = ..() + if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks! + do_sparks(1, 1, src) + else if(iscarbon(target)) + var/mob/living/carbon/C = target + if(HULK in C.mutations) + C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" )) + else if(C.status_flags & CANWEAKEN) + spawn(5) + C.do_jitter_animation(jitter) + +/obj/item/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet + do_sparks(1, 1, src) + ..() + +/obj/item/projectile/energy/declone + name = "declone" + icon_state = "declone" + damage = 20 + damage_type = CLONE + irradiate = 10 + impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser + +/obj/item/projectile/energy/dart + name = "dart" + icon_state = "toxin" + damage = 5 + damage_type = TOX + weaken = 5 + range = 7 + +/obj/item/projectile/energy/shuriken + name = "shuriken" + icon_state = "toxin" + damage = 10 + damage_type = TOX + weaken = 5 + stutter = 5 + +/obj/item/projectile/energy/bolt + name = "bolt" + icon_state = "cbbolt" + damage = 15 + damage_type = TOX + nodamage = 0 + weaken = 5 + stutter = 5 + +/obj/item/projectile/energy/bolt/large + damage = 20 + +/obj/item/projectile/energy/shock_revolver + name = "shock bolt" + icon_state = "purple_laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser + var/chain + +/obj/item/ammo_casing/energy/shock_revolver/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") + ..() + var/obj/item/projectile/energy/shock_revolver/P = BB + spawn(1) + P.chain = P.Beam(user,icon_state="purple_lightning",icon = 'icons/effects/effects.dmi',time=1000, maxdistance = 30) + +/obj/item/projectile/energy/shock_revolver/on_hit(atom/target) + . = ..() + if(isliving(target)) + tesla_zap(src, 3, 10000) + qdel(chain) + +/obj/item/projectile/energy/toxplasma + name = "plasma bolt" + icon_state = "energy" + damage = 20 + damage_type = TOX + irradiate = 20 diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 76d7c513bcc..01503fb16b7 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -344,4 +344,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/projectiles/projectile/reusable.dm b/code/modules/projectiles/projectile/reusable.dm index bd249037946..244100c3120 100644 --- a/code/modules/projectiles/projectile/reusable.dm +++ b/code/modules/projectiles/projectile/reusable.dm @@ -77,4 +77,4 @@ icon_state = "foamdartsniper_riot" ammo_type = /obj/item/ammo_casing/caseless/foam_dart/sniper/riot stamina = 100 - log_override = FALSE \ No newline at end of file + log_override = FALSE diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/special.dm index 06ab5c9a61b..1f82155b7ee 100644 --- a/code/modules/projectiles/projectile/special.dm +++ b/code/modules/projectiles/projectile/special.dm @@ -1,348 +1,348 @@ -/obj/item/projectile/ion - name = "ion bolt" - icon_state = "ion" - damage = 0 - alwayslog = TRUE - damage_type = BURN - nodamage = 1 - impact_effect_type = /obj/effect/temp_visual/impact_effect/ion - flag = "energy" - -/obj/item/projectile/ion/on_hit(var/atom/target, var/blocked = 0) - ..() - empulse(target, 1, 1, 1, cause = "[type] fired by [key_name(firer)]") - return 1 - -/obj/item/projectile/ion/weak - -/obj/item/projectile/ion/weak/on_hit(atom/target, blocked = 0) - ..() - empulse(target, 0, 0, 1, cause = "[type] fired by [key_name(firer)]") - return 1 - -/obj/item/projectile/bullet/gyro - name ="explosive bolt" - icon_state= "bolter" - damage = 50 - alwayslog = TRUE - flag = "bullet" - -/obj/item/projectile/bullet/gyro/on_hit(var/atom/target, var/blocked = 0) - ..() - explosion(target, -1, 0, 2, cause = "[type] fired by [key_name(firer)]") - return 1 - -/obj/item/projectile/bullet/a40mm - name ="40mm grenade" - desc = "USE A WEEL GUN" - icon_state= "bolter" - alwayslog = TRUE - damage = 60 - flag = "bullet" - -/obj/item/projectile/bullet/a40mm/on_hit(atom/target, blocked = 0) - ..() - explosion(target, -1, 0, 2, 1, 0, flame_range = 3, cause = "[type] fired by [key_name(firer)]") - return 1 - -/obj/item/projectile/temp - name = "temperature beam" - icon_state = "temp_4" - damage = 0 - damage_type = BURN - nodamage = 1 - flag = "energy" - var/temperature = 300 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - -/obj/item/projectile/temp/New(loc, shot_temp) - ..() - if(!isnull(shot_temp)) - temperature = shot_temp - switch(temperature) - if(501 to INFINITY) - name = "searing beam" //if emagged - icon_state = "temp_8" - if(400 to 500) - name = "burning beam" //temp at which mobs start taking HEAT_DAMAGE_LEVEL_2 - icon_state = "temp_7" - if(360 to 400) - name = "hot beam" //temp at which mobs start taking HEAT_DAMAGE_LEVEL_1 - icon_state = "temp_6" - if(335 to 360) - name = "warm beam" //temp at which players get notified of their high body temp - icon_state = "temp_5" - if(295 to 335) - name = "ambient beam" - icon_state = "temp_4" - if(260 to 295) - name = "cool beam" //temp at which players get notified of their low body temp - icon_state = "temp_3" - if(200 to 260) - name = "cold beam" //temp at which mobs start taking COLD_DAMAGE_LEVEL_1 - icon_state = "temp_2" - if(120 to 260) - name = "ice beam" //temp at which mobs start taking COLD_DAMAGE_LEVEL_2 - icon_state = "temp_1" - if(-INFINITY to 120) - name = "freeze beam" //temp at which mobs start taking COLD_DAMAGE_LEVEL_3 - icon_state = "temp_0" - else - name = "temperature beam"//failsafe - icon_state = "temp_4" - - -/obj/item/projectile/temp/on_hit(var/atom/target, var/blocked = 0)//These two could likely check temp protection on the mob - ..() - if(isliving(target)) - var/mob/living/M = target - M.bodytemperature = temperature - if(temperature > 500)//emagged - M.adjust_fire_stacks(0.5) - M.IgniteMob() - playsound(M.loc, 'sound/effects/bamf.ogg', 50, 0) - return 1 - -/obj/item/projectile/meteor - name = "meteor" - icon = 'icons/obj/meteor.dmi' - icon_state = "small" - damage = 0 - damage_type = BRUTE - nodamage = 1 - flag = "bullet" - -/obj/item/projectile/meteor/Bump(atom/A, yes) - if(yes) - return - if(A == firer) - loc = A.loc - return - playsound(loc, 'sound/effects/meteorimpact.ogg', 40, 1) - for(var/mob/M in urange(10, src)) - if(!M.stat) - shake_camera(M, 3, 1) - qdel(src) - -/obj/item/projectile/energy/floramut - name = "alpha somatoray" - icon_state = "energy" - damage = 0 - damage_type = TOX - nodamage = 1 - impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser - flag = "energy" - -/obj/item/projectile/energy/floramut/on_hit(var/atom/target, var/blocked = 0) - ..() - var/mob/living/M = target - if(ishuman(target)) - var/mob/living/carbon/human/H = M - if(IS_PLANT in H.dna.species.species_traits) - if(prob(15)) - M.apply_effect((rand(30,80)),IRRADIATE) - M.Weaken(5) - M.visible_message("[M] writhes in pain as [M.p_their()] vacuoles boil.", "You writhe in pain as your vacuoles boil!", "You hear the crunching of leaves.") - if(prob(80)) - randmutb(M) - domutcheck(M,null) - else - randmutg(M) - domutcheck(M,null) - else - M.adjustFireLoss(rand(5,15)) - M.show_message("The radiation beam singes you!") - else if(iscarbon(target)) - M.show_message("The radiation beam dissipates harmlessly through your body.") - else - return 1 - -/obj/item/projectile/energy/florayield - name = "beta somatoray" - icon_state = "energy2" - damage = 0 - damage_type = TOX - nodamage = 1 - flag = "energy" - -/obj/item/projectile/energy/florayield/on_hit(var/atom/target, var/blocked = 0) - ..() - var/mob/M = target - if(ishuman(target)) //These rays make plantmen fat. - var/mob/living/carbon/human/H = M - if(IS_PLANT in H.dna.species.species_traits) - H.set_nutrition(min(H.nutrition+30, NUTRITION_LEVEL_FULL)) - else if(iscarbon(target)) - M.show_message("The radiation beam dissipates harmlessly through your body.") - else - return 1 - - -/obj/item/projectile/beam/mindflayer - name = "flayer ray" - -/obj/item/projectile/beam/mindflayer/on_hit(var/atom/target, var/blocked = 0) - . = ..() - if(ishuman(target)) - var/mob/living/carbon/human/M = target - M.adjustBrainLoss(20) - M.AdjustHallucinate(20) - -/obj/item/projectile/clown - name = "snap-pop" - icon = 'icons/obj/toy.dmi' - icon_state = "snappop" - -/obj/item/projectile/clown/Bump(atom/A as mob|obj|turf|area) - do_sparks(3, 1, src) - new /obj/effect/decal/cleanable/ash(loc) - visible_message("The [name] explodes!","You hear a snap!") - playsound(src, 'sound/effects/snap.ogg', 50, 1) - qdel(src) - -/obj/item/projectile/beam/wormhole - name = "bluespace beam" - icon_state = "spark" - hitsound = "sparks" - damage = 0 - var/obj/item/gun/energy/wormhole_projector/gun - color = "#33CCFF" - nodamage = TRUE - -/obj/item/projectile/beam/wormhole/orange - name = "orange bluespace beam" - color = "#FF6600" - -/obj/item/projectile/beam/wormhole/New(var/obj/item/ammo_casing/energy/wormhole/casing) - if(casing) - gun = casing.gun - -/obj/item/projectile/beam/wormhole/on_hit(atom/target) - if(ismob(target)) - if(is_teleport_allowed(target.z)) - var/turf/portal_destination = pick(orange(6, src)) - do_teleport(target, portal_destination) - return ..() - if(!gun) - qdel(src) - gun.create_portal(src) - -/obj/item/projectile/bullet/frag12 - name ="explosive slug" - damage = 25 - weaken = 5 - alwayslog = TRUE - -/obj/item/projectile/bullet/frag12/on_hit(atom/target, blocked = 0) - ..() - explosion(target, -1, 0, 1) - return 1 - -/obj/item/projectile/plasma - name = "plasma blast" - icon_state = "plasmacutter" - damage_type = BRUTE - damage = 5 - range = 3 - dismemberment = 20 - impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser - -/obj/item/projectile/plasma/on_hit(atom/target) - . = ..() - if(ismineralturf(target)) - forcedodge = 1 - var/turf/simulated/mineral/M = target - M.gets_drilled(firer) - else - forcedodge = 0 - -/obj/item/projectile/plasma/adv - damage = 7 - range = 5 - -/obj/item/projectile/plasma/adv/mech - damage = 10 - range = 9 - -/obj/item/projectile/energy/teleport - name = "teleportation burst" - icon_state = "bluespace" - damage = 0 - nodamage = 1 - alwayslog = TRUE - var/teleport_target = null - -/obj/item/projectile/energy/teleport/New(loc, tele_target) - ..(loc) - if(tele_target) - teleport_target = tele_target - -/obj/item/projectile/energy/teleport/on_hit(var/atom/target, var/blocked = 0) - if(isliving(target)) - if(teleport_target) - do_teleport(target, teleport_target, 0)//teleport what's in the tile to the beacon - else - do_teleport(target, target, 15) //Otherwise it just warps you off somewhere. - add_attack_logs(firer, target, "Shot with a [type] [teleport_target ? "(Destination: [teleport_target])" : ""]") - -/obj/item/projectile/snowball - name = "snowball" - icon_state = "snowball" - hitsound = 'sound/items/dodgeball.ogg' - damage = 4 - damage_type = BURN - -/obj/item/projectile/snowball/on_hit(atom/target) //chilling - . = ..() - if(istype(target, /mob/living)) - var/mob/living/M = target - M.bodytemperature = max(0, M.bodytemperature - 50) //each hit will drop your body temp, so don't get surrounded! - M.ExtinguishMob() //bright side, they counter being on fire! - -/obj/item/projectile/ornament - name = "ornament" - icon_state = "ornament-1" - hitsound = 'sound/effects/glasshit.ogg' - damage = 7 - damage_type = BRUTE - -/obj/item/projectile/ornament/New() - icon_state = pick("ornament-1", "ornament-2") - ..() - -/obj/item/projectile/ornament/on_hit(atom/target) //knockback - ..() - if(istype(target, /turf)) - return 0 - var/obj/T = target - var/throwdir = get_dir(firer,target) - T.throw_at(get_edge_target_turf(target, throwdir),10,10) - return 1 - -/obj/item/projectile/mimic - name = "googly-eyed gun" - hitsound = 'sound/weapons/genhit1.ogg' - damage = 0 - nodamage = 1 - damage_type = BURN - flag = "melee" - var/obj/item/gun/stored_gun - -/obj/item/projectile/mimic/New(loc, mimic_type) - ..(loc) - if(mimic_type) - stored_gun = new mimic_type(src) - icon = stored_gun.icon - icon_state = stored_gun.icon_state - overlays = stored_gun.overlays - SpinAnimation(20, -1) - -/obj/item/projectile/mimic/on_hit(atom/target) - ..() - var/turf/T = get_turf(src) - var/obj/item/gun/G = stored_gun - stored_gun = null - G.forceMove(T) - var/mob/living/simple_animal/hostile/mimic/copy/ranged/R = new /mob/living/simple_animal/hostile/mimic/copy/ranged(T, G, firer) - if(ismob(target)) - R.target = target +/obj/item/projectile/ion + name = "ion bolt" + icon_state = "ion" + damage = 0 + alwayslog = TRUE + damage_type = BURN + nodamage = 1 + impact_effect_type = /obj/effect/temp_visual/impact_effect/ion + flag = "energy" + +/obj/item/projectile/ion/on_hit(var/atom/target, var/blocked = 0) + ..() + empulse(target, 1, 1, 1, cause = "[type] fired by [key_name(firer)]") + return 1 + +/obj/item/projectile/ion/weak + +/obj/item/projectile/ion/weak/on_hit(atom/target, blocked = 0) + ..() + empulse(target, 0, 0, 1, cause = "[type] fired by [key_name(firer)]") + return 1 + +/obj/item/projectile/bullet/gyro + name ="explosive bolt" + icon_state= "bolter" + damage = 50 + alwayslog = TRUE + flag = "bullet" + +/obj/item/projectile/bullet/gyro/on_hit(var/atom/target, var/blocked = 0) + ..() + explosion(target, -1, 0, 2, cause = "[type] fired by [key_name(firer)]") + return 1 + +/obj/item/projectile/bullet/a40mm + name ="40mm grenade" + desc = "USE A WEEL GUN" + icon_state= "bolter" + alwayslog = TRUE + damage = 60 + flag = "bullet" + +/obj/item/projectile/bullet/a40mm/on_hit(atom/target, blocked = 0) + ..() + explosion(target, -1, 0, 2, 1, 0, flame_range = 3, cause = "[type] fired by [key_name(firer)]") + return 1 + +/obj/item/projectile/temp + name = "temperature beam" + icon_state = "temp_4" + damage = 0 + damage_type = BURN + nodamage = 1 + flag = "energy" + var/temperature = 300 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + +/obj/item/projectile/temp/New(loc, shot_temp) + ..() + if(!isnull(shot_temp)) + temperature = shot_temp + switch(temperature) + if(501 to INFINITY) + name = "searing beam" //if emagged + icon_state = "temp_8" + if(400 to 500) + name = "burning beam" //temp at which mobs start taking HEAT_DAMAGE_LEVEL_2 + icon_state = "temp_7" + if(360 to 400) + name = "hot beam" //temp at which mobs start taking HEAT_DAMAGE_LEVEL_1 + icon_state = "temp_6" + if(335 to 360) + name = "warm beam" //temp at which players get notified of their high body temp + icon_state = "temp_5" + if(295 to 335) + name = "ambient beam" + icon_state = "temp_4" + if(260 to 295) + name = "cool beam" //temp at which players get notified of their low body temp + icon_state = "temp_3" + if(200 to 260) + name = "cold beam" //temp at which mobs start taking COLD_DAMAGE_LEVEL_1 + icon_state = "temp_2" + if(120 to 260) + name = "ice beam" //temp at which mobs start taking COLD_DAMAGE_LEVEL_2 + icon_state = "temp_1" + if(-INFINITY to 120) + name = "freeze beam" //temp at which mobs start taking COLD_DAMAGE_LEVEL_3 + icon_state = "temp_0" + else + name = "temperature beam"//failsafe + icon_state = "temp_4" + + +/obj/item/projectile/temp/on_hit(var/atom/target, var/blocked = 0)//These two could likely check temp protection on the mob + ..() + if(isliving(target)) + var/mob/living/M = target + M.bodytemperature = temperature + if(temperature > 500)//emagged + M.adjust_fire_stacks(0.5) + M.IgniteMob() + playsound(M.loc, 'sound/effects/bamf.ogg', 50, 0) + return 1 + +/obj/item/projectile/meteor + name = "meteor" + icon = 'icons/obj/meteor.dmi' + icon_state = "small" + damage = 0 + damage_type = BRUTE + nodamage = 1 + flag = "bullet" + +/obj/item/projectile/meteor/Bump(atom/A, yes) + if(yes) + return + if(A == firer) + loc = A.loc + return + playsound(loc, 'sound/effects/meteorimpact.ogg', 40, 1) + for(var/mob/M in urange(10, src)) + if(!M.stat) + shake_camera(M, 3, 1) + qdel(src) + +/obj/item/projectile/energy/floramut + name = "alpha somatoray" + icon_state = "energy" + damage = 0 + damage_type = TOX + nodamage = 1 + impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser + flag = "energy" + +/obj/item/projectile/energy/floramut/on_hit(var/atom/target, var/blocked = 0) + ..() + var/mob/living/M = target + if(ishuman(target)) + var/mob/living/carbon/human/H = M + if(IS_PLANT in H.dna.species.species_traits) + if(prob(15)) + M.apply_effect((rand(30,80)),IRRADIATE) + M.Weaken(5) + M.visible_message("[M] writhes in pain as [M.p_their()] vacuoles boil.", "You writhe in pain as your vacuoles boil!", "You hear the crunching of leaves.") + if(prob(80)) + randmutb(M) + domutcheck(M,null) + else + randmutg(M) + domutcheck(M,null) + else + M.adjustFireLoss(rand(5,15)) + M.show_message("The radiation beam singes you!") + else if(iscarbon(target)) + M.show_message("The radiation beam dissipates harmlessly through your body.") + else + return 1 + +/obj/item/projectile/energy/florayield + name = "beta somatoray" + icon_state = "energy2" + damage = 0 + damage_type = TOX + nodamage = 1 + flag = "energy" + +/obj/item/projectile/energy/florayield/on_hit(var/atom/target, var/blocked = 0) + ..() + var/mob/M = target + if(ishuman(target)) //These rays make plantmen fat. + var/mob/living/carbon/human/H = M + if(IS_PLANT in H.dna.species.species_traits) + H.set_nutrition(min(H.nutrition+30, NUTRITION_LEVEL_FULL)) + else if(iscarbon(target)) + M.show_message("The radiation beam dissipates harmlessly through your body.") + else + return 1 + + +/obj/item/projectile/beam/mindflayer + name = "flayer ray" + +/obj/item/projectile/beam/mindflayer/on_hit(var/atom/target, var/blocked = 0) + . = ..() + if(ishuman(target)) + var/mob/living/carbon/human/M = target + M.adjustBrainLoss(20) + M.AdjustHallucinate(20) + +/obj/item/projectile/clown + name = "snap-pop" + icon = 'icons/obj/toy.dmi' + icon_state = "snappop" + +/obj/item/projectile/clown/Bump(atom/A as mob|obj|turf|area) + do_sparks(3, 1, src) + new /obj/effect/decal/cleanable/ash(loc) + visible_message("The [name] explodes!","You hear a snap!") + playsound(src, 'sound/effects/snap.ogg', 50, 1) + qdel(src) + +/obj/item/projectile/beam/wormhole + name = "bluespace beam" + icon_state = "spark" + hitsound = "sparks" + damage = 0 + var/obj/item/gun/energy/wormhole_projector/gun + color = "#33CCFF" + nodamage = TRUE + +/obj/item/projectile/beam/wormhole/orange + name = "orange bluespace beam" + color = "#FF6600" + +/obj/item/projectile/beam/wormhole/New(var/obj/item/ammo_casing/energy/wormhole/casing) + if(casing) + gun = casing.gun + +/obj/item/projectile/beam/wormhole/on_hit(atom/target) + if(ismob(target)) + if(is_teleport_allowed(target.z)) + var/turf/portal_destination = pick(orange(6, src)) + do_teleport(target, portal_destination) + return ..() + if(!gun) + qdel(src) + gun.create_portal(src) + +/obj/item/projectile/bullet/frag12 + name ="explosive slug" + damage = 25 + weaken = 5 + alwayslog = TRUE + +/obj/item/projectile/bullet/frag12/on_hit(atom/target, blocked = 0) + ..() + explosion(target, -1, 0, 1) + return 1 + +/obj/item/projectile/plasma + name = "plasma blast" + icon_state = "plasmacutter" + damage_type = BRUTE + damage = 5 + range = 3 + dismemberment = 20 + impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser + +/obj/item/projectile/plasma/on_hit(atom/target) + . = ..() + if(ismineralturf(target)) + forcedodge = 1 + var/turf/simulated/mineral/M = target + M.gets_drilled(firer) + else + forcedodge = 0 + +/obj/item/projectile/plasma/adv + damage = 7 + range = 5 + +/obj/item/projectile/plasma/adv/mech + damage = 10 + range = 9 + +/obj/item/projectile/energy/teleport + name = "teleportation burst" + icon_state = "bluespace" + damage = 0 + nodamage = 1 + alwayslog = TRUE + var/teleport_target = null + +/obj/item/projectile/energy/teleport/New(loc, tele_target) + ..(loc) + if(tele_target) + teleport_target = tele_target + +/obj/item/projectile/energy/teleport/on_hit(var/atom/target, var/blocked = 0) + if(isliving(target)) + if(teleport_target) + do_teleport(target, teleport_target, 0)//teleport what's in the tile to the beacon + else + do_teleport(target, target, 15) //Otherwise it just warps you off somewhere. + add_attack_logs(firer, target, "Shot with a [type] [teleport_target ? "(Destination: [teleport_target])" : ""]") + +/obj/item/projectile/snowball + name = "snowball" + icon_state = "snowball" + hitsound = 'sound/items/dodgeball.ogg' + damage = 4 + damage_type = BURN + +/obj/item/projectile/snowball/on_hit(atom/target) //chilling + . = ..() + if(istype(target, /mob/living)) + var/mob/living/M = target + M.bodytemperature = max(0, M.bodytemperature - 50) //each hit will drop your body temp, so don't get surrounded! + M.ExtinguishMob() //bright side, they counter being on fire! + +/obj/item/projectile/ornament + name = "ornament" + icon_state = "ornament-1" + hitsound = 'sound/effects/glasshit.ogg' + damage = 7 + damage_type = BRUTE + +/obj/item/projectile/ornament/New() + icon_state = pick("ornament-1", "ornament-2") + ..() + +/obj/item/projectile/ornament/on_hit(atom/target) //knockback + ..() + if(istype(target, /turf)) + return 0 + var/obj/T = target + var/throwdir = get_dir(firer,target) + T.throw_at(get_edge_target_turf(target, throwdir),10,10) + return 1 + +/obj/item/projectile/mimic + name = "googly-eyed gun" + hitsound = 'sound/weapons/genhit1.ogg' + damage = 0 + nodamage = 1 + damage_type = BURN + flag = "melee" + var/obj/item/gun/stored_gun + +/obj/item/projectile/mimic/New(loc, mimic_type) + ..(loc) + if(mimic_type) + stored_gun = new mimic_type(src) + icon = stored_gun.icon + icon_state = stored_gun.icon_state + overlays = stored_gun.overlays + SpinAnimation(20, -1) + +/obj/item/projectile/mimic/on_hit(atom/target) + ..() + var/turf/T = get_turf(src) + var/obj/item/gun/G = stored_gun + stored_gun = null + G.forceMove(T) + var/mob/living/simple_animal/hostile/mimic/copy/ranged/R = new /mob/living/simple_animal/hostile/mimic/copy/ranged(T, G, firer) + if(ismob(target)) + R.target = target diff --git a/code/modules/reagents/chemistry/colors.dm b/code/modules/reagents/chemistry/colors.dm index 0eea945349c..e9ced2ecfbe 100644 --- a/code/modules/reagents/chemistry/colors.dm +++ b/code/modules/reagents/chemistry/colors.dm @@ -28,4 +28,4 @@ var/list/random_color_list = list("#00aedb","#a200ff","#f47835","#d41243","#d111 color = BlendRGB(color, reagent_color, vol_temp/vol_counter) else color = BlendRGB(reagent_color, color, vol_temp/vol_counter) - return color \ No newline at end of file + return color diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index dd9a548d0fb..cca429276dd 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -397,4 +397,4 @@ component_parts += new /obj/item/stock_parts/manipulator/pico(null) component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new cell_type(null) - RefreshParts() \ No newline at end of file + RefreshParts() diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index ea53c0c782d..39d491bf192 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -502,4 +502,4 @@ component_parts += new /obj/item/stack/sheet/glass(null) component_parts += new /obj/item/reagent_containers/glass/beaker(null) component_parts += new /obj/item/reagent_containers/glass/beaker(null) - RefreshParts() \ No newline at end of file + RefreshParts() diff --git a/code/modules/reagents/chemistry/readme.dm b/code/modules/reagents/chemistry/readme.dm index 6c76d0a3a0f..ddcdb8a123e 100644 --- a/code/modules/reagents/chemistry/readme.dm +++ b/code/modules/reagents/chemistry/readme.dm @@ -238,4 +238,4 @@ About the Tools: It simply tells us how much to transfer when 'pouring' our reagents into something else. -*/ \ No newline at end of file +*/ diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm index d0ba5c61f12..26dfa2d815f 100644 --- a/code/modules/reagents/chemistry/reagents.dm +++ b/code/modules/reagents/chemistry/reagents.dm @@ -218,4 +218,4 @@ if(M.healthdoll) M.healthdoll.cached_healthdoll_overlays.Cut() if(M.dna.species) - M.dna.species.handle_hud_icons(M) \ No newline at end of file + M.dna.species.handle_hud_icons(M) diff --git a/code/modules/reagents/chemistry/reagents/blob.dm b/code/modules/reagents/chemistry/reagents/blob.dm index e2ea941709c..acf244a892d 100644 --- a/code/modules/reagents/chemistry/reagents/blob.dm +++ b/code/modules/reagents/chemistry/reagents/blob.dm @@ -157,4 +157,4 @@ if(message_living && !issilicon(M)) totalmessage += message_living totalmessage += "!" - to_chat(M, "[totalmessage]") \ No newline at end of file + to_chat(M, "[totalmessage]") diff --git a/code/modules/reagents/chemistry/reagents/disease.dm b/code/modules/reagents/chemistry/reagents/disease.dm index 331157d4acc..1cd1111b49e 100644 --- a/code/modules/reagents/chemistry/reagents/disease.dm +++ b/code/modules/reagents/chemistry/reagents/disease.dm @@ -211,4 +211,4 @@ /datum/reagent/plasma_dust/plasmavirusfood/weak name = "weakened virus plasma" id = "weakplasmavirusfood" - color = "#CEC3C6" // rgb: 206,195,198 \ No newline at end of file + color = "#CEC3C6" // rgb: 206,195,198 diff --git a/code/modules/reagents/chemistry/reagents/food.dm b/code/modules/reagents/chemistry/reagents/food.dm index d25ffe8c495..6d1c4d74e72 100644 --- a/code/modules/reagents/chemistry/reagents/food.dm +++ b/code/modules/reagents/chemistry/reagents/food.dm @@ -1029,4 +1029,4 @@ if(prob(80)) update_flags |= M.adjustBruteLoss(-1 * REAGENTS_EFFECT_MULTIPLIER, FALSE) update_flags |= M.adjustFireLoss(-1 * REAGENTS_EFFECT_MULTIPLIER, FALSE) - return ..() | update_flags \ No newline at end of file + return ..() | update_flags diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm index 144f7b9a8ac..2d99b4025bd 100644 --- a/code/modules/reagents/chemistry/recipes.dm +++ b/code/modules/reagents/chemistry/recipes.dm @@ -66,4 +66,4 @@ if(setting_type) X.throw_at(T, 20 + round(volume * 2), 1 + round(volume / 10)) else - X.throw_at(get_edge_target_turf(T, get_dir(T, X)), 20 + round(volume * 2), 1 + round(volume / 10)) \ No newline at end of file + X.throw_at(get_edge_target_turf(T, get_dir(T, X)), 20 + round(volume * 2), 1 + round(volume / 10)) diff --git a/code/modules/reagents/chemistry/recipes/drinks.dm b/code/modules/reagents/chemistry/recipes/drinks.dm index 5d088bd9ccb..1f15c95bbf2 100644 --- a/code/modules/reagents/chemistry/recipes/drinks.dm +++ b/code/modules/reagents/chemistry/recipes/drinks.dm @@ -867,4 +867,4 @@ result = "bacchus_blessing" required_reagents = list("hooch" = 1, "absinthe" = 1, "manlydorf" = 1, "syndicatebomb" = 1) result_amount = 4 - mix_message = "The mixture turns to a sickening froth." \ No newline at end of file + mix_message = "The mixture turns to a sickening froth." diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm index d2499a06c54..f8c16598946 100644 --- a/code/modules/reagents/chemistry/recipes/drugs.dm +++ b/code/modules/reagents/chemistry/recipes/drugs.dm @@ -113,4 +113,4 @@ result = "surge" required_reagents = list("thermite" = 3, "uranium" = 1, "fluorosurfactant" = 1, "sacid" = 1) result_amount = 6 - mix_message = "The mixture congeals into a metallic green gel that crackles with electrical activity." \ No newline at end of file + mix_message = "The mixture congeals into a metallic green gel that crackles with electrical activity." diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index 5066f059317..f7fb78dcdec 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -279,4 +279,4 @@ required_reagents = list("ethanol" = 1, "copper" = 1, "silver" = 1) result_amount = 3 min_temp = T0C + 100 - mix_message = "The solution gently swirls with a metallic sheen." \ No newline at end of file + mix_message = "The solution gently swirls with a metallic sheen." diff --git a/code/modules/reagents/chemistry/recipes/toxins.dm b/code/modules/reagents/chemistry/recipes/toxins.dm index 38a4900a77e..977e3e0b276 100644 --- a/code/modules/reagents/chemistry/recipes/toxins.dm +++ b/code/modules/reagents/chemistry/recipes/toxins.dm @@ -173,4 +173,4 @@ result = "rotatium" required_reagents = list("lsd" = 1, "teslium" = 1, "methamphetamine" = 1) result_amount = 3 - mix_message = "After sparks, fire, and the smell of LSD, the mix is constantly spinning with no stop in sight." \ No newline at end of file + mix_message = "After sparks, fire, and the smell of LSD, the mix is constantly spinning with no stop in sight." diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 41f9524874b..db65f761e2e 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -1,74 +1,74 @@ -/obj/item/reagent_containers - name = "Container" - desc = "..." - icon = 'icons/obj/chemical.dmi' - icon_state = null - w_class = WEIGHT_CLASS_TINY - var/amount_per_transfer_from_this = 5 - var/possible_transfer_amounts = list(5,10,15,25,30) - var/volume = 30 - var/list/list_reagents = null - var/spawned_disease = null - var/disease_amount = 20 - var/has_lid = FALSE // Used for containers where we want to put lids on and off - -/obj/item/reagent_containers/verb/set_APTFT() //set amount_per_transfer_from_this - set name = "Set transfer amount" - set category = "Object" - set src in range(0) - - if(usr.stat || !usr.canmove || usr.restrained()) - return - var/default = null - if(amount_per_transfer_from_this in possible_transfer_amounts) - default = amount_per_transfer_from_this - var/N = input("Amount per transfer from this:", "[src]", default) as null|anything in possible_transfer_amounts - if(N) - amount_per_transfer_from_this = N - -/obj/item/reagent_containers/New() - ..() - if(!possible_transfer_amounts) - verbs -= /obj/item/reagent_containers/verb/set_APTFT - create_reagents(volume) - if(spawned_disease) - var/datum/disease/F = new spawned_disease(0) - var/list/data = list("viruses" = list(F), "blood_color" = "#A10808") - reagents.add_reagent("blood", disease_amount, data) - add_initial_reagents() - -obj/item/reagent_containers/proc/add_initial_reagents() - if(list_reagents) - reagents.add_reagent_list(list_reagents) - -/obj/item/reagent_containers/ex_act() - if(reagents) - for(var/datum/reagent/R in reagents.reagent_list) - R.on_ex_act() - if(!QDELETED(src)) - ..() - -/obj/item/reagent_containers/attack_self(mob/user) - if(has_lid) - if(is_open_container()) - to_chat(usr, "You put the lid on [src].") - container_type ^= REFILLABLE | DRAINABLE - else - to_chat(usr, "You take the lid off [src].") - container_type |= REFILLABLE | DRAINABLE - update_icon() - return - -/obj/item/reagent_containers/afterattack(obj/target, mob/user , flag) - return - -/obj/item/reagent_containers/wash(mob/user, atom/source) - if(is_open_container()) - if(reagents.total_volume >= volume) - to_chat(user, "[src] is full.") - return - else - reagents.add_reagent("water", min(volume - reagents.total_volume, amount_per_transfer_from_this)) - to_chat(user, "You fill [src] from [source].") - return - ..() +/obj/item/reagent_containers + name = "Container" + desc = "..." + icon = 'icons/obj/chemical.dmi' + icon_state = null + w_class = WEIGHT_CLASS_TINY + var/amount_per_transfer_from_this = 5 + var/possible_transfer_amounts = list(5,10,15,25,30) + var/volume = 30 + var/list/list_reagents = null + var/spawned_disease = null + var/disease_amount = 20 + var/has_lid = FALSE // Used for containers where we want to put lids on and off + +/obj/item/reagent_containers/verb/set_APTFT() //set amount_per_transfer_from_this + set name = "Set transfer amount" + set category = "Object" + set src in range(0) + + if(usr.stat || !usr.canmove || usr.restrained()) + return + var/default = null + if(amount_per_transfer_from_this in possible_transfer_amounts) + default = amount_per_transfer_from_this + var/N = input("Amount per transfer from this:", "[src]", default) as null|anything in possible_transfer_amounts + if(N) + amount_per_transfer_from_this = N + +/obj/item/reagent_containers/New() + ..() + if(!possible_transfer_amounts) + verbs -= /obj/item/reagent_containers/verb/set_APTFT + create_reagents(volume) + if(spawned_disease) + var/datum/disease/F = new spawned_disease(0) + var/list/data = list("viruses" = list(F), "blood_color" = "#A10808") + reagents.add_reagent("blood", disease_amount, data) + add_initial_reagents() + +obj/item/reagent_containers/proc/add_initial_reagents() + if(list_reagents) + reagents.add_reagent_list(list_reagents) + +/obj/item/reagent_containers/ex_act() + if(reagents) + for(var/datum/reagent/R in reagents.reagent_list) + R.on_ex_act() + if(!QDELETED(src)) + ..() + +/obj/item/reagent_containers/attack_self(mob/user) + if(has_lid) + if(is_open_container()) + to_chat(usr, "You put the lid on [src].") + container_type ^= REFILLABLE | DRAINABLE + else + to_chat(usr, "You take the lid off [src].") + container_type |= REFILLABLE | DRAINABLE + update_icon() + return + +/obj/item/reagent_containers/afterattack(obj/target, mob/user , flag) + return + +/obj/item/reagent_containers/wash(mob/user, atom/source) + if(is_open_container()) + if(reagents.total_volume >= volume) + to_chat(user, "[src] is full.") + return + else + reagents.add_reagent("water", min(volume - reagents.total_volume, amount_per_transfer_from_this)) + to_chat(user, "You fill [src] from [source].") + return + ..() diff --git a/code/modules/reagents/reagent_containers/borghydro.dm b/code/modules/reagents/reagent_containers/borghydro.dm index d82c128be47..f80fe2cb62a 100644 --- a/code/modules/reagents/reagent_containers/borghydro.dm +++ b/code/modules/reagents/reagent_containers/borghydro.dm @@ -1,116 +1,116 @@ - -/obj/item/reagent_containers/borghypo - name = "Cyborg Hypospray" - desc = "An advanced chemical synthesizer and injection system, designed for heavy-duty medical equipment." - icon = 'icons/obj/hypo.dmi' - item_state = "hypo" - icon_state = "borghypo" - amount_per_transfer_from_this = 5 - volume = 30 - possible_transfer_amounts = null - var/mode = 1 - var/charge_cost = 50 - var/charge_tick = 0 - var/recharge_time = 5 //Time it takes for shots to recharge (in seconds) - var/bypass_protection = 0 //If the hypospray can go through armor or thick material - - var/list/datum/reagents/reagent_list = list() - var/list/reagent_ids = list("salglu_solution", "epinephrine", "spaceacillin", "charcoal", "hydrocodone") - //var/list/reagent_ids = list("salbutamol", "silver_sulfadiazine", "styptic_powder", "charcoal", "epinephrine", "spaceacillin", "hydrocodone") - -/obj/item/reagent_containers/borghypo/surgeon - reagent_ids = list("styptic_powder", "epinephrine", "salbutamol") - -/obj/item/reagent_containers/borghypo/crisis - reagent_ids = list("salglu_solution", "epinephrine", "sal_acid") - -/obj/item/reagent_containers/borghypo/syndicate - name = "syndicate cyborg hypospray" - desc = "An experimental piece of Syndicate technology used to produce powerful restorative nanites used to very quickly restore injuries of all types. Also metabolizes potassium iodide, for radiation poisoning, and hydrocodone, for field surgery and pain relief." - icon_state = "borghypo_s" - charge_cost = 20 - recharge_time = 2 - reagent_ids = list("syndicate_nanites", "potass_iodide", "hydrocodone") - bypass_protection = 1 - -/obj/item/reagent_containers/borghypo/New() - ..() - for(var/R in reagent_ids) - add_reagent(R) - - START_PROCESSING(SSobj, src) - -/obj/item/reagent_containers/borghypo/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/reagent_containers/borghypo/process() //Every [recharge_time] seconds, recharge some reagents for the cyborg - charge_tick++ - if(charge_tick < recharge_time) return 0 - charge_tick = 0 - - if(isrobot(loc)) - var/mob/living/silicon/robot/R = loc - if(R && R.cell) - var/datum/reagents/RG = reagent_list[mode] - if(RG.total_volume < RG.maximum_volume) //Don't recharge reagents and drain power if the storage is full. - R.cell.use(charge_cost) //Take power from borg... - RG.add_reagent(reagent_ids[mode], 5) //And fill hypo with reagent. - //update_icon() - return 1 - -// Use this to add more chemicals for the borghypo to produce. -/obj/item/reagent_containers/borghypo/proc/add_reagent(reagent) - reagent_ids |= reagent - var/datum/reagents/RG = new(30) - RG.my_atom = src - reagent_list += RG - - var/datum/reagents/R = reagent_list[reagent_list.len] - R.add_reagent(reagent, 30) - -/obj/item/reagent_containers/borghypo/attack(mob/living/M, mob/user) - var/datum/reagents/R = reagent_list[mode] - if(!R.total_volume) - to_chat(user, "The injector is empty.") - return - if(!istype(M)) - return - if(R.total_volume && M.can_inject(user, TRUE, user.zone_selected, penetrate_thick = bypass_protection)) - to_chat(user, "You inject [M] with the injector.") - to_chat(M, "You feel a tiny prick!") - - R.add_reagent(M) - if(M.reagents) - var/datum/reagent/injected = GLOB.chemical_reagents_list[reagent_ids[mode]] - var/contained = injected.name - var/trans = R.trans_to(M, amount_per_transfer_from_this) - add_attack_logs(user, M, "Injected with [name] containing [contained], transfered [trans] units", injected.harmless ? ATKLOG_ALMOSTALL : null) - M.LAssailant = user - to_chat(user, "[trans] units injected. [R.total_volume] units remaining.") - return - -/obj/item/reagent_containers/borghypo/attack_self(mob/user) - playsound(loc, 'sound/effects/pop.ogg', 50, 0) //Change the mode - mode++ - if(mode > reagent_list.len) - mode = 1 - - charge_tick = 0 //Prevents wasted chems/cell charge if you're cycling through modes. - var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_ids[mode]] - to_chat(user, "Synthesizer is now producing '[R.name]'.") - return - -/obj/item/reagent_containers/borghypo/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 2) - var/empty = TRUE - - for(var/datum/reagents/RS in reagent_list) - var/datum/reagent/R = locate() in RS.reagent_list - if(R) - . += "It currently has [R.volume] units of [R.name] stored." - empty = FALSE - - if(empty) - . += "It is currently empty. Allow some time for the internal syntheszier to produce more." + +/obj/item/reagent_containers/borghypo + name = "Cyborg Hypospray" + desc = "An advanced chemical synthesizer and injection system, designed for heavy-duty medical equipment." + icon = 'icons/obj/hypo.dmi' + item_state = "hypo" + icon_state = "borghypo" + amount_per_transfer_from_this = 5 + volume = 30 + possible_transfer_amounts = null + var/mode = 1 + var/charge_cost = 50 + var/charge_tick = 0 + var/recharge_time = 5 //Time it takes for shots to recharge (in seconds) + var/bypass_protection = 0 //If the hypospray can go through armor or thick material + + var/list/datum/reagents/reagent_list = list() + var/list/reagent_ids = list("salglu_solution", "epinephrine", "spaceacillin", "charcoal", "hydrocodone") + //var/list/reagent_ids = list("salbutamol", "silver_sulfadiazine", "styptic_powder", "charcoal", "epinephrine", "spaceacillin", "hydrocodone") + +/obj/item/reagent_containers/borghypo/surgeon + reagent_ids = list("styptic_powder", "epinephrine", "salbutamol") + +/obj/item/reagent_containers/borghypo/crisis + reagent_ids = list("salglu_solution", "epinephrine", "sal_acid") + +/obj/item/reagent_containers/borghypo/syndicate + name = "syndicate cyborg hypospray" + desc = "An experimental piece of Syndicate technology used to produce powerful restorative nanites used to very quickly restore injuries of all types. Also metabolizes potassium iodide, for radiation poisoning, and hydrocodone, for field surgery and pain relief." + icon_state = "borghypo_s" + charge_cost = 20 + recharge_time = 2 + reagent_ids = list("syndicate_nanites", "potass_iodide", "hydrocodone") + bypass_protection = 1 + +/obj/item/reagent_containers/borghypo/New() + ..() + for(var/R in reagent_ids) + add_reagent(R) + + START_PROCESSING(SSobj, src) + +/obj/item/reagent_containers/borghypo/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/reagent_containers/borghypo/process() //Every [recharge_time] seconds, recharge some reagents for the cyborg + charge_tick++ + if(charge_tick < recharge_time) return 0 + charge_tick = 0 + + if(isrobot(loc)) + var/mob/living/silicon/robot/R = loc + if(R && R.cell) + var/datum/reagents/RG = reagent_list[mode] + if(RG.total_volume < RG.maximum_volume) //Don't recharge reagents and drain power if the storage is full. + R.cell.use(charge_cost) //Take power from borg... + RG.add_reagent(reagent_ids[mode], 5) //And fill hypo with reagent. + //update_icon() + return 1 + +// Use this to add more chemicals for the borghypo to produce. +/obj/item/reagent_containers/borghypo/proc/add_reagent(reagent) + reagent_ids |= reagent + var/datum/reagents/RG = new(30) + RG.my_atom = src + reagent_list += RG + + var/datum/reagents/R = reagent_list[reagent_list.len] + R.add_reagent(reagent, 30) + +/obj/item/reagent_containers/borghypo/attack(mob/living/M, mob/user) + var/datum/reagents/R = reagent_list[mode] + if(!R.total_volume) + to_chat(user, "The injector is empty.") + return + if(!istype(M)) + return + if(R.total_volume && M.can_inject(user, TRUE, user.zone_selected, penetrate_thick = bypass_protection)) + to_chat(user, "You inject [M] with the injector.") + to_chat(M, "You feel a tiny prick!") + + R.add_reagent(M) + if(M.reagents) + var/datum/reagent/injected = GLOB.chemical_reagents_list[reagent_ids[mode]] + var/contained = injected.name + var/trans = R.trans_to(M, amount_per_transfer_from_this) + add_attack_logs(user, M, "Injected with [name] containing [contained], transfered [trans] units", injected.harmless ? ATKLOG_ALMOSTALL : null) + M.LAssailant = user + to_chat(user, "[trans] units injected. [R.total_volume] units remaining.") + return + +/obj/item/reagent_containers/borghypo/attack_self(mob/user) + playsound(loc, 'sound/effects/pop.ogg', 50, 0) //Change the mode + mode++ + if(mode > reagent_list.len) + mode = 1 + + charge_tick = 0 //Prevents wasted chems/cell charge if you're cycling through modes. + var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_ids[mode]] + to_chat(user, "Synthesizer is now producing '[R.name]'.") + return + +/obj/item/reagent_containers/borghypo/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 2) + var/empty = TRUE + + for(var/datum/reagents/RS in reagent_list) + var/datum/reagent/R = locate() in RS.reagent_list + if(R) + . += "It currently has [R.volume] units of [R.name] stored." + empty = FALSE + + if(empty) + . += "It is currently empty. Allow some time for the internal syntheszier to produce more." diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm index b9080bf3d12..61552eae789 100644 --- a/code/modules/reagents/reagent_containers/bottle.dm +++ b/code/modules/reagents/reagent_containers/bottle.dm @@ -392,4 +392,4 @@ name = "BVAK bottle" desc = "A small bottle containing Bio Virus Antidote Kit." icon_state = "wide_bottle" - list_reagents = list("atropine" = 5, "epinephrine" = 5, "salbutamol" = 10, "spaceacillin" = 10) \ No newline at end of file + list_reagents = list("atropine" = 5, "epinephrine" = 5, "salbutamol" = 10, "spaceacillin" = 10) diff --git a/code/modules/reagents/reagent_containers/dropper.dm b/code/modules/reagents/reagent_containers/dropper.dm index c9b9867efaa..ddd15413820 100644 --- a/code/modules/reagents/reagent_containers/dropper.dm +++ b/code/modules/reagents/reagent_containers/dropper.dm @@ -1,144 +1,144 @@ -//////////////////////////////////////////////////////////////////////////////// -/// Droppers. /// -//////////////////////////////////////////////////////////////////////////////// - -/obj/item/reagent_containers/dropper - name = "dropper" - desc = "A dropper. Transfers 5 units." - icon_state = "dropper" - item_state = "dropper" - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list(1, 2, 3, 4, 5) - volume = 5 - -/obj/item/reagent_containers/dropper/on_reagent_change() - if(!reagents.total_volume) - icon_state = "[initial(icon_state)]" - else - icon_state = "[initial(icon_state)]1" - -/obj/item/reagent_containers/dropper/attack(mob/living/M, mob/living/user, def_zone) - return - -/obj/item/reagent_containers/dropper/afterattack(atom/target, mob/user, proximity) - if(!proximity) - return - var/to_transfer = 0 - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(!reagents.total_volume) - return - if(user != C) - visible_message("[user] begins to drip something into [C]'s eyes!") - if(!do_mob(user, C, 30)) - return - if(ishuman(target)) - var/mob/living/carbon/human/H = target - var/obj/item/safe_thing = null - - if(H.glasses) - safe_thing = H.glasses - if(H.wear_mask ) - if(H.wear_mask.flags_cover & MASKCOVERSEYES) - safe_thing = H.wear_mask - if(H.head) - if(H.head.flags_cover & MASKCOVERSEYES) - safe_thing = H.head - - if(safe_thing) - visible_message("[user] tries to drip something into [H]'s eyes, but fails!") - - reagents.reaction(safe_thing, REAGENT_TOUCH) - to_transfer = reagents.remove_any(amount_per_transfer_from_this) - - to_chat(user, "You transfer [to_transfer] units of the solution.") - return - - visible_message("[user] drips something into [C]'s eyes!") - reagents.reaction(C, REAGENT_TOUCH) - - var/list/injected = list() - for(var/datum/reagent/R in reagents.reagent_list) - injected += R.name - var/contained = english_list(injected) - add_attack_logs(user, C, "Dripped with [src] containing ([contained]), transfering [to_transfer]") - - to_transfer = reagents.trans_to(C, amount_per_transfer_from_this) - to_chat(user, "You transfer [to_transfer] units of the solution.") - - if(isobj(target)) - if(!target.reagents) - return - - if(reagents.total_volume) - if(!target.is_open_container() && !(istype(target, /obj/item/reagent_containers/food) && !istype(target, /obj/item/reagent_containers/food/pill)) && !istype(target, /obj/item/clothing/mask/cigarette)) - to_chat(user, "You cannot directly fill this object.") - return - - if(target.reagents.total_volume >= target.reagents.maximum_volume) - to_chat(user, "[target] is full.") - return - - to_transfer = reagents.trans_to(target, amount_per_transfer_from_this) - to_chat(user, "You transfer [to_transfer] units of the solution.") - - else - if(!target.is_open_container() && !istype(target, /obj/structure/reagent_dispensers)) - to_chat(user, "You cannot directly remove reagents from [target].") - return - - if(!target.reagents.total_volume) - to_chat(user, "[target] is empty.") - return - - to_transfer = target.reagents.trans_to(src, amount_per_transfer_from_this) - - to_chat(user, "You fill [src] with [to_transfer] units of the solution.") - -/obj/item/reagent_containers/dropper/cyborg - name = "Industrial Dropper" - desc = "A larger dropper. Transfers 10 units." - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) - volume = 10 - -/obj/item/reagent_containers/dropper/precision - name = "pipette" - desc = "A high precision pippette. Holds 1 unit." - icon_state = "pipette" - amount_per_transfer_from_this = 1 - possible_transfer_amounts = list(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1) - volume = 1 - -//Syndicate item. Virus transmitting mini hypospray -/obj/item/reagent_containers/dropper/precision/viral_injector - -/obj/item/reagent_containers/dropper/precision/viral_injector/attack(mob/living/M, mob/living/user, def_zone) - if(M.can_inject(user, TRUE)) - to_chat(user, "You stab [M] with the [src].") - if(reagents.total_volume && M.reagents) - var/list/injected = list() - for(var/datum/reagent/R in reagents.reagent_list) - injected += R.name - var/datum/reagent/blood/B = R - - if(istype(B) && B.data["viruses"]) - var/virList = list() - for(var/dis in B.data["viruses"]) - var/datum/disease/D = dis - var/virusData = D.name - var/english_symptoms = list() - var/datum/disease/advance/A = D - if(A) - for(var/datum/symptom/S in A.symptoms) - english_symptoms += S.name - virusData += " ([english_list(english_symptoms)])" - virList += virusData - var/str = english_list(virList) - add_attack_logs(user, M, "Infected with [str].") - - reagents.reaction(M, REAGENT_INGEST, reagents.total_volume) - reagents.trans_to(M, 1) - - var/contained = english_list(injected) - add_attack_logs(user, M, "Injected with [src] containing ([contained])") +//////////////////////////////////////////////////////////////////////////////// +/// Droppers. /// +//////////////////////////////////////////////////////////////////////////////// + +/obj/item/reagent_containers/dropper + name = "dropper" + desc = "A dropper. Transfers 5 units." + icon_state = "dropper" + item_state = "dropper" + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list(1, 2, 3, 4, 5) + volume = 5 + +/obj/item/reagent_containers/dropper/on_reagent_change() + if(!reagents.total_volume) + icon_state = "[initial(icon_state)]" + else + icon_state = "[initial(icon_state)]1" + +/obj/item/reagent_containers/dropper/attack(mob/living/M, mob/living/user, def_zone) + return + +/obj/item/reagent_containers/dropper/afterattack(atom/target, mob/user, proximity) + if(!proximity) + return + var/to_transfer = 0 + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(!reagents.total_volume) + return + if(user != C) + visible_message("[user] begins to drip something into [C]'s eyes!") + if(!do_mob(user, C, 30)) + return + if(ishuman(target)) + var/mob/living/carbon/human/H = target + var/obj/item/safe_thing = null + + if(H.glasses) + safe_thing = H.glasses + if(H.wear_mask ) + if(H.wear_mask.flags_cover & MASKCOVERSEYES) + safe_thing = H.wear_mask + if(H.head) + if(H.head.flags_cover & MASKCOVERSEYES) + safe_thing = H.head + + if(safe_thing) + visible_message("[user] tries to drip something into [H]'s eyes, but fails!") + + reagents.reaction(safe_thing, REAGENT_TOUCH) + to_transfer = reagents.remove_any(amount_per_transfer_from_this) + + to_chat(user, "You transfer [to_transfer] units of the solution.") + return + + visible_message("[user] drips something into [C]'s eyes!") + reagents.reaction(C, REAGENT_TOUCH) + + var/list/injected = list() + for(var/datum/reagent/R in reagents.reagent_list) + injected += R.name + var/contained = english_list(injected) + add_attack_logs(user, C, "Dripped with [src] containing ([contained]), transfering [to_transfer]") + + to_transfer = reagents.trans_to(C, amount_per_transfer_from_this) + to_chat(user, "You transfer [to_transfer] units of the solution.") + + if(isobj(target)) + if(!target.reagents) + return + + if(reagents.total_volume) + if(!target.is_open_container() && !(istype(target, /obj/item/reagent_containers/food) && !istype(target, /obj/item/reagent_containers/food/pill)) && !istype(target, /obj/item/clothing/mask/cigarette)) + to_chat(user, "You cannot directly fill this object.") + return + + if(target.reagents.total_volume >= target.reagents.maximum_volume) + to_chat(user, "[target] is full.") + return + + to_transfer = reagents.trans_to(target, amount_per_transfer_from_this) + to_chat(user, "You transfer [to_transfer] units of the solution.") + + else + if(!target.is_open_container() && !istype(target, /obj/structure/reagent_dispensers)) + to_chat(user, "You cannot directly remove reagents from [target].") + return + + if(!target.reagents.total_volume) + to_chat(user, "[target] is empty.") + return + + to_transfer = target.reagents.trans_to(src, amount_per_transfer_from_this) + + to_chat(user, "You fill [src] with [to_transfer] units of the solution.") + +/obj/item/reagent_containers/dropper/cyborg + name = "Industrial Dropper" + desc = "A larger dropper. Transfers 10 units." + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + volume = 10 + +/obj/item/reagent_containers/dropper/precision + name = "pipette" + desc = "A high precision pippette. Holds 1 unit." + icon_state = "pipette" + amount_per_transfer_from_this = 1 + possible_transfer_amounts = list(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1) + volume = 1 + +//Syndicate item. Virus transmitting mini hypospray +/obj/item/reagent_containers/dropper/precision/viral_injector + +/obj/item/reagent_containers/dropper/precision/viral_injector/attack(mob/living/M, mob/living/user, def_zone) + if(M.can_inject(user, TRUE)) + to_chat(user, "You stab [M] with the [src].") + if(reagents.total_volume && M.reagents) + var/list/injected = list() + for(var/datum/reagent/R in reagents.reagent_list) + injected += R.name + var/datum/reagent/blood/B = R + + if(istype(B) && B.data["viruses"]) + var/virList = list() + for(var/dis in B.data["viruses"]) + var/datum/disease/D = dis + var/virusData = D.name + var/english_symptoms = list() + var/datum/disease/advance/A = D + if(A) + for(var/datum/symptom/S in A.symptoms) + english_symptoms += S.name + virusData += " ([english_list(english_symptoms)])" + virList += virusData + var/str = english_list(virList) + add_attack_logs(user, M, "Infected with [str].") + + reagents.reaction(M, REAGENT_INGEST, reagents.total_volume) + reagents.trans_to(M, 1) + + var/contained = english_list(injected) + add_attack_logs(user, M, "Injected with [src] containing ([contained])") diff --git a/code/modules/reagents/reagent_containers/glass_containers.dm b/code/modules/reagents/reagent_containers/glass_containers.dm index 6e0d8c76620..3e9480f166d 100644 --- a/code/modules/reagents/reagent_containers/glass_containers.dm +++ b/code/modules/reagents/reagent_containers/glass_containers.dm @@ -397,4 +397,4 @@ amount_per_transfer_from_this = 20 /obj/item/reagent_containers/glass/beaker/waterbottle/large/empty - list_reagents = list() \ No newline at end of file + list_reagents = list() diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 3b71a5bd741..490a5d808fa 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -1,175 +1,175 @@ -//////////////////////////////////////////////////////////////////////////////// -/// HYPOSPRAY -//////////////////////////////////////////////////////////////////////////////// - -/obj/item/reagent_containers/hypospray - name = "hypospray" - desc = "The DeForest Medical Corporation hypospray is a sterile, air-needle autoinjector for rapid administration of drugs to patients." - icon = 'icons/obj/hypo.dmi' - item_state = "hypo" - icon_state = "hypo" - amount_per_transfer_from_this = 5 - volume = 30 - possible_transfer_amounts = list(1,2,3,4,5,10,15,20,25,30) - resistance_flags = ACID_PROOF - container_type = OPENCONTAINER - slot_flags = SLOT_BELT - var/ignore_flags = FALSE - var/emagged = FALSE - var/safety_hypo = FALSE - -/obj/item/reagent_containers/hypospray/attack(mob/living/M, mob/user) - if(!reagents.total_volume) - to_chat(user, "[src] is empty!") - return - if(!iscarbon(M)) - return - - if(reagents.total_volume && (ignore_flags || M.can_inject(user, TRUE))) // Ignore flag should be checked first or there will be an error message. - to_chat(M, "You feel a tiny prick!") - to_chat(user, "You inject [M] with [src].") - - if(M.reagents) - var/list/injected = list() - for(var/datum/reagent/R in reagents.reagent_list) - injected += R.name - - var/primary_reagent_name = reagents.get_master_reagent_name() - var/trans = reagents.trans_to(M, amount_per_transfer_from_this) - - if(safety_hypo) - visible_message("[user] injects [M] with [trans] units of [primary_reagent_name].") - playsound(loc, 'sound/goonstation/items/hypo.ogg', 80, 0) - - to_chat(user, "[trans] unit\s injected. [reagents.total_volume] unit\s remaining in [src].") - - var/contained = english_list(injected) - - add_attack_logs(user, M, "Injected with [src] containing ([contained])", reagents.harmless_helper() ? ATKLOG_ALMOSTALL : null) - - return TRUE - -/obj/item/reagent_containers/hypospray/on_reagent_change() - if(safety_hypo && !emagged) - var/found_forbidden_reagent = FALSE - for(var/datum/reagent/R in reagents.reagent_list) - if(!GLOB.safe_chem_list.Find(R.id)) - reagents.del_reagent(R.id) - found_forbidden_reagent = TRUE - if(found_forbidden_reagent) - if(ismob(loc)) - to_chat(loc, "[src] identifies and removes a harmful substance.") - else - visible_message("[src] identifies and removes a harmful substance.") - -/obj/item/reagent_containers/hypospray/emag_act(mob/user) - if(safety_hypo && !emagged) - emagged = TRUE - ignore_flags = TRUE - to_chat(user, "You short out the safeties on [src].") - -/obj/item/reagent_containers/hypospray/safety - name = "medical hypospray" - desc = "A general use medical hypospray for quick injection of chemicals. There is a safety button by the trigger." - icon_state = "medivend_hypo" - safety_hypo = TRUE - -/obj/item/reagent_containers/hypospray/safety/ert - list_reagents = list("omnizine" = 30) - -/obj/item/reagent_containers/hypospray/CMO - list_reagents = list("omnizine" = 30) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/reagent_containers/hypospray/combat - name = "combat stimulant injector" - desc = "A modified air-needle autoinjector, used by support operatives to quickly heal injuries in combat." - amount_per_transfer_from_this = 15 - possible_transfer_amounts = list(15) - icon_state = "combat_hypo" - volume = 90 - ignore_flags = 1 // So they can heal their comrades. - list_reagents = list("epinephrine" = 30, "weak_omnizine" = 30, "salglu_solution" = 30) - -/obj/item/reagent_containers/hypospray/combat/nanites - desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with expensive medical nanites for rapid healing." - volume = 100 - list_reagents = list("nanites" = 100) - -/obj/item/reagent_containers/hypospray/autoinjector - name = "emergency autoinjector" - desc = "A rapid and safe way to stabilize patients in critical condition for personnel without advanced medical knowledge." - icon_state = "autoinjector" - item_state = "autoinjector" - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(10) - volume = 10 - ignore_flags = TRUE //so you can medipen through hardsuits - container_type = DRAWABLE - flags = null - list_reagents = list("epinephrine" = 10) - -/obj/item/reagent_containers/hypospray/autoinjector/attack(mob/M, mob/user) - if(!reagents.total_volume) - to_chat(user, "[src] is empty!") - return - ..() - update_icon() - return TRUE - -/obj/item/reagent_containers/hypospray/autoinjector/update_icon() - if(reagents.total_volume > 0) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)]0" - -/obj/item/reagent_containers/hypospray/autoinjector/examine() - . = ..() - if(reagents && reagents.reagent_list.len) - . += "It is currently loaded." - else - . += "It is spent." - -/obj/item/reagent_containers/hypospray/autoinjector/teporone //basilisks - name = "teporone autoinjector" - desc = "A rapid way to regulate your body's temperature in the event of a hardsuit malfunction." - icon_state = "lepopen" - list_reagents = list("teporone" = 10) - -/obj/item/reagent_containers/hypospray/autoinjector/stimpack //goliath kiting - name = "stimpack autoinjector" - desc = "A rapid way to stimulate your body's adrenaline, allowing for freer movement in restrictive armor." - icon_state = "stimpen" - volume = 20 - amount_per_transfer_from_this = 20 - list_reagents = list("methamphetamine" = 10, "coffee" = 10) - -/obj/item/reagent_containers/hypospray/autoinjector/stimulants - name = "Stimulants autoinjector" - desc = "Rapidly stimulates and regenerates the body's organ system." - icon_state = "stimpen" - amount_per_transfer_from_this = 50 - possible_transfer_amounts = list(50) - volume = 50 - list_reagents = list("stimulants" = 50) - -/obj/item/reagent_containers/hypospray/autoinjector/survival - name = "survival medipen" - desc = "A medipen for surviving in the harshest of environments, heals and protects from environmental hazards.
        WARNING: Do not inject more than one pen in quick succession." - icon_state = "stimpen" - volume = 42 - amount_per_transfer_from_this = 42 - list_reagents = list("salbutamol" = 10, "teporone" = 15, "epinephrine" = 10, "lavaland_extract" = 2, "weak_omnizine" = 5) //Short burst of healing, followed by minor healing from the saline - -/obj/item/reagent_containers/hypospray/autoinjector/nanocalcium - name = "nanocalcium autoinjector" - desc = "After a short period of time the nanites will slow the body's systems and assist with bone repair. Nanomachines son." - icon_state = "bonepen" - amount_per_transfer_from_this = 30 - possible_transfer_amounts = list(30) - volume = 30 - list_reagents = list("nanocalcium" = 30) - -/obj/item/reagent_containers/hypospray/autoinjector/nanocalcium/attack(mob/living/M, mob/user) - if(..()) - playsound(loc, 'sound/weapons/smg_empty_alarm.ogg', 20, 1) +//////////////////////////////////////////////////////////////////////////////// +/// HYPOSPRAY +//////////////////////////////////////////////////////////////////////////////// + +/obj/item/reagent_containers/hypospray + name = "hypospray" + desc = "The DeForest Medical Corporation hypospray is a sterile, air-needle autoinjector for rapid administration of drugs to patients." + icon = 'icons/obj/hypo.dmi' + item_state = "hypo" + icon_state = "hypo" + amount_per_transfer_from_this = 5 + volume = 30 + possible_transfer_amounts = list(1,2,3,4,5,10,15,20,25,30) + resistance_flags = ACID_PROOF + container_type = OPENCONTAINER + slot_flags = SLOT_BELT + var/ignore_flags = FALSE + var/emagged = FALSE + var/safety_hypo = FALSE + +/obj/item/reagent_containers/hypospray/attack(mob/living/M, mob/user) + if(!reagents.total_volume) + to_chat(user, "[src] is empty!") + return + if(!iscarbon(M)) + return + + if(reagents.total_volume && (ignore_flags || M.can_inject(user, TRUE))) // Ignore flag should be checked first or there will be an error message. + to_chat(M, "You feel a tiny prick!") + to_chat(user, "You inject [M] with [src].") + + if(M.reagents) + var/list/injected = list() + for(var/datum/reagent/R in reagents.reagent_list) + injected += R.name + + var/primary_reagent_name = reagents.get_master_reagent_name() + var/trans = reagents.trans_to(M, amount_per_transfer_from_this) + + if(safety_hypo) + visible_message("[user] injects [M] with [trans] units of [primary_reagent_name].") + playsound(loc, 'sound/goonstation/items/hypo.ogg', 80, 0) + + to_chat(user, "[trans] unit\s injected. [reagents.total_volume] unit\s remaining in [src].") + + var/contained = english_list(injected) + + add_attack_logs(user, M, "Injected with [src] containing ([contained])", reagents.harmless_helper() ? ATKLOG_ALMOSTALL : null) + + return TRUE + +/obj/item/reagent_containers/hypospray/on_reagent_change() + if(safety_hypo && !emagged) + var/found_forbidden_reagent = FALSE + for(var/datum/reagent/R in reagents.reagent_list) + if(!GLOB.safe_chem_list.Find(R.id)) + reagents.del_reagent(R.id) + found_forbidden_reagent = TRUE + if(found_forbidden_reagent) + if(ismob(loc)) + to_chat(loc, "[src] identifies and removes a harmful substance.") + else + visible_message("[src] identifies and removes a harmful substance.") + +/obj/item/reagent_containers/hypospray/emag_act(mob/user) + if(safety_hypo && !emagged) + emagged = TRUE + ignore_flags = TRUE + to_chat(user, "You short out the safeties on [src].") + +/obj/item/reagent_containers/hypospray/safety + name = "medical hypospray" + desc = "A general use medical hypospray for quick injection of chemicals. There is a safety button by the trigger." + icon_state = "medivend_hypo" + safety_hypo = TRUE + +/obj/item/reagent_containers/hypospray/safety/ert + list_reagents = list("omnizine" = 30) + +/obj/item/reagent_containers/hypospray/CMO + list_reagents = list("omnizine" = 30) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/reagent_containers/hypospray/combat + name = "combat stimulant injector" + desc = "A modified air-needle autoinjector, used by support operatives to quickly heal injuries in combat." + amount_per_transfer_from_this = 15 + possible_transfer_amounts = list(15) + icon_state = "combat_hypo" + volume = 90 + ignore_flags = 1 // So they can heal their comrades. + list_reagents = list("epinephrine" = 30, "weak_omnizine" = 30, "salglu_solution" = 30) + +/obj/item/reagent_containers/hypospray/combat/nanites + desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with expensive medical nanites for rapid healing." + volume = 100 + list_reagents = list("nanites" = 100) + +/obj/item/reagent_containers/hypospray/autoinjector + name = "emergency autoinjector" + desc = "A rapid and safe way to stabilize patients in critical condition for personnel without advanced medical knowledge." + icon_state = "autoinjector" + item_state = "autoinjector" + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(10) + volume = 10 + ignore_flags = TRUE //so you can medipen through hardsuits + container_type = DRAWABLE + flags = null + list_reagents = list("epinephrine" = 10) + +/obj/item/reagent_containers/hypospray/autoinjector/attack(mob/M, mob/user) + if(!reagents.total_volume) + to_chat(user, "[src] is empty!") + return + ..() + update_icon() + return TRUE + +/obj/item/reagent_containers/hypospray/autoinjector/update_icon() + if(reagents.total_volume > 0) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]0" + +/obj/item/reagent_containers/hypospray/autoinjector/examine() + . = ..() + if(reagents && reagents.reagent_list.len) + . += "It is currently loaded." + else + . += "It is spent." + +/obj/item/reagent_containers/hypospray/autoinjector/teporone //basilisks + name = "teporone autoinjector" + desc = "A rapid way to regulate your body's temperature in the event of a hardsuit malfunction." + icon_state = "lepopen" + list_reagents = list("teporone" = 10) + +/obj/item/reagent_containers/hypospray/autoinjector/stimpack //goliath kiting + name = "stimpack autoinjector" + desc = "A rapid way to stimulate your body's adrenaline, allowing for freer movement in restrictive armor." + icon_state = "stimpen" + volume = 20 + amount_per_transfer_from_this = 20 + list_reagents = list("methamphetamine" = 10, "coffee" = 10) + +/obj/item/reagent_containers/hypospray/autoinjector/stimulants + name = "Stimulants autoinjector" + desc = "Rapidly stimulates and regenerates the body's organ system." + icon_state = "stimpen" + amount_per_transfer_from_this = 50 + possible_transfer_amounts = list(50) + volume = 50 + list_reagents = list("stimulants" = 50) + +/obj/item/reagent_containers/hypospray/autoinjector/survival + name = "survival medipen" + desc = "A medipen for surviving in the harshest of environments, heals and protects from environmental hazards.
        WARNING: Do not inject more than one pen in quick succession." + icon_state = "stimpen" + volume = 42 + amount_per_transfer_from_this = 42 + list_reagents = list("salbutamol" = 10, "teporone" = 15, "epinephrine" = 10, "lavaland_extract" = 2, "weak_omnizine" = 5) //Short burst of healing, followed by minor healing from the saline + +/obj/item/reagent_containers/hypospray/autoinjector/nanocalcium + name = "nanocalcium autoinjector" + desc = "After a short period of time the nanites will slow the body's systems and assist with bone repair. Nanomachines son." + icon_state = "bonepen" + amount_per_transfer_from_this = 30 + possible_transfer_amounts = list(30) + volume = 30 + list_reagents = list("nanocalcium" = 30) + +/obj/item/reagent_containers/hypospray/autoinjector/nanocalcium/attack(mob/living/M, mob/user) + if(..()) + playsound(loc, 'sound/weapons/smg_empty_alarm.ogg', 20, 1) diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm index e22e9c86793..f6980c03620 100644 --- a/code/modules/reagents/reagent_containers/pill.dm +++ b/code/modules/reagents/reagent_containers/pill.dm @@ -1,155 +1,155 @@ -//////////////////////////////////////////////////////////////////////////////// -/// Pills. -//////////////////////////////////////////////////////////////////////////////// -/obj/item/reagent_containers/food/pill - name = "pill" - desc = "a pill." - icon = 'icons/obj/chemical.dmi' - icon_state = null - item_state = "pill" - possible_transfer_amounts = null - volume = 100 - consume_sound = null - can_taste = FALSE - antable = FALSE - -/obj/item/reagent_containers/food/pill/New() - ..() - if(!icon_state) - icon_state = "pill[rand(1,20)]" - -/obj/item/reagent_containers/food/pill/attack_self(mob/user) - return - -/obj/item/reagent_containers/food/pill/attack(mob/living/carbon/M, mob/user, def_zone) - if(!istype(M)) - return 0 - bitesize = reagents.total_volume - if(M.eat(src, user)) - spawn(0) - qdel(src) - return 1 - return 0 - -/obj/item/reagent_containers/food/pill/afterattack(obj/target, mob/user, proximity) - if(!proximity) - return - - if(target.is_open_container() != 0 && target.reagents) - if(!target.reagents.total_volume) - to_chat(user, "[target] is empty. Cant dissolve [src].") - return - - to_chat(user, "You dissolve [src] in [target].") - reagents.trans_to(target, reagents.total_volume) - for(var/mob/O in viewers(2, user)) - O.show_message("[user] puts something in [target].", 1) - spawn(5) - qdel(src) - -//////////////////////////////////////////////////////////////////////////////// -/// Pills. END -//////////////////////////////////////////////////////////////////////////////// - -//Pills -/obj/item/reagent_containers/food/pill/tox - name = "Toxins pill" - desc = "Highly toxic." - icon_state = "pill21" - list_reagents = list("toxin" = 50) - -/obj/item/reagent_containers/food/pill/initropidril - name = "initropidril pill" - desc = "Don't swallow this." - icon_state = "pill21" - list_reagents = list("initropidril" = 50) - -/obj/item/reagent_containers/food/pill/fakedeath - name = "fake death pill" - desc = "Swallow then rest to appear dead, stand up to wake up. Also mutes the user's voice." - icon_state = "pill4" - list_reagents = list("capulettium_plus" = 50) - -/obj/item/reagent_containers/food/pill/adminordrazine - name = "Adminordrazine pill" - desc = "It's magic. We don't have to explain it." - icon_state = "pill16" - list_reagents = list("adminordrazine" = 50) - -/obj/item/reagent_containers/food/pill/morphine - name = "Morphine pill" - desc = "Commonly used to treat insomnia." - icon_state = "pill8" - list_reagents = list("morphine" = 30) - -/obj/item/reagent_containers/food/pill/methamphetamine - name = "Methamphetamine pill" - desc = "Helps improve the ability to concentrate." - icon_state = "pill8" - list_reagents = list("methamphetamine" = 5) - -/obj/item/reagent_containers/food/pill/haloperidol - name = "Haloperidol pill" - desc = "Haloperidol is an anti-psychotic use to treat psychiatric problems." - icon_state = "pill8" - list_reagents = list("haloperidol" = 15) - -/obj/item/reagent_containers/food/pill/happy - name = "Happy pill" - desc = "Happy happy joy joy!" - icon_state = "pill18" - list_reagents = list("space_drugs" = 15, "sugar" = 15) - -/obj/item/reagent_containers/food/pill/zoom - name = "Zoom pill" - desc = "Zoooom!" - icon_state = "pill18" - list_reagents = list("synaptizine" = 5, "methamphetamine" = 5) - -/obj/item/reagent_containers/food/pill/charcoal - name = "Charcoal pill" - desc = "Neutralizes many common toxins." - icon_state = "pill17" - list_reagents = list("charcoal" = 50) - -/obj/item/reagent_containers/food/pill/epinephrine - name = "Epinephrine pill" - desc = "Used to provide shots of adrenaline." - icon_state = "pill6" - list_reagents = list("epinephrine" = 50) - -/obj/item/reagent_containers/food/pill/salicylic - name = "Salicylic Acid pill" - desc = "Commonly used to treat moderate pain and fevers." - icon_state = "pill4" - list_reagents = list("sal_acid" = 20) - -/obj/item/reagent_containers/food/pill/salbutamol - name = "Salbutamol pill" - desc = "Used to treat respiratory distress." - icon_state = "pill8" - list_reagents = list("salbutamol" = 20) - -/obj/item/reagent_containers/food/pill/hydrocodone - name = "Hydrocodone pill" - desc = "Used to treat extreme pain." - icon_state = "pill6" - list_reagents = list("hydrocodone" = 15) - -/obj/item/reagent_containers/food/pill/calomel - name = "calomel pill" - desc = "Can be used to purge impurities, but is highly toxic itself." - icon_state = "pill3" - list_reagents = list("calomel" = 15) - -/obj/item/reagent_containers/food/pill/mutadone - name = "mutadone pill" - desc = "Used to cure genetic abnormalities." - icon_state = "pill18" - list_reagents = list("mutadone" = 20) - -/obj/item/reagent_containers/food/pill/mannitol - name = "mannitol pill" - desc = "Used to treat cranial swelling." - icon_state = "pill19" - list_reagents = list("mannitol" = 20) \ No newline at end of file +//////////////////////////////////////////////////////////////////////////////// +/// Pills. +//////////////////////////////////////////////////////////////////////////////// +/obj/item/reagent_containers/food/pill + name = "pill" + desc = "a pill." + icon = 'icons/obj/chemical.dmi' + icon_state = null + item_state = "pill" + possible_transfer_amounts = null + volume = 100 + consume_sound = null + can_taste = FALSE + antable = FALSE + +/obj/item/reagent_containers/food/pill/New() + ..() + if(!icon_state) + icon_state = "pill[rand(1,20)]" + +/obj/item/reagent_containers/food/pill/attack_self(mob/user) + return + +/obj/item/reagent_containers/food/pill/attack(mob/living/carbon/M, mob/user, def_zone) + if(!istype(M)) + return 0 + bitesize = reagents.total_volume + if(M.eat(src, user)) + spawn(0) + qdel(src) + return 1 + return 0 + +/obj/item/reagent_containers/food/pill/afterattack(obj/target, mob/user, proximity) + if(!proximity) + return + + if(target.is_open_container() != 0 && target.reagents) + if(!target.reagents.total_volume) + to_chat(user, "[target] is empty. Cant dissolve [src].") + return + + to_chat(user, "You dissolve [src] in [target].") + reagents.trans_to(target, reagents.total_volume) + for(var/mob/O in viewers(2, user)) + O.show_message("[user] puts something in [target].", 1) + spawn(5) + qdel(src) + +//////////////////////////////////////////////////////////////////////////////// +/// Pills. END +//////////////////////////////////////////////////////////////////////////////// + +//Pills +/obj/item/reagent_containers/food/pill/tox + name = "Toxins pill" + desc = "Highly toxic." + icon_state = "pill21" + list_reagents = list("toxin" = 50) + +/obj/item/reagent_containers/food/pill/initropidril + name = "initropidril pill" + desc = "Don't swallow this." + icon_state = "pill21" + list_reagents = list("initropidril" = 50) + +/obj/item/reagent_containers/food/pill/fakedeath + name = "fake death pill" + desc = "Swallow then rest to appear dead, stand up to wake up. Also mutes the user's voice." + icon_state = "pill4" + list_reagents = list("capulettium_plus" = 50) + +/obj/item/reagent_containers/food/pill/adminordrazine + name = "Adminordrazine pill" + desc = "It's magic. We don't have to explain it." + icon_state = "pill16" + list_reagents = list("adminordrazine" = 50) + +/obj/item/reagent_containers/food/pill/morphine + name = "Morphine pill" + desc = "Commonly used to treat insomnia." + icon_state = "pill8" + list_reagents = list("morphine" = 30) + +/obj/item/reagent_containers/food/pill/methamphetamine + name = "Methamphetamine pill" + desc = "Helps improve the ability to concentrate." + icon_state = "pill8" + list_reagents = list("methamphetamine" = 5) + +/obj/item/reagent_containers/food/pill/haloperidol + name = "Haloperidol pill" + desc = "Haloperidol is an anti-psychotic use to treat psychiatric problems." + icon_state = "pill8" + list_reagents = list("haloperidol" = 15) + +/obj/item/reagent_containers/food/pill/happy + name = "Happy pill" + desc = "Happy happy joy joy!" + icon_state = "pill18" + list_reagents = list("space_drugs" = 15, "sugar" = 15) + +/obj/item/reagent_containers/food/pill/zoom + name = "Zoom pill" + desc = "Zoooom!" + icon_state = "pill18" + list_reagents = list("synaptizine" = 5, "methamphetamine" = 5) + +/obj/item/reagent_containers/food/pill/charcoal + name = "Charcoal pill" + desc = "Neutralizes many common toxins." + icon_state = "pill17" + list_reagents = list("charcoal" = 50) + +/obj/item/reagent_containers/food/pill/epinephrine + name = "Epinephrine pill" + desc = "Used to provide shots of adrenaline." + icon_state = "pill6" + list_reagents = list("epinephrine" = 50) + +/obj/item/reagent_containers/food/pill/salicylic + name = "Salicylic Acid pill" + desc = "Commonly used to treat moderate pain and fevers." + icon_state = "pill4" + list_reagents = list("sal_acid" = 20) + +/obj/item/reagent_containers/food/pill/salbutamol + name = "Salbutamol pill" + desc = "Used to treat respiratory distress." + icon_state = "pill8" + list_reagents = list("salbutamol" = 20) + +/obj/item/reagent_containers/food/pill/hydrocodone + name = "Hydrocodone pill" + desc = "Used to treat extreme pain." + icon_state = "pill6" + list_reagents = list("hydrocodone" = 15) + +/obj/item/reagent_containers/food/pill/calomel + name = "calomel pill" + desc = "Can be used to purge impurities, but is highly toxic itself." + icon_state = "pill3" + list_reagents = list("calomel" = 15) + +/obj/item/reagent_containers/food/pill/mutadone + name = "mutadone pill" + desc = "Used to cure genetic abnormalities." + icon_state = "pill18" + list_reagents = list("mutadone" = 20) + +/obj/item/reagent_containers/food/pill/mannitol + name = "mannitol pill" + desc = "Used to treat cranial swelling." + icon_state = "pill19" + list_reagents = list("mannitol" = 20) diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index a4320639f39..8826efa5eb8 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -1,217 +1,217 @@ -/obj/item/reagent_containers/spray - name = "spray bottle" - desc = "A spray bottle, with an unscrewable top." - icon = 'icons/obj/janitor.dmi' - icon_state = "cleaner" - item_state = "cleaner" - flags = NOBLUDGEON - container_type = OPENCONTAINER - slot_flags = SLOT_BELT - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 7 - var/spray_maxrange = 3 //what the sprayer will set spray_currentrange to in the attack_self. - var/spray_currentrange = 3 //the range of tiles the sprayer will reach when in fixed mode. - amount_per_transfer_from_this = 5 - volume = 250 - possible_transfer_amounts = null - - -/obj/item/reagent_containers/spray/afterattack(atom/A, mob/user) - if(istype(A, /obj/item/storage) || istype(A, /obj/structure/table) || istype(A, /obj/structure/rack) || istype(A, /obj/structure/closet) \ - || istype(A, /obj/item/reagent_containers) || istype(A, /obj/structure/sink) || istype(A, /obj/structure/janitorialcart) || istype(A, /obj/machinery/hydroponics)) - return - - if(istype(A, /obj/effect/proc_holder/spell)) - return - - if(istype(A, /obj/structure/reagent_dispensers) && get_dist(src,A) <= 1) //this block copypasted from reagent_containers/glass, for lack of a better solution - if(!A.reagents.total_volume && A.reagents) - to_chat(user, "[A] is empty.") - return - - if(reagents.total_volume >= reagents.maximum_volume) - to_chat(user, "[src] is full.") - return - - var/trans = A.reagents.trans_to(src, 50) //This is a static amount, otherwise, it'll take forever to fill. - to_chat(user, "You fill [src] with [trans] units of the contents of [A].") - return - - if(reagents.total_volume < amount_per_transfer_from_this) - to_chat(user, "[src] is empty!") - return - - var/contents_log = reagents.reagent_list.Join(", ") - spray(A) - - playsound(loc, 'sound/effects/spray2.ogg', 50, 1, -6) - user.changeNext_move(CLICK_CD_RANGE*2) - user.newtonian_move(get_dir(A, user)) - - if(reagents.reagent_list.len == 1 && reagents.has_reagent("cleaner")) // Only show space cleaner logs if it's burning people from being too hot or cold - if(reagents.chem_temp < 300 && reagents.chem_temp > 280) // 280 is the cold threshold for slimes, 300 the hot threshold for drask - return - - var/attack_log_type = ATKLOG_MOST - if(reagents.has_reagent("sacid") || reagents.has_reagent("facid") || reagents.has_reagent("lube")) - attack_log_type = ATKLOG_FEW - msg_admin_attack("[key_name_admin(user)] used a spray bottle at [COORD(user)] - Contents: [contents_log] - Temperature: [reagents.chem_temp]K", attack_log_type) - log_game("[key_name(user)] used a spray bottle at [COORD(user)] - Contents: [contents_log] - Temperature: [reagents.chem_temp]K") - return - - -/obj/item/reagent_containers/spray/proc/spray(var/atom/A) - var/obj/effect/decal/chempuff/D = new /obj/effect/decal/chempuff(get_turf(src)) - D.create_reagents(amount_per_transfer_from_this) - reagents.trans_to(D, amount_per_transfer_from_this, 1/spray_currentrange) - D.icon += mix_color_from_reagents(D.reagents.reagent_list) - spawn(0) - for(var/i=0, iYou [amount_per_transfer_from_this == 10 ? "remove" : "fix"] the nozzle. You'll now use [amount_per_transfer_from_this] units per spray.") - -/obj/item/reagent_containers/spray/examine(mob/user) - . = ..() - if(get_dist(user, src) && user == loc) - . += "[round(reagents.total_volume)] units left." - -/obj/item/reagent_containers/spray/verb/empty() - - set name = "Empty Spray Bottle" - set category = "Object" - set src in usr - if(usr.stat || !usr.canmove || usr.restrained()) - return - if(alert(usr, "Are you sure you want to empty that?", "Empty Bottle:", "Yes", "No") != "Yes") - return - if(isturf(usr.loc) && loc == usr) - to_chat(usr, "You empty [src] onto the floor.") - reagents.reaction(usr.loc) - reagents.clear_reagents() - -//space cleaner -/obj/item/reagent_containers/spray/cleaner - name = "space cleaner" - desc = "BLAM!-brand non-foaming space cleaner!" - list_reagents = list("cleaner" = 250) - -/obj/item/reagent_containers/spray/cleaner/drone - name = "space cleaner" - desc = "BLAM!-brand non-foaming space cleaner!" - volume = 50 - list_reagents = list("cleaner" = 50) - -//spray tan -/obj/item/reagent_containers/spray/spraytan - name = "spray tan" - volume = 50 - desc = "Gyaro brand spray tan. Do not spray near eyes or other orifices." - list_reagents = list("spraytan" = 50) - -//pepperspray -/obj/item/reagent_containers/spray/pepper - name = "pepperspray" - desc = "Manufactured by UhangInc, used to blind and down an opponent quickly." - icon = 'icons/obj/items.dmi' - icon_state = "pepperspray" - item_state = "pepperspray" - volume = 40 - spray_maxrange = 4 - amount_per_transfer_from_this = 5 - list_reagents = list("condensedcapsaicin" = 40) - -//water flower -/obj/item/reagent_containers/spray/waterflower - name = "water flower" - desc = "A seemingly innocent sunflower...with a twist." - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "sunflower" - item_state = "sunflower" - amount_per_transfer_from_this = 1 - volume = 10 - list_reagents = list("water" = 10) - -/obj/item/reagent_containers/spray/waterflower/attack_self(mob/user) //Don't allow changing how much the flower sprays - return - -//chemsprayer -/obj/item/reagent_containers/spray/chemsprayer - name = "chem sprayer" - desc = "A utility used to spray large amounts of reagents in a given area." - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "chemsprayer" - item_state = "chemsprayer" - throwforce = 0 - w_class = WEIGHT_CLASS_NORMAL - spray_maxrange = 7 - spray_currentrange = 7 - amount_per_transfer_from_this = 10 - volume = 600 - origin_tech = "combat=3;materials=3;engineering=3" - - -/obj/item/reagent_containers/spray/chemsprayer/spray(var/atom/A) - var/Sprays[3] - for(var/i=1, i<=3, i++) // intialize sprays - if(reagents.total_volume < 1) break - var/obj/effect/decal/chempuff/D = new/obj/effect/decal/chempuff(get_turf(src)) - D.create_reagents(amount_per_transfer_from_this) - reagents.trans_to(D, amount_per_transfer_from_this) - - D.icon += mix_color_from_reagents(D.reagents.reagent_list) - - Sprays[i] = D - - var/direction = get_dir(src, A) - var/turf/T = get_turf(A) - var/turf/T1 = get_step(T,turn(direction, 90)) - var/turf/T2 = get_step(T,turn(direction, -90)) - var/list/the_targets = list(T,T1,T2) - - for(var/i=1, i<=Sprays.len, i++) - spawn() - var/obj/effect/decal/chempuff/D = Sprays[i] - if(!D) continue - - // Spreads the sprays a little bit - var/turf/my_target = pick(the_targets) - the_targets -= my_target - - for(var/j=0, j<=spray_currentrange, j++) - step_towards(D, my_target) - D.reagents.reaction(get_turf(D)) - for(var/atom/t in get_turf(D)) - D.reagents.reaction(t) - sleep(2) - qdel(D) - - - -/obj/item/reagent_containers/spray/chemsprayer/attack_self(var/mob/user) - - amount_per_transfer_from_this = (amount_per_transfer_from_this == 10 ? 5 : 10) - to_chat(user, "You adjust the output switch. You'll now use [amount_per_transfer_from_this] units per spray.") - - -// Plant-B-Gone -/obj/item/reagent_containers/spray/plantbgone // -- Skie - name = "Plant-B-Gone" - desc = "Kills those pesky weeds!" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "plantbgone" - item_state = "plantbgone" - volume = 100 - list_reagents = list("glyphosate" = 100) +/obj/item/reagent_containers/spray + name = "spray bottle" + desc = "A spray bottle, with an unscrewable top." + icon = 'icons/obj/janitor.dmi' + icon_state = "cleaner" + item_state = "cleaner" + flags = NOBLUDGEON + container_type = OPENCONTAINER + slot_flags = SLOT_BELT + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 7 + var/spray_maxrange = 3 //what the sprayer will set spray_currentrange to in the attack_self. + var/spray_currentrange = 3 //the range of tiles the sprayer will reach when in fixed mode. + amount_per_transfer_from_this = 5 + volume = 250 + possible_transfer_amounts = null + + +/obj/item/reagent_containers/spray/afterattack(atom/A, mob/user) + if(istype(A, /obj/item/storage) || istype(A, /obj/structure/table) || istype(A, /obj/structure/rack) || istype(A, /obj/structure/closet) \ + || istype(A, /obj/item/reagent_containers) || istype(A, /obj/structure/sink) || istype(A, /obj/structure/janitorialcart) || istype(A, /obj/machinery/hydroponics)) + return + + if(istype(A, /obj/effect/proc_holder/spell)) + return + + if(istype(A, /obj/structure/reagent_dispensers) && get_dist(src,A) <= 1) //this block copypasted from reagent_containers/glass, for lack of a better solution + if(!A.reagents.total_volume && A.reagents) + to_chat(user, "[A] is empty.") + return + + if(reagents.total_volume >= reagents.maximum_volume) + to_chat(user, "[src] is full.") + return + + var/trans = A.reagents.trans_to(src, 50) //This is a static amount, otherwise, it'll take forever to fill. + to_chat(user, "You fill [src] with [trans] units of the contents of [A].") + return + + if(reagents.total_volume < amount_per_transfer_from_this) + to_chat(user, "[src] is empty!") + return + + var/contents_log = reagents.reagent_list.Join(", ") + spray(A) + + playsound(loc, 'sound/effects/spray2.ogg', 50, 1, -6) + user.changeNext_move(CLICK_CD_RANGE*2) + user.newtonian_move(get_dir(A, user)) + + if(reagents.reagent_list.len == 1 && reagents.has_reagent("cleaner")) // Only show space cleaner logs if it's burning people from being too hot or cold + if(reagents.chem_temp < 300 && reagents.chem_temp > 280) // 280 is the cold threshold for slimes, 300 the hot threshold for drask + return + + var/attack_log_type = ATKLOG_MOST + if(reagents.has_reagent("sacid") || reagents.has_reagent("facid") || reagents.has_reagent("lube")) + attack_log_type = ATKLOG_FEW + msg_admin_attack("[key_name_admin(user)] used a spray bottle at [COORD(user)] - Contents: [contents_log] - Temperature: [reagents.chem_temp]K", attack_log_type) + log_game("[key_name(user)] used a spray bottle at [COORD(user)] - Contents: [contents_log] - Temperature: [reagents.chem_temp]K") + return + + +/obj/item/reagent_containers/spray/proc/spray(var/atom/A) + var/obj/effect/decal/chempuff/D = new /obj/effect/decal/chempuff(get_turf(src)) + D.create_reagents(amount_per_transfer_from_this) + reagents.trans_to(D, amount_per_transfer_from_this, 1/spray_currentrange) + D.icon += mix_color_from_reagents(D.reagents.reagent_list) + spawn(0) + for(var/i=0, iYou [amount_per_transfer_from_this == 10 ? "remove" : "fix"] the nozzle. You'll now use [amount_per_transfer_from_this] units per spray.") + +/obj/item/reagent_containers/spray/examine(mob/user) + . = ..() + if(get_dist(user, src) && user == loc) + . += "[round(reagents.total_volume)] units left." + +/obj/item/reagent_containers/spray/verb/empty() + + set name = "Empty Spray Bottle" + set category = "Object" + set src in usr + if(usr.stat || !usr.canmove || usr.restrained()) + return + if(alert(usr, "Are you sure you want to empty that?", "Empty Bottle:", "Yes", "No") != "Yes") + return + if(isturf(usr.loc) && loc == usr) + to_chat(usr, "You empty [src] onto the floor.") + reagents.reaction(usr.loc) + reagents.clear_reagents() + +//space cleaner +/obj/item/reagent_containers/spray/cleaner + name = "space cleaner" + desc = "BLAM!-brand non-foaming space cleaner!" + list_reagents = list("cleaner" = 250) + +/obj/item/reagent_containers/spray/cleaner/drone + name = "space cleaner" + desc = "BLAM!-brand non-foaming space cleaner!" + volume = 50 + list_reagents = list("cleaner" = 50) + +//spray tan +/obj/item/reagent_containers/spray/spraytan + name = "spray tan" + volume = 50 + desc = "Gyaro brand spray tan. Do not spray near eyes or other orifices." + list_reagents = list("spraytan" = 50) + +//pepperspray +/obj/item/reagent_containers/spray/pepper + name = "pepperspray" + desc = "Manufactured by UhangInc, used to blind and down an opponent quickly." + icon = 'icons/obj/items.dmi' + icon_state = "pepperspray" + item_state = "pepperspray" + volume = 40 + spray_maxrange = 4 + amount_per_transfer_from_this = 5 + list_reagents = list("condensedcapsaicin" = 40) + +//water flower +/obj/item/reagent_containers/spray/waterflower + name = "water flower" + desc = "A seemingly innocent sunflower...with a twist." + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "sunflower" + item_state = "sunflower" + amount_per_transfer_from_this = 1 + volume = 10 + list_reagents = list("water" = 10) + +/obj/item/reagent_containers/spray/waterflower/attack_self(mob/user) //Don't allow changing how much the flower sprays + return + +//chemsprayer +/obj/item/reagent_containers/spray/chemsprayer + name = "chem sprayer" + desc = "A utility used to spray large amounts of reagents in a given area." + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "chemsprayer" + item_state = "chemsprayer" + throwforce = 0 + w_class = WEIGHT_CLASS_NORMAL + spray_maxrange = 7 + spray_currentrange = 7 + amount_per_transfer_from_this = 10 + volume = 600 + origin_tech = "combat=3;materials=3;engineering=3" + + +/obj/item/reagent_containers/spray/chemsprayer/spray(var/atom/A) + var/Sprays[3] + for(var/i=1, i<=3, i++) // intialize sprays + if(reagents.total_volume < 1) break + var/obj/effect/decal/chempuff/D = new/obj/effect/decal/chempuff(get_turf(src)) + D.create_reagents(amount_per_transfer_from_this) + reagents.trans_to(D, amount_per_transfer_from_this) + + D.icon += mix_color_from_reagents(D.reagents.reagent_list) + + Sprays[i] = D + + var/direction = get_dir(src, A) + var/turf/T = get_turf(A) + var/turf/T1 = get_step(T,turn(direction, 90)) + var/turf/T2 = get_step(T,turn(direction, -90)) + var/list/the_targets = list(T,T1,T2) + + for(var/i=1, i<=Sprays.len, i++) + spawn() + var/obj/effect/decal/chempuff/D = Sprays[i] + if(!D) continue + + // Spreads the sprays a little bit + var/turf/my_target = pick(the_targets) + the_targets -= my_target + + for(var/j=0, j<=spray_currentrange, j++) + step_towards(D, my_target) + D.reagents.reaction(get_turf(D)) + for(var/atom/t in get_turf(D)) + D.reagents.reaction(t) + sleep(2) + qdel(D) + + + +/obj/item/reagent_containers/spray/chemsprayer/attack_self(var/mob/user) + + amount_per_transfer_from_this = (amount_per_transfer_from_this == 10 ? 5 : 10) + to_chat(user, "You adjust the output switch. You'll now use [amount_per_transfer_from_this] units per spray.") + + +// Plant-B-Gone +/obj/item/reagent_containers/spray/plantbgone // -- Skie + name = "Plant-B-Gone" + desc = "Kills those pesky weeds!" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "plantbgone" + item_state = "plantbgone" + volume = 100 + list_reagents = list("glyphosate" = 100) diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm index 6a84de6390b..e87e72d8750 100644 --- a/code/modules/reagents/reagent_containers/syringes.dm +++ b/code/modules/reagents/reagent_containers/syringes.dm @@ -1,234 +1,234 @@ -#define SYRINGE_DRAW 0 -#define SYRINGE_INJECT 1 -#define SYRINGE_BROKEN 2 - -/obj/item/reagent_containers/syringe - name = "Syringe" - desc = "A syringe." - icon = 'icons/goonstation/objects/syringe.dmi' - item_state = "syringe_0" - icon_state = "0" - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list() - volume = 15 - sharp = TRUE - var/busy = FALSE - var/mode = SYRINGE_DRAW - var/projectile_type = /obj/item/projectile/bullet/dart/syringe - materials = list(MAT_METAL=10, MAT_GLASS=20) - container_type = TRANSPARENT - -/obj/item/reagent_containers/syringe/New() - ..() - if(list_reagents) //syringe starts in inject mode if its already got something inside - mode = SYRINGE_INJECT - update_icon() - -/obj/item/reagent_containers/syringe/on_reagent_change() - update_icon() - -/obj/item/reagent_containers/syringe/pickup(mob/user) - . = ..() - update_icon() - -/obj/item/reagent_containers/syringe/dropped(mob/user) - ..() - update_icon() - -/obj/item/reagent_containers/syringe/attack_self(mob/user) - mode = !mode - update_icon() - -/obj/item/reagent_containers/syringe/attack_hand() - ..() - update_icon() - -/obj/item/reagent_containers/syringe/attack(mob/living/M, mob/living/user, def_zone) - return - -/obj/item/reagent_containers/syringe/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/storage/bag)) - ..() - -/obj/item/reagent_containers/syringe/afterattack(atom/target, mob/user , proximity) - if(!proximity) - return - if(!target.reagents) - return - - var/mob/living/L - if(isliving(target)) - L = target - if(!L.can_inject(user, TRUE)) - return - - switch(mode) - if(SYRINGE_DRAW) - - if(reagents.holder_full()) - to_chat(user, "The syringe is full.") - return - - if(L) //living mob - var/drawn_amount = reagents.maximum_volume - reagents.total_volume - if(target != user) - target.visible_message("[user] is trying to take a blood sample from [target]!", \ - "[user] is trying to take a blood sample from [target]!") - busy = TRUE - if(!do_mob(user, target)) - busy = FALSE - return - if(reagents.holder_full()) - return - busy = FALSE - if(L.transfer_blood_to(src, drawn_amount)) - user.visible_message("[user] takes a blood sample from [L].") - else - to_chat(user, "You are unable to draw any blood from [L]!") - - else //if not mob - if(!target.reagents.total_volume) - to_chat(user, "[target] is empty!") - return - - if(!target.is_drawable(user)) - to_chat(user, "You cannot directly remove reagents from [target]!") - return - - var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) // transfer from, transfer to - who cares? - - to_chat(user, "You fill [src] with [trans] units of the solution. It now contains [reagents.total_volume] units.") - if(reagents.holder_full()) - mode = !mode - update_icon() - - if(SYRINGE_INJECT) - if(!reagents.total_volume) - to_chat(user, "[src] is empty.") - return - - if(!L && !target.is_injectable(user)) //only checks on non-living mobs, due to how can_inject() handles - to_chat(user, "You cannot directly fill [target]!") - return - - if(target.reagents.total_volume >= target.reagents.maximum_volume) - to_chat(user, "[target] is full.") - return - - if(L) //living mob - if(!L.can_inject(user, TRUE)) - return - if(L != user) - L.visible_message("[user] is trying to inject [L]!", \ - "[user] is trying to inject you!") - if(!do_mob(user, L)) - return - if(!reagents.total_volume) - return - if(L.reagents.total_volume >= L.reagents.maximum_volume) - return - L.visible_message("[user] injects [L] with the syringe!", \ - "[user] injects [L] with the syringe!") - - var/list/rinject = list() - for(var/datum/reagent/R in reagents.reagent_list) - rinject += R.name - var/contained = english_list(rinject) - - add_attack_logs(user, L, "Injected with [name] containing [contained], transfered [amount_per_transfer_from_this] units", reagents.harmless_helper() ? ATKLOG_ALMOSTALL : null) - - var/fraction = min(amount_per_transfer_from_this / reagents.total_volume, 1) - reagents.reaction(L, REAGENT_INGEST, fraction) - reagents.trans_to(target, amount_per_transfer_from_this) - to_chat(user, "You inject [amount_per_transfer_from_this] units of the solution. The syringe now contains [reagents.total_volume] units.") - if(reagents.total_volume <= 0 && mode == SYRINGE_INJECT) - mode = SYRINGE_DRAW - update_icon() - -/obj/item/reagent_containers/syringe/update_icon() - cut_overlays() - var/rounded_vol - if(reagents && reagents.total_volume) - rounded_vol = Clamp(round((reagents.total_volume / volume * 15), 5), 1, 15) - var/image/filling_overlay = mutable_appearance('icons/obj/reagentfillings.dmi', "syringe[rounded_vol]") - filling_overlay.icon += mix_color_from_reagents(reagents.reagent_list) - add_overlay(filling_overlay) - else - rounded_vol = 0 - icon_state = "[rounded_vol]" - item_state = "syringe_[rounded_vol]" - if(ismob(loc)) - var/mob/M = loc - var/injoverlay - switch(mode) - if(SYRINGE_DRAW) - injoverlay = "draw" - if(SYRINGE_INJECT) - injoverlay = "inject" - add_overlay(injoverlay) - M.update_inv_l_hand() - M.update_inv_r_hand() - -/obj/item/reagent_containers/syringe/antiviral - name = "Syringe (spaceacillin)" - desc = "Contains antiviral agents." - list_reagents = list("spaceacillin" = 15) - -/obj/item/reagent_containers/syringe/charcoal - name = "Syringe (charcoal)" - desc = "Contains charcoal - used to treat toxins and damage from toxins." - list_reagents = list("charcoal" = 15) - -/obj/item/reagent_containers/syringe/epinephrine - name = "Syringe (Epinephrine)" - desc = "Contains epinephrine - used to stabilize patients." - list_reagents = list("epinephrine" = 15) - -/obj/item/reagent_containers/syringe/insulin - name = "Syringe (insulin)" - desc = "Contains insulin - used to treat diabetes." - list_reagents = list("insulin" = 15) - -/obj/item/reagent_containers/syringe/calomel - name = "Syringe (calomel)" - desc = "Contains calomel, which be used to purge impurities, but is highly toxic itself." - list_reagents = list("calomel" = 15) - -/obj/item/reagent_containers/syringe/heparin - name = "Syringe (heparin)" - desc = "Contains heparin, a blood anticoagulant." - list_reagents = list("heparin" = 15) - -/obj/item/reagent_containers/syringe/bioterror - name = "bioterror syringe" - desc = "Contains several paralyzing reagents." - list_reagents = list("neurotoxin" = 5, "capulettium_plus" = 5, "sodium_thiopental" = 5) - -/obj/item/reagent_containers/syringe/gluttony - name = "Gluttony's Blessing" - desc = "A syringe recovered from a dread place. It probably isn't wise to use." - amount_per_transfer_from_this = 1 - volume = 1 - list_reagents = list("gluttonytoxin" = 1) - -/obj/item/reagent_containers/syringe/capulettium_plus - name = "capulettium plus syringe" - desc = "For silencing targets. Allows for fake deaths." - list_reagents = list("capulettium_plus" = 15) - -/obj/item/reagent_containers/syringe/sarin - name = "sarin syringe" - desc = "A deadly neurotoxin, for killing." - list_reagents = list("sarin" = 15) - -/obj/item/reagent_containers/syringe/pancuronium - name = "pancuronium syringe" - desc = "A powerful paralyzing poison." - list_reagents = list("pancuronium" = 15) - -/obj/item/reagent_containers/syringe/lethal - name = "lethal injection syringe" - desc = "A syringe used for lethal injections. It can hold up to 50 units." - amount_per_transfer_from_this = 50 - volume = 50 - list_reagents = list("toxin" = 15, "pancuronium" = 10, "cyanide" = 5, "facid" = 10, "fluorine" = 10) \ No newline at end of file +#define SYRINGE_DRAW 0 +#define SYRINGE_INJECT 1 +#define SYRINGE_BROKEN 2 + +/obj/item/reagent_containers/syringe + name = "Syringe" + desc = "A syringe." + icon = 'icons/goonstation/objects/syringe.dmi' + item_state = "syringe_0" + icon_state = "0" + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list() + volume = 15 + sharp = TRUE + var/busy = FALSE + var/mode = SYRINGE_DRAW + var/projectile_type = /obj/item/projectile/bullet/dart/syringe + materials = list(MAT_METAL=10, MAT_GLASS=20) + container_type = TRANSPARENT + +/obj/item/reagent_containers/syringe/New() + ..() + if(list_reagents) //syringe starts in inject mode if its already got something inside + mode = SYRINGE_INJECT + update_icon() + +/obj/item/reagent_containers/syringe/on_reagent_change() + update_icon() + +/obj/item/reagent_containers/syringe/pickup(mob/user) + . = ..() + update_icon() + +/obj/item/reagent_containers/syringe/dropped(mob/user) + ..() + update_icon() + +/obj/item/reagent_containers/syringe/attack_self(mob/user) + mode = !mode + update_icon() + +/obj/item/reagent_containers/syringe/attack_hand() + ..() + update_icon() + +/obj/item/reagent_containers/syringe/attack(mob/living/M, mob/living/user, def_zone) + return + +/obj/item/reagent_containers/syringe/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/storage/bag)) + ..() + +/obj/item/reagent_containers/syringe/afterattack(atom/target, mob/user , proximity) + if(!proximity) + return + if(!target.reagents) + return + + var/mob/living/L + if(isliving(target)) + L = target + if(!L.can_inject(user, TRUE)) + return + + switch(mode) + if(SYRINGE_DRAW) + + if(reagents.holder_full()) + to_chat(user, "The syringe is full.") + return + + if(L) //living mob + var/drawn_amount = reagents.maximum_volume - reagents.total_volume + if(target != user) + target.visible_message("[user] is trying to take a blood sample from [target]!", \ + "[user] is trying to take a blood sample from [target]!") + busy = TRUE + if(!do_mob(user, target)) + busy = FALSE + return + if(reagents.holder_full()) + return + busy = FALSE + if(L.transfer_blood_to(src, drawn_amount)) + user.visible_message("[user] takes a blood sample from [L].") + else + to_chat(user, "You are unable to draw any blood from [L]!") + + else //if not mob + if(!target.reagents.total_volume) + to_chat(user, "[target] is empty!") + return + + if(!target.is_drawable(user)) + to_chat(user, "You cannot directly remove reagents from [target]!") + return + + var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) // transfer from, transfer to - who cares? + + to_chat(user, "You fill [src] with [trans] units of the solution. It now contains [reagents.total_volume] units.") + if(reagents.holder_full()) + mode = !mode + update_icon() + + if(SYRINGE_INJECT) + if(!reagents.total_volume) + to_chat(user, "[src] is empty.") + return + + if(!L && !target.is_injectable(user)) //only checks on non-living mobs, due to how can_inject() handles + to_chat(user, "You cannot directly fill [target]!") + return + + if(target.reagents.total_volume >= target.reagents.maximum_volume) + to_chat(user, "[target] is full.") + return + + if(L) //living mob + if(!L.can_inject(user, TRUE)) + return + if(L != user) + L.visible_message("[user] is trying to inject [L]!", \ + "[user] is trying to inject you!") + if(!do_mob(user, L)) + return + if(!reagents.total_volume) + return + if(L.reagents.total_volume >= L.reagents.maximum_volume) + return + L.visible_message("[user] injects [L] with the syringe!", \ + "[user] injects [L] with the syringe!") + + var/list/rinject = list() + for(var/datum/reagent/R in reagents.reagent_list) + rinject += R.name + var/contained = english_list(rinject) + + add_attack_logs(user, L, "Injected with [name] containing [contained], transfered [amount_per_transfer_from_this] units", reagents.harmless_helper() ? ATKLOG_ALMOSTALL : null) + + var/fraction = min(amount_per_transfer_from_this / reagents.total_volume, 1) + reagents.reaction(L, REAGENT_INGEST, fraction) + reagents.trans_to(target, amount_per_transfer_from_this) + to_chat(user, "You inject [amount_per_transfer_from_this] units of the solution. The syringe now contains [reagents.total_volume] units.") + if(reagents.total_volume <= 0 && mode == SYRINGE_INJECT) + mode = SYRINGE_DRAW + update_icon() + +/obj/item/reagent_containers/syringe/update_icon() + cut_overlays() + var/rounded_vol + if(reagents && reagents.total_volume) + rounded_vol = Clamp(round((reagents.total_volume / volume * 15), 5), 1, 15) + var/image/filling_overlay = mutable_appearance('icons/obj/reagentfillings.dmi', "syringe[rounded_vol]") + filling_overlay.icon += mix_color_from_reagents(reagents.reagent_list) + add_overlay(filling_overlay) + else + rounded_vol = 0 + icon_state = "[rounded_vol]" + item_state = "syringe_[rounded_vol]" + if(ismob(loc)) + var/mob/M = loc + var/injoverlay + switch(mode) + if(SYRINGE_DRAW) + injoverlay = "draw" + if(SYRINGE_INJECT) + injoverlay = "inject" + add_overlay(injoverlay) + M.update_inv_l_hand() + M.update_inv_r_hand() + +/obj/item/reagent_containers/syringe/antiviral + name = "Syringe (spaceacillin)" + desc = "Contains antiviral agents." + list_reagents = list("spaceacillin" = 15) + +/obj/item/reagent_containers/syringe/charcoal + name = "Syringe (charcoal)" + desc = "Contains charcoal - used to treat toxins and damage from toxins." + list_reagents = list("charcoal" = 15) + +/obj/item/reagent_containers/syringe/epinephrine + name = "Syringe (Epinephrine)" + desc = "Contains epinephrine - used to stabilize patients." + list_reagents = list("epinephrine" = 15) + +/obj/item/reagent_containers/syringe/insulin + name = "Syringe (insulin)" + desc = "Contains insulin - used to treat diabetes." + list_reagents = list("insulin" = 15) + +/obj/item/reagent_containers/syringe/calomel + name = "Syringe (calomel)" + desc = "Contains calomel, which be used to purge impurities, but is highly toxic itself." + list_reagents = list("calomel" = 15) + +/obj/item/reagent_containers/syringe/heparin + name = "Syringe (heparin)" + desc = "Contains heparin, a blood anticoagulant." + list_reagents = list("heparin" = 15) + +/obj/item/reagent_containers/syringe/bioterror + name = "bioterror syringe" + desc = "Contains several paralyzing reagents." + list_reagents = list("neurotoxin" = 5, "capulettium_plus" = 5, "sodium_thiopental" = 5) + +/obj/item/reagent_containers/syringe/gluttony + name = "Gluttony's Blessing" + desc = "A syringe recovered from a dread place. It probably isn't wise to use." + amount_per_transfer_from_this = 1 + volume = 1 + list_reagents = list("gluttonytoxin" = 1) + +/obj/item/reagent_containers/syringe/capulettium_plus + name = "capulettium plus syringe" + desc = "For silencing targets. Allows for fake deaths." + list_reagents = list("capulettium_plus" = 15) + +/obj/item/reagent_containers/syringe/sarin + name = "sarin syringe" + desc = "A deadly neurotoxin, for killing." + list_reagents = list("sarin" = 15) + +/obj/item/reagent_containers/syringe/pancuronium + name = "pancuronium syringe" + desc = "A powerful paralyzing poison." + list_reagents = list("pancuronium" = 15) + +/obj/item/reagent_containers/syringe/lethal + name = "lethal injection syringe" + desc = "A syringe used for lethal injections. It can hold up to 50 units." + amount_per_transfer_from_this = 50 + volume = 50 + list_reagents = list("toxin" = 15, "pancuronium" = 10, "cyanide" = 5, "facid" = 10, "fluorine" = 10) diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 1c74be11965..194675a21d8 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -1,278 +1,278 @@ -/obj/structure/reagent_dispensers - name = "Dispenser" - desc = "..." - icon = 'icons/obj/objects.dmi' - icon_state = "watertank" - density = 1 - anchored = 0 - pressure_resistance = 2*ONE_ATMOSPHERE - container_type = DRAINABLE | AMOUNT_VISIBLE - max_integrity = 300 - var/tank_volume = 1000 //In units, how much the dispenser can hold - var/reagent_id = "water" //The ID of the reagent that the dispenser uses - var/lastrigger = "" // The last person to rig this fuel tank - Stored with the object. Only the last person matter for investigation - -/obj/structure/reagent_dispensers/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(. && obj_integrity > 0) - if(tank_volume && (damage_flag == "bullet" || damage_flag == "laser")) - boom() - -/obj/structure/reagent_dispensers/attackby(obj/item/I, mob/user, params) - if(I.is_refillable()) - return FALSE //so we can refill them via their afterattack. - return ..() - -/obj/structure/reagent_dispensers/New() - create_reagents(tank_volume) - reagents.add_reagent(reagent_id, tank_volume) - ..() - -/obj/structure/reagent_dispensers/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - ..() - if(reagents) - for(var/i in 1 to 8) - if(reagents) - reagents.temperature_reagents(exposed_temperature) - -/obj/structure/reagent_dispensers/proc/boom() - visible_message("[src] ruptures!") - chem_splash(loc, 5, list(reagents)) - qdel(src) - -/obj/structure/reagent_dispensers/deconstruct(disassembled = TRUE) - if(!(flags & NODECONSTRUCT)) - if(!disassembled) - boom() - else - qdel(src) - -//Dispensers -/obj/structure/reagent_dispensers/watertank - name = "water tank" - desc = "A water tank." - icon_state = "water" - -/obj/structure/reagent_dispensers/watertank/high - name = "high-capacity water tank" - desc = "A highly-pressurized water tank made to hold gargantuan amounts of water.." - icon_state = "water_high" //I was gonna clean my room... - tank_volume = 100000 - - -/obj/structure/reagent_dispensers/oil - name = "oil tank" - desc = "A tank of oil, commonly used to by robotics to fix leaking IPCs or just to loosen up those rusted underused parts." - icon_state = "oil" - reagent_id = "oil" - tank_volume = 3000 - -/obj/structure/reagent_dispensers/fueltank - name = "fuel tank" - desc = "A tank full of industrial welding fuel. Do not consume." - icon_state = "fuel" - reagent_id = "fuel" - tank_volume = 4000 - var/obj/item/assembly_holder/rig = null - var/accepts_rig = 1 - -/obj/structure/reagent_dispensers/fueltank/Destroy() - QDEL_NULL(rig) - return ..() - -/obj/structure/reagent_dispensers/fueltank/bullet_act(obj/item/projectile/P) - ..() - 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)] ") - 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 - 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)]") - investigate_log("A fueltank, last rigged by [lastrigger], triggered at [COORD(loc)]", INVESTIGATE_BOMB) - if(reagents) - reagents.set_reagent_temp(1000) //uh-oh - qdel(src) - -/obj/structure/reagent_dispensers/fueltank/blob_act(obj/structure/blob/B) - boom() - -/obj/structure/reagent_dispensers/fueltank/ex_act() - boom() - -/obj/structure/reagent_dispensers/fueltank/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) - ..() - boom() - -/obj/structure/reagent_dispensers/fueltank/tesla_act() - ..() //extend the zap - boom() - -/obj/structure/reagent_dispensers/fueltank/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 2 && rig) - . += "There is some kind of device rigged to the tank." - -/obj/structure/reagent_dispensers/fueltank/attack_hand() - if(rig) - usr.visible_message("[usr] begins to detach [rig] from [src].", "You begin to detach [rig] from [src].") - if(do_after(usr, 20, target = src)) - usr.visible_message("[usr] detaches [rig] from [src].", "You detach [rig] from [src].") - rig.forceMove(get_turf(usr)) - rig = null - lastrigger = null - overlays.Cut() - -/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/assembly_holder) && accepts_rig) - if(rig) - to_chat(user, "There is another device in the way.") - return ..() - user.visible_message("[user] begins rigging [I] to [src].", "You begin rigging [I] to [src]") - if(do_after(user, 20, target = src)) - user.visible_message("[user] rigs [I] to [src].", "You rig [I] to [src].") - - var/obj/item/assembly_holder/H = I - 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)]") - investigate_log("[key_name(user)] rigged [src.name] with [I.name] for explosion at [COORD(loc)]", INVESTIGATE_BOMB) - - lastrigger = "[key_name(user)]" - rig = H - user.drop_item() - H.forceMove(src) - - var/icon/test = getFlatIcon(H) - test.Shift(NORTH, 1) - test.Shift(EAST, 6) - overlays += test - else - return ..() - -obj/structure/reagent_dispensers/fueltank/welder_act(mob/user, obj/item/I) - . = TRUE - if(!reagents.has_reagent("fuel")) - to_chat(user, "[src] is out of fuel!") - return - if(I.tool_enabled && I.use_tool(src, user, volume = I.tool_volume)) //check it's enabled first to prevent duplicate messages when refuelling - 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)]") - investigate_log("[key_name(user)] triggered a fueltank explosion at [COORD(loc)]", INVESTIGATE_BOMB) - boom() - else - I.refill(user, src, reagents.get_reagent_amount("fuel")) //Try dump all fuel into the welder - - -/obj/structure/reagent_dispensers/fueltank/Move() - ..() - if(rig) - rig.process_movement() - -/obj/structure/reagent_dispensers/fueltank/HasProximity(atom/movable/AM) - if(rig) - rig.HasProximity(AM) - -/obj/structure/reagent_dispensers/fueltank/Crossed(atom/movable/AM, oldloc) - if(rig) - rig.Crossed(AM, oldloc) - -/obj/structure/reagent_dispensers/fueltank/hear_talk(mob/living/M, list/message_pieces) - if(rig) - rig.hear_talk(M, message_pieces) - -/obj/structure/reagent_dispensers/fueltank/hear_message(mob/living/M, msg) - if(rig) - rig.hear_message(M, msg) - -/obj/structure/reagent_dispensers/fueltank/Bump() - ..() - if(rig) - rig.process_movement() - - -/obj/structure/reagent_dispensers/peppertank - name = "pepper spray refiller" - desc = "Contains condensed capsaicin for use in law \"enforcement.\"" - icon_state = "pepper" - anchored = 1 - density = 0 - reagent_id = "condensedcapsaicin" - -/obj/structure/reagent_dispensers/water_cooler - name = "liquid cooler" - desc = "A machine that dispenses liquid to drink." - icon = 'icons/obj/vending.dmi' - icon_state = "water_cooler" - anchored = 1 - tank_volume = 500 - reagent_id = "water" - var/paper_cups = 25 //Paper cups left from the cooler - -/obj/structure/reagent_dispensers/water_cooler/examine(mob/user) - . = ..() - if(get_dist(user, src) <= 2) - . += "There are [paper_cups ? paper_cups : "no"] paper cups left." - -/obj/structure/reagent_dispensers/water_cooler/attack_hand(mob/living/user) - if(!paper_cups) - to_chat(user, "There aren't any cups left!") - return - user.visible_message("[user] takes a cup from [src].", "You take a paper cup from [src].") - var/obj/item/reagent_containers/food/drinks/sillycup/S = new(get_turf(src)) - user.put_in_hands(S) - paper_cups-- - -/obj/structure/reagent_dispensers/water_cooler/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - default_unfasten_wrench(user, I, 40) - -/obj/structure/reagent_dispensers/beerkeg - name = "beer keg" - desc = "Beer is liquid bread, it's good for you..." - icon_state = "beer" - reagent_id = "beer" - -/obj/structure/reagent_dispensers/beerkeg/blob_act(obj/structure/blob/B) - explosion(loc, 0, 3, 5, 7, 10) - if(!QDELETED(src)) - qdel(src) - -/obj/structure/reagent_dispensers/beerkeg/nuke - name = "Nanotrasen-brand nuclear fission explosive" - desc = "One of the more successful achievements of the Nanotrasen Corporate Warfare Division, their nuclear fission explosives are renowned for being cheap\ - to produce and devestatingly effective. Signs explain that though this is just a model, every Nanotrasen station is equipped with one, just in case. \ - All Captains carefully guard the disk needed to detonate them - at least, the sign says they do. There seems to be a tap on the back." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "nuclearbomb0" - -/obj/structure/reagent_dispensers/virusfood - name = "virus food dispenser" - desc = "A dispenser of low-potency virus mutagenic." - icon_state = "virus_food" - anchored = 1 - density = 0 - reagent_id = "virusfood" - -/obj/structure/reagent_dispensers/spacecleanertank - name = "space cleaner refiller" - desc = "Refills space cleaner bottles." - icon_state = "cleaner" - anchored = 1 - density = 0 - tank_volume = 5000 - reagent_id = "cleaner" - -/obj/structure/reagent_dispensers/fueltank/chem - icon_state = "fuel_chem" - anchored = 1 - density = 0 - accepts_rig = 0 - tank_volume = 1000 +/obj/structure/reagent_dispensers + name = "Dispenser" + desc = "..." + icon = 'icons/obj/objects.dmi' + icon_state = "watertank" + density = 1 + anchored = 0 + pressure_resistance = 2*ONE_ATMOSPHERE + container_type = DRAINABLE | AMOUNT_VISIBLE + max_integrity = 300 + var/tank_volume = 1000 //In units, how much the dispenser can hold + var/reagent_id = "water" //The ID of the reagent that the dispenser uses + var/lastrigger = "" // The last person to rig this fuel tank - Stored with the object. Only the last person matter for investigation + +/obj/structure/reagent_dispensers/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(. && obj_integrity > 0) + if(tank_volume && (damage_flag == "bullet" || damage_flag == "laser")) + boom() + +/obj/structure/reagent_dispensers/attackby(obj/item/I, mob/user, params) + if(I.is_refillable()) + return FALSE //so we can refill them via their afterattack. + return ..() + +/obj/structure/reagent_dispensers/New() + create_reagents(tank_volume) + reagents.add_reagent(reagent_id, tank_volume) + ..() + +/obj/structure/reagent_dispensers/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + ..() + if(reagents) + for(var/i in 1 to 8) + if(reagents) + reagents.temperature_reagents(exposed_temperature) + +/obj/structure/reagent_dispensers/proc/boom() + visible_message("[src] ruptures!") + chem_splash(loc, 5, list(reagents)) + qdel(src) + +/obj/structure/reagent_dispensers/deconstruct(disassembled = TRUE) + if(!(flags & NODECONSTRUCT)) + if(!disassembled) + boom() + else + qdel(src) + +//Dispensers +/obj/structure/reagent_dispensers/watertank + name = "water tank" + desc = "A water tank." + icon_state = "water" + +/obj/structure/reagent_dispensers/watertank/high + name = "high-capacity water tank" + desc = "A highly-pressurized water tank made to hold gargantuan amounts of water.." + icon_state = "water_high" //I was gonna clean my room... + tank_volume = 100000 + + +/obj/structure/reagent_dispensers/oil + name = "oil tank" + desc = "A tank of oil, commonly used to by robotics to fix leaking IPCs or just to loosen up those rusted underused parts." + icon_state = "oil" + reagent_id = "oil" + tank_volume = 3000 + +/obj/structure/reagent_dispensers/fueltank + name = "fuel tank" + desc = "A tank full of industrial welding fuel. Do not consume." + icon_state = "fuel" + reagent_id = "fuel" + tank_volume = 4000 + var/obj/item/assembly_holder/rig = null + var/accepts_rig = 1 + +/obj/structure/reagent_dispensers/fueltank/Destroy() + QDEL_NULL(rig) + return ..() + +/obj/structure/reagent_dispensers/fueltank/bullet_act(obj/item/projectile/P) + ..() + 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)] ") + 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 + 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)]") + investigate_log("A fueltank, last rigged by [lastrigger], triggered at [COORD(loc)]", INVESTIGATE_BOMB) + if(reagents) + reagents.set_reagent_temp(1000) //uh-oh + qdel(src) + +/obj/structure/reagent_dispensers/fueltank/blob_act(obj/structure/blob/B) + boom() + +/obj/structure/reagent_dispensers/fueltank/ex_act() + boom() + +/obj/structure/reagent_dispensers/fueltank/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = TRUE) + ..() + boom() + +/obj/structure/reagent_dispensers/fueltank/tesla_act() + ..() //extend the zap + boom() + +/obj/structure/reagent_dispensers/fueltank/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 2 && rig) + . += "There is some kind of device rigged to the tank." + +/obj/structure/reagent_dispensers/fueltank/attack_hand() + if(rig) + usr.visible_message("[usr] begins to detach [rig] from [src].", "You begin to detach [rig] from [src].") + if(do_after(usr, 20, target = src)) + usr.visible_message("[usr] detaches [rig] from [src].", "You detach [rig] from [src].") + rig.forceMove(get_turf(usr)) + rig = null + lastrigger = null + overlays.Cut() + +/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/assembly_holder) && accepts_rig) + if(rig) + to_chat(user, "There is another device in the way.") + return ..() + user.visible_message("[user] begins rigging [I] to [src].", "You begin rigging [I] to [src]") + if(do_after(user, 20, target = src)) + user.visible_message("[user] rigs [I] to [src].", "You rig [I] to [src].") + + var/obj/item/assembly_holder/H = I + 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)]") + investigate_log("[key_name(user)] rigged [src.name] with [I.name] for explosion at [COORD(loc)]", INVESTIGATE_BOMB) + + lastrigger = "[key_name(user)]" + rig = H + user.drop_item() + H.forceMove(src) + + var/icon/test = getFlatIcon(H) + test.Shift(NORTH, 1) + test.Shift(EAST, 6) + overlays += test + else + return ..() + +obj/structure/reagent_dispensers/fueltank/welder_act(mob/user, obj/item/I) + . = TRUE + if(!reagents.has_reagent("fuel")) + to_chat(user, "[src] is out of fuel!") + return + if(I.tool_enabled && I.use_tool(src, user, volume = I.tool_volume)) //check it's enabled first to prevent duplicate messages when refuelling + 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)]") + investigate_log("[key_name(user)] triggered a fueltank explosion at [COORD(loc)]", INVESTIGATE_BOMB) + boom() + else + I.refill(user, src, reagents.get_reagent_amount("fuel")) //Try dump all fuel into the welder + + +/obj/structure/reagent_dispensers/fueltank/Move() + ..() + if(rig) + rig.process_movement() + +/obj/structure/reagent_dispensers/fueltank/HasProximity(atom/movable/AM) + if(rig) + rig.HasProximity(AM) + +/obj/structure/reagent_dispensers/fueltank/Crossed(atom/movable/AM, oldloc) + if(rig) + rig.Crossed(AM, oldloc) + +/obj/structure/reagent_dispensers/fueltank/hear_talk(mob/living/M, list/message_pieces) + if(rig) + rig.hear_talk(M, message_pieces) + +/obj/structure/reagent_dispensers/fueltank/hear_message(mob/living/M, msg) + if(rig) + rig.hear_message(M, msg) + +/obj/structure/reagent_dispensers/fueltank/Bump() + ..() + if(rig) + rig.process_movement() + + +/obj/structure/reagent_dispensers/peppertank + name = "pepper spray refiller" + desc = "Contains condensed capsaicin for use in law \"enforcement.\"" + icon_state = "pepper" + anchored = 1 + density = 0 + reagent_id = "condensedcapsaicin" + +/obj/structure/reagent_dispensers/water_cooler + name = "liquid cooler" + desc = "A machine that dispenses liquid to drink." + icon = 'icons/obj/vending.dmi' + icon_state = "water_cooler" + anchored = 1 + tank_volume = 500 + reagent_id = "water" + var/paper_cups = 25 //Paper cups left from the cooler + +/obj/structure/reagent_dispensers/water_cooler/examine(mob/user) + . = ..() + if(get_dist(user, src) <= 2) + . += "There are [paper_cups ? paper_cups : "no"] paper cups left." + +/obj/structure/reagent_dispensers/water_cooler/attack_hand(mob/living/user) + if(!paper_cups) + to_chat(user, "There aren't any cups left!") + return + user.visible_message("[user] takes a cup from [src].", "You take a paper cup from [src].") + var/obj/item/reagent_containers/food/drinks/sillycup/S = new(get_turf(src)) + user.put_in_hands(S) + paper_cups-- + +/obj/structure/reagent_dispensers/water_cooler/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + default_unfasten_wrench(user, I, 40) + +/obj/structure/reagent_dispensers/beerkeg + name = "beer keg" + desc = "Beer is liquid bread, it's good for you..." + icon_state = "beer" + reagent_id = "beer" + +/obj/structure/reagent_dispensers/beerkeg/blob_act(obj/structure/blob/B) + explosion(loc, 0, 3, 5, 7, 10) + if(!QDELETED(src)) + qdel(src) + +/obj/structure/reagent_dispensers/beerkeg/nuke + name = "Nanotrasen-brand nuclear fission explosive" + desc = "One of the more successful achievements of the Nanotrasen Corporate Warfare Division, their nuclear fission explosives are renowned for being cheap\ + to produce and devestatingly effective. Signs explain that though this is just a model, every Nanotrasen station is equipped with one, just in case. \ + All Captains carefully guard the disk needed to detonate them - at least, the sign says they do. There seems to be a tap on the back." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "nuclearbomb0" + +/obj/structure/reagent_dispensers/virusfood + name = "virus food dispenser" + desc = "A dispenser of low-potency virus mutagenic." + icon_state = "virus_food" + anchored = 1 + density = 0 + reagent_id = "virusfood" + +/obj/structure/reagent_dispensers/spacecleanertank + name = "space cleaner refiller" + desc = "Refills space cleaner bottles." + icon_state = "cleaner" + anchored = 1 + density = 0 + tank_volume = 5000 + reagent_id = "cleaner" + +/obj/structure/reagent_dispensers/fueltank/chem + icon_state = "fuel_chem" + anchored = 1 + density = 0 + accepts_rig = 0 + tank_volume = 1000 diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm index 4d242212f2c..e82db0ddc60 100644 --- a/code/modules/recycling/conveyor2.dm +++ b/code/modules/recycling/conveyor2.dm @@ -1,492 +1,492 @@ -#define DIRECTION_FORWARDS 1 -#define DIRECTION_OFF 0 -#define DIRECTION_REVERSED -1 -#define IS_OPERATING (operating && can_conveyor_run()) - -GLOBAL_LIST_INIT(conveyor_belts, list()) //Saves us having to look through the entire machines list for our things -GLOBAL_LIST_INIT(conveyor_switches, list()) - -//conveyor2 is pretty much like the original, except it supports corners, but not diverters. -//Except this is pretty heavily modified so it's more like conveyor2.5 - -/obj/machinery/conveyor - icon = 'icons/obj/recycling.dmi' - icon_state = "conveyor_stopped_cw" - name = "conveyor belt" - desc = "It's a conveyor belt, commonly used to transport large numbers of items elsewhere quite quickly." - layer = CONVEYOR_LAYER // so they appear under stuff but not below stuff like vents - anchored = TRUE - move_force = MOVE_FORCE_DEFAULT - var/operating = FALSE //NB: this can be TRUE while the belt doesn't go - var/forwards // The direction the conveyor sends you in - var/backwards // hopefully self-explanatory - var/clockwise = TRUE // For corner pieces - do we go clockwise or counterclockwise? - var/operable = TRUE // Can this belt actually go? - var/list/affecting // the list of all items that will be moved this ptick - var/reversed = FALSE // set to TRUE to have the conveyor belt be reversed - var/id //ID of the connected lever - - // create a conveyor -/obj/machinery/conveyor/New(loc, new_dir, new_id) - ..(loc) - GLOB.conveyor_belts += src - if(new_id) - id = new_id - if(new_dir) - dir = new_dir - update_move_direction() - for(var/I in GLOB.conveyor_switches) - var/obj/machinery/conveyor_switch/S = I - if(id == S.id) - S.conveyors += src - -/obj/machinery/conveyor/Destroy() - GLOB.conveyor_belts -= src - return ..() - -/obj/machinery/conveyor/setDir(newdir) - . = ..() - update_move_direction() - -// attack with item, place item on conveyor -/obj/machinery/conveyor/attackby(obj/item/I, mob/user) - if(stat & BROKEN) - return ..() - else if(istype(I, /obj/item/conveyor_switch_construct)) - var/obj/item/conveyor_switch_construct/S = I - if(S.id == id) - return ..() - for(var/obj/machinery/conveyor_switch/CS in GLOB.conveyor_switches) - if(CS.id == id) - CS.conveyors -= src - id = S.id - to_chat(user, "You link [I] with [src].") - else if(user.a_intent != INTENT_HARM) - if(user.drop_item()) - I.forceMove(loc) - else - return ..() - - -/obj/machinery/conveyor/crowbar_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(!(stat & BROKEN)) - var/obj/item/conveyor_construct/C = new(loc) - C.id = id - transfer_fingerprints_to(C) - to_chat(user,"You remove [src].") - qdel(src) - -/obj/machinery/conveyor/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - set_rotation(user) - update_move_direction() - -// attack with hand, move pulled object onto conveyor -/obj/machinery/conveyor/attack_hand(mob/user as mob) - user.Move_Pulled(src) - -/obj/machinery/conveyor/update_icon() - ..() - if(IS_OPERATING) - icon_state = "conveyor_started_[clockwise ? "cw" : "ccw"]" - if(reversed) - icon_state += "_r" - else - icon_state = "conveyor_stopped_[clockwise ? "cw" : "ccw"]" - -/obj/machinery/conveyor/proc/update_move_direction() - update_icon() - switch(dir) - if(NORTH) - forwards = NORTH - backwards = SOUTH - if(EAST) - forwards = EAST - backwards = WEST - if(SOUTH) - forwards = SOUTH - backwards = NORTH - if(WEST) - forwards = WEST - backwards = EAST - if(NORTHEAST) - forwards = clockwise ? EAST : NORTH - backwards = clockwise ? SOUTH : WEST - if(SOUTHEAST) - forwards = clockwise ? SOUTH : EAST - backwards = clockwise ? WEST : NORTH - if(SOUTHWEST) - forwards = clockwise ? WEST : SOUTH - backwards = clockwise ? NORTH : EAST - if(NORTHWEST) - forwards = clockwise ? NORTH : WEST - backwards = clockwise ? EAST : SOUTH - if(!reversed) - return - var/temporary_direction = forwards - forwards = backwards - backwards = temporary_direction - -/obj/machinery/conveyor/proc/set_rotation(mob/user) - dir = turn(reversed ? backwards : forwards, -90) //Fuck it, let's do it this way instead of doing something clever with dir - var/turf/left = get_step(src, turn(dir, 90)) //We need to get conveyors to the right, left, and behind this one to be able to determine if we need to make a corner piece - var/turf/right = get_step(src, turn(dir, -90)) - var/turf/back = get_step(src, turn(dir, 180)) - to_chat(user, "You rotate [src].") - var/obj/machinery/conveyor/CL = locate() in left - var/obj/machinery/conveyor/CR = locate() in right - var/obj/machinery/conveyor/CB = locate() in back - var/link_to_left = FALSE - var/link_to_right = FALSE - var/link_to_back = FALSE - if(CL) - if(CL.id == id && get_step(CL, CL.reversed ? CL.backwards : CL.forwards) == loc) - link_to_left = TRUE - if(CR) - if(CR.id == id && get_step(CR, CR.reversed ? CR.backwards : CR.forwards) == loc) - link_to_right = TRUE - if(CB) - if(CB.id == id && get_step(CB, CB.reversed ? CB.backwards : CB.forwards) == loc) - link_to_back = TRUE - if(link_to_back) //Don't need to do anything because we can assume the conveyor carries on in a line - return - else if(!(link_to_left ^ link_to_right)) //Either no valid conveyors point here, or two point here (making a "junction" with this belt as the middle piece). Either way we don't need a corner - return - if(link_to_right) - dir = turn(dir, 45) - clockwise = TRUE - else if(link_to_left) - dir = turn(dir, -45) - clockwise = FALSE - -/obj/machinery/conveyor/power_change() - ..() - update_icon() - -/obj/machinery/conveyor/process() - if(!IS_OPERATING) - return - use_power(100) - affecting = loc.contents - src // moved items will be all in loc - var/still_stuff_to_move = FALSE - for(var/atom/movable/AM in affecting) - if(AM.anchored) - continue - still_stuff_to_move = TRUE - addtimer(CALLBACK(src, .proc/move_thing, AM), 1) - CHECK_TICK - if(!still_stuff_to_move && speed_process) - makeNormalProcess() - else if(still_stuff_to_move && !speed_process) - makeSpeedProcess() - -/obj/machinery/conveyor/Crossed(atom/movable/AM, oldloc) - if(!speed_process && !AM.anchored) - makeSpeedProcess() - ..() - -/obj/machinery/conveyor/proc/move_thing(atom/movable/AM) - if(move_force < (AM.move_resist)) - return FALSE - if(!AM.anchored && AM.loc == loc) - step(AM, forwards) - - -/obj/machinery/conveyor/proc/can_conveyor_run() - if(stat & BROKEN) - return FALSE - else if(stat & NOPOWER) - return FALSE - else if(!operable) - return FALSE - return TRUE - -// make the conveyor broken and propagate inoperability to any connected conveyor with the same conveyor datum -/obj/machinery/conveyor/proc/make_broken() - stat |= BROKEN - operable = FALSE - update_icon() - var/obj/machinery/conveyor/C = locate() in get_step(src, forwards) - if(C) - C.set_operable(TRUE, id, FALSE) - C = locate() in get_step(src, backwards) - if(C) - C.set_operable(FALSE, id, FALSE) - -/obj/machinery/conveyor/proc/set_operable(propagate_forwards, match_id, op) //Sets a conveyor inoperable if ID matches it, and propagates forwards / backwards - if(id != match_id) - return - operable = op - update_icon() - var/obj/machinery/conveyor/C = locate() in get_step(src, propagate_forwards ? forwards : backwards) - if(C) - C.set_operable(propagate_forwards ? TRUE : FALSE, id, op) - -// the conveyor control switch - -/obj/machinery/conveyor_switch - name = "conveyor switch" - desc = "This switch controls any and all conveyor belts it is linked to." - icon = 'icons/obj/recycling.dmi' - icon_state = "switch-off" - var/position = DIRECTION_OFF - var/reversed = TRUE - var/one_way = FALSE // Do we go in one direction? - anchored = TRUE - var/id - var/list/conveyors = list() - -/obj/machinery/conveyor_switch/New(newloc, new_id) - ..(newloc) - GLOB.conveyor_switches += src - if(!id) - id = new_id - for(var/I in GLOB.conveyor_belts) - var/obj/machinery/conveyor/C = I - if(C.id == id) - conveyors += C - -/obj/machinery/conveyor_switch/Destroy() - GLOB.conveyor_switches -= src - return ..() - -// update the icon depending on the position - -/obj/machinery/conveyor_switch/update_icon() - overlays.Cut() - if(!position) - icon_state = "switch-off" - else if(position == DIRECTION_REVERSED) - icon_state = "switch-rev" - if(!(stat & NOPOWER)) - overlays += "redlight" - else if(position == DIRECTION_FORWARDS) - icon_state = "switch-fwd" - if(!(stat & NOPOWER)) - overlays += "greenlight" - -/obj/machinery/conveyor_switch/oneway - one_way = TRUE - -// attack with hand, switch position -/obj/machinery/conveyor_switch/attack_hand(mob/user) - if(..()) - return TRUE - toggle(user) - -/obj/machinery/conveyor_switch/attack_ghost(mob/user) - if(user.can_advanced_admin_interact()) - toggle(user) - -/obj/machinery/conveyor_switch/proc/toggle(mob/user) - add_fingerprint(user) - if(!allowed(user) && !user.can_advanced_admin_interact()) //this is in Para but not TG. I don't think there's any which are set anyway. - to_chat(user, "Access denied.") - return - if(position) - position = DIRECTION_OFF - else - reversed = one_way ? FALSE : !reversed - position = reversed ? DIRECTION_REVERSED : DIRECTION_FORWARDS - update_icon() - for(var/obj/machinery/conveyor/C in conveyors) - C.operating = abs(position) - if(C.reversed != reversed) - C.reversed = reversed - C.update_move_direction() - else - C.update_icon() - CHECK_TICK - for(var/I in GLOB.conveyor_switches) // find any switches with same id as this one, and set their positions to match us - var/obj/machinery/conveyor_switch/S = I - if(S == src || S.id != id) - continue - S.position = position - S.one_way = one_way //Break everything!!1! - S.reversed = reversed - S.update_icon() - CHECK_TICK - -/obj/machinery/conveyor_switch/crowbar_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - var/obj/item/conveyor_switch_construct/C = new(loc, id) - transfer_fingerprints_to(C) - to_chat(user,"You detach [src].") - qdel(src) - -/obj/machinery/conveyor_switch/multitool_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - one_way = !one_way - to_chat(user, "[src] will now go [one_way ? "forwards only" : "both forwards and backwards"].") - -/obj/machinery/conveyor_switch/power_change() - ..() - update_icon() - -// CONVEYOR CONSTRUCTION STARTS HERE - -/obj/item/conveyor_construct - icon = 'icons/obj/recycling.dmi' - icon_state = "conveyor_loose" - name = "conveyor belt assembly" - desc = "A conveyor belt assembly, used for the assembly of conveyor belt systems." - w_class = WEIGHT_CLASS_BULKY - var/id - -/obj/item/conveyor_construct/attackby(obj/item/I, mob/user, params) - ..() - if(!istype(I, /obj/item/conveyor_switch_construct)) - return - var/obj/item/conveyor_switch_construct/C = I - to_chat(user, "You link [src] to [C].") - id = C.id - -/obj/item/conveyor_construct/afterattack(turf/T, mob/user, proximity) - if(!proximity) - return - if(user.incapacitated()) - return - if(!istype(T, /turf/simulated/floor)) - return - if(T == get_turf(user)) - to_chat(user, "You cannot place [src] under yourself.") - return - if(locate(/obj/machinery/conveyor) in T) //Can't put conveyors beneath conveyors - to_chat(user, "There's already a conveyor there!") - return - var/obj/machinery/conveyor/C = new(T, user.dir, id) - transfer_fingerprints_to(C) - qdel(src) - -/obj/item/conveyor_switch_construct - name = "conveyor switch assembly" - desc = "A conveyor control switch assembly. When set up, it'll control any and all conveyor belts it is linked to." - icon = 'icons/obj/recycling.dmi' - icon_state = "switch" - w_class = WEIGHT_CLASS_BULKY - var/id - -/obj/item/conveyor_switch_construct/New(loc, new_id) - ..(loc) - if(new_id) - id = new_id - else - id = world.time + rand() //this couldn't possibly go wrong - - -/obj/item/conveyor_switch_construct/afterattack(turf/T, mob/user, proximity) - if(!proximity) - return - if(user.incapacitated()) - return - if(!istype(T, /turf/simulated/floor)) - return - var/found = FALSE - for(var/obj/machinery/conveyor/C in view()) - if(C.id == id) - found = TRUE - break - if(!found) - to_chat(user, "[src] did not detect any linked conveyor belts in range.") - return - var/obj/machinery/conveyor_switch/NC = new(T, id) - transfer_fingerprints_to(NC) - qdel(src) - -/obj/item/conveyor_switch_construct/attackby(obj/item/I, mob/user) - if(!istype(I, /obj/item/conveyor_switch_construct)) - return ..() - var/obj/item/conveyor_switch_construct/S = I - id = S.id - to_chat(user, "You link the two switch constructs.") - -/obj/item/paper/conveyor - name = "paper- 'Nano-it-up U-build series, #9: Build your very own conveyor belt, in SPACE'" - info = "

        Congratulations!

        You are now the proud owner of the best conveyor set available for space mail order! \ - We at Nano-it-up know you love to prepare your own structures without wasting time, so we have devised a special streamlined \ - assembly procedure that puts all other mail-order products to shame!

        \ -

        Firstly, you need to link the conveyor switch assembly to each of the conveyor belt assemblies. After doing so, you simply need to install the belt \ - assemblies onto the floor, et voila, belt built. Our special Nano-it-up smart switch will detected any linked assemblies as far as the eye can see!

        \ -

        Set single directional switches by using your multitool on the switch after you've installed the switch assembly.

        \ -

        This convenience, you can only have it when you Nano-it-up. Stay nano!

        " - -/obj/machinery/conveyor/counterclockwise - clockwise = FALSE - icon_state = "conveyor_stopped_ccw" - -/obj/machinery/conveyor/auto/New(loc, newdir) - ..(loc, newdir) - operating = TRUE - update_icon() - -//Other types of conveyor, mostly for saving yourself a headache during mapping - -/obj/machinery/conveyor/north - dir = NORTH - -/obj/machinery/conveyor/northeast - dir = NORTHEAST - -/obj/machinery/conveyor/east - dir = EAST - -/obj/machinery/conveyor/southeast - dir = SOUTHEAST - -/obj/machinery/conveyor/south - dir = SOUTH - -/obj/machinery/conveyor/southwest - dir = SOUTHWEST - -/obj/machinery/conveyor/west - dir = WEST - -/obj/machinery/conveyor/northwest - dir = NORTHWEST - -/obj/machinery/conveyor/north/ccw - icon_state = "conveyor_stopped_ccw" - clockwise = FALSE - -/obj/machinery/conveyor/northeast/ccw - icon_state = "conveyor_stopped_ccw" - clockwise = FALSE - -/obj/machinery/conveyor/east/ccw - icon_state = "conveyor_stopped_ccw" - clockwise = FALSE - -/obj/machinery/conveyor/southeast/ccw - icon_state = "conveyor_stopped_ccw" - clockwise = FALSE - -/obj/machinery/conveyor/south/ccw - icon_state = "conveyor_stopped_ccw" - clockwise = FALSE - -/obj/machinery/conveyor/southwest/ccw - icon_state = "conveyor_stopped_ccw" - clockwise = FALSE - -/obj/machinery/conveyor/west/ccw - icon_state = "conveyor_stopped_ccw" - clockwise = FALSE - -/obj/machinery/conveyor/northwest/ccw - icon_state = "conveyor_stopped_ccw" - clockwise = FALSE - -#undef DIRECTION_FORWARDS -#undef DIRECTION_OFF -#undef DIRECTION_REVERSED -#undef IS_OPERATING +#define DIRECTION_FORWARDS 1 +#define DIRECTION_OFF 0 +#define DIRECTION_REVERSED -1 +#define IS_OPERATING (operating && can_conveyor_run()) + +GLOBAL_LIST_INIT(conveyor_belts, list()) //Saves us having to look through the entire machines list for our things +GLOBAL_LIST_INIT(conveyor_switches, list()) + +//conveyor2 is pretty much like the original, except it supports corners, but not diverters. +//Except this is pretty heavily modified so it's more like conveyor2.5 + +/obj/machinery/conveyor + icon = 'icons/obj/recycling.dmi' + icon_state = "conveyor_stopped_cw" + name = "conveyor belt" + desc = "It's a conveyor belt, commonly used to transport large numbers of items elsewhere quite quickly." + layer = CONVEYOR_LAYER // so they appear under stuff but not below stuff like vents + anchored = TRUE + move_force = MOVE_FORCE_DEFAULT + var/operating = FALSE //NB: this can be TRUE while the belt doesn't go + var/forwards // The direction the conveyor sends you in + var/backwards // hopefully self-explanatory + var/clockwise = TRUE // For corner pieces - do we go clockwise or counterclockwise? + var/operable = TRUE // Can this belt actually go? + var/list/affecting // the list of all items that will be moved this ptick + var/reversed = FALSE // set to TRUE to have the conveyor belt be reversed + var/id //ID of the connected lever + + // create a conveyor +/obj/machinery/conveyor/New(loc, new_dir, new_id) + ..(loc) + GLOB.conveyor_belts += src + if(new_id) + id = new_id + if(new_dir) + dir = new_dir + update_move_direction() + for(var/I in GLOB.conveyor_switches) + var/obj/machinery/conveyor_switch/S = I + if(id == S.id) + S.conveyors += src + +/obj/machinery/conveyor/Destroy() + GLOB.conveyor_belts -= src + return ..() + +/obj/machinery/conveyor/setDir(newdir) + . = ..() + update_move_direction() + +// attack with item, place item on conveyor +/obj/machinery/conveyor/attackby(obj/item/I, mob/user) + if(stat & BROKEN) + return ..() + else if(istype(I, /obj/item/conveyor_switch_construct)) + var/obj/item/conveyor_switch_construct/S = I + if(S.id == id) + return ..() + for(var/obj/machinery/conveyor_switch/CS in GLOB.conveyor_switches) + if(CS.id == id) + CS.conveyors -= src + id = S.id + to_chat(user, "You link [I] with [src].") + else if(user.a_intent != INTENT_HARM) + if(user.drop_item()) + I.forceMove(loc) + else + return ..() + + +/obj/machinery/conveyor/crowbar_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(!(stat & BROKEN)) + var/obj/item/conveyor_construct/C = new(loc) + C.id = id + transfer_fingerprints_to(C) + to_chat(user,"You remove [src].") + qdel(src) + +/obj/machinery/conveyor/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + set_rotation(user) + update_move_direction() + +// attack with hand, move pulled object onto conveyor +/obj/machinery/conveyor/attack_hand(mob/user as mob) + user.Move_Pulled(src) + +/obj/machinery/conveyor/update_icon() + ..() + if(IS_OPERATING) + icon_state = "conveyor_started_[clockwise ? "cw" : "ccw"]" + if(reversed) + icon_state += "_r" + else + icon_state = "conveyor_stopped_[clockwise ? "cw" : "ccw"]" + +/obj/machinery/conveyor/proc/update_move_direction() + update_icon() + switch(dir) + if(NORTH) + forwards = NORTH + backwards = SOUTH + if(EAST) + forwards = EAST + backwards = WEST + if(SOUTH) + forwards = SOUTH + backwards = NORTH + if(WEST) + forwards = WEST + backwards = EAST + if(NORTHEAST) + forwards = clockwise ? EAST : NORTH + backwards = clockwise ? SOUTH : WEST + if(SOUTHEAST) + forwards = clockwise ? SOUTH : EAST + backwards = clockwise ? WEST : NORTH + if(SOUTHWEST) + forwards = clockwise ? WEST : SOUTH + backwards = clockwise ? NORTH : EAST + if(NORTHWEST) + forwards = clockwise ? NORTH : WEST + backwards = clockwise ? EAST : SOUTH + if(!reversed) + return + var/temporary_direction = forwards + forwards = backwards + backwards = temporary_direction + +/obj/machinery/conveyor/proc/set_rotation(mob/user) + dir = turn(reversed ? backwards : forwards, -90) //Fuck it, let's do it this way instead of doing something clever with dir + var/turf/left = get_step(src, turn(dir, 90)) //We need to get conveyors to the right, left, and behind this one to be able to determine if we need to make a corner piece + var/turf/right = get_step(src, turn(dir, -90)) + var/turf/back = get_step(src, turn(dir, 180)) + to_chat(user, "You rotate [src].") + var/obj/machinery/conveyor/CL = locate() in left + var/obj/machinery/conveyor/CR = locate() in right + var/obj/machinery/conveyor/CB = locate() in back + var/link_to_left = FALSE + var/link_to_right = FALSE + var/link_to_back = FALSE + if(CL) + if(CL.id == id && get_step(CL, CL.reversed ? CL.backwards : CL.forwards) == loc) + link_to_left = TRUE + if(CR) + if(CR.id == id && get_step(CR, CR.reversed ? CR.backwards : CR.forwards) == loc) + link_to_right = TRUE + if(CB) + if(CB.id == id && get_step(CB, CB.reversed ? CB.backwards : CB.forwards) == loc) + link_to_back = TRUE + if(link_to_back) //Don't need to do anything because we can assume the conveyor carries on in a line + return + else if(!(link_to_left ^ link_to_right)) //Either no valid conveyors point here, or two point here (making a "junction" with this belt as the middle piece). Either way we don't need a corner + return + if(link_to_right) + dir = turn(dir, 45) + clockwise = TRUE + else if(link_to_left) + dir = turn(dir, -45) + clockwise = FALSE + +/obj/machinery/conveyor/power_change() + ..() + update_icon() + +/obj/machinery/conveyor/process() + if(!IS_OPERATING) + return + use_power(100) + affecting = loc.contents - src // moved items will be all in loc + var/still_stuff_to_move = FALSE + for(var/atom/movable/AM in affecting) + if(AM.anchored) + continue + still_stuff_to_move = TRUE + addtimer(CALLBACK(src, .proc/move_thing, AM), 1) + CHECK_TICK + if(!still_stuff_to_move && speed_process) + makeNormalProcess() + else if(still_stuff_to_move && !speed_process) + makeSpeedProcess() + +/obj/machinery/conveyor/Crossed(atom/movable/AM, oldloc) + if(!speed_process && !AM.anchored) + makeSpeedProcess() + ..() + +/obj/machinery/conveyor/proc/move_thing(atom/movable/AM) + if(move_force < (AM.move_resist)) + return FALSE + if(!AM.anchored && AM.loc == loc) + step(AM, forwards) + + +/obj/machinery/conveyor/proc/can_conveyor_run() + if(stat & BROKEN) + return FALSE + else if(stat & NOPOWER) + return FALSE + else if(!operable) + return FALSE + return TRUE + +// make the conveyor broken and propagate inoperability to any connected conveyor with the same conveyor datum +/obj/machinery/conveyor/proc/make_broken() + stat |= BROKEN + operable = FALSE + update_icon() + var/obj/machinery/conveyor/C = locate() in get_step(src, forwards) + if(C) + C.set_operable(TRUE, id, FALSE) + C = locate() in get_step(src, backwards) + if(C) + C.set_operable(FALSE, id, FALSE) + +/obj/machinery/conveyor/proc/set_operable(propagate_forwards, match_id, op) //Sets a conveyor inoperable if ID matches it, and propagates forwards / backwards + if(id != match_id) + return + operable = op + update_icon() + var/obj/machinery/conveyor/C = locate() in get_step(src, propagate_forwards ? forwards : backwards) + if(C) + C.set_operable(propagate_forwards ? TRUE : FALSE, id, op) + +// the conveyor control switch + +/obj/machinery/conveyor_switch + name = "conveyor switch" + desc = "This switch controls any and all conveyor belts it is linked to." + icon = 'icons/obj/recycling.dmi' + icon_state = "switch-off" + var/position = DIRECTION_OFF + var/reversed = TRUE + var/one_way = FALSE // Do we go in one direction? + anchored = TRUE + var/id + var/list/conveyors = list() + +/obj/machinery/conveyor_switch/New(newloc, new_id) + ..(newloc) + GLOB.conveyor_switches += src + if(!id) + id = new_id + for(var/I in GLOB.conveyor_belts) + var/obj/machinery/conveyor/C = I + if(C.id == id) + conveyors += C + +/obj/machinery/conveyor_switch/Destroy() + GLOB.conveyor_switches -= src + return ..() + +// update the icon depending on the position + +/obj/machinery/conveyor_switch/update_icon() + overlays.Cut() + if(!position) + icon_state = "switch-off" + else if(position == DIRECTION_REVERSED) + icon_state = "switch-rev" + if(!(stat & NOPOWER)) + overlays += "redlight" + else if(position == DIRECTION_FORWARDS) + icon_state = "switch-fwd" + if(!(stat & NOPOWER)) + overlays += "greenlight" + +/obj/machinery/conveyor_switch/oneway + one_way = TRUE + +// attack with hand, switch position +/obj/machinery/conveyor_switch/attack_hand(mob/user) + if(..()) + return TRUE + toggle(user) + +/obj/machinery/conveyor_switch/attack_ghost(mob/user) + if(user.can_advanced_admin_interact()) + toggle(user) + +/obj/machinery/conveyor_switch/proc/toggle(mob/user) + add_fingerprint(user) + if(!allowed(user) && !user.can_advanced_admin_interact()) //this is in Para but not TG. I don't think there's any which are set anyway. + to_chat(user, "Access denied.") + return + if(position) + position = DIRECTION_OFF + else + reversed = one_way ? FALSE : !reversed + position = reversed ? DIRECTION_REVERSED : DIRECTION_FORWARDS + update_icon() + for(var/obj/machinery/conveyor/C in conveyors) + C.operating = abs(position) + if(C.reversed != reversed) + C.reversed = reversed + C.update_move_direction() + else + C.update_icon() + CHECK_TICK + for(var/I in GLOB.conveyor_switches) // find any switches with same id as this one, and set their positions to match us + var/obj/machinery/conveyor_switch/S = I + if(S == src || S.id != id) + continue + S.position = position + S.one_way = one_way //Break everything!!1! + S.reversed = reversed + S.update_icon() + CHECK_TICK + +/obj/machinery/conveyor_switch/crowbar_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + var/obj/item/conveyor_switch_construct/C = new(loc, id) + transfer_fingerprints_to(C) + to_chat(user,"You detach [src].") + qdel(src) + +/obj/machinery/conveyor_switch/multitool_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + one_way = !one_way + to_chat(user, "[src] will now go [one_way ? "forwards only" : "both forwards and backwards"].") + +/obj/machinery/conveyor_switch/power_change() + ..() + update_icon() + +// CONVEYOR CONSTRUCTION STARTS HERE + +/obj/item/conveyor_construct + icon = 'icons/obj/recycling.dmi' + icon_state = "conveyor_loose" + name = "conveyor belt assembly" + desc = "A conveyor belt assembly, used for the assembly of conveyor belt systems." + w_class = WEIGHT_CLASS_BULKY + var/id + +/obj/item/conveyor_construct/attackby(obj/item/I, mob/user, params) + ..() + if(!istype(I, /obj/item/conveyor_switch_construct)) + return + var/obj/item/conveyor_switch_construct/C = I + to_chat(user, "You link [src] to [C].") + id = C.id + +/obj/item/conveyor_construct/afterattack(turf/T, mob/user, proximity) + if(!proximity) + return + if(user.incapacitated()) + return + if(!istype(T, /turf/simulated/floor)) + return + if(T == get_turf(user)) + to_chat(user, "You cannot place [src] under yourself.") + return + if(locate(/obj/machinery/conveyor) in T) //Can't put conveyors beneath conveyors + to_chat(user, "There's already a conveyor there!") + return + var/obj/machinery/conveyor/C = new(T, user.dir, id) + transfer_fingerprints_to(C) + qdel(src) + +/obj/item/conveyor_switch_construct + name = "conveyor switch assembly" + desc = "A conveyor control switch assembly. When set up, it'll control any and all conveyor belts it is linked to." + icon = 'icons/obj/recycling.dmi' + icon_state = "switch" + w_class = WEIGHT_CLASS_BULKY + var/id + +/obj/item/conveyor_switch_construct/New(loc, new_id) + ..(loc) + if(new_id) + id = new_id + else + id = world.time + rand() //this couldn't possibly go wrong + + +/obj/item/conveyor_switch_construct/afterattack(turf/T, mob/user, proximity) + if(!proximity) + return + if(user.incapacitated()) + return + if(!istype(T, /turf/simulated/floor)) + return + var/found = FALSE + for(var/obj/machinery/conveyor/C in view()) + if(C.id == id) + found = TRUE + break + if(!found) + to_chat(user, "[src] did not detect any linked conveyor belts in range.") + return + var/obj/machinery/conveyor_switch/NC = new(T, id) + transfer_fingerprints_to(NC) + qdel(src) + +/obj/item/conveyor_switch_construct/attackby(obj/item/I, mob/user) + if(!istype(I, /obj/item/conveyor_switch_construct)) + return ..() + var/obj/item/conveyor_switch_construct/S = I + id = S.id + to_chat(user, "You link the two switch constructs.") + +/obj/item/paper/conveyor + name = "paper- 'Nano-it-up U-build series, #9: Build your very own conveyor belt, in SPACE'" + info = "

        Congratulations!

        You are now the proud owner of the best conveyor set available for space mail order! \ + We at Nano-it-up know you love to prepare your own structures without wasting time, so we have devised a special streamlined \ + assembly procedure that puts all other mail-order products to shame!

        \ +

        Firstly, you need to link the conveyor switch assembly to each of the conveyor belt assemblies. After doing so, you simply need to install the belt \ + assemblies onto the floor, et voila, belt built. Our special Nano-it-up smart switch will detected any linked assemblies as far as the eye can see!

        \ +

        Set single directional switches by using your multitool on the switch after you've installed the switch assembly.

        \ +

        This convenience, you can only have it when you Nano-it-up. Stay nano!

        " + +/obj/machinery/conveyor/counterclockwise + clockwise = FALSE + icon_state = "conveyor_stopped_ccw" + +/obj/machinery/conveyor/auto/New(loc, newdir) + ..(loc, newdir) + operating = TRUE + update_icon() + +//Other types of conveyor, mostly for saving yourself a headache during mapping + +/obj/machinery/conveyor/north + dir = NORTH + +/obj/machinery/conveyor/northeast + dir = NORTHEAST + +/obj/machinery/conveyor/east + dir = EAST + +/obj/machinery/conveyor/southeast + dir = SOUTHEAST + +/obj/machinery/conveyor/south + dir = SOUTH + +/obj/machinery/conveyor/southwest + dir = SOUTHWEST + +/obj/machinery/conveyor/west + dir = WEST + +/obj/machinery/conveyor/northwest + dir = NORTHWEST + +/obj/machinery/conveyor/north/ccw + icon_state = "conveyor_stopped_ccw" + clockwise = FALSE + +/obj/machinery/conveyor/northeast/ccw + icon_state = "conveyor_stopped_ccw" + clockwise = FALSE + +/obj/machinery/conveyor/east/ccw + icon_state = "conveyor_stopped_ccw" + clockwise = FALSE + +/obj/machinery/conveyor/southeast/ccw + icon_state = "conveyor_stopped_ccw" + clockwise = FALSE + +/obj/machinery/conveyor/south/ccw + icon_state = "conveyor_stopped_ccw" + clockwise = FALSE + +/obj/machinery/conveyor/southwest/ccw + icon_state = "conveyor_stopped_ccw" + clockwise = FALSE + +/obj/machinery/conveyor/west/ccw + icon_state = "conveyor_stopped_ccw" + clockwise = FALSE + +/obj/machinery/conveyor/northwest/ccw + icon_state = "conveyor_stopped_ccw" + clockwise = FALSE + +#undef DIRECTION_FORWARDS +#undef DIRECTION_OFF +#undef DIRECTION_REVERSED +#undef IS_OPERATING diff --git a/code/modules/recycling/disposal-construction.dm b/code/modules/recycling/disposal-construction.dm index 66caa897f21..df29663588f 100644 --- a/code/modules/recycling/disposal-construction.dm +++ b/code/modules/recycling/disposal-construction.dm @@ -1,265 +1,265 @@ -// Disposal pipe construction -// This is the pipe that you drag around, not the attached ones. - -/obj/structure/disposalconstruct - - name = "disposal pipe segment" - desc = "A huge pipe segment used for constructing disposal systems." - icon = 'icons/obj/pipes/disposal.dmi' - icon_state = "conpipe-s" - anchored = 0 - density = 0 - pressure_resistance = 5*ONE_ATMOSPHERE - level = 2 - max_integrity = 200 - var/ptype = PIPE_DISPOSALS_STRAIGHT //Use the defines - var/base_state - var/dpdir = 0 // directions as disposalpipe - -/obj/structure/disposalconstruct/New(loc, pipe_type, direction) - ..() - if(pipe_type) - ptype = pipe_type - if(dir) - dir = direction - update() - - // update iconstate and dpdir due to dir and type -/obj/structure/disposalconstruct/proc/update() - base_state = get_pipe_icon(ptype) - icon_state = "con[base_state]" - var/flip = turn(dir, 180) - var/left = turn(dir, 90) - var/right = turn(dir, -90) - name = get_pipe_name(ptype, PIPETYPE_DISPOSAL) - switch(ptype) - if(PIPE_DISPOSALS_STRAIGHT) - dpdir = dir | flip - if(PIPE_DISPOSALS_BENT) - dpdir = dir | right - if(PIPE_DISPOSALS_JUNCTION_RIGHT) - dpdir = dir | right | flip - if(PIPE_DISPOSALS_JUNCTION_LEFT) - dpdir = dir | left | flip - if(PIPE_DISPOSALS_Y_JUNCTION) - dpdir = dir | left | right - if(PIPE_DISPOSALS_TRUNK) - dpdir = dir - if(PIPE_DISPOSALS_SORT_RIGHT) - dpdir = dir | right | flip - if(PIPE_DISPOSALS_SORT_LEFT) - dpdir = dir | left | flip - // disposal bin has only one dir, thus we don't need to care about setting it - if(PIPE_DISPOSALS_BIN) - if(!anchored) - icon_state = "[base_state]-unanchored" - else - icon_state = base_state - if(PIPE_DISPOSALS_OUTLET) - dpdir = dir - icon_state = base_state - if(PIPE_DISPOSALS_CHUTE) - dpdir = dir - icon_state = base_state - if(!(ptype in list(PIPE_DISPOSALS_BIN, PIPE_DISPOSALS_OUTLET, PIPE_DISPOSALS_CHUTE))) - icon_state = "con[base_state]" - if(invisibility) // if invisible, fade icon - icon -= rgb(0,0,0,128) - -// hide called by levelupdate if turf intact status changes -// change visibility status and force update of icon -/obj/structure/disposalconstruct/hide(var/intact) - invisibility = (intact && level==1) ? 101: 0 // hide if floor is intact - update() - - -// flip and rotate verbs -/obj/structure/disposalconstruct/verb/rotate() - set name = "Rotate Pipe" - set src in view(1) - - if(usr.stat) - return - - if(anchored) - to_chat(usr, "You must unfasten the pipe before rotating it.") - return - - dir = turn(dir, -90) - update() - -/obj/structure/disposalconstruct/AltClick(mob/user) - if(user.incapacitated()) - to_chat(user, "You can't do that right now!") - return - if(!Adjacent(user)) - return - rotate() - -/obj/structure/disposalconstruct/verb/flip() - set name = "Flip Pipe" - set src in view(1) - if(usr.stat) - return - - if(anchored) - to_chat(usr, "You must unfasten the pipe before flipping it.") - return - - dir = turn(dir, 180) - switch(ptype) - if(PIPE_DISPOSALS_JUNCTION_RIGHT) - ptype = PIPE_DISPOSALS_JUNCTION_LEFT - if(PIPE_DISPOSALS_JUNCTION_LEFT) - ptype = PIPE_DISPOSALS_JUNCTION_RIGHT - if(PIPE_DISPOSALS_SORT_RIGHT) - ptype = PIPE_DISPOSALS_SORT_LEFT - if(PIPE_DISPOSALS_SORT_LEFT) - ptype = PIPE_DISPOSALS_SORT_RIGHT - - update() - -// returns the type path of disposalpipe corresponding to this item dtype -/obj/structure/disposalconstruct/proc/dpipetype() - switch(ptype) - if(PIPE_DISPOSALS_STRAIGHT, PIPE_DISPOSALS_BENT) - return /obj/structure/disposalpipe/segment - if(PIPE_DISPOSALS_JUNCTION_RIGHT, PIPE_DISPOSALS_JUNCTION_LEFT, PIPE_DISPOSALS_Y_JUNCTION) - return /obj/structure/disposalpipe/junction - if(PIPE_DISPOSALS_TRUNK) - return /obj/structure/disposalpipe/trunk - if(PIPE_DISPOSALS_BIN) - return /obj/machinery/disposal - if(PIPE_DISPOSALS_OUTLET) - return /obj/structure/disposaloutlet - if(PIPE_DISPOSALS_CHUTE) - return /obj/machinery/disposal/deliveryChute - if(PIPE_DISPOSALS_SORT_RIGHT, PIPE_DISPOSALS_SORT_LEFT) - return /obj/structure/disposalpipe/sortjunction - return - - - -// attackby item -// wrench: (un)anchor -// weldingtool: convert to real pipe - -/obj/structure/disposalconstruct/attackby(var/obj/item/I, var/mob/user, params) - var/nicetype = "pipe" - var/ispipe = 0 // Indicates if we should change the level of this pipe - src.add_fingerprint(user) - switch(ptype) - if(PIPE_DISPOSALS_BIN) - nicetype = "disposal bin" - if(PIPE_DISPOSALS_OUTLET) - nicetype = "disposal outlet" - if(PIPE_DISPOSALS_CHUTE) - nicetype = "delivery chute" - if(PIPE_DISPOSALS_SORT_RIGHT, PIPE_DISPOSALS_SORT_LEFT) - nicetype = "sorting pipe" - ispipe = 1 - else - nicetype = "pipe" - ispipe = 1 - - var/turf/T = src.loc - if(T.intact) - to_chat(user, "You can only attach the [nicetype] if the floor plating is removed.") - return - - if(istype(I, /obj/item/wrench)) - if(anchored) - anchored = 0 - if(ispipe) - level = 2 - density = 0 - else - density = 1 - to_chat(user, "You detach the [nicetype] from the underfloor.") - else - anchored = 1 - if(ispipe) - level = 1 // We don't want disposal bins to disappear under the floors - density = 0 - else - density = 1 // We don't want disposal bins or outlets to go density 0 - to_chat(user, "You attach the [nicetype] to the underfloor.") - playsound(src.loc, I.usesound, 100, 1) - update() - return - - - if(ptype in list(PIPE_DISPOSALS_BIN, PIPE_DISPOSALS_OUTLET, PIPE_DISPOSALS_CHUTE)) // Disposal or outlet - var/obj/structure/disposalpipe/trunk/CP = locate() in T - if(!CP) // There's no trunk - to_chat(user, "The [nicetype] requires a trunk underneath it in order to work.") - return - else - for(var/obj/structure/disposalpipe/CP in T) - if(CP) - update() - var/pdir = CP.dpdir - if(istype(CP, /obj/structure/disposalpipe/broken)) - pdir = CP.dir - if(pdir & dpdir) - to_chat(user, "There is already a [nicetype] at that location.") - return - - if(istype(I, /obj/item/weldingtool)) - if(anchored) - if(I.tool_use_check(user, 0)) - to_chat(user, "Welding the [nicetype] in place.") - if(I.use_tool(src, user, 20, volume = I.tool_volume)) - to_chat(user, "The [nicetype] has been welded in place!") - update() // TODO: Make this neat - if(ispipe) // Pipe - - var/pipetype = dpipetype() - var/obj/structure/disposalpipe/P = new pipetype(src.loc) - src.transfer_fingerprints_to(P) - P.base_icon_state = base_state - P.dir = dir - P.dpdir = dpdir - P.update_icon() - - //Needs some special treatment ;) - if(ptype == PIPE_DISPOSALS_SORT_RIGHT || ptype == PIPE_DISPOSALS_SORT_LEFT) - var/obj/structure/disposalpipe/sortjunction/SortP = P - SortP.updatedir() - - else if(ptype == PIPE_DISPOSALS_BIN) // Disposal bin - var/obj/machinery/disposal/P = new /obj/machinery/disposal(src.loc) - src.transfer_fingerprints_to(P) - P.mode = 0 // start with pump off - - else if(ptype == PIPE_DISPOSALS_OUTLET) // Disposal outlet - - var/obj/structure/disposaloutlet/P = new /obj/structure/disposaloutlet(src.loc) - src.transfer_fingerprints_to(P) - P.dir = dir - - else if(ptype==PIPE_DISPOSALS_CHUTE) // Disposal outlet - - var/obj/machinery/disposal/deliveryChute/P = new /obj/machinery/disposal/deliveryChute(src.loc) - src.transfer_fingerprints_to(P) - P.dir = dir - - qdel(src) - return - else - to_chat(user, "You need more welding fuel to complete this task.") - return - else - to_chat(user, "You need to attach it to the plating first!") - return - -/obj/structure/disposalconstruct/rpd_act(mob/user, obj/item/rpd/our_rpd) - . = TRUE - if(our_rpd.mode == RPD_ROTATE_MODE) - rotate() - else if(our_rpd.mode == RPD_FLIP_MODE) - flip() - else if(our_rpd.mode == RPD_DELETE_MODE) - our_rpd.delete_single_pipe(user, src) - else - return ..() +// Disposal pipe construction +// This is the pipe that you drag around, not the attached ones. + +/obj/structure/disposalconstruct + + name = "disposal pipe segment" + desc = "A huge pipe segment used for constructing disposal systems." + icon = 'icons/obj/pipes/disposal.dmi' + icon_state = "conpipe-s" + anchored = 0 + density = 0 + pressure_resistance = 5*ONE_ATMOSPHERE + level = 2 + max_integrity = 200 + var/ptype = PIPE_DISPOSALS_STRAIGHT //Use the defines + var/base_state + var/dpdir = 0 // directions as disposalpipe + +/obj/structure/disposalconstruct/New(loc, pipe_type, direction) + ..() + if(pipe_type) + ptype = pipe_type + if(dir) + dir = direction + update() + + // update iconstate and dpdir due to dir and type +/obj/structure/disposalconstruct/proc/update() + base_state = get_pipe_icon(ptype) + icon_state = "con[base_state]" + var/flip = turn(dir, 180) + var/left = turn(dir, 90) + var/right = turn(dir, -90) + name = get_pipe_name(ptype, PIPETYPE_DISPOSAL) + switch(ptype) + if(PIPE_DISPOSALS_STRAIGHT) + dpdir = dir | flip + if(PIPE_DISPOSALS_BENT) + dpdir = dir | right + if(PIPE_DISPOSALS_JUNCTION_RIGHT) + dpdir = dir | right | flip + if(PIPE_DISPOSALS_JUNCTION_LEFT) + dpdir = dir | left | flip + if(PIPE_DISPOSALS_Y_JUNCTION) + dpdir = dir | left | right + if(PIPE_DISPOSALS_TRUNK) + dpdir = dir + if(PIPE_DISPOSALS_SORT_RIGHT) + dpdir = dir | right | flip + if(PIPE_DISPOSALS_SORT_LEFT) + dpdir = dir | left | flip + // disposal bin has only one dir, thus we don't need to care about setting it + if(PIPE_DISPOSALS_BIN) + if(!anchored) + icon_state = "[base_state]-unanchored" + else + icon_state = base_state + if(PIPE_DISPOSALS_OUTLET) + dpdir = dir + icon_state = base_state + if(PIPE_DISPOSALS_CHUTE) + dpdir = dir + icon_state = base_state + if(!(ptype in list(PIPE_DISPOSALS_BIN, PIPE_DISPOSALS_OUTLET, PIPE_DISPOSALS_CHUTE))) + icon_state = "con[base_state]" + if(invisibility) // if invisible, fade icon + icon -= rgb(0,0,0,128) + +// hide called by levelupdate if turf intact status changes +// change visibility status and force update of icon +/obj/structure/disposalconstruct/hide(var/intact) + invisibility = (intact && level==1) ? 101: 0 // hide if floor is intact + update() + + +// flip and rotate verbs +/obj/structure/disposalconstruct/verb/rotate() + set name = "Rotate Pipe" + set src in view(1) + + if(usr.stat) + return + + if(anchored) + to_chat(usr, "You must unfasten the pipe before rotating it.") + return + + dir = turn(dir, -90) + update() + +/obj/structure/disposalconstruct/AltClick(mob/user) + if(user.incapacitated()) + to_chat(user, "You can't do that right now!") + return + if(!Adjacent(user)) + return + rotate() + +/obj/structure/disposalconstruct/verb/flip() + set name = "Flip Pipe" + set src in view(1) + if(usr.stat) + return + + if(anchored) + to_chat(usr, "You must unfasten the pipe before flipping it.") + return + + dir = turn(dir, 180) + switch(ptype) + if(PIPE_DISPOSALS_JUNCTION_RIGHT) + ptype = PIPE_DISPOSALS_JUNCTION_LEFT + if(PIPE_DISPOSALS_JUNCTION_LEFT) + ptype = PIPE_DISPOSALS_JUNCTION_RIGHT + if(PIPE_DISPOSALS_SORT_RIGHT) + ptype = PIPE_DISPOSALS_SORT_LEFT + if(PIPE_DISPOSALS_SORT_LEFT) + ptype = PIPE_DISPOSALS_SORT_RIGHT + + update() + +// returns the type path of disposalpipe corresponding to this item dtype +/obj/structure/disposalconstruct/proc/dpipetype() + switch(ptype) + if(PIPE_DISPOSALS_STRAIGHT, PIPE_DISPOSALS_BENT) + return /obj/structure/disposalpipe/segment + if(PIPE_DISPOSALS_JUNCTION_RIGHT, PIPE_DISPOSALS_JUNCTION_LEFT, PIPE_DISPOSALS_Y_JUNCTION) + return /obj/structure/disposalpipe/junction + if(PIPE_DISPOSALS_TRUNK) + return /obj/structure/disposalpipe/trunk + if(PIPE_DISPOSALS_BIN) + return /obj/machinery/disposal + if(PIPE_DISPOSALS_OUTLET) + return /obj/structure/disposaloutlet + if(PIPE_DISPOSALS_CHUTE) + return /obj/machinery/disposal/deliveryChute + if(PIPE_DISPOSALS_SORT_RIGHT, PIPE_DISPOSALS_SORT_LEFT) + return /obj/structure/disposalpipe/sortjunction + return + + + +// attackby item +// wrench: (un)anchor +// weldingtool: convert to real pipe + +/obj/structure/disposalconstruct/attackby(var/obj/item/I, var/mob/user, params) + var/nicetype = "pipe" + var/ispipe = 0 // Indicates if we should change the level of this pipe + src.add_fingerprint(user) + switch(ptype) + if(PIPE_DISPOSALS_BIN) + nicetype = "disposal bin" + if(PIPE_DISPOSALS_OUTLET) + nicetype = "disposal outlet" + if(PIPE_DISPOSALS_CHUTE) + nicetype = "delivery chute" + if(PIPE_DISPOSALS_SORT_RIGHT, PIPE_DISPOSALS_SORT_LEFT) + nicetype = "sorting pipe" + ispipe = 1 + else + nicetype = "pipe" + ispipe = 1 + + var/turf/T = src.loc + if(T.intact) + to_chat(user, "You can only attach the [nicetype] if the floor plating is removed.") + return + + if(istype(I, /obj/item/wrench)) + if(anchored) + anchored = 0 + if(ispipe) + level = 2 + density = 0 + else + density = 1 + to_chat(user, "You detach the [nicetype] from the underfloor.") + else + anchored = 1 + if(ispipe) + level = 1 // We don't want disposal bins to disappear under the floors + density = 0 + else + density = 1 // We don't want disposal bins or outlets to go density 0 + to_chat(user, "You attach the [nicetype] to the underfloor.") + playsound(src.loc, I.usesound, 100, 1) + update() + return + + + if(ptype in list(PIPE_DISPOSALS_BIN, PIPE_DISPOSALS_OUTLET, PIPE_DISPOSALS_CHUTE)) // Disposal or outlet + var/obj/structure/disposalpipe/trunk/CP = locate() in T + if(!CP) // There's no trunk + to_chat(user, "The [nicetype] requires a trunk underneath it in order to work.") + return + else + for(var/obj/structure/disposalpipe/CP in T) + if(CP) + update() + var/pdir = CP.dpdir + if(istype(CP, /obj/structure/disposalpipe/broken)) + pdir = CP.dir + if(pdir & dpdir) + to_chat(user, "There is already a [nicetype] at that location.") + return + + if(istype(I, /obj/item/weldingtool)) + if(anchored) + if(I.tool_use_check(user, 0)) + to_chat(user, "Welding the [nicetype] in place.") + if(I.use_tool(src, user, 20, volume = I.tool_volume)) + to_chat(user, "The [nicetype] has been welded in place!") + update() // TODO: Make this neat + if(ispipe) // Pipe + + var/pipetype = dpipetype() + var/obj/structure/disposalpipe/P = new pipetype(src.loc) + src.transfer_fingerprints_to(P) + P.base_icon_state = base_state + P.dir = dir + P.dpdir = dpdir + P.update_icon() + + //Needs some special treatment ;) + if(ptype == PIPE_DISPOSALS_SORT_RIGHT || ptype == PIPE_DISPOSALS_SORT_LEFT) + var/obj/structure/disposalpipe/sortjunction/SortP = P + SortP.updatedir() + + else if(ptype == PIPE_DISPOSALS_BIN) // Disposal bin + var/obj/machinery/disposal/P = new /obj/machinery/disposal(src.loc) + src.transfer_fingerprints_to(P) + P.mode = 0 // start with pump off + + else if(ptype == PIPE_DISPOSALS_OUTLET) // Disposal outlet + + var/obj/structure/disposaloutlet/P = new /obj/structure/disposaloutlet(src.loc) + src.transfer_fingerprints_to(P) + P.dir = dir + + else if(ptype==PIPE_DISPOSALS_CHUTE) // Disposal outlet + + var/obj/machinery/disposal/deliveryChute/P = new /obj/machinery/disposal/deliveryChute(src.loc) + src.transfer_fingerprints_to(P) + P.dir = dir + + qdel(src) + return + else + to_chat(user, "You need more welding fuel to complete this task.") + return + else + to_chat(user, "You need to attach it to the plating first!") + return + +/obj/structure/disposalconstruct/rpd_act(mob/user, obj/item/rpd/our_rpd) + . = TRUE + if(our_rpd.mode == RPD_ROTATE_MODE) + rotate() + else if(our_rpd.mode == RPD_FLIP_MODE) + flip() + else if(our_rpd.mode == RPD_DELETE_MODE) + our_rpd.delete_single_pipe(user, src) + else + return ..() diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm index 59f6d395aff..906d1136ebc 100644 --- a/code/modules/recycling/disposal.dm +++ b/code/modules/recycling/disposal.dm @@ -1,1384 +1,1384 @@ -// Disposal bin -// Holds items for disposal into pipe system -// Draws air from turf, gradually charges internal reservoir -// Once full (~1 atm), uses air resv to flush items into the pipes -// Automatically recharges air (unless off), will flush when ready if pre-set -// Can hold items and human size things, no other draggables -// Toilets are a type of disposal bin for small objects only and work on magic. By magic, I mean torque rotation -#define SEND_PRESSURE 0.05*ONE_ATMOSPHERE - -/obj/machinery/disposal - name = "disposal unit" - desc = "A pneumatic waste disposal unit." - icon = 'icons/obj/pipes/disposal.dmi' - icon_state = "disposal" - anchored = 1 - density = 1 - on_blueprints = TRUE - armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) - max_integrity = 200 - resistance_flags = FIRE_PROOF - var/datum/gas_mixture/air_contents // internal reservoir - var/mode = 1 // item mode 0=off 1=charging 2=charged - var/flush = 0 // true if flush handle is pulled - var/obj/structure/disposalpipe/trunk/trunk = null // the attached pipe trunk - var/flushing = 0 // true if flushing in progress - var/flush_every_ticks = 30 //Every 30 ticks it will look whether it is ready to flush - var/flush_count = 0 //this var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush. - var/last_sound = 0 - var/required_mode_to_deconstruct = -1 - var/deconstructs_to = PIPE_DISPOSALS_BIN - active_power_usage = 600 - idle_power_usage = 100 - - -// create a new disposal -// find the attached trunk (if present) -/obj/machinery/disposal/New() - ..() - trunk_check() - //gas.volume = 1.05 * CELLSTANDARD - update() - -/obj/machinery/disposal/proc/trunk_check() - var/obj/structure/disposalpipe/trunk/T = locate() in loc - if(!T) - mode = 0 - flush = 0 - else - mode = initial(mode) - flush = initial(flush) - T.nicely_link_to_other_stuff(src) - -//When the disposalsoutlet is forcefully moved. Due to meteorshot (not the recall spell) -/obj/machinery/disposal/Moved(atom/OldLoc, Dir) - . = ..() - eject() - var/ptype = istype(src, /obj/machinery/disposal/deliveryChute) ? PIPE_DISPOSALS_CHUTE : PIPE_DISPOSALS_BIN //Check what disposaltype it is - var/turf/T = OldLoc - if(T.intact) - var/turf/simulated/floor/F = T - F.remove_tile(null,TRUE,TRUE) - T.visible_message("The floortile is ripped from the floor!", "You hear a loud bang!") - if(trunk) - trunk.remove_trunk_links() - var/obj/structure/disposalconstruct/C = new (loc) - transfer_fingerprints_to(C) - C.ptype = ptype - C.update() - C.anchored = 0 - C.density = 1 - qdel(src) - -/obj/machinery/disposal/Destroy() - eject() - if(trunk) - trunk.remove_trunk_links() - return ..() - -/obj/machinery/disposal/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -/obj/machinery/disposal/Initialize() - // this will get a copy of the air turf and take a SEND PRESSURE amount of air from it - ..() - var/atom/L = loc - var/datum/gas_mixture/env = new - env.copy_from(L.return_air()) - var/datum/gas_mixture/removed = env.remove(SEND_PRESSURE + 1) - air_contents = new - air_contents.merge(removed) - trunk_check() - -// attack by item places it in to disposal -/obj/machinery/disposal/attackby(var/obj/item/I, var/mob/user, params) - if(stat & BROKEN || !I || !user) - return - - src.add_fingerprint(user) - - if(istype(I, /obj/item/melee/energy/blade)) - to_chat(user, "You can't place that item inside the disposal unit.") - return - - if(istype(I, /obj/item/storage)) - var/obj/item/storage/S = I - if((S.allow_quick_empty || S.allow_quick_gather) && S.contents.len) - S.hide_from(user) - user.visible_message("[user] empties \the [S] into \the [src].", "You empty \the [S] into \the [src].") - for(var/obj/item/O in S.contents) - S.remove_from_storage(O, src) - S.update_icon() // For content-sensitive icons - update() - return - - var/obj/item/grab/G = I - if(istype(G)) // handle grabbed mob - if(ismob(G.affecting)) - var/mob/GM = G.affecting - for(var/mob/V in viewers(usr)) - V.show_message("[usr] starts putting [GM.name] into the disposal.", 3) - if(do_after(usr, 20, target = GM)) - GM.forceMove(src) - for(var/mob/C in viewers(src)) - C.show_message("[GM.name] has been placed in the [src] by [user].", 3) - qdel(G) - add_attack_logs(usr, GM, "Disposal'ed", !!GM.ckey ? null : ATKLOG_ALL) - return - - if(!I) - return - - if(!user.drop_item()) - return - if(I) - I.forceMove(src) - - to_chat(user, "You place \the [I] into the [src].") - for(var/mob/M in viewers(src)) - if(M == user) - continue - M.show_message("[user.name] places \the [I] into the [src].", 3) - - update() - - - - -/obj/machinery/disposal/screwdriver_act(mob/user, obj/item/I) - if(mode>0) // It's on - return - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - if(contents.len > 0) - to_chat(user, "Eject the items first!") - return - if(mode==0) // It's off but still not unscrewed - mode=-1 // Set it to doubleoff l0l - else if(mode==-1) - mode=0 - to_chat(user, "You [mode ? "unfasten": "fasten"] the screws around the power connection.") - -/obj/machinery/disposal/welder_act(mob/user, obj/item/I) - . = TRUE - if(mode != required_mode_to_deconstruct) - return - if(contents.len > 0) - to_chat(user, "Eject the items first!") - return - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE - if(I.use_tool(src, user, 20, volume = I.tool_volume)) - WELDER_FLOOR_SLICE_SUCCESS_MESSAGE - var/obj/structure/disposalconstruct/C = new (src.loc) - C.ptype = deconstructs_to - C.update() - C.anchored = 1 - C.density = 1 - qdel(src) - -// mouse drop another mob or self -// -/obj/machinery/disposal/MouseDrop_T(mob/target, mob/user) - if(!istype(target) || target.buckled || target.has_buckled_mobs() || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || istype(user, /mob/living/silicon/ai)) - return - if(isanimal(user) && target != user) return //animals cannot put mobs other than themselves into disposal - src.add_fingerprint(user) - var/target_loc = target.loc - var/msg - for(var/mob/V in viewers(usr)) - if(target == user && !user.stat && !user.IsWeakened() && !user.stunned && !user.paralysis) - V.show_message("[usr] starts climbing into the disposal.", 3) - if(target != user && !user.restrained() && !user.stat && !user.IsWeakened() && !user.stunned && !user.paralysis) - if(target.anchored) return - V.show_message("[usr] starts stuffing [target.name] into the disposal.", 3) - if(!do_after(usr, 20, target = target)) - return - if(target_loc != target.loc) - return - if(target == user && !user.stat && !user.IsWeakened() && !user.stunned && !user.paralysis) // if drop self, then climbed in - // must be awake, not stunned or whatever - msg = "[user.name] climbs into the [src]." - to_chat(user, "You climb into the [src].") - else if(target != user && !user.restrained() && !user.stat && !user.IsWeakened() && !user.stunned && !user.paralysis) - msg = "[user.name] stuffs [target.name] into the [src]!" - to_chat(user, "You stuff [target.name] into the [src]!") - - add_attack_logs(user, target, "Disposal'ed", !!target.ckey ? null : ATKLOG_ALL) - else - return - target.forceMove(src) - - for(var/mob/C in viewers(src)) - if(C == user) - continue - C.show_message(msg, 3) - - update() - return - -// attempt to move while inside -/obj/machinery/disposal/relaymove(mob/user as mob) - if(user.stat || src.flushing) - return - src.go_out(user) - return - -// leave the disposal -/obj/machinery/disposal/proc/go_out(mob/user) - if(user) - user.forceMove(loc) - update() - -// ai as human but can't flush -/obj/machinery/disposal/attack_ai(mob/user as mob) - src.add_hiddenprint(user) - ui_interact(user) - -/obj/machinery/disposal/attack_ghost(mob/user as mob) - ui_interact(user) - -// human interact with machine -/obj/machinery/disposal/attack_hand(mob/user as mob) - if(..(user)) - return 1 - - if(stat & BROKEN) - return - - if(user && user.loc == src) - to_chat(usr, "You cannot reach the controls from inside.") - return - - // Clumsy folks can only flush it. - if(user.IsAdvancedToolUser()) - ui_interact(user) - else - flush = !flush - update() - return - -/obj/machinery/disposal/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "disposal_bin.tmpl", "Waste Disposal Unit", 395, 250) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/disposal/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - var/pressure = Clamp(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 100) - var/pressure_round = round(pressure,1) - - data["isAI"] = isAI(user) - data["flushing"] = flush - data["mode"] = mode - if(mode <= 0) - data["pumpstatus"] = "N/A" - else if(mode == 1) - data["pumpstatus"] = "Pressurizing" - else if(mode == 2) - data["pumpstatus"] = "Ready" - else - data["pumpstatus"] = "Idle" - data["pressure"] = pressure_round - - return data - -/obj/machinery/disposal/Topic(href, href_list) - if(usr.loc == src) - to_chat(usr, "You cannot reach the controls from inside.") - return - - if(mode==-1 && !href_list["eject"]) // only allow ejecting if mode is -1 - to_chat(usr, "The disposal units power is disabled.") - return - - if(..()) - return - - if(stat & BROKEN) - return - - src.add_fingerprint(usr) - - if(usr.stat || usr.restrained() || src.flushing) - return - - if(istype(src.loc, /turf)) - if(href_list["pump"]) - if(text2num(href_list["pump"])) - mode = 1 - else - mode = 0 - update() - - if(!isAI(usr)) - if(href_list["handle"]) - flush = text2num(href_list["handle"]) - update() - - if(href_list["eject"]) - eject() - return - -// eject the contents of the disposal unit -/obj/machinery/disposal/proc/eject() - for(var/atom/movable/AM in src) - AM.loc = src.loc - AM.pipe_eject(0) - update() - -// update the icon & overlays to reflect mode & status -/obj/machinery/disposal/proc/update() - overlays.Cut() - if(stat & BROKEN) - icon_state = "disposal-broken" - mode = 0 - flush = 0 - return - - // flush handle - if(flush) - overlays += image('icons/obj/pipes/disposal.dmi', "dispover-handle") - - // only handle is shown if no power - if(stat & NOPOWER || mode == -1) - return - - // check for items in disposal - occupied light - if(contents.len > 0) - overlays += image('icons/obj/pipes/disposal.dmi', "dispover-full") - - // charging and ready light - if(mode == 1) - overlays += image('icons/obj/pipes/disposal.dmi', "dispover-charge") - else if(mode == 2) - overlays += image('icons/obj/pipes/disposal.dmi', "dispover-ready") - -// timed process -// charge the gas reservoir and perform flush if ready -/obj/machinery/disposal/process() - use_power = NO_POWER_USE - if(stat & BROKEN) // nothing can happen if broken - return - - flush_count++ - if( flush_count >= flush_every_ticks ) - if( contents.len ) - if(mode == 2) - spawn(0) - feedback_inc("disposal_auto_flush",1) - flush() - flush_count = 0 - - src.updateDialog() - - if(flush && air_contents.return_pressure() >= SEND_PRESSURE ) // flush can happen even without power - flush() - - if(stat & NOPOWER) // won't charge if no power - return - - use_power = IDLE_POWER_USE - - if(mode != 1) // if off or ready, no need to charge - return - - // otherwise charge - use_power = ACTIVE_POWER_USE - - var/atom/L = loc // recharging from loc turf - - var/datum/gas_mixture/env = L.return_air() - var/pressure_delta = (SEND_PRESSURE*1.01) - air_contents.return_pressure() - - if(env.temperature > 0) - var/transfer_moles = 0.1 * pressure_delta*air_contents.volume/(env.temperature * R_IDEAL_GAS_EQUATION) - - //Actually transfer the gas - var/datum/gas_mixture/removed = env.remove(transfer_moles) - air_contents.merge(removed) - air_update_turf() - - - // if full enough, switch to ready mode - if(air_contents.return_pressure() >= SEND_PRESSURE) - mode = 2 - update() - return - -// perform a flush -/obj/machinery/disposal/proc/flush() - - flushing = 1 - flick("[icon_state]-flush", src) - - var/wrapcheck = 0 - var/obj/structure/disposalholder/H = new() // virtual holder object which actually - // travels through the pipes. - //Hacky test to get drones to mail themselves through disposals. - for(var/mob/living/silicon/robot/drone/D in src) - wrapcheck = 1 - - for(var/mob/living/silicon/robot/syndicate/saboteur/R in src) - wrapcheck = 1 - - for(var/obj/item/smallDelivery/O in src) - wrapcheck = 1 - - if(wrapcheck == 1) - H.tomail = 1 - - sleep(10) - if(last_sound < world.time + 1) - playsound(src, 'sound/machines/disposalflush.ogg', 50, 0, 0) - last_sound = world.time - sleep(5) // wait for animation to finish - - - H.init(src) // copy the contents of disposer to holder - air_contents = new() // The holder just took our gas; replace it - H.start(src) // start the holder processing movement - flushing = 0 - // now reset disposal state - flush = 0 - if(mode == 2) // if was ready, - mode = 1 // switch to charging - update() - return - - -// called when area power changes -/obj/machinery/disposal/power_change() - ..() // do default setting/reset of stat NOPOWER bit - update() // update icon - return - - -// called when holder is expelled from a disposal -// should usually only occur if the pipe network is modified -/obj/machinery/disposal/proc/expel(var/obj/structure/disposalholder/H) - - var/turf/target - playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0) - if(H) // Somehow, someone managed to flush a window which broke mid-transit and caused the disposal to go in an infinite loop trying to expel null, hopefully this fixes it - for(var/atom/movable/AM in H) - target = get_offset_target_turf(src.loc, rand(5)-rand(5), rand(5)-rand(5)) - - AM.loc = src.loc - AM.pipe_eject(0) - if(!istype(AM, /mob/living/silicon/robot/drone) && !istype(AM, /mob/living/silicon/robot/syndicate/saboteur)) //Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z - spawn(1) - if(AM) - AM.throw_at(target, 5, 1) - - H.vent_gas(loc) - qdel(H) - -/obj/machinery/disposal/CanPass(atom/movable/mover, turf/target, height=0) - if(istype(mover,/obj/item) && mover.throwing) - var/obj/item/I = mover - if(istype(I, /obj/item/projectile)) - return - if(prob(75)) - I.loc = src - for(var/mob/M in viewers(src)) - M.show_message("\the [I] lands in \the [src].", 3) - update() - else - for(var/mob/M in viewers(src)) - M.show_message("\the [I] bounces off of \the [src]'s rim!.", 3) - return 0 - else - return ..(mover, target, height) - - -/obj/machinery/disposal/singularity_pull(S, current_size) - if(current_size >= STAGE_FIVE) - qdel(src) - -/obj/machinery/disposal/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) - -// virtual disposal object -// travels through pipes in lieu of actual items -// contents will be items flushed by the disposal -// this allows the gas flushed to be tracked - -/obj/structure/disposalholder - invisibility = 101 - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/datum/gas_mixture/gas = null // gas used to flush, will appear at exit point - var/active = 0 // true if the holder is moving, otherwise inactive - dir = 0 - var/count = 1000 //*** can travel 1000 steps before going inactive (in case of loops) - var/has_fat_guy = 0 // true if contains a fat person - var/destinationTag = 0 // changes if contains a delivery container - var/tomail = 0 //changes if contains wrapped package - var/hasmob = 0 //If it contains a mob - -/obj/structure/disposalholder/Destroy() - QDEL_NULL(gas) - active = 0 - return ..() - - // initialize a holder from the contents of a disposal unit -/obj/structure/disposalholder/proc/init(var/obj/machinery/disposal/D) - gas = D.air_contents// transfer gas resv. into holder object - - //Check for any living mobs trigger hasmob. - //hasmob effects whether the package goes to cargo or its tagged destination. - for(var/mob/living/M in D) - if(M && M.stat != 2 && !istype(M, /mob/living/silicon/robot/drone) && !istype(M, /mob/living/silicon/robot/syndicate/saboteur)) - hasmob = 1 - - //Checks 1 contents level deep. This means that players can be sent through disposals... - //...but it should require a second person to open the package. (i.e. person inside a wrapped locker) - for(var/obj/O in D) - if(O.contents) - for(var/mob/living/M in O.contents) - if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone) && !istype(M, /mob/living/silicon/robot/syndicate/saboteur)) - hasmob = 1 - - // now everything inside the disposal gets put into the holder - // note AM since can contain mobs or objs - for(var/atom/movable/AM in D) - AM.loc = src - SEND_SIGNAL(AM, COMSIG_MOVABLE_DISPOSING, src, D) - if(istype(AM, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = AM - if(FAT in H.mutations) // is a human and fat? - has_fat_guy = 1 // set flag on holder - if(istype(AM, /obj/structure/bigDelivery) && !hasmob) - var/obj/structure/bigDelivery/T = AM - destinationTag = T.sortTag - if(istype(AM, /obj/item/smallDelivery) && !hasmob) - var/obj/item/smallDelivery/T = AM - destinationTag = T.sortTag - //Drones can mail themselves through maint. - if(istype(AM, /mob/living/silicon/robot/drone)) - var/mob/living/silicon/robot/drone/drone = AM - destinationTag = drone.mail_destination - if(istype(AM, /mob/living/silicon/robot/syndicate/saboteur)) - var/mob/living/silicon/robot/syndicate/saboteur/S = AM - destinationTag = S.mail_destination - if(istype(AM, /obj/item/shippingPackage) && !hasmob) - var/obj/item/shippingPackage/sp = AM - if(sp.sealed) //only sealed packages get delivered to their intended destination - destinationTag = sp.sortTag - - - // start the movement process - // argument is the disposal unit the holder started in -/obj/structure/disposalholder/proc/start(var/obj/machinery/disposal/D) - if(!D.trunk) - D.expel(src) // no trunk connected, so expel immediately - return - - loc = D.trunk - active = 1 - dir = DOWN - spawn(1) - move() // spawn off the movement process - - return - - // movement process, persists while holder is moving through pipes -/obj/structure/disposalholder/proc/move() - var/obj/structure/disposalpipe/last - while(active) - /* if(hasmob && prob(3)) - for(var/mob/living/H in src) - if(!istype(H,/mob/living/silicon/robot/drone)) //Drones use the mailing code to move through the disposal system, - H.take_overall_damage(20, 0, "Blunt Trauma") */ //horribly maim any living creature jumping down disposals. c'est la vie - - if(has_fat_guy && prob(2)) // chance of becoming stuck per segment if contains a fat guy - active = 0 - // find the fat guys - for(var/mob/living/carbon/human/H in src) - - break - sleep(1) // was 1 - var/obj/structure/disposalpipe/curr = loc - last = curr - curr = curr.transfer(src) - if(!curr) - last.expel(src, loc, dir) - - // - if(!(count--)) - active = 0 - return - - - - // find the turf which should contain the next pipe -/obj/structure/disposalholder/proc/nextloc() - return get_step(loc,dir) - - // find a matching pipe on a turf -/obj/structure/disposalholder/proc/findpipe(var/turf/T) - if(!T) - return null - - var/fdir = turn(dir, 180) // flip the movement direction - for(var/obj/structure/disposalpipe/P in T) - if(fdir & P.dpdir) // find pipe direction mask that matches flipped dir - return P - // if no matching pipe, return null - return null - - // merge two holder objects - // used when a a holder meets a stuck holder -/obj/structure/disposalholder/proc/merge(var/obj/structure/disposalholder/other) - for(var/atom/movable/AM in other) - AM.loc = src // move everything in other holder to this one - if(ismob(AM)) - var/mob/M = AM - if(M.client) // if a client mob, update eye to follow this holder - M.client.eye = src - - if(other.has_fat_guy) - has_fat_guy = 1 - qdel(other) - - - // called when player tries to move while in a pipe -/obj/structure/disposalholder/relaymove(mob/user as mob) - if(!istype(user,/mob/living)) - return - - var/mob/living/U = user - - if(U.stat || U.last_special <= world.time) - return - - U.last_special = world.time+100 - - if(src.loc) - for(var/mob/M in hearers(src.loc.loc)) - to_chat(M, "CLONG, clong!") - - playsound(src.loc, 'sound/effects/clang.ogg', 50, 0, 0) - - // called to vent all gas in holder to a location -/obj/structure/disposalholder/proc/vent_gas(var/atom/location) - if(location) - location.assume_air(gas) // vent all gas to turf - air_update_turf() - return - -// Disposal pipes - -/obj/structure/disposalpipe - icon = 'icons/obj/pipes/disposal.dmi' - name = "disposal pipe" - desc = "An underfloor disposal pipe." - anchored = 1 - density = 0 - - on_blueprints = TRUE - level = 1 // underfloor only - var/dpdir = 0 // bitmask of pipe directions - dir = 0 // dir will contain dominant direction for junction pipes - var/health = 10 // health points 0-10 - max_integrity = 200 - armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) - damage_deflection = 10 - layer = DISPOSAL_PIPE_LAYER // slightly lower than wires and other pipes - var/base_icon_state // initial icon state on map - - // new pipe, set the icon_state as on map -/obj/structure/disposalpipe/New() - ..() - base_icon_state = icon_state - - -// pipe is deleted -// ensure if holder is present, it is expelled -/obj/structure/disposalpipe/Destroy() - for(var/obj/structure/disposalholder/H in contents) - H.active = 0 - var/turf/T = loc - if(T.density) - // deleting pipe is inside a dense turf (wall) - // this is unlikely, but just dump out everything into the turf in case - - for(var/atom/movable/AM in H) - AM.loc = T - AM.pipe_eject(0) - qdel(H) - ..() - return - - // otherwise, do normal expel from turf - expel(H, T, 0) - return ..() - -/obj/structure/disposalpipe/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -// returns the direction of the next pipe object, given the entrance dir -// by default, returns the bitmask of remaining directions -/obj/structure/disposalpipe/proc/nextdir(var/fromdir) - return dpdir & (~turn(fromdir, 180)) - -// transfer the holder through this pipe segment -// overriden for special behaviour -// -/obj/structure/disposalpipe/proc/transfer(var/obj/structure/disposalholder/H) - var/nextdir = nextdir(H.dir) - H.dir = nextdir - var/turf/T = H.nextloc() - var/obj/structure/disposalpipe/P = H.findpipe(T) - - if(P) - // find other holder in next loc, if inactive merge it with current - var/obj/structure/disposalholder/H2 = locate() in P - if(H2 && !H2.active) - H.merge(H2) - - H.loc = P - else // if wasn't a pipe, then set loc to turf - H.loc = T - return null - - return P - - -// update the icon_state to reflect hidden status -/obj/structure/disposalpipe/proc/update() - var/turf/T = src.loc - hide(T.intact && !istype(T,/turf/space)) // space never hides pipes - update_icon() - -// hide called by levelupdate if turf intact status changes -// change visibility status and force update of icon -/obj/structure/disposalpipe/hide(var/intact) - invisibility = intact ? 101: 0 // hide if floor is intact - update_icon() - -// update actual icon_state depending on visibility -// if invisible, append "f" to icon_state to show faded version -// this will be revealed if a T-scanner is used -// if visible, use regular icon_state -/obj/structure/disposalpipe/update_icon() - if(invisibility) - icon_state = "[base_icon_state]f" - else - icon_state = base_icon_state - - -// expel the held objects into a turf -// called when there is a break in the pipe -// - -/obj/structure/disposalpipe/proc/expel(var/obj/structure/disposalholder/H, var/turf/T, var/direction) - - if(!T) - return - - var/turf/target - - if(T.density) // dense ouput turf, so stop holder - H.active = 0 - H.loc = src - return - if(T.intact && istype(T,/turf/simulated/floor)) //intact floor, pop the tile - var/turf/simulated/floor/F = T - new F.builtin_tile.type(H) - F.remove_tile(null,TRUE,FALSE) - - if(direction) // direction is specified - if(istype(T, /turf/space)) // if ended in space, then range is unlimited - target = get_edge_target_turf(T, direction) - else // otherwise limit to 10 tiles - target = get_ranged_target_turf(T, direction, 10) - - playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0) - if(H) - for(var/atom/movable/AM in H) - AM.loc = T - AM.pipe_eject(direction) - spawn(1) - if(AM) - AM.throw_at(target, 100, 1) - H.vent_gas(T) - qdel(H) - - else // no specified direction, so throw in random direction - - playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0) - if(H) - for(var/atom/movable/AM in H) - target = get_offset_target_turf(T, rand(5)-rand(5), rand(5)-rand(5)) - - AM.loc = T - AM.pipe_eject(0) - spawn(1) - if(AM) - AM.throw_at(target, 5, 1) - - H.vent_gas(T) // all gas vent to turf - qdel(H) - -// call to break the pipe -// will expel any holder inside at the time -// then delete the pipe -// remains : set to leave broken pipe pieces in place -/obj/structure/disposalpipe/proc/broken(remains = 0) - if(remains) - for(var/D in cardinal) - if(D & dpdir) - var/obj/structure/disposalpipe/broken/P = new(src.loc) - P.setDir(D) - - invisibility = 101 // make invisible (since we won't delete the pipe immediately) - var/obj/structure/disposalholder/H = locate() in src - if(H) - // holder was present - H.active = 0 - var/turf/T = src.loc - if(T.density) - // broken pipe is inside a dense turf (wall) - // this is unlikely, but just dump out everything into the turf in case - - for(var/atom/movable/AM in H) - AM.loc = T - AM.pipe_eject(0) - qdel(H) - return - - // otherwise, do normal expel from turf - if(H) - expel(H, T, 0) - - spawn(2) // delete pipe after 2 ticks to ensure expel proc finished - qdel(src) - -// pipe affected by explosion -/obj/structure/disposalpipe/ex_act(severity) - switch(severity) - if(1) - broken(0) - if(2) - health -= rand(5, 15) - healthcheck() - if(3) - health -= rand(0, 15) - healthcheck() - -// test health for brokenness -/obj/structure/disposalpipe/proc/healthcheck() - if(health < -2) - broken(0) - else if(health<1) - broken(1) - return - -//attack by item -//weldingtool: unfasten and convert to obj/disposalconstruct - -/obj/structure/disposalpipe/attackby(var/obj/item/I, var/mob/user, params) - var/turf/T = get_turf(src) - if(T.intact) - return // prevent interaction with T-scanner revealed pipes - - add_fingerprint(user) - -/obj/structure/disposalpipe/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_SLICING_MESSAGE - if(!I.use_tool(src, user, 30, volume = I.tool_volume)) - return - WELDER_SLICING_SUCCESS_MESSAGE - var/obj/structure/disposalconstruct/C = new (get_turf(src)) - switch(base_icon_state) - if("pipe-s") - C.ptype = PIPE_DISPOSALS_STRAIGHT - if("pipe-c") - C.ptype = PIPE_DISPOSALS_BENT - if("pipe-j1") - C.ptype = PIPE_DISPOSALS_JUNCTION_RIGHT - if("pipe-j2") - C.ptype = PIPE_DISPOSALS_JUNCTION_LEFT - if("pipe-y") - C.ptype = PIPE_DISPOSALS_Y_JUNCTION - if("pipe-t") - C.ptype = PIPE_DISPOSALS_TRUNK - if("pipe-j1s") - C.ptype = PIPE_DISPOSALS_SORT_RIGHT - if("pipe-j2s") - C.ptype = PIPE_DISPOSALS_SORT_LEFT - src.transfer_fingerprints_to(C) - C.dir = dir - C.density = FALSE - C.anchored = TRUE - C.update() - - qdel(src) - -// *** TEST verb -//client/verb/dispstop() -// for(var/obj/structure/disposalholder/H in world) -// H.active = 0 - -// a straight or bent segment -/obj/structure/disposalpipe/segment - icon_state = "pipe-s" - -/obj/structure/disposalpipe/segment/New() - ..() - if(icon_state == "pipe-s") - dpdir = dir | turn(dir, 180) - else - dpdir = dir | turn(dir, -90) - - update() - - - -//a three-way junction with dir being the dominant direction -/obj/structure/disposalpipe/junction - icon_state = "pipe-j1" - -/obj/structure/disposalpipe/junction/New() - ..() - if(icon_state == "pipe-j1") - dpdir = dir | turn(dir, -90) | turn(dir,180) - else if(icon_state == "pipe-j2") - dpdir = dir | turn(dir, 90) | turn(dir,180) - else // pipe-y - dpdir = dir | turn(dir,90) | turn(dir, -90) - update() - - - // next direction to move - // if coming in from secondary dirs, then next is primary dir - // if coming in from primary dir, then next is equal chance of other dirs - -/obj/structure/disposalpipe/junction/nextdir(var/fromdir) - var/flipdir = turn(fromdir, 180) - if(flipdir != dir) // came from secondary dir - return dir // so exit through primary - else // came from primary - // so need to choose either secondary exit - var/mask = ..(fromdir) - - // find a bit which is set - var/setbit = 0 - if(mask & NORTH) - setbit = NORTH - else if(mask & SOUTH) - setbit = SOUTH - else if(mask & EAST) - setbit = EAST - else - setbit = WEST - - if(prob(50)) // 50% chance to choose the found bit or the other one - return setbit - else - return mask & (~setbit) - -//a three-way junction that sorts objects -/obj/structure/disposalpipe/sortjunction - - icon_state = "pipe-j1s" - var/sortType = 0 //Look at the list called TAGGERLOCATIONS in /code/_globalvars/lists/flavor_misc.dm - var/posdir = 0 - var/negdir = 0 - var/sortdir = 0 - -/obj/structure/disposalpipe/sortjunction/proc/updatedesc() - desc = "An underfloor disposal pipe with a package sorting mechanism." - if(sortType>0) - var/tag = uppertext(GLOB.TAGGERLOCATIONS[sortType]) - desc += "\nIt's tagged with [tag]" - -/obj/structure/disposalpipe/sortjunction/proc/updatedir() - posdir = dir - negdir = turn(posdir, 180) - - if(icon_state == "pipe-j1s") - sortdir = turn(posdir, -90) - else - icon_state = "pipe-j2s" - sortdir = turn(posdir, 90) - - dpdir = sortdir | posdir | negdir - -/obj/structure/disposalpipe/sortjunction/New() - ..() - updatedir() - updatedesc() - update() - return - -/obj/structure/disposalpipe/sortjunction/attackby(var/obj/item/I, var/mob/user, params) - if(..()) - return - - if(istype(I, /obj/item/destTagger)) - var/obj/item/destTagger/O = I - - if(O.currTag > 0)// Tag set - sortType = O.currTag - name = GLOB.TAGGERLOCATIONS[O.currTag] - playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1) - var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) - to_chat(user, "Changed filter to [tag]") - updatedesc() - - - // next direction to move - // if coming in from negdir, then next is primary dir or sortdir - // if coming in from posdir, then flip around and go back to posdir - // if coming in from sortdir, go to posdir - -/obj/structure/disposalpipe/sortjunction/nextdir(var/fromdir, var/sortTag) - //var/flipdir = turn(fromdir, 180) - if(fromdir != sortdir) // probably came from the negdir - - if(src.sortType == sortTag) //if destination matches filtered type... - return sortdir // exit through sortdirection - else - return posdir - else // came from sortdir - // so go with the flow to positive direction - return posdir - -/obj/structure/disposalpipe/sortjunction/transfer(var/obj/structure/disposalholder/H) - var/nextdir = nextdir(H.dir, H.destinationTag) - H.dir = nextdir - var/turf/T = H.nextloc() - var/obj/structure/disposalpipe/P = H.findpipe(T) - - if(P) - // find other holder in next loc, if inactive merge it with current - var/obj/structure/disposalholder/H2 = locate() in P - if(H2 && !H2.active) - H.merge(H2) - H.loc = P - else // if wasn't a pipe, then set loc to turf - H.loc = T - return null - - return P - - -//a three-way junction that sorts objects destined for the mail office mail table (tomail = 1) -/obj/structure/disposalpipe/wrapsortjunction - desc = "An underfloor disposal pipe which sorts wrapped and unwrapped objects." - icon_state = "pipe-j1s" - var/posdir = 0 - var/negdir = 0 - var/sortdir = 0 - -/obj/structure/disposalpipe/wrapsortjunction/New() - ..() - posdir = dir - if(icon_state == "pipe-j1s") - sortdir = turn(posdir, -90) - negdir = turn(posdir, 180) - else - icon_state = "pipe-j2s" - sortdir = turn(posdir, 90) - negdir = turn(posdir, 180) - dpdir = sortdir | posdir | negdir - - update() - return - - - // next direction to move - // if coming in from negdir, then next is primary dir or sortdir - // if coming in from posdir, then flip around and go back to posdir - // if coming in from sortdir, go to posdir - -/obj/structure/disposalpipe/wrapsortjunction/nextdir(var/fromdir, var/istomail) - //var/flipdir = turn(fromdir, 180) - if(fromdir != sortdir) // probably came from the negdir - if(istomail) //if destination matches filtered type... - return sortdir // exit through sortdirection - else - return posdir - else // came from sortdir - return posdir // so go with the flow to positive direction - -/obj/structure/disposalpipe/wrapsortjunction/transfer(var/obj/structure/disposalholder/H) - var/nextdir = nextdir(H.dir, H.tomail) - H.dir = nextdir - var/turf/T = H.nextloc() - var/obj/structure/disposalpipe/P = H.findpipe(T) - - if(P) - // find other holder in next loc, if inactive merge it with current - var/obj/structure/disposalholder/H2 = locate() in P - if(H2 && !H2.active) - H.merge(H2) - - H.loc = P - else // if wasn't a pipe, then set loc to turf - H.loc = T - return null - - return P - -//a trunk joining to a disposal bin or outlet on the same turf -/obj/structure/disposalpipe/trunk - icon_state = "pipe-t" - var/obj/linked // the linked obj/machinery/disposal or obj/disposaloutlet - -/obj/structure/disposalpipe/trunk/New() - ..() - dpdir = dir - spawn(1) - getlinked() - - update() - return - -/obj/structure/disposalpipe/trunk/Destroy() - if(istype(linked, /obj/structure/disposaloutlet)) - var/obj/structure/disposaloutlet/O = linked - O.expel(animation = 0) - else if(istype(linked, /obj/machinery/disposal)) - var/obj/machinery/disposal/D = linked - if(D.trunk == src) - D.go_out() - D.trunk = null - remove_trunk_links() - return ..() - -/obj/structure/disposalpipe/trunk/proc/getlinked() - var/obj/machinery/disposal/D = locate() in src.loc - if(D) - nicely_link_to_other_stuff(D) - return - var/obj/structure/disposaloutlet/O = locate() in src.loc - if(O) - nicely_link_to_other_stuff(O) - -/obj/structure/disposalpipe/trunk/proc/remove_trunk_links() //disposals is well-coded - if(!linked) - return - else if(istype(linked, /obj/machinery/disposal)) //jk lol - var/obj/machinery/disposal/D = linked - D.trunk = null - else if(istype(linked, /obj/structure/disposaloutlet)) //God fucking damn it - var/obj/structure/disposaloutlet/D = linked - D.linkedtrunk = null - linked = null - -/obj/structure/disposalpipe/trunk/proc/nicely_link_to_other_stuff(obj/O) - remove_trunk_links() //Breaks the connections between this trunk and the linked machinery so we don't get sent to nullspace or some shit like that - if(istype(O, /obj/machinery/disposal)) - var/obj/machinery/disposal/D = O - linked = D - D.trunk = src - else if(istype(O, /obj/structure/disposaloutlet)) - var/obj/structure/disposaloutlet/D = O - linked = D - D.linkedtrunk = src - - // Override attackby so we disallow trunkremoval when somethings ontop -/obj/structure/disposalpipe/trunk/attackby(var/obj/item/I, var/mob/user, params) - - //Disposal bins or chutes - //Disposal constructors - var/obj/structure/disposalconstruct/C = locate() in src.loc - if(C && C.anchored) - return - - var/turf/T = src.loc - if(T.intact) - return // prevent interaction with T-scanner revealed pipes - src.add_fingerprint(user) - - // would transfer to next pipe segment, but we are in a trunk - // if not entering from disposal bin, - // transfer to linked object (outlet or bin) - -/obj/structure/disposalpipe/trunk/transfer(obj/structure/disposalholder/H) - if(!H) - return - if(H.dir == DOWN) // we just entered from a disposer - return ..() // so do base transfer proc - // otherwise, go to the linked object - if(!linked) - expel(H, loc, FALSE) // expel at turf - else if(istype(linked, /obj/structure/disposaloutlet)) - var/obj/structure/disposaloutlet/DO = linked - for(var/atom/movable/AM in H) - AM.forceMove(DO) - qdel(H) - H.vent_gas(loc) - DO.expel() - else if(istype(linked, /obj/machinery/disposal)) - var/obj/machinery/disposal/D = linked - H.forceMove(D) - D.expel(H) // expel at disposal - else //just in case - expel(H, loc, FALSE) - // nextdir - -/obj/structure/disposalpipe/trunk/nextdir(var/fromdir) - if(fromdir == DOWN) - return dir - else - return 0 - -// a broken pipe -/obj/structure/disposalpipe/broken - icon_state = "pipe-b" - dpdir = 0 // broken pipes have dpdir=0 so they're not found as 'real' pipes - // i.e. will be treated as an empty turf - desc = "A broken piece of disposal pipe." - -/obj/structure/disposalpipe/broken/New() - ..() - update() - return - -/obj/structure/disposalpipe/broken/welder_act(mob/user, obj/item/I) - if(I.use_tool(src, user, 0, volume = I.tool_volume)) - to_chat(user, "You remove [src]!") - I.play_tool_sound(src, I.tool_volume) - qdel(src) - return TRUE - -// the disposal outlet machine - -/obj/structure/disposaloutlet - name = "disposal outlet" - desc = "An outlet for the pneumatic disposal system." - icon = 'icons/obj/pipes/disposal.dmi' - icon_state = "outlet" - density = 1 - anchored = 1 - var/active = 0 - var/turf/target // this will be where the output objects are 'thrown' to. - var/obj/structure/disposalpipe/trunk/linkedtrunk - var/mode = 0 - -/obj/structure/disposaloutlet/New() - ..() - spawn(1) - target = get_ranged_target_turf(src, dir, 10) - var/obj/structure/disposalpipe/trunk/T = locate() in loc - if(T) - T.nicely_link_to_other_stuff(src) - -/obj/structure/disposaloutlet/Destroy() - if(linkedtrunk) - linkedtrunk.remove_trunk_links() - expel(FALSE) - return ..() - - -// expel the contents of the outlet -/obj/structure/disposaloutlet/proc/expel(animation = TRUE) - if(animation) - flick("outlet-open", src) - playsound(src, 'sound/machines/warning-buzzer.ogg', 50, 0, 0) - sleep(20) //wait until correct animation frame - playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0) - for(var/atom/movable/AM in contents) - AM.forceMove(loc) - AM.pipe_eject(dir) - if(istype(AM,/mob/living/silicon/robot/drone) || istype(AM, /mob/living/silicon/robot/syndicate/saboteur)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z - return - spawn(5) - if(QDELETED(AM)) - return - AM.throw_at(target, 3, 1) - - -/obj/structure/disposaloutlet/attackby(var/obj/item/I, var/mob/user, params) - if(!I || !user) - return - src.add_fingerprint(user) - if(istype(I, /obj/item/screwdriver)) - if(mode==0) - mode=1 - playsound(src.loc, I.usesound, 50, 1) - to_chat(user, "You remove the screws around the power connection.") - return - else if(mode==1) - mode=0 - playsound(src.loc, I.usesound, 50, 1) - to_chat(user, "You attach the screws around the power connection.") - return - -/obj/structure/disposaloutlet/welder_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE - if(I.use_tool(src, user, 20, volume = I.tool_volume)) - WELDER_FLOOR_SLICE_SUCCESS_MESSAGE - var/obj/structure/disposalconstruct/C = new (src.loc) - C.ptype = PIPE_DISPOSALS_OUTLET - C.update() - C.anchored = TRUE - C.density = TRUE - transfer_fingerprints_to(C) - qdel(src) - -//When the disposalsoutlet is forcefully moved. Due to meteorshot or the recall item spell for instance -/obj/structure/disposaloutlet/Moved(atom/OldLoc, Dir) - . = ..() - var/turf/T = OldLoc - if(T.intact) - var/turf/simulated/floor/F = T - F.remove_tile(null,TRUE,TRUE) - T.visible_message("The floortile is ripped from the floor!", "You hear a loud bang!") - if(linkedtrunk) - linkedtrunk.remove_trunk_links() - var/obj/structure/disposalconstruct/C = new (loc) - transfer_fingerprints_to(C) - C.ptype = PIPE_DISPOSALS_OUTLET - C.update() - C.anchored = 0 - C.density = 1 - qdel(src) - -// called when movable is expelled from a disposal pipe or outlet -// by default does nothing, override for special behaviour - -/atom/movable/proc/pipe_eject(var/direction) - return - -// check if mob has client, if so restore client view on eject -/mob/pipe_eject(var/direction) - reset_perspective(null) - -/obj/effect/decal/cleanable/blood/gibs/pipe_eject(var/direction) - var/list/dirs - if(direction) - dirs = list( direction, turn(direction, -45), turn(direction, 45)) - else - dirs = alldirs.Copy() - - src.streak(dirs) - -/obj/effect/decal/cleanable/blood/gibs/robot/gib/pipe_eject(var/direction) - var/list/dirs - if(direction) - dirs = list( direction, turn(direction, -45), turn(direction, 45)) - else - dirs = alldirs.Copy() - - src.streak(dirs) +// Disposal bin +// Holds items for disposal into pipe system +// Draws air from turf, gradually charges internal reservoir +// Once full (~1 atm), uses air resv to flush items into the pipes +// Automatically recharges air (unless off), will flush when ready if pre-set +// Can hold items and human size things, no other draggables +// Toilets are a type of disposal bin for small objects only and work on magic. By magic, I mean torque rotation +#define SEND_PRESSURE 0.05*ONE_ATMOSPHERE + +/obj/machinery/disposal + name = "disposal unit" + desc = "A pneumatic waste disposal unit." + icon = 'icons/obj/pipes/disposal.dmi' + icon_state = "disposal" + anchored = 1 + density = 1 + on_blueprints = TRUE + armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) + max_integrity = 200 + resistance_flags = FIRE_PROOF + var/datum/gas_mixture/air_contents // internal reservoir + var/mode = 1 // item mode 0=off 1=charging 2=charged + var/flush = 0 // true if flush handle is pulled + var/obj/structure/disposalpipe/trunk/trunk = null // the attached pipe trunk + var/flushing = 0 // true if flushing in progress + var/flush_every_ticks = 30 //Every 30 ticks it will look whether it is ready to flush + var/flush_count = 0 //this var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush. + var/last_sound = 0 + var/required_mode_to_deconstruct = -1 + var/deconstructs_to = PIPE_DISPOSALS_BIN + active_power_usage = 600 + idle_power_usage = 100 + + +// create a new disposal +// find the attached trunk (if present) +/obj/machinery/disposal/New() + ..() + trunk_check() + //gas.volume = 1.05 * CELLSTANDARD + update() + +/obj/machinery/disposal/proc/trunk_check() + var/obj/structure/disposalpipe/trunk/T = locate() in loc + if(!T) + mode = 0 + flush = 0 + else + mode = initial(mode) + flush = initial(flush) + T.nicely_link_to_other_stuff(src) + +//When the disposalsoutlet is forcefully moved. Due to meteorshot (not the recall spell) +/obj/machinery/disposal/Moved(atom/OldLoc, Dir) + . = ..() + eject() + var/ptype = istype(src, /obj/machinery/disposal/deliveryChute) ? PIPE_DISPOSALS_CHUTE : PIPE_DISPOSALS_BIN //Check what disposaltype it is + var/turf/T = OldLoc + if(T.intact) + var/turf/simulated/floor/F = T + F.remove_tile(null,TRUE,TRUE) + T.visible_message("The floortile is ripped from the floor!", "You hear a loud bang!") + if(trunk) + trunk.remove_trunk_links() + var/obj/structure/disposalconstruct/C = new (loc) + transfer_fingerprints_to(C) + C.ptype = ptype + C.update() + C.anchored = 0 + C.density = 1 + qdel(src) + +/obj/machinery/disposal/Destroy() + eject() + if(trunk) + trunk.remove_trunk_links() + return ..() + +/obj/machinery/disposal/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +/obj/machinery/disposal/Initialize() + // this will get a copy of the air turf and take a SEND PRESSURE amount of air from it + ..() + var/atom/L = loc + var/datum/gas_mixture/env = new + env.copy_from(L.return_air()) + var/datum/gas_mixture/removed = env.remove(SEND_PRESSURE + 1) + air_contents = new + air_contents.merge(removed) + trunk_check() + +// attack by item places it in to disposal +/obj/machinery/disposal/attackby(var/obj/item/I, var/mob/user, params) + if(stat & BROKEN || !I || !user) + return + + src.add_fingerprint(user) + + if(istype(I, /obj/item/melee/energy/blade)) + to_chat(user, "You can't place that item inside the disposal unit.") + return + + if(istype(I, /obj/item/storage)) + var/obj/item/storage/S = I + if((S.allow_quick_empty || S.allow_quick_gather) && S.contents.len) + S.hide_from(user) + user.visible_message("[user] empties \the [S] into \the [src].", "You empty \the [S] into \the [src].") + for(var/obj/item/O in S.contents) + S.remove_from_storage(O, src) + S.update_icon() // For content-sensitive icons + update() + return + + var/obj/item/grab/G = I + if(istype(G)) // handle grabbed mob + if(ismob(G.affecting)) + var/mob/GM = G.affecting + for(var/mob/V in viewers(usr)) + V.show_message("[usr] starts putting [GM.name] into the disposal.", 3) + if(do_after(usr, 20, target = GM)) + GM.forceMove(src) + for(var/mob/C in viewers(src)) + C.show_message("[GM.name] has been placed in the [src] by [user].", 3) + qdel(G) + add_attack_logs(usr, GM, "Disposal'ed", !!GM.ckey ? null : ATKLOG_ALL) + return + + if(!I) + return + + if(!user.drop_item()) + return + if(I) + I.forceMove(src) + + to_chat(user, "You place \the [I] into the [src].") + for(var/mob/M in viewers(src)) + if(M == user) + continue + M.show_message("[user.name] places \the [I] into the [src].", 3) + + update() + + + + +/obj/machinery/disposal/screwdriver_act(mob/user, obj/item/I) + if(mode>0) // It's on + return + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(contents.len > 0) + to_chat(user, "Eject the items first!") + return + if(mode==0) // It's off but still not unscrewed + mode=-1 // Set it to doubleoff l0l + else if(mode==-1) + mode=0 + to_chat(user, "You [mode ? "unfasten": "fasten"] the screws around the power connection.") + +/obj/machinery/disposal/welder_act(mob/user, obj/item/I) + . = TRUE + if(mode != required_mode_to_deconstruct) + return + if(contents.len > 0) + to_chat(user, "Eject the items first!") + return + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE + if(I.use_tool(src, user, 20, volume = I.tool_volume)) + WELDER_FLOOR_SLICE_SUCCESS_MESSAGE + var/obj/structure/disposalconstruct/C = new (src.loc) + C.ptype = deconstructs_to + C.update() + C.anchored = 1 + C.density = 1 + qdel(src) + +// mouse drop another mob or self +// +/obj/machinery/disposal/MouseDrop_T(mob/target, mob/user) + if(!istype(target) || target.buckled || target.has_buckled_mobs() || get_dist(user, src) > 1 || get_dist(user, target) > 1 || user.stat || istype(user, /mob/living/silicon/ai)) + return + if(isanimal(user) && target != user) return //animals cannot put mobs other than themselves into disposal + src.add_fingerprint(user) + var/target_loc = target.loc + var/msg + for(var/mob/V in viewers(usr)) + if(target == user && !user.stat && !user.IsWeakened() && !user.stunned && !user.paralysis) + V.show_message("[usr] starts climbing into the disposal.", 3) + if(target != user && !user.restrained() && !user.stat && !user.IsWeakened() && !user.stunned && !user.paralysis) + if(target.anchored) return + V.show_message("[usr] starts stuffing [target.name] into the disposal.", 3) + if(!do_after(usr, 20, target = target)) + return + if(target_loc != target.loc) + return + if(target == user && !user.stat && !user.IsWeakened() && !user.stunned && !user.paralysis) // if drop self, then climbed in + // must be awake, not stunned or whatever + msg = "[user.name] climbs into the [src]." + to_chat(user, "You climb into the [src].") + else if(target != user && !user.restrained() && !user.stat && !user.IsWeakened() && !user.stunned && !user.paralysis) + msg = "[user.name] stuffs [target.name] into the [src]!" + to_chat(user, "You stuff [target.name] into the [src]!") + + add_attack_logs(user, target, "Disposal'ed", !!target.ckey ? null : ATKLOG_ALL) + else + return + target.forceMove(src) + + for(var/mob/C in viewers(src)) + if(C == user) + continue + C.show_message(msg, 3) + + update() + return + +// attempt to move while inside +/obj/machinery/disposal/relaymove(mob/user as mob) + if(user.stat || src.flushing) + return + src.go_out(user) + return + +// leave the disposal +/obj/machinery/disposal/proc/go_out(mob/user) + if(user) + user.forceMove(loc) + update() + +// ai as human but can't flush +/obj/machinery/disposal/attack_ai(mob/user as mob) + src.add_hiddenprint(user) + ui_interact(user) + +/obj/machinery/disposal/attack_ghost(mob/user as mob) + ui_interact(user) + +// human interact with machine +/obj/machinery/disposal/attack_hand(mob/user as mob) + if(..(user)) + return 1 + + if(stat & BROKEN) + return + + if(user && user.loc == src) + to_chat(usr, "You cannot reach the controls from inside.") + return + + // Clumsy folks can only flush it. + if(user.IsAdvancedToolUser()) + ui_interact(user) + else + flush = !flush + update() + return + +/obj/machinery/disposal/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "disposal_bin.tmpl", "Waste Disposal Unit", 395, 250) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/disposal/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + var/pressure = Clamp(100* air_contents.return_pressure() / (SEND_PRESSURE), 0, 100) + var/pressure_round = round(pressure,1) + + data["isAI"] = isAI(user) + data["flushing"] = flush + data["mode"] = mode + if(mode <= 0) + data["pumpstatus"] = "N/A" + else if(mode == 1) + data["pumpstatus"] = "Pressurizing" + else if(mode == 2) + data["pumpstatus"] = "Ready" + else + data["pumpstatus"] = "Idle" + data["pressure"] = pressure_round + + return data + +/obj/machinery/disposal/Topic(href, href_list) + if(usr.loc == src) + to_chat(usr, "You cannot reach the controls from inside.") + return + + if(mode==-1 && !href_list["eject"]) // only allow ejecting if mode is -1 + to_chat(usr, "The disposal units power is disabled.") + return + + if(..()) + return + + if(stat & BROKEN) + return + + src.add_fingerprint(usr) + + if(usr.stat || usr.restrained() || src.flushing) + return + + if(istype(src.loc, /turf)) + if(href_list["pump"]) + if(text2num(href_list["pump"])) + mode = 1 + else + mode = 0 + update() + + if(!isAI(usr)) + if(href_list["handle"]) + flush = text2num(href_list["handle"]) + update() + + if(href_list["eject"]) + eject() + return + +// eject the contents of the disposal unit +/obj/machinery/disposal/proc/eject() + for(var/atom/movable/AM in src) + AM.loc = src.loc + AM.pipe_eject(0) + update() + +// update the icon & overlays to reflect mode & status +/obj/machinery/disposal/proc/update() + overlays.Cut() + if(stat & BROKEN) + icon_state = "disposal-broken" + mode = 0 + flush = 0 + return + + // flush handle + if(flush) + overlays += image('icons/obj/pipes/disposal.dmi', "dispover-handle") + + // only handle is shown if no power + if(stat & NOPOWER || mode == -1) + return + + // check for items in disposal - occupied light + if(contents.len > 0) + overlays += image('icons/obj/pipes/disposal.dmi', "dispover-full") + + // charging and ready light + if(mode == 1) + overlays += image('icons/obj/pipes/disposal.dmi', "dispover-charge") + else if(mode == 2) + overlays += image('icons/obj/pipes/disposal.dmi', "dispover-ready") + +// timed process +// charge the gas reservoir and perform flush if ready +/obj/machinery/disposal/process() + use_power = NO_POWER_USE + if(stat & BROKEN) // nothing can happen if broken + return + + flush_count++ + if( flush_count >= flush_every_ticks ) + if( contents.len ) + if(mode == 2) + spawn(0) + feedback_inc("disposal_auto_flush",1) + flush() + flush_count = 0 + + src.updateDialog() + + if(flush && air_contents.return_pressure() >= SEND_PRESSURE ) // flush can happen even without power + flush() + + if(stat & NOPOWER) // won't charge if no power + return + + use_power = IDLE_POWER_USE + + if(mode != 1) // if off or ready, no need to charge + return + + // otherwise charge + use_power = ACTIVE_POWER_USE + + var/atom/L = loc // recharging from loc turf + + var/datum/gas_mixture/env = L.return_air() + var/pressure_delta = (SEND_PRESSURE*1.01) - air_contents.return_pressure() + + if(env.temperature > 0) + var/transfer_moles = 0.1 * pressure_delta*air_contents.volume/(env.temperature * R_IDEAL_GAS_EQUATION) + + //Actually transfer the gas + var/datum/gas_mixture/removed = env.remove(transfer_moles) + air_contents.merge(removed) + air_update_turf() + + + // if full enough, switch to ready mode + if(air_contents.return_pressure() >= SEND_PRESSURE) + mode = 2 + update() + return + +// perform a flush +/obj/machinery/disposal/proc/flush() + + flushing = 1 + flick("[icon_state]-flush", src) + + var/wrapcheck = 0 + var/obj/structure/disposalholder/H = new() // virtual holder object which actually + // travels through the pipes. + //Hacky test to get drones to mail themselves through disposals. + for(var/mob/living/silicon/robot/drone/D in src) + wrapcheck = 1 + + for(var/mob/living/silicon/robot/syndicate/saboteur/R in src) + wrapcheck = 1 + + for(var/obj/item/smallDelivery/O in src) + wrapcheck = 1 + + if(wrapcheck == 1) + H.tomail = 1 + + sleep(10) + if(last_sound < world.time + 1) + playsound(src, 'sound/machines/disposalflush.ogg', 50, 0, 0) + last_sound = world.time + sleep(5) // wait for animation to finish + + + H.init(src) // copy the contents of disposer to holder + air_contents = new() // The holder just took our gas; replace it + H.start(src) // start the holder processing movement + flushing = 0 + // now reset disposal state + flush = 0 + if(mode == 2) // if was ready, + mode = 1 // switch to charging + update() + return + + +// called when area power changes +/obj/machinery/disposal/power_change() + ..() // do default setting/reset of stat NOPOWER bit + update() // update icon + return + + +// called when holder is expelled from a disposal +// should usually only occur if the pipe network is modified +/obj/machinery/disposal/proc/expel(var/obj/structure/disposalholder/H) + + var/turf/target + playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0) + if(H) // Somehow, someone managed to flush a window which broke mid-transit and caused the disposal to go in an infinite loop trying to expel null, hopefully this fixes it + for(var/atom/movable/AM in H) + target = get_offset_target_turf(src.loc, rand(5)-rand(5), rand(5)-rand(5)) + + AM.loc = src.loc + AM.pipe_eject(0) + if(!istype(AM, /mob/living/silicon/robot/drone) && !istype(AM, /mob/living/silicon/robot/syndicate/saboteur)) //Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z + spawn(1) + if(AM) + AM.throw_at(target, 5, 1) + + H.vent_gas(loc) + qdel(H) + +/obj/machinery/disposal/CanPass(atom/movable/mover, turf/target, height=0) + if(istype(mover,/obj/item) && mover.throwing) + var/obj/item/I = mover + if(istype(I, /obj/item/projectile)) + return + if(prob(75)) + I.loc = src + for(var/mob/M in viewers(src)) + M.show_message("\the [I] lands in \the [src].", 3) + update() + else + for(var/mob/M in viewers(src)) + M.show_message("\the [I] bounces off of \the [src]'s rim!.", 3) + return 0 + else + return ..(mover, target, height) + + +/obj/machinery/disposal/singularity_pull(S, current_size) + if(current_size >= STAGE_FIVE) + qdel(src) + +/obj/machinery/disposal/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) + +// virtual disposal object +// travels through pipes in lieu of actual items +// contents will be items flushed by the disposal +// this allows the gas flushed to be tracked + +/obj/structure/disposalholder + invisibility = 101 + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/datum/gas_mixture/gas = null // gas used to flush, will appear at exit point + var/active = 0 // true if the holder is moving, otherwise inactive + dir = 0 + var/count = 1000 //*** can travel 1000 steps before going inactive (in case of loops) + var/has_fat_guy = 0 // true if contains a fat person + var/destinationTag = 0 // changes if contains a delivery container + var/tomail = 0 //changes if contains wrapped package + var/hasmob = 0 //If it contains a mob + +/obj/structure/disposalholder/Destroy() + QDEL_NULL(gas) + active = 0 + return ..() + + // initialize a holder from the contents of a disposal unit +/obj/structure/disposalholder/proc/init(var/obj/machinery/disposal/D) + gas = D.air_contents// transfer gas resv. into holder object + + //Check for any living mobs trigger hasmob. + //hasmob effects whether the package goes to cargo or its tagged destination. + for(var/mob/living/M in D) + if(M && M.stat != 2 && !istype(M, /mob/living/silicon/robot/drone) && !istype(M, /mob/living/silicon/robot/syndicate/saboteur)) + hasmob = 1 + + //Checks 1 contents level deep. This means that players can be sent through disposals... + //...but it should require a second person to open the package. (i.e. person inside a wrapped locker) + for(var/obj/O in D) + if(O.contents) + for(var/mob/living/M in O.contents) + if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone) && !istype(M, /mob/living/silicon/robot/syndicate/saboteur)) + hasmob = 1 + + // now everything inside the disposal gets put into the holder + // note AM since can contain mobs or objs + for(var/atom/movable/AM in D) + AM.loc = src + SEND_SIGNAL(AM, COMSIG_MOVABLE_DISPOSING, src, D) + if(istype(AM, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = AM + if(FAT in H.mutations) // is a human and fat? + has_fat_guy = 1 // set flag on holder + if(istype(AM, /obj/structure/bigDelivery) && !hasmob) + var/obj/structure/bigDelivery/T = AM + destinationTag = T.sortTag + if(istype(AM, /obj/item/smallDelivery) && !hasmob) + var/obj/item/smallDelivery/T = AM + destinationTag = T.sortTag + //Drones can mail themselves through maint. + if(istype(AM, /mob/living/silicon/robot/drone)) + var/mob/living/silicon/robot/drone/drone = AM + destinationTag = drone.mail_destination + if(istype(AM, /mob/living/silicon/robot/syndicate/saboteur)) + var/mob/living/silicon/robot/syndicate/saboteur/S = AM + destinationTag = S.mail_destination + if(istype(AM, /obj/item/shippingPackage) && !hasmob) + var/obj/item/shippingPackage/sp = AM + if(sp.sealed) //only sealed packages get delivered to their intended destination + destinationTag = sp.sortTag + + + // start the movement process + // argument is the disposal unit the holder started in +/obj/structure/disposalholder/proc/start(var/obj/machinery/disposal/D) + if(!D.trunk) + D.expel(src) // no trunk connected, so expel immediately + return + + loc = D.trunk + active = 1 + dir = DOWN + spawn(1) + move() // spawn off the movement process + + return + + // movement process, persists while holder is moving through pipes +/obj/structure/disposalholder/proc/move() + var/obj/structure/disposalpipe/last + while(active) + /* if(hasmob && prob(3)) + for(var/mob/living/H in src) + if(!istype(H,/mob/living/silicon/robot/drone)) //Drones use the mailing code to move through the disposal system, + H.take_overall_damage(20, 0, "Blunt Trauma") */ //horribly maim any living creature jumping down disposals. c'est la vie + + if(has_fat_guy && prob(2)) // chance of becoming stuck per segment if contains a fat guy + active = 0 + // find the fat guys + for(var/mob/living/carbon/human/H in src) + + break + sleep(1) // was 1 + var/obj/structure/disposalpipe/curr = loc + last = curr + curr = curr.transfer(src) + if(!curr) + last.expel(src, loc, dir) + + // + if(!(count--)) + active = 0 + return + + + + // find the turf which should contain the next pipe +/obj/structure/disposalholder/proc/nextloc() + return get_step(loc,dir) + + // find a matching pipe on a turf +/obj/structure/disposalholder/proc/findpipe(var/turf/T) + if(!T) + return null + + var/fdir = turn(dir, 180) // flip the movement direction + for(var/obj/structure/disposalpipe/P in T) + if(fdir & P.dpdir) // find pipe direction mask that matches flipped dir + return P + // if no matching pipe, return null + return null + + // merge two holder objects + // used when a a holder meets a stuck holder +/obj/structure/disposalholder/proc/merge(var/obj/structure/disposalholder/other) + for(var/atom/movable/AM in other) + AM.loc = src // move everything in other holder to this one + if(ismob(AM)) + var/mob/M = AM + if(M.client) // if a client mob, update eye to follow this holder + M.client.eye = src + + if(other.has_fat_guy) + has_fat_guy = 1 + qdel(other) + + + // called when player tries to move while in a pipe +/obj/structure/disposalholder/relaymove(mob/user as mob) + if(!istype(user,/mob/living)) + return + + var/mob/living/U = user + + if(U.stat || U.last_special <= world.time) + return + + U.last_special = world.time+100 + + if(src.loc) + for(var/mob/M in hearers(src.loc.loc)) + to_chat(M, "CLONG, clong!") + + playsound(src.loc, 'sound/effects/clang.ogg', 50, 0, 0) + + // called to vent all gas in holder to a location +/obj/structure/disposalholder/proc/vent_gas(var/atom/location) + if(location) + location.assume_air(gas) // vent all gas to turf + air_update_turf() + return + +// Disposal pipes + +/obj/structure/disposalpipe + icon = 'icons/obj/pipes/disposal.dmi' + name = "disposal pipe" + desc = "An underfloor disposal pipe." + anchored = 1 + density = 0 + + on_blueprints = TRUE + level = 1 // underfloor only + var/dpdir = 0 // bitmask of pipe directions + dir = 0 // dir will contain dominant direction for junction pipes + var/health = 10 // health points 0-10 + max_integrity = 200 + armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) + damage_deflection = 10 + layer = DISPOSAL_PIPE_LAYER // slightly lower than wires and other pipes + var/base_icon_state // initial icon state on map + + // new pipe, set the icon_state as on map +/obj/structure/disposalpipe/New() + ..() + base_icon_state = icon_state + + +// pipe is deleted +// ensure if holder is present, it is expelled +/obj/structure/disposalpipe/Destroy() + for(var/obj/structure/disposalholder/H in contents) + H.active = 0 + var/turf/T = loc + if(T.density) + // deleting pipe is inside a dense turf (wall) + // this is unlikely, but just dump out everything into the turf in case + + for(var/atom/movable/AM in H) + AM.loc = T + AM.pipe_eject(0) + qdel(H) + ..() + return + + // otherwise, do normal expel from turf + expel(H, T, 0) + return ..() + +/obj/structure/disposalpipe/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +// returns the direction of the next pipe object, given the entrance dir +// by default, returns the bitmask of remaining directions +/obj/structure/disposalpipe/proc/nextdir(var/fromdir) + return dpdir & (~turn(fromdir, 180)) + +// transfer the holder through this pipe segment +// overriden for special behaviour +// +/obj/structure/disposalpipe/proc/transfer(var/obj/structure/disposalholder/H) + var/nextdir = nextdir(H.dir) + H.dir = nextdir + var/turf/T = H.nextloc() + var/obj/structure/disposalpipe/P = H.findpipe(T) + + if(P) + // find other holder in next loc, if inactive merge it with current + var/obj/structure/disposalholder/H2 = locate() in P + if(H2 && !H2.active) + H.merge(H2) + + H.loc = P + else // if wasn't a pipe, then set loc to turf + H.loc = T + return null + + return P + + +// update the icon_state to reflect hidden status +/obj/structure/disposalpipe/proc/update() + var/turf/T = src.loc + hide(T.intact && !istype(T,/turf/space)) // space never hides pipes + update_icon() + +// hide called by levelupdate if turf intact status changes +// change visibility status and force update of icon +/obj/structure/disposalpipe/hide(var/intact) + invisibility = intact ? 101: 0 // hide if floor is intact + update_icon() + +// update actual icon_state depending on visibility +// if invisible, append "f" to icon_state to show faded version +// this will be revealed if a T-scanner is used +// if visible, use regular icon_state +/obj/structure/disposalpipe/update_icon() + if(invisibility) + icon_state = "[base_icon_state]f" + else + icon_state = base_icon_state + + +// expel the held objects into a turf +// called when there is a break in the pipe +// + +/obj/structure/disposalpipe/proc/expel(var/obj/structure/disposalholder/H, var/turf/T, var/direction) + + if(!T) + return + + var/turf/target + + if(T.density) // dense ouput turf, so stop holder + H.active = 0 + H.loc = src + return + if(T.intact && istype(T,/turf/simulated/floor)) //intact floor, pop the tile + var/turf/simulated/floor/F = T + new F.builtin_tile.type(H) + F.remove_tile(null,TRUE,FALSE) + + if(direction) // direction is specified + if(istype(T, /turf/space)) // if ended in space, then range is unlimited + target = get_edge_target_turf(T, direction) + else // otherwise limit to 10 tiles + target = get_ranged_target_turf(T, direction, 10) + + playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0) + if(H) + for(var/atom/movable/AM in H) + AM.loc = T + AM.pipe_eject(direction) + spawn(1) + if(AM) + AM.throw_at(target, 100, 1) + H.vent_gas(T) + qdel(H) + + else // no specified direction, so throw in random direction + + playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0) + if(H) + for(var/atom/movable/AM in H) + target = get_offset_target_turf(T, rand(5)-rand(5), rand(5)-rand(5)) + + AM.loc = T + AM.pipe_eject(0) + spawn(1) + if(AM) + AM.throw_at(target, 5, 1) + + H.vent_gas(T) // all gas vent to turf + qdel(H) + +// call to break the pipe +// will expel any holder inside at the time +// then delete the pipe +// remains : set to leave broken pipe pieces in place +/obj/structure/disposalpipe/proc/broken(remains = 0) + if(remains) + for(var/D in cardinal) + if(D & dpdir) + var/obj/structure/disposalpipe/broken/P = new(src.loc) + P.setDir(D) + + invisibility = 101 // make invisible (since we won't delete the pipe immediately) + var/obj/structure/disposalholder/H = locate() in src + if(H) + // holder was present + H.active = 0 + var/turf/T = src.loc + if(T.density) + // broken pipe is inside a dense turf (wall) + // this is unlikely, but just dump out everything into the turf in case + + for(var/atom/movable/AM in H) + AM.loc = T + AM.pipe_eject(0) + qdel(H) + return + + // otherwise, do normal expel from turf + if(H) + expel(H, T, 0) + + spawn(2) // delete pipe after 2 ticks to ensure expel proc finished + qdel(src) + +// pipe affected by explosion +/obj/structure/disposalpipe/ex_act(severity) + switch(severity) + if(1) + broken(0) + if(2) + health -= rand(5, 15) + healthcheck() + if(3) + health -= rand(0, 15) + healthcheck() + +// test health for brokenness +/obj/structure/disposalpipe/proc/healthcheck() + if(health < -2) + broken(0) + else if(health<1) + broken(1) + return + +//attack by item +//weldingtool: unfasten and convert to obj/disposalconstruct + +/obj/structure/disposalpipe/attackby(var/obj/item/I, var/mob/user, params) + var/turf/T = get_turf(src) + if(T.intact) + return // prevent interaction with T-scanner revealed pipes + + add_fingerprint(user) + +/obj/structure/disposalpipe/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_SLICING_MESSAGE + if(!I.use_tool(src, user, 30, volume = I.tool_volume)) + return + WELDER_SLICING_SUCCESS_MESSAGE + var/obj/structure/disposalconstruct/C = new (get_turf(src)) + switch(base_icon_state) + if("pipe-s") + C.ptype = PIPE_DISPOSALS_STRAIGHT + if("pipe-c") + C.ptype = PIPE_DISPOSALS_BENT + if("pipe-j1") + C.ptype = PIPE_DISPOSALS_JUNCTION_RIGHT + if("pipe-j2") + C.ptype = PIPE_DISPOSALS_JUNCTION_LEFT + if("pipe-y") + C.ptype = PIPE_DISPOSALS_Y_JUNCTION + if("pipe-t") + C.ptype = PIPE_DISPOSALS_TRUNK + if("pipe-j1s") + C.ptype = PIPE_DISPOSALS_SORT_RIGHT + if("pipe-j2s") + C.ptype = PIPE_DISPOSALS_SORT_LEFT + src.transfer_fingerprints_to(C) + C.dir = dir + C.density = FALSE + C.anchored = TRUE + C.update() + + qdel(src) + +// *** TEST verb +//client/verb/dispstop() +// for(var/obj/structure/disposalholder/H in world) +// H.active = 0 + +// a straight or bent segment +/obj/structure/disposalpipe/segment + icon_state = "pipe-s" + +/obj/structure/disposalpipe/segment/New() + ..() + if(icon_state == "pipe-s") + dpdir = dir | turn(dir, 180) + else + dpdir = dir | turn(dir, -90) + + update() + + + +//a three-way junction with dir being the dominant direction +/obj/structure/disposalpipe/junction + icon_state = "pipe-j1" + +/obj/structure/disposalpipe/junction/New() + ..() + if(icon_state == "pipe-j1") + dpdir = dir | turn(dir, -90) | turn(dir,180) + else if(icon_state == "pipe-j2") + dpdir = dir | turn(dir, 90) | turn(dir,180) + else // pipe-y + dpdir = dir | turn(dir,90) | turn(dir, -90) + update() + + + // next direction to move + // if coming in from secondary dirs, then next is primary dir + // if coming in from primary dir, then next is equal chance of other dirs + +/obj/structure/disposalpipe/junction/nextdir(var/fromdir) + var/flipdir = turn(fromdir, 180) + if(flipdir != dir) // came from secondary dir + return dir // so exit through primary + else // came from primary + // so need to choose either secondary exit + var/mask = ..(fromdir) + + // find a bit which is set + var/setbit = 0 + if(mask & NORTH) + setbit = NORTH + else if(mask & SOUTH) + setbit = SOUTH + else if(mask & EAST) + setbit = EAST + else + setbit = WEST + + if(prob(50)) // 50% chance to choose the found bit or the other one + return setbit + else + return mask & (~setbit) + +//a three-way junction that sorts objects +/obj/structure/disposalpipe/sortjunction + + icon_state = "pipe-j1s" + var/sortType = 0 //Look at the list called TAGGERLOCATIONS in /code/_globalvars/lists/flavor_misc.dm + var/posdir = 0 + var/negdir = 0 + var/sortdir = 0 + +/obj/structure/disposalpipe/sortjunction/proc/updatedesc() + desc = "An underfloor disposal pipe with a package sorting mechanism." + if(sortType>0) + var/tag = uppertext(GLOB.TAGGERLOCATIONS[sortType]) + desc += "\nIt's tagged with [tag]" + +/obj/structure/disposalpipe/sortjunction/proc/updatedir() + posdir = dir + negdir = turn(posdir, 180) + + if(icon_state == "pipe-j1s") + sortdir = turn(posdir, -90) + else + icon_state = "pipe-j2s" + sortdir = turn(posdir, 90) + + dpdir = sortdir | posdir | negdir + +/obj/structure/disposalpipe/sortjunction/New() + ..() + updatedir() + updatedesc() + update() + return + +/obj/structure/disposalpipe/sortjunction/attackby(var/obj/item/I, var/mob/user, params) + if(..()) + return + + if(istype(I, /obj/item/destTagger)) + var/obj/item/destTagger/O = I + + if(O.currTag > 0)// Tag set + sortType = O.currTag + name = GLOB.TAGGERLOCATIONS[O.currTag] + playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1) + var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) + to_chat(user, "Changed filter to [tag]") + updatedesc() + + + // next direction to move + // if coming in from negdir, then next is primary dir or sortdir + // if coming in from posdir, then flip around and go back to posdir + // if coming in from sortdir, go to posdir + +/obj/structure/disposalpipe/sortjunction/nextdir(var/fromdir, var/sortTag) + //var/flipdir = turn(fromdir, 180) + if(fromdir != sortdir) // probably came from the negdir + + if(src.sortType == sortTag) //if destination matches filtered type... + return sortdir // exit through sortdirection + else + return posdir + else // came from sortdir + // so go with the flow to positive direction + return posdir + +/obj/structure/disposalpipe/sortjunction/transfer(var/obj/structure/disposalholder/H) + var/nextdir = nextdir(H.dir, H.destinationTag) + H.dir = nextdir + var/turf/T = H.nextloc() + var/obj/structure/disposalpipe/P = H.findpipe(T) + + if(P) + // find other holder in next loc, if inactive merge it with current + var/obj/structure/disposalholder/H2 = locate() in P + if(H2 && !H2.active) + H.merge(H2) + H.loc = P + else // if wasn't a pipe, then set loc to turf + H.loc = T + return null + + return P + + +//a three-way junction that sorts objects destined for the mail office mail table (tomail = 1) +/obj/structure/disposalpipe/wrapsortjunction + desc = "An underfloor disposal pipe which sorts wrapped and unwrapped objects." + icon_state = "pipe-j1s" + var/posdir = 0 + var/negdir = 0 + var/sortdir = 0 + +/obj/structure/disposalpipe/wrapsortjunction/New() + ..() + posdir = dir + if(icon_state == "pipe-j1s") + sortdir = turn(posdir, -90) + negdir = turn(posdir, 180) + else + icon_state = "pipe-j2s" + sortdir = turn(posdir, 90) + negdir = turn(posdir, 180) + dpdir = sortdir | posdir | negdir + + update() + return + + + // next direction to move + // if coming in from negdir, then next is primary dir or sortdir + // if coming in from posdir, then flip around and go back to posdir + // if coming in from sortdir, go to posdir + +/obj/structure/disposalpipe/wrapsortjunction/nextdir(var/fromdir, var/istomail) + //var/flipdir = turn(fromdir, 180) + if(fromdir != sortdir) // probably came from the negdir + if(istomail) //if destination matches filtered type... + return sortdir // exit through sortdirection + else + return posdir + else // came from sortdir + return posdir // so go with the flow to positive direction + +/obj/structure/disposalpipe/wrapsortjunction/transfer(var/obj/structure/disposalholder/H) + var/nextdir = nextdir(H.dir, H.tomail) + H.dir = nextdir + var/turf/T = H.nextloc() + var/obj/structure/disposalpipe/P = H.findpipe(T) + + if(P) + // find other holder in next loc, if inactive merge it with current + var/obj/structure/disposalholder/H2 = locate() in P + if(H2 && !H2.active) + H.merge(H2) + + H.loc = P + else // if wasn't a pipe, then set loc to turf + H.loc = T + return null + + return P + +//a trunk joining to a disposal bin or outlet on the same turf +/obj/structure/disposalpipe/trunk + icon_state = "pipe-t" + var/obj/linked // the linked obj/machinery/disposal or obj/disposaloutlet + +/obj/structure/disposalpipe/trunk/New() + ..() + dpdir = dir + spawn(1) + getlinked() + + update() + return + +/obj/structure/disposalpipe/trunk/Destroy() + if(istype(linked, /obj/structure/disposaloutlet)) + var/obj/structure/disposaloutlet/O = linked + O.expel(animation = 0) + else if(istype(linked, /obj/machinery/disposal)) + var/obj/machinery/disposal/D = linked + if(D.trunk == src) + D.go_out() + D.trunk = null + remove_trunk_links() + return ..() + +/obj/structure/disposalpipe/trunk/proc/getlinked() + var/obj/machinery/disposal/D = locate() in src.loc + if(D) + nicely_link_to_other_stuff(D) + return + var/obj/structure/disposaloutlet/O = locate() in src.loc + if(O) + nicely_link_to_other_stuff(O) + +/obj/structure/disposalpipe/trunk/proc/remove_trunk_links() //disposals is well-coded + if(!linked) + return + else if(istype(linked, /obj/machinery/disposal)) //jk lol + var/obj/machinery/disposal/D = linked + D.trunk = null + else if(istype(linked, /obj/structure/disposaloutlet)) //God fucking damn it + var/obj/structure/disposaloutlet/D = linked + D.linkedtrunk = null + linked = null + +/obj/structure/disposalpipe/trunk/proc/nicely_link_to_other_stuff(obj/O) + remove_trunk_links() //Breaks the connections between this trunk and the linked machinery so we don't get sent to nullspace or some shit like that + if(istype(O, /obj/machinery/disposal)) + var/obj/machinery/disposal/D = O + linked = D + D.trunk = src + else if(istype(O, /obj/structure/disposaloutlet)) + var/obj/structure/disposaloutlet/D = O + linked = D + D.linkedtrunk = src + + // Override attackby so we disallow trunkremoval when somethings ontop +/obj/structure/disposalpipe/trunk/attackby(var/obj/item/I, var/mob/user, params) + + //Disposal bins or chutes + //Disposal constructors + var/obj/structure/disposalconstruct/C = locate() in src.loc + if(C && C.anchored) + return + + var/turf/T = src.loc + if(T.intact) + return // prevent interaction with T-scanner revealed pipes + src.add_fingerprint(user) + + // would transfer to next pipe segment, but we are in a trunk + // if not entering from disposal bin, + // transfer to linked object (outlet or bin) + +/obj/structure/disposalpipe/trunk/transfer(obj/structure/disposalholder/H) + if(!H) + return + if(H.dir == DOWN) // we just entered from a disposer + return ..() // so do base transfer proc + // otherwise, go to the linked object + if(!linked) + expel(H, loc, FALSE) // expel at turf + else if(istype(linked, /obj/structure/disposaloutlet)) + var/obj/structure/disposaloutlet/DO = linked + for(var/atom/movable/AM in H) + AM.forceMove(DO) + qdel(H) + H.vent_gas(loc) + DO.expel() + else if(istype(linked, /obj/machinery/disposal)) + var/obj/machinery/disposal/D = linked + H.forceMove(D) + D.expel(H) // expel at disposal + else //just in case + expel(H, loc, FALSE) + // nextdir + +/obj/structure/disposalpipe/trunk/nextdir(var/fromdir) + if(fromdir == DOWN) + return dir + else + return 0 + +// a broken pipe +/obj/structure/disposalpipe/broken + icon_state = "pipe-b" + dpdir = 0 // broken pipes have dpdir=0 so they're not found as 'real' pipes + // i.e. will be treated as an empty turf + desc = "A broken piece of disposal pipe." + +/obj/structure/disposalpipe/broken/New() + ..() + update() + return + +/obj/structure/disposalpipe/broken/welder_act(mob/user, obj/item/I) + if(I.use_tool(src, user, 0, volume = I.tool_volume)) + to_chat(user, "You remove [src]!") + I.play_tool_sound(src, I.tool_volume) + qdel(src) + return TRUE + +// the disposal outlet machine + +/obj/structure/disposaloutlet + name = "disposal outlet" + desc = "An outlet for the pneumatic disposal system." + icon = 'icons/obj/pipes/disposal.dmi' + icon_state = "outlet" + density = 1 + anchored = 1 + var/active = 0 + var/turf/target // this will be where the output objects are 'thrown' to. + var/obj/structure/disposalpipe/trunk/linkedtrunk + var/mode = 0 + +/obj/structure/disposaloutlet/New() + ..() + spawn(1) + target = get_ranged_target_turf(src, dir, 10) + var/obj/structure/disposalpipe/trunk/T = locate() in loc + if(T) + T.nicely_link_to_other_stuff(src) + +/obj/structure/disposaloutlet/Destroy() + if(linkedtrunk) + linkedtrunk.remove_trunk_links() + expel(FALSE) + return ..() + + +// expel the contents of the outlet +/obj/structure/disposaloutlet/proc/expel(animation = TRUE) + if(animation) + flick("outlet-open", src) + playsound(src, 'sound/machines/warning-buzzer.ogg', 50, 0, 0) + sleep(20) //wait until correct animation frame + playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0) + for(var/atom/movable/AM in contents) + AM.forceMove(loc) + AM.pipe_eject(dir) + if(istype(AM,/mob/living/silicon/robot/drone) || istype(AM, /mob/living/silicon/robot/syndicate/saboteur)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z + return + spawn(5) + if(QDELETED(AM)) + return + AM.throw_at(target, 3, 1) + + +/obj/structure/disposaloutlet/attackby(var/obj/item/I, var/mob/user, params) + if(!I || !user) + return + src.add_fingerprint(user) + if(istype(I, /obj/item/screwdriver)) + if(mode==0) + mode=1 + playsound(src.loc, I.usesound, 50, 1) + to_chat(user, "You remove the screws around the power connection.") + return + else if(mode==1) + mode=0 + playsound(src.loc, I.usesound, 50, 1) + to_chat(user, "You attach the screws around the power connection.") + return + +/obj/structure/disposaloutlet/welder_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE + if(I.use_tool(src, user, 20, volume = I.tool_volume)) + WELDER_FLOOR_SLICE_SUCCESS_MESSAGE + var/obj/structure/disposalconstruct/C = new (src.loc) + C.ptype = PIPE_DISPOSALS_OUTLET + C.update() + C.anchored = TRUE + C.density = TRUE + transfer_fingerprints_to(C) + qdel(src) + +//When the disposalsoutlet is forcefully moved. Due to meteorshot or the recall item spell for instance +/obj/structure/disposaloutlet/Moved(atom/OldLoc, Dir) + . = ..() + var/turf/T = OldLoc + if(T.intact) + var/turf/simulated/floor/F = T + F.remove_tile(null,TRUE,TRUE) + T.visible_message("The floortile is ripped from the floor!", "You hear a loud bang!") + if(linkedtrunk) + linkedtrunk.remove_trunk_links() + var/obj/structure/disposalconstruct/C = new (loc) + transfer_fingerprints_to(C) + C.ptype = PIPE_DISPOSALS_OUTLET + C.update() + C.anchored = 0 + C.density = 1 + qdel(src) + +// called when movable is expelled from a disposal pipe or outlet +// by default does nothing, override for special behaviour + +/atom/movable/proc/pipe_eject(var/direction) + return + +// check if mob has client, if so restore client view on eject +/mob/pipe_eject(var/direction) + reset_perspective(null) + +/obj/effect/decal/cleanable/blood/gibs/pipe_eject(var/direction) + var/list/dirs + if(direction) + dirs = list( direction, turn(direction, -45), turn(direction, 45)) + else + dirs = alldirs.Copy() + + src.streak(dirs) + +/obj/effect/decal/cleanable/blood/gibs/robot/gib/pipe_eject(var/direction) + var/list/dirs + if(direction) + dirs = list( direction, turn(direction, -45), turn(direction, 45)) + else + dirs = alldirs.Copy() + + src.streak(dirs) diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm index 95e076d2670..7d2be6953c6 100755 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -1,444 +1,444 @@ -/obj/structure/bigDelivery - name = "large parcel" - desc = "A big wrapped package." - icon = 'icons/obj/storage.dmi' - icon_state = "deliverycloset" - density = 1 - mouse_drag_pointer = MOUSE_ACTIVE_POINTER - var/obj/wrapped = null - var/init_welded = 0 - var/giftwrapped = 0 - var/sortTag = 0 - -/obj/structure/bigDelivery/Destroy() - var/turf/T = get_turf(src) - for(var/atom/movable/AM in contents) - AM.forceMove(T) - return ..() - -/obj/structure/bigDelivery/ex_act(severity) - for(var/atom/movable/AM in contents) - AM.ex_act() - CHECK_TICK - ..() - -/obj/structure/bigDelivery/attack_hand(mob/user as mob) - playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) - if(wrapped) - wrapped.loc = get_turf(src) - if(istype(wrapped, /obj/structure/closet)) - var/obj/structure/closet/O = wrapped - O.welded = init_welded - var/turf/T = get_turf(src) - for(var/atom/movable/AM in src) - AM.loc = T - - qdel(src) - -/obj/structure/bigDelivery/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/destTagger)) - var/obj/item/destTagger/O = W - - if(sortTag != O.currTag) - var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) - to_chat(user, "*[tag]*") - sortTag = O.currTag - playsound(loc, 'sound/machines/twobeep.ogg', 100, 1) - - else if(istype(W, /obj/item/shippingPackage)) - var/obj/item/shippingPackage/sp = W - if(sp.sealed) - return - else - sortTag = sp.sortTag - to_chat(user, "You rip the label off the shipping package and affix it to [src].") - qdel(sp) - playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1) - - else if(istype(W, /obj/item/pen)) - var/str = copytext(sanitize(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) - if(!str || !length(str)) - to_chat(user, "Invalid text.") - return - user.visible_message("[user] labels [src] as [str].") - name = "[name] ([str])" - - else if(istype(W, /obj/item/stack/wrapping_paper) && !giftwrapped) - var/obj/item/stack/wrapping_paper/WP = W - if(WP.use(3)) - user.visible_message("[user] wraps the package in festive paper!") - giftwrapped = 1 - if(istype(wrapped, /obj/structure/closet/crate)) - icon_state = "giftcrate" - else - icon_state = "giftcloset" - if(WP.amount <= 0 && !WP.loc) //if we used our last wrapping paper, drop a cardboard tube - new /obj/item/c_tube( get_turf(user) ) - else - to_chat(user, "You need more paper.") - else - return ..() - -/obj/item/smallDelivery - name = "small parcel" - desc = "A small wrapped package." - icon = 'icons/obj/storage.dmi' - icon_state = "deliverycrateSmall" - item_state = "deliverypackage" - var/obj/item/wrapped = null - var/giftwrapped = 0 - var/sortTag = 0 - -/obj/item/smallDelivery/ex_act(severity) - for(var/atom/movable/AM in contents) - AM.ex_act() - CHECK_TICK - ..() - -/obj/item/smallDelivery/attack_self(mob/user as mob) - if(wrapped && wrapped.loc) //sometimes items can disappear. For example, bombs. --rastaf0 - wrapped.loc = user.loc - if(ishuman(user)) - user.put_in_hands(wrapped) - else - wrapped.loc = get_turf(src) - playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) - qdel(src) - -/obj/item/smallDelivery/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/destTagger)) - var/obj/item/destTagger/O = W - - if(sortTag != O.currTag) - var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) - to_chat(user, "*[tag]*") - sortTag = O.currTag - playsound(loc, 'sound/machines/twobeep.ogg', 100, 1) - - else if(istype(W, /obj/item/shippingPackage)) - var/obj/item/shippingPackage/sp = W - if(sp.sealed) - return - else - sortTag = sp.sortTag - to_chat(user, "You rip the label off the shipping package and affix it to [src].") - qdel(sp) - playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1) - - else if(istype(W, /obj/item/pen)) - var/str = copytext(sanitize(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) - if(!str || !length(str)) - to_chat(user, "Invalid text.") - return - user.visible_message("[user] labels [src] as [str].") - name = "[name] ([str])" - - else if(istype(W, /obj/item/stack/wrapping_paper) && !giftwrapped) - var/obj/item/stack/wrapping_paper/WP = W - if(WP.use(1)) - icon_state = "giftcrate[wrapped.w_class]" - giftwrapped = 1 - user.visible_message("[user] wraps the package in festive paper!") - if(WP.amount <= 0 && !WP.loc) //if we used our last wrapping paper, drop a cardboard tube - new /obj/item/c_tube( get_turf(user) ) - else - to_chat(user, "You need more paper.") - else - return ..() - -/obj/item/stack/packageWrap - name = "package wrapper" - icon = 'icons/obj/items.dmi' - icon_state = "deliveryPaper" - singular_name = "package wrapper" - flags = NOBLUDGEON - amount = 25 - max_amount = 25 - resistance_flags = FLAMMABLE - -/obj/item/stack/packageWrap/afterattack(var/obj/target as obj, mob/user as mob, proximity) - if(!proximity) return - if(!istype(target)) //this really shouldn't be necessary (but it is). -Pete - return - if(istype(target, /obj/item/smallDelivery) || istype(target,/obj/structure/bigDelivery) \ - || istype(target, /obj/item/evidencebag) || istype(target, /obj/structure/closet/body_bag)) - return - if(target.anchored) - return - if(target in user) - return - - if(istype(target, /obj/item) && !(istype(target, /obj/item/storage) && !istype(target,/obj/item/storage/box) && !istype(target, /obj/item/shippingPackage))) - var/obj/item/O = target - if(use(1)) - var/obj/item/smallDelivery/P = new /obj/item/smallDelivery(get_turf(O.loc)) //Aaannd wrap it up! - if(!istype(O.loc, /turf)) - if(user.client) - user.client.screen -= O - P.wrapped = O - O.loc = P - var/i = round(O.w_class) - if(i in list(1,2,3,4,5)) - P.icon_state = "deliverycrate[i]" - P.w_class = i - P.add_fingerprint(usr) - O.add_fingerprint(usr) - add_fingerprint(usr) - else - return - else if(istype(target, /obj/structure/closet/crate)) - var/obj/structure/closet/crate/O = target - if(O.opened) - return - if(amount >= 3 && do_after_once(user, 15, target = target)) - if(O.opened || !use(3)) - return - var/obj/structure/bigDelivery/P = new /obj/structure/bigDelivery(get_turf(O.loc)) - P.icon_state = "deliverycrate" - P.wrapped = O - O.loc = P - else - to_chat(user, "You need more paper.") - return - else if(istype (target, /obj/structure/closet)) - var/obj/structure/closet/O = target - if(O.opened) - return - if(amount >= 3 && do_after_once(user, 15, target = target)) - if(O.opened || !use(3)) - return - var/obj/structure/bigDelivery/P = new /obj/structure/bigDelivery(get_turf(O.loc)) - P.wrapped = O - P.init_welded = O.welded - O.welded = 1 - O.loc = P - else - to_chat(user, "You need more paper.") - return - else - to_chat(user, "The object you are trying to wrap is unsuitable for the sorting machinery.") - return - - user.visible_message("[user] wraps [target].") - user.create_attack_log("Has used [name] on [target]") - - if(amount <= 0 && !src.loc) //if we used our last wrapping paper, drop a cardboard tube - new /obj/item/c_tube( get_turf(user) ) - return - -/obj/item/destTagger - name = "destination tagger" - desc = "Used to set the destination of properly wrapped packages." - icon = 'icons/obj/device.dmi' - icon_state = "dest_tagger" - var/currTag = 0 - //The whole system for the sorttype var is determined based on the order of this list, - //disposals must always be 1, since anything that's untagged will automatically go to disposals, or sorttype = 1 --Superxpdude - - w_class = WEIGHT_CLASS_TINY - item_state = "electronic" - flags = CONDUCT - slot_flags = SLOT_BELT - -/obj/item/destTagger/proc/openwindow(mob/user as mob) - var/dat = "

        TagMaster 2.2

        " - - dat += "" - for(var/i = 1, i <= GLOB.TAGGERLOCATIONS.len, i++) - dat += "" - - if(i%4==0) - dat += "" - - dat += "
        [GLOB.TAGGERLOCATIONS[i]]

        Current Selection: [currTag ? GLOB.TAGGERLOCATIONS[currTag] : "None"]
        " - - user << browse(dat, "window=destTagScreen;size=450x350") - onclose(user, "destTagScreen") - -/obj/item/destTagger/attack_self(mob/user as mob) - openwindow(user) - return - -/obj/item/destTagger/Topic(href, href_list) - src.add_fingerprint(usr) - if(href_list["nextTag"]) - var/n = text2num(href_list["nextTag"]) - src.currTag = n - openwindow(usr) - -/obj/machinery/disposal/deliveryChute - name = "Delivery chute" - desc = "A chute for big and small packages alike!" - density = 1 - icon_state = "intake" - required_mode_to_deconstruct = 1 - deconstructs_to = PIPE_DISPOSALS_CHUTE - var/can_deconstruct = FALSE - -/obj/machinery/disposal/deliveryChute/New() - ..() - spawn(5) - trunk = locate() in src.loc - if(trunk) - trunk.linked = src // link the pipe trunk to self - -/obj/machinery/disposal/deliveryChute/interact() - return - -/obj/machinery/disposal/deliveryChute/update() - return - -/obj/machinery/disposal/deliveryChute/Bumped(atom/movable/AM) //Go straight into the chute - if(istype(AM, /obj/item/projectile)) return - switch(dir) - if(NORTH) - if(AM.loc.y != src.loc.y+1) return - if(EAST) - if(AM.loc.x != src.loc.x+1) return - if(SOUTH) - if(AM.loc.y != src.loc.y-1) return - if(WEST) - if(AM.loc.x != src.loc.x-1) return - - if(istype(AM, /obj)) - var/obj/O = AM - O.loc = src - else if(istype(AM, /mob)) - var/mob/M = AM - M.loc = src - src.flush() - -/obj/machinery/disposal/deliveryChute/flush() - flushing = 1 - flick("intake-closing", src) - var/deliveryCheck = 0 - var/obj/structure/disposalholder/H = new() // virtual holder object which actually - // travels through the pipes. - for(var/obj/structure/bigDelivery/O in src) - deliveryCheck = 1 - if(O.sortTag == 0) - O.sortTag = 1 - for(var/obj/item/smallDelivery/O in src) - deliveryCheck = 1 - if(O.sortTag == 0) - O.sortTag = 1 - for(var/obj/item/shippingPackage/O in src) - deliveryCheck = 1 - if(!O.sealed || O.sortTag == 0) //unsealed or untagged shipping packages will default to disposals - O.sortTag = 1 - if(deliveryCheck == 0) - H.destinationTag = 1 - - sleep(10) - playsound(src, 'sound/machines/disposalflush.ogg', 50, 0, 0) - sleep(5) // wait for animation to finish - - H.init(src) // copy the contents of disposer to holder - air_contents = new() // The holder just took our gas; replace it - H.start(src) // start the holder processing movement - flushing = 0 - // now reset disposal state - flush = 0 - if(mode == 2) // if was ready, - mode = 1 // switch to charging - update() - return - -/obj/machinery/disposal/deliveryChute/screwdriver_act(mob/user, obj/item/I) - . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return - can_deconstruct = !can_deconstruct - to_chat(user, "You [can_deconstruct ? "unfasten": "fasten"] the screws around the power connection.") - -/obj/machinery/disposal/deliveryChute/welder_act(mob/user, obj/item/I) - . = TRUE - if(!can_deconstruct) - return - if(contents.len > 0) - to_chat(user, "Eject the items first!") - return - if(!I.tool_use_check(user, 0)) - return - WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE - if(I.use_tool(src, user, 20, volume = I.tool_volume)) - WELDER_FLOOR_SLICE_SUCCESS_MESSAGE - var/obj/structure/disposalconstruct/C = new (loc) - C.ptype = deconstructs_to - C.update() - C.anchored = TRUE - C.density = TRUE - qdel(src) - -/obj/item/shippingPackage - name = "Shipping package" - desc = "A pre-labeled package for shipping an item to coworkers." - icon = 'icons/obj/storage.dmi' - icon_state = "shippack" - var/obj/item/wrapped = null - var/sortTag = 0 - var/sealed = 0 - -/obj/item/shippingPackage/attackby(obj/item/O, mob/user, params) - if(sealed) - if(istype(O, /obj/item/pen)) - var/str = copytext(sanitize(input(user,"Intended recipient?","Address","")),1,MAX_NAME_LEN) - if(!str || !length(str)) - to_chat(user, "Invalid text.") - return - user.visible_message("[user] addresses [src] to [str].") - name = "Shipping package (RE: [str])" - return - if(wrapped) - to_chat(user, "[src] already contains \a [wrapped].") - return - if(istype(O, /obj/item) && !istype(O, /obj/item/storage) && !istype(O, /obj/item/shippingPackage)) - if(!user.canUnEquip(O)) - to_chat(user, "[O] is stuck to your hand, you cannot put it in [src]!") - return - if(O.w_class > 3) - to_chat(user, "[O] is too large to fit in [src].") - else - wrapped = O - user.unEquip(O) - O.forceMove(src) - O.add_fingerprint(usr) - add_fingerprint(usr) - to_chat(user, "You put [O] in [src].") - -/obj/item/shippingPackage/attack_self(mob/user) - if(sealed) - to_chat(user, "You tear open [src], dropping the contents onto the floor.") - playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1) - user.unEquip(src) - wrapped.forceMove(get_turf(user)) - wrapped = null - qdel(src) - else if(wrapped) - switch(alert("Select an action:",, "Remove Object", "Seal Package", "Cancel")) - if("Remove Object") - to_chat(user, "You shake out [src]'s contents onto the floor.") - wrapped.forceMove(get_turf(user)) - wrapped = null - if("Seal Package") - to_chat(user, "You seal [src], preparing it for delivery.") - icon_state = "shippack_sealed" - sealed = 1 - update_desc() - else - if(alert("Do you want to tear up the package?",, "Yes", "No") == "Yes") - to_chat(user, "You shred [src].") - playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1) - user.unEquip(src) - qdel(src) - -/obj/item/shippingPackage/proc/update_desc() - desc = "A pre-labeled package for shipping an item to coworkers." - if(sortTag) - desc += " The label says \"Deliver to [GLOB.TAGGERLOCATIONS[sortTag]]\"." - if(!sealed) - desc += " The package is not sealed." - -/obj/item/shippingPackage/Destroy() - QDEL_NULL(wrapped) - return ..() +/obj/structure/bigDelivery + name = "large parcel" + desc = "A big wrapped package." + icon = 'icons/obj/storage.dmi' + icon_state = "deliverycloset" + density = 1 + mouse_drag_pointer = MOUSE_ACTIVE_POINTER + var/obj/wrapped = null + var/init_welded = 0 + var/giftwrapped = 0 + var/sortTag = 0 + +/obj/structure/bigDelivery/Destroy() + var/turf/T = get_turf(src) + for(var/atom/movable/AM in contents) + AM.forceMove(T) + return ..() + +/obj/structure/bigDelivery/ex_act(severity) + for(var/atom/movable/AM in contents) + AM.ex_act() + CHECK_TICK + ..() + +/obj/structure/bigDelivery/attack_hand(mob/user as mob) + playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) + if(wrapped) + wrapped.loc = get_turf(src) + if(istype(wrapped, /obj/structure/closet)) + var/obj/structure/closet/O = wrapped + O.welded = init_welded + var/turf/T = get_turf(src) + for(var/atom/movable/AM in src) + AM.loc = T + + qdel(src) + +/obj/structure/bigDelivery/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/destTagger)) + var/obj/item/destTagger/O = W + + if(sortTag != O.currTag) + var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) + to_chat(user, "*[tag]*") + sortTag = O.currTag + playsound(loc, 'sound/machines/twobeep.ogg', 100, 1) + + else if(istype(W, /obj/item/shippingPackage)) + var/obj/item/shippingPackage/sp = W + if(sp.sealed) + return + else + sortTag = sp.sortTag + to_chat(user, "You rip the label off the shipping package and affix it to [src].") + qdel(sp) + playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1) + + else if(istype(W, /obj/item/pen)) + var/str = copytext(sanitize(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) + if(!str || !length(str)) + to_chat(user, "Invalid text.") + return + user.visible_message("[user] labels [src] as [str].") + name = "[name] ([str])" + + else if(istype(W, /obj/item/stack/wrapping_paper) && !giftwrapped) + var/obj/item/stack/wrapping_paper/WP = W + if(WP.use(3)) + user.visible_message("[user] wraps the package in festive paper!") + giftwrapped = 1 + if(istype(wrapped, /obj/structure/closet/crate)) + icon_state = "giftcrate" + else + icon_state = "giftcloset" + if(WP.amount <= 0 && !WP.loc) //if we used our last wrapping paper, drop a cardboard tube + new /obj/item/c_tube( get_turf(user) ) + else + to_chat(user, "You need more paper.") + else + return ..() + +/obj/item/smallDelivery + name = "small parcel" + desc = "A small wrapped package." + icon = 'icons/obj/storage.dmi' + icon_state = "deliverycrateSmall" + item_state = "deliverypackage" + var/obj/item/wrapped = null + var/giftwrapped = 0 + var/sortTag = 0 + +/obj/item/smallDelivery/ex_act(severity) + for(var/atom/movable/AM in contents) + AM.ex_act() + CHECK_TICK + ..() + +/obj/item/smallDelivery/attack_self(mob/user as mob) + if(wrapped && wrapped.loc) //sometimes items can disappear. For example, bombs. --rastaf0 + wrapped.loc = user.loc + if(ishuman(user)) + user.put_in_hands(wrapped) + else + wrapped.loc = get_turf(src) + playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) + qdel(src) + +/obj/item/smallDelivery/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/destTagger)) + var/obj/item/destTagger/O = W + + if(sortTag != O.currTag) + var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) + to_chat(user, "*[tag]*") + sortTag = O.currTag + playsound(loc, 'sound/machines/twobeep.ogg', 100, 1) + + else if(istype(W, /obj/item/shippingPackage)) + var/obj/item/shippingPackage/sp = W + if(sp.sealed) + return + else + sortTag = sp.sortTag + to_chat(user, "You rip the label off the shipping package and affix it to [src].") + qdel(sp) + playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1) + + else if(istype(W, /obj/item/pen)) + var/str = copytext(sanitize(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) + if(!str || !length(str)) + to_chat(user, "Invalid text.") + return + user.visible_message("[user] labels [src] as [str].") + name = "[name] ([str])" + + else if(istype(W, /obj/item/stack/wrapping_paper) && !giftwrapped) + var/obj/item/stack/wrapping_paper/WP = W + if(WP.use(1)) + icon_state = "giftcrate[wrapped.w_class]" + giftwrapped = 1 + user.visible_message("[user] wraps the package in festive paper!") + if(WP.amount <= 0 && !WP.loc) //if we used our last wrapping paper, drop a cardboard tube + new /obj/item/c_tube( get_turf(user) ) + else + to_chat(user, "You need more paper.") + else + return ..() + +/obj/item/stack/packageWrap + name = "package wrapper" + icon = 'icons/obj/items.dmi' + icon_state = "deliveryPaper" + singular_name = "package wrapper" + flags = NOBLUDGEON + amount = 25 + max_amount = 25 + resistance_flags = FLAMMABLE + +/obj/item/stack/packageWrap/afterattack(var/obj/target as obj, mob/user as mob, proximity) + if(!proximity) return + if(!istype(target)) //this really shouldn't be necessary (but it is). -Pete + return + if(istype(target, /obj/item/smallDelivery) || istype(target,/obj/structure/bigDelivery) \ + || istype(target, /obj/item/evidencebag) || istype(target, /obj/structure/closet/body_bag)) + return + if(target.anchored) + return + if(target in user) + return + + if(istype(target, /obj/item) && !(istype(target, /obj/item/storage) && !istype(target,/obj/item/storage/box) && !istype(target, /obj/item/shippingPackage))) + var/obj/item/O = target + if(use(1)) + var/obj/item/smallDelivery/P = new /obj/item/smallDelivery(get_turf(O.loc)) //Aaannd wrap it up! + if(!istype(O.loc, /turf)) + if(user.client) + user.client.screen -= O + P.wrapped = O + O.loc = P + var/i = round(O.w_class) + if(i in list(1,2,3,4,5)) + P.icon_state = "deliverycrate[i]" + P.w_class = i + P.add_fingerprint(usr) + O.add_fingerprint(usr) + add_fingerprint(usr) + else + return + else if(istype(target, /obj/structure/closet/crate)) + var/obj/structure/closet/crate/O = target + if(O.opened) + return + if(amount >= 3 && do_after_once(user, 15, target = target)) + if(O.opened || !use(3)) + return + var/obj/structure/bigDelivery/P = new /obj/structure/bigDelivery(get_turf(O.loc)) + P.icon_state = "deliverycrate" + P.wrapped = O + O.loc = P + else + to_chat(user, "You need more paper.") + return + else if(istype (target, /obj/structure/closet)) + var/obj/structure/closet/O = target + if(O.opened) + return + if(amount >= 3 && do_after_once(user, 15, target = target)) + if(O.opened || !use(3)) + return + var/obj/structure/bigDelivery/P = new /obj/structure/bigDelivery(get_turf(O.loc)) + P.wrapped = O + P.init_welded = O.welded + O.welded = 1 + O.loc = P + else + to_chat(user, "You need more paper.") + return + else + to_chat(user, "The object you are trying to wrap is unsuitable for the sorting machinery.") + return + + user.visible_message("[user] wraps [target].") + user.create_attack_log("Has used [name] on [target]") + + if(amount <= 0 && !src.loc) //if we used our last wrapping paper, drop a cardboard tube + new /obj/item/c_tube( get_turf(user) ) + return + +/obj/item/destTagger + name = "destination tagger" + desc = "Used to set the destination of properly wrapped packages." + icon = 'icons/obj/device.dmi' + icon_state = "dest_tagger" + var/currTag = 0 + //The whole system for the sorttype var is determined based on the order of this list, + //disposals must always be 1, since anything that's untagged will automatically go to disposals, or sorttype = 1 --Superxpdude + + w_class = WEIGHT_CLASS_TINY + item_state = "electronic" + flags = CONDUCT + slot_flags = SLOT_BELT + +/obj/item/destTagger/proc/openwindow(mob/user as mob) + var/dat = "

        TagMaster 2.2

        " + + dat += "" + for(var/i = 1, i <= GLOB.TAGGERLOCATIONS.len, i++) + dat += "" + + if(i%4==0) + dat += "" + + dat += "
        [GLOB.TAGGERLOCATIONS[i]]

        Current Selection: [currTag ? GLOB.TAGGERLOCATIONS[currTag] : "None"]
        " + + user << browse(dat, "window=destTagScreen;size=450x350") + onclose(user, "destTagScreen") + +/obj/item/destTagger/attack_self(mob/user as mob) + openwindow(user) + return + +/obj/item/destTagger/Topic(href, href_list) + src.add_fingerprint(usr) + if(href_list["nextTag"]) + var/n = text2num(href_list["nextTag"]) + src.currTag = n + openwindow(usr) + +/obj/machinery/disposal/deliveryChute + name = "Delivery chute" + desc = "A chute for big and small packages alike!" + density = 1 + icon_state = "intake" + required_mode_to_deconstruct = 1 + deconstructs_to = PIPE_DISPOSALS_CHUTE + var/can_deconstruct = FALSE + +/obj/machinery/disposal/deliveryChute/New() + ..() + spawn(5) + trunk = locate() in src.loc + if(trunk) + trunk.linked = src // link the pipe trunk to self + +/obj/machinery/disposal/deliveryChute/interact() + return + +/obj/machinery/disposal/deliveryChute/update() + return + +/obj/machinery/disposal/deliveryChute/Bumped(atom/movable/AM) //Go straight into the chute + if(istype(AM, /obj/item/projectile)) return + switch(dir) + if(NORTH) + if(AM.loc.y != src.loc.y+1) return + if(EAST) + if(AM.loc.x != src.loc.x+1) return + if(SOUTH) + if(AM.loc.y != src.loc.y-1) return + if(WEST) + if(AM.loc.x != src.loc.x-1) return + + if(istype(AM, /obj)) + var/obj/O = AM + O.loc = src + else if(istype(AM, /mob)) + var/mob/M = AM + M.loc = src + src.flush() + +/obj/machinery/disposal/deliveryChute/flush() + flushing = 1 + flick("intake-closing", src) + var/deliveryCheck = 0 + var/obj/structure/disposalholder/H = new() // virtual holder object which actually + // travels through the pipes. + for(var/obj/structure/bigDelivery/O in src) + deliveryCheck = 1 + if(O.sortTag == 0) + O.sortTag = 1 + for(var/obj/item/smallDelivery/O in src) + deliveryCheck = 1 + if(O.sortTag == 0) + O.sortTag = 1 + for(var/obj/item/shippingPackage/O in src) + deliveryCheck = 1 + if(!O.sealed || O.sortTag == 0) //unsealed or untagged shipping packages will default to disposals + O.sortTag = 1 + if(deliveryCheck == 0) + H.destinationTag = 1 + + sleep(10) + playsound(src, 'sound/machines/disposalflush.ogg', 50, 0, 0) + sleep(5) // wait for animation to finish + + H.init(src) // copy the contents of disposer to holder + air_contents = new() // The holder just took our gas; replace it + H.start(src) // start the holder processing movement + flushing = 0 + // now reset disposal state + flush = 0 + if(mode == 2) // if was ready, + mode = 1 // switch to charging + update() + return + +/obj/machinery/disposal/deliveryChute/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + can_deconstruct = !can_deconstruct + to_chat(user, "You [can_deconstruct ? "unfasten": "fasten"] the screws around the power connection.") + +/obj/machinery/disposal/deliveryChute/welder_act(mob/user, obj/item/I) + . = TRUE + if(!can_deconstruct) + return + if(contents.len > 0) + to_chat(user, "Eject the items first!") + return + if(!I.tool_use_check(user, 0)) + return + WELDER_ATTEMPT_FLOOR_SLICE_MESSAGE + if(I.use_tool(src, user, 20, volume = I.tool_volume)) + WELDER_FLOOR_SLICE_SUCCESS_MESSAGE + var/obj/structure/disposalconstruct/C = new (loc) + C.ptype = deconstructs_to + C.update() + C.anchored = TRUE + C.density = TRUE + qdel(src) + +/obj/item/shippingPackage + name = "Shipping package" + desc = "A pre-labeled package for shipping an item to coworkers." + icon = 'icons/obj/storage.dmi' + icon_state = "shippack" + var/obj/item/wrapped = null + var/sortTag = 0 + var/sealed = 0 + +/obj/item/shippingPackage/attackby(obj/item/O, mob/user, params) + if(sealed) + if(istype(O, /obj/item/pen)) + var/str = copytext(sanitize(input(user,"Intended recipient?","Address","")),1,MAX_NAME_LEN) + if(!str || !length(str)) + to_chat(user, "Invalid text.") + return + user.visible_message("[user] addresses [src] to [str].") + name = "Shipping package (RE: [str])" + return + if(wrapped) + to_chat(user, "[src] already contains \a [wrapped].") + return + if(istype(O, /obj/item) && !istype(O, /obj/item/storage) && !istype(O, /obj/item/shippingPackage)) + if(!user.canUnEquip(O)) + to_chat(user, "[O] is stuck to your hand, you cannot put it in [src]!") + return + if(O.w_class > 3) + to_chat(user, "[O] is too large to fit in [src].") + else + wrapped = O + user.unEquip(O) + O.forceMove(src) + O.add_fingerprint(usr) + add_fingerprint(usr) + to_chat(user, "You put [O] in [src].") + +/obj/item/shippingPackage/attack_self(mob/user) + if(sealed) + to_chat(user, "You tear open [src], dropping the contents onto the floor.") + playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1) + user.unEquip(src) + wrapped.forceMove(get_turf(user)) + wrapped = null + qdel(src) + else if(wrapped) + switch(alert("Select an action:",, "Remove Object", "Seal Package", "Cancel")) + if("Remove Object") + to_chat(user, "You shake out [src]'s contents onto the floor.") + wrapped.forceMove(get_turf(user)) + wrapped = null + if("Seal Package") + to_chat(user, "You seal [src], preparing it for delivery.") + icon_state = "shippack_sealed" + sealed = 1 + update_desc() + else + if(alert("Do you want to tear up the package?",, "Yes", "No") == "Yes") + to_chat(user, "You shred [src].") + playsound(loc, 'sound/items/poster_ripped.ogg', 50, 1) + user.unEquip(src) + qdel(src) + +/obj/item/shippingPackage/proc/update_desc() + desc = "A pre-labeled package for shipping an item to coworkers." + if(sortTag) + desc += " The label says \"Deliver to [GLOB.TAGGERLOCATIONS[sortTag]]\"." + if(!sealed) + desc += " The package is not sealed." + +/obj/item/shippingPackage/Destroy() + QDEL_NULL(wrapped) + return ..() diff --git a/code/modules/research/circuitprinter.dm b/code/modules/research/circuitprinter.dm index 0332b30a413..eda48794d86 100644 --- a/code/modules/research/circuitprinter.dm +++ b/code/modules/research/circuitprinter.dm @@ -1,105 +1,105 @@ -/*///////////////Circuit Imprinter (By Darem)//////////////////////// - Used to print new circuit boards (for computers and similar systems) and AI modules. Each circuit board pattern are stored in -a /datum/desgin on the linked R&D console. You can then print them out in a fasion similar to a regular lathe. However, instead of -using metal and glass, it uses glass and reagents (usually sulfuric acis). - -*/ -/obj/machinery/r_n_d/circuit_imprinter - name = "Circuit Imprinter" - desc = "Manufactures circuit boards for the construction of machines." - icon_state = "circuit_imprinter" - container_type = OPENCONTAINER - - var/efficiency_coeff - - var/list/categories = list( - "AI Modules", - "Computer Boards", - "Computer Parts", - "Engineering Machinery", - "Exosuit Modules", - "Hydroponics Machinery", - "Medical Machinery", - "Misc. Machinery", - "Research Machinery", - "Subspace Telecomms", - "Teleportation Machinery" - ) - - reagents = new() - -/obj/machinery/r_n_d/circuit_imprinter/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/circuit_imprinter(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/reagent_containers/glass/beaker(null) - component_parts += new /obj/item/reagent_containers/glass/beaker(null) - RefreshParts() - reagents.my_atom = src - -/obj/machinery/r_n_d/circuit_imprinter/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/circuit_imprinter(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) - component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) - RefreshParts() - reagents.my_atom = src - -/obj/machinery/r_n_d/circuit_imprinter/RefreshParts() - reagents.maximum_volume = 0 - for(var/obj/item/reagent_containers/glass/G in component_parts) - reagents.maximum_volume += G.volume - G.reagents.trans_to(src, G.reagents.total_volume) - - materials.max_amount = 0 - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - materials.max_amount += M.rating * 75000 - - var/T = 0 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T += M.rating - efficiency_coeff = 2 ** (T - 1) //Only 1 manipulator here, you're making runtimes Razharas - -/obj/machinery/r_n_d/circuit_imprinter/proc/check_mat(datum/design/being_built, var/M) - var/list/all_materials = being_built.reagents_list + being_built.materials - - var/A = materials.amount(M) - if(!A) - A = reagents.get_reagent_amount(M) - - return round(A / max(1, (all_materials[M]/efficiency_coeff))) - -/obj/machinery/r_n_d/circuit_imprinter/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - if(shocked) - if(shock(user,50)) - return TRUE - if(default_deconstruction_screwdriver(user, "circuit_imprinter_t", "circuit_imprinter", O)) - if(linked_console) - linked_console.linked_imprinter = null - linked_console = null - return - - if(exchange_parts(user, O)) - return - - if(panel_open) - if(istype(O, /obj/item/crowbar)) - for(var/obj/I in component_parts) - if(istype(I, /obj/item/reagent_containers/glass/beaker)) - reagents.trans_to(I, reagents.total_volume) - I.loc = src.loc - materials.retrieve_all() - default_deconstruction_crowbar(user, O) - return - else - to_chat(user, "You can't load the [src.name] while it's opened.") - return - if(O.is_open_container()) - return FALSE - else - return ..() \ No newline at end of file +/*///////////////Circuit Imprinter (By Darem)//////////////////////// + Used to print new circuit boards (for computers and similar systems) and AI modules. Each circuit board pattern are stored in +a /datum/desgin on the linked R&D console. You can then print them out in a fasion similar to a regular lathe. However, instead of +using metal and glass, it uses glass and reagents (usually sulfuric acis). + +*/ +/obj/machinery/r_n_d/circuit_imprinter + name = "Circuit Imprinter" + desc = "Manufactures circuit boards for the construction of machines." + icon_state = "circuit_imprinter" + container_type = OPENCONTAINER + + var/efficiency_coeff + + var/list/categories = list( + "AI Modules", + "Computer Boards", + "Computer Parts", + "Engineering Machinery", + "Exosuit Modules", + "Hydroponics Machinery", + "Medical Machinery", + "Misc. Machinery", + "Research Machinery", + "Subspace Telecomms", + "Teleportation Machinery" + ) + + reagents = new() + +/obj/machinery/r_n_d/circuit_imprinter/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/circuit_imprinter(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/reagent_containers/glass/beaker(null) + component_parts += new /obj/item/reagent_containers/glass/beaker(null) + RefreshParts() + reagents.my_atom = src + +/obj/machinery/r_n_d/circuit_imprinter/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/circuit_imprinter(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) + component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) + RefreshParts() + reagents.my_atom = src + +/obj/machinery/r_n_d/circuit_imprinter/RefreshParts() + reagents.maximum_volume = 0 + for(var/obj/item/reagent_containers/glass/G in component_parts) + reagents.maximum_volume += G.volume + G.reagents.trans_to(src, G.reagents.total_volume) + + materials.max_amount = 0 + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + materials.max_amount += M.rating * 75000 + + var/T = 0 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + T += M.rating + efficiency_coeff = 2 ** (T - 1) //Only 1 manipulator here, you're making runtimes Razharas + +/obj/machinery/r_n_d/circuit_imprinter/proc/check_mat(datum/design/being_built, var/M) + var/list/all_materials = being_built.reagents_list + being_built.materials + + var/A = materials.amount(M) + if(!A) + A = reagents.get_reagent_amount(M) + + return round(A / max(1, (all_materials[M]/efficiency_coeff))) + +/obj/machinery/r_n_d/circuit_imprinter/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + if(shocked) + if(shock(user,50)) + return TRUE + if(default_deconstruction_screwdriver(user, "circuit_imprinter_t", "circuit_imprinter", O)) + if(linked_console) + linked_console.linked_imprinter = null + linked_console = null + return + + if(exchange_parts(user, O)) + return + + if(panel_open) + if(istype(O, /obj/item/crowbar)) + for(var/obj/I in component_parts) + if(istype(I, /obj/item/reagent_containers/glass/beaker)) + reagents.trans_to(I, reagents.total_volume) + I.loc = src.loc + materials.retrieve_all() + default_deconstruction_crowbar(user, O) + return + else + to_chat(user, "You can't load the [src.name] while it's opened.") + return + if(O.is_open_container()) + return FALSE + else + return ..() diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index 90a759e3c19..bfe959cb6c3 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -1,47 +1,47 @@ -/*************************************************************** -** Design Datums ** -** All the data for building stuff. ** -***************************************************************/ -/* -For the materials datum, it assumes you need reagents unless specified otherwise. To designate a material that isn't a reagent, -you use one of the material IDs below. These are NOT ids in the usual sense (they aren't defined in the object or part of a datum), -they are simply references used as part of a "has materials?" type proc. They all start with a $ to denote that they aren't reagents. -The currently supporting non-reagent materials: -- MAT_METAL (/obj/item/stack/metal). -- MAT_GLASS (/obj/item/stack/glass). -- MAT_PLASMA (/obj/item/stack/plasma). -- MAT_SILVER (/obj/item/stack/silver). -- MAT_GOLD (/obj/item/stack/gold). -- MAT_URANIUM (/obj/item/stack/uranium). -- MAT_DIAMOND (/obj/item/stack/diamond). -- MAT_BANANIUM (/obj/item/stack/bananium). -- MAT_TRANQUILLITE (/obj/item/stack/tranquillite). -(Insert new ones here) - -Don't add new keyword/IDs if they are made from an existing one (such as rods which are made from metal). Only add raw materials. - -Design Guidlines -- When adding new designs, check rdreadme.dm to see what kind of things have already been made and where new stuff is needed. -- A single sheet of anything is 2000 units of material. Materials besides metal/glass require help from other jobs (mining for -other types of metals and chemistry for reagents). -- Add the AUTOLATHE tag to - - -*/ - -/datum/design //Datum for object designs, used in construction - var/name = "Name" //Name of the created object. - var/desc = "Desc" //Description of the created object. - var/id = "id" //ID of the created object for easy refernece. Alphanumeric, lower-case, no symbols - var/list/req_tech = list() //IDs of that techs the object originated from and the minimum level requirements. - var/build_type = null //Flag as to what kind machine the design is built in. See defines. - var/list/materials = list() //List of materials. Format: "id" = amount. - var/construction_time //Amount of time required for building the object - var/build_path = null //The file path of the object that gets created - var/list/make_reagents = list() //Reagents produced. Format: "id" = amount. Currently only supported by the biogenerator. - var/locked = 0 //If true it will spawn inside a lockbox with currently sec access - var/access_requirement = list(ACCESS_ARMORY) //What special access requirements will the lockbox have? Defaults to armory. - var/category = null //Primarily used for Mech Fabricators, but can be used for anything - var/list/reagents_list = list() //List of reagents. Format: "id" = amount. - var/maxstack = 1 - var/lathe_time_factor = 1 //How many times faster than normal is this to build on the protolathe +/*************************************************************** +** Design Datums ** +** All the data for building stuff. ** +***************************************************************/ +/* +For the materials datum, it assumes you need reagents unless specified otherwise. To designate a material that isn't a reagent, +you use one of the material IDs below. These are NOT ids in the usual sense (they aren't defined in the object or part of a datum), +they are simply references used as part of a "has materials?" type proc. They all start with a $ to denote that they aren't reagents. +The currently supporting non-reagent materials: +- MAT_METAL (/obj/item/stack/metal). +- MAT_GLASS (/obj/item/stack/glass). +- MAT_PLASMA (/obj/item/stack/plasma). +- MAT_SILVER (/obj/item/stack/silver). +- MAT_GOLD (/obj/item/stack/gold). +- MAT_URANIUM (/obj/item/stack/uranium). +- MAT_DIAMOND (/obj/item/stack/diamond). +- MAT_BANANIUM (/obj/item/stack/bananium). +- MAT_TRANQUILLITE (/obj/item/stack/tranquillite). +(Insert new ones here) + +Don't add new keyword/IDs if they are made from an existing one (such as rods which are made from metal). Only add raw materials. + +Design Guidlines +- When adding new designs, check rdreadme.dm to see what kind of things have already been made and where new stuff is needed. +- A single sheet of anything is 2000 units of material. Materials besides metal/glass require help from other jobs (mining for +other types of metals and chemistry for reagents). +- Add the AUTOLATHE tag to + + +*/ + +/datum/design //Datum for object designs, used in construction + var/name = "Name" //Name of the created object. + var/desc = "Desc" //Description of the created object. + var/id = "id" //ID of the created object for easy refernece. Alphanumeric, lower-case, no symbols + var/list/req_tech = list() //IDs of that techs the object originated from and the minimum level requirements. + var/build_type = null //Flag as to what kind machine the design is built in. See defines. + var/list/materials = list() //List of materials. Format: "id" = amount. + var/construction_time //Amount of time required for building the object + var/build_path = null //The file path of the object that gets created + var/list/make_reagents = list() //Reagents produced. Format: "id" = amount. Currently only supported by the biogenerator. + var/locked = 0 //If true it will spawn inside a lockbox with currently sec access + var/access_requirement = list(ACCESS_ARMORY) //What special access requirements will the lockbox have? Defaults to armory. + var/category = null //Primarily used for Mech Fabricators, but can be used for anything + var/list/reagents_list = list() //List of reagents. Format: "id" = amount. + var/maxstack = 1 + var/lathe_time_factor = 1 //How many times faster than normal is this to build on the protolathe diff --git a/code/modules/research/designs/biogenerator_designs.dm b/code/modules/research/designs/biogenerator_designs.dm index a0bfdf01c61..b70771cb8d3 100644 --- a/code/modules/research/designs/biogenerator_designs.dm +++ b/code/modules/research/designs/biogenerator_designs.dm @@ -177,4 +177,4 @@ build_type = BIOGENERATOR materials = list(MAT_BIOMASS = 300) build_path = /obj/item/clothing/head/rice_hat - category = list("initial","Leather and Cloth") \ No newline at end of file + category = list("initial","Leather and Cloth") diff --git a/code/modules/research/designs/computer_part_designs.dm b/code/modules/research/designs/computer_part_designs.dm index 3c653107ce6..438a679f97a 100644 --- a/code/modules/research/designs/computer_part_designs.dm +++ b/code/modules/research/designs/computer_part_designs.dm @@ -248,4 +248,4 @@ build_type = IMPRINTER materials = list(MAT_GLASS = 3200, MAT_GOLD = 1000) build_path = /obj/item/computer_hardware/processor_unit/photonic/small - category = list("Computer Parts") \ No newline at end of file + category = list("Computer Parts") diff --git a/code/modules/research/designs/mining_designs.dm b/code/modules/research/designs/mining_designs.dm index 76439c64987..1fa33e6d27b 100644 --- a/code/modules/research/designs/mining_designs.dm +++ b/code/modules/research/designs/mining_designs.dm @@ -1,112 +1,112 @@ -///////////////////////////////////////// -/////////////////Mining////////////////// -///////////////////////////////////////// -/datum/design/drill - name = "Mining Drill" - desc = "Yours is the drill that will pierce through the rock walls." - id = "drill" - req_tech = list("materials" = 2, "powerstorage" = 2, "engineering" = 3) - build_type = PROTOLATHE - materials = list(MAT_METAL = 6000, MAT_GLASS = 1000) - build_path = /obj/item/pickaxe/drill - category = list("Mining") - -/datum/design/drill_diamond - name = "Diamond Mining Drill" - desc = "Yours is the drill that will pierce the heavens!" - id = "drill_diamond" - req_tech = list("materials" = 6, "powerstorage" = 5, "engineering" = 5) - build_type = PROTOLATHE - materials = list(MAT_METAL = 3000, MAT_GLASS = 1000, MAT_DIAMOND = 2000) //Yes, a whole diamond is needed. - build_path = /obj/item/pickaxe/drill/diamonddrill - category = list("Mining") - -/datum/design/plasmacutter - name = "Plasma Cutter" - desc = "You could use it to cut limbs off of xenos! Or, you know, mine stuff." - id = "plasmacutter" - req_tech = list("materials" = 3, "plasmatech" = 3, "magnets" = 2) - build_type = PROTOLATHE - materials = list(MAT_METAL = 1500, MAT_GLASS = 500, MAT_PLASMA = 400) - build_path = /obj/item/gun/energy/plasmacutter - category = list("Mining") - -/datum/design/plasmacutter_adv - name = "Advanced Plasma Cutter" - desc = "It's an advanced plasma cutter, oh my god." - id = "plasmacutter_adv" - req_tech = list("materials" = 4, "plasmatech" = 4, "engineering" = 2, "combat" = 3, "magnets" = 3) - build_type = PROTOLATHE - materials = list(MAT_METAL = 3000, MAT_GLASS = 1000, MAT_PLASMA = 2000, MAT_GOLD = 500) - build_path = /obj/item/gun/energy/plasmacutter/adv - category = list("Mining") - -/datum/design/jackhammer - name = "Sonic Jackhammer" - desc = "Essentially a handheld planet-cracker. Can drill through walls with ease as well." - id = "jackhammer" - req_tech = list("materials" = 7, "powerstorage" = 5, "engineering" = 6, "magnets" = 5) - build_type = PROTOLATHE - materials = list(MAT_METAL = 6000, MAT_GLASS = 2000, MAT_SILVER = 2000, MAT_DIAMOND = 6000) - build_path = /obj/item/pickaxe/drill/jackhammer - category = list("Mining") - -/datum/design/superresonator - name = "Upgraded Resonator" - desc = "An upgraded version of the resonator that allows more fields to be active at once." - id = "superresonator" - req_tech = list("materials" = 4, "powerstorage" = 3, "engineering" = 3, "magnets" = 3) - build_type = PROTOLATHE - materials = list(MAT_METAL = 4000, MAT_GLASS = 1500, MAT_SILVER = 1000, MAT_URANIUM = 1000) - build_path = /obj/item/resonator/upgraded - category = list("Mining") - -/datum/design/trigger_guard_mod - name = "Kinetic Accelerator Trigger Guard Mod" - desc = "A device which allows kinetic accelerators to be wielded by any organism." - id = "triggermod" - req_tech = list("materials" = 5, "powerstorage" = 4, "engineering" = 4, "magnets" = 4, "combat" = 3) - build_type = PROTOLATHE - materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) - build_path = /obj/item/borg/upgrade/modkit/trigger_guard - category = list("Mining") - -/datum/design/damage_mod - name = "Kinetic Accelerator Damage Mod" - desc = "A device which allows kinetic accelerators to deal more damage." - id = "damagemod" - req_tech = list("materials" = 5, "powerstorage" = 4, "engineering" = 4, "magnets" = 4, "combat" = 3) - build_type = PROTOLATHE | MECHFAB - materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) - build_path = /obj/item/borg/upgrade/modkit/damage - category = list("Mining", "Cyborg Upgrade Modules") - -/datum/design/cooldown_mod - name = "Kinetic Accelerator Cooldown Mod" - desc = "A device which decreases the cooldown of a Kinetic Accelerator." - id = "cooldownmod" - req_tech = list("materials" = 5, "powerstorage" = 4, "engineering" = 4, "magnets" = 4, "combat" = 3) - build_type = PROTOLATHE | MECHFAB - materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) - build_path = /obj/item/borg/upgrade/modkit/cooldown - category = list("Mining", "Cyborg Upgrade Modules") - -/datum/design/range_mod - name = "Kinetic Accelerator Range Mod" - desc = "A device which allows kinetic accelerators to fire at a further range." - id = "rangemod" - req_tech = list("materials" = 5, "powerstorage" = 4, "engineering" = 4, "magnets" = 4, "combat" = 3) - build_type = PROTOLATHE | MECHFAB - materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) - build_path = /obj/item/borg/upgrade/modkit/range - category = list("Mining", "Cyborg Upgrade Modules") - -/datum/design/hyperaccelerator - name = "Kinetic Accelerator Mining AoE Mod" - desc = "A modification kit for Kinetic Accelerators which causes it to fire AoE blasts that destroy rock." - id = "hypermod" - req_tech = list("materials" = 7, "powerstorage" = 5, "engineering" = 5, "magnets" = 5, "combat" = 4) - build_type = PROTOLATHE | MECHFAB - materials = list(MAT_METAL = 8000, MAT_GLASS = 1500, MAT_SILVER = 2000, MAT_GOLD = 2000, MAT_DIAMOND = 2000) - build_path = /obj/item/borg/upgrade/modkit/aoe/turfs - category = list("Mining", "Cyborg Upgrade Modules") \ No newline at end of file +///////////////////////////////////////// +/////////////////Mining////////////////// +///////////////////////////////////////// +/datum/design/drill + name = "Mining Drill" + desc = "Yours is the drill that will pierce through the rock walls." + id = "drill" + req_tech = list("materials" = 2, "powerstorage" = 2, "engineering" = 3) + build_type = PROTOLATHE + materials = list(MAT_METAL = 6000, MAT_GLASS = 1000) + build_path = /obj/item/pickaxe/drill + category = list("Mining") + +/datum/design/drill_diamond + name = "Diamond Mining Drill" + desc = "Yours is the drill that will pierce the heavens!" + id = "drill_diamond" + req_tech = list("materials" = 6, "powerstorage" = 5, "engineering" = 5) + build_type = PROTOLATHE + materials = list(MAT_METAL = 3000, MAT_GLASS = 1000, MAT_DIAMOND = 2000) //Yes, a whole diamond is needed. + build_path = /obj/item/pickaxe/drill/diamonddrill + category = list("Mining") + +/datum/design/plasmacutter + name = "Plasma Cutter" + desc = "You could use it to cut limbs off of xenos! Or, you know, mine stuff." + id = "plasmacutter" + req_tech = list("materials" = 3, "plasmatech" = 3, "magnets" = 2) + build_type = PROTOLATHE + materials = list(MAT_METAL = 1500, MAT_GLASS = 500, MAT_PLASMA = 400) + build_path = /obj/item/gun/energy/plasmacutter + category = list("Mining") + +/datum/design/plasmacutter_adv + name = "Advanced Plasma Cutter" + desc = "It's an advanced plasma cutter, oh my god." + id = "plasmacutter_adv" + req_tech = list("materials" = 4, "plasmatech" = 4, "engineering" = 2, "combat" = 3, "magnets" = 3) + build_type = PROTOLATHE + materials = list(MAT_METAL = 3000, MAT_GLASS = 1000, MAT_PLASMA = 2000, MAT_GOLD = 500) + build_path = /obj/item/gun/energy/plasmacutter/adv + category = list("Mining") + +/datum/design/jackhammer + name = "Sonic Jackhammer" + desc = "Essentially a handheld planet-cracker. Can drill through walls with ease as well." + id = "jackhammer" + req_tech = list("materials" = 7, "powerstorage" = 5, "engineering" = 6, "magnets" = 5) + build_type = PROTOLATHE + materials = list(MAT_METAL = 6000, MAT_GLASS = 2000, MAT_SILVER = 2000, MAT_DIAMOND = 6000) + build_path = /obj/item/pickaxe/drill/jackhammer + category = list("Mining") + +/datum/design/superresonator + name = "Upgraded Resonator" + desc = "An upgraded version of the resonator that allows more fields to be active at once." + id = "superresonator" + req_tech = list("materials" = 4, "powerstorage" = 3, "engineering" = 3, "magnets" = 3) + build_type = PROTOLATHE + materials = list(MAT_METAL = 4000, MAT_GLASS = 1500, MAT_SILVER = 1000, MAT_URANIUM = 1000) + build_path = /obj/item/resonator/upgraded + category = list("Mining") + +/datum/design/trigger_guard_mod + name = "Kinetic Accelerator Trigger Guard Mod" + desc = "A device which allows kinetic accelerators to be wielded by any organism." + id = "triggermod" + req_tech = list("materials" = 5, "powerstorage" = 4, "engineering" = 4, "magnets" = 4, "combat" = 3) + build_type = PROTOLATHE + materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) + build_path = /obj/item/borg/upgrade/modkit/trigger_guard + category = list("Mining") + +/datum/design/damage_mod + name = "Kinetic Accelerator Damage Mod" + desc = "A device which allows kinetic accelerators to deal more damage." + id = "damagemod" + req_tech = list("materials" = 5, "powerstorage" = 4, "engineering" = 4, "magnets" = 4, "combat" = 3) + build_type = PROTOLATHE | MECHFAB + materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) + build_path = /obj/item/borg/upgrade/modkit/damage + category = list("Mining", "Cyborg Upgrade Modules") + +/datum/design/cooldown_mod + name = "Kinetic Accelerator Cooldown Mod" + desc = "A device which decreases the cooldown of a Kinetic Accelerator." + id = "cooldownmod" + req_tech = list("materials" = 5, "powerstorage" = 4, "engineering" = 4, "magnets" = 4, "combat" = 3) + build_type = PROTOLATHE | MECHFAB + materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) + build_path = /obj/item/borg/upgrade/modkit/cooldown + category = list("Mining", "Cyborg Upgrade Modules") + +/datum/design/range_mod + name = "Kinetic Accelerator Range Mod" + desc = "A device which allows kinetic accelerators to fire at a further range." + id = "rangemod" + req_tech = list("materials" = 5, "powerstorage" = 4, "engineering" = 4, "magnets" = 4, "combat" = 3) + build_type = PROTOLATHE | MECHFAB + materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) + build_path = /obj/item/borg/upgrade/modkit/range + category = list("Mining", "Cyborg Upgrade Modules") + +/datum/design/hyperaccelerator + name = "Kinetic Accelerator Mining AoE Mod" + desc = "A modification kit for Kinetic Accelerators which causes it to fire AoE blasts that destroy rock." + id = "hypermod" + req_tech = list("materials" = 7, "powerstorage" = 5, "engineering" = 5, "magnets" = 5, "combat" = 4) + build_type = PROTOLATHE | MECHFAB + materials = list(MAT_METAL = 8000, MAT_GLASS = 1500, MAT_SILVER = 2000, MAT_GOLD = 2000, MAT_DIAMOND = 2000) + build_path = /obj/item/borg/upgrade/modkit/aoe/turfs + category = list("Mining", "Cyborg Upgrade Modules") diff --git a/code/modules/research/designs/smelting_designs.dm b/code/modules/research/designs/smelting_designs.dm index a043d90fb19..0fc368164b5 100644 --- a/code/modules/research/designs/smelting_designs.dm +++ b/code/modules/research/designs/smelting_designs.dm @@ -50,4 +50,4 @@ materials = list(MAT_METAL = 4000, MAT_PLASMA = 4000) build_path = /obj/item/stack/sheet/mineral/abductor category = list("Stock Parts") - lathe_time_factor = 5 \ No newline at end of file + lathe_time_factor = 5 diff --git a/code/modules/research/designs/stock_parts_designs.dm b/code/modules/research/designs/stock_parts_designs.dm index e32b79cc91b..e27aaf0ee70 100644 --- a/code/modules/research/designs/stock_parts_designs.dm +++ b/code/modules/research/designs/stock_parts_designs.dm @@ -248,4 +248,4 @@ build_type = PROTOLATHE materials = list(MAT_METAL = 15000, MAT_GLASS = 5000, MAT_SILVER = 2500) //hardcore build_path = /obj/item/storage/part_replacer/bluespace - category = list("Stock Parts") \ No newline at end of file + category = list("Stock Parts") diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm index a360e0ee231..58a8ab21284 100644 --- a/code/modules/research/destructive_analyzer.dm +++ b/code/modules/research/destructive_analyzer.dm @@ -1,88 +1,88 @@ -/* -Destructive Analyzer - -It is used to destroy hand-held objects and advance technological research. Controls are in the linked R&D console. - -Note: Must be placed within 3 tiles of the R&D Console -*/ -/obj/machinery/r_n_d/destructive_analyzer - name = "Destructive Analyzer" - desc = "Learn science by destroying things!" - icon_state = "d_analyzer" - var/decon_mod = 0 - -/obj/machinery/r_n_d/destructive_analyzer/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/destructive_analyzer(null) - component_parts += new /obj/item/stock_parts/scanning_module(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/micro_laser(null) - RefreshParts() - -/obj/machinery/r_n_d/destructive_analyzer/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/destructive_analyzer(null) - component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) - RefreshParts() - -/obj/machinery/r_n_d/destructive_analyzer/RefreshParts() - var/T = 0 - for(var/obj/item/stock_parts/S in component_parts) - T += S.rating - decon_mod = T - - -/obj/machinery/r_n_d/destructive_analyzer/proc/ConvertReqString2List(var/list/source_list) - var/list/temp_list = params2list(source_list) - for(var/O in temp_list) - temp_list[O] = text2num(temp_list[O]) - return temp_list - - -/obj/machinery/r_n_d/destructive_analyzer/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - if(shocked) - if(shock(user,50)) - return TRUE - if(default_deconstruction_screwdriver(user, "d_analyzer_t", "d_analyzer", O)) - if(linked_console) - linked_console.linked_destroy = null - linked_console = null - return - - if(exchange_parts(user, O)) - return - - if(default_deconstruction_crowbar(user, O)) - return - - if(disabled) - return - if(!linked_console) - to_chat(user, "The [src.name] must be linked to an R&D console first!") - return - if(busy) - to_chat(user, "The [src.name] is busy right now.") - return - if(istype(O, /obj/item) && !loaded_item) - if(!O.origin_tech) - to_chat(user, "This doesn't seem to have a tech origin!") - return - var/list/temp_tech = ConvertReqString2List(O.origin_tech) - if(temp_tech.len == 0) - to_chat(user, "You cannot deconstruct this item!") - return - if(!user.drop_item()) - to_chat(user, "\The [O] is stuck to your hand, you cannot put it in the [src.name]!") - return - busy = 1 - loaded_item = O - O.loc = src - to_chat(user, "You add the [O.name] to the [src.name]!") - flick("d_analyzer_la", src) - spawn(10) - icon_state = "d_analyzer_l" - busy = 0 \ No newline at end of file +/* +Destructive Analyzer + +It is used to destroy hand-held objects and advance technological research. Controls are in the linked R&D console. + +Note: Must be placed within 3 tiles of the R&D Console +*/ +/obj/machinery/r_n_d/destructive_analyzer + name = "Destructive Analyzer" + desc = "Learn science by destroying things!" + icon_state = "d_analyzer" + var/decon_mod = 0 + +/obj/machinery/r_n_d/destructive_analyzer/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/destructive_analyzer(null) + component_parts += new /obj/item/stock_parts/scanning_module(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/micro_laser(null) + RefreshParts() + +/obj/machinery/r_n_d/destructive_analyzer/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/destructive_analyzer(null) + component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stock_parts/micro_laser/ultra(null) + RefreshParts() + +/obj/machinery/r_n_d/destructive_analyzer/RefreshParts() + var/T = 0 + for(var/obj/item/stock_parts/S in component_parts) + T += S.rating + decon_mod = T + + +/obj/machinery/r_n_d/destructive_analyzer/proc/ConvertReqString2List(var/list/source_list) + var/list/temp_list = params2list(source_list) + for(var/O in temp_list) + temp_list[O] = text2num(temp_list[O]) + return temp_list + + +/obj/machinery/r_n_d/destructive_analyzer/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + if(shocked) + if(shock(user,50)) + return TRUE + if(default_deconstruction_screwdriver(user, "d_analyzer_t", "d_analyzer", O)) + if(linked_console) + linked_console.linked_destroy = null + linked_console = null + return + + if(exchange_parts(user, O)) + return + + if(default_deconstruction_crowbar(user, O)) + return + + if(disabled) + return + if(!linked_console) + to_chat(user, "The [src.name] must be linked to an R&D console first!") + return + if(busy) + to_chat(user, "The [src.name] is busy right now.") + return + if(istype(O, /obj/item) && !loaded_item) + if(!O.origin_tech) + to_chat(user, "This doesn't seem to have a tech origin!") + return + var/list/temp_tech = ConvertReqString2List(O.origin_tech) + if(temp_tech.len == 0) + to_chat(user, "You cannot deconstruct this item!") + return + if(!user.drop_item()) + to_chat(user, "\The [O] is stuck to your hand, you cannot put it in the [src.name]!") + return + busy = 1 + loaded_item = O + O.loc = src + to_chat(user, "You add the [O.name] to the [src.name]!") + flick("d_analyzer_la", src) + spawn(10) + icon_state = "d_analyzer_l" + busy = 0 diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm index fa5015987e5..2a0e201ac54 100644 --- a/code/modules/research/message_server.dm +++ b/code/modules/research/message_server.dm @@ -1,380 +1,380 @@ -var/global/list/obj/machinery/message_server/message_servers = list() - -/datum/data_pda_msg - var/recipient = "Unspecified" //name of the person - var/sender = "Unspecified" //name of the sender - var/message = "Blank" //transferred message - -/datum/data_pda_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "") - - if(param_rec) - recipient = param_rec - if(param_sender) - sender = param_sender - if(param_message) - message = param_message - -/datum/data_rc_msg - var/rec_dpt = "Unspecified" //name of the person - var/send_dpt = "Unspecified" //name of the sender - var/message = "Blank" //transferred message - var/stamp = "Unstamped" - var/id_auth = "Unauthenticated" - var/priority = "Normal" - -/datum/data_rc_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "",var/param_stamp = "",var/param_id_auth = "",var/param_priority) - if(param_rec) - rec_dpt = param_rec - if(param_sender) - send_dpt = param_sender - if(param_message) - message = param_message - if(param_stamp) - stamp = param_stamp - if(param_id_auth) - id_auth = param_id_auth - if(param_priority) - switch(param_priority) - if(1) - priority = "Normal" - if(2) - priority = "High" - if(3) - priority = "Extreme" - else - priority = "Undetermined" - -/obj/machinery/message_server - icon = 'icons/obj/machines/research.dmi' - icon_state = "server" - name = "Messaging Server" - density = 1 - anchored = 1.0 - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - - var/list/datum/data_pda_msg/pda_msgs = list() - var/list/datum/data_rc_msg/rc_msgs = list() - var/active = 1 - var/decryptkey = "password" - -/obj/machinery/message_server/New() - message_servers += src - decryptkey = GenerateKey() - send_pda_message("System Administrator", "system", "This is an automated message. The messaging system is functioning correctly.") - ..() - return - -/obj/machinery/message_server/Destroy() - message_servers -= src - return ..() - -/obj/machinery/message_server/process() - //if(decryptkey == "password") - // decryptkey = generateKey() - if(active && (stat & (BROKEN|NOPOWER))) - active = 0 - return - if(prob(3)) - playsound(loc, "computer_ambience", 50, 1) - update_icon() - return - -/obj/machinery/message_server/proc/send_pda_message(var/recipient = "",var/sender = "",var/message = "") - pda_msgs += new/datum/data_pda_msg(recipient,sender,message) - -/obj/machinery/message_server/proc/send_rc_message(var/recipient = "",var/sender = "",var/message = "",var/stamp = "", var/id_auth = "", var/priority = 1) - rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth) - var/authmsg = "[message]
        " - if(id_auth) - authmsg += "[id_auth]
        " - if(stamp) - authmsg += "[stamp]
        " - for(var/obj/machinery/requests_console/Console in allConsoles) - if(ckey(Console.department) == ckey(recipient)) - if(Console.inoperable()) - Console.message_log += "Message lost due to console failure.
        Please contact [station_name()] system adminsitrator or AI for technical assistance.
        " - continue - if(Console.newmessagepriority < priority) - Console.newmessagepriority = priority - Console.icon_state = "req_comp[priority]" - switch(priority) - if(2) - if(!Console.silent) - playsound(Console.loc, 'sound/machines/twobeep.ogg', 50, 1) - Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5) - Console.message_log += "High Priority message from [sender]
        [authmsg]" - else - if(!Console.silent) - playsound(Console.loc, 'sound/machines/twobeep.ogg', 50, 1) - Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4) - Console.message_log += "Message from [sender]
        [authmsg]" - Console.set_light(2) - -/obj/machinery/message_server/attack_hand(user as mob) -// to_chat(user, "There seem to be some parts missing from this server. They should arrive on the station in a few days, give or take a few CentComm delays.") - to_chat(user, "You toggle PDA message passing from [active ? "On" : "Off"] to [active ? "Off" : "On"]") - active = !active - update_icon() - - return - -/obj/machinery/message_server/update_icon() - if((stat & (BROKEN|NOPOWER))) - icon_state = "server-nopower" - else if(!active) - icon_state = "server-off" - else - icon_state = "server-on" - - return - - -/datum/feedback_variable - var/variable - var/value - var/details - -/datum/feedback_variable/New(var/param_variable,var/param_value = 0) - variable = param_variable - value = param_value - -/datum/feedback_variable/vv_edit_var(var_name, var_value) - return FALSE // come on guys don't break the stats - -/datum/feedback_variable/proc/inc(var/num = 1) - if(isnum(value)) - value += num - else - value = text2num(value) - if(isnum(value)) - value += num - else - value = num - -/datum/feedback_variable/proc/dec(var/num = 1) - if(isnum(value)) - value -= num - else - value = text2num(value) - if(isnum(value)) - value -= num - else - value = -num - -/datum/feedback_variable/proc/set_value(var/num) - if(isnum(num)) - value = num - -/datum/feedback_variable/proc/get_value() - return value - -/datum/feedback_variable/proc/get_variable() - return variable - -/datum/feedback_variable/proc/set_details(var/text) - if(istext(text)) - details = text - -/datum/feedback_variable/proc/add_details(var/text) - if(istext(text)) - if(!details) - details = text - else - details += " [text]" - -/datum/feedback_variable/proc/get_details() - return details - -/datum/feedback_variable/proc/get_parsed() - return list(variable,value,details) - -var/obj/machinery/blackbox_recorder/blackbox - -//TODO: kill whoever designed this cancer -/obj/machinery/blackbox_recorder - icon = 'icons/obj/stationobjs.dmi' - icon_state = "blackbox" - name = "Blackbox Recorder" - density = 1 - anchored = 1.0 - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - var/list/messages = list() //Stores messages of non-standard frequencies - var/list/messages_admin = list() - - var/list/msg_common = list() - var/list/msg_science = list() - var/list/msg_command = list() - var/list/msg_medical = list() - var/list/msg_engineering = list() - var/list/msg_security = list() - var/list/msg_deathsquad = list() - var/list/msg_syndicate = list() - var/list/msg_syndteam = list() - var/list/msg_mining = list() - var/list/msg_cargo = list() - var/list/msg_service = list() - - var/list/datum/feedback_variable/feedback = new() - - //Only one can exsist in the world! -/obj/machinery/blackbox_recorder/New() - if(blackbox) - if(istype(blackbox,/obj/machinery/blackbox_recorder)) - qdel(src) - blackbox = src - -/obj/machinery/blackbox_recorder/Destroy() - var/turf/T = locate(1,1,2) - if(T) - blackbox = null - var/obj/machinery/blackbox_recorder/BR = new/obj/machinery/blackbox_recorder(T) - BR.msg_common = msg_common - BR.msg_science = msg_science - BR.msg_command = msg_command - BR.msg_medical = msg_medical - BR.msg_engineering = msg_engineering - BR.msg_security = msg_security - BR.msg_deathsquad = msg_deathsquad - BR.msg_syndicate = msg_syndicate - BR.msg_syndteam = msg_syndteam - BR.msg_mining = msg_mining - BR.msg_cargo = msg_cargo - BR.msg_service = msg_service - BR.feedback = feedback - BR.messages = messages - BR.messages_admin = messages_admin - if(blackbox != BR) - blackbox = BR - return ..() - -/obj/machinery/blackbox_recorder/proc/find_feedback_datum(var/variable) - for(var/datum/feedback_variable/FV in feedback) - if(FV.get_variable() == variable) - return FV - var/datum/feedback_variable/FV = new(variable) - feedback += FV - return FV - -/obj/machinery/blackbox_recorder/proc/get_round_feedback() - return feedback - -/obj/machinery/blackbox_recorder/proc/round_end_data_gathering() - - var/pda_msg_amt = 0 - var/rc_msg_amt = 0 - - for(var/obj/machinery/message_server/MS in GLOB.machines) - if(MS.pda_msgs.len > pda_msg_amt) - pda_msg_amt = MS.pda_msgs.len - if(MS.rc_msgs.len > rc_msg_amt) - rc_msg_amt = MS.rc_msgs.len - - feedback_set_details("radio_usage","") - - feedback_add_details("radio_usage","COM-[msg_common.len]") - feedback_add_details("radio_usage","SCI-[msg_science.len]") - feedback_add_details("radio_usage","HEA-[msg_command.len]") - feedback_add_details("radio_usage","MED-[msg_medical.len]") - feedback_add_details("radio_usage","ENG-[msg_engineering.len]") - feedback_add_details("radio_usage","SEC-[msg_security.len]") - feedback_add_details("radio_usage","DTH-[msg_deathsquad.len]") - feedback_add_details("radio_usage","SYN-[msg_syndicate.len]") - feedback_add_details("radio_usage","SYT-[msg_syndteam.len]") - feedback_add_details("radio_usage","MIN-[msg_mining.len]") - feedback_add_details("radio_usage","CAR-[msg_cargo.len]") - feedback_add_details("radio_usage","SRV-[msg_service.len]") - feedback_add_details("radio_usage","OTH-[messages.len]") - feedback_add_details("radio_usage","PDA-[pda_msg_amt]") - feedback_add_details("radio_usage","RC-[rc_msg_amt]") - - - feedback_set_details("round_end","[time2text(world.realtime)]") //This one MUST be the last one that gets set. - - -//This proc is only to be called at round end. -/obj/machinery/blackbox_recorder/proc/save_all_data_to_sql() - if(!feedback) return - - round_end_data_gathering() //round_end time logging and some other data processing - establish_db_connection() - if(!dbcon.IsConnected()) return - var/round_id - - var/DBQuery/query = dbcon.NewQuery("SELECT MAX(round_id) AS round_id FROM [format_table_name("feedback")]") - query.Execute() - while(query.NextRow()) - round_id = query.item[1] - - if(!isnum(round_id)) - round_id = text2num(round_id) - round_id++ - - for(var/datum/feedback_variable/FV in feedback) - var/sql = "INSERT INTO [format_table_name("feedback")] VALUES (null, Now(), [round_id], \"[FV.get_variable()]\", [FV.get_value()], \"[FV.get_details()]\")" - var/DBQuery/query_insert = dbcon.NewQuery(sql) - query_insert.Execute() - -/obj/machinery/blackbox_recorder/vv_edit_var(var_name, var_value) - return FALSE // don't fuck with the stupid blackbox shit - - -proc/feedback_set(var/variable,var/value) - if(!blackbox) return - - variable = sanitizeSQL(variable) - - var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) - - if(!FV) return - - FV.set_value(value) - -proc/feedback_inc(var/variable,var/value) - if(!blackbox) return - - variable = sanitizeSQL(variable) - - var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) - - if(!FV) return - - FV.inc(value) - -proc/feedback_dec(var/variable,var/value) - if(!blackbox) return - - variable = sanitizeSQL(variable) - - var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) - - if(!FV) return - - FV.dec(value) - -proc/feedback_set_details(var/variable,var/details) - if(!blackbox) return - - variable = sanitizeSQL(variable) - details = sanitizeSQL(details) - - var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) - - if(!FV) return - - FV.set_details(details) - -proc/feedback_add_details(var/variable,var/details) - if(!blackbox) return - - variable = sanitizeSQL(variable) - details = sanitizeSQL(details) - - var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) - - if(!FV) return - - FV.add_details(details) +var/global/list/obj/machinery/message_server/message_servers = list() + +/datum/data_pda_msg + var/recipient = "Unspecified" //name of the person + var/sender = "Unspecified" //name of the sender + var/message = "Blank" //transferred message + +/datum/data_pda_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "") + + if(param_rec) + recipient = param_rec + if(param_sender) + sender = param_sender + if(param_message) + message = param_message + +/datum/data_rc_msg + var/rec_dpt = "Unspecified" //name of the person + var/send_dpt = "Unspecified" //name of the sender + var/message = "Blank" //transferred message + var/stamp = "Unstamped" + var/id_auth = "Unauthenticated" + var/priority = "Normal" + +/datum/data_rc_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "",var/param_stamp = "",var/param_id_auth = "",var/param_priority) + if(param_rec) + rec_dpt = param_rec + if(param_sender) + send_dpt = param_sender + if(param_message) + message = param_message + if(param_stamp) + stamp = param_stamp + if(param_id_auth) + id_auth = param_id_auth + if(param_priority) + switch(param_priority) + if(1) + priority = "Normal" + if(2) + priority = "High" + if(3) + priority = "Extreme" + else + priority = "Undetermined" + +/obj/machinery/message_server + icon = 'icons/obj/machines/research.dmi' + icon_state = "server" + name = "Messaging Server" + density = 1 + anchored = 1.0 + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + + var/list/datum/data_pda_msg/pda_msgs = list() + var/list/datum/data_rc_msg/rc_msgs = list() + var/active = 1 + var/decryptkey = "password" + +/obj/machinery/message_server/New() + message_servers += src + decryptkey = GenerateKey() + send_pda_message("System Administrator", "system", "This is an automated message. The messaging system is functioning correctly.") + ..() + return + +/obj/machinery/message_server/Destroy() + message_servers -= src + return ..() + +/obj/machinery/message_server/process() + //if(decryptkey == "password") + // decryptkey = generateKey() + if(active && (stat & (BROKEN|NOPOWER))) + active = 0 + return + if(prob(3)) + playsound(loc, "computer_ambience", 50, 1) + update_icon() + return + +/obj/machinery/message_server/proc/send_pda_message(var/recipient = "",var/sender = "",var/message = "") + pda_msgs += new/datum/data_pda_msg(recipient,sender,message) + +/obj/machinery/message_server/proc/send_rc_message(var/recipient = "",var/sender = "",var/message = "",var/stamp = "", var/id_auth = "", var/priority = 1) + rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth) + var/authmsg = "[message]
        " + if(id_auth) + authmsg += "[id_auth]
        " + if(stamp) + authmsg += "[stamp]
        " + for(var/obj/machinery/requests_console/Console in allConsoles) + if(ckey(Console.department) == ckey(recipient)) + if(Console.inoperable()) + Console.message_log += "Message lost due to console failure.
        Please contact [station_name()] system adminsitrator or AI for technical assistance.
        " + continue + if(Console.newmessagepriority < priority) + Console.newmessagepriority = priority + Console.icon_state = "req_comp[priority]" + switch(priority) + if(2) + if(!Console.silent) + playsound(Console.loc, 'sound/machines/twobeep.ogg', 50, 1) + Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5) + Console.message_log += "High Priority message from [sender]
        [authmsg]" + else + if(!Console.silent) + playsound(Console.loc, 'sound/machines/twobeep.ogg', 50, 1) + Console.audible_message(text("[bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4) + Console.message_log += "Message from [sender]
        [authmsg]" + Console.set_light(2) + +/obj/machinery/message_server/attack_hand(user as mob) +// to_chat(user, "There seem to be some parts missing from this server. They should arrive on the station in a few days, give or take a few CentComm delays.") + to_chat(user, "You toggle PDA message passing from [active ? "On" : "Off"] to [active ? "Off" : "On"]") + active = !active + update_icon() + + return + +/obj/machinery/message_server/update_icon() + if((stat & (BROKEN|NOPOWER))) + icon_state = "server-nopower" + else if(!active) + icon_state = "server-off" + else + icon_state = "server-on" + + return + + +/datum/feedback_variable + var/variable + var/value + var/details + +/datum/feedback_variable/New(var/param_variable,var/param_value = 0) + variable = param_variable + value = param_value + +/datum/feedback_variable/vv_edit_var(var_name, var_value) + return FALSE // come on guys don't break the stats + +/datum/feedback_variable/proc/inc(var/num = 1) + if(isnum(value)) + value += num + else + value = text2num(value) + if(isnum(value)) + value += num + else + value = num + +/datum/feedback_variable/proc/dec(var/num = 1) + if(isnum(value)) + value -= num + else + value = text2num(value) + if(isnum(value)) + value -= num + else + value = -num + +/datum/feedback_variable/proc/set_value(var/num) + if(isnum(num)) + value = num + +/datum/feedback_variable/proc/get_value() + return value + +/datum/feedback_variable/proc/get_variable() + return variable + +/datum/feedback_variable/proc/set_details(var/text) + if(istext(text)) + details = text + +/datum/feedback_variable/proc/add_details(var/text) + if(istext(text)) + if(!details) + details = text + else + details += " [text]" + +/datum/feedback_variable/proc/get_details() + return details + +/datum/feedback_variable/proc/get_parsed() + return list(variable,value,details) + +var/obj/machinery/blackbox_recorder/blackbox + +//TODO: kill whoever designed this cancer +/obj/machinery/blackbox_recorder + icon = 'icons/obj/stationobjs.dmi' + icon_state = "blackbox" + name = "Blackbox Recorder" + density = 1 + anchored = 1.0 + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + var/list/messages = list() //Stores messages of non-standard frequencies + var/list/messages_admin = list() + + var/list/msg_common = list() + var/list/msg_science = list() + var/list/msg_command = list() + var/list/msg_medical = list() + var/list/msg_engineering = list() + var/list/msg_security = list() + var/list/msg_deathsquad = list() + var/list/msg_syndicate = list() + var/list/msg_syndteam = list() + var/list/msg_mining = list() + var/list/msg_cargo = list() + var/list/msg_service = list() + + var/list/datum/feedback_variable/feedback = new() + + //Only one can exsist in the world! +/obj/machinery/blackbox_recorder/New() + if(blackbox) + if(istype(blackbox,/obj/machinery/blackbox_recorder)) + qdel(src) + blackbox = src + +/obj/machinery/blackbox_recorder/Destroy() + var/turf/T = locate(1,1,2) + if(T) + blackbox = null + var/obj/machinery/blackbox_recorder/BR = new/obj/machinery/blackbox_recorder(T) + BR.msg_common = msg_common + BR.msg_science = msg_science + BR.msg_command = msg_command + BR.msg_medical = msg_medical + BR.msg_engineering = msg_engineering + BR.msg_security = msg_security + BR.msg_deathsquad = msg_deathsquad + BR.msg_syndicate = msg_syndicate + BR.msg_syndteam = msg_syndteam + BR.msg_mining = msg_mining + BR.msg_cargo = msg_cargo + BR.msg_service = msg_service + BR.feedback = feedback + BR.messages = messages + BR.messages_admin = messages_admin + if(blackbox != BR) + blackbox = BR + return ..() + +/obj/machinery/blackbox_recorder/proc/find_feedback_datum(var/variable) + for(var/datum/feedback_variable/FV in feedback) + if(FV.get_variable() == variable) + return FV + var/datum/feedback_variable/FV = new(variable) + feedback += FV + return FV + +/obj/machinery/blackbox_recorder/proc/get_round_feedback() + return feedback + +/obj/machinery/blackbox_recorder/proc/round_end_data_gathering() + + var/pda_msg_amt = 0 + var/rc_msg_amt = 0 + + for(var/obj/machinery/message_server/MS in GLOB.machines) + if(MS.pda_msgs.len > pda_msg_amt) + pda_msg_amt = MS.pda_msgs.len + if(MS.rc_msgs.len > rc_msg_amt) + rc_msg_amt = MS.rc_msgs.len + + feedback_set_details("radio_usage","") + + feedback_add_details("radio_usage","COM-[msg_common.len]") + feedback_add_details("radio_usage","SCI-[msg_science.len]") + feedback_add_details("radio_usage","HEA-[msg_command.len]") + feedback_add_details("radio_usage","MED-[msg_medical.len]") + feedback_add_details("radio_usage","ENG-[msg_engineering.len]") + feedback_add_details("radio_usage","SEC-[msg_security.len]") + feedback_add_details("radio_usage","DTH-[msg_deathsquad.len]") + feedback_add_details("radio_usage","SYN-[msg_syndicate.len]") + feedback_add_details("radio_usage","SYT-[msg_syndteam.len]") + feedback_add_details("radio_usage","MIN-[msg_mining.len]") + feedback_add_details("radio_usage","CAR-[msg_cargo.len]") + feedback_add_details("radio_usage","SRV-[msg_service.len]") + feedback_add_details("radio_usage","OTH-[messages.len]") + feedback_add_details("radio_usage","PDA-[pda_msg_amt]") + feedback_add_details("radio_usage","RC-[rc_msg_amt]") + + + feedback_set_details("round_end","[time2text(world.realtime)]") //This one MUST be the last one that gets set. + + +//This proc is only to be called at round end. +/obj/machinery/blackbox_recorder/proc/save_all_data_to_sql() + if(!feedback) return + + round_end_data_gathering() //round_end time logging and some other data processing + establish_db_connection() + if(!dbcon.IsConnected()) return + var/round_id + + var/DBQuery/query = dbcon.NewQuery("SELECT MAX(round_id) AS round_id FROM [format_table_name("feedback")]") + query.Execute() + while(query.NextRow()) + round_id = query.item[1] + + if(!isnum(round_id)) + round_id = text2num(round_id) + round_id++ + + for(var/datum/feedback_variable/FV in feedback) + var/sql = "INSERT INTO [format_table_name("feedback")] VALUES (null, Now(), [round_id], \"[FV.get_variable()]\", [FV.get_value()], \"[FV.get_details()]\")" + var/DBQuery/query_insert = dbcon.NewQuery(sql) + query_insert.Execute() + +/obj/machinery/blackbox_recorder/vv_edit_var(var_name, var_value) + return FALSE // don't fuck with the stupid blackbox shit + + +proc/feedback_set(var/variable,var/value) + if(!blackbox) return + + variable = sanitizeSQL(variable) + + var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) + + if(!FV) return + + FV.set_value(value) + +proc/feedback_inc(var/variable,var/value) + if(!blackbox) return + + variable = sanitizeSQL(variable) + + var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) + + if(!FV) return + + FV.inc(value) + +proc/feedback_dec(var/variable,var/value) + if(!blackbox) return + + variable = sanitizeSQL(variable) + + var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) + + if(!FV) return + + FV.dec(value) + +proc/feedback_set_details(var/variable,var/details) + if(!blackbox) return + + variable = sanitizeSQL(variable) + details = sanitizeSQL(details) + + var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) + + if(!FV) return + + FV.set_details(details) + +proc/feedback_add_details(var/variable,var/details) + if(!blackbox) return + + variable = sanitizeSQL(variable) + details = sanitizeSQL(details) + + var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable) + + if(!FV) return + + FV.add_details(details) diff --git a/code/modules/research/protolathe.dm b/code/modules/research/protolathe.dm index de71f51a0cd..6f2e608ef4b 100644 --- a/code/modules/research/protolathe.dm +++ b/code/modules/research/protolathe.dm @@ -1,113 +1,113 @@ -/* -Protolathe - -Similar to an autolathe, you load glass and metal sheets (but not other objects) into it to be used as raw materials for the stuff -it creates. All the menus and other manipulation commands are in the R&D console. - -Note: Must be placed west/left of and R&D console to function. - -*/ -/obj/machinery/r_n_d/protolathe - name = "Protolathe" - desc = "Converts raw materials into useful objects." - icon_state = "protolathe" - container_type = OPENCONTAINER - - var/efficiency_coeff - - var/list/categories = list( - "Bluespace", - "Computer Parts", - "Equipment", - "Janitorial", - "Medical", - "Mining", - "Miscellaneous", - "Power", - "Stock Parts", - "Weapons" - ) - - reagents = new() - - -/obj/machinery/r_n_d/protolathe/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/protolathe(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/matter_bin(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/stock_parts/manipulator(null) - component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) - component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) - RefreshParts() - - reagents.my_atom = src - -/obj/machinery/r_n_d/protolathe/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/protolathe(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) - component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) - RefreshParts() - - reagents.my_atom = src - -/obj/machinery/r_n_d/protolathe/RefreshParts() - var/T = 0 - for(var/obj/item/reagent_containers/glass/G in component_parts) - G.reagents.trans_to(src, G.reagents.total_volume) - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - T += M.rating - materials.max_amount = T * 75000 - T = 1.2 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - T -= M.rating/10 - efficiency_coeff = min(max(0, T), 1) - -/obj/machinery/r_n_d/protolathe/proc/check_mat(datum/design/being_built, var/M) // now returns how many times the item can be built with the material - var/A = materials.amount(M) - if(!A) - A = reagents.get_reagent_amount(M) - A = A / max(1, (being_built.reagents_list[M])) - else - A = A / max(1, (being_built.materials[M])) - return A - -/obj/machinery/r_n_d/protolathe/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - if(shocked) - if(shock(user,50)) - return TRUE - if(default_deconstruction_screwdriver(user, "protolathe_t", "protolathe", O)) - if(linked_console) - linked_console.linked_lathe = null - linked_console = null - return - - if(exchange_parts(user, O)) - return - - if(panel_open) - if(istype(O, /obj/item/crowbar)) - for(var/obj/I in component_parts) - if(istype(I, /obj/item/reagent_containers/glass/beaker)) - reagents.trans_to(I, reagents.total_volume) - I.loc = src.loc - for(var/obj/item/reagent_containers/glass/G in component_parts) - reagents.trans_to(G, G.reagents.maximum_volume) - materials.retrieve_all() - default_deconstruction_crowbar(user, O) - return 1 - else - to_chat(user, "You can't load the [src.name] while it's opened.") - return 1 - if(O.is_open_container()) - return FALSE - else - return ..() \ No newline at end of file +/* +Protolathe + +Similar to an autolathe, you load glass and metal sheets (but not other objects) into it to be used as raw materials for the stuff +it creates. All the menus and other manipulation commands are in the R&D console. + +Note: Must be placed west/left of and R&D console to function. + +*/ +/obj/machinery/r_n_d/protolathe + name = "Protolathe" + desc = "Converts raw materials into useful objects." + icon_state = "protolathe" + container_type = OPENCONTAINER + + var/efficiency_coeff + + var/list/categories = list( + "Bluespace", + "Computer Parts", + "Equipment", + "Janitorial", + "Medical", + "Mining", + "Miscellaneous", + "Power", + "Stock Parts", + "Weapons" + ) + + reagents = new() + + +/obj/machinery/r_n_d/protolathe/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/protolathe(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/matter_bin(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/stock_parts/manipulator(null) + component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) + component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) + RefreshParts() + + reagents.my_atom = src + +/obj/machinery/r_n_d/protolathe/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/protolathe(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) + component_parts += new /obj/item/reagent_containers/glass/beaker/large(null) + RefreshParts() + + reagents.my_atom = src + +/obj/machinery/r_n_d/protolathe/RefreshParts() + var/T = 0 + for(var/obj/item/reagent_containers/glass/G in component_parts) + G.reagents.trans_to(src, G.reagents.total_volume) + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + T += M.rating + materials.max_amount = T * 75000 + T = 1.2 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + T -= M.rating/10 + efficiency_coeff = min(max(0, T), 1) + +/obj/machinery/r_n_d/protolathe/proc/check_mat(datum/design/being_built, var/M) // now returns how many times the item can be built with the material + var/A = materials.amount(M) + if(!A) + A = reagents.get_reagent_amount(M) + A = A / max(1, (being_built.reagents_list[M])) + else + A = A / max(1, (being_built.materials[M])) + return A + +/obj/machinery/r_n_d/protolathe/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + if(shocked) + if(shock(user,50)) + return TRUE + if(default_deconstruction_screwdriver(user, "protolathe_t", "protolathe", O)) + if(linked_console) + linked_console.linked_lathe = null + linked_console = null + return + + if(exchange_parts(user, O)) + return + + if(panel_open) + if(istype(O, /obj/item/crowbar)) + for(var/obj/I in component_parts) + if(istype(I, /obj/item/reagent_containers/glass/beaker)) + reagents.trans_to(I, reagents.total_volume) + I.loc = src.loc + for(var/obj/item/reagent_containers/glass/G in component_parts) + reagents.trans_to(G, G.reagents.maximum_volume) + materials.retrieve_all() + default_deconstruction_crowbar(user, O) + return 1 + else + to_chat(user, "You can't load the [src.name] while it's opened.") + return 1 + if(O.is_open_container()) + return FALSE + else + return ..() diff --git a/code/modules/research/rd-readme.dm b/code/modules/research/rd-readme.dm index 1ecc9dbb0da..d1b8f77056c 100644 --- a/code/modules/research/rd-readme.dm +++ b/code/modules/research/rd-readme.dm @@ -1,239 +1,239 @@ -/* -Research and Development System. (Designed specifically for the /tg/station 13 (Space Station 13) open source project) - -///////////////Overview/////////////////// -This system is a "tech tree" research and development system designed for SS13. It allows a "researcher" job (this document assumes -the "scientist" job is given this role) the tools necessiary to research new and better technologies. In general, the system works -by breaking existing technology and using what you learn from to advance your knowledge of SCIENCE! As your knowledge progresses, -you can build newer (and better?) devices (which you can also, eventually, deconstruct to advance your knowledge). - -A brief overview is below. For more details, see the related files. - -////////////Game Use///////////// -The major research and development is performed using a combination of four machines: -- R&D Console: A computer console that allows you to manipulate the other devices that are linked to it and view/manipulate the -technologies you have researched so far. -- Protolathe: Used to make new hand-held devices and parts for larger devices. All metals and reagents as raw materials. -- Destructive Analyzer: You can put hand-held objects into it and it'll analyze them for technological advancements but it destroys -them in the process. Destroyed items will send their raw materials to a linked Protolathe (if any) -- Circuit Imprinter: Similar to the Protolathe, it allows for the construction of circuit boards. Uses glass and acid as the raw -materials. - -While researching you are dealing with two different types of information: Technology Paths and Device Designs. Technology Paths -are the "Tech Trees" of the game. You start out with a number of them at the game start and they are improved by using the -Destructive Analyzer. By themselves, they don't do a whole lot. However, they unlock Device Designs. This is the information used -by the circuit imprinter and the protolathe to produce objects. - -//EXISTING TECH -Each tech path should have at LEAST one item at every level (levels 1 - 20). This is to allow for a more fluid progression of the -researching. Existing tech (ie, anything you can find on the station or get from the quartermaster) shouldn't go higher then -level 5 or 7. Everything past that should be stuff you research. - -Below is a checklist to make sure every tree is filled. As new items get added to R&D, add them here if there is an empty slot. -When thinking about new stuff, check here to see if there are any slots unfilled. - -//MATERIALS -1 | Metal -2 | Solid Plasma -3 | Silver -4 | Gold, Super Capacitor -5 | Uranium, Nuclear Gun, SUPERPACMAN -6 | Diamond, MRSPACMAN -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - -//PLASMA TECH -1 | -2 | Solid Plasma -3 | Pacman Generator -4 | -5 | -6 | -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - -//POWER TECH -1 | Basic Capacitor, Basic Cell -2 | High-Capacity Cell (10,000) -3 | Super-Capacity Cell (20,000), Powersink, PACMAN -4 | SUPERPACMAN -5 | MRSPACMAN, Super Capacitor -6 | Hyper-Capacity Cell (30,000) -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - -//BLUE SPACE -1 | -2 | Teleporter Console Board -3 | Teleport Gun, Hand Tele -4 | Teleportation Scroll -5 | -6 | -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - -//BIOTECH -1 | Bruise Pack, Scalple -2 | PANDEMIC Board, Mass Spectrometer -3 | AI Core, Brains (MMI) -4 | MMI+Radio -5 | -6 | -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - -//MAGNETS -1 | Basic Sensor -2 | Comm Console Board -3 | Adv Sensor -4 | Adv Mass Spectrometer, Chameleon Projector -5 | Phasic Sensor -6 | -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - -//PROGRAMMING -1 | Arcade Board -2 | Sec Camera -3 | Cloning Machine Console Board -4 | AI Core, Intellicard -5 | Pico-Manipulator, Ultra-Micro-Laser -6 | -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - -//SYNDICATE -1 | Sleepypen -2 | TYRANT Module, Emag -3 | Cloaking Device, Power Sink -4 | -5 | -6 | -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - -//COMBAT -1 | Flashbang, Mousetrap, Nettle -2 | Stun Baton -3 | Power Axe, Death Nettle, Nuclear Gun -4 | -5 | -6 | -7 | -8 | -9 | -10 | -11 | -12 | -13 | -14 | -15 | -16 | -17 | -18 | -19 | -20 | - - - - - - - -*/ \ No newline at end of file +/* +Research and Development System. (Designed specifically for the /tg/station 13 (Space Station 13) open source project) + +///////////////Overview/////////////////// +This system is a "tech tree" research and development system designed for SS13. It allows a "researcher" job (this document assumes +the "scientist" job is given this role) the tools necessiary to research new and better technologies. In general, the system works +by breaking existing technology and using what you learn from to advance your knowledge of SCIENCE! As your knowledge progresses, +you can build newer (and better?) devices (which you can also, eventually, deconstruct to advance your knowledge). + +A brief overview is below. For more details, see the related files. + +////////////Game Use///////////// +The major research and development is performed using a combination of four machines: +- R&D Console: A computer console that allows you to manipulate the other devices that are linked to it and view/manipulate the +technologies you have researched so far. +- Protolathe: Used to make new hand-held devices and parts for larger devices. All metals and reagents as raw materials. +- Destructive Analyzer: You can put hand-held objects into it and it'll analyze them for technological advancements but it destroys +them in the process. Destroyed items will send their raw materials to a linked Protolathe (if any) +- Circuit Imprinter: Similar to the Protolathe, it allows for the construction of circuit boards. Uses glass and acid as the raw +materials. + +While researching you are dealing with two different types of information: Technology Paths and Device Designs. Technology Paths +are the "Tech Trees" of the game. You start out with a number of them at the game start and they are improved by using the +Destructive Analyzer. By themselves, they don't do a whole lot. However, they unlock Device Designs. This is the information used +by the circuit imprinter and the protolathe to produce objects. + +//EXISTING TECH +Each tech path should have at LEAST one item at every level (levels 1 - 20). This is to allow for a more fluid progression of the +researching. Existing tech (ie, anything you can find on the station or get from the quartermaster) shouldn't go higher then +level 5 or 7. Everything past that should be stuff you research. + +Below is a checklist to make sure every tree is filled. As new items get added to R&D, add them here if there is an empty slot. +When thinking about new stuff, check here to see if there are any slots unfilled. + +//MATERIALS +1 | Metal +2 | Solid Plasma +3 | Silver +4 | Gold, Super Capacitor +5 | Uranium, Nuclear Gun, SUPERPACMAN +6 | Diamond, MRSPACMAN +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + +//PLASMA TECH +1 | +2 | Solid Plasma +3 | Pacman Generator +4 | +5 | +6 | +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + +//POWER TECH +1 | Basic Capacitor, Basic Cell +2 | High-Capacity Cell (10,000) +3 | Super-Capacity Cell (20,000), Powersink, PACMAN +4 | SUPERPACMAN +5 | MRSPACMAN, Super Capacitor +6 | Hyper-Capacity Cell (30,000) +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + +//BLUE SPACE +1 | +2 | Teleporter Console Board +3 | Teleport Gun, Hand Tele +4 | Teleportation Scroll +5 | +6 | +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + +//BIOTECH +1 | Bruise Pack, Scalple +2 | PANDEMIC Board, Mass Spectrometer +3 | AI Core, Brains (MMI) +4 | MMI+Radio +5 | +6 | +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + +//MAGNETS +1 | Basic Sensor +2 | Comm Console Board +3 | Adv Sensor +4 | Adv Mass Spectrometer, Chameleon Projector +5 | Phasic Sensor +6 | +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + +//PROGRAMMING +1 | Arcade Board +2 | Sec Camera +3 | Cloning Machine Console Board +4 | AI Core, Intellicard +5 | Pico-Manipulator, Ultra-Micro-Laser +6 | +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + +//SYNDICATE +1 | Sleepypen +2 | TYRANT Module, Emag +3 | Cloaking Device, Power Sink +4 | +5 | +6 | +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + +//COMBAT +1 | Flashbang, Mousetrap, Nettle +2 | Stun Baton +3 | Power Axe, Death Nettle, Nuclear Gun +4 | +5 | +6 | +7 | +8 | +9 | +10 | +11 | +12 | +13 | +14 | +15 | +16 | +17 | +18 | +19 | +20 | + + + + + + + +*/ diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index b867803e0e2..d4132bf3f4b 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -1,948 +1,948 @@ -/* -Research and Development (R&D) Console - -This is the main work horse of the R&D system. It contains the menus/controls for the Destructive Analyzer, Protolathe, and Circuit -imprinter. It also contains the /datum/research holder with all the known/possible technology paths and device designs. - -Basic use: When it first is created, it will attempt to link up to related devices within 3 squares. It'll only link up if they -aren't already linked to another console. Any consoles it cannot link up with (either because all of a certain type are already -linked or there aren't any in range), you'll just not have access to that menu. In the settings menu, there are menu options that -allow a player to attempt to re-sync with nearby consoles. You can also force it to disconnect from a specific console. - -The imprinting and construction menus do NOT require toxins access to access but all the other menus do. However, if you leave it -on a menu, nothing is to stop the person from using the options on that menu (although they won't be able to change to a different -one). You can also lock the console on the settings menu if you're feeling paranoid and you don't want anyone messing with it who -doesn't have toxins access. - -When a R&D console is destroyed or even partially disassembled, you lose all research data on it. However, there are two ways around -this dire fate: -- The easiest way is to go to the settings menu and select "Sync Database with Network." That causes it to upload (but not download) -it's data to every other device in the game. Each console has a "disconnect from network" option that'll will cause data base sync -operations to skip that console. This is useful if you want to make a "public" R&D console or, for example, give the engineers -a circuit imprinter with certain designs on it and don't want it accidentally updating. The downside of this method is that you have -to have physical access to the other console to send data back. Note: An R&D console is on Centcom so if a random griffan happens to -cause a ton of data to be lost, an admin can go send it back. -- The second method is with Technology Disks and Design Disks. Each of these disks can hold a single technology or design datum in -it's entirety. You can then take the disk to any R&D console and upload it's data to it. This method is a lot more secure (since it -won't update every console in existence) but it's more of a hassle to do. Also, the disks can be stolen. - - -*/ - -// Who likes #defines? -// I don't! -// but I gotta add 'em anyways because we have a bias against /const statements for some reason -#define TECH_UPDATE_DELAY 50 -#define DESIGN_UPDATE_DELAY 50 -#define PROTOLATHE_CONSTRUCT_DELAY 32 -#define SYNC_RESEARCH_DELAY 30 -#define DECONSTRUCT_DELAY 24 -#define SYNC_DEVICE_DELAY 20 -#define RESET_RESEARCH_DELAY 20 -#define IMPRINTER_DELAY 16 - -/obj/machinery/computer/rdconsole - name = "\improper R&D console" - icon_screen = "rdcomp" - icon_keyboard = "rd_key" - light_color = LIGHT_COLOR_FADEDPURPLE - circuit = /obj/item/circuitboard/rdconsole - var/datum/research/files //Stores all the collected research data. - var/obj/item/disk/tech_disk/t_disk = null //Stores the technology disk. - var/obj/item/disk/design_disk/d_disk = null //Stores the design disk. - - var/obj/machinery/r_n_d/destructive_analyzer/linked_destroy = null //Linked Destructive Analyzer - var/obj/machinery/r_n_d/protolathe/linked_lathe = null //Linked Protolathe - var/obj/machinery/r_n_d/circuit_imprinter/linked_imprinter = null //Linked Circuit Imprinter - - var/screen = 1.0 //Which screen is currently showing. - - var/menu = 0 // Current menu. - var/submenu = 0 - var/wait_message = 0 - var/wait_message_timer = 0 - - var/id = 0 //ID of the computer (for server restrictions). - var/sync = 1 //If sync = 0, it doesn't show up on Server Control Console - - req_access = list(ACCESS_TOX) //Data and setting manipulation requires scientist access. - - var/selected_category - var/list/datum/design/matching_designs = list() //for the search function - -/proc/CallTechName(ID) //A simple helper proc to find the name of a tech with a given ID. - for(var/T in subtypesof(/datum/tech)) - var/datum/tech/tt = T - if(initial(tt.id) == ID) - return initial(tt.name) - -/proc/CallMaterialName(ID) - if(copytext(ID, 1, 2) == "$") - var/return_name = copytext(ID, 2) - switch(return_name) - if("metal") - return_name = "Metal" - if("glass") - return_name = "Glass" - if("gold") - return_name = "Gold" - if("silver") - return_name = "Silver" - if("plasma") - return_name = "Solid Plasma" - if("uranium") - return_name = "Uranium" - if("diamond") - return_name = "Diamond" - if("clown") - return_name = "Bananium" - if("mime") - return_name = "Tranquillite" - if("titanium") - return_name = "Titanium" - if("bluespace") - return_name = "Bluespace Mesh" - if("plastic") - return_name = "Plastic" - return return_name - else - for(var/R in subtypesof(/datum/reagent)) - var/datum/reagent/rt = R - if(initial(rt.id) == ID) - return initial(rt.name) - -/obj/machinery/computer/rdconsole/proc/SyncRDevices() //Makes sure it is properly sync'ed up with the devices attached to it (if any). - for(var/obj/machinery/r_n_d/D in range(3,src)) - if(!isnull(D.linked_console) || D.disabled || D.panel_open) - continue - if(istype(D, /obj/machinery/r_n_d/destructive_analyzer)) - if(linked_destroy == null) - linked_destroy = D - D.linked_console = src - else if(istype(D, /obj/machinery/r_n_d/protolathe)) - if(linked_lathe == null) - linked_lathe = D - D.linked_console = src - else if(istype(D, /obj/machinery/r_n_d/circuit_imprinter)) - if(linked_imprinter == null) - linked_imprinter = D - D.linked_console = src - return - -//Have it automatically push research to the centcom server so wild griffins can't fuck up R&D's work --NEO -/obj/machinery/computer/rdconsole/proc/griefProtection() - for(var/obj/machinery/r_n_d/server/centcom/C in world) - files.push_data(C.files) - -/obj/machinery/computer/rdconsole/proc/Maximize() - for(var/datum/tech/T in files.possible_tech) - files.known_tech[T.id] = T - for(var/v in files.known_tech) - var/datum/tech/KT = files.known_tech[v] - if(KT.level < KT.max_level) - KT.level=KT.max_level - files.RefreshResearch() - -/obj/machinery/computer/rdconsole/New() - ..() - files = new /datum/research(src) //Setup the research data holder. - matching_designs = list() - if(!id) - for(var/obj/machinery/r_n_d/server/centcom/S in world) - S.initialize_serv() - break - -/obj/machinery/computer/rdconsole/Initialize() - ..() - SyncRDevices() - -/obj/machinery/computer/rdconsole/Destroy() - if(wait_message_timer) - deltimer(wait_message_timer) - wait_message_timer = 0 - return ..() - -/* Instead of calling this every tick, it is only being called when needed -/obj/machinery/computer/rdconsole/process() - griefProtection() -*/ - -/obj/machinery/computer/rdconsole/attackby(var/obj/item/D as obj, var/mob/user as mob, params) - - //Loading a disk into it. - if(istype(D, /obj/item/disk)) - if(t_disk || d_disk) - to_chat(user, "A disk is already loaded into the machine.") - return - - if(istype(D, /obj/item/disk/tech_disk)) t_disk = D - else if(istype(D, /obj/item/disk/design_disk)) d_disk = D - else - to_chat(user, "Machine cannot accept disks in that format.") - return - if(!user.drop_item()) - return - D.loc = src - to_chat(user, "You add the disk to the machine!") - else if(!(linked_destroy && linked_destroy.busy) && !(linked_lathe && linked_lathe.busy) && !(linked_imprinter && linked_imprinter.busy)) - ..() - SSnanoui.update_uis(src) - return - -/obj/machinery/computer/rdconsole/emag_act(user as mob) - if(!emagged) - playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1) - req_access = list() - emagged = 1 - to_chat(user, "You disable the security protocols") - -/obj/machinery/computer/rdconsole/Topic(href, href_list) - if(..()) - return 1 - - if(!allowed(usr) && !isobserver(usr)) - return 1 - - add_fingerprint(usr) - - usr.set_machine(src) - if(href_list["menu"]) //Switches menu screens. Converts a sent text string into a number. Saves a LOT of code. - var/temp_screen = text2num(href_list["menu"]) - menu = temp_screen - if(href_list["submenu"]) //Switches menu screens. Converts a sent text string into a number. Saves a LOT of code. - var/temp_screen = text2num(href_list["submenu"]) - submenu = temp_screen - - if(href_list["category"]) - var/compare - - matching_designs.Cut() - - if(menu == 4) - compare = PROTOLATHE - else - compare = IMPRINTER - - for(var/v in files.known_designs) - var/datum/design/D = files.known_designs[v] - if(!(D.build_type & compare)) - continue - if(href_list["category"] in D.category) - matching_designs.Add(D) - submenu = 1 - - selected_category = "Viewing Category [href_list["category"]]" - - else if(href_list["updt_tech"]) //Update the research holder with information from the technology disk. - add_wait_message("Updating Database...", TECH_UPDATE_DELAY) - spawn(TECH_UPDATE_DELAY) - clear_wait_message() - files.AddTech2Known(t_disk.stored) - SSnanoui.update_uis(src) - griefProtection() //Update centcom too - - else if(href_list["clear_tech"]) //Erase data on the technology disk. - if(t_disk) - t_disk.wipe_tech() - - else if(href_list["eject_tech"]) //Eject the technology disk. - if(t_disk) - t_disk.loc = src.loc - t_disk = null - menu = 0 - submenu = 0 - - else if(href_list["copy_tech"]) //Copy some technology data from the research holder to the disk. - // Somehow this href makes me very nervous - t_disk.stored = files.known_tech[href_list["copy_tech_ID"]] - menu = 2 - submenu = 0 - - else if(href_list["updt_design"]) //Updates the research holder with design data from the design disk. - add_wait_message("Updating Database...", DESIGN_UPDATE_DELAY) - spawn(DESIGN_UPDATE_DELAY) - clear_wait_message() - files.AddDesign2Known(d_disk.blueprint) - SSnanoui.update_uis(src) - griefProtection() //Update centcom too - - else if(href_list["clear_design"]) //Erases data on the design disk. - if(d_disk) - d_disk.wipe_blueprint() - - else if(href_list["eject_design"]) //Eject the design disk. - if(d_disk) - d_disk.loc = src.loc - d_disk = null - menu = 0 - submenu = 0 - - else if(href_list["copy_design"]) //Copy design data from the research holder to the design disk. - // This href ALSO makes me very nervous - var/datum/design/D = files.known_designs[href_list["copy_design_ID"]] - if(D) - // eeeeeep design datums are global be careful! - var/autolathe_friendly = 1 - for(var/x in D.materials) - if( !(x in list(MAT_METAL, MAT_GLASS))) - autolathe_friendly = 0 - D.category -= "Imported" - if(D.locked) - autolathe_friendly = 0 - D.category -= "Imported" - if(D.build_type & (AUTOLATHE|PROTOLATHE|CRAFTLATHE)) // Specifically excludes circuit imprinter and mechfab - D.build_type = autolathe_friendly ? (D.build_type | AUTOLATHE) : D.build_type - D.category |= "Imported" - d_disk.blueprint = D - menu = 2 - submenu = 0 - - else if(href_list["eject_item"]) //Eject the item inside the destructive analyzer. - if(linked_destroy) - if(linked_destroy.busy) - to_chat(usr, " The destructive analyzer is busy at the moment.") - - else if(linked_destroy.loaded_item) - linked_destroy.loaded_item.loc = linked_destroy.loc - linked_destroy.loaded_item = null - linked_destroy.icon_state = "d_analyzer" - menu = 3 - - else if(href_list["maxresearch"]) //Eject the item inside the destructive analyzer. - if(!check_rights(R_ADMIN)) - return - if(alert("Are you sure you want to maximize research levels?","Confirmation","Yes","No")=="No") - return - log_admin("[key_name(usr)] has maximized the research levels.") - message_admins("[key_name_admin(usr)] has maximized the research levels.") - Maximize() - SSnanoui.update_uis(src) - griefProtection() //Update centcomm too - - else if(href_list["deconstruct"]) //Deconstruct the item in the destructive analyzer and update the research holder. - if(linked_destroy) - if(linked_destroy.busy) - to_chat(usr, "The destructive analyzer is busy at the moment.") - return - var/list/temp_tech = linked_destroy.ConvertReqString2List(linked_destroy.loaded_item.origin_tech) - var/cancontinue = FALSE - for(var/T in temp_tech) - if(files.IsTechHigher(T, temp_tech[T])) - cancontinue = TRUE - break - if(!cancontinue) - var/choice = input("This item does not raise tech levels. Proceed destroying loaded item anyway?") in list("Proceed", "Cancel") - if(choice == "Cancel" || !linked_destroy) - return - linked_destroy.busy = 1 - add_wait_message("Processing and Updating Database...", DECONSTRUCT_DELAY) - SSnanoui.update_uis(src) - flick("d_analyzer_process", linked_destroy) - spawn(DECONSTRUCT_DELAY) - clear_wait_message() - if(linked_destroy) - linked_destroy.busy = 0 - if(!linked_destroy.hacked) - if(!linked_destroy.loaded_item) - to_chat(usr, "The destructive analyzer appears to be empty.") - menu = 0 - submenu = 0 - return - for(var/T in temp_tech) - var/datum/tech/KT = files.known_tech[T] //For stat logging of high levels - if(files.IsTechHigher(T, temp_tech[T]) && KT.level >= 5) //For stat logging of high levels - feedback_add_details("high_research_level","[KT][KT.level + 1]") //+1 to show the level which we're about to get - files.UpdateTech(T, temp_tech[T]) - menu = 0 - submenu = 0 - if(linked_lathe) //Also sends salvaged materials to a linked protolathe, if any. - for(var/material in linked_destroy.loaded_item.materials) - var/can_insert = min(linked_lathe.materials.max_amount - linked_lathe.materials.total_amount, linked_destroy.loaded_item.materials[material] * (linked_destroy.decon_mod / 10), linked_destroy.loaded_item.materials[material]) - linked_lathe.materials.insert_amount(can_insert, material) - linked_destroy.loaded_item = null - else - menu = 0 - submenu = 0 - for(var/obj/I in linked_destroy.contents) - for(var/mob/M in I.contents) - M.death() - if(istype(I,/obj/item/stack/sheet))//Only deconsturcts one sheet at a time instead of the entire stack - var/obj/item/stack/sheet/S = I - if(S.amount > 1) - S.amount-- - linked_destroy.loaded_item = S - else - qdel(S) - linked_destroy.icon_state = "d_analyzer" - else - if(!(I in linked_destroy.component_parts)) - qdel(I) - linked_destroy.icon_state = "d_analyzer" - use_power(250) - SSnanoui.update_uis(src) - - else if(href_list["sync"]) //Sync the research holder with all the R&D consoles in the game that aren't sync protected. - if(!sync) - to_chat(usr, "You must connect to the network first!") - else - add_wait_message("Updating Database...", SYNC_RESEARCH_DELAY) - griefProtection() //Putting this here because I dont trust the sync process - spawn(SYNC_RESEARCH_DELAY) - clear_wait_message() - if(src) - for(var/obj/machinery/r_n_d/server/S in world) - var/server_processed = 0 - if(S.disabled) - continue - if((id in S.id_with_upload) || istype(S, /obj/machinery/r_n_d/server/centcom)) - files.push_data(S.files) - server_processed = 1 - if(((id in S.id_with_download) && !istype(S, /obj/machinery/r_n_d/server/centcom)) || S.hacked) - S.files.push_data(files) - server_processed = 1 - if(!istype(S, /obj/machinery/r_n_d/server/centcom) && server_processed) - S.produce_heat(100) - SSnanoui.update_uis(src) - - else if(href_list["togglesync"]) //Prevents the console from being synced by other consoles. Can still send data. - sync = !sync - - else if(href_list["build"]) //Causes the Protolathe to build something. - if(linked_lathe) - if(linked_lathe.busy) - to_chat(usr, "Protolathe is busy at the moment.") - return - var/coeff = linked_lathe.efficiency_coeff - var/g2g = 1 - var/datum/design/being_built = files.known_designs[href_list["build"]] - if(being_built) - var/power = 2000 - var/amount=text2num(href_list["amount"]) - if(being_built.make_reagents.len) - return 0 - amount = max(1, min(10, amount)) - for(var/M in being_built.materials) - power += round(being_built.materials[M] * amount / 5) - power = max(2000, power) - var/key = usr.key //so we don't lose the info during the spawn delay - if(!(being_built.build_type & PROTOLATHE)) - g2g = 0 - message_admins("Protolathe exploit attempted by [key_name(usr, TRUE)]!") - - if(g2g) //If input is incorrect, nothing happens - var/new_coeff = coeff * being_built.lathe_time_factor - var/time_to_construct = PROTOLATHE_CONSTRUCT_DELAY * new_coeff * amount ** 0.8 - var/enough_materials = 1 - - add_wait_message("Constructing Prototype. Please Wait...", time_to_construct) - linked_lathe.busy = 1 - flick("protolathe_n",linked_lathe) - use_power(power) - - var/list/efficient_mats = list() - for(var/MAT in being_built.materials) - efficient_mats[MAT] = being_built.materials[MAT]*coeff - - if(!linked_lathe.materials.has_materials(efficient_mats, amount)) - src.visible_message("The [src.name] beeps, \"Not enough materials to complete prototype.\"") - enough_materials = 0 - g2g = 0 - else - for(var/R in being_built.reagents_list) - if(!linked_lathe.reagents.has_reagent(R, being_built.reagents_list[R])*coeff) - src.visible_message("The [src.name] beeps, \"Not enough reagents to complete prototype.\"") - enough_materials = 0 - g2g = 0 - - if(enough_materials) - linked_lathe.materials.use_amount(efficient_mats, amount) - for(var/R in being_built.reagents_list) - linked_lathe.reagents.remove_reagent(R, being_built.reagents_list[R]*coeff) - - var/P = being_built.build_path //lets save these values before the spawn() just in case. Nobody likes runtimes. - var/O = being_built.locked - - spawn(time_to_construct) - if(g2g) //And if we only fail the material requirements, we still spend time and power - for(var/i = 0, iCircuit Imprinter is busy at the moment.
        ") - return - var/datum/design/being_built = null - being_built = files.known_designs[href_list["imprint"]] - if(being_built) - var/power = 2000 - for(var/M in being_built.materials) - power += round(being_built.materials[M] / 5) - power = max(2000, power) - if(!(being_built.build_type & IMPRINTER)) - g2g = 0 - message_admins("Circuit imprinter exploit attempted by [key_name(usr, TRUE)]!") - - if(g2g) //Again, if input is wrong, do nothing - add_wait_message("Imprinting Circuit. Please Wait...", IMPRINTER_DELAY) - linked_imprinter.busy = 1 - flick("circuit_imprinter_ani",linked_imprinter) - use_power(power) - - var/list/efficient_mats = list() - for(var/MAT in being_built.materials) - efficient_mats[MAT] = being_built.materials[MAT]/coeff - - if(!linked_imprinter.materials.has_materials(efficient_mats)) - visible_message("The [src.name] beeps, \"Not enough materials to complete prototype.\"") - enough_materials = 0 - g2g = 0 - else - for(var/R in being_built.reagents_list) - if(!linked_imprinter.reagents.has_reagent(R, being_built.reagents_list[R]/coeff)) - visible_message("The [name] beeps, \"Not enough reagents to complete prototype.\"") - enough_materials = 0 - g2g = 0 - - if(enough_materials) - linked_imprinter.materials.use_amount(efficient_mats) - for(var/R in being_built.reagents_list) - linked_imprinter.reagents.remove_reagent(R, being_built.reagents_list[R]/coeff) - - var/P = being_built.build_path //lets save these values before the spawn() just in case. Nobody likes runtimes. - spawn(IMPRINTER_DELAY) - if(g2g) - var/obj/item/new_item = new P(src) - new_item.loc = linked_imprinter.loc - linked_imprinter.busy = 0 - clear_wait_message() - SSnanoui.update_uis(src) - - else if(href_list["disposeI"] && linked_imprinter) //Causes the circuit imprinter to dispose of a single reagent (all of it) - linked_imprinter.reagents.del_reagent(href_list["disposeI"]) - - else if(href_list["disposeallI"] && linked_imprinter) //Causes the circuit imprinter to dispose of all it's reagents. - linked_imprinter.reagents.clear_reagents() - - else if(href_list["disposeP"] && linked_lathe) //Causes the protolathe to dispose of a single reagent (all of it) - linked_lathe.reagents.del_reagent(href_list["disposeP"]) - - else if(href_list["disposeallP"] && linked_lathe) //Causes the protolathe to dispose of all it's reagents. - linked_lathe.reagents.clear_reagents() - - else if(href_list["lathe_ejectsheet"] && linked_lathe) //Causes the protolathe to eject a sheet of material - var/desired_num_sheets - if(href_list["lathe_ejectsheet_amt"] == "custom") - desired_num_sheets = input("How many sheets would you like to eject from the machine?", "How much?", 1) as null|num - desired_num_sheets = max(0,desired_num_sheets) // If you input too high of a number, the mineral datum will take care of it either way - if(!desired_num_sheets) - return - desired_num_sheets = round(desired_num_sheets) // No partial-sheet goofery - else - desired_num_sheets = text2num(href_list["lathe_ejectsheet_amt"]) - linked_lathe.materials.retrieve_sheets(desired_num_sheets, href_list["lathe_ejectsheet"]) - - else if(href_list["imprinter_ejectsheet"] && linked_imprinter) //Causes the protolathe to eject a sheet of material - var/desired_num_sheets = text2num(href_list["imprinter_ejectsheet_amt"]) - if(href_list["imprinter_ejectsheet_amt"] == "custom") - desired_num_sheets = input("How many sheets would you like to eject from the machine?", "How much?", 1) as null|num - desired_num_sheets = max(0,desired_num_sheets) // for the imprinter they have something hacky, that still will guard against shenanigans. eh - if(!desired_num_sheets) - return - desired_num_sheets = round(desired_num_sheets) // No partial-sheet goofery - else - desired_num_sheets = text2num(href_list["imprinter_ejectsheet_amt"]) - linked_imprinter.materials.retrieve_sheets(desired_num_sheets, href_list["imprinter_ejectsheet"]) - - else if(href_list["find_device"]) //The R&D console looks for devices nearby to link up with. - add_wait_message("Updating Database...", SYNC_DEVICE_DELAY) - spawn(SYNC_DEVICE_DELAY) - SyncRDevices() - clear_wait_message() - SSnanoui.update_uis(src) - - else if(href_list["disconnect"]) //The R&D console disconnects with a specific device. - switch(href_list["disconnect"]) - if("destroy") - linked_destroy.linked_console = null - linked_destroy = null - if("lathe") - linked_lathe.linked_console = null - linked_lathe = null - if("imprinter") - linked_imprinter.linked_console = null - linked_imprinter = null - - else if(href_list["reset"]) //Reset the R&D console's database. - griefProtection() - var/choice = alert("Are you sure you want to reset the R&D console's database? Data lost cannot be recovered.", "R&D Console Database Reset", "Continue", "Cancel") - if(choice == "Continue") - add_wait_message("Updating Database...", RESET_RESEARCH_DELAY) - qdel(files) - files = new /datum/research(src) - spawn(RESET_RESEARCH_DELAY) - clear_wait_message() - SSnanoui.update_uis(src) - - else if(href_list["search"]) //Search for designs with name matching pattern - var/compare - - matching_designs.Cut() - - if(menu == 4) - compare = PROTOLATHE - else - compare = IMPRINTER - - for(var/v in files.known_designs) - var/datum/design/D = files.known_designs[v] - if(!(D.build_type & compare)) - continue - if(findtext(D.name,href_list["to_search"])) - matching_designs.Add(D) - submenu = 1 - - selected_category = "Search Results for '[href_list["to_search"]]'" - - SSnanoui.update_uis(src) - return - - -/obj/machinery/computer/rdconsole/attack_hand(mob/user as mob) - if(..()) - return 1 - if(!allowed(user) && !isobserver(user)) - to_chat(user, "Access denied.") - return 1 - ui_interact(user) - -/obj/machinery/computer/rdconsole/ui_interact(mob/user, ui_key="main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "r_n_d.tmpl", src.name, 800, 550) - ui.open() - -/obj/machinery/computer/rdconsole/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) - var/data[0] - - files.RefreshResearch() - - data["menu"] = menu - data["submenu"] = submenu - data["wait_message"] = wait_message - data["src_ref"] = UID() - - data["linked_destroy"] = linked_destroy ? 1 : 0 - data["linked_lathe"] = linked_lathe ? 1 : 0 - data["linked_imprinter"] = linked_imprinter ? 1 : 0 - data["sync"] = sync - data["admin"] = check_rights(R_ADMIN,0) - data["disk_type"] = d_disk ? 2 : (t_disk ? 1 : 0) - data["category"] = selected_category - - if(menu == 0 || menu == 1) - var/list/tech_levels = list() - data["tech_levels"] = tech_levels - for(var/v in files.known_tech) - var/datum/tech/T = files.known_tech[v] - if(T.level <= 0) - continue - var/list/this_tech_list = list() - this_tech_list["name"] = T.name - this_tech_list["level"] = T.level - this_tech_list["desc"] = T.desc - tech_levels[++tech_levels.len] = this_tech_list - - if(menu == 2) - - if(t_disk != null && t_disk.stored != null && submenu == 0) //Technology Disk Menu - var/list/disk_data = list() - data["disk_data"] = disk_data - disk_data["name"] = t_disk.stored.name - disk_data["level"] = t_disk.stored.level - disk_data["desc"] = t_disk.stored.desc - - if(t_disk != null && submenu == 1) - var/list/to_copy = list() - data["to_copy"] = to_copy - for(var/v in files.known_tech) - var/datum/tech/T = files.known_tech[v] - var/list/item = list() - to_copy[++to_copy.len] = item - if(T.level <= 0) - continue - item["name"] = T.name - item["id"] = T.id - - if(d_disk != null && d_disk.blueprint != null && submenu == 0) - var/list/disk_data = list() - data["disk_data"] = disk_data - disk_data["name"] = d_disk.blueprint.name - var/b_type = d_disk.blueprint.build_type - var/list/lathe_types = list() - disk_data["lathe_types"] = lathe_types - if(b_type) - if(b_type & IMPRINTER) lathe_types += "Circuit Imprinter" - if(b_type & PROTOLATHE) lathe_types += "Protolathe" - if(b_type & AUTOLATHE) lathe_types += "Autolathe" - if(b_type & MECHFAB) lathe_types += "Mech Fabricator" - if(b_type & PODFAB) lathe_types += "Spacepod Fabricator" - if(b_type & BIOGENERATOR) lathe_types += "Biogenerator" - if(b_type & SMELTER) lathe_types += "Smelter" - var/list/materials = list() - disk_data["materials"] = materials - for(var/M in d_disk.blueprint.materials) - var/list/material = list() - materials[++materials.len] = material - material["name"] = CallMaterialName(M) - material["amount"] = d_disk.blueprint.materials[M] - - if(d_disk != null && submenu == 1) - var/list/to_copy = list() - data["to_copy"] = to_copy - for(var/v in files.known_designs) - var/datum/design/D = files.known_designs[v] - var/list/item = list() - to_copy[++to_copy.len] = item - item["name"] = D.name - item["id"] = D.id - - if(menu == 3 && linked_destroy && linked_destroy.loaded_item) - var/list/loaded_item_list = list() - data["loaded_item"] = loaded_item_list - loaded_item_list["name"] = linked_destroy.loaded_item.name - var/list/temp_tech = linked_destroy.ConvertReqString2List(linked_destroy.loaded_item.origin_tech) - var/list/tech_list = list() - loaded_item_list["origin_tech"] = tech_list - for(var/T in temp_tech) - var/list/tech_item = list() - tech_list[++tech_list.len] = tech_item - tech_item["name"] = CallTechName(T) - tech_item["object_level"] = temp_tech[T] - for(var/v in files.known_tech) - var/datum/tech/F = files.known_tech[v] - if(F.name == CallTechName(T)) - tech_item["current_level"] = F.level - break - - if(menu == 4 && linked_lathe) - data["total_materials"] = linked_lathe.materials.total_amount - data["max_materials"] = linked_lathe.materials.max_amount - data["total_chemicals"] = linked_lathe.reagents.total_volume - data["max_chemicals"] = linked_lathe.reagents.maximum_volume - data["categories"] = linked_lathe.categories - if(submenu == 1) - var/list/designs_list = list() - data["matching_designs"] = designs_list - var/coeff = linked_lathe.efficiency_coeff - for(var/datum/design/D in matching_designs) - var/list/design_list = list() - designs_list[++designs_list.len] = design_list - var/list/materials_list = list() - design_list["materials"] = materials_list - design_list["id"] = D.id - design_list["name"] = sanitize(D.name) - var/c = 50 - for(var/M in D.materials) - var/list/material_list = list() - materials_list[++materials_list.len] = material_list - material_list["name"] = CallMaterialName(M) - material_list["amount"] = D.materials[M]*coeff - var/t = linked_lathe.check_mat(D, M) - - if(t < 1) - material_list["is_red"] = 1 - else - material_list["is_red"] = 0 - c = min(c, t) - - for(var/R in D.reagents_list) - var/list/material_list = list() - materials_list[++materials_list.len] = material_list - material_list["name"] = CallMaterialName(R) - material_list["amount"] = D.reagents_list[R]*coeff - var/t = linked_lathe.check_mat(D, R) - - if(t < 1) - material_list["is_red"] = 1 - else - material_list["is_red"] = 0 - c = min(c, t) - design_list["can_build"] = c - if(submenu == 2) - var/list/materials_list = list() - data["loaded_materials"] = materials_list - materials_list[++materials_list.len] = list("name" = "Metal", "id" = MAT_METAL, "amount" = linked_lathe.materials.amount(MAT_METAL)) - materials_list[++materials_list.len] = list("name" = "Glass", "id" = MAT_GLASS, "amount" = linked_lathe.materials.amount(MAT_GLASS)) - materials_list[++materials_list.len] = list("name" = "Gold", "id" = MAT_GOLD, "amount" = linked_lathe.materials.amount(MAT_GOLD)) - materials_list[++materials_list.len] = list("name" = "Silver", "id" = MAT_SILVER, "amount" = linked_lathe.materials.amount(MAT_SILVER)) - materials_list[++materials_list.len] = list("name" = "Solid Plasma", "id" = MAT_PLASMA, "amount" = linked_lathe.materials.amount(MAT_PLASMA)) - materials_list[++materials_list.len] = list("name" = "Uranium", "id" = MAT_URANIUM, "amount" = linked_lathe.materials.amount(MAT_URANIUM)) - materials_list[++materials_list.len] = list("name" = "Diamond", "id" = MAT_DIAMOND, "amount" = linked_lathe.materials.amount(MAT_DIAMOND)) - materials_list[++materials_list.len] = list("name" = "Bananium", "id" = MAT_BANANIUM, "amount" = linked_lathe.materials.amount(MAT_BANANIUM)) - materials_list[++materials_list.len] = list("name" = "Tranquillite", "id" = MAT_TRANQUILLITE, "amount" = linked_lathe.materials.amount(MAT_TRANQUILLITE)) - materials_list[++materials_list.len] = list("name" = "Titanium", "id" = MAT_TITANIUM, "amount" = linked_lathe.materials.amount(MAT_TITANIUM)) - materials_list[++materials_list.len] = list("name" = "Plastic", "id" = MAT_PLASTIC, "amount" = linked_lathe.materials.amount(MAT_PLASTIC)) - materials_list[++materials_list.len] = list("name" = "Bluespace Mesh", "id" = MAT_BLUESPACE, "amount" = linked_lathe.materials.amount(MAT_BLUESPACE)) - if(submenu == 3) - var/list/loaded_chemicals = list() - data["loaded_chemicals"] = loaded_chemicals - for(var/datum/reagent/R in linked_lathe.reagents.reagent_list) - var/list/loaded_chemical = list() - loaded_chemicals[++loaded_chemicals.len] = loaded_chemical - loaded_chemical["name"] = R.name - loaded_chemical["volume"] = R.volume - loaded_chemical["id"] = R.id - - if(menu == 5 && linked_imprinter) - data["total_materials"] = linked_imprinter.materials.total_amount - data["max_materials"] = linked_imprinter.materials.max_amount - data["total_chemicals"] = linked_imprinter.reagents.total_volume - data["max_chemicals"] = linked_imprinter.reagents.maximum_volume - data["categories"] = linked_imprinter.categories - if(submenu == 1) - var/list/designs_list = list() - data["matching_designs"] = designs_list - var/coeff = linked_imprinter.efficiency_coeff - for(var/datum/design/D in matching_designs) - var/list/design_list = list() - designs_list[++designs_list.len] = design_list - var/list/materials_list = list() - design_list["materials"] = materials_list - design_list["id"] = D.id - design_list["name"] = sanitize(D.name) - var/check_materials = 1 - for(var/M in D.materials) - var/list/material_list = list() - materials_list[++materials_list.len] = material_list - material_list["name"] = CallMaterialName(M) - material_list["amount"] = D.materials[M] / coeff - if(!linked_imprinter.check_mat(D, M)) - check_materials = 0 - material_list["is_red"] = 1 - else - material_list["is_red"] = 0 - - for(var/R in D.reagents_list) - var/list/material_list = list() - materials_list[++materials_list.len] = material_list - material_list["name"] = CallMaterialName(R) - material_list["amount"] = D.reagents_list[R]*coeff - if(!linked_imprinter.check_mat(D, R)) - check_materials = 0 - material_list["is_red"] = 1 - else - material_list["is_red"] = 0 - - design_list["can_build"] = check_materials - if(submenu == 2) - var/list/materials_list = list() - data["loaded_materials"] = materials_list - materials_list[++materials_list.len] = list("name" = "Metal", "id" = MAT_METAL, "amount" = linked_imprinter.materials.amount(MAT_METAL)) - materials_list[++materials_list.len] = list("name" = "Glass", "id" = MAT_GLASS, "amount" = linked_imprinter.materials.amount(MAT_GLASS)) - materials_list[++materials_list.len] = list("name" = "Gold", "id" = MAT_GOLD, "amount" = linked_imprinter.materials.amount(MAT_GOLD)) - materials_list[++materials_list.len] = list("name" = "Silver", "id" = MAT_SILVER, "amount" = linked_imprinter.materials.amount(MAT_SILVER)) - materials_list[++materials_list.len] = list("name" = "Solid Plasma", "id" = MAT_PLASMA, "amount" = linked_imprinter.materials.amount(MAT_PLASMA)) - materials_list[++materials_list.len] = list("name" = "Uranium", "id" = MAT_URANIUM, "amount" = linked_imprinter.materials.amount(MAT_URANIUM)) - materials_list[++materials_list.len] = list("name" = "Diamond", "id" = MAT_DIAMOND, "amount" = linked_imprinter.materials.amount(MAT_DIAMOND)) - materials_list[++materials_list.len] = list("name" = "Bananium", "id" = MAT_BANANIUM, "amount" = linked_imprinter.materials.amount(MAT_BANANIUM)) - materials_list[++materials_list.len] = list("name" = "Tranquillite", "id" = MAT_TRANQUILLITE, "amount" = linked_imprinter.materials.amount(MAT_TRANQUILLITE)) - materials_list[++materials_list.len] = list("name" = "Titanium", "id" = MAT_TITANIUM, "amount" = linked_imprinter.materials.amount(MAT_TITANIUM)) - materials_list[++materials_list.len] = list("name" = "Plastic", "id" = MAT_PLASTIC, "amount" = linked_imprinter.materials.amount(MAT_PLASTIC)) - materials_list[++materials_list.len] = list("name" = "Bluespace Mesh", "id" = MAT_BLUESPACE, "amount" = linked_imprinter.materials.amount(MAT_BLUESPACE)) - if(submenu == 3) - var/list/loaded_chemicals = list() - data["loaded_chemicals"] = loaded_chemicals - for(var/datum/reagent/R in linked_imprinter.reagents.reagent_list) - var/list/loaded_chemical = list() - loaded_chemicals[++loaded_chemicals.len] = loaded_chemical - loaded_chemical["name"] = R.name - loaded_chemical["volume"] = R.volume - loaded_chemical["id"] = R.id - - return data - -//helper proc that guarantees the wait message will not freeze the UI -/obj/machinery/computer/rdconsole/proc/add_wait_message(message, delay) - wait_message = message - wait_message_timer = addtimer(CALLBACK(src, .proc/clear_wait_message), delay, TIMER_UNIQUE | TIMER_STOPPABLE) - -// This is here to guarantee that we never lock the console, so long as the timer -// process is running -// So long as the spawns never runtime, though, the timer should be stopped -// before it gets the chance to fire -// Since the timer process can have significant delays, you should call this -// in the operations that take time, once they complete -/obj/machinery/computer/rdconsole/proc/clear_wait_message() - wait_message = "" - if(wait_message_timer) - // This could be expensive, and will still be called - // if the timer calls this function - deltimer(wait_message_timer) - wait_message_timer = 0 - SSnanoui.update_uis(src) - - -/obj/machinery/computer/rdconsole/core - name = "core R&D console" - desc = "A console used to interface with R&D tools." - id = 1 - -/obj/machinery/computer/rdconsole/robotics - name = "robotics R&D console" - desc = "A console used to interface with R&D tools." - id = 2 - req_access = list(ACCESS_ROBOTICS) - circuit = /obj/item/circuitboard/rdconsole/robotics - -/obj/machinery/computer/rdconsole/experiment - name = "\improper E.X.P.E.R.I-MENTOR R&D console" - desc = "A console used to interface with R&D tools." - id = 3 - circuit = /obj/item/circuitboard/rdconsole/experiment - -/obj/machinery/computer/rdconsole/mechanics - name = "mechanics R&D console" - desc = "A console used to interface with R&D tools." - id = 4 - req_access = list(ACCESS_MECHANIC) - circuit = /obj/item/circuitboard/rdconsole/mechanics - -/obj/machinery/computer/rdconsole/public - name = "public R&D console" - desc = "A console used to interface with R&D tools." - id = 5 - req_access = list() - circuit = /obj/item/circuitboard/rdconsole/public - -#undef TECH_UPDATE_DELAY -#undef DESIGN_UPDATE_DELAY -#undef PROTOLATHE_CONSTRUCT_DELAY -#undef SYNC_RESEARCH_DELAY -#undef DECONSTRUCT_DELAY -#undef SYNC_DEVICE_DELAY -#undef RESET_RESEARCH_DELAY -#undef IMPRINTER_DELAY +/* +Research and Development (R&D) Console + +This is the main work horse of the R&D system. It contains the menus/controls for the Destructive Analyzer, Protolathe, and Circuit +imprinter. It also contains the /datum/research holder with all the known/possible technology paths and device designs. + +Basic use: When it first is created, it will attempt to link up to related devices within 3 squares. It'll only link up if they +aren't already linked to another console. Any consoles it cannot link up with (either because all of a certain type are already +linked or there aren't any in range), you'll just not have access to that menu. In the settings menu, there are menu options that +allow a player to attempt to re-sync with nearby consoles. You can also force it to disconnect from a specific console. + +The imprinting and construction menus do NOT require toxins access to access but all the other menus do. However, if you leave it +on a menu, nothing is to stop the person from using the options on that menu (although they won't be able to change to a different +one). You can also lock the console on the settings menu if you're feeling paranoid and you don't want anyone messing with it who +doesn't have toxins access. + +When a R&D console is destroyed or even partially disassembled, you lose all research data on it. However, there are two ways around +this dire fate: +- The easiest way is to go to the settings menu and select "Sync Database with Network." That causes it to upload (but not download) +it's data to every other device in the game. Each console has a "disconnect from network" option that'll will cause data base sync +operations to skip that console. This is useful if you want to make a "public" R&D console or, for example, give the engineers +a circuit imprinter with certain designs on it and don't want it accidentally updating. The downside of this method is that you have +to have physical access to the other console to send data back. Note: An R&D console is on Centcom so if a random griffan happens to +cause a ton of data to be lost, an admin can go send it back. +- The second method is with Technology Disks and Design Disks. Each of these disks can hold a single technology or design datum in +it's entirety. You can then take the disk to any R&D console and upload it's data to it. This method is a lot more secure (since it +won't update every console in existence) but it's more of a hassle to do. Also, the disks can be stolen. + + +*/ + +// Who likes #defines? +// I don't! +// but I gotta add 'em anyways because we have a bias against /const statements for some reason +#define TECH_UPDATE_DELAY 50 +#define DESIGN_UPDATE_DELAY 50 +#define PROTOLATHE_CONSTRUCT_DELAY 32 +#define SYNC_RESEARCH_DELAY 30 +#define DECONSTRUCT_DELAY 24 +#define SYNC_DEVICE_DELAY 20 +#define RESET_RESEARCH_DELAY 20 +#define IMPRINTER_DELAY 16 + +/obj/machinery/computer/rdconsole + name = "\improper R&D console" + icon_screen = "rdcomp" + icon_keyboard = "rd_key" + light_color = LIGHT_COLOR_FADEDPURPLE + circuit = /obj/item/circuitboard/rdconsole + var/datum/research/files //Stores all the collected research data. + var/obj/item/disk/tech_disk/t_disk = null //Stores the technology disk. + var/obj/item/disk/design_disk/d_disk = null //Stores the design disk. + + var/obj/machinery/r_n_d/destructive_analyzer/linked_destroy = null //Linked Destructive Analyzer + var/obj/machinery/r_n_d/protolathe/linked_lathe = null //Linked Protolathe + var/obj/machinery/r_n_d/circuit_imprinter/linked_imprinter = null //Linked Circuit Imprinter + + var/screen = 1.0 //Which screen is currently showing. + + var/menu = 0 // Current menu. + var/submenu = 0 + var/wait_message = 0 + var/wait_message_timer = 0 + + var/id = 0 //ID of the computer (for server restrictions). + var/sync = 1 //If sync = 0, it doesn't show up on Server Control Console + + req_access = list(ACCESS_TOX) //Data and setting manipulation requires scientist access. + + var/selected_category + var/list/datum/design/matching_designs = list() //for the search function + +/proc/CallTechName(ID) //A simple helper proc to find the name of a tech with a given ID. + for(var/T in subtypesof(/datum/tech)) + var/datum/tech/tt = T + if(initial(tt.id) == ID) + return initial(tt.name) + +/proc/CallMaterialName(ID) + if(copytext(ID, 1, 2) == "$") + var/return_name = copytext(ID, 2) + switch(return_name) + if("metal") + return_name = "Metal" + if("glass") + return_name = "Glass" + if("gold") + return_name = "Gold" + if("silver") + return_name = "Silver" + if("plasma") + return_name = "Solid Plasma" + if("uranium") + return_name = "Uranium" + if("diamond") + return_name = "Diamond" + if("clown") + return_name = "Bananium" + if("mime") + return_name = "Tranquillite" + if("titanium") + return_name = "Titanium" + if("bluespace") + return_name = "Bluespace Mesh" + if("plastic") + return_name = "Plastic" + return return_name + else + for(var/R in subtypesof(/datum/reagent)) + var/datum/reagent/rt = R + if(initial(rt.id) == ID) + return initial(rt.name) + +/obj/machinery/computer/rdconsole/proc/SyncRDevices() //Makes sure it is properly sync'ed up with the devices attached to it (if any). + for(var/obj/machinery/r_n_d/D in range(3,src)) + if(!isnull(D.linked_console) || D.disabled || D.panel_open) + continue + if(istype(D, /obj/machinery/r_n_d/destructive_analyzer)) + if(linked_destroy == null) + linked_destroy = D + D.linked_console = src + else if(istype(D, /obj/machinery/r_n_d/protolathe)) + if(linked_lathe == null) + linked_lathe = D + D.linked_console = src + else if(istype(D, /obj/machinery/r_n_d/circuit_imprinter)) + if(linked_imprinter == null) + linked_imprinter = D + D.linked_console = src + return + +//Have it automatically push research to the centcom server so wild griffins can't fuck up R&D's work --NEO +/obj/machinery/computer/rdconsole/proc/griefProtection() + for(var/obj/machinery/r_n_d/server/centcom/C in world) + files.push_data(C.files) + +/obj/machinery/computer/rdconsole/proc/Maximize() + for(var/datum/tech/T in files.possible_tech) + files.known_tech[T.id] = T + for(var/v in files.known_tech) + var/datum/tech/KT = files.known_tech[v] + if(KT.level < KT.max_level) + KT.level=KT.max_level + files.RefreshResearch() + +/obj/machinery/computer/rdconsole/New() + ..() + files = new /datum/research(src) //Setup the research data holder. + matching_designs = list() + if(!id) + for(var/obj/machinery/r_n_d/server/centcom/S in world) + S.initialize_serv() + break + +/obj/machinery/computer/rdconsole/Initialize() + ..() + SyncRDevices() + +/obj/machinery/computer/rdconsole/Destroy() + if(wait_message_timer) + deltimer(wait_message_timer) + wait_message_timer = 0 + return ..() + +/* Instead of calling this every tick, it is only being called when needed +/obj/machinery/computer/rdconsole/process() + griefProtection() +*/ + +/obj/machinery/computer/rdconsole/attackby(var/obj/item/D as obj, var/mob/user as mob, params) + + //Loading a disk into it. + if(istype(D, /obj/item/disk)) + if(t_disk || d_disk) + to_chat(user, "A disk is already loaded into the machine.") + return + + if(istype(D, /obj/item/disk/tech_disk)) t_disk = D + else if(istype(D, /obj/item/disk/design_disk)) d_disk = D + else + to_chat(user, "Machine cannot accept disks in that format.") + return + if(!user.drop_item()) + return + D.loc = src + to_chat(user, "You add the disk to the machine!") + else if(!(linked_destroy && linked_destroy.busy) && !(linked_lathe && linked_lathe.busy) && !(linked_imprinter && linked_imprinter.busy)) + ..() + SSnanoui.update_uis(src) + return + +/obj/machinery/computer/rdconsole/emag_act(user as mob) + if(!emagged) + playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1) + req_access = list() + emagged = 1 + to_chat(user, "You disable the security protocols") + +/obj/machinery/computer/rdconsole/Topic(href, href_list) + if(..()) + return 1 + + if(!allowed(usr) && !isobserver(usr)) + return 1 + + add_fingerprint(usr) + + usr.set_machine(src) + if(href_list["menu"]) //Switches menu screens. Converts a sent text string into a number. Saves a LOT of code. + var/temp_screen = text2num(href_list["menu"]) + menu = temp_screen + if(href_list["submenu"]) //Switches menu screens. Converts a sent text string into a number. Saves a LOT of code. + var/temp_screen = text2num(href_list["submenu"]) + submenu = temp_screen + + if(href_list["category"]) + var/compare + + matching_designs.Cut() + + if(menu == 4) + compare = PROTOLATHE + else + compare = IMPRINTER + + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(!(D.build_type & compare)) + continue + if(href_list["category"] in D.category) + matching_designs.Add(D) + submenu = 1 + + selected_category = "Viewing Category [href_list["category"]]" + + else if(href_list["updt_tech"]) //Update the research holder with information from the technology disk. + add_wait_message("Updating Database...", TECH_UPDATE_DELAY) + spawn(TECH_UPDATE_DELAY) + clear_wait_message() + files.AddTech2Known(t_disk.stored) + SSnanoui.update_uis(src) + griefProtection() //Update centcom too + + else if(href_list["clear_tech"]) //Erase data on the technology disk. + if(t_disk) + t_disk.wipe_tech() + + else if(href_list["eject_tech"]) //Eject the technology disk. + if(t_disk) + t_disk.loc = src.loc + t_disk = null + menu = 0 + submenu = 0 + + else if(href_list["copy_tech"]) //Copy some technology data from the research holder to the disk. + // Somehow this href makes me very nervous + t_disk.stored = files.known_tech[href_list["copy_tech_ID"]] + menu = 2 + submenu = 0 + + else if(href_list["updt_design"]) //Updates the research holder with design data from the design disk. + add_wait_message("Updating Database...", DESIGN_UPDATE_DELAY) + spawn(DESIGN_UPDATE_DELAY) + clear_wait_message() + files.AddDesign2Known(d_disk.blueprint) + SSnanoui.update_uis(src) + griefProtection() //Update centcom too + + else if(href_list["clear_design"]) //Erases data on the design disk. + if(d_disk) + d_disk.wipe_blueprint() + + else if(href_list["eject_design"]) //Eject the design disk. + if(d_disk) + d_disk.loc = src.loc + d_disk = null + menu = 0 + submenu = 0 + + else if(href_list["copy_design"]) //Copy design data from the research holder to the design disk. + // This href ALSO makes me very nervous + var/datum/design/D = files.known_designs[href_list["copy_design_ID"]] + if(D) + // eeeeeep design datums are global be careful! + var/autolathe_friendly = 1 + for(var/x in D.materials) + if( !(x in list(MAT_METAL, MAT_GLASS))) + autolathe_friendly = 0 + D.category -= "Imported" + if(D.locked) + autolathe_friendly = 0 + D.category -= "Imported" + if(D.build_type & (AUTOLATHE|PROTOLATHE|CRAFTLATHE)) // Specifically excludes circuit imprinter and mechfab + D.build_type = autolathe_friendly ? (D.build_type | AUTOLATHE) : D.build_type + D.category |= "Imported" + d_disk.blueprint = D + menu = 2 + submenu = 0 + + else if(href_list["eject_item"]) //Eject the item inside the destructive analyzer. + if(linked_destroy) + if(linked_destroy.busy) + to_chat(usr, " The destructive analyzer is busy at the moment.") + + else if(linked_destroy.loaded_item) + linked_destroy.loaded_item.loc = linked_destroy.loc + linked_destroy.loaded_item = null + linked_destroy.icon_state = "d_analyzer" + menu = 3 + + else if(href_list["maxresearch"]) //Eject the item inside the destructive analyzer. + if(!check_rights(R_ADMIN)) + return + if(alert("Are you sure you want to maximize research levels?","Confirmation","Yes","No")=="No") + return + log_admin("[key_name(usr)] has maximized the research levels.") + message_admins("[key_name_admin(usr)] has maximized the research levels.") + Maximize() + SSnanoui.update_uis(src) + griefProtection() //Update centcomm too + + else if(href_list["deconstruct"]) //Deconstruct the item in the destructive analyzer and update the research holder. + if(linked_destroy) + if(linked_destroy.busy) + to_chat(usr, "The destructive analyzer is busy at the moment.") + return + var/list/temp_tech = linked_destroy.ConvertReqString2List(linked_destroy.loaded_item.origin_tech) + var/cancontinue = FALSE + for(var/T in temp_tech) + if(files.IsTechHigher(T, temp_tech[T])) + cancontinue = TRUE + break + if(!cancontinue) + var/choice = input("This item does not raise tech levels. Proceed destroying loaded item anyway?") in list("Proceed", "Cancel") + if(choice == "Cancel" || !linked_destroy) + return + linked_destroy.busy = 1 + add_wait_message("Processing and Updating Database...", DECONSTRUCT_DELAY) + SSnanoui.update_uis(src) + flick("d_analyzer_process", linked_destroy) + spawn(DECONSTRUCT_DELAY) + clear_wait_message() + if(linked_destroy) + linked_destroy.busy = 0 + if(!linked_destroy.hacked) + if(!linked_destroy.loaded_item) + to_chat(usr, "The destructive analyzer appears to be empty.") + menu = 0 + submenu = 0 + return + for(var/T in temp_tech) + var/datum/tech/KT = files.known_tech[T] //For stat logging of high levels + if(files.IsTechHigher(T, temp_tech[T]) && KT.level >= 5) //For stat logging of high levels + feedback_add_details("high_research_level","[KT][KT.level + 1]") //+1 to show the level which we're about to get + files.UpdateTech(T, temp_tech[T]) + menu = 0 + submenu = 0 + if(linked_lathe) //Also sends salvaged materials to a linked protolathe, if any. + for(var/material in linked_destroy.loaded_item.materials) + var/can_insert = min(linked_lathe.materials.max_amount - linked_lathe.materials.total_amount, linked_destroy.loaded_item.materials[material] * (linked_destroy.decon_mod / 10), linked_destroy.loaded_item.materials[material]) + linked_lathe.materials.insert_amount(can_insert, material) + linked_destroy.loaded_item = null + else + menu = 0 + submenu = 0 + for(var/obj/I in linked_destroy.contents) + for(var/mob/M in I.contents) + M.death() + if(istype(I,/obj/item/stack/sheet))//Only deconsturcts one sheet at a time instead of the entire stack + var/obj/item/stack/sheet/S = I + if(S.amount > 1) + S.amount-- + linked_destroy.loaded_item = S + else + qdel(S) + linked_destroy.icon_state = "d_analyzer" + else + if(!(I in linked_destroy.component_parts)) + qdel(I) + linked_destroy.icon_state = "d_analyzer" + use_power(250) + SSnanoui.update_uis(src) + + else if(href_list["sync"]) //Sync the research holder with all the R&D consoles in the game that aren't sync protected. + if(!sync) + to_chat(usr, "You must connect to the network first!") + else + add_wait_message("Updating Database...", SYNC_RESEARCH_DELAY) + griefProtection() //Putting this here because I dont trust the sync process + spawn(SYNC_RESEARCH_DELAY) + clear_wait_message() + if(src) + for(var/obj/machinery/r_n_d/server/S in world) + var/server_processed = 0 + if(S.disabled) + continue + if((id in S.id_with_upload) || istype(S, /obj/machinery/r_n_d/server/centcom)) + files.push_data(S.files) + server_processed = 1 + if(((id in S.id_with_download) && !istype(S, /obj/machinery/r_n_d/server/centcom)) || S.hacked) + S.files.push_data(files) + server_processed = 1 + if(!istype(S, /obj/machinery/r_n_d/server/centcom) && server_processed) + S.produce_heat(100) + SSnanoui.update_uis(src) + + else if(href_list["togglesync"]) //Prevents the console from being synced by other consoles. Can still send data. + sync = !sync + + else if(href_list["build"]) //Causes the Protolathe to build something. + if(linked_lathe) + if(linked_lathe.busy) + to_chat(usr, "Protolathe is busy at the moment.") + return + var/coeff = linked_lathe.efficiency_coeff + var/g2g = 1 + var/datum/design/being_built = files.known_designs[href_list["build"]] + if(being_built) + var/power = 2000 + var/amount=text2num(href_list["amount"]) + if(being_built.make_reagents.len) + return 0 + amount = max(1, min(10, amount)) + for(var/M in being_built.materials) + power += round(being_built.materials[M] * amount / 5) + power = max(2000, power) + var/key = usr.key //so we don't lose the info during the spawn delay + if(!(being_built.build_type & PROTOLATHE)) + g2g = 0 + message_admins("Protolathe exploit attempted by [key_name(usr, TRUE)]!") + + if(g2g) //If input is incorrect, nothing happens + var/new_coeff = coeff * being_built.lathe_time_factor + var/time_to_construct = PROTOLATHE_CONSTRUCT_DELAY * new_coeff * amount ** 0.8 + var/enough_materials = 1 + + add_wait_message("Constructing Prototype. Please Wait...", time_to_construct) + linked_lathe.busy = 1 + flick("protolathe_n",linked_lathe) + use_power(power) + + var/list/efficient_mats = list() + for(var/MAT in being_built.materials) + efficient_mats[MAT] = being_built.materials[MAT]*coeff + + if(!linked_lathe.materials.has_materials(efficient_mats, amount)) + src.visible_message("The [src.name] beeps, \"Not enough materials to complete prototype.\"") + enough_materials = 0 + g2g = 0 + else + for(var/R in being_built.reagents_list) + if(!linked_lathe.reagents.has_reagent(R, being_built.reagents_list[R])*coeff) + src.visible_message("The [src.name] beeps, \"Not enough reagents to complete prototype.\"") + enough_materials = 0 + g2g = 0 + + if(enough_materials) + linked_lathe.materials.use_amount(efficient_mats, amount) + for(var/R in being_built.reagents_list) + linked_lathe.reagents.remove_reagent(R, being_built.reagents_list[R]*coeff) + + var/P = being_built.build_path //lets save these values before the spawn() just in case. Nobody likes runtimes. + var/O = being_built.locked + + spawn(time_to_construct) + if(g2g) //And if we only fail the material requirements, we still spend time and power + for(var/i = 0, iCircuit Imprinter is busy at the moment.
        ") + return + var/datum/design/being_built = null + being_built = files.known_designs[href_list["imprint"]] + if(being_built) + var/power = 2000 + for(var/M in being_built.materials) + power += round(being_built.materials[M] / 5) + power = max(2000, power) + if(!(being_built.build_type & IMPRINTER)) + g2g = 0 + message_admins("Circuit imprinter exploit attempted by [key_name(usr, TRUE)]!") + + if(g2g) //Again, if input is wrong, do nothing + add_wait_message("Imprinting Circuit. Please Wait...", IMPRINTER_DELAY) + linked_imprinter.busy = 1 + flick("circuit_imprinter_ani",linked_imprinter) + use_power(power) + + var/list/efficient_mats = list() + for(var/MAT in being_built.materials) + efficient_mats[MAT] = being_built.materials[MAT]/coeff + + if(!linked_imprinter.materials.has_materials(efficient_mats)) + visible_message("The [src.name] beeps, \"Not enough materials to complete prototype.\"") + enough_materials = 0 + g2g = 0 + else + for(var/R in being_built.reagents_list) + if(!linked_imprinter.reagents.has_reagent(R, being_built.reagents_list[R]/coeff)) + visible_message("The [name] beeps, \"Not enough reagents to complete prototype.\"") + enough_materials = 0 + g2g = 0 + + if(enough_materials) + linked_imprinter.materials.use_amount(efficient_mats) + for(var/R in being_built.reagents_list) + linked_imprinter.reagents.remove_reagent(R, being_built.reagents_list[R]/coeff) + + var/P = being_built.build_path //lets save these values before the spawn() just in case. Nobody likes runtimes. + spawn(IMPRINTER_DELAY) + if(g2g) + var/obj/item/new_item = new P(src) + new_item.loc = linked_imprinter.loc + linked_imprinter.busy = 0 + clear_wait_message() + SSnanoui.update_uis(src) + + else if(href_list["disposeI"] && linked_imprinter) //Causes the circuit imprinter to dispose of a single reagent (all of it) + linked_imprinter.reagents.del_reagent(href_list["disposeI"]) + + else if(href_list["disposeallI"] && linked_imprinter) //Causes the circuit imprinter to dispose of all it's reagents. + linked_imprinter.reagents.clear_reagents() + + else if(href_list["disposeP"] && linked_lathe) //Causes the protolathe to dispose of a single reagent (all of it) + linked_lathe.reagents.del_reagent(href_list["disposeP"]) + + else if(href_list["disposeallP"] && linked_lathe) //Causes the protolathe to dispose of all it's reagents. + linked_lathe.reagents.clear_reagents() + + else if(href_list["lathe_ejectsheet"] && linked_lathe) //Causes the protolathe to eject a sheet of material + var/desired_num_sheets + if(href_list["lathe_ejectsheet_amt"] == "custom") + desired_num_sheets = input("How many sheets would you like to eject from the machine?", "How much?", 1) as null|num + desired_num_sheets = max(0,desired_num_sheets) // If you input too high of a number, the mineral datum will take care of it either way + if(!desired_num_sheets) + return + desired_num_sheets = round(desired_num_sheets) // No partial-sheet goofery + else + desired_num_sheets = text2num(href_list["lathe_ejectsheet_amt"]) + linked_lathe.materials.retrieve_sheets(desired_num_sheets, href_list["lathe_ejectsheet"]) + + else if(href_list["imprinter_ejectsheet"] && linked_imprinter) //Causes the protolathe to eject a sheet of material + var/desired_num_sheets = text2num(href_list["imprinter_ejectsheet_amt"]) + if(href_list["imprinter_ejectsheet_amt"] == "custom") + desired_num_sheets = input("How many sheets would you like to eject from the machine?", "How much?", 1) as null|num + desired_num_sheets = max(0,desired_num_sheets) // for the imprinter they have something hacky, that still will guard against shenanigans. eh + if(!desired_num_sheets) + return + desired_num_sheets = round(desired_num_sheets) // No partial-sheet goofery + else + desired_num_sheets = text2num(href_list["imprinter_ejectsheet_amt"]) + linked_imprinter.materials.retrieve_sheets(desired_num_sheets, href_list["imprinter_ejectsheet"]) + + else if(href_list["find_device"]) //The R&D console looks for devices nearby to link up with. + add_wait_message("Updating Database...", SYNC_DEVICE_DELAY) + spawn(SYNC_DEVICE_DELAY) + SyncRDevices() + clear_wait_message() + SSnanoui.update_uis(src) + + else if(href_list["disconnect"]) //The R&D console disconnects with a specific device. + switch(href_list["disconnect"]) + if("destroy") + linked_destroy.linked_console = null + linked_destroy = null + if("lathe") + linked_lathe.linked_console = null + linked_lathe = null + if("imprinter") + linked_imprinter.linked_console = null + linked_imprinter = null + + else if(href_list["reset"]) //Reset the R&D console's database. + griefProtection() + var/choice = alert("Are you sure you want to reset the R&D console's database? Data lost cannot be recovered.", "R&D Console Database Reset", "Continue", "Cancel") + if(choice == "Continue") + add_wait_message("Updating Database...", RESET_RESEARCH_DELAY) + qdel(files) + files = new /datum/research(src) + spawn(RESET_RESEARCH_DELAY) + clear_wait_message() + SSnanoui.update_uis(src) + + else if(href_list["search"]) //Search for designs with name matching pattern + var/compare + + matching_designs.Cut() + + if(menu == 4) + compare = PROTOLATHE + else + compare = IMPRINTER + + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + if(!(D.build_type & compare)) + continue + if(findtext(D.name,href_list["to_search"])) + matching_designs.Add(D) + submenu = 1 + + selected_category = "Search Results for '[href_list["to_search"]]'" + + SSnanoui.update_uis(src) + return + + +/obj/machinery/computer/rdconsole/attack_hand(mob/user as mob) + if(..()) + return 1 + if(!allowed(user) && !isobserver(user)) + to_chat(user, "Access denied.") + return 1 + ui_interact(user) + +/obj/machinery/computer/rdconsole/ui_interact(mob/user, ui_key="main", var/datum/nanoui/ui = null, var/force_open = 1) + user.set_machine(src) + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "r_n_d.tmpl", src.name, 800, 550) + ui.open() + +/obj/machinery/computer/rdconsole/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state) + var/data[0] + + files.RefreshResearch() + + data["menu"] = menu + data["submenu"] = submenu + data["wait_message"] = wait_message + data["src_ref"] = UID() + + data["linked_destroy"] = linked_destroy ? 1 : 0 + data["linked_lathe"] = linked_lathe ? 1 : 0 + data["linked_imprinter"] = linked_imprinter ? 1 : 0 + data["sync"] = sync + data["admin"] = check_rights(R_ADMIN,0) + data["disk_type"] = d_disk ? 2 : (t_disk ? 1 : 0) + data["category"] = selected_category + + if(menu == 0 || menu == 1) + var/list/tech_levels = list() + data["tech_levels"] = tech_levels + for(var/v in files.known_tech) + var/datum/tech/T = files.known_tech[v] + if(T.level <= 0) + continue + var/list/this_tech_list = list() + this_tech_list["name"] = T.name + this_tech_list["level"] = T.level + this_tech_list["desc"] = T.desc + tech_levels[++tech_levels.len] = this_tech_list + + if(menu == 2) + + if(t_disk != null && t_disk.stored != null && submenu == 0) //Technology Disk Menu + var/list/disk_data = list() + data["disk_data"] = disk_data + disk_data["name"] = t_disk.stored.name + disk_data["level"] = t_disk.stored.level + disk_data["desc"] = t_disk.stored.desc + + if(t_disk != null && submenu == 1) + var/list/to_copy = list() + data["to_copy"] = to_copy + for(var/v in files.known_tech) + var/datum/tech/T = files.known_tech[v] + var/list/item = list() + to_copy[++to_copy.len] = item + if(T.level <= 0) + continue + item["name"] = T.name + item["id"] = T.id + + if(d_disk != null && d_disk.blueprint != null && submenu == 0) + var/list/disk_data = list() + data["disk_data"] = disk_data + disk_data["name"] = d_disk.blueprint.name + var/b_type = d_disk.blueprint.build_type + var/list/lathe_types = list() + disk_data["lathe_types"] = lathe_types + if(b_type) + if(b_type & IMPRINTER) lathe_types += "Circuit Imprinter" + if(b_type & PROTOLATHE) lathe_types += "Protolathe" + if(b_type & AUTOLATHE) lathe_types += "Autolathe" + if(b_type & MECHFAB) lathe_types += "Mech Fabricator" + if(b_type & PODFAB) lathe_types += "Spacepod Fabricator" + if(b_type & BIOGENERATOR) lathe_types += "Biogenerator" + if(b_type & SMELTER) lathe_types += "Smelter" + var/list/materials = list() + disk_data["materials"] = materials + for(var/M in d_disk.blueprint.materials) + var/list/material = list() + materials[++materials.len] = material + material["name"] = CallMaterialName(M) + material["amount"] = d_disk.blueprint.materials[M] + + if(d_disk != null && submenu == 1) + var/list/to_copy = list() + data["to_copy"] = to_copy + for(var/v in files.known_designs) + var/datum/design/D = files.known_designs[v] + var/list/item = list() + to_copy[++to_copy.len] = item + item["name"] = D.name + item["id"] = D.id + + if(menu == 3 && linked_destroy && linked_destroy.loaded_item) + var/list/loaded_item_list = list() + data["loaded_item"] = loaded_item_list + loaded_item_list["name"] = linked_destroy.loaded_item.name + var/list/temp_tech = linked_destroy.ConvertReqString2List(linked_destroy.loaded_item.origin_tech) + var/list/tech_list = list() + loaded_item_list["origin_tech"] = tech_list + for(var/T in temp_tech) + var/list/tech_item = list() + tech_list[++tech_list.len] = tech_item + tech_item["name"] = CallTechName(T) + tech_item["object_level"] = temp_tech[T] + for(var/v in files.known_tech) + var/datum/tech/F = files.known_tech[v] + if(F.name == CallTechName(T)) + tech_item["current_level"] = F.level + break + + if(menu == 4 && linked_lathe) + data["total_materials"] = linked_lathe.materials.total_amount + data["max_materials"] = linked_lathe.materials.max_amount + data["total_chemicals"] = linked_lathe.reagents.total_volume + data["max_chemicals"] = linked_lathe.reagents.maximum_volume + data["categories"] = linked_lathe.categories + if(submenu == 1) + var/list/designs_list = list() + data["matching_designs"] = designs_list + var/coeff = linked_lathe.efficiency_coeff + for(var/datum/design/D in matching_designs) + var/list/design_list = list() + designs_list[++designs_list.len] = design_list + var/list/materials_list = list() + design_list["materials"] = materials_list + design_list["id"] = D.id + design_list["name"] = sanitize(D.name) + var/c = 50 + for(var/M in D.materials) + var/list/material_list = list() + materials_list[++materials_list.len] = material_list + material_list["name"] = CallMaterialName(M) + material_list["amount"] = D.materials[M]*coeff + var/t = linked_lathe.check_mat(D, M) + + if(t < 1) + material_list["is_red"] = 1 + else + material_list["is_red"] = 0 + c = min(c, t) + + for(var/R in D.reagents_list) + var/list/material_list = list() + materials_list[++materials_list.len] = material_list + material_list["name"] = CallMaterialName(R) + material_list["amount"] = D.reagents_list[R]*coeff + var/t = linked_lathe.check_mat(D, R) + + if(t < 1) + material_list["is_red"] = 1 + else + material_list["is_red"] = 0 + c = min(c, t) + design_list["can_build"] = c + if(submenu == 2) + var/list/materials_list = list() + data["loaded_materials"] = materials_list + materials_list[++materials_list.len] = list("name" = "Metal", "id" = MAT_METAL, "amount" = linked_lathe.materials.amount(MAT_METAL)) + materials_list[++materials_list.len] = list("name" = "Glass", "id" = MAT_GLASS, "amount" = linked_lathe.materials.amount(MAT_GLASS)) + materials_list[++materials_list.len] = list("name" = "Gold", "id" = MAT_GOLD, "amount" = linked_lathe.materials.amount(MAT_GOLD)) + materials_list[++materials_list.len] = list("name" = "Silver", "id" = MAT_SILVER, "amount" = linked_lathe.materials.amount(MAT_SILVER)) + materials_list[++materials_list.len] = list("name" = "Solid Plasma", "id" = MAT_PLASMA, "amount" = linked_lathe.materials.amount(MAT_PLASMA)) + materials_list[++materials_list.len] = list("name" = "Uranium", "id" = MAT_URANIUM, "amount" = linked_lathe.materials.amount(MAT_URANIUM)) + materials_list[++materials_list.len] = list("name" = "Diamond", "id" = MAT_DIAMOND, "amount" = linked_lathe.materials.amount(MAT_DIAMOND)) + materials_list[++materials_list.len] = list("name" = "Bananium", "id" = MAT_BANANIUM, "amount" = linked_lathe.materials.amount(MAT_BANANIUM)) + materials_list[++materials_list.len] = list("name" = "Tranquillite", "id" = MAT_TRANQUILLITE, "amount" = linked_lathe.materials.amount(MAT_TRANQUILLITE)) + materials_list[++materials_list.len] = list("name" = "Titanium", "id" = MAT_TITANIUM, "amount" = linked_lathe.materials.amount(MAT_TITANIUM)) + materials_list[++materials_list.len] = list("name" = "Plastic", "id" = MAT_PLASTIC, "amount" = linked_lathe.materials.amount(MAT_PLASTIC)) + materials_list[++materials_list.len] = list("name" = "Bluespace Mesh", "id" = MAT_BLUESPACE, "amount" = linked_lathe.materials.amount(MAT_BLUESPACE)) + if(submenu == 3) + var/list/loaded_chemicals = list() + data["loaded_chemicals"] = loaded_chemicals + for(var/datum/reagent/R in linked_lathe.reagents.reagent_list) + var/list/loaded_chemical = list() + loaded_chemicals[++loaded_chemicals.len] = loaded_chemical + loaded_chemical["name"] = R.name + loaded_chemical["volume"] = R.volume + loaded_chemical["id"] = R.id + + if(menu == 5 && linked_imprinter) + data["total_materials"] = linked_imprinter.materials.total_amount + data["max_materials"] = linked_imprinter.materials.max_amount + data["total_chemicals"] = linked_imprinter.reagents.total_volume + data["max_chemicals"] = linked_imprinter.reagents.maximum_volume + data["categories"] = linked_imprinter.categories + if(submenu == 1) + var/list/designs_list = list() + data["matching_designs"] = designs_list + var/coeff = linked_imprinter.efficiency_coeff + for(var/datum/design/D in matching_designs) + var/list/design_list = list() + designs_list[++designs_list.len] = design_list + var/list/materials_list = list() + design_list["materials"] = materials_list + design_list["id"] = D.id + design_list["name"] = sanitize(D.name) + var/check_materials = 1 + for(var/M in D.materials) + var/list/material_list = list() + materials_list[++materials_list.len] = material_list + material_list["name"] = CallMaterialName(M) + material_list["amount"] = D.materials[M] / coeff + if(!linked_imprinter.check_mat(D, M)) + check_materials = 0 + material_list["is_red"] = 1 + else + material_list["is_red"] = 0 + + for(var/R in D.reagents_list) + var/list/material_list = list() + materials_list[++materials_list.len] = material_list + material_list["name"] = CallMaterialName(R) + material_list["amount"] = D.reagents_list[R]*coeff + if(!linked_imprinter.check_mat(D, R)) + check_materials = 0 + material_list["is_red"] = 1 + else + material_list["is_red"] = 0 + + design_list["can_build"] = check_materials + if(submenu == 2) + var/list/materials_list = list() + data["loaded_materials"] = materials_list + materials_list[++materials_list.len] = list("name" = "Metal", "id" = MAT_METAL, "amount" = linked_imprinter.materials.amount(MAT_METAL)) + materials_list[++materials_list.len] = list("name" = "Glass", "id" = MAT_GLASS, "amount" = linked_imprinter.materials.amount(MAT_GLASS)) + materials_list[++materials_list.len] = list("name" = "Gold", "id" = MAT_GOLD, "amount" = linked_imprinter.materials.amount(MAT_GOLD)) + materials_list[++materials_list.len] = list("name" = "Silver", "id" = MAT_SILVER, "amount" = linked_imprinter.materials.amount(MAT_SILVER)) + materials_list[++materials_list.len] = list("name" = "Solid Plasma", "id" = MAT_PLASMA, "amount" = linked_imprinter.materials.amount(MAT_PLASMA)) + materials_list[++materials_list.len] = list("name" = "Uranium", "id" = MAT_URANIUM, "amount" = linked_imprinter.materials.amount(MAT_URANIUM)) + materials_list[++materials_list.len] = list("name" = "Diamond", "id" = MAT_DIAMOND, "amount" = linked_imprinter.materials.amount(MAT_DIAMOND)) + materials_list[++materials_list.len] = list("name" = "Bananium", "id" = MAT_BANANIUM, "amount" = linked_imprinter.materials.amount(MAT_BANANIUM)) + materials_list[++materials_list.len] = list("name" = "Tranquillite", "id" = MAT_TRANQUILLITE, "amount" = linked_imprinter.materials.amount(MAT_TRANQUILLITE)) + materials_list[++materials_list.len] = list("name" = "Titanium", "id" = MAT_TITANIUM, "amount" = linked_imprinter.materials.amount(MAT_TITANIUM)) + materials_list[++materials_list.len] = list("name" = "Plastic", "id" = MAT_PLASTIC, "amount" = linked_imprinter.materials.amount(MAT_PLASTIC)) + materials_list[++materials_list.len] = list("name" = "Bluespace Mesh", "id" = MAT_BLUESPACE, "amount" = linked_imprinter.materials.amount(MAT_BLUESPACE)) + if(submenu == 3) + var/list/loaded_chemicals = list() + data["loaded_chemicals"] = loaded_chemicals + for(var/datum/reagent/R in linked_imprinter.reagents.reagent_list) + var/list/loaded_chemical = list() + loaded_chemicals[++loaded_chemicals.len] = loaded_chemical + loaded_chemical["name"] = R.name + loaded_chemical["volume"] = R.volume + loaded_chemical["id"] = R.id + + return data + +//helper proc that guarantees the wait message will not freeze the UI +/obj/machinery/computer/rdconsole/proc/add_wait_message(message, delay) + wait_message = message + wait_message_timer = addtimer(CALLBACK(src, .proc/clear_wait_message), delay, TIMER_UNIQUE | TIMER_STOPPABLE) + +// This is here to guarantee that we never lock the console, so long as the timer +// process is running +// So long as the spawns never runtime, though, the timer should be stopped +// before it gets the chance to fire +// Since the timer process can have significant delays, you should call this +// in the operations that take time, once they complete +/obj/machinery/computer/rdconsole/proc/clear_wait_message() + wait_message = "" + if(wait_message_timer) + // This could be expensive, and will still be called + // if the timer calls this function + deltimer(wait_message_timer) + wait_message_timer = 0 + SSnanoui.update_uis(src) + + +/obj/machinery/computer/rdconsole/core + name = "core R&D console" + desc = "A console used to interface with R&D tools." + id = 1 + +/obj/machinery/computer/rdconsole/robotics + name = "robotics R&D console" + desc = "A console used to interface with R&D tools." + id = 2 + req_access = list(ACCESS_ROBOTICS) + circuit = /obj/item/circuitboard/rdconsole/robotics + +/obj/machinery/computer/rdconsole/experiment + name = "\improper E.X.P.E.R.I-MENTOR R&D console" + desc = "A console used to interface with R&D tools." + id = 3 + circuit = /obj/item/circuitboard/rdconsole/experiment + +/obj/machinery/computer/rdconsole/mechanics + name = "mechanics R&D console" + desc = "A console used to interface with R&D tools." + id = 4 + req_access = list(ACCESS_MECHANIC) + circuit = /obj/item/circuitboard/rdconsole/mechanics + +/obj/machinery/computer/rdconsole/public + name = "public R&D console" + desc = "A console used to interface with R&D tools." + id = 5 + req_access = list() + circuit = /obj/item/circuitboard/rdconsole/public + +#undef TECH_UPDATE_DELAY +#undef DESIGN_UPDATE_DELAY +#undef PROTOLATHE_CONSTRUCT_DELAY +#undef SYNC_RESEARCH_DELAY +#undef DECONSTRUCT_DELAY +#undef SYNC_DEVICE_DELAY +#undef RESET_RESEARCH_DELAY +#undef IMPRINTER_DELAY diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm index f6fc35932bc..7acdc53ce3d 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -1,133 +1,133 @@ -//All devices that link into the R&D console fall into thise type for easy identification and some shared procs. - - -/obj/machinery/r_n_d - name = "R&D Device" - icon = 'icons/obj/machines/research.dmi' - density = 1 - anchored = 1 - use_power = IDLE_POWER_USE - var/busy = 0 - var/hacked = 0 - var/disabled = 0 - var/shocked = 0 - var/list/wires = list() - var/hack_wire - var/disable_wire - var/shock_wire - var/obj/machinery/computer/rdconsole/linked_console - var/obj/item/loaded_item = null - var/datum/component/material_container/materials //Store for hyper speed! - -/obj/machinery/r_n_d/New() - materials = AddComponent(/datum/component/material_container, - list(MAT_METAL, MAT_GLASS, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_PLASMA, MAT_URANIUM, MAT_BANANIUM, MAT_TRANQUILLITE, MAT_TITANIUM, MAT_BLUESPACE, MAT_PLASTIC), 0, - TRUE, /obj/item/stack, CALLBACK(src, .proc/is_insertion_ready), CALLBACK(src, .proc/AfterMaterialInsert)) - materials.precise_insertion = TRUE - ..() - wires["Red"] = 0 - wires["Blue"] = 0 - wires["Green"] = 0 - wires["Yellow"] = 0 - wires["Black"] = 0 - wires["White"] = 0 - var/list/w = list("Red","Blue","Green","Yellow","Black","White") - src.hack_wire = pick(w) - w -= src.hack_wire - src.shock_wire = pick(w) - w -= src.shock_wire - src.disable_wire = pick(w) - w -= src.disable_wire - -/obj/machinery/r_n_d/attack_hand(mob/user as mob) - if(shocked) - shock(user,50) - if(panel_open) - var/list/dat = list() - dat += "[src.name] Wires:
        " - for(var/wire in wires) - dat += "[wire] Wire: [src.wires[wire] ? "Mend" : "Cut"] Pulse
        " - - dat += "The red light is [src.disabled ? "off" : "on"].
        " - dat += "The green light is [src.shocked ? "off" : "on"].
        " - dat += "The blue light is [src.hacked ? "off" : "on"].
        " - user << browse("[src.name] Hacking[dat.Join("")]","window=hack_win") - return - - -/obj/machinery/r_n_d/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - src.add_fingerprint(usr) - if(href_list["pulse"]) - var/temp_wire = href_list["wire"] - if(!istype(usr.get_active_hand(), /obj/item/multitool)) - to_chat(usr, "You need a multitool!") - else - if(src.wires[temp_wire]) - to_chat(usr, "You can't pulse a cut wire.") - else - if(src.hack_wire == href_list["wire"]) - src.hacked = !src.hacked - spawn(100) src.hacked = !src.hacked - if(src.disable_wire == href_list["wire"]) - src.disabled = !src.disabled - src.shock(usr,50) - spawn(100) src.disabled = !src.disabled - if(src.shock_wire == href_list["wire"]) - src.shocked = !src.shocked - src.shock(usr,50) - spawn(100) src.shocked = !src.shocked - if(href_list["cut"]) - if(!istype(usr.get_active_hand(), /obj/item/wirecutters)) - to_chat(usr, "You need wirecutters!") - else - var/temp_wire = href_list["wire"] - wires[temp_wire] = !wires[temp_wire] - if(src.hack_wire == temp_wire) - src.hacked = !src.hacked - if(src.disable_wire == temp_wire) - src.disabled = !src.disabled - src.shock(usr,50) - if(src.shock_wire == temp_wire) - src.shocked = !src.shocked - src.shock(usr,50) - src.updateUsrDialog() - -//whether the machine can have an item inserted in its current state. -/obj/machinery/r_n_d/proc/is_insertion_ready(mob/user) - if(panel_open) - to_chat(user, "You can't load [src] while it's opened!") - return FALSE - if(disabled) - return FALSE - if(!linked_console) - to_chat(user, "[src] must be linked to an R&D console first!") - return FALSE - if(busy) - to_chat(user, "[src] is busy right now.") - return FALSE - if(stat & BROKEN) - to_chat(user, "[src] is broken.") - return FALSE - if(stat & NOPOWER) - to_chat(user, "[src] has no power.") - return FALSE - if(loaded_item) - to_chat(user, "[src] is already loaded.") - return FALSE - return TRUE - -/obj/machinery/r_n_d/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) - var/stack_name - if(ispath(type_inserted, /obj/item/stack/ore/bluespace_crystal)) - stack_name = "bluespace polycrystal" - use_power(MINERAL_MATERIAL_AMOUNT / 10) - else - var/obj/item/stack/S = type_inserted - stack_name = initial(S.name) - use_power(min(1000, (amount_inserted / 100))) - overlays += "[initial(name)]_[stack_name]" - sleep(10) - overlays -= "[initial(name)]_[stack_name]" +//All devices that link into the R&D console fall into thise type for easy identification and some shared procs. + + +/obj/machinery/r_n_d + name = "R&D Device" + icon = 'icons/obj/machines/research.dmi' + density = 1 + anchored = 1 + use_power = IDLE_POWER_USE + var/busy = 0 + var/hacked = 0 + var/disabled = 0 + var/shocked = 0 + var/list/wires = list() + var/hack_wire + var/disable_wire + var/shock_wire + var/obj/machinery/computer/rdconsole/linked_console + var/obj/item/loaded_item = null + var/datum/component/material_container/materials //Store for hyper speed! + +/obj/machinery/r_n_d/New() + materials = AddComponent(/datum/component/material_container, + list(MAT_METAL, MAT_GLASS, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_PLASMA, MAT_URANIUM, MAT_BANANIUM, MAT_TRANQUILLITE, MAT_TITANIUM, MAT_BLUESPACE, MAT_PLASTIC), 0, + TRUE, /obj/item/stack, CALLBACK(src, .proc/is_insertion_ready), CALLBACK(src, .proc/AfterMaterialInsert)) + materials.precise_insertion = TRUE + ..() + wires["Red"] = 0 + wires["Blue"] = 0 + wires["Green"] = 0 + wires["Yellow"] = 0 + wires["Black"] = 0 + wires["White"] = 0 + var/list/w = list("Red","Blue","Green","Yellow","Black","White") + src.hack_wire = pick(w) + w -= src.hack_wire + src.shock_wire = pick(w) + w -= src.shock_wire + src.disable_wire = pick(w) + w -= src.disable_wire + +/obj/machinery/r_n_d/attack_hand(mob/user as mob) + if(shocked) + shock(user,50) + if(panel_open) + var/list/dat = list() + dat += "[src.name] Wires:
        " + for(var/wire in wires) + dat += "[wire] Wire: [src.wires[wire] ? "Mend" : "Cut"] Pulse
        " + + dat += "The red light is [src.disabled ? "off" : "on"].
        " + dat += "The green light is [src.shocked ? "off" : "on"].
        " + dat += "The blue light is [src.hacked ? "off" : "on"].
        " + user << browse("[src.name] Hacking[dat.Join("")]","window=hack_win") + return + + +/obj/machinery/r_n_d/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + src.add_fingerprint(usr) + if(href_list["pulse"]) + var/temp_wire = href_list["wire"] + if(!istype(usr.get_active_hand(), /obj/item/multitool)) + to_chat(usr, "You need a multitool!") + else + if(src.wires[temp_wire]) + to_chat(usr, "You can't pulse a cut wire.") + else + if(src.hack_wire == href_list["wire"]) + src.hacked = !src.hacked + spawn(100) src.hacked = !src.hacked + if(src.disable_wire == href_list["wire"]) + src.disabled = !src.disabled + src.shock(usr,50) + spawn(100) src.disabled = !src.disabled + if(src.shock_wire == href_list["wire"]) + src.shocked = !src.shocked + src.shock(usr,50) + spawn(100) src.shocked = !src.shocked + if(href_list["cut"]) + if(!istype(usr.get_active_hand(), /obj/item/wirecutters)) + to_chat(usr, "You need wirecutters!") + else + var/temp_wire = href_list["wire"] + wires[temp_wire] = !wires[temp_wire] + if(src.hack_wire == temp_wire) + src.hacked = !src.hacked + if(src.disable_wire == temp_wire) + src.disabled = !src.disabled + src.shock(usr,50) + if(src.shock_wire == temp_wire) + src.shocked = !src.shocked + src.shock(usr,50) + src.updateUsrDialog() + +//whether the machine can have an item inserted in its current state. +/obj/machinery/r_n_d/proc/is_insertion_ready(mob/user) + if(panel_open) + to_chat(user, "You can't load [src] while it's opened!") + return FALSE + if(disabled) + return FALSE + if(!linked_console) + to_chat(user, "[src] must be linked to an R&D console first!") + return FALSE + if(busy) + to_chat(user, "[src] is busy right now.") + return FALSE + if(stat & BROKEN) + to_chat(user, "[src] is broken.") + return FALSE + if(stat & NOPOWER) + to_chat(user, "[src] has no power.") + return FALSE + if(loaded_item) + to_chat(user, "[src] is already loaded.") + return FALSE + return TRUE + +/obj/machinery/r_n_d/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) + var/stack_name + if(ispath(type_inserted, /obj/item/stack/ore/bluespace_crystal)) + stack_name = "bluespace polycrystal" + use_power(MINERAL_MATERIAL_AMOUNT / 10) + else + var/obj/item/stack/S = type_inserted + stack_name = initial(S.name) + use_power(min(1000, (amount_inserted / 100))) + overlays += "[initial(name)]_[stack_name]" + sleep(10) + overlays -= "[initial(name)]_[stack_name]" diff --git a/code/modules/research/research.dm b/code/modules/research/research.dm index 65d9a1d3ad4..b5fc9e9c879 100644 --- a/code/modules/research/research.dm +++ b/code/modules/research/research.dm @@ -1,400 +1,400 @@ -/* -General Explination: -The research datum is the "folder" where all the research information is stored in a R&D console. It's also a holder for all the -various procs used to manipulate it. It has four variables and seven procs: - -Variables: -- possible_tech is a list of all the /datum/tech that can potentially be researched by the player. The RefreshResearch() proc -(explained later) only goes through those when refreshing what you know. Generally, possible_tech contains ALL of the existing tech -but it is possible to add tech to the game that DON'T start in it (example: Xeno tech). Generally speaking, you don't want to mess -with these since they should be the default version of the datums. They're actually stored in a list rather then using typesof to -refer to them since it makes it a bit easier to search through them for specific information. -- know_tech is the companion list to possible_tech. It's the tech you can actually research and improve. Until it's added to this -list, it can't be improved. All the tech in this list are visible to the player. -- possible_designs is functionally identical to possbile_tech except it's for /datum/design. -- known_designs is functionally identical to known_tech except it's for /datum/design - -Procs: -- TechHasReqs: Used by other procs (specifically RefreshResearch) to see whether all of a tech's requirements are currently in -known_tech and at a high enough level. -- DesignHasReqs: Same as TechHasReqs but for /datum/design and known_design. -- AddTech2Known: Adds a /datum/tech to known_tech. It checks to see whether it already has that tech (if so, it just replaces it). If -it doesn't have it, it adds it. Note: It does NOT check possible_tech at all. So if you want to add something strange to it (like -a player made tech?) you can. -- AddDesign2Known: Same as AddTech2Known except for /datum/design and known_designs. -- RefreshResearch: This is the workhorse of the R&D system. It updates the /datum/research holder and adds any unlocked tech paths -and designs you have reached the requirements for. It only checks through possible_tech and possible_designs, however, so it won't -accidentally add "secret" tech to it. -- UpdateTech is used as part of the actual researching process. It takes an ID and finds techs with that same ID in known_tech. When -it finds it, it checks to see whether it can improve it at all. If the known_tech's level is less then or equal to -the inputted level, it increases the known tech's level to the inputted level -1 or know tech's level +1 (whichever is higher). - -The tech datums are the actual "tech trees" that you improve through researching. Each one has five variables: -- Name: Pretty obvious. This is often viewable to the players. -- Desc: Pretty obvious. Also player viewable. -- ID: This is the unique ID of the tech that is used by the various procs to find and/or maniuplate it. -- Level: This is the current level of the tech. All techs start at 1 and have a max of 20. Devices and some techs require a certain -level in specific techs before you can produce them. -- Req_tech: This is a list of the techs required to unlock this tech path. If left blank, it'll automatically be loaded into the -research holder datum. - -*/ -/*************************************************************** -** Master Types ** -** Includes all the helper procs and basic tech processing. ** -***************************************************************/ - -/datum/research //Holder for all the existing, archived, and known tech. Individual to console. - - //Datum/tech go here. - // Possible is a list of direct datum references - // known is a list of id -> datum mappings - var/list/possible_tech = list() //List of all tech in the game that players have access to (barring special events). - var/list/known_tech = list() //List of locally known tech. - var/list/possible_designs = list() //List of all designs - var/list/known_designs = list() //List of available designs - -/datum/research/New() //Insert techs into possible_tech here. Known_tech automatically updated. - // MON DIEU!!! - // These are semi-global, but not TOTALLY global? - // Using research disks, you can get techs/designs from one research datum - // onto another. What consequences this could have, I am presently unsure, but - // I imagine nothing good. - for(var/T in subtypesof(/datum/tech)) - possible_tech += new T(src) - for(var/D in subtypesof(/datum/design)) - possible_designs += new D(src) - RefreshResearch() - - - -//Checks to see if tech has all the required pre-reqs. -//Input: datum/tech; Output: 0/1 (false/true) -/datum/research/proc/TechHasReqs(var/datum/tech/T) - if(T.req_tech.len == 0) - return TRUE - for(var/req in T.req_tech) - var/datum/tech/known = known_tech[req] - if(!known || known.level < T.req_tech[req]) - return FALSE - return TRUE - -//Checks to see if design has all the required pre-reqs. -//Input: datum/design; Output: 0/1 (false/true) -/datum/research/proc/DesignHasReqs(var/datum/design/D) - if(D.req_tech.len == 0) - return TRUE - for(var/req in D.req_tech) - var/datum/tech/known = known_tech[req] - if(!known || known.level < D.req_tech[req]) - return FALSE - return TRUE - -//Adds a tech to known_tech list. Checks to make sure there aren't duplicates and updates existing tech's levels if needed. -//Input: datum/tech; Output: Null -/datum/research/proc/AddTech2Known(var/datum/tech/T) - if(T.id in known_tech) - var/datum/tech/known = known_tech[T.id] - if(T.level > known.level) - known.level = T.level - return - known_tech[T.id] = T - -/datum/research/proc/AddDesign2Known(var/datum/design/D) - if(D.id in known_designs) - return - // Global datums make me nervous - known_designs[D.id] = D - -//Refreshes known_tech and known_designs list. -//Input/Output: n/a -/datum/research/proc/RefreshResearch() - for(var/datum/tech/PT in possible_tech) - if(TechHasReqs(PT)) - AddTech2Known(PT) - for(var/datum/design/PD in possible_designs) - if(DesignHasReqs(PD)) - AddDesign2Known(PD) - for(var/v in known_tech) - var/datum/tech/T = known_tech[v] - T.level = Clamp(T.level, 0, 20) - -//Refreshes the levels of a given tech. -//Input: Tech's ID and Level; Output: null -/datum/research/proc/UpdateTech(var/ID, var/level) - var/datum/tech/KT = known_tech[ID] - if(KT) - if(KT.level <= level) - // Will bump the tech to (value_of_target) automatically - - // after that it'll bump it up by 1 until it's greater - // than the source tech - KT.level = max((KT.level + 1), level) - -//Checks if the origin level can raise current tech levels -//Input: Tech's ID and Level; Output: TRUE for yes, FALSE for no -/datum/research/proc/IsTechHigher(ID, level) - var/datum/tech/KT = known_tech[ID] - if(KT) - if(KT.level <= level) - return TRUE - else - return FALSE - -/datum/research/proc/FindDesignByID(var/id) - return known_designs[id] - -// A common task is for one research datum to copy over its techs and designs -// and update them on another research datum. -// Arguments: -// `other` - The research datum to send designs and techs to -/datum/research/proc/push_data(datum/research/other) - for(var/v in known_tech) - var/datum/tech/T = known_tech[v] - other.AddTech2Known(T) - for(var/v in known_designs) - var/datum/design/D = known_designs[v] - other.AddDesign2Known(D) - other.RefreshResearch() - - -//Autolathe files -/datum/research/autolathe - -/datum/research/autolathe/DesignHasReqs(var/datum/design/D) - return D && (D.build_type & AUTOLATHE) && ("initial" in D.category) - -/datum/research/autolathe/AddDesign2Known(var/datum/design/D) - if(!(D.build_type & AUTOLATHE)) - return - ..() - -//Biogenerator files -/datum/research/biogenerator/New() - for(var/T in (subtypesof(/datum/tech))) - possible_tech += new T(src) - for(var/path in subtypesof(/datum/design)) - var/datum/design/D = new path(src) - possible_designs += D - if((D.build_type & BIOGENERATOR) && ("initial" in D.category)) - AddDesign2Known(D) - -/datum/research/biogenerator/AddDesign2Known(datum/design/D) - if(!(D.build_type & BIOGENERATOR)) - return - ..() - -//Smelter files -/datum/research/smelter/New() - for(var/T in (subtypesof(/datum/tech))) - possible_tech += new T(src) - for(var/path in subtypesof(/datum/design)) - var/datum/design/D = new path(src) - possible_designs += D - if((D.build_type & SMELTER) && ("initial" in D.category)) - AddDesign2Known(D) - -/datum/research/smelter/AddDesign2Known(datum/design/D) - if(!(D.build_type & SMELTER)) - return - ..() - -/*************************************************************** -** Technology Datums ** -** Includes all the various technoliges and what they make. ** -***************************************************************/ - -/datum/tech //Datum of individual technologies. - var/name = "name" //Name of the technology. - var/desc = "description" //General description of what it does and what it makes. - var/id = "id" //An easily referenced ID. Must be alphanumeric, lower-case, and no symbols. - var/level = 1 //A simple number scale of the research level. Level 0 = Secret tech. - var/max_level = 1 // Maximum level this can be at (for job objectives) - var/rare = 1 //How much CentCom wants to get that tech. Used in supply shuttle tech cost calculation. - var/list/req_tech = list() //List of ids associated values of techs required to research this tech. "id" = # - - -//Trunk Technologies (don't require any other techs and you start knowning them). - -/datum/tech/materials - name = "Materials Research" - desc = "Development of new and improved materials." - id = "materials" - max_level = 7 - -/datum/tech/engineering - name = "Engineering Research" - desc = "Development of new and improved engineering parts and methods." - id = "engineering" - max_level = 7 - -/datum/tech/plasmatech - name = "Plasma Research" - desc = "Research into the mysterious substance colloqually known as 'plasma'." - id = "plasmatech" - max_level = 7 - rare = 3 - -/datum/tech/powerstorage - name = "Power Manipulation Technology" - desc = "The various technologies behind the storage and generation of electicity." - id = "powerstorage" - max_level = 7 - -/datum/tech/bluespace - name = "'Blue-space' Research" - desc = "Research into the sub-reality known as 'blue-space'." - id = "bluespace" - max_level = 7 - rare = 2 - -/datum/tech/biotech - name = "Biological Technology" - desc = "Research into the deeper mysteries of life and organic substances." - id = "biotech" - max_level = 7 - -/datum/tech/combat - name = "Combat Systems Research" - desc = "The development of offensive and defensive systems." - id = "combat" - max_level = 7 - -/datum/tech/magnets - name = "Electromagnetic Spectrum Research" - desc = "Research into the electromagnetic spectrum. No clue how they actually work, though." - id = "magnets" - max_level = 7 - -/datum/tech/programming - name = "Data Theory Research" - desc = "The development of new computer and artificial intelligence and data storage systems." - id = "programming" - max_level = 7 - -/datum/tech/toxins //not meant to be raised by deconstruction, do not give objects toxins as an origin_tech - name = "Toxins Research" - desc = "Research into plasma based explosive devices. Upgrade through testing explosives in the toxins lab." - id = "toxins" - max_level = 7 - rare = 2 - -/datum/tech/syndicate - name = "Illegal Technologies Research" - desc = "The study of technologies that violate standard Nanotrasen regulations." - id = "syndicate" - max_level = 0 // Don't count towards maxed research, since it's illegal. - rare = 4 - -/datum/tech/abductor - name = "Alien Technologies Research" - desc = "The study of technologies used by the advanced alien race known as Abductors." - id = "abductor" - rare = 5 - level = 0 - -/* -datum/tech/arcane - name = "Arcane Research" - desc = "Research into the occult and arcane field for use in practical science" - id = "arcane" - level = 0 //It didn't become "secret" as advertised. - -//Branch Techs -datum/tech/explosives - name = "Explosives Research" - desc = "The creation and application of explosive materials." - id = "explosives" - req_tech = list("materials" = 3) - -datum/tech/generators - name = "Power Generation Technology" - desc = "Research into more powerful and more reliable sources." - id = "generators" - req_tech = list("powerstorage" = 2) - -datum/tech/robotics - name = "Robotics Technology" - desc = "The development of advanced automated, autonomous machines." - id = "robotics" - req_tech = list("materials" = 3, "programming" = 3) -*/ - -/datum/tech/proc/getCost(var/current_level = null) - // Calculates tech disk's supply points sell cost - if(!current_level) - current_level = initial(level) - - if(current_level >= level) - return 0 - - var/cost = 0 - for(var/i=current_level+1, i<=level, i++) - if(i == initial(level)) - continue - cost += i*5*rare - - return cost - -/obj/item/disk/tech_disk - name = "\improper Technology Disk" - desc = "A disk for storing technology data for further research." - icon_state = "datadisk2" - materials = list(MAT_METAL=30, MAT_GLASS=10) - var/datum/tech/stored - var/default_name = "\improper Technology Disk" - var/default_desc = "A disk for storing technology data for further research." - -/obj/item/disk/tech_disk/New() - ..() - src.pixel_x = rand(-5.0, 5) - src.pixel_y = rand(-5.0, 5) - -/obj/item/disk/tech_disk/proc/load_tech(datum/tech/T) - name = "[default_name] \[[T]\]" - desc = T.desc + " Level: '[T.level]'" - // NOTE: This is just a reference to the tech on the system it grabbed it from - // This seems highly fragile - stored = T - -/obj/item/disk/tech_disk/proc/wipe_tech() - name = default_name - desc = default_desc - stored = null - -/obj/item/disk/design_disk - name = "\improper Component Design Disk" - desc = "A disk for storing device design data for construction in lathes." - icon_state = "datadisk2" - materials = list(MAT_METAL=100, MAT_GLASS=100) - var/datum/design/blueprint - // I'm doing this so that disk paths with pre-loaded designs don't get weird names - // Otherwise, I'd use "initial()" - var/default_name = "\improper Component Design Disk" - var/default_desc = "A disk for storing device design data for construction in lathes." - -/obj/item/disk/design_disk/New() - ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - -/obj/item/disk/design_disk/proc/load_blueprint(datum/design/D) - name = "[default_name] \[[D]\]" - desc = D.desc - // NOTE: This is just a reference to the design on the system it grabbed it from - // This seems highly fragile - blueprint = D - -/obj/item/disk/design_disk/proc/wipe_blueprint() - name = default_name - desc = default_desc - blueprint = null - -/obj/item/disk/design_disk/golem_shell - name = "golem creation disk" - desc = "A gift from the Liberator." - icon_state = "datadisk1" - -/obj/item/disk/design_disk/golem_shell/Initialize() - . = ..() - var/datum/design/golem_shell/G = new - blueprint = G \ No newline at end of file +/* +General Explination: +The research datum is the "folder" where all the research information is stored in a R&D console. It's also a holder for all the +various procs used to manipulate it. It has four variables and seven procs: + +Variables: +- possible_tech is a list of all the /datum/tech that can potentially be researched by the player. The RefreshResearch() proc +(explained later) only goes through those when refreshing what you know. Generally, possible_tech contains ALL of the existing tech +but it is possible to add tech to the game that DON'T start in it (example: Xeno tech). Generally speaking, you don't want to mess +with these since they should be the default version of the datums. They're actually stored in a list rather then using typesof to +refer to them since it makes it a bit easier to search through them for specific information. +- know_tech is the companion list to possible_tech. It's the tech you can actually research and improve. Until it's added to this +list, it can't be improved. All the tech in this list are visible to the player. +- possible_designs is functionally identical to possbile_tech except it's for /datum/design. +- known_designs is functionally identical to known_tech except it's for /datum/design + +Procs: +- TechHasReqs: Used by other procs (specifically RefreshResearch) to see whether all of a tech's requirements are currently in +known_tech and at a high enough level. +- DesignHasReqs: Same as TechHasReqs but for /datum/design and known_design. +- AddTech2Known: Adds a /datum/tech to known_tech. It checks to see whether it already has that tech (if so, it just replaces it). If +it doesn't have it, it adds it. Note: It does NOT check possible_tech at all. So if you want to add something strange to it (like +a player made tech?) you can. +- AddDesign2Known: Same as AddTech2Known except for /datum/design and known_designs. +- RefreshResearch: This is the workhorse of the R&D system. It updates the /datum/research holder and adds any unlocked tech paths +and designs you have reached the requirements for. It only checks through possible_tech and possible_designs, however, so it won't +accidentally add "secret" tech to it. +- UpdateTech is used as part of the actual researching process. It takes an ID and finds techs with that same ID in known_tech. When +it finds it, it checks to see whether it can improve it at all. If the known_tech's level is less then or equal to +the inputted level, it increases the known tech's level to the inputted level -1 or know tech's level +1 (whichever is higher). + +The tech datums are the actual "tech trees" that you improve through researching. Each one has five variables: +- Name: Pretty obvious. This is often viewable to the players. +- Desc: Pretty obvious. Also player viewable. +- ID: This is the unique ID of the tech that is used by the various procs to find and/or maniuplate it. +- Level: This is the current level of the tech. All techs start at 1 and have a max of 20. Devices and some techs require a certain +level in specific techs before you can produce them. +- Req_tech: This is a list of the techs required to unlock this tech path. If left blank, it'll automatically be loaded into the +research holder datum. + +*/ +/*************************************************************** +** Master Types ** +** Includes all the helper procs and basic tech processing. ** +***************************************************************/ + +/datum/research //Holder for all the existing, archived, and known tech. Individual to console. + + //Datum/tech go here. + // Possible is a list of direct datum references + // known is a list of id -> datum mappings + var/list/possible_tech = list() //List of all tech in the game that players have access to (barring special events). + var/list/known_tech = list() //List of locally known tech. + var/list/possible_designs = list() //List of all designs + var/list/known_designs = list() //List of available designs + +/datum/research/New() //Insert techs into possible_tech here. Known_tech automatically updated. + // MON DIEU!!! + // These are semi-global, but not TOTALLY global? + // Using research disks, you can get techs/designs from one research datum + // onto another. What consequences this could have, I am presently unsure, but + // I imagine nothing good. + for(var/T in subtypesof(/datum/tech)) + possible_tech += new T(src) + for(var/D in subtypesof(/datum/design)) + possible_designs += new D(src) + RefreshResearch() + + + +//Checks to see if tech has all the required pre-reqs. +//Input: datum/tech; Output: 0/1 (false/true) +/datum/research/proc/TechHasReqs(var/datum/tech/T) + if(T.req_tech.len == 0) + return TRUE + for(var/req in T.req_tech) + var/datum/tech/known = known_tech[req] + if(!known || known.level < T.req_tech[req]) + return FALSE + return TRUE + +//Checks to see if design has all the required pre-reqs. +//Input: datum/design; Output: 0/1 (false/true) +/datum/research/proc/DesignHasReqs(var/datum/design/D) + if(D.req_tech.len == 0) + return TRUE + for(var/req in D.req_tech) + var/datum/tech/known = known_tech[req] + if(!known || known.level < D.req_tech[req]) + return FALSE + return TRUE + +//Adds a tech to known_tech list. Checks to make sure there aren't duplicates and updates existing tech's levels if needed. +//Input: datum/tech; Output: Null +/datum/research/proc/AddTech2Known(var/datum/tech/T) + if(T.id in known_tech) + var/datum/tech/known = known_tech[T.id] + if(T.level > known.level) + known.level = T.level + return + known_tech[T.id] = T + +/datum/research/proc/AddDesign2Known(var/datum/design/D) + if(D.id in known_designs) + return + // Global datums make me nervous + known_designs[D.id] = D + +//Refreshes known_tech and known_designs list. +//Input/Output: n/a +/datum/research/proc/RefreshResearch() + for(var/datum/tech/PT in possible_tech) + if(TechHasReqs(PT)) + AddTech2Known(PT) + for(var/datum/design/PD in possible_designs) + if(DesignHasReqs(PD)) + AddDesign2Known(PD) + for(var/v in known_tech) + var/datum/tech/T = known_tech[v] + T.level = Clamp(T.level, 0, 20) + +//Refreshes the levels of a given tech. +//Input: Tech's ID and Level; Output: null +/datum/research/proc/UpdateTech(var/ID, var/level) + var/datum/tech/KT = known_tech[ID] + if(KT) + if(KT.level <= level) + // Will bump the tech to (value_of_target) automatically - + // after that it'll bump it up by 1 until it's greater + // than the source tech + KT.level = max((KT.level + 1), level) + +//Checks if the origin level can raise current tech levels +//Input: Tech's ID and Level; Output: TRUE for yes, FALSE for no +/datum/research/proc/IsTechHigher(ID, level) + var/datum/tech/KT = known_tech[ID] + if(KT) + if(KT.level <= level) + return TRUE + else + return FALSE + +/datum/research/proc/FindDesignByID(var/id) + return known_designs[id] + +// A common task is for one research datum to copy over its techs and designs +// and update them on another research datum. +// Arguments: +// `other` - The research datum to send designs and techs to +/datum/research/proc/push_data(datum/research/other) + for(var/v in known_tech) + var/datum/tech/T = known_tech[v] + other.AddTech2Known(T) + for(var/v in known_designs) + var/datum/design/D = known_designs[v] + other.AddDesign2Known(D) + other.RefreshResearch() + + +//Autolathe files +/datum/research/autolathe + +/datum/research/autolathe/DesignHasReqs(var/datum/design/D) + return D && (D.build_type & AUTOLATHE) && ("initial" in D.category) + +/datum/research/autolathe/AddDesign2Known(var/datum/design/D) + if(!(D.build_type & AUTOLATHE)) + return + ..() + +//Biogenerator files +/datum/research/biogenerator/New() + for(var/T in (subtypesof(/datum/tech))) + possible_tech += new T(src) + for(var/path in subtypesof(/datum/design)) + var/datum/design/D = new path(src) + possible_designs += D + if((D.build_type & BIOGENERATOR) && ("initial" in D.category)) + AddDesign2Known(D) + +/datum/research/biogenerator/AddDesign2Known(datum/design/D) + if(!(D.build_type & BIOGENERATOR)) + return + ..() + +//Smelter files +/datum/research/smelter/New() + for(var/T in (subtypesof(/datum/tech))) + possible_tech += new T(src) + for(var/path in subtypesof(/datum/design)) + var/datum/design/D = new path(src) + possible_designs += D + if((D.build_type & SMELTER) && ("initial" in D.category)) + AddDesign2Known(D) + +/datum/research/smelter/AddDesign2Known(datum/design/D) + if(!(D.build_type & SMELTER)) + return + ..() + +/*************************************************************** +** Technology Datums ** +** Includes all the various technoliges and what they make. ** +***************************************************************/ + +/datum/tech //Datum of individual technologies. + var/name = "name" //Name of the technology. + var/desc = "description" //General description of what it does and what it makes. + var/id = "id" //An easily referenced ID. Must be alphanumeric, lower-case, and no symbols. + var/level = 1 //A simple number scale of the research level. Level 0 = Secret tech. + var/max_level = 1 // Maximum level this can be at (for job objectives) + var/rare = 1 //How much CentCom wants to get that tech. Used in supply shuttle tech cost calculation. + var/list/req_tech = list() //List of ids associated values of techs required to research this tech. "id" = # + + +//Trunk Technologies (don't require any other techs and you start knowning them). + +/datum/tech/materials + name = "Materials Research" + desc = "Development of new and improved materials." + id = "materials" + max_level = 7 + +/datum/tech/engineering + name = "Engineering Research" + desc = "Development of new and improved engineering parts and methods." + id = "engineering" + max_level = 7 + +/datum/tech/plasmatech + name = "Plasma Research" + desc = "Research into the mysterious substance colloqually known as 'plasma'." + id = "plasmatech" + max_level = 7 + rare = 3 + +/datum/tech/powerstorage + name = "Power Manipulation Technology" + desc = "The various technologies behind the storage and generation of electicity." + id = "powerstorage" + max_level = 7 + +/datum/tech/bluespace + name = "'Blue-space' Research" + desc = "Research into the sub-reality known as 'blue-space'." + id = "bluespace" + max_level = 7 + rare = 2 + +/datum/tech/biotech + name = "Biological Technology" + desc = "Research into the deeper mysteries of life and organic substances." + id = "biotech" + max_level = 7 + +/datum/tech/combat + name = "Combat Systems Research" + desc = "The development of offensive and defensive systems." + id = "combat" + max_level = 7 + +/datum/tech/magnets + name = "Electromagnetic Spectrum Research" + desc = "Research into the electromagnetic spectrum. No clue how they actually work, though." + id = "magnets" + max_level = 7 + +/datum/tech/programming + name = "Data Theory Research" + desc = "The development of new computer and artificial intelligence and data storage systems." + id = "programming" + max_level = 7 + +/datum/tech/toxins //not meant to be raised by deconstruction, do not give objects toxins as an origin_tech + name = "Toxins Research" + desc = "Research into plasma based explosive devices. Upgrade through testing explosives in the toxins lab." + id = "toxins" + max_level = 7 + rare = 2 + +/datum/tech/syndicate + name = "Illegal Technologies Research" + desc = "The study of technologies that violate standard Nanotrasen regulations." + id = "syndicate" + max_level = 0 // Don't count towards maxed research, since it's illegal. + rare = 4 + +/datum/tech/abductor + name = "Alien Technologies Research" + desc = "The study of technologies used by the advanced alien race known as Abductors." + id = "abductor" + rare = 5 + level = 0 + +/* +datum/tech/arcane + name = "Arcane Research" + desc = "Research into the occult and arcane field for use in practical science" + id = "arcane" + level = 0 //It didn't become "secret" as advertised. + +//Branch Techs +datum/tech/explosives + name = "Explosives Research" + desc = "The creation and application of explosive materials." + id = "explosives" + req_tech = list("materials" = 3) + +datum/tech/generators + name = "Power Generation Technology" + desc = "Research into more powerful and more reliable sources." + id = "generators" + req_tech = list("powerstorage" = 2) + +datum/tech/robotics + name = "Robotics Technology" + desc = "The development of advanced automated, autonomous machines." + id = "robotics" + req_tech = list("materials" = 3, "programming" = 3) +*/ + +/datum/tech/proc/getCost(var/current_level = null) + // Calculates tech disk's supply points sell cost + if(!current_level) + current_level = initial(level) + + if(current_level >= level) + return 0 + + var/cost = 0 + for(var/i=current_level+1, i<=level, i++) + if(i == initial(level)) + continue + cost += i*5*rare + + return cost + +/obj/item/disk/tech_disk + name = "\improper Technology Disk" + desc = "A disk for storing technology data for further research." + icon_state = "datadisk2" + materials = list(MAT_METAL=30, MAT_GLASS=10) + var/datum/tech/stored + var/default_name = "\improper Technology Disk" + var/default_desc = "A disk for storing technology data for further research." + +/obj/item/disk/tech_disk/New() + ..() + src.pixel_x = rand(-5.0, 5) + src.pixel_y = rand(-5.0, 5) + +/obj/item/disk/tech_disk/proc/load_tech(datum/tech/T) + name = "[default_name] \[[T]\]" + desc = T.desc + " Level: '[T.level]'" + // NOTE: This is just a reference to the tech on the system it grabbed it from + // This seems highly fragile + stored = T + +/obj/item/disk/tech_disk/proc/wipe_tech() + name = default_name + desc = default_desc + stored = null + +/obj/item/disk/design_disk + name = "\improper Component Design Disk" + desc = "A disk for storing device design data for construction in lathes." + icon_state = "datadisk2" + materials = list(MAT_METAL=100, MAT_GLASS=100) + var/datum/design/blueprint + // I'm doing this so that disk paths with pre-loaded designs don't get weird names + // Otherwise, I'd use "initial()" + var/default_name = "\improper Component Design Disk" + var/default_desc = "A disk for storing device design data for construction in lathes." + +/obj/item/disk/design_disk/New() + ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/item/disk/design_disk/proc/load_blueprint(datum/design/D) + name = "[default_name] \[[D]\]" + desc = D.desc + // NOTE: This is just a reference to the design on the system it grabbed it from + // This seems highly fragile + blueprint = D + +/obj/item/disk/design_disk/proc/wipe_blueprint() + name = default_name + desc = default_desc + blueprint = null + +/obj/item/disk/design_disk/golem_shell + name = "golem creation disk" + desc = "A gift from the Liberator." + icon_state = "datadisk1" + +/obj/item/disk/design_disk/golem_shell/Initialize() + . = ..() + var/datum/design/golem_shell/G = new + blueprint = G diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index ff362b8effc..de9194953da 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -1,360 +1,360 @@ -/obj/machinery/r_n_d/server - name = "R&D Server" - icon = 'icons/obj/machines/research.dmi' - icon_state = "server" - var/datum/research/files - var/health = 100 - var/list/id_with_upload = list() //List of R&D consoles with upload to server access. - var/list/id_with_download = list() //List of R&D consoles with download from server access. - var/id_with_upload_string = "" //String versions for easy editing in map editor. - var/id_with_download_string = "" - var/server_id = 0 - var/heat_gen = 100 - var/heating_power = 40000 - var/delay = 10 - req_access = list(ACCESS_RD) //Only the R&D can change server settings. - var/plays_sound = 0 - -/obj/machinery/r_n_d/server/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/rdserver(null) - component_parts += new /obj/item/stock_parts/scanning_module(null) - component_parts += new /obj/item/stack/cable_coil(null,1) - component_parts += new /obj/item/stack/cable_coil(null,1) - RefreshParts() - initialize_serv(); //Agouri // fuck you agouri - -/obj/machinery/r_n_d/server/upgraded/New() - ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/rdserver(null) - component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) - component_parts += new /obj/item/stack/cable_coil(null,1) - component_parts += new /obj/item/stack/cable_coil(null,1) - RefreshParts() - -/obj/machinery/r_n_d/server/Destroy() - griefProtection() - return ..() - -/obj/machinery/r_n_d/server/RefreshParts() - var/tot_rating = 0 - for(var/obj/item/stock_parts/SP in src) - tot_rating += SP.rating - heat_gen /= max(1, tot_rating) - -/obj/machinery/r_n_d/server/proc/initialize_serv() - if(!files) - files = new /datum/research(src) - var/list/temp_list - if(!id_with_upload.len) - temp_list = list() - temp_list = splittext(id_with_upload_string, ";") - for(var/N in temp_list) - id_with_upload += text2num(N) - if(!id_with_download.len) - temp_list = list() - temp_list = splittext(id_with_download_string, ";") - for(var/N in temp_list) - id_with_download += text2num(N) - -/obj/machinery/r_n_d/server/process() - if(prob(3) && plays_sound) - playsound(loc, "computer_ambience", 50, 1) - - var/datum/gas_mixture/environment = loc.return_air() - switch(environment.temperature) - if(0 to T0C) - health = min(100, health + 1) - if(T0C to (T20C + 20)) - health = Clamp(health, 0, 100) - if((T20C + 20) to (T0C + 70)) - health = max(0, health - 1) - if(health <= 0) - /*griefProtection() This seems to get called twice before running any code that deletes/damages the server or it's files anwyay. - refreshParts and the hasReq procs that get called by this are laggy and do not need to be called by every server on the map every tick */ - var/updateRD = 0 - files.known_designs = list() - for(var/v in files.known_tech) - var/datum/tech/T = files.known_tech[v] - // Slowly decrease research if health drops below 0 - if(prob(1)) - updateRD++ - T.level-- - if(updateRD) - files.RefreshResearch() - if(delay) - delay-- - else - produce_heat(heat_gen) - delay = initial(delay) - -/obj/machinery/r_n_d/server/emp_act(severity) - griefProtection() - ..() - - -/obj/machinery/r_n_d/server/ex_act(severity) - griefProtection() - return ..() - -/obj/machinery/r_n_d/server/blob_act(obj/structure/blob/B) - griefProtection() - return ..() - -// Backup files to CentComm to help admins recover data after griefer attacks -/obj/machinery/r_n_d/server/proc/griefProtection() - for(var/obj/machinery/r_n_d/server/centcom/C in GLOB.machines) - files.push_data(C.files) - -/obj/machinery/r_n_d/server/proc/produce_heat(heat_amt) - if(!(stat & (NOPOWER|BROKEN))) // Blatantly stolen from space heater. - var/turf/simulated/L = loc - if(istype(L)) - var/datum/gas_mixture/env = L.return_air() - if(env.temperature < (heat_amt+T0C)) - - var/transfer_moles = 0.25 * env.total_moles() - - var/datum/gas_mixture/removed = env.remove(transfer_moles) - - if(removed) - - var/heat_capacity = removed.heat_capacity() - if(heat_capacity == 0 || heat_capacity == null) - heat_capacity = 1 - removed.temperature = min((removed.temperature*heat_capacity + heating_power)/heat_capacity, 1000) - - env.merge(removed) - air_update_turf() - -/obj/machinery/r_n_d/server/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - if(disabled) - return - - if(shocked) - shock(user,50) - - if(istype(O, /obj/item/screwdriver)) - default_deconstruction_screwdriver(user, "server_o", "server", O) - return 1 - - if(exchange_parts(user, O)) - return 1 - - if(panel_open) - if(istype(O, /obj/item/crowbar)) - griefProtection() - default_deconstruction_crowbar(user, O) - return 1 - else - return ..() - -/obj/machinery/r_n_d/server/attack_hand(mob/user as mob) - if(disabled) - return - - if(shocked) - shock(user,50) - return - -/obj/machinery/r_n_d/server/centcom - name = "CentComm. Central R&D Database" - server_id = -1 - -/obj/machinery/r_n_d/server/centcom/Initialize() - ..() - var/list/no_id_servers = list() - var/list/server_ids = list() - for(var/obj/machinery/r_n_d/server/S in GLOB.machines) - switch(S.server_id) - if(-1) - continue - if(0) - no_id_servers += S - else - server_ids += S.server_id - - for(var/obj/machinery/r_n_d/server/S in no_id_servers) - var/num = 1 - while(!S.server_id) - if(num in server_ids) - num++ - else - S.server_id = num - server_ids += num - no_id_servers -= S - -/obj/machinery/r_n_d/server/centcom/process() - return PROCESS_KILL //don't need process() - - -/obj/machinery/computer/rdservercontrol - name = "\improper R&D server controller" - icon_screen = "rdcomp" - icon_keyboard = "rd_key" - light_color = LIGHT_COLOR_FADEDPURPLE - circuit = /obj/item/circuitboard/rdservercontrol - var/screen = 0 - var/obj/machinery/r_n_d/server/temp_server - var/list/servers = list() - var/list/consoles = list() - var/badmin = 0 - -/obj/machinery/computer/rdservercontrol/Topic(href, href_list) - if(..()) - return - - add_fingerprint(usr) - usr.set_machine(src) - if(!src.allowed(usr) && !emagged) - to_chat(usr, "You do not have the required access level") - return - - if(href_list["main"]) - screen = 0 - - else if(href_list["access"] || href_list["data"] || href_list["transfer"]) - temp_server = null - consoles = list() - servers = list() - for(var/obj/machinery/r_n_d/server/S in GLOB.machines) - if(S.server_id == text2num(href_list["access"]) || S.server_id == text2num(href_list["data"]) || S.server_id == text2num(href_list["transfer"])) - temp_server = S - break - if(href_list["access"]) - screen = 1 - for(var/obj/machinery/computer/rdconsole/C in GLOB.machines) - if(C.sync) - consoles += C - else if(href_list["data"]) - screen = 2 - else if(href_list["transfer"]) - screen = 3 - for(var/obj/machinery/r_n_d/server/S in GLOB.machines) - if(S == src) - continue - servers += S - - else if(href_list["upload_toggle"]) - var/num = text2num(href_list["upload_toggle"]) - if(num in temp_server.id_with_upload) - temp_server.id_with_upload -= num - else - temp_server.id_with_upload += num - - else if(href_list["download_toggle"]) - var/num = text2num(href_list["download_toggle"]) - if(num in temp_server.id_with_download) - temp_server.id_with_download -= num - else - temp_server.id_with_download += num - - else if(href_list["reset_tech"]) - var/choice = alert("Technology Data Reset", "Are you sure you want to reset this technology to its default data? Data lost cannot be recovered.", "Continue", "Cancel") - if(choice == "Continue") - for(var/I in temp_server.files.known_tech) - var/datum/tech/T = temp_server.files.known_tech[I] - if(T.id == href_list["reset_tech"]) - T.level = 1 - break - temp_server.files.RefreshResearch() - - else if(href_list["reset_design"]) - var/choice = alert("Design Data Deletion", "Are you sure you want to delete this design? Data lost cannot be recovered.", "Continue", "Cancel") - if(choice == "Continue") - for(var/I in temp_server.files.known_designs) - var/datum/design/D = temp_server.files.known_designs[I] - if(D.id == href_list["reset_design"]) - temp_server.files.known_designs -= D.id - break - temp_server.files.RefreshResearch() - - updateUsrDialog() - return - -/obj/machinery/computer/rdservercontrol/attack_hand(mob/user as mob) - if(stat & (BROKEN|NOPOWER)) - return - user.set_machine(src) - var/dat = "" - - switch(screen) - if(0) //Main Menu - dat += "Connected Servers:

        " - - for(var/obj/machinery/r_n_d/server/S in GLOB.machines) - if(istype(S, /obj/machinery/r_n_d/server/centcom) && !badmin) - continue - dat += "[S.name] || " - dat += "Access Rights | " - dat += "Data Management" - if(badmin) dat += " | Server-to-Server Transfer" - dat += "
        " - - if(1) //Access rights menu - dat += "[temp_server.name] Access Rights

        " - dat += "Consoles with Upload Access
        " - for(var/obj/machinery/computer/rdconsole/C in consoles) - var/turf/console_turf = get_turf(C) - dat += "* [console_turf.loc]" //FYI, these are all numeric ids, eventually. - if(C.id in temp_server.id_with_upload) - dat += " (Remove)
        " - else - dat += " (Add)
        " - dat += "Consoles with Download Access
        " - for(var/obj/machinery/computer/rdconsole/C in consoles) - var/turf/console_turf = get_turf(C) - dat += "* [console_turf.loc]" - if(C.id in temp_server.id_with_download) - dat += " (Remove)
        " - else - dat += " (Add)
        " - dat += "
        Main Menu" - - if(2) //Data Management menu - dat += "[temp_server.name] Data Management

        " - dat += "Known Technologies
        " - for(var/I in temp_server.files.known_tech) - var/datum/tech/T = temp_server.files.known_tech[I] - if(T.level <= 0) - continue - dat += "* [T.name] " - dat += "(Reset)
        " //FYI, these are all strings. - dat += "Known Designs
        " - for(var/I in temp_server.files.known_designs) - var/datum/design/D = temp_server.files.known_designs[I] - dat += "* [D.name] " - dat += "(Delete)
        " - dat += "
        Main Menu" - - if(3) //Server Data Transfer - dat += "[temp_server.name] Server to Server Transfer

        " - dat += "Send Data to what server?
        " - for(var/obj/machinery/r_n_d/server/S in servers) - dat += "[S.name] (Transfer)
        " - dat += "
        Main Menu" - user << browse("R&D Server Control
        [dat]", "window=server_control;size=575x400") - onclose(user, "server_control") - return - -/obj/machinery/computer/rdservercontrol/emag_act(user as mob) - if(!emagged) - playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1) - emagged = 1 - to_chat(user, "You you disable the security protocols") - src.updateUsrDialog() - -/obj/machinery/r_n_d/server/core - name = "Core R&D Server" - id_with_upload_string = "1;3" - id_with_download_string = "1;3" - server_id = 1 - plays_sound = 1 - -/obj/machinery/r_n_d/server/robotics - name = "Robotics and Mechanic R&D Server" - id_with_upload_string = "1;2;4" - id_with_download_string = "1;2;4" - server_id = 2 +/obj/machinery/r_n_d/server + name = "R&D Server" + icon = 'icons/obj/machines/research.dmi' + icon_state = "server" + var/datum/research/files + var/health = 100 + var/list/id_with_upload = list() //List of R&D consoles with upload to server access. + var/list/id_with_download = list() //List of R&D consoles with download from server access. + var/id_with_upload_string = "" //String versions for easy editing in map editor. + var/id_with_download_string = "" + var/server_id = 0 + var/heat_gen = 100 + var/heating_power = 40000 + var/delay = 10 + req_access = list(ACCESS_RD) //Only the R&D can change server settings. + var/plays_sound = 0 + +/obj/machinery/r_n_d/server/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/rdserver(null) + component_parts += new /obj/item/stock_parts/scanning_module(null) + component_parts += new /obj/item/stack/cable_coil(null,1) + component_parts += new /obj/item/stack/cable_coil(null,1) + RefreshParts() + initialize_serv(); //Agouri // fuck you agouri + +/obj/machinery/r_n_d/server/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/rdserver(null) + component_parts += new /obj/item/stock_parts/scanning_module/phasic(null) + component_parts += new /obj/item/stack/cable_coil(null,1) + component_parts += new /obj/item/stack/cable_coil(null,1) + RefreshParts() + +/obj/machinery/r_n_d/server/Destroy() + griefProtection() + return ..() + +/obj/machinery/r_n_d/server/RefreshParts() + var/tot_rating = 0 + for(var/obj/item/stock_parts/SP in src) + tot_rating += SP.rating + heat_gen /= max(1, tot_rating) + +/obj/machinery/r_n_d/server/proc/initialize_serv() + if(!files) + files = new /datum/research(src) + var/list/temp_list + if(!id_with_upload.len) + temp_list = list() + temp_list = splittext(id_with_upload_string, ";") + for(var/N in temp_list) + id_with_upload += text2num(N) + if(!id_with_download.len) + temp_list = list() + temp_list = splittext(id_with_download_string, ";") + for(var/N in temp_list) + id_with_download += text2num(N) + +/obj/machinery/r_n_d/server/process() + if(prob(3) && plays_sound) + playsound(loc, "computer_ambience", 50, 1) + + var/datum/gas_mixture/environment = loc.return_air() + switch(environment.temperature) + if(0 to T0C) + health = min(100, health + 1) + if(T0C to (T20C + 20)) + health = Clamp(health, 0, 100) + if((T20C + 20) to (T0C + 70)) + health = max(0, health - 1) + if(health <= 0) + /*griefProtection() This seems to get called twice before running any code that deletes/damages the server or it's files anwyay. + refreshParts and the hasReq procs that get called by this are laggy and do not need to be called by every server on the map every tick */ + var/updateRD = 0 + files.known_designs = list() + for(var/v in files.known_tech) + var/datum/tech/T = files.known_tech[v] + // Slowly decrease research if health drops below 0 + if(prob(1)) + updateRD++ + T.level-- + if(updateRD) + files.RefreshResearch() + if(delay) + delay-- + else + produce_heat(heat_gen) + delay = initial(delay) + +/obj/machinery/r_n_d/server/emp_act(severity) + griefProtection() + ..() + + +/obj/machinery/r_n_d/server/ex_act(severity) + griefProtection() + return ..() + +/obj/machinery/r_n_d/server/blob_act(obj/structure/blob/B) + griefProtection() + return ..() + +// Backup files to CentComm to help admins recover data after griefer attacks +/obj/machinery/r_n_d/server/proc/griefProtection() + for(var/obj/machinery/r_n_d/server/centcom/C in GLOB.machines) + files.push_data(C.files) + +/obj/machinery/r_n_d/server/proc/produce_heat(heat_amt) + if(!(stat & (NOPOWER|BROKEN))) // Blatantly stolen from space heater. + var/turf/simulated/L = loc + if(istype(L)) + var/datum/gas_mixture/env = L.return_air() + if(env.temperature < (heat_amt+T0C)) + + var/transfer_moles = 0.25 * env.total_moles() + + var/datum/gas_mixture/removed = env.remove(transfer_moles) + + if(removed) + + var/heat_capacity = removed.heat_capacity() + if(heat_capacity == 0 || heat_capacity == null) + heat_capacity = 1 + removed.temperature = min((removed.temperature*heat_capacity + heating_power)/heat_capacity, 1000) + + env.merge(removed) + air_update_turf() + +/obj/machinery/r_n_d/server/attackby(var/obj/item/O as obj, var/mob/user as mob, params) + if(disabled) + return + + if(shocked) + shock(user,50) + + if(istype(O, /obj/item/screwdriver)) + default_deconstruction_screwdriver(user, "server_o", "server", O) + return 1 + + if(exchange_parts(user, O)) + return 1 + + if(panel_open) + if(istype(O, /obj/item/crowbar)) + griefProtection() + default_deconstruction_crowbar(user, O) + return 1 + else + return ..() + +/obj/machinery/r_n_d/server/attack_hand(mob/user as mob) + if(disabled) + return + + if(shocked) + shock(user,50) + return + +/obj/machinery/r_n_d/server/centcom + name = "CentComm. Central R&D Database" + server_id = -1 + +/obj/machinery/r_n_d/server/centcom/Initialize() + ..() + var/list/no_id_servers = list() + var/list/server_ids = list() + for(var/obj/machinery/r_n_d/server/S in GLOB.machines) + switch(S.server_id) + if(-1) + continue + if(0) + no_id_servers += S + else + server_ids += S.server_id + + for(var/obj/machinery/r_n_d/server/S in no_id_servers) + var/num = 1 + while(!S.server_id) + if(num in server_ids) + num++ + else + S.server_id = num + server_ids += num + no_id_servers -= S + +/obj/machinery/r_n_d/server/centcom/process() + return PROCESS_KILL //don't need process() + + +/obj/machinery/computer/rdservercontrol + name = "\improper R&D server controller" + icon_screen = "rdcomp" + icon_keyboard = "rd_key" + light_color = LIGHT_COLOR_FADEDPURPLE + circuit = /obj/item/circuitboard/rdservercontrol + var/screen = 0 + var/obj/machinery/r_n_d/server/temp_server + var/list/servers = list() + var/list/consoles = list() + var/badmin = 0 + +/obj/machinery/computer/rdservercontrol/Topic(href, href_list) + if(..()) + return + + add_fingerprint(usr) + usr.set_machine(src) + if(!src.allowed(usr) && !emagged) + to_chat(usr, "You do not have the required access level") + return + + if(href_list["main"]) + screen = 0 + + else if(href_list["access"] || href_list["data"] || href_list["transfer"]) + temp_server = null + consoles = list() + servers = list() + for(var/obj/machinery/r_n_d/server/S in GLOB.machines) + if(S.server_id == text2num(href_list["access"]) || S.server_id == text2num(href_list["data"]) || S.server_id == text2num(href_list["transfer"])) + temp_server = S + break + if(href_list["access"]) + screen = 1 + for(var/obj/machinery/computer/rdconsole/C in GLOB.machines) + if(C.sync) + consoles += C + else if(href_list["data"]) + screen = 2 + else if(href_list["transfer"]) + screen = 3 + for(var/obj/machinery/r_n_d/server/S in GLOB.machines) + if(S == src) + continue + servers += S + + else if(href_list["upload_toggle"]) + var/num = text2num(href_list["upload_toggle"]) + if(num in temp_server.id_with_upload) + temp_server.id_with_upload -= num + else + temp_server.id_with_upload += num + + else if(href_list["download_toggle"]) + var/num = text2num(href_list["download_toggle"]) + if(num in temp_server.id_with_download) + temp_server.id_with_download -= num + else + temp_server.id_with_download += num + + else if(href_list["reset_tech"]) + var/choice = alert("Technology Data Reset", "Are you sure you want to reset this technology to its default data? Data lost cannot be recovered.", "Continue", "Cancel") + if(choice == "Continue") + for(var/I in temp_server.files.known_tech) + var/datum/tech/T = temp_server.files.known_tech[I] + if(T.id == href_list["reset_tech"]) + T.level = 1 + break + temp_server.files.RefreshResearch() + + else if(href_list["reset_design"]) + var/choice = alert("Design Data Deletion", "Are you sure you want to delete this design? Data lost cannot be recovered.", "Continue", "Cancel") + if(choice == "Continue") + for(var/I in temp_server.files.known_designs) + var/datum/design/D = temp_server.files.known_designs[I] + if(D.id == href_list["reset_design"]) + temp_server.files.known_designs -= D.id + break + temp_server.files.RefreshResearch() + + updateUsrDialog() + return + +/obj/machinery/computer/rdservercontrol/attack_hand(mob/user as mob) + if(stat & (BROKEN|NOPOWER)) + return + user.set_machine(src) + var/dat = "" + + switch(screen) + if(0) //Main Menu + dat += "Connected Servers:

        " + + for(var/obj/machinery/r_n_d/server/S in GLOB.machines) + if(istype(S, /obj/machinery/r_n_d/server/centcom) && !badmin) + continue + dat += "[S.name] || " + dat += "Access Rights | " + dat += "Data Management" + if(badmin) dat += " | Server-to-Server Transfer" + dat += "
        " + + if(1) //Access rights menu + dat += "[temp_server.name] Access Rights

        " + dat += "Consoles with Upload Access
        " + for(var/obj/machinery/computer/rdconsole/C in consoles) + var/turf/console_turf = get_turf(C) + dat += "* [console_turf.loc]" //FYI, these are all numeric ids, eventually. + if(C.id in temp_server.id_with_upload) + dat += " (Remove)
        " + else + dat += " (Add)
        " + dat += "Consoles with Download Access
        " + for(var/obj/machinery/computer/rdconsole/C in consoles) + var/turf/console_turf = get_turf(C) + dat += "* [console_turf.loc]" + if(C.id in temp_server.id_with_download) + dat += " (Remove)
        " + else + dat += " (Add)
        " + dat += "
        Main Menu" + + if(2) //Data Management menu + dat += "[temp_server.name] Data Management

        " + dat += "Known Technologies
        " + for(var/I in temp_server.files.known_tech) + var/datum/tech/T = temp_server.files.known_tech[I] + if(T.level <= 0) + continue + dat += "* [T.name] " + dat += "(Reset)
        " //FYI, these are all strings. + dat += "Known Designs
        " + for(var/I in temp_server.files.known_designs) + var/datum/design/D = temp_server.files.known_designs[I] + dat += "* [D.name] " + dat += "(Delete)
        " + dat += "
        Main Menu" + + if(3) //Server Data Transfer + dat += "[temp_server.name] Server to Server Transfer

        " + dat += "Send Data to what server?
        " + for(var/obj/machinery/r_n_d/server/S in servers) + dat += "[S.name] (Transfer)
        " + dat += "
        Main Menu" + user << browse("R&D Server Control
        [dat]", "window=server_control;size=575x400") + onclose(user, "server_control") + return + +/obj/machinery/computer/rdservercontrol/emag_act(user as mob) + if(!emagged) + playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1) + emagged = 1 + to_chat(user, "You you disable the security protocols") + src.updateUsrDialog() + +/obj/machinery/r_n_d/server/core + name = "Core R&D Server" + id_with_upload_string = "1;3" + id_with_download_string = "1;3" + server_id = 1 + plays_sound = 1 + +/obj/machinery/r_n_d/server/robotics + name = "Robotics and Mechanic R&D Server" + id_with_upload_string = "1;2;4" + id_with_download_string = "1;2;4" + server_id = 2 diff --git a/code/modules/ruins/lavalandruin_code/animal_hospital.dm b/code/modules/ruins/lavalandruin_code/animal_hospital.dm index 62793fa4226..1cf21d5a890 100644 --- a/code/modules/ruins/lavalandruin_code/animal_hospital.dm +++ b/code/modules/ruins/lavalandruin_code/animal_hospital.dm @@ -15,4 +15,4 @@ /obj/effect/mob_spawn/human/doctor/alive/lavaland/Destroy() var/obj/structure/fluff/empty_sleeper/S = new(drop_location()) S.setDir(dir) - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/ruins/lavalandruin_code/ash_walker_den.dm b/code/modules/ruins/lavalandruin_code/ash_walker_den.dm index e95a5381b99..c0b8e0f5555 100644 --- a/code/modules/ruins/lavalandruin_code/ash_walker_den.dm +++ b/code/modules/ruins/lavalandruin_code/ash_walker_den.dm @@ -86,4 +86,4 @@ /datum/outfit/ashwalker name ="Ashwalker" head = /obj/item/clothing/head/helmet/gladiator - uniform = /obj/item/clothing/under/gladiator/ash_walker \ No newline at end of file + uniform = /obj/item/clothing/under/gladiator/ash_walker diff --git a/code/modules/ruins/lavalandruin_code/clown_planet.dm b/code/modules/ruins/lavalandruin_code/clown_planet.dm index 6121c40d2af..3105739126a 100644 --- a/code/modules/ruins/lavalandruin_code/clown_planet.dm +++ b/code/modules/ruins/lavalandruin_code/clown_planet.dm @@ -4,4 +4,4 @@ info = "If you dare not continue down this path of madness, escape can be found through the chute in this room." /obj/item/paper/crumpled/bloody/ruins/lavaland/clown_planet/hope - info = "Abandon hope, all ye who enter here." \ No newline at end of file + info = "Abandon hope, all ye who enter here." diff --git a/code/modules/ruins/lavalandruin_code/fountain_hall.dm b/code/modules/ruins/lavalandruin_code/fountain_hall.dm index 75e2b8d9aa3..26c8ebca7c6 100644 --- a/code/modules/ruins/lavalandruin_code/fountain_hall.dm +++ b/code/modules/ruins/lavalandruin_code/fountain_hall.dm @@ -46,4 +46,4 @@ if(last_process + time_between_uses > world.time) icon_state = "fountain" else - icon_state = "fountain-red" \ No newline at end of file + icon_state = "fountain-red" diff --git a/code/modules/ruins/lavalandruin_code/pizzaparty.dm b/code/modules/ruins/lavalandruin_code/pizzaparty.dm index 3201652c371..192a80e3e7f 100644 --- a/code/modules/ruins/lavalandruin_code/pizzaparty.dm +++ b/code/modules/ruins/lavalandruin_code/pizzaparty.dm @@ -8,4 +8,4 @@ /obj/structure/reagent_dispensers/water_cooler/pizzaparty name = "punch cooler" - reagent_id = "bacchus_blessing" \ No newline at end of file + reagent_id = "bacchus_blessing" diff --git a/code/modules/ruins/lavalandruin_code/puzzle.dm b/code/modules/ruins/lavalandruin_code/puzzle.dm index dcd72b44c64..0220498df5e 100644 --- a/code/modules/ruins/lavalandruin_code/puzzle.dm +++ b/code/modules/ruins/lavalandruin_code/puzzle.dm @@ -348,4 +348,4 @@ //Move them into random block var/obj/structure/puzzle_element/E = pick(cube.elements) prisoner.forceMove(E) - return TRUE \ No newline at end of file + return TRUE diff --git a/code/modules/ruins/lavalandruin_code/seed_vault.dm b/code/modules/ruins/lavalandruin_code/seed_vault.dm index db16c7b3a71..2c999a7ce29 100644 --- a/code/modules/ruins/lavalandruin_code/seed_vault.dm +++ b/code/modules/ruins/lavalandruin_code/seed_vault.dm @@ -30,4 +30,4 @@ /obj/effect/mob_spawn/human/seed_vault/Destroy() new/obj/structure/fluff/empty_terrarium(get_turf(src)) - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/ruins/lavalandruin_code/syndicate_base.dm b/code/modules/ruins/lavalandruin_code/syndicate_base.dm index b6a55a1361f..33e4fb527ae 100644 --- a/code/modules/ruins/lavalandruin_code/syndicate_base.dm +++ b/code/modules/ruins/lavalandruin_code/syndicate_base.dm @@ -83,4 +83,4 @@ new /obj/item/gps/internal/lavaland_syndicate_base(src) /obj/item/gps/internal/lavaland_syndicate_base - gpstag = "Encrypted Signal" \ No newline at end of file + gpstag = "Encrypted Signal" diff --git a/code/modules/ruins/objects_and_mobs/gym.dm b/code/modules/ruins/objects_and_mobs/gym.dm index 69abf1a7e88..e2d593a82d6 100644 --- a/code/modules/ruins/objects_and_mobs/gym.dm +++ b/code/modules/ruins/objects_and_mobs/gym.dm @@ -90,4 +90,4 @@ sleep(3) animate(user, pixel_y = 2, time = 3) sleep(3) - cut_overlay(swole_overlay) \ No newline at end of file + cut_overlay(swole_overlay) diff --git a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm index 5a65a536b46..d5f7a1e1aa2 100644 --- a/code/modules/ruins/objects_and_mobs/necropolis_gate.dm +++ b/code/modules/ruins/objects_and_mobs/necropolis_gate.dm @@ -407,4 +407,4 @@ GLOBAL_DATUM(necropolis_gate, /obj/structure/necropolis_gate/legion_gate) #undef STABLE #undef COLLAPSE_ON_CROSS #undef DESTROY_ON_CROSS -#undef UNIQUE_EFFECT \ No newline at end of file +#undef UNIQUE_EFFECT diff --git a/code/modules/shuttle/README.md b/code/modules/shuttle/README.md index 9568131ba94..6a7168c1269 100644 --- a/code/modules/shuttle/README.md +++ b/code/modules/shuttle/README.md @@ -143,4 +143,4 @@ not yet created when `New()` is called. To fix this issue, all docking ports will not initialize automatically on `New()`. Instead, they are manually initialized by the shuttle controller when it is created, via -a proc called `initialize()`. \ No newline at end of file +a proc called `initialize()`. diff --git a/code/modules/shuttle/ert.dm b/code/modules/shuttle/ert.dm index 3bc6f627354..1f664a69c8f 100644 --- a/code/modules/shuttle/ert.dm +++ b/code/modules/shuttle/ert.dm @@ -20,4 +20,4 @@ flags = NODECONSTRUCT access_tcomms = FALSE access_construction = FALSE - access_mining = FALSE \ No newline at end of file + access_mining = FALSE diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm index 3aeda513116..c57cea9216e 100644 --- a/code/modules/shuttle/navigation_computer.dm +++ b/code/modules/shuttle/navigation_computer.dm @@ -354,4 +354,4 @@ remote_eye.setLoc(T) to_chat(target, "Jumped to [selected]") else - playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0) \ No newline at end of file + playsound(console, 'sound/machines/terminal_prompt_deny.ogg', 25, 0) diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm index 0a11a4397ad..dbec1c611f7 100644 --- a/code/modules/shuttle/on_move.dm +++ b/code/modules/shuttle/on_move.dm @@ -72,4 +72,4 @@ return FALSE disconnect() LateInitialize() - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index 5260fc2b68e..36ad93eb28e 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -942,4 +942,4 @@ T.color = color if(T.dir != dir) T.dir = dir - return T \ No newline at end of file + return T diff --git a/code/modules/shuttle/shuttle_rotate.dm b/code/modules/shuttle/shuttle_rotate.dm index bea581d4174..2adaad8e166 100644 --- a/code/modules/shuttle/shuttle_rotate.dm +++ b/code/modules/shuttle/shuttle_rotate.dm @@ -88,4 +88,4 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate //prevents shuttles attempting to rotate this since it messes up sprites /obj/machinery/gravity_generator/shuttleRotate(rotation, params) params = NONE - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/syndicate.dm index dc588eda887..f9ec738b167 100644 --- a/code/modules/shuttle/syndicate.dm +++ b/code/modules/shuttle/syndicate.dm @@ -93,4 +93,4 @@ x_offset = 0 y_offset = 0 -#undef SYNDICATE_CHALLENGE_TIMER \ No newline at end of file +#undef SYNDICATE_CHALLENGE_TIMER diff --git a/code/modules/space_management/level_traits.dm b/code/modules/space_management/level_traits.dm index 39b584df64d..6fcd267105f 100644 --- a/code/modules/space_management/level_traits.dm +++ b/code/modules/space_management/level_traits.dm @@ -60,4 +60,4 @@ var/list/default_map_traits = MAP_TRANSITION_CONFIG /proc/level_name_to_num(name) var/datum/space_level/S = space_manager.get_zlev_by_name(name) - return S.zpos \ No newline at end of file + return S.zpos diff --git a/code/modules/space_management/space_chunk.dm b/code/modules/space_management/space_chunk.dm index b311dad0028..fada2527d1c 100644 --- a/code/modules/space_management/space_chunk.dm +++ b/code/modules/space_management/space_chunk.dm @@ -97,4 +97,4 @@ #undef BOTTOM_LEFT_CHUNK #undef BOTTOM_RIGHT_CHUNK #undef TOP_LEFT_CHUNK -#undef TOP_RIGHT_CHUNK \ No newline at end of file +#undef TOP_RIGHT_CHUNK diff --git a/code/modules/space_management/zlevel_manager.dm b/code/modules/space_management/zlevel_manager.dm index acb1cf5c0a0..e45aff76078 100644 --- a/code/modules/space_management/zlevel_manager.dm +++ b/code/modules/space_management/zlevel_manager.dm @@ -168,4 +168,4 @@ var/global/datum/zlev_manager/space_manager = new var/datum/space_level/heap/heap = z_list["[C.zpos]"] if(!istype(heap)) throw EXCEPTION("Attempted to free chunk at invalid z-level ([C.x],[C.y],[C.zpos]) [C.width]x[C.height]") - heap.free(C) \ No newline at end of file + heap.free(C) diff --git a/code/modules/spacepods/construction.dm b/code/modules/spacepods/construction.dm index eba600d883f..be2998fc0a3 100644 --- a/code/modules/spacepods/construction.dm +++ b/code/modules/spacepods/construction.dm @@ -225,4 +225,4 @@ spawn_result(mob/user as mob) ..() feedback_inc("spacepod_created",1) - return \ No newline at end of file + return diff --git a/code/modules/spacepods/lock_buster.dm b/code/modules/spacepods/lock_buster.dm index 75f5e4ee9db..8a83315ce3d 100644 --- a/code/modules/spacepods/lock_buster.dm +++ b/code/modules/spacepods/lock_buster.dm @@ -11,4 +11,4 @@ icon_state = "lock_buster_on" else icon_state = "lock_buster_off" - to_chat(usr, "You turn the [src] [on ? "on" : "off"].") \ No newline at end of file + to_chat(usr, "You turn the [src] [on ? "on" : "off"].") diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm index a55657fc724..5b07d27361e 100644 --- a/code/modules/station_goals/dna_vault.dm +++ b/code/modules/station_goals/dna_vault.dm @@ -323,4 +323,4 @@ var/list/non_simple_animals = typecacheof(list(/mob/living/carbon/human/monkey,/ #undef VAULT_STUNTIME #undef VAULT_ARMOUR #undef VAULT_SPEED -#undef VAULT_QUICK \ No newline at end of file +#undef VAULT_QUICK diff --git a/code/modules/station_goals/station_goal.dm b/code/modules/station_goals/station_goal.dm index 90d4defa26c..d6be4540a01 100644 --- a/code/modules/station_goals/station_goal.dm +++ b/code/modules/station_goals/station_goal.dm @@ -46,4 +46,4 @@ on_report() send_report() else if(href_list["remove"]) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/surgery/cavity_implant.dm b/code/modules/surgery/cavity_implant.dm index e86ab317fce..d7b7621e9c0 100644 --- a/code/modules/surgery/cavity_implant.dm +++ b/code/modules/surgery/cavity_implant.dm @@ -203,4 +203,4 @@ return 1 else to_chat(user, "You don't find anything in [target]'s [target_zone].") - return 0 \ No newline at end of file + return 0 diff --git a/code/modules/surgery/core_removal.dm b/code/modules/surgery/core_removal.dm index 21458720978..a54aa25d228 100644 --- a/code/modules/surgery/core_removal.dm +++ b/code/modules/surgery/core_removal.dm @@ -58,4 +58,4 @@ /datum/surgery_step/slime/extract_core/fail_step(mob/living/user, mob/living/simple_animal/slime/target, target_zone, obj/item/tool) user.visible_message(" [user]'s hand slips, tearing [target]'s flesh with \the [tool]!", \ " Your hand slips, tearing [target]'s flesh with \the [tool]!") - return FALSE \ No newline at end of file + return FALSE diff --git a/code/modules/surgery/dental_implant.dm b/code/modules/surgery/dental_implant.dm index 26e8a2bf577..dece5826719 100644 --- a/code/modules/surgery/dental_implant.dm +++ b/code/modules/surgery/dental_implant.dm @@ -55,4 +55,4 @@ target.reagents.trans_to(owner, target.reagents.total_volume) Remove(owner) qdel(target) - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/surgery/implant_removal.dm b/code/modules/surgery/implant_removal.dm index 48895a3378a..078d0ec1384 100644 --- a/code/modules/surgery/implant_removal.dm +++ b/code/modules/surgery/implant_removal.dm @@ -76,4 +76,4 @@ else user.visible_message(" [user] could not find anything inside [target]'s [affected.name], and pulls \the [tool] out.", \ "You could not find anything inside [target]'s [affected.name].") - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/surgery/limb_augmentation.dm b/code/modules/surgery/limb_augmentation.dm index bec73e6e69b..561257c2595 100644 --- a/code/modules/surgery/limb_augmentation.dm +++ b/code/modules/surgery/limb_augmentation.dm @@ -54,4 +54,4 @@ affected.open = 0 affected.germ_level = 0 - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm index 2973573bc2c..60f4c22743d 100644 --- a/code/modules/surgery/organs/eyes.dm +++ b/code/modules/surgery/organs/eyes.dm @@ -78,4 +78,4 @@ icon_state = "eyes-prosthetic" desc = "An electronic device designed to mimic the functions of a pair of human eyes. It has no benefits over organic eyes, but is easy to produce." origin_tech = "biotech=4" - status = ORGAN_ROBOT \ No newline at end of file + status = ORGAN_ROBOT diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 987315ac13b..9269b2340af 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -267,4 +267,4 @@ owner.adjustFireLoss(numMid) if(prob(numLow)) to_chat(owner, "Your [name] lurches awkwardly!") - owner.ForceContractDisease(new /datum/disease/critical/heart_failure(0)) \ No newline at end of file + owner.ForceContractDisease(new /datum/disease/critical/heart_failure(0)) diff --git a/code/modules/surgery/organs/helpers.dm b/code/modules/surgery/organs/helpers.dm index 91bba8d4a1b..ca2698f2ad4 100644 --- a/code/modules/surgery/organs/helpers.dm +++ b/code/modules/surgery/organs/helpers.dm @@ -122,4 +122,4 @@ for(var/zone in full) if(has_organ(zone)) full -= zone - return full \ No newline at end of file + return full diff --git a/code/modules/surgery/organs/kidneys.dm b/code/modules/surgery/organs/kidneys.dm index 26b59191a7c..9d3048beaba 100644 --- a/code/modules/surgery/organs/kidneys.dm +++ b/code/modules/surgery/organs/kidneys.dm @@ -22,4 +22,4 @@ icon_state = "kidneys-c" desc = "An electronic device designed to mimic the functions of human kidneys. It has no benefits over a pair of organic kidneys, but is easy to produce." origin_tech = "biotech=4" - status = ORGAN_ROBOT \ No newline at end of file + status = ORGAN_ROBOT diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm index ba3112b6392..799b8e8a6ab 100644 --- a/code/modules/surgery/organs/liver.dm +++ b/code/modules/surgery/organs/liver.dm @@ -55,4 +55,4 @@ icon_state = "liver-c" desc = "An electronic device designed to mimic the functions of a human liver. It has no benefits over an organic liver, but is easy to produce." origin_tech = "biotech=4" - status = ORGAN_ROBOT \ No newline at end of file + status = ORGAN_ROBOT diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 47fa74969d0..782bcbe6ca6 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -355,4 +355,4 @@ cold_level_1_threshold = 200 cold_level_2_threshold = 140 - cold_level_3_threshold = 100 \ No newline at end of file + cold_level_3_threshold = 100 diff --git a/code/modules/surgery/organs/mmi_holder.dm b/code/modules/surgery/organs/mmi_holder.dm index d4228fb9625..bd885471ff1 100644 --- a/code/modules/surgery/organs/mmi_holder.dm +++ b/code/modules/surgery/organs/mmi_holder.dm @@ -33,4 +33,4 @@ desc = stored_mmi.desc icon = stored_mmi.icon icon_state = stored_mmi.icon_state - set_dna(stored_mmi.brainmob.dna) \ No newline at end of file + set_dna(stored_mmi.brainmob.dna) diff --git a/code/modules/surgery/organs/pain.dm b/code/modules/surgery/organs/pain.dm index 4863bd63901..91e4a2f6781 100644 --- a/code/modules/surgery/organs/pain.dm +++ b/code/modules/surgery/organs/pain.dm @@ -81,4 +81,4 @@ mob/living/carbon/human/proc/handle_pain() continue if(I.damage > 2 && prob(2)) var/obj/item/organ/external/parent = get_organ(I.parent_organ) - custom_pain("You feel a sharp pain in your [parent.limb_name]") \ No newline at end of file + custom_pain("You feel a sharp pain in your [parent.limb_name]") diff --git a/code/modules/surgery/organs/robolimbs.dm b/code/modules/surgery/organs/robolimbs.dm index d3c297bc0c7..2e25a1c1998 100644 --- a/code/modules/surgery/organs/robolimbs.dm +++ b/code/modules/surgery/organs/robolimbs.dm @@ -153,4 +153,4 @@ var/global/datum/robolimb/basic_robolimb parts = list("head") is_monitor = 1 selectable = 0 - has_subtypes = null \ No newline at end of file + has_subtypes = null diff --git a/code/modules/surgery/organs/subtypes/abductor.dm b/code/modules/surgery/organs/subtypes/abductor.dm index b499135417e..7fabbd96ae1 100644 --- a/code/modules/surgery/organs/subtypes/abductor.dm +++ b/code/modules/surgery/organs/subtypes/abductor.dm @@ -4,4 +4,4 @@ /obj/item/organ/internal/eyes/abductor name = "abductor eyeballs" - see_in_dark = 3 \ No newline at end of file + see_in_dark = 3 diff --git a/code/modules/surgery/organs/subtypes/diona.dm b/code/modules/surgery/organs/subtypes/diona.dm index 0e76c8fe20b..3317aca4648 100644 --- a/code/modules/surgery/organs/subtypes/diona.dm +++ b/code/modules/surgery/organs/subtypes/diona.dm @@ -102,4 +102,4 @@ name = "nutrient vessel" icon = 'icons/mob/alien.dmi' icon_state = "claw" - alcohol_intensity = 0.5 \ No newline at end of file + alcohol_intensity = 0.5 diff --git a/code/modules/surgery/organs/subtypes/drask.dm b/code/modules/surgery/organs/subtypes/drask.dm index 49b11d096b6..201c05b83e3 100644 --- a/code/modules/surgery/organs/subtypes/drask.dm +++ b/code/modules/surgery/organs/subtypes/drask.dm @@ -26,4 +26,4 @@ name = "drask eyeballs" icon = 'icons/obj/species_organs/drask.dmi' desc = "Drask eyes. They look even stranger disembodied" - see_in_dark = 5 \ No newline at end of file + see_in_dark = 5 diff --git a/code/modules/surgery/organs/subtypes/grey.dm b/code/modules/surgery/organs/subtypes/grey.dm index 6e4a0e3eafd..15b7fea2264 100644 --- a/code/modules/surgery/organs/subtypes/grey.dm +++ b/code/modules/surgery/organs/subtypes/grey.dm @@ -35,4 +35,4 @@ /obj/item/organ/internal/kidneys/grey name = "grey kidneys" - icon = 'icons/obj/species_organs/grey.dmi' \ No newline at end of file + icon = 'icons/obj/species_organs/grey.dmi' diff --git a/code/modules/surgery/organs/subtypes/kidan.dm b/code/modules/surgery/organs/subtypes/kidan.dm index b87531ac337..6407917bcae 100644 --- a/code/modules/surgery/organs/subtypes/kidan.dm +++ b/code/modules/surgery/organs/subtypes/kidan.dm @@ -135,4 +135,4 @@ #undef KIDAN_LANTERN_HUNGERCOST #undef KIDAN_LANTERN_MINHUNGER -#undef KIDAN_LANTERN_LIGHT \ No newline at end of file +#undef KIDAN_LANTERN_LIGHT diff --git a/code/modules/surgery/organs/subtypes/machine.dm b/code/modules/surgery/organs/subtypes/machine.dm index 837e9b5ffb7..52cad684e11 100644 --- a/code/modules/surgery/organs/subtypes/machine.dm +++ b/code/modules/surgery/organs/subtypes/machine.dm @@ -145,4 +145,4 @@ /obj/item/organ/internal/ears/microphone/remove(mob/living/user, special = FALSE) if(!special) to_chat(owner, "BZZZZZZZZZZZZZZT! Microphone error!") - . = ..() \ No newline at end of file + . = ..() diff --git a/code/modules/surgery/organs/subtypes/nucleation.dm b/code/modules/surgery/organs/subtypes/nucleation.dm index 3be564fc800..bac821f7f6c 100644 --- a/code/modules/surgery/organs/subtypes/nucleation.dm +++ b/code/modules/surgery/organs/subtypes/nucleation.dm @@ -31,4 +31,4 @@ /obj/item/organ/internal/brain/crystal name = "crystallized brain" icon_state = "crystal-brain" - organ_tag = "crystallized brain" \ No newline at end of file + organ_tag = "crystallized brain" diff --git a/code/modules/surgery/organs/subtypes/plasmaman.dm b/code/modules/surgery/organs/subtypes/plasmaman.dm index 3fd59189fb0..4de19d0950e 100644 --- a/code/modules/surgery/organs/subtypes/plasmaman.dm +++ b/code/modules/surgery/organs/subtypes/plasmaman.dm @@ -18,4 +18,4 @@ /obj/item/organ/internal/kidneys/plasmaman name = "plasmaman kidneys" - icon = 'icons/obj/species_organs/plasmaman.dmi' \ No newline at end of file + icon = 'icons/obj/species_organs/plasmaman.dmi' diff --git a/code/modules/surgery/organs/subtypes/shadow.dm b/code/modules/surgery/organs/subtypes/shadow.dm index 56bec1a4a7e..d147feb0317 100644 --- a/code/modules/surgery/organs/subtypes/shadow.dm +++ b/code/modules/surgery/organs/subtypes/shadow.dm @@ -1,3 +1,3 @@ /obj/item/organ/internal/eyes/shadow name = "dark orbs" - see_in_dark = 8 \ No newline at end of file + see_in_dark = 8 diff --git a/code/modules/surgery/organs/subtypes/skrell.dm b/code/modules/surgery/organs/subtypes/skrell.dm index 3c1e3cd40ce..8a56956777f 100644 --- a/code/modules/surgery/organs/subtypes/skrell.dm +++ b/code/modules/surgery/organs/subtypes/skrell.dm @@ -87,4 +87,4 @@ /obj/item/organ/internal/eyes/skrell name = "skrell eyeballs" - icon = 'icons/obj/species_organs/skrell.dmi' \ No newline at end of file + icon = 'icons/obj/species_organs/skrell.dmi' diff --git a/code/modules/surgery/organs/subtypes/slime.dm b/code/modules/surgery/organs/subtypes/slime.dm index 14c89aded63..752375047fe 100644 --- a/code/modules/surgery/organs/subtypes/slime.dm +++ b/code/modules/surgery/organs/subtypes/slime.dm @@ -12,4 +12,4 @@ icon = 'icons/obj/species_organs/slime.dmi' name = "slime lungs" icon_state = "lungs" - desc = "This is a slime's gas exchange membrane, this membrane used for oxygen intake and gas exchange. These seem to work similar to lungs." \ No newline at end of file + desc = "This is a slime's gas exchange membrane, this membrane used for oxygen intake and gas exchange. These seem to work similar to lungs." diff --git a/code/modules/surgery/organs/subtypes/tajaran.dm b/code/modules/surgery/organs/subtypes/tajaran.dm index 99598a80fb1..e8d87b55b89 100644 --- a/code/modules/surgery/organs/subtypes/tajaran.dm +++ b/code/modules/surgery/organs/subtypes/tajaran.dm @@ -32,4 +32,4 @@ /obj/item/organ/internal/kidneys/tajaran name = "tajaran kidneys" - icon = 'icons/obj/species_organs/tajaran.dmi' \ No newline at end of file + icon = 'icons/obj/species_organs/tajaran.dmi' diff --git a/code/modules/surgery/organs/subtypes/unathi.dm b/code/modules/surgery/organs/subtypes/unathi.dm index a612bc7f253..15409b85d7e 100644 --- a/code/modules/surgery/organs/subtypes/unathi.dm +++ b/code/modules/surgery/organs/subtypes/unathi.dm @@ -27,4 +27,4 @@ /obj/item/organ/internal/kidneys/unathi name = "unathi kidneys" - icon = 'icons/obj/species_organs/unathi.dmi' \ No newline at end of file + icon = 'icons/obj/species_organs/unathi.dmi' diff --git a/code/modules/surgery/organs/subtypes/unbreakable.dm b/code/modules/surgery/organs/subtypes/unbreakable.dm index 245629ba26e..b4fca2150fc 100644 --- a/code/modules/surgery/organs/subtypes/unbreakable.dm +++ b/code/modules/surgery/organs/subtypes/unbreakable.dm @@ -65,4 +65,4 @@ cannot_amputate = TRUE /obj/item/organ/external/head/unbreakable/sturdy - cannot_amputate = TRUE \ No newline at end of file + cannot_amputate = TRUE diff --git a/code/modules/surgery/organs/subtypes/vox.dm b/code/modules/surgery/organs/subtypes/vox.dm index e7366c748c2..1ab1eef7238 100644 --- a/code/modules/surgery/organs/subtypes/vox.dm +++ b/code/modules/surgery/organs/subtypes/vox.dm @@ -26,4 +26,4 @@ /obj/item/organ/internal/kidneys/vox name = "vox kidneys" icon = 'icons/obj/species_organs/vox.dmi' - sterile = TRUE \ No newline at end of file + sterile = TRUE diff --git a/code/modules/surgery/organs/subtypes/vulpkanin.dm b/code/modules/surgery/organs/subtypes/vulpkanin.dm index 820b152c43f..a2815d32dc6 100644 --- a/code/modules/surgery/organs/subtypes/vulpkanin.dm +++ b/code/modules/surgery/organs/subtypes/vulpkanin.dm @@ -32,4 +32,4 @@ /obj/item/organ/internal/kidneys/vulpkanin name = "vulpkanin kidneys" - icon = 'icons/obj/species_organs/vulpkanin.dmi' \ No newline at end of file + icon = 'icons/obj/species_organs/vulpkanin.dmi' diff --git a/code/modules/surgery/organs/subtypes/wryn.dm b/code/modules/surgery/organs/subtypes/wryn.dm index 8900bb8b37d..cbc60599744 100644 --- a/code/modules/surgery/organs/subtypes/wryn.dm +++ b/code/modules/surgery/organs/subtypes/wryn.dm @@ -8,4 +8,4 @@ slot = "hivenode" /obj/item/organ/internal/eyes/wryn - see_in_dark = 3 \ No newline at end of file + see_in_dark = 3 diff --git a/code/modules/surgery/plastic_surgery.dm b/code/modules/surgery/plastic_surgery.dm index 0aac0bf705f..ed93f95c3c9 100644 --- a/code/modules/surgery/plastic_surgery.dm +++ b/code/modules/surgery/plastic_surgery.dm @@ -53,4 +53,4 @@ user.visible_message(" [user]'s hand slips, tearing skin on [target]'s face with [tool]!", \ " Your hand slips, tearing skin on [target]'s face with [tool]!") target.apply_damage(10, BRUTE, head, sharp = TRUE) - return FALSE \ No newline at end of file + return FALSE diff --git a/code/modules/surgery/remove_embedded_object.dm b/code/modules/surgery/remove_embedded_object.dm index 340492545be..2040138640f 100644 --- a/code/modules/surgery/remove_embedded_object.dm +++ b/code/modules/surgery/remove_embedded_object.dm @@ -63,4 +63,4 @@ else to_chat(user, "You can't find [target]'s [parse_zone(user.zone_selected)], let alone any objects embedded in it!") - return 1 \ No newline at end of file + return 1 diff --git a/code/modules/tram/tram_control_pad.dm b/code/modules/tram/tram_control_pad.dm index 16d2252ef82..3bbb3811de2 100644 --- a/code/modules/tram/tram_control_pad.dm +++ b/code/modules/tram/tram_control_pad.dm @@ -30,4 +30,4 @@ usr << browse(null, "window=trampad") src.add_fingerprint(usr) - src.updateUsrDialog() \ No newline at end of file + src.updateUsrDialog() diff --git a/code/modules/tram/tram_floor.dm b/code/modules/tram/tram_floor.dm index fc3b1d162d5..a5c3ec2a3a0 100644 --- a/code/modules/tram/tram_floor.dm +++ b/code/modules/tram/tram_floor.dm @@ -17,4 +17,4 @@ if(istype(TF)) if(TF in controller.tram_floors) continue controller.add_floor(TF) - TF.spread_floors() \ No newline at end of file + TF.spread_floors() diff --git a/code/modules/tram/tram_rail.dm b/code/modules/tram/tram_rail.dm index 2a22f8d4238..254f2e8636b 100644 --- a/code/modules/tram/tram_rail.dm +++ b/code/modules/tram/tram_rail.dm @@ -5,4 +5,4 @@ icon_state = "rail" var/godir = null var/stop_duration = null - layer = TURF_LAYER + 0.1 \ No newline at end of file + layer = TURF_LAYER + 0.1 diff --git a/code/modules/tram/tram_wall.dm b/code/modules/tram/tram_wall.dm index bfde77f6a4c..73c278f0ca0 100644 --- a/code/modules/tram/tram_wall.dm +++ b/code/modules/tram/tram_wall.dm @@ -18,4 +18,4 @@ if(istype(TW)) if(TW in controller.tram_walls) continue controller.add_wall(TW) - TW.spread_walls() \ No newline at end of file + TW.spread_walls() diff --git a/code/modules/vehicle/ambulance.dm b/code/modules/vehicle/ambulance.dm index 7b4797c6fe3..450a6c88372 100644 --- a/code/modules/vehicle/ambulance.dm +++ b/code/modules/vehicle/ambulance.dm @@ -118,4 +118,4 @@ to_chat(usr, "You unhook the bed to the ambulance.") else amb.bed = src - to_chat(usr, "You hook the bed to the ambulance.") \ No newline at end of file + to_chat(usr, "You hook the bed to the ambulance.") diff --git a/code/modules/vehicle/atv.dm b/code/modules/vehicle/atv.dm index 39d3e7e4612..a077c848f05 100644 --- a/code/modules/vehicle/atv.dm +++ b/code/modules/vehicle/atv.dm @@ -74,4 +74,4 @@ turret.pixel_y = 4 if(WEST) turret.pixel_x = 12 - turret.pixel_y = 4 \ No newline at end of file + turret.pixel_y = 4 diff --git a/code/modules/vehicle/janicart.dm b/code/modules/vehicle/janicart.dm index 7df2f8da59a..12ed9f727eb 100644 --- a/code/modules/vehicle/janicart.dm +++ b/code/modules/vehicle/janicart.dm @@ -93,4 +93,4 @@ mybag.forceMove(get_turf(user)) user.put_in_hands(mybag) mybag = null - update_icon() \ No newline at end of file + update_icon() diff --git a/code/modules/vehicle/motorcycle.dm b/code/modules/vehicle/motorcycle.dm index ea165c1c8df..d208471fa74 100644 --- a/code/modules/vehicle/motorcycle.dm +++ b/code/modules/vehicle/motorcycle.dm @@ -26,4 +26,4 @@ if(dir == SOUTH) layer = ABOVE_MOB_LAYER else - layer = OBJ_LAYER \ No newline at end of file + layer = OBJ_LAYER diff --git a/code/modules/vehicle/secway.dm b/code/modules/vehicle/secway.dm index 48821a6e630..b3f022c5fe8 100644 --- a/code/modules/vehicle/secway.dm +++ b/code/modules/vehicle/secway.dm @@ -13,4 +13,4 @@ /obj/item/key/security desc = "A keyring with a small steel key, and a rubber stun baton accessory." - icon_state = "keysec" \ No newline at end of file + icon_state = "keysec" diff --git a/code/modules/vehicle/speedbike.dm b/code/modules/vehicle/speedbike.dm index 75e25c2c5e6..6de2b58d99a 100644 --- a/code/modules/vehicle/speedbike.dm +++ b/code/modules/vehicle/speedbike.dm @@ -47,4 +47,4 @@ /obj/vehicle/space/speedbike/red icon_state = "speedbike_red" - overlay_state = "cover_red" \ No newline at end of file + overlay_state = "cover_red" diff --git a/code/modules/vehicle/sportscar.dm b/code/modules/vehicle/sportscar.dm index 0f9543d4811..07352f3f65d 100644 --- a/code/modules/vehicle/sportscar.dm +++ b/code/modules/vehicle/sportscar.dm @@ -45,4 +45,4 @@ if(dir == SOUTH) layer = ABOVE_MOB_LAYER else - layer = OBJ_LAYER \ No newline at end of file + layer = OBJ_LAYER diff --git a/code/modules/vehicle/vehicle.dm b/code/modules/vehicle/vehicle.dm index cbaa3dc56e5..79021affa25 100644 --- a/code/modules/vehicle/vehicle.dm +++ b/code/modules/vehicle/vehicle.dm @@ -221,4 +221,4 @@ spaceworthy = TRUE /obj/vehicle/space/Process_Spacemove(direction) - return TRUE \ No newline at end of file + return TRUE diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm index c8214ed8283..0f0f85b088b 100644 --- a/code/modules/zombie/items.dm +++ b/code/modules/zombie/items.dm @@ -66,4 +66,4 @@ var/obj/item/organ/external/O = L.get_organ("head") if(O) O.droplimb() - return (BRUTELOSS) \ No newline at end of file + return (BRUTELOSS) diff --git a/code/modules/zombie/organs.dm b/code/modules/zombie/organs.dm index f88ed23c107..6ea40c90e96 100644 --- a/code/modules/zombie/organs.dm +++ b/code/modules/zombie/organs.dm @@ -96,4 +96,4 @@ to_chat(owner, "You are now a zombie! Do not seek to be cured, do not help any non-zombies in any way, do not harm your zombie brethren and spread the disease by killing others. You are a creature of hunger and violence.") /obj/item/organ/internal/zombie_infection/nodamage - causes_damage = FALSE \ No newline at end of file + causes_damage = FALSE diff --git a/code/world.dm b/code/world.dm index 8e8d2176172..c79588e7c6c 100644 --- a/code/world.dm +++ b/code/world.dm @@ -1,9 +1,9 @@ -// This file is just for the necessary /world definition -// Try looking in game/world.dm - -/world - mob = /mob/new_player - turf = /turf/space - area = /area/space - view = "15x15" - cache_lifespan = 0 //stops player uploaded stuff from being kept in the rsc past the current session +// This file is just for the necessary /world definition +// Try looking in game/world.dm + +/world + mob = /mob/new_player + turf = /turf/space + area = /area/space + view = "15x15" + cache_lifespan = 0 //stops player uploaded stuff from being kept in the rsc past the current session diff --git a/goon/code/datums/browserOutput.dm b/goon/code/datums/browserOutput.dm index f782e663d0a..f32b1d53610 100644 --- a/goon/code/datums/browserOutput.dm +++ b/goon/code/datums/browserOutput.dm @@ -310,4 +310,4 @@ var/to_chat_src return SSchat.queue(target, message, flag) -#undef MAX_COOKIE_LENGTH \ No newline at end of file +#undef MAX_COOKIE_LENGTH diff --git a/sound/items/PDA/__credits.dm b/sound/items/PDA/__credits.dm index 4814eb325b5..5012fea2b34 100644 --- a/sound/items/PDA/__credits.dm +++ b/sound/items/PDA/__credits.dm @@ -7,4 +7,4 @@ //ambicha4-short.ogg /*4-second excerpt from sound/ambience/ambicha4.ogg -*/ \ No newline at end of file +*/ diff --git a/tools/travis/README.MD b/tools/travis/README.MD new file mode 100644 index 00000000000..3079e9bfa43 --- /dev/null +++ b/tools/travis/README.MD @@ -0,0 +1,3 @@ +# Info + +All scripts utilised by Travis CI should be placed into this folder for the sake of maintainability, and knowing where stuff is \ No newline at end of file diff --git a/tools/travis/check_grep.sh b/tools/travis/check_grep.sh new file mode 100644 index 00000000000..22d02647bcf --- /dev/null +++ b/tools/travis/check_grep.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -euo pipefail + +#nb: must be bash to support shopt globstar +shopt -s globstar + +st=0 + +if grep -El '^\".+\" = \(.+\)' _maps/**/*.dmm; then + echo "ERROR: Non-TGM formatted map detected. Please convert it using Map Merger!" + st=1 +fi; +if grep -P 'step_[xy]' _maps/**/*.dmm; then + echo "ERROR: step_x/step_y variables detected in maps, please remove them." + st=1 +fi; +#### TODO: Uncomment after managing all globs #### +#if grep -P '^/*var/' code/**/*.dm; then + #echo "ERROR: Unmanaged global var use detected in code, please use the helpers." + #st=1 +#fi; +#### +nl=' +' +nl=$'\n' +while read f; do + t=$(tail -c2 "$f"; printf x); r1="${nl}$"; r2="${nl}${r1}" + if [[ ! ${t%x} =~ $r1 ]]; then + echo "file $f is missing a trailing newline" + st=1 + fi; +done < <(find . -type f -name '*.dm') + +exit $st \ No newline at end of file diff --git a/tools/travis/check_line_endings.py b/tools/travis/check_line_endings.py new file mode 100644 index 00000000000..86c2a8b77c7 --- /dev/null +++ b/tools/travis/check_line_endings.py @@ -0,0 +1,49 @@ + +#!/usr/bin/env python + +import os +import sys +import glob + +WINDOWS_NEWLINE = b'\r\n' + +FILES_TO_READ = [] +FILES_TO_READ.extend(glob.glob(r"**/*.dm", recursive=True)) +FILES_TO_READ.extend(glob.glob(r"**/*.dmm", recursive=True)) +FILES_TO_READ.extend(glob.glob(r"*.dme")) +#for i in FILES_TO_READ: +# if os.path.isdir(i): +# FILES_TO_READ.remove(i) + + +def _reader(filepath): + data = open(filepath, "rb") + return data + + +def main(): + filelist = [] + foundfiles = False + + for file in FILES_TO_READ: + data = _reader(file) + lines = data.readlines() + for line in lines: + if line.endswith(WINDOWS_NEWLINE): + filelist.append(file) + foundfiles = True + break + data.close() + + if not foundfiles: + print("No CRLF files found.") + sys.exit(0) + else: + print("Found files with suspected CRLF type.") + for i in filelist: + print(i) + sys.exit(1) + + +if __name__ == "__main__": + main() From 341c438ce3e8507de42fd98900a9c9581d290675 Mon Sep 17 00:00:00 2001 From: Tom Heeren Date: Tue, 17 Mar 2020 17:13:27 -0500 Subject: [PATCH 235/238] Adds the borg deathgasp sound to IPCs, and updates the IPC deathgasp message (#13116) --- code/modules/mob/living/carbon/human/species/machine.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/species/machine.dm b/code/modules/mob/living/carbon/human/species/machine.dm index 57353efc01d..e920cc38b90 100644 --- a/code/modules/mob/living/carbon/human/species/machine.dm +++ b/code/modules/mob/living/carbon/human/species/machine.dm @@ -19,7 +19,8 @@ burn_mod = 2.28 // So they take 50% extra damage from brute/burn overall tox_mod = 0 clone_mod = 0 - death_message = "gives one shrill beep before falling limp, their monitor flashing blue before completely shutting off..." + death_message = "gives a short series of shrill beeps, their chassis shuddering before falling limp, nonfunctional." + death_sounds = list('sound/voice/borg_deathsound.ogg') //I've made this a list in the event we add more sounds for dead robots. species_traits = list(IS_WHITELISTED, NO_BREATHE, NO_SCAN, NO_INTORGANS, NO_PAIN, NO_DNA, RADIMMUNE, VIRUSIMMUNE, NO_GERMS, NO_DECAY, NOTRANSSTING) //Computers that don't decay? What a lie! clothing_flags = HAS_UNDERWEAR | HAS_UNDERSHIRT | HAS_SOCKS From 6b7b9723ebccdf759ffd5bfde80b462607fb93cf Mon Sep 17 00:00:00 2001 From: AffectedArc07 Date: Tue, 17 Mar 2020 22:34:49 +0000 Subject: [PATCH 236/238] covids making it increasingly harder to buy coffee (#13136) --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index bb892b6e9e5..1770177310d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,7 @@ indent_size = 4 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +end_of_line = lf [*.yml] indent_style = space From 8e754873469f01687ccf81d0e6b34c53335dc5d8 Mon Sep 17 00:00:00 2001 From: Mitchell Date: Tue, 17 Mar 2020 17:43:11 -0500 Subject: [PATCH 237/238] Fixes CLRF --- .../crates_lockers/closets/secure/medical.dm | 592 +++++++++--------- 1 file changed, 296 insertions(+), 296 deletions(-) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm index b6fc0bd5134..dfca5f0fa70 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -1,296 +1,296 @@ -/obj/structure/closet/secure_closet/medical1 - name = "medicine closet" - desc = "Filled with medical junk." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/medical1/New() - ..() - new /obj/item/storage/box/autoinjectors(src) - new /obj/item/storage/box/syringes(src) - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/patch_packs(src) - new /obj/item/storage/box/iv_bags(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/glass/bottle/epinephrine(src) - new /obj/item/reagent_containers/glass/bottle/epinephrine(src) - new /obj/item/reagent_containers/glass/bottle/charcoal(src) - new /obj/item/reagent_containers/glass/bottle/charcoal(src) - - -/obj/structure/closet/secure_closet/medical2 - name = "anesthetic locker" - desc = "Used to knock people out." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/medical2/New() - ..() - new /obj/item/tank/anesthetic(src) - new /obj/item/tank/anesthetic(src) - new /obj/item/tank/anesthetic(src) - new /obj/item/clothing/mask/breath/medical(src) - new /obj/item/clothing/mask/breath/medical(src) - new /obj/item/clothing/mask/breath/medical(src) - - -/obj/structure/closet/secure_closet/medical3 - name = "medical doctor's locker" - req_access = list(ACCESS_SURGERY) - icon_state = "securemed1" - icon_closed = "securemed" - icon_locked = "securemed1" - icon_opened = "securemedopen" - icon_broken = "securemedbroken" - icon_off = "securemedoff" - -/obj/structure/closet/secure_closet/medical3/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/medic(src) - else - new /obj/item/storage/backpack/satchel_med(src) - new /obj/item/storage/backpack/duffel/medical(src) - new /obj/item/clothing/under/rank/medical(src) - new /obj/item/clothing/suit/storage/labcoat(src) - new /obj/item/clothing/shoes/white(src) - new /obj/item/radio/headset/headset_med(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/defibrillator/loaded(src) - new /obj/item/handheld_defibrillator(src) - new /obj/item/handheld_defibrillator(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/clothing/glasses/hud/health(src) - new /obj/item/clothing/shoes/sandal/white(src) - - -//Exam Room -/obj/structure/closet/secure_closet/exam - name = "exam room closet" - desc = "Filled with exam room materials." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/exam/New() - ..() - new /obj/item/storage/box/syringes(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/glasses/hud/health(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/clothing/accessory/stethoscope(src) - new /obj/item/flashlight/pen(src) - new /obj/item/storage/firstaid/regular(src) - new /obj/item/storage/firstaid/adv(src) - new /obj/item/storage/firstaid/brute(src) - new /obj/item/storage/firstaid/fire(src) - new /obj/item/storage/firstaid/o2(src) - new /obj/item/storage/firstaid/toxin(src) - - -// Psychiatrist's pill bottle -/obj/item/storage/pill_bottle/psychiatrist - name = "psychiatrist's pill bottle" - desc = "Contains various pills to calm or sedate patients." - wrapper_color = COLOR_PALE_BTL_GREEN - -/obj/item/storage/pill_bottle/psychiatrist/New() - ..() - new /obj/item/reagent_containers/food/pill/haloperidol(src) - new /obj/item/reagent_containers/food/pill/haloperidol(src) - new /obj/item/reagent_containers/food/pill/haloperidol(src) - new /obj/item/reagent_containers/food/pill/methamphetamine(src) - new /obj/item/reagent_containers/food/pill/methamphetamine(src) - new /obj/item/reagent_containers/food/pill/methamphetamine(src) - new /obj/item/reagent_containers/food/pill/patch/nicotine(src) - new /obj/item/reagent_containers/food/pill/patch/nicotine(src) - new /obj/item/reagent_containers/food/pill/patch/nicotine(src) - new /obj/item/reagent_containers/food/pill/hydrocodone(src) - new /obj/item/reagent_containers/food/pill/hydrocodone(src) - -/obj/structure/closet/secure_closet/psychiatrist - name = "psychiatrist's locker" - req_access = list(ACCESS_PSYCHIATRIST) - icon_state = "securemed1" - icon_closed = "securemed" - icon_locked = "securemed1" - icon_opened = "securemedopen" - icon_broken = "securemedbroken" - icon_off = "securemedoff" - -/obj/structure/closet/secure_closet/psychiatrist/New() - ..() - new /obj/item/clothing/suit/straight_jacket(src) - new /obj/item/reagent_containers/syringe(src) - new /obj/item/reagent_containers/glass/bottle/ether(src) - new /obj/item/storage/fancy/cigarettes/cigpack_med(src) - new /obj/item/storage/fancy/cigarettes/cigpack_med(src) - new /obj/item/storage/fancy/cigarettes/cigpack_med(src) - new /obj/item/storage/pill_bottle/psychiatrist(src) - new /obj/random/plushie(src) - for(var/i in 0 to 3) - var/candy = pick(subtypesof(/obj/item/reagent_containers/food/snacks/candy/fudge)) - new candy(src) - -/obj/structure/closet/secure_closet/CMO - name = "chief medical officer's locker" - req_access = list(ACCESS_CMO) - icon_state = "cmosecure1" - icon_closed = "cmosecure" - icon_locked = "cmosecure1" - icon_opened = "cmosecureopen" - icon_broken = "cmosecurebroken" - icon_off = "cmosecureoff" - -/obj/structure/closet/secure_closet/CMO/New() - ..() - if(prob(50)) - new /obj/item/storage/backpack/medic(src) - else - new /obj/item/storage/backpack/satchel_med(src) - new /obj/item/storage/backpack/duffel/medical(src) - new /obj/item/clothing/suit/bio_suit/cmo(src) - new /obj/item/clothing/head/bio_hood/cmo(src) - new /obj/item/clothing/shoes/white(src) - switch(pick("blue", "green", "purple")) - if("blue") - new /obj/item/clothing/under/rank/medical/blue(src) - new /obj/item/clothing/head/surgery/blue(src) - if("green") - new /obj/item/clothing/under/rank/medical/green(src) - new /obj/item/clothing/head/surgery/green(src) - if("purple") - new /obj/item/clothing/under/rank/medical/purple(src) - new /obj/item/clothing/head/surgery/purple(src) - new /obj/item/clothing/suit/storage/labcoat/cmo(src) - new /obj/item/clothing/under/rank/chief_medical_officer(src) - new /obj/item/clothing/suit/mantle/labcoat/chief_medical_officer(src) - new /obj/item/clothing/shoes/brown (src) - new /obj/item/radio/headset/heads/cmo(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/defibrillator/compact/loaded(src) - new /obj/item/handheld_defibrillator(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/flash(src) - new /obj/item/reagent_containers/hypospray/CMO(src) - new /obj/item/organ/internal/cyberimp/eyes/hud/medical(src) - new /obj/item/door_remote/chief_medical_officer(src) - new /obj/item/reagent_containers/food/drinks/mug/cmo(src) - new /obj/item/clothing/accessory/medal/medical(src) - - -/obj/structure/closet/secure_closet/animal - name = "animal control locker" - req_access = list(ACCESS_SURGERY) - -/obj/structure/closet/secure_closet/animal/New() - ..() - new /obj/item/assembly/signaler(src) - new /obj/item/radio/electropack(src) - new /obj/item/radio/electropack(src) - new /obj/item/radio/electropack(src) - - -/obj/structure/closet/secure_closet/chemical - name = "chemical closet" - desc = "Store dangerous chemicals in here." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_CHEMISTRY) - -/obj/structure/closet/secure_closet/chemical/New() - ..() - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/pillbottles(src) - new /obj/item/storage/box/patch_packs(src) - new /obj/item/storage/box/patch_packs(src) - - -/obj/structure/closet/secure_closet/medical_wall - name = "first aid closet" - desc = "It's a secure wall-mounted storage unit for first aid supplies." - icon_state = "medical_wall_locked" - icon_closed = "medical_wall_unlocked" - icon_locked = "medical_wall_locked" - icon_opened = "medical_wall_open" - icon_broken = "medical_wall_spark" - icon_off = "medical_wall_off" - anchored = 1 - density = 0 - wall_mounted = 1 - req_access = list(ACCESS_MEDICAL) - -/obj/structure/closet/secure_closet/medical_wall/update_icon() - if(broken) - icon_state = icon_broken - else - if(!opened) - if(locked) - icon_state = icon_locked - else - icon_state = icon_closed - else - icon_state = icon_opened - -/obj/structure/closet/secure_closet/paramedic - name = "paramedic EVA gear" - desc = "A locker with a Paramedic EVA suit." - icon_state = "medical1" - icon_closed = "medical" - icon_locked = "medical1" - icon_opened = "medicalopen" - icon_broken = "medicalbroken" - icon_off = "medicaloff" - req_access = list(ACCESS_PARAMEDIC) - -/obj/structure/closet/secure_closet/paramedic/New() - ..() - new /obj/item/clothing/suit/space/eva/paramedic(src) - new /obj/item/clothing/head/helmet/space/eva/paramedic(src) - new /obj/item/sensor_device(src) - new /obj/item/key/ambulance(src) - new /obj/item/pinpointer/crew(src) - new /obj/item/handheld_defibrillator(src) - -/obj/structure/closet/secure_closet/reagents - name = "chemical storage closet" - desc = "Store dangerous chemicals in here." - icon_state = "chemical1" - icon_closed = "chemical" - icon_locked = "chemical1" - icon_opened = "medicalopen" - icon_broken = "chemicalbroken" - icon_off = "chemicaloff" - req_access = list(ACCESS_CHEMISTRY) - -/obj/structure/closet/secure_closet/reagents/New() - ..() - new /obj/item/reagent_containers/glass/bottle/reagent/phenol(src) - new /obj/item/reagent_containers/glass/bottle/reagent/ammonia(src) - new /obj/item/reagent_containers/glass/bottle/reagent/oil(src) - new /obj/item/reagent_containers/glass/bottle/reagent/acetone(src) - new /obj/item/reagent_containers/glass/bottle/reagent/acid(src) - new /obj/item/reagent_containers/glass/bottle/reagent/diethylamine(src) +/obj/structure/closet/secure_closet/medical1 + name = "medicine closet" + desc = "Filled with medical junk." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/medical1/New() + ..() + new /obj/item/storage/box/autoinjectors(src) + new /obj/item/storage/box/syringes(src) + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/patch_packs(src) + new /obj/item/storage/box/iv_bags(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/glass/bottle/epinephrine(src) + new /obj/item/reagent_containers/glass/bottle/epinephrine(src) + new /obj/item/reagent_containers/glass/bottle/charcoal(src) + new /obj/item/reagent_containers/glass/bottle/charcoal(src) + + +/obj/structure/closet/secure_closet/medical2 + name = "anesthetic locker" + desc = "Used to knock people out." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/medical2/New() + ..() + new /obj/item/tank/anesthetic(src) + new /obj/item/tank/anesthetic(src) + new /obj/item/tank/anesthetic(src) + new /obj/item/clothing/mask/breath/medical(src) + new /obj/item/clothing/mask/breath/medical(src) + new /obj/item/clothing/mask/breath/medical(src) + + +/obj/structure/closet/secure_closet/medical3 + name = "medical doctor's locker" + req_access = list(ACCESS_SURGERY) + icon_state = "securemed1" + icon_closed = "securemed" + icon_locked = "securemed1" + icon_opened = "securemedopen" + icon_broken = "securemedbroken" + icon_off = "securemedoff" + +/obj/structure/closet/secure_closet/medical3/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/medic(src) + else + new /obj/item/storage/backpack/satchel_med(src) + new /obj/item/storage/backpack/duffel/medical(src) + new /obj/item/clothing/under/rank/medical(src) + new /obj/item/clothing/suit/storage/labcoat(src) + new /obj/item/clothing/shoes/white(src) + new /obj/item/radio/headset/headset_med(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/defibrillator/loaded(src) + new /obj/item/handheld_defibrillator(src) + new /obj/item/handheld_defibrillator(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/clothing/glasses/hud/health(src) + new /obj/item/clothing/shoes/sandal/white(src) + + +//Exam Room +/obj/structure/closet/secure_closet/exam + name = "exam room closet" + desc = "Filled with exam room materials." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/exam/New() + ..() + new /obj/item/storage/box/syringes(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/glasses/hud/health(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/clothing/accessory/stethoscope(src) + new /obj/item/flashlight/pen(src) + new /obj/item/storage/firstaid/regular(src) + new /obj/item/storage/firstaid/adv(src) + new /obj/item/storage/firstaid/brute(src) + new /obj/item/storage/firstaid/fire(src) + new /obj/item/storage/firstaid/o2(src) + new /obj/item/storage/firstaid/toxin(src) + + +// Psychiatrist's pill bottle +/obj/item/storage/pill_bottle/psychiatrist + name = "psychiatrist's pill bottle" + desc = "Contains various pills to calm or sedate patients." + wrapper_color = COLOR_PALE_BTL_GREEN + +/obj/item/storage/pill_bottle/psychiatrist/New() + ..() + new /obj/item/reagent_containers/food/pill/haloperidol(src) + new /obj/item/reagent_containers/food/pill/haloperidol(src) + new /obj/item/reagent_containers/food/pill/haloperidol(src) + new /obj/item/reagent_containers/food/pill/methamphetamine(src) + new /obj/item/reagent_containers/food/pill/methamphetamine(src) + new /obj/item/reagent_containers/food/pill/methamphetamine(src) + new /obj/item/reagent_containers/food/pill/patch/nicotine(src) + new /obj/item/reagent_containers/food/pill/patch/nicotine(src) + new /obj/item/reagent_containers/food/pill/patch/nicotine(src) + new /obj/item/reagent_containers/food/pill/hydrocodone(src) + new /obj/item/reagent_containers/food/pill/hydrocodone(src) + +/obj/structure/closet/secure_closet/psychiatrist + name = "psychiatrist's locker" + req_access = list(ACCESS_PSYCHIATRIST) + icon_state = "securemed1" + icon_closed = "securemed" + icon_locked = "securemed1" + icon_opened = "securemedopen" + icon_broken = "securemedbroken" + icon_off = "securemedoff" + +/obj/structure/closet/secure_closet/psychiatrist/New() + ..() + new /obj/item/clothing/suit/straight_jacket(src) + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/glass/bottle/ether(src) + new /obj/item/storage/fancy/cigarettes/cigpack_med(src) + new /obj/item/storage/fancy/cigarettes/cigpack_med(src) + new /obj/item/storage/fancy/cigarettes/cigpack_med(src) + new /obj/item/storage/pill_bottle/psychiatrist(src) + new /obj/random/plushie(src) + for(var/i in 0 to 3) + var/candy = pick(subtypesof(/obj/item/reagent_containers/food/snacks/candy/fudge)) + new candy(src) + +/obj/structure/closet/secure_closet/CMO + name = "chief medical officer's locker" + req_access = list(ACCESS_CMO) + icon_state = "cmosecure1" + icon_closed = "cmosecure" + icon_locked = "cmosecure1" + icon_opened = "cmosecureopen" + icon_broken = "cmosecurebroken" + icon_off = "cmosecureoff" + +/obj/structure/closet/secure_closet/CMO/New() + ..() + if(prob(50)) + new /obj/item/storage/backpack/medic(src) + else + new /obj/item/storage/backpack/satchel_med(src) + new /obj/item/storage/backpack/duffel/medical(src) + new /obj/item/clothing/suit/bio_suit/cmo(src) + new /obj/item/clothing/head/bio_hood/cmo(src) + new /obj/item/clothing/shoes/white(src) + switch(pick("blue", "green", "purple")) + if("blue") + new /obj/item/clothing/under/rank/medical/blue(src) + new /obj/item/clothing/head/surgery/blue(src) + if("green") + new /obj/item/clothing/under/rank/medical/green(src) + new /obj/item/clothing/head/surgery/green(src) + if("purple") + new /obj/item/clothing/under/rank/medical/purple(src) + new /obj/item/clothing/head/surgery/purple(src) + new /obj/item/clothing/suit/storage/labcoat/cmo(src) + new /obj/item/clothing/under/rank/chief_medical_officer(src) + new /obj/item/clothing/suit/mantle/labcoat/chief_medical_officer(src) + new /obj/item/clothing/shoes/brown (src) + new /obj/item/radio/headset/heads/cmo(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/defibrillator/compact/loaded(src) + new /obj/item/handheld_defibrillator(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/flash(src) + new /obj/item/reagent_containers/hypospray/CMO(src) + new /obj/item/organ/internal/cyberimp/eyes/hud/medical(src) + new /obj/item/door_remote/chief_medical_officer(src) + new /obj/item/reagent_containers/food/drinks/mug/cmo(src) + new /obj/item/clothing/accessory/medal/medical(src) + + +/obj/structure/closet/secure_closet/animal + name = "animal control locker" + req_access = list(ACCESS_SURGERY) + +/obj/structure/closet/secure_closet/animal/New() + ..() + new /obj/item/assembly/signaler(src) + new /obj/item/radio/electropack(src) + new /obj/item/radio/electropack(src) + new /obj/item/radio/electropack(src) + + +/obj/structure/closet/secure_closet/chemical + name = "chemical closet" + desc = "Store dangerous chemicals in here." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_CHEMISTRY) + +/obj/structure/closet/secure_closet/chemical/New() + ..() + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/pillbottles(src) + new /obj/item/storage/box/patch_packs(src) + new /obj/item/storage/box/patch_packs(src) + + +/obj/structure/closet/secure_closet/medical_wall + name = "first aid closet" + desc = "It's a secure wall-mounted storage unit for first aid supplies." + icon_state = "medical_wall_locked" + icon_closed = "medical_wall_unlocked" + icon_locked = "medical_wall_locked" + icon_opened = "medical_wall_open" + icon_broken = "medical_wall_spark" + icon_off = "medical_wall_off" + anchored = 1 + density = 0 + wall_mounted = 1 + req_access = list(ACCESS_MEDICAL) + +/obj/structure/closet/secure_closet/medical_wall/update_icon() + if(broken) + icon_state = icon_broken + else + if(!opened) + if(locked) + icon_state = icon_locked + else + icon_state = icon_closed + else + icon_state = icon_opened + +/obj/structure/closet/secure_closet/paramedic + name = "paramedic EVA gear" + desc = "A locker with a Paramedic EVA suit." + icon_state = "medical1" + icon_closed = "medical" + icon_locked = "medical1" + icon_opened = "medicalopen" + icon_broken = "medicalbroken" + icon_off = "medicaloff" + req_access = list(ACCESS_PARAMEDIC) + +/obj/structure/closet/secure_closet/paramedic/New() + ..() + new /obj/item/clothing/suit/space/eva/paramedic(src) + new /obj/item/clothing/head/helmet/space/eva/paramedic(src) + new /obj/item/sensor_device(src) + new /obj/item/key/ambulance(src) + new /obj/item/pinpointer/crew(src) + new /obj/item/handheld_defibrillator(src) + +/obj/structure/closet/secure_closet/reagents + name = "chemical storage closet" + desc = "Store dangerous chemicals in here." + icon_state = "chemical1" + icon_closed = "chemical" + icon_locked = "chemical1" + icon_opened = "medicalopen" + icon_broken = "chemicalbroken" + icon_off = "chemicaloff" + req_access = list(ACCESS_CHEMISTRY) + +/obj/structure/closet/secure_closet/reagents/New() + ..() + new /obj/item/reagent_containers/glass/bottle/reagent/phenol(src) + new /obj/item/reagent_containers/glass/bottle/reagent/ammonia(src) + new /obj/item/reagent_containers/glass/bottle/reagent/oil(src) + new /obj/item/reagent_containers/glass/bottle/reagent/acetone(src) + new /obj/item/reagent_containers/glass/bottle/reagent/acid(src) + new /obj/item/reagent_containers/glass/bottle/reagent/diethylamine(src) From bbfcd0de04513425293ae4d0744066b47125d2c1 Mon Sep 17 00:00:00 2001 From: Mitchs98 Date: Tue, 17 Mar 2020 18:02:36 -0500 Subject: [PATCH 238/238] Alphabetizes Access on Electronics. (#13120) * ALPHABETIZE!!! Yes. * Kills spaces, hopefully. * Fixes CLRF --- code/game/jobs/access.dm | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/code/game/jobs/access.dm b/code/game/jobs/access.dm index 9d772b52422..7f93fd9fef6 100644 --- a/code/game/jobs/access.dm +++ b/code/game/jobs/access.dm @@ -129,18 +129,15 @@ return list(ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER, ACCESS_SYNDICATE_COMMAND) /proc/get_all_accesses() - return list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_FORENSICS_LOCKERS, ACCESS_COURT, - ACCESS_MEDICAL, ACCESS_GENETICS, ACCESS_MORGUE, ACCESS_RD, - ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_CHEMISTRY, ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_MAINT_TUNNELS, - ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, - ACCESS_TELEPORTER, ACCESS_EVA, ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_ALL_PERSONAL_LOCKERS, - ACCESS_TECH_STORAGE, ACCESS_CHAPEL_OFFICE, ACCESS_ATMOSPHERICS, ACCESS_KITCHEN, - ACCESS_BAR, ACCESS_JANITOR, ACCESS_CREMATORIUM, ACCESS_ROBOTICS, ACCESS_CARGO, ACCESS_CONSTRUCTION, - ACCESS_HYDROPONICS, ACCESS_LIBRARY, ACCESS_LAWYER, ACCESS_VIROLOGY, ACCESS_PSYCHIATRIST, ACCESS_CMO, ACCESS_QM, ACCESS_CLOWN, ACCESS_MIME, ACCESS_SURGERY, - ACCESS_THEATRE, ACCESS_RESEARCH, ACCESS_MINING, ACCESS_MAILSORTING, - ACCESS_HEADS_VAULT, ACCESS_MINING_STATION, ACCESS_XENOBIOLOGY, ACCESS_CE, ACCESS_HOP, ACCESS_HOS, ACCESS_RC_ANNOUNCE, - ACCESS_KEYCARD_AUTH, ACCESS_TCOMSAT, ACCESS_GATEWAY, ACCESS_XENOARCH, ACCESS_PARAMEDIC, ACCESS_BLUESHIELD, ACCESS_MECHANIC,ACCESS_WEAPONS, - ACCESS_PILOT, ACCESS_NTREP, ACCESS_MAGISTRATE, ACCESS_MINERAL_STOREROOM, ACCESS_MINISAT, ACCESS_NETWORK) + return list(ACCESS_MINISAT, ACCESS_AI_UPLOAD, ACCESS_ARMORY, ACCESS_ATMOSPHERICS, ACCESS_BAR, ACCESS_SEC_DOORS, ACCESS_BLUESHIELD, + ACCESS_HEADS, ACCESS_CAPTAIN, ACCESS_CARGO, ACCESS_MAILSORTING, ACCESS_CHAPEL_OFFICE, ACCESS_CE, ACCESS_CHEMISTRY, ACCESS_CLOWN, ACCESS_CMO, + ACCESS_COURT, ACCESS_CONSTRUCTION, ACCESS_CREMATORIUM, ACCESS_JANITOR, ACCESS_ENGINE, ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS, ACCESS_FORENSICS_LOCKERS, + ACCESS_GENETICS, ACCESS_GATEWAY, ACCESS_BRIG, ACCESS_HOP, ACCESS_HOS, ACCESS_HYDROPONICS, ACCESS_CHANGE_IDS, ACCESS_KEYCARD_AUTH, ACCESS_KITCHEN, + ACCESS_LAWYER, ACCESS_LIBRARY, ACCESS_MAGISTRATE, ACCESS_MAINT_TUNNELS, ACCESS_HEADS_VAULT, ACCESS_MEDICAL, ACCESS_MECHANIC, ACCESS_MIME, + ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM, ACCESS_MORGUE, ACCESS_NETWORK, ACCESS_NTREP, ACCESS_PARAMEDIC, ACCESS_ALL_PERSONAL_LOCKERS, + ACCESS_ENGINE_EQUIP, ACCESS_PSYCHIATRIST, ACCESS_QM, ACCESS_RD, ACCESS_RC_ANNOUNCE, ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_RESEARCH, ACCESS_SECURITY, ACCESS_PILOT, + ACCESS_SURGERY, ACCESS_TECH_STORAGE, ACCESS_TELEPORTER, ACCESS_THEATRE, ACCESS_TCOMSAT, ACCESS_TOX_STORAGE, ACCESS_VIROLOGY, ACCESS_WEAPONS, ACCESS_XENOBIOLOGY, + ACCESS_XENOARCH) /proc/get_all_centcom_access() return list(ACCESS_CENT_GENERAL, ACCESS_CENT_LIVING, ACCESS_CENT_MEDICAL, ACCESS_CENT_SECURITY, ACCESS_CENT_STORAGE, ACCESS_CENT_SHUTTLES, ACCESS_CENT_TELECOMMS, ACCESS_CENT_TELEPORTER, ACCESS_CENT_SPECOPS, ACCESS_CENT_SPECOPS_COMMANDER, ACCESS_CENT_BLACKOPS, ACCESS_CENT_THUNDER, ACCESS_CENT_BRIDGE, ACCESS_CENT_COMMANDER) @@ -499,7 +496,7 @@ proc/get_all_job_icons() //For all existing HUD icons var/obj/item/card/id/I = src assignmentName = I.assignment rankName = I.rank - + var/job_icons = get_all_job_icons() var/centcom = get_all_centcom_jobs() @@ -513,5 +510,5 @@ proc/get_all_job_icons() //For all existing HUD icons return assignmentName if(rankName in job_icons) return rankName - + return "Unknown" //Return unknown if none of the above apply

    }jv(RJU12w-gk;o)0JUd37d!v- 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 WJ?M>FT&;(I59KEsk1OTPg8v6?z-^fT delta 255191 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

    fDiT`I`Z(ct>`S$g4+j{ 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 X>j6$;O8o!;5CZDT+Dac3tzZ2Y`6P}_ diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi index 15a4adb9f416b181bfea225866b636e022f8099c..bb7a1ce9188484dbe1f6f677e740cbec0b82dcbd 100644 GIT binary patch delta 19613 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 SZ2lU64ofpz(|Ti%oBsh)G5U)D From 24cdaeb41369766594855953821c895747f9ee62 Mon Sep 17 00:00:00 2001 From: Ty-Omaha Date: Wed, 6 Nov 2019 07:42:46 -0500 Subject: [PATCH 031/238] Revert "Updates Naval Officer Outfit" This reverts commit e6997dfdbe8812f8d6e04d4eaa2877f224929130. --- code/game/jobs/job/central.dm | 2 +- code/modules/clothing/under/miscellaneous.dm | 23 +++++++++---------- icons/mob/inhands/clothing_lefthand.dmi | Bin 91512 -> 92390 bytes icons/mob/inhands/clothing_righthand.dmi | Bin 88812 -> 89497 bytes icons/mob/species/drask/uniform.dmi | Bin 345141 -> 346838 bytes icons/mob/species/grey/uniform.dmi | Bin 298477 -> 297266 bytes icons/mob/species/vox/uniform.dmi | Bin 318344 -> 316925 bytes icons/mob/uniform.dmi | Bin 378630 -> 377385 bytes icons/obj/clothing/uniforms.dmi | Bin 93213 -> 92825 bytes 9 files changed, 12 insertions(+), 13 deletions(-) diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm index 695e4a1ec39..1d6ae0162a2 100644 --- a/code/game/jobs/job/central.dm +++ b/code/game/jobs/job/central.dm @@ -66,7 +66,7 @@ /datum/outfit/job/ntspecops name = "Special Operations Officer" jobtype = /datum/job/ntspecops - uniform = /obj/item/clothing/under/rank/centcom/officer/captain + uniform = /obj/item/clothing/under/rank/centcom_commander suit = /obj/item/clothing/suit/space/deathsquad/officer back = /obj/item/storage/backpack/security belt = /obj/item/storage/belt/military/assault diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index b82a04ad263..9e009014deb 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -96,21 +96,20 @@ item_color = "centcom" /obj/item/clothing/under/rank/centcom/officer - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Officers Uniform" - icon_state = "navy_gold" - item_state = "navy_gold" - item_color = "navy_gold" + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Lieutenant-Commander\" and bears \"N.C.V. Fearless CV-286\" on the left shoulder." + name = "\improper Nanotrasen Officers Uniform" + icon_state = "officer" + item_state = "g_suit" + item_color = "officer" displays_id = 0 flags_size = ONESIZEFITSALL - armor = list("melee" = 60, "bullet" = 60, "laser" = 60,"energy" = 60, "bomb" = 60, "bio" = 100, "rad" = 30, "fire" = 60, "acid" = 100) -/obj/item/clothing/under/rank/centcom/officer/captain - desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.A.S. Trurl \" on the left shoulder. Worn exclusively by officers of the Nanotrasen Navy. It's got exotic materials for protection." - name = "\improper Nanotrasen Naval Captains Uniform" - icon_state = "navy_gold" - item_state = "navy_gold" - item_color = "navy_gold" +/obj/item/clothing/under/rank/centcom/captain + desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Captain\" and bears \"N.C.V. Fearless CV-286\" on the left shoulder." + name = "\improper Nanotrasen Captains Uniform" + icon_state = "centcom" + item_state = "dg_suit" + item_color = "centcom" displays_id = 0 /obj/item/clothing/under/rank/centcom/blueshield diff --git a/icons/mob/inhands/clothing_lefthand.dmi b/icons/mob/inhands/clothing_lefthand.dmi index 69e602fc6529749c36d0be46e5eddb55940022f1..745dd19aac7104409f45a1586aff46c65fd802c1 100644 GIT binary patch delta 71961 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%0DwxuQ}C$@1$sK)wXZb2)RWpBwJsfWw0uKOEYNP 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